auto import from //depot/cupcake/@135843
diff --git a/core/java/android/accounts/AccountMonitor.java b/core/java/android/accounts/AccountMonitor.java
new file mode 100644
index 0000000..f21385e
--- /dev/null
+++ b/core/java/android/accounts/AccountMonitor.java
@@ -0,0 +1,174 @@
+/*
+ * Copyright (C) 2007 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 android.accounts;
+
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.ServiceConnection;
+import android.database.SQLException;
+import android.os.IBinder;
+import android.os.Process;
+import android.os.RemoteException;
+import android.util.Log;
+
+/**
+ * A helper class that calls back on the provided
+ * AccountMonitorListener with the set of current accounts both when
+ * it gets created and whenever the set changes. It does this by
+ * binding to the AccountsService and registering to receive the
+ * intent broadcast when the set of accounts is changed.  The
+ * connection to the accounts service is only made when it needs to
+ * fetch the current list of accounts (that is, when the
+ * AccountMonitor is first created, and when the intent is received).
+ */
+public class AccountMonitor extends BroadcastReceiver implements ServiceConnection {
+    private final Context mContext;
+    private final AccountMonitorListener mListener;
+    private boolean mClosed = false;
+    private int pending = 0;
+
+    // This thread runs in the background and runs the code to update accounts
+    // in the listener.
+    private class AccountUpdater extends Thread {
+        private IBinder mService;
+
+        public AccountUpdater(IBinder service) {
+            mService = service;
+        }
+
+        @Override
+        public void run() {
+            Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
+            IAccountsService accountsService = IAccountsService.Stub.asInterface(mService);
+            String[] accounts = null;
+            do {
+                try {
+                    accounts = accountsService.getAccounts();
+                } catch (RemoteException e) {
+                    // if the service was killed then the system will restart it and when it does we
+                    // will get another onServiceConnected, at which point we will do a notify.
+                    Log.w("AccountMonitor", "Remote exception when getting accounts", e);
+                    return;
+                }
+
+                synchronized (AccountMonitor.this) {
+                    --pending;
+                    if (pending == 0) {
+                        break;
+                    }
+                }
+            } while (true);
+
+            mContext.unbindService(AccountMonitor.this);
+
+            try {
+                mListener.onAccountsUpdated(accounts);
+            } catch (SQLException e) {
+                // Better luck next time.  If the problem was disk-full,
+                // the STORAGE_OK intent will re-trigger the update.
+                Log.e("AccountMonitor", "Can't update accounts", e);
+            }
+        }
+    }
+
+    /**
+     * Initializes the AccountMonitor and initiates a bind to the
+     * AccountsService to get the initial account list.  For 1.0,
+     * the "list" is always a single account.
+     *
+     * @param context the context we are running in
+     * @param listener the user to notify when the account set changes
+     */
+    public AccountMonitor(Context context, AccountMonitorListener listener) {
+        if (listener == null) {
+            throw new IllegalArgumentException("listener is null");
+        }
+
+        mContext = context;
+        mListener = listener;
+
+        // Register a broadcast receiver to monitor account changes
+        IntentFilter intentFilter = new IntentFilter();
+        intentFilter.addAction(AccountsServiceConstants.LOGIN_ACCOUNTS_CHANGED_ACTION);
+        intentFilter.addAction(Intent.ACTION_DEVICE_STORAGE_OK);  // To recover from disk-full.
+        mContext.registerReceiver(this, intentFilter);
+
+        // Send the listener the initial state now.
+        notifyListener();
+    }
+
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        notifyListener();
+    }
+
+    public void onServiceConnected(ComponentName className, IBinder service) {
+        // Create a background thread to update the accounts.
+        new AccountUpdater(service).start();
+    }
+
+    public void onServiceDisconnected(ComponentName className) {
+    }
+
+    private synchronized void notifyListener() {
+        if (pending == 0) {
+            // initiate the bind
+            if (!mContext.bindService(AccountsServiceConstants.SERVICE_INTENT,
+                                      this, Context.BIND_AUTO_CREATE)) {
+                // This is normal if GLS isn't part of this build.
+                Log.w("AccountMonitor",
+                      "Couldn't connect to "  +
+                      AccountsServiceConstants.SERVICE_INTENT +
+                      " (Missing service?)");
+            }
+        } else {
+            // already bound.  bindService will not trigger another
+            // call to onServiceConnected, so instead we make sure
+            // that the existing background thread will call
+            // getAccounts() after this function returns, by
+            // incrementing pending.
+            //
+            // Yes, this else clause contains only a comment.
+        }
+        ++pending;
+    }
+
+    /**
+     * calls close()
+     * @throws Throwable
+     */
+    @Override
+    protected void finalize() throws Throwable {
+        close();
+        super.finalize();
+    }
+
+    /**
+     * Unregisters the account receiver.  Consecutive calls to this
+     * method are harmless, but also do nothing.  Once this call is
+     * made no more notifications will occur.
+     */
+    public synchronized void close() {
+        if (!mClosed) {
+            mContext.unregisterReceiver(this);
+            mClosed = true;
+        }
+    }
+}
diff --git a/core/java/android/accounts/AccountMonitorListener.java b/core/java/android/accounts/AccountMonitorListener.java
new file mode 100644
index 0000000..d0bd9a9
--- /dev/null
+++ b/core/java/android/accounts/AccountMonitorListener.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2007 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 android.accounts;
+
+/**
+ * An interface that contains the callback used by the AccountMonitor
+ */
+public interface AccountMonitorListener {
+    /**
+     * This invoked when the AccountMonitor starts up and whenever the account
+     * set changes.
+     * @param currentAccounts the current accounts
+     */
+    void onAccountsUpdated(String[] currentAccounts);
+}
diff --git a/core/java/android/accounts/AccountsServiceConstants.java b/core/java/android/accounts/AccountsServiceConstants.java
new file mode 100644
index 0000000..b882e7b
--- /dev/null
+++ b/core/java/android/accounts/AccountsServiceConstants.java
@@ -0,0 +1,78 @@
+/*
+ * 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 android.accounts;
+
+import android.content.Intent;
+
+/**
+ * Miscellaneous constants used by the AccountsService and its
+ * clients.
+ */
+// TODO: These constants *could* come directly from the
+// IAccountsService interface, but that's not possible since the
+// aidl compiler doesn't let you define constants (yet.)
+public class AccountsServiceConstants {
+    /** This class is never instantiated. */
+    private AccountsServiceConstants() {
+    }
+
+    /**
+     * Action sent as a broadcast Intent by the AccountsService
+     * when accounts are added to and/or removed from the device's
+     * database, or when the primary account is changed.
+     */
+    public static final String LOGIN_ACCOUNTS_CHANGED_ACTION =
+        "android.accounts.LOGIN_ACCOUNTS_CHANGED";
+
+    /**
+     * Action sent as a broadcast Intent by the AccountsService
+     * when it starts up and no accounts are available (so some should be added).
+     */
+    public static final String LOGIN_ACCOUNTS_MISSING_ACTION =
+        "android.accounts.LOGIN_ACCOUNTS_MISSING";
+
+    /**
+     * Action on the intent used to bind to the IAccountsService interface. This
+     * is used for services that have multiple interfaces (allowing
+     * them to differentiate the interface intended, and return the proper
+     * Binder.)
+     */
+    private static final String ACCOUNTS_SERVICE_ACTION = "android.accounts.IAccountsService";
+
+    /*
+     * The intent uses a component in addition to the action to ensure the actual
+     * accounts service is bound to (a malicious third-party app could
+     * theoretically have a service with the same action).
+     */
+    /** The intent used to bind to the accounts service. */
+    public static final Intent SERVICE_INTENT =
+        new Intent()
+            .setClassName("com.google.android.googleapps",
+                    "com.google.android.googleapps.GoogleLoginService")
+            .setAction(ACCOUNTS_SERVICE_ACTION);
+
+    /**
+     * Checks whether the intent is to bind to the accounts service.
+     * 
+     * @param bindIntent The Intent used to bind to the service. 
+     * @return Whether the intent is to bind to the accounts service.
+     */
+    public static final boolean isForAccountsService(Intent bindIntent) {
+        String otherAction = bindIntent.getAction();
+        return otherAction != null && otherAction.equals(ACCOUNTS_SERVICE_ACTION);
+    }
+}
diff --git a/core/java/android/accounts/IAccountsService.aidl b/core/java/android/accounts/IAccountsService.aidl
new file mode 100644
index 0000000..dda513c
--- /dev/null
+++ b/core/java/android/accounts/IAccountsService.aidl
@@ -0,0 +1,54 @@
+/*
+ * 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 android.accounts;
+
+/**
+ * Central application service that allows querying the list of accounts.
+ */
+interface IAccountsService {
+    /**
+     * Gets the list of Accounts the user has previously logged
+     * in to.  Accounts are of the form "username@domain".
+     * <p>
+     * This method will return an empty array if the device doesn't
+     * know about any accounts (yet).
+     *
+     * @return The accounts.  The array will be zero-length if the
+     *         AccountsService doesn't know about any accounts yet.
+     */
+    String[] getAccounts();
+
+    /**
+     * This is an interim solution for bypassing a forgotten gesture on the
+     * unlock screen (it is hidden, please make sure it stays this way!). This
+     * will be *removed* when the unlock screen design supports additional
+     * authenticators.
+     * <p>
+     * The user will be presented with username and password fields that are
+     * called as parameters to this method. If true is returned, the user is
+     * able to define a new gesture and get back into the system. If false, the
+     * user can try again.
+     * 
+     * @param username The username entered.
+     * @param password The password entered.
+     * @return Whether to allow the user to bypass the lock screen and define a
+     *         new gesture.
+     * @hide (The package is already hidden, but just in case someone
+     *       unhides that, this should not be revealed.)
+     */
+    boolean shouldUnlock(String username, String password);
+}
diff --git a/core/java/android/accounts/package.html b/core/java/android/accounts/package.html
new file mode 100755
index 0000000..c9f96a6
--- /dev/null
+++ b/core/java/android/accounts/package.html
@@ -0,0 +1,5 @@
+<body>
+
+{@hide}
+
+</body>
diff --git a/core/java/android/annotation/SdkConstant.java b/core/java/android/annotation/SdkConstant.java
new file mode 100644
index 0000000..6ac70f0
--- /dev/null
+++ b/core/java/android/annotation/SdkConstant.java
@@ -0,0 +1,36 @@
+/*
+ * 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 android.annotation;
+
+import java.lang.annotation.Target;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Indicates a constant field value should be exported to be used in the SDK tools.
+ * @hide
+ */
+@Target({ ElementType.FIELD })
+@Retention(RetentionPolicy.SOURCE)
+public @interface SdkConstant {
+    public static enum SdkConstantType {
+        ACTIVITY_INTENT_ACTION, BROADCAST_INTENT_ACTION, SERVICE_ACTION, INTENT_CATEGORY;
+    }
+
+    SdkConstantType value();
+}
diff --git a/core/java/android/annotation/Widget.java b/core/java/android/annotation/Widget.java
new file mode 100644
index 0000000..6756cd7
--- /dev/null
+++ b/core/java/android/annotation/Widget.java
@@ -0,0 +1,37 @@
+/*
+ * 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 android.annotation;
+
+import java.lang.annotation.Target;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Indicates a class is a widget usable by application developers to create UI.
+ * <p>
+ * This must be used in cases where:
+ * <ul>
+ * <li>The widget is not in the package <code>android.widget</code></li>
+ * <li>The widget extends <code>android.view.ViewGroup</code></li>
+ * </ul>
+ * @hide
+ */
+@Target({ ElementType.TYPE })
+@Retention(RetentionPolicy.SOURCE)
+public @interface Widget {
+}
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
new file mode 100644
index 0000000..849a37d
--- /dev/null
+++ b/core/java/android/app/Activity.java
@@ -0,0 +1,3598 @@
+/*
+ * 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 android.app;
+
+import android.content.ComponentCallbacks;
+import android.content.ComponentName;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.content.pm.ActivityInfo;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.database.Cursor;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.drawable.Drawable;
+import android.media.AudioManager;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.os.Handler;
+import android.os.IBinder;
+import android.text.Selection;
+import android.text.SpannableStringBuilder;
+import android.text.method.TextKeyListener;
+import android.util.AttributeSet;
+import android.util.Config;
+import android.util.EventLog;
+import android.util.Log;
+import android.util.SparseArray;
+import android.view.ContextMenu;
+import android.view.ContextThemeWrapper;
+import android.view.KeyEvent;
+import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewManager;
+import android.view.Window;
+import android.view.WindowManager;
+import android.view.ContextMenu.ContextMenuInfo;
+import android.view.View.OnCreateContextMenuListener;
+import android.widget.AdapterView;
+
+import com.android.internal.policy.PolicyManager;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+
+/**
+ * An activity is a single, focused thing that the user can do.  Almost all
+ * activities interact with the user, so the Activity class takes care of
+ * creating a window for you in which you can place your UI with
+ * {@link #setContentView}.  While activities are often presented to the user
+ * as full-screen windows, they can also be used in other ways: as floating
+ * windows (via a theme with {@link android.R.attr#windowIsFloating} set)
+ * or embedded inside of another activity (using {@link ActivityGroup}).
+ *
+ * There are two methods almost all subclasses of Activity will implement:
+ * 
+ * <ul>
+ *     <li> {@link #onCreate} is where you initialize your activity.  Most
+ *     importantly, here you will usually call {@link #setContentView(int)}
+ *     with a layout resource defining your UI, and using {@link #findViewById}
+ *     to retrieve the widgets in that UI that you need to interact with
+ *     programmatically.
+ * 
+ *     <li> {@link #onPause} is where you deal with the user leaving your
+ *     activity.  Most importantly, any changes made by the user should at this
+ *     point be committed (usually to the
+ *     {@link android.content.ContentProvider} holding the data).
+ * </ul>
+ *
+ * <p>To be of use with {@link android.content.Context#startActivity Context.startActivity()}, all
+ * activity classes must have a corresponding
+ * {@link android.R.styleable#AndroidManifestActivity &lt;activity&gt;}
+ * declaration in their package's <code>AndroidManifest.xml</code>.</p>
+ * 
+ * <p>The Activity class is an important part of an application's overall lifecycle,
+ * and the way activities are launched and put together is a fundamental
+ * part of the platform's application model. For a detailed perspective on the structure of
+ * Android applications and lifecycles, please read the <em>Dev Guide</em> document on 
+ * <a href="{@docRoot}guide/topics/fundamentals.html">Application Fundamentals</a>.</p>
+ * 
+ * <p>Topics covered here:
+ * <ol>
+ * <li><a href="#ActivityLifecycle">Activity Lifecycle</a>
+ * <li><a href="#ConfigurationChanges">Configuration Changes</a>
+ * <li><a href="#StartingActivities">Starting Activities and Getting Results</a>
+ * <li><a href="#SavingPersistentState">Saving Persistent State</a>
+ * <li><a href="#Permissions">Permissions</a>
+ * <li><a href="#ProcessLifecycle">Process Lifecycle</a>
+ * </ol>
+ * 
+ * <a name="ActivityLifecycle"></a>
+ * <h3>Activity Lifecycle</h3>
+ *
+ * <p>Activities in the system are managed as an <em>activity stack</em>.
+ * When a new activity is started, it is placed on the top of the stack
+ * and becomes the running activity -- the previous activity always remains
+ * below it in the stack, and will not come to the foreground again until
+ * the new activity exits.</p>
+ * 
+ * <p>An activity has essentially four states:</p>
+ * <ul>
+ *     <li> If an activity in the foreground of the screen (at the top of
+ *         the stack),
+ *         it is <em>active</em> or  <em>running</em>. </li>
+ *     <li>If an activity has lost focus but is still visible (that is, a new non-full-sized
+ *         or transparent activity has focus on top of your activity), it 
+ *         is <em>paused</em>. A paused activity is completely alive (it
+ *         maintains all state and member information and remains attached to
+ *         the window manager), but can be killed by the system in extreme
+ *         low memory situations.
+ *     <li>If an activity is completely obscured by another activity,
+ *         it is <em>stopped</em>. It still retains all state and member information,
+ *         however, it is no longer visible to the user so its window is hidden
+ *         and it will often be killed by the system when memory is needed
+ *         elsewhere.</li>
+ *     <li>If an activity is paused or stopped, the system can drop the activity
+ *         from memory by either asking it to finish, or simply killing its
+ *         process.  When it is displayed again to the user, it must be
+ *         completely restarted and restored to its previous state.</li>
+ * </ul>
+ *
+ * <p>The following diagram shows the important state paths of an Activity.
+ * The square rectangles represent callback methods you can implement to
+ * perform operations when the Activity moves between states.  The colored
+ * ovals are major states the Activity can be in.</p>
+ * 
+ * <p><img src="../../../images/activity_lifecycle.png"
+ *      alt="State diagram for an Android Activity Lifecycle." border="0" /></p>
+ * 
+ * <p>There are three key loops you may be interested in monitoring within your
+ * activity:
+ * 
+ * <ul>
+ * <li>The <b>entire lifetime</b> of an activity happens between the first call
+ * to {@link android.app.Activity#onCreate} through to a single final call
+ * to {@link android.app.Activity#onDestroy}.  An activity will do all setup
+ * of "global" state in onCreate(), and release all remaining resources in
+ * onDestroy().  For example, if it has a thread running in the background
+ * to download data from the network, it may create that thread in onCreate()
+ * and then stop the thread in onDestroy().
+ * 
+ * <li>The <b>visible lifetime</b> of an activity happens between a call to
+ * {@link android.app.Activity#onStart} until a corresponding call to
+ * {@link android.app.Activity#onStop}.  During this time the user can see the
+ * activity on-screen, though it may not be in the foreground and interacting
+ * with the user.  Between these two methods you can maintain resources that
+ * are needed to show the activity to the user.  For example, you can register
+ * a {@link android.content.BroadcastReceiver} in onStart() to monitor for changes
+ * that impact your UI, and unregister it in onStop() when the user an no
+ * longer see what you are displaying.  The onStart() and onStop() methods
+ * can be called multiple times, as the activity becomes visible and hidden
+ * to the user.
+ * 
+ * <li>The <b>foreground lifetime</b> of an activity happens between a call to
+ * {@link android.app.Activity#onResume} until a corresponding call to
+ * {@link android.app.Activity#onPause}.  During this time the activity is
+ * in front of all other activities and interacting with the user.  An activity
+ * can frequently go between the resumed and paused states -- for example when
+ * the device goes to sleep, when an activity result is delivered, when a new
+ * intent is delivered -- so the code in these methods should be fairly
+ * lightweight.
+ * </ul>
+ * 
+ * <p>The entire lifecycle of an activity is defined by the following
+ * Activity methods.  All of these are hooks that you can override
+ * to do appropriate work when the activity changes state.  All
+ * activities will implement {@link android.app.Activity#onCreate}
+ * to do their initial setup; many will also implement
+ * {@link android.app.Activity#onPause} to commit changes to data and
+ * otherwise prepare to stop interacting with the user.  You should always
+ * call up to your superclass when implementing these methods.</p>
+ *
+ * </p>
+ * <pre class="prettyprint">
+ * public class Activity extends ApplicationContext {
+ *     protected void onCreate(Bundle savedInstanceState);
+ *
+ *     protected void onStart();
+ *     
+ *     protected void onRestart();
+ *
+ *     protected void onResume();
+ *
+ *     protected void onPause();
+ *
+ *     protected void onStop();
+ *
+ *     protected void onDestroy();
+ * }
+ * </pre>
+ *
+ * <p>In general the movement through an activity's lifecycle looks like
+ * this:</p>
+ *
+ * <table border="2" width="85%" align="center" frame="hsides" rules="rows">
+ *     <colgroup align="left" span="3" />
+ *     <colgroup align="left" />
+ *     <colgroup align="center" />
+ *     <colgroup align="center" />
+ *
+ *     <thead>
+ *     <tr><th colspan="3">Method</th> <th>Description</th> <th>Killable?</th> <th>Next</th></tr>
+ *     </thead>
+ *
+ *     <tbody>
+ *     <tr><th colspan="3" align="left" border="0">{@link android.app.Activity#onCreate onCreate()}</th>
+ *         <td>Called when the activity is first created.
+ *             This is where you should do all of your normal static set up:
+ *             create views, bind data to lists, etc.  This method also
+ *             provides you with a Bundle containing the activity's previously
+ *             frozen state, if there was one.
+ *             <p>Always followed by <code>onStart()</code>.</td>
+ *         <td align="center">No</td>
+ *         <td align="center"><code>onStart()</code></td>
+ *     </tr>
+ *
+ *     <tr><td rowspan="5" style="border-left: none; border-right: none;">&nbsp;&nbsp;&nbsp;&nbsp;</td>
+ *         <th colspan="2" align="left" border="0">{@link android.app.Activity#onRestart onRestart()}</th>
+ *         <td>Called after your activity has been stopped, prior to it being
+ *             started again.
+ *             <p>Always followed by <code>onStart()</code></td>
+ *         <td align="center">No</td>
+ *         <td align="center"><code>onStart()</code></td>
+ *     </tr>
+ *
+ *     <tr><th colspan="2" align="left" border="0">{@link android.app.Activity#onStart onStart()}</th>
+ *         <td>Called when the activity is becoming visible to the user.
+ *             <p>Followed by <code>onResume()</code> if the activity comes
+ *             to the foreground, or <code>onStop()</code> if it becomes hidden.</td>
+ *         <td align="center">No</td>
+ *         <td align="center"><code>onResume()</code> or <code>onStop()</code></td>
+ *     </tr>
+ *
+ *     <tr><td rowspan="2" style="border-left: none;">&nbsp;&nbsp;&nbsp;&nbsp;</td>
+ *         <th align="left" border="0">{@link android.app.Activity#onResume onResume()}</th>
+ *         <td>Called when the activity will start
+ *             interacting with the user.  At this point your activity is at
+ *             the top of the activity stack, with user input going to it.
+ *             <p>Always followed by <code>onPause()</code>.</td>
+ *         <td align="center">No</td>
+ *         <td align="center"><code>onPause()</code></td>
+ *     </tr>
+ *
+ *     <tr><th align="left" border="0">{@link android.app.Activity#onPause onPause()}</th>
+ *         <td>Called when the system is about to start resuming a previous
+ *             activity.  This is typically used to commit unsaved changes to
+ *             persistent data, stop animations and other things that may be consuming
+ *             CPU, etc.  Implementations of this method must be very quick because
+ *             the next activity will not be resumed until this method returns.
+ *             <p>Followed by either <code>onResume()</code> if the activity
+ *             returns back to the front, or <code>onStop()</code> if it becomes
+ *             invisible to the user.</td>
+ *         <td align="center"><font color="#800000"><strong>Yes</strong></font></td>
+ *         <td align="center"><code>onResume()</code> or<br>
+ *                 <code>onStop()</code></td>
+ *     </tr>
+ *
+ *     <tr><th colspan="2" align="left" border="0">{@link android.app.Activity#onStop onStop()}</th>
+ *         <td>Called when the activity is no longer visible to the user, because
+ *             another activity has been resumed and is covering this one.  This
+ *             may happen either because a new activity is being started, an existing
+ *             one is being brought in front of this one, or this one is being
+ *             destroyed.
+ *             <p>Followed by either <code>onRestart()</code> if
+ *             this activity is coming back to interact with the user, or
+ *             <code>onDestroy()</code> if this activity is going away.</td>
+ *         <td align="center"><font color="#800000"><strong>Yes</strong></font></td>
+ *         <td align="center"><code>onRestart()</code> or<br>
+ *                 <code>onDestroy()</code></td>
+ *     </tr>
+ *
+ *     <tr><th colspan="3" align="left" border="0">{@link android.app.Activity#onDestroy onDestroy()}</th>
+ *         <td>The final call you receive before your
+ *             activity is destroyed.  This can happen either because the
+ *             activity is finishing (someone called {@link Activity#finish} on
+ *             it, or because the system is temporarily destroying this
+ *             instance of the activity to save space.  You can distinguish
+ *             between these two scenarios with the {@link
+ *             Activity#isFinishing} method.</td>
+ *         <td align="center"><font color="#800000"><strong>Yes</strong></font></td>
+ *         <td align="center"><em>nothing</em></td>
+ *     </tr>
+ *     </tbody>
+ * </table>
+ *
+ * <p>Note the "Killable" column in the above table -- for those methods that
+ * are marked as being killable, after that method returns the process hosting the
+ * activity may killed by the system <em>at any time</em> without another line
+ * of its code being executed.  Because of this, you should use the
+ * {@link #onPause} method to write any persistent data (such as user edits)
+ * to storage.  In addition, the method
+ * {@link #onSaveInstanceState(Bundle)} is called before placing the activity
+ * in such a background state, allowing you to save away any dynamic instance
+ * state in your activity into the given Bundle, to be later received in
+ * {@link #onCreate} if the activity needs to be re-created.  
+ * See the <a href="#ProcessLifecycle">Process Lifecycle</a>
+ * section for more information on how the lifecycle of a process is tied
+ * to the activities it is hosting.  Note that it is important to save
+ * persistent data in {@link #onPause} instead of {@link #onSaveInstanceState}
+ * because the later is not part of the lifecycle callbacks, so will not
+ * be called in every situation as described in its documentation.</p>
+ *
+ * <p>For those methods that are not marked as being killable, the activity's
+ * process will not be killed by the system starting from the time the method
+ * is called and continuing after it returns.  Thus an activity is in the killable
+ * state, for example, between after <code>onPause()</code> to the start of
+ * <code>onResume()</code>.</p>
+ *
+ * <a name="ConfigurationChanges"></a>
+ * <h3>Configuration Changes</h3>
+ * 
+ * <p>If the configuration of the device (as defined by the
+ * {@link Configuration Resources.Configuration} class) changes,
+ * then anything displaying a user interface will need to update to match that
+ * configuration.  Because Activity is the primary mechanism for interacting
+ * with the user, it includes special support for handling configuration
+ * changes.</p>
+ * 
+ * <p>Unless you specify otherwise, a configuration change (such as a change
+ * in screen orientation, language, input devices, etc) will cause your
+ * current activity to be <em>destroyed</em>, going through the normal activity
+ * lifecycle process of {@link #onPause},
+ * {@link #onStop}, and {@link #onDestroy} as appropriate.  If the activity
+ * had been in the foreground or visible to the user, once {@link #onDestroy} is
+ * called in that instance then a new instance of the activity will be
+ * created, with whatever savedInstanceState the previous instance had generated
+ * from {@link #onSaveInstanceState}.</p>
+ * 
+ * <p>This is done because any application resource,
+ * including layout files, can change based on any configuration value.  Thus
+ * the only safe way to handle a configuration change is to re-retrieve all
+ * resources, including layouts, drawables, and strings.  Because activities
+ * must already know how to save their state and re-create themselves from
+ * that state, this is a convenient way to have an activity restart itself
+ * with a new configuration.</p>
+ * 
+ * <p>In some special cases, you may want to bypass restarting of your
+ * activity based on one or more types of configuration changes.  This is
+ * done with the {@link android.R.attr#configChanges android:configChanges}
+ * attribute in its manifest.  For any types of configuration changes you say
+ * that you handle there, you will receive a call to your current activity's
+ * {@link #onConfigurationChanged} method instead of being restarted.  If
+ * a configuration change involves any that you do not handle, however, the
+ * activity will still be restarted and {@link #onConfigurationChanged}
+ * will not be called.</p>
+ * 
+ * <a name="StartingActivities"></a>
+ * <h3>Starting Activities and Getting Results</h3>
+ *
+ * <p>The {@link android.app.Activity#startActivity}
+ * method is used to start a
+ * new activity, which will be placed at the top of the activity stack.  It
+ * takes a single argument, an {@link android.content.Intent Intent},
+ * which describes the activity
+ * to be executed.</p>
+ *
+ * <p>Sometimes you want to get a result back from an activity when it
+ * ends.  For example, you may start an activity that lets the user pick
+ * a person in a list of contacts; when it ends, it returns the person
+ * that was selected.  To do this, you call the
+ * {@link android.app.Activity#startActivityForResult(Intent, int)} 
+ * version with a second integer parameter identifying the call.  The result 
+ * will come back through your {@link android.app.Activity#onActivityResult}
+ * method.</p> 
+ *
+ * <p>When an activity exits, it can call
+ * {@link android.app.Activity#setResult(int)}
+ * to return data back to its parent.  It must always supply a result code,
+ * which can be the standard results RESULT_CANCELED, RESULT_OK, or any
+ * custom values starting at RESULT_FIRST_USER.  In addition, it can optionally
+ * return back an Intent containing any additional data it wants.  All of this
+ * information appears back on the
+ * parent's <code>Activity.onActivityResult()</code>, along with the integer
+ * identifier it originally supplied.</p>
+ *
+ * <p>If a child activity fails for any reason (such as crashing), the parent
+ * activity will receive a result with the code RESULT_CANCELED.</p>
+ *
+ * <pre class="prettyprint">
+ * public class MyActivity extends Activity {
+ *     ...
+ *
+ *     static final int PICK_CONTACT_REQUEST = 0;
+ *
+ *     protected boolean onKeyDown(int keyCode, KeyEvent event) {
+ *         if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER) {
+ *             // When the user center presses, let them pick a contact.
+ *             startActivityForResult(
+ *                 new Intent(Intent.ACTION_PICK,
+ *                 new Uri("content://contacts")),
+ *                 PICK_CONTACT_REQUEST);
+ *            return true;
+ *         }
+ *         return false;
+ *     }
+ *
+ *     protected void onActivityResult(int requestCode, int resultCode,
+ *             Intent data) {
+ *         if (requestCode == PICK_CONTACT_REQUEST) {
+ *             if (resultCode == RESULT_OK) {
+ *                 // A contact was picked.  Here we will just display it
+ *                 // to the user.
+ *                 startActivity(new Intent(Intent.ACTION_VIEW, data));
+ *             }
+ *         }
+ *     }
+ * }
+ * </pre>
+ *
+ * <a name="SavingPersistentState"></a>
+ * <h3>Saving Persistent State</h3>
+ *
+ * <p>There are generally two kinds of persistent state than an activity
+ * will deal with: shared document-like data (typically stored in a SQLite
+ * database using a {@linkplain android.content.ContentProvider content provider})
+ * and internal state such as user preferences.</p>
+ *
+ * <p>For content provider data, we suggest that activities use a
+ * "edit in place" user model.  That is, any edits a user makes are effectively
+ * made immediately without requiring an additional confirmation step.
+ * Supporting this model is generally a simple matter of following two rules:</p>
+ *
+ * <ul>
+ *     <li> <p>When creating a new document, the backing database entry or file for
+ *             it is created immediately.  For example, if the user chooses to write
+ *             a new e-mail, a new entry for that e-mail is created as soon as they
+ *             start entering data, so that if they go to any other activity after
+ *             that point this e-mail will now appear in the list of drafts.</p>
+ *     <li> <p>When an activity's <code>onPause()</code> method is called, it should
+ *             commit to the backing content provider or file any changes the user
+ *             has made.  This ensures that those changes will be seen by any other
+ *             activity that is about to run.  You will probably want to commit
+ *             your data even more aggressively at key times during your
+ *             activity's lifecycle: for example before starting a new
+ *             activity, before finishing your own activity, when the user
+ *             switches between input fields, etc.</p>
+ * </ul>
+ *
+ * <p>This model is designed to prevent data loss when a user is navigating
+ * between activities, and allows the system to safely kill an activity (because
+ * system resources are needed somewhere else) at any time after it has been
+ * paused.  Note this implies
+ * that the user pressing BACK from your activity does <em>not</em>
+ * mean "cancel" -- it means to leave the activity with its current contents
+ * saved away.  Cancelling edits in an activity must be provided through
+ * some other mechanism, such as an explicit "revert" or "undo" option.</p>
+ *
+ * <p>See the {@linkplain android.content.ContentProvider content package} for
+ * more information about content providers.  These are a key aspect of how
+ * different activities invoke and propagate data between themselves.</p>
+ *
+ * <p>The Activity class also provides an API for managing internal persistent state
+ * associated with an activity.  This can be used, for example, to remember
+ * the user's preferred initial display in a calendar (day view or week view)
+ * or the user's default home page in a web browser.</p>
+ *
+ * <p>Activity persistent state is managed
+ * with the method {@link #getPreferences},
+ * allowing you to retrieve and
+ * modify a set of name/value pairs associated with the activity.  To use
+ * preferences that are shared across multiple application components
+ * (activities, receivers, services, providers), you can use the underlying
+ * {@link Context#getSharedPreferences Context.getSharedPreferences()} method
+ * to retrieve a preferences
+ * object stored under a specific name.
+ * (Note that it is not possible to share settings data across application
+ * packages -- for that you will need a content provider.)</p>
+ *
+ * <p>Here is an excerpt from a calendar activity that stores the user's
+ * preferred view mode in its persistent settings:</p>
+ *
+ * <pre class="prettyprint">
+ * public class CalendarActivity extends Activity {
+ *     ...
+ *
+ *     static final int DAY_VIEW_MODE = 0;
+ *     static final int WEEK_VIEW_MODE = 1;
+ *
+ *     private SharedPreferences mPrefs;
+ *     private int mCurViewMode;
+ *
+ *     protected void onCreate(Bundle savedInstanceState) {
+ *         super.onCreate(savedInstanceState);
+ *
+ *         SharedPreferences mPrefs = getSharedPreferences();
+ *         mCurViewMode = mPrefs.getInt("view_mode" DAY_VIEW_MODE);
+ *     }
+ *
+ *     protected void onPause() {
+ *         super.onPause();
+ * 
+ *         SharedPreferences.Editor ed = mPrefs.edit();
+ *         ed.putInt("view_mode", mCurViewMode);
+ *         ed.commit();
+ *     }
+ * }
+ * </pre>
+ * 
+ * <a name="Permissions"></a>
+ * <h3>Permissions</h3>
+ * 
+ * <p>The ability to start a particular Activity can be enforced when it is
+ * declared in its
+ * manifest's {@link android.R.styleable#AndroidManifestActivity &lt;activity&gt;}
+ * tag.  By doing so, other applications will need to declare a corresponding
+ * {@link android.R.styleable#AndroidManifestUsesPermission &lt;uses-permission&gt;}
+ * element in their own manifest to be able to start that activity.
+ * 
+ * <p>See the <a href="{@docRoot}guide/topics/security/security.html">Security and Permissions</a>
+ * document for more information on permissions and security in general.
+ * 
+ * <a name="ProcessLifecycle"></a>
+ * <h3>Process Lifecycle</h3>
+ * 
+ * <p>The Android system attempts to keep application process around for as
+ * long as possible, but eventually will need to remove old processes when
+ * memory runs low.  As described in <a href="#ActivityLifecycle">Activity
+ * Lifecycle</a>, the decision about which process to remove is intimately
+ * tied to the state of the user's interaction with it.  In general, there
+ * are four states a process can be in based on the activities running in it,
+ * listed here in order of importance.  The system will kill less important
+ * processes (the last ones) before it resorts to killing more important
+ * processes (the first ones).
+ * 
+ * <ol>
+ * <li> <p>The <b>foreground activity</b> (the activity at the top of the screen
+ * that the user is currently interacting with) is considered the most important.
+ * Its process will only be killed as a last resort, if it uses more memory
+ * than is available on the device.  Generally at this point the device has
+ * reached a memory paging state, so this is required in order to keep the user
+ * interface responsive.
+ * <li> <p>A <b>visible activity</b> (an activity that is visible to the user
+ * but not in the foreground, such as one sitting behind a foreground dialog)
+ * is considered extremely important and will not be killed unless that is
+ * required to keep the foreground activity running.
+ * <li> <p>A <b>background activity</b> (an activity that is not visible to
+ * the user and has been paused) is no longer critical, so the system may
+ * safely kill its process to reclaim memory for other foreground or
+ * visible processes.  If its process needs to be killed, when the user navigates
+ * back to the activity (making it visible on the screen again), its
+ * {@link #onCreate} method will be called with the savedInstanceState it had previously
+ * supplied in {@link #onSaveInstanceState} so that it can restart itself in the same
+ * state as the user last left it.
+ * <li> <p>An <b>empty process</b> is one hosting no activities or other
+ * application components (such as {@link Service} or
+ * {@link android.content.BroadcastReceiver} classes).  These are killed very
+ * quickly by the system as memory becomes low.  For this reason, any
+ * background operation you do outside of an activity must be executed in the
+ * context of an activity BroadcastReceiver or Service to ensure that the system
+ * knows it needs to keep your process around.
+ * </ol>
+ * 
+ * <p>Sometimes an Activity may need to do a long-running operation that exists
+ * independently of the activity lifecycle itself.  An example may be a camera
+ * application that allows you to upload a picture to a web site.  The upload
+ * may take a long time, and the application should allow the user to leave
+ * the application will it is executing.  To accomplish this, your Activity
+ * should start a {@link Service} in which the upload takes place.  This allows
+ * the system to properly prioritize your process (considering it to be more
+ * important than other non-visible applications) for the duration of the
+ * upload, independent of whether the original activity is paused, stopped,
+ * or finished.
+ */
+public class Activity extends ContextThemeWrapper
+        implements LayoutInflater.Factory,
+        Window.Callback, KeyEvent.Callback,
+        OnCreateContextMenuListener, ComponentCallbacks {
+    private static final String TAG = "Activity";
+
+    /** Standard activity result: operation canceled. */
+    public static final int RESULT_CANCELED    = 0;
+    /** Standard activity result: operation succeeded. */
+    public static final int RESULT_OK           = -1;
+    /** Start of user-defined activity results. */
+    public static final int RESULT_FIRST_USER   = 1;
+
+    private static long sInstanceCount = 0;
+
+    private static final String WINDOW_HIERARCHY_TAG = "android:viewHierarchyState";
+    private static final String SAVED_DIALOG_IDS_KEY = "android:savedDialogIds";
+    private static final String SAVED_DIALOGS_TAG = "android:savedDialogs";
+    private static final String SAVED_DIALOG_KEY_PREFIX = "android:dialog_";
+    private static final String SAVED_SEARCH_DIALOG_KEY = "android:search_dialog";
+
+    private SparseArray<Dialog> mManagedDialogs;
+
+    // set by the thread after the constructor and before onCreate(Bundle savedInstanceState) is called.
+    private Instrumentation mInstrumentation;
+    private IBinder mToken;
+    /*package*/ String mEmbeddedID;
+    private Application mApplication;
+    private Intent mIntent;
+    private ComponentName mComponent;
+    /*package*/ ActivityInfo mActivityInfo;
+    /*package*/ ActivityThread mMainThread;
+    /*package*/ Object mLastNonConfigurationInstance;
+    /*package*/ HashMap<String,Object> mLastNonConfigurationChildInstances;
+    Activity mParent;
+    boolean mCalled;
+    private boolean mResumed;
+    private boolean mStopped;
+    boolean mFinished;
+    boolean mStartedActivity;
+    /*package*/ int mConfigChangeFlags;
+    /*package*/ Configuration mCurrentConfig;
+
+    private Window mWindow;
+
+    private WindowManager mWindowManager;
+    /*package*/ View mDecor = null;
+    /*package*/ boolean mWindowAdded = false;
+    /*package*/ boolean mVisibleFromServer = false;
+    /*package*/ boolean mVisibleFromClient = true;
+
+    private CharSequence mTitle;
+    private int mTitleColor = 0;
+
+    private static final class ManagedCursor {
+        ManagedCursor(Cursor cursor) {
+            mCursor = cursor;
+            mReleased = false;
+            mUpdated = false;
+        }
+
+        private final Cursor mCursor;
+        private boolean mReleased;
+        private boolean mUpdated;
+    }
+    private final ArrayList<ManagedCursor> mManagedCursors =
+        new ArrayList<ManagedCursor>();
+
+    // protected by synchronized (this) 
+    int mResultCode = RESULT_CANCELED;
+    Intent mResultData = null;
+
+    private boolean mTitleReady = false;
+
+    private int mDefaultKeyMode = DEFAULT_KEYS_DISABLE;
+    private SpannableStringBuilder mDefaultKeySsb = null;
+    
+    protected static final int[] FOCUSED_STATE_SET = {com.android.internal.R.attr.state_focused};
+
+    private Thread mUiThread;
+    private final Handler mHandler = new Handler();
+
+    public Activity() {
+        ++sInstanceCount;
+    }
+
+
+    @Override
+    protected void finalize() throws Throwable {
+        super.finalize();
+        --sInstanceCount;
+    }
+    
+    public static long getInstanceCount() {
+        return sInstanceCount;
+    }
+
+    /** Return the intent that started this activity. */
+    public Intent getIntent() {
+        return mIntent;
+    }
+
+    /** 
+     * Change the intent returned by {@link #getIntent}.  This holds a 
+     * reference to the given intent; it does not copy it.  Often used in 
+     * conjunction with {@link #onNewIntent}. 
+     *  
+     * @param newIntent The new Intent object to return from getIntent 
+     * 
+     * @see #getIntent
+     * @see #onNewIntent
+     */ 
+    public void setIntent(Intent newIntent) {
+        mIntent = newIntent;
+    }
+
+    /** Return the application that owns this activity. */
+    public final Application getApplication() {
+        return mApplication;
+    }
+
+    /** Is this activity embedded inside of another activity? */
+    public final boolean isChild() {
+        return mParent != null;
+    }
+    
+    /** Return the parent activity if this view is an embedded child. */
+    public final Activity getParent() {
+        return mParent;
+    }
+
+    /** Retrieve the window manager for showing custom windows. */
+    public WindowManager getWindowManager() {
+        return mWindowManager;
+    }
+
+    /**
+     * Retrieve the current {@link android.view.Window} for the activity.
+     * This can be used to directly access parts of the Window API that
+     * are not available through Activity/Screen.
+     * 
+     * @return Window The current window, or null if the activity is not
+     *         visual.
+     */
+    public Window getWindow() {
+        return mWindow;
+    }
+
+    /**
+     * Calls {@link android.view.Window#getCurrentFocus} on the
+     * Window of this Activity to return the currently focused view.
+     * 
+     * @return View The current View with focus or null.
+     * 
+     * @see #getWindow
+     * @see android.view.Window#getCurrentFocus
+     */
+    public View getCurrentFocus() {
+        return mWindow != null ? mWindow.getCurrentFocus() : null;
+    }
+
+    @Override
+    public int getWallpaperDesiredMinimumWidth() {
+        int width = super.getWallpaperDesiredMinimumWidth();
+        return width <= 0 ? getWindowManager().getDefaultDisplay().getWidth() : width;
+    }
+
+    @Override
+    public int getWallpaperDesiredMinimumHeight() {
+        int height = super.getWallpaperDesiredMinimumHeight();
+        return height <= 0 ? getWindowManager().getDefaultDisplay().getHeight() : height;
+    }
+
+    /**
+     * Called when the activity is starting.  This is where most initialization
+     * should go: calling {@link #setContentView(int)} to inflate the
+     * activity's UI, using {@link #findViewById} to programmatically interact
+     * with widgets in the UI, calling
+     * {@link #managedQuery(android.net.Uri , String[], String, String[], String)} to retrieve
+     * cursors for data being displayed, etc.
+     * 
+     * <p>You can call {@link #finish} from within this function, in
+     * which case onDestroy() will be immediately called without any of the rest
+     * of the activity lifecycle ({@link #onStart}, {@link #onResume},
+     * {@link #onPause}, etc) executing.
+     * 
+     * <p><em>Derived classes must call through to the super class's
+     * implementation of this method.  If they do not, an exception will be
+     * thrown.</em></p>
+     * 
+     * @param savedInstanceState If the activity is being re-initialized after
+     *     previously being shut down then this Bundle contains the data it most
+     *     recently supplied in {@link #onSaveInstanceState}.  <b><i>Note: Otherwise it is null.</i></b>
+     * 
+     * @see #onStart
+     * @see #onSaveInstanceState
+     * @see #onRestoreInstanceState
+     * @see #onPostCreate
+     */
+    protected void onCreate(Bundle savedInstanceState) {
+        mVisibleFromClient = mWindow.getWindowStyle().getBoolean(
+                com.android.internal.R.styleable.Window_windowNoDisplay, true);
+        mCalled = true;
+    }
+
+    /**
+     * The hook for {@link ActivityThread} to restore the state of this activity.
+     *
+     * Calls {@link #onSaveInstanceState(android.os.Bundle)} and
+     * {@link #restoreManagedDialogs(android.os.Bundle)}.
+     *
+     * @param savedInstanceState contains the saved state
+     */
+    final void performRestoreInstanceState(Bundle savedInstanceState) {
+        onRestoreInstanceState(savedInstanceState);
+        restoreManagedDialogs(savedInstanceState);
+        
+        // Also restore the state of a search dialog (if any)
+        // TODO more generic than just this manager
+        SearchManager searchManager = 
+            (SearchManager) getSystemService(Context.SEARCH_SERVICE);
+        searchManager.restoreSearchDialog(savedInstanceState, SAVED_SEARCH_DIALOG_KEY);
+    }
+
+    /**
+     * This method is called after {@link #onStart} when the activity is
+     * being re-initialized from a previously saved state, given here in
+     * <var>state</var>.  Most implementations will simply use {@link #onCreate}
+     * to restore their state, but it is sometimes convenient to do it here
+     * after all of the initialization has been done or to allow subclasses to
+     * decide whether to use your default implementation.  The default
+     * implementation of this method performs a restore of any view state that
+     * had previously been frozen by {@link #onSaveInstanceState}.
+     * 
+     * <p>This method is called between {@link #onStart} and
+     * {@link #onPostCreate}.
+     * 
+     * @param savedInstanceState the data most recently supplied in {@link #onSaveInstanceState}.
+     * 
+     * @see #onCreate
+     * @see #onPostCreate
+     * @see #onResume
+     * @see #onSaveInstanceState
+     */
+    protected void onRestoreInstanceState(Bundle savedInstanceState) {
+        if (mWindow != null) {
+            Bundle windowState = savedInstanceState.getBundle(WINDOW_HIERARCHY_TAG);
+            if (windowState != null) {
+                mWindow.restoreHierarchyState(windowState);
+            }
+        }
+    }
+    
+    /**
+     * Restore the state of any saved managed dialogs.
+     *
+     * @param savedInstanceState The bundle to restore from.
+     */
+    private void restoreManagedDialogs(Bundle savedInstanceState) {
+        final Bundle b = savedInstanceState.getBundle(SAVED_DIALOGS_TAG);
+        if (b == null) {
+            return;
+        }
+
+        final int[] ids = b.getIntArray(SAVED_DIALOG_IDS_KEY);
+        final int numDialogs = ids.length;
+        mManagedDialogs = new SparseArray<Dialog>(numDialogs);
+        for (int i = 0; i < numDialogs; i++) {
+            final Integer dialogId = ids[i];
+            Bundle dialogState = b.getBundle(savedDialogKeyFor(dialogId));
+            if (dialogState != null) {
+                final Dialog dialog = onCreateDialog(dialogId);
+                dialog.onRestoreInstanceState(dialogState);
+                mManagedDialogs.put(dialogId, dialog);
+            }
+        }
+    }
+
+    private String savedDialogKeyFor(int key) {
+        return SAVED_DIALOG_KEY_PREFIX + key;
+    }
+
+
+    /**
+     * Called when activity start-up is complete (after {@link #onStart}
+     * and {@link #onRestoreInstanceState} have been called).  Applications will
+     * generally not implement this method; it is intended for system
+     * classes to do final initialization after application code has run.
+     * 
+     * <p><em>Derived classes must call through to the super class's
+     * implementation of this method.  If they do not, an exception will be
+     * thrown.</em></p>
+     * 
+     * @param savedInstanceState If the activity is being re-initialized after
+     *     previously being shut down then this Bundle contains the data it most
+     *     recently supplied in {@link #onSaveInstanceState}.  <b><i>Note: Otherwise it is null.</i></b>
+     * @see #onCreate
+     */
+    protected void onPostCreate(Bundle savedInstanceState) {
+        if (!isChild()) {
+            mTitleReady = true;
+            onTitleChanged(getTitle(), getTitleColor());
+        }
+        mCalled = true;
+    }
+
+    /**
+     * Called after {@link #onCreate} &mdash; or after {@link #onRestart} when  
+     * the activity had been stopped, but is now again being displayed to the 
+	 * user.  It will be followed by {@link #onResume}.
+     *
+     * <p><em>Derived classes must call through to the super class's
+     * implementation of this method.  If they do not, an exception will be
+     * thrown.</em></p>
+     * 
+     * @see #onCreate
+     * @see #onStop
+     * @see #onResume
+     */
+    protected void onStart() {
+        mCalled = true;
+    }
+
+    /**
+     * Called after {@link #onStop} when the current activity is being
+     * re-displayed to the user (the user has navigated back to it).  It will
+     * be followed by {@link #onStart} and then {@link #onResume}.
+     *
+     * <p>For activities that are using raw {@link Cursor} objects (instead of
+     * creating them through
+     * {@link #managedQuery(android.net.Uri , String[], String, String[], String)},
+     * this is usually the place
+     * where the cursor should be requeried (because you had deactivated it in
+     * {@link #onStop}.
+     * 
+     * <p><em>Derived classes must call through to the super class's
+     * implementation of this method.  If they do not, an exception will be
+     * thrown.</em></p>
+     * 
+     * @see #onStop
+     * @see #onStart
+     * @see #onResume
+     */
+    protected void onRestart() {
+        mCalled = true;
+    }
+
+    /**
+     * Called after {@link #onRestoreInstanceState}, {@link #onRestart}, or
+     * {@link #onPause}, for your activity to start interacting with the user.
+     * This is a good place to begin animations, open exclusive-access devices
+     * (such as the camera), etc.
+     *
+     * <p>Keep in mind that onResume is not the best indicator that your activity
+     * is visible to the user; a system window such as the keyguard may be in
+     * front.  Use {@link #onWindowFocusChanged} to know for certain that your
+     * activity is visible to the user (for example, to resume a game).
+     *
+     * <p><em>Derived classes must call through to the super class's
+     * implementation of this method.  If they do not, an exception will be
+     * thrown.</em></p>
+     * 
+     * @see #onRestoreInstanceState
+     * @see #onRestart
+     * @see #onPostResume
+     * @see #onPause
+     */
+    protected void onResume() {
+        mCalled = true;
+    }
+
+    /**
+     * Called when activity resume is complete (after {@link #onResume} has
+     * been called). Applications will generally not implement this method;
+     * it is intended for system classes to do final setup after application
+     * resume code has run.
+     * 
+     * <p><em>Derived classes must call through to the super class's
+     * implementation of this method.  If they do not, an exception will be
+     * thrown.</em></p>
+     * 
+     * @see #onResume
+     */
+    protected void onPostResume() {
+        final Window win = getWindow();
+        if (win != null) win.makeActive();
+        mCalled = true;
+    }
+
+    /**
+     * This is called for activities that set launchMode to "singleTop" in
+     * their package, or if a client used the {@link Intent#FLAG_ACTIVITY_SINGLE_TOP}
+     * flag when calling {@link #startActivity}.  In either case, when the
+     * activity is re-launched while at the top of the activity stack instead
+     * of a new instance of the activity being started, onNewIntent() will be
+     * called on the existing instance with the Intent that was used to
+     * re-launch it. 
+     *  
+     * <p>An activity will always be paused before receiving a new intent, so 
+     * you can count on {@link #onResume} being called after this method. 
+     * 
+     * <p>Note that {@link #getIntent} still returns the original Intent.  You 
+     * can use {@link #setIntent} to update it to this new Intent. 
+     * 
+     * @param intent The new intent that was started for the activity. 
+     *  
+     * @see #getIntent
+     * @see #setIntent 
+     * @see #onResume 
+     */
+    protected void onNewIntent(Intent intent) {
+    }
+
+    /**
+     * The hook for {@link ActivityThread} to save the state of this activity.
+     *
+     * Calls {@link #onSaveInstanceState(android.os.Bundle)}
+     * and {@link #saveManagedDialogs(android.os.Bundle)}.
+     *
+     * @param outState The bundle to save the state to.
+     */
+    final void performSaveInstanceState(Bundle outState) {
+        onSaveInstanceState(outState);
+        saveManagedDialogs(outState);
+
+        // Also save the state of a search dialog (if any)
+        // TODO more generic than just this manager
+        SearchManager searchManager = 
+            (SearchManager) getSystemService(Context.SEARCH_SERVICE);
+        searchManager.saveSearchDialog(outState, SAVED_SEARCH_DIALOG_KEY);
+    }
+
+    /**
+     * Called to retrieve per-instance state from an activity before being killed
+     * so that the state can be restored in {@link #onCreate} or
+     * {@link #onRestoreInstanceState} (the {@link Bundle} populated by this method
+     * will be passed to both).
+     *
+     * <p>This method is called before an activity may be killed so that when it
+     * comes back some time in the future it can restore its state.  For example,
+     * if activity B is launched in front of activity A, and at some point activity
+     * A is killed to reclaim resources, activity A will have a chance to save the
+     * current state of its user interface via this method so that when the user
+     * returns to activity A, the state of the user interface can be restored
+     * via {@link #onCreate} or {@link #onRestoreInstanceState}.
+     *
+     * <p>Do not confuse this method with activity lifecycle callbacks such as
+     * {@link #onPause}, which is always called when an activity is being placed
+     * in the background or on its way to destruction, or {@link #onStop} which
+     * is called before destruction.  One example of when {@link #onPause} and
+     * {@link #onStop} is called and not this method is when a user navigates back
+     * from activity B to activity A: there is no need to call {@link #onSaveInstanceState}
+     * on B because that particular instance will never be restored, so the
+     * system avoids calling it.  An example when {@link #onPause} is called and
+     * not {@link #onSaveInstanceState} is when activity B is launched in front of activity A:
+     * the system may avoid calling {@link #onSaveInstanceState} on activity A if it isn't
+     * killed during the lifetime of B since the state of the user interface of
+     * A will stay intact.
+     *
+     * <p>The default implementation takes care of most of the UI per-instance
+     * state for you by calling {@link android.view.View#onSaveInstanceState()} on each
+     * view in the hierarchy that has an id, and by saving the id of the currently
+     * focused view (all of which is restored by the default implementation of
+     * {@link #onRestoreInstanceState}).  If you override this method to save additional
+     * information not captured by each individual view, you will likely want to
+     * call through to the default implementation, otherwise be prepared to save
+     * all of the state of each view yourself.
+     *
+     * <p>If called, this method will occur before {@link #onStop}.  There are
+     * no guarantees about whether it will occur before or after {@link #onPause}.
+     * 
+     * @param outState Bundle in which to place your saved state.
+     * 
+     * @see #onCreate
+     * @see #onRestoreInstanceState
+     * @see #onPause
+     */
+    protected void onSaveInstanceState(Bundle outState) {
+        outState.putBundle(WINDOW_HIERARCHY_TAG, mWindow.saveHierarchyState());
+    }
+
+    /**
+     * Save the state of any managed dialogs.
+     *
+     * @param outState place to store the saved state.
+     */
+    private void saveManagedDialogs(Bundle outState) {
+        if (mManagedDialogs == null) {
+            return;
+        }
+
+        final int numDialogs = mManagedDialogs.size();
+        if (numDialogs == 0) {
+            return;
+        }
+
+        Bundle dialogState = new Bundle();
+
+        int[] ids = new int[mManagedDialogs.size()];
+
+        // save each dialog's bundle, gather the ids
+        for (int i = 0; i < numDialogs; i++) {
+            final int key = mManagedDialogs.keyAt(i);
+            ids[i] = key;
+            final Dialog dialog = mManagedDialogs.valueAt(i);
+            dialogState.putBundle(savedDialogKeyFor(key), dialog.onSaveInstanceState());
+        }
+
+        dialogState.putIntArray(SAVED_DIALOG_IDS_KEY, ids);
+        outState.putBundle(SAVED_DIALOGS_TAG, dialogState);
+    }
+
+
+    /**
+     * Called as part of the activity lifecycle when an activity is going into
+     * the background, but has not (yet) been killed.  The counterpart to
+     * {@link #onResume}.
+     *
+     * <p>When activity B is launched in front of activity A, this callback will
+     * be invoked on A.  B will not be created until A's {@link #onPause} returns,
+     * so be sure to not do anything lengthy here.
+     *
+     * <p>This callback is mostly used for saving any persistent state the
+     * activity is editing, to present a "edit in place" model to the user and
+     * making sure nothing is lost if there are not enough resources to start
+     * the new activity without first killing this one.  This is also a good
+     * place to do things like stop animations and other things that consume a
+     * noticeable mount of CPU in order to make the switch to the next activity
+     * as fast as possible, or to close resources that are exclusive access
+     * such as the camera.
+     * 
+     * <p>In situations where the system needs more memory it may kill paused
+     * processes to reclaim resources.  Because of this, you should be sure
+     * that all of your state is saved by the time you return from
+     * this function.  In general {@link #onSaveInstanceState} is used to save
+     * per-instance state in the activity and this method is used to store
+     * global persistent data (in content providers, files, etc.)
+     * 
+     * <p>After receiving this call you will usually receive a following call
+     * to {@link #onStop} (after the next activity has been resumed and
+     * displayed), however in some cases there will be a direct call back to
+     * {@link #onResume} without going through the stopped state.
+     * 
+     * <p><em>Derived classes must call through to the super class's
+     * implementation of this method.  If they do not, an exception will be
+     * thrown.</em></p>
+     * 
+     * @see #onResume
+     * @see #onSaveInstanceState
+     * @see #onStop
+     */
+    protected void onPause() {
+        mCalled = true;
+    }
+
+    /**
+     * Called as part of the activity lifecycle when an activity is about to go
+     * into the background as the result of user choice.  For example, when the
+     * user presses the Home key, {@link #onUserLeaveHint} will be called, but
+     * when an incoming phone call causes the in-call Activity to be automatically
+     * brought to the foreground, {@link #onUserLeaveHint} will not be called on
+     * the activity being interrupted.  In cases when it is invoked, this method
+     * is called right before the activity's {@link #onPause} callback.
+     * 
+     * <p>This callback and {@link #onUserInteraction} are intended to help
+     * activities manage status bar notifications intelligently; specifically,
+     * for helping activities determine the proper time to cancel a notfication.
+     * 
+     * @see #onUserInteraction()
+     */
+    protected void onUserLeaveHint() {
+    }
+    
+    /**
+     * Generate a new thumbnail for this activity.  This method is called before
+     * pausing the activity, and should draw into <var>outBitmap</var> the
+     * imagery for the desired thumbnail in the dimensions of that bitmap.  It
+     * can use the given <var>canvas</var>, which is configured to draw into the
+     * bitmap, for rendering if desired.
+     * 
+     * <p>The default implementation renders the Screen's current view
+     * hierarchy into the canvas to generate a thumbnail.
+     * 
+     * <p>If you return false, the bitmap will be filled with a default
+     * thumbnail.
+     * 
+     * @param outBitmap The bitmap to contain the thumbnail.
+     * @param canvas Can be used to render into the bitmap.
+     * 
+     * @return Return true if you have drawn into the bitmap; otherwise after
+     *         you return it will be filled with a default thumbnail.
+     * 
+     * @see #onCreateDescription
+     * @see #onSaveInstanceState
+     * @see #onPause
+     */
+    public boolean onCreateThumbnail(Bitmap outBitmap, Canvas canvas) {
+        final View view = mDecor;
+        if (view == null) {
+            return false;
+        }
+
+        final int vw = view.getWidth();
+        final int vh = view.getHeight();
+        final int dw = outBitmap.getWidth();
+        final int dh = outBitmap.getHeight();
+
+        canvas.save();
+        canvas.scale(((float)dw)/vw, ((float)dh)/vh);
+        view.draw(canvas);
+        canvas.restore();
+
+        return true;
+    }
+
+    /**
+     * Generate a new description for this activity.  This method is called
+     * before pausing the activity and can, if desired, return some textual
+     * description of its current state to be displayed to the user.
+     * 
+     * <p>The default implementation returns null, which will cause you to
+     * inherit the description from the previous activity.  If all activities
+     * return null, generally the label of the top activity will be used as the
+     * description.
+     * 
+     * @return A description of what the user is doing.  It should be short and
+     *         sweet (only a few words).
+     * 
+     * @see #onCreateThumbnail
+     * @see #onSaveInstanceState
+     * @see #onPause
+     */
+    public CharSequence onCreateDescription() {
+        return null;
+    }
+
+    /**
+     * Called when you are no longer visible to the user.  You will next
+     * receive either {@link #onRestart}, {@link #onDestroy}, or nothing,
+     * depending on later user activity.
+     * 
+     * <p>Note that this method may never be called, in low memory situations
+     * where the system does not have enough memory to keep your activity's
+     * process running after its {@link #onPause} method is called.
+     * 
+     * <p><em>Derived classes must call through to the super class's
+     * implementation of this method.  If they do not, an exception will be
+     * thrown.</em></p>
+     * 
+     * @see #onRestart
+     * @see #onResume
+     * @see #onSaveInstanceState
+     * @see #onDestroy
+     */
+    protected void onStop() {
+        mCalled = true;
+    }
+
+    /**
+     * Perform any final cleanup before an activity is destroyed.  This can
+     * happen either because the activity is finishing (someone called
+     * {@link #finish} on it, or because the system is temporarily destroying
+     * this instance of the activity to save space.  You can distinguish
+     * between these two scenarios with the {@link #isFinishing} method.
+     * 
+     * <p><em>Note: do not count on this method being called as a place for
+     * saving data! For example, if an activity is editing data in a content
+     * provider, those edits should be committed in either {@link #onPause} or
+     * {@link #onSaveInstanceState}, not here.</em> This method is usually implemented to
+     * free resources like threads that are associated with an activity, so
+     * that a destroyed activity does not leave such things around while the
+     * rest of its application is still running.  There are situations where
+     * the system will simply kill the activity's hosting process without
+     * calling this method (or any others) in it, so it should not be used to
+     * do things that are intended to remain around after the process goes
+     * away.
+     * 
+     * <p><em>Derived classes must call through to the super class's
+     * implementation of this method.  If they do not, an exception will be
+     * thrown.</em></p>
+     * 
+     * @see #onPause
+     * @see #onStop
+     * @see #finish
+     * @see #isFinishing
+     */
+    protected void onDestroy() {
+        mCalled = true;
+
+        // dismiss any dialogs we are managing.
+        if (mManagedDialogs != null) {
+
+            final int numDialogs = mManagedDialogs.size();
+            for (int i = 0; i < numDialogs; i++) {
+                final Dialog dialog = mManagedDialogs.valueAt(i);
+                if (dialog.isShowing()) {
+                    dialog.dismiss();
+                }
+            }
+        }
+        
+        // also dismiss search dialog if showing
+        // TODO more generic than just this manager
+        SearchManager searchManager = 
+            (SearchManager) getSystemService(Context.SEARCH_SERVICE);
+        searchManager.stopSearch();
+
+        // close any cursors we are managing.
+        int numCursors = mManagedCursors.size();
+        for (int i = 0; i < numCursors; i++) {
+            ManagedCursor c = mManagedCursors.get(i);
+            if (c != null) {
+                c.mCursor.close();
+            }
+        }
+    }
+
+    /**
+     * Called by the system when the device configuration changes while your
+     * activity is running.  Note that this will <em>only</em> be called if
+     * you have selected configurations you would like to handle with the
+     * {@link android.R.attr#configChanges} attribute in your manifest.  If
+     * any configuration change occurs that is not selected to be reported
+     * by that attribute, then instead of reporting it the system will stop
+     * and restart the activity (to have it launched with the new
+     * configuration).
+     * 
+     * <p>At the time that this function has been called, your Resources
+     * object will have been updated to return resource values matching the
+     * new configuration.
+     * 
+     * @param newConfig The new device configuration.
+     */
+    public void onConfigurationChanged(Configuration newConfig) {
+        mCalled = true;
+        
+        // also update search dialog if showing
+        // TODO more generic than just this manager
+        SearchManager searchManager = 
+            (SearchManager) getSystemService(Context.SEARCH_SERVICE);
+        searchManager.onConfigurationChanged(newConfig);
+        
+        if (mWindow != null) {
+            // Pass the configuration changed event to the window
+            mWindow.onConfigurationChanged(newConfig);
+        }
+    }
+    
+    /**
+     * If this activity is being destroyed because it can not handle a
+     * configuration parameter being changed (and thus its
+     * {@link #onConfigurationChanged(Configuration)} method is
+     * <em>not</em> being called), then you can use this method to discover
+     * the set of changes that have occurred while in the process of being
+     * destroyed.  Note that there is no guarantee that these will be
+     * accurate (other changes could have happened at any time), so you should
+     * only use this as an optimization hint.
+     * 
+     * @return Returns a bit field of the configuration parameters that are
+     * changing, as defined by the {@link android.content.res.Configuration}
+     * class.
+     */
+    public int getChangingConfigurations() {
+        return mConfigChangeFlags;
+    }
+    
+    /**
+     * Retrieve the non-configuration instance data that was previously
+     * returned by {@link #onRetainNonConfigurationInstance()}.  This will
+     * be available from the initial {@link #onCreate} and
+     * {@link #onStart} calls to the new instance, allowing you to extract
+     * any useful dynamic state from the previous instance.
+     * 
+     * <p>Note that the data you retrieve here should <em>only</em> be used
+     * as an optimization for handling configuration changes.  You should always
+     * be able to handle getting a null pointer back, and an activity must
+     * still be able to restore itself to its previous state (through the
+     * normal {@link #onSaveInstanceState(Bundle)} mechanism) even if this
+     * function returns null.
+     * 
+     * @return Returns the object previously returned by
+     * {@link #onRetainNonConfigurationInstance()}.
+     */
+    public Object getLastNonConfigurationInstance() {
+        return mLastNonConfigurationInstance;
+    }
+    
+    /**
+     * Called by the system, as part of destroying an
+     * activity due to a configuration change, when it is known that a new
+     * instance will immediately be created for the new configuration.  You
+     * can return any object you like here, including the activity instance
+     * itself, which can later be retrieved by calling
+     * {@link #getLastNonConfigurationInstance()} in the new activity
+     * instance.
+     * 
+     * <p>This function is called purely as an optimization, and you must
+     * not rely on it being called.  When it is called, a number of guarantees
+     * will be made to help optimize configuration switching:
+     * <ul>
+     * <li> The function will be called between {@link #onStop} and
+     * {@link #onDestroy}.
+     * <li> A new instance of the activity will <em>always</em> be immediately
+     * created after this one's {@link #onDestroy()} is called.
+     * <li> The object you return here will <em>always</em> be available from
+     * the {@link #getLastNonConfigurationInstance()} method of the following
+     * activity instance as described there.
+     * </ul>
+     * 
+     * <p>These guarantees are designed so that an activity can use this API
+     * to propagate extensive state from the old to new activity instance, from
+     * loaded bitmaps, to network connections, to evenly actively running
+     * threads.  Note that you should <em>not</em> propagate any data that
+     * may change based on the configuration, including any data loaded from
+     * resources such as strings, layouts, or drawables.
+     * 
+     * @return Return any Object holding the desired state to propagate to the
+     * next activity instance.
+     */
+    public Object onRetainNonConfigurationInstance() {
+        return null;
+    }
+    
+    /**
+     * Retrieve the non-configuration instance data that was previously
+     * returned by {@link #onRetainNonConfigurationChildInstances()}.  This will
+     * be available from the initial {@link #onCreate} and
+     * {@link #onStart} calls to the new instance, allowing you to extract
+     * any useful dynamic state from the previous instance.
+     * 
+     * <p>Note that the data you retrieve here should <em>only</em> be used
+     * as an optimization for handling configuration changes.  You should always
+     * be able to handle getting a null pointer back, and an activity must
+     * still be able to restore itself to its previous state (through the
+     * normal {@link #onSaveInstanceState(Bundle)} mechanism) even if this
+     * function returns null.
+     * 
+     * @return Returns the object previously returned by
+     * {@link #onRetainNonConfigurationChildInstances()}
+     */
+    HashMap<String,Object> getLastNonConfigurationChildInstances() {
+        return mLastNonConfigurationChildInstances;
+    }
+    
+    /**
+     * This method is similar to {@link #onRetainNonConfigurationInstance()} except that
+     * it should return either a mapping from  child activity id strings to arbitrary objects,
+     * or null.  This method is intended to be used by Activity framework subclasses that control a
+     * set of child activities, such as ActivityGroup.  The same guarantees and restrictions apply
+     * as for {@link #onRetainNonConfigurationInstance()}.  The default implementation returns null.
+     */
+    HashMap<String,Object> onRetainNonConfigurationChildInstances() {
+        return null;
+    }
+    
+    public void onLowMemory() {
+        mCalled = true;
+    }
+    
+    /**
+     * Wrapper around
+     * {@link ContentResolver#query(android.net.Uri , String[], String, String[], String)}
+     * that gives the resulting {@link Cursor} to call
+     * {@link #startManagingCursor} so that the activity will manage its
+     * lifecycle for you.
+     * 
+     * @param uri The URI of the content provider to query.
+     * @param projection List of columns to return.
+     * @param selection SQL WHERE clause.
+     * @param sortOrder SQL ORDER BY clause.
+     * 
+     * @return The Cursor that was returned by query().
+     * 
+     * @see ContentResolver#query(android.net.Uri , String[], String, String[], String)
+     * @see #startManagingCursor
+     * @hide
+     */
+    public final Cursor managedQuery(Uri uri,
+                                     String[] projection,
+                                     String selection,
+                                     String sortOrder)
+    {
+        Cursor c = getContentResolver().query(uri, projection, selection, null, sortOrder);
+        if (c != null) {
+            startManagingCursor(c);
+        }
+        return c;
+    }
+
+    /**
+     * Wrapper around
+     * {@link ContentResolver#query(android.net.Uri , String[], String, String[], String)}
+     * that gives the resulting {@link Cursor} to call
+     * {@link #startManagingCursor} so that the activity will manage its
+     * lifecycle for you.
+     * 
+     * @param uri The URI of the content provider to query.
+     * @param projection List of columns to return.
+     * @param selection SQL WHERE clause.
+     * @param selectionArgs The arguments to selection, if any ?s are pesent
+     * @param sortOrder SQL ORDER BY clause.
+     * 
+     * @return The Cursor that was returned by query().
+     * 
+     * @see ContentResolver#query(android.net.Uri , String[], String, String[], String)
+     * @see #startManagingCursor
+     */
+    public final Cursor managedQuery(Uri uri,
+                                     String[] projection,
+                                     String selection,
+                                     String[] selectionArgs,
+                                     String sortOrder)
+    {
+        Cursor c = getContentResolver().query(uri, projection, selection, selectionArgs, sortOrder);
+        if (c != null) {
+            startManagingCursor(c);
+        }
+        return c;
+    }
+
+    /**
+     * Wrapper around {@link Cursor#commitUpdates()} that takes care of noting
+     * that the Cursor needs to be requeried.  You can call this method in
+     * {@link #onPause} or {@link #onStop} to have the system call
+     * {@link Cursor#requery} for you if the activity is later resumed.  This
+     * allows you to avoid determing when to do the requery yourself (which is
+     * required for the Cursor to see any data changes that were committed with
+     * it).
+     * 
+     * @param c The Cursor whose changes are to be committed.
+     * 
+     * @see #managedQuery(android.net.Uri , String[], String, String[], String)
+     * @see #startManagingCursor
+     * @see Cursor#commitUpdates()
+     * @see Cursor#requery
+     * @hide
+     */
+    @Deprecated
+    public void managedCommitUpdates(Cursor c) {
+        synchronized (mManagedCursors) {
+            final int N = mManagedCursors.size();
+            for (int i=0; i<N; i++) {
+                ManagedCursor mc = mManagedCursors.get(i);
+                if (mc.mCursor == c) {
+                    c.commitUpdates();
+                    mc.mUpdated = true;
+                    return;
+                }
+            }
+            throw new RuntimeException(
+                "Cursor " + c + " is not currently managed");
+        }
+    }
+
+    /**
+     * This method allows the activity to take care of managing the given
+     * {@link Cursor}'s lifecycle for you based on the activity's lifecycle.
+     * That is, when the activity is stopped it will automatically call
+     * {@link Cursor#deactivate} on the given Cursor, and when it is later restarted
+     * it will call {@link Cursor#requery} for you.  When the activity is
+     * destroyed, all managed Cursors will be closed automatically.
+     * 
+     * @param c The Cursor to be managed.
+     * 
+     * @see #managedQuery(android.net.Uri , String[], String, String[], String)
+     * @see #stopManagingCursor
+     */
+    public void startManagingCursor(Cursor c) {
+        synchronized (mManagedCursors) {
+            mManagedCursors.add(new ManagedCursor(c));
+        }
+    }
+
+    /**
+     * Given a Cursor that was previously given to
+     * {@link #startManagingCursor}, stop the activity's management of that
+     * cursor.
+     * 
+     * @param c The Cursor that was being managed.
+     * 
+     * @see #startManagingCursor
+     */
+    public void stopManagingCursor(Cursor c) {
+        synchronized (mManagedCursors) {
+            final int N = mManagedCursors.size();
+            for (int i=0; i<N; i++) {
+                ManagedCursor mc = mManagedCursors.get(i);
+                if (mc.mCursor == c) {
+                    mManagedCursors.remove(i);
+                    break;
+                }
+            }
+        }
+    }
+
+    /**
+     * Control whether this activity is required to be persistent.  By default
+     * activities are not persistent; setting this to true will prevent the
+     * system from stopping this activity or its process when running low on
+     * resources.
+     * 
+     * <p><em>You should avoid using this method</em>, it has severe negative
+     * consequences on how well the system can manage its resources.  A better
+     * approach is to implement an application service that you control with
+     * {@link Context#startService} and {@link Context#stopService}.
+     * 
+     * @param isPersistent Control whether the current activity must be
+     *                     persistent, true if so, false for the normal
+     *                     behavior.
+     */
+    public void setPersistent(boolean isPersistent) {
+        if (mParent == null) {
+            try {
+                ActivityManagerNative.getDefault()
+                    .setPersistent(mToken, isPersistent);
+            } catch (RemoteException e) {
+                // Empty
+            }
+        } else {
+            throw new RuntimeException("setPersistent() not yet supported for embedded activities");
+        }
+    }
+
+    /**
+     * Finds a view that was identified by the id attribute from the XML that
+     * was processed in {@link #onCreate}.
+     *
+     * @return The view if found or null otherwise.
+     */
+    public View findViewById(int id) {
+        return getWindow().findViewById(id);
+    }
+
+    /**
+     * Set the activity content from a layout resource.  The resource will be
+     * inflated, adding all top-level views to the activity.
+     * 
+     * @param layoutResID Resource ID to be inflated.
+     */
+    public void setContentView(int layoutResID) {
+        getWindow().setContentView(layoutResID);
+    }
+
+    /**
+     * Set the activity content to an explicit view.  This view is placed
+     * directly into the activity's view hierarchy.  It can itself be a complex
+     * view hierarhcy.
+     * 
+     * @param view The desired content to display.
+     */
+    public void setContentView(View view) {
+        getWindow().setContentView(view);
+    }
+
+    /**
+     * Set the activity content to an explicit view.  This view is placed
+     * directly into the activity's view hierarchy.  It can itself be a complex
+     * view hierarhcy.
+     * 
+     * @param view The desired content to display.
+     * @param params Layout parameters for the view.
+     */
+    public void setContentView(View view, ViewGroup.LayoutParams params) {
+        getWindow().setContentView(view, params);
+    }
+
+    /**
+     * Add an additional content view to the activity.  Added after any existing
+     * ones in the activity -- existing views are NOT removed.
+     * 
+     * @param view The desired content to display.
+     * @param params Layout parameters for the view.
+     */
+    public void addContentView(View view, ViewGroup.LayoutParams params) {
+        getWindow().addContentView(view, params);
+    }
+
+    /**
+     * Use with {@link #setDefaultKeyMode} to turn off default handling of
+     * keys.
+     * 
+     * @see #setDefaultKeyMode
+     */
+    static public final int DEFAULT_KEYS_DISABLE = 0;
+    /**
+     * Use with {@link #setDefaultKeyMode} to launch the dialer during default
+     * key handling.
+     * 
+     * @see #setDefaultKeyMode
+     */
+    static public final int DEFAULT_KEYS_DIALER = 1;
+    /**
+     * Use with {@link #setDefaultKeyMode} to execute a menu shortcut in
+     * default key handling.
+     * 
+     * <p>That is, the user does not need to hold down the menu key to execute menu shortcuts.
+     * 
+     * @see #setDefaultKeyMode
+     */
+    static public final int DEFAULT_KEYS_SHORTCUT = 2;
+    /**
+     * Use with {@link #setDefaultKeyMode} to specify that unhandled keystrokes
+     * will start an application-defined search.  (If the application or activity does not
+     * actually define a search, the the keys will be ignored.)
+     * 
+     * <p>See {@link android.app.SearchManager android.app.SearchManager} for more details.
+     * 
+     * @see #setDefaultKeyMode
+     */
+    static public final int DEFAULT_KEYS_SEARCH_LOCAL = 3;
+
+    /**
+     * Use with {@link #setDefaultKeyMode} to specify that unhandled keystrokes
+     * will start a global search (typically web search, but some platforms may define alternate
+     * methods for global search)
+     * 
+     * <p>See {@link android.app.SearchManager android.app.SearchManager} for more details.
+     * 
+     * @see #setDefaultKeyMode
+     */
+    static public final int DEFAULT_KEYS_SEARCH_GLOBAL = 4;
+
+    /**
+     * Select the default key handling for this activity.  This controls what
+     * will happen to key events that are not otherwise handled.  The default
+     * mode ({@link #DEFAULT_KEYS_DISABLE}) will simply drop them on the
+     * floor. Other modes allow you to launch the dialer
+     * ({@link #DEFAULT_KEYS_DIALER}), execute a shortcut in your options
+     * menu without requiring the menu key be held down
+     * ({@link #DEFAULT_KEYS_SHORTCUT}), or launch a search ({@link #DEFAULT_KEYS_SEARCH_LOCAL} 
+     * and {@link #DEFAULT_KEYS_SEARCH_GLOBAL}).
+     * 
+     * <p>Note that the mode selected here does not impact the default
+     * handling of system keys, such as the "back" and "menu" keys, and your
+     * activity and its views always get a first chance to receive and handle
+     * all application keys.
+     * 
+     * @param mode The desired default key mode constant.
+     * 
+     * @see #DEFAULT_KEYS_DISABLE
+     * @see #DEFAULT_KEYS_DIALER
+     * @see #DEFAULT_KEYS_SHORTCUT
+     * @see #DEFAULT_KEYS_SEARCH_LOCAL
+     * @see #DEFAULT_KEYS_SEARCH_GLOBAL
+     * @see #onKeyDown
+     */
+    public final void setDefaultKeyMode(int mode) {
+        mDefaultKeyMode = mode;
+        
+        // Some modes use a SpannableStringBuilder to track & dispatch input events
+        // This list must remain in sync with the switch in onKeyDown()
+        switch (mode) {
+        case DEFAULT_KEYS_DISABLE:
+        case DEFAULT_KEYS_SHORTCUT:
+            mDefaultKeySsb = null;      // not used in these modes
+            break;
+        case DEFAULT_KEYS_DIALER:
+        case DEFAULT_KEYS_SEARCH_LOCAL:
+        case DEFAULT_KEYS_SEARCH_GLOBAL:
+            mDefaultKeySsb = new SpannableStringBuilder();
+            Selection.setSelection(mDefaultKeySsb,0);
+            break;
+        default:
+            throw new IllegalArgumentException();
+        }
+    }
+
+    /**
+     * Called when a key was pressed down and not handled by any of the views
+     * inside of the activity. So, for example, key presses while the cursor 
+     * is inside a TextView will not trigger the event (unless it is a navigation
+     * to another object) because TextView handles its own key presses.
+     * 
+     * <p>If the focused view didn't want this event, this method is called.
+     *
+     * <p>The default implementation handles KEYCODE_BACK to stop the activity
+     * and go back, and other default key handling if configured with {@link #setDefaultKeyMode}.
+     * 
+     * @return Return <code>true</code> to prevent this event from being propagated
+     * further, or <code>false</code> to indicate that you have not handled 
+     * this event and it should continue to be propagated.
+     * @see #onKeyUp
+     * @see android.view.KeyEvent
+     */
+    public boolean onKeyDown(int keyCode, KeyEvent event)  {
+        if (keyCode == KeyEvent.KEYCODE_BACK && event.getRepeatCount() == 0) {
+            finish();
+            return true;
+        }
+        
+        if (mDefaultKeyMode == DEFAULT_KEYS_DISABLE) {
+            return false;
+        } else if (mDefaultKeyMode == DEFAULT_KEYS_SHORTCUT) {
+            return getWindow().performPanelShortcut(Window.FEATURE_OPTIONS_PANEL, 
+                                                    keyCode, event, Menu.FLAG_ALWAYS_PERFORM_CLOSE);
+        } else {
+            // Common code for DEFAULT_KEYS_DIALER & DEFAULT_KEYS_SEARCH_*
+            boolean clearSpannable = false;
+            boolean handled;
+            if ((event.getRepeatCount() != 0) || event.isSystem()) {
+                clearSpannable = true;
+                handled = false;
+            } else {
+                handled = TextKeyListener.getInstance().onKeyDown(null, mDefaultKeySsb, 
+                                                                  keyCode, event);
+                if (handled && mDefaultKeySsb.length() > 0) {
+                    // something useable has been typed - dispatch it now.
+
+                    final String str = mDefaultKeySsb.toString();
+                    clearSpannable = true;
+                    
+                    switch (mDefaultKeyMode) {
+                    case DEFAULT_KEYS_DIALER:
+                        Intent intent = new Intent(Intent.ACTION_DIAL,  Uri.parse("tel:" + str));
+                        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+                        startActivity(intent);    
+                        break;
+                    case DEFAULT_KEYS_SEARCH_LOCAL:
+                        startSearch(str, false, null, false);
+                        break;
+                    case DEFAULT_KEYS_SEARCH_GLOBAL:
+                        startSearch(str, false, null, true);
+                        break;
+                    }
+                }
+            }
+            if (clearSpannable) {
+                mDefaultKeySsb.clear();
+                mDefaultKeySsb.clearSpans();
+                Selection.setSelection(mDefaultKeySsb,0);
+            }
+            return handled;
+        }
+    }
+
+    /**
+     * Called when a key was released and not handled by any of the views
+     * inside of the activity. So, for example, key presses while the cursor 
+     * is inside a TextView will not trigger the event (unless it is a navigation
+     * to another object) because TextView handles its own key presses.
+     * 
+     * @return Return <code>true</code> to prevent this event from being propagated
+     * further, or <code>false</code> to indicate that you have not handled 
+     * this event and it should continue to be propagated. 
+     * @see #onKeyDown
+     * @see KeyEvent
+     */
+    public boolean onKeyUp(int keyCode, KeyEvent event) {
+        return false;
+    }
+
+    /**
+     * Default implementation of {@link KeyEvent.Callback#onKeyMultiple(int, int, KeyEvent)
+     * KeyEvent.Callback.onKeyMultiple()}: always returns false (doesn't handle
+     * the event).
+     */
+    public boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event) {
+        return false;
+    }
+    
+    /**
+     * Called when a touch screen event was not handled by any of the views
+     * under it.  This is most useful to process touch events that happen
+     * outside of your window bounds, where there is no view to receive it.
+     * 
+     * @param event The touch screen event being processed.
+     * 
+     * @return Return true if you have consumed the event, false if you haven't.
+     * The default implementation always returns false.
+     */
+    public boolean onTouchEvent(MotionEvent event) {
+        return false;
+    }
+    
+    /**
+     * Called when the trackball was moved and not handled by any of the
+     * views inside of the activity.  So, for example, if the trackball moves
+     * while focus is on a button, you will receive a call here because
+     * buttons do not normally do anything with trackball events.  The call
+     * here happens <em>before</em> trackball movements are converted to
+     * DPAD key events, which then get sent back to the view hierarchy, and
+     * will be processed at the point for things like focus navigation.
+     * 
+     * @param event The trackball event being processed.
+     * 
+     * @return Return true if you have consumed the event, false if you haven't.
+     * The default implementation always returns false.
+     */
+    public boolean onTrackballEvent(MotionEvent event) {
+        return false;
+    }
+    
+    /**
+     * Called whenever a key, touch, or trackball event is dispatched to the
+     * activity.  Implement this method if you wish to know that the user has
+     * interacted with the device in some way while your activity is running.
+     * This callback and {@link #onUserLeaveHint} are intended to help
+     * activities manage status bar notifications intelligently; specifically,
+     * for helping activities determine the proper time to cancel a notfication.
+     * 
+     * <p>All calls to your activity's {@link #onUserLeaveHint} callback will
+     * be accompanied by calls to {@link #onUserInteraction}.  This
+     * ensures that your activity will be told of relevant user activity such
+     * as pulling down the notification pane and touching an item there.
+     * 
+     * <p>Note that this callback will be invoked for the touch down action
+     * that begins a touch gesture, but may not be invoked for the touch-moved
+     * and touch-up actions that follow.
+     * 
+     * @see #onUserLeaveHint()
+     */
+    public void onUserInteraction() {
+    }
+    
+    public void onWindowAttributesChanged(WindowManager.LayoutParams params) {
+        // Update window manager if: we have a view, that view is
+        // attached to its parent (which will be a RootView), and
+        // this activity is not embedded.
+        if (mParent == null) {
+            View decor = mDecor;
+            if (decor != null && decor.getParent() != null) {
+                getWindowManager().updateViewLayout(decor, params);
+            }
+        }
+    }
+
+    public void onContentChanged() {
+    }
+
+    /**
+     * Called when the current {@link Window} of the activity gains or loses
+     * focus.  This is the best indicator of whether this activity is visible
+     * to the user.
+     * 
+     * <p>Note that this provides information what global focus state, which
+     * is managed independently of activity lifecycles.  As such, while focus
+     * changes will generally have some relation to lifecycle changes (an
+     * activity that is stopped will not generally get window focus), you
+     * should not rely on any particular order between the callbacks here and
+     * those in the other lifecycle methods such as {@link #onResume}.
+     * 
+     * <p>As a general rule, however, a resumed activity will have window
+     * focus...  unless it has displayed other dialogs or popups that take
+     * input focus, in which case the activity itself will not have focus
+     * when the other windows have it.  Likewise, the system may display
+     * system-level windows (such as the status bar notification panel or
+     * a system alert) which will temporarily take window input focus without
+     * pausing the foreground activity.
+     *
+     * @param hasFocus Whether the window of this activity has focus.
+     * 
+     * @see #hasWindowFocus()
+     * @see #onResume
+     */
+    public void onWindowFocusChanged(boolean hasFocus) {
+    }
+    
+    /**
+     * Returns true if this activity's <em>main</em> window currently has window focus.
+     * Note that this is not the same as the view itself having focus.
+     * 
+     * @return True if this activity's main window currently has window focus.
+     * 
+     * @see #onWindowAttributesChanged(android.view.WindowManager.LayoutParams)
+     */
+    public boolean hasWindowFocus() {
+        Window w = getWindow();
+        if (w != null) {
+            View d = w.getDecorView();
+            if (d != null) {
+                return d.hasWindowFocus();
+            }
+        }
+        return false;
+    }
+    
+    /**
+     * Called to process key events.  You can override this to intercept all 
+     * key events before they are dispatched to the window.  Be sure to call 
+     * this implementation for key events that should be handled normally.
+     * 
+     * @param event The key event.
+     * 
+     * @return boolean Return true if this event was consumed.
+     */
+    public boolean dispatchKeyEvent(KeyEvent event) {
+        onUserInteraction();
+        if (getWindow().superDispatchKeyEvent(event)) {
+            return true;
+        }
+        return event.dispatch(this);
+    }
+
+    /**
+     * Called to process touch screen events.  You can override this to
+     * intercept all touch screen events before they are dispatched to the
+     * window.  Be sure to call this implementation for touch screen events
+     * that should be handled normally.
+     * 
+     * @param ev The touch screen event.
+     * 
+     * @return boolean Return true if this event was consumed.
+     */
+    public boolean dispatchTouchEvent(MotionEvent ev) {
+        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
+            onUserInteraction();
+        }
+        if (getWindow().superDispatchTouchEvent(ev)) {
+            return true;
+        }
+        return onTouchEvent(ev);
+    }
+    
+    /**
+     * Called to process trackball events.  You can override this to
+     * intercept all trackball events before they are dispatched to the
+     * window.  Be sure to call this implementation for trackball events
+     * that should be handled normally.
+     * 
+     * @param ev The trackball event.
+     * 
+     * @return boolean Return true if this event was consumed.
+     */
+    public boolean dispatchTrackballEvent(MotionEvent ev) {
+        onUserInteraction();
+        if (getWindow().superDispatchTrackballEvent(ev)) {
+            return true;
+        }
+        return onTrackballEvent(ev);
+    }
+    
+    /**
+     * Default implementation of
+     * {@link android.view.Window.Callback#onCreatePanelView}
+     * for activities. This
+     * simply returns null so that all panel sub-windows will have the default
+     * menu behavior.
+     */
+    public View onCreatePanelView(int featureId) {
+        return null;
+    }
+
+    /**
+     * Default implementation of
+     * {@link android.view.Window.Callback#onCreatePanelMenu}
+     * for activities.  This calls through to the new
+     * {@link #onCreateOptionsMenu} method for the
+     * {@link android.view.Window#FEATURE_OPTIONS_PANEL} panel,
+     * so that subclasses of Activity don't need to deal with feature codes.
+     */
+    public boolean onCreatePanelMenu(int featureId, Menu menu) {
+        if (featureId == Window.FEATURE_OPTIONS_PANEL) {
+            return onCreateOptionsMenu(menu);
+        }
+        return false;
+    }
+
+    /**
+     * Default implementation of
+     * {@link android.view.Window.Callback#onPreparePanel}
+     * for activities.  This
+     * calls through to the new {@link #onPrepareOptionsMenu} method for the
+     * {@link android.view.Window#FEATURE_OPTIONS_PANEL}
+     * panel, so that subclasses of
+     * Activity don't need to deal with feature codes.
+     */
+    public boolean onPreparePanel(int featureId, View view, Menu menu) {
+        if (featureId == Window.FEATURE_OPTIONS_PANEL && menu != null) {
+            boolean goforit = onPrepareOptionsMenu(menu);
+            return goforit && menu.hasVisibleItems();
+        }
+        return true;
+    }
+
+    /**
+     * {@inheritDoc}
+     * 
+     * @return The default implementation returns true.
+     */
+    public boolean onMenuOpened(int featureId, Menu menu) {
+        return true;
+    }
+
+    /**
+     * Default implementation of
+     * {@link android.view.Window.Callback#onMenuItemSelected}
+     * for activities.  This calls through to the new
+     * {@link #onOptionsItemSelected} method for the
+     * {@link android.view.Window#FEATURE_OPTIONS_PANEL}
+     * panel, so that subclasses of
+     * Activity don't need to deal with feature codes.
+     */
+    public boolean onMenuItemSelected(int featureId, MenuItem item) {
+        switch (featureId) {
+            case Window.FEATURE_OPTIONS_PANEL:
+                // Put event logging here so it gets called even if subclass
+                // doesn't call through to superclass's implmeentation of each
+                // of these methods below
+                EventLog.writeEvent(50000, 0, item.getTitleCondensed());
+                return onOptionsItemSelected(item);
+                
+            case Window.FEATURE_CONTEXT_MENU:
+                EventLog.writeEvent(50000, 1, item.getTitleCondensed());
+                return onContextItemSelected(item);
+                
+            default:
+                return false;
+        }
+    }
+    
+    /**
+     * Default implementation of
+     * {@link android.view.Window.Callback#onPanelClosed(int, Menu)} for
+     * activities. This calls through to {@link #onOptionsMenuClosed(Menu)}
+     * method for the {@link android.view.Window#FEATURE_OPTIONS_PANEL} panel,
+     * so that subclasses of Activity don't need to deal with feature codes.
+     * For context menus ({@link Window#FEATURE_CONTEXT_MENU}), the
+     * {@link #onContextMenuClosed(Menu)} will be called.
+     */
+    public void onPanelClosed(int featureId, Menu menu) {
+        switch (featureId) {
+            case Window.FEATURE_OPTIONS_PANEL:
+                onOptionsMenuClosed(menu);
+                break;
+                
+            case Window.FEATURE_CONTEXT_MENU:
+                onContextMenuClosed(menu);
+                break;
+        }
+    }
+
+    /**
+     * Initialize the contents of the Activity's standard options menu.  You
+     * should place your menu items in to <var>menu</var>.
+     * 
+     * <p>This is only called once, the first time the options menu is
+     * displayed.  To update the menu every time it is displayed, see
+     * {@link #onPrepareOptionsMenu}.
+     * 
+     * <p>The default implementation populates the menu with standard system
+     * menu items.  These are placed in the {@link Menu#CATEGORY_SYSTEM} group so that 
+     * they will be correctly ordered with application-defined menu items. 
+     * Deriving classes should always call through to the base implementation. 
+     * 
+     * <p>You can safely hold on to <var>menu</var> (and any items created
+     * from it), making modifications to it as desired, until the next
+     * time onCreateOptionsMenu() is called.
+     * 
+     * <p>When you add items to the menu, you can implement the Activity's
+     * {@link #onOptionsItemSelected} method to handle them there.
+     * 
+     * @param menu The options menu in which you place your items.
+     * 
+     * @return You must return true for the menu to be displayed;
+     *         if you return false it will not be shown.
+     * 
+     * @see #onPrepareOptionsMenu
+     * @see #onOptionsItemSelected
+     */
+    public boolean onCreateOptionsMenu(Menu menu) {
+        if (mParent != null) {
+            return mParent.onCreateOptionsMenu(menu);
+        }
+        return true;
+    }
+
+    /**
+     * Prepare the Screen's standard options menu to be displayed.  This is
+     * called right before the menu is shown, every time it is shown.  You can
+     * use this method to efficiently enable/disable items or otherwise
+     * dynamically modify the contents.
+     * 
+     * <p>The default implementation updates the system menu items based on the
+     * activity's state.  Deriving classes should always call through to the
+     * base class implementation.
+     * 
+     * @param menu The options menu as last shown or first initialized by
+     *             onCreateOptionsMenu().
+     * 
+     * @return You must return true for the menu to be displayed;
+     *         if you return false it will not be shown.
+     * 
+     * @see #onCreateOptionsMenu
+     */
+    public boolean onPrepareOptionsMenu(Menu menu) {
+        if (mParent != null) {
+            return mParent.onPrepareOptionsMenu(menu);
+        }
+        return true;
+    }
+
+    /**
+     * This hook is called whenever an item in your options menu is selected.
+     * The default implementation simply returns false to have the normal
+     * processing happen (calling the item's Runnable or sending a message to
+     * its Handler as appropriate).  You can use this method for any items
+     * for which you would like to do processing without those other
+     * facilities.
+     * 
+     * <p>Derived classes should call through to the base class for it to
+     * perform the default menu handling.
+     * 
+     * @param item The menu item that was selected.
+     * 
+     * @return boolean Return false to allow normal menu processing to
+     *         proceed, true to consume it here.
+     * 
+     * @see #onCreateOptionsMenu
+     */
+    public boolean onOptionsItemSelected(MenuItem item) {
+        if (mParent != null) {
+            return mParent.onOptionsItemSelected(item);
+        }
+        return false;
+    }
+
+    /**
+     * This hook is called whenever the options menu is being closed (either by the user canceling
+     * the menu with the back/menu button, or when an item is selected).
+     *  
+     * @param menu The options menu as last shown or first initialized by
+     *             onCreateOptionsMenu().
+     */
+    public void onOptionsMenuClosed(Menu menu) {
+        if (mParent != null) {
+            mParent.onOptionsMenuClosed(menu);
+        }
+    }
+    
+    /**
+     * Programmatically opens the options menu. If the options menu is already
+     * open, this method does nothing.
+     */
+    public void openOptionsMenu() {
+        mWindow.openPanel(Window.FEATURE_OPTIONS_PANEL, null);
+    }
+    
+    /**
+     * Progammatically closes the options menu. If the options menu is already
+     * closed, this method does nothing.
+     */
+    public void closeOptionsMenu() {
+        mWindow.closePanel(Window.FEATURE_OPTIONS_PANEL);
+    }
+
+    /**
+     * Called when a context menu for the {@code view} is about to be shown.
+     * Unlike {@link #onCreateOptionsMenu(Menu)}, this will be called every
+     * time the context menu is about to be shown and should be populated for
+     * the view (or item inside the view for {@link AdapterView} subclasses,
+     * this can be found in the {@code menuInfo})).
+     * <p>
+     * Use {@link #onContextItemSelected(android.view.MenuItem)} to know when an
+     * item has been selected.
+     * <p>
+     * It is not safe to hold onto the context menu after this method returns.
+     * {@inheritDoc}
+     */
+    public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
+    }
+
+    /**
+     * Registers a context menu to be shown for the given view (multiple views
+     * can show the context menu). This method will set the
+     * {@link OnCreateContextMenuListener} on the view to this activity, so
+     * {@link #onCreateContextMenu(ContextMenu, View, ContextMenuInfo)} will be
+     * called when it is time to show the context menu.
+     * 
+     * @see #unregisterForContextMenu(View)
+     * @param view The view that should show a context menu.
+     */
+    public void registerForContextMenu(View view) {
+        view.setOnCreateContextMenuListener(this);
+    }
+    
+    /**
+     * Prevents a context menu to be shown for the given view. This method will remove the
+     * {@link OnCreateContextMenuListener} on the view.
+     * 
+     * @see #registerForContextMenu(View)
+     * @param view The view that should stop showing a context menu.
+     */
+    public void unregisterForContextMenu(View view) {
+        view.setOnCreateContextMenuListener(null);
+    }
+    
+    /**
+     * Programmatically opens the context menu for a particular {@code view}.
+     * The {@code view} should have been added via
+     * {@link #registerForContextMenu(View)}.
+     * 
+     * @param view The view to show the context menu for.
+     */
+    public void openContextMenu(View view) {
+        view.showContextMenu();
+    }
+    
+    /**
+     * Programmatically closes the most recently opened context menu, if showing.
+     */
+    public void closeContextMenu() {
+        mWindow.closePanel(Window.FEATURE_CONTEXT_MENU);
+    }
+    
+    /**
+     * This hook is called whenever an item in a context menu is selected. The
+     * default implementation simply returns false to have the normal processing
+     * happen (calling the item's Runnable or sending a message to its Handler
+     * as appropriate). You can use this method for any items for which you
+     * would like to do processing without those other facilities.
+     * <p>
+     * Use {@link MenuItem#getMenuInfo()} to get extra information set by the
+     * View that added this menu item.
+     * <p>
+     * Derived classes should call through to the base class for it to perform
+     * the default menu handling.
+     * 
+     * @param item The context menu item that was selected.
+     * @return boolean Return false to allow normal context menu processing to
+     *         proceed, true to consume it here.
+     */
+    public boolean onContextItemSelected(MenuItem item) {
+        if (mParent != null) {
+            return mParent.onContextItemSelected(item);
+        }
+        return false;
+    }
+
+    /**
+     * This hook is called whenever the context menu is being closed (either by
+     * the user canceling the menu with the back/menu button, or when an item is
+     * selected).
+     * 
+     * @param menu The context menu that is being closed.
+     */
+    public void onContextMenuClosed(Menu menu) {
+        if (mParent != null) {
+            mParent.onContextMenuClosed(menu);
+        }
+    }
+
+    /**
+     * Callback for creating dialogs that are managed (saved and restored) for you
+     * by the activity.
+     *
+     * If you use {@link #showDialog(int)}, the activity will call through to
+     * this method the first time, and hang onto it thereafter.  Any dialog
+     * that is created by this method will automatically be saved and restored
+     * for you, including whether it is showing.
+     *
+     * If you would like the activity to manage the saving and restoring dialogs
+     * for you, you should override this method and handle any ids that are
+     * passed to {@link #showDialog}.
+     *
+     * If you would like an opportunity to prepare your dialog before it is shown,
+     * override {@link #onPrepareDialog(int, Dialog)}.
+     *
+     * @param id The id of the dialog.
+     * @return The dialog
+     *
+     * @see #onPrepareDialog(int, Dialog)
+     * @see #showDialog(int)
+     * @see #dismissDialog(int)
+     * @see #removeDialog(int)
+     */
+    protected Dialog onCreateDialog(int id) {
+        return null;
+    }
+
+    /**
+     * Provides an opportunity to prepare a managed dialog before it is being
+     * shown.
+     * <p>
+     * Override this if you need to update a managed dialog based on the state
+     * of the application each time it is shown. For example, a time picker
+     * dialog might want to be updated with the current time. You should call
+     * through to the superclass's implementation. The default implementation
+     * will set this Activity as the owner activity on the Dialog.
+     * 
+     * @param id The id of the managed dialog.
+     * @param dialog The dialog.
+     * @see #onCreateDialog(int)
+     * @see #showDialog(int)
+     * @see #dismissDialog(int)
+     * @see #removeDialog(int)
+     */
+    protected void onPrepareDialog(int id, Dialog dialog) {
+        dialog.setOwnerActivity(this);
+    }
+
+    /**
+     * Show a dialog managed by this activity.  A call to {@link #onCreateDialog(int)}
+     * will be made with the same id the first time this is called for a given
+     * id.  From thereafter, the dialog will be automatically saved and restored.
+     *
+     * Each time a dialog is shown, {@link #onPrepareDialog(int, Dialog)} will
+     * be made to provide an opportunity to do any timely preparation.
+     *
+     * @param id The id of the managed dialog.
+     *
+     * @see #onCreateDialog(int)
+     * @see #onPrepareDialog(int, Dialog)
+     * @see #dismissDialog(int)
+     * @see #removeDialog(int)
+     */
+    public final void showDialog(int id) {
+        if (mManagedDialogs == null) {
+            mManagedDialogs = new SparseArray<Dialog>();
+        }
+        Dialog dialog = mManagedDialogs.get(id);
+        if (dialog == null) {
+            dialog = onCreateDialog(id);
+            if (dialog == null) {
+                throw new IllegalArgumentException("Activity#onCreateDialog did "
+                        + "not create a dialog for id " + id);
+            }
+            dialog.dispatchOnCreate(null);
+            mManagedDialogs.put(id, dialog);
+        }
+        
+        onPrepareDialog(id, dialog);
+        dialog.show();
+    }
+
+    /**
+     * Dismiss a dialog that was previously shown via {@link #showDialog(int)}.
+     *
+     * @param id The id of the managed dialog.
+     *
+     * @throws IllegalArgumentException if the id was not previously shown via
+     *   {@link #showDialog(int)}.
+     *
+     * @see #onCreateDialog(int)
+     * @see #onPrepareDialog(int, Dialog)
+     * @see #showDialog(int)
+     * @see #removeDialog(int)
+     */
+    public final void dismissDialog(int id) {
+        if (mManagedDialogs == null) {
+            throw missingDialog(id);
+
+        }
+        final Dialog dialog = mManagedDialogs.get(id);
+        if (dialog == null) {
+            throw missingDialog(id);
+        }
+        dialog.dismiss();
+    }
+
+    /**
+     * Creates an exception to throw if a user passed in a dialog id that is
+     * unexpected.
+     */
+    private IllegalArgumentException missingDialog(int id) {
+        return new IllegalArgumentException("no dialog with id " + id + " was ever "
+                + "shown via Activity#showDialog");
+    }
+
+    /**
+     * Removes any internal references to a dialog managed by this Activity.
+     * If the dialog is showing, it will dismiss it as part of the clean up.
+     *
+     * This can be useful if you know that you will never show a dialog again and
+     * want to avoid the overhead of saving and restoring it in the future.
+     *
+     * @param id The id of the managed dialog.
+     *
+     * @see #onCreateDialog(int)
+     * @see #onPrepareDialog(int, Dialog)
+     * @see #showDialog(int)
+     * @see #dismissDialog(int)
+     */
+    public final void removeDialog(int id) {
+
+        if (mManagedDialogs == null) {
+            return;
+        }
+
+        final Dialog dialog = mManagedDialogs.get(id);
+        if (dialog == null) {
+            return;
+        }
+
+        dialog.dismiss();
+        mManagedDialogs.remove(id);
+    }
+
+    /**
+     * This hook is called when the user signals the desire to start a search.
+     * 
+     * <p>You can use this function as a simple way to launch the search UI, in response to a 
+     * menu item, search button, or other widgets within your activity.  Unless overidden, 
+     * calling this function is the same as calling:
+     * <p>The default implementation simply calls 
+     * {@link #startSearch startSearch(null, false, null, false)}, launching a local search.
+     * 
+     * <p>You can override this function to force global search, e.g. in response to a dedicated
+     * search key, or to block search entirely (by simply returning false).
+     * 
+     * @return Returns true if search launched, false if activity blocks it
+     * 
+     * @see android.app.SearchManager
+     */
+    public boolean onSearchRequested() {
+        startSearch(null, false, null, false); 
+        return true;
+    }
+    
+    /**
+     * This hook is called to launch the search UI.
+     * 
+     * <p>It is typically called from onSearchRequested(), either directly from 
+     * Activity.onSearchRequested() or from an overridden version in any given 
+     * Activity.  If your goal is simply to activate search, it is preferred to call
+     * onSearchRequested(), which may have been overriden elsewhere in your Activity.  If your goal
+     * is to inject specific data such as context data, it is preferred to <i>override</i>
+     * onSearchRequested(), so that any callers to it will benefit from the override.
+     * 
+     * @param initialQuery Any non-null non-empty string will be inserted as 
+     * pre-entered text in the search query box.
+     * @param selectInitialQuery If true, the intial query will be preselected, which means that
+     * any further typing will replace it.  This is useful for cases where an entire pre-formed
+     * query is being inserted.  If false, the selection point will be placed at the end of the
+     * inserted query.  This is useful when the inserted query is text that the user entered,
+     * and the user would expect to be able to keep typing.  <i>This parameter is only meaningful
+     * if initialQuery is a non-empty string.</i>
+     * @param appSearchData An application can insert application-specific 
+     * context here, in order to improve quality or specificity of its own 
+     * searches.  This data will be returned with SEARCH intent(s).  Null if
+     * no extra data is required.
+     * @param globalSearch If false, this will only launch the search that has been specifically
+     * defined by the application (which is usually defined as a local search).  If no default 
+     * search is defined in the current application or activity, no search will be launched.
+     * If true, this will always launch a platform-global (e.g. web-based) search instead.
+     * 
+     * @see android.app.SearchManager
+     * @see #onSearchRequested
+     */
+    public void startSearch(String initialQuery, boolean selectInitialQuery, 
+            Bundle appSearchData, boolean globalSearch) {
+        // activate the search manager and start it up!
+        SearchManager searchManager = (SearchManager)
+                        getSystemService(Context.SEARCH_SERVICE);
+        searchManager.startSearch(initialQuery, selectInitialQuery, getComponentName(),
+                        appSearchData, globalSearch); 
+    }
+
+    /**
+     * Request that key events come to this activity. Use this if your
+     * activity has no views with focus, but the activity still wants
+     * a chance to process key events.
+     * 
+     * @see android.view.Window#takeKeyEvents
+     */
+    public void takeKeyEvents(boolean get) {
+        getWindow().takeKeyEvents(get);
+    }
+
+    /**
+     * Enable extended window features.  This is a convenience for calling
+     * {@link android.view.Window#requestFeature getWindow().requestFeature()}.
+     * 
+     * @param featureId The desired feature as defined in
+     *                  {@link android.view.Window}.
+     * @return Returns true if the requested feature is supported and now
+     *         enabled.
+     * 
+     * @see android.view.Window#requestFeature
+     */
+    public final boolean requestWindowFeature(int featureId) {
+        return getWindow().requestFeature(featureId);
+    }
+
+    /**
+     * Convenience for calling
+     * {@link android.view.Window#setFeatureDrawableResource}.
+     */
+    public final void setFeatureDrawableResource(int featureId, int resId) {
+        getWindow().setFeatureDrawableResource(featureId, resId);
+    }
+
+    /**
+     * Convenience for calling
+     * {@link android.view.Window#setFeatureDrawableUri}.
+     */
+    public final void setFeatureDrawableUri(int featureId, Uri uri) {
+        getWindow().setFeatureDrawableUri(featureId, uri);
+    }
+
+    /**
+     * Convenience for calling
+     * {@link android.view.Window#setFeatureDrawable(int, Drawable)}.
+     */
+    public final void setFeatureDrawable(int featureId, Drawable drawable) {
+        getWindow().setFeatureDrawable(featureId, drawable);
+    }
+
+    /**
+     * Convenience for calling
+     * {@link android.view.Window#setFeatureDrawableAlpha}.
+     */
+    public final void setFeatureDrawableAlpha(int featureId, int alpha) {
+        getWindow().setFeatureDrawableAlpha(featureId, alpha);
+    }
+
+    /**
+     * Convenience for calling
+     * {@link android.view.Window#getLayoutInflater}.
+     */
+    public LayoutInflater getLayoutInflater() {
+        return getWindow().getLayoutInflater();
+    }
+
+    /**
+     * Returns a {@link MenuInflater} with this context.
+     */
+    public MenuInflater getMenuInflater() {
+        return new MenuInflater(this);
+    }
+
+    @Override
+    protected void onApplyThemeResource(Resources.Theme theme,
+                                      int resid,
+                                      boolean first)
+    {
+        if (mParent == null) {
+            super.onApplyThemeResource(theme, resid, first);
+        } else {
+            try {
+                theme.setTo(mParent.getTheme());
+            } catch (Exception e) {
+                // Empty
+            }
+            theme.applyStyle(resid, false);
+        }
+    }
+
+    /**
+     * Launch an activity for which you would like a result when it finished.
+     * When this activity exits, your
+     * onActivityResult() method will be called with the given requestCode. 
+     * Using a negative requestCode is the same as calling 
+     * {@link #startActivity} (the activity is not launched as a sub-activity).
+     * 
+     * <p>Note that this method should only be used with Intent protocols
+     * that are defined to return a result.  In other protocols (such as
+     * {@link Intent#ACTION_MAIN} or {@link Intent#ACTION_VIEW}), you may
+     * not get the result when you expect.  For example, if the activity you
+     * are launching uses the singleTask launch mode, it will not run in your
+     * task and thus you will immediately receive a cancel result.
+     * 
+     * <p>As a special case, if you call startActivityForResult() with a requestCode 
+     * >= 0 during the initial onCreate(Bundle savedInstanceState)/onResume() of your
+     * activity, then your window will not be displayed until a result is 
+     * returned back from the started activity.  This is to avoid visible 
+     * flickering when redirecting to another activity. 
+     * 
+     * <p>This method throws {@link android.content.ActivityNotFoundException}
+     * if there was no Activity found to run the given Intent.
+     * 
+     * @param intent The intent to start.
+     * @param requestCode If >= 0, this code will be returned in
+     *                    onActivityResult() when the activity exits.
+     * 
+     * @throws android.content.ActivityNotFoundException
+     * 
+     * @see #startActivity 
+     */
+    public void startActivityForResult(Intent intent, int requestCode) {
+        if (mParent == null) {
+            Instrumentation.ActivityResult ar =
+                mInstrumentation.execStartActivity(
+                    this, mMainThread.getApplicationThread(), mToken, this,
+                    intent, requestCode);
+            if (ar != null) {
+                mMainThread.sendActivityResult(
+                    mToken, mEmbeddedID, requestCode, ar.getResultCode(),
+                    ar.getResultData());
+            }
+            if (requestCode >= 0) {
+                // If this start is requesting a result, we can avoid making
+                // the activity visible until the result is received.  Setting
+                // this code during onCreate(Bundle savedInstanceState) or onResume() will keep the
+                // activity hidden during this time, to avoid flickering.
+                // This can only be done when a result is requested because
+                // that guarantees we will get information back when the
+                // activity is finished, no matter what happens to it.
+                mStartedActivity = true;
+            }
+        } else {
+            mParent.startActivityFromChild(this, intent, requestCode);
+        }
+    }
+
+    /**
+     * Launch a new activity.  You will not receive any information about when
+     * the activity exits.  This implementation overrides the base version,
+     * providing information about
+     * the activity performing the launch.  Because of this additional
+     * information, the {@link Intent#FLAG_ACTIVITY_NEW_TASK} launch flag is not
+     * required; if not specified, the new activity will be added to the
+     * task of the caller.
+     * 
+     * <p>This method throws {@link android.content.ActivityNotFoundException}
+     * if there was no Activity found to run the given Intent.
+     * 
+     * @param intent The intent to start. 
+     * 
+     * @throws android.content.ActivityNotFoundException
+     * 
+     * @see #startActivityForResult 
+     */
+    @Override
+    public void startActivity(Intent intent) {
+        startActivityForResult(intent, -1);
+    }
+
+    /**
+     * A special variation to launch an activity only if a new activity
+     * instance is needed to handle the given Intent.  In other words, this is
+     * just like {@link #startActivityForResult(Intent, int)} except: if you are 
+     * using the {@link Intent#FLAG_ACTIVITY_SINGLE_TOP} flag, or
+     * singleTask or singleTop 
+     * {@link android.R.styleable#AndroidManifestActivity_launchMode launchMode},
+     * and the activity 
+     * that handles <var>intent</var> is the same as your currently running 
+     * activity, then a new instance is not needed.  In this case, instead of 
+     * the normal behavior of calling {@link #onNewIntent} this function will 
+     * return and you can handle the Intent yourself. 
+     * 
+     * <p>This function can only be called from a top-level activity; if it is
+     * called from a child activity, a runtime exception will be thrown.
+     * 
+     * @param intent The intent to start.
+     * @param requestCode If >= 0, this code will be returned in
+     *         onActivityResult() when the activity exits, as described in
+     *         {@link #startActivityForResult}.
+     * 
+     * @return If a new activity was launched then true is returned; otherwise
+     *         false is returned and you must handle the Intent yourself.
+     *  
+     * @see #startActivity
+     * @see #startActivityForResult
+     */
+    public boolean startActivityIfNeeded(Intent intent, int requestCode) {
+        if (mParent == null) {
+            int result = IActivityManager.START_RETURN_INTENT_TO_CALLER;
+            try {
+                result = ActivityManagerNative.getDefault()
+                    .startActivity(mMainThread.getApplicationThread(),
+                            intent, intent.resolveTypeIfNeeded(
+                                    getContentResolver()),
+                            null, 0,
+                            mToken, mEmbeddedID, requestCode, true, false);
+            } catch (RemoteException e) {
+                // Empty
+            }
+            
+            Instrumentation.checkStartActivityResult(result, intent);
+            
+            if (requestCode >= 0) {
+                // If this start is requesting a result, we can avoid making
+                // the activity visible until the result is received.  Setting
+                // this code during onCreate(Bundle savedInstanceState) or onResume() will keep the
+                // activity hidden during this time, to avoid flickering.
+                // This can only be done when a result is requested because
+                // that guarantees we will get information back when the
+                // activity is finished, no matter what happens to it.
+                mStartedActivity = true;
+            }
+            return result != IActivityManager.START_RETURN_INTENT_TO_CALLER;
+        }
+
+        throw new UnsupportedOperationException(
+            "startActivityIfNeeded can only be called from a top-level activity");
+    }
+
+    /**
+     * Special version of starting an activity, for use when you are replacing
+     * other activity components.  You can use this to hand the Intent off
+     * to the next Activity that can handle it.  You typically call this in
+     * {@link #onCreate} with the Intent returned by {@link #getIntent}.
+     * 
+     * @param intent The intent to dispatch to the next activity.  For
+     * correct behavior, this must be the same as the Intent that started
+     * your own activity; the only changes you can make are to the extras
+     * inside of it.
+     * 
+     * @return Returns a boolean indicating whether there was another Activity
+     * to start: true if there was a next activity to start, false if there
+     * wasn't.  In general, if true is returned you will then want to call
+     * finish() on yourself.
+     */
+    public boolean startNextMatchingActivity(Intent intent) {
+        if (mParent == null) {
+            try {
+                return ActivityManagerNative.getDefault()
+                    .startNextMatchingActivity(mToken, intent);
+            } catch (RemoteException e) {
+                // Empty
+            }
+            return false;
+        }
+
+        throw new UnsupportedOperationException(
+            "startNextMatchingActivity can only be called from a top-level activity");
+    }
+    
+    /**
+     * This is called when a child activity of this one calls its 
+     * {@link #startActivity} or {@link #startActivityForResult} method.
+     * 
+     * <p>This method throws {@link android.content.ActivityNotFoundException}
+     * if there was no Activity found to run the given Intent.
+     * 
+     * @param child The activity making the call.
+     * @param intent The intent to start.
+     * @param requestCode Reply request code.  < 0 if reply is not requested. 
+     * 
+     * @throws android.content.ActivityNotFoundException
+     * 
+     * @see #startActivity 
+     * @see #startActivityForResult 
+     */
+    public void startActivityFromChild(Activity child, Intent intent, 
+            int requestCode) {
+        Instrumentation.ActivityResult ar =
+            mInstrumentation.execStartActivity(
+                this, mMainThread.getApplicationThread(), mToken, child,
+                intent, requestCode);
+        if (ar != null) {
+            mMainThread.sendActivityResult(
+                mToken, child.mEmbeddedID, requestCode,
+                ar.getResultCode(), ar.getResultData());
+        }
+    }
+
+    /**
+     * Call this to set the result that your activity will return to its
+     * caller.
+     * 
+     * @param resultCode The result code to propagate back to the originating
+     *                   activity, often RESULT_CANCELED or RESULT_OK
+     * 
+     * @see #RESULT_CANCELED
+     * @see #RESULT_OK
+     * @see #RESULT_FIRST_USER
+     * @see #setResult(int, Intent)
+     */
+    public final void setResult(int resultCode) {
+        synchronized (this) {
+            mResultCode = resultCode;
+            mResultData = null;
+        }
+    }
+
+    /**
+     * Call this to set the result that your activity will return to its
+     * caller.
+     * 
+     * @param resultCode The result code to propagate back to the originating
+     *                   activity, often RESULT_CANCELED or RESULT_OK
+     * @param data The data to propagate back to the originating activity.
+     * 
+     * @see #RESULT_CANCELED
+     * @see #RESULT_OK
+     * @see #RESULT_FIRST_USER
+     * @see #setResult(int)
+     */
+    public final void setResult(int resultCode, Intent data) {
+        synchronized (this) {
+            mResultCode = resultCode;
+            mResultData = data;
+        }
+    }
+
+    /**
+     * Return the name of the package that invoked this activity.  This is who
+     * the data in {@link #setResult setResult()} will be sent to.  You can
+     * use this information to validate that the recipient is allowed to
+     * receive the data.
+     * 
+     * <p>Note: if the calling activity is not expecting a result (that is it
+     * did not use the {@link #startActivityForResult} 
+     * form that includes a request code), then the calling package will be 
+     * null. 
+     * 
+     * @return The package of the activity that will receive your
+     *         reply, or null if none.
+     */
+    public String getCallingPackage() {
+        try {
+            return ActivityManagerNative.getDefault().getCallingPackage(mToken);
+        } catch (RemoteException e) {
+            return null;
+        }
+    }
+
+    /**
+     * Return the name of the activity that invoked this activity.  This is
+     * who the data in {@link #setResult setResult()} will be sent to.  You
+     * can use this information to validate that the recipient is allowed to
+     * receive the data.
+     * 
+     * <p>Note: if the calling activity is not expecting a result (that is it
+     * did not use the {@link #startActivityForResult} 
+     * form that includes a request code), then the calling package will be 
+     * null. 
+     * 
+     * @return String The full name of the activity that will receive your
+     *         reply, or null if none.
+     */
+    public ComponentName getCallingActivity() {
+        try {
+            return ActivityManagerNative.getDefault().getCallingActivity(mToken);
+        } catch (RemoteException e) {
+            return null;
+        }
+    }
+
+    /**
+     * Control whether this activity's main window is visible.  This is intended
+     * only for the special case of an activity that is not going to show a
+     * UI itself, but can't just finish prior to onResume() because it needs
+     * to wait for a service binding or such.  Setting this to false allows
+     * you to prevent your UI from being shown during that time.
+     * 
+     * <p>The default value for this is taken from the
+     * {@link android.R.attr#windowNoDisplay} attribute of the activity's theme.
+     */
+    public void setVisible(boolean visible) {
+        if (mVisibleFromClient != visible) {
+            mVisibleFromClient = visible;
+            if (mVisibleFromServer) {
+                if (visible) makeVisible();
+                else mDecor.setVisibility(View.INVISIBLE);
+            }
+        }
+    }
+    
+    void makeVisible() {
+        if (!mWindowAdded) {
+            ViewManager wm = getWindowManager();
+            wm.addView(mDecor, getWindow().getAttributes());
+            mWindowAdded = true;
+        }
+        mDecor.setVisibility(View.VISIBLE);
+    }
+    
+    /**
+     * Check to see whether this activity is in the process of finishing,
+     * either because you called {@link #finish} on it or someone else
+     * has requested that it finished.  This is often used in
+     * {@link #onPause} to determine whether the activity is simply pausing or
+     * completely finishing.
+     * 
+     * @return If the activity is finishing, returns true; else returns false.
+     * 
+     * @see #finish
+     */
+    public boolean isFinishing() {
+        return mFinished;
+    }
+
+    /**
+     * Call this when your activity is done and should be closed.  The
+     * ActivityResult is propagated back to whoever launched you via
+     * onActivityResult().
+     */
+    public void finish() {
+        if (mParent == null) {
+            int resultCode;
+            Intent resultData;
+            synchronized (this) {
+                resultCode = mResultCode;
+                resultData = mResultData;
+            }
+            if (Config.LOGV) Log.v(TAG, "Finishing self: token=" + mToken);
+            try {
+                if (ActivityManagerNative.getDefault()
+                    .finishActivity(mToken, resultCode, resultData)) {
+                    mFinished = true;
+                }
+            } catch (RemoteException e) {
+                // Empty
+            }
+        } else {
+            mParent.finishFromChild(this);
+        }
+    }
+
+    /**
+     * This is called when a child activity of this one calls its 
+     * {@link #finish} method.  The default implementation simply calls
+     * finish() on this activity (the parent), finishing the entire group.
+     * 
+     * @param child The activity making the call.
+     * 
+     * @see #finish
+     */
+    public void finishFromChild(Activity child) {
+        finish();
+    }
+
+    /**
+     * Force finish another activity that you had previously started with
+     * {@link #startActivityForResult}.
+     * 
+     * @param requestCode The request code of the activity that you had
+     *                    given to startActivityForResult().  If there are multiple
+     *                    activities started with this request code, they
+     *                    will all be finished.
+     */
+    public void finishActivity(int requestCode) {
+        if (mParent == null) {
+            try {
+                ActivityManagerNative.getDefault()
+                    .finishSubActivity(mToken, mEmbeddedID, requestCode);
+            } catch (RemoteException e) {
+                // Empty
+            }
+        } else {
+            mParent.finishActivityFromChild(this, requestCode);
+        }
+    }
+
+    /**
+     * This is called when a child activity of this one calls its
+     * finishActivity().
+     * 
+     * @param child The activity making the call.
+     * @param requestCode Request code that had been used to start the
+     *                    activity.
+     */
+    public void finishActivityFromChild(Activity child, int requestCode) {
+        try {
+            ActivityManagerNative.getDefault()
+                .finishSubActivity(mToken, child.mEmbeddedID, requestCode);
+        } catch (RemoteException e) {
+            // Empty
+        }
+    }
+
+    /**
+     * Called when an activity you launched exits, giving you the requestCode
+     * you started it with, the resultCode it returned, and any additional
+     * data from it.  The <var>resultCode</var> will be
+     * {@link #RESULT_CANCELED} if the activity explicitly returned that,
+     * didn't return any result, or crashed during its operation.
+     * 
+     * <p>You will receive this call immediately before onResume() when your
+     * activity is re-starting.
+     * 
+     * @param requestCode The integer request code originally supplied to
+     *                    startActivityForResult(), allowing you to identify who this
+     *                    result came from.
+     * @param resultCode The integer result code returned by the child activity
+     *                   through its setResult().
+     * @param data An Intent, which can return result data to the caller
+     *               (various data can be attached to Intent "extras").
+     * 
+     * @see #startActivityForResult
+     * @see #createPendingResult
+     * @see #setResult(int)
+     */
+    protected void onActivityResult(int requestCode, int resultCode,
+            Intent data) {
+    }
+
+    /**
+     * Create a new PendingIntent object which you can hand to others 
+     * for them to use to send result data back to your 
+     * {@link #onActivityResult} callback.  The created object will be either 
+     * one-shot (becoming invalid after a result is sent back) or multiple 
+     * (allowing any number of results to be sent through it). 
+     *  
+     * @param requestCode Private request code for the sender that will be
+     * associated with the result data when it is returned.  The sender can not
+     * modify this value, allowing you to identify incoming results.
+     * @param data Default data to supply in the result, which may be modified
+     * by the sender.
+     * @param flags May be {@link PendingIntent#FLAG_ONE_SHOT PendingIntent.FLAG_ONE_SHOT},
+     * {@link PendingIntent#FLAG_NO_CREATE PendingIntent.FLAG_NO_CREATE},
+     * {@link PendingIntent#FLAG_CANCEL_CURRENT PendingIntent.FLAG_CANCEL_CURRENT},
+     * {@link PendingIntent#FLAG_UPDATE_CURRENT PendingIntent.FLAG_UPDATE_CURRENT},
+     * or any of the flags as supported by
+     * {@link Intent#fillIn Intent.fillIn()} to control which unspecified parts
+     * of the intent that can be supplied when the actual send happens.
+     * 
+     * @return Returns an existing or new PendingIntent matching the given
+     * parameters.  May return null only if
+     * {@link PendingIntent#FLAG_NO_CREATE PendingIntent.FLAG_NO_CREATE} has been
+     * supplied.
+     * 
+     * @see PendingIntent
+     */
+    public PendingIntent createPendingResult(int requestCode, Intent data,
+            int flags) {
+        String packageName = getPackageName();
+        try {
+            IIntentSender target =
+                ActivityManagerNative.getDefault().getIntentSender(
+                        IActivityManager.INTENT_SENDER_ACTIVITY_RESULT, packageName,
+                        mParent == null ? mToken : mParent.mToken,
+                        mEmbeddedID, requestCode, data, null, flags);
+            return target != null ? new PendingIntent(target) : null;
+        } catch (RemoteException e) {
+            // Empty
+        }
+        return null;
+    }
+
+    /**
+     * Change the desired orientation of this activity.  If the activity
+     * is currently in the foreground or otherwise impacting the screen
+     * orientation, the screen will immediately be changed (possibly causing
+     * the activity to be restarted). Otherwise, this will be used the next
+     * time the activity is visible.
+     * 
+     * @param requestedOrientation An orientation constant as used in
+     * {@link ActivityInfo#screenOrientation ActivityInfo.screenOrientation}.
+     */
+    public void setRequestedOrientation(int requestedOrientation) {
+        if (mParent == null) {
+            try {
+                ActivityManagerNative.getDefault().setRequestedOrientation(
+                        mToken, requestedOrientation);
+            } catch (RemoteException e) {
+                // Empty
+            }
+        } else {
+            mParent.setRequestedOrientation(requestedOrientation);
+        }
+    }
+    
+    /**
+     * Return the current requested orientation of the activity.  This will
+     * either be the orientation requested in its component's manifest, or
+     * the last requested orientation given to
+     * {@link #setRequestedOrientation(int)}.
+     * 
+     * @return Returns an orientation constant as used in
+     * {@link ActivityInfo#screenOrientation ActivityInfo.screenOrientation}.
+     */
+    public int getRequestedOrientation() {
+        if (mParent == null) {
+            try {
+                return ActivityManagerNative.getDefault()
+                        .getRequestedOrientation(mToken);
+            } catch (RemoteException e) {
+                // Empty
+            }
+        } else {
+            return mParent.getRequestedOrientation();
+        }
+        return ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
+    }
+    
+    /**
+     * Return the identifier of the task this activity is in.  This identifier
+     * will remain the same for the lifetime of the activity.
+     * 
+     * @return Task identifier, an opaque integer.
+     */
+    public int getTaskId() {
+        try {
+            return ActivityManagerNative.getDefault()
+                .getTaskForActivity(mToken, false);
+        } catch (RemoteException e) {
+            return -1;
+        }
+    }
+
+    /**
+     * Return whether this activity is the root of a task.  The root is the
+     * first activity in a task.
+     * 
+     * @return True if this is the root activity, else false.
+     */
+    public boolean isTaskRoot() {
+        try {
+            return ActivityManagerNative.getDefault()
+                .getTaskForActivity(mToken, true) >= 0;
+        } catch (RemoteException e) {
+            return false;
+        }
+    }
+
+    /**
+     * Move the task containing this activity to the back of the activity
+     * stack.  The activity's order within the task is unchanged.
+     * 
+     * @param nonRoot If false then this only works if the activity is the root
+     *                of a task; if true it will work for any activity in
+     *                a task.
+     * 
+     * @return If the task was moved (or it was already at the
+     *         back) true is returned, else false.
+     */
+    public boolean moveTaskToBack(boolean nonRoot) {
+        try {
+            return ActivityManagerNative.getDefault().moveActivityTaskToBack(
+                    mToken, nonRoot);
+        } catch (RemoteException e) {
+            // Empty
+        }
+        return false;
+    }
+
+    /**
+     * Returns class name for this activity with the package prefix removed.
+     * This is the default name used to read and write settings.
+     * 
+     * @return The local class name.
+     */
+    public String getLocalClassName() {
+        final String pkg = getPackageName();
+        final String cls = mComponent.getClassName();
+        int packageLen = pkg.length();
+        if (!cls.startsWith(pkg) || cls.length() <= packageLen
+                || cls.charAt(packageLen) != '.') {
+            return cls;
+        }
+        return cls.substring(packageLen+1);
+    }
+    
+    /**
+     * Returns complete component name of this activity.
+     * 
+     * @return Returns the complete component name for this activity
+     */
+    public ComponentName getComponentName()
+    {
+        return mComponent;
+    }
+
+    /**
+     * Retrieve a {@link SharedPreferences} object for accessing preferences
+     * that are private to this activity.  This simply calls the underlying
+     * {@link #getSharedPreferences(String, int)} method by passing in this activity's
+     * class name as the preferences name.
+     * 
+     * @param mode Operating mode.  Use {@link #MODE_PRIVATE} for the default 
+     *             operation, {@link #MODE_WORLD_READABLE} and 
+     *             {@link #MODE_WORLD_WRITEABLE} to control permissions.
+     *
+     * @return Returns the single SharedPreferences instance that can be used
+     *         to retrieve and modify the preference values.
+     */
+    public SharedPreferences getPreferences(int mode) {
+        return getSharedPreferences(getLocalClassName(), mode);
+    }
+    
+    @Override
+    public Object getSystemService(String name) {
+        if (getBaseContext() == null) {
+            throw new IllegalStateException(
+                    "System services not available to Activities before onCreate()");
+        }
+
+        if (WINDOW_SERVICE.equals(name)) {
+            return mWindowManager;
+        }
+        return super.getSystemService(name);
+    }
+
+    /**
+     * Change the title associated with this activity.  If this is a
+     * top-level activity, the title for its window will change.  If it
+     * is an embedded activity, the parent can do whatever it wants
+     * with it.
+     */
+    public void setTitle(CharSequence title) {
+        mTitle = title;
+        onTitleChanged(title, mTitleColor);
+
+        if (mParent != null) {
+            mParent.onChildTitleChanged(this, title);
+        }
+    }
+
+    /**
+     * Change the title associated with this activity.  If this is a
+     * top-level activity, the title for its window will change.  If it
+     * is an embedded activity, the parent can do whatever it wants
+     * with it.
+     */
+    public void setTitle(int titleId) {
+        setTitle(getText(titleId));
+    }
+
+    public void setTitleColor(int textColor) {
+        mTitleColor = textColor;
+        onTitleChanged(mTitle, textColor);
+    }
+
+    public final CharSequence getTitle() {
+        return mTitle;
+    }
+
+    public final int getTitleColor() {
+        return mTitleColor;
+    }
+
+    protected void onTitleChanged(CharSequence title, int color) {
+        if (mTitleReady) {
+            final Window win = getWindow();
+            if (win != null) {
+                win.setTitle(title);
+                if (color != 0) {
+                    win.setTitleColor(color);
+                }
+            }
+        }
+    }
+
+    protected void onChildTitleChanged(Activity childActivity, CharSequence title) {
+    }
+
+    /**
+     * Sets the visibility of the progress bar in the title.
+     * <p>
+     * In order for the progress bar to be shown, the feature must be requested
+     * via {@link #requestWindowFeature(int)}.
+     * 
+     * @param visible Whether to show the progress bars in the title.
+     */
+    public final void setProgressBarVisibility(boolean visible) {
+        getWindow().setFeatureInt(Window.FEATURE_PROGRESS, visible ? Window.PROGRESS_VISIBILITY_ON :
+            Window.PROGRESS_VISIBILITY_OFF);
+    }
+
+    /**
+     * Sets the visibility of the indeterminate progress bar in the title.
+     * <p>
+     * In order for the progress bar to be shown, the feature must be requested
+     * via {@link #requestWindowFeature(int)}.
+     *
+     * @param visible Whether to show the progress bars in the title.
+     */
+    public final void setProgressBarIndeterminateVisibility(boolean visible) {
+        getWindow().setFeatureInt(Window.FEATURE_INDETERMINATE_PROGRESS,
+                visible ? Window.PROGRESS_VISIBILITY_ON : Window.PROGRESS_VISIBILITY_OFF);
+    }
+    
+    /**
+     * Sets whether the horizontal progress bar in the title should be indeterminate (the circular
+     * is always indeterminate).
+     * <p>
+     * In order for the progress bar to be shown, the feature must be requested
+     * via {@link #requestWindowFeature(int)}.
+     * 
+     * @param indeterminate Whether the horizontal progress bar should be indeterminate.
+     */
+    public final void setProgressBarIndeterminate(boolean indeterminate) {
+        getWindow().setFeatureInt(Window.FEATURE_PROGRESS,
+                indeterminate ? Window.PROGRESS_INDETERMINATE_ON : Window.PROGRESS_INDETERMINATE_OFF);
+    }
+    
+    /**
+     * Sets the progress for the progress bars in the title.
+     * <p>
+     * In order for the progress bar to be shown, the feature must be requested
+     * via {@link #requestWindowFeature(int)}.
+     * 
+     * @param progress The progress for the progress bar. Valid ranges are from
+     *            0 to 10000 (both inclusive). If 10000 is given, the progress
+     *            bar will be completely filled and will fade out.
+     */
+    public final void setProgress(int progress) {
+        getWindow().setFeatureInt(Window.FEATURE_PROGRESS, progress + Window.PROGRESS_START);
+    }
+    
+    /**
+     * Sets the secondary progress for the progress bar in the title. This
+     * progress is drawn between the primary progress (set via
+     * {@link #setProgress(int)} and the background. It can be ideal for media
+     * scenarios such as showing the buffering progress while the default
+     * progress shows the play progress.
+     * <p>
+     * In order for the progress bar to be shown, the feature must be requested
+     * via {@link #requestWindowFeature(int)}.
+     * 
+     * @param secondaryProgress The secondary progress for the progress bar. Valid ranges are from
+     *            0 to 10000 (both inclusive).
+     */
+    public final void setSecondaryProgress(int secondaryProgress) {
+        getWindow().setFeatureInt(Window.FEATURE_PROGRESS,
+                secondaryProgress + Window.PROGRESS_SECONDARY_START);
+    }
+
+    /**
+     * Suggests an audio stream whose volume should be changed by the hardware
+     * volume controls.
+     * <p>
+     * The suggested audio stream will be tied to the window of this Activity.
+     * If the Activity is switched, the stream set here is no longer the
+     * suggested stream. The client does not need to save and restore the old
+     * suggested stream value in onPause and onResume.
+     * 
+     * @param streamType The type of the audio stream whose volume should be
+     *        changed by the hardware volume controls. It is not guaranteed that
+     *        the hardware volume controls will always change this stream's
+     *        volume (for example, if a call is in progress, its stream's volume
+     *        may be changed instead). To reset back to the default, use
+     *        {@link AudioManager#USE_DEFAULT_STREAM_TYPE}.
+     */
+    public final void setVolumeControlStream(int streamType) {
+        getWindow().setVolumeControlStream(streamType);
+    }
+
+    /**
+     * Gets the suggested audio stream whose volume should be changed by the
+     * harwdare volume controls.
+     * 
+     * @return The suggested audio stream type whose volume should be changed by
+     *         the hardware volume controls.
+     * @see #setVolumeControlStream(int)
+     */
+    public final int getVolumeControlStream() {
+        return getWindow().getVolumeControlStream();
+    }
+    
+    /**
+     * Runs the specified action on the UI thread. If the current thread is the UI
+     * thread, then the action is executed immediately. If the current thread is
+     * not the UI thread, the action is posted to the event queue of the UI thread.
+     *
+     * @param action the action to run on the UI thread
+     */
+    public final void runOnUiThread(Runnable action) {
+        if (Thread.currentThread() != mUiThread) {
+            mHandler.post(action);
+        } else {
+            action.run();
+        }
+    }
+
+    /**
+     * Stub implementation of {@link android.view.LayoutInflater.Factory#onCreateView} used when
+     * inflating with the LayoutInflater returned by {@link #getSystemService}.  This
+     * implementation simply returns null for all view names.
+     *
+     * @see android.view.LayoutInflater#createView
+     * @see android.view.Window#getLayoutInflater
+     */
+    public View onCreateView(String name, Context context, AttributeSet attrs) {
+        return null;
+    }
+
+    // ------------------ Internal API ------------------
+    
+    final void setParent(Activity parent) {
+        mParent = parent;
+    }
+
+    final void attach(Context context, ActivityThread aThread, Instrumentation instr, IBinder token,
+            Application application, Intent intent, ActivityInfo info, CharSequence title, 
+            Activity parent, String id, Object lastNonConfigurationInstance,
+            Configuration config) {
+        attach(context, aThread, instr, token, application, intent, info, title, parent, id,
+            lastNonConfigurationInstance, null, config);
+    }
+    
+    final void attach(Context context, ActivityThread aThread, Instrumentation instr, IBinder token,
+        Application application, Intent intent, ActivityInfo info, CharSequence title, 
+        Activity parent, String id, Object lastNonConfigurationInstance,
+        HashMap<String,Object> lastNonConfigurationChildInstances, Configuration config) {
+        attachBaseContext(context);
+
+        mWindow = PolicyManager.makeNewWindow(this);
+        mWindow.setCallback(this);
+        if (info.softInputMode != WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED) {
+            mWindow.setSoftInputMode(info.softInputMode);
+        }
+        mUiThread = Thread.currentThread();
+
+        mMainThread = aThread;
+        mInstrumentation = instr;
+        mToken = token;
+        mApplication = application;
+        mIntent = intent;
+        mComponent = intent.getComponent();
+        mActivityInfo = info;
+        mTitle = title;
+        mParent = parent;
+        mEmbeddedID = id;
+        mLastNonConfigurationInstance = lastNonConfigurationInstance;
+        mLastNonConfigurationChildInstances = lastNonConfigurationChildInstances;
+
+        mWindow.setWindowManager(null, mToken, mComponent.flattenToString());
+        if (mParent != null) {
+            mWindow.setContainer(mParent.getWindow());
+        }
+        mWindowManager = mWindow.getWindowManager();
+        mCurrentConfig = config;
+    }
+
+    final IBinder getActivityToken() {
+        return mParent != null ? mParent.getActivityToken() : mToken;
+    }
+
+    final void performStart() {
+        mCalled = false;
+        mInstrumentation.callActivityOnStart(this);
+        if (!mCalled) {
+            throw new SuperNotCalledException(
+                "Activity " + mComponent.toShortString() +
+                " did not call through to super.onStart()");
+        }
+    }
+    
+    final void performRestart() {
+        final int N = mManagedCursors.size();
+        for (int i=0; i<N; i++) {
+            ManagedCursor mc = mManagedCursors.get(i);
+            if (mc.mReleased || mc.mUpdated) {
+                mc.mCursor.requery();
+                mc.mReleased = false;
+                mc.mUpdated = false;
+            }
+        }
+
+        if (mStopped) {
+            mStopped = false;
+            mCalled = false;
+            mInstrumentation.callActivityOnRestart(this);
+            if (!mCalled) {
+                throw new SuperNotCalledException(
+                    "Activity " + mComponent.toShortString() +
+                    " did not call through to super.onRestart()");
+            }
+            performStart();
+        }
+    }
+    
+    final void performResume() {
+        performRestart();
+        
+        mLastNonConfigurationInstance = null;
+        
+        // First call onResume() -before- setting mResumed, so we don't
+        // send out any status bar / menu notifications the client makes.
+        mCalled = false;
+        mInstrumentation.callActivityOnResume(this);
+        if (!mCalled) {
+            throw new SuperNotCalledException(
+                "Activity " + mComponent.toShortString() +
+                " did not call through to super.onResume()");
+        }
+
+        // Now really resume, and install the current status bar and menu.
+        mResumed = true;
+        mCalled = false;
+        onPostResume();
+        if (!mCalled) {
+            throw new SuperNotCalledException(
+                "Activity " + mComponent.toShortString() +
+                " did not call through to super.onPostResume()");
+        }
+    }
+
+    final void performPause() {
+        onPause();
+    }
+    
+    final void performUserLeaving() {
+        onUserInteraction();
+        onUserLeaveHint();
+    }
+    
+    final void performStop() {
+        if (!mStopped) {
+            if (mWindow != null) {
+                mWindow.closeAllPanels();
+            }
+
+            mCalled = false;
+            mInstrumentation.callActivityOnStop(this);
+            if (!mCalled) {
+                throw new SuperNotCalledException(
+                    "Activity " + mComponent.toShortString() +
+                    " did not call through to super.onStop()");
+            }
+    
+            final int N = mManagedCursors.size();
+            for (int i=0; i<N; i++) {
+                ManagedCursor mc = mManagedCursors.get(i);
+                if (!mc.mReleased) {
+                    mc.mCursor.deactivate();
+                    mc.mReleased = true;
+                }
+            }
+    
+            mStopped = true;
+        }
+        mResumed = false;
+    }
+
+    final boolean isResumed() {
+        return mResumed;
+    }
+
+    void dispatchActivityResult(String who, int requestCode, 
+        int resultCode, Intent data) {
+        if (Config.LOGV) Log.v(
+            TAG, "Dispatching result: who=" + who + ", reqCode=" + requestCode
+            + ", resCode=" + resultCode + ", data=" + data);
+        if (who == null) {
+            onActivityResult(requestCode, resultCode, data);
+        }
+    }
+}
diff --git a/core/java/android/app/ActivityGroup.java b/core/java/android/app/ActivityGroup.java
new file mode 100644
index 0000000..f1216f9
--- /dev/null
+++ b/core/java/android/app/ActivityGroup.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2007 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 android.app;
+
+import java.util.HashMap;
+
+import android.content.Intent;
+import android.os.Bundle;
+import android.util.Log;
+
+/**
+ * A screen that contains and runs multiple embedded activities.
+ */
+public class ActivityGroup extends Activity {
+    private static final String TAG = "ActivityGroup";
+    private static final String STATES_KEY = "android:states";
+    static final String PARENT_NON_CONFIG_INSTANCE_KEY = "android:parent_non_config_instance";
+
+    /**
+     * This field should be made private, so it is hidden from the SDK.
+     * {@hide}
+     */
+    protected LocalActivityManager mLocalActivityManager;
+    
+    public ActivityGroup() {
+        this(true);
+    }
+    
+    public ActivityGroup(boolean singleActivityMode) {
+        mLocalActivityManager = new LocalActivityManager(this, singleActivityMode);
+    }
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        Bundle states = savedInstanceState != null
+                ? (Bundle) savedInstanceState.getBundle(STATES_KEY) : null;
+        mLocalActivityManager.dispatchCreate(states);
+    }
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+        mLocalActivityManager.dispatchResume();
+    }
+
+    @Override
+    protected void onSaveInstanceState(Bundle outState) {
+        super.onSaveInstanceState(outState);
+        Bundle state = mLocalActivityManager.saveInstanceState();
+        if (state != null) {
+            outState.putBundle(STATES_KEY, state);
+        }
+    }
+
+    @Override
+    protected void onPause() {
+        super.onPause();
+        mLocalActivityManager.dispatchPause(isFinishing());
+    }
+
+    @Override
+    protected void onStop() {
+        super.onStop();
+        mLocalActivityManager.dispatchStop();
+    }
+
+    @Override
+    protected void onDestroy() {
+        super.onDestroy();
+        mLocalActivityManager.dispatchDestroy(isFinishing());
+    }
+
+    /**
+     * Returns a HashMap mapping from child activity ids to the return values
+     * from calls to their onRetainNonConfigurationInstance methods.
+     *
+     * {@hide}
+     */
+    @Override
+    public HashMap<String,Object> onRetainNonConfigurationChildInstances() {
+        return mLocalActivityManager.dispatchRetainNonConfigurationInstance();
+    }
+
+    public Activity getCurrentActivity() {
+        return mLocalActivityManager.getCurrentActivity();
+    }
+
+    public final LocalActivityManager getLocalActivityManager() {
+        return mLocalActivityManager;
+    }
+
+    @Override
+    void dispatchActivityResult(String who, int requestCode, int resultCode,
+            Intent data) {
+        if (who != null) {
+            Activity act = mLocalActivityManager.getActivity(who);
+            /*
+            if (Config.LOGV) Log.v(
+                TAG, "Dispatching result: who=" + who + ", reqCode=" + requestCode
+                + ", resCode=" + resultCode + ", data=" + data
+                + ", rec=" + rec);
+            */
+            if (act != null) {
+                act.onActivityResult(requestCode, resultCode, data);
+                return;
+            }
+        }
+        super.dispatchActivityResult(who, requestCode, resultCode, data);
+    }
+}
+
+
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
new file mode 100644
index 0000000..07520c9d
--- /dev/null
+++ b/core/java/android/app/ActivityManager.java
@@ -0,0 +1,761 @@
+/*
+ * Copyright (C) 2007 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 android.app;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ConfigurationInfo;
+import android.content.pm.IPackageDataObserver;
+import android.graphics.Bitmap;
+import android.os.RemoteException;
+import android.os.Handler;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+import java.util.List;
+
+/**
+ * Interact with the overall activities running in the system.
+ */
+public class ActivityManager {
+    private static String TAG = "ActivityManager";
+    private static boolean DEBUG = false;
+    private static boolean localLOGV = DEBUG || android.util.Config.LOGV;
+
+    private final Context mContext;
+    private final Handler mHandler;
+
+    /*package*/ ActivityManager(Context context, Handler handler) {
+        mContext = context;
+        mHandler = handler;
+    }
+
+    /**
+     * Information you can retrieve about tasks that the user has most recently
+     * started or visited.
+     */
+    public static class RecentTaskInfo implements Parcelable {
+        /**
+         * If this task is currently running, this is the identifier for it.
+         * If it is not running, this will be -1.
+         */
+        public int id;
+
+        /**
+         * The original Intent used to launch the task.  You can use this
+         * Intent to re-launch the task (if it is no longer running) or bring
+         * the current task to the front.
+         */
+        public Intent baseIntent;
+
+        /**
+         * If this task was started from an alias, this is the actual
+         * activity component that was initially started; the component of
+         * the baseIntent in this case is the name of the actual activity
+         * implementation that the alias referred to.  Otherwise, this is null.
+         */
+        public ComponentName origActivity;
+        
+        public RecentTaskInfo() {
+        }
+
+        public int describeContents() {
+            return 0;
+        }
+
+        public void writeToParcel(Parcel dest, int flags) {
+            dest.writeInt(id);
+            if (baseIntent != null) {
+                dest.writeInt(1);
+                baseIntent.writeToParcel(dest, 0);
+            } else {
+                dest.writeInt(0);
+            }
+            ComponentName.writeToParcel(origActivity, dest);
+        }
+
+        public void readFromParcel(Parcel source) {
+            id = source.readInt();
+            if (source.readInt() != 0) {
+                baseIntent = Intent.CREATOR.createFromParcel(source);
+            } else {
+                baseIntent = null;
+            }
+            origActivity = ComponentName.readFromParcel(source);
+        }
+        
+        public static final Creator<RecentTaskInfo> CREATOR
+                = new Creator<RecentTaskInfo>() {
+            public RecentTaskInfo createFromParcel(Parcel source) {
+                return new RecentTaskInfo(source);
+            }
+            public RecentTaskInfo[] newArray(int size) {
+                return new RecentTaskInfo[size];
+            }
+        };
+
+        private RecentTaskInfo(Parcel source) {
+            readFromParcel(source);
+        }
+    }
+
+    /**
+     * Flag for use with {@link #getRecentTasks}: return all tasks, even those
+     * that have set their
+     * {@link android.content.Intent#FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS} flag.
+     */
+    public static final int RECENT_WITH_EXCLUDED = 0x0001;
+    
+    /**
+     * Return a list of the tasks that the user has recently launched, with
+     * the most recent being first and older ones after in order.
+     * 
+     * @param maxNum The maximum number of entries to return in the list.  The
+     * actual number returned may be smaller, depending on how many tasks the
+     * user has started and the maximum number the system can remember.
+     * 
+     * @return Returns a list of RecentTaskInfo records describing each of
+     * the recent tasks.
+     * 
+     * @throws SecurityException Throws SecurityException if the caller does
+     * not hold the {@link android.Manifest.permission#GET_TASKS} permission.
+     */
+    public List<RecentTaskInfo> getRecentTasks(int maxNum, int flags)
+            throws SecurityException {
+        try {
+            return ActivityManagerNative.getDefault().getRecentTasks(maxNum,
+                    flags);
+        } catch (RemoteException e) {
+            // System dead, we will be dead too soon!
+            return null;
+        }
+    }
+
+    /**
+     * Information you can retrieve about a particular task that is currently
+     * "running" in the system.  Note that a running task does not mean the
+     * given task actual has a process it is actively running in; it simply
+     * means that the user has gone to it and never closed it, but currently
+     * the system may have killed its process and is only holding on to its
+     * last state in order to restart it when the user returns.
+     */
+    public static class RunningTaskInfo implements Parcelable {
+        /**
+         * A unique identifier for this task.
+         */
+        public int id;
+
+        /**
+         * The component launched as the first activity in the task.  This can
+         * be considered the "application" of this task.
+         */
+        public ComponentName baseActivity;
+
+        /**
+         * The activity component at the top of the history stack of the task.
+         * This is what the user is currently doing.
+         */
+        public ComponentName topActivity;
+
+        /**
+         * Thumbnail representation of the task's current state.
+         */
+        public Bitmap thumbnail;
+
+        /**
+         * Description of the task's current state.
+         */
+        public CharSequence description;
+
+        /**
+         * Number of activities in this task.
+         */
+        public int numActivities;
+
+        /**
+         * Number of activities that are currently running (not stopped
+         * and persisted) in this task.
+         */
+        public int numRunning;
+
+        public RunningTaskInfo() {
+        }
+
+        public int describeContents() {
+            return 0;
+        }
+
+        public void writeToParcel(Parcel dest, int flags) {
+            dest.writeInt(id);
+            ComponentName.writeToParcel(baseActivity, dest);
+            ComponentName.writeToParcel(topActivity, dest);
+            if (thumbnail != null) {
+                dest.writeInt(1);
+                thumbnail.writeToParcel(dest, 0);
+            } else {
+                dest.writeInt(0);
+            }
+            TextUtils.writeToParcel(description, dest,
+                    Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
+            dest.writeInt(numActivities);
+            dest.writeInt(numRunning);
+        }
+
+        public void readFromParcel(Parcel source) {
+            id = source.readInt();
+            baseActivity = ComponentName.readFromParcel(source);
+            topActivity = ComponentName.readFromParcel(source);
+            if (source.readInt() != 0) {
+                thumbnail = Bitmap.CREATOR.createFromParcel(source);
+            } else {
+                thumbnail = null;
+            }
+            description = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source);
+            numActivities = source.readInt();
+            numRunning = source.readInt();
+        }
+        
+        public static final Creator<RunningTaskInfo> CREATOR = new Creator<RunningTaskInfo>() {
+            public RunningTaskInfo createFromParcel(Parcel source) {
+                return new RunningTaskInfo(source);
+            }
+            public RunningTaskInfo[] newArray(int size) {
+                return new RunningTaskInfo[size];
+            }
+        };
+
+        private RunningTaskInfo(Parcel source) {
+            readFromParcel(source);
+        }
+    }
+
+    /**
+     * Return a list of the tasks that are currently running, with
+     * the most recent being first and older ones after in order.  Note that
+     * "running" does not mean any of the task's code is currently loaded or
+     * activity -- the task may have been frozen by the system, so that it
+     * can be restarted in its previous state when next brought to the
+     * foreground.
+     * 
+     * @param maxNum The maximum number of entries to return in the list.  The
+     * actual number returned may be smaller, depending on how many tasks the
+     * user has started.
+     * 
+     * @return Returns a list of RunningTaskInfo records describing each of
+     * the running tasks.
+     * 
+     * @throws SecurityException Throws SecurityException if the caller does
+     * not hold the {@link android.Manifest.permission#GET_TASKS} permission.
+     */
+    public List<RunningTaskInfo> getRunningTasks(int maxNum)
+            throws SecurityException {
+        try {
+            return (List<RunningTaskInfo>)ActivityManagerNative.getDefault()
+                    .getTasks(maxNum, 0, null);
+        } catch (RemoteException e) {
+            // System dead, we will be dead too soon!
+            return null;
+        }
+    }
+    
+    /**
+     * Information you can retrieve about a particular Service that is
+     * currently running in the system.
+     */
+    public static class RunningServiceInfo implements Parcelable {
+        /**
+         * The service component.
+         */
+        public ComponentName service;
+
+        /**
+         * If non-zero, this is the process the service is running in.
+         */
+        public int pid;
+        
+        /**
+         * The name of the process this service runs in.
+         */
+        public String process;
+        
+        /**
+         * Set to true if the service has asked to run as a foreground process.
+         */
+        public boolean foreground;
+        
+        /**
+         * The time when the service was first made activity, either by someone
+         * starting or binding to it.
+         */
+        public long activeSince;
+        
+        /**
+         * Set to true if this service has been explicitly started.
+         */
+        public boolean started;
+        
+        /**
+         * Number of clients connected to the service.
+         */
+        public int clientCount;
+        
+        /**
+         * Number of times the service's process has crashed while the service
+         * is running.
+         */
+        public int crashCount;
+        
+        /**
+         * The time when there was last activity in the service (either
+         * explicit requests to start it or clients binding to it).
+         */
+        public long lastActivityTime;
+        
+        /**
+         * If non-zero, this service is not currently running, but scheduled to
+         * restart at the given time.
+         */
+        public long restarting;
+        
+        public RunningServiceInfo() {
+        }
+
+        public int describeContents() {
+            return 0;
+        }
+
+        public void writeToParcel(Parcel dest, int flags) {
+            ComponentName.writeToParcel(service, dest);
+            dest.writeInt(pid);
+            dest.writeString(process);
+            dest.writeInt(foreground ? 1 : 0);
+            dest.writeLong(activeSince);
+            dest.writeInt(started ? 1 : 0);
+            dest.writeInt(clientCount);
+            dest.writeInt(crashCount);
+            dest.writeLong(lastActivityTime);
+            dest.writeLong(restarting);
+        }
+
+        public void readFromParcel(Parcel source) {
+            service = ComponentName.readFromParcel(source);
+            pid = source.readInt();
+            process = source.readString();
+            foreground = source.readInt() != 0;
+            activeSince = source.readLong();
+            started = source.readInt() != 0;
+            clientCount = source.readInt();
+            crashCount = source.readInt();
+            lastActivityTime = source.readLong();
+            restarting = source.readLong();
+        }
+        
+        public static final Creator<RunningServiceInfo> CREATOR = new Creator<RunningServiceInfo>() {
+            public RunningServiceInfo createFromParcel(Parcel source) {
+                return new RunningServiceInfo(source);
+            }
+            public RunningServiceInfo[] newArray(int size) {
+                return new RunningServiceInfo[size];
+            }
+        };
+
+        private RunningServiceInfo(Parcel source) {
+            readFromParcel(source);
+        }
+    }
+
+    /**
+     * Return a list of the services that are currently running.
+     * 
+     * @param maxNum The maximum number of entries to return in the list.  The
+     * actual number returned may be smaller, depending on how many services
+     * are running.
+     * 
+     * @return Returns a list of RunningServiceInfo records describing each of
+     * the running tasks.
+     */
+    public List<RunningServiceInfo> getRunningServices(int maxNum)
+            throws SecurityException {
+        try {
+            return (List<RunningServiceInfo>)ActivityManagerNative.getDefault()
+                    .getServices(maxNum, 0);
+        } catch (RemoteException e) {
+            // System dead, we will be dead too soon!
+            return null;
+        }
+    }
+    
+    /**
+     * Information you can retrieve about the available memory through
+     * {@link ActivityManager#getMemoryInfo}.
+     */
+    public static class MemoryInfo implements Parcelable {
+        /**
+         * The total available memory on the system.  This number should not
+         * be considered absolute: due to the nature of the kernel, a significant
+         * portion of this memory is actually in use and needed for the overall
+         * system to run well.
+         */
+        public long availMem;
+        
+        /**
+         * The threshold of {@link #availMem} at which we consider memory to be
+         * low and start killing background services and other non-extraneous
+         * processes.
+         */
+        public long threshold;
+        
+        /**
+         * Set to true if the system considers itself to currently be in a low
+         * memory situation.
+         */
+        public boolean lowMemory;
+
+        public MemoryInfo() {
+        }
+
+        public int describeContents() {
+            return 0;
+        }
+
+        public void writeToParcel(Parcel dest, int flags) {
+            dest.writeLong(availMem);
+            dest.writeLong(threshold);
+            dest.writeInt(lowMemory ? 1 : 0);
+        }
+        
+        public void readFromParcel(Parcel source) {
+            availMem = source.readLong();
+            threshold = source.readLong();
+            lowMemory = source.readInt() != 0;
+        }
+
+        public static final Creator<MemoryInfo> CREATOR
+                = new Creator<MemoryInfo>() {
+            public MemoryInfo createFromParcel(Parcel source) {
+                return new MemoryInfo(source);
+            }
+            public MemoryInfo[] newArray(int size) {
+                return new MemoryInfo[size];
+            }
+        };
+
+        private MemoryInfo(Parcel source) {
+            readFromParcel(source);
+        }
+    }
+
+    public void getMemoryInfo(MemoryInfo outInfo) {
+        try {
+            ActivityManagerNative.getDefault().getMemoryInfo(outInfo);
+        } catch (RemoteException e) {
+        }
+    }
+    
+    /**
+     * @hide
+     */
+    public boolean clearApplicationUserData(String packageName, IPackageDataObserver observer) {
+        try {
+            return ActivityManagerNative.getDefault().clearApplicationUserData(packageName, 
+                    observer);
+        } catch (RemoteException e) {
+            return false;
+        }
+    }
+    
+    /**
+     * Information you can retrieve about any processes that are in an error condition.
+     */
+    public static class ProcessErrorStateInfo implements Parcelable {
+        /**
+         * Condition codes
+         */
+        public static final int NO_ERROR = 0;
+        public static final int CRASHED = 1;
+        public static final int NOT_RESPONDING = 2;
+
+        /**
+         * The condition that the process is in.
+         */
+        public int condition;
+
+        /**
+         * The process name in which the crash or error occurred.
+         */
+        public String processName;
+        
+        /**
+         * The pid of this process; 0 if none
+         */
+        public int pid;
+
+        /**
+         * The kernel user-ID that has been assigned to this process;
+         * currently this is not a unique ID (multiple applications can have
+         * the same uid).
+         */
+        public int uid;
+        
+        /**
+         * The tag that was provided when the process crashed.
+         */
+        public String tag;
+
+        /**
+         * A short message describing the error condition.
+         */
+        public String shortMsg;
+
+        /**
+         * A long message describing the error condition.
+         */
+        public String longMsg;
+
+        /**
+         * Raw data about the crash (typically a stack trace).
+         */
+        public byte[] crashData;
+
+        public ProcessErrorStateInfo() {
+        }
+
+        public int describeContents() {
+            return 0;
+        }
+
+        public void writeToParcel(Parcel dest, int flags) {
+            dest.writeInt(condition);
+            dest.writeString(processName);
+            dest.writeInt(pid);
+            dest.writeInt(uid);
+            dest.writeString(tag);
+            dest.writeString(shortMsg);
+            dest.writeString(longMsg);
+            dest.writeInt(crashData == null ? -1 : crashData.length);
+            dest.writeByteArray(crashData);
+        }
+        
+        public void readFromParcel(Parcel source) {
+            condition = source.readInt();
+            processName = source.readString();
+            pid = source.readInt();
+            uid = source.readInt();
+            tag = source.readString();
+            shortMsg = source.readString();
+            longMsg = source.readString();
+            int cdLen = source.readInt();
+            if (cdLen == -1) {
+                crashData = null;
+            } else {
+                crashData = new byte[cdLen];
+                source.readByteArray(crashData);
+            }
+        }
+        
+        public static final Creator<ProcessErrorStateInfo> CREATOR = 
+                new Creator<ProcessErrorStateInfo>() {
+            public ProcessErrorStateInfo createFromParcel(Parcel source) {
+                return new ProcessErrorStateInfo(source);
+            }
+            public ProcessErrorStateInfo[] newArray(int size) {
+                return new ProcessErrorStateInfo[size];
+            }
+        };
+
+        private ProcessErrorStateInfo(Parcel source) {
+            readFromParcel(source);
+        }
+    }
+    
+    /**
+     * Returns a list of any processes that are currently in an error condition.  The result 
+     * will be null if all processes are running properly at this time.
+     * 
+     * @return Returns a list of ProcessErrorStateInfo records, or null if there are no
+     * current error conditions (it will not return an empty list).  This list ordering is not
+     * specified.
+     */
+    public List<ProcessErrorStateInfo> getProcessesInErrorState() {
+        try {
+            return ActivityManagerNative.getDefault().getProcessesInErrorState();
+        } catch (RemoteException e) {
+            return null;
+        }
+    }
+
+    /**
+     * Information you can retrieve about a running process.
+     */
+    public static class RunningAppProcessInfo implements Parcelable {        
+        /**
+         * The name of the process that this object is associated with
+         */
+        public String processName;
+
+        /**
+         * The pid of this process; 0 if none
+         */
+        public int pid;
+        
+        public String pkgList[];
+        
+        /**
+         * Constant for {@link #importance}: this process is running the
+         * foreground UI.
+         */
+        public static final int IMPORTANCE_FOREGROUND = 100;
+        
+        /**
+         * Constant for {@link #importance}: this process is running something
+         * that is considered to be actively visible to the user.
+         */
+        public static final int IMPORTANCE_VISIBLE = 200;
+        
+        /**
+         * Constant for {@link #importance}: this process is contains services
+         * that should remain running.
+         */
+        public static final int IMPORTANCE_SERVICE = 300;
+        
+        /**
+         * Constant for {@link #importance}: this process process contains
+         * background code that is expendable.
+         */
+        public static final int IMPORTANCE_BACKGROUND = 400;
+        
+        /**
+         * Constant for {@link #importance}: this process is empty of any
+         * actively running code.
+         */
+        public static final int IMPORTANCE_EMPTY = 500;
+        
+        /**
+         * The relative importance level that the system places on this
+         * process.  May be one of {@link #IMPORTANCE_FOREGROUND},
+         * {@link #IMPORTANCE_VISIBLE}, {@link #IMPORTANCE_SERVICE},
+         * {@link #IMPORTANCE_BACKGROUND}, or {@link #IMPORTANCE_EMPTY}.  These
+         * constants are numbered so that "more important" values are always
+         * smaller than "less important" values.
+         */
+        public int importance;
+        
+        /**
+         * An additional ordering within a particular {@link #importance}
+         * category, providing finer-grained information about the relative
+         * utility of processes within a category.  This number means nothing
+         * except that a smaller values are more recently used (and thus
+         * more important).  Currently an LRU value is only maintained for
+         * the {@link #IMPORTANCE_BACKGROUND} category, though others may
+         * be maintained in the future.
+         */
+        public int lru;
+        
+        public RunningAppProcessInfo() {
+            importance = IMPORTANCE_FOREGROUND;
+        }
+        
+        public RunningAppProcessInfo(String pProcessName, int pPid, String pArr[]) {
+            processName = pProcessName;
+            pid = pPid;
+            pkgList = pArr;
+        }
+
+        public int describeContents() {
+            return 0;
+        }
+
+        public void writeToParcel(Parcel dest, int flags) {
+            dest.writeString(processName);
+            dest.writeInt(pid);
+            dest.writeStringArray(pkgList);
+            dest.writeInt(importance);
+            dest.writeInt(lru);
+        }
+
+        public void readFromParcel(Parcel source) {
+            processName = source.readString();
+            pid = source.readInt();
+            pkgList = source.readStringArray();
+            importance = source.readInt();
+            lru = source.readInt();
+        }
+
+        public static final Creator<RunningAppProcessInfo> CREATOR = 
+            new Creator<RunningAppProcessInfo>() {
+            public RunningAppProcessInfo createFromParcel(Parcel source) {
+                return new RunningAppProcessInfo(source);
+            }
+            public RunningAppProcessInfo[] newArray(int size) {
+                return new RunningAppProcessInfo[size];
+            }
+        };
+
+        private RunningAppProcessInfo(Parcel source) {
+            readFromParcel(source);
+        }
+    }
+    
+    /**
+     * Returns a list of application processes that are running on the device.
+     * 
+     * @return Returns a list of RunningAppProcessInfo records, or null if there are no
+     * running processes (it will not return an empty list).  This list ordering is not
+     * specified.
+     */
+    public List<RunningAppProcessInfo> getRunningAppProcesses() {
+        try {
+            return ActivityManagerNative.getDefault().getRunningAppProcesses();
+        } catch (RemoteException e) {
+            return null;
+        }
+    }
+    
+    /**
+     * Have the system perform a force stop of everything associated with
+     * the given application package.  All processes that share its uid
+     * will be killed, all services it has running stopped, all activities
+     * removed, etc.  In addition, a {@link Intent#ACTION_PACKAGE_RESTARTED}
+     * broadcast will be sent, so that any of its registered alarms can
+     * be stopped, notifications removed, etc.
+     * 
+     * <p>You must hold the permission
+     * {@link android.Manifest.permission#RESTART_PACKAGES} to be able to
+     * call this method.
+     * 
+     * @param packageName The name of the package to be stopped.
+     */
+    public void restartPackage(String packageName) {
+        try {
+            ActivityManagerNative.getDefault().restartPackage(packageName);
+        } catch (RemoteException e) {
+        }
+    }
+    
+    /**
+     * Get the device configuration attributes.
+     */
+    public ConfigurationInfo getDeviceConfigurationInfo() {
+        try {
+            return ActivityManagerNative.getDefault().getDeviceConfigurationInfo();
+        } catch (RemoteException e) {
+        }
+        return null;
+    }
+    
+}
diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java
new file mode 100644
index 0000000..f11dbec
--- /dev/null
+++ b/core/java/android/app/ActivityManagerNative.java
@@ -0,0 +1,2135 @@
+/*
+ * 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 android.app;
+
+import android.content.ComponentName;
+import android.content.ContentResolver;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.ConfigurationInfo;
+import android.content.pm.IPackageDataObserver;
+import android.content.res.Configuration;
+import android.graphics.Bitmap;
+import android.net.Uri;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.Parcelable;
+import android.os.ParcelFileDescriptor;
+import android.os.RemoteException;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.ServiceManager;
+import android.text.TextUtils;
+import android.util.Config;
+import android.util.Log;
+
+import java.io.FileNotFoundException;
+import java.io.FileDescriptor;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+/** {@hide} */
+public abstract class ActivityManagerNative extends Binder implements IActivityManager
+{
+    /**
+     * Cast a Binder object into an activity manager interface, generating
+     * a proxy if needed.
+     */
+    static public IActivityManager asInterface(IBinder obj)
+    {
+        if (obj == null) {
+            return null;
+        }
+        IActivityManager in =
+            (IActivityManager)obj.queryLocalInterface(descriptor);
+        if (in != null) {
+            return in;
+        }
+        
+        return new ActivityManagerProxy(obj);
+    }
+    
+    /**
+     * Retrieve the system's default/global activity manager.
+     */
+    static public IActivityManager getDefault()
+    {
+        if (gDefault != null) {
+            //if (Config.LOGV) Log.v(
+            //    "ActivityManager", "returning cur default = " + gDefault);
+            return gDefault;
+        }
+        IBinder b = ServiceManager.getService("activity");
+        if (Config.LOGV) Log.v(
+            "ActivityManager", "default service binder = " + b);
+        gDefault = asInterface(b);
+        if (Config.LOGV) Log.v(
+            "ActivityManager", "default service = " + gDefault);
+        return gDefault;
+    }
+
+    /**
+     * Convenience for checking whether the system is ready.  For internal use only.
+     */
+    static public boolean isSystemReady() {
+        if (!sSystemReady) {
+            sSystemReady = getDefault().testIsSystemReady();
+        }
+        return sSystemReady;
+    }
+    static boolean sSystemReady = false;
+    
+    /**
+     * Convenience for sending a sticky broadcast.  For internal use only.
+     * If you don't care about permission, use null.
+     */
+    static public void broadcastStickyIntent(Intent intent, String permission)
+    {
+        try {
+            getDefault().broadcastIntent(
+                null, intent, null, null, Activity.RESULT_OK, null, null,
+                null /*permission*/, false, true);
+        } catch (RemoteException ex) {
+        }
+    }
+
+    static public void noteWakeupAlarm(PendingIntent ps) {
+        try {
+            getDefault().noteWakeupAlarm(ps.getTarget());
+        } catch (RemoteException ex) {
+        }
+    }
+
+    public ActivityManagerNative()
+    {
+        attachInterface(this, descriptor);
+    }
+    
+    public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
+            throws RemoteException {
+        switch (code) {
+        case START_ACTIVITY_TRANSACTION:
+        {
+            data.enforceInterface(IActivityManager.descriptor);
+            IBinder b = data.readStrongBinder();
+            IApplicationThread app = ApplicationThreadNative.asInterface(b);
+            Intent intent = Intent.CREATOR.createFromParcel(data);
+            String resolvedType = data.readString();
+            Uri[] grantedUriPermissions = data.createTypedArray(Uri.CREATOR);
+            int grantedMode = data.readInt();
+            IBinder resultTo = data.readStrongBinder();
+            String resultWho = data.readString();    
+            int requestCode = data.readInt();
+            boolean onlyIfNeeded = data.readInt() != 0;
+            boolean debug = data.readInt() != 0;
+            int result = startActivity(app, intent, resolvedType,
+                    grantedUriPermissions, grantedMode, resultTo, resultWho,
+                    requestCode, onlyIfNeeded, debug);
+            reply.writeNoException();
+            reply.writeInt(result);
+            return true;
+        }
+        
+        case START_NEXT_MATCHING_ACTIVITY_TRANSACTION:
+        {
+            data.enforceInterface(IActivityManager.descriptor);
+            IBinder callingActivity = data.readStrongBinder();
+            Intent intent = Intent.CREATOR.createFromParcel(data);
+            boolean result = startNextMatchingActivity(callingActivity, intent);
+            reply.writeNoException();
+            reply.writeInt(result ? 1 : 0);
+            return true;
+        }
+        
+        case FINISH_ACTIVITY_TRANSACTION: {
+            data.enforceInterface(IActivityManager.descriptor);
+            IBinder token = data.readStrongBinder();
+            Intent resultData = null;
+            int resultCode = data.readInt();
+            if (data.readInt() != 0) {
+                resultData = Intent.CREATOR.createFromParcel(data);
+            }
+            boolean res = finishActivity(token, resultCode, resultData);
+            reply.writeNoException();
+            reply.writeInt(res ? 1 : 0);
+            return true;
+        }
+
+        case FINISH_SUB_ACTIVITY_TRANSACTION: {
+            data.enforceInterface(IActivityManager.descriptor);
+            IBinder token = data.readStrongBinder();
+            String resultWho = data.readString();    
+            int requestCode = data.readInt();
+            finishSubActivity(token, resultWho, requestCode);
+            reply.writeNoException();
+            return true;
+        }
+
+        case REGISTER_RECEIVER_TRANSACTION:
+        {
+            data.enforceInterface(IActivityManager.descriptor);
+            IBinder b = data.readStrongBinder();
+            IApplicationThread app =
+                b != null ? ApplicationThreadNative.asInterface(b) : null;
+            b = data.readStrongBinder();
+            IIntentReceiver rec
+                = b != null ? IIntentReceiver.Stub.asInterface(b) : null;
+            IntentFilter filter = IntentFilter.CREATOR.createFromParcel(data);
+            String perm = data.readString();
+            Intent intent = registerReceiver(app, rec, filter, perm);
+            reply.writeNoException();
+            if (intent != null) {
+                reply.writeInt(1);
+                intent.writeToParcel(reply, 0);
+            } else {
+                reply.writeInt(0);
+            }
+            return true;
+        }
+
+        case UNREGISTER_RECEIVER_TRANSACTION:
+        {
+            data.enforceInterface(IActivityManager.descriptor);
+            IBinder b = data.readStrongBinder();
+            if (b == null) {
+                return true;
+            }
+            IIntentReceiver rec = IIntentReceiver.Stub.asInterface(b);
+            unregisterReceiver(rec);
+            reply.writeNoException();
+            return true;
+        }
+
+        case BROADCAST_INTENT_TRANSACTION:
+        {
+            data.enforceInterface(IActivityManager.descriptor);
+            IBinder b = data.readStrongBinder();
+            IApplicationThread app =
+                b != null ? ApplicationThreadNative.asInterface(b) : null;
+            Intent intent = Intent.CREATOR.createFromParcel(data);
+            String resolvedType = data.readString();
+            b = data.readStrongBinder();
+            IIntentReceiver resultTo =
+                b != null ? IIntentReceiver.Stub.asInterface(b) : null;
+            int resultCode = data.readInt();
+            String resultData = data.readString();
+            Bundle resultExtras = data.readBundle();
+            String perm = data.readString();
+            boolean serialized = data.readInt() != 0;
+            boolean sticky = data.readInt() != 0;
+            int res = broadcastIntent(app, intent, resolvedType, resultTo,
+                    resultCode, resultData, resultExtras, perm,
+                    serialized, sticky);
+            reply.writeNoException();
+            reply.writeInt(res);
+            return true;
+        }
+
+        case UNBROADCAST_INTENT_TRANSACTION:
+        {
+            data.enforceInterface(IActivityManager.descriptor);
+            IBinder b = data.readStrongBinder();
+            IApplicationThread app = b != null ? ApplicationThreadNative.asInterface(b) : null;
+            Intent intent = Intent.CREATOR.createFromParcel(data);
+            unbroadcastIntent(app, intent);
+            reply.writeNoException();
+            return true;
+        }
+
+        case FINISH_RECEIVER_TRANSACTION: {
+            data.enforceInterface(IActivityManager.descriptor);
+            IBinder who = data.readStrongBinder();
+            int resultCode = data.readInt();
+            String resultData = data.readString();
+            Bundle resultExtras = data.readBundle();
+            boolean resultAbort = data.readInt() != 0;
+            if (who != null) {
+                finishReceiver(who, resultCode, resultData, resultExtras, resultAbort);
+            }
+            reply.writeNoException();
+            return true;
+        }
+
+        case SET_PERSISTENT_TRANSACTION: {
+            data.enforceInterface(IActivityManager.descriptor);
+            IBinder token = data.readStrongBinder();
+            boolean isPersistent = data.readInt() != 0;
+            if (token != null) {
+                setPersistent(token, isPersistent);
+            }
+            reply.writeNoException();
+            return true;
+        }
+
+        case ATTACH_APPLICATION_TRANSACTION: {
+            data.enforceInterface(IActivityManager.descriptor);
+            IApplicationThread app = ApplicationThreadNative.asInterface(
+                    data.readStrongBinder());
+            if (app != null) {
+                attachApplication(app);
+            }
+            reply.writeNoException();
+            return true;
+        }
+
+        case ACTIVITY_IDLE_TRANSACTION: {
+            data.enforceInterface(IActivityManager.descriptor);
+            IBinder token = data.readStrongBinder();
+            if (token != null) {
+                activityIdle(token);
+            }
+            reply.writeNoException();
+            return true;
+        }
+
+        case ACTIVITY_PAUSED_TRANSACTION: {
+            data.enforceInterface(IActivityManager.descriptor);
+            IBinder token = data.readStrongBinder();
+            Bundle map = data.readBundle();
+            activityPaused(token, map);
+            reply.writeNoException();
+            return true;
+        }
+
+        case ACTIVITY_STOPPED_TRANSACTION: {
+            data.enforceInterface(IActivityManager.descriptor);
+            IBinder token = data.readStrongBinder();
+            Bitmap thumbnail = data.readInt() != 0
+                ? Bitmap.CREATOR.createFromParcel(data) : null;
+            CharSequence description = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(data);
+            activityStopped(token, thumbnail, description);
+            reply.writeNoException();
+            return true;
+        }
+
+        case ACTIVITY_DESTROYED_TRANSACTION: {
+            data.enforceInterface(IActivityManager.descriptor);
+            IBinder token = data.readStrongBinder();
+            activityDestroyed(token);
+            reply.writeNoException();
+            return true;
+        }
+
+        case GET_CALLING_PACKAGE_TRANSACTION: {
+            data.enforceInterface(IActivityManager.descriptor);
+            IBinder token = data.readStrongBinder();
+            String res = token != null ? getCallingPackage(token) : null;
+            reply.writeNoException();
+            reply.writeString(res);
+            return true;
+        }
+
+        case GET_CALLING_ACTIVITY_TRANSACTION: {
+            data.enforceInterface(IActivityManager.descriptor);
+            IBinder token = data.readStrongBinder();
+            ComponentName cn = getCallingActivity(token);
+            reply.writeNoException();
+            ComponentName.writeToParcel(cn, reply);
+            return true;
+        }
+
+        case GET_TASKS_TRANSACTION: {
+            data.enforceInterface(IActivityManager.descriptor);
+            int maxNum = data.readInt();
+            int fl = data.readInt();
+            IBinder receiverBinder = data.readStrongBinder();
+            IThumbnailReceiver receiver = receiverBinder != null
+                ? IThumbnailReceiver.Stub.asInterface(receiverBinder)
+                : null;
+            List list = getTasks(maxNum, fl, receiver);
+            reply.writeNoException();
+            int N = list != null ? list.size() : -1;
+            reply.writeInt(N);
+            int i;
+            for (i=0; i<N; i++) {
+                ActivityManager.RunningTaskInfo info =
+                        (ActivityManager.RunningTaskInfo)list.get(i);
+                info.writeToParcel(reply, 0);
+            }
+            return true;
+        }
+
+        case GET_RECENT_TASKS_TRANSACTION: {
+            data.enforceInterface(IActivityManager.descriptor);
+            int maxNum = data.readInt();
+            int fl = data.readInt();
+            List<ActivityManager.RecentTaskInfo> list = getRecentTasks(maxNum,
+                    fl);
+            reply.writeNoException();
+            reply.writeTypedList(list);
+            return true;
+        }
+        
+        case GET_SERVICES_TRANSACTION: {
+            data.enforceInterface(IActivityManager.descriptor);
+            int maxNum = data.readInt();
+            int fl = data.readInt();
+            List list = getServices(maxNum, fl);
+            reply.writeNoException();
+            int N = list != null ? list.size() : -1;
+            reply.writeInt(N);
+            int i;
+            for (i=0; i<N; i++) {
+                ActivityManager.RunningServiceInfo info =
+                        (ActivityManager.RunningServiceInfo)list.get(i);
+                info.writeToParcel(reply, 0);
+            }
+            return true;
+        }
+
+        case GET_PROCESSES_IN_ERROR_STATE_TRANSACTION: {
+            data.enforceInterface(IActivityManager.descriptor);
+            List<ActivityManager.ProcessErrorStateInfo> list = getProcessesInErrorState();
+            reply.writeNoException();
+            reply.writeTypedList(list);
+            return true;
+        }
+        
+        case GET_RUNNING_APP_PROCESSES_TRANSACTION: {
+            data.enforceInterface(IActivityManager.descriptor);
+            List<ActivityManager.RunningAppProcessInfo> list = getRunningAppProcesses();
+            reply.writeNoException();
+            reply.writeTypedList(list);
+            return true;
+        }
+
+        case MOVE_TASK_TO_FRONT_TRANSACTION: {
+            data.enforceInterface(IActivityManager.descriptor);
+            int task = data.readInt();
+            moveTaskToFront(task);
+            reply.writeNoException();
+            return true;
+        }
+
+        case MOVE_TASK_TO_BACK_TRANSACTION: {
+            data.enforceInterface(IActivityManager.descriptor);
+            int task = data.readInt();
+            moveTaskToBack(task);
+            reply.writeNoException();
+            return true;
+        }
+
+        case MOVE_ACTIVITY_TASK_TO_BACK_TRANSACTION: {
+            data.enforceInterface(IActivityManager.descriptor);
+            IBinder token = data.readStrongBinder();
+            boolean nonRoot = data.readInt() != 0;
+            boolean res = moveActivityTaskToBack(token, nonRoot);
+            reply.writeNoException();
+            reply.writeInt(res ? 1 : 0);
+            return true;
+        }
+
+        case MOVE_TASK_BACKWARDS_TRANSACTION: {
+            data.enforceInterface(IActivityManager.descriptor);
+            int task = data.readInt();
+            moveTaskBackwards(task);
+            reply.writeNoException();
+            return true;
+        }
+
+        case GET_TASK_FOR_ACTIVITY_TRANSACTION: {
+            data.enforceInterface(IActivityManager.descriptor);
+            IBinder token = data.readStrongBinder();
+            boolean onlyRoot = data.readInt() != 0;
+            int res = token != null
+                ? getTaskForActivity(token, onlyRoot) : -1;
+                reply.writeNoException();
+            reply.writeInt(res);
+            return true;
+        }
+
+        case FINISH_OTHER_INSTANCES_TRANSACTION: {
+            data.enforceInterface(IActivityManager.descriptor);
+            IBinder token = data.readStrongBinder();
+            ComponentName className = ComponentName.readFromParcel(data);
+            finishOtherInstances(token, className);
+            reply.writeNoException();
+            return true;
+        }
+
+        case REPORT_THUMBNAIL_TRANSACTION: {
+            data.enforceInterface(IActivityManager.descriptor);
+            IBinder token = data.readStrongBinder();
+            Bitmap thumbnail = data.readInt() != 0
+                ? Bitmap.CREATOR.createFromParcel(data) : null;
+            CharSequence description = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(data);
+            reportThumbnail(token, thumbnail, description);
+            reply.writeNoException();
+            return true;
+        }
+
+        case GET_CONTENT_PROVIDER_TRANSACTION: {
+            data.enforceInterface(IActivityManager.descriptor);
+            IBinder b = data.readStrongBinder();
+            IApplicationThread app = ApplicationThreadNative.asInterface(b);
+            String name = data.readString();
+            ContentProviderHolder cph = getContentProvider(app, name);
+            reply.writeNoException();
+            if (cph != null) {
+                reply.writeInt(1);
+                cph.writeToParcel(reply, 0);
+            } else {
+                reply.writeInt(0);
+            }
+            return true;
+        }
+
+        case PUBLISH_CONTENT_PROVIDERS_TRANSACTION: {
+            data.enforceInterface(IActivityManager.descriptor);
+            IBinder b = data.readStrongBinder();
+            IApplicationThread app = ApplicationThreadNative.asInterface(b);
+            ArrayList<ContentProviderHolder> providers =
+                data.createTypedArrayList(ContentProviderHolder.CREATOR);
+            publishContentProviders(app, providers);
+            reply.writeNoException();
+            return true;
+        }
+
+        case REMOVE_CONTENT_PROVIDER_TRANSACTION: {
+            data.enforceInterface(IActivityManager.descriptor);
+            IBinder b = data.readStrongBinder();
+            IApplicationThread app = ApplicationThreadNative.asInterface(b);
+            String name = data.readString();
+            removeContentProvider(app, name);
+            reply.writeNoException();
+            return true;
+        }
+        
+        case START_SERVICE_TRANSACTION: {
+            data.enforceInterface(IActivityManager.descriptor);
+            IBinder b = data.readStrongBinder();
+            IApplicationThread app = ApplicationThreadNative.asInterface(b);
+            Intent service = Intent.CREATOR.createFromParcel(data);
+            String resolvedType = data.readString();
+            ComponentName cn = startService(app, service, resolvedType);
+            reply.writeNoException();
+            ComponentName.writeToParcel(cn, reply);
+            return true;
+        }
+
+        case STOP_SERVICE_TRANSACTION: {
+            data.enforceInterface(IActivityManager.descriptor);
+            IBinder b = data.readStrongBinder();
+            IApplicationThread app = ApplicationThreadNative.asInterface(b);
+            Intent service = Intent.CREATOR.createFromParcel(data);
+            String resolvedType = data.readString();
+            int res = stopService(app, service, resolvedType);
+            reply.writeNoException();
+            reply.writeInt(res);
+            return true;
+        }
+
+        case STOP_SERVICE_TOKEN_TRANSACTION: {
+            data.enforceInterface(IActivityManager.descriptor);
+            ComponentName className = ComponentName.readFromParcel(data);
+            IBinder token = data.readStrongBinder();
+            int startId = data.readInt();
+            boolean res = stopServiceToken(className, token, startId);
+            reply.writeNoException();
+            reply.writeInt(res ? 1 : 0);
+            return true;
+        }
+
+        case SET_SERVICE_FOREGROUND_TRANSACTION: {
+            data.enforceInterface(IActivityManager.descriptor);
+            ComponentName className = ComponentName.readFromParcel(data);
+            IBinder token = data.readStrongBinder();
+            boolean isForeground = data.readInt() != 0;
+            setServiceForeground(className, token, isForeground);
+            reply.writeNoException();
+            return true;
+        }
+
+        case BIND_SERVICE_TRANSACTION: {
+            data.enforceInterface(IActivityManager.descriptor);
+            IBinder b = data.readStrongBinder();
+            IApplicationThread app = ApplicationThreadNative.asInterface(b);
+            IBinder token = data.readStrongBinder();
+            Intent service = Intent.CREATOR.createFromParcel(data);
+            String resolvedType = data.readString();
+            b = data.readStrongBinder();
+            int fl = data.readInt();
+            IServiceConnection conn = IServiceConnection.Stub.asInterface(b);
+            int res = bindService(app, token, service, resolvedType, conn, fl);
+            reply.writeNoException();
+            reply.writeInt(res);
+            return true;
+        }
+
+        case UNBIND_SERVICE_TRANSACTION: {
+            data.enforceInterface(IActivityManager.descriptor);
+            IBinder b = data.readStrongBinder();
+            IServiceConnection conn = IServiceConnection.Stub.asInterface(b);
+            boolean res = unbindService(conn);
+            reply.writeNoException();
+            reply.writeInt(res ? 1 : 0);
+            return true;
+        }
+
+        case PUBLISH_SERVICE_TRANSACTION: {
+            data.enforceInterface(IActivityManager.descriptor);
+            IBinder token = data.readStrongBinder();
+            Intent intent = Intent.CREATOR.createFromParcel(data);
+            IBinder service = data.readStrongBinder();
+            publishService(token, intent, service);
+            reply.writeNoException();
+            return true;
+        }
+
+        case UNBIND_FINISHED_TRANSACTION: {
+            data.enforceInterface(IActivityManager.descriptor);
+            IBinder token = data.readStrongBinder();
+            Intent intent = Intent.CREATOR.createFromParcel(data);
+            boolean doRebind = data.readInt() != 0;
+            unbindFinished(token, intent, doRebind);
+            reply.writeNoException();
+            return true;
+        }
+
+        case SERVICE_DONE_EXECUTING_TRANSACTION: {
+            data.enforceInterface(IActivityManager.descriptor);
+            IBinder token = data.readStrongBinder();
+            serviceDoneExecuting(token);
+            reply.writeNoException();
+            return true;
+        }
+
+        case START_INSTRUMENTATION_TRANSACTION: {
+            data.enforceInterface(IActivityManager.descriptor);
+            ComponentName className = ComponentName.readFromParcel(data);
+            String profileFile = data.readString();
+            int fl = data.readInt();
+            Bundle arguments = data.readBundle();
+            IBinder b = data.readStrongBinder();
+            IInstrumentationWatcher w = IInstrumentationWatcher.Stub.asInterface(b);
+            boolean res = startInstrumentation(className, profileFile, fl, arguments, w);
+            reply.writeNoException();
+            reply.writeInt(res ? 1 : 0);
+            return true;
+        }
+
+
+        case FINISH_INSTRUMENTATION_TRANSACTION: {
+            data.enforceInterface(IActivityManager.descriptor);
+            IBinder b = data.readStrongBinder();
+            IApplicationThread app = ApplicationThreadNative.asInterface(b);
+            int resultCode = data.readInt();
+            Bundle results = data.readBundle();
+            finishInstrumentation(app, resultCode, results);
+            reply.writeNoException();
+            return true;
+        }
+
+        case GET_CONFIGURATION_TRANSACTION: {
+            data.enforceInterface(IActivityManager.descriptor);
+            Configuration config = getConfiguration();
+            reply.writeNoException();
+            config.writeToParcel(reply, 0);
+            return true;
+        }
+
+        case UPDATE_CONFIGURATION_TRANSACTION: {
+            data.enforceInterface(IActivityManager.descriptor);
+            Configuration config = Configuration.CREATOR.createFromParcel(data);
+            updateConfiguration(config);
+            reply.writeNoException();
+            return true;
+        }
+
+        case SET_REQUESTED_ORIENTATION_TRANSACTION: {
+            data.enforceInterface(IActivityManager.descriptor);
+            IBinder token = data.readStrongBinder();
+            int requestedOrientation = data.readInt();
+            setRequestedOrientation(token, requestedOrientation);
+            reply.writeNoException();
+            return true;
+        }
+
+        case GET_REQUESTED_ORIENTATION_TRANSACTION: {
+            data.enforceInterface(IActivityManager.descriptor);
+            IBinder token = data.readStrongBinder();
+            int req = getRequestedOrientation(token);
+            reply.writeNoException();
+            reply.writeInt(req);
+            return true;
+        }
+
+        case GET_ACTIVITY_CLASS_FOR_TOKEN_TRANSACTION: {
+            data.enforceInterface(IActivityManager.descriptor);
+            IBinder token = data.readStrongBinder();
+            ComponentName cn = getActivityClassForToken(token);
+            reply.writeNoException();
+            ComponentName.writeToParcel(cn, reply);
+            return true;
+        }
+
+        case GET_PACKAGE_FOR_TOKEN_TRANSACTION: {
+            data.enforceInterface(IActivityManager.descriptor);
+            IBinder token = data.readStrongBinder();
+            reply.writeNoException();
+            reply.writeString(getPackageForToken(token));
+            return true;
+        }
+
+        case GET_INTENT_SENDER_TRANSACTION: {
+            data.enforceInterface(IActivityManager.descriptor);
+            int type = data.readInt();
+            String packageName = data.readString();
+            IBinder token = data.readStrongBinder();
+            String resultWho = data.readString();
+            int requestCode = data.readInt();
+            Intent requestIntent = data.readInt() != 0
+                    ? Intent.CREATOR.createFromParcel(data) : null;
+            String requestResolvedType = data.readString();
+            int fl = data.readInt();
+            IIntentSender res = getIntentSender(type, packageName, token,
+                    resultWho, requestCode, requestIntent,
+                    requestResolvedType, fl);
+            reply.writeNoException();
+            reply.writeStrongBinder(res != null ? res.asBinder() : null);
+            return true;
+        }
+
+        case CANCEL_INTENT_SENDER_TRANSACTION: {
+            data.enforceInterface(IActivityManager.descriptor);
+            IIntentSender r = IIntentSender.Stub.asInterface(
+                data.readStrongBinder());
+            cancelIntentSender(r);
+            reply.writeNoException();
+            return true;
+        }
+
+        case GET_PACKAGE_FOR_INTENT_SENDER_TRANSACTION: {
+            data.enforceInterface(IActivityManager.descriptor);
+            IIntentSender r = IIntentSender.Stub.asInterface(
+                data.readStrongBinder());
+            String res = getPackageForIntentSender(r);
+            reply.writeNoException();
+            reply.writeString(res);
+            return true;
+        }
+
+        case SET_PROCESS_LIMIT_TRANSACTION: {
+            data.enforceInterface(IActivityManager.descriptor);
+            int max = data.readInt();
+            setProcessLimit(max);
+            reply.writeNoException();
+            return true;
+        }
+
+        case GET_PROCESS_LIMIT_TRANSACTION: {
+            data.enforceInterface(IActivityManager.descriptor);
+            int limit = getProcessLimit();
+            reply.writeNoException();
+            reply.writeInt(limit);
+            return true;
+        }
+
+        case SET_PROCESS_FOREGROUND_TRANSACTION: {
+            data.enforceInterface(IActivityManager.descriptor);
+            IBinder token = data.readStrongBinder();
+            int pid = data.readInt();
+            boolean isForeground = data.readInt() != 0;
+            setProcessForeground(token, pid, isForeground);
+            reply.writeNoException();
+            return true;
+        }
+
+        case CHECK_PERMISSION_TRANSACTION: {
+            data.enforceInterface(IActivityManager.descriptor);
+            String perm = data.readString();
+            int pid = data.readInt();
+            int uid = data.readInt();
+            int res = checkPermission(perm, pid, uid);
+            reply.writeNoException();
+            reply.writeInt(res);
+            return true;
+        }
+
+        case CHECK_URI_PERMISSION_TRANSACTION: {
+            data.enforceInterface(IActivityManager.descriptor);
+            Uri uri = Uri.CREATOR.createFromParcel(data);
+            int pid = data.readInt();
+            int uid = data.readInt();
+            int mode = data.readInt();
+            int res = checkUriPermission(uri, pid, uid, mode);
+            reply.writeNoException();
+            reply.writeInt(res);
+            return true;
+        }
+        
+        case CLEAR_APP_DATA_TRANSACTION: {
+            data.enforceInterface(IActivityManager.descriptor);            
+            String packageName = data.readString();
+            IPackageDataObserver observer = IPackageDataObserver.Stub.asInterface(
+                    data.readStrongBinder());
+            boolean res = clearApplicationUserData(packageName, observer);
+            reply.writeNoException();
+            reply.writeInt(res ? 1 : 0);
+            return true;
+        }
+        
+        case GRANT_URI_PERMISSION_TRANSACTION: {
+            data.enforceInterface(IActivityManager.descriptor);
+            IBinder b = data.readStrongBinder();
+            IApplicationThread app = ApplicationThreadNative.asInterface(b);
+            String targetPkg = data.readString();
+            Uri uri = Uri.CREATOR.createFromParcel(data);
+            int mode = data.readInt();
+            grantUriPermission(app, targetPkg, uri, mode);
+            reply.writeNoException();
+            return true;
+        }
+        
+        case REVOKE_URI_PERMISSION_TRANSACTION: {
+            data.enforceInterface(IActivityManager.descriptor);
+            IBinder b = data.readStrongBinder();
+            IApplicationThread app = ApplicationThreadNative.asInterface(b);
+            Uri uri = Uri.CREATOR.createFromParcel(data);
+            int mode = data.readInt();
+            revokeUriPermission(app, uri, mode);
+            reply.writeNoException();
+            return true;
+        }
+        
+        case SHOW_WAITING_FOR_DEBUGGER_TRANSACTION: {
+            data.enforceInterface(IActivityManager.descriptor);
+            IBinder b = data.readStrongBinder();
+            IApplicationThread app = ApplicationThreadNative.asInterface(b);
+            boolean waiting = data.readInt() != 0;
+            showWaitingForDebugger(app, waiting);
+            reply.writeNoException();
+            return true;
+        }
+
+        case GET_MEMORY_INFO_TRANSACTION: {
+            data.enforceInterface(IActivityManager.descriptor);
+            ActivityManager.MemoryInfo mi = new ActivityManager.MemoryInfo();
+            getMemoryInfo(mi);
+            reply.writeNoException();
+            mi.writeToParcel(reply, 0);
+            return true;
+        }
+
+        case UNHANDLED_BACK_TRANSACTION: {
+            data.enforceInterface(IActivityManager.descriptor);
+            unhandledBack();
+            reply.writeNoException();
+            return true;
+        }
+
+        case OPEN_CONTENT_URI_TRANSACTION: {
+            data.enforceInterface(IActivityManager.descriptor);
+            Uri uri = Uri.parse(data.readString());
+            ParcelFileDescriptor pfd = openContentUri(uri);
+            reply.writeNoException();
+            if (pfd != null) {
+                reply.writeInt(1);
+                pfd.writeToParcel(reply, Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
+            } else {
+                reply.writeInt(0);
+            }
+            return true;
+        }
+        
+        case GOING_TO_SLEEP_TRANSACTION: {
+            data.enforceInterface(IActivityManager.descriptor);
+            goingToSleep();
+            reply.writeNoException();
+            return true;
+        }
+
+        case WAKING_UP_TRANSACTION: {
+            data.enforceInterface(IActivityManager.descriptor);
+            wakingUp();
+            reply.writeNoException();
+            return true;
+        }
+
+        case SET_DEBUG_APP_TRANSACTION: {
+            data.enforceInterface(IActivityManager.descriptor);
+            String pn = data.readString();
+            boolean wfd = data.readInt() != 0;
+            boolean per = data.readInt() != 0;
+            setDebugApp(pn, wfd, per);
+            reply.writeNoException();
+            return true;
+        }
+
+        case SET_ALWAYS_FINISH_TRANSACTION: {
+            data.enforceInterface(IActivityManager.descriptor);
+            boolean enabled = data.readInt() != 0;
+            setAlwaysFinish(enabled);
+            reply.writeNoException();
+            return true;
+        }
+
+        case SET_ACTIVITY_WATCHER_TRANSACTION: {
+            data.enforceInterface(IActivityManager.descriptor);
+            IActivityWatcher watcher = IActivityWatcher.Stub.asInterface(
+                    data.readStrongBinder());
+            setActivityWatcher(watcher);
+            return true;
+        }
+
+        case ENTER_SAFE_MODE_TRANSACTION: {
+            data.enforceInterface(IActivityManager.descriptor);
+            enterSafeMode();
+            reply.writeNoException();
+            return true;
+        }
+
+        case NOTE_WAKEUP_ALARM_TRANSACTION: {
+            data.enforceInterface(IActivityManager.descriptor);
+            IIntentSender is = IIntentSender.Stub.asInterface(
+                    data.readStrongBinder());
+            noteWakeupAlarm(is);
+            reply.writeNoException();
+            return true;
+        }
+
+        case KILL_PIDS_FOR_MEMORY_TRANSACTION: {
+            data.enforceInterface(IActivityManager.descriptor);
+            int[] pids = data.createIntArray();
+            boolean res = killPidsForMemory(pids);
+            reply.writeNoException();
+            reply.writeInt(res ? 1 : 0);
+            return true;
+        }
+
+        case REPORT_PSS_TRANSACTION: {
+            data.enforceInterface(IActivityManager.descriptor);
+            IBinder b = data.readStrongBinder();
+            IApplicationThread app = ApplicationThreadNative.asInterface(b);
+            int pss = data.readInt();
+            reportPss(app, pss);
+            reply.writeNoException();
+            return true;
+        }
+
+        case START_RUNNING_TRANSACTION: {
+            data.enforceInterface(IActivityManager.descriptor);
+            String pkg = data.readString();
+            String cls = data.readString();
+            String action = data.readString();
+            String indata = data.readString();
+            startRunning(pkg, cls, action, indata);
+            reply.writeNoException();
+            return true;
+        }
+
+        case SYSTEM_READY_TRANSACTION: {
+            data.enforceInterface(IActivityManager.descriptor);
+            systemReady();
+            reply.writeNoException();
+            return true;
+        }
+
+        case HANDLE_APPLICATION_ERROR_TRANSACTION: {
+            data.enforceInterface(IActivityManager.descriptor);
+            IBinder app = data.readStrongBinder();
+            int fl = data.readInt();
+            String tag = data.readString();
+            String shortMsg = data.readString();
+            String longMsg = data.readString();
+            byte[] crashData = data.createByteArray();
+            int res = handleApplicationError(app, fl, tag, shortMsg, longMsg,
+                    crashData);
+            reply.writeNoException();
+            reply.writeInt(res);
+            return true;
+        }
+        
+        case SIGNAL_PERSISTENT_PROCESSES_TRANSACTION: {
+            data.enforceInterface(IActivityManager.descriptor);
+            int sig = data.readInt();
+            signalPersistentProcesses(sig);
+            reply.writeNoException();
+            return true;
+        }
+
+        case RESTART_PACKAGE_TRANSACTION: {
+            data.enforceInterface(IActivityManager.descriptor);            
+            String packageName = data.readString();
+            restartPackage(packageName);
+            reply.writeNoException();
+            return true;
+        }
+        
+        case GET_DEVICE_CONFIGURATION_TRANSACTION: {
+            data.enforceInterface(IActivityManager.descriptor);
+            ConfigurationInfo config = getDeviceConfigurationInfo();
+            reply.writeNoException();
+            config.writeToParcel(reply, 0);
+            return true;
+        }
+        
+        case PEEK_SERVICE_TRANSACTION: {
+            data.enforceInterface(IActivityManager.descriptor);
+            Intent service = Intent.CREATOR.createFromParcel(data);
+            String resolvedType = data.readString();
+            IBinder binder = peekService(service, resolvedType);
+            reply.writeNoException();
+            reply.writeStrongBinder(binder);
+            return true;
+        }
+        }
+        
+        return super.onTransact(code, data, reply, flags);
+    }
+
+    public IBinder asBinder()
+    {
+        return this;
+    }
+
+    private static IActivityManager gDefault;
+}
+
+class ActivityManagerProxy implements IActivityManager
+{
+    public ActivityManagerProxy(IBinder remote)
+    {
+        mRemote = remote;
+    }
+    
+    public IBinder asBinder()
+    {
+        return mRemote;
+    }
+    
+    public int startActivity(IApplicationThread caller, Intent intent,
+            String resolvedType, Uri[] grantedUriPermissions, int grantedMode,
+            IBinder resultTo, String resultWho,
+            int requestCode, boolean onlyIfNeeded,
+            boolean debug) throws RemoteException {
+        Parcel data = Parcel.obtain();
+        Parcel reply = Parcel.obtain();
+        data.writeInterfaceToken(IActivityManager.descriptor);
+        data.writeStrongBinder(caller != null ? caller.asBinder() : null);
+        intent.writeToParcel(data, 0);
+        data.writeString(resolvedType);
+        data.writeTypedArray(grantedUriPermissions, 0);
+        data.writeInt(grantedMode);
+        data.writeStrongBinder(resultTo);
+        data.writeString(resultWho);
+        data.writeInt(requestCode);
+        data.writeInt(onlyIfNeeded ? 1 : 0);
+        data.writeInt(debug ? 1 : 0);
+        mRemote.transact(START_ACTIVITY_TRANSACTION, data, reply, 0);
+        reply.readException();
+        int result = reply.readInt();
+        reply.recycle();
+        data.recycle();
+        return result;
+    }
+    public boolean startNextMatchingActivity(IBinder callingActivity,
+            Intent intent) throws RemoteException {
+        Parcel data = Parcel.obtain();
+        Parcel reply = Parcel.obtain();
+        data.writeInterfaceToken(IActivityManager.descriptor);
+        data.writeStrongBinder(callingActivity);
+        intent.writeToParcel(data, 0);
+        mRemote.transact(START_NEXT_MATCHING_ACTIVITY_TRANSACTION, data, reply, 0);
+        reply.readException();
+        int result = reply.readInt();
+        reply.recycle();
+        data.recycle();
+        return result != 0;
+    }
+    public boolean finishActivity(IBinder token, int resultCode, Intent resultData)
+            throws RemoteException {
+        Parcel data = Parcel.obtain();
+        Parcel reply = Parcel.obtain();
+        data.writeInterfaceToken(IActivityManager.descriptor);
+        data.writeStrongBinder(token);
+        data.writeInt(resultCode);
+        if (resultData != null) {
+            data.writeInt(1);
+            resultData.writeToParcel(data, 0);
+        } else {
+            data.writeInt(0);
+        }
+        mRemote.transact(FINISH_ACTIVITY_TRANSACTION, data, reply, 0);
+        reply.readException();
+        boolean res = reply.readInt() != 0;
+        data.recycle();
+        reply.recycle();
+        return res;
+    }
+    public void finishSubActivity(IBinder token, String resultWho, int requestCode) throws RemoteException
+    {
+        Parcel data = Parcel.obtain();
+        Parcel reply = Parcel.obtain();
+        data.writeInterfaceToken(IActivityManager.descriptor);
+        data.writeStrongBinder(token);
+        data.writeString(resultWho);
+        data.writeInt(requestCode);
+        mRemote.transact(FINISH_SUB_ACTIVITY_TRANSACTION, data, reply, 0);
+        reply.readException();
+        data.recycle();
+        reply.recycle();
+    }
+    public Intent registerReceiver(IApplicationThread caller,
+            IIntentReceiver receiver,
+            IntentFilter filter, String perm) throws RemoteException
+    {
+        Parcel data = Parcel.obtain();
+        Parcel reply = Parcel.obtain();
+        data.writeInterfaceToken(IActivityManager.descriptor);
+        data.writeStrongBinder(caller != null ? caller.asBinder() : null);
+        data.writeStrongBinder(receiver != null ? receiver.asBinder() : null);
+        filter.writeToParcel(data, 0);
+        data.writeString(perm);
+        mRemote.transact(REGISTER_RECEIVER_TRANSACTION, data, reply, 0);
+        reply.readException();
+        Intent intent = null;
+        int haveIntent = reply.readInt();
+        if (haveIntent != 0) {
+            intent = Intent.CREATOR.createFromParcel(reply);
+        }
+        reply.recycle();
+        data.recycle();
+        return intent;
+    }
+    public void unregisterReceiver(IIntentReceiver receiver) throws RemoteException
+    {
+        Parcel data = Parcel.obtain();
+        Parcel reply = Parcel.obtain();
+        data.writeInterfaceToken(IActivityManager.descriptor);
+        data.writeStrongBinder(receiver.asBinder());
+        mRemote.transact(UNREGISTER_RECEIVER_TRANSACTION, data, reply, 0);
+        reply.readException();
+        data.recycle();
+        reply.recycle();
+    }
+    public int broadcastIntent(IApplicationThread caller,
+            Intent intent, String resolvedType,  IIntentReceiver resultTo,
+            int resultCode, String resultData, Bundle map,
+            String requiredPermission, boolean serialized,
+            boolean sticky) throws RemoteException
+    {
+        Parcel data = Parcel.obtain();
+        Parcel reply = Parcel.obtain();
+        data.writeInterfaceToken(IActivityManager.descriptor);
+        data.writeStrongBinder(caller != null ? caller.asBinder() : null);
+        intent.writeToParcel(data, 0);
+        data.writeString(resolvedType);
+        data.writeStrongBinder(resultTo != null ? resultTo.asBinder() : null);
+        data.writeInt(resultCode);
+        data.writeString(resultData);
+        data.writeBundle(map);
+        data.writeString(requiredPermission);
+        data.writeInt(serialized ? 1 : 0);
+        data.writeInt(sticky ? 1 : 0);
+        mRemote.transact(BROADCAST_INTENT_TRANSACTION, data, reply, 0);
+        reply.readException();
+        int res = reply.readInt();
+        reply.recycle();
+        data.recycle();
+        return res;
+    }
+    public void unbroadcastIntent(IApplicationThread caller, Intent intent) throws RemoteException
+    {
+        Parcel data = Parcel.obtain();
+        Parcel reply = Parcel.obtain();
+        data.writeInterfaceToken(IActivityManager.descriptor);
+        data.writeStrongBinder(caller != null ? caller.asBinder() : null);
+        intent.writeToParcel(data, 0);
+        mRemote.transact(UNBROADCAST_INTENT_TRANSACTION, data, reply, 0);
+        reply.readException();
+        data.recycle();
+        reply.recycle();
+    }
+    public void finishReceiver(IBinder who, int resultCode, String resultData, Bundle map, boolean abortBroadcast) throws RemoteException
+    {
+        Parcel data = Parcel.obtain();
+        Parcel reply = Parcel.obtain();
+        data.writeInterfaceToken(IActivityManager.descriptor);
+        data.writeStrongBinder(who);
+        data.writeInt(resultCode);
+        data.writeString(resultData);
+        data.writeBundle(map);
+        data.writeInt(abortBroadcast ? 1 : 0);
+        mRemote.transact(FINISH_RECEIVER_TRANSACTION, data, reply, IBinder.FLAG_ONEWAY);
+        reply.readException();
+        data.recycle();
+        reply.recycle();
+    }
+    public void setPersistent(IBinder token, boolean isPersistent) throws RemoteException
+    {
+        Parcel data = Parcel.obtain();
+        Parcel reply = Parcel.obtain();
+        data.writeInterfaceToken(IActivityManager.descriptor);
+        data.writeStrongBinder(token);
+        data.writeInt(isPersistent ? 1 : 0);
+        mRemote.transact(SET_PERSISTENT_TRANSACTION, data, reply, 0);
+        reply.readException();
+        data.recycle();
+        reply.recycle();
+    }
+    public void attachApplication(IApplicationThread app) throws RemoteException
+    {
+        Parcel data = Parcel.obtain();
+        Parcel reply = Parcel.obtain();
+        data.writeInterfaceToken(IActivityManager.descriptor);
+        data.writeStrongBinder(app.asBinder());
+        mRemote.transact(ATTACH_APPLICATION_TRANSACTION, data, reply, 0);
+        reply.readException();
+        data.recycle();
+        reply.recycle();
+    }
+    public void activityIdle(IBinder token) throws RemoteException
+    {
+        Parcel data = Parcel.obtain();
+        Parcel reply = Parcel.obtain();
+        data.writeInterfaceToken(IActivityManager.descriptor);
+        data.writeStrongBinder(token);
+        mRemote.transact(ACTIVITY_IDLE_TRANSACTION, data, reply, IBinder.FLAG_ONEWAY);
+        reply.readException();
+        data.recycle();
+        reply.recycle();
+    }
+    public void activityPaused(IBinder token, Bundle state) throws RemoteException
+    {
+        Parcel data = Parcel.obtain();
+        Parcel reply = Parcel.obtain();
+        data.writeInterfaceToken(IActivityManager.descriptor);
+        data.writeStrongBinder(token);
+        data.writeBundle(state);
+        mRemote.transact(ACTIVITY_PAUSED_TRANSACTION, data, reply, 0);
+        reply.readException();
+        data.recycle();
+        reply.recycle();
+    }
+    public void activityStopped(IBinder token,
+                                Bitmap thumbnail, CharSequence description) throws RemoteException
+    {
+        Parcel data = Parcel.obtain();
+        Parcel reply = Parcel.obtain();
+        data.writeInterfaceToken(IActivityManager.descriptor);
+        data.writeStrongBinder(token);
+        if (thumbnail != null) {
+            data.writeInt(1);
+            thumbnail.writeToParcel(data, 0);
+        } else {
+            data.writeInt(0);
+        }
+        TextUtils.writeToParcel(description, data, 0);
+        mRemote.transact(ACTIVITY_STOPPED_TRANSACTION, data, reply, IBinder.FLAG_ONEWAY);
+        reply.readException();
+        data.recycle();
+        reply.recycle();
+    }
+    public void activityDestroyed(IBinder token) throws RemoteException
+    {
+        Parcel data = Parcel.obtain();
+        Parcel reply = Parcel.obtain();
+        data.writeInterfaceToken(IActivityManager.descriptor);
+        data.writeStrongBinder(token);
+        mRemote.transact(ACTIVITY_DESTROYED_TRANSACTION, data, reply, IBinder.FLAG_ONEWAY);
+        reply.readException();
+        data.recycle();
+        reply.recycle();
+    }
+    public String getCallingPackage(IBinder token) throws RemoteException
+    {
+        Parcel data = Parcel.obtain();
+        Parcel reply = Parcel.obtain();
+        data.writeInterfaceToken(IActivityManager.descriptor);
+        data.writeStrongBinder(token);
+        mRemote.transact(GET_CALLING_PACKAGE_TRANSACTION, data, reply, 0);
+        reply.readException();
+        String res = reply.readString();
+        data.recycle();
+        reply.recycle();
+        return res;
+    }
+    public ComponentName getCallingActivity(IBinder token)
+            throws RemoteException {
+        Parcel data = Parcel.obtain();
+        Parcel reply = Parcel.obtain();
+        data.writeInterfaceToken(IActivityManager.descriptor);
+        data.writeStrongBinder(token);
+        mRemote.transact(GET_CALLING_ACTIVITY_TRANSACTION, data, reply, 0);
+        reply.readException();
+        ComponentName res = ComponentName.readFromParcel(reply);
+        data.recycle();
+        reply.recycle();
+        return res;
+    }
+    public List getTasks(int maxNum, int flags,
+            IThumbnailReceiver receiver) throws RemoteException {
+        Parcel data = Parcel.obtain();
+        Parcel reply = Parcel.obtain();
+        data.writeInterfaceToken(IActivityManager.descriptor);
+        data.writeInt(maxNum);
+        data.writeInt(flags);
+        data.writeStrongBinder(receiver != null ? receiver.asBinder() : null);
+        mRemote.transact(GET_TASKS_TRANSACTION, data, reply, 0);
+        reply.readException();
+        ArrayList list = null;
+        int N = reply.readInt();
+        if (N >= 0) {
+            list = new ArrayList();
+            while (N > 0) {
+                ActivityManager.RunningTaskInfo info =
+                        ActivityManager.RunningTaskInfo.CREATOR
+                        .createFromParcel(reply);
+                list.add(info);
+                N--;
+            }
+        }
+        data.recycle();
+        reply.recycle();
+        return list;
+    }
+    public List<ActivityManager.RecentTaskInfo> getRecentTasks(int maxNum,
+            int flags) throws RemoteException {
+        Parcel data = Parcel.obtain();
+        Parcel reply = Parcel.obtain();
+        data.writeInterfaceToken(IActivityManager.descriptor);
+        data.writeInt(maxNum);
+        data.writeInt(flags);
+        mRemote.transact(GET_RECENT_TASKS_TRANSACTION, data, reply, 0);
+        reply.readException();
+        ArrayList<ActivityManager.RecentTaskInfo> list
+            = reply.createTypedArrayList(ActivityManager.RecentTaskInfo.CREATOR);
+        data.recycle();
+        reply.recycle();
+        return list;
+    }
+    public List getServices(int maxNum, int flags) throws RemoteException {
+        Parcel data = Parcel.obtain();
+        Parcel reply = Parcel.obtain();
+        data.writeInterfaceToken(IActivityManager.descriptor);
+        data.writeInt(maxNum);
+        data.writeInt(flags);
+        mRemote.transact(GET_SERVICES_TRANSACTION, data, reply, 0);
+        reply.readException();
+        ArrayList list = null;
+        int N = reply.readInt();
+        if (N >= 0) {
+            list = new ArrayList();
+            while (N > 0) {
+                ActivityManager.RunningServiceInfo info =
+                        ActivityManager.RunningServiceInfo.CREATOR
+                        .createFromParcel(reply);
+                list.add(info);
+                N--;
+            }
+        }
+        data.recycle();
+        reply.recycle();
+        return list;
+    }
+    public List<ActivityManager.ProcessErrorStateInfo> getProcessesInErrorState()
+            throws RemoteException {
+        Parcel data = Parcel.obtain();
+        Parcel reply = Parcel.obtain();
+        data.writeInterfaceToken(IActivityManager.descriptor);
+        mRemote.transact(GET_PROCESSES_IN_ERROR_STATE_TRANSACTION, data, reply, 0);
+        reply.readException();
+        ArrayList<ActivityManager.ProcessErrorStateInfo> list
+            = reply.createTypedArrayList(ActivityManager.ProcessErrorStateInfo.CREATOR);
+        data.recycle();
+        reply.recycle();
+        return list;
+    }
+    public List<ActivityManager.RunningAppProcessInfo> getRunningAppProcesses()
+            throws RemoteException {
+        Parcel data = Parcel.obtain();
+        Parcel reply = Parcel.obtain();
+        data.writeInterfaceToken(IActivityManager.descriptor);
+        mRemote.transact(GET_RUNNING_APP_PROCESSES_TRANSACTION, data, reply, 0);
+        reply.readException();
+        ArrayList<ActivityManager.RunningAppProcessInfo> list
+        = reply.createTypedArrayList(ActivityManager.RunningAppProcessInfo.CREATOR);
+        data.recycle();
+        reply.recycle();
+        return list;
+    }
+    public void moveTaskToFront(int task) throws RemoteException
+    {
+        Parcel data = Parcel.obtain();
+        Parcel reply = Parcel.obtain();
+        data.writeInterfaceToken(IActivityManager.descriptor);
+        data.writeInt(task);
+        mRemote.transact(MOVE_TASK_TO_FRONT_TRANSACTION, data, reply, 0);
+        reply.readException();
+        data.recycle();
+        reply.recycle();
+    }
+    public void moveTaskToBack(int task) throws RemoteException
+    {
+        Parcel data = Parcel.obtain();
+        Parcel reply = Parcel.obtain();
+        data.writeInterfaceToken(IActivityManager.descriptor);
+        data.writeInt(task);
+        mRemote.transact(MOVE_TASK_TO_BACK_TRANSACTION, data, reply, 0);
+        reply.readException();
+        data.recycle();
+        reply.recycle();
+    }
+    public boolean moveActivityTaskToBack(IBinder token, boolean nonRoot)
+            throws RemoteException {
+        Parcel data = Parcel.obtain();
+        Parcel reply = Parcel.obtain();
+        data.writeInterfaceToken(IActivityManager.descriptor);
+        data.writeStrongBinder(token);
+        data.writeInt(nonRoot ? 1 : 0);
+        mRemote.transact(MOVE_ACTIVITY_TASK_TO_BACK_TRANSACTION, data, reply, 0);
+        reply.readException();
+        boolean res = reply.readInt() != 0;
+        data.recycle();
+        reply.recycle();
+        return res;
+    }
+    public void moveTaskBackwards(int task) throws RemoteException
+    {
+        Parcel data = Parcel.obtain();
+        Parcel reply = Parcel.obtain();
+        data.writeInterfaceToken(IActivityManager.descriptor);
+        data.writeInt(task);
+        mRemote.transact(MOVE_TASK_BACKWARDS_TRANSACTION, data, reply, 0);
+        reply.readException();
+        data.recycle();
+        reply.recycle();
+    }
+    public int getTaskForActivity(IBinder token, boolean onlyRoot) throws RemoteException
+    {
+        Parcel data = Parcel.obtain();
+        Parcel reply = Parcel.obtain();
+        data.writeInterfaceToken(IActivityManager.descriptor);
+        data.writeStrongBinder(token);
+        data.writeInt(onlyRoot ? 1 : 0);
+        mRemote.transact(GET_TASK_FOR_ACTIVITY_TRANSACTION, data, reply, 0);
+        reply.readException();
+        int res = reply.readInt();
+        data.recycle();
+        reply.recycle();
+        return res;
+    }
+    public void finishOtherInstances(IBinder token, ComponentName className) throws RemoteException
+    {
+        Parcel data = Parcel.obtain();
+        Parcel reply = Parcel.obtain();
+        data.writeInterfaceToken(IActivityManager.descriptor);
+        data.writeStrongBinder(token);
+        ComponentName.writeToParcel(className, data);
+        mRemote.transact(FINISH_OTHER_INSTANCES_TRANSACTION, data, reply, 0);
+        reply.readException();
+        data.recycle();
+        reply.recycle();
+    }
+    public void reportThumbnail(IBinder token,
+                                Bitmap thumbnail, CharSequence description) throws RemoteException
+    {
+        Parcel data = Parcel.obtain();
+        Parcel reply = Parcel.obtain();
+        data.writeInterfaceToken(IActivityManager.descriptor);
+        data.writeStrongBinder(token);
+        if (thumbnail != null) {
+            data.writeInt(1);
+            thumbnail.writeToParcel(data, 0);
+        } else {
+            data.writeInt(0);
+        }
+        TextUtils.writeToParcel(description, data, 0);
+        mRemote.transact(REPORT_THUMBNAIL_TRANSACTION, data, reply, IBinder.FLAG_ONEWAY);
+        reply.readException();
+        data.recycle();
+        reply.recycle();
+    }
+    public ContentProviderHolder getContentProvider(IApplicationThread caller,
+                                                    String name) throws RemoteException
+    {
+        Parcel data = Parcel.obtain();
+        Parcel reply = Parcel.obtain();
+        data.writeInterfaceToken(IActivityManager.descriptor);
+        data.writeStrongBinder(caller != null ? caller.asBinder() : null);
+        data.writeString(name);
+        mRemote.transact(GET_CONTENT_PROVIDER_TRANSACTION, data, reply, 0);
+        reply.readException();
+        int res = reply.readInt();
+        ContentProviderHolder cph = null;
+        if (res != 0) {
+            cph = ContentProviderHolder.CREATOR.createFromParcel(reply);
+        }
+        data.recycle();
+        reply.recycle();
+        return cph;
+    }
+    public void publishContentProviders(IApplicationThread caller,
+                                        List<ContentProviderHolder> providers) throws RemoteException
+    {
+        Parcel data = Parcel.obtain();
+        Parcel reply = Parcel.obtain();
+        data.writeInterfaceToken(IActivityManager.descriptor);
+        data.writeStrongBinder(caller != null ? caller.asBinder() : null);
+        data.writeTypedList(providers);
+        mRemote.transact(PUBLISH_CONTENT_PROVIDERS_TRANSACTION, data, reply, 0);
+        reply.readException();
+        data.recycle();
+        reply.recycle();
+    }
+    
+    public void removeContentProvider(IApplicationThread caller,
+            String name) throws RemoteException {
+        Parcel data = Parcel.obtain();
+        Parcel reply = Parcel.obtain();
+        data.writeInterfaceToken(IActivityManager.descriptor);
+        data.writeStrongBinder(caller != null ? caller.asBinder() : null);
+        data.writeString(name);
+        mRemote.transact(REMOVE_CONTENT_PROVIDER_TRANSACTION, data, reply, 0);
+        reply.readException();
+        data.recycle();
+        reply.recycle();
+    }
+    
+    public ComponentName startService(IApplicationThread caller, Intent service,
+            String resolvedType) throws RemoteException
+    {
+        Parcel data = Parcel.obtain();
+        Parcel reply = Parcel.obtain();
+        data.writeInterfaceToken(IActivityManager.descriptor);
+        data.writeStrongBinder(caller != null ? caller.asBinder() : null);
+        service.writeToParcel(data, 0);
+        data.writeString(resolvedType);
+        mRemote.transact(START_SERVICE_TRANSACTION, data, reply, 0);
+        reply.readException();
+        ComponentName res = ComponentName.readFromParcel(reply);
+        data.recycle();
+        reply.recycle();
+        return res;
+    }
+    public int stopService(IApplicationThread caller, Intent service,
+            String resolvedType) throws RemoteException
+    {
+        Parcel data = Parcel.obtain();
+        Parcel reply = Parcel.obtain();
+        data.writeInterfaceToken(IActivityManager.descriptor);
+        data.writeStrongBinder(caller != null ? caller.asBinder() : null);
+        service.writeToParcel(data, 0);
+        data.writeString(resolvedType);
+        mRemote.transact(STOP_SERVICE_TRANSACTION, data, reply, 0);
+        reply.readException();
+        int res = reply.readInt();
+        reply.recycle();
+        data.recycle();
+        return res;
+    }
+    public boolean stopServiceToken(ComponentName className, IBinder token,
+            int startId) throws RemoteException {
+        Parcel data = Parcel.obtain();
+        Parcel reply = Parcel.obtain();
+        data.writeInterfaceToken(IActivityManager.descriptor);
+        ComponentName.writeToParcel(className, data);
+        data.writeStrongBinder(token);
+        data.writeInt(startId);
+        mRemote.transact(STOP_SERVICE_TOKEN_TRANSACTION, data, reply, 0);
+        reply.readException();
+        boolean res = reply.readInt() != 0;
+        data.recycle();
+        reply.recycle();
+        return res;
+    }
+    public void setServiceForeground(ComponentName className, IBinder token,
+            boolean isForeground) throws RemoteException {
+        Parcel data = Parcel.obtain();
+        Parcel reply = Parcel.obtain();
+        data.writeInterfaceToken(IActivityManager.descriptor);
+        ComponentName.writeToParcel(className, data);
+        data.writeStrongBinder(token);
+        data.writeInt(isForeground ? 1 : 0);
+        mRemote.transact(SET_SERVICE_FOREGROUND_TRANSACTION, data, reply, 0);
+        reply.readException();
+        data.recycle();
+        reply.recycle();
+    }
+    public int bindService(IApplicationThread caller, IBinder token,
+            Intent service, String resolvedType, IServiceConnection connection,
+            int flags) throws RemoteException {
+        Parcel data = Parcel.obtain();
+        Parcel reply = Parcel.obtain();
+        data.writeInterfaceToken(IActivityManager.descriptor);
+        data.writeStrongBinder(caller != null ? caller.asBinder() : null);
+        data.writeStrongBinder(token);
+        service.writeToParcel(data, 0);
+        data.writeString(resolvedType);
+        data.writeStrongBinder(connection.asBinder());
+        data.writeInt(flags);
+        mRemote.transact(BIND_SERVICE_TRANSACTION, data, reply, 0);
+        reply.readException();
+        int res = reply.readInt();
+        data.recycle();
+        reply.recycle();
+        return res;
+    }
+    public boolean unbindService(IServiceConnection connection) throws RemoteException
+    {
+        Parcel data = Parcel.obtain();
+        Parcel reply = Parcel.obtain();
+        data.writeInterfaceToken(IActivityManager.descriptor);
+        data.writeStrongBinder(connection.asBinder());
+        mRemote.transact(UNBIND_SERVICE_TRANSACTION, data, reply, 0);
+        reply.readException();
+        boolean res = reply.readInt() != 0;
+        data.recycle();
+        reply.recycle();
+        return res;
+    }
+    
+    public void publishService(IBinder token,
+            Intent intent, IBinder service) throws RemoteException {
+        Parcel data = Parcel.obtain();
+        Parcel reply = Parcel.obtain();
+        data.writeInterfaceToken(IActivityManager.descriptor);
+        data.writeStrongBinder(token);
+        intent.writeToParcel(data, 0);
+        data.writeStrongBinder(service);
+        mRemote.transact(PUBLISH_SERVICE_TRANSACTION, data, reply, 0);
+        reply.readException();
+        data.recycle();
+        reply.recycle();
+    }
+
+    public void unbindFinished(IBinder token, Intent intent, boolean doRebind)
+            throws RemoteException {
+        Parcel data = Parcel.obtain();
+        Parcel reply = Parcel.obtain();
+        data.writeInterfaceToken(IActivityManager.descriptor);
+        data.writeStrongBinder(token);
+        intent.writeToParcel(data, 0);
+        data.writeInt(doRebind ? 1 : 0);
+        mRemote.transact(UNBIND_FINISHED_TRANSACTION, data, reply, 0);
+        reply.readException();
+        data.recycle();
+        reply.recycle();
+    }
+
+    public void serviceDoneExecuting(IBinder token) throws RemoteException {
+        Parcel data = Parcel.obtain();
+        Parcel reply = Parcel.obtain();
+        data.writeInterfaceToken(IActivityManager.descriptor);
+        data.writeStrongBinder(token);
+        mRemote.transact(SERVICE_DONE_EXECUTING_TRANSACTION, data, reply, IBinder.FLAG_ONEWAY);
+        reply.readException();
+        data.recycle();
+        reply.recycle();
+    }
+    
+    public IBinder peekService(Intent service, String resolvedType) throws RemoteException {
+        Parcel data = Parcel.obtain();
+        Parcel reply = Parcel.obtain();
+        data.writeInterfaceToken(IActivityManager.descriptor);
+        service.writeToParcel(data, 0);
+        data.writeString(resolvedType);
+        mRemote.transact(PEEK_SERVICE_TRANSACTION, data, reply, 0);
+        reply.readException();
+        IBinder binder = reply.readStrongBinder();
+        reply.recycle();
+        data.recycle();
+        return binder;
+    }
+
+    public boolean startInstrumentation(ComponentName className, String profileFile,
+            int flags, Bundle arguments, IInstrumentationWatcher watcher)
+            throws RemoteException {
+        Parcel data = Parcel.obtain();
+        Parcel reply = Parcel.obtain();
+        data.writeInterfaceToken(IActivityManager.descriptor);
+        ComponentName.writeToParcel(className, data);
+        data.writeString(profileFile);
+        data.writeInt(flags);
+        data.writeBundle(arguments);
+        data.writeStrongBinder(watcher != null ? watcher.asBinder() : null);
+        mRemote.transact(START_INSTRUMENTATION_TRANSACTION, data, reply, 0);
+        reply.readException();
+        boolean res = reply.readInt() != 0;
+        reply.recycle();
+        data.recycle();
+        return res;
+    }
+
+    public void finishInstrumentation(IApplicationThread target,
+            int resultCode, Bundle results) throws RemoteException {
+        Parcel data = Parcel.obtain();
+        Parcel reply = Parcel.obtain();
+        data.writeInterfaceToken(IActivityManager.descriptor);
+        data.writeStrongBinder(target != null ? target.asBinder() : null);
+        data.writeInt(resultCode);
+        data.writeBundle(results);
+        mRemote.transact(FINISH_INSTRUMENTATION_TRANSACTION, data, reply, 0);
+        reply.readException();
+        data.recycle();
+        reply.recycle();
+    }
+    public Configuration getConfiguration() throws RemoteException
+    {
+        Parcel data = Parcel.obtain();
+        Parcel reply = Parcel.obtain();
+        data.writeInterfaceToken(IActivityManager.descriptor);
+        mRemote.transact(GET_CONFIGURATION_TRANSACTION, data, reply, 0);
+        reply.readException();
+        Configuration res = Configuration.CREATOR.createFromParcel(reply);
+        reply.recycle();
+        data.recycle();
+        return res;
+    }
+    public void updateConfiguration(Configuration values) throws RemoteException
+    {
+        Parcel data = Parcel.obtain();
+        Parcel reply = Parcel.obtain();
+        data.writeInterfaceToken(IActivityManager.descriptor);
+        values.writeToParcel(data, 0);
+        mRemote.transact(UPDATE_CONFIGURATION_TRANSACTION, data, reply, 0);
+        reply.readException();
+        data.recycle();
+        reply.recycle();
+    }
+    public void setRequestedOrientation(IBinder token, int requestedOrientation)
+            throws RemoteException {
+        Parcel data = Parcel.obtain();
+        Parcel reply = Parcel.obtain();
+        data.writeInterfaceToken(IActivityManager.descriptor);
+        data.writeStrongBinder(token);
+        data.writeInt(requestedOrientation);
+        mRemote.transact(SET_REQUESTED_ORIENTATION_TRANSACTION, data, reply, 0);
+        reply.readException();
+        data.recycle();
+        reply.recycle();
+    }
+    public int getRequestedOrientation(IBinder token) throws RemoteException {
+        Parcel data = Parcel.obtain();
+        Parcel reply = Parcel.obtain();
+        data.writeInterfaceToken(IActivityManager.descriptor);
+        data.writeStrongBinder(token);
+        mRemote.transact(GET_REQUESTED_ORIENTATION_TRANSACTION, data, reply, 0);
+        reply.readException();
+        int res = reply.readInt();
+        data.recycle();
+        reply.recycle();
+        return res;
+    }
+    public ComponentName getActivityClassForToken(IBinder token)
+            throws RemoteException {
+        Parcel data = Parcel.obtain();
+        Parcel reply = Parcel.obtain();
+        data.writeInterfaceToken(IActivityManager.descriptor);
+        data.writeStrongBinder(token);
+        mRemote.transact(GET_ACTIVITY_CLASS_FOR_TOKEN_TRANSACTION, data, reply, 0);
+        reply.readException();
+        ComponentName res = ComponentName.readFromParcel(reply);
+        data.recycle();
+        reply.recycle();
+        return res;
+    }
+    public String getPackageForToken(IBinder token) throws RemoteException
+    {
+        Parcel data = Parcel.obtain();
+        Parcel reply = Parcel.obtain();
+        data.writeInterfaceToken(IActivityManager.descriptor);
+        data.writeStrongBinder(token);
+        mRemote.transact(GET_PACKAGE_FOR_TOKEN_TRANSACTION, data, reply, 0);
+        reply.readException();
+        String res = reply.readString();
+        data.recycle();
+        reply.recycle();
+        return res;
+    }
+    public IIntentSender getIntentSender(int type,
+            String packageName, IBinder token, String resultWho,
+            int requestCode, Intent intent, String resolvedType, int flags)
+            throws RemoteException {
+        Parcel data = Parcel.obtain();
+        Parcel reply = Parcel.obtain();
+        data.writeInterfaceToken(IActivityManager.descriptor);
+        data.writeInt(type);
+        data.writeString(packageName);
+        data.writeStrongBinder(token);
+        data.writeString(resultWho);
+        data.writeInt(requestCode);
+        if (intent != null) {
+            data.writeInt(1);
+            intent.writeToParcel(data, 0);
+        } else {
+            data.writeInt(0);
+        }
+        data.writeString(resolvedType);
+        data.writeInt(flags);
+        mRemote.transact(GET_INTENT_SENDER_TRANSACTION, data, reply, 0);
+        reply.readException();
+        IIntentSender res = IIntentSender.Stub.asInterface(
+            reply.readStrongBinder());
+        data.recycle();
+        reply.recycle();
+        return res;
+    }
+    public void cancelIntentSender(IIntentSender sender) throws RemoteException {
+        Parcel data = Parcel.obtain();
+        Parcel reply = Parcel.obtain();
+        data.writeInterfaceToken(IActivityManager.descriptor);
+        data.writeStrongBinder(sender.asBinder());
+        mRemote.transact(CANCEL_INTENT_SENDER_TRANSACTION, data, reply, 0);
+        reply.readException();
+        data.recycle();
+        reply.recycle();
+    }
+    public String getPackageForIntentSender(IIntentSender sender) throws RemoteException {
+        Parcel data = Parcel.obtain();
+        Parcel reply = Parcel.obtain();
+        data.writeInterfaceToken(IActivityManager.descriptor);
+        data.writeStrongBinder(sender.asBinder());
+        mRemote.transact(GET_PACKAGE_FOR_INTENT_SENDER_TRANSACTION, data, reply, 0);
+        reply.readException();
+        String res = reply.readString();
+        data.recycle();
+        reply.recycle();
+        return res;
+    }
+    public void setProcessLimit(int max) throws RemoteException
+    {
+        Parcel data = Parcel.obtain();
+        Parcel reply = Parcel.obtain();
+        data.writeInterfaceToken(IActivityManager.descriptor);
+        data.writeInt(max);
+        mRemote.transact(SET_PROCESS_LIMIT_TRANSACTION, data, reply, 0);
+        reply.readException();
+        data.recycle();
+        reply.recycle();
+    }
+    public int getProcessLimit() throws RemoteException
+    {
+        Parcel data = Parcel.obtain();
+        Parcel reply = Parcel.obtain();
+        data.writeInterfaceToken(IActivityManager.descriptor);
+        mRemote.transact(GET_PROCESS_LIMIT_TRANSACTION, data, reply, 0);
+        reply.readException();
+        int res = reply.readInt();
+        data.recycle();
+        reply.recycle();
+        return res;
+    }
+    public void setProcessForeground(IBinder token, int pid,
+            boolean isForeground) throws RemoteException {
+        Parcel data = Parcel.obtain();
+        Parcel reply = Parcel.obtain();
+        data.writeInterfaceToken(IActivityManager.descriptor);
+        data.writeStrongBinder(token);
+        data.writeInt(pid);
+        data.writeInt(isForeground ? 1 : 0);
+        mRemote.transact(SET_PROCESS_FOREGROUND_TRANSACTION, data, reply, 0);
+        reply.readException();
+        data.recycle();
+        reply.recycle();
+    }
+    public int checkPermission(String permission, int pid, int uid)
+            throws RemoteException {
+        Parcel data = Parcel.obtain();
+        Parcel reply = Parcel.obtain();
+        data.writeInterfaceToken(IActivityManager.descriptor);
+        data.writeString(permission);
+        data.writeInt(pid);
+        data.writeInt(uid);
+        mRemote.transact(CHECK_PERMISSION_TRANSACTION, data, reply, 0);
+        reply.readException();
+        int res = reply.readInt();
+        data.recycle();
+        reply.recycle();
+        return res;
+    }
+    public boolean clearApplicationUserData(final String packageName,
+            final IPackageDataObserver observer) throws RemoteException {        
+        Parcel data = Parcel.obtain();
+        Parcel reply = Parcel.obtain();
+        data.writeInterfaceToken(IActivityManager.descriptor);
+        data.writeString(packageName);
+        data.writeStrongBinder(observer.asBinder());
+        mRemote.transact(CLEAR_APP_DATA_TRANSACTION, data, reply, 0);
+        reply.readException();
+        boolean res = reply.readInt() != 0;
+        data.recycle();
+        reply.recycle();
+        return res;
+    }
+    public int checkUriPermission(Uri uri, int pid, int uid, int mode) 
+            throws RemoteException {
+        Parcel data = Parcel.obtain();
+        Parcel reply = Parcel.obtain();
+        data.writeInterfaceToken(IActivityManager.descriptor);
+        uri.writeToParcel(data, 0);
+        data.writeInt(pid);
+        data.writeInt(uid);
+        data.writeInt(mode);
+        mRemote.transact(CHECK_URI_PERMISSION_TRANSACTION, data, reply, 0);
+        reply.readException();
+        int res = reply.readInt();
+        data.recycle();
+        reply.recycle();
+        return res;
+    }
+    public void grantUriPermission(IApplicationThread caller, String targetPkg,
+            Uri uri, int mode) throws RemoteException {
+        Parcel data = Parcel.obtain();
+        Parcel reply = Parcel.obtain();
+        data.writeInterfaceToken(IActivityManager.descriptor);
+        data.writeStrongBinder(caller.asBinder());
+        data.writeString(targetPkg);
+        uri.writeToParcel(data, 0);
+        data.writeInt(mode);
+        mRemote.transact(GRANT_URI_PERMISSION_TRANSACTION, data, reply, 0);
+        reply.readException();
+        data.recycle();
+        reply.recycle();
+    }
+    public void revokeUriPermission(IApplicationThread caller, Uri uri,
+            int mode) throws RemoteException {
+        Parcel data = Parcel.obtain();
+        Parcel reply = Parcel.obtain();
+        data.writeInterfaceToken(IActivityManager.descriptor);
+        data.writeStrongBinder(caller.asBinder());
+        uri.writeToParcel(data, 0);
+        data.writeInt(mode);
+        mRemote.transact(REVOKE_URI_PERMISSION_TRANSACTION, data, reply, 0);
+        reply.readException();
+        data.recycle();
+        reply.recycle();
+    }
+    public void showWaitingForDebugger(IApplicationThread who, boolean waiting)
+            throws RemoteException {
+        Parcel data = Parcel.obtain();
+        Parcel reply = Parcel.obtain();
+        data.writeInterfaceToken(IActivityManager.descriptor);
+        data.writeStrongBinder(who.asBinder());
+        data.writeInt(waiting ? 1 : 0);
+        mRemote.transact(SHOW_WAITING_FOR_DEBUGGER_TRANSACTION, data, reply, 0);
+        reply.readException();
+        data.recycle();
+        reply.recycle();
+    }
+    public void getMemoryInfo(ActivityManager.MemoryInfo outInfo) throws RemoteException {
+        Parcel data = Parcel.obtain();
+        Parcel reply = Parcel.obtain();
+        data.writeInterfaceToken(IActivityManager.descriptor);
+        mRemote.transact(GET_MEMORY_INFO_TRANSACTION, data, reply, 0);
+        reply.readException();
+        outInfo.readFromParcel(reply);
+        data.recycle();
+        reply.recycle();
+    }
+    public void unhandledBack() throws RemoteException
+    {
+        Parcel data = Parcel.obtain();
+        Parcel reply = Parcel.obtain();
+        data.writeInterfaceToken(IActivityManager.descriptor);
+        mRemote.transact(UNHANDLED_BACK_TRANSACTION, data, reply, 0);
+        reply.readException();
+        data.recycle();
+        reply.recycle();
+    }
+    public ParcelFileDescriptor openContentUri(Uri uri) throws RemoteException
+    {
+        Parcel data = Parcel.obtain();
+        Parcel reply = Parcel.obtain();
+        data.writeInterfaceToken(IActivityManager.descriptor);
+        mRemote.transact(OPEN_CONTENT_URI_TRANSACTION, data, reply, 0);
+        reply.readException();
+        ParcelFileDescriptor pfd = null;
+        if (reply.readInt() != 0) {
+            pfd = ParcelFileDescriptor.CREATOR.createFromParcel(reply);
+        }
+        data.recycle();
+        reply.recycle();
+        return pfd;
+    }
+    public void goingToSleep() throws RemoteException
+    {
+        Parcel data = Parcel.obtain();
+        Parcel reply = Parcel.obtain();
+        data.writeInterfaceToken(IActivityManager.descriptor);
+        mRemote.transact(GOING_TO_SLEEP_TRANSACTION, data, reply, 0);
+        reply.readException();
+        data.recycle();
+        reply.recycle();
+    }
+    public void wakingUp() throws RemoteException
+    {
+        Parcel data = Parcel.obtain();
+        Parcel reply = Parcel.obtain();
+        data.writeInterfaceToken(IActivityManager.descriptor);
+        mRemote.transact(WAKING_UP_TRANSACTION, data, reply, 0);
+        reply.readException();
+        data.recycle();
+        reply.recycle();
+    }
+    public void setDebugApp(
+        String packageName, boolean waitForDebugger, boolean persistent)
+        throws RemoteException
+    {
+        Parcel data = Parcel.obtain();
+        Parcel reply = Parcel.obtain();
+        data.writeInterfaceToken(IActivityManager.descriptor);
+        data.writeString(packageName);
+        data.writeInt(waitForDebugger ? 1 : 0);
+        data.writeInt(persistent ? 1 : 0);
+        mRemote.transact(SET_DEBUG_APP_TRANSACTION, data, reply, 0);
+        reply.readException();
+        data.recycle();
+        reply.recycle();
+    }
+    public void setAlwaysFinish(boolean enabled) throws RemoteException
+    {
+        Parcel data = Parcel.obtain();
+        Parcel reply = Parcel.obtain();
+        data.writeInterfaceToken(IActivityManager.descriptor);
+        data.writeInt(enabled ? 1 : 0);
+        mRemote.transact(SET_ALWAYS_FINISH_TRANSACTION, data, reply, 0);
+        reply.readException();
+        data.recycle();
+        reply.recycle();
+    }
+    public void setActivityWatcher(IActivityWatcher watcher) throws RemoteException
+    {
+        Parcel data = Parcel.obtain();
+        Parcel reply = Parcel.obtain();
+        data.writeInterfaceToken(IActivityManager.descriptor);
+        data.writeStrongBinder(watcher != null ? watcher.asBinder() : null);
+        mRemote.transact(SET_ACTIVITY_WATCHER_TRANSACTION, data, reply, 0);
+        reply.readException();
+        data.recycle();
+        reply.recycle();
+    }
+    public void enterSafeMode() throws RemoteException {
+        Parcel data = Parcel.obtain();
+        data.writeInterfaceToken(IActivityManager.descriptor);
+        mRemote.transact(ENTER_SAFE_MODE_TRANSACTION, data, null, 0);
+        data.recycle();
+    }
+    public void noteWakeupAlarm(IIntentSender sender) throws RemoteException {
+        Parcel data = Parcel.obtain();
+        data.writeStrongBinder(sender.asBinder());
+        data.writeInterfaceToken(IActivityManager.descriptor);
+        mRemote.transact(NOTE_WAKEUP_ALARM_TRANSACTION, data, null, 0);
+        data.recycle();
+    }
+    public boolean killPidsForMemory(int[] pids) throws RemoteException {
+        Parcel data = Parcel.obtain();
+        Parcel reply = Parcel.obtain();
+        data.writeInterfaceToken(IActivityManager.descriptor);
+        data.writeIntArray(pids);
+        mRemote.transact(KILL_PIDS_FOR_MEMORY_TRANSACTION, data, reply, 0);
+        boolean res = reply.readInt() != 0;
+        data.recycle();
+        reply.recycle();
+        return res;
+    }
+    public void reportPss(IApplicationThread caller, int pss) throws RemoteException {
+        Parcel data = Parcel.obtain();
+        data.writeInterfaceToken(IActivityManager.descriptor);
+        data.writeStrongBinder(caller.asBinder());
+        data.writeInt(pss);
+        mRemote.transact(REPORT_PSS_TRANSACTION, data, null, 0);
+        data.recycle();
+    }
+    public void startRunning(String pkg, String cls, String action,
+            String indata) throws RemoteException {
+        Parcel data = Parcel.obtain();
+        Parcel reply = Parcel.obtain();
+        data.writeInterfaceToken(IActivityManager.descriptor);
+        data.writeString(pkg);
+        data.writeString(cls);
+        data.writeString(action);
+        data.writeString(indata);
+        mRemote.transact(START_RUNNING_TRANSACTION, data, reply, 0);
+        reply.readException();
+        data.recycle();
+        reply.recycle();
+    }
+    public void systemReady() throws RemoteException
+    {
+        Parcel data = Parcel.obtain();
+        Parcel reply = Parcel.obtain();
+        data.writeInterfaceToken(IActivityManager.descriptor);
+        mRemote.transact(SYSTEM_READY_TRANSACTION, data, reply, 0);
+        reply.readException();
+        data.recycle();
+        reply.recycle();
+    }
+    public boolean testIsSystemReady()
+    {
+        /* this base class version is never called */
+        return true;
+    }
+    public int handleApplicationError(IBinder app, int flags,
+            String tag, String shortMsg, String longMsg,
+            byte[] crashData) throws RemoteException
+    {
+        Parcel data = Parcel.obtain();
+        Parcel reply = Parcel.obtain();
+        data.writeInterfaceToken(IActivityManager.descriptor);
+        data.writeStrongBinder(app);
+        data.writeInt(flags);
+        data.writeString(tag);
+        data.writeString(shortMsg);
+        data.writeString(longMsg);
+        data.writeByteArray(crashData);
+        mRemote.transact(HANDLE_APPLICATION_ERROR_TRANSACTION, data, reply, 0);
+        reply.readException();
+        int res = reply.readInt();
+        reply.recycle();
+        data.recycle();
+        return res;
+    }
+    
+    public void signalPersistentProcesses(int sig) throws RemoteException {
+        Parcel data = Parcel.obtain();
+        Parcel reply = Parcel.obtain();
+        data.writeInterfaceToken(IActivityManager.descriptor);
+        data.writeInt(sig);
+        mRemote.transact(SIGNAL_PERSISTENT_PROCESSES_TRANSACTION, data, reply, 0);
+        reply.readException();
+        data.recycle();
+        reply.recycle();
+    }
+    
+    public void restartPackage(String packageName) throws RemoteException {
+        Parcel data = Parcel.obtain();
+        Parcel reply = Parcel.obtain();
+        data.writeInterfaceToken(IActivityManager.descriptor);
+        data.writeString(packageName);
+        mRemote.transact(RESTART_PACKAGE_TRANSACTION, data, reply, 0);
+        reply.readException();
+        data.recycle();
+        reply.recycle();
+    }
+    
+    public ConfigurationInfo getDeviceConfigurationInfo() throws RemoteException
+    {
+        Parcel data = Parcel.obtain();
+        Parcel reply = Parcel.obtain();
+        data.writeInterfaceToken(IActivityManager.descriptor);
+        mRemote.transact(GET_DEVICE_CONFIGURATION_TRANSACTION, data, reply, 0);
+        reply.readException();
+        ConfigurationInfo res = ConfigurationInfo.CREATOR.createFromParcel(reply);
+        reply.recycle();
+        data.recycle();
+        return res;
+    }
+    private IBinder mRemote;
+}
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
new file mode 100644
index 0000000..bf5616e
--- /dev/null
+++ b/core/java/android/app/ActivityThread.java
@@ -0,0 +1,3916 @@
+/*
+ * 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 android.app;
+
+import android.content.BroadcastReceiver;
+import android.content.ComponentCallbacks;
+import android.content.ComponentName;
+import android.content.ContentProvider;
+import android.content.Context;
+import android.content.IContentProvider;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.IPackageManager;
+import android.content.pm.InstrumentationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ProviderInfo;
+import android.content.pm.ServiceInfo;
+import android.content.res.AssetManager;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteDebug;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.net.http.AndroidHttpClient;
+import android.os.Bundle;
+import android.os.Debug;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.os.MessageQueue;
+import android.os.Process;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.SystemClock;
+import android.util.AndroidRuntimeException;
+import android.util.Config;
+import android.util.DisplayMetrics;
+import android.util.EventLog;
+import android.util.Log;
+import android.view.Display;
+import android.view.View;
+import android.view.ViewDebug;
+import android.view.ViewManager;
+import android.view.Window;
+import android.view.WindowManager;
+import android.view.WindowManagerImpl;
+
+import com.android.internal.os.BinderInternal;
+import com.android.internal.os.RuntimeInit;
+import com.android.internal.util.ArrayUtils;
+
+import org.apache.harmony.xnet.provider.jsse.OpenSSLSocketImpl;
+
+import java.io.File;
+import java.io.FileDescriptor;
+import java.io.FileOutputStream;
+import java.io.PrintWriter;
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.TimeZone;
+import java.util.regex.Pattern;
+
+final class IntentReceiverLeaked extends AndroidRuntimeException {
+    public IntentReceiverLeaked(String msg) {
+        super(msg);
+    }
+}
+
+final class ServiceConnectionLeaked extends AndroidRuntimeException {
+    public ServiceConnectionLeaked(String msg) {
+        super(msg);
+    }
+}
+
+final class SuperNotCalledException extends AndroidRuntimeException {
+    public SuperNotCalledException(String msg) {
+        super(msg);
+    }
+}
+
+/**
+ * This manages the execution of the main thread in an
+ * application process, scheduling and executing activities,
+ * broadcasts, and other operations on it as the activity
+ * manager requests.
+ *
+ * {@hide}
+ */
+public final class ActivityThread {
+    private static final String TAG = "ActivityThread";
+    private static final boolean DEBUG = false;
+    private static final boolean localLOGV = DEBUG ? Config.LOGD : Config.LOGV;
+    private static final boolean DEBUG_BROADCAST = false;
+    private static final long MIN_TIME_BETWEEN_GCS = 5*1000;
+    private static final Pattern PATTERN_SEMICOLON = Pattern.compile(";");
+    private static final int SQLITE_MEM_RELEASED_EVENT_LOG_TAG = 75003;
+    private static final int LOG_ON_PAUSE_CALLED = 30021;
+    private static final int LOG_ON_RESUME_CALLED = 30022;
+
+    
+    public static final ActivityThread currentActivityThread() {
+        return (ActivityThread)sThreadLocal.get();
+    }
+
+    public static final String currentPackageName()
+    {
+        ActivityThread am = currentActivityThread();
+        return (am != null && am.mBoundApplication != null)
+            ? am.mBoundApplication.processName : null;
+    }
+
+    public static IPackageManager getPackageManager() {
+        if (sPackageManager != null) {
+            //Log.v("PackageManager", "returning cur default = " + sPackageManager);
+            return sPackageManager;
+        }
+        IBinder b = ServiceManager.getService("package");
+        //Log.v("PackageManager", "default service binder = " + b);
+        sPackageManager = IPackageManager.Stub.asInterface(b);
+        //Log.v("PackageManager", "default service = " + sPackageManager);
+        return sPackageManager;
+    }
+
+    DisplayMetrics getDisplayMetricsLocked(boolean forceUpdate) {
+        if (mDisplayMetrics != null && !forceUpdate) {
+            return mDisplayMetrics;
+        }
+        if (mDisplay == null) {
+            WindowManager wm = WindowManagerImpl.getDefault();
+            mDisplay = wm.getDefaultDisplay();
+        }
+        DisplayMetrics metrics = mDisplayMetrics = new DisplayMetrics();
+        mDisplay.getMetrics(metrics);
+        //Log.i("foo", "New metrics: w=" + metrics.widthPixels + " h="
+        //        + metrics.heightPixels + " den=" + metrics.density
+        //        + " xdpi=" + metrics.xdpi + " ydpi=" + metrics.ydpi);
+        return metrics;
+    }
+
+    Resources getTopLevelResources(String appDir) {
+        synchronized (mPackages) {
+            //Log.w(TAG, "getTopLevelResources: " + appDir);
+            WeakReference<Resources> wr = mActiveResources.get(appDir);
+            Resources r = wr != null ? wr.get() : null;
+            if (r != null && r.getAssets().isUpToDate()) {
+                //Log.w(TAG, "Returning cached resources " + r + " " + appDir);
+                return r;
+            }
+
+            //if (r != null) {
+            //    Log.w(TAG, "Throwing away out-of-date resources!!!! "
+            //            + r + " " + appDir);
+            //}
+
+            AssetManager assets = new AssetManager();
+            if (assets.addAssetPath(appDir) == 0) {
+                return null;
+            }
+            DisplayMetrics metrics = getDisplayMetricsLocked(false);
+            r = new Resources(assets, metrics, getConfiguration());
+            //Log.i(TAG, "Created app resources " + r + ": " + r.getConfiguration());
+            // XXX need to remove entries when weak references go away
+            mActiveResources.put(appDir, new WeakReference<Resources>(r));
+            return r;
+        }
+    }
+
+    final Handler getHandler() {
+        return mH;
+    }
+
+    public final static class PackageInfo {
+
+        private final ActivityThread mActivityThread;
+        private final ApplicationInfo mApplicationInfo;
+        private final String mPackageName;
+        private final String mAppDir;
+        private final String mResDir;
+        private final String[] mSharedLibraries;
+        private final String mDataDir;
+        private final File mDataDirFile;
+        private final ClassLoader mBaseClassLoader;
+        private final boolean mSecurityViolation;
+        private final boolean mIncludeCode;
+        private Resources mResources;
+        private ClassLoader mClassLoader;
+        private Application mApplication;
+        private final HashMap<Context, HashMap<BroadcastReceiver, ReceiverDispatcher>> mReceivers
+            = new HashMap<Context, HashMap<BroadcastReceiver, ReceiverDispatcher>>();
+        private final HashMap<Context, HashMap<BroadcastReceiver, ReceiverDispatcher>> mUnregisteredReceivers
+        = new HashMap<Context, HashMap<BroadcastReceiver, ReceiverDispatcher>>();
+        private final HashMap<Context, HashMap<ServiceConnection, ServiceDispatcher>> mServices
+            = new HashMap<Context, HashMap<ServiceConnection, ServiceDispatcher>>();
+        private final HashMap<Context, HashMap<ServiceConnection, ServiceDispatcher>> mUnboundServices
+            = new HashMap<Context, HashMap<ServiceConnection, ServiceDispatcher>>();
+
+        int mClientCount = 0;
+
+        public PackageInfo(ActivityThread activityThread, ApplicationInfo aInfo,
+                ActivityThread mainThread, ClassLoader baseLoader,
+                boolean securityViolation, boolean includeCode) {
+            mActivityThread = activityThread;
+            mApplicationInfo = aInfo;
+            mPackageName = aInfo.packageName;
+            mAppDir = aInfo.sourceDir;
+            mResDir = aInfo.uid == Process.myUid() ? aInfo.sourceDir
+                    : aInfo.publicSourceDir;
+            mSharedLibraries = aInfo.sharedLibraryFiles;
+            mDataDir = aInfo.dataDir;
+            mDataDirFile = mDataDir != null ? new File(mDataDir) : null;
+            mBaseClassLoader = baseLoader;
+            mSecurityViolation = securityViolation;
+            mIncludeCode = includeCode;
+
+            if (mAppDir == null) {
+                if (mSystemContext == null) {
+                    mSystemContext =
+                        ApplicationContext.createSystemContext(mainThread);
+                    mSystemContext.getResources().updateConfiguration(
+                            mainThread.getConfiguration(),
+                            mainThread.getDisplayMetricsLocked(false));
+                    //Log.i(TAG, "Created system resources "
+                    //        + mSystemContext.getResources() + ": "
+                    //        + mSystemContext.getResources().getConfiguration());
+                }
+                mClassLoader = mSystemContext.getClassLoader();
+                mResources = mSystemContext.getResources();
+            }
+        }
+
+        public PackageInfo(ActivityThread activityThread, String name,
+                Context systemContext) {
+            mActivityThread = activityThread;
+            mApplicationInfo = new ApplicationInfo();
+            mApplicationInfo.packageName = name;
+            mPackageName = name;
+            mAppDir = null;
+            mResDir = null;
+            mSharedLibraries = null;
+            mDataDir = null;
+            mDataDirFile = null;
+            mBaseClassLoader = null;
+            mSecurityViolation = false;
+            mIncludeCode = true;
+            mClassLoader = systemContext.getClassLoader();
+            mResources = systemContext.getResources();
+        }
+
+        public String getPackageName() {
+            return mPackageName;
+        }
+
+        public boolean isSecurityViolation() {
+            return mSecurityViolation;
+        }
+
+        /**
+         * Gets the array of shared libraries that are listed as
+         * used by the given package.
+         * 
+         * @param packageName the name of the package (note: not its
+         * file name)
+         * @return null-ok; the array of shared libraries, each one
+         * a fully-qualified path
+         */
+        private static String[] getLibrariesFor(String packageName) {
+            ApplicationInfo ai = null;
+            try {
+                ai = getPackageManager().getApplicationInfo(packageName,
+                        PackageManager.GET_SHARED_LIBRARY_FILES);
+            } catch (RemoteException e) {
+                throw new AssertionError(e);
+            }
+
+            if (ai == null) {
+                return null;
+            }
+
+            return ai.sharedLibraryFiles;
+        }
+
+        /**
+         * Combines two arrays (of library names) such that they are
+         * concatenated in order but are devoid of duplicates. The
+         * result is a single string with the names of the libraries
+         * separated by colons, or <code>null</code> if both lists
+         * were <code>null</code> or empty.
+         * 
+         * @param list1 null-ok; the first list
+         * @param list2 null-ok; the second list
+         * @return null-ok; the combination
+         */
+        private static String combineLibs(String[] list1, String[] list2) {
+            StringBuilder result = new StringBuilder(300);
+            boolean first = true;
+
+            if (list1 != null) {
+                for (String s : list1) {
+                    if (first) {
+                        first = false;
+                    } else {
+                        result.append(':');
+                    }
+                    result.append(s);
+                }
+            }
+
+            // Only need to check for duplicates if list1 was non-empty.
+            boolean dupCheck = !first;
+
+            if (list2 != null) {
+                for (String s : list2) {
+                    if (dupCheck && ArrayUtils.contains(list1, s)) {
+                        continue;
+                    }
+                    
+                    if (first) {
+                        first = false;
+                    } else {
+                        result.append(':');
+                    }
+                    result.append(s);
+                }
+            }
+
+            return result.toString();
+        }
+                
+        public ClassLoader getClassLoader() {
+            synchronized (this) {
+                if (mClassLoader != null) {
+                    return mClassLoader;
+                }
+
+                if (mIncludeCode && !mPackageName.equals("android")) {
+                    String zip = mAppDir;
+
+                    /*
+                     * The following is a bit of a hack to inject
+                     * instrumentation into the system: If the app
+                     * being started matches one of the instrumentation names,
+                     * then we combine both the "instrumentation" and
+                     * "instrumented" app into the path, along with the
+                     * concatenation of both apps' shared library lists.
+                     */
+
+                    String instrumentationAppDir =
+                            mActivityThread.mInstrumentationAppDir;
+                    String instrumentationAppPackage =
+                            mActivityThread.mInstrumentationAppPackage;
+                    String instrumentedAppDir =
+                            mActivityThread.mInstrumentedAppDir;
+                    String[] instrumentationLibs = null;
+
+                    if (mAppDir.equals(instrumentationAppDir)
+                            || mAppDir.equals(instrumentedAppDir)) {
+                        zip = instrumentationAppDir + ":" + instrumentedAppDir;
+                        if (! instrumentedAppDir.equals(instrumentationAppDir)) {
+                            instrumentationLibs =
+                                getLibrariesFor(instrumentationAppPackage);
+                        }
+                    }
+
+                    if ((mSharedLibraries != null) ||
+                            (instrumentationLibs != null)) {
+                        zip = 
+                            combineLibs(mSharedLibraries, instrumentationLibs)
+                            + ':' + zip;
+                    }
+
+                    /*
+                     * With all the combination done (if necessary, actually
+                     * create the class loader.
+                     */
+
+                    if (localLOGV) Log.v(TAG, "Class path: " + zip);
+
+                    mClassLoader =
+                        ApplicationLoaders.getDefault().getClassLoader(
+                            zip, mDataDir, mBaseClassLoader);
+                } else {
+                    if (mBaseClassLoader == null) {
+                        mClassLoader = ClassLoader.getSystemClassLoader();
+                    } else {
+                        mClassLoader = mBaseClassLoader;
+                    }
+                }
+                return mClassLoader;
+            }
+        }
+
+        public String getAppDir() {
+            return mAppDir;
+        }
+
+        public String getResDir() {
+            return mResDir;
+        }
+
+        public String getDataDir() {
+            return mDataDir;
+        }
+
+        public File getDataDirFile() {
+            return mDataDirFile;
+        }
+
+        public AssetManager getAssets(ActivityThread mainThread) {
+            return getResources(mainThread).getAssets();
+        }
+
+        public Resources getResources(ActivityThread mainThread) {
+            if (mResources == null) {
+                mResources = mainThread.getTopLevelResources(mResDir);
+            }
+            return mResources;
+        }
+
+        public Application makeApplication() {
+            if (mApplication != null) {
+                return mApplication;
+            }
+            
+            Application app = null;
+            
+            String appClass = mApplicationInfo.className;
+            if (appClass == null) {
+                appClass = "android.app.Application";
+            }
+
+            try {
+                java.lang.ClassLoader cl = getClassLoader();
+                ApplicationContext appContext = new ApplicationContext();
+                appContext.init(this, null, mActivityThread);
+                app = mActivityThread.mInstrumentation.newApplication(
+                        cl, appClass, appContext);
+                appContext.setOuterContext(app);
+            } catch (Exception e) {
+                if (!mActivityThread.mInstrumentation.onException(app, e)) {
+                    throw new RuntimeException(
+                        "Unable to instantiate application " + appClass
+                        + ": " + e.toString(), e);
+                }
+            }
+            mActivityThread.mAllApplications.add(app);
+            return mApplication = app;
+        }
+        
+        public void removeContextRegistrations(Context context,
+                String who, String what) {
+            HashMap<BroadcastReceiver, ReceiverDispatcher> rmap =
+                mReceivers.remove(context);
+            if (rmap != null) {
+                Iterator<ReceiverDispatcher> it = rmap.values().iterator();
+                while (it.hasNext()) {
+                    ReceiverDispatcher rd = it.next();
+                    IntentReceiverLeaked leak = new IntentReceiverLeaked(
+                            what + " " + who + " has leaked IntentReceiver "
+                            + rd.getIntentReceiver() + " that was " +
+                            "originally registered here. Are you missing a " +
+                            "call to unregisterReceiver()?");
+                    leak.setStackTrace(rd.getLocation().getStackTrace());
+                    Log.e(TAG, leak.getMessage(), leak);
+                    try {
+                        ActivityManagerNative.getDefault().unregisterReceiver(
+                                rd.getIIntentReceiver());
+                    } catch (RemoteException e) {
+                        // system crashed, nothing we can do
+                    }
+                }
+            }
+            mUnregisteredReceivers.remove(context);
+            //Log.i(TAG, "Receiver registrations: " + mReceivers);
+            HashMap<ServiceConnection, ServiceDispatcher> smap =
+                mServices.remove(context);
+            if (smap != null) {
+                Iterator<ServiceDispatcher> it = smap.values().iterator();
+                while (it.hasNext()) {
+                    ServiceDispatcher sd = it.next();
+                    ServiceConnectionLeaked leak = new ServiceConnectionLeaked(
+                            what + " " + who + " has leaked ServiceConnection "
+                            + sd.getServiceConnection() + " that was originally bound here");
+                    leak.setStackTrace(sd.getLocation().getStackTrace());
+                    Log.e(TAG, leak.getMessage(), leak);
+                    try {
+                        ActivityManagerNative.getDefault().unbindService(
+                                sd.getIServiceConnection());
+                    } catch (RemoteException e) {
+                        // system crashed, nothing we can do
+                    }
+                    sd.doForget();
+                }
+            }
+            mUnboundServices.remove(context);
+            //Log.i(TAG, "Service registrations: " + mServices);
+        }
+
+        public IIntentReceiver getReceiverDispatcher(BroadcastReceiver r,
+                Context context, Handler handler,
+                Instrumentation instrumentation, boolean registered) {
+            synchronized (mReceivers) {
+                ReceiverDispatcher rd = null;
+                HashMap<BroadcastReceiver, ReceiverDispatcher> map = null;
+                if (registered) {
+                    map = mReceivers.get(context);
+                    if (map != null) {
+                        rd = map.get(r);
+                    }
+                }
+                if (rd == null) {
+                    rd = new ReceiverDispatcher(r, context, handler,
+                            instrumentation, registered);
+                    if (registered) {
+                        if (map == null) {
+                            map = new HashMap<BroadcastReceiver, ReceiverDispatcher>();
+                            mReceivers.put(context, map);
+                        }
+                        map.put(r, rd);
+                    }
+                } else {
+                    rd.validate(context, handler);
+                }
+                return rd.getIIntentReceiver();
+            }
+        }
+
+        public IIntentReceiver forgetReceiverDispatcher(Context context,
+                BroadcastReceiver r) {
+            synchronized (mReceivers) {
+                HashMap<BroadcastReceiver, ReceiverDispatcher> map = mReceivers.get(context);
+                ReceiverDispatcher rd = null;
+                if (map != null) {
+                    rd = map.get(r);
+                    if (rd != null) {
+                        map.remove(r);
+                        if (map.size() == 0) {
+                            mReceivers.remove(context);
+                        }
+                        if (r.getDebugUnregister()) {
+                            HashMap<BroadcastReceiver, ReceiverDispatcher> holder
+                                    = mUnregisteredReceivers.get(context);
+                            if (holder == null) {
+                                holder = new HashMap<BroadcastReceiver, ReceiverDispatcher>();
+                                mUnregisteredReceivers.put(context, holder);
+                            }
+                            RuntimeException ex = new IllegalArgumentException(
+                                    "Originally unregistered here:");
+                            ex.fillInStackTrace();
+                            rd.setUnregisterLocation(ex);
+                            holder.put(r, rd);
+                        }
+                        return rd.getIIntentReceiver();
+                    }
+                }
+                HashMap<BroadcastReceiver, ReceiverDispatcher> holder
+                        = mUnregisteredReceivers.get(context);
+                if (holder != null) {
+                    rd = holder.get(r);
+                    if (rd != null) {
+                        RuntimeException ex = rd.getUnregisterLocation();
+                        throw new IllegalArgumentException(
+                                "Unregistering Receiver " + r
+                                + " that was already unregistered", ex);
+                    }
+                }
+                if (context == null) {
+                    throw new IllegalStateException("Unbinding Receiver " + r
+                            + " from Context that is no longer in use: " + context);
+                } else {
+                    throw new IllegalArgumentException("Receiver not registered: " + r);
+                }
+
+            }
+        }
+
+        static final class ReceiverDispatcher {
+
+            final static class InnerReceiver extends IIntentReceiver.Stub {
+                final WeakReference<ReceiverDispatcher> mDispatcher;
+                final ReceiverDispatcher mStrongRef;
+                
+                InnerReceiver(ReceiverDispatcher rd, boolean strong) {
+                    mDispatcher = new WeakReference<ReceiverDispatcher>(rd);
+                    mStrongRef = strong ? rd : null;
+                }
+                public void performReceive(Intent intent, int resultCode,
+                        String data, Bundle extras, boolean ordered) {
+                    ReceiverDispatcher rd = mDispatcher.get();
+                    if (DEBUG_BROADCAST) {
+                        int seq = intent.getIntExtra("seq", -1);
+                        Log.i(TAG, "Receiving broadcast " + intent.getAction() + " seq=" + seq
+                                + " to " + rd);
+                    }
+                    if (rd != null) {
+                        rd.performReceive(intent, resultCode, data, extras, ordered);
+                    }
+                }
+            }
+            
+            final IIntentReceiver.Stub mIIntentReceiver;
+            final BroadcastReceiver mReceiver;
+            final Context mContext;
+            final Handler mActivityThread;
+            final Instrumentation mInstrumentation;
+            final boolean mRegistered;
+            final IntentReceiverLeaked mLocation;
+            RuntimeException mUnregisterLocation;
+
+            final class Args implements Runnable {
+                private Intent mCurIntent;
+                private int mCurCode;
+                private String mCurData;
+                private Bundle mCurMap;
+                private boolean mCurOrdered;
+
+                public void run() {
+                    BroadcastReceiver receiver = mReceiver;
+                    if (DEBUG_BROADCAST) {
+                        int seq = mCurIntent.getIntExtra("seq", -1);
+                        Log.i(TAG, "Dispathing broadcast " + mCurIntent.getAction() + " seq=" + seq
+                                + " to " + mReceiver);
+                    }
+                    if (receiver == null) {
+                        return;
+                    }
+
+                    IActivityManager mgr = ActivityManagerNative.getDefault();
+                    Intent intent = mCurIntent;
+                    mCurIntent = null;
+                    try {
+                        ClassLoader cl =  mReceiver.getClass().getClassLoader();
+                        intent.setExtrasClassLoader(cl);
+                        if (mCurMap != null) {
+                            mCurMap.setClassLoader(cl);
+                        }
+                        receiver.setOrderedHint(true);
+                        receiver.setResult(mCurCode, mCurData, mCurMap);
+                        receiver.clearAbortBroadcast();
+                        receiver.setOrderedHint(mCurOrdered);
+                        receiver.onReceive(mContext, intent);
+                    } catch (Exception e) {
+                        if (mRegistered && mCurOrdered) {
+                            try {
+                                mgr.finishReceiver(mIIntentReceiver,
+                                        mCurCode, mCurData, mCurMap, false);
+                            } catch (RemoteException ex) {
+                            }
+                        }
+                        if (mInstrumentation == null ||
+                                !mInstrumentation.onException(mReceiver, e)) {
+                            throw new RuntimeException(
+                                "Error receiving broadcast " + intent
+                                + " in " + mReceiver, e);
+                        }
+                    }
+                    if (mRegistered && mCurOrdered) {
+                        try {
+                            mgr.finishReceiver(mIIntentReceiver,
+                                    receiver.getResultCode(),
+                                    receiver.getResultData(),
+                                    receiver.getResultExtras(false),
+                                    receiver.getAbortBroadcast());
+                        } catch (RemoteException ex) {
+                        }
+                    }
+                }
+            }
+
+            ReceiverDispatcher(BroadcastReceiver receiver, Context context,
+                    Handler activityThread, Instrumentation instrumentation,
+                    boolean registered) {
+                if (activityThread == null) {
+                    throw new NullPointerException("Handler must not be null");
+                }
+
+                mIIntentReceiver = new InnerReceiver(this, !registered);
+                mReceiver = receiver;
+                mContext = context;
+                mActivityThread = activityThread;
+                mInstrumentation = instrumentation;
+                mRegistered = registered;
+                mLocation = new IntentReceiverLeaked(null);
+                mLocation.fillInStackTrace();
+            }
+
+            void validate(Context context, Handler activityThread) {
+                if (mContext != context) {
+                    throw new IllegalStateException(
+                        "Receiver " + mReceiver +
+                        " registered with differing Context (was " +
+                        mContext + " now " + context + ")");
+                }
+                if (mActivityThread != activityThread) {
+                    throw new IllegalStateException(
+                        "Receiver " + mReceiver +
+                        " registered with differing handler (was " +
+                        mActivityThread + " now " + activityThread + ")");
+                }
+            }
+
+            IntentReceiverLeaked getLocation() {
+                return mLocation;
+            }
+
+            BroadcastReceiver getIntentReceiver() {
+                return mReceiver;
+            }
+            
+            IIntentReceiver getIIntentReceiver() {
+                return mIIntentReceiver;
+            }
+
+            void setUnregisterLocation(RuntimeException ex) {
+                mUnregisterLocation = ex;
+            }
+
+            RuntimeException getUnregisterLocation() {
+                return mUnregisterLocation;
+            }
+
+            public void performReceive(Intent intent, int resultCode,
+                    String data, Bundle extras, boolean ordered) {
+                if (DEBUG_BROADCAST) {
+                    int seq = intent.getIntExtra("seq", -1);
+                    Log.i(TAG, "Enqueueing broadcast " + intent.getAction() + " seq=" + seq
+                            + " to " + mReceiver);
+                }
+                Args args = new Args();
+                args.mCurIntent = intent;
+                args.mCurCode = resultCode;
+                args.mCurData = data;
+                args.mCurMap = extras;
+                args.mCurOrdered = ordered;
+                if (!mActivityThread.post(args)) {
+                    if (mRegistered) {
+                        IActivityManager mgr = ActivityManagerNative.getDefault();
+                        try {
+                            mgr.finishReceiver(mIIntentReceiver, args.mCurCode,
+                                    args.mCurData, args.mCurMap, false);
+                        } catch (RemoteException ex) {
+                        }
+                    }
+                }
+            }
+
+        }
+
+        public final IServiceConnection getServiceDispatcher(ServiceConnection c,
+                Context context, Handler handler, int flags) {
+            synchronized (mServices) {
+                ServiceDispatcher sd = null;
+                HashMap<ServiceConnection, ServiceDispatcher> map = mServices.get(context);
+                if (map != null) {
+                    sd = map.get(c);
+                }
+                if (sd == null) {
+                    sd = new ServiceDispatcher(c, context, handler, flags);
+                    if (map == null) {
+                        map = new HashMap<ServiceConnection, ServiceDispatcher>();
+                        mServices.put(context, map);
+                    }
+                    map.put(c, sd);
+                } else {
+                    sd.validate(context, handler);
+                }
+                return sd.getIServiceConnection();
+            }
+        }
+
+        public final IServiceConnection forgetServiceDispatcher(Context context,
+                ServiceConnection c) {
+            synchronized (mServices) {
+                HashMap<ServiceConnection, ServiceDispatcher> map
+                        = mServices.get(context);
+                ServiceDispatcher sd = null;
+                if (map != null) {
+                    sd = map.get(c);
+                    if (sd != null) {
+                        map.remove(c);
+                        sd.doForget();
+                        if (map.size() == 0) {
+                            mServices.remove(context);
+                        }
+                        if ((sd.getFlags()&Context.BIND_DEBUG_UNBIND) != 0) {
+                            HashMap<ServiceConnection, ServiceDispatcher> holder
+                                    = mUnboundServices.get(context);
+                            if (holder == null) {
+                                holder = new HashMap<ServiceConnection, ServiceDispatcher>();
+                                mUnboundServices.put(context, holder);
+                            }
+                            RuntimeException ex = new IllegalArgumentException(
+                                    "Originally unbound here:");
+                            ex.fillInStackTrace();
+                            sd.setUnbindLocation(ex);
+                            holder.put(c, sd);
+                        }
+                        return sd.getIServiceConnection();
+                    }
+                }
+                HashMap<ServiceConnection, ServiceDispatcher> holder
+                        = mUnboundServices.get(context);
+                if (holder != null) {
+                    sd = holder.get(c);
+                    if (sd != null) {
+                        RuntimeException ex = sd.getUnbindLocation();
+                        throw new IllegalArgumentException(
+                                "Unbinding Service " + c
+                                + " that was already unbound", ex);
+                    }
+                }
+                if (context == null) {
+                    throw new IllegalStateException("Unbinding Service " + c
+                            + " from Context that is no longer in use: " + context);
+                } else {
+                    throw new IllegalArgumentException("Service not registered: " + c);
+                }
+            }
+        }
+
+        static final class ServiceDispatcher {
+            private final InnerConnection mIServiceConnection;
+            private final ServiceConnection mConnection;
+            private final Context mContext;
+            private final Handler mActivityThread;
+            private final ServiceConnectionLeaked mLocation;
+            private final int mFlags;
+
+            private RuntimeException mUnbindLocation;
+
+            private boolean mDied;
+
+            private static class ConnectionInfo {
+                IBinder binder;
+                IBinder.DeathRecipient deathMonitor;
+            }
+
+            private static class InnerConnection extends IServiceConnection.Stub {
+                final WeakReference<ServiceDispatcher> mDispatcher;
+                
+                InnerConnection(ServiceDispatcher sd) {
+                    mDispatcher = new WeakReference<ServiceDispatcher>(sd);
+                }
+
+                public void connected(ComponentName name, IBinder service) throws RemoteException {
+                    ServiceDispatcher sd = mDispatcher.get();
+                    if (sd != null) {
+                        sd.connected(name, service);
+                    }
+                }
+            }
+            
+            private final HashMap<ComponentName, ConnectionInfo> mActiveConnections
+                = new HashMap<ComponentName, ConnectionInfo>();
+
+            ServiceDispatcher(ServiceConnection conn,
+                    Context context, Handler activityThread, int flags) {
+                mIServiceConnection = new InnerConnection(this);
+                mConnection = conn;
+                mContext = context;
+                mActivityThread = activityThread;
+                mLocation = new ServiceConnectionLeaked(null);
+                mLocation.fillInStackTrace();
+                mFlags = flags;
+            }
+
+            void validate(Context context, Handler activityThread) {
+                if (mContext != context) {
+                    throw new RuntimeException(
+                        "ServiceConnection " + mConnection +
+                        " registered with differing Context (was " +
+                        mContext + " now " + context + ")");
+                }
+                if (mActivityThread != activityThread) {
+                    throw new RuntimeException(
+                        "ServiceConnection " + mConnection +
+                        " registered with differing handler (was " +
+                        mActivityThread + " now " + activityThread + ")");
+                }
+            }
+
+            void doForget() {
+                synchronized(this) {
+                    Iterator<ConnectionInfo> it = mActiveConnections.values().iterator();
+                    while (it.hasNext()) {
+                        ConnectionInfo ci = it.next();
+                        ci.binder.unlinkToDeath(ci.deathMonitor, 0);
+                    }
+                    mActiveConnections.clear();
+                }
+            }
+
+            ServiceConnectionLeaked getLocation() {
+                return mLocation;
+            }
+
+            ServiceConnection getServiceConnection() {
+                return mConnection;
+            }
+
+            IServiceConnection getIServiceConnection() {
+                return mIServiceConnection;
+            }
+            
+            int getFlags() {
+                return mFlags;
+            }
+
+            void setUnbindLocation(RuntimeException ex) {
+                mUnbindLocation = ex;
+            }
+
+            RuntimeException getUnbindLocation() {
+                return mUnbindLocation;
+            }
+
+            public void connected(ComponentName name, IBinder service) {
+                if (mActivityThread != null) {
+                    mActivityThread.post(new RunConnection(name, service, 0));
+                } else {
+                    doConnected(name, service);
+                }
+            }
+
+            public void death(ComponentName name, IBinder service) {
+                ConnectionInfo old;
+
+                synchronized (this) {
+                    mDied = true;
+                    old = mActiveConnections.remove(name);
+                    if (old == null || old.binder != service) {
+                        // Death for someone different than who we last
+                        // reported...  just ignore it.
+                        return;
+                    }
+                    old.binder.unlinkToDeath(old.deathMonitor, 0);
+                }
+
+                if (mActivityThread != null) {
+                    mActivityThread.post(new RunConnection(name, service, 1));
+                } else {
+                    doDeath(name, service);
+                }
+            }
+
+            public void doConnected(ComponentName name, IBinder service) {
+                ConnectionInfo old;
+                ConnectionInfo info;
+
+                synchronized (this) {
+                    old = mActiveConnections.get(name);
+                    if (old != null && old.binder == service) {
+                        // Huh, already have this one.  Oh well!
+                        return;
+                    }
+
+                    if (service != null) {
+                        // A new service is being connected... set it all up.
+                        mDied = false;
+                        info = new ConnectionInfo();
+                        info.binder = service;
+                        info.deathMonitor = new DeathMonitor(name, service);
+                        try {
+                            service.linkToDeath(info.deathMonitor, 0);
+                            mActiveConnections.put(name, info);
+                        } catch (RemoteException e) {
+                            // This service was dead before we got it...  just
+                            // don't do anything with it.
+                            mActiveConnections.remove(name);
+                            return;
+                        }
+
+                    } else {
+                        // The named service is being disconnected... clean up.
+                        mActiveConnections.remove(name);
+                    }
+
+                    if (old != null) {
+                        old.binder.unlinkToDeath(old.deathMonitor, 0);
+                    }
+                }
+
+                // If there was an old service, it is not disconnected.
+                if (old != null) {
+                    mConnection.onServiceDisconnected(name);
+                }
+                // If there is a new service, it is now connected.
+                if (service != null) {
+                    mConnection.onServiceConnected(name, service);
+                }
+            }
+
+            public void doDeath(ComponentName name, IBinder service) {
+                mConnection.onServiceDisconnected(name);
+            }
+
+            private final class RunConnection implements Runnable {
+                RunConnection(ComponentName name, IBinder service, int command) {
+                    mName = name;
+                    mService = service;
+                    mCommand = command;
+                }
+
+                public void run() {
+                    if (mCommand == 0) {
+                        doConnected(mName, mService);
+                    } else if (mCommand == 1) {
+                        doDeath(mName, mService);
+                    }
+                }
+
+                final ComponentName mName;
+                final IBinder mService;
+                final int mCommand;
+            }
+
+            private final class DeathMonitor implements IBinder.DeathRecipient
+            {
+                DeathMonitor(ComponentName name, IBinder service) {
+                    mName = name;
+                    mService = service;
+                }
+
+                public void binderDied() {
+                    death(mName, mService);
+                }
+
+                final ComponentName mName;
+                final IBinder mService;
+            }
+        }
+    }
+
+    private static ApplicationContext mSystemContext = null;
+
+    private static final class ActivityRecord {
+        IBinder token;
+        Intent intent;
+        Bundle state;
+        Activity activity;
+        Window window;
+        Activity parent;
+        String embeddedID;
+        Object lastNonConfigurationInstance;
+        HashMap<String,Object> lastNonConfigurationChildInstances;
+        boolean paused;
+        boolean stopped;
+        boolean hideForNow;
+        Configuration newConfig;
+        ActivityRecord nextIdle;
+
+        ActivityInfo activityInfo;
+        PackageInfo packageInfo;
+
+        List<ResultInfo> pendingResults;
+        List<Intent> pendingIntents;
+
+        boolean startsNotResumed;
+        boolean isForward;
+
+        ActivityRecord() {
+            parent = null;
+            embeddedID = null;
+            paused = false;
+            stopped = false;
+            hideForNow = false;
+            nextIdle = null;
+        }
+
+        public String toString() {
+            ComponentName componentName = intent.getComponent();
+            return "ActivityRecord{"
+                + Integer.toHexString(System.identityHashCode(this))
+                + " token=" + token + " " + (componentName == null
+                        ? "no component name" : componentName.toShortString())
+                + "}";
+        }
+    }
+
+    private final class ProviderRecord implements IBinder.DeathRecipient {
+        final String mName;
+        final IContentProvider mProvider;
+        final ContentProvider mLocalProvider;
+
+        ProviderRecord(String name, IContentProvider provider,
+                ContentProvider localProvider) {
+            mName = name;
+            mProvider = provider;
+            mLocalProvider = localProvider;
+        }
+
+        public void binderDied() {
+            removeDeadProvider(mName, mProvider);
+        }
+    }
+
+    private static final class NewIntentData {
+        List<Intent> intents;
+        IBinder token;
+        public String toString() {
+            return "NewIntentData{intents=" + intents + " token=" + token + "}";
+        }
+    }
+
+    private static final class ReceiverData {
+        Intent intent;
+        ActivityInfo info;
+        int resultCode;
+        String resultData;
+        Bundle resultExtras;
+        boolean sync;
+        boolean resultAbort;
+        public String toString() {
+            return "ReceiverData{intent=" + intent + " packageName=" +
+            info.packageName + " resultCode=" + resultCode
+            + " resultData=" + resultData + " resultExtras=" + resultExtras + "}";
+        }
+    }
+
+    private static final class CreateServiceData {
+        IBinder token;
+        ServiceInfo info;
+        Intent intent;
+        public String toString() {
+            return "CreateServiceData{token=" + token + " className="
+            + info.name + " packageName=" + info.packageName
+            + " intent=" + intent + "}";
+        }
+    }
+
+    private static final class BindServiceData {
+        IBinder token;
+        Intent intent;
+        boolean rebind;
+        public String toString() {
+            return "BindServiceData{token=" + token + " intent=" + intent + "}";
+        }
+    }
+
+    private static final class ServiceArgsData {
+        IBinder token;
+        int startId;
+        Intent args;
+        public String toString() {
+            return "ServiceArgsData{token=" + token + " startId=" + startId
+            + " args=" + args + "}";
+        }
+    }
+
+    private static final class AppBindData {
+        PackageInfo info;
+        String processName;
+        ApplicationInfo appInfo;
+        List<ProviderInfo> providers;
+        ComponentName instrumentationName;
+        String profileFile;
+        Bundle instrumentationArgs;
+        IInstrumentationWatcher instrumentationWatcher;
+        int debugMode;
+        Configuration config;
+        boolean handlingProfiling;
+        public String toString() {
+            return "AppBindData{appInfo=" + appInfo + "}";
+        }
+    }
+
+    private static final class DumpServiceInfo {
+        FileDescriptor fd;
+        IBinder service;
+        String[] args;
+        boolean dumped;
+    }
+
+    private static final class ResultData {
+        IBinder token;
+        List<ResultInfo> results;
+        public String toString() {
+            return "ResultData{token=" + token + " results" + results + "}";
+        }
+    }
+
+    private static final class ContextCleanupInfo {
+        ApplicationContext context;
+        String what;
+        String who;
+    }
+
+    private final class ApplicationThread extends ApplicationThreadNative {
+        private static final String HEAP_COLUMN = "%17s %8s %8s %8s %8s";
+        private static final String ONE_COUNT_COLUMN = "%17s %8d";
+        private static final String TWO_COUNT_COLUMNS = "%17s %8d %17s %8d";
+        
+        // Formatting for checkin service - update version if row format changes
+        private static final int ACTIVITY_THREAD_CHECKIN_VERSION = 1;
+       
+        public final void schedulePauseActivity(IBinder token, boolean finished,
+                boolean userLeaving, int configChanges) {
+            queueOrSendMessage(
+                    finished ? H.PAUSE_ACTIVITY_FINISHING : H.PAUSE_ACTIVITY,
+                    token,
+                    (userLeaving ? 1 : 0),
+                    configChanges);
+        }
+
+        public final void scheduleStopActivity(IBinder token, boolean showWindow,
+                int configChanges) {
+           queueOrSendMessage(
+                showWindow ? H.STOP_ACTIVITY_SHOW : H.STOP_ACTIVITY_HIDE,
+                token, 0, configChanges);
+        }
+
+        public final void scheduleWindowVisibility(IBinder token, boolean showWindow) {
+            queueOrSendMessage(
+                showWindow ? H.SHOW_WINDOW : H.HIDE_WINDOW,
+                token);
+        }
+
+        public final void scheduleResumeActivity(IBinder token, boolean isForward) {
+            queueOrSendMessage(H.RESUME_ACTIVITY, token, isForward ? 1 : 0);
+        }
+
+        public final void scheduleSendResult(IBinder token, List<ResultInfo> results) {
+            ResultData res = new ResultData();
+            res.token = token;
+            res.results = results;
+            queueOrSendMessage(H.SEND_RESULT, res);
+        }
+
+        // we use token to identify this activity without having to send the
+        // activity itself back to the activity manager. (matters more with ipc)
+        public final void scheduleLaunchActivity(Intent intent, IBinder token,
+                ActivityInfo info, Bundle state, List<ResultInfo> pendingResults,
+                List<Intent> pendingNewIntents, boolean notResumed, boolean isForward) {
+            ActivityRecord r = new ActivityRecord();
+
+            r.token = token;
+            r.intent = intent;
+            r.activityInfo = info;
+            r.state = state;
+
+            r.pendingResults = pendingResults;
+            r.pendingIntents = pendingNewIntents;
+
+            r.startsNotResumed = notResumed;
+            r.isForward = isForward;
+
+            queueOrSendMessage(H.LAUNCH_ACTIVITY, r);
+        }
+
+        public final void scheduleRelaunchActivity(IBinder token,
+                List<ResultInfo> pendingResults, List<Intent> pendingNewIntents,
+                int configChanges, boolean notResumed) {
+            ActivityRecord r = new ActivityRecord();
+
+            r.token = token;
+            r.pendingResults = pendingResults;
+            r.pendingIntents = pendingNewIntents;
+            r.startsNotResumed = notResumed;
+
+            synchronized (mRelaunchingActivities) {
+                mRelaunchingActivities.add(r);
+            }
+            
+            queueOrSendMessage(H.RELAUNCH_ACTIVITY, r, configChanges);
+        }
+
+        public final void scheduleNewIntent(List<Intent> intents, IBinder token) {
+            NewIntentData data = new NewIntentData();
+            data.intents = intents;
+            data.token = token;
+
+            queueOrSendMessage(H.NEW_INTENT, data);
+        }
+
+        public final void scheduleDestroyActivity(IBinder token, boolean finishing,
+                int configChanges) {
+            queueOrSendMessage(H.DESTROY_ACTIVITY, token, finishing ? 1 : 0,
+                    configChanges);
+        }
+
+        public final void scheduleReceiver(Intent intent, ActivityInfo info,
+                int resultCode, String data, Bundle extras, boolean sync) {
+            ReceiverData r = new ReceiverData();
+
+            r.intent = intent;
+            r.info = info;
+            r.resultCode = resultCode;
+            r.resultData = data;
+            r.resultExtras = extras;
+            r.sync = sync;
+
+            queueOrSendMessage(H.RECEIVER, r);
+        }
+
+        public final void scheduleCreateService(IBinder token,
+                ServiceInfo info) {
+            CreateServiceData s = new CreateServiceData();
+            s.token = token;
+            s.info = info;
+
+            queueOrSendMessage(H.CREATE_SERVICE, s);
+        }
+
+        public final void scheduleBindService(IBinder token, Intent intent,
+                boolean rebind) {
+            BindServiceData s = new BindServiceData();
+            s.token = token;
+            s.intent = intent;
+            s.rebind = rebind;
+
+            queueOrSendMessage(H.BIND_SERVICE, s);
+        }
+
+        public final void scheduleUnbindService(IBinder token, Intent intent) {
+            BindServiceData s = new BindServiceData();
+            s.token = token;
+            s.intent = intent;
+
+            queueOrSendMessage(H.UNBIND_SERVICE, s);
+        }
+
+        public final void scheduleServiceArgs(IBinder token, int startId,
+            Intent args) {
+            ServiceArgsData s = new ServiceArgsData();
+            s.token = token;
+            s.startId = startId;
+            s.args = args;
+
+            queueOrSendMessage(H.SERVICE_ARGS, s);
+        }
+
+        public final void scheduleStopService(IBinder token) {
+            queueOrSendMessage(H.STOP_SERVICE, token);
+        }
+
+        public final void bindApplication(String processName,
+                ApplicationInfo appInfo, List<ProviderInfo> providers,
+                ComponentName instrumentationName, String profileFile,
+                Bundle instrumentationArgs, IInstrumentationWatcher instrumentationWatcher,
+                int debugMode, Configuration config,
+                Map<String, IBinder> services) {
+            Process.setArgV0(processName);
+
+            if (services != null) {
+                // Setup the service cache in the ServiceManager
+                ServiceManager.initServiceCache(services);
+            }
+
+            AppBindData data = new AppBindData();
+            data.processName = processName;
+            data.appInfo = appInfo;
+            data.providers = providers;
+            data.instrumentationName = instrumentationName;
+            data.profileFile = profileFile;
+            data.instrumentationArgs = instrumentationArgs;
+            data.instrumentationWatcher = instrumentationWatcher;
+            data.debugMode = debugMode;
+            data.config = config;
+            queueOrSendMessage(H.BIND_APPLICATION, data);
+        }
+
+        public final void scheduleExit() {
+            queueOrSendMessage(H.EXIT_APPLICATION, null);
+        }
+
+        public void requestThumbnail(IBinder token) {
+            queueOrSendMessage(H.REQUEST_THUMBNAIL, token);
+        }
+
+        public void scheduleConfigurationChanged(Configuration config) {
+            synchronized (mRelaunchingActivities) {
+                mPendingConfiguration = config;
+            }
+            queueOrSendMessage(H.CONFIGURATION_CHANGED, config);
+        }
+
+        public void updateTimeZone() {
+            TimeZone.setDefault(null);
+        }
+
+        public void processInBackground() {
+            mH.removeMessages(H.GC_WHEN_IDLE);
+            mH.sendMessage(mH.obtainMessage(H.GC_WHEN_IDLE));
+        }
+
+        public void dumpService(FileDescriptor fd, IBinder servicetoken, String[] args) {
+            DumpServiceInfo data = new DumpServiceInfo();
+            data.fd = fd;
+            data.service = servicetoken;
+            data.args = args;
+            data.dumped = false;
+            queueOrSendMessage(H.DUMP_SERVICE, data);
+            synchronized (data) {
+                while (!data.dumped) {
+                    try {
+                        data.wait();
+                    } catch (InterruptedException e) {
+                        // no need to do anything here, we will keep waiting until
+                        // dumped is set
+                    }
+                }
+            }
+        }
+
+        // This function exists to make sure all receiver dispatching is
+        // correctly ordered, since these are one-way calls and the binder driver
+        // applies transaction ordering per object for such calls.
+        public void scheduleRegisteredReceiver(IIntentReceiver receiver, Intent intent,
+                int resultCode, String dataStr, Bundle extras, boolean ordered)
+                throws RemoteException {
+            receiver.performReceive(intent, resultCode, dataStr, extras, ordered);
+        }
+        
+        public void scheduleLowMemory() {
+            queueOrSendMessage(H.LOW_MEMORY, null);
+        }
+
+        public void scheduleActivityConfigurationChanged(IBinder token) {
+            queueOrSendMessage(H.ACTIVITY_CONFIGURATION_CHANGED, token);
+        }
+
+        public void requestPss() {
+            try {
+                ActivityManagerNative.getDefault().reportPss(this,
+                        (int)Process.getPss(Process.myPid()));
+            } catch (RemoteException e) {
+            }
+        }
+        
+        @Override
+        protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+            long nativeMax = Debug.getNativeHeapSize() / 1024;
+            long nativeAllocated = Debug.getNativeHeapAllocatedSize() / 1024;
+            long nativeFree = Debug.getNativeHeapFreeSize() / 1024;
+
+            Debug.MemoryInfo memInfo = new Debug.MemoryInfo();
+            Debug.getMemoryInfo(memInfo);
+
+            final int nativeShared = memInfo.nativeSharedDirty;
+            final int dalvikShared = memInfo.dalvikSharedDirty;
+            final int otherShared = memInfo.otherSharedDirty;
+
+            final int nativePrivate = memInfo.nativePrivateDirty;
+            final int dalvikPrivate = memInfo.dalvikPrivateDirty;
+            final int otherPrivate = memInfo.otherPrivateDirty;
+
+            Runtime runtime = Runtime.getRuntime();
+
+            long dalvikMax = runtime.totalMemory() / 1024;
+            long dalvikFree = runtime.freeMemory() / 1024;
+            long dalvikAllocated = dalvikMax - dalvikFree;
+            long viewInstanceCount = ViewDebug.getViewInstanceCount();
+            long viewRootInstanceCount = ViewDebug.getViewRootInstanceCount();
+            long appContextInstanceCount = ApplicationContext.getInstanceCount();
+            long activityInstanceCount = Activity.getInstanceCount();
+            int globalAssetCount = AssetManager.getGlobalAssetCount();
+            int globalAssetManagerCount = AssetManager.getGlobalAssetManagerCount();
+            int binderLocalObjectCount = Debug.getBinderLocalObjectCount();
+            int binderProxyObjectCount = Debug.getBinderProxyObjectCount();
+            int binderDeathObjectCount = Debug.getBinderDeathObjectCount();
+            int openSslSocketCount = OpenSSLSocketImpl.getInstanceCount();
+            long sqliteAllocated = SQLiteDebug.getHeapAllocatedSize() / 1024;
+            SQLiteDebug.PagerStats stats = new SQLiteDebug.PagerStats();
+            SQLiteDebug.getPagerStats(stats);
+            
+            // Check to see if we were called by checkin server. If so, print terse format.
+            boolean doCheckinFormat = false;
+            if (args != null) {
+                for (String arg : args) {
+                    if ("-c".equals(arg)) doCheckinFormat = true;
+                }
+            }
+            
+            // For checkin, we print one long comma-separated list of values
+            if (doCheckinFormat) {
+                // NOTE: if you change anything significant below, also consider changing
+                // ACTIVITY_THREAD_CHECKIN_VERSION.
+                String processName = (mBoundApplication != null) 
+                        ? mBoundApplication.processName : "unknown";
+                
+                // Header
+                pw.print(ACTIVITY_THREAD_CHECKIN_VERSION); pw.print(',');
+                pw.print(Process.myPid()); pw.print(',');
+                pw.print(processName); pw.print(',');
+                
+                // Heap info - max
+                pw.print(nativeMax); pw.print(',');
+                pw.print(dalvikMax); pw.print(',');
+                pw.print("N/A,");
+                pw.print(nativeMax + dalvikMax); pw.print(',');
+                
+                // Heap info - allocated
+                pw.print(nativeAllocated); pw.print(',');
+                pw.print(dalvikAllocated); pw.print(',');
+                pw.print("N/A,");
+                pw.print(nativeAllocated + dalvikAllocated); pw.print(',');
+                
+                // Heap info - free
+                pw.print(nativeFree); pw.print(',');
+                pw.print(dalvikFree); pw.print(',');
+                pw.print("N/A,");
+                pw.print(nativeFree + dalvikFree); pw.print(',');
+                
+                // Heap info - proportional set size
+                pw.print(memInfo.nativePss); pw.print(',');
+                pw.print(memInfo.dalvikPss); pw.print(',');
+                pw.print(memInfo.otherPss); pw.print(',');
+                pw.print(memInfo.nativePss + memInfo.dalvikPss + memInfo.otherPss); pw.print(',');
+                
+                // Heap info - shared
+                pw.print(nativeShared); pw.print(','); 
+                pw.print(dalvikShared); pw.print(','); 
+                pw.print(otherShared); pw.print(','); 
+                pw.print(nativeShared + dalvikShared + otherShared); pw.print(',');
+                
+                // Heap info - private
+                pw.print(nativePrivate); pw.print(','); 
+                pw.print(dalvikPrivate); pw.print(',');
+                pw.print(otherPrivate); pw.print(',');
+                pw.print(nativePrivate + dalvikPrivate + otherPrivate); pw.print(',');
+                
+                // Object counts
+                pw.print(viewInstanceCount); pw.print(',');
+                pw.print(viewRootInstanceCount); pw.print(',');
+                pw.print(appContextInstanceCount); pw.print(',');
+                pw.print(activityInstanceCount); pw.print(',');
+                
+                pw.print(globalAssetCount); pw.print(',');
+                pw.print(globalAssetManagerCount); pw.print(',');
+                pw.print(binderLocalObjectCount); pw.print(',');
+                pw.print(binderProxyObjectCount); pw.print(',');
+                
+                pw.print(binderDeathObjectCount); pw.print(',');
+                pw.print(openSslSocketCount); pw.print(',');
+                
+                // SQL
+                pw.print(sqliteAllocated); pw.print(',');
+                pw.print(stats.databaseBytes / 1024); pw.print(','); 
+                pw.print(stats.numPagers); pw.print(',');
+                pw.print((stats.totalBytes - stats.referencedBytes) / 1024); pw.print(',');
+                pw.print(stats.referencedBytes / 1024); pw.print('\n');
+                
+                return;
+            }
+            
+            // otherwise, show human-readable format
+            printRow(pw, HEAP_COLUMN, "", "native", "dalvik", "other", "total");
+            printRow(pw, HEAP_COLUMN, "size:", nativeMax, dalvikMax, "N/A", nativeMax + dalvikMax);
+            printRow(pw, HEAP_COLUMN, "allocated:", nativeAllocated, dalvikAllocated, "N/A",
+                    nativeAllocated + dalvikAllocated);
+            printRow(pw, HEAP_COLUMN, "free:", nativeFree, dalvikFree, "N/A",
+                    nativeFree + dalvikFree);
+
+            printRow(pw, HEAP_COLUMN, "(Pss):", memInfo.nativePss, memInfo.dalvikPss,
+                    memInfo.otherPss, memInfo.nativePss + memInfo.dalvikPss + memInfo.otherPss);
+
+            printRow(pw, HEAP_COLUMN, "(shared dirty):", nativeShared, dalvikShared, otherShared,
+                    nativeShared + dalvikShared + otherShared);
+            printRow(pw, HEAP_COLUMN, "(priv dirty):", nativePrivate, dalvikPrivate, otherPrivate,
+                    nativePrivate + dalvikPrivate + otherPrivate);
+
+            pw.println(" ");
+            pw.println(" Objects");
+            printRow(pw, TWO_COUNT_COLUMNS, "Views:", viewInstanceCount, "ViewRoots:",
+                    viewRootInstanceCount);
+
+            printRow(pw, TWO_COUNT_COLUMNS, "AppContexts:", appContextInstanceCount,
+                    "Activities:", activityInstanceCount);
+
+            printRow(pw, TWO_COUNT_COLUMNS, "Assets:", globalAssetCount,
+                    "AssetManagers:", globalAssetManagerCount);
+
+            printRow(pw, TWO_COUNT_COLUMNS, "Local Binders:", binderLocalObjectCount,
+                    "Proxy Binders:", binderProxyObjectCount);
+            printRow(pw, ONE_COUNT_COLUMN, "Death Recipients:", binderDeathObjectCount);
+
+            printRow(pw, ONE_COUNT_COLUMN, "OpenSSL Sockets:", openSslSocketCount);
+            
+            // SQLite mem info
+            pw.println(" ");
+            pw.println(" SQL");
+            printRow(pw, TWO_COUNT_COLUMNS, "heap:", sqliteAllocated, "dbFiles:",
+                    stats.databaseBytes / 1024);
+            printRow(pw, TWO_COUNT_COLUMNS, "numPagers:", stats.numPagers, "inactivePageKB:",
+                    (stats.totalBytes - stats.referencedBytes) / 1024);
+            printRow(pw, ONE_COUNT_COLUMN, "activePageKB:", stats.referencedBytes / 1024);
+        }
+
+        private void printRow(PrintWriter pw, String format, Object...objs) {
+            pw.println(String.format(format, objs));
+        }
+    }
+
+    private final class H extends Handler {
+        public static final int LAUNCH_ACTIVITY         = 100;
+        public static final int PAUSE_ACTIVITY          = 101;
+        public static final int PAUSE_ACTIVITY_FINISHING= 102;
+        public static final int STOP_ACTIVITY_SHOW      = 103;
+        public static final int STOP_ACTIVITY_HIDE      = 104;
+        public static final int SHOW_WINDOW             = 105;
+        public static final int HIDE_WINDOW             = 106;
+        public static final int RESUME_ACTIVITY         = 107;
+        public static final int SEND_RESULT             = 108;
+        public static final int DESTROY_ACTIVITY         = 109;
+        public static final int BIND_APPLICATION        = 110;
+        public static final int EXIT_APPLICATION        = 111;
+        public static final int NEW_INTENT              = 112;
+        public static final int RECEIVER                = 113;
+        public static final int CREATE_SERVICE          = 114;
+        public static final int SERVICE_ARGS            = 115;
+        public static final int STOP_SERVICE            = 116;
+        public static final int REQUEST_THUMBNAIL       = 117;
+        public static final int CONFIGURATION_CHANGED   = 118;
+        public static final int CLEAN_UP_CONTEXT        = 119;
+        public static final int GC_WHEN_IDLE            = 120;
+        public static final int BIND_SERVICE            = 121;
+        public static final int UNBIND_SERVICE          = 122;
+        public static final int DUMP_SERVICE            = 123;
+        public static final int LOW_MEMORY              = 124;
+        public static final int ACTIVITY_CONFIGURATION_CHANGED = 125;
+        public static final int RELAUNCH_ACTIVITY       = 126;
+        String codeToString(int code) {
+            if (localLOGV) {
+                switch (code) {
+                    case LAUNCH_ACTIVITY: return "LAUNCH_ACTIVITY";
+                    case PAUSE_ACTIVITY: return "PAUSE_ACTIVITY";
+                    case PAUSE_ACTIVITY_FINISHING: return "PAUSE_ACTIVITY_FINISHING";
+                    case STOP_ACTIVITY_SHOW: return "STOP_ACTIVITY_SHOW";
+                    case STOP_ACTIVITY_HIDE: return "STOP_ACTIVITY_HIDE";
+                    case SHOW_WINDOW: return "SHOW_WINDOW";
+                    case HIDE_WINDOW: return "HIDE_WINDOW";
+                    case RESUME_ACTIVITY: return "RESUME_ACTIVITY";
+                    case SEND_RESULT: return "SEND_RESULT";
+                    case DESTROY_ACTIVITY: return "DESTROY_ACTIVITY";
+                    case BIND_APPLICATION: return "BIND_APPLICATION";
+                    case EXIT_APPLICATION: return "EXIT_APPLICATION";
+                    case NEW_INTENT: return "NEW_INTENT";
+                    case RECEIVER: return "RECEIVER";
+                    case CREATE_SERVICE: return "CREATE_SERVICE";
+                    case SERVICE_ARGS: return "SERVICE_ARGS";
+                    case STOP_SERVICE: return "STOP_SERVICE";
+                    case REQUEST_THUMBNAIL: return "REQUEST_THUMBNAIL";
+                    case CONFIGURATION_CHANGED: return "CONFIGURATION_CHANGED";
+                    case CLEAN_UP_CONTEXT: return "CLEAN_UP_CONTEXT";
+                    case GC_WHEN_IDLE: return "GC_WHEN_IDLE";
+                    case BIND_SERVICE: return "BIND_SERVICE";
+                    case UNBIND_SERVICE: return "UNBIND_SERVICE";
+                    case DUMP_SERVICE: return "DUMP_SERVICE";
+                    case LOW_MEMORY: return "LOW_MEMORY";
+                    case ACTIVITY_CONFIGURATION_CHANGED: return "ACTIVITY_CONFIGURATION_CHANGED";
+                    case RELAUNCH_ACTIVITY: return "RELAUNCH_ACTIVITY";
+                }
+            }
+            return "(unknown)";
+        }
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case LAUNCH_ACTIVITY: {
+                    ActivityRecord r = (ActivityRecord)msg.obj;
+
+                    r.packageInfo = getPackageInfoNoCheck(
+                            r.activityInfo.applicationInfo);
+                    handleLaunchActivity(r);
+                } break;
+                case RELAUNCH_ACTIVITY: {
+                    ActivityRecord r = (ActivityRecord)msg.obj;
+                    handleRelaunchActivity(r, msg.arg1);
+                } break;
+                case PAUSE_ACTIVITY:
+                    handlePauseActivity((IBinder)msg.obj, false, msg.arg1 != 0, msg.arg2);
+                    break;
+                case PAUSE_ACTIVITY_FINISHING:
+                    handlePauseActivity((IBinder)msg.obj, true, msg.arg1 != 0, msg.arg2);
+                    break;
+                case STOP_ACTIVITY_SHOW:
+                    handleStopActivity((IBinder)msg.obj, true, msg.arg2);
+                    break;
+                case STOP_ACTIVITY_HIDE:
+                    handleStopActivity((IBinder)msg.obj, false, msg.arg2);
+                    break;
+                case SHOW_WINDOW:
+                    handleWindowVisibility((IBinder)msg.obj, true);
+                    break;
+                case HIDE_WINDOW:
+                    handleWindowVisibility((IBinder)msg.obj, false);
+                    break;
+                case RESUME_ACTIVITY:
+                    handleResumeActivity((IBinder)msg.obj, true,
+                            msg.arg1 != 0);
+                    break;
+                case SEND_RESULT:
+                    handleSendResult((ResultData)msg.obj);
+                    break;
+                case DESTROY_ACTIVITY:
+                    handleDestroyActivity((IBinder)msg.obj, msg.arg1 != 0,
+                            msg.arg2, false);
+                    break;
+                case BIND_APPLICATION:
+                    AppBindData data = (AppBindData)msg.obj;
+                    handleBindApplication(data);
+                    break;
+                case EXIT_APPLICATION:
+                    if (mInitialApplication != null) {
+                        mInitialApplication.onTerminate();
+                    }
+                    Looper.myLooper().quit();
+                    break;
+                case NEW_INTENT:
+                    handleNewIntent((NewIntentData)msg.obj);
+                    break;
+                case RECEIVER:
+                    handleReceiver((ReceiverData)msg.obj);
+                    break;
+                case CREATE_SERVICE:
+                    handleCreateService((CreateServiceData)msg.obj);
+                    break;
+                case BIND_SERVICE:
+                    handleBindService((BindServiceData)msg.obj);
+                    break;
+                case UNBIND_SERVICE:
+                    handleUnbindService((BindServiceData)msg.obj);
+                    break;
+                case SERVICE_ARGS:
+                    handleServiceArgs((ServiceArgsData)msg.obj);
+                    break;
+                case STOP_SERVICE:
+                    handleStopService((IBinder)msg.obj);
+                    break;
+                case REQUEST_THUMBNAIL:
+                    handleRequestThumbnail((IBinder)msg.obj);
+                    break;
+                case CONFIGURATION_CHANGED:
+                    handleConfigurationChanged((Configuration)msg.obj);
+                    break;
+                case CLEAN_UP_CONTEXT:
+                    ContextCleanupInfo cci = (ContextCleanupInfo)msg.obj;
+                    cci.context.performFinalCleanup(cci.who, cci.what);
+                    break;
+                case GC_WHEN_IDLE:
+                    scheduleGcIdler();
+                    break;
+                case DUMP_SERVICE:
+                    handleDumpService((DumpServiceInfo)msg.obj);
+                    break;
+                case LOW_MEMORY:
+                    handleLowMemory();
+                    break;
+                case ACTIVITY_CONFIGURATION_CHANGED:
+                    handleActivityConfigurationChanged((IBinder)msg.obj);
+                    break;
+            }
+        }
+    }
+
+    private final class Idler implements MessageQueue.IdleHandler {
+        public final boolean queueIdle() {
+            ActivityRecord a = mNewActivities;
+            if (a != null) {
+                mNewActivities = null;
+                IActivityManager am = ActivityManagerNative.getDefault();
+                ActivityRecord prev;
+                do {
+                    if (localLOGV) Log.v(
+                        TAG, "Reporting idle of " + a +
+                        " finished=" +
+                        (a.activity != null ? a.activity.mFinished : false));
+                    if (a.activity != null && !a.activity.mFinished) {
+                        try {
+                            am.activityIdle(a.token);
+                        } catch (RemoteException ex) {
+                        }
+                    }
+                    prev = a;
+                    a = a.nextIdle;
+                    prev.nextIdle = null;
+                } while (a != null);
+            }
+            return false;
+        }
+    }
+
+    final class GcIdler implements MessageQueue.IdleHandler {
+        public final boolean queueIdle() {
+            doGcIfNeeded();
+            return false;
+        }
+    }
+
+    static IPackageManager sPackageManager;
+
+    final ApplicationThread mAppThread = new ApplicationThread();
+    final Looper mLooper = Looper.myLooper();
+    final H mH = new H();
+    final HashMap<IBinder, ActivityRecord> mActivities
+            = new HashMap<IBinder, ActivityRecord>();
+    // List of new activities (via ActivityRecord.nextIdle) that should
+    // be reported when next we idle.
+    ActivityRecord mNewActivities = null;
+    // Number of activities that are currently visible on-screen.
+    int mNumVisibleActivities = 0;
+    final HashMap<IBinder, Service> mServices
+            = new HashMap<IBinder, Service>();
+    AppBindData mBoundApplication;
+    Configuration mConfiguration;
+    Application mInitialApplication;
+    final ArrayList<Application> mAllApplications
+            = new ArrayList<Application>();
+    static final ThreadLocal sThreadLocal = new ThreadLocal();
+    Instrumentation mInstrumentation;
+    String mInstrumentationAppDir = null;
+    String mInstrumentationAppPackage = null;
+    String mInstrumentedAppDir = null;
+    boolean mSystemThread = false;
+
+    /**
+     * Activities that are enqueued to be relaunched.  This list is accessed
+     * by multiple threads, so you must synchronize on it when accessing it.
+     */
+    final ArrayList<ActivityRecord> mRelaunchingActivities
+            = new ArrayList<ActivityRecord>();
+    Configuration mPendingConfiguration = null;
+    
+    // These can be accessed by multiple threads; mPackages is the lock.
+    // XXX For now we keep around information about all packages we have
+    // seen, not removing entries from this map.
+    final HashMap<String, WeakReference<PackageInfo>> mPackages
+        = new HashMap<String, WeakReference<PackageInfo>>();
+    final HashMap<String, WeakReference<PackageInfo>> mResourcePackages
+        = new HashMap<String, WeakReference<PackageInfo>>();
+    Display mDisplay = null;
+    DisplayMetrics mDisplayMetrics = null;
+    HashMap<String, WeakReference<Resources> > mActiveResources
+        = new HashMap<String, WeakReference<Resources> >();
+
+    // The lock of mProviderMap protects the following variables.
+    final HashMap<String, ProviderRecord> mProviderMap
+        = new HashMap<String, ProviderRecord>();
+    final HashMap<IBinder, ProviderRefCount> mProviderRefCountMap
+        = new HashMap<IBinder, ProviderRefCount>();
+    final HashMap<IBinder, ProviderRecord> mLocalProviders
+        = new HashMap<IBinder, ProviderRecord>();
+
+    final GcIdler mGcIdler = new GcIdler();
+    boolean mGcIdlerScheduled = false;
+
+    public final PackageInfo getPackageInfo(String packageName, int flags) {
+        synchronized (mPackages) {
+            WeakReference<PackageInfo> ref;
+            if ((flags&Context.CONTEXT_INCLUDE_CODE) != 0) {
+                ref = mPackages.get(packageName);
+            } else {
+                ref = mResourcePackages.get(packageName);
+            }
+            PackageInfo packageInfo = ref != null ? ref.get() : null;
+            //Log.i(TAG, "getPackageInfo " + packageName + ": " + packageInfo);
+            if (packageInfo != null && (packageInfo.mResources == null
+                    || packageInfo.mResources.getAssets().isUpToDate())) {
+                if (packageInfo.isSecurityViolation()
+                        && (flags&Context.CONTEXT_IGNORE_SECURITY) == 0) {
+                    throw new SecurityException(
+                            "Requesting code from " + packageName
+                            + " to be run in process "
+                            + mBoundApplication.processName
+                            + "/" + mBoundApplication.appInfo.uid);
+                }
+                return packageInfo;
+            }
+        }
+
+        ApplicationInfo ai = null;
+        try {
+            ai = getPackageManager().getApplicationInfo(packageName,
+                    PackageManager.GET_SHARED_LIBRARY_FILES);
+        } catch (RemoteException e) {
+        }
+
+        if (ai != null) {
+            return getPackageInfo(ai, flags);
+        }
+
+        return null;
+    }
+
+    public final PackageInfo getPackageInfo(ApplicationInfo ai, int flags) {
+        boolean includeCode = (flags&Context.CONTEXT_INCLUDE_CODE) != 0;
+        boolean securityViolation = includeCode && ai.uid != 0
+                && ai.uid != Process.SYSTEM_UID && (mBoundApplication != null
+                        ? ai.uid != mBoundApplication.appInfo.uid : true);
+        if ((flags&(Context.CONTEXT_INCLUDE_CODE
+                |Context.CONTEXT_IGNORE_SECURITY))
+                == Context.CONTEXT_INCLUDE_CODE) {
+            if (securityViolation) {
+                String msg = "Requesting code from " + ai.packageName
+                        + " (with uid " + ai.uid + ")";
+                if (mBoundApplication != null) {
+                    msg = msg + " to be run in process "
+                        + mBoundApplication.processName + " (with uid "
+                        + mBoundApplication.appInfo.uid + ")";
+                }
+                throw new SecurityException(msg);
+            }
+        }
+        return getPackageInfo(ai, null, securityViolation, includeCode);
+    }
+
+    public final PackageInfo getPackageInfoNoCheck(ApplicationInfo ai) {
+        return getPackageInfo(ai, null, false, true);
+    }
+
+    private final PackageInfo getPackageInfo(ApplicationInfo aInfo,
+            ClassLoader baseLoader, boolean securityViolation, boolean includeCode) {
+        synchronized (mPackages) {
+            WeakReference<PackageInfo> ref;
+            if (includeCode) {
+                ref = mPackages.get(aInfo.packageName);
+            } else {
+                ref = mResourcePackages.get(aInfo.packageName);
+            }
+            PackageInfo packageInfo = ref != null ? ref.get() : null;
+            if (packageInfo == null || (packageInfo.mResources != null
+                    && !packageInfo.mResources.getAssets().isUpToDate())) {
+                if (localLOGV) Log.v(TAG, (includeCode ? "Loading code package "
+                        : "Loading resource-only package ") + aInfo.packageName
+                        + " (in " + (mBoundApplication != null
+                                ? mBoundApplication.processName : null)
+                        + ")");
+                packageInfo =
+                    new PackageInfo(this, aInfo, this, baseLoader,
+                            securityViolation, includeCode &&
+                            (aInfo.flags&ApplicationInfo.FLAG_HAS_CODE) != 0);
+                if (includeCode) {
+                    mPackages.put(aInfo.packageName,
+                            new WeakReference<PackageInfo>(packageInfo));
+                } else {
+                    mResourcePackages.put(aInfo.packageName,
+                            new WeakReference<PackageInfo>(packageInfo));
+                }
+            }
+            return packageInfo;
+        }
+    }
+
+    public final boolean hasPackageInfo(String packageName) {
+        synchronized (mPackages) {
+            WeakReference<PackageInfo> ref;
+            ref = mPackages.get(packageName);
+            if (ref != null && ref.get() != null) {
+                return true;
+            }
+            ref = mResourcePackages.get(packageName);
+            if (ref != null && ref.get() != null) {
+                return true;
+            }
+            return false;
+        }
+    }
+    
+    ActivityThread() {
+    }
+
+    public ApplicationThread getApplicationThread()
+    {
+        return mAppThread;
+    }
+
+    public Instrumentation getInstrumentation()
+    {
+        return mInstrumentation;
+    }
+
+    public Configuration getConfiguration() {
+        return mConfiguration;
+    }
+
+    public boolean isProfiling() {
+        return mBoundApplication != null && mBoundApplication.profileFile != null;
+    }
+
+    public String getProfileFilePath() {
+        return mBoundApplication.profileFile;
+    }
+
+    public Looper getLooper() {
+        return mLooper;
+    }
+
+    public Application getApplication() {
+        return mInitialApplication;
+    }
+    
+    public ApplicationContext getSystemContext() {
+        synchronized (this) {
+            if (mSystemContext == null) {
+                ApplicationContext context =
+                    ApplicationContext.createSystemContext(this);
+                PackageInfo info = new PackageInfo(this, "android", context);
+                context.init(info, null, this);
+                context.getResources().updateConfiguration(
+                        getConfiguration(), getDisplayMetricsLocked(false));
+                mSystemContext = context;
+                //Log.i(TAG, "Created system resources " + context.getResources()
+                //        + ": " + context.getResources().getConfiguration());
+            }
+        }
+        return mSystemContext;
+    }
+
+    void scheduleGcIdler() {
+        if (!mGcIdlerScheduled) {
+            mGcIdlerScheduled = true;
+            Looper.myQueue().addIdleHandler(mGcIdler);
+        }
+        mH.removeMessages(H.GC_WHEN_IDLE);
+    }
+
+    void unscheduleGcIdler() {
+        if (mGcIdlerScheduled) {
+            mGcIdlerScheduled = false;
+            Looper.myQueue().removeIdleHandler(mGcIdler);
+        }
+        mH.removeMessages(H.GC_WHEN_IDLE);
+    }
+
+    void doGcIfNeeded() {
+        mGcIdlerScheduled = false;
+        final long now = SystemClock.uptimeMillis();
+        //Log.i(TAG, "**** WE MIGHT WANT TO GC: then=" + Binder.getLastGcTime()
+        //        + "m now=" + now);
+        if ((BinderInternal.getLastGcTime()+MIN_TIME_BETWEEN_GCS) < now) {
+            //Log.i(TAG, "**** WE DO, WE DO WANT TO GC!");
+            BinderInternal.forceGc("bg");
+        }
+    }
+
+    public final ActivityInfo resolveActivityInfo(Intent intent) {
+        ActivityInfo aInfo = intent.resolveActivityInfo(
+                mInitialApplication.getPackageManager(), PackageManager.GET_SHARED_LIBRARY_FILES);
+        if (aInfo == null) {
+            // Throw an exception.
+            Instrumentation.checkStartActivityResult(
+                    IActivityManager.START_CLASS_NOT_FOUND, intent);
+        }
+        return aInfo;
+    }
+    
+    public final Activity startActivityNow(Activity parent, String id,
+            Intent intent, IBinder token, Bundle state) {
+        ActivityInfo aInfo = resolveActivityInfo(intent);
+        return startActivityNow(parent, id, intent, aInfo, token, state);
+    }
+    
+    public final Activity startActivityNow(Activity parent, String id,
+            Intent intent, ActivityInfo activityInfo, IBinder token, Bundle state) {
+        return startActivityNow(parent, id, intent, activityInfo, token, state, null);
+    }
+
+    public final Activity startActivityNow(Activity parent, String id,
+        Intent intent, ActivityInfo activityInfo, IBinder token, Bundle state,
+        Object lastNonConfigurationInstance) {
+        ActivityRecord r = new ActivityRecord();
+            r.token = token;
+            r.intent = intent;
+            r.state = state;
+            r.parent = parent;
+            r.embeddedID = id;
+            r.activityInfo = activityInfo;
+            r.lastNonConfigurationInstance = lastNonConfigurationInstance;
+        if (localLOGV) {
+            ComponentName compname = intent.getComponent();
+            String name;
+            if (compname != null) {
+                name = compname.toShortString();
+            } else {
+                name = "(Intent " + intent + ").getComponent() returned null";
+            }
+            Log.v(TAG, "Performing launch: action=" + intent.getAction()
+                    + ", comp=" + name
+                    + ", token=" + token);
+        }
+        return performLaunchActivity(r);
+    }
+
+    public final Activity getActivity(IBinder token) {
+        return mActivities.get(token).activity;
+    }
+
+    public final void sendActivityResult(
+            IBinder token, String id, int requestCode,
+            int resultCode, Intent data) {
+        ArrayList<ResultInfo> list = new ArrayList<ResultInfo>();
+        list.add(new ResultInfo(id, requestCode, resultCode, data));
+        mAppThread.scheduleSendResult(token, list);
+    }
+
+    // if the thread hasn't started yet, we don't have the handler, so just
+    // save the messages until we're ready.
+    private final void queueOrSendMessage(int what, Object obj) {
+        queueOrSendMessage(what, obj, 0, 0);
+    }
+
+    private final void queueOrSendMessage(int what, Object obj, int arg1) {
+        queueOrSendMessage(what, obj, arg1, 0);
+    }
+
+    private final void queueOrSendMessage(int what, Object obj, int arg1, int arg2) {
+        synchronized (this) {
+            if (localLOGV) Log.v(
+                TAG, "SCHEDULE " + what + " " + mH.codeToString(what)
+                + ": " + arg1 + " / " + obj);
+            Message msg = Message.obtain();
+            msg.what = what;
+            msg.obj = obj;
+            msg.arg1 = arg1;
+            msg.arg2 = arg2;
+            mH.sendMessage(msg);
+        }
+    }
+
+    final void scheduleContextCleanup(ApplicationContext context, String who,
+            String what) {
+        ContextCleanupInfo cci = new ContextCleanupInfo();
+        cci.context = context;
+        cci.who = who;
+        cci.what = what;
+        queueOrSendMessage(H.CLEAN_UP_CONTEXT, cci);
+    }
+
+    private final Activity performLaunchActivity(ActivityRecord r) {
+        // System.out.println("##### [" + System.currentTimeMillis() + "] ActivityThread.performLaunchActivity(" + r + ")");
+
+        ActivityInfo aInfo = r.activityInfo;
+        if (r.packageInfo == null) {
+            r.packageInfo = getPackageInfo(aInfo.applicationInfo,
+                    Context.CONTEXT_INCLUDE_CODE);
+        }
+            
+        ComponentName component = r.intent.getComponent();
+        if (component == null) {
+            component = r.intent.resolveActivity(
+                mInitialApplication.getPackageManager());
+            r.intent.setComponent(component);
+        }
+
+        if (r.activityInfo.targetActivity != null) {
+            component = new ComponentName(r.activityInfo.packageName,
+                    r.activityInfo.targetActivity);
+        }
+
+        Activity activity = null;
+        try {
+            java.lang.ClassLoader cl = r.packageInfo.getClassLoader();
+            activity = mInstrumentation.newActivity(
+                    cl, component.getClassName(), r.intent);
+            r.intent.setExtrasClassLoader(cl);
+            if (r.state != null) {
+                r.state.setClassLoader(cl);
+            }
+        } catch (Exception e) {
+            if (!mInstrumentation.onException(activity, e)) {
+                throw new RuntimeException(
+                    "Unable to instantiate activity " + component
+                    + ": " + e.toString(), e);
+            }
+        }
+
+        try {
+            Application app = r.packageInfo.makeApplication();
+            
+            if (localLOGV) Log.v(TAG, "Performing launch of " + r);
+            if (localLOGV) Log.v(
+                    TAG, r + ": app=" + app
+                    + ", appName=" + app.getPackageName()
+                    + ", pkg=" + r.packageInfo.getPackageName()
+                    + ", comp=" + r.intent.getComponent().toShortString()
+                    + ", dir=" + r.packageInfo.getAppDir());
+
+            if (activity != null) {
+                ApplicationContext appContext = new ApplicationContext();
+                appContext.init(r.packageInfo, r.token, this);
+                appContext.setOuterContext(activity);
+                CharSequence title = r.activityInfo.loadLabel(appContext.getPackageManager());
+                Configuration config = new Configuration(mConfiguration);
+                activity.attach(appContext, this, getInstrumentation(), r.token, app, 
+                        r.intent, r.activityInfo, title, r.parent, r.embeddedID,
+                        r.lastNonConfigurationInstance, r.lastNonConfigurationChildInstances,
+                        config);
+                
+                r.lastNonConfigurationInstance = null;
+                r.lastNonConfigurationChildInstances = null;
+                activity.mStartedActivity = false;
+                int theme = r.activityInfo.getThemeResource();
+                if (theme != 0) {
+                    activity.setTheme(theme);
+                }
+
+                activity.mCalled = false;
+                mInstrumentation.callActivityOnCreate(activity, r.state);
+                if (!activity.mCalled) {
+                    throw new SuperNotCalledException(
+                        "Activity " + r.intent.getComponent().toShortString() +
+                        " did not call through to super.onCreate()");
+                }
+                r.activity = activity;
+                r.stopped = true;
+                if (!r.activity.mFinished) {
+                    activity.performStart();
+                    r.stopped = false;
+                }
+                if (!r.activity.mFinished) {
+                    if (r.state != null) {
+                        mInstrumentation.callActivityOnRestoreInstanceState(activity, r.state);
+                    }
+                }
+                if (!r.activity.mFinished) {
+                    activity.mCalled = false;
+                    mInstrumentation.callActivityOnPostCreate(activity, r.state);
+                    if (!activity.mCalled) {
+                        throw new SuperNotCalledException(
+                            "Activity " + r.intent.getComponent().toShortString() +
+                            " did not call through to super.onPostCreate()");
+                    }
+                }
+                r.state = null;
+            }
+            r.paused = true;
+
+            mActivities.put(r.token, r);
+
+        } catch (SuperNotCalledException e) {
+            throw e;
+
+        } catch (Exception e) {
+            if (!mInstrumentation.onException(activity, e)) {
+                throw new RuntimeException(
+                    "Unable to start activity " + component
+                    + ": " + e.toString(), e);
+            }
+        }
+
+        return activity;
+    }
+
+    private final void handleLaunchActivity(ActivityRecord r) {
+        // If we are getting ready to gc after going to the background, well
+        // we are back active so skip it.
+        unscheduleGcIdler();
+
+        if (localLOGV) Log.v(
+            TAG, "Handling launch of " + r);
+        Activity a = performLaunchActivity(r);
+
+        if (a != null) {
+            handleResumeActivity(r.token, false, r.isForward);
+
+            if (!r.activity.mFinished && r.startsNotResumed) {
+                // The activity manager actually wants this one to start out
+                // paused, because it needs to be visible but isn't in the
+                // foreground.  We accomplish this by going through the
+                // normal startup (because activities expect to go through
+                // onResume() the first time they run, before their window
+                // is displayed), and then pausing it.  However, in this case
+                // we do -not- need to do the full pause cycle (of freezing
+                // and such) because the activity manager assumes it can just
+                // retain the current state it has.
+                try {
+                    r.activity.mCalled = false;
+                    mInstrumentation.callActivityOnPause(r.activity);
+                    if (!r.activity.mCalled) {
+                        throw new SuperNotCalledException(
+                            "Activity " + r.intent.getComponent().toShortString() +
+                            " did not call through to super.onPause()");
+                    }
+
+                } catch (SuperNotCalledException e) {
+                    throw e;
+
+                } catch (Exception e) {
+                    if (!mInstrumentation.onException(r.activity, e)) {
+                        throw new RuntimeException(
+                                "Unable to pause activity "
+                                + r.intent.getComponent().toShortString()
+                                + ": " + e.toString(), e);
+                    }
+                }
+                r.paused = true;
+            }
+        } else {
+            // If there was an error, for any reason, tell the activity
+            // manager to stop us.
+            try {
+                ActivityManagerNative.getDefault()
+                    .finishActivity(r.token, Activity.RESULT_CANCELED, null);
+            } catch (RemoteException ex) {
+            }
+        }
+    }
+
+    private final void deliverNewIntents(ActivityRecord r,
+            List<Intent> intents) {
+        final int N = intents.size();
+        for (int i=0; i<N; i++) {
+            Intent intent = intents.get(i);
+            intent.setExtrasClassLoader(r.activity.getClassLoader());
+            mInstrumentation.callActivityOnNewIntent(r.activity, intent);
+        }
+    }
+
+    public final void performNewIntents(IBinder token,
+            List<Intent> intents) {
+        ActivityRecord r = mActivities.get(token);
+        if (r != null) {
+            final boolean resumed = !r.paused;
+            if (resumed) {
+                mInstrumentation.callActivityOnPause(r.activity);
+            }
+            deliverNewIntents(r, intents);
+            if (resumed) {
+                mInstrumentation.callActivityOnResume(r.activity);
+            }
+        }
+    }
+    
+    private final void handleNewIntent(NewIntentData data) {
+        performNewIntents(data.token, data.intents);
+    }
+
+    private final void handleReceiver(ReceiverData data) {
+        // If we are getting ready to gc after going to the background, well
+        // we are back active so skip it.
+        unscheduleGcIdler();
+
+        String component = data.intent.getComponent().getClassName();
+
+        PackageInfo packageInfo = getPackageInfoNoCheck(
+                data.info.applicationInfo);
+
+        IActivityManager mgr = ActivityManagerNative.getDefault();
+
+        BroadcastReceiver receiver = null;
+        try {
+            java.lang.ClassLoader cl = packageInfo.getClassLoader();
+            data.intent.setExtrasClassLoader(cl);
+            if (data.resultExtras != null) {
+                data.resultExtras.setClassLoader(cl);
+            }
+            receiver = (BroadcastReceiver)cl.loadClass(component).newInstance();
+        } catch (Exception e) {
+            try {
+                mgr.finishReceiver(mAppThread.asBinder(), data.resultCode,
+                                   data.resultData, data.resultExtras, data.resultAbort);
+            } catch (RemoteException ex) {
+            }
+            throw new RuntimeException(
+                "Unable to instantiate receiver " + component
+                + ": " + e.toString(), e);
+        }
+
+        try {
+            Application app = packageInfo.makeApplication();
+            
+            if (localLOGV) Log.v(
+                TAG, "Performing receive of " + data.intent
+                + ": app=" + app
+                + ", appName=" + app.getPackageName()
+                + ", pkg=" + packageInfo.getPackageName()
+                + ", comp=" + data.intent.getComponent().toShortString()
+                + ", dir=" + packageInfo.getAppDir());
+
+            ApplicationContext context = (ApplicationContext)app.getBaseContext();
+            receiver.setOrderedHint(true);
+            receiver.setResult(data.resultCode, data.resultData,
+                data.resultExtras);
+            receiver.setOrderedHint(data.sync);
+            receiver.onReceive(context.getReceiverRestrictedContext(),
+                    data.intent);
+        } catch (Exception e) {
+            try {
+                mgr.finishReceiver(mAppThread.asBinder(), data.resultCode,
+                    data.resultData, data.resultExtras, data.resultAbort);
+            } catch (RemoteException ex) {
+            }
+            if (!mInstrumentation.onException(receiver, e)) {
+                throw new RuntimeException(
+                    "Unable to start receiver " + component
+                    + ": " + e.toString(), e);
+            }
+        }
+
+        try {
+            if (data.sync) {
+                mgr.finishReceiver(
+                    mAppThread.asBinder(), receiver.getResultCode(),
+                    receiver.getResultData(), receiver.getResultExtras(false),
+                        receiver.getAbortBroadcast());
+            } else {
+                mgr.finishReceiver(mAppThread.asBinder(), 0, null, null, false);
+            }
+        } catch (RemoteException ex) {
+        }
+    }
+
+    private final void handleCreateService(CreateServiceData data) {
+        // If we are getting ready to gc after going to the background, well
+        // we are back active so skip it.
+        unscheduleGcIdler();
+
+        PackageInfo packageInfo = getPackageInfoNoCheck(
+                data.info.applicationInfo);
+        Service service = null;
+        try {
+            java.lang.ClassLoader cl = packageInfo.getClassLoader();
+            service = (Service) cl.loadClass(data.info.name).newInstance();
+        } catch (Exception e) {
+            if (!mInstrumentation.onException(service, e)) {
+                throw new RuntimeException(
+                    "Unable to instantiate service " + data.info.name
+                    + ": " + e.toString(), e);
+            }
+        }
+
+        try {
+            if (localLOGV) Log.v(TAG, "Creating service " + data.info.name);
+
+            ApplicationContext context = new ApplicationContext();
+            context.init(packageInfo, null, this);
+
+            Application app = packageInfo.makeApplication();
+            context.setOuterContext(service);
+            service.attach(context, this, data.info.name, data.token, app,
+                    ActivityManagerNative.getDefault());
+            service.onCreate();
+            mServices.put(data.token, service);
+            try {
+                ActivityManagerNative.getDefault().serviceDoneExecuting(data.token);
+            } catch (RemoteException e) {
+                // nothing to do.
+            }
+        } catch (Exception e) {
+            if (!mInstrumentation.onException(service, e)) {
+                throw new RuntimeException(
+                    "Unable to create service " + data.info.name
+                    + ": " + e.toString(), e);
+            }
+        }
+    }
+
+    private final void handleBindService(BindServiceData data) {
+        Service s = mServices.get(data.token);
+        if (s != null) {
+            try {
+                data.intent.setExtrasClassLoader(s.getClassLoader());
+                try {
+                    if (!data.rebind) {
+                        IBinder binder = s.onBind(data.intent);
+                        ActivityManagerNative.getDefault().publishService(
+                                data.token, data.intent, binder);
+                    } else {
+                        s.onRebind(data.intent);
+                        ActivityManagerNative.getDefault().serviceDoneExecuting(
+                                data.token);
+                    }
+                } catch (RemoteException ex) {
+                }
+            } catch (Exception e) {
+                if (!mInstrumentation.onException(s, e)) {
+                    throw new RuntimeException(
+                            "Unable to bind to service " + s
+                            + " with " + data.intent + ": " + e.toString(), e);
+                }
+            }
+        }
+    }
+
+    private final void handleUnbindService(BindServiceData data) {
+        Service s = mServices.get(data.token);
+        if (s != null) {
+            try {
+                data.intent.setExtrasClassLoader(s.getClassLoader());
+                boolean doRebind = s.onUnbind(data.intent);
+                try {
+                    if (doRebind) {
+                        ActivityManagerNative.getDefault().unbindFinished(
+                                data.token, data.intent, doRebind);
+                    } else {
+                        ActivityManagerNative.getDefault().serviceDoneExecuting(
+                                data.token);
+                    }
+                } catch (RemoteException ex) {
+                }
+            } catch (Exception e) {
+                if (!mInstrumentation.onException(s, e)) {
+                    throw new RuntimeException(
+                            "Unable to unbind to service " + s
+                            + " with " + data.intent + ": " + e.toString(), e);
+                }
+            }
+        }
+    }
+
+    private void handleDumpService(DumpServiceInfo info) {
+        try {
+            Service s = mServices.get(info.service);
+            if (s != null) {
+                PrintWriter pw = new PrintWriter(new FileOutputStream(info.fd));
+                s.dump(info.fd, pw, info.args);
+                pw.close();
+            }
+        } finally {
+            synchronized (info) {
+                info.dumped = true;
+                info.notifyAll();
+            }
+        }
+    }
+
+    private final void handleServiceArgs(ServiceArgsData data) {
+        Service s = mServices.get(data.token);
+        if (s != null) {
+            try {
+                if (data.args != null) {
+                    data.args.setExtrasClassLoader(s.getClassLoader());
+                }
+                s.onStart(data.args, data.startId);
+                try {
+                    ActivityManagerNative.getDefault().serviceDoneExecuting(data.token);
+                } catch (RemoteException e) {
+                    // nothing to do.
+                }
+            } catch (Exception e) {
+                if (!mInstrumentation.onException(s, e)) {
+                    throw new RuntimeException(
+                            "Unable to start service " + s
+                            + " with " + data.args + ": " + e.toString(), e);
+                }
+            }
+        }
+    }
+
+    private final void handleStopService(IBinder token) {
+        Service s = mServices.remove(token);
+        if (s != null) {
+            try {
+                if (localLOGV) Log.v(TAG, "Destroying service " + s);
+                s.onDestroy();
+                Context context = s.getBaseContext();
+                if (context instanceof ApplicationContext) {
+                    final String who = s.getClassName();
+                    ((ApplicationContext) context).scheduleFinalCleanup(who, "Service");
+                }
+                try {
+                    ActivityManagerNative.getDefault().serviceDoneExecuting(token);
+                } catch (RemoteException e) {
+                    // nothing to do.
+                }
+            } catch (Exception e) {
+                if (!mInstrumentation.onException(s, e)) {
+                    throw new RuntimeException(
+                            "Unable to stop service " + s
+                            + ": " + e.toString(), e);
+                }
+            }
+        }
+        //Log.i(TAG, "Running services: " + mServices);
+    }
+
+    public final ActivityRecord performResumeActivity(IBinder token,
+            boolean clearHide) {
+        ActivityRecord r = mActivities.get(token);
+        if (localLOGV) Log.v(TAG, "Performing resume of " + r
+                + " finished=" + r.activity.mFinished);
+        if (r != null && !r.activity.mFinished) {
+            if (clearHide) {
+                r.hideForNow = false;
+                r.activity.mStartedActivity = false;
+            }
+            try {
+                if (r.pendingIntents != null) {
+                    deliverNewIntents(r, r.pendingIntents);
+                    r.pendingIntents = null;
+                }
+                if (r.pendingResults != null) {
+                    deliverResults(r, r.pendingResults);
+                    r.pendingResults = null;
+                }
+                r.activity.performResume();
+
+                EventLog.writeEvent(LOG_ON_RESUME_CALLED, 
+                        r.activity.getComponentName().getClassName());
+                
+                r.paused = false;
+                r.stopped = false;
+                if (r.activity.mStartedActivity) {
+                    r.hideForNow = true;
+                }
+                r.state = null;
+            } catch (Exception e) {
+                if (!mInstrumentation.onException(r.activity, e)) {
+                    throw new RuntimeException(
+                        "Unable to resume activity "
+                        + r.intent.getComponent().toShortString()
+                        + ": " + e.toString(), e);
+                }
+            }
+        }
+        return r;
+    }
+
+    final void handleResumeActivity(IBinder token, boolean clearHide, boolean isForward) {
+        // If we are getting ready to gc after going to the background, well
+        // we are back active so skip it.
+        unscheduleGcIdler();
+
+        ActivityRecord r = performResumeActivity(token, clearHide);
+
+        if (r != null) {
+            final Activity a = r.activity;
+
+            if (localLOGV) Log.v(
+                TAG, "Resume " + r + " started activity: " +
+                a.mStartedActivity + ", hideForNow: " + r.hideForNow
+                + ", finished: " + a.mFinished);
+
+            final int forwardBit = isForward ?
+                    WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION : 0;
+            
+            // If the window hasn't yet been added to the window manager,
+            // and this guy didn't finish itself or start another activity,
+            // then go ahead and add the window.
+            if (r.window == null && !a.mFinished && !a.mStartedActivity) {
+                r.window = r.activity.getWindow();
+                View decor = r.window.getDecorView();
+                decor.setVisibility(View.INVISIBLE);
+                ViewManager wm = a.getWindowManager();
+                WindowManager.LayoutParams l = r.window.getAttributes();
+                a.mDecor = decor;
+                l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
+                l.softInputMode |= forwardBit;
+                if (a.mVisibleFromClient) {
+                    a.mWindowAdded = true;
+                    wm.addView(decor, l);
+                }
+
+            // If the window has already been added, but during resume
+            // we started another activity, then don't yet make the
+            // window visisble.
+            } else if (a.mStartedActivity) {
+                if (localLOGV) Log.v(
+                    TAG, "Launch " + r + " mStartedActivity set");
+                r.hideForNow = true;
+            }
+
+            // The window is now visible if it has been added, we are not
+            // simply finishing, and we are not starting another activity.
+            if (!r.activity.mFinished && r.activity.mDecor != null
+                    && !r.hideForNow) {
+                if (r.newConfig != null) {
+                    performConfigurationChanged(r.activity, r.newConfig);
+                    r.newConfig = null;
+                }
+                if (localLOGV) Log.v(TAG, "Resuming " + r + " with isForward="
+                        + isForward);
+                WindowManager.LayoutParams l = r.window.getAttributes();
+                if ((l.softInputMode
+                        & WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION)
+                        != forwardBit) {
+                    l.softInputMode = (l.softInputMode
+                            & (~WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION))
+                            | forwardBit;
+                    ViewManager wm = a.getWindowManager();
+                    View decor = r.window.getDecorView();
+                    wm.updateViewLayout(decor, l);
+                }
+                r.activity.mVisibleFromServer = true;
+                mNumVisibleActivities++;
+                if (r.activity.mVisibleFromClient) {
+                    r.activity.makeVisible();
+                }
+            }
+
+            r.nextIdle = mNewActivities;
+            mNewActivities = r;
+            if (localLOGV) Log.v(
+                TAG, "Scheduling idle handler for " + r);
+            Looper.myQueue().addIdleHandler(new Idler());
+
+        } else {
+            // If an exception was thrown when trying to resume, then
+            // just end this activity.
+            try {
+                ActivityManagerNative.getDefault()
+                    .finishActivity(token, Activity.RESULT_CANCELED, null);
+            } catch (RemoteException ex) {
+            }
+        }
+    }
+
+    private int mThumbnailWidth = -1;
+    private int mThumbnailHeight = -1;
+
+    private final Bitmap createThumbnailBitmap(ActivityRecord r) {
+        Bitmap thumbnail = null;
+        try {
+            int w = mThumbnailWidth;
+            int h;
+            if (w < 0) {
+                Resources res = r.activity.getResources();
+                mThumbnailHeight = h =
+                    res.getDimensionPixelSize(com.android.internal.R.dimen.thumbnail_height);
+
+                mThumbnailWidth = w =
+                    res.getDimensionPixelSize(com.android.internal.R.dimen.thumbnail_width);
+            } else {
+                h = mThumbnailHeight;
+            }
+
+            // XXX Only set hasAlpha if needed?
+            thumbnail = Bitmap.createBitmap(w, h, Bitmap.Config.RGB_565);
+            thumbnail.eraseColor(0);
+            Canvas cv = new Canvas(thumbnail);
+            if (!r.activity.onCreateThumbnail(thumbnail, cv)) {
+                thumbnail = null;
+            }
+        } catch (Exception e) {
+            if (!mInstrumentation.onException(r.activity, e)) {
+                throw new RuntimeException(
+                        "Unable to create thumbnail of "
+                        + r.intent.getComponent().toShortString()
+                        + ": " + e.toString(), e);
+            }
+            thumbnail = null;
+        }
+
+        return thumbnail;
+    }
+
+    private final void handlePauseActivity(IBinder token, boolean finished,
+            boolean userLeaving, int configChanges) {
+        ActivityRecord r = mActivities.get(token);
+        if (r != null) {
+            //Log.v(TAG, "userLeaving=" + userLeaving + " handling pause of " + r);
+            if (userLeaving) {
+                performUserLeavingActivity(r);
+            }
+            
+            r.activity.mConfigChangeFlags |= configChanges;
+            Bundle state = performPauseActivity(token, finished, true);
+
+            // Tell the activity manager we have paused.
+            try {
+                ActivityManagerNative.getDefault().activityPaused(token, state);
+            } catch (RemoteException ex) {
+            }
+        }
+    }
+
+    final void performUserLeavingActivity(ActivityRecord r) {
+        mInstrumentation.callActivityOnUserLeaving(r.activity);
+    }
+
+    final Bundle performPauseActivity(IBinder token, boolean finished,
+            boolean saveState) {
+        ActivityRecord r = mActivities.get(token);
+        return r != null ? performPauseActivity(r, finished, saveState) : null;
+    }
+
+    final Bundle performPauseActivity(ActivityRecord r, boolean finished,
+            boolean saveState) {
+        if (r.paused) {
+            if (r.activity.mFinished) {
+                // If we are finishing, we won't call onResume() in certain cases.
+                // So here we likewise don't want to call onPause() if the activity
+                // isn't resumed.
+                return null;
+            }
+            RuntimeException e = new RuntimeException(
+                    "Performing pause of activity that is not resumed: "
+                    + r.intent.getComponent().toShortString());
+            Log.e(TAG, e.getMessage(), e);
+        }
+        Bundle state = null;
+        if (finished) {
+            r.activity.mFinished = true;
+        }
+        try {
+            // Next have the activity save its current state and managed dialogs...
+            if (!r.activity.mFinished && saveState) {
+                state = new Bundle();
+                mInstrumentation.callActivityOnSaveInstanceState(r.activity, state);
+                r.state = state;
+            }
+            // Now we are idle.
+            r.activity.mCalled = false;
+            mInstrumentation.callActivityOnPause(r.activity);
+            EventLog.writeEvent(LOG_ON_PAUSE_CALLED, r.activity.getComponentName().getClassName());
+            if (!r.activity.mCalled) {
+                throw new SuperNotCalledException(
+                    "Activity " + r.intent.getComponent().toShortString() +
+                    " did not call through to super.onPause()");
+            }
+
+        } catch (SuperNotCalledException e) {
+            throw e;
+
+        } catch (Exception e) {
+            if (!mInstrumentation.onException(r.activity, e)) {
+                throw new RuntimeException(
+                        "Unable to pause activity "
+                        + r.intent.getComponent().toShortString()
+                        + ": " + e.toString(), e);
+            }
+        }
+        r.paused = true;
+        return state;
+    }
+
+    final void performStopActivity(IBinder token) {
+        ActivityRecord r = mActivities.get(token);
+        performStopActivityInner(r, null, false);
+    }
+
+    private static class StopInfo {
+        Bitmap thumbnail;
+        CharSequence description;
+    }
+
+    private final class ProviderRefCount {
+        public int count;
+        ProviderRefCount(int pCount) {
+            count = pCount;
+        }
+    }
+
+    private final void performStopActivityInner(ActivityRecord r,
+            StopInfo info, boolean keepShown) {
+        if (localLOGV) Log.v(TAG, "Performing stop of " + r);
+        if (r != null) {
+            if (!keepShown && r.stopped) {
+                if (r.activity.mFinished) {
+                    // If we are finishing, we won't call onResume() in certain
+                    // cases.  So here we likewise don't want to call onStop()
+                    // if the activity isn't resumed.
+                    return;
+                }
+                RuntimeException e = new RuntimeException(
+                        "Performing stop of activity that is not resumed: "
+                        + r.intent.getComponent().toShortString());
+                Log.e(TAG, e.getMessage(), e);
+            }
+
+            if (info != null) {
+                try {
+                    // First create a thumbnail for the activity...
+                    //info.thumbnail = createThumbnailBitmap(r);
+                    info.description = r.activity.onCreateDescription();
+                } catch (Exception e) {
+                    if (!mInstrumentation.onException(r.activity, e)) {
+                        throw new RuntimeException(
+                                "Unable to save state of activity "
+                                + r.intent.getComponent().toShortString()
+                                + ": " + e.toString(), e);
+                    }
+                }
+            }
+
+            if (!keepShown) {
+                try {
+                    // Now we are idle.
+                    r.activity.performStop();
+                } catch (Exception e) {
+                    if (!mInstrumentation.onException(r.activity, e)) {
+                        throw new RuntimeException(
+                                "Unable to stop activity "
+                                + r.intent.getComponent().toShortString()
+                                + ": " + e.toString(), e);
+                    }
+                }
+                r.stopped = true;
+            }
+
+            r.paused = true;
+        }
+    }
+
+    private final void updateVisibility(ActivityRecord r, boolean show) {
+        View v = r.activity.mDecor;
+        if (v != null) {
+            if (show) {
+                if (!r.activity.mVisibleFromServer) {
+                    r.activity.mVisibleFromServer = true;
+                    mNumVisibleActivities++;
+                    if (r.activity.mVisibleFromClient) {
+                        r.activity.makeVisible();
+                    }
+                }
+                if (r.newConfig != null) {
+                    performConfigurationChanged(r.activity, r.newConfig);
+                    r.newConfig = null;
+                }
+            } else {
+                if (r.activity.mVisibleFromServer) {
+                    r.activity.mVisibleFromServer = false;
+                    mNumVisibleActivities--;
+                    v.setVisibility(View.INVISIBLE);
+                }
+            }
+        }
+    }
+
+    private final void handleStopActivity(IBinder token, boolean show, int configChanges) {
+        ActivityRecord r = mActivities.get(token);
+        r.activity.mConfigChangeFlags |= configChanges;
+
+        StopInfo info = new StopInfo();
+        performStopActivityInner(r, info, show);
+
+        if (localLOGV) Log.v(
+            TAG, "Finishing stop of " + r + ": show=" + show
+            + " win=" + r.window);
+
+        updateVisibility(r, show);
+        
+        // Tell activity manager we have been stopped.
+        try {
+            ActivityManagerNative.getDefault().activityStopped(
+                r.token, info.thumbnail, info.description);
+        } catch (RemoteException ex) {
+        }
+    }
+
+    final void performRestartActivity(IBinder token) {
+        ActivityRecord r = mActivities.get(token);
+        if (r.stopped) {
+            r.activity.performRestart();
+            r.stopped = false;
+        }
+    }
+
+    private final void handleWindowVisibility(IBinder token, boolean show) {
+        ActivityRecord r = mActivities.get(token);
+        if (!show && !r.stopped) {
+            performStopActivityInner(r, null, show);
+        } else if (show && r.stopped) {
+            // If we are getting ready to gc after going to the background, well
+            // we are back active so skip it.
+            unscheduleGcIdler();
+
+            r.activity.performRestart();
+            r.stopped = false;
+        }
+        if (r.activity.mDecor != null) {
+            if (Config.LOGV) Log.v(
+                TAG, "Handle window " + r + " visibility: " + show);
+            updateVisibility(r, show);
+        }
+    }
+
+    private final void deliverResults(ActivityRecord r, List<ResultInfo> results) {
+        final int N = results.size();
+        for (int i=0; i<N; i++) {
+            ResultInfo ri = results.get(i);
+            try {
+                if (ri.mData != null) {
+                    ri.mData.setExtrasClassLoader(r.activity.getClassLoader());
+                }
+                r.activity.dispatchActivityResult(ri.mResultWho,
+                        ri.mRequestCode, ri.mResultCode, ri.mData);
+            } catch (Exception e) {
+                if (!mInstrumentation.onException(r.activity, e)) {
+                    throw new RuntimeException(
+                            "Failure delivering result " + ri + " to activity "
+                            + r.intent.getComponent().toShortString()
+                            + ": " + e.toString(), e);
+                }
+            }
+        }
+    }
+
+    private final void handleSendResult(ResultData res) {
+        ActivityRecord r = mActivities.get(res.token);
+        if (localLOGV) Log.v(TAG, "Handling send result to " + r);
+        if (r != null) {
+            final boolean resumed = !r.paused;
+            if (!r.activity.mFinished && r.activity.mDecor != null
+                    && r.hideForNow && resumed) {
+                // We had hidden the activity because it started another
+                // one...  we have gotten a result back and we are not
+                // paused, so make sure our window is visible.
+                updateVisibility(r, true);
+            }
+            if (resumed) {
+                try {
+                    // Now we are idle.
+                    r.activity.mCalled = false;
+                    mInstrumentation.callActivityOnPause(r.activity);
+                    if (!r.activity.mCalled) {
+                        throw new SuperNotCalledException(
+                            "Activity " + r.intent.getComponent().toShortString()
+                            + " did not call through to super.onPause()");
+                    }
+                } catch (SuperNotCalledException e) {
+                    throw e;
+                } catch (Exception e) {
+                    if (!mInstrumentation.onException(r.activity, e)) {
+                        throw new RuntimeException(
+                                "Unable to pause activity "
+                                + r.intent.getComponent().toShortString()
+                                + ": " + e.toString(), e);
+                    }
+                }
+            }
+            deliverResults(r, res.results);
+            if (resumed) {
+                mInstrumentation.callActivityOnResume(r.activity);
+            }
+        }
+    }
+
+    public final ActivityRecord performDestroyActivity(IBinder token, boolean finishing) {
+        return performDestroyActivity(token, finishing, 0, false);
+    }
+
+    private final ActivityRecord performDestroyActivity(IBinder token, boolean finishing,
+            int configChanges, boolean getNonConfigInstance) {
+        ActivityRecord r = mActivities.get(token);
+        if (localLOGV) Log.v(TAG, "Performing finish of " + r);
+        if (r != null) {
+            r.activity.mConfigChangeFlags |= configChanges;
+            if (finishing) {
+                r.activity.mFinished = true;
+            }
+            if (!r.paused) {
+                try {
+                    r.activity.mCalled = false;
+                    mInstrumentation.callActivityOnPause(r.activity);
+                    EventLog.writeEvent(LOG_ON_PAUSE_CALLED, 
+                            r.activity.getComponentName().getClassName());
+                    if (!r.activity.mCalled) {
+                        throw new SuperNotCalledException(
+                            "Activity " + r.intent.getComponent().toShortString()
+                            + " did not call through to super.onPause()");
+                    }
+                } catch (SuperNotCalledException e) {
+                    throw e;
+                } catch (Exception e) {
+                    if (!mInstrumentation.onException(r.activity, e)) {
+                        throw new RuntimeException(
+                                "Unable to pause activity "
+                                + r.intent.getComponent().toShortString()
+                                + ": " + e.toString(), e);
+                    }
+                }
+                r.paused = true;
+            }
+            if (!r.stopped) {
+                try {
+                    r.activity.performStop();
+                } catch (SuperNotCalledException e) {
+                    throw e;
+                } catch (Exception e) {
+                    if (!mInstrumentation.onException(r.activity, e)) {
+                        throw new RuntimeException(
+                                "Unable to stop activity "
+                                + r.intent.getComponent().toShortString()
+                                + ": " + e.toString(), e);
+                    }
+                }
+                r.stopped = true;
+            }
+            if (getNonConfigInstance) {
+                try {
+                    r.lastNonConfigurationInstance
+                            = r.activity.onRetainNonConfigurationInstance();
+                } catch (Exception e) {
+                    if (!mInstrumentation.onException(r.activity, e)) {
+                        throw new RuntimeException(
+                                "Unable to retain activity "
+                                + r.intent.getComponent().toShortString()
+                                + ": " + e.toString(), e);
+                    }
+                }
+                try {
+                    r.lastNonConfigurationChildInstances
+                            = r.activity.onRetainNonConfigurationChildInstances();
+                } catch (Exception e) {
+                    if (!mInstrumentation.onException(r.activity, e)) {
+                        throw new RuntimeException(
+                                "Unable to retain child activities "
+                                + r.intent.getComponent().toShortString()
+                                + ": " + e.toString(), e);
+                    }
+                }
+                
+            }
+            try {
+                r.activity.mCalled = false;
+                r.activity.onDestroy();
+                if (!r.activity.mCalled) {
+                    throw new SuperNotCalledException(
+                        "Activity " + r.intent.getComponent().toShortString() +
+                        " did not call through to super.onDestroy()");
+                }
+                if (r.window != null) {
+                    r.window.closeAllPanels();
+                }
+            } catch (SuperNotCalledException e) {
+                throw e;
+            } catch (Exception e) {
+                if (!mInstrumentation.onException(r.activity, e)) {
+                    throw new RuntimeException(
+                            "Unable to destroy activity "
+                            + r.intent.getComponent().toShortString()
+                            + ": " + e.toString(), e);
+                }
+            }
+        }
+        mActivities.remove(token);
+
+        return r;
+    }
+
+    private final void handleDestroyActivity(IBinder token, boolean finishing,
+            int configChanges, boolean getNonConfigInstance) {
+        ActivityRecord r = performDestroyActivity(token, finishing,
+                configChanges, getNonConfigInstance);
+        if (r != null) {
+            WindowManager wm = r.activity.getWindowManager();
+            View v = r.activity.mDecor;
+            if (v != null) {
+                if (r.activity.mVisibleFromServer) {
+                    mNumVisibleActivities--;
+                }
+                IBinder wtoken = v.getWindowToken();
+                if (r.activity.mWindowAdded) {
+                    wm.removeViewImmediate(v);
+                }
+                if (wtoken != null) {
+                    WindowManagerImpl.getDefault().closeAll(wtoken,
+                            r.activity.getClass().getName(), "Activity");
+                }
+                r.activity.mDecor = null;
+            }
+            WindowManagerImpl.getDefault().closeAll(token,
+                    r.activity.getClass().getName(), "Activity");
+
+            // Mocked out contexts won't be participating in the normal
+            // process lifecycle, but if we're running with a proper
+            // ApplicationContext we need to have it tear down things
+            // cleanly.
+            Context c = r.activity.getBaseContext();
+            if (c instanceof ApplicationContext) {
+                ((ApplicationContext) c).scheduleFinalCleanup(
+                        r.activity.getClass().getName(), "Activity");
+            }
+        }
+        if (finishing) {
+            try {
+                ActivityManagerNative.getDefault().activityDestroyed(token);
+            } catch (RemoteException ex) {
+                // If the system process has died, it's game over for everyone.
+            }
+        }
+    }
+
+    private final void handleRelaunchActivity(ActivityRecord tmp, int configChanges) {
+        // If we are getting ready to gc after going to the background, well
+        // we are back active so skip it.
+        unscheduleGcIdler();
+
+        Configuration changedConfig = null;
+        
+        // First: make sure we have the most recent configuration and most
+        // recent version of the activity, or skip it if some previous call
+        // had taken a more recent version.
+        synchronized (mRelaunchingActivities) {
+            int N = mRelaunchingActivities.size();
+            IBinder token = tmp.token;
+            tmp = null;
+            for (int i=0; i<N; i++) {
+                ActivityRecord r = mRelaunchingActivities.get(i);
+                if (r.token == token) {
+                    tmp = r;
+                    mRelaunchingActivities.remove(i);
+                    i--;
+                    N--;
+                }
+            }
+            
+            if (tmp == null) {
+                return;
+            }
+            
+            if (mPendingConfiguration != null) {
+                changedConfig = mPendingConfiguration;
+                mPendingConfiguration = null;
+            }
+        }
+        
+        // If there was a pending configuration change, execute it first.
+        if (changedConfig != null) {
+            handleConfigurationChanged(changedConfig);
+        }
+        
+        ActivityRecord r = mActivities.get(tmp.token);
+        if (localLOGV) Log.v(TAG, "Handling relaunch of " + r);
+        if (r == null) {
+            return;
+        }
+        
+        r.activity.mConfigChangeFlags |= configChanges;
+        
+        Bundle savedState = null;
+        if (!r.paused) {
+            savedState = performPauseActivity(r.token, false, true);
+        }
+        
+        handleDestroyActivity(r.token, false, configChanges, true);
+        
+        r.activity = null;
+        r.window = null;
+        r.hideForNow = false;
+        r.nextIdle = null;
+        r.pendingResults = tmp.pendingResults;
+        r.pendingIntents = tmp.pendingIntents;
+        r.startsNotResumed = tmp.startsNotResumed;
+        if (savedState != null) {
+            r.state = savedState;
+        }
+        
+        handleLaunchActivity(r);
+    }
+
+    private final void handleRequestThumbnail(IBinder token) {
+        ActivityRecord r = mActivities.get(token);
+        Bitmap thumbnail = createThumbnailBitmap(r);
+        CharSequence description = null;
+        try {
+            description = r.activity.onCreateDescription();
+        } catch (Exception e) {
+            if (!mInstrumentation.onException(r.activity, e)) {
+                throw new RuntimeException(
+                        "Unable to create description of activity "
+                        + r.intent.getComponent().toShortString()
+                        + ": " + e.toString(), e);
+            }
+        }
+        //System.out.println("Reporting top thumbnail " + thumbnail);
+        try {
+            ActivityManagerNative.getDefault().reportThumbnail(
+                token, thumbnail, description);
+        } catch (RemoteException ex) {
+        }
+    }
+
+    ArrayList<ComponentCallbacks> collectComponentCallbacksLocked(
+            boolean allActivities, Configuration newConfig) {
+        ArrayList<ComponentCallbacks> callbacks
+                = new ArrayList<ComponentCallbacks>();
+        
+        if (mActivities.size() > 0) {
+            Iterator<ActivityRecord> it = mActivities.values().iterator();
+            while (it.hasNext()) {
+                ActivityRecord ar = it.next();
+                Activity a = ar.activity;
+                if (a != null) {
+                    if (!ar.activity.mFinished && (allActivities ||
+                            (a != null && !ar.paused))) {
+                        // If the activity is currently resumed, its configuration
+                        // needs to change right now.
+                        callbacks.add(a);
+                    } else if (newConfig != null) {
+                        // Otherwise, we will tell it about the change
+                        // the next time it is resumed or shown.  Note that
+                        // the activity manager may, before then, decide the
+                        // activity needs to be destroyed to handle its new
+                        // configuration.
+                        ar.newConfig = newConfig;
+                    }
+                }
+            }
+        }
+        if (mServices.size() > 0) {
+            Iterator<Service> it = mServices.values().iterator();
+            while (it.hasNext()) {
+                callbacks.add(it.next());
+            }
+        }
+        synchronized (mProviderMap) {
+            if (mLocalProviders.size() > 0) {
+                Iterator<ProviderRecord> it = mLocalProviders.values().iterator();
+                while (it.hasNext()) {
+                    callbacks.add(it.next().mLocalProvider);
+                }
+            }
+        }
+        final int N = mAllApplications.size();
+        for (int i=0; i<N; i++) {
+            callbacks.add(mAllApplications.get(i));
+        }
+        
+        return callbacks;
+    }
+    
+    private final void performConfigurationChanged(
+            ComponentCallbacks cb, Configuration config) {
+        // Only for Activity objects, check that they actually call up to their
+        // superclass implementation.  ComponentCallbacks is an interface, so
+        // we check the runtime type and act accordingly.
+        Activity activity = (cb instanceof Activity) ? (Activity) cb : null;
+        if (activity != null) {
+            activity.mCalled = false;
+        }
+        
+        boolean shouldChangeConfig = false;
+        if ((activity == null) || (activity.mCurrentConfig == null)) {
+            shouldChangeConfig = true;
+        } else {
+            
+            // If the new config is the same as the config this Activity
+            // is already running with then don't bother calling
+            // onConfigurationChanged
+            int diff = activity.mCurrentConfig.diff(config);
+            if (diff != 0) {
+                
+                // If this activity doesn't handle any of the config changes
+                // then don't bother calling onConfigurationChanged as we're
+                // going to destroy it.
+                if ((~activity.mActivityInfo.configChanges & diff) == 0) {
+                    shouldChangeConfig = true;
+                }
+            }
+        }
+        
+        if (shouldChangeConfig) {
+            cb.onConfigurationChanged(config);
+            
+            if (activity != null) {
+                if (!activity.mCalled) {
+                    throw new SuperNotCalledException(
+                            "Activity " + activity.getLocalClassName() +
+                        " did not call through to super.onConfigurationChanged()");
+                }
+                activity.mConfigChangeFlags = 0;
+                activity.mCurrentConfig = new Configuration(config);
+            }
+        }
+    }
+
+    final void handleConfigurationChanged(Configuration config) {
+        
+        synchronized (mRelaunchingActivities) {
+            if (mPendingConfiguration != null) {
+                config = mPendingConfiguration;
+                mPendingConfiguration = null;
+            }
+        }
+        
+        ArrayList<ComponentCallbacks> callbacks
+                = new ArrayList<ComponentCallbacks>();
+        
+        synchronized(mPackages) {
+            if (mConfiguration == null) {
+                mConfiguration = new Configuration();
+            }
+            mConfiguration.updateFrom(config);
+            DisplayMetrics dm = getDisplayMetricsLocked(true);
+
+            // set it for java, this also affects newly created Resources
+            if (config.locale != null) {
+                Locale.setDefault(config.locale);
+            }
+
+            Resources.updateSystemConfiguration(config, null);
+
+            ApplicationContext.ApplicationPackageManager.configurationChanged();
+            //Log.i(TAG, "Configuration changed in " + currentPackageName());
+            {
+                Iterator<WeakReference<Resources>> it =
+                    mActiveResources.values().iterator();
+                //Iterator<Map.Entry<String, WeakReference<Resources>>> it =
+                //    mActiveResources.entrySet().iterator();
+                while (it.hasNext()) {
+                    WeakReference<Resources> v = it.next();
+                    Resources r = v.get();
+                    if (r != null) {
+                        r.updateConfiguration(config, dm);
+                        //Log.i(TAG, "Updated app resources " + v.getKey()
+                        //        + " " + r + ": " + r.getConfiguration());
+                    } else {
+                        //Log.i(TAG, "Removing old resources " + v.getKey());
+                        it.remove();
+                    }
+                }
+            }
+            
+            callbacks = collectComponentCallbacksLocked(false, config);
+        }
+        
+        final int N = callbacks.size();
+        for (int i=0; i<N; i++) {
+            performConfigurationChanged(callbacks.get(i), config);
+        }
+    }
+
+    final void handleActivityConfigurationChanged(IBinder token) {
+        ActivityRecord r = mActivities.get(token);
+        if (r == null || r.activity == null) {
+            return;
+        }
+        
+        performConfigurationChanged(r.activity, mConfiguration);
+    }
+
+    final void handleLowMemory() {
+        ArrayList<ComponentCallbacks> callbacks
+                = new ArrayList<ComponentCallbacks>();
+
+        synchronized(mPackages) {
+            callbacks = collectComponentCallbacksLocked(true, null);
+        }
+        
+        final int N = callbacks.size();
+        for (int i=0; i<N; i++) {
+            callbacks.get(i).onLowMemory();
+        }
+
+        // Ask SQLite to free up as much memory as it can, mostly from it's page caches
+        int sqliteReleased = SQLiteDatabase.releaseMemory();
+        EventLog.writeEvent(SQLITE_MEM_RELEASED_EVENT_LOG_TAG, sqliteReleased);
+
+        BinderInternal.forceGc("mem");
+    }
+
+    private final void handleBindApplication(AppBindData data) {
+        mBoundApplication = data;
+        mConfiguration = new Configuration(data.config);
+
+        // We now rely on this being set by zygote.
+        //Process.setGid(data.appInfo.gid);
+        //Process.setUid(data.appInfo.uid);
+
+        // send up app name; do this *before* waiting for debugger
+        android.ddm.DdmHandleAppName.setAppName(data.processName);
+
+        /*
+         * Before spawning a new process, reset the time zone to be the system time zone.
+         * This needs to be done because the system time zone could have changed after the
+         * the spawning of this process. Without doing this this process would have the incorrect
+         * system time zone.
+         */
+        TimeZone.setDefault(null);
+
+        /*
+         * Initialize the default locale in this process for the reasons we set the time zone.
+         */
+        Locale.setDefault(data.config.locale);
+
+        data.info = getPackageInfoNoCheck(data.appInfo);
+
+        if (data.debugMode != IApplicationThread.DEBUG_OFF) {
+            // XXX should have option to change the port.
+            Debug.changeDebugPort(8100);
+            if (data.debugMode == IApplicationThread.DEBUG_WAIT) {
+                Log.w(TAG, "Application " + data.info.getPackageName()
+                      + " is waiting for the debugger on port 8100...");
+
+                IActivityManager mgr = ActivityManagerNative.getDefault();
+                try {
+                    mgr.showWaitingForDebugger(mAppThread, true);
+                } catch (RemoteException ex) {
+                }
+
+                Debug.waitForDebugger();
+
+                try {
+                    mgr.showWaitingForDebugger(mAppThread, false);
+                } catch (RemoteException ex) {
+                }
+
+            } else {
+                Log.w(TAG, "Application " + data.info.getPackageName()
+                      + " can be debugged on port 8100...");
+            }
+        }
+
+        if (data.instrumentationName != null) {
+            ApplicationContext appContext = new ApplicationContext();
+            appContext.init(data.info, null, this);
+            InstrumentationInfo ii = null;
+            try {
+                ii = appContext.getPackageManager().
+                    getInstrumentationInfo(data.instrumentationName, 0);
+            } catch (PackageManager.NameNotFoundException e) {
+            }
+            if (ii == null) {
+                throw new RuntimeException(
+                    "Unable to find instrumentation info for: "
+                    + data.instrumentationName);
+            }
+
+            mInstrumentationAppDir = ii.sourceDir;
+            mInstrumentationAppPackage = ii.packageName;
+            mInstrumentedAppDir = data.info.getAppDir();
+
+            ApplicationInfo instrApp = new ApplicationInfo();
+            instrApp.packageName = ii.packageName;
+            instrApp.sourceDir = ii.sourceDir;
+            instrApp.publicSourceDir = ii.publicSourceDir;
+            instrApp.dataDir = ii.dataDir;
+            PackageInfo pi = getPackageInfo(instrApp,
+                    appContext.getClassLoader(), false, true);
+            ApplicationContext instrContext = new ApplicationContext();
+            instrContext.init(pi, null, this);
+
+            try {
+                java.lang.ClassLoader cl = instrContext.getClassLoader();
+                mInstrumentation = (Instrumentation)
+                    cl.loadClass(data.instrumentationName.getClassName()).newInstance();
+            } catch (Exception e) {
+                throw new RuntimeException(
+                    "Unable to instantiate instrumentation "
+                    + data.instrumentationName + ": " + e.toString(), e);
+            }
+
+            mInstrumentation.init(this, instrContext, appContext,
+                    new ComponentName(ii.packageName, ii.name), data.instrumentationWatcher);
+
+            if (data.profileFile != null && !ii.handleProfiling) {
+                data.handlingProfiling = true;
+                File file = new File(data.profileFile);
+                file.getParentFile().mkdirs();
+                Debug.startMethodTracing(file.toString(), 8 * 1024 * 1024);
+            }
+
+            try {
+                mInstrumentation.onCreate(data.instrumentationArgs);
+            }
+            catch (Exception e) {
+                throw new RuntimeException(
+                    "Exception thrown in onCreate() of "
+                    + data.instrumentationName + ": " + e.toString(), e);
+            }
+
+        } else {
+            mInstrumentation = new Instrumentation();
+        }
+
+        Application app = data.info.makeApplication();
+        mInitialApplication = app;
+
+        List<ProviderInfo> providers = data.providers;
+        if (providers != null) {
+            installContentProviders(app, providers);
+        }
+
+        try {
+            mInstrumentation.callApplicationOnCreate(app);
+        } catch (Exception e) {
+            if (!mInstrumentation.onException(app, e)) {
+                throw new RuntimeException(
+                    "Unable to create application " + app.getClass().getName()
+                    + ": " + e.toString(), e);
+            }
+        }
+    }
+
+    /*package*/ final void finishInstrumentation(int resultCode, Bundle results) {
+        IActivityManager am = ActivityManagerNative.getDefault();
+        if (mBoundApplication.profileFile != null && mBoundApplication.handlingProfiling) {
+            Debug.stopMethodTracing();
+        }
+        //Log.i(TAG, "am: " + ActivityManagerNative.getDefault()
+        //      + ", app thr: " + mAppThread);
+        try {
+            am.finishInstrumentation(mAppThread, resultCode, results);
+        } catch (RemoteException ex) {
+        }
+    }
+
+    private final void installContentProviders(
+            Context context, List<ProviderInfo> providers) {
+        final ArrayList<IActivityManager.ContentProviderHolder> results =
+            new ArrayList<IActivityManager.ContentProviderHolder>();
+
+        Iterator<ProviderInfo> i = providers.iterator();
+        while (i.hasNext()) {
+            ProviderInfo cpi = i.next();
+            StringBuilder buf = new StringBuilder(128);
+            buf.append("Publishing provider ");
+            buf.append(cpi.authority);
+            buf.append(": ");
+            buf.append(cpi.name);
+            Log.i(TAG, buf.toString());
+            IContentProvider cp = installProvider(context, null, cpi, false);
+            if (cp != null) {
+                IActivityManager.ContentProviderHolder cph =
+                    new IActivityManager.ContentProviderHolder(cpi);
+                cph.provider = cp;
+                results.add(cph);
+                // Don't ever unload this provider from the process.
+                synchronized(mProviderMap) {
+                    mProviderRefCountMap.put(cp.asBinder(), new ProviderRefCount(10000));
+                }
+            }
+        }
+
+        try {
+            ActivityManagerNative.getDefault().publishContentProviders(
+                getApplicationThread(), results);
+        } catch (RemoteException ex) {
+        }
+    }
+
+    private final IContentProvider getProvider(Context context, String name) {
+        synchronized(mProviderMap) {
+            final ProviderRecord pr = mProviderMap.get(name);
+            if (pr != null) {
+                return pr.mProvider;
+            }
+        }
+
+        IActivityManager.ContentProviderHolder holder = null;
+        try {
+            holder = ActivityManagerNative.getDefault().getContentProvider(
+                getApplicationThread(), name);
+        } catch (RemoteException ex) {
+        }
+        if (holder == null) {
+            Log.e(TAG, "Failed to find provider info for " + name);
+            return null;
+        }
+        if (holder.permissionFailure != null) {
+            throw new SecurityException("Permission " + holder.permissionFailure
+                    + " required for provider " + name);
+        }
+
+        IContentProvider prov = installProvider(context, holder.provider,
+                holder.info, true);
+        //Log.i(TAG, "noReleaseNeeded=" + holder.noReleaseNeeded);
+        if (holder.noReleaseNeeded || holder.provider == null) {
+            // We are not going to release the provider if it is an external
+            // provider that doesn't care about being released, or if it is
+            // a local provider running in this process.
+            //Log.i(TAG, "*** NO RELEASE NEEDED");
+            synchronized(mProviderMap) {
+                mProviderRefCountMap.put(prov.asBinder(), new ProviderRefCount(10000));
+            }
+        }
+        return prov;
+    }
+
+    public final IContentProvider acquireProvider(Context c, String name) {
+        IContentProvider provider = getProvider(c, name);
+        if(provider == null)
+            return null;
+        IBinder jBinder = provider.asBinder();
+        synchronized(mProviderMap) {
+            ProviderRefCount prc = mProviderRefCountMap.get(jBinder);
+            if(prc == null) {
+                mProviderRefCountMap.put(jBinder, new ProviderRefCount(1));
+            } else {
+                prc.count++;
+            } //end else
+        } //end synchronized
+        return provider;
+    }
+
+    public final boolean releaseProvider(IContentProvider provider) {
+        if(provider == null) {
+            return false;
+        }
+        IBinder jBinder = provider.asBinder();
+        synchronized(mProviderMap) {
+            ProviderRefCount prc = mProviderRefCountMap.get(jBinder);
+            if(prc == null) {
+                if(localLOGV) Log.v(TAG, "releaseProvider::Weird shouldnt be here");
+                return false;
+            } else {
+                prc.count--;
+                if(prc.count == 0) {
+                    mProviderRefCountMap.remove(jBinder);
+                    //invoke removeProvider to dereference provider
+                    removeProviderLocked(provider);
+                } //end if
+            } //end else
+        } //end synchronized
+        return true;
+    }
+
+    public final void removeProviderLocked(IContentProvider provider) {
+        if (provider == null) {
+            return;
+        }
+        IBinder providerBinder = provider.asBinder();
+        boolean amRemoveFlag = false;
+
+        // remove the provider from mProviderMap
+        Iterator<ProviderRecord> iter = mProviderMap.values().iterator();
+        while (iter.hasNext()) {
+            ProviderRecord pr = iter.next();
+            IBinder myBinder = pr.mProvider.asBinder();
+            if (myBinder == providerBinder) {
+                //find if its published by this process itself
+                if(pr.mLocalProvider != null) {
+                    if(localLOGV) Log.i(TAG, "removeProvider::found local provider returning");
+                    return;
+                }
+                if(localLOGV) Log.v(TAG, "removeProvider::Not local provider Unlinking " +
+                        "death recipient");
+                //content provider is in another process
+                myBinder.unlinkToDeath(pr, 0);
+                iter.remove();
+                //invoke remove only once for the very first name seen
+                if(!amRemoveFlag) {
+                    try {
+                        if(localLOGV) Log.v(TAG, "removeProvider::Invoking " +
+                                "ActivityManagerNative.removeContentProvider("+pr.mName);
+                        ActivityManagerNative.getDefault().removeContentProvider(getApplicationThread(), pr.mName);
+                        amRemoveFlag = true;
+                    } catch (RemoteException e) {
+                        //do nothing content provider object is dead any way
+                    } //end catch
+                }
+            } //end if myBinder
+        }  //end while iter
+    }
+
+    final void removeDeadProvider(String name, IContentProvider provider) {
+        synchronized(mProviderMap) {
+            ProviderRecord pr = mProviderMap.get(name);
+            if (pr.mProvider.asBinder() == provider.asBinder()) {
+                Log.i(TAG, "Removing dead content provider: " + name);
+                mProviderMap.remove(name);
+            }
+        }
+    }
+
+    final void removeDeadProviderLocked(String name, IContentProvider provider) {
+        ProviderRecord pr = mProviderMap.get(name);
+        if (pr.mProvider.asBinder() == provider.asBinder()) {
+            Log.i(TAG, "Removing dead content provider: " + name);
+            mProviderMap.remove(name);
+        }
+    }
+
+    private final IContentProvider installProvider(Context context,
+            IContentProvider provider, ProviderInfo info, boolean noisy) {
+        ContentProvider localProvider = null;
+        if (provider == null) {
+            if (noisy) {
+                Log.d(TAG, "Loading provider " + info.authority + ": "
+                        + info.name);
+            }
+            Context c = null;
+            ApplicationInfo ai = info.applicationInfo;
+            if (context.getPackageName().equals(ai.packageName)) {
+                c = context;
+            } else if (mInitialApplication != null &&
+                    mInitialApplication.getPackageName().equals(ai.packageName)) {
+                c = mInitialApplication;
+            } else {
+                try {
+                    c = context.createPackageContext(ai.packageName,
+                            Context.CONTEXT_INCLUDE_CODE);
+                } catch (PackageManager.NameNotFoundException e) {
+                }
+            }
+            if (c == null) {
+                Log.w(TAG, "Unable to get context for package " +
+                      ai.packageName +
+                      " while loading content provider " +
+                      info.name);
+                return null;
+            }
+            try {
+                final java.lang.ClassLoader cl = c.getClassLoader();
+                localProvider = (ContentProvider)cl.
+                    loadClass(info.name).newInstance();
+                provider = localProvider.getIContentProvider();
+                if (provider == null) {
+                    Log.e(TAG, "Failed to instantiate class " +
+                          info.name + " from sourceDir " +
+                          info.applicationInfo.sourceDir);
+                    return null;
+                }
+                if (Config.LOGV) Log.v(
+                    TAG, "Instantiating local provider " + info.name);
+                // XXX Need to create the correct context for this provider.
+                localProvider.attachInfo(c, info);
+            } catch (java.lang.Exception e) {
+                if (!mInstrumentation.onException(null, e)) {
+                    throw new RuntimeException(
+                            "Unable to get provider " + info.name
+                            + ": " + e.toString(), e);
+                }
+                return null;
+            }
+        } else if (localLOGV) {
+            Log.v(TAG, "Installing external provider " + info.authority + ": "
+                    + info.name);
+        }
+
+        synchronized (mProviderMap) {
+            // Cache the pointer for the remote provider.
+            String names[] = PATTERN_SEMICOLON.split(info.authority);
+            for (int i=0; i<names.length; i++) {
+                ProviderRecord pr = new ProviderRecord(names[i], provider,
+                        localProvider);
+                try {
+                    provider.asBinder().linkToDeath(pr, 0);
+                    mProviderMap.put(names[i], pr);
+                } catch (RemoteException e) {
+                    return null;
+                }
+            }
+            if (localProvider != null) {
+                mLocalProviders.put(provider.asBinder(),
+                        new ProviderRecord(null, provider, localProvider));
+            }
+        }
+
+        return provider;
+    }
+
+    private final void attach(boolean system) {
+        sThreadLocal.set(this);
+        mSystemThread = system;
+        AndroidHttpClient.setThreadBlocked(true);
+        if (!system) {
+            android.ddm.DdmHandleAppName.setAppName("<pre-initialized>");
+            RuntimeInit.setApplicationObject(mAppThread.asBinder());
+            IActivityManager mgr = ActivityManagerNative.getDefault();
+            try {
+                mgr.attachApplication(mAppThread);
+            } catch (RemoteException ex) {
+            }
+        } else {
+            // Don't set application object here -- if the system crashes,
+            // we can't display an alert, we just want to die die die.
+            android.ddm.DdmHandleAppName.setAppName("system_process");
+            try {
+                mInstrumentation = new Instrumentation();
+                ApplicationContext context = new ApplicationContext();
+                context.init(getSystemContext().mPackageInfo, null, this);
+                Application app = Instrumentation.newApplication(Application.class, context);
+                mAllApplications.add(app);
+                mInitialApplication = app;
+                app.onCreate();
+            } catch (Exception e) {
+                throw new RuntimeException(
+                        "Unable to instantiate Application():" + e.toString(), e);
+            }
+        }
+    }
+
+    private final void detach()
+    {
+        AndroidHttpClient.setThreadBlocked(false);
+        sThreadLocal.set(null);
+    }
+
+    public static final ActivityThread systemMain() {
+        ActivityThread thread = new ActivityThread();
+        thread.attach(true);
+        return thread;
+    }
+
+    public final void installSystemProviders(List providers) {
+        if (providers != null) {
+            installContentProviders(mInitialApplication,
+                                    (List<ProviderInfo>)providers);
+        }
+    }
+
+    public static final void main(String[] args) {
+        Process.setArgV0("<pre-initialized>");
+
+        Looper.prepareMainLooper();
+
+        ActivityThread thread = new ActivityThread();
+        thread.attach(false);
+
+        Looper.loop();
+
+        if (Process.supportsProcesses()) {
+            throw new RuntimeException("Main thread loop unexpectedly exited");
+        }
+
+        thread.detach();
+        String name;
+        if (thread.mInitialApplication != null) name = thread.mInitialApplication.getPackageName();
+        else name = "<unknown>";
+        Log.i(TAG, "Main thread of " + name + " is now exiting");
+    }
+}
diff --git a/core/java/android/app/AlarmManager.java b/core/java/android/app/AlarmManager.java
new file mode 100644
index 0000000..b4c0e31
--- /dev/null
+++ b/core/java/android/app/AlarmManager.java
@@ -0,0 +1,276 @@
+/*
+ * Copyright (C) 2007 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 android.app;
+
+import android.content.Context;
+import android.content.Intent;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+
+/**
+ * This class provides access to the system alarm services.  These allow you
+ * to schedule your application to be run at some point in the future.  When
+ * an alarm goes off, the {@link Intent} that had been registered for it
+ * is broadcast by the system, automatically starting the target application
+ * if it is not already running.  Registered alarms are retained while the
+ * device is asleep (and can optionally wake the device up if they go off
+ * during that time), but will be cleared if it is turned off and rebooted.
+ *
+ * <p><b>Note: The Alarm Manager is intended for cases where you want to have
+ * your application code run at a specific time, even if your application is
+ * not currently running.  For normal timing operations (ticks, timeouts,
+ * etc) it is easier and much more efficient to use
+ * {@link android.os.Handler}.</b>
+ *
+ * <p>You do not
+ * instantiate this class directly; instead, retrieve it through
+ * {@link android.content.Context#getSystemService
+ * Context.getSystemService(Context.ALARM_SERVICE)}.
+ */
+public class AlarmManager
+{
+    /**
+     * Alarm time in {@link System#currentTimeMillis System.currentTimeMillis()}
+     * (wall clock time in UTC), which will wake up the device when
+     * it goes off.
+     */
+    public static final int RTC_WAKEUP = 0;
+    /**
+     * Alarm time in {@link System#currentTimeMillis System.currentTimeMillis()}
+     * (wall clock time in UTC).  This alarm does not wake the
+     * device up; if it goes off while the device is asleep, it will not be
+     * delivered until the next time the device wakes up.
+     */
+    public static final int RTC = 1;
+    /**
+     * Alarm time in {@link android.os.SystemClock#elapsedRealtime
+     * SystemClock.elapsedRealtime()} (time since boot, including sleep),
+     * which will wake up the device when it goes off.
+     */
+    public static final int ELAPSED_REALTIME_WAKEUP = 2;
+    /**
+     * Alarm time in {@link android.os.SystemClock#elapsedRealtime
+     * SystemClock.elapsedRealtime()} (time since boot, including sleep).
+     * This alarm does not wake the device up; if it goes off while the device
+     * is asleep, it will not be delivered until the next time the device
+     * wakes up.
+     */
+    public static final int ELAPSED_REALTIME = 3;
+
+    private final IAlarmManager mService;
+
+    /**
+     * package private on purpose
+     */
+    AlarmManager(IAlarmManager service) {
+        mService = service;
+    }
+    
+    /**
+     * Schedule an alarm.  <b>Note: for timing operations (ticks, timeouts,
+     * etc) it is easier and much more efficient to use
+     * {@link android.os.Handler}.</b>  If there is already an alarm scheduled
+     * for the same IntentSender, it will first be canceled.
+     *
+     * <p>If the time occurs in the past, the alarm will be triggered
+     * immediately.  If there is already an alarm for this Intent
+     * scheduled (with the equality of two intents being defined by
+     * {@link Intent#filterEquals}), then it will be removed and replaced by
+     * this one.
+     *
+     * <p>
+     * The alarm is an intent broadcast that goes to a broadcast receiver that
+     * you registered with {@link android.content.Context#registerReceiver}
+     * or through the &lt;receiver&gt; tag in an AndroidManifest.xml file.
+     *
+     * <p>
+     * Alarm intents are delivered with a data extra of type int called
+     * {@link Intent#EXTRA_ALARM_COUNT Intent.EXTRA_ALARM_COUNT} that indicates
+     * how many past alarm events have been accumulated into this intent
+     * broadcast.  Recurring alarms that have gone undelivered because the
+     * phone was asleep may have a count greater than one when delivered.  
+     *  
+     * @param type One of ELAPSED_REALTIME, ELAPSED_REALTIME_WAKEUP, RTC or
+     *             RTC_WAKEUP.
+     * @param triggerAtTime Time the alarm should go off, using the
+     *                      appropriate clock (depending on the alarm type).
+     * @param operation Action to perform when the alarm goes off;
+     * typically comes from {@link PendingIntent#getBroadcast
+     * IntentSender.getBroadcast()}.
+     *
+     * @see android.os.Handler
+     * @see #setRepeating
+     * @see #cancel
+     * @see android.content.Context#sendBroadcast
+     * @see android.content.Context#registerReceiver
+     * @see android.content.Intent#filterEquals
+     * @see #ELAPSED_REALTIME
+     * @see #ELAPSED_REALTIME_WAKEUP
+     * @see #RTC
+     * @see #RTC_WAKEUP
+     */
+    public void set(int type, long triggerAtTime, PendingIntent operation) {
+        try {
+            mService.set(type, triggerAtTime, operation);
+        } catch (RemoteException ex) {
+        }
+    }
+
+    /**
+     * Schedule a repeating alarm.  <b>Note: for timing operations (ticks,
+     * timeouts, etc) it is easier and much more efficient to use
+     * {@link android.os.Handler}.</b>  If there is already an alarm scheduled
+     * for the same IntentSender, it will first be canceled.
+     *
+     * <p>Like {@link #set}, except you can also
+     * supply a rate at which the alarm will repeat.  This alarm continues
+     * repeating until explicitly removed with {@link #cancel}.  If the time
+     * occurs in the past, the alarm will be triggered immediately, with an
+     * alarm count depending on how far in the past the trigger time is relative
+     * to the repeat interval.
+     *
+     * <p>If an alarm is delayed (by system sleep, for example, for non
+     * _WAKEUP alarm types), a skipped repeat will be delivered as soon as
+     * possible.  After that, future alarms will be delivered according to the
+     * original schedule; they do not drift over time.  For example, if you have
+     * set a recurring alarm for the top of every hour but the phone was asleep
+     * from 7:45 until 8:45, an alarm will be sent as soon as the phone awakens,
+     * then the next alarm will be sent at 9:00.
+     * 
+     * <p>If your application wants to allow the delivery times to drift in 
+     * order to guarantee that at least a certain time interval always elapses
+     * between alarms, then the approach to take is to use one-time alarms, 
+     * scheduling the next one yourself when handling each alarm delivery.
+     *
+     * @param type One of ELAPSED_REALTIME, ELAPSED_REALTIME_WAKEUP}, RTC or
+     *             RTC_WAKEUP.
+     * @param triggerAtTime Time the alarm should first go off, using the
+     *                      appropriate clock (depending on the alarm type).
+     * @param interval Interval between subsequent repeats of the alarm.
+     * @param operation Action to perform when the alarm goes off;
+     * typically comes from {@link PendingIntent#getBroadcast
+     * IntentSender.getBroadcast()}.
+     *
+     * @see android.os.Handler
+     * @see #set
+     * @see #cancel
+     * @see android.content.Context#sendBroadcast
+     * @see android.content.Context#registerReceiver
+     * @see android.content.Intent#filterEquals
+     * @see #ELAPSED_REALTIME
+     * @see #ELAPSED_REALTIME_WAKEUP
+     * @see #RTC
+     * @see #RTC_WAKEUP
+     */
+    public void setRepeating(int type, long triggerAtTime, long interval,
+            PendingIntent operation) {
+        try {
+            mService.setRepeating(type, triggerAtTime, interval, operation);
+        } catch (RemoteException ex) {
+        }
+    }
+
+    /**
+     * Available inexact recurrence intervals recognized by
+     * {@link #setInexactRepeating(int, long, long, PendingIntent)} 
+     */
+    public static final long INTERVAL_FIFTEEN_MINUTES = 15 * 60 * 1000;
+    public static final long INTERVAL_HALF_HOUR = 2*INTERVAL_FIFTEEN_MINUTES;
+    public static final long INTERVAL_HOUR = 2*INTERVAL_HALF_HOUR;
+    public static final long INTERVAL_HALF_DAY = 12*INTERVAL_HOUR;
+    public static final long INTERVAL_DAY = 2*INTERVAL_HALF_DAY;
+    
+    /**
+     * Schedule a repeating alarm that has inexact trigger time requirements;
+     * for example, an alarm that repeats every hour, but not necessarily at
+     * the top of every hour.  These alarms are more power-efficient than
+     * the strict recurrences supplied by {@link #setRepeating}, since the
+     * system can adjust alarms' phase to cause them to fire simultaneously,
+     * avoiding waking the device from sleep more than necessary.
+     * 
+     * <p>Your alarm's first trigger will not be before the requested time,
+     * but it might not occur for almost a full interval after that time.  In
+     * addition, while the overall period of the repeating alarm will be as
+     * requested, the time between any two successive firings of the alarm
+     * may vary.  If your application demands very low jitter, use
+     * {@link #setRepeating} instead.
+     * 
+     * @param type One of ELAPSED_REALTIME, ELAPSED_REALTIME_WAKEUP}, RTC or
+     *             RTC_WAKEUP.
+     * @param triggerAtTime Time the alarm should first go off, using the
+     *                      appropriate clock (depending on the alarm type).  This
+     *                      is inexact: the alarm will not fire before this time,
+     *                      but there may be a delay of almost an entire alarm
+     *                      interval before the first invocation of the alarm.
+     * @param interval Interval between subsequent repeats of the alarm.  If
+     *                 this is one of INTERVAL_FIFTEEN_MINUTES, INTERVAL_HALF_HOUR,
+     *                 INTERVAL_HOUR, INTERVAL_HALF_DAY, or INTERVAL_DAY then the
+     *                 alarm will be phase-aligned with other alarms to reduce
+     *                 the number of wakeups.  Otherwise, the alarm will be set
+     *                 as though the application had called {@link #setRepeating}.
+     * @param operation Action to perform when the alarm goes off;
+     * typically comes from {@link PendingIntent#getBroadcast
+     * IntentSender.getBroadcast()}.
+     *
+     * @see android.os.Handler
+     * @see #set
+     * @see #cancel
+     * @see android.content.Context#sendBroadcast
+     * @see android.content.Context#registerReceiver
+     * @see android.content.Intent#filterEquals
+     * @see #ELAPSED_REALTIME
+     * @see #ELAPSED_REALTIME_WAKEUP
+     * @see #RTC
+     * @see #RTC_WAKEUP
+     * @see #INTERVAL_FIFTEEN_MINUTES
+     * @see #INTERVAL_HALF_HOUR
+     * @see #INTERVAL_HOUR
+     * @see #INTERVAL_HALF_DAY
+     * @see #INTERVAL_DAY
+     */
+    public void setInexactRepeating(int type, long triggerAtTime, long interval,
+            PendingIntent operation) {
+        try {
+            mService.setInexactRepeating(type, triggerAtTime, interval, operation);
+        } catch (RemoteException ex) {
+        }
+    }
+    
+    /**
+     * Remove any alarms with a matching {@link Intent}.
+     * Any alarm, of any type, whose Intent matches this one (as defined by
+     * {@link Intent#filterEquals}), will be canceled.
+     *
+     * @param operation IntentSender which matches a previously added
+     * IntentSender.
+     *
+     * @see #set
+     */
+    public void cancel(PendingIntent operation) {
+        try {
+            mService.remove(operation);
+        } catch (RemoteException ex) {
+        }
+    }
+    
+    public void setTimeZone(String timeZone) {
+        try {
+            mService.setTimeZone(timeZone);
+        } catch (RemoteException ex) {
+        }
+    }
+}
diff --git a/core/java/android/app/AlertDialog.java b/core/java/android/app/AlertDialog.java
new file mode 100644
index 0000000..f2b89c3
--- /dev/null
+++ b/core/java/android/app/AlertDialog.java
@@ -0,0 +1,795 @@
+/*
+ * Copyright (C) 2007 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 android.app;
+
+import android.content.Context;
+import android.content.DialogInterface;
+import android.database.Cursor;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.os.Message;
+import android.view.KeyEvent;
+import android.view.View;
+import android.view.WindowManager;
+import android.widget.AdapterView;
+import android.widget.Button;
+import android.widget.ListAdapter;
+import android.widget.ListView;
+
+import com.android.internal.app.AlertController;
+
+/**
+ * A subclass of Dialog that can display one, two or three buttons. If you only want to
+ * display a String in this dialog box, use the setMessage() method.  If you
+ * want to display a more complex view, look up the FrameLayout called "body"
+ * and add your view to it:
+ *
+ * <pre>
+ * FrameLayout fl = (FrameLayout) findViewById(R.id.body);
+ * fl.add(myView, new LayoutParams(FILL_PARENT, WRAP_CONTENT));
+ * </pre>
+ * 
+ * <p>The AlertDialog class takes care of automatically setting
+ * {@link WindowManager.LayoutParams#FLAG_ALT_FOCUSABLE_IM
+ * WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM} for you based on whether
+ * any views in the dialog return true from {@link View#onCheckIsTextEditor()
+ * View.onCheckIsTextEditor()}.  Generally you want this set for a Dialog
+ * without text editors, so that it will be placed on top of the current
+ * input method UI.  You can modify this behavior by forcing the flag to your
+ * desired mode after calling {@link #onCreate}.
+ */
+public class AlertDialog extends Dialog implements DialogInterface {
+    private AlertController mAlert;
+
+    protected AlertDialog(Context context) {
+        this(context, com.android.internal.R.style.Theme_Dialog_Alert);
+    }
+
+    protected AlertDialog(Context context, int theme) {
+        super(context, theme);
+        mAlert = new AlertController(context, this, getWindow());
+    }
+
+    protected AlertDialog(Context context, boolean cancelable, OnCancelListener cancelListener) {
+        super(context, com.android.internal.R.style.Theme_Dialog_Alert);
+        setCancelable(cancelable);
+        setOnCancelListener(cancelListener);
+        mAlert = new AlertController(context, this, getWindow());
+    }
+
+    /**
+     * Gets one of the buttons used in the dialog.
+     * <p>
+     * If a button does not exist in the dialog, null will be returned.
+     * 
+     * @param whichButton The identifier of the button that should be returned.
+     *            For example, this can be
+     *            {@link DialogInterface#BUTTON_POSITIVE}.
+     * @return The button from the dialog, or null if a button does not exist.
+     */
+    public Button getButton(int whichButton) {
+        return mAlert.getButton(whichButton);
+    }
+    
+    /**
+     * Gets the list view used in the dialog.
+     *  
+     * @return The {@link ListView} from the dialog.
+     */
+    public ListView getListView() {
+        return mAlert.getListView();
+    }
+    
+    @Override
+    public void setTitle(CharSequence title) {
+        super.setTitle(title);
+        mAlert.setTitle(title);
+    }
+
+    /**
+     * @see Builder#setCustomTitle(View)
+     */
+    public void setCustomTitle(View customTitleView) {
+        mAlert.setCustomTitle(customTitleView);
+    }
+    
+    public void setMessage(CharSequence message) {
+        mAlert.setMessage(message);
+    }
+
+    /**
+     * Set the view to display in that dialog.
+     */
+    public void setView(View view) {
+        mAlert.setView(view);
+    }
+    
+    /**
+     * Set the view to display in that dialog, specifying the spacing to appear around that 
+     * view.
+     *
+     * @param view The view to show in the content area of the dialog
+     * @param viewSpacingLeft Extra space to appear to the left of {@code view}
+     * @param viewSpacingTop Extra space to appear above {@code view}
+     * @param viewSpacingRight Extra space to appear to the right of {@code view}
+     * @param viewSpacingBottom Extra space to appear below {@code view}
+     */
+    public void setView(View view, int viewSpacingLeft, int viewSpacingTop, int viewSpacingRight,
+            int viewSpacingBottom) {
+        mAlert.setView(view, viewSpacingLeft, viewSpacingTop, viewSpacingRight, viewSpacingBottom);
+    }
+
+    /**
+     * Set a message to be sent when a button is pressed.
+     * 
+     * @param whichButton Which button to set the message for, can be one of
+     *            {@link DialogInterface#BUTTON_POSITIVE},
+     *            {@link DialogInterface#BUTTON_NEGATIVE}, or
+     *            {@link DialogInterface#BUTTON_NEUTRAL}
+     * @param text The text to display in positive button.
+     * @param msg The {@link Message} to be sent when clicked.
+     */
+    public void setButton(int whichButton, CharSequence text, Message msg) {
+        mAlert.setButton(whichButton, text, null, msg);
+    }
+    
+    /**
+     * Set a listener to be invoked when the positive button of the dialog is pressed.
+     * 
+     * @param whichButton Which button to set the listener on, can be one of
+     *            {@link DialogInterface#BUTTON_POSITIVE},
+     *            {@link DialogInterface#BUTTON_NEGATIVE}, or
+     *            {@link DialogInterface#BUTTON_NEUTRAL}
+     * @param text The text to display in positive button.
+     * @param listener The {@link DialogInterface.OnClickListener} to use.
+     */
+    public void setButton(int whichButton, CharSequence text, OnClickListener listener) {
+        mAlert.setButton(whichButton, text, listener, null);
+    }
+
+    /**
+     * @deprecated Use {@link #setButton(int, CharSequence, Message)} with
+     *             {@link DialogInterface#BUTTON_POSITIVE}.
+     */
+    @Deprecated
+    public void setButton(CharSequence text, Message msg) {
+        setButton(BUTTON_POSITIVE, text, msg);
+    }
+        
+    /**
+     * @deprecated Use {@link #setButton(int, CharSequence, Message)} with
+     *             {@link DialogInterface#BUTTON_NEGATIVE}.
+     */
+    @Deprecated
+    public void setButton2(CharSequence text, Message msg) {
+        setButton(BUTTON_NEGATIVE, text, msg);
+    }
+
+    /**
+     * @deprecated Use {@link #setButton(int, CharSequence, Message)} with
+     *             {@link DialogInterface#BUTTON_NEUTRAL}.
+     */
+    @Deprecated
+    public void setButton3(CharSequence text, Message msg) {
+        setButton(BUTTON_NEUTRAL, text, msg);
+    }
+
+    /**
+     * Set a listener to be invoked when button 1 of the dialog is pressed.
+     * 
+     * @param text The text to display in button 1.
+     * @param listener The {@link DialogInterface.OnClickListener} to use.
+     * @deprecated Use
+     *             {@link #setButton(int, CharSequence, android.content.DialogInterface.OnClickListener)}
+     *             with {@link DialogInterface#BUTTON_POSITIVE}
+     */
+    @Deprecated
+    public void setButton(CharSequence text, final OnClickListener listener) {
+        setButton(BUTTON_POSITIVE, text, listener);
+    }
+
+    /**
+     * Set a listener to be invoked when button 2 of the dialog is pressed.
+     * @param text The text to display in button 2.
+     * @param listener The {@link DialogInterface.OnClickListener} to use.
+     * @deprecated Use
+     *             {@link #setButton(int, CharSequence, android.content.DialogInterface.OnClickListener)}
+     *             with {@link DialogInterface#BUTTON_NEGATIVE}
+     */
+    @Deprecated
+    public void setButton2(CharSequence text, final OnClickListener listener) {
+        setButton(BUTTON_NEGATIVE, text, listener);
+    }
+
+    /**
+     * Set a listener to be invoked when button 3 of the dialog is pressed.
+     * @param text The text to display in button 3.
+     * @param listener The {@link DialogInterface.OnClickListener} to use.
+     * @deprecated Use
+     *             {@link #setButton(int, CharSequence, android.content.DialogInterface.OnClickListener)}
+     *             with {@link DialogInterface#BUTTON_POSITIVE}
+     */
+    @Deprecated
+    public void setButton3(CharSequence text, final OnClickListener listener) {
+        setButton(BUTTON_NEUTRAL, text, listener);
+    }
+
+    /**
+     * Set resId to 0 if you don't want an icon.
+     * @param resId the resourceId of the drawable to use as the icon or 0
+     * if you don't want an icon.
+     */
+    public void setIcon(int resId) {
+        mAlert.setIcon(resId);
+    }
+    
+    public void setIcon(Drawable icon) {
+        mAlert.setIcon(icon);
+    }
+
+    public void setInverseBackgroundForced(boolean forceInverseBackground) {
+        mAlert.setInverseBackgroundForced(forceInverseBackground);
+    }
+    
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        mAlert.installContent();
+    }
+
+    @Override
+    public boolean onKeyDown(int keyCode, KeyEvent event) {
+        if (mAlert.onKeyDown(keyCode, event)) return true;
+        return super.onKeyDown(keyCode, event);
+    }
+
+    @Override
+    public boolean onKeyUp(int keyCode, KeyEvent event) {
+        if (mAlert.onKeyUp(keyCode, event)) return true;
+        return super.onKeyUp(keyCode, event);
+    }
+    
+    public static class Builder {
+        private final AlertController.AlertParams P;
+        
+        /**
+         * Constructor using a context for this builder and the {@link AlertDialog} it creates.
+         */
+        public Builder(Context context) {
+            P = new AlertController.AlertParams(context);
+        }
+        
+        /**
+         * Set the title using the given resource id.
+         *
+         * @return This Builder object to allow for chaining of calls to set methods
+         */
+        public Builder setTitle(int titleId) {
+            P.mTitle = P.mContext.getText(titleId);
+            return this;
+        }
+        
+        /**
+         * Set the title displayed in the {@link Dialog}.
+         *
+         * @return This Builder object to allow for chaining of calls to set methods
+         */
+        public Builder setTitle(CharSequence title) {
+            P.mTitle = title;
+            return this;
+        }
+        
+        /**
+         * Set the title using the custom view {@code customTitleView}. The
+         * methods {@link #setTitle(int)} and {@link #setIcon(int)} should be
+         * sufficient for most titles, but this is provided if the title needs
+         * more customization. Using this will replace the title and icon set
+         * via the other methods.
+         * 
+         * @param customTitleView The custom view to use as the title.
+         *
+         * @return This Builder object to allow for chaining of calls to set methods
+         */
+        public Builder setCustomTitle(View customTitleView) {
+            P.mCustomTitleView = customTitleView;
+            return this;
+        }
+        
+        /**
+         * Set the message to display using the given resource id.
+         *
+         * @return This Builder object to allow for chaining of calls to set methods
+         */
+        public Builder setMessage(int messageId) {
+            P.mMessage = P.mContext.getText(messageId);
+            return this;
+        }
+        
+        /**
+         * Set the message to display.
+          *
+         * @return This Builder object to allow for chaining of calls to set methods
+         */
+        public Builder setMessage(CharSequence message) {
+            P.mMessage = message;
+            return this;
+        }
+        
+        /**
+         * Set the resource id of the {@link Drawable} to be used in the title.
+         *
+         * @return This Builder object to allow for chaining of calls to set methods
+         */
+        public Builder setIcon(int iconId) {
+            P.mIconId = iconId;
+            return this;
+        }
+        
+        /**
+         * Set the {@link Drawable} to be used in the title.
+          *
+         * @return This Builder object to allow for chaining of calls to set methods
+         */
+        public Builder setIcon(Drawable icon) {
+            P.mIcon = icon;
+            return this;
+        }
+        
+        /**
+         * Set a listener to be invoked when the positive button of the dialog is pressed.
+         * @param textId The resource id of the text to display in the positive button
+         * @param listener The {@link DialogInterface.OnClickListener} to use.
+         *
+         * @return This Builder object to allow for chaining of calls to set methods
+         */
+        public Builder setPositiveButton(int textId, final OnClickListener listener) {
+            P.mPositiveButtonText = P.mContext.getText(textId);
+            P.mPositiveButtonListener = listener;
+            return this;
+        }
+        
+        /**
+         * Set a listener to be invoked when the positive button of the dialog is pressed.
+         * @param text The text to display in the positive button
+         * @param listener The {@link DialogInterface.OnClickListener} to use.
+         *
+         * @return This Builder object to allow for chaining of calls to set methods
+         */
+        public Builder setPositiveButton(CharSequence text, final OnClickListener listener) {
+            P.mPositiveButtonText = text;
+            P.mPositiveButtonListener = listener;
+            return this;
+        }
+        
+        /**
+         * Set a listener to be invoked when the negative button of the dialog is pressed.
+         * @param textId The resource id of the text to display in the negative button
+         * @param listener The {@link DialogInterface.OnClickListener} to use.
+         *
+         * @return This Builder object to allow for chaining of calls to set methods
+         */
+        public Builder setNegativeButton(int textId, final OnClickListener listener) {
+            P.mNegativeButtonText = P.mContext.getText(textId);
+            P.mNegativeButtonListener = listener;
+            return this;
+        }
+        
+        /**
+         * Set a listener to be invoked when the negative button of the dialog is pressed.
+         * @param text The text to display in the negative button
+         * @param listener The {@link DialogInterface.OnClickListener} to use.
+         *
+         * @return This Builder object to allow for chaining of calls to set methods
+         */
+        public Builder setNegativeButton(CharSequence text, final OnClickListener listener) {
+            P.mNegativeButtonText = text;
+            P.mNegativeButtonListener = listener;
+            return this;
+        }
+        
+        /**
+         * Set a listener to be invoked when the neutral button of the dialog is pressed.
+         * @param textId The resource id of the text to display in the neutral button
+         * @param listener The {@link DialogInterface.OnClickListener} to use.
+         *
+         * @return This Builder object to allow for chaining of calls to set methods
+         */
+        public Builder setNeutralButton(int textId, final OnClickListener listener) {
+            P.mNeutralButtonText = P.mContext.getText(textId);
+            P.mNeutralButtonListener = listener;
+            return this;
+        }
+        
+        /**
+         * Set a listener to be invoked when the neutral button of the dialog is pressed.
+         * @param text The text to display in the neutral button
+         * @param listener The {@link DialogInterface.OnClickListener} to use.
+         *
+         * @return This Builder object to allow for chaining of calls to set methods
+         */
+        public Builder setNeutralButton(CharSequence text, final OnClickListener listener) {
+            P.mNeutralButtonText = text;
+            P.mNeutralButtonListener = listener;
+            return this;
+        }
+        
+        /**
+         * Sets whether the dialog is cancelable or not default is true.
+         *
+         * @return This Builder object to allow for chaining of calls to set methods
+         */
+        public Builder setCancelable(boolean cancelable) {
+            P.mCancelable = cancelable;
+            return this;
+        }
+        
+        /**
+         * Sets the callback that will be called if the dialog is canceled.
+         * @see #setCancelable(boolean)
+         *
+         * @return This Builder object to allow for chaining of calls to set methods
+         */
+        public Builder setOnCancelListener(OnCancelListener onCancelListener) {
+            P.mOnCancelListener = onCancelListener;
+            return this;
+        }
+        
+        /**
+         * Sets the callback that will be called if a key is dispatched to the dialog.
+         *
+         * @return This Builder object to allow for chaining of calls to set methods
+         */
+        public Builder setOnKeyListener(OnKeyListener onKeyListener) {
+            P.mOnKeyListener = onKeyListener;
+            return this;
+        }
+        
+        /**
+         * Set a list of items to be displayed in the dialog as the content, you will be notified of the
+         * selected item via the supplied listener. This should be an array type i.e. R.array.foo
+         *
+         * @return This Builder object to allow for chaining of calls to set methods
+         */
+        public Builder setItems(int itemsId, final OnClickListener listener) {
+            P.mItems = P.mContext.getResources().getTextArray(itemsId);
+            P.mOnClickListener = listener;
+            return this;
+        }
+        
+        /**
+         * Set a list of items to be displayed in the dialog as the content, you will be notified of the
+         * selected item via the supplied listener.
+         *
+         * @return This Builder object to allow for chaining of calls to set methods
+         */
+        public Builder setItems(CharSequence[] items, final OnClickListener listener) {
+            P.mItems = items;
+            P.mOnClickListener = listener;
+            return this;
+        }
+        
+        /**
+         * Set a list of items, which are supplied by the given {@link ListAdapter}, to be
+         * displayed in the dialog as the content, you will be notified of the
+         * selected item via the supplied listener.
+         * 
+         * @param adapter The {@link ListAdapter} to supply the list of items
+         * @param listener The listener that will be called when an item is clicked.
+         *
+         * @return This Builder object to allow for chaining of calls to set methods
+         */
+        public Builder setAdapter(final ListAdapter adapter, final OnClickListener listener) {
+            P.mAdapter = adapter;
+            P.mOnClickListener = listener;
+            return this;
+        }
+        
+        /**
+         * Set a list of items, which are supplied by the given {@link Cursor}, to be
+         * displayed in the dialog as the content, you will be notified of the
+         * selected item via the supplied listener.
+         * 
+         * @param cursor The {@link Cursor} to supply the list of items
+         * @param listener The listener that will be called when an item is clicked.
+         * @param labelColumn The column name on the cursor containing the string to display
+         *          in the label.
+         *
+         * @return This Builder object to allow for chaining of calls to set methods
+         */
+        public Builder setCursor(final Cursor cursor, final OnClickListener listener,
+                String labelColumn) {
+            P.mCursor = cursor;
+            P.mLabelColumn = labelColumn;
+            P.mOnClickListener = listener;
+            return this;
+        }
+        
+        /**
+         * Set a list of items to be displayed in the dialog as the content,
+         * you will be notified of the selected item via the supplied listener.
+         * This should be an array type, e.g. R.array.foo. The list will have
+         * a check mark displayed to the right of the text for each checked
+         * item. Clicking on an item in the list will not dismiss the dialog.
+         * Clicking on a button will dismiss the dialog.
+         * 
+         * @param itemsId the resource id of an array i.e. R.array.foo
+         * @param checkedItems specifies which items are checked. It should be null in which case no
+         *        items are checked. If non null it must be exactly the same length as the array of
+         *        items.
+         * @param listener notified when an item on the list is clicked. The dialog will not be
+         *        dismissed when an item is clicked. It will only be dismissed if clicked on a
+         *        button, if no buttons are supplied it's up to the user to dismiss the dialog.
+         *
+         * @return This Builder object to allow for chaining of calls to set methods
+         */
+        public Builder setMultiChoiceItems(int itemsId, boolean[] checkedItems, 
+                final OnMultiChoiceClickListener listener) {
+            P.mItems = P.mContext.getResources().getTextArray(itemsId);
+            P.mOnCheckboxClickListener = listener;
+            P.mCheckedItems = checkedItems;
+            P.mIsMultiChoice = true;
+            return this;
+        }
+        
+        /**
+         * Set a list of items to be displayed in the dialog as the content,
+         * you will be notified of the selected item via the supplied listener.
+         * The list will have a check mark displayed to the right of the text
+         * for each checked item. Clicking on an item in the list will not
+         * dismiss the dialog. Clicking on a button will dismiss the dialog.
+         * 
+         * @param items the text of the items to be displayed in the list.
+         * @param checkedItems specifies which items are checked. It should be null in which case no
+         *        items are checked. If non null it must be exactly the same length as the array of
+         *        items.
+         * @param listener notified when an item on the list is clicked. The dialog will not be
+         *        dismissed when an item is clicked. It will only be dismissed if clicked on a
+         *        button, if no buttons are supplied it's up to the user to dismiss the dialog.
+         *
+         * @return This Builder object to allow for chaining of calls to set methods
+         */
+        public Builder setMultiChoiceItems(CharSequence[] items, boolean[] checkedItems, 
+                final OnMultiChoiceClickListener listener) {
+            P.mItems = items;
+            P.mOnCheckboxClickListener = listener;
+            P.mCheckedItems = checkedItems;
+            P.mIsMultiChoice = true;
+            return this;
+        }
+        
+        /**
+         * Set a list of items to be displayed in the dialog as the content,
+         * you will be notified of the selected item via the supplied listener.
+         * The list will have a check mark displayed to the right of the text
+         * for each checked item. Clicking on an item in the list will not
+         * dismiss the dialog. Clicking on a button will dismiss the dialog.
+         * 
+         * @param cursor the cursor used to provide the items.
+         * @param isCheckedColumn specifies the column name on the cursor to use to determine
+         *        whether a checkbox is checked or not. It must return an integer value where 1
+         *        means checked and 0 means unchecked.
+         * @param labelColumn The column name on the cursor containing the string to display in the
+         *        label.
+         * @param listener notified when an item on the list is clicked. The dialog will not be
+         *        dismissed when an item is clicked. It will only be dismissed if clicked on a
+         *        button, if no buttons are supplied it's up to the user to dismiss the dialog.
+         *
+         * @return This Builder object to allow for chaining of calls to set methods
+         */
+        public Builder setMultiChoiceItems(Cursor cursor, String isCheckedColumn, String labelColumn, 
+                final OnMultiChoiceClickListener listener) {
+            P.mCursor = cursor;
+            P.mOnCheckboxClickListener = listener;
+            P.mIsCheckedColumn = isCheckedColumn;
+            P.mLabelColumn = labelColumn;
+            P.mIsMultiChoice = true;
+            return this;
+        }
+        
+        /**
+         * Set a list of items to be displayed in the dialog as the content, you will be notified of
+         * the selected item via the supplied listener. This should be an array type i.e.
+         * R.array.foo The list will have a check mark displayed to the right of the text for the
+         * checked item. Clicking on an item in the list will not dismiss the dialog. Clicking on a
+         * button will dismiss the dialog.
+         * 
+         * @param itemsId the resource id of an array i.e. R.array.foo
+         * @param checkedItem specifies which item is checked. If -1 no items are checked.
+         * @param listener notified when an item on the list is clicked. The dialog will not be
+         *        dismissed when an item is clicked. It will only be dismissed if clicked on a
+         *        button, if no buttons are supplied it's up to the user to dismiss the dialog.
+         *
+         * @return This Builder object to allow for chaining of calls to set methods
+         */
+        public Builder setSingleChoiceItems(int itemsId, int checkedItem, 
+                final OnClickListener listener) {
+            P.mItems = P.mContext.getResources().getTextArray(itemsId);
+            P.mOnClickListener = listener;
+            P.mCheckedItem = checkedItem;
+            P.mIsSingleChoice = true;
+            return this;
+        }
+        
+        /**
+         * Set a list of items to be displayed in the dialog as the content, you will be notified of
+         * the selected item via the supplied listener. The list will have a check mark displayed to
+         * the right of the text for the checked item. Clicking on an item in the list will not
+         * dismiss the dialog. Clicking on a button will dismiss the dialog.
+         * 
+         * @param cursor the cursor to retrieve the items from.
+         * @param checkedItem specifies which item is checked. If -1 no items are checked.
+         * @param labelColumn The column name on the cursor containing the string to display in the
+         *        label.
+         * @param listener notified when an item on the list is clicked. The dialog will not be
+         *        dismissed when an item is clicked. It will only be dismissed if clicked on a
+         *        button, if no buttons are supplied it's up to the user to dismiss the dialog.
+         *
+         * @return This Builder object to allow for chaining of calls to set methods
+         */
+        public Builder setSingleChoiceItems(Cursor cursor, int checkedItem, String labelColumn, 
+                final OnClickListener listener) {
+            P.mCursor = cursor;
+            P.mOnClickListener = listener;
+            P.mCheckedItem = checkedItem;
+            P.mLabelColumn = labelColumn;
+            P.mIsSingleChoice = true;
+            return this;
+        }
+        
+        /**
+         * Set a list of items to be displayed in the dialog as the content, you will be notified of
+         * the selected item via the supplied listener. The list will have a check mark displayed to
+         * the right of the text for the checked item. Clicking on an item in the list will not
+         * dismiss the dialog. Clicking on a button will dismiss the dialog.
+         * 
+         * @param items the items to be displayed.
+         * @param checkedItem specifies which item is checked. If -1 no items are checked.
+         * @param listener notified when an item on the list is clicked. The dialog will not be
+         *        dismissed when an item is clicked. It will only be dismissed if clicked on a
+         *        button, if no buttons are supplied it's up to the user to dismiss the dialog.
+         *
+         * @return This Builder object to allow for chaining of calls to set methods
+         */
+        public Builder setSingleChoiceItems(CharSequence[] items, int checkedItem, final OnClickListener listener) {
+            P.mItems = items;
+            P.mOnClickListener = listener;
+            P.mCheckedItem = checkedItem;
+            P.mIsSingleChoice = true;
+            return this;
+        } 
+        
+        /**
+         * Set a list of items to be displayed in the dialog as the content, you will be notified of
+         * the selected item via the supplied listener. The list will have a check mark displayed to
+         * the right of the text for the checked item. Clicking on an item in the list will not
+         * dismiss the dialog. Clicking on a button will dismiss the dialog.
+         * 
+         * @param adapter The {@link ListAdapter} to supply the list of items
+         * @param checkedItem specifies which item is checked. If -1 no items are checked.
+         * @param listener notified when an item on the list is clicked. The dialog will not be
+         *        dismissed when an item is clicked. It will only be dismissed if clicked on a
+         *        button, if no buttons are supplied it's up to the user to dismiss the dialog.
+         *
+         * @return This Builder object to allow for chaining of calls to set methods
+         */
+        public Builder setSingleChoiceItems(ListAdapter adapter, int checkedItem, final OnClickListener listener) {
+            P.mAdapter = adapter;
+            P.mOnClickListener = listener;
+            P.mCheckedItem = checkedItem;
+            P.mIsSingleChoice = true;
+            return this;
+        }
+        
+        /**
+         * Sets a listener to be invoked when an item in the list is selected.
+         * 
+         * @param listener The listener to be invoked.
+         * @see AdapterView#setOnItemSelectedListener(android.widget.AdapterView.OnItemSelectedListener)
+         *
+         * @return This Builder object to allow for chaining of calls to set methods
+         */
+        public Builder setOnItemSelectedListener(final AdapterView.OnItemSelectedListener listener) {
+            P.mOnItemSelectedListener = listener;
+            return this;
+        }
+        
+        /**
+         * Set a custom view to be the contents of the Dialog. If the supplied view is an instance
+         * of a {@link ListView} the light background will be used.
+         *
+         * @param view The view to use as the contents of the Dialog.
+         * 
+         * @return This Builder object to allow for chaining of calls to set methods
+         */
+        public Builder setView(View view) {
+            P.mView = view;
+            P.mViewSpacingSpecified = false;
+            return this;
+        }
+        
+        /**
+         * Set a custom view to be the contents of the Dialog, specifying the
+         * spacing to appear around that view. If the supplied view is an
+         * instance of a {@link ListView} the light background will be used.
+         * 
+         * @param view The view to use as the contents of the Dialog.
+         * @param viewSpacingLeft Spacing between the left edge of the view and
+         *        the dialog frame
+         * @param viewSpacingTop Spacing between the top edge of the view and
+         *        the dialog frame
+         * @param viewSpacingRight Spacing between the right edge of the view
+         *        and the dialog frame
+         * @param viewSpacingBottom Spacing between the bottom edge of the view
+         *        and the dialog frame
+         * @return This Builder object to allow for chaining of calls to set
+         *         methods
+         *         
+         * @hide pending API review
+         */
+        public Builder setView(View view, int viewSpacingLeft, int viewSpacingTop,
+                int viewSpacingRight, int viewSpacingBottom) {
+            P.mView = view;
+            P.mViewSpacingSpecified = true;
+            P.mViewSpacingLeft = viewSpacingLeft;
+            P.mViewSpacingTop = viewSpacingTop;
+            P.mViewSpacingRight = viewSpacingRight;
+            P.mViewSpacingBottom = viewSpacingBottom;
+            return this;
+        }
+        
+        /**
+         * Sets the Dialog to use the inverse background, regardless of what the
+         * contents is.
+         * 
+         * @param useInverseBackground Whether to use the inverse background
+         * 
+         * @return This Builder object to allow for chaining of calls to set methods
+         */
+        public Builder setInverseBackgroundForced(boolean useInverseBackground) {
+            P.mForceInverseBackground = useInverseBackground;
+            return this;
+        }
+        
+        /**
+         * Creates a {@link AlertDialog} with the arguments supplied to this builder. It does not
+         * {@link Dialog#show()} the dialog. This allows the user to do any extra processing
+         * before displaying the dialog. Use {@link #show()} if you don't have any other processing
+         * to do and want this to be created and displayed.
+         */
+        public AlertDialog create() {
+            final AlertDialog dialog = new AlertDialog(P.mContext);
+            P.apply(dialog.mAlert);
+            dialog.setCancelable(P.mCancelable);
+            dialog.setOnCancelListener(P.mOnCancelListener);
+            if (P.mOnKeyListener != null) {
+                dialog.setOnKeyListener(P.mOnKeyListener);
+            }
+            return dialog;
+        }
+
+        /**
+         * Creates a {@link AlertDialog} with the arguments supplied to this builder and
+         * {@link Dialog#show()}'s the dialog.
+         */
+        public AlertDialog show() {
+            AlertDialog dialog = create();
+            dialog.show();
+            return dialog;
+        }
+    }
+    
+}
diff --git a/core/java/android/app/AliasActivity.java b/core/java/android/app/AliasActivity.java
new file mode 100644
index 0000000..4f91e02
--- /dev/null
+++ b/core/java/android/app/AliasActivity.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2007 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 android.app;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.pm.PackageManager;
+import android.content.res.XmlResourceParser;
+import android.os.Bundle;
+import android.util.AttributeSet;
+import android.util.Xml;
+import com.android.internal.util.XmlUtils;
+
+import java.io.IOException;
+
+/**
+ * Stub activity that launches another activity (and then finishes itself)
+ * based on information in its component's manifest meta-data.  This is a
+ * simple way to implement an alias-like mechanism.
+ * 
+ * To use this activity, you should include in the manifest for the associated
+ * component an entry named "android.app.alias".  It is a reference to an XML
+ * resource describing an intent that launches the real application.
+ */
+public class AliasActivity extends Activity {
+    /**
+     * This is the name under which you should store in your component the
+     * meta-data information about the alias.  It is a reference to an XML
+     * resource describing an intent that launches the real application.
+     * {@hide}
+     */
+    public final String ALIAS_META_DATA = "android.app.alias";
+    
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        
+        XmlResourceParser parser = null;
+        try {
+            ActivityInfo ai = getPackageManager().getActivityInfo(
+                    getComponentName(), PackageManager.GET_META_DATA);
+            parser = ai.loadXmlMetaData(getPackageManager(),
+                    ALIAS_META_DATA);
+            if (parser == null) {
+                throw new RuntimeException("Alias requires a meta-data field "
+                        + ALIAS_META_DATA);
+            }
+            
+            Intent intent = parseAlias(parser);
+            if (intent == null) {
+                throw new RuntimeException(
+                        "No <intent> tag found in alias description");
+            }
+            
+            startActivity(intent);
+            finish();
+            
+        } catch (PackageManager.NameNotFoundException e) {
+            throw new RuntimeException("Error parsing alias", e);
+        } catch (XmlPullParserException e) {
+            throw new RuntimeException("Error parsing alias", e);
+        } catch (IOException e) {
+            throw new RuntimeException("Error parsing alias", e);
+        } finally {
+            if (parser != null) parser.close();
+        }
+    }
+
+    private Intent parseAlias(XmlPullParser parser)
+            throws XmlPullParserException, IOException {
+        AttributeSet attrs = Xml.asAttributeSet(parser);
+        
+        Intent intent = null;
+        
+        int type;
+        while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
+                && type != XmlPullParser.START_TAG) {
+        }
+        
+        String nodeName = parser.getName();
+        if (!"alias".equals(nodeName)) {
+            throw new RuntimeException(
+                    "Alias meta-data must start with <alias> tag; found"
+                    + nodeName + " at " + parser.getPositionDescription());
+        }
+        
+        int outerDepth = parser.getDepth();
+        while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
+               && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
+            if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+                continue;
+            }
+
+            nodeName = parser.getName();
+            if ("intent".equals(nodeName)) {
+                Intent gotIntent = Intent.parseIntent(getResources(), parser, attrs);
+                if (intent == null) intent = gotIntent;
+            } else {
+                XmlUtils.skipCurrentTag(parser);
+            }
+        }
+        
+        return intent;
+    }
+    
+}
diff --git a/core/java/android/app/Application.java b/core/java/android/app/Application.java
new file mode 100644
index 0000000..45ce860
--- /dev/null
+++ b/core/java/android/app/Application.java
@@ -0,0 +1,74 @@
+/*
+ * 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 android.app;
+
+import android.content.ComponentCallbacks;
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.content.res.Configuration;
+
+/**
+ * Base class for those who need to maintain global application state. You can
+ * provide your own implementation by specifying its name in your
+ * AndroidManifest.xml's &lt;application&gt; tag, which will cause that class
+ * to be instantiated for you when the process for your application/package is
+ * created.
+ */
+public class Application extends ContextWrapper implements ComponentCallbacks {
+    
+    public Application() {
+        super(null);
+    }
+
+    /**
+     * Called when the application is starting, before any other application
+     * objects have been created.  Implementations should be as quick as
+     * possible (for example using lazy initialization of state) since the time
+     * spent in this function directly impacts the performance of starting the
+     * first activity, service, or receiver in a process.
+     * If you override this method, be sure to call super.onCreate().
+     */
+    public void onCreate() {
+    }
+
+    /**
+     * Called when the application is stopping.  There are no more application
+     * objects running and the process will exit.  <em>Note: never depend on
+     * this method being called; in many cases an unneeded application process
+     * will simply be killed by the kernel without executing any application
+     * code.</em>
+     * If you override this method, be sure to call super.onTerminate().
+     */
+    public void onTerminate() {
+    }
+    
+    public void onConfigurationChanged(Configuration newConfig) {
+    }
+    
+    public void onLowMemory() {
+    }
+    
+    // ------------------ Internal API ------------------
+    
+    /**
+     * @hide
+     */
+    /* package */ final void attach(Context context) {
+        attachBaseContext(context);
+    }
+
+}
diff --git a/core/java/android/app/ApplicationContext.java b/core/java/android/app/ApplicationContext.java
new file mode 100644
index 0000000..3b5ad86
--- /dev/null
+++ b/core/java/android/app/ApplicationContext.java
@@ -0,0 +1,2765 @@
+/*
+ * 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 android.app;
+
+import com.google.android.collect.Maps;
+import com.android.internal.util.XmlUtils;
+
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.IBluetoothDevice;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.content.IContentProvider;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.ReceiverCallNotAllowedException;
+import android.content.ServiceConnection;
+import android.content.SharedPreferences;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.ComponentInfo;
+import android.content.pm.IPackageDataObserver;
+import android.content.pm.IPackageDeleteObserver;
+import android.content.pm.IPackageStatsObserver;
+import android.content.pm.IPackageInstallObserver;
+import android.content.pm.IPackageManager;
+import android.content.pm.InstrumentationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PermissionGroupInfo;
+import android.content.pm.PermissionInfo;
+import android.content.pm.ProviderInfo;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
+import android.content.res.AssetManager;
+import android.content.res.Resources;
+import android.content.res.XmlResourceParser;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteDatabase.CursorFactory;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.hardware.SensorManager;
+import android.location.ILocationManager;
+import android.location.LocationManager;
+import android.media.AudioManager;
+import android.net.ConnectivityManager;
+import android.net.IConnectivityManager;
+import android.net.Uri;
+import android.net.wifi.IWifiManager;
+import android.net.wifi.WifiManager;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.Looper;
+import android.os.RemoteException;
+import android.os.FileUtils;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.IPowerManager;
+import android.os.ParcelFileDescriptor;
+import android.os.PowerManager;
+import android.os.Process;
+import android.os.ServiceManager;
+import android.os.Vibrator;
+import android.os.FileUtils.FileStatus;
+import android.telephony.TelephonyManager;
+import android.text.ClipboardManager;
+import android.util.AndroidRuntimeException;
+import android.util.Log;
+import android.view.ContextThemeWrapper;
+import android.view.LayoutInflater;
+import android.view.WindowManagerImpl;
+import android.view.inputmethod.InputMethodManager;
+
+import com.android.internal.policy.PolicyManager;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+
+import org.xmlpull.v1.XmlPullParserException;
+
+class ReceiverRestrictedContext extends ContextWrapper {
+    ReceiverRestrictedContext(Context base) {
+        super(base);
+    }
+
+    @Override
+    public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter) {
+        return registerReceiver(receiver, filter, null, null);
+    }
+
+    @Override
+    public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter,
+            String broadcastPermission, Handler scheduler) {
+        throw new ReceiverCallNotAllowedException(
+                "IntentReceiver components are not allowed to register to receive intents");
+        //ex.fillInStackTrace();
+        //Log.e("IntentReceiver", ex.getMessage(), ex);
+        //return mContext.registerReceiver(receiver, filter, broadcastPermission,
+        //        scheduler);
+    }
+
+    @Override
+    public boolean bindService(Intent service, ServiceConnection conn, int flags) {
+        throw new ReceiverCallNotAllowedException(
+                "IntentReceiver components are not allowed to bind to services");
+        //ex.fillInStackTrace();
+        //Log.e("IntentReceiver", ex.getMessage(), ex);
+        //return mContext.bindService(service, interfaceName, conn, flags);
+    }
+}
+
+/**
+ * Common implementation of Context API, which Activity and other application
+ * classes inherit.
+ */
+class ApplicationContext extends Context {
+    private final static String TAG = "ApplicationContext";
+    private final static boolean DEBUG_ICONS = false;
+
+    private static final Object sSync = new Object();
+    private static AlarmManager sAlarmManager;
+    private static PowerManager sPowerManager;
+    private static ConnectivityManager sConnectivityManager;
+    private static WifiManager sWifiManager;
+    private static LocationManager sLocationManager;
+    private static boolean sIsBluetoothDeviceCached = false;
+    private static BluetoothDevice sBluetoothDevice;
+    private static IWallpaperService sWallpaperService;
+    private static final HashMap<File, SharedPreferencesImpl> sSharedPrefs =
+            new HashMap<File, SharedPreferencesImpl>();
+
+    private AudioManager mAudioManager;
+    /*package*/ ActivityThread.PackageInfo mPackageInfo;
+    private Resources mResources;
+    /*package*/ ActivityThread mMainThread;
+    private Context mOuterContext;
+    private IBinder mActivityToken = null;
+    private ApplicationContentResolver mContentResolver;
+    private int mThemeResource = 0;
+    private Resources.Theme mTheme = null;
+    private PackageManager mPackageManager;
+    private NotificationManager mNotificationManager = null;
+    private ActivityManager mActivityManager = null;
+    private Context mReceiverRestrictedContext = null;
+    private SearchManager mSearchManager = null;
+    private SensorManager mSensorManager = null;
+    private Vibrator mVibrator = null;
+    private LayoutInflater mLayoutInflater = null;
+    private StatusBarManager mStatusBarManager = null;
+    private TelephonyManager mTelephonyManager = null;
+    private ClipboardManager mClipboardManager = null;
+
+    private final Object mSync = new Object();
+
+    private File mDatabasesDir;
+    private File mPreferencesDir;
+    private File mFilesDir;
+    
+
+    private File mCacheDir;
+    
+    private Drawable mWallpaper;
+    private IWallpaperServiceCallback mWallpaperCallback = null;
+        
+    private static long sInstanceCount = 0;
+
+    private static final String[] EMPTY_FILE_LIST = {};
+
+    @Override
+    protected void finalize() throws Throwable {
+        super.finalize();
+        --sInstanceCount;
+    }
+
+    public static long getInstanceCount() {
+        return sInstanceCount;
+    }
+
+    @Override
+    public AssetManager getAssets() {
+        return mResources.getAssets();
+    }
+
+    @Override
+    public Resources getResources() {
+        return mResources;
+    }
+
+    @Override
+    public PackageManager getPackageManager() {
+        if (mPackageManager != null) {
+            return mPackageManager;
+        }
+
+        IPackageManager pm = ActivityThread.getPackageManager();
+        if (pm != null) {
+            // Doesn't matter if we make more than one instance.
+            return (mPackageManager = new ApplicationPackageManager(this, pm));
+        }
+
+        return null;
+    }
+
+    @Override
+    public ContentResolver getContentResolver() {
+        return mContentResolver;
+    }
+
+    @Override
+    public Looper getMainLooper() {
+        return mMainThread.getLooper();
+    }
+    
+    @Override
+    public Context getApplicationContext() {
+        return mMainThread.getApplication();
+    }
+    
+    @Override
+    public void setTheme(int resid) {
+        mThemeResource = resid;
+    }
+    
+    @Override
+    public Resources.Theme getTheme() {
+        if (mTheme == null) {
+            if (mThemeResource == 0) {
+                mThemeResource = com.android.internal.R.style.Theme;
+            }
+            mTheme = mResources.newTheme();
+            mTheme.applyStyle(mThemeResource, true);
+        }
+        return mTheme;
+    }
+
+    @Override
+    public ClassLoader getClassLoader() {
+        return mPackageInfo != null ?
+                mPackageInfo.getClassLoader() : ClassLoader.getSystemClassLoader();
+    }
+
+    @Override
+    public String getPackageName() {
+        if (mPackageInfo != null) {
+            return mPackageInfo.getPackageName();
+        }
+        throw new RuntimeException("Not supported in system context");
+    }
+
+    @Override
+    public String getPackageResourcePath() {
+        if (mPackageInfo != null) {
+            return mPackageInfo.getResDir();
+        }
+        throw new RuntimeException("Not supported in system context");
+    }
+
+    @Override
+    public String getPackageCodePath() {
+        if (mPackageInfo != null) {
+            return mPackageInfo.getAppDir();
+        }
+        throw new RuntimeException("Not supported in system context");
+    }
+    
+    private static File makeBackupFile(File prefsFile) {
+        return new File(prefsFile.getPath() + ".bak");
+    }
+
+    @Override
+    public SharedPreferences getSharedPreferences(String name, int mode) {
+        SharedPreferencesImpl sp;
+        File f = makeFilename(getPreferencesDir(), name + ".xml");
+        synchronized (sSharedPrefs) {
+            sp = sSharedPrefs.get(f);
+            if (sp != null && !sp.hasFileChanged()) {
+                //Log.i(TAG, "Returning existing prefs " + name + ": " + sp);
+                return sp;
+            }
+        }
+        
+        FileInputStream str = null;
+        File backup = makeBackupFile(f);
+        if (backup.exists()) {
+            f.delete();
+            backup.renameTo(f);
+        }
+
+        // Debugging
+        if (f.exists() && !f.canRead()) {
+            Log.w(TAG, "Attempt to read preferences file " + f + " without permission");
+        }
+        
+        Map map = null;
+        if (f.exists() && f.canRead()) {
+            try {
+                str = new FileInputStream(f);
+                map = XmlUtils.readMapXml(str);
+                str.close();
+            } catch (org.xmlpull.v1.XmlPullParserException e) {
+                Log.w(TAG, "getSharedPreferences", e);
+            } catch (FileNotFoundException e) {
+                Log.w(TAG, "getSharedPreferences", e);
+            } catch (IOException e) {
+                Log.w(TAG, "getSharedPreferences", e);
+            }
+        }
+
+        synchronized (sSharedPrefs) {
+            if (sp != null) {
+                //Log.i(TAG, "Updating existing prefs " + name + " " + sp + ": " + map);
+                sp.replace(map);
+            } else {
+                sp = sSharedPrefs.get(f);
+                if (sp == null) {
+                    sp = new SharedPreferencesImpl(f, mode, map);
+                    sSharedPrefs.put(f, sp);
+                }
+            }
+            return sp;
+        }
+    }
+
+    private File getPreferencesDir() {
+        synchronized (mSync) {
+            if (mPreferencesDir == null) {
+                mPreferencesDir = new File(getDataDirFile(), "shared_prefs");
+            }
+            return mPreferencesDir;
+        }
+    }
+
+    @Override
+    public FileInputStream openFileInput(String name)
+        throws FileNotFoundException {
+        File f = makeFilename(getFilesDir(), name);
+        return new FileInputStream(f);
+    }
+
+    @Override
+    public FileOutputStream openFileOutput(String name, int mode)
+        throws FileNotFoundException {
+        final boolean append = (mode&MODE_APPEND) != 0;
+        File f = makeFilename(getFilesDir(), name);
+        try {
+            FileOutputStream fos = new FileOutputStream(f, append);
+            setFilePermissionsFromMode(f.getPath(), mode, 0);
+            return fos;
+        } catch (FileNotFoundException e) {
+        }
+
+        File parent = f.getParentFile();
+        parent.mkdir();
+        FileUtils.setPermissions(
+            parent.getPath(),
+            FileUtils.S_IRWXU|FileUtils.S_IRWXG|FileUtils.S_IXOTH,
+            -1, -1);
+        FileOutputStream fos = new FileOutputStream(f, append);
+        setFilePermissionsFromMode(f.getPath(), mode, 0);
+        return fos;
+    }
+
+    @Override
+    public boolean deleteFile(String name) {
+        File f = makeFilename(getFilesDir(), name);
+        return f.delete();
+    }
+
+    @Override
+    public File getFilesDir() {
+        synchronized (mSync) {
+            if (mFilesDir == null) {
+                mFilesDir = new File(getDataDirFile(), "files");
+            }
+            if (!mFilesDir.exists()) {
+                if(!mFilesDir.mkdirs()) {
+                    Log.w(TAG, "Unable to create files directory");
+                    return null;
+                }
+                FileUtils.setPermissions(
+                        mFilesDir.getPath(),
+                        FileUtils.S_IRWXU|FileUtils.S_IRWXG|FileUtils.S_IXOTH,
+                        -1, -1);
+            }
+            return mFilesDir;
+        }
+    }
+    
+    @Override
+    public File getCacheDir() {
+        synchronized (mSync) {
+            if (mCacheDir == null) {
+                mCacheDir = new File(getDataDirFile(), "cache");
+            }
+            if (!mCacheDir.exists()) {
+                if(!mCacheDir.mkdirs()) {
+                    Log.w(TAG, "Unable to create cache directory");
+                    return null;
+                }
+                FileUtils.setPermissions(
+                        mCacheDir.getPath(),
+                        FileUtils.S_IRWXU|FileUtils.S_IRWXG|FileUtils.S_IXOTH,
+                        -1, -1);
+            }
+        }
+        return mCacheDir;
+    }
+    
+
+    @Override
+    public File getFileStreamPath(String name) {
+        return makeFilename(getFilesDir(), name);
+    }
+
+    @Override
+    public String[] fileList() {
+        final String[] list = getFilesDir().list();
+        return (list != null) ? list : EMPTY_FILE_LIST;
+    }
+
+    @Override
+    public SQLiteDatabase openOrCreateDatabase(String name, int mode, CursorFactory factory) {
+        File dir = getDatabasesDir();
+        if (!dir.isDirectory() && dir.mkdir()) {
+            FileUtils.setPermissions(dir.getPath(),
+                FileUtils.S_IRWXU|FileUtils.S_IRWXG|FileUtils.S_IXOTH,
+                -1, -1);
+        }
+
+        File f = makeFilename(dir, name);
+        SQLiteDatabase db = SQLiteDatabase.openOrCreateDatabase(f, factory);
+        setFilePermissionsFromMode(f.getPath(), mode, 0);
+        return db;
+    }
+
+    @Override
+    public boolean deleteDatabase(String name) {
+        try {
+            File f = makeFilename(getDatabasesDir(), name);
+            return f.delete();
+        } catch (Exception e) {
+        }
+        return false;
+    }
+
+    @Override
+    public File getDatabasePath(String name) {
+        return makeFilename(getDatabasesDir(), name);
+    }
+
+    @Override
+    public String[] databaseList() {
+        final String[] list = getDatabasesDir().list();
+        return (list != null) ? list : EMPTY_FILE_LIST;
+    }
+
+    
+    private File getDatabasesDir() {
+        synchronized (mSync) {
+            if (mDatabasesDir == null) {
+                mDatabasesDir = new File(getDataDirFile(), "databases");
+            }
+            if (mDatabasesDir.getPath().equals("databases")) {
+                mDatabasesDir = new File("/data/system");
+            }
+            return mDatabasesDir;
+        }
+    }
+    
+    @Override
+    public Drawable getWallpaper() {
+        Drawable dr = peekWallpaper();
+        return dr != null ? dr : getResources().getDrawable(
+                com.android.internal.R.drawable.default_wallpaper);
+    }
+
+    @Override
+    public synchronized Drawable peekWallpaper() {
+        if (mWallpaper != null) {
+            return mWallpaper;
+        }
+        mWallpaperCallback = new WallpaperCallback(this);
+        mWallpaper = getCurrentWallpaperLocked();
+        return mWallpaper;
+    }
+
+    private Drawable getCurrentWallpaperLocked() {
+        try {
+            ParcelFileDescriptor fd = getWallpaperService().getWallpaper(mWallpaperCallback);
+            if (fd != null) {
+                Bitmap bm = BitmapFactory.decodeFileDescriptor(fd.getFileDescriptor());
+                if (bm != null) {
+                    return new BitmapDrawable(bm);
+                }
+            }
+        } catch (RemoteException e) {
+        }
+        return null;
+    }
+
+    @Override
+    public int getWallpaperDesiredMinimumWidth() {
+        try {
+            return getWallpaperService().getWidthHint();
+        } catch (RemoteException e) {
+            // Shouldn't happen!
+            return 0;
+        }
+    }
+
+    @Override
+    public int getWallpaperDesiredMinimumHeight() {
+        try {
+            return getWallpaperService().getHeightHint();
+        } catch (RemoteException e) {
+            // Shouldn't happen!
+            return 0;
+        }
+    }
+
+    @Override
+    public void setWallpaper(Bitmap bitmap) throws IOException  {
+        try {
+            ParcelFileDescriptor fd = getWallpaperService().setWallpaper();
+            if (fd == null) {
+                return;
+            }
+            FileOutputStream fos = null;
+            try {
+                fos = new ParcelFileDescriptor.AutoCloseOutputStream(fd);
+                bitmap.compress(Bitmap.CompressFormat.PNG, 90, fos);
+            } finally {
+                if (fos != null) {
+                    fos.close();
+                }
+            }
+        } catch (RemoteException e) {
+        }
+    }
+
+    @Override
+    public void setWallpaper(InputStream data) throws IOException {
+        try {
+            ParcelFileDescriptor fd = getWallpaperService().setWallpaper();
+            if (fd == null) {
+                return;
+            }
+            FileOutputStream fos = null;
+            try {
+                fos = new ParcelFileDescriptor.AutoCloseOutputStream(fd);
+                setWallpaper(data, fos);
+            } finally {
+                if (fos != null) {
+                    fos.close();
+                }
+            }
+        } catch (RemoteException e) {
+        }
+    }
+
+    private void setWallpaper(InputStream data, FileOutputStream fos)
+            throws IOException {
+        byte[] buffer = new byte[32768];
+        int amt;
+        while ((amt=data.read(buffer)) > 0) {
+            fos.write(buffer, 0, amt);
+        }
+    }
+
+    @Override
+    public void clearWallpaper() throws IOException {
+        try {
+            /* Set the wallpaper to the default values */
+            ParcelFileDescriptor fd = getWallpaperService().setWallpaper();
+            if (fd != null) {
+                FileOutputStream fos = null;
+                try {
+                    fos = new ParcelFileDescriptor.AutoCloseOutputStream(fd);
+                    setWallpaper(getResources().openRawResource(
+                            com.android.internal.R.drawable.default_wallpaper),
+                            fos);
+                } finally {
+                    if (fos != null) {
+                        fos.close();
+                    }
+                }
+            }
+        } catch (RemoteException e) {
+        }
+    }
+
+    @Override
+    public void startActivity(Intent intent) {
+        if ((intent.getFlags()&Intent.FLAG_ACTIVITY_NEW_TASK) == 0) {
+            throw new AndroidRuntimeException(
+                    "Calling startActivity() from outside of an Activity "
+                    + " context requires the FLAG_ACTIVITY_NEW_TASK flag."
+                    + " Is this really what you want?");
+        }
+        mMainThread.getInstrumentation().execStartActivity(
+            getOuterContext(), mMainThread.getApplicationThread(), null, null, intent, -1);
+    }
+
+    @Override
+    public void sendBroadcast(Intent intent) {
+        String resolvedType = intent.resolveTypeIfNeeded(getContentResolver());
+        try {
+            ActivityManagerNative.getDefault().broadcastIntent(
+                mMainThread.getApplicationThread(), intent, resolvedType, null,
+                Activity.RESULT_OK, null, null, null, false, false);
+        } catch (RemoteException e) {
+        }
+    }
+
+    @Override
+    public void sendBroadcast(Intent intent, String receiverPermission) {
+        String resolvedType = intent.resolveTypeIfNeeded(getContentResolver());
+        try {
+            ActivityManagerNative.getDefault().broadcastIntent(
+                mMainThread.getApplicationThread(), intent, resolvedType, null,
+                Activity.RESULT_OK, null, null, receiverPermission, false, false);
+        } catch (RemoteException e) {
+        }
+    }
+
+    @Override
+    public void sendOrderedBroadcast(Intent intent,
+            String receiverPermission) {
+        String resolvedType = intent.resolveTypeIfNeeded(getContentResolver());
+        try {
+            ActivityManagerNative.getDefault().broadcastIntent(
+                mMainThread.getApplicationThread(), intent, resolvedType, null,
+                Activity.RESULT_OK, null, null, receiverPermission, true, false);
+        } catch (RemoteException e) {
+        }
+    }
+
+    @Override
+    public void sendOrderedBroadcast(Intent intent,
+            String receiverPermission, BroadcastReceiver resultReceiver,
+            Handler scheduler, int initialCode, String initialData,
+            Bundle initialExtras) {
+        IIntentReceiver rd = null;
+        if (resultReceiver != null) {
+            if (mPackageInfo != null) {
+                if (scheduler == null) {
+                    scheduler = mMainThread.getHandler();
+                }
+                rd = mPackageInfo.getReceiverDispatcher(
+                    resultReceiver, getOuterContext(), scheduler,
+                    mMainThread.getInstrumentation(), false);
+            } else {
+                if (scheduler == null) {
+                    scheduler = mMainThread.getHandler();
+                }
+                rd = new ActivityThread.PackageInfo.ReceiverDispatcher(
+                        resultReceiver, getOuterContext(), scheduler, null, false).getIIntentReceiver();
+            }
+        }
+        String resolvedType = intent.resolveTypeIfNeeded(getContentResolver());
+        try {
+            ActivityManagerNative.getDefault().broadcastIntent(
+                mMainThread.getApplicationThread(), intent, resolvedType, rd,
+                initialCode, initialData, initialExtras, receiverPermission,
+                true, false);
+        } catch (RemoteException e) {
+        }
+    }
+
+    @Override
+    public void sendStickyBroadcast(Intent intent) {
+        String resolvedType = intent.resolveTypeIfNeeded(getContentResolver());
+        try {
+            ActivityManagerNative.getDefault().broadcastIntent(
+                mMainThread.getApplicationThread(), intent, resolvedType, null,
+                Activity.RESULT_OK, null, null, null, false, true);
+        } catch (RemoteException e) {
+        }
+    }
+
+    @Override
+    public void removeStickyBroadcast(Intent intent) {
+        String resolvedType = intent.resolveTypeIfNeeded(getContentResolver());
+        if (resolvedType != null) {
+            intent = new Intent(intent);
+            intent.setDataAndType(intent.getData(), resolvedType);
+        }
+        try {
+            ActivityManagerNative.getDefault().unbroadcastIntent(
+                mMainThread.getApplicationThread(), intent);
+        } catch (RemoteException e) {
+        }
+    }
+
+    @Override
+    public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter) {
+        return registerReceiver(receiver, filter, null, null);
+    }
+
+    @Override
+    public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter,
+            String broadcastPermission, Handler scheduler) {
+        return registerReceiverInternal(receiver, filter, broadcastPermission,
+                scheduler, getOuterContext());
+    }
+
+    private Intent registerReceiverInternal(BroadcastReceiver receiver,
+            IntentFilter filter, String broadcastPermission,
+            Handler scheduler, Context context) {
+        IIntentReceiver rd = null;
+        if (receiver != null) {
+            if (mPackageInfo != null && context != null) {
+                if (scheduler == null) {
+                    scheduler = mMainThread.getHandler();
+                }
+                rd = mPackageInfo.getReceiverDispatcher(
+                    receiver, context, scheduler,
+                    mMainThread.getInstrumentation(), true);
+            } else {
+                if (scheduler == null) {
+                    scheduler = mMainThread.getHandler();
+                }
+                rd = new ActivityThread.PackageInfo.ReceiverDispatcher(
+                        receiver, context, scheduler, null, false).getIIntentReceiver();
+            }
+        }
+        try {
+            return ActivityManagerNative.getDefault().registerReceiver(
+                    mMainThread.getApplicationThread(),
+                    rd, filter, broadcastPermission);
+        } catch (RemoteException e) {
+            return null;
+        }
+    }
+
+    @Override
+    public void unregisterReceiver(BroadcastReceiver receiver) {
+        if (mPackageInfo != null) {
+            IIntentReceiver rd = mPackageInfo.forgetReceiverDispatcher(
+                    getOuterContext(), receiver);
+            try {
+                ActivityManagerNative.getDefault().unregisterReceiver(rd);
+            } catch (RemoteException e) {
+            }
+        } else {
+            throw new RuntimeException("Not supported in system context");
+        }
+    }
+
+    @Override
+    public ComponentName startService(Intent service) {
+        try {
+            ComponentName cn = ActivityManagerNative.getDefault().startService(
+                mMainThread.getApplicationThread(), service,
+                service.resolveTypeIfNeeded(getContentResolver()));
+            if (cn != null && cn.getPackageName().equals("!")) {
+                throw new SecurityException(
+                        "Not allowed to start service " + service
+                        + " without permission " + cn.getClassName());
+            }
+            return cn;
+        } catch (RemoteException e) {
+            return null;
+        }
+    }
+
+    @Override
+    public boolean stopService(Intent service) {
+        try {
+            int res = ActivityManagerNative.getDefault().stopService(
+                mMainThread.getApplicationThread(), service,
+                service.resolveTypeIfNeeded(getContentResolver()));
+            if (res < 0) {
+                throw new SecurityException(
+                        "Not allowed to stop service " + service);
+            }
+            return res != 0;
+        } catch (RemoteException e) {
+            return false;
+        }
+    }
+
+    @Override
+    public boolean bindService(Intent service, ServiceConnection conn,
+            int flags) {
+        IServiceConnection sd;
+        if (mPackageInfo != null) {
+            sd = mPackageInfo.getServiceDispatcher(conn, getOuterContext(),
+                    mMainThread.getHandler(), flags);
+        } else {
+            throw new RuntimeException("Not supported in system context");
+        }
+        try {
+            int res = ActivityManagerNative.getDefault().bindService(
+                mMainThread.getApplicationThread(), getActivityToken(),
+                service, service.resolveTypeIfNeeded(getContentResolver()),
+                sd, flags);
+            if (res < 0) {
+                throw new SecurityException(
+                        "Not allowed to bind to service " + service);
+            }
+            return res != 0;
+        } catch (RemoteException e) {
+            return false;
+        }
+    }
+
+    @Override
+    public void unbindService(ServiceConnection conn) {
+        if (mPackageInfo != null) {
+            IServiceConnection sd = mPackageInfo.forgetServiceDispatcher(
+                    getOuterContext(), conn);
+            try {
+                ActivityManagerNative.getDefault().unbindService(sd);
+            } catch (RemoteException e) {
+            }
+        } else {
+            throw new RuntimeException("Not supported in system context");
+        }
+    }
+
+    @Override
+    public boolean startInstrumentation(ComponentName className,
+            String profileFile, Bundle arguments) {
+        try {
+            return ActivityManagerNative.getDefault().startInstrumentation(
+                    className, profileFile, 0, arguments, null);
+        } catch (RemoteException e) {
+            // System has crashed, nothing we can do.
+        }
+        return false;
+    }
+
+    @Override
+    public Object getSystemService(String name) {
+        if (WINDOW_SERVICE.equals(name)) {
+            return WindowManagerImpl.getDefault();
+        } else if (LAYOUT_INFLATER_SERVICE.equals(name)) {
+            synchronized (mSync) {
+                LayoutInflater inflater = mLayoutInflater;
+                if (inflater != null) {
+                    return inflater;
+                }
+                mLayoutInflater = inflater =
+                    PolicyManager.makeNewLayoutInflater(getOuterContext());
+                return inflater;
+            }
+        } else if (ACTIVITY_SERVICE.equals(name)) {
+            return getActivityManager();
+        } else if (ALARM_SERVICE.equals(name)) {
+            return getAlarmManager();
+        } else if (POWER_SERVICE.equals(name)) {
+            return getPowerManager();
+        } else if (CONNECTIVITY_SERVICE.equals(name)) {
+            return getConnectivityManager();
+        } else if (WIFI_SERVICE.equals(name)) {
+            return getWifiManager();
+        } else if (NOTIFICATION_SERVICE.equals(name)) {
+            return getNotificationManager();
+        } else if (KEYGUARD_SERVICE.equals(name)) {
+            return new KeyguardManager();
+        } else if (LOCATION_SERVICE.equals(name)) {
+            return getLocationManager();
+        } else if (SEARCH_SERVICE.equals(name)) {
+            return getSearchManager();
+        } else if ( SENSOR_SERVICE.equals(name)) {
+            return getSensorManager();
+        } else if (BLUETOOTH_SERVICE.equals(name)) {
+            return getBluetoothDevice();
+        } else if (VIBRATOR_SERVICE.equals(name)) {
+            return getVibrator();
+        } else if (STATUS_BAR_SERVICE.equals(name)) {
+            synchronized (mSync) {
+                if (mStatusBarManager == null) {
+                    mStatusBarManager = new StatusBarManager(getOuterContext());
+                }
+                return mStatusBarManager;
+            }
+        } else if (AUDIO_SERVICE.equals(name)) {
+            return getAudioManager();
+        } else if (TELEPHONY_SERVICE.equals(name)) {
+            return getTelephonyManager();
+        } else if (CLIPBOARD_SERVICE.equals(name)) {
+            return getClipboardManager();
+        } else if (INPUT_METHOD_SERVICE.equals(name)) {
+            return InputMethodManager.getInstance(this);
+        }
+
+        return null;
+    }
+
+    private ActivityManager getActivityManager() {
+        synchronized (mSync) {
+            if (mActivityManager == null) {
+                mActivityManager = new ActivityManager(getOuterContext(),
+                        mMainThread.getHandler());
+            }
+        }
+        return mActivityManager;
+    }
+
+    private AlarmManager getAlarmManager() {
+        synchronized (sSync) {
+            if (sAlarmManager == null) {
+                IBinder b = ServiceManager.getService(ALARM_SERVICE);
+                IAlarmManager service = IAlarmManager.Stub.asInterface(b);
+                sAlarmManager = new AlarmManager(service);
+            }
+        }
+        return sAlarmManager;
+    }
+
+    private PowerManager getPowerManager() {
+        synchronized (sSync) {
+            if (sPowerManager == null) {
+                IBinder b = ServiceManager.getService(POWER_SERVICE);
+                IPowerManager service = IPowerManager.Stub.asInterface(b);
+                sPowerManager = new PowerManager(service, mMainThread.getHandler());
+            }
+        }
+        return sPowerManager;
+    }
+
+    private ConnectivityManager getConnectivityManager()
+    {
+        synchronized (sSync) {
+            if (sConnectivityManager == null) {
+                IBinder b = ServiceManager.getService(CONNECTIVITY_SERVICE);
+                IConnectivityManager service = IConnectivityManager.Stub.asInterface(b);
+                sConnectivityManager = new ConnectivityManager(service);
+            }
+        }
+        return sConnectivityManager;
+    }
+
+    private WifiManager getWifiManager()
+    {
+        synchronized (sSync) {
+            if (sWifiManager == null) {
+                IBinder b = ServiceManager.getService(WIFI_SERVICE);
+                IWifiManager service = IWifiManager.Stub.asInterface(b);
+                sWifiManager = new WifiManager(service, mMainThread.getHandler());
+            }
+        }
+        return sWifiManager;
+    }
+
+    private NotificationManager getNotificationManager()
+    {
+        synchronized (mSync) {
+            if (mNotificationManager == null) {
+                mNotificationManager = new NotificationManager(
+                        new ContextThemeWrapper(getOuterContext(), com.android.internal.R.style.Theme_Dialog),
+                        mMainThread.getHandler());
+            }
+        }
+        return mNotificationManager;
+    }
+
+    private TelephonyManager getTelephonyManager() {
+        synchronized (mSync) {
+            if (mTelephonyManager == null) {
+                mTelephonyManager = new TelephonyManager(getOuterContext());
+            }
+        }
+        return mTelephonyManager;
+    }
+
+    private ClipboardManager getClipboardManager() {
+        synchronized (mSync) {
+            if (mClipboardManager == null) {
+                mClipboardManager = new ClipboardManager(getOuterContext(),
+                        mMainThread.getHandler());
+            }
+        }
+        return mClipboardManager;
+    }
+
+    private LocationManager getLocationManager() {
+        synchronized (sSync) {
+            if (sLocationManager == null) {
+                IBinder b = ServiceManager.getService(LOCATION_SERVICE);
+                ILocationManager service = ILocationManager.Stub.asInterface(b);
+                sLocationManager = new LocationManager(service);
+            }
+        }
+        return sLocationManager;
+    }
+
+    private SearchManager getSearchManager() {
+        // This is only useable in Activity Contexts
+        if (getActivityToken() == null) {
+            throw new AndroidRuntimeException(
+                "Acquiring SearchManager objects only valid in Activity Contexts.");
+        }
+        synchronized (mSync) {
+            if (mSearchManager == null) {
+                mSearchManager = new SearchManager(getOuterContext(), mMainThread.getHandler());
+            }
+        }
+        return mSearchManager;
+    }
+
+    private BluetoothDevice getBluetoothDevice() {
+        if (sIsBluetoothDeviceCached) {
+            return sBluetoothDevice;
+        }
+        synchronized (sSync) {
+            IBinder b = ServiceManager.getService(BLUETOOTH_SERVICE);
+            if (b == null) {
+                sBluetoothDevice = null;
+            } else {
+                IBluetoothDevice service = IBluetoothDevice.Stub.asInterface(b);
+                sBluetoothDevice = new BluetoothDevice(service);
+            }
+            sIsBluetoothDeviceCached = true;
+        }
+        return sBluetoothDevice;
+    }
+
+    private SensorManager getSensorManager() {
+        synchronized (mSync) {
+            if (mSensorManager == null) {
+                mSensorManager = new SensorManager(mMainThread.getHandler().getLooper());
+            }
+        }
+        return mSensorManager;
+    }
+
+    private Vibrator getVibrator() {
+        synchronized (mSync) {
+            if (mVibrator == null) {
+                mVibrator = new Vibrator();
+            }
+        }
+        return mVibrator;
+    }
+  
+    private IWallpaperService getWallpaperService() {
+        synchronized (sSync) {
+            if (sWallpaperService == null) {
+                IBinder b = ServiceManager.getService(WALLPAPER_SERVICE);
+                sWallpaperService = IWallpaperService.Stub.asInterface(b);
+            }
+        }
+        return sWallpaperService;
+    }
+
+    private AudioManager getAudioManager()
+    {
+        if (mAudioManager == null) {
+            mAudioManager = new AudioManager(this);
+        }
+        return mAudioManager;
+    }
+
+    @Override
+    public int checkPermission(String permission, int pid, int uid) {
+        if (permission == null) {
+            throw new IllegalArgumentException("permission is null");
+        }
+
+        if (!Process.supportsProcesses()) {
+            return PackageManager.PERMISSION_GRANTED;
+        }
+        try {
+            return ActivityManagerNative.getDefault().checkPermission(
+                    permission, pid, uid);
+        } catch (RemoteException e) {
+            return PackageManager.PERMISSION_DENIED;
+        }
+    }
+
+    @Override
+    public int checkCallingPermission(String permission) {
+        if (permission == null) {
+            throw new IllegalArgumentException("permission is null");
+        }
+
+        if (!Process.supportsProcesses()) {
+            return PackageManager.PERMISSION_GRANTED;
+        }
+        int pid = Binder.getCallingPid();
+        if (pid != Process.myPid()) {
+            return checkPermission(permission, pid,
+                    Binder.getCallingUid());
+        }
+        return PackageManager.PERMISSION_DENIED;
+    }
+
+    @Override
+    public int checkCallingOrSelfPermission(String permission) {
+        if (permission == null) {
+            throw new IllegalArgumentException("permission is null");
+        }
+
+        return checkPermission(permission, Binder.getCallingPid(),
+                Binder.getCallingUid());
+    }
+
+    private void enforce(
+            String permission, int resultOfCheck,
+            boolean selfToo, int uid, String message) {
+        if (resultOfCheck != PackageManager.PERMISSION_GRANTED) {
+            throw new SecurityException(
+                    (message != null ? (message + ": ") : "") +
+                    (selfToo
+                     ? "Neither user " + uid + " nor current process has "
+                     : "User " + uid + " does not have ") +
+                    permission +
+                    ".");
+        }
+    }
+
+    public void enforcePermission(
+            String permission, int pid, int uid, String message) {
+        enforce(permission,
+                checkPermission(permission, pid, uid),
+                false,
+                uid,
+                message);
+    }
+
+    public void enforceCallingPermission(String permission, String message) {
+        enforce(permission,
+                checkCallingPermission(permission),
+                false,
+                Binder.getCallingUid(),
+                message);
+    }
+
+    public void enforceCallingOrSelfPermission(
+            String permission, String message) {
+        enforce(permission,
+                checkCallingOrSelfPermission(permission),
+                true,
+                Binder.getCallingUid(),
+                message);
+    }
+
+    @Override
+    public void grantUriPermission(String toPackage, Uri uri, int modeFlags) {
+         try {
+            ActivityManagerNative.getDefault().grantUriPermission(
+                    mMainThread.getApplicationThread(), toPackage, uri,
+                    modeFlags);
+        } catch (RemoteException e) {
+        }
+    }
+
+    @Override
+    public void revokeUriPermission(Uri uri, int modeFlags) {
+         try {
+            ActivityManagerNative.getDefault().revokeUriPermission(
+                    mMainThread.getApplicationThread(), uri,
+                    modeFlags);
+        } catch (RemoteException e) {
+        }
+    }
+
+    @Override
+    public int checkUriPermission(Uri uri, int pid, int uid, int modeFlags) {
+        if (!Process.supportsProcesses()) {
+            return PackageManager.PERMISSION_GRANTED;
+        }
+        try {
+            return ActivityManagerNative.getDefault().checkUriPermission(
+                    uri, pid, uid, modeFlags);
+        } catch (RemoteException e) {
+            return PackageManager.PERMISSION_DENIED;
+        }
+    }
+
+    @Override
+    public int checkCallingUriPermission(Uri uri, int modeFlags) {
+        if (!Process.supportsProcesses()) {
+            return PackageManager.PERMISSION_GRANTED;
+        }
+        int pid = Binder.getCallingPid();
+        if (pid != Process.myPid()) {
+            return checkUriPermission(uri, pid,
+                    Binder.getCallingUid(), modeFlags);
+        }
+        return PackageManager.PERMISSION_DENIED;
+    }
+
+    @Override
+    public int checkCallingOrSelfUriPermission(Uri uri, int modeFlags) {
+        return checkUriPermission(uri, Binder.getCallingPid(),
+                Binder.getCallingUid(), modeFlags);
+    }
+
+    @Override
+    public int checkUriPermission(Uri uri, String readPermission,
+            String writePermission, int pid, int uid, int modeFlags) {
+        if (false) {
+            Log.i("foo", "checkUriPermission: uri=" + uri + "readPermission="
+                    + readPermission + " writePermission=" + writePermission
+                    + " pid=" + pid + " uid=" + uid + " mode" + modeFlags);
+        }
+        if ((modeFlags&Intent.FLAG_GRANT_READ_URI_PERMISSION) != 0) {
+            if (readPermission == null
+                    || checkPermission(readPermission, pid, uid)
+                    == PackageManager.PERMISSION_GRANTED) {
+                return PackageManager.PERMISSION_GRANTED;
+            }
+        }
+        if ((modeFlags&Intent.FLAG_GRANT_WRITE_URI_PERMISSION) != 0) {
+            if (writePermission == null
+                    || checkPermission(writePermission, pid, uid)
+                    == PackageManager.PERMISSION_GRANTED) {
+                return PackageManager.PERMISSION_GRANTED;
+            }
+        }
+        return uri != null ? checkUriPermission(uri, pid, uid, modeFlags)
+                : PackageManager.PERMISSION_DENIED;
+    }
+
+    private String uriModeFlagToString(int uriModeFlags) {
+        switch (uriModeFlags) {
+            case Intent.FLAG_GRANT_READ_URI_PERMISSION |
+                    Intent.FLAG_GRANT_WRITE_URI_PERMISSION:
+                return "read and write";
+            case Intent.FLAG_GRANT_READ_URI_PERMISSION:
+                return "read";
+            case Intent.FLAG_GRANT_WRITE_URI_PERMISSION:
+                return "write";
+        }
+        throw new IllegalArgumentException(
+                "Unknown permission mode flags: " + uriModeFlags);
+    }
+
+    private void enforceForUri(
+            int modeFlags, int resultOfCheck, boolean selfToo,
+            int uid, Uri uri, String message) {
+        if (resultOfCheck != PackageManager.PERMISSION_GRANTED) {
+            throw new SecurityException(
+                    (message != null ? (message + ": ") : "") +
+                    (selfToo
+                     ? "Neither user " + uid + " nor current process has "
+                     : "User " + uid + " does not have ") +
+                    uriModeFlagToString(modeFlags) +
+                    " permission on " +
+                    uri +
+                    ".");
+        }
+    }
+
+    public void enforceUriPermission(
+            Uri uri, int pid, int uid, int modeFlags, String message) {
+        enforceForUri(
+                modeFlags, checkUriPermission(uri, pid, uid, modeFlags),
+                false, uid, uri, message);
+    }
+
+    public void enforceCallingUriPermission(
+            Uri uri, int modeFlags, String message) {
+        enforceForUri(
+                modeFlags, checkCallingUriPermission(uri, modeFlags),
+                false, Binder.getCallingUid(), uri, message);
+    }
+
+    public void enforceCallingOrSelfUriPermission(
+            Uri uri, int modeFlags, String message) {
+        enforceForUri(
+                modeFlags,
+                checkCallingOrSelfUriPermission(uri, modeFlags), true,
+                Binder.getCallingUid(), uri, message);
+    }
+
+    public void enforceUriPermission(
+            Uri uri, String readPermission, String writePermission,
+            int pid, int uid, int modeFlags, String message) {
+        enforceForUri(modeFlags,
+                      checkUriPermission(
+                              uri, readPermission, writePermission, pid, uid,
+                              modeFlags),
+                      false,
+                      uid,
+                      uri,
+                      message);
+    }
+
+    @Override
+    public Context createPackageContext(String packageName, int flags)
+        throws PackageManager.NameNotFoundException {
+        if (packageName.equals("system") || packageName.equals("android")) {
+            return new ApplicationContext(mMainThread.getSystemContext());
+        }
+
+        ActivityThread.PackageInfo pi =
+            mMainThread.getPackageInfo(packageName, flags);
+        if (pi != null) {
+            ApplicationContext c = new ApplicationContext();
+            c.init(pi, null, mMainThread);
+            if (c.mResources != null) {
+                return c;
+            }
+        }
+
+        // Should be a better exception.
+        throw new PackageManager.NameNotFoundException(
+            "Application package " + packageName + " not found");
+    }
+
+    private File getDataDirFile() {
+        if (mPackageInfo != null) {
+            return mPackageInfo.getDataDirFile();
+        }
+        throw new RuntimeException("Not supported in system context");
+    }
+
+    @Override
+    public File getDir(String name, int mode) {
+        name = "app_" + name;
+        File file = makeFilename(getDataDirFile(), name);
+        if (!file.exists()) {
+            file.mkdir();
+            setFilePermissionsFromMode(file.getPath(), mode,
+                    FileUtils.S_IRWXU|FileUtils.S_IRWXG|FileUtils.S_IXOTH);
+        }
+        return file;
+    }
+
+    static ApplicationContext createSystemContext(ActivityThread mainThread) {
+        ApplicationContext context = new ApplicationContext();
+        context.init(Resources.getSystem(), mainThread);
+        return context;
+    }
+
+    ApplicationContext() {
+        ++sInstanceCount;
+        mOuterContext = this;
+    }
+
+    /**
+     * Create a new ApplicationContext from an existing one.  The new one
+     * works and operates the same as the one it is copying.
+     *
+     * @param context Existing application context.
+     */
+    public ApplicationContext(ApplicationContext context) {
+        ++sInstanceCount;
+        mPackageInfo = context.mPackageInfo;
+        mResources = context.mResources;
+        mMainThread = context.mMainThread;
+        mContentResolver = context.mContentResolver;
+        mOuterContext = this;
+    }
+
+    final void init(ActivityThread.PackageInfo packageInfo,
+            IBinder activityToken, ActivityThread mainThread) {
+        mPackageInfo = packageInfo;
+        mResources = mPackageInfo.getResources(mainThread);
+        mMainThread = mainThread;
+        mContentResolver = new ApplicationContentResolver(this, mainThread);
+
+        setActivityToken(activityToken);
+    }
+
+    final void init(Resources resources, ActivityThread mainThread) {
+        mPackageInfo = null;
+        mResources = resources;
+        mMainThread = mainThread;
+        mContentResolver = new ApplicationContentResolver(this, mainThread);
+    }
+
+    final void scheduleFinalCleanup(String who, String what) {
+        mMainThread.scheduleContextCleanup(this, who, what);
+    }
+
+    final void performFinalCleanup(String who, String what) {
+        //Log.i(TAG, "Cleanup up context: " + this);
+        mPackageInfo.removeContextRegistrations(getOuterContext(), who, what);
+    }
+
+    final Context getReceiverRestrictedContext() {
+        if (mReceiverRestrictedContext != null) {
+            return mReceiverRestrictedContext;
+        }
+        return mReceiverRestrictedContext = new ReceiverRestrictedContext(getOuterContext());
+    }
+
+    final void setActivityToken(IBinder token) {
+        mActivityToken = token;
+    }
+    
+    final void setOuterContext(Context context) {
+        mOuterContext = context;
+    }
+    
+    final Context getOuterContext() {
+        return mOuterContext;
+    }
+    
+    final IBinder getActivityToken() {
+        return mActivityToken;
+    }
+
+    private static void setFilePermissionsFromMode(String name, int mode,
+            int extraPermissions) {
+        int perms = FileUtils.S_IRUSR|FileUtils.S_IWUSR
+            |FileUtils.S_IRGRP|FileUtils.S_IWGRP
+            |extraPermissions;
+        if ((mode&MODE_WORLD_READABLE) != 0) {
+            perms |= FileUtils.S_IROTH;
+        }
+        if ((mode&MODE_WORLD_WRITEABLE) != 0) {
+            perms |= FileUtils.S_IWOTH;
+        }
+        if (false) {
+            Log.i(TAG, "File " + name + ": mode=0x" + Integer.toHexString(mode)
+                  + ", perms=0x" + Integer.toHexString(perms));
+        }
+        FileUtils.setPermissions(name, perms, -1, -1);
+    }
+
+    private File makeFilename(File base, String name) {
+        if (name.indexOf(File.separatorChar) < 0) {
+            return new File(base, name);
+        }
+        throw new IllegalArgumentException(
+            "File " + name + " contains a path separator");
+    }
+
+    // ----------------------------------------------------------------------
+    // ----------------------------------------------------------------------
+    // ----------------------------------------------------------------------
+
+    private static final class ApplicationContentResolver extends ContentResolver {
+        public ApplicationContentResolver(Context context,
+                                          ActivityThread mainThread)
+        {
+            super(context);
+            mMainThread = mainThread;
+        }
+
+        @Override
+        protected IContentProvider acquireProvider(Context context, String name)
+        {
+            return mMainThread.acquireProvider(context, name);
+        }
+
+        @Override
+        public boolean releaseProvider(IContentProvider provider)
+        {
+            return mMainThread.releaseProvider(provider);
+        }
+        
+        private final ActivityThread mMainThread;
+    }
+
+    // ----------------------------------------------------------------------
+    // ----------------------------------------------------------------------
+    // ----------------------------------------------------------------------
+
+    /*package*/
+    static final class ApplicationPackageManager extends PackageManager {
+        @Override
+        public PackageInfo getPackageInfo(String packageName, int flags)
+                throws NameNotFoundException {
+            try {
+                PackageInfo pi = mPM.getPackageInfo(packageName, flags);
+                if (pi != null) {
+                    return pi;
+                }
+            } catch (RemoteException e) {
+                throw new RuntimeException("Package manager has died", e);
+            }
+
+            throw new NameNotFoundException(packageName);
+        }
+
+        public Intent getLaunchIntentForPackage(String packageName)
+                throws NameNotFoundException {
+            // First see if the package has an INFO activity; the existence of
+            // such an activity is implied to be the desired front-door for the
+            // overall package (such as if it has multiple launcher entries).
+            Intent intent = getLaunchIntentForPackageCategory(this, packageName,
+                    Intent.CATEGORY_INFO);
+            if (intent != null) {
+                return intent;
+            }
+            
+            // Otherwise, try to find a main launcher activity.
+            return getLaunchIntentForPackageCategory(this, packageName,
+                    Intent.CATEGORY_LAUNCHER);
+        }
+        
+        // XXX This should be implemented as a call to the package manager,
+        // to reduce the work needed.
+        static Intent getLaunchIntentForPackageCategory(PackageManager pm,
+                String packageName, String category) {
+            Intent intent = new Intent(Intent.ACTION_MAIN);
+            intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+            Intent intentToResolve = new Intent(Intent.ACTION_MAIN, null);
+            intentToResolve.addCategory(category);
+            final List<ResolveInfo> apps =
+                    pm.queryIntentActivities(intentToResolve, 0);
+            // I wish there were a way to directly get the "main" activity of a
+            // package but ...
+            for (ResolveInfo app : apps) {
+                if (app.activityInfo.packageName.equals(packageName)) {
+                    intent.setClassName(packageName, app.activityInfo.name);
+                    return intent;
+                }
+            }
+            return null;
+        }
+        
+        @Override
+        public int[] getPackageGids(String packageName)
+            throws NameNotFoundException {
+            try {
+                int[] gids = mPM.getPackageGids(packageName);
+                if (gids == null || gids.length > 0) {
+                    return gids;
+                }
+            } catch (RemoteException e) {
+                throw new RuntimeException("Package manager has died", e);
+            }
+
+            throw new NameNotFoundException(packageName);
+        }
+
+        @Override
+        public PermissionInfo getPermissionInfo(String name, int flags)
+            throws NameNotFoundException {
+            try {
+                PermissionInfo pi = mPM.getPermissionInfo(name, flags);
+                if (pi != null) {
+                    return pi;
+                }
+            } catch (RemoteException e) {
+                throw new RuntimeException("Package manager has died", e);
+            }
+
+            throw new NameNotFoundException(name);
+        }
+
+        @Override
+        public List<PermissionInfo> queryPermissionsByGroup(String group, int flags)
+                throws NameNotFoundException {
+            try {
+                List<PermissionInfo> pi = mPM.queryPermissionsByGroup(group, flags);
+                if (pi != null) {
+                    return pi;
+                }
+            } catch (RemoteException e) {
+                throw new RuntimeException("Package manager has died", e);
+            }
+
+            throw new NameNotFoundException(group);
+        }
+
+        @Override
+        public PermissionGroupInfo getPermissionGroupInfo(String name,
+                int flags) throws NameNotFoundException {
+            try {
+                PermissionGroupInfo pgi = mPM.getPermissionGroupInfo(name, flags);
+                if (pgi != null) {
+                    return pgi;
+                }
+            } catch (RemoteException e) {
+                throw new RuntimeException("Package manager has died", e);
+            }
+
+            throw new NameNotFoundException(name);
+        }
+
+        @Override
+        public List<PermissionGroupInfo> getAllPermissionGroups(int flags) {
+            try {
+                return mPM.getAllPermissionGroups(flags);
+            } catch (RemoteException e) {
+                throw new RuntimeException("Package manager has died", e);
+            }
+        }
+
+        @Override
+        public ApplicationInfo getApplicationInfo(String packageName, int flags)
+            throws NameNotFoundException {
+            try {
+                ApplicationInfo ai = mPM.getApplicationInfo(packageName, flags);
+                if (ai != null) {
+                    return ai;
+                }
+            } catch (RemoteException e) {
+                throw new RuntimeException("Package manager has died", e);
+            }
+
+            throw new NameNotFoundException(packageName);
+        }
+
+        @Override
+        public ActivityInfo getActivityInfo(ComponentName className, int flags)
+            throws NameNotFoundException {
+            try {
+                ActivityInfo ai = mPM.getActivityInfo(className, flags);
+                if (ai != null) {
+                    return ai;
+                }
+            } catch (RemoteException e) {
+                throw new RuntimeException("Package manager has died", e);
+            }
+
+            throw new NameNotFoundException(className.toString());
+        }
+
+        @Override
+        public ActivityInfo getReceiverInfo(ComponentName className, int flags)
+            throws NameNotFoundException {
+            try {
+                ActivityInfo ai = mPM.getReceiverInfo(className, flags);
+                if (ai != null) {
+                    return ai;
+                }
+            } catch (RemoteException e) {
+                throw new RuntimeException("Package manager has died", e);
+            }
+
+            throw new NameNotFoundException(className.toString());
+        }
+
+        @Override
+        public ServiceInfo getServiceInfo(ComponentName className, int flags)
+            throws NameNotFoundException {
+            try {
+                ServiceInfo si = mPM.getServiceInfo(className, flags);
+                if (si != null) {
+                    return si;
+                }
+            } catch (RemoteException e) {
+                throw new RuntimeException("Package manager has died", e);
+            }
+
+            throw new NameNotFoundException(className.toString());
+        }
+
+        @Override
+        public String[] getSystemSharedLibraryNames() {
+             try {
+                 return mPM.getSystemSharedLibraryNames();
+             } catch (RemoteException e) {
+                 throw new RuntimeException("Package manager has died", e);
+             }
+        }
+
+        @Override
+        public int checkPermission(String permName, String pkgName) {
+            try {
+                return mPM.checkPermission(permName, pkgName);
+            } catch (RemoteException e) {
+                throw new RuntimeException("Package manager has died", e);
+            }
+        }
+
+        @Override
+        public boolean addPermission(PermissionInfo info) {
+            try {
+                return mPM.addPermission(info);
+            } catch (RemoteException e) {
+                throw new RuntimeException("Package manager has died", e);
+            }
+        }
+
+        @Override
+        public void removePermission(String name) {
+            try {
+                mPM.removePermission(name);
+            } catch (RemoteException e) {
+                throw new RuntimeException("Package manager has died", e);
+            }
+        }
+
+        @Override
+        public int checkSignatures(String pkg1, String pkg2) {
+            try {
+                return mPM.checkSignatures(pkg1, pkg2);
+            } catch (RemoteException e) {
+                throw new RuntimeException("Package manager has died", e);
+            }
+        }
+
+        @Override
+        public String[] getPackagesForUid(int uid) {
+            try {
+                return mPM.getPackagesForUid(uid);
+            } catch (RemoteException e) {
+                throw new RuntimeException("Package manager has died", e);
+            }
+        }
+
+        @Override
+        public String getNameForUid(int uid) {
+            try {
+                return mPM.getNameForUid(uid);
+            } catch (RemoteException e) {
+                throw new RuntimeException("Package manager has died", e);
+            }
+        }
+        
+        @Override
+        public int getUidForSharedUser(String sharedUserName) 
+                throws NameNotFoundException {
+            try {
+                int uid = mPM.getUidForSharedUser(sharedUserName);
+                if(uid != -1) {
+                    return uid;
+                }
+            } catch (RemoteException e) {
+                throw new RuntimeException("Package manager has died", e);
+            }
+            throw new NameNotFoundException("No shared userid for user:"+sharedUserName);
+        }
+
+        @Override
+        public List<PackageInfo> getInstalledPackages(int flags) {
+            try {
+                return mPM.getInstalledPackages(flags);
+            } catch (RemoteException e) {
+                throw new RuntimeException("Package manager has died", e);
+            }
+        }
+
+        @Override
+        public List<ApplicationInfo> getInstalledApplications(int flags) {
+            try {
+                return mPM.getInstalledApplications(flags);
+            } catch (RemoteException e) {
+                throw new RuntimeException("Package manager has died", e);
+            }
+        }
+
+        @Override
+        public ResolveInfo resolveActivity(Intent intent, int flags) {
+            try {
+                return mPM.resolveIntent(
+                    intent,
+                    intent.resolveTypeIfNeeded(mContext.getContentResolver()),
+                    flags);
+            } catch (RemoteException e) {
+                throw new RuntimeException("Package manager has died", e);
+            }
+        }
+
+        @Override
+        public List<ResolveInfo> queryIntentActivities(Intent intent,
+                int flags) {
+            try {
+                return mPM.queryIntentActivities(
+                    intent,
+                    intent.resolveTypeIfNeeded(mContext.getContentResolver()),
+                    flags);
+            } catch (RemoteException e) {
+                throw new RuntimeException("Package manager has died", e);
+            }
+        }
+
+        @Override
+        public List<ResolveInfo> queryIntentActivityOptions(
+                ComponentName caller, Intent[] specifics, Intent intent,
+                int flags) {
+            final ContentResolver resolver = mContext.getContentResolver();
+
+            String[] specificTypes = null;
+            if (specifics != null) {
+                final int N = specifics.length;
+                for (int i=0; i<N; i++) {
+                    Intent sp = specifics[i];
+                    if (sp != null) {
+                        String t = sp.resolveTypeIfNeeded(resolver);
+                        if (t != null) {
+                            if (specificTypes == null) {
+                                specificTypes = new String[N];
+                            }
+                            specificTypes[i] = t;
+                        }
+                    }
+                }
+            }
+
+            try {
+                return mPM.queryIntentActivityOptions(caller, specifics,
+                    specificTypes, intent, intent.resolveTypeIfNeeded(resolver),
+                    flags);
+            } catch (RemoteException e) {
+                throw new RuntimeException("Package manager has died", e);
+            }
+        }
+
+        @Override
+        public List<ResolveInfo> queryBroadcastReceivers(Intent intent, int flags) {
+            try {
+                return mPM.queryIntentReceivers(
+                    intent,
+                    intent.resolveTypeIfNeeded(mContext.getContentResolver()),
+                    flags);
+            } catch (RemoteException e) {
+                throw new RuntimeException("Package manager has died", e);
+            }
+        }
+
+        @Override
+        public ResolveInfo resolveService(Intent intent, int flags) {
+            try {
+                return mPM.resolveService(
+                    intent,
+                    intent.resolveTypeIfNeeded(mContext.getContentResolver()),
+                    flags);
+            } catch (RemoteException e) {
+                throw new RuntimeException("Package manager has died", e);
+            }
+        }
+
+        @Override
+        public List<ResolveInfo> queryIntentServices(Intent intent, int flags) {
+            try {
+                return mPM.queryIntentServices(
+                    intent,
+                    intent.resolveTypeIfNeeded(mContext.getContentResolver()),
+                    flags);
+            } catch (RemoteException e) {
+                throw new RuntimeException("Package manager has died", e);
+            }
+        }
+
+        @Override
+        public ProviderInfo resolveContentProvider(String name,
+                int flags) {
+            try {
+                return mPM.resolveContentProvider(name, flags);
+            } catch (RemoteException e) {
+                throw new RuntimeException("Package manager has died", e);
+            }
+        }
+
+        @Override
+        public List<ProviderInfo> queryContentProviders(String processName,
+                int uid, int flags) {
+            try {
+                return mPM.queryContentProviders(processName, uid, flags);
+            } catch (RemoteException e) {
+                throw new RuntimeException("Package manager has died", e);
+            }
+        }
+
+        @Override
+        public InstrumentationInfo getInstrumentationInfo(
+                ComponentName className, int flags)
+                throws NameNotFoundException {
+            try {
+                InstrumentationInfo ii = mPM.getInstrumentationInfo(
+                        className, flags);
+                if (ii != null) {
+                    return ii;
+                }
+            } catch (RemoteException e) {
+                throw new RuntimeException("Package manager has died", e);
+            }
+
+            throw new NameNotFoundException(className.toString());
+        }
+
+        @Override
+        public List<InstrumentationInfo> queryInstrumentation(
+                String targetPackage, int flags) {
+            try {
+                return mPM.queryInstrumentation(targetPackage, flags);
+            } catch (RemoteException e) {
+                throw new RuntimeException("Package manager has died", e);
+            }
+        }
+
+        @Override public Drawable getDrawable(String packageName, int resid,
+                ApplicationInfo appInfo) {
+            ResourceName name = new ResourceName(packageName, resid);
+            Drawable dr = getCachedIcon(name);
+            if (dr != null) {
+                return dr;
+            }
+            if (appInfo == null) {
+                try {
+                    appInfo = getApplicationInfo(packageName, 0);
+                } catch (NameNotFoundException e) {
+                    return null;
+                }
+            }
+            try {
+                Resources r = getResourcesForApplication(appInfo);
+                dr = r.getDrawable(resid);
+                if (DEBUG_ICONS) Log.v(TAG, "Getting drawable 0x"
+                        + Integer.toHexString(resid) + " from " + r
+                        + ": " + dr);
+                putCachedIcon(name, dr);
+                return dr;
+            } catch (NameNotFoundException e) {
+                Log.w("PackageManager", "Failure retrieving resources for"
+                        + appInfo.packageName);
+            } catch (RuntimeException e) {
+                // If an exception was thrown, fall through to return
+                // default icon.
+                Log.w("PackageManager", "Failure retrieving icon 0x"
+                        + Integer.toHexString(resid) + " in package "
+                        + packageName, e);
+            }
+            return null;
+        }
+
+        @Override public Drawable getActivityIcon(ComponentName activityName)
+                throws NameNotFoundException {
+            return getActivityInfo(activityName, 0).loadIcon(this);
+        }
+
+        @Override public Drawable getActivityIcon(Intent intent)
+                throws NameNotFoundException {
+            if (intent.getComponent() != null) {
+                return getActivityIcon(intent.getComponent());
+            }
+
+            ResolveInfo info = resolveActivity(
+                intent, PackageManager.MATCH_DEFAULT_ONLY);
+            if (info != null) {
+                return info.activityInfo.loadIcon(this);
+            }
+
+            throw new NameNotFoundException(intent.toURI());
+        }
+
+        @Override public Drawable getDefaultActivityIcon() {
+            return Resources.getSystem().getDrawable(
+                com.android.internal.R.drawable.sym_def_app_icon);
+        }
+
+        @Override public Drawable getApplicationIcon(ApplicationInfo info) {
+            final int icon = info.icon;
+            if (icon != 0) {
+                ResourceName name = new ResourceName(info, icon);
+                Drawable dr = getCachedIcon(name);
+                if (dr != null) {
+                    return dr;
+                }
+                try {
+                    Resources r = getResourcesForApplication(info);
+                    dr = r.getDrawable(icon);
+                    if (DEBUG_ICONS) Log.v(TAG, "Getting drawable 0x"
+                            + Integer.toHexString(icon) + " from " + r
+                            + ": " + dr);
+                    putCachedIcon(name, dr);
+                    return dr;
+                } catch (NameNotFoundException e) {
+                    Log.w("PackageManager", "Failure retrieving resources for"
+                            + info.packageName);
+                } catch (RuntimeException e) {
+                    // If an exception was thrown, fall through to return
+                    // default icon.
+                    Log.w("PackageManager", "Failure retrieving app icon", e);
+                }
+            }
+            return getDefaultActivityIcon();
+        }
+
+        @Override public Drawable getApplicationIcon(String packageName)
+                throws NameNotFoundException {
+            return getApplicationIcon(getApplicationInfo(packageName, 0));
+        }
+
+        @Override public Resources getResourcesForActivity(
+                ComponentName activityName) throws NameNotFoundException {
+            return getResourcesForApplication(
+                getActivityInfo(activityName, 0).applicationInfo);
+        }
+
+        @Override public Resources getResourcesForApplication(
+                ApplicationInfo app) throws NameNotFoundException {
+            if (app.packageName.equals("system")) {
+                return mContext.mMainThread.getSystemContext().getResources();
+            }
+            Resources r = mContext.mMainThread.getTopLevelResources(
+                    app.uid == Process.myUid() ? app.sourceDir
+                    : app.publicSourceDir);
+            if (r != null) {
+                return r;
+            }
+            throw new NameNotFoundException("Unable to open " + app.publicSourceDir);
+        }
+
+        @Override public Resources getResourcesForApplication(
+                String appPackageName) throws NameNotFoundException {
+            return getResourcesForApplication(
+                getApplicationInfo(appPackageName, 0));
+        }
+
+        int mCachedSafeMode = -1;
+        @Override public boolean isSafeMode() {
+            try {
+                if (mCachedSafeMode < 0) {
+                    mCachedSafeMode = mPM.isSafeMode() ? 1 : 0;
+                }
+                return mCachedSafeMode != 0;
+            } catch (RemoteException e) {
+                throw new RuntimeException("Package manager has died", e);
+            }
+        }
+
+        static void configurationChanged() {
+            synchronized (sSync) {
+                sIconCache.clear();
+                sStringCache.clear();
+            }
+        }
+
+        ApplicationPackageManager(ApplicationContext context,
+                IPackageManager pm) {
+            mContext = context;
+            mPM = pm;
+        }
+
+        private Drawable getCachedIcon(ResourceName name) {
+            synchronized (sSync) {
+                WeakReference<Drawable> wr = sIconCache.get(name);
+                if (DEBUG_ICONS) Log.v(TAG, "Get cached weak drawable ref for "
+                        + name + ": " + wr);
+                if (wr != null) {   // we have the activity
+                    Drawable dr = wr.get();
+                    if (dr != null) {
+                        if (DEBUG_ICONS) Log.v(TAG, "Get cached drawable for "
+                                + name + ": " + dr);
+                        return dr;
+                    }
+                    // our entry has been purged
+                    sIconCache.remove(name);
+                }
+            }
+            return null;
+        }
+
+        private void establishPackageRemovedReceiver() {
+            // mContext.registerReceiverInternal() winds up acquiring the
+            // main ActivityManagerService.this lock.  If we hold our usual
+            // sSync global lock at the same time, we impose a required ordering
+            // on those two locks, which is not good for deadlock prevention.
+            // Use a dedicated lock around initialization of
+            // sPackageRemovedReceiver to avoid this.
+            synchronized (sPackageRemovedSync) {
+                if (sPackageRemovedReceiver == null) {
+                    sPackageRemovedReceiver = new PackageRemovedReceiver();
+                    IntentFilter filter = new IntentFilter(
+                            Intent.ACTION_PACKAGE_REMOVED);
+                    filter.addDataScheme("package");
+                    mContext.registerReceiverInternal(sPackageRemovedReceiver,
+                            filter, null, null, null);
+                }
+            }
+        }
+        
+        private void putCachedIcon(ResourceName name, Drawable dr) {
+            establishPackageRemovedReceiver();
+
+            synchronized (sSync) {
+                sIconCache.put(name, new WeakReference<Drawable>(dr));
+                if (DEBUG_ICONS) Log.v(TAG, "Added cached drawable for "
+                        + name + ": " + dr);
+            }
+        }
+
+        private static final class PackageRemovedReceiver extends BroadcastReceiver {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                Uri data = intent.getData();
+                String ssp;
+                if (data != null && (ssp=data.getSchemeSpecificPart()) != null) {
+                    boolean needCleanup = false;
+                    synchronized (sSync) {
+                        Iterator<ResourceName> it = sIconCache.keySet().iterator();
+                        while (it.hasNext()) {
+                            ResourceName nm = it.next();
+                            if (nm.packageName.equals(ssp)) {
+                                //Log.i(TAG, "Removing cached drawable for " + nm);
+                                it.remove();
+                                needCleanup = true;
+                            }
+                        }
+                        it = sStringCache.keySet().iterator();
+                        while (it.hasNext()) {
+                            ResourceName nm = it.next();
+                            if (nm.packageName.equals(ssp)) {
+                                //Log.i(TAG, "Removing cached string for " + nm);
+                                it.remove();
+                                needCleanup = true;
+                            }
+                        }
+                    }
+                    if (needCleanup || ActivityThread.currentActivityThread().hasPackageInfo(ssp)) {
+                        ActivityThread.currentActivityThread().scheduleGcIdler();
+                    }
+                }
+            }
+        }
+
+        private static final class ResourceName {
+            final String packageName;
+            final int iconId;
+
+            ResourceName(String _packageName, int _iconId) {
+                packageName = _packageName;
+                iconId = _iconId;
+            }
+
+            ResourceName(ApplicationInfo aInfo, int _iconId) {
+                this(aInfo.packageName, _iconId);
+            }
+
+            ResourceName(ComponentInfo cInfo, int _iconId) {
+                this(cInfo.applicationInfo.packageName, _iconId);
+            }
+
+            ResourceName(ResolveInfo rInfo, int _iconId) {
+                this(rInfo.activityInfo.applicationInfo.packageName, _iconId);
+            }
+
+            @Override
+            public boolean equals(Object o) {
+                if (this == o) return true;
+                if (o == null || getClass() != o.getClass()) return false;
+
+                ResourceName that = (ResourceName) o;
+
+                if (iconId != that.iconId) return false;
+                return !(packageName != null ?
+                        !packageName.equals(that.packageName) : that.packageName != null);
+
+            }
+
+            @Override
+            public int hashCode() {
+                int result;
+                result = packageName.hashCode();
+                result = 31 * result + iconId;
+                return result;
+            }
+
+            @Override
+            public String toString() {
+                return "{ResourceName " + packageName + " / " + iconId + "}";
+            }
+        }
+
+        private CharSequence getCachedString(ResourceName name) {
+            synchronized (sSync) {
+                WeakReference<CharSequence> wr = sStringCache.get(name);
+                if (wr != null) {   // we have the activity
+                    CharSequence cs = wr.get();
+                    if (cs != null) {
+                        return cs;
+                    }
+                    // our entry has been purged
+                    sStringCache.remove(name);
+                }
+            }
+            return null;
+        }
+
+        private void putCachedString(ResourceName name, CharSequence cs) {
+            establishPackageRemovedReceiver();
+
+            synchronized (sSync) {
+                sStringCache.put(name, new WeakReference<CharSequence>(cs));
+            }
+        }
+
+        private CharSequence getLabel(ResourceName name, ApplicationInfo app, int id) {
+            CharSequence cs = getCachedString(name);
+            if (cs != null) {
+                return cs;
+            }
+            try {
+                Resources r = getResourcesForApplication(app);
+                cs = r.getText(id);
+                putCachedString(name, cs);
+            } catch (NameNotFoundException e) {
+                Log.w("PackageManager", "Failure retrieving resources for"
+                        + app.packageName);
+            } catch (RuntimeException e) {
+                // If an exception was thrown, fall through to return null
+                Log.w("ApplicationInfo", "Failure retrieving activity name", e);
+            }
+            return cs;
+        }
+
+        @Override
+        public CharSequence getText(String packageName, int resid,
+                ApplicationInfo appInfo) {
+            ResourceName name = new ResourceName(packageName, resid);
+            CharSequence text = getCachedString(name);
+            if (text != null) {
+                return text;
+            }
+            if (appInfo == null) {
+                try {
+                    appInfo = getApplicationInfo(packageName, 0);
+                } catch (NameNotFoundException e) {
+                    return null;
+                }
+            }
+            try {
+                Resources r = getResourcesForApplication(appInfo);
+                text = r.getText(resid);
+                putCachedString(name, text);
+                return text;
+            } catch (NameNotFoundException e) {
+                Log.w("PackageManager", "Failure retrieving resources for"
+                        + appInfo.packageName);
+            } catch (RuntimeException e) {
+                // If an exception was thrown, fall through to return
+                // default icon.
+                Log.w("PackageManager", "Failure retrieving text 0x"
+                        + Integer.toHexString(resid) + " in package "
+                        + packageName, e);
+            }
+            return null;
+        }
+
+        @Override
+        public XmlResourceParser getXml(String packageName, int resid,
+                ApplicationInfo appInfo) {
+            if (appInfo == null) {
+                try {
+                    appInfo = getApplicationInfo(packageName, 0);
+                } catch (NameNotFoundException e) {
+                    return null;
+                }
+            }
+            try {
+                Resources r = getResourcesForApplication(appInfo);
+                return r.getXml(resid);
+            } catch (RuntimeException e) {
+                // If an exception was thrown, fall through to return
+                // default icon.
+                Log.w("PackageManager", "Failure retrieving xml 0x"
+                        + Integer.toHexString(resid) + " in package "
+                        + packageName, e);
+            } catch (NameNotFoundException e) {
+                Log.w("PackageManager", "Failure retrieving resources for"
+                        + appInfo.packageName);
+            }
+            return null;
+        }
+
+        @Override
+        public CharSequence getApplicationLabel(ApplicationInfo info) {
+            if (info.nonLocalizedLabel != null) {
+                return info.nonLocalizedLabel;
+            }
+            final int id = info.labelRes;
+            if (id != 0) {
+                CharSequence cs = getLabel(new ResourceName(info, id), info, id);
+                if (cs != null) {
+                    return cs;
+                }
+            }
+            return info.packageName;
+        }
+
+        @Override
+        public void installPackage(Uri packageURI, IPackageInstallObserver observer, int flags) {
+            try {
+                mPM.installPackage(packageURI, observer, flags);
+            } catch (RemoteException e) {
+                // Should never happen!
+            }
+        }
+
+        @Override
+        public void deletePackage(String packageName, IPackageDeleteObserver observer, int flags) {
+            try {
+                mPM.deletePackage(packageName, observer, flags);
+            } catch (RemoteException e) {
+                // Should never happen!
+            }
+        }
+        @Override
+        public void clearApplicationUserData(String packageName, 
+                IPackageDataObserver observer) {
+            try {
+                mPM.clearApplicationUserData(packageName, observer);
+            } catch (RemoteException e) {
+                // Should never happen!
+            }
+        }
+        @Override
+        public void deleteApplicationCacheFiles(String packageName, 
+                IPackageDataObserver observer) {
+            try {
+                mPM.deleteApplicationCacheFiles(packageName, observer);
+            } catch (RemoteException e) {
+                // Should never happen!
+            }
+        }
+        @Override
+        public void freeStorageAndNotify(long idealStorageSize, IPackageDataObserver observer) {
+            try {
+                mPM.freeStorageAndNotify(idealStorageSize, observer);
+            } catch (RemoteException e) {
+                // Should never happen!
+            }
+        }
+        
+        @Override
+        public void freeStorage(long idealStorageSize, PendingIntent opFinishedIntent) {
+            try {
+                mPM.freeStorage(idealStorageSize, opFinishedIntent);
+            } catch (RemoteException e) {
+                // Should never happen!
+            }
+        }
+        
+        @Override
+        public void getPackageSizeInfo(String packageName, 
+                IPackageStatsObserver observer) {
+            try {
+                mPM.getPackageSizeInfo(packageName, observer);
+            } catch (RemoteException e) {
+                // Should never happen!
+            }
+        }
+        @Override
+        public void addPackageToPreferred(String packageName) {
+            try {
+                mPM.addPackageToPreferred(packageName);
+            } catch (RemoteException e) {
+                // Should never happen!
+            }
+        }
+
+        @Override
+        public void removePackageFromPreferred(String packageName) {
+            try {
+                mPM.removePackageFromPreferred(packageName);
+            } catch (RemoteException e) {
+                // Should never happen!
+            }
+        }
+
+        @Override
+        public List<PackageInfo> getPreferredPackages(int flags) {
+            try {
+                return mPM.getPreferredPackages(flags);
+            } catch (RemoteException e) {
+                // Should never happen!
+            }
+            return new ArrayList<PackageInfo>();
+        }
+
+        @Override
+        public void addPreferredActivity(IntentFilter filter,
+                int match, ComponentName[] set, ComponentName activity) {
+            try {
+                mPM.addPreferredActivity(filter, match, set, activity);
+            } catch (RemoteException e) {
+                // Should never happen!
+            }
+        }
+        
+        @Override
+        public void clearPackagePreferredActivities(String packageName) {
+            try {
+                mPM.clearPackagePreferredActivities(packageName);
+            } catch (RemoteException e) {
+                // Should never happen!
+            }
+        }
+        
+        @Override
+        public int getPreferredActivities(List<IntentFilter> outFilters,
+                List<ComponentName> outActivities, String packageName) {
+            try {
+                return mPM.getPreferredActivities(outFilters, outActivities, packageName);
+            } catch (RemoteException e) {
+                // Should never happen!
+            }
+            return 0;
+        }
+        
+        @Override
+        public void setComponentEnabledSetting(ComponentName componentName,
+                int newState, int flags) {
+            try {
+                mPM.setComponentEnabledSetting(componentName, newState, flags);
+            } catch (RemoteException e) {
+                // Should never happen!
+            }
+        }
+
+        @Override
+        public int getComponentEnabledSetting(ComponentName componentName) {
+            try {
+                return mPM.getComponentEnabledSetting(componentName);
+            } catch (RemoteException e) {
+                // Should never happen!
+            }
+            return PackageManager.COMPONENT_ENABLED_STATE_DEFAULT;
+        }
+
+        @Override
+        public void setApplicationEnabledSetting(String packageName,
+                int newState, int flags) {
+            try {
+                mPM.setApplicationEnabledSetting(packageName, newState, flags);
+            } catch (RemoteException e) {
+                // Should never happen!
+            }
+        }
+        
+        @Override
+        public int getApplicationEnabledSetting(String packageName) {
+            try {
+                return mPM.getApplicationEnabledSetting(packageName);
+            } catch (RemoteException e) {
+                // Should never happen!
+            }
+            return PackageManager.COMPONENT_ENABLED_STATE_DEFAULT;
+        }
+
+        private final ApplicationContext mContext;
+        private final IPackageManager mPM;
+
+        private static final Object sSync = new Object();
+        private static final Object sPackageRemovedSync = new Object();
+        private static BroadcastReceiver sPackageRemovedReceiver;
+        private static HashMap<ResourceName, WeakReference<Drawable> > sIconCache
+                = new HashMap<ResourceName, WeakReference<Drawable> >();
+        private static HashMap<ResourceName, WeakReference<CharSequence> > sStringCache
+                = new HashMap<ResourceName, WeakReference<CharSequence> >();
+    }
+
+    // ----------------------------------------------------------------------
+    // ----------------------------------------------------------------------
+    // ----------------------------------------------------------------------
+
+    private static final class SharedPreferencesImpl implements SharedPreferences {
+
+        private final File mFile;
+        private final File mBackupFile;
+        private final int mMode;
+        private Map mMap;
+        private final FileStatus mFileStatus = new FileStatus();
+        private long mTimestamp;
+
+        private List<OnSharedPreferenceChangeListener> mListeners;
+
+        SharedPreferencesImpl(
+            File file, int mode, Map initialContents) {
+            mFile = file;
+            mBackupFile = makeBackupFile(file);
+            mMode = mode;
+            mMap = initialContents != null ? initialContents : new HashMap();
+            if (FileUtils.getFileStatus(file.getPath(), mFileStatus)) {
+                mTimestamp = mFileStatus.mtime;
+            }
+            mListeners = new ArrayList<OnSharedPreferenceChangeListener>();
+        }
+
+        public boolean hasFileChanged() {
+            synchronized (this) {
+                if (!FileUtils.getFileStatus(mFile.getPath(), mFileStatus)) {
+                    return true;
+                }
+                return mTimestamp != mFileStatus.mtime;
+            }
+        }
+        
+        public void replace(Map newContents) {
+            if (newContents != null) {
+                synchronized (this) {
+                    mMap = newContents;
+                }
+            }
+        }
+        
+        public void registerOnSharedPreferenceChangeListener(OnSharedPreferenceChangeListener listener) {
+            synchronized(this) {
+                if (!mListeners.contains(listener)) {
+                    mListeners.add(listener);
+                }
+            }
+        }
+
+        public void unregisterOnSharedPreferenceChangeListener(OnSharedPreferenceChangeListener listener) {
+            synchronized(this) {
+                mListeners.remove(listener);
+            }
+        }
+
+        public Map<String, ?> getAll() {
+            synchronized(this) {
+                //noinspection unchecked
+                return new HashMap(mMap);
+            }
+        }
+
+        public String getString(String key, String defValue) {
+            synchronized (this) {
+                String v = (String)mMap.get(key);
+                return v != null ? v : defValue;
+            }
+        }
+
+        public int getInt(String key, int defValue) {
+            synchronized (this) {
+                Integer v = (Integer)mMap.get(key);
+                return v != null ? v : defValue;
+            }
+        }
+        public long getLong(String key, long defValue) {
+            synchronized (this) {
+                Long v = (Long) mMap.get(key);
+                return v != null ? v : defValue;
+            }
+        }
+        public float getFloat(String key, float defValue) {
+            synchronized (this) {
+                Float v = (Float)mMap.get(key);
+                return v != null ? v : defValue;
+            }
+        }
+        public boolean getBoolean(String key, boolean defValue) {
+            synchronized (this) {
+                Boolean v = (Boolean)mMap.get(key);
+                return v != null ? v : defValue;
+            }
+        }
+
+        public boolean contains(String key) {
+            synchronized (this) {
+                return mMap.containsKey(key);
+            }
+        }
+
+        public final class EditorImpl implements Editor {
+            private final Map<String, Object> mModified = Maps.newHashMap();
+            private boolean mClear = false;
+
+            public Editor putString(String key, String value) {
+                synchronized (this) {
+                    mModified.put(key, value);
+                    return this;
+                }
+            }
+            public Editor putInt(String key, int value) {
+                synchronized (this) {
+                    mModified.put(key, value);
+                    return this;
+                }
+            }
+            public Editor putLong(String key, long value) {
+                synchronized (this) {
+                    mModified.put(key, value);
+                    return this;
+                }
+            }
+            public Editor putFloat(String key, float value) {
+                synchronized (this) {
+                    mModified.put(key, value);
+                    return this;
+                }
+            }
+            public Editor putBoolean(String key, boolean value) {
+                synchronized (this) {
+                    mModified.put(key, value);
+                    return this;
+                }
+            }
+
+            public Editor remove(String key) {
+                synchronized (this) {
+                    mModified.put(key, this);
+                    return this;
+                }
+            }
+
+            public Editor clear() {
+                synchronized (this) {
+                    mClear = true;
+                    return this;
+                }
+            }
+
+            public boolean commit() {
+                boolean returnValue;
+
+                boolean hasListeners;
+                List<String> keysModified = null;
+                List<OnSharedPreferenceChangeListener> listeners = null;
+
+                synchronized (SharedPreferencesImpl.this) {
+                    hasListeners = mListeners.size() > 0;
+                    if (hasListeners) {
+                        keysModified = new ArrayList<String>();
+                        listeners = new ArrayList<OnSharedPreferenceChangeListener>(mListeners);
+                    }
+
+                    synchronized (this) {
+                        if (mClear) {
+                            mMap.clear();
+                            mClear = false;
+                        }
+
+                        Iterator<Entry<String, Object>> it = mModified.entrySet().iterator();
+                        while (it.hasNext()) {
+                            Map.Entry<String, Object> e = it.next();
+                            String k = e.getKey();
+                            Object v = e.getValue();
+                            if (v == this) {
+                                mMap.remove(k);
+                            } else {
+                                mMap.put(k, v);
+                            }
+
+                            if (hasListeners) {
+                                keysModified.add(k);
+                            }
+                        }
+
+                        mModified.clear();
+                    }
+
+                    returnValue = writeFileLocked();
+                }
+
+                if (hasListeners) {
+                    for (int i = keysModified.size() - 1; i >= 0; i--) {
+                        final String key = keysModified.get(i);
+                        // Call in the order they were registered
+                        final int listenersSize = listeners.size();
+                        for (int j = 0; j < listenersSize; j++) {
+                            listeners.get(j).onSharedPreferenceChanged(SharedPreferencesImpl.this, key);
+                        }
+                    }
+                }
+
+                return returnValue;
+            }
+        }
+
+        public Editor edit() {
+            return new EditorImpl();
+        }
+        
+        private FileOutputStream createFileOutputStream(File file) {
+            FileOutputStream str = null;
+            try {
+                str = new FileOutputStream(file);
+            } catch (FileNotFoundException e) {
+                File parent = file.getParentFile();
+                if (!parent.mkdir()) {
+                    Log.e(TAG, "Couldn't create directory for SharedPreferences file " + file);
+                    return null;
+                }
+                FileUtils.setPermissions(
+                    parent.getPath(),
+                    FileUtils.S_IRWXU|FileUtils.S_IRWXG|FileUtils.S_IXOTH,
+                    -1, -1);
+                try {
+                    str = new FileOutputStream(file);
+                } catch (FileNotFoundException e2) {
+                    Log.e(TAG, "Couldn't create SharedPreferences file " + file, e2);
+                }
+            }
+            return str;
+        }
+
+        private boolean writeFileLocked() {
+            // Rename the current file so it may be used as a backup during the next read
+            if (mFile.exists()) {
+                if (!mFile.renameTo(mBackupFile)) {
+                    Log.e(TAG, "Couldn't rename file " + mFile + " to backup file " + mBackupFile);
+                }
+            }
+            
+            // Attempt to write the file, delete the backup and return true as atomically as
+            // possible.  If any exception occurs, delete the new file; next time we will restore
+            // from the backup.
+            try {
+                FileOutputStream str = createFileOutputStream(mFile);
+                if (str == null) {
+                    return false;
+                }
+                XmlUtils.writeMapXml(mMap, str);
+                str.close();
+                setFilePermissionsFromMode(mFile.getPath(), mMode, 0);
+                if (FileUtils.getFileStatus(mFile.getPath(), mFileStatus)) {
+                    mTimestamp = mFileStatus.mtime;
+                }
+                
+                // Writing was successful, delete the backup file
+                if (!mBackupFile.delete()) {
+                    Log.e(TAG, "Couldn't delete new backup file " + mBackupFile);
+                }
+                return true;
+            } catch (XmlPullParserException e) {
+                Log.w(TAG, "writeFileLocked: Got exception:", e);
+            } catch (IOException e) {
+                Log.w(TAG, "writeFileLocked: Got exception:", e);
+            }
+            // Clean up an unsuccessfully written file
+            if (mFile.exists()) {
+                if (!mFile.delete()) {
+                    Log.e(TAG, "Couldn't clean up partially-written file " + mFile);
+                }
+            }
+            return false;
+        }
+    }
+
+    private static class WallpaperCallback extends IWallpaperServiceCallback.Stub {
+        private WeakReference<ApplicationContext> mContext;
+
+        public WallpaperCallback(ApplicationContext context) {
+            mContext = new WeakReference<ApplicationContext>(context);
+        }
+
+        public synchronized void onWallpaperChanged() {
+
+            /* The wallpaper has changed but we shouldn't eagerly load the
+             * wallpaper as that would be inefficient. Reset the cached wallpaper
+             * to null so if the user requests the wallpaper again then we'll
+             * fetch it.
+             */
+            final ApplicationContext applicationContext = mContext.get();
+            if (applicationContext != null) {
+                applicationContext.mWallpaper = null;
+            }
+        }
+    }
+}
diff --git a/core/java/android/app/ApplicationLoaders.java b/core/java/android/app/ApplicationLoaders.java
new file mode 100644
index 0000000..2e301c9
--- /dev/null
+++ b/core/java/android/app/ApplicationLoaders.java
@@ -0,0 +1,72 @@
+/*
+ * 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 android.app;
+
+import dalvik.system.PathClassLoader;
+
+import java.util.HashMap;
+
+class ApplicationLoaders
+{
+    public static ApplicationLoaders getDefault()
+    {
+        return gApplicationLoaders;
+    }
+
+    public ClassLoader getClassLoader(String zip, String appDataDir,
+            ClassLoader parent)
+    {
+        /*
+         * This is the parent we use if they pass "null" in.  In theory
+         * this should be the "system" class loader; in practice we
+         * don't use that and can happily (and more efficiently) use the
+         * bootstrap class loader.
+         */
+        ClassLoader baseParent = ClassLoader.getSystemClassLoader().getParent();
+
+        synchronized (mLoaders) {
+            if (parent == null) {
+                parent = baseParent;
+            }
+
+            /*
+             * If we're one step up from the base class loader, find
+             * something in our cache.  Otherwise, we create a whole
+             * new ClassLoader for the zip archive.
+             */
+            if (parent == baseParent) {
+                ClassLoader loader = (ClassLoader)mLoaders.get(zip);
+                if (loader != null) {
+                    return loader;
+                }
+    
+                PathClassLoader pathClassloader =
+                    new PathClassLoader(zip, appDataDir + "/lib", parent);
+                
+                mLoaders.put(zip, pathClassloader);
+                return pathClassloader;
+            }
+
+            return new PathClassLoader(zip, parent);
+        }
+    }
+
+    private final HashMap mLoaders = new HashMap();
+
+    private static final ApplicationLoaders gApplicationLoaders
+        = new ApplicationLoaders();
+}
diff --git a/core/java/android/app/ApplicationThreadNative.java b/core/java/android/app/ApplicationThreadNative.java
new file mode 100644
index 0000000..d2cf55a
--- /dev/null
+++ b/core/java/android/app/ApplicationThreadNative.java
@@ -0,0 +1,658 @@
+/*
+ * 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 android.app;
+
+import android.content.ComponentName;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.ProviderInfo;
+import android.content.pm.ServiceInfo;
+import android.content.res.Configuration;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.ParcelFileDescriptor;
+
+import java.io.FileDescriptor;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/** {@hide} */
+public abstract class ApplicationThreadNative extends Binder
+        implements IApplicationThread {
+    /**
+     * Cast a Binder object into an application thread interface, generating
+     * a proxy if needed.
+     */
+    static public IApplicationThread asInterface(IBinder obj) {
+        if (obj == null) {
+            return null;
+        }
+        IApplicationThread in =
+            (IApplicationThread)obj.queryLocalInterface(descriptor);
+        if (in != null) {
+            return in;
+        }
+        
+        return new ApplicationThreadProxy(obj);
+    }
+    
+    public ApplicationThreadNative() {
+        attachInterface(this, descriptor);
+    }
+    
+    @Override
+    public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
+            throws RemoteException {
+        switch (code) {
+        case SCHEDULE_PAUSE_ACTIVITY_TRANSACTION:
+        {
+            data.enforceInterface(IApplicationThread.descriptor);
+            IBinder b = data.readStrongBinder();
+            boolean finished = data.readInt() != 0;
+            boolean userLeaving = data.readInt() != 0;
+            int configChanges = data.readInt();
+            schedulePauseActivity(b, finished, userLeaving, configChanges);
+            return true;
+        }
+
+        case SCHEDULE_STOP_ACTIVITY_TRANSACTION:
+        {
+            data.enforceInterface(IApplicationThread.descriptor);
+            IBinder b = data.readStrongBinder();
+            boolean show = data.readInt() != 0;
+            int configChanges = data.readInt();
+            scheduleStopActivity(b, show, configChanges);
+            return true;
+        }
+        
+        case SCHEDULE_WINDOW_VISIBILITY_TRANSACTION:
+        {
+            data.enforceInterface(IApplicationThread.descriptor);
+            IBinder b = data.readStrongBinder();
+            boolean show = data.readInt() != 0;
+            scheduleWindowVisibility(b, show);
+            return true;
+        }
+
+        case SCHEDULE_RESUME_ACTIVITY_TRANSACTION:
+        {
+            data.enforceInterface(IApplicationThread.descriptor);
+            IBinder b = data.readStrongBinder();
+            boolean isForward = data.readInt() != 0;
+            scheduleResumeActivity(b, isForward);
+            return true;
+        }
+        
+        case SCHEDULE_SEND_RESULT_TRANSACTION:
+        {
+            data.enforceInterface(IApplicationThread.descriptor);
+            IBinder b = data.readStrongBinder();
+            List<ResultInfo> ri = data.createTypedArrayList(ResultInfo.CREATOR);
+            scheduleSendResult(b, ri);
+            return true;
+        }
+        
+        case SCHEDULE_LAUNCH_ACTIVITY_TRANSACTION:
+        {
+            data.enforceInterface(IApplicationThread.descriptor);
+            Intent intent = Intent.CREATOR.createFromParcel(data);
+            IBinder b = data.readStrongBinder();
+            ActivityInfo info = ActivityInfo.CREATOR.createFromParcel(data);
+            Bundle state = data.readBundle();
+            List<ResultInfo> ri = data.createTypedArrayList(ResultInfo.CREATOR);
+            List<Intent> pi = data.createTypedArrayList(Intent.CREATOR);
+            boolean notResumed = data.readInt() != 0;
+            boolean isForward = data.readInt() != 0;
+            scheduleLaunchActivity(intent, b, info, state, ri, pi, notResumed, isForward);
+            return true;
+        }
+        
+        case SCHEDULE_RELAUNCH_ACTIVITY_TRANSACTION:
+        {
+            data.enforceInterface(IApplicationThread.descriptor);
+            IBinder b = data.readStrongBinder();
+            List<ResultInfo> ri = data.createTypedArrayList(ResultInfo.CREATOR);
+            List<Intent> pi = data.createTypedArrayList(Intent.CREATOR);
+            int configChanges = data.readInt();
+            boolean notResumed = data.readInt() != 0;
+            scheduleRelaunchActivity(b, ri, pi, configChanges, notResumed);
+            return true;
+        }
+        
+        case SCHEDULE_NEW_INTENT_TRANSACTION:
+        {
+            data.enforceInterface(IApplicationThread.descriptor);
+            List<Intent> pi = data.createTypedArrayList(Intent.CREATOR);
+            IBinder b = data.readStrongBinder();
+            scheduleNewIntent(pi, b);
+            return true;
+        }
+
+        case SCHEDULE_FINISH_ACTIVITY_TRANSACTION:
+        {
+            data.enforceInterface(IApplicationThread.descriptor);
+            IBinder b = data.readStrongBinder();
+            boolean finishing = data.readInt() != 0;
+            int configChanges = data.readInt();
+            scheduleDestroyActivity(b, finishing, configChanges);
+            return true;
+        }
+        
+        case SCHEDULE_RECEIVER_TRANSACTION:
+        {
+            data.enforceInterface(IApplicationThread.descriptor);
+            Intent intent = Intent.CREATOR.createFromParcel(data);
+            ActivityInfo info = ActivityInfo.CREATOR.createFromParcel(data);
+            int resultCode = data.readInt();
+            String resultData = data.readString();
+            Bundle resultExtras = data.readBundle();
+            boolean sync = data.readInt() != 0;
+            scheduleReceiver(intent, info, resultCode, resultData,
+                    resultExtras, sync);
+            return true;
+        }
+
+        case SCHEDULE_CREATE_SERVICE_TRANSACTION: {
+            data.enforceInterface(IApplicationThread.descriptor);
+            IBinder token = data.readStrongBinder();
+            ServiceInfo info = ServiceInfo.CREATOR.createFromParcel(data);
+            scheduleCreateService(token, info);
+            return true;
+        }
+
+        case SCHEDULE_BIND_SERVICE_TRANSACTION: {
+            data.enforceInterface(IApplicationThread.descriptor);
+            IBinder token = data.readStrongBinder();
+            Intent intent = Intent.CREATOR.createFromParcel(data);
+            boolean rebind = data.readInt() != 0;
+            scheduleBindService(token, intent, rebind);
+            return true;
+        }
+
+        case SCHEDULE_UNBIND_SERVICE_TRANSACTION: {
+            data.enforceInterface(IApplicationThread.descriptor);
+            IBinder token = data.readStrongBinder();
+            Intent intent = Intent.CREATOR.createFromParcel(data);
+            scheduleUnbindService(token, intent);
+            return true;
+        }
+
+        case SCHEDULE_SERVICE_ARGS_TRANSACTION:
+        {
+            data.enforceInterface(IApplicationThread.descriptor);
+            IBinder token = data.readStrongBinder();
+            int startId = data.readInt();
+            Intent args = Intent.CREATOR.createFromParcel(data);
+            scheduleServiceArgs(token, startId, args);
+            return true;
+        }
+
+        case SCHEDULE_STOP_SERVICE_TRANSACTION:
+        {
+            data.enforceInterface(IApplicationThread.descriptor);
+            IBinder token = data.readStrongBinder();
+            scheduleStopService(token);
+            return true;
+        }
+
+        case BIND_APPLICATION_TRANSACTION:
+        {
+            data.enforceInterface(IApplicationThread.descriptor);
+            String packageName = data.readString();
+            ApplicationInfo info =
+                ApplicationInfo.CREATOR.createFromParcel(data);
+            List<ProviderInfo> providers =
+                data.createTypedArrayList(ProviderInfo.CREATOR);
+            ComponentName testName = (data.readInt() != 0)
+                ? new ComponentName(data) : null;
+            String profileName = data.readString();
+            Bundle testArgs = data.readBundle();
+            IBinder binder = data.readStrongBinder();
+            IInstrumentationWatcher testWatcher = IInstrumentationWatcher.Stub.asInterface(binder);
+            int testMode = data.readInt();
+            Configuration config = Configuration.CREATOR.createFromParcel(data);
+            HashMap<String, IBinder> services = data.readHashMap(null);
+            bindApplication(packageName, info,
+                            providers, testName, profileName,
+                            testArgs, testWatcher, testMode, config, services);
+            return true;
+        }
+        
+        case SCHEDULE_EXIT_TRANSACTION:
+        {
+            data.enforceInterface(IApplicationThread.descriptor);
+            scheduleExit();
+            return true;
+        }
+
+        case REQUEST_THUMBNAIL_TRANSACTION:
+        {
+            data.enforceInterface(IApplicationThread.descriptor);
+            IBinder b = data.readStrongBinder();
+            requestThumbnail(b);
+            return true;
+        }
+        
+        case SCHEDULE_CONFIGURATION_CHANGED_TRANSACTION:
+        {
+            data.enforceInterface(IApplicationThread.descriptor);
+            Configuration config = Configuration.CREATOR.createFromParcel(data);
+            scheduleConfigurationChanged(config);
+            return true;
+        }
+
+        case UPDATE_TIME_ZONE_TRANSACTION: {
+            data.enforceInterface(IApplicationThread.descriptor);
+            updateTimeZone();
+            return true;
+        }
+
+        case PROCESS_IN_BACKGROUND_TRANSACTION: {
+            data.enforceInterface(IApplicationThread.descriptor);
+            processInBackground();
+            return true;
+        }
+        
+        case DUMP_SERVICE_TRANSACTION: {
+            data.enforceInterface(IApplicationThread.descriptor);
+            ParcelFileDescriptor fd = data.readFileDescriptor();
+            final IBinder service = data.readStrongBinder();
+            final String[] args = data.readStringArray();
+            if (fd != null) {
+                dumpService(fd.getFileDescriptor(), service, args);
+                try {
+                    fd.close();
+                } catch (IOException e) {
+                }
+            }
+            return true;
+        }
+        
+        case SCHEDULE_REGISTERED_RECEIVER_TRANSACTION: {
+            data.enforceInterface(IApplicationThread.descriptor);
+            IIntentReceiver receiver = IIntentReceiver.Stub.asInterface(
+                    data.readStrongBinder());
+            Intent intent = Intent.CREATOR.createFromParcel(data);
+            int resultCode = data.readInt();
+            String dataStr = data.readString();
+            Bundle extras = data.readBundle();
+            boolean ordered = data.readInt() != 0;
+            scheduleRegisteredReceiver(receiver, intent,
+                    resultCode, dataStr, extras, ordered);
+            return true;
+        }
+
+        case SCHEDULE_LOW_MEMORY_TRANSACTION:
+        {
+            scheduleLowMemory();
+            return true;
+        }
+        
+        case SCHEDULE_ACTIVITY_CONFIGURATION_CHANGED_TRANSACTION:
+        {
+            data.enforceInterface(IApplicationThread.descriptor);
+            IBinder b = data.readStrongBinder();
+            scheduleActivityConfigurationChanged(b);
+            return true;
+        }
+        
+        case REQUEST_PSS_TRANSACTION:
+        {
+            data.enforceInterface(IApplicationThread.descriptor);
+            requestPss();
+            return true;
+        }
+        }
+
+        return super.onTransact(code, data, reply, flags);
+    }
+
+    public IBinder asBinder()
+    {
+        return this;
+    }
+}
+
+class ApplicationThreadProxy implements IApplicationThread {
+    private final IBinder mRemote;
+    
+    public ApplicationThreadProxy(IBinder remote) {
+        mRemote = remote;
+    }
+    
+    public final IBinder asBinder() {
+        return mRemote;
+    }
+    
+    public final void schedulePauseActivity(IBinder token, boolean finished,
+            boolean userLeaving, int configChanges) throws RemoteException {
+        Parcel data = Parcel.obtain();
+        data.writeInterfaceToken(IApplicationThread.descriptor);
+        data.writeStrongBinder(token);
+        data.writeInt(finished ? 1 : 0);
+        data.writeInt(userLeaving ? 1 :0);
+        data.writeInt(configChanges);
+        mRemote.transact(SCHEDULE_PAUSE_ACTIVITY_TRANSACTION, data, null,
+                IBinder.FLAG_ONEWAY);
+        data.recycle();
+    }
+
+    public final void scheduleStopActivity(IBinder token, boolean showWindow,
+            int configChanges) throws RemoteException {
+        Parcel data = Parcel.obtain();
+        data.writeInterfaceToken(IApplicationThread.descriptor);
+        data.writeStrongBinder(token);
+        data.writeInt(showWindow ? 1 : 0);
+        data.writeInt(configChanges);
+        mRemote.transact(SCHEDULE_STOP_ACTIVITY_TRANSACTION, data, null,
+                IBinder.FLAG_ONEWAY);
+        data.recycle();
+    }
+
+    public final void scheduleWindowVisibility(IBinder token,
+            boolean showWindow) throws RemoteException {
+        Parcel data = Parcel.obtain();
+        data.writeInterfaceToken(IApplicationThread.descriptor);
+        data.writeStrongBinder(token);
+        data.writeInt(showWindow ? 1 : 0);
+        mRemote.transact(SCHEDULE_WINDOW_VISIBILITY_TRANSACTION, data, null,
+                IBinder.FLAG_ONEWAY);
+        data.recycle();
+    }
+
+    public final void scheduleResumeActivity(IBinder token, boolean isForward)
+            throws RemoteException {
+        Parcel data = Parcel.obtain();
+        data.writeInterfaceToken(IApplicationThread.descriptor);
+        data.writeStrongBinder(token);
+        data.writeInt(isForward ? 1 : 0);
+        mRemote.transact(SCHEDULE_RESUME_ACTIVITY_TRANSACTION, data, null,
+                IBinder.FLAG_ONEWAY);
+        data.recycle();
+    }
+
+    public final void scheduleSendResult(IBinder token, List<ResultInfo> results)
+    		throws RemoteException {
+        Parcel data = Parcel.obtain();
+        data.writeInterfaceToken(IApplicationThread.descriptor);
+        data.writeStrongBinder(token);
+        data.writeTypedList(results);
+        mRemote.transact(SCHEDULE_SEND_RESULT_TRANSACTION, data, null,
+                IBinder.FLAG_ONEWAY);
+        data.recycle();
+    }
+
+    public final void scheduleLaunchActivity(Intent intent, IBinder token,
+            ActivityInfo info, Bundle state, List<ResultInfo> pendingResults,
+    		List<Intent> pendingNewIntents, boolean notResumed, boolean isForward)
+    		throws RemoteException {
+        Parcel data = Parcel.obtain();
+        data.writeInterfaceToken(IApplicationThread.descriptor);
+        intent.writeToParcel(data, 0);
+        data.writeStrongBinder(token);
+        info.writeToParcel(data, 0);
+        data.writeBundle(state);
+        data.writeTypedList(pendingResults);
+        data.writeTypedList(pendingNewIntents);
+        data.writeInt(notResumed ? 1 : 0);
+        data.writeInt(isForward ? 1 : 0);
+        mRemote.transact(SCHEDULE_LAUNCH_ACTIVITY_TRANSACTION, data, null,
+                IBinder.FLAG_ONEWAY);
+        data.recycle();
+    }
+
+    public final void scheduleRelaunchActivity(IBinder token,
+            List<ResultInfo> pendingResults, List<Intent> pendingNewIntents,
+            int configChanges, boolean notResumed) throws RemoteException {
+        Parcel data = Parcel.obtain();
+        data.writeInterfaceToken(IApplicationThread.descriptor);
+        data.writeStrongBinder(token);
+        data.writeTypedList(pendingResults);
+        data.writeTypedList(pendingNewIntents);
+        data.writeInt(configChanges);
+        data.writeInt(notResumed ? 1 : 0);
+        mRemote.transact(SCHEDULE_RELAUNCH_ACTIVITY_TRANSACTION, data, null,
+                IBinder.FLAG_ONEWAY);
+        data.recycle();
+    }
+
+    public void scheduleNewIntent(List<Intent> intents, IBinder token)
+            throws RemoteException {
+        Parcel data = Parcel.obtain();
+        data.writeInterfaceToken(IApplicationThread.descriptor);
+        data.writeTypedList(intents);
+        data.writeStrongBinder(token);
+        mRemote.transact(SCHEDULE_NEW_INTENT_TRANSACTION, data, null,
+                IBinder.FLAG_ONEWAY);
+        data.recycle();
+    }
+
+    public final void scheduleDestroyActivity(IBinder token, boolean finishing,
+            int configChanges) throws RemoteException {
+        Parcel data = Parcel.obtain();
+        data.writeInterfaceToken(IApplicationThread.descriptor);
+        data.writeStrongBinder(token);
+        data.writeInt(finishing ? 1 : 0);
+        data.writeInt(configChanges);
+        mRemote.transact(SCHEDULE_FINISH_ACTIVITY_TRANSACTION, data, null,
+                IBinder.FLAG_ONEWAY);
+        data.recycle();
+    }
+    
+    public final void scheduleReceiver(Intent intent, ActivityInfo info,
+            int resultCode, String resultData,
+            Bundle map, boolean sync) throws RemoteException {
+        Parcel data = Parcel.obtain();
+        data.writeInterfaceToken(IApplicationThread.descriptor);
+        intent.writeToParcel(data, 0);
+        info.writeToParcel(data, 0);
+        data.writeInt(resultCode);
+        data.writeString(resultData);
+        data.writeBundle(map);
+        data.writeInt(sync ? 1 : 0);
+        mRemote.transact(SCHEDULE_RECEIVER_TRANSACTION, data, null,
+                IBinder.FLAG_ONEWAY);
+        data.recycle();
+    }
+
+    public final void scheduleCreateService(IBinder token, ServiceInfo info)
+            throws RemoteException {
+        Parcel data = Parcel.obtain();
+        data.writeInterfaceToken(IApplicationThread.descriptor);
+        data.writeStrongBinder(token);
+        info.writeToParcel(data, 0);
+        mRemote.transact(SCHEDULE_CREATE_SERVICE_TRANSACTION, data, null,
+                IBinder.FLAG_ONEWAY);
+        data.recycle();
+    }
+
+    public final void scheduleBindService(IBinder token, Intent intent, boolean rebind)
+            throws RemoteException {
+        Parcel data = Parcel.obtain();
+        data.writeInterfaceToken(IApplicationThread.descriptor);
+        data.writeStrongBinder(token);
+        intent.writeToParcel(data, 0);
+        data.writeInt(rebind ? 1 : 0);
+        mRemote.transact(SCHEDULE_BIND_SERVICE_TRANSACTION, data, null,
+                IBinder.FLAG_ONEWAY);
+        data.recycle();
+    }
+
+    public final void scheduleUnbindService(IBinder token, Intent intent)
+            throws RemoteException {
+        Parcel data = Parcel.obtain();
+        data.writeInterfaceToken(IApplicationThread.descriptor);
+        data.writeStrongBinder(token);
+        intent.writeToParcel(data, 0);
+        mRemote.transact(SCHEDULE_UNBIND_SERVICE_TRANSACTION, data, null,
+                IBinder.FLAG_ONEWAY);
+        data.recycle();
+    }
+
+    public final void scheduleServiceArgs(IBinder token, int startId,
+	    Intent args) throws RemoteException {
+        Parcel data = Parcel.obtain();
+        data.writeInterfaceToken(IApplicationThread.descriptor);
+        data.writeStrongBinder(token);
+        data.writeInt(startId);
+        args.writeToParcel(data, 0);
+        mRemote.transact(SCHEDULE_SERVICE_ARGS_TRANSACTION, data, null,
+                IBinder.FLAG_ONEWAY);
+        data.recycle();
+    }
+
+    public final void scheduleStopService(IBinder token)
+            throws RemoteException {
+        Parcel data = Parcel.obtain();
+        data.writeInterfaceToken(IApplicationThread.descriptor);
+        data.writeStrongBinder(token);
+        mRemote.transact(SCHEDULE_STOP_SERVICE_TRANSACTION, data, null,
+                IBinder.FLAG_ONEWAY);
+        data.recycle();
+    }
+
+    public final void bindApplication(String packageName, ApplicationInfo info,
+            List<ProviderInfo> providers, ComponentName testName,
+            String profileName, Bundle testArgs, IInstrumentationWatcher testWatcher, int debugMode,
+            Configuration config, Map<String, IBinder> services) throws RemoteException {
+        Parcel data = Parcel.obtain();
+        data.writeInterfaceToken(IApplicationThread.descriptor);
+        data.writeString(packageName);
+        info.writeToParcel(data, 0);
+        data.writeTypedList(providers);
+        if (testName == null) {
+            data.writeInt(0);
+        } else {
+            data.writeInt(1);
+            testName.writeToParcel(data, 0);
+        }
+        data.writeString(profileName);
+        data.writeBundle(testArgs);
+        data.writeStrongInterface(testWatcher);
+        data.writeInt(debugMode);
+        config.writeToParcel(data, 0);
+        data.writeMap(services);
+        mRemote.transact(BIND_APPLICATION_TRANSACTION, data, null,
+                IBinder.FLAG_ONEWAY);
+        data.recycle();
+    }
+    
+    public final void scheduleExit() throws RemoteException {
+        Parcel data = Parcel.obtain();
+        data.writeInterfaceToken(IApplicationThread.descriptor);
+        mRemote.transact(SCHEDULE_EXIT_TRANSACTION, data, null,
+                IBinder.FLAG_ONEWAY);
+        data.recycle();
+    }
+    
+    public final void requestThumbnail(IBinder token)
+            throws RemoteException {
+        Parcel data = Parcel.obtain();
+        data.writeInterfaceToken(IApplicationThread.descriptor);
+        data.writeStrongBinder(token);
+        mRemote.transact(REQUEST_THUMBNAIL_TRANSACTION, data, null,
+                IBinder.FLAG_ONEWAY);
+        data.recycle();
+    }
+
+    public final void scheduleConfigurationChanged(Configuration config)
+            throws RemoteException {
+        Parcel data = Parcel.obtain();
+        data.writeInterfaceToken(IApplicationThread.descriptor);
+        config.writeToParcel(data, 0);
+        mRemote.transact(SCHEDULE_CONFIGURATION_CHANGED_TRANSACTION, data, null,
+                IBinder.FLAG_ONEWAY);
+        data.recycle();
+    }
+
+    public void updateTimeZone() throws RemoteException {
+        Parcel data = Parcel.obtain();
+        data.writeInterfaceToken(IApplicationThread.descriptor);
+        mRemote.transact(UPDATE_TIME_ZONE_TRANSACTION, data, null,
+                IBinder.FLAG_ONEWAY);
+        data.recycle();
+    }
+
+    public void processInBackground() throws RemoteException {
+        Parcel data = Parcel.obtain();
+        data.writeInterfaceToken(IApplicationThread.descriptor);
+        mRemote.transact(PROCESS_IN_BACKGROUND_TRANSACTION, data, null,
+                IBinder.FLAG_ONEWAY);
+        data.recycle();
+    }
+
+    public void dumpService(FileDescriptor fd, IBinder token, String[] args)
+            throws RemoteException {
+        Parcel data = Parcel.obtain();
+        data.writeInterfaceToken(IApplicationThread.descriptor);
+        data.writeFileDescriptor(fd);
+        data.writeStrongBinder(token);
+        data.writeStringArray(args);
+        mRemote.transact(DUMP_SERVICE_TRANSACTION, data, null, 0);
+        data.recycle();
+    }
+    
+    public void scheduleRegisteredReceiver(IIntentReceiver receiver, Intent intent,
+            int resultCode, String dataStr, Bundle extras, boolean ordered)
+            throws RemoteException {
+        Parcel data = Parcel.obtain();
+        data.writeInterfaceToken(IApplicationThread.descriptor);
+        data.writeStrongBinder(receiver.asBinder());
+        intent.writeToParcel(data, 0);
+        data.writeInt(resultCode);
+        data.writeString(dataStr);
+        data.writeBundle(extras);
+        data.writeInt(ordered ? 1 : 0);
+        mRemote.transact(SCHEDULE_REGISTERED_RECEIVER_TRANSACTION, data, null,
+                IBinder.FLAG_ONEWAY);
+        data.recycle();
+    }
+
+    public final void scheduleLowMemory() throws RemoteException {
+        Parcel data = Parcel.obtain();
+        data.writeInterfaceToken(IApplicationThread.descriptor);
+        mRemote.transact(SCHEDULE_LOW_MEMORY_TRANSACTION, data, null,
+                IBinder.FLAG_ONEWAY);
+        data.recycle();
+    }
+    
+    public final void scheduleActivityConfigurationChanged(
+            IBinder token) throws RemoteException {
+        Parcel data = Parcel.obtain();
+        data.writeInterfaceToken(IApplicationThread.descriptor);
+        data.writeStrongBinder(token);
+        mRemote.transact(SCHEDULE_ACTIVITY_CONFIGURATION_CHANGED_TRANSACTION, data, null,
+                IBinder.FLAG_ONEWAY);
+        data.recycle();
+    }
+    
+    public final void requestPss() throws RemoteException {
+        Parcel data = Parcel.obtain();
+        data.writeInterfaceToken(IApplicationThread.descriptor);
+        mRemote.transact(REQUEST_PSS_TRANSACTION, data, null,
+                IBinder.FLAG_ONEWAY);
+        data.recycle();
+    }
+    
+}
+
diff --git a/core/java/android/app/DatePickerDialog.java b/core/java/android/app/DatePickerDialog.java
new file mode 100644
index 0000000..ee5e0d5
--- /dev/null
+++ b/core/java/android/app/DatePickerDialog.java
@@ -0,0 +1,185 @@
+/*
+ * Copyright (C) 2007 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 android.app;
+
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.DialogInterface.OnClickListener;
+import android.os.Bundle;
+import android.text.TextUtils.TruncateAt;
+import android.text.format.DateFormat;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.DatePicker;
+import android.widget.TextView;
+import android.widget.DatePicker.OnDateChangedListener;
+
+import com.android.internal.R;
+
+import java.text.DateFormatSymbols;
+import java.util.Calendar;
+
+/**
+ * A simple dialog containing an {@link android.widget.DatePicker}.
+ */
+public class DatePickerDialog extends AlertDialog implements OnClickListener, 
+        OnDateChangedListener {
+
+    private static final String YEAR = "year";
+    private static final String MONTH = "month";
+    private static final String DAY = "day";
+    
+    private final DatePicker mDatePicker;
+    private final OnDateSetListener mCallBack;
+    private final Calendar mCalendar;
+    private final java.text.DateFormat mDateFormat;
+    private final String[] mWeekDays;
+
+    private int mInitialYear;
+    private int mInitialMonth;
+    private int mInitialDay;
+
+    /**
+     * The callback used to indicate the user is done filling in the date.
+     */
+    public interface OnDateSetListener {
+
+        /**
+         * @param view The view associated with this listener.
+         * @param year The year that was set.
+         * @param monthOfYear The month that was set (0-11) for compatibility
+         *  with {@link java.util.Calendar}.
+         * @param dayOfMonth The day of the month that was set.
+         */
+        void onDateSet(DatePicker view, int year, int monthOfYear, int dayOfMonth);
+    }
+
+    /**
+     * @param context The context the dialog is to run in.
+     * @param callBack How the parent is notified that the date is set.
+     * @param year The initial year of the dialog.
+     * @param monthOfYear The initial month of the dialog.
+     * @param dayOfMonth The initial day of the dialog.
+     */
+    public DatePickerDialog(Context context,
+            OnDateSetListener callBack,
+            int year,
+            int monthOfYear,
+            int dayOfMonth) {
+        this(context, com.android.internal.R.style.Theme_Dialog_Alert, 
+                callBack, year, monthOfYear, dayOfMonth);
+    }
+
+    /**
+     * @param context The context the dialog is to run in.
+     * @param theme the theme to apply to this dialog
+     * @param callBack How the parent is notified that the date is set.
+     * @param year The initial year of the dialog.
+     * @param monthOfYear The initial month of the dialog.
+     * @param dayOfMonth The initial day of the dialog.
+     */
+    public DatePickerDialog(Context context,
+            int theme,
+            OnDateSetListener callBack,
+            int year,
+            int monthOfYear,
+            int dayOfMonth) {
+        super(context, theme);
+
+        mCallBack = callBack;
+        mInitialYear = year;
+        mInitialMonth = monthOfYear;
+        mInitialDay = dayOfMonth;
+        DateFormatSymbols symbols = new DateFormatSymbols();
+        mWeekDays = symbols.getShortWeekdays();
+        
+        mDateFormat = DateFormat.getMediumDateFormat(context);
+        mCalendar = Calendar.getInstance();
+        updateTitle(mInitialYear, mInitialMonth, mInitialDay);
+        
+        setButton(context.getText(R.string.date_time_set), this);
+        setButton2(context.getText(R.string.cancel), (OnClickListener) null);
+        setIcon(R.drawable.ic_dialog_time);
+        
+        LayoutInflater inflater = 
+                (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+        View view = inflater.inflate(R.layout.date_picker_dialog, null);
+        setView(view);
+        mDatePicker = (DatePicker) view.findViewById(R.id.datePicker);
+        mDatePicker.init(mInitialYear, mInitialMonth, mInitialDay, this);
+    }
+
+    @Override
+    public void show() {
+        super.show();
+        
+        /* Sometimes the full month is displayed causing the title
+         * to be very long, in those cases ensure it doesn't wrap to
+         * 2 lines (as that looks jumpy) and ensure we ellipsize the end.
+         */
+        TextView title = (TextView) findViewById(R.id.alertTitle);
+        title.setSingleLine();
+        title.setEllipsize(TruncateAt.END);
+    }
+    
+    public void onClick(DialogInterface dialog, int which) {
+        if (mCallBack != null) {
+            mDatePicker.clearFocus();
+            mCallBack.onDateSet(mDatePicker, mDatePicker.getYear(), 
+                    mDatePicker.getMonth(), mDatePicker.getDayOfMonth());
+        }
+    }
+    
+    public void onDateChanged(DatePicker view, int year,
+            int month, int day) {
+        updateTitle(year, month, day);
+    }
+    
+    public void updateDate(int year, int monthOfYear, int dayOfMonth) {
+        mInitialYear = year;
+        mInitialMonth = monthOfYear;
+        mInitialDay = dayOfMonth;
+        mDatePicker.updateDate(year, monthOfYear, dayOfMonth);
+    }
+
+    private void updateTitle(int year, int month, int day) {
+        mCalendar.set(Calendar.YEAR, year);
+        mCalendar.set(Calendar.MONTH, month);
+        mCalendar.set(Calendar.DAY_OF_MONTH, day);
+        String weekday = mWeekDays[mCalendar.get(Calendar.DAY_OF_WEEK)];
+        setTitle(weekday + ", " + mDateFormat.format(mCalendar.getTime()));
+    }
+    
+    @Override
+    public Bundle onSaveInstanceState() {
+        Bundle state = super.onSaveInstanceState();
+        state.putInt(YEAR, mDatePicker.getYear());
+        state.putInt(MONTH, mDatePicker.getMonth());
+        state.putInt(DAY, mDatePicker.getDayOfMonth());
+        return state;
+    }
+    
+    @Override
+    public void onRestoreInstanceState(Bundle savedInstanceState) {
+        super.onRestoreInstanceState(savedInstanceState);
+        int year = savedInstanceState.getInt(YEAR);
+        int month = savedInstanceState.getInt(MONTH);
+        int day = savedInstanceState.getInt(DAY);
+        mDatePicker.init(year, month, day, this);
+        updateTitle(year, month, day);
+    }
+}
diff --git a/core/java/android/app/Dialog.java b/core/java/android/app/Dialog.java
new file mode 100644
index 0000000..b09a57f
--- /dev/null
+++ b/core/java/android/app/Dialog.java
@@ -0,0 +1,954 @@
+/*
+ * 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 android.app;
+
+import android.content.Context;
+import android.content.DialogInterface;
+import android.graphics.drawable.Drawable;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.Message;
+import android.os.Bundle;
+import android.util.Config;
+import android.util.Log;
+import android.view.ContextMenu;
+import android.view.ContextThemeWrapper;
+import android.view.Gravity;
+import android.view.KeyEvent;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewConfiguration;
+import android.view.ViewGroup;
+import android.view.LayoutInflater;
+import android.view.Window;
+import android.view.WindowManager;
+import android.view.ContextMenu.ContextMenuInfo;
+import android.view.View.OnCreateContextMenuListener;
+
+import com.android.internal.policy.PolicyManager;
+
+import java.lang.ref.WeakReference;
+
+/**
+ * Base class for Dialogs.
+ * 
+ * <p>Note: Activities provide a facility to manage the creation, saving and
+ * restoring of dialogs. See {@link Activity#onCreateDialog(int)},
+ * {@link Activity#onPrepareDialog(int, Dialog)},
+ * {@link Activity#showDialog(int)}, and {@link Activity#dismissDialog(int)}. If
+ * these methods are used, {@link #getOwnerActivity()} will return the Activity
+ * that managed this dialog.
+ * 
+ * <p>Often you will want to have a Dialog display on top of the current
+ * input method, because there is no reason for it to accept text.  You can
+ * do this by setting the {@link WindowManager.LayoutParams#FLAG_ALT_FOCUSABLE_IM
+ * WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM} window flag (assuming
+ * your Dialog takes input focus, as it the default) with the following code:
+ * 
+ * <pre>
+ *     getWindow().setFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM,
+ *             WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM);
+ * </pre>
+ */
+public class Dialog implements DialogInterface, Window.Callback,
+        KeyEvent.Callback, OnCreateContextMenuListener {
+    private static final String LOG_TAG = "Dialog";
+
+    private Activity mOwnerActivity;
+    
+    final Context mContext;
+    final WindowManager mWindowManager;
+    Window mWindow;
+    View mDecor;
+    /**
+     * This field should be made private, so it is hidden from the SDK.
+     * {@hide}
+     */
+    protected boolean mCancelable = true;
+    private Message mCancelMessage;
+    private Message mDismissMessage;
+
+    /**
+     * Whether to cancel the dialog when a touch is received outside of the
+     * window's bounds.
+     */
+    private boolean mCanceledOnTouchOutside = false;
+    
+    private OnKeyListener mOnKeyListener;
+
+    private boolean mCreated = false;
+    private boolean mShowing = false;
+
+    private final Thread mUiThread;
+    private final Handler mHandler = new Handler();
+
+    private final Runnable mDismissAction = new Runnable() {
+        public void run() {
+            dismissDialog();
+        }
+    };
+
+    /**
+     * Create a Dialog window that uses the default dialog frame style.
+     * 
+     * @param context The Context the Dialog is to run it.  In particular, it
+     *                uses the window manager and theme in this context to
+     *                present its UI.
+     */
+    public Dialog(Context context) {
+        this(context, 0);
+    }
+
+    /**
+     * Create a Dialog window that uses a custom dialog style.
+     * 
+     * @param context The Context in which the Dialog should run. In particular, it
+     *                uses the window manager and theme from this context to
+     *                present its UI.
+     * @param theme A style resource describing the theme to use for the 
+     * window. See <a href="{@docRoot}guide/topics/resources/available-resources.html#stylesandthemes">Style 
+     * and Theme Resources</a> for more information about defining and using 
+     * styles.  This theme is applied on top of the current theme in 
+     * <var>context</var>.  If 0, the default dialog theme will be used.
+     */
+    public Dialog(Context context, int theme) {
+        mContext = new ContextThemeWrapper(
+            context, theme == 0 ? com.android.internal.R.style.Theme_Dialog : theme);
+        mWindowManager = (WindowManager)context.getSystemService("window");
+        Window w = PolicyManager.makeNewWindow(mContext);
+        mWindow = w;
+        w.setCallback(this);
+        w.setWindowManager(mWindowManager, null, null);
+        w.setGravity(Gravity.CENTER);
+        mUiThread = Thread.currentThread();
+        mDismissCancelHandler = new DismissCancelHandler(this);
+    }
+
+    /**
+     * @deprecated
+     * @hide
+     */
+    @Deprecated
+    protected Dialog(Context context, boolean cancelable,
+            Message cancelCallback) {
+        this(context);
+        mCancelable = cancelable;
+        mCancelMessage = cancelCallback;
+    }
+
+    protected Dialog(Context context, boolean cancelable,
+            OnCancelListener cancelListener) {
+        this(context);
+        mCancelable = cancelable;
+        setOnCancelListener(cancelListener);
+    }
+
+    /**
+     * Retrieve the Context this Dialog is running in.
+     * 
+     * @return Context The Context that was supplied to the constructor.
+     */
+    public final Context getContext() {
+        return mContext;
+    }
+
+    /**
+     * Sets the Activity that owns this dialog. An example use: This Dialog will
+     * use the suggested volume control stream of the Activity.
+     * 
+     * @param activity The Activity that owns this dialog.
+     */
+    public final void setOwnerActivity(Activity activity) {
+        mOwnerActivity = activity;
+        
+        getWindow().setVolumeControlStream(mOwnerActivity.getVolumeControlStream());
+    }
+
+    /**
+     * Returns the Activity that owns this Dialog. For example, if
+     * {@link Activity#showDialog(int)} is used to show this Dialog, that
+     * Activity will be the owner (by default). Depending on how this dialog was
+     * created, this may return null.
+     * 
+     * @return The Activity that owns this Dialog.
+     */
+    public final Activity getOwnerActivity() {
+        return mOwnerActivity;
+    }
+    
+    /**
+     * @return Whether the dialog is currently showing.
+     */
+    public boolean isShowing() {
+        return mShowing;
+    }
+
+    /**
+     * Start the dialog and display it on screen.  The window is placed in the
+     * application layer and opaque.  Note that you should not override this
+     * method to do initialization when the dialog is shown, instead implement
+     * that in {@link #onStart}.
+     */
+    public void show() {
+        if (mShowing) {
+            if (Config.LOGV) Log.v(LOG_TAG,
+                    "[Dialog] start: already showing, ignore");
+            if (mDecor != null) mDecor.setVisibility(View.VISIBLE);
+            return;
+        }
+
+        if (!mCreated) {
+            dispatchOnCreate(null);
+        }
+
+        onStart();
+        mDecor = mWindow.getDecorView();
+        WindowManager.LayoutParams l = mWindow.getAttributes();
+        if ((l.softInputMode
+                & WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) == 0) {
+            WindowManager.LayoutParams nl = new WindowManager.LayoutParams();
+            nl.copyFrom(l);
+            nl.softInputMode |=
+                    WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION;
+            l = nl;
+        }
+        mWindowManager.addView(mDecor, l);
+        mShowing = true;
+    }
+    
+    /**
+     * Hide the dialog, but do not dismiss it.
+     */
+    public void hide() {
+        if (mDecor != null) mDecor.setVisibility(View.GONE);
+    }
+
+    /**
+     * Dismiss this dialog, removing it from the screen. This method can be
+     * invoked safely from any thread.  Note that you should not override this
+     * method to do cleanup when the dialog is dismissed, instead implement
+     * that in {@link #onStop}.
+     */
+    public void dismiss() {
+        if (Thread.currentThread() != mUiThread) {
+            mHandler.post(mDismissAction);
+        } else {
+            mDismissAction.run();
+        }
+    }
+
+    private void dismissDialog() {
+        if (mDecor == null) {
+            if (Config.LOGV) Log.v(LOG_TAG,
+                    "[Dialog] dismiss: already dismissed, ignore");
+            return;
+        }
+        if (!mShowing) {
+            if (Config.LOGV) Log.v(LOG_TAG,
+                    "[Dialog] dismiss: not showing, ignore");
+            return;
+        }
+
+        mWindowManager.removeView(mDecor);
+        mDecor = null;
+        mWindow.closeAllPanels();
+        onStop();
+        mShowing = false;
+        
+        sendDismissMessage();
+    }
+
+    private void sendDismissMessage() {
+        if (mDismissMessage != null) {
+            // Obtain a new message so this dialog can be re-used
+            Message.obtain(mDismissMessage).sendToTarget();
+        }
+    }
+    
+    // internal method to make sure mcreated is set properly without requiring
+    // users to call through to super in onCreate
+    void dispatchOnCreate(Bundle savedInstanceState) {
+        onCreate(savedInstanceState);
+        mCreated = true;
+    }
+
+    /**
+     * Similar to {@link Activity#onCreate}, you should initialized your dialog
+     * in this method, including calling {@link #setContentView}.
+     * @param savedInstanceState If this dialog is being reinitalized after a
+     *     the hosting activity was previously shut down, holds the result from
+     *     the most recent call to {@link #onSaveInstanceState}, or null if this
+     *     is the first time.
+     */
+    protected void onCreate(Bundle savedInstanceState) {
+    }
+
+    /**
+     * Called when the dialog is starting.
+     */
+    protected void onStart() {
+    }
+
+    /**
+     * Called to tell you that you're stopping.
+     */
+    protected void onStop() {
+    }
+
+    private static final String DIALOG_SHOWING_TAG = "android:dialogShowing";
+    private static final String DIALOG_HIERARCHY_TAG = "android:dialogHierarchy";
+
+    /**
+     * Saves the state of the dialog into a bundle.
+     *
+     * The default implementation saves the state of its view hierarchy, so you'll
+     * likely want to call through to super if you override this to save additional
+     * state.
+     * @return A bundle with the state of the dialog.
+     */
+    public Bundle onSaveInstanceState() {
+        Bundle bundle = new Bundle();
+        bundle.putBoolean(DIALOG_SHOWING_TAG, mShowing);
+        if (mCreated) {
+            bundle.putBundle(DIALOG_HIERARCHY_TAG, mWindow.saveHierarchyState());
+        }
+        return bundle;
+    }
+
+    /**
+     * Restore the state of the dialog from a previously saved bundle.
+     *
+     * The default implementation restores the state of the dialog's view
+     * hierarchy that was saved in the default implementation of {@link #onSaveInstanceState()},
+     * so be sure to call through to super when overriding unless you want to
+     * do all restoring of state yourself.
+     * @param savedInstanceState The state of the dialog previously saved by
+     *     {@link #onSaveInstanceState()}.
+     */
+    public void onRestoreInstanceState(Bundle savedInstanceState) {
+        final Bundle dialogHierarchyState = savedInstanceState.getBundle(DIALOG_HIERARCHY_TAG);
+        if (dialogHierarchyState == null) {
+            // dialog has never been shown, or onCreated, nothing to restore.
+            return;
+        }
+        dispatchOnCreate(savedInstanceState);
+        mWindow.restoreHierarchyState(dialogHierarchyState);
+        if (savedInstanceState.getBoolean(DIALOG_SHOWING_TAG)) {
+            show();
+        }
+    }
+
+    /**
+     * Retrieve the current Window for the activity.  This can be used to
+     * directly access parts of the Window API that are not available
+     * through Activity/Screen.
+     * 
+     * @return Window The current window, or null if the activity is not
+     *         visual.
+     */
+    public Window getWindow() {
+        return mWindow;
+    }
+
+    /**
+     * Call {@link android.view.Window#getCurrentFocus} on the
+     * Window if this Activity to return the currently focused view.
+     * 
+     * @return View The current View with focus or null.
+     * 
+     * @see #getWindow
+     * @see android.view.Window#getCurrentFocus
+     */
+    public View getCurrentFocus() {
+        return mWindow != null ? mWindow.getCurrentFocus() : null;
+    }
+
+    /**
+     * Finds a view that was identified by the id attribute from the XML that
+     * was processed in {@link #onStart}.
+     *
+     * @param id the identifier of the view to find
+     * @return The view if found or null otherwise.
+     */
+    public View findViewById(int id) {
+        return mWindow.findViewById(id);
+    }
+
+    /**
+     * Set the screen content from a layout resource.  The resource will be
+     * inflated, adding all top-level views to the screen.
+     * 
+     * @param layoutResID Resource ID to be inflated.
+     */
+    public void setContentView(int layoutResID) {
+        mWindow.setContentView(layoutResID);
+    }
+
+    /**
+     * Set the screen content to an explicit view.  This view is placed
+     * directly into the screen's view hierarchy.  It can itself be a complex
+     * view hierarhcy.
+     * 
+     * @param view The desired content to display.
+     */
+    public void setContentView(View view) {
+        mWindow.setContentView(view);
+    }
+
+    /**
+     * Set the screen content to an explicit view.  This view is placed
+     * directly into the screen's view hierarchy.  It can itself be a complex
+     * view hierarhcy.
+     * 
+     * @param view The desired content to display.
+     * @param params Layout parameters for the view.
+     */
+    public void setContentView(View view, ViewGroup.LayoutParams params) {
+        mWindow.setContentView(view, params);
+    }
+
+    /**
+     * Add an additional content view to the screen.  Added after any existing
+     * ones in the screen -- existing views are NOT removed.
+     * 
+     * @param view The desired content to display.
+     * @param params Layout parameters for the view.
+     */
+    public void addContentView(View view, ViewGroup.LayoutParams params) {
+        mWindow.addContentView(view, params);
+    }
+
+    /**
+     * Set the title text for this dialog's window.
+     * 
+     * @param title The new text to display in the title.
+     */
+    public void setTitle(CharSequence title) {
+        mWindow.setTitle(title);
+        mWindow.getAttributes().setTitle(title);
+    }
+
+    /**
+     * Set the title text for this dialog's window. The text is retrieved
+     * from the resources with the supplied identifier.
+     *
+     * @param titleId the title's text resource identifier
+     */
+    public void setTitle(int titleId) {
+        setTitle(mContext.getText(titleId));
+    }
+
+    /**
+     * A key was pressed down.
+     * 
+     * <p>If the focused view didn't want this event, this method is called.
+     *
+     * <p>The default implementation handles KEYCODE_BACK to close the
+     * dialog.
+     *
+     * @see #onKeyUp
+     * @see android.view.KeyEvent
+     */
+    public boolean onKeyDown(int keyCode, KeyEvent event) {
+        if (keyCode == KeyEvent.KEYCODE_BACK) {
+            if (mCancelable) {
+                cancel();
+            }
+            return true;
+        }
+
+        return false;
+    }
+
+    /**
+     * A key was released.
+     * 
+     * @see #onKeyDown
+     * @see KeyEvent
+     */
+    public boolean onKeyUp(int keyCode, KeyEvent event) {
+        return false;
+    }
+
+    /**
+     * Default implementation of {@link KeyEvent.Callback#onKeyMultiple(int, int, KeyEvent)
+     * KeyEvent.Callback.onKeyMultiple()}: always returns false (doesn't handle
+     * the event).
+     */
+    public boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event) {
+        return false;
+    }
+    
+    /**
+     * Called when a touch screen event was not handled by any of the views
+     * under it. This is most useful to process touch events that happen outside
+     * of your window bounds, where there is no view to receive it.
+     * 
+     * @param event The touch screen event being processed.
+     * @return Return true if you have consumed the event, false if you haven't.
+     *         The default implementation will cancel the dialog when a touch
+     *         happens outside of the window bounds.
+     */
+    public boolean onTouchEvent(MotionEvent event) {
+        if (mCancelable && mCanceledOnTouchOutside && event.getAction() == MotionEvent.ACTION_DOWN
+                && isOutOfBounds(event)) {
+            cancel();
+            return true;
+        }
+        
+        return false;
+    }
+
+    private boolean isOutOfBounds(MotionEvent event) {
+        final int x = (int) event.getX();
+        final int y = (int) event.getY();
+        final int slop = ViewConfiguration.get(mContext).getScaledWindowTouchSlop();
+        final View decorView = getWindow().getDecorView();
+        return (x < -slop) || (y < -slop)
+                || (x > (decorView.getWidth()+slop))
+                || (y > (decorView.getHeight()+slop));
+    }
+    
+    /**
+     * Called when the trackball was moved and not handled by any of the
+     * views inside of the activity.  So, for example, if the trackball moves
+     * while focus is on a button, you will receive a call here because
+     * buttons do not normally do anything with trackball events.  The call
+     * here happens <em>before</em> trackball movements are converted to
+     * DPAD key events, which then get sent back to the view hierarchy, and
+     * will be processed at the point for things like focus navigation.
+     * 
+     * @param event The trackball event being processed.
+     * 
+     * @return Return true if you have consumed the event, false if you haven't.
+     * The default implementation always returns false.
+     */
+    public boolean onTrackballEvent(MotionEvent event) {
+        return false;
+    }
+    
+    public void onWindowAttributesChanged(WindowManager.LayoutParams params) {
+        if (mDecor != null) {
+            mWindowManager.updateViewLayout(mDecor, params);
+        }
+    }
+
+    public void onContentChanged() {
+    }
+    
+    public void onWindowFocusChanged(boolean hasFocus) {
+    }
+
+    /**
+     * Called to process key events.  You can override this to intercept all 
+     * key events before they are dispatched to the window.  Be sure to call 
+     * this implementation for key events that should be handled normally.
+     * 
+     * @param event The key event.
+     * 
+     * @return boolean Return true if this event was consumed.
+     */
+    public boolean dispatchKeyEvent(KeyEvent event) {
+        if ((mOnKeyListener != null) && (mOnKeyListener.onKey(this, event.getKeyCode(), event))) {
+            return true;
+        }
+        if (mWindow.superDispatchKeyEvent(event)) {
+            return true;
+        }
+        return event.dispatch(this);
+    }
+
+    /**
+     * Called to process touch screen events.  You can override this to
+     * intercept all touch screen events before they are dispatched to the
+     * window.  Be sure to call this implementation for touch screen events
+     * that should be handled normally.
+     * 
+     * @param ev The touch screen event.
+     * 
+     * @return boolean Return true if this event was consumed.
+     */
+    public boolean dispatchTouchEvent(MotionEvent ev) {
+        if (mWindow.superDispatchTouchEvent(ev)) {
+            return true;
+        }
+        return onTouchEvent(ev);
+    }
+    
+    /**
+     * Called to process trackball events.  You can override this to
+     * intercept all trackball events before they are dispatched to the
+     * window.  Be sure to call this implementation for trackball events
+     * that should be handled normally.
+     * 
+     * @param ev The trackball event.
+     * 
+     * @return boolean Return true if this event was consumed.
+     */
+    public boolean dispatchTrackballEvent(MotionEvent ev) {
+        if (mWindow.superDispatchTrackballEvent(ev)) {
+            return true;
+        }
+        return onTrackballEvent(ev);
+    }
+
+    /**
+     * @see Activity#onCreatePanelView(int)
+     */
+    public View onCreatePanelView(int featureId) {
+        return null;
+    }
+
+    /**
+     * @see Activity#onCreatePanelMenu(int, Menu)
+     */
+    public boolean onCreatePanelMenu(int featureId, Menu menu) {
+        if (featureId == Window.FEATURE_OPTIONS_PANEL) {
+            return onCreateOptionsMenu(menu);
+        }
+        
+        return false;
+    }
+
+    /**
+     * @see Activity#onPreparePanel(int, View, Menu)
+     */
+    public boolean onPreparePanel(int featureId, View view, Menu menu) {
+        if (featureId == Window.FEATURE_OPTIONS_PANEL && menu != null) {
+            boolean goforit = onPrepareOptionsMenu(menu);
+            return goforit && menu.hasVisibleItems();
+        }
+        return true;
+    }
+
+    /**
+     * @see Activity#onMenuOpened(int, Menu)
+     */
+    public boolean onMenuOpened(int featureId, Menu menu) {
+        return true;
+    }
+
+    /**
+     * @see Activity#onMenuItemSelected(int, MenuItem)
+     */
+    public boolean onMenuItemSelected(int featureId, MenuItem item) {
+        return false;
+    }
+
+    /**
+     * @see Activity#onPanelClosed(int, Menu)
+     */
+    public void onPanelClosed(int featureId, Menu menu) {
+    }
+
+    /**
+     * It is usually safe to proxy this call to the owner activity's
+     * {@link Activity#onCreateOptionsMenu(Menu)} if the client desires the same
+     * menu for this Dialog.
+     * 
+     * @see Activity#onCreateOptionsMenu(Menu)
+     * @see #getOwnerActivity()
+     */
+    public boolean onCreateOptionsMenu(Menu menu) {
+        return true;
+    }
+
+    /**
+     * It is usually safe to proxy this call to the owner activity's
+     * {@link Activity#onPrepareOptionsMenu(Menu)} if the client desires the
+     * same menu for this Dialog.
+     * 
+     * @see Activity#onPrepareOptionsMenu(Menu)
+     * @see #getOwnerActivity()
+     */
+    public boolean onPrepareOptionsMenu(Menu menu) {
+        return true;
+    }
+
+    /**
+     * @see Activity#onOptionsItemSelected(MenuItem)
+     */
+    public boolean onOptionsItemSelected(MenuItem item) {
+        return false;
+    }
+
+    /**
+     * @see Activity#onOptionsMenuClosed(Menu)
+     */
+    public void onOptionsMenuClosed(Menu menu) {
+    }
+
+    /**
+     * @see Activity#openOptionsMenu()
+     */
+    public void openOptionsMenu() {
+        mWindow.openPanel(Window.FEATURE_OPTIONS_PANEL, null);
+    }
+    
+    /**
+     * @see Activity#closeOptionsMenu()
+     */
+    public void closeOptionsMenu() {
+        mWindow.closePanel(Window.FEATURE_OPTIONS_PANEL);
+    }
+
+    /**
+     * @see Activity#onCreateContextMenu(ContextMenu, View, ContextMenuInfo)
+     */
+    public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
+    }
+
+    /**
+     * @see Activity#registerForContextMenu(View)
+     */
+    public void registerForContextMenu(View view) {
+        view.setOnCreateContextMenuListener(this);
+    }
+    
+    /**
+     * @see Activity#unregisterForContextMenu(View)
+     */
+    public void unregisterForContextMenu(View view) {
+        view.setOnCreateContextMenuListener(null);
+    }
+    
+    /**
+     * @see Activity#openContextMenu(View)
+     */
+    public void openContextMenu(View view) {
+        view.showContextMenu();
+    }
+
+    /**
+     * @see Activity#onContextItemSelected(MenuItem)
+     */
+    public boolean onContextItemSelected(MenuItem item) {
+        return false;
+    }
+    
+    /**
+     * @see Activity#onContextMenuClosed(Menu)
+     */
+    public void onContextMenuClosed(Menu menu) {
+    }
+    
+    /**
+     * This hook is called when the user signals the desire to start a search.
+     */
+    public boolean onSearchRequested() {
+        // not during dialogs, no.
+        return false;
+    }
+
+
+    /**
+     * Request that key events come to this dialog. Use this if your
+     * dialog has no views with focus, but the dialog still wants
+     * a chance to process key events.
+     * 
+     * @param get true if the dialog should receive key events, false otherwise
+     * @see android.view.Window#takeKeyEvents
+     */
+    public void takeKeyEvents(boolean get) {
+        mWindow.takeKeyEvents(get);
+    }
+
+    /**
+     * Enable extended window features.  This is a convenience for calling
+     * {@link android.view.Window#requestFeature getWindow().requestFeature()}.
+     * 
+     * @param featureId The desired feature as defined in
+     *                  {@link android.view.Window}.
+     * @return Returns true if the requested feature is supported and now
+     *         enabled.
+     * 
+     * @see android.view.Window#requestFeature
+     */
+    public final boolean requestWindowFeature(int featureId) {
+        return getWindow().requestFeature(featureId);
+    }
+
+    /**
+     * Convenience for calling
+     * {@link android.view.Window#setFeatureDrawableResource}.
+     */
+    public final void setFeatureDrawableResource(int featureId, int resId) {
+        getWindow().setFeatureDrawableResource(featureId, resId);
+    }
+
+    /**
+     * Convenience for calling
+     * {@link android.view.Window#setFeatureDrawableUri}.
+     */
+    public final void setFeatureDrawableUri(int featureId, Uri uri) {
+        getWindow().setFeatureDrawableUri(featureId, uri);
+    }
+
+    /**
+     * Convenience for calling
+     * {@link android.view.Window#setFeatureDrawable(int, Drawable)}.
+     */
+    public final void setFeatureDrawable(int featureId, Drawable drawable) {
+        getWindow().setFeatureDrawable(featureId, drawable);
+    }
+
+    /**
+     * Convenience for calling
+     * {@link android.view.Window#setFeatureDrawableAlpha}.
+     */
+    public final void setFeatureDrawableAlpha(int featureId, int alpha) {
+        getWindow().setFeatureDrawableAlpha(featureId, alpha);
+    }
+
+    public LayoutInflater getLayoutInflater() {
+        return getWindow().getLayoutInflater();
+    }
+
+    /**
+     * Sets whether this dialog is cancelable with the
+     * {@link KeyEvent#KEYCODE_BACK BACK} key.
+     */
+    public void setCancelable(boolean flag) {
+        mCancelable = flag;
+    }
+
+    /**
+     * Sets whether this dialog is canceled when touched outside the window's
+     * bounds. If setting to true, the dialog is set to be cancelable if not
+     * already set.
+     * 
+     * @param cancel Whether the dialog should be canceled when touched outside
+     *            the window.
+     */
+    public void setCanceledOnTouchOutside(boolean cancel) {
+        if (cancel && !mCancelable) {
+            mCancelable = true;
+        }
+        
+        mCanceledOnTouchOutside = cancel;
+    }
+    
+    /**
+     * Cancel the dialog.  This is essentially the same as calling {@link #dismiss()}, but it will
+     * also call your {@link DialogInterface.OnCancelListener} (if registered).
+     */
+    public void cancel() {
+        if (mCancelMessage != null) {
+            
+            // Obtain a new message so this dialog can be re-used
+            Message.obtain(mCancelMessage).sendToTarget();
+        }
+        dismiss();
+    }
+
+    /**
+     * Set a listener to be invoked when the dialog is canceled.
+     * <p>
+     * This will only be invoked when the dialog is canceled, if the creator
+     * needs to know when it is dismissed in general, use
+     * {@link #setOnDismissListener}.
+     * 
+     * @param listener The {@link DialogInterface.OnCancelListener} to use.
+     */
+    public void setOnCancelListener(final OnCancelListener listener) {
+        if (listener != null) {
+            mCancelMessage = mDismissCancelHandler.obtainMessage(CANCEL, listener);
+        } else {
+            mCancelMessage = null;
+        }
+    }
+
+    /**
+     * Set a message to be sent when the dialog is canceled.
+     * @param msg The msg to send when the dialog is canceled.
+     * @see #setOnCancelListener(android.content.DialogInterface.OnCancelListener)
+     */
+    public void setCancelMessage(final Message msg) {
+        mCancelMessage = msg;
+    }
+
+    /**
+     * Set a listener to be invoked when the dialog is dismissed.
+     * @param listener The {@link DialogInterface.OnDismissListener} to use.
+     */
+    public void setOnDismissListener(final OnDismissListener listener) {
+        if (listener != null) {
+            mDismissMessage = mDismissCancelHandler.obtainMessage(DISMISS, listener);
+        } else {
+            mDismissMessage = null;
+        }
+    }
+
+    /**
+     * Set a message to be sent when the dialog is dismissed.
+     * @param msg The msg to send when the dialog is dismissed.
+     */
+    public void setDismissMessage(final Message msg) {
+        mDismissMessage = msg;
+    }
+
+    /**
+     * By default, this will use the owner Activity's suggested stream type.
+     * 
+     * @see Activity#setVolumeControlStream(int)
+     * @see #setOwnerActivity(Activity)
+     */
+    public final void setVolumeControlStream(int streamType) {
+        getWindow().setVolumeControlStream(streamType);
+    }
+
+    /**
+     * @see Activity#getVolumeControlStream()
+     */
+    public final int getVolumeControlStream() {
+        return getWindow().getVolumeControlStream();
+    }
+    
+    /**
+     * Sets the callback that will be called if a key is dispatched to the dialog.
+     */
+    public void setOnKeyListener(final OnKeyListener onKeyListener) {
+        mOnKeyListener = onKeyListener;
+    }
+
+    private static final int DISMISS = 0x43;
+    private static final int CANCEL = 0x44;
+
+    private Handler mDismissCancelHandler;
+
+    private static final class DismissCancelHandler extends Handler {
+        private WeakReference<DialogInterface> mDialog;
+
+        public DismissCancelHandler(Dialog dialog) {
+            mDialog = new WeakReference<DialogInterface>(dialog);
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case DISMISS:
+                    ((OnDismissListener) msg.obj).onDismiss(mDialog.get());
+                    break;
+                case CANCEL:
+                    ((OnCancelListener) msg.obj).onCancel(mDialog.get());
+                    break;
+            }
+        }
+    }
+}
diff --git a/core/java/android/app/ExpandableListActivity.java b/core/java/android/app/ExpandableListActivity.java
new file mode 100644
index 0000000..a2e048f
--- /dev/null
+++ b/core/java/android/app/ExpandableListActivity.java
@@ -0,0 +1,324 @@
+/*
+ * 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 android.app;
+
+import android.database.Cursor;
+import android.os.Bundle;
+import java.util.List;
+import android.view.ContextMenu;
+import android.view.View;
+import android.view.ContextMenu.ContextMenuInfo;
+import android.view.View.OnCreateContextMenuListener;
+import android.widget.ExpandableListAdapter;
+import android.widget.ExpandableListView;
+import android.widget.SimpleCursorTreeAdapter;
+import android.widget.SimpleExpandableListAdapter;
+import android.widget.AdapterView.AdapterContextMenuInfo;
+
+import java.util.Map;
+
+/**
+ * An activity that displays an expandable list of items by binding to a data
+ * source implementing the ExpandableListAdapter, and exposes event handlers
+ * when the user selects an item.
+ * <p>
+ * ExpandableListActivity hosts a
+ * {@link android.widget.ExpandableListView ExpandableListView} object that can
+ * be bound to different data sources that provide a two-levels of data (the
+ * top-level is group, and below each group are children). Binding, screen
+ * layout, and row layout are discussed in the following sections.
+ * <p>
+ * <strong>Screen Layout</strong>
+ * </p>
+ * <p>
+ * ExpandableListActivity has a default layout that consists of a single,
+ * full-screen, centered expandable list. However, if you desire, you can
+ * customize the screen layout by setting your own view layout with
+ * setContentView() in onCreate(). To do this, your own view MUST contain an
+ * ExpandableListView object with the id "@android:id/list" (or
+ * {@link android.R.id#list} if it's in code)
+ * <p>
+ * Optionally, your custom view can contain another view object of any type to
+ * display when the list view is empty. This "empty list" notifier must have an
+ * id "android:empty". Note that when an empty view is present, the expandable
+ * list view will be hidden when there is no data to display.
+ * <p>
+ * The following code demonstrates an (ugly) custom screen layout. It has a list
+ * with a green background, and an alternate red "no data" message.
+ * </p>
+ * 
+ * <pre>
+ * &lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;
+ * &lt;LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ *         android:orientation=&quot;vertical&quot;
+ *         android:layout_width=&quot;fill_parent&quot; 
+ *         android:layout_height=&quot;fill_parent&quot;
+ *         android:paddingLeft=&quot;8dp&quot;
+ *         android:paddingRight=&quot;8dp&quot;&gt;
+ * 
+ *     &lt;ExpandableListView android:id=&quot;@id/android:list&quot;
+ *               android:layout_width=&quot;fill_parent&quot; 
+ *               android:layout_height=&quot;fill_parent&quot;
+ *               android:background=&quot;#00FF00&quot;
+ *               android:layout_weight=&quot;1&quot;
+ *               android:drawSelectorOnTop=&quot;false&quot;/&gt;
+ * 
+ *     &lt;TextView android:id=&quot;@id/android:empty&quot;
+ *               android:layout_width=&quot;fill_parent&quot; 
+ *               android:layout_height=&quot;fill_parent&quot;
+ *               android:background=&quot;#FF0000&quot;
+ *               android:text=&quot;No data&quot;/&gt;
+ * &lt;/LinearLayout&gt;
+ * </pre>
+ * 
+ * <p>
+ * <strong>Row Layout</strong>
+ * </p>
+ * The {@link ExpandableListAdapter} set in the {@link ExpandableListActivity}
+ * via {@link #setListAdapter(ExpandableListAdapter)} provides the {@link View}s
+ * for each row. This adapter has separate methods for providing the group
+ * {@link View}s and child {@link View}s. There are a couple provided
+ * {@link ExpandableListAdapter}s that simplify use of adapters:
+ * {@link SimpleCursorTreeAdapter} and {@link SimpleExpandableListAdapter}.
+ * <p>
+ * With these, you can specify the layout of individual rows for groups and
+ * children in the list. These constructor takes a few parameters that specify
+ * layout resources for groups and children. It also has additional parameters
+ * that let you specify which data field to associate with which object in the
+ * row layout resource. The {@link SimpleCursorTreeAdapter} fetches data from
+ * {@link Cursor}s and the {@link SimpleExpandableListAdapter} fetches data
+ * from {@link List}s of {@link Map}s.
+ * </p>
+ * <p>
+ * Android provides some standard row layout resources. These are in the
+ * {@link android.R.layout} class, and have names such as simple_list_item_1,
+ * simple_list_item_2, and two_line_list_item. The following layout XML is the
+ * source for the resource two_line_list_item, which displays two data
+ * fields,one above the other, for each list row.
+ * </p>
+ * 
+ * <pre>
+ * &lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&gt;
+ * &lt;LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ *     android:layout_width=&quot;fill_parent&quot;
+ *     android:layout_height=&quot;wrap_content&quot;
+ *     android:orientation=&quot;vertical&quot;&gt;
+ * 
+ *     &lt;TextView android:id=&quot;@+id/text1&quot;
+ *         android:textSize=&quot;16sp&quot;
+ *         android:textStyle=&quot;bold&quot;
+ *         android:layout_width=&quot;fill_parent&quot;
+ *         android:layout_height=&quot;wrap_content&quot;/&gt;
+ * 
+ *     &lt;TextView android:id=&quot;@+id/text2&quot;
+ *         android:textSize=&quot;16sp&quot;
+ *         android:layout_width=&quot;fill_parent&quot;
+ *         android:layout_height=&quot;wrap_content&quot;/&gt;
+ * &lt;/LinearLayout&gt;
+ * </pre>
+ * 
+ * <p>
+ * You must identify the data bound to each TextView object in this layout. The
+ * syntax for this is discussed in the next section.
+ * </p>
+ * <p>
+ * <strong>Binding to Data</strong>
+ * </p>
+ * <p>
+ * You bind the ExpandableListActivity's ExpandableListView object to data using
+ * a class that implements the
+ * {@link android.widget.ExpandableListAdapter ExpandableListAdapter} interface.
+ * Android provides two standard list adapters:
+ * {@link android.widget.SimpleExpandableListAdapter SimpleExpandableListAdapter}
+ * for static data (Maps), and
+ * {@link android.widget.SimpleCursorTreeAdapter SimpleCursorTreeAdapter} for
+ * Cursor query results.
+ * </p>
+ * 
+ * @see #setListAdapter
+ * @see android.widget.ExpandableListView
+ */
+public class ExpandableListActivity extends Activity implements
+        OnCreateContextMenuListener,
+        ExpandableListView.OnChildClickListener, ExpandableListView.OnGroupCollapseListener,
+        ExpandableListView.OnGroupExpandListener {   
+    ExpandableListAdapter mAdapter;
+    ExpandableListView mList;
+    boolean mFinishedStart = false;
+
+    /**
+     * Override this to populate the context menu when an item is long pressed. menuInfo
+     * will contain an {@link android.widget.ExpandableListView.ExpandableListContextMenuInfo}
+     * whose packedPosition is a packed position
+     * that should be used with {@link ExpandableListView#getPackedPositionType(long)} and
+     * the other similar methods.
+     * <p>
+     * {@inheritDoc}
+     */
+    @Override
+    public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
+    }
+
+    /**
+     * Override this for receiving callbacks when a child has been clicked.
+     * <p>
+     * {@inheritDoc}
+     */
+    public boolean onChildClick(ExpandableListView parent, View v, int groupPosition,
+            int childPosition, long id) {
+        return false;
+    }
+
+    /**
+     * Override this for receiving callbacks when a group has been collapsed.
+     */
+    public void onGroupCollapse(int groupPosition) {
+    }
+
+    /**
+     * Override this for receiving callbacks when a group has been expanded.
+     */
+    public void onGroupExpand(int groupPosition) {
+    }
+
+    /**
+     * Ensures the expandable list view has been created before Activity restores all
+     * of the view states.
+     * 
+     *@see Activity#onRestoreInstanceState(Bundle)
+     */
+    @Override
+    protected void onRestoreInstanceState(Bundle state) {
+        ensureList();
+        super.onRestoreInstanceState(state);
+    }
+
+    /**
+     * Updates the screen state (current list and other views) when the
+     * content changes.
+     * 
+     * @see Activity#onContentChanged()
+     */
+    @Override
+    public void onContentChanged() {
+        super.onContentChanged();
+        View emptyView = findViewById(com.android.internal.R.id.empty);
+        mList = (ExpandableListView)findViewById(com.android.internal.R.id.list);
+        if (mList == null) {
+            throw new RuntimeException(
+                    "Your content must have a ExpandableListView whose id attribute is " +
+                    "'android.R.id.list'");
+        }
+        if (emptyView != null) {
+            mList.setEmptyView(emptyView);
+        }
+        mList.setOnChildClickListener(this);
+        mList.setOnGroupExpandListener(this);
+        mList.setOnGroupCollapseListener(this);
+        
+        if (mFinishedStart) {
+            setListAdapter(mAdapter);
+        }
+        mFinishedStart = true;
+    }
+
+    /**
+     * Provide the adapter for the expandable list.
+     */
+    public void setListAdapter(ExpandableListAdapter adapter) {
+        synchronized (this) {
+            ensureList();
+            mAdapter = adapter;
+            mList.setAdapter(adapter);
+        }
+    }
+
+    /**
+     * Get the activity's expandable list view widget.  This can be used to get the selection,
+     * set the selection, and many other useful functions.
+     * 
+     * @see ExpandableListView
+     */
+    public ExpandableListView getExpandableListView() {
+        ensureList();
+        return mList;
+    }
+    
+    /**
+     * Get the ExpandableListAdapter associated with this activity's
+     * ExpandableListView.
+     */
+    public ExpandableListAdapter getExpandableListAdapter() {
+        return mAdapter;
+    }
+
+    private void ensureList() {
+        if (mList != null) {
+            return;
+        }
+        setContentView(com.android.internal.R.layout.expandable_list_content);
+    }
+
+    /**
+     * Gets the ID of the currently selected group or child.
+     * 
+     * @return The ID of the currently selected group or child.
+     */
+    public long getSelectedId() {
+        return mList.getSelectedId();
+    }
+
+    /**
+     * Gets the position (in packed position representation) of the currently
+     * selected group or child. Use
+     * {@link ExpandableListView#getPackedPositionType},
+     * {@link ExpandableListView#getPackedPositionGroup}, and
+     * {@link ExpandableListView#getPackedPositionChild} to unpack the returned
+     * packed position.
+     * 
+     * @return A packed position representation containing the currently
+     *         selected group or child's position and type.
+     */
+    public long getSelectedPosition() {
+        return mList.getSelectedPosition();
+    }
+
+    /**
+     * Sets the selection to the specified child. If the child is in a collapsed
+     * group, the group will only be expanded and child subsequently selected if
+     * shouldExpandGroup is set to true, otherwise the method will return false.
+     * 
+     * @param groupPosition The position of the group that contains the child.
+     * @param childPosition The position of the child within the group.
+     * @param shouldExpandGroup Whether the child's group should be expanded if
+     *            it is collapsed.
+     * @return Whether the selection was successfully set on the child.
+     */
+    public boolean setSelectedChild(int groupPosition, int childPosition, boolean shouldExpandGroup) {
+        return mList.setSelectedChild(groupPosition, childPosition, shouldExpandGroup);
+    }
+
+    /**
+     * Sets the selection to the specified group.
+     * @param groupPosition The position of the group that should be selected.
+     */
+    public void setSelectedGroup(int groupPosition) {
+        mList.setSelectedGroup(groupPosition);
+    }
+
+}
+
diff --git a/core/java/android/app/IActivityManager.java b/core/java/android/app/IActivityManager.java
new file mode 100644
index 0000000..cd3701f
--- /dev/null
+++ b/core/java/android/app/IActivityManager.java
@@ -0,0 +1,368 @@
+/*
+ * 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 android.app;
+
+import android.app.ActivityManager.MemoryInfo;
+import android.content.ComponentName;
+import android.content.ContentProviderNative;
+import android.content.IContentProvider;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.ConfigurationInfo;
+import android.content.pm.IPackageDataObserver;
+import android.content.pm.ProviderInfo;
+import android.content.res.Configuration;
+import android.graphics.Bitmap;
+import android.net.Uri;
+import android.os.RemoteException;
+import android.os.IBinder;
+import android.os.IInterface;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.ParcelFileDescriptor;
+import android.text.TextUtils;
+import android.os.Bundle;
+
+import java.util.List;
+
+/**
+ * System private API for talking with the activity manager service.  This
+ * provides calls from the application back to the activity manager.
+ *
+ * {@hide}
+ */
+public interface IActivityManager extends IInterface {
+    public static final int START_DELIVERED_TO_TOP = 3;
+    public static final int START_TASK_TO_FRONT = 2;
+    public static final int START_RETURN_INTENT_TO_CALLER = 1;
+    public static final int START_SUCCESS = 0;
+    public static final int START_INTENT_NOT_RESOLVED = -1;
+    public static final int START_CLASS_NOT_FOUND = -2;
+    public static final int START_FORWARD_AND_REQUEST_CONFLICT = -3;
+    public static final int START_PERMISSION_DENIED = -4;
+    public int startActivity(IApplicationThread caller,
+            Intent intent, String resolvedType, Uri[] grantedUriPermissions,
+            int grantedMode, IBinder resultTo, String resultWho, int requestCode,
+            boolean onlyIfNeeded, boolean debug) throws RemoteException;
+    public boolean startNextMatchingActivity(IBinder callingActivity,
+            Intent intent) throws RemoteException;
+    public boolean finishActivity(IBinder token, int code, Intent data)
+            throws RemoteException;
+    public void finishSubActivity(IBinder token, String resultWho, int requestCode) throws RemoteException;
+    public Intent registerReceiver(IApplicationThread caller,
+            IIntentReceiver receiver, IntentFilter filter,
+            String requiredPermission) throws RemoteException;
+    public void unregisterReceiver(IIntentReceiver receiver) throws RemoteException;
+    public static final int BROADCAST_SUCCESS = 0;
+    public static final int BROADCAST_STICKY_CANT_HAVE_PERMISSION = -1;
+    public int broadcastIntent(IApplicationThread caller, Intent intent,
+            String resolvedType, IIntentReceiver resultTo, int resultCode,
+            String resultData, Bundle map, String requiredPermission,
+            boolean serialized, boolean sticky) throws RemoteException;
+    public void unbroadcastIntent(IApplicationThread caller, Intent intent) throws RemoteException;
+    /* oneway */
+    public void finishReceiver(IBinder who, int resultCode, String resultData, Bundle map, boolean abortBroadcast) throws RemoteException;
+    public void setPersistent(IBinder token, boolean isPersistent) throws RemoteException;
+    public void attachApplication(IApplicationThread app) throws RemoteException;
+    /* oneway */
+    public void activityIdle(IBinder token) throws RemoteException;
+    public void activityPaused(IBinder token, Bundle state) throws RemoteException;
+    /* oneway */
+    public void activityStopped(IBinder token,
+                                Bitmap thumbnail, CharSequence description) throws RemoteException;
+    /* oneway */
+    public void activityDestroyed(IBinder token) throws RemoteException;
+    public String getCallingPackage(IBinder token) throws RemoteException;
+    public ComponentName getCallingActivity(IBinder token) throws RemoteException;
+    public List getTasks(int maxNum, int flags,
+                         IThumbnailReceiver receiver) throws RemoteException;
+    public List<ActivityManager.RecentTaskInfo> getRecentTasks(int maxNum,
+            int flags) throws RemoteException;
+    public List getServices(int maxNum, int flags) throws RemoteException;
+    public List<ActivityManager.ProcessErrorStateInfo> getProcessesInErrorState()
+            throws RemoteException;
+    public void moveTaskToFront(int task) throws RemoteException;
+    public void moveTaskToBack(int task) throws RemoteException;
+    public boolean moveActivityTaskToBack(IBinder token, boolean nonRoot) throws RemoteException;
+    public void moveTaskBackwards(int task) throws RemoteException;
+    public int getTaskForActivity(IBinder token, boolean onlyRoot) throws RemoteException;
+    public void finishOtherInstances(IBinder token, ComponentName className) throws RemoteException;
+    /* oneway */
+    public void reportThumbnail(IBinder token,
+                                Bitmap thumbnail, CharSequence description) throws RemoteException;
+    public ContentProviderHolder getContentProvider(IApplicationThread caller,
+                                                    String name) throws RemoteException;
+    public void removeContentProvider(IApplicationThread caller,
+        String name) throws RemoteException;
+    public void publishContentProviders(IApplicationThread caller,
+                                        List<ContentProviderHolder> providers) throws RemoteException;
+    public ComponentName startService(IApplicationThread caller, Intent service,
+            String resolvedType) throws RemoteException;
+    public int stopService(IApplicationThread caller, Intent service,
+            String resolvedType) throws RemoteException;
+    public boolean stopServiceToken(ComponentName className, IBinder token,
+            int startId) throws RemoteException;
+    public void setServiceForeground(ComponentName className, IBinder token,
+            boolean isForeground) throws RemoteException;
+    public int bindService(IApplicationThread caller, IBinder token,
+            Intent service, String resolvedType,
+            IServiceConnection connection, int flags) throws RemoteException;
+    public boolean unbindService(IServiceConnection connection) throws RemoteException;
+    public void publishService(IBinder token,
+            Intent intent, IBinder service) throws RemoteException;
+    public void unbindFinished(IBinder token, Intent service,
+            boolean doRebind) throws RemoteException;
+    /* oneway */
+    public void serviceDoneExecuting(IBinder token) throws RemoteException;
+    public IBinder peekService(Intent service, String resolvedType) throws RemoteException;
+    
+    public boolean startInstrumentation(ComponentName className, String profileFile,
+            int flags, Bundle arguments, IInstrumentationWatcher watcher)
+            throws RemoteException;
+    public void finishInstrumentation(IApplicationThread target,
+            int resultCode, Bundle results) throws RemoteException;
+
+    public Configuration getConfiguration() throws RemoteException;
+    public void updateConfiguration(Configuration values) throws RemoteException;
+    public void setRequestedOrientation(IBinder token,
+            int requestedOrientation) throws RemoteException;
+    public int getRequestedOrientation(IBinder token) throws RemoteException;
+    
+    public ComponentName getActivityClassForToken(IBinder token) throws RemoteException;
+    public String getPackageForToken(IBinder token) throws RemoteException;
+
+    public static final int INTENT_SENDER_BROADCAST = 1;
+    public static final int INTENT_SENDER_ACTIVITY = 2;
+    public static final int INTENT_SENDER_ACTIVITY_RESULT = 3;
+    public static final int INTENT_SENDER_SERVICE = 4;
+    public IIntentSender getIntentSender(int type,
+            String packageName, IBinder token, String resultWho,
+            int requestCode, Intent intent, String resolvedType, int flags) throws RemoteException;
+    public void cancelIntentSender(IIntentSender sender) throws RemoteException;
+    public boolean clearApplicationUserData(final String packageName,
+            final IPackageDataObserver observer) throws RemoteException;
+    public String getPackageForIntentSender(IIntentSender sender) throws RemoteException;
+    
+    public void setProcessLimit(int max) throws RemoteException;
+    public int getProcessLimit() throws RemoteException;
+    
+    public void setProcessForeground(IBinder token, int pid, boolean isForeground) throws RemoteException;
+    
+    public int checkPermission(String permission, int pid, int uid)
+            throws RemoteException;
+
+    public int checkUriPermission(Uri uri, int pid, int uid, int mode)
+            throws RemoteException;
+    public void grantUriPermission(IApplicationThread caller, String targetPkg,
+            Uri uri, int mode) throws RemoteException;
+    public void revokeUriPermission(IApplicationThread caller, Uri uri,
+            int mode) throws RemoteException;
+    
+    public void showWaitingForDebugger(IApplicationThread who, boolean waiting)
+            throws RemoteException;
+    
+    public void getMemoryInfo(ActivityManager.MemoryInfo outInfo) throws RemoteException;
+    
+    public void restartPackage(final String packageName) throws RemoteException;
+    
+    // Note: probably don't want to allow applications access to these.
+    public void goingToSleep() throws RemoteException;
+    public void wakingUp() throws RemoteException;
+    
+    public void unhandledBack() throws RemoteException;
+    public ParcelFileDescriptor openContentUri(Uri uri) throws RemoteException;
+    public void setDebugApp(
+        String packageName, boolean waitForDebugger, boolean persistent)
+        throws RemoteException;
+    public void setAlwaysFinish(boolean enabled) throws RemoteException;
+    public void setActivityWatcher(IActivityWatcher watcher)
+        throws RemoteException;
+
+    public void enterSafeMode() throws RemoteException;
+    
+    public void noteWakeupAlarm(IIntentSender sender) throws RemoteException;
+    
+    public boolean killPidsForMemory(int[] pids) throws RemoteException;
+    
+    public void reportPss(IApplicationThread caller, int pss) throws RemoteException;
+    
+    // Special low-level communication with activity manager.
+    public void startRunning(String pkg, String cls, String action,
+            String data) throws RemoteException;
+    public void systemReady() throws RemoteException;
+    // Returns 1 if the user wants to debug.
+    public int handleApplicationError(IBinder app,
+            int flags,    /* 1 == can debug */
+            String tag, String shortMsg, String longMsg,
+            byte[] crashData) throws RemoteException;
+    
+    /*
+     * This will deliver the specified signal to all the persistent processes. Currently only 
+     * SIGUSR1 is delivered. All others are ignored.
+     */
+    public void signalPersistentProcesses(int signal) throws RemoteException;
+    // Retrieve running application processes in the system
+    public List<ActivityManager.RunningAppProcessInfo> getRunningAppProcesses()
+            throws RemoteException;
+    // Get device configuration
+    public ConfigurationInfo getDeviceConfigurationInfo() throws RemoteException;
+    
+    /*
+     * Private non-Binder interfaces
+     */
+    /* package */ boolean testIsSystemReady();
+    
+    /** Information you can retrieve about a particular application. */
+    public static class ContentProviderHolder implements Parcelable {
+        public final ProviderInfo info;
+        public final String permissionFailure;
+        public IContentProvider provider;
+        public boolean noReleaseNeeded;
+
+        public ContentProviderHolder(ProviderInfo _info) {
+            info = _info;
+            permissionFailure = null;
+        }
+
+        public ContentProviderHolder(ProviderInfo _info,
+                String _permissionFailure) {
+            info = _info;
+            permissionFailure = _permissionFailure;
+        }
+        
+        public int describeContents() {
+            return 0;
+        }
+
+        public void writeToParcel(Parcel dest, int flags) {
+            info.writeToParcel(dest, 0);
+            dest.writeString(permissionFailure);
+            if (provider != null) {
+                dest.writeStrongBinder(provider.asBinder());
+            } else {
+                dest.writeStrongBinder(null);
+            }
+            dest.writeInt(noReleaseNeeded ? 1:0);
+        }
+
+        public static final Parcelable.Creator<ContentProviderHolder> CREATOR
+                = new Parcelable.Creator<ContentProviderHolder>() {
+            public ContentProviderHolder createFromParcel(Parcel source) {
+                return new ContentProviderHolder(source);
+            }
+
+            public ContentProviderHolder[] newArray(int size) {
+                return new ContentProviderHolder[size];
+            }
+        };
+
+        private ContentProviderHolder(Parcel source) {
+            info = ProviderInfo.CREATOR.createFromParcel(source);
+            permissionFailure = source.readString();
+            provider = ContentProviderNative.asInterface(
+                source.readStrongBinder());
+            noReleaseNeeded = source.readInt() != 0;
+        }
+    };
+
+    String descriptor = "android.app.IActivityManager";
+
+    // Please keep these transaction codes the same -- they are also
+    // sent by C++ code.
+    int START_RUNNING_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION;
+    int HANDLE_APPLICATION_ERROR_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+1;
+    int START_ACTIVITY_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+2;
+    int UNHANDLED_BACK_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+3;
+    int OPEN_CONTENT_URI_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+4;
+
+    // Remaining non-native transaction codes.
+    int FINISH_ACTIVITY_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+10;
+    int REGISTER_RECEIVER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+11;
+    int UNREGISTER_RECEIVER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+12;
+    int BROADCAST_INTENT_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+13;
+    int UNBROADCAST_INTENT_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+14;
+    int FINISH_RECEIVER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+15;
+    int ATTACH_APPLICATION_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+16;
+    int ACTIVITY_IDLE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+17;
+    int ACTIVITY_PAUSED_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+18;
+    int ACTIVITY_STOPPED_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+19;
+    int GET_CALLING_PACKAGE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+20;
+    int GET_CALLING_ACTIVITY_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+21;
+    int GET_TASKS_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+22;
+    int MOVE_TASK_TO_FRONT_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+23;
+    int MOVE_TASK_TO_BACK_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+24;
+    int MOVE_TASK_BACKWARDS_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+25;
+    int GET_TASK_FOR_ACTIVITY_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+26;
+    int REPORT_THUMBNAIL_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+27;
+    int GET_CONTENT_PROVIDER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+28;
+    int PUBLISH_CONTENT_PROVIDERS_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+29;
+    int SET_PERSISTENT_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+30;
+    int FINISH_SUB_ACTIVITY_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+31;
+    int SYSTEM_READY_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+32;
+    int START_SERVICE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+33;
+    int STOP_SERVICE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+34;
+    int BIND_SERVICE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+35;
+    int UNBIND_SERVICE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+36;
+    int PUBLISH_SERVICE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+37;
+    int FINISH_OTHER_INSTANCES_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+38;
+    int GOING_TO_SLEEP_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+39;
+    int WAKING_UP_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+40;
+    int SET_DEBUG_APP_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+41;
+    int SET_ALWAYS_FINISH_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+42;
+    int START_INSTRUMENTATION_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+43;
+    int FINISH_INSTRUMENTATION_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+44;
+    int GET_CONFIGURATION_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+45;
+    int UPDATE_CONFIGURATION_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+46;
+    int STOP_SERVICE_TOKEN_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+47;
+    int GET_ACTIVITY_CLASS_FOR_TOKEN_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+48;
+    int GET_PACKAGE_FOR_TOKEN_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+49;
+    int SET_PROCESS_LIMIT_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+50;
+    int GET_PROCESS_LIMIT_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+51;
+    int CHECK_PERMISSION_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+52;
+    int CHECK_URI_PERMISSION_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+53;
+    int GRANT_URI_PERMISSION_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+54;
+    int REVOKE_URI_PERMISSION_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+55;
+    int SET_ACTIVITY_WATCHER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+56;
+    int SHOW_WAITING_FOR_DEBUGGER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+57;
+    int SIGNAL_PERSISTENT_PROCESSES_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+58;
+    int GET_RECENT_TASKS_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+59;
+    int SERVICE_DONE_EXECUTING_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+60;
+    int ACTIVITY_DESTROYED_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+61;
+    int GET_INTENT_SENDER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+62;
+    int CANCEL_INTENT_SENDER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+63;
+    int GET_PACKAGE_FOR_INTENT_SENDER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+64;
+    int ENTER_SAFE_MODE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+65;
+    int START_NEXT_MATCHING_ACTIVITY_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+66;
+    int NOTE_WAKEUP_ALARM_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+67;
+    int REMOVE_CONTENT_PROVIDER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+68;
+    int SET_REQUESTED_ORIENTATION_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+69;
+    int GET_REQUESTED_ORIENTATION_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+70;
+    int UNBIND_FINISHED_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+71;
+    int SET_PROCESS_FOREGROUND_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+72;
+    int SET_SERVICE_FOREGROUND_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+73;
+    int MOVE_ACTIVITY_TASK_TO_BACK_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+74;
+    int GET_MEMORY_INFO_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+75;
+    int GET_PROCESSES_IN_ERROR_STATE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+76;
+    int CLEAR_APP_DATA_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+77;
+    int RESTART_PACKAGE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+78;
+    int KILL_PIDS_FOR_MEMORY_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+79;
+    int GET_SERVICES_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+80;
+    int REPORT_PSS_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+81;
+    int GET_RUNNING_APP_PROCESSES_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+82;
+    int GET_DEVICE_CONFIGURATION_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+83;
+    int PEEK_SERVICE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+84;
+}
diff --git a/core/java/android/app/IActivityPendingResult.aidl b/core/java/android/app/IActivityPendingResult.aidl
new file mode 100644
index 0000000..e8eebf1
--- /dev/null
+++ b/core/java/android/app/IActivityPendingResult.aidl
@@ -0,0 +1,27 @@
+/* //device/java/android/android/app/IActivityPendingResult.aidl
+**
+** Copyright 2007, 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 android.app;
+
+import android.os.Bundle;
+
+/** @hide */
+interface IActivityPendingResult
+{
+    boolean sendResult(int code, String data, in Bundle ex);
+}
+
diff --git a/core/java/android/app/IActivityWatcher.aidl b/core/java/android/app/IActivityWatcher.aidl
new file mode 100644
index 0000000..f13a385
--- /dev/null
+++ b/core/java/android/app/IActivityWatcher.aidl
@@ -0,0 +1,55 @@
+/* //device/java/android/android/app/IInstrumentationWatcher.aidl
+**
+** Copyright 2007, 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 android.app;
+
+import android.content.Intent;
+
+/**
+ * Testing interface to monitor what is happening in the activity manager
+ * while tests are running.  Not for normal application development.
+ * {@hide}
+ */
+interface IActivityWatcher
+{
+    /**
+     * The system is trying to start an activity.  Return true to allow
+     * it to be started as normal, or false to cancel/reject this activity.
+     */
+    boolean activityStarting(in Intent intent, String pkg);
+    
+    /**
+     * The system is trying to return to an activity.  Return true to allow
+     * it to be resumed as normal, or false to cancel/reject this activity.
+     */
+    boolean activityResuming(String pkg);
+    
+    /**
+     * An application process has crashed (in Java).  Return true for the
+     * normal error recovery (app crash dialog) to occur, false to kill
+     * it immediately.
+     */
+    boolean appCrashed(String processName, int pid, String shortMsg,
+            String longMsg, in byte[] crashData);
+    
+    /**
+     * An application process is not responding.  Return 0 to show the "app
+     * not responding" dialog, 1 to continue waiting, or -1 to kill it
+     * immediately.
+     */
+    int appNotResponding(String processName, int pid, String processStats);
+}
diff --git a/core/java/android/app/IAlarmManager.aidl b/core/java/android/app/IAlarmManager.aidl
new file mode 100755
index 0000000..cb42236
--- /dev/null
+++ b/core/java/android/app/IAlarmManager.aidl
@@ -0,0 +1,34 @@
+/* //device/java/android/android/app/IAlarmManager.aidl
+**
+** Copyright 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 android.app;
+
+import android.app.PendingIntent;
+
+/**
+ * System private API for talking with the alarm manager service.
+ *
+ * {@hide}
+ */
+interface IAlarmManager {
+    void set(int type, long triggerAtTime, in PendingIntent operation);
+    void setRepeating(int type, long triggerAtTime, long interval, in PendingIntent operation);
+    void setInexactRepeating(int type, long triggerAtTime, long interval, in PendingIntent operation);
+    void setTimeZone(String zone);
+    void remove(in PendingIntent operation);
+}
+
+
diff --git a/core/java/android/app/IApplicationThread.java b/core/java/android/app/IApplicationThread.java
new file mode 100644
index 0000000..47476b5
--- /dev/null
+++ b/core/java/android/app/IApplicationThread.java
@@ -0,0 +1,118 @@
+/*
+ * 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 android.app;
+
+import android.content.ComponentName;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.ProviderInfo;
+import android.content.pm.ServiceInfo;
+import android.content.res.Configuration;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.os.IBinder;
+import android.os.IInterface;
+
+import java.io.FileDescriptor;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * System private API for communicating with the application.  This is given to
+ * the activity manager by an application  when it starts up, for the activity
+ * manager to tell the application about things it needs to do.
+ *
+ * {@hide}
+ */
+public interface IApplicationThread extends IInterface {
+    void schedulePauseActivity(IBinder token, boolean finished, boolean userLeaving,
+            int configChanges) throws RemoteException;
+    void scheduleStopActivity(IBinder token, boolean showWindow,
+            int configChanges) throws RemoteException;
+    void scheduleWindowVisibility(IBinder token, boolean showWindow) throws RemoteException;
+    void scheduleResumeActivity(IBinder token, boolean isForward) throws RemoteException;
+    void scheduleSendResult(IBinder token, List<ResultInfo> results) throws RemoteException;
+    void scheduleLaunchActivity(Intent intent, IBinder token,
+            ActivityInfo info, Bundle state, List<ResultInfo> pendingResults,
+    		List<Intent> pendingNewIntents, boolean notResumed, boolean isForward)
+    		throws RemoteException;
+    void scheduleRelaunchActivity(IBinder token, List<ResultInfo> pendingResults,
+            List<Intent> pendingNewIntents, int configChanges,
+            boolean notResumed) throws RemoteException;
+    void scheduleNewIntent(List<Intent> intent, IBinder token) throws RemoteException;
+    void scheduleDestroyActivity(IBinder token, boolean finished,
+            int configChanges) throws RemoteException;
+    void scheduleReceiver(Intent intent, ActivityInfo info, int resultCode,
+            String data, Bundle extras, boolean sync) throws RemoteException;
+    void scheduleCreateService(IBinder token, ServiceInfo info) throws RemoteException;
+    void scheduleBindService(IBinder token,
+            Intent intent, boolean rebind) throws RemoteException;
+    void scheduleUnbindService(IBinder token,
+            Intent intent) throws RemoteException;
+    void scheduleServiceArgs(IBinder token, int startId, Intent args) throws RemoteException;
+    void scheduleStopService(IBinder token) throws RemoteException;
+    static final int DEBUG_OFF = 0;
+    static final int DEBUG_ON = 1;
+    static final int DEBUG_WAIT = 2;
+    void bindApplication(String packageName, ApplicationInfo info, List<ProviderInfo> providers,
+            ComponentName testName, String profileName, Bundle testArguments, 
+            IInstrumentationWatcher testWatcher, int debugMode, Configuration config, Map<String,
+            IBinder> services) throws RemoteException;
+    void scheduleExit() throws RemoteException;
+    void requestThumbnail(IBinder token) throws RemoteException;
+    void scheduleConfigurationChanged(Configuration config) throws RemoteException;
+    void updateTimeZone() throws RemoteException;
+    void processInBackground() throws RemoteException;
+    void dumpService(FileDescriptor fd, IBinder servicetoken, String[] args)
+            throws RemoteException;
+    void scheduleRegisteredReceiver(IIntentReceiver receiver, Intent intent,
+            int resultCode, String data, Bundle extras, boolean ordered)
+            throws RemoteException;
+    void scheduleLowMemory() throws RemoteException;
+    void scheduleActivityConfigurationChanged(IBinder token) throws RemoteException;
+    void requestPss() throws RemoteException;
+
+    String descriptor = "android.app.IApplicationThread";
+
+    int SCHEDULE_PAUSE_ACTIVITY_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION;
+    int SCHEDULE_STOP_ACTIVITY_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+2;
+    int SCHEDULE_WINDOW_VISIBILITY_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+3;
+    int SCHEDULE_RESUME_ACTIVITY_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+4;
+    int SCHEDULE_SEND_RESULT_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+5;
+    int SCHEDULE_LAUNCH_ACTIVITY_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+6;
+    int SCHEDULE_NEW_INTENT_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+7;
+    int SCHEDULE_FINISH_ACTIVITY_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+8;
+    int SCHEDULE_RECEIVER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+9;
+    int SCHEDULE_CREATE_SERVICE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+10;
+    int SCHEDULE_STOP_SERVICE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+11;
+    int BIND_APPLICATION_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+12;
+    int SCHEDULE_EXIT_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+13;
+    int REQUEST_THUMBNAIL_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+14;
+    int SCHEDULE_CONFIGURATION_CHANGED_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+15;
+    int SCHEDULE_SERVICE_ARGS_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+16;
+    int UPDATE_TIME_ZONE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+17;
+    int PROCESS_IN_BACKGROUND_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+18;
+    int SCHEDULE_BIND_SERVICE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+19;
+    int SCHEDULE_UNBIND_SERVICE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+20;
+    int DUMP_SERVICE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+21;
+    int SCHEDULE_REGISTERED_RECEIVER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+22;
+    int SCHEDULE_LOW_MEMORY_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+23;
+    int SCHEDULE_ACTIVITY_CONFIGURATION_CHANGED_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+24;
+    int SCHEDULE_RELAUNCH_ACTIVITY_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+25;
+    int REQUEST_PSS_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+26;
+}
diff --git a/core/java/android/app/IInstrumentationWatcher.aidl b/core/java/android/app/IInstrumentationWatcher.aidl
new file mode 100644
index 0000000..405a3d8
--- /dev/null
+++ b/core/java/android/app/IInstrumentationWatcher.aidl
@@ -0,0 +1,31 @@
+/* //device/java/android/android/app/IInstrumentationWatcher.aidl
+**
+** Copyright 2007, 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 android.app;
+
+import android.content.ComponentName;
+import android.os.Bundle;
+
+/** @hide */
+oneway interface IInstrumentationWatcher
+{
+    void instrumentationStatus(in ComponentName name, int resultCode,
+            in Bundle results);
+    void instrumentationFinished(in ComponentName name, int resultCode,
+            in Bundle results);
+}
+
diff --git a/core/java/android/app/IIntentReceiver.aidl b/core/java/android/app/IIntentReceiver.aidl
new file mode 100755
index 0000000..5f5d0eb
--- /dev/null
+++ b/core/java/android/app/IIntentReceiver.aidl
@@ -0,0 +1,33 @@
+/*
+**
+** Copyright 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 android.app;
+
+import android.content.Intent;
+import android.os.Bundle;
+
+/**
+ * System private API for dispatching intent broadcasts.  This is given to the
+ * activity manager as part of registering for an intent broadcasts, and is
+ * called when it receives intents.
+ *
+ * {@hide}
+ */
+oneway interface IIntentReceiver {
+    void performReceive(in Intent intent, int resultCode,
+                        String data, in Bundle extras, boolean ordered);
+}
+
diff --git a/core/java/android/app/IIntentSender.aidl b/core/java/android/app/IIntentSender.aidl
new file mode 100644
index 0000000..53e135a
--- /dev/null
+++ b/core/java/android/app/IIntentSender.aidl
@@ -0,0 +1,27 @@
+/* //device/java/android/android/app/IActivityPendingResult.aidl
+**
+** Copyright 2007, 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 android.app;
+
+import android.app.IIntentReceiver;
+import android.content.Intent;
+
+/** @hide */
+interface IIntentSender {
+    int send(int code, in Intent intent, String resolvedType,
+            IIntentReceiver finishedReceiver);
+}
diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl
new file mode 100644
index 0000000..c1035b6
--- /dev/null
+++ b/core/java/android/app/INotificationManager.aidl
@@ -0,0 +1,34 @@
+/* //device/java/android/android/app/INotificationManager.aidl
+**
+** Copyright 2007, 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 android.app;
+
+import android.app.ITransientNotification;
+import android.app.Notification;
+import android.content.Intent;
+
+/** {@hide} */
+interface INotificationManager
+{
+    void enqueueNotification(String pkg, int id, in Notification notification, inout int[] idReceived);
+    void cancelNotification(String pkg, int id);
+    void cancelAllNotifications(String pkg);
+
+    void enqueueToast(String pkg, ITransientNotification callback, int duration);
+    void cancelToast(String pkg, ITransientNotification callback);
+}
+
diff --git a/core/java/android/app/ISearchManager.aidl b/core/java/android/app/ISearchManager.aidl
new file mode 100644
index 0000000..6c3617a
--- /dev/null
+++ b/core/java/android/app/ISearchManager.aidl
@@ -0,0 +1,25 @@
+/**
+ * Copyright (c) 2007, 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 android.app;
+
+import android.content.ComponentName;
+import android.server.search.SearchableInfo;
+
+/** @hide */
+interface ISearchManager {
+   SearchableInfo getSearchableInfo(in ComponentName launchActivity, boolean globalSearch);
+}
diff --git a/core/java/android/app/IServiceConnection.aidl b/core/java/android/app/IServiceConnection.aidl
new file mode 100644
index 0000000..6804071
--- /dev/null
+++ b/core/java/android/app/IServiceConnection.aidl
@@ -0,0 +1,26 @@
+/* //device/java/android/android/app/IServiceConnection.aidl
+**
+** Copyright 2007, 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 android.app;
+
+import android.content.ComponentName;
+
+/** @hide */
+oneway interface IServiceConnection {
+    void connected(in ComponentName name, IBinder service);
+}
+
diff --git a/core/java/android/app/IStatusBar.aidl b/core/java/android/app/IStatusBar.aidl
new file mode 100644
index 0000000..c64fa50
--- /dev/null
+++ b/core/java/android/app/IStatusBar.aidl
@@ -0,0 +1,29 @@
+/**
+ * Copyright (c) 2007, 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 android.app;
+
+/** @hide */
+interface IStatusBar
+{
+    void activate();
+    void deactivate();
+    void toggle();
+    void disable(int what, IBinder token, String pkg);
+    IBinder addIcon(String slot, String iconPackage, int iconId, int iconLevel);
+    void updateIcon(IBinder key, String slot, String iconPackage, int iconId, int iconLevel);
+    void removeIcon(IBinder key);
+}
diff --git a/core/java/android/app/IThumbnailReceiver.aidl b/core/java/android/app/IThumbnailReceiver.aidl
new file mode 100755
index 0000000..7943f2c
--- /dev/null
+++ b/core/java/android/app/IThumbnailReceiver.aidl
@@ -0,0 +1,30 @@
+/* //device/java/android/android/app/IThumbnailReceiver.aidl
+**
+** Copyright 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 android.app;
+
+import android.graphics.Bitmap;
+
+/**
+ * System private API for receiving updated thumbnails from a checkpoint.
+ *
+ * {@hide}
+ */
+oneway interface IThumbnailReceiver {
+    void newThumbnail(int id, in Bitmap thumbnail, CharSequence description);
+    void finished();
+}
+
diff --git a/core/java/android/app/ITransientNotification.aidl b/core/java/android/app/ITransientNotification.aidl
new file mode 100644
index 0000000..35b53a4
--- /dev/null
+++ b/core/java/android/app/ITransientNotification.aidl
@@ -0,0 +1,25 @@
+/* //device/java/android/android/app/ITransientNotification.aidl
+**
+** Copyright 2007, 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 android.app;
+
+/** @hide */
+oneway interface ITransientNotification {
+    void show();
+    void hide();
+}
+
diff --git a/core/java/android/app/IWallpaperService.aidl b/core/java/android/app/IWallpaperService.aidl
new file mode 100644
index 0000000..a332b1a
--- /dev/null
+++ b/core/java/android/app/IWallpaperService.aidl
@@ -0,0 +1,55 @@
+/**
+ * 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 android.app;
+
+import android.os.ParcelFileDescriptor;
+import android.app.IWallpaperServiceCallback;
+
+/** @hide */
+interface IWallpaperService {
+
+    /**
+     * Set the wallpaper.
+     */
+    ParcelFileDescriptor setWallpaper();
+    
+    /**
+     * Get the wallpaper.
+     */
+    ParcelFileDescriptor getWallpaper(IWallpaperServiceCallback cb);
+    
+    /**
+     * Clear the wallpaper.
+     */
+    void clearWallpaper();
+
+    /**
+     * Sets the dimension hint for the wallpaper. These hints indicate the desired
+     * minimum width and height for the wallpaper.
+     */
+    void setDimensionHints(in int width, in int height);
+
+    /**
+     * Returns the desired minimum width for the wallpaper.
+     */
+    int getWidthHint();
+
+    /**
+     * Returns the desired minimum height for the wallpaper.
+     */
+    int getHeightHint();
+}
diff --git a/core/java/android/app/IWallpaperServiceCallback.aidl b/core/java/android/app/IWallpaperServiceCallback.aidl
new file mode 100644
index 0000000..6086f40
--- /dev/null
+++ b/core/java/android/app/IWallpaperServiceCallback.aidl
@@ -0,0 +1,31 @@
+/*
+ * 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 android.app;
+
+/**
+ * Callback interface used by IWallpaperService to send asynchronous 
+ * notifications back to its clients.  Note that this is a
+ * one-way interface so the server does not block waiting for the client.
+ *
+ * @hide
+ */
+oneway interface IWallpaperServiceCallback {
+    /**
+     * Called when the wallpaper has changed
+     */
+    void onWallpaperChanged();
+}
diff --git a/core/java/android/app/Instrumentation.java b/core/java/android/app/Instrumentation.java
new file mode 100644
index 0000000..f6a28b2
--- /dev/null
+++ b/core/java/android/app/Instrumentation.java
@@ -0,0 +1,1613 @@
+/*
+ * 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 android.app;
+
+import android.content.ActivityNotFoundException;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.ActivityInfo;
+import android.content.res.Configuration;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.os.Debug;
+import android.os.IBinder;
+import android.os.MessageQueue;
+import android.os.Process;
+import android.os.SystemClock;
+import android.os.ServiceManager;
+import android.util.AndroidRuntimeException;
+import android.util.Config;
+import android.util.Log;
+import android.view.IWindowManager;
+import android.view.KeyCharacterMap;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+import android.view.ViewConfiguration;
+import android.view.Window;
+import android.view.inputmethod.InputMethodManager;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+
+
+/**
+ * Base class for implementing application instrumentation code.  When running
+ * with instrumentation turned on, this class will be instantiated for you
+ * before any of the application code, allowing you to monitor all of the
+ * interaction the system has with the application.  An Instrumentation
+ * implementation is described to the system through an AndroidManifest.xml's
+ * &lt;instrumentation&gt; tag.
+ */
+public class Instrumentation {
+    /**
+     * If included in the status or final bundle sent to an IInstrumentationWatcher, this key 
+     * identifies the class that is writing the report.  This can be used to provide more structured
+     * logging or reporting capabilities in the IInstrumentationWatcher.
+     */
+    public static final String REPORT_KEY_IDENTIFIER = "id";
+    /**
+     * If included in the status or final bundle sent to an IInstrumentationWatcher, this key 
+     * identifies a string which can simply be printed to the output stream.  Using these streams
+     * provides a "pretty printer" version of the status & final packets.  Any bundles including 
+     * this key should also include the complete set of raw key/value pairs, so that the
+     * instrumentation can also be launched, and results collected, by an automated system.
+     */
+    public static final String REPORT_KEY_STREAMRESULT = "stream";
+    
+    private static final String TAG = "Instrumentation";
+    
+    private final Object mSync = new Object();
+    private ActivityThread mThread = null;
+    private MessageQueue mMessageQueue = null;
+    private Context mInstrContext;
+    private Context mAppContext;
+    private ComponentName mComponent;
+    private Thread mRunner;
+    private List<ActivityWaiter> mWaitingActivities;
+    private List<ActivityMonitor> mActivityMonitors;
+    private IInstrumentationWatcher mWatcher;
+    private long mPreCpuTime;
+    private long mStart;
+    private boolean mAutomaticPerformanceSnapshots = false;
+    private Bundle mPrePerfMetrics = new Bundle();
+    private Bundle mPerfMetrics = new Bundle();
+
+    public Instrumentation() {
+    }
+
+    /**
+     * Called when the instrumentation is starting, before any application code
+     * has been loaded.  Usually this will be implemented to simply call
+     * {@link #start} to begin the instrumentation thread, which will then
+     * continue execution in {@link #onStart}.
+     * 
+     * <p>If you do not need your own thread -- that is you are writing your
+     * instrumentation to be completely asynchronous (returning to the event
+     * loop so that the application can run), you can simply begin your
+     * instrumentation here, for example call {@link Context#startActivity} to
+     * begin the appropriate first activity of the application. 
+     *  
+     * @param arguments Any additional arguments that were supplied when the 
+     *                  instrumentation was started.
+     */
+    public void onCreate(Bundle arguments) {
+    }
+
+    /**
+     * Create and start a new thread in which to run instrumentation.  This new
+     * thread will call to {@link #onStart} where you can implement the
+     * instrumentation.
+     */
+    public void start() {
+        if (mRunner != null) {
+            throw new RuntimeException("Instrumentation already started");
+        }
+        mRunner = new InstrumentationThread("Instr: " + getClass().getName());
+        mRunner.start();
+    }
+
+    /**
+     * Method where the instrumentation thread enters execution.  This allows
+     * you to run your instrumentation code in a separate thread than the
+     * application, so that it can perform blocking operation such as
+     * {@link #sendKeySync} or {@link #startActivitySync}.
+     * 
+     * <p>You will typically want to call finish() when this function is done,
+     * to end your instrumentation.
+     */
+    public void onStart() {
+    }
+
+    /**
+     * This is called whenever the system captures an unhandled exception that
+     * was thrown by the application.  The default implementation simply
+     * returns false, allowing normal system handling of the exception to take
+     * place.
+     * 
+     * @param obj The client object that generated the exception.  May be an
+     *            Application, Activity, BroadcastReceiver, Service, or null.
+     * @param e The exception that was thrown.
+     *  
+     * @return To allow normal system exception process to occur, return false.
+     *         If true is returned, the system will proceed as if the exception
+     *         didn't happen.
+     */
+    public boolean onException(Object obj, Throwable e) {
+        return false;
+    }
+
+    /**
+     * Provide a status report about the application.
+     *  
+     * @param resultCode Current success/failure of instrumentation. 
+     * @param results Any results to send back to the code that started the instrumentation.
+     */
+    public void sendStatus(int resultCode, Bundle results) {
+        if (mWatcher != null) {
+            try {
+                mWatcher.instrumentationStatus(mComponent, resultCode, results);
+            }
+            catch (RemoteException e) {
+                mWatcher = null;
+            }
+        }
+    }
+    
+    /**
+     * Terminate instrumentation of the application.  This will cause the
+     * application process to exit, removing this instrumentation from the next
+     * time the application is started. 
+     *  
+     * @param resultCode Overall success/failure of instrumentation. 
+     * @param results Any results to send back to the code that started the 
+     *                instrumentation.
+     */
+    public void finish(int resultCode, Bundle results) {
+        if (mAutomaticPerformanceSnapshots) {
+            endPerformanceSnapshot();
+        }
+        if (mPerfMetrics != null) {
+            results.putAll(mPerfMetrics);
+        }
+        mThread.finishInstrumentation(resultCode, results);
+    }
+    
+    public void setAutomaticPerformanceSnapshots() {
+        mAutomaticPerformanceSnapshots = true;
+    }
+
+    public void startPerformanceSnapshot() {
+        mStart = 0;
+        if (!isProfiling()) {
+            // Add initial binder counts
+            Bundle binderCounts = getBinderCounts();
+            for (String key: binderCounts.keySet()) {
+                addPerfMetricLong("pre_" + key, binderCounts.getLong(key));
+            }
+
+            // Force a GC and zero out the performance counters.  Do this
+            // before reading initial CPU/wall-clock times so we don't include
+            // the cost of this setup in our final metrics.
+            startAllocCounting();
+
+            // Record CPU time up to this point, and start timing.  Note:  this
+            // must happen at the end of this method, otherwise the timing will
+            // include noise.
+            mStart = SystemClock.uptimeMillis();
+            mPreCpuTime = Process.getElapsedCpuTime();
+        }
+    }
+    
+    public void endPerformanceSnapshot() {
+        if (!isProfiling()) {
+            // Stop the timing. This must be done first before any other counting is stopped.
+            long cpuTime = Process.getElapsedCpuTime();
+            long duration = SystemClock.uptimeMillis();
+            
+            stopAllocCounting();
+            
+            long nativeMax = Debug.getNativeHeapSize() / 1024;
+            long nativeAllocated = Debug.getNativeHeapAllocatedSize() / 1024;
+            long nativeFree = Debug.getNativeHeapFreeSize() / 1024;
+
+            Debug.MemoryInfo memInfo = new Debug.MemoryInfo();
+            Debug.getMemoryInfo(memInfo);
+
+            Runtime runtime = Runtime.getRuntime();
+
+            long dalvikMax = runtime.totalMemory() / 1024;
+            long dalvikFree = runtime.freeMemory() / 1024;
+            long dalvikAllocated = dalvikMax - dalvikFree;
+            
+            // Add final binder counts
+            Bundle binderCounts = getBinderCounts();
+            for (String key: binderCounts.keySet()) {
+                addPerfMetricLong(key, binderCounts.getLong(key));
+            }
+            
+            // Add alloc counts
+            Bundle allocCounts = getAllocCounts();
+            for (String key: allocCounts.keySet()) {
+                addPerfMetricLong(key, allocCounts.getLong(key));
+            }
+            
+            addPerfMetricLong("execution_time", duration - mStart);
+            addPerfMetricLong("pre_cpu_time", mPreCpuTime);
+            addPerfMetricLong("cpu_time", cpuTime - mPreCpuTime);
+
+            addPerfMetricLong("native_size", nativeMax);
+            addPerfMetricLong("native_allocated", nativeAllocated);
+            addPerfMetricLong("native_free", nativeFree);
+            addPerfMetricInt("native_pss", memInfo.nativePss);
+            addPerfMetricInt("native_private_dirty", memInfo.nativePrivateDirty);
+            addPerfMetricInt("native_shared_dirty", memInfo.nativeSharedDirty);
+            
+            addPerfMetricLong("java_size", dalvikMax);
+            addPerfMetricLong("java_allocated", dalvikAllocated);
+            addPerfMetricLong("java_free", dalvikFree);
+            addPerfMetricInt("java_pss", memInfo.dalvikPss);
+            addPerfMetricInt("java_private_dirty", memInfo.dalvikPrivateDirty);
+            addPerfMetricInt("java_shared_dirty", memInfo.dalvikSharedDirty);
+            
+            addPerfMetricInt("other_pss", memInfo.otherPss);
+            addPerfMetricInt("other_private_dirty", memInfo.otherPrivateDirty);
+            addPerfMetricInt("other_shared_dirty", memInfo.otherSharedDirty);
+            
+        }
+    }
+    
+    private void addPerfMetricLong(String key, long value) {
+        mPerfMetrics.putLong("performance." + key, value);
+    }
+    
+    private void addPerfMetricInt(String key, int value) {
+        mPerfMetrics.putInt("performance." + key, value);
+    }
+    
+    /**
+     * Called when the instrumented application is stopping, after all of the
+     * normal application cleanup has occurred.
+     */
+    public void onDestroy() {
+    }
+
+    /**
+     * Return the Context of this instrumentation's package.  Note that this is
+     * often different than the Context of the application being
+     * instrumentated, since the instrumentation code often lives is a
+     * different package than that of the application it is running against.
+     * See {@link #getTargetContext} to retrieve a Context for the target
+     * application.
+     * 
+     * @return The instrumentation's package context.
+     * 
+     * @see #getTargetContext
+     */
+    public Context getContext() {
+        return mInstrContext;
+    }
+
+    /**
+     * Returns complete component name of this instrumentation.
+     * 
+     * @return Returns the complete component name for this instrumentation.
+     */
+    public ComponentName getComponentName() {
+        return mComponent;
+    }
+    
+    /**
+     * Return a Context for the target application being instrumented.  Note
+     * that this is often different than the Context of the instrumentation
+     * code, since the instrumentation code often lives is a different package
+     * than that of the application it is running against. See
+     * {@link #getContext} to retrieve a Context for the instrumentation code.
+     * 
+     * @return A Context in the target application.
+     * 
+     * @see #getContext
+     */
+    public Context getTargetContext() {
+        return mAppContext;
+    }
+
+    /**
+     * Check whether this instrumentation was started with profiling enabled.
+     * 
+     * @return Returns true if profiling was enabled when starting, else false.
+     */
+    public boolean isProfiling() {
+        return mThread.isProfiling();
+    }
+
+    /**
+     * This method will start profiling if isProfiling() returns true. You should
+     * only call this method if you set the handleProfiling attribute in the 
+     * manifest file for this Instrumentation to true.  
+     */
+    public void startProfiling() {
+        if (mThread.isProfiling()) {
+            File file = new File(mThread.getProfileFilePath());
+            file.getParentFile().mkdirs();
+            Debug.startMethodTracing(file.toString(), 8 * 1024 * 1024);
+        }
+    }
+
+    /**
+     * Stops profiling if isProfiling() returns true.
+     */
+    public void stopProfiling() {
+        if (mThread.isProfiling()) {
+            Debug.stopMethodTracing();
+        }
+    }
+    
+    /**
+     * Force the global system in or out of touch mode.  This can be used if
+     * your instrumentation relies on the UI being in one more or the other
+     * when it starts.
+     * 
+     * @param inTouch Set to true to be in touch mode, false to be in
+     * focus mode.
+     */
+    public void setInTouchMode(boolean inTouch) {
+        try {
+            IWindowManager.Stub.asInterface(
+                    ServiceManager.getService("window")).setInTouchMode(inTouch);
+        } catch (RemoteException e) {
+            // Shouldn't happen!
+        }
+    }
+    
+    /**
+     * Schedule a callback for when the application's main thread goes idle
+     * (has no more events to process).
+     * 
+     * @param recipient Called the next time the thread's message queue is
+     *                  idle.
+     */
+    public void waitForIdle(Runnable recipient) {
+        mMessageQueue.addIdleHandler(new Idler(recipient));
+        mThread.getHandler().post(new EmptyRunnable());
+    }
+
+    /**
+     * Synchronously wait for the application to be idle.  Can not be called
+     * from the main application thread -- use {@link #start} to execute
+     * instrumentation in its own thread.
+     */
+    public void waitForIdleSync() {
+        validateNotAppThread();
+        Idler idler = new Idler(null);
+        mMessageQueue.addIdleHandler(idler);
+        mThread.getHandler().post(new EmptyRunnable());
+        idler.waitForIdle();
+    }
+
+    /**
+     * Execute a call on the application's main thread, blocking until it is
+     * complete.  Useful for doing things that are not thread-safe, such as
+     * looking at or modifying the view hierarchy.
+     * 
+     * @param runner The code to run on the main thread.
+     */
+    public void runOnMainSync(Runnable runner) {
+        validateNotAppThread();
+        SyncRunnable sr = new SyncRunnable(runner);
+        mThread.getHandler().post(sr);
+        sr.waitForComplete();
+    }
+
+    /**
+     * Start a new activity and wait for it to begin running before returning.
+     * In addition to being synchronous, this method as some semantic
+     * differences from the standard {@link Context#startActivity} call: the
+     * activity component is resolved before talking with the activity manager
+     * (its class name is specified in the Intent that this method ultimately
+     * starts), and it does not allow you to start activities that run in a
+     * different process.  In addition, if the given Intent resolves to
+     * multiple activities, instead of displaying a dialog for the user to
+     * select an activity, an exception will be thrown.
+     * 
+     * <p>The function returns as soon as the activity goes idle following the
+     * call to its {@link Activity#onCreate}.  Generally this means it has gone
+     * through the full initialization including {@link Activity#onResume} and
+     * drawn and displayed its initial window.
+     * 
+     * @param intent Description of the activity to start.
+     * 
+     * @see Context#startActivity
+     */
+    public Activity startActivitySync(Intent intent) {
+        validateNotAppThread();
+
+        synchronized (mSync) {
+            intent = new Intent(intent);
+    
+            ActivityInfo ai = intent.resolveActivityInfo(
+                getTargetContext().getPackageManager(), 0);
+            if (ai == null) {
+                throw new RuntimeException("Unable to resolve activity for: " + intent);
+            }
+            if (!ai.applicationInfo.processName.equals(
+                    getTargetContext().getPackageName())) {
+                // todo: if this intent is ambiguous, look here to see if
+                // there is a single match that is in our package.
+                throw new RuntimeException("Intent resolved to different package "
+                                           + ai.applicationInfo.packageName + ": "
+                                           + intent);
+            }
+    
+            intent.setComponent(new ComponentName(
+                    ai.applicationInfo.packageName, ai.name));
+            final ActivityWaiter aw = new ActivityWaiter(intent);
+
+            if (mWaitingActivities == null) {
+                mWaitingActivities = new ArrayList();
+            }
+            mWaitingActivities.add(aw);
+
+            getTargetContext().startActivity(intent);
+
+            do {
+                try {
+                    mSync.wait();
+                } catch (InterruptedException e) {
+                }
+            } while (mWaitingActivities.contains(aw));
+         
+            return aw.activity;
+        }
+    }
+
+    /**
+     * Information about a particular kind of Intent that is being monitored.
+     * An instance of this class is added to the 
+     * current instrumentation through {@link #addMonitor}; after being added, 
+     * when a new activity is being started the monitor will be checked and, if 
+     * matching, its hit count updated and (optionally) the call stopped and a 
+     * canned result returned.
+     * 
+     * <p>An ActivityMonitor can also be used to look for the creation of an
+     * activity, through the {@link #waitForActivity} method.  This will return
+     * after a matching activity has been created with that activity object.
+     */
+    public static class ActivityMonitor {
+        private final IntentFilter mWhich;
+        private final String mClass;
+        private final ActivityResult mResult;
+        private final boolean mBlock;
+
+
+        // This is protected by 'Instrumentation.this.mSync'.
+        /*package*/ int mHits = 0;
+
+        // This is protected by 'this'.
+        /*package*/ Activity mLastActivity = null;
+
+        /**
+         * Create a new ActivityMonitor that looks for a particular kind of 
+         * intent to be started.
+         *  
+         * @param which The set of intents this monitor is responsible for.
+         * @param result A canned result to return if the monitor is hit; can 
+         *               be null.
+         * @param block Controls whether the monitor should block the activity 
+         *              start (returning its canned result) or let the call
+         *              proceed.
+         *  
+         * @see Instrumentation#addMonitor 
+         */
+        public ActivityMonitor(
+            IntentFilter which, ActivityResult result, boolean block) {
+            mWhich = which;
+            mClass = null;
+            mResult = result;
+            mBlock = block;
+        }
+
+        /**
+         * Create a new ActivityMonitor that looks for a specific activity 
+         * class to be started. 
+         *  
+         * @param cls The activity class this monitor is responsible for.
+         * @param result A canned result to return if the monitor is hit; can 
+         *               be null.
+         * @param block Controls whether the monitor should block the activity 
+         *              start (returning its canned result) or let the call
+         *              proceed.
+         *  
+         * @see Instrumentation#addMonitor 
+         */
+        public ActivityMonitor(
+            String cls, ActivityResult result, boolean block) {
+            mWhich = null;
+            mClass = cls;
+            mResult = result;
+            mBlock = block;
+        }
+
+        /**
+         * Retrieve the filter associated with this ActivityMonitor.
+         */
+        public final IntentFilter getFilter() {
+            return mWhich;
+        }
+
+        /**
+         * Retrieve the result associated with this ActivityMonitor, or null if 
+         * none. 
+         */
+        public final ActivityResult getResult() {
+            return mResult;
+        }
+
+        /**
+         * Check whether this monitor blocks activity starts (not allowing the 
+         * actual activity to run) or allows them to execute normally. 
+         */
+        public final boolean isBlocking() {
+            return mBlock;
+        }
+
+        /**
+         * Retrieve the number of times the monitor has been hit so far.
+         */
+        public final int getHits() {
+            return mHits;
+        }
+
+        /**
+         * Retrieve the most recent activity class that was seen by this 
+         * monitor. 
+         */
+        public final Activity getLastActivity() {
+            return mLastActivity;
+        }
+
+        /**
+         * Block until an Activity is created that matches this monitor, 
+         * returning the resulting activity. 
+         * 
+         * @return Activity
+         */
+        public final Activity waitForActivity() {
+            synchronized (this) {
+                while (mLastActivity == null) {
+                    try {
+                        wait();
+                    } catch (InterruptedException e) {
+                    }
+                }
+                Activity res = mLastActivity;
+                mLastActivity = null;
+                return res;
+            }
+        }
+
+        /**
+         * Block until an Activity is created that matches this monitor, 
+         * returning the resulting activity or till the timeOut period expires.
+         * If the timeOut expires before the activity is started, return null. 
+         * 
+         * @param timeOut Time to wait before the activity is created.
+         * 
+         * @return Activity
+         */
+        public final Activity waitForActivityWithTimeout(long timeOut) {
+            synchronized (this) {
+                try {
+                    wait(timeOut);
+                } catch (InterruptedException e) {
+                }
+                if (mLastActivity == null) {
+                    return null;
+                } else {
+                    Activity res = mLastActivity;
+                    mLastActivity = null;
+                    return res;
+                }
+            }
+        }
+        
+        final boolean match(Context who,
+                            Activity activity,
+                            Intent intent) {
+            synchronized (this) {
+                if (mWhich != null
+                    && mWhich.match(who.getContentResolver(), intent,
+                                    true, "Instrumentation") < 0) {
+                    return false;
+                }
+                if (mClass != null) {
+                    String cls = null;
+                    if (activity != null) {
+                        cls = activity.getClass().getName();
+                    } else if (intent.getComponent() != null) {
+                        cls = intent.getComponent().getClassName();
+                    }
+                    if (cls == null || !mClass.equals(cls)) {
+                        return false;
+                    }
+                }
+                if (activity != null) {
+                    mLastActivity = activity;
+                    notifyAll();
+                }
+                return true;
+            }
+        }
+    }
+
+    /**
+     * Add a new {@link ActivityMonitor} that will be checked whenever an 
+     * activity is started.  The monitor is added 
+     * after any existing ones; the monitor will be hit only if none of the 
+     * existing monitors can themselves handle the Intent. 
+     *  
+     * @param monitor The new ActivityMonitor to see. 
+     *  
+     * @see #addMonitor(IntentFilter, ActivityResult, boolean) 
+     * @see #checkMonitorHit 
+     */
+    public void addMonitor(ActivityMonitor monitor) {
+        synchronized (mSync) {
+            if (mActivityMonitors == null) {
+                mActivityMonitors = new ArrayList();
+            }
+            mActivityMonitors.add(monitor);
+        }
+    }
+
+    /**
+     * A convenience wrapper for {@link #addMonitor(ActivityMonitor)} that 
+     * creates an intent filter matching {@link ActivityMonitor} for you and 
+     * returns it. 
+     *  
+     * @param filter The set of intents this monitor is responsible for.
+     * @param result A canned result to return if the monitor is hit; can 
+     *               be null.
+     * @param block Controls whether the monitor should block the activity 
+     *              start (returning its canned result) or let the call
+     *              proceed.
+     * 
+     * @return The newly created and added activity monitor. 
+     *  
+     * @see #addMonitor(ActivityMonitor) 
+     * @see #checkMonitorHit 
+     */
+    public ActivityMonitor addMonitor(
+        IntentFilter filter, ActivityResult result, boolean block) {
+        ActivityMonitor am = new ActivityMonitor(filter, result, block);
+        addMonitor(am);
+        return am;
+    }
+
+    /**
+     * A convenience wrapper for {@link #addMonitor(ActivityMonitor)} that 
+     * creates a class matching {@link ActivityMonitor} for you and returns it.
+     *  
+     * @param cls The activity class this monitor is responsible for.
+     * @param result A canned result to return if the monitor is hit; can 
+     *               be null.
+     * @param block Controls whether the monitor should block the activity 
+     *              start (returning its canned result) or let the call
+     *              proceed.
+     * 
+     * @return The newly created and added activity monitor. 
+     *  
+     * @see #addMonitor(ActivityMonitor) 
+     * @see #checkMonitorHit 
+     */
+    public ActivityMonitor addMonitor(
+        String cls, ActivityResult result, boolean block) {
+        ActivityMonitor am = new ActivityMonitor(cls, result, block);
+        addMonitor(am);
+        return am;
+    }
+
+    /**
+     * Test whether an existing {@link ActivityMonitor} has been hit.  If the 
+     * monitor has been hit at least <var>minHits</var> times, then it will be 
+     * removed from the activity monitor list and true returned.  Otherwise it 
+     * is left as-is and false is returned. 
+     *  
+     * @param monitor The ActivityMonitor to check.
+     * @param minHits The minimum number of hits required.
+     * 
+     * @return True if the hit count has been reached, else false. 
+     *  
+     * @see #addMonitor 
+     */
+    public boolean checkMonitorHit(ActivityMonitor monitor, int minHits) {
+        waitForIdleSync();
+        synchronized (mSync) {
+            if (monitor.getHits() < minHits) {
+                return false;
+            }
+            mActivityMonitors.remove(monitor);
+        }
+        return true;
+    }
+
+    /**
+     * Wait for an existing {@link ActivityMonitor} to be hit.  Once the 
+     * monitor has been hit, it is removed from the activity monitor list and 
+     * the first created Activity object that matched it is returned.
+     *  
+     * @param monitor The ActivityMonitor to wait for.
+     * 
+     * @return The Activity object that matched the monitor.
+     */
+    public Activity waitForMonitor(ActivityMonitor monitor) {
+        Activity activity = monitor.waitForActivity();
+        synchronized (mSync) {
+            mActivityMonitors.remove(monitor);
+        }
+        return activity;
+    }
+
+    /**
+     * Wait for an existing {@link ActivityMonitor} to be hit till the timeout
+     * expires.  Once the monitor has been hit, it is removed from the activity 
+     * monitor list and the first created Activity object that matched it is 
+     * returned.  If the timeout expires, a null object is returned. 
+     *
+     * @param monitor The ActivityMonitor to wait for.
+     * @param timeOut The timeout value in secs.
+     *
+     * @return The Activity object that matched the monitor.
+     */
+    public Activity waitForMonitorWithTimeout(ActivityMonitor monitor, long timeOut) {
+        Activity activity = monitor.waitForActivityWithTimeout(timeOut);
+        synchronized (mSync) {
+            mActivityMonitors.remove(monitor);
+        }
+        return activity;
+    }
+    
+    /**
+     * Remove an {@link ActivityMonitor} that was previously added with 
+     * {@link #addMonitor}.
+     *  
+     * @param monitor The monitor to remove.
+     *  
+     * @see #addMonitor 
+     */
+    public void removeMonitor(ActivityMonitor monitor) {
+        synchronized (mSync) {
+            mActivityMonitors.remove(monitor);
+        }
+    }
+
+    /**
+     * Execute a particular menu item.
+     * 
+     * @param targetActivity The activity in question.
+     * @param id The identifier associated with the menu item.
+     * @param flag Additional flags, if any.
+     * @return Whether the invocation was successful (for example, it could be
+     *         false if item is disabled).
+     */
+    public boolean invokeMenuActionSync(Activity targetActivity, 
+                                    int id, int flag) {
+        class MenuRunnable implements Runnable {
+            private final Activity activity;
+            private final int identifier;
+            private final int flags;
+            boolean returnValue;
+            
+            public MenuRunnable(Activity _activity, int _identifier,
+                                    int _flags) {
+                activity = _activity;
+                identifier = _identifier;
+                flags = _flags;
+            }
+            
+            public void run() {
+                Window win = activity.getWindow();
+                
+                returnValue = win.performPanelIdentifierAction(
+                            Window.FEATURE_OPTIONS_PANEL,
+                            identifier, 
+                            flags);                
+            }
+            
+        }        
+        MenuRunnable mr = new MenuRunnable(targetActivity, id, flag);
+        runOnMainSync(mr);
+        return mr.returnValue;
+    }
+
+    /**
+     * Show the context menu for the currently focused view and executes a
+     * particular context menu item.
+     * 
+     * @param targetActivity The activity in question.
+     * @param id The identifier associated with the context menu item.
+     * @param flag Additional flags, if any.
+     * @return Whether the invocation was successful (for example, it could be
+     *         false if item is disabled).
+     */
+    public boolean invokeContextMenuAction(Activity targetActivity, int id, int flag) {
+        validateNotAppThread();
+        
+        // Bring up context menu for current focus.
+        // It'd be nice to do this through code, but currently ListView depends on
+        //   long press to set metadata for its selected child
+        
+        final KeyEvent downEvent = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DPAD_CENTER); 
+        sendKeySync(downEvent);
+
+        // Need to wait for long press
+        waitForIdleSync();
+        try {
+            Thread.sleep(ViewConfiguration.getLongPressTimeout());
+        } catch (InterruptedException e) {
+            Log.e(TAG, "Could not sleep for long press timeout", e);
+            return false;
+        }
+
+        final KeyEvent upEvent = new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_DPAD_CENTER); 
+        sendKeySync(upEvent);
+
+        // Wait for context menu to appear
+        waitForIdleSync();
+        
+        class ContextMenuRunnable implements Runnable {
+            private final Activity activity;
+            private final int identifier;
+            private final int flags;
+            boolean returnValue;
+            
+            public ContextMenuRunnable(Activity _activity, int _identifier,
+                                    int _flags) {
+                activity = _activity;
+                identifier = _identifier;
+                flags = _flags;
+            }
+            
+            public void run() {
+                Window win = activity.getWindow();
+                returnValue = win.performContextMenuIdentifierAction(
+                            identifier, 
+                            flags);                
+            }
+            
+        }        
+        
+        ContextMenuRunnable cmr = new ContextMenuRunnable(targetActivity, id, flag);
+        runOnMainSync(cmr);
+        return cmr.returnValue;
+    }
+    
+    /**
+     * Sends the key events corresponding to the text to the app being
+     * instrumented.
+     * 
+     * @param text The text to be sent. 
+     */
+    public void sendStringSync(String text) {
+        if (text == null) {
+            return;
+        }
+        KeyCharacterMap keyCharacterMap = 
+            KeyCharacterMap.load(KeyCharacterMap.BUILT_IN_KEYBOARD);
+        
+        KeyEvent[] events = keyCharacterMap.getEvents(text.toCharArray());
+        
+        if (events != null) {
+            for (int i = 0; i < events.length; i++) {
+                sendKeySync(events[i]);
+            }
+        }        
+    }
+    
+    /**
+     * Send a key event to the currently focused window/view and wait for it to
+     * be processed.  Finished at some point after the recipient has returned
+     * from its event processing, though it may <em>not</em> have completely
+     * finished reacting from the event -- for example, if it needs to update
+     * its display as a result, it may still be in the process of doing that.
+     * 
+     * @param event The event to send to the current focus.
+     */
+    public void sendKeySync(KeyEvent event) {
+        validateNotAppThread();
+        try {
+            (IWindowManager.Stub.asInterface(ServiceManager.getService("window")))
+                .injectKeyEvent(event, true);
+        } catch (RemoteException e) {
+        }
+    }
+    
+    /**
+     * Sends an up and down key event sync to the currently focused window.
+     * 
+     * @param key The integer keycode for the event.
+     */
+    public void sendKeyDownUpSync(int key) {        
+        sendKeySync(new KeyEvent(KeyEvent.ACTION_DOWN, key));
+        sendKeySync(new KeyEvent(KeyEvent.ACTION_UP, key));
+    }
+
+    /**
+     * Higher-level method for sending both the down and up key events for a
+     * particular character key code.  Equivalent to creating both KeyEvent
+     * objects by hand and calling {@link #sendKeySync}.  The event appears
+     * as if it came from keyboard 0, the built in one.
+     * 
+     * @param keyCode The key code of the character to send.
+     */
+    public void sendCharacterSync(int keyCode) {
+        sendKeySync(new KeyEvent(KeyEvent.ACTION_DOWN, keyCode));
+        sendKeySync(new KeyEvent(KeyEvent.ACTION_UP, keyCode));
+    }
+    
+    /**
+     * Dispatch a pointer event. Finished at some point after the recipient has
+     * returned from its event processing, though it may <em>not</em> have
+     * completely finished reacting from the event -- for example, if it needs
+     * to update its display as a result, it may still be in the process of
+     * doing that.
+     * 
+     * @param event A motion event describing the pointer action.  (As noted in 
+     * {@link MotionEvent#obtain(long, long, int, float, float, int)}, be sure to use 
+     * {@link SystemClock#uptimeMillis()} as the timebase.
+     */
+    public void sendPointerSync(MotionEvent event) {
+        validateNotAppThread();
+        try {
+            (IWindowManager.Stub.asInterface(ServiceManager.getService("window")))
+                .injectPointerEvent(event, true);
+        } catch (RemoteException e) {
+        }
+    }
+
+    /**
+     * Dispatch a trackball event. Finished at some point after the recipient has
+     * returned from its event processing, though it may <em>not</em> have
+     * completely finished reacting from the event -- for example, if it needs
+     * to update its display as a result, it may still be in the process of
+     * doing that.
+     * 
+     * @param event A motion event describing the trackball action.  (As noted in 
+     * {@link MotionEvent#obtain(long, long, int, float, float, int)}, be sure to use 
+     * {@link SystemClock#uptimeMillis()} as the timebase.
+     */
+    public void sendTrackballEventSync(MotionEvent event) {
+        validateNotAppThread();
+        try {
+            (IWindowManager.Stub.asInterface(ServiceManager.getService("window")))
+                .injectTrackballEvent(event, true);
+        } catch (RemoteException e) {
+        }
+    }
+
+    /**
+     * Perform instantiation of the process's {@link Application} object.  The
+     * default implementation provides the normal system behavior.
+     * 
+     * @param cl The ClassLoader with which to instantiate the object.
+     * @param className The name of the class implementing the Application
+     *                  object.
+     * @param context The context to initialize the application with
+     * 
+     * @return The newly instantiated Application object.
+     */
+    public Application newApplication(ClassLoader cl, String className, Context context)
+            throws InstantiationException, IllegalAccessException, 
+            ClassNotFoundException {
+        return newApplication(cl.loadClass(className), context);
+    }
+    
+    /**
+     * Perform instantiation of the process's {@link Application} object.  The
+     * default implementation provides the normal system behavior.
+     * 
+     * @param clazz The class used to create an Application object from.
+     * @param context The context to initialize the application with
+     * 
+     * @return The newly instantiated Application object.
+     */
+    static public Application newApplication(Class<?> clazz, Context context)
+            throws InstantiationException, IllegalAccessException, 
+            ClassNotFoundException {
+        Application app = (Application)clazz.newInstance();
+        app.attach(context);
+        return app;
+    }
+
+    /**
+     * Perform calling of the application's {@link Application#onCreate}
+     * method.  The default implementation simply calls through to that method.
+     * 
+     * @param app The application being created.
+     */
+    public void callApplicationOnCreate(Application app) {
+        app.onCreate();
+    }
+    
+    /**
+     * Perform instantiation of an {@link Activity} object.  This method is intended for use with
+     * unit tests, such as android.test.ActivityUnitTestCase.  The activity will be useable
+     * locally but will be missing some of the linkages necessary for use within the sytem.
+     * 
+     * @param clazz The Class of the desired Activity
+     * @param context The base context for the activity to use
+     * @param token The token for this activity to communicate with
+     * @param application The application object (if any)
+     * @param intent The intent that started this Activity
+     * @param info ActivityInfo from the manifest
+     * @param title The title, typically retrieved from the ActivityInfo record
+     * @param parent The parent Activity (if any)
+     * @param id The embedded Id (if any)
+     * @param lastNonConfigurationInstance Arbitrary object that will be
+     * available via {@link Activity#getLastNonConfigurationInstance()
+     * Activity.getLastNonConfigurationInstance()}.
+     * @return Returns the instantiated activity
+     * @throws InstantiationException
+     * @throws IllegalAccessException
+     */
+    public Activity newActivity(Class<?> clazz, Context context, 
+            IBinder token, Application application, Intent intent, ActivityInfo info, 
+            CharSequence title, Activity parent, String id,
+            Object lastNonConfigurationInstance) throws InstantiationException, 
+            IllegalAccessException {
+        Activity activity = (Activity)clazz.newInstance();
+        ActivityThread aThread = null;
+        activity.attach(context, aThread, this, token, application, intent, info, title,
+                parent, id, lastNonConfigurationInstance, new Configuration());
+        return activity;
+    }
+
+    /**
+     * Perform instantiation of the process's {@link Activity} object.  The
+     * default implementation provides the normal system behavior.
+     * 
+     * @param cl The ClassLoader with which to instantiate the object.
+     * @param className The name of the class implementing the Activity
+     *                  object.
+     * @param intent The Intent object that specified the activity class being
+     *               instantiated.
+     * 
+     * @return The newly instantiated Activity object.
+     */
+    public Activity newActivity(ClassLoader cl, String className,
+            Intent intent)
+            throws InstantiationException, IllegalAccessException,
+            ClassNotFoundException {
+        return (Activity)cl.loadClass(className).newInstance();
+    }
+
+    /**
+     * Perform calling of an activity's {@link Activity#onCreate}
+     * method.  The default implementation simply calls through to that method.
+     * 
+     * @param activity The activity being created.
+     * @param icicle The previously frozen state (or null) to pass through to
+     *               onCreate().
+     */
+    public void callActivityOnCreate(Activity activity, Bundle icicle) {
+        if (mWaitingActivities != null) {
+            synchronized (mSync) {
+                final int N = mWaitingActivities.size();
+                for (int i=0; i<N; i++) {
+                    final ActivityWaiter aw = mWaitingActivities.get(i);
+                    final Intent intent = aw.intent;
+                    if (intent.filterEquals(activity.getIntent())) {
+                        aw.activity = activity;
+                        mMessageQueue.addIdleHandler(new ActivityGoing(aw));
+                    }
+                }
+            }
+        }
+        
+        activity.onCreate(icicle);
+        
+        if (mActivityMonitors != null) {
+            synchronized (mSync) {
+                final int N = mActivityMonitors.size();
+                for (int i=0; i<N; i++) {
+                    final ActivityMonitor am = mActivityMonitors.get(i);
+                    am.match(activity, activity, activity.getIntent());
+                }
+            }
+        }
+    }
+    
+    public void callActivityOnDestroy(Activity activity) {
+      if (mWaitingActivities != null) {
+          synchronized (mSync) {
+              final int N = mWaitingActivities.size();
+              for (int i=0; i<N; i++) {
+                  final ActivityWaiter aw = mWaitingActivities.get(i);
+                  final Intent intent = aw.intent;
+                  if (intent.filterEquals(activity.getIntent())) {
+                      aw.activity = activity;
+                      mMessageQueue.addIdleHandler(new ActivityGoing(aw));
+                  }
+              }
+          }
+      }
+      
+      activity.onDestroy();
+      
+      if (mActivityMonitors != null) {
+          synchronized (mSync) {
+              final int N = mActivityMonitors.size();
+              for (int i=0; i<N; i++) {
+                  final ActivityMonitor am = mActivityMonitors.get(i);
+                  am.match(activity, activity, activity.getIntent());
+              }
+          }
+      }
+  }
+
+    /**
+     * Perform calling of an activity's {@link Activity#onRestoreInstanceState}
+     * method.  The default implementation simply calls through to that method.
+     * 
+     * @param activity The activity being restored.
+     * @param savedInstanceState The previously saved state being restored.
+     */
+    public void callActivityOnRestoreInstanceState(Activity activity, Bundle savedInstanceState) {
+        activity.performRestoreInstanceState(savedInstanceState);
+    }
+
+    /**
+     * Perform calling of an activity's {@link Activity#onPostCreate} method.
+     * The default implementation simply calls through to that method.
+     * 
+     * @param activity The activity being created.
+     * @param icicle The previously frozen state (or null) to pass through to
+     *               onPostCreate().
+     */
+    public void callActivityOnPostCreate(Activity activity, Bundle icicle) {
+        activity.onPostCreate(icicle);
+    }
+
+    /**
+     * Perform calling of an activity's {@link Activity#onNewIntent}
+     * method.  The default implementation simply calls through to that method.
+     * 
+     * @param activity The activity receiving a new Intent.
+     * @param intent The new intent being received.
+     */
+    public void callActivityOnNewIntent(Activity activity, Intent intent) {
+        activity.onNewIntent(intent);
+    }
+
+    /**
+     * Perform calling of an activity's {@link Activity#onStart}
+     * method.  The default implementation simply calls through to that method.
+     * 
+     * @param activity The activity being started.
+     */
+    public void callActivityOnStart(Activity activity) {
+        activity.onStart();
+    }
+
+    /**
+     * Perform calling of an activity's {@link Activity#onRestart}
+     * method.  The default implementation simply calls through to that method.
+     * 
+     * @param activity The activity being restarted.
+     */
+    public void callActivityOnRestart(Activity activity) {
+        activity.onRestart();
+    }
+
+    /**
+     * Perform calling of an activity's {@link Activity#onResume} method.  The
+     * default implementation simply calls through to that method.
+     * 
+     * @param activity The activity being resumed.
+     */
+    public void callActivityOnResume(Activity activity) {
+        activity.onResume();
+        
+        if (mActivityMonitors != null) {
+            synchronized (mSync) {
+                final int N = mActivityMonitors.size();
+                for (int i=0; i<N; i++) {
+                    final ActivityMonitor am = mActivityMonitors.get(i);
+                    am.match(activity, activity, activity.getIntent());
+                }
+            }
+        }
+    }
+
+    /**
+     * Perform calling of an activity's {@link Activity#onStop}
+     * method.  The default implementation simply calls through to that method.
+     * 
+     * @param activity The activity being stopped.
+     */
+    public void callActivityOnStop(Activity activity) {
+        activity.onStop();
+    }
+
+    /**
+     * Perform calling of an activity's {@link Activity#onPause} method.  The
+     * default implementation simply calls through to that method.
+     * 
+     * @param activity The activity being saved.
+     * @param outState The bundle to pass to the call.
+     */
+    public void callActivityOnSaveInstanceState(Activity activity, Bundle outState) {
+        activity.performSaveInstanceState(outState);
+    }
+
+    /**
+     * Perform calling of an activity's {@link Activity#onPause} method.  The
+     * default implementation simply calls through to that method.
+     * 
+     * @param activity The activity being paused.
+     */
+    public void callActivityOnPause(Activity activity) {
+        activity.performPause();
+    }
+    
+    /**
+     * Perform calling of an activity's {@link Activity#onUserLeaveHint} method.
+     * The default implementation simply calls through to that method.
+     * 
+     * @param activity The activity being notified that the user has navigated away
+     */
+    public void callActivityOnUserLeaving(Activity activity) {
+        activity.performUserLeaving();
+    }
+    
+    /*
+     * Starts allocation counting. This triggers a gc and resets the counts.
+     */
+    public void startAllocCounting() {
+        // Before we start trigger a GC and reset the debug counts. Run the 
+        // finalizers and another GC before starting and stopping the alloc
+        // counts. This will free up any objects that were just sitting around 
+        // waiting for their finalizers to be run.
+        Runtime.getRuntime().gc();
+        Runtime.getRuntime().runFinalization();
+        Runtime.getRuntime().gc();
+
+        Debug.resetAllCounts();
+        
+        // start the counts
+        Debug.startAllocCounting();
+    }
+    
+    /*
+     * Stops allocation counting.
+     */
+    public void stopAllocCounting() {
+        Runtime.getRuntime().gc();
+        Runtime.getRuntime().runFinalization();
+        Runtime.getRuntime().gc();
+        Debug.stopAllocCounting();
+    }
+    
+    /**
+     * If Results already contains Key, it appends Value to the key's ArrayList
+     * associated with the key. If the key doesn't already exist in results, it
+     * adds the key/value pair to results.
+     */
+    private void addValue(String key, int value, Bundle results) {
+        if (results.containsKey(key)) {
+            List<Integer> list = results.getIntegerArrayList(key);
+            if (list != null) {
+                list.add(value);
+            }
+        } else {
+            ArrayList<Integer> list = new ArrayList<Integer>();
+            list.add(value);
+            results.putIntegerArrayList(key, list);
+        }
+    }
+
+    /**
+     * Returns a bundle with the current results from the allocation counting.
+     */
+    public Bundle getAllocCounts() {
+        Bundle results = new Bundle();
+        results.putLong("global_alloc_count", Debug.getGlobalAllocCount());
+        results.putLong("global_alloc_size", Debug.getGlobalAllocSize());
+        results.putLong("global_freed_count", Debug.getGlobalFreedCount());
+        results.putLong("global_freed_size", Debug.getGlobalFreedSize());
+        results.putLong("gc_invocation_count", Debug.getGlobalGcInvocationCount());    
+        return results;
+    }
+
+    /**
+     * Returns a bundle with the counts for various binder counts for this process. Currently the only two that are
+     * reported are the number of send and the number of received transactions.
+     */
+    public Bundle getBinderCounts() {
+        Bundle results = new Bundle();
+        results.putLong("sent_transactions", Debug.getBinderSentTransactions());
+        results.putLong("received_transactions", Debug.getBinderReceivedTransactions());
+        return results;
+    }
+    
+    /**
+     * Description of a Activity execution result to return to the original
+     * activity.
+     */
+    public static final class ActivityResult {
+        /**
+         * Create a new activity result.  See {@link Activity#setResult} for 
+         * more information. 
+         *  
+         * @param resultCode The result code to propagate back to the
+         * originating activity, often RESULT_CANCELED or RESULT_OK
+         * @param resultData The data to propagate back to the originating
+         * activity.
+         */
+        public ActivityResult(int resultCode, Intent resultData) {
+            mResultCode = resultCode;
+            mResultData = resultData;
+        }
+
+        /**
+         * Retrieve the result code contained in this result.
+         */
+        public int getResultCode() {
+            return mResultCode;
+        }
+
+        /**
+         * Retrieve the data contained in this result.
+         */
+        public Intent getResultData() {
+            return mResultData;
+        }
+
+        private final int mResultCode;
+        private final Intent mResultData;
+    }
+
+    /**
+     * Execute a startActivity call made by the application.  The default 
+     * implementation takes care of updating any active {@link ActivityMonitor}
+     * objects and dispatches this call to the system activity manager; you can
+     * override this to watch for the application to start an activity, and 
+     * modify what happens when it does. 
+     *  
+     * <p>This method returns an {@link ActivityResult} object, which you can 
+     * use when intercepting application calls to avoid performing the start 
+     * activity action but still return the result the application is 
+     * expecting.  To do this, override this method to catch the call to start 
+     * activity so that it returns a new ActivityResult containing the results 
+     * you would like the application to see, and don't call up to the super 
+     * class.  Note that an application is only expecting a result if 
+     * <var>requestCode</var> is &gt;= 0.
+     *  
+     * <p>This method throws {@link android.content.ActivityNotFoundException}
+     * if there was no Activity found to run the given Intent.
+     * 
+     * @param who The Context from which the activity is being started.
+     * @param contextThread The main thread of the Context from which the activity
+     *                      is being started.
+     * @param token Internal token identifying to the system who is starting 
+     *              the activity; may be null.
+     * @param target Which activity is perform the start (and thus receiving 
+     *               any result); may be null if this call is not being made
+     *               from an activity.
+     * @param intent The actual Intent to start.
+     * @param requestCode Identifier for this request's result; less than zero 
+     *                    if the caller is not expecting a result.
+     * 
+     * @return To force the return of a particular result, return an 
+     *         ActivityResult object containing the desired data; otherwise
+     *         return null.  The default implementation always returns null.
+     *  
+     * @throws android.content.ActivityNotFoundException
+     * 
+     * @see Activity#startActivity(Intent)
+     * @see Activity#startActivityForResult(Intent, int)
+     * @see Activity#startActivityFromChild
+     * 
+     * {@hide}
+     */
+    public ActivityResult execStartActivity(
+        Context who, IBinder contextThread, IBinder token, Activity target,
+        Intent intent, int requestCode) {
+        IApplicationThread whoThread = (IApplicationThread) contextThread;
+        if (mActivityMonitors != null) {
+            synchronized (mSync) {
+                final int N = mActivityMonitors.size();
+                for (int i=0; i<N; i++) {
+                    final ActivityMonitor am = mActivityMonitors.get(i);
+                    if (am.match(who, null, intent)) {
+                        am.mHits++;
+                        if (am.isBlocking()) {
+                            return requestCode >= 0 ? am.getResult() : null;
+                        }
+                        break;
+                    }
+                }
+            }
+        }
+        try {
+            int result = ActivityManagerNative.getDefault()
+                .startActivity(whoThread, intent,
+                        intent.resolveTypeIfNeeded(who.getContentResolver()),
+                        null, 0, token, target != null ? target.mEmbeddedID : null,
+                        requestCode, false, false);
+            checkStartActivityResult(result, intent);
+        } catch (RemoteException e) {
+        }
+        return null;
+    }
+
+    /*package*/ final void init(ActivityThread thread,
+            Context instrContext, Context appContext, ComponentName component, 
+            IInstrumentationWatcher watcher) {
+        mThread = thread;
+        mMessageQueue = mThread.getLooper().myQueue();
+        mInstrContext = instrContext;
+        mAppContext = appContext;
+        mComponent = component;
+        mWatcher = watcher;
+    }
+
+    /*package*/ static void checkStartActivityResult(int res, Intent intent) {
+        if (res >= IActivityManager.START_SUCCESS) {
+            return;
+        }
+        
+        switch (res) {
+            case IActivityManager.START_INTENT_NOT_RESOLVED:
+            case IActivityManager.START_CLASS_NOT_FOUND:
+                if (intent.getComponent() != null)
+                    throw new ActivityNotFoundException(
+                            "Unable to find explicit activity class "
+                            + intent.getComponent().toShortString()
+                            + "; have you declared this activity in your AndroidManifest.xml?");
+                throw new ActivityNotFoundException(
+                        "No Activity found to handle " + intent);
+            case IActivityManager.START_PERMISSION_DENIED:
+                throw new SecurityException("Not allowed to start activity "
+                        + intent);
+            case IActivityManager.START_FORWARD_AND_REQUEST_CONFLICT:
+                throw new AndroidRuntimeException(
+                        "FORWARD_RESULT_FLAG used while also requesting a result");
+            default:
+                throw new AndroidRuntimeException("Unknown error code "
+                        + res + " when starting " + intent);
+        }
+    }
+    
+    private final void validateNotAppThread() {
+        if (ActivityThread.currentActivityThread() != null) {
+            throw new RuntimeException(
+                "This method can not be called from the main application thread");
+        }
+    }
+
+    private final class InstrumentationThread extends Thread {
+        public InstrumentationThread(String name) {
+            super(name);
+        }
+        public void run() {
+            IActivityManager am = ActivityManagerNative.getDefault();
+            try {
+                Process.setThreadPriority(Process.THREAD_PRIORITY_URGENT_DISPLAY);
+            } catch (RuntimeException e) {
+                Log.w(TAG, "Exception setting priority of instrumentation thread "                                            
+                        + Process.myTid(), e);                                                                             
+            }
+            if (mAutomaticPerformanceSnapshots) {
+                startPerformanceSnapshot();
+            }
+            onStart();
+        }
+    }
+    
+    private static final class EmptyRunnable implements Runnable {
+        public void run() {
+        }
+    }
+
+    private static final class SyncRunnable implements Runnable {
+        private final Runnable mTarget;
+        private boolean mComplete;
+
+        public SyncRunnable(Runnable target) {
+            mTarget = target;
+        }
+
+        public void run() {
+            mTarget.run();
+            synchronized (this) {
+                mComplete = true;
+                notifyAll();
+            }
+        }
+
+        public void waitForComplete() {
+            synchronized (this) {
+                while (!mComplete) {
+                    try {
+                        wait();
+                    } catch (InterruptedException e) {
+                    }
+                }
+            }
+        }
+    }
+
+    private static final class ActivityWaiter {
+        public final Intent intent;
+        public Activity activity;
+
+        public ActivityWaiter(Intent _intent) {
+            intent = _intent;
+        }
+    }
+
+    private final class ActivityGoing implements MessageQueue.IdleHandler {
+        private final ActivityWaiter mWaiter;
+
+        public ActivityGoing(ActivityWaiter waiter) {
+            mWaiter = waiter;
+        }
+
+        public final boolean queueIdle() {
+            synchronized (mSync) {
+                mWaitingActivities.remove(mWaiter);
+                mSync.notifyAll();
+            }
+            return false;
+        }
+    }
+
+    private static final class Idler implements MessageQueue.IdleHandler {
+        private final Runnable mCallback;
+        private boolean mIdle;
+
+        public Idler(Runnable callback) {
+            mCallback = callback;
+            mIdle = false;
+        }
+
+        public final boolean queueIdle() {
+            if (mCallback != null) {
+                mCallback.run();
+            }
+            synchronized (this) {
+                mIdle = true;
+                notifyAll();
+            }
+            return false;
+        }
+
+        public void waitForIdle() {
+            synchronized (this) {
+                while (!mIdle) {
+                    try {
+                        wait();
+                    } catch (InterruptedException e) {
+                    }
+                }
+            }
+        }
+    }
+}
diff --git a/core/java/android/app/IntentService.java b/core/java/android/app/IntentService.java
new file mode 100644
index 0000000..2b12a2a
--- /dev/null
+++ b/core/java/android/app/IntentService.java
@@ -0,0 +1,74 @@
+package android.app;
+
+import android.content.Intent;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+
+/**
+ * An abstract {@link Service} that serializes the handling of the Intents passed upon service
+ * start and handles them on a handler thread.
+ *
+ * <p>To use this class extend it and implement {@link #onHandleIntent}. The {@link Service} will
+ * automatically be stopped when the last enqueued {@link Intent} is handled.
+ */
+public abstract class IntentService extends Service {
+    private volatile Looper mServiceLooper;
+    private volatile ServiceHandler mServiceHandler;
+    private String mName;
+
+    private final class ServiceHandler extends Handler {
+        public ServiceHandler(Looper looper) {
+            super(looper);
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            onHandleIntent((Intent)msg.obj);
+            stopSelf(msg.arg1);
+        }
+    }
+
+    public IntentService(String name) {
+        super();
+        mName = name;
+    }
+
+    @Override
+    public void onCreate() {
+        super.onCreate();
+        HandlerThread thread = new HandlerThread("IntentService[" + mName + "]");
+        thread.start();
+
+        mServiceLooper = thread.getLooper();
+        mServiceHandler = new ServiceHandler(mServiceLooper);
+    }
+
+    @Override
+    public void onStart(Intent intent, int startId) {
+        super.onStart(intent, startId);
+        Message msg = mServiceHandler.obtainMessage();
+        msg.arg1 = startId;
+        msg.obj = intent;
+        mServiceHandler.sendMessage(msg);
+    }
+
+    @Override
+    public void onDestroy() {
+        mServiceLooper.quit();
+    }
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        return null;
+    }
+
+    /**
+     * Invoked on the Handler thread with the {@link Intent} that is passed to {@link #onStart}.
+     * Note that this will be invoked from a different thread than the one that handles the
+     * {@link #onStart} call.
+     */
+    protected abstract void onHandleIntent(Intent intent);
+}
diff --git a/core/java/android/app/KeyguardManager.java b/core/java/android/app/KeyguardManager.java
new file mode 100644
index 0000000..0c07553
--- /dev/null
+++ b/core/java/android/app/KeyguardManager.java
@@ -0,0 +1,155 @@
+/*
+ * Copyright (C) 2007 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 android.app;
+
+import android.content.Context;
+import android.os.Binder;
+import android.os.RemoteException;
+import android.os.IBinder;
+import android.os.ServiceManager;
+import android.view.IWindowManager;
+import android.view.IOnKeyguardExitResult;
+
+/**
+ * Class that can be used to lock and unlock the keyboard. Get an instance of this 
+ * class by calling {@link android.content.Context#getSystemService(java.lang.String)}
+ * with argument {@link android.content.Context#KEYGUARD_SERVICE}. The
+ * Actual class to control the keyboard locking is
+ * {@link android.app.KeyguardManager.KeyguardLock}.
+ */
+public class KeyguardManager {
+    private IWindowManager mWM;
+
+    /**
+     * Handle returned by {@link KeyguardManager#newKeyguardLock} that allows
+     * you to disable / reenable the keyguard.
+     */
+    public class KeyguardLock {
+        private IBinder mToken = new Binder();
+        private String mTag;
+
+        KeyguardLock(String tag) {
+            mTag = tag;
+        }
+
+        /**
+         * Disable the keyguard from showing.  If the keyguard is currently
+         * showing, hide it.  The keyguard will be prevented from showing again
+         * until {@link #reenableKeyguard()} is called.
+         *
+         * A good place to call this is from {@link android.app.Activity#onResume()}
+         *
+         * @see #reenableKeyguard()
+         */
+        public void disableKeyguard() {
+            try {
+                mWM.disableKeyguard(mToken, mTag);
+            } catch (RemoteException ex) {
+            }
+        }
+
+        /**
+         * Reenable the keyguard.  The keyguard will reappear if the previous
+         * call to {@link #disableKeyguard()} caused it it to be hidden.
+         *
+         * A good place to call this is from {@link android.app.Activity#onPause()} 
+         *
+         * @see #disableKeyguard()
+         */
+        public void reenableKeyguard() {
+            try {
+                mWM.reenableKeyguard(mToken);
+            } catch (RemoteException ex) {
+            }
+        }
+    }
+
+    /**
+     * Callback passed to {@link KeyguardManager#exitKeyguardSecurely} to notify
+     * caller of result.
+     */
+    public interface OnKeyguardExitResult {
+
+        /**
+         * @param success True if the user was able to authenticate, false if
+         *   not.
+         */
+        void onKeyguardExitResult(boolean success);
+    }
+
+
+    KeyguardManager() {
+        mWM = IWindowManager.Stub.asInterface(ServiceManager.getService(Context.WINDOW_SERVICE));
+    }
+
+    /**
+     * Enables you to lock or unlock the keyboard. Get an instance of this class by
+     * calling {@link android.content.Context#getSystemService(java.lang.String) Context.getSystemService()}. 
+     * This class is wrapped by {@link android.app.KeyguardManager KeyguardManager}.
+     * @param tag A tag that informally identifies who you are (for debugging who
+     *   is disabling he keyguard).
+     *
+     * @return A {@link KeyguardLock} handle to use to disable and reenable the
+     *   keyguard.
+     */
+    public KeyguardLock newKeyguardLock(String tag) {
+        return new KeyguardLock(tag);
+    }
+
+    /**
+     * If keyguard screen is showing or in restricted key input mode (i.e. in
+     * keyguard password emergency screen). When in such mode, certain keys,
+     * such as the Home key and the right soft keys, don't work.
+     *
+     * @return true if in keyguard restricted input mode.
+     *
+     * @see android.view.WindowManagerPolicy#inKeyguardRestrictedKeyInputMode
+     */
+    public boolean inKeyguardRestrictedInputMode() {
+        try {
+            return mWM.inKeyguardRestrictedInputMode();
+        } catch (RemoteException ex) {
+            return false;
+        }
+    }
+
+    /**
+     * Exit the keyguard securely.  The use case for this api is that, after
+     * disabling the keyguard, your app, which was granted permission to
+     * disable the keyguard and show a limited amount of information deemed
+     * safe without the user getting past the keyguard, needs to navigate to
+     * something that is not safe to view without getting past the keyguard.
+     *
+     * This will, if the keyguard is secure, bring up the unlock screen of
+     * the keyguard.
+     *
+     * @param callback Let's you know whether the operation was succesful and
+     *   it is safe to launch anything that would normally be considered safe
+     *   once the user has gotten past the keyguard.
+     */
+    public void exitKeyguardSecurely(final OnKeyguardExitResult callback) {
+        try {
+            mWM.exitKeyguardSecurely(new IOnKeyguardExitResult.Stub() {
+                public void onKeyguardExitResult(boolean success) throws RemoteException {
+                    callback.onKeyguardExitResult(success);
+                }
+            });
+        } catch (RemoteException e) {
+
+        }
+    }
+}
diff --git a/core/java/android/app/LauncherActivity.java b/core/java/android/app/LauncherActivity.java
new file mode 100644
index 0000000..d6fcbb1
--- /dev/null
+++ b/core/java/android/app/LauncherActivity.java
@@ -0,0 +1,380 @@
+/*
+ * Copyright (C) 2007 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 android.app;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.PaintFlagsDrawFilter;
+import android.graphics.PixelFormat;
+import android.graphics.Rect;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.PaintDrawable;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.Window;
+import android.widget.BaseAdapter;
+import android.widget.Filter;
+import android.widget.Filterable;
+import android.widget.ListView;
+import android.widget.TextView;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+
+/**
+ * Displays a list of all activities which can be performed
+ * for a given intent. Launches when clicked.
+ *
+ */
+public abstract class LauncherActivity extends ListActivity {
+
+    Intent mIntent;
+    PackageManager mPackageManager;
+    
+    /**
+     * An item in the list
+     */
+    public static class ListItem {
+        public CharSequence label;
+        //public CharSequence description;
+        public Drawable icon;
+        public String packageName;
+        public String className;
+        
+        ListItem(PackageManager pm, ResolveInfo resolveInfo, IconResizer resizer) {
+            label = resolveInfo.loadLabel(pm);
+            if (label == null && resolveInfo.activityInfo != null) {
+                label = resolveInfo.activityInfo.name;
+            }
+            
+            /*
+            if (resolveInfo.activityInfo != null &&
+                    resolveInfo.activityInfo.applicationInfo != null) {
+                description = resolveInfo.activityInfo.applicationInfo.loadDescription(pm);
+            }
+            */
+            
+            icon = resizer.createIconThumbnail(resolveInfo.loadIcon(pm));
+            packageName = resolveInfo.activityInfo.applicationInfo.packageName;
+            className = resolveInfo.activityInfo.name;
+        }
+
+        public ListItem() {
+        }
+    }
+
+    /**
+     * Adapter which shows the set of activities that can be performed for a given intent.
+     */
+    private class ActivityAdapter extends BaseAdapter implements Filterable {
+        private final Object lock = new Object();
+        private ArrayList<ListItem> mOriginalValues;
+
+        protected final LayoutInflater mInflater;
+
+        protected List<ListItem> mActivitiesList;
+
+        private Filter mFilter;
+        
+        public ActivityAdapter() {
+            mInflater = (LayoutInflater) LauncherActivity.this.getSystemService(
+                    Context.LAYOUT_INFLATER_SERVICE);
+            mActivitiesList = makeListItems();
+        }
+
+        public Intent intentForPosition(int position) {
+            if (mActivitiesList == null) {
+                return null;
+            }
+
+            Intent intent = new Intent(mIntent);
+            ListItem item = mActivitiesList.get(position);
+            intent.setClassName(item.packageName, item.className);
+            return intent;
+        }
+
+        public int getCount() {
+            return mActivitiesList != null ? mActivitiesList.size() : 0;
+        }
+
+        public Object getItem(int position) {
+            return position;
+        }
+
+        public long getItemId(int position) {
+            return position;
+        }
+
+        public View getView(int position, View convertView, ViewGroup parent) {
+            View view;
+            if (convertView == null) {
+                view = mInflater.inflate(
+                        com.android.internal.R.layout.activity_list_item_2, parent, false);
+            } else {
+                view = convertView;
+            }
+            bindView(view, mActivitiesList.get(position));
+            return view;
+        }
+
+        private void bindView(View view, ListItem item) {
+            TextView text = (TextView) view;
+            text.setText(item.label);
+            text.setCompoundDrawablesWithIntrinsicBounds(item.icon, null, null, null);
+        }
+        
+        public Filter getFilter() {
+            if (mFilter == null) {
+                mFilter = new ArrayFilter();
+            }
+            return mFilter;
+        }
+        
+        /**
+         * An array filters constrains the content of the array adapter with a prefix. Each
+         * item that does not start with the supplied prefix is removed from the list.
+         */
+        private class ArrayFilter extends Filter {
+            @Override
+            protected FilterResults performFiltering(CharSequence prefix) {
+                FilterResults results = new FilterResults();
+
+                if (mOriginalValues == null) {
+                    synchronized (lock) {
+                        mOriginalValues = new ArrayList<ListItem>(mActivitiesList);
+                    }
+                }
+
+                if (prefix == null || prefix.length() == 0) {
+                    synchronized (lock) {
+                        ArrayList<ListItem> list = new ArrayList<ListItem>(mOriginalValues);
+                        results.values = list;
+                        results.count = list.size();
+                    }
+                } else {
+                    final String prefixString = prefix.toString().toLowerCase();
+
+                    ArrayList<ListItem> values = mOriginalValues;
+                    int count = values.size();
+
+                    ArrayList<ListItem> newValues = new ArrayList<ListItem>(count);
+
+                    for (int i = 0; i < count; i++) {
+                        ListItem item = values.get(i);
+
+                        String[] words = item.label.toString().toLowerCase().split(" ");
+                        int wordCount = words.length;
+
+                        for (int k = 0; k < wordCount; k++) {
+                            final String word = words[k];
+
+                            if (word.startsWith(prefixString)) {
+                                newValues.add(item);
+                                break;
+                            }
+                        }
+                    }
+
+                    results.values = newValues;
+                    results.count = newValues.size();
+                }
+
+                return results;
+            }
+
+            @Override
+            protected void publishResults(CharSequence constraint, FilterResults results) {
+                //noinspection unchecked
+                mActivitiesList = (List<ListItem>) results.values;
+                if (results.count > 0) {
+                    notifyDataSetChanged();
+                } else {
+                    notifyDataSetInvalidated();
+                }
+            }
+        }
+    }
+        
+    /**
+     * Utility class to resize icons to match default icon size.  
+     */
+    public class IconResizer {
+        // Code is borrowed from com.android.launcher.Utilities. 
+        private int mIconWidth = -1;
+        private int mIconHeight = -1;
+
+        private final Rect mOldBounds = new Rect();
+        private Canvas mCanvas = new Canvas();
+        
+        public IconResizer() {
+            mCanvas.setDrawFilter(new PaintFlagsDrawFilter(Paint.DITHER_FLAG,
+                    Paint.FILTER_BITMAP_FLAG));
+            
+            final Resources resources = LauncherActivity.this.getResources();
+            mIconWidth = mIconHeight = (int) resources.getDimension(
+                    android.R.dimen.app_icon_size);
+        }
+
+        /**
+         * Returns a Drawable representing the thumbnail of the specified Drawable.
+         * The size of the thumbnail is defined by the dimension
+         * android.R.dimen.launcher_application_icon_size.
+         *
+         * This method is not thread-safe and should be invoked on the UI thread only.
+         *
+         * @param icon The icon to get a thumbnail of.
+         *
+         * @return A thumbnail for the specified icon or the icon itself if the
+         *         thumbnail could not be created. 
+         */
+        public Drawable createIconThumbnail(Drawable icon) {
+            int width = mIconWidth;
+            int height = mIconHeight;
+
+            final int iconWidth = icon.getIntrinsicWidth();
+            final int iconHeight = icon.getIntrinsicHeight();
+
+            if (icon instanceof PaintDrawable) {
+                PaintDrawable painter = (PaintDrawable) icon;
+                painter.setIntrinsicWidth(width);
+                painter.setIntrinsicHeight(height);
+            }
+
+            if (width > 0 && height > 0) {
+                if (width < iconWidth || height < iconHeight) {
+                    final float ratio = (float) iconWidth / iconHeight;
+
+                    if (iconWidth > iconHeight) {
+                        height = (int) (width / ratio);
+                    } else if (iconHeight > iconWidth) {
+                        width = (int) (height * ratio);
+                    }
+
+                    final Bitmap.Config c = icon.getOpacity() != PixelFormat.OPAQUE ?
+                                Bitmap.Config.ARGB_8888 : Bitmap.Config.RGB_565;
+                    final Bitmap thumb = Bitmap.createBitmap(mIconWidth, mIconHeight, c);
+                    final Canvas canvas = mCanvas;
+                    canvas.setBitmap(thumb);
+                    // Copy the old bounds to restore them later
+                    // If we were to do oldBounds = icon.getBounds(),
+                    // the call to setBounds() that follows would
+                    // change the same instance and we would lose the
+                    // old bounds
+                    mOldBounds.set(icon.getBounds());
+                    final int x = (mIconWidth - width) / 2;
+                    final int y = (mIconHeight - height) / 2;
+                    icon.setBounds(x, y, x + width, y + height);
+                    icon.draw(canvas);
+                    icon.setBounds(mOldBounds);
+                    icon = new BitmapDrawable(thumb);
+                } else if (iconWidth < width && iconHeight < height) {
+                    final Bitmap.Config c = Bitmap.Config.ARGB_8888;
+                    final Bitmap thumb = Bitmap.createBitmap(mIconWidth, mIconHeight, c);
+                    final Canvas canvas = mCanvas;
+                    canvas.setBitmap(thumb);
+                    mOldBounds.set(icon.getBounds());
+                    final int x = (width - iconWidth) / 2;
+                    final int y = (height - iconHeight) / 2;
+                    icon.setBounds(x, y, x + iconWidth, y + iconHeight);
+                    icon.draw(canvas);
+                    icon.setBounds(mOldBounds);
+                    icon = new BitmapDrawable(thumb);
+                }
+            }
+
+            return icon;
+        }
+    }
+
+    @Override
+    protected void onCreate(Bundle icicle) {
+        super.onCreate(icicle);
+        
+        mPackageManager = getPackageManager();
+    
+        requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
+        setProgressBarIndeterminateVisibility(true);
+        setContentView(com.android.internal.R.layout.activity_list);
+        
+            
+        mIntent = new Intent(getTargetIntent());
+        mIntent.setComponent(null);
+        mAdapter = new ActivityAdapter();
+        
+        setListAdapter(mAdapter);
+        getListView().setTextFilterEnabled(true);
+        
+        setProgressBarIndeterminateVisibility(false);
+    }
+
+    @Override
+    protected void onListItemClick(ListView l, View v, int position, long id) {
+        Intent intent = ((ActivityAdapter)mAdapter).intentForPosition(position);
+
+        startActivity(intent);
+    }
+    
+    /**
+     * Return the actual Intent for a specific position in our
+     * {@link android.widget.ListView}.
+     * @param position The item whose Intent to return
+     */
+    protected Intent intentForPosition(int position) {
+        ActivityAdapter adapter = (ActivityAdapter) mAdapter;
+        return adapter.intentForPosition(position);
+    }
+    
+    /**
+     * Get the base intent to use when running
+     * {@link PackageManager#queryIntentActivities(Intent, int)}.
+     */
+    protected Intent getTargetIntent() {
+        return new Intent();
+    }
+
+    /**
+     * Perform the query to determine which results to show and return a list of them.
+     */
+    public List<ListItem> makeListItems() {
+        // Load all matching activities and sort correctly
+        List<ResolveInfo> list = mPackageManager.queryIntentActivities(mIntent,
+                /* no flags */ 0);
+        Collections.sort(list, new ResolveInfo.DisplayNameComparator(mPackageManager));
+        
+        IconResizer resizer = new IconResizer();
+        
+        ArrayList<ListItem> result = new ArrayList<ListItem>(list.size());
+        int listSize = list.size();
+        for (int i = 0; i < listSize; i++) {
+            ResolveInfo resolveInfo = list.get(i);
+            result.add(new ListItem(mPackageManager, resolveInfo, resizer));
+        }
+
+        return result;
+    }
+}
diff --git a/core/java/android/app/ListActivity.java b/core/java/android/app/ListActivity.java
new file mode 100644
index 0000000..5523c18
--- /dev/null
+++ b/core/java/android/app/ListActivity.java
@@ -0,0 +1,316 @@
+/*
+ * 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 android.app;
+
+import android.os.Bundle;
+import android.os.Handler;
+import android.view.KeyEvent;
+import android.view.View;
+import android.widget.Adapter;
+import android.widget.AdapterView;
+import android.widget.ListAdapter;
+import android.widget.ListView;
+
+/**
+ * An activity that displays a list of items by binding to a data source such as
+ * an array or Cursor, and exposes event handlers when the user selects an item.
+ * <p>
+ * ListActivity hosts a {@link android.widget.ListView ListView} object that can
+ * be bound to different data sources, typically either an array or a Cursor
+ * holding query results. Binding, screen layout, and row layout are discussed
+ * in the following sections.
+ * <p>
+ * <strong>Screen Layout</strong>
+ * </p>
+ * <p>
+ * ListActivity has a default layout that consists of a single, full-screen list
+ * in the center of the screen. However, if you desire, you can customize the
+ * screen layout by setting your own view layout with setContentView() in
+ * onCreate(). To do this, your own view MUST contain a ListView object with the
+ * id "@android:id/list" (or {@link android.R.id#list} if it's in code)
+ * <p>
+ * Optionally, your custom view can contain another view object of any type to
+ * display when the list view is empty. This "empty list" notifier must have an
+ * id "android:empty". Note that when an empty view is present, the list view
+ * will be hidden when there is no data to display.
+ * <p>
+ * The following code demonstrates an (ugly) custom screen layout. It has a list
+ * with a green background, and an alternate red "no data" message.
+ * </p>
+ * 
+ * <pre>
+ * &lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&gt;
+ * &lt;LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ *         android:orientation=&quot;vertical&quot;
+ *         android:layout_width=&quot;fill_parent&quot; 
+ *         android:layout_height=&quot;fill_parent&quot;
+ *         android:paddingLeft=&quot;8dp&quot;
+ *         android:paddingRight=&quot;8dp&quot;&gt;
+ * 
+ *     &lt;ListView android:id=&quot;@id/android:list&quot;
+ *               android:layout_width=&quot;fill_parent&quot; 
+ *               android:layout_height=&quot;fill_parent&quot;
+ *               android:background=&quot;#00FF00&quot;
+ *               android:layout_weight=&quot;1&quot;
+ *               android:drawSelectorOnTop=&quot;false&quot;/&gt;
+ * 
+ *     &lt;TextView id=&quot;@id/android:empty&quot;
+ *               android:layout_width=&quot;fill_parent&quot; 
+ *               android:layout_height=&quot;fill_parent&quot;
+ *               android:background=&quot;#FF0000&quot;
+ *               android:text=&quot;No data&quot;/&gt;
+ * &lt;/LinearLayout&gt;
+ * </pre>
+ * 
+ * <p>
+ * <strong>Row Layout</strong>
+ * </p>
+ * <p>
+ * You can specify the layout of individual rows in the list. You do this by
+ * specifying a layout resource in the ListAdapter object hosted by the activity
+ * (the ListAdapter binds the ListView to the data; more on this later).
+ * <p>
+ * A ListAdapter constructor takes a parameter that specifies a layout resource
+ * for each row. It also has two additional parameters that let you specify
+ * which data field to associate with which object in the row layout resource.
+ * These two parameters are typically parallel arrays.
+ * </p>
+ * <p>
+ * Android provides some standard row layout resources. These are in the
+ * {@link android.R.layout} class, and have names such as simple_list_item_1,
+ * simple_list_item_2, and two_line_list_item. The following layout XML is the
+ * source for the resource two_line_list_item, which displays two data
+ * fields,one above the other, for each list row.
+ * </p>
+ * 
+ * <pre>
+ * &lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&gt;
+ * &lt;LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ *     android:layout_width=&quot;fill_parent&quot;
+ *     android:layout_height=&quot;wrap_content&quot;
+ *     android:orientation=&quot;vertical&quot;&gt;
+ * 
+ *     &lt;TextView android:id=&quot;@+id/text1&quot;
+ *         android:textSize=&quot;16sp&quot;
+ *         android:textStyle=&quot;bold&quot;
+ *         android:layout_width=&quot;fill_parent&quot;
+ *         android:layout_height=&quot;wrap_content&quot;/&gt;
+ * 
+ *     &lt;TextView android:id=&quot;@+id/text2&quot;
+ *         android:textSize=&quot;16sp&quot;
+ *         android:layout_width=&quot;fill_parent&quot;
+ *         android:layout_height=&quot;wrap_content&quot;/&gt;
+ * &lt;/LinearLayout&gt;
+ * </pre>
+ * 
+ * <p>
+ * You must identify the data bound to each TextView object in this layout. The
+ * syntax for this is discussed in the next section.
+ * </p>
+ * <p>
+ * <strong>Binding to Data</strong>
+ * </p>
+ * <p>
+ * You bind the ListActivity's ListView object to data using a class that
+ * implements the {@link android.widget.ListAdapter ListAdapter} interface.
+ * Android provides two standard list adapters:
+ * {@link android.widget.SimpleAdapter SimpleAdapter} for static data (Maps),
+ * and {@link android.widget.SimpleCursorAdapter SimpleCursorAdapter} for Cursor
+ * query results.
+ * </p>
+ * <p>
+ * The following code from a custom ListActivity demonstrates querying the
+ * Contacts provider for all contacts, then binding the Name and Company fields
+ * to a two line row layout in the activity's ListView.
+ * </p>
+ * 
+ * <pre>
+ * public class MyListAdapter extends ListActivity {
+ * 
+ *     &#064;Override
+ *     protected void onCreate(Bundle savedInstanceState){
+ *         super.onCreate(savedInstanceState);
+ * 
+ *         // We'll define a custom screen layout here (the one shown above), but
+ *         // typically, you could just use the standard ListActivity layout.
+ *         setContentView(R.layout.custom_list_activity_view);
+ * 
+ *         // Query for all people contacts using the {@link android.provider.Contacts.People} convenience class.
+ *         // Put a managed wrapper around the retrieved cursor so we don't have to worry about
+ *         // requerying or closing it as the activity changes state.
+ *         mCursor = People.query(this.getContentResolver(), null);
+ *         startManagingCursor(mCursor);
+ * 
+ *         // Now create a new list adapter bound to the cursor. 
+ *         // SimpleListAdapter is designed for binding to a Cursor.
+ *         ListAdapter adapter = new SimpleCursorAdapter(
+ *                 this, // Context.
+ *                 android.R.layout.two_line_list_item,  // Specify the row template to use (here, two columns bound to the two retrieved cursor 
+ * rows).
+ *                 mCursor,                                    // Pass in the cursor to bind to.
+ *                 new String[] {People.NAME, People.COMPANY}, // Array of cursor columns to bind to.
+ *                 new int[]);                                 // Parallel array of which template objects to bind to those columns.
+ * 
+ *         // Bind to our new adapter.
+ *         setListAdapter(adapter);
+ *     }
+ * }
+ * </pre>
+ * 
+ * @see #setListAdapter
+ * @see android.widget.ListView
+ */
+public class ListActivity extends Activity {
+    /**
+     * This field should be made private, so it is hidden from the SDK.
+     * {@hide}
+     */
+    protected ListAdapter mAdapter;
+    /**
+     * This field should be made private, so it is hidden from the SDK.
+     * {@hide}
+     */
+    protected ListView mList;
+
+    private Handler mHandler = new Handler();
+    private boolean mFinishedStart = false;
+
+    private Runnable mRequestFocus = new Runnable() {
+        public void run() {
+            mList.focusableViewAvailable(mList);
+        }
+    };
+    
+    /**
+     * This method will be called when an item in the list is selected.
+     * Subclasses should override. Subclasses can call
+     * getListView().getItemAtPosition(position) if they need to access the
+     * data associated with the selected item.
+     * 
+     * @param l The ListView where the click happened
+     * @param v The view that was clicked within the ListView
+     * @param position The position of the view in the list
+     * @param id The row id of the item that was clicked
+     */
+    protected void onListItemClick(ListView l, View v, int position, long id) {
+    }
+    
+    /**
+     * Ensures the list view has been created before Activity restores all
+     * of the view states.
+     * 
+     *@see Activity#onRestoreInstanceState(Bundle)
+     */
+    @Override
+    protected void onRestoreInstanceState(Bundle state) {
+        ensureList();
+        super.onRestoreInstanceState(state);
+    }
+
+    /**
+     * Updates the screen state (current list and other views) when the
+     * content changes.
+     * 
+     * @see Activity#onContentChanged()
+     */
+    @Override
+    public void onContentChanged() {
+        super.onContentChanged();
+        View emptyView = findViewById(com.android.internal.R.id.empty);
+        mList = (ListView)findViewById(com.android.internal.R.id.list);
+        if (mList == null) {
+            throw new RuntimeException(
+                    "Your content must have a ListView whose id attribute is " +
+                    "'android.R.id.list'");
+        }
+        if (emptyView != null) {
+            mList.setEmptyView(emptyView);
+        }
+        mList.setOnItemClickListener(mOnClickListener);
+        if (mFinishedStart) {
+            setListAdapter(mAdapter);
+        }
+        mHandler.post(mRequestFocus);
+        mFinishedStart = true;
+    }
+
+    /**
+     * Provide the cursor for the list view.
+     */
+    public void setListAdapter(ListAdapter adapter) {
+        synchronized (this) {
+            ensureList();
+            mAdapter = adapter;
+            mList.setAdapter(adapter);
+        }
+    }
+
+    /**
+     * Set the currently selected list item to the specified
+     * position with the adapter's data
+     * 
+     * @param position
+     */
+    public void setSelection(int position) {
+        mList.setSelection(position);
+    }
+
+    /**
+     * Get the position of the currently selected list item.
+     */
+    public int getSelectedItemPosition() {
+        return mList.getSelectedItemPosition();
+    }
+
+    /**
+     * Get the cursor row ID of the currently selected list item.
+     */
+    public long getSelectedItemId() {
+        return mList.getSelectedItemId();
+    }
+
+    /**
+     * Get the activity's list view widget.
+     */
+    public ListView getListView() {
+        ensureList();
+        return mList;
+    }
+    
+    /**
+     * Get the ListAdapter associated with this activity's ListView.
+     */
+    public ListAdapter getListAdapter() {
+        return mAdapter;
+    }
+
+    private void ensureList() {
+        if (mList != null) {
+            return;
+        }
+        setContentView(com.android.internal.R.layout.list_content);
+        
+    }
+
+    private AdapterView.OnItemClickListener mOnClickListener = new AdapterView.OnItemClickListener() {
+        public void onItemClick(AdapterView parent, View v, int position, long id)
+        {
+            onListItemClick((ListView)parent, v, position, id);
+        }
+    };
+}
+
diff --git a/core/java/android/app/LocalActivityManager.java b/core/java/android/app/LocalActivityManager.java
new file mode 100644
index 0000000..a24fcae
--- /dev/null
+++ b/core/java/android/app/LocalActivityManager.java
@@ -0,0 +1,627 @@
+/*
+ * 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 android.app;
+
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.os.Binder;
+import android.os.Bundle;
+import android.util.Config;
+import android.util.Log;
+import android.view.Window;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+/**
+ * Helper class for managing multiple running embedded activities in the same
+ * process. This class is not normally used directly, but rather created for
+ * you as part of the {@link android.app.ActivityGroup} implementation.
+ *
+ * @see ActivityGroup
+ */
+public class LocalActivityManager {
+    private static final String TAG = "LocalActivityManager";
+    private static final boolean localLOGV = false || Config.LOGV;
+
+    // Internal token for an Activity being managed by LocalActivityManager.
+    private static class LocalActivityRecord extends Binder {
+        LocalActivityRecord(String _id, Intent _intent) {
+            id = _id;
+            intent = _intent;
+        }
+
+        final String id;                // Unique name of this record.
+        Intent intent;                  // Which activity to run here.
+        ActivityInfo activityInfo;      // Package manager info about activity.
+        Activity activity;              // Currently instantiated activity.
+        Window window;                  // Activity's top-level window.
+        Bundle instanceState;           // Last retrieved freeze state.
+        int curState = RESTORED;        // Current state the activity is in.
+    }
+
+    static final int RESTORED = 0;      // State restored, but no startActivity().
+    static final int INITIALIZING = 1;  // Ready to launch (after startActivity()).
+    static final int CREATED = 2;       // Created, not started or resumed.
+    static final int STARTED = 3;       // Created and started, not resumed.
+    static final int RESUMED = 4;       // Created started and resumed.
+    static final int DESTROYED = 5;     // No longer with us.
+    
+    /** Thread our activities are running in. */
+    private final ActivityThread mActivityThread;
+    /** The containing activity that owns the activities we create. */
+    private final Activity mParent;
+
+    /** The activity that is currently resumed. */
+    private LocalActivityRecord mResumed;
+    /** id -> record of all known activities. */
+    private final Map<String, LocalActivityRecord> mActivities
+            = new HashMap<String, LocalActivityRecord>();
+    /** array of all known activities for easy iterating. */
+    private final ArrayList<LocalActivityRecord> mActivityArray
+            = new ArrayList<LocalActivityRecord>();
+
+    /** True if only one activity can be resumed at a time */
+    private boolean mSingleMode;
+    
+    /** Set to true once we find out the container is finishing. */
+    private boolean mFinishing;
+    
+    /** Current state the owner (ActivityGroup) is in */
+    private int mCurState = INITIALIZING;
+    
+    /** String ids of running activities starting with least recently used. */
+    // TODO: put back in stopping of activities.
+    //private List<LocalActivityRecord>  mLRU = new ArrayList();
+
+    /**
+     * Create a new LocalActivityManager for holding activities running within
+     * the given <var>parent</var>.
+     * 
+     * @param parent the host of the embedded activities
+     * @param singleMode True if the LocalActivityManger should keep a maximum
+     * of one activity resumed
+     */
+    public LocalActivityManager(Activity parent, boolean singleMode) {
+        mActivityThread = ActivityThread.currentActivityThread();
+        mParent = parent;
+        mSingleMode = singleMode;
+    }
+
+    private void moveToState(LocalActivityRecord r, int desiredState) {
+        if (r.curState == RESTORED || r.curState == DESTROYED) {
+            // startActivity() has not yet been called, so nothing to do.
+            return;
+        }
+        
+        if (r.curState == INITIALIZING) {
+            // Get the lastNonConfigurationInstance for the activity
+            HashMap<String,Object> lastNonConfigurationInstances =
+                mParent.getLastNonConfigurationChildInstances();
+            Object instance = null;
+            if (lastNonConfigurationInstances != null) {
+                instance = lastNonConfigurationInstances.get(r.id);
+            }
+            
+            // We need to have always created the activity.
+            if (localLOGV) Log.v(TAG, r.id + ": starting " + r.intent);
+            if (r.activityInfo == null) {
+                r.activityInfo = mActivityThread.resolveActivityInfo(r.intent);
+            }
+            r.activity = mActivityThread.startActivityNow(
+                    mParent, r.id, r.intent, r.activityInfo, r, r.instanceState, instance);
+            if (r.activity == null) {
+                return;
+            }
+            r.window = r.activity.getWindow();
+            r.instanceState = null;
+            r.curState = STARTED;
+            
+            if (desiredState == RESUMED) {
+                if (localLOGV) Log.v(TAG, r.id + ": resuming");
+                mActivityThread.performResumeActivity(r, true);
+                r.curState = RESUMED;
+            }
+            
+            // Don't do anything more here.  There is an important case:
+            // if this is being done as part of onCreate() of the group, then
+            // the launching of the activity gets its state a little ahead
+            // of our own (it is now STARTED, while we are only CREATED).
+            // If we just leave things as-is, we'll deal with it as the
+            // group's state catches up.
+            return;
+        }
+        
+        switch (r.curState) {
+            case CREATED:
+                if (desiredState == STARTED) {
+                    if (localLOGV) Log.v(TAG, r.id + ": restarting");
+                    mActivityThread.performRestartActivity(r);
+                    r.curState = STARTED;
+                }
+                if (desiredState == RESUMED) {
+                    if (localLOGV) Log.v(TAG, r.id + ": restarting and resuming");
+                    mActivityThread.performRestartActivity(r);
+                    mActivityThread.performResumeActivity(r, true);
+                    r.curState = RESUMED;
+                }
+                return;
+                
+            case STARTED:
+                if (desiredState == RESUMED) {
+                    // Need to resume it...
+                    if (localLOGV) Log.v(TAG, r.id + ": resuming");
+                    mActivityThread.performResumeActivity(r, true);
+                    r.instanceState = null;
+                    r.curState = RESUMED;
+                }
+                if (desiredState == CREATED) {
+                    if (localLOGV) Log.v(TAG, r.id + ": stopping");
+                    mActivityThread.performStopActivity(r);
+                    r.curState = CREATED;
+                }
+                return;
+                
+            case RESUMED:
+                if (desiredState == STARTED) {
+                    if (localLOGV) Log.v(TAG, r.id + ": pausing");
+                    performPause(r, mFinishing);
+                    r.curState = STARTED;
+                }
+                if (desiredState == CREATED) {
+                    if (localLOGV) Log.v(TAG, r.id + ": pausing");
+                    performPause(r, mFinishing);
+                    if (localLOGV) Log.v(TAG, r.id + ": stopping");
+                    mActivityThread.performStopActivity(r);
+                    r.curState = CREATED;
+                }
+                return;
+        }
+    }
+    
+    private void performPause(LocalActivityRecord r, boolean finishing) {
+        boolean needState = r.instanceState == null;
+        Bundle instanceState = mActivityThread.performPauseActivity(r,
+                finishing, needState);
+        if (needState) {
+            r.instanceState = instanceState;
+        }
+    }
+    
+    /**
+     * Start a new activity running in the group.  Every activity you start
+     * must have a unique string ID associated with it -- this is used to keep
+     * track of the activity, so that if you later call startActivity() again
+     * on it the same activity object will be retained.
+     * 
+     * <p>When there had previously been an activity started under this id,
+     * it may either be destroyed and a new one started, or the current
+     * one re-used, based on these conditions, in order:</p>
+     * 
+     * <ul>
+     * <li> If the Intent maps to a different activity component than is
+     * currently running, the current activity is finished and a new one
+     * started.
+     * <li> If the current activity uses a non-multiple launch mode (such
+     * as singleTop), or the Intent has the
+     * {@link Intent#FLAG_ACTIVITY_SINGLE_TOP} flag set, then the current
+     * activity will remain running and its
+     * {@link Activity#onNewIntent(Intent) Activity.onNewIntent()} method
+     * called.
+     * <li> If the new Intent is the same (excluding extras) as the previous
+     * one, and the new Intent does not have the
+     * {@link Intent#FLAG_ACTIVITY_CLEAR_TOP} set, then the current activity
+     * will remain running as-is.
+     * <li> Otherwise, the current activity will be finished and a new
+     * one started.
+     * </ul>
+     * 
+     * <p>If the given Intent can not be resolved to an available Activity,
+     * this method throws {@link android.content.ActivityNotFoundException}.
+     * 
+     * <p>Warning: There is an issue where, if the Intent does not
+     * include an explicit component, we can restore the state for a different
+     * activity class than was previously running when the state was saved (if
+     * the set of available activities changes between those points).
+     * 
+     * @param id Unique identifier of the activity to be started
+     * @param intent The Intent describing the activity to be started
+     * 
+     * @return Returns the window of the activity.  The caller needs to take
+     * care of adding this window to a view hierarchy, and likewise dealing
+     * with removing the old window if the activity has changed.
+     * 
+     * @throws android.content.ActivityNotFoundException
+     */
+    public Window startActivity(String id, Intent intent) {
+        if (mCurState == INITIALIZING) {
+            throw new IllegalStateException(
+                    "Activities can't be added until the containing group has been created.");
+        }
+        
+        boolean adding = false;
+        boolean sameIntent = false;
+
+        ActivityInfo aInfo = null;
+        
+        // Already have information about the new activity id?
+        LocalActivityRecord r = mActivities.get(id);
+        if (r == null) {
+            // Need to create it...
+            r = new LocalActivityRecord(id, intent);
+            adding = true;
+        } else if (r.intent != null) {
+            sameIntent = r.intent.filterEquals(intent); 
+            if (sameIntent) {
+                // We are starting the same activity.
+                aInfo = r.activityInfo;
+            }
+        }
+        if (aInfo == null) {
+            aInfo = mActivityThread.resolveActivityInfo(intent);
+        }
+        
+        // Pause the currently running activity if there is one and only a single
+        // activity is allowed to be running at a time.
+        if (mSingleMode) {
+            LocalActivityRecord old = mResumed;
+    
+            // If there was a previous activity, and it is not the current
+            // activity, we need to stop it.
+            if (old != null && old != r && mCurState == RESUMED) {
+                moveToState(old, STARTED);
+            }
+        }
+
+        if (adding) {
+            // It's a brand new world.
+            mActivities.put(id, r);
+            mActivityArray.add(r);
+        } else if (r.activityInfo != null) {
+            // If the new activity is the same as the current one, then
+            // we may be able to reuse it.
+            if (aInfo == r.activityInfo ||
+                    (aInfo.name.equals(r.activityInfo.name) &&
+                            aInfo.packageName.equals(r.activityInfo.packageName))) {
+                if (aInfo.launchMode != ActivityInfo.LAUNCH_MULTIPLE ||
+                        (intent.getFlags()&Intent.FLAG_ACTIVITY_SINGLE_TOP) != 0) {
+                    // The activity wants onNewIntent() called.
+                    ArrayList<Intent> intents = new ArrayList<Intent>(1);
+                    intents.add(intent);
+                    if (localLOGV) Log.v(TAG, r.id + ": new intent");
+                    mActivityThread.performNewIntents(r, intents);
+                    r.intent = intent;
+                    moveToState(r, mCurState);
+                    if (mSingleMode) {
+                        mResumed = r;
+                    }
+                    return r.window;
+                }
+                if (sameIntent &&
+                        (intent.getFlags()&Intent.FLAG_ACTIVITY_CLEAR_TOP) == 0) {
+                    // We are showing the same thing, so this activity is
+                    // just resumed and stays as-is.
+                    r.intent = intent;
+                    moveToState(r, mCurState);
+                    if (mSingleMode) {
+                        mResumed = r;
+                    }
+                    return r.window;
+                }
+            }
+            
+            // The new activity is different than the current one, or it
+            // is a multiple launch activity, so we need to destroy what
+            // is currently there.
+            performDestroy(r, true);
+        }
+        
+        r.intent = intent;
+        r.curState = INITIALIZING;
+        r.activityInfo = aInfo;
+
+        moveToState(r, mCurState);
+
+        // When in single mode keep track of the current activity
+        if (mSingleMode) {
+            mResumed = r;
+        }
+        return r.window;
+    }
+
+    private Window performDestroy(LocalActivityRecord r, boolean finish) {
+        Window win = null;
+        win = r.window;
+        if (r.curState == RESUMED && !finish) {
+            performPause(r, finish);
+        }
+        if (localLOGV) Log.v(TAG, r.id + ": destroying");
+        mActivityThread.performDestroyActivity(r, finish);
+        r.activity = null;
+        r.window = null;
+        if (finish) {
+            r.instanceState = null;
+        }
+        r.curState = DESTROYED;
+        return win;
+    }
+    
+    /**
+     * Destroy the activity associated with a particular id.  This activity
+     * will go through the normal lifecycle events and fine onDestroy(), and
+     * then the id removed from the group.
+     * 
+     * @param id Unique identifier of the activity to be destroyed
+     * @param finish If true, this activity will be finished, so its id and
+     * all state are removed from the group.
+     * 
+     * @return Returns the window that was used to display the activity, or
+     * null if there was none.
+     */
+    public Window destroyActivity(String id, boolean finish) {
+        LocalActivityRecord r = mActivities.get(id);
+        Window win = null;
+        if (r != null) {
+            win = performDestroy(r, finish);
+            if (finish) {
+                mActivities.remove(r);
+            }
+        }
+        return win;
+    }
+    
+    /**
+     * Retrieve the Activity that is currently running.
+     * 
+     * @return the currently running (resumed) Activity, or null if there is
+     *         not one
+     * 
+     * @see #startActivity
+     * @see #getCurrentId
+     */
+    public Activity getCurrentActivity() {
+        return mResumed != null ? mResumed.activity : null;
+    }
+
+    /**
+     * Retrieve the ID of the activity that is currently running.
+     * 
+     * @return the ID of the currently running (resumed) Activity, or null if
+     *         there is not one
+     * 
+     * @see #startActivity
+     * @see #getCurrentActivity
+     */
+    public String getCurrentId() {
+        return mResumed != null ? mResumed.id : null;
+    }
+
+    /**
+     * Return the Activity object associated with a string ID.
+     * 
+     * @see #startActivity
+     * 
+     * @return the associated Activity object, or null if the id is unknown or
+     *         its activity is not currently instantiated
+     */
+    public Activity getActivity(String id) {
+        LocalActivityRecord r = mActivities.get(id);
+        return r != null ? r.activity : null;
+    }
+
+    /**
+     * Restore a state that was previously returned by {@link #saveInstanceState}.  This
+     * adds to the activity group information about all activity IDs that had
+     * previously been saved, even if they have not been started yet, so if the
+     * user later navigates to them the correct state will be restored.
+     * 
+     * <p>Note: This does <b>not</b> change the current running activity, or
+     * start whatever activity was previously running when the state was saved.
+     * That is up to the client to do, in whatever way it thinks is best.
+     * 
+     * @param state a previously saved state; does nothing if this is null
+     * 
+     * @see #saveInstanceState
+     */
+    public void dispatchCreate(Bundle state) {
+        if (state != null) {
+            final Iterator<String> i = state.keySet().iterator();
+            while (i.hasNext()) {
+                try {
+                    final String id = i.next();
+                    final Bundle astate = state.getBundle(id);
+                    LocalActivityRecord r = mActivities.get(id);
+                    if (r != null) {
+                        r.instanceState = astate;
+                    } else {
+                        r = new LocalActivityRecord(id, null);
+                        r.instanceState = astate;
+                        mActivities.put(id, r);
+                        mActivityArray.add(r);
+                    }
+                } catch (Exception e) {
+                    // Recover from -all- app errors.
+                    Log.e(TAG,
+                          "Exception thrown when restoring LocalActivityManager state",
+                          e);
+                }
+            }
+        }
+        
+        mCurState = CREATED;
+    }
+
+    /**
+     * Retrieve the state of all activities known by the group.  For
+     * activities that have previously run and are now stopped or finished, the
+     * last saved state is used.  For the current running activity, its
+     * {@link Activity#onSaveInstanceState} is called to retrieve its current state.
+     * 
+     * @return a Bundle holding the newly created state of all known activities
+     * 
+     * @see #dispatchCreate
+     */
+    public Bundle saveInstanceState() {
+        Bundle state = null;
+
+        // FIXME: child activities will freeze as part of onPaused. Do we
+        // need to do this here?
+        final int N = mActivityArray.size();
+        for (int i=0; i<N; i++) {
+            final LocalActivityRecord r = mActivityArray.get(i);
+            if (state == null) {
+                state = new Bundle();
+            }
+            if ((r.instanceState != null || r.curState == RESUMED)
+                    && r.activity != null) {
+                // We need to save the state now, if we don't currently
+                // already have it or the activity is currently resumed.
+                final Bundle childState = new Bundle();
+                r.activity.onSaveInstanceState(childState);
+                r.instanceState = childState;
+            }
+            if (r.instanceState != null) {
+                state.putBundle(r.id, r.instanceState);
+            }
+        }
+
+        return state;
+    }
+
+    /**
+     * Called by the container activity in its {@link Activity#onResume} so
+     * that LocalActivityManager can perform the corresponding action on the
+     * activities it holds.
+     * 
+     * @see Activity#onResume
+     */
+    public void dispatchResume() {
+        mCurState = RESUMED;
+        if (mSingleMode) {
+            if (mResumed != null) {
+                moveToState(mResumed, RESUMED);
+            }
+        } else {
+            final int N = mActivityArray.size();
+            for (int i=0; i<N; i++) {
+                moveToState(mActivityArray.get(i), RESUMED);
+            }
+        }
+    }
+
+    /**
+     * Called by the container activity in its {@link Activity#onPause} so
+     * that LocalActivityManager can perform the corresponding action on the
+     * activities it holds.
+     * 
+     * @param finishing set to true if the parent activity has been finished;
+     *                  this can be determined by calling
+     *                  Activity.isFinishing()
+     * 
+     * @see Activity#onPause
+     * @see Activity#isFinishing
+     */
+    public void dispatchPause(boolean finishing) {
+        if (finishing) {
+            mFinishing = true;
+        }
+        mCurState = STARTED;
+        if (mSingleMode) {
+            if (mResumed != null) {
+                moveToState(mResumed, STARTED);
+            }
+        } else {
+            final int N = mActivityArray.size();
+            for (int i=0; i<N; i++) {
+                LocalActivityRecord r = mActivityArray.get(i);
+                if (r.curState == RESUMED) {
+                    moveToState(r, STARTED);
+                }
+            }
+        }
+    }
+
+    /**
+     * Called by the container activity in its {@link Activity#onStop} so
+     * that LocalActivityManager can perform the corresponding action on the
+     * activities it holds.
+     * 
+     * @see Activity#onStop
+     */
+    public void dispatchStop() {
+        mCurState = CREATED;
+        final int N = mActivityArray.size();
+        for (int i=0; i<N; i++) {
+            LocalActivityRecord r = mActivityArray.get(i);
+            moveToState(r, CREATED);
+        }
+    }
+    
+    /**
+     * Call onRetainNonConfigurationInstance on each child activity and store the
+     * results in a HashMap by id.  Only construct the HashMap if there is a non-null
+     * object to store.  Note that this does not support nested ActivityGroups.
+     * 
+     * {@hide}
+     */
+    public HashMap<String,Object> dispatchRetainNonConfigurationInstance() {
+        HashMap<String,Object> instanceMap = null;
+        
+        final int N = mActivityArray.size();
+        for (int i=0; i<N; i++) {
+            LocalActivityRecord r = mActivityArray.get(i);
+            if ((r != null) && (r.activity != null)) {
+                Object instance = r.activity.onRetainNonConfigurationInstance();
+                if (instance != null) {
+                    if (instanceMap == null) {
+                        instanceMap = new HashMap<String,Object>();
+                    }
+                    instanceMap.put(r.id, instance);
+                }
+            }
+        }
+        return instanceMap;
+    }
+
+    /**
+     * Remove all activities from this LocalActivityManager, performing an
+     * {@link Activity#onDestroy} on any that are currently instantiated.
+     */
+    public void removeAllActivities() {
+        dispatchDestroy(true);
+    }
+
+    /**
+     * Called by the container activity in its {@link Activity#onDestroy} so
+     * that LocalActivityManager can perform the corresponding action on the
+     * activities it holds.
+     * 
+     * @see Activity#onDestroy
+     */
+    public void dispatchDestroy(boolean finishing) {
+        final int N = mActivityArray.size();
+        for (int i=0; i<N; i++) {
+            LocalActivityRecord r = mActivityArray.get(i);
+            if (localLOGV) Log.v(TAG, r.id + ": destroying");
+            mActivityThread.performDestroyActivity(r, finishing);
+        }
+        mActivities.clear();
+        mActivityArray.clear();
+    }
+}
diff --git a/core/java/android/app/Notification.aidl b/core/java/android/app/Notification.aidl
new file mode 100644
index 0000000..9d8129c
--- /dev/null
+++ b/core/java/android/app/Notification.aidl
@@ -0,0 +1,19 @@
+/**
+ * Copyright (c) 2007, 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 android.app;
+
+parcelable Notification;
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
new file mode 100644
index 0000000..51fddb1
--- /dev/null
+++ b/core/java/android/app/Notification.java
@@ -0,0 +1,483 @@
+/*
+ * Copyright (C) 2007 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 android.app;
+
+import java.util.Date;
+
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+import android.media.AudioManager;
+import android.net.Uri;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+import android.text.format.DateFormat;
+import android.text.format.DateUtils;
+import android.widget.RemoteViews;
+
+/**
+ * A class that represents how a persistent notification is to be presented to
+ * the user using the {@link android.app.NotificationManager}.
+ *
+ */
+public class Notification implements Parcelable
+{
+    /**
+     * Use all default values (where applicable).
+     */
+    public static final int DEFAULT_ALL = ~0;
+    
+    /**
+     * Use the default notification sound. This will ignore any given
+     * {@link #sound}.
+     * 
+     * @see #defaults
+     */ 
+    public static final int DEFAULT_SOUND = 1;
+
+    /**
+     * Use the default notification vibrate. This will ignore any given
+     * {@link #vibrate}.
+     * 
+     * @see #defaults
+     */ 
+    public static final int DEFAULT_VIBRATE = 2;
+    
+    /**
+     * Use the default notification lights. This will ignore the
+     * {@link #FLAG_SHOW_LIGHTS} bit, and {@link #ledARGB}, {@link #ledOffMS}, or
+     * {@link #ledOnMS}.
+     * 
+     * @see #defaults
+     */ 
+    public static final int DEFAULT_LIGHTS = 4;
+    
+    /**
+     * The timestamp for the notification.  The icons and expanded views
+     * are sorted by this key.
+     */
+    public long when;
+
+    /**
+     * The resource id of a drawable to use as the icon in the status bar.
+     */
+    public int icon;
+
+    /**
+     * The number of events that this notification represents.  For example, if this is the
+     * new mail notification, this would be the number of unread messages.  This number is
+     * be superimposed over the icon in the status bar.  If the number is 0 or negative, it
+     * is not shown in the status bar.
+     */
+    public int number;
+
+    /**
+     * The intent to execute when the expanded status entry is clicked.  If
+     * this is an activity, it must include the
+     * {@link android.content.Intent#FLAG_ACTIVITY_NEW_TASK} flag, which requires
+     * that you take care of task management as described in the <em>Activities and Tasks</em>
+     * section of the <a href="{@docRoot}guide/topics/fundamentals.html#acttask">Application 
+     * Fundamentals</a> document.
+     */
+    public PendingIntent contentIntent;
+
+    /**
+     * The intent to execute when the status entry is deleted by the user
+     * with the "Clear All Notifications" button. This probably shouldn't
+     * be launching an activity since several of those will be sent at the
+     * same time.
+     */
+    public PendingIntent deleteIntent;
+
+    /**
+     * Text to scroll across the screen when this item is added to
+     * the status bar.
+     */
+    public CharSequence tickerText;
+
+    /**
+     * The view that shows when this notification is shown in the expanded status bar.
+     */
+    public RemoteViews contentView;
+
+    /**
+     * If the icon in the status bar is to have more than one level, you can set this.  Otherwise,
+     * leave it at its default value of 0.
+     *
+     * @see android.widget.ImageView#setImageLevel
+     * @see android.graphics.drawable#setLevel
+     */
+    public int iconLevel;
+
+    /**
+     * The sound to play.
+     * 
+     * <p>
+     * To play the default notification sound, see {@link #defaults}. 
+     * </p>
+     */
+    public Uri sound;
+
+    /**
+     * Use this constant as the value for audioStreamType to request that
+     * the default stream type for notifications be used.  Currently the
+     * default stream type is STREAM_RING.
+     */
+    public static final int STREAM_DEFAULT = -1;
+
+    /**
+     * The audio stream type to use when playing the sound.
+     * Should be one of the STREAM_ constants from
+     * {@link android.media.AudioManager}.
+     */
+    public int audioStreamType = STREAM_DEFAULT;
+
+    
+    /**
+     * The pattern with which to vibrate. This pattern will repeat if {@link
+     * #FLAG_INSISTENT} bit is set in the {@link #flags} field.
+     * 
+     * <p>
+     * To vibrate the default pattern, see {@link #defaults}.
+     * </p>
+     * 
+     * @see android.os.Vibrator#vibrate(long[],int)
+     */
+    public long[] vibrate;
+
+    /**
+     * The color of the led.  The hardware will do its best approximation.
+     *
+     * @see #FLAG_SHOW_LIGHTS
+     * @see #flags
+     */
+    public int ledARGB;
+
+    /**
+     * The number of milliseconds for the LED to be on while it's flashing.
+     * The hardware will do its best approximation.
+     *
+     * @see #FLAG_SHOW_LIGHTS
+     * @see #flags
+     */
+    public int ledOnMS;
+
+    /**
+     * The number of milliseconds for the LED to be off while it's flashing.
+     * The hardware will do its best approximation.
+     *
+     * @see #FLAG_SHOW_LIGHTS
+     * @see #flags
+     */
+    public int ledOffMS;
+
+    /**
+     * Specifies which values should be taken from the defaults.
+     * <p>
+     * To set, OR the desired from {@link #DEFAULT_SOUND},
+     * {@link #DEFAULT_VIBRATE}, {@link #DEFAULT_LIGHTS}. For all default
+     * values, use {@link #DEFAULT_ALL}.
+     * </p>
+     */
+    public int defaults;
+
+
+    /**
+     * Bit to be bitwise-ored into the {@link #flags} field that should be
+     * set if you want the LED on for this notification.
+     * <ul>
+     * <li>To turn the LED off, pass 0 in the alpha channel for colorARGB
+     *      or 0 for both ledOnMS and ledOffMS.</li>
+     * <li>To turn the LED on, pass 1 for ledOnMS and 0 for ledOffMS.</li>
+     * <li>To flash the LED, pass the number of milliseconds that it should
+     *      be on and off to ledOnMS and ledOffMS.</li>
+     * </ul>
+     * <p>
+     * Since hardware varies, you are not guaranteed that any of the values
+     * you pass are honored exactly.  Use the system defaults (TODO) if possible
+     * because they will be set to values that work on any given hardware.
+     * <p>
+     * The alpha channel must be set for forward compatibility.
+     * 
+     */
+    public static final int FLAG_SHOW_LIGHTS        = 0x00000001;
+
+    /**
+     * Bit to be bitwise-ored into the {@link #flags} field that should be
+     * set if this notification is in reference to something that is ongoing,
+     * like a phone call.  It should not be set if this notification is in
+     * reference to something that happened at a particular point in time,
+     * like a missed phone call.
+     */
+    public static final int FLAG_ONGOING_EVENT      = 0x00000002;
+
+    /**
+     * Bit to be bitwise-ored into the {@link #flags} field that if set,
+     * the audio and vibration will be repeated until the notification is
+     * cancelled.
+     *
+     * <p>
+     * NOTE: This notion will change when we have decided exactly
+     * what the UI will be.
+     * </p>
+     */
+    public static final int FLAG_INSISTENT          = 0x00000004;
+
+    /**
+     * Bit to be bitwise-ored into the {@link #flags} field that should be
+     * set if you want the sound and/or vibration play each time the
+     * notification is sent, even if it has not been canceled before that.
+     */
+    public static final int FLAG_ONLY_ALERT_ONCE    = 0x00000008;
+
+    /**
+     * Bit to be bitwise-ored into the {@link #flags} field that should be
+     * set if the notification should be canceled when it is clicked by the
+     * user. 
+     */
+    public static final int FLAG_AUTO_CANCEL        = 0x00000010;
+
+    /**
+     * Bit to be bitwise-ored into the {@link #flags} field that should be
+     * set if the notification should not be canceled when the user clicks
+     * the Clear all button.
+     */
+    public static final int FLAG_NO_CLEAR           = 0x00000020;
+
+    public int flags;
+
+    /**
+     * Constructs a Notification object with everything set to 0.
+     */
+    public Notification()
+    {
+        this.when = System.currentTimeMillis();
+    }
+
+    /**
+     * @deprecated use {@link #Notification(int,CharSequence,long)} and {@link #setLatestEventInfo}.
+     * @hide
+     */
+    public Notification(Context context, int icon, CharSequence tickerText, long when,
+            CharSequence contentTitle, CharSequence contentText, Intent contentIntent)
+    {
+        this.when = when;
+        this.icon = icon;
+        this.tickerText = tickerText;
+        setLatestEventInfo(context, contentTitle, contentText,
+                PendingIntent.getActivity(context, 0, contentIntent, 0));
+    }
+
+    /**
+     * Constructs a Notification object with the information needed to
+     * have a status bar icon without the standard expanded view.
+     *
+     * @param icon          The resource id of the icon to put in the status bar.
+     * @param tickerText    The text that flows by in the status bar when the notification first
+     *                      activates.
+     * @param when          The time to show in the time field.  In the System.currentTimeMillis
+     *                      timebase.
+     */
+    public Notification(int icon, CharSequence tickerText, long when)
+    {
+        this.icon = icon;
+        this.tickerText = tickerText;
+        this.when = when;
+    }
+
+    /**
+     * Unflatten the notification from a parcel.
+     */
+    public Notification(Parcel parcel)
+    {
+        int version = parcel.readInt();
+
+        when = parcel.readLong();
+        icon = parcel.readInt();
+        number = parcel.readInt();
+        if (parcel.readInt() != 0) {
+            contentIntent = PendingIntent.CREATOR.createFromParcel(parcel);
+        }
+        if (parcel.readInt() != 0) {
+            deleteIntent = PendingIntent.CREATOR.createFromParcel(parcel);
+        }
+        if (parcel.readInt() != 0) {
+            tickerText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(parcel);
+        }
+        if (parcel.readInt() != 0) {
+            contentView = RemoteViews.CREATOR.createFromParcel(parcel);
+        }
+        defaults = parcel.readInt();
+        flags = parcel.readInt();
+        if (parcel.readInt() != 0) {
+            sound = Uri.CREATOR.createFromParcel(parcel);
+        }
+
+        audioStreamType = parcel.readInt();
+        vibrate = parcel.createLongArray();
+        ledARGB = parcel.readInt();
+        ledOnMS = parcel.readInt();
+        ledOffMS = parcel.readInt();
+        iconLevel = parcel.readInt();
+    }
+
+    public int describeContents() {
+        return 0;
+    }
+
+    /**
+     * Flatten this notification from a parcel.
+     */
+    public void writeToParcel(Parcel parcel, int flags)
+    {
+        parcel.writeInt(1);
+
+        parcel.writeLong(when);
+        parcel.writeInt(icon);
+        parcel.writeInt(number);
+        if (contentIntent != null) {
+            parcel.writeInt(1);
+            contentIntent.writeToParcel(parcel, 0);
+        } else {
+            parcel.writeInt(0);
+        }
+        if (deleteIntent != null) {
+            parcel.writeInt(1);
+            deleteIntent.writeToParcel(parcel, 0);
+        } else {
+            parcel.writeInt(0);
+        }
+        if (tickerText != null) {
+            parcel.writeInt(1);
+            TextUtils.writeToParcel(tickerText, parcel, flags);
+        } else {
+            parcel.writeInt(0);
+        }
+        if (contentView != null) {
+            parcel.writeInt(1);
+            contentView.writeToParcel(parcel, 0);
+        } else {
+            parcel.writeInt(0);
+        }
+
+        parcel.writeInt(defaults);
+        parcel.writeInt(this.flags);
+
+        if (sound != null) {
+            parcel.writeInt(1);
+            sound.writeToParcel(parcel, 0);
+        } else {
+            parcel.writeInt(0);
+        }
+        parcel.writeInt(audioStreamType);
+        parcel.writeLongArray(vibrate);
+        parcel.writeInt(ledARGB);
+        parcel.writeInt(ledOnMS);
+        parcel.writeInt(ledOffMS);
+        parcel.writeInt(iconLevel);
+    }
+
+    /**
+     * Parcelable.Creator that instantiates Notification objects
+     */
+    public static final Parcelable.Creator<Notification> CREATOR
+            = new Parcelable.Creator<Notification>()
+    {
+        public Notification createFromParcel(Parcel parcel)
+        {
+            return new Notification(parcel);
+        }
+
+        public Notification[] newArray(int size)
+        {
+            return new Notification[size];
+        }
+    };
+
+    /**
+     * Sets the {@link #contentView} field to be a view with the standard "Latest Event"
+     * layout.
+     *
+     * <p>Uses the {@link #icon} and {@link #when} fields to set the icon and time fields
+     * in the view.</p>
+     * @param context       The context for your application / activity.
+     * @param contentTitle The title that goes in the expanded entry.
+     * @param contentText  The text that goes in the expanded entry.
+     * @param contentIntent The intent to launch when the user clicks the expanded notification.
+     * If this is an activity, it must include the
+     * {@link android.content.Intent#FLAG_ACTIVITY_NEW_TASK} flag, which requires
+     * that you take care of task management as described in 
+     * <a href="{@docRoot}guide/topics/fundamentals.html#lcycles">Application Fundamentals: Activities and Tasks</a>.
+     */
+    public void setLatestEventInfo(Context context,
+            CharSequence contentTitle, CharSequence contentText, PendingIntent contentIntent) {
+        RemoteViews contentView = new RemoteViews(context.getPackageName(),
+                com.android.internal.R.layout.status_bar_latest_event_content);
+        if (this.icon != 0) {
+            contentView.setImageViewResource(com.android.internal.R.id.icon, this.icon);
+        }
+        if (contentTitle != null) {
+            contentView.setTextViewText(com.android.internal.R.id.title, contentTitle);
+        }
+        if (contentText != null) {
+            contentView.setTextViewText(com.android.internal.R.id.text, contentText);
+        }
+        if (this.when != 0) {
+            Date date = new Date(when);
+            CharSequence str = 
+                DateUtils.isToday(when) ? DateFormat.getTimeFormat(context).format(date)
+                    : DateFormat.getDateFormat(context).format(date);
+            contentView.setTextViewText(com.android.internal.R.id.time, str);
+        }
+
+        this.contentView = contentView;
+        this.contentIntent = contentIntent;
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder sb = new StringBuilder();
+        sb.append("Notification(vibrate=");
+        if (this.vibrate != null) {
+            int N = this.vibrate.length-1;
+            sb.append("[");
+            for (int i=0; i<N; i++) {
+                sb.append(this.vibrate[i]);
+                sb.append(',');
+            }
+            sb.append(this.vibrate[N]);
+            sb.append("]");
+        } else if ((this.defaults & DEFAULT_VIBRATE) != 0) {
+            sb.append("default");
+        } else {
+            sb.append("null");
+        }
+        sb.append(",sound=");
+        if (this.sound != null) {
+            sb.append(this.sound.toString());
+        } else if ((this.defaults & DEFAULT_SOUND) != 0) {
+            sb.append("default");
+        } else {
+            sb.append("null");
+        }
+        sb.append(",defaults=0x");
+        sb.append(Integer.toHexString(this.defaults));
+        sb.append(")");
+        return sb.toString();
+    }
+}
diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java
new file mode 100644
index 0000000..39edab7
--- /dev/null
+++ b/core/java/android/app/NotificationManager.java
@@ -0,0 +1,134 @@
+/*
+ * Copyright (C) 2007 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 android.app;
+
+import android.content.Context;
+import android.os.RemoteException;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.ServiceManager;
+import android.util.Log;
+
+/**
+ * Class to notify the user of events that happen.  This is how you tell
+ * the user that something has happened in the background. {@more}
+ *
+ * Notifications can take different forms:
+ * <ul>
+ *      <li>A persistent icon that goes in the status bar and is accessible
+ *          through the launcher, (when the user selects it, a designated Intent
+ *          can be launched),</li>
+ *      <li>Turning on or flashing LEDs on the device, or</li>
+ *      <li>Alerting the user by flashing the backlight, playing a sound,
+ *          or vibrating.</li>
+ * </ul>
+ *
+ * <p>
+ * Each of the notify methods takes an int id parameter.  This id identifies
+ * this notification from your app to the system, so that id should be unique
+ * within your app.  If you call one of the notify methods with an id that is
+ * currently active and a new set of notification parameters, it will be
+ * updated.  For example, if you pass a new status bar icon, the old icon in
+ * the status bar will be replaced with the new one.  This is also the same
+ * id you pass to the {@link #cancel} method to clear this notification.
+ *
+ * <p>
+ * You do not instantiate this class directly; instead, retrieve it through
+ * {@link android.content.Context#getSystemService}.
+ *
+ * @see android.app.Notification
+ * @see android.content.Context#getSystemService
+ */
+public class NotificationManager
+{
+    private static String TAG = "NotificationManager";
+    private static boolean DEBUG = false;
+    private static boolean localLOGV = DEBUG || android.util.Config.LOGV;
+
+    private static INotificationManager sService;
+
+    static private INotificationManager getService()
+    {
+        if (sService != null) {
+            return sService;
+        }
+        IBinder b = ServiceManager.getService("notification");
+        sService = INotificationManager.Stub.asInterface(b);
+        return sService;
+    }
+
+    /*package*/ NotificationManager(Context context, Handler handler)
+    {
+        mContext = context;
+    }
+
+    /**
+     * Persistent notification on the status bar, 
+     *
+     * @param id An identifier for this notification unique within your
+     *        application.
+     * @param notification A {@link Notification} object describing how to
+     *        notify the user, other than the view you're providing. Must not be null.
+     */
+    public void notify(int id, Notification notification)
+    {
+        int[] idOut = new int[1];
+        INotificationManager service = getService();
+        String pkg = mContext.getPackageName();
+        if (localLOGV) Log.v(TAG, pkg + ": notify(" + id + ", " + notification + ")");
+        try {
+            service.enqueueNotification(pkg, id, notification, idOut);
+            if (id != idOut[0]) {
+                Log.w(TAG, "notify: id corrupted: sent " + id + ", got back " + idOut[0]);
+            }
+        } catch (RemoteException e) {
+        }
+    }
+
+    /**
+     * Cancel a previously shown notification.  If it's transient, the view
+     * will be hidden.  If it's persistent, it will be removed from the status
+     * bar.
+     */
+    public void cancel(int id)
+    {
+        INotificationManager service = getService();
+        String pkg = mContext.getPackageName();
+        if (localLOGV) Log.v(TAG, pkg + ": cancel(" + id + ")");
+        try {
+            service.cancelNotification(pkg, id);
+        } catch (RemoteException e) {
+        }
+    }
+
+    /**
+     * Cancel all previously shown notifications. See {@link #cancel} for the
+     * detailed behavior.
+     */
+    public void cancelAll()
+    {
+        INotificationManager service = getService();
+        String pkg = mContext.getPackageName();
+        if (localLOGV) Log.v(TAG, pkg + ": cancelAll()");
+        try {
+            service.cancelAllNotifications(pkg);
+        } catch (RemoteException e) {
+        }
+    }
+
+    private Context mContext;
+}
diff --git a/core/java/android/app/PendingIntent.aidl b/core/java/android/app/PendingIntent.aidl
new file mode 100644
index 0000000..f0d530c
--- /dev/null
+++ b/core/java/android/app/PendingIntent.aidl
@@ -0,0 +1,20 @@
+/* //device/java/android/android/content/Intent.aidl
+**
+** Copyright 2007, 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 android.app;
+
+parcelable PendingIntent;
diff --git a/core/java/android/app/PendingIntent.java b/core/java/android/app/PendingIntent.java
new file mode 100644
index 0000000..1bed706
--- /dev/null
+++ b/core/java/android/app/PendingIntent.java
@@ -0,0 +1,508 @@
+/*
+ * 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 android.app;
+
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.AndroidException;
+
+/**
+ * A description of an Intent and target action to perform with it.  Instances
+ * of this class are created with {@link #getActivity},
+ * {@link #getBroadcast}, {@link #getService}; the returned object can be
+ * handed to other applications so that they can perform the action you
+ * described on your behalf at a later time.
+ *
+ * <p>By giving a PendingIntent to another application,
+ * you are granting it the right to perform the operation you have specified
+ * as if the other application was yourself (with the same permissions and
+ * identity).  As such, you should be careful about how you build the PendingIntent:
+ * often, for example, the base Intent you supply will have the component
+ * name explicitly set to one of your own components, to ensure it is ultimately
+ * sent there and nowhere else.
+ *
+ * <p>A PendingIntent itself is simply a reference to a token maintained by
+ * the system describing the original data used to retrieve it.  This means
+ * that, even if its owning application's process is killed, the
+ * PendingIntent itself will remain usable from other processes that
+ * have been given it.  If the creating application later re-retrieves the
+ * same kind of PendingIntent (same operation, same Intent action, data,
+ * categories, and components, and same flags), it will receive a PendingIntent
+ * representing the same token if that is still valid, and can thus call
+ * {@link #cancel} to remove it.
+ */
+public final class PendingIntent implements Parcelable {
+    private final IIntentSender mTarget;
+
+    /**
+     * Flag for use with {@link #getActivity}, {@link #getBroadcast}, and
+     * {@link #getService}: this
+     * PendingIntent can only be used once.  If set, after
+     * {@link #send()} is called on it, it will be automatically
+     * canceled for you and any future attempt to send through it will fail.
+     */
+    public static final int FLAG_ONE_SHOT = 1<<30;
+    /**
+     * Flag for use with {@link #getActivity}, {@link #getBroadcast}, and
+     * {@link #getService}: if the described PendingIntent does not already
+     * exist, then simply return null instead of creating it.
+     */
+    public static final int FLAG_NO_CREATE = 1<<29;
+    /**
+     * Flag for use with {@link #getActivity}, {@link #getBroadcast}, and
+     * {@link #getService}: if the described PendingIntent already exists,
+     * the current one is canceled before generating a new one.  You can use
+     * this to retrieve a new PendingIntent when you are only changing the
+     * extra data in the Intent; by canceling the previous pending intent,
+     * this ensures that only entities given the new data will be able to
+     * launch it.  If this assurance is not an issue, consider
+     * {@link #FLAG_UPDATE_CURRENT}.
+     */
+    public static final int FLAG_CANCEL_CURRENT = 1<<28;
+    /**
+     * Flag for use with {@link #getActivity}, {@link #getBroadcast}, and
+     * {@link #getService}: if the described PendingIntent already exists,
+     * then keep it but its replace its extra data with what is in this new
+     * Intent.  This can be used if you are creating intents where only the
+     * extras change, and don't care that any entities that received your
+     * previous PendingIntent will be able to launch it with your new
+     * extras even if they are not explicitly given to it.
+     */
+    public static final int FLAG_UPDATE_CURRENT = 1<<27;
+
+    /**
+     * Exception thrown when trying to send through a PendingIntent that
+     * has been canceled or is otherwise no longer able to execute the request.
+     */
+    public static class CanceledException extends AndroidException {
+        public CanceledException() {
+        }
+
+        public CanceledException(String name) {
+            super(name);
+        }
+
+        public CanceledException(Exception cause) {
+            super(cause);
+        }
+    };
+
+    /**
+     * Callback interface for discovering when a send operation has
+     * completed.  Primarily for use with a PendingIntent that is
+     * performing a broadcast, this provides the same information as
+     * calling {@link Context#sendOrderedBroadcast(Intent, String,
+     * android.content.BroadcastReceiver, Handler, int, String, Bundle)
+     * Context.sendBroadcast()} with a final BroadcastReceiver.
+     */
+    public interface OnFinished {
+        /**
+         * Called when a send operation as completed.
+         *
+         * @param pendingIntent The PendingIntent this operation was sent through.
+         * @param intent The original Intent that was sent.
+         * @param resultCode The final result code determined by the send.
+         * @param resultData The final data collected by a broadcast.
+         * @param resultExtras The final extras collected by a broadcast.
+         */
+        void onSendFinished(PendingIntent pendingIntent, Intent intent,
+                int resultCode, String resultData, Bundle resultExtras);
+    }
+
+    private static class FinishedDispatcher extends IIntentReceiver.Stub
+            implements Runnable {
+        private final PendingIntent mPendingIntent;
+        private final OnFinished mWho;
+        private final Handler mHandler;
+        private Intent mIntent;
+        private int mResultCode;
+        private String mResultData;
+        private Bundle mResultExtras;
+        FinishedDispatcher(PendingIntent pi, OnFinished who, Handler handler) {
+            mPendingIntent = pi;
+            mWho = who;
+            mHandler = handler;
+        }
+        public void performReceive(Intent intent, int resultCode,
+                String data, Bundle extras, boolean serialized) {
+            mIntent = intent;
+            mResultCode = resultCode;
+            mResultData = data;
+            mResultExtras = extras;
+            if (mHandler == null) {
+                run();
+            } else {
+                mHandler.post(this);
+            }
+        }
+        public void run() {
+            mWho.onSendFinished(mPendingIntent, mIntent, mResultCode,
+                    mResultData, mResultExtras);
+        }
+    }
+
+    /**
+     * Retrieve a PendingIntent that will start a new activity, like calling
+     * {@link Context#startActivity(Intent) Context.startActivity(Intent)}.
+     * Note that the activity will be started outside of the context of an
+     * existing activity, so you must use the {@link Intent#FLAG_ACTIVITY_NEW_TASK
+     * Intent.FLAG_ACTIVITY_NEW_TASK} launch flag in the Intent.
+     *
+     * @param context The Context in which this PendingIntent should start
+     * the activity.
+     * @param requestCode Private request code for the sender (currently
+     * not used).
+     * @param intent Intent of the activity to be launched.
+     * @param flags May be {@link #FLAG_ONE_SHOT}, {@link #FLAG_NO_CREATE},
+     * {@link #FLAG_CANCEL_CURRENT}, {@link #FLAG_UPDATE_CURRENT},
+     * or any of the flags as supported by
+     * {@link Intent#fillIn Intent.fillIn()} to control which unspecified parts
+     * of the intent that can be supplied when the actual send happens.
+     *
+     * @return Returns an existing or new PendingIntent matching the given
+     * parameters.  May return null only if {@link #FLAG_NO_CREATE} has been
+     * supplied.
+     */
+    public static PendingIntent getActivity(Context context, int requestCode,
+            Intent intent, int flags) {
+        String packageName = context.getPackageName();
+        String resolvedType = intent != null ? intent.resolveTypeIfNeeded(
+                context.getContentResolver()) : null;
+        try {
+            IIntentSender target =
+                ActivityManagerNative.getDefault().getIntentSender(
+                    IActivityManager.INTENT_SENDER_ACTIVITY, packageName,
+                    null, null, requestCode, intent, resolvedType, flags);
+            return target != null ? new PendingIntent(target) : null;
+        } catch (RemoteException e) {
+        }
+        return null;
+    }
+
+    /**
+     * Retrieve a PendingIntent that will perform a broadcast, like calling
+     * {@link Context#sendBroadcast(Intent) Context.sendBroadcast()}.
+     *
+     * @param context The Context in which this PendingIntent should perform
+     * the broadcast.
+     * @param requestCode Private request code for the sender (currently
+     * not used).
+     * @param intent The Intent to be broadcast.
+     * @param flags May be {@link #FLAG_ONE_SHOT}, {@link #FLAG_NO_CREATE},
+     * {@link #FLAG_CANCEL_CURRENT}, {@link #FLAG_UPDATE_CURRENT},
+     * or any of the flags as supported by
+     * {@link Intent#fillIn Intent.fillIn()} to control which unspecified parts
+     * of the intent that can be supplied when the actual send happens.
+     *
+     * @return Returns an existing or new PendingIntent matching the given
+     * parameters.  May return null only if {@link #FLAG_NO_CREATE} has been
+     * supplied.
+     */
+    public static PendingIntent getBroadcast(Context context, int requestCode,
+            Intent intent, int flags) {
+        String packageName = context.getPackageName();
+        String resolvedType = intent != null ? intent.resolveTypeIfNeeded(
+                context.getContentResolver()) : null;
+        try {
+            IIntentSender target =
+                ActivityManagerNative.getDefault().getIntentSender(
+                    IActivityManager.INTENT_SENDER_BROADCAST, packageName,
+                    null, null, requestCode, intent, resolvedType, flags);
+            return target != null ? new PendingIntent(target) : null;
+        } catch (RemoteException e) {
+        }
+        return null;
+    }
+
+    /**
+     * Retrieve a PendingIntent that will start a service, like calling
+     * {@link Context#startService Context.startService()}.  The start
+     * arguments given to the service will come from the extras of the Intent.
+     *
+     * @param context The Context in which this PendingIntent should start
+     * the service.
+     * @param requestCode Private request code for the sender (currently
+     * not used).
+     * @param intent An Intent describing the service to be started.
+     * @param flags May be {@link #FLAG_ONE_SHOT}, {@link #FLAG_NO_CREATE},
+     * {@link #FLAG_CANCEL_CURRENT}, {@link #FLAG_UPDATE_CURRENT},
+     * or any of the flags as supported by
+     * {@link Intent#fillIn Intent.fillIn()} to control which unspecified parts
+     * of the intent that can be supplied when the actual send happens.
+     *
+     * @return Returns an existing or new PendingIntent matching the given
+     * parameters.  May return null only if {@link #FLAG_NO_CREATE} has been
+     * supplied.
+     */
+    public static PendingIntent getService(Context context, int requestCode,
+            Intent intent, int flags) {
+        String packageName = context.getPackageName();
+        String resolvedType = intent != null ? intent.resolveTypeIfNeeded(
+                context.getContentResolver()) : null;
+        try {
+            IIntentSender target =
+                ActivityManagerNative.getDefault().getIntentSender(
+                    IActivityManager.INTENT_SENDER_SERVICE, packageName,
+                    null, null, requestCode, intent, resolvedType, flags);
+            return target != null ? new PendingIntent(target) : null;
+        } catch (RemoteException e) {
+        }
+        return null;
+    }
+
+    /**
+     * Cancel a currently active PendingIntent.  Only the original application
+     * owning an PendingIntent can cancel it.
+     */
+    public void cancel() {
+        try {
+            ActivityManagerNative.getDefault().cancelIntentSender(mTarget);
+        } catch (RemoteException e) {
+        }
+    }
+
+    /**
+     * Perform the operation associated with this PendingIntent.
+     *
+     * @see #send(Context, int, Intent, android.app.PendingIntent.OnFinished, Handler)
+     *
+     * @throws CanceledException Throws CanceledException if the PendingIntent
+     * is no longer allowing more intents to be sent through it.
+     */
+    public void send() throws CanceledException {
+        send(null, 0, null, null, null);
+    }
+
+    /**
+     * Perform the operation associated with this PendingIntent.
+     *
+     * @param code Result code to supply back to the PendingIntent's target.
+     *
+     * @see #send(Context, int, Intent, android.app.PendingIntent.OnFinished, Handler)
+     *
+     * @throws CanceledException Throws CanceledException if the PendingIntent
+     * is no longer allowing more intents to be sent through it.
+     */
+    public void send(int code) throws CanceledException {
+        send(null, code, null, null, null);
+    }
+
+    /**
+     * Perform the operation associated with this PendingIntent, allowing the
+     * caller to specify information about the Intent to use.
+     *
+     * @param context The Context of the caller.
+     * @param code Result code to supply back to the PendingIntent's target.
+     * @param intent Additional Intent data.  See {@link Intent#fillIn
+     * Intent.fillIn()} for information on how this is applied to the
+     * original Intent.
+     *
+     * @see #send(Context, int, Intent, android.app.PendingIntent.OnFinished, Handler)
+     *
+     * @throws CanceledException Throws CanceledException if the PendingIntent
+     * is no longer allowing more intents to be sent through it.
+     */
+    public void send(Context context, int code, Intent intent)
+            throws CanceledException {
+        send(context, code, intent, null, null);
+    }
+
+    /**
+     * Perform the operation associated with this PendingIntent, allowing the
+     * caller to be notified when the send has completed.
+     *
+     * @param code Result code to supply back to the PendingIntent's target.
+     * @param onFinished The object to call back on when the send has
+     * completed, or null for no callback.
+     * @param handler Handler identifying the thread on which the callback
+     * should happen.  If null, the callback will happen from the thread
+     * pool of the process.
+     *
+     * @see #send(Context, int, Intent, android.app.PendingIntent.OnFinished, Handler)
+     *
+     * @throws CanceledException Throws CanceledException if the PendingIntent
+     * is no longer allowing more intents to be sent through it.
+     */
+    public void send(int code, OnFinished onFinished, Handler handler)
+            throws CanceledException {
+        send(null, code, null, onFinished, handler);
+    }
+
+    /**
+     * Perform the operation associated with this PendingIntent, allowing the
+     * caller to specify information about the Intent to use and be notified
+     * when the send has completed.
+     *
+     * <p>For the intent parameter, a PendingIntent
+     * often has restrictions on which fields can be supplied here, based on
+     * how the PendingIntent was retrieved in {@link #getActivity},
+     * {@link #getBroadcast}, or {@link #getService}.
+     *
+     * @param context The Context of the caller.  This may be null if
+     * <var>intent</var> is also null.
+     * @param code Result code to supply back to the PendingIntent's target.
+     * @param intent Additional Intent data.  See {@link Intent#fillIn
+     * Intent.fillIn()} for information on how this is applied to the
+     * original Intent.  Use null to not modify the original Intent.
+     * @param onFinished The object to call back on when the send has
+     * completed, or null for no callback.
+     * @param handler Handler identifying the thread on which the callback
+     * should happen.  If null, the callback will happen from the thread
+     * pool of the process.
+     *
+     * @see #send()
+     * @see #send(int)
+     * @see #send(Context, int, Intent)
+     * @see #send(int, android.app.PendingIntent.OnFinished, Handler)
+     *
+     * @throws CanceledException Throws CanceledException if the PendingIntent
+     * is no longer allowing more intents to be sent through it.
+     */
+    public void send(Context context, int code, Intent intent,
+            OnFinished onFinished, Handler handler) throws CanceledException {
+        try {
+            String resolvedType = intent != null ?
+                    intent.resolveTypeIfNeeded(context.getContentResolver())
+                    : null;
+            int res = mTarget.send(code, intent, resolvedType,
+                    onFinished != null
+                    ? new FinishedDispatcher(this, onFinished, handler)
+                    : null);
+            if (res < 0) {
+                throw new CanceledException();
+            }
+        } catch (RemoteException e) {
+            throw new CanceledException(e);
+        }
+    }
+
+    /**
+     * Return the package name of the application that created this
+     * PendingIntent, that is the identity under which you will actually be
+     * sending the Intent.  The returned string is supplied by the system, so
+     * that an application can not spoof its package.
+     *
+     * @return The package name of the PendingIntent, or null if there is
+     * none associated with it.
+     */
+    public String getTargetPackage() {
+        try {
+            return ActivityManagerNative.getDefault()
+                .getPackageForIntentSender(mTarget);
+        } catch (RemoteException e) {
+            // Should never happen.
+            return null;
+        }
+    }
+
+    /**
+     * Comparison operator on two PendingIntent objects, such that true
+     * is returned then they both represent the same operation from the
+     * same package.  This allows you to use {@link #getActivity},
+     * {@link #getBroadcast}, or {@link #getService} multiple times (even
+     * across a process being killed), resulting in different PendingIntent
+     * objects but whose equals() method identifies them as being the same
+     * operation.
+     */
+    @Override
+    public boolean equals(Object otherObj) {
+        if (otherObj instanceof PendingIntent) {
+            return mTarget.asBinder().equals(((PendingIntent)otherObj)
+                    .mTarget.asBinder());
+        }
+        return false;
+    }
+
+    @Override
+    public int hashCode() {
+        return mTarget.asBinder().hashCode();
+    }
+
+    @Override
+    public String toString() {
+        return "PendingIntent{"
+                + Integer.toHexString(System.identityHashCode(this))
+                + " target " + (mTarget != null ? mTarget.asBinder() : null) + "}";
+    }
+    
+    public int describeContents() {
+        return 0;
+    }
+
+    public void writeToParcel(Parcel out, int flags) {
+        out.writeStrongBinder(mTarget.asBinder());
+    }
+
+    public static final Parcelable.Creator<PendingIntent> CREATOR
+            = new Parcelable.Creator<PendingIntent>() {
+        public PendingIntent createFromParcel(Parcel in) {
+            IBinder target = in.readStrongBinder();
+            return target != null ? new PendingIntent(target) : null;
+        }
+
+        public PendingIntent[] newArray(int size) {
+            return new PendingIntent[size];
+        }
+    };
+
+    /**
+     * Convenience function for writing either a PendingIntent or null pointer to
+     * a Parcel.  You must use this with {@link #readPendingIntentOrNullFromParcel}
+     * for later reading it.
+     *
+     * @param sender The PendingIntent to write, or null.
+     * @param out Where to write the PendingIntent.
+     */
+    public static void writePendingIntentOrNullToParcel(PendingIntent sender,
+            Parcel out) {
+        out.writeStrongBinder(sender != null ? sender.mTarget.asBinder()
+                : null);
+    }
+
+    /**
+     * Convenience function for reading either a Messenger or null pointer from
+     * a Parcel.  You must have previously written the Messenger with
+     * {@link #writePendingIntentOrNullToParcel}.
+     *
+     * @param in The Parcel containing the written Messenger.
+     *
+     * @return Returns the Messenger read from the Parcel, or null if null had
+     * been written.
+     */
+    public static PendingIntent readPendingIntentOrNullFromParcel(Parcel in) {
+        IBinder b = in.readStrongBinder();
+        return b != null ? new PendingIntent(b) : null;
+    }
+
+    /*package*/ PendingIntent(IIntentSender target) {
+        mTarget = target;
+    }
+
+    /*package*/ PendingIntent(IBinder target) {
+        mTarget = IIntentSender.Stub.asInterface(target);
+    }
+
+    /*package*/ IIntentSender getTarget() {
+        return mTarget;
+    }
+}
diff --git a/core/java/android/app/ProgressDialog.java b/core/java/android/app/ProgressDialog.java
new file mode 100644
index 0000000..c87e398
--- /dev/null
+++ b/core/java/android/app/ProgressDialog.java
@@ -0,0 +1,301 @@
+/*
+ * Copyright (C) 2007 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 android.app;
+
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.ProgressBar;
+import android.widget.TextView;
+
+import com.android.internal.R;
+
+import java.text.NumberFormat;
+
+/**
+ * <p>A dialog showing a progress indicator and an optional text message or view.
+ * Only a text message or a view can be used at the same time.</p>
+ * <p>The dialog can be made cancelable on back key press.</p>
+ * <p>The progress range is 0..10000.</p>
+ */
+public class ProgressDialog extends AlertDialog {
+    
+    /** Creates a ProgressDialog with a ciruclar, spinning progress
+     * bar. This is the default.
+     */
+    public static final int STYLE_SPINNER = 0;
+    
+    /** Creates a ProgressDialog with a horizontal progress bar.
+     */
+    public static final int STYLE_HORIZONTAL = 1;
+    
+    private ProgressBar mProgress;
+    private TextView mMessageView;
+    
+    private int mProgressStyle = STYLE_SPINNER;
+    private TextView mProgressNumber;
+    private TextView mProgressPercent;
+    private NumberFormat mProgressPercentFormat;
+    
+    private int mMax;
+    private int mProgressVal;
+    private int mSecondaryProgressVal;
+    private int mIncrementBy;
+    private int mIncrementSecondaryBy;
+    private Drawable mProgressDrawable;
+    private Drawable mIndeterminateDrawable;
+    private CharSequence mMessage;
+    private boolean mIndeterminate;
+    
+    private boolean mHasStarted;
+    private Handler mViewUpdateHandler;
+    
+    public ProgressDialog(Context context) {
+        this(context, com.android.internal.R.style.Theme_Dialog_Alert);
+    }
+
+    public ProgressDialog(Context context, int theme) {
+        super(context, theme);
+    }
+
+    public static ProgressDialog show(Context context, CharSequence title,
+            CharSequence message) {
+        return show(context, title, message, false);
+    }
+
+    public static ProgressDialog show(Context context, CharSequence title,
+            CharSequence message, boolean indeterminate) {
+        return show(context, title, message, indeterminate, false, null);
+    }
+
+    public static ProgressDialog show(Context context, CharSequence title,
+            CharSequence message, boolean indeterminate, boolean cancelable) {
+        return show(context, title, message, indeterminate, cancelable, null);
+    }
+
+    public static ProgressDialog show(Context context, CharSequence title,
+            CharSequence message, boolean indeterminate,
+            boolean cancelable, OnCancelListener cancelListener) {
+        ProgressDialog dialog = new ProgressDialog(context);
+        dialog.setTitle(title);
+        dialog.setMessage(message);
+        dialog.setIndeterminate(indeterminate);
+        dialog.setCancelable(cancelable);
+        dialog.setOnCancelListener(cancelListener);
+        dialog.show();
+        return dialog;
+    }
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        LayoutInflater inflater = LayoutInflater.from(mContext);
+        if (mProgressStyle == STYLE_HORIZONTAL) {
+            
+            /* Use a separate handler to update the text views as they
+             * must be updated on the same thread that created them.
+             */
+            mViewUpdateHandler = new Handler() {
+                @Override
+                public void handleMessage(Message msg) {
+                    super.handleMessage(msg);
+                    
+                    /* Update the number and percent */
+                    int progress = mProgress.getProgress();
+                    int max = mProgress.getMax();
+                    double percent = (double) progress / (double) max;
+                    mProgressNumber.setText(progress + "/" + max);
+                    mProgressPercent.setText(mProgressPercentFormat.format(percent));
+                }
+            };
+            View view = inflater.inflate(R.layout.alert_dialog_progress, null);
+            mProgress = (ProgressBar) view.findViewById(R.id.progress);
+            mProgressNumber = (TextView) view.findViewById(R.id.progress_number);
+            mProgressPercent = (TextView) view.findViewById(R.id.progress_percent);
+            mProgressPercentFormat = NumberFormat.getPercentInstance();
+            mProgressPercentFormat.setMaximumFractionDigits(0);
+            setView(view);
+        } else {
+            View view = inflater.inflate(R.layout.progress_dialog, null);
+            mProgress = (ProgressBar) view.findViewById(R.id.progress);
+            mMessageView = (TextView) view.findViewById(R.id.message);
+            setView(view);
+        }
+        if (mMax > 0) {
+            setMax(mMax);
+        }
+        if (mProgressVal > 0) {
+            setProgress(mProgressVal);
+        }
+        if (mSecondaryProgressVal > 0) {
+            setSecondaryProgress(mSecondaryProgressVal);
+        }
+        if (mIncrementBy > 0) {
+            incrementProgressBy(mIncrementBy);
+        }
+        if (mIncrementSecondaryBy > 0) {
+            incrementSecondaryProgressBy(mIncrementSecondaryBy);
+        }
+        if (mProgressDrawable != null) {
+            setProgressDrawable(mProgressDrawable);
+        }
+        if (mIndeterminateDrawable != null) {
+            setIndeterminateDrawable(mIndeterminateDrawable);
+        }
+        if (mMessage != null) {
+            setMessage(mMessage);
+        }
+        setIndeterminate(mIndeterminate);
+        onProgressChanged();
+        super.onCreate(savedInstanceState);
+    }
+    
+    @Override
+    public void onStart() {
+        super.onStart();
+        mHasStarted = true;
+    }
+    
+    @Override
+    protected void onStop() {
+        super.onStop();
+        mHasStarted = false;
+    }
+
+    public void setProgress(int value) {
+        if (mHasStarted) {
+            mProgress.setProgress(value);
+            onProgressChanged();
+        } else {
+            mProgressVal = value;
+        }
+    }
+
+    public void setSecondaryProgress(int secondaryProgress) {
+        if (mProgress != null) {
+            mProgress.setSecondaryProgress(secondaryProgress);
+            onProgressChanged();
+        } else {
+            mSecondaryProgressVal = secondaryProgress;
+        }
+    }
+
+    public int getProgress() {
+        if (mProgress != null) {
+            return mProgress.getProgress();
+        }
+        return mProgressVal;
+    }
+
+    public int getSecondaryProgress() {
+        if (mProgress != null) {
+            return mProgress.getSecondaryProgress();
+        }
+        return mSecondaryProgressVal;
+    }
+
+    public int getMax() {
+        if (mProgress != null) {
+            return mProgress.getMax();
+        }
+        return mMax;
+    }
+
+    public void setMax(int max) {
+        if (mProgress != null) {
+            mProgress.setMax(max);
+            onProgressChanged();
+        } else {
+            mMax = max;
+        }
+    }
+
+    public void incrementProgressBy(int diff) {
+        if (mProgress != null) {
+            mProgress.incrementProgressBy(diff);
+            onProgressChanged();
+        } else {
+            mIncrementBy += diff;
+        }
+    }
+
+    public void incrementSecondaryProgressBy(int diff) {
+        if (mProgress != null) {
+            mProgress.incrementSecondaryProgressBy(diff);
+            onProgressChanged();
+        } else {
+            mIncrementSecondaryBy += diff;
+        }
+    }
+
+    public void setProgressDrawable(Drawable d) {
+        if (mProgress != null) {
+            mProgress.setProgressDrawable(d);
+        } else {
+            mProgressDrawable = d;
+        }
+    }
+
+    public void setIndeterminateDrawable(Drawable d) {
+        if (mProgress != null) {
+            mProgress.setIndeterminateDrawable(d);
+        } else {
+            mIndeterminateDrawable = d;
+        }
+    }
+
+    public void setIndeterminate(boolean indeterminate) {
+        if (mProgress != null) {
+            mProgress.setIndeterminate(indeterminate);
+        } else {
+            mIndeterminate = indeterminate;
+        }
+    }
+
+    public boolean isIndeterminate() {
+        if (mProgress != null) {
+            return mProgress.isIndeterminate();
+        }
+        return mIndeterminate;
+    }
+    
+    @Override
+    public void setMessage(CharSequence message) {
+        if (mProgress != null) {
+            if (mProgressStyle == STYLE_HORIZONTAL) {
+                super.setMessage(message);
+            } else {
+                mMessageView.setText(message);
+            }
+        } else {
+            mMessage = message;
+        }
+    }
+    
+    public void setProgressStyle(int style) {
+        mProgressStyle = style;
+    }
+
+    private void onProgressChanged() {
+        if (mProgressStyle == STYLE_HORIZONTAL) {
+            mViewUpdateHandler.sendEmptyMessage(0);
+        }
+    }
+}
diff --git a/core/java/android/app/ResultInfo.java b/core/java/android/app/ResultInfo.java
new file mode 100644
index 0000000..48a0fc2
--- /dev/null
+++ b/core/java/android/app/ResultInfo.java
@@ -0,0 +1,86 @@
+/*
+ * 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 android.app;
+
+import android.content.Intent;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.Bundle;
+
+import java.util.Map;
+
+/**
+ * {@hide}
+ */
+public class ResultInfo implements Parcelable {
+    public final String mResultWho;
+    public final int mRequestCode;
+    public final int mResultCode;
+    public final Intent mData;
+
+    public ResultInfo(String resultWho, int requestCode, int resultCode,
+            Intent data) {
+        mResultWho = resultWho;
+        mRequestCode = requestCode;
+        mResultCode = resultCode;
+        mData = data;
+    }
+
+    public String toString() {
+        return "ResultInfo{who=" + mResultWho + ", request=" + mRequestCode
+            + ", result=" + mResultCode + ", data=" + mData + "}";
+    }
+
+    public int describeContents() {
+        return 0;
+    }
+
+    public void writeToParcel(Parcel out, int flags) {
+        out.writeString(mResultWho);
+        out.writeInt(mRequestCode);
+        out.writeInt(mResultCode);
+        if (mData != null) {
+            out.writeInt(1);
+            mData.writeToParcel(out, 0);
+        } else {
+            out.writeInt(0);
+        }
+    }
+
+    public static final Parcelable.Creator<ResultInfo> CREATOR
+            = new Parcelable.Creator<ResultInfo>() {
+        public ResultInfo createFromParcel(Parcel in) {
+            return new ResultInfo(in);
+        }
+
+        public ResultInfo[] newArray(int size) {
+            return new ResultInfo[size];
+        }
+    };
+
+    public ResultInfo(Parcel in) {
+        mResultWho = in.readString();
+        mRequestCode = in.readInt();
+        mResultCode = in.readInt();
+        if (in.readInt() != 0) {
+            mData = Intent.CREATOR.createFromParcel(in);
+        } else {
+            mData = null;
+        }
+    }
+}
diff --git a/core/java/android/app/SearchDialog.java b/core/java/android/app/SearchDialog.java
new file mode 100644
index 0000000..d447eb2
--- /dev/null
+++ b/core/java/android/app/SearchDialog.java
@@ -0,0 +1,1593 @@
+/*
+ * 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 android.app;
+
+import android.content.ActivityNotFoundException;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.content.res.Resources.NotFoundException;
+import android.database.Cursor;
+import android.graphics.drawable.Drawable;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.SystemClock;
+import android.server.search.SearchableInfo;
+import android.speech.RecognizerIntent;
+import android.text.Editable;
+import android.text.InputType;
+import android.text.TextUtils;
+import android.text.TextWatcher;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.Gravity;
+import android.view.KeyEvent;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.Window;
+import android.view.WindowManager;
+import android.widget.AdapterView;
+import android.widget.AutoCompleteTextView;
+import android.widget.Button;
+import android.widget.CursorAdapter;
+import android.widget.ImageButton;
+import android.widget.ImageView;
+import android.widget.ListView;
+import android.widget.SimpleCursorAdapter;
+import android.widget.TextView;
+import android.widget.WrapperListAdapter;
+import android.widget.AdapterView.OnItemClickListener;
+import android.widget.AdapterView.OnItemSelectedListener;
+
+import java.lang.ref.WeakReference;
+import java.util.concurrent.atomic.AtomicLong;
+
+/**
+ * In-application-process implementation of Search Bar.  This is still controlled by the 
+ * SearchManager, but it runs in the current activity's process to keep things lighter weight.
+ * 
+ * @hide
+ */
+public class SearchDialog extends Dialog implements OnItemClickListener, OnItemSelectedListener {
+
+    // Debugging support
+    final static String LOG_TAG = "SearchDialog";
+    private static final int DBG_LOG_TIMING = 0;
+    final static int DBG_JAM_THREADING = 0;
+
+    // interaction with runtime
+    IntentFilter mCloseDialogsFilter;
+    IntentFilter mPackageFilter;
+    
+    private static final String INSTANCE_KEY_COMPONENT = "comp";
+    private static final String INSTANCE_KEY_APPDATA = "data";
+    private static final String INSTANCE_KEY_GLOBALSEARCH = "glob";
+    private static final String INSTANCE_KEY_DISPLAY_QUERY = "dQry";
+    private static final String INSTANCE_KEY_DISPLAY_SEL_START = "sel1";
+    private static final String INSTANCE_KEY_DISPLAY_SEL_END = "sel2";
+    private static final String INSTANCE_KEY_USER_QUERY = "uQry";
+    private static final String INSTANCE_KEY_SUGGESTION_QUERY = "sQry";
+    private static final String INSTANCE_KEY_SELECTED_ELEMENT = "slEl";
+    private static final int INSTANCE_SELECTED_BUTTON = -2;
+    private static final int INSTANCE_SELECTED_QUERY = -1;
+
+    // views & widgets
+    private TextView mBadgeLabel;
+    private AutoCompleteTextView mSearchTextField;
+    private Button mGoButton;
+    private ImageButton mVoiceButton;
+
+    // interaction with searchable application
+    private ComponentName mLaunchComponent;
+    private Bundle mAppSearchData;
+    private boolean mGlobalSearchMode;
+    private Context mActivityContext;
+
+    // interaction with the search manager service
+    private SearchableInfo mSearchable;
+    
+    // support for suggestions 
+    private String mUserQuery = null;
+    private int mUserQuerySelStart;
+    private int mUserQuerySelEnd;
+    private boolean mLeaveJammedQueryOnRefocus = false;
+    private String mPreviousSuggestionQuery = null;
+    private int mPresetSelection = -1;
+    private String mSuggestionAction = null;
+    private Uri mSuggestionData = null;
+    private String mSuggestionQuery = null;
+    
+    // For voice searching
+    private Intent mVoiceWebSearchIntent;
+    private Intent mVoiceAppSearchIntent;
+
+    // support for AutoCompleteTextView suggestions display
+    private SuggestionsAdapter mSuggestionsAdapter;
+
+
+    /**
+     * Constructor - fires it up and makes it look like the search UI.
+     * 
+     * @param context Application Context we can use for system acess
+     */
+    public SearchDialog(Context context) {
+        super(context, com.android.internal.R.style.Theme_SearchBar);
+    }
+
+    /**
+     * We create the search dialog just once, and it stays around (hidden)
+     * until activated by the user.
+     */
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        Window theWindow = getWindow();
+        theWindow.setGravity(Gravity.TOP|Gravity.FILL_HORIZONTAL);
+
+        setContentView(com.android.internal.R.layout.search_bar);
+
+        theWindow.setLayout(ViewGroup.LayoutParams.FILL_PARENT,
+                ViewGroup.LayoutParams.WRAP_CONTENT);
+        WindowManager.LayoutParams lp = theWindow.getAttributes();
+        lp.setTitle("Search Dialog");
+        lp.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE;
+        theWindow.setAttributes(lp);
+
+        // get the view elements for local access
+        mBadgeLabel = (TextView) findViewById(com.android.internal.R.id.search_badge);
+        mSearchTextField = (AutoCompleteTextView) 
+                findViewById(com.android.internal.R.id.search_src_text);
+        mGoButton = (Button) findViewById(com.android.internal.R.id.search_go_btn);
+        mVoiceButton = (ImageButton) findViewById(com.android.internal.R.id.search_voice_btn);
+        
+        // attach listeners
+        mSearchTextField.addTextChangedListener(mTextWatcher);
+        mSearchTextField.setOnKeyListener(mTextKeyListener);
+        mGoButton.setOnClickListener(mGoButtonClickListener);
+        mGoButton.setOnKeyListener(mButtonsKeyListener);
+        mVoiceButton.setOnClickListener(mVoiceButtonClickListener);
+        mVoiceButton.setOnKeyListener(mButtonsKeyListener);
+
+        // pre-hide all the extraneous elements
+        mBadgeLabel.setVisibility(View.GONE);
+
+        // Additional adjustments to make Dialog work for Search
+
+        // Touching outside of the search dialog will dismiss it 
+        setCanceledOnTouchOutside(true);
+        
+        // Set up broadcast filters
+        mCloseDialogsFilter = new IntentFilter(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
+        mPackageFilter = new IntentFilter();
+        mPackageFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
+        mPackageFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
+        mPackageFilter.addAction(Intent.ACTION_PACKAGE_CHANGED);
+        mPackageFilter.addDataScheme("package");
+        
+        // Save voice intent for later queries/launching
+        mVoiceWebSearchIntent = new Intent(RecognizerIntent.ACTION_WEB_SEARCH);
+        mVoiceWebSearchIntent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL,
+                RecognizerIntent.LANGUAGE_MODEL_WEB_SEARCH);
+        
+        mVoiceAppSearchIntent = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH);
+    }
+
+    /**
+     * Set up the search dialog
+     * 
+     * @param Returns true if search dialog launched, false if not
+     */
+    public boolean show(String initialQuery, boolean selectInitialQuery,
+            ComponentName componentName, Bundle appSearchData, boolean globalSearch) {
+        if (isShowing()) {
+            // race condition - already showing but not handling events yet.
+            // in this case, just discard the "show" request
+            return true;
+        }
+
+        // Get searchable info from search manager and use to set up other elements of UI
+        // Do this first so we can get out quickly if there's nothing to search
+        ISearchManager sms;
+        sms = ISearchManager.Stub.asInterface(ServiceManager.getService(Context.SEARCH_SERVICE));
+        try {
+            mSearchable = sms.getSearchableInfo(componentName, globalSearch);
+        } catch (RemoteException e) {
+            mSearchable = null;
+        }
+        if (mSearchable == null) {
+            // unfortunately, we can't log here.  it would be logspam every time the user
+            // clicks the "search" key on a non-search app
+            return false;
+        }
+        
+        // OK, we're going to show ourselves
+        super.show();
+
+        setupSearchableInfo();
+        
+        mLaunchComponent = componentName;
+        mAppSearchData = appSearchData;
+        mGlobalSearchMode = globalSearch;
+
+        // receive broadcasts
+        getContext().registerReceiver(mBroadcastReceiver, mCloseDialogsFilter);
+        getContext().registerReceiver(mBroadcastReceiver, mPackageFilter);
+        
+        // configure the autocomplete aspects of the input box
+        mSearchTextField.setOnItemClickListener(this);
+        mSearchTextField.setOnItemSelectedListener(this);
+
+        // This conversion is necessary to force a preload of the EditText and thus force
+        // suggestions to be presented (even for an empty query)
+        if (initialQuery == null) {
+            initialQuery = "";     // This forces the preload to happen, triggering suggestions
+        }
+
+        // attach the suggestions adapter, if suggestions are available
+        // The existence of a suggestions authority is the proxy for "suggestions available here"
+        if (mSearchable.getSuggestAuthority() == null) {
+            mSuggestionsAdapter = null;
+            mSearchTextField.setAdapter(mSuggestionsAdapter);
+            mSearchTextField.setText(initialQuery);
+        } else {
+            mSuggestionsAdapter = new SuggestionsAdapter(getContext(), mSearchable);
+            mSearchTextField.setAdapter(mSuggestionsAdapter);
+
+            // finally, load the user's initial text (which may trigger suggestions)
+            mSuggestionsAdapter.setNonUserQuery(false);
+            mSearchTextField.setText(initialQuery);
+        }
+        
+        if (selectInitialQuery) {
+            mSearchTextField.selectAll();
+        } else {
+            mSearchTextField.setSelection(initialQuery.length());
+        }
+        return true;
+    }
+
+    /**
+     * The default show() for this Dialog is not supported.
+     */
+    @Override
+    public void show() {
+        return;
+    }
+
+    /**
+     * The search dialog is being dismissed, so handle all of the local shutdown operations.
+     * 
+     * This function is designed to be idempotent so that dismiss() can be safely called at any time
+     * (even if already closed) and more likely to really dump any memory.  No leaks!
+     */
+    @Override
+    public void onStop() {
+        super.onStop();
+        
+        setOnCancelListener(null);
+        setOnDismissListener(null);
+        
+        // stop receiving broadcasts (throws exception if none registered)
+        try {
+            getContext().unregisterReceiver(mBroadcastReceiver);
+        } catch (RuntimeException e) {
+            // This is OK - it just means we didn't have any registered
+        }
+        
+        // close any leftover cursor
+        if (mSuggestionsAdapter != null) {
+            mSuggestionsAdapter.changeCursor(null);
+        }
+        
+        // dump extra memory we're hanging on to
+        mLaunchComponent = null;
+        mAppSearchData = null;
+        mSearchable = null;
+        mSuggestionAction = null;
+        mSuggestionData = null;
+        mSuggestionQuery = null;
+        mActivityContext = null;
+        mPreviousSuggestionQuery = null;
+        mUserQuery = null;
+    }
+    
+    /**
+     * Save the minimal set of data necessary to recreate the search
+     * 
+     * @return A bundle with the state of the dialog.
+     */
+    @Override
+    public Bundle onSaveInstanceState() {
+        Bundle bundle = new Bundle();
+        
+        // setup info so I can recreate this particular search       
+        bundle.putParcelable(INSTANCE_KEY_COMPONENT, mLaunchComponent);
+        bundle.putBundle(INSTANCE_KEY_APPDATA, mAppSearchData);
+        bundle.putBoolean(INSTANCE_KEY_GLOBALSEARCH, mGlobalSearchMode);
+        
+        // UI state
+        bundle.putString(INSTANCE_KEY_DISPLAY_QUERY, mSearchTextField.getText().toString());
+        bundle.putInt(INSTANCE_KEY_DISPLAY_SEL_START, mSearchTextField.getSelectionStart());
+        bundle.putInt(INSTANCE_KEY_DISPLAY_SEL_END, mSearchTextField.getSelectionEnd());
+        bundle.putString(INSTANCE_KEY_USER_QUERY, mUserQuery);
+        bundle.putString(INSTANCE_KEY_SUGGESTION_QUERY, mPreviousSuggestionQuery);
+        
+        int selectedElement = INSTANCE_SELECTED_QUERY;
+        if (mGoButton.isFocused()) {
+            selectedElement = INSTANCE_SELECTED_BUTTON;
+        } else if (mSearchTextField.isPopupShowing()) {
+            selectedElement = 0; // TODO mSearchTextField.getListSelection()    // 0..n
+        }
+        bundle.putInt(INSTANCE_KEY_SELECTED_ELEMENT, selectedElement);
+        
+        return bundle;
+    }
+
+    /**
+     * Restore the state of the dialog from a previously saved bundle.
+     *
+     * @param savedInstanceState The state of the dialog previously saved by
+     *     {@link #onSaveInstanceState()}.
+     */
+    @Override
+    public void onRestoreInstanceState(Bundle savedInstanceState) {
+        // Get the launch info
+        ComponentName launchComponent = savedInstanceState.getParcelable(INSTANCE_KEY_COMPONENT);
+        Bundle appSearchData = savedInstanceState.getBundle(INSTANCE_KEY_APPDATA);
+        boolean globalSearch = savedInstanceState.getBoolean(INSTANCE_KEY_GLOBALSEARCH);
+        
+        // get the UI state
+        String displayQuery = savedInstanceState.getString(INSTANCE_KEY_DISPLAY_QUERY);
+        int querySelStart = savedInstanceState.getInt(INSTANCE_KEY_DISPLAY_SEL_START, -1);
+        int querySelEnd = savedInstanceState.getInt(INSTANCE_KEY_DISPLAY_SEL_END, -1);
+        String userQuery = savedInstanceState.getString(INSTANCE_KEY_USER_QUERY);
+        int selectedElement = savedInstanceState.getInt(INSTANCE_KEY_SELECTED_ELEMENT);
+        String suggestionQuery = savedInstanceState.getString(INSTANCE_KEY_SUGGESTION_QUERY);
+        
+        // show the dialog.  skip any show/hide animation, we want to go fast.
+        // send the text that actually generates the suggestions here;  we'll replace the display
+        // text as necessary in a moment.
+        if (!show(suggestionQuery, false, launchComponent, appSearchData, globalSearch)) {
+            // for some reason, we couldn't re-instantiate
+            return;
+        }
+        
+        if (mSuggestionsAdapter != null) {
+            mSuggestionsAdapter.setNonUserQuery(true);
+        }
+        mSearchTextField.setText(displayQuery);
+        // TODO because the new query is (not) processed in another thread, we can't just
+        // take away this flag (yet).  The better solution here is going to require a new API
+        // in AutoCompleteTextView which allows us to change the text w/o changing the suggestions.
+//      mSuggestionsAdapter.setNonUserQuery(false);
+        
+        // clean up the selection state
+        switch (selectedElement) {
+        case INSTANCE_SELECTED_BUTTON:
+            mGoButton.setEnabled(true);
+            mGoButton.setFocusable(true);
+            mGoButton.requestFocus();
+            break;
+        case INSTANCE_SELECTED_QUERY:
+            if (querySelStart >= 0 && querySelEnd >= 0) {
+                mSearchTextField.requestFocus();
+                mSearchTextField.setSelection(querySelStart, querySelEnd);
+            }
+            break;
+        default:
+            // defer selecting a list element until suggestion list appears
+            mPresetSelection = selectedElement;
+            // TODO mSearchTextField.setListSelection(selectedElement)
+            break;
+        }
+    }
+    
+    /**
+     * Hook for updating layout on a rotation
+     * 
+     */
+    public void onConfigurationChanged(Configuration newConfig) {
+        if (isShowing()) {
+            // Redraw (resources may have changed)
+            updateSearchButton();
+            updateSearchBadge();
+            updateQueryHint();
+        } 
+    }
+
+    /**
+     * Use SearchableInfo record (from search manager service) to preconfigure the UI in various
+     * ways.
+     */
+    private void setupSearchableInfo() {
+        if (mSearchable != null) {
+            mActivityContext = mSearchable.getActivityContext(getContext());
+            
+            updateSearchButton();
+            updateSearchBadge();
+            updateQueryHint();
+            updateVoiceButton();
+            
+            // In order to properly configure the input method (if one is being used), we
+            // need to let it know if we'll be providing suggestions.  Although it would be
+            // difficult/expensive to know if every last detail has been configured properly, we 
+            // can at least see if a suggestions provider has been configured, and use that
+            // as our trigger.
+            int inputType = mSearchable.getInputType();
+            // We only touch this if the input type is set up for text (which it almost certainly
+            // should be, in the case of search!)
+            if ((inputType & InputType.TYPE_MASK_CLASS) == InputType.TYPE_CLASS_TEXT) {
+                // The existence of a suggestions authority is the proxy for "suggestions 
+                // are available here"
+                inputType &= ~InputType.TYPE_TEXT_FLAG_AUTO_COMPLETE;
+                if (mSearchable.getSuggestAuthority() != null) {
+                    inputType |= InputType.TYPE_TEXT_FLAG_AUTO_COMPLETE;
+                }
+            }
+            mSearchTextField.setInputType(inputType);
+            mSearchTextField.setImeOptions(mSearchable.getImeOptions());
+        }
+    }
+
+    /**
+     * The list of installed packages has just changed.  This means that our current context
+     * may no longer be valid.  This would only happen if a package is installed/removed exactly
+     * when the search bar is open.  So for now we're just going to close the search
+     * bar.  
+     * 
+     * Anything fancier would require some checks to see if the user's context was still valid.
+     * Which would be messier.
+     */
+    public void onPackageListChange() {
+        cancel();
+    }
+    
+    /**    
+     * Update the text in the search button.  Note: This is deprecated functionality, for 
+     * 1.0 compatibility only.
+     */  
+    private void updateSearchButton() { 
+        String textLabel = null;
+        Drawable iconLabel = null;
+        int textId = mSearchable.getSearchButtonText(); 
+        if (textId != 0) {
+            textLabel = mActivityContext.getResources().getString(textId);  
+        } else {
+            iconLabel = getContext().getResources().
+                    getDrawable(com.android.internal.R.drawable.ic_btn_search);
+        }
+        mGoButton.setText(textLabel);  
+        mGoButton.setCompoundDrawablesWithIntrinsicBounds(iconLabel, null, null, null);
+    }
+    
+    /**
+     * Setup the search "Badge" if request by mode flags.
+     */
+    private void updateSearchBadge() {
+        // assume both hidden
+        int visibility = View.GONE;
+        Drawable icon = null;
+        String text = null;
+        
+        // optionally show one or the other.
+        if (mSearchable.mBadgeIcon) {
+            icon = mActivityContext.getResources().getDrawable(mSearchable.getIconId());
+            visibility = View.VISIBLE;
+        } else if (mSearchable.mBadgeLabel) {
+            text = mActivityContext.getResources().getText(mSearchable.getLabelId()).toString();
+            visibility = View.VISIBLE;
+        }
+        
+        mBadgeLabel.setCompoundDrawablesWithIntrinsicBounds(icon, null, null, null);
+        mBadgeLabel.setText(text);
+        mBadgeLabel.setVisibility(visibility);
+    }
+
+    /**
+     * Update the hint in the query text field.
+     */
+    private void updateQueryHint() {
+        if (isShowing()) {
+            String hint = null;
+            if (mSearchable != null) {
+                int hintId = mSearchable.getHintId();
+                if (hintId != 0) {
+                    hint = mActivityContext.getString(hintId);
+                }
+            }
+            mSearchTextField.setHint(hint);
+        }
+    }
+
+    /**
+     * Update the visibility of the voice button.  There are actually two voice search modes, 
+     * either of which will activate the button.
+     */
+    private void updateVoiceButton() {
+        int visibility = View.GONE;
+        if (mSearchable.getVoiceSearchEnabled()) {
+            Intent testIntent = null;
+            if (mSearchable.getVoiceSearchLaunchWebSearch()) {
+                testIntent = mVoiceWebSearchIntent;
+            } else if (mSearchable.getVoiceSearchLaunchRecognizer()) {
+                testIntent = mVoiceAppSearchIntent;
+            }      
+            if (testIntent != null) {
+                ResolveInfo ri = getContext().getPackageManager().
+                        resolveActivity(testIntent, PackageManager.MATCH_DEFAULT_ONLY);
+                if (ri != null) {
+                    visibility = View.VISIBLE;
+                }
+            }
+        }
+        mVoiceButton.setVisibility(visibility);
+    }
+    
+    /**
+     * Listeners of various types
+     */
+
+    /**
+     * Dialog's OnKeyListener implements various search-specific functionality
+     *
+     * @param keyCode This is the keycode of the typed key, and is the same value as
+     * found in the KeyEvent parameter.
+     * @param event The complete event record for the typed key
+     *
+     * @return Return true if the event was handled here, or false if not.
+     */
+    @Override
+    public boolean onKeyDown(int keyCode, KeyEvent event) {
+        switch (keyCode) {
+        case KeyEvent.KEYCODE_BACK:
+            cancel();
+            return true;
+        case KeyEvent.KEYCODE_SEARCH:
+            if (TextUtils.getTrimmedLength(mSearchTextField.getText()) != 0) {
+                launchQuerySearch(KeyEvent.KEYCODE_UNKNOWN, null);
+            } else {
+                cancel();
+            }
+            return true;
+        default:
+            SearchableInfo.ActionKeyInfo actionKey = mSearchable.findActionKey(keyCode);
+            if ((actionKey != null) && (actionKey.mQueryActionMsg != null)) {
+                launchQuerySearch(keyCode, actionKey.mQueryActionMsg);
+                return true;
+            }
+            break;
+        }
+        return false;
+    }
+
+    /**
+     * Callback to watch the textedit field for empty/non-empty
+     */
+    private TextWatcher mTextWatcher = new TextWatcher() {
+
+        public void beforeTextChanged(CharSequence s, int start, int
+                before, int after) { }
+
+        public void onTextChanged(CharSequence s, int start,
+                int before, int after) {
+            if (DBG_LOG_TIMING == 1) {
+                dbgLogTiming("onTextChanged()");
+            }
+            updateWidgetState();
+            // Only do suggestions if actually typed by user
+            if ((mSuggestionsAdapter != null) && !mSuggestionsAdapter.getNonUserQuery()) {
+                mPreviousSuggestionQuery = s.toString();
+                mUserQuery = mSearchTextField.getText().toString();
+                mUserQuerySelStart = mSearchTextField.getSelectionStart();
+                mUserQuerySelEnd = mSearchTextField.getSelectionEnd();
+            }
+        }
+
+        public void afterTextChanged(Editable s) { }
+    };
+
+    /**
+     * Enable/Disable the cancel button based on edit text state (any text?)
+     */
+    private void updateWidgetState() {
+        // enable the button if we have one or more non-space characters
+        boolean enabled =
+            TextUtils.getTrimmedLength(mSearchTextField.getText()) != 0;
+
+        mGoButton.setEnabled(enabled);
+        mGoButton.setFocusable(enabled);
+    }
+
+    private final static String[] ONE_LINE_FROM =       {SearchManager.SUGGEST_COLUMN_TEXT_1 };
+    private final static String[] ONE_LINE_ICONS_FROM = {SearchManager.SUGGEST_COLUMN_TEXT_1,
+                                                         SearchManager.SUGGEST_COLUMN_ICON_1,
+                                                         SearchManager.SUGGEST_COLUMN_ICON_2};
+    private final static String[] TWO_LINE_FROM =       {SearchManager.SUGGEST_COLUMN_TEXT_1,
+                                                         SearchManager.SUGGEST_COLUMN_TEXT_2 };
+    private final static String[] TWO_LINE_ICONS_FROM = {SearchManager.SUGGEST_COLUMN_TEXT_1,
+                                                         SearchManager.SUGGEST_COLUMN_TEXT_2,
+                                                         SearchManager.SUGGEST_COLUMN_ICON_1,
+                                                         SearchManager.SUGGEST_COLUMN_ICON_2 };
+    
+    private final static int[] ONE_LINE_TO =       {com.android.internal.R.id.text1};
+    private final static int[] ONE_LINE_ICONS_TO = {com.android.internal.R.id.text1,
+                                                    com.android.internal.R.id.icon1, 
+                                                    com.android.internal.R.id.icon2};
+    private final static int[] TWO_LINE_TO =       {com.android.internal.R.id.text1, 
+                                                    com.android.internal.R.id.text2};
+    private final static int[] TWO_LINE_ICONS_TO = {com.android.internal.R.id.text1, 
+                                                    com.android.internal.R.id.text2,
+                                                    com.android.internal.R.id.icon1, 
+                                                    com.android.internal.R.id.icon2};
+    
+    /**
+     * Safely retrieve the suggestions cursor adapter from the ListView
+     * 
+     * @param adapterView The ListView containing our adapter
+     * @result The CursorAdapter that we installed, or null if not set
+     */
+    private static CursorAdapter getSuggestionsAdapter(AdapterView<?> adapterView) {
+        CursorAdapter result = null;
+        if (adapterView != null) {
+            Object ad = adapterView.getAdapter();
+            if (ad instanceof CursorAdapter) {
+                result = (CursorAdapter) ad;
+            } else if (ad instanceof WrapperListAdapter) {
+                result = (CursorAdapter) ((WrapperListAdapter)ad).getWrappedAdapter();
+            }
+        }
+        return result;
+    }
+
+    /**
+     * React to typing in the GO search button by refocusing to EditText. 
+     * Continue typing the query.
+     */
+    View.OnKeyListener mButtonsKeyListener = new View.OnKeyListener() {
+        public boolean onKey(View v, int keyCode, KeyEvent event) {
+            // also guard against possible race conditions (late arrival after dismiss)
+            if (mSearchable != null) {
+                return refocusingKeyListener(v, keyCode, event);
+            }
+            return false;
+        }
+    };
+
+    /**
+     * React to a click in the GO button by launching a search.
+     */
+    View.OnClickListener mGoButtonClickListener = new View.OnClickListener() {
+        public void onClick(View v) {
+            // also guard against possible race conditions (late arrival after dismiss)
+            if (mSearchable != null) {
+                launchQuerySearch(KeyEvent.KEYCODE_UNKNOWN, null);
+            }
+        }
+    };
+    
+    /**
+     * React to a click in the voice search button.
+     */
+    View.OnClickListener mVoiceButtonClickListener = new View.OnClickListener() {
+        public void onClick(View v) {
+            try {
+                if (mSearchable.getVoiceSearchLaunchWebSearch()) {
+                    getContext().startActivity(mVoiceWebSearchIntent);
+                    dismiss();
+                } else if (mSearchable.getVoiceSearchLaunchRecognizer()) {
+                    Intent appSearchIntent = createVoiceAppSearchIntent(mVoiceAppSearchIntent);
+                    getContext().startActivity(appSearchIntent);
+                    dismiss();
+                }
+            } catch (ActivityNotFoundException e) {
+                // Should not happen, since we check the availability of
+                // voice search before showing the button. But just in case...
+                Log.w(LOG_TAG, "Could not find voice search activity");
+            }
+         }
+    };
+    
+    /**
+     * Create and return an Intent that can launch the voice search activity, perform a specific
+     * voice transcription, and forward the results to the searchable activity.
+     * 
+     * @param baseIntent The voice app search intent to start from
+     * @return A completely-configured intent ready to send to the voice search activity
+     */
+    private Intent createVoiceAppSearchIntent(Intent baseIntent) {
+        // create the necessary intent to set up a search-and-forward operation
+        // in the voice search system.   We have to keep the bundle separate,
+        // because it becomes immutable once it enters the PendingIntent
+        Intent queryIntent = new Intent(Intent.ACTION_SEARCH);
+        queryIntent.setComponent(mSearchable.mSearchActivity);
+        PendingIntent pending = PendingIntent.getActivity(
+                getContext(), 0, queryIntent, PendingIntent.FLAG_ONE_SHOT);
+        
+        // Now set up the bundle that will be inserted into the pending intent
+        // when it's time to do the search.  We always build it here (even if empty)
+        // because the voice search activity will always need to insert "QUERY" into
+        // it anyway.
+        Bundle queryExtras = new Bundle();
+        if (mAppSearchData != null) {
+            queryExtras.putBundle(SearchManager.APP_DATA, mAppSearchData);
+        }
+        
+        // Now build the intent to launch the voice search.  Add all necessary
+        // extras to launch the voice recognizer, and then all the necessary extras
+        // to forward the results to the searchable activity
+        Intent voiceIntent = new Intent(baseIntent);
+        
+        // Add all of the configuration options supplied by the searchable's metadata
+        String languageModel = RecognizerIntent.LANGUAGE_MODEL_FREE_FORM;
+        String prompt = null;
+        String language = null;
+        int maxResults = 1;
+        Resources resources = mActivityContext.getResources();
+        if (mSearchable.getVoiceLanguageModeId() != 0) {
+            languageModel = resources.getString(mSearchable.getVoiceLanguageModeId());
+        }
+        if (mSearchable.getVoicePromptTextId() != 0) {
+            prompt = resources.getString(mSearchable.getVoicePromptTextId());
+        }
+        if (mSearchable.getVoiceLanguageId() != 0) {
+            language = resources.getString(mSearchable.getVoiceLanguageId());
+        }
+        if (mSearchable.getVoiceMaxResults() != 0) {
+            maxResults = mSearchable.getVoiceMaxResults();
+        }
+        voiceIntent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL, languageModel);
+        voiceIntent.putExtra(RecognizerIntent.EXTRA_PROMPT, prompt);
+        voiceIntent.putExtra(RecognizerIntent.EXTRA_LANGUAGE, language);
+        voiceIntent.putExtra(RecognizerIntent.EXTRA_MAX_RESULTS, maxResults);
+        
+        // Add the values that configure forwarding the results
+        voiceIntent.putExtra(RecognizerIntent.EXTRA_RESULTS_PENDINGINTENT, pending);
+        voiceIntent.putExtra(RecognizerIntent.EXTRA_RESULTS_PENDINGINTENT_BUNDLE, queryExtras);
+        
+        return voiceIntent;
+    }
+
+    /**
+     * React to the user typing "enter" or other hardwired keys while typing in the search box.
+     * This handles these special keys while the edit box has focus.
+     */
+    View.OnKeyListener mTextKeyListener = new View.OnKeyListener() {
+        public boolean onKey(View v, int keyCode, KeyEvent event) {
+            if (keyCode == KeyEvent.KEYCODE_BACK) {
+                cancel();
+                return true;
+            }
+            // also guard against possible race conditions (late arrival after dismiss)
+            if (mSearchable != null && 
+                    TextUtils.getTrimmedLength(mSearchTextField.getText()) > 0) {
+                if (DBG_LOG_TIMING == 1) {
+                    dbgLogTiming("doTextKey()");
+                }
+                // dispatch "typing in the list" first
+                if (mSearchTextField.isPopupShowing() && 
+                        mSearchTextField.getListSelection() != ListView.INVALID_POSITION) {
+                     return onSuggestionsKey(v, keyCode, event);
+                }
+                // otherwise, dispatch an "edit view" key
+                switch (keyCode) {
+                case KeyEvent.KEYCODE_ENTER:
+                    if (event.getAction() == KeyEvent.ACTION_UP) {
+                        v.cancelLongPress();
+                        launchQuerySearch(KeyEvent.KEYCODE_UNKNOWN, null);                    
+                        return true;
+                    }
+                    break;
+                case KeyEvent.KEYCODE_DPAD_DOWN:                    
+                    // capture the EditText state, so we can restore the user entry later
+                    mUserQuery = mSearchTextField.getText().toString();
+                    mUserQuerySelStart = mSearchTextField.getSelectionStart();
+                    mUserQuerySelEnd = mSearchTextField.getSelectionEnd();
+                    // pass through - we're just watching here
+                    break;
+                default:
+                    if (event.getAction() == KeyEvent.ACTION_DOWN) {
+                        SearchableInfo.ActionKeyInfo actionKey = mSearchable.findActionKey(keyCode);
+                        if ((actionKey != null) && (actionKey.mQueryActionMsg != null)) {
+                            launchQuerySearch(keyCode, actionKey.mQueryActionMsg);
+                            return true;
+                        }
+                    }
+                    break;
+                }
+            }
+            return false;
+        }
+    };
+
+    /**
+     * React to the user typing while the suggestions are focused.  First, check for action
+     * keys.  If not handled, try refocusing regular characters into the EditText.  In this case,
+     * replace the query text (start typing fresh text).
+     */
+    private boolean onSuggestionsKey(View v, int keyCode, KeyEvent event) {
+        boolean handled = false;
+        // also guard against possible race conditions (late arrival after dismiss)
+        if (mSearchable != null) {
+            handled = doSuggestionsKey(v, keyCode, event);
+        }
+        return handled;
+    }
+    
+    /**
+     * Per UI design, we're going to "steer" any typed keystrokes back into the EditText
+     * box, even if the user has navigated the focus to the dropdown or to the GO button.
+     * 
+     * @param v The view into which the keystroke was typed
+     * @param keyCode keyCode of entered key
+     * @param event Full KeyEvent record of entered key
+     */
+    private boolean refocusingKeyListener(View v, int keyCode, KeyEvent event) {
+        boolean handled = false;
+
+        if (!event.isSystem() && 
+                (keyCode != KeyEvent.KEYCODE_DPAD_UP) &&
+                (keyCode != KeyEvent.KEYCODE_DPAD_DOWN) &&
+                (keyCode != KeyEvent.KEYCODE_DPAD_LEFT) &&
+                (keyCode != KeyEvent.KEYCODE_DPAD_RIGHT) &&
+                (keyCode != KeyEvent.KEYCODE_DPAD_CENTER)) {
+            // restore focus and give key to EditText ...
+            // but don't replace the user's query
+            mLeaveJammedQueryOnRefocus = true;
+            if (mSearchTextField.requestFocus()) {
+                handled = mSearchTextField.dispatchKeyEvent(event);
+            }
+            mLeaveJammedQueryOnRefocus = false;
+        }
+        return handled;
+    }
+    
+    /**
+     * Update query text based on transitions in and out of suggestions list.
+     */
+    /*
+     * TODO - figure out if this logic is required for the autocomplete text view version
+
+    OnFocusChangeListener mSuggestFocusListener = new OnFocusChangeListener() {
+        public void onFocusChange(View v, boolean hasFocus) {
+            // also guard against possible race conditions (late arrival after dismiss)
+            if (mSearchable == null) {
+                return;
+            }
+            // Update query text based on navigation in to/out of the suggestions list
+            if (hasFocus) {
+                // Entering the list view - record selection point from user's query
+                mUserQuery = mSearchTextField.getText().toString();
+                mUserQuerySelStart = mSearchTextField.getSelectionStart();
+                mUserQuerySelEnd = mSearchTextField.getSelectionEnd();
+                // then update the query to match the entered selection
+                jamSuggestionQuery(true, mSuggestionsList, 
+                                    mSuggestionsList.getSelectedItemPosition());
+            } else {
+                // Exiting the list view
+                
+                if (mSuggestionsList.getSelectedItemPosition() < 0) {
+                    // Direct exit - Leave new suggestion in place (do nothing)
+                } else {
+                    // Navigation exit - restore user's query text
+                    if (!mLeaveJammedQueryOnRefocus) {
+                        jamSuggestionQuery(false, null, -1);
+                    }
+                }
+            }
+
+        }
+    };
+    */
+    
+    /**
+     * This is the listener for the ACTION_CLOSE_SYSTEM_DIALOGS intent.  It's an indication that
+     * we should close ourselves immediately, in order to allow a higher-priority UI to take over
+     * (e.g. phone call received).
+     */
+    private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            String action = intent.getAction();
+            if (Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(action)) {
+                cancel();
+            } else if (Intent.ACTION_PACKAGE_ADDED.equals(action)
+                    || Intent.ACTION_PACKAGE_REMOVED.equals(action)
+                    || Intent.ACTION_PACKAGE_CHANGED.equals(action)) {
+                onPackageListChange();
+            }
+        }
+    };
+
+    /**
+     * Various ways to launch searches
+     */
+
+    /**
+     * React to the user clicking the "GO" button.  Hide the UI and launch a search.
+     *
+     * @param actionKey Pass a keycode if the launch was triggered by an action key.  Pass
+     * KeyEvent.KEYCODE_UNKNOWN for no actionKey code.
+     * @param actionMsg Pass the suggestion-provided message if the launch was triggered by an
+     * action key.  Pass null for no actionKey message.
+     */
+    private void launchQuerySearch(int actionKey, final String actionMsg)  {
+        final String query = mSearchTextField.getText().toString();
+        final Bundle appData = mAppSearchData;
+        final SearchableInfo si = mSearchable;      // cache briefly (dismiss() nulls it)
+        dismiss();
+        sendLaunchIntent(Intent.ACTION_SEARCH, null, query, appData, actionKey, actionMsg, si);
+    }
+
+    /**
+     * React to the user typing an action key while in the suggestions list
+     */
+    private boolean doSuggestionsKey(View v, int keyCode, KeyEvent event) {
+        // Exit early in case of race condition
+        if (mSuggestionsAdapter == null) {
+            return false;
+        }
+        if (event.getAction() == KeyEvent.ACTION_DOWN) {
+            if (DBG_LOG_TIMING == 1) {
+                dbgLogTiming("doSuggestionsKey()");
+            }
+            
+            // First, check for enter or search (both of which we'll treat as a "click")
+            if (keyCode == KeyEvent.KEYCODE_ENTER || keyCode == KeyEvent.KEYCODE_SEARCH) {
+                int position = mSearchTextField.getListSelection();
+                return launchSuggestion(mSuggestionsAdapter, position);
+            }
+            
+            // Next, check for left/right moves, which we use to "return" the user to the edit view
+            if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT || keyCode == KeyEvent.KEYCODE_DPAD_RIGHT) {
+                // give "focus" to text editor, but don't restore the user's original query
+                int selPoint = (keyCode == KeyEvent.KEYCODE_DPAD_LEFT) ? 
+                        0 : mSearchTextField.length();
+                mSearchTextField.setSelection(selPoint);
+                mSearchTextField.setListSelection(0);
+                mSearchTextField.clearListSelection();
+                return true;
+            }
+            
+            // Next, check for an "up and out" move
+            if (keyCode == KeyEvent.KEYCODE_DPAD_UP && 0 == mSearchTextField.getListSelection()) {
+                jamSuggestionQuery(false, null, -1);
+                // let ACTV complete the move
+                return false;
+            }
+            
+            // Next, check for an "action key"
+            SearchableInfo.ActionKeyInfo actionKey = mSearchable.findActionKey(keyCode);
+            if ((actionKey != null) && 
+                    ((actionKey.mSuggestActionMsg != null) || 
+                     (actionKey.mSuggestActionMsgColumn != null))) {
+                //   launch suggestion using action key column
+                int position = mSearchTextField.getListSelection();
+                if (position >= 0) {
+                    Cursor c = mSuggestionsAdapter.getCursor();
+                    if (c.moveToPosition(position)) {
+                        final String actionMsg = getActionKeyMessage(c, actionKey);
+                        if (actionMsg != null && (actionMsg.length() > 0)) {
+                            // shut down search bar and launch the activity
+                            // cache everything we need because dismiss releases mems
+                            setupSuggestionIntent(c, mSearchable);
+                            final String query = mSearchTextField.getText().toString();
+                            final Bundle appData =  mAppSearchData;
+                            SearchableInfo si = mSearchable;
+                            String suggestionAction = mSuggestionAction;
+                            Uri suggestionData = mSuggestionData;
+                            String suggestionQuery = mSuggestionQuery;
+                            dismiss();
+                            sendLaunchIntent(suggestionAction, suggestionData,
+                                    suggestionQuery, appData,
+                                             keyCode, actionMsg, si);
+                            return true;
+                        }
+                    }
+                }
+            }
+        }
+        return false;
+    }    
+
+    /**
+     * Set or reset the user query to follow the selections in the suggestions
+     * 
+     * @param jamQuery True means to set the query, false means to reset it to the user's choice
+     */
+    private void jamSuggestionQuery(boolean jamQuery, AdapterView<?> parent, int position) {
+        // quick check against race conditions
+        if (mSearchable == null) {
+            return;
+        }
+        
+        mSuggestionsAdapter.setNonUserQuery(true);       // disables any suggestions processing
+        if (jamQuery) {
+            CursorAdapter ca = getSuggestionsAdapter(parent);
+            Cursor c = ca.getCursor();
+            if (c.moveToPosition(position)) {
+                setupSuggestionIntent(c, mSearchable);
+                String jamText = null;
+
+                // Simple heuristic for selecting text with which to rewrite the query.
+                if (mSuggestionQuery != null) {
+                    jamText = mSuggestionQuery;
+                } else if (mSearchable.mQueryRewriteFromData && (mSuggestionData != null)) {
+                    jamText = mSuggestionData.toString();
+                } else if (mSearchable.mQueryRewriteFromText) {
+                    try {
+                        int column = c.getColumnIndexOrThrow(SearchManager.SUGGEST_COLUMN_TEXT_1);
+                        jamText = c.getString(column);
+                    } catch (RuntimeException e) {
+                        // no work here, jamText is null
+                    }
+                }
+                if (jamText != null) {
+                    mSearchTextField.setText(jamText);
+                    /* mSearchTextField.selectAll(); */ // this didn't work anyway in the old UI
+                    // TODO this is only needed in the model where we have a selection in the ACTV
+                    // and in the dropdown at the same time.
+                    mSearchTextField.setSelection(jamText.length());
+                }
+            }
+        } else {
+            // reset user query
+            mSearchTextField.setText(mUserQuery);
+            try {
+                mSearchTextField.setSelection(mUserQuerySelStart, mUserQuerySelEnd);
+            } catch (IndexOutOfBoundsException e) {
+                // In case of error, just select all
+                Log.e(LOG_TAG, "Caught IndexOutOfBoundsException while setting selection.  " +
+                        "start=" + mUserQuerySelStart + " end=" + mUserQuerySelEnd +
+                        " text=\"" + mUserQuery + "\"");
+                mSearchTextField.selectAll();
+            }
+        }
+        // TODO because the new query is (not) processed in another thread, we can't just
+        // take away this flag (yet).  The better solution here is going to require a new API
+        // in AutoCompleteTextView which allows us to change the text w/o changing the suggestions.
+//      mSuggestionsAdapter.setNonUserQuery(false);
+    }
+
+    /**
+     * Assemble a search intent and send it.
+     *
+     * @param action The intent to send, typically Intent.ACTION_SEARCH
+     * @param data The data for the intent
+     * @param query The user text entered (so far)
+     * @param appData The app data bundle (if supplied)
+     * @param actionKey If the intent was triggered by an action key, e.g. KEYCODE_CALL, it will
+     * be sent here.  Pass KeyEvent.KEYCODE_UNKNOWN for no actionKey code.
+     * @param actionMsg If the intent was triggered by an action key, e.g. KEYCODE_CALL, the
+     * corresponding tag message will be sent here.  Pass null for no actionKey message.
+     * @param si Reference to the current SearchableInfo.  Passed here so it can be used even after
+     * we've called dismiss(), which attempts to null mSearchable.
+     */
+    private void sendLaunchIntent(final String action, final Uri data, final String query,
+            final Bundle appData, int actionKey, final String actionMsg, final SearchableInfo si) {
+        Intent launcher = new Intent(action);
+
+        if (query != null) {
+            launcher.putExtra(SearchManager.QUERY, query);
+        }
+
+        if (data != null) {
+            launcher.setData(data);
+        }
+
+        if (appData != null) {
+            launcher.putExtra(SearchManager.APP_DATA, appData);
+        }
+
+        // add launch info (action key, etc.)
+        if (actionKey != KeyEvent.KEYCODE_UNKNOWN) {
+            launcher.putExtra(SearchManager.ACTION_KEY, actionKey);
+            launcher.putExtra(SearchManager.ACTION_MSG, actionMsg);
+        }
+
+        // attempt to enforce security requirement (no 3rd-party intents)
+        launcher.setComponent(si.mSearchActivity);
+
+        getContext().startActivity(launcher);
+    }
+
+    /**
+     * Shared code for launching a query from a suggestion.
+     * @param ca The cursor adapter containing the suggestions
+     * @param position The suggestion we'll be launching from
+     * @return true if a successful launch, false if could not (e.g. bad position)
+     */
+    private boolean launchSuggestion(CursorAdapter ca, int position) {
+        Cursor c = ca.getCursor();
+        if ((c != null) && c.moveToPosition(position)) {
+            setupSuggestionIntent(c, mSearchable);
+            
+            final Bundle appData =  mAppSearchData;
+            SearchableInfo si = mSearchable;
+            String suggestionAction = mSuggestionAction;
+            Uri suggestionData = mSuggestionData;
+            String suggestionQuery = mSuggestionQuery;
+            dismiss();
+            sendLaunchIntent(suggestionAction, suggestionData, suggestionQuery, appData,
+                                KeyEvent.KEYCODE_UNKNOWN, null, si);
+            return true;
+        }
+        return false;
+    }
+    
+    /**
+     * When a particular suggestion has been selected, perform the various lookups required
+     * to use the suggestion.  This includes checking the cursor for suggestion-specific data,
+     * and/or falling back to the XML for defaults;  It also creates REST style Uri data when
+     * the suggestion includes a data id.
+     * 
+     * NOTE:  Return values are in member variables mSuggestionAction & mSuggestionData.
+     * 
+     * @param c The suggestions cursor, moved to the row of the user's selection
+     * @param si The searchable activity's info record
+     */
+    void setupSuggestionIntent(Cursor c, SearchableInfo si) {
+        try {
+            // use specific action if supplied, or default action if supplied, or fixed default
+            mSuggestionAction = null;
+            int mColumn = c.getColumnIndex(SearchManager.SUGGEST_COLUMN_INTENT_ACTION);
+            if (mColumn >= 0) {
+                final String action = c.getString(mColumn);
+                if (action != null) {
+                    mSuggestionAction = action;
+                }
+            }
+            if (mSuggestionAction == null) {
+                mSuggestionAction = si.getSuggestIntentAction();
+            }
+            if (mSuggestionAction == null) {
+                mSuggestionAction = Intent.ACTION_SEARCH;
+            }
+            
+            // use specific data if supplied, or default data if supplied
+            String data = null;
+            mColumn = c.getColumnIndex(SearchManager.SUGGEST_COLUMN_INTENT_DATA);
+            if (mColumn >= 0) {
+                final String rowData = c.getString(mColumn);
+                if (rowData != null) {
+                    data = rowData;
+                }
+            }
+            if (data == null) {
+                data = si.getSuggestIntentData();
+            }
+            
+            // then, if an ID was provided, append it.
+            if (data != null) {
+                mColumn = c.getColumnIndex(SearchManager.SUGGEST_COLUMN_INTENT_DATA_ID);
+                if (mColumn >= 0) {
+                    final String id = c.getString(mColumn);
+                    if (id != null) {
+                        data = data + "/" + Uri.encode(id);
+                    }
+                }
+            }
+            mSuggestionData = (data == null) ? null : Uri.parse(data);
+            
+            mSuggestionQuery = null;
+            mColumn = c.getColumnIndex(SearchManager.SUGGEST_COLUMN_QUERY);
+            if (mColumn >= 0) {
+                final String query = c.getString(mColumn);
+                if (query != null) {
+                    mSuggestionQuery = query;
+                }
+            }
+        } catch (RuntimeException e ) {
+            int rowNum;
+            try {                       // be really paranoid now
+                rowNum = c.getPosition();
+            } catch (RuntimeException e2 ) {
+                rowNum = -1;
+            }
+            Log.w(LOG_TAG, "Search Suggestions cursor at row " + rowNum + 
+                            " returned exception" + e.toString());
+        }
+    }
+    
+    /**
+     * For a given suggestion and a given cursor row, get the action message.  If not provided
+     * by the specific row/column, also check for a single definition (for the action key).
+     * 
+     * @param c The cursor providing suggestions
+     * @param actionKey The actionkey record being examined
+     * 
+     * @return Returns a string, or null if no action key message for this suggestion
+     */
+    private String getActionKeyMessage(Cursor c, final SearchableInfo.ActionKeyInfo actionKey) {
+        String result = null;
+        // check first in the cursor data, for a suggestion-specific message
+        final String column = actionKey.mSuggestActionMsgColumn;
+        if (column != null) {
+            try {
+                int colId = c.getColumnIndexOrThrow(column);
+                result = c.getString(colId);
+            } catch (RuntimeException e) {
+                // OK - result is already null
+            }
+        }
+        // If the cursor didn't give us a message, see if there's a single message defined
+        // for the actionkey (for all suggestions)
+        if (result == null) {
+            result = actionKey.mSuggestActionMsg;
+        }
+        return result;
+    }
+        
+    /**
+     * Local subclass for AutoCompleteTextView
+     * 
+     * This exists entirely to override the threshold method.  Otherwise we just use the class
+     * as-is.
+     */
+    public static class SearchAutoComplete extends AutoCompleteTextView {
+
+        public SearchAutoComplete(Context context) {
+            super(null);
+        }
+        
+        public SearchAutoComplete(Context context, AttributeSet attrs) {
+            super(context, attrs);
+        }
+
+        public SearchAutoComplete(Context context, AttributeSet attrs, int defStyle) {
+            super(context, attrs, defStyle);
+        }
+        
+        /**
+         * We never allow ACTV to automatically replace the text, since we use "jamSuggestionQuery"
+         * to do that.  There's no point in letting ACTV do this here, because in the search UI,
+         * as soon as we click a suggestion, we're going to start shutting things down.
+         */
+        @Override
+        public void replaceText(CharSequence text) {
+        }
+        
+        /**
+         * We always return true, so that the effective threshold is "zero".  This allows us
+         * to provide "null" suggestions such as "just show me some recent entries".
+         */
+        @Override
+        public boolean enoughToFilter() {
+            return true;
+        }
+    }
+    
+    /**
+     * Support for AutoCompleteTextView-based suggestions
+     */
+    /**
+     * This class provides the filtering-based interface to suggestions providers.
+     * It is hardwired in a couple of places to support GoogleSearch - for example, it supports
+     * two-line suggestions, but it does not support icons.
+     */
+    private static class SuggestionsAdapter extends SimpleCursorAdapter {
+        private final String TAG = "SuggestionsAdapter";
+        
+        SearchableInfo mSearchable;
+        private Resources mProviderResources;
+        
+        // These private variables are shared by the filter thread and must be protected
+        private WeakReference<Cursor> mRecentCursor = new WeakReference<Cursor>(null);
+        private boolean mNonUserQuery = false;
+
+        public SuggestionsAdapter(Context context, SearchableInfo searchable) {
+            super(context, -1, null, null, null);
+            mSearchable = searchable;
+            
+            // set up provider resources (gives us icons, etc.)
+            Context activityContext = mSearchable.getActivityContext(mContext);
+            Context providerContext = mSearchable.getProviderContext(mContext, activityContext);
+            mProviderResources = providerContext.getResources();
+        }
+        
+        /**
+         * Set this field (temporarily!) to disable suggestions updating.  This allows us
+         * to change the string in the text view without changing the suggestions list.
+         */
+        public void setNonUserQuery(boolean nonUserQuery) {
+            synchronized (this) {
+                mNonUserQuery = nonUserQuery;
+            }
+        }
+        
+        public boolean getNonUserQuery() {
+            synchronized (this) {
+                return mNonUserQuery;
+            }
+        }
+
+        /**
+         * Use the search suggestions provider to obtain a live cursor.  This will be called
+         * in a worker thread, so it's OK if the query is slow (e.g. round trip for suggestions).
+         * The results will be processed in the UI thread and changeCursor() will be called.
+         * 
+         * In order to provide the Search Mgr functionality of seeing your query change as you
+         * scroll through the list, we have to be able to jam new text into the string without
+         * retriggering the suggestions.  We do that here via the "nonUserQuery" flag.  In that
+         * case we simply return the existing cursor.
+         * 
+         * TODO: Dianne suggests that this should simply be promoted into an AutoCompleteTextView
+         * behavior (perhaps optionally).
+         * 
+         * TODO: The "nonuserquery" logic has a race condition because it happens in another thread.
+         * This also needs to be fixed.
+         */
+        @Override
+        public Cursor runQueryOnBackgroundThread(CharSequence constraint) {
+            String query = (constraint == null) ? "" : constraint.toString();
+            Cursor c = null;
+            synchronized (this) {
+                if (mNonUserQuery) {
+                    c = mRecentCursor.get();
+                    mNonUserQuery = false;
+                }
+            }
+            if (c == null) {
+                c = getSuggestions(mSearchable, query);
+                synchronized (this) {
+                    mRecentCursor = new WeakReference<Cursor>(c);
+                }
+            }
+            return c;
+        }
+        
+        /**
+         * Overriding changeCursor() allows us to change not only the cursor, but by sampling
+         * the cursor's columns, the actual display characteristics of the list.
+         */
+        @Override
+        public void changeCursor(Cursor c) {
+            
+            // first, check for various conditions that disqualify this cursor
+            if ((c == null) || (c.getCount() == 0)) {
+                // no cursor, or cursor with no data
+                changeCursorAndColumns(null, null, null);
+                if (c != null) {
+                    c.close();
+                }
+                return;
+            }
+
+            // check cursor before trying to create list views from it
+            int colId = c.getColumnIndex("_id");
+            int col1 = c.getColumnIndex(SearchManager.SUGGEST_COLUMN_TEXT_1);
+            int col2 = c.getColumnIndex(SearchManager.SUGGEST_COLUMN_TEXT_2);
+            int colIc1 = c.getColumnIndex(SearchManager.SUGGEST_COLUMN_ICON_1);
+            int colIc2 = c.getColumnIndex(SearchManager.SUGGEST_COLUMN_ICON_2);
+
+            boolean minimal = (colId >= 0) && (col1 >= 0);
+            boolean hasIcons = (colIc1 >= 0) && (colIc2 >= 0);
+            boolean has2Lines = col2 >= 0;
+
+            if (minimal) {
+                int layout;
+                String[] from;
+                int[] to;
+
+                if (hasIcons) {
+                    if (has2Lines) {
+                        layout = com.android.internal.R.layout.search_dropdown_item_icons_2line;
+                        from = TWO_LINE_ICONS_FROM;
+                        to = TWO_LINE_ICONS_TO;
+                    } else {
+                        layout = com.android.internal.R.layout.search_dropdown_item_icons_1line;
+                        from = ONE_LINE_ICONS_FROM;
+                        to = ONE_LINE_ICONS_TO;
+                    }
+                } else {
+                    if (has2Lines) {
+                        layout = com.android.internal.R.layout.search_dropdown_item_2line;
+                        from = TWO_LINE_FROM;
+                        to = TWO_LINE_TO;
+                    } else {
+                        layout = com.android.internal.R.layout.search_dropdown_item_1line;
+                        from = ONE_LINE_FROM;
+                        to = ONE_LINE_TO;
+                    }
+                }
+                // Now actually set up the cursor, columns, and the list view
+                changeCursorAndColumns(c, from, to);
+                setViewResource(layout);              
+            } else {
+                // Provide some help for developers instead of just silently discarding
+                Log.w(LOG_TAG, "Suggestions cursor discarded due to missing required columns.");
+                changeCursorAndColumns(null, null, null);
+                c.close();
+            }
+            if ((colIc1 >= 0) != (colIc2 >= 0)) {
+                Log.w(LOG_TAG, "Suggestion icon column(s) discarded, must be 0 or 2 columns.");
+            }    
+        }
+        
+        /**
+         * Overriding this allows us to write the selected query back into the box.
+         * NOTE:  This is a vastly simplified version of SearchDialog.jamQuery() and does
+         * not universally support the search API.  But it is sufficient for Google Search.
+         */
+        @Override
+        public CharSequence convertToString(Cursor cursor) {
+            CharSequence result = null;
+            if (cursor != null) {
+                int column = cursor.getColumnIndex(SearchManager.SUGGEST_COLUMN_QUERY);
+                if (column >= 0) {
+                    final String query = cursor.getString(column);
+                    if (query != null) {
+                        result = query;
+                    }
+                }
+            }
+            return result;
+        }
+
+        /**
+         * Get the query cursor for the search suggestions.
+         * 
+         * TODO this is functionally identical to the version in SearchDialog.java.  Perhaps it 
+         * could be hoisted into SearchableInfo or some other shared spot.
+         * 
+         * @param query The search text entered (so far)
+         * @return Returns a cursor with suggestions, or null if no suggestions 
+         */
+        private Cursor getSuggestions(final SearchableInfo searchable, final String query) {
+            Cursor cursor = null;
+            if (searchable.getSuggestAuthority() != null) {
+                try {
+                    StringBuilder uriStr = new StringBuilder("content://");
+                    uriStr.append(searchable.getSuggestAuthority());
+
+                    // if content path provided, insert it now
+                    final String contentPath = searchable.getSuggestPath();
+                    if (contentPath != null) {
+                        uriStr.append('/');
+                        uriStr.append(contentPath);
+                    }
+
+                    // append standard suggestion query path 
+                    uriStr.append('/' + SearchManager.SUGGEST_URI_PATH_QUERY);
+
+                    // inject query, either as selection args or inline
+                    String[] selArgs = null;
+                    if (searchable.getSuggestSelection() != null) {    // use selection if provided
+                        selArgs = new String[] {query};
+                    } else {
+                        uriStr.append('/');                             // no sel, use REST pattern
+                        uriStr.append(Uri.encode(query));
+                    }
+
+                    // finally, make the query
+                    cursor = mContext.getContentResolver().query(
+                                                        Uri.parse(uriStr.toString()), null, 
+                                                        searchable.getSuggestSelection(), selArgs,
+                                                        null);
+                } catch (RuntimeException e) {
+                    Log.w(TAG, "Search Suggestions query returned exception " + e.toString());
+                    cursor = null;
+                }
+            }
+            
+            return cursor;
+        }
+
+        /**
+         * Overriding this allows us to affect the way that an icon is loaded.  Specifically,
+         * we can be more controlling about the resource path (and allow icons to come from other
+         * packages).
+         * 
+         * TODO: This is 100% identical to the version in SearchDialog.java
+         *
+         * @param v ImageView to receive an image
+         * @param value the value retrieved from the cursor
+         */
+        @Override
+        public void setViewImage(ImageView v, String value) {
+            int resID;
+            Drawable img = null;
+
+            try {
+                resID = Integer.parseInt(value);
+                if (resID != 0) {
+                    img = mProviderResources.getDrawable(resID);
+                }
+            } catch (NumberFormatException nfe) {
+                // img = null;
+            } catch (NotFoundException e2) {
+                // img = null;
+            }
+            
+            // finally, set the image to whatever we've gotten
+            v.setImageDrawable(img);
+        }
+        
+        /**
+         * This method is overridden purely to provide a bit of protection against
+         * flaky content providers.
+         * 
+         * TODO: This is 100% identical to the version in SearchDialog.java
+         * 
+         * @see android.widget.ListAdapter#getView(int, View, ViewGroup)
+         */
+        @Override 
+        public View getView(int position, View convertView, ViewGroup parent) {
+            try {
+                return super.getView(position, convertView, parent);
+            } catch (RuntimeException e) {
+                Log.w(TAG, "Search Suggestions cursor returned exception " + e.toString());
+                // what can I return here?
+                View v = newView(mContext, mCursor, parent);
+                if (v != null) {
+                    TextView tv = (TextView) v.findViewById(com.android.internal.R.id.text1);
+                    tv.setText(e.toString());
+                }
+                return v;
+            }
+        }
+
+    }
+    
+    /**
+     * Implements OnItemClickListener
+     */
+    public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
+        // Log.d(LOG_TAG, "onItemClick() position " + position);
+        launchSuggestion(mSuggestionsAdapter, position);
+    }
+    
+    /** 
+     * Implements OnItemSelectedListener
+     */
+     public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
+         // Log.d(LOG_TAG, "onItemSelected() position " + position);
+         jamSuggestionQuery(true, parent, position);
+     }
+
+     /** 
+      * Implements OnItemSelectedListener
+      */
+     public void onNothingSelected(AdapterView<?> parent) {
+         // Log.d(LOG_TAG, "onNothingSelected()");
+     }
+
+    /**
+     * Debugging Support
+     */
+
+    /**
+     * For debugging only, sample the millisecond clock and log it.
+     * Uses AtomicLong so we can use in multiple threads
+     */
+    private AtomicLong mLastLogTime = new AtomicLong(SystemClock.uptimeMillis());
+    private void dbgLogTiming(final String caller) {
+        long millis = SystemClock.uptimeMillis();
+        long oldTime = mLastLogTime.getAndSet(millis);
+        long delta = millis - oldTime;
+        final String report = millis + " (+" + delta + ") ticks for Search keystroke in " + caller;
+        Log.d(LOG_TAG,report);
+    }
+}
diff --git a/core/java/android/app/SearchManager.java b/core/java/android/app/SearchManager.java
new file mode 100644
index 0000000..c1d66f4
--- /dev/null
+++ b/core/java/android/app/SearchManager.java
@@ -0,0 +1,1385 @@
+/*
+ * Copyright (C) 2007 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 android.app;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.res.Configuration;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.ServiceManager;
+import android.view.KeyEvent;
+
+/**
+ * This class provides access to the system search services.
+ * 
+ * <p>In practice, you won't interact with this class directly, as search
+ * services are provided through methods in {@link android.app.Activity Activity}
+ * methods and the the {@link android.content.Intent#ACTION_SEARCH ACTION_SEARCH}
+ * {@link android.content.Intent Intent}.  This class does provide a basic
+ * overview of search services and how to integrate them with your activities.
+ * If you do require direct access to the Search Manager, do not instantiate 
+ * this class directly; instead, retrieve it through
+ * {@link android.content.Context#getSystemService
+ * context.getSystemService(Context.SEARCH_SERVICE)}.
+ * 
+ * <p>Topics covered here:
+ * <ol>
+ * <li><a href="#DeveloperGuide">Developer Guide</a>
+ * <li><a href="#HowSearchIsInvoked">How Search Is Invoked</a>
+ * <li><a href="#QuerySearchApplications">Query-Search Applications</a>
+ * <li><a href="#FilterSearchApplications">Filter-Search Applications</a>
+ * <li><a href="#Suggestions">Search Suggestions</a>
+ * <li><a href="#ActionKeys">Action Keys</a>
+ * <li><a href="#SearchabilityMetadata">Searchability Metadata</a>
+ * <li><a href="#PassingSearchContext">Passing Search Context</a>
+ * <li><a href="#ProtectingUserPrivacy">Protecting User Privacy</a>
+ * </ol>
+ * 
+ * <a name="DeveloperGuide"></a>
+ * <h3>Developer Guide</h3>
+ * 
+ * <p>The ability to search for user, system, or network based data is considered to be
+ * a core user-level feature of the android platform.  At any time, the user should be
+ * able to use a familiar command, button, or keystroke to invoke search, and the user
+ * should be able to search any data which is available to them.  The goal is to make search 
+ * appear to the user as a seamless, system-wide feature.
+ * 
+ * <p>In terms of implementation, there are three broad classes of Applications:
+ * <ol>
+ * <li>Applications that are not inherently searchable</li>
+ * <li>Query-Search Applications</li>
+ * <li>Filter-Search Applications</li>
+ * </ol>
+ * <p>These categories, as well as related topics, are discussed in
+ * the sections below.
+ *
+ * <p>Even if your application is not <i>searchable</i>, it can still support the invocation of
+ * search.  Please review the section <a href="#HowSearchIsInvoked">How Search Is Invoked</a>
+ * for more information on how to support this.
+ * 
+ * <p>Many applications are <i>searchable</i>.  These are 
+ * the applications which can convert a query string into a list of results.  
+ * Within this subset, applications can be grouped loosely into two families:  
+ * <ul><li><i>Query Search</i> applications perform batch-mode searches - each query string is 
+ * converted to a list of results.</li>
+ * <li><i>Filter Search</i> applications provide live filter-as-you-type searches.</li></ul>
+ * <p>Generally speaking, you would use query search for network-based data, and filter 
+ * search for local data, but this is not a hard requirement and applications 
+ * are free to use the model that fits them best (or invent a new model).
+ * <p>It should be clear that the search implementation decouples "search 
+ * invocation" from "searchable".  This satisfies the goal of making search appear
+ * to be "universal".  The user should be able to launch any search from 
+ * almost any context.
+ * 
+ * <a name="HowSearchIsInvoked"></a>
+ * <h3>How Search Is Invoked</h3>
+ * 
+ * <p>Unless impossible or inapplicable, all applications should support
+ * invoking the search UI.  This means that when the user invokes the search command, 
+ * a search UI will be presented to them.  The search command is currently defined as a menu
+ * item called "Search" (with an alphabetic shortcut key of "S"), or on some devices, a dedicated
+ * search button key.
+ * <p>If your application is not inherently searchable, you can also allow the search UI
+ * to be invoked in a "web search" mode.  If the user enters a search term and clicks the 
+ * "Search" button, this will bring the browser to the front and will launch a web-based
+ * search.  The user will be able to click the "Back" button and return to your application.
+ * <p>In general this is implemented by your activity, or the {@link android.app.Activity Activity}
+ * base class, which captures the search command and invokes the Search Manager to 
+ * display and operate the search UI.  You can also cause the search UI to be presented in response
+ * to user keystrokes in your activity (for example, to instantly start filter searching while
+ * viewing a list and typing any key).
+ * <p>The search UI is presented as a floating 
+ * window and does not cause any change in the activity stack.  If the user 
+ * cancels search, the previous activity re-emerges.  If the user launches a 
+ * search, this will be done by sending a search {@link android.content.Intent Intent} (see below), 
+ * and the normal intent-handling sequence will take place (your activity will pause,
+ * etc.)
+ * <p><b>What you need to do:</b> First, you should consider the way in which you want to
+ * handle invoking search.  There are four broad (and partially overlapping) categories for 
+ * you to choose from.
+ * <ul><li>You can capture the search command yourself, by including a <i>search</i>
+ * button or menu item - and invoking the search UI directly.</li>
+ * <li>You can provide a <i>type-to-search</i> feature, in which search is invoked automatically
+ * when the user enters any characters.</li>
+ * <li>Even if your application is not inherently searchable, you can allow web search, 
+ * via the search key (or even via a search menu item).
+ * <li>You can disable search entirely.  This should only be used in very rare circumstances,
+ * as search is a system-wide feature and users will expect it to be available in all contexts.</li>
+ * </ul>
+ * 
+ * <p><b>How to define a search menu.</b>  The system provides the following resources which may
+ * be useful when adding a search item to your menu:
+ * <ul><li>android.R.drawable.ic_search_category_default is an icon you can use in your menu.</li>
+ * <li>{@link #MENU_KEY SearchManager.MENU_KEY} is the recommended alphabetic shortcut.</li>
+ * </ul>
+ * 
+ * <p><b>How to invoke search directly.</b>  In order to invoke search directly, from a button
+ * or menu item, you can launch a generic search by calling
+ * {@link android.app.Activity#onSearchRequested onSearchRequested} as shown:
+ * <pre class="prettyprint">
+ * onSearchRequested();</pre>
+ * 
+ * <p><b>How to implement type-to-search.</b>  While setting up your activity, call
+ * {@link android.app.Activity#setDefaultKeyMode setDefaultKeyMode}:
+ * <pre class="prettyprint">
+ * setDefaultKeyMode(DEFAULT_KEYS_SEARCH_LOCAL);   // search within your activity
+ * setDefaultKeyMode(DEFAULT_KEYS_SEARCH_GLOBAL);  // search using platform global search</pre>
+ * 
+ * <p><b>How to enable web-based search.</b>  In addition to searching within your activity or
+ * application, you can also use the Search Manager to invoke a platform-global search, typically
+ * a web search.  There are two ways to do this:
+ * <ul><li>You can simply define "search" within your application or activity to mean global search.
+ * This is described in more detail in the 
+ * <a href="#SearchabilityMetadata">Searchability Metadata</a> section.  Briefly, you will
+ * add a single meta-data entry to your manifest, declaring that the default search
+ * for your application is "*".  This indicates to the system that no application-specific
+ * search activity is provided, and that it should launch web-based search instead.</li>
+ * <li>You can specify this at invocation time via default keys (see above), overriding
+ * {@link android.app.Activity#onSearchRequested}, or via a direct call to 
+ * {@link android.app.Activity#startSearch}.  This is most useful if you wish to provide local
+ * searchability <i>and</i> access to global search.</li></ul> 
+ * 
+ * <p><b>How to disable search from your activity.</b>  search is a system-wide feature and users
+ * will expect it to be available in all contexts.  If your UI design absolutely precludes
+ * launching search, override {@link android.app.Activity#onSearchRequested onSearchRequested}
+ * as shown:
+ * <pre class="prettyprint">
+ * &#64;Override
+ * public boolean onSearchRequested() {
+ *    return false;
+ * }</pre> 
+ * 
+ * <p><b>Managing focus and knowing if Search is active.</b>  The search UI is not a separate
+ * activity, and when the UI is invoked or dismissed, your activity will not typically be paused,
+ * resumed, or otherwise notified by the methods defined in 
+ * <a href="{@docRoot}guide/topics/fundamentals.html#actlife">Application Fundamentals: 
+ * Activity Lifecycle</a>.  The search UI is
+ * handled in the same way as other system UI elements which may appear from time to time, such as 
+ * notifications, screen locks, or other system alerts:  
+ * <p>When the search UI appears, your activity will lose input focus.
+ * <p>When the search activity is dismissed, there are three possible outcomes:
+ * <ul><li>If the user simply canceled the search UI, your activity will regain input focus and
+ * proceed as before.  See {@link #setOnDismissListener} and {@link #setOnCancelListener} if you 
+ * required direct notification of search dialog dismissals.</li>
+ * <li>If the user launched a search, and this required switching to another activity to receive
+ * and process the search {@link android.content.Intent Intent}, your activity will receive the 
+ * normal sequence of activity pause or stop notifications.</li>
+ * <li>If the user launched a search, and the current activity is the recipient of the search 
+ * {@link android.content.Intent Intent}, you will receive notification via the 
+ * {@link android.app.Activity#onNewIntent onNewIntent()} method.</li></ul>
+ * <p>This list is provided in order to clarify the ways in which your activities will interact with
+ * the search UI.  More details on searchable activities and search intents are provided in the
+ * sections below.
+ *
+ * <a name="QuerySearchApplications"></a>
+ * <h3>Query-Search Applications</h3>
+ * 
+ * <p>Query-search applications are those that take a single query (e.g. a search
+ * string) and present a set of results that may fit.  Primary examples include
+ * web queries, map lookups, or email searches (with the common thread being
+ * network query dispatch).  It may also be the case that certain local searches
+ * are treated this way.  It's up to the application to decide.
+ *
+ * <p><b>What you need to do:</b>  The following steps are necessary in order to
+ * implement query search.
+ * <ul>
+ * <li>Implement search invocation as described above.  (Strictly speaking, 
+ * these are decoupled, but it would make little sense to be "searchable" but not 
+ * "search-invoking".)</li>
+ * <li>Your application should have an activity that takes a search string and
+ * converts it to a list of results.  This could be your primary display activity
+ * or it could be a dedicated search results activity.  This is your <i>searchable</i>
+ * activity and every query-search application must have one.</li>
+ * <li>In the searchable activity, in onCreate(), you must receive and handle the 
+ * {@link android.content.Intent#ACTION_SEARCH ACTION_SEARCH}
+ * {@link android.content.Intent Intent}.  The text to search (query string) for is provided by 
+ * calling 
+ * {@link #QUERY getStringExtra(SearchManager.QUERY)}.</li>
+ * <li>To identify and support your searchable activity, you'll need to 
+ * provide an XML file providing searchability configuration parameters, a reference to that 
+ * in your searchable activity's <a href="{@docRoot}guide/topics/manifest/manifest-intro.html">manifest</a>
+ * entry, and an intent-filter declaring that you can 
+ * receive ACTION_SEARCH intents.  This is described in more detail in the 
+ * <a href="#SearchabilityMetadata">Searchability Metadata</a> section.</li>
+ * <li>Your <a href="{@docRoot}guide/topics/manifest/manifest-intro.html">manifest</a> also needs a metadata entry
+ * providing a global reference to the searchable activity.  This is the "glue" directing the search
+ * UI, when invoked from any of your <i>other</i> activities, to use your application as the
+ * default search context.  This is also described in more detail in the 
+ * <a href="#SearchabilityMetadata">Searchability Metadata</a> section.</li> 
+ * <li>Finally, you may want to define your search results activity as with the 
+ * {@link android.R.attr#launchMode singleTop} launchMode flag.  This allows the system 
+ * to launch searches from/to the same activity without creating a pile of them on the 
+ * activity stack.  If you do this, be sure to also override 
+ * {@link android.app.Activity#onNewIntent onNewIntent} to handle the
+ * updated intents (with new queries) as they arrive.</li>
+ * </ul>
+ *
+ * <p>Code snippet showing handling of intents in your search activity:
+ * <pre class="prettyprint">
+ * &#64;Override
+ * protected void onCreate(Bundle icicle) {
+ *     super.onCreate(icicle);
+ *     
+ *     final Intent queryIntent = getIntent();
+ *     final String queryAction = queryIntent.getAction();
+ *     if (Intent.ACTION_SEARCH.equals(queryAction)) {
+ *         doSearchWithIntent(queryIntent);
+ *     }
+ * }
+ * 
+ * private void doSearchWithIntent(final Intent queryIntent) {
+ *     final String queryString = queryIntent.getStringExtra(SearchManager.QUERY);
+ *     doSearchWithQuery(queryString);
+ * }</pre>
+ * 
+ * <a name="FilterSearchApplications"></a>
+ * <h3>Filter-Search Applications</h3>
+ * 
+ * <p>Filter-search applications are those that use live text entry (e.g. keystrokes)) to
+ * display and continuously update a list of results.  Primary examples include applications
+ * that use locally-stored data.
+ * 
+ * <p>Filter search is not directly supported by the Search Manager.  Most filter search
+ * implementations will use variants of {@link android.widget.Filterable}, such as a 
+ * {@link android.widget.ListView} bound to a {@link android.widget.SimpleCursorAdapter}.  However,
+ * you may find it useful to mix them together, by declaring your filtered view searchable.  With
+ * this configuration, you can still present the standard search dialog in all activities
+ * within your application, but transition to a filtered search when you enter the activity
+ * and display the results.
+ * 
+ * <a name="Suggestions"></a>
+ * <h3>Search Suggestions</h3>
+ * 
+ * <p>A powerful feature of the Search Manager is the ability of any application to easily provide
+ * live "suggestions" in order to prompt the user.  Each application implements suggestions in a 
+ * different, unique, and appropriate way.  Suggestions be drawn from many sources, including but 
+ * not limited to:
+ * <ul>
+ * <li>Actual searchable results (e.g. names in the address book)</li>
+ * <li>Recently entered queries</li>
+ * <li>Recently viewed data or results</li>
+ * <li>Contextually appropriate queries or results</li>
+ * <li>Summaries of possible results</li>
+ * </ul>
+ * 
+ * <p>Another feature of suggestions is that they can expose queries or results before the user
+ * ever visits the application.  This reduces the amount of context switching required, and helps
+ * the user access their data quickly and with less context shifting.  In order to provide this
+ * capability, suggestions are accessed via a 
+ * {@link android.content.ContentProvider Content Provider}.  
+ * 
+ * <p>The primary form of suggestions is known as <i>queried suggestions</i> and is based on query
+ * text that the user has already typed.  This would generally be based on partial matches in
+ * the available data.  In certain situations - for example, when no query text has been typed yet -
+ * an application may also opt to provide <i>zero-query suggestions</i>.
+ * These would typically be drawn from the same data source, but because no partial query text is 
+ * available, they should be weighted based on other factors - for example, most recent queries 
+ * or most recent results.
+ * 
+ * <p><b>Overview of how suggestions are provided.</b>  When the search manager identifies a 
+ * particular activity as searchable, it will check for certain metadata which indicates that
+ * there is also a source of suggestions.  If suggestions are provided, the following steps are
+ * taken.
+ * <ul><li>Using formatting information found in the metadata, the user's query text (whatever
+ * has been typed so far) will be formatted into a query and sent to the suggestions 
+ * {@link android.content.ContentProvider Content Provider}.</li>
+ * <li>The suggestions {@link android.content.ContentProvider Content Provider} will create a
+ * {@link android.database.Cursor Cursor} which can iterate over the possible suggestions.</li>
+ * <li>The search manager will populate a list using display data found in each row of the cursor,
+ * and display these suggestions to the user.</li>
+ * <li>If the user types another key, or changes the query in any way, the above steps are repeated
+ * and the suggestions list is updated or repopulated.</li>
+ * <li>If the user clicks or touches the "GO" button, the suggestions are ignored and the search is
+ * launched using the normal {@link android.content.Intent#ACTION_SEARCH ACTION_SEARCH} type of 
+ * {@link android.content.Intent Intent}.</li>
+ * <li>If the user uses the directional controls to navigate the focus into the suggestions list,
+ * the query text will be updated while the user navigates from suggestion to suggestion.  The user
+ * can then click or touch the updated query and edit it further.  If the user navigates back to
+ * the edit field, the original typed query is restored.</li>
+ * <li>If the user clicks or touches a particular suggestion, then a combination of data from the 
+ * cursor and
+ * values found in the metadata are used to synthesize an Intent and send it to the application.
+ * Depending on the design of the activity and the way it implements search, this might be a
+ * {@link android.content.Intent#ACTION_SEARCH ACTION_SEARCH} (in order to launch a query), or it
+ * might be a {@link android.content.Intent#ACTION_VIEW ACTION_VIEW}, in order to proceed directly
+ * to display of specific data.</li>
+ * </ul>
+ *  
+ * <p><b>Simple Recent-Query-Based Suggestions.</b>  The Android framework provides a simple Search
+ * Suggestions provider, which simply records and replays recent queries.  For many applications,
+ * this will be sufficient.  The basic steps you will need to
+ * do, in order to use the built-in recent queries suggestions provider, are as follows:
+ * <ul>
+ * <li>Implement and test query search, as described in the previous sections.</li>
+ * <li>Create a Provider within your application by extending 
+ * {@link android.content.SearchRecentSuggestionsProvider}.</li>
+ * <li>Create a manifest entry describing your provider.</li>
+ * <li>Update your searchable activity's XML configuration file with information about your
+ * provider.</li>
+ * <li>In your searchable activities, capture any user-generated queries and record them
+ * for future searches by calling {@link android.provider.SearchRecentSuggestions#saveRecentQuery}.
+ * </li>
+ * </ul>
+ * <p>For complete implementation details, please refer to 
+ * {@link android.content.SearchRecentSuggestionsProvider}.  The rest of the information in this
+ * section should not be necessary, as it refers to custom suggestions providers.
+ * 
+ * <p><b>Creating a Customized Suggestions Provider:</b>  In order to create more sophisticated
+ * suggestion providers, you'll need to take the following steps:
+ * <ul>
+ * <li>Implement and test query search, as described in the previous sections.</li>
+ * <li>Decide how you wish to <i>receive</i> suggestions.  Just like queries that the user enters,
+ * suggestions will be delivered to your searchable activity as 
+ * {@link android.content.Intent Intent} messages;  Unlike simple queries, you have quite a bit of
+ * flexibility in forming those intents.  A query search application will probably
+ * wish to continue receiving the {@link android.content.Intent#ACTION_SEARCH ACTION_SEARCH} 
+ * {@link android.content.Intent Intent}, which will launch a query search using query text as
+ * provided by the suggestion.  A filter search application will probably wish to 
+ * receive the {@link android.content.Intent#ACTION_VIEW ACTION_VIEW} 
+ * {@link android.content.Intent Intent}, which will take the user directly to a selected entry.
+ * Other interesting suggestions, including hybrids, are possible, and the suggestion provider
+ * can easily mix-and-match results to provide a richer set of suggestions for the user.  Finally,
+ * you'll need to update your searchable activity (or other activities) to receive the intents
+ * as you've defined them.</li>
+ * <li>Implement a Content Provider that provides suggestions.  If you already have one, and it 
+ * has access to your suggestions data.  If not, you'll have to create one.
+ * You'll also provide information about your Content Provider in your 
+ * package's <a href="{@docRoot}guide/topics/manifest/manifest-intro.html">manifest</a>.</li>
+ * <li>Update your searchable activity's XML configuration file.  There are two categories of
+ * information used for suggestions:
+ * <ul><li>The first is (required) data that the search manager will
+ * use to format the queries which are sent to the Content Provider.</li>
+ * <li>The second is (optional) parameters to configure structure
+ * if intents generated by suggestions.</li></li>
+ * </ul>
+ * </ul>
+ * 
+ * <p><b>Configuring your Content Provider to Receive Suggestion Queries.</b>  The basic job of
+ * a search suggestions {@link android.content.ContentProvider Content Provider} is to provide
+ * "live" (while-you-type) conversion of the user's query text into a set of zero or more 
+ * suggestions.  Each application is free to define the conversion, and as described above there are
+ * many possible solutions.  This section simply defines how to communicate with the suggestion
+ * provider.  
+ * 
+ * <p>The Search Manager must first determine if your package provides suggestions.  This is done
+ * by examination of your searchable meta-data XML file.  The android:searchSuggestAuthority
+ * attribute, if provided, is the signal to obtain & display suggestions.
+ * 
+ * <p>Every query includes a Uri, and the Search Manager will format the Uri as shown:
+ * <p><pre class="prettyprint">
+ * content:// your.suggest.authority / your.suggest.path / SearchManager.SUGGEST_URI_PATH_QUERY</pre>
+ * 
+ * <p>Your Content Provider can receive the query text in one of two ways.
+ * <ul>
+ * <li><b>Query provided as a selection argument.</b>  If you define the attribute value
+ * android:searchSuggestSelection and include a string, this string will be passed as the 
+ * <i>selection</i> parameter to your Content Provider's query function.  You must define a single
+ * selection argument, using the '?' character.  The user's query text will be passed to you
+ * as the first element of the selection arguments array.</li>
+ * <li><b>Query provided with Data Uri.</b>  If you <i>do not</i> define the attribute value
+ * android:searchSuggestSelection, then the Search Manager will append another "/" followed by
+ * the user's query to the query Uri.  The query will be encoding using Uri encoding rules - don't
+ * forget to decode it.  (See {@link android.net.Uri#getPathSegments} and
+ * {@link android.net.Uri#getLastPathSegment} for helpful utilities you can use here.)</li>
+ * </ul>
+ * 
+ * <p><b>Handling empty queries.</b>  Your application should handle the "empty query"
+ * (no user text entered) case properly, and generate useful suggestions in this case.  There are a
+ * number of ways to do this;  Two are outlined here:
+ * <ul><li>For a simple filter search of local data, you could simply present the entire dataset,
+ * unfiltered.  (example: People)</li>
+ * <li>For a query search, you could simply present the most recent queries.  This allows the user
+ * to quickly repeat a recent search.</li></ul>
+ * 
+ * <p><b>The Format of Individual Suggestions.</b>  Your suggestions are communicated back to the
+ * Search Manager by way of a {@link android.database.Cursor Cursor}.  The Search Manager will
+ * usually pass a null Projection, which means that your provider can simply return all appropriate
+ * columns for each suggestion.  The columns currently defined are:
+ * 
+ * <table border="2" width="85%" align="center" frame="hsides" rules="rows">
+ *
+ *     <thead>
+ *     <tr><th>Column Name</th> <th>Description</th> <th>Required?</th></tr>
+ *     </thead>
+ *
+ *     <tbody>
+ *     <tr><th>{@link #SUGGEST_COLUMN_FORMAT}</th>
+ *         <td><i>Unused - can be null.</i></td>
+ *         <td align="center">No</td>
+ *     </tr>
+ *     
+ *     <tr><th>{@link #SUGGEST_COLUMN_TEXT_1}</th>
+ *         <td>This is the line of text that will be presented to the user as the suggestion.</td>
+ *         <td align="center">Yes</td>
+ *     </tr>
+ *     
+ *     <tr><th>{@link #SUGGEST_COLUMN_TEXT_2}</th>
+ *         <td>If your cursor includes this column, then all suggestions will be provided in a 
+ *             two-line format.  The data in this column will be displayed as a second, smaller
+ *             line of text below the primary suggestion, or it can be null or empty to indicate no
+ *             text in this row's suggestion.</td>
+ *         <td align="center">No</td>
+ *     </tr>
+ *     
+ *     <tr><th>{@link #SUGGEST_COLUMN_ICON_1}</th>
+ *         <td>If your cursor includes this column, then all suggestions will be provided in an
+ *             icons+text format.  This value should be a reference (resource ID) of the icon to
+ *             draw on the left side, or it can be null or zero to indicate no icon in this row.
+ *             You must provide both cursor columns, or neither.
+ *             </td>
+ *         <td align="center">No, but required if you also have {@link #SUGGEST_COLUMN_ICON_2}</td>
+ *     </tr>
+ *     
+ *     <tr><th>{@link #SUGGEST_COLUMN_ICON_2}</th>
+ *         <td>If your cursor includes this column, then all suggestions will be provided in an
+ *             icons+text format.  This value should be a reference (resource ID) of the icon to
+ *             draw on the right side, or it can be null or zero to indicate no icon in this row.
+ *             You must provide both cursor columns, or neither.
+ *             </td>
+ *         <td align="center">No, but required if you also have {@link #SUGGEST_COLUMN_ICON_1}</td>
+ *     </tr>
+ *     
+ *     <tr><th>{@link #SUGGEST_COLUMN_INTENT_ACTION}</th>
+ *         <td>If this column exists <i>and</i> this element exists at the given row, this is the 
+ *             action that will be used when forming the suggestion's intent.  If the element is 
+ *             not provided, the action will be taken from the android:searchSuggestIntentAction 
+ *             field in your XML metadata.  <i>At least one of these must be present for the 
+ *             suggestion to generate an intent.</i>  Note:  If your action is the same for all 
+ *             suggestions, it is more efficient to specify it using XML metadata and omit it from 
+ *             the cursor.</td>
+ *         <td align="center">No</td>
+ *     </tr>
+ *     
+ *     <tr><th>{@link #SUGGEST_COLUMN_INTENT_DATA}</th>
+ *         <td>If this column exists <i>and</i> this element exists at the given row, this is the 
+ *             data that will be used when forming the suggestion's intent.  If the element is not 
+ *             provided, the data will be taken from the android:searchSuggestIntentData field in 
+ *             your XML metadata.  If neither source is provided, the Intent's data field will be 
+ *             null.  Note:  If your data is the same for all suggestions, or can be described 
+ *             using a constant part and a specific ID, it is more efficient to specify it using 
+ *             XML metadata and omit it from the cursor.</td>
+ *         <td align="center">No</td>
+ *     </tr>
+ *     
+ *     <tr><th>{@link #SUGGEST_COLUMN_INTENT_DATA_ID}</th>
+ *         <td>If this column exists <i>and</i> this element exists at the given row, then "/" and 
+ *             this value will be appended to the data field in the Intent.  This should only be 
+ *             used if the data field has already been set to an appropriate base string.</td>
+ *         <td align="center">No</td>
+ *     </tr>
+ *     
+ *     <tr><th>{@link #SUGGEST_COLUMN_QUERY}</th>
+ *         <td>If this column exists <i>and</i> this element exists at the given row, this is the 
+ *             data that will be used when forming the suggestion's query.</td>
+ *         <td align="center">Required if suggestion's action is 
+ *             {@link android.content.Intent#ACTION_SEARCH ACTION_SEARCH}, optional otherwise.</td>
+ *     </tr>
+ *     
+ *     <tr><th><i>Other Columns</i></th>
+ *         <td>Finally, if you have defined any <a href="#ActionKeys">Action Keys</a> and you wish 
+ *             for them to have suggestion-specific definitions, you'll need to define one 
+ *             additional column per action key.  The action key will only trigger if the 
+ *             currently-selection suggestion has a non-empty string in the corresponding column.  
+ *             See the section on <a href="#ActionKeys">Action Keys</a> for additional details and 
+ *             implementation steps.</td>
+ *         <td align="center">No</td>
+ *     </tr>
+ *     
+ *     </tbody>
+ * </table>
+ *
+ * <p>Clearly there are quite a few permutations of your suggestion data, but in the next section
+ * we'll look at a few simple combinations that you'll select from. 
+ *
+ * <p><b>The Format Of Intents Sent By Search Suggestions.</b>  Although there are many ways to 
+ * configure these intents, this document will provide specific information on just a few of them.  
+ * <ul><li><b>Launch a query.</b>  In this model, each suggestion represents a query that your
+ * searchable activity can perform, and the {@link android.content.Intent Intent} will be formatted
+ * exactly like those sent when the user enters query text and clicks the "GO" button:
+ *   <ul>
+ *   <li><b>Action:</b> {@link android.content.Intent#ACTION_SEARCH ACTION_SEARCH} provided
+ *   using your XML metadata (android:searchSuggestIntentAction).</li>
+ *   <li><b>Data:</b> empty (not used).</li>
+ *   <li><b>Query:</b> query text supplied by the cursor.</li>
+ *   </ul>
+ * </li>
+ * <li><b>Go directly to a result, using a complete Data Uri.</b>  In this model, the user will be 
+ * taken directly to a specific result.
+ *   <ul>
+ *   <li><b>Action:</b> {@link android.content.Intent#ACTION_VIEW ACTION_VIEW}</li>
+ *   <li><b>Data:</b> a complete Uri, supplied by the cursor, that identifies the desired data.</li>
+ *   <li><b>Query:</b> query text supplied with the suggestion (probably ignored)</li>
+ *   </ul>
+ * </li>
+ * <li><b>Go directly to a result, using a synthesized Data Uri.</b>  This has the same result
+ * as the previous suggestion, but provides the Data Uri in a different way.
+ *   <ul>
+ *   <li><b>Action:</b> {@link android.content.Intent#ACTION_VIEW ACTION_VIEW}</li>
+ *   <li><b>Data:</b> The search manager will assemble a Data Uri using the following elements:  
+ *   a Uri fragment provided in your XML metadata (android:searchSuggestIntentData), followed by 
+ *   a single "/", followed by the value found in the {@link #SUGGEST_COLUMN_INTENT_DATA_ID} 
+ *   entry in your cursor.</li>
+ *   <li><b>Query:</b> query text supplied with the suggestion (probably ignored)</li>
+ *   </ul>
+ * </li>
+ * </ul>
+ * <p>This list is not meant to be exhaustive.  Applications should feel free to define other types
+ * of suggestions.  For example, you could reduce long lists of results to summaries, and use one
+ * of the above intents (or one of your own) with specially formatted Data Uri's to display more
+ * detailed results.  Or you could display textual shortcuts as suggestions, but launch a display
+ * in a more data-appropriate format such as media artwork.
+ * 
+ * <p><b>Suggestion Rewriting.</b>  If the user navigates through the suggestions list, the UI
+ * may temporarily rewrite the user's query with a query that matches the currently selected 
+ * suggestion.  This enables the user to see what query is being suggested, and also allows the user
+ * to click or touch in the entry EditText element and make further edits to the query before
+ * dispatching it.  In order to perform this correctly, the Search UI needs to know exactly what
+ * text to rewrite the query with.
+ * 
+ * <p>For each suggestion, the following logic is used to select a new query string:
+ * <ul><li>If the suggestion provides an explicit value in the {@link #SUGGEST_COLUMN_QUERY} 
+ * column, this value will be used.</li>
+ * <li>If the metadata includes the queryRewriteFromData flag, and the suggestion provides an 
+ * explicit value for the intent Data field, this Uri will be used.  Note that this should only be
+ * used with Uri's that are intended to be user-visible, such as HTTP.  Internal Uri schemes should
+ * not be used in this way.</li>
+ * <li>If the metadata includes the queryRewriteFromText flag, the text in 
+ * {@link #SUGGEST_COLUMN_TEXT_1} will be used.  This should be used for suggestions in which no
+ * query text is provided and the SUGGEST_COLUMN_INTENT_DATA values are not suitable for user 
+ * inspection and editing.</li></ul>
+ *
+ * <a name="ActionKeys"></a>
+ * <h3>Action Keys</h3>
+ * 
+ * <p>Searchable activities may also wish to provide shortcuts based on the various action keys
+ * available on the device.  The most basic example of this is the contacts app, which enables the
+ * green "dial" key for quick access during searching.  Not all action keys are available on 
+ * every device, and not all are allowed to be overriden in this way.  (For example, the "Home"
+ * key must always return to the home screen, with no exceptions.)
+ * 
+ * <p>In order to define action keys for your searchable application, you must do two things.
+ * 
+ * <ul>
+ * <li>You'll add one or more <i>actionkey</i> elements to your searchable metadata configuration
+ * file.  Each element defines one of the keycodes you are interested in, 
+ * defines the conditions under which they are sent, and provides details
+ * on how to communicate the action key event back to your searchable activity.</li>
+ * <li>In your broadcast receiver, if you wish, you can check for action keys by checking the 
+ * extras field of the {@link android.content.Intent Intent}.</li>
+ * </ul>
+ * 
+ * <p><b>Updating metadata.</b>  For each keycode of interest, you must add an &lt;actionkey&gt;
+ * element.  Within this element you must define two or three attributes.  The first attribute,
+ * &lt;android:keycode&gt;, is required;  It is the key code of the action key event, as defined in 
+ * {@link android.view.KeyEvent}.  The remaining two attributes define the value of the actionkey's
+ * <i>message</i>, which will be passed to your searchable activity in the 
+ * {@link android.content.Intent Intent} (see below for more details).  Although each of these 
+ * attributes is optional, you must define one or both for the action key to have any effect.
+ * &lt;android:queryActionMsg&gt; provides the message that will be sent if the action key is 
+ * pressed while the user is simply entering query text.  &lt;android:suggestActionMsgColumn&gt;
+ * is used when action keys are tied to specific suggestions.  This attribute provides the name
+ * of a <i>column</i> in your suggestion cursor;  The individual suggestion, in that column,
+ * provides the message.  (If the cell is empty or null, that suggestion will not work with that
+ * action key.)
+ * <p>See the <a href="#SearchabilityMetadata">Searchability Metadata</a> section for more details 
+ * and examples.
+ * 
+ * <p><b>Receiving Action Keys</b>  Intents launched by action keys will be specially marked
+ * using a combination of values.  This enables your searchable application to examine the intent,
+ * if necessary, and perform special processing.  For example, clicking a suggested contact might
+ * simply display them;  Selecting a suggested contact and clicking the dial button might
+ * immediately call them.
+ * 
+ * <p>When a search {@link android.content.Intent Intent} is launched by an action key, two values
+ * will be added to the extras field.
+ * <ul>
+ * <li>To examine the key code, use {@link android.content.Intent#getIntExtra 
+ * getIntExtra(SearchManager.ACTION_KEY)}.</li>
+ * <li>To examine the message string, use {@link android.content.Intent#getStringExtra 
+ * getStringExtra(SearchManager.ACTION_MSG)}</li>
+ * </ul>
+ * 
+ * <a name="SearchabilityMetadata"></a>
+ * <h3>Searchability Metadata</h3>
+ * 
+ * <p>Every activity that is searchable must provide a small amount of additional information
+ * in order to properly configure the search system.  This controls the way that your search
+ * is presented to the user, and controls for the various modalities described previously.
+ * 
+ * <p>If your application is not searchable,
+ * then you do not need to provide any search metadata, and you can skip the rest of this section.
+ * When this search metadata cannot be found, the search manager will assume that the activity 
+ * does not implement search.  (Note: to implement web-based search, you will need to add
+ * the android.app.default_searchable metadata to your manifest, as shown below.)
+ * 
+ * <p>Values you supply in metadata apply only to each local searchable activity.  Each
+ * searchable activity can define a completely unique search experience relevant to its own
+ * capabilities and user experience requirements, and a single application can even define multiple
+ * searchable activities.
+ *
+ * <p><b>Metadata for searchable activity.</b>  As with your search implementations described 
+ * above, you must first identify which of your activities is searchable.  In the 
+ * <a href="{@docRoot}guide/topics/manifest/manifest-intro.html">manifest</a> entry for this activity, you must 
+ * provide two elements:
+ * <ul><li>An intent-filter specifying that you can receive and process the 
+ * {@link android.content.Intent#ACTION_SEARCH ACTION_SEARCH} {@link android.content.Intent Intent}.
+ * </li>
+ * <li>A reference to a small XML file (typically called "searchable.xml") which contains the
+ * remaining configuration information for how your application implements search.</li></ul>
+ * 
+ * <p>Here is a snippet showing the necessary elements in the 
+ * <a href="{@docRoot}guide/topics/manifest/manifest-intro.html">manifest</a> entry for your searchable activity.
+ * <pre class="prettyprint">
+ *        &lt;!-- Search Activity - searchable --&gt;
+ *        &lt;activity android:name="MySearchActivity" 
+ *                  android:label="Search"
+ *                  android:launchMode="singleTop"&gt;
+ *            &lt;intent-filter&gt;
+ *                &lt;action android:name="android.intent.action.SEARCH" /&gt;
+ *                &lt;category android:name="android.intent.category.DEFAULT" /&gt;
+ *            &lt;/intent-filter&gt;
+ *            &lt;meta-data android:name="android.app.searchable" 
+ *                       android:resource="@xml/searchable" /&gt;
+ *        &lt;/activity&gt;</pre>
+ *
+ * <p>Next, you must provide the rest of the searchability configuration in 
+ * the small XML file, stored in the ../xml/ folder in your build.  The XML file is a 
+ * simple enumeration of the search configuration parameters for searching within this activity,
+ * application, or package.  Here is a sample XML file (named searchable.xml, for use with
+ * the above manifest) for a query-search activity.
+ *
+ * <pre class="prettyprint">
+ * &lt;searchable xmlns:android="http://schemas.android.com/apk/res/android"
+ *     android:label="@string/search_label"
+ *     android:hint="@string/search_hint" &gt;
+ * &lt;/searchable&gt;</pre>
+ *
+ * <p>Note that all user-visible strings <i>must</i> be provided in the form of "@string" 
+ * references.  Hard-coded strings, which cannot be localized, will not work properly in search
+ * metadata.
+ * 
+ * <p>Attributes you can set in search metadata:
+ * <table border="2" width="85%" align="center" frame="hsides" rules="rows">
+ *
+ *     <thead>
+ *     <tr><th>Attribute</th> <th>Description</th> <th>Required?</th></tr>
+ *     </thead>
+ *
+ *     <tbody>
+ *     <tr><th>android:label</th>
+ *         <td>This is the name for your application that will be presented to the user in a 
+ *             list of search targets, or in the search box as a label.</td>
+ *         <td align="center">Yes</td>
+ *     </tr>
+ *     
+ *     <tr><th>android:icon</th>
+ *         <td>If provided, this icon will be used <i>in place</i> of the label string.  This
+ *         is provided in order to present logos or other non-textual banners.</td>
+ *         <td align="center">No</td>
+ *     </tr>
+ *     
+ *     <tr><th>android:hint</th>
+ *         <td>This is the text to display in the search text field when no user text has been 
+ *             entered.</td>
+ *         <td align="center">No</td>
+ *     </tr>
+ *     
+ *     <tr><th>android:searchButtonText</th>
+ *         <td>If provided, this text will replace the default text in the "Search" button.</td>
+ *         <td align="center">No</td>
+ *     </tr>
+ *     
+ *     <tr><th>android:searchMode</th>
+ *         <td>If provided and non-zero, sets additional modes for control of the search 
+ *             presentation.  The following mode bits are defined:
+ *             <table border="2" align="center" frame="hsides" rules="rows">
+ *                 <tbody>
+ *                 <tr><th>showSearchLabelAsBadge</th>
+ *                     <td>If set, this flag enables the display of the search target (label) 
+ *                         within the search bar.  If this flag and showSearchIconAsBadge
+ *                         (see below) are both not set, no badge will be shown.</td>
+ *                 </tr>
+ *                 <tr><th>showSearchIconAsBadge</th>
+ *                     <td>If set, this flag enables the display of the search target (icon) within
+ *                         the search bar.  If this flag and showSearchLabelAsBadge
+ *                         (see above) are both not set, no badge will be shown.  If both flags
+ *                         are set, showSearchIconAsBadge has precedence and the icon will be
+ *                         shown.</td>
+ *                 </tr>
+ *                 <tr><th>queryRewriteFromData</th>
+ *                     <td>If set, this flag causes the suggestion column SUGGEST_COLUMN_INTENT_DATA
+ *                         to be considered as the text for suggestion query rewriting.  This should
+ *                         only be used when the values in SUGGEST_COLUMN_INTENT_DATA are suitable
+ *                         for user inspection and editing - typically, HTTP/HTTPS Uri's.</td>
+ *                 </tr>
+ *                 <tr><th>queryRewriteFromText</th>
+ *                     <td>If set, this flag causes the suggestion column SUGGEST_COLUMN_TEXT_1 to 
+ *                         be considered as the text for suggestion query rewriting.  This should 
+ *                         be used for suggestions in which no query text is provided and the 
+ *                         SUGGEST_COLUMN_INTENT_DATA values are not suitable for user inspection 
+ *                         and editing.</td>
+ *                 </tr>
+ *                 </tbody>
+ *            </table></td>
+ *         <td align="center">No</td>
+ *     </tr>
+ *     
+ *     <tr><th>android:inputType</th>
+ *         <td>If provided, supplies a hint about the type of search text the user will be
+ *             entering.  For most searches, in which free form text is expected, this attribute
+ *             need not be provided.  Suitable values for this attribute are described in the
+ *             <a href="../R.attr.html#inputType">inputType</a> attribute.</td>
+ *         <td align="center">No</td>
+ *     </tr>
+ *     <tr><th>android:imeOptions</th>
+ *         <td>If provided, supplies additional options for the input method.
+ *             For most searches, in which free form text is expected, this attribute
+ *             need not be provided, and will default to "actionSearch".
+ *             Suitable values for this attribute are described in the
+ *             <a href="../R.attr.html#imeOptions">imeOptions</a> attribute.</td>
+ *         <td align="center">No</td>
+ *     </tr>
+ *     
+ *     </tbody>
+ * </table>
+ * 
+ * <p><b>Styleable Resources in your Metadata.</b>  It's possible to provide alternate strings
+ * for your searchable application, in order to provide localization and/or to better visual 
+ * presentation on different device configurations.  Each searchable activity has a single XML 
+ * metadata file, but any resource references can be replaced at runtime based on device
+ * configuration, language setting, and other system inputs.
+ * 
+ * <p>A concrete example is the "hint" text you supply using the android:searchHint attribute.
+ * In portrait mode you'll have less screen space and may need to provide a shorter string, but
+ * in landscape mode you can provide a longer, more descriptive hint.  To do this, you'll need to
+ * define two or more strings.xml files, in the following directories:
+ * <ul><li>.../res/values-land/strings.xml</li>
+ * <li>.../res/values-port/strings.xml</li>
+ * <li>.../res/values/strings.xml</li></ul>
+ * 
+ * <p>For more complete documentation on this capability, see
+ * <a href="{@docRoot}guide/topics/resources/resources-i18n.html#AlternateResources">Resources and 
+ * Internationalization: Alternate Resources</a>.
+ *
+ * <p><b>Metadata for non-searchable activities.</b>  Activities which are part of a searchable
+ * application, but don't implement search itself, require a bit of "glue" in order to cause
+ * them to invoke search using your searchable activity as their primary context.  If this is not
+ * provided, then searches from these activities will use the system default search context.
+ * 
+ * <p>The simplest way to specify this is to add a <i>search reference</i> element to the
+ * application entry in the <a href="{@docRoot}guide/topics/manifest/manifest-intro.html">manifest</a> file.  
+ * The value of this reference can be either of:
+ * <ul><li>The name of your searchable activity.  
+ * It is typically prefixed by '.' to indicate that it's in the same package.</li>
+ * <li>A "*" indicates that the system may select a default searchable activity, in which
+ * case it will typically select web-based search.</li>
+ * </ul>
+ *
+ * <p>Here is a snippet showing the necessary addition to the manifest entry for your 
+ * non-searchable activities.
+ * <pre class="prettyprint">
+ *        &lt;application&gt;
+ *            &lt;meta-data android:name="android.app.default_searchable"
+ *                       android:value=".MySearchActivity" /&gt;
+ *            
+ *            &lt;!-- followed by activities, providers, etc... --&gt;
+ *        &lt;/application&gt;</pre>
+ *
+ * <p>You can also specify android.app.default_searchable on a per-activity basis, by including
+ * the meta-data element (as shown above) in one or more activity sections.  If found, these will
+ * override the reference in the application section.  The only reason to configure your application
+ * this way would be if you wish to partition it into separate sections with different search 
+ * behaviors;  Otherwise this configuration is not recommended.
+ * 
+ * <p><b>Additional Metadata for search suggestions.</b>  If you have defined a content provider
+ * to generate search suggestions, you'll need to publish it to the system, and you'll need to 
+ * provide a bit of additional XML metadata in order to configure communications with it.
+ * 
+ * <p>First, in your <a href="{@docRoot}guide/topics/manifest/manifest-intro.html">manifest</a>, you'll add the
+ * following lines.
+ * <pre class="prettyprint">
+ *        &lt;!-- Content provider for search suggestions --&gt;
+ *        &lt;provider android:name="YourSuggestionProviderClass"
+ *                android:authorities="your.suggestion.authority" /&gt;</pre>
+ * 
+ * <p>Next, you'll add a few lines to your XML metadata file, as shown:
+ * <pre class="prettyprint">
+ *     &lt;!-- Required attribute for any suggestions provider --&gt;
+ *     android:searchSuggestAuthority="your.suggestion.authority"
+ *     
+ *     &lt;!-- Optional attribute for configuring queries --&gt;
+ *     android:searchSuggestSelection="field =?"
+ *     
+ *     &lt;!-- Optional attributes for configuring intent construction --&gt;
+ *     android:searchSuggestIntentAction="intent action string"
+ *     android:searchSuggestIntentData="intent data Uri" /&gt;</pre>
+ * 
+ * <p>Elements of search metadata that support suggestions:
+ * <table border="2" width="85%" align="center" frame="hsides" rules="rows">
+ *
+ *     <thead>
+ *     <tr><th>Attribute</th> <th>Description</th> <th>Required?</th></tr>
+ *     </thead>
+ *
+ *     <tbody>
+ *     <tr><th>android:searchSuggestAuthority</th>
+ *         <td>This value must match the authority string provided in the <i>provider</i> section 
+ *             of your <a href="{@docRoot}guide/topics/manifest/manifest-intro.html">manifest</a>.</td>
+ *         <td align="center">Yes</td>
+ *     </tr>
+ *     
+ *     <tr><th>android:searchSuggestPath</th>
+ *         <td>If provided, this will be inserted in the suggestions query Uri, after the authority
+ *             you have provide but before the standard suggestions path.  This is only required if
+ *             you have a single content provider issuing different types of suggestions (e.g. for
+ *             different data types) and you need a way to disambiguate the suggestions queries
+ *             when they are received.</td>
+ *         <td align="center">No</td>
+ *     </tr>
+ *     
+ *     <tr><th>android:searchSuggestSelection</th>
+ *         <td>If provided, this value will be passed into your query function as the 
+ *             <i>selection</i> parameter.  Typically this will be a WHERE clause for your database, 
+ *             and will contain a single question mark, which represents the actual query string 
+ *             that has been typed by the user.  However, you can also use any non-null value
+ *             to simply trigger the delivery of the query text (via selection arguments), and then
+ *             use the query text in any way appropriate for your provider (ignoring the actual
+ *             text of the selection parameter.)</td>
+ *         <td align="center">No</td>
+ *     </tr>
+ *     
+ *     <tr><th>android:searchSuggestIntentAction</th>
+ *         <td>If provided, and not overridden by the selected suggestion, this value will be 
+ *             placed in the action field of the {@link android.content.Intent Intent} when the 
+ *             user clicks a suggestion.</td>
+ *         <td align="center">No</td>
+ *     
+ *     <tr><th>android:searchSuggestIntentData</th>
+ *         <td>If provided, and not overridden by the selected suggestion, this value will be 
+ *             placed in the data field of the {@link android.content.Intent Intent} when the user 
+ *             clicks a suggestion.</td>
+ *         <td align="center">No</td>
+ *     </tr>
+ *     
+ *     </tbody>
+ * </table>
+ * 
+ * <p><b>Additional Metadata for search action keys.</b>  For each action key that you would like to
+ * define, you'll need to add an additional element defining that key, and using the attributes
+ * discussed in <a href="#ActionKeys">Action Keys</a>.  A simple example is shown here:
+ * 
+ * <pre class="prettyprint">&lt;actionkey
+ *     android:keycode="KEYCODE_CALL"
+ *     android:queryActionMsg="call"
+ *     android:suggestActionMsg="call"
+ *     android:suggestActionMsgColumn="call_column" /&gt;</pre>
+ *
+ * <p>Elements of search metadata that support search action keys.  Note that although each of the
+ * action message elements are marked as <i>optional</i>, at least one must be present for the 
+ * action key to have any effect.
+ * 
+ * <table border="2" width="85%" align="center" frame="hsides" rules="rows">
+ *
+ *     <thead>
+ *     <tr><th>Attribute</th> <th>Description</th> <th>Required?</th></tr>
+ *     </thead>
+ *
+ *     <tbody>
+ *     <tr><th>android:keycode</th>
+ *         <td>This attribute denotes the action key you wish to respond to.  Note that not
+ *             all action keys are actually supported using this mechanism, as many of them are
+ *             used for typing, navigation, or system functions.  This will be added to the 
+ *             {@link android.content.Intent#ACTION_SEARCH ACTION_SEARCH} intent that is passed to 
+ *             your searchable activity.  To examine the key code, use 
+ *             {@link android.content.Intent#getIntExtra getIntExtra(SearchManager.ACTION_KEY)}.  
+ *             <p>Note, in addition to the keycode, you must also provide one or more of the action
+ *             specifier attributes.</td>
+ *         <td align="center">Yes</td>
+ *     </tr>
+ *     
+ *     <tr><th>android:queryActionMsg</th>
+ *         <td>If you wish to handle an action key during normal search query entry, you
+ *          must define an action string here.  This will be added to the 
+ *          {@link android.content.Intent#ACTION_SEARCH ACTION_SEARCH} intent that is passed to your
+ *          searchable activity.  To examine the string, use 
+ *          {@link android.content.Intent#getStringExtra 
+ *          getStringExtra(SearchManager.ACTION_MSG)}.</td>
+ *         <td align="center">No</td>
+ *     </tr>
+ *     
+ *     <tr><th>android:suggestActionMsg</th>
+ *         <td>If you wish to handle an action key while a suggestion is being displayed <i>and
+ *             selected</i>, there are two ways to handle this.  If <i>all</i> of your suggestions
+ *             can handle the action key, you can simply define the action message using this 
+ *             attribute.  This will be added to the 
+ *             {@link android.content.Intent#ACTION_SEARCH ACTION_SEARCH} intent that is passed to
+ *             your searchable activity.  To examine the string, use 
+ *             {@link android.content.Intent#getStringExtra 
+ *             getStringExtra(SearchManager.ACTION_MSG)}.</td>
+ *         <td align="center">No</td>
+ *     </tr>
+ *     
+ *     <tr><th>android:suggestActionMsgColumn</th>
+ *         <td>If you wish to handle an action key while a suggestion is being displayed <i>and
+ *             selected</i>, but you do not wish to enable this action key for every suggestion, 
+ *             then you can use this attribute to control it on a suggestion-by-suggestion basis.
+ *             First, you must define a column (and name it here) where your suggestions will 
+ *             include the action string.  Then, in your content provider, you must provide this
+ *             column, and when desired, provide data in this column.
+ *             The search manager will look at your suggestion cursor, using the string 
+ *             provided here in order to select a column, and will use that to select a string from 
+ *             the cursor.  That string will be added to the 
+ *             {@link android.content.Intent#ACTION_SEARCH ACTION_SEARCH} intent that is passed to 
+ *             your searchable activity.  To examine the string, use 
+ *             {@link android.content.Intent#getStringExtra 
+ *             getStringExtra(SearchManager.ACTION_MSG)}.  <i>If the data does not exist for the
+ *             selection suggestion, the action key will be ignored.</i></td>
+ *         <td align="center">No</td>
+ *     </tr>
+ * 
+ *     </tbody>
+ * </table>
+ * 
+ * <a name="PassingSearchContext"></a>
+ * <h3>Passing Search Context</h3>
+ * 
+ * <p>In order to improve search experience, an application may wish to specify
+ * additional data along with the search, such as local history or context.  For
+ * example, a maps search would be improved by including the current location.  
+ * In order to simplify the structure of your activities, this can be done using 
+ * the search manager.
+ *
+ * <p>Any data can be provided at the time the search is launched, as long as it
+ * can be stored in a {@link android.os.Bundle Bundle} object.
+ *
+ * <p>To pass application data into the Search Manager, you'll need to override
+ * {@link android.app.Activity#onSearchRequested onSearchRequested} as follows:
+ *
+ * <pre class="prettyprint">
+ * &#64;Override
+ * public boolean onSearchRequested() {
+ *     Bundle appData = new Bundle();
+ *     appData.put...();
+ *     appData.put...();
+ *     startSearch(null, false, appData);
+ *     return true;
+ * }</pre> 
+ *
+ * <p>To receive application data from the Search Manager, you'll extract it from
+ * the {@link android.content.Intent#ACTION_SEARCH ACTION_SEARCH}
+ * {@link android.content.Intent Intent} as follows:
+ *
+ * <pre class="prettyprint">
+ * final Bundle appData = queryIntent.getBundleExtra(SearchManager.APP_DATA);
+ * if (appData != null) {
+ *     appData.get...();
+ *     appData.get...();
+ * }</pre>
+ * 
+ * <a name="ProtectingUserPrivacy"></a>
+ * <h3>Protecting User Privacy</h3>
+ * 
+ * <p>Many users consider their activities on the phone, including searches, to be private 
+ * information.  Applications that implement search should take steps to protect users' privacy
+ * wherever possible.  This section covers two areas of concern, but you should consider your search
+ * design carefully and take any additional steps necessary.
+ * 
+ * <p><b>Don't send personal information to servers, and if you do, don't log it.</b>
+ * "Personal information" is information that can personally identify your users, such as name, 
+ * email address or billing information, or other data which can be reasonably linked to such 
+ * information.  If your application implements search with the assistance of a server, try to 
+ * avoid sending personal information with your searches.  For example, if you are searching for 
+ * businesses near a zip code, you don't need to send the user ID as well - just send the zip code
+ * to the server.  If you do need to send personal information, you should take steps to avoid 
+ * logging it.  If you must log it, you should protect that data very carefully, and erase it as 
+ * soon as possible.
+ * 
+ * <p><b>Provide the user with a way to clear their search history.</b>  The Search Manager helps
+ * your application provide context-specific suggestions.  Sometimes these suggestions are based
+ * on previous searches, or other actions taken by the user in an earlier session.  A user may not
+ * wish for previous searches to be revealed to other users, for instance if they share their phone
+ * with a friend.  If your application provides suggestions that can reveal previous activities,
+ * you should implement a "Clear History" menu, preference, or button.  If you are using 
+ * {@link android.provider.SearchRecentSuggestions}, you can simply call its 
+ * {@link android.provider.SearchRecentSuggestions#clearHistory() clearHistory()} method from
+ * your "Clear History" UI.  If you are implementing your own form of recent suggestions, you'll 
+ * need to provide a similar a "clear history" API in your provider, and call it from your
+ * "Clear History" UI.
+ */
+public class SearchManager 
+        implements DialogInterface.OnDismissListener, DialogInterface.OnCancelListener
+{
+    /**
+     * This is a shortcut definition for the default menu key to use for invoking search.
+     * 
+     * See Menu.Item.setAlphabeticShortcut() for more information.
+     */
+    public final static char MENU_KEY = 's';
+
+    /**
+     * This is a shortcut definition for the default menu key to use for invoking search.
+     * 
+     * See Menu.Item.setAlphabeticShortcut() for more information.
+     */
+    public final static int MENU_KEYCODE = KeyEvent.KEYCODE_S;
+
+    /**
+     * Intent extra data key: Use this key with 
+     * {@link android.content.Intent#getStringExtra
+     *  content.Intent.getStringExtra()}
+     * to obtain the query string from Intent.ACTION_SEARCH.
+     */
+    public final static String QUERY = "query";
+
+    /**
+     * Intent extra data key: Use this key with Intent.ACTION_SEARCH and
+     * {@link android.content.Intent#getBundleExtra
+     *  content.Intent.getBundleExtra()}
+     * to obtain any additional app-specific data that was inserted by the 
+     * activity that launched the search.
+     */
+    public final static String APP_DATA = "app_data";
+
+    /**
+     * Intent app_data bundle key: Use this key with the bundle from
+     * {@link android.content.Intent#getBundleExtra
+     * content.Intent.getBundleExtra(APP_DATA)} to obtain the source identifier
+     * set by the activity that launched the search.
+     *
+     * @hide
+     */
+    public final static String SOURCE = "source";
+    
+    /**
+     * Intent extra data key: Use this key with Intent.ACTION_SEARCH and
+     * {@link android.content.Intent#getIntExtra content.Intent.getIntExtra()}
+     * to obtain the keycode that the user used to trigger this query.  It will be zero if the
+     * user simply pressed the "GO" button on the search UI.  This is primarily used in conjunction
+     * with the keycode attribute in the actionkey element of your searchable.xml configuration
+     * file.
+     */
+    public final static String ACTION_KEY = "action_key";
+    
+    /**
+     * Intent extra data key: Use this key with Intent.ACTION_SEARCH and
+     * {@link android.content.Intent#getStringExtra content.Intent.getStringExtra()}
+     * to obtain the action message that was defined for a particular search action key and/or
+     * suggestion.  It will be null if the search was launched by typing "enter", touched the the 
+     * "GO" button, or other means not involving any action key. 
+     */
+    public final static String ACTION_MSG = "action_msg";
+    
+    /**
+     * Uri path for queried suggestions data.  This is the path that the search manager
+     * will use when querying your content provider for suggestions data based on user input
+     * (e.g. looking for partial matches).
+     * Typically you'll use this with a URI matcher.
+     */
+    public final static String SUGGEST_URI_PATH_QUERY = "search_suggest_query";
+    
+    /**
+     * MIME type for suggestions data.  You'll use this in your suggestions content provider
+     * in the getType() function.
+     */
+    public final static String SUGGEST_MIME_TYPE = 
+                                  "vnd.android.cursor.dir/vnd.android.search.suggest";
+
+    /**
+     * Column name for suggestions cursor.  <i>Unused - can be null or column can be omitted.</i>
+     */
+    public final static String SUGGEST_COLUMN_FORMAT = "suggest_format";
+    /**
+     * Column name for suggestions cursor.  <i>Required.</i>  This is the primary line of text that 
+     * will be presented to the user as the suggestion.
+     */
+    public final static String SUGGEST_COLUMN_TEXT_1 = "suggest_text_1";
+    /**
+     * Column name for suggestions cursor.  <i>Optional.</i>  If your cursor includes this column,
+     *  then all suggestions will be provided in a two-line format.  The second line of text is in
+     *  a much smaller appearance.
+     */
+    public final static String SUGGEST_COLUMN_TEXT_2 = "suggest_text_2";
+    /**
+     * Column name for suggestions cursor.  <i>Optional.</i>  If your cursor includes this column,
+     *  then all suggestions will be provided in format that includes space for two small icons,
+     *  one at the left and one at the right of each suggestion.  The data in the column must
+     *  be a a resource ID for the icon you wish to have displayed.  If you include this column,
+     *  you must also include {@link #SUGGEST_COLUMN_ICON_2}.
+     */
+    public final static String SUGGEST_COLUMN_ICON_1 = "suggest_icon_1";
+    /**
+     * Column name for suggestions cursor.  <i>Optional.</i>  If your cursor includes this column,
+     *  then all suggestions will be provided in format that includes space for two small icons,
+     *  one at the left and one at the right of each suggestion.  The data in the column must
+     *  be a a resource ID for the icon you wish to have displayed.  If you include this column,
+     *  you must also include {@link #SUGGEST_COLUMN_ICON_1}.
+     */
+    public final static String SUGGEST_COLUMN_ICON_2 = "suggest_icon_2";
+    /**
+     * Column name for suggestions cursor.  <i>Optional.</i>  If this column exists <i>and</i>
+     * this element exists at the given row, this is the action that will be used when
+     * forming the suggestion's intent.  If the element is not provided, the action will be taken
+     * from the android:searchSuggestIntentAction field in your XML metadata.  <i>At least one of
+     * these must be present for the suggestion to generate an intent.</i>  Note:  If your action is
+     * the same for all suggestions, it is more efficient to specify it using XML metadata and omit
+     * it from the cursor.
+     */
+    public final static String SUGGEST_COLUMN_INTENT_ACTION = "suggest_intent_action";
+    /**
+     * Column name for suggestions cursor.  <i>Optional.</i>  If this column exists <i>and</i>
+     * this element exists at the given row, this is the data that will be used when
+     * forming the suggestion's intent.  If the element is not provided, the data will be taken
+     * from the android:searchSuggestIntentData field in your XML metadata.  If neither source
+     * is provided, the Intent's data field will be null.  Note:  If your data is
+     * the same for all suggestions, or can be described using a constant part and a specific ID,
+     * it is more efficient to specify it using XML metadata and omit it from the cursor.
+     */
+    public final static String SUGGEST_COLUMN_INTENT_DATA = "suggest_intent_data";
+    /**
+     * Column name for suggestions cursor.  <i>Optional.</i>  If this column exists <i>and</i>
+     * this element exists at the given row, then "/" and this value will be appended to the data
+     * field in the Intent.  This should only be used if the data field has already been set to an
+     * appropriate base string.
+     */
+    public final static String SUGGEST_COLUMN_INTENT_DATA_ID = "suggest_intent_data_id";
+    /**
+     * Column name for suggestions cursor.  <i>Required if action is 
+     * {@link android.content.Intent#ACTION_SEARCH ACTION_SEARCH}, optional otherwise.</i>  If this 
+     * column exists <i>and</i> this element exists at the given row, this is the data that will be
+     * used when forming the suggestion's query.
+     */
+    public final static String SUGGEST_COLUMN_QUERY = "suggest_intent_query";
+
+
+    private final Context mContext;
+    private final Handler mHandler;
+    
+    private SearchDialog mSearchDialog;
+    
+    private OnDismissListener mDismissListener = null;
+    private OnCancelListener mCancelListener = null;
+
+    /*package*/ SearchManager(Context context, Handler handler)  {
+        mContext = context;
+        mHandler = handler;
+    }
+    private static ISearchManager mService;
+
+    static {
+        mService = ISearchManager.Stub.asInterface(
+                    ServiceManager.getService(Context.SEARCH_SERVICE));
+    }
+    
+    /**
+     * Launch search UI.
+     *
+     * <p>The search manager will open a search widget in an overlapping
+     * window, and the underlying activity may be obscured.  The search 
+     * entry state will remain in effect until one of the following events:
+     * <ul>
+     * <li>The user completes the search.  In most cases this will launch 
+     * a search intent.</li>
+     * <li>The user uses the back, home, or other keys to exit the search.</li>
+     * <li>The application calls the {@link #stopSearch}
+     * method, which will hide the search window and return focus to the
+     * activity from which it was launched.</li>
+     *
+     * <p>Most applications will <i>not</i> use this interface to invoke search.
+     * The primary method for invoking search is to call 
+     * {@link android.app.Activity#onSearchRequested Activity.onSearchRequested()} or 
+     * {@link android.app.Activity#startSearch Activity.startSearch()}.
+     *
+     * @param initialQuery A search string can be pre-entered here, but this
+     * is typically null or empty.
+     * @param selectInitialQuery If true, the intial query will be preselected, which means that
+     * any further typing will replace it.  This is useful for cases where an entire pre-formed
+     * query is being inserted.  If false, the selection point will be placed at the end of the
+     * inserted query.  This is useful when the inserted query is text that the user entered,
+     * and the user would expect to be able to keep typing.  <i>This parameter is only meaningful
+     * if initialQuery is a non-empty string.</i>
+     * @param launchActivity The ComponentName of the activity that has launched this search.
+     * @param appSearchData An application can insert application-specific 
+     * context here, in order to improve quality or specificity of its own 
+     * searches.  This data will be returned with SEARCH intent(s).  Null if
+     * no extra data is required.
+     * @param globalSearch If false, this will only launch the search that has been specifically
+     * defined by the application (which is usually defined as a local search).  If no default 
+     * search is defined in the current application or activity, no search will be launched.
+     * If true, this will always launch a platform-global (e.g. web-based) search instead.
+     * 
+     * @see android.app.Activity#onSearchRequested
+     * @see #stopSearch
+     */
+    public void startSearch(String initialQuery, 
+                            boolean selectInitialQuery,
+                            ComponentName launchActivity,
+                            Bundle appSearchData,
+                            boolean globalSearch) {
+        
+        if (mSearchDialog == null) {
+            mSearchDialog = new SearchDialog(mContext);
+        }
+
+        // activate the search manager and start it up!
+        mSearchDialog.show(initialQuery, selectInitialQuery, launchActivity, appSearchData, 
+                globalSearch);
+        
+        mSearchDialog.setOnCancelListener(this);
+        mSearchDialog.setOnDismissListener(this);
+    }
+
+    /**
+     * Terminate search UI.
+     *
+     * <p>Typically the user will terminate the search UI by launching a
+     * search or by canceling.  This function allows the underlying application
+     * or activity to cancel the search prematurely (for any reason).
+     * 
+     * <p>This function can be safely called at any time (even if no search is active.)
+     *
+     * @see #startSearch
+     */
+    public void stopSearch()  {
+        if (mSearchDialog != null) {
+            mSearchDialog.cancel();
+        }
+    }
+
+    /**
+     * Determine if the Search UI is currently displayed.  
+     * 
+     * This is provided primarily for application test purposes.
+     *
+     * @return Returns true if the search UI is currently displayed.
+     * 
+     * @hide
+     */
+    public boolean isVisible()  {
+        if (mSearchDialog != null) {
+            return mSearchDialog.isShowing();
+        }
+        return false;
+    }
+    
+    /**
+     * See {@link #setOnDismissListener} for configuring your activity to monitor search UI state.
+     */
+    public interface OnDismissListener {
+        /**
+         * This method will be called when the search UI is dismissed. To make use if it, you must
+         * implement this method in your activity, and call {@link #setOnDismissListener} to 
+         * register it.
+         */
+        public void onDismiss();
+    }
+    
+    /**
+     * See {@link #setOnCancelListener} for configuring your activity to monitor search UI state.
+     */
+    public interface OnCancelListener {
+        /**
+         * This method will be called when the search UI is canceled. To make use if it, you must
+         * implement this method in your activity, and call {@link #setOnCancelListener} to 
+         * register it.
+         */
+        public void onCancel();
+    }
+
+    /**
+     * Set or clear the callback that will be invoked whenever the search UI is dismissed.
+     * 
+     * @param listener The {@link OnDismissListener} to use, or null.
+     */
+    public void setOnDismissListener(final OnDismissListener listener) {
+        mDismissListener = listener;
+    }
+    
+    /**
+     * The callback from the search dialog when dismissed
+     * @hide
+     */
+    public void onDismiss(DialogInterface dialog) {
+        if (dialog == mSearchDialog) {
+            if (mDismissListener != null) {
+                mDismissListener.onDismiss();
+            }
+        }
+    }
+
+    /**
+     * Set or clear the callback that will be invoked whenever the search UI is canceled.
+     * 
+     * @param listener The {@link OnCancelListener} to use, or null.
+     */
+    public void setOnCancelListener(final OnCancelListener listener) {
+        mCancelListener = listener;
+    }
+    
+    
+    /**
+     * The callback from the search dialog when canceled
+     * @hide
+     */
+    public void onCancel(DialogInterface dialog) {
+        if (dialog == mSearchDialog) {
+            if (mCancelListener != null) {
+                mCancelListener.onCancel();
+            }
+        }
+    }
+
+    /**
+     * Save instance state so we can recreate after a rotation.
+     * 
+     * @hide
+     */
+    void saveSearchDialog(Bundle outState, String key) {
+        if (mSearchDialog != null && mSearchDialog.isShowing()) {
+            Bundle searchDialogState = mSearchDialog.onSaveInstanceState();
+            outState.putBundle(key, searchDialogState);
+        }
+    }
+
+    /**
+     * Restore instance state after a rotation.
+     * 
+     * @hide
+     */
+    void restoreSearchDialog(Bundle inState, String key) {        
+        Bundle searchDialogState = inState.getBundle(key);
+        if (searchDialogState != null) {
+            if (mSearchDialog == null) {
+                mSearchDialog = new SearchDialog(mContext);
+            }
+            mSearchDialog.onRestoreInstanceState(searchDialogState);
+        }
+    }
+    
+    /**
+     * Hook for updating layout on a rotation
+     * 
+     * @hide
+     */
+    void onConfigurationChanged(Configuration newConfig) {
+        if (mSearchDialog != null && mSearchDialog.isShowing()) {
+            mSearchDialog.onConfigurationChanged(newConfig);
+        }
+    }
+      
+}
diff --git a/core/java/android/app/Service.java b/core/java/android/app/Service.java
new file mode 100644
index 0000000..a6a436f
--- /dev/null
+++ b/core/java/android/app/Service.java
@@ -0,0 +1,378 @@
+/*
+ * 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 android.app;
+
+import android.content.ComponentCallbacks;
+import android.content.ComponentName;
+import android.content.Intent;
+import android.content.ContextWrapper;
+import android.content.Context;
+import android.content.res.Configuration;
+import android.os.RemoteException;
+import android.os.IBinder;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+
+/**
+ * A Service is an application component that runs in the background, not
+ * interacting with the user, for an indefinite period of time.  Each service
+ * class must have a corresponding
+ * {@link android.R.styleable#AndroidManifestService &lt;service&gt;}
+ * declaration in its package's <code>AndroidManifest.xml</code>.  Services
+ * can be started with
+ * {@link android.content.Context#startService Context.startService()} and
+ * {@link android.content.Context#bindService Context.bindService()}.
+ * 
+ * <p>Note that services, like other application objects, run in the main
+ * thread of their hosting process.  This means that, if your service is going
+ * to do any CPU intensive (such as MP3 playback) or blocking (such as
+ * networking) operations, it should spawn its own thread in which to do that
+ * work.  More information on this can be found in
+ * <a href="{@docRoot}guide/topics/fundamentals.html#procthread">Application Fundamentals:
+ * Processes and Threads</a>.</p>
+ * 
+ * <p>The Service class is an important part of an
+ * <a href="{@docRoot}guide/topics/fundamentals.html#lcycles">application's overall lifecycle</a>.</p>
+ * 
+ * <p>Topics covered here:
+ * <ol>
+ * <li><a href="#ServiceLifecycle">Service Lifecycle</a>
+ * <li><a href="#Permissions">Permissions</a>
+ * <li><a href="#ProcessLifecycle">Process Lifecycle</a>
+ * </ol>
+ * 
+ * <a name="ServiceLifecycle"></a>
+ * <h3>Service Lifecycle</h3>
+ * 
+ * <p>There are two reasons that a service can be run by the system.  If someone
+ * calls {@link android.content.Context#startService Context.startService()} then the system will
+ * retrieve the service (creating it and calling its {@link #onCreate} method
+ * if needed) and then call its {@link #onStart} method with the
+ * arguments supplied by the client.  The service will at this point continue
+ * running until {@link android.content.Context#stopService Context.stopService()} or
+ * {@link #stopSelf()} is called.  Note that multiple calls to
+ * Context.startService() do not nest (though they do result in multiple corresponding
+ * calls to onStart()), so no matter how many times it is started a service
+ * will be stopped once Context.stopService() or stopSelf() is called.
+ * 
+ * <p>Clients can also use {@link android.content.Context#bindService Context.bindService()} to
+ * obtain a persistent connection to a service.  This likewise creates the
+ * service if it is not already running (calling {@link #onCreate} while
+ * doing so), but does not call onStart().  The client will receive the
+ * {@link android.os.IBinder} object that the service returns from its
+ * {@link #onBind} method, allowing the client to then make calls back
+ * to the service.  The service will remain running as long as the connection
+ * is established (whether or not the client retains a reference on the
+ * service's IBinder).  Usually the IBinder returned is for a complex
+ * interface that has been <a href="{@docRoot}guide/developing/tools/aidl.html">written
+ * in aidl</a>.
+ * 
+ * <p>A service can be both started and have connections bound to it.  In such
+ * a case, the system will keep the service running as long as either it is
+ * started <em>or</em> there are one or more connections to it with the
+ * {@link android.content.Context#BIND_AUTO_CREATE Context.BIND_AUTO_CREATE}
+ * flag.  Once neither
+ * of these situations hold, the service's {@link #onDestroy} method is called
+ * and the service is effectively terminated.  All cleanup (stopping threads,
+ * unregistering receivers) should be complete upon returning from onDestroy().
+ * 
+ * <a name="Permissions"></a>
+ * <h3>Permissions</h3>
+ * 
+ * <p>Global access to a service can be enforced when it is declared in its
+ * manifest's {@link android.R.styleable#AndroidManifestService &lt;service&gt;}
+ * tag.  By doing so, other applications will need to declare a corresponding
+ * {@link android.R.styleable#AndroidManifestUsesPermission &lt;uses-permission&gt;}
+ * element in their own manifest to be able to start, stop, or bind to
+ * the service.
+ * 
+ * <p>In addition, a service can protect individual IPC calls into it with
+ * permissions, by calling the
+ * {@link #checkCallingPermission}
+ * method before executing the implementation of that call.
+ * 
+ * <p>See the <a href="{@docRoot}guide/topics/security/security.html">Security and Permissions</a>
+ * document for more information on permissions and security in general.
+ * 
+ * <a name="ProcessLifecycle"></a>
+ * <h3>Process Lifecycle</h3>
+ * 
+ * <p>The Android system will attempt to keep the process hosting a service
+ * around as long as the service has been started or has clients bound to it.
+ * When running low on memory and needing to kill existing processes, the
+ * priority of a process hosting the service will be the higher of the
+ * following possibilities:
+ * 
+ * <ul>
+ * <li><p>If the service is currently executing code in its
+ * {@link #onCreate onCreate()}, {@link #onStart onStart()},
+ * or {@link #onDestroy onDestroy()} methods, then the hosting process will
+ * be a foreground process to ensure this code can execute without
+ * being killed.
+ * <li><p>If the service has been started, then its hosting process is considered
+ * to be less important than any processes that are currently visible to the
+ * user on-screen, but more important than any process not visible.  Because
+ * only a few processes are generally visible to the user, this means that
+ * the service should not be killed except in extreme low memory conditions.
+ * <li><p>If there are clients bound to the service, then the service's hosting
+ * process is never less important than the most important client.  That is,
+ * if one of its clients is visible to the user, then the service itself is
+ * considered to be visible.
+ * </ul>
+ * 
+ * <p>Note this means that most of the time your service is running, it may
+ * be killed by the system if it is under heavy memory pressure.  If this
+ * happens, the system will later try to restart the service.  An important
+ * consequence of this is that if you implement {@link #onStart onStart()}
+ * to schedule work to be done asynchronously or in another thread, then you
+ * may want to write information about that work into persistent storage
+ * during the onStart() call so that it does not get lost if the service later
+ * gets killed.
+ * 
+ * <p>Other application components running in the same process as the service
+ * (such as an {@link android.app.Activity}) can, of course, increase the
+ * importance of the overall
+ * process beyond just the importance of the service itself.
+ */
+public abstract class Service extends ContextWrapper implements ComponentCallbacks {
+    private static final String TAG = "Service";
+
+    public Service() {
+        super(null);
+    }
+
+    /** Return the application that owns this service. */
+    public final Application getApplication() {
+        return mApplication;
+    }
+
+    /**
+     * Called by the system when the service is first created.  Do not call this method directly.
+     */
+    public void onCreate() {
+    }
+
+    /**
+     * Called by the system every time a client explicitly starts the service by calling 
+     * {@link android.content.Context#startService}, providing the arguments it supplied and a 
+     * unique integer token representing the start request.  Do not call this method directly.
+     *  
+     * @param intent The Intent supplied to {@link android.content.Context#startService}, 
+     *                  as given.
+     * @param startId A unique integer representing this specific request to 
+     *                start.  Use with {@link #stopSelfResult(int)}.
+     * 
+     * @see #stopSelfResult(int)
+     */
+    public void onStart(Intent intent, int startId) {
+    }
+
+    /**
+     * Called by the system to notify a Service that it is no longer used and is being removed.  The
+     * service should clean up an resources it holds (threads, registered
+     * receivers, etc) at this point.  Upon return, there will be no more calls
+     * in to this Service object and it is effectively dead.  Do not call this method directly.
+     */
+    public void onDestroy() {
+    }
+
+    public void onConfigurationChanged(Configuration newConfig) {
+    }
+    
+    public void onLowMemory() {
+    }
+    
+    /**
+     * Return the communication channel to the service.  May return null if 
+     * clients can not bind to the service.  The returned
+     * {@link android.os.IBinder} is usually for a complex interface
+     * that has been <a href="{@docRoot}guide/developing/tools/aidl.html">described using
+     * aidl</a>.
+     * 
+     * <p><em>Note that unlike other application components, calls on to the
+     * IBinder interface returned here may not happen on the main thread
+     * of the process</em>.  More information about this can be found
+     * in <a href="{@docRoot}guide/topics/fundamentals.html#procthread">Application Fundamentals:
+     * Processes and Threads</a>.</p>
+     * 
+     * @param intent The Intent that was used to bind to this service,
+     * as given to {@link android.content.Context#bindService
+     * Context.bindService}.  Note that any extras that were included with
+     * the Intent at that point will <em>not</em> be seen here.
+     * 
+     * @return Return an IBinder through which clients can call on to the 
+     *         service.
+     */
+    public abstract IBinder onBind(Intent intent);
+
+    /**
+     * Called when all clients have disconnected from a particular interface
+     * published by the service.  The default implementation does nothing and
+     * returns false.
+     * 
+     * @param intent The Intent that was used to bind to this service,
+     * as given to {@link android.content.Context#bindService
+     * Context.bindService}.  Note that any extras that were included with
+     * the Intent at that point will <em>not</em> be seen here.
+     * 
+     * @return Return true if you would like to have the service's
+     * {@link #onRebind} method later called when new clients bind to it.
+     */
+    public boolean onUnbind(Intent intent) {
+        return false;
+    }
+    
+    /**
+     * Called when new clients have connected to the service, after it had
+     * previously been notified that all had disconnected in its
+     * {@link #onUnbind}.  This will only be called if the implementation
+     * of {@link #onUnbind} was overridden to return true.
+     * 
+     * @param intent The Intent that was used to bind to this service,
+     * as given to {@link android.content.Context#bindService
+     * Context.bindService}.  Note that any extras that were included with
+     * the Intent at that point will <em>not</em> be seen here.
+     */
+    public void onRebind(Intent intent) {
+    }
+    
+    /**
+     * Stop the service, if it was previously started.  This is the same as
+     * calling {@link android.content.Context#stopService} for this particular service.
+     *  
+     * @see #stopSelfResult(int)
+     */
+    public final void stopSelf() {
+        stopSelf(-1);
+    }
+
+    /**
+     * Old version of {@link #stopSelfResult} that doesn't return a result.
+     *  
+     * @see #stopSelfResult
+     */
+    public final void stopSelf(int startId) {
+        if (mActivityManager == null) {
+            return;
+        }
+        try {
+            mActivityManager.stopServiceToken(
+                    new ComponentName(this, mClassName), mToken, startId);
+        } catch (RemoteException ex) {
+        }
+    }
+    
+    /**
+     * Stop the service, if the most recent time it was started was 
+     * <var>startId</var>.  This is the same as calling {@link 
+     * android.content.Context#stopService} for this particular service but allows you to 
+     * safely avoid stopping if there is a start request from a client that you 
+     * haven't yet see in {@link #onStart}. 
+     * 
+     * @param startId The most recent start identifier received in {@link 
+     *                #onStart}.
+     * @return Returns true if the startId matches the last start request
+     * and the service will be stopped, else false.
+     *  
+     * @see #stopSelf()
+     */
+    public final boolean stopSelfResult(int startId) {
+        if (mActivityManager == null) {
+            return false;
+        }
+        try {
+            return mActivityManager.stopServiceToken(
+                    new ComponentName(this, mClassName), mToken, startId);
+        } catch (RemoteException ex) {
+        }
+        return false;
+    }
+    
+    /**
+     * Control whether this service is considered to be a foreground service.
+     * By default services are background, meaning that if the system needs to
+     * kill them to reclaim more memory (such as to display a large page in a
+     * web browser), they can be killed without too much harm.  You can set this
+     * flag if killing your service would be disruptive to the user: such as
+     * if your service is performing background music playback, so the user
+     * would notice if their music stopped playing.
+     * 
+     * @param isForeground Determines whether this service is considered to
+     * be foreground (true) or background (false).
+     */
+    public final void setForeground(boolean isForeground) {
+        if (mActivityManager == null) {
+            return;
+        }
+        try {
+            mActivityManager.setServiceForeground(
+                    new ComponentName(this, mClassName), mToken, isForeground);
+        } catch (RemoteException ex) {
+        }
+    }
+    
+    /**
+     * Print the Service's state into the given stream.  This gets invoked if
+     * you run "adb shell dumpsys activity service <yourservicename>".
+     * This is distinct from "dumpsys <servicename>", which only works for
+     * named system services and which invokes the {@link IBinder#dump} method
+     * on the {@link IBinder} interface registered with ServiceManager.
+     *
+     * @param fd The raw file descriptor that the dump is being sent to.
+     * @param writer The PrintWriter to which you should dump your state.  This will be
+     * closed for you after you return.
+     * @param args additional arguments to the dump request.
+     */
+    protected void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
+        writer.println("nothing to dump");
+    }
+    
+    @Override
+    protected void finalize() throws Throwable {
+        super.finalize();
+        //Log.i("Service", "Finalizing Service: " + this);
+    }
+
+    // ------------------ Internal API ------------------
+    
+    /**
+     * @hide
+     */
+    public final void attach(
+            Context context,
+            ActivityThread thread, String className, IBinder token,
+            Application application, Object activityManager) {
+        attachBaseContext(context);
+        mThread = thread;           // NOTE:  unused - remove?
+        mClassName = className;
+        mToken = token;
+        mApplication = application;
+        mActivityManager = (IActivityManager)activityManager;
+    }
+    
+    final String getClassName() {
+        return mClassName;
+    }
+
+    // set by the thread after the constructor and before onCreate(Bundle icicle) is called.
+    private ActivityThread mThread = null;
+    private String mClassName = null;
+    private IBinder mToken = null;
+    private Application mApplication = null;
+    private IActivityManager mActivityManager = null;
+}
diff --git a/core/java/android/app/StatusBarManager.java b/core/java/android/app/StatusBarManager.java
new file mode 100644
index 0000000..51d7393
--- /dev/null
+++ b/core/java/android/app/StatusBarManager.java
@@ -0,0 +1,139 @@
+/*
+ * Copyright (C) 2007 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 android.app;
+
+import android.content.Context;
+import android.os.Binder;
+import android.os.RemoteException;
+import android.os.IBinder;
+import android.os.ServiceManager;
+
+/**
+ * Allows an app to control the status bar.
+ *
+ * @hide
+ */
+public class StatusBarManager {
+    /**
+     * Flag for {@link #disable} to make the status bar not expandable.  Unless you also
+     * set {@link #DISABLE_NOTIFICATIONS}, new notifications will continue to show.
+     */
+    public static final int DISABLE_EXPAND = 0x00000001;
+
+    /**
+     * Flag for {@link #disable} to hide notification icons and ticker text.
+     */
+    public static final int DISABLE_NOTIFICATION_ICONS = 0x00000002;
+
+    /**
+     * Flag for {@link #disable} to disable incoming notification alerts.  This will not block
+     * icons, but it will block sound, vibrating and other visual or aural notifications.
+     */
+    public static final int DISABLE_NOTIFICATION_ALERTS = 0x00000004;
+
+    /**
+     * Re-enable all of the status bar features that you've disabled.
+     */
+    public static final int DISABLE_NONE = 0x00000000;
+
+    private Context mContext;
+    private IStatusBar mService;
+    private IBinder mToken = new Binder();
+
+    StatusBarManager(Context context) {
+        mContext = context;
+        mService = IStatusBar.Stub.asInterface(
+                ServiceManager.getService(Context.STATUS_BAR_SERVICE));
+    }
+
+    /**
+     * Disable some features in the status bar.  Pass the bitwise-or of the DISABLE_* flags.
+     * To re-enable everything, pass {@link #DISABLE_NONE}.
+     */
+    public void disable(int what) {
+        try {
+            mService.disable(what, mToken, mContext.getPackageName());
+        } catch (RemoteException ex) {
+            // system process is dead anyway.
+            throw new RuntimeException(ex);
+        }
+    }
+    
+    /**
+     * Expand the status bar.
+     */
+    public void expand() {
+        try {
+            mService.activate();
+        } catch (RemoteException ex) {
+            // system process is dead anyway.
+            throw new RuntimeException(ex);
+        }
+    }
+    
+    /**
+     * Collapse the status bar.
+     */
+    public void collapse() {
+        try {
+            mService.deactivate();
+        } catch (RemoteException ex) {
+            // system process is dead anyway.
+            throw new RuntimeException(ex);
+        }
+    }
+    
+    /**
+     * Toggle the status bar.
+     */
+    public void toggle() {
+        try {
+            mService.toggle();
+        } catch (RemoteException ex) {
+            // system process is dead anyway.
+            throw new RuntimeException(ex);
+        }
+    }
+
+    public IBinder addIcon(String slot, int iconId, int iconLevel) {
+        try {
+            return mService.addIcon(slot, mContext.getPackageName(), iconId, iconLevel);
+        } catch (RemoteException ex) {
+            // system process is dead anyway.
+            throw new RuntimeException(ex);
+        }
+    }
+
+    public void updateIcon(IBinder key, String slot, int iconId, int iconLevel) {
+        try {
+            mService.updateIcon(key, slot, mContext.getPackageName(), iconId, iconLevel);
+        } catch (RemoteException ex) {
+            // system process is dead anyway.
+            throw new RuntimeException(ex);
+        }
+    }
+
+    public void removeIcon(IBinder key) {
+        try {
+            mService.removeIcon(key);
+        } catch (RemoteException ex) {
+            // system process is dead anyway.
+            throw new RuntimeException(ex);
+        }
+    }
+}
diff --git a/core/java/android/app/TabActivity.java b/core/java/android/app/TabActivity.java
new file mode 100644
index 0000000..033fa0c
--- /dev/null
+++ b/core/java/android/app/TabActivity.java
@@ -0,0 +1,148 @@
+/*
+ * 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 android.app;
+
+import android.os.Bundle;
+import android.view.View;
+import android.widget.TabHost;
+import android.widget.TabWidget;
+import android.widget.TextView;
+
+/**
+ * An activity that contains and runs multiple embedded activities or views.
+ */
+public class TabActivity extends ActivityGroup {
+    private TabHost mTabHost;
+    private String mDefaultTab = null;
+    private int mDefaultTabIndex = -1;
+
+    public TabActivity() {
+    }
+
+    /**
+     * Sets the default tab that is the first tab highlighted.
+     * 
+     * @param tag the name of the default tab
+     */
+    public void setDefaultTab(String tag) {
+        mDefaultTab = tag;
+        mDefaultTabIndex = -1;
+    }
+
+    /**
+     * Sets the default tab that is the first tab highlighted.
+     * 
+     * @param index the index of the default tab
+     */
+    public void setDefaultTab(int index) {
+        mDefaultTab = null;
+        mDefaultTabIndex = index;
+    }
+
+    @Override
+    protected void onRestoreInstanceState(Bundle state) {
+        super.onRestoreInstanceState(state);
+        ensureTabHost();
+        String cur = state.getString("currentTab");
+        if (cur != null) {
+            mTabHost.setCurrentTabByTag(cur);
+        }
+        if (mTabHost.getCurrentTab() < 0) {
+            if (mDefaultTab != null) {
+                mTabHost.setCurrentTabByTag(mDefaultTab);
+            } else if (mDefaultTabIndex >= 0) {
+                mTabHost.setCurrentTab(mDefaultTabIndex);
+            }
+        }
+    }
+
+    @Override
+    protected void onPostCreate(Bundle icicle) {        
+        super.onPostCreate(icicle);
+
+        ensureTabHost();
+
+        if (mTabHost.getCurrentTab() == -1) {
+            mTabHost.setCurrentTab(0);
+        }
+    }
+
+    @Override
+    protected void onSaveInstanceState(Bundle outState) {
+        super.onSaveInstanceState(outState);
+        String currentTabTag = mTabHost.getCurrentTabTag();
+        if (currentTabTag != null) {
+            outState.putString("currentTab", currentTabTag);
+        }
+    }
+
+    /**
+     * Updates the screen state (current list and other views) when the
+     * content changes.
+     * 
+     *@see Activity#onContentChanged()
+     */
+    @Override
+    public void onContentChanged() {
+        super.onContentChanged();
+        mTabHost = (TabHost) findViewById(com.android.internal.R.id.tabhost);
+
+        if (mTabHost == null) {
+            throw new RuntimeException(
+                    "Your content must have a TabHost whose id attribute is " +
+                    "'android.R.id.tabhost'");
+        }
+        mTabHost.setup(getLocalActivityManager());
+    }
+
+    private void ensureTabHost() {
+        if (mTabHost == null) {
+            this.setContentView(com.android.internal.R.layout.tab_content);
+        }
+    }
+
+    @Override
+    protected void
+    onChildTitleChanged(Activity childActivity, CharSequence title) {
+        // Dorky implementation until we can have multiple activities running.
+        if (getLocalActivityManager().getCurrentActivity() == childActivity) {
+            View tabView = mTabHost.getCurrentTabView();
+            if (tabView != null && tabView instanceof TextView) {
+                ((TextView) tabView).setText(title);
+            }
+        }
+    }
+
+    /**
+     * Returns the {@link TabHost} the activity is using to host its tabs.
+     *
+     * @return the {@link TabHost} the activity is using to host its tabs.
+     */
+    public TabHost getTabHost() {
+        ensureTabHost();
+        return mTabHost;
+    }
+
+    /**
+     * Returns the {@link TabWidget} the activity is using to draw the actual tabs.
+     *
+     * @return the {@link TabWidget} the activity is using to draw the actual tabs.
+     */
+    public TabWidget getTabWidget() {
+        return mTabHost.getTabWidget();
+    }
+}
diff --git a/core/java/android/app/TimePickerDialog.java b/core/java/android/app/TimePickerDialog.java
new file mode 100644
index 0000000..002b01f
--- /dev/null
+++ b/core/java/android/app/TimePickerDialog.java
@@ -0,0 +1,162 @@
+/*
+ * Copyright (C) 2007 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 android.app;
+
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.DialogInterface.OnClickListener;
+import android.os.Bundle;
+import android.text.format.DateFormat;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.TimePicker;
+import android.widget.TimePicker.OnTimeChangedListener;
+
+import com.android.internal.R;
+
+import java.util.Calendar;
+
+/**
+ * A dialog that prompts the user for the time of day using a {@link TimePicker}.
+ */
+public class TimePickerDialog extends AlertDialog implements OnClickListener, 
+        OnTimeChangedListener {
+
+    /**
+     * The callback interface used to indicate the user is done filling in
+     * the time (they clicked on the 'Set' button).
+     */
+    public interface OnTimeSetListener {
+
+        /**
+         * @param view The view associated with this listener.
+         * @param hourOfDay The hour that was set.
+         * @param minute The minute that was set.
+         */
+        void onTimeSet(TimePicker view, int hourOfDay, int minute);
+    }
+
+    private static final String HOUR = "hour";
+    private static final String MINUTE = "minute";
+    private static final String IS_24_HOUR = "is24hour";
+    
+    private final TimePicker mTimePicker;
+    private final OnTimeSetListener mCallback;
+    private final Calendar mCalendar;
+    private final java.text.DateFormat mDateFormat;
+    
+    int mInitialHourOfDay;
+    int mInitialMinute;
+    boolean mIs24HourView;
+
+    /**
+     * @param context Parent.
+     * @param callBack How parent is notified.
+     * @param hourOfDay The initial hour.
+     * @param minute The initial minute.
+     * @param is24HourView Whether this is a 24 hour view, or AM/PM.
+     */
+    public TimePickerDialog(Context context,
+            OnTimeSetListener callBack,
+            int hourOfDay, int minute, boolean is24HourView) {
+        this(context, com.android.internal.R.style.Theme_Dialog_Alert,
+                callBack, hourOfDay, minute, is24HourView);
+    }
+
+    /**
+     * @param context Parent.
+     * @param theme the theme to apply to this dialog
+     * @param callBack How parent is notified.
+     * @param hourOfDay The initial hour.
+     * @param minute The initial minute.
+     * @param is24HourView Whether this is a 24 hour view, or AM/PM.
+     */
+    public TimePickerDialog(Context context,
+            int theme,
+            OnTimeSetListener callBack,
+            int hourOfDay, int minute, boolean is24HourView) {
+        super(context, theme);
+        mCallback = callBack;
+        mInitialHourOfDay = hourOfDay;
+        mInitialMinute = minute;
+        mIs24HourView = is24HourView;
+
+        mDateFormat = DateFormat.getTimeFormat(context);
+        mCalendar = Calendar.getInstance();
+        updateTitle(mInitialHourOfDay, mInitialMinute);
+        
+        setButton(context.getText(R.string.date_time_set), this);
+        setButton2(context.getText(R.string.cancel), (OnClickListener) null);
+        setIcon(R.drawable.ic_dialog_time);
+        
+        LayoutInflater inflater = 
+                (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+        View view = inflater.inflate(R.layout.time_picker_dialog, null);
+        setView(view);
+        mTimePicker = (TimePicker) view.findViewById(R.id.timePicker);
+
+        // initialize state
+        mTimePicker.setCurrentHour(mInitialHourOfDay);
+        mTimePicker.setCurrentMinute(mInitialMinute);
+        mTimePicker.setIs24HourView(mIs24HourView);
+        mTimePicker.setOnTimeChangedListener(this);
+    }
+    
+    public void onClick(DialogInterface dialog, int which) {
+        if (mCallback != null) {
+            mTimePicker.clearFocus();
+            mCallback.onTimeSet(mTimePicker, mTimePicker.getCurrentHour(), 
+                    mTimePicker.getCurrentMinute());
+        }
+    }
+
+    public void onTimeChanged(TimePicker view, int hourOfDay, int minute) {
+        updateTitle(hourOfDay, minute);
+    }
+    
+    public void updateTime(int hourOfDay, int minutOfHour) {
+        mTimePicker.setCurrentHour(hourOfDay);
+        mTimePicker.setCurrentMinute(minutOfHour);
+    }
+
+    private void updateTitle(int hour, int minute) {
+        mCalendar.set(Calendar.HOUR_OF_DAY, hour);
+        mCalendar.set(Calendar.MINUTE, minute);
+        setTitle(mDateFormat.format(mCalendar.getTime()));
+    }
+    
+    @Override
+    public Bundle onSaveInstanceState() {
+        Bundle state = super.onSaveInstanceState();
+        state.putInt(HOUR, mTimePicker.getCurrentHour());
+        state.putInt(MINUTE, mTimePicker.getCurrentMinute());
+        state.putBoolean(IS_24_HOUR, mTimePicker.is24HourView());
+        return state;
+    }
+    
+    @Override
+    public void onRestoreInstanceState(Bundle savedInstanceState) {
+        super.onRestoreInstanceState(savedInstanceState);
+        int hour = savedInstanceState.getInt(HOUR);
+        int minute = savedInstanceState.getInt(MINUTE);
+        mTimePicker.setCurrentHour(hour);
+        mTimePicker.setCurrentMinute(minute);
+        mTimePicker.setIs24HourView(savedInstanceState.getBoolean(IS_24_HOUR));
+        mTimePicker.setOnTimeChangedListener(this);
+        updateTitle(hour, minute);
+    }
+}
diff --git a/core/java/android/app/package.html b/core/java/android/app/package.html
new file mode 100644
index 0000000..048ee93
--- /dev/null
+++ b/core/java/android/app/package.html
@@ -0,0 +1,72 @@
+<html>
+<head>
+<script type="text/javascript" src="http://www.corp.google.com/style/prettify.js"></script>
+<script src="http://www.corp.google.com/eng/techpubs/include/navbar.js" type="text/javascript"></script>
+</head>
+
+<body>
+
+<p>High-level classes encapsulating the overall Android application model.
+The central class is {@link android.app.Activity}, with other top-level
+application components being defined by {@link android.app.Service} and,
+from the {@link android.content} package, {@link android.content.BroadcastReceiver}
+and {@link android.content.ContentProvider}.  It also includes application
+tools, such as dialogs and notifications.</p>
+
+<p>This package builds on top of the lower-level Android packages
+{@link android.widget}, {@link android.view}, {@link android.content},
+{@link android.text}, {@link android.graphics}, {@link android.os}, and
+{@link android.util}.</p>
+
+<p>An {@link android.app.Activity Activity} is a specific operation the
+user can perform, generally corresponding
+to one screen in the user interface.
+It is the basic building block of an Android application.
+Examples of activities are "view the
+list of people," "view the details of a person," "edit information about
+a person," "view an image," etc.  Switching from one activity to another
+generally implies adding a new entry on the navigation history; that is,
+going "back" means moving to the previous activity you were doing.</p>
+
+<p>A set of related activities can be grouped together as a "task".  Until
+a new task is explicitly specified, all activites you start are considered
+to be part of the current task.  While the only way to navigate between
+individual activities is by going "back" in the history stack, the group
+of activities in a task can be moved in relation to other tasks: for example
+to the front or the back of the history stack.  This mechanism can be used
+to present to the user a list of things they have been doing, moving
+between them without disrupting previous work.
+</p>
+
+<p>A complete "application" is a set of activities that allow the user to do a
+cohesive group of operations -- such as working with contacts, working with a
+calendar, messaging, etc.  Though there can be a custom application object
+associated with a set of activities, in many cases this is not needed --
+each activity provides a particular path into one of the various kinds of
+functionality inside of the application, serving as its on self-contained
+"mini application".
+</p>
+
+<p>This approach allows an application to be broken into pieces, which
+can be reused and replaced in a variety of ways.  Consider, for example,
+a "camera application."  There are a number of things this application
+must do, each of which is provided by a separate activity: take a picture
+(creating a new image), browse through the existing images, display a
+specific image, etc.  If the "contacts application" then wants to let the
+user associate an image with a person, it can simply launch the existing
+"take a picture" or "select an image" activity that is part of the camera
+application and attach the picture it gets back.
+</p>
+
+<p>Note that there is no hard relationship between tasks the user sees and
+applications the developer writes.  A task can be composed of activities from
+multiple applications (such as the contact application using an activity in 
+the camera application to get a picture for a person), and multiple active
+tasks may be running for the same application (such as editing e-mail messages
+to two different people).  The way tasks are organized is purely a UI policy
+decided by the system; for example, typically a new task is started when the
+user goes to the application launcher and selects an application.
+</p>
+
+</body>
+</html>
diff --git a/core/java/android/bluetooth/AtCommandHandler.java b/core/java/android/bluetooth/AtCommandHandler.java
new file mode 100644
index 0000000..8de2133
--- /dev/null
+++ b/core/java/android/bluetooth/AtCommandHandler.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2007 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 android.bluetooth;
+
+import android.bluetooth.AtCommandResult;
+
+/**
+ * Handler Interface for {@link AtParser}.<p>
+ * @hide
+ */
+public abstract class AtCommandHandler {
+
+    /**
+     * Handle Basic commands "ATA".<p>
+     * These are single letter commands such as ATA and ATD. Anything following
+     * the single letter command ('A' and 'D' respectively) will be passed as
+     * 'arg'.<p>
+     * For example, "ATDT1234" would result in the call
+     * handleBasicCommand("T1234").<p>
+     * @param arg Everything following the basic command character.
+     * @return    The result of this command.
+     */
+    public AtCommandResult handleBasicCommand(String arg) {
+        return new AtCommandResult(AtCommandResult.ERROR);
+    }
+
+    /**
+     * Handle Actions command "AT+FOO".<p>
+     * Action commands are part of the Extended command syntax, and are
+     * typically used to signal an action on "FOO".<p>
+     * @return The result of this command.
+     */
+    public AtCommandResult handleActionCommand() {
+        return new AtCommandResult(AtCommandResult.ERROR);
+    }
+
+    /**
+     * Handle Read command "AT+FOO?".<p>
+     * Read commands are part of the Extended command syntax, and are
+     * typically used to read the value of "FOO".<p>
+     * @return The result of this command.
+     */
+    public AtCommandResult handleReadCommand() {
+        return new AtCommandResult(AtCommandResult.ERROR);
+    }
+
+    /**
+     * Handle Set command "AT+FOO=...".<p>
+     * Set commands are part of the Extended command syntax, and are
+     * typically used to set the value of "FOO". Multiple arguments can be
+     * sent.<p>
+     * AT+FOO=[<arg1>[,<arg2>[,...]]]<p>
+     * Each argument will be either numeric (Integer) or String.
+     * handleSetCommand is passed a generic Object[] array in which each
+     * element will be an Integer (if it can be parsed with parseInt()) or
+     * String.<p>
+     * Missing arguments ",," are set to empty Strings.<p>
+     * @param args Array of String and/or Integer's. There will always be at
+     *             least one element in this array.
+     * @return     The result of this command.
+     */
+    // Typically used to set this paramter
+    public AtCommandResult handleSetCommand(Object[] args) {
+        return new AtCommandResult(AtCommandResult.ERROR);
+    }
+
+    /**
+     * Handle Test command "AT+FOO=?".<p>
+     * Test commands are part of the Extended command syntax, and are typically
+     * used to request an indication of the range of legal values that "FOO"
+     * can take.<p>
+     * By defualt we return an OK result, to indicate that this command is at
+     * least recognized.<p>
+     * @return The result of this command.
+     */
+    public AtCommandResult handleTestCommand() {
+        return new AtCommandResult(AtCommandResult.OK);
+    }
+}
diff --git a/core/java/android/bluetooth/AtCommandResult.java b/core/java/android/bluetooth/AtCommandResult.java
new file mode 100644
index 0000000..638be2d
--- /dev/null
+++ b/core/java/android/bluetooth/AtCommandResult.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2007 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 android.bluetooth;
+
+import java.util.*;
+
+/**
+ * The result of execution of an single AT command.<p>
+ *
+ *
+ * This class can represent the final response to an AT command line, and also
+ * intermediate responses to a single command within a chained AT command
+ * line.<p>
+ *
+ * The actual responses that are intended to be send in reply to the AT command
+ * line are stored in a string array. The final response is stored as an
+ * int enum, converted to a string when toString() is called. Only a single
+ * final response is sent from multiple commands chained into a single command
+ * line.<p>
+ * @hide
+ */
+public class AtCommandResult {
+    // Result code enumerations
+    public static final int OK = 0;
+    public static final int ERROR = 1;
+    public static final int UNSOLICITED = 2;
+
+    private static final String OK_STRING = "OK";
+    private static final String ERROR_STRING = "ERROR";
+
+    private int mResultCode;  // Result code
+    private StringBuilder mResponse; // Response with CRLF line breaks
+
+    /**
+     * Construct a new AtCommandResult with given result code, and an empty
+     * response array.
+     * @param resultCode One of OK, ERROR or UNSOLICITED.
+     */
+    public AtCommandResult(int resultCode) {
+        mResultCode = resultCode;
+        mResponse = new StringBuilder();
+    }
+
+    /**
+     * Construct a new AtCommandResult with result code OK, and the specified
+     * single line response.
+     * @param response The single line response.
+     */
+    public AtCommandResult(String response) {
+        this(OK);
+        addResponse(response);
+    }
+
+    public int getResultCode() {
+        return mResultCode;
+    }
+
+    /**
+     * Add another line to the response.
+     */
+    public void addResponse(String response) {
+        appendWithCrlf(mResponse, response);
+    }
+
+    /**
+     * Add the given result into this AtCommandResult object.<p>
+     * Used to combine results from multiple commands in a single command line
+     * (command chaining).
+     * @param result The AtCommandResult to add to this result.
+     */
+    public void addResult(AtCommandResult result) {
+        if (result != null) {
+            appendWithCrlf(mResponse, result.mResponse.toString());
+            mResultCode = result.mResultCode;
+        }
+    }
+
+    /**
+     * Generate the string response ready to send
+     */
+    public String toString() {
+        StringBuilder result = new StringBuilder(mResponse.toString());
+        switch (mResultCode) {
+        case OK:
+            appendWithCrlf(result, OK_STRING);
+            break;
+        case ERROR:
+            appendWithCrlf(result, ERROR_STRING);
+            break;
+        }
+        return result.toString();
+    }
+
+    /** Append a string to a string builder, joining with a double
+     * CRLF. Used to create multi-line AT command replies
+     */
+    public static void appendWithCrlf(StringBuilder str1, String str2) {
+        if (str1.length() > 0 && str2.length() > 0) {
+            str1.append("\r\n\r\n");
+        }
+        str1.append(str2);
+    }
+};
diff --git a/core/java/android/bluetooth/AtParser.java b/core/java/android/bluetooth/AtParser.java
new file mode 100644
index 0000000..1ea3150
--- /dev/null
+++ b/core/java/android/bluetooth/AtParser.java
@@ -0,0 +1,370 @@
+/*
+ * Copyright (C) 2007 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 android.bluetooth;
+
+import android.bluetooth.AtCommandHandler;
+import android.bluetooth.AtCommandResult;
+
+import java.util.*;
+
+/**
+ * An AT (Hayes command) Parser based on (a subset of) the ITU-T V.250 standard.
+ * <p>
+ *
+ * Conforment with the subset of V.250 required for implementation of the
+ * Bluetooth Headset and Handsfree Profiles, as per Bluetooth SIP
+ * specifications. Also implements some V.250 features not required by
+ * Bluetooth - such as chained commands.<p>
+ *
+ * Command handlers are registered with an AtParser object. These handlers are
+ * invoked when command lines are processed by AtParser's process() method.<p>
+ *
+ * The AtParser object accepts a new command line to parse via its process()
+ * method. It breaks each command line into one or more commands. Each command
+ * is parsed for name, type, and (optional) arguments, and an appropriate
+ * external handler method is called through the AtCommandHandler interface.
+ *
+ * The command types are<ul>
+ * <li>Basic Command. For example "ATDT1234567890". Basic command names are a
+ * single character (e.g. "D"), and everything following this character is
+ * passed to the handler as a string argument (e.g. "T1234567890").
+ * <li>Action Command. For example "AT+CIMI". The command name is "CIMI", and
+ * there are no arguments for action commands.
+ * <li>Read Command. For example "AT+VGM?". The command name is "VGM", and there
+ * are no arguments for get commands.
+ * <li>Set Command. For example "AT+VGM=14". The command name is "VGM", and
+ * there is a single integer argument in this case. In the general case then
+ * can be zero or more arguments (comma deliminated) each of integer or string
+ * form.
+ * <li>Test Command. For example "AT+VGM=?. No arguments.
+ * </ul>
+ *
+ * In V.250 the last four command types are known as Extended Commands, and
+ * they are used heavily in Bluetooth.<p>
+ *
+ * Basic commands cannot be chained in this implementation. For Bluetooth
+ * headset/handsfree use this is acceptable, because they only use the basic
+ * commands ATA and ATD, which are not allowed to be chained. For general V.250
+ * use we would need to improve this class to allow Basic command chaining -
+ * however its tricky to get right becuase there is no deliminator for Basic
+ * command chaining.<p>
+ *
+ * Extended commands can be chained. For example:<p>
+ * AT+VGM?;+VGM=14;+CIMI<p>
+ * This is equivalent to:<p>
+ * AT+VGM?
+ * AT+VGM=14
+ * AT+CIMI
+ * Except that only one final result code is return (although several
+ * intermediate responses may be returned), and as soon as one command in the
+ * chain fails the rest are abandonded.<p>
+ *
+ * Handlers are registered by there command name via register(Char c, ...) or
+ * register(String s, ...). Handlers for Basic command should be registered by
+ * the basic command character, and handlers for Extended commands should be
+ * registered by String.<p>
+ *
+ * Refer to:<ul>
+ * <li>ITU-T Recommendation V.250
+ * <li>ETSI TS 127.007  (AT Comannd set for User Equipment, 3GPP TS 27.007)
+ * <li>Bluetooth Headset Profile Spec (K6)
+ * <li>Bluetooth Handsfree Profile Spec (HFP 1.5)
+ * </ul>
+ * @hide
+ */
+public class AtParser {
+
+    // Extended command type enumeration, only used internally
+    private static final int TYPE_ACTION = 0;   // AT+FOO
+    private static final int TYPE_READ = 1;     // AT+FOO?
+    private static final int TYPE_SET = 2;      // AT+FOO=
+    private static final int TYPE_TEST = 3;     // AT+FOO=?
+
+    private HashMap<String, AtCommandHandler> mExtHandlers;
+    private HashMap<Character, AtCommandHandler> mBasicHandlers;
+
+    private String mLastInput;  // for "A/" (repeat last command) support
+
+    /**
+     * Create a new AtParser.<p>
+     * No handlers are registered.
+     */
+    public AtParser() {
+        mBasicHandlers = new HashMap<Character, AtCommandHandler>();
+        mExtHandlers = new HashMap<String, AtCommandHandler>();
+        mLastInput = "";
+    }
+
+    /**
+     * Register a Basic command handler.<p>
+     * Basic command handlers are later called via their
+     * <code>handleBasicCommand(String args)</code> method.
+     * @param  command Command name - a single character
+     * @param  handler Handler to register
+     */
+    public void register(Character command, AtCommandHandler handler) {
+        mBasicHandlers.put(command, handler);
+    }
+
+    /**
+     * Register an Extended command handler.<p>
+     * Extended command handlers are later called via:<ul>
+     * <li><code>handleActionCommand()</code>
+     * <li><code>handleGetCommand()</code>
+     * <li><code>handleSetCommand()</code>
+     * <li><code>handleTestCommand()</code>
+     * </ul>
+     * Only one method will be called for each command processed.
+     * @param  command Command name - can be multiple characters
+     * @param  handler Handler to register
+     */
+    public void register(String command, AtCommandHandler handler) {
+        mExtHandlers.put(command, handler);
+    }
+
+
+    /**
+     * Strip input of whitespace and force Uppercase - except sections inside
+     * quotes. Also fixes unmatched quotes (by appending a quote). Double
+     * quotes " are the only quotes allowed by V.250
+     */
+    static private String clean(String input) {
+        StringBuilder out = new StringBuilder(input.length());
+
+        for (int i = 0; i < input.length(); i++) {
+            char c = input.charAt(i);
+            if (c == '"') {
+                int j = input.indexOf('"', i + 1 );  // search for closing "
+                if (j == -1) {  // unmatched ", insert one.
+                    out.append(input.substring(i, input.length()));
+                    out.append('"');
+                    break;
+                }
+                out.append(input.substring(i, j + 1));
+                i = j;
+            } else if (c != ' ') {
+                out.append(Character.toUpperCase(c));
+            }
+        }
+
+        return out.toString();
+    }
+
+    static private boolean isAtoZ(char c) {
+        return (c >= 'A' && c <= 'Z');
+    }
+
+    /**
+     * Find a character ch, ignoring quoted sections.
+     * Return input.length() if not found.
+     */
+    static private int findChar(char ch, String input, int fromIndex) {
+        for (int i = fromIndex; i < input.length(); i++) {
+            char c = input.charAt(i);
+            if (c == '"') {
+                i = input.indexOf('"', i + 1);
+                if (i == -1) {
+                    return input.length();
+                }
+            } else if (c == ch) {
+                return i;
+            }
+        }
+        return input.length();
+    }
+
+    /**
+     * Break an argument string into individual arguments (comma deliminated).
+     * Integer arguments are turned into Integer objects. Otherwise a String
+     * object is used.
+     */
+    static private Object[] generateArgs(String input) {
+        int i = 0;
+        int j;
+        ArrayList<Object> out = new ArrayList<Object>();
+        while (i <= input.length()) {
+            j = findChar(',', input, i);
+
+            String arg = input.substring(i, j);
+            try {
+                out.add(new Integer(arg));
+            } catch (NumberFormatException e) {
+                out.add(arg);
+            }
+
+            i = j + 1; // move past comma
+        }
+        return out.toArray();
+    }
+
+    /**
+     * Return the index of the end of character after the last characeter in
+     * the extended command name. Uses the V.250 spec for allowed command
+     * names.
+     */
+    static private int findEndExtendedName(String input, int index) {
+        for (int i = index; i < input.length(); i++) {
+            char c = input.charAt(i);
+
+            // V.250 defines the following chars as legal extended command
+            // names
+            if (isAtoZ(c)) continue;
+            if (c >= '0' && c <= '9') continue;
+            switch (c) {
+            case '!':
+            case '%':
+            case '-':
+            case '.':
+            case '/':
+            case ':':
+            case '_':
+                continue;
+            default:
+                return i;
+            }
+        }
+        return input.length();
+    }
+
+    /**
+     * Processes an incoming AT command line.<p>
+     * This method will invoke zero or one command handler methods for each
+     * command in the command line.<p>
+     * @param raw_input The AT input, without EOL deliminator (e.g. <CR>).
+     * @return          Result object for this command line. This can be
+     *                  converted to a String[] response with toStrings().
+     */
+    public AtCommandResult process(String raw_input) {
+        String input = clean(raw_input);
+
+        // Handle "A/" (repeat previous line)
+        if (input.regionMatches(0, "A/", 0, 2)) {
+            input = new String(mLastInput);
+        } else {
+            mLastInput = new String(input);
+        }
+
+        // Handle empty line - no response necessary
+        if (input.equals("")) {
+            // Return []
+            return new AtCommandResult(AtCommandResult.UNSOLICITED);
+        }
+
+        // Anything else deserves an error
+        if (!input.regionMatches(0, "AT", 0, 2)) {
+            // Return ["ERROR"]
+            return new AtCommandResult(AtCommandResult.ERROR);
+        }
+
+        // Ok we have a command that starts with AT. Process it
+        int index = 2;
+        AtCommandResult result =
+                new AtCommandResult(AtCommandResult.UNSOLICITED);
+        while (index < input.length()) {
+            char c = input.charAt(index);
+
+            if (isAtoZ(c)) {
+                // Option 1: Basic Command
+                // Pass the rest of the line as is to the handler. Do not
+                // look for any more commands on this line.
+                String args = input.substring(index + 1);
+                if (mBasicHandlers.containsKey((Character)c)) {
+                    result.addResult(mBasicHandlers.get(
+                            (Character)c).handleBasicCommand(args));
+                    return result;
+                } else {
+                    // no handler
+                    result.addResult(
+                            new AtCommandResult(AtCommandResult.ERROR));
+                    return result;
+                }
+                // control never reaches here
+            }
+
+            if (c == '+') {
+                // Option 2: Extended Command
+                // Search for first non-name character. Shortcircuit if we dont
+                // handle this command name.
+                int i = findEndExtendedName(input, index + 1);
+                String commandName = input.substring(index, i);
+                if (!mExtHandlers.containsKey(commandName)) {
+                    // no handler
+                    result.addResult(
+                            new AtCommandResult(AtCommandResult.ERROR));
+                    return result;
+                }
+                AtCommandHandler handler = mExtHandlers.get(commandName);
+
+                // Search for end of this command - this is usually the end of
+                // line
+                int endIndex = findChar(';', input, index);
+
+                // Determine what type of command this is.
+                // Default to TYPE_ACTION if we can't find anything else
+                // obvious.
+                int type;
+
+                if (i >= endIndex) {
+                    type = TYPE_ACTION;
+                } else if (input.charAt(i) == '?') {
+                    type = TYPE_READ;
+                } else if (input.charAt(i) == '=') {
+                    if (i + 1 < endIndex) {
+                        if (input.charAt(i + 1) == '?') {
+                            type = TYPE_TEST;
+                        } else {
+                            type = TYPE_SET;
+                        }
+                    } else {
+                        type = TYPE_SET;
+                    }
+                } else {
+                    type = TYPE_ACTION;
+                }
+
+                // Call this command. Short-circuit as soon as a command fails
+                switch (type) {
+                case TYPE_ACTION:
+                    result.addResult(handler.handleActionCommand());
+                    break;
+                case TYPE_READ:
+                    result.addResult(handler.handleReadCommand());
+                    break;
+                case TYPE_TEST:
+                    result.addResult(handler.handleTestCommand());
+                    break;
+                case TYPE_SET:
+                    Object[] args =
+                            generateArgs(input.substring(i + 1, endIndex));
+                    result.addResult(handler.handleSetCommand(args));
+                    break;
+                }
+                if (result.getResultCode() != AtCommandResult.OK) {
+                    return result;   // short-circuit
+                }
+
+                index = endIndex;
+            } else {
+                // Can't tell if this is a basic or extended command.
+                // Push forwards and hope we hit something.
+                index++;
+            }
+        }
+        // Finished processing (and all results were ok)
+        return result;
+    }
+}
diff --git a/core/java/android/bluetooth/BluetoothA2dp.java b/core/java/android/bluetooth/BluetoothA2dp.java
new file mode 100644
index 0000000..b0b0154
--- /dev/null
+++ b/core/java/android/bluetooth/BluetoothA2dp.java
@@ -0,0 +1,247 @@
+/*
+ * 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 android.bluetooth;
+
+import android.annotation.SdkConstant;
+import android.annotation.SdkConstant.SdkConstantType;
+import android.server.BluetoothA2dpService;
+import android.content.Context;
+import android.os.ServiceManager;
+import android.os.RemoteException;
+import android.os.IBinder;
+import android.util.Log;
+
+import java.util.List;
+
+/**
+ * Public API for controlling the Bluetooth A2DP Profile Service.
+ *
+ * BluetoothA2dp is a proxy object for controlling the Bluetooth A2DP
+ * Service via IPC.
+ *
+ * Creating a BluetoothA2dp object will initiate a binding with the
+ * BluetoothHeadset service. Users of this object should call close() when they
+ * are finished, so that this proxy object can unbind from the service.
+ *
+ * Currently the BluetoothA2dp service runs in the system server and this
+ * proxy object will be immediately bound to the service on construction.
+ * However this may change in future releases, and error codes such as
+ * BluetoothError.ERROR_IPC_NOT_READY will be returned from this API when the
+ * proxy object is not yet attached.
+ * 
+ * Currently this class provides methods to connect to A2DP audio sinks.
+ *
+ * @hide
+ */
+public class BluetoothA2dp {
+    private static final String TAG = "BluetoothA2dp";
+
+    /** int extra for SINK_STATE_CHANGED_ACTION */
+    public static final String SINK_STATE =
+        "android.bluetooth.a2dp.intent.SINK_STATE";
+    /** int extra for SINK_STATE_CHANGED_ACTION */
+    public static final String SINK_PREVIOUS_STATE =
+        "android.bluetooth.a2dp.intent.SINK_PREVIOUS_STATE";
+
+    /** Indicates the state of an A2DP audio sink has changed.
+     *  This intent will always contain SINK_STATE, SINK_PREVIOUS_STATE and
+     *  BluetoothIntent.ADDRESS extras.
+     */
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String SINK_STATE_CHANGED_ACTION =
+        "android.bluetooth.a2dp.intent.action.SINK_STATE_CHANGED";
+
+    public static final int STATE_DISCONNECTED = 0;
+    public static final int STATE_CONNECTING   = 1;
+    public static final int STATE_CONNECTED    = 2;
+    public static final int STATE_DISCONNECTING = 3;
+    /** Playing implies connected */
+    public static final int STATE_PLAYING    = 4;
+
+    /** Default priority for a2dp devices that should allow incoming
+     * connections */
+    public static final int PRIORITY_AUTO = 100;
+    /** Default priority for a2dp devices that should not allow incoming
+     * connections */
+    public static final int PRIORITY_OFF = 0;
+    private final IBluetoothA2dp mService;
+    private final Context mContext;
+
+    /**
+     * Create a BluetoothA2dp proxy object for interacting with the local
+     * Bluetooth A2DP service.
+     * @param c Context
+     */
+    public BluetoothA2dp(Context c) {
+        mContext = c;
+        IBinder b = ServiceManager.getService(BluetoothA2dpService.BLUETOOTH_A2DP_SERVICE);
+        if (b == null) {
+            throw new RuntimeException("Bluetooth A2DP service not available!");
+        }
+        mService = IBluetoothA2dp.Stub.asInterface(b);
+    }
+
+    /** Initiate a connection to an A2DP sink.
+     *  Listen for SINK_STATE_CHANGED_ACTION to find out when the
+     *  connection is completed.
+     *  @param address Remote BT address.
+     *  @return Result code, negative indicates an immediate error.
+     *  @hide
+     */
+    public int connectSink(String address) {
+        try {
+            return mService.connectSink(address);
+        } catch (RemoteException e) {
+            Log.w(TAG, "", e);
+            return BluetoothError.ERROR_IPC;
+        }
+    }
+
+    /** Initiate disconnect from an A2DP sink.
+     *  Listen for SINK_STATE_CHANGED_ACTION to find out when
+     *  disconnect is completed.
+     *  @param address Remote BT address.
+     *  @return Result code, negative indicates an immediate error.
+     *  @hide
+     */
+    public int disconnectSink(String address) {
+        try {
+            return mService.disconnectSink(address);
+        } catch (RemoteException e) {
+            Log.w(TAG, "", e);
+            return BluetoothError.ERROR_IPC;
+        }
+    }
+
+    /** Check if a specified A2DP sink is connected.
+     *  @param address Remote BT address.
+     *  @return True if connected (or playing), false otherwise and on error.
+     *  @hide
+     */
+    public boolean isSinkConnected(String address) {
+        int state = getSinkState(address);
+        return state == STATE_CONNECTED || state == STATE_PLAYING;
+    }
+
+    /** Check if any A2DP sink is connected.
+     * @return a List of connected A2DP sinks, or null on error.
+     * @hide
+     */
+    public List<String> listConnectedSinks() {
+        try {
+            return mService.listConnectedSinks();
+        } catch (RemoteException e) {
+            Log.w(TAG, "", e);
+            return null;
+        }
+    }
+
+    /** Get the state of an A2DP sink
+     *  @param address Remote BT address.
+     *  @return State code, or negative on error
+     *  @hide
+     */
+    public int getSinkState(String address) {
+        try {
+            return mService.getSinkState(address);
+        } catch (RemoteException e) {
+            Log.w(TAG, "", e);
+            return BluetoothError.ERROR_IPC;
+        }
+    }
+
+    /**
+     * Set priority of a2dp sink.
+     * Priority is a non-negative integer. By default paired sinks will have
+     * a priority of PRIORITY_AUTO, and unpaired headset PRIORITY_NONE (0).
+     * Sinks with priority greater than zero will accept incoming connections
+     * (if no sink is currently connected).
+     * Priority for unpaired sink must be PRIORITY_NONE.
+     * @param address Paired sink
+     * @param priority Integer priority, for example PRIORITY_AUTO or
+     *                 PRIORITY_NONE
+     * @return Result code, negative indicates an error
+     */
+    public int setSinkPriority(String address, int priority) {
+        try {
+            return mService.setSinkPriority(address, priority);
+        } catch (RemoteException e) {
+            Log.w(TAG, "", e);
+            return BluetoothError.ERROR_IPC;
+        }
+    }
+
+    /**
+     * Get priority of a2dp sink.
+     * @param address Sink
+     * @return non-negative priority, or negative error code on error.
+     */
+    public int getSinkPriority(String address) {
+        try {
+            return mService.getSinkPriority(address);
+        } catch (RemoteException e) {
+            Log.w(TAG, "", e);
+            return BluetoothError.ERROR_IPC;
+        }
+    }
+
+    /**
+     * Check class bits for possible A2DP Sink support.
+     * This is a simple heuristic that tries to guess if a device with the
+     * given class bits might be a A2DP Sink. It is not accurate for all
+     * devices. It tries to err on the side of false positives.
+     * @return True if this device might be a A2DP sink
+     */
+    public static boolean doesClassMatchSink(int btClass) {
+        if (BluetoothClass.Service.hasService(btClass, BluetoothClass.Service.RENDER)) {
+            return true;
+        }
+        // By the A2DP spec, sinks must indicate the RENDER service.
+        // However we found some that do not (Chordette). So lets also
+        // match on some other class bits.
+        switch (BluetoothClass.Device.getDevice(btClass)) {
+        case BluetoothClass.Device.AUDIO_VIDEO_HIFI_AUDIO:
+        case BluetoothClass.Device.AUDIO_VIDEO_HEADPHONES:
+        case BluetoothClass.Device.AUDIO_VIDEO_LOUDSPEAKER:
+        case BluetoothClass.Device.AUDIO_VIDEO_CAR_AUDIO:
+            return true;
+        default:
+            return false;
+        }
+    }
+
+    /** Helper for converting a state to a string.
+     * For debug use only - strings are not internationalized.
+     * @hide
+     */
+    public static String stateToString(int state) {
+        switch (state) {
+        case STATE_DISCONNECTED:
+            return "disconnected";
+        case STATE_CONNECTING:
+            return "connecting";
+        case STATE_CONNECTED:
+            return "connected";
+        case STATE_DISCONNECTING:
+            return "disconnecting";
+        case STATE_PLAYING:
+            return "playing";
+        default:
+            return "<unknown state " + state + ">";
+        }
+    }
+}
diff --git a/core/java/android/bluetooth/BluetoothAudioGateway.java b/core/java/android/bluetooth/BluetoothAudioGateway.java
new file mode 100644
index 0000000..f3afd2a
--- /dev/null
+++ b/core/java/android/bluetooth/BluetoothAudioGateway.java
@@ -0,0 +1,190 @@
+package android.bluetooth;
+
+import java.lang.Thread;
+
+import android.os.Message;
+import android.os.Handler;
+import android.util.Log;
+
+/**
+ * Listen's for incoming RFCOMM connection for the headset / handsfree service.
+ *
+ * This class is planned for deletion, in favor of a generic Rfcomm class.
+ *
+ * @hide
+ */
+public class BluetoothAudioGateway {
+    private static final String TAG = "BT Audio Gateway";
+    private static final boolean DBG = false;
+
+    private int mNativeData;
+    static { classInitNative(); }
+
+    private BluetoothDevice mBluetooth;
+
+    /* in */
+    private int mHandsfreeAgRfcommChannel = -1;
+    private int mHeadsetAgRfcommChannel   = -1;
+
+    /* out */
+    private String mConnectingHeadsetAddress;
+    private int mConnectingHeadsetRfcommChannel; /* -1 when not connected */
+    private int mConnectingHeadsetSocketFd;
+    private String mConnectingHandsfreeAddress;
+    private int mConnectingHandsfreeRfcommChannel; /* -1 when not connected */
+    private int mConnectingHandsfreeSocketFd;
+    private int mTimeoutRemainingMs; /* in/out */
+
+    public static final int DEFAULT_HF_AG_CHANNEL = 10;
+    public static final int DEFAULT_HS_AG_CHANNEL = 11;
+
+    public BluetoothAudioGateway(BluetoothDevice bluetooth) {
+        this(bluetooth, DEFAULT_HF_AG_CHANNEL, DEFAULT_HS_AG_CHANNEL);
+    }
+
+    public BluetoothAudioGateway(BluetoothDevice bluetooth,
+                                 int handsfreeAgRfcommChannel,
+                                 int headsetAgRfcommChannel) {
+        mBluetooth = bluetooth;
+        mHandsfreeAgRfcommChannel = handsfreeAgRfcommChannel;
+        mHeadsetAgRfcommChannel = headsetAgRfcommChannel;
+        initializeNativeDataNative();
+    }
+
+    private Thread mConnectThead;
+    private volatile boolean mInterrupted;
+    private static final int SELECT_WAIT_TIMEOUT = 1000;
+
+    private Handler mCallback;
+
+    public class IncomingConnectionInfo {
+        IncomingConnectionInfo(BluetoothDevice bluetooth, String address, int socketFd,
+                               int rfcommChan) {
+            mBluetooth = bluetooth;
+            mAddress = address;
+            mSocketFd = socketFd;
+            mRfcommChan = rfcommChan;
+        }
+
+        public BluetoothDevice mBluetooth;
+        public String mAddress;
+        public int mSocketFd;
+        public int mRfcommChan;
+    }
+
+    public static final int MSG_INCOMING_HEADSET_CONNECTION   = 100;
+    public static final int MSG_INCOMING_HANDSFREE_CONNECTION = 101;
+
+    public synchronized boolean start(Handler callback) {
+
+        if (mConnectThead == null) {
+            mCallback = callback;
+            mConnectThead = new Thread(TAG) {
+                    public void run() {
+                        if (DBG) log("Connect Thread starting");
+                        while (!mInterrupted) {
+                            //Log.i(TAG, "waiting for connect");
+                            mConnectingHeadsetRfcommChannel = -1;
+                            mConnectingHandsfreeRfcommChannel = -1;
+                            if (waitForHandsfreeConnectNative(SELECT_WAIT_TIMEOUT) == false) {
+                                if (mTimeoutRemainingMs > 0) {
+                                    try {
+                                        Log.i(TAG, "select thread timed out, but " + 
+                                              mTimeoutRemainingMs + "ms of waiting remain.");
+                                        Thread.sleep(mTimeoutRemainingMs);
+                                    } catch (InterruptedException e) {
+                                        Log.i(TAG, "select thread was interrupted (2), exiting");
+                                        mInterrupted = true;
+                                    }
+                                }
+                            }
+                            else {
+                                Log.i(TAG, "connect notification!");
+                                /* A device connected (most likely just one, but 
+                                   it is possible for two separate devices, one 
+                                   a headset and one a handsfree, to connect
+                                   simultaneously. 
+                                */
+                                if (mConnectingHeadsetRfcommChannel >= 0) {
+                                    Log.i(TAG, "Incoming connection from headset " + 
+                                          mConnectingHeadsetAddress + " on channel " + 
+                                          mConnectingHeadsetRfcommChannel);
+                                    Message msg = Message.obtain(mCallback);
+                                    msg.what = MSG_INCOMING_HEADSET_CONNECTION;
+                                    msg.obj = 
+                                        new IncomingConnectionInfo(
+                                            mBluetooth, 
+                                            mConnectingHeadsetAddress,
+                                            mConnectingHeadsetSocketFd,
+                                            mConnectingHeadsetRfcommChannel);
+                                    msg.sendToTarget();
+                                }
+                                if (mConnectingHandsfreeRfcommChannel >= 0) {
+                                    Log.i(TAG, "Incoming connection from handsfree " + 
+                                          mConnectingHandsfreeAddress + " on channel " + 
+                                          mConnectingHandsfreeRfcommChannel);
+                                    Message msg = Message.obtain();
+                                    msg.setTarget(mCallback);
+                                    msg.what = MSG_INCOMING_HANDSFREE_CONNECTION;
+                                    msg.obj = 
+                                        new IncomingConnectionInfo(
+                                            mBluetooth,
+                                            mConnectingHandsfreeAddress,
+                                            mConnectingHandsfreeSocketFd,
+                                            mConnectingHandsfreeRfcommChannel);
+                                    msg.sendToTarget();
+                                }
+                            }
+                        }
+                        if (DBG) log("Connect Thread finished");
+                    }
+                };
+
+            if (setUpListeningSocketsNative() == false) {
+                Log.e(TAG, "Could not set up listening socket, exiting");
+                return false;
+            }
+
+            mInterrupted = false;
+            mConnectThead.start();
+        }
+
+        return true;
+    }
+
+    public synchronized void stop() {
+        if (mConnectThead != null) {
+            if (DBG) log("stopping Connect Thread");
+            mInterrupted = true;
+            try {
+                mConnectThead.interrupt();
+                if (DBG) log("waiting for thread to terminate");
+                mConnectThead.join();
+                mConnectThead = null;
+                mCallback = null;
+                tearDownListeningSocketsNative();
+            } catch (InterruptedException e) {
+                Log.w(TAG, "Interrupted waiting for Connect Thread to join");
+            }
+        }
+    }
+
+    protected void finalize() throws Throwable {
+        try {
+            cleanupNativeDataNative();
+        } finally {
+            super.finalize();
+        }
+    }
+
+    private static native void classInitNative();
+    private native void initializeNativeDataNative();
+    private native void cleanupNativeDataNative();
+    private native boolean waitForHandsfreeConnectNative(int timeoutMs);
+    private native boolean setUpListeningSocketsNative();
+    private native void tearDownListeningSocketsNative();
+
+    private static void log(String msg) {
+        Log.d(TAG, msg);
+    }
+}
diff --git a/core/java/android/bluetooth/BluetoothClass.java b/core/java/android/bluetooth/BluetoothClass.java
new file mode 100644
index 0000000..88ce18b
--- /dev/null
+++ b/core/java/android/bluetooth/BluetoothClass.java
@@ -0,0 +1,191 @@
+/*
+ * 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 android.bluetooth;
+
+/**
+ * The Android Bluetooth API is not finalized, and *will* change. Use at your
+ * own risk.
+ *
+ * Static helper methods and constants to decode the device class bit vector
+ * returned by the Bluetooth API.
+ *
+ * The Android Bluetooth API returns a 32-bit integer to represent the class.
+ * The format of these bits is defined at
+ *   http://www.bluetooth.org/Technical/AssignedNumbers/baseband.htm
+ * (login required). This class provides static helper methods and constants to
+ * determine what Service Class(es) and Device Class are encoded in the 32-bit
+ * class.
+ *
+ * Devices typically have zero or more service classes, and exactly one device
+ * class. The device class is encoded as a major and minor device class, the
+ * minor being a subset of the major.
+ *
+ * Class is useful to describe a device (for example to show an icon),
+ * but does not reliably describe what profiles a device supports. To determine
+ * profile support you usually need to perform SDP queries.
+ *
+ * Each of these helper methods takes the 32-bit integer class as an argument.
+ *
+ * @hide
+ */
+public class BluetoothClass {
+    /** Indicates the Bluetooth API could not retrieve the class */
+    public static final int ERROR = 0xFF000000;
+
+    /** Every Bluetooth device has zero or more service classes */
+    public static class Service {
+        public static final int BITMASK                 = 0xFFE000;
+
+        public static final int LIMITED_DISCOVERABILITY = 0x002000;
+        public static final int POSITIONING             = 0x010000;
+        public static final int NETWORKING              = 0x020000;
+        public static final int RENDER                  = 0x040000;
+        public static final int CAPTURE                 = 0x080000;
+        public static final int OBJECT_TRANSFER         = 0x100000;
+        public static final int AUDIO                   = 0x200000;
+        public static final int TELEPHONY               = 0x400000;
+        public static final int INFORMATION             = 0x800000;
+
+        /** Returns true if the given class supports the given Service Class.
+         * A bluetooth device can claim to support zero or more service classes.
+         * @param btClass The bluetooth class.
+         * @param serviceClass The service class constant to test for. For
+         *                     example, Service.AUDIO. Must be one of the
+         *                     Service.FOO constants.
+         * @return True if the service class is supported.
+         */
+        public static boolean hasService(int btClass, int serviceClass) {
+            if (btClass == ERROR) {
+                return false;
+            }
+            return ((btClass & Service.BITMASK & serviceClass) != 0);
+        }
+    }
+
+    /** Every Bluetooth device has exactly one device class, comprimised of
+     *  major and minor components. We have not included the minor classes for
+     *  major classes: NETWORKING, PERIPHERAL and IMAGING yet because they work
+     *  a little differently. */
+    public static class Device {
+        public static final int BITMASK               = 0x1FFC;
+
+        public static class Major {
+            public static final int BITMASK           = 0x1F00;
+
+            public static final int MISC              = 0x0000;
+            public static final int COMPUTER          = 0x0100;
+            public static final int PHONE             = 0x0200;
+            public static final int NETWORKING        = 0x0300;
+            public static final int AUDIO_VIDEO       = 0x0400;
+            public static final int PERIPHERAL        = 0x0500;
+            public static final int IMAGING           = 0x0600;
+            public static final int WEARABLE          = 0x0700;
+            public static final int TOY               = 0x0800;
+            public static final int HEALTH            = 0x0900;
+            public static final int UNCATEGORIZED     = 0x1F00;
+
+            /** Returns the Major Device Class component of a bluetooth class.
+             * Values returned from this function can be compared with the constants
+             * Device.Major.FOO. A bluetooth device can only be associated
+             * with one major class.
+             */
+            public static int getDeviceMajor(int btClass) {
+                if (btClass == ERROR) {
+                    return ERROR;
+                }
+                return (btClass & Device.Major.BITMASK);
+            }
+        }
+
+        // Devices in the COMPUTER major class
+        public static final int COMPUTER_UNCATEGORIZED              = 0x0100;
+        public static final int COMPUTER_DESKTOP                    = 0x0104;
+        public static final int COMPUTER_SERVER                     = 0x0108;
+        public static final int COMPUTER_LAPTOP                     = 0x010C;
+        public static final int COMPUTER_HANDHELD_PC_PDA            = 0x0110;
+        public static final int COMPUTER_PALM_SIZE_PC_PDA           = 0x0114;
+        public static final int COMPUTER_WEARABLE                   = 0x0118;
+
+        // Devices in the PHONE major class
+        public static final int PHONE_UNCATEGORIZED                 = 0x0200;
+        public static final int PHONE_CELLULAR                      = 0x0204;
+        public static final int PHONE_CORDLESS                      = 0x0208;
+        public static final int PHONE_SMART                         = 0x020C;
+        public static final int PHONE_MODEM_OR_GATEWAY              = 0x0210;
+        public static final int PHONE_ISDN                          = 0x0214;
+
+        // Minor classes for the AUDIO_VIDEO major class
+        public static final int AUDIO_VIDEO_UNCATEGORIZED           = 0x0400;
+        public static final int AUDIO_VIDEO_WEARABLE_HEADSET        = 0x0404;
+        public static final int AUDIO_VIDEO_HANDSFREE               = 0x0408;
+        //public static final int AUDIO_VIDEO_RESERVED              = 0x040C;
+        public static final int AUDIO_VIDEO_MICROPHONE              = 0x0410;
+        public static final int AUDIO_VIDEO_LOUDSPEAKER             = 0x0414;
+        public static final int AUDIO_VIDEO_HEADPHONES              = 0x0418;
+        public static final int AUDIO_VIDEO_PORTABLE_AUDIO          = 0x041C;
+        public static final int AUDIO_VIDEO_CAR_AUDIO               = 0x0420;
+        public static final int AUDIO_VIDEO_SET_TOP_BOX             = 0x0424;
+        public static final int AUDIO_VIDEO_HIFI_AUDIO              = 0x0428;
+        public static final int AUDIO_VIDEO_VCR                     = 0x042C;
+        public static final int AUDIO_VIDEO_VIDEO_CAMERA            = 0x0430;
+        public static final int AUDIO_VIDEO_CAMCORDER               = 0x0434;
+        public static final int AUDIO_VIDEO_VIDEO_MONITOR           = 0x0438;
+        public static final int AUDIO_VIDEO_VIDEO_DISPLAY_AND_LOUDSPEAKER = 0x043C;
+        public static final int AUDIO_VIDEO_VIDEO_CONFERENCING      = 0x0440;
+        //public static final int AUDIO_VIDEO_RESERVED              = 0x0444;
+        public static final int AUDIO_VIDEO_VIDEO_GAMING_TOY        = 0x0448;
+
+        // Devices in the WEARABLE major class
+        public static final int WEARABLE_UNCATEGORIZED              = 0x0700;
+        public static final int WEARABLE_WRIST_WATCH                = 0x0704;
+        public static final int WEARABLE_PAGER                      = 0x0708;
+        public static final int WEARABLE_JACKET                     = 0x070C;
+        public static final int WEARABLE_HELMET                     = 0x0710;
+        public static final int WEARABLE_GLASSES                    = 0x0714;
+
+        // Devices in the TOY major class
+        public static final int TOY_UNCATEGORIZED                   = 0x0800;
+        public static final int TOY_ROBOT                           = 0x0804;
+        public static final int TOY_VEHICLE                         = 0x0808;
+        public static final int TOY_DOLL_ACTION_FIGURE              = 0x080C;
+        public static final int TOY_CONTROLLER                      = 0x0810;
+        public static final int TOY_GAME                            = 0x0814;
+
+        // Devices in the HEALTH major class
+        public static final int HEALTH_UNCATEGORIZED                = 0x0900;
+        public static final int HEALTH_BLOOD_PRESSURE               = 0x0904;
+        public static final int HEALTH_THERMOMETER                  = 0x0908;
+        public static final int HEALTH_WEIGHING                     = 0x090C;
+        public static final int HEALTH_GLUCOSE                      = 0x0910;
+        public static final int HEALTH_PULSE_OXIMETER               = 0x0914;
+        public static final int HEALTH_PULSE_RATE                   = 0x0918;
+        public static final int HEALTH_DATA_DISPLAY                 = 0x091C;
+
+        /** Returns the Device Class component of a bluetooth class. This includes
+         * both the major and minor device components. Values returned from this
+         * function can be compared with the constants Device.FOO. A bluetooth
+         * device can only be associated with one device class.
+         */
+        public static int getDevice(int btClass) {
+            if (btClass == ERROR) {
+                return ERROR;
+            }
+            return (btClass & Device.BITMASK);
+        }
+    }
+}
+
diff --git a/core/java/android/bluetooth/BluetoothDevice.java b/core/java/android/bluetooth/BluetoothDevice.java
new file mode 100644
index 0000000..1ba1c1e
--- /dev/null
+++ b/core/java/android/bluetooth/BluetoothDevice.java
@@ -0,0 +1,580 @@
+/*
+ * 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 android.bluetooth;
+
+import android.os.RemoteException;
+import android.util.Log;
+
+import java.io.UnsupportedEncodingException;
+
+/**
+ * The Android Bluetooth API is not finalized, and *will* change. Use at your
+ * own risk.
+ *
+ * Manages the local Bluetooth device. Scan for devices, create bondings,
+ * power up and down the adapter.
+ *
+ * @hide
+ */
+public class BluetoothDevice {
+    /** Inquiry scan and page scan are both off.
+     *  Device is neither discoverable nor connectable */
+    public static final int SCAN_MODE_NONE = 0;
+    /** Page scan is on, inquiry scan is off.
+     *  Device is connectable, but not discoverable */
+    public static final int SCAN_MODE_CONNECTABLE = 1;
+    /** Page scan and inquiry scan are on.
+     *  Device is connectable and discoverable */
+    public static final int SCAN_MODE_CONNECTABLE_DISCOVERABLE = 3;
+
+    public static final int RESULT_FAILURE = -1;
+    public static final int RESULT_SUCCESS = 0;
+
+    /** We do not have a link key for the remote device, and are therefore not
+     * bonded */
+    public static final int BOND_NOT_BONDED = 0;
+    /** We have a link key for the remote device, and are probably bonded. */
+    public static final int BOND_BONDED = 1;
+    /** We are currently attempting bonding */
+    public static final int BOND_BONDING = 2;
+
+    //TODO: Unify these result codes in BluetoothResult or BluetoothError
+    /** A bond attempt failed because pins did not match, or remote device did
+     * not respond to pin request in time */
+    public static final int UNBOND_REASON_AUTH_FAILED = 1;
+    /** A bond attempt failed because the other side explicilty rejected
+     * bonding */
+    public static final int UNBOND_REASON_AUTH_REJECTED = 2;
+    /** A bond attempt failed because we canceled the bonding process */
+    public static final int UNBOND_REASON_AUTH_CANCELED = 3;
+    /** A bond attempt failed because we could not contact the remote device */
+    public static final int UNBOND_REASON_REMOTE_DEVICE_DOWN = 4;
+    /** A bond attempt failed because a discovery is in progress */
+    public static final int UNBOND_REASON_DISCOVERY_IN_PROGRESS = 5;
+    /** An existing bond was explicitly revoked */
+    public static final int UNBOND_REASON_REMOVED = 6;
+
+    private static final String TAG = "BluetoothDevice";
+    
+    private final IBluetoothDevice mService;
+    /**
+     * @hide - hide this because it takes a parameter of type
+     * IBluetoothDevice, which is a System private class.
+     * Also note that Context.getSystemService is a factory that
+     * returns a BlueToothDevice. That is the right way to get
+     * a BluetoothDevice.
+     */
+    public BluetoothDevice(IBluetoothDevice service) {
+        mService = service;
+    }
+
+    /**
+     * Get the current status of Bluetooth hardware.
+     *
+     * @return true if Bluetooth enabled, false otherwise.
+     */
+    public boolean isEnabled() {
+        try {
+            return mService.isEnabled();
+        } catch (RemoteException e) {Log.e(TAG, "", e);}
+        return false;
+    }
+
+    /**
+     * Enable the Bluetooth device.
+     * Turn on the underlying hardware.
+     * This is an asynchronous call, BluetoothIntent.ENABLED_ACTION will be
+     * sent if and when the device is successfully enabled.
+     * @return false if we cannot enable the Bluetooth device. True does not
+     * imply the device was enabled, it only implies that so far there were no
+     * problems.
+     */
+    public boolean enable() {
+        return enable(null);
+    }
+
+    /**
+     * Enable the Bluetooth device.
+     * Turns on the underlying hardware.
+     * This is an asynchronous call. onEnableResult() of your callback will be
+     * called when the call is complete, with either RESULT_SUCCESS or
+     * RESULT_FAILURE.
+     *
+     * Your callback will be called from a binder thread, not the main thread.
+     *
+     * In addition to the callback, BluetoothIntent.ENABLED_ACTION will be
+     * broadcast if the device is successfully enabled.
+     *
+     * @param callback Your callback, null is ok.
+     * @return true if your callback was successfully registered, or false if
+     * there was an error, implying your callback will never be called.
+     */
+    public boolean enable(IBluetoothDeviceCallback callback) {
+        try {
+            return mService.enable(callback);
+        } catch (RemoteException e) {Log.e(TAG, "", e);}
+        return false;
+    }
+
+    /**
+     * Disable the Bluetooth device.
+     * This turns off the underlying hardware.
+     *
+     * @return true if successful, false otherwise.
+     */
+    public boolean disable() {
+        try {
+            return mService.disable();
+        } catch (RemoteException e) {Log.e(TAG, "", e);}
+        return false;
+    }
+
+    public String getAddress() {
+        try {
+            return mService.getAddress();
+        } catch (RemoteException e) {Log.e(TAG, "", e);}
+        return null;
+    }
+
+    /**
+     * Get the friendly Bluetooth name of this device.
+     *
+     * This name is visible to remote Bluetooth devices. Currently it is only
+     * possible to retrieve the Bluetooth name when Bluetooth is enabled.
+     *
+     * @return the Bluetooth name, or null if there was a problem.
+     */
+    public String getName() {
+        try {
+            return mService.getName();
+        } catch (RemoteException e) {Log.e(TAG, "", e);}
+        return null;
+    }
+
+    /**
+     * Set the friendly Bluetooth name of this device.
+     *
+     * This name is visible to remote Bluetooth devices. The Bluetooth Service
+     * is responsible for persisting this name.
+     *
+     * @param name the name to set
+     * @return     true, if the name was successfully set. False otherwise.
+     */
+    public boolean setName(String name) {
+        try {
+            return mService.setName(name);
+        } catch (RemoteException e) {Log.e(TAG, "", e);}
+        return false;
+    }
+
+    public String getVersion() {
+        try {
+            return mService.getVersion();
+        } catch (RemoteException e) {Log.e(TAG, "", e);}
+        return null;
+    }
+    public String getRevision() {
+        try {
+            return mService.getRevision();
+        } catch (RemoteException e) {Log.e(TAG, "", e);}
+        return null;
+    }
+    public String getManufacturer() {
+        try {
+            return mService.getManufacturer();
+        } catch (RemoteException e) {Log.e(TAG, "", e);}
+        return null;
+    }
+    public String getCompany() {
+        try {
+            return mService.getCompany();
+        } catch (RemoteException e) {Log.e(TAG, "", e);}
+        return null;
+    }
+
+    /**
+     * Get the current scan mode.
+     * Used to determine if the local device is connectable and/or discoverable
+     * @return Scan mode, one of SCAN_MODE_* or an error code
+     */
+    public int getScanMode() {
+        try {
+            return mService.getScanMode();
+        } catch (RemoteException e) {Log.e(TAG, "", e);}
+        return BluetoothError.ERROR_IPC;
+    }
+
+    /**
+     * Set the current scan mode.
+     * Used to make the local device connectable and/or discoverable
+     * @param scanMode One of SCAN_MODE_*
+     */
+    public void setScanMode(int scanMode) {
+        try {
+            mService.setScanMode(scanMode);
+        } catch (RemoteException e) {Log.e(TAG, "", e);}
+    }
+
+    public int getDiscoverableTimeout() {
+        try {
+            return mService.getDiscoverableTimeout();
+        } catch (RemoteException e) {Log.e(TAG, "", e);}
+        return -1;
+    }
+    public void setDiscoverableTimeout(int timeout) {
+        try {
+            mService.setDiscoverableTimeout(timeout);
+        } catch (RemoteException e) {Log.e(TAG, "", e);}
+    }
+
+    public boolean startDiscovery() {
+        return startDiscovery(true);
+    }
+    public boolean startDiscovery(boolean resolveNames) {
+        try {
+            return mService.startDiscovery(resolveNames);
+        } catch (RemoteException e) {Log.e(TAG, "", e);}
+        return false;
+    }
+
+    public void cancelDiscovery() {
+        try {
+            mService.cancelDiscovery();
+        } catch (RemoteException e) {Log.e(TAG, "", e);}
+    }
+
+    public boolean isDiscovering() {
+        try {
+            return mService.isDiscovering();
+        } catch (RemoteException e) {Log.e(TAG, "", e);}
+        return false;
+    }
+
+    public boolean startPeriodicDiscovery() {
+        try {
+            return mService.startPeriodicDiscovery();
+        } catch (RemoteException e) {Log.e(TAG, "", e);}
+        return false;
+    }
+    public boolean stopPeriodicDiscovery() {
+        try {
+            return mService.stopPeriodicDiscovery();
+        } catch (RemoteException e) {Log.e(TAG, "", e);}
+        return false;
+    }
+    public boolean isPeriodicDiscovery() {
+        try {
+            return mService.isPeriodicDiscovery();
+        } catch (RemoteException e) {Log.e(TAG, "", e);}
+        return false;
+    }
+
+    public String[] listRemoteDevices() {
+        try {
+            return mService.listRemoteDevices();
+        } catch (RemoteException e) {Log.e(TAG, "", e);}
+        return null;
+    }
+
+    /**
+     * List remote devices that have a low level (ACL) connection.
+     *
+     * RFCOMM, SDP and L2CAP are all built on ACL connections. Devices can have
+     * an ACL connection even when not paired - this is common for SDP queries
+     * or for in-progress pairing requests.
+     *
+     * In most cases you probably want to test if a higher level protocol is
+     * connected, rather than testing ACL connections.
+     *
+     * @return bluetooth hardware addresses of remote devices with a current
+     *         ACL connection. Array size is 0 if no devices have a
+     *         connection. Null on error.
+     */
+    public String[] listAclConnections() {
+        try {
+            return mService.listAclConnections();
+        } catch (RemoteException e) {Log.e(TAG, "", e);}
+        return null;
+    }
+
+    /**
+     * Check if a specified remote device has a low level (ACL) connection.
+     *
+     * RFCOMM, SDP and L2CAP are all built on ACL connections. Devices can have
+     * an ACL connection even when not paired - this is common for SDP queries
+     * or for in-progress pairing requests.
+     *
+     * In most cases you probably want to test if a higher level protocol is
+     * connected, rather than testing ACL connections.
+     *
+     * @param address the Bluetooth hardware address you want to check.
+     * @return true if there is an ACL connection, false otherwise and on
+     *         error.
+     */
+    public boolean isAclConnected(String address) {
+        try {
+            return mService.isAclConnected(address);
+        } catch (RemoteException e) {Log.e(TAG, "", e);}
+        return false;
+    }
+
+    /**
+     * Perform a low level (ACL) disconnection of a remote device.
+     *
+     * This forcably disconnects the ACL layer connection to a remote device,
+     * which will cause all RFCOMM, SDP and L2CAP connections to this remote
+     * device to close.
+     *
+     * @param address the Bluetooth hardware address you want to disconnect.
+     * @return true if the device was disconnected, false otherwise and on
+     *         error.
+     */
+    public boolean disconnectRemoteDeviceAcl(String address) {
+        try {
+            return mService.disconnectRemoteDeviceAcl(address);
+        } catch (RemoteException e) {Log.e(TAG, "", e);}
+        return false;
+    }
+
+    /**
+     * Create a bonding with a remote bluetooth device.
+     *
+     * This is an asynchronous call. The result of this bonding attempt can be
+     * observed through BluetoothIntent.BOND_STATE_CHANGED_ACTION intents.
+     *
+     * @param address the remote device Bluetooth address.
+     * @return false If there was an immediate problem creating the bonding,
+     *         true otherwise.
+     */
+    public boolean createBond(String address) {
+        try {
+            return mService.createBond(address);
+        } catch (RemoteException e) {Log.e(TAG, "", e);}
+        return false;
+    }
+
+    /**
+     * Cancel an in-progress bonding request started with createBond.
+     */
+    public boolean cancelBondProcess(String address) {
+        try {
+            return mService.cancelBondProcess(address);
+        } catch (RemoteException e) {Log.e(TAG, "", e);}
+        return false;
+    }
+
+    /**
+     * Remove an already exisiting bonding (delete the link key).
+     */
+    public boolean removeBond(String address) {
+        try {
+            return mService.removeBond(address);
+        } catch (RemoteException e) {Log.e(TAG, "", e);}
+        return false;
+    }
+
+    /**
+     * List remote devices that are bonded (paired) to the local device.
+     *
+     * Bonding (pairing) is the process by which the user enters a pin code for
+     * the device, which generates a shared link key, allowing for
+     * authentication and encryption of future connections. In Android we
+     * require bonding before RFCOMM or SCO connections can be made to a remote
+     * device.
+     *
+     * This function lists which remote devices we have a link key for. It does
+     * not cause any RF transmission, and does not check if the remote device
+     * still has it's link key with us. If the other side no longer has its
+     * link key then the RFCOMM or SCO connection attempt will result in an
+     * error.
+     *
+     * This function does not check if the remote device is in range.
+     *
+     * Remote devices that have an in-progress bonding attempt are not
+     * returned.
+     *
+     * @return bluetooth hardware addresses of remote devices that are
+     *         bonded. Array size is 0 if no devices are bonded. Null on error.
+     */
+    public String[] listBonds() {
+        try {
+            return mService.listBonds();
+        } catch (RemoteException e) {Log.e(TAG, "", e);}
+        return null;
+    }
+
+    /**
+     * Get the bonding state of a remote device.
+     *
+     * Result is one of:
+     * BluetoothError.*
+     * BOND_*
+     *
+     * @param address Bluetooth hardware address of the remote device to check.
+     * @return Result code
+     */
+    public int getBondState(String address) {
+        try {
+            return mService.getBondState(address);
+        } catch (RemoteException e) {Log.e(TAG, "", e);}
+        return BluetoothError.ERROR_IPC;
+    }
+
+    public String getRemoteName(String address) {
+        try {
+            return mService.getRemoteName(address);
+        } catch (RemoteException e) {Log.e(TAG, "", e);}
+        return null;
+    }
+
+    public String getRemoteVersion(String address) {
+        try {
+            return mService.getRemoteVersion(address);
+        } catch (RemoteException e) {Log.e(TAG, "", e);}
+        return null;
+    }
+    public String getRemoteRevision(String address) {
+        try {
+            return mService.getRemoteRevision(address);
+        } catch (RemoteException e) {Log.e(TAG, "", e);}
+        return null;
+    }
+    public String getRemoteManufacturer(String address) {
+        try {
+            return mService.getRemoteManufacturer(address);
+        } catch (RemoteException e) {Log.e(TAG, "", e);}
+        return null;
+    }
+    public String getRemoteCompany(String address) {
+        try {
+            return mService.getRemoteCompany(address);
+        } catch (RemoteException e) {Log.e(TAG, "", e);}
+        return null;
+    }
+
+    /**
+     * Returns the RFCOMM channel associated with the 16-byte UUID on
+     * the remote Bluetooth address.
+     *
+     * Performs a SDP ServiceSearchAttributeRequest transaction. The provided
+     * uuid is verified in the returned record. If there was a problem, or the
+     * specified uuid does not exist, -1 is returned.
+     */
+    public boolean getRemoteServiceChannel(String address, short uuid16,
+            IBluetoothDeviceCallback callback) {
+        try {
+            return mService.getRemoteServiceChannel(address, uuid16, callback);
+        } catch (RemoteException e) {Log.e(TAG, "", e);}
+        return false;
+    }
+
+    /**
+     * Get the major, minor and servics classes of a remote device.
+     * These classes are encoded as a 32-bit integer. See BluetoothClass.
+     * @param address remote device
+     * @return 32-bit class suitable for use with BluetoothClass.
+     */
+    public int getRemoteClass(String address) {
+        try {
+            return mService.getRemoteClass(address);
+        } catch (RemoteException e) {Log.e(TAG, "", e);}
+        return BluetoothClass.ERROR;
+    }
+
+    public byte[] getRemoteFeatures(String address) {
+        try {
+            return mService.getRemoteFeatures(address);
+        } catch (RemoteException e) {Log.e(TAG, "", e);}
+        return null;
+    }
+    public String lastSeen(String address) {
+        try {
+            return mService.lastSeen(address);
+        } catch (RemoteException e) {Log.e(TAG, "", e);}
+        return null;
+    }
+    public String lastUsed(String address) {
+        try {
+            return mService.lastUsed(address);
+        } catch (RemoteException e) {Log.e(TAG, "", e);}
+        return null;
+    }
+
+    public boolean setPin(String address, byte[] pin) {
+        try {
+            return mService.setPin(address, pin);
+        } catch (RemoteException e) {Log.e(TAG, "", e);}
+        return false;
+    }
+    public boolean cancelPin(String address) {
+        try {
+            return mService.cancelPin(address);
+        } catch (RemoteException e) {Log.e(TAG, "", e);}
+        return false;
+    }
+
+    /**
+     * Check that a pin is valid and convert to byte array.
+     *
+     * Bluetooth pin's are 1 to 16 bytes of UTF8 characters.
+     * @param pin pin as java String
+     * @return the pin code as a UTF8 byte array, or null if it is an invalid
+     *         Bluetooth pin.
+     */
+    public static byte[] convertPinToBytes(String pin) {
+        if (pin == null) {
+            return null;
+        }
+        byte[] pinBytes;
+        try {
+            pinBytes = pin.getBytes("UTF8");
+        } catch (UnsupportedEncodingException uee) {
+            Log.e(TAG, "UTF8 not supported?!?");  // this should not happen
+            return null;
+        }
+        if (pinBytes.length <= 0 || pinBytes.length > 16) {
+            return null;
+        }
+        return pinBytes;
+    }
+    
+
+    private static final int ADDRESS_LENGTH = 17;
+    /** Sanity check a bluetooth address, such as "00:43:A8:23:10:F0" */
+    public static boolean checkBluetoothAddress(String address) {
+        if (address == null || address.length() != ADDRESS_LENGTH) {
+            return false;
+        }
+        for (int i = 0; i < ADDRESS_LENGTH; i++) {
+            char c = address.charAt(i);
+            switch (i % 3) {
+            case 0:
+            case 1:
+                if (Character.digit(c, 16) != -1) {
+                    break;  // hex character, OK
+                }
+                return false;
+            case 2:
+                if (c == ':') {
+                    break;  // OK
+                }
+                return false;
+            }
+        }
+        return true;
+    }
+}
diff --git a/core/java/android/bluetooth/BluetoothError.java b/core/java/android/bluetooth/BluetoothError.java
new file mode 100644
index 0000000..2554bea
--- /dev/null
+++ b/core/java/android/bluetooth/BluetoothError.java
@@ -0,0 +1,42 @@
+/*
+ * 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 android.bluetooth;
+
+/**
+ * Bluetooth API error codes.
+ *
+ * Errors are always negative.
+ *
+ * @hide
+ */
+public class BluetoothError {
+    /** No error */
+    public static final int SUCCESS = 0;
+
+    /** Generic error */
+    public static final int ERROR = -1000;
+
+    /** Bluetooth currently disabled */
+    public static final int ERROR_DISABLED = -1001;
+
+    /** IPC is not ready, for example service is not yet bound */
+    public static final int ERROR_IPC_NOT_READY = -1011;
+
+    /** Some other IPC error, for example a RemoteException */
+    public static final int ERROR_IPC = -1012;
+
+}
diff --git a/core/java/android/bluetooth/BluetoothHeadset.java b/core/java/android/bluetooth/BluetoothHeadset.java
new file mode 100644
index 0000000..34196bf
--- /dev/null
+++ b/core/java/android/bluetooth/BluetoothHeadset.java
@@ -0,0 +1,353 @@
+/*
+ * 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 android.bluetooth;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.RemoteException;
+import android.os.IBinder;
+import android.util.Log;
+
+/**
+ * The Android Bluetooth API is not finalized, and *will* change. Use at your
+ * own risk.
+ *
+ * Public API for controlling the Bluetooth Headset Service. This includes both
+ * Bluetooth Headset and Handsfree (v1.5) profiles. The Headset service will
+ * attempt a handsfree connection first, and fall back to headset.
+ *
+ * BluetoothHeadset is a proxy object for controlling the Bluetooth Headset
+ * Service via IPC.
+ *
+ * Creating a BluetoothHeadset object will create a binding with the
+ * BluetoothHeadset service. Users of this object should call close() when they
+ * are finished with the BluetoothHeadset, so that this proxy object can unbind
+ * from the service.
+ *
+ * This BluetoothHeadset object is not immediately bound to the
+ * BluetoothHeadset service. Use the ServiceListener interface to obtain a
+ * notification when it is bound, this is especially important if you wish to
+ * immediately call methods on BluetootHeadset after construction.
+ *
+ * Android only supports one connected Bluetooth Headset at a time.
+ *
+ * @hide
+ */
+public class BluetoothHeadset {
+
+    private static final String TAG = "BluetoothHeadset";
+    private static final boolean DBG = false;
+
+    private IBluetoothHeadset mService;
+    private final Context mContext;
+    private final ServiceListener mServiceListener;
+
+    /** There was an error trying to obtain the state */
+    public static final int STATE_ERROR        = -1;
+    /** No headset currently connected */
+    public static final int STATE_DISCONNECTED = 0;
+    /** Connection attempt in progress */
+    public static final int STATE_CONNECTING   = 1;
+    /** A headset is currently connected */
+    public static final int STATE_CONNECTED    = 2;
+
+    public static final int RESULT_FAILURE = 0;
+    public static final int RESULT_SUCCESS = 1;
+    /** Connection canceled before completetion. */
+    public static final int RESULT_CANCELED = 2;
+
+    /** Default priority for headsets that should be auto-connected */
+    public static final int PRIORITY_AUTO = 100;
+    /** Default priority for headsets that should not be auto-connected */
+    public static final int PRIORITY_OFF = 0;
+
+    /**
+     * An interface for notifying BluetoothHeadset IPC clients when they have
+     * been connected to the BluetoothHeadset service.
+     */
+    public interface ServiceListener {
+        /**
+         * Called to notify the client when this proxy object has been
+         * connected to the BluetoothHeadset service. Clients must wait for
+         * this callback before making IPC calls on the BluetoothHeadset
+         * service.
+         */
+        public void onServiceConnected();
+
+        /**
+         * Called to notify the client that this proxy object has been
+         * disconnected from the BluetoothHeadset service. Clients must not
+         * make IPC calls on the BluetoothHeadset service after this callback.
+         * This callback will currently only occur if the application hosting
+         * the BluetoothHeadset service, but may be called more often in future.
+         */
+        public void onServiceDisconnected();
+    }
+
+    /**
+     * Create a BluetoothHeadset proxy object.
+     */
+    public BluetoothHeadset(Context context, ServiceListener l) {
+        mContext = context;
+        mServiceListener = l;
+        if (!context.bindService(new Intent(IBluetoothHeadset.class.getName()), mConnection, 0)) {
+            Log.e(TAG, "Could not bind to Bluetooth Headset Service");
+        }
+    }
+
+    protected void finalize() throws Throwable {
+        try {
+            close();
+        } finally {
+            super.finalize();
+        }
+    }
+
+    /**
+     * Close the connection to the backing service.
+     * Other public functions of BluetoothHeadset will return default error
+     * results once close() has been called. Multiple invocations of close()
+     * are ok.
+     */
+    public synchronized void close() {
+        if (mConnection != null) {
+            mContext.unbindService(mConnection);
+            mConnection = null;
+        }
+    }
+
+    /**
+     * Get the current state of the Bluetooth Headset service.
+     * @return One of the STATE_ return codes, or STATE_ERROR if this proxy
+     *         object is currently not connected to the Headset service.
+     */
+    public int getState() {
+        if (mService != null) {
+            try {
+                return mService.getState();
+            } catch (RemoteException e) {Log.e(TAG, e.toString());}
+        } else {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
+        }
+        return BluetoothHeadset.STATE_ERROR;
+    }
+
+    /**
+     * Get the Bluetooth address of the current headset.
+     * @return The Bluetooth address, or null if not in connected or connecting
+     *         state, or if this proxy object is not connected to the Headset
+     *         service.
+     */
+    public String getHeadsetAddress() {
+        if (mService != null) {
+            try {
+                return mService.getHeadsetAddress();
+            } catch (RemoteException e) {Log.e(TAG, e.toString());}
+        } else {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
+        }
+        return null;
+    }
+
+    /**
+     * Request to initiate a connection to a headset.
+     * This call does not block. Fails if a headset is already connecting
+     * or connected.
+     * Initiates auto-connection if address is null. Tries to connect to all
+     * devices with priority greater than PRIORITY_AUTO in descending order.
+     * @param address The Bluetooth Address to connect to, or null to
+     *                auto-connect to the last connected headset.
+     * @return        False if there was a problem initiating the connection
+     *                procedure, and no further HEADSET_STATE_CHANGED intents
+     *                will be expected.
+     */
+    public boolean connectHeadset(String address) {
+        if (mService != null) {
+            try {
+                if (mService.connectHeadset(address)) {
+                    return true;
+                }
+            } catch (RemoteException e) {Log.e(TAG, e.toString());}
+        } else {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
+        }
+        return false;
+    }
+
+    /**
+     * Returns true if the specified headset is connected (does not include
+     * connecting). Returns false if not connected, or if this proxy object
+     * if not currently connected to the headset service.
+     */
+    public boolean isConnected(String address) {
+        if (mService != null) {
+            try {
+                return mService.isConnected(address);
+            } catch (RemoteException e) {Log.e(TAG, e.toString());}
+        } else {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
+        }
+        return false;
+    }
+
+    /**
+     * Disconnects the current headset. Currently this call blocks, it may soon
+     * be made asynchornous. Returns false if this proxy object is
+     * not currently connected to the Headset service.
+     */
+    public boolean disconnectHeadset() {
+        if (mService != null) {
+            try {
+                mService.disconnectHeadset();
+                return true;
+            } catch (RemoteException e) {Log.e(TAG, e.toString());}
+        } else {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
+        }
+        return false;
+    }
+
+    /**
+     * Start BT Voice Recognition mode, and set up Bluetooth audio path.
+     * Returns false if there is no headset connected, or if the
+     * connected headset does not support voice recognition, or on
+     * error.
+     */
+    public boolean startVoiceRecognition() {
+        if (mService != null) {
+            try {
+                return mService.startVoiceRecognition();
+            } catch (RemoteException e) {Log.e(TAG, e.toString());}
+        } else {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
+        }
+        return false;
+    }
+
+    /**
+     * Stop BT Voice Recognition mode, and shut down Bluetooth audio path.
+     * Returns false if there is no headset connected, or the connected
+     * headset is not in voice recognition mode, or on error.
+     */
+    public boolean stopVoiceRecognition() {
+        if (mService != null) {
+            try {
+                return mService.stopVoiceRecognition();
+            } catch (RemoteException e) {Log.e(TAG, e.toString());}
+        } else {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
+        }
+        return false;
+    }
+
+    /**
+     * Set priority of headset.
+     * Priority is a non-negative integer. By default paired headsets will have
+     * a priority of PRIORITY_AUTO, and unpaired headset PRIORITY_NONE (0).
+     * Headsets with priority greater than zero will be auto-connected, and
+     * incoming connections will be accepted (if no other headset is
+     * connected).
+     * Auto-connection occurs at the following events: boot, incoming phone
+     * call, outgoing phone call.
+     * Headsets with priority equal to zero, or that are unpaired, are not
+     * auto-connected.
+     * Incoming connections are ignored regardless of priority if there is
+     * already a headset connected.
+     * @param address Paired headset
+     * @param priority Integer priority, for example PRIORITY_AUTO or
+     *                 PRIORITY_NONE
+     * @return True if successful, false if there was some error.
+     */
+    public boolean setPriority(String address, int priority) {
+        if (mService != null) {
+            try {
+                return mService.setPriority(address, priority);
+            } catch (RemoteException e) {Log.e(TAG, e.toString());}
+        } else {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
+        }
+        return false;
+    }
+
+    /**
+     * Get priority of headset.
+     * @param address Headset
+     * @return non-negative priority, or negative error code on error.
+     */
+    public int getPriority(String address) {
+        if (mService != null) {
+            try {
+                return mService.getPriority(address);
+            } catch (RemoteException e) {Log.e(TAG, e.toString());}
+        } else {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
+        }
+        return -1;
+    }
+
+    /**
+     * Check class bits for possible HSP or HFP support.
+     * This is a simple heuristic that tries to guess if a device with the
+     * given class bits might support HSP or HFP. It is not accurate for all
+     * devices. It tries to err on the side of false positives.
+     * @return True if this device might support HSP or HFP.
+     */
+    public static boolean doesClassMatch(int btClass) {
+        // The render service class is required by the spec for HFP, so is a
+        // pretty good signal
+        if (BluetoothClass.Service.hasService(btClass, BluetoothClass.Service.RENDER)) {
+            return true;
+        }
+        // Just in case they forgot the render service class
+        switch (BluetoothClass.Device.getDevice(btClass)) {
+        case BluetoothClass.Device.AUDIO_VIDEO_HANDSFREE:
+        case BluetoothClass.Device.AUDIO_VIDEO_WEARABLE_HEADSET:
+        case BluetoothClass.Device.AUDIO_VIDEO_CAR_AUDIO:
+            return true;
+        default:
+            return false;
+        }
+    }
+
+    private ServiceConnection mConnection = new ServiceConnection() {
+        public void onServiceConnected(ComponentName className, IBinder service) {
+            if (DBG) Log.d(TAG, "Proxy object connected");
+            mService = IBluetoothHeadset.Stub.asInterface(service);
+            if (mServiceListener != null) {
+                mServiceListener.onServiceConnected();
+            }
+        }
+        public void onServiceDisconnected(ComponentName className) {
+            if (DBG) Log.d(TAG, "Proxy object disconnected");
+            mService = null;
+            if (mServiceListener != null) {
+                mServiceListener.onServiceDisconnected();
+            }
+        }
+    };
+}
diff --git a/core/java/android/bluetooth/BluetoothIntent.java b/core/java/android/bluetooth/BluetoothIntent.java
new file mode 100644
index 0000000..b66b06e
--- /dev/null
+++ b/core/java/android/bluetooth/BluetoothIntent.java
@@ -0,0 +1,128 @@
+/*
+ * 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 android.bluetooth;
+
+import android.annotation.SdkConstant;
+import android.annotation.SdkConstant.SdkConstantType;
+
+/**
+ * The Android Bluetooth API is not finalized, and *will* change. Use at your
+ * own risk.
+ *
+ * Manages the local Bluetooth device. Scan for devices, create bondings,
+ * power up and down the adapter.
+ *
+ * @hide
+ */
+public interface BluetoothIntent {
+    public static final String SCAN_MODE =
+        "android.bluetooth.intent.SCAN_MODE";
+    public static final String ADDRESS =
+        "android.bluetooth.intent.ADDRESS";
+    public static final String NAME =
+        "android.bluetooth.intent.NAME";
+    public static final String ALIAS =
+        "android.bluetooth.intent.ALIAS";
+    public static final String RSSI =
+        "android.bluetooth.intent.RSSI";
+    public static final String CLASS =
+        "android.bluetooth.intent.CLASS";
+    public static final String HEADSET_STATE =
+        "android.bluetooth.intent.HEADSET_STATE";
+    public static final String HEADSET_PREVIOUS_STATE =
+        "android.bluetooth.intent.HEADSET_PREVIOUS_STATE";
+    public static final String BOND_STATE =
+        "android.bluetooth.intent.BOND_STATE";
+    public static final String BOND_PREVIOUS_STATE =
+        "android.bluetooth.intent.BOND_PREVIOUS_STATE";
+    public static final String REASON =
+        "android.bluetooth.intent.REASON";
+
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String ENABLED_ACTION          =
+        "android.bluetooth.intent.action.ENABLED";
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String DISABLED_ACTION         =
+        "android.bluetooth.intent.action.DISABLED";
+
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String NAME_CHANGED_ACTION  =
+        "android.bluetooth.intent.action.NAME_CHANGED";
+
+    /**
+     * Broadcast when the scan mode changes. Always contains an int extra
+     * named SCAN_MODE that contains the new scan mode.
+     */
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String SCAN_MODE_CHANGED_ACTION         =
+        "android.bluetooth.intent.action.SCAN_MODE_CHANGED";
+
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String DISCOVERY_STARTED_ACTION          =
+        "android.bluetooth.intent.action.DISCOVERY_STARTED";
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String DISCOVERY_COMPLETED_ACTION        =
+        "android.bluetooth.intent.action.DISCOVERY_COMPLETED";
+
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String PAIRING_REQUEST_ACTION            =
+        "android.bluetooth.intent.action.PAIRING_REQUEST";
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String PAIRING_CANCEL_ACTION             =
+        "android.bluetooth.intent.action.PAIRING_CANCEL";
+
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String REMOTE_DEVICE_FOUND_ACTION        =
+        "android.bluetooth.intent.action.REMOTE_DEVICE_FOUND";
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String REMOTE_DEVICE_DISAPPEARED_ACTION  =
+        "android.bluetooth.intent.action.REMOTE_DEVICE_DISAPPEARED";
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String REMOTE_DEVICE_CLASS_UPDATED_ACTION  =
+        "android.bluetooth.intent.action.REMOTE_DEVICE_DISAPPEARED";
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String REMOTE_DEVICE_CONNECTED_ACTION    =
+        "android.bluetooth.intent.action.REMOTE_DEVICE_CONNECTED";
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String REMOTE_DEVICE_DISCONNECT_REQUESTED_ACTION =
+        "android.bluetooth.intent.action.REMOTE_DEVICE_DISCONNECT_REQUESTED";
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String REMOTE_DEVICE_DISCONNECTED_ACTION =
+        "android.bluetooth.intent.action.REMOTE_DEVICE_DISCONNECTED";
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String REMOTE_NAME_UPDATED_ACTION        =
+        "android.bluetooth.intent.action.REMOTE_NAME_UPDATED";
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String REMOTE_NAME_FAILED_ACTION         =
+        "android.bluetooth.intent.action.REMOTE_NAME_FAILED";
+
+    /**
+     * Broadcast when the bond state of a remote device changes.
+     * Has string extra ADDRESS and int extras BOND_STATE and
+     * BOND_PREVIOUS_STATE.
+     * If BOND_STATE is BluetoothDevice.BOND_NOT_BONDED then will
+     * also have an int extra REASON with a value of:
+     * BluetoothDevice.BOND_RESULT_*
+     * */
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String BOND_STATE_CHANGED_ACTION      =
+        "android.bluetooth.intent.action.BOND_STATE_CHANGED_ACTION";
+
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String HEADSET_STATE_CHANGED_ACTION      =
+        "android.bluetooth.intent.action.HEADSET_STATE_CHANGED";
+}
diff --git a/core/java/android/bluetooth/Database.java b/core/java/android/bluetooth/Database.java
new file mode 100644
index 0000000..fef641a
--- /dev/null
+++ b/core/java/android/bluetooth/Database.java
@@ -0,0 +1,200 @@
+/*
+ * Copyright (C) 2007 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 android.bluetooth;
+
+import android.bluetooth.RfcommSocket;
+
+import android.util.Log;
+
+import java.io.*;
+import java.util.*;
+
+/**
+ * The Android Bluetooth API is not finalized, and *will* change. Use at your
+ * own risk.
+ * 
+ * A low-level API to the Service Discovery Protocol (SDP) Database.
+ *
+ * Allows service records to be added to the local SDP database. Once added,
+ * these services will be advertised to remote devices when they make SDP
+ * queries on this device.
+ *
+ * Currently this API is a thin wrapper to the bluez SDP Database API. See:
+ * http://wiki.bluez.org/wiki/Database
+ * http://wiki.bluez.org/wiki/HOWTO/ManagingServiceRecords
+ * @hide
+ */
+public final class Database {
+    private static Database mInstance;
+
+    private static final String sLogName = "android.bluetooth.Database";
+
+    /**
+     * Class load time initialization
+     */
+    static {
+        classInitNative();
+    }
+    private native static void classInitNative();
+
+    /**
+     * Private to enforce singleton property
+     */
+    private Database() {
+        initializeNativeDataNative();
+    }
+    private native void initializeNativeDataNative();
+
+    protected void finalize() throws Throwable {
+        try {
+            cleanupNativeDataNative();
+        } finally {
+            super.finalize();
+        }
+    }
+    private native void cleanupNativeDataNative();
+
+    /**
+     * Singelton accessor
+     * @return The singleton instance of Database
+     */
+    public static synchronized Database getInstance() {
+        if (mInstance == null) {
+            mInstance = new Database();
+        }
+        return mInstance;
+    }
+
+    /**
+     * Advertise a service with an RfcommSocket.
+     *
+     * This adds the service the SDP Database with the following attributes
+     * set: Service Name, Protocol Descriptor List, Service Class ID List
+     * TODO: Construct a byte[] record directly, rather than via XML.
+     * @param       socket The rfcomm socket to advertise (by channel).
+     * @param       serviceName A short name for this service
+     * @param       uuid
+     *                  Unique identifier for this service, by which clients
+     *                  can search for your service
+     * @return      Handle to the new service record
+     */
+    public int advertiseRfcommService(RfcommSocket socket,
+                                      String serviceName,
+                                      UUID uuid) throws IOException {
+        String xmlRecord =
+                "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n" +
+                "<record>\n" +
+                "  <attribute id=\"0x0001\">\n" + // ServiceClassIDList
+                "    <sequence>\n" +
+                "      <uuid value=\""
+                        + uuid.toString() +       // UUID for this service
+                        "\"/>\n" +
+                "    </sequence>\n" +
+                "  </attribute>\n" +
+                "  <attribute id=\"0x0004\">\n" + // ProtocolDescriptorList
+                "    <sequence>\n" +
+                "      <sequence>\n" +
+                "        <uuid value=\"0x0100\"/>\n" +  // L2CAP
+                "      </sequence>\n" +
+                "      <sequence>\n" +
+                "        <uuid value=\"0x0003\"/>\n" +  // RFCOMM
+                "        <uint8 value=\"" +
+                        socket.getPort() +              // RFCOMM port
+                        "\" name=\"channel\"/>\n" +
+                "      </sequence>\n" +
+                "    </sequence>\n" +
+                "  </attribute>\n" +
+                "  <attribute id=\"0x0100\">\n" + // ServiceName
+                "    <text value=\"" + serviceName + "\"/>\n" +
+                "  </attribute>\n" +
+                "</record>\n";
+        Log.i(sLogName, xmlRecord);
+        return addServiceRecordFromXml(xmlRecord);
+    }
+
+
+    /**
+     * Add a new service record.
+     * @param record The byte[] record
+     * @return       A handle to the new record
+     */
+    public synchronized int addServiceRecord(byte[] record) throws IOException {
+        int handle = addServiceRecordNative(record);
+        Log.i(sLogName, "Added SDP record: " + Integer.toHexString(handle));
+        return handle;
+    }
+    private native int addServiceRecordNative(byte[] record)
+            throws IOException;
+
+    /**
+     * Add a new service record, using XML.
+     * @param record The record as an XML string
+     * @return       A handle to the new record
+     */
+    public synchronized int addServiceRecordFromXml(String record) throws IOException {
+        int handle = addServiceRecordFromXmlNative(record);
+        Log.i(sLogName, "Added SDP record: " + Integer.toHexString(handle));
+        return handle;
+    }
+    private native int addServiceRecordFromXmlNative(String record)
+            throws IOException;
+
+    /**
+     * Update an exisiting service record.
+     * @param handle Handle to exisiting record
+     * @param record The updated byte[] record
+     */
+    public synchronized void updateServiceRecord(int handle, byte[] record) {
+        try {
+            updateServiceRecordNative(handle, record);
+        } catch (IOException e) {
+            Log.e(getClass().toString(), e.getMessage());
+        }
+    }
+    private native void updateServiceRecordNative(int handle, byte[] record)
+            throws IOException;
+
+    /**
+     * Update an exisiting record, using XML.
+     * @param handle Handle to exisiting record
+     * @param record The record as an XML string.
+     */
+    public synchronized void updateServiceRecordFromXml(int handle, String record) {
+        try {
+            updateServiceRecordFromXmlNative(handle, record);
+        } catch (IOException e) {
+            Log.e(getClass().toString(), e.getMessage());
+        }
+    }
+    private native void updateServiceRecordFromXmlNative(int handle, String record)
+            throws IOException;
+
+    /**
+     * Remove a service record.
+     * It is only possible to remove service records that were added by the
+     * current connection.
+     * @param handle Handle to exisiting record to be removed
+     */
+    public synchronized void removeServiceRecord(int handle) {
+        try {
+            removeServiceRecordNative(handle);
+        } catch (IOException e) {
+            Log.e(getClass().toString(), e.getMessage());
+        }
+    }
+    private native void removeServiceRecordNative(int handle) throws IOException;
+}
diff --git a/core/java/android/bluetooth/HeadsetBase.java b/core/java/android/bluetooth/HeadsetBase.java
new file mode 100644
index 0000000..fd2d2ab
--- /dev/null
+++ b/core/java/android/bluetooth/HeadsetBase.java
@@ -0,0 +1,285 @@
+/*
+ * Copyright (C) 2007 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 android.bluetooth;
+
+import android.os.Handler;
+import android.os.PowerManager;
+import android.os.PowerManager.WakeLock;
+import android.util.Log;
+
+/**
+ * The Android Bluetooth API is not finalized, and *will* change. Use at your
+ * own risk.
+ *
+ * The base RFCOMM (service) connection for a headset or handsfree device.
+ *
+ * In the future this class will be removed.
+ *
+ * @hide
+ */
+public class HeadsetBase {
+    private static final String TAG = "Bluetooth HeadsetBase";
+    private static final boolean DBG = false;
+
+    public static final int RFCOMM_DISCONNECTED = 1;
+
+    public static final int DIRECTION_INCOMING = 1;
+    public static final int DIRECTION_OUTGOING = 2;
+
+    private final BluetoothDevice mBluetooth;
+    private final String mAddress;
+    private final int mRfcommChannel;
+    private int mNativeData;
+    private Thread mEventThread;
+    private volatile boolean mEventThreadInterrupted;
+    private Handler mEventThreadHandler;
+    private int mTimeoutRemainingMs;
+    private final int mDirection;
+    private final long mConnectTimestamp;
+
+    protected AtParser mAtParser;
+
+    private WakeLock mWakeLock;  // held while processing an AT command
+
+    private native static void classInitNative();
+    static {
+        classInitNative();
+    }
+
+    protected void finalize() throws Throwable {
+        try {
+            cleanupNativeDataNative();
+            releaseWakeLock();
+        } finally {
+            super.finalize();
+        }
+    }
+
+    private native void cleanupNativeDataNative();
+
+    public HeadsetBase(PowerManager pm, BluetoothDevice bluetooth, String address,
+                       int rfcommChannel) {
+        mDirection = DIRECTION_OUTGOING;
+        mConnectTimestamp = System.currentTimeMillis();
+        mBluetooth = bluetooth;
+        mAddress = address;
+        mRfcommChannel = rfcommChannel;
+        mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "HeadsetBase");
+        mWakeLock.setReferenceCounted(false);
+        initializeAtParser();
+        // Must be called after this.mAddress is set.
+        initializeNativeDataNative(-1);
+    }
+
+    /* Create from an already exisiting rfcomm connection */
+    public HeadsetBase(PowerManager pm, BluetoothDevice bluetooth, String address, int socketFd,
+            int rfcommChannel, Handler handler) {
+        mDirection = DIRECTION_INCOMING;
+        mConnectTimestamp = System.currentTimeMillis();
+        mBluetooth = bluetooth;
+        mAddress = address;
+        mRfcommChannel = rfcommChannel;
+        mEventThreadHandler = handler;
+        mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "HeadsetBase");
+        mWakeLock.setReferenceCounted(false);
+        initializeAtParser();
+        // Must be called after this.mAddress is set.
+        initializeNativeDataNative(socketFd);
+    }
+
+    private native void initializeNativeDataNative(int socketFd);
+
+    /* Process an incoming AT command line
+     */
+    protected synchronized void handleInput(String input) {
+        acquireWakeLock();
+        long timestamp;
+
+        if (DBG) timestamp = System.currentTimeMillis();
+        AtCommandResult result = mAtParser.process(input);
+        if (DBG) Log.d(TAG, "Processing " + input + " took " +
+                       (System.currentTimeMillis() - timestamp) + " ms");
+
+        if (result.getResultCode() == AtCommandResult.ERROR) {
+            Log.i(TAG, "Error pocessing <" + input + ">");
+        }
+
+        sendURC(result.toString());
+
+        releaseWakeLock();
+    }
+
+    /**
+     * Register AT commands that are common to all Headset / Handsets. This
+     * function is called by the HeadsetBase constructor.
+     */
+    protected void initializeAtParser() {
+        mAtParser = new AtParser();
+        //TODO(): Get rid of this as there are no parsers registered. But because of dependencies,
+        //it needs to be done as part of refactoring HeadsetBase and BluetoothHandsfree
+    }
+
+    public AtParser getAtParser() {
+        return mAtParser;
+    }
+
+    public void startEventThread() {
+        mEventThread =
+            new Thread("HeadsetBase Event Thread") {
+                public void run() {
+                    int last_read_error;
+                    while (!mEventThreadInterrupted) {
+                        String input = readNative(500);
+                        if (input != null) {
+                            handleInput(input);
+                        }
+                        else {
+                            last_read_error = getLastReadStatusNative();
+                            if (last_read_error != 0) {
+                                Log.i(TAG, "headset read error " + last_read_error);
+                                if (mEventThreadHandler != null) {
+                                    mEventThreadHandler.obtainMessage(RFCOMM_DISCONNECTED)
+                                            .sendToTarget();
+                                }
+                                disconnectNative();
+                                break;
+                            }
+                        }
+                    }
+                }
+            };
+        mEventThreadInterrupted = false;
+        mEventThread.start();
+    }
+
+
+
+    private native String readNative(int timeout_ms);
+    private native int getLastReadStatusNative();
+
+    private void stopEventThread() {
+        mEventThreadInterrupted = true;
+        mEventThread.interrupt();
+        try {
+            mEventThread.join();
+        } catch (java.lang.InterruptedException e) {
+            // FIXME: handle this,
+        }
+        mEventThread = null;
+    }
+
+    public boolean connect(Handler handler) {
+        if (mEventThread == null) {
+            if (!connectNative()) return false;
+            mEventThreadHandler = handler;
+        }
+        return true;
+    }
+    private native boolean connectNative();
+
+    /*
+     * Returns true when either the asynchronous connect is in progress, or
+     * the connect is complete.  Call waitForAsyncConnect() to find out whether
+     * the connect is actually complete, or disconnect() to cancel.
+     */
+
+    public boolean connectAsync() {
+        return connectAsyncNative();
+    }
+    private native boolean connectAsyncNative();
+
+    public int getRemainingAsyncConnectWaitingTimeMs() {
+        return mTimeoutRemainingMs;
+    }
+
+    /*
+     * Returns 1 when an async connect is complete, 0 on timeout, and -1 on
+     * error.  On error, handler will be called, and you need to re-initiate
+     * the async connect.
+     */
+    public int waitForAsyncConnect(int timeout_ms, Handler handler) {
+        int res = waitForAsyncConnectNative(timeout_ms);
+        if (res > 0) {
+            mEventThreadHandler = handler;
+        }
+        return res;
+    }
+    private native int waitForAsyncConnectNative(int timeout_ms);
+
+    public void disconnect() {
+        if (mEventThread != null) {
+            stopEventThread();
+        }
+        disconnectNative();
+    }
+    private native void disconnectNative();
+
+
+    /*
+     * Note that if a remote side disconnects, this method will still return
+     * true until disconnect() is called.  You know when a remote side
+     * disconnects because you will receive the intent
+     * IBluetoothService.REMOTE_DEVICE_DISCONNECTED_ACTION.  If, when you get
+     * this intent, method isConnected() returns true, you know that the
+     * disconnect was initiated by the remote device.
+     */
+
+    public boolean isConnected() {
+        return mEventThread != null;
+    }
+
+    public String getAddress() {
+        return mAddress;
+    }
+
+    public String getName() {
+        return mBluetooth.getRemoteName(mAddress);
+    }
+
+    public int getDirection() {
+        return mDirection;
+    }
+
+    public long getConnectTimestamp() {
+        return mConnectTimestamp;
+    }
+
+    public synchronized boolean sendURC(String urc) {
+        if (urc.length() > 0) {
+            boolean ret = sendURCNative(urc);
+            return ret;
+        }
+        return true;
+    }
+    private native boolean sendURCNative(String urc);
+
+    private void acquireWakeLock() {
+        if (!mWakeLock.isHeld()) {
+            mWakeLock.acquire();
+        }
+    }
+
+    private void releaseWakeLock() {
+        if (mWakeLock.isHeld()) {
+            mWakeLock.release();
+        }
+    }
+
+    private void log(String msg) {
+        Log.d(TAG, msg);
+    }
+}
diff --git a/core/java/android/bluetooth/IBluetoothA2dp.aidl b/core/java/android/bluetooth/IBluetoothA2dp.aidl
new file mode 100644
index 0000000..55ff27f
--- /dev/null
+++ b/core/java/android/bluetooth/IBluetoothA2dp.aidl
@@ -0,0 +1,31 @@
+/*
+ * 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 android.bluetooth;
+
+/**
+ * System private API for Bluetooth A2DP service
+ *
+ * {@hide}
+ */
+interface IBluetoothA2dp {
+    int connectSink(in String address);
+    int disconnectSink(in String address);
+    List<String> listConnectedSinks();
+    int getSinkState(in String address);
+    int setSinkPriority(in String address, int priority);
+    int getSinkPriority(in String address);
+}
diff --git a/core/java/android/bluetooth/IBluetoothDevice.aidl b/core/java/android/bluetooth/IBluetoothDevice.aidl
new file mode 100644
index 0000000..4351d2e
--- /dev/null
+++ b/core/java/android/bluetooth/IBluetoothDevice.aidl
@@ -0,0 +1,77 @@
+/*
+ * 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 android.bluetooth;
+
+import android.bluetooth.IBluetoothDeviceCallback;
+
+/**
+ * System private API for talking with the Bluetooth service.
+ *
+ * {@hide}
+ */
+interface IBluetoothDevice
+{
+    boolean isEnabled();
+    boolean enable(in IBluetoothDeviceCallback callback);  // async
+    boolean disable();
+
+    String getAddress();
+    String getName();
+    boolean setName(in String name);
+    String getVersion();
+    String getRevision();
+    String getManufacturer();
+    String getCompany();
+
+    int getScanMode();
+    boolean setScanMode(int mode);
+
+    int getDiscoverableTimeout();
+    boolean setDiscoverableTimeout(int timeout);
+
+    boolean startDiscovery(boolean resolveNames);
+    boolean cancelDiscovery();
+    boolean isDiscovering();
+    boolean startPeriodicDiscovery();
+    boolean stopPeriodicDiscovery();
+    boolean isPeriodicDiscovery();
+    String[] listRemoteDevices();
+
+    String[] listAclConnections();
+    boolean isAclConnected(in String address);
+    boolean disconnectRemoteDeviceAcl(in String address);
+
+    boolean createBond(in String address);
+    boolean cancelBondProcess(in String address);
+    boolean removeBond(in String address);
+    String[] listBonds();
+    int getBondState(in String address);
+
+    String getRemoteName(in String address);
+    String getRemoteVersion(in String address);
+    String getRemoteRevision(in String address);
+    int getRemoteClass(in String address);
+    String getRemoteManufacturer(in String address);
+    String getRemoteCompany(in String address);
+    boolean getRemoteServiceChannel(in String address, int uuid16, in IBluetoothDeviceCallback callback);
+    byte[] getRemoteFeatures(in String adddress);
+    String lastSeen(in String address);
+    String lastUsed(in String address);
+
+    boolean setPin(in String address, in byte[] pin);
+    boolean cancelPin(in String address);
+}
diff --git a/core/java/android/bluetooth/IBluetoothDeviceCallback.aidl b/core/java/android/bluetooth/IBluetoothDeviceCallback.aidl
new file mode 100644
index 0000000..d25bd56
--- /dev/null
+++ b/core/java/android/bluetooth/IBluetoothDeviceCallback.aidl
@@ -0,0 +1,27 @@
+/*
+ * 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 android.bluetooth;
+
+/**
+ * {@hide}
+ */
+oneway interface IBluetoothDeviceCallback
+{
+    void onGetRemoteServiceChannelResult(in String address, int channel);
+
+    void onEnableResult(int result);
+}
diff --git a/core/java/android/bluetooth/IBluetoothHeadset.aidl b/core/java/android/bluetooth/IBluetoothHeadset.aidl
new file mode 100644
index 0000000..582d4e3
--- /dev/null
+++ b/core/java/android/bluetooth/IBluetoothHeadset.aidl
@@ -0,0 +1,34 @@
+/*
+ * 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 android.bluetooth;
+
+/**
+ * System private API for Bluetooth Headset service
+ *
+ * {@hide}
+ */
+interface IBluetoothHeadset {
+    int getState();
+    String getHeadsetAddress();
+    boolean connectHeadset(in String address);
+    void disconnectHeadset();
+    boolean isConnected(in String address);
+    boolean startVoiceRecognition();
+    boolean stopVoiceRecognition();
+    boolean setPriority(in String address, int priority);
+    int getPriority(in String address);
+}
diff --git a/core/java/android/bluetooth/RfcommSocket.java b/core/java/android/bluetooth/RfcommSocket.java
new file mode 100644
index 0000000..a33263f
--- /dev/null
+++ b/core/java/android/bluetooth/RfcommSocket.java
@@ -0,0 +1,674 @@
+/*
+ * Copyright (C) 2007 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 android.bluetooth;
+
+import java.io.IOException;
+import java.io.FileOutputStream;
+import java.io.FileInputStream;
+import java.io.OutputStream;
+import java.io.InputStream;
+import java.io.FileDescriptor;
+
+/**
+ * The Android Bluetooth API is not finalized, and *will* change. Use at your
+ * own risk.
+ * 
+ * This class implements an API to the Bluetooth RFCOMM layer. An RFCOMM socket
+ * is similar to a normal socket in that it takes an address and a port number.
+ * The difference is of course that the address is a Bluetooth-device address,
+ * and the port number is an RFCOMM channel. The API allows for the
+ * establishment of listening sockets via methods
+ * {@link #bind(String, int) bind}, {@link #listen(int) listen}, and
+ * {@link #accept(RfcommSocket, int) accept}, as well as for the making of
+ * outgoing connections with {@link #connect(String, int) connect},
+ * {@link #connectAsync(String, int) connectAsync}, and
+ * {@link #waitForAsyncConnect(int) waitForAsyncConnect}.
+ * 
+ * After constructing a socket, you need to {@link #create() create} it and then
+ * {@link #destroy() destroy} it when you are done using it. Both
+ * {@link #create() create} and {@link #accept(RfcommSocket, int) accept} return
+ * a {@link java.io.FileDescriptor FileDescriptor} for the actual data.
+ * Alternatively, you may call {@link #getInputStream() getInputStream} and
+ * {@link #getOutputStream() getOutputStream} to retrieve the respective streams
+ * without going through the FileDescriptor.
+ *
+ * @hide
+ */
+public class RfcommSocket {
+
+    /**
+     * Used by the native implementation of the class.
+     */
+    private int mNativeData;
+
+    /**
+     * Used by the native implementation of the class.
+     */
+    private int mPort;
+
+    /**
+     * Used by the native implementation of the class.
+     */
+    private String mAddress;
+
+    /**
+     * We save the return value of {@link #create() create} and
+     * {@link #accept(RfcommSocket,int) accept} in this variable, and use it to
+     * retrieve the I/O streams.
+     */
+    private FileDescriptor mFd;
+
+    /**
+     * After a call to {@link #waitForAsyncConnect(int) waitForAsyncConnect},
+     * if the return value is zero, then, the the remaining time left to wait is
+     * written into this variable (by the native implementation). It is possible
+     * that {@link #waitForAsyncConnect(int) waitForAsyncConnect} returns before
+     * the user-specified timeout expires, which is why we save the remaining
+     * time in this member variable for the user to retrieve by calling method
+     * {@link #getRemainingAsyncConnectWaitingTimeMs() getRemainingAsyncConnectWaitingTimeMs}.
+     */
+    private int mTimeoutRemainingMs;
+
+    /**
+     * Set to true when an asynchronous (nonblocking) connect is in progress.
+     * {@see #connectAsync(String,int)}.
+     */
+    private boolean mIsConnecting;
+
+    /**
+     * Set to true after a successful call to {@link #bind(String,int) bind} and
+     * used for error checking in {@link #listen(int) listen}. Reset to false
+     * on {@link #destroy() destroy}.
+     */
+    private boolean mIsBound = false;
+
+    /**
+     * Set to true after a successful call to {@link #listen(int) listen} and
+     * used for error checking in {@link #accept(RfcommSocket,int) accept}.
+     * Reset to false on {@link #destroy() destroy}.
+     */
+    private boolean mIsListening = false;
+
+    /**
+     * Used to store the remaining time after an accept with a non-negative
+     * timeout returns unsuccessfully. It is possible that a blocking
+     * {@link #accept(int) accept} may wait for less than the time specified by
+     * the user, which is why we store the remainder in this member variable for
+     * it to be retrieved with method
+     * {@link #getRemainingAcceptWaitingTimeMs() getRemainingAcceptWaitingTimeMs}.
+     */
+    private int mAcceptTimeoutRemainingMs;
+
+    /**
+     * Maintained by {@link #getInputStream() getInputStream}.
+     */
+    protected FileInputStream mInputStream;
+
+    /**
+     * Maintained by {@link #getOutputStream() getOutputStream}.
+     */
+    protected FileOutputStream mOutputStream;
+
+    private native void initializeNativeDataNative();
+
+    /**
+     * Constructor.
+     */
+    public RfcommSocket() {
+        initializeNativeDataNative();
+    }
+
+    private native void cleanupNativeDataNative();
+
+    /**
+     * Called by the GC to clean up the native data that we set up when we
+     * construct the object.
+     */
+    protected void finalize() throws Throwable {
+        try {
+            cleanupNativeDataNative();
+        } finally {
+            super.finalize();
+        }
+    }
+
+    private native static void classInitNative();
+
+    static {
+        classInitNative();
+    }
+
+    /**
+     * Creates a socket. You need to call this method before performing any
+     * other operation on a socket.
+     * 
+     * @return FileDescriptor for the data stream.
+     * @throws IOException
+     * @see #destroy()
+     */
+    public FileDescriptor create() throws IOException {
+        if (mFd == null) {
+            mFd = createNative();
+        }
+        if (mFd == null) {
+            throw new IOException("socket not created");
+        }
+        return mFd;
+    }
+
+    private native FileDescriptor createNative();
+
+    /**
+     * Destroys a socket created by {@link #create() create}. Call this
+     * function when you no longer use the socket in order to release the
+     * underlying OS resources.
+     * 
+     * @see #create()
+     */
+    public void destroy() {
+        synchronized (this) {
+            destroyNative();
+            mFd = null;
+            mIsBound = false;
+            mIsListening = false;
+        }
+    }
+
+    private native void destroyNative();
+
+    /**
+     * Returns the {@link java.io.FileDescriptor FileDescriptor} of the socket.
+     * 
+     * @return the FileDescriptor
+     * @throws IOException
+     *             when the socket has not been {@link #create() created}.
+     */
+    public FileDescriptor getFileDescriptor() throws IOException {
+        if (mFd == null) {
+            throw new IOException("socket not created");
+        }
+        return mFd;
+    }
+
+    /**
+     * Retrieves the input stream from the socket. Alternatively, you can do
+     * that from the FileDescriptor returned by {@link #create() create} or
+     * {@link #accept(RfcommSocket, int) accept}.
+     * 
+     * @return InputStream
+     * @throws IOException
+     *             if you have not called {@link #create() create} on the
+     *             socket.
+     */
+    public InputStream getInputStream() throws IOException {
+        if (mFd == null) {
+            throw new IOException("socket not created");
+        }
+
+        synchronized (this) {
+            if (mInputStream == null) {
+                mInputStream = new FileInputStream(mFd);
+            }
+
+            return mInputStream;
+        }
+    }
+
+    /**
+     * Retrieves the output stream from the socket. Alternatively, you can do
+     * that from the FileDescriptor returned by {@link #create() create} or
+     * {@link #accept(RfcommSocket, int) accept}.
+     * 
+     * @return OutputStream
+     * @throws IOException
+     *             if you have not called {@link #create() create} on the
+     *             socket.
+     */
+    public OutputStream getOutputStream() throws IOException {
+        if (mFd == null) {
+            throw new IOException("socket not created");
+        }
+
+        synchronized (this) {
+            if (mOutputStream == null) {
+                mOutputStream = new FileOutputStream(mFd);
+            }
+
+            return mOutputStream;
+        }
+    }
+
+    /**
+     * Starts a blocking connect to a remote RFCOMM socket. It takes the address
+     * of a device and the RFCOMM channel (port) to which to connect.
+     * 
+     * @param address
+     *            is the Bluetooth address of the remote device.
+     * @param port
+     *            is the RFCOMM channel
+     * @return true on success, false on failure
+     * @throws IOException
+     *             if {@link #create() create} has not been called.
+     * @see #connectAsync(String, int)
+     */
+    public boolean connect(String address, int port) throws IOException {
+        synchronized (this) {
+            if (mFd == null) {
+                throw new IOException("socket not created");
+            }
+            return connectNative(address, port);
+        }
+    }
+
+    private native boolean connectNative(String address, int port);
+
+    /**
+     * Starts an asynchronous (nonblocking) connect to a remote RFCOMM socket.
+     * It takes the address of the device to connect to, as well as the RFCOMM
+     * channel (port). On successful return (return value is true), you need to
+     * call method {@link #waitForAsyncConnect(int) waitForAsyncConnect} to
+     * block for up to a specified number of milliseconds while waiting for the
+     * asyncronous connect to complete.
+     * 
+     * @param address
+     *            of remote device
+     * @param port
+     *            the RFCOMM channel
+     * @return true when the asynchronous connect has successfully started,
+     *         false if there was an error.
+     * @throws IOException
+     *             is you have not called {@link #create() create}
+     * @see #waitForAsyncConnect(int)
+     * @see #getRemainingAsyncConnectWaitingTimeMs()
+     * @see #connect(String, int)
+     */
+    public boolean connectAsync(String address, int port) throws IOException {
+        synchronized (this) {
+            if (mFd == null) {
+                throw new IOException("socket not created");
+            }
+            mIsConnecting = connectAsyncNative(address, port);
+            return mIsConnecting;
+        }
+    }
+
+    private native boolean connectAsyncNative(String address, int port);
+
+    /**
+     * Interrupts an asynchronous connect in progress. This method does nothing
+     * when there is no asynchronous connect in progress.
+     * 
+     * @throws IOException
+     *             if you have not called {@link #create() create}.
+     * @see #connectAsync(String, int)
+     */
+    public void interruptAsyncConnect() throws IOException {
+        synchronized (this) {
+            if (mFd == null) {
+                throw new IOException("socket not created");
+            }
+            if (mIsConnecting) {
+                mIsConnecting = !interruptAsyncConnectNative();
+            }
+        }
+    }
+
+    private native boolean interruptAsyncConnectNative();
+
+    /**
+     * Tells you whether there is an asynchronous connect in progress. This
+     * method returns an undefined value when there is a synchronous connect in
+     * progress.
+     * 
+     * @return true if there is an asyc connect in progress, false otherwise
+     * @see #connectAsync(String, int)
+     */
+    public boolean isConnecting() {
+        return mIsConnecting;
+    }
+
+    /**
+     * Blocks for a specified amount of milliseconds while waiting for an
+     * asynchronous connect to complete. Returns an integer value to indicate
+     * one of the following: the connect succeeded, the connect is still in
+     * progress, or the connect failed. It is possible for this method to block
+     * for less than the time specified by the user, and still return zero
+     * (i.e., async connect is still in progress.) For this reason, if the
+     * return value is zero, you need to call method
+     * {@link #getRemainingAsyncConnectWaitingTimeMs() getRemainingAsyncConnectWaitingTimeMs}
+     * to retrieve the remaining time.
+     * 
+     * @param timeoutMs
+     *            the time to block while waiting for the async connect to
+     *            complete.
+     * @return a positive value if the connect succeeds; zero, if the connect is
+     *         still in progress, and a negative value if the connect failed.
+     * 
+     * @throws IOException
+     * @see #getRemainingAsyncConnectWaitingTimeMs()
+     * @see #connectAsync(String, int)
+     */
+    public int waitForAsyncConnect(int timeoutMs) throws IOException {
+        synchronized (this) {
+            if (mFd == null) {
+                throw new IOException("socket not created");
+            }
+            int ret = waitForAsyncConnectNative(timeoutMs);
+            if (ret != 0) {
+                mIsConnecting = false;
+            }
+            return ret;
+        }
+    }
+
+    private native int waitForAsyncConnectNative(int timeoutMs);
+
+    /**
+     * Returns the number of milliseconds left to wait after the last call to
+     * {@link #waitForAsyncConnect(int) waitForAsyncConnect}.
+     * 
+     * It is possible that waitForAsyncConnect() waits for less than the time
+     * specified by the user, and still returns zero (i.e., async connect is
+     * still in progress.) For this reason, if the return value is zero, you
+     * need to call this method to retrieve the remaining time before you call
+     * waitForAsyncConnect again.
+     * 
+     * @return the remaining timeout in milliseconds.
+     * @see #waitForAsyncConnect(int)
+     * @see #connectAsync(String, int)
+     */
+    public int getRemainingAsyncConnectWaitingTimeMs() {
+        return mTimeoutRemainingMs;
+    }
+
+    /**
+     * Shuts down both directions on a socket.
+     * 
+     * @return true on success, false on failure; if the return value is false,
+     *         the socket might be left in a patially shut-down state (i.e. one
+     *         direction is shut down, but the other is still open.) In this
+     *         case, you should {@link #destroy() destroy} and then
+     *         {@link #create() create} the socket again.
+     * @throws IOException
+     *             is you have not caled {@link #create() create}.
+     * @see #shutdownInput()
+     * @see #shutdownOutput()
+     */
+    public boolean shutdown() throws IOException {
+        synchronized (this) {
+            if (mFd == null) {
+                throw new IOException("socket not created");
+            }
+            if (shutdownNative(true)) {
+                return shutdownNative(false);
+            }
+
+            return false;
+        }
+    }
+
+    /**
+     * Shuts down the input stream of the socket, but leaves the output stream
+     * in its current state.
+     * 
+     * @return true on success, false on failure
+     * @throws IOException
+     *             is you have not called {@link #create() create}
+     * @see #shutdown()
+     * @see #shutdownOutput()
+     */
+    public boolean shutdownInput() throws IOException {
+        synchronized (this) {
+            if (mFd == null) {
+                throw new IOException("socket not created");
+            }
+            return shutdownNative(true);
+        }
+    }
+
+    /**
+     * Shut down the output stream of the socket, but leaves the input stream in
+     * its current state.
+     * 
+     * @return true on success, false on failure
+     * @throws IOException
+     *             is you have not called {@link #create() create}
+     * @see #shutdown()
+     * @see #shutdownInput()
+     */
+    public boolean shutdownOutput() throws IOException {
+        synchronized (this) {
+            if (mFd == null) {
+                throw new IOException("socket not created");
+            }
+            return shutdownNative(false);
+        }
+    }
+
+    private native boolean shutdownNative(boolean shutdownInput);
+
+    /**
+     * Tells you whether a socket is connected to another socket. This could be
+     * for input or output or both.
+     * 
+     * @return true if connected, false otherwise.
+     * @see #isInputConnected()
+     * @see #isOutputConnected()
+     */
+    public boolean isConnected() {
+        return isConnectedNative() > 0;
+    }
+
+    /**
+     * Determines whether input is connected (i.e., whether you can receive data
+     * on this socket.)
+     * 
+     * @return true if input is connected, false otherwise.
+     * @see #isConnected()
+     * @see #isOutputConnected()
+     */
+    public boolean isInputConnected() {
+        return (isConnectedNative() & 1) != 0;
+    }
+
+    /**
+     * Determines whether output is connected (i.e., whether you can send data
+     * on this socket.)
+     * 
+     * @return true if output is connected, false otherwise.
+     * @see #isConnected()
+     * @see #isInputConnected()
+     */
+    public boolean isOutputConnected() {
+        return (isConnectedNative() & 2) != 0;
+    }
+
+    private native int isConnectedNative();
+
+    /**
+     * Binds a listening socket to the local device, or a non-listening socket
+     * to a remote device. The port is automatically selected as the first
+     * available port in the range 12 to 30.
+     *
+     * NOTE: Currently we ignore the device parameter and always bind the socket
+     * to the local device, assuming that it is a listening socket.
+     *
+     * TODO: Use bind(0) in native code to have the kernel select an unused
+     * port.
+     *
+     * @param device
+     *            Bluetooth address of device to bind to (currently ignored).
+     * @return true on success, false on failure
+     * @throws IOException
+     *             if you have not called {@link #create() create}
+     * @see #listen(int)
+     * @see #accept(RfcommSocket,int)
+     */
+    public boolean bind(String device) throws IOException {
+        if (mFd == null) {
+            throw new IOException("socket not created");
+        }
+        for (int port = 12; port <= 30; port++) {
+            if (bindNative(device, port)) {
+                mIsBound = true;
+                return true;
+            }
+        }
+        mIsBound = false;
+        return false;
+    }
+
+    /**
+     * Binds a listening socket to the local device, or a non-listening socket
+     * to a remote device.
+     *
+     * NOTE: Currently we ignore the device parameter and always bind the socket
+     * to the local device, assuming that it is a listening socket.
+     *
+     * @param device
+     *            Bluetooth address of device to bind to (currently ignored).
+     * @param port
+     *            RFCOMM channel to bind socket to.
+     * @return true on success, false on failure
+     * @throws IOException
+     *             if you have not called {@link #create() create}
+     * @see #listen(int)
+     * @see #accept(RfcommSocket,int)
+     */
+    public boolean bind(String device, int port) throws IOException {
+        if (mFd == null) {
+            throw new IOException("socket not created");
+        }
+        mIsBound = bindNative(device, port);
+        return mIsBound;
+    }
+
+    private native boolean bindNative(String device, int port);
+
+    /**
+     * Starts listening for incoming connections on this socket, after it has
+     * been bound to an address and RFCOMM channel with
+     * {@link #bind(String,int) bind}.
+     * 
+     * @param backlog
+     *            the number of pending incoming connections to queue for
+     *            {@link #accept(RfcommSocket, int) accept}.
+     * @return true on success, false on failure
+     * @throws IOException
+     *             if you have not called {@link #create() create} or if the
+     *             socket has not been bound to a device and RFCOMM channel.
+     */
+    public boolean listen(int backlog) throws IOException {
+        if (mFd == null) {
+            throw new IOException("socket not created");
+        }
+        if (!mIsBound) {
+            throw new IOException("socket not bound");
+        }
+        mIsListening = listenNative(backlog);
+        return mIsListening;
+    }
+
+    private native boolean listenNative(int backlog);
+
+    /**
+     * Accepts incoming-connection requests for a listening socket bound to an
+     * RFCOMM channel. The user may provide a time to wait for an incoming
+     * connection.
+     * 
+     * Note that this method may return null (i.e., no incoming connection)
+     * before the user-specified timeout expires. For this reason, on a null
+     * return value, you need to call
+     * {@link #getRemainingAcceptWaitingTimeMs() getRemainingAcceptWaitingTimeMs}
+     * in order to see how much time is left to wait, before you call this
+     * method again.
+     * 
+     * @param newSock
+     *            is set to the new socket that is created as a result of a
+     *            successful accept.
+     * @param timeoutMs
+     *            time (in milliseconds) to block while waiting to an
+     *            incoming-connection request. A negative value is an infinite
+     *            wait.
+     * @return FileDescriptor of newSock on success, null on failure. Failure
+     *         occurs if the timeout expires without a successful connect.
+     * @throws IOException
+     *             if the socket has not been {@link #create() create}ed, is
+     *             not bound, or is not a listening socket.
+     * @see #bind(String, int)
+     * @see #listen(int)
+     * @see #getRemainingAcceptWaitingTimeMs()
+     */
+    public FileDescriptor accept(RfcommSocket newSock, int timeoutMs)
+            throws IOException {
+        synchronized (newSock) {
+            if (mFd == null) {
+                throw new IOException("socket not created");
+            }
+            if (mIsListening == false) {
+                throw new IOException("not listening on socket");
+            }
+            newSock.mFd = acceptNative(newSock, timeoutMs);
+            return newSock.mFd;
+        }
+    }
+
+    /**
+     * Returns the number of milliseconds left to wait after the last call to
+     * {@link #accept(RfcommSocket, int) accept}.
+     * 
+     * Since accept() may return null (i.e., no incoming connection) before the
+     * user-specified timeout expires, you need to call this method in order to
+     * see how much time is left to wait, and wait for that amount of time
+     * before you call accept again.
+     * 
+     * @return the remaining time, in milliseconds.
+     */
+    public int getRemainingAcceptWaitingTimeMs() {
+        return mAcceptTimeoutRemainingMs;
+    }
+
+    private native FileDescriptor acceptNative(RfcommSocket newSock,
+            int timeoutMs);
+
+    /**
+     * Get the port (rfcomm channel) associated with this socket.
+     *
+     * This is only valid if the port has been set via a successful call to
+     * {@link #bind(String, int)}, {@link #connect(String, int)}
+     * or {@link #connectAsync(String, int)}. This can be checked
+     * with {@link #isListening()} and {@link #isConnected()}.
+     * @return Port (rfcomm channel)
+     */
+    public int getPort() throws IOException {
+        if (mFd == null) {
+            throw new IOException("socket not created");
+        }
+        if (!mIsListening && !isConnected()) {
+            throw new IOException("not listening or connected on socket");
+        }
+        return mPort;
+    }
+
+    /**
+     * Return true if this socket is listening ({@link #listen(int)}
+     * has been called successfully).
+     */
+    public boolean isListening() {
+        return mIsListening;
+    }
+}
diff --git a/core/java/android/bluetooth/ScoSocket.java b/core/java/android/bluetooth/ScoSocket.java
new file mode 100644
index 0000000..a43a08b
--- /dev/null
+++ b/core/java/android/bluetooth/ScoSocket.java
@@ -0,0 +1,194 @@
+/*
+ * 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 android.bluetooth;
+
+import android.os.Handler;
+import android.os.Message;
+import android.os.PowerManager;
+import android.os.PowerManager.WakeLock;
+import android.util.Log;
+
+/**
+ * The Android Bluetooth API is not finalized, and *will* change. Use at your
+ * own risk.
+ *
+ * Simple SCO Socket.
+ * Currently in Android, there is no support for sending data over a SCO
+ * socket - this is managed by the hardware link to the Bluetooth Chip. This
+ * class is instead intended for management of the SCO socket lifetime, 
+ * and is tailored for use with the headset / handsfree profiles.
+ * @hide
+ */
+public class ScoSocket {
+    private static final String TAG = "ScoSocket";
+    private static final boolean DBG = true;
+    private static final boolean VDBG = false;  // even more logging
+
+    public static final int STATE_READY = 1;    // Ready for use. No threads or sockets
+    public static final int STATE_ACCEPT = 2;   // accept() thread running
+    public static final int STATE_CONNECTING = 3;  // connect() thread running
+    public static final int STATE_CONNECTED = 4;   // connected, waiting for close()
+    public static final int STATE_CLOSED = 5;   // was connected, now closed.
+
+    private int mState;
+    private int mNativeData;
+    private Handler mHandler;
+    private int mAcceptedCode;
+    private int mConnectedCode;
+    private int mClosedCode;
+
+    private WakeLock mWakeLock;  // held while in STATE_CONNECTING
+
+    static {
+        classInitNative();
+    }
+    private native static void classInitNative();
+
+    public ScoSocket(PowerManager pm, Handler handler, int acceptedCode, int connectedCode,
+                     int closedCode) {
+        initNative();
+        mState = STATE_READY;
+        mHandler = handler;
+        mAcceptedCode = acceptedCode;
+        mConnectedCode = connectedCode;
+        mClosedCode = closedCode;
+        mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "ScoSocket");
+        mWakeLock.setReferenceCounted(false);
+        if (VDBG) log(this + " SCO OBJECT CTOR");
+    }
+    private native void initNative();
+
+    protected void finalize() throws Throwable {
+        try {
+            if (VDBG) log(this + " SCO OBJECT DTOR");
+            destroyNative();
+            releaseWakeLock();
+        } finally {
+            super.finalize();
+        }
+    }
+    private native void destroyNative();
+
+    /** Connect this SCO socket to the given BT address.
+     *  Does not block.
+     */
+    public synchronized boolean connect(String address) {
+        if (VDBG) log("connect() " + this);
+        if (mState != STATE_READY) {
+            if (DBG) log("connect(): Bad state");
+            return false;
+        }
+        acquireWakeLock();
+        if (connectNative(address)) {
+            mState = STATE_CONNECTING;
+            return true;
+        } else {
+            mState = STATE_CLOSED;
+            releaseWakeLock();
+            return false;
+        }
+    }
+    private native boolean connectNative(String address);
+
+    /** Accept incoming SCO connections.
+     *  Does not block.
+     */
+    public synchronized boolean accept() {
+        if (VDBG) log("accept() " + this);
+        if (mState != STATE_READY) {
+            if (DBG) log("Bad state");
+            return false;
+        }
+        if (acceptNative()) {
+            mState = STATE_ACCEPT;
+            return true;
+        } else {
+            mState = STATE_CLOSED;
+            return false;
+        }
+    }
+    private native boolean acceptNative();
+
+    public synchronized void close() {
+        if (DBG) log(this + " SCO OBJECT close() mState = " + mState);
+        acquireWakeLock();
+        mState = STATE_CLOSED;
+        closeNative();
+        releaseWakeLock();
+    }
+    private native void closeNative();
+
+    public synchronized int getState() {
+        return mState;
+    }
+
+    private synchronized void onConnected(int result) {
+        if (VDBG) log(this + " onConnected() mState = " + mState + " " + this);
+        if (mState != STATE_CONNECTING) {
+            if (DBG) log("Strange state, closing " + mState + " " + this);
+            return;
+        }
+        if (result >= 0) {
+            mState = STATE_CONNECTED;
+        } else {
+            mState = STATE_CLOSED;
+        }
+        mHandler.obtainMessage(mConnectedCode, mState, -1, this).sendToTarget();
+        releaseWakeLock();
+    }
+
+    private synchronized void onAccepted(int result) {
+        if (VDBG) log("onAccepted() " + this);
+        if (mState != STATE_ACCEPT) {
+            if (DBG) log("Strange state " + this);
+            return;
+        }
+        if (result >= 0) {
+            mState = STATE_CONNECTED;
+        } else {
+            mState = STATE_CLOSED;
+        }
+        mHandler.obtainMessage(mAcceptedCode, mState, -1, this).sendToTarget();
+    }
+
+    private synchronized void onClosed() {
+        if (DBG) log("onClosed() " + this);
+        if (mState != STATE_CLOSED) {
+            mState = STATE_CLOSED;
+            mHandler.obtainMessage(mClosedCode, mState, -1, this).sendToTarget();
+            releaseWakeLock();
+        }
+    }
+
+    private void acquireWakeLock() {
+        if (!mWakeLock.isHeld()) {
+            mWakeLock.acquire();
+            if (VDBG) log("mWakeLock.acquire() " + this);
+        }
+    }
+
+    private void releaseWakeLock() {
+        if (mWakeLock.isHeld()) {
+            if (VDBG) log("mWakeLock.release() " + this);
+            mWakeLock.release();
+        }
+    }
+
+    private void log(String msg) {
+        Log.d(TAG, msg);
+    }
+}
diff --git a/core/java/android/bluetooth/package.html b/core/java/android/bluetooth/package.html
new file mode 100644
index 0000000..79abf0c
--- /dev/null
+++ b/core/java/android/bluetooth/package.html
@@ -0,0 +1,13 @@
+<HTML>
+<BODY>
+Provides classes that manage Bluetooth functionality on the device.
+<p>
+The Bluetooth APIs allow applications can connect and disconnect headsets, or scan 
+for other kinds of Bluetooth devices and pair them. Further control includes the 
+ability to write and modify the local Service Discovery Protocol (SDP) database, 
+query the SDP database of other Bluetooth devices, establish RFCOMM 
+channels/sockets on Android, and connect to specified sockets on other devices.
+</p>
+<p>Remember, not all Android devices are guaranteed to have Bluetooth functionality.</p>
+</BODY>
+</HTML>
diff --git a/core/java/android/content/AbstractSyncableContentProvider.java b/core/java/android/content/AbstractSyncableContentProvider.java
new file mode 100644
index 0000000..ce6501c
--- /dev/null
+++ b/core/java/android/content/AbstractSyncableContentProvider.java
@@ -0,0 +1,601 @@
+package android.content;
+
+import android.database.sqlite.SQLiteOpenHelper;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.Cursor;
+import android.net.Uri;
+import android.accounts.AccountMonitor;
+import android.accounts.AccountMonitorListener;
+import android.provider.SyncConstValue;
+import android.util.Config;
+import android.util.Log;
+import android.os.Bundle;
+import android.text.TextUtils;
+
+import java.util.Collections;
+import java.util.Map;
+import java.util.HashMap;
+import java.util.Vector;
+import java.util.ArrayList;
+
+/**
+ * A specialization of the ContentProvider that centralizes functionality
+ * used by ContentProviders that are syncable. It also wraps calls to the ContentProvider
+ * inside of database transactions.
+ *
+ * @hide
+ */
+public abstract class AbstractSyncableContentProvider extends SyncableContentProvider {
+    private static final String TAG = "SyncableContentProvider";
+    protected SQLiteOpenHelper mOpenHelper;
+    protected SQLiteDatabase mDb;
+    private final String mDatabaseName;
+    private final int mDatabaseVersion;
+    private final Uri mContentUri;
+    private AccountMonitor mAccountMonitor;
+
+    /** the account set in the last call to onSyncStart() */
+    private String mSyncingAccount;
+
+    private SyncStateContentProviderHelper mSyncState = null;
+
+    private static final String[] sAccountProjection = new String[] {SyncConstValue._SYNC_ACCOUNT};
+
+    private boolean mIsTemporary;
+
+    private AbstractTableMerger mCurrentMerger = null;
+    private boolean mIsMergeCancelled = false;
+
+    private static final String SYNC_ACCOUNT_WHERE_CLAUSE = SyncConstValue._SYNC_ACCOUNT + "=?";
+
+    protected boolean isTemporary() {
+        return mIsTemporary;
+    }
+
+    /**
+     * Indicates whether or not this ContentProvider contains a full
+     * set of data or just diffs. This knowledge comes in handy when
+     * determining how to incorporate the contents of a temporary
+     * provider into a real provider.
+     */
+    private boolean mContainsDiffs;
+
+    /**
+     * Initializes the AbstractSyncableContentProvider
+     * @param dbName the filename of the database
+     * @param dbVersion the current version of the database schema
+     * @param contentUri The base Uri of the syncable content in this provider
+     */
+    public AbstractSyncableContentProvider(String dbName, int dbVersion, Uri contentUri) {
+        super();
+
+        mDatabaseName = dbName;
+        mDatabaseVersion = dbVersion;
+        mContentUri = contentUri;
+        mIsTemporary = false;
+        setContainsDiffs(false);
+        if (Config.LOGV) {
+            Log.v(TAG, "created SyncableContentProvider " + this);
+        }
+    }
+
+    /**
+     * Close resources that must be closed. You must call this to properly release
+     * the resources used by the AbstractSyncableContentProvider.
+     */
+    public void close() {
+        if (mOpenHelper != null) {
+            mOpenHelper.close();  // OK to call .close() repeatedly.
+        }
+    }
+
+    /**
+     * Override to create your schema and do anything else you need to do with a new database.
+     * This is run inside a transaction (so you don't need to use one).
+     * This method may not use getDatabase(), or call content provider methods, it must only
+     * use the database handle passed to it.
+     */
+    protected void bootstrapDatabase(SQLiteDatabase db) {}
+
+    /**
+     * Override to upgrade your database from an old version to the version you specified.
+     * Don't set the DB version; this will automatically be done after the method returns.
+     * This method may not use getDatabase(), or call content provider methods, it must only
+     * use the database handle passed to it.
+     *
+     * @param oldVersion version of the existing database
+     * @param newVersion current version to upgrade to
+     * @return true if the upgrade was lossless, false if it was lossy
+     */
+    protected abstract boolean upgradeDatabase(SQLiteDatabase db, int oldVersion, int newVersion);
+
+    /**
+     * Override to do anything (like cleanups or checks) you need to do after opening a database.
+     * Does nothing by default.  This is run inside a transaction (so you don't need to use one).
+     * This method may not use getDatabase(), or call content provider methods, it must only
+     * use the database handle passed to it.
+     */
+    protected void onDatabaseOpened(SQLiteDatabase db) {}
+
+    private class DatabaseHelper extends SQLiteOpenHelper {
+        DatabaseHelper(Context context, String name) {
+            // Note: context and name may be null for temp providers
+            super(context, name, null, mDatabaseVersion);
+        }
+
+        @Override
+        public void onCreate(SQLiteDatabase db) {
+            bootstrapDatabase(db);
+            mSyncState.createDatabase(db);
+        }
+
+        @Override
+        public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
+            if (!upgradeDatabase(db, oldVersion, newVersion)) {
+                mSyncState.discardSyncData(db, null /* all accounts */);
+                getContext().getContentResolver().startSync(mContentUri, new Bundle());
+            }
+        }
+
+        @Override
+        public void onOpen(SQLiteDatabase db) {
+            onDatabaseOpened(db);
+            mSyncState.onDatabaseOpened(db);
+        }
+    }
+
+    @Override
+    public boolean onCreate() {
+        if (isTemporary()) throw new IllegalStateException("onCreate() called for temp provider");
+        mOpenHelper = new AbstractSyncableContentProvider.DatabaseHelper(getContext(), mDatabaseName);
+        mSyncState = new SyncStateContentProviderHelper(mOpenHelper);
+
+        AccountMonitorListener listener = new AccountMonitorListener() {
+            public void onAccountsUpdated(String[] accounts) {
+                // Some providers override onAccountsChanged(); give them a database to work with.
+                mDb = mOpenHelper.getWritableDatabase();
+                onAccountsChanged(accounts);
+                TempProviderSyncAdapter syncAdapter = (TempProviderSyncAdapter)getSyncAdapter();
+                if (syncAdapter != null) {
+                    syncAdapter.onAccountsChanged(accounts);
+                }
+            }
+        };
+        mAccountMonitor = new AccountMonitor(getContext(), listener);
+
+        return true;
+    }
+
+    /**
+     * Get a non-persistent instance of this content provider.
+     * You must call {@link #close} on the returned
+     * SyncableContentProvider when you are done with it.
+     *
+     * @return a non-persistent content provider with the same layout as this
+     * provider.
+     */
+    public AbstractSyncableContentProvider getTemporaryInstance() {
+        AbstractSyncableContentProvider temp;
+        try {
+            temp = getClass().newInstance();
+        } catch (InstantiationException e) {
+            throw new RuntimeException("unable to instantiate class, "
+                    + "this should never happen", e);
+        } catch (IllegalAccessException e) {
+            throw new RuntimeException(
+                    "IllegalAccess while instantiating class, "
+                            + "this should never happen", e);
+        }
+
+        // Note: onCreate() isn't run for the temp provider, and it has no Context.
+        temp.mIsTemporary = true;
+        temp.setContainsDiffs(true);
+        temp.mOpenHelper = temp.new DatabaseHelper(null, null);
+        temp.mSyncState = new SyncStateContentProviderHelper(temp.mOpenHelper);
+        if (!isTemporary()) {
+            mSyncState.copySyncState(
+                    mOpenHelper.getReadableDatabase(),
+                    temp.mOpenHelper.getWritableDatabase(),
+                    getSyncingAccount());
+        }
+        return temp;
+    }
+
+    public SQLiteDatabase getDatabase() {
+       if (mDb == null) mDb = mOpenHelper.getWritableDatabase();
+       return mDb;
+    }
+
+    public boolean getContainsDiffs() {
+        return mContainsDiffs;
+    }
+
+    public void setContainsDiffs(boolean containsDiffs) {
+        if (containsDiffs && !isTemporary()) {
+            throw new IllegalStateException(
+                    "only a temporary provider can contain diffs");
+        }
+        mContainsDiffs = containsDiffs;
+    }
+
+    /**
+     * Each subclass of this class should define a subclass of {@link
+     * android.content.AbstractTableMerger} for each table they wish to merge.  It
+     * should then override this method and return one instance of
+     * each merger, in sequence.  Their {@link
+     * android.content.AbstractTableMerger#merge merge} methods will be called, one at a
+     * time, in the order supplied.
+     *
+     * <p>The default implementation returns an empty list, so that no
+     * merging will occur.
+     * @return A sequence of subclasses of {@link
+     * android.content.AbstractTableMerger}, one for each table that should be merged.
+     */
+    protected Iterable<? extends AbstractTableMerger> getMergers() {
+        return Collections.emptyList();
+    }
+
+    @Override
+    public final int update(final Uri url, final ContentValues values,
+            final String selection, final String[] selectionArgs) {
+        mDb = mOpenHelper.getWritableDatabase();
+        mDb.beginTransaction();
+        try {
+            if (isTemporary() && mSyncState.matches(url)) {
+                int numRows = mSyncState.asContentProvider().update(
+                        url, values, selection, selectionArgs);
+                mDb.setTransactionSuccessful();
+                return numRows;
+            }
+
+            int result = updateInternal(url, values, selection, selectionArgs);
+            mDb.setTransactionSuccessful();
+
+            if (!isTemporary() && result > 0) {
+                getContext().getContentResolver().notifyChange(url, null /* observer */,
+                        changeRequiresLocalSync(url));
+            }
+
+            return result;
+        } finally {
+            mDb.endTransaction();
+        }
+    }
+
+    @Override
+    public final int delete(final Uri url, final String selection,
+            final String[] selectionArgs) {
+        mDb = mOpenHelper.getWritableDatabase();
+        mDb.beginTransaction();
+        try {
+            if (isTemporary() && mSyncState.matches(url)) {
+                int numRows = mSyncState.asContentProvider().delete(url, selection, selectionArgs);
+                mDb.setTransactionSuccessful();
+                return numRows;
+            }
+            int result = deleteInternal(url, selection, selectionArgs);
+            mDb.setTransactionSuccessful();
+            if (!isTemporary() && result > 0) {
+                getContext().getContentResolver().notifyChange(url, null /* observer */,
+                        changeRequiresLocalSync(url));
+            }
+            return result;
+        } finally {
+            mDb.endTransaction();
+        }
+    }
+
+    @Override
+    public final Uri insert(final Uri url, final ContentValues values) {
+        mDb = mOpenHelper.getWritableDatabase();
+        mDb.beginTransaction();
+        try {
+            if (isTemporary() && mSyncState.matches(url)) {
+                Uri result = mSyncState.asContentProvider().insert(url, values);
+                mDb.setTransactionSuccessful();
+                return result;
+            }
+            Uri result = insertInternal(url, values);
+            mDb.setTransactionSuccessful();
+            if (!isTemporary() && result != null) {
+                getContext().getContentResolver().notifyChange(url, null /* observer */,
+                        changeRequiresLocalSync(url));
+            }
+            return result;
+        } finally {
+            mDb.endTransaction();
+        }
+    }
+
+    @Override
+    public final int bulkInsert(final Uri uri, final ContentValues[] values) {
+        int size = values.length;
+        int completed = 0;
+        final boolean isSyncStateUri = mSyncState.matches(uri);
+        mDb = mOpenHelper.getWritableDatabase();
+        mDb.beginTransaction();
+        try {
+            for (int i = 0; i < size; i++) {
+                Uri result;
+                if (isTemporary() && isSyncStateUri) {
+                    result = mSyncState.asContentProvider().insert(uri, values[i]);
+                } else {
+                    result = insertInternal(uri, values[i]);
+                    mDb.yieldIfContended();
+                }
+                if (result != null) {
+                    completed++;
+                }
+            }
+            mDb.setTransactionSuccessful();
+        } finally {
+            mDb.endTransaction();
+        }
+        if (!isTemporary() && completed == size) {
+            getContext().getContentResolver().notifyChange(uri, null /* observer */,
+                    changeRequiresLocalSync(uri));
+        }
+        return completed;
+    }
+
+    /**
+     * Check if changes to this URI can be syncable changes.
+     * @param uri the URI of the resource that was changed
+     * @return true if changes to this URI can be syncable changes, false otherwise
+     */
+    public boolean changeRequiresLocalSync(Uri uri) {
+        return true;
+    }
+
+    @Override
+    public final Cursor query(final Uri url, final String[] projection,
+            final String selection, final String[] selectionArgs,
+            final String sortOrder) {
+        mDb = mOpenHelper.getReadableDatabase();
+        if (isTemporary() && mSyncState.matches(url)) {
+            return mSyncState.asContentProvider().query(
+                    url, projection, selection,  selectionArgs, sortOrder);
+        }
+        return queryInternal(url, projection, selection, selectionArgs, sortOrder);
+    }
+
+    /**
+     * Called right before a sync is started.
+     *
+     * @param context the sync context for the operation
+     * @param account
+     */
+    public void onSyncStart(SyncContext context, String account) {
+        if (TextUtils.isEmpty(account)) {
+            throw new IllegalArgumentException("you passed in an empty account");
+        }
+        mSyncingAccount = account;
+    }
+
+    /**
+     * Called right after a sync is completed
+     *
+     * @param context the sync context for the operation
+     * @param success true if the sync succeeded, false if an error occurred
+     */
+    public void onSyncStop(SyncContext context, boolean success) {
+    }
+
+    /**
+     * The account of the most recent call to onSyncStart()
+     * @return the account
+     */
+    public String getSyncingAccount() {
+        return mSyncingAccount;
+    }
+
+    /**
+     * Merge diffs from a sync source with this content provider.
+     *
+     * @param context the SyncContext within which this merge is taking place
+     * @param diffs A temporary content provider containing diffs from a sync
+     *   source.
+     * @param result a MergeResult that contains information about the merge, including
+     *   a temporary content provider with the same layout as this provider containing
+     * @param syncResult
+     */
+    public void merge(SyncContext context, SyncableContentProvider diffs,
+            TempProviderSyncResult result, SyncResult syncResult) {
+        SQLiteDatabase db = mOpenHelper.getWritableDatabase();
+        db.beginTransaction();
+        try {
+            synchronized(this) {
+                mIsMergeCancelled = false;
+            }
+            Iterable<? extends AbstractTableMerger> mergers = getMergers();
+            try {
+                for (AbstractTableMerger merger : mergers) {
+                    synchronized(this) {
+                        if (mIsMergeCancelled) break;
+                        mCurrentMerger = merger;
+                    }
+                    merger.merge(context, getSyncingAccount(), diffs, result, syncResult, this);
+                }
+                if (mIsMergeCancelled) return;
+                if (diffs != null) {
+                    mSyncState.copySyncState(
+                        ((AbstractSyncableContentProvider)diffs).mOpenHelper.getReadableDatabase(),
+                        mOpenHelper.getWritableDatabase(),
+                        getSyncingAccount());
+                }
+            } finally {
+                synchronized (this) {
+                    mCurrentMerger = null;
+                }
+            }
+            db.setTransactionSuccessful();
+        } finally {
+            db.endTransaction();
+        }
+    }
+
+
+    /**
+     * Invoked when the active sync has been canceled. Sets the sync state of this provider and
+     * its merger to canceled.
+     */
+    public void onSyncCanceled() {
+        synchronized (this) {
+            mIsMergeCancelled = true;
+            if (mCurrentMerger != null) {
+                mCurrentMerger.onMergeCancelled();
+            }
+        }
+    }
+
+
+    public boolean isMergeCancelled() {
+        return mIsMergeCancelled;
+    }
+
+    /**
+     * Subclasses should override this instead of update(). See update()
+     * for details.
+     *
+     * <p> This method is called within a acquireDbLock()/releaseDbLock() block,
+     * which means a database transaction will be active during the call;
+     */
+    protected abstract int updateInternal(Uri url, ContentValues values,
+            String selection, String[] selectionArgs);
+
+    /**
+     * Subclasses should override this instead of delete(). See delete()
+     * for details.
+     *
+     * <p> This method is called within a acquireDbLock()/releaseDbLock() block,
+     * which means a database transaction will be active during the call;
+     */
+    protected abstract int deleteInternal(Uri url, String selection, String[] selectionArgs);
+
+    /**
+     * Subclasses should override this instead of insert(). See insert()
+     * for details.
+     *
+     * <p> This method is called within a acquireDbLock()/releaseDbLock() block,
+     * which means a database transaction will be active during the call;
+     */
+    protected abstract Uri insertInternal(Uri url, ContentValues values);
+
+    /**
+     * Subclasses should override this instead of query(). See query()
+     * for details.
+     *
+     * <p> This method is *not* called within a acquireDbLock()/releaseDbLock()
+     * block for performance reasons. If an implementation needs atomic access
+     * to the database the lock can be acquired then.
+     */
+    protected abstract Cursor queryInternal(Uri url, String[] projection,
+            String selection, String[] selectionArgs, String sortOrder);
+
+    /**
+     * Make sure that there are no entries for accounts that no longer exist
+     * @param accountsArray the array of currently-existing accounts
+     */
+    protected void onAccountsChanged(String[] accountsArray) {
+        Map<String, Boolean> accounts = new HashMap<String, Boolean>();
+        for (String account : accountsArray) {
+            accounts.put(account, false);
+        }
+        accounts.put(SyncConstValue.NON_SYNCABLE_ACCOUNT, false);
+
+        SQLiteDatabase db = mOpenHelper.getWritableDatabase();
+        Map<String, String> tableMap = db.getSyncedTables();
+        Vector<String> tables = new Vector<String>();
+        tables.addAll(tableMap.keySet());
+        tables.addAll(tableMap.values());
+
+        db.beginTransaction();
+        try {
+            mSyncState.onAccountsChanged(accountsArray);
+            for (String table : tables) {
+                deleteRowsForRemovedAccounts(accounts, table,
+                        SyncConstValue._SYNC_ACCOUNT);
+            }
+            db.setTransactionSuccessful();
+        } finally {
+            db.endTransaction();
+        }
+    }
+
+    /**
+     * A helper method to delete all rows whose account is not in the accounts
+     * map. The accountColumnName is the name of the column that is expected
+     * to hold the account. If a row has an empty account it is never deleted.
+     *
+     * @param accounts a map of existing accounts
+     * @param table the table to delete from
+     * @param accountColumnName the name of the column that is expected
+     * to hold the account.
+     */
+    protected void deleteRowsForRemovedAccounts(Map<String, Boolean> accounts,
+            String table, String accountColumnName) {
+        SQLiteDatabase db = mOpenHelper.getWritableDatabase();
+        Cursor c = db.query(table, sAccountProjection, null, null,
+                accountColumnName, null, null);
+        try {
+            while (c.moveToNext()) {
+                String account = c.getString(0);
+                if (TextUtils.isEmpty(account)) {
+                    continue;
+                }
+                if (!accounts.containsKey(account)) {
+                    int numDeleted;
+                    numDeleted = db.delete(table, accountColumnName + "=?", new String[]{account});
+                    if (Config.LOGV) {
+                        Log.v(TAG, "deleted " + numDeleted
+                                + " records from table " + table
+                                + " for account " + account);
+                    }
+                }
+            }
+        } finally {
+            c.close();
+        }
+    }
+
+    /**
+     * Called when the sync system determines that this provider should no longer
+     * contain records for the specified account.
+     */
+    public void wipeAccount(String account) {
+        SQLiteDatabase db = mOpenHelper.getWritableDatabase();
+        Map<String, String> tableMap = db.getSyncedTables();
+        ArrayList<String> tables = new ArrayList<String>();
+        tables.addAll(tableMap.keySet());
+        tables.addAll(tableMap.values());
+
+        db.beginTransaction();
+
+        try {
+            // remove the SyncState data
+            mSyncState.discardSyncData(db, account);
+
+            // remove the data in the synced tables
+            for (String table : tables) {
+                db.delete(table, SYNC_ACCOUNT_WHERE_CLAUSE, new String[]{account});
+            }
+            db.setTransactionSuccessful();
+        } finally {
+            db.endTransaction();
+        }
+    }
+
+    /**
+     * Retrieves the SyncData bytes for the given account. The byte array returned may be null.
+     */
+    public byte[] readSyncDataBytes(String account) {
+        return mSyncState.readSyncDataBytes(mOpenHelper.getReadableDatabase(), account);
+    }
+
+    /**
+     * Sets the SyncData bytes for the given account. The byte array may be null.
+     */
+    public void writeSyncDataBytes(String account, byte[] data) {
+        mSyncState.writeSyncDataBytes(mOpenHelper.getWritableDatabase(), account, data);
+    }
+}
diff --git a/core/java/android/content/AbstractTableMerger.java b/core/java/android/content/AbstractTableMerger.java
new file mode 100644
index 0000000..700f1d8
--- /dev/null
+++ b/core/java/android/content/AbstractTableMerger.java
@@ -0,0 +1,582 @@
+/*
+ * 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 android.content;
+
+import android.database.Cursor;
+import android.database.DatabaseUtils;
+import android.database.sqlite.SQLiteDatabase;
+import android.net.Uri;
+import android.os.Debug;
+import android.provider.BaseColumns;
+import static android.provider.SyncConstValue.*;
+import android.text.TextUtils;
+import android.util.Log;
+
+/**
+ * @hide
+ */
+public abstract class AbstractTableMerger
+{
+    private ContentValues mValues;
+
+    protected SQLiteDatabase mDb;
+    protected String mTable;
+    protected Uri mTableURL;
+    protected String mDeletedTable;
+    protected Uri mDeletedTableURL;
+    static protected ContentValues mSyncMarkValues;
+    static private boolean TRACE;
+
+    static {
+        mSyncMarkValues = new ContentValues();
+        mSyncMarkValues.put(_SYNC_MARK, 1);
+        TRACE = false;
+    }
+
+    private static final String TAG = "AbstractTableMerger";
+    private static final String[] syncDirtyProjection =
+            new String[] {_SYNC_DIRTY, BaseColumns._ID, _SYNC_ID, _SYNC_VERSION};
+    private static final String[] syncIdAndVersionProjection =
+            new String[] {_SYNC_ID, _SYNC_VERSION};
+
+    private volatile boolean mIsMergeCancelled;
+
+    private static final String SELECT_MARKED = _SYNC_MARK + "> 0 and " + _SYNC_ACCOUNT + "=?";
+
+    private static final String SELECT_BY_SYNC_ID_AND_ACCOUNT =
+            _SYNC_ID +"=? and " + _SYNC_ACCOUNT + "=?";
+    private static final String SELECT_BY_ID = BaseColumns._ID +"=?";
+
+    private static final String SELECT_UNSYNCED = ""
+            + _SYNC_DIRTY + " > 0 and (" + _SYNC_ACCOUNT + "=? or " + _SYNC_ACCOUNT + " is null)";
+
+    public AbstractTableMerger(SQLiteDatabase database,
+            String table, Uri tableURL, String deletedTable,
+            Uri deletedTableURL)
+    {
+        mDb = database;
+        mTable = table;
+        mTableURL = tableURL;
+        mDeletedTable = deletedTable;
+        mDeletedTableURL = deletedTableURL;
+        mValues = new ContentValues();
+    }
+
+    public abstract void insertRow(ContentProvider diffs,
+            Cursor diffsCursor);
+    public abstract void updateRow(long localPersonID,
+            ContentProvider diffs, Cursor diffsCursor);
+    public abstract void resolveRow(long localPersonID,
+            String syncID, ContentProvider diffs, Cursor diffsCursor);
+
+    /**
+     * This is called when it is determined that a row should be deleted from the
+     * ContentProvider. The localCursor is on a table from the local ContentProvider
+     * and its current position is of the row that should be deleted. The localCursor
+     * is only guaranteed to contain the BaseColumns.ID column so the implementation
+     * of deleteRow() must query the database directly if other columns are needed.
+     * <p>
+     * It is the responsibility of the implementation of this method to ensure that the cursor
+     * points to the next row when this method returns, either by calling Cursor.deleteRow() or
+     * Cursor.next().
+     *
+     * @param localCursor The Cursor into the local table, which points to the row that
+     *   is to be deleted.
+     */
+    public void deleteRow(Cursor localCursor) {
+        localCursor.deleteRow();
+    }
+
+    /**
+     * After {@link #merge} has completed, this method is called to send
+     * notifications to {@link android.database.ContentObserver}s of changes
+     * to the containing {@link ContentProvider}.  These notifications likely
+     * do not want to request a sync back to the network.
+     */
+    protected abstract void notifyChanges();
+
+    private static boolean findInCursor(Cursor cursor, int column, String id) {
+        while (!cursor.isAfterLast() && !cursor.isNull(column)) {
+            int comp = id.compareTo(cursor.getString(column));
+            if (comp > 0) {
+                cursor.moveToNext();
+                continue;
+            }
+            return comp == 0;
+        }
+        return false;
+    }
+
+    public void onMergeCancelled() {
+        mIsMergeCancelled = true;
+    }
+
+    /**
+     * Carry out a merge of the given diffs, and add the results to
+     * the given MergeResult.  If we are the first merge to find
+     * client-side diffs, we'll use the given ContentProvider to
+     * construct a temporary instance to hold them.
+     */
+    public void merge(final SyncContext context,
+            final String account,
+            final SyncableContentProvider serverDiffs,
+            TempProviderSyncResult result,
+            SyncResult syncResult, SyncableContentProvider temporaryInstanceFactory) {
+        mIsMergeCancelled = false;
+        if (serverDiffs != null) {
+            if (!mDb.isDbLockedByCurrentThread()) {
+                throw new IllegalStateException("this must be called from within a DB transaction");
+            }
+            mergeServerDiffs(context, account, serverDiffs, syncResult);
+            notifyChanges();
+        }
+
+        if (result != null) {
+            findLocalChanges(result, temporaryInstanceFactory, account, syncResult);
+        }
+        if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "merge complete");
+    }
+
+    /**
+     * @hide this is public for testing purposes only
+     */
+    public void mergeServerDiffs(SyncContext context,
+            String account, SyncableContentProvider serverDiffs, SyncResult syncResult) {
+        boolean diffsArePartial = serverDiffs.getContainsDiffs();
+        // mark the current rows so that we can distinguish these from new
+        // inserts that occur during the merge
+        mDb.update(mTable, mSyncMarkValues, null, null);
+        if (mDeletedTable != null) {
+            mDb.update(mDeletedTable, mSyncMarkValues, null, null);
+        }
+
+        // load the local database entries, so we can merge them with the server
+        final String[] accountSelectionArgs = new String[]{account};
+        Cursor localCursor = mDb.query(mTable, syncDirtyProjection,
+                SELECT_MARKED, accountSelectionArgs, null, null,
+                mTable + "." + _SYNC_ID);
+        Cursor deletedCursor;
+        if (mDeletedTable != null) {
+            deletedCursor = mDb.query(mDeletedTable, syncIdAndVersionProjection,
+                    SELECT_MARKED, accountSelectionArgs, null, null,
+                    mDeletedTable + "." + _SYNC_ID);
+        } else {
+            deletedCursor =
+                    mDb.rawQuery("select 'a' as _sync_id, 'b' as _sync_version limit 0", null);
+        }
+
+        // Apply updates and insertions from the server
+        Cursor diffsCursor = serverDiffs.query(mTableURL,
+                null, null, null, mTable + "." + _SYNC_ID);
+        int deletedSyncIDColumn = deletedCursor.getColumnIndexOrThrow(_SYNC_ID);
+        int deletedSyncVersionColumn = deletedCursor.getColumnIndexOrThrow(_SYNC_VERSION);
+        int serverSyncIDColumn = diffsCursor.getColumnIndexOrThrow(_SYNC_ID);
+        int serverSyncVersionColumn = diffsCursor.getColumnIndexOrThrow(_SYNC_VERSION);
+        int serverSyncLocalIdColumn = diffsCursor.getColumnIndexOrThrow(_SYNC_LOCAL_ID);
+
+        String lastSyncId = null;
+        int diffsCount = 0;
+        int localCount = 0;
+        localCursor.moveToFirst();
+        deletedCursor.moveToFirst();
+        while (diffsCursor.moveToNext()) {
+            if (mIsMergeCancelled) {
+                localCursor.close();
+                deletedCursor.close();
+                diffsCursor.close();
+                return;
+            }
+            mDb.yieldIfContended();
+            String serverSyncId = diffsCursor.getString(serverSyncIDColumn);
+            String serverSyncVersion = diffsCursor.getString(serverSyncVersionColumn);
+            long localRowId = 0;
+            String localSyncVersion = null;
+
+            diffsCount++;
+            context.setStatusText("Processing " + diffsCount + "/"
+                    + diffsCursor.getCount());
+            if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "processing server entry " +
+                    diffsCount + ", " + serverSyncId);
+
+            if (TRACE) {
+                if (diffsCount == 10) {
+                    Debug.startMethodTracing("atmtrace");
+                }
+                if (diffsCount == 20) {
+                    Debug.stopMethodTracing();
+                }
+            }
+
+            boolean conflict = false;
+            boolean update = false;
+            boolean insert = false;
+
+            if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                Log.v(TAG, "found event with serverSyncID " + serverSyncId);
+            }
+            if (TextUtils.isEmpty(serverSyncId)) {
+                if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                    Log.e(TAG, "server entry doesn't have a serverSyncID");
+                }
+                continue;
+            }
+
+            // It is possible that the sync adapter wrote the same record multiple times,
+            // e.g. if the same record came via multiple feeds. If this happens just ignore
+            // the duplicate records.
+            if (serverSyncId.equals(lastSyncId)) {
+                if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                    Log.v(TAG, "skipping record with duplicate remote server id " + lastSyncId);
+                }
+                continue;
+            }
+            lastSyncId = serverSyncId;
+
+            String localSyncID = null;
+            boolean localSyncDirty = false;
+
+            while (!localCursor.isAfterLast()) {
+                if (mIsMergeCancelled) {
+                    localCursor.deactivate();
+                    deletedCursor.deactivate();
+                    diffsCursor.deactivate();
+                    return;
+                }
+                localCount++;
+                localSyncID = localCursor.getString(2);
+
+                // If the local record doesn't have a _sync_id then
+                // it is new. Ignore it for now, we will send an insert
+                // the the server later.
+                if (TextUtils.isEmpty(localSyncID)) {
+                    if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                        Log.v(TAG, "local record " +
+                                localCursor.getLong(1) +
+                                " has no _sync_id, ignoring");
+                    }
+                    localCursor.moveToNext();
+                    localSyncID = null;
+                    continue;
+                }
+
+                int comp = serverSyncId.compareTo(localSyncID);
+
+                // the local DB has a record that the server doesn't have
+                if (comp > 0) {
+                    if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                        Log.v(TAG, "local record " +
+                                localCursor.getLong(1) +
+                                " has _sync_id " + localSyncID +
+                                " that is < server _sync_id " + serverSyncId);
+                    }
+                    if (diffsArePartial) {
+                        localCursor.moveToNext();
+                    } else {
+                        deleteRow(localCursor);
+                        if (mDeletedTable != null) {
+                            mDb.delete(mDeletedTable, _SYNC_ID +"=?", new String[] {localSyncID});
+                        }
+                        syncResult.stats.numDeletes++;
+                        mDb.yieldIfContended();
+                    }
+                    localSyncID = null;
+                    continue;
+                }
+
+                // the server has a record that the local DB doesn't have
+                if (comp < 0) {
+                    if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                        Log.v(TAG, "local record " +
+                                localCursor.getLong(1) +
+                                " has _sync_id " + localSyncID +
+                                " that is > server _sync_id " + serverSyncId);
+                    }
+                    localSyncID = null;
+                }
+
+                // the server and the local DB both have this record
+                if (comp == 0) {
+                    if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                        Log.v(TAG, "local record " +
+                                localCursor.getLong(1) +
+                                " has _sync_id " + localSyncID +
+                                " that matches the server _sync_id");
+                    }
+                    localSyncDirty = localCursor.getInt(0) != 0;
+                    localRowId = localCursor.getLong(1);
+                    localSyncVersion = localCursor.getString(3);
+                    localCursor.moveToNext();
+                }
+
+                break;
+            }
+
+            // If this record is in the deleted table then update the server version
+            // in the deleted table, if necessary, and then ignore it here.
+            // We will send a deletion indication to the server down a
+            // little further.
+            if (findInCursor(deletedCursor, deletedSyncIDColumn, serverSyncId)) {
+                if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                    Log.v(TAG, "remote record " + serverSyncId + " is in the deleted table");
+                }
+                final String deletedSyncVersion = deletedCursor.getString(deletedSyncVersionColumn);
+                if (!TextUtils.equals(deletedSyncVersion, serverSyncVersion)) {
+                    if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                        Log.v(TAG, "setting version of deleted record " + serverSyncId + " to "
+                                + serverSyncVersion);
+                    }
+                    ContentValues values = new ContentValues();
+                    values.put(_SYNC_VERSION, serverSyncVersion);
+                    mDb.update(mDeletedTable, values, "_sync_id=?", new String[]{serverSyncId});
+                }
+                continue;
+            }
+
+            // If the _sync_local_id is present in the diffsCursor
+            // then this record corresponds to a local record that was just
+            // inserted into the server and the _sync_local_id is the row id
+            // of the local record. Set these fields so that the next check
+            // treats this record as an update, which will allow the
+            // merger to update the record with the server's sync id
+            if (!diffsCursor.isNull(serverSyncLocalIdColumn)) {
+                localRowId = diffsCursor.getLong(serverSyncLocalIdColumn);
+                if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                    Log.v(TAG, "the remote record with sync id " + serverSyncId
+                            + " has a local sync id, " + localRowId);
+                }
+                localSyncID = serverSyncId;
+                localSyncDirty = false;
+                localSyncVersion = null;
+            }
+
+            if (!TextUtils.isEmpty(localSyncID)) {
+                // An existing server item has changed
+                boolean recordChanged = (localSyncVersion == null) ||
+                        !serverSyncVersion.equals(localSyncVersion);
+                if (recordChanged) {
+                    if (localSyncDirty) {
+                        if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                            Log.v(TAG, "remote record " + serverSyncId
+                                    + " conflicts with local _sync_id " + localSyncID
+                                    + ", local _id " + localRowId);
+                        }
+                        conflict = true;
+                    } else {
+                        if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                             Log.v(TAG,
+                                     "remote record " +
+                                             serverSyncId +
+                                     " updates local _sync_id " +
+                                     localSyncID + ", local _id " +
+                                     localRowId);
+                         }
+                         update = true;
+                    }
+                }
+            } else {
+                // the local db doesn't know about this record so add it
+                if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                    Log.v(TAG, "remote record " + serverSyncId + " is new, inserting");
+                }
+                insert = true;
+            }
+
+            if (update) {
+                updateRow(localRowId, serverDiffs, diffsCursor);
+                syncResult.stats.numUpdates++;
+            } else if (conflict) {
+                resolveRow(localRowId, serverSyncId, serverDiffs, diffsCursor);
+                syncResult.stats.numUpdates++;
+            } else if (insert) {
+                insertRow(serverDiffs, diffsCursor);
+                syncResult.stats.numInserts++;
+            }
+        }
+
+        if (Log.isLoggable(TAG, Log.VERBOSE)) {
+            Log.v(TAG, "processed " + diffsCount + " server entries");
+        }
+
+        // If tombstones aren't in use delete any remaining local rows that
+        // don't have corresponding server rows. Keep the rows that don't
+        // have a sync id since those were created locally and haven't been
+        // synced to the server yet.
+        if (!diffsArePartial) {
+            while (!localCursor.isAfterLast() && !TextUtils.isEmpty(localCursor.getString(2))) {
+                if (mIsMergeCancelled) {
+                    localCursor.deactivate();
+                    deletedCursor.deactivate();
+                    diffsCursor.deactivate();
+                    return;
+                }
+                localCount++;
+                final String localSyncId = localCursor.getString(2);
+                if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                    Log.v(TAG,
+                            "deleting local record " +
+                                    localCursor.getLong(1) +
+                                    " _sync_id " + localSyncId);
+                }
+                deleteRow(localCursor);
+                if (mDeletedTable != null) {
+                    mDb.delete(mDeletedTable, _SYNC_ID + "=?", new String[] {localSyncId});
+                }
+                syncResult.stats.numDeletes++;
+                mDb.yieldIfContended();
+            }
+        }
+
+        if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "checked " + localCount +
+                " local entries");
+        diffsCursor.deactivate();
+        localCursor.deactivate();
+        deletedCursor.deactivate();
+
+        if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "applying deletions from the server");
+
+        // Apply deletions from the server
+        if (mDeletedTableURL != null) {
+            diffsCursor = serverDiffs.query(mDeletedTableURL, null, null, null, null);
+
+            while (diffsCursor.moveToNext()) {
+                if (mIsMergeCancelled) {
+                    diffsCursor.deactivate();
+                    return;
+                }
+                // delete all rows that match each element in the diffsCursor
+                fullyDeleteMatchingRows(diffsCursor, account, syncResult);
+                mDb.yieldIfContended();
+            }
+            diffsCursor.deactivate();
+        }
+    }
+
+    private void fullyDeleteMatchingRows(Cursor diffsCursor, String account,
+            SyncResult syncResult) {
+        int serverSyncIdColumn = diffsCursor.getColumnIndexOrThrow(_SYNC_ID);
+        final boolean deleteBySyncId = !diffsCursor.isNull(serverSyncIdColumn);
+
+        // delete the rows explicitly so that the delete operation can be overridden
+        final Cursor c;
+        final String[] selectionArgs;
+        if (deleteBySyncId) {
+            selectionArgs = new String[]{diffsCursor.getString(serverSyncIdColumn), account};
+            c = mDb.query(mTable, new String[]{BaseColumns._ID}, SELECT_BY_SYNC_ID_AND_ACCOUNT,
+                    selectionArgs, null, null, null);
+        } else {
+            int serverSyncLocalIdColumn = diffsCursor.getColumnIndexOrThrow(_SYNC_LOCAL_ID);
+            selectionArgs = new String[]{diffsCursor.getString(serverSyncLocalIdColumn)};
+            c = mDb.query(mTable, new String[]{BaseColumns._ID}, SELECT_BY_ID, selectionArgs,
+                    null, null, null);
+        }
+        try {
+            c.moveToFirst();
+            while (!c.isAfterLast()) {
+                deleteRow(c); // advances the cursor
+                syncResult.stats.numDeletes++;
+            }
+        } finally {
+            c.deactivate();
+        }
+        if (deleteBySyncId && mDeletedTable != null) {
+            mDb.delete(mDeletedTable, SELECT_BY_SYNC_ID_AND_ACCOUNT, selectionArgs);
+        }
+    }
+
+    /**
+     * Converts cursor into a Map, using the correct types for the values.
+     */
+    protected void cursorRowToContentValues(Cursor cursor, ContentValues map) {
+        DatabaseUtils.cursorRowToContentValues(cursor, map);
+    }
+
+    /**
+     * Finds local changes, placing the results in the given result object.
+     * @param temporaryInstanceFactory As an optimization for the case
+     * where there are no client-side diffs, mergeResult may initially
+     * have no {@link android.content.TempProviderSyncResult#tempContentProvider}.  If this is
+     * the first in the sequence of AbstractTableMergers to find
+     * client-side diffs, it will use the given ContentProvider to
+     * create a temporary instance and store its {@link
+     * ContentProvider} in the mergeResult.
+     * @param account
+     * @param syncResult
+     */
+    private void findLocalChanges(TempProviderSyncResult mergeResult,
+            SyncableContentProvider temporaryInstanceFactory, String account,
+            SyncResult syncResult) {
+        SyncableContentProvider clientDiffs = mergeResult.tempContentProvider;
+        if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "generating client updates");
+
+        final String[] accountSelectionArgs = new String[]{account};
+
+        // Generate the client updates and insertions
+        // Create a cursor for dirty records
+        Cursor localChangesCursor = mDb.query(mTable, null, SELECT_UNSYNCED, accountSelectionArgs,
+                null, null, null);
+        long numInsertsOrUpdates = localChangesCursor.getCount();
+        while (localChangesCursor.moveToNext()) {
+            if (mIsMergeCancelled) {
+                localChangesCursor.close();
+                return;
+            }
+            if (clientDiffs == null) {
+                clientDiffs = temporaryInstanceFactory.getTemporaryInstance();
+            }
+            mValues.clear();
+            cursorRowToContentValues(localChangesCursor, mValues);
+            mValues.remove("_id");
+            DatabaseUtils.cursorLongToContentValues(localChangesCursor, "_id", mValues,
+                    _SYNC_LOCAL_ID);
+            clientDiffs.insert(mTableURL, mValues);
+        }
+        localChangesCursor.close();
+
+        // Generate the client deletions
+        if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "generating client deletions");
+        long numEntries = DatabaseUtils.queryNumEntries(mDb, mTable);
+        long numDeletedEntries = 0;
+        if (mDeletedTable != null) {
+            Cursor deletedCursor = mDb.query(mDeletedTable,
+                    syncIdAndVersionProjection,
+                    _SYNC_ACCOUNT + "=? AND " + _SYNC_ID + " IS NOT NULL", accountSelectionArgs,
+                    null, null, mDeletedTable + "." + _SYNC_ID);
+
+            numDeletedEntries = deletedCursor.getCount();
+            while (deletedCursor.moveToNext()) {
+                if (mIsMergeCancelled) {
+                    deletedCursor.close();
+                    return;
+                }
+                if (clientDiffs == null) {
+                    clientDiffs = temporaryInstanceFactory.getTemporaryInstance();
+                }
+                mValues.clear();
+                DatabaseUtils.cursorRowToContentValues(deletedCursor, mValues);
+                clientDiffs.insert(mDeletedTableURL, mValues);
+            }
+            deletedCursor.close();
+        }
+
+        if (clientDiffs != null) {
+            mergeResult.tempContentProvider = clientDiffs;
+        }
+        syncResult.stats.numDeletes += numDeletedEntries;
+        syncResult.stats.numUpdates += numInsertsOrUpdates;
+        syncResult.stats.numEntries += numEntries;
+    }
+}
diff --git a/core/java/android/content/ActivityNotFoundException.java b/core/java/android/content/ActivityNotFoundException.java
new file mode 100644
index 0000000..16149bb
--- /dev/null
+++ b/core/java/android/content/ActivityNotFoundException.java
@@ -0,0 +1,35 @@
+/*
+ * 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 android.content;
+
+/**
+ * This exception is thrown when a call to {@link Context#startActivity} or
+ * one of its variants fails because an Activity can not be found to execute
+ * the given Intent.
+ */
+public class ActivityNotFoundException extends RuntimeException
+{
+    public ActivityNotFoundException()
+    {
+    }
+
+    public ActivityNotFoundException(String name)
+    {
+        super(name);
+    }
+};
+
diff --git a/core/java/android/content/AsyncQueryHandler.java b/core/java/android/content/AsyncQueryHandler.java
new file mode 100644
index 0000000..ac851cc
--- /dev/null
+++ b/core/java/android/content/AsyncQueryHandler.java
@@ -0,0 +1,360 @@
+/*
+ * Copyright (C) 2007 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 android.content;
+
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.os.Message;
+import android.util.Log;
+
+import java.lang.ref.WeakReference;
+
+/**
+ * A helper class to help make handling asynchronous {@link ContentResolver}
+ * queries easier.
+ */
+public abstract class AsyncQueryHandler extends Handler {
+    private static final String TAG = "AsyncQuery";
+    private static final boolean localLOGV = false;
+
+    private static final int EVENT_ARG_QUERY = 1;
+    private static final int EVENT_ARG_INSERT = 2;
+    private static final int EVENT_ARG_UPDATE = 3;
+    private static final int EVENT_ARG_DELETE = 4;
+    
+    /* package */ final WeakReference<ContentResolver> mResolver;
+
+    private static Looper sLooper = null;
+
+    private Handler mWorkerThreadHandler;
+
+    protected static final class WorkerArgs {
+        public Uri uri;
+        public Handler handler;
+        public String[] projection;
+        public String selection;
+        public String[] selectionArgs;
+        public String orderBy;
+        public Object result;
+        public Object cookie;
+        public ContentValues values;
+    }
+
+    protected class WorkerHandler extends Handler {
+        public WorkerHandler(Looper looper) {
+            super(looper);
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            final ContentResolver resolver = mResolver.get();
+            if (resolver == null) return;
+
+            WorkerArgs args = (WorkerArgs) msg.obj;
+
+            int token = msg.what;
+            int event = msg.arg1;
+
+            switch (event) {
+                case EVENT_ARG_QUERY:
+                    Cursor cursor;
+                    try {
+                        cursor = resolver.query(args.uri, args.projection,
+                                args.selection, args.selectionArgs,
+                                args.orderBy);
+                        // Calling getCount() causes the cursor window to be filled,
+                        // which will make the first access on the main thread a lot faster.
+                        if (cursor != null) {
+                            cursor.getCount();
+                        }
+                    } catch (Exception e) {
+                        cursor = null;
+                    }
+
+                    args.result = cursor;
+                    break;
+
+                case EVENT_ARG_INSERT:
+                    args.result = resolver.insert(args.uri, args.values);
+                    break;
+
+                case EVENT_ARG_UPDATE:
+                    args.result = resolver.update(args.uri, args.values, args.selection,
+                            args.selectionArgs);
+                    break;
+
+                case EVENT_ARG_DELETE:
+                    args.result = resolver.delete(args.uri, args.selection, args.selectionArgs);
+                    break;
+
+            }
+
+            // passing the original token value back to the caller
+            // on top of the event values in arg1.
+            Message reply = args.handler.obtainMessage(token);
+            reply.obj = args;
+            reply.arg1 = msg.arg1;
+
+            if (localLOGV) {
+                Log.d(TAG, "WorkerHandler.handleMsg: msg.arg1=" + msg.arg1
+                        + ", reply.what=" + reply.what);
+            }
+
+            reply.sendToTarget();
+        }
+    }
+
+    public AsyncQueryHandler(ContentResolver cr) {
+        super();
+        mResolver = new WeakReference<ContentResolver>(cr);
+        synchronized (AsyncQueryHandler.class) {
+            if (sLooper == null) {
+                HandlerThread thread = new HandlerThread("AsyncQueryWorker");
+                thread.start();
+                
+                sLooper = thread.getLooper();
+            }
+        }
+        mWorkerThreadHandler = createHandler(sLooper);
+    }
+
+    protected Handler createHandler(Looper looper) {
+        return new WorkerHandler(looper);
+    }
+
+    /**
+     * This method begins an asynchronous query. When the query is done
+     * {@link #onQueryComplete} is called.
+     *
+     * @param token A token passed into {@link #onQueryComplete} to identify
+     *  the query.
+     * @param cookie An object that gets passed into {@link #onQueryComplete}
+     * @param uri The URI, using the content:// scheme, for the content to
+     *         retrieve.
+     * @param projection A list of which columns to return. Passing null will
+     *         return all columns, which is discouraged to prevent reading data
+     *         from storage that isn't going to be used.
+     * @param selection A filter declaring which rows to return, formatted as an
+     *         SQL WHERE clause (excluding the WHERE itself). Passing null will
+     *         return all rows for the given URI.
+     * @param selectionArgs You may include ?s in selection, which will be
+     *         replaced by the values from selectionArgs, in the order that they
+     *         appear in the selection. The values will be bound as Strings.
+     * @param orderBy How to order the rows, formatted as an SQL ORDER BY
+     *         clause (excluding the ORDER BY itself). Passing null will use the
+     *         default sort order, which may be unordered.
+     */
+    public void startQuery(int token, Object cookie, Uri uri,
+            String[] projection, String selection, String[] selectionArgs,
+            String orderBy) {
+        // Use the token as what so cancelOperations works properly
+        Message msg = mWorkerThreadHandler.obtainMessage(token);
+        msg.arg1 = EVENT_ARG_QUERY;
+
+        WorkerArgs args = new WorkerArgs();
+        args.handler = this;
+        args.uri = uri;
+        args.projection = projection;
+        args.selection = selection;
+        args.selectionArgs = selectionArgs;
+        args.orderBy = orderBy;
+        args.cookie = cookie;
+        msg.obj = args;
+
+        mWorkerThreadHandler.sendMessage(msg);
+    }
+
+    /**
+     * Attempts to cancel operation that has not already started. Note that
+     * there is no guarantee that the operation will be canceled. They still may
+     * result in a call to on[Query/Insert/Update/Delete]Complete after this
+     * call has completed.
+     *
+     * @param token The token representing the operation to be canceled.
+     *  If multiple operations have the same token they will all be canceled.
+     */
+    public final void cancelOperation(int token) {
+        mWorkerThreadHandler.removeMessages(token);
+    }
+
+    /**
+     * This method begins an asynchronous insert. When the insert operation is
+     * done {@link #onInsertComplete} is called.
+     *
+     * @param token A token passed into {@link #onInsertComplete} to identify
+     *  the insert operation.
+     * @param cookie An object that gets passed into {@link #onInsertComplete}
+     * @param uri the Uri passed to the insert operation.
+     * @param initialValues the ContentValues parameter passed to the insert operation.
+     */
+    public final void startInsert(int token, Object cookie, Uri uri,
+            ContentValues initialValues) {
+        // Use the token as what so cancelOperations works properly
+        Message msg = mWorkerThreadHandler.obtainMessage(token);
+        msg.arg1 = EVENT_ARG_INSERT;
+
+        WorkerArgs args = new WorkerArgs();
+        args.handler = this;
+        args.uri = uri;
+        args.cookie = cookie;
+        args.values = initialValues;
+        msg.obj = args;
+
+        mWorkerThreadHandler.sendMessage(msg);
+    }
+
+    /**
+     * This method begins an asynchronous update. When the update operation is
+     * done {@link #onUpdateComplete} is called.
+     *
+     * @param token A token passed into {@link #onUpdateComplete} to identify
+     *  the update operation.
+     * @param cookie An object that gets passed into {@link #onUpdateComplete}
+     * @param uri the Uri passed to the update operation.
+     * @param values the ContentValues parameter passed to the update operation.
+     */
+    public final void startUpdate(int token, Object cookie, Uri uri,
+            ContentValues values, String selection, String[] selectionArgs) {
+        // Use the token as what so cancelOperations works properly
+        Message msg = mWorkerThreadHandler.obtainMessage(token);
+        msg.arg1 = EVENT_ARG_UPDATE;
+
+        WorkerArgs args = new WorkerArgs();
+        args.handler = this;
+        args.uri = uri;
+        args.cookie = cookie;
+        args.values = values;
+        args.selection = selection;
+        args.selectionArgs = selectionArgs;
+        msg.obj = args;
+
+        mWorkerThreadHandler.sendMessage(msg);
+    }
+
+    /**
+     * This method begins an asynchronous delete. When the delete operation is
+     * done {@link #onDeleteComplete} is called.
+     *
+     * @param token A token passed into {@link #onDeleteComplete} to identify
+     *  the delete operation.
+     * @param cookie An object that gets passed into {@link #onDeleteComplete}
+     * @param uri the Uri passed to the delete operation.
+     * @param selection the where clause.
+     */
+    public final void startDelete(int token, Object cookie, Uri uri,
+            String selection, String[] selectionArgs) {
+        // Use the token as what so cancelOperations works properly
+        Message msg = mWorkerThreadHandler.obtainMessage(token);
+        msg.arg1 = EVENT_ARG_DELETE;
+
+        WorkerArgs args = new WorkerArgs();
+        args.handler = this;
+        args.uri = uri;
+        args.cookie = cookie;
+        args.selection = selection;
+        args.selectionArgs = selectionArgs;
+        msg.obj = args;
+
+        mWorkerThreadHandler.sendMessage(msg);
+    }
+
+    /**
+     * Called when an asynchronous query is completed.
+     *
+     * @param token the token to identify the query, passed in from
+     *        {@link #startQuery}.
+     * @param cookie the cookie object that's passed in from {@link #startQuery}.
+     * @param cursor The cursor holding the results from the query.
+     */
+    protected void onQueryComplete(int token, Object cookie, Cursor cursor) {
+        // Empty
+    }
+
+    /**
+     * Called when an asynchronous insert is completed.
+     *
+     * @param token the token to identify the query, passed in from
+     *        {@link #startInsert}.
+     * @param cookie the cookie object that's passed in from
+     *        {@link #startInsert}.
+     * @param uri the uri returned from the insert operation.
+     */
+    protected void onInsertComplete(int token, Object cookie, Uri uri) {
+        // Empty
+    }
+
+    /**
+     * Called when an asynchronous update is completed.
+     *
+     * @param token the token to identify the query, passed in from
+     *        {@link #startUpdate}.
+     * @param cookie the cookie object that's passed in from
+     *        {@link #startUpdate}.
+     * @param result the result returned from the update operation
+     */
+    protected void onUpdateComplete(int token, Object cookie, int result) {
+        // Empty
+    }
+
+    /**
+     * Called when an asynchronous delete is completed.
+     *
+     * @param token the token to identify the query, passed in from
+     *        {@link #startDelete}.
+     * @param cookie the cookie object that's passed in from
+     *        {@link #startDelete}.
+     * @param result the result returned from the delete operation
+     */
+    protected void onDeleteComplete(int token, Object cookie, int result) {
+        // Empty
+    }
+
+    @Override
+    public void handleMessage(Message msg) {
+        WorkerArgs args = (WorkerArgs) msg.obj;
+
+        if (localLOGV) {
+            Log.d(TAG, "AsyncQueryHandler.handleMessage: msg.what=" + msg.what
+                    + ", msg.arg1=" + msg.arg1);
+        }
+
+        int token = msg.what;
+        int event = msg.arg1;
+        
+        // pass token back to caller on each callback.
+        switch (event) {
+            case EVENT_ARG_QUERY:
+                onQueryComplete(token, args.cookie, (Cursor) args.result);
+                break;
+
+            case EVENT_ARG_INSERT:
+                onInsertComplete(token, args.cookie, (Uri) args.result);
+                break;
+
+            case EVENT_ARG_UPDATE:
+                onUpdateComplete(token, args.cookie, (Integer) args.result);
+                break;
+
+            case EVENT_ARG_DELETE:
+                onDeleteComplete(token, args.cookie, (Integer) args.result);
+                break;
+        }
+    }
+}
diff --git a/core/java/android/content/BroadcastReceiver.java b/core/java/android/content/BroadcastReceiver.java
new file mode 100644
index 0000000..08f6191
--- /dev/null
+++ b/core/java/android/content/BroadcastReceiver.java
@@ -0,0 +1,425 @@
+/*
+ * 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 android.content;
+
+import android.app.ActivityManagerNative;
+import android.app.IActivityManager;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Log;
+
+/**
+ * Base class for code that will receive intents sent by sendBroadcast().
+ * You can either dynamically register an instance of this class with
+ * {@link Context#registerReceiver Context.registerReceiver()}
+ * or statically publish an implementation through the
+ * {@link android.R.styleable#AndroidManifestReceiver &lt;receiver&gt;}
+ * tag in your <code>AndroidManifest.xml</code>. <em><strong>Note:</strong></em>
+ * &nbsp;&nbsp;&nbsp;If registering a receiver in your
+ * {@link android.app.Activity#onResume() Activity.onResume()}
+ * implementation, you should unregister it in 
+ * {@link android.app.Activity#onPause() Activity.onPause()}.
+ * (You won't receive intents when paused, 
+ * and this will cut down on unnecessary system overhead). Do not unregister in 
+ * {@link android.app.Activity#onSaveInstanceState(android.os.Bundle) Activity.onSaveInstanceState()},
+ * because this won't be called if the user moves back in the history
+ * stack.
+ * 
+ * <p>There are two major classes of broadcasts that can be received:</p>
+ * <ul>
+ * <li> <b>Normal broadcasts</b> (sent with {@link Context#sendBroadcast(Intent)
+ * Context.sendBroadcast}) are completely asynchronous.  All receivers of the
+ * broadcast are run, in an undefined order, often at the same time.  This is
+ * more efficient, but means that receivers can not use the result or abort
+ * APIs included here.
+ * <li> <b>Ordered broadcasts</b> (sent with {@link Context#sendOrderedBroadcast(Intent, String)
+ * Context.sendOrderedBroadcast}) are delivered to one receiver at a time.
+ * As each receiver executes in turn, it can propagate a result to the next
+ * receiver, or it can completely abort the broadcast so that it won't be passed
+ * to other receivers.  The order receivers runs in can be controlled with the
+ * {@link android.R.styleable#AndroidManifestIntentFilter_priority
+ * android:priority} attribute of the matching intent-filter; receivers with
+ * the same priority will be run in an arbitrary order.
+ * </ul>
+ * 
+ * <p>Even in the case of normal broadcasts, the system may in some
+ * situations revert to delivering the broadcast one receiver at a time.  In
+ * particular, for receivers that may require the creation of a process, only
+ * one will be run at a time to avoid overloading the system with new processes.
+ * In this situation, however, the non-ordered semantics hold: these receivers
+ * can not return results or abort their broadcast.</p>
+ * 
+ * <p>Note that, although the Intent class is used for sending and receiving
+ * these broadcasts, the Intent broadcast mechanism here is completely separate
+ * from Intents that are used to start Activities with
+ * {@link Context#startActivity Context.startActivity()}.
+ * There is no way for an BroadcastReceiver
+ * to see or capture Intents used with startActivity(); likewise, when
+ * you broadcast an Intent, you will never find or start an Activity.
+ * These two operations are semantically very different: starting an
+ * Activity with an Intent is a foreground operation that modifies what the
+ * user is currently interacting with; broadcasting an Intent is a background
+ * operation that the user is not normally aware of.
+ * 
+ * <p>The BroadcastReceiver class (when launched as a component through
+ * a manifest's {@link android.R.styleable#AndroidManifestReceiver &lt;receiver&gt;}
+ * tag) is an important part of an
+ * <a href="{@docRoot}guide/topics/fundamentals.html#lcycles">application's overall lifecycle</a>.</p>
+ * 
+ * <p>Topics covered here:
+ * <ol>
+ * <li><a href="#ReceiverLifecycle">Receiver Lifecycle</a>
+ * <li><a href="#Permissions">Permissions</a>
+ * <li><a href="#ProcessLifecycle">Process Lifecycle</a>
+ * </ol>
+ * 
+ * <a name="ReceiverLifecycle"></a>
+ * <h3>Receiver Lifecycle</h3>
+ * 
+ * <p>A BroadcastReceiver object is only valid for the duration of the call
+ * to {@link #onReceive}.  Once your code returns from this function,
+ * the system considers the object to be finished and no longer active.
+ * 
+ * <p>This has important repercussions to what you can do in an
+ * {@link #onReceive} implementation: anything that requires asynchronous
+ * operation is not available, because you will need to return from the
+ * function to handle the asynchronous operation, but at that point the
+ * BroadcastReceiver is no longer active and thus the system is free to kill
+ * its process before the asynchronous operation completes.
+ * 
+ * <p>In particular, you may <i>not</i> show a dialog or bind to a service from
+ * within an BroadcastReceiver.  For the former, you should instead use the
+ * {@link android.app.NotificationManager} API.  For the latter, you can
+ * use {@link android.content.Context#startService Context.startService()} to
+ * send a command to the service.
+ * 
+ * <a name="Permissions"></a>
+ * <h3>Permissions</h3>
+ * 
+ * <p>Access permissions can be enforced by either the sender or receiver
+ * of an Intent.
+ * 
+ * <p>To enforce a permission when sending, you supply a non-null
+ * <var>permission</var> argument to
+ * {@link Context#sendBroadcast(Intent, String)} or
+ * {@link Context#sendOrderedBroadcast(Intent, String, BroadcastReceiver, android.os.Handler, int, String, Bundle)}.
+ * Only receivers who have been granted this permission
+ * (by requesting it with the
+ * {@link android.R.styleable#AndroidManifestUsesPermission &lt;uses-permission&gt;}
+ * tag in their <code>AndroidManifest.xml</code>) will be able to receive
+ * the broadcast.
+ * 
+ * <p>To enforce a permission when receiving, you supply a non-null
+ * <var>permission</var> when registering your receiver -- either when calling
+ * {@link Context#registerReceiver(BroadcastReceiver, IntentFilter, String, android.os.Handler)}
+ * or in the static
+ * {@link android.R.styleable#AndroidManifestReceiver &lt;receiver&gt;}
+ * tag in your <code>AndroidManifest.xml</code>.  Only broadcasters who have
+ * been granted this permission (by requesting it with the
+ * {@link android.R.styleable#AndroidManifestUsesPermission &lt;uses-permission&gt;}
+ * tag in their <code>AndroidManifest.xml</code>) will be able to send an
+ * Intent to the receiver.
+ * 
+ * <p>See the <a href="{@docRoot}guide/topics/security/security.html">Security and Permissions</a>
+ * document for more information on permissions and security in general.
+ * 
+ * <a name="ProcessLifecycle"></a>
+ * <h3>Process Lifecycle</h3>
+ * 
+ * <p>A process that is currently executing an BroadcastReceiver (that is,
+ * currently running the code in its {@link #onReceive} method) is
+ * considered to be a foreground process and will be kept running by the
+ * system except under cases of extreme memory pressure.
+ * 
+ * <p>Once you return from onReceive(), the BroadcastReceiver is no longer
+ * active, and its hosting process is only as important as any other application
+ * components that are running in it.  This is especially important because if
+ * that process was only hosting the BroadcastReceiver (a common case for
+ * applications that the user has never or not recently interacted with), then
+ * upon returning from onReceive() the system will consider its process
+ * to be empty and aggressively kill it so that resources are available for other
+ * more important processes.
+ * 
+ * <p>This means that for longer-running operations you will often use
+ * a {@link android.app.Service} in conjunction with an BroadcastReceiver to keep
+ * the containing process active for the entire time of your operation.
+ */
+public abstract class BroadcastReceiver {
+    public BroadcastReceiver() {
+    }
+
+    /**
+     * This method is called when the BroadcastReceiver is receiving an Intent
+     * broadcast.  During this time you can use the other methods on
+     * BroadcastReceiver to view/modify the current result values.  The function
+     * is normally called from the main thread of its process, so you should
+     * never perform long-running operations in it (there is a timeout of
+     * 10 seconds that the system allows before considering the receiver to
+     * be blocked and a candidate to be killed). You cannot launch a popup dialog
+     * in your implementation of onReceive().
+     * 
+     * <p><b>If this BroadcastReceiver was launched through a &lt;receiver&gt; tag,
+     * then the object is no longer alive after returning from this
+     * function.</b>  This means you should not perform any operations that
+     * return a result to you asynchronously -- in particular, for interacting
+     * with services, you should use
+     * {@link Context#startService(Intent)} instead of
+     * {@link Context#bindService(Intent, ServiceConnection, int)}.  If you wish
+     * to interact with a service that is already running, you can use
+     * {@link #peekService}.
+     * 
+     * @param context The Context in which the receiver is running.
+     * @param intent The Intent being received.
+     */
+    public abstract void onReceive(Context context, Intent intent);
+
+    /**
+     * Provide a binder to an already-running service.  This method is synchronous
+     * and will not start the target service if it is not present, so it is safe
+     * to call from {@link #onReceive}.
+     * 
+     * @param myContext The Context that had been passed to {@link #onReceive(Context, Intent)}
+     * @param service The Intent indicating the service you wish to use.  See {@link
+     * Context#startService(Intent)} for more information.
+     */
+    public IBinder peekService(Context myContext, Intent service) {
+        IActivityManager am = ActivityManagerNative.getDefault();
+        IBinder binder = null;
+        try {
+            binder = am.peekService(service, service.resolveTypeIfNeeded(
+                    myContext.getContentResolver()));
+        } catch (RemoteException e) {
+        }
+        return binder;
+    }
+
+    /**
+     * Change the current result code of this broadcast; only works with
+     * broadcasts sent through
+     * {@link Context#sendOrderedBroadcast(Intent, String)
+     * Context.sendOrderedBroadcast}.  Often uses the
+     * Activity {@link android.app.Activity#RESULT_CANCELED} and
+     * {@link android.app.Activity#RESULT_OK} constants, though the
+     * actual meaning of this value is ultimately up to the broadcaster.
+     * 
+     * <p><strong>This method does not work with non-ordered broadcasts such
+     * as those sent with {@link Context#sendBroadcast(Intent)
+     * Context.sendBroadcast}</strong></p>
+     * 
+     * @param code The new result code.
+     * 
+     * @see #setResult(int, String, Bundle)
+     */
+    public final void setResultCode(int code) {
+        checkSynchronousHint();
+        mResultCode = code;
+    }
+
+    /**
+     * Retrieve the current result code, as set by the previous receiver.
+     * 
+     * @return int The current result code.
+     */
+    public final int getResultCode() {
+        return mResultCode;
+    }
+
+    /**
+     * Change the current result data of this broadcast; only works with
+     * broadcasts sent through
+     * {@link Context#sendOrderedBroadcast(Intent, String)
+     * Context.sendOrderedBroadcast}.  This is an arbitrary
+     * string whose interpretation is up to the broadcaster.
+     * 
+     * <p><strong>This method does not work with non-ordered broadcasts such
+     * as those sent with {@link Context#sendBroadcast(Intent)
+     * Context.sendBroadcast}</strong></p>
+     * 
+     * @param data The new result data; may be null.
+     * 
+     * @see #setResult(int, String, Bundle)
+     */
+    public final void setResultData(String data) {
+        checkSynchronousHint();
+        mResultData = data;
+    }
+
+    /**
+     * Retrieve the current result data, as set by the previous receiver.
+     * Often this is null.
+     * 
+     * @return String The current result data; may be null.
+     */
+    public final String getResultData() {
+        return mResultData;
+    }
+
+    /**
+     * Change the current result extras of this broadcast; only works with
+     * broadcasts sent through
+     * {@link Context#sendOrderedBroadcast(Intent, String)
+     * Context.sendOrderedBroadcast}.  This is a Bundle
+     * holding arbitrary data, whose interpretation is up to the
+     * broadcaster.  Can be set to null.  Calling this method completely
+     * replaces the current map (if any).
+     * 
+     * <p><strong>This method does not work with non-ordered broadcasts such
+     * as those sent with {@link Context#sendBroadcast(Intent)
+     * Context.sendBroadcast}</strong></p>
+     * 
+     * @param extras The new extra data map; may be null.
+     * 
+     * @see #setResult(int, String, Bundle)
+     */
+    public final void setResultExtras(Bundle extras) {
+        checkSynchronousHint();
+        mResultExtras = extras;
+    }
+
+    /**
+     * Retrieve the current result extra data, as set by the previous receiver.
+     * Any changes you make to the returned Map will be propagated to the next
+     * receiver.
+     * 
+     * @param makeMap If true then a new empty Map will be made for you if the
+     *                current Map is null; if false you should be prepared to
+     *                receive a null Map.
+     * 
+     * @return Map The current extras map.
+     */
+    public final Bundle getResultExtras(boolean makeMap) {
+        Bundle e = mResultExtras;
+        if (!makeMap) return e;
+        if (e == null) mResultExtras = e = new Bundle();
+        return e;
+    }
+
+    /**
+     * Change all of the result data returned from this broadcasts; only works
+     * with broadcasts sent through
+     * {@link Context#sendOrderedBroadcast(Intent, String)
+     * Context.sendOrderedBroadcast}.  All current result data is replaced
+     * by the value given to this method.
+     * 
+     * <p><strong>This method does not work with non-ordered broadcasts such
+     * as those sent with {@link Context#sendBroadcast(Intent)
+     * Context.sendBroadcast}</strong></p>
+     * 
+     * @param code The new result code.  Often uses the
+     * Activity {@link android.app.Activity#RESULT_CANCELED} and
+     * {@link android.app.Activity#RESULT_OK} constants, though the
+     * actual meaning of this value is ultimately up to the broadcaster.
+     * @param data The new result data.  This is an arbitrary
+     * string whose interpretation is up to the broadcaster; may be null.
+     * @param extras The new extra data map.  This is a Bundle
+     * holding arbitrary data, whose interpretation is up to the
+     * broadcaster.  Can be set to null.  This completely
+     * replaces the current map (if any).
+     */
+    public final void setResult(int code, String data, Bundle extras) {
+        checkSynchronousHint();
+        mResultCode = code;
+        mResultData = data;
+        mResultExtras = extras;
+    }
+ 
+    /**
+     * Returns the flag indicating whether or not this receiver should
+     * abort the current broadcast.
+     * 
+     * @return True if the broadcast should be aborted.
+     */
+    public final boolean getAbortBroadcast() {
+        return mAbortBroadcast;
+    }
+
+    /**
+     * Sets the flag indicating that this receiver should abort the
+     * current broadcast; only works with broadcasts sent through
+     * {@link Context#sendOrderedBroadcast(Intent, String)
+     * Context.sendOrderedBroadcast}.  This will prevent
+     * any other broadcast receivers from receiving the broadcast. It will still
+     * call {@link #onReceive} of the BroadcastReceiver that the caller of 
+     * {@link Context#sendOrderedBroadcast(Intent, String)
+     * Context.sendOrderedBroadcast} passed in.
+     * 
+     * <p><strong>This method does not work with non-ordered broadcasts such
+     * as those sent with {@link Context#sendBroadcast(Intent)
+     * Context.sendBroadcast}</strong></p>
+     */
+    public final void abortBroadcast() {
+        checkSynchronousHint();
+        mAbortBroadcast = true;
+    }
+    
+    /**
+     * Clears the flag indicating that this receiver should abort the current
+     * broadcast.
+     */
+    public final void clearAbortBroadcast() {
+        mAbortBroadcast = false;
+    }
+    
+    /**
+     * For internal use, sets the hint about whether this BroadcastReceiver is
+     * running in ordered mode.
+     */
+    public final void setOrderedHint(boolean isOrdered) {
+        mOrderedHint = isOrdered;
+    }
+    
+    /**
+     * Control inclusion of debugging help for mismatched
+     * calls to {@ Context#registerReceiver(BroadcastReceiver, IntentFilter)
+     * Context.registerReceiver()}.
+     * If called with true, before given to registerReceiver(), then the
+     * callstack of the following {@link Context#unregisterReceiver(BroadcastReceiver)
+     * Context.unregisterReceiver()} call is retained, to be printed if a later
+     * incorrect unregister call is made.  Note that doing this requires retaining
+     * information about the BroadcastReceiver for the lifetime of the app,
+     * resulting in a leak -- this should only be used for debugging.
+     */
+    public final void setDebugUnregister(boolean debug) {
+        mDebugUnregister = debug;
+    }
+    
+    /**
+     * Return the last value given to {@link #setDebugUnregister}.
+     */
+    public final boolean getDebugUnregister() {
+        return mDebugUnregister;
+    }
+    
+    void checkSynchronousHint() {
+        if (mOrderedHint) {
+            return;
+        }
+        RuntimeException e = new RuntimeException(
+                "BroadcastReceiver trying to return result during a non-ordered broadcast");
+        e.fillInStackTrace();
+        Log.e("BroadcastReceiver", e.getMessage(), e);
+    }
+    
+    private int mResultCode;
+    private String mResultData;
+    private Bundle mResultExtras;
+    private boolean mAbortBroadcast;
+    private boolean mDebugUnregister;
+    private boolean mOrderedHint;
+}
+
diff --git a/core/java/android/content/ComponentCallbacks.java b/core/java/android/content/ComponentCallbacks.java
new file mode 100644
index 0000000..dad60b0
--- /dev/null
+++ b/core/java/android/content/ComponentCallbacks.java
@@ -0,0 +1,54 @@
+/*
+ * 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 android.content;
+
+import android.content.res.Configuration;
+
+/**
+ * The set of callback APIs that are common to all application components
+ * ({@link android.app.Activity}, {@link android.app.Service},
+ * {@link ContentProvider}, and {@link android.app.Application}).
+ */
+public interface ComponentCallbacks {
+    /**
+     * Called by the system when the device configuration changes while your
+     * component is running.  Note that, unlike activities, other components
+     * are never restarted when a configuration changes: they must always deal
+     * with the results of the change, such as by re-retrieving resources.
+     * 
+     * <p>At the time that this function has been called, your Resources
+     * object will have been updated to return resource values matching the
+     * new configuration.
+     * 
+     * @param newConfig The new device configuration.
+     */
+    void onConfigurationChanged(Configuration newConfig);
+    
+    /**
+     * This is called when the overall system is running low on memory, and
+     * would like actively running process to try to tighten their belt.  While
+     * the exact point at which this will be called is not defined, generally
+     * it will happen around the time all background process have been killed,
+     * that is before reaching the point of killing processes hosting
+     * service and foreground UI that we would like to avoid killing.
+     * 
+     * <p>Applications that want to be nice can implement this method to release
+     * any caches or other unnecessary resources they may be holding on to.
+     * The system will perform a gc for you after returning from this method.
+     */
+    void onLowMemory();
+}
diff --git a/core/java/android/content/ComponentName.aidl b/core/java/android/content/ComponentName.aidl
new file mode 100644
index 0000000..40dc8de
--- /dev/null
+++ b/core/java/android/content/ComponentName.aidl
@@ -0,0 +1,19 @@
+/**
+ * Copyright (c) 2007, 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 android.content;
+
+parcelable ComponentName;
diff --git a/core/java/android/content/ComponentName.java b/core/java/android/content/ComponentName.java
new file mode 100644
index 0000000..32c6864
--- /dev/null
+++ b/core/java/android/content/ComponentName.java
@@ -0,0 +1,276 @@
+/*
+ * 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 android.content;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Identifier for a specific application component
+ * ({@link android.app.Activity}, {@link android.app.Service},
+ * {@link android.content.BroadcastReceiver}, or
+ * {@link android.content.ContentProvider}) that is available.  Two
+ * pieces of information, encapsulated here, are required to identify
+ * a component: the package (a String) it exists in, and the class (a String)
+ * name inside of that package.
+ * 
+ */
+public final class ComponentName implements Parcelable {
+    private final String mPackage;
+    private final String mClass;
+
+    /**
+     * Create a new component identifier.
+     * 
+     * @param pkg The name of the package that the component exists in.  Can
+     * not be null.
+     * @param cls The name of the class inside of <var>pkg</var> that
+     * implements the component.  Can not be null.
+     */
+    public ComponentName(String pkg, String cls) {
+        if (pkg == null) throw new NullPointerException("package name is null");
+        if (cls == null) throw new NullPointerException("class name is null");
+        mPackage = pkg;
+        mClass = cls;
+    }
+
+    /**
+     * Create a new component identifier from a Context and class name.
+     * 
+     * @param pkg A Context for the package implementing the component,
+     * from which the actual package name will be retrieved.
+     * @param cls The name of the class inside of <var>pkg</var> that
+     * implements the component.
+     */
+    public ComponentName(Context pkg, String cls) {
+        if (cls == null) throw new NullPointerException("class name is null");
+        mPackage = pkg.getPackageName();
+        mClass = cls;
+    }
+
+    /**
+     * Create a new component identifier from a Context and Class object.
+     * 
+     * @param pkg A Context for the package implementing the component, from
+     * which the actual package name will be retrieved.
+     * @param cls The Class object of the desired component, from which the
+     * actual class name will be retrieved.
+     */
+    public ComponentName(Context pkg, Class<?> cls) {
+        mPackage = pkg.getPackageName();
+        mClass = cls.getName();
+    }
+
+    /**
+     * Return the package name of this component.
+     */
+    public String getPackageName() {
+        return mPackage;
+    }
+    
+    /**
+     * Return the class name of this component.
+     */
+    public String getClassName() {
+        return mClass;
+    }
+    
+    /**
+     * Return the class name, either fully qualified or in a shortened form
+     * (with a leading '.') if it is a suffix of the package.
+     */
+    public String getShortClassName() {
+        if (mClass.startsWith(mPackage)) {
+            int PN = mPackage.length();
+            int CN = mClass.length();
+            if (CN > PN && mClass.charAt(PN) == '.') {
+                return mClass.substring(PN, CN);
+            }
+        }
+        return mClass;
+    }
+    
+    /**
+     * Return a String that unambiguously describes both the package and
+     * class names contained in the ComponentName.  You can later recover
+     * the ComponentName from this string through
+     * {@link #unflattenFromString(String)}.
+     * 
+     * @return Returns a new String holding the package and class names.  This
+     * is represented as the package name, concatenated with a '/' and then the
+     * class name.
+     * 
+     * @see #unflattenFromString(String)
+     */
+    public String flattenToString() {
+        return mPackage + "/" + mClass;
+    }
+    
+    /**
+     * The samee as {@link #flattenToString()}, but abbreviates the class
+     * name if it is a suffix of the package.  The result can still be used
+     * with {@link #unflattenFromString(String)}.
+     * 
+     * @return Returns a new String holding the package and class names.  This
+     * is represented as the package name, concatenated with a '/' and then the
+     * class name.
+     * 
+     * @see #unflattenFromString(String)
+     */
+    public String flattenToShortString() {
+        return mPackage + "/" + getShortClassName();
+    }
+    
+    /**
+     * Recover a ComponentName from a String that was previously created with
+     * {@link #flattenToString()}.  It splits the string at the first '/',
+     * taking the part before as the package name and the part after as the
+     * class name.  As a special convenience (to use, for example, when
+     * parsing component names on the command line), if the '/' is immediately
+     * followed by a '.' then the final class name will be the concatenation
+     * of the package name with the string following the '/'.  Thus
+     * "com.foo/.Blah" becomes package="com.foo" class="com.foo.Blah".
+     * 
+     * @param str The String that was returned by flattenToString().
+     * @return Returns a new ComponentName containing the package and class
+     * names that were encoded in <var>str</var>
+     * 
+     * @see #flattenToString()
+     */
+    public static ComponentName unflattenFromString(String str) {
+        int sep = str.indexOf('/');
+        if (sep < 0 || (sep+1) >= str.length()) {
+            return null;
+        }
+        String pkg = str.substring(0, sep);
+        String cls = str.substring(sep+1);
+        if (cls.length() > 0 && cls.charAt(0) == '.') {
+            cls = pkg + cls;
+        }
+        return new ComponentName(pkg, cls);
+    }
+    
+    /**
+     * Return string representation of this class without the class's name
+     * as a prefix.
+     */
+    public String toShortString() {
+        return "{" + mPackage + "/" + mClass + "}";
+    }
+
+    @Override
+    public String toString() {
+        return "ComponentInfo{" + mPackage + "/" + mClass + "}";
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        try {
+            if (obj != null) {
+                ComponentName other = (ComponentName)obj;
+                // Note: no null checks, because mPackage and mClass can
+                // never be null.
+                return mPackage.equals(other.mPackage)
+                        && mClass.equals(other.mClass);
+            }
+        } catch (ClassCastException e) {
+        }
+        return false;
+    }
+
+    @Override
+    public int hashCode() {
+        return mPackage.hashCode() + mClass.hashCode();
+    }
+    
+    public int describeContents() {
+        return 0;
+    }
+
+    public void writeToParcel(Parcel out, int flags) {
+        out.writeString(mPackage);
+        out.writeString(mClass);
+    }
+
+    /**
+     * Write a ComponentName to a Parcel, handling null pointers.  Must be
+     * read with {@link #readFromParcel(Parcel)}.
+     * 
+     * @param c The ComponentName to be written.
+     * @param out The Parcel in which the ComponentName will be placed.
+     * 
+     * @see #readFromParcel(Parcel)
+     */
+    public static void writeToParcel(ComponentName c, Parcel out) {
+        if (c != null) {
+            c.writeToParcel(out, 0);
+        } else {
+            out.writeString(null);
+        }
+    }
+    
+    /**
+     * Read a ComponentName from a Parcel that was previously written
+     * with {@link #writeToParcel(ComponentName, Parcel)}, returning either
+     * a null or new object as appropriate.
+     * 
+     * @param in The Parcel from which to read the ComponentName
+     * @return Returns a new ComponentName matching the previously written
+     * object, or null if a null had been written.
+     * 
+     * @see #writeToParcel(ComponentName, Parcel)
+     */
+    public static ComponentName readFromParcel(Parcel in) {
+        String pkg = in.readString();
+        return pkg != null ? new ComponentName(pkg, in) : null;
+    }
+    
+    public static final Parcelable.Creator<ComponentName> CREATOR
+            = new Parcelable.Creator<ComponentName>() {
+        public ComponentName createFromParcel(Parcel in) {
+            return new ComponentName(in);
+        }
+
+        public ComponentName[] newArray(int size) {
+            return new ComponentName[size];
+        }
+    };
+
+    /**
+     * Instantiate a new ComponentName from the data in a Parcel that was
+     * previously written with {@link #writeToParcel(Parcel, int)}.  Note that you
+     * must not use this with data written by
+     * {@link #writeToParcel(ComponentName, Parcel)} since it is not possible
+     * to handle a null ComponentObject here.
+     * 
+     * @param in The Parcel containing the previously written ComponentName,
+     * positioned at the location in the buffer where it was written.
+     */
+    public ComponentName(Parcel in) {
+        mPackage = in.readString();
+        if (mPackage == null) throw new NullPointerException(
+                "package name is null");
+        mClass = in.readString();
+        if (mClass == null) throw new NullPointerException(
+                "class name is null");
+    }
+
+    private ComponentName(String pkg, Parcel in) {
+        mPackage = pkg;
+        mClass = in.readString();
+    }
+}
diff --git a/core/java/android/content/ContentInsertHandler.java b/core/java/android/content/ContentInsertHandler.java
new file mode 100644
index 0000000..fbf726e
--- /dev/null
+++ b/core/java/android/content/ContentInsertHandler.java
@@ -0,0 +1,50 @@
+/*
+ * 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 android.content;
+
+
+import org.xml.sax.ContentHandler;
+import org.xml.sax.SAXException;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * Interface to insert data to ContentResolver
+ * @hide
+ */
+public interface ContentInsertHandler extends ContentHandler {
+    /**
+     * insert data from InputStream to ContentResolver
+     * @param contentResolver
+     * @param in InputStream
+     * @throws IOException
+     * @throws SAXException
+     */
+    public void insert(ContentResolver contentResolver, InputStream in) 
+        throws IOException, SAXException;
+    
+    /**
+     * insert data from String to ContentResolver
+     * @param contentResolver
+     * @param in input string
+     * @throws SAXException
+     */
+    public void insert(ContentResolver contentResolver, String in) 
+        throws SAXException;
+    
+}
diff --git a/core/java/android/content/ContentProvider.java b/core/java/android/content/ContentProvider.java
new file mode 100644
index 0000000..25544de
--- /dev/null
+++ b/core/java/android/content/ContentProvider.java
@@ -0,0 +1,609 @@
+/*
+ * 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 android.content;
+
+import android.content.pm.PackageManager;
+import android.content.pm.ProviderInfo;
+import android.content.res.AssetFileDescriptor;
+import android.content.res.Configuration;
+import android.database.Cursor;
+import android.database.CursorToBulkCursorAdaptor;
+import android.database.CursorWindow;
+import android.database.IBulkCursor;
+import android.database.IContentObserver;
+import android.database.SQLException;
+import android.net.Uri;
+import android.os.Binder;
+import android.os.ParcelFileDescriptor;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+
+/**
+ * Content providers are one of the primary building blocks of Android applications, providing
+ * content to applications. They encapsulate data and provide it to applications through the single
+ * {@link ContentResolver} interface. A content provider is only required if you need to share
+ * data between multiple applications. For example, the contacts data is used by multiple
+ * applications and must be stored in a content provider. If you don't need to share data amongst
+ * multiple applications you can use a database directly via
+ * {@link android.database.sqlite.SQLiteDatabase}.
+ *
+ * <p>For more information, read <a href="{@docRoot}guide/topics/providers/content-providers.html">Content
+ * Providers</a>.</p>
+ *
+ * <p>When a request is made via
+ * a {@link ContentResolver} the system inspects the authority of the given URI and passes the
+ * request to the content provider registered with the authority. The content provider can interpret
+ * the rest of the URI however it wants. The {@link UriMatcher} class is helpful for parsing
+ * URIs.</p>
+ *
+ * <p>The primary methods that need to be implemented are:
+ * <ul>
+ *   <li>{@link #query} which returns data to the caller</li>
+ *   <li>{@link #insert} which inserts new data into the content provider</li>
+ *   <li>{@link #update} which updates existing data in the content provider</li>
+ *   <li>{@link #delete} which deletes data from the content provider</li>
+ *   <li>{@link #getType} which returns the MIME type of data in the content provider</li>
+ * </ul></p>
+ *
+ * <p>This class takes care of cross process calls so subclasses don't have to worry about which
+ * process a request is coming from.</p>
+ */
+public abstract class ContentProvider implements ComponentCallbacks {
+    private Context mContext = null;
+    private String mReadPermission;
+    private String mWritePermission;
+
+    private Transport mTransport = new Transport();
+
+    /**
+     * Given an IContentProvider, try to coerce it back to the real
+     * ContentProvider object if it is running in the local process.  This can
+     * be used if you know you are running in the same process as a provider,
+     * and want to get direct access to its implementation details.  Most
+     * clients should not nor have a reason to use it.
+     *
+     * @param abstractInterface The ContentProvider interface that is to be
+     *              coerced.
+     * @return If the IContentProvider is non-null and local, returns its actual
+     * ContentProvider instance.  Otherwise returns null.
+     * @hide
+     */
+    public static ContentProvider coerceToLocalContentProvider(
+            IContentProvider abstractInterface) {
+        if (abstractInterface instanceof Transport) {
+            return ((Transport)abstractInterface).getContentProvider();
+        }
+        return null;
+    }
+
+    /**
+     * Binder object that deals with remoting.
+     *
+     * @hide
+     */
+    class Transport extends ContentProviderNative {
+        ContentProvider getContentProvider() {
+            return ContentProvider.this;
+        }
+
+        /**
+         * Remote version of a query, which returns an IBulkCursor. The bulk
+         * cursor should be wrapped with BulkCursorToCursorAdaptor before use.
+         */
+        public IBulkCursor bulkQuery(Uri uri, String[] projection,
+                String selection, String[] selectionArgs, String sortOrder,
+                IContentObserver observer, CursorWindow window) {
+            checkReadPermission(uri);
+            Cursor cursor = ContentProvider.this.query(uri, projection,
+                    selection, selectionArgs, sortOrder);
+            if (cursor == null) {
+                return null;
+            }
+            String wperm = getWritePermission();
+            return new CursorToBulkCursorAdaptor(cursor, observer,
+                    ContentProvider.this.getClass().getName(),
+                    wperm == null ||
+                    getContext().checkCallingOrSelfPermission(getWritePermission())
+                            == PackageManager.PERMISSION_GRANTED,
+                    window);
+        }
+
+        public Cursor query(Uri uri, String[] projection,
+                String selection, String[] selectionArgs, String sortOrder) {
+            checkReadPermission(uri);
+            return ContentProvider.this.query(uri, projection, selection,
+                    selectionArgs, sortOrder);
+        }
+
+        public String getType(Uri uri) {
+            return ContentProvider.this.getType(uri);
+        }
+
+
+        public Uri insert(Uri uri, ContentValues initialValues) {
+            checkWritePermission(uri);
+            return ContentProvider.this.insert(uri, initialValues);
+        }
+
+        public int bulkInsert(Uri uri, ContentValues[] initialValues) {
+            checkWritePermission(uri);
+            return ContentProvider.this.bulkInsert(uri, initialValues);
+        }
+
+        public int delete(Uri uri, String selection, String[] selectionArgs) {
+            checkWritePermission(uri);
+            return ContentProvider.this.delete(uri, selection, selectionArgs);
+        }
+
+        public int update(Uri uri, ContentValues values, String selection,
+                String[] selectionArgs) {
+            checkWritePermission(uri);
+            return ContentProvider.this.update(uri, values, selection, selectionArgs);
+        }
+
+        public ParcelFileDescriptor openFile(Uri uri, String mode)
+                throws FileNotFoundException {
+            if (mode != null && mode.startsWith("rw")) checkWritePermission(uri);
+            else checkReadPermission(uri);
+            return ContentProvider.this.openFile(uri, mode);
+        }
+
+        public AssetFileDescriptor openAssetFile(Uri uri, String mode)
+                throws FileNotFoundException {
+            if (mode != null && mode.startsWith("rw")) checkWritePermission(uri);
+            else checkReadPermission(uri);
+            return ContentProvider.this.openAssetFile(uri, mode);
+        }
+
+        public ISyncAdapter getSyncAdapter() {
+            checkWritePermission(null);
+            return ContentProvider.this.getSyncAdapter().getISyncAdapter();
+        }
+
+        private void checkReadPermission(Uri uri) {
+            final String rperm = getReadPermission();
+            final int pid = Binder.getCallingPid();
+            final int uid = Binder.getCallingUid();
+            if (getContext().checkUriPermission(uri, rperm, null, pid, uid,
+                    Intent.FLAG_GRANT_READ_URI_PERMISSION)
+                    == PackageManager.PERMISSION_GRANTED) {
+                return;
+            }
+            String msg = "Permission Denial: reading "
+                    + ContentProvider.this.getClass().getName()
+                    + " uri " + uri + " from pid=" + Binder.getCallingPid()
+                    + ", uid=" + Binder.getCallingUid()
+                    + " requires " + rperm;
+            throw new SecurityException(msg);
+        }
+
+        private void checkWritePermission(Uri uri) {
+            final String wperm = getWritePermission();
+            final int pid = Binder.getCallingPid();
+            final int uid = Binder.getCallingUid();
+            if (getContext().checkUriPermission(uri, null, wperm, pid, uid,
+                    Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
+                    == PackageManager.PERMISSION_GRANTED) {
+                return;
+            }
+            String msg = "Permission Denial: writing "
+                    + ContentProvider.this.getClass().getName()
+                    + " uri " + uri + " from pid=" + Binder.getCallingPid()
+                    + ", uid=" + Binder.getCallingUid()
+                    + " requires " + wperm;
+            throw new SecurityException(msg);
+        }
+    }
+
+
+    /**
+     * Retrieve the Context this provider is running in.  Only available once
+     * onCreate(Map icicle) has been called -- this will be null in the
+     * constructor.
+     */
+    public final Context getContext() {
+        return mContext;
+    }
+
+    /**
+     * Change the permission required to read data from the content
+     * provider.  This is normally set for you from its manifest information
+     * when the provider is first created.
+     *
+     * @param permission Name of the permission required for read-only access.
+     */
+    protected final void setReadPermission(String permission) {
+        mReadPermission = permission;
+    }
+
+    /**
+     * Return the name of the permission required for read-only access to
+     * this content provider.  This method can be called from multiple
+     * threads, as described in
+     * <a href="{@docRoot}guide/topics/fundamentals.html#procthread">Application Fundamentals:
+     * Processes and Threads</a>.
+     */
+    public final String getReadPermission() {
+        return mReadPermission;
+    }
+
+    /**
+     * Change the permission required to read and write data in the content
+     * provider.  This is normally set for you from its manifest information
+     * when the provider is first created.
+     *
+     * @param permission Name of the permission required for read/write access.
+     */
+    protected final void setWritePermission(String permission) {
+        mWritePermission = permission;
+    }
+
+    /**
+     * Return the name of the permission required for read/write access to
+     * this content provider.  This method can be called from multiple
+     * threads, as described in
+     * <a href="{@docRoot}guide/topics/fundamentals.html#procthread">Application Fundamentals:
+     * Processes and Threads</a>.
+     */
+    public final String getWritePermission() {
+        return mWritePermission;
+    }
+
+    /**
+     * Called when the provider is being started.
+     *
+     * @return true if the provider was successfully loaded, false otherwise
+     */
+    public abstract boolean onCreate();
+
+    public void onConfigurationChanged(Configuration newConfig) {
+    }
+    
+    public void onLowMemory() {
+    }
+
+    /**
+     * Receives a query request from a client in a local process, and
+     * returns a Cursor. This is called internally by the {@link ContentResolver}.
+     * This method can be called from multiple
+     * threads, as described in
+     * <a href="{@docRoot}guide/topics/fundamentals.html#procthread">Application Fundamentals:
+     * Processes and Threads</a>.
+     * <p>
+     * Example client call:<p>
+     * <pre>// Request a specific record.
+     * Cursor managedCursor = managedQuery(
+                Contacts.People.CONTENT_URI.addId(2),
+                projection,    // Which columns to return.
+                null,          // WHERE clause.
+                People.NAME + " ASC");   // Sort order.</pre>
+     * Example implementation:<p>
+     * <pre>// SQLiteQueryBuilder is a helper class that creates the
+        // proper SQL syntax for us.
+        SQLiteQueryBuilder qBuilder = new SQLiteQueryBuilder();
+
+        // Set the table we're querying.
+        qBuilder.setTables(DATABASE_TABLE_NAME);
+
+        // If the query ends in a specific record number, we're
+        // being asked for a specific record, so set the
+        // WHERE clause in our query.
+        if((URI_MATCHER.match(uri)) == SPECIFIC_MESSAGE){
+            qBuilder.appendWhere("_id=" + uri.getPathLeafId());
+        }
+
+        // Make the query.
+        Cursor c = qBuilder.query(mDb,
+                projection,
+                selection,
+                selectionArgs,
+                groupBy,
+                having,
+                sortOrder);
+        c.setNotificationUri(getContext().getContentResolver(), uri);
+        return c;</pre>
+     *
+     * @param uri The URI to query. This will be the full URI sent by the client;
+     * if the client is requesting a specific record, the URI will end in a record number
+     * that the implementation should parse and add to a WHERE or HAVING clause, specifying
+     * that _id value.
+     * @param projection The list of columns to put into the cursor. If
+     *      null all columns are included.
+     * @param selection A selection criteria to apply when filtering rows.
+     *      If null then all rows are included.
+     * @param sortOrder How the rows in the cursor should be sorted.
+     *        If null then the provider is free to define the sort order.
+     * @return a Cursor or null.
+     */
+    public abstract Cursor query(Uri uri, String[] projection,
+            String selection, String[] selectionArgs, String sortOrder);
+
+    /**
+     * Return the MIME type of the data at the given URI. This should start with
+     * <code>vnd.android.cursor.item</code> for a single record,
+     * or <code>vnd.android.cursor.dir/</code> for multiple items.
+     * This method can be called from multiple
+     * threads, as described in
+     * <a href="{@docRoot}guide/topics/fundamentals.html#procthread">Application Fundamentals:
+     * Processes and Threads</a>.
+     *
+     * @param uri the URI to query.
+     * @return a MIME type string, or null if there is no type.
+     */
+    public abstract String getType(Uri uri);
+
+    /**
+     * Implement this to insert a new row.
+     * As a courtesy, call {@link ContentResolver#notifyChange(android.net.Uri ,android.database.ContentObserver) notifyChange()}
+     * after inserting.
+     * This method can be called from multiple
+     * threads, as described in
+     * <a href="{@docRoot}guide/topics/fundamentals.html#procthread">Application Fundamentals:
+     * Processes and Threads</a>.
+     * @param uri The content:// URI of the insertion request.
+     * @param values A set of column_name/value pairs to add to the database.
+     * @return The URI for the newly inserted item.
+     */
+    public abstract Uri insert(Uri uri, ContentValues values);
+
+    /**
+     * Implement this to insert a set of new rows, or the default implementation will
+     * iterate over the values and call {@link #insert} on each of them.
+     * As a courtesy, call {@link ContentResolver#notifyChange(android.net.Uri ,android.database.ContentObserver) notifyChange()}
+     * after inserting.
+     * This method can be called from multiple
+     * threads, as described in
+     * <a href="{@docRoot}guide/topics/fundamentals.html#procthread">Application Fundamentals:
+     * Processes and Threads</a>.
+     *
+     * @param uri The content:// URI of the insertion request.
+     * @param values An array of sets of column_name/value pairs to add to the database.
+     * @return The number of values that were inserted.
+     */
+    public int bulkInsert(Uri uri, ContentValues[] values) {
+        int numValues = values.length;
+        for (int i = 0; i < numValues; i++) {
+            insert(uri, values[i]);
+        }
+        return numValues;
+    }
+
+    /**
+     * A request to delete one or more rows. The selection clause is applied when performing
+     * the deletion, allowing the operation to affect multiple rows in a
+     * directory.
+     * As a courtesy, call {@link ContentResolver#notifyChange(android.net.Uri ,android.database.ContentObserver) notifyDelete()}
+     * after deleting.
+     * This method can be called from multiple
+     * threads, as described in
+     * <a href="{@docRoot}guide/topics/fundamentals.html#procthread">Application Fundamentals:
+     * Processes and Threads</a>.
+     *
+     * <p>The implementation is responsible for parsing out a row ID at the end
+     * of the URI, if a specific row is being deleted. That is, the client would
+     * pass in <code>content://contacts/people/22</code> and the implementation is
+     * responsible for parsing the record number (22) when creating a SQL statement.
+     *
+     * @param uri The full URI to query, including a row ID (if a specific record is requested).
+     * @param selection An optional restriction to apply to rows when deleting.
+     * @return The number of rows affected.
+     * @throws SQLException
+     */
+    public abstract int delete(Uri uri, String selection, String[] selectionArgs);
+
+    /**
+     * Update a content URI. All rows matching the optionally provided selection
+     * will have their columns listed as the keys in the values map with the
+     * values of those keys.
+     * As a courtesy, call {@link ContentResolver#notifyChange(android.net.Uri ,android.database.ContentObserver) notifyChange()}
+     * after updating.
+     * This method can be called from multiple
+     * threads, as described in
+     * <a href="{@docRoot}guide/topics/fundamentals.html#procthread">Application Fundamentals:
+     * Processes and Threads</a>.
+     *
+     * @param uri The URI to query. This can potentially have a record ID if this
+     * is an update request for a specific record.
+     * @param values A Bundle mapping from column names to new column values (NULL is a
+     *               valid value).
+     * @param selection An optional filter to match rows to update.
+     * @return the number of rows affected.
+     */
+    public abstract int update(Uri uri, ContentValues values, String selection,
+            String[] selectionArgs);
+
+    /**
+     * Open a file blob associated with a content URI.
+     * This method can be called from multiple
+     * threads, as described in
+     * <a href="{@docRoot}guide/topics/fundamentals.html#procthread">Application Fundamentals:
+     * Processes and Threads</a>.
+     * 
+     * <p>Returns a
+     * ParcelFileDescriptor, from which you can obtain a
+     * {@link java.io.FileDescriptor} for use with
+     * {@link java.io.FileInputStream}, {@link java.io.FileOutputStream}, etc.
+     * This can be used to store large data (such as an image) associated with
+     * a particular piece of content.
+     *
+     * <p>The returned ParcelFileDescriptor is owned by the caller, so it is
+     * their responsibility to close it when done.  That is, the implementation
+     * of this method should create a new ParcelFileDescriptor for each call.
+     *
+     * @param uri The URI whose file is to be opened.
+     * @param mode Access mode for the file.  May be "r" for read-only access,
+     * "rw" for read and write access, or "rwt" for read and write access
+     * that truncates any existing file.
+     *
+     * @return Returns a new ParcelFileDescriptor which you can use to access
+     * the file.
+     *
+     * @throws FileNotFoundException Throws FileNotFoundException if there is
+     * no file associated with the given URI or the mode is invalid.
+     * @throws SecurityException Throws SecurityException if the caller does
+     * not have permission to access the file.
+     * 
+     * @see #openAssetFile(Uri, String)
+     * @see #openFileHelper(Uri, String)
+     */    
+    public ParcelFileDescriptor openFile(Uri uri, String mode)
+            throws FileNotFoundException {
+        throw new FileNotFoundException("No files supported by provider at "
+                + uri);
+    }
+    
+    /**
+     * This is like {@link #openFile}, but can be implemented by providers
+     * that need to be able to return sub-sections of files, often assets
+     * inside of their .apk.  Note that when implementing this your clients
+     * must be able to deal with such files, either directly with
+     * {@link ContentResolver#openAssetFileDescriptor
+     * ContentResolver.openAssetFileDescriptor}, or by using the higher-level
+     * {@link ContentResolver#openInputStream ContentResolver.openInputStream}
+     * or {@link ContentResolver#openOutputStream ContentResolver.openOutputStream}
+     * methods.
+     * 
+     * <p><em>Note: if you are implementing this to return a full file, you
+     * should create the AssetFileDescriptor with
+     * {@link AssetFileDescriptor#UNKNOWN_LENGTH} to be compatible with
+     * applications that can not handle sub-sections of files.</em></p>
+     *
+     * @param uri The URI whose file is to be opened.
+     * @param mode Access mode for the file.  May be "r" for read-only access,
+     * "w" for write-only access (erasing whatever data is currently in
+     * the file), "wa" for write-only access to append to any existing data,
+     * "rw" for read and write access on any existing data, and "rwt" for read
+     * and write access that truncates any existing file.
+     *
+     * @return Returns a new AssetFileDescriptor which you can use to access
+     * the file.
+     *
+     * @throws FileNotFoundException Throws FileNotFoundException if there is
+     * no file associated with the given URI or the mode is invalid.
+     * @throws SecurityException Throws SecurityException if the caller does
+     * not have permission to access the file.
+     * 
+     * @see #openFile(Uri, String)
+     * @see #openFileHelper(Uri, String)
+     */
+    public AssetFileDescriptor openAssetFile(Uri uri, String mode)
+            throws FileNotFoundException {
+        ParcelFileDescriptor fd = openFile(uri, mode);
+        return fd != null ? new AssetFileDescriptor(fd, 0, -1) : null;
+    }
+
+    /**
+     * Convenience for subclasses that wish to implement {@link #openFile}
+     * by looking up a column named "_data" at the given URI.
+     *
+     * @param uri The URI to be opened.
+     * @param mode The file mode.  May be "r" for read-only access,
+     * "w" for write-only access (erasing whatever data is currently in
+     * the file), "wa" for write-only access to append to any existing data,
+     * "rw" for read and write access on any existing data, and "rwt" for read
+     * and write access that truncates any existing file.
+     *
+     * @return Returns a new ParcelFileDescriptor that can be used by the
+     * client to access the file.
+     */
+    protected final ParcelFileDescriptor openFileHelper(Uri uri,
+            String mode) throws FileNotFoundException {
+        Cursor c = query(uri, new String[]{"_data"}, null, null, null);
+        int count = (c != null) ? c.getCount() : 0;
+        if (count != 1) {
+            // If there is not exactly one result, throw an appropriate
+            // exception.
+            if (c != null) {
+                c.close();
+            }
+            if (count == 0) {
+                throw new FileNotFoundException("No entry for " + uri);
+            }
+            throw new FileNotFoundException("Multiple items at " + uri);
+        }
+
+        c.moveToFirst();
+        int i = c.getColumnIndex("_data");
+        String path = (i >= 0 ? c.getString(i) : null);
+        c.close();
+        if (path == null) {
+            throw new FileNotFoundException("Column _data not found.");
+        }
+
+        int modeBits = ContentResolver.modeToMode(uri, mode);
+        return ParcelFileDescriptor.open(new File(path), modeBits);
+    }
+
+    /**
+     * Get the sync adapter that is to be used by this content provider.
+     * This is intended for use by the sync system. If null then this
+     * content provider is considered not syncable.
+     * This method can be called from multiple
+     * threads, as described in
+     * <a href="{@docRoot}guide/topics/fundamentals.html#procthread">Application Fundamentals:
+     * Processes and Threads</a>.
+     * 
+     * @return the SyncAdapter that is to be used by this ContentProvider, or null
+     *   if this ContentProvider is not syncable
+     * @hide
+     */
+    public SyncAdapter getSyncAdapter() {
+        return null;
+    }
+
+    /**
+     * Returns true if this instance is a temporary content provider.
+     * @return true if this instance is a temporary content provider
+     */
+    protected boolean isTemporary() {
+        return false;
+    }
+
+    /**
+     * Returns the Binder object for this provider.
+     *
+     * @return the Binder object for this provider
+     * @hide
+     */
+    public IContentProvider getIContentProvider() {
+        return mTransport;
+    }
+
+    /**
+     * After being instantiated, this is called to tell the content provider
+     * about itself.
+     *
+     * @param context The context this provider is running in
+     * @param info Registered information about this content provider
+     */
+    public void attachInfo(Context context, ProviderInfo info) {
+
+        /*
+         * Only allow it to be set once, so after the content service gives
+         * this to us clients can't change it.
+         */
+        if (mContext == null) {
+            mContext = context;
+            if (info != null) {
+                setReadPermission(info.readPermission);
+                setWritePermission(info.writePermission);
+            }
+            ContentProvider.this.onCreate();
+        }
+    }
+}
diff --git a/core/java/android/content/ContentProviderNative.java b/core/java/android/content/ContentProviderNative.java
new file mode 100644
index 0000000..e5e3f74
--- /dev/null
+++ b/core/java/android/content/ContentProviderNative.java
@@ -0,0 +1,478 @@
+/*
+ * 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 android.content;
+
+import android.content.res.AssetFileDescriptor;
+import android.database.BulkCursorNative;
+import android.database.BulkCursorToCursorAdaptor;
+import android.database.Cursor;
+import android.database.CursorWindow;
+import android.database.DatabaseUtils;
+import android.database.IBulkCursor;
+import android.database.IContentObserver;
+import android.net.Uri;
+import android.os.Binder;
+import android.os.RemoteException;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.ParcelFileDescriptor;
+import android.os.Parcelable;
+
+import java.io.FileNotFoundException;
+
+/**
+ * {@hide}
+ */
+abstract public class ContentProviderNative extends Binder implements IContentProvider {
+    private static final String TAG = "ContentProvider";
+
+    public ContentProviderNative()
+    {
+        attachInterface(this, descriptor);
+    }
+
+    /**
+     * Cast a Binder object into a content resolver interface, generating
+     * a proxy if needed.
+     */
+    static public IContentProvider asInterface(IBinder obj)
+    {
+        if (obj == null) {
+            return null;
+        }
+        IContentProvider in =
+            (IContentProvider)obj.queryLocalInterface(descriptor);
+        if (in != null) {
+            return in;
+        }
+
+        return new ContentProviderProxy(obj);
+    }
+
+    @Override
+    public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
+            throws RemoteException {
+        try {
+            switch (code) {
+                case QUERY_TRANSACTION:
+                {
+                    data.enforceInterface(IContentProvider.descriptor);
+                    Uri url = Uri.CREATOR.createFromParcel(data);
+                    int num = data.readInt();
+                    String[] projection = null;
+                    if (num > 0) {
+                        projection = new String[num];
+                        for (int i = 0; i < num; i++) {
+                            projection[i] = data.readString();
+                        }
+                    }
+                    String selection = data.readString();
+                    num = data.readInt();
+                    String[] selectionArgs = null;
+                    if (num > 0) {
+                        selectionArgs = new String[num];
+                        for (int i = 0; i < num; i++) {
+                            selectionArgs[i] = data.readString();
+                        }
+                    }
+                    String sortOrder = data.readString();
+                    IContentObserver observer = IContentObserver.Stub.
+                        asInterface(data.readStrongBinder());
+                    CursorWindow window = CursorWindow.CREATOR.createFromParcel(data);
+
+                    IBulkCursor bulkCursor = bulkQuery(url, projection, selection,
+                            selectionArgs, sortOrder, observer, window);
+                    reply.writeNoException();
+                    if (bulkCursor != null) {
+                        reply.writeStrongBinder(bulkCursor.asBinder());
+                    } else {
+                        reply.writeStrongBinder(null);
+                    }
+                    return true;
+                }
+
+                case GET_TYPE_TRANSACTION:
+                {
+                    data.enforceInterface(IContentProvider.descriptor);
+                    Uri url = Uri.CREATOR.createFromParcel(data);
+                    String type = getType(url);
+                    reply.writeNoException();
+                    reply.writeString(type);
+
+                    return true;
+                }
+
+                case INSERT_TRANSACTION:
+                {
+                    data.enforceInterface(IContentProvider.descriptor);
+                    Uri url = Uri.CREATOR.createFromParcel(data);
+                    ContentValues values = ContentValues.CREATOR.createFromParcel(data);
+
+                    Uri out = insert(url, values);
+                    reply.writeNoException();
+                    Uri.writeToParcel(reply, out);
+                    return true;
+                }
+
+                case BULK_INSERT_TRANSACTION:
+                {
+                    data.enforceInterface(IContentProvider.descriptor);
+                    Uri url = Uri.CREATOR.createFromParcel(data);
+                    ContentValues[] values = data.createTypedArray(ContentValues.CREATOR);
+
+                    int count = bulkInsert(url, values);
+                    reply.writeNoException();
+                    reply.writeInt(count);
+                    return true;
+                }
+
+                case DELETE_TRANSACTION:
+                {
+                    data.enforceInterface(IContentProvider.descriptor);
+                    Uri url = Uri.CREATOR.createFromParcel(data);
+                    String selection = data.readString();
+                    String[] selectionArgs = data.readStringArray();
+
+                    int count = delete(url, selection, selectionArgs);
+
+                    reply.writeNoException();
+                    reply.writeInt(count);
+                    return true;
+                }
+
+                case UPDATE_TRANSACTION:
+                {
+                    data.enforceInterface(IContentProvider.descriptor);
+                    Uri url = Uri.CREATOR.createFromParcel(data);
+                    ContentValues values = ContentValues.CREATOR.createFromParcel(data);
+                    String selection = data.readString();
+                    String[] selectionArgs = data.readStringArray();
+
+                    int count = update(url, values, selection, selectionArgs);
+
+                    reply.writeNoException();
+                    reply.writeInt(count);
+                    return true;
+                }
+
+                case OPEN_FILE_TRANSACTION:
+                {
+                    data.enforceInterface(IContentProvider.descriptor);
+                    Uri url = Uri.CREATOR.createFromParcel(data);
+                    String mode = data.readString();
+
+                    ParcelFileDescriptor fd;
+                    fd = openFile(url, mode);
+                    reply.writeNoException();
+                    if (fd != null) {
+                        reply.writeInt(1);
+                        fd.writeToParcel(reply,
+                                Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
+                    } else {
+                        reply.writeInt(0);
+                    }
+                    return true;
+                }
+
+                case OPEN_ASSET_FILE_TRANSACTION:
+                {
+                    data.enforceInterface(IContentProvider.descriptor);
+                    Uri url = Uri.CREATOR.createFromParcel(data);
+                    String mode = data.readString();
+
+                    AssetFileDescriptor fd;
+                    fd = openAssetFile(url, mode);
+                    reply.writeNoException();
+                    if (fd != null) {
+                        reply.writeInt(1);
+                        fd.writeToParcel(reply,
+                                Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
+                    } else {
+                        reply.writeInt(0);
+                    }
+                    return true;
+                }
+
+                case GET_SYNC_ADAPTER_TRANSACTION:
+                {
+                    data.enforceInterface(IContentProvider.descriptor);
+                    ISyncAdapter sa = getSyncAdapter();
+                    reply.writeNoException();
+                    reply.writeStrongBinder(sa != null ? sa.asBinder() : null);
+                    return true;
+                }
+            }
+        } catch (Exception e) {
+            DatabaseUtils.writeExceptionToParcel(reply, e);
+            return true;
+        }
+
+        return super.onTransact(code, data, reply, flags);
+    }
+
+    public IBinder asBinder()
+    {
+        return this;
+    }
+}
+
+
+final class ContentProviderProxy implements IContentProvider
+{
+    public ContentProviderProxy(IBinder remote)
+    {
+        mRemote = remote;
+    }
+
+    public IBinder asBinder()
+    {
+        return mRemote;
+    }
+
+    public IBulkCursor bulkQuery(Uri url, String[] projection,
+            String selection, String[] selectionArgs, String sortOrder, IContentObserver observer,
+            CursorWindow window) throws RemoteException {
+        Parcel data = Parcel.obtain();
+        Parcel reply = Parcel.obtain();
+
+        data.writeInterfaceToken(IContentProvider.descriptor);
+
+        url.writeToParcel(data, 0);
+        int length = 0;
+        if (projection != null) {
+            length = projection.length;
+        }
+        data.writeInt(length);
+        for (int i = 0; i < length; i++) {
+            data.writeString(projection[i]);
+        }
+        data.writeString(selection);
+        if (selectionArgs != null) {
+            length = selectionArgs.length;
+        } else {
+            length = 0;
+        }
+        data.writeInt(length);
+        for (int i = 0; i < length; i++) {
+            data.writeString(selectionArgs[i]);
+        }
+        data.writeString(sortOrder);
+        data.writeStrongBinder(observer.asBinder());
+        window.writeToParcel(data, 0);
+
+        mRemote.transact(IContentProvider.QUERY_TRANSACTION, data, reply, 0);
+
+        DatabaseUtils.readExceptionFromParcel(reply);
+
+        IBulkCursor bulkCursor = null;
+        IBinder bulkCursorBinder = reply.readStrongBinder();
+        if (bulkCursorBinder != null) {
+            bulkCursor = BulkCursorNative.asInterface(bulkCursorBinder);
+        }
+        
+        data.recycle();
+        reply.recycle();
+        
+        return bulkCursor;
+    }
+
+    public Cursor query(Uri url, String[] projection, String selection,
+            String[] selectionArgs, String sortOrder) throws RemoteException {
+        //TODO make a pool of windows so we can reuse memory dealers
+        CursorWindow window = new CursorWindow(false /* window will be used remotely */);
+        BulkCursorToCursorAdaptor adaptor = new BulkCursorToCursorAdaptor();
+        IBulkCursor bulkCursor = bulkQuery(url, projection, selection, selectionArgs, sortOrder,
+                adaptor.getObserver(), window);
+         
+        if (bulkCursor == null) {
+            return null;
+        }
+        adaptor.set(bulkCursor);
+        return adaptor;
+    }
+
+    public String getType(Uri url) throws RemoteException
+    {
+        Parcel data = Parcel.obtain();
+        Parcel reply = Parcel.obtain();
+
+        data.writeInterfaceToken(IContentProvider.descriptor);
+
+        url.writeToParcel(data, 0);
+
+        mRemote.transact(IContentProvider.GET_TYPE_TRANSACTION, data, reply, 0);
+
+        DatabaseUtils.readExceptionFromParcel(reply);
+        String out = reply.readString();
+
+        data.recycle();
+        reply.recycle();
+
+        return out;
+    }
+
+    public Uri insert(Uri url, ContentValues values) throws RemoteException
+    {
+        Parcel data = Parcel.obtain();
+        Parcel reply = Parcel.obtain();
+
+        data.writeInterfaceToken(IContentProvider.descriptor);
+
+        url.writeToParcel(data, 0);
+        values.writeToParcel(data, 0);
+
+        mRemote.transact(IContentProvider.INSERT_TRANSACTION, data, reply, 0);
+
+        DatabaseUtils.readExceptionFromParcel(reply);
+        Uri out = Uri.CREATOR.createFromParcel(reply);
+
+        data.recycle();
+        reply.recycle();
+
+        return out;
+    }
+
+    public int bulkInsert(Uri url, ContentValues[] values) throws RemoteException {
+        Parcel data = Parcel.obtain();
+        Parcel reply = Parcel.obtain();
+
+        data.writeInterfaceToken(IContentProvider.descriptor);
+
+        url.writeToParcel(data, 0);
+        data.writeTypedArray(values, 0);
+
+        mRemote.transact(IContentProvider.BULK_INSERT_TRANSACTION, data, reply, 0);
+
+        DatabaseUtils.readExceptionFromParcel(reply);
+        int count = reply.readInt();
+
+        data.recycle();
+        reply.recycle();
+
+        return count;
+    }
+
+    public int delete(Uri url, String selection, String[] selectionArgs)
+            throws RemoteException {
+        Parcel data = Parcel.obtain();
+        Parcel reply = Parcel.obtain();
+
+        data.writeInterfaceToken(IContentProvider.descriptor);
+
+        url.writeToParcel(data, 0);
+        data.writeString(selection);
+        data.writeStringArray(selectionArgs);
+
+        mRemote.transact(IContentProvider.DELETE_TRANSACTION, data, reply, 0);
+
+        DatabaseUtils.readExceptionFromParcel(reply);
+        int count = reply.readInt();
+
+        data.recycle();
+        reply.recycle();
+
+        return count;
+    }
+
+    public int update(Uri url, ContentValues values, String selection,
+            String[] selectionArgs) throws RemoteException {
+        Parcel data = Parcel.obtain();
+        Parcel reply = Parcel.obtain();
+
+        data.writeInterfaceToken(IContentProvider.descriptor);
+
+        url.writeToParcel(data, 0);
+        values.writeToParcel(data, 0);
+        data.writeString(selection);
+        data.writeStringArray(selectionArgs);
+
+        mRemote.transact(IContentProvider.UPDATE_TRANSACTION, data, reply, 0);
+
+        DatabaseUtils.readExceptionFromParcel(reply);
+        int count = reply.readInt();
+
+        data.recycle();
+        reply.recycle();
+
+        return count;
+    }
+
+    public ParcelFileDescriptor openFile(Uri url, String mode)
+            throws RemoteException, FileNotFoundException {
+        Parcel data = Parcel.obtain();
+        Parcel reply = Parcel.obtain();
+
+        data.writeInterfaceToken(IContentProvider.descriptor);
+
+        url.writeToParcel(data, 0);
+        data.writeString(mode);
+
+        mRemote.transact(IContentProvider.OPEN_FILE_TRANSACTION, data, reply, 0);
+
+        DatabaseUtils.readExceptionWithFileNotFoundExceptionFromParcel(reply);
+        int has = reply.readInt();
+        ParcelFileDescriptor fd = has != 0 ? reply.readFileDescriptor() : null;
+
+        data.recycle();
+        reply.recycle();
+
+        return fd;
+    }
+
+    public AssetFileDescriptor openAssetFile(Uri url, String mode)
+            throws RemoteException, FileNotFoundException {
+        Parcel data = Parcel.obtain();
+        Parcel reply = Parcel.obtain();
+
+        data.writeInterfaceToken(IContentProvider.descriptor);
+
+        url.writeToParcel(data, 0);
+        data.writeString(mode);
+
+        mRemote.transact(IContentProvider.OPEN_ASSET_FILE_TRANSACTION, data, reply, 0);
+
+        DatabaseUtils.readExceptionWithFileNotFoundExceptionFromParcel(reply);
+        int has = reply.readInt();
+        AssetFileDescriptor fd = has != 0
+                ? AssetFileDescriptor.CREATOR.createFromParcel(reply) : null;
+
+        data.recycle();
+        reply.recycle();
+
+        return fd;
+    }
+
+    public ISyncAdapter getSyncAdapter() throws RemoteException {
+        Parcel data = Parcel.obtain();
+        Parcel reply = Parcel.obtain();
+
+        data.writeInterfaceToken(IContentProvider.descriptor);
+
+        mRemote.transact(IContentProvider.GET_SYNC_ADAPTER_TRANSACTION, data, reply, 0);
+
+        DatabaseUtils.readExceptionFromParcel(reply);
+        ISyncAdapter syncAdapter = ISyncAdapter.Stub.asInterface(reply.readStrongBinder());
+
+        data.recycle();
+        reply.recycle();
+
+        return syncAdapter;
+    }
+
+    private IBinder mRemote;
+}
+
diff --git a/core/java/android/content/ContentQueryMap.java b/core/java/android/content/ContentQueryMap.java
new file mode 100644
index 0000000..dbcb4a7
--- /dev/null
+++ b/core/java/android/content/ContentQueryMap.java
@@ -0,0 +1,172 @@
+/*
+ * Copyright (C) 2007 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 android.content;
+
+import android.database.ContentObserver;
+import android.database.Cursor;
+import android.os.Handler;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Observable;
+
+/**
+ * Caches the contents of a cursor into a Map of String->ContentValues and optionally
+ * keeps the cache fresh by registering for updates on the content backing the cursor. The column of
+ * the database that is to be used as the key of the map is user-configurable, and the
+ * ContentValues contains all columns other than the one that is designated the key.
+ * <p>
+ * The cursor data is accessed by row key and column name via getValue().
+ */
+public class ContentQueryMap extends Observable {
+    private Cursor mCursor;
+    private String[] mColumnNames;
+    private int mKeyColumn;
+
+    private Handler mHandlerForUpdateNotifications = null;
+    private boolean mKeepUpdated = false;
+
+    private Map<String, ContentValues> mValues = null;
+
+    private ContentObserver mContentObserver;
+
+    /** Set when a cursor change notification is received and is cleared on a call to requery(). */
+    private boolean mDirty = false;
+
+    /**
+     * Creates a ContentQueryMap that caches the content backing the cursor
+     *
+     * @param cursor the cursor whose contents should be cached
+     * @param columnNameOfKey the column that is to be used as the key of the values map
+     * @param keepUpdated true if the cursor's ContentProvider should be monitored for changes and 
+     * the map updated when changes do occur
+     * @param handlerForUpdateNotifications the Handler that should be used to receive
+     *  notifications of changes (if requested). Normally you pass null here, but if
+     *  you know that the thread that is creating this isn't a thread that can receive
+     *  messages then you can create your own handler and use that here.
+     */
+    public ContentQueryMap(Cursor cursor, String columnNameOfKey, boolean keepUpdated,
+            Handler handlerForUpdateNotifications) {
+        mCursor = cursor;
+        mColumnNames = mCursor.getColumnNames();
+        mKeyColumn = mCursor.getColumnIndexOrThrow(columnNameOfKey);
+        mHandlerForUpdateNotifications = handlerForUpdateNotifications;
+        setKeepUpdated(keepUpdated);
+
+        // If we aren't keeping the cache updated with the current state of the cursor's 
+        // ContentProvider then read it once into the cache. Otherwise the cache will be filled 
+        // automatically.
+        if (!keepUpdated) {
+            readCursorIntoCache();
+        }
+    }
+
+    /**
+     * Change whether or not the ContentQueryMap will register with the cursor's ContentProvider 
+     * for change notifications. If you use a ContentQueryMap in an activity you should call this
+     * with false in onPause(), which means you need to call it with true in onResume()
+     * if want it to be kept updated.
+     * @param keepUpdated if true the ContentQueryMap should be registered with the cursor's
+     * ContentProvider, false otherwise
+     */
+    public void setKeepUpdated(boolean keepUpdated) {
+        if (keepUpdated == mKeepUpdated) return;
+        mKeepUpdated = keepUpdated;
+
+        if (!mKeepUpdated) {
+            mCursor.unregisterContentObserver(mContentObserver);
+            mContentObserver = null;
+        } else {
+            if (mHandlerForUpdateNotifications == null) {
+                mHandlerForUpdateNotifications = new Handler();
+            }
+            if (mContentObserver == null) {
+                mContentObserver = new ContentObserver(mHandlerForUpdateNotifications) {
+                    @Override
+                    public void onChange(boolean selfChange) {
+                        // If anyone is listening, we need to do this now to broadcast
+                        // to the observers.  Otherwise, we'll just set mDirty and
+                        // let it query lazily when they ask for the values.
+                        if (countObservers() != 0) {
+                            requery();
+                        } else {
+                            mDirty = true;
+                        }
+                    }
+                };
+            }
+            mCursor.registerContentObserver(mContentObserver);
+            // mark dirty, since it is possible the cursor's backing data had changed before we 
+            // registered for changes
+            mDirty = true;
+        }
+    }
+
+    /**
+     * Access the ContentValues for the row specified by rowName
+     * @param rowName which row to read
+     * @return the ContentValues for the row, or null if the row wasn't present in the cursor
+     */
+    public synchronized ContentValues getValues(String rowName) {
+        if (mDirty) requery();
+        return mValues.get(rowName);
+    }
+
+    /** Requeries the cursor and reads the contents into the cache */
+    public void requery() {
+        mDirty = false;
+        mCursor.requery();
+        readCursorIntoCache();
+        setChanged();
+        notifyObservers();
+    }
+
+    private synchronized void readCursorIntoCache() {
+        // Make a new map so old values returned by getRows() are undisturbed.
+        int capacity = mValues != null ? mValues.size() : 0;
+        mValues = new HashMap<String, ContentValues>(capacity);
+        while (mCursor.moveToNext()) {
+            ContentValues values = new ContentValues();
+            for (int i = 0; i < mColumnNames.length; i++) {
+                if (i != mKeyColumn) {
+                    values.put(mColumnNames[i], mCursor.getString(i));
+                }
+            }
+            mValues.put(mCursor.getString(mKeyColumn), values);
+        }
+    }
+
+    public synchronized Map<String, ContentValues> getRows() {
+        if (mDirty) requery();
+        return mValues;
+    }
+
+    public synchronized void close() {
+        if (mContentObserver != null) {
+            mCursor.unregisterContentObserver(mContentObserver);
+            mContentObserver = null;
+        }
+        mCursor.close();
+        mCursor = null;
+    }
+
+    @Override
+    protected void finalize() throws Throwable {
+        if (mCursor != null) close();
+        super.finalize();
+    }
+}
diff --git a/core/java/android/content/ContentResolver.java b/core/java/android/content/ContentResolver.java
new file mode 100644
index 0000000..0d886ee
--- /dev/null
+++ b/core/java/android/content/ContentResolver.java
@@ -0,0 +1,784 @@
+/*
+ * 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 android.content;
+
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.res.AssetFileDescriptor;
+import android.content.res.Resources;
+import android.database.ContentObserver;
+import android.database.Cursor;
+import android.database.CursorWrapper;
+import android.database.IContentObserver;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.ParcelFileDescriptor;
+import android.os.RemoteException;
+import android.text.TextUtils;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.List;
+
+
+/**
+ * This class provides applications access to the content model.
+ */
+public abstract class ContentResolver {
+    public final static String SYNC_EXTRAS_ACCOUNT = "account";
+    public static final String SYNC_EXTRAS_EXPEDITED = "expedited";
+    public static final String SYNC_EXTRAS_FORCE = "force";
+    public static final String SYNC_EXTRAS_UPLOAD = "upload";
+    public static final String SYNC_EXTRAS_OVERRIDE_TOO_MANY_DELETIONS = "deletions_override";
+    public static final String SYNC_EXTRAS_DISCARD_LOCAL_DELETIONS = "discard_deletions";
+
+    public static final String SCHEME_CONTENT = "content";
+    public static final String SCHEME_ANDROID_RESOURCE = "android.resource";
+    public static final String SCHEME_FILE = "file";
+
+    /**
+     * This is the Android platform's base MIME type for a content: URI
+     * containing a Cursor of a single item.  Applications should use this
+     * as the base type along with their own sub-type of their content: URIs
+     * that represent a particular item.  For example, hypothetical IMAP email
+     * client may have a URI
+     * <code>content://com.company.provider.imap/inbox/1</code> for a particular
+     * message in the inbox, whose MIME type would be reported as
+     * <code>CURSOR_ITEM_BASE_TYPE + "/vnd.company.imap-msg"</code>
+     * 
+     * <p>Compare with {@link #CURSOR_DIR_BASE_TYPE}.
+     */
+    public static final String CURSOR_ITEM_BASE_TYPE = "vnd.android.cursor.item";
+    
+    /**
+     * This is the Android platform's base MIME type for a content: URI
+     * containing a Cursor of zero or more items.  Applications should use this
+     * as the base type along with their own sub-type of their content: URIs
+     * that represent a directory of items.  For example, hypothetical IMAP email
+     * client may have a URI
+     * <code>content://com.company.provider.imap/inbox</code> for all of the
+     * messages in its inbox, whose MIME type would be reported as
+     * <code>CURSOR_DIR_BASE_TYPE + "/vnd.company.imap-msg"</code>
+     * 
+     * <p>Note how the base MIME type varies between this and
+     * {@link #CURSOR_ITEM_BASE_TYPE} depending on whether there is
+     * one single item or multiple items in the data set, while the sub-type
+     * remains the same because in either case the data structure contained
+     * in the cursor is the same.
+     */
+    public static final String CURSOR_DIR_BASE_TYPE = "vnd.android.cursor.dir";
+    
+    public ContentResolver(Context context)
+    {
+        mContext = context;
+    }
+
+    /** @hide */
+    protected abstract IContentProvider acquireProvider(Context c, String name);
+    /** @hide */
+    public abstract boolean releaseProvider(IContentProvider icp);
+
+    /**
+     * Return the MIME type of the given content URL.
+     *
+     * @param url A Uri identifying content (either a list or specific type),
+     * using the content:// scheme.
+     * @return A MIME type for the content, or null if the URL is invalid or the type is unknown
+     */
+    public final String getType(Uri url)
+    {
+        IContentProvider provider = acquireProvider(url);
+        if (provider == null) {
+            return null;
+        }
+        try {
+            return provider.getType(url);
+        } catch (RemoteException e) {
+            return null;
+        } catch (java.lang.Exception e) {
+            return null;
+        } finally {
+            releaseProvider(provider);
+        }
+    }
+
+    /**
+     * Query the given URI, returning a {@link Cursor} over the result set.
+     *
+     * @param uri The URI, using the content:// scheme, for the content to
+     *         retrieve.
+     * @param projection A list of which columns to return. Passing null will
+     *         return all columns, which is discouraged to prevent reading data
+     *         from storage that isn't going to be used.
+     * @param selection A filter declaring which rows to return, formatted as an
+     *         SQL WHERE clause (excluding the WHERE itself). Passing null will
+     *         return all rows for the given URI.
+     * @param selectionArgs You may include ?s in selection, which will be
+     *         replaced by the values from selectionArgs, in the order that they
+     *         appear in the selection. The values will be bound as Strings.
+     * @param sortOrder How to order the rows, formatted as an SQL ORDER BY
+     *         clause (excluding the ORDER BY itself). Passing null will use the
+     *         default sort order, which may be unordered.
+     * @return A Cursor object, which is positioned before the first entry, or null
+     * @see Cursor
+     */
+    public final Cursor query(Uri uri, String[] projection,
+            String selection, String[] selectionArgs, String sortOrder) {
+        IContentProvider provider = acquireProvider(uri);
+        if (provider == null) {
+            return null;
+        }
+        try {
+            Cursor qCursor = provider.query(uri, projection, selection, selectionArgs, sortOrder);
+            if(qCursor == null) {
+                releaseProvider(provider);
+                return null;
+            }
+            //Wrap the cursor object into CursorWrapperInner object
+            return new CursorWrapperInner(qCursor, provider);
+        } catch (RemoteException e) {
+            releaseProvider(provider);
+            return null;
+        } catch(RuntimeException e) {
+            releaseProvider(provider);
+            throw e;
+        }
+    }
+
+    /**
+     * Open a stream on to the content associated with a content URI.  If there
+     * is no data associated with the URI, FileNotFoundException is thrown.
+     *
+     * <h5>Accepts the following URI schemes:</h5>
+     * <ul>
+     * <li>content ({@link #SCHEME_CONTENT})</li>
+     * <li>android.resource ({@link #SCHEME_ANDROID_RESOURCE})</li>
+     * <li>file ({@link #SCHEME_FILE})</li>
+     * </ul>
+     * 
+     * <p>See {@link #openAssetFileDescriptor(Uri, String)} for more information
+     * on these schemes.
+     * 
+     * @param uri The desired URI.
+     * @return InputStream
+     * @throws FileNotFoundException if the provided URI could not be opened.
+     * @see #openAssetFileDescriptor(Uri, String)
+     */
+    public final InputStream openInputStream(Uri uri)
+            throws FileNotFoundException {
+        String scheme = uri.getScheme();
+        if (SCHEME_ANDROID_RESOURCE.equals(scheme)) {
+            // Note: left here to avoid breaking compatibility.  May be removed
+            // with sufficient testing.
+            OpenResourceIdResult r = getResourceId(uri);
+            try {
+                InputStream stream = r.r.openRawResource(r.id);
+                return stream;
+            } catch (Resources.NotFoundException ex) {
+                throw new FileNotFoundException("Resource does not exist: " + uri);
+            }
+        } else if (SCHEME_FILE.equals(scheme)) {
+            // Note: left here to avoid breaking compatibility.  May be removed
+            // with sufficient testing.
+            return new FileInputStream(uri.getPath());
+        } else {
+            AssetFileDescriptor fd = openAssetFileDescriptor(uri, "r");
+            try {
+                return fd != null ? fd.createInputStream() : null;
+            } catch (IOException e) {
+                throw new FileNotFoundException("Unable to create stream");
+            }
+        }
+    }
+
+    /**
+     * Synonym for {@link #openOutputStream(Uri, String)
+     * openOutputStream(uri, "w")}.
+     * @throws FileNotFoundException if the provided URI could not be opened.
+     */
+    public final OutputStream openOutputStream(Uri uri)
+            throws FileNotFoundException {
+        return openOutputStream(uri, "w");
+    }
+
+    /**
+     * Open a stream on to the content associated with a content URI.  If there
+     * is no data associated with the URI, FileNotFoundException is thrown.
+     *
+     * <h5>Accepts the following URI schemes:</h5>
+     * <ul>
+     * <li>content ({@link #SCHEME_CONTENT})</li>
+     * <li>file ({@link #SCHEME_FILE})</li>
+     * </ul>
+     *
+     * <p>See {@link #openAssetFileDescriptor(Uri, String)} for more information
+     * on these schemes.
+     * 
+     * @param uri The desired URI.
+     * @param mode May be "w", "wa", "rw", or "rwt".
+     * @return OutputStream
+     * @throws FileNotFoundException if the provided URI could not be opened.
+     * @see #openAssetFileDescriptor(Uri, String)
+     */
+    public final OutputStream openOutputStream(Uri uri, String mode)
+            throws FileNotFoundException {
+        AssetFileDescriptor fd = openAssetFileDescriptor(uri, mode);
+        try {
+            return fd != null ? fd.createOutputStream() : null;
+        } catch (IOException e) {
+            throw new FileNotFoundException("Unable to create stream");
+        }
+    }
+
+    /**
+     * Open a raw file descriptor to access data under a "content:" URI.  This
+     * is like {@link #openAssetFileDescriptor(Uri, String)}, but uses the
+     * underlying {@link ContentProvider#openFile}
+     * ContentProvider.openFile()} method, so will <em>not</em> work with
+     * providers that return sub-sections of files.  If at all possible,
+     * you should use {@link #openAssetFileDescriptor(Uri, String)}.  You
+     * will receive a FileNotFoundException exception if the provider returns a
+     * sub-section of a file.
+     *
+     * <h5>Accepts the following URI schemes:</h5>
+     * <ul>
+     * <li>content ({@link #SCHEME_CONTENT})</li>
+     * <li>file ({@link #SCHEME_FILE})</li>
+     * </ul>
+     *
+     * <p>See {@link #openAssetFileDescriptor(Uri, String)} for more information
+     * on these schemes.
+     * 
+     * @param uri The desired URI to open.
+     * @param mode The file mode to use, as per {@link ContentProvider#openFile
+     * ContentProvider.openFile}.
+     * @return Returns a new ParcelFileDescriptor pointing to the file.  You
+     * own this descriptor and are responsible for closing it when done.
+     * @throws FileNotFoundException Throws FileNotFoundException of no
+     * file exists under the URI or the mode is invalid.
+     * @see #openAssetFileDescriptor(Uri, String)
+     */
+    public final ParcelFileDescriptor openFileDescriptor(Uri uri,
+            String mode) throws FileNotFoundException {
+        AssetFileDescriptor afd = openAssetFileDescriptor(uri, mode);
+        if (afd == null) {
+            return null;
+        }
+        
+        if (afd.getDeclaredLength() < 0) {
+            // This is a full file!
+            return afd.getParcelFileDescriptor();
+        }
+        
+        // Client can't handle a sub-section of a file, so close what
+        // we got and bail with an exception.
+        try {
+            afd.close();
+        } catch (IOException e) {
+        }
+        
+        throw new FileNotFoundException("Not a whole file");
+    }
+
+    /**
+     * Open a raw file descriptor to access data under a "content:" URI.  This
+     * interacts with the underlying {@link ContentProvider#openAssetFile}
+     * ContentProvider.openAssetFile()} method of the provider associated with the
+     * given URI, to retrieve any file stored there.
+     *
+     * <h5>Accepts the following URI schemes:</h5>
+     * <ul>
+     * <li>content ({@link #SCHEME_CONTENT})</li>
+     * <li>android.resource ({@link #SCHEME_ANDROID_RESOURCE})</li>
+     * <li>file ({@link #SCHEME_FILE})</li>
+     * </ul>
+     * <h5>The android.resource ({@link #SCHEME_ANDROID_RESOURCE}) Scheme</h5>
+     * <p>
+     * A Uri object can be used to reference a resource in an APK file.  The
+     * Uri should be one of the following formats:
+     * <ul>
+     * <li><code>android.resource://package_name/id_number</code><br/>
+     * <code>package_name</code> is your package name as listed in your AndroidManifest.xml.
+     * For example <code>com.example.myapp</code><br/>
+     * <code>id_number</code> is the int form of the ID.<br/>
+     * The easiest way to construct this form is
+     * <pre>Uri uri = Uri.parse("android.resource://com.example.myapp/" + R.raw.my_resource");</pre>
+     * </li>
+     * <li><code>android.resource://package_name/type/name</code><br/>
+     * <code>package_name</code> is your package name as listed in your AndroidManifest.xml.
+     * For example <code>com.example.myapp</code><br/>
+     * <code>type</code> is the string form of the resource type.  For example, <code>raw</code>
+     * or <code>drawable</code>.
+     * <code>name</code> is the string form of the resource name.  That is, whatever the file
+     * name was in your res directory, without the type extension.
+     * The easiest way to construct this form is
+     * <pre>Uri uri = Uri.parse("android.resource://com.example.myapp/raw/my_resource");</pre>
+     * </li>
+     * </ul>
+     *
+     * @param uri The desired URI to open.
+     * @param mode The file mode to use, as per {@link ContentProvider#openAssetFile
+     * ContentProvider.openAssetFile}.
+     * @return Returns a new ParcelFileDescriptor pointing to the file.  You
+     * own this descriptor and are responsible for closing it when done.
+     * @throws FileNotFoundException Throws FileNotFoundException of no
+     * file exists under the URI or the mode is invalid.
+     */
+    public final AssetFileDescriptor openAssetFileDescriptor(Uri uri,
+            String mode) throws FileNotFoundException {
+        String scheme = uri.getScheme();
+        if (SCHEME_ANDROID_RESOURCE.equals(scheme)) {
+            if (!"r".equals(mode)) {
+                throw new FileNotFoundException("Can't write resources: " + uri);
+            }
+            OpenResourceIdResult r = getResourceId(uri);
+            try {
+                return r.r.openRawResourceFd(r.id);
+            } catch (Resources.NotFoundException ex) {
+                throw new FileNotFoundException("Resource does not exist: " + uri);
+            }
+        } else if (SCHEME_FILE.equals(scheme)) {
+            ParcelFileDescriptor pfd = ParcelFileDescriptor.open(
+                    new File(uri.getPath()), modeToMode(uri, mode));
+            return new AssetFileDescriptor(pfd, 0, -1);
+        } else {
+            IContentProvider provider = acquireProvider(uri);
+            if (provider == null) {
+                throw new FileNotFoundException("No content provider: " + uri);
+            }
+            try {
+                AssetFileDescriptor fd = provider.openAssetFile(uri, mode);
+                if(fd == null) {
+                    releaseProvider(provider);
+                    return null;
+                }
+                ParcelFileDescriptor pfd = new ParcelFileDescriptorInner(
+                        fd.getParcelFileDescriptor(), provider);
+                return new AssetFileDescriptor(pfd, fd.getStartOffset(),
+                        fd.getDeclaredLength());
+            } catch (RemoteException e) {
+                releaseProvider(provider);
+                throw new FileNotFoundException("Dead content provider: " + uri);
+            } catch (FileNotFoundException e) {
+                releaseProvider(provider);
+                throw e;
+            } catch (RuntimeException e) {
+                releaseProvider(provider);
+                throw e;
+            }
+        }
+    }
+
+    class OpenResourceIdResult {
+        Resources r;
+        int id;
+    }
+    
+    OpenResourceIdResult getResourceId(Uri uri) throws FileNotFoundException {
+        String authority = uri.getAuthority();
+        Resources r;
+        if (TextUtils.isEmpty(authority)) {
+            throw new FileNotFoundException("No authority: " + uri);
+        } else {
+            try {
+                r = mContext.getPackageManager().getResourcesForApplication(authority);
+            } catch (NameNotFoundException ex) {
+                throw new FileNotFoundException("No package found for authority: " + uri);
+            }
+        }
+        List<String> path = uri.getPathSegments();
+        if (path == null) {
+            throw new FileNotFoundException("No path: " + uri);
+        }
+        int len = path.size();
+        int id;
+        if (len == 1) {
+            try {
+                id = Integer.parseInt(path.get(0));
+            } catch (NumberFormatException e) {
+                throw new FileNotFoundException("Single path segment is not a resource ID: " + uri);
+            }
+        } else if (len == 2) {
+            id = r.getIdentifier(path.get(1), path.get(0), authority);
+        } else {
+            throw new FileNotFoundException("More than two path segments: " + uri);
+        }
+        if (id == 0) {
+            throw new FileNotFoundException("No resource found for: " + uri);
+        }
+        OpenResourceIdResult res = new OpenResourceIdResult();
+        res.r = r;
+        res.id = id;
+        return res;
+    }
+    
+    /** @hide */
+    static public int modeToMode(Uri uri, String mode) throws FileNotFoundException {
+        int modeBits;
+        if ("r".equals(mode)) {
+            modeBits = ParcelFileDescriptor.MODE_READ_ONLY;
+        } else if ("w".equals(mode) || "wt".equals(mode)) {
+            modeBits = ParcelFileDescriptor.MODE_WRITE_ONLY
+                    | ParcelFileDescriptor.MODE_CREATE
+                    | ParcelFileDescriptor.MODE_TRUNCATE;
+        } else if ("wa".equals(mode)) {
+            modeBits = ParcelFileDescriptor.MODE_WRITE_ONLY
+                    | ParcelFileDescriptor.MODE_CREATE
+                    | ParcelFileDescriptor.MODE_APPEND;
+        } else if ("rw".equals(mode)) {
+            modeBits = ParcelFileDescriptor.MODE_READ_WRITE
+                    | ParcelFileDescriptor.MODE_CREATE;
+        } else if ("rwt".equals(mode)) {
+            modeBits = ParcelFileDescriptor.MODE_READ_WRITE
+                    | ParcelFileDescriptor.MODE_CREATE
+                    | ParcelFileDescriptor.MODE_TRUNCATE;
+        } else {
+            throw new FileNotFoundException("Bad mode for " + uri + ": "
+                    + mode);
+        }
+        return modeBits;
+    }
+    
+    /**
+     * Inserts a row into a table at the given URL.
+     *
+     * If the content provider supports transactions the insertion will be atomic.
+     *
+     * @param url The URL of the table to insert into.
+     * @param values The initial values for the newly inserted row. The key is the column name for
+     *               the field. Passing an empty ContentValues will create an empty row.
+     * @return the URL of the newly created row.
+     */
+    public final Uri insert(Uri url, ContentValues values)
+    {
+        IContentProvider provider = acquireProvider(url);
+        if (provider == null) {
+            throw new IllegalArgumentException("Unknown URL " + url);
+        }
+        try {
+            return provider.insert(url, values);
+        } catch (RemoteException e) {
+            return null;
+        } finally {
+            releaseProvider(provider);
+        }
+    }
+
+    /**
+     * Inserts multiple rows into a table at the given URL.
+     *
+     * This function make no guarantees about the atomicity of the insertions.
+     *
+     * @param url The URL of the table to insert into.
+     * @param values The initial values for the newly inserted rows. The key is the column name for
+     *               the field. Passing null will create an empty row.
+     * @return the number of newly created rows.
+     */
+    public final int bulkInsert(Uri url, ContentValues[] values)
+    {
+        IContentProvider provider = acquireProvider(url);
+        if (provider == null) {
+            throw new IllegalArgumentException("Unknown URL " + url);
+        }
+        try {
+            return provider.bulkInsert(url, values);
+        } catch (RemoteException e) {
+            return 0;
+        } finally {
+            releaseProvider(provider);
+        }
+    }
+
+    /**
+     * Deletes row(s) specified by a content URI.
+     *
+     * If the content provider supports transactions, the deletion will be atomic.
+     *
+     * @param url The URL of the row to delete.
+     * @param where A filter to apply to rows before deleting, formatted as an SQL WHERE clause
+                    (excluding the WHERE itself).
+     * @return The number of rows deleted.
+     */
+    public final int delete(Uri url, String where, String[] selectionArgs)
+    {
+        IContentProvider provider = acquireProvider(url);
+        if (provider == null) {
+            throw new IllegalArgumentException("Unknown URL " + url);
+        }
+        try {
+            return provider.delete(url, where, selectionArgs);
+        } catch (RemoteException e) {
+            return -1;
+        } finally {
+            releaseProvider(provider);
+        }
+    }
+
+    /**
+     * Update row(s) in a content URI.
+     *
+     * If the content provider supports transactions the update will be atomic.
+     *
+     * @param uri The URI to modify.
+     * @param values The new field values. The key is the column name for the field.
+                     A null value will remove an existing field value.
+     * @param where A filter to apply to rows before deleting, formatted as an SQL WHERE clause
+                    (excluding the WHERE itself).
+     * @return the URL of the newly created row
+     * @throws NullPointerException if uri or values are null
+     */
+    public final int update(Uri uri, ContentValues values, String where,
+            String[] selectionArgs) {
+        IContentProvider provider = acquireProvider(uri);
+        if (provider == null) {
+            throw new IllegalArgumentException("Unknown URI " + uri);
+        }
+        try {
+            return provider.update(uri, values, where, selectionArgs);
+        } catch (RemoteException e) {
+            return -1;
+        } finally {
+            releaseProvider(provider);
+        }
+    }
+
+    /**
+     * Returns the content provider for the given content URI..
+     *
+     * @param uri The URI to a content provider
+     * @return The ContentProvider for the given URI, or null if no content provider is found.
+     * @hide
+     */
+    public final IContentProvider acquireProvider(Uri uri)
+    {
+        if (!SCHEME_CONTENT.equals(uri.getScheme())) {
+            return null;
+        }
+        String auth = uri.getAuthority();
+        if (auth != null) {
+            return acquireProvider(mContext, uri.getAuthority());
+        }
+        return null;
+    }
+
+    /**
+     * @hide
+     */
+    public final IContentProvider acquireProvider(String name) {
+        if(name == null) {
+            return null;
+        }
+        return acquireProvider(mContext, name);
+    }
+
+    /**
+     * Register an observer class that gets callbacks when data identified by a
+     * given content URI changes.
+     *
+     * @param uri The URI to watch for changes. This can be a specific row URI, or a base URI
+     * for a whole class of content.
+     * @param notifyForDescendents If <code>true</code> changes to URIs beginning with <code>uri</code>
+     * will also cause notifications to be sent. If <code>false</code> only changes to the exact URI
+     * specified by <em>uri</em> will cause notifications to be sent. If true, than any URI values
+     * at or below the specified URI will also trigger a match.
+     * @param observer The object that receives callbacks when changes occur.
+     * @see #unregisterContentObserver
+     */
+    public final void registerContentObserver(Uri uri, boolean notifyForDescendents,
+            ContentObserver observer)
+    {
+        try {
+            ContentServiceNative.getDefault().registerContentObserver(uri, notifyForDescendents,
+                    observer.getContentObserver());
+        } catch (RemoteException e) {
+        }
+    }
+
+    /**
+     * Unregisters a change observer.
+     *
+     * @param observer The previously registered observer that is no longer needed.
+     * @see #registerContentObserver
+     */
+    public final void unregisterContentObserver(ContentObserver observer) {
+        try {
+            IContentObserver contentObserver = observer.releaseContentObserver();
+            if (contentObserver != null) {
+                ContentServiceNative.getDefault().unregisterContentObserver(
+                        contentObserver);
+            }
+        } catch (RemoteException e) {
+        }
+    }
+
+    /**
+     * Notify registered observers that a row was updated.
+     * To register, call {@link #registerContentObserver(android.net.Uri , boolean, android.database.ContentObserver) registerContentObserver()}.
+     * By default, CursorAdapter objects will get this notification.
+     *
+     * @param uri
+     * @param observer The observer that originated the change, may be <code>null</null>
+     */
+    public void notifyChange(Uri uri, ContentObserver observer) {
+        notifyChange(uri, observer, true /* sync to network */);
+    }
+
+    /**
+     * Notify registered observers that a row was updated.
+     * To register, call {@link #registerContentObserver(android.net.Uri , boolean, android.database.ContentObserver) registerContentObserver()}.
+     * By default, CursorAdapter objects will get this notification.
+     *
+     * @param uri
+     * @param observer The observer that originated the change, may be <code>null</null>
+     * @param syncToNetwork If true, attempt to sync the change to the network.
+     */
+    public void notifyChange(Uri uri, ContentObserver observer, boolean syncToNetwork) {
+        try {
+            ContentServiceNative.getDefault().notifyChange(
+                    uri, observer == null ? null : observer.getContentObserver(),
+                    observer != null && observer.deliverSelfNotifications(), syncToNetwork);
+        } catch (RemoteException e) {
+        }
+    }
+
+    /**
+     * Start an asynchronous sync operation. If you want to monitor the progress
+     * of the sync you may register a SyncObserver. Only values of the following
+     * types may be used in the extras bundle:
+     * <ul>
+     * <li>Integer</li>
+     * <li>Long</li>
+     * <li>Boolean</li>
+     * <li>Float</li>
+     * <li>Double</li>
+     * <li>String</li>
+     * </ul>
+     *
+     * @param uri the uri of the provider to sync or null to sync all providers.
+     * @param extras any extras to pass to the SyncAdapter.
+     */
+    public void startSync(Uri uri, Bundle extras) {
+        validateSyncExtrasBundle(extras);
+        try {
+            ContentServiceNative.getDefault().startSync(uri, extras);
+        } catch (RemoteException e) {
+        }
+    }
+
+    /**
+     * Check that only values of the following types are in the Bundle:
+     * <ul>
+     * <li>Integer</li>
+     * <li>Long</li>
+     * <li>Boolean</li>
+     * <li>Float</li>
+     * <li>Double</li>
+     * <li>String</li>
+     * <li>null</li>
+     * </ul>
+     * @param extras the Bundle to check
+     */
+    public static void validateSyncExtrasBundle(Bundle extras) {
+        try {
+            for (String key : extras.keySet()) {
+                Object value = extras.get(key);
+                if (value == null) continue;
+                if (value instanceof Long) continue;
+                if (value instanceof Integer) continue;
+                if (value instanceof Boolean) continue;
+                if (value instanceof Float) continue;
+                if (value instanceof Double) continue;
+                if (value instanceof String) continue;
+                throw new IllegalArgumentException("unexpected value type: "
+                        + value.getClass().getName());
+            }
+        } catch (IllegalArgumentException e) {
+            throw e;
+        } catch (RuntimeException exc) {
+            throw new IllegalArgumentException("error unparceling Bundle", exc);
+        }
+    }
+
+    public void cancelSync(Uri uri) {
+        try {
+            ContentServiceNative.getDefault().cancelSync(uri);
+        } catch (RemoteException e) {
+        }
+    }
+
+    private final class CursorWrapperInner extends CursorWrapper {
+        private IContentProvider mContentProvider;
+        public static final String TAG="CursorWrapperInner";
+        private boolean mCloseFlag = false;
+
+        CursorWrapperInner(Cursor cursor, IContentProvider icp) {
+            super(cursor);
+            mContentProvider = icp;
+        }
+
+        @Override
+        public void close() {
+            super.close();
+            ContentResolver.this.releaseProvider(mContentProvider);
+            mCloseFlag = true;
+        }
+
+        @Override
+        protected void finalize() throws Throwable {
+            try {
+                if(!mCloseFlag) {
+                    ContentResolver.this.releaseProvider(mContentProvider);
+                }
+            } finally {
+                super.finalize();
+            }
+        }
+    }
+
+    private final class ParcelFileDescriptorInner extends ParcelFileDescriptor {
+        private IContentProvider mContentProvider;
+        public static final String TAG="ParcelFileDescriptorInner";
+        private boolean mReleaseProviderFlag = false;
+
+        ParcelFileDescriptorInner(ParcelFileDescriptor pfd, IContentProvider icp) {
+            super(pfd);
+            mContentProvider = icp;
+        }
+
+        @Override
+        public void close() throws IOException {
+            if(!mReleaseProviderFlag) {
+                super.close();
+                ContentResolver.this.releaseProvider(mContentProvider);
+                mReleaseProviderFlag = true;
+            }
+        }
+
+        @Override
+        protected void finalize() throws Throwable {
+            if (!mReleaseProviderFlag) {
+                close();
+            }
+        }
+    }
+
+    private final Context mContext;
+    private static final String TAG = "ContentResolver";
+}
diff --git a/core/java/android/content/ContentService.java b/core/java/android/content/ContentService.java
new file mode 100644
index 0000000..b028868
--- /dev/null
+++ b/core/java/android/content/ContentService.java
@@ -0,0 +1,376 @@
+/*
+ * 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 android.content;
+
+import android.database.IContentObserver;
+import android.database.sqlite.SQLiteException;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.util.Config;
+import android.util.Log;
+import android.Manifest;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+
+/**
+ * {@hide}
+ */
+public final class ContentService extends ContentServiceNative {
+    private static final String TAG = "ContentService";
+    private Context mContext;
+    private boolean mFactoryTest;
+    private final ObserverNode mRootNode = new ObserverNode("");
+    private SyncManager mSyncManager = null;
+    private final Object mSyncManagerLock = new Object();
+
+    private SyncManager getSyncManager() {
+        synchronized(mSyncManagerLock) {
+            try {
+                // Try to create the SyncManager, return null if it fails (e.g. the disk is full).
+                if (mSyncManager == null) mSyncManager = new SyncManager(mContext, mFactoryTest);
+            } catch (SQLiteException e) {
+                Log.e(TAG, "Can't create SyncManager", e);
+            }
+            return mSyncManager;
+        }
+    }
+
+    @Override
+    protected synchronized void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        mContext.enforceCallingOrSelfPermission(Manifest.permission.DUMP,
+                "caller doesn't have the DUMP permission");
+
+        // This makes it so that future permission checks will be in the context of this
+        // process rather than the caller's process. We will restore this before returning.
+        long identityToken = clearCallingIdentity();
+        try {
+            if (mSyncManager == null) {
+                pw.println("No SyncManager created!  (Disk full?)");
+            } else {
+                mSyncManager.dump(fd, pw);
+            }
+        } finally {
+            restoreCallingIdentity(identityToken);
+        }
+    }
+
+    /*package*/ ContentService(Context context, boolean factoryTest) {
+        mContext = context;
+        mFactoryTest = factoryTest;
+        getSyncManager();
+    }
+
+    public void registerContentObserver(Uri uri, boolean notifyForDescendents,
+            IContentObserver observer) {
+        if (observer == null || uri == null) {
+            throw new IllegalArgumentException("You must pass a valid uri and observer");
+        }
+        synchronized (mRootNode) {
+            mRootNode.addObserver(uri, observer, notifyForDescendents);
+            if (Config.LOGV) Log.v(TAG, "Registered observer " + observer + " at " + uri +
+                    " with notifyForDescendents " + notifyForDescendents);
+        }
+    }
+
+    public void unregisterContentObserver(IContentObserver observer) {
+        if (observer == null) {
+            throw new IllegalArgumentException("You must pass a valid observer");
+        }
+        synchronized (mRootNode) {
+            mRootNode.removeObserver(observer);
+            if (Config.LOGV) Log.v(TAG, "Unregistered observer " + observer);
+        }
+    }
+
+    public void notifyChange(Uri uri, IContentObserver observer,
+            boolean observerWantsSelfNotifications, boolean syncToNetwork) {
+        if (Log.isLoggable(TAG, Log.VERBOSE)) {
+            Log.v(TAG, "Notifying update of " + uri + " from observer " + observer
+                    + ", syncToNetwork " + syncToNetwork);
+        }
+        // This makes it so that future permission checks will be in the context of this
+        // process rather than the caller's process. We will restore this before returning.
+        long identityToken = clearCallingIdentity();
+        try {
+            ArrayList<ObserverCall> calls = new ArrayList<ObserverCall>();
+            synchronized (mRootNode) {
+                mRootNode.collectObservers(uri, 0, observer, observerWantsSelfNotifications,
+                        calls);
+            }
+            final int numCalls = calls.size();
+            for (int i=0; i<numCalls; i++) {
+                ObserverCall oc = calls.get(i);
+                try {
+                    oc.mObserver.onChange(oc.mSelfNotify);
+                    if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                        Log.v(TAG, "Notified " + oc.mObserver + " of " + "update at " + uri);
+                    }
+                } catch (RemoteException ex) {
+                    synchronized (mRootNode) {
+                        Log.w(TAG, "Found dead observer, removing");
+                        IBinder binder = oc.mObserver.asBinder();
+                        final ArrayList<ObserverNode.ObserverEntry> list
+                                = oc.mNode.mObservers;
+                        int numList = list.size();
+                        for (int j=0; j<numList; j++) {
+                            ObserverNode.ObserverEntry oe = list.get(j);
+                            if (oe.observer.asBinder() == binder) {
+                                list.remove(j);
+                                j--;
+                                numList--;
+                            }
+                        }
+                    }
+                }
+            }
+            if (syncToNetwork) {
+                SyncManager syncManager = getSyncManager();
+                if (syncManager != null) syncManager.scheduleLocalSync(uri);
+            }
+        } finally {
+            restoreCallingIdentity(identityToken);
+        }
+    }
+
+    /**
+     * Hide this class since it is not part of api,
+     * but current unittest framework requires it to be public
+     * @hide
+     *
+     */
+    public static final class ObserverCall {
+        final ObserverNode mNode;
+        final IContentObserver mObserver;
+        final boolean mSelfNotify;
+
+        ObserverCall(ObserverNode node, IContentObserver observer,
+                boolean selfNotify) {
+            mNode = node;
+            mObserver = observer;
+            mSelfNotify = selfNotify;
+        }
+    }
+
+    public void startSync(Uri url, Bundle extras) {
+        ContentResolver.validateSyncExtrasBundle(extras);
+        // This makes it so that future permission checks will be in the context of this
+        // process rather than the caller's process. We will restore this before returning.
+        long identityToken = clearCallingIdentity();
+        try {
+            SyncManager syncManager = getSyncManager();
+            if (syncManager != null) syncManager.startSync(url, extras);
+        } finally {
+            restoreCallingIdentity(identityToken);
+        }
+    }
+
+    /**
+     * Clear all scheduled sync operations that match the uri and cancel the active sync
+     * if it matches the uri. If the uri is null, clear all scheduled syncs and cancel
+     * the active one, if there is one.
+     * @param uri Filter on the sync operations to cancel, or all if null.
+     */
+    public void cancelSync(Uri uri) {
+        // This makes it so that future permission checks will be in the context of this
+        // process rather than the caller's process. We will restore this before returning.
+        long identityToken = clearCallingIdentity();
+        try {
+            SyncManager syncManager = getSyncManager();
+            if (syncManager != null) {
+                syncManager.clearScheduledSyncOperations(uri);
+                syncManager.cancelActiveSync(uri);
+            }
+        } finally {
+            restoreCallingIdentity(identityToken);
+        }
+    }
+
+    public static IContentService main(Context context, boolean factoryTest) {
+        ContentService service = new ContentService(context, factoryTest);
+        ServiceManager.addService("content", service);
+        return service;
+    }
+
+    /**
+     * Hide this class since it is not part of api,
+     * but current unittest framework requires it to be public
+     * @hide
+     */
+    public static final class ObserverNode {
+        private class ObserverEntry implements IBinder.DeathRecipient {
+            public IContentObserver observer;
+            public boolean notifyForDescendents;
+
+            public ObserverEntry(IContentObserver o, boolean n) {
+                observer = o;
+                notifyForDescendents = n;
+                try {
+                    observer.asBinder().linkToDeath(this, 0);
+                } catch (RemoteException e) {
+                    binderDied();
+                }
+            }
+
+            public void binderDied() {
+                removeObserver(observer);
+            }
+        }
+
+        public static final int INSERT_TYPE = 0;
+        public static final int UPDATE_TYPE = 1;
+        public static final int DELETE_TYPE = 2;
+
+        private String mName;
+        private ArrayList<ObserverNode> mChildren = new ArrayList<ObserverNode>();
+        private ArrayList<ObserverEntry> mObservers = new ArrayList<ObserverEntry>();
+
+        public ObserverNode(String name) {
+            mName = name;
+        }
+
+        private String getUriSegment(Uri uri, int index) {
+            if (uri != null) {
+                if (index == 0) {
+                    return uri.getAuthority();
+                } else {
+                    return uri.getPathSegments().get(index - 1);
+                }
+            } else {
+                return null;
+            }
+        }
+
+        private int countUriSegments(Uri uri) {
+            if (uri == null) {
+                return 0;
+            }
+            return uri.getPathSegments().size() + 1;
+        }
+
+        public void addObserver(Uri uri, IContentObserver observer, boolean notifyForDescendents) {
+            addObserver(uri, 0, observer, notifyForDescendents);
+        }
+
+        private void addObserver(Uri uri, int index, IContentObserver observer,
+                boolean notifyForDescendents) {
+
+            // If this is the leaf node add the observer
+            if (index == countUriSegments(uri)) {
+                mObservers.add(new ObserverEntry(observer, notifyForDescendents));
+                return;
+            }
+
+            // Look to see if the proper child already exists
+            String segment = getUriSegment(uri, index);
+            int N = mChildren.size();
+            for (int i = 0; i < N; i++) {
+                ObserverNode node = mChildren.get(i);
+                if (node.mName.equals(segment)) {
+                    node.addObserver(uri, index + 1, observer, notifyForDescendents);
+                    return;
+                }
+            }
+
+            // No child found, create one
+            ObserverNode node = new ObserverNode(segment);
+            mChildren.add(node);
+            node.addObserver(uri, index + 1, observer, notifyForDescendents);
+        }
+
+        public boolean removeObserver(IContentObserver observer) {
+            int size = mChildren.size();
+            for (int i = 0; i < size; i++) {
+                boolean empty = mChildren.get(i).removeObserver(observer);
+                if (empty) {
+                    mChildren.remove(i);
+                    i--;
+                    size--;
+                }
+            }
+
+            IBinder observerBinder = observer.asBinder();
+            size = mObservers.size();
+            for (int i = 0; i < size; i++) {
+                ObserverEntry entry = mObservers.get(i);
+                if (entry.observer.asBinder() == observerBinder) {
+                    mObservers.remove(i);
+                    // We no longer need to listen for death notifications. Remove it.
+                    observerBinder.unlinkToDeath(entry, 0);
+                    break;
+                }
+            }
+
+            if (mChildren.size() == 0 && mObservers.size() == 0) {
+                return true;
+            }
+            return false;
+        }
+
+        private void collectMyObservers(Uri uri,
+                boolean leaf, IContentObserver observer, boolean selfNotify,
+                ArrayList<ObserverCall> calls)
+        {
+            int N = mObservers.size();
+            IBinder observerBinder = observer == null ? null : observer.asBinder();
+            for (int i = 0; i < N; i++) {
+                ObserverEntry entry = mObservers.get(i);
+
+                // Don't notify the observer if it sent the notification and isn't interesed
+                // in self notifications
+                if (entry.observer.asBinder() == observerBinder && !selfNotify) {
+                    continue;
+                }
+
+                // Make sure the observer is interested in the notification
+                if (leaf || (!leaf && entry.notifyForDescendents)) {
+                    calls.add(new ObserverCall(this, entry.observer, selfNotify));
+                }
+            }
+        }
+
+        public void collectObservers(Uri uri, int index, IContentObserver observer,
+                boolean selfNotify, ArrayList<ObserverCall> calls) {
+            String segment = null;
+            int segmentCount = countUriSegments(uri);
+            if (index >= segmentCount) {
+                // This is the leaf node, notify all observers
+                collectMyObservers(uri, true, observer, selfNotify, calls);
+            } else if (index < segmentCount){
+                segment = getUriSegment(uri, index);
+                // Notify any observers at this level who are interested in descendents
+                collectMyObservers(uri, false, observer, selfNotify, calls);
+            }
+
+            int N = mChildren.size();
+            for (int i = 0; i < N; i++) {
+                ObserverNode node = mChildren.get(i);
+                if (segment == null || node.mName.equals(segment)) {
+                    // We found the child,
+                    node.collectObservers(uri, index + 1, observer, selfNotify, calls);
+                    if (segment != null) {
+                        break;
+                    }
+                }
+            }
+        }
+    }
+}
diff --git a/core/java/android/content/ContentServiceNative.java b/core/java/android/content/ContentServiceNative.java
new file mode 100644
index 0000000..364f9ee
--- /dev/null
+++ b/core/java/android/content/ContentServiceNative.java
@@ -0,0 +1,216 @@
+/*
+ * 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 android.content;
+
+import android.database.IContentObserver;
+import android.net.Uri;
+import android.os.Binder;
+import android.os.RemoteException;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.ServiceManager;
+import android.os.Bundle;
+import android.util.Config;
+import android.util.Log;
+
+/**
+ * {@hide}
+ */
+abstract class ContentServiceNative extends Binder implements IContentService
+{
+    public ContentServiceNative()
+    {
+        attachInterface(this, descriptor);
+    }
+
+    /**
+     * Cast a Binder object into a content resolver interface, generating
+     * a proxy if needed.
+     */
+    static public IContentService asInterface(IBinder obj)
+    {
+        if (obj == null) {
+            return null;
+        }
+        IContentService in =
+            (IContentService)obj.queryLocalInterface(descriptor);
+        if (in != null) {
+            return in;
+        }
+
+        return new ContentServiceProxy(obj);
+    }
+
+    /**
+     * Retrieve the system's default/global content service.
+     */
+    static public IContentService getDefault()
+    {
+        if (gDefault != null) {
+            return gDefault;
+        }
+        IBinder b = ServiceManager.getService("content");
+        if (Config.LOGV) Log.v("ContentService", "default service binder = " + b);
+        gDefault = asInterface(b);
+        if (Config.LOGV) Log.v("ContentService", "default service = " + gDefault);
+        return gDefault;
+    }
+
+    @Override
+    public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
+    {
+        try {
+        switch (code) {
+            case 5038: {
+                data.readString(); // ignore the interface token that service generated
+                Uri uri = Uri.parse(data.readString());
+                notifyChange(uri, null, false, false);
+                return true;
+            }
+            
+            case REGISTER_CONTENT_OBSERVER_TRANSACTION: {
+                Uri uri = Uri.CREATOR.createFromParcel(data);
+                boolean notifyForDescendents = data.readInt() != 0;
+                IContentObserver observer = IContentObserver.Stub.asInterface(data.readStrongBinder());
+                registerContentObserver(uri, notifyForDescendents, observer);
+                return true;
+            }
+
+            case UNREGISTER_CHANGE_OBSERVER_TRANSACTION: {
+                IContentObserver observer = IContentObserver.Stub.asInterface(data.readStrongBinder());
+                unregisterContentObserver(observer);
+                return true;
+            }
+
+            case NOTIFY_CHANGE_TRANSACTION: {
+                Uri uri = Uri.CREATOR.createFromParcel(data);
+                IContentObserver observer = IContentObserver.Stub.asInterface(data.readStrongBinder());
+                boolean observerWantsSelfNotifications = data.readInt() != 0;
+                boolean syncToNetwork = data.readInt() != 0;
+                notifyChange(uri, observer, observerWantsSelfNotifications, syncToNetwork);
+                return true;
+            }
+
+            case START_SYNC_TRANSACTION: {
+                Uri url = null;
+                int hasUrl = data.readInt();
+                if (hasUrl != 0) {
+                    url = Uri.CREATOR.createFromParcel(data);
+                }
+                startSync(url, data.readBundle());
+                return true;
+            }
+
+            case CANCEL_SYNC_TRANSACTION: {
+                Uri url = null;
+                int hasUrl = data.readInt();
+                if (hasUrl != 0) {
+                    url = Uri.CREATOR.createFromParcel(data);
+                }
+                cancelSync(url);
+                return true;
+            }
+
+            default:
+                return super.onTransact(code, data, reply, flags);
+        }
+        } catch (Exception e) {
+            Log.e("ContentServiceNative", "Caught exception in transact", e);
+        }
+
+        return false;
+    }
+
+    public IBinder asBinder()
+    {
+        return this;
+    }
+
+    private static IContentService gDefault;
+}
+
+
+final class ContentServiceProxy implements IContentService
+{
+    public ContentServiceProxy(IBinder remote)
+    {
+        mRemote = remote;
+    }
+
+    public IBinder asBinder()
+    {
+        return mRemote;
+    }
+
+    public void registerContentObserver(Uri uri, boolean notifyForDescendents,
+            IContentObserver observer) throws RemoteException
+    {
+        Parcel data = Parcel.obtain();
+        uri.writeToParcel(data, 0);
+        data.writeInt(notifyForDescendents ? 1 : 0);
+        data.writeStrongInterface(observer);
+        mRemote.transact(REGISTER_CONTENT_OBSERVER_TRANSACTION, data, null, 0);
+        data.recycle();
+    }
+
+    public void unregisterContentObserver(IContentObserver observer) throws RemoteException {
+        Parcel data = Parcel.obtain();
+        data.writeStrongInterface(observer);
+        mRemote.transact(UNREGISTER_CHANGE_OBSERVER_TRANSACTION, data, null, 0);
+        data.recycle();
+    }
+
+    public void notifyChange(Uri uri, IContentObserver observer,
+            boolean observerWantsSelfNotifications, boolean syncToNetwork)
+            throws RemoteException {
+        Parcel data = Parcel.obtain();
+        uri.writeToParcel(data, 0);
+        data.writeStrongInterface(observer);
+        data.writeInt(observerWantsSelfNotifications ? 1 : 0);
+        data.writeInt(syncToNetwork ? 1 : 0);
+        mRemote.transact(NOTIFY_CHANGE_TRANSACTION, data, null, IBinder.FLAG_ONEWAY);
+        data.recycle();
+    }
+
+    public void startSync(Uri url, Bundle extras) throws RemoteException {
+        Parcel data = Parcel.obtain();
+        if (url == null) {
+            data.writeInt(0);
+        } else {
+            data.writeInt(1);
+            url.writeToParcel(data, 0);
+        }
+        extras.writeToParcel(data, 0);
+        mRemote.transact(START_SYNC_TRANSACTION, data, null, IBinder.FLAG_ONEWAY);
+        data.recycle();
+    }
+
+    public void cancelSync(Uri url) throws RemoteException {
+        Parcel data = Parcel.obtain();
+        if (url == null) {
+            data.writeInt(0);
+        } else {
+            data.writeInt(1);
+            url.writeToParcel(data, 0);
+        }
+        mRemote.transact(CANCEL_SYNC_TRANSACTION, data, null /* reply */, IBinder.FLAG_ONEWAY);
+        data.recycle();
+    }
+
+    private IBinder mRemote;
+}
+
diff --git a/core/java/android/content/ContentUris.java b/core/java/android/content/ContentUris.java
new file mode 100644
index 0000000..aa7603470b
--- /dev/null
+++ b/core/java/android/content/ContentUris.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2007 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 android.content;
+
+import android.net.Uri;
+
+/**
+ * Utility methods useful for working with content {@link android.net.Uri}s,
+ * those with a "content" scheme.
+ */
+public class ContentUris {
+
+    /**
+     * Converts the last path segment to a long.
+     *
+     * <p>This supports a common convention for content URIs where an ID is
+     * stored in the last segment.
+     *
+     * @throws UnsupportedOperationException if this isn't a hierarchical URI
+     * @throws NumberFormatException if the last segment isn't a number
+     *
+     * @return the long conversion of the last segment or -1 if the path is
+     *  empty
+     */
+    public static long parseId(Uri contentUri) {
+        String last = contentUri.getLastPathSegment();
+        return last == null ? -1 : Long.parseLong(last);
+    }
+
+    /**
+     * Appends the given ID to the end of the path.
+     *
+     * @param builder to append the ID to
+     * @param id to append
+     *
+     * @return the given builder
+     */
+    public static Uri.Builder appendId(Uri.Builder builder, long id) {
+        return builder.appendEncodedPath(String.valueOf(id));
+    }
+
+    /**
+     * Appends the given ID to the end of the path.
+     *
+     * @param contentUri to start with
+     * @param id to append
+     *
+     * @return a new URI with the given ID appended to the end of the path
+     */
+    public static Uri withAppendedId(Uri contentUri, long id) {
+        return appendId(contentUri.buildUpon(), id).build();
+    }
+}
diff --git a/core/java/android/content/ContentValues.java b/core/java/android/content/ContentValues.java
new file mode 100644
index 0000000..532cc03
--- /dev/null
+++ b/core/java/android/content/ContentValues.java
@@ -0,0 +1,501 @@
+/*
+ * Copyright (C) 2007 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 android.content;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * This class is used to store a set of values that the {@link ContentResolver}
+ * can process.
+ */
+public final class ContentValues implements Parcelable {
+    public static final String TAG = "ContentValues";
+
+    /** Holds the actual values */
+    private HashMap<String, Object> mValues;
+
+    /**
+     * Creates an empty set of values using the default initial size
+     */
+    public ContentValues() {
+        // Choosing a default size of 8 based on analysis of typical 
+        // consumption by applications.
+        mValues = new HashMap<String, Object>(8);
+    }
+
+    /**
+     * Creates an empty set of values using the given initial size
+     * 
+     * @param size the initial size of the set of values
+     */
+    public ContentValues(int size) {
+        mValues = new HashMap<String, Object>(size, 1.0f);
+    }
+
+    /**
+     * Creates a set of values copied from the given set
+     * 
+     * @param from the values to copy
+     */
+    public ContentValues(ContentValues from) {
+        mValues = new HashMap<String, Object>(from.mValues);
+    }
+
+    /**
+     * Creates a set of values copied from the given HashMap. This is used
+     * by the Parcel unmarshalling code.
+     * 
+     * @param from the values to start with
+     * {@hide}
+     */
+    private ContentValues(HashMap<String, Object> values) {
+        mValues = values;
+    }
+
+    @Override
+    public boolean equals(Object object) {
+        if (!(object instanceof ContentValues)) {
+            return false;
+        }
+        return mValues.equals(((ContentValues) object).mValues);
+    }
+
+    @Override
+    public int hashCode() {
+        return mValues.hashCode();
+    }
+
+    /**
+     * Adds a value to the set.
+     *
+     * @param key the name of the value to put
+     * @param value the data for the value to put
+     */
+    public void put(String key, String value) {
+        mValues.put(key, value);
+    }
+
+    /**
+     * Adds all values from the passed in ContentValues.
+     *
+     * @param other the ContentValues from which to copy
+     */
+    public void putAll(ContentValues other) {
+        mValues.putAll(other.mValues);
+    }
+
+    /**
+     * Adds a value to the set.
+     *
+     * @param key the name of the value to put
+     * @param value the data for the value to put
+     */
+    public void put(String key, Byte value) {
+        mValues.put(key, value);
+    }
+
+    /**
+     * Adds a value to the set.
+     *
+     * @param key the name of the value to put
+     * @param value the data for the value to put
+     */
+    public void put(String key, Short value) {
+        mValues.put(key, value);
+    }
+
+    /**
+     * Adds a value to the set.
+     *
+     * @param key the name of the value to put
+     * @param value the data for the value to put
+     */
+    public void put(String key, Integer value) {
+        mValues.put(key, value);
+    }
+
+    /**
+     * Adds a value to the set.
+     *
+     * @param key the name of the value to put
+     * @param value the data for the value to put
+     */
+    public void put(String key, Long value) {
+        mValues.put(key, value);
+    }
+
+    /**
+     * Adds a value to the set.
+     *
+     * @param key the name of the value to put
+     * @param value the data for the value to put
+     */
+    public void put(String key, Float value) {
+        mValues.put(key, value);
+    }
+
+    /**
+     * Adds a value to the set.
+     *
+     * @param key the name of the value to put
+     * @param value the data for the value to put
+     */
+    public void put(String key, Double value) {
+        mValues.put(key, value);
+    }
+
+    /**
+     * Adds a value to the set.
+     *
+     * @param key the name of the value to put
+     * @param value the data for the value to put
+     */
+    public void put(String key, Boolean value) {
+        mValues.put(key, value);
+    }
+    
+    /**
+     * Adds a value to the set.
+     *
+     * @param key the name of the value to put
+     * @param value the data for the value to put
+     */
+    public void put(String key, byte[] value) {
+        mValues.put(key, value);
+    }
+
+    /**
+     * Adds a null value to the set.
+     * 
+     * @param key the name of the value to make null
+     */
+    public void putNull(String key) {
+        mValues.put(key, null);
+    }
+
+    /**
+     * Returns the number of values.
+     * 
+     * @return the number of values
+     */
+    public int size() {
+        return mValues.size();
+    }
+
+    /**
+     * Remove a single value.
+     *
+     * @param key the name of the value to remove
+     */
+    public void remove(String key) {
+        mValues.remove(key);
+    }
+
+    /**
+     * Removes all values.
+     */
+    public void clear() {
+        mValues.clear();
+    }
+
+    /**
+     * Returns true if this object has the named value.
+     *
+     * @param key the value to check for
+     * @return {@code true} if the value is present, {@code false} otherwise 
+     */
+    public boolean containsKey(String key) {
+        return mValues.containsKey(key);
+    }
+
+    /**
+     * Gets a value. Valid value types are {@link String}, {@link Boolean}, and
+     * {@link Number} implementations.
+     *
+     * @param key the value to get
+     * @return the data for the value
+     */
+    public Object get(String key) {
+        return mValues.get(key);
+    }
+
+    /**
+     * Gets a value and converts it to a String.
+     * 
+     * @param key the value to get
+     * @return the String for the value
+     */
+    public String getAsString(String key) {
+        Object value = mValues.get(key);
+        return value != null ? mValues.get(key).toString() : null;
+    }
+
+    /**
+     * Gets a value and converts it to a Long.
+     * 
+     * @param key the value to get
+     * @return the Long value, or null if the value is missing or cannot be converted
+     */
+    public Long getAsLong(String key) {
+        Object value = mValues.get(key);
+        try {
+            return value != null ? ((Number) value).longValue() : null;
+        } catch (ClassCastException e) {
+            if (value instanceof CharSequence) {
+                try {
+                    return Long.valueOf(value.toString());
+                } catch (NumberFormatException e2) {
+                    Log.e(TAG, "Cannot parse Long value for " + value + " at key " + key);
+                    return null;
+                }
+            } else {
+                Log.e(TAG, "Cannot cast value for " + key + " to a Long");
+                return null;
+            }
+        }
+    }
+
+    /**
+     * Gets a value and converts it to an Integer.
+     * 
+     * @param key the value to get
+     * @return the Integer value, or null if the value is missing or cannot be converted
+     */
+    public Integer getAsInteger(String key) {
+        Object value = mValues.get(key);
+        try {
+            return value != null ? ((Number) value).intValue() : null;
+        } catch (ClassCastException e) {
+            if (value instanceof CharSequence) {
+                try {
+                    return Integer.valueOf(value.toString());
+                } catch (NumberFormatException e2) {
+                    Log.e(TAG, "Cannot parse Integer value for " + value + " at key " + key);
+                    return null;
+                }
+            } else {
+                Log.e(TAG, "Cannot cast value for " + key + " to a Integer");
+                return null;
+            }
+        }
+    }
+
+    /**
+     * Gets a value and converts it to a Short.
+     * 
+     * @param key the value to get
+     * @return the Short value, or null if the value is missing or cannot be converted
+     */
+    public Short getAsShort(String key) {
+        Object value = mValues.get(key);
+        try {
+            return value != null ? ((Number) value).shortValue() : null;
+        } catch (ClassCastException e) {
+            if (value instanceof CharSequence) {
+                try {
+                    return Short.valueOf(value.toString());
+                } catch (NumberFormatException e2) {
+                    Log.e(TAG, "Cannot parse Short value for " + value + " at key " + key);
+                    return null;
+                }
+            } else {
+                Log.e(TAG, "Cannot cast value for " + key + " to a Short");
+                return null;
+            }
+        }
+    }
+
+    /**
+     * Gets a value and converts it to a Byte.
+     * 
+     * @param key the value to get
+     * @return the Byte value, or null if the value is missing or cannot be converted
+     */
+    public Byte getAsByte(String key) {
+        Object value = mValues.get(key);
+        try {
+            return value != null ? ((Number) value).byteValue() : null;
+        } catch (ClassCastException e) {
+            if (value instanceof CharSequence) {
+                try {
+                    return Byte.valueOf(value.toString());
+                } catch (NumberFormatException e2) {
+                    Log.e(TAG, "Cannot parse Byte value for " + value + " at key " + key);
+                    return null;
+                }
+            } else {
+                Log.e(TAG, "Cannot cast value for " + key + " to a Byte");
+                return null;
+            }
+        }
+    }
+
+    /**
+     * Gets a value and converts it to a Double.
+     * 
+     * @param key the value to get
+     * @return the Double value, or null if the value is missing or cannot be converted
+     */
+    public Double getAsDouble(String key) {
+        Object value = mValues.get(key);
+        try {
+            return value != null ? ((Number) value).doubleValue() : null;
+        } catch (ClassCastException e) {
+            if (value instanceof CharSequence) {
+                try {
+                    return Double.valueOf(value.toString());
+                } catch (NumberFormatException e2) {
+                    Log.e(TAG, "Cannot parse Double value for " + value + " at key " + key);
+                    return null;
+                }
+            } else {
+                Log.e(TAG, "Cannot cast value for " + key + " to a Double");
+                return null;
+            }
+        }
+    }
+
+    /**
+     * Gets a value and converts it to a Float.
+     * 
+     * @param key the value to get
+     * @return the Float value, or null if the value is missing or cannot be converted
+     */
+    public Float getAsFloat(String key) {
+        Object value = mValues.get(key);
+        try {
+            return value != null ? ((Number) value).floatValue() : null;
+        } catch (ClassCastException e) {
+            if (value instanceof CharSequence) {
+                try {
+                    return Float.valueOf(value.toString());
+                } catch (NumberFormatException e2) {
+                    Log.e(TAG, "Cannot parse Float value for " + value + " at key " + key);
+                    return null;
+                }
+            } else {
+                Log.e(TAG, "Cannot cast value for " + key + " to a Float");
+                return null;
+            }
+        }
+    }
+
+    /**
+     * Gets a value and converts it to a Boolean.
+     * 
+     * @param key the value to get
+     * @return the Boolean value, or null if the value is missing or cannot be converted
+     */
+    public Boolean getAsBoolean(String key) {
+        Object value = mValues.get(key);
+        try {
+            return (Boolean) value;
+        } catch (ClassCastException e) {
+            if (value instanceof CharSequence) {
+                return Boolean.valueOf(value.toString());
+            } else {
+                Log.e(TAG, "Cannot cast value for " + key + " to a Boolean");
+                return null;
+            }
+        }
+    }
+
+    /**
+     * Gets a value that is a byte array. Note that this method will not convert
+     * any other types to byte arrays.
+     * 
+     * @param key the value to get
+     * @return the byte[] value, or null is the value is missing or not a byte[]
+     */
+    public byte[] getAsByteArray(String key) {
+        Object value = mValues.get(key);
+        if (value instanceof byte[]) {
+            return (byte[]) value;
+        } else {
+            return null;
+        }
+    }
+
+    /**
+     * Returns a set of all of the keys and values
+     * 
+     * @return a set of all of the keys and values
+     */
+    public Set<Map.Entry<String, Object>> valueSet() {
+        return mValues.entrySet();
+    }
+    
+    public static final Parcelable.Creator<ContentValues> CREATOR =
+            new Parcelable.Creator<ContentValues>() {
+        @SuppressWarnings({"deprecation", "unchecked"})
+        public ContentValues createFromParcel(Parcel in) {
+            // TODO - what ClassLoader should be passed to readHashMap?
+            HashMap<String, Object> values = in.readHashMap(null);
+            return new ContentValues(values);
+        }
+
+        public ContentValues[] newArray(int size) {
+            return new ContentValues[size];
+        }
+    };
+
+    public int describeContents() {
+        return 0;
+    }
+
+    @SuppressWarnings("deprecation")
+    public void writeToParcel(Parcel parcel, int flags) {
+        parcel.writeMap(mValues);
+    }
+
+    /**
+     * Unsupported, here until we get proper bulk insert APIs.
+     * {@hide}
+     */
+    @Deprecated
+    public void putStringArrayList(String key, ArrayList<String> value) {
+        mValues.put(key, value);
+    }
+
+    /**
+     * Unsupported, here until we get proper bulk insert APIs.
+     * {@hide}
+     */
+    @SuppressWarnings("unchecked")
+    @Deprecated
+    public ArrayList<String> getStringArrayList(String key) {
+        return (ArrayList<String>) mValues.get(key);
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder sb = new StringBuilder();
+        for (String name : mValues.keySet()) {
+            String value = getAsString(name);
+            if (sb.length() > 0) sb.append(" ");
+            sb.append(name + "=" + value);
+        }
+        return sb.toString();
+    }
+}
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
new file mode 100644
index 0000000..e0fe533
--- /dev/null
+++ b/core/java/android/content/Context.java
@@ -0,0 +1,1654 @@
+/*
+ * 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 android.content;
+
+import android.content.pm.PackageManager;
+import android.content.res.AssetManager;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteDatabase.CursorFactory;
+import android.graphics.Bitmap;
+import android.graphics.drawable.Drawable;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import android.util.AttributeSet;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * Interface to global information about an application environment.  This is
+ * an abstract class whose implementation is provided by
+ * the Android system.  It
+ * allows access to application-specific resources and classes, as well as
+ * up-calls for application-level operations such as launching activities,
+ * broadcasting and receiving intents, etc.
+ */
+public abstract class Context {
+    /**
+     * File creation mode: the default mode, where the created file can only
+     * be accessed by the calling application (or all applications sharing the
+     * same user ID).
+     * @see #MODE_WORLD_READABLE
+     * @see #MODE_WORLD_WRITEABLE
+     */
+    public static final int MODE_PRIVATE = 0x0000;
+    /**
+     * File creation mode: allow all other applications to have read access
+     * to the created file.
+     * @see #MODE_PRIVATE
+     * @see #MODE_WORLD_WRITEABLE
+     */
+    public static final int MODE_WORLD_READABLE = 0x0001;
+    /**
+     * File creation mode: allow all other applications to have write access
+     * to the created file.
+     * @see #MODE_PRIVATE
+     * @see #MODE_WORLD_READABLE
+     */
+    public static final int MODE_WORLD_WRITEABLE = 0x0002;
+    /**
+     * File creation mode: for use with {@link #openFileOutput}, if the file
+     * already exists then write data to the end of the existing file
+     * instead of erasing it.
+     * @see #openFileOutput
+     */
+    public static final int MODE_APPEND = 0x8000;
+
+    /**
+     * Flag for {@link #bindService}: automatically create the service as long
+     * as the binding exists.  Note that while this will create the service,
+     * its {@link android.app.Service#onStart} method will still only be called due to an
+     * explicit call to {@link #startService}.  Even without that, though,
+     * this still provides you with access to the service object while the
+     * service is created.
+     *
+     * <p>Specifying this flag also tells the system to treat the service
+     * as being as important as your own process -- that is, when deciding
+     * which process should be killed to free memory, the service will only
+     * be considered a candidate as long as the processes of any such bindings
+     * is also a candidate to be killed.  This is to avoid situations where
+     * the service is being continually created and killed due to low memory.
+     */
+    public static final int BIND_AUTO_CREATE = 0x0001;
+
+    /**
+     * Flag for {@link #bindService}: include debugging help for mismatched
+     * calls to unbind.  When this flag is set, the callstack of the following
+     * {@link #unbindService} call is retained, to be printed if a later
+     * incorrect unbind call is made.  Note that doing this requires retaining
+     * information about the binding that was made for the lifetime of the app,
+     * resulting in a leak -- this should only be used for debugging.
+     */
+    public static final int BIND_DEBUG_UNBIND = 0x0002;
+
+    /** Return an AssetManager instance for your application's package. */
+    public abstract AssetManager getAssets();
+
+    /** Return a Resources instance for your application's package. */
+    public abstract Resources getResources();
+
+    /** Return PackageManager instance to find global package information. */
+    public abstract PackageManager getPackageManager();
+
+    /** Return a ContentResolver instance for your application's package. */
+    public abstract ContentResolver getContentResolver();
+
+    /**
+     * Return the Looper for the main thread of the current process.  This is
+     * the thread used to dispatch calls to application components (activities,
+     * services, etc).
+     */
+    public abstract Looper getMainLooper();
+    
+    /**
+     * Return the context of the single, global Application object of the
+     * current process.
+     */
+    public abstract Context getApplicationContext();
+
+    /**
+     * Return a localized, styled CharSequence from the application's package's
+     * default string table.
+     *
+     * @param resId Resource id for the CharSequence text
+     */
+    public final CharSequence getText(int resId) {
+        return getResources().getText(resId);
+    }
+
+    /**
+     * Return a localized string from the application's package's
+     * default string table.
+     *
+     * @param resId Resource id for the string
+     */
+    public final String getString(int resId) {
+        return getResources().getString(resId);
+    }
+
+    /**
+     * Return a localized formatted string from the application's package's
+     * default string table, substituting the format arguments as defined in
+     * {@link java.util.Formatter} and {@link java.lang.String#format}.
+     *
+     * @param resId Resource id for the format string
+     * @param formatArgs The format arguments that will be used for substitution.
+     */
+
+    public final String getString(int resId, Object... formatArgs) {
+        return getResources().getString(resId, formatArgs);
+    }
+
+     /**
+     * Set the base theme for this context.  Note that this should be called
+     * before any views are instantiated in the Context (for example before
+     * calling {@link android.app.Activity#setContentView} or
+     * {@link android.view.LayoutInflater#inflate}).
+     *
+     * @param resid The style resource describing the theme.
+     */
+    public abstract void setTheme(int resid);
+
+    /**
+     * Return the Theme object associated with this Context.
+     */
+    public abstract Resources.Theme getTheme();
+
+    /**
+     * Retrieve styled attribute information in this Context's theme.  See
+     * {@link Resources.Theme#obtainStyledAttributes(int[])}
+     * for more information.
+     *
+     * @see Resources.Theme#obtainStyledAttributes(int[])
+     */
+    public final TypedArray obtainStyledAttributes(
+            int[] attrs) {
+        return getTheme().obtainStyledAttributes(attrs);
+    }
+
+    /**
+     * Retrieve styled attribute information in this Context's theme.  See
+     * {@link Resources.Theme#obtainStyledAttributes(int, int[])}
+     * for more information.
+     *
+     * @see Resources.Theme#obtainStyledAttributes(int, int[])
+     */
+    public final TypedArray obtainStyledAttributes(
+            int resid, int[] attrs) throws Resources.NotFoundException {
+        return getTheme().obtainStyledAttributes(resid, attrs);
+    }
+
+    /**
+     * Retrieve styled attribute information in this Context's theme.  See
+     * {@link Resources.Theme#obtainStyledAttributes(AttributeSet, int[], int, int)}
+     * for more information.
+     *
+     * @see Resources.Theme#obtainStyledAttributes(AttributeSet, int[], int, int)
+     */
+    public final TypedArray obtainStyledAttributes(
+            AttributeSet set, int[] attrs) {
+        return getTheme().obtainStyledAttributes(set, attrs, 0, 0);
+    }
+
+    /**
+     * Retrieve styled attribute information in this Context's theme.  See
+     * {@link Resources.Theme#obtainStyledAttributes(AttributeSet, int[], int, int)}
+     * for more information.
+     *
+     * @see Resources.Theme#obtainStyledAttributes(AttributeSet, int[], int, int)
+     */
+    public final TypedArray obtainStyledAttributes(
+            AttributeSet set, int[] attrs, int defStyleAttr, int defStyleRes) {
+        return getTheme().obtainStyledAttributes(
+            set, attrs, defStyleAttr, defStyleRes);
+    }
+
+    /**
+     * Return a class loader you can use to retrieve classes in this package.
+     */
+    public abstract ClassLoader getClassLoader();
+
+    /** Return the name of this application's package. */
+    public abstract String getPackageName();
+
+    /**
+     * {@hide}
+     * Return the full path to this context's resource files.  This is the ZIP files
+     * containing the application's resources.
+     *
+     * <p>Note: this is not generally useful for applications, since they should
+     * not be directly accessing the file system.
+     *
+     *
+     * @return String Path to the resources.
+     */
+    public abstract String getPackageResourcePath();
+
+    /**
+     * {@hide}
+     * Return the full path to this context's code and asset files.  This is the ZIP files
+     * containing the application's code and assets.
+     *
+     * <p>Note: this is not generally useful for applications, since they should
+     * not be directly accessing the file system.
+     *
+     *
+     * @return String Path to the code and assets.
+     */
+    public abstract String getPackageCodePath();
+
+    /**
+     * Retrieve and hold the contents of the preferences file 'name', returning
+     * a SharedPreferences through which you can retrieve and modify its
+     * values.  Only one instance of the SharedPreferences object is returned
+     * to any callers for the same name, meaning they will see each other's
+     * edits as soon as they are made.
+     *
+     * @param name Desired preferences file. If a preferences file by this name
+     * does not exist, it will be created when you retrieve an
+     * editor (SharedPreferences.edit()) and then commit changes (Editor.commit()).
+     * @param mode Operating mode.  Use 0 or {@link #MODE_PRIVATE} for the
+     * default operation, {@link #MODE_WORLD_READABLE}
+     * and {@link #MODE_WORLD_WRITEABLE} to control permissions.
+     *
+     * @return Returns the single SharedPreferences instance that can be used
+     *         to retrieve and modify the preference values.
+     *
+     * @see #MODE_PRIVATE
+     * @see #MODE_WORLD_READABLE
+     * @see #MODE_WORLD_WRITEABLE
+     */
+    public abstract SharedPreferences getSharedPreferences(String name,
+            int mode);
+
+    /**
+     * Open a private file associated with this Context's application package
+     * for reading.
+     *
+     * @param name The name of the file to open; can not contain path
+     *             separators.
+     *
+     * @return FileInputStream Resulting input stream.
+     *
+     * @see #openFileOutput
+     * @see #fileList
+     * @see #deleteFile
+     * @see java.io.FileInputStream#FileInputStream(String)
+     */
+    public abstract FileInputStream openFileInput(String name)
+        throws FileNotFoundException;
+
+    /**
+     * Open a private file associated with this Context's application package
+     * for writing.  Creates the file if it doesn't already exist.
+     *
+     * @param name The name of the file to open; can not contain path
+     *             separators.
+     * @param mode Operating mode.  Use 0 or {@link #MODE_PRIVATE} for the
+     * default operation, {@link #MODE_APPEND} to append to an existing file,
+     * {@link #MODE_WORLD_READABLE} and {@link #MODE_WORLD_WRITEABLE} to control
+     * permissions.
+     *
+     * @return FileOutputStream Resulting output stream.
+     *
+     * @see #MODE_APPEND
+     * @see #MODE_PRIVATE
+     * @see #MODE_WORLD_READABLE
+     * @see #MODE_WORLD_WRITEABLE
+     * @see #openFileInput
+     * @see #fileList
+     * @see #deleteFile
+     * @see java.io.FileOutputStream#FileOutputStream(String)
+     */
+    public abstract FileOutputStream openFileOutput(String name, int mode)
+        throws FileNotFoundException;
+
+    /**
+     * Delete the given private file associated with this Context's
+     * application package.
+     *
+     * @param name The name of the file to delete; can not contain path
+     *             separators.
+     *
+     * @return True if the file was successfully deleted; else
+     *         false.
+     *
+     * @see #openFileInput
+     * @see #openFileOutput
+     * @see #fileList
+     * @see java.io.File#delete()
+     */
+    public abstract boolean deleteFile(String name);
+
+    /**
+     * Returns the absolute path on the filesystem where a file created with
+     * {@link #openFileOutput} is stored.
+     *
+     * @param name The name of the file for which you would like to get
+     *          its path.
+     *
+     * @return Returns an absolute path to the given file.
+     *
+     * @see #openFileOutput
+     * @see #getFilesDir
+     * @see #getDir
+     */
+    public abstract File getFileStreamPath(String name);
+
+    /**
+     * Returns the absolute path to the directory on the filesystem where
+     * files created with {@link #openFileOutput} are stored.
+     *
+     * @return Returns the path of the directory holding application files.
+     *
+     * @see #openFileOutput
+     * @see #getFileStreamPath
+     * @see #getDir
+     */
+    public abstract File getFilesDir();
+    
+    /**
+     * Returns the absolute path to the application specific cache directory 
+     * on the filesystem. These files will be ones that get deleted first when the
+     * device runs low on storage
+     * There is no guarantee when these files will be deleted.
+     *
+     * @return Returns the path of the directory holding application cache files.
+     *
+     * @see #openFileOutput
+     * @see #getFileStreamPath
+     * @see #getDir
+     */
+    public abstract File getCacheDir();
+
+    /**
+     * Returns an array of strings naming the private files associated with
+     * this Context's application package.
+     *
+     * @return Array of strings naming the private files.
+     *
+     * @see #openFileInput
+     * @see #openFileOutput
+     * @see #deleteFile
+     */
+    public abstract String[] fileList();
+
+    /**
+     * Retrieve, creating if needed, a new directory in which the application
+     * can place its own custom data files.  You can use the returned File
+     * object to create and access files in this directory.  Note that files
+     * created through a File object will only be accessible by your own
+     * application; you can only set the mode of the entire directory, not
+     * of individual files.
+     *
+     * @param name Name of the directory to retrieve.  This is a directory
+     * that is created as part of your application data.
+     * @param mode Operating mode.  Use 0 or {@link #MODE_PRIVATE} for the
+     * default operation, {@link #MODE_WORLD_READABLE} and
+     * {@link #MODE_WORLD_WRITEABLE} to control permissions.
+     *
+     * @return Returns a File object for the requested directory.  The directory
+     * will have been created if it does not already exist.
+     *
+     * @see #openFileOutput(String, int)
+     */
+    public abstract File getDir(String name, int mode);
+
+    /**
+     * Open a new private SQLiteDatabase associated with this Context's
+     * application package.  Create the database file if it doesn't exist.
+     *
+     * @param name The name (unique in the application package) of the database.
+     * @param mode Operating mode.  Use 0 or {@link #MODE_PRIVATE} for the
+     *     default operation, {@link #MODE_WORLD_READABLE}
+     *     and {@link #MODE_WORLD_WRITEABLE} to control permissions.
+     * @param factory An optional factory class that is called to instantiate a
+     *     cursor when query is called.
+     *
+     * @return The contents of a newly created database with the given name.
+     * @throws android.database.sqlite.SQLiteException if the database file could not be opened.
+     *
+     * @see #MODE_PRIVATE
+     * @see #MODE_WORLD_READABLE
+     * @see #MODE_WORLD_WRITEABLE
+     * @see #deleteDatabase
+     */
+    public abstract SQLiteDatabase openOrCreateDatabase(String name,
+            int mode, CursorFactory factory);
+
+    /**
+     * Delete an existing private SQLiteDatabase associated with this Context's
+     * application package.
+     *
+     * @param name The name (unique in the application package) of the
+     *             database.
+     *
+     * @return True if the database was successfully deleted; else false.
+     *
+     * @see #openOrCreateDatabase
+     */
+    public abstract boolean deleteDatabase(String name);
+
+    /**
+     * Returns the absolute path on the filesystem where a database created with
+     * {@link #openOrCreateDatabase} is stored.
+     *
+     * @param name The name of the database for which you would like to get
+     *          its path.
+     *
+     * @return Returns an absolute path to the given database.
+     *
+     * @see #openOrCreateDatabase
+     */
+    public abstract File getDatabasePath(String name);
+
+    /**
+     * Returns an array of strings naming the private databases associated with
+     * this Context's application package.
+     *
+     * @return Array of strings naming the private databases.
+     *
+     * @see #openOrCreateDatabase
+     * @see #deleteDatabase
+     */
+    public abstract String[] databaseList();
+
+    /**
+     * Like {@link #peekWallpaper}, but always returns a valid Drawable.  If
+     * no wallpaper is set, the system default wallpaper is returned.
+     *
+     * @return Returns a Drawable object that will draw the wallpaper.
+     */
+    public abstract Drawable getWallpaper();
+
+    /**
+     * Retrieve the current system wallpaper.  This is returned as an
+     * abstract Drawable that you can install in a View to display whatever
+     * wallpaper the user has currently set.  If there is no wallpaper set,
+     * a null pointer is returned.
+     *
+     * @return Returns a Drawable object that will draw the wallpaper or a
+     * null pointer if these is none.
+     */
+    public abstract Drawable peekWallpaper();
+
+    /**
+     * Returns the desired minimum width for the wallpaper. Callers of
+     * {@link #setWallpaper(android.graphics.Bitmap)} or
+     * {@link #setWallpaper(java.io.InputStream)} should check this value
+     * beforehand to make sure the supplied wallpaper respects the desired
+     * minimum width.
+     *
+     * If the returned value is <= 0, the caller should use the width of
+     * the default display instead.
+     *
+     * @return The desired minimum width for the wallpaper. This value should
+     * be honored by applications that set the wallpaper but it is not
+     * mandatory.
+     */
+    public abstract int getWallpaperDesiredMinimumWidth();
+
+    /**
+     * Returns the desired minimum height for the wallpaper. Callers of
+     * {@link #setWallpaper(android.graphics.Bitmap)} or
+     * {@link #setWallpaper(java.io.InputStream)} should check this value
+     * beforehand to make sure the supplied wallpaper respects the desired
+     * minimum height.
+     *
+     * If the returned value is <= 0, the caller should use the height of
+     * the default display instead.
+     *
+     * @return The desired minimum height for the wallpaper. This value should
+     * be honored by applications that set the wallpaper but it is not
+     * mandatory.
+     */
+    public abstract int getWallpaperDesiredMinimumHeight();
+
+    /**
+     * Change the current system wallpaper to a bitmap.  The given bitmap is
+     * converted to a PNG and stored as the wallpaper.  On success, the intent
+     * {@link Intent#ACTION_WALLPAPER_CHANGED} is broadcast.
+     *
+     * @param bitmap The bitmap to save.
+     *
+     * @throws IOException If an error occurs reverting to the default
+     * wallpaper.
+     */
+    public abstract void setWallpaper(Bitmap bitmap) throws IOException;
+
+    /**
+     * Change the current system wallpaper to a specific byte stream.  The
+     * give InputStream is copied into persistent storage and will now be
+     * used as the wallpaper.  Currently it must be either a JPEG or PNG
+     * image.  On success, the intent {@link Intent#ACTION_WALLPAPER_CHANGED}
+     * is broadcast.
+     *
+     * @param data A stream containing the raw data to install as a wallpaper.
+     *
+     * @throws IOException If an error occurs reverting to the default
+     * wallpaper.
+     */
+    public abstract void setWallpaper(InputStream data) throws IOException;
+
+    /**
+     * Remove any currently set wallpaper, reverting to the system's default
+     * wallpaper. On success, the intent {@link Intent#ACTION_WALLPAPER_CHANGED}
+     * is broadcast.
+     *
+     * @throws IOException If an error occurs reverting to the default
+     * wallpaper.
+     */
+    public abstract void clearWallpaper() throws IOException;
+
+    /**
+     * Launch a new activity.  You will not receive any information about when
+     * the activity exits.
+     *
+     * <p>Note that if this method is being called from outside of an
+     * {@link android.app.Activity} Context, then the Intent must include
+     * the {@link Intent#FLAG_ACTIVITY_NEW_TASK} launch flag.  This is because,
+     * without being started from an existing Activity, there is no existing
+     * task in which to place the new activity and thus it needs to be placed
+     * in its own separate task.
+     *
+     * <p>This method throws {@link ActivityNotFoundException}
+     * if there was no Activity found to run the given Intent.
+     *
+     * @param intent The description of the activity to start.
+     *
+     * @throws ActivityNotFoundException
+     *
+     * @see PackageManager#resolveActivity
+     */
+    public abstract void startActivity(Intent intent);
+
+    /**
+     * Broadcast the given intent to all interested BroadcastReceivers.  This
+     * call is asynchronous; it returns immediately, and you will continue
+     * executing while the receivers are run.  No results are propagated from
+     * receivers and receivers can not abort the broadcast. If you want
+     * to allow receivers to propagate results or abort the broadcast, you must
+     * send an ordered broadcast using
+     * {@link #sendOrderedBroadcast(Intent, String)}.
+     *
+     * <p>See {@link BroadcastReceiver} for more information on Intent broadcasts.
+     *
+     * @param intent The Intent to broadcast; all receivers matching this
+     *               Intent will receive the broadcast.
+     *
+     * @see android.content.BroadcastReceiver
+     * @see #registerReceiver
+     * @see #sendBroadcast(Intent, String)
+     * @see #sendOrderedBroadcast(Intent, String)
+     * @see #sendOrderedBroadcast(Intent, String, BroadcastReceiver, Handler, int, String, Bundle)
+     */
+    public abstract void sendBroadcast(Intent intent);
+
+    /**
+     * Broadcast the given intent to all interested BroadcastReceivers, allowing
+     * an optional required permission to be enforced.  This
+     * call is asynchronous; it returns immediately, and you will continue
+     * executing while the receivers are run.  No results are propagated from
+     * receivers and receivers can not abort the broadcast. If you want
+     * to allow receivers to propagate results or abort the broadcast, you must
+     * send an ordered broadcast using
+     * {@link #sendOrderedBroadcast(Intent, String)}.
+     *
+     * <p>See {@link BroadcastReceiver} for more information on Intent broadcasts.
+     *
+     * @param intent The Intent to broadcast; all receivers matching this
+     *               Intent will receive the broadcast.
+     * @param receiverPermission (optional) String naming a permissions that
+     *               a receiver must hold in order to receive your broadcast.
+     *               If null, no permission is required.
+     *
+     * @see android.content.BroadcastReceiver
+     * @see #registerReceiver
+     * @see #sendBroadcast(Intent)
+     * @see #sendOrderedBroadcast(Intent, String)
+     * @see #sendOrderedBroadcast(Intent, String, BroadcastReceiver, Handler, int, String, Bundle)
+     */
+    public abstract void sendBroadcast(Intent intent,
+            String receiverPermission);
+
+    /**
+     * Broadcast the given intent to all interested BroadcastReceivers, delivering
+     * them one at a time to allow more preferred receivers to consume the
+     * broadcast before it is delivered to less preferred receivers.  This
+     * call is asynchronous; it returns immediately, and you will continue
+     * executing while the receivers are run.
+     *
+     * <p>See {@link BroadcastReceiver} for more information on Intent broadcasts.
+     *
+     * @param intent The Intent to broadcast; all receivers matching this
+     *               Intent will receive the broadcast.
+     * @param receiverPermission (optional) String naming a permissions that
+     *               a receiver must hold in order to receive your broadcast.
+     *               If null, no permission is required.
+     *
+     * @see android.content.BroadcastReceiver
+     * @see #registerReceiver
+     * @see #sendBroadcast(Intent)
+     * @see #sendOrderedBroadcast(Intent, String, BroadcastReceiver, Handler, int, String, Bundle)
+     */
+    public abstract void sendOrderedBroadcast(Intent intent,
+            String receiverPermission);
+
+    /**
+     * Version of {@link #sendBroadcast(Intent)} that allows you to
+     * receive data back from the broadcast.  This is accomplished by
+     * supplying your own BroadcastReceiver when calling, which will be
+     * treated as a final receiver at the end of the broadcast -- its
+     * {@link BroadcastReceiver#onReceive} method will be called with
+     * the result values collected from the other receivers.  If you use
+     * an <var>resultReceiver</var> with this method, then the broadcast will
+     * be serialized in the same way as calling
+     * {@link #sendOrderedBroadcast(Intent, String)}.
+     *
+     * <p>Like {@link #sendBroadcast(Intent)}, this method is
+     * asynchronous; it will return before
+     * resultReceiver.onReceive() is called.
+     *
+     * <p>See {@link BroadcastReceiver} for more information on Intent broadcasts.
+     *
+     * @param intent The Intent to broadcast; all receivers matching this
+     *               Intent will receive the broadcast.
+     * @param receiverPermission String naming a permissions that
+     *               a receiver must hold in order to receive your broadcast.
+     *               If null, no permission is required.
+     * @param resultReceiver Your own BroadcastReceiver to treat as the final
+     *                       receiver of the broadcast.
+     * @param scheduler A custom Handler with which to schedule the
+     *                  resultReceiver callback; if null it will be
+     *                  scheduled in the Context's main thread.
+     * @param initialCode An initial value for the result code.  Often
+     *                    Activity.RESULT_OK.
+     * @param initialData An initial value for the result data.  Often
+     *                    null.
+     * @param initialExtras An initial value for the result extras.  Often
+     *                      null.
+     *
+     * @see #sendBroadcast(Intent)
+     * @see #sendBroadcast(Intent, String)
+     * @see #sendOrderedBroadcast(Intent, String)
+     * @see #sendStickyBroadcast(Intent)
+     * @see android.content.BroadcastReceiver
+     * @see #registerReceiver
+     * @see android.app.Activity#RESULT_OK
+     */
+    public abstract void sendOrderedBroadcast(Intent intent,
+            String receiverPermission, BroadcastReceiver resultReceiver,
+            Handler scheduler, int initialCode, String initialData,
+            Bundle initialExtras);
+
+    /**
+     * Perform a {@link #sendBroadcast(Intent)} that is "sticky," meaning the
+     * Intent you are sending stays around after the broadcast is complete,
+     * so that others can quickly retrieve that data through the return
+     * value of {@link #registerReceiver(BroadcastReceiver, IntentFilter)}.  In
+     * all other ways, this behaves the same as
+     * {@link #sendBroadcast(Intent)}.
+     *
+     * <p>You must hold the {@link android.Manifest.permission#BROADCAST_STICKY}
+     * permission in order to use this API.  If you do not hold that
+     * permission, {@link SecurityException} will be thrown.
+     *
+     * @param intent The Intent to broadcast; all receivers matching this
+     * Intent will receive the broadcast, and the Intent will be held to
+     * be re-broadcast to future receivers.
+     *
+     * @see #sendBroadcast(Intent)
+     */
+    public abstract void sendStickyBroadcast(Intent intent);
+
+    /**
+     * Remove the data previously sent with {@link #sendStickyBroadcast},
+     * so that it is as if the sticky broadcast had never happened.
+     *
+     * <p>You must hold the {@link android.Manifest.permission#BROADCAST_STICKY}
+     * permission in order to use this API.  If you do not hold that
+     * permission, {@link SecurityException} will be thrown.
+     *
+     * @param intent The Intent that was previously broadcast.
+     *
+     * @see #sendStickyBroadcast
+     */
+    public abstract void removeStickyBroadcast(Intent intent);
+
+    /**
+     * Register an BroadcastReceiver to be run in the main activity thread.  The
+     * <var>receiver</var> will be called with any broadcast Intent that
+     * matches <var>filter</var>, in the main application thread.
+     *
+     * <p>The system may broadcast Intents that are "sticky" -- these stay
+     * around after the broadcast as finished, to be sent to any later
+     * registrations. If your IntentFilter matches one of these sticky
+     * Intents, that Intent will be returned by this function
+     * <strong>and</strong> sent to your <var>receiver</var> as if it had just
+     * been broadcast.
+     *
+     * <p>There may be multiple sticky Intents that match <var>filter</var>,
+     * in which case each of these will be sent to <var>receiver</var>.  In
+     * this case, only one of these can be returned directly by the function;
+     * which of these that is returned is arbitrarily decided by the system.
+     *
+     * <p>If you know the Intent your are registering for is sticky, you can
+     * supply null for your <var>receiver</var>.  In this case, no receiver is
+     * registered -- the function simply returns the sticky Intent that
+     * matches <var>filter</var>.  In the case of multiple matches, the same
+     * rules as described above apply.
+     *
+     * <p>See {@link BroadcastReceiver} for more information on Intent broadcasts.
+     *
+     * <p class="note">Note: this method <em>can not be called from an
+     * {@link BroadcastReceiver} component</em>.  It is okay, however, to use
+     * this method from another BroadcastReceiver that has itself been registered with
+     * {@link #registerReceiver}, since the lifetime of such an BroadcastReceiver
+     * is tied to another object (the one that registered it).</p>
+     *
+     * @param receiver The BroadcastReceiver to handle the broadcast.
+     * @param filter Selects the Intent broadcasts to be received.
+     *
+     * @return The first sticky intent found that matches <var>filter</var>,
+     *         or null if there are none.
+     *
+     * @see #registerReceiver(BroadcastReceiver, IntentFilter, String, Handler)
+     * @see #sendBroadcast
+     * @see #unregisterReceiver
+     */
+    public abstract Intent registerReceiver(BroadcastReceiver receiver,
+                                            IntentFilter filter);
+
+    /**
+     * Register to receive intent broadcasts, to run in the context of
+     * <var>scheduler</var>.  See
+     * {@link #registerReceiver(BroadcastReceiver, IntentFilter)} for more
+     * information.  This allows you to enforce permissions on who can
+     * broadcast intents to your receiver, or have the receiver run in
+     * a different thread than the main application thread.
+     *
+     * <p>See {@link BroadcastReceiver} for more information on Intent broadcasts.
+     *
+     * @param receiver The BroadcastReceiver to handle the broadcast.
+     * @param filter Selects the Intent broadcasts to be received.
+     * @param broadcastPermission String naming a permissions that a
+     *      broadcaster must hold in order to send an Intent to you.  If null,
+     *      no permission is required.
+     * @param scheduler Handler identifying the thread that will receive
+     *      the Intent.  If null, the main thread of the process will be used.
+     *
+     * @return The first sticky intent found that matches <var>filter</var>,
+     *         or null if there are none.
+     *
+     * @see #registerReceiver(BroadcastReceiver, IntentFilter)
+     * @see #sendBroadcast
+     * @see #unregisterReceiver
+     */
+    public abstract Intent registerReceiver(BroadcastReceiver receiver,
+                                            IntentFilter filter,
+                                            String broadcastPermission,
+                                            Handler scheduler);
+
+    /**
+     * Unregister a previously registered BroadcastReceiver.  <em>All</em>
+     * filters that have been registered for this BroadcastReceiver will be
+     * removed.
+     *
+     * @param receiver The BroadcastReceiver to unregister.
+     *
+     * @see #registerReceiver
+     */
+    public abstract void unregisterReceiver(BroadcastReceiver receiver);
+
+    /**
+     * Request that a given application service be started.  The Intent
+     * can either contain the complete class name of a specific service
+     * implementation to start, or an abstract definition through the
+     * action and other fields of the kind of service to start.  If this service
+     * is not already running, it will be instantiated and started (creating a
+     * process for it if needed); if it is running then it remains running.
+     *
+     * <p>Every call to this method will result in a corresponding call to
+     * the target service's {@link android.app.Service#onStart} method,
+     * with the <var>intent</var> given here.  This provides a convenient way
+     * to submit jobs to a service without having to bind and call on to its
+     * interface.
+     *
+     * <p>Using startService() overrides the default service lifetime that is
+     * managed by {@link #bindService}: it requires the service to remain
+     * running until {@link #stopService} is called, regardless of whether
+     * any clients are connected to it.  Note that calls to startService()
+     * are not nesting: no matter how many times you call startService(),
+     * a single call to {@link #stopService} will stop it.
+     *
+     * <p>The system attempts to keep running services around as much as
+     * possible.  The only time they should be stopped is if the current
+     * foreground application is using so many resources that the service needs
+     * to be killed.  If any errors happen in the service's process, it will
+     * automatically be restarted.
+     *
+     * <p>This function will throw {@link SecurityException} if you do not
+     * have permission to start the given service.
+     *
+     * @param service Identifies the service to be started.  The Intent may
+     *      specify either an explicit component name to start, or a logical
+     *      description (action, category, etc) to match an
+     *      {@link IntentFilter} published by a service.  Additional values
+     *      may be included in the Intent extras to supply arguments along with
+     *      this specific start call.
+     *
+     * @return If the service is being started or is already running, the
+     * {@link ComponentName} of the actual service that was started is
+     * returned; else if the service does not exist null is returned.
+     *
+     * @throws SecurityException
+     *
+     * @see #stopService
+     * @see #bindService
+     */
+    public abstract ComponentName startService(Intent service);
+
+    /**
+     * Request that a given application service be stopped.  If the service is
+     * not running, nothing happens.  Otherwise it is stopped.  Note that calls
+     * to startService() are not counted -- this stops the service no matter
+     * how many times it was started.
+     *
+     * <p>Note that if a stopped service still has {@link ServiceConnection}
+     * objects bound to it with the {@link #BIND_AUTO_CREATE} set, it will
+     * not be destroyed until all of these bindings are removed.  See
+     * the {@link android.app.Service} documentation for more details on a
+     * service's lifecycle.
+     *
+     * <p>This function will throw {@link SecurityException} if you do not
+     * have permission to stop the given service.
+     *
+     * @param service Description of the service to be stopped.  The Intent may
+     *      specify either an explicit component name to start, or a logical
+     *      description (action, category, etc) to match an
+     *      {@link IntentFilter} published by a service.
+     *
+     * @return If there is a service matching the given Intent that is already
+     * running, then it is stopped and true is returned; else false is returned.
+     *
+     * @throws SecurityException
+     *
+     * @see #startService
+     */
+    public abstract boolean stopService(Intent service);
+
+    /**
+     * Connect to an application service, creating it if needed.  This defines
+     * a dependency between your application and the service.  The given
+     * <var>conn</var> will receive the service object when its created and be
+     * told if it dies and restarts.  The service will be considered required
+     * by the system only for as long as the calling context exists.  For
+     * example, if this Context is an Activity that is stopped, the service will
+     * not be required to continue running until the Activity is resumed.
+     *
+     * <p>This function will throw {@link SecurityException} if you do not
+     * have permission to bind to the given service.
+     *
+     * <p class="note">Note: this method <em>can not be called from an
+     * {@link BroadcastReceiver} component</em>.  A pattern you can use to
+     * communicate from an BroadcastReceiver to a Service is to call
+     * {@link #startService} with the arguments containing the command to be
+     * sent, with the service calling its
+     * {@link android.app.Service#stopSelf(int)} method when done executing
+     * that command.  See the API demo App/Service/Service Start Arguments
+     * Controller for an illustration of this.  It is okay, however, to use
+     * this method from an BroadcastReceiver that has been registered with
+     * {@link #registerReceiver}, since the lifetime of this BroadcastReceiver
+     * is tied to another object (the one that registered it).</p>
+     *
+     * @param service Identifies the service to connect to.  The Intent may
+     *      specify either an explicit component name, or a logical
+     *      description (action, category, etc) to match an
+     *      {@link IntentFilter} published by a service.
+     * @param conn Receives information as the service is started and stopped.
+     * @param flags Operation options for the binding.  May be 0 or
+     *          {@link #BIND_AUTO_CREATE}.
+     * @return If you have successfully bound to the service, true is returned;
+     *         false is returned if the connection is not made so you will not
+     *         receive the service object.
+     *
+     * @throws SecurityException
+     *
+     * @see #unbindService
+     * @see #startService
+     * @see #BIND_AUTO_CREATE
+     */
+    public abstract boolean bindService(Intent service, ServiceConnection conn,
+            int flags);
+
+    /**
+     * Disconnect from an application service.  You will no longer receive
+     * calls as the service is restarted, and the service is now allowed to
+     * stop at any time.
+     *
+     * @param conn The connection interface previously supplied to
+     *             bindService().
+     *
+     * @see #bindService
+     */
+    public abstract void unbindService(ServiceConnection conn);
+
+    /**
+     * Start executing an {@link android.app.Instrumentation} class.  The given
+     * Instrumentation component will be run by killing its target application
+     * (if currently running), starting the target process, instantiating the
+     * instrumentation component, and then letting it drive the application.
+     *
+     * <p>This function is not synchronous -- it returns as soon as the
+     * instrumentation has started and while it is running.
+     *
+     * <p>Instrumentation is normally only allowed to run against a package
+     * that is either unsigned or signed with a signature that the
+     * the instrumentation package is also signed with (ensuring the target
+     * trusts the instrumentation).
+     *
+     * @param className Name of the Instrumentation component to be run.
+     * @param profileFile Optional path to write profiling data as the
+     * instrumentation runs, or null for no profiling.
+     * @param arguments Additional optional arguments to pass to the
+     * instrumentation, or null.
+     *
+     * @return Returns true if the instrumentation was successfully started,
+     * else false if it could not be found.
+     */
+    public abstract boolean startInstrumentation(ComponentName className,
+            String profileFile, Bundle arguments);
+
+    /**
+     * Return the handle to a system-level service by name. The class of the
+     * returned object varies by the requested name. Currently available names
+     * are:
+     * 
+     * <dl>
+     *  <dt> {@link #WINDOW_SERVICE} ("window")
+     *  <dd> The top-level window manager in which you can place custom
+     *  windows.  The returned object is a {@link android.view.WindowManager}.
+     *  <dt> {@link #LAYOUT_INFLATER_SERVICE} ("layout_inflater")
+     *  <dd> A {@link android.view.LayoutInflater} for inflating layout resources
+     *  in this context.
+     *  <dt> {@link #ACTIVITY_SERVICE} ("activity")
+     *  <dd> A {@link android.app.ActivityManager} for interacting with the
+     *  global activity state of the system.
+     *  <dt> {@link #POWER_SERVICE} ("power")
+     *  <dd> A {@link android.os.PowerManager} for controlling power
+     *  management.
+     *  <dt> {@link #ALARM_SERVICE} ("alarm")
+     *  <dd> A {@link android.app.AlarmManager} for receiving intents at the
+     *  time of your choosing.
+     *  <dt> {@link #NOTIFICATION_SERVICE} ("notification")
+     *  <dd> A {@link android.app.NotificationManager} for informing the user
+     *   of background events.
+     *  <dt> {@link #KEYGUARD_SERVICE} ("keyguard")
+     *  <dd> A {@link android.app.KeyguardManager} for controlling keyguard.
+     *  <dt> {@link #LOCATION_SERVICE} ("location")
+     *  <dd> A {@link android.location.LocationManager} for controlling location
+     *   (e.g., GPS) updates.
+     *  <dt> {@link #SEARCH_SERVICE} ("search")
+     *  <dd> A {@link android.app.SearchManager} for handling search.
+     *  <dt> {@link #VIBRATOR_SERVICE} ("vibrator")
+     *  <dd> A {@link android.os.Vibrator} for interacting with the vibrator
+     *  hardware.
+     *  <dt> {@link #CONNECTIVITY_SERVICE} ("connection")
+     *  <dd> A {@link android.net.ConnectivityManager ConnectivityManager} for
+     *  handling management of network connections.
+     *  <dt> {@link #WIFI_SERVICE} ("wifi")
+     *  <dd> A {@link android.net.wifi.WifiManager WifiManager} for management of
+     * Wi-Fi connectivity.
+     * <dt> {@link #INPUT_METHOD_SERVICE} ("input_method")
+     * <dd> An {@link android.view.inputmethod.InputMethodManager InputMethodManager}
+     * for management of input methods.
+     * </dl>
+     * 
+     * <p>Note:  System services obtained via this API may be closely associated with
+     * the Context in which they are obtained from.  In general, do not share the
+     * service objects between various different contexts (Activities, Applications,
+     * Services, Providers, etc.)
+     *
+     * @param name The name of the desired service.
+     * 
+     * @return The service or null if the name does not exist.
+     * 
+     * @see #WINDOW_SERVICE
+     * @see android.view.WindowManager
+     * @see #LAYOUT_INFLATER_SERVICE
+     * @see android.view.LayoutInflater
+     * @see #ACTIVITY_SERVICE
+     * @see android.app.ActivityManager
+     * @see #POWER_SERVICE
+     * @see android.os.PowerManager
+     * @see #ALARM_SERVICE
+     * @see android.app.AlarmManager
+     * @see #NOTIFICATION_SERVICE
+     * @see android.app.NotificationManager
+     * @see #KEYGUARD_SERVICE
+     * @see android.app.KeyguardManager
+     * @see #LOCATION_SERVICE
+     * @see android.location.LocationManager
+     * @see #SEARCH_SERVICE
+     * @see android.app.SearchManager
+     * @see #SENSOR_SERVICE
+     * @see android.hardware.SensorManager
+     * @see #VIBRATOR_SERVICE
+     * @see android.os.Vibrator
+     * @see #CONNECTIVITY_SERVICE
+     * @see android.net.ConnectivityManager
+     * @see #WIFI_SERVICE
+     * @see android.net.wifi.WifiManager
+     * @see #AUDIO_SERVICE
+     * @see android.media.AudioManager
+     * @see #TELEPHONY_SERVICE
+     * @see android.telephony.TelephonyManager
+     * @see #INPUT_METHOD_SERVICE
+     * @see android.view.inputmethod.InputMethodManager
+     */
+    public abstract Object getSystemService(String name);
+
+    /**
+     * Use with {@link #getSystemService} to retrieve a
+     * {@link android.os.PowerManager} for controlling power management,
+     * including "wake locks," which let you keep the device on while
+     * you're running long tasks.
+     */
+    public static final String POWER_SERVICE = "power";
+    /**
+     * Use with {@link #getSystemService} to retrieve a
+     * {@link android.view.WindowManager} for accessing the system's window
+     * manager.
+     *
+     * @see #getSystemService
+     * @see android.view.WindowManager
+     */
+    public static final String WINDOW_SERVICE = "window";
+    /**
+     * Use with {@link #getSystemService} to retrieve a
+     * {@link android.view.LayoutInflater} for inflating layout resources in this
+     * context.
+     *
+     * @see #getSystemService
+     * @see android.view.LayoutInflater
+     */
+    public static final String LAYOUT_INFLATER_SERVICE = "layout_inflater";
+    /**
+     * Use with {@link #getSystemService} to retrieve a
+     * {@link android.app.ActivityManager} for interacting with the global
+     * system state.
+     *
+     * @see #getSystemService
+     * @see android.app.ActivityManager
+     */
+    public static final String ACTIVITY_SERVICE = "activity";
+    /**
+     * Use with {@link #getSystemService} to retrieve a
+     * {@link android.app.AlarmManager} for receiving intents at a
+     * time of your choosing.
+     *
+     * @see #getSystemService
+     * @see android.app.AlarmManager
+     */
+    public static final String ALARM_SERVICE = "alarm";
+    /**
+     * Use with {@link #getSystemService} to retrieve a
+     * {@link android.app.NotificationManager} for informing the user of
+     * background events.
+     *
+     * @see #getSystemService
+     * @see android.app.NotificationManager
+     */
+    public static final String NOTIFICATION_SERVICE = "notification";
+    /**
+     * Use with {@link #getSystemService} to retrieve a
+     * {@link android.app.NotificationManager} for controlling keyguard.
+     *
+     * @see #getSystemService
+     * @see android.app.KeyguardManager
+     */
+    public static final String KEYGUARD_SERVICE = "keyguard";
+    /**
+     * Use with {@link #getSystemService} to retrieve a {@link
+     * android.location.LocationManager} for controlling location
+     * updates.
+     *
+     * @see #getSystemService
+     * @see android.location.LocationManager
+     */
+    public static final String LOCATION_SERVICE = "location";
+    /**
+     * Use with {@link #getSystemService} to retrieve a {@link
+     * android.app.SearchManager} for handling searches.
+     *
+     * @see #getSystemService
+     * @see android.app.SearchManager
+     */
+    public static final String SEARCH_SERVICE = "search";
+    /**
+     * Use with {@link #getSystemService} to retrieve a {@link
+     * android.hardware.SensorManager} for accessing sensors.
+     *
+     * @see #getSystemService
+     * @see android.hardware.SensorManager
+     */
+    public static final String SENSOR_SERVICE = "sensor";
+    /**
+     * Use with {@link #getSystemService} to retrieve a {@link
+     * android.bluetooth.BluetoothDevice} for interacting with Bluetooth.
+     *
+     * @see #getSystemService
+     * @see android.bluetooth.BluetoothDevice
+     * @hide
+     */
+    public static final String BLUETOOTH_SERVICE = "bluetooth";
+    /**
+     * Use with {@link #getSystemService} to retrieve a
+     * com.android.server.WallpaperService for accessing wallpapers.
+     *
+     * @see #getSystemService
+     */
+    public static final String WALLPAPER_SERVICE = "wallpaper";
+    /**
+     * Use with {@link #getSystemService} to retrieve a {@link
+     * android.os.Vibrator} for interacting with the vibration hardware.
+     *
+     * @see #getSystemService
+     * @see android.os.Vibrator
+     */
+    public static final String VIBRATOR_SERVICE = "vibrator";
+    /**
+     * Use with {@link #getSystemService} to retrieve a {@link
+     * android.app.StatusBarManager} for interacting with the status bar.
+     *
+     * @see #getSystemService
+     * @see android.app.StatusBarManager
+     * @hide
+     */
+    public static final String STATUS_BAR_SERVICE = "statusbar";
+
+    /**
+     * Use with {@link #getSystemService} to retrieve a {@link
+     * android.net.ConnectivityManager} for handling management of
+     * network connections.
+     *
+     * @see #getSystemService
+     * @see android.net.ConnectivityManager
+     */
+    public static final String CONNECTIVITY_SERVICE = "connectivity";
+
+    /**
+     * Use with {@link #getSystemService} to retrieve a {@link
+     * android.net.wifi.WifiManager} for handling management of
+     * Wi-Fi access.
+     *
+     * @see #getSystemService
+     * @see android.net.wifi.WifiManager
+     */
+    public static final String WIFI_SERVICE = "wifi";
+    
+    /**
+     * Use with {@link #getSystemService} to retrieve a
+     * {@link android.media.AudioManager} for handling management of volume,
+     * ringer modes and audio routing.
+     * 
+     * @see #getSystemService
+     * @see android.media.AudioManager
+     */
+    public static final String AUDIO_SERVICE = "audio";
+    
+    /**
+     * Use with {@link #getSystemService} to retrieve a
+     * {@link android.telephony.TelephonyManager} for handling management the
+     * telephony features of the device.
+     * 
+     * @see #getSystemService
+     * @see android.telephony.TelephonyManager
+     */
+    public static final String TELEPHONY_SERVICE = "phone";
+
+    /**
+     * Use with {@link #getSystemService} to retrieve a
+     * {@link android.text.ClipboardManager} for accessing and modifying
+     * the contents of the global clipboard.
+     * 
+     * @see #getSystemService
+     * @see android.text.ClipboardManager
+     */
+    public static final String CLIPBOARD_SERVICE = "clipboard";
+
+    /**
+     * Use with {@link #getSystemService} to retrieve a 
+     * {@link android.view.inputmethod.InputMethodManager} for accessing input
+     * methods.
+     *
+     * @see #getSystemService
+     */
+    public static final String INPUT_METHOD_SERVICE = "input_method";
+
+    /**
+     * Use with {@link #getSystemService} to retrieve a
+     * {@blink android.gadget.GadgetManager} for accessing wallpapers.
+     *
+     * @hide
+     * @see #getSystemService
+     */
+    public static final String GADGET_SERVICE = "gadget";
+    
+    /**
+     * Determine whether the given permission is allowed for a particular
+     * process and user ID running in the system.
+     *
+     * @param permission The name of the permission being checked.
+     * @param pid The process ID being checked against.  Must be > 0.
+     * @param uid The user ID being checked against.  A uid of 0 is the root
+     * user, which will pass every permission check.
+     *
+     * @return Returns {@link PackageManager#PERMISSION_GRANTED} if the given
+     * pid/uid is allowed that permission, or
+     * {@link PackageManager#PERMISSION_DENIED} if it is not.
+     *
+     * @see PackageManager#checkPermission(String, String)
+     * @see #checkCallingPermission
+     */
+    public abstract int checkPermission(String permission, int pid, int uid);
+
+    /**
+     * Determine whether the calling process of an IPC you are handling has been
+     * granted a particular permission.  This is basically the same as calling
+     * {@link #checkPermission(String, int, int)} with the pid and uid returned
+     * by {@link android.os.Binder#getCallingPid} and
+     * {@link android.os.Binder#getCallingUid}.  One important difference
+     * is that if you are not currently processing an IPC, this function
+     * will always fail.  This is done to protect against accidentally
+     * leaking permissions; you can use {@link #checkCallingOrSelfPermission}
+     * to avoid this protection.
+     *
+     * @param permission The name of the permission being checked.
+     *
+     * @return Returns {@link PackageManager#PERMISSION_GRANTED} if the calling
+     * pid/uid is allowed that permission, or
+     * {@link PackageManager#PERMISSION_DENIED} if it is not.
+     *
+     * @see PackageManager#checkPermission(String, String)
+     * @see #checkPermission
+     * @see #checkCallingOrSelfPermission
+     */
+    public abstract int checkCallingPermission(String permission);
+
+    /**
+     * Determine whether the calling process of an IPC <em>or you</em> have been
+     * granted a particular permission.  This is the same as
+     * {@link #checkCallingPermission}, except it grants your own permissions
+     * if you are not currently processing an IPC.  Use with care!
+     *
+     * @param permission The name of the permission being checked.
+     *
+     * @return Returns {@link PackageManager#PERMISSION_GRANTED} if the calling
+     * pid/uid is allowed that permission, or
+     * {@link PackageManager#PERMISSION_DENIED} if it is not.
+     *
+     * @see PackageManager#checkPermission(String, String)
+     * @see #checkPermission
+     * @see #checkCallingPermission
+     */
+    public abstract int checkCallingOrSelfPermission(String permission);
+
+    /**
+     * If the given permission is not allowed for a particular process
+     * and user ID running in the system, throw a {@link SecurityException}.
+     *
+     * @param permission The name of the permission being checked.
+     * @param pid The process ID being checked against.  Must be &gt; 0.
+     * @param uid The user ID being checked against.  A uid of 0 is the root
+     * user, which will pass every permission check.
+     * @param message A message to include in the exception if it is thrown.
+     *
+     * @see #checkPermission(String, int, int)
+     */
+    public abstract void enforcePermission(
+            String permission, int pid, int uid, String message);
+
+    /**
+     * If the calling process of an IPC you are handling has not been
+     * granted a particular permission, throw a {@link
+     * SecurityException}.  This is basically the same as calling
+     * {@link #enforcePermission(String, int, int, String)} with the
+     * pid and uid returned by {@link android.os.Binder#getCallingPid}
+     * and {@link android.os.Binder#getCallingUid}.  One important
+     * difference is that if you are not currently processing an IPC,
+     * this function will always throw the SecurityException.  This is
+     * done to protect against accidentally leaking permissions; you
+     * can use {@link #enforceCallingOrSelfPermission} to avoid this
+     * protection.
+     *
+     * @param permission The name of the permission being checked.
+     * @param message A message to include in the exception if it is thrown.
+     *
+     * @see #checkCallingPermission(String)
+     */
+    public abstract void enforceCallingPermission(
+            String permission, String message);
+
+    /**
+     * If neither you nor the calling process of an IPC you are
+     * handling has been granted a particular permission, throw a
+     * {@link SecurityException}.  This is the same as {@link
+     * #enforceCallingPermission}, except it grants your own
+     * permissions if you are not currently processing an IPC.  Use
+     * with care!
+     *
+     * @param permission The name of the permission being checked.
+     * @param message A message to include in the exception if it is thrown.
+     *
+     * @see #checkCallingOrSelfPermission(String)
+     */
+    public abstract void enforceCallingOrSelfPermission(
+            String permission, String message);
+
+    /**
+     * Grant permission to access a specific Uri to another package, regardless
+     * of whether that package has general permission to access the Uri's
+     * content provider.  This can be used to grant specific, temporary
+     * permissions, typically in response to user interaction (such as the
+     * user opening an attachment that you would like someone else to
+     * display).
+     *
+     * <p>Normally you should use {@link Intent#FLAG_GRANT_READ_URI_PERMISSION
+     * Intent.FLAG_GRANT_READ_URI_PERMISSION} or
+     * {@link Intent#FLAG_GRANT_WRITE_URI_PERMISSION
+     * Intent.FLAG_GRANT_WRITE_URI_PERMISSION} with the Intent being used to
+     * start an activity instead of this function directly.  If you use this
+     * function directly, you should be sure to call
+     * {@link #revokeUriPermission} when the target should no longer be allowed
+     * to access it.
+     *
+     * <p>To succeed, the content provider owning the Uri must have set the
+     * {@link android.R.styleable#AndroidManifestProvider_grantUriPermissions
+     * grantUriPermissions} attribute in its manifest or included the
+     * {@link android.R.styleable#AndroidManifestGrantUriPermission
+     * &lt;grant-uri-permissions&gt;} tag.
+     *
+     * @param toPackage The package you would like to allow to access the Uri.
+     * @param uri The Uri you would like to grant access to.
+     * @param modeFlags The desired access modes.  Any combination of
+     * {@link Intent#FLAG_GRANT_READ_URI_PERMISSION
+     * Intent.FLAG_GRANT_READ_URI_PERMISSION} or
+     * {@link Intent#FLAG_GRANT_WRITE_URI_PERMISSION
+     * Intent.FLAG_GRANT_WRITE_URI_PERMISSION}.
+     *
+     * @see #revokeUriPermission
+     */
+    public abstract void grantUriPermission(String toPackage, Uri uri,
+            int modeFlags);
+
+    /**
+     * Remove all permissions to access a particular content provider Uri
+     * that were previously added with {@link #grantUriPermission}.  The given
+     * Uri will match all previously granted Uris that are the same or a
+     * sub-path of the given Uri.  That is, revoking "content://foo/one" will
+     * revoke both "content://foo/target" and "content://foo/target/sub", but not
+     * "content://foo".
+     *
+     * @param uri The Uri you would like to revoke access to.
+     * @param modeFlags The desired access modes.  Any combination of
+     * {@link Intent#FLAG_GRANT_READ_URI_PERMISSION
+     * Intent.FLAG_GRANT_READ_URI_PERMISSION} or
+     * {@link Intent#FLAG_GRANT_WRITE_URI_PERMISSION
+     * Intent.FLAG_GRANT_WRITE_URI_PERMISSION}.
+     *
+     * @see #grantUriPermission
+     */
+    public abstract void revokeUriPermission(Uri uri, int modeFlags);
+
+    /**
+     * Determine whether a particular process and user ID has been granted
+     * permission to access a specific URI.  This only checks for permissions
+     * that have been explicitly granted -- if the given process/uid has
+     * more general access to the URI's content provider then this check will
+     * always fail.
+     *
+     * @param uri The uri that is being checked.
+     * @param pid The process ID being checked against.  Must be &gt; 0.
+     * @param uid The user ID being checked against.  A uid of 0 is the root
+     * user, which will pass every permission check.
+     * @param modeFlags The type of access to grant.  May be one or both of
+     * {@link Intent#FLAG_GRANT_READ_URI_PERMISSION Intent.FLAG_GRANT_READ_URI_PERMISSION} or
+     * {@link Intent#FLAG_GRANT_WRITE_URI_PERMISSION Intent.FLAG_GRANT_WRITE_URI_PERMISSION}.
+     *
+     * @return Returns {@link PackageManager#PERMISSION_GRANTED} if the given
+     * pid/uid is allowed to access that uri, or
+     * {@link PackageManager#PERMISSION_DENIED} if it is not.
+     *
+     * @see #checkCallingUriPermission
+     */
+    public abstract int checkUriPermission(Uri uri, int pid, int uid, int modeFlags);
+
+    /**
+     * Determine whether the calling process and user ID has been
+     * granted permission to access a specific URI.  This is basically
+     * the same as calling {@link #checkUriPermission(Uri, int, int,
+     * int)} with the pid and uid returned by {@link
+     * android.os.Binder#getCallingPid} and {@link
+     * android.os.Binder#getCallingUid}.  One important difference is
+     * that if you are not currently processing an IPC, this function
+     * will always fail.
+     *
+     * @param uri The uri that is being checked.
+     * @param modeFlags The type of access to grant.  May be one or both of
+     * {@link Intent#FLAG_GRANT_READ_URI_PERMISSION Intent.FLAG_GRANT_READ_URI_PERMISSION} or
+     * {@link Intent#FLAG_GRANT_WRITE_URI_PERMISSION Intent.FLAG_GRANT_WRITE_URI_PERMISSION}.
+     *
+     * @return Returns {@link PackageManager#PERMISSION_GRANTED} if the caller
+     * is allowed to access that uri, or
+     * {@link PackageManager#PERMISSION_DENIED} if it is not.
+     *
+     * @see #checkUriPermission(Uri, int, int, int)
+     */
+    public abstract int checkCallingUriPermission(Uri uri, int modeFlags);
+
+    /**
+     * Determine whether the calling process of an IPC <em>or you</em> has been granted
+     * permission to access a specific URI.  This is the same as
+     * {@link #checkCallingUriPermission}, except it grants your own permissions
+     * if you are not currently processing an IPC.  Use with care!
+     *
+     * @param uri The uri that is being checked.
+     * @param modeFlags The type of access to grant.  May be one or both of
+     * {@link Intent#FLAG_GRANT_READ_URI_PERMISSION Intent.FLAG_GRANT_READ_URI_PERMISSION} or
+     * {@link Intent#FLAG_GRANT_WRITE_URI_PERMISSION Intent.FLAG_GRANT_WRITE_URI_PERMISSION}.
+     *
+     * @return Returns {@link PackageManager#PERMISSION_GRANTED} if the caller
+     * is allowed to access that uri, or
+     * {@link PackageManager#PERMISSION_DENIED} if it is not.
+     *
+     * @see #checkCallingUriPermission
+     */
+    public abstract int checkCallingOrSelfUriPermission(Uri uri, int modeFlags);
+
+    /**
+     * Check both a Uri and normal permission.  This allows you to perform
+     * both {@link #checkPermission} and {@link #checkUriPermission} in one
+     * call.
+     *
+     * @param uri The Uri whose permission is to be checked, or null to not
+     * do this check.
+     * @param readPermission The permission that provides overall read access,
+     * or null to not do this check.
+     * @param writePermission The permission that provides overall write
+     * acess, or null to not do this check.
+     * @param pid The process ID being checked against.  Must be &gt; 0.
+     * @param uid The user ID being checked against.  A uid of 0 is the root
+     * user, which will pass every permission check.
+     * @param modeFlags The type of access to grant.  May be one or both of
+     * {@link Intent#FLAG_GRANT_READ_URI_PERMISSION Intent.FLAG_GRANT_READ_URI_PERMISSION} or
+     * {@link Intent#FLAG_GRANT_WRITE_URI_PERMISSION Intent.FLAG_GRANT_WRITE_URI_PERMISSION}.
+     *
+     * @return Returns {@link PackageManager#PERMISSION_GRANTED} if the caller
+     * is allowed to access that uri or holds one of the given permissions, or
+     * {@link PackageManager#PERMISSION_DENIED} if it is not.
+     */
+    public abstract int checkUriPermission(Uri uri, String readPermission,
+            String writePermission, int pid, int uid, int modeFlags);
+
+    /**
+     * If a particular process and user ID has not been granted
+     * permission to access a specific URI, throw {@link
+     * SecurityException}.  This only checks for permissions that have
+     * been explicitly granted -- if the given process/uid has more
+     * general access to the URI's content provider then this check
+     * will always fail.
+     *
+     * @param uri The uri that is being checked.
+     * @param pid The process ID being checked against.  Must be &gt; 0.
+     * @param uid The user ID being checked against.  A uid of 0 is the root
+     * user, which will pass every permission check.
+     * @param modeFlags The type of access to grant.  May be one or both of
+     * {@link Intent#FLAG_GRANT_READ_URI_PERMISSION Intent.FLAG_GRANT_READ_URI_PERMISSION} or
+     * {@link Intent#FLAG_GRANT_WRITE_URI_PERMISSION Intent.FLAG_GRANT_WRITE_URI_PERMISSION}.
+     * @param message A message to include in the exception if it is thrown.
+     *
+     * @see #checkUriPermission(Uri, int, int, int)
+     */
+    public abstract void enforceUriPermission(
+            Uri uri, int pid, int uid, int modeFlags, String message);
+
+    /**
+     * If the calling process and user ID has not been granted
+     * permission to access a specific URI, throw {@link
+     * SecurityException}.  This is basically the same as calling
+     * {@link #enforceUriPermission(Uri, int, int, int, String)} with
+     * the pid and uid returned by {@link
+     * android.os.Binder#getCallingPid} and {@link
+     * android.os.Binder#getCallingUid}.  One important difference is
+     * that if you are not currently processing an IPC, this function
+     * will always throw a SecurityException.
+     *
+     * @param uri The uri that is being checked.
+     * @param modeFlags The type of access to grant.  May be one or both of
+     * {@link Intent#FLAG_GRANT_READ_URI_PERMISSION Intent.FLAG_GRANT_READ_URI_PERMISSION} or
+     * {@link Intent#FLAG_GRANT_WRITE_URI_PERMISSION Intent.FLAG_GRANT_WRITE_URI_PERMISSION}.
+     * @param message A message to include in the exception if it is thrown.
+     *
+     * @see #checkCallingUriPermission(Uri, int)
+     */
+    public abstract void enforceCallingUriPermission(
+            Uri uri, int modeFlags, String message);
+
+    /**
+     * If the calling process of an IPC <em>or you</em> has not been
+     * granted permission to access a specific URI, throw {@link
+     * SecurityException}.  This is the same as {@link
+     * #enforceCallingUriPermission}, except it grants your own
+     * permissions if you are not currently processing an IPC.  Use
+     * with care!
+     * 
+     * @param uri The uri that is being checked.
+     * @param modeFlags The type of access to grant.  May be one or both of
+     * {@link Intent#FLAG_GRANT_READ_URI_PERMISSION Intent.FLAG_GRANT_READ_URI_PERMISSION} or
+     * {@link Intent#FLAG_GRANT_WRITE_URI_PERMISSION Intent.FLAG_GRANT_WRITE_URI_PERMISSION}.
+     * @param message A message to include in the exception if it is thrown.
+     *
+     * @see #checkCallingOrSelfUriPermission(Uri, int)
+     */
+    public abstract void enforceCallingOrSelfUriPermission(
+            Uri uri, int modeFlags, String message);
+
+    /**
+     * Enforce both a Uri and normal permission.  This allows you to perform
+     * both {@link #enforcePermission} and {@link #enforceUriPermission} in one
+     * call.
+     * 
+     * @param uri The Uri whose permission is to be checked, or null to not
+     * do this check.
+     * @param readPermission The permission that provides overall read access,
+     * or null to not do this check.
+     * @param writePermission The permission that provides overall write
+     * acess, or null to not do this check.
+     * @param pid The process ID being checked against.  Must be &gt; 0.
+     * @param uid The user ID being checked against.  A uid of 0 is the root
+     * user, which will pass every permission check.
+     * @param modeFlags The type of access to grant.  May be one or both of
+     * {@link Intent#FLAG_GRANT_READ_URI_PERMISSION Intent.FLAG_GRANT_READ_URI_PERMISSION} or
+     * {@link Intent#FLAG_GRANT_WRITE_URI_PERMISSION Intent.FLAG_GRANT_WRITE_URI_PERMISSION}.
+     * @param message A message to include in the exception if it is thrown.
+     *
+     * @see #checkUriPermission(Uri, String, String, int, int, int)
+     */
+    public abstract void enforceUriPermission(
+            Uri uri, String readPermission, String writePermission,
+            int pid, int uid, int modeFlags, String message);
+
+    /**
+     * Flag for use with {@link #createPackageContext}: include the application
+     * code with the context.  This means loading code into the caller's
+     * process, so that {@link #getClassLoader()} can be used to instantiate
+     * the application's classes.  Setting this flags imposes security
+     * restrictions on what application context you can access; if the
+     * requested application can not be safely loaded into your process,
+     * java.lang.SecurityException will be thrown.  If this flag is not set,
+     * there will be no restrictions on the packages that can be loaded,
+     * but {@link #getClassLoader} will always return the default system
+     * class loader.
+     */
+    public static final int CONTEXT_INCLUDE_CODE = 0x00000001;
+
+    /**
+     * Flag for use with {@link #createPackageContext}: ignore any security
+     * restrictions on the Context being requested, allowing it to always
+     * be loaded.  For use with {@link #CONTEXT_INCLUDE_CODE} to allow code
+     * to be loaded into a process even when it isn't safe to do so.  Use
+     * with extreme care!
+     */
+    public static final int CONTEXT_IGNORE_SECURITY = 0x00000002;
+
+    /**
+     * Return a new Context object for the given application name.  This
+     * Context is the same as what the named application gets when it is
+     * launched, containing the same resources and class loader.  Each call to
+     * this method returns a new instance of a Context object; Context objects
+     * are not shared, however they share common state (Resources, ClassLoader,
+     * etc) so the Context instance itself is fairly lightweight.
+     *
+     * <p>Throws {@link PackageManager.NameNotFoundException} if there is no
+     * application with the given package name.
+     *
+     * <p>Throws {@link java.lang.SecurityException} if the Context requested
+     * can not be loaded into the caller's process for security reasons (see
+     * {@link #CONTEXT_INCLUDE_CODE} for more information}.
+     *
+     * @param packageName Name of the application's package.
+     * @param flags Option flags, one of {@link #CONTEXT_INCLUDE_CODE}
+     *              or {@link #CONTEXT_IGNORE_SECURITY}.
+     *
+     * @return A Context for the application.
+     *
+     * @throws java.lang.SecurityException
+     * @throws PackageManager.NameNotFoundException if there is no application with
+     * the given package name
+     */
+    public abstract Context createPackageContext(String packageName,
+            int flags) throws PackageManager.NameNotFoundException;
+}
diff --git a/core/java/android/content/ContextWrapper.java b/core/java/android/content/ContextWrapper.java
new file mode 100644
index 0000000..36e1c34
--- /dev/null
+++ b/core/java/android/content/ContextWrapper.java
@@ -0,0 +1,422 @@
+/*
+ * 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 android.content;
+
+import android.content.pm.PackageManager;
+import android.content.res.AssetManager;
+import android.content.res.Resources;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteDatabase.CursorFactory;
+import android.graphics.Bitmap;
+import android.graphics.drawable.Drawable;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * Proxying implementation of Context that simply delegates all of its calls to
+ * another Context.  Can be subclassed to modify behavior without changing
+ * the original Context.
+ */
+public class ContextWrapper extends Context {
+    Context mBase;
+
+    public ContextWrapper(Context base) {
+        mBase = base;
+    }
+    
+    /**
+     * Set the base context for this ContextWrapper.  All calls will then be
+     * delegated to the base context.  Throws
+     * IllegalStateException if a base context has already been set.
+     * 
+     * @param base The new base context for this wrapper.
+     */
+    protected void attachBaseContext(Context base) {
+        if (mBase != null) {
+            throw new IllegalStateException("Base context already set");
+        }
+        mBase = base;
+    }
+
+    /**
+     * @return the base context as set by the constructor or setBaseContext
+     */
+    public Context getBaseContext() {
+        return mBase;
+    }
+
+    @Override
+    public AssetManager getAssets() {
+        return mBase.getAssets();
+    }
+
+    @Override
+    public Resources getResources()
+    {
+        return mBase.getResources();
+    }
+
+    @Override
+    public PackageManager getPackageManager() {
+        return mBase.getPackageManager();
+    }
+
+    @Override
+    public ContentResolver getContentResolver() {
+        return mBase.getContentResolver();
+    }
+
+    @Override
+    public Looper getMainLooper() {
+        return mBase.getMainLooper();
+    }
+    
+    @Override
+    public Context getApplicationContext() {
+        return mBase.getApplicationContext();
+    }
+    
+    @Override
+    public void setTheme(int resid) {
+        mBase.setTheme(resid);
+    }
+
+    @Override
+    public Resources.Theme getTheme() {
+        return mBase.getTheme();
+    }
+
+    @Override
+    public ClassLoader getClassLoader() {
+        return mBase.getClassLoader();
+    }
+
+    @Override
+    public String getPackageName() {
+        return mBase.getPackageName();
+    }
+
+    @Override
+    public String getPackageResourcePath() {
+        return mBase.getPackageResourcePath();
+    }
+
+    @Override
+    public String getPackageCodePath() {
+        return mBase.getPackageCodePath();
+    }
+
+    @Override
+    public SharedPreferences getSharedPreferences(String name, int mode) {
+        return mBase.getSharedPreferences(name, mode);
+    }
+
+    @Override
+    public FileInputStream openFileInput(String name)
+        throws FileNotFoundException {
+        return mBase.openFileInput(name);
+    }
+
+    @Override
+    public FileOutputStream openFileOutput(String name, int mode)
+        throws FileNotFoundException {
+        return mBase.openFileOutput(name, mode);
+    }
+
+    @Override
+    public boolean deleteFile(String name) {
+        return mBase.deleteFile(name);
+    }
+
+    @Override
+    public File getFileStreamPath(String name) {
+        return mBase.getFileStreamPath(name);
+    }
+
+    @Override
+    public String[] fileList() {
+        return mBase.fileList();
+    }
+
+    @Override
+    public File getFilesDir() {
+        return mBase.getFilesDir();
+    }
+    
+    @Override
+    public File getCacheDir() {
+        return mBase.getCacheDir();
+    }
+
+    @Override
+    public File getDir(String name, int mode) {
+        return mBase.getDir(name, mode);
+    }
+
+    @Override
+    public SQLiteDatabase openOrCreateDatabase(String name, int mode, CursorFactory factory) {
+        return mBase.openOrCreateDatabase(name, mode, factory);
+    }
+
+    @Override
+    public boolean deleteDatabase(String name) {
+        return mBase.deleteDatabase(name);
+    }
+
+    @Override
+    public File getDatabasePath(String name) {
+        return mBase.getDatabasePath(name);
+    }
+
+    @Override
+    public String[] databaseList() {
+        return mBase.databaseList();
+    }
+
+    @Override
+    public Drawable getWallpaper() {
+        return mBase.getWallpaper();
+    }
+
+    @Override
+    public Drawable peekWallpaper() {
+        return mBase.peekWallpaper();
+    }
+
+    @Override
+    public int getWallpaperDesiredMinimumWidth() {
+        return mBase.getWallpaperDesiredMinimumWidth();
+    }
+
+    @Override
+    public int getWallpaperDesiredMinimumHeight() {
+        return mBase.getWallpaperDesiredMinimumHeight();
+    }
+
+    @Override
+    public void setWallpaper(Bitmap bitmap) throws IOException {
+        mBase.setWallpaper(bitmap);
+    }
+
+    @Override
+    public void setWallpaper(InputStream data) throws IOException {
+        mBase.setWallpaper(data);
+    }
+
+    @Override
+    public void clearWallpaper() throws IOException {
+        mBase.clearWallpaper();
+    }
+
+    @Override
+    public void startActivity(Intent intent) {
+        mBase.startActivity(intent);
+    }
+
+    @Override
+    public void sendBroadcast(Intent intent) {
+        mBase.sendBroadcast(intent);
+    }
+
+    @Override
+    public void sendBroadcast(Intent intent, String receiverPermission) {
+        mBase.sendBroadcast(intent, receiverPermission);
+    }
+
+    @Override
+    public void sendOrderedBroadcast(Intent intent,
+            String receiverPermission) {
+        mBase.sendOrderedBroadcast(intent, receiverPermission);
+    }
+
+    @Override
+    public void sendOrderedBroadcast(
+        Intent intent, String receiverPermission, BroadcastReceiver resultReceiver,
+        Handler scheduler, int initialCode, String initialData,
+        Bundle initialExtras) {
+        mBase.sendOrderedBroadcast(intent, receiverPermission,
+                resultReceiver, scheduler, initialCode,
+                initialData, initialExtras);
+    }
+
+    @Override
+    public void sendStickyBroadcast(Intent intent) {
+        mBase.sendStickyBroadcast(intent);
+    }
+
+    @Override
+    public void removeStickyBroadcast(Intent intent) {
+        mBase.removeStickyBroadcast(intent);
+    }
+
+    @Override
+    public Intent registerReceiver(
+        BroadcastReceiver receiver, IntentFilter filter) {
+        return mBase.registerReceiver(receiver, filter);
+    }
+
+    @Override
+    public Intent registerReceiver(
+        BroadcastReceiver receiver, IntentFilter filter,
+        String broadcastPermission, Handler scheduler) {
+        return mBase.registerReceiver(receiver, filter, broadcastPermission,
+                scheduler);
+    }
+
+    @Override
+    public void unregisterReceiver(BroadcastReceiver receiver) {
+        mBase.unregisterReceiver(receiver);
+    }
+
+    @Override
+    public ComponentName startService(Intent service) {
+        return mBase.startService(service);
+    }
+
+    @Override
+    public boolean stopService(Intent name) {
+        return mBase.stopService(name);
+    }
+
+    @Override
+    public boolean bindService(Intent service, ServiceConnection conn,
+            int flags) {
+        return mBase.bindService(service, conn, flags);
+    }
+
+    @Override
+    public void unbindService(ServiceConnection conn) {
+        mBase.unbindService(conn);
+    }
+
+    @Override
+    public boolean startInstrumentation(ComponentName className,
+            String profileFile, Bundle arguments) {
+        return mBase.startInstrumentation(className, profileFile, arguments);
+    }
+
+    @Override
+    public Object getSystemService(String name) {
+        return mBase.getSystemService(name);
+    }
+
+    @Override
+    public int checkPermission(String permission, int pid, int uid) {
+        return mBase.checkPermission(permission, pid, uid);
+    }
+
+    @Override
+    public int checkCallingPermission(String permission) {
+        return mBase.checkCallingPermission(permission);
+    }
+
+    @Override
+    public int checkCallingOrSelfPermission(String permission) {
+        return mBase.checkCallingOrSelfPermission(permission);
+    }
+
+    @Override
+    public void enforcePermission(
+            String permission, int pid, int uid, String message) {
+        mBase.enforcePermission(permission, pid, uid, message);
+    }
+
+    @Override
+    public void enforceCallingPermission(String permission, String message) {
+        mBase.enforceCallingPermission(permission, message);
+    }
+
+    @Override
+    public void enforceCallingOrSelfPermission(
+            String permission, String message) {
+        mBase.enforceCallingOrSelfPermission(permission, message);
+    }
+
+    @Override
+    public void grantUriPermission(String toPackage, Uri uri, int modeFlags) {
+        mBase.grantUriPermission(toPackage, uri, modeFlags);
+    }
+
+    @Override
+    public void revokeUriPermission(Uri uri, int modeFlags) {
+        mBase.revokeUriPermission(uri, modeFlags);
+    }
+
+    @Override
+    public int checkUriPermission(Uri uri, int pid, int uid, int modeFlags) {
+        return mBase.checkUriPermission(uri, pid, uid, modeFlags);
+    }
+
+    @Override
+    public int checkCallingUriPermission(Uri uri, int modeFlags) {
+        return mBase.checkCallingUriPermission(uri, modeFlags);
+    }
+
+    @Override
+    public int checkCallingOrSelfUriPermission(Uri uri, int modeFlags) {
+        return mBase.checkCallingOrSelfUriPermission(uri, modeFlags);
+    }
+
+    @Override
+    public int checkUriPermission(Uri uri, String readPermission,
+            String writePermission, int pid, int uid, int modeFlags) {
+        return mBase.checkUriPermission(uri, readPermission, writePermission,
+                pid, uid, modeFlags);
+    }
+
+    @Override
+    public void enforceUriPermission(
+            Uri uri, int pid, int uid, int modeFlags, String message) {
+        mBase.enforceUriPermission(uri, pid, uid, modeFlags, message);
+    }
+
+    @Override
+    public void enforceCallingUriPermission(
+            Uri uri, int modeFlags, String message) {
+        mBase.enforceCallingUriPermission(uri, modeFlags, message);
+    }
+
+    @Override
+    public void enforceCallingOrSelfUriPermission(
+            Uri uri, int modeFlags, String message) {
+        mBase.enforceCallingOrSelfUriPermission(uri, modeFlags, message);
+    }
+
+    @Override
+    public void enforceUriPermission(
+            Uri uri, String readPermission, String writePermission,
+            int pid, int uid, int modeFlags, String message) {
+        mBase.enforceUriPermission(
+                uri, readPermission, writePermission, pid, uid, modeFlags,
+                message);
+    }
+
+    @Override
+    public Context createPackageContext(String packageName, int flags)
+        throws PackageManager.NameNotFoundException {
+        return mBase.createPackageContext(packageName, flags);
+    }
+}
diff --git a/core/java/android/content/DefaultDataHandler.java b/core/java/android/content/DefaultDataHandler.java
new file mode 100644
index 0000000..863c9f6
--- /dev/null
+++ b/core/java/android/content/DefaultDataHandler.java
@@ -0,0 +1,262 @@
+/*
+ * 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 android.content;
+
+import android.net.Uri;
+import android.util.Xml;
+
+import org.xml.sax.Attributes;
+import org.xml.sax.Locator;
+import org.xml.sax.SAXException;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Stack;
+
+/**
+ * Inserts default data from InputStream, should be in XML format.
+ * If the provider syncs data to the server, the imported data will be synced to the server.
+ * <p>Samples:</p>
+ * <br/>
+ *  Insert one row:
+ * <pre>
+ * &lt;row uri="content://contacts/people">
+ *  &lt;Col column = "name" value = "foo feebe "/>
+ *  &lt;Col column = "addr" value = "Tx"/>
+ * &lt;/row></pre>
+ * <br/>
+ * Delete, it must be in order of uri, select, and arg:
+ * <pre>
+ * &lt;del uri="content://contacts/people" select="name=? and addr=?" 
+ *  arg1 = "foo feebe" arg2 ="Tx"/></pre>
+ * <br/>
+ *  Use first row's uri to insert into another table,
+ *  content://contacts/people/1/phones:
+ * <pre>
+ * &lt;row uri="content://contacts/people">
+ *  &lt;col column = "name" value = "foo feebe"/>
+ *  &lt;col column = "addr" value = "Tx"/>
+ *  &lt;row postfix="phones">
+ *    &lt;col column="number" value="512-514-6535"/>
+ *  &lt;/row>
+ *  &lt;row postfix="phones">
+ *    &lt;col column="cell" value="512-514-6535"/>
+ *  &lt;/row>  
+ * &lt;/row></pre>
+ * <br/>
+ *  Insert multiple rows in to same table and same attributes:
+ * <pre>
+ * &lt;row uri="content://contacts/people" >
+ *  &lt;row>
+ *   &lt;col column= "name" value = "foo feebe"/>
+ *   &lt;col column= "addr" value = "Tx"/>
+ *  &lt;/row>
+ *  &lt;row>
+ *  &lt;/row>
+ * &lt;/row></pre>
+ *
+ * @hide
+ */ 
+public class DefaultDataHandler implements ContentInsertHandler {
+    private final static String ROW = "row";
+    private final static String COL = "col";
+    private final static String URI_STR = "uri";
+    private final static String POSTFIX = "postfix";
+    private final static String DEL = "del";
+    private final static String SELECT = "select";
+    private final static String ARG = "arg";   
+   
+    private Stack<Uri> mUris = new Stack<Uri>();
+    private ContentValues mValues;
+    private ContentResolver mContentResolver;   
+    
+    public void insert(ContentResolver contentResolver, InputStream in)
+            throws IOException, SAXException {
+        mContentResolver = contentResolver;
+        Xml.parse(in, Xml.Encoding.UTF_8, this);
+    }
+    
+    public void insert(ContentResolver contentResolver, String in)
+        throws SAXException {
+        mContentResolver = contentResolver;
+        Xml.parse(in, this);
+    }
+    
+    private void parseRow(Attributes atts) throws SAXException {
+        String uriStr = atts.getValue(URI_STR);
+        Uri uri;
+        if (uriStr != null) {
+            // case 1
+            uri = Uri.parse(uriStr);
+            if (uri == null) {
+                throw new SAXException("attribute " +
+                        atts.getValue(URI_STR) + " parsing failure"); 
+            }
+            
+        } else if (mUris.size() > 0){
+            // case 2
+            String postfix = atts.getValue(POSTFIX);
+            if (postfix != null) {
+                uri = Uri.withAppendedPath(mUris.lastElement(),
+                        postfix);
+            } else {
+                uri = mUris.lastElement();
+            } 
+        } else {
+            throw new SAXException("attribute parsing failure"); 
+        }
+        
+        mUris.push(uri);
+        
+    }
+    
+    private Uri insertRow() {
+        Uri u = mContentResolver.insert(mUris.lastElement(), mValues);
+        mValues = null;
+        return u;
+    }
+    
+    public void startElement(String uri, String localName, String name,
+            Attributes atts) throws SAXException {
+        if (ROW.equals(localName)) {            
+            if (mValues != null) {
+                // case 2, <Col> before <Row> insert last uri
+                if (mUris.empty()) {
+                    throw new SAXException("uri is empty");
+                }
+                Uri nextUri = insertRow();
+                if (nextUri == null) {
+                    throw new SAXException("insert to uri " + 
+                            mUris.lastElement().toString() + " failure");
+                } else {
+                    // make sure the stack lastElement save uri for more than one row
+                    mUris.pop();
+                    mUris.push(nextUri);
+                    parseRow(atts);
+                }
+            } else {
+                int attrLen = atts.getLength();
+                if (attrLen == 0) {
+                    // case 3, share same uri as last level
+                    mUris.push(mUris.lastElement());
+                } else {
+                    parseRow(atts);
+                }
+            }                
+        } else if (COL.equals(localName)) {
+            int attrLen = atts.getLength();
+            if (attrLen != 2) {
+                throw new SAXException("illegal attributes number " + attrLen);
+            }
+            String key = atts.getValue(0);
+            String value = atts.getValue(1);
+            if (key != null && key.length() > 0 && value != null && value.length() > 0) {
+                if (mValues == null) {
+                    mValues = new ContentValues();
+                }
+                mValues.put(key, value);
+            } else {
+                throw new SAXException("illegal attributes value");
+            }            
+        } else if (DEL.equals(localName)){
+            Uri u = Uri.parse(atts.getValue(URI_STR));
+            if (u == null) {
+                throw new SAXException("attribute " +
+                        atts.getValue(URI_STR) + " parsing failure"); 
+            }
+            int attrLen = atts.getLength() - 2;
+            if (attrLen > 0) {
+                String[] selectionArgs = new String[attrLen];
+                for (int i = 0; i < attrLen; i++) {
+                    selectionArgs[i] = atts.getValue(i+2);
+                }
+                mContentResolver.delete(u, atts.getValue(1), selectionArgs);
+            } else if (attrLen == 0){
+                mContentResolver.delete(u, atts.getValue(1), null);
+            } else {
+                mContentResolver.delete(u, null, null);
+            }
+            
+        } else {
+            throw new SAXException("unknown element: " + localName);
+        }
+    }
+    
+    public void endElement(String uri, String localName, String name)
+            throws SAXException {
+        if (ROW.equals(localName)) {
+            if (mUris.empty()) {
+                throw new SAXException("uri mismatch"); 
+            }
+            if (mValues != null) {
+                insertRow();
+            }
+            mUris.pop();                
+        } 
+    }
+
+
+    public void characters(char[] ch, int start, int length)
+            throws SAXException {
+        // TODO Auto-generated method stub
+
+    }
+
+    public void endDocument() throws SAXException {
+        // TODO Auto-generated method stub
+
+    }
+
+    public void endPrefixMapping(String prefix) throws SAXException {
+        // TODO Auto-generated method stub
+
+    }
+
+    public void ignorableWhitespace(char[] ch, int start, int length)
+            throws SAXException {
+        // TODO Auto-generated method stub
+
+    }
+
+    public void processingInstruction(String target, String data)
+            throws SAXException {
+        // TODO Auto-generated method stub
+
+    }
+
+    public void setDocumentLocator(Locator locator) {
+        // TODO Auto-generated method stub
+
+    }
+
+    public void skippedEntity(String name) throws SAXException {
+        // TODO Auto-generated method stub
+
+    }
+
+    public void startDocument() throws SAXException {
+        // TODO Auto-generated method stub
+
+    }
+
+    public void startPrefixMapping(String prefix, String uri)
+            throws SAXException {
+        // TODO Auto-generated method stub
+
+    }
+    
+}
diff --git a/core/java/android/content/DialogInterface.java b/core/java/android/content/DialogInterface.java
new file mode 100644
index 0000000..4afa294
--- /dev/null
+++ b/core/java/android/content/DialogInterface.java
@@ -0,0 +1,144 @@
+/*
+ * 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 android.content;
+
+import android.view.KeyEvent;
+
+/**
+ * 
+ */
+public interface DialogInterface {    
+    /**
+     * The identifier for the positive button.
+     */
+    public static final int BUTTON_POSITIVE = -1;
+
+    /**
+     * The identifier for the negative button. 
+     */
+    public static final int BUTTON_NEGATIVE = -2;
+
+    /**
+     * The identifier for the neutral button. 
+     */
+    public static final int BUTTON_NEUTRAL = -3;
+
+    /**
+     * @deprecated Use {@link #BUTTON_POSITIVE}
+     */
+    @Deprecated
+    public static final int BUTTON1 = BUTTON_POSITIVE;
+
+    /**
+     * @deprecated Use {@link #BUTTON_NEGATIVE}
+     */
+    @Deprecated
+    public static final int BUTTON2 = BUTTON_NEGATIVE;
+
+    /**
+     * @deprecated Use {@link #BUTTON_NEUTRAL}
+     */
+    @Deprecated
+    public static final int BUTTON3 = BUTTON_NEUTRAL;
+    
+    public void cancel();
+
+    public void dismiss();
+
+    /**
+     * Interface used to allow the creator of a dialog to run some code when the
+     * dialog is canceled.
+     * <p>
+     * This will only be called when the dialog is canceled, if the creator
+     * needs to know when it is dismissed in general, use
+     * {@link DialogInterface.OnDismissListener}.
+     */
+    interface OnCancelListener {
+        /**
+         * This method will be invoked when the dialog is canceled.
+         * 
+         * @param dialog The dialog that was canceled will be passed into the
+         *            method.
+         */
+        public void onCancel(DialogInterface dialog);
+    }
+
+    /**
+     * Interface used to allow the creator of a dialog to run some code when the
+     * dialog is dismissed.
+     */
+    interface OnDismissListener {
+        /**
+         * This method will be invoked when the dialog is dismissed.
+         * 
+         * @param dialog The dialog that was dismissed will be passed into the
+         *            method.
+         */
+        public void onDismiss(DialogInterface dialog);
+    }
+
+    /**
+     * Interface used to allow the creator of a dialog to run some code when an
+     * item on the dialog is clicked..
+     */
+    interface OnClickListener {
+        /**
+         * This method will be invoked when a button in the dialog is clicked.
+         * 
+         * @param dialog The dialog that received the click.
+         * @param which The button that was clicked (e.g.
+         *            {@link DialogInterface#BUTTON1}) or the position
+         *            of the item clicked.
+         */
+        /* TODO: Change to use BUTTON_POSITIVE after API council */
+        public void onClick(DialogInterface dialog, int which);
+    }
+    
+    /**
+     * Interface used to allow the creator of a dialog to run some code when an
+     * item in a multi-choice dialog is clicked.
+     */
+    interface OnMultiChoiceClickListener {
+        /**
+         * This method will be invoked when an item in the dialog is clicked.
+         * 
+         * @param dialog The dialog where the selection was made.
+         * @param which The position of the item in the list that was clicked.
+         * @param isChecked True if the click checked the item, else false.
+         */
+        public void onClick(DialogInterface dialog, int which, boolean isChecked);
+    }
+    
+    /**
+     * Interface definition for a callback to be invoked when a key event is
+     * dispatched to this dialog. The callback will be invoked before the key
+     * event is given to the dialog.
+     */
+    interface OnKeyListener {
+        /**
+         * Called when a key is dispatched to a dialog. This allows listeners to
+         * get a chance to respond before the dialog.
+         * 
+         * @param dialog The dialog the key has been dispatched to.
+         * @param keyCode The code for the physical key that was pressed
+         * @param event The KeyEvent object containing full information about
+         *            the event.
+         * @return True if the listener has consumed the event, false otherwise.
+         */
+        public boolean onKey(DialogInterface dialog, int keyCode, KeyEvent event);
+    }
+}
diff --git a/core/java/android/content/IContentProvider.java b/core/java/android/content/IContentProvider.java
new file mode 100644
index 0000000..0606956
--- /dev/null
+++ b/core/java/android/content/IContentProvider.java
@@ -0,0 +1,72 @@
+/*
+ * 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 android.content;
+
+import android.content.res.AssetFileDescriptor;
+import android.database.Cursor;
+import android.database.CursorWindow;
+import android.database.IBulkCursor;
+import android.database.IContentObserver;
+import android.net.Uri;
+import android.os.RemoteException;
+import android.os.IBinder;
+import android.os.IInterface;
+import android.os.ParcelFileDescriptor;
+
+import java.io.FileNotFoundException;
+
+/**
+ * The ipc interface to talk to a content provider.
+ * @hide
+ */
+public interface IContentProvider extends IInterface {
+    /**
+     * @hide - hide this because return type IBulkCursor and parameter
+     * IContentObserver are system private classes.
+     */
+    public IBulkCursor bulkQuery(Uri url, String[] projection,
+            String selection, String[] selectionArgs, String sortOrder, IContentObserver observer,
+            CursorWindow window) throws RemoteException;
+    public Cursor query(Uri url, String[] projection, String selection,
+            String[] selectionArgs, String sortOrder) throws RemoteException;
+    public String getType(Uri url) throws RemoteException;
+    public Uri insert(Uri url, ContentValues initialValues)
+            throws RemoteException;
+    public int bulkInsert(Uri url, ContentValues[] initialValues) throws RemoteException;
+    public int delete(Uri url, String selection, String[] selectionArgs)
+            throws RemoteException;
+    public int update(Uri url, ContentValues values, String selection,
+            String[] selectionArgs) throws RemoteException;
+    public ParcelFileDescriptor openFile(Uri url, String mode)
+            throws RemoteException, FileNotFoundException;
+    public AssetFileDescriptor openAssetFile(Uri url, String mode)
+            throws RemoteException, FileNotFoundException;
+    public ISyncAdapter getSyncAdapter() throws RemoteException;
+
+    /* IPC constants */
+    static final String descriptor = "android.content.IContentProvider";
+
+    static final int QUERY_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION;
+    static final int GET_TYPE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 1;
+    static final int INSERT_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 2;
+    static final int DELETE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 3;
+    static final int UPDATE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 9;
+    static final int GET_SYNC_ADAPTER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 10;
+    static final int BULK_INSERT_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 12;
+    static final int OPEN_FILE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 13;
+    static final int OPEN_ASSET_FILE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 14;
+}
diff --git a/core/java/android/content/IContentService.java b/core/java/android/content/IContentService.java
new file mode 100644
index 0000000..a3047da
--- /dev/null
+++ b/core/java/android/content/IContentService.java
@@ -0,0 +1,53 @@
+/*
+ * 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 android.content;
+
+import android.database.IContentObserver;
+import android.net.Uri;
+import android.os.RemoteException;
+import android.os.IBinder;
+import android.os.IInterface;
+import android.os.Bundle;
+
+/**
+ * {@hide}
+ */
+public interface IContentService extends IInterface
+{
+    public void registerContentObserver(Uri uri, boolean notifyForDescendentsn,
+            IContentObserver observer) throws RemoteException;
+    public void unregisterContentObserver(IContentObserver observer) throws RemoteException;
+
+    public void notifyChange(Uri uri, IContentObserver observer,
+            boolean observerWantsSelfNotifications, boolean syncToNetwork)
+            throws RemoteException;
+
+    public void startSync(Uri url, Bundle extras) throws RemoteException;
+    public void cancelSync(Uri uri) throws RemoteException;
+
+    static final String SERVICE_NAME = "content";
+
+    /* IPC constants */
+    static final String descriptor = "android.content.IContentService";
+
+    static final int REGISTER_CONTENT_OBSERVER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 1;
+    static final int UNREGISTER_CHANGE_OBSERVER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 2;
+    static final int NOTIFY_CHANGE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 3;
+    static final int START_SYNC_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 4;
+    static final int CANCEL_SYNC_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 5;
+}
+
diff --git a/core/java/android/content/ISyncAdapter.aidl b/core/java/android/content/ISyncAdapter.aidl
new file mode 100644
index 0000000..671188c
--- /dev/null
+++ b/core/java/android/content/ISyncAdapter.aidl
@@ -0,0 +1,43 @@
+/*
+ * 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 android.content;
+
+import android.os.Bundle;
+import android.content.ISyncContext;
+
+/**
+ * Interface used to control the sync activity on a SyncAdapter
+ * @hide
+ */
+oneway interface ISyncAdapter {
+    /**
+     * Initiate a sync for this account. SyncAdapter-specific parameters may
+     * be specified in extras, which is guaranteed to not be null.
+     *
+     * @param syncContext the ISyncContext used to indicate the progress of the sync. When
+     *   the sync is finished (successfully or not) ISyncContext.onFinished() must be called.
+     * @param account the account that should be synced
+     * @param extras SyncAdapter-specific parameters
+     */
+    void startSync(ISyncContext syncContext, String account, in Bundle extras);
+
+    /**
+     * Cancel the most recently initiated sync. Due to race conditions, this may arrive
+     * after the ISyncContext.onFinished() for that sync was called.
+     */
+    void cancelSync();
+}
diff --git a/core/java/android/content/ISyncContext.aidl b/core/java/android/content/ISyncContext.aidl
new file mode 100644
index 0000000..6d18a1c
--- /dev/null
+++ b/core/java/android/content/ISyncContext.aidl
@@ -0,0 +1,38 @@
+/*
+ * 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 android.content;
+
+import android.content.SyncResult;
+
+/**
+ * Interface used by the SyncAdapter to indicate its progress.
+ * @hide
+ */
+interface ISyncContext {
+    /**
+     * Call to indicate that the SyncAdapter is making progress. E.g., if this SyncAdapter
+     * downloads or sends records to/from the server, this may be called after each record
+     * is downloaded or uploaded.
+     */
+    void sendHeartbeat();
+
+    /**
+     * Signal that the corresponding sync session is completed.
+     * @param result information about this sync session
+     */
+    void onFinished(in SyncResult result);
+}
diff --git a/core/java/android/content/Intent.aidl b/core/java/android/content/Intent.aidl
new file mode 100644
index 0000000..568986b
--- /dev/null
+++ b/core/java/android/content/Intent.aidl
@@ -0,0 +1,20 @@
+/* //device/java/android/android/content/Intent.aidl
+**
+** Copyright 2007, 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 android.content;
+
+parcelable Intent;
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
new file mode 100644
index 0000000..e1c1f64
--- /dev/null
+++ b/core/java/android/content/Intent.java
@@ -0,0 +1,4530 @@
+/*
+ * 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 android.content;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import android.annotation.SdkConstant;
+import android.annotation.SdkConstant.SdkConstantType;
+import android.content.pm.ActivityInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.AttributeSet;
+import android.util.Log;
+import com.android.internal.util.XmlUtils;
+
+import java.io.IOException;
+import java.io.Serializable;
+import java.net.URISyntaxException;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Set;
+
+/**
+ * An intent is an abstract description of an operation to be performed.  It
+ * can be used with {@link Context#startActivity(Intent) startActivity} to
+ * launch an {@link android.app.Activity},
+ * {@link android.content.Context#sendBroadcast(Intent) broadcastIntent} to
+ * send it to any interested {@link BroadcastReceiver BroadcastReceiver} components,
+ * and {@link android.content.Context#startService} or
+ * {@link android.content.Context#bindService} to communicate with a
+ * background {@link android.app.Service}.
+ *
+ * <p>An Intent provides a facility for performing late runtime binding between
+ * the code in different applications.  Its most significant use is in the
+ * launching of activities, where it can be thought of as the glue between
+ * activities. It is
+ * basically a passive data structure holding an abstract description of an
+ * action to be performed. The primary pieces of information in an intent
+ * are:</p>
+ *
+ * <ul>
+ *   <li> <p><b>action</b> -- The general action to be performed, such as
+ *     {@link #ACTION_VIEW}, {@link #ACTION_EDIT}, {@link #ACTION_MAIN},
+ *     etc.</p>
+ *   </li>
+ *   <li> <p><b>data</b> -- The data to operate on, such as a person record
+ *     in the contacts database, expressed as a {@link android.net.Uri}.</p>
+ *   </li>
+ * </ul>
+ *
+ *
+ * <p>Some examples of action/data pairs are:</p>
+ *
+ * <ul>
+ *   <li> <p><b>{@link #ACTION_VIEW} <i>content://contacts/1</i></b> -- Display
+ *     information about the person whose identifier is "1".</p>
+ *   </li>
+ *   <li> <p><b>{@link #ACTION_DIAL} <i>content://contacts/1</i></b> -- Display
+ *     the phone dialer with the person filled in.</p>
+ *   </li>
+ *   <li> <p><b>{@link #ACTION_VIEW} <i>tel:123</i></b> -- Display
+ *     the phone dialer with the given number filled in.  Note how the
+ *     VIEW action does what what is considered the most reasonable thing for
+ *     a particular URI.</p>
+ *   </li>
+ *   <li> <p><b>{@link #ACTION_DIAL} <i>tel:123</i></b> -- Display
+ *     the phone dialer with the given number filled in.</p>
+ *   </li>
+ *   <li> <p><b>{@link #ACTION_EDIT} <i>content://contacts/1</i></b> -- Edit
+ *     information about the person whose identifier is "1".</p>
+ *   </li>
+ *   <li> <p><b>{@link #ACTION_VIEW} <i>content://contacts/</i></b> -- Display
+ *     a list of people, which the user can browse through.  This example is a
+ *     typical top-level entry into the Contacts application, showing you the
+ *     list of people. Selecting a particular person to view would result in a
+ *     new intent { <b>{@link #ACTION_VIEW} <i>content://contacts/N</i></b> }
+ *     being used to start an activity to display that person.</p>
+ *   </li>
+ * </ul>
+ *
+ * <p>In addition to these primary attributes, there are a number of secondary
+ * attributes that you can also include with an intent:</p>
+ *
+ * <ul>
+ *     <li> <p><b>category</b> -- Gives additional information about the action
+ *         to execute.  For example, {@link #CATEGORY_LAUNCHER} means it should
+ *         appear in the Launcher as a top-level application, while
+ *         {@link #CATEGORY_ALTERNATIVE} means it should be included in a list
+ *         of alternative actions the user can perform on a piece of data.</p>
+ *     <li> <p><b>type</b> -- Specifies an explicit type (a MIME type) of the
+ *         intent data.  Normally the type is inferred from the data itself.
+ *         By setting this attribute, you disable that evaluation and force
+ *         an explicit type.</p>
+ *     <li> <p><b>component</b> -- Specifies an explicit name of a component
+ *         class to use for the intent.  Normally this is determined by looking
+ *         at the other information in the intent (the action, data/type, and
+ *         categories) and matching that with a component that can handle it.
+ *         If this attribute is set then none of the evaluation is performed,
+ *         and this component is used exactly as is.  By specifying this attribute,
+ *         all of the other Intent attributes become optional.</p>
+ *     <li> <p><b>extras</b> -- This is a {@link Bundle} of any additional information.
+ *         This can be used to provide extended information to the component.
+ *         For example, if we have a action to send an e-mail message, we could
+ *         also include extra pieces of data here to supply a subject, body,
+ *         etc.</p>
+ * </ul>
+ *
+ * <p>Here are some examples of other operations you can specify as intents
+ * using these additional parameters:</p>
+ *
+ * <ul>
+ *   <li> <p><b>{@link #ACTION_MAIN} with category {@link #CATEGORY_HOME}</b> --
+ *     Launch the home screen.</p>
+ *   </li>
+ *   <li> <p><b>{@link #ACTION_GET_CONTENT} with MIME type
+ *     <i>{@link android.provider.Contacts.Phones#CONTENT_URI
+ *     vnd.android.cursor.item/phone}</i></b>
+ *     -- Display the list of people's phone numbers, allowing the user to
+ *     browse through them and pick one and return it to the parent activity.</p>
+ *   </li>
+ *   <li> <p><b>{@link #ACTION_GET_CONTENT} with MIME type
+ *     <i>*{@literal /}*</i> and category {@link #CATEGORY_OPENABLE}</b>
+ *     -- Display all pickers for data that can be opened with
+ *     {@link ContentResolver#openInputStream(Uri) ContentResolver.openInputStream()},
+ *     allowing the user to pick one of them and then some data inside of it
+ *     and returning the resulting URI to the caller.  This can be used,
+ *     for example, in an e-mail application to allow the user to pick some
+ *     data to include as an attachment.</p>
+ *   </li>
+ * </ul>
+ *
+ * <p>There are a variety of standard Intent action and category constants
+ * defined in the Intent class, but applications can also define their own.
+ * These strings use java style scoping, to ensure they are unique -- for
+ * example, the standard {@link #ACTION_VIEW} is called
+ * "android.app.action.VIEW".</p>
+ *
+ * <p>Put together, the set of actions, data types, categories, and extra data
+ * defines a language for the system allowing for the expression of phrases
+ * such as "call john smith's cell".  As applications are added to the system,
+ * they can extend this language by adding new actions, types, and categories, or
+ * they can modify the behavior of existing phrases by supplying their own
+ * activities that handle them.</p>
+ *
+ * <a name="IntentResolution"></a>
+ * <h3>Intent Resolution</h3>
+ *
+ * <p>There are two primary forms of intents you will use.
+ *
+ * <ul>
+ *     <li> <p><b>Explicit Intents</b> have specified a component (via
+ *     {@link #setComponent} or {@link #setClass}), which provides the exact
+ *     class to be run.  Often these will not include any other information,
+ *     simply being a way for an application to launch various internal
+ *     activities it has as the user interacts with the application.
+ *
+ *     <li> <p><b>Implicit Intents</b> have not specified a component;
+ *     instead, they must include enough information for the system to
+ *     determine which of the available components is best to run for that
+ *     intent.
+ * </ul>
+ *
+ * <p>When using implicit intents, given such an arbitrary intent we need to
+ * know what to do with it. This is handled by the process of <em>Intent
+ * resolution</em>, which maps an Intent to an {@link android.app.Activity},
+ * {@link BroadcastReceiver}, or {@link android.app.Service} (or sometimes two or
+ * more activities/receivers) that can handle it.</p>
+ *
+ * <p>The intent resolution mechanism basically revolves around matching an
+ * Intent against all of the &lt;intent-filter&gt; descriptions in the
+ * installed application packages.  (Plus, in the case of broadcasts, any {@link BroadcastReceiver}
+ * objects explicitly registered with {@link Context#registerReceiver}.)  More
+ * details on this can be found in the documentation on the {@link
+ * IntentFilter} class.</p>
+ *
+ * <p>There are three pieces of information in the Intent that are used for
+ * resolution: the action, type, and category.  Using this information, a query
+ * is done on the {@link PackageManager} for a component that can handle the
+ * intent. The appropriate component is determined based on the intent
+ * information supplied in the <code>AndroidManifest.xml</code> file as
+ * follows:</p>
+ *
+ * <ul>
+ *     <li> <p>The <b>action</b>, if given, must be listed by the component as
+ *         one it handles.</p>
+ *     <li> <p>The <b>type</b> is retrieved from the Intent's data, if not
+ *         already supplied in the Intent.  Like the action, if a type is
+ *         included in the intent (either explicitly or implicitly in its
+ *         data), then this must be listed by the component as one it handles.</p>
+ *     <li> For data that is not a <code>content:</code> URI and where no explicit
+ *         type is included in the Intent, instead the <b>scheme</b> of the
+ *         intent data (such as <code>http:</code> or <code>mailto:</code>) is
+ *         considered. Again like the action, if we are matching a scheme it
+ *         must be listed by the component as one it can handle.
+ *     <li> <p>The <b>categories</b>, if supplied, must <em>all</em> be listed
+ *         by the activity as categories it handles.  That is, if you include
+ *         the categories {@link #CATEGORY_LAUNCHER} and
+ *         {@link #CATEGORY_ALTERNATIVE}, then you will only resolve to components
+ *         with an intent that lists <em>both</em> of those categories.
+ *         Activities will very often need to support the
+ *         {@link #CATEGORY_DEFAULT} so that they can be found by
+ *         {@link Context#startActivity Context.startActivity()}.</p>
+ * </ul>
+ *
+ * <p>For example, consider the Note Pad sample application that
+ * allows user to browse through a list of notes data and view details about
+ * individual items.  Text in italics indicate places were you would replace a
+ * name with one specific to your own package.</p>
+ *
+ * <pre> &lt;manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ *       package="<i>com.android.notepad</i>"&gt;
+ *     &lt;application android:icon="@drawable/app_notes"
+ *             android:label="@string/app_name"&gt;
+ *
+ *         &lt;provider class=".NotePadProvider"
+ *                 android:authorities="<i>com.google.provider.NotePad</i>" /&gt;
+ *
+ *         &lt;activity class=".NotesList" android:label="@string/title_notes_list"&gt;
+ *             &lt;intent-filter&gt;
+ *                 &lt;action android:value="android.intent.action.MAIN" /&gt;
+ *                 &lt;category android:value="android.intent.category.LAUNCHER" /&gt;
+ *             &lt;/intent-filter&gt;
+ *             &lt;intent-filter&gt;
+ *                 &lt;action android:value="android.intent.action.VIEW" /&gt;
+ *                 &lt;action android:value="android.intent.action.EDIT" /&gt;
+ *                 &lt;action android:value="android.intent.action.PICK" /&gt;
+ *                 &lt;category android:value="android.intent.category.DEFAULT" /&gt;
+ *                 &lt;type android:value="vnd.android.cursor.dir/<i>vnd.google.note</i>" /&gt;
+ *             &lt;/intent-filter&gt;
+ *             &lt;intent-filter&gt;
+ *                 &lt;action android:value="android.intent.action.GET_CONTENT" /&gt;
+ *                 &lt;category android:value="android.intent.category.DEFAULT" /&gt;
+ *                 &lt;type android:value="vnd.android.cursor.item/<i>vnd.google.note</i>" /&gt;
+ *             &lt;/intent-filter&gt;
+ *         &lt;/activity&gt;
+ *
+ *         &lt;activity class=".NoteEditor" android:label="@string/title_note"&gt;
+ *             &lt;intent-filter android:label="@string/resolve_edit"&gt;
+ *                 &lt;action android:value="android.intent.action.VIEW" /&gt;
+ *                 &lt;action android:value="android.intent.action.EDIT" /&gt;
+ *                 &lt;category android:value="android.intent.category.DEFAULT" /&gt;
+ *                 &lt;type android:value="vnd.android.cursor.item/<i>vnd.google.note</i>" /&gt;
+ *             &lt;/intent-filter&gt;
+ *
+ *             &lt;intent-filter&gt;
+ *                 &lt;action android:value="android.intent.action.INSERT" /&gt;
+ *                 &lt;category android:value="android.intent.category.DEFAULT" /&gt;
+ *                 &lt;type android:value="vnd.android.cursor.dir/<i>vnd.google.note</i>" /&gt;
+ *             &lt;/intent-filter&gt;
+ *
+ *         &lt;/activity&gt;
+ *
+ *         &lt;activity class=".TitleEditor" android:label="@string/title_edit_title"
+ *                 android:theme="@android:style/Theme.Dialog"&gt;
+ *             &lt;intent-filter android:label="@string/resolve_title"&gt;
+ *                 &lt;action android:value="<i>com.android.notepad.action.EDIT_TITLE</i>" /&gt;
+ *                 &lt;category android:value="android.intent.category.DEFAULT" /&gt;
+ *                 &lt;category android:value="android.intent.category.ALTERNATIVE" /&gt;
+ *                 &lt;category android:value="android.intent.category.SELECTED_ALTERNATIVE" /&gt;
+ *                 &lt;type android:value="vnd.android.cursor.item/<i>vnd.google.note</i>" /&gt;
+ *             &lt;/intent-filter&gt;
+ *         &lt;/activity&gt;
+ *
+ *     &lt;/application&gt;
+ * &lt;/manifest&gt;</pre>
+ *
+ * <p>The first activity,
+ * <code>com.android.notepad.NotesList</code>, serves as our main
+ * entry into the app.  It can do three things as described by its three intent
+ * templates:
+ * <ol>
+ * <li><pre>
+ * &lt;intent-filter&gt;
+ *     &lt;action android:value="{@link #ACTION_MAIN android.intent.action.MAIN}" /&gt;
+ *     &lt;category android:value="{@link #CATEGORY_LAUNCHER android.intent.category.LAUNCHER}" /&gt;
+ * &lt;/intent-filter&gt;</pre>
+ * <p>This provides a top-level entry into the NotePad application: the standard
+ * MAIN action is a main entry point (not requiring any other information in
+ * the Intent), and the LAUNCHER category says that this entry point should be
+ * listed in the application launcher.</p>
+ * <li><pre>
+ * &lt;intent-filter&gt;
+ *     &lt;action android:value="{@link #ACTION_VIEW android.intent.action.VIEW}" /&gt;
+ *     &lt;action android:value="{@link #ACTION_EDIT android.intent.action.EDIT}" /&gt;
+ *     &lt;action android:value="{@link #ACTION_PICK android.intent.action.PICK}" /&gt;
+ *     &lt;category android:value="{@link #CATEGORY_DEFAULT android.intent.category.DEFAULT}" /&gt;
+ *     &lt;type android:value="vnd.android.cursor.dir/<i>vnd.google.note</i>" /&gt;
+ * &lt;/intent-filter&gt;</pre>
+ * <p>This declares the things that the activity can do on a directory of
+ * notes.  The type being supported is given with the &lt;type&gt; tag, where
+ * <code>vnd.android.cursor.dir/vnd.google.note</code> is a URI from which
+ * a Cursor of zero or more items (<code>vnd.android.cursor.dir</code>) can
+ * be retrieved which holds our note pad data (<code>vnd.google.note</code>).
+ * The activity allows the user to view or edit the directory of data (via
+ * the VIEW and EDIT actions), or to pick a particular note and return it
+ * to the caller (via the PICK action).  Note also the DEFAULT category
+ * supplied here: this is <em>required</em> for the
+ * {@link Context#startActivity Context.startActivity} method to resolve your
+ * activity when its component name is not explicitly specified.</p>
+ * <li><pre>
+ * &lt;intent-filter&gt;
+ *     &lt;action android:value="{@link #ACTION_GET_CONTENT android.intent.action.GET_CONTENT}" /&gt;
+ *     &lt;category android:value="{@link #CATEGORY_DEFAULT android.intent.category.DEFAULT}" /&gt;
+ *     &lt;type android:value="vnd.android.cursor.item/<i>vnd.google.note</i>" /&gt;
+ * &lt;/intent-filter&gt;</pre>
+ * <p>This filter describes the ability return to the caller a note selected by
+ * the user without needing to know where it came from.  The data type
+ * <code>vnd.android.cursor.item/vnd.google.note</code> is a URI from which
+ * a Cursor of exactly one (<code>vnd.android.cursor.item</code>) item can
+ * be retrieved which contains our note pad data (<code>vnd.google.note</code>).
+ * The GET_CONTENT action is similar to the PICK action, where the activity
+ * will return to its caller a piece of data selected by the user.  Here,
+ * however, the caller specifies the type of data they desire instead of
+ * the type of data the user will be picking from.</p>
+ * </ol>
+ *
+ * <p>Given these capabilities, the following intents will resolve to the
+ * NotesList activity:</p>
+ *
+ * <ul>
+ *     <li> <p><b>{ action=android.app.action.MAIN }</b> matches all of the
+ *         activities that can be used as top-level entry points into an
+ *         application.</p>
+ *     <li> <p><b>{ action=android.app.action.MAIN,
+ *         category=android.app.category.LAUNCHER }</b> is the actual intent
+ *         used by the Launcher to populate its top-level list.</p>
+ *     <li> <p><b>{ action=android.app.action.VIEW
+ *          data=content://com.google.provider.NotePad/notes }</b>
+ *         displays a list of all the notes under
+ *         "content://com.google.provider.NotePad/notes", which
+ *         the user can browse through and see the details on.</p>
+ *     <li> <p><b>{ action=android.app.action.PICK
+ *          data=content://com.google.provider.NotePad/notes }</b>
+ *         provides a list of the notes under
+ *         "content://com.google.provider.NotePad/notes", from which
+ *         the user can pick a note whose data URL is returned back to the caller.</p>
+ *     <li> <p><b>{ action=android.app.action.GET_CONTENT
+ *          type=vnd.android.cursor.item/vnd.google.note }</b>
+ *         is similar to the pick action, but allows the caller to specify the
+ *         kind of data they want back so that the system can find the appropriate
+ *         activity to pick something of that data type.</p>
+ * </ul>
+ *
+ * <p>The second activity,
+ * <code>com.android.notepad.NoteEditor</code>, shows the user a single
+ * note entry and allows them to edit it.  It can do two things as described
+ * by its two intent templates:
+ * <ol>
+ * <li><pre>
+ * &lt;intent-filter android:label="@string/resolve_edit"&gt;
+ *     &lt;action android:value="{@link #ACTION_VIEW android.intent.action.VIEW}" /&gt;
+ *     &lt;action android:value="{@link #ACTION_EDIT android.intent.action.EDIT}" /&gt;
+ *     &lt;category android:value="{@link #CATEGORY_DEFAULT android.intent.category.DEFAULT}" /&gt;
+ *     &lt;type android:value="vnd.android.cursor.item/<i>vnd.google.note</i>" /&gt;
+ * &lt;/intent-filter&gt;</pre>
+ * <p>The first, primary, purpose of this activity is to let the user interact
+ * with a single note, as decribed by the MIME type
+ * <code>vnd.android.cursor.item/vnd.google.note</code>.  The activity can
+ * either VIEW a note or allow the user to EDIT it.  Again we support the
+ * DEFAULT category to allow the activity to be launched without explicitly
+ * specifying its component.</p>
+ * <li><pre>
+ * &lt;intent-filter&gt;
+ *     &lt;action android:value="{@link #ACTION_INSERT android.intent.action.INSERT}" /&gt;
+ *     &lt;category android:value="{@link #CATEGORY_DEFAULT android.intent.category.DEFAULT}" /&gt;
+ *     &lt;type android:value="vnd.android.cursor.dir/<i>vnd.google.note</i>" /&gt;
+ * &lt;/intent-filter&gt;</pre>
+ * <p>The secondary use of this activity is to insert a new note entry into
+ * an existing directory of notes.  This is used when the user creates a new
+ * note: the INSERT action is executed on the directory of notes, causing
+ * this activity to run and have the user create the new note data which
+ * it then adds to the content provider.</p>
+ * </ol>
+ *
+ * <p>Given these capabilities, the following intents will resolve to the
+ * NoteEditor activity:</p>
+ *
+ * <ul>
+ *     <li> <p><b>{ action=android.app.action.VIEW
+ *          data=content://com.google.provider.NotePad/notes/<var>{ID}</var> }</b>
+ *         shows the user the content of note <var>{ID}</var>.</p>
+ *     <li> <p><b>{ action=android.app.action.EDIT
+ *          data=content://com.google.provider.NotePad/notes/<var>{ID}</var> }</b>
+ *         allows the user to edit the content of note <var>{ID}</var>.</p>
+ *     <li> <p><b>{ action=android.app.action.INSERT
+ *          data=content://com.google.provider.NotePad/notes }</b>
+ *         creates a new, empty note in the notes list at
+ *         "content://com.google.provider.NotePad/notes"
+ *         and allows the user to edit it.  If they keep their changes, the URI
+ *         of the newly created note is returned to the caller.</p>
+ * </ul>
+ *
+ * <p>The last activity,
+ * <code>com.android.notepad.TitleEditor</code>, allows the user to
+ * edit the title of a note.  This could be implemented as a class that the
+ * application directly invokes (by explicitly setting its component in
+ * the Intent), but here we show a way you can publish alternative
+ * operations on existing data:</p>
+ *
+ * <pre>
+ * &lt;intent-filter android:label="@string/resolve_title"&gt;
+ *     &lt;action android:value="<i>com.android.notepad.action.EDIT_TITLE</i>" /&gt;
+ *     &lt;category android:value="{@link #CATEGORY_DEFAULT android.intent.category.DEFAULT}" /&gt;
+ *     &lt;category android:value="{@link #CATEGORY_ALTERNATIVE android.intent.category.ALTERNATIVE}" /&gt;
+ *     &lt;category android:value="{@link #CATEGORY_SELECTED_ALTERNATIVE android.intent.category.SELECTED_ALTERNATIVE}" /&gt;
+ *     &lt;type android:value="vnd.android.cursor.item/<i>vnd.google.note</i>" /&gt;
+ * &lt;/intent-filter&gt;</pre>
+ *
+ * <p>In the single intent template here, we
+ * have created our own private action called
+ * <code>com.android.notepad.action.EDIT_TITLE</code> which means to
+ * edit the title of a note.  It must be invoked on a specific note
+ * (data type <code>vnd.android.cursor.item/vnd.google.note</code>) like the previous
+ * view and edit actions, but here displays and edits the title contained
+ * in the note data.
+ *
+ * <p>In addition to supporting the default category as usual, our title editor
+ * also supports two other standard categories: ALTERNATIVE and
+ * SELECTED_ALTERNATIVE.  Implementing
+ * these categories allows others to find the special action it provides
+ * without directly knowing about it, through the
+ * {@link android.content.pm.PackageManager#queryIntentActivityOptions} method, or
+ * more often to build dynamic menu items with
+ * {@link android.view.Menu#addIntentOptions}.  Note that in the intent
+ * template here was also supply an explicit name for the template
+ * (via <code>android:label="@string/resolve_title"</code>) to better control
+ * what the user sees when presented with this activity as an alternative
+ * action to the data they are viewing.
+ *
+ * <p>Given these capabilities, the following intent will resolve to the
+ * TitleEditor activity:</p>
+ *
+ * <ul>
+ *     <li> <p><b>{ action=com.android.notepad.action.EDIT_TITLE
+ *          data=content://com.google.provider.NotePad/notes/<var>{ID}</var> }</b>
+ *         displays and allows the user to edit the title associated
+ *         with note <var>{ID}</var>.</p>
+ * </ul>
+ *
+ * <h3>Standard Activity Actions</h3>
+ *
+ * <p>These are the current standard actions that Intent defines for launching
+ * activities (usually through {@link Context#startActivity}.  The most
+ * important, and by far most frequently used, are {@link #ACTION_MAIN} and
+ * {@link #ACTION_EDIT}.
+ *
+ * <ul>
+ *     <li> {@link #ACTION_MAIN}
+ *     <li> {@link #ACTION_VIEW}
+ *     <li> {@link #ACTION_ATTACH_DATA}
+ *     <li> {@link #ACTION_EDIT}
+ *     <li> {@link #ACTION_PICK}
+ *     <li> {@link #ACTION_CHOOSER}
+ *     <li> {@link #ACTION_GET_CONTENT}
+ *     <li> {@link #ACTION_DIAL}
+ *     <li> {@link #ACTION_CALL}
+ *     <li> {@link #ACTION_SEND}
+ *     <li> {@link #ACTION_SENDTO}
+ *     <li> {@link #ACTION_ANSWER}
+ *     <li> {@link #ACTION_INSERT}
+ *     <li> {@link #ACTION_DELETE}
+ *     <li> {@link #ACTION_RUN}
+ *     <li> {@link #ACTION_SYNC}
+ *     <li> {@link #ACTION_PICK_ACTIVITY}
+ *     <li> {@link #ACTION_SEARCH}
+ *     <li> {@link #ACTION_WEB_SEARCH}
+ *     <li> {@link #ACTION_FACTORY_TEST}
+ * </ul>
+ *
+ * <h3>Standard Broadcast Actions</h3>
+ *
+ * <p>These are the current standard actions that Intent defines for receiving
+ * broadcasts (usually through {@link Context#registerReceiver} or a
+ * &lt;receiver&gt; tag in a manifest).
+ *
+ * <ul>
+ *     <li> {@link #ACTION_TIME_TICK}
+ *     <li> {@link #ACTION_TIME_CHANGED}
+ *     <li> {@link #ACTION_TIMEZONE_CHANGED}
+ *     <li> {@link #ACTION_BOOT_COMPLETED}
+ *     <li> {@link #ACTION_PACKAGE_ADDED}
+ *     <li> {@link #ACTION_PACKAGE_CHANGED}
+ *     <li> {@link #ACTION_PACKAGE_REMOVED}
+ *     <li> {@link #ACTION_PACKAGE_RESTARTED}
+ *     <li> {@link #ACTION_PACKAGE_DATA_CLEARED}
+ *     <li> {@link #ACTION_UID_REMOVED}
+ *     <li> {@link #ACTION_BATTERY_CHANGED}
+ * </ul>
+ *
+ * <h3>Standard Categories</h3>
+ *
+ * <p>These are the current standard categories that can be used to further
+ * clarify an Intent via {@link #addCategory}.
+ *
+ * <ul>
+ *     <li> {@link #CATEGORY_DEFAULT}
+ *     <li> {@link #CATEGORY_BROWSABLE}
+ *     <li> {@link #CATEGORY_TAB}
+ *     <li> {@link #CATEGORY_ALTERNATIVE}
+ *     <li> {@link #CATEGORY_SELECTED_ALTERNATIVE}
+ *     <li> {@link #CATEGORY_LAUNCHER}
+ *     <li> {@link #CATEGORY_INFO}
+ *     <li> {@link #CATEGORY_HOME}
+ *     <li> {@link #CATEGORY_PREFERENCE}
+ *     <li> {@link #CATEGORY_GADGET}
+ *     <li> {@link #CATEGORY_TEST}
+ * </ul>
+ *
+ * <h3>Standard Extra Data</h3>
+ *
+ * <p>These are the current standard fields that can be used as extra data via
+ * {@link #putExtra}.
+ *
+ * <ul>
+ *     <li> {@link #EXTRA_TEMPLATE}
+ *     <li> {@link #EXTRA_INTENT}
+ *     <li> {@link #EXTRA_STREAM}
+ *     <li> {@link #EXTRA_TEXT}
+ * </ul>
+ *
+ * <h3>Flags</h3>
+ *
+ * <p>These are the possible flags that can be used in the Intent via
+ * {@link #setFlags} and {@link #addFlags}.  See {@link #setFlags} for a list
+ * of all possible flags.
+ */
+public class Intent implements Parcelable {
+    // ---------------------------------------------------------------------
+    // ---------------------------------------------------------------------
+    // Standard intent activity actions (see action variable).
+
+    /**
+     *  Activity Action: Start as a main entry point, does not expect to
+     *  receive data.
+     *  <p>Input: nothing
+     *  <p>Output: nothing
+     */
+    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+    public static final String ACTION_MAIN = "android.intent.action.MAIN";
+
+    /**
+     * Activity Action: Display the data to the user.  This is the most common
+     * action performed on data -- it is the generic action you can use on
+     * a piece of data to get the most reasonable thing to occur.  For example,
+     * when used on a contacts entry it will view the entry; when used on a
+     * mailto: URI it will bring up a compose window filled with the information
+     * supplied by the URI; when used with a tel: URI it will invoke the
+     * dialer.
+     * <p>Input: {@link #getData} is URI from which to retrieve data.
+     * <p>Output: nothing.
+     */
+    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+    public static final String ACTION_VIEW = "android.intent.action.VIEW";
+
+    /**
+     * A synonym for {@link #ACTION_VIEW}, the "standard" action that is
+     * performed on a piece of data.
+     */
+    public static final String ACTION_DEFAULT = ACTION_VIEW;
+
+    /**
+     * Used to indicate that some piece of data should be attached to some other
+     * place.  For example, image data could be attached to a contact.  It is up
+     * to the recipient to decide where the data should be attached; the intent
+     * does not specify the ultimate destination.
+     * <p>Input: {@link #getData} is URI of data to be attached.
+     * <p>Output: nothing.
+     */
+    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+    public static final String ACTION_ATTACH_DATA = "android.intent.action.ATTACH_DATA";
+
+    /**
+     * Activity Action: Provide explicit editable access to the given data.
+     * <p>Input: {@link #getData} is URI of data to be edited.
+     * <p>Output: nothing.
+     */
+    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+    public static final String ACTION_EDIT = "android.intent.action.EDIT";
+
+    /**
+     * Activity Action: Pick an existing item, or insert a new item, and then edit it.
+     * <p>Input: {@link #getType} is the desired MIME type of the item to create or edit.
+     * The extras can contain type specific data to pass through to the editing/creating
+     * activity.
+     * <p>Output: The URI of the item that was picked.  This must be a content:
+     * URI so that any receiver can access it.
+     */
+    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+    public static final String ACTION_INSERT_OR_EDIT = "android.intent.action.INSERT_OR_EDIT";
+
+    /**
+     * Activity Action: Pick an item from the data, returning what was selected.
+     * <p>Input: {@link #getData} is URI containing a directory of data
+     * (vnd.android.cursor.dir/*) from which to pick an item.
+     * <p>Output: The URI of the item that was picked.
+     */
+    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+    public static final String ACTION_PICK = "android.intent.action.PICK";
+
+    /**
+     * Activity Action: Creates a shortcut.
+     * <p>Input: Nothing.</p>
+     * <p>Output: An Intent representing the shortcut. The intent must contain three
+     * extras: SHORTCUT_INTENT (value: Intent), SHORTCUT_NAME (value: String),
+     * and SHORTCUT_ICON (value: Bitmap) or SHORTCUT_ICON_RESOURCE
+     * (value: ShortcutIconResource).</p>
+     *
+     * @see #EXTRA_SHORTCUT_INTENT
+     * @see #EXTRA_SHORTCUT_NAME
+     * @see #EXTRA_SHORTCUT_ICON
+     * @see #EXTRA_SHORTCUT_ICON_RESOURCE
+     * @see android.content.Intent.ShortcutIconResource
+     */
+    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+    public static final String ACTION_CREATE_SHORTCUT = "android.intent.action.CREATE_SHORTCUT";
+
+    /**
+     * The name of the extra used to define the Intent of a shortcut.
+     *
+     * @see #ACTION_CREATE_SHORTCUT
+     */
+    public static final String EXTRA_SHORTCUT_INTENT = "android.intent.extra.shortcut.INTENT";
+    /**
+     * The name of the extra used to define the name of a shortcut.
+     *
+     * @see #ACTION_CREATE_SHORTCUT
+     */
+    public static final String EXTRA_SHORTCUT_NAME = "android.intent.extra.shortcut.NAME";
+    /**
+     * The name of the extra used to define the icon, as a Bitmap, of a shortcut.
+     *
+     * @see #ACTION_CREATE_SHORTCUT
+     */
+    public static final String EXTRA_SHORTCUT_ICON = "android.intent.extra.shortcut.ICON";
+    /**
+     * The name of the extra used to define the icon, as a ShortcutIconResource, of a shortcut.
+     *
+     * @see #ACTION_CREATE_SHORTCUT
+     * @see android.content.Intent.ShortcutIconResource
+     */
+    public static final String EXTRA_SHORTCUT_ICON_RESOURCE =
+            "android.intent.extra.shortcut.ICON_RESOURCE";
+
+    /**
+     * Represents a shortcut/live folder icon resource.
+     *
+     * @see Intent#ACTION_CREATE_SHORTCUT
+     * @see Intent#EXTRA_SHORTCUT_ICON_RESOURCE
+     * @see android.provider.LiveFolders#ACTION_CREATE_LIVE_FOLDER
+     * @see android.provider.LiveFolders#EXTRA_LIVE_FOLDER_ICON
+     */
+    public static class ShortcutIconResource implements Parcelable {
+        /**
+         * The package name of the application containing the icon.
+         */
+        public String packageName;
+
+        /**
+         * The resource name of the icon, including package, name and type.
+         */
+        public String resourceName;
+
+        /**
+         * Creates a new ShortcutIconResource for the specified context and resource
+         * identifier.
+         *
+         * @param context The context of the application.
+         * @param resourceId The resource idenfitier for the icon.
+         * @return A new ShortcutIconResource with the specified's context package name
+         *         and icon resource idenfitier.
+         */
+        public static ShortcutIconResource fromContext(Context context, int resourceId) {
+            ShortcutIconResource icon = new ShortcutIconResource();
+            icon.packageName = context.getPackageName();
+            icon.resourceName = context.getResources().getResourceName(resourceId);
+            return icon;
+        }
+
+        /**
+         * Used to read a ShortcutIconResource from a Parcel.
+         */
+        public static final Parcelable.Creator<ShortcutIconResource> CREATOR =
+            new Parcelable.Creator<ShortcutIconResource>() {
+
+                public ShortcutIconResource createFromParcel(Parcel source) {
+                    ShortcutIconResource icon = new ShortcutIconResource();
+                    icon.packageName = source.readString();
+                    icon.resourceName = source.readString();
+                    return icon;
+                }
+
+                public ShortcutIconResource[] newArray(int size) {
+                    return new ShortcutIconResource[size];
+                }
+            };
+
+        /**
+         * No special parcel contents.
+         */
+        public int describeContents() {
+            return 0;
+        }
+
+        public void writeToParcel(Parcel dest, int flags) {
+            dest.writeString(packageName);
+            dest.writeString(resourceName);
+        }
+
+        @Override
+        public String toString() {
+            return resourceName;
+        }
+    }
+
+    /**
+     * Activity Action: Display an activity chooser, allowing the user to pick
+     * what they want to before proceeding.  This can be used as an alternative
+     * to the standard activity picker that is displayed by the system when
+     * you try to start an activity with multiple possible matches, with these
+     * differences in behavior:
+     * <ul>
+     * <li>You can specify the title that will appear in the activity chooser.
+     * <li>The user does not have the option to make one of the matching
+     * activities a preferred activity, and all possible activities will
+     * always be shown even if one of them is currently marked as the
+     * preferred activity.
+     * </ul>
+     * <p>
+     * This action should be used when the user will naturally expect to
+     * select an activity in order to proceed.  An example if when not to use
+     * it is when the user clicks on a "mailto:" link.  They would naturally
+     * expect to go directly to their mail app, so startActivity() should be
+     * called directly: it will
+     * either launch the current preferred app, or put up a dialog allowing the
+     * user to pick an app to use and optionally marking that as preferred.
+     * <p>
+     * In contrast, if the user is selecting a menu item to send a picture
+     * they are viewing to someone else, there are many different things they
+     * may want to do at this point: send it through e-mail, upload it to a
+     * web service, etc.  In this case the CHOOSER action should be used, to
+     * always present to the user a list of the things they can do, with a
+     * nice title given by the caller such as "Send this photo with:".
+     * <p>
+     * As a convenience, an Intent of this form can be created with the
+     * {@link #createChooser} function.
+     * <p>Input: No data should be specified.  get*Extra must have
+     * a {@link #EXTRA_INTENT} field containing the Intent being executed,
+     * and can optionally have a {@link #EXTRA_TITLE} field containing the
+     * title text to display in the chooser.
+     * <p>Output: Depends on the protocol of {@link #EXTRA_INTENT}.
+     */
+    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+    public static final String ACTION_CHOOSER = "android.intent.action.CHOOSER";
+
+    /**
+     * Convenience function for creating a {@link #ACTION_CHOOSER} Intent.
+     *
+     * @param target The Intent that the user will be selecting an activity
+     * to perform.
+     * @param title Optional title that will be displayed in the chooser.
+     * @return Return a new Intent object that you can hand to
+     * {@link Context#startActivity(Intent) Context.startActivity()} and
+     * related methods.
+     */
+    public static Intent createChooser(Intent target, CharSequence title) {
+        Intent intent = new Intent(ACTION_CHOOSER);
+        intent.putExtra(EXTRA_INTENT, target);
+        if (title != null) {
+            intent.putExtra(EXTRA_TITLE, title);
+        }
+        return intent;
+    }
+    /**
+     * Activity Action: Allow the user to select a particular kind of data and
+     * return it.  This is different than {@link #ACTION_PICK} in that here we
+     * just say what kind of data is desired, not a URI of existing data from
+     * which the user can pick.  A ACTION_GET_CONTENT could allow the user to
+     * create the data as it runs (for example taking a picture or recording a
+     * sound), let them browser over the web and download the desired data,
+     * etc.
+     * <p>
+     * There are two main ways to use this action: if you want an specific kind
+     * of data, such as a person contact, you set the MIME type to the kind of
+     * data you want and launch it with {@link Context#startActivity(Intent)}.
+     * The system will then launch the best application to select that kind
+     * of data for you.
+     * <p>
+     * You may also be interested in any of a set of types of content the user
+     * can pick.  For example, an e-mail application that wants to allow the
+     * user to add an attachment to an e-mail message can use this action to
+     * bring up a list of all of the types of content the user can attach.
+     * <p>
+     * In this case, you should wrap the GET_CONTENT intent with a chooser
+     * (through {@link #createChooser}), which will give the proper interface
+     * for the user to pick how to send your data and allow you to specify
+     * a prompt indicating what they are doing.  You will usually specify a
+     * broad MIME type (such as image/* or {@literal *}/*), resulting in a
+     * broad range of content types the user can select from.
+     * <p>
+     * When using such a broad GET_CONTENT action, it is often desireable to
+     * only pick from data that can be represented as a stream.  This is
+     * accomplished by requiring the {@link #CATEGORY_OPENABLE} in the Intent.
+     * <p>
+     * Input: {@link #getType} is the desired MIME type to retrieve.  Note
+     * that no URI is supplied in the intent, as there are no constraints on
+     * where the returned data originally comes from.  You may also include the
+     * {@link #CATEGORY_OPENABLE} if you can only accept data that can be
+     * opened as a stream.
+     * <p>
+     * Output: The URI of the item that was picked.  This must be a content:
+     * URI so that any receiver can access it.
+     */
+    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+    public static final String ACTION_GET_CONTENT = "android.intent.action.GET_CONTENT";
+    /**
+     * Activity Action: Dial a number as specified by the data.  This shows a
+     * UI with the number being dialed, allowing the user to explicitly
+     * initiate the call.
+     * <p>Input: If nothing, an empty dialer is started; else {@link #getData}
+     * is URI of a phone number to be dialed or a tel: URI of an explicit phone
+     * number.
+     * <p>Output: nothing.
+     */
+    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+    public static final String ACTION_DIAL = "android.intent.action.DIAL";
+    /**
+     * Activity Action: Perform a call to someone specified by the data.
+     * <p>Input: If nothing, an empty dialer is started; else {@link #getData}
+     * is URI of a phone number to be dialed or a tel: URI of an explicit phone
+     * number.
+     * <p>Output: nothing.
+     *
+     * <p>Note: there will be restrictions on which applications can initiate a
+     * call; most applications should use the {@link #ACTION_DIAL}.
+     * <p>Note: this Intent <strong>cannot</strong> be used to call emergency
+     * numbers.  Applications can <strong>dial</strong> emergency numbers using
+     * {@link #ACTION_DIAL}, however.
+     */
+    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+    public static final String ACTION_CALL = "android.intent.action.CALL";
+    /**
+     * Activity Action: Perform a call to an emergency number specified by the
+     * data.
+     * <p>Input: {@link #getData} is URI of a phone number to be dialed or a
+     * tel: URI of an explicit phone number.
+     * <p>Output: nothing.
+     * @hide
+     */
+    public static final String ACTION_CALL_EMERGENCY = "android.intent.action.CALL_EMERGENCY";
+    /**
+     * Activity action: Perform a call to any number (emergency or not)
+     * specified by the data.
+     * <p>Input: {@link #getData} is URI of a phone number to be dialed or a
+     * tel: URI of an explicit phone number.
+     * <p>Output: nothing.
+     * @hide
+     */
+    public static final String ACTION_CALL_PRIVILEGED = "android.intent.action.CALL_PRIVILEGED";
+    /**
+     * Activity Action: Send a message to someone specified by the data.
+     * <p>Input: {@link #getData} is URI describing the target.
+     * <p>Output: nothing.
+     */
+    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+    public static final String ACTION_SENDTO = "android.intent.action.SENDTO";
+    /**
+     * Activity Action: Deliver some data to someone else.  Who the data is
+     * being delivered to is not specified; it is up to the receiver of this
+     * action to ask the user where the data should be sent.
+     * <p>
+     * When launching a SEND intent, you should usually wrap it in a chooser
+     * (through {@link #createChooser}), which will give the proper interface
+     * for the user to pick how to send your data and allow you to specify
+     * a prompt indicating what they are doing.
+     * <p>
+     * Input: {@link #getType} is the MIME type of the data being sent.
+     * get*Extra can have either a {@link #EXTRA_TEXT}
+     * or {@link #EXTRA_STREAM} field, containing the data to be sent.  If
+     * using EXTRA_TEXT, the MIME type should be "text/plain"; otherwise it
+     * should be the MIME type of the data in EXTRA_STREAM.  Use {@literal *}/*
+     * if the MIME type is unknown (this will only allow senders that can
+     * handle generic data streams).
+     * <p>
+     * Optional standard extras, which may be interpreted by some recipients as
+     * appropriate, are: {@link #EXTRA_EMAIL}, {@link #EXTRA_CC},
+     * {@link #EXTRA_BCC}, {@link #EXTRA_SUBJECT}.
+     * <p>
+     * Output: nothing.
+     */
+    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+    public static final String ACTION_SEND = "android.intent.action.SEND";
+    /**
+     * Activity Action: Handle an incoming phone call.
+     * <p>Input: nothing.
+     * <p>Output: nothing.
+     */
+    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+    public static final String ACTION_ANSWER = "android.intent.action.ANSWER";
+    /**
+     * Activity Action: Insert an empty item into the given container.
+     * <p>Input: {@link #getData} is URI of the directory (vnd.android.cursor.dir/*)
+     * in which to place the data.
+     * <p>Output: URI of the new data that was created.
+     */
+    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+    public static final String ACTION_INSERT = "android.intent.action.INSERT";
+    /**
+     * Activity Action: Delete the given data from its container.
+     * <p>Input: {@link #getData} is URI of data to be deleted.
+     * <p>Output: nothing.
+     */
+    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+    public static final String ACTION_DELETE = "android.intent.action.DELETE";
+    /**
+     * Activity Action: Run the data, whatever that means.
+     * <p>Input: ?  (Note: this is currently specific to the test harness.)
+     * <p>Output: nothing.
+     */
+    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+    public static final String ACTION_RUN = "android.intent.action.RUN";
+    /**
+     * Activity Action: Perform a data synchronization.
+     * <p>Input: ?
+     * <p>Output: ?
+     */
+    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+    public static final String ACTION_SYNC = "android.intent.action.SYNC";
+    /**
+     * Activity Action: Pick an activity given an intent, returning the class
+     * selected.
+     * <p>Input: get*Extra field {@link #EXTRA_INTENT} is an Intent
+     * used with {@link PackageManager#queryIntentActivities} to determine the
+     * set of activities from which to pick.
+     * <p>Output: Class name of the activity that was selected.
+     */
+    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+    public static final String ACTION_PICK_ACTIVITY = "android.intent.action.PICK_ACTIVITY";
+    /**
+     * Activity Action: Perform a search.
+     * <p>Input: {@link android.app.SearchManager#QUERY getStringExtra(SearchManager.QUERY)}
+     * is the text to search for.  If empty, simply
+     * enter your search results Activity with the search UI activated.
+     * <p>Output: nothing.
+     */
+    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+    public static final String ACTION_SEARCH = "android.intent.action.SEARCH";
+    /**
+     * Activity Action: Perform a web search.
+     * <p>
+     * Input: {@link android.app.SearchManager#QUERY
+     * getStringExtra(SearchManager.QUERY)} is the text to search for. If it is
+     * a url starts with http or https, the site will be opened. If it is plain
+     * text, Google search will be applied.
+     * <p>
+     * Output: nothing.
+     */
+    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+    public static final String ACTION_WEB_SEARCH = "android.intent.action.WEB_SEARCH";
+    /**
+     * Activity Action: List all available applications
+     * <p>Input: Nothing.
+     * <p>Output: nothing.
+     */
+    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+    public static final String ACTION_ALL_APPS = "android.intent.action.ALL_APPS";
+    /**
+     * Activity Action: Show settings for choosing wallpaper
+     * <p>Input: Nothing.
+     * <p>Output: Nothing.
+     */
+    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+    public static final String ACTION_SET_WALLPAPER = "android.intent.action.SET_WALLPAPER";
+
+    /**
+     * Activity Action: Show activity for reporting a bug.
+     * <p>Input: Nothing.
+     * <p>Output: Nothing.
+     */
+    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+    public static final String ACTION_BUG_REPORT = "android.intent.action.BUG_REPORT";
+
+    /**
+     *  Activity Action: Main entry point for factory tests.  Only used when
+     *  the device is booting in factory test node.  The implementing package
+     *  must be installed in the system image.
+     *  <p>Input: nothing
+     *  <p>Output: nothing
+     */
+    public static final String ACTION_FACTORY_TEST = "android.intent.action.FACTORY_TEST";
+
+    /**
+     * Activity Action: The user pressed the "call" button to go to the dialer
+     * or other appropriate UI for placing a call.
+     * <p>Input: Nothing.
+     * <p>Output: Nothing.
+     */
+    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+    public static final String ACTION_CALL_BUTTON = "android.intent.action.CALL_BUTTON";
+
+    /**
+     * Activity Action: Start Voice Command.
+     * <p>Input: Nothing.
+     * <p>Output: Nothing.
+     */
+    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+    public static final String ACTION_VOICE_COMMAND = "android.intent.action.VOICE_COMMAND";
+    
+    // ---------------------------------------------------------------------
+    // ---------------------------------------------------------------------
+    // Standard intent broadcast actions (see action variable).
+
+    /**
+     * Broadcast Action: Sent after the screen turns off.
+     */
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String ACTION_SCREEN_OFF = "android.intent.action.SCREEN_OFF";
+    /**
+     * Broadcast Action: Sent after the screen turns on.
+     */
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String ACTION_SCREEN_ON = "android.intent.action.SCREEN_ON";
+    /**
+     * Broadcast Action: The current time has changed.  Sent every
+     * minute.  You can <em>not</em> receive this through components declared
+     * in manifests, only by exlicitly registering for it with
+     * {@link Context#registerReceiver(BroadcastReceiver, IntentFilter)
+     * Context.registerReceiver()}.
+     */
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String ACTION_TIME_TICK = "android.intent.action.TIME_TICK";
+    /**
+     * Broadcast Action: The time was set.
+     */
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String ACTION_TIME_CHANGED = "android.intent.action.TIME_SET";
+    /**
+     * Broadcast Action: The date has changed.
+     */
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String ACTION_DATE_CHANGED = "android.intent.action.DATE_CHANGED";
+    /**
+     * Broadcast Action: The timezone has changed. The intent will have the following extra values:</p>
+     * <ul>
+     *   <li><em>time-zone</em> - The java.util.TimeZone.getID() value identifying the new time zone.</li>
+     * </ul>
+     */
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String ACTION_TIMEZONE_CHANGED = "android.intent.action.TIMEZONE_CHANGED";
+    /**
+     * Alarm Changed Action: This is broadcast when the AlarmClock
+     * application's alarm is set or unset.  It is used by the
+     * AlarmClock application and the StatusBar service.
+     * @hide
+     */
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String ACTION_ALARM_CHANGED = "android.intent.action.ALARM_CHANGED";
+    /**
+     * Sync State Changed Action: This is broadcast when the sync starts or stops or when one has
+     * been failing for a long time.  It is used by the SyncManager and the StatusBar service.
+     * @hide
+     */
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String ACTION_SYNC_STATE_CHANGED
+            = "android.intent.action.SYNC_STATE_CHANGED";
+    /**
+     * Broadcast Action: This is broadcast once, after the system has finished
+     * booting.  It can be used to perform application-specific initialization,
+     * such as installing alarms.  You must hold the
+     * {@link android.Manifest.permission#RECEIVE_BOOT_COMPLETED} permission
+     * in order to receive this broadcast.
+     */
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String ACTION_BOOT_COMPLETED = "android.intent.action.BOOT_COMPLETED";
+    /**
+     * Broadcast Action: This is broadcast when a user action should request a
+     * temporary system dialog to dismiss.  Some examples of temporary system
+     * dialogs are the notification window-shade and the recent tasks dialog.
+     */
+    public static final String ACTION_CLOSE_SYSTEM_DIALOGS = "android.intent.action.CLOSE_SYSTEM_DIALOGS";
+    /**
+     * Broadcast Action: Trigger the download and eventual installation
+     * of a package.
+     * <p>Input: {@link #getData} is the URI of the package file to download.
+     */
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String ACTION_PACKAGE_INSTALL = "android.intent.action.PACKAGE_INSTALL";
+    /**
+     * Broadcast Action: A new application package has been installed on the
+     * device. The data contains the name of the package.
+     * <p>My include the following extras:
+     * <ul>
+     * <li> {@link #EXTRA_UID} containing the integer uid assigned to the new package.
+     * <li> {@link #EXTRA_REPLACING} is set to true if this is following
+     * an {@link #ACTION_PACKAGE_REMOVED} broadcast for the same package.
+     * </ul>
+     */
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String ACTION_PACKAGE_ADDED = "android.intent.action.PACKAGE_ADDED";
+    /**
+     * Broadcast Action: An existing application package has been removed from
+     * the device.  The data contains the name of the package.  The package
+     * that is being installed does <em>not</em> receive this Intent.
+     * <ul>
+     * <li> {@link #EXTRA_UID} containing the integer uid previously assigned
+     * to the package.
+     * <li> {@link #EXTRA_DATA_REMOVED} is set to true if the entire
+     * application -- data and code -- is being removed.
+     * <li> {@link #EXTRA_REPLACING} is set to true if this will be followed
+     * by an {@link #ACTION_PACKAGE_ADDED} broadcast for the same package.
+     * </ul>
+     */
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String ACTION_PACKAGE_REMOVED = "android.intent.action.PACKAGE_REMOVED";
+    /**
+     * Broadcast Action: An existing application package has been changed (e.g. a component has been
+     * enabled or disabled.  The data contains the name of the package.
+     * <ul>
+     * <li> {@link #EXTRA_UID} containing the integer uid assigned to the package.
+     * </ul>
+     */
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String ACTION_PACKAGE_CHANGED = "android.intent.action.PACKAGE_CHANGED";
+    /**
+     * Broadcast Action: The user has restarted a package, and all of its
+     * processes have been killed.  All runtime state
+     * associated with it (processes, alarms, notifications, etc) should
+     * be removed.  The data contains the name of the package.
+     * <ul>
+     * <li> {@link #EXTRA_UID} containing the integer uid assigned to the package.
+     * </ul>
+     */
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String ACTION_PACKAGE_RESTARTED = "android.intent.action.PACKAGE_RESTARTED";
+    /**
+     * Broadcast Action: The user has cleared the data of a package.  This should
+     * be preceded by {@link #ACTION_PACKAGE_RESTARTED}, after which all of
+     * its persistent data is erased and this broadcast sent.  The data contains
+     * the name of the package.
+     * <ul>
+     * <li> {@link #EXTRA_UID} containing the integer uid assigned to the package.
+     * </ul>
+     */
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String ACTION_PACKAGE_DATA_CLEARED = "android.intent.action.PACKAGE_DATA_CLEARED";
+    /**
+     * Broadcast Action: A user ID has been removed from the system.  The user
+     * ID number is stored in the extra data under {@link #EXTRA_UID}.
+     */
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String ACTION_UID_REMOVED = "android.intent.action.UID_REMOVED";
+    /**
+     * Broadcast Action:  The current system wallpaper has changed.  See
+     * {@link Context#getWallpaper} for retrieving the new wallpaper.
+     */
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String ACTION_WALLPAPER_CHANGED = "android.intent.action.WALLPAPER_CHANGED";
+    /**
+     * Broadcast Action: The current device {@link android.content.res.Configuration}
+     * (orientation, locale, etc) has changed.  When such a change happens, the
+     * UIs (view hierarchy) will need to be rebuilt based on this new
+     * information; for the most part, applications don't need to worry about
+     * this, because the system will take care of stopping and restarting the
+     * application to make sure it sees the new changes.  Some system code that
+     * can not be restarted will need to watch for this action and handle it
+     * appropriately.
+     *
+     * @see android.content.res.Configuration
+     */
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String ACTION_CONFIGURATION_CHANGED = "android.intent.action.CONFIGURATION_CHANGED";
+    /**
+     * Broadcast Action:  The charging state, or charge level of the battery has
+     * changed.
+     * 
+     * <p class="note">
+     * You can <em>not</em> receive this through components declared
+     * in manifests, only by exlicitly registering for it with
+     * {@link Context#registerReceiver(BroadcastReceiver, IntentFilter)
+     * Context.registerReceiver()}.
+     */
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String ACTION_BATTERY_CHANGED = "android.intent.action.BATTERY_CHANGED";
+    /**
+     * Broadcast Action:  Indicates low battery condition on the device.
+     * This broadcast corresponds to the "Low battery warning" system dialog.
+     */
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String ACTION_BATTERY_LOW = "android.intent.action.BATTERY_LOW";
+    /**
+     * Broadcast Action:  Indicates low memory condition on the device
+     */
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String ACTION_DEVICE_STORAGE_LOW = "android.intent.action.DEVICE_STORAGE_LOW";
+    /**
+     * Broadcast Action:  Indicates low memory condition on the device no longer exists
+     */
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String ACTION_DEVICE_STORAGE_OK = "android.intent.action.DEVICE_STORAGE_OK";
+    /**
+     * Broadcast Action:  Indicates low memory condition notification acknowledged by user
+     * and package management should be started.
+     * This is triggered by the user from the ACTION_DEVICE_STORAGE_LOW
+     * notification.
+     */
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String ACTION_MANAGE_PACKAGE_STORAGE = "android.intent.action.MANAGE_PACKAGE_STORAGE";
+    /**
+     * Broadcast Action:  The device has entered USB Mass Storage mode.
+     * This is used mainly for the USB Settings panel.
+     * Apps should listen for ACTION_MEDIA_MOUNTED and ACTION_MEDIA_UNMOUNTED broadcasts to be notified
+     * when the SD card file system is mounted or unmounted
+     */
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String ACTION_UMS_CONNECTED = "android.intent.action.UMS_CONNECTED";
+
+    /**
+     * Broadcast Action:  The device has exited USB Mass Storage mode.
+     * This is used mainly for the USB Settings panel.
+     * Apps should listen for ACTION_MEDIA_MOUNTED and ACTION_MEDIA_UNMOUNTED broadcasts to be notified
+     * when the SD card file system is mounted or unmounted
+     */
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String ACTION_UMS_DISCONNECTED = "android.intent.action.UMS_DISCONNECTED";
+
+    /**
+     * Broadcast Action:  External media has been removed.
+     * The path to the mount point for the removed media is contained in the Intent.mData field.
+     */
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String ACTION_MEDIA_REMOVED = "android.intent.action.MEDIA_REMOVED";
+
+    /**
+     * Broadcast Action:  External media is present, but not mounted at its mount point.
+     * The path to the mount point for the removed media is contained in the Intent.mData field.
+     */
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String ACTION_MEDIA_UNMOUNTED = "android.intent.action.MEDIA_UNMOUNTED";
+
+    /**
+     * Broadcast Action:  External media is present, and being disk-checked
+     * The path to the mount point for the checking media is contained in the Intent.mData field.
+     */
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String ACTION_MEDIA_CHECKING = "android.intent.action.MEDIA_CHECKING";
+
+    /**
+     * Broadcast Action:  External media is present, but is using an incompatible fs (or is blank)
+     * The path to the mount point for the checking media is contained in the Intent.mData field.
+     */
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String ACTION_MEDIA_NOFS = "android.intent.action.MEDIA_NOFS";
+
+    /**
+     * Broadcast Action:  External media is present and mounted at its mount point.
+     * The path to the mount point for the removed media is contained in the Intent.mData field.
+     * The Intent contains an extra with name "read-only" and Boolean value to indicate if the
+     * media was mounted read only.
+     */
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String ACTION_MEDIA_MOUNTED = "android.intent.action.MEDIA_MOUNTED";
+
+    /**
+     * Broadcast Action:  External media is unmounted because it is being shared via USB mass storage.
+     * The path to the mount point for the removed media is contained in the Intent.mData field.
+     */
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String ACTION_MEDIA_SHARED = "android.intent.action.MEDIA_SHARED";
+
+    /**
+     * Broadcast Action:  External media was removed from SD card slot, but mount point was not unmounted.
+     * The path to the mount point for the removed media is contained in the Intent.mData field.
+     */
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String ACTION_MEDIA_BAD_REMOVAL = "android.intent.action.MEDIA_BAD_REMOVAL";
+
+    /**
+     * Broadcast Action:  External media is present but cannot be mounted.
+     * The path to the mount point for the removed media is contained in the Intent.mData field.
+     */
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String ACTION_MEDIA_UNMOUNTABLE = "android.intent.action.MEDIA_UNMOUNTABLE";
+
+   /**
+     * Broadcast Action:  User has expressed the desire to remove the external storage media.
+     * Applications should close all files they have open within the mount point when they receive this intent.
+     * The path to the mount point for the media to be ejected is contained in the Intent.mData field.
+     */
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String ACTION_MEDIA_EJECT = "android.intent.action.MEDIA_EJECT";
+
+    /**
+     * Broadcast Action:  The media scanner has started scanning a directory.
+     * The path to the directory being scanned is contained in the Intent.mData field.
+     */
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String ACTION_MEDIA_SCANNER_STARTED = "android.intent.action.MEDIA_SCANNER_STARTED";
+
+   /**
+     * Broadcast Action:  The media scanner has finished scanning a directory.
+     * The path to the scanned directory is contained in the Intent.mData field.
+     */
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String ACTION_MEDIA_SCANNER_FINISHED = "android.intent.action.MEDIA_SCANNER_FINISHED";
+
+   /**
+     * Broadcast Action:  Request the media scanner to scan a file and add it to the media database.
+     * The path to the file is contained in the Intent.mData field.
+     */
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String ACTION_MEDIA_SCANNER_SCAN_FILE = "android.intent.action.MEDIA_SCANNER_SCAN_FILE";
+
+   /**
+     * Broadcast Action:  The "Media Button" was pressed.  Includes a single
+     * extra field, {@link #EXTRA_KEY_EVENT}, containing the key event that
+     * caused the broadcast.
+     */
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String ACTION_MEDIA_BUTTON = "android.intent.action.MEDIA_BUTTON";
+
+    /**
+     * Broadcast Action:  The "Camera Button" was pressed.  Includes a single
+     * extra field, {@link #EXTRA_KEY_EVENT}, containing the key event that
+     * caused the broadcast.
+     */
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String ACTION_CAMERA_BUTTON = "android.intent.action.CAMERA_BUTTON";
+
+    // *** NOTE: @todo(*) The following really should go into a more domain-specific
+    // location; they are not general-purpose actions.
+
+    /**
+     * Broadcast Action: An GTalk connection has been established.
+     */
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String ACTION_GTALK_SERVICE_CONNECTED =
+            "android.intent.action.GTALK_CONNECTED";
+
+    /**
+     * Broadcast Action: An GTalk connection has been disconnected.
+     */
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String ACTION_GTALK_SERVICE_DISCONNECTED =
+            "android.intent.action.GTALK_DISCONNECTED";
+    
+    /**
+     * Broadcast Action: An input method has been changed.
+     * {@hide pending API Council approval}
+     */
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String ACTION_INPUT_METHOD_CHANGED =
+            "android.intent.action.INPUT_METHOD_CHANGED";
+
+    /**
+     * <p>Broadcast Action: The user has switched the phone into or out of Airplane Mode. One or
+     * more radios have been turned off or on. The intent will have the following extra value:</p>
+     * <ul>
+     *   <li><em>state</em> - A boolean value indicating whether Airplane Mode is on. If true,
+     *   then cell radio and possibly other radios such as bluetooth or WiFi may have also been
+     *   turned off</li>
+     * </ul>
+     */
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String ACTION_AIRPLANE_MODE_CHANGED = "android.intent.action.AIRPLANE_MODE";
+
+    /**
+     * Broadcast Action: Some content providers have parts of their namespace
+     * where they publish new events or items that the user may be especially
+     * interested in. For these things, they may broadcast this action when the
+     * set of interesting items change.
+     *
+     * For example, GmailProvider sends this notification when the set of unread
+     * mail in the inbox changes.
+     *
+     * <p>The data of the intent identifies which part of which provider
+     * changed. When queried through the content resolver, the data URI will
+     * return the data set in question.
+     *
+     * <p>The intent will have the following extra values:
+     * <ul>
+     *   <li><em>count</em> - The number of items in the data set. This is the
+     *       same as the number of items in the cursor returned by querying the
+     *       data URI. </li>
+     * </ul>
+     *
+     * This intent will be sent at boot (if the count is non-zero) and when the
+     * data set changes. It is possible for the data set to change without the
+     * count changing (for example, if a new unread message arrives in the same
+     * sync operation in which a message is archived). The phone should still
+     * ring/vibrate/etc as normal in this case.
+     */
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String ACTION_PROVIDER_CHANGED =
+            "android.intent.action.PROVIDER_CHANGED";
+
+    /**
+     * Broadcast Action: Wired Headset plugged in or unplugged.
+     *
+     * <p>The intent will have the following extra values:
+     * <ul>
+     *   <li><em>state</em> - 0 for unplugged, 1 for plugged. </li>
+     *   <li><em>name</em> - Headset type, human readable string </li>
+     * </ul>
+     * </ul>
+     */
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String ACTION_HEADSET_PLUG =
+            "android.intent.action.HEADSET_PLUG";
+
+    /**
+     * Broadcast Action: An outgoing call is about to be placed.
+     *
+     * <p>The Intent will have the following extra value:
+     * <ul>
+     *   <li><em>{@link android.content.Intent#EXTRA_PHONE_NUMBER}</em> - 
+     *       the phone number originally intended to be dialed.</li>
+     * </ul>
+     * <p>Once the broadcast is finished, the resultData is used as the actual
+     * number to call.  If  <code>null</code>, no call will be placed.</p>
+     * <p>It is perfectly acceptable for multiple receivers to process the 
+     * outgoing call in turn: for example, a parental control application
+     * might verify that the user is authorized to place the call at that
+     * time, then a number-rewriting application might add an area code if
+     * one was not specified.</p>
+     * <p>For consistency, any receiver whose purpose is to prohibit phone
+     * calls should have a priority of 0, to ensure it will see the final
+     * phone number to be dialed.
+     * Any receiver whose purpose is to rewrite phone numbers to be called 
+     * should have a positive priority.
+     * Negative priorities are reserved for the system for this broadcast;
+     * using them may cause problems.</p>
+     * <p>Any BroadcastReceiver receiving this Intent <em>must not</em>
+     * abort the broadcast.</p>
+     * <p>Emergency calls cannot be intercepted using this mechanism, and
+     * other calls cannot be modified to call emergency numbers using this
+     * mechanism.
+     * <p>You must hold the 
+     * {@link android.Manifest.permission#PROCESS_OUTGOING_CALLS}
+     * permission to receive this Intent.</p>
+     */
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String ACTION_NEW_OUTGOING_CALL =
+            "android.intent.action.NEW_OUTGOING_CALL";
+
+    /**
+     * Broadcast Action: Have the device reboot.  This is only for use by
+     * system code.
+     */
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String ACTION_REBOOT =
+            "android.intent.action.REBOOT";
+
+    // ---------------------------------------------------------------------
+    // ---------------------------------------------------------------------
+    // Standard intent categories (see addCategory()).
+
+    /**
+     * Set if the activity should be an option for the default action
+     * (center press) to perform on a piece of data.  Setting this will
+     * hide from the user any activities without it set when performing an
+     * action on some data.  Note that this is normal -not- set in the
+     * Intent when initiating an action -- it is for use in intent filters
+     * specified in packages.
+     */
+    @SdkConstant(SdkConstantType.INTENT_CATEGORY)
+    public static final String CATEGORY_DEFAULT = "android.intent.category.DEFAULT";
+    /**
+     * Activities that can be safely invoked from a browser must support this
+     * category.  For example, if the user is viewing a web page or an e-mail
+     * and clicks on a link in the text, the Intent generated execute that
+     * link will require the BROWSABLE category, so that only activities
+     * supporting this category will be considered as possible actions.  By
+     * supporting this category, you are promising that there is nothing
+     * damaging (without user intervention) that can happen by invoking any
+     * matching Intent.
+     */
+    @SdkConstant(SdkConstantType.INTENT_CATEGORY)
+    public static final String CATEGORY_BROWSABLE = "android.intent.category.BROWSABLE";
+    /**
+     * Set if the activity should be considered as an alternative action to
+     * the data the user is currently viewing.  See also
+     * {@link #CATEGORY_SELECTED_ALTERNATIVE} for an alternative action that
+     * applies to the selection in a list of items.
+     *
+     * <p>Supporting this category means that you would like your activity to be
+     * displayed in the set of alternative things the user can do, usually as
+     * part of the current activity's options menu.  You will usually want to
+     * include a specific label in the &lt;intent-filter&gt; of this action
+     * describing to the user what it does.
+     *
+     * <p>The action of IntentFilter with this category is important in that it
+     * describes the specific action the target will perform.  This generally
+     * should not be a generic action (such as {@link #ACTION_VIEW}, but rather
+     * a specific name such as "com.android.camera.action.CROP.  Only one
+     * alternative of any particular action will be shown to the user, so using
+     * a specific action like this makes sure that your alternative will be
+     * displayed while also allowing other applications to provide their own
+     * overrides of that particular action.
+     */
+    @SdkConstant(SdkConstantType.INTENT_CATEGORY)
+    public static final String CATEGORY_ALTERNATIVE = "android.intent.category.ALTERNATIVE";
+    /**
+     * Set if the activity should be considered as an alternative selection
+     * action to the data the user has currently selected.  This is like
+     * {@link #CATEGORY_ALTERNATIVE}, but is used in activities showing a list
+     * of items from which the user can select, giving them alternatives to the
+     * default action that will be performed on it.
+     */
+    @SdkConstant(SdkConstantType.INTENT_CATEGORY)
+    public static final String CATEGORY_SELECTED_ALTERNATIVE = "android.intent.category.SELECTED_ALTERNATIVE";
+    /**
+     * Intended to be used as a tab inside of an containing TabActivity.
+     */
+    @SdkConstant(SdkConstantType.INTENT_CATEGORY)
+    public static final String CATEGORY_TAB = "android.intent.category.TAB";
+    /**
+     * This activity can be embedded inside of another activity that is hosting
+     * gadgets.
+     */
+    @SdkConstant(SdkConstantType.INTENT_CATEGORY)
+    public static final String CATEGORY_GADGET = "android.intent.category.GADGET";
+    /**
+     * Should be displayed in the top-level launcher.
+     */
+    @SdkConstant(SdkConstantType.INTENT_CATEGORY)
+    public static final String CATEGORY_LAUNCHER = "android.intent.category.LAUNCHER";
+    /**
+     * Provides information about the package it is in; typically used if
+     * a package does not contain a {@link #CATEGORY_LAUNCHER} to provide
+     * a front-door to the user without having to be shown in the all apps list.
+     */
+    @SdkConstant(SdkConstantType.INTENT_CATEGORY)
+    public static final String CATEGORY_INFO = "android.intent.category.INFO";
+    /**
+     * This is the home activity, that is the first activity that is displayed
+     * when the device boots.
+     */
+    @SdkConstant(SdkConstantType.INTENT_CATEGORY)
+    public static final String CATEGORY_HOME = "android.intent.category.HOME";
+    /**
+     * This activity is a preference panel.
+     */
+    @SdkConstant(SdkConstantType.INTENT_CATEGORY)
+    public static final String CATEGORY_PREFERENCE = "android.intent.category.PREFERENCE";
+    /**
+     * This activity is a development preference panel.
+     */
+    @SdkConstant(SdkConstantType.INTENT_CATEGORY)
+    public static final String CATEGORY_DEVELOPMENT_PREFERENCE = "android.intent.category.DEVELOPMENT_PREFERENCE";
+    /**
+     * Capable of running inside a parent activity container.
+     *
+     * <p>Note: being removed in favor of more explicit categories such as
+     * CATEGORY_GADGET
+     */
+    @SdkConstant(SdkConstantType.INTENT_CATEGORY)
+    public static final String CATEGORY_EMBED = "android.intent.category.EMBED";
+    /**
+     * This activity may be exercised by the monkey or other automated test tools.
+     */
+    @SdkConstant(SdkConstantType.INTENT_CATEGORY)
+    public static final String CATEGORY_MONKEY = "android.intent.category.MONKEY";
+    /**
+     * To be used as a test (not part of the normal user experience).
+     */
+    public static final String CATEGORY_TEST = "android.intent.category.TEST";
+    /**
+     * To be used as a unit test (run through the Test Harness).
+     */
+    public static final String CATEGORY_UNIT_TEST = "android.intent.category.UNIT_TEST";
+    /**
+     * To be used as an sample code example (not part of the normal user
+     * experience).
+     */
+    public static final String CATEGORY_SAMPLE_CODE = "android.intent.category.SAMPLE_CODE";
+    /**
+     * Used to indicate that a GET_CONTENT intent only wants URIs that can be opened with
+     * ContentResolver.openInputStream. Openable URIs must support the columns in OpenableColumns
+     * when queried, though it is allowable for those columns to be blank.
+     */
+    @SdkConstant(SdkConstantType.INTENT_CATEGORY)
+    public static final String CATEGORY_OPENABLE = "android.intent.category.OPENABLE";
+
+    /**
+     * To be used as code under test for framework instrumentation tests.
+     */
+    public static final String CATEGORY_FRAMEWORK_INSTRUMENTATION_TEST =
+            "android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST";
+    // ---------------------------------------------------------------------
+    // ---------------------------------------------------------------------
+    // Standard extra data keys.
+
+    /**
+     * The initial data to place in a newly created record.  Use with
+     * {@link #ACTION_INSERT}.  The data here is a Map containing the same
+     * fields as would be given to the underlying ContentProvider.insert()
+     * call.
+     */
+    public static final String EXTRA_TEMPLATE = "android.intent.extra.TEMPLATE";
+
+    /**
+     * A constant CharSequence that is associated with the Intent, used with
+     * {@link #ACTION_SEND} to supply the literal data to be sent.  Note that
+     * this may be a styled CharSequence, so you must use
+     * {@link Bundle#getCharSequence(String) Bundle.getCharSequence()} to
+     * retrieve it.
+     */
+    public static final String EXTRA_TEXT = "android.intent.extra.TEXT";
+
+    /**
+     * A content: URI holding a stream of data associated with the Intent,
+     * used with {@link #ACTION_SEND} to supply the data being sent.
+     */
+    public static final String EXTRA_STREAM = "android.intent.extra.STREAM";
+
+    /**
+     * A String[] holding e-mail addresses that should be delivered to.
+     */
+    public static final String EXTRA_EMAIL       = "android.intent.extra.EMAIL";
+
+    /**
+     * A String[] holding e-mail addresses that should be carbon copied.
+     */
+    public static final String EXTRA_CC       = "android.intent.extra.CC";
+
+    /**
+     * A String[] holding e-mail addresses that should be blind carbon copied.
+     */
+    public static final String EXTRA_BCC      = "android.intent.extra.BCC";
+
+    /**
+     * A constant string holding the desired subject line of a message.
+     */
+    public static final String EXTRA_SUBJECT  = "android.intent.extra.SUBJECT";
+
+    /**
+     * An Intent describing the choices you would like shown with
+     * {@link #ACTION_PICK_ACTIVITY}.
+     */
+    public static final String EXTRA_INTENT = "android.intent.extra.INTENT";
+
+    /**
+     * A CharSequence dialog title to provide to the user when used with a
+     * {@link #ACTION_CHOOSER}.
+     */
+    public static final String EXTRA_TITLE = "android.intent.extra.TITLE";
+
+    /**
+     * A {@link android.view.KeyEvent} object containing the event that
+     * triggered the creation of the Intent it is in.
+     */
+    public static final String EXTRA_KEY_EVENT = "android.intent.extra.KEY_EVENT";
+
+    /**
+     * Used as an boolean extra field in {@link android.content.Intent#ACTION_PACKAGE_REMOVED} or
+     * {@link android.content.Intent#ACTION_PACKAGE_CHANGED} intents to override the default action
+     * of restarting the application.
+     */
+    public static final String EXTRA_DONT_KILL_APP = "android.intent.extra.DONT_KILL_APP";
+
+    /**
+     * A String holding the phone number originally entered in
+     * {@link android.content.Intent#ACTION_NEW_OUTGOING_CALL}, or the actual
+     * number to call in a {@link android.content.Intent#ACTION_CALL}.
+     */
+    public static final String EXTRA_PHONE_NUMBER = "android.intent.extra.PHONE_NUMBER";
+    /**
+     * Used as an int extra field in {@link android.content.Intent#ACTION_UID_REMOVED}
+     * intents to supply the uid the package had been assigned.  Also an optional
+     * extra in {@link android.content.Intent#ACTION_PACKAGE_REMOVED} or
+     * {@link android.content.Intent#ACTION_PACKAGE_CHANGED} for the same
+     * purpose.
+     */
+    public static final String EXTRA_UID = "android.intent.extra.UID";
+
+    /**
+     * Used as a boolean extra field in {@link android.content.Intent#ACTION_PACKAGE_REMOVED}
+     * intents to indicate whether this represents a full uninstall (removing
+     * both the code and its data) or a partial uninstall (leaving its data,
+     * implying that this is an update).
+     */
+    public static final String EXTRA_DATA_REMOVED = "android.intent.extra.DATA_REMOVED";
+    
+    /**
+     * Used as a boolean extra field in {@link android.content.Intent#ACTION_PACKAGE_REMOVED}
+     * intents to indicate that this is a replacement of the package, so this
+     * broadcast will immediately be followed by an add broadcast for a
+     * different version of the same package.
+     */
+    public static final String EXTRA_REPLACING = "android.intent.extra.REPLACING";
+    
+    /**
+     * Used as an int extra field in {@link android.app.AlarmManager} intents
+     * to tell the application being invoked how many pending alarms are being
+     * delievered with the intent.  For one-shot alarms this will always be 1.
+     * For recurring alarms, this might be greater than 1 if the device was
+     * asleep or powered off at the time an earlier alarm would have been
+     * delivered.
+     */
+    public static final String EXTRA_ALARM_COUNT = "android.intent.extra.ALARM_COUNT";
+
+    /**
+     * Used as an int extra field in {@link android.content.Intent#ACTION_VOICE_COMMAND}
+     * intents to request which audio route the voice command should prefer.
+     * The value should be a route from {@link android.media.AudioManager}, for
+     * example ROUTE_BLUETOOTH_SCO. Providing this value is optional.
+     * {@hide pending API Council approval}
+     */
+    public static final String EXTRA_AUDIO_ROUTE = "android.intent.extra.AUDIO_ROUTE";
+
+    // ---------------------------------------------------------------------
+    // ---------------------------------------------------------------------
+    // Intent flags (see mFlags variable).
+
+    /**
+     * If set, the recipient of this Intent will be granted permission to
+     * perform read operations on the Uri in the Intent's data.
+     */
+    public static final int FLAG_GRANT_READ_URI_PERMISSION = 0x00000001;
+    /**
+     * If set, the recipient of this Intent will be granted permission to
+     * perform write operations on the Uri in the Intent's data.
+     */
+    public static final int FLAG_GRANT_WRITE_URI_PERMISSION = 0x00000002;
+    /**
+     * Can be set by the caller to indicate that this Intent is coming from
+     * a background operation, not from direct user interaction.
+     */
+    public static final int FLAG_FROM_BACKGROUND = 0x00000004;
+    /**
+     * A flag you can enable for debugging: when set, log messages will be
+     * printed during the resolution of this intent to show you what has
+     * been found to create the final resolved list.
+     */
+    public static final int FLAG_DEBUG_LOG_RESOLUTION = 0x00000008;
+
+    /**
+     * If set, the new activity is not kept in the history stack.  As soon as
+     * the user navigates away from it, the activity is finished.  This may also
+     * be set with the {@link android.R.styleable#AndroidManifestActivity_noHistory
+     * noHistory} attribute.
+     */
+    public static final int FLAG_ACTIVITY_NO_HISTORY = 0x40000000;
+    /**
+     * If set, the activity will not be launched if it is already running
+     * at the top of the history stack.
+     */
+    public static final int FLAG_ACTIVITY_SINGLE_TOP = 0x20000000;
+    /**
+     * If set, this activity will become the start of a new task on this
+     * history stack.  A task (from the activity that started it to the
+     * next task activity) defines an atomic group of activities that the
+     * user can move to.  Tasks can be moved to the foreground and background;
+     * all of the activities inside of a particular task always remain in
+     * the same order.  See 
+     * <a href="{@docRoot}guide/topics/fundamentals.html#acttask">Application Fundamentals:
+     * Activities and Tasks</a> for more details on tasks.
+     *
+     * <p>This flag is generally used by activities that want
+     * to present a "launcher" style behavior: they give the user a list of
+     * separate things that can be done, which otherwise run completely
+     * independently of the activity launching them.
+     *
+     * <p>When using this flag, if a task is already running for the activity
+     * you are now starting, then a new activity will not be started; instead,
+     * the current task will simply be brought to the front of the screen with
+     * the state it was last in.  See {@link #FLAG_ACTIVITY_MULTIPLE_TASK} for a flag
+     * to disable this behavior.
+     *
+     * <p>This flag can not be used when the caller is requesting a result from
+     * the activity being launched.
+     */
+    public static final int FLAG_ACTIVITY_NEW_TASK = 0x10000000;
+    /**
+     * <strong>Do not use this flag unless you are implementing your own
+     * top-level application launcher.</strong>  Used in conjunction with
+     * {@link #FLAG_ACTIVITY_NEW_TASK} to disable the
+     * behavior of bringing an existing task to the foreground.  When set,
+     * a new task is <em>always</em> started to host the Activity for the
+     * Intent, regardless of whether there is already an existing task running
+     * the same thing.
+     *
+     * <p><strong>Because the default system does not include graphical task management,
+     * you should not use this flag unless you provide some way for a user to
+     * return back to the tasks you have launched.</strong>
+     * 
+     * <p>This flag is ignored if
+     * {@link #FLAG_ACTIVITY_NEW_TASK} is not set.
+     *
+     * <p>See <a href="{@docRoot}guide/topics/fundamentals.html#acttask">Application Fundamentals:
+     * Activities and Tasks</a> for more details on tasks.
+     */
+    public static final int FLAG_ACTIVITY_MULTIPLE_TASK = 0x08000000;
+    /**
+     * If set, and the activity being launched is already running in the
+     * current task, then instead of launching a new instance of that activity,
+     * all of the other activities on top of it will be closed and this Intent
+     * will be delivered to the (now on top) old activity as a new Intent.
+     *
+     * <p>For example, consider a task consisting of the activities: A, B, C, D.
+     * If D calls startActivity() with an Intent that resolves to the component
+     * of activity B, then C and D will be finished and B receive the given
+     * Intent, resulting in the stack now being: A, B.
+     *
+     * <p>The currently running instance of task B in the above example will
+     * either receive the new intent you are starting here in its
+     * onNewIntent() method, or be itself finished and restarted with the
+     * new intent.  If it has declared its launch mode to be "multiple" (the
+     * default) it will be finished and re-created; for all other launch modes
+     * it will receive the Intent in the current instance.
+     *
+     * <p>This launch mode can also be used to good effect in conjunction with
+     * {@link #FLAG_ACTIVITY_NEW_TASK}: if used to start the root activity
+     * of a task, it will bring any currently running instance of that task
+     * to the foreground, and then clear it to its root state.  This is
+     * especially useful, for example, when launching an activity from the
+     * notification manager.
+     *
+     * <p>See <a href="{@docRoot}guide/topics/fundamentals.html#acttask">Application Fundamentals:
+     * Activities and Tasks</a> for more details on tasks.
+     */
+    public static final int FLAG_ACTIVITY_CLEAR_TOP = 0x04000000;
+    /**
+     * If set and this intent is being used to launch a new activity from an
+     * existing one, then the reply target of the existing activity will be
+     * transfered to the new activity.  This way the new activity can call
+     * {@link android.app.Activity#setResult} and have that result sent back to
+     * the reply target of the original activity.
+     */
+    public static final int FLAG_ACTIVITY_FORWARD_RESULT = 0x02000000;
+    /**
+     * If set and this intent is being used to launch a new activity from an
+     * existing one, the current activity will not be counted as the top
+     * activity for deciding whether the new intent should be delivered to
+     * the top instead of starting a new one.  The previous activity will
+     * be used as the top, with the assumption being that the current activity
+     * will finish itself immediately.
+     */
+    public static final int FLAG_ACTIVITY_PREVIOUS_IS_TOP = 0x01000000;
+    /**
+     * If set, the new activity is not kept in the list of recently launched
+     * activities.
+     */
+    public static final int FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS = 0x00800000;
+    /**
+     * This flag is not normally set by application code, but set for you by
+     * the system as described in the
+     * {@link android.R.styleable#AndroidManifestActivity_launchMode
+     * launchMode} documentation for the singleTask mode.
+     */
+    public static final int FLAG_ACTIVITY_BROUGHT_TO_FRONT = 0x00400000;
+    /**
+     * If set, and this activity is either being started in a new task or
+     * bringing to the top an existing task, then it will be launched as
+     * the front door of the task.  This will result in the application of
+     * any affinities needed to have that task in the proper state (either
+     * moving activities to or from it), or simply resetting that task to
+     * its initial state if needed.
+     */
+    public static final int FLAG_ACTIVITY_RESET_TASK_IF_NEEDED = 0x00200000;
+    /**
+     * This flag is not normally set by application code, but set for you by
+     * the system if this activity is being launched from history
+     * (longpress home key).
+     */
+    public static final int FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY = 0x00100000;
+    /**
+     * If set, this marks a point in the task's activity stack that should
+     * be cleared when the task is reset.  That is, the next time the task
+     * is broad to the foreground with
+     * {@link #FLAG_ACTIVITY_RESET_TASK_IF_NEEDED} (typically as a result of
+     * the user re-launching it from home), this activity and all on top of
+     * it will be finished so that the user does not return to them, but
+     * instead returns to whatever activity preceeded it.
+     * 
+     * <p>This is useful for cases where you have a logical break in your
+     * application.  For example, an e-mail application may have a command
+     * to view an attachment, which launches an image view activity to
+     * display it.  This activity should be part of the e-mail application's
+     * task, since it is a part of the task the user is involved in.  However,
+     * if the user leaves that task, and later selects the e-mail app from
+     * home, we may like them to return to the conversation they were
+     * viewing, not the picture attachment, since that is confusing.  By
+     * setting this flag when launching the image viewer, that viewer and
+     * any activities it starts will be removed the next time the user returns
+     * to mail.
+     */
+    public static final int FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET = 0x00080000;
+    /**
+     * If set, this flag will prevent the normal {@link android.app.Activity#onUserLeaveHint}
+     * callback from occurring on the current frontmost activity before it is
+     * paused as the newly-started activity is brought to the front.
+     * 
+     * <p>Typically, an activity can rely on that callback to indicate that an
+     * explicit user action has caused their activity to be moved out of the
+     * foreground. The callback marks an appropriate point in the activity's
+     * lifecycle for it to dismiss any notifications that it intends to display
+     * "until the user has seen them," such as a blinking LED.
+     * 
+     * <p>If an activity is ever started via any non-user-driven events such as
+     * phone-call receipt or an alarm handler, this flag should be passed to {@link
+     * Context#startActivity Context.startActivity}, ensuring that the pausing
+     * activity does not think the user has acknowledged its notification. 
+     */
+    public static final int FLAG_ACTIVITY_NO_USER_ACTION = 0x00040000;
+    /**
+     * If set in an Intent passed to {@link Context#startActivity Context.startActivity()},
+     * this flag will cause the launched activity to be brought to the front of its
+     * task's history stack if it is already running.
+     * 
+     * <p>For example, consider a task consisting of four activities: A, B, C, D.
+     * If D calls startActivity() with an Intent that resolves to the component
+     * of activity B, then B will be brought to the front of the history stack,
+     * with this resulting order:  A, C, D, B.
+     * 
+     * This flag will be ignored if {@link #FLAG_ACTIVITY_CLEAR_TOP} is also
+     * specified. 
+     */
+    public static final int FLAG_ACTIVITY_REORDER_TO_FRONT = 0X00020000;
+    /**
+     * If set, when sending a broadcast only registered receivers will be
+     * called -- no BroadcastReceiver components will be launched.
+     */
+    public static final int FLAG_RECEIVER_REGISTERED_ONLY = 0x40000000;
+    /**
+     * If set, when sending a broadcast <i>before boot has completed</i> only
+     * registered receivers will be called -- no BroadcastReceiver components
+     * will be launched.  Sticky intent state will be recorded properly even
+     * if no receivers wind up being called.  If {@link #FLAG_RECEIVER_REGISTERED_ONLY}
+     * is specified in the broadcast intent, this flag is unnecessary.
+     * 
+     * <p>This flag is only for use by system sevices as a convenience to
+     * avoid having to implement a more complex mechanism around detection
+     * of boot completion.
+     * 
+     * @hide
+     */
+    public static final int FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT = 0x20000000;
+
+    // ---------------------------------------------------------------------
+
+    private String mAction;
+    private Uri mData;
+    private String mType;
+    private ComponentName mComponent;
+    private int mFlags;
+    private HashSet<String> mCategories;
+    private Bundle mExtras;
+
+    // ---------------------------------------------------------------------
+
+    /**
+     * Create an empty intent.
+     */
+    public Intent() {
+    }
+
+    /**
+     * Copy constructor.
+     */
+    public Intent(Intent o) {
+        this.mAction = o.mAction;
+        this.mData = o.mData;
+        this.mType = o.mType;
+        this.mComponent = o.mComponent;
+        this.mFlags = o.mFlags;
+        if (o.mCategories != null) {
+            this.mCategories = new HashSet<String>(o.mCategories);
+        }
+        if (o.mExtras != null) {
+            this.mExtras = new Bundle(o.mExtras);
+        }
+    }
+
+    @Override
+    public Object clone() {
+        return new Intent(this);
+    }
+
+    private Intent(Intent o, boolean all) {
+        this.mAction = o.mAction;
+        this.mData = o.mData;
+        this.mType = o.mType;
+        this.mComponent = o.mComponent;
+        if (o.mCategories != null) {
+            this.mCategories = new HashSet<String>(o.mCategories);
+        }
+    }
+
+    /**
+     * Make a clone of only the parts of the Intent that are relevant for
+     * filter matching: the action, data, type, component, and categories.
+     */
+    public Intent cloneFilter() {
+        return new Intent(this, false);
+    }
+
+    /**
+     * Create an intent with a given action.  All other fields (data, type,
+     * class) are null.  Note that the action <em>must</em> be in a
+     * namespace because Intents are used globally in the system -- for
+     * example the system VIEW action is android.intent.action.VIEW; an
+     * application's custom action would be something like
+     * com.google.app.myapp.CUSTOM_ACTION.
+     *
+     * @param action The Intent action, such as ACTION_VIEW.
+     */
+    public Intent(String action) {
+        mAction = action;
+    }
+
+    /**
+     * Create an intent with a given action and for a given data url.  Note
+     * that the action <em>must</em> be in a namespace because Intents are
+     * used globally in the system -- for example the system VIEW action is
+     * android.intent.action.VIEW; an application's custom action would be
+     * something like com.google.app.myapp.CUSTOM_ACTION.
+     *
+     * @param action The Intent action, such as ACTION_VIEW.
+     * @param uri The Intent data URI.
+     */
+    public Intent(String action, Uri uri) {
+        mAction = action;
+        mData = uri;
+    }
+
+    /**
+     * Create an intent for a specific component.  All other fields (action, data,
+     * type, class) are null, though they can be modified later with explicit
+     * calls.  This provides a convenient way to create an intent that is
+     * intended to execute a hard-coded class name, rather than relying on the
+     * system to find an appropriate class for you; see {@link #setComponent}
+     * for more information on the repercussions of this.
+     *
+     * @param packageContext A Context of the application package implementing
+     * this class.
+     * @param cls The component class that is to be used for the intent.
+     *
+     * @see #setClass
+     * @see #setComponent
+     * @see #Intent(String, android.net.Uri , Context, Class)
+     */
+    public Intent(Context packageContext, Class<?> cls) {
+        mComponent = new ComponentName(packageContext, cls);
+    }
+
+    /**
+     * Create an intent for a specific component with a specified action and data.
+     * This is equivalent using {@link #Intent(String, android.net.Uri)} to
+     * construct the Intent and then calling {@link #setClass} to set its
+     * class.
+     *
+     * @param action The Intent action, such as ACTION_VIEW.
+     * @param uri The Intent data URI.
+     * @param packageContext A Context of the application package implementing
+     * this class.
+     * @param cls The component class that is to be used for the intent.
+     *
+     * @see #Intent(String, android.net.Uri)
+     * @see #Intent(Context, Class)
+     * @see #setClass
+     * @see #setComponent
+     */
+    public Intent(String action, Uri uri,
+            Context packageContext, Class<?> cls) {
+        mAction = action;
+        mData = uri;
+        mComponent = new ComponentName(packageContext, cls);
+    }
+
+    /**
+     * Create an intent from a URI.  This URI may encode the action,
+     * category, and other intent fields, if it was returned by toURI().  If
+     * the Intent was not generate by toURI(), its data will be the entire URI
+     * and its action will be ACTION_VIEW.
+     *
+     * <p>The URI given here must not be relative -- that is, it must include
+     * the scheme and full path.
+     *
+     * @param uri The URI to turn into an Intent.
+     *
+     * @return Intent The newly created Intent object.
+     *
+     * @see #toURI
+     */
+    public static Intent getIntent(String uri) throws URISyntaxException {
+        int i = 0;
+        try {
+            // simple case
+            i = uri.lastIndexOf("#");
+            if (i == -1) return new Intent(ACTION_VIEW, Uri.parse(uri));
+
+            // old format Intent URI
+            if (!uri.startsWith("#Intent;", i)) return getIntentOld(uri);
+            
+            // new format
+            Intent intent = new Intent(ACTION_VIEW);
+
+            // fetch data part, if present
+            if (i > 0) {
+                intent.mData = Uri.parse(uri.substring(0, i));
+            }
+            i += "#Intent;".length();
+            
+            // loop over contents of Intent, all name=value;
+            while (!uri.startsWith("end", i)) {
+                int eq = uri.indexOf('=', i);
+                int semi = uri.indexOf(';', eq);
+                String value = uri.substring(eq + 1, semi);
+
+                // action
+                if (uri.startsWith("action=", i)) {
+                    intent.mAction = value;
+                }
+
+                // categories
+                else if (uri.startsWith("category=", i)) {
+                    intent.addCategory(value);
+                }
+
+                // type
+                else if (uri.startsWith("type=", i)) {
+                    intent.mType = value;
+                }
+
+                // launch  flags
+                else if (uri.startsWith("launchFlags=", i)) {
+                    intent.mFlags = Integer.decode(value).intValue();
+                }
+
+                // component
+                else if (uri.startsWith("component=", i)) {
+                    intent.mComponent = ComponentName.unflattenFromString(value);
+                }
+                
+                // extra
+                else {
+                    String key = Uri.decode(uri.substring(i + 2, eq));
+                    value = Uri.decode(value);
+                    // create Bundle if it doesn't already exist
+                    if (intent.mExtras == null) intent.mExtras = new Bundle();
+                    Bundle b = intent.mExtras;
+                    // add EXTRA
+                    if      (uri.startsWith("S.", i)) b.putString(key, value);
+                    else if (uri.startsWith("B.", i)) b.putBoolean(key, Boolean.parseBoolean(value));
+                    else if (uri.startsWith("b.", i)) b.putByte(key, Byte.parseByte(value));
+                    else if (uri.startsWith("c.", i)) b.putChar(key, value.charAt(0));
+                    else if (uri.startsWith("d.", i)) b.putDouble(key, Double.parseDouble(value));
+                    else if (uri.startsWith("f.", i)) b.putFloat(key, Float.parseFloat(value));
+                    else if (uri.startsWith("i.", i)) b.putInt(key, Integer.parseInt(value));
+                    else if (uri.startsWith("l.", i)) b.putLong(key, Long.parseLong(value));
+                    else if (uri.startsWith("s.", i)) b.putShort(key, Short.parseShort(value));
+                    else throw new URISyntaxException(uri, "unknown EXTRA type", i);
+                }
+                
+                // move to the next item
+                i = semi + 1;
+            }
+
+            return intent;
+            
+        } catch (IndexOutOfBoundsException e) {
+            throw new URISyntaxException(uri, "illegal Intent URI format", i);
+        }
+    }
+    
+    public static Intent getIntentOld(String uri) throws URISyntaxException {
+        Intent intent;
+
+        int i = uri.lastIndexOf('#');
+        if (i >= 0) {
+            Uri data = null;
+            String action = null;
+            if (i > 0) {
+                data = Uri.parse(uri.substring(0, i));
+            }
+
+            i++;
+
+            if (uri.regionMatches(i, "action(", 0, 7)) {
+                i += 7;
+                int j = uri.indexOf(')', i);
+                action = uri.substring(i, j);
+                i = j + 1;
+            }
+
+            intent = new Intent(action, data);
+
+            if (uri.regionMatches(i, "categories(", 0, 11)) {
+                i += 11;
+                int j = uri.indexOf(')', i);
+                while (i < j) {
+                    int sep = uri.indexOf('!', i);
+                    if (sep < 0) sep = j;
+                    if (i < sep) {
+                        intent.addCategory(uri.substring(i, sep));
+                    }
+                    i = sep + 1;
+                }
+                i = j + 1;
+            }
+
+            if (uri.regionMatches(i, "type(", 0, 5)) {
+                i += 5;
+                int j = uri.indexOf(')', i);
+                intent.mType = uri.substring(i, j);
+                i = j + 1;
+            }
+
+            if (uri.regionMatches(i, "launchFlags(", 0, 12)) {
+                i += 12;
+                int j = uri.indexOf(')', i);
+                intent.mFlags = Integer.decode(uri.substring(i, j)).intValue();
+                i = j + 1;
+            }
+
+            if (uri.regionMatches(i, "component(", 0, 10)) {
+                i += 10;
+                int j = uri.indexOf(')', i);
+                int sep = uri.indexOf('!', i);
+                if (sep >= 0 && sep < j) {
+                    String pkg = uri.substring(i, sep);
+                    String cls = uri.substring(sep + 1, j);
+                    intent.mComponent = new ComponentName(pkg, cls);
+                }
+                i = j + 1;
+            }
+
+            if (uri.regionMatches(i, "extras(", 0, 7)) {
+                i += 7;
+                
+                final int closeParen = uri.indexOf(')', i);
+                if (closeParen == -1) throw new URISyntaxException(uri,
+                        "EXTRA missing trailing ')'", i);
+
+                while (i < closeParen) {
+                    // fetch the key value
+                    int j = uri.indexOf('=', i);
+                    if (j <= i + 1 || i >= closeParen) {
+                        throw new URISyntaxException(uri, "EXTRA missing '='", i);
+                    }
+                    char type = uri.charAt(i);
+                    i++;
+                    String key = uri.substring(i, j);
+                    i = j + 1;
+                    
+                    // get type-value
+                    j = uri.indexOf('!', i);
+                    if (j == -1 || j >= closeParen) j = closeParen;
+                    if (i >= j) throw new URISyntaxException(uri, "EXTRA missing '!'", i);
+                    String value = uri.substring(i, j);
+                    i = j;
+
+                    // create Bundle if it doesn't already exist
+                    if (intent.mExtras == null) intent.mExtras = new Bundle();
+                    
+                    // add item to bundle
+                    try {
+                        switch (type) {
+                            case 'S':
+                                intent.mExtras.putString(key, Uri.decode(value));
+                                break;
+                            case 'B':
+                                intent.mExtras.putBoolean(key, Boolean.parseBoolean(value));
+                                break;
+                            case 'b':
+                                intent.mExtras.putByte(key, Byte.parseByte(value));
+                                break;
+                            case 'c':
+                                intent.mExtras.putChar(key, Uri.decode(value).charAt(0));
+                                break;
+                            case 'd':
+                                intent.mExtras.putDouble(key, Double.parseDouble(value));
+                                break;
+                            case 'f':
+                                intent.mExtras.putFloat(key, Float.parseFloat(value));
+                                break;
+                            case 'i':
+                                intent.mExtras.putInt(key, Integer.parseInt(value));
+                                break;
+                            case 'l':
+                                intent.mExtras.putLong(key, Long.parseLong(value));
+                                break;
+                            case 's':
+                                intent.mExtras.putShort(key, Short.parseShort(value));
+                                break;
+                            default:
+                                throw new URISyntaxException(uri, "EXTRA has unknown type", i);
+                        }
+                    } catch (NumberFormatException e) {
+                        throw new URISyntaxException(uri, "EXTRA value can't be parsed", i);
+                    }
+                    
+                    char ch = uri.charAt(i);
+                    if (ch == ')') break;
+                    if (ch != '!') throw new URISyntaxException(uri, "EXTRA missing '!'", i);
+                    i++;
+                }
+            }
+
+            if (intent.mAction == null) {
+                // By default, if no action is specified, then use VIEW.
+                intent.mAction = ACTION_VIEW;
+            }
+
+        } else {
+            intent = new Intent(ACTION_VIEW, Uri.parse(uri));
+        }
+
+        return intent;
+    }
+
+    /**
+     * Retrieve the general action to be performed, such as
+     * {@link #ACTION_VIEW}.  The action describes the general way the rest of
+     * the information in the intent should be interpreted -- most importantly,
+     * what to do with the data returned by {@link #getData}.
+     *
+     * @return The action of this intent or null if none is specified.
+     *
+     * @see #setAction
+     */
+    public String getAction() {
+        return mAction;
+    }
+
+    /**
+     * Retrieve data this intent is operating on.  This URI specifies the name
+     * of the data; often it uses the content: scheme, specifying data in a
+     * content provider.  Other schemes may be handled by specific activities,
+     * such as http: by the web browser.
+     *
+     * @return The URI of the data this intent is targeting or null.
+     *
+     * @see #getScheme
+     * @see #setData
+     */
+    public Uri getData() {
+        return mData;
+    }
+
+    /**
+     * The same as {@link #getData()}, but returns the URI as an encoded
+     * String.
+     */
+    public String getDataString() {
+        return mData != null ? mData.toString() : null;
+    }
+
+    /**
+     * Return the scheme portion of the intent's data.  If the data is null or
+     * does not include a scheme, null is returned.  Otherwise, the scheme
+     * prefix without the final ':' is returned, i.e. "http".
+     *
+     * <p>This is the same as calling getData().getScheme() (and checking for
+     * null data).
+     *
+     * @return The scheme of this intent.
+     *
+     * @see #getData
+     */
+    public String getScheme() {
+        return mData != null ? mData.getScheme() : null;
+    }
+
+    /**
+     * Retrieve any explicit MIME type included in the intent.  This is usually
+     * null, as the type is determined by the intent data.
+     *
+     * @return If a type was manually set, it is returned; else null is
+     *         returned.
+     *
+     * @see #resolveType(ContentResolver)
+     * @see #setType
+     */
+    public String getType() {
+        return mType;
+    }
+
+    /**
+     * Return the MIME data type of this intent.  If the type field is
+     * explicitly set, that is simply returned.  Otherwise, if the data is set,
+     * the type of that data is returned.  If neither fields are set, a null is
+     * returned.
+     *
+     * @return The MIME type of this intent.
+     *
+     * @see #getType
+     * @see #resolveType(ContentResolver)
+     */
+    public String resolveType(Context context) {
+        return resolveType(context.getContentResolver());
+    }
+
+    /**
+     * Return the MIME data type of this intent.  If the type field is
+     * explicitly set, that is simply returned.  Otherwise, if the data is set,
+     * the type of that data is returned.  If neither fields are set, a null is
+     * returned.
+     *
+     * @param resolver A ContentResolver that can be used to determine the MIME
+     *                 type of the intent's data.
+     *
+     * @return The MIME type of this intent.
+     *
+     * @see #getType
+     * @see #resolveType(Context)
+     */
+    public String resolveType(ContentResolver resolver) {
+        if (mType != null) {
+            return mType;
+        }
+        if (mData != null) {
+            if ("content".equals(mData.getScheme())) {
+                return resolver.getType(mData);
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Return the MIME data type of this intent, only if it will be needed for
+     * intent resolution.  This is not generally useful for application code;
+     * it is used by the frameworks for communicating with back-end system
+     * services.
+     *
+     * @param resolver A ContentResolver that can be used to determine the MIME
+     *                 type of the intent's data.
+     *
+     * @return The MIME type of this intent, or null if it is unknown or not
+     *         needed.
+     */
+    public String resolveTypeIfNeeded(ContentResolver resolver) {
+        if (mComponent != null) {
+            return mType;
+        }
+        return resolveType(resolver);
+    }
+
+    /**
+     * Check if an category exists in the intent.
+     *
+     * @param category The category to check.
+     *
+     * @return boolean True if the intent contains the category, else false.
+     *
+     * @see #getCategories
+     * @see #addCategory
+     */
+    public boolean hasCategory(String category) {
+        return mCategories != null && mCategories.contains(category);
+    }
+
+    /**
+     * Return the set of all categories in the intent.  If there are no categories,
+     * returns NULL.
+     *
+     * @return Set The set of categories you can examine.  Do not modify!
+     *
+     * @see #hasCategory
+     * @see #addCategory
+     */
+    public Set<String> getCategories() {
+        return mCategories;
+    }
+
+    /**
+     * Sets the ClassLoader that will be used when unmarshalling
+     * any Parcelable values from the extras of this Intent.
+     *
+     * @param loader a ClassLoader, or null to use the default loader
+     * at the time of unmarshalling.
+     */
+    public void setExtrasClassLoader(ClassLoader loader) {
+        if (mExtras != null) {
+            mExtras.setClassLoader(loader);
+        }
+    }
+
+    /**
+     * Returns true if an extra value is associated with the given name.
+     * @param name the extra's name
+     * @return true if the given extra is present.
+     */
+    public boolean hasExtra(String name) {
+        return mExtras != null && mExtras.containsKey(name);
+    }
+
+    /**
+     * Returns true if the Intent's extras contain a parcelled file descriptor.
+     * @return true if the Intent contains a parcelled file descriptor.
+     */
+    public boolean hasFileDescriptors() {
+        return mExtras != null && mExtras.hasFileDescriptors();
+    }
+    
+    /**
+     * Retrieve extended data from the intent.
+     *
+     * @param name The name of the desired item.
+     *
+     * @return the value of an item that previously added with putExtra()
+     * or null if none was found.
+     *
+     * @deprecated
+     * @hide
+     */
+    @Deprecated
+    public Object getExtra(String name) {
+        return getExtra(name, null);
+    }
+
+    /**
+     * Retrieve extended data from the intent.
+     *
+     * @param name The name of the desired item.
+     * @param defaultValue the value to be returned if no value of the desired
+     * type is stored with the given name.
+     *
+     * @return the value of an item that previously added with putExtra()
+     * or the default value if none was found.
+     *
+     * @see #putExtra(String, boolean)
+     */
+    public boolean getBooleanExtra(String name, boolean defaultValue) {
+        return mExtras == null ? defaultValue :
+            mExtras.getBoolean(name, defaultValue);
+    }
+
+    /**
+     * Retrieve extended data from the intent.
+     *
+     * @param name The name of the desired item.
+     * @param defaultValue the value to be returned if no value of the desired
+     * type is stored with the given name.
+     *
+     * @return the value of an item that previously added with putExtra()
+     * or the default value if none was found.
+     *
+     * @see #putExtra(String, byte)
+     */
+    public byte getByteExtra(String name, byte defaultValue) {
+        return mExtras == null ? defaultValue :
+            mExtras.getByte(name, defaultValue);
+    }
+
+    /**
+     * Retrieve extended data from the intent.
+     *
+     * @param name The name of the desired item.
+     * @param defaultValue the value to be returned if no value of the desired
+     * type is stored with the given name.
+     *
+     * @return the value of an item that previously added with putExtra()
+     * or the default value if none was found.
+     *
+     * @see #putExtra(String, short)
+     */
+    public short getShortExtra(String name, short defaultValue) {
+        return mExtras == null ? defaultValue :
+            mExtras.getShort(name, defaultValue);
+    }
+
+    /**
+     * Retrieve extended data from the intent.
+     *
+     * @param name The name of the desired item.
+     * @param defaultValue the value to be returned if no value of the desired
+     * type is stored with the given name.
+     *
+     * @return the value of an item that previously added with putExtra()
+     * or the default value if none was found.
+     *
+     * @see #putExtra(String, char)
+     */
+    public char getCharExtra(String name, char defaultValue) {
+        return mExtras == null ? defaultValue :
+            mExtras.getChar(name, defaultValue);
+    }
+
+    /**
+     * Retrieve extended data from the intent.
+     *
+     * @param name The name of the desired item.
+     * @param defaultValue the value to be returned if no value of the desired
+     * type is stored with the given name.
+     *
+     * @return the value of an item that previously added with putExtra()
+     * or the default value if none was found.
+     *
+     * @see #putExtra(String, int)
+     */
+    public int getIntExtra(String name, int defaultValue) {
+        return mExtras == null ? defaultValue :
+            mExtras.getInt(name, defaultValue);
+    }
+
+    /**
+     * Retrieve extended data from the intent.
+     *
+     * @param name The name of the desired item.
+     * @param defaultValue the value to be returned if no value of the desired
+     * type is stored with the given name.
+     *
+     * @return the value of an item that previously added with putExtra()
+     * or the default value if none was found.
+     *
+     * @see #putExtra(String, long)
+     */
+    public long getLongExtra(String name, long defaultValue) {
+        return mExtras == null ? defaultValue :
+            mExtras.getLong(name, defaultValue);
+    }
+
+    /**
+     * Retrieve extended data from the intent.
+     *
+     * @param name The name of the desired item.
+     * @param defaultValue the value to be returned if no value of the desired
+     * type is stored with the given name.
+     *
+     * @return the value of an item that previously added with putExtra(),
+     * or the default value if no such item is present
+     *
+     * @see #putExtra(String, float)
+     */
+    public float getFloatExtra(String name, float defaultValue) {
+        return mExtras == null ? defaultValue :
+            mExtras.getFloat(name, defaultValue);
+    }
+
+    /**
+     * Retrieve extended data from the intent.
+     *
+     * @param name The name of the desired item.
+     * @param defaultValue the value to be returned if no value of the desired
+     * type is stored with the given name.
+     *
+     * @return the value of an item that previously added with putExtra()
+     * or the default value if none was found.
+     *
+     * @see #putExtra(String, double)
+     */
+    public double getDoubleExtra(String name, double defaultValue) {
+        return mExtras == null ? defaultValue :
+            mExtras.getDouble(name, defaultValue);
+    }
+
+    /**
+     * Retrieve extended data from the intent.
+     *
+     * @param name The name of the desired item.
+     *
+     * @return the value of an item that previously added with putExtra()
+     * or null if no String value was found.
+     *
+     * @see #putExtra(String, String)
+     */
+    public String getStringExtra(String name) {
+        return mExtras == null ? null : mExtras.getString(name);
+    }
+
+    /**
+     * Retrieve extended data from the intent.
+     *
+     * @param name The name of the desired item.
+     *
+     * @return the value of an item that previously added with putExtra()
+     * or null if no CharSequence value was found.
+     *
+     * @see #putExtra(String, CharSequence)
+     */
+    public CharSequence getCharSequenceExtra(String name) {
+        return mExtras == null ? null : mExtras.getCharSequence(name);
+    }
+
+    /**
+     * Retrieve extended data from the intent.
+     *
+     * @param name The name of the desired item.
+     *
+     * @return the value of an item that previously added with putExtra()
+     * or null if no Parcelable value was found.
+     *
+     * @see #putExtra(String, Parcelable)
+     */
+    public <T extends Parcelable> T getParcelableExtra(String name) {
+        return mExtras == null ? null : mExtras.<T>getParcelable(name);
+    }
+
+    /**
+     * Retrieve extended data from the intent.
+     *
+     * @param name The name of the desired item.
+     *
+     * @return the value of an item that previously added with putExtra()
+     * or null if no Parcelable[] value was found.
+     *
+     * @see #putExtra(String, Parcelable[])
+     */
+    public Parcelable[] getParcelableArrayExtra(String name) {
+        return mExtras == null ? null : mExtras.getParcelableArray(name);
+    }
+
+    /**
+     * Retrieve extended data from the intent.
+     *
+     * @param name The name of the desired item.
+     *
+     * @return the value of an item that previously added with putExtra()
+     * or null if no ArrayList<Parcelable> value was found.
+     *
+     * @see #putParcelableArrayListExtra(String, ArrayList)
+     */
+    public <T extends Parcelable> ArrayList<T> getParcelableArrayListExtra(String name) {
+        return mExtras == null ? null : mExtras.<T>getParcelableArrayList(name);
+    }
+
+    /**
+     * Retrieve extended data from the intent.
+     *
+     * @param name The name of the desired item.
+     *
+     * @return the value of an item that previously added with putExtra()
+     * or null if no Serializable value was found.
+     *
+     * @see #putExtra(String, Serializable)
+     */
+    public Serializable getSerializableExtra(String name) {
+        return mExtras == null ? null : mExtras.getSerializable(name);
+    }
+
+    /**
+     * Retrieve extended data from the intent.
+     *
+     * @param name The name of the desired item.
+     *
+     * @return the value of an item that previously added with putExtra()
+     * or null if no ArrayList<Integer> value was found.
+     *
+     * @see #putIntegerArrayListExtra(String, ArrayList)
+     */
+    public ArrayList<Integer> getIntegerArrayListExtra(String name) {
+        return mExtras == null ? null : mExtras.getIntegerArrayList(name);
+    }
+
+    /**
+     * Retrieve extended data from the intent.
+     *
+     * @param name The name of the desired item.
+     *
+     * @return the value of an item that previously added with putExtra()
+     * or null if no ArrayList<String> value was found.
+     *
+     * @see #putStringArrayListExtra(String, ArrayList)
+     */
+    public ArrayList<String> getStringArrayListExtra(String name) {
+        return mExtras == null ? null : mExtras.getStringArrayList(name);
+    }
+
+    /**
+     * Retrieve extended data from the intent.
+     *
+     * @param name The name of the desired item.
+     *
+     * @return the value of an item that previously added with putExtra()
+     * or null if no boolean array value was found.
+     *
+     * @see #putExtra(String, boolean[])
+     */
+    public boolean[] getBooleanArrayExtra(String name) {
+        return mExtras == null ? null : mExtras.getBooleanArray(name);
+    }
+
+    /**
+     * Retrieve extended data from the intent.
+     *
+     * @param name The name of the desired item.
+     *
+     * @return the value of an item that previously added with putExtra()
+     * or null if no byte array value was found.
+     *
+     * @see #putExtra(String, byte[])
+     */
+    public byte[] getByteArrayExtra(String name) {
+        return mExtras == null ? null : mExtras.getByteArray(name);
+    }
+
+    /**
+     * Retrieve extended data from the intent.
+     *
+     * @param name The name of the desired item.
+     *
+     * @return the value of an item that previously added with putExtra()
+     * or null if no short array value was found.
+     *
+     * @see #putExtra(String, short[])
+     */
+    public short[] getShortArrayExtra(String name) {
+        return mExtras == null ? null : mExtras.getShortArray(name);
+    }
+
+    /**
+     * Retrieve extended data from the intent.
+     *
+     * @param name The name of the desired item.
+     *
+     * @return the value of an item that previously added with putExtra()
+     * or null if no char array value was found.
+     *
+     * @see #putExtra(String, char[])
+     */
+    public char[] getCharArrayExtra(String name) {
+        return mExtras == null ? null : mExtras.getCharArray(name);
+    }
+
+    /**
+     * Retrieve extended data from the intent.
+     *
+     * @param name The name of the desired item.
+     *
+     * @return the value of an item that previously added with putExtra()
+     * or null if no int array value was found.
+     *
+     * @see #putExtra(String, int[])
+     */
+    public int[] getIntArrayExtra(String name) {
+        return mExtras == null ? null : mExtras.getIntArray(name);
+    }
+
+    /**
+     * Retrieve extended data from the intent.
+     *
+     * @param name The name of the desired item.
+     *
+     * @return the value of an item that previously added with putExtra()
+     * or null if no long array value was found.
+     *
+     * @see #putExtra(String, long[])
+     */
+    public long[] getLongArrayExtra(String name) {
+        return mExtras == null ? null : mExtras.getLongArray(name);
+    }
+
+    /**
+     * Retrieve extended data from the intent.
+     *
+     * @param name The name of the desired item.
+     *
+     * @return the value of an item that previously added with putExtra()
+     * or null if no float array value was found.
+     *
+     * @see #putExtra(String, float[])
+     */
+    public float[] getFloatArrayExtra(String name) {
+        return mExtras == null ? null : mExtras.getFloatArray(name);
+    }
+
+    /**
+     * Retrieve extended data from the intent.
+     *
+     * @param name The name of the desired item.
+     *
+     * @return the value of an item that previously added with putExtra()
+     * or null if no double array value was found.
+     *
+     * @see #putExtra(String, double[])
+     */
+    public double[] getDoubleArrayExtra(String name) {
+        return mExtras == null ? null : mExtras.getDoubleArray(name);
+    }
+
+    /**
+     * Retrieve extended data from the intent.
+     *
+     * @param name The name of the desired item.
+     *
+     * @return the value of an item that previously added with putExtra()
+     * or null if no String array value was found.
+     *
+     * @see #putExtra(String, String[])
+     */
+    public String[] getStringArrayExtra(String name) {
+        return mExtras == null ? null : mExtras.getStringArray(name);
+    }
+
+    /**
+     * Retrieve extended data from the intent.
+     *
+     * @param name The name of the desired item.
+     *
+     * @return the value of an item that previously added with putExtra()
+     * or null if no Bundle value was found.
+     *
+     * @see #putExtra(String, Bundle)
+     */
+    public Bundle getBundleExtra(String name) {
+        return mExtras == null ? null : mExtras.getBundle(name);
+    }
+
+    /**
+     * Retrieve extended data from the intent.
+     *
+     * @param name The name of the desired item.
+     *
+     * @return the value of an item that previously added with putExtra()
+     * or null if no IBinder value was found.
+     *
+     * @see #putExtra(String, IBinder)
+     *
+     * @deprecated
+     * @hide
+     */
+    @Deprecated
+    public IBinder getIBinderExtra(String name) {
+        return mExtras == null ? null : mExtras.getIBinder(name);
+    }
+
+    /**
+     * Retrieve extended data from the intent.
+     *
+     * @param name The name of the desired item.
+     * @param defaultValue The default value to return in case no item is
+     * associated with the key 'name'
+     *
+     * @return the value of an item that previously added with putExtra()
+     * or defaultValue if none was found.
+     *
+     * @see #putExtra
+     *
+     * @deprecated
+     * @hide
+     */
+    @Deprecated
+    public Object getExtra(String name, Object defaultValue) {
+        Object result = defaultValue;
+        if (mExtras != null) {
+            Object result2 = mExtras.get(name);
+            if (result2 != null) {
+                result = result2;
+            }
+        }
+
+        return result;
+    }
+
+    /**
+     * Retrieves a map of extended data from the intent.
+     *
+     * @return the map of all extras previously added with putExtra(),
+     * or null if none have been added.
+     */
+    public Bundle getExtras() {
+        return (mExtras != null)
+                ? new Bundle(mExtras)
+                : null;
+    }
+
+    /**
+     * Retrieve any special flags associated with this intent.  You will
+     * normally just set them with {@link #setFlags} and let the system
+     * take the appropriate action with them.
+     *
+     * @return int The currently set flags.
+     *
+     * @see #setFlags
+     */
+    public int getFlags() {
+        return mFlags;
+    }
+
+    /**
+     * Retrieve the concrete component associated with the intent.  When receiving
+     * an intent, this is the component that was found to best handle it (that is,
+     * yourself) and will always be non-null; in all other cases it will be
+     * null unless explicitly set.
+     *
+     * @return The name of the application component to handle the intent.
+     *
+     * @see #resolveActivity
+     * @see #setComponent
+     */
+    public ComponentName getComponent() {
+        return mComponent;
+    }
+
+    /**
+     * Return the Activity component that should be used to handle this intent.
+     * The appropriate component is determined based on the information in the
+     * intent, evaluated as follows:
+     *
+     * <p>If {@link #getComponent} returns an explicit class, that is returned
+     * without any further consideration.
+     *
+     * <p>The activity must handle the {@link Intent#CATEGORY_DEFAULT} Intent
+     * category to be considered.
+     *
+     * <p>If {@link #getAction} is non-NULL, the activity must handle this
+     * action.
+     *
+     * <p>If {@link #resolveType} returns non-NULL, the activity must handle
+     * this type.
+     *
+     * <p>If {@link #addCategory} has added any categories, the activity must
+     * handle ALL of the categories specified.
+     *
+     * <p>If there are no activities that satisfy all of these conditions, a
+     * null string is returned.
+     *
+     * <p>If multiple activities are found to satisfy the intent, the one with
+     * the highest priority will be used.  If there are multiple activities
+     * with the same priority, the system will either pick the best activity
+     * based on user preference, or resolve to a system class that will allow
+     * the user to pick an activity and forward from there.
+     *
+     * <p>This method is implemented simply by calling
+     * {@link PackageManager#resolveActivity} with the "defaultOnly" parameter
+     * true.</p>
+     * <p> This API is called for you as part of starting an activity from an
+     * intent.  You do not normally need to call it yourself.</p>
+     *
+     * @param pm The package manager with which to resolve the Intent.
+     *
+     * @return Name of the component implementing an activity that can
+     *         display the intent.
+     *
+     * @see #setComponent
+     * @see #getComponent
+     * @see #resolveActivityInfo
+     */
+    public ComponentName resolveActivity(PackageManager pm) {
+        if (mComponent != null) {
+            return mComponent;
+        }
+
+        ResolveInfo info = pm.resolveActivity(
+            this, PackageManager.MATCH_DEFAULT_ONLY);
+        if (info != null) {
+            return new ComponentName(
+                    info.activityInfo.applicationInfo.packageName,
+                    info.activityInfo.name);
+        }
+
+        return null;
+    }
+
+    /**
+     * Resolve the Intent into an {@link ActivityInfo}
+     * describing the activity that should execute the intent.  Resolution
+     * follows the same rules as described for {@link #resolveActivity}, but
+     * you get back the completely information about the resolved activity
+     * instead of just its class name.
+     *
+     * @param pm The package manager with which to resolve the Intent.
+     * @param flags Addition information to retrieve as per
+     * {@link PackageManager#getActivityInfo(ComponentName, int)
+     * PackageManager.getActivityInfo()}.
+     *
+     * @return PackageManager.ActivityInfo
+     *
+     * @see #resolveActivity
+     */
+    public ActivityInfo resolveActivityInfo(PackageManager pm, int flags) {
+        ActivityInfo ai = null;
+        if (mComponent != null) {
+            try {
+                ai = pm.getActivityInfo(mComponent, flags);
+            } catch (PackageManager.NameNotFoundException e) {
+                // ignore
+            }
+        } else {
+            ResolveInfo info = pm.resolveActivity(
+                this, PackageManager.MATCH_DEFAULT_ONLY);
+            if (info != null) {
+                ai = info.activityInfo;
+            }
+        }
+
+        return ai;
+    }
+
+    /**
+     * Set the general action to be performed.
+     *
+     * @param action An action name, such as ACTION_VIEW.  Application-specific
+     *               actions should be prefixed with the vendor's package name.
+     *
+     * @return Returns the same Intent object, for chaining multiple calls
+     * into a single statement.
+     *
+     * @see #getAction
+     */
+    public Intent setAction(String action) {
+        mAction = action;
+        return this;
+    }
+
+    /**
+     * Set the data this intent is operating on.  This method automatically
+     * clears any type that was previously set by {@link #setType}.
+     *
+     * @param data The URI of the data this intent is now targeting.
+     *
+     * @return Returns the same Intent object, for chaining multiple calls
+     * into a single statement.
+     *
+     * @see #getData
+     * @see #setType
+     * @see #setDataAndType
+     */
+    public Intent setData(Uri data) {
+        mData = data;
+        mType = null;
+        return this;
+    }
+
+    /**
+     * Set an explicit MIME data type.  This is used to create intents that
+     * only specify a type and not data, for example to indicate the type of
+     * data to return.  This method automatically clears any data that was
+     * previously set by {@link #setData}.
+     *
+     * @param type The MIME type of the data being handled by this intent.
+     *
+     * @return Returns the same Intent object, for chaining multiple calls
+     * into a single statement.
+     *
+     * @see #getType
+     * @see #setData
+     * @see #setDataAndType
+     */
+    public Intent setType(String type) {
+        mData = null;
+        mType = type;
+        return this;
+    }
+
+    /**
+     * (Usually optional) Set the data for the intent along with an explicit
+     * MIME data type.  This method should very rarely be used -- it allows you
+     * to override the MIME type that would ordinarily be inferred from the
+     * data with your own type given here.
+     *
+     * @param data The URI of the data this intent is now targeting.
+     * @param type The MIME type of the data being handled by this intent.
+     *
+     * @return Returns the same Intent object, for chaining multiple calls
+     * into a single statement.
+     *
+     * @see #setData
+     * @see #setType
+     */
+    public Intent setDataAndType(Uri data, String type) {
+        mData = data;
+        mType = type;
+        return this;
+    }
+
+    /**
+     * Add a new category to the intent.  Categories provide additional detail
+     * about the action the intent is perform.  When resolving an intent, only
+     * activities that provide <em>all</em> of the requested categories will be
+     * used.
+     *
+     * @param category The desired category.  This can be either one of the
+     *               predefined Intent categories, or a custom category in your own
+     *               namespace.
+     *
+     * @return Returns the same Intent object, for chaining multiple calls
+     * into a single statement.
+     *
+     * @see #hasCategory
+     * @see #removeCategory
+     */
+    public Intent addCategory(String category) {
+        if (mCategories == null) {
+            mCategories = new HashSet<String>();
+        }
+        mCategories.add(category);
+        return this;
+    }
+
+    /**
+     * Remove an category from an intent.
+     *
+     * @param category The category to remove.
+     *
+     * @see #addCategory
+     */
+    public void removeCategory(String category) {
+        if (mCategories != null) {
+            mCategories.remove(category);
+            if (mCategories.size() == 0) {
+                mCategories = null;
+            }
+        }
+    }
+
+    /**
+     * Add extended data to the intent.  The name must include a package
+     * prefix, for example the app com.android.contacts would use names
+     * like "com.android.contacts.ShowAll".
+     *
+     * @param name The name of the extra data, with package prefix.
+     * @param value The boolean data value.
+     *
+     * @return Returns the same Intent object, for chaining multiple calls
+     * into a single statement.
+     *
+     * @see #putExtras
+     * @see #removeExtra
+     * @see #getBooleanExtra(String, boolean)
+     */
+    public Intent putExtra(String name, boolean value) {
+        if (mExtras == null) {
+            mExtras = new Bundle();
+        }
+        mExtras.putBoolean(name, value);
+        return this;
+    }
+
+    /**
+     * Add extended data to the intent.  The name must include a package
+     * prefix, for example the app com.android.contacts would use names
+     * like "com.android.contacts.ShowAll".
+     *
+     * @param name The name of the extra data, with package prefix.
+     * @param value The byte data value.
+     *
+     * @return Returns the same Intent object, for chaining multiple calls
+     * into a single statement.
+     *
+     * @see #putExtras
+     * @see #removeExtra
+     * @see #getByteExtra(String, byte)
+     */
+    public Intent putExtra(String name, byte value) {
+        if (mExtras == null) {
+            mExtras = new Bundle();
+        }
+        mExtras.putByte(name, value);
+        return this;
+    }
+
+    /**
+     * Add extended data to the intent.  The name must include a package
+     * prefix, for example the app com.android.contacts would use names
+     * like "com.android.contacts.ShowAll".
+     *
+     * @param name The name of the extra data, with package prefix.
+     * @param value The char data value.
+     *
+     * @return Returns the same Intent object, for chaining multiple calls
+     * into a single statement.
+     *
+     * @see #putExtras
+     * @see #removeExtra
+     * @see #getCharExtra(String, char)
+     */
+    public Intent putExtra(String name, char value) {
+        if (mExtras == null) {
+            mExtras = new Bundle();
+        }
+        mExtras.putChar(name, value);
+        return this;
+    }
+
+    /**
+     * Add extended data to the intent.  The name must include a package
+     * prefix, for example the app com.android.contacts would use names
+     * like "com.android.contacts.ShowAll".
+     *
+     * @param name The name of the extra data, with package prefix.
+     * @param value The short data value.
+     *
+     * @return Returns the same Intent object, for chaining multiple calls
+     * into a single statement.
+     *
+     * @see #putExtras
+     * @see #removeExtra
+     * @see #getShortExtra(String, short)
+     */
+    public Intent putExtra(String name, short value) {
+        if (mExtras == null) {
+            mExtras = new Bundle();
+        }
+        mExtras.putShort(name, value);
+        return this;
+    }
+
+    /**
+     * Add extended data to the intent.  The name must include a package
+     * prefix, for example the app com.android.contacts would use names
+     * like "com.android.contacts.ShowAll".
+     *
+     * @param name The name of the extra data, with package prefix.
+     * @param value The integer data value.
+     *
+     * @return Returns the same Intent object, for chaining multiple calls
+     * into a single statement.
+     *
+     * @see #putExtras
+     * @see #removeExtra
+     * @see #getIntExtra(String, int)
+     */
+    public Intent putExtra(String name, int value) {
+        if (mExtras == null) {
+            mExtras = new Bundle();
+        }
+        mExtras.putInt(name, value);
+        return this;
+    }
+
+    /**
+     * Add extended data to the intent.  The name must include a package
+     * prefix, for example the app com.android.contacts would use names
+     * like "com.android.contacts.ShowAll".
+     *
+     * @param name The name of the extra data, with package prefix.
+     * @param value The long data value.
+     *
+     * @return Returns the same Intent object, for chaining multiple calls
+     * into a single statement.
+     *
+     * @see #putExtras
+     * @see #removeExtra
+     * @see #getLongExtra(String, long)
+     */
+    public Intent putExtra(String name, long value) {
+        if (mExtras == null) {
+            mExtras = new Bundle();
+        }
+        mExtras.putLong(name, value);
+        return this;
+    }
+
+    /**
+     * Add extended data to the intent.  The name must include a package
+     * prefix, for example the app com.android.contacts would use names
+     * like "com.android.contacts.ShowAll".
+     *
+     * @param name The name of the extra data, with package prefix.
+     * @param value The float data value.
+     *
+     * @return Returns the same Intent object, for chaining multiple calls
+     * into a single statement.
+     *
+     * @see #putExtras
+     * @see #removeExtra
+     * @see #getFloatExtra(String, float)
+     */
+    public Intent putExtra(String name, float value) {
+        if (mExtras == null) {
+            mExtras = new Bundle();
+        }
+        mExtras.putFloat(name, value);
+        return this;
+    }
+
+    /**
+     * Add extended data to the intent.  The name must include a package
+     * prefix, for example the app com.android.contacts would use names
+     * like "com.android.contacts.ShowAll".
+     *
+     * @param name The name of the extra data, with package prefix.
+     * @param value The double data value.
+     *
+     * @return Returns the same Intent object, for chaining multiple calls
+     * into a single statement.
+     *
+     * @see #putExtras
+     * @see #removeExtra
+     * @see #getDoubleExtra(String, double)
+     */
+    public Intent putExtra(String name, double value) {
+        if (mExtras == null) {
+            mExtras = new Bundle();
+        }
+        mExtras.putDouble(name, value);
+        return this;
+    }
+
+    /**
+     * Add extended data to the intent.  The name must include a package
+     * prefix, for example the app com.android.contacts would use names
+     * like "com.android.contacts.ShowAll".
+     *
+     * @param name The name of the extra data, with package prefix.
+     * @param value The String data value.
+     *
+     * @return Returns the same Intent object, for chaining multiple calls
+     * into a single statement.
+     *
+     * @see #putExtras
+     * @see #removeExtra
+     * @see #getStringExtra(String)
+     */
+    public Intent putExtra(String name, String value) {
+        if (mExtras == null) {
+            mExtras = new Bundle();
+        }
+        mExtras.putString(name, value);
+        return this;
+    }
+
+    /**
+     * Add extended data to the intent.  The name must include a package
+     * prefix, for example the app com.android.contacts would use names
+     * like "com.android.contacts.ShowAll".
+     *
+     * @param name The name of the extra data, with package prefix.
+     * @param value The CharSequence data value.
+     *
+     * @return Returns the same Intent object, for chaining multiple calls
+     * into a single statement.
+     *
+     * @see #putExtras
+     * @see #removeExtra
+     * @see #getCharSequenceExtra(String)
+     */
+    public Intent putExtra(String name, CharSequence value) {
+        if (mExtras == null) {
+            mExtras = new Bundle();
+        }
+        mExtras.putCharSequence(name, value);
+        return this;
+    }
+
+    /**
+     * Add extended data to the intent.  The name must include a package
+     * prefix, for example the app com.android.contacts would use names
+     * like "com.android.contacts.ShowAll".
+     *
+     * @param name The name of the extra data, with package prefix.
+     * @param value The Parcelable data value.
+     *
+     * @return Returns the same Intent object, for chaining multiple calls
+     * into a single statement.
+     *
+     * @see #putExtras
+     * @see #removeExtra
+     * @see #getParcelableExtra(String)
+     */
+    public Intent putExtra(String name, Parcelable value) {
+        if (mExtras == null) {
+            mExtras = new Bundle();
+        }
+        mExtras.putParcelable(name, value);
+        return this;
+    }
+
+    /**
+     * Add extended data to the intent.  The name must include a package
+     * prefix, for example the app com.android.contacts would use names
+     * like "com.android.contacts.ShowAll".
+     *
+     * @param name The name of the extra data, with package prefix.
+     * @param value The Parcelable[] data value.
+     *
+     * @return Returns the same Intent object, for chaining multiple calls
+     * into a single statement.
+     *
+     * @see #putExtras
+     * @see #removeExtra
+     * @see #getParcelableArrayExtra(String)
+     */
+    public Intent putExtra(String name, Parcelable[] value) {
+        if (mExtras == null) {
+            mExtras = new Bundle();
+        }
+        mExtras.putParcelableArray(name, value);
+        return this;
+    }
+
+    /**
+     * Add extended data to the intent.  The name must include a package
+     * prefix, for example the app com.android.contacts would use names
+     * like "com.android.contacts.ShowAll".
+     *
+     * @param name The name of the extra data, with package prefix.
+     * @param value The ArrayList<Parcelable> data value.
+     *
+     * @return Returns the same Intent object, for chaining multiple calls
+     * into a single statement.
+     *
+     * @see #putExtras
+     * @see #removeExtra
+     * @see #getParcelableArrayListExtra(String)
+     */
+    public Intent putParcelableArrayListExtra(String name, ArrayList<? extends Parcelable> value) {
+        if (mExtras == null) {
+            mExtras = new Bundle();
+        }
+        mExtras.putParcelableArrayList(name, value);
+        return this;
+    }
+
+    /**
+     * Add extended data to the intent.  The name must include a package
+     * prefix, for example the app com.android.contacts would use names
+     * like "com.android.contacts.ShowAll".
+     *
+     * @param name The name of the extra data, with package prefix.
+     * @param value The ArrayList<Integer> data value.
+     *
+     * @return Returns the same Intent object, for chaining multiple calls
+     * into a single statement.
+     *
+     * @see #putExtras
+     * @see #removeExtra
+     * @see #getIntegerArrayListExtra(String)
+     */
+    public Intent putIntegerArrayListExtra(String name, ArrayList<Integer> value) {
+        if (mExtras == null) {
+            mExtras = new Bundle();
+        }
+        mExtras.putIntegerArrayList(name, value);
+        return this;
+    }
+
+    /**
+     * Add extended data to the intent.  The name must include a package
+     * prefix, for example the app com.android.contacts would use names
+     * like "com.android.contacts.ShowAll".
+     *
+     * @param name The name of the extra data, with package prefix.
+     * @param value The ArrayList<String> data value.
+     *
+     * @return Returns the same Intent object, for chaining multiple calls
+     * into a single statement.
+     *
+     * @see #putExtras
+     * @see #removeExtra
+     * @see #getStringArrayListExtra(String)
+     */
+    public Intent putStringArrayListExtra(String name, ArrayList<String> value) {
+        if (mExtras == null) {
+            mExtras = new Bundle();
+        }
+        mExtras.putStringArrayList(name, value);
+        return this;
+    }
+
+    /**
+     * Add extended data to the intent.  The name must include a package
+     * prefix, for example the app com.android.contacts would use names
+     * like "com.android.contacts.ShowAll".
+     *
+     * @param name The name of the extra data, with package prefix.
+     * @param value The Serializable data value.
+     *
+     * @return Returns the same Intent object, for chaining multiple calls
+     * into a single statement.
+     *
+     * @see #putExtras
+     * @see #removeExtra
+     * @see #getSerializableExtra(String)
+     */
+    public Intent putExtra(String name, Serializable value) {
+        if (mExtras == null) {
+            mExtras = new Bundle();
+        }
+        mExtras.putSerializable(name, value);
+        return this;
+    }
+
+    /**
+     * Add extended data to the intent.  The name must include a package
+     * prefix, for example the app com.android.contacts would use names
+     * like "com.android.contacts.ShowAll".
+     *
+     * @param name The name of the extra data, with package prefix.
+     * @param value The boolean array data value.
+     *
+     * @return Returns the same Intent object, for chaining multiple calls
+     * into a single statement.
+     *
+     * @see #putExtras
+     * @see #removeExtra
+     * @see #getBooleanArrayExtra(String)
+     */
+    public Intent putExtra(String name, boolean[] value) {
+        if (mExtras == null) {
+            mExtras = new Bundle();
+        }
+        mExtras.putBooleanArray(name, value);
+        return this;
+    }
+
+    /**
+     * Add extended data to the intent.  The name must include a package
+     * prefix, for example the app com.android.contacts would use names
+     * like "com.android.contacts.ShowAll".
+     *
+     * @param name The name of the extra data, with package prefix.
+     * @param value The byte array data value.
+     *
+     * @return Returns the same Intent object, for chaining multiple calls
+     * into a single statement.
+     *
+     * @see #putExtras
+     * @see #removeExtra
+     * @see #getByteArrayExtra(String)
+     */
+    public Intent putExtra(String name, byte[] value) {
+        if (mExtras == null) {
+            mExtras = new Bundle();
+        }
+        mExtras.putByteArray(name, value);
+        return this;
+    }
+
+    /**
+     * Add extended data to the intent.  The name must include a package
+     * prefix, for example the app com.android.contacts would use names
+     * like "com.android.contacts.ShowAll".
+     *
+     * @param name The name of the extra data, with package prefix.
+     * @param value The short array data value.
+     *
+     * @return Returns the same Intent object, for chaining multiple calls
+     * into a single statement.
+     *
+     * @see #putExtras
+     * @see #removeExtra
+     * @see #getShortArrayExtra(String)
+     */
+    public Intent putExtra(String name, short[] value) {
+        if (mExtras == null) {
+            mExtras = new Bundle();
+        }
+        mExtras.putShortArray(name, value);
+        return this;
+    }
+
+    /**
+     * Add extended data to the intent.  The name must include a package
+     * prefix, for example the app com.android.contacts would use names
+     * like "com.android.contacts.ShowAll".
+     *
+     * @param name The name of the extra data, with package prefix.
+     * @param value The char array data value.
+     *
+     * @return Returns the same Intent object, for chaining multiple calls
+     * into a single statement.
+     *
+     * @see #putExtras
+     * @see #removeExtra
+     * @see #getCharArrayExtra(String)
+     */
+    public Intent putExtra(String name, char[] value) {
+        if (mExtras == null) {
+            mExtras = new Bundle();
+        }
+        mExtras.putCharArray(name, value);
+        return this;
+    }
+
+    /**
+     * Add extended data to the intent.  The name must include a package
+     * prefix, for example the app com.android.contacts would use names
+     * like "com.android.contacts.ShowAll".
+     *
+     * @param name The name of the extra data, with package prefix.
+     * @param value The int array data value.
+     *
+     * @return Returns the same Intent object, for chaining multiple calls
+     * into a single statement.
+     *
+     * @see #putExtras
+     * @see #removeExtra
+     * @see #getIntArrayExtra(String)
+     */
+    public Intent putExtra(String name, int[] value) {
+        if (mExtras == null) {
+            mExtras = new Bundle();
+        }
+        mExtras.putIntArray(name, value);
+        return this;
+    }
+
+    /**
+     * Add extended data to the intent.  The name must include a package
+     * prefix, for example the app com.android.contacts would use names
+     * like "com.android.contacts.ShowAll".
+     *
+     * @param name The name of the extra data, with package prefix.
+     * @param value The byte array data value.
+     *
+     * @return Returns the same Intent object, for chaining multiple calls
+     * into a single statement.
+     *
+     * @see #putExtras
+     * @see #removeExtra
+     * @see #getLongArrayExtra(String)
+     */
+    public Intent putExtra(String name, long[] value) {
+        if (mExtras == null) {
+            mExtras = new Bundle();
+        }
+        mExtras.putLongArray(name, value);
+        return this;
+    }
+
+    /**
+     * Add extended data to the intent.  The name must include a package
+     * prefix, for example the app com.android.contacts would use names
+     * like "com.android.contacts.ShowAll".
+     *
+     * @param name The name of the extra data, with package prefix.
+     * @param value The float array data value.
+     *
+     * @return Returns the same Intent object, for chaining multiple calls
+     * into a single statement.
+     *
+     * @see #putExtras
+     * @see #removeExtra
+     * @see #getFloatArrayExtra(String)
+     */
+    public Intent putExtra(String name, float[] value) {
+        if (mExtras == null) {
+            mExtras = new Bundle();
+        }
+        mExtras.putFloatArray(name, value);
+        return this;
+    }
+
+    /**
+     * Add extended data to the intent.  The name must include a package
+     * prefix, for example the app com.android.contacts would use names
+     * like "com.android.contacts.ShowAll".
+     *
+     * @param name The name of the extra data, with package prefix.
+     * @param value The double array data value.
+     *
+     * @return Returns the same Intent object, for chaining multiple calls
+     * into a single statement.
+     *
+     * @see #putExtras
+     * @see #removeExtra
+     * @see #getDoubleArrayExtra(String)
+     */
+    public Intent putExtra(String name, double[] value) {
+        if (mExtras == null) {
+            mExtras = new Bundle();
+        }
+        mExtras.putDoubleArray(name, value);
+        return this;
+    }
+
+    /**
+     * Add extended data to the intent.  The name must include a package
+     * prefix, for example the app com.android.contacts would use names
+     * like "com.android.contacts.ShowAll".
+     *
+     * @param name The name of the extra data, with package prefix.
+     * @param value The String array data value.
+     *
+     * @return Returns the same Intent object, for chaining multiple calls
+     * into a single statement.
+     *
+     * @see #putExtras
+     * @see #removeExtra
+     * @see #getStringArrayExtra(String)
+     */
+    public Intent putExtra(String name, String[] value) {
+        if (mExtras == null) {
+            mExtras = new Bundle();
+        }
+        mExtras.putStringArray(name, value);
+        return this;
+    }
+
+    /**
+     * Add extended data to the intent.  The name must include a package
+     * prefix, for example the app com.android.contacts would use names
+     * like "com.android.contacts.ShowAll".
+     *
+     * @param name The name of the extra data, with package prefix.
+     * @param value The Bundle data value.
+     *
+     * @return Returns the same Intent object, for chaining multiple calls
+     * into a single statement.
+     *
+     * @see #putExtras
+     * @see #removeExtra
+     * @see #getBundleExtra(String)
+     */
+    public Intent putExtra(String name, Bundle value) {
+        if (mExtras == null) {
+            mExtras = new Bundle();
+        }
+        mExtras.putBundle(name, value);
+        return this;
+    }
+
+    /**
+     * Add extended data to the intent.  The name must include a package
+     * prefix, for example the app com.android.contacts would use names
+     * like "com.android.contacts.ShowAll".
+     *
+     * @param name The name of the extra data, with package prefix.
+     * @param value The IBinder data value.
+     *
+     * @return Returns the same Intent object, for chaining multiple calls
+     * into a single statement.
+     *
+     * @see #putExtras
+     * @see #removeExtra
+     * @see #getIBinderExtra(String)
+     *
+     * @deprecated
+     * @hide
+     */
+    @Deprecated
+    public Intent putExtra(String name, IBinder value) {
+        if (mExtras == null) {
+            mExtras = new Bundle();
+        }
+        mExtras.putIBinder(name, value);
+        return this;
+    }
+
+    /**
+     * Copy all extras in 'src' in to this intent.
+     *
+     * @param src Contains the extras to copy.
+     *
+     * @see #putExtra
+     */
+    public Intent putExtras(Intent src) {
+        if (src.mExtras != null) {
+            if (mExtras == null) {
+                mExtras = new Bundle(src.mExtras);
+            } else {
+                mExtras.putAll(src.mExtras);
+            }
+        }
+        return this;
+    }
+
+    /**
+     * Add a set of extended data to the intent.  The keys must include a package
+     * prefix, for example the app com.android.contacts would use names
+     * like "com.android.contacts.ShowAll".
+     *
+     * @param extras The Bundle of extras to add to this intent.
+     *
+     * @see #putExtra
+     * @see #removeExtra
+     */
+    public Intent putExtras(Bundle extras) {
+        if (mExtras == null) {
+            mExtras = new Bundle();
+        }
+        mExtras.putAll(extras);
+        return this;
+    }
+
+    /**
+     * Completely replace the extras in the Intent with the extras in the
+     * given Intent.
+     * 
+     * @param src The exact extras contained in this Intent are copied
+     * into the target intent, replacing any that were previously there.
+     */
+    public Intent replaceExtras(Intent src) {
+        mExtras = src.mExtras != null ? new Bundle(src.mExtras) : null;
+        return this;
+    }
+    
+    /**
+     * Completely replace the extras in the Intent with the given Bundle of
+     * extras.
+     * 
+     * @param extras The new set of extras in the Intent, or null to erase
+     * all extras.
+     */
+    public Intent replaceExtras(Bundle extras) {
+        mExtras = extras != null ? new Bundle(extras) : null;
+        return this;
+    }
+    
+    /**
+     * Remove extended data from the intent.
+     *
+     * @see #putExtra
+     */
+    public void removeExtra(String name) {
+        if (mExtras != null) {
+            mExtras.remove(name);
+            if (mExtras.size() == 0) {
+                mExtras = null;
+            }
+        }
+    }
+
+    /**
+     * Set special flags controlling how this intent is handled.  Most values
+     * here depend on the type of component being executed by the Intent,
+     * specifically the FLAG_ACTIVITY_* flags are all for use with
+     * {@link Context#startActivity Context.startActivity()} and the
+     * FLAG_RECEIVER_* flags are all for use with
+     * {@link Context#sendBroadcast(Intent) Context.sendBroadcast()}.
+     *
+     * <p>See the <a href="{@docRoot}guide/topics/fundamentals.html#acttask">Application Fundamentals:
+     * Activities and Tasks</a> documentation for important information on how some of these options impact
+     * the behavior of your application.
+     *
+     * @param flags The desired flags.
+     *
+     * @return Returns the same Intent object, for chaining multiple calls
+     * into a single statement.
+     *
+     * @see #getFlags
+     * @see #addFlags
+     *
+     * @see #FLAG_GRANT_READ_URI_PERMISSION
+     * @see #FLAG_GRANT_WRITE_URI_PERMISSION
+     * @see #FLAG_DEBUG_LOG_RESOLUTION
+     * @see #FLAG_FROM_BACKGROUND
+     * @see #FLAG_ACTIVITY_BROUGHT_TO_FRONT
+     * @see #FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET
+     * @see #FLAG_ACTIVITY_CLEAR_TOP
+     * @see #FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS
+     * @see #FLAG_ACTIVITY_FORWARD_RESULT
+     * @see #FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY
+     * @see #FLAG_ACTIVITY_MULTIPLE_TASK
+     * @see #FLAG_ACTIVITY_NEW_TASK
+     * @see #FLAG_ACTIVITY_NO_HISTORY
+     * @see #FLAG_ACTIVITY_NO_USER_ACTION
+     * @see #FLAG_ACTIVITY_PREVIOUS_IS_TOP
+     * @see #FLAG_ACTIVITY_RESET_TASK_IF_NEEDED
+     * @see #FLAG_ACTIVITY_SINGLE_TOP
+     * @see #FLAG_RECEIVER_REGISTERED_ONLY
+     */
+    public Intent setFlags(int flags) {
+        mFlags = flags;
+        return this;
+    }
+
+    /**
+     * Add additional flags to the intent (or with existing flags
+     * value).
+     *
+     * @param flags The new flags to set.
+     *
+     * @return Returns the same Intent object, for chaining multiple calls
+     * into a single statement.
+     *
+     * @see #setFlags
+     */
+    public Intent addFlags(int flags) {
+        mFlags |= flags;
+        return this;
+    }
+
+    /**
+     * (Usually optional) Explicitly set the component to handle the intent.
+     * If left with the default value of null, the system will determine the
+     * appropriate class to use based on the other fields (action, data,
+     * type, categories) in the Intent.  If this class is defined, the
+     * specified class will always be used regardless of the other fields.  You
+     * should only set this value when you know you absolutely want a specific
+     * class to be used; otherwise it is better to let the system find the
+     * appropriate class so that you will respect the installed applications
+     * and user preferences.
+     *
+     * @param component The name of the application component to handle the
+     * intent, or null to let the system find one for you.
+     *
+     * @return Returns the same Intent object, for chaining multiple calls
+     * into a single statement.
+     *
+     * @see #setClass
+     * @see #setClassName(Context, String)
+     * @see #setClassName(String, String)
+     * @see #getComponent
+     * @see #resolveActivity
+     */
+    public Intent setComponent(ComponentName component) {
+        mComponent = component;
+        return this;
+    }
+
+    /**
+     * Convenience for calling {@link #setComponent} with an
+     * explicit class name.
+     *
+     * @param packageContext A Context of the application package implementing
+     * this class.
+     * @param className The name of a class inside of the application package
+     * that will be used as the component for this Intent.
+     *
+     * @return Returns the same Intent object, for chaining multiple calls
+     * into a single statement.
+     *
+     * @see #setComponent
+     * @see #setClass
+     */
+    public Intent setClassName(Context packageContext, String className) {
+        mComponent = new ComponentName(packageContext, className);
+        return this;
+    }
+
+    /**
+     * Convenience for calling {@link #setComponent} with an
+     * explicit application package name and class name.
+     *
+     * @param packageName The name of the package implementing the desired
+     * component.
+     * @param className The name of a class inside of the application package
+     * that will be used as the component for this Intent.
+     *
+     * @return Returns the same Intent object, for chaining multiple calls
+     * into a single statement.
+     *
+     * @see #setComponent
+     * @see #setClass
+     */
+    public Intent setClassName(String packageName, String className) {
+        mComponent = new ComponentName(packageName, className);
+        return this;
+    }
+
+    /**
+     * Convenience for calling {@link #setComponent(ComponentName)} with the
+     * name returned by a {@link Class} object.
+     *
+     * @param packageContext A Context of the application package implementing
+     * this class.
+     * @param cls The class name to set, equivalent to
+     *            <code>setClassName(context, cls.getName())</code>.
+     *
+     * @return Returns the same Intent object, for chaining multiple calls
+     * into a single statement.
+     *
+     * @see #setComponent
+     */
+    public Intent setClass(Context packageContext, Class<?> cls) {
+        mComponent = new ComponentName(packageContext, cls);
+        return this;
+    }
+
+    /**
+     * Use with {@link #fillIn} to allow the current action value to be
+     * overwritten, even if it is already set.
+     */
+    public static final int FILL_IN_ACTION = 1<<0;
+
+    /**
+     * Use with {@link #fillIn} to allow the current data or type value
+     * overwritten, even if it is already set.
+     */
+    public static final int FILL_IN_DATA = 1<<1;
+
+    /**
+     * Use with {@link #fillIn} to allow the current categories to be
+     * overwritten, even if they are already set.
+     */
+    public static final int FILL_IN_CATEGORIES = 1<<2;
+
+    /**
+     * Use with {@link #fillIn} to allow the current component value to be
+     * overwritten, even if it is already set.
+     */
+    public static final int FILL_IN_COMPONENT = 1<<3;
+
+    /**
+     * Copy the contents of <var>other</var> in to this object, but only
+     * where fields are not defined by this object.  For purposes of a field
+     * being defined, the following pieces of data in the Intent are
+     * considered to be separate fields:
+     *
+     * <ul>
+     * <li> action, as set by {@link #setAction}.
+     * <li> data URI and MIME type, as set by {@link #setData(Uri)},
+     * {@link #setType(String)}, or {@link #setDataAndType(Uri, String)}.
+     * <li> categories, as set by {@link #addCategory}.
+     * <li> component, as set by {@link #setComponent(ComponentName)} or
+     * related methods.
+     * <li> each top-level name in the associated extras.
+     * </ul>
+     *
+     * <p>In addition, you can use the {@link #FILL_IN_ACTION},
+     * {@link #FILL_IN_DATA}, {@link #FILL_IN_CATEGORIES}, and
+     * {@link #FILL_IN_COMPONENT} to override the restriction where the
+     * corresponding field will not be replaced if it is already set.
+     *
+     * <p>For example, consider Intent A with {data="foo", categories="bar"}
+     * and Intent B with {action="gotit", data-type="some/thing",
+     * categories="one","two"}.
+     *
+     * <p>Calling A.fillIn(B, Intent.FILL_IN_DATA) will result in A now
+     * containing: {action="gotit", data-type="some/thing",
+     * categories="bar"}.
+     *
+     * @param other Another Intent whose values are to be used to fill in
+     * the current one.
+     * @param flags Options to control which fields can be filled in.
+     *
+     * @return Returns a bit mask of {@link #FILL_IN_ACTION},
+     * {@link #FILL_IN_DATA}, {@link #FILL_IN_CATEGORIES}, and
+     * {@link #FILL_IN_COMPONENT} indicating which fields were changed.
+     */
+    public int fillIn(Intent other, int flags) {
+        int changes = 0;
+        if ((mAction == null && other.mAction == null)
+                || (flags&FILL_IN_ACTION) != 0) {
+            mAction = other.mAction;
+            changes |= FILL_IN_ACTION;
+        }
+        if ((mData == null && mType == null &&
+                (other.mData != null || other.mType != null))
+                || (flags&FILL_IN_DATA) != 0) {
+            mData = other.mData;
+            mType = other.mType;
+            changes |= FILL_IN_DATA;
+        }
+        if ((mCategories == null && other.mCategories == null)
+                || (flags&FILL_IN_CATEGORIES) != 0) {
+            if (other.mCategories != null) {
+                mCategories = new HashSet<String>(other.mCategories);
+            }
+            changes |= FILL_IN_CATEGORIES;
+        }
+        if ((mComponent == null && other.mComponent == null)
+                || (flags&FILL_IN_COMPONENT) != 0) {
+            mComponent = other.mComponent;
+            changes |= FILL_IN_COMPONENT;
+        }
+        mFlags |= other.mFlags;
+        if (mExtras == null) {
+            if (other.mExtras != null) {
+                mExtras = new Bundle(other.mExtras);
+            }
+        } else if (other.mExtras != null) {
+            try {
+                Bundle newb = new Bundle(other.mExtras);
+                newb.putAll(mExtras);
+                mExtras = newb;
+            } catch (RuntimeException e) {
+                // Modifying the extras can cause us to unparcel the contents
+                // of the bundle, and if we do this in the system process that
+                // may fail.  We really should handle this (i.e., the Bundle
+                // impl shouldn't be on top of a plain map), but for now just
+                // ignore it and keep the original contents. :(
+                Log.w("Intent", "Failure filling in extras", e);
+            }
+        }
+        return changes;
+    }
+
+    /**
+     * Wrapper class holding an Intent and implementing comparisons on it for
+     * the purpose of filtering.  The class implements its
+     * {@link #equals equals()} and {@link #hashCode hashCode()} methods as
+     * simple calls to {@link Intent#filterEquals(Intent)}  filterEquals()} and
+     * {@link android.content.Intent#filterHashCode()}  filterHashCode()}
+     * on the wrapped Intent.
+     */
+    public static final class FilterComparison {
+        private final Intent mIntent;
+        private final int mHashCode;
+
+        public FilterComparison(Intent intent) {
+            mIntent = intent;
+            mHashCode = intent.filterHashCode();
+        }
+
+        /**
+         * Return the Intent that this FilterComparison represents.
+         * @return Returns the Intent held by the FilterComparison.  Do
+         * not modify!
+         */
+        public Intent getIntent() {
+            return mIntent;
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            if (obj instanceof FilterComparison) {
+                Intent other = ((FilterComparison)obj).mIntent;
+                return mIntent.filterEquals(other);
+            }
+            return false;
+        }
+
+        @Override
+        public int hashCode() {
+            return mHashCode;
+        }
+    }
+
+    /**
+     * Determine if two intents are the same for the purposes of intent
+     * resolution (filtering). That is, if their action, data, type,
+     * class, and categories are the same.  This does <em>not</em> compare
+     * any extra data included in the intents.
+     *
+     * @param other The other Intent to compare against.
+     *
+     * @return Returns true if action, data, type, class, and categories
+     *         are the same.
+     */
+    public boolean filterEquals(Intent other) {
+        if (other == null) {
+            return false;
+        }
+        if (mAction != other.mAction) {
+            if (mAction != null) {
+                if (!mAction.equals(other.mAction)) {
+                    return false;
+                }
+            } else {
+                if (!other.mAction.equals(mAction)) {
+                    return false;
+                }
+            }
+        }
+        if (mData != other.mData) {
+            if (mData != null) {
+                if (!mData.equals(other.mData)) {
+                    return false;
+                }
+            } else {
+                if (!other.mData.equals(mData)) {
+                    return false;
+                }
+            }
+        }
+        if (mType != other.mType) {
+            if (mType != null) {
+                if (!mType.equals(other.mType)) {
+                    return false;
+                }
+            } else {
+                if (!other.mType.equals(mType)) {
+                    return false;
+                }
+            }
+        }
+        if (mComponent != other.mComponent) {
+            if (mComponent != null) {
+                if (!mComponent.equals(other.mComponent)) {
+                    return false;
+                }
+            } else {
+                if (!other.mComponent.equals(mComponent)) {
+                    return false;
+                }
+            }
+        }
+        if (mCategories != other.mCategories) {
+            if (mCategories != null) {
+                if (!mCategories.equals(other.mCategories)) {
+                    return false;
+                }
+            } else {
+                if (!other.mCategories.equals(mCategories)) {
+                    return false;
+                }
+            }
+        }
+
+        return true;
+    }
+
+    /**
+     * Generate hash code that matches semantics of filterEquals().
+     *
+     * @return Returns the hash value of the action, data, type, class, and
+     *         categories.
+     *
+     * @see #filterEquals
+     */
+    public int filterHashCode() {
+        int code = 0;
+        if (mAction != null) {
+            code += mAction.hashCode();
+        }
+        if (mData != null) {
+            code += mData.hashCode();
+        }
+        if (mType != null) {
+            code += mType.hashCode();
+        }
+        if (mComponent != null) {
+            code += mComponent.hashCode();
+        }
+        if (mCategories != null) {
+            code += mCategories.hashCode();
+        }
+        return code;
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder   b = new StringBuilder();
+
+        b.append("Intent {");
+        if (mAction != null) b.append(" action=").append(mAction);
+        if (mCategories != null) {
+            b.append(" categories={");
+            Iterator<String> i = mCategories.iterator();
+            boolean didone = false;
+            while (i.hasNext()) {
+                if (didone) b.append(",");
+                didone = true;
+                b.append(i.next());
+            }
+            b.append("}");
+        }
+        if (mData != null) b.append(" data=").append(mData);
+        if (mType != null) b.append(" type=").append(mType);
+        if (mFlags != 0) b.append(" flags=0x").append(Integer.toHexString(mFlags));
+        if (mComponent != null) b.append(" comp=").append(mComponent.toShortString());
+        if (mExtras != null) b.append(" (has extras)");
+        b.append(" }");
+
+        return b.toString();
+    }
+
+    public String toURI() {
+        StringBuilder uri = new StringBuilder(mData != null ? mData.toString() : "");
+
+        uri.append("#Intent;");
+
+        if (mAction != null) {
+            uri.append("action=").append(mAction).append(';');
+        }
+        if (mCategories != null) {
+            for (String category : mCategories) {
+                uri.append("category=").append(category).append(';');
+            }
+        }
+        if (mType != null) {
+            uri.append("type=").append(mType).append(';');
+        }
+        if (mFlags != 0) {
+            uri.append("launchFlags=0x").append(Integer.toHexString(mFlags)).append(';');
+        }
+        if (mComponent != null) {
+            uri.append("component=").append(mComponent.flattenToShortString()).append(';');
+        }
+        if (mExtras != null) {
+            for (String key : mExtras.keySet()) {
+                final Object value = mExtras.get(key);
+                char entryType =
+                        value instanceof String    ? 'S' :
+                        value instanceof Boolean   ? 'B' :
+                        value instanceof Byte      ? 'b' :
+                        value instanceof Character ? 'c' :
+                        value instanceof Double    ? 'd' :
+                        value instanceof Float     ? 'f' :
+                        value instanceof Integer   ? 'i' :
+                        value instanceof Long      ? 'l' :
+                        value instanceof Short     ? 's' :
+                        '\0';
+
+                if (entryType != '\0') {
+                    uri.append(entryType);
+                    uri.append('.');
+                    uri.append(Uri.encode(key));
+                    uri.append('=');
+                    uri.append(Uri.encode(value.toString()));
+                    uri.append(';');
+                }
+            }
+        }
+        
+        uri.append("end");
+
+        return uri.toString();
+    }
+    
+    public int describeContents() {
+        return (mExtras != null) ? mExtras.describeContents() : 0;
+    }
+
+    public void writeToParcel(Parcel out, int flags) {
+        out.writeString(mAction);
+        Uri.writeToParcel(out, mData);
+        out.writeString(mType);
+        out.writeInt(mFlags);
+        ComponentName.writeToParcel(mComponent, out);
+
+        if (mCategories != null) {
+            out.writeInt(mCategories.size());
+            for (String category : mCategories) {
+                out.writeString(category);
+            }
+        } else {
+            out.writeInt(0);
+        }
+
+        out.writeBundle(mExtras);
+    }
+
+    public static final Parcelable.Creator<Intent> CREATOR
+            = new Parcelable.Creator<Intent>() {
+        public Intent createFromParcel(Parcel in) {
+            return new Intent(in);
+        }
+        public Intent[] newArray(int size) {
+            return new Intent[size];
+        }
+    };
+
+    private Intent(Parcel in) {
+        readFromParcel(in);
+    }
+
+    public void readFromParcel(Parcel in) {
+        mAction = in.readString();
+        mData = Uri.CREATOR.createFromParcel(in);
+        mType = in.readString();
+        mFlags = in.readInt();
+        mComponent = ComponentName.readFromParcel(in);
+
+        int N = in.readInt();
+        if (N > 0) {
+            mCategories = new HashSet<String>();
+            int i;
+            for (i=0; i<N; i++) {
+                mCategories.add(in.readString());
+            }
+        } else {
+            mCategories = null;
+        }
+
+        mExtras = in.readBundle();
+    }
+
+    /**
+     * Parses the "intent" element (and its children) from XML and instantiates
+     * an Intent object.  The given XML parser should be located at the tag
+     * where parsing should start (often named "intent"), from which the
+     * basic action, data, type, and package and class name will be
+     * retrieved.  The function will then parse in to any child elements,
+     * looking for <category android:name="xxx"> tags to add categories and
+     * <extra android:name="xxx" android:value="yyy"> to attach extra data
+     * to the intent.
+     *
+     * @param resources The Resources to use when inflating resources.
+     * @param parser The XML parser pointing at an "intent" tag.
+     * @param attrs The AttributeSet interface for retrieving extended
+     * attribute data at the current <var>parser</var> location.
+     * @return An Intent object matching the XML data.
+     * @throws XmlPullParserException If there was an XML parsing error.
+     * @throws IOException If there was an I/O error.
+     */
+    public static Intent parseIntent(Resources resources, XmlPullParser parser, AttributeSet attrs)
+            throws XmlPullParserException, IOException {
+        Intent intent = new Intent();
+
+        TypedArray sa = resources.obtainAttributes(attrs,
+                com.android.internal.R.styleable.Intent);
+
+        intent.setAction(sa.getString(com.android.internal.R.styleable.Intent_action));
+
+        String data = sa.getString(com.android.internal.R.styleable.Intent_data);
+        String mimeType = sa.getString(com.android.internal.R.styleable.Intent_mimeType);
+        intent.setDataAndType(data != null ? Uri.parse(data) : null, mimeType);
+
+        String packageName = sa.getString(com.android.internal.R.styleable.Intent_targetPackage);
+        String className = sa.getString(com.android.internal.R.styleable.Intent_targetClass);
+        if (packageName != null && className != null) {
+            intent.setComponent(new ComponentName(packageName, className));
+        }
+
+        sa.recycle();
+
+        int outerDepth = parser.getDepth();
+        int type;
+        while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
+               && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
+            if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+                continue;
+            }
+
+            String nodeName = parser.getName();
+            if (nodeName.equals("category")) {
+                sa = resources.obtainAttributes(attrs,
+                        com.android.internal.R.styleable.IntentCategory);
+                String cat = sa.getString(com.android.internal.R.styleable.IntentCategory_name);
+                sa.recycle();
+
+                if (cat != null) {
+                    intent.addCategory(cat);
+                }
+                XmlUtils.skipCurrentTag(parser);
+
+            } else if (nodeName.equals("extra")) {
+                if (intent.mExtras == null) {
+                    intent.mExtras = new Bundle();
+                }
+                resources.parseBundleExtra("extra", attrs, intent.mExtras);
+                XmlUtils.skipCurrentTag(parser);
+
+            } else {
+                XmlUtils.skipCurrentTag(parser);
+            }
+        }
+
+        return intent;
+    }
+}
diff --git a/core/java/android/content/IntentFilter.aidl b/core/java/android/content/IntentFilter.aidl
new file mode 100644
index 0000000..a9bcd5e
--- /dev/null
+++ b/core/java/android/content/IntentFilter.aidl
@@ -0,0 +1,19 @@
+/*
+ * 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 android.content;
+
+parcelable IntentFilter;
diff --git a/core/java/android/content/IntentFilter.java b/core/java/android/content/IntentFilter.java
new file mode 100644
index 0000000..e81bc86
--- /dev/null
+++ b/core/java/android/content/IntentFilter.java
@@ -0,0 +1,1408 @@
+/*
+ * 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 android.content;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.Set;
+
+import android.net.Uri;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.PatternMatcher;
+import android.util.AndroidException;
+import android.util.Config;
+import android.util.Log;
+import android.util.Printer;
+import com.android.internal.util.XmlUtils;
+
+/**
+ * Structured description of Intent values to be matched.  An IntentFilter can
+ * match against actions, categories, and data (either via its type, scheme,
+ * and/or path) in an Intent.  It also includes a "priority" value which is
+ * used to order multiple matching filters.
+ *
+ * <p>IntentFilter objects are often created in XML as part of a package's
+ * {@link android.R.styleable#AndroidManifest AndroidManifest.xml} file,
+ * using {@link android.R.styleable#AndroidManifestIntentFilter intent-filter}
+ * tags.
+ *
+ * <p>There are three Intent characteristics you can filter on: the
+ * <em>action</em>, <em>data</em>, and <em>categories</em>.  For each of these
+ * characteristics you can provide
+ * multiple possible matching values (via {@link #addAction},
+ * {@link #addDataType}, {@link #addDataScheme} {@link #addDataAuthority},
+ * {@link #addDataPath}, and {@link #addCategory}, respectively).
+ * For actions, the field
+ * will not be tested if no values have been given (treating it as a wildcard);
+ * if no data characteristics are specified, however, then the filter will
+ * only match intents that contain no data.
+ *
+ * <p>The data characteristic is
+ * itself divided into three attributes: type, scheme, authority, and path.
+ * Any that are
+ * specified must match the contents of the Intent.  If you specify a scheme
+ * but no type, only Intent that does not have a type (such as mailto:) will
+ * match; a content: URI will never match because they always have a MIME type
+ * that is supplied by their content provider.  Specifying a type with no scheme
+ * has somewhat special meaning: it will match either an Intent with no URI
+ * field, or an Intent with a content: or file: URI.  If you specify neither,
+ * then only an Intent with no data or type will match.  To specify an authority,
+ * you must also specify one or more schemes that it is associated with.
+ * To specify a path, you also must specify both one or more authorities and
+ * one or more schemes it is associated with.
+ *
+ * <p>A match is based on the following rules.  Note that
+ * for an IntentFilter to match an Intent, three conditions must hold:
+ * the <strong>action</strong> and <strong>category</strong> must match, and
+ * the data (both the <strong>data type</strong> and
+ * <strong>data scheme+authority+path</strong> if specified) must match.
+ *
+ * <p><strong>Action</strong> matches if any of the given values match the
+ * Intent action, <em>or</em> if no actions were specified in the filter.
+ *
+ * <p><strong>Data Type</strong> matches if any of the given values match the
+ * Intent type.  The Intent
+ * type is determined by calling {@link Intent#resolveType}.  A wildcard can be
+ * used for the MIME sub-type, in both the Intent and IntentFilter, so that the
+ * type "audio/*" will match "audio/mpeg", "audio/aiff", "audio/*", etc.
+ *
+ * <p><strong>Data Scheme</strong> matches if any of the given values match the
+ * Intent data's scheme.
+ * The Intent scheme is determined by calling {@link Intent#getData}
+ * and {@link android.net.Uri#getScheme} on that URI.
+ *
+ * <p><strong>Data Authority</strong> matches if any of the given values match
+ * the Intent's data authority <em>and</em> one of the data scheme's in the filter
+ * has matched the Intent, <em>or</em> no authories were supplied in the filter.
+ * The Intent authority is determined by calling
+ * {@link Intent#getData} and {@link android.net.Uri#getAuthority} on that URI.
+ *
+ * <p><strong>Data Path</strong> matches if any of the given values match the
+ * Intent's data path <em>and</em> both a scheme and authority in the filter
+ * has matched against the Intent, <em>or</em> no paths were supplied in the
+ * filter.  The Intent authority is determined by calling
+ * {@link Intent#getData} and {@link android.net.Uri#getPath} on that URI.
+ *
+ * <p><strong>Categories</strong> match if <em>all</em> of the categories in
+ * the Intent match categories given in the filter.  Extra categories in the
+ * filter that are not in the Intent will not cause the match to fail.  Note
+ * that unlike the action, an IntentFilter with no categories
+ * will only match an Intent that does not have any categories.
+ */
+public class IntentFilter implements Parcelable {
+    private static final String SGLOB_STR = "sglob";
+    private static final String PREFIX_STR = "prefix";
+    private static final String LITERAL_STR = "literal";
+    private static final String PATH_STR = "path";
+    private static final String PORT_STR = "port";
+    private static final String HOST_STR = "host";
+    private static final String AUTH_STR = "auth";
+    private static final String SCHEME_STR = "scheme";
+    private static final String TYPE_STR = "type";
+    private static final String CAT_STR = "cat";
+    private static final String NAME_STR = "name";
+    private static final String ACTION_STR = "action";
+
+    /**
+     * The filter {@link #setPriority} value at which system high-priority
+     * receivers are placed; that is, receivers that should execute before
+     * application code. Applications should never use filters with this or
+     * higher priorities.
+     *
+     * @see #setPriority
+     */
+    public static final int SYSTEM_HIGH_PRIORITY = 1000;
+
+    /**
+     * The filter {@link #setPriority} value at which system low-priority
+     * receivers are placed; that is, receivers that should execute after
+     * application code. Applications should never use filters with this or
+     * lower priorities.
+     *
+     * @see #setPriority
+     */
+    public static final int SYSTEM_LOW_PRIORITY = -1000;
+
+    /**
+     * The part of a match constant that describes the category of match
+     * that occurred.  May be either {@link #MATCH_CATEGORY_EMPTY},
+     * {@link #MATCH_CATEGORY_SCHEME}, {@link #MATCH_CATEGORY_HOST},
+     * {@link #MATCH_CATEGORY_PORT},
+     * {@link #MATCH_CATEGORY_PATH}, or {@link #MATCH_CATEGORY_TYPE}.  Higher
+     * values indicate a better match.
+     */
+    public static final int MATCH_CATEGORY_MASK = 0xfff0000;
+
+    /**
+     * The part of a match constant that applies a quality adjustment to the
+     * basic category of match.  The value {@link #MATCH_ADJUSTMENT_NORMAL}
+     * is no adjustment; higher numbers than that improve the quality, while
+     * lower numbers reduce it.
+     */
+    public static final int MATCH_ADJUSTMENT_MASK = 0x000ffff;
+
+    /**
+     * Quality adjustment applied to the category of match that signifies
+     * the default, base value; higher numbers improve the quality while
+     * lower numbers reduce it.
+     */
+    public static final int MATCH_ADJUSTMENT_NORMAL = 0x8000;
+
+    /**
+     * The filter matched an intent that had no data specified.
+     */
+    public static final int MATCH_CATEGORY_EMPTY = 0x0100000;
+    /**
+     * The filter matched an intent with the same data URI scheme.
+     */
+    public static final int MATCH_CATEGORY_SCHEME = 0x0200000;
+    /**
+     * The filter matched an intent with the same data URI scheme and
+     * authority host.
+     */
+    public static final int MATCH_CATEGORY_HOST = 0x0300000;
+    /**
+     * The filter matched an intent with the same data URI scheme and
+     * authority host and port.
+     */
+    public static final int MATCH_CATEGORY_PORT = 0x0400000;
+    /**
+     * The filter matched an intent with the same data URI scheme,
+     * authority, and path.
+     */
+    public static final int MATCH_CATEGORY_PATH = 0x0500000;
+    /**
+     * The filter matched an intent with the same data MIME type.
+     */
+    public static final int MATCH_CATEGORY_TYPE = 0x0600000;
+
+    /**
+     * The filter didn't match due to different MIME types.
+     */
+    public static final int NO_MATCH_TYPE = -1;
+    /**
+     * The filter didn't match due to different data URIs.
+     */
+    public static final int NO_MATCH_DATA = -2;
+    /**
+     * The filter didn't match due to different actions.
+     */
+    public static final int NO_MATCH_ACTION = -3;
+    /**
+     * The filter didn't match because it required one or more categories
+     * that were not in the Intent.
+     */
+    public static final int NO_MATCH_CATEGORY = -4;
+
+    private int mPriority;
+    private final ArrayList<String> mActions;
+    private ArrayList<String> mCategories = null;
+    private ArrayList<String> mDataSchemes = null;
+    private ArrayList<AuthorityEntry> mDataAuthorities = null;
+    private ArrayList<PatternMatcher> mDataPaths = null;
+    private ArrayList<String> mDataTypes = null;
+    private boolean mHasPartialTypes = false;
+
+    // These functions are the start of more optimized code for managing
+    // the string sets...  not yet implemented.
+
+    private static int findStringInSet(String[] set, String string,
+            int[] lengths, int lenPos) {
+        if (set == null) return -1;
+        final int N = lengths[lenPos];
+        for (int i=0; i<N; i++) {
+            if (set[i].equals(string)) return i;
+        }
+        return -1;
+    }
+
+    private static String[] addStringToSet(String[] set, String string,
+            int[] lengths, int lenPos) {
+        if (findStringInSet(set, string, lengths, lenPos) >= 0) return set;
+        if (set == null) {
+            set = new String[2];
+            set[0] = string;
+            lengths[lenPos] = 1;
+            return set;
+        }
+        final int N = lengths[lenPos];
+        if (N < set.length) {
+            set[N] = string;
+            lengths[lenPos] = N+1;
+            return set;
+        }
+
+        String[] newSet = new String[(N*3)/2 + 2];
+        System.arraycopy(set, 0, newSet, 0, N);
+        set = newSet;
+        set[N] = string;
+        lengths[lenPos] = N+1;
+        return set;
+    }
+
+    private static String[] removeStringFromSet(String[] set, String string,
+            int[] lengths, int lenPos) {
+        int pos = findStringInSet(set, string, lengths, lenPos);
+        if (pos < 0) return set;
+        final int N = lengths[lenPos];
+        if (N > (set.length/4)) {
+            int copyLen = N-(pos+1);
+            if (copyLen > 0) {
+                System.arraycopy(set, pos+1, set, pos, copyLen);
+            }
+            set[N-1] = null;
+            lengths[lenPos] = N-1;
+            return set;
+        }
+
+        String[] newSet = new String[set.length/3];
+        if (pos > 0) System.arraycopy(set, 0, newSet, 0, pos);
+        if ((pos+1) < N) System.arraycopy(set, pos+1, newSet, pos, N-(pos+1));
+        return newSet;
+    }
+
+    /**
+     * This exception is thrown when a given MIME type does not have a valid
+     * syntax.
+     */
+    public static class MalformedMimeTypeException extends AndroidException {
+        public MalformedMimeTypeException() {
+        }
+
+        public MalformedMimeTypeException(String name) {
+            super(name);
+        }
+    };
+
+    /**
+     * Create a new IntentFilter instance with a specified action and MIME
+     * type, where you know the MIME type is correctly formatted.  This catches
+     * the {@link MalformedMimeTypeException} exception that the constructor
+     * can call and turns it into a runtime exception.
+     *
+     * @param action The action to match, i.e. Intent.ACTION_VIEW.
+     * @param dataType The type to match, i.e. "vnd.android.cursor.dir/person".
+     *
+     * @return A new IntentFilter for the given action and type.
+     *
+     * @see #IntentFilter(String, String)
+     */
+    public static IntentFilter create(String action, String dataType) {
+        try {
+            return new IntentFilter(action, dataType);
+        } catch (MalformedMimeTypeException e) {
+            throw new RuntimeException("Bad MIME type", e);
+        }
+    }
+
+    /**
+     * New empty IntentFilter.
+     */
+    public IntentFilter() {
+        mPriority = 0;
+        mActions = new ArrayList<String>();
+    }
+
+    /**
+     * New IntentFilter that matches a single action with no data.  If
+     * no data characteristics are subsequently specified, then the
+     * filter will only match intents that contain no data.
+     *
+     * @param action The action to match, i.e. Intent.ACTION_MAIN.
+     */
+    public IntentFilter(String action) {
+        mPriority = 0;
+        mActions = new ArrayList<String>();
+        addAction(action);
+    }
+
+    /**
+     * New IntentFilter that matches a single action and data type.
+     *
+     * <p>Throws {@link MalformedMimeTypeException} if the given MIME type is
+     * not syntactically correct.
+     *
+     * @param action The action to match, i.e. Intent.ACTION_VIEW.
+     * @param dataType The type to match, i.e. "vnd.android.cursor.dir/person".
+     *
+     */
+    public IntentFilter(String action, String dataType)
+        throws MalformedMimeTypeException {
+        mPriority = 0;
+        mActions = new ArrayList<String>();
+        addDataType(dataType);
+    }
+
+    /**
+     * New IntentFilter containing a copy of an existing filter.
+     *
+     * @param o The original filter to copy.
+     */
+    public IntentFilter(IntentFilter o) {
+        mPriority = o.mPriority;
+        mActions = new ArrayList<String>(o.mActions);
+        if (o.mCategories != null) {
+            mCategories = new ArrayList<String>(o.mCategories);
+        }
+        if (o.mDataTypes != null) {
+            mDataTypes = new ArrayList<String>(o.mDataTypes);
+        }
+        if (o.mDataSchemes != null) {
+            mDataSchemes = new ArrayList<String>(o.mDataSchemes);
+        }
+        if (o.mDataAuthorities != null) {
+            mDataAuthorities = new ArrayList<AuthorityEntry>(o.mDataAuthorities);
+        }
+        if (o.mDataPaths != null) {
+            mDataPaths = new ArrayList<PatternMatcher>(o.mDataPaths);
+        }
+        mHasPartialTypes = o.mHasPartialTypes;
+    }
+
+    /**
+     * Modify priority of this filter.  The default priority is 0. Positive
+     * values will be before the default, lower values will be after it.
+     * Applications must use a value that is larger than
+     * {@link #SYSTEM_LOW_PRIORITY} and smaller than
+     * {@link #SYSTEM_HIGH_PRIORITY} .
+     *
+     * @param priority The new priority value.
+     *
+     * @see #getPriority
+     * @see #SYSTEM_LOW_PRIORITY
+     * @see #SYSTEM_HIGH_PRIORITY
+     */
+    public final void setPriority(int priority) {
+        mPriority = priority;
+    }
+
+    /**
+     * Return the priority of this filter.
+     *
+     * @return The priority of the filter.
+     *
+     * @see #setPriority
+     */
+    public final int getPriority() {
+        return mPriority;
+    }
+
+    /**
+     * Add a new Intent action to match against.  If any actions are included
+     * in the filter, then an Intent's action must be one of those values for
+     * it to match.  If no actions are included, the Intent action is ignored.
+     *
+     * @param action Name of the action to match, i.e. Intent.ACTION_VIEW.
+     */
+    public final void addAction(String action) {
+        if (!mActions.contains(action)) {
+            mActions.add(action.intern());
+        }
+    }
+
+    /**
+     * Return the number of actions in the filter.
+     */
+    public final int countActions() {
+        return mActions.size();
+    }
+
+    /**
+     * Return an action in the filter.
+     */
+    public final String getAction(int index) {
+        return mActions.get(index);
+    }
+
+    /**
+     * Is the given action included in the filter?  Note that if the filter
+     * does not include any actions, false will <em>always</em> be returned.
+     *
+     * @param action The action to look for.
+     *
+     * @return True if the action is explicitly mentioned in the filter.
+     */
+    public final boolean hasAction(String action) {
+        return mActions.contains(action);
+    }
+
+    /**
+     * Match this filter against an Intent's action.  If the filter does not
+     * specify any actions, the match will always fail.
+     *
+     * @param action The desired action to look for.
+     *
+     * @return True if the action is listed in the filter or the filter does
+     *         not specify any actions.
+     */
+    public final boolean matchAction(String action) {
+        if (action == null || mActions == null || mActions.size() == 0) {
+            return false;
+        }
+        return mActions.contains(action);
+    }
+
+    /**
+     * Return an iterator over the filter's actions.  If there are no actions,
+     * returns null.
+     */
+    public final Iterator<String> actionsIterator() {
+        return mActions != null ? mActions.iterator() : null;
+    }
+
+    /**
+     * Add a new Intent data type to match against.  If any types are
+     * included in the filter, then an Intent's data must be <em>either</em>
+     * one of these types <em>or</em> a matching scheme.  If no data types
+     * are included, then an Intent will only match if it specifies no data.
+     *
+     * <p>Throws {@link MalformedMimeTypeException} if the given MIME type is
+     * not syntactically correct.
+     *
+     * @param type Name of the data type to match, i.e. "vnd.android.cursor.dir/person".
+     *
+     * @see #matchData
+     */
+    public final void addDataType(String type)
+        throws MalformedMimeTypeException {
+        final int slashpos = type.indexOf('/');
+        final int typelen = type.length();
+        if (slashpos > 0 && typelen >= slashpos+2) {
+            if (mDataTypes == null) mDataTypes = new ArrayList<String>();
+            if (typelen == slashpos+2 && type.charAt(slashpos+1) == '*') {
+                String str = type.substring(0, slashpos);
+                if (!mDataTypes.contains(str)) {
+                    mDataTypes.add(str.intern());
+                }
+                mHasPartialTypes = true;
+            } else {
+                if (!mDataTypes.contains(type)) {
+                    mDataTypes.add(type.intern());
+                }
+            }
+            return;
+        }
+
+        throw new MalformedMimeTypeException(type);
+    }
+
+    /**
+     * Is the given data type included in the filter?  Note that if the filter
+     * does not include any type, false will <em>always</em> be returned.
+     *
+     * @param type The data type to look for.
+     *
+     * @return True if the type is explicitly mentioned in the filter.
+     */
+    public final boolean hasDataType(String type) {
+        return mDataTypes != null && findMimeType(type);
+    }
+
+    /**
+     * Return the number of data types in the filter.
+     */
+    public final int countDataTypes() {
+        return mDataTypes != null ? mDataTypes.size() : 0;
+    }
+
+    /**
+     * Return a data type in the filter.
+     */
+    public final String getDataType(int index) {
+        return mDataTypes.get(index);
+    }
+
+    /**
+     * Return an iterator over the filter's data types.
+     */
+    public final Iterator<String> typesIterator() {
+        return mDataTypes != null ? mDataTypes.iterator() : null;
+    }
+
+    /**
+     * Add a new Intent data scheme to match against.  If any schemes are
+     * included in the filter, then an Intent's data must be <em>either</em>
+     * one of these schemes <em>or</em> a matching data type.  If no schemes
+     * are included, then an Intent will match only if it includes no data.
+     *
+     * @param scheme Name of the scheme to match, i.e. "http".
+     *
+     * @see #matchData
+     */
+    public final void addDataScheme(String scheme) {
+        if (mDataSchemes == null) mDataSchemes = new ArrayList<String>();
+        if (!mDataSchemes.contains(scheme)) {
+            mDataSchemes.add(scheme.intern());
+        }
+    }
+
+    /**
+     * Return the number of data schemes in the filter.
+     */
+    public final int countDataSchemes() {
+        return mDataSchemes != null ? mDataSchemes.size() : 0;
+    }
+
+    /**
+     * Return a data scheme in the filter.
+     */
+    public final String getDataScheme(int index) {
+        return mDataSchemes.get(index);
+    }
+
+    /**
+     * Is the given data scheme included in the filter?  Note that if the
+     * filter does not include any scheme, false will <em>always</em> be
+     * returned.
+     *
+     * @param scheme The data scheme to look for.
+     *
+     * @return True if the scheme is explicitly mentioned in the filter.
+     */
+    public final boolean hasDataScheme(String scheme) {
+        return mDataSchemes != null && mDataSchemes.contains(scheme);
+    }
+
+    /**
+     * Return an iterator over the filter's data schemes.
+     */
+    public final Iterator<String> schemesIterator() {
+        return mDataSchemes != null ? mDataSchemes.iterator() : null;
+    }
+
+    /**
+     * This is an entry for a single authority in the Iterator returned by
+     * {@link #authoritiesIterator()}.
+     */
+    public final static class AuthorityEntry {
+        private final String mOrigHost;
+        private final String mHost;
+        private final boolean mWild;
+        private final int mPort;
+
+        public AuthorityEntry(String host, String port) {
+            mOrigHost = host;
+            mWild = host.length() > 0 && host.charAt(0) == '*';
+            mHost = mWild ? host.substring(1).intern() : host;
+            mPort = port != null ? Integer.parseInt(port) : -1;
+        }
+
+        AuthorityEntry(Parcel src) {
+            mOrigHost = src.readString();
+            mHost = src.readString();
+            mWild = src.readInt() != 0;
+            mPort = src.readInt();
+        }
+
+        void writeToParcel(Parcel dest) {
+            dest.writeString(mOrigHost);
+            dest.writeString(mHost);
+            dest.writeInt(mWild ? 1 : 0);
+            dest.writeInt(mPort);
+        }
+
+        public String getHost() {
+            return mOrigHost;
+        }
+
+        public int getPort() {
+            return mPort;
+        }
+
+        public int match(Uri data) {
+            String host = data.getHost();
+            if (host == null) {
+                return NO_MATCH_DATA;
+            }
+            if (Config.LOGV) Log.v("IntentFilter",
+                    "Match host " + host + ": " + mHost);
+            if (mWild) {
+                if (host.length() < mHost.length()) {
+                    return NO_MATCH_DATA;
+                }
+                host = host.substring(host.length()-mHost.length());
+            }
+            if (host.compareToIgnoreCase(mHost) != 0) {
+                return NO_MATCH_DATA;
+            }
+            if (mPort >= 0) {
+                if (mPort != data.getPort()) {
+                    return NO_MATCH_DATA;
+                }
+                return MATCH_CATEGORY_PORT;
+            }
+            return MATCH_CATEGORY_HOST;
+        }
+    };
+
+    /**
+     * Add a new Intent data authority to match against.  The filter must
+     * include one or more schemes (via {@link #addDataScheme}) for the
+     * authority to be considered.  If any authorities are
+     * included in the filter, then an Intent's data must match one of
+     * them.  If no authorities are included, then only the scheme must match.
+     *
+     * @param host The host part of the authority to match.  May start with a
+     *             single '*' to wildcard the front of the host name.
+     * @param port Optional port part of the authority to match.  If null, any
+     *             port is allowed.
+     *
+     * @see #matchData
+     * @see #addDataScheme
+     */
+    public final void addDataAuthority(String host, String port) {
+        if (mDataAuthorities == null) mDataAuthorities =
+                new ArrayList<AuthorityEntry>();
+        if (port != null) port = port.intern();
+        mDataAuthorities.add(new AuthorityEntry(host.intern(), port));
+    }
+
+    /**
+     * Return the number of data authorities in the filter.
+     */
+    public final int countDataAuthorities() {
+        return mDataAuthorities != null ? mDataAuthorities.size() : 0;
+    }
+
+    /**
+     * Return a data authority in the filter.
+     */
+    public final AuthorityEntry getDataAuthority(int index) {
+        return mDataAuthorities.get(index);
+    }
+
+    /**
+     * Is the given data authority included in the filter?  Note that if the
+     * filter does not include any authorities, false will <em>always</em> be
+     * returned.
+     *
+     * @param data The data whose authority is being looked for.
+     *
+     * @return Returns true if the data string matches an authority listed in the
+     *         filter.
+     */
+    public final boolean hasDataAuthority(Uri data) {
+        return matchDataAuthority(data) >= 0;
+    }
+
+    /**
+     * Return an iterator over the filter's data authorities.
+     */
+    public final Iterator<AuthorityEntry> authoritiesIterator() {
+        return mDataAuthorities != null ? mDataAuthorities.iterator() : null;
+    }
+
+    /**
+     * Add a new Intent data oath to match against.  The filter must
+     * include one or more schemes (via {@link #addDataScheme}) <em>and</em>
+     * one or more authorities (via {@link #addDataAuthority}) for the
+     * path to be considered.  If any paths are
+     * included in the filter, then an Intent's data must match one of
+     * them.  If no paths are included, then only the scheme/authority must
+     * match.
+     *
+     * <p>The path given here can either be a literal that must directly
+     * match or match against a prefix, or it can be a simple globbing pattern.
+     * If the latter, you can use '*' anywhere in the pattern to match zero
+     * or more instances of the previous character, '.' as a wildcard to match
+     * any character, and '\' to escape the next character.
+     *
+     * @param path Either a raw string that must exactly match the file
+     * path, or a simple pattern, depending on <var>type</var>.
+     * @param type Determines how <var>path</var> will be compared to
+     * determine a match: either {@link PatternMatcher#PATTERN_LITERAL},
+     * {@link PatternMatcher#PATTERN_PREFIX}, or
+     * {@link PatternMatcher#PATTERN_SIMPLE_GLOB}.
+     *
+     * @see #matchData
+     * @see #addDataScheme
+     * @see #addDataAuthority
+     */
+    public final void addDataPath(String path, int type) {
+        if (mDataPaths == null) mDataPaths = new ArrayList<PatternMatcher>();
+        mDataPaths.add(new PatternMatcher(path.intern(), type));
+    }
+
+    /**
+     * Return the number of data paths in the filter.
+     */
+    public final int countDataPaths() {
+        return mDataPaths != null ? mDataPaths.size() : 0;
+    }
+
+    /**
+     * Return a data path in the filter.
+     */
+    public final PatternMatcher getDataPath(int index) {
+        return mDataPaths.get(index);
+    }
+
+    /**
+     * Is the given data path included in the filter?  Note that if the
+     * filter does not include any paths, false will <em>always</em> be
+     * returned.
+     *
+     * @param data The data path to look for.  This is without the scheme
+     *             prefix.
+     *
+     * @return True if the data string matches a path listed in the
+     *         filter.
+     */
+    public final boolean hasDataPath(String data) {
+        if (mDataPaths == null) {
+            return false;
+        }
+        Iterator<PatternMatcher> i = mDataPaths.iterator();
+        while (i.hasNext()) {
+            final PatternMatcher pe = i.next();
+            if (pe.match(data)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Return an iterator over the filter's data paths.
+     */
+    public final Iterator<PatternMatcher> pathsIterator() {
+        return mDataPaths != null ? mDataPaths.iterator() : null;
+    }
+
+    /**
+     * Match this intent filter against the given Intent data.  This ignores
+     * the data scheme -- unlike {@link #matchData}, the authority will match
+     * regardless of whether there is a matching scheme.
+     *
+     * @param data The data whose authority is being looked for.
+     *
+     * @return Returns either {@link #MATCH_CATEGORY_HOST},
+     * {@link #MATCH_CATEGORY_PORT}, {@link #NO_MATCH_DATA}.
+     */
+    public final int matchDataAuthority(Uri data) {
+        if (mDataAuthorities == null) {
+            return NO_MATCH_DATA;
+        }
+        Iterator<AuthorityEntry> i = mDataAuthorities.iterator();
+        while (i.hasNext()) {
+            final AuthorityEntry ae = i.next();
+            int match = ae.match(data);
+            if (match >= 0) {
+                return match;
+            }
+        }
+        return NO_MATCH_DATA;
+    }
+
+    /**
+     * Match this filter against an Intent's data (type, scheme and path). If
+     * the filter does not specify any types and does not specify any
+     * schemes/paths, the match will only succeed if the intent does not
+     * also specify a type or data.
+     *
+     * <p>Note that to match against an authority, you must also specify a base
+     * scheme the authority is in.  To match against a data path, both a scheme
+     * and authority must be specified.  If the filter does not specify any
+     * types or schemes that it matches against, it is considered to be empty
+     * (any authority or data path given is ignored, as if it were empty as
+     * well).
+     *
+     * @param type The desired data type to look for, as returned by
+     *             Intent.resolveType().
+     * @param scheme The desired data scheme to look for, as returned by
+     *               Intent.getScheme().
+     * @param data The full data string to match against, as supplied in
+     *             Intent.data.
+     *
+     * @return Returns either a valid match constant (a combination of
+     * {@link #MATCH_CATEGORY_MASK} and {@link #MATCH_ADJUSTMENT_MASK}),
+     * or one of the error codes {@link #NO_MATCH_TYPE} if the type didn't match
+     * or {@link #NO_MATCH_DATA} if the scheme/path didn't match.
+     *
+     * @see #match
+     */
+    public final int matchData(String type, String scheme, Uri data) {
+        final ArrayList<String> types = mDataTypes;
+        final ArrayList<String> schemes = mDataSchemes;
+        final ArrayList<AuthorityEntry> authorities = mDataAuthorities;
+        final ArrayList<PatternMatcher> paths = mDataPaths;
+
+        int match = MATCH_CATEGORY_EMPTY;
+
+        if (types == null && schemes == null) {
+            return ((type == null && data == null)
+                ? (MATCH_CATEGORY_EMPTY+MATCH_ADJUSTMENT_NORMAL) : NO_MATCH_DATA);
+        }
+
+        if (schemes != null) {
+            if (schemes.contains(scheme != null ? scheme : "")) {
+                match = MATCH_CATEGORY_SCHEME;
+            } else {
+                return NO_MATCH_DATA;
+            }
+
+            if (authorities != null) {
+                int authMatch = matchDataAuthority(data);
+                if (authMatch >= 0) {
+                    if (paths == null) {
+                        match = authMatch;
+                    } else if (hasDataPath(data.getPath())) {
+                        match = MATCH_CATEGORY_PATH;
+                    } else {
+                        return NO_MATCH_DATA;
+                    }
+                } else {
+                    return NO_MATCH_DATA;
+                }
+            }
+        } else {
+            // Special case: match either an Intent with no data URI,
+            // or with a scheme: URI.  This is to give a convenience for
+            // the common case where you want to deal with data in a
+            // content provider, which is done by type, and we don't want
+            // to force everyone to say they handle content: or file: URIs.
+            if (scheme != null && !"".equals(scheme)
+                    && !"content".equals(scheme)
+                    && !"file".equals(scheme)) {
+                return NO_MATCH_DATA;
+            }
+        }
+
+        if (types != null) {
+            if (findMimeType(type)) {
+                match = MATCH_CATEGORY_TYPE;
+            } else {
+                return NO_MATCH_TYPE;
+            }
+        } else {
+            // If no MIME types are specified, then we will only match against
+            // an Intent that does not have a MIME type.
+            if (type != null) {
+                return NO_MATCH_TYPE;
+            }
+        }
+
+        return match + MATCH_ADJUSTMENT_NORMAL;
+    }
+
+    /**
+     * Add a new Intent category to match against.  The semantics of
+     * categories is the opposite of actions -- an Intent includes the
+     * categories that it requires, all of which must be included in the
+     * filter in order to match.  In other words, adding a category to the
+     * filter has no impact on matching unless that category is specified in
+     * the intent.
+     *
+     * @param category Name of category to match, i.e. Intent.CATEGORY_EMBED.
+     */
+    public final void addCategory(String category) {
+        if (mCategories == null) mCategories = new ArrayList<String>();
+        if (!mCategories.contains(category)) {
+            mCategories.add(category.intern());
+        }
+    }
+
+    /**
+     * Return the number of categories in the filter.
+     */
+    public final int countCategories() {
+        return mCategories != null ? mCategories.size() : 0;
+    }
+
+    /**
+     * Return a category in the filter.
+     */
+    public final String getCategory(int index) {
+        return mCategories.get(index);
+    }
+
+    /**
+     * Is the given category included in the filter?
+     *
+     * @param category The category that the filter supports.
+     *
+     * @return True if the category is explicitly mentioned in the filter.
+     */
+    public final boolean hasCategory(String category) {
+        return mCategories != null && mCategories.contains(category);
+    }
+
+    /**
+     * Return an iterator over the filter's categories.
+     */
+    public final Iterator<String> categoriesIterator() {
+        return mCategories != null ? mCategories.iterator() : null;
+    }
+
+    /**
+     * Match this filter against an Intent's categories.  Each category in
+     * the Intent must be specified by the filter; if any are not in the
+     * filter, the match fails.
+     *
+     * @param categories The categories included in the intent, as returned by
+     *                   Intent.getCategories().
+     *
+     * @return If all categories match (success), null; else the name of the
+     *         first category that didn't match.
+     */
+    public final String matchCategories(Set<String> categories) {
+        if (categories == null) {
+            return null;
+        }
+
+        Iterator<String> it = categories.iterator();
+
+        if (mCategories == null) {
+            return it.hasNext() ? it.next() : null;
+        }
+
+        while (it.hasNext()) {
+            final String category = it.next();
+            if (!mCategories.contains(category)) {
+                return category;
+            }
+        }
+
+        return null;
+    }
+
+    /**
+     * Test whether this filter matches the given <var>intent</var>.
+     *
+     * @param intent The Intent to compare against.
+     * @param resolve If true, the intent's type will be resolved by calling
+     *                Intent.resolveType(); otherwise a simple match against
+     *                Intent.type will be performed.
+     * @param logTag Tag to use in debugging messages.
+     *
+     * @return Returns either a valid match constant (a combination of
+     * {@link #MATCH_CATEGORY_MASK} and {@link #MATCH_ADJUSTMENT_MASK}),
+     * or one of the error codes {@link #NO_MATCH_TYPE} if the type didn't match,
+     * {@link #NO_MATCH_DATA} if the scheme/path didn't match,
+     * {@link #NO_MATCH_ACTION if the action didn't match, or
+     * {@link #NO_MATCH_CATEGORY} if one or more categories didn't match.
+     *
+     * @return How well the filter matches.  Negative if it doesn't match,
+     *         zero or positive positive value if it does with a higher
+     *         value representing a better match.
+     *
+     * @see #match(String, String, String, android.net.Uri , Set, String)
+     */
+    public final int match(ContentResolver resolver, Intent intent,
+            boolean resolve, String logTag) {
+        String type = resolve ? intent.resolveType(resolver) : intent.getType();
+        return match(intent.getAction(), type, intent.getScheme(),
+                     intent.getData(), intent.getCategories(), logTag);
+    }
+
+    /**
+     * Test whether this filter matches the given intent data.  A match is
+     * only successful if the actions and categories in the Intent match
+     * against the filter, as described in {@link IntentFilter}; in that case,
+     * the match result returned will be as per {@link #matchData}.
+     *
+     * @param action The intent action to match against (Intent.getAction).
+     * @param type The intent type to match against (Intent.resolveType()).
+     * @param scheme The data scheme to match against (Intent.getScheme()).
+     * @param data The data URI to match against (Intent.getData()).
+     * @param categories The categories to match against
+     *                   (Intent.getCategories()).
+     * @param logTag Tag to use in debugging messages.
+     *
+     * @return Returns either a valid match constant (a combination of
+     * {@link #MATCH_CATEGORY_MASK} and {@link #MATCH_ADJUSTMENT_MASK}),
+     * or one of the error codes {@link #NO_MATCH_TYPE} if the type didn't match,
+     * {@link #NO_MATCH_DATA} if the scheme/path didn't match,
+     * {@link #NO_MATCH_ACTION if the action didn't match, or
+     * {@link #NO_MATCH_CATEGORY} if one or more categories didn't match.
+     *
+     * @see #matchData
+     * @see Intent#getAction
+     * @see Intent#resolveType
+     * @see Intent#getScheme
+     * @see Intent#getData
+     * @see Intent#getCategories
+     */
+    public final int match(String action, String type, String scheme,
+            Uri data, Set<String> categories, String logTag) {
+        if (action != null && !matchAction(action)) {
+            if (Config.LOGV) Log.v(
+                logTag, "No matching action " + action + " for " + this);
+            return NO_MATCH_ACTION;
+        }
+
+        int dataMatch = matchData(type, scheme, data);
+        if (dataMatch < 0) {
+            if (Config.LOGV) {
+                if (dataMatch == NO_MATCH_TYPE) {
+                    Log.v(logTag, "No matching type " + type
+                          + " for " + this);
+                }
+                if (dataMatch == NO_MATCH_DATA) {
+                    Log.v(logTag, "No matching scheme/path " + data
+                          + " for " + this);
+                }
+            }
+            return dataMatch;
+        }
+
+        String categoryMatch = matchCategories(categories);
+        if (categoryMatch != null) {
+            if (Config.LOGV) Log.v(
+                logTag, "No matching category "
+                + categoryMatch + " for " + this);
+            return NO_MATCH_CATEGORY;
+        }
+
+        // It would be nice to treat container activities as more
+        // important than ones that can be embedded, but this is not the way...
+        if (false) {
+            if (categories != null) {
+                dataMatch -= mCategories.size() - categories.size();
+            }
+        }
+
+        return dataMatch;
+    }
+
+    /**
+     * Write the contents of the IntentFilter as an XML stream.
+     */
+    public void writeToXml(XmlSerializer serializer) throws IOException {
+        int N = countActions();
+        for (int i=0; i<N; i++) {
+            serializer.startTag(null, ACTION_STR);
+            serializer.attribute(null, NAME_STR, mActions.get(i));
+            serializer.endTag(null, ACTION_STR);
+        }
+        N = countCategories();
+        for (int i=0; i<N; i++) {
+            serializer.startTag(null, CAT_STR);
+            serializer.attribute(null, NAME_STR, mCategories.get(i));
+            serializer.endTag(null, CAT_STR);
+        }
+        N = countDataTypes();
+        for (int i=0; i<N; i++) {
+            serializer.startTag(null, TYPE_STR);
+            String type = mDataTypes.get(i);
+            if (type.indexOf('/') < 0) type = type + "/*";
+            serializer.attribute(null, NAME_STR, type);
+            serializer.endTag(null, TYPE_STR);
+        }
+        N = countDataSchemes();
+        for (int i=0; i<N; i++) {
+            serializer.startTag(null, SCHEME_STR);
+            serializer.attribute(null, NAME_STR, mDataSchemes.get(i));
+            serializer.endTag(null, SCHEME_STR);
+        }
+        N = countDataAuthorities();
+        for (int i=0; i<N; i++) {
+            serializer.startTag(null, AUTH_STR);
+            AuthorityEntry ae = mDataAuthorities.get(i);
+            serializer.attribute(null, HOST_STR, ae.getHost());
+            if (ae.getPort() >= 0) {
+                serializer.attribute(null, PORT_STR, Integer.toString(ae.getPort()));
+            }
+            serializer.endTag(null, AUTH_STR);
+        }
+        N = countDataPaths();
+        for (int i=0; i<N; i++) {
+            serializer.startTag(null, PATH_STR);
+            PatternMatcher pe = mDataPaths.get(i);
+            switch (pe.getType()) {
+                case PatternMatcher.PATTERN_LITERAL:
+                    serializer.attribute(null, LITERAL_STR, pe.getPath());
+                    break;
+                case PatternMatcher.PATTERN_PREFIX:
+                    serializer.attribute(null, PREFIX_STR, pe.getPath());
+                    break;
+                case PatternMatcher.PATTERN_SIMPLE_GLOB:
+                    serializer.attribute(null, SGLOB_STR, pe.getPath());
+                    break;
+            }
+            serializer.endTag(null, PATH_STR);
+        }
+    }
+
+    public void readFromXml(XmlPullParser parser) throws XmlPullParserException,
+            IOException {
+        int outerDepth = parser.getDepth();
+        int type;
+        while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
+               && (type != XmlPullParser.END_TAG
+                       || parser.getDepth() > outerDepth)) {
+            if (type == XmlPullParser.END_TAG
+                    || type == XmlPullParser.TEXT) {
+                continue;
+            }
+
+            String tagName = parser.getName();
+            if (tagName.equals(ACTION_STR)) {
+                String name = parser.getAttributeValue(null, NAME_STR);
+                if (name != null) {
+                    addAction(name);
+                }
+            } else if (tagName.equals(CAT_STR)) {
+                String name = parser.getAttributeValue(null, NAME_STR);
+                if (name != null) {
+                    addCategory(name);
+                }
+            } else if (tagName.equals(TYPE_STR)) {
+                String name = parser.getAttributeValue(null, NAME_STR);
+                if (name != null) {
+                    try {
+                        addDataType(name);
+                    } catch (MalformedMimeTypeException e) {
+                    }
+                }
+            } else if (tagName.equals(SCHEME_STR)) {
+                String name = parser.getAttributeValue(null, NAME_STR);
+                if (name != null) {
+                    addDataScheme(name);
+                }
+            } else if (tagName.equals(AUTH_STR)) {
+                String host = parser.getAttributeValue(null, HOST_STR);
+                String port = parser.getAttributeValue(null, PORT_STR);
+                if (host != null) {
+                    addDataAuthority(host, port);
+                }
+            } else if (tagName.equals(PATH_STR)) {
+                String path = parser.getAttributeValue(null, LITERAL_STR);
+                if (path != null) {
+                    addDataPath(path, PatternMatcher.PATTERN_LITERAL);
+                } else if ((path=parser.getAttributeValue(null, PREFIX_STR)) != null) {
+                    addDataPath(path, PatternMatcher.PATTERN_PREFIX);
+                } else if ((path=parser.getAttributeValue(null, SGLOB_STR)) != null) {
+                    addDataPath(path, PatternMatcher.PATTERN_SIMPLE_GLOB);
+                }
+            } else {
+                Log.w("IntentFilter", "Unknown tag parsing IntentFilter: " + tagName);
+            }
+            XmlUtils.skipCurrentTag(parser);
+        }
+    }
+
+    public void dump(Printer du, String prefix) {
+        if (mActions.size() > 0) {
+            Iterator<String> it = mActions.iterator();
+            while (it.hasNext()) {
+               du.println(prefix + "Action: \"" + it.next() + "\"");
+            }
+        }
+        if (mCategories != null) {
+            Iterator<String> it = mCategories.iterator();
+            while (it.hasNext()) {
+                du.println(prefix + "Category: \"" + it.next() + "\"");
+            }
+        }
+        if (mDataSchemes != null) {
+            Iterator<String> it = mDataSchemes.iterator();
+            while (it.hasNext()) {
+                du.println(prefix + "Data Scheme: \"" + it.next() + "\"");
+            }
+        }
+        if (mDataAuthorities != null) {
+            Iterator<AuthorityEntry> it = mDataAuthorities.iterator();
+            while (it.hasNext()) {
+                AuthorityEntry ae = it.next();
+                du.println(prefix + "Data Authority: \"" + ae.mHost + "\":"
+                        + ae.mPort + (ae.mWild ? " WILD" : ""));
+            }
+        }
+        if (mDataPaths != null) {
+            Iterator<PatternMatcher> it = mDataPaths.iterator();
+            while (it.hasNext()) {
+                PatternMatcher pe = it.next();
+                du.println(prefix + "Data Path: \"" + pe + "\"");
+            }
+        }
+        if (mDataTypes != null) {
+            Iterator<String> it = mDataTypes.iterator();
+            while (it.hasNext()) {
+                du.println(prefix + "Data Type: \"" + it.next() + "\"");
+            }
+        }
+        du.println(prefix + "mPriority=" + mPriority
+                + ", mHasPartialTypes=" + mHasPartialTypes);
+    }
+
+    public static final Parcelable.Creator<IntentFilter> CREATOR
+            = new Parcelable.Creator<IntentFilter>() {
+        public IntentFilter createFromParcel(Parcel source) {
+            return new IntentFilter(source);
+        }
+
+        public IntentFilter[] newArray(int size) {
+            return new IntentFilter[size];
+        }
+    };
+
+    public final int describeContents() {
+        return 0;
+    }
+
+    public final void writeToParcel(Parcel dest, int flags) {
+        dest.writeStringList(mActions);
+        if (mCategories != null) {
+            dest.writeInt(1);
+            dest.writeStringList(mCategories);
+        } else {
+            dest.writeInt(0);
+        }
+        if (mDataSchemes != null) {
+            dest.writeInt(1);
+            dest.writeStringList(mDataSchemes);
+        } else {
+            dest.writeInt(0);
+        }
+        if (mDataTypes != null) {
+            dest.writeInt(1);
+            dest.writeStringList(mDataTypes);
+        } else {
+            dest.writeInt(0);
+        }
+        if (mDataAuthorities != null) {
+            final int N = mDataAuthorities.size();
+            dest.writeInt(N);
+            for (int i=0; i<N; i++) {
+                mDataAuthorities.get(i).writeToParcel(dest);
+            }
+        } else {
+            dest.writeInt(0);
+        }
+        if (mDataPaths != null) {
+            final int N = mDataPaths.size();
+            dest.writeInt(N);
+            for (int i=0; i<N; i++) {
+                mDataPaths.get(i).writeToParcel(dest, 0);
+            }
+        } else {
+            dest.writeInt(0);
+        }
+        dest.writeInt(mPriority);
+        dest.writeInt(mHasPartialTypes ? 1 : 0);
+    }
+
+    /**
+     * For debugging -- perform a check on the filter, return true if it passed
+     * or false if it failed.
+     *
+     * {@hide}
+     */
+    public boolean debugCheck() {
+        return true;
+
+        // This code looks for intent filters that do not specify data.
+        /*
+        if (mActions != null && mActions.size() == 1
+                && mActions.contains(Intent.ACTION_MAIN)) {
+            return true;
+        }
+
+        if (mDataTypes == null && mDataSchemes == null) {
+            Log.w("IntentFilter", "QUESTIONABLE INTENT FILTER:");
+            dump(Log.WARN, "IntentFilter", "  ");
+            return false;
+        }
+
+        return true;
+        */
+    }
+
+    private IntentFilter(Parcel source) {
+        mActions = new ArrayList<String>();
+        source.readStringList(mActions);
+        if (source.readInt() != 0) {
+            mCategories = new ArrayList<String>();
+            source.readStringList(mCategories);
+        }
+        if (source.readInt() != 0) {
+            mDataSchemes = new ArrayList<String>();
+            source.readStringList(mDataSchemes);
+        }
+        if (source.readInt() != 0) {
+            mDataTypes = new ArrayList<String>();
+            source.readStringList(mDataTypes);
+        }
+        int N = source.readInt();
+        if (N > 0) {
+            mDataAuthorities = new ArrayList<AuthorityEntry>();
+            for (int i=0; i<N; i++) {
+                mDataAuthorities.add(new AuthorityEntry(source));
+            }
+        }
+        N = source.readInt();
+        if (N > 0) {
+            mDataPaths = new ArrayList<PatternMatcher>();
+            for (int i=0; i<N; i++) {
+                mDataPaths.add(new PatternMatcher(source));
+            }
+        }
+        mPriority = source.readInt();
+        mHasPartialTypes = source.readInt() > 0;
+    }
+
+    private final boolean findMimeType(String type) {
+        final ArrayList<String> t = mDataTypes;
+
+        if (type == null) {
+            return false;
+        }
+
+        if (t.contains(type)) {
+            return true;
+        }
+
+        // Deal with an Intent wanting to match every type in the IntentFilter.
+        final int typeLength = type.length();
+        if (typeLength == 3 && type.equals("*/*")) {
+            return !t.isEmpty();
+        }
+
+        // Deal with this IntentFilter wanting to match every Intent type.
+        if (mHasPartialTypes && t.contains("*")) {
+            return true;
+        }
+
+        final int slashpos = type.indexOf('/');
+        if (slashpos > 0) {
+            if (mHasPartialTypes && t.contains(type.substring(0, slashpos))) {
+                return true;
+            }
+            if (typeLength == slashpos+2 && type.charAt(slashpos+1) == '*') {
+                // Need to look through all types for one that matches
+                // our base...
+                final Iterator<String> it = t.iterator();
+                while (it.hasNext()) {
+                    String v = it.next();
+                    if (type.regionMatches(0, v, 0, slashpos+1)) {
+                        return true;
+                    }
+                }
+            }
+        }
+
+        return false;
+    }
+}
diff --git a/core/java/android/content/MutableContextWrapper.java b/core/java/android/content/MutableContextWrapper.java
new file mode 100644
index 0000000..820479c
--- /dev/null
+++ b/core/java/android/content/MutableContextWrapper.java
@@ -0,0 +1,38 @@
+/*
+ * 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 android.content;
+
+/**
+ * Special version of {@link ContextWrapper} that allows the base context to
+ * be modified after it is initially set.
+ */
+public class MutableContextWrapper extends ContextWrapper {
+    public MutableContextWrapper(Context base) {
+        super(base);
+    }
+    
+    /**
+     * Change the base context for this ContextWrapper. All calls will then be
+     * delegated to the base context.  Unlike ContextWrapper, the base context
+     * can be changed even after one is already set.
+     * 
+     * @param base The new base context for this wrapper.
+     */
+    public void setBaseContext(Context base) {
+        mBase = base;
+    }
+}
diff --git a/core/java/android/content/ReceiverCallNotAllowedException.java b/core/java/android/content/ReceiverCallNotAllowedException.java
new file mode 100644
index 0000000..96b269c
--- /dev/null
+++ b/core/java/android/content/ReceiverCallNotAllowedException.java
@@ -0,0 +1,32 @@
+/*
+ * 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 android.content;
+
+import android.util.AndroidRuntimeException;
+
+/**
+ * This exception is thrown from {@link Context#registerReceiver} and
+ * {@link Context#bindService} when these methods are being used from
+ * an {@link BroadcastReceiver} component.  In this case, the component will no
+ * longer be active upon returning from receiving the Intent, so it is
+ * not valid to use asynchronous APIs.
+ */
+public class ReceiverCallNotAllowedException extends AndroidRuntimeException {
+    public ReceiverCallNotAllowedException(String msg) {
+        super(msg);
+    }
+}
diff --git a/core/java/android/content/SearchRecentSuggestionsProvider.java b/core/java/android/content/SearchRecentSuggestionsProvider.java
new file mode 100644
index 0000000..3d89e92
--- /dev/null
+++ b/core/java/android/content/SearchRecentSuggestionsProvider.java
@@ -0,0 +1,385 @@
+/*
+ * 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 android.content;
+
+import android.app.SearchManager;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteOpenHelper;
+import android.net.Uri;
+import android.text.TextUtils;
+import android.util.Log;
+
+/**
+ * This superclass can be used to create a simple search suggestions provider for your application.
+ * It creates suggestions (as the user types) based on recent queries and/or recent views.
+ * 
+ * <p>In order to use this class, you must do the following.
+ * 
+ * <ul>
+ * <li>Implement and test query search, as described in {@link android.app.SearchManager}.  (This
+ * provider will send any suggested queries via the standard 
+ * {@link android.content.Intent#ACTION_SEARCH ACTION_SEARCH} Intent, which you'll already
+ * support once you have implemented and tested basic searchability.)</li>
+ * <li>Create a Content Provider within your application by extending 
+ * {@link android.content.SearchRecentSuggestionsProvider}.  The class you create will be
+ * very simple - typically, it will have only a constructor.  But the constructor has a very 
+ * important responsibility:  When it calls {@link #setupSuggestions(String, int)}, it
+ * <i>configures</i> the provider to match the requirements of your searchable activity.</li>
+ * <li>Create a manifest entry describing your provider.  Typically this would be as simple
+ * as adding the following lines:
+ * <pre class="prettyprint">
+ *     &lt;!-- Content provider for search suggestions --&gt;
+ *     &lt;provider android:name="YourSuggestionProviderClass"
+ *               android:authorities="your.suggestion.authority" /&gt;</pre>
+ * </li>
+ * <li>Please note that you <i>do not</i> instantiate this content provider directly from within
+ * your code.  This is done automatically by the system Content Resolver, when the search dialog
+ * looks for suggestions.</li>
+ * <li>In order for the Content Resolver to do this, you must update your searchable activity's 
+ * XML configuration file with information about your content provider.  The following additions 
+ * are usually sufficient:
+ * <pre class="prettyprint">
+ *     android:searchSuggestAuthority="your.suggestion.authority"
+ *     android:searchSuggestSelection=" ? "</pre>
+ * </li>
+ * <li>In your searchable activities, capture any user-generated queries and record them
+ * for future searches by calling {@link android.provider.SearchRecentSuggestions#saveRecentQuery
+ * SearchRecentSuggestions.saveRecentQuery()}.</li>
+ * </ul>
+ * 
+ * @see android.provider.SearchRecentSuggestions
+ */
+public class SearchRecentSuggestionsProvider extends ContentProvider {
+    // debugging support
+    private static final String TAG = "SuggestionsProvider";
+    
+    // client-provided configuration values
+    private String mAuthority;
+    private int mMode;
+    private boolean mTwoLineDisplay;
+
+    // general database configuration and tables
+    private SQLiteOpenHelper mOpenHelper;
+    private static final String sDatabaseName = "suggestions.db";
+    private static final String sSuggestions = "suggestions";
+    private static final String ORDER_BY = "date DESC";
+    private static final String NULL_COLUMN = "query";
+    
+    // Table of database versions.  Don't forget to update!
+    // NOTE:  These version values are shifted left 8 bits (x 256) in order to create space for
+    // a small set of mode bitflags in the version int.
+    //
+    // 1      original implementation with queries, and 1 or 2 display columns
+    // 1->2   added UNIQUE constraint to display1 column
+    private static final int DATABASE_VERSION = 2 * 256;
+    
+    /**
+     * This mode bit configures the database to record recent queries.  <i>required</i>
+     * 
+     * @see #setupSuggestions(String, int)
+     */
+    public static final int DATABASE_MODE_QUERIES = 1;
+    /**
+     * This mode bit configures the database to include a 2nd annotation line with each entry.
+     * <i>optional</i>
+     * 
+     * @see #setupSuggestions(String, int)
+     */
+    public static final int DATABASE_MODE_2LINES = 2;
+
+    // Uri and query support
+    private static final int URI_MATCH_SUGGEST = 1;
+    
+    private Uri mSuggestionsUri;
+    private UriMatcher mUriMatcher;
+    
+    private String mSuggestSuggestionClause;
+    private String[] mSuggestionProjection;
+
+    /**
+     * Builds the database.  This version has extra support for using the version field
+     * as a mode flags field, and configures the database columns depending on the mode bits
+     * (features) requested by the extending class.
+     * 
+     * @hide
+     */
+    private static class DatabaseHelper extends SQLiteOpenHelper {
+        
+        private int mNewVersion;
+        
+        public DatabaseHelper(Context context, int newVersion) {
+            super(context, sDatabaseName, null, newVersion);
+            mNewVersion = newVersion;
+        }
+        
+        @Override
+        public void onCreate(SQLiteDatabase db) {
+            StringBuilder builder = new StringBuilder();
+            builder.append("CREATE TABLE suggestions (" +
+                    "_id INTEGER PRIMARY KEY" +
+                    ",display1 TEXT UNIQUE ON CONFLICT REPLACE");
+            if (0 != (mNewVersion & DATABASE_MODE_2LINES)) {
+                builder.append(",display2 TEXT");
+            }
+            builder.append(",query TEXT" +
+                    ",date LONG" +
+                    ");");
+            db.execSQL(builder.toString());
+        }
+
+        @Override
+        public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
+            Log.w(TAG, "Upgrading database from version " + oldVersion + " to "
+                    + newVersion + ", which will destroy all old data");
+            db.execSQL("DROP TABLE IF EXISTS suggestions");
+            onCreate(db);
+        }
+    }
+    
+    /**
+     * In order to use this class, you must extend it, and call this setup function from your
+     * constructor.  In your application or activities, you must provide the same values when 
+     * you create the {@link android.provider.SearchRecentSuggestions} helper.
+     * 
+     * @param authority This must match the authority that you've declared in your manifest.
+     * @param mode You can use mode flags here to determine certain functional aspects of your
+     * database.  Note, this value should not change from run to run, because when it does change,
+     * your suggestions database may be wiped.
+     * 
+     * @see #DATABASE_MODE_QUERIES
+     * @see #DATABASE_MODE_2LINES
+     */
+    protected void setupSuggestions(String authority, int mode) {
+        if (TextUtils.isEmpty(authority) || 
+                ((mode & DATABASE_MODE_QUERIES) == 0)) {
+            throw new IllegalArgumentException();
+        }
+        // unpack mode flags
+        mTwoLineDisplay = (0 != (mode & DATABASE_MODE_2LINES));
+            
+        // saved values
+        mAuthority = new String(authority);
+        mMode = mode;
+        
+        // derived values
+        mSuggestionsUri = Uri.parse("content://" + mAuthority + "/suggestions");
+        mUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
+        mUriMatcher.addURI(mAuthority, SearchManager.SUGGEST_URI_PATH_QUERY, URI_MATCH_SUGGEST);
+        
+        if (mTwoLineDisplay) {
+            mSuggestSuggestionClause = "display1 LIKE ? OR display2 LIKE ?";
+
+            mSuggestionProjection = new String [] {
+                    "0 AS " + SearchManager.SUGGEST_COLUMN_FORMAT,
+                    "display1 AS " + SearchManager.SUGGEST_COLUMN_TEXT_1,
+                    "display2 AS " + SearchManager.SUGGEST_COLUMN_TEXT_2,
+                    "query AS " + SearchManager.SUGGEST_COLUMN_QUERY,
+                    "_id"
+            };
+        } else {
+            mSuggestSuggestionClause = "display1 LIKE ?";
+
+            mSuggestionProjection = new String [] {
+                    "0 AS " + SearchManager.SUGGEST_COLUMN_FORMAT,
+                    "display1 AS " + SearchManager.SUGGEST_COLUMN_TEXT_1,
+                    "query AS " + SearchManager.SUGGEST_COLUMN_QUERY,
+                    "_id"
+            };
+        }
+
+
+    }
+    
+    /**
+     * This method is provided for use by the ContentResolver.  Do not override, or directly
+     * call from your own code.
+     */
+    @Override
+    public int delete(Uri uri, String selection, String[] selectionArgs) {
+        SQLiteDatabase db = mOpenHelper.getWritableDatabase();
+
+        final int length = uri.getPathSegments().size();
+        if (length != 1) {
+            throw new IllegalArgumentException("Unknown Uri");
+        }
+
+        final String base = uri.getPathSegments().get(0);
+        int count = 0;
+        if (base.equals(sSuggestions)) {
+            count = db.delete(sSuggestions, selection, selectionArgs);
+        } else {
+            throw new IllegalArgumentException("Unknown Uri");
+        }
+        getContext().getContentResolver().notifyChange(uri, null);
+        return count;
+    }
+
+    /**
+     * This method is provided for use by the ContentResolver.  Do not override, or directly
+     * call from your own code.
+     */
+    @Override
+    public String getType(Uri uri) {
+        if (mUriMatcher.match(uri) == URI_MATCH_SUGGEST) {
+            return SearchManager.SUGGEST_MIME_TYPE;
+        }
+        int length = uri.getPathSegments().size();
+        if (length >= 1) {
+            String base = uri.getPathSegments().get(0);
+            if (base.equals(sSuggestions)) {
+                if (length == 1) {
+                    return "vnd.android.cursor.dir/suggestion";
+                } else if (length == 2) {
+                    return "vnd.android.cursor.item/suggestion";
+                }
+            }
+        }            
+        throw new IllegalArgumentException("Unknown Uri");
+    }
+
+    /**
+     * This method is provided for use by the ContentResolver.  Do not override, or directly
+     * call from your own code.
+     */
+    @Override
+    public Uri insert(Uri uri, ContentValues values) {
+        SQLiteDatabase db = mOpenHelper.getWritableDatabase();
+
+        int length = uri.getPathSegments().size();
+        if (length < 1) {
+            throw new IllegalArgumentException("Unknown Uri");
+        }
+        // Note:  This table has on-conflict-replace semantics, so insert() may actually replace()
+        long rowID = -1;
+        String base = uri.getPathSegments().get(0);
+        Uri newUri = null;
+        if (base.equals(sSuggestions)) {
+            if (length == 1) {
+                rowID = db.insert(sSuggestions, NULL_COLUMN, values);
+                if (rowID > 0) {
+                    newUri = Uri.withAppendedPath(mSuggestionsUri, String.valueOf(rowID));
+                }
+            }
+        }
+        if (rowID < 0) {
+            throw new IllegalArgumentException("Unknown Uri");
+        }
+        getContext().getContentResolver().notifyChange(newUri, null);
+        return newUri;
+    }
+
+    /**
+     * This method is provided for use by the ContentResolver.  Do not override, or directly
+     * call from your own code.
+     */
+    @Override
+    public boolean onCreate() {
+        if (mAuthority == null || mMode == 0) {
+            throw new IllegalArgumentException("Provider not configured");
+        }
+        int mWorkingDbVersion = DATABASE_VERSION + mMode;
+        mOpenHelper = new DatabaseHelper(getContext(), mWorkingDbVersion);
+        
+        return true;
+    }
+
+    /**
+     * This method is provided for use by the ContentResolver.  Do not override, or directly
+     * call from your own code.
+     */
+    // TODO: Confirm no injection attacks here, or rewrite.
+    @Override
+    public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, 
+            String sortOrder) {
+        SQLiteDatabase db = mOpenHelper.getReadableDatabase();
+        
+        // special case for actual suggestions (from search manager)
+        if (mUriMatcher.match(uri) == URI_MATCH_SUGGEST) {
+            String suggestSelection;
+            String[] myArgs;
+            if (TextUtils.isEmpty(selectionArgs[0])) {
+                suggestSelection = null;
+                myArgs = null;
+            } else {
+                String like = "%" + selectionArgs[0] + "%";
+                if (mTwoLineDisplay) {
+                    myArgs = new String [] { like, like };
+                } else {
+                    myArgs = new String [] { like };
+                }
+                suggestSelection = mSuggestSuggestionClause;
+            }
+            // Suggestions are always performed with the default sort order
+            Cursor c = db.query(sSuggestions, mSuggestionProjection,
+                    suggestSelection, myArgs, null, null, ORDER_BY, null);
+            c.setNotificationUri(getContext().getContentResolver(), uri);
+            return c;
+        }
+
+        // otherwise process arguments and perform a standard query
+        int length = uri.getPathSegments().size();
+        if (length != 1 && length != 2) {
+            throw new IllegalArgumentException("Unknown Uri");
+        }
+
+        String base = uri.getPathSegments().get(0);
+        if (!base.equals(sSuggestions)) {
+            throw new IllegalArgumentException("Unknown Uri");
+        }
+
+        String[] useProjection = null;
+        if (projection != null && projection.length > 0) {
+            useProjection = new String[projection.length + 1];
+            System.arraycopy(projection, 0, useProjection, 0, projection.length);
+            useProjection[projection.length] = "_id AS _id";
+        }
+
+        StringBuilder whereClause = new StringBuilder(256);
+        if (length == 2) {
+            whereClause.append("(_id = ").append(uri.getPathSegments().get(1)).append(")");
+        }
+
+        // Tack on the user's selection, if present
+        if (selection != null && selection.length() > 0) {
+            if (whereClause.length() > 0) {
+                whereClause.append(" AND ");
+            }
+
+            whereClause.append('(');
+            whereClause.append(selection);
+            whereClause.append(')');
+        }
+        
+        // And perform the generic query as requested
+        Cursor c = db.query(base, useProjection, whereClause.toString(),
+                selectionArgs, null, null, sortOrder,
+                null);
+        c.setNotificationUri(getContext().getContentResolver(), uri);
+        return c;
+    }
+
+    /**
+     * This method is provided for use by the ContentResolver.  Do not override, or directly
+     * call from your own code.
+     */
+    @Override
+    public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
+        throw new UnsupportedOperationException("Not implemented");
+    }
+
+}
diff --git a/core/java/android/content/ServiceConnection.java b/core/java/android/content/ServiceConnection.java
new file mode 100644
index 0000000..d115ce4
--- /dev/null
+++ b/core/java/android/content/ServiceConnection.java
@@ -0,0 +1,53 @@
+/*
+ * 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 android.content;
+
+import android.os.IBinder;
+
+/**
+ * Interface for monitoring the state of an application service.  See
+ * {@link android.app.Service} and
+ * {@link Context#bindService Context.bindService()} for more information.
+ * <p>Like many callbacks from the system, the methods on this class are called
+ * from the main thread of your process.
+ */
+public interface ServiceConnection {
+    /**
+     * Called when a connection to the Service has been established, with
+     * the {@link android.os.IBinder} of the communication channel to the
+     * Service.
+     *
+     * @param name The concrete component name of the service that has
+     * been connected.
+     *
+     * @param service The IBinder of the Service's communication channel,
+     * which you can now make calls on.
+     */
+    public void onServiceConnected(ComponentName name, IBinder service);
+
+    /**
+     * Called when a connection to the Service has been lost.  This typically
+     * happens when the process hosting the service has crashed or been killed.
+     * This does <em>not</em> remove the ServiceConnection itself -- this
+     * binding to the service will remain active, and you will receive a call
+     * to {@link #onServiceConnected} when the Service is next running.
+     *
+     * @param name The concrete component name of the service whose
+     * connection has been lost.
+     */
+    public void onServiceDisconnected(ComponentName name);
+}
diff --git a/core/java/android/content/SharedPreferences.java b/core/java/android/content/SharedPreferences.java
new file mode 100644
index 0000000..a15e29e
--- /dev/null
+++ b/core/java/android/content/SharedPreferences.java
@@ -0,0 +1,282 @@
+/*
+ * 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 android.content;
+
+import java.util.Map;
+
+/**
+ * Interface for accessing and modifying preference data returned by {@link
+ * Context#getSharedPreferences}.  For any particular set of preferences,
+ * there is a single instance of this class that all clients share.
+ * Modifications to the preferences must go through an {@link Editor} object
+ * to ensure the preference values remain in a consistent state and control
+ * when they are committed to storage.
+ *
+ * <p><em>Note: currently this class does not support use across multiple
+ * processes.  This will be added later.</em>
+ *
+ * @see Context#getSharedPreferences
+ */
+public interface SharedPreferences {
+    /**
+     * Interface definition for a callback to be invoked when a shared
+     * preference is changed.
+     */
+    public interface OnSharedPreferenceChangeListener {
+        /**
+         * Called when a shared preference is changed, added, or removed. This
+         * may be called even if a preference is set to its existing value.
+         * 
+         * @param sharedPreferences The {@link SharedPreferences} that received
+         *            the change.
+         * @param key The key of the preference that was changed, added, or
+         *            removed.
+         */
+        void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key);
+    }
+    
+    /**
+     * Interface used for modifying values in a {@link SharedPreferences}
+     * object.  All changes you make in an editor are batched, and not copied
+     * back to the original {@link SharedPreferences} or persistent storage
+     * until you call {@link #commit}.
+     */
+    public interface Editor {
+        /**
+         * Set a String value in the preferences editor, to be written back once
+         * {@link #commit} is called.
+         * 
+         * @param key The name of the preference to modify.
+         * @param value The new value for the preference.
+         * 
+         * @return Returns a reference to the same Editor object, so you can
+         * chain put calls together.
+         */
+        Editor putString(String key, String value);
+        
+        /**
+         * Set an int value in the preferences editor, to be written back once
+         * {@link #commit} is called.
+         * 
+         * @param key The name of the preference to modify.
+         * @param value The new value for the preference.
+         * 
+         * @return Returns a reference to the same Editor object, so you can
+         * chain put calls together.
+         */
+        Editor putInt(String key, int value);
+        
+        /**
+         * Set a long value in the preferences editor, to be written back once
+         * {@link #commit} is called.
+         * 
+         * @param key The name of the preference to modify.
+         * @param value The new value for the preference.
+         * 
+         * @return Returns a reference to the same Editor object, so you can
+         * chain put calls together.
+         */
+        Editor putLong(String key, long value);
+        
+        /**
+         * Set a float value in the preferences editor, to be written back once
+         * {@link #commit} is called.
+         * 
+         * @param key The name of the preference to modify.
+         * @param value The new value for the preference.
+         * 
+         * @return Returns a reference to the same Editor object, so you can
+         * chain put calls together.
+         */
+        Editor putFloat(String key, float value);
+        
+        /**
+         * Set a boolean value in the preferences editor, to be written back
+         * once {@link #commit} is called.
+         * 
+         * @param key The name of the preference to modify.
+         * @param value The new value for the preference.
+         * 
+         * @return Returns a reference to the same Editor object, so you can
+         * chain put calls together.
+         */
+        Editor putBoolean(String key, boolean value);
+
+        /**
+         * Mark in the editor that a preference value should be removed, which
+         * will be done in the actual preferences once {@link #commit} is
+         * called.
+         * 
+         * <p>Note that when committing back to the preferences, all removals
+         * are done first, regardless of whether you called remove before
+         * or after put methods on this editor.
+         * 
+         * @param key The name of the preference to remove.
+         * 
+         * @return Returns a reference to the same Editor object, so you can
+         * chain put calls together.
+         */
+        Editor remove(String key);
+
+        /**
+         * Mark in the editor to remove <em>all</em> values from the
+         * preferences.  Once commit is called, the only remaining preferences
+         * will be any that you have defined in this editor.
+         * 
+         * <p>Note that when committing back to the preferences, the clear
+         * is done first, regardless of whether you called clear before
+         * or after put methods on this editor.
+         * 
+         * @return Returns a reference to the same Editor object, so you can
+         * chain put calls together.
+         */
+        Editor clear();
+
+        /**
+         * Commit your preferences changes back from this Editor to the
+         * {@link SharedPreferences} object it is editing.  This atomically
+         * performs the requested modifications, replacing whatever is currently
+         * in the SharedPreferences.
+         * 
+         * <p>Note that when two editors are modifying preferences at the same
+         * time, the last one to call commit wins.
+         * 
+         * @return Returns true if the new values were successfully written
+         * to persistent storage.
+         */
+        boolean commit();
+    }
+
+    /**
+     * Retrieve all values from the preferences.
+     *
+     * @return Returns a map containing a list of pairs key/value representing
+     * the preferences.
+     *
+     * @throws NullPointerException
+     */
+    Map<String, ?> getAll();
+
+    /**
+     * Retrieve a String value from the preferences.
+     * 
+     * @param key The name of the preference to retrieve.
+     * @param defValue Value to return if this preference does not exist.
+     * 
+     * @return Returns the preference value if it exists, or defValue.  Throws
+     * ClassCastException if there is a preference with this name that is not
+     * a String.
+     * 
+     * @throws ClassCastException
+     */
+    String getString(String key, String defValue);
+    
+    /**
+     * Retrieve an int value from the preferences.
+     * 
+     * @param key The name of the preference to retrieve.
+     * @param defValue Value to return if this preference does not exist.
+     * 
+     * @return Returns the preference value if it exists, or defValue.  Throws
+     * ClassCastException if there is a preference with this name that is not
+     * an int.
+     * 
+     * @throws ClassCastException
+     */
+    int getInt(String key, int defValue);
+    
+    /**
+     * Retrieve a long value from the preferences.
+     * 
+     * @param key The name of the preference to retrieve.
+     * @param defValue Value to return if this preference does not exist.
+     * 
+     * @return Returns the preference value if it exists, or defValue.  Throws
+     * ClassCastException if there is a preference with this name that is not
+     * a long.
+     * 
+     * @throws ClassCastException
+     */
+    long getLong(String key, long defValue);
+    
+    /**
+     * Retrieve a float value from the preferences.
+     * 
+     * @param key The name of the preference to retrieve.
+     * @param defValue Value to return if this preference does not exist.
+     * 
+     * @return Returns the preference value if it exists, or defValue.  Throws
+     * ClassCastException if there is a preference with this name that is not
+     * a float.
+     * 
+     * @throws ClassCastException
+     */
+    float getFloat(String key, float defValue);
+    
+    /**
+     * Retrieve a boolean value from the preferences.
+     * 
+     * @param key The name of the preference to retrieve.
+     * @param defValue Value to return if this preference does not exist.
+     * 
+     * @return Returns the preference value if it exists, or defValue.  Throws
+     * ClassCastException if there is a preference with this name that is not
+     * a boolean.
+     * 
+     * @throws ClassCastException
+     */
+    boolean getBoolean(String key, boolean defValue);
+
+    /**
+     * Checks whether the preferences contains a preference.
+     * 
+     * @param key The name of the preference to check.
+     * @return Returns true if the preference exists in the preferences,
+     *         otherwise false.
+     */
+    boolean contains(String key);
+    
+    /**
+     * Create a new Editor for these preferences, through which you can make
+     * modifications to the data in the preferences and atomically commit those
+     * changes back to the SharedPreferences object.
+     * 
+     * <p>Note that you <em>must</em> call {@link Editor#commit} to have any
+     * changes you perform in the Editor actually show up in the
+     * SharedPreferences.
+     * 
+     * @return Returns a new instance of the {@link Editor} interface, allowing
+     * you to modify the values in this SharedPreferences object.
+     */
+    Editor edit();
+    
+    /**
+     * Registers a callback to be invoked when a change happens to a preference.
+     * 
+     * @param listener The callback that will run.
+     * @see #unregisterOnSharedPreferenceChangeListener
+     */
+    void registerOnSharedPreferenceChangeListener(OnSharedPreferenceChangeListener listener);
+    
+    /**
+     * Unregisters a previous callback.
+     * 
+     * @param listener The callback that should be unregistered.
+     * @see #registerOnSharedPreferenceChangeListener
+     */
+    void unregisterOnSharedPreferenceChangeListener(OnSharedPreferenceChangeListener listener);
+}
diff --git a/core/java/android/content/SyncAdapter.java b/core/java/android/content/SyncAdapter.java
new file mode 100644
index 0000000..7826e50
--- /dev/null
+++ b/core/java/android/content/SyncAdapter.java
@@ -0,0 +1,70 @@
+/*
+ * 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 android.content;
+
+import android.os.Bundle;
+import android.os.RemoteException;
+
+/**
+ * @hide
+ */
+public abstract class SyncAdapter {
+    private static final String TAG = "SyncAdapter";
+
+    /** Kernel event log tag.  Also listed in data/etc/event-log-tags. */
+    public static final int LOG_SYNC_DETAILS = 2743;
+
+    class Transport extends ISyncAdapter.Stub {
+        public void startSync(ISyncContext syncContext, String account,
+                Bundle extras) throws RemoteException {
+            SyncAdapter.this.startSync(new SyncContext(syncContext), account, extras);
+        }
+
+        public void cancelSync() throws RemoteException {
+            SyncAdapter.this.cancelSync();
+        }
+    }
+
+    Transport mTransport = new Transport();
+
+    /**
+     * Get the Transport object.  (note this is package private).
+     */
+    final ISyncAdapter getISyncAdapter()
+    {
+        return mTransport;
+    }
+
+    /**
+     * Initiate a sync for this account. SyncAdapter-specific parameters may
+     * be specified in extras, which is guaranteed to not be null. IPC invocations
+     * of this method and cancelSync() are guaranteed to be serialized.
+     *
+     * @param syncContext the ISyncContext used to indicate the progress of the sync. When
+     *   the sync is finished (successfully or not) ISyncContext.onFinished() must be called.
+     * @param account the account that should be synced
+     * @param extras SyncAdapter-specific parameters
+     */
+    public abstract void startSync(SyncContext syncContext, String account, Bundle extras);
+
+    /**
+     * Cancel the most recently initiated sync. Due to race conditions, this may arrive
+     * after the ISyncContext.onFinished() for that sync was called. IPC invocations
+     * of this method and startSync() are guaranteed to be serialized.
+     */
+    public abstract void cancelSync();
+}
diff --git a/core/java/android/content/SyncContext.java b/core/java/android/content/SyncContext.java
new file mode 100644
index 0000000..f4faa04
--- /dev/null
+++ b/core/java/android/content/SyncContext.java
@@ -0,0 +1,73 @@
+/*
+ * 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 android.content;
+
+import android.os.RemoteException;
+import android.os.SystemClock;
+
+/**
+ * @hide
+ */
+public class SyncContext {
+    private ISyncContext mSyncContext;
+    private long mLastHeartbeatSendTime;
+
+    private static final long HEARTBEAT_SEND_INTERVAL_IN_MS = 1000;
+
+    public SyncContext(ISyncContext syncContextInterface) {
+        mSyncContext = syncContextInterface;
+        mLastHeartbeatSendTime = 0;
+    }
+
+    /**
+     * Call to update the status text for this sync. This internally invokes
+     * {@link #updateHeartbeat}, so it also takes the place of a call to that.
+     *
+     * @param message the current status message for this sync
+     */
+    public void setStatusText(String message) {
+        updateHeartbeat();
+    }
+
+    /**
+     * Call to indicate that the SyncAdapter is making progress. E.g., if this SyncAdapter
+     * downloads or sends records to/from the server, this may be called after each record
+     * is downloaded or uploaded.
+     */
+    public void updateHeartbeat() {
+        final long now = SystemClock.elapsedRealtime();
+        if (now < mLastHeartbeatSendTime + HEARTBEAT_SEND_INTERVAL_IN_MS) return;
+        try {
+            mLastHeartbeatSendTime = now;
+            mSyncContext.sendHeartbeat();
+        } catch (RemoteException e) {
+            // this should never happen
+        }
+    }
+
+    public void onFinished(SyncResult result) {
+        try {
+            mSyncContext.onFinished(result);
+        } catch (RemoteException e) {
+            // this should never happen
+        }
+    }
+
+    public ISyncContext getISyncContext() {
+        return mSyncContext;
+    }
+}
diff --git a/core/java/android/content/SyncManager.java b/core/java/android/content/SyncManager.java
new file mode 100644
index 0000000..96470c3
--- /dev/null
+++ b/core/java/android/content/SyncManager.java
@@ -0,0 +1,2175 @@
+/*
+ * 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 android.content;
+
+import com.google.android.collect.Maps;
+
+import com.android.internal.R;
+import com.android.internal.util.ArrayUtils;
+
+import android.accounts.AccountMonitor;
+import android.accounts.AccountMonitorListener;
+import android.app.AlarmManager;
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.IPackageManager;
+import android.content.pm.PackageManager;
+import android.content.pm.ProviderInfo;
+import android.content.pm.ResolveInfo;
+import android.database.Cursor;
+import android.database.DatabaseUtils;
+import android.net.ConnectivityManager;
+import android.net.NetworkInfo;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.os.Parcel;
+import android.os.PowerManager;
+import android.os.Process;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.SystemClock;
+import android.os.SystemProperties;
+import android.preference.Preference;
+import android.preference.PreferenceGroup;
+import android.provider.Sync;
+import android.provider.Settings;
+import android.provider.Sync.History;
+import android.text.TextUtils;
+import android.text.format.DateUtils;
+import android.text.format.Time;
+import android.util.Config;
+import android.util.EventLog;
+import android.util.Log;
+
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.File;
+import java.io.FileDescriptor;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.PriorityQueue;
+import java.util.Random;
+import java.util.Observer;
+import java.util.Observable;
+
+/**
+ * @hide
+ */
+class SyncManager {
+    private static final String TAG = "SyncManager";
+
+    // used during dumping of the Sync history
+    private static final long MILLIS_IN_HOUR = 1000 * 60 * 60;
+    private static final long MILLIS_IN_DAY = MILLIS_IN_HOUR * 24;
+    private static final long MILLIS_IN_WEEK = MILLIS_IN_DAY * 7;
+    private static final long MILLIS_IN_4WEEKS = MILLIS_IN_WEEK * 4;
+
+    /** Delay a sync due to local changes this long. In milliseconds */
+    private static final long LOCAL_SYNC_DELAY = 30 * 1000; // 30 seconds
+
+    /**
+     * If a sync takes longer than this and the sync queue is not empty then we will
+     * cancel it and add it back to the end of the sync queue. In milliseconds.
+     */
+    private static final long MAX_TIME_PER_SYNC = 5 * 60 * 1000; // 5 minutes
+
+    private static final long SYNC_NOTIFICATION_DELAY = 30 * 1000; // 30 seconds
+
+    /**
+     * When retrying a sync for the first time use this delay. After that
+     * the retry time will double until it reached MAX_SYNC_RETRY_TIME.
+     * In milliseconds.
+     */
+    private static final long INITIAL_SYNC_RETRY_TIME_IN_MS = 30 * 1000; // 30 seconds
+
+    /**
+     * Default the max sync retry time to this value.
+     */
+    private static final long DEFAULT_MAX_SYNC_RETRY_TIME_IN_SECONDS = 60 * 60; // one hour
+
+    /**
+     * An error notification is sent if sync of any of the providers has been failing for this long.
+     */
+    private static final long ERROR_NOTIFICATION_DELAY_MS = 1000 * 60 * 10; // 10 minutes
+
+    private static final String SYNC_WAKE_LOCK = "SyncManagerSyncWakeLock";
+    private static final String HANDLE_SYNC_ALARM_WAKE_LOCK = "SyncManagerHandleSyncAlarmWakeLock";
+
+    private Context mContext;
+    private ContentResolver mContentResolver;
+
+    private String mStatusText = "";
+    private long mHeartbeatTime = 0;
+
+    private AccountMonitor mAccountMonitor;
+
+    private volatile String[] mAccounts = null;
+
+    volatile private PowerManager.WakeLock mSyncWakeLock;
+    volatile private PowerManager.WakeLock mHandleAlarmWakeLock;
+    volatile private boolean mDataConnectionIsConnected = false;
+    volatile private boolean mStorageIsLow = false;
+    private Sync.Settings.QueryMap mSyncSettings;
+
+    private final NotificationManager mNotificationMgr;
+    private AlarmManager mAlarmService = null;
+    private HandlerThread mSyncThread;
+
+    private volatile IPackageManager mPackageManager;
+
+    private final SyncStorageEngine mSyncStorageEngine;
+    private final SyncQueue mSyncQueue;
+
+    private ActiveSyncContext mActiveSyncContext = null;
+
+    // set if the sync error indicator should be reported.
+    private boolean mNeedSyncErrorNotification = false;
+    // set if the sync active indicator should be reported
+    private boolean mNeedSyncActiveNotification = false;
+
+    private volatile boolean mSyncPollInitialized;
+    private final PendingIntent mSyncAlarmIntent;
+    private final PendingIntent mSyncPollAlarmIntent;
+
+    private BroadcastReceiver mStorageIntentReceiver =
+            new BroadcastReceiver() {
+                public void onReceive(Context context, Intent intent) {
+                    ensureContentResolver();
+                    String action = intent.getAction();
+                    if (Intent.ACTION_DEVICE_STORAGE_LOW.equals(action)) {
+                        if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                            Log.v(TAG, "Internal storage is low.");
+                        }
+                        mStorageIsLow = true;
+                        cancelActiveSync(null /* no url */);
+                    } else if (Intent.ACTION_DEVICE_STORAGE_OK.equals(action)) {
+                        if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                            Log.v(TAG, "Internal storage is ok.");
+                        }
+                        mStorageIsLow = false;
+                        sendCheckAlarmsMessage();
+                    }
+                }
+            };
+
+    private BroadcastReceiver mConnectivityIntentReceiver =
+            new BroadcastReceiver() {
+        public void onReceive(Context context, Intent intent) {
+            NetworkInfo networkInfo =
+                    intent.getParcelableExtra(ConnectivityManager.EXTRA_NETWORK_INFO);
+            NetworkInfo.State state = (networkInfo == null ? NetworkInfo.State.UNKNOWN :
+                    networkInfo.getState());
+            if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                Log.v(TAG, "received connectivity action.  network info: " + networkInfo);
+            }
+
+            // only pay attention to the CONNECTED and DISCONNECTED states.
+            // if connected, we are connected.
+            // if disconnected, we may not be connected.  in some cases, we may be connected on
+            // a different network.
+            // e.g., if switching from GPRS to WiFi, we may receive the CONNECTED to WiFi and
+            // DISCONNECTED for GPRS in any order.  if we receive the CONNECTED first, and then
+            // a DISCONNECTED, we want to make sure we set mDataConnectionIsConnected to true
+            // since we still have a WiFi connection.
+            switch (state) {
+                case CONNECTED:
+                    mDataConnectionIsConnected = true;
+                    break;
+                case DISCONNECTED:
+                    if (intent.getBooleanExtra(ConnectivityManager.EXTRA_NO_CONNECTIVITY, false)) {
+                        mDataConnectionIsConnected = false;
+                    } else {
+                        mDataConnectionIsConnected = true;
+                    }
+                    break;
+                default:
+                    // ignore the rest of the states -- leave our boolean alone.
+            }
+            if (mDataConnectionIsConnected) {
+                initializeSyncPoll();
+                sendCheckAlarmsMessage();
+            }
+        }
+    };
+
+    private static final String ACTION_SYNC_ALARM = "android.content.syncmanager.SYNC_ALARM";
+    private static final String SYNC_POLL_ALARM = "android.content.syncmanager.SYNC_POLL_ALARM";
+    private final SyncHandler mSyncHandler;
+
+    private static final String[] SYNC_ACTIVE_PROJECTION = new String[]{
+            Sync.Active.ACCOUNT,
+            Sync.Active.AUTHORITY,
+            Sync.Active.START_TIME,
+    };
+
+    private static final String[] SYNC_PENDING_PROJECTION = new String[]{
+            Sync.Pending.ACCOUNT,
+            Sync.Pending.AUTHORITY
+    };
+
+    private static final int MAX_SYNC_POLL_DELAY_SECONDS = 36 * 60 * 60; // 36 hours
+    private static final int MIN_SYNC_POLL_DELAY_SECONDS = 24 * 60 * 60; // 24 hours
+
+    private static final String SYNCMANAGER_PREFS_FILENAME = "/data/system/syncmanager.prefs";
+
+    public SyncManager(Context context, boolean factoryTest) {
+        // Initialize the SyncStorageEngine first, before registering observers
+        // and creating threads and so on; it may fail if the disk is full.
+        SyncStorageEngine.init(context);
+        mSyncStorageEngine = SyncStorageEngine.getSingleton();
+        mSyncQueue = new SyncQueue(mSyncStorageEngine);
+
+        mContext = context;
+
+        mSyncThread = new HandlerThread("SyncHandlerThread", Process.THREAD_PRIORITY_BACKGROUND);
+        mSyncThread.start();
+        mSyncHandler = new SyncHandler(mSyncThread.getLooper());
+
+        mPackageManager = null;
+
+        mSyncAlarmIntent = PendingIntent.getBroadcast(
+                mContext, 0 /* ignored */, new Intent(ACTION_SYNC_ALARM), 0);
+
+        mSyncPollAlarmIntent = PendingIntent.getBroadcast(
+                mContext, 0 /* ignored */, new Intent(SYNC_POLL_ALARM), 0);
+
+        IntentFilter intentFilter = new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION);
+        context.registerReceiver(mConnectivityIntentReceiver, intentFilter);
+
+        intentFilter = new IntentFilter(Intent.ACTION_DEVICE_STORAGE_LOW);
+        intentFilter.addAction(Intent.ACTION_DEVICE_STORAGE_OK);
+        context.registerReceiver(mStorageIntentReceiver, intentFilter);
+
+        if (!factoryTest) {
+            mNotificationMgr = (NotificationManager)
+                context.getSystemService(Context.NOTIFICATION_SERVICE);
+            context.registerReceiver(new SyncAlarmIntentReceiver(),
+                    new IntentFilter(ACTION_SYNC_ALARM));
+        } else {
+            mNotificationMgr = null;
+        }
+        PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
+        mSyncWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, SYNC_WAKE_LOCK);
+        mSyncWakeLock.setReferenceCounted(false);
+
+        // This WakeLock is used to ensure that we stay awake between the time that we receive
+        // a sync alarm notification and when we finish processing it. We need to do this
+        // because we don't do the work in the alarm handler, rather we do it in a message
+        // handler.
+        mHandleAlarmWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
+                HANDLE_SYNC_ALARM_WAKE_LOCK);
+        mHandleAlarmWakeLock.setReferenceCounted(false);
+
+        if (!factoryTest) {
+            AccountMonitorListener listener = new AccountMonitorListener() {
+                public void onAccountsUpdated(String[] accounts) {
+                    final boolean hadAccountsAlready = mAccounts != null;
+                    // copy the accounts into a new array and change mAccounts to point to it
+                    String[] newAccounts = new String[accounts.length];
+                    System.arraycopy(accounts, 0, newAccounts, 0, accounts.length);
+                    mAccounts = newAccounts;
+
+                    // if a sync is in progress yet it is no longer in the accounts list, cancel it
+                    ActiveSyncContext activeSyncContext = mActiveSyncContext;
+                    if (activeSyncContext != null) {
+                        if (!ArrayUtils.contains(newAccounts,
+                                activeSyncContext.mSyncOperation.account)) {
+                            Log.d(TAG, "canceling sync since the account has been removed");
+                            sendSyncFinishedOrCanceledMessage(activeSyncContext,
+                                    null /* no result since this is a cancel */);
+                        }
+                    }
+
+                    // we must do this since we don't bother scheduling alarms when
+                    // the accounts are not set yet
+                    sendCheckAlarmsMessage();
+
+                    mSyncStorageEngine.doDatabaseCleanup(accounts);
+
+                    if (hadAccountsAlready && mAccounts.length > 0) {
+                        // request a sync so that if the password was changed we will retry any sync
+                        // that failed when it was wrong
+                        startSync(null /* all providers */, null /* no extras */);
+                    }
+                }
+            };
+            mAccountMonitor = new AccountMonitor(context, listener);
+        }
+    }
+
+    private synchronized void initializeSyncPoll() {
+        if (mSyncPollInitialized) return;
+        mSyncPollInitialized = true;
+
+        mContext.registerReceiver(new SyncPollAlarmReceiver(), new IntentFilter(SYNC_POLL_ALARM));
+
+        // load the next poll time from shared preferences
+        long absoluteAlarmTime = readSyncPollTime();
+
+        if (Log.isLoggable(TAG, Log.VERBOSE)) {
+            Log.v(TAG, "initializeSyncPoll: absoluteAlarmTime is " + absoluteAlarmTime);
+        }
+
+        // Convert absoluteAlarmTime to elapsed realtime. If this time was in the past then
+        // schedule the poll immediately, if it is too far in the future then cap it at
+        // MAX_SYNC_POLL_DELAY_SECONDS.
+        long absoluteNow = System.currentTimeMillis();
+        long relativeNow = SystemClock.elapsedRealtime();
+        long relativeAlarmTime = relativeNow;
+        if (absoluteAlarmTime > absoluteNow) {
+            long delayInMs = absoluteAlarmTime - absoluteNow;
+            final int maxDelayInMs = MAX_SYNC_POLL_DELAY_SECONDS * 1000;
+            if (delayInMs > maxDelayInMs) {
+                delayInMs = MAX_SYNC_POLL_DELAY_SECONDS * 1000;
+            }
+            relativeAlarmTime += delayInMs;
+        }
+
+        // schedule an alarm for the next poll time
+        scheduleSyncPollAlarm(relativeAlarmTime);
+    }
+
+    private void scheduleSyncPollAlarm(long relativeAlarmTime) {
+        if (Log.isLoggable(TAG, Log.VERBOSE)) {
+            Log.v(TAG, "scheduleSyncPollAlarm: relativeAlarmTime is " + relativeAlarmTime
+                    + ", now is " + SystemClock.elapsedRealtime()
+                    + ", delay is " + (relativeAlarmTime - SystemClock.elapsedRealtime()));
+        }
+        ensureAlarmService();
+        mAlarmService.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, relativeAlarmTime,
+                mSyncPollAlarmIntent);
+    }
+
+    /**
+     * Return a random value v that satisfies minValue <= v < maxValue. The difference between
+     * maxValue and minValue must be less than Integer.MAX_VALUE.
+     */
+    private long jitterize(long minValue, long maxValue) {
+        Random random = new Random(SystemClock.elapsedRealtime());
+        long spread = maxValue - minValue;
+        if (spread > Integer.MAX_VALUE) {
+            throw new IllegalArgumentException("the difference between the maxValue and the "
+                    + "minValue must be less than " + Integer.MAX_VALUE);
+        }
+        return minValue + random.nextInt((int)spread);
+    }
+
+    private void handleSyncPollAlarm() {
+        // determine the next poll time
+        long delayMs = jitterize(MIN_SYNC_POLL_DELAY_SECONDS, MAX_SYNC_POLL_DELAY_SECONDS) * 1000;
+        long nextRelativePollTimeMs = SystemClock.elapsedRealtime() + delayMs;
+
+        if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "handleSyncPollAlarm: delay " + delayMs);
+
+        // write the absolute time to shared preferences
+        writeSyncPollTime(System.currentTimeMillis() + delayMs);
+
+        // schedule an alarm for the next poll time
+        scheduleSyncPollAlarm(nextRelativePollTimeMs);
+
+        // perform a poll
+        scheduleSync(null /* sync all syncable providers */, new Bundle(), 0 /* no delay */);
+    }
+
+    private void writeSyncPollTime(long when) {
+        File f = new File(SYNCMANAGER_PREFS_FILENAME);
+        DataOutputStream str = null;
+        try {
+            str = new DataOutputStream(new FileOutputStream(f));
+            str.writeLong(when);
+        } catch (FileNotFoundException e) {
+            Log.w(TAG, "error writing to file " + f, e);
+        } catch (IOException e) {
+            Log.w(TAG, "error writing to file " + f, e);
+        } finally {
+            if (str != null) {
+                try {
+                    str.close();
+                } catch (IOException e) {
+                    Log.w(TAG, "error closing file " + f, e);
+                }
+            }
+        }
+    }
+
+    private long readSyncPollTime() {
+        File f = new File(SYNCMANAGER_PREFS_FILENAME);
+
+        DataInputStream str = null;
+        try {
+            str = new DataInputStream(new FileInputStream(f));
+            return str.readLong();
+        } catch (FileNotFoundException e) {
+            writeSyncPollTime(0);
+        } catch (IOException e) {
+            Log.w(TAG, "error reading file " + f, e);
+        } finally {
+            if (str != null) {
+                try {
+                    str.close();
+                } catch (IOException e) {
+                    Log.w(TAG, "error closing file " + f, e);
+                }
+            }
+        }
+        return 0;
+    }
+
+    public ActiveSyncContext getActiveSyncContext() {
+        return mActiveSyncContext;
+    }
+
+    private Sync.Settings.QueryMap getSyncSettings() {
+        if (mSyncSettings == null) {
+            mSyncSettings = new Sync.Settings.QueryMap(mContext.getContentResolver(), true,
+                    new Handler());
+            mSyncSettings.addObserver(new Observer(){
+                public void update(Observable o, Object arg) {
+                    // force the sync loop to run if the settings change
+                    sendCheckAlarmsMessage();
+                }
+            });
+        }
+        return mSyncSettings;
+    }
+
+    private void ensureContentResolver() {
+        if (mContentResolver == null) {
+            mContentResolver = mContext.getContentResolver();
+        }
+    }
+
+    private void ensureAlarmService() {
+        if (mAlarmService == null) {
+            mAlarmService = (AlarmManager)mContext.getSystemService(Context.ALARM_SERVICE);
+        }
+    }
+
+    public String getSyncingAccount() {
+        ActiveSyncContext activeSyncContext = mActiveSyncContext;
+        return (activeSyncContext != null) ? activeSyncContext.mSyncOperation.account : null;
+    }
+
+    /**
+     * Returns whether or not sync is enabled.  Sync can be enabled by
+     * setting the system property "ro.config.sync" to the value "yes".
+     * This is normally done at boot time on builds that support sync.
+     * @return true if sync is enabled
+     */
+    private boolean isSyncEnabled() {
+        // Require the precise value "yes" to discourage accidental activation.
+        return "yes".equals(SystemProperties.get("ro.config.sync"));
+    }
+
+    /**
+     * Initiate a sync. This can start a sync for all providers
+     * (pass null to url, set onlyTicklable to false), only those
+     * providers that are marked as ticklable (pass null to url,
+     * set onlyTicklable to true), or a specific provider (set url
+     * to the content url of the provider).
+     *
+     * <p>If the ContentResolver.SYNC_EXTRAS_UPLOAD boolean in extras is
+     * true then initiate a sync that just checks for local changes to send
+     * to the server, otherwise initiate a sync that first gets any
+     * changes from the server before sending local changes back to
+     * the server.
+     *
+     * <p>If a specific provider is being synced (the url is non-null)
+     * then the extras can contain SyncAdapter-specific information
+     * to control what gets synced (e.g. which specific feed to sync).
+     *
+     * <p>You'll start getting callbacks after this.
+     *
+     * @param url The Uri of a specific provider to be synced, or
+     *          null to sync all providers.
+     * @param extras a Map of SyncAdapter-specific information to control
+*          syncs of a specific provider. Can be null. Is ignored
+*          if the url is null.
+     * @param delay how many milliseconds in the future to wait before performing this
+     *   sync. -1 means to make this the next sync to perform.
+     */
+    public void scheduleSync(Uri url, Bundle extras, long delay) {
+        boolean isLoggable = Log.isLoggable(TAG, Log.VERBOSE);
+        if (isLoggable) {
+            Log.v(TAG, "scheduleSync:"
+                    + " delay " + delay
+                    + ", url " + ((url == null) ? "(null)" : url)
+                    + ", extras " + ((extras == null) ? "(null)" : extras));
+        }
+
+        if (!isSyncEnabled()) {
+            if (isLoggable) {
+                Log.v(TAG, "not syncing because sync is disabled");
+            }
+            setStatusText("Sync is disabled.");
+            return;
+        }
+
+        if (mAccounts == null) setStatusText("The accounts aren't known yet.");
+        if (!mDataConnectionIsConnected) setStatusText("No data connection");
+        if (mStorageIsLow) setStatusText("Memory low");
+
+        if (extras == null) extras = new Bundle();
+
+        Boolean expedited = extras.getBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, false);
+        if (expedited) {
+            delay = -1; // this means schedule at the front of the queue
+        }
+
+        String[] accounts;
+        String accountFromExtras = extras.getString(ContentResolver.SYNC_EXTRAS_ACCOUNT);
+        if (!TextUtils.isEmpty(accountFromExtras)) {
+            accounts = new String[]{accountFromExtras};
+        } else {
+            // if the accounts aren't configured yet then we can't support an account-less
+            // sync request
+            accounts = mAccounts;
+            if (accounts == null) {
+                // not ready yet
+                if (isLoggable) {
+                    Log.v(TAG, "scheduleSync: no accounts yet, dropping");
+                }
+                return;
+            }
+            if (accounts.length == 0) {
+                if (isLoggable) {
+                    Log.v(TAG, "scheduleSync: no accounts configured, dropping");
+                }
+                setStatusText("No accounts are configured.");
+                return;
+            }
+        }
+
+        final boolean uploadOnly = extras.getBoolean(ContentResolver.SYNC_EXTRAS_UPLOAD, false);
+        final boolean force = extras.getBoolean(ContentResolver.SYNC_EXTRAS_FORCE, false);
+
+        int source;
+        if (uploadOnly) {
+            source = Sync.History.SOURCE_LOCAL;
+        } else if (force) {
+            source = Sync.History.SOURCE_USER;
+        } else if (url == null) {
+            source = Sync.History.SOURCE_POLL;
+        } else {
+            // this isn't strictly server, since arbitrary callers can (and do) request
+            // a non-forced two-way sync on a specific url
+            source = Sync.History.SOURCE_SERVER;
+        }
+
+        List<String> names = new ArrayList<String>();
+        List<ProviderInfo> providers = new ArrayList<ProviderInfo>();
+        populateProvidersList(url, names, providers);
+
+        final int numProviders = providers.size();
+        for (int i = 0; i < numProviders; i++) {
+            if (!providers.get(i).isSyncable) continue;
+            final String name = names.get(i);
+            for (String account : accounts) {
+                scheduleSyncOperation(new SyncOperation(account, source, name, extras, delay));
+                // TODO: remove this when Calendar supports multiple accounts. Until then
+                // pretend that only the first account exists when syncing calendar.
+                if ("calendar".equals(name)) {
+                    break;
+                }
+            }
+        }
+    }
+
+    private void setStatusText(String message) {
+        mStatusText = message;
+    }
+
+    private void populateProvidersList(Uri url, List<String> names, List<ProviderInfo> providers) {
+        try {
+            final IPackageManager packageManager = getPackageManager();
+            if (url == null) {
+                packageManager.querySyncProviders(names, providers);
+            } else {
+                final String authority = url.getAuthority();
+                ProviderInfo info = packageManager.resolveContentProvider(url.getAuthority(), 0);
+                if (info != null) {
+                    // only set this provider if the requested authority is the primary authority
+                    String[] providerNames = info.authority.split(";");
+                    if (url.getAuthority().equals(providerNames[0])) {
+                        names.add(authority);
+                        providers.add(info);
+                    }
+                }
+            }
+        } catch (RemoteException ex) {
+            // we should really never get this, but if we do then clear the lists, which
+            // will result in the dropping of the sync request
+            Log.e(TAG, "error trying to get the ProviderInfo for " + url, ex);
+            names.clear();
+            providers.clear();
+        }
+    }
+
+    public void scheduleLocalSync(Uri url) {
+        final Bundle extras = new Bundle();
+        extras.putBoolean(ContentResolver.SYNC_EXTRAS_UPLOAD, true);
+        scheduleSync(url, extras, LOCAL_SYNC_DELAY);
+    }
+
+    private IPackageManager getPackageManager() {
+        // Don't bother synchronizing on this. The worst that can happen is that two threads
+        // can try to get the package manager at the same time but only one result gets
+        // used. Since there is only one package manager in the system this doesn't matter.
+        if (mPackageManager == null) {
+            IBinder b = ServiceManager.getService("package");
+            mPackageManager = IPackageManager.Stub.asInterface(b);
+        }
+        return mPackageManager;
+    }
+
+    /**
+     * Initiate a sync for this given URL, or pass null for a full sync.
+     *
+     * <p>You'll start getting callbacks after this.
+     *
+     * @param url The Uri of a specific provider to be synced, or
+     *          null to sync all providers.
+     * @param extras a Map of SyncAdapter specific information to control
+     *          syncs of a specific provider. Can be null. Is ignored
+     */
+    public void startSync(Uri url, Bundle extras) {
+        scheduleSync(url, extras, 0 /* no delay */);
+    }
+
+    public void updateHeartbeatTime() {
+        mHeartbeatTime = SystemClock.elapsedRealtime();
+        ensureContentResolver();
+        mContentResolver.notifyChange(Sync.Active.CONTENT_URI,
+                null /* this change wasn't made through an observer */);
+    }
+
+    private void sendSyncAlarmMessage() {
+        if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "sending MESSAGE_SYNC_ALARM");
+        mSyncHandler.sendEmptyMessage(SyncHandler.MESSAGE_SYNC_ALARM);
+    }
+
+    private void sendCheckAlarmsMessage() {
+        if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "sending MESSAGE_CHECK_ALARMS");
+        mSyncHandler.sendEmptyMessage(SyncHandler.MESSAGE_CHECK_ALARMS);
+    }
+
+    private void sendSyncFinishedOrCanceledMessage(ActiveSyncContext syncContext,
+            SyncResult syncResult) {
+        if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "sending MESSAGE_SYNC_FINISHED");
+        Message msg = mSyncHandler.obtainMessage();
+        msg.what = SyncHandler.MESSAGE_SYNC_FINISHED;
+        msg.obj = new SyncHandlerMessagePayload(syncContext, syncResult);
+        mSyncHandler.sendMessage(msg);
+    }
+
+    class SyncHandlerMessagePayload {
+        public final ActiveSyncContext activeSyncContext;
+        public final SyncResult syncResult;
+
+        SyncHandlerMessagePayload(ActiveSyncContext syncContext, SyncResult syncResult) {
+            this.activeSyncContext = syncContext;
+            this.syncResult = syncResult;
+        }
+    }
+
+    class SyncAlarmIntentReceiver extends BroadcastReceiver {
+        public void onReceive(Context context, Intent intent) {
+            mHandleAlarmWakeLock.acquire();
+            sendSyncAlarmMessage();
+        }
+    }
+
+    class SyncPollAlarmReceiver extends BroadcastReceiver {
+        public void onReceive(Context context, Intent intent) {
+            handleSyncPollAlarm();
+        }
+    }
+
+    private void rescheduleImmediately(SyncOperation syncOperation) {
+        SyncOperation rescheduledSyncOperation = new SyncOperation(syncOperation);
+        rescheduledSyncOperation.setDelay(0);
+        scheduleSyncOperation(rescheduledSyncOperation);
+    }
+
+    private long rescheduleWithDelay(SyncOperation syncOperation) {
+        long newDelayInMs;
+
+        if (syncOperation.delay == 0) {
+            // The initial delay is the jitterized INITIAL_SYNC_RETRY_TIME_IN_MS
+            newDelayInMs = jitterize(INITIAL_SYNC_RETRY_TIME_IN_MS,
+                    (long)(INITIAL_SYNC_RETRY_TIME_IN_MS * 1.1));
+        } else {
+            // Subsequent delays are the double of the previous delay
+            newDelayInMs = syncOperation.delay * 2;
+        }
+
+        // Cap the delay
+        ensureContentResolver();
+        long maxSyncRetryTimeInSeconds = Settings.Gservices.getLong(mContentResolver,
+                Settings.Gservices.SYNC_MAX_RETRY_DELAY_IN_SECONDS,
+                DEFAULT_MAX_SYNC_RETRY_TIME_IN_SECONDS);
+        if (newDelayInMs > maxSyncRetryTimeInSeconds * 1000) {
+            newDelayInMs = maxSyncRetryTimeInSeconds * 1000;
+        }
+
+        SyncOperation rescheduledSyncOperation = new SyncOperation(syncOperation);
+        rescheduledSyncOperation.setDelay(newDelayInMs);
+        scheduleSyncOperation(rescheduledSyncOperation);
+        return newDelayInMs;
+    }
+
+    /**
+     * Cancel the active sync if it matches the uri. The uri corresponds to the one passed
+     * in to startSync().
+     * @param uri If non-null, the active sync is only canceled if it matches the uri.
+     *   If null, any active sync is canceled.
+     */
+    public void cancelActiveSync(Uri uri) {
+        ActiveSyncContext activeSyncContext = mActiveSyncContext;
+        if (activeSyncContext != null) {
+            // if a Uri was specified then only cancel the sync if it matches the the uri
+            if (uri != null) {
+                if (!uri.getAuthority().equals(activeSyncContext.mSyncOperation.authority)) {
+                    return;
+                }
+            }
+            sendSyncFinishedOrCanceledMessage(activeSyncContext,
+                    null /* no result since this is a cancel */);
+        }
+    }
+
+    /**
+     * Create and schedule a SyncOperation.
+     *
+     * @param syncOperation the SyncOperation to schedule
+     */
+    public void scheduleSyncOperation(SyncOperation syncOperation) {
+        // If this operation is expedited and there is a sync in progress then
+        // reschedule the current operation and send a cancel for it.
+        final boolean expedited = syncOperation.delay < 0;
+        final ActiveSyncContext activeSyncContext = mActiveSyncContext;
+        if (expedited && activeSyncContext != null) {
+            final boolean activeIsExpedited = activeSyncContext.mSyncOperation.delay < 0;
+            final boolean hasSameKey =
+                    activeSyncContext.mSyncOperation.key.equals(syncOperation.key);
+            // This request is expedited and there is a sync in progress.
+            // Interrupt the current sync only if it is not expedited and if it has a different
+            // key than the one we are scheduling.
+            if (!activeIsExpedited && !hasSameKey) {
+                rescheduleImmediately(activeSyncContext.mSyncOperation);
+                sendSyncFinishedOrCanceledMessage(activeSyncContext,
+                        null /* no result since this is a cancel */);
+            }
+        }
+
+        boolean operationEnqueued;
+        synchronized (mSyncQueue) {
+            operationEnqueued = mSyncQueue.add(syncOperation);
+        }
+
+        if (operationEnqueued) {
+            if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                Log.v(TAG, "scheduleSyncOperation: enqueued " + syncOperation);
+            }
+            sendCheckAlarmsMessage();
+        } else {
+            if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                Log.v(TAG, "scheduleSyncOperation: dropping duplicate sync operation "
+                        + syncOperation);
+            }
+        }
+    }
+
+    /**
+     * Remove any scheduled sync operations that match uri. The uri corresponds to the one passed
+     * in to startSync().
+     * @param uri If non-null, only operations that match the uri are cleared.
+     *   If null, all operations are cleared.
+     */
+    public void clearScheduledSyncOperations(Uri uri) {
+        synchronized (mSyncQueue) {
+            mSyncQueue.clear(null, uri != null ? uri.getAuthority() : null);
+        }
+    }
+
+    void maybeRescheduleSync(SyncResult syncResult, SyncOperation previousSyncOperation) {
+        boolean isLoggable = Log.isLoggable(TAG, Log.DEBUG);
+        if (isLoggable) {
+            Log.d(TAG, "encountered error(s) during the sync: " + syncResult + ", "
+                    + previousSyncOperation);
+        }
+
+        // If the operation succeeded to some extent then retry immediately.
+        // If this was a two-way sync then retry soft errors with an exponential backoff.
+        // If this was an upward sync then schedule a two-way sync immediately.
+        // Otherwise do not reschedule.
+
+        if (syncResult.madeSomeProgress()) {
+            if (isLoggable) {
+                Log.d(TAG, "retrying sync operation immediately because "
+                        + "even though it had an error it achieved some success");
+            }
+            rescheduleImmediately(previousSyncOperation);
+        } else if (previousSyncOperation.extras.getBoolean(
+                ContentResolver.SYNC_EXTRAS_UPLOAD, false)) {
+            final SyncOperation newSyncOperation = new SyncOperation(previousSyncOperation);
+            newSyncOperation.extras.putBoolean(ContentResolver.SYNC_EXTRAS_UPLOAD, false);
+            newSyncOperation.setDelay(0);
+            if (Config.LOGD) {
+                Log.d(TAG, "retrying sync operation as a two-way sync because an upload-only sync "
+                        + "encountered an error: " + previousSyncOperation);
+            }
+            scheduleSyncOperation(newSyncOperation);
+        } else if (syncResult.hasSoftError()) {
+            long delay = rescheduleWithDelay(previousSyncOperation);
+            if (delay >= 0) {
+                if (isLoggable) {
+                    Log.d(TAG, "retrying sync operation in " + delay + " ms because "
+                            + "it encountered a soft error: " + previousSyncOperation);
+                }
+            }
+        } else {
+            if (Config.LOGD) {
+                Log.d(TAG, "not retrying sync operation because the error is a hard error: "
+                        + previousSyncOperation);
+            }
+        }
+    }
+
+    /**
+     * Value type that represents a sync operation.
+     */
+    static class SyncOperation implements Comparable {
+        final String account;
+        int syncSource;
+        String authority;
+        Bundle extras;
+        final String key;
+        long earliestRunTime;
+        long delay;
+        Long rowId = null;
+
+        SyncOperation(String account, int source, String authority, Bundle extras, long delay) {
+            this.account = account;
+            this.syncSource = source;
+            this.authority = authority;
+            this.extras = new Bundle(extras);
+            this.setDelay(delay);
+            this.key = toKey();
+        }
+
+        SyncOperation(SyncOperation other) {
+            this.account = other.account;
+            this.syncSource = other.syncSource;
+            this.authority = other.authority;
+            this.extras = new Bundle(other.extras);
+            this.delay = other.delay;
+            this.earliestRunTime = other.earliestRunTime;
+            this.key = toKey();
+        }
+
+        public void setDelay(long delay) {
+            this.delay = delay;
+            if (delay >= 0) {
+                this.earliestRunTime = SystemClock.elapsedRealtime() + delay;
+            } else {
+                this.earliestRunTime = 0;
+            }
+        }
+
+        public String toString() {
+            StringBuilder sb = new StringBuilder();
+            sb.append("authority: ").append(authority);
+            sb.append(" account: ").append(account);
+            sb.append(" extras: ");
+            extrasToStringBuilder(extras, sb);
+            sb.append(" syncSource: ").append(syncSource);
+            sb.append(" when: ").append(earliestRunTime);
+            sb.append(" delay: ").append(delay);
+            sb.append(" key: {").append(key).append("}");
+            if (rowId != null) sb.append(" rowId: ").append(rowId);
+            return sb.toString();
+        }
+
+        private String toKey() {
+            StringBuilder sb = new StringBuilder();
+            sb.append("authority: ").append(authority);
+            sb.append(" account: ").append(account);
+            sb.append(" extras: ");
+            extrasToStringBuilder(extras, sb);
+            return sb.toString();
+        }
+
+        private static void extrasToStringBuilder(Bundle bundle, StringBuilder sb) {
+            sb.append("[");
+            for (String key : bundle.keySet()) {
+                sb.append(key).append("=").append(bundle.get(key)).append(" ");
+            }
+            sb.append("]");
+        }
+
+        public int compareTo(Object o) {
+            SyncOperation other = (SyncOperation)o;
+            if (earliestRunTime == other.earliestRunTime) {
+                return 0;
+            }
+            return (earliestRunTime < other.earliestRunTime) ? -1 : 1;
+        }
+    }
+
+    /**
+     * @hide
+     */
+    class ActiveSyncContext extends ISyncContext.Stub {
+        final SyncOperation mSyncOperation;
+        final long mHistoryRowId;
+        final IContentProvider mContentProvider;
+        final ISyncAdapter mSyncAdapter;
+        final long mStartTime;
+        long mTimeoutStartTime;
+
+        public ActiveSyncContext(SyncOperation syncOperation, IContentProvider contentProvider,
+                ISyncAdapter syncAdapter, long historyRowId) {
+            super();
+            mSyncOperation = syncOperation;
+            mHistoryRowId = historyRowId;
+            mContentProvider = contentProvider;
+            mSyncAdapter = syncAdapter;
+            mStartTime = SystemClock.elapsedRealtime();
+            mTimeoutStartTime = mStartTime;
+        }
+
+        public void sendHeartbeat() {
+            // ignore this call if it corresponds to an old sync session
+            if (mActiveSyncContext == this) {
+                SyncManager.this.updateHeartbeatTime();
+            }
+        }
+
+        public void onFinished(SyncResult result) {
+            // include "this" in the message so that the handler can ignore it if this
+            // ActiveSyncContext is no longer the mActiveSyncContext at message handling
+            // time
+            sendSyncFinishedOrCanceledMessage(this, result);
+        }
+
+        public void toString(StringBuilder sb) {
+            sb.append("startTime ").append(mStartTime)
+                    .append(", mTimeoutStartTime ").append(mTimeoutStartTime)
+                    .append(", mHistoryRowId ").append(mHistoryRowId)
+                    .append(", syncOperation ").append(mSyncOperation);
+        }
+
+        @Override
+        public String toString() {
+            StringBuilder sb = new StringBuilder();
+            toString(sb);
+            return sb.toString();
+        }
+    }
+
+    protected void dump(FileDescriptor fd, PrintWriter pw) {
+        StringBuilder sb = new StringBuilder();
+        dumpSyncState(sb);
+        sb.append("\n");
+        if (isSyncEnabled()) {
+            dumpSyncHistory(sb);
+        }
+        pw.println(sb.toString());
+    }
+
+    protected void dumpSyncState(StringBuilder sb) {
+        sb.append("sync enabled: ").append(isSyncEnabled()).append("\n");
+        sb.append("data connected: ").append(mDataConnectionIsConnected).append("\n");
+        sb.append("memory low: ").append(mStorageIsLow).append("\n");
+
+        final String[] accounts = mAccounts;
+        sb.append("accounts: ");
+        if (accounts != null) {
+            sb.append(accounts.length);
+        } else {
+            sb.append("none");
+        }
+        sb.append("\n");
+        final long now = SystemClock.elapsedRealtime();
+        sb.append("now: ").append(now).append("\n");
+        sb.append("uptime: ").append(DateUtils.formatElapsedTime(now/1000)).append(" (HH:MM:SS)\n");
+        sb.append("time spent syncing : ")
+                .append(DateUtils.formatElapsedTime(
+                        mSyncHandler.mSyncTimeTracker.timeSpentSyncing() / 1000))
+                .append(" (HH:MM:SS), sync ")
+                .append(mSyncHandler.mSyncTimeTracker.mLastWasSyncing ? "" : "not ")
+                .append("in progress").append("\n");
+        if (mSyncHandler.mAlarmScheduleTime != null) {
+            sb.append("next alarm time: ").append(mSyncHandler.mAlarmScheduleTime)
+                    .append(" (")
+                    .append(DateUtils.formatElapsedTime((mSyncHandler.mAlarmScheduleTime-now)/1000))
+                    .append(" (HH:MM:SS) from now)\n");
+        } else {
+            sb.append("no alarm is scheduled (there had better not be any pending syncs)\n");
+        }
+
+        sb.append("active sync: ").append(mActiveSyncContext).append("\n");
+
+        sb.append("notification info: ");
+        mSyncHandler.mSyncNotificationInfo.toString(sb);
+        sb.append("\n");
+
+        synchronized (mSyncQueue) {
+            sb.append("sync queue: ");
+            mSyncQueue.dump(sb);
+        }
+
+        Cursor c = mSyncStorageEngine.query(Sync.Active.CONTENT_URI,
+                SYNC_ACTIVE_PROJECTION, null, null, null);
+        sb.append("\n");
+        try {
+            if (c.moveToNext()) {
+                final long durationInSeconds = (now - c.getLong(2)) / 1000;
+                sb.append("Active sync: ").append(c.getString(0))
+                        .append(" ").append(c.getString(1))
+                        .append(", duration is ")
+                        .append(DateUtils.formatElapsedTime(durationInSeconds)).append(".\n");
+            } else {
+                sb.append("No sync is in progress.\n");
+            }
+        } finally {
+            c.close();
+        }
+
+        c = mSyncStorageEngine.query(Sync.Pending.CONTENT_URI,
+                SYNC_PENDING_PROJECTION, null, null, "account, authority");
+        sb.append("\nPending Syncs\n");
+        try {
+            if (c.getCount() != 0) {
+                dumpSyncPendingHeader(sb);
+                while (c.moveToNext()) {
+                    dumpSyncPendingRow(sb, c);
+                }
+                dumpSyncPendingFooter(sb);
+            } else {
+                sb.append("none\n");
+            }
+        } finally {
+            c.close();
+        }
+
+        String currentAccount = null;
+        c = mSyncStorageEngine.query(Sync.Status.CONTENT_URI,
+                STATUS_PROJECTION, null, null, "account, authority");
+        sb.append("\nSync history by account and authority\n");
+        try {
+            while (c.moveToNext()) {
+                if (!TextUtils.equals(currentAccount, c.getString(0))) {
+                    if (currentAccount != null) {
+                        dumpSyncHistoryFooter(sb);
+                    }
+                    currentAccount = c.getString(0);
+                    dumpSyncHistoryHeader(sb, currentAccount);
+                }
+
+                dumpSyncHistoryRow(sb, c);
+            }
+            if (c.getCount() > 0) dumpSyncHistoryFooter(sb);
+        } finally {
+            c.close();
+        }
+    }
+
+    private void dumpSyncHistoryHeader(StringBuilder sb, String account) {
+        sb.append(" Account: ").append(account).append("\n");
+        sb.append("  ___________________________________________________________________________________________________________________________\n");
+        sb.append(" |                 |             num times synced           |   total  |         last success          |                     |\n");
+        sb.append(" | authority       | local |  poll | server |  user | total | duration |  source |               time  |   result if failing |\n");
+    }
+
+    private static String[] STATUS_PROJECTION = new String[]{
+            Sync.Status.ACCOUNT, // 0
+            Sync.Status.AUTHORITY, // 1
+            Sync.Status.NUM_SYNCS, // 2
+            Sync.Status.TOTAL_ELAPSED_TIME, // 3
+            Sync.Status.NUM_SOURCE_LOCAL, // 4
+            Sync.Status.NUM_SOURCE_POLL, // 5
+            Sync.Status.NUM_SOURCE_SERVER, // 6
+            Sync.Status.NUM_SOURCE_USER, // 7
+            Sync.Status.LAST_SUCCESS_SOURCE, // 8
+            Sync.Status.LAST_SUCCESS_TIME, // 9
+            Sync.Status.LAST_FAILURE_SOURCE, // 10
+            Sync.Status.LAST_FAILURE_TIME, // 11
+            Sync.Status.LAST_FAILURE_MESG // 12
+    };
+
+    private void dumpSyncHistoryRow(StringBuilder sb, Cursor c) {
+        boolean hasSuccess = !c.isNull(9);
+        boolean hasFailure = !c.isNull(11);
+        Time timeSuccess = new Time();
+        if (hasSuccess) timeSuccess.set(c.getLong(9));
+        Time timeFailure = new Time();
+        if (hasFailure) timeFailure.set(c.getLong(11));
+        sb.append(String.format(" | %-15s | %5d | %5d | %6d | %5d | %5d | %8s | %7s | %19s | %19s |\n",
+                c.getString(1),
+                c.getLong(4),
+                c.getLong(5),
+                c.getLong(6),
+                c.getLong(7),
+                c.getLong(2),
+                DateUtils.formatElapsedTime(c.getLong(3)/1000),
+                hasSuccess ? Sync.History.SOURCES[c.getInt(8)] : "",
+                hasSuccess ? timeSuccess.format("%Y-%m-%d %H:%M:%S") : "",
+                hasFailure ? History.mesgToString(c.getString(12)) : ""));
+    }
+
+    private void dumpSyncHistoryFooter(StringBuilder sb) {
+        sb.append(" |___________________________________________________________________________________________________________________________|\n");
+    }
+
+    private void dumpSyncPendingHeader(StringBuilder sb) {
+        sb.append(" ____________________________________________________\n");
+        sb.append(" | account                        | authority       |\n");
+    }
+
+    private void dumpSyncPendingRow(StringBuilder sb, Cursor c) {
+        sb.append(String.format(" | %-30s | %-15s |\n", c.getString(0), c.getString(1)));
+    }
+
+    private void dumpSyncPendingFooter(StringBuilder sb) {
+        sb.append(" |__________________________________________________|\n");
+    }
+
+    protected void dumpSyncHistory(StringBuilder sb) {
+        Cursor c = mSyncStorageEngine.query(Sync.History.CONTENT_URI, null, "event=?",
+                new String[]{String.valueOf(Sync.History.EVENT_STOP)},
+                Sync.HistoryColumns.EVENT_TIME + " desc");
+        try {
+            long numSyncsLastHour = 0, durationLastHour = 0;
+            long numSyncsLastDay = 0, durationLastDay = 0;
+            long numSyncsLastWeek = 0, durationLastWeek = 0;
+            long numSyncsLast4Weeks = 0, durationLast4Weeks = 0;
+            long numSyncsTotal = 0, durationTotal = 0;
+
+            long now = System.currentTimeMillis();
+            int indexEventTime = c.getColumnIndexOrThrow(Sync.History.EVENT_TIME);
+            int indexElapsedTime = c.getColumnIndexOrThrow(Sync.History.ELAPSED_TIME);
+            while (c.moveToNext()) {
+                long duration = c.getLong(indexElapsedTime);
+                long endTime = c.getLong(indexEventTime) + duration;
+                long millisSinceStart = now - endTime;
+                numSyncsTotal++;
+                durationTotal += duration;
+                if (millisSinceStart < MILLIS_IN_HOUR) {
+                    numSyncsLastHour++;
+                    durationLastHour += duration;
+                }
+                if (millisSinceStart < MILLIS_IN_DAY) {
+                    numSyncsLastDay++;
+                    durationLastDay += duration;
+                }
+                if (millisSinceStart < MILLIS_IN_WEEK) {
+                    numSyncsLastWeek++;
+                    durationLastWeek += duration;
+                }
+                if (millisSinceStart < MILLIS_IN_4WEEKS) {
+                    numSyncsLast4Weeks++;
+                    durationLast4Weeks += duration;
+                }
+            }
+            dumpSyncIntervalHeader(sb);
+            dumpSyncInterval(sb, "hour", MILLIS_IN_HOUR, numSyncsLastHour, durationLastHour);
+            dumpSyncInterval(sb, "day", MILLIS_IN_DAY, numSyncsLastDay, durationLastDay);
+            dumpSyncInterval(sb, "week", MILLIS_IN_WEEK, numSyncsLastWeek, durationLastWeek);
+            dumpSyncInterval(sb, "4 weeks",
+                    MILLIS_IN_4WEEKS, numSyncsLast4Weeks, durationLast4Weeks);
+            dumpSyncInterval(sb, "total", 0, numSyncsTotal, durationTotal);
+            dumpSyncIntervalFooter(sb);
+        } finally {
+            c.close();
+        }
+    }
+
+    private void dumpSyncIntervalHeader(StringBuilder sb) {
+        sb.append("Sync Stats\n");
+        sb.append(" ___________________________________________________________\n");
+        sb.append(" |          |        |   duration in sec   |               |\n");
+        sb.append(" | interval |  count |  average |    total | % of interval |\n");
+    }
+
+    private void dumpSyncInterval(StringBuilder sb, String label,
+            long interval, long numSyncs, long duration) {
+        sb.append(String.format(" | %-8s | %6d | %8.1f | %8.1f",
+                label, numSyncs, ((float)duration/numSyncs)/1000, (float)duration/1000));
+        if (interval > 0) {
+            sb.append(String.format(" | %13.2f |\n", ((float)duration/interval)*100.0));
+        } else {
+            sb.append(String.format(" | %13s |\n", "na"));
+        }
+    }
+
+    private void dumpSyncIntervalFooter(StringBuilder sb) {
+        sb.append(" |_________________________________________________________|\n");
+    }
+
+    /**
+     * A helper object to keep track of the time we have spent syncing since the last boot
+     */
+    private class SyncTimeTracker {
+        /** True if a sync was in progress on the most recent call to update() */
+        boolean mLastWasSyncing = false;
+        /** Used to track when lastWasSyncing was last set */
+        long mWhenSyncStarted = 0;
+        /** The cumulative time we have spent syncing */
+        private long mTimeSpentSyncing;
+
+        /** Call to let the tracker know that the sync state may have changed */
+        public synchronized void update() {
+            final boolean isSyncInProgress = mActiveSyncContext != null;
+            if (isSyncInProgress == mLastWasSyncing) return;
+            final long now = SystemClock.elapsedRealtime();
+            if (isSyncInProgress) {
+                mWhenSyncStarted = now;
+            } else {
+                mTimeSpentSyncing += now - mWhenSyncStarted;
+            }
+            mLastWasSyncing = isSyncInProgress;
+        }
+
+        /** Get how long we have been syncing, in ms */
+        public synchronized long timeSpentSyncing() {
+            if (!mLastWasSyncing) return mTimeSpentSyncing;
+
+            final long now = SystemClock.elapsedRealtime();
+            return mTimeSpentSyncing + (now - mWhenSyncStarted);
+        }
+    }
+
+    /**
+     * Handles SyncOperation Messages that are posted to the associated
+     * HandlerThread.
+     */
+    class SyncHandler extends Handler {
+        // Messages that can be sent on mHandler
+        private static final int MESSAGE_SYNC_FINISHED = 1;
+        private static final int MESSAGE_SYNC_ALARM = 2;
+        private static final int MESSAGE_CHECK_ALARMS = 3;
+
+        public final SyncNotificationInfo mSyncNotificationInfo = new SyncNotificationInfo();
+        private Long mAlarmScheduleTime = null;
+        public final SyncTimeTracker mSyncTimeTracker = new SyncTimeTracker();
+
+        // used to track if we have installed the error notification so that we don't reinstall
+        // it if sync is still failing
+        private boolean mErrorNotificationInstalled = false;
+
+        /**
+         * Used to keep track of whether a sync notification is active and who it is for.
+         */
+        class SyncNotificationInfo {
+            // only valid if isActive is true
+            public String account;
+
+            // only valid if isActive is true
+            public String authority;
+
+            // true iff the notification manager has been asked to send the notification
+            public boolean isActive = false;
+
+            // Set when we transition from not running a sync to running a sync, and cleared on
+            // the opposite transition.
+            public Long startTime = null;
+
+            public void toString(StringBuilder sb) {
+                sb.append("account ").append(account)
+                        .append(", authority ").append(authority)
+                        .append(", isActive ").append(isActive)
+                        .append(", startTime ").append(startTime);
+            }
+
+            @Override
+            public String toString() {
+                StringBuilder sb = new StringBuilder();
+                toString(sb);
+                return sb.toString();
+            }
+        }
+
+        public SyncHandler(Looper looper) {
+            super(looper);
+        }
+
+        public void handleMessage(Message msg) {
+            handleSyncHandlerMessage(msg);
+        }
+
+        private void handleSyncHandlerMessage(Message msg) {
+            try {
+                switch (msg.what) {
+                    case SyncHandler.MESSAGE_SYNC_FINISHED:
+                        if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                            Log.v(TAG, "handleSyncHandlerMessage: MESSAGE_SYNC_FINISHED");
+                        }
+                        SyncHandlerMessagePayload payload = (SyncHandlerMessagePayload)msg.obj;
+                        if (mActiveSyncContext != payload.activeSyncContext) {
+                            if (Config.LOGD) {
+                                Log.d(TAG, "handleSyncHandlerMessage: sync context doesn't match, "
+                                        + "dropping: mActiveSyncContext " + mActiveSyncContext
+                                        + " != " + payload.activeSyncContext);
+                            }
+                            return;
+                        }
+                        runSyncFinishedOrCanceled(payload.syncResult);
+
+                        // since we are no longer syncing, check if it is time to start a new sync
+                        runStateIdle();
+                        break;
+
+                    case SyncHandler.MESSAGE_SYNC_ALARM: {
+                        boolean isLoggable = Log.isLoggable(TAG, Log.VERBOSE);
+                        if (isLoggable) {
+                            Log.v(TAG, "handleSyncHandlerMessage: MESSAGE_SYNC_ALARM");
+                        }
+                        mAlarmScheduleTime = null;
+                        try {
+                            if (mActiveSyncContext != null) {
+                                if (isLoggable) {
+                                    Log.v(TAG, "handleSyncHandlerMessage: sync context is active");
+                                }
+                                runStateSyncing();
+                            }
+
+                            // if the above call to runStateSyncing() resulted in the end of a sync,
+                            // check if it is time to start a new sync
+                            if (mActiveSyncContext == null) {
+                                if (isLoggable) {
+                                    Log.v(TAG, "handleSyncHandlerMessage: "
+                                            + "sync context is not active");
+                                }
+                                runStateIdle();
+                            }
+                        } finally {
+                            mHandleAlarmWakeLock.release();
+                        }
+                        break;
+                    }
+
+                    case SyncHandler.MESSAGE_CHECK_ALARMS:
+                        if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                            Log.v(TAG, "handleSyncHandlerMessage: MESSAGE_CHECK_ALARMS");
+                        }
+                        // we do all the work for this case in the finally block
+                        break;
+                }
+            } finally {
+                final boolean isSyncInProgress = mActiveSyncContext != null;
+                if (!isSyncInProgress) {
+                    mSyncWakeLock.release();
+                }
+                manageSyncNotification();
+                manageErrorNotification();
+                manageSyncAlarm();
+                mSyncTimeTracker.update();
+            }
+        }
+
+        private void runStateSyncing() {
+            // if the sync timeout has been reached then cancel it
+
+            ActiveSyncContext activeSyncContext = mActiveSyncContext;
+
+            final long now = SystemClock.elapsedRealtime();
+            if (now > activeSyncContext.mTimeoutStartTime + MAX_TIME_PER_SYNC) {
+                SyncOperation nextSyncOperation;
+                synchronized (mSyncQueue) {
+                    nextSyncOperation = mSyncQueue.head();
+                }
+                if (nextSyncOperation != null && nextSyncOperation.earliestRunTime <= now) {
+                    if (Config.LOGD) {
+                        Log.d(TAG, "canceling and rescheduling sync because it ran too long: "
+                                + activeSyncContext.mSyncOperation);
+                    }
+                    rescheduleImmediately(activeSyncContext.mSyncOperation);
+                    sendSyncFinishedOrCanceledMessage(activeSyncContext,
+                            null /* no result since this is a cancel */);
+                } else {
+                    activeSyncContext.mTimeoutStartTime = now + MAX_TIME_PER_SYNC;
+                }
+            }
+
+            // no need to schedule an alarm, as that will be done by our caller.
+        }
+
+        private void runStateIdle() {
+            boolean isLoggable = Log.isLoggable(TAG, Log.VERBOSE);
+            if (isLoggable) Log.v(TAG, "runStateIdle");
+
+            // If we aren't ready to run (e.g. the data connection is down), get out.
+            if (!mDataConnectionIsConnected) {
+                if (isLoggable) {
+                    Log.v(TAG, "runStateIdle: no data connection, skipping");
+                }
+                setStatusText("No data connection");
+                return;
+            }
+
+            if (mStorageIsLow) {
+                if (isLoggable) {
+                    Log.v(TAG, "runStateIdle: memory low, skipping");
+                }
+                setStatusText("Memory low");
+                return;
+            }
+
+            // If the accounts aren't known yet then we aren't ready to run. We will be kicked
+            // when the account lookup request does complete.
+            String[] accounts = mAccounts;
+            if (accounts == null) {
+                if (isLoggable) {
+                    Log.v(TAG, "runStateIdle: accounts not known, skipping");
+                }
+                setStatusText("Accounts not known yet");
+                return;
+            }
+
+            // Otherwise consume SyncOperations from the head of the SyncQueue until one is
+            // found that is runnable (not disabled, etc). If that one is ready to run then
+            // start it, otherwise just get out.
+            SyncOperation syncOperation;
+            final Sync.Settings.QueryMap syncSettings = getSyncSettings();
+            final ConnectivityManager connManager = (ConnectivityManager)
+                    mContext.getSystemService(Context.CONNECTIVITY_SERVICE);
+            final boolean backgroundDataSetting = connManager.getBackgroundDataSetting();
+            synchronized (mSyncQueue) {
+                while (true) {
+                    syncOperation = mSyncQueue.head();
+                    if (syncOperation == null) {
+                        if (isLoggable) {
+                            Log.v(TAG, "runStateIdle: no more sync operations, returning");
+                        }
+                        return;
+                    }
+
+                    // Sync is disabled, drop this operation.
+                    if (!isSyncEnabled()) {
+                        if (isLoggable) {
+                            Log.v(TAG, "runStateIdle: sync disabled, dropping " + syncOperation);
+                        }
+                        mSyncQueue.popHead();
+                        continue;
+                    }
+
+                    // skip the sync if it isn't a force and the settings are off for this provider
+                    final boolean force = syncOperation.extras.getBoolean(
+                            ContentResolver.SYNC_EXTRAS_FORCE, false);
+                    if (!force && (!backgroundDataSetting
+                            || !syncSettings.getListenForNetworkTickles()
+                            || !syncSettings.getSyncProviderAutomatically(
+                                    syncOperation.authority))) {
+                        if (isLoggable) {
+                            Log.v(TAG, "runStateIdle: sync off, dropping " + syncOperation);
+                        }
+                        mSyncQueue.popHead();
+                        continue;
+                    }
+
+                    // skip the sync if the account of this operation no longer exists
+                    if (!ArrayUtils.contains(accounts, syncOperation.account)) {
+                        mSyncQueue.popHead();
+                        if (isLoggable) {
+                            Log.v(TAG, "runStateIdle: account not present, dropping "
+                                    + syncOperation);
+                        }
+                        continue;
+                    }
+
+                    // go ahead and try to sync this syncOperation
+                    if (isLoggable) {
+                        Log.v(TAG, "runStateIdle: found sync candidate: " + syncOperation);
+                    }
+                    break;
+                }
+
+                // If the first SyncOperation isn't ready to run schedule a wakeup and
+                // get out.
+                final long now = SystemClock.elapsedRealtime();
+                if (syncOperation.earliestRunTime > now) {
+                    if (Log.isLoggable(TAG, Log.DEBUG)) {
+                        Log.d(TAG, "runStateIdle: the time is " + now + " yet the next "
+                                + "sync operation is for " + syncOperation.earliestRunTime
+                                + ": " + syncOperation);
+                    }
+                    return;
+                }
+
+                // We will do this sync. Remove it from the queue and run it outside of the
+                // synchronized block.
+                if (isLoggable) {
+                    Log.v(TAG, "runStateIdle: we are going to sync " + syncOperation);
+                }
+                mSyncQueue.popHead();
+            }
+
+            String providerName = syncOperation.authority;
+            ensureContentResolver();
+            IContentProvider contentProvider;
+
+            // acquire the provider and update the sync history
+            try {
+                contentProvider = mContentResolver.acquireProvider(providerName);
+                if (contentProvider == null) {
+                    Log.e(TAG, "Provider " + providerName + " doesn't exist");
+                    return;
+                }
+                if (contentProvider.getSyncAdapter() == null) {
+                    Log.e(TAG, "Provider " + providerName + " isn't syncable, " + contentProvider);
+                    return;
+                }
+            } catch (RemoteException remoteExc) {
+                Log.e(TAG, "Caught a RemoteException while preparing for sync, rescheduling "
+                        + syncOperation, remoteExc);
+                rescheduleWithDelay(syncOperation);
+                return;
+            } catch (RuntimeException exc) {
+                Log.e(TAG, "Caught a RuntimeException while validating sync of " + providerName,
+                        exc);
+                return;
+            }
+
+            final long historyRowId = insertStartSyncEvent(syncOperation);
+
+            try {
+                ISyncAdapter syncAdapter = contentProvider.getSyncAdapter();
+                ActiveSyncContext activeSyncContext = new ActiveSyncContext(syncOperation,
+                        contentProvider, syncAdapter, historyRowId);
+                mSyncWakeLock.acquire();
+                if (Log.isLoggable(TAG, Log.DEBUG)) {
+                    Log.d(TAG, "starting sync of " + syncOperation);
+                }
+                syncAdapter.startSync(activeSyncContext, syncOperation.account,
+                        syncOperation.extras);
+                mActiveSyncContext = activeSyncContext;
+                mSyncStorageEngine.setActiveSync(mActiveSyncContext);
+            } catch (RemoteException remoteExc) {
+                if (Config.LOGD) {
+                    Log.d(TAG, "runStateIdle: caught a RemoteException, rescheduling", remoteExc);
+                }
+                mActiveSyncContext = null;
+                mSyncStorageEngine.setActiveSync(mActiveSyncContext);
+                rescheduleWithDelay(syncOperation);
+            } catch (RuntimeException exc) {
+                mActiveSyncContext = null;
+                mSyncStorageEngine.setActiveSync(mActiveSyncContext);
+                Log.e(TAG, "Caught a RuntimeException while starting the sync " + syncOperation,
+                        exc);
+            }
+
+            // no need to schedule an alarm, as that will be done by our caller.
+        }
+
+        private void runSyncFinishedOrCanceled(SyncResult syncResult) {
+            boolean isLoggable = Log.isLoggable(TAG, Log.VERBOSE);
+            if (isLoggable) Log.v(TAG, "runSyncFinishedOrCanceled");
+            ActiveSyncContext activeSyncContext = mActiveSyncContext;
+            mActiveSyncContext = null;
+            mSyncStorageEngine.setActiveSync(mActiveSyncContext);
+
+            final SyncOperation syncOperation = activeSyncContext.mSyncOperation;
+
+            final long elapsedTime = SystemClock.elapsedRealtime() - activeSyncContext.mStartTime;
+
+            String historyMessage;
+            int downstreamActivity;
+            int upstreamActivity;
+            if (syncResult != null) {
+                if (isLoggable) {
+                    Log.v(TAG, "runSyncFinishedOrCanceled: is a finished: operation "
+                            + syncOperation + ", result " + syncResult);
+                }
+
+                if (!syncResult.hasError()) {
+                    if (isLoggable) {
+                        Log.v(TAG, "finished sync operation " + syncOperation);
+                    }
+                    historyMessage = History.MESG_SUCCESS;
+                    // TODO: set these correctly when the SyncResult is extended to include it
+                    downstreamActivity = 0;
+                    upstreamActivity = 0;
+                } else {
+                    maybeRescheduleSync(syncResult, syncOperation);
+                    if (Config.LOGD) {
+                        Log.d(TAG, "failed sync operation " + syncOperation);
+                    }
+                    historyMessage = Integer.toString(syncResultToErrorNumber(syncResult));
+                    // TODO: set these correctly when the SyncResult is extended to include it
+                    downstreamActivity = 0;
+                    upstreamActivity = 0;
+                }
+            } else {
+                if (isLoggable) {
+                    Log.v(TAG, "runSyncFinishedOrCanceled: is a cancel: operation "
+                            + syncOperation);
+                }
+                try {
+                    activeSyncContext.mSyncAdapter.cancelSync();
+                } catch (RemoteException e) {
+                    // we don't need to retry this in this case
+                }
+                historyMessage = History.MESG_CANCELED;
+                downstreamActivity = 0;
+                upstreamActivity = 0;
+            }
+
+            stopSyncEvent(activeSyncContext.mHistoryRowId, syncOperation, historyMessage,
+                    upstreamActivity, downstreamActivity, elapsedTime);
+
+            mContentResolver.releaseProvider(activeSyncContext.mContentProvider);
+
+            if (syncResult != null && syncResult.tooManyDeletions) {
+                installHandleTooManyDeletesNotification(syncOperation.account,
+                        syncOperation.authority, syncResult.stats.numDeletes);
+            } else {
+                mNotificationMgr.cancel(
+                        syncOperation.account.hashCode() ^ syncOperation.authority.hashCode());
+            }
+
+            if (syncResult != null && syncResult.fullSyncRequested) {
+                scheduleSyncOperation(new SyncOperation(syncOperation.account,
+                        syncOperation.syncSource, syncOperation.authority, new Bundle(), 0));
+            }
+            // no need to schedule an alarm, as that will be done by our caller.
+        }
+
+        /**
+         * Convert the error-containing SyncResult into the Sync.History error number. Since
+         * the SyncResult may indicate multiple errors at once, this method just returns the
+         * most "serious" error.
+         * @param syncResult the SyncResult from which to read
+         * @return the most "serious" error set in the SyncResult
+         * @throws IllegalStateException if the SyncResult does not indicate any errors.
+         *   If SyncResult.error() is true then it is safe to call this.
+         */
+        private int syncResultToErrorNumber(SyncResult syncResult) {
+            if (syncResult.syncAlreadyInProgress) return History.ERROR_SYNC_ALREADY_IN_PROGRESS;
+            if (syncResult.stats.numAuthExceptions > 0) return History.ERROR_AUTHENTICATION;
+            if (syncResult.stats.numIoExceptions > 0) return History.ERROR_IO;
+            if (syncResult.stats.numParseExceptions > 0) return History.ERROR_PARSE;
+            if (syncResult.stats.numConflictDetectedExceptions > 0) return History.ERROR_CONFLICT;
+            if (syncResult.tooManyDeletions) return History.ERROR_TOO_MANY_DELETIONS;
+            if (syncResult.tooManyRetries) return History.ERROR_TOO_MANY_RETRIES;
+            if (syncResult.databaseError) return History.ERROR_INTERNAL;
+            throw new IllegalStateException("we are not in an error state, " + syncResult);
+        }
+
+        private void manageSyncNotification() {
+            boolean shouldCancel;
+            boolean shouldInstall;
+
+            if (mActiveSyncContext == null) {
+                mSyncNotificationInfo.startTime = null;
+
+                // we aren't syncing. if the notification is active then remember that we need
+                // to cancel it and then clear out the info
+                shouldCancel = mSyncNotificationInfo.isActive;
+                shouldInstall = false;
+            } else {
+                // we are syncing
+                final SyncOperation syncOperation = mActiveSyncContext.mSyncOperation;
+
+                final long now = SystemClock.elapsedRealtime();
+                if (mSyncNotificationInfo.startTime == null) {
+                    mSyncNotificationInfo.startTime = now;
+                }
+
+                // cancel the notification if it is up and the authority or account is wrong
+                shouldCancel = mSyncNotificationInfo.isActive &&
+                        (!syncOperation.authority.equals(mSyncNotificationInfo.authority)
+                        || !syncOperation.account.equals(mSyncNotificationInfo.account));
+
+                // there are four cases:
+                // - the notification is up and there is no change: do nothing
+                // - the notification is up but we should cancel since it is stale:
+                //   need to install
+                // - the notification is not up but it isn't time yet: don't install
+                // - the notification is not up and it is time: need to install
+
+                if (mSyncNotificationInfo.isActive) {
+                    shouldInstall = shouldCancel;
+                } else {
+                    final boolean timeToShowNotification =
+                            now > mSyncNotificationInfo.startTime + SYNC_NOTIFICATION_DELAY;
+                    final boolean syncIsForced = syncOperation.extras
+                            .getBoolean(ContentResolver.SYNC_EXTRAS_FORCE, false);
+                    shouldInstall = timeToShowNotification || syncIsForced;
+                }
+            }
+
+            if (shouldCancel && !shouldInstall) {
+                mNeedSyncActiveNotification = false;
+                sendSyncStateIntent();
+                mSyncNotificationInfo.isActive = false;
+            }
+
+            if (shouldInstall) {
+                SyncOperation syncOperation = mActiveSyncContext.mSyncOperation;
+                mNeedSyncActiveNotification = true;
+                sendSyncStateIntent();
+                mSyncNotificationInfo.isActive = true;
+                mSyncNotificationInfo.account = syncOperation.account;
+                mSyncNotificationInfo.authority = syncOperation.authority;
+            }
+        }
+
+        /**
+         * Check if there were any long-lasting errors, if so install the error notification,
+         * otherwise cancel the error notification.
+         */
+        private void manageErrorNotification() {
+            //
+            long when = mSyncStorageEngine.getInitialSyncFailureTime();
+            if ((when > 0) && (when + ERROR_NOTIFICATION_DELAY_MS < System.currentTimeMillis())) {
+                if (!mErrorNotificationInstalled) {
+                    mNeedSyncErrorNotification = true;
+                    sendSyncStateIntent();
+                }
+                mErrorNotificationInstalled = true;
+            } else {
+                if (mErrorNotificationInstalled) {
+                    mNeedSyncErrorNotification = false;
+                    sendSyncStateIntent();
+                }
+                mErrorNotificationInstalled = false;
+            }
+        }
+
+        private void manageSyncAlarm() {
+            // in each of these cases the sync loop will be kicked, which will cause this
+            // method to be called again
+            if (!mDataConnectionIsConnected) return;
+            if (mAccounts == null) return;
+            if (mStorageIsLow) return;
+
+            // Compute the alarm fire time:
+            // - not syncing: time of the next sync operation
+            // - syncing, no notification: time from sync start to notification create time
+            // - syncing, with notification: time till timeout of the active sync operation
+            Long alarmTime = null;
+            ActiveSyncContext activeSyncContext = mActiveSyncContext;
+            if (activeSyncContext == null) {
+                SyncOperation syncOperation;
+                synchronized (mSyncQueue) {
+                    syncOperation = mSyncQueue.head();
+                }
+                if (syncOperation != null) {
+                    alarmTime = syncOperation.earliestRunTime;
+                }
+            } else {
+                final long notificationTime =
+                        mSyncHandler.mSyncNotificationInfo.startTime + SYNC_NOTIFICATION_DELAY;
+                final long timeoutTime =
+                        mActiveSyncContext.mTimeoutStartTime + MAX_TIME_PER_SYNC;
+                if (mSyncHandler.mSyncNotificationInfo.isActive) {
+                    alarmTime = timeoutTime;
+                } else {
+                    alarmTime = Math.min(notificationTime, timeoutTime);
+                }
+            }
+
+            // adjust the alarmTime so that we will wake up when it is time to
+            // install the error notification
+            if (!mErrorNotificationInstalled) {
+                long when = mSyncStorageEngine.getInitialSyncFailureTime();
+                if (when > 0) {
+                    when += ERROR_NOTIFICATION_DELAY_MS;
+                    // convert when fron absolute time to elapsed run time
+                    long delay = when - System.currentTimeMillis();
+                    when = SystemClock.elapsedRealtime() + delay;
+                    alarmTime = alarmTime != null ? Math.min(alarmTime, when) : when;
+                }
+            }
+
+            // determine if we need to set or cancel the alarm
+            boolean shouldSet = false;
+            boolean shouldCancel = false;
+            final boolean alarmIsActive = mAlarmScheduleTime != null;
+            final boolean needAlarm = alarmTime != null;
+            if (needAlarm) {
+                if (!alarmIsActive || alarmTime < mAlarmScheduleTime) {
+                    shouldSet = true;
+                }
+            } else {
+                shouldCancel = alarmIsActive;
+            }
+
+            // set or cancel the alarm as directed
+            ensureAlarmService();
+            if (shouldSet) {
+                mAlarmScheduleTime = alarmTime;
+                mAlarmService.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, alarmTime,
+                        mSyncAlarmIntent);
+            } else if (shouldCancel) {
+                mAlarmScheduleTime = null;
+                mAlarmService.cancel(mSyncAlarmIntent);
+            }
+        }
+
+        private void sendSyncStateIntent() {
+            Intent syncStateIntent = new Intent(Intent.ACTION_SYNC_STATE_CHANGED);
+            syncStateIntent.putExtra("active", mNeedSyncActiveNotification);
+            syncStateIntent.putExtra("failing", mNeedSyncErrorNotification);
+            mContext.sendBroadcast(syncStateIntent);
+        }
+
+        private void installHandleTooManyDeletesNotification(String account, String authority,
+                long numDeletes) {
+            if (mNotificationMgr == null) return;
+            Intent clickIntent = new Intent();
+            clickIntent.setClassName("com.android.providers.subscribedfeeds",
+                    "com.android.settings.SyncActivityTooManyDeletes");
+            clickIntent.putExtra("account", account);
+            clickIntent.putExtra("provider", authority);
+            clickIntent.putExtra("numDeletes", numDeletes);
+
+            if (!isActivityAvailable(clickIntent)) {
+                Log.w(TAG, "No activity found to handle too many deletes.");
+                return;
+            }
+
+            final PendingIntent pendingIntent = PendingIntent
+                    .getActivity(mContext, 0, clickIntent, PendingIntent.FLAG_CANCEL_CURRENT);
+
+            CharSequence tooManyDeletesDescFormat = mContext.getResources().getText(
+                    R.string.contentServiceTooManyDeletesNotificationDesc);
+
+            String[] authorities = authority.split(";");
+            Notification notification =
+                new Notification(R.drawable.stat_notify_sync_error,
+                        mContext.getString(R.string.contentServiceSync),
+                        System.currentTimeMillis());
+            notification.setLatestEventInfo(mContext,
+                    mContext.getString(R.string.contentServiceSyncNotificationTitle),
+                    String.format(tooManyDeletesDescFormat.toString(), authorities[0]),
+                    pendingIntent);
+            notification.flags |= Notification.FLAG_ONGOING_EVENT;
+            mNotificationMgr.notify(account.hashCode() ^ authority.hashCode(), notification);
+        }
+
+        /**
+         * Checks whether an activity exists on the system image for the given intent.
+         *
+         * @param intent The intent for an activity.
+         * @return Whether or not an activity exists.
+         */
+        private boolean isActivityAvailable(Intent intent) {
+            PackageManager pm = mContext.getPackageManager();
+            List<ResolveInfo> list = pm.queryIntentActivities(intent, 0);
+            int listSize = list.size();
+            for (int i = 0; i < listSize; i++) {
+                ResolveInfo resolveInfo = list.get(i);
+                if ((resolveInfo.activityInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM)
+                        != 0) {
+                    return true;
+                }
+            }
+
+            return false;
+        }
+
+        public long insertStartSyncEvent(SyncOperation syncOperation) {
+            final int source = syncOperation.syncSource;
+            final long now = System.currentTimeMillis();
+
+            EventLog.writeEvent(2720, syncOperation.authority, Sync.History.EVENT_START, source);
+
+            return mSyncStorageEngine.insertStartSyncEvent(
+                    syncOperation.account, syncOperation.authority, now, source);
+        }
+
+        public void stopSyncEvent(long rowId, SyncOperation syncOperation, String resultMessage,
+                int upstreamActivity, int downstreamActivity, long elapsedTime) {
+            EventLog.writeEvent(2720, syncOperation.authority, Sync.History.EVENT_STOP, syncOperation.syncSource);
+
+            mSyncStorageEngine.stopSyncEvent(rowId, elapsedTime, resultMessage,
+                    downstreamActivity, upstreamActivity);
+        }
+    }
+
+    static class SyncQueue {
+        private SyncStorageEngine mSyncStorageEngine;
+        private final String[] COLUMNS = new String[]{
+                "_id",
+                "authority",
+                "account",
+                "extras",
+                "source"
+        };
+        private static final int COLUMN_ID = 0;
+        private static final int COLUMN_AUTHORITY = 1;
+        private static final int COLUMN_ACCOUNT = 2;
+        private static final int COLUMN_EXTRAS = 3;
+        private static final int COLUMN_SOURCE = 4;
+
+        private static final boolean DEBUG_CHECK_DATA_CONSISTENCY = false;
+
+        // A priority queue of scheduled SyncOperations that is designed to make it quick
+        // to find the next SyncOperation that should be considered for running.
+        private final PriorityQueue<SyncOperation> mOpsByWhen = new PriorityQueue<SyncOperation>();
+
+        // A Map of SyncOperations operationKey -> SyncOperation that is designed for
+        // quick lookup of an enqueued SyncOperation.
+        private final HashMap<String, SyncOperation> mOpsByKey = Maps.newHashMap();
+
+        public SyncQueue(SyncStorageEngine syncStorageEngine) {
+            mSyncStorageEngine = syncStorageEngine;
+            Cursor cursor = mSyncStorageEngine.getPendingSyncsCursor(COLUMNS);
+            try {
+                while (cursor.moveToNext()) {
+                    add(cursorToOperation(cursor),
+                            true /* this is being added from the database */);
+                }
+            } finally {
+                cursor.close();
+                if (DEBUG_CHECK_DATA_CONSISTENCY) debugCheckDataStructures(true /* check the DB */);
+            }
+        }
+
+        public boolean add(SyncOperation operation) {
+            return add(new SyncOperation(operation),
+                    false /* this is not coming from the database */);
+        }
+
+        private boolean add(SyncOperation operation, boolean fromDatabase) {
+            if (DEBUG_CHECK_DATA_CONSISTENCY) debugCheckDataStructures(!fromDatabase);
+
+            // If this operation is expedited then set its earliestRunTime to be immediately
+            // before the head of the list, or not if none are in the list.
+            if (operation.delay < 0) {
+                SyncOperation headOperation = head();
+                if (headOperation != null) {
+                    operation.earliestRunTime = Math.min(SystemClock.elapsedRealtime(),
+                            headOperation.earliestRunTime - 1);
+                } else {
+                    operation.earliestRunTime = SystemClock.elapsedRealtime();
+                }
+            }
+
+            // - if an operation with the same key exists and this one should run earlier,
+            //   delete the old one and add the new one
+            // - if an operation with the same key exists and if this one should run
+            //   later, ignore it
+            // - if no operation exists then add the new one
+            final String operationKey = operation.key;
+            SyncOperation existingOperation = mOpsByKey.get(operationKey);
+
+            // if this operation matches an existing operation that is being retried (delay > 0)
+            // and this operation isn't forced, ignore this operation
+            if (existingOperation != null && existingOperation.delay > 0) {
+                if (!operation.extras.getBoolean(ContentResolver.SYNC_EXTRAS_FORCE, false)) {
+                    return false;
+                }
+            }
+
+            if (existingOperation != null
+                    && operation.earliestRunTime >= existingOperation.earliestRunTime) {
+                if (DEBUG_CHECK_DATA_CONSISTENCY) debugCheckDataStructures(!fromDatabase);
+                return false;
+            }
+
+            if (existingOperation != null) {
+                removeByKey(operationKey);
+            }
+
+            if (operation.rowId == null) {
+                byte[] extrasData = null;
+                Parcel parcel = Parcel.obtain();
+                try {
+                    operation.extras.writeToParcel(parcel, 0);
+                    extrasData = parcel.marshall();
+                } finally {
+                    parcel.recycle();
+                }
+                ContentValues values = new ContentValues();
+                values.put("account", operation.account);
+                values.put("authority", operation.authority);
+                values.put("source", operation.syncSource);
+                values.put("extras", extrasData);
+                Uri pendingUri = mSyncStorageEngine.insertIntoPending(values);
+                operation.rowId = pendingUri == null ? null : ContentUris.parseId(pendingUri);
+                if (operation.rowId == null) {
+                    throw new IllegalStateException("error adding pending sync operation "
+                            + operation);
+                }
+            }
+
+            if (DEBUG_CHECK_DATA_CONSISTENCY) {
+                debugCheckDataStructures(
+                        false /* don't compare with the DB, since we know
+                               it is inconsistent right now */ );
+            }
+            mOpsByKey.put(operationKey, operation);
+            mOpsByWhen.add(operation);
+            if (DEBUG_CHECK_DATA_CONSISTENCY) debugCheckDataStructures(!fromDatabase);
+            return true;
+        }
+
+        public void removeByKey(String operationKey) {
+            if (DEBUG_CHECK_DATA_CONSISTENCY) debugCheckDataStructures(true /* check the DB */);
+            SyncOperation operationToRemove = mOpsByKey.remove(operationKey);
+            if (!mOpsByWhen.remove(operationToRemove)) {
+                throw new IllegalStateException(
+                        "unable to find " + operationToRemove + " in mOpsByWhen");
+            }
+
+            if (mSyncStorageEngine.deleteFromPending(operationToRemove.rowId) != 1) {
+                throw new IllegalStateException("unable to find pending row for "
+                        + operationToRemove);
+            }
+
+            if (DEBUG_CHECK_DATA_CONSISTENCY) debugCheckDataStructures(true /* check the DB */);
+        }
+
+        public SyncOperation head() {
+            if (DEBUG_CHECK_DATA_CONSISTENCY) debugCheckDataStructures(true /* check the DB */);
+            return mOpsByWhen.peek();
+        }
+
+        public void popHead() {
+            if (DEBUG_CHECK_DATA_CONSISTENCY) debugCheckDataStructures(true /* check the DB */);
+            SyncOperation operation = mOpsByWhen.remove();
+            if (mOpsByKey.remove(operation.key) == null) {
+                throw new IllegalStateException("unable to find " + operation + " in mOpsByKey");
+            }
+
+            if (mSyncStorageEngine.deleteFromPending(operation.rowId) != 1) {
+                throw new IllegalStateException("unable to find pending row for " + operation);
+            }
+
+            if (DEBUG_CHECK_DATA_CONSISTENCY) debugCheckDataStructures(true /* check the DB */);
+        }
+
+        public void clear(String account, String authority) {
+            Iterator<Map.Entry<String, SyncOperation>> entries = mOpsByKey.entrySet().iterator();
+            while (entries.hasNext()) {
+                Map.Entry<String, SyncOperation> entry = entries.next();
+                SyncOperation syncOperation = entry.getValue();
+                if (account != null && !syncOperation.account.equals(account)) continue;
+                if (authority != null && !syncOperation.authority.equals(authority)) continue;
+
+                if (DEBUG_CHECK_DATA_CONSISTENCY) debugCheckDataStructures(true /* check the DB */);
+                entries.remove();
+                if (!mOpsByWhen.remove(syncOperation)) {
+                    throw new IllegalStateException(
+                            "unable to find " + syncOperation + " in mOpsByWhen");
+                }
+
+                if (mSyncStorageEngine.deleteFromPending(syncOperation.rowId) != 1) {
+                    throw new IllegalStateException("unable to find pending row for "
+                            + syncOperation);
+                }
+
+                if (DEBUG_CHECK_DATA_CONSISTENCY) debugCheckDataStructures(true /* check the DB */);
+            }
+        }
+
+        public void dump(StringBuilder sb) {
+            sb.append("SyncQueue: ").append(mOpsByWhen.size()).append(" operation(s)\n");
+            for (SyncOperation operation : mOpsByWhen) {
+                sb.append(operation).append("\n");
+            }
+        }
+
+        private void debugCheckDataStructures(boolean checkDatabase) {
+            if (mOpsByKey.size() != mOpsByWhen.size()) {
+                throw new IllegalStateException("size mismatch: "
+                        + mOpsByKey .size() + " != " + mOpsByWhen.size());
+            }
+            for (SyncOperation operation : mOpsByWhen) {
+                if (!mOpsByKey.containsKey(operation.key)) {
+                    throw new IllegalStateException(
+                        "operation " + operation + " is in mOpsByWhen but not mOpsByKey");
+                }
+            }
+            for (Map.Entry<String, SyncOperation> entry : mOpsByKey.entrySet()) {
+                final SyncOperation operation = entry.getValue();
+                final String key = entry.getKey();
+                if (!key.equals(operation.key)) {
+                    throw new IllegalStateException(
+                        "operation " + operation + " in mOpsByKey doesn't match key " + key);
+                }
+                if (!mOpsByWhen.contains(operation)) {
+                    throw new IllegalStateException(
+                        "operation " + operation + " is in mOpsByKey but not mOpsByWhen");
+                }
+            }
+
+            if (checkDatabase) {
+                // check that the DB contains the same rows as the in-memory data structures
+                Cursor cursor = mSyncStorageEngine.getPendingSyncsCursor(COLUMNS);
+                try {
+                    if (mOpsByKey.size() != cursor.getCount()) {
+                        StringBuilder sb = new StringBuilder();
+                        DatabaseUtils.dumpCursor(cursor, sb);
+                        sb.append("\n");
+                        dump(sb);
+                        throw new IllegalStateException("DB size mismatch: "
+                                + mOpsByKey .size() + " != " + cursor.getCount() + "\n"
+                                + sb.toString());
+                    }
+                } finally {
+                    cursor.close();
+                }
+            }
+        }
+
+        private SyncOperation cursorToOperation(Cursor cursor) {
+            byte[] extrasData = cursor.getBlob(COLUMN_EXTRAS);
+            Bundle extras;
+            Parcel parcel = Parcel.obtain();
+            try {
+                parcel.unmarshall(extrasData, 0, extrasData.length);
+                parcel.setDataPosition(0);
+                extras = parcel.readBundle();
+            } catch (RuntimeException e) {
+                // A RuntimeException is thrown if we were unable to parse the parcel.
+                // Create an empty parcel in this case.
+                extras = new Bundle();
+            } finally {
+                parcel.recycle();
+            }
+
+            SyncOperation syncOperation = new SyncOperation(
+                    cursor.getString(COLUMN_ACCOUNT),
+                    cursor.getInt(COLUMN_SOURCE),
+                    cursor.getString(COLUMN_AUTHORITY),
+                    extras,
+                    0 /* delay */);
+            syncOperation.rowId = cursor.getLong(COLUMN_ID);
+            return syncOperation;
+        }
+    }
+}
diff --git a/core/java/android/content/SyncProvider.java b/core/java/android/content/SyncProvider.java
new file mode 100644
index 0000000..6ddd046
--- /dev/null
+++ b/core/java/android/content/SyncProvider.java
@@ -0,0 +1,53 @@
+// Copyright 2007 The Android Open Source Project
+package android.content;
+
+import android.database.Cursor;
+import android.net.Uri;
+
+/**
+ * ContentProvider that tracks the sync data and overall sync
+ * history on the device.
+ * 
+ * @hide
+ */
+public class SyncProvider extends ContentProvider {
+    public SyncProvider() {
+    }
+
+    private SyncStorageEngine mSyncStorageEngine;
+
+    @Override
+    public boolean onCreate() {
+        mSyncStorageEngine = SyncStorageEngine.getSingleton();
+        return true;
+    }
+
+    @Override
+    public Cursor query(Uri url, String[] projectionIn,
+                        String selection, String[] selectionArgs, String sort) {
+        return mSyncStorageEngine.query(url, projectionIn, selection, selectionArgs, sort);
+    }
+
+    @Override
+    public Uri insert(Uri url, ContentValues initialValues) {
+        return mSyncStorageEngine.insert(true /* the caller is the provider */,
+                url, initialValues);
+    }
+
+    @Override
+    public int delete(Uri url, String where, String[] whereArgs) {
+        return mSyncStorageEngine.delete(true /* the caller is the provider */,
+                url, where, whereArgs);
+    }
+
+    @Override
+    public int update(Uri url, ContentValues initialValues, String where, String[] whereArgs) {
+        return mSyncStorageEngine.update(true /* the caller is the provider */, 
+                url, initialValues, where, whereArgs);
+    }
+
+    @Override
+    public String getType(Uri url) {
+        return mSyncStorageEngine.getType(url);
+    }
+}
diff --git a/core/java/android/content/SyncResult.aidl b/core/java/android/content/SyncResult.aidl
new file mode 100644
index 0000000..061b81c
--- /dev/null
+++ b/core/java/android/content/SyncResult.aidl
@@ -0,0 +1,19 @@
+/*
+ * 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 android.content;
+
+parcelable SyncResult;
diff --git a/core/java/android/content/SyncResult.java b/core/java/android/content/SyncResult.java
new file mode 100644
index 0000000..f3260f3
--- /dev/null
+++ b/core/java/android/content/SyncResult.java
@@ -0,0 +1,178 @@
+package android.content;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * This class is used to store information about the result of a sync
+ * 
+ * @hide
+ */
+public final class SyncResult implements Parcelable {
+    public final boolean syncAlreadyInProgress;
+    public boolean tooManyDeletions;
+    public boolean tooManyRetries;
+    public boolean databaseError;
+    public boolean fullSyncRequested;
+    public boolean partialSyncUnavailable;
+    public boolean moreRecordsToGet;
+    public final SyncStats stats;
+    public static final SyncResult ALREADY_IN_PROGRESS;
+
+    static {
+        ALREADY_IN_PROGRESS = new SyncResult(true);
+    }
+
+    public SyncResult() {
+        this(false);
+    }
+
+    private SyncResult(boolean syncAlreadyInProgress) {
+        this.syncAlreadyInProgress = syncAlreadyInProgress;
+        this.tooManyDeletions = false;
+        this.tooManyRetries = false;
+        this.fullSyncRequested = false;
+        this.partialSyncUnavailable = false;
+        this.moreRecordsToGet = false;
+        this.stats = new SyncStats();
+    }
+
+    private SyncResult(Parcel parcel) {
+        syncAlreadyInProgress = parcel.readInt() != 0;
+        tooManyDeletions = parcel.readInt() != 0;
+        tooManyRetries = parcel.readInt() != 0;
+        databaseError = parcel.readInt() != 0;
+        fullSyncRequested = parcel.readInt() != 0;
+        partialSyncUnavailable = parcel.readInt() != 0;
+        moreRecordsToGet = parcel.readInt() != 0;
+        stats = new SyncStats(parcel);
+    }
+
+    public boolean hasHardError() {
+        return stats.numParseExceptions > 0
+                || stats.numConflictDetectedExceptions > 0
+                || stats.numAuthExceptions > 0
+                || tooManyDeletions
+                || tooManyRetries
+                || databaseError;
+    }
+
+    public boolean hasSoftError() {
+        return syncAlreadyInProgress || stats.numIoExceptions > 0;
+    }
+
+    public boolean hasError() {
+        return hasSoftError() || hasHardError();
+    }
+
+    public boolean madeSomeProgress() {
+        return ((stats.numDeletes > 0) && !tooManyDeletions)
+                || stats.numInserts > 0
+                || stats.numUpdates > 0;
+    }
+
+    public void clear() {
+        if (syncAlreadyInProgress) {
+            throw new UnsupportedOperationException(
+                    "you are not allowed to clear the ALREADY_IN_PROGRESS SyncStats");
+        }
+        tooManyDeletions = false;
+        tooManyRetries = false;
+        databaseError = false;
+        fullSyncRequested = false;
+        partialSyncUnavailable = false;
+        moreRecordsToGet = false;
+        stats.clear();
+    }
+
+    public static final Creator<SyncResult> CREATOR = new Creator<SyncResult>() {
+        public SyncResult createFromParcel(Parcel in) {
+            return new SyncResult(in);
+        }
+
+        public SyncResult[] newArray(int size) {
+            return new SyncResult[size];
+        }
+    };
+
+    public int describeContents() {
+        return 0;
+    }
+
+    public void writeToParcel(Parcel parcel, int flags) {
+        parcel.writeInt(syncAlreadyInProgress ? 1 : 0);
+        parcel.writeInt(tooManyDeletions ? 1 : 0);
+        parcel.writeInt(tooManyRetries ? 1 : 0);
+        parcel.writeInt(databaseError ? 1 : 0);
+        parcel.writeInt(fullSyncRequested ? 1 : 0);
+        parcel.writeInt(partialSyncUnavailable ? 1 : 0);
+        parcel.writeInt(moreRecordsToGet ? 1 : 0);
+        stats.writeToParcel(parcel, flags);
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder sb = new StringBuilder();
+        sb.append(" syncAlreadyInProgress: ").append(syncAlreadyInProgress);
+        sb.append(" tooManyDeletions: ").append(tooManyDeletions);
+        sb.append(" tooManyRetries: ").append(tooManyRetries);
+        sb.append(" databaseError: ").append(databaseError);
+        sb.append(" fullSyncRequested: ").append(fullSyncRequested);
+        sb.append(" partialSyncUnavailable: ").append(partialSyncUnavailable);
+        sb.append(" moreRecordsToGet: ").append(moreRecordsToGet);
+        sb.append(" stats: ").append(stats);
+        return sb.toString();
+    }
+
+    /**
+     * Generates a debugging string indicating the status.
+     * The string consist of a sequence of code letter followed by the count.
+     * Code letters are f - fullSyncRequested, r - partialSyncUnavailable,
+     * X - hardError, e - numParseExceptions, c - numConflictDetectedExceptions,
+     * a - numAuthExceptions, D - tooManyDeletions, R - tooManyRetries,
+     * b - databaseError, x - softError, l - syncAlreadyInProgress,
+     * I - numIoExceptions
+     * @return debugging string.
+     */
+    public String toDebugString() {
+        StringBuffer sb = new StringBuffer();
+
+        if (fullSyncRequested) {
+            sb.append("f1");
+        }
+        if (partialSyncUnavailable) {
+            sb.append("r1");
+        }
+        if (hasHardError()) {
+            sb.append("X1");
+        }
+        if (stats.numParseExceptions > 0) {
+            sb.append("e").append(stats.numParseExceptions);
+        }
+        if (stats.numConflictDetectedExceptions > 0) {
+            sb.append("c").append(stats.numConflictDetectedExceptions);
+        }
+        if (stats.numAuthExceptions > 0) {
+            sb.append("a").append(stats.numAuthExceptions);
+        }
+        if (tooManyDeletions) {
+            sb.append("D1");
+        }
+        if (tooManyRetries) {
+            sb.append("R1");
+        }
+        if (databaseError) {
+            sb.append("b1");
+        }
+        if (hasSoftError()) {
+            sb.append("x1");
+        }
+        if (syncAlreadyInProgress) {
+            sb.append("l1");
+        }
+        if (stats.numIoExceptions > 0) {
+            sb.append("I").append(stats.numIoExceptions);
+        }
+        return sb.toString();
+    }
+}
diff --git a/core/java/android/content/SyncStateContentProviderHelper.java b/core/java/android/content/SyncStateContentProviderHelper.java
new file mode 100644
index 0000000..f503e6f
--- /dev/null
+++ b/core/java/android/content/SyncStateContentProviderHelper.java
@@ -0,0 +1,234 @@
+/*
+ * Copyright (C) 2007 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 android.content;
+
+import com.android.internal.util.ArrayUtils;
+
+import android.database.Cursor;
+import android.database.DatabaseUtils;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteOpenHelper;
+import android.net.Uri;
+
+/**
+ * Extends the schema of a ContentProvider to include the _sync_state table
+ * and implements query/insert/update/delete to access that table using the
+ * authority "syncstate". This can be used to store the sync state for a
+ * set of accounts.
+ * 
+ * @hide
+ */
+public class SyncStateContentProviderHelper {
+    final SQLiteOpenHelper mOpenHelper;
+
+    private static final String SYNC_STATE_AUTHORITY = "syncstate";
+    private static final UriMatcher sURIMatcher = new UriMatcher(UriMatcher.NO_MATCH);
+
+    private static final int STATE = 0;
+
+    private static final Uri CONTENT_URI =
+            Uri.parse("content://" + SYNC_STATE_AUTHORITY + "/state");
+
+    private static final String ACCOUNT_WHERE = "_sync_account = ?";
+
+    private final Provider mInternalProviderInterface;
+
+    private static final String SYNC_STATE_TABLE = "_sync_state";
+    private static long DB_VERSION = 2;
+
+    private static final String[] ACCOUNT_PROJECTION = new String[]{"_sync_account"};
+
+    static {
+        sURIMatcher.addURI(SYNC_STATE_AUTHORITY, "state", STATE);
+    }
+
+    public SyncStateContentProviderHelper(SQLiteOpenHelper openHelper) {
+        mOpenHelper = openHelper;
+        mInternalProviderInterface = new Provider();
+    }
+
+    public ContentProvider asContentProvider() {
+        return mInternalProviderInterface;
+    }
+
+    public void createDatabase(SQLiteDatabase db) {
+        db.execSQL("DROP TABLE IF EXISTS _sync_state");
+        db.execSQL("CREATE TABLE _sync_state (" +
+                   "_id INTEGER PRIMARY KEY," +
+                   "_sync_account TEXT," +
+                   "data TEXT," +
+                   "UNIQUE(_sync_account)" +
+                   ");");
+
+        db.execSQL("DROP TABLE IF EXISTS _sync_state_metadata");
+        db.execSQL("CREATE TABLE _sync_state_metadata (" +
+                    "version INTEGER" +
+                    ");");
+        ContentValues values = new ContentValues();
+        values.put("version", DB_VERSION);
+        db.insert("_sync_state_metadata", "version", values);
+    }
+
+    protected void onDatabaseOpened(SQLiteDatabase db) {
+        long version = DatabaseUtils.longForQuery(db,
+                "select version from _sync_state_metadata", null);
+        if (version != DB_VERSION) {
+            createDatabase(db);
+        }
+    }
+
+    class Provider extends ContentProvider {
+        public boolean onCreate() {
+            throw new UnsupportedOperationException("not implemented");
+        }
+
+        public Cursor query(Uri url, String[] projection, String selection, String[] selectionArgs,
+                String sortOrder) {
+            SQLiteDatabase db = mOpenHelper.getReadableDatabase();
+            int match = sURIMatcher.match(url);
+            switch (match) {
+                case STATE:
+                    return db.query(SYNC_STATE_TABLE, projection, selection, selectionArgs,
+                            null, null, sortOrder);
+                default:
+                    throw new UnsupportedOperationException("Cannot query URL: " + url);
+            }
+        }
+
+        public String getType(Uri uri) {
+            throw new UnsupportedOperationException("not implemented");
+        }
+
+        public Uri insert(Uri url, ContentValues values) {
+            SQLiteDatabase db = mOpenHelper.getWritableDatabase();
+            int match = sURIMatcher.match(url);
+            switch (match) {
+                case STATE: {
+                    long id = db.insert(SYNC_STATE_TABLE, "feed", values);
+                    return CONTENT_URI.buildUpon().appendPath(String.valueOf(id)).build();
+                }
+                default:
+                    throw new UnsupportedOperationException("Cannot insert into URL: " + url);
+            }
+        }
+
+        public int delete(Uri url, String userWhere, String[] whereArgs) {
+            SQLiteDatabase db = mOpenHelper.getWritableDatabase();
+            switch (sURIMatcher.match(url)) {
+                case STATE:
+                    return db.delete(SYNC_STATE_TABLE, userWhere, whereArgs);
+                default:
+                    throw new IllegalArgumentException("Unknown URL " + url);
+            }
+
+        }
+
+        public int update(Uri url, ContentValues values, String selection, String[] selectionArgs) {
+            SQLiteDatabase db = mOpenHelper.getWritableDatabase();
+            switch (sURIMatcher.match(url)) {
+                case STATE:
+                    return db.update(SYNC_STATE_TABLE, values, selection, selectionArgs);
+                default:
+                    throw new UnsupportedOperationException("Cannot update URL: " + url);
+            }
+
+        }
+    }
+
+    /**
+     * Check if the url matches content that this ContentProvider manages.
+     * @param url the Uri to check
+     * @return true if this ContentProvider can handle that Uri.
+     */
+    public boolean matches(Uri url) {
+        return (SYNC_STATE_AUTHORITY.equals(url.getAuthority()));
+    }
+
+    /**
+     * Replaces the contents of the _sync_state table in the destination ContentProvider
+     * with the row that matches account, if any, in the source ContentProvider.
+     * <p>
+     * The ContentProviders must expose the _sync_state table as URI content://syncstate/state.
+     * @param dbSrc the database to read from
+     * @param dbDest the database to write to
+     * @param account the account of the row that should be copied over.
+     */
+    public void copySyncState(SQLiteDatabase dbSrc, SQLiteDatabase dbDest,
+            String account) {
+        final String[] whereArgs = new String[]{account};
+        Cursor c = dbSrc.query(SYNC_STATE_TABLE, new String[]{"_sync_account", "data"},
+                ACCOUNT_WHERE, whereArgs, null, null, null);
+        try {
+            if (c.moveToNext()) {
+                ContentValues values = new ContentValues();
+                values.put("_sync_account", c.getString(0));
+                values.put("data", c.getBlob(1));
+                dbDest.replace(SYNC_STATE_TABLE, "_sync_account", values);
+            }
+        } finally {
+            c.close();
+        }
+    }
+
+    public void onAccountsChanged(String[] accounts) {
+        SQLiteDatabase db = mOpenHelper.getWritableDatabase();
+        Cursor c = db.query(SYNC_STATE_TABLE, ACCOUNT_PROJECTION, null, null, null, null, null);
+        try {
+            while (c.moveToNext()) {
+                final String account = c.getString(0);
+                if (!ArrayUtils.contains(accounts, account)) {
+                    db.delete(SYNC_STATE_TABLE, ACCOUNT_WHERE, new String[]{account});
+                }
+            }
+        } finally {
+            c.close();
+        }
+    }
+
+    public void discardSyncData(SQLiteDatabase db, String account) {
+        if (account != null) {
+            db.delete(SYNC_STATE_TABLE, ACCOUNT_WHERE, new String[]{account});
+        } else {
+            db.delete(SYNC_STATE_TABLE, null, null);
+        }
+    }
+
+    /**
+     * Retrieves the SyncData bytes for the given account. The byte array returned may be null.
+     */
+    public byte[] readSyncDataBytes(SQLiteDatabase db, String account) {
+        Cursor c = db.query(SYNC_STATE_TABLE, null, ACCOUNT_WHERE,
+                new String[]{account}, null, null, null);
+        try {
+            if (c.moveToFirst()) {
+                return c.getBlob(c.getColumnIndexOrThrow("data"));
+            }
+        } finally {
+            c.close();
+        }
+        return null;
+    }
+
+    /**
+     * Sets the SyncData bytes for the given account. The bytes array may be null.
+     */
+    public void writeSyncDataBytes(SQLiteDatabase db, String account, byte[] data) {
+        ContentValues values = new ContentValues();
+        values.put("data", data);
+        db.update(SYNC_STATE_TABLE, values, ACCOUNT_WHERE, new String[]{account});
+    }
+}
diff --git a/core/java/android/content/SyncStats.aidl b/core/java/android/content/SyncStats.aidl
new file mode 100644
index 0000000..dff0ebf
--- /dev/null
+++ b/core/java/android/content/SyncStats.aidl
@@ -0,0 +1,19 @@
+/*
+ * 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 android.content;
+
+parcelable SyncStats;
diff --git a/core/java/android/content/SyncStats.java b/core/java/android/content/SyncStats.java
new file mode 100644
index 0000000..b561b05
--- /dev/null
+++ b/core/java/android/content/SyncStats.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2007 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 android.content;
+
+import android.os.Parcelable;
+import android.os.Parcel;
+
+/**
+ * @hide
+ */
+public class SyncStats implements Parcelable {
+    public long numAuthExceptions;
+    public long numIoExceptions;
+    public long numParseExceptions;
+    public long numConflictDetectedExceptions;
+    public long numInserts;
+    public long numUpdates;
+    public long numDeletes;
+    public long numEntries;
+    public long numSkippedEntries;
+
+    public SyncStats() {
+        numAuthExceptions = 0;
+        numIoExceptions = 0;
+        numParseExceptions = 0;
+        numConflictDetectedExceptions = 0;
+        numInserts = 0;
+        numUpdates = 0;
+        numDeletes = 0;
+        numEntries = 0;
+        numSkippedEntries = 0;
+    }
+
+    public SyncStats(Parcel in) {
+        numAuthExceptions = in.readLong();
+        numIoExceptions = in.readLong();
+        numParseExceptions = in.readLong();
+        numConflictDetectedExceptions = in.readLong();
+        numInserts = in.readLong();
+        numUpdates = in.readLong();
+        numDeletes = in.readLong();
+        numEntries = in.readLong();
+        numSkippedEntries = in.readLong();
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder sb = new StringBuilder();
+        sb.append("numAuthExceptions: ").append(numAuthExceptions);
+        sb.append(" numIoExceptions: ").append(numIoExceptions);
+        sb.append(" numParseExceptions: ").append(numParseExceptions);
+        sb.append(" numConflictDetectedExceptions: ").append(numConflictDetectedExceptions);
+        sb.append(" numInserts: ").append(numInserts);
+        sb.append(" numUpdates: ").append(numUpdates);
+        sb.append(" numDeletes: ").append(numDeletes);
+        sb.append(" numEntries: ").append(numEntries);
+        sb.append(" numSkippedEntries: ").append(numSkippedEntries);
+        return sb.toString();
+    }
+
+    public void clear() {
+        numAuthExceptions = 0;
+        numIoExceptions = 0;
+        numParseExceptions = 0;
+        numConflictDetectedExceptions = 0;
+        numInserts = 0;
+        numUpdates = 0;
+        numDeletes = 0;
+        numEntries = 0;
+        numSkippedEntries = 0;
+    }
+
+    public int describeContents() {
+        return 0;
+    }
+
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeLong(numAuthExceptions);
+        dest.writeLong(numIoExceptions);
+        dest.writeLong(numParseExceptions);
+        dest.writeLong(numConflictDetectedExceptions);
+        dest.writeLong(numInserts);
+        dest.writeLong(numUpdates);
+        dest.writeLong(numDeletes);
+        dest.writeLong(numEntries);
+        dest.writeLong(numSkippedEntries);
+    }
+
+    public static final Creator<SyncStats> CREATOR = new Creator<SyncStats>() {
+        public SyncStats createFromParcel(Parcel in) {
+            return new SyncStats(in);
+        }
+
+        public SyncStats[] newArray(int size) {
+            return new SyncStats[size];
+        }
+    };
+}
diff --git a/core/java/android/content/SyncStorageEngine.java b/core/java/android/content/SyncStorageEngine.java
new file mode 100644
index 0000000..282f6e7
--- /dev/null
+++ b/core/java/android/content/SyncStorageEngine.java
@@ -0,0 +1,758 @@
+package android.content;
+
+import android.Manifest;
+import android.database.Cursor;
+import android.database.DatabaseUtils;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteOpenHelper;
+import android.database.sqlite.SQLiteQueryBuilder;
+import android.net.Uri;
+import android.provider.Sync;
+import android.text.TextUtils;
+import android.util.Config;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+
+/**
+ * ContentProvider that tracks the sync data and overall sync
+ * history on the device.
+ * 
+ * @hide
+ */
+public class SyncStorageEngine {
+    private static final String TAG = "SyncManager";
+
+    private static final String DATABASE_NAME = "syncmanager.db";
+    private static final int DATABASE_VERSION = 10;
+
+    private static final int STATS = 1;
+    private static final int STATS_ID = 2;
+    private static final int HISTORY = 3;
+    private static final int HISTORY_ID = 4;
+    private static final int SETTINGS = 5;
+    private static final int PENDING = 7;
+    private static final int ACTIVE = 8;
+    private static final int STATUS = 9;
+
+    private static final UriMatcher sURLMatcher =
+            new UriMatcher(UriMatcher.NO_MATCH);
+
+    private static final HashMap<String,String> HISTORY_PROJECTION_MAP;
+    private static final HashMap<String,String> PENDING_PROJECTION_MAP;
+    private static final HashMap<String,String> ACTIVE_PROJECTION_MAP;
+    private static final HashMap<String,String> STATUS_PROJECTION_MAP;
+
+    private final Context mContext;
+    private final SQLiteOpenHelper mOpenHelper;
+    private static SyncStorageEngine sSyncStorageEngine = null;
+
+    static {
+        sURLMatcher.addURI("sync", "stats", STATS);
+        sURLMatcher.addURI("sync", "stats/#", STATS_ID);
+        sURLMatcher.addURI("sync", "history", HISTORY);
+        sURLMatcher.addURI("sync", "history/#", HISTORY_ID);
+        sURLMatcher.addURI("sync", "settings", SETTINGS);
+        sURLMatcher.addURI("sync", "status", STATUS);
+        sURLMatcher.addURI("sync", "active", ACTIVE);
+        sURLMatcher.addURI("sync", "pending", PENDING);
+
+        HashMap<String,String> map;
+        PENDING_PROJECTION_MAP = map = new HashMap<String,String>();
+        map.put(Sync.History._ID, Sync.History._ID);
+        map.put(Sync.History.ACCOUNT, Sync.History.ACCOUNT);
+        map.put(Sync.History.AUTHORITY, Sync.History.AUTHORITY);
+
+        ACTIVE_PROJECTION_MAP = map = new HashMap<String,String>();
+        map.put(Sync.History._ID, Sync.History._ID);
+        map.put(Sync.History.ACCOUNT, Sync.History.ACCOUNT);
+        map.put(Sync.History.AUTHORITY, Sync.History.AUTHORITY);
+        map.put("startTime", "startTime");
+
+        HISTORY_PROJECTION_MAP = map = new HashMap<String,String>();
+        map.put(Sync.History._ID, "history._id as _id");
+        map.put(Sync.History.ACCOUNT, "stats.account as account");
+        map.put(Sync.History.AUTHORITY, "stats.authority as authority");
+        map.put(Sync.History.EVENT, Sync.History.EVENT);
+        map.put(Sync.History.EVENT_TIME, Sync.History.EVENT_TIME);
+        map.put(Sync.History.ELAPSED_TIME, Sync.History.ELAPSED_TIME);
+        map.put(Sync.History.SOURCE, Sync.History.SOURCE);
+        map.put(Sync.History.UPSTREAM_ACTIVITY, Sync.History.UPSTREAM_ACTIVITY);
+        map.put(Sync.History.DOWNSTREAM_ACTIVITY, Sync.History.DOWNSTREAM_ACTIVITY);
+        map.put(Sync.History.MESG, Sync.History.MESG);
+
+        STATUS_PROJECTION_MAP = map = new HashMap<String,String>();
+        map.put(Sync.Status._ID, "status._id as _id");
+        map.put(Sync.Status.ACCOUNT, "stats.account as account");
+        map.put(Sync.Status.AUTHORITY, "stats.authority as authority");
+        map.put(Sync.Status.TOTAL_ELAPSED_TIME, Sync.Status.TOTAL_ELAPSED_TIME);
+        map.put(Sync.Status.NUM_SYNCS, Sync.Status.NUM_SYNCS);
+        map.put(Sync.Status.NUM_SOURCE_LOCAL, Sync.Status.NUM_SOURCE_LOCAL);
+        map.put(Sync.Status.NUM_SOURCE_POLL, Sync.Status.NUM_SOURCE_POLL);
+        map.put(Sync.Status.NUM_SOURCE_SERVER, Sync.Status.NUM_SOURCE_SERVER);
+        map.put(Sync.Status.NUM_SOURCE_USER, Sync.Status.NUM_SOURCE_USER);
+        map.put(Sync.Status.LAST_SUCCESS_SOURCE, Sync.Status.LAST_SUCCESS_SOURCE);
+        map.put(Sync.Status.LAST_SUCCESS_TIME, Sync.Status.LAST_SUCCESS_TIME);
+        map.put(Sync.Status.LAST_FAILURE_SOURCE, Sync.Status.LAST_FAILURE_SOURCE);
+        map.put(Sync.Status.LAST_FAILURE_TIME, Sync.Status.LAST_FAILURE_TIME);
+        map.put(Sync.Status.LAST_FAILURE_MESG, Sync.Status.LAST_FAILURE_MESG);
+        map.put(Sync.Status.PENDING, Sync.Status.PENDING);
+    }
+
+    private static final String[] STATS_ACCOUNT_PROJECTION =
+            new String[] { Sync.Stats.ACCOUNT };
+
+    private static final int MAX_HISTORY_EVENTS_TO_KEEP = 5000;
+
+    private static final String SELECT_INITIAL_FAILURE_TIME_QUERY_STRING = ""
+            + "SELECT min(a) "
+            + "FROM ("
+            + "  SELECT initialFailureTime AS a "
+            + "  FROM status "
+            + "  WHERE stats_id=? AND a IS NOT NULL "
+            + "    UNION "
+            + "  SELECT ? AS a"
+            + " )";
+
+    private SyncStorageEngine(Context context) {
+        mContext = context;
+        mOpenHelper = new SyncStorageEngine.DatabaseHelper(context);
+        sSyncStorageEngine = this;
+    }
+
+    public static SyncStorageEngine newTestInstance(Context context) {
+        return new SyncStorageEngine(context);
+    }
+
+    public static void init(Context context) {
+        if (sSyncStorageEngine != null) {
+            throw new IllegalStateException("already initialized");
+        }
+        sSyncStorageEngine = new SyncStorageEngine(context);
+    }
+
+    public static SyncStorageEngine getSingleton() {
+        if (sSyncStorageEngine == null) {
+            throw new IllegalStateException("not initialized");
+        }
+        return sSyncStorageEngine;
+    }
+
+    private class DatabaseHelper extends SQLiteOpenHelper {
+        DatabaseHelper(Context context) {
+            super(context, DATABASE_NAME, null, DATABASE_VERSION);
+        }
+
+        @Override
+        public void onCreate(SQLiteDatabase db) {
+            db.execSQL("CREATE TABLE pending ("
+                    + "_id INTEGER PRIMARY KEY,"
+                    + "authority TEXT NOT NULL,"
+                    + "account TEXT NOT NULL,"
+                    + "extras BLOB NOT NULL,"
+                    + "source INTEGER NOT NULL"
+                    + ");");
+
+            db.execSQL("CREATE TABLE stats (" +
+                       "_id INTEGER PRIMARY KEY," +
+                       "account TEXT, " +
+                       "authority TEXT, " +
+                       "syncdata TEXT, " +
+                       "UNIQUE (account, authority)" +
+                       ");");
+
+            db.execSQL("CREATE TABLE history (" +
+                       "_id INTEGER PRIMARY KEY," +
+                       "stats_id INTEGER," +
+                       "eventTime INTEGER," +
+                       "elapsedTime INTEGER," +
+                       "source INTEGER," +
+                       "event INTEGER," +
+                       "upstreamActivity INTEGER," +
+                       "downstreamActivity INTEGER," +
+                       "mesg TEXT);");
+
+            db.execSQL("CREATE TABLE status ("
+                    + "_id INTEGER PRIMARY KEY,"
+                    + "stats_id INTEGER NOT NULL,"
+                    + "totalElapsedTime INTEGER NOT NULL DEFAULT 0,"
+                    + "numSyncs INTEGER NOT NULL DEFAULT 0,"
+                    + "numSourcePoll INTEGER NOT NULL DEFAULT 0,"
+                    + "numSourceServer INTEGER NOT NULL DEFAULT 0,"
+                    + "numSourceLocal INTEGER NOT NULL DEFAULT 0,"
+                    + "numSourceUser INTEGER NOT NULL DEFAULT 0,"
+                    + "lastSuccessTime INTEGER,"
+                    + "lastSuccessSource INTEGER,"
+                    + "lastFailureTime INTEGER,"
+                    + "lastFailureSource INTEGER,"
+                    + "lastFailureMesg STRING,"
+                    + "initialFailureTime INTEGER,"
+                    + "pending INTEGER NOT NULL DEFAULT 0);");
+
+            db.execSQL("CREATE TABLE active ("
+                    + "_id INTEGER PRIMARY KEY,"
+                    + "authority TEXT,"
+                    + "account TEXT,"
+                    + "startTime INTEGER);");
+
+            db.execSQL("CREATE INDEX historyEventTime ON history (eventTime)");
+
+            db.execSQL("CREATE TABLE settings (" +
+                       "name TEXT PRIMARY KEY," +
+                       "value TEXT);");
+        }
+
+        @Override
+        public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
+            if (oldVersion == 9 && newVersion == 10) {
+                Log.w(TAG, "Upgrading database from version " + oldVersion + " to "
+                        + newVersion + ", which will preserve old data");
+                db.execSQL("ALTER TABLE status ADD COLUMN initialFailureTime INTEGER");
+                return;
+            }
+
+            Log.w(TAG, "Upgrading database from version " + oldVersion + " to "
+                    + newVersion + ", which will destroy all old data");
+            db.execSQL("DROP TABLE IF EXISTS pending");
+            db.execSQL("DROP TABLE IF EXISTS stats");
+            db.execSQL("DROP TABLE IF EXISTS history");
+            db.execSQL("DROP TABLE IF EXISTS settings");
+            db.execSQL("DROP TABLE IF EXISTS active");
+            db.execSQL("DROP TABLE IF EXISTS status");
+            onCreate(db);
+        }
+
+        @Override
+        public void onOpen(SQLiteDatabase db) {
+            if (!db.isReadOnly()) {
+                db.delete("active", null, null);
+                db.insert("active", "account", null);
+            }
+        }
+    }
+
+    protected void doDatabaseCleanup(String[] accounts) {
+        HashSet<String> currentAccounts = new HashSet<String>();
+        for (String account : accounts) currentAccounts.add(account);
+        SQLiteDatabase db = mOpenHelper.getWritableDatabase();
+        Cursor cursor = db.query("stats", STATS_ACCOUNT_PROJECTION,
+                null /* where */, null /* where args */, Sync.Stats.ACCOUNT,
+                null /* having */, null /* order by */);
+        try {
+            while (cursor.moveToNext()) {
+                String account = cursor.getString(0);
+                if (TextUtils.isEmpty(account)) {
+                    continue;
+                }
+                if (!currentAccounts.contains(account)) {
+                    String where = Sync.Stats.ACCOUNT + "=?";
+                    int numDeleted;
+                    numDeleted = db.delete("stats", where, new String[]{account});
+                    if (Config.LOGD) {
+                        Log.d(TAG, "deleted " + numDeleted
+                                + " records from stats table"
+                                + " for account " + account);
+                    }
+                }
+            }
+        } finally {
+            cursor.close();
+        }
+    }
+
+    protected void setActiveSync(SyncManager.ActiveSyncContext activeSyncContext) {
+        if (activeSyncContext != null) {
+            updateActiveSync(activeSyncContext.mSyncOperation.account,
+                    activeSyncContext.mSyncOperation.authority, activeSyncContext.mStartTime);
+        } else {
+            // we indicate that the sync is not active by passing null for all the parameters
+            updateActiveSync(null, null, null);
+        }
+    }
+
+    private int updateActiveSync(String account, String authority, Long startTime) {
+        SQLiteDatabase db = mOpenHelper.getWritableDatabase();
+        ContentValues values = new ContentValues();
+        values.put("account", account);
+        values.put("authority", authority);
+        values.put("startTime", startTime);
+        int numChanges = db.update("active", values, null, null);
+        if (numChanges > 0) {
+            mContext.getContentResolver().notifyChange(Sync.Active.CONTENT_URI,
+                    null /* this change wasn't made through an observer */);
+        }
+        return numChanges;
+    }
+
+    /**
+     * Implements the {@link ContentProvider#query} method
+     */
+    public Cursor query(Uri url, String[] projectionIn,
+            String selection, String[] selectionArgs, String sort) {
+        SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
+
+        // Generate the body of the query
+        int match = sURLMatcher.match(url);
+        String groupBy = null;
+        switch (match) {
+            case STATS:
+                qb.setTables("stats");
+                break;
+            case STATS_ID:
+                qb.setTables("stats");
+                qb.appendWhere("_id=");
+                qb.appendWhere(url.getPathSegments().get(1));
+                break;
+            case HISTORY:
+                // join the stats and history tables, so the caller can get
+                // the account and authority information as part of this query.
+                qb.setTables("stats, history");
+                qb.setProjectionMap(HISTORY_PROJECTION_MAP);
+                qb.appendWhere("stats._id = history.stats_id");
+                break;
+            case ACTIVE:
+                qb.setTables("active");
+                qb.setProjectionMap(ACTIVE_PROJECTION_MAP);
+                qb.appendWhere("account is not null");
+                break;
+            case PENDING:
+                qb.setTables("pending");
+                qb.setProjectionMap(PENDING_PROJECTION_MAP);
+                groupBy = "account, authority";
+                break;
+            case STATUS:
+                // join the stats and status tables, so the caller can get
+                // the account and authority information as part of this query.
+                qb.setTables("stats, status");
+                qb.setProjectionMap(STATUS_PROJECTION_MAP);
+                qb.appendWhere("stats._id = status.stats_id");
+                break;
+            case HISTORY_ID:
+                // join the stats and history tables, so the caller can get
+                // the account and authority information as part of this query.
+                qb.setTables("stats, history");
+                qb.setProjectionMap(HISTORY_PROJECTION_MAP);
+                qb.appendWhere("stats._id = history.stats_id");
+                qb.appendWhere("AND history._id=");
+                qb.appendWhere(url.getPathSegments().get(1));
+                break;
+            case SETTINGS:
+                qb.setTables("settings");
+                break;
+            default:
+                throw new IllegalArgumentException("Unknown URL " + url);
+        }
+
+        if (match == SETTINGS) {
+            mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_SYNC_SETTINGS,
+                    "no permission to read the sync settings");
+        } else {
+            mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_SYNC_STATS,
+                    "no permission to read the sync stats");
+        }
+        SQLiteDatabase db = mOpenHelper.getReadableDatabase();
+        Cursor c = qb.query(db, projectionIn, selection, selectionArgs, groupBy, null, sort);
+        c.setNotificationUri(mContext.getContentResolver(), url);
+        return c;
+    }
+
+    /**
+     * Implements the {@link ContentProvider#insert} method
+     * @param callerIsTheProvider true if this is being called via the
+     *  {@link ContentProvider#insert} in method rather than directly.
+     * @throws UnsupportedOperationException if callerIsTheProvider is true and the url isn't
+     *   for the Settings table.
+     */
+    public Uri insert(boolean callerIsTheProvider, Uri url, ContentValues values) {
+        String table;
+        long rowID;
+        SQLiteDatabase db = mOpenHelper.getWritableDatabase();
+        final int match = sURLMatcher.match(url);
+        checkCaller(callerIsTheProvider, match);
+        switch (match) {
+            case SETTINGS:
+                mContext.enforceCallingOrSelfPermission(Manifest.permission.WRITE_SYNC_SETTINGS,
+                        "no permission to write the sync settings");
+                table = "settings";
+                rowID = db.replace(table, null, values);
+                break;
+            default:
+                throw new IllegalArgumentException("Unknown URL " + url);
+        }
+
+
+        if (rowID > 0) {
+            mContext.getContentResolver().notifyChange(url, null /* observer */);
+            return Uri.parse("content://sync/" + table + "/" + rowID);
+        }
+
+        return null;
+    }
+
+    private static void checkCaller(boolean callerIsTheProvider, int match) {
+        if (callerIsTheProvider && match != SETTINGS) {
+            throw new UnsupportedOperationException(
+                    "only the settings are modifiable via the ContentProvider interface");
+        }
+    }
+
+    /**
+     * Implements the {@link ContentProvider#delete} method
+     * @param callerIsTheProvider true if this is being called via the
+     *  {@link ContentProvider#delete} in method rather than directly.
+     * @throws UnsupportedOperationException if callerIsTheProvider is true and the url isn't
+     *   for the Settings table.
+     */
+    public int delete(boolean callerIsTheProvider, Uri url, String where, String[] whereArgs) {
+        SQLiteDatabase db = mOpenHelper.getWritableDatabase();
+        int match = sURLMatcher.match(url);
+
+        int numRows;
+        switch (match) {
+            case SETTINGS:
+                mContext.enforceCallingOrSelfPermission(Manifest.permission.WRITE_SYNC_SETTINGS,
+                        "no permission to write the sync settings");
+                numRows = db.delete("settings", where, whereArgs);
+                break;
+            default:
+                throw new UnsupportedOperationException("Cannot delete URL: " + url);
+        }
+
+        if (numRows > 0) {
+            mContext.getContentResolver().notifyChange(url, null /* observer */);
+        }
+        return numRows;
+    }
+
+    /**
+     * Implements the {@link ContentProvider#update} method
+     * @param callerIsTheProvider true if this is being called via the
+     *  {@link ContentProvider#update} in method rather than directly.
+     * @throws UnsupportedOperationException if callerIsTheProvider is true and the url isn't
+     *   for the Settings table.
+     */
+    public int update(boolean callerIsTheProvider, Uri url, ContentValues initialValues,
+            String where, String[] whereArgs) {
+        switch (sURLMatcher.match(url)) {
+            case SETTINGS:
+                throw new UnsupportedOperationException("updating url " + url
+                        + " is not allowed, use insert instead");
+            default:
+                throw new UnsupportedOperationException("Cannot update URL: " + url);
+        }
+    }
+
+    /**
+     * Implements the {@link ContentProvider#getType} method
+     */
+    public String getType(Uri url) {
+        int match = sURLMatcher.match(url);
+        switch (match) {
+            case SETTINGS:
+                return "vnd.android.cursor.dir/sync-settings";
+            default:
+                throw new IllegalArgumentException("Unknown URL");
+        }
+    }
+
+    protected Uri insertIntoPending(ContentValues values) {
+        SQLiteDatabase db = mOpenHelper.getWritableDatabase();
+        try {
+            db.beginTransaction();
+            long rowId = db.insert("pending", Sync.Pending.ACCOUNT, values);
+            if (rowId < 0) return null;
+            String account = values.getAsString(Sync.Pending.ACCOUNT);
+            String authority = values.getAsString(Sync.Pending.AUTHORITY);
+
+            long statsId = createStatsRowIfNecessary(account, authority);
+            createStatusRowIfNecessary(statsId);
+
+            values.clear();
+            values.put(Sync.Status.PENDING, 1);
+            int numUpdatesStatus = db.update("status", values, "stats_id=" + statsId, null);
+
+            db.setTransactionSuccessful();
+
+            mContext.getContentResolver().notifyChange(Sync.Pending.CONTENT_URI,
+                    null /* no observer initiated this change */);
+            if (numUpdatesStatus > 0) {
+                mContext.getContentResolver().notifyChange(Sync.Status.CONTENT_URI,
+                        null /* no observer initiated this change */);
+            }
+            return ContentUris.withAppendedId(Sync.Pending.CONTENT_URI, rowId);
+        } finally {
+            db.endTransaction();
+        }
+    }
+
+    int deleteFromPending(long rowId) {
+        SQLiteDatabase db = mOpenHelper.getWritableDatabase();
+        db.beginTransaction();
+        try {
+            String account;
+            String authority;
+            Cursor c = db.query("pending",
+                    new String[]{Sync.Pending.ACCOUNT, Sync.Pending.AUTHORITY},
+                    "_id=" + rowId, null, null, null, null);
+            try {
+                if (c.getCount() != 1) {
+                    return 0;
+                }
+                c.moveToNext();
+                account = c.getString(0);
+                authority = c.getString(1);
+            } finally {
+                c.close();
+            }
+            db.delete("pending", "_id=" + rowId, null /* no where args */);
+            final String[] accountAuthorityWhereArgs = new String[]{account, authority};
+            boolean isPending = 0 < DatabaseUtils.longForQuery(db,
+                    "SELECT COUNT(*) FROM PENDING WHERE account=? AND authority=?",
+                    accountAuthorityWhereArgs);
+            if (!isPending) {
+                long statsId = createStatsRowIfNecessary(account, authority);
+                db.execSQL("UPDATE status SET pending=0 WHERE stats_id=" + statsId);
+            }
+            db.setTransactionSuccessful();
+
+            mContext.getContentResolver().notifyChange(Sync.Pending.CONTENT_URI,
+                    null /* no observer initiated this change */);
+            if (!isPending) {
+                mContext.getContentResolver().notifyChange(Sync.Status.CONTENT_URI,
+                        null /* no observer initiated this change */);
+            }
+            return 1;
+        } finally {
+            db.endTransaction();
+        }
+    }
+
+    int clearPending() {
+        SQLiteDatabase db = mOpenHelper.getWritableDatabase();
+        db.beginTransaction();
+        try {
+            int numChanges = db.delete("pending", null, null /* no where args */);
+            if (numChanges > 0) {
+                db.execSQL("UPDATE status SET pending=0");
+                mContext.getContentResolver().notifyChange(Sync.Pending.CONTENT_URI,
+                        null /* no observer initiated this change */);
+                mContext.getContentResolver().notifyChange(Sync.Status.CONTENT_URI,
+                        null /* no observer initiated this change */);
+            }
+            db.setTransactionSuccessful();
+            return numChanges;
+        } finally {
+            db.endTransaction();
+        }
+    }
+
+    /**
+     * Returns a cursor over all the pending syncs in no particular order. This cursor is not
+     * "live", in that if changes are made to the pending table any observers on this cursor
+     * will not be notified.
+     * @param projection Return only these columns. If null then all columns are returned.
+     * @return the cursor of pending syncs
+     */
+    public Cursor getPendingSyncsCursor(String[] projection) {
+        SQLiteDatabase db = mOpenHelper.getReadableDatabase();
+        return db.query("pending", projection, null, null, null, null, null);
+    }
+
+    // @VisibleForTesting
+    static final long MILLIS_IN_4WEEKS = 1000L * 60 * 60 * 24 * 7 * 4;
+
+    private boolean purgeOldHistoryEvents(long now) {
+        // remove events that are older than MILLIS_IN_4WEEKS
+        SQLiteDatabase db = mOpenHelper.getWritableDatabase();
+        int numDeletes = db.delete("history", "eventTime<" + (now - MILLIS_IN_4WEEKS), null);
+        if (Log.isLoggable(TAG, Log.VERBOSE)) {
+            if (numDeletes > 0) {
+                Log.v(TAG, "deleted " + numDeletes + " old event(s) from the sync history");
+            }
+        }
+
+        // keep only the last MAX_HISTORY_EVENTS_TO_KEEP history events
+        numDeletes += db.delete("history", "eventTime < (select min(eventTime) from "
+                + "(select eventTime from history order by eventTime desc limit ?))",
+                new String[]{String.valueOf(MAX_HISTORY_EVENTS_TO_KEEP)});
+        
+        return numDeletes > 0;
+    }
+
+    public long insertStartSyncEvent(String account, String authority, long now, int source) {
+        SQLiteDatabase db = mOpenHelper.getWritableDatabase();
+        long statsId = createStatsRowIfNecessary(account, authority);
+
+        purgeOldHistoryEvents(now);
+        ContentValues values = new ContentValues();
+        values.put(Sync.History.STATS_ID, statsId);
+        values.put(Sync.History.EVENT_TIME, now);
+        values.put(Sync.History.SOURCE, source);
+        values.put(Sync.History.EVENT, Sync.History.EVENT_START);
+        long rowId = db.insert("history", null, values);
+        mContext.getContentResolver().notifyChange(Sync.History.CONTENT_URI, null /* observer */);
+        mContext.getContentResolver().notifyChange(Sync.Status.CONTENT_URI, null /* observer */);
+        return rowId;
+    }
+
+    public void stopSyncEvent(long historyId, long elapsedTime, String resultMessage,
+            long downstreamActivity, long upstreamActivity) {
+        SQLiteDatabase db = mOpenHelper.getWritableDatabase();
+        db.beginTransaction();
+        try {
+            ContentValues values = new ContentValues();
+            values.put(Sync.History.ELAPSED_TIME, elapsedTime);
+            values.put(Sync.History.EVENT, Sync.History.EVENT_STOP);
+            values.put(Sync.History.MESG, resultMessage);
+            values.put(Sync.History.DOWNSTREAM_ACTIVITY, downstreamActivity);
+            values.put(Sync.History.UPSTREAM_ACTIVITY, upstreamActivity);
+
+            int count = db.update("history", values, "_id=?",
+                    new String[]{Long.toString(historyId)});
+            // We think that count should always be 1 but don't want to change this until after
+            // launch.
+            if (count > 0) {
+                int source = (int) DatabaseUtils.longForQuery(db,
+                        "SELECT source FROM history WHERE _id=" + historyId, null);
+                long eventTime = DatabaseUtils.longForQuery(db,
+                        "SELECT eventTime FROM history WHERE _id=" + historyId, null);
+                long statsId = DatabaseUtils.longForQuery(db,
+                        "SELECT stats_id FROM history WHERE _id=" + historyId, null);
+
+                createStatusRowIfNecessary(statsId);
+
+                // update the status table to reflect this sync
+                StringBuilder sb = new StringBuilder();
+                ArrayList<String> bindArgs = new ArrayList<String>();
+                sb.append("UPDATE status SET");
+                sb.append(" numSyncs=numSyncs+1");
+                sb.append(", totalElapsedTime=totalElapsedTime+" + elapsedTime);
+                switch (source) {
+                    case Sync.History.SOURCE_LOCAL:
+                        sb.append(", numSourceLocal=numSourceLocal+1");
+                        break;
+                    case Sync.History.SOURCE_POLL:
+                        sb.append(", numSourcePoll=numSourcePoll+1");
+                        break;
+                    case Sync.History.SOURCE_USER:
+                        sb.append(", numSourceUser=numSourceUser+1");
+                        break;
+                    case Sync.History.SOURCE_SERVER:
+                        sb.append(", numSourceServer=numSourceServer+1");
+                        break;
+                }
+
+                final String statsIdString = String.valueOf(statsId);
+                final long lastSyncTime = (eventTime + elapsedTime);
+                if (Sync.History.MESG_SUCCESS.equals(resultMessage)) {
+                    // - if successful, update the successful columns
+                    sb.append(", lastSuccessTime=" + lastSyncTime);
+                    sb.append(", lastSuccessSource=" + source);
+                    sb.append(", lastFailureTime=null");
+                    sb.append(", lastFailureSource=null");
+                    sb.append(", lastFailureMesg=null");
+                    sb.append(", initialFailureTime=null");
+                } else if (!Sync.History.MESG_CANCELED.equals(resultMessage)) {
+                    sb.append(", lastFailureTime=" + lastSyncTime);
+                    sb.append(", lastFailureSource=" + source);
+                    sb.append(", lastFailureMesg=?");
+                    bindArgs.add(resultMessage);
+                    long initialFailureTime = DatabaseUtils.longForQuery(db,
+                            SELECT_INITIAL_FAILURE_TIME_QUERY_STRING, 
+                            new String[]{statsIdString, String.valueOf(lastSyncTime)});
+                    sb.append(", initialFailureTime=" + initialFailureTime);
+                }
+                sb.append(" WHERE stats_id=?");
+                bindArgs.add(statsIdString);
+                db.execSQL(sb.toString(), bindArgs.toArray());
+                db.setTransactionSuccessful();
+                mContext.getContentResolver().notifyChange(Sync.History.CONTENT_URI,
+                        null /* observer */);
+                mContext.getContentResolver().notifyChange(Sync.Status.CONTENT_URI,
+                        null /* observer */);
+            }
+        } finally {
+            db.endTransaction();
+        }
+    }
+
+    /**
+     * If sync is failing for any of the provider/accounts then determine the time at which it
+     * started failing and return the earliest time over all the provider/accounts. If none are
+     * failing then return 0.
+     */
+    public long getInitialSyncFailureTime() {
+        SQLiteDatabase db = mOpenHelper.getReadableDatabase();
+        // Join the settings for a provider with the status so that we can easily
+        // check if each provider is enabled for syncing. We also join in the overall
+        // enabled flag ("listen_for_tickles") to each row so that we don't need to
+        // make a separate DB lookup to access it.
+        Cursor c = db.rawQuery(""
+                + "SELECT initialFailureTime, s1.value, s2.value "
+                + "FROM status "
+                + "LEFT JOIN stats ON status.stats_id=stats._id "
+                + "LEFT JOIN settings as s1 ON 'sync_provider_' || authority=s1.name "
+                + "LEFT JOIN settings as s2 ON s2.name='listen_for_tickles' "
+                + "where initialFailureTime is not null "
+                + "  AND lastFailureMesg!=" + Sync.History.ERROR_TOO_MANY_DELETIONS
+                + "  AND lastFailureMesg!=" + Sync.History.ERROR_AUTHENTICATION
+                + "  AND lastFailureMesg!=" + Sync.History.ERROR_SYNC_ALREADY_IN_PROGRESS
+                + "  AND authority!='subscribedfeeds' "
+                + " ORDER BY initialFailureTime", null);
+        try {
+            while (c.moveToNext()) {
+                // these settings default to true, so if they are null treat them as enabled
+                final String providerEnabledString = c.getString(1);
+                if (providerEnabledString != null && !Boolean.parseBoolean(providerEnabledString)) {
+                    continue;
+                }
+                final String allEnabledString = c.getString(2);
+                if (allEnabledString != null && !Boolean.parseBoolean(allEnabledString)) {
+                    continue;
+                }
+                return c.getLong(0);
+            }
+        } finally {
+            c.close();
+        }
+        return 0;
+    }
+
+    private void createStatusRowIfNecessary(long statsId) {
+        SQLiteDatabase db = mOpenHelper.getWritableDatabase();
+        boolean statusExists = 0 != DatabaseUtils.longForQuery(db,
+                "SELECT count(*) FROM status WHERE stats_id=" + statsId, null);
+        if (!statusExists) {
+            ContentValues values = new ContentValues();
+            values.put("stats_id", statsId);
+            db.insert("status", null, values);
+        }
+    }
+
+    private long createStatsRowIfNecessary(String account, String authority) {
+        SQLiteDatabase db = mOpenHelper.getWritableDatabase();
+        StringBuilder where = new StringBuilder();
+        where.append(Sync.Stats.ACCOUNT + "= ?");
+        where.append(" and " + Sync.Stats.AUTHORITY + "= ?");
+        Cursor cursor = query(Sync.Stats.CONTENT_URI,
+                Sync.Stats.SYNC_STATS_PROJECTION,
+                where.toString(), new String[] { account, authority },
+                null /* order */);
+        try {
+            long id;
+            if (cursor.moveToFirst()) {
+                id = cursor.getLong(cursor.getColumnIndexOrThrow(Sync.Stats._ID));
+            } else {
+                ContentValues values = new ContentValues();
+                values.put(Sync.Stats.ACCOUNT, account);
+                values.put(Sync.Stats.AUTHORITY, authority);
+                id = db.insert("stats", null, values);
+            }
+            return id;
+        } finally {
+            cursor.close();
+        }
+    }
+}
diff --git a/core/java/android/content/SyncUIContext.java b/core/java/android/content/SyncUIContext.java
new file mode 100644
index 0000000..6dde004
--- /dev/null
+++ b/core/java/android/content/SyncUIContext.java
@@ -0,0 +1,37 @@
+/*
+ * 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 android.content;
+
+/**
+ * Class with callback methods for SyncAdapters and ContentProviders
+ * that are called in response to the calls on SyncContext.  This class
+ * is really only meant to be used by the Sync UI activities.
+ *
+ * <p>All of the onXXX callback methods here are called from a handler
+ * on the thread this object was created in.
+ *
+ * <p>This interface is unused. It should be removed.
+ * 
+ * @hide
+ */
+@Deprecated
+public interface SyncUIContext {
+    
+    void setStatusText(String text);
+
+    Context context();
+}
diff --git a/core/java/android/content/SyncableContentProvider.java b/core/java/android/content/SyncableContentProvider.java
new file mode 100644
index 0000000..e0cd786
--- /dev/null
+++ b/core/java/android/content/SyncableContentProvider.java
@@ -0,0 +1,228 @@
+/*
+ * Copyright (C) 2007 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 android.content;
+
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+import android.net.Uri;
+
+import java.util.Map;
+
+/**
+ * A specialization of the ContentProvider that centralizes functionality
+ * used by ContentProviders that are syncable. It also wraps calls to the ContentProvider
+ * inside of database transactions.
+ *
+ * @hide
+ */
+public abstract class SyncableContentProvider extends ContentProvider {
+    protected abstract boolean isTemporary();
+
+    /**
+     * Close resources that must be closed. You must call this to properly release
+     * the resources used by the SyncableContentProvider.
+     */
+    public abstract void close();
+
+    /**
+     * Override to create your schema and do anything else you need to do with a new database.
+     * This is run inside a transaction (so you don't need to use one).
+     * This method may not use getDatabase(), or call content provider methods, it must only
+     * use the database handle passed to it.
+     */
+    protected abstract void bootstrapDatabase(SQLiteDatabase db);
+
+    /**
+     * Override to upgrade your database from an old version to the version you specified.
+     * Don't set the DB version, this will automatically be done after the method returns.
+     * This method may not use getDatabase(), or call content provider methods, it must only
+     * use the database handle passed to it.
+     *
+     * @param oldVersion version of the existing database
+     * @param newVersion current version to upgrade to
+     * @return true if the upgrade was lossless, false if it was lossy
+     */
+    protected abstract boolean upgradeDatabase(SQLiteDatabase db, int oldVersion, int newVersion);
+
+    /**
+     * Override to do anything (like cleanups or checks) you need to do after opening a database.
+     * Does nothing by default.  This is run inside a transaction (so you don't need to use one).
+     * This method may not use getDatabase(), or call content provider methods, it must only
+     * use the database handle passed to it.
+     */
+    protected abstract void onDatabaseOpened(SQLiteDatabase db);
+
+    /**
+     * Get a non-persistent instance of this content provider.
+     * You must call {@link #close} on the returned
+     * SyncableContentProvider when you are done with it.
+     *
+     * @return a non-persistent content provider with the same layout as this
+     * provider.
+     */
+    public abstract SyncableContentProvider getTemporaryInstance();
+
+    public abstract SQLiteDatabase getDatabase();
+
+    public abstract boolean getContainsDiffs();
+
+    public abstract void setContainsDiffs(boolean containsDiffs);
+
+    /**
+     * Each subclass of this class should define a subclass of {@link
+     * AbstractTableMerger} for each table they wish to merge.  It
+     * should then override this method and return one instance of
+     * each merger, in sequence.  Their {@link
+     * AbstractTableMerger#merge merge} methods will be called, one at a
+     * time, in the order supplied.
+     *
+     * <p>The default implementation returns an empty list, so that no
+     * merging will occur.
+     * @return A sequence of subclasses of {@link
+     * AbstractTableMerger}, one for each table that should be merged.
+     */
+    protected abstract Iterable<? extends AbstractTableMerger> getMergers();
+
+    /**
+     * Check if changes to this URI can be syncable changes.
+     * @param uri the URI of the resource that was changed
+     * @return true if changes to this URI can be syncable changes, false otherwise
+     */
+    public abstract boolean changeRequiresLocalSync(Uri uri);
+
+    /**
+     * Called right before a sync is started.
+     *
+     * @param context the sync context for the operation
+     * @param account
+     */
+    public abstract void onSyncStart(SyncContext context, String account);
+
+    /**
+     * Called right after a sync is completed
+     *
+     * @param context the sync context for the operation
+     * @param success true if the sync succeeded, false if an error occurred
+     */
+    public abstract void onSyncStop(SyncContext context, boolean success);
+
+    /**
+     * The account of the most recent call to onSyncStart()
+     * @return the account
+     */
+    public abstract String getSyncingAccount();
+
+    /**
+     * Merge diffs from a sync source with this content provider.
+     *
+     * @param context the SyncContext within which this merge is taking place
+     * @param diffs A temporary content provider containing diffs from a sync
+     *   source.
+     * @param result a MergeResult that contains information about the merge, including
+     *   a temporary content provider with the same layout as this provider containing
+     * @param syncResult
+     */
+    public abstract void merge(SyncContext context, SyncableContentProvider diffs,
+            TempProviderSyncResult result, SyncResult syncResult);
+
+
+    /**
+     * Invoked when the active sync has been canceled. The default
+     * implementation doesn't do anything (except ensure that this
+     * provider is syncable). Subclasses of ContentProvider
+     * that support canceling of sync should override this.
+     */
+    public abstract void onSyncCanceled();
+
+
+    public abstract boolean isMergeCancelled();
+
+    /**
+     * Subclasses should override this instead of update(). See update()
+     * for details.
+     *
+     * <p> This method is called within a acquireDbLock()/releaseDbLock() block,
+     * which means a database transaction will be active during the call;
+     */
+    protected abstract int updateInternal(Uri url, ContentValues values,
+            String selection, String[] selectionArgs);
+
+    /**
+     * Subclasses should override this instead of delete(). See delete()
+     * for details.
+     *
+     * <p> This method is called within a acquireDbLock()/releaseDbLock() block,
+     * which means a database transaction will be active during the call;
+     */
+    protected abstract int deleteInternal(Uri url, String selection, String[] selectionArgs);
+
+    /**
+     * Subclasses should override this instead of insert(). See insert()
+     * for details.
+     *
+     * <p> This method is called within a acquireDbLock()/releaseDbLock() block,
+     * which means a database transaction will be active during the call;
+     */
+    protected abstract Uri insertInternal(Uri url, ContentValues values);
+
+    /**
+     * Subclasses should override this instead of query(). See query()
+     * for details.
+     *
+     * <p> This method is *not* called within a acquireDbLock()/releaseDbLock()
+     * block for performance reasons. If an implementation needs atomic access
+     * to the database the lock can be acquired then.
+     */
+    protected abstract Cursor queryInternal(Uri url, String[] projection,
+            String selection, String[] selectionArgs, String sortOrder);
+
+    /**
+     * Make sure that there are no entries for accounts that no longer exist
+     * @param accountsArray the array of currently-existing accounts
+     */
+    protected abstract void onAccountsChanged(String[] accountsArray);
+
+    /**
+     * A helper method to delete all rows whose account is not in the accounts
+     * map. The accountColumnName is the name of the column that is expected
+     * to hold the account. If a row has an empty account it is never deleted.
+     *
+     * @param accounts a map of existing accounts
+     * @param table the table to delete from
+     * @param accountColumnName the name of the column that is expected
+     * to hold the account.
+     */
+    protected abstract void deleteRowsForRemovedAccounts(Map<String, Boolean> accounts,
+            String table, String accountColumnName);
+
+    /**
+     * Called when the sync system determines that this provider should no longer
+     * contain records for the specified account.
+     */
+    public abstract void wipeAccount(String account);
+
+    /**
+     * Retrieves the SyncData bytes for the given account. The byte array returned may be null.
+     */
+    public abstract byte[] readSyncDataBytes(String account);
+
+    /**
+     * Sets the SyncData bytes for the given account. The bytes array may be null.
+     */
+    public abstract void writeSyncDataBytes(String account, byte[] data);
+}
+
diff --git a/core/java/android/content/TempProviderSyncAdapter.java b/core/java/android/content/TempProviderSyncAdapter.java
new file mode 100644
index 0000000..eb3a5da
--- /dev/null
+++ b/core/java/android/content/TempProviderSyncAdapter.java
@@ -0,0 +1,550 @@
+package android.content;
+
+import android.database.SQLException;
+import android.os.Bundle;
+import android.os.Debug;
+import android.os.NetStat;
+import android.os.Parcelable;
+import android.os.Process;
+import android.os.SystemProperties;
+import android.text.TextUtils;
+import android.util.Config;
+import android.util.EventLog;
+import android.util.Log;
+import android.util.TimingLogger;
+
+/**
+ * @hide
+ */
+public abstract class TempProviderSyncAdapter extends SyncAdapter {
+    private static final String TAG = "Sync";
+
+    private static final int MAX_GET_SERVER_DIFFS_LOOP_COUNT = 20;
+    private static final int MAX_UPLOAD_CHANGES_LOOP_COUNT = 10;
+    private static final int NUM_ALLOWED_SIMULTANEOUS_DELETIONS = 5;
+    private static final long PERCENT_ALLOWED_SIMULTANEOUS_DELETIONS = 20;
+
+    private volatile SyncableContentProvider mProvider;
+    private volatile SyncThread mSyncThread = null;
+    private volatile boolean mProviderSyncStarted;
+    private volatile boolean mAdapterSyncStarted;
+    
+    public TempProviderSyncAdapter(SyncableContentProvider provider) {
+        super();
+        mProvider = provider;
+    }
+
+    /**
+     * Used by getServerDiffs() to track the sync progress for a given
+     * sync adapter. Implementations of SyncAdapter generally specialize
+     * this class in order to track specific data about that SyncAdapter's
+     * sync. If an implementation of SyncAdapter doesn't need to store
+     * any data for a sync it may use TrivialSyncData.
+     */
+    public static abstract class SyncData implements Parcelable {
+
+    }
+
+    public final void setContext(Context context) {
+        mContext = context;
+    }
+
+    /**
+     * Retrieve the Context this adapter is running in.  Only available
+     * once onSyncStarting() is called (not available from constructor).
+     */
+    final public Context getContext() {
+        return mContext;
+    }
+
+    /**
+     * Called right before a sync is started.
+     *
+     * @param context allows you to publish status and interact with the
+     * @param account the account to sync
+     * @param forced if true then the sync was forced
+     * @param result information to track what happened during this sync attempt
+     * @return true, if the sync was successfully started. One reason it can
+     *   fail to start is if there is no user configured on the device.
+     */
+    public abstract void onSyncStarting(SyncContext context, String account, boolean forced,
+            SyncResult result);
+
+    /**
+     * Called right after a sync is completed
+     *
+     * @param context allows you to publish status and interact with the
+     *                user during interactive syncs.
+     * @param success true if the sync suceeded, false if an error occured
+     */
+    public abstract void onSyncEnding(SyncContext context, boolean success);
+
+    /**
+     * Implement this to return true if the data in your content provider
+     * is read only.
+     */
+    public abstract boolean isReadOnly();
+
+    /**
+     * Get diffs from the server since the last completed sync and put them
+     * into a temporary provider.
+     *
+     * @param context allows you to publish status and interact with the
+     *                user during interactive syncs.
+     * @param syncData used to track the progress this client has made in syncing data
+     *   from the server
+     * @param tempProvider this is where the diffs should be stored
+     * @param extras any extra data describing the sync that is desired
+     * @param syncInfo sync adapter-specific data that is used during a single sync operation
+     * @param syncResult information to track what happened during this sync attempt
+     */
+    public abstract void getServerDiffs(SyncContext context,
+            SyncData syncData, SyncableContentProvider tempProvider,
+            Bundle extras, Object syncInfo, SyncResult syncResult);
+
+    /**
+     * Send client diffs to the server, optionally receiving more diffs from the server
+     *
+     * @param context allows you to publish status and interact with the
+     *                user during interactive syncs.
+     * @param clientDiffs the diffs from the client
+     * @param serverDiffs the SyncableContentProvider that should be populated with
+*   the entries that were returned in response to an insert/update/delete request
+*   to the server
+     * @param syncResult information to track what happened during this sync attempt
+     * @param dontActuallySendDeletes
+     */
+    public abstract void sendClientDiffs(SyncContext context,
+            SyncableContentProvider clientDiffs,
+            SyncableContentProvider serverDiffs, SyncResult syncResult,
+            boolean dontActuallySendDeletes);
+
+    /**
+     * Reads the sync data from the ContentProvider
+     * @param contentProvider the ContentProvider to read from
+     * @return the SyncData for the provider. This may be null.
+     */
+    public SyncData readSyncData(SyncableContentProvider contentProvider) {
+        return null;
+    }
+
+    /**
+     * Create and return a new, empty SyncData object
+     */
+    public SyncData newSyncData() {
+        return null;
+    }
+
+    /**
+     * Stores the sync data in the Sync Stats database, keying it by
+     * the account that was set in the last call to onSyncStarting()
+     */
+    public void writeSyncData(SyncData syncData, SyncableContentProvider contentProvider) {}
+
+    /**
+     * Indicate to the SyncAdapter that the last sync that was started has
+     * been cancelled.
+     */
+    public abstract void onSyncCanceled();
+
+    /**
+     * Initializes the temporary content providers used during
+     * {@link TempProviderSyncAdapter#sendClientDiffs}.
+     * May copy relevant data from the underlying db into this provider so
+     * joins, etc., can work.
+     *
+     * @param cp The ContentProvider to initialize.
+     */
+    protected void initTempProvider(SyncableContentProvider cp) {}
+
+    protected Object createSyncInfo() {
+        return null;
+    }
+
+    /**
+     * Called when the accounts list possibly changed, to give the
+     * SyncAdapter a chance to do any necessary bookkeeping, e.g.
+     * to make sure that any required SubscribedFeeds subscriptions
+     * exist.
+     * @param accounts the list of accounts
+     */
+    public abstract void onAccountsChanged(String[] accounts);
+
+    private Context mContext;
+
+    private class SyncThread extends Thread {
+        private final String mAccount;
+        private final Bundle mExtras;
+        private final SyncContext mSyncContext;
+        private volatile boolean mIsCanceled = false;
+        private long mInitialTxBytes;
+        private long mInitialRxBytes;
+        private final SyncResult mResult;
+
+        SyncThread(SyncContext syncContext, String account, Bundle extras) {
+            super("SyncThread");
+            mAccount = account;
+            mExtras = extras;
+            mSyncContext = syncContext;
+            mResult = new SyncResult();
+        }
+
+        void cancelSync() {
+            mIsCanceled = true;
+            if (mAdapterSyncStarted) onSyncCanceled();
+            if (mProviderSyncStarted) mProvider.onSyncCanceled();
+            // We may lose the last few sync events when canceling.  Oh well.
+            int uid = Process.myUid();
+            logSyncDetails(NetStat.getUidTxBytes(uid) - mInitialTxBytes,
+                    NetStat.getUidRxBytes(uid) - mInitialRxBytes, mResult);
+        }
+        
+        @Override
+        public void run() {
+            Process.setThreadPriority(Process.myTid(),
+                    Process.THREAD_PRIORITY_BACKGROUND);
+            int uid = Process.myUid();
+            mInitialTxBytes = NetStat.getUidTxBytes(uid);
+            mInitialRxBytes = NetStat.getUidRxBytes(uid);
+            try {
+                sync(mSyncContext, mAccount, mExtras);
+            } catch (SQLException e) {
+                Log.e(TAG, "Sync failed", e);
+                mResult.databaseError = true;
+            } finally {
+                mSyncThread = null;
+                if (!mIsCanceled) {
+                    logSyncDetails(NetStat.getUidTxBytes(uid) - mInitialTxBytes,
+                    NetStat.getUidRxBytes(uid) - mInitialRxBytes, mResult);
+                    mSyncContext.onFinished(mResult);
+                }
+            }
+        }
+
+        private void sync(SyncContext syncContext, String account, Bundle extras) {
+            mIsCanceled = false;
+
+            mProviderSyncStarted = false;
+            mAdapterSyncStarted = false;
+            String message = null;
+
+            boolean syncForced = extras.getBoolean(ContentResolver.SYNC_EXTRAS_FORCE, false);
+
+            try {
+                mProvider.onSyncStart(syncContext, account);
+                mProviderSyncStarted = true;
+                onSyncStarting(syncContext, account, syncForced, mResult);
+                if (mResult.hasError()) {
+                    message = "SyncAdapter failed while trying to start sync";
+                    return;
+                }
+                mAdapterSyncStarted = true;
+                if (mIsCanceled) {
+                    return;
+                }
+                final String syncTracingEnabledValue = SystemProperties.get(TAG + "Tracing");
+                final boolean syncTracingEnabled = !TextUtils.isEmpty(syncTracingEnabledValue);
+                try {
+                    if (syncTracingEnabled) {
+                        System.gc();
+                        System.gc();
+                        Debug.startMethodTracing("synctrace." + System.currentTimeMillis());
+                    }
+                    runSyncLoop(syncContext, account, extras);
+                } finally {
+                    if (syncTracingEnabled) Debug.stopMethodTracing();
+                }
+                onSyncEnding(syncContext, !mResult.hasError());
+                mAdapterSyncStarted = false;
+                mProvider.onSyncStop(syncContext, true);
+                mProviderSyncStarted = false;
+            } finally {
+                if (mAdapterSyncStarted) {
+                    mAdapterSyncStarted = false;
+                    onSyncEnding(syncContext, false);
+                }
+                if (mProviderSyncStarted) {
+                    mProviderSyncStarted = false;
+                    mProvider.onSyncStop(syncContext, false);
+                }
+                if (!mIsCanceled) {
+                    if (message != null) syncContext.setStatusText(message);
+                }
+            }
+        }
+
+        private void runSyncLoop(SyncContext syncContext, String account, Bundle extras) {
+            TimingLogger syncTimer = new TimingLogger(TAG + "Profiling", "sync");
+            syncTimer.addSplit("start");
+            int loopCount = 0;
+            boolean tooManyGetServerDiffsAttempts = false;
+
+            final boolean overrideTooManyDeletions =
+                    extras.getBoolean(ContentResolver.SYNC_EXTRAS_OVERRIDE_TOO_MANY_DELETIONS,
+                            false);
+            final boolean discardLocalDeletions =
+                    extras.getBoolean(ContentResolver.SYNC_EXTRAS_DISCARD_LOCAL_DELETIONS, false);
+            boolean uploadOnly = extras.getBoolean(ContentResolver.SYNC_EXTRAS_UPLOAD,
+                    false /* default this flag to false */);
+            SyncableContentProvider serverDiffs = null;
+            TempProviderSyncResult result = new TempProviderSyncResult();
+            try {
+                if (!uploadOnly) {
+                    /**
+                     * This loop repeatedly calls SyncAdapter.getServerDiffs()
+                     * (to get changes from the feed) followed by
+                     * ContentProvider.merge() (to incorporate these changes
+                     * into the provider), stopping when the SyncData returned
+                     * from getServerDiffs() indicates that all the data was
+                     * fetched.
+                     */
+                    while (!mIsCanceled) {
+                        // Don't let a bad sync go forever
+                        if (loopCount++ == MAX_GET_SERVER_DIFFS_LOOP_COUNT) {
+                            Log.e(TAG, "runSyncLoop: Hit max loop count while getting server diffs "
+                                    + getClass().getName());
+                            // TODO: change the structure here to schedule a new sync
+                            // with a backoff time, keeping track to be sure
+                            // we don't keep doing this forever (due to some bug or
+                            // mismatch between the client and the server)
+                            tooManyGetServerDiffsAttempts = true;
+                            break;
+                        }
+
+                        // Get an empty content provider to put the diffs into
+                        if (serverDiffs != null) serverDiffs.close();
+                        serverDiffs = mProvider.getTemporaryInstance();
+
+                        // Get records from the server which will be put into the serverDiffs
+                        initTempProvider(serverDiffs);
+                        Object syncInfo = createSyncInfo();
+                        SyncData syncData = readSyncData(serverDiffs);
+                        // syncData will only be null if there was a demarshalling error
+                        // while reading the sync data.
+                        if (syncData == null) {
+                            mProvider.wipeAccount(account);
+                            syncData = newSyncData();
+                        }
+                        mResult.clear();
+                        if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                            Log.v(TAG, "runSyncLoop: running getServerDiffs using syncData "
+                                    + syncData.toString());
+                        }
+                        getServerDiffs(syncContext, syncData, serverDiffs, extras, syncInfo,
+                                mResult);
+
+                        if (mIsCanceled) return;
+                        if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                            Log.v(TAG, "runSyncLoop: result: " + mResult);
+                        }
+                        if (mResult.hasError()) return;
+                        if (mResult.partialSyncUnavailable) {
+                            if (Config.LOGD) {
+                                Log.d(TAG, "partialSyncUnavailable is set, setting "
+                                        + "ignoreSyncData and retrying");
+                            }
+                            mProvider.wipeAccount(account);
+                            continue;
+                        }
+
+                        // write the updated syncData back into the temp provider
+                        writeSyncData(syncData, serverDiffs);
+
+                        // apply the downloaded changes to the provider
+                        if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                            Log.v(TAG, "runSyncLoop: running merge");
+                        }
+                        mProvider.merge(syncContext, serverDiffs,
+                                null /* don't return client diffs */, mResult);
+                        if (mIsCanceled) return;
+                        if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                            Log.v(TAG, "runSyncLoop: result: " + mResult);
+                        }
+
+                        // if the server has no more changes then break out of the loop
+                        if (!mResult.moreRecordsToGet) {
+                            if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                                Log.v(TAG, "runSyncLoop: fetched all data, moving on");
+                            }
+                            break;
+                        }
+                        if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                            Log.v(TAG, "runSyncLoop: more data to fetch, looping");
+                        }
+                    }
+                }
+
+                /**
+                 * This loop repeatedly calls ContentProvider.merge() followed
+                 * by SyncAdapter.merge() until either indicate that there is
+                 * no more work to do by returning null.
+                 * <p>
+                 * The initial ContentProvider.merge() returns a temporary
+                 * ContentProvider that contains any local changes that need
+                 * to be committed to the server.
+                 * <p>
+                 * The SyncAdapter.merge() calls upload the changes to the server
+                 * and populates temporary provider (the serverDiffs) with the
+                 * result.
+                 * <p>
+                 * Subsequent calls to ContentProvider.merge() incoporate the
+                 * result of previous SyncAdapter.merge() calls into the
+                 * real ContentProvider and again return a temporary
+                 * ContentProvider that contains any local changes that need
+                 * to be committed to the server.
+                 */
+                loopCount = 0;
+                boolean readOnly = isReadOnly();
+                long previousNumModifications = 0;
+                if (serverDiffs != null) {
+                    serverDiffs.close();
+                    serverDiffs = null;
+                }
+
+                // If we are discarding local deletions then we need to redownload all the items
+                // again (since some of them might have been deleted). We do this by deleting the
+                // sync data for the current account by writing in a null one.
+                if (discardLocalDeletions) {
+                    serverDiffs = mProvider.getTemporaryInstance();
+                    initTempProvider(serverDiffs);
+                    writeSyncData(null, serverDiffs);
+                }
+
+                while (!mIsCanceled) {
+                    if (Config.LOGV) {
+                        Log.v(TAG, "runSyncLoop: Merging diffs from server to client");
+                    }
+                    if (result.tempContentProvider != null) {
+                        result.tempContentProvider.close();
+                        result.tempContentProvider = null;
+                    }
+                    mResult.clear();
+                    mProvider.merge(syncContext, serverDiffs, readOnly ? null : result,
+                            mResult);
+                    if (mIsCanceled) return;
+                    if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                        Log.v(TAG, "runSyncLoop: result: " + mResult);
+                    }
+
+                    SyncableContentProvider clientDiffs =
+                            readOnly ? null : result.tempContentProvider;
+                    if (clientDiffs == null) {
+                        // Nothing to commit back to the server
+                        if (Config.LOGV) Log.v(TAG, "runSyncLoop: No client diffs");
+                        break;
+                    }
+
+                    long numModifications = mResult.stats.numUpdates
+                            + mResult.stats.numDeletes
+                            + mResult.stats.numInserts;
+
+                    // as long as we are making progress keep resetting the loop count
+                    if (numModifications < previousNumModifications) {
+                        loopCount = 0;
+                    }
+                    previousNumModifications = numModifications;
+
+                    // Don't let a bad sync go forever
+                    if (loopCount++ >= MAX_UPLOAD_CHANGES_LOOP_COUNT) {
+                        Log.e(TAG, "runSyncLoop: Hit max loop count while syncing "
+                                + getClass().getName());
+                        mResult.tooManyRetries = true;
+                        break;
+                    }
+
+                    if (!overrideTooManyDeletions && !discardLocalDeletions
+                            && hasTooManyDeletions(mResult.stats)) {
+                        if (Config.LOGD) {
+                            Log.d(TAG, "runSyncLoop: Too many deletions were found in provider "
+                                    + getClass().getName() + ", not doing any more updates");
+                        }
+                        long numDeletes = mResult.stats.numDeletes;
+                        mResult.stats.clear();
+                        mResult.tooManyDeletions = true;
+                        mResult.stats.numDeletes = numDeletes;
+                        break;
+                    }
+
+                    if (Config.LOGV) Log.v(TAG, "runSyncLoop: Merging diffs from client to server");
+                    if (serverDiffs != null) serverDiffs.close();
+                    serverDiffs = clientDiffs.getTemporaryInstance();
+                    initTempProvider(serverDiffs);
+                    mResult.clear();
+                    sendClientDiffs(syncContext, clientDiffs, serverDiffs, mResult,
+                            discardLocalDeletions);
+                    if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                        Log.v(TAG, "runSyncLoop: result: " + mResult);
+                    }
+
+                    if (!mResult.madeSomeProgress()) {
+                        if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                            Log.v(TAG, "runSyncLoop: No data from client diffs merge");
+                        }
+                        break;
+                    }
+                    if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                        Log.v(TAG, "runSyncLoop: made some progress, looping");
+                    }
+                }
+
+                // add in any status codes that we saved from earlier
+                mResult.tooManyRetries |= tooManyGetServerDiffsAttempts;
+                if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                    Log.v(TAG, "runSyncLoop: final result: " + mResult);
+                }
+            } finally {
+                // do this in the finally block to guarantee that is is set and not overwritten
+                if (discardLocalDeletions) {
+                    mResult.fullSyncRequested = true;
+                }
+                if (serverDiffs != null) serverDiffs.close();
+                if (result.tempContentProvider != null) result.tempContentProvider.close();
+                syncTimer.addSplit("stop");
+                syncTimer.dumpToLog();
+            }
+        }
+    }
+
+    /**
+     * Logs details on the sync.
+     * Normally this will be overridden by a subclass that will provide
+     * provider-specific details.
+     * 
+     * @param bytesSent number of bytes the sync sent over the network
+     * @param bytesReceived number of bytes the sync received over the network
+     * @param result The SyncResult object holding info on the sync
+     */
+    protected void logSyncDetails(long bytesSent, long bytesReceived, SyncResult result) {
+        EventLog.writeEvent(SyncAdapter.LOG_SYNC_DETAILS, TAG, bytesSent, bytesReceived, "");
+    }
+
+    public void startSync(SyncContext syncContext, String account, Bundle extras) {
+        if (mSyncThread != null) {
+            syncContext.onFinished(SyncResult.ALREADY_IN_PROGRESS);
+            return;
+        }
+
+        mSyncThread = new SyncThread(syncContext, account, extras);
+        mSyncThread.start();
+    }
+
+    public void cancelSync() {
+        if (mSyncThread != null) {
+            mSyncThread.cancelSync();
+        }
+    }
+
+    protected boolean hasTooManyDeletions(SyncStats stats) {
+        long numEntries = stats.numEntries;
+        long numDeletedEntries = stats.numDeletes;
+
+        long percentDeleted = (numDeletedEntries == 0)
+                ? 0
+                : (100 * numDeletedEntries /
+                        (numEntries + numDeletedEntries));
+        boolean tooManyDeletions =
+                (numDeletedEntries > NUM_ALLOWED_SIMULTANEOUS_DELETIONS)
+                && (percentDeleted > PERCENT_ALLOWED_SIMULTANEOUS_DELETIONS);
+        return tooManyDeletions;
+    }
+}
diff --git a/core/java/android/content/TempProviderSyncResult.java b/core/java/android/content/TempProviderSyncResult.java
new file mode 100644
index 0000000..81f6f79
--- /dev/null
+++ b/core/java/android/content/TempProviderSyncResult.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2007 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 android.content;
+
+/**
+ * Used to hold data returned from a given phase of a TempProviderSync.
+ * @hide
+ */
+public class TempProviderSyncResult {
+    /**
+     * An interface to a temporary content provider that contains
+     * the result of updates that were sent to the server. This
+     * provider must be merged into the permanent content provider.
+     * This may be null, which indicates that there is nothing to
+     * merge back into the content provider.
+     */
+    public SyncableContentProvider tempContentProvider;
+
+    public TempProviderSyncResult() {
+        tempContentProvider = null;
+    }
+}
diff --git a/core/java/android/content/UriMatcher.java b/core/java/android/content/UriMatcher.java
new file mode 100644
index 0000000..a98e6d5
--- /dev/null
+++ b/core/java/android/content/UriMatcher.java
@@ -0,0 +1,262 @@
+/*
+ * 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 android.content;
+
+import android.net.Uri;
+
+import java.util.ArrayList;
+import java.util.regex.Pattern;
+
+/**
+Utility class to aid in matching URIs in content providers.
+
+<p>To use this class, build up a tree of UriMatcher objects.
+Typically, it looks something like this:
+<pre>
+    private static final int PEOPLE = 1;
+    private static final int PEOPLE_ID = 2;
+    private static final int PEOPLE_PHONES = 3;
+    private static final int PEOPLE_PHONES_ID = 4;
+    private static final int PEOPLE_CONTACTMETHODS = 7;
+    private static final int PEOPLE_CONTACTMETHODS_ID = 8;
+
+    private static final int DELETED_PEOPLE = 20;
+
+    private static final int PHONES = 9;
+    private static final int PHONES_ID = 10;
+    private static final int PHONES_FILTER = 14;
+
+    private static final int CONTACTMETHODS = 18;
+    private static final int CONTACTMETHODS_ID = 19;
+
+    private static final int CALLS = 11;
+    private static final int CALLS_ID = 12;
+    private static final int CALLS_FILTER = 15;
+
+    private static final UriMatcher sURIMatcher = new UriMatcher();
+
+    static
+    {
+        sURIMatcher.addURI("contacts", "/people", PEOPLE);
+        sURIMatcher.addURI("contacts", "/people/#", PEOPLE_ID);
+        sURIMatcher.addURI("contacts", "/people/#/phones", PEOPLE_PHONES);
+        sURIMatcher.addURI("contacts", "/people/#/phones/#", PEOPLE_PHONES_ID);
+        sURIMatcher.addURI("contacts", "/people/#/contact_methods", PEOPLE_CONTACTMETHODS);
+        sURIMatcher.addURI("contacts", "/people/#/contact_methods/#", PEOPLE_CONTACTMETHODS_ID);
+        sURIMatcher.addURI("contacts", "/deleted_people", DELETED_PEOPLE);
+        sURIMatcher.addURI("contacts", "/phones", PHONES);
+        sURIMatcher.addURI("contacts", "/phones/filter/*", PHONES_FILTER);
+        sURIMatcher.addURI("contacts", "/phones/#", PHONES_ID);
+        sURIMatcher.addURI("contacts", "/contact_methods", CONTACTMETHODS);
+        sURIMatcher.addURI("contacts", "/contact_methods/#", CONTACTMETHODS_ID);
+        sURIMatcher.addURI("call_log", "/calls", CALLS);
+        sURIMatcher.addURI("call_log", "/calls/filter/*", CALLS_FILTER);
+        sURIMatcher.addURI("call_log", "/calls/#", CALLS_ID);
+    }
+</pre>
+<p>Then when you need to match match against a URI, call {@link #match}, providing
+the tokenized url you've been given, and the value you want if there isn't
+a match.  You can use the result to build a query, return a type, insert or
+delete a row, or whatever you need, without duplicating all of the if-else
+logic you'd otherwise need.  Like this:
+<pre>
+    public String getType(String[] url)
+    {
+        int match = sURIMatcher.match(url, NO_MATCH);
+        switch (match)
+        {
+            case PEOPLE:
+                return "vnd.android.cursor.dir/person";
+            case PEOPLE_ID:
+                return "vnd.android.cursor.item/person";
+... snip ...
+                return "vnd.android.cursor.dir/snail-mail";
+            case PEOPLE_ADDRESS_ID:
+                return "vnd.android.cursor.item/snail-mail";
+            default:
+                return null;
+        }
+    }
+</pre>
+instead of
+<pre>
+    public String getType(String[] url)
+    {
+        if (url.length >= 2) {
+            if (url[1].equals("people")) {
+                if (url.length == 2) {
+                    return "vnd.android.cursor.dir/person";
+                } else if (url.length == 3) {
+                    return "vnd.android.cursor.item/person";
+... snip ...
+                    return "vnd.android.cursor.dir/snail-mail";
+                } else if (url.length == 3) {
+                    return "vnd.android.cursor.item/snail-mail";
+                }
+            }
+        }
+        return null;
+    }
+</pre>
+*/
+public class UriMatcher
+{
+    public static final int NO_MATCH = -1;
+    /**
+     * Creates the root node of the URI tree.
+     *
+     * @param code the code to match for the root URI
+     */
+    public UriMatcher(int code)
+    {
+        mCode = code;
+        mWhich = -1;
+        mChildren = new ArrayList<UriMatcher>();
+        mText = null;
+    }
+
+    private UriMatcher()
+    {
+        mCode = NO_MATCH;
+        mWhich = -1;
+        mChildren = new ArrayList<UriMatcher>();
+        mText = null;
+    }
+
+    /**
+     * Add a URI to match, and the code to return when this URI is
+     * matched. URI nodes may be exact match string, the token "*"
+     * that matches any text, or the token "#" that matches only
+     * numbers.
+     *
+     * @param authority the authority to match
+     * @param path the path to match. * may be used as a wild card for
+     * any text, and # may be used as a wild card for numbers.
+     * @param code the code that is returned when a URI is matched
+     * against the given components. Must be positive.
+     */
+    public void addURI(String authority, String path, int code)
+    {
+        if (code < 0) {
+            throw new IllegalArgumentException("code " + code + " is invalid: it must be positive");
+        }
+        String[] tokens = path != null ? PATH_SPLIT_PATTERN.split(path) : null;
+        int numTokens = tokens != null ? tokens.length : 0;
+        UriMatcher node = this;
+        for (int i = -1; i < numTokens; i++) {
+            String token = i < 0 ? authority : tokens[i];
+            ArrayList<UriMatcher> children = node.mChildren;
+            int numChildren = children.size();
+            UriMatcher child;
+            int j;
+            for (j = 0; j < numChildren; j++) {
+                child = children.get(j);
+                if (token.equals(child.mText)) {
+                    node = child;
+                    break;
+                }
+            }
+            if (j == numChildren) {
+                // Child not found, create it
+                child = new UriMatcher();
+                if (token.equals("#")) {
+                    child.mWhich = NUMBER;
+                } else if (token.equals("*")) {
+                    child.mWhich = TEXT;
+                } else {
+                    child.mWhich = EXACT;
+                }
+                child.mText = token;
+                node.mChildren.add(child);
+                node = child;
+            }
+        }
+        node.mCode = code;
+    }
+
+    static final Pattern PATH_SPLIT_PATTERN = Pattern.compile("/");
+
+    /**
+     * Try to match against the path in a url.
+     *
+     * @param uri       The url whose path we will match against.
+     *
+     * @return  The code for the matched node (added using addURI), 
+     * or -1 if there is no matched node.
+     */
+    public int match(Uri uri)
+    {
+        final int li = uri.getPathSegments().size();
+
+        UriMatcher node = this;
+
+        if (li == 0 && uri.getAuthority() == null) {
+            return this.mCode;
+        }
+
+        for (int i=-1; i<li; i++) {
+            String u = i < 0 ? uri.getAuthority() : uri.getPathSegments().get(i);
+            ArrayList<UriMatcher> list = node.mChildren;
+            if (list == null) {
+                break;
+            }
+            node = null;
+            int lj = list.size();
+            for (int j=0; j<lj; j++) {
+                UriMatcher n = list.get(j);
+          which_switch:
+                switch (n.mWhich) {
+                    case EXACT:
+                        if (n.mText.equals(u)) {
+                            node = n;
+                        }
+                        break;
+                    case NUMBER:
+                        int lk = u.length();
+                        for (int k=0; k<lk; k++) {
+                            char c = u.charAt(k);
+                            if (c < '0' || c > '9') {
+                                break which_switch;
+                            }
+                        }
+                        node = n;
+                        break;
+                    case TEXT:
+                        node = n;
+                        break;
+                }
+                if (node != null) {
+                    break;
+                }
+            }
+            if (node == null) {
+                return NO_MATCH;
+            }
+        }
+
+        return node.mCode;
+    }
+
+    private static final int EXACT = 0;
+    private static final int NUMBER = 1;
+    private static final int TEXT = 2;
+
+    private int mCode;
+    private int mWhich;
+    private String mText;
+    private ArrayList<UriMatcher> mChildren;
+}
diff --git a/core/java/android/content/package.html b/core/java/android/content/package.html
new file mode 100644
index 0000000..dd5360f
--- /dev/null
+++ b/core/java/android/content/package.html
@@ -0,0 +1,650 @@
+<html>
+<head>
+<script type="text/javascript" src="http://www.corp.google.com/style/prettify.js"></script>
+<script src="http://www.corp.google.com/eng/techpubs/include/navbar.js" type="text/javascript"></script>
+</head>
+
+<body>
+
+<p>Contains classes for accessing and publishing data
+on the device.  It includes three main categories of APIs:
+the {@link android.content.res.Resources Resources} for
+retrieving resource data associated with an application;
+{@link android.content.ContentProvider Content Providers} and
+{@link android.content.ContentResolver ContentResolver} for managing and
+publishing persistent data associated with an application; and
+the {@link android.content.pm.PackageManager Package Manager}
+for finding out information about the application packages installed
+on the device.</p>
+
+<p>In addition, the {@link android.content.Context Context} abstract class
+is a base API for pulling these pieces together, allowing you to access
+an application's resources and transfer data between applications.</p>
+
+<p>This package builds on top of the lower-level Android packages
+{@link android.database}, {@link android.text},
+{@link android.graphics.drawable}, {@link android.graphics},
+{@link android.os}, and {@link android.util}.</p>
+
+<ol>
+	<li> <a href="#Resources">Resources</a>
+		<ol>
+			<li> <a href="#ResourcesTerminology">Terminology</a>
+			<li> <a href="#ResourcesQuickStart">Examples</a>
+				<ol>
+					<li> <a href="#UsingSystemResources">Using System Resources</a>
+					<li> <a href="#StringResources">String Resources</a>
+					<li> <a href="#ColorResources">Color Resources</a>
+					<li> <a href="#DrawableResources">Drawable Resources</a>
+					<li> <a href="#LayoutResources">Layout Resources</a>
+					<li> <a href="#ReferencesToResources">References to Resources</a>
+					<li> <a href="#ReferencesToThemeAttributes">References to Theme Attributes</a>
+					<li> <a href="#StyleResources">Style Resources</a>
+					<li> <a href="#StylesInLayoutResources">Styles in Layout Resources</a>                    
+				</ol>
+		</ol>
+</ol>
+
+<a name="Resources"></a>
+<h2>Resources</h2>
+
+<p>This topic includes a terminology list associated with resources, and a series
+    of examples of using resources in code. For a complete guide on creating and
+    using resources, see the document on <a href="{@docRoot}guide/topics/resources/resources-i18n.html">Resources
+    and Internationalization</a>.  For a reference on the supported Android resource types,
+    see <a href="{@docRoot}guide/topics/resources/available-resources.html">Available Resource Types</a>.</p>
+<p>The Android resource system keeps track of all non-code
+    assets associated with an application.  You use the
+    {@link android.content.res.Resources Resources} class to access your
+    application's resources; the Resources instance associated with your
+    application can generally be found through
+    {@link android.content.Context#getResources Context.getResources()}.</p>
+<p>An application's resources are compiled into the application
+binary at build time for you by the build system.  To use a resource,
+you must install it correctly in the source tree and build your
+application.  As part of the build process, Java symbols for each
+of the resources are generated that you can use in your source
+code -- this allows the compiler to verify that your application code matches
+up with the resources you defined.</p>
+
+<p>The rest of this section is organized as a tutorial on how to
+use resources in an application.</p>
+
+<a name="ResourcesTerminology"></a>
+<h3>Terminology</h3>
+
+<p>The resource system brings a number of different pieces together to
+form the final complete resource functionality.  To help understand the
+overall system, here are some brief definitions of the core concepts and
+components you will encounter in using it:</p>
+
+<p><b>Asset</b>: A single blob of data associated with an application.  This
+includes Java object files, graphics (such as PNG images), XML files, etc.
+These files are organized in a directory hierarchy that, during final packaging
+of the application, is bundled together into a single ZIP file.</p>
+
+<p><b>aapt</b>: The tool that generates the final ZIP file of application
+assets.  In addition to collecting raw assets together, it also parses
+resource definitions into binary asset data.</p>
+
+<p><b>Resource Table</b>: A special asset that aapt generates for you,
+describing all of the resources contained in an application/package.
+This file is accessed for you by the Resources class; it is not touched
+directly by applications.</p>
+
+<p><b>Resource</b>: An entry in the Resource Table describing a single
+named value.  Broadly, there are two types of resources: primitives and
+bags.</p>
+
+<p><b>Resource Identifier</b>: In the Resource Table all resources are
+identified by a unique integer number.  In source code (resource descriptions,
+XML files, Java code) you can use symbolic names that stand as constants for
+the actual resource identifier integer.</p>
+
+<p><b>Primitive Resource</b>: All primitive resources can be written as a
+simple string, using formatting to describe a variety of primitive types
+included in the resource system: integers, colors, strings, references to
+other resources, etc.  Complex resources, such as bitmaps and XML
+describes, are stored as a primitive string resource whose value is the path
+of the underlying Asset holding its actual data.</p>
+
+<p><b>Bag Resource</b>: A special kind of resource entry that, instead of a
+simple string, holds an arbitrary list of name/value pairs.  Each name is
+itself a resource identifier, and each value can hold
+the same kinds of string formatted data as a normal resource.  Bags also
+support inheritance: a bag can inherit the values from another bag, selectively
+replacing or extending them to generate its own contents.</p>
+
+<p><b>Kind</b>: The resource kind is a way to organize resource identifiers
+for various purposes.  For example, drawable resources are used to
+instantiate Drawable objects, so their data is a primitive resource containing
+either a color constant or string path to a bitmap or XML asset.  Other
+common resource kinds are string (localized string primitives), color
+(color primitives), layout (a string path to an XML asset describing a view
+layout), and style (a bag resource describing user interface attributes).
+There is also a standard "attr" resource kind, which defines the resource
+identifiers to be used for naming bag items and XML attributes</p>
+
+<p><b>Style</b>: The name of the resource kind containing bags that are used
+to supply a set of user interface attributes.  For example, a TextView class may
+be given a style resource that defines its text size, color, and alignment.
+In a layout XML file, you associate a style with a bag using the "style"
+attribute, whose value is the name of the style resource.</p>
+
+<p><b>Style Class</b>: Specifies a related set of attribute resources.
+This data is not placed in the resource table itself, but used to generate
+Java constants that make it easier for you to retrieve values out of
+a style resource and/or XML tag's attributes.  For example, the
+Android platform defines a "View" style class that
+contains all of the standard view attributes: padding, visibility,
+background, etc.; when View is inflated it uses this style class to
+retrieve those values from the XML file (at which point style and theme
+information is applied as approriate) and load them into its instance.</p>
+
+<p><b>Configuration</b>: For any particular resource identifier, there may be
+multiple different available values depending on the current configuration.
+The configuration includes the locale (language and country), screen
+orientation, screen density, etc.  The current configuration is used to
+select which resource values are in effect when the resource table is
+loaded.</p>
+
+<p><b>Theme</b>: A standard style resource that supplies global
+attribute values for a particular context.  For example, when writing a
+Activity the application developer can select a standard theme to use, such
+as the Theme.White or Theme.Black styles; this style supplies information
+such as the screen background image/color, default text color, button style,
+text editor style, text size, etc.  When inflating a layout resource, most
+values for widgets (the text color, selector, background) if not explicitly
+set will come from the current theme; style and attribute
+values supplied in the layout can also assign their value from explicitly
+named values in the theme attributes if desired.</p>
+
+<p><b>Overlay</b>: A resource table that does not define a new set of resources,
+but instead replaces the values of resources that are in another resource table.
+Like a configuration, this is applied at load time
+to the resource data; it can add new configuration values (for example
+strings in a new locale), replace existing values (for example change
+the standard white background image to a "Hello Kitty" background image),
+and modify resource bags (for example change the font size of the Theme.White
+style to have an 18 pt font size).  This is the facility that allows the
+user to select between different global appearances of their device, or
+download files with new appearances.</p>
+
+<a name="ResourcesQuickStart"></a>
+<h3>Examples</h3>
+
+<p>This section gives a few quick examples you can use to make your own resources.
+    For more details on how to define and use resources, see <a
+    href="{@docRoot}guide/topics/resources/resources-i18n.html">Resources and 
+    Internationalization</a>. </p>
+
+<a name="UsingSystemResources"></a>
+<h4>Using System Resources</h4>
+
+<p>Many resources included with the system are available to applications.
+All such resources are defined under the class "android.R".  For example,
+you can display the standard application icon in a screen with the following
+code:</p>
+
+<pre class="prettyprint">
+public class MyActivity extends Activity
+{
+    public void onStart() 
+    {
+        requestScreenFeatures(FEATURE_BADGE_IMAGE);
+
+        super.onStart();
+
+        setBadgeResource(android.R.drawable.sym_def_app_icon);
+    }
+}
+</pre>
+
+<p>In a similar way, this code will apply to your screen the standard
+"green background" visual treatment defined by the system:</p>
+
+<pre class="prettyprint">
+public class MyActivity extends Activity
+{
+    public void onStart() 
+    {
+        super.onStart();
+
+        setTheme(android.R.style.Theme_Black);
+    }
+}
+</pre>
+
+<a name="StringResources"></a>
+<h4>String Resources</h4>
+
+<p>String resources are defined using an XML resource description syntax.
+The file or multiple files containing these resources can be given any name
+(as long as it has a .xml suffix) and placed at an appropriate location in
+the source tree for the desired configuration (locale/orientation/density).
+
+<p>Here is a simple resource file describing a few strings:</p>
+
+<pre>
+&lt;?xml version="1.0" encoding="utf-8"?&gt;
+&lt;resources&gt;
+    &lt;string id="mainLabel"&gt;Hello &lt;u&gt;th&lt;ignore&gt;e&lt;/ignore&gt;re&lt;/u&gt;, &lt;i&gt;you&lt;/i&gt; &lt;b&gt;Activity&lt;/b&gt;!&lt;/string&gt;
+    &lt;string id="back"&gt;Back&lt;/string&gt;
+    &lt;string id="clear"&gt;Clear&lt;/string&gt;
+&lt;/resources&gt;
+</pre>
+
+<p>Typically this file will be called "strings.xml", and must be placed
+in the <code>values</code> directory:</p>
+
+<pre>
+MyApp/res/values/strings.xml
+</pre>
+
+<p>The strings can now be retrieved by your application through the
+symbol specified in the "id" attribute:</p>
+
+<pre class="prettyprint">
+public class MyActivity extends Activity
+{
+    public void onStart() 
+    {
+        super.onStart();
+
+        String back = getResources().getString(R.string.back).toString();
+        back = getString(R.string.back).toString();  // synonym
+    }
+}
+</pre>
+
+<p>Unlike system resources, the resource symbol (the R class) we are using 
+here comes from our own application's package, not android.R.</p>
+
+<p>Note that the "mainLabel" string is complex, including style information.
+To support this, the <code>getString()</code> method returns a
+<code>CharSequence</code> object that you can pass to a
+<code>TextView</code> to retain those style.  This is why code
+must call <code>toString()</code> on the returned resource if it wants
+a raw string.</p>
+
+<a name="ColorResources"></a>
+<h4>Color Resources</h4>
+
+<p>Color resources are created in a way very similar to string resources,
+but with the &lt;color&gt; resource tag.  The data for these resources
+must be a hex color constant of the form "#rgb", "#argb", "#rrggbb", or
+"#aarrggbb".  The alpha channel is 0xff (or 0xf) for opaque and 0
+for transparent.</p>
+
+<pre>
+&lt;?xml version="1.0" encoding="utf-8"?&gt;
+&lt;resources&gt;
+    &lt;color id="opaque_red"&gt;#ffff0000&lt;/color&gt;
+    &lt;color id="transparent_red"&gt;#80ff0000&lt;/color&gt;
+    &lt;color id="opaque_blue"&gt;#0000ff&lt;/color&gt;
+    &lt;color id="opaque_green"&gt;#0f0&lt;/color&gt;
+&lt;/resources&gt;
+</pre>
+
+<p>While color definitions could be placed in the same resource file
+as the previously shown string data, usually you will place the colors in
+their own file:</p>
+
+<pre>
+MyApp/res/values/colors.xml
+</pre>
+
+<p>The colors can now be retrieved by your application through the
+symbol specified in the "id" attribute:</p>
+
+<pre class="prettyprint">
+public class MyActivity extends Activity
+{
+    public void onStart() 
+    {
+        super.onStart();
+
+        int red = getResources().getColor(R.color.opaque_red);
+    }
+}
+</pre>
+
+<a name="DrawableResources"></a>
+<h4>Drawable Resources</h4>
+
+<p>For simple drawable resources, all you need to do is place your
+image in a special resource sub-directory called "drawable".  Files here
+are things that can be handled by an implementation of the
+{@link android.graphics.drawable.Drawable Drawable} class, often bitmaps
+(such as PNG images) but also various kinds of XML descriptions
+for selectors, gradients, etc.</p>
+
+<p>The drawable files will be scanned by the
+resource tool, automatically generating a resource entry for each found.
+For example the file <code>res/drawable/&lt;myimage&gt;.&lt;ext&gt;</code>
+will result in a resource symbol named "myimage" (without the extension).  Note
+that these file names <em>must</em> be valid Java identifiers, and should
+have only lower-case letters.</p>
+
+<p>For example, to use your own custom image as a badge in a screen,
+you can place the image here:</p>
+
+<pre>
+MyApp/res/drawable/my_badge.png
+</pre>
+
+<p>The image can then be used in your code like this:</p>
+
+<pre class="prettyprint">
+public class MyActivity extends Activity
+{
+    public void onStart() 
+    {
+        requestScreenFeatures(FEATURE_BADGE_IMAGE);
+
+        super.onStart();
+
+        setBadgeResource(R.drawable.my_badge);
+    }
+}
+</pre>
+
+<p>For drawables that are a single solid color, you can also define them
+in a resource file very much like colors shown previously.  The only
+difference is that here we use the &lt;drawable&gt; tag to create a
+drawable resource.</p>
+
+<pre>
+&lt;?xml version="1.0" encoding="utf-8"?&gt;
+&lt;resources&gt;
+    &lt;drawable id="opaque_red"&gt;#ffff0000&lt;/drawable&gt;
+    &lt;drawable id="transparent_red"&gt;#80ff0000&lt;/drawable&gt;
+    &lt;drawable id="opaque_blue"&gt;#0000ff&lt;/drawable&gt;
+    &lt;drawable id="opaque_green"&gt;#0f0&lt;/drawable&gt;
+&lt;/resources&gt;
+</pre>
+
+<p>These resource entries are often placed in the same resource file
+as color definitions:</p>
+
+<pre>
+MyApp/res/values/colors.xml
+</pre>
+
+<a name="LayoutResources"></a>
+<h4>Layout Resources</h4>
+
+<p>Layout resources describe a view hierarchy configuration that is
+generated at runtime.  These resources are XML files placed in the
+resource directory "layout", and are how you should create the content
+views inside of your screen (instead of creating them by hand) so that
+they can be themed, styled, configured, and overlayed.</p>
+
+<p>Here is a simple layout resource consisting of a single view, a text
+editor:</p>
+
+<pre>
+&lt;?xml version="1.0" encoding="utf-8"?&gt;
+&lt;root&gt;
+    &lt;EditText id="text"
+        android:layout_width="fill-parent" android:layout_height="fill-parent"
+        android:text="Hello, World!" /&gt;
+&lt;/root&gt;
+</pre>
+
+<p>To use this layout, it can be placed in a file like this:</p>
+
+<pre>
+MyApp/res/layout/my_layout.xml
+</pre>
+
+<p>The layout can then be instantiated in your screen like this:</p>
+
+<pre class="prettyprint">
+public class MyActivity extends Activity
+{
+    public void onStart() 
+    {
+        super.onStart();
+        setContentView(R.layout.my_layout);
+    }
+}
+</pre>
+
+<p>Note that there are a number of visual attributes that can be supplied
+to TextView (including textSize, textColor, and textStyle) that we did
+not define in the previous example; in such a sitation, the default values for
+those attributes come from the theme.  If we want to customize them, we
+can supply them explicitly in the XML file:</p>
+
+<pre>
+&lt;?xml version="1.0" encoding="utf-8"?&gt;
+&lt;root&gt;
+    &lt;EditText id="text"
+        android:layout_width="fill_parent" android:layout_height="fill_parent"
+        <b>android:textSize="18" android:textColor="#008"</b>
+        android:text="Hello, World!" /&gt;
+&lt;/root&gt;
+</pre>
+
+<p>However, usually these kinds of attributes (those being attributes that
+usually make sense to vary with theme or overlay) should be defined through
+the theme or separate style resource.  Later we will see how this is done.</p>
+
+<a name="ReferencesToResources"></a>
+<h4>References to Resources</h4>
+
+<p>A value supplied in an attribute (or resource) can also be a reference to
+a resource.  This is often used in layout files to supply strings (so they
+can be localized) and images (which exist in another file), though a reference
+can be do any resource type including colors and integers.</p>
+
+<p>For example, if we have the previously defined color resources, we can
+write a layout file that sets the text color size to be the value contained in
+one of those resources:</p>
+
+<pre>
+&lt;?xml version="1.0" encoding="utf-8"?&gt;
+&lt;root&gt;
+    &lt;EditText id="text"
+        android:layout_width="fill_parent" android:layout_height="fill_parent"
+        <b>android:textColor="@color/opaque_red"</b>
+        android:text="Hello, World!" /&gt;
+&lt;/root&gt;
+</pre>
+
+<p>Note here the use of the '@' prefix to introduce a resource reference -- the
+text following that is the name of a resource in the form
+of <code>@[package:]type/name</code>.  In this case we didn't need to specify
+the package because we are referencing a resource in our own package.  To
+reference a system resource, you would need to write:</p>
+
+<pre>
+&lt;?xml version="1.0" encoding="utf-8"?&gt;
+&lt;root&gt;
+    &lt;EditText id="text"
+        android:layout_width="fill_parent" android:layout_height="fill_parent"
+        android:textColor="@<b>android:</b>color/opaque_red"
+        android:text="Hello, World!" /&gt;
+&lt;/root&gt;
+</pre>
+
+<p>As another example, you should always use resource references when supplying
+strings in a layout file so that they can be localized:</p>
+
+<pre>
+&lt;?xml version="1.0" encoding="utf-8"?&gt;
+&lt;root&gt;
+    &lt;EditText id="text"
+        android:layout_width="fill_parent" android:layout_height="fill_parent"
+        android:textColor="@android:color/opaque_red"
+        android:text="@string/hello_world" /&gt;
+&lt;/root&gt;
+</pre>
+
+<p>This facility can also be used to create references between resources.
+For example, we can create new drawable resources that are aliases for
+existing images:</p>
+
+<pre>
+&lt;?xml version="1.0" encoding="utf-8"?&gt;
+&lt;resources&gt;
+    &lt;drawable id="my_background"&gt;@android:drawable/theme2_background&lt;/drawable&gt;
+&lt;/resources&gt;
+</pre>
+
+<a name="ReferencesToThemeAttributes"></a>
+<h4>References to Theme Attributes</h4>
+
+<p>Another kind of resource value allows you to reference the value of an
+attribute in the current theme.  This attribute reference can <em>only</em>
+be used in style resources and XML attributes; it allows you to customize the
+look of UI elements by changing them to standard variations supplied by the
+current theme, instead of supplying more concrete values.</p>
+
+<p>As an example, we can use this in our layout to set the text color to
+one of the standard colors defined in the base system theme:</p>
+
+<pre>
+&lt;?xml version="1.0" encoding="utf-8"?&gt;
+&lt;root&gt;
+    &lt;EditText id="text"
+        android:layout_width="fill_parent" android:layout_height="fill_parent"
+        <b>android:textColor="?android:textDisabledColor"</b>
+        android:text="@string/hello_world" /&gt;
+&lt;/root&gt;
+</pre>
+
+<p>Note that this is very similar to a resource reference, except we are using
+an '?' prefix instead of '@'.  When you use this markup, you are supplying
+the name of an attribute resource that will be looked up in the theme --
+because the resource tool knows that an attribute resource is expected,
+you do not need to explicitly state the type (which would be
+<code>?android:attr/android:textDisabledColor</code>).</p>
+
+<p>Other than using this resource identifier to find the value in the
+theme instead of raw resources, the name syntax is identical to the '@' format:
+<code>?[package:]type/name</code> with the type here being optional.</p>
+
+<a name="StyleResources"></a>
+<h4>Style Resources</h4>
+
+<p>A style resource is a set of name/value pairs describing a group
+of related attributes.  There are two main uses for these resources:
+defining overall visual themes, and describing a set of visual attributes
+to apply to a class in a layout resource.  In this section we will look
+at their use to describe themes; later we will look at using them in
+conjunction with layouts.</p>
+
+<p>Like strings, styles are defined through a resource XML file.  In the
+situation where we want to define a new theme, we can create a custom theme
+style that inherits from one of the standard system themes:</p>
+
+<pre>
+&lt;?xml version="1.0" encoding="utf-8"?&gt;
+&lt;resources&gt;
+    &lt;style id="Theme" parent="android:Theme.White"&gt;
+        &lt;item id="android:foregroundColor"&gt;#FFF8D96F&lt;/item&gt;
+        &lt;item id="android:textColor"&gt;@color/opaque_blue&lt;/item&gt;
+        &lt;item id="android:textSelectedColor"&gt;?android:textColor&lt;/item&gt;
+    &lt;/style&gt;
+&lt;/resources&gt;
+</pre>
+
+<p>Typically these resource definitions will be placed in a file
+called "styles.xml" , and must be placed in the <code>values</code>
+directory:</p>
+
+<pre>
+MyApp/res/values/styles.xml
+</pre>
+
+<p>Similar to how we previously used a system style for an Activity theme,
+you can apply this style to your Activity:</p>
+
+<pre class="prettyprint">
+public class MyActivity extends Activity
+{
+    public void onStart() 
+    {
+        super.onStart();
+
+        setTheme(R.style.Theme);
+    }
+}
+</pre>
+
+<p>In the style resource shown here, we used the <code>parent</code>
+attribute to specify another style resource from which it inherits
+its values -- in this case the <code>Theme.White</code> system resource:</p>
+
+<pre>
+    &lt;style id="Home" parent="android:Theme.White"&gt;
+        ...
+    &lt;/style&gt;
+</pre>
+
+<p>Note, when doing this, that you must use the "android" prefix in front
+to tell the compiler the namespace to look in for the resource --
+the resources you are specifying here are in your application's namespace,
+not the system.  This explicit namespace specification ensures that names
+the application uses will not accidentally conflict with those defined by
+the system.</p>
+
+<p>If you don't specify an explicit parent style, it will be inferred
+from the style name -- everything before the final '.' in the name of the
+style being defined is taken as the parent style name.  Thus, to make
+another style in your application that inherits from this base Theme style,
+you can write:</p>
+
+<pre>
+&lt;?xml version="1.0" encoding="utf-8"?&gt;
+&lt;resources&gt;
+    &lt;style id="Theme.WhiteText"&gt;
+        &lt;item id="android:foregroundColor"&gt;#FFFFFFFF&lt;/item&gt;
+        &lt;item id="android:textColor"&gt;?android:foregroundColor&lt;/item&gt;
+    &lt;/style&gt;
+&lt;/resources&gt;
+</pre>
+
+<p>This results in the symbol <code>R.style.Theme_WhiteText</code> that
+can be used in Java just like we did with <code>R.style.Theme</code>
+above.</p>
+
+<a name="StylesInLayoutResources"></a>
+<h4>Styles in Layout Resources</h4>
+
+<p>Often you will have a number fo views in a layout that all use the same
+set of attributes, or want to allow resource overlays to modify the values of
+attributes.  Style resources can be used for both of these purposes, to put
+attribute definitions in a single place that can be references by multiple
+XML tags and modified by overlays.  To do this, you simply define a
+new style resource with the desired values:</p>
+
+<pre>
+&lt;?xml version="1.0" encoding="utf-8"?&gt;
+&lt;resources&gt;
+    &lt;style id="SpecialText"&gt;
+        &lt;item id="android:textSize"&gt;18&lt;/item&gt;
+        &lt;item id="android:textColor"&gt;#008&lt;/item&gt;
+    &lt;/style&gt;
+&lt;/resources&gt;
+</pre>
+
+<p>You can now apply this style to your TextView in the XML file:</p>
+
+<pre>
+&lt;?xml version="1.0" encoding="utf-8"?&gt;
+&lt;root&gt;
+    &lt;EditText id="text1" <b>style="@style/SpecialText"</b>
+        android:layout_width="fill_parent" android:layout_height="wrap_content"
+        android:text="Hello, World!" /&gt;
+    &lt;EditText id="text2" <b>style="@style/SpecialText"</b>
+        android:layout_width="fill_parent" android:layout_height="wrap_content"
+        android:text="I love you all." /&gt;
+&lt;/root&gt;</pre>
+<h4>&nbsp;</h4>
+
+</body>
+</html>
+
diff --git a/core/java/android/content/pm/ActivityInfo.aidl b/core/java/android/content/pm/ActivityInfo.aidl
new file mode 100755
index 0000000..dd90302
--- /dev/null
+++ b/core/java/android/content/pm/ActivityInfo.aidl
@@ -0,0 +1,20 @@
+/* //device/java/android/android/view/WindowManager.aidl
+**
+** Copyright 2007, 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 android.content.pm;
+
+parcelable ActivityInfo;
diff --git a/core/java/android/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java
new file mode 100644
index 0000000..85d877a
--- /dev/null
+++ b/core/java/android/content/pm/ActivityInfo.java
@@ -0,0 +1,353 @@
+package android.content.pm;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.Printer;
+
+/**
+ * Information you can retrieve about a particular application
+ * activity or receiver. This corresponds to information collected
+ * from the AndroidManifest.xml's &lt;activity&gt; and
+ * &lt;receiver&gt; tags.
+ */
+public class ActivityInfo extends ComponentInfo
+        implements Parcelable {
+    /**
+     * A style resource identifier (in the package's resources) of this
+     * activity's theme.  From the "theme" attribute or, if not set, 0.
+     */
+    public int theme;
+    
+    /**
+     * Constant corresponding to <code>standard</code> in
+     * the {@link android.R.attr#launchMode} attribute.
+     */
+    public static final int LAUNCH_MULTIPLE = 0;
+    /**
+     * Constant corresponding to <code>singleTop</code> in
+     * the {@link android.R.attr#launchMode} attribute.
+     */
+    public static final int LAUNCH_SINGLE_TOP = 1;
+    /**
+     * Constant corresponding to <code>singleTask</code> in
+     * the {@link android.R.attr#launchMode} attribute.
+     */
+    public static final int LAUNCH_SINGLE_TASK = 2;
+    /**
+     * Constant corresponding to <code>singleInstance</code> in
+     * the {@link android.R.attr#launchMode} attribute.
+     */
+    public static final int LAUNCH_SINGLE_INSTANCE = 3;
+    /**
+     * The launch mode style requested by the activity.  From the
+     * {@link android.R.attr#launchMode} attribute, one of
+     * {@link #LAUNCH_MULTIPLE},
+     * {@link #LAUNCH_SINGLE_TOP}, {@link #LAUNCH_SINGLE_TASK}, or 
+     * {@link #LAUNCH_SINGLE_INSTANCE}.
+     */
+    public int launchMode;
+    
+    /**
+     * Optional name of a permission required to be able to access this
+     * Activity.  From the "permission" attribute.
+     */
+    public String permission;
+    
+    /**
+     * The affinity this activity has for another task in the system.  The
+     * string here is the name of the task, often the package name of the
+     * overall package.  If null, the activity has no affinity.  Set from the
+     * {@link android.R.attr#taskAffinity} attribute.
+     */
+    public String taskAffinity;
+    
+    /**
+     * If this is an activity alias, this is the real activity class to run
+     * for it.  Otherwise, this is null.
+     */
+    public String targetActivity;
+    
+    /**
+     * Bit in {@link #flags} indicating whether this activity is able to
+     * run in multiple processes.  If
+     * true, the system may instantiate it in the some process as the
+     * process starting it in order to conserve resources.  If false, the
+     * default, it always runs in {@link #processName}.  Set from the
+     * {@link android.R.attr#multiprocess} attribute.
+     */
+    public static final int FLAG_MULTIPROCESS = 0x0001;
+    /**
+     * Bit in {@link #flags} indicating that, when the activity's task is
+     * relaunched from home, this activity should be finished.
+     * Set from the
+     * {@link android.R.attr#finishOnTaskLaunch} attribute.
+     */
+    public static final int FLAG_FINISH_ON_TASK_LAUNCH = 0x0002;
+    /**
+     * Bit in {@link #flags} indicating that, when the activity is the root
+     * of a task, that task's stack should be cleared each time the user
+     * re-launches it from home.  As a result, the user will always
+     * return to the original activity at the top of the task.
+     * This flag only applies to activities that
+     * are used to start the root of a new task.  Set from the
+     * {@link android.R.attr#clearTaskOnLaunch} attribute.
+     */
+    public static final int FLAG_CLEAR_TASK_ON_LAUNCH = 0x0004;
+    /**
+     * Bit in {@link #flags} indicating that, when the activity is the root
+     * of a task, that task's stack should never be cleared when it is
+     * relaunched from home.  Set from the
+     * {@link android.R.attr#alwaysRetainTaskState} attribute.
+     */
+    public static final int FLAG_ALWAYS_RETAIN_TASK_STATE = 0x0008;
+    /**
+     * Bit in {@link #flags} indicating that the activity's state
+     * is not required to be saved, so that if there is a failure the
+     * activity will not be removed from the activity stack.  Set from the
+     * {@link android.R.attr#stateNotNeeded} attribute.
+     */
+    public static final int FLAG_STATE_NOT_NEEDED = 0x0010;
+    /**
+     * Bit in {@link #flags} that indicates that the activity should not
+     * appear in the list of recently launched activities.  Set from the
+     * {@link android.R.attr#excludeFromRecents} attribute.
+     */
+    public static final int FLAG_EXCLUDE_FROM_RECENTS = 0x0020;
+    /**
+     * Bit in {@link #flags} that indicates that the activity can be moved
+     * between tasks based on its task affinity.  Set from the
+     * {@link android.R.attr#allowTaskReparenting} attribute.
+     */
+    public static final int FLAG_ALLOW_TASK_REPARENTING = 0x0040;
+    /**
+     * Bit in {@link #flags} indicating that, when the user navigates away
+     * from an activity, it should be finished.
+     * Set from the
+     * {@link android.R.attr#noHistory} attribute.
+     */
+    public static final int FLAG_NO_HISTORY = 0x0080;
+    /**
+     * Options that have been set in the activity declaration in the
+     * manifest: {@link #FLAG_MULTIPROCESS},
+     * {@link #FLAG_FINISH_ON_TASK_LAUNCH}, {@link #FLAG_CLEAR_TASK_ON_LAUNCH},
+     * {@link #FLAG_ALWAYS_RETAIN_TASK_STATE},
+     * {@link #FLAG_STATE_NOT_NEEDED}, {@link #FLAG_EXCLUDE_FROM_RECENTS},
+     * {@link #FLAG_ALLOW_TASK_REPARENTING}, {@link #FLAG_NO_HISTORY}.
+     */
+    public int flags;
+
+    /**
+     * Constant corresponding to <code>unspecified</code> in
+     * the {@link android.R.attr#screenOrientation} attribute.
+     */
+    public static final int SCREEN_ORIENTATION_UNSPECIFIED = -1;
+    /**
+     * Constant corresponding to <code>landscape</code> in
+     * the {@link android.R.attr#screenOrientation} attribute.
+     */
+    public static final int SCREEN_ORIENTATION_LANDSCAPE = 0;
+    /**
+     * Constant corresponding to <code>portrait</code> in
+     * the {@link android.R.attr#screenOrientation} attribute.
+     */
+    public static final int SCREEN_ORIENTATION_PORTRAIT = 1;
+    /**
+     * Constant corresponding to <code>user</code> in
+     * the {@link android.R.attr#screenOrientation} attribute.
+     */
+    public static final int SCREEN_ORIENTATION_USER = 2;
+    /**
+     * Constant corresponding to <code>behind</code> in
+     * the {@link android.R.attr#screenOrientation} attribute.
+     */
+    public static final int SCREEN_ORIENTATION_BEHIND = 3;
+    /**
+     * Constant corresponding to <code>sensor</code> in
+     * the {@link android.R.attr#screenOrientation} attribute.
+     */
+    public static final int SCREEN_ORIENTATION_SENSOR = 4;
+  
+    /**
+     * Constant corresponding to <code>sensor</code> in
+     * the {@link android.R.attr#screenOrientation} attribute.
+     */
+    public static final int SCREEN_ORIENTATION_NOSENSOR = 5;
+    /**
+     * The preferred screen orientation this activity would like to run in.
+     * From the {@link android.R.attr#screenOrientation} attribute, one of
+     * {@link #SCREEN_ORIENTATION_UNSPECIFIED},
+     * {@link #SCREEN_ORIENTATION_LANDSCAPE}, 
+     * {@link #SCREEN_ORIENTATION_PORTRAIT},
+     * {@link #SCREEN_ORIENTATION_USER},
+     * {@link #SCREEN_ORIENTATION_BEHIND},
+     * {@link #SCREEN_ORIENTATION_SENSOR},
+     * {@link #SCREEN_ORIENTATION_NOSENSOR}.
+     */
+    public int screenOrientation = SCREEN_ORIENTATION_UNSPECIFIED;
+    
+    /**
+     * Bit in {@link #configChanges} that indicates that the activity
+     * can itself handle changes to the IMSI MCC.  Set from the
+     * {@link android.R.attr#configChanges} attribute.
+     */
+    public static final int CONFIG_MCC = 0x0001;
+    /**
+     * Bit in {@link #configChanges} that indicates that the activity
+     * can itself handle changes to the IMSI MNC.  Set from the
+     * {@link android.R.attr#configChanges} attribute.
+     */
+    public static final int CONFIG_MNC = 0x0002;
+    /**
+     * Bit in {@link #configChanges} that indicates that the activity
+     * can itself handle changes to the locale.  Set from the
+     * {@link android.R.attr#configChanges} attribute.
+     */
+    public static final int CONFIG_LOCALE = 0x0004;
+    /**
+     * Bit in {@link #configChanges} that indicates that the activity
+     * can itself handle changes to the touchscreen type.  Set from the
+     * {@link android.R.attr#configChanges} attribute.
+     */
+    public static final int CONFIG_TOUCHSCREEN = 0x0008;
+    /**
+     * Bit in {@link #configChanges} that indicates that the activity
+     * can itself handle changes to the keyboard type.  Set from the
+     * {@link android.R.attr#configChanges} attribute.
+     */
+    public static final int CONFIG_KEYBOARD = 0x0010;
+    /**
+     * Bit in {@link #configChanges} that indicates that the activity
+     * can itself handle changes to the keyboard being hidden/exposed. 
+     * Set from the {@link android.R.attr#configChanges} attribute.
+     */
+    public static final int CONFIG_KEYBOARD_HIDDEN = 0x0020;
+    /**
+     * Bit in {@link #configChanges} that indicates that the activity
+     * can itself handle changes to the navigation type.  Set from the
+     * {@link android.R.attr#configChanges} attribute.
+     */
+    public static final int CONFIG_NAVIGATION = 0x0040;
+    /**
+     * Bit in {@link #configChanges} that indicates that the activity
+     * can itself handle changes to the screen orientation.  Set from the
+     * {@link android.R.attr#configChanges} attribute.
+     */
+    public static final int CONFIG_ORIENTATION = 0x0080;
+    /**
+     * Bit in {@link #configChanges} that indicates that the activity
+     * can itself handle changes to the font scaling factor.  Set from the
+     * {@link android.R.attr#configChanges} attribute.  This is
+     * not a core resource configutation, but a higher-level value, so its
+     * constant starts at the high bits.
+     */
+    public static final int CONFIG_FONT_SCALE = 0x40000000;
+    
+    /**
+     * Bit mask of kinds of configuration changes that this activity
+     * can handle itself (without being restarted by the system).
+     * Contains any combination of {@link #CONFIG_FONT_SCALE},
+     * {@link #CONFIG_MCC}, {@link #CONFIG_MNC},
+     * {@link #CONFIG_LOCALE}, {@link #CONFIG_TOUCHSCREEN},
+     * {@link #CONFIG_KEYBOARD}, {@link #CONFIG_NAVIGATION}, and
+     * {@link #CONFIG_ORIENTATION}.  Set from the
+     * {@link android.R.attr#configChanges} attribute.
+     */
+    public int configChanges;
+    
+    /**
+     * The desired soft input mode for this activity's main window.
+     * Set from the {@link android.R.attr#windowSoftInputMode} attribute
+     * in the activity's manifest.  May be any of the same values allowed
+     * for {@link android.view.WindowManager.LayoutParams#softInputMode
+     * WindowManager.LayoutParams.softInputMode}.  If 0 (unspecified),
+     * the mode from the theme will be used.
+     */
+    public int softInputMode;
+    
+    public ActivityInfo() {
+    }
+
+    public ActivityInfo(ActivityInfo orig) {
+        super(orig);
+        theme = orig.theme;
+        launchMode = orig.launchMode;
+        permission = orig.permission;
+        taskAffinity = orig.taskAffinity;
+        targetActivity = orig.targetActivity;
+        flags = orig.flags;
+        screenOrientation = orig.screenOrientation;
+        configChanges = orig.configChanges;
+        softInputMode = orig.softInputMode;
+    }
+    
+    /**
+     * Return the theme resource identifier to use for this activity.  If
+     * the activity defines a theme, that is used; else, the application
+     * theme is used.
+     * 
+     * @return The theme associated with this activity.
+     */
+    public final int getThemeResource() {
+        return theme != 0 ? theme : applicationInfo.theme;
+    }
+
+    public void dump(Printer pw, String prefix) {
+        super.dumpFront(pw, prefix);
+        pw.println(prefix + "permission=" + permission);
+        pw.println(prefix + "taskAffinity=" + taskAffinity
+                + " targetActivity=" + targetActivity);
+        pw.println(prefix + "launchMode=" + launchMode
+                + " flags=0x" + Integer.toHexString(flags)
+                + " theme=0x" + Integer.toHexString(theme));
+        pw.println(prefix + "screenOrientation=" + screenOrientation
+                + " configChanges=0x" + Integer.toHexString(configChanges)
+                + " softInputMode=0x" + Integer.toHexString(softInputMode));
+        super.dumpBack(pw, prefix);
+    }
+    
+    public String toString() {
+        return "ActivityInfo{"
+            + Integer.toHexString(System.identityHashCode(this))
+            + " " + name + "}";
+    }
+
+    public int describeContents() {
+        return 0;
+    }
+
+    public void writeToParcel(Parcel dest, int parcelableFlags) {
+        super.writeToParcel(dest, parcelableFlags);
+        dest.writeInt(theme);
+        dest.writeInt(launchMode);
+        dest.writeString(permission);
+        dest.writeString(taskAffinity);
+        dest.writeString(targetActivity);
+        dest.writeInt(flags);
+        dest.writeInt(screenOrientation);
+        dest.writeInt(configChanges);
+        dest.writeInt(softInputMode);
+    }
+
+    public static final Parcelable.Creator<ActivityInfo> CREATOR
+            = new Parcelable.Creator<ActivityInfo>() {
+        public ActivityInfo createFromParcel(Parcel source) {
+            return new ActivityInfo(source);
+        }
+        public ActivityInfo[] newArray(int size) {
+            return new ActivityInfo[size];
+        }
+    };
+
+    private ActivityInfo(Parcel source) {
+        super(source);
+        theme = source.readInt();
+        launchMode = source.readInt();
+        permission = source.readString();
+        taskAffinity = source.readString();
+        targetActivity = source.readString();
+        flags = source.readInt();
+        screenOrientation = source.readInt();
+        configChanges = source.readInt();
+        softInputMode = source.readInt();
+    }
+}
diff --git a/core/java/android/content/pm/ApplicationInfo.aidl b/core/java/android/content/pm/ApplicationInfo.aidl
new file mode 100755
index 0000000..006d1bd
--- /dev/null
+++ b/core/java/android/content/pm/ApplicationInfo.aidl
@@ -0,0 +1,20 @@
+/* //device/java/android/android/view/WindowManager.aidl
+**
+** Copyright 2007, 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 android.content.pm;
+
+parcelable ApplicationInfo;
diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java
new file mode 100644
index 0000000..8d727ed
--- /dev/null
+++ b/core/java/android/content/pm/ApplicationInfo.java
@@ -0,0 +1,310 @@
+package android.content.pm;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.Printer;
+
+import java.text.Collator;
+import java.util.Comparator;
+
+/**
+ * Information you can retrieve about a particular application.  This
+ * corresponds to information collected from the AndroidManifest.xml's
+ * &lt;application&gt; tag.
+ */
+public class ApplicationInfo extends PackageItemInfo implements Parcelable {
+    
+    /**
+     * Default task affinity of all activities in this application. See 
+     * {@link ActivityInfo#taskAffinity} for more information.  This comes 
+     * from the "taskAffinity" attribute. 
+     */
+    public String taskAffinity;
+    
+    /**
+     * Optional name of a permission required to be able to access this
+     * application's components.  From the "permission" attribute.
+     */
+    public String permission;
+    
+    /**
+     * The name of the process this application should run in.  From the
+     * "process" attribute or, if not set, the same as
+     * <var>packageName</var>.
+     */
+    public String processName;
+    
+    /**
+     * Class implementing the Application object.  From the "class"
+     * attribute.
+     */
+    public String className;
+    
+    /**
+     * A style resource identifier (in the package's resources) of the
+     * description of an application.  From the "description" attribute
+     * or, if not set, 0.
+     */
+    public int descriptionRes;    
+    
+    /**
+     * A style resource identifier (in the package's resources) of the
+     * default visual theme of the application.  From the "theme" attribute
+     * or, if not set, 0.
+     */
+    public int theme;
+    
+    /**
+     * Class implementing the Application's manage space
+     * functionality.  From the "manageSpaceActivity"
+     * attribute. This is an optional attribute and will be null if
+     * application's dont specify it in their manifest
+     */
+    public String manageSpaceActivityName;    
+    
+    /**
+     * Value for {@link #flags}: if set, this application is installed in the
+     * device's system image.
+     */
+    public static final int FLAG_SYSTEM = 1<<0;
+    
+    /**
+     * Value for {@link #flags}: set to true if this application would like to
+     * allow debugging of its
+     * code, even when installed on a non-development system.  Comes
+     * from {@link android.R.styleable#AndroidManifestApplication_debuggable
+     * android:debuggable} of the &lt;application&gt; tag.
+     */
+    public static final int FLAG_DEBUGGABLE = 1<<1;
+    
+    /**
+     * Value for {@link #flags}: set to true if this application has code
+     * associated with it.  Comes
+     * from {@link android.R.styleable#AndroidManifestApplication_hasCode
+     * android:hasCode} of the &lt;application&gt; tag.
+     */
+    public static final int FLAG_HAS_CODE = 1<<2;
+    
+    /**
+     * Value for {@link #flags}: set to true if this application is persistent.
+     * Comes from {@link android.R.styleable#AndroidManifestApplication_persistent
+     * android:persistent} of the &lt;application&gt; tag.
+     */
+    public static final int FLAG_PERSISTENT = 1<<3;
+
+    /**
+     * Value for {@link #flags}: set to true iif this application holds the
+     * {@link android.Manifest.permission#FACTORY_TEST} permission and the
+     * device is running in factory test mode.
+     */
+    public static final int FLAG_FACTORY_TEST = 1<<4;
+
+    /**
+     * Value for {@link #flags}: default value for the corresponding ActivityInfo flag.
+     * Comes from {@link android.R.styleable#AndroidManifestApplication_allowTaskReparenting
+     * android:allowTaskReparenting} of the &lt;application&gt; tag.
+     */
+    public static final int FLAG_ALLOW_TASK_REPARENTING = 1<<5;
+    
+    /**
+     * Value for {@link #flags}: default value for the corresponding ActivityInfo flag.
+     * Comes from {@link android.R.styleable#AndroidManifestApplication_allowClearUserData
+     * android:allowClearUserData} of the &lt;application&gt; tag.
+     */
+    public static final int FLAG_ALLOW_CLEAR_USER_DATA = 1<<6;
+    
+    
+    /**
+     * Value for {@link #flags}: default value for the corresponding ActivityInfo flag.
+     *  {@hide}
+     */
+    public static final int FLAG_UPDATED_SYSTEM_APP = 1<<7;
+
+    /**
+     * Flags associated with the application.  Any combination of
+     * {@link #FLAG_SYSTEM}, {@link #FLAG_DEBUGGABLE}, {@link #FLAG_HAS_CODE},
+     * {@link #FLAG_PERSISTENT}, {@link #FLAG_FACTORY_TEST}, and
+     * {@link #FLAG_ALLOW_TASK_REPARENTING}
+     * {@link #FLAG_ALLOW_CLEAR_USER_DATA}.
+     */
+    public int flags = 0;
+    
+    /**
+     * Full path to the location of this package.
+     */
+    public String sourceDir;
+
+    /**
+     * Full path to the location of the publicly available parts of this package (i.e. the resources
+     * and manifest).  For non-forward-locked apps this will be the same as {@link #sourceDir).
+     */
+    public String publicSourceDir;
+    
+    /**
+     * Paths to all shared libraries this application is linked against.  This
+     * field is only set if the {@link PackageManager#GET_SHARED_LIBRARY_FILES
+     * PackageManager.GET_SHARED_LIBRARY_FILES} flag was used when retrieving
+     * the structure.
+     */
+    public String[] sharedLibraryFiles;
+    
+    /**
+     * Full path to a directory assigned to the package for its persistent
+     * data.
+     */
+    public String dataDir;
+    
+    /**
+     * The kernel user-ID that has been assigned to this application;
+     * currently this is not a unique ID (multiple applications can have
+     * the same uid).
+     */
+    public int uid;
+    
+    /**
+     * When false, indicates that all components within this application are
+     * considered disabled, regardless of their individually set enabled status.
+     */
+    public boolean enabled = true;
+
+    public void dump(Printer pw, String prefix) {
+        super.dumpFront(pw, prefix);
+        pw.println(prefix + "className=" + className);
+        pw.println(prefix + "permission=" + permission
+                + " uid=" + uid);
+        pw.println(prefix + "taskAffinity=" + taskAffinity);
+        pw.println(prefix + "theme=0x" + Integer.toHexString(theme));
+        pw.println(prefix + "flags=0x" + Integer.toHexString(flags)
+                + " processName=" + processName);
+        pw.println(prefix + "sourceDir=" + sourceDir);
+        pw.println(prefix + "publicSourceDir=" + publicSourceDir);
+        pw.println(prefix + "sharedLibraryFiles=" + sharedLibraryFiles);
+        pw.println(prefix + "dataDir=" + dataDir);
+        pw.println(prefix + "enabled=" + enabled);
+        pw.println(prefix+"manageSpaceActivityName="+manageSpaceActivityName);
+        pw.println(prefix+"description=0x"+Integer.toHexString(descriptionRes));
+        super.dumpBack(pw, prefix);
+    }
+    
+    public static class DisplayNameComparator
+            implements Comparator<ApplicationInfo> {
+        public DisplayNameComparator(PackageManager pm) {
+            mPM = pm;
+        }
+
+        public final int compare(ApplicationInfo aa, ApplicationInfo ab) {
+            CharSequence  sa = mPM.getApplicationLabel(aa);
+            if (sa == null) {
+                sa = aa.packageName;
+            }
+            CharSequence  sb = mPM.getApplicationLabel(ab);
+            if (sb == null) {
+                sb = ab.packageName;
+            }
+            
+            return sCollator.compare(sa.toString(), sb.toString());
+        }
+
+        private final Collator   sCollator = Collator.getInstance();
+        private PackageManager   mPM;
+    }
+
+    public ApplicationInfo() {
+    }
+    
+    public ApplicationInfo(ApplicationInfo orig) {
+        super(orig);
+        taskAffinity = orig.taskAffinity;
+        permission = orig.permission;
+        processName = orig.processName;
+        className = orig.className;
+        theme = orig.theme;
+        flags = orig.flags;
+        sourceDir = orig.sourceDir;
+        publicSourceDir = orig.publicSourceDir;
+        sharedLibraryFiles = orig.sharedLibraryFiles;
+        dataDir = orig.dataDir;
+        uid = orig.uid;
+        enabled = orig.enabled;
+        manageSpaceActivityName = orig.manageSpaceActivityName;
+        descriptionRes = orig.descriptionRes;
+    }
+
+
+    public String toString() {
+        return "ApplicationInfo{"
+            + Integer.toHexString(System.identityHashCode(this))
+            + " " + packageName + "}";
+    }
+
+    public int describeContents() {
+        return 0;
+    }
+
+    public void writeToParcel(Parcel dest, int parcelableFlags) {
+        super.writeToParcel(dest, parcelableFlags);
+        dest.writeString(taskAffinity);
+        dest.writeString(permission);
+        dest.writeString(processName);
+        dest.writeString(className);
+        dest.writeInt(theme);
+        dest.writeInt(flags);
+        dest.writeString(sourceDir);
+        dest.writeString(publicSourceDir);
+        dest.writeStringArray(sharedLibraryFiles);
+        dest.writeString(dataDir);
+        dest.writeInt(uid);
+        dest.writeInt(enabled ? 1 : 0);
+        dest.writeString(manageSpaceActivityName);
+        dest.writeInt(descriptionRes);
+    }
+
+    public static final Parcelable.Creator<ApplicationInfo> CREATOR
+            = new Parcelable.Creator<ApplicationInfo>() {
+        public ApplicationInfo createFromParcel(Parcel source) {
+            return new ApplicationInfo(source);
+        }
+        public ApplicationInfo[] newArray(int size) {
+            return new ApplicationInfo[size];
+        }
+    };
+
+    private ApplicationInfo(Parcel source) {
+        super(source);
+        taskAffinity = source.readString();
+        permission = source.readString();
+        processName = source.readString();
+        className = source.readString();
+        theme = source.readInt();
+        flags = source.readInt();
+        sourceDir = source.readString();
+        publicSourceDir = source.readString();
+        sharedLibraryFiles = source.readStringArray();
+        dataDir = source.readString();
+        uid = source.readInt();
+        enabled = source.readInt() != 0;
+        manageSpaceActivityName = source.readString();
+        descriptionRes = source.readInt();
+    }
+    
+    /**
+     * Retrieve the textual description of the application.  This
+     * will call back on the given PackageManager to load the description from
+     * the application.
+     *
+     * @param pm A PackageManager from which the label can be loaded; usually
+     * the PackageManager from which you originally retrieved this item.
+     *
+     * @return Returns a CharSequence containing the application's description.
+     * If there is no description, null is returned.
+     */
+    public CharSequence loadDescription(PackageManager pm) {
+        if (descriptionRes != 0) {
+            CharSequence label = pm.getText(packageName, descriptionRes, null);
+            if (label != null) {
+                return label;
+            }
+        }
+        return null;
+    }
+}
diff --git a/core/java/android/content/pm/ComponentInfo.java b/core/java/android/content/pm/ComponentInfo.java
new file mode 100644
index 0000000..73c9244
--- /dev/null
+++ b/core/java/android/content/pm/ComponentInfo.java
@@ -0,0 +1,138 @@
+package android.content.pm;
+
+import android.graphics.drawable.Drawable;
+import android.os.Parcel;
+import android.util.Printer;
+
+/**
+ * Base class containing information common to all application components
+ * ({@link ActivityInfo}, {@link ServiceInfo}).  This class is not intended
+ * to be used by itself; it is simply here to share common definitions
+ * between all application components.  As such, it does not itself
+ * implement Parcelable, but does provide convenience methods to assist
+ * in the implementation of Parcelable in subclasses.
+ */
+public class ComponentInfo extends PackageItemInfo {
+    /**
+     * Global information about the application/package this component is a
+     * part of.
+     */
+    public ApplicationInfo applicationInfo;
+    
+    /**
+     * The name of the process this component should run in.
+     * From the "android:process" attribute or, if not set, the same
+     * as <var>applicationInfo.processName</var>.
+     */
+    public String processName;
+
+    /**
+     * Indicates whether or not this component may be instantiated.  Note that this value can be
+     * overriden by the one in its parent {@link ApplicationInfo}.
+     */
+    public boolean enabled = true;
+
+    /**
+     * Set to true if this component is available for use by other applications.
+     * Comes from {@link android.R.attr#exported android:exported} of the
+     * &lt;activity&gt;, &lt;receiver&gt;, &lt;service&gt;, or
+     * &lt;provider&gt; tag.
+     */
+    public boolean exported = false;
+    
+    public ComponentInfo() {
+    }
+
+    public ComponentInfo(ComponentInfo orig) {
+        super(orig);
+        applicationInfo = orig.applicationInfo;
+        processName = orig.processName;
+        enabled = orig.enabled;
+        exported = orig.exported;
+    }
+
+    @Override public CharSequence loadLabel(PackageManager pm) {
+        if (nonLocalizedLabel != null) {
+            return nonLocalizedLabel;
+        }
+        ApplicationInfo ai = applicationInfo;
+        CharSequence label;
+        if (labelRes != 0) {
+            label = pm.getText(packageName, labelRes, ai);
+            if (label != null) {
+                return label;
+            }
+        }
+        if (ai.nonLocalizedLabel != null) {
+            return ai.nonLocalizedLabel;
+        }
+        if (ai.labelRes != 0) {
+            label = pm.getText(packageName, ai.labelRes, ai);
+            if (label != null) {
+                return label;
+            }
+        }
+        return name;
+    }
+    
+    @Override public Drawable loadIcon(PackageManager pm) {
+        ApplicationInfo ai = applicationInfo;
+        Drawable dr;
+        if (icon != 0) {
+            dr = pm.getDrawable(packageName, icon, ai);
+            if (dr != null) {
+                return dr;
+            }
+        }
+        if (ai.icon != 0) {
+            dr = pm.getDrawable(packageName, ai.icon, ai);
+            if (dr != null) {
+                return dr;
+            }
+        }
+        return pm.getDefaultActivityIcon();
+    }
+    
+    /**
+     * Return the icon resource identifier to use for this component.  If
+     * the component defines an icon, that is used; else, the application
+     * icon is used.
+     * 
+     * @return The icon associated with this component.
+     */
+    public final int getIconResource() {
+        return icon != 0 ? icon : applicationInfo.icon;
+    }
+    
+    protected void dumpFront(Printer pw, String prefix) {
+        super.dumpFront(pw, prefix);
+        pw.println(prefix + "enabled=" + enabled + " exported=" + exported
+                + " processName=" + processName);
+    }
+    
+    protected void dumpBack(Printer pw, String prefix) {
+        if (applicationInfo != null) {
+            pw.println(prefix + "ApplicationInfo:");
+            applicationInfo.dump(pw, prefix + "  ");
+        } else {
+            pw.println(prefix + "ApplicationInfo: null");
+        }
+        super.dumpBack(pw, prefix);
+    }
+    
+    public void writeToParcel(Parcel dest, int parcelableFlags) {
+        super.writeToParcel(dest, parcelableFlags);
+        applicationInfo.writeToParcel(dest, parcelableFlags);
+        dest.writeString(processName);
+        dest.writeInt(enabled ? 1 : 0);
+        dest.writeInt(exported ? 1 : 0);
+    }
+
+    protected ComponentInfo(Parcel source) {
+        super(source);
+        applicationInfo = ApplicationInfo.CREATOR.createFromParcel(source);
+        processName = source.readString();
+        enabled = (source.readInt() != 0);
+        exported = (source.readInt() != 0);
+    }
+}
diff --git a/core/java/android/content/pm/ConfigurationInfo.java b/core/java/android/content/pm/ConfigurationInfo.java
new file mode 100755
index 0000000..dcc7463
--- /dev/null
+++ b/core/java/android/content/pm/ConfigurationInfo.java
@@ -0,0 +1,119 @@
+/*
+ * 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 android.content.pm;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Information you can retrieve about hardware configuration preferences
+ * declared by an application. This corresponds to information collected from the
+ * AndroidManifest.xml's &lt;uses-configuration&gt; tags.
+ */
+public class ConfigurationInfo implements Parcelable {    
+    /**
+     * The kind of touch screen attached to the device.
+     * One of: {@link android.content.res.Configuration#TOUCHSCREEN_NOTOUCH},
+     * {@link android.content.res.Configuration#TOUCHSCREEN_STYLUS}, 
+     * {@link android.content.res.Configuration#TOUCHSCREEN_FINGER}. 
+     */
+    public int reqTouchScreen;
+    
+    /**
+     * Application's input method preference.
+     * One of: {@link android.content.res.Configuration#KEYBOARD_UNDEFINED},
+     * {@link android.content.res.Configuration#KEYBOARD_NOKEYS},
+     * {@link android.content.res.Configuration#KEYBOARD_QWERTY},
+     * {@link android.content.res.Configuration#KEYBOARD_12KEY}
+     */
+    public int reqKeyboardType;
+    
+    /**
+     * A flag indicating whether any keyboard is available.
+     * one of: {@link android.content.res.Configuration#NAVIGATION_UNDEFINED},
+     * {@link android.content.res.Configuration#NAVIGATION_DPAD}, 
+     * {@link android.content.res.Configuration#NAVIGATION_TRACKBALL},
+     * {@link android.content.res.Configuration#NAVIGATION_WHEEL}
+     */
+    public int reqNavigation;
+    
+    /**
+     * Value for {@link #reqInputFeatures}: if set, indicates that the application
+     * requires a hard keyboard
+     */
+    public static final int INPUT_FEATURE_HARD_KEYBOARD = 0x00000001;
+    
+    /**
+     * Value for {@link #reqInputFeatures}: if set, indicates that the application
+     * requires a five way navigation device
+     */
+    public static final int INPUT_FEATURE_FIVE_WAY_NAV = 0x00000002;
+    
+    /**
+     * Flags associated with the input features.  Any combination of
+     * {@link #INPUT_FEATURE_HARD_KEYBOARD},
+     * {@link #INPUT_FEATURE_FIVE_WAY_NAV}
+     */
+    public int reqInputFeatures = 0;
+
+    public ConfigurationInfo() {
+    }
+
+    public ConfigurationInfo(ConfigurationInfo orig) {
+        reqTouchScreen = orig.reqTouchScreen;
+        reqKeyboardType = orig.reqKeyboardType;
+        reqNavigation = orig.reqNavigation;
+        reqInputFeatures = orig.reqInputFeatures;
+    }
+
+    public String toString() {
+        return "ApplicationHardwarePreferences{"
+            + Integer.toHexString(System.identityHashCode(this))
+            + ", touchscreen = " + reqTouchScreen + "}"
+            + ", inputMethod = " + reqKeyboardType + "}"
+            + ", navigation = " + reqNavigation + "}"
+            + ", reqInputFeatures = " + reqInputFeatures + "}";
+    }
+
+    public int describeContents() {
+        return 0;
+    }
+
+    public void writeToParcel(Parcel dest, int parcelableFlags) {
+        dest.writeInt(reqTouchScreen);
+        dest.writeInt(reqKeyboardType);
+        dest.writeInt(reqNavigation);
+        dest.writeInt(reqInputFeatures);
+    }
+
+    public static final Creator<ConfigurationInfo> CREATOR =
+        new Creator<ConfigurationInfo>() {
+        public ConfigurationInfo createFromParcel(Parcel source) {
+            return new ConfigurationInfo(source);
+        }
+        public ConfigurationInfo[] newArray(int size) {
+            return new ConfigurationInfo[size];
+        }
+    };
+
+    private ConfigurationInfo(Parcel source) {
+        reqTouchScreen = source.readInt();
+        reqKeyboardType = source.readInt();
+        reqNavigation = source.readInt();
+        reqInputFeatures = source.readInt();
+    }
+}
diff --git a/core/java/android/content/pm/IPackageDataObserver.aidl b/core/java/android/content/pm/IPackageDataObserver.aidl
new file mode 100755
index 0000000..d010ee4
--- /dev/null
+++ b/core/java/android/content/pm/IPackageDataObserver.aidl
@@ -0,0 +1,28 @@
+/*
+**
+** Copyright 2007, 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 android.content.pm;
+
+/**
+ * API for package data change related callbacks from the Package Manager.
+ * Some usage scenarios include deletion of cache directory, generate
+ * statistics related to code, data, cache usage(TODO)
+ * {@hide}
+ */
+oneway interface IPackageDataObserver {
+    void onRemoveCompleted(in String packageName, boolean succeeded);
+}
diff --git a/core/java/android/content/pm/IPackageDeleteObserver.aidl b/core/java/android/content/pm/IPackageDeleteObserver.aidl
new file mode 100644
index 0000000..bc16b3e
--- /dev/null
+++ b/core/java/android/content/pm/IPackageDeleteObserver.aidl
@@ -0,0 +1,28 @@
+/*
+**
+** Copyright 2007, 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 android.content.pm;
+
+/**
+ * API for deletion callbacks from the Package Manager.
+ *
+ * {@hide}
+ */
+oneway interface IPackageDeleteObserver {
+    void packageDeleted(in boolean succeeded);
+}
+
diff --git a/core/java/android/content/pm/IPackageInstallObserver.aidl b/core/java/android/content/pm/IPackageInstallObserver.aidl
new file mode 100644
index 0000000..e83bbc6
--- /dev/null
+++ b/core/java/android/content/pm/IPackageInstallObserver.aidl
@@ -0,0 +1,27 @@
+/*
+**
+** Copyright 2007, 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 android.content.pm;
+
+/**
+ * API for installation callbacks from the Package Manager.
+ *
+ */
+oneway interface IPackageInstallObserver {
+    void packageInstalled(in String packageName, int returnCode);
+}
+
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
new file mode 100644
index 0000000..d3f6f3c5
--- /dev/null
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -0,0 +1,269 @@
+/*
+**
+** Copyright 2007, 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 android.content.pm;
+
+import android.content.ComponentName;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.IPackageInstallObserver;
+import android.content.pm.IPackageDeleteObserver;
+import android.content.pm.IPackageDataObserver;
+import android.content.pm.IPackageStatsObserver;
+import android.content.pm.InstrumentationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.ProviderInfo;
+import android.content.pm.PermissionGroupInfo;
+import android.content.pm.PermissionInfo;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
+import android.net.Uri;
+import android.app.PendingIntent;
+
+/**
+ *  See {@link PackageManager} for documentation on most of the APIs
+ *  here.
+ * 
+ *  {@hide}
+ */
+interface IPackageManager {
+    PackageInfo getPackageInfo(String packageName, int flags);
+    int getPackageUid(String packageName);
+    int[] getPackageGids(String packageName);
+
+    PermissionInfo getPermissionInfo(String name, int flags);
+    
+    List<PermissionInfo> queryPermissionsByGroup(String group, int flags);
+    
+    PermissionGroupInfo getPermissionGroupInfo(String name, int flags);
+    
+    List<PermissionGroupInfo> getAllPermissionGroups(int flags);
+    
+    ApplicationInfo getApplicationInfo(String packageName, int flags);
+
+    ActivityInfo getActivityInfo(in ComponentName className, int flags);
+
+    ActivityInfo getReceiverInfo(in ComponentName className, int flags);
+
+    ServiceInfo getServiceInfo(in ComponentName className, int flags);
+
+    int checkPermission(String permName, String pkgName);
+    
+    int checkUidPermission(String permName, int uid);
+    
+    boolean addPermission(in PermissionInfo info);
+    
+    void removePermission(String name);
+    
+    int checkSignatures(String pkg1, String pkg2);
+    
+    String[] getPackagesForUid(int uid);
+    
+    String getNameForUid(int uid);
+    
+    int getUidForSharedUser(String sharedUserName);
+    
+    ResolveInfo resolveIntent(in Intent intent, String resolvedType, int flags);
+
+    List<ResolveInfo> queryIntentActivities(in Intent intent, 
+            String resolvedType, int flags);
+
+    List<ResolveInfo> queryIntentActivityOptions(
+            in ComponentName caller, in Intent[] specifics,
+            in String[] specificTypes, in Intent intent,
+            String resolvedType, int flags);
+
+    List<ResolveInfo> queryIntentReceivers(in Intent intent,
+            String resolvedType, int flags);
+
+    ResolveInfo resolveService(in Intent intent,
+            String resolvedType, int flags);
+
+    List<ResolveInfo> queryIntentServices(in Intent intent,
+            String resolvedType, int flags);
+
+    List<PackageInfo> getInstalledPackages(int flags);
+
+    List<ApplicationInfo> getInstalledApplications(int flags);
+
+    /**
+     * Retrieve all applications that are marked as persistent.
+     * 
+     * @return A List&lt;applicationInfo> containing one entry for each persistent
+     *         application.
+     */
+    List<ApplicationInfo> getPersistentApplications(int flags);
+
+    ProviderInfo resolveContentProvider(String name, int flags);
+
+    /**
+     * Retrieve sync information for all content providers.
+     * 
+     * @param outNames Filled in with a list of the root names of the content
+     *                 providers that can sync.
+     * @param outInfo Filled in with a list of the ProviderInfo for each
+     *                name in 'outNames'.
+     */
+    void querySyncProviders(inout List<String> outNames,
+            inout List<ProviderInfo> outInfo);
+
+    List<ProviderInfo> queryContentProviders(
+            String processName, int uid, int flags);
+
+    InstrumentationInfo getInstrumentationInfo(
+            in ComponentName className, int flags);
+
+    List<InstrumentationInfo> queryInstrumentation(
+            String targetPackage, int flags);
+
+    /**
+     * Install a package.
+     *
+     * @param packageURI The location of the package file to install.
+     * @param observer a callback to use to notify when the package installation in finished.
+     * @param flags - possible values: {@link #FORWARD_LOCK_PACKAGE},
+     * {@link #REPLACE_EXISITING_PACKAGE}
+     */
+    void installPackage(in Uri packageURI, IPackageInstallObserver observer, int flags);
+
+    /**
+     * Delete a package.
+     *
+     * @param packageName The fully qualified name of the package to delete.
+     * @param observer a callback to use to notify when the package deletion in finished.
+     * @param flags - possible values: {@link #DONT_DELETE_DATA}
+     */
+    void deletePackage(in String packageName, IPackageDeleteObserver observer, int flags);
+
+    void addPackageToPreferred(String packageName);
+    
+    void removePackageFromPreferred(String packageName);
+    
+    List<PackageInfo> getPreferredPackages(int flags);
+
+    void addPreferredActivity(in IntentFilter filter, int match,
+            in ComponentName[] set, in ComponentName activity);
+    void clearPackagePreferredActivities(String packageName);
+    int getPreferredActivities(out List<IntentFilter> outFilters,
+            out List<ComponentName> outActivities, String packageName);
+    
+    /**
+     * As per {@link android.content.pm.PackageManager#setComponentEnabledSetting}.
+     */
+    void setComponentEnabledSetting(in ComponentName componentName,
+            in int newState, in int flags);
+
+    /**
+     * As per {@link android.content.pm.PackageManager#getComponentEnabledSetting}.
+     */
+    int getComponentEnabledSetting(in ComponentName componentName);
+    
+    /**
+     * As per {@link android.content.pm.PackageManager#setApplicationEnabledSetting}.
+     */
+    void setApplicationEnabledSetting(in String packageName, in int newState, int flags);
+    
+    /**
+     * As per {@link android.content.pm.PackageManager#getApplicationEnabledSetting}.
+     */
+    int getApplicationEnabledSetting(in String packageName);
+    
+    /**
+     * Free storage by deleting LRU sorted list of cache files across
+     * all applications. If the currently available free storage
+     * on the device is greater than or equal to the requested
+     * free storage, no cache files are cleared. If the currently
+     * available storage on the device is less than the requested
+     * free storage, some or all of the cache files across
+     * all applications are deleted (based on last accessed time)
+     * to increase the free storage space on the device to
+     * the requested value. There is no guarantee that clearing all
+     * the cache files from all applications will clear up
+     * enough storage to achieve the desired value.
+     * @param freeStorageSize The number of bytes of storage to be
+     * freed by the system. Say if freeStorageSize is XX,
+     * and the current free storage is YY,
+     * if XX is less than YY, just return. if not free XX-YY number
+     * of bytes if possible.
+     * @param observer call back used to notify when
+     * the operation is completed
+     */
+     void freeStorageAndNotify(in long freeStorageSize,
+             IPackageDataObserver observer);
+
+    /**
+     * Free storage by deleting LRU sorted list of cache files across
+     * all applications. If the currently available free storage
+     * on the device is greater than or equal to the requested
+     * free storage, no cache files are cleared. If the currently
+     * available storage on the device is less than the requested
+     * free storage, some or all of the cache files across
+     * all applications are deleted (based on last accessed time)
+     * to increase the free storage space on the device to
+     * the requested value. There is no guarantee that clearing all
+     * the cache files from all applications will clear up
+     * enough storage to achieve the desired value.
+     * @param freeStorageSize The number of bytes of storage to be
+     * freed by the system. Say if freeStorageSize is XX,
+     * and the current free storage is YY,
+     * if XX is less than YY, just return. if not free XX-YY number
+     * of bytes if possible.
+     * @param opFinishedIntent PendingIntent call back used to
+     * notify when the operation is completed.May be null
+     * to indicate that no call back is desired.
+     */
+     void freeStorage(in long freeStorageSize,
+             in PendingIntent opFinishedIntent);
+     
+    /**
+     * Delete all the cache files in an applications cache directory
+     * @param packageName The package name of the application whose cache
+     * files need to be deleted
+     * @param observer a callback used to notify when the deletion is finished.
+     */
+    void deleteApplicationCacheFiles(in String packageName, IPackageDataObserver observer);
+    
+    /**
+     * Clear the user data directory of an application.
+     * @param packageName The package name of the application whose cache
+     * files need to be deleted
+     * @param observer a callback used to notify when the operation is completed.
+     */
+    void clearApplicationUserData(in String packageName, IPackageDataObserver observer);
+    
+   /**
+     * Get package statistics including the code, data and cache size for
+     * an already installed package
+     * @param packageName The package name of the application
+     * @param observer a callback to use to notify when the asynchronous
+     * retrieval of information is complete.
+     */
+    void getPackageSizeInfo(in String packageName, IPackageStatsObserver observer);
+    
+    /**
+     * Get a list of shared libraries that are available on the
+     * system.
+     */
+    String[] getSystemSharedLibraryNames();
+
+    void enterSafeMode();
+    boolean isSafeMode();
+    void systemReady();
+    boolean hasSystemUidErrors();
+}
diff --git a/core/java/android/content/pm/IPackageStatsObserver.aidl b/core/java/android/content/pm/IPackageStatsObserver.aidl
new file mode 100755
index 0000000..ede4d1d
--- /dev/null
+++ b/core/java/android/content/pm/IPackageStatsObserver.aidl
@@ -0,0 +1,30 @@
+/*
+**
+** Copyright 2007, 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 android.content.pm;
+
+import android.content.pm.PackageStats;
+/**
+ * API for package data change related callbacks from the Package Manager.
+ * Some usage scenarios include deletion of cache directory, generate
+ * statistics related to code, data, cache usage(TODO)
+ * {@hide}
+ */
+oneway interface IPackageStatsObserver {
+    
+    void onGetStatsCompleted(in PackageStats pStats, boolean succeeded);
+}
diff --git a/core/java/android/content/pm/InstrumentationInfo.aidl b/core/java/android/content/pm/InstrumentationInfo.aidl
new file mode 100755
index 0000000..3d847ae
--- /dev/null
+++ b/core/java/android/content/pm/InstrumentationInfo.aidl
@@ -0,0 +1,20 @@
+/* //device/java/android/android/view/WindowManager.aidl
+**
+** Copyright 2007, 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 android.content.pm;
+
+parcelable InstrumentationInfo;
diff --git a/core/java/android/content/pm/InstrumentationInfo.java b/core/java/android/content/pm/InstrumentationInfo.java
new file mode 100644
index 0000000..30ca002
--- /dev/null
+++ b/core/java/android/content/pm/InstrumentationInfo.java
@@ -0,0 +1,98 @@
+package android.content.pm;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+
+import java.text.Collator;
+import java.util.Comparator;
+
+/**
+ * Information you can retrieve about a particular piece of test
+ * instrumentation.  This corresponds to information collected
+ * from the AndroidManifest.xml's &lt;instrumentation&gt; tag.
+ */
+public class InstrumentationInfo extends PackageItemInfo implements Parcelable {
+    /**
+     * The name of the application package being instrumented.  From the
+     * "package" attribute.
+     */
+    public String targetPackage;
+    
+    /**
+     * Full path to the location of this package.
+     */
+    public String sourceDir;
+    
+    /**
+     * Full path to the location of the publicly available parts of this package (i.e. the resources
+     * and manifest).  For non-forward-locked apps this will be the same as {@link #sourceDir).
+     */
+    public String publicSourceDir;
+    /**
+     * Full path to a directory assigned to the package for its persistent
+     * data.
+     */
+    public String dataDir;
+    
+    /**
+     * Specifies whether or not this instrumentation will handle profiling.
+     */
+    public boolean handleProfiling;
+    
+    /** Specifies whether or not to run this instrumentation as a functional test */
+    public boolean functionalTest;
+
+    public InstrumentationInfo() {
+    }
+
+    public InstrumentationInfo(InstrumentationInfo orig) {
+        super(orig);
+        targetPackage = orig.targetPackage;
+        sourceDir = orig.sourceDir;
+        publicSourceDir = orig.publicSourceDir;
+        dataDir = orig.dataDir;
+        handleProfiling = orig.handleProfiling;
+        functionalTest = orig.functionalTest;
+    }
+
+    public String toString() {
+        return "InstrumentationInfo{"
+            + Integer.toHexString(System.identityHashCode(this))
+            + " " + packageName + "}";
+    }
+
+    public int describeContents() {
+        return 0;
+    }
+
+    public void writeToParcel(Parcel dest, int parcelableFlags) {
+        super.writeToParcel(dest, parcelableFlags);
+        dest.writeString(targetPackage);
+        dest.writeString(sourceDir);
+        dest.writeString(publicSourceDir);
+        dest.writeString(dataDir);
+        dest.writeInt((handleProfiling == false) ? 0 : 1);
+        dest.writeInt((functionalTest == false) ? 0 : 1);
+    }
+
+    public static final Parcelable.Creator<InstrumentationInfo> CREATOR
+            = new Parcelable.Creator<InstrumentationInfo>() {
+        public InstrumentationInfo createFromParcel(Parcel source) {
+            return new InstrumentationInfo(source);
+        }
+        public InstrumentationInfo[] newArray(int size) {
+            return new InstrumentationInfo[size];
+        }
+    };
+
+    private InstrumentationInfo(Parcel source) {
+        super(source);
+        targetPackage = source.readString();
+        sourceDir = source.readString();
+        publicSourceDir = source.readString();
+        dataDir = source.readString();
+        handleProfiling = source.readInt() != 0;
+        functionalTest = source.readInt() != 0;
+    }
+}
diff --git a/core/java/android/content/pm/PackageInfo.aidl b/core/java/android/content/pm/PackageInfo.aidl
new file mode 100755
index 0000000..35e2322
--- /dev/null
+++ b/core/java/android/content/pm/PackageInfo.aidl
@@ -0,0 +1,20 @@
+/* //device/java/android/android/view/WindowManager.aidl
+**
+** Copyright 2007, 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 android.content.pm;
+
+parcelable PackageInfo;
diff --git a/core/java/android/content/pm/PackageInfo.java b/core/java/android/content/pm/PackageInfo.java
new file mode 100644
index 0000000..d9326f2
--- /dev/null
+++ b/core/java/android/content/pm/PackageInfo.java
@@ -0,0 +1,199 @@
+package android.content.pm;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Overall information about the contents of a package.  This corresponds
+ * to all of the information collected from AndroidManifest.xml.
+ */
+public class PackageInfo implements Parcelable {
+    /**
+     * The name of this package.  From the &lt;manifest&gt; tag's "name"
+     * attribute.
+     */
+    public String packageName;
+
+    /**
+     * The version number of this package, as specified by the &lt;manifest&gt;
+     * tag's {@link android.R.styleable#AndroidManifest_versionCode versionCode}
+     * attribute.
+     */
+    public int versionCode;
+    
+    /**
+     * The version name of this package, as specified by the &lt;manifest&gt;
+     * tag's {@link android.R.styleable#AndroidManifest_versionName versionName}
+     * attribute.
+     */
+    public String versionName;
+    
+    /**
+     * The shared user ID name of this package, as specified by the &lt;manifest&gt;
+     * tag's {@link android.R.styleable#AndroidManifest_sharedUserId sharedUserId}
+     * attribute.
+     */
+    public String sharedUserId;
+    
+    /**
+     * The shared user ID label of this package, as specified by the &lt;manifest&gt;
+     * tag's {@link android.R.styleable#AndroidManifest_sharedUserLabel sharedUserLabel}
+     * attribute.
+     */
+    public int sharedUserLabel;
+    
+    /**
+     * Information collected from the &lt;application&gt; tag, or null if
+     * there was none.
+     */
+    public ApplicationInfo applicationInfo;
+    
+    /**
+     * All kernel group-IDs that have been assigned to this package.
+     * This is only filled in if the flag {@link PackageManager#GET_GIDS} was set.
+     */
+    public int[] gids;
+
+    /**
+     * Array of all {@link android.R.styleable#AndroidManifestActivity
+     * &lt;activity&gt;} tags included under &lt;application&gt;,
+     * or null if there were none.  This is only filled in if the flag
+     * {@link PackageManager#GET_ACTIVITIES} was set.
+     */
+    public ActivityInfo[] activities;
+
+    /**
+     * Array of all {@link android.R.styleable#AndroidManifestReceiver
+     * &lt;receiver&gt;} tags included under &lt;application&gt;,
+     * or null if there were none.  This is only filled in if the flag
+     * {@link PackageManager#GET_RECEIVERS} was set.
+     */
+    public ActivityInfo[] receivers;
+
+    /**
+     * Array of all {@link android.R.styleable#AndroidManifestService
+     * &lt;service&gt;} tags included under &lt;application&gt;,
+     * or null if there were none.  This is only filled in if the flag
+     * {@link PackageManager#GET_SERVICES} was set.
+     */
+    public ServiceInfo[] services;
+
+    /**
+     * Array of all {@link android.R.styleable#AndroidManifestProvider
+     * &lt;provider&gt;} tags included under &lt;application&gt;,
+     * or null if there were none.  This is only filled in if the flag
+     * {@link PackageManager#GET_PROVIDERS} was set.
+     */
+    public ProviderInfo[] providers;
+
+    /**
+     * Array of all {@link android.R.styleable#AndroidManifestInstrumentation
+     * &lt;instrumentation&gt;} tags included under &lt;manifest&gt;,
+     * or null if there were none.  This is only filled in if the flag
+     * {@link PackageManager#GET_INSTRUMENTATION} was set.
+     */
+    public InstrumentationInfo[] instrumentation;
+    
+    /**
+     * Array of all {@link android.R.styleable#AndroidManifestPermission
+     * &lt;permission&gt;} tags included under &lt;manifest&gt;,
+     * or null if there were none.  This is only filled in if the flag
+     * {@link PackageManager#GET_PERMISSIONS} was set.
+     */
+    public PermissionInfo[] permissions;
+    
+    /**
+     * Array of all {@link android.R.styleable#AndroidManifestUsesPermission
+     * &lt;uses-permission&gt;} tags included under &lt;manifest&gt;,
+     * or null if there were none.  This is only filled in if the flag
+     * {@link PackageManager#GET_PERMISSIONS} was set.  This list includes
+     * all permissions requested, even those that were not granted or known
+     * by the system at install time.
+     */
+    public String[] requestedPermissions;
+    
+    /**
+     * Array of all signatures read from the package file.  This is only filled
+     * in if the flag {@link PackageManager#GET_SIGNATURES} was set.
+     */
+    public Signature[] signatures;
+    
+    /**
+     * Application specified preferred configuration
+     * {@link android.R.styleable#AndroidManifestUsesConfiguration
+     * &lt;uses-configuration&gt;} tags included under &lt;manifest&gt;,
+     * or null if there were none. This is only filled in if the flag
+     * {@link PackageManager#GET_CONFIGURATIONS} was set.  
+     */
+    public ConfigurationInfo[] configPreferences;
+
+    public PackageInfo() {
+    }
+
+    public String toString() {
+        return "PackageInfo{"
+            + Integer.toHexString(System.identityHashCode(this))
+            + " " + packageName + "}";
+    }
+
+    public int describeContents() {
+        return 0;
+    }
+
+    public void writeToParcel(Parcel dest, int parcelableFlags) {
+        dest.writeString(packageName);
+        dest.writeInt(versionCode);
+        dest.writeString(versionName);
+        dest.writeString(sharedUserId);
+        dest.writeInt(sharedUserLabel);
+        if (applicationInfo != null) {
+            dest.writeInt(1);
+            applicationInfo.writeToParcel(dest, parcelableFlags);
+        } else {
+            dest.writeInt(0);
+        }
+        dest.writeIntArray(gids);
+        dest.writeTypedArray(activities, parcelableFlags);
+        dest.writeTypedArray(receivers, parcelableFlags);
+        dest.writeTypedArray(services, parcelableFlags);
+        dest.writeTypedArray(providers, parcelableFlags);
+        dest.writeTypedArray(instrumentation, parcelableFlags);
+        dest.writeTypedArray(permissions, parcelableFlags);
+        dest.writeStringArray(requestedPermissions);
+        dest.writeTypedArray(signatures, parcelableFlags);
+        dest.writeTypedArray(configPreferences, parcelableFlags);
+    }
+
+    public static final Parcelable.Creator<PackageInfo> CREATOR
+            = new Parcelable.Creator<PackageInfo>() {
+        public PackageInfo createFromParcel(Parcel source) {
+            return new PackageInfo(source);
+        }
+
+        public PackageInfo[] newArray(int size) {
+            return new PackageInfo[size];
+        }
+    };
+
+    private PackageInfo(Parcel source) {
+        packageName = source.readString();
+        versionCode = source.readInt();
+        versionName = source.readString();
+        sharedUserId = source.readString();
+        sharedUserLabel = source.readInt();
+        int hasApp = source.readInt();
+        if (hasApp != 0) {
+            applicationInfo = ApplicationInfo.CREATOR.createFromParcel(source);
+        }
+        gids = source.createIntArray();
+        activities = source.createTypedArray(ActivityInfo.CREATOR);
+        receivers = source.createTypedArray(ActivityInfo.CREATOR);
+        services = source.createTypedArray(ServiceInfo.CREATOR);
+        providers = source.createTypedArray(ProviderInfo.CREATOR);
+        instrumentation = source.createTypedArray(InstrumentationInfo.CREATOR);
+        permissions = source.createTypedArray(PermissionInfo.CREATOR);
+        requestedPermissions = source.createStringArray();
+        signatures = source.createTypedArray(Signature.CREATOR);
+        configPreferences = source.createTypedArray(ConfigurationInfo.CREATOR);
+    }
+}
diff --git a/core/java/android/content/pm/PackageItemInfo.java b/core/java/android/content/pm/PackageItemInfo.java
new file mode 100644
index 0000000..46e7ca43
--- /dev/null
+++ b/core/java/android/content/pm/PackageItemInfo.java
@@ -0,0 +1,191 @@
+package android.content.pm;
+
+import android.content.res.XmlResourceParser;
+
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.text.TextUtils;
+import android.util.Printer;
+
+import java.text.Collator;
+import java.util.Comparator;
+
+/**
+ * Base class containing information common to all package items held by
+ * the package manager.  This provides a very common basic set of attributes:
+ * a label, icon, and meta-data.  This class is not intended
+ * to be used by itself; it is simply here to share common definitions
+ * between all items returned by the package manager.  As such, it does not
+ * itself implement Parcelable, but does provide convenience methods to assist
+ * in the implementation of Parcelable in subclasses.
+ */
+public class PackageItemInfo {
+    /**
+     * Public name of this item. From the "android:name" attribute.
+     */
+    public String name;
+    
+    /**
+     * Name of the package that this item is in.
+     */
+    public String packageName;
+    
+    /**
+     * A string resource identifier (in the package's resources) of this
+     * component's label.  From the "label" attribute or, if not set, 0.
+     */
+    public int labelRes;
+    
+    /**
+     * The string provided in the AndroidManifest file, if any.  You
+     * probably don't want to use this.  You probably want
+     * {@link PackageManager#getApplicationLabel}
+     */
+    public CharSequence nonLocalizedLabel;
+    
+    /**
+     * A drawable resource identifier (in the package's resources) of this
+     * component's icon.  From the "icon" attribute or, if not set, 0.
+     */
+    public int icon;
+    
+    /**
+     * Additional meta-data associated with this component.  This field
+     * will only be filled in if you set the
+     * {@link PackageManager#GET_META_DATA} flag when requesting the info.
+     */
+    public Bundle metaData;
+    
+    public PackageItemInfo() {
+    }
+
+    public PackageItemInfo(PackageItemInfo orig) {
+        name = orig.name;
+        packageName = orig.packageName;
+        labelRes = orig.labelRes;
+        nonLocalizedLabel = orig.nonLocalizedLabel;
+        icon = orig.icon;
+        metaData = orig.metaData;
+    }
+
+    /**
+     * Retrieve the current textual label associated with this item.  This
+     * will call back on the given PackageManager to load the label from
+     * the application.
+     * 
+     * @param pm A PackageManager from which the label can be loaded; usually
+     * the PackageManager from which you originally retrieved this item.
+     * 
+     * @return Returns a CharSequence containing the item's label.  If the
+     * item does not have a label, its name is returned.
+     */
+    public CharSequence loadLabel(PackageManager pm) {
+        if (nonLocalizedLabel != null) {
+            return nonLocalizedLabel;
+        }
+        if (labelRes != 0) {
+            CharSequence label = pm.getText(packageName, labelRes, null);
+            if (label != null) {
+                return label;
+            }
+        }
+        if(name != null) {
+            return name;
+        }
+        return packageName;
+    }
+    
+    /**
+     * Retrieve the current graphical icon associated with this item.  This
+     * will call back on the given PackageManager to load the icon from
+     * the application.
+     * 
+     * @param pm A PackageManager from which the icon can be loaded; usually
+     * the PackageManager from which you originally retrieved this item.
+     * 
+     * @return Returns a Drawable containing the item's icon.  If the
+     * item does not have an icon, the default activity icon is returned.
+     */
+    public Drawable loadIcon(PackageManager pm) {
+        if (icon != 0) {
+            Drawable dr = pm.getDrawable(packageName, icon, null);
+            if (dr != null) {
+                return dr;
+            }
+        }
+        return pm.getDefaultActivityIcon();
+    }
+    
+    /**
+     * Load an XML resource attached to the meta-data of this item.  This will
+     * retrieved the name meta-data entry, and if defined call back on the
+     * given PackageManager to load its XML file from the application.
+     * 
+     * @param pm A PackageManager from which the XML can be loaded; usually
+     * the PackageManager from which you originally retrieved this item.
+     * @param name Name of the meta-date you would like to load.
+     * 
+     * @return Returns an XmlPullParser you can use to parse the XML file
+     * assigned as the given meta-data.  If the meta-data name is not defined
+     * or the XML resource could not be found, null is returned.
+     */
+    public XmlResourceParser loadXmlMetaData(PackageManager pm, String name) {
+        if (metaData != null) {
+            int resid = metaData.getInt(name);
+            if (resid != 0) {
+                return pm.getXml(packageName, resid, null);
+            }
+        }
+        return null;
+    }
+    
+    protected void dumpFront(Printer pw, String prefix) {
+        pw.println(prefix + "name=" + name);
+        pw.println(prefix + "packageName=" + packageName);
+        pw.println(prefix + "labelRes=0x" + Integer.toHexString(labelRes)
+                + " nonLocalizedLabel=" + nonLocalizedLabel
+                + " icon=0x" + Integer.toHexString(icon));
+    }
+    
+    protected void dumpBack(Printer pw, String prefix) {
+        // no back here
+    }
+    
+    public void writeToParcel(Parcel dest, int parcelableFlags) {
+        dest.writeString(name);
+        dest.writeString(packageName);
+        dest.writeInt(labelRes);
+        TextUtils.writeToParcel(nonLocalizedLabel, dest, parcelableFlags);
+        dest.writeInt(icon);
+        dest.writeBundle(metaData);
+    }
+
+    protected PackageItemInfo(Parcel source) {
+        name = source.readString();
+        packageName = source.readString();
+        labelRes = source.readInt();
+        nonLocalizedLabel
+                = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source);
+        icon = source.readInt();
+        metaData = source.readBundle();
+    }
+
+    public static class DisplayNameComparator
+            implements Comparator<PackageItemInfo> {
+        public DisplayNameComparator(PackageManager pm) {
+            mPM = pm;
+        }
+
+        public final int compare(PackageItemInfo aa, PackageItemInfo ab) {
+            CharSequence  sa = aa.loadLabel(mPM);
+            if (sa == null) sa = aa.name;
+            CharSequence  sb = ab.loadLabel(mPM);
+            if (sb == null) sb = ab.name;
+            return sCollator.compare(sa.toString(), sb.toString());
+        }
+
+        private final Collator   sCollator = Collator.getInstance();
+        private PackageManager   mPM;
+    }
+}
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
new file mode 100644
index 0000000..7287d9c
--- /dev/null
+++ b/core/java/android/content/pm/PackageManager.java
@@ -0,0 +1,1646 @@
+/*
+ * 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 android.content.pm;
+
+
+import android.app.PendingIntent;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.res.Resources;
+import android.content.res.XmlResourceParser;
+import android.graphics.drawable.Drawable;
+import android.net.Uri;
+import android.util.AndroidException;
+import android.util.DisplayMetrics;
+
+import java.io.File;
+import java.util.List;
+
+/**
+ * Class for retrieving various kinds of information related to the application
+ * packages that are currently installed on the device.
+ *
+ * You can find this class through {@link Context#getPackageManager}.
+ */
+public abstract class PackageManager {
+
+    /**
+     * This exception is thrown when a given package, application, or component
+     * name can not be found.
+     */
+    public static class NameNotFoundException extends AndroidException {
+        public NameNotFoundException() {
+        }
+
+        public NameNotFoundException(String name) {
+            super(name);
+        }
+    }
+
+    /**
+     * {@link PackageInfo} flag: return information about
+     * activities in the package in {@link PackageInfo#activities}.
+     */
+    public static final int GET_ACTIVITIES              = 0x00000001;
+
+    /**
+     * {@link PackageInfo} flag: return information about
+     * intent receivers in the package in
+     * {@link PackageInfo#receivers}.
+     */
+    public static final int GET_RECEIVERS               = 0x00000002;
+
+    /**
+     * {@link PackageInfo} flag: return information about
+     * services in the package in {@link PackageInfo#services}.
+     */
+    public static final int GET_SERVICES                = 0x00000004;
+
+    /**
+     * {@link PackageInfo} flag: return information about
+     * content providers in the package in
+     * {@link PackageInfo#providers}.
+     */
+    public static final int GET_PROVIDERS               = 0x00000008;
+
+    /**
+     * {@link PackageInfo} flag: return information about
+     * instrumentation in the package in
+     * {@link PackageInfo#instrumentation}.
+     */
+    public static final int GET_INSTRUMENTATION         = 0x00000010;
+
+    /**
+     * {@link PackageInfo} flag: return information about the
+     * intent filters supported by the activity.
+     */
+    public static final int GET_INTENT_FILTERS          = 0x00000020;
+
+    /**
+     * {@link PackageInfo} flag: return information about the
+     * signatures included in the package.
+     */
+    public static final int GET_SIGNATURES          = 0x00000040;
+
+    /**
+     * {@link ResolveInfo} flag: return the IntentFilter that
+     * was matched for a particular ResolveInfo in
+     * {@link ResolveInfo#filter}.
+     */
+    public static final int GET_RESOLVED_FILTER         = 0x00000040;
+
+    /**
+     * {@link ComponentInfo} flag: return the {@link ComponentInfo#metaData}
+     * data {@link android.os.Bundle}s that are associated with a component.
+     * This applies for any API returning a ComponentInfo subclass.
+     */
+    public static final int GET_META_DATA               = 0x00000080;
+
+    /**
+     * {@link PackageInfo} flag: return the
+     * {@link PackageInfo#gids group ids} that are associated with an
+     * application.
+     * This applies for any API returning an PackageInfo class, either
+     * directly or nested inside of another.
+     */
+    public static final int GET_GIDS                    = 0x00000100;
+
+    /**
+     * {@link PackageInfo} flag: include disabled components in the returned info.
+     */
+    public static final int GET_DISABLED_COMPONENTS     = 0x00000200;
+
+    /**
+     * {@link ApplicationInfo} flag: return the
+     * {@link ApplicationInfo#sharedLibraryFiles paths to the shared libraries}
+     * that are associated with an application.
+     * This applies for any API returning an ApplicationInfo class, either
+     * directly or nested inside of another.
+     */
+    public static final int GET_SHARED_LIBRARY_FILES    = 0x00000400;
+
+    /**
+     * {@link ProviderInfo} flag: return the
+     * {@link ProviderInfo#uriPermissionPatterns URI permission patterns}
+     * that are associated with a content provider.
+     * This applies for any API returning an ProviderInfo class, either
+     * directly or nested inside of another.
+     */
+    public static final int GET_URI_PERMISSION_PATTERNS  = 0x00000800;
+    /**
+     * {@link PackageInfo} flag: return information about
+     * permissions in the package in
+     * {@link PackageInfo#permissions}.
+     */
+    public static final int GET_PERMISSIONS               = 0x00001000;
+    
+    /**
+     * Flag parameter to retrieve all applications(even uninstalled ones) with data directories.
+     * This state could have resulted if applications have been deleted with flag 
+     * DONT_DELETE_DATA
+     * with a possibility of being replaced or reinstalled in future
+     */
+    public static final int GET_UNINSTALLED_PACKAGES = 0x00002000;
+    
+    /**
+     * {@link PackageInfo} flag: return information about
+     * hardware preferences
+     * {@link PackageInfo#configPreferences}
+     */
+    public static final int GET_CONFIGURATIONS = 0x00004000;
+
+    /**
+     * Permission check result: this is returned by {@link #checkPermission}
+     * if the permission has been granted to the given package.
+     */
+    public static final int PERMISSION_GRANTED = 0;
+
+    /**
+     * Permission check result: this is returned by {@link #checkPermission}
+     * if the permission has not been granted to the given package.
+     */
+    public static final int PERMISSION_DENIED = -1;
+
+    /**
+     * Signature check result: this is returned by {@link #checkSignatures}
+     * if the two packages have a matching signature.
+     */
+    public static final int SIGNATURE_MATCH = 0;
+
+    /**
+     * Signature check result: this is returned by {@link #checkSignatures}
+     * if neither of the two packages is signed.
+     */
+    public static final int SIGNATURE_NEITHER_SIGNED = 1;
+
+    /**
+     * Signature check result: this is returned by {@link #checkSignatures}
+     * if the first package is not signed, but the second is.
+     */
+    public static final int SIGNATURE_FIRST_NOT_SIGNED = -1;
+
+    /**
+     * Signature check result: this is returned by {@link #checkSignatures}
+     * if the second package is not signed, but the first is.
+     */
+    public static final int SIGNATURE_SECOND_NOT_SIGNED = -2;
+
+    /**
+     * Signature check result: this is returned by {@link #checkSignatures}
+     * if both packages are signed but there is no matching signature.
+     */
+    public static final int SIGNATURE_NO_MATCH = -3;
+
+    /**
+     * Signature check result: this is returned by {@link #checkSignatures}
+     * if either of the given package names are not valid.
+     */
+    public static final int SIGNATURE_UNKNOWN_PACKAGE = -4;
+
+    /**
+     * Resolution and querying flag: if set, only filters that support the
+     * {@link android.content.Intent#CATEGORY_DEFAULT} will be considered for
+     * matching.  This is a synonym for including the CATEGORY_DEFAULT in your
+     * supplied Intent.
+     */
+    public static final int MATCH_DEFAULT_ONLY   = 0x00010000;
+
+    public static final int COMPONENT_ENABLED_STATE_DEFAULT = 0;
+    public static final int COMPONENT_ENABLED_STATE_ENABLED = 1;
+    public static final int COMPONENT_ENABLED_STATE_DISABLED = 2;
+
+    /**
+     * Flag parameter for {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)} to
+     * indicate that this package should be installed as forward locked, i.e. only the app itself
+     * should have access to it's code and non-resource assets.
+     */
+    public static final int FORWARD_LOCK_PACKAGE = 0x00000001;
+
+    /**
+     * Flag parameter for {@link #installPackage} to indicate that you want to replace an already
+     * installed package, if one exists
+     */
+    public static final int REPLACE_EXISTING_PACKAGE = 0x00000002;
+
+    /**
+     * Flag parameter for
+     * {@link #setComponentEnabledSetting(android.content.ComponentName, int, int)} to indicate
+     * that you don't want to kill the app containing the component.  Be careful when you set this
+     * since changing component states can make the containing application's behavior unpredictable.
+     */
+    public static final int DONT_KILL_APP = 0x00000001;
+
+    /**
+     * Installation return code: this is passed to the {@link IPackageInstallObserver} by
+     * {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)} on success.
+     */
+    public static final int INSTALL_SUCCEEDED = 1;
+
+    /**
+     * Installation return code: this is passed to the {@link IPackageInstallObserver} by
+     * {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)} if the package is
+     * already installed.
+     */
+    public static final int INSTALL_FAILED_ALREADY_EXISTS = -1;
+
+    /**
+     * Installation return code: this is passed to the {@link IPackageInstallObserver} by
+     * {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)} if the package archive
+     * file is invalid.
+     */
+    public static final int INSTALL_FAILED_INVALID_APK = -2;
+
+    /**
+     * Installation return code: this is passed to the {@link IPackageInstallObserver} by
+     * {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)} if the URI passed in
+     * is invalid.
+     */
+    public static final int INSTALL_FAILED_INVALID_URI = -3;
+
+    /**
+     * Installation return code: this is passed to the {@link IPackageInstallObserver} by
+     * {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)} if the package manager
+     * service found that the device didn't have enough storage space to install the app
+     */
+    public static final int INSTALL_FAILED_INSUFFICIENT_STORAGE = -4;
+
+    /**
+     * Installation return code: this is passed to the {@link IPackageInstallObserver} by
+     * {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)} if a
+     * package is already installed with the same name.
+     */
+    public static final int INSTALL_FAILED_DUPLICATE_PACKAGE = -5;
+
+    /**
+     * Installation return code: this is passed to the {@link IPackageInstallObserver} by
+     * {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)} if
+     * the requested shared user does not exist.
+     */
+    public static final int INSTALL_FAILED_NO_SHARED_USER = -6;
+
+    /**
+     * Installation return code: this is passed to the {@link IPackageInstallObserver} by
+     * {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)} if
+     * a previously installed package of the same name has a different signature
+     * than the new package (and the old package's data was not removed).
+     */
+    public static final int INSTALL_FAILED_UPDATE_INCOMPATIBLE = -7;
+
+    /**
+     * Installation return code: this is passed to the {@link IPackageInstallObserver} by
+     * {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)} if
+     * the new package is requested a shared user which is already installed on the
+     * device and does not have matching signature.
+     */
+    public static final int INSTALL_FAILED_SHARED_USER_INCOMPATIBLE = -8;
+
+    /**
+     * Installation return code: this is passed to the {@link IPackageInstallObserver} by
+     * {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)} if
+     * the new package uses a shared library that is not available.
+     */
+    public static final int INSTALL_FAILED_MISSING_SHARED_LIBRARY = -9;
+
+    /**
+     * Installation return code: this is passed to the {@link IPackageInstallObserver} by
+     * {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)} if
+     * the new package uses a shared library that is not available.
+     */
+    public static final int INSTALL_FAILED_REPLACE_COULDNT_DELETE = -10;
+
+    /**
+     * Installation return code: this is passed to the {@link IPackageInstallObserver} by
+     * {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)} if
+     * the new package failed while optimizing and validating its dex files,
+     * either because there was not enough storage or the validation failed.
+     */
+    public static final int INSTALL_FAILED_DEXOPT = -11;
+
+    /**
+     * Installation return code: this is passed to the {@link IPackageInstallObserver} by
+     * {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)} if
+     * the new package failed because the current SDK version is older than
+     * that required by the package.
+     */
+    public static final int INSTALL_FAILED_OLDER_SDK = -12;
+
+    /**
+     * Installation parse return code: this is passed to the {@link IPackageInstallObserver} by
+     * {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)}
+     * if the parser was given a path that is not a file, or does not end with the expected
+     * '.apk' extension.
+     */
+    public static final int INSTALL_PARSE_FAILED_NOT_APK = -100;
+
+    /**
+     * Installation parse return code: this is passed to the {@link IPackageInstallObserver} by
+     * {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)}
+     * if the parser was unable to retrieve the AndroidManifest.xml file.
+     */
+    public static final int INSTALL_PARSE_FAILED_BAD_MANIFEST = -101;
+
+    /**
+     * Installation parse return code: this is passed to the {@link IPackageInstallObserver} by
+     * {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)}
+     * if the parser encountered an unexpected exception.
+     */
+    public static final int INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION = -102;
+
+    /**
+     * Installation parse return code: this is passed to the {@link IPackageInstallObserver} by
+     * {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)}
+     * if the parser did not find any certificates in the .apk.
+     */
+    public static final int INSTALL_PARSE_FAILED_NO_CERTIFICATES = -103;
+
+    /**
+     * Installation parse return code: this is passed to the {@link IPackageInstallObserver} by
+     * {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)}
+     * if the parser found inconsistent certificates on the files in the .apk.
+     */
+    public static final int INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES = -104;
+
+    /**
+     * Installation parse return code: this is passed to the {@link IPackageInstallObserver} by
+     * {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)}
+     * if the parser encountered a CertificateEncodingException in one of the
+     * files in the .apk.
+     */
+    public static final int INSTALL_PARSE_FAILED_CERTIFICATE_ENCODING = -105;
+
+    /**
+     * Installation parse return code: this is passed to the {@link IPackageInstallObserver} by
+     * {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)}
+     * if the parser encountered a bad or missing package name in the manifest.
+     */
+    public static final int INSTALL_PARSE_FAILED_BAD_PACKAGE_NAME = -106;
+
+    /**
+     * Installation parse return code: this is passed to the {@link IPackageInstallObserver} by
+     * {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)}
+     * if the parser encountered a bad shared user id name in the manifest.
+     */
+    public static final int INSTALL_PARSE_FAILED_BAD_SHARED_USER_ID = -107;
+
+    /**
+     * Installation parse return code: this is passed to the {@link IPackageInstallObserver} by
+     * {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)}
+     * if the parser encountered some structural problem in the manifest.
+     */
+    public static final int INSTALL_PARSE_FAILED_MANIFEST_MALFORMED = -108;
+
+    /**
+     * Installation parse return code: this is passed to the {@link IPackageInstallObserver} by
+     * {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)}
+     * if the parser did not find any actionable tags (instrumentation or application)
+     * in the manifest.
+     */
+    public static final int INSTALL_PARSE_FAILED_MANIFEST_EMPTY = -109;
+
+    /**
+     * Indicates the state of installation. Used by PackageManager to
+     * figure out incomplete installations. Say a package is being installed
+     * (the state is set to PKG_INSTALL_INCOMPLETE) and remains so till
+     * the package installation is successful or unsuccesful lin which case
+     * the PackageManager will no longer maintain state information associated
+     * with the package. If some exception(like device freeze or battery being
+     * pulled out) occurs during installation of a package, the PackageManager
+     * needs this information to clean up the previously failed installation.
+     */
+    public static final int PKG_INSTALL_INCOMPLETE = 0;
+    public static final int PKG_INSTALL_COMPLETE = 1;
+
+    /**
+     * Flag parameter for {@link #deletePackage} to indicate that you don't want to delete the
+     * package's data directory.
+     *
+     * @hide
+     */
+    public static final int DONT_DELETE_DATA = 0x00000001;
+
+    /**
+     * Retrieve overall information about an application package that is
+     * installed on the system.
+     *
+     * <p>Throws {@link NameNotFoundException} if a package with the given
+     * name can not be found on the system.
+     *
+     * @param packageName The full name (i.e. com.google.apps.contacts) of the
+     *                    desired package.
+
+     * @param flags Additional option flags. Use any combination of
+     * {@link #GET_ACTIVITIES},
+     * {@link #GET_GIDS},
+     * {@link #GET_CONFIGURATIONS},
+     * {@link #GET_INSTRUMENTATION},
+     * {@link #GET_PERMISSIONS},
+     * {@link #GET_PROVIDERS},
+     * {@link #GET_RECEIVERS},
+     * {@link #GET_SERVICES},
+     * {@link #GET_SIGNATURES},
+     * {@link #GET_UNINSTALLED_PACKAGES} to modify the data returned.
+     *
+     * @return Returns a PackageInfo object containing information about the package.
+     *         If flag GET_UNINSTALLED_PACKAGES is set and  if the package is not
+     *         found in the list of installed applications, the package information is
+     *         retrieved from the list of uninstalled applications(which includes 
+     *         installed applications as well as applications
+     *         with data directory ie applications which had been
+     *         deleted with DONT_DELTE_DATA flag set).
+     *         
+     * @see #GET_ACTIVITIES
+     * @see #GET_GIDS
+     * @see #GET_CONFIGURATIONS
+     * @see #GET_INSTRUMENTATION
+     * @see #GET_PERMISSIONS
+     * @see #GET_PROVIDERS
+     * @see #GET_RECEIVERS
+     * @see #GET_SERVICES
+     * @see #GET_SIGNATURES
+     * @see #GET_UNINSTALLED_PACKAGES
+     *                  
+     */
+    public abstract PackageInfo getPackageInfo(String packageName, int flags)
+            throws NameNotFoundException;
+
+    /**
+     * Return a "good" intent to launch a front-door activity in a package,
+     * for use for example to implement an "open" button when browsing through
+     * packages.  The current implementation will look first for a main
+     * activity in the category {@link Intent#CATEGORY_INFO}, next for a
+     * main activity in the category {@link Intent#CATEGORY_LAUNCHER}, or return
+     * null if neither are found.
+     * 
+     * <p>Throws {@link NameNotFoundException} if a package with the given
+     * name can not be found on the system.
+     *
+     * @param packageName The name of the package to inspect.
+     * 
+     * @return Returns either a fully-qualified Intent that can be used to
+     * launch the main activity in the package, or null if the package does
+     * not contain such an activity.
+     */
+    public abstract Intent getLaunchIntentForPackage(String packageName)
+            throws NameNotFoundException;
+    
+    /**
+     * Return an array of all of the secondary group-ids that have been
+     * assigned to a package.
+     *
+     * <p>Throws {@link NameNotFoundException} if a package with the given
+     * name can not be found on the system.
+     *
+     * @param packageName The full name (i.e. com.google.apps.contacts) of the
+     *                    desired package.
+     *
+     * @return Returns an int array of the assigned gids, or null if there
+     * are none.
+     */
+    public abstract int[] getPackageGids(String packageName)
+            throws NameNotFoundException;
+
+    /**
+     * Retrieve all of the information we know about a particular permission.
+     *
+     * <p>Throws {@link NameNotFoundException} if a permission with the given
+     * name can not be found on the system.
+     *
+     * @param name The fully qualified name (i.e. com.google.permission.LOGIN)
+     *             of the permission you are interested in.
+     * @param flags Additional option flags.  Use {@link #GET_META_DATA} to
+     * retrieve any meta-data associated with the permission.
+     *
+     * @return Returns a {@link PermissionInfo} containing information about the
+     *         permission.
+     */
+    public abstract PermissionInfo getPermissionInfo(String name, int flags)
+            throws NameNotFoundException;
+
+    /**
+     * Query for all of the permissions associated with a particular group.
+     *
+     * <p>Throws {@link NameNotFoundException} if the given group does not
+     * exist.
+     *
+     * @param group The fully qualified name (i.e. com.google.permission.LOGIN)
+     *             of the permission group you are interested in.  Use null to
+     *             find all of the permissions not associated with a group.
+     * @param flags Additional option flags.  Use {@link #GET_META_DATA} to
+     * retrieve any meta-data associated with the permissions.
+     *
+     * @return Returns a list of {@link PermissionInfo} containing information
+     * about all of the permissions in the given group.
+     */
+    public abstract List<PermissionInfo> queryPermissionsByGroup(String group,
+            int flags) throws NameNotFoundException;
+
+    /**
+     * Retrieve all of the information we know about a particular group of
+     * permissions.
+     *
+     * <p>Throws {@link NameNotFoundException} if a permission group with the given
+     * name can not be found on the system.
+     *
+     * @param name The fully qualified name (i.e. com.google.permission_group.APPS)
+     *             of the permission you are interested in.
+     * @param flags Additional option flags.  Use {@link #GET_META_DATA} to
+     * retrieve any meta-data associated with the permission group.
+     *
+     * @return Returns a {@link PermissionGroupInfo} containing information
+     * about the permission.
+     */
+    public abstract PermissionGroupInfo getPermissionGroupInfo(String name,
+            int flags) throws NameNotFoundException;
+
+    /**
+     * Retrieve all of the known permission groups in the system.
+     *
+     * @param flags Additional option flags.  Use {@link #GET_META_DATA} to
+     * retrieve any meta-data associated with the permission group.
+     *
+     * @return Returns a list of {@link PermissionGroupInfo} containing
+     * information about all of the known permission groups.
+     */
+    public abstract List<PermissionGroupInfo> getAllPermissionGroups(int flags);
+
+    /**
+     * Retrieve all of the information we know about a particular
+     * package/application.
+     *
+     * <p>Throws {@link NameNotFoundException} if an application with the given
+     * package name can not be found on the system.
+     *
+     * @param packageName The full name (i.e. com.google.apps.contacts) of an
+     *                    application.
+     * @param flags Additional option flags. Use any combination of 
+     * {@link #GET_META_DATA}, {@link #GET_SHARED_LIBRARY_FILES},
+     * {@link #GET_UNINSTALLED_PACKAGES} to modify the data returned.
+     *
+     * @return  {@link ApplicationInfo} Returns ApplicationInfo object containing 
+     *         information about the package.
+     *         If flag GET_UNINSTALLED_PACKAGES is set and  if the package is not
+     *         found in the list of installed applications, 
+     *         the application information is retrieved from the 
+     *         list of uninstalled applications(which includes 
+     *         installed applications as well as applications
+     *         with data directory ie applications which had been
+     *         deleted with DONT_DELTE_DATA flag set).
+     *
+     * @see #GET_META_DATA
+     * @see #GET_SHARED_LIBRARY_FILES
+     * @see #GET_UNINSTALLED_PACKAGES
+     */
+    public abstract ApplicationInfo getApplicationInfo(String packageName,
+            int flags) throws NameNotFoundException;
+
+    /**
+     * Retrieve all of the information we know about a particular activity
+     * class.
+     *
+     * <p>Throws {@link NameNotFoundException} if an activity with the given
+     * class name can not be found on the system.
+     *
+     * @param className The full name (i.e.
+     *                  com.google.apps.contacts.ContactsList) of an Activity
+     *                  class.
+     * @param flags Additional option flags. Use any combination of 
+     * {@link #GET_META_DATA}, {@link #GET_SHARED_LIBRARY_FILES},
+     * to modify the data (in ApplicationInfo) returned.
+     *
+     * @return {@link ActivityInfo} containing information about the activity.
+     *
+     * @see #GET_INTENT_FILTERS
+     * @see #GET_META_DATA
+     * @see #GET_SHARED_LIBRARY_FILES
+     */
+    public abstract ActivityInfo getActivityInfo(ComponentName className,
+            int flags) throws NameNotFoundException;
+
+    /**
+     * Retrieve all of the information we know about a particular receiver
+     * class.
+     *
+     * <p>Throws {@link NameNotFoundException} if a receiver with the given
+     * class name can not be found on the system.
+     *
+     * @param className The full name (i.e.
+     *                  com.google.apps.contacts.CalendarAlarm) of a Receiver
+     *                  class.
+     * @param flags Additional option flags.  Use any combination of 
+     * {@link #GET_META_DATA}, {@link #GET_SHARED_LIBRARY_FILES},
+     * to modify the data returned.
+     *
+     * @return {@link ActivityInfo} containing information about the receiver.
+     *
+     * @see #GET_INTENT_FILTERS
+     * @see #GET_META_DATA
+     * @see #GET_SHARED_LIBRARY_FILES
+     */
+    public abstract ActivityInfo getReceiverInfo(ComponentName className,
+            int flags) throws NameNotFoundException;
+
+    /**
+     * Retrieve all of the information we know about a particular service
+     * class.
+     *
+     * <p>Throws {@link NameNotFoundException} if a service with the given
+     * class name can not be found on the system.
+     *
+     * @param className The full name (i.e.
+     *                  com.google.apps.media.BackgroundPlayback) of a Service
+     *                  class.
+     * @param flags Additional option flags.  Use any combination of 
+     * {@link #GET_META_DATA}, {@link #GET_SHARED_LIBRARY_FILES},
+     * to modify the data returned.
+     *
+     * @return ServiceInfo containing information about the service.
+     * 
+     * @see #GET_META_DATA
+     * @see #GET_SHARED_LIBRARY_FILES
+     */
+    public abstract ServiceInfo getServiceInfo(ComponentName className,
+            int flags) throws NameNotFoundException;
+
+    /**
+     * Return a List of all packages that are installed
+     * on the device.
+     *
+     * @param flags Additional option flags. Use any combination of
+     * {@link #GET_ACTIVITIES},
+     * {@link #GET_GIDS},
+     * {@link #GET_CONFIGURATIONS},
+     * {@link #GET_INSTRUMENTATION},
+     * {@link #GET_PERMISSIONS},
+     * {@link #GET_PROVIDERS},
+     * {@link #GET_RECEIVERS},
+     * {@link #GET_SERVICES},
+     * {@link #GET_SIGNATURES},
+     * {@link #GET_UNINSTALLED_PACKAGES} to modify the data returned.
+     *
+     * @return A List of PackageInfo objects, one for each package that is
+     *         installed on the device.  In the unlikely case of there being no
+     *         installed packages, an empty list is returned. 
+     *         If flag GET_UNINSTALLED_PACKAGES is set, a list of all
+     *         applications including those deleted with DONT_DELETE_DATA
+     *         (partially installed apps with data directory) will be returned.
+     *
+     * @see #GET_ACTIVITIES
+     * @see #GET_GIDS
+     * @see #GET_CONFIGURATIONS
+     * @see #GET_INSTRUMENTATION
+     * @see #GET_PERMISSIONS
+     * @see #GET_PROVIDERS
+     * @see #GET_RECEIVERS
+     * @see #GET_SERVICES
+     * @see #GET_SIGNATURES
+     * @see #GET_UNINSTALLED_PACKAGES
+     * 
+     */
+    public abstract List<PackageInfo> getInstalledPackages(int flags);
+
+    /**
+     * Check whether a particular package has been granted a particular
+     * permission.
+     *
+     * @param permName The name of the permission you are checking for,
+     * @param pkgName The name of the package you are checking against.
+     *
+     * @return If the package has the permission, PERMISSION_GRANTED is
+     * returned.  If it does not have the permission, PERMISSION_DENIED
+     * is returned.
+     *
+     * @see #PERMISSION_GRANTED
+     * @see #PERMISSION_DENIED
+     */
+    public abstract int checkPermission(String permName, String pkgName);
+
+    /**
+     * Add a new dynamic permission to the system.  For this to work, your
+     * package must have defined a permission tree through the
+     * {@link android.R.styleable#AndroidManifestPermissionTree
+     * &lt;permission-tree&gt;} tag in its manifest.  A package can only add
+     * permissions to trees that were defined by either its own package or
+     * another with the same user id; a permission is in a tree if it
+     * matches the name of the permission tree + ".": for example,
+     * "com.foo.bar" is a member of the permission tree "com.foo".
+     *
+     * <p>It is good to make your permission tree name descriptive, because you
+     * are taking possession of that entire set of permission names.  Thus, it
+     * must be under a domain you control, with a suffix that will not match
+     * any normal permissions that may be declared in any applications that
+     * are part of that domain.
+     *
+     * <p>New permissions must be added before
+     * any .apks are installed that use those permissions.  Permissions you
+     * add through this method are remembered across reboots of the device.
+     * If the given permission already exists, the info you supply here
+     * will be used to update it.
+     *
+     * @param info Description of the permission to be added.
+     *
+     * @return Returns true if a new permission was created, false if an
+     * existing one was updated.
+     *
+     * @throws SecurityException if you are not allowed to add the
+     * given permission name.
+     *
+     * @see #removePermission(String)
+     */
+    public abstract boolean addPermission(PermissionInfo info);
+
+    /**
+     * Removes a permission that was previously added with
+     * {@link #addPermission(PermissionInfo)}.  The same ownership rules apply
+     * -- you are only allowed to remove permissions that you are allowed
+     * to add.
+     *
+     * @param name The name of the permission to remove.
+     *
+     * @throws SecurityException if you are not allowed to remove the
+     * given permission name.
+     *
+     * @see #addPermission(PermissionInfo)
+     */
+    public abstract void removePermission(String name);
+
+    /**
+     * Compare the signatures of two packages to determine if the same
+     * signature appears in both of them.  If they do contain the same
+     * signature, then they are allowed special privileges when working
+     * with each other: they can share the same user-id, run instrumentation
+     * against each other, etc.
+     *
+     * @param pkg1 First package name whose signature will be compared.
+     * @param pkg2 Second package name whose signature will be compared.
+     * @return Returns an integer indicating whether there is a matching
+     * signature: the value is >= 0 if there is a match (or neither package
+     * is signed), or < 0 if there is not a match.  The match result can be
+     * further distinguished with the success (>= 0) constants
+     * {@link #SIGNATURE_MATCH}, {@link #SIGNATURE_NEITHER_SIGNED}; or
+     * failure (< 0) constants {@link #SIGNATURE_FIRST_NOT_SIGNED},
+     * {@link #SIGNATURE_SECOND_NOT_SIGNED}, {@link #SIGNATURE_NO_MATCH},
+     * or {@link #SIGNATURE_UNKNOWN_PACKAGE}.
+     *
+     * @see #SIGNATURE_MATCH
+     * @see #SIGNATURE_NEITHER_SIGNED
+     * @see #SIGNATURE_FIRST_NOT_SIGNED
+     * @see #SIGNATURE_SECOND_NOT_SIGNED
+     * @see #SIGNATURE_NO_MATCH
+     * @see #SIGNATURE_UNKNOWN_PACKAGE
+     */
+    public abstract int checkSignatures(String pkg1, String pkg2);
+
+    /**
+     * Retrieve the names of all packages that are associated with a particular
+     * user id.  In most cases, this will be a single package name, the package
+     * that has been assigned that user id.  Where there are multiple packages
+     * sharing the same user id through the "sharedUserId" mechanism, all
+     * packages with that id will be returned.
+     *
+     * @param uid The user id for which you would like to retrieve the
+     * associated packages.
+     *
+     * @return Returns an array of one or more packages assigned to the user
+     * id, or null if there are no known packages with the given id.
+     */
+    public abstract String[] getPackagesForUid(int uid);
+
+    /**
+     * Retrieve the official name associated with a user id.  This name is
+     * guaranteed to never change, though it is possibly for the underlying
+     * user id to be changed.  That is, if you are storing information about
+     * user ids in persistent storage, you should use the string returned
+     * by this function instead of the raw user-id.
+     *
+     * @param uid The user id for which you would like to retrieve a name.
+     * @return Returns a unique name for the given user id, or null if the
+     * user id is not currently assigned.
+     */
+    public abstract String getNameForUid(int uid);
+    
+    /**
+     * Return the user id associated with a shared user name. Multiple
+     * applications can specify a shared user name in their manifest and thus
+     * end up using a common uid. This might be used for new applications
+     * that use an existing shared user name and need to know the uid of the
+     * shared user.
+     *
+     * @param sharedUserName The shared user name whose uid is to be retrieved.
+     * @return Returns the uid associated with the shared user, or  NameNotFoundException
+     * if the shared user name is not being used by any installed packages
+     * @hide
+     */
+    public abstract int getUidForSharedUser(String sharedUserName)
+            throws NameNotFoundException;
+
+    /**
+     * Return a List of all application packages that are installed on the
+     * device. If flag GET_UNINSTALLED_PACKAGES has been set, a list of all
+     * applications including those deleted with DONT_DELETE_DATA(partially
+     * installed apps with data directory) will be returned.
+     * 
+     * @param flags Additional option flags. Use any combination of 
+     * {@link #GET_META_DATA}, {@link #GET_SHARED_LIBRARY_FILES},
+     * {link #GET_UNINSTALLED_PACKAGES} to modify the data returned.
+     *
+     * @return A List of ApplicationInfo objects, one for each application that
+     *         is installed on the device.  In the unlikely case of there being
+     *         no installed applications, an empty list is returned. 
+     *         If flag GET_UNINSTALLED_PACKAGES is set, a list of all
+     *         applications including those deleted with DONT_DELETE_DATA
+     *         (partially installed apps with data directory) will be returned.
+     *         
+     * @see #GET_META_DATA
+     * @see #GET_SHARED_LIBRARY_FILES
+     * @see #GET_UNINSTALLED_PACKAGES
+     */
+    public abstract List<ApplicationInfo> getInstalledApplications(int flags);
+    
+    /**
+     * Get a list of shared libraries that are available on the
+     * system.
+     * 
+     * @return An array of shared library names that are
+     * available on the system, or null if none are installed.
+     * 
+     */
+    public abstract String[] getSystemSharedLibraryNames();
+
+    /**
+     * Determine the best action to perform for a given Intent.  This is how
+     * {@link Intent#resolveActivity} finds an activity if a class has not
+     * been explicitly specified.
+     *
+     * @param intent An intent containing all of the desired specification
+     *               (action, data, type, category, and/or component).
+     * @param flags Additional option flags.  The most important is
+     *                    MATCH_DEFAULT_ONLY, to limit the resolution to only
+     *                    those activities that support the CATEGORY_DEFAULT.
+     *
+     * @return Returns a ResolveInfo containing the final activity intent that
+     *         was determined to be the best action.  Returns null if no
+     *         matching activity was found.
+     *
+     * @see #MATCH_DEFAULT_ONLY
+     * @see #GET_INTENT_FILTERS
+     * @see #GET_RESOLVED_FILTER
+     */
+    public abstract ResolveInfo resolveActivity(Intent intent, int flags);
+
+    /**
+     * Retrieve all activities that can be performed for the given intent.
+     *
+     * @param intent The desired intent as per resolveActivity().
+     * @param flags Additional option flags.  The most important is
+     *                    MATCH_DEFAULT_ONLY, to limit the resolution to only
+     *                    those activities that support the CATEGORY_DEFAULT.
+     *
+     * @return A List<ResolveInfo> containing one entry for each matching
+     *         Activity. These are ordered from best to worst match -- that
+     *         is, the first item in the list is what is returned by
+     *         resolveActivity().  If there are no matching activities, an empty
+     *         list is returned.
+     *
+     * @see #MATCH_DEFAULT_ONLY
+     * @see #GET_INTENT_FILTERS
+     * @see #GET_RESOLVED_FILTER
+     */
+    public abstract List<ResolveInfo> queryIntentActivities(Intent intent,
+            int flags);
+
+    /**
+     * Retrieve a set of activities that should be presented to the user as
+     * similar options.  This is like {@link #queryIntentActivities}, except it
+     * also allows you to supply a list of more explicit Intents that you would
+     * like to resolve to particular options, and takes care of returning the
+     * final ResolveInfo list in a reasonable order, with no duplicates, based
+     * on those inputs.
+     *
+     * @param caller The class name of the activity that is making the
+     *               request.  This activity will never appear in the output
+     *               list.  Can be null.
+     * @param specifics An array of Intents that should be resolved to the
+     *                  first specific results.  Can be null.
+     * @param intent The desired intent as per resolveActivity().
+     * @param flags Additional option flags.  The most important is
+     *                    MATCH_DEFAULT_ONLY, to limit the resolution to only
+     *                    those activities that support the CATEGORY_DEFAULT.
+     *
+     * @return A List<ResolveInfo> containing one entry for each matching
+     *         Activity. These are ordered first by all of the intents resolved
+     *         in <var>specifics</var> and then any additional activities that
+     *         can handle <var>intent</var> but did not get included by one of
+     *         the <var>specifics</var> intents.  If there are no matching
+     *         activities, an empty list is returned.
+     *
+     * @see #MATCH_DEFAULT_ONLY
+     * @see #GET_INTENT_FILTERS
+     * @see #GET_RESOLVED_FILTER
+     */
+    public abstract List<ResolveInfo> queryIntentActivityOptions(
+            ComponentName caller, Intent[] specifics, Intent intent, int flags);
+
+    /**
+     * Retrieve all receivers that can handle a broadcast of the given intent.
+     *
+     * @param intent The desired intent as per resolveActivity().
+     * @param flags Additional option flags.  The most important is
+     *                    MATCH_DEFAULT_ONLY, to limit the resolution to only
+     *                    those activities that support the CATEGORY_DEFAULT.
+     *
+     * @return A List<ResolveInfo> containing one entry for each matching
+     *         Receiver. These are ordered from first to last in priority.  If
+     *         there are no matching receivers, an empty list is returned.
+     *
+     * @see #MATCH_DEFAULT_ONLY
+     * @see #GET_INTENT_FILTERS
+     * @see #GET_RESOLVED_FILTER
+     */
+    public abstract List<ResolveInfo> queryBroadcastReceivers(Intent intent,
+            int flags);
+
+    /**
+     * Determine the best service to handle for a given Intent.
+     *
+     * @param intent An intent containing all of the desired specification
+     *               (action, data, type, category, and/or component).
+     * @param flags Additional option flags.
+     *
+     * @return Returns a ResolveInfo containing the final service intent that
+     *         was determined to be the best action.  Returns null if no
+     *         matching service was found.
+     *
+     * @see #GET_INTENT_FILTERS
+     * @see #GET_RESOLVED_FILTER
+     */
+    public abstract ResolveInfo resolveService(Intent intent, int flags);
+
+    /**
+     * Retrieve all services that can match the given intent.
+     *
+     * @param intent The desired intent as per resolveService().
+     * @param flags Additional option flags.
+     *
+     * @return A List<ResolveInfo> containing one entry for each matching
+     *         ServiceInfo. These are ordered from best to worst match -- that
+     *         is, the first item in the list is what is returned by
+     *         resolveService().  If there are no matching services, an empty
+     *         list is returned.
+     *
+     * @see #GET_INTENT_FILTERS
+     * @see #GET_RESOLVED_FILTER
+     */
+    public abstract List<ResolveInfo> queryIntentServices(Intent intent,
+            int flags);
+
+    /**
+     * Find a single content provider by its base path name.
+     *
+     * @param name The name of the provider to find.
+     * @param flags Additional option flags.  Currently should always be 0.
+     *
+     * @return ContentProviderInfo Information about the provider, if found,
+     *         else null.
+     */
+    public abstract ProviderInfo resolveContentProvider(String name,
+            int flags);
+
+    /**
+     * Retrieve content provider information.
+     *
+     * <p><em>Note: unlike most other methods, an empty result set is indicated
+     * by a null return instead of an empty list.</em>
+     *
+     * @param processName If non-null, limits the returned providers to only
+     *                    those that are hosted by the given process.  If null,
+     *                    all content providers are returned.
+     * @param uid If <var>processName</var> is non-null, this is the required
+     *        uid owning the requested content providers.
+     * @param flags Additional option flags.  Currently should always be 0.
+     *
+     * @return A List<ContentProviderInfo> containing one entry for each
+     *         content provider either patching <var>processName</var> or, if
+     *         <var>processName</var> is null, all known content providers.
+     *         <em>If there are no matching providers, null is returned.</em>
+     */
+    public abstract List<ProviderInfo> queryContentProviders(
+            String processName, int uid, int flags);
+
+    /**
+     * Retrieve all of the information we know about a particular
+     * instrumentation class.
+     *
+     * <p>Throws {@link NameNotFoundException} if instrumentation with the
+     * given class name can not be found on the system.
+     *
+     * @param className The full name (i.e.
+     *                  com.google.apps.contacts.InstrumentList) of an
+     *                  Instrumentation class.
+     * @param flags Additional option flags.  Currently should always be 0.
+     *
+     * @return InstrumentationInfo containing information about the
+     *         instrumentation.
+     */
+    public abstract InstrumentationInfo getInstrumentationInfo(
+            ComponentName className, int flags) throws NameNotFoundException;
+
+    /**
+     * Retrieve information about available instrumentation code.  May be used
+     * to retrieve either all instrumentation code, or only the code targeting
+     * a particular package.
+     *
+     * @param targetPackage If null, all instrumentation is returned; only the
+     *                      instrumentation targeting this package name is
+     *                      returned.
+     * @param flags Additional option flags.  Currently should always be 0.
+     *
+     * @return A List<InstrumentationInfo> containing one entry for each
+     *         matching available Instrumentation.  Returns an empty list if
+     *         there is no instrumentation available for the given package.
+     */
+    public abstract List<InstrumentationInfo> queryInstrumentation(
+            String targetPackage, int flags);
+
+    /**
+     * Retrieve an image from a package.  This is a low-level API used by
+     * the various package manager info structures (such as
+     * {@link ComponentInfo} to implement retrieval of their associated
+     * icon.
+     *
+     * @param packageName The name of the package that this icon is coming from.
+     * Can not be null.
+     * @param resid The resource identifier of the desired image.  Can not be 0.
+     * @param appInfo Overall information about <var>packageName</var>.  This
+     * may be null, in which case the application information will be retrieved
+     * for you if needed; if you already have this information around, it can
+     * be much more efficient to supply it here.
+     *
+     * @return Returns a Drawable holding the requested image.  Returns null if
+     * an image could not be found for any reason.
+     */
+    public abstract Drawable getDrawable(String packageName, int resid,
+            ApplicationInfo appInfo);
+
+    /**
+     * Retrieve the icon associated with an activity.  Given the full name of
+     * an activity, retrieves the information about it and calls
+     * {@link ComponentInfo#loadIcon ComponentInfo.loadIcon()} to return its icon.
+     * If the activity can not be found, NameNotFoundException is thrown.
+     *
+     * @param activityName Name of the activity whose icon is to be retrieved.
+     *
+     * @return Returns the image of the icon, or the default activity icon if
+     * it could not be found.  Does not return null.
+     * @throws NameNotFoundException Thrown if the resources for the given
+     * activity could not be loaded.
+     *
+     * @see #getActivityIcon(Intent)
+     */
+    public abstract Drawable getActivityIcon(ComponentName activityName)
+            throws NameNotFoundException;
+
+    /**
+     * Retrieve the icon associated with an Intent.  If intent.getClassName() is
+     * set, this simply returns the result of
+     * getActivityIcon(intent.getClassName()).  Otherwise it resolves the intent's
+     * component and returns the icon associated with the resolved component.
+     * If intent.getClassName() can not be found or the Intent can not be resolved
+     * to a component, NameNotFoundException is thrown.
+     *
+     * @param intent The intent for which you would like to retrieve an icon.
+     *
+     * @return Returns the image of the icon, or the default activity icon if
+     * it could not be found.  Does not return null.
+     * @throws NameNotFoundException Thrown if the resources for application
+     * matching the given intent could not be loaded.
+     *
+     * @see #getActivityIcon(ComponentName)
+     */
+    public abstract Drawable getActivityIcon(Intent intent)
+            throws NameNotFoundException;
+
+    /**
+     * Return the generic icon for an activity that is used when no specific
+     * icon is defined.
+     *
+     * @return Drawable Image of the icon.
+     */
+    public abstract Drawable getDefaultActivityIcon();
+
+    /**
+     * Retrieve the icon associated with an application.  If it has not defined
+     * an icon, the default app icon is returned.  Does not return null.
+     *
+     * @param info Information about application being queried.
+     *
+     * @return Returns the image of the icon, or the default application icon
+     * if it could not be found.
+     *
+     * @see #getApplicationIcon(String)
+     */
+    public abstract Drawable getApplicationIcon(ApplicationInfo info);
+
+    /**
+     * Retrieve the icon associated with an application.  Given the name of the
+     * application's package, retrieves the information about it and calls
+     * getApplicationIcon() to return its icon. If the application can not be
+     * found, NameNotFoundException is thrown.
+     *
+     * @param packageName Name of the package whose application icon is to be
+     *                    retrieved.
+     *
+     * @return Returns the image of the icon, or the default application icon
+     * if it could not be found.  Does not return null.
+     * @throws NameNotFoundException Thrown if the resources for the given
+     * application could not be loaded.
+     *
+     * @see #getApplicationIcon(ApplicationInfo)
+     */
+    public abstract Drawable getApplicationIcon(String packageName)
+            throws NameNotFoundException;
+
+    /**
+     * Retrieve text from a package.  This is a low-level API used by
+     * the various package manager info structures (such as
+     * {@link ComponentInfo} to implement retrieval of their associated
+     * labels and other text.
+     *
+     * @param packageName The name of the package that this text is coming from.
+     * Can not be null.
+     * @param resid The resource identifier of the desired text.  Can not be 0.
+     * @param appInfo Overall information about <var>packageName</var>.  This
+     * may be null, in which case the application information will be retrieved
+     * for you if needed; if you already have this information around, it can
+     * be much more efficient to supply it here.
+     *
+     * @return Returns a CharSequence holding the requested text.  Returns null
+     * if the text could not be found for any reason.
+     */
+    public abstract CharSequence getText(String packageName, int resid,
+            ApplicationInfo appInfo);
+
+    /**
+     * Retrieve an XML file from a package.  This is a low-level API used to
+     * retrieve XML meta data.
+     *
+     * @param packageName The name of the package that this xml is coming from.
+     * Can not be null.
+     * @param resid The resource identifier of the desired xml.  Can not be 0.
+     * @param appInfo Overall information about <var>packageName</var>.  This
+     * may be null, in which case the application information will be retrieved
+     * for you if needed; if you already have this information around, it can
+     * be much more efficient to supply it here.
+     *
+     * @return Returns an XmlPullParser allowing you to parse out the XML
+     * data.  Returns null if the xml resource could not be found for any
+     * reason.
+     */
+    public abstract XmlResourceParser getXml(String packageName, int resid,
+            ApplicationInfo appInfo);
+
+    /**
+     * Return the label to use for this application.
+     *
+     * @return Returns the label associated with this application, or null if
+     * it could not be found for any reason.
+     * @param info The application to get the label of
+     */
+    public abstract CharSequence getApplicationLabel(ApplicationInfo info);
+
+    /**
+     * Retrieve the resources associated with an activity.  Given the full
+     * name of an activity, retrieves the information about it and calls
+     * getResources() to return its application's resources.  If the activity
+     * can not be found, NameNotFoundException is thrown.
+     *
+     * @param activityName Name of the activity whose resources are to be
+     *                     retrieved.
+     *
+     * @return Returns the application's Resources.
+     * @throws NameNotFoundException Thrown if the resources for the given
+     * application could not be loaded.
+     *
+     * @see #getResourcesForApplication(ApplicationInfo)
+     */
+    public abstract Resources getResourcesForActivity(ComponentName activityName)
+            throws NameNotFoundException;
+
+    /**
+     * Retrieve the resources for an application.  Throws NameNotFoundException
+     * if the package is no longer installed.
+     *
+     * @param app Information about the desired application.
+     *
+     * @return Returns the application's Resources.
+     * @throws NameNotFoundException Thrown if the resources for the given
+     * application could not be loaded (most likely because it was uninstalled).
+     */
+    public abstract Resources getResourcesForApplication(ApplicationInfo app)
+            throws NameNotFoundException;
+
+    /**
+     * Retrieve the resources associated with an application.  Given the full
+     * package name of an application, retrieves the information about it and
+     * calls getResources() to return its application's resources.  If the
+     * appPackageName can not be found, NameNotFoundException is thrown.
+     *
+     * @param appPackageName Package name of the application whose resources
+     *                       are to be retrieved.
+     *
+     * @return Returns the application's Resources.
+     * @throws NameNotFoundException Thrown if the resources for the given
+     * application could not be loaded.
+     *
+     * @see #getResourcesForApplication(ApplicationInfo)
+     */
+    public abstract Resources getResourcesForApplication(String appPackageName)
+            throws NameNotFoundException;
+
+    /**
+     * Retrieve overall information about an application package defined
+     * in a package archive file
+     *
+     * @param archiveFilePath The path to the archive file
+     * @param flags Additional option flags. Use any combination of
+     * {@link #GET_ACTIVITIES},
+     * {@link #GET_GIDS},
+     * {@link #GET_CONFIGURATIONS},
+     * {@link #GET_INSTRUMENTATION},
+     * {@link #GET_PERMISSIONS},
+     * {@link #GET_PROVIDERS},
+     * {@link #GET_RECEIVERS},
+     * {@link #GET_SERVICES},
+     * {@link #GET_SIGNATURES}, to modify the data returned.
+     *
+     * @return Returns the information about the package. Returns
+     * null if the package could not be successfully parsed.
+     *
+     * @see #GET_ACTIVITIES
+     * @see #GET_GIDS
+     * @see #GET_CONFIGURATIONS
+     * @see #GET_INSTRUMENTATION
+     * @see #GET_PERMISSIONS
+     * @see #GET_PROVIDERS
+     * @see #GET_RECEIVERS
+     * @see #GET_SERVICES
+     * @see #GET_SIGNATURES
+     * 
+     */
+    public PackageInfo getPackageArchiveInfo(String archiveFilePath, int flags) {
+        PackageParser packageParser = new PackageParser(archiveFilePath);
+        DisplayMetrics metrics = new DisplayMetrics();
+        metrics.setToDefaults();
+        final File sourceFile = new File(archiveFilePath);
+        PackageParser.Package pkg = packageParser.parsePackage(
+                sourceFile, archiveFilePath, metrics, 0);
+        if (pkg == null) {
+            return null;
+        }
+        return PackageParser.generatePackageInfo(pkg, null, flags);
+    }
+
+    /**
+     * Install a package. Since this may take a little while, the result will
+     * be posted back to the given observer.  An installation will fail if the calling context
+     * lacks the {@link android.Manifest.permission#INSTALL_PACKAGES} permission, if the
+     * package named in the package file's manifest is already installed, or if there's no space
+     * available on the device.
+     *
+     * @param packageURI The location of the package file to install.  This can be a 'file:' or a
+     * 'content:' URI.
+     * @param observer An observer callback to get notified when the package installation is
+     * complete. {@link IPackageInstallObserver#packageInstalled(String, int)} will be
+     * called when that happens.  observer may be null to indicate that no callback is desired.
+     * @param flags - possible values: {@link #FORWARD_LOCK_PACKAGE},
+     * {@link #REPLACE_EXISTING_PACKAGE}
+     *
+     * @see #installPackage(android.net.Uri)
+     */
+    public abstract void installPackage(
+            Uri packageURI, IPackageInstallObserver observer, int flags);
+
+    /**
+     * Attempts to delete a package.  Since this may take a little while, the result will
+     * be posted back to the given observer.  A deletion will fail if the calling context
+     * lacks the {@link android.Manifest.permission#DELETE_PACKAGES} permission, if the
+     * named package cannot be found, or if the named package is a "system package".
+     * (TODO: include pointer to documentation on "system packages")
+     *
+     * @param packageName The name of the package to delete
+     * @param observer An observer callback to get notified when the package deletion is
+     * complete. {@link android.content.pm.IPackageDeleteObserver#packageDeleted(boolean)} will be
+     * called when that happens.  observer may be null to indicate that no callback is desired.
+     * @param flags - possible values: {@link #DONT_DELETE_DATA}
+     *
+     * @hide
+     */
+    public abstract void deletePackage(
+            String packageName, IPackageDeleteObserver observer, int flags);
+    /**
+     * Attempts to clear the user data directory of an application.
+     * Since this may take a little while, the result will
+     * be posted back to the given observer.  A deletion will fail if the
+     * named package cannot be found, or if the named package is a "system package".
+     *
+     * @param packageName The name of the package
+     * @param observer An observer callback to get notified when the operation is finished
+     * {@link android.content.pm.IPackageDataObserver#onRemoveCompleted(String, boolean)}
+     * will be called when that happens.  observer may be null to indicate that
+     * no callback is desired.
+     *
+     * @hide
+     */
+    public abstract void clearApplicationUserData(String packageName,
+            IPackageDataObserver observer);
+    /**
+     * Attempts to delete the cache files associated with an application.
+     * Since this may take a little while, the result will
+     * be posted back to the given observer.  A deletion will fail if the calling context
+     * lacks the {@link android.Manifest.permission#DELETE_CACHE_FILES} permission, if the
+     * named package cannot be found, or if the named package is a "system package".
+     *
+     * @param packageName The name of the package to delete
+     * @param observer An observer callback to get notified when the cache file deletion
+     * is complete.
+     * {@link android.content.pm.IPackageDataObserver#onRemoveCompleted(String, boolean)}
+     * will be called when that happens.  observer may be null to indicate that
+     * no callback is desired.
+     *
+     * @hide
+     */
+    public abstract void deleteApplicationCacheFiles(String packageName,
+            IPackageDataObserver observer);
+
+    /**
+     * Free storage by deleting LRU sorted list of cache files across
+     * all applications. If the currently available free storage
+     * on the device is greater than or equal to the requested
+     * free storage, no cache files are cleared. If the currently
+     * available storage on the device is less than the requested
+     * free storage, some or all of the cache files across
+     * all applications are deleted (based on last accessed time)
+     * to increase the free storage space on the device to
+     * the requested value. There is no guarantee that clearing all
+     * the cache files from all applications will clear up
+     * enough storage to achieve the desired value.
+     * @param freeStorageSize The number of bytes of storage to be
+     * freed by the system. Say if freeStorageSize is XX,
+     * and the current free storage is YY,
+     * if XX is less than YY, just return. if not free XX-YY number
+     * of bytes if possible.
+     * @param observer call back used to notify when
+     * the operation is completed
+     * 
+     * @hide
+     */
+    public abstract void freeStorageAndNotify(long freeStorageSize, IPackageDataObserver observer);
+    
+    /**
+     * Free storage by deleting LRU sorted list of cache files across
+     * all applications. If the currently available free storage
+     * on the device is greater than or equal to the requested
+     * free storage, no cache files are cleared. If the currently
+     * available storage on the device is less than the requested
+     * free storage, some or all of the cache files across
+     * all applications are deleted (based on last accessed time)
+     * to increase the free storage space on the device to
+     * the requested value. There is no guarantee that clearing all
+     * the cache files from all applications will clear up
+     * enough storage to achieve the desired value.
+     * @param freeStorageSize The number of bytes of storage to be
+     * freed by the system. Say if freeStorageSize is XX,
+     * and the current free storage is YY,
+     * if XX is less than YY, just return. if not free XX-YY number
+     * of bytes if possible.
+     * @param opFinishedIntent PendingIntent call back used to
+     * notify when the operation is completed.May be null
+     * to indicate that no call back is desired.
+     * 
+     * @hide
+     */
+    public abstract void freeStorage(long freeStorageSize, PendingIntent opFinishedIntent);
+
+    /**
+     * Retrieve the size information for a package.
+     * Since this may take a little while, the result will
+     * be posted back to the given observer.  The calling context
+     * should have the {@link android.Manifest.permission#GET_PACKAGE_SIZE} permission.
+     *
+     * @param packageName The name of the package whose size information is to be retrieved
+     * @param observer An observer callback to get notified when the operation
+     * is complete.
+     * {@link android.content.pm.IPackageStatsObserver#onGetStatsCompleted(PackageStats, boolean)}
+     * The observer's callback is invoked with a PackageStats object(containing the
+     * code, data and cache sizes of the package) and a boolean value representing
+     * the status of the operation. observer may be null to indicate that
+     * no callback is desired.
+     *
+     * @hide
+     */
+    public abstract void getPackageSizeInfo(String packageName,
+            IPackageStatsObserver observer);
+
+    /**
+     * Install a package.
+     *
+     * @param packageURI The location of the package file to install
+     *
+     * @see #installPackage(android.net.Uri, IPackageInstallObserver, int)
+     */
+    public void installPackage(Uri packageURI) {
+        installPackage(packageURI, null, 0);
+    }
+
+    /**
+     * Add a new package to the list of preferred packages.  This new package
+     * will be added to the front of the list (removed from its current location
+     * if already listed), meaning it will now be preferred over all other
+     * packages when resolving conflicts.
+     *
+     * @param packageName The package name of the new package to make preferred.
+     */
+    public abstract void addPackageToPreferred(String packageName);
+
+    /**
+     * Remove a package from the list of preferred packages.  If it was on
+     * the list, it will no longer be preferred over other packages.
+     *
+     * @param packageName The package name to remove.
+     */
+    public abstract void removePackageFromPreferred(String packageName);
+
+    /**
+     * Retrieve the list of all currently configured preferred packages.  The
+     * first package on the list is the most preferred, the last is the
+     * least preferred.
+     *
+     * @param flags Additional option flags. Use any combination of
+     * {@link #GET_ACTIVITIES},
+     * {@link #GET_GIDS},
+     * {@link #GET_CONFIGURATIONS},
+     * {@link #GET_INSTRUMENTATION},
+     * {@link #GET_PERMISSIONS},
+     * {@link #GET_PROVIDERS},
+     * {@link #GET_RECEIVERS},
+     * {@link #GET_SERVICES},
+     * {@link #GET_SIGNATURES}, to modify the data returned.
+     *
+     * @return Returns a list of PackageInfo objects describing each
+     * preferred application, in order of preference.
+     *
+     * @see #GET_ACTIVITIES
+     * @see #GET_GIDS
+     * @see #GET_CONFIGURATIONS
+     * @see #GET_INSTRUMENTATION
+     * @see #GET_PERMISSIONS
+     * @see #GET_PROVIDERS
+     * @see #GET_RECEIVERS
+     * @see #GET_SERVICES
+     * @see #GET_SIGNATURES
+     */
+    public abstract List<PackageInfo> getPreferredPackages(int flags);
+
+    /**
+     * Add a new preferred activity mapping to the system.  This will be used
+     * to automatically select the given activity component when
+     * {@link Context#startActivity(Intent) Context.startActivity()} finds
+     * multiple matching activities and also matches the given filter.
+     *
+     * @param filter The set of intents under which this activity will be
+     * made preferred.
+     * @param match The IntentFilter match category that this preference
+     * applies to.
+     * @param set The set of activities that the user was picking from when
+     * this preference was made.
+     * @param activity The component name of the activity that is to be
+     * preferred.
+     */
+    public abstract void addPreferredActivity(IntentFilter filter, int match,
+            ComponentName[] set, ComponentName activity);
+
+    /**
+     * Remove all preferred activity mappings, previously added with
+     * {@link #addPreferredActivity}, from the
+     * system whose activities are implemented in the given package name.
+     *
+     * @param packageName The name of the package whose preferred activity
+     * mappings are to be removed.
+     */
+    public abstract void clearPackagePreferredActivities(String packageName);
+
+    /**
+     * Retrieve all preferred activities, previously added with
+     * {@link #addPreferredActivity}, that are
+     * currently registered with the system.
+     *
+     * @param outFilters A list in which to place the filters of all of the
+     * preferred activities, or null for none.
+     * @param outActivities A list in which to place the component names of
+     * all of the preferred activities, or null for none.
+     * @param packageName An option package in which you would like to limit
+     * the list.  If null, all activities will be returned; if non-null, only
+     * those activities in the given package are returned.
+     *
+     * @return Returns the total number of registered preferred activities
+     * (the number of distinct IntentFilter records, not the number of unique
+     * activity components) that were found.
+     */
+    public abstract int getPreferredActivities(List<IntentFilter> outFilters,
+            List<ComponentName> outActivities, String packageName);
+
+    /**
+     * Set the enabled setting for a package component (activity, receiver, service, provider).
+     * This setting will override any enabled state which may have been set by the component in its
+     * manifest.
+     *
+     * @param componentName The component to enable
+     * @param newState The new enabled state for the component.  The legal values for this state
+     *                 are:
+     *                   {@link #COMPONENT_ENABLED_STATE_ENABLED},
+     *                   {@link #COMPONENT_ENABLED_STATE_DISABLED}
+     *                   and
+     *                   {@link #COMPONENT_ENABLED_STATE_DEFAULT}
+     *                 The last one removes the setting, thereby restoring the component's state to
+     *                 whatever was set in it's manifest (or enabled, by default).
+     * @param flags Optional behavior flags: {@link #DONT_KILL_APP} or 0.
+     */
+    public abstract void setComponentEnabledSetting(ComponentName componentName,
+            int newState, int flags);
+
+
+    /**
+     * Return the the enabled setting for a package component (activity,
+     * receiver, service, provider).  This returns the last value set by
+     * {@link #setComponentEnabledSetting(ComponentName, int, int)}; in most
+     * cases this value will be {@link #COMPONENT_ENABLED_STATE_DEFAULT} since
+     * the value originally specified in the manifest has not been modified.
+     *
+     * @param componentName The component to retrieve.
+     * @return Returns the current enabled state for the component.  May
+     * be one of {@link #COMPONENT_ENABLED_STATE_ENABLED},
+     * {@link #COMPONENT_ENABLED_STATE_DISABLED}, or
+     * {@link #COMPONENT_ENABLED_STATE_DEFAULT}.  The last one means the
+     * component's enabled state is based on the original information in
+     * the manifest as found in {@link ComponentInfo}.
+     */
+    public abstract int getComponentEnabledSetting(ComponentName componentName);
+
+    /**
+     * Set the enabled setting for an application
+     * This setting will override any enabled state which may have been set by the application in
+     * its manifest.  It also overrides the enabled state set in the manifest for any of the
+     * application's components.  It does not override any enabled state set by
+     * {@link #setComponentEnabledSetting} for any of the application's components.
+     *
+     * @param packageName The package name of the application to enable
+     * @param newState The new enabled state for the component.  The legal values for this state
+     *                 are:
+     *                   {@link #COMPONENT_ENABLED_STATE_ENABLED},
+     *                   {@link #COMPONENT_ENABLED_STATE_DISABLED}
+     *                   and
+     *                   {@link #COMPONENT_ENABLED_STATE_DEFAULT}
+     *                 The last one removes the setting, thereby restoring the applications's state to
+     *                 whatever was set in its manifest (or enabled, by default).
+     * @param flags Optional behavior flags: {@link #DONT_KILL_APP} or 0.
+     */
+    public abstract void setApplicationEnabledSetting(String packageName,
+            int newState, int flags);
+    
+    /**
+     * Return the the enabled setting for an application.  This returns
+     * the last value set by
+     * {@link #setApplicationEnabledSetting(String, int, int)}; in most
+     * cases this value will be {@link #COMPONENT_ENABLED_STATE_DEFAULT} since
+     * the value originally specified in the manifest has not been modified.
+     *
+     * @param packageName The component to retrieve.
+     * @return Returns the current enabled state for the component.  May
+     * be one of {@link #COMPONENT_ENABLED_STATE_ENABLED},
+     * {@link #COMPONENT_ENABLED_STATE_DISABLED}, or
+     * {@link #COMPONENT_ENABLED_STATE_DEFAULT}.  The last one means the
+     * application's enabled state is based on the original information in
+     * the manifest as found in {@link ComponentInfo}.
+     */
+    public abstract int getApplicationEnabledSetting(String packageName);
+
+    /**
+     * Return whether the device has been booted into safe mode.
+     */
+    public abstract boolean isSafeMode();
+}
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
new file mode 100644
index 0000000..4ae8b08
--- /dev/null
+++ b/core/java/android/content/pm/PackageParser.java
@@ -0,0 +1,2352 @@
+/*
+ * Copyright (C) 2007 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 android.content.pm;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import android.content.ComponentName;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.res.AssetManager;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.content.res.XmlResourceParser;
+import android.os.Bundle;
+import android.os.PatternMatcher;
+import android.util.AttributeSet;
+import android.util.Config;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import android.util.TypedValue;
+import com.android.internal.util.XmlUtils;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.ref.WeakReference;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateEncodingException;
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.Iterator;
+import java.util.jar.JarEntry;
+import java.util.jar.JarFile;
+
+/**
+ * Package archive parsing
+ *
+ * {@hide}
+ */
+public class PackageParser {
+
+    private String mArchiveSourcePath;
+    private String[] mSeparateProcesses;
+    private int mSdkVersion;
+
+    private int mParseError = PackageManager.INSTALL_SUCCEEDED;
+
+    private static final Object mSync = new Object();
+    private static WeakReference<byte[]> mReadBuffer;
+
+    /** If set to true, we will only allow package files that exactly match
+     *  the DTD.  Otherwise, we try to get as much from the package as we
+     *  can without failing.  This should normally be set to false, to
+     *  support extensions to the DTD in future versions. */
+    private static final boolean RIGID_PARSER = false;
+
+    private static final String TAG = "PackageParser";
+
+    public PackageParser(String archiveSourcePath) {
+        mArchiveSourcePath = archiveSourcePath;
+    }
+
+    public void setSeparateProcesses(String[] procs) {
+        mSeparateProcesses = procs;
+    }
+
+    public void setSdkVersion(int sdkVersion) {
+        mSdkVersion = sdkVersion;
+    }
+
+    private static final boolean isPackageFilename(String name) {
+        return name.endsWith(".apk");
+    }
+
+    /**
+     * Generate and return the {@link PackageInfo} for a parsed package.
+     *
+     * @param p the parsed package.
+     * @param flags indicating which optional information is included.
+     */
+    public static PackageInfo generatePackageInfo(PackageParser.Package p,
+            int gids[], int flags) {
+
+        PackageInfo pi = new PackageInfo();
+        pi.packageName = p.packageName;
+        pi.versionCode = p.mVersionCode;
+        pi.versionName = p.mVersionName;
+        pi.sharedUserId = p.mSharedUserId;
+        pi.sharedUserLabel = p.mSharedUserLabel;
+        pi.applicationInfo = p.applicationInfo;
+        if ((flags&PackageManager.GET_GIDS) != 0) {
+            pi.gids = gids;
+        }
+        if ((flags&PackageManager.GET_CONFIGURATIONS) != 0) {
+            int N = p.configPreferences.size();
+            if (N > 0) {
+                pi.configPreferences = new ConfigurationInfo[N];
+                for (int i=0; i<N; i++) {
+                    pi.configPreferences[i] = p.configPreferences.get(i);
+                }
+            }
+        }
+        if ((flags&PackageManager.GET_ACTIVITIES) != 0) {
+            int N = p.activities.size();
+            if (N > 0) {
+                pi.activities = new ActivityInfo[N];
+                for (int i=0; i<N; i++) {
+                    final Activity activity = p.activities.get(i);
+                    if (activity.info.enabled
+                        || (flags&PackageManager.GET_DISABLED_COMPONENTS) != 0) {
+                        pi.activities[i] = generateActivityInfo(p.activities.get(i), flags);
+                    }
+                }
+            }
+        }
+        if ((flags&PackageManager.GET_RECEIVERS) != 0) {
+            int N = p.receivers.size();
+            if (N > 0) {
+                pi.receivers = new ActivityInfo[N];
+                for (int i=0; i<N; i++) {
+                    final Activity activity = p.receivers.get(i);
+                    if (activity.info.enabled
+                        || (flags&PackageManager.GET_DISABLED_COMPONENTS) != 0) {
+                        pi.receivers[i] = generateActivityInfo(p.receivers.get(i), flags);
+                    }
+                }
+            }
+        }
+        if ((flags&PackageManager.GET_SERVICES) != 0) {
+            int N = p.services.size();
+            if (N > 0) {
+                pi.services = new ServiceInfo[N];
+                for (int i=0; i<N; i++) {
+                    final Service service = p.services.get(i);
+                    if (service.info.enabled
+                        || (flags&PackageManager.GET_DISABLED_COMPONENTS) != 0) {
+                        pi.services[i] = generateServiceInfo(p.services.get(i), flags);
+                    }
+                }
+            }
+        }
+        if ((flags&PackageManager.GET_PROVIDERS) != 0) {
+            int N = p.providers.size();
+            if (N > 0) {
+                pi.providers = new ProviderInfo[N];
+                for (int i=0; i<N; i++) {
+                    final Provider provider = p.providers.get(i);
+                    if (provider.info.enabled
+                        || (flags&PackageManager.GET_DISABLED_COMPONENTS) != 0) {
+                        pi.providers[i] = generateProviderInfo(p.providers.get(i), flags);
+                    }
+                }
+            }
+        }
+        if ((flags&PackageManager.GET_INSTRUMENTATION) != 0) {
+            int N = p.instrumentation.size();
+            if (N > 0) {
+                pi.instrumentation = new InstrumentationInfo[N];
+                for (int i=0; i<N; i++) {
+                    pi.instrumentation[i] = generateInstrumentationInfo(
+                            p.instrumentation.get(i), flags);
+                }
+            }
+        }
+        if ((flags&PackageManager.GET_PERMISSIONS) != 0) {
+            int N = p.permissions.size();
+            if (N > 0) {
+                pi.permissions = new PermissionInfo[N];
+                for (int i=0; i<N; i++) {
+                    pi.permissions[i] = generatePermissionInfo(p.permissions.get(i), flags);
+                }
+            }
+            N = p.requestedPermissions.size();
+            if (N > 0) {
+                pi.requestedPermissions = new String[N];
+                for (int i=0; i<N; i++) {
+                    pi.requestedPermissions[i] = p.requestedPermissions.get(i);
+                }
+            }
+        }
+        if ((flags&PackageManager.GET_SIGNATURES) != 0) {
+            int N = p.mSignatures.length;
+            if (N > 0) {
+                pi.signatures = new Signature[N];
+                System.arraycopy(p.mSignatures, 0, pi.signatures, 0, N);
+            }
+        }
+        return pi;
+    }
+
+    private Certificate[] loadCertificates(JarFile jarFile, JarEntry je,
+            byte[] readBuffer) {
+        try {
+            // We must read the stream for the JarEntry to retrieve
+            // its certificates.
+            InputStream is = jarFile.getInputStream(je);
+            while (is.read(readBuffer, 0, readBuffer.length) != -1) {
+                // not using
+            }
+            is.close();
+            return je != null ? je.getCertificates() : null;
+        } catch (IOException e) {
+            Log.w(TAG, "Exception reading " + je.getName() + " in "
+                    + jarFile.getName(), e);
+        }
+        return null;
+    }
+
+    public final static int PARSE_IS_SYSTEM = 0x0001;
+    public final static int PARSE_CHATTY = 0x0002;
+    public final static int PARSE_MUST_BE_APK = 0x0004;
+    public final static int PARSE_IGNORE_PROCESSES = 0x0008;
+
+    public int getParseError() {
+        return mParseError;
+    }
+
+    public Package parsePackage(File sourceFile, String destFileName,
+            DisplayMetrics metrics, int flags) {
+        mParseError = PackageManager.INSTALL_SUCCEEDED;
+
+        mArchiveSourcePath = sourceFile.getPath();
+        if (!sourceFile.isFile()) {
+            Log.w(TAG, "Skipping dir: " + mArchiveSourcePath);
+            mParseError = PackageManager.INSTALL_PARSE_FAILED_NOT_APK;
+            return null;
+        }
+        if (!isPackageFilename(sourceFile.getName())
+                && (flags&PARSE_MUST_BE_APK) != 0) {
+            if ((flags&PARSE_IS_SYSTEM) == 0) {
+                // We expect to have non-.apk files in the system dir,
+                // so don't warn about them.
+                Log.w(TAG, "Skipping non-package file: " + mArchiveSourcePath);
+            }
+            mParseError = PackageManager.INSTALL_PARSE_FAILED_NOT_APK;
+            return null;
+        }
+
+        if ((flags&PARSE_CHATTY) != 0 && Config.LOGD) Log.d(
+            TAG, "Scanning package: " + mArchiveSourcePath);
+
+        XmlResourceParser parser = null;
+        AssetManager assmgr = null;
+        boolean assetError = true;
+        try {
+            assmgr = new AssetManager();
+            if(assmgr.addAssetPath(mArchiveSourcePath) != 0) {
+                parser = assmgr.openXmlResourceParser("AndroidManifest.xml");
+                assetError = false;
+            } else {
+                Log.w(TAG, "Failed adding asset path:"+mArchiveSourcePath);
+            }
+        } catch (Exception e) {
+            Log.w(TAG, "Unable to read AndroidManifest.xml of "
+                    + mArchiveSourcePath, e);
+        }
+        if(assetError) {
+            if (assmgr != null) assmgr.close();
+            mParseError = PackageManager.INSTALL_PARSE_FAILED_BAD_MANIFEST;
+            return null;
+        }
+        String[] errorText = new String[1];
+        Package pkg = null;
+        Exception errorException = null;
+        try {
+            // XXXX todo: need to figure out correct configuration.
+            Resources res = new Resources(assmgr, metrics, null);
+            pkg = parsePackage(res, parser, flags, errorText);
+        } catch (Exception e) {
+            errorException = e;
+            mParseError = PackageManager.INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION;
+        }
+
+
+        if (pkg == null) {
+            if (errorException != null) {
+                Log.w(TAG, mArchiveSourcePath, errorException);
+            } else {
+                Log.w(TAG, mArchiveSourcePath + " (at "
+                        + parser.getPositionDescription()
+                        + "): " + errorText[0]);
+            }
+            parser.close();
+            assmgr.close();
+            if (mParseError == PackageManager.INSTALL_SUCCEEDED) {
+                mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
+            }
+            return null;
+        }
+
+        parser.close();
+        assmgr.close();
+
+        pkg.applicationInfo.sourceDir = destFileName;
+        pkg.applicationInfo.publicSourceDir = destFileName;
+        pkg.mSignatures = null;
+
+        return pkg;
+    }
+
+    public boolean collectCertificates(Package pkg, int flags) {
+        pkg.mSignatures = null;
+
+        WeakReference<byte[]> readBufferRef;
+        byte[] readBuffer = null;
+        synchronized (mSync) {
+            readBufferRef = mReadBuffer;
+            if (readBufferRef != null) {
+                mReadBuffer = null;
+                readBuffer = readBufferRef.get();
+            }
+            if (readBuffer == null) {
+                readBuffer = new byte[8192];
+                readBufferRef = new WeakReference<byte[]>(readBuffer);
+            }
+        }
+
+        try {
+            JarFile jarFile = new JarFile(mArchiveSourcePath);
+
+            Certificate[] certs = null;
+
+            if ((flags&PARSE_IS_SYSTEM) != 0) {
+                // If this package comes from the system image, then we
+                // can trust it...  we'll just use the AndroidManifest.xml
+                // to retrieve its signatures, not validating all of the
+                // files.
+                JarEntry jarEntry = jarFile.getJarEntry("AndroidManifest.xml");
+                certs = loadCertificates(jarFile, jarEntry, readBuffer);
+                if (certs == null) {
+                    Log.e(TAG, "Package " + pkg.packageName
+                            + " has no certificates at entry "
+                            + jarEntry.getName() + "; ignoring!");
+                    jarFile.close();
+                    mParseError = PackageManager.INSTALL_PARSE_FAILED_NO_CERTIFICATES;
+                    return false;
+                }
+                if (false) {
+                    Log.i(TAG, "File " + mArchiveSourcePath + ": entry=" + jarEntry
+                            + " certs=" + (certs != null ? certs.length : 0));
+                    if (certs != null) {
+                        final int N = certs.length;
+                        for (int i=0; i<N; i++) {
+                            Log.i(TAG, "  Public key: "
+                                    + certs[i].getPublicKey().getEncoded()
+                                    + " " + certs[i].getPublicKey());
+                        }
+                    }
+                }
+
+            } else {
+                Enumeration entries = jarFile.entries();
+                while (entries.hasMoreElements()) {
+                    JarEntry je = (JarEntry)entries.nextElement();
+                    if (je.isDirectory()) continue;
+                    if (je.getName().startsWith("META-INF/")) continue;
+                    Certificate[] localCerts = loadCertificates(jarFile, je,
+                            readBuffer);
+                    if (false) {
+                        Log.i(TAG, "File " + mArchiveSourcePath + " entry " + je.getName()
+                                + ": certs=" + certs + " ("
+                                + (certs != null ? certs.length : 0) + ")");
+                    }
+                    if (localCerts == null) {
+                        Log.e(TAG, "Package " + pkg.packageName
+                                + " has no certificates at entry "
+                                + je.getName() + "; ignoring!");
+                        jarFile.close();
+                        mParseError = PackageManager.INSTALL_PARSE_FAILED_NO_CERTIFICATES;
+                        return false;
+                    } else if (certs == null) {
+                        certs = localCerts;
+                    } else {
+                        // Ensure all certificates match.
+                        for (int i=0; i<certs.length; i++) {
+                            boolean found = false;
+                            for (int j=0; j<localCerts.length; j++) {
+                                if (certs[i] != null &&
+                                        certs[i].equals(localCerts[j])) {
+                                    found = true;
+                                    break;
+                                }
+                            }
+                            if (!found || certs.length != localCerts.length) {
+                                Log.e(TAG, "Package " + pkg.packageName
+                                        + " has mismatched certificates at entry "
+                                        + je.getName() + "; ignoring!");
+                                jarFile.close();
+                                mParseError = PackageManager.INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES;
+                                return false;
+                            }
+                        }
+                    }
+                }
+            }
+            jarFile.close();
+
+            synchronized (mSync) {
+                mReadBuffer = readBufferRef;
+            }
+
+            if (certs != null && certs.length > 0) {
+                final int N = certs.length;
+                pkg.mSignatures = new Signature[certs.length];
+                for (int i=0; i<N; i++) {
+                    pkg.mSignatures[i] = new Signature(
+                            certs[i].getEncoded());
+                }
+            } else {
+                Log.e(TAG, "Package " + pkg.packageName
+                        + " has no certificates; ignoring!");
+                mParseError = PackageManager.INSTALL_PARSE_FAILED_NO_CERTIFICATES;
+                return false;
+            }
+        } catch (CertificateEncodingException e) {
+            Log.w(TAG, "Exception reading " + mArchiveSourcePath, e);
+            mParseError = PackageManager.INSTALL_PARSE_FAILED_CERTIFICATE_ENCODING;
+            return false;
+        } catch (IOException e) {
+            Log.w(TAG, "Exception reading " + mArchiveSourcePath, e);
+            mParseError = PackageManager.INSTALL_PARSE_FAILED_CERTIFICATE_ENCODING;
+            return false;
+        } catch (RuntimeException e) {
+            Log.w(TAG, "Exception reading " + mArchiveSourcePath, e);
+            mParseError = PackageManager.INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION;
+            return false;
+        }
+
+        return true;
+    }
+
+    public static String parsePackageName(String packageFilePath, int flags) {
+        XmlResourceParser parser = null;
+        AssetManager assmgr = null;
+        try {
+            assmgr = new AssetManager();
+            int cookie = assmgr.addAssetPath(packageFilePath);
+            parser = assmgr.openXmlResourceParser(cookie, "AndroidManifest.xml");
+        } catch (Exception e) {
+            if (assmgr != null) assmgr.close();
+            Log.w(TAG, "Unable to read AndroidManifest.xml of "
+                    + packageFilePath, e);
+            return null;
+        }
+        AttributeSet attrs = parser;
+        String errors[] = new String[1];
+        String packageName = null;
+        try {
+            packageName = parsePackageName(parser, attrs, flags, errors);
+        } catch (IOException e) {
+            Log.w(TAG, packageFilePath, e);
+        } catch (XmlPullParserException e) {
+            Log.w(TAG, packageFilePath, e);
+        } finally {
+            if (parser != null) parser.close();
+            if (assmgr != null) assmgr.close();
+        }
+        if (packageName == null) {
+            Log.e(TAG, "parsePackageName error: " + errors[0]);
+            return null;
+        }
+        return packageName;
+    }
+
+    private static String validateName(String name, boolean requiresSeparator) {
+        final int N = name.length();
+        boolean hasSep = false;
+        boolean front = true;
+        for (int i=0; i<N; i++) {
+            final char c = name.charAt(i);
+            if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) {
+                front = false;
+                continue;
+            }
+            if (!front) {
+                if ((c >= '0' && c <= '9') || c == '_') {
+                    continue;
+                }
+            }
+            if (c == '.') {
+                hasSep = true;
+                front = true;
+                continue;
+            }
+            return "bad character '" + c + "'";
+        }
+        return hasSep || !requiresSeparator
+                ? null : "must have at least one '.' separator";
+    }
+
+    private static String parsePackageName(XmlPullParser parser,
+            AttributeSet attrs, int flags, String[] outError)
+            throws IOException, XmlPullParserException {
+
+        int type;
+        while ((type=parser.next()) != parser.START_TAG
+                   && type != parser.END_DOCUMENT) {
+            ;
+        }
+
+        if (type != parser.START_TAG) {
+            outError[0] = "No start tag found";
+            return null;
+        }
+        if ((flags&PARSE_CHATTY) != 0 && Config.LOGV) Log.v(
+            TAG, "Root element name: '" + parser.getName() + "'");
+        if (!parser.getName().equals("manifest")) {
+            outError[0] = "No <manifest> tag";
+            return null;
+        }
+        String pkgName = attrs.getAttributeValue(null, "package");
+        if (pkgName == null || pkgName.length() == 0) {
+            outError[0] = "<manifest> does not specify package";
+            return null;
+        }
+        String nameError = validateName(pkgName, true);
+        if (nameError != null && !"android".equals(pkgName)) {
+            outError[0] = "<manifest> specifies bad package name \""
+                + pkgName + "\": " + nameError;
+            return null;
+        }
+
+        return pkgName.intern();
+    }
+
+    /**
+     * Temporary.
+     */
+    static public Signature stringToSignature(String str) {
+        final int N = str.length();
+        byte[] sig = new byte[N];
+        for (int i=0; i<N; i++) {
+            sig[i] = (byte)str.charAt(i);
+        }
+        return new Signature(sig);
+    }
+
+    private Package parsePackage(
+        Resources res, XmlResourceParser parser, int flags, String[] outError)
+        throws XmlPullParserException, IOException {
+        AttributeSet attrs = parser;
+
+        String pkgName = parsePackageName(parser, attrs, flags, outError);
+        if (pkgName == null) {
+            mParseError = PackageManager.INSTALL_PARSE_FAILED_BAD_PACKAGE_NAME;
+            return null;
+        }
+        int type;
+
+        final Package pkg = new Package(pkgName);
+        pkg.mSystem = (flags&PARSE_IS_SYSTEM) != 0;
+        boolean foundApp = false;
+
+        TypedArray sa = res.obtainAttributes(attrs,
+                com.android.internal.R.styleable.AndroidManifest);
+        pkg.mVersionCode = sa.getInteger(
+                com.android.internal.R.styleable.AndroidManifest_versionCode, 0);
+        pkg.mVersionName = sa.getNonResourceString(
+                com.android.internal.R.styleable.AndroidManifest_versionName);
+        if (pkg.mVersionName != null) {
+            pkg.mVersionName = pkg.mVersionName.intern();
+        }
+        String str = sa.getNonResourceString(
+                com.android.internal.R.styleable.AndroidManifest_sharedUserId);
+        if (str != null) {
+            String nameError = validateName(str, true);
+            if (nameError != null && !"android".equals(pkgName)) {
+                outError[0] = "<manifest> specifies bad sharedUserId name \""
+                    + str + "\": " + nameError;
+                mParseError = PackageManager.INSTALL_PARSE_FAILED_BAD_SHARED_USER_ID;
+                return null;
+            }
+            pkg.mSharedUserId = str.intern();
+            pkg.mSharedUserLabel = sa.getResourceId(
+                    com.android.internal.R.styleable.AndroidManifest_sharedUserLabel, 0);
+        }
+        sa.recycle();
+
+        final int innerDepth = parser.getDepth();
+
+        int outerDepth = parser.getDepth();
+        while ((type=parser.next()) != parser.END_DOCUMENT
+               && (type != parser.END_TAG || parser.getDepth() > outerDepth)) {
+            if (type == parser.END_TAG || type == parser.TEXT) {
+                continue;
+            }
+
+            String tagName = parser.getName();
+            if (tagName.equals("application")) {
+                if (foundApp) {
+                    if (RIGID_PARSER) {
+                        outError[0] = "<manifest> has more than one <application>";
+                        mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
+                        return null;
+                    } else {
+                        Log.w(TAG, "<manifest> has more than one <application>");
+                        XmlUtils.skipCurrentTag(parser);
+                        continue;
+                    }
+                }
+
+                foundApp = true;
+                if (!parseApplication(pkg, res, parser, attrs, flags, outError)) {
+                    return null;
+                }
+            } else if (tagName.equals("permission-group")) {
+                if (parsePermissionGroup(pkg, res, parser, attrs, outError) == null) {
+                    return null;
+                }
+            } else if (tagName.equals("permission")) {
+                if (parsePermission(pkg, res, parser, attrs, outError) == null) {
+                    return null;
+                }
+            } else if (tagName.equals("permission-tree")) {
+                if (parsePermissionTree(pkg, res, parser, attrs, outError) == null) {
+                    return null;
+                }
+            } else if (tagName.equals("uses-permission")) {
+                sa = res.obtainAttributes(attrs,
+                        com.android.internal.R.styleable.AndroidManifestUsesPermission);
+
+                String name = sa.getNonResourceString(
+                        com.android.internal.R.styleable.AndroidManifestUsesPermission_name);
+
+                sa.recycle();
+
+                if (name != null && !pkg.requestedPermissions.contains(name)) {
+                    pkg.requestedPermissions.add(name);
+                }
+
+                XmlUtils.skipCurrentTag(parser);
+
+            } else if (tagName.equals("uses-configuration")) {
+                ConfigurationInfo cPref = new ConfigurationInfo();
+                sa = res.obtainAttributes(attrs,
+                        com.android.internal.R.styleable.AndroidManifestUsesConfiguration);
+                cPref.reqTouchScreen = sa.getInt(
+                        com.android.internal.R.styleable.AndroidManifestUsesConfiguration_reqTouchScreen,
+                        Configuration.TOUCHSCREEN_UNDEFINED);
+                cPref.reqKeyboardType = sa.getInt(
+                        com.android.internal.R.styleable.AndroidManifestUsesConfiguration_reqKeyboardType,
+                        Configuration.KEYBOARD_UNDEFINED);
+                if (sa.getBoolean(
+                        com.android.internal.R.styleable.AndroidManifestUsesConfiguration_reqHardKeyboard,
+                        false)) {
+                    cPref.reqInputFeatures |= ConfigurationInfo.INPUT_FEATURE_HARD_KEYBOARD;
+                }
+                cPref.reqNavigation = sa.getInt(
+                        com.android.internal.R.styleable.AndroidManifestUsesConfiguration_reqNavigation,
+                        Configuration.NAVIGATION_UNDEFINED);
+                if (sa.getBoolean(
+                        com.android.internal.R.styleable.AndroidManifestUsesConfiguration_reqFiveWayNav,
+                        false)) {
+                    cPref.reqInputFeatures |= ConfigurationInfo.INPUT_FEATURE_FIVE_WAY_NAV;
+                }
+                sa.recycle();
+                pkg.configPreferences.add(cPref);
+
+                XmlUtils.skipCurrentTag(parser);
+
+            }  else if (tagName.equals("uses-sdk")) {
+                if (mSdkVersion > 0) {
+                    sa = res.obtainAttributes(attrs,
+                            com.android.internal.R.styleable.AndroidManifestUsesSdk);
+
+                    int vers = sa.getInt(
+                            com.android.internal.R.styleable.AndroidManifestUsesSdk_minSdkVersion, 0);
+
+                    sa.recycle();
+
+                    if (vers > mSdkVersion) {
+                        outError[0] = "Requires newer sdk version #" + vers
+                            + " (current version is #" + mSdkVersion + ")";
+                        mParseError = PackageManager.INSTALL_FAILED_OLDER_SDK;
+                        return null;
+                    }
+                }
+
+                XmlUtils.skipCurrentTag(parser);
+
+            } else if (tagName.equals("instrumentation")) {
+                if (parseInstrumentation(pkg, res, parser, attrs, outError) == null) {
+                    return null;
+                }
+            } else if (tagName.equals("eat-comment")) {
+                // Just skip this tag
+                XmlUtils.skipCurrentTag(parser);
+                continue;
+            } else if (RIGID_PARSER) {
+                outError[0] = "Bad element under <manifest>: "
+                    + parser.getName();
+                mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
+                return null;
+            } else {
+                Log.w(TAG, "Bad element under <manifest>: "
+                      + parser.getName());
+                XmlUtils.skipCurrentTag(parser);
+                continue;
+            }
+        }
+
+        if (!foundApp && pkg.instrumentation.size() == 0) {
+            outError[0] = "<manifest> does not contain an <application> or <instrumentation>";
+            mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_EMPTY;
+        }
+
+        if (pkg.usesLibraries.size() > 0) {
+            pkg.usesLibraryFiles = new String[pkg.usesLibraries.size()];
+            pkg.usesLibraries.toArray(pkg.usesLibraryFiles);
+        }
+
+        return pkg;
+    }
+
+    private static String buildClassName(String pkg, CharSequence clsSeq,
+            String[] outError) {
+        if (clsSeq == null || clsSeq.length() <= 0) {
+            outError[0] = "Empty class name in package " + pkg;
+            return null;
+        }
+        String cls = clsSeq.toString();
+        char c = cls.charAt(0);
+        if (c == '.') {
+            return (pkg + cls).intern();
+        }
+        if (cls.indexOf('.') < 0) {
+            StringBuilder b = new StringBuilder(pkg);
+            b.append('.');
+            b.append(cls);
+            return b.toString().intern();
+        }
+        if (c >= 'a' && c <= 'z') {
+            return cls.intern();
+        }
+        outError[0] = "Bad class name " + cls + " in package " + pkg;
+        return null;
+    }
+
+    private static String buildCompoundName(String pkg,
+            CharSequence procSeq, String type, String[] outError) {
+        String proc = procSeq.toString();
+        char c = proc.charAt(0);
+        if (pkg != null && c == ':') {
+            if (proc.length() < 2) {
+                outError[0] = "Bad " + type + " name " + proc + " in package " + pkg
+                        + ": must be at least two characters";
+                return null;
+            }
+            String subName = proc.substring(1);
+            String nameError = validateName(subName, false);
+            if (nameError != null) {
+                outError[0] = "Invalid " + type + " name " + proc + " in package "
+                        + pkg + ": " + nameError;
+                return null;
+            }
+            return (pkg + proc).intern();
+        }
+        String nameError = validateName(proc, true);
+        if (nameError != null && !"system".equals(proc)) {
+            outError[0] = "Invalid " + type + " name " + proc + " in package "
+                    + pkg + ": " + nameError;
+            return null;
+        }
+        return proc.intern();
+    }
+    
+    private static String buildProcessName(String pkg, String defProc,
+            CharSequence procSeq, int flags, String[] separateProcesses,
+            String[] outError) {
+        if ((flags&PARSE_IGNORE_PROCESSES) != 0 && !"system".equals(procSeq)) {
+            return defProc != null ? defProc : pkg;
+        }
+        if (separateProcesses != null) {
+            for (int i=separateProcesses.length-1; i>=0; i--) {
+                String sp = separateProcesses[i];
+                if (sp.equals(pkg) || sp.equals(defProc) || sp.equals(procSeq)) {
+                    return pkg;
+                }
+            }
+        }
+        if (procSeq == null || procSeq.length() <= 0) {
+            return defProc;
+        }
+        return buildCompoundName(pkg, procSeq, "package", outError);
+    }
+
+    private static String buildTaskAffinityName(String pkg, String defProc,
+            CharSequence procSeq, String[] outError) {
+        if (procSeq == null) {
+            return defProc;
+        }
+        if (procSeq.length() <= 0) {
+            return null;
+        }
+        return buildCompoundName(pkg, procSeq, "taskAffinity", outError);
+    }
+    
+    private PermissionGroup parsePermissionGroup(Package owner, Resources res,
+            XmlPullParser parser, AttributeSet attrs, String[] outError)
+        throws XmlPullParserException, IOException {
+        PermissionGroup perm = new PermissionGroup(owner);
+
+        TypedArray sa = res.obtainAttributes(attrs,
+                com.android.internal.R.styleable.AndroidManifestPermissionGroup);
+
+        if (!parsePackageItemInfo(owner, perm.info, outError,
+                "<permission-group>", sa,
+                com.android.internal.R.styleable.AndroidManifestPermissionGroup_name,
+                com.android.internal.R.styleable.AndroidManifestPermissionGroup_label,
+                com.android.internal.R.styleable.AndroidManifestPermissionGroup_icon)) {
+            sa.recycle();
+            mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
+            return null;
+        }
+
+        perm.info.descriptionRes = sa.getResourceId(
+                com.android.internal.R.styleable.AndroidManifestPermissionGroup_description,
+                0);
+
+        sa.recycle();
+        
+        if (!parseAllMetaData(res, parser, attrs, "<permission-group>", perm,
+                outError)) {
+            mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
+            return null;
+        }
+
+        owner.permissionGroups.add(perm);
+
+        return perm;
+    }
+
+    private Permission parsePermission(Package owner, Resources res,
+            XmlPullParser parser, AttributeSet attrs, String[] outError)
+        throws XmlPullParserException, IOException {
+        Permission perm = new Permission(owner);
+
+        TypedArray sa = res.obtainAttributes(attrs,
+                com.android.internal.R.styleable.AndroidManifestPermission);
+
+        if (!parsePackageItemInfo(owner, perm.info, outError,
+                "<permission>", sa,
+                com.android.internal.R.styleable.AndroidManifestPermission_name,
+                com.android.internal.R.styleable.AndroidManifestPermission_label,
+                com.android.internal.R.styleable.AndroidManifestPermission_icon)) {
+            sa.recycle();
+            mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
+            return null;
+        }
+
+        perm.info.group = sa.getNonResourceString(
+                com.android.internal.R.styleable.AndroidManifestPermission_permissionGroup);
+        if (perm.info.group != null) {
+            perm.info.group = perm.info.group.intern();
+        }
+        
+        perm.info.descriptionRes = sa.getResourceId(
+                com.android.internal.R.styleable.AndroidManifestPermission_description,
+                0);
+
+        perm.info.protectionLevel = sa.getInt(
+                com.android.internal.R.styleable.AndroidManifestPermission_protectionLevel,
+                PermissionInfo.PROTECTION_NORMAL);
+
+        sa.recycle();
+        
+        if (perm.info.protectionLevel == -1) {
+            outError[0] = "<permission> does not specify protectionLevel";
+            mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
+            return null;
+        }
+        
+        if (!parseAllMetaData(res, parser, attrs, "<permission>", perm,
+                outError)) {
+            mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
+            return null;
+        }
+
+        owner.permissions.add(perm);
+
+        return perm;
+    }
+
+    private Permission parsePermissionTree(Package owner, Resources res,
+            XmlPullParser parser, AttributeSet attrs, String[] outError)
+        throws XmlPullParserException, IOException {
+        Permission perm = new Permission(owner);
+
+        TypedArray sa = res.obtainAttributes(attrs,
+                com.android.internal.R.styleable.AndroidManifestPermissionTree);
+
+        if (!parsePackageItemInfo(owner, perm.info, outError,
+                "<permission-tree>", sa,
+                com.android.internal.R.styleable.AndroidManifestPermissionTree_name,
+                com.android.internal.R.styleable.AndroidManifestPermissionTree_label,
+                com.android.internal.R.styleable.AndroidManifestPermissionTree_icon)) {
+            sa.recycle();
+            mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
+            return null;
+        }
+
+        sa.recycle();
+        
+        int index = perm.info.name.indexOf('.');
+        if (index > 0) {
+            index = perm.info.name.indexOf('.', index+1);
+        }
+        if (index < 0) {
+            outError[0] = "<permission-tree> name has less than three segments: "
+                + perm.info.name;
+            mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
+            return null;
+        }
+
+        perm.info.descriptionRes = 0;
+        perm.info.protectionLevel = PermissionInfo.PROTECTION_NORMAL;
+        perm.tree = true;
+
+        if (!parseAllMetaData(res, parser, attrs, "<permission-tree>", perm,
+                outError)) {
+            mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
+            return null;
+        }
+
+        owner.permissions.add(perm);
+
+        return perm;
+    }
+
+    private Instrumentation parseInstrumentation(Package owner, Resources res,
+            XmlPullParser parser, AttributeSet attrs, String[] outError)
+        throws XmlPullParserException, IOException {
+        TypedArray sa = res.obtainAttributes(attrs,
+                com.android.internal.R.styleable.AndroidManifestInstrumentation);
+
+        Instrumentation a = new Instrumentation(owner);
+
+        if (!parsePackageItemInfo(owner, a.info, outError, "<instrumentation>", sa,
+                com.android.internal.R.styleable.AndroidManifestInstrumentation_name,
+                com.android.internal.R.styleable.AndroidManifestInstrumentation_label,
+                com.android.internal.R.styleable.AndroidManifestInstrumentation_icon)) {
+            sa.recycle();
+            mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
+            return null;
+        }
+
+        a.component = new ComponentName(owner.applicationInfo.packageName,
+                a.info.name);
+
+        String str;
+        str = sa.getNonResourceString(
+                com.android.internal.R.styleable.AndroidManifestInstrumentation_targetPackage);
+        a.info.targetPackage = str != null ? str.intern() : null;
+
+        a.info.handleProfiling = sa.getBoolean(
+                com.android.internal.R.styleable.AndroidManifestInstrumentation_handleProfiling,
+                false);
+
+        a.info.functionalTest = sa.getBoolean(
+                com.android.internal.R.styleable.AndroidManifestInstrumentation_functionalTest,
+                false);
+
+        sa.recycle();
+
+        if (a.info.targetPackage == null) {
+            outError[0] = "<instrumentation> does not specify targetPackage";
+            mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
+            return null;
+        }
+
+        if (!parseAllMetaData(res, parser, attrs, "<instrumentation>", a,
+                outError)) {
+            mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
+            return null;
+        }
+
+        owner.instrumentation.add(a);
+
+        return a;
+    }
+
+    private boolean parseApplication(Package owner, Resources res,
+            XmlPullParser parser, AttributeSet attrs, int flags, String[] outError)
+        throws XmlPullParserException, IOException {
+        final ApplicationInfo ai = owner.applicationInfo;
+        final String pkgName = owner.applicationInfo.packageName;
+
+        TypedArray sa = res.obtainAttributes(attrs,
+                com.android.internal.R.styleable.AndroidManifestApplication);
+
+        String name = sa.getNonResourceString(
+                com.android.internal.R.styleable.AndroidManifestApplication_name);
+        if (name != null) {
+            ai.className = buildClassName(pkgName, name, outError);
+            if (ai.className == null) {
+                sa.recycle();
+                mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
+                return false;
+            }
+        }
+
+        String manageSpaceActivity = sa.getNonResourceString(
+                com.android.internal.R.styleable.AndroidManifestApplication_manageSpaceActivity);
+        if (manageSpaceActivity != null) {
+            ai.manageSpaceActivityName = buildClassName(pkgName, manageSpaceActivity,
+                    outError);
+        }
+
+        TypedValue v = sa.peekValue(
+                com.android.internal.R.styleable.AndroidManifestApplication_label);
+        if (v != null && (ai.labelRes=v.resourceId) == 0) {
+            ai.nonLocalizedLabel = v.coerceToString();
+        }
+
+        ai.icon = sa.getResourceId(
+                com.android.internal.R.styleable.AndroidManifestApplication_icon, 0);
+        ai.theme = sa.getResourceId(
+                com.android.internal.R.styleable.AndroidManifestApplication_theme, 0);
+        ai.descriptionRes = sa.getResourceId(
+                com.android.internal.R.styleable.AndroidManifestApplication_description, 0);
+
+        if ((flags&PARSE_IS_SYSTEM) != 0) {
+            if (sa.getBoolean(
+                    com.android.internal.R.styleable.AndroidManifestApplication_persistent,
+                    false)) {
+                ai.flags |= ApplicationInfo.FLAG_PERSISTENT;
+            }
+        }
+
+        if (sa.getBoolean(
+                com.android.internal.R.styleable.AndroidManifestApplication_debuggable,
+                false)) {
+            ai.flags |= ApplicationInfo.FLAG_DEBUGGABLE;
+        }
+
+        if (sa.getBoolean(
+                com.android.internal.R.styleable.AndroidManifestApplication_hasCode,
+                true)) {
+            ai.flags |= ApplicationInfo.FLAG_HAS_CODE;
+        }
+
+        if (sa.getBoolean(
+                com.android.internal.R.styleable.AndroidManifestApplication_allowTaskReparenting,
+                false)) {
+            ai.flags |= ApplicationInfo.FLAG_ALLOW_TASK_REPARENTING;
+        }
+
+        if (sa.getBoolean(
+                com.android.internal.R.styleable.AndroidManifestApplication_allowClearUserData,
+                true)) {
+            ai.flags |= ApplicationInfo.FLAG_ALLOW_CLEAR_USER_DATA;
+        }
+
+        String str;
+        str = sa.getNonResourceString(
+                com.android.internal.R.styleable.AndroidManifestApplication_permission);
+        ai.permission = (str != null && str.length() > 0) ? str.intern() : null;
+
+        str = sa.getNonResourceString(
+                com.android.internal.R.styleable.AndroidManifestApplication_taskAffinity);
+        ai.taskAffinity = buildTaskAffinityName(ai.packageName, ai.packageName,
+                str, outError);
+
+        if (outError[0] == null) {
+            ai.processName = buildProcessName(ai.packageName, null, sa.getNonResourceString(
+                    com.android.internal.R.styleable.AndroidManifestApplication_process),
+                    flags, mSeparateProcesses, outError);
+    
+            ai.enabled = sa.getBoolean(com.android.internal.R.styleable.AndroidManifestApplication_enabled, true);
+        }
+
+        sa.recycle();
+
+        if (outError[0] != null) {
+            mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
+            return false;
+        }
+
+        final int innerDepth = parser.getDepth();
+
+        int type;
+        while ((type=parser.next()) != parser.END_DOCUMENT
+               && (type != parser.END_TAG || parser.getDepth() > innerDepth)) {
+            if (type == parser.END_TAG || type == parser.TEXT) {
+                continue;
+            }
+
+            String tagName = parser.getName();
+            if (tagName.equals("activity")) {
+                Activity a = parseActivity(owner, res, parser, attrs, flags, outError, false);
+                if (a == null) {
+                    mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
+                    return false;
+                }
+
+                owner.activities.add(a);
+
+            } else if (tagName.equals("receiver")) {
+                Activity a = parseActivity(owner, res, parser, attrs, flags, outError, true);
+                if (a == null) {
+                    mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
+                    return false;
+                }
+
+                owner.receivers.add(a);
+
+            } else if (tagName.equals("service")) {
+                Service s = parseService(owner, res, parser, attrs, flags, outError);
+                if (s == null) {
+                    mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
+                    return false;
+                }
+
+                owner.services.add(s);
+
+            } else if (tagName.equals("provider")) {
+                Provider p = parseProvider(owner, res, parser, attrs, flags, outError);
+                if (p == null) {
+                    mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
+                    return false;
+                }
+
+                owner.providers.add(p);
+
+            } else if (tagName.equals("activity-alias")) {
+                Activity a = parseActivityAlias(owner, res, parser, attrs, flags, outError, false);
+                if (a == null) {
+                    mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
+                    return false;
+                }
+
+                owner.activities.add(a);
+
+            } else if (parser.getName().equals("meta-data")) {
+                // note: application meta-data is stored off to the side, so it can
+                // remain null in the primary copy (we like to avoid extra copies because
+                // it can be large)
+                if ((owner.mAppMetaData = parseMetaData(res, parser, attrs, owner.mAppMetaData,
+                        outError)) == null) {
+                    mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
+                    return false;
+                }
+
+            } else if (tagName.equals("uses-library")) {
+                sa = res.obtainAttributes(attrs,
+                        com.android.internal.R.styleable.AndroidManifestUsesLibrary);
+
+                String lname = sa.getNonResourceString(
+                        com.android.internal.R.styleable.AndroidManifestUsesLibrary_name);
+
+                sa.recycle();
+
+                if (lname != null && !owner.usesLibraries.contains(lname)) {
+                    owner.usesLibraries.add(lname);
+                }
+
+                XmlUtils.skipCurrentTag(parser);
+
+            } else {
+                if (!RIGID_PARSER) {
+                    Log.w(TAG, "Problem in package " + mArchiveSourcePath + ":");
+                    Log.w(TAG, "Unknown element under <application>: " + tagName);
+                    XmlUtils.skipCurrentTag(parser);
+                    continue;
+                } else {
+                    outError[0] = "Bad element under <application>: " + tagName;
+                    mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
+                    return false;
+                }
+            }
+        }
+
+        return true;
+    }
+
+    private boolean parsePackageItemInfo(Package owner, PackageItemInfo outInfo,
+            String[] outError, String tag, TypedArray sa,
+            int nameRes, int labelRes, int iconRes) {
+        String name = sa.getNonResourceString(nameRes);
+        if (name == null) {
+            outError[0] = tag + " does not specify android:name";
+            return false;
+        }
+
+        outInfo.name
+            = buildClassName(owner.applicationInfo.packageName, name, outError);
+        if (outInfo.name == null) {
+            return false;
+        }
+
+        int iconVal = sa.getResourceId(iconRes, 0);
+        if (iconVal != 0) {
+            outInfo.icon = iconVal;
+            outInfo.nonLocalizedLabel = null;
+        }
+
+        TypedValue v = sa.peekValue(labelRes);
+        if (v != null && (outInfo.labelRes=v.resourceId) == 0) {
+            outInfo.nonLocalizedLabel = v.coerceToString();
+        }
+
+        outInfo.packageName = owner.packageName;
+
+        return true;
+    }
+
+    private boolean parseComponentInfo(Package owner, int flags,
+            ComponentInfo outInfo, String[] outError, String tag, TypedArray sa,
+            int nameRes, int labelRes, int iconRes, int processRes,
+            int enabledRes) {
+        if (!parsePackageItemInfo(owner, outInfo, outError, tag, sa,
+                nameRes, labelRes, iconRes)) {
+            return false;
+        }
+
+        if (processRes != 0) {
+            outInfo.processName = buildProcessName(owner.applicationInfo.packageName,
+                    owner.applicationInfo.processName, sa.getNonResourceString(processRes),
+                    flags, mSeparateProcesses, outError);
+        }
+        outInfo.enabled = sa.getBoolean(enabledRes, true);
+
+        return outError[0] == null;
+    }
+
+    private Activity parseActivity(Package owner, Resources res,
+            XmlPullParser parser, AttributeSet attrs, int flags, String[] outError,
+            boolean receiver) throws XmlPullParserException, IOException {
+        TypedArray sa = res.obtainAttributes(attrs,
+                com.android.internal.R.styleable.AndroidManifestActivity);
+
+        Activity a = new Activity(owner);
+
+        if (!parseComponentInfo(owner, flags, a.info, outError,
+                receiver ? "<receiver>" : "<activity>", sa,
+                com.android.internal.R.styleable.AndroidManifestActivity_name,
+                com.android.internal.R.styleable.AndroidManifestActivity_label,
+                com.android.internal.R.styleable.AndroidManifestActivity_icon,
+                com.android.internal.R.styleable.AndroidManifestActivity_process,
+                com.android.internal.R.styleable.AndroidManifestActivity_enabled)) {
+            sa.recycle();
+            return null;
+        }
+
+        final boolean setExported = sa.hasValue(
+                com.android.internal.R.styleable.AndroidManifestActivity_exported);
+        if (setExported) {
+            a.info.exported = sa.getBoolean(
+                    com.android.internal.R.styleable.AndroidManifestActivity_exported, false);
+        }
+
+        a.component = new ComponentName(owner.applicationInfo.packageName,
+                a.info.name);
+
+        a.info.theme = sa.getResourceId(
+                com.android.internal.R.styleable.AndroidManifestActivity_theme, 0);
+
+        String str;
+        str = sa.getNonResourceString(
+                com.android.internal.R.styleable.AndroidManifestActivity_permission);
+        if (str == null) {
+            a.info.permission = owner.applicationInfo.permission;
+        } else {
+            a.info.permission = str.length() > 0 ? str.toString().intern() : null;
+        }
+
+        str = sa.getNonResourceString(
+                com.android.internal.R.styleable.AndroidManifestActivity_taskAffinity);
+        a.info.taskAffinity = buildTaskAffinityName(owner.applicationInfo.packageName,
+                owner.applicationInfo.taskAffinity, str, outError);
+
+        a.info.flags = 0;
+        if (sa.getBoolean(
+                com.android.internal.R.styleable.AndroidManifestActivity_multiprocess,
+                false)) {
+            a.info.flags |= ActivityInfo.FLAG_MULTIPROCESS;
+        }
+
+        if (sa.getBoolean(
+                com.android.internal.R.styleable.AndroidManifestActivity_finishOnTaskLaunch,
+                false)) {
+            a.info.flags |= ActivityInfo.FLAG_FINISH_ON_TASK_LAUNCH;
+        }
+
+        if (sa.getBoolean(
+                com.android.internal.R.styleable.AndroidManifestActivity_clearTaskOnLaunch,
+                false)) {
+            a.info.flags |= ActivityInfo.FLAG_CLEAR_TASK_ON_LAUNCH;
+        }
+
+        if (sa.getBoolean(
+                com.android.internal.R.styleable.AndroidManifestActivity_noHistory,
+                false)) {
+            a.info.flags |= ActivityInfo.FLAG_NO_HISTORY;
+        }
+
+        if (sa.getBoolean(
+                com.android.internal.R.styleable.AndroidManifestActivity_alwaysRetainTaskState,
+                false)) {
+            a.info.flags |= ActivityInfo.FLAG_ALWAYS_RETAIN_TASK_STATE;
+        }
+
+        if (sa.getBoolean(
+                com.android.internal.R.styleable.AndroidManifestActivity_stateNotNeeded,
+                false)) {
+            a.info.flags |= ActivityInfo.FLAG_STATE_NOT_NEEDED;
+        }
+
+        if (sa.getBoolean(
+                com.android.internal.R.styleable.AndroidManifestActivity_excludeFromRecents,
+                false)) {
+            a.info.flags |= ActivityInfo.FLAG_EXCLUDE_FROM_RECENTS;
+        }
+
+        if (sa.getBoolean(
+                com.android.internal.R.styleable.AndroidManifestActivity_allowTaskReparenting,
+                (owner.applicationInfo.flags&ApplicationInfo.FLAG_ALLOW_TASK_REPARENTING) != 0)) {
+            a.info.flags |= ActivityInfo.FLAG_ALLOW_TASK_REPARENTING;
+        }
+
+        if (!receiver) {
+            a.info.launchMode = sa.getInt(
+                    com.android.internal.R.styleable.AndroidManifestActivity_launchMode,
+                    ActivityInfo.LAUNCH_MULTIPLE);
+            a.info.screenOrientation = sa.getInt(
+                    com.android.internal.R.styleable.AndroidManifestActivity_screenOrientation,
+                    ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
+            a.info.configChanges = sa.getInt(
+                    com.android.internal.R.styleable.AndroidManifestActivity_configChanges,
+                    0);
+            a.info.softInputMode = sa.getInt(
+                    com.android.internal.R.styleable.AndroidManifestActivity_windowSoftInputMode,
+                    0);
+        } else {
+            a.info.launchMode = ActivityInfo.LAUNCH_MULTIPLE;
+            a.info.configChanges = 0;
+        }
+
+        sa.recycle();
+
+        if (outError[0] != null) {
+            return null;
+        }
+
+        int outerDepth = parser.getDepth();
+        int type;
+        while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
+               && (type != XmlPullParser.END_TAG
+                       || parser.getDepth() > outerDepth)) {
+            if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+                continue;
+            }
+
+            if (parser.getName().equals("intent-filter")) {
+                ActivityIntentInfo intent = new ActivityIntentInfo(a);
+                if (!parseIntent(res, parser, attrs, flags, intent, outError, !receiver)) {
+                    return null;
+                }
+                if (intent.countActions() == 0) {
+                    Log.w(TAG, "Intent filter for activity " + intent
+                            + " defines no actions");
+                } else {
+                    a.intents.add(intent);
+                }
+            } else if (parser.getName().equals("meta-data")) {
+                if ((a.metaData=parseMetaData(res, parser, attrs, a.metaData,
+                        outError)) == null) {
+                    return null;
+                }
+            } else {
+                if (!RIGID_PARSER) {
+                    Log.w(TAG, "Problem in package " + mArchiveSourcePath + ":");
+                    if (receiver) {
+                        Log.w(TAG, "Unknown element under <receiver>: " + parser.getName());
+                    } else {
+                        Log.w(TAG, "Unknown element under <activity>: " + parser.getName());
+                    }
+                    XmlUtils.skipCurrentTag(parser);
+                    continue;
+                }
+                if (receiver) {
+                    outError[0] = "Bad element under <receiver>: " + parser.getName();
+                } else {
+                    outError[0] = "Bad element under <activity>: " + parser.getName();
+                }
+                return null;
+            }
+        }
+
+        if (!setExported) {
+            a.info.exported = a.intents.size() > 0;
+        }
+
+        return a;
+    }
+
+    private Activity parseActivityAlias(Package owner, Resources res,
+            XmlPullParser parser, AttributeSet attrs, int flags, String[] outError,
+            boolean receiver) throws XmlPullParserException, IOException {
+        TypedArray sa = res.obtainAttributes(attrs,
+                com.android.internal.R.styleable.AndroidManifestActivityAlias);
+
+        String targetActivity = sa.getNonResourceString(
+                com.android.internal.R.styleable.AndroidManifestActivityAlias_targetActivity);
+        if (targetActivity == null) {
+            outError[0] = "<activity-alias> does not specify android:targetActivity";
+            sa.recycle();
+            return null;
+        }
+
+        targetActivity = buildClassName(owner.applicationInfo.packageName,
+                targetActivity, outError);
+        if (targetActivity == null) {
+            sa.recycle();
+            return null;
+        }
+
+        Activity a = new Activity(owner);
+        Activity target = null;
+
+        final int NA = owner.activities.size();
+        for (int i=0; i<NA; i++) {
+            Activity t = owner.activities.get(i);
+            if (targetActivity.equals(t.info.name)) {
+                target = t;
+                break;
+            }
+        }
+
+        if (target == null) {
+            outError[0] = "<activity-alias> target activity " + targetActivity
+                    + " not found in manifest";
+            sa.recycle();
+            return null;
+        }
+
+        a.info.targetActivity = targetActivity;
+
+        a.info.configChanges = target.info.configChanges;
+        a.info.flags = target.info.flags;
+        a.info.icon = target.info.icon;
+        a.info.labelRes = target.info.labelRes;
+        a.info.launchMode = target.info.launchMode;
+        a.info.nonLocalizedLabel = target.info.nonLocalizedLabel;
+        a.info.processName = target.info.processName;
+        a.info.screenOrientation = target.info.screenOrientation;
+        a.info.taskAffinity = target.info.taskAffinity;
+        a.info.theme = target.info.theme;
+
+        if (!parseComponentInfo(owner, flags, a.info, outError,
+                receiver ? "<receiver>" : "<activity>", sa,
+                com.android.internal.R.styleable.AndroidManifestActivityAlias_name,
+                com.android.internal.R.styleable.AndroidManifestActivityAlias_label,
+                com.android.internal.R.styleable.AndroidManifestActivityAlias_icon,
+                0,
+                com.android.internal.R.styleable.AndroidManifestActivityAlias_enabled)) {
+            sa.recycle();
+            return null;
+        }
+
+        final boolean setExported = sa.hasValue(
+                com.android.internal.R.styleable.AndroidManifestActivityAlias_exported);
+        if (setExported) {
+            a.info.exported = sa.getBoolean(
+                    com.android.internal.R.styleable.AndroidManifestActivityAlias_exported, false);
+        }
+
+        a.component = new ComponentName(owner.applicationInfo.packageName,
+                a.info.name);
+
+        String str;
+        str = sa.getNonResourceString(
+                com.android.internal.R.styleable.AndroidManifestActivityAlias_permission);
+        if (str != null) {
+            a.info.permission = str.length() > 0 ? str.toString().intern() : null;
+        }
+
+        sa.recycle();
+
+        if (outError[0] != null) {
+            return null;
+        }
+
+        int outerDepth = parser.getDepth();
+        int type;
+        while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
+               && (type != XmlPullParser.END_TAG
+                       || parser.getDepth() > outerDepth)) {
+            if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+                continue;
+            }
+
+            if (parser.getName().equals("intent-filter")) {
+                ActivityIntentInfo intent = new ActivityIntentInfo(a);
+                if (!parseIntent(res, parser, attrs, flags, intent, outError, true)) {
+                    return null;
+                }
+                if (intent.countActions() == 0) {
+                    Log.w(TAG, "Intent filter for activity alias " + intent
+                            + " defines no actions");
+                } else {
+                    a.intents.add(intent);
+                }
+            } else if (parser.getName().equals("meta-data")) {
+                if ((a.metaData=parseMetaData(res, parser, attrs, a.metaData,
+                        outError)) == null) {
+                    return null;
+                }
+            } else {
+                if (!RIGID_PARSER) {
+                    Log.w(TAG, "Problem in package " + mArchiveSourcePath + ":");
+                    Log.w(TAG, "Unknown element under <activity-alias>: " + parser.getName());
+                    XmlUtils.skipCurrentTag(parser);
+                    continue;
+                }
+                outError[0] = "Bad element under <activity-alias>: " + parser.getName();
+                return null;
+            }
+        }
+
+        if (!setExported) {
+            a.info.exported = a.intents.size() > 0;
+        }
+
+        return a;
+    }
+
+    private Provider parseProvider(Package owner, Resources res,
+            XmlPullParser parser, AttributeSet attrs, int flags, String[] outError)
+            throws XmlPullParserException, IOException {
+        TypedArray sa = res.obtainAttributes(attrs,
+                com.android.internal.R.styleable.AndroidManifestProvider);
+
+        Provider p = new Provider(owner);
+
+        if (!parseComponentInfo(owner, flags, p.info, outError, "<provider>", sa,
+                com.android.internal.R.styleable.AndroidManifestProvider_name,
+                com.android.internal.R.styleable.AndroidManifestProvider_label,
+                com.android.internal.R.styleable.AndroidManifestProvider_icon,
+                com.android.internal.R.styleable.AndroidManifestProvider_process,
+                com.android.internal.R.styleable.AndroidManifestProvider_enabled)) {
+            sa.recycle();
+            return null;
+        }
+
+        p.info.exported = sa.getBoolean(
+                com.android.internal.R.styleable.AndroidManifestProvider_exported, true);
+
+        p.component = new ComponentName(owner.applicationInfo.packageName,
+                p.info.name);
+
+        String cpname = sa.getNonResourceString(
+                com.android.internal.R.styleable.AndroidManifestProvider_authorities);
+
+        p.info.isSyncable = sa.getBoolean(
+                com.android.internal.R.styleable.AndroidManifestProvider_syncable,
+                false);
+
+        String permission = sa.getNonResourceString(
+                com.android.internal.R.styleable.AndroidManifestProvider_permission);
+        String str = sa.getNonResourceString(
+                com.android.internal.R.styleable.AndroidManifestProvider_readPermission);
+        if (str == null) {
+            str = permission;
+        }
+        if (str == null) {
+            p.info.readPermission = owner.applicationInfo.permission;
+        } else {
+            p.info.readPermission =
+                str.length() > 0 ? str.toString().intern() : null;
+        }
+        str = sa.getNonResourceString(
+                com.android.internal.R.styleable.AndroidManifestProvider_writePermission);
+        if (str == null) {
+            str = permission;
+        }
+        if (str == null) {
+            p.info.writePermission = owner.applicationInfo.permission;
+        } else {
+            p.info.writePermission =
+                str.length() > 0 ? str.toString().intern() : null;
+        }
+
+        p.info.grantUriPermissions = sa.getBoolean(
+                com.android.internal.R.styleable.AndroidManifestProvider_grantUriPermissions,
+                false);
+
+        p.info.multiprocess = sa.getBoolean(
+                com.android.internal.R.styleable.AndroidManifestProvider_multiprocess,
+                false);
+
+        p.info.initOrder = sa.getInt(
+                com.android.internal.R.styleable.AndroidManifestProvider_initOrder,
+                0);
+
+        sa.recycle();
+
+        if (cpname == null) {
+            outError[0] = "<provider> does not incude authorities attribute";
+            return null;
+        }
+        p.info.authority = cpname.intern();
+
+        if (!parseProviderTags(res, parser, attrs, p, outError)) {
+            return null;
+        }
+
+        return p;
+    }
+
+    private boolean parseProviderTags(Resources res,
+            XmlPullParser parser, AttributeSet attrs,
+            Provider outInfo, String[] outError)
+            throws XmlPullParserException, IOException {
+        int outerDepth = parser.getDepth();
+        int type;
+        while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
+               && (type != XmlPullParser.END_TAG
+                       || parser.getDepth() > outerDepth)) {
+            if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+                continue;
+            }
+
+            if (parser.getName().equals("meta-data")) {
+                if ((outInfo.metaData=parseMetaData(res, parser, attrs,
+                        outInfo.metaData, outError)) == null) {
+                    return false;
+                }
+            } else if (parser.getName().equals("grant-uri-permission")) {
+                TypedArray sa = res.obtainAttributes(attrs,
+                        com.android.internal.R.styleable.AndroidManifestGrantUriPermission);
+
+                PatternMatcher pa = null;
+
+                String str = sa.getNonResourceString(
+                        com.android.internal.R.styleable.AndroidManifestGrantUriPermission_path);
+                if (str != null) {
+                    pa = new PatternMatcher(str, PatternMatcher.PATTERN_LITERAL);
+                }
+
+                str = sa.getNonResourceString(
+                        com.android.internal.R.styleable.AndroidManifestGrantUriPermission_pathPrefix);
+                if (str != null) {
+                    pa = new PatternMatcher(str, PatternMatcher.PATTERN_PREFIX);
+                }
+
+                str = sa.getNonResourceString(
+                        com.android.internal.R.styleable.AndroidManifestGrantUriPermission_pathPattern);
+                if (str != null) {
+                    pa = new PatternMatcher(str, PatternMatcher.PATTERN_SIMPLE_GLOB);
+                }
+
+                sa.recycle();
+
+                if (pa != null) {
+                    if (outInfo.info.uriPermissionPatterns == null) {
+                        outInfo.info.uriPermissionPatterns = new PatternMatcher[1];
+                        outInfo.info.uriPermissionPatterns[0] = pa;
+                    } else {
+                        final int N = outInfo.info.uriPermissionPatterns.length;
+                        PatternMatcher[] newp = new PatternMatcher[N+1];
+                        System.arraycopy(outInfo.info.uriPermissionPatterns, 0, newp, 0, N);
+                        newp[N] = pa;
+                        outInfo.info.uriPermissionPatterns = newp;
+                    }
+                    outInfo.info.grantUriPermissions = true;
+                }
+                XmlUtils.skipCurrentTag(parser);
+
+            } else {
+                if (!RIGID_PARSER) {
+                    Log.w(TAG, "Problem in package " + mArchiveSourcePath + ":");
+                    Log.w(TAG, "Unknown element under <provider>: "
+                            + parser.getName());
+                    XmlUtils.skipCurrentTag(parser);
+                    continue;
+                }
+                outError[0] = "Bad element under <provider>: "
+                    + parser.getName();
+                return false;
+            }
+        }
+        return true;
+    }
+
+    private Service parseService(Package owner, Resources res,
+            XmlPullParser parser, AttributeSet attrs, int flags, String[] outError)
+            throws XmlPullParserException, IOException {
+        TypedArray sa = res.obtainAttributes(attrs,
+                com.android.internal.R.styleable.AndroidManifestService);
+
+        Service s = new Service(owner);
+
+        if (!parseComponentInfo(owner, flags, s.info, outError, "<service>", sa,
+                com.android.internal.R.styleable.AndroidManifestService_name,
+                com.android.internal.R.styleable.AndroidManifestService_label,
+                com.android.internal.R.styleable.AndroidManifestService_icon,
+                com.android.internal.R.styleable.AndroidManifestService_process,
+                com.android.internal.R.styleable.AndroidManifestService_enabled)) {
+            sa.recycle();
+            return null;
+        }
+
+        final boolean setExported = sa.hasValue(
+                com.android.internal.R.styleable.AndroidManifestService_exported);
+        if (setExported) {
+            s.info.exported = sa.getBoolean(
+                    com.android.internal.R.styleable.AndroidManifestService_exported, false);
+        }
+
+        s.component = new ComponentName(owner.applicationInfo.packageName,
+                s.info.name);
+
+        String str = sa.getNonResourceString(
+                com.android.internal.R.styleable.AndroidManifestService_permission);
+        if (str == null) {
+            s.info.permission = owner.applicationInfo.permission;
+        } else {
+            s.info.permission = str.length() > 0 ? str.toString().intern() : null;
+        }
+
+        sa.recycle();
+
+        int outerDepth = parser.getDepth();
+        int type;
+        while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
+               && (type != XmlPullParser.END_TAG
+                       || parser.getDepth() > outerDepth)) {
+            if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+                continue;
+            }
+
+            if (parser.getName().equals("intent-filter")) {
+                ServiceIntentInfo intent = new ServiceIntentInfo(s);
+                if (!parseIntent(res, parser, attrs, flags, intent, outError, false)) {
+                    return null;
+                }
+
+                s.intents.add(intent);
+            } else if (parser.getName().equals("meta-data")) {
+                if ((s.metaData=parseMetaData(res, parser, attrs, s.metaData,
+                        outError)) == null) {
+                    return null;
+                }
+            } else {
+                if (!RIGID_PARSER) {
+                    Log.w(TAG, "Problem in package " + mArchiveSourcePath + ":");
+                    Log.w(TAG, "Unknown element under <service>: "
+                            + parser.getName());
+                    XmlUtils.skipCurrentTag(parser);
+                    continue;
+                }
+                outError[0] = "Bad element under <service>: "
+                    + parser.getName();
+                return null;
+            }
+        }
+
+        if (!setExported) {
+            s.info.exported = s.intents.size() > 0;
+        }
+
+        return s;
+    }
+
+    private boolean parseAllMetaData(Resources res,
+            XmlPullParser parser, AttributeSet attrs, String tag,
+            Component outInfo, String[] outError)
+            throws XmlPullParserException, IOException {
+        int outerDepth = parser.getDepth();
+        int type;
+        while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
+               && (type != XmlPullParser.END_TAG
+                       || parser.getDepth() > outerDepth)) {
+            if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+                continue;
+            }
+
+            if (parser.getName().equals("meta-data")) {
+                if ((outInfo.metaData=parseMetaData(res, parser, attrs,
+                        outInfo.metaData, outError)) == null) {
+                    return false;
+                }
+            } else {
+                if (!RIGID_PARSER) {
+                    Log.w(TAG, "Problem in package " + mArchiveSourcePath + ":");
+                    Log.w(TAG, "Unknown element under " + tag + ": "
+                            + parser.getName());
+                    XmlUtils.skipCurrentTag(parser);
+                    continue;
+                }
+                outError[0] = "Bad element under " + tag + ": "
+                    + parser.getName();
+                return false;
+            }
+        }
+        return true;
+    }
+
+    private Bundle parseMetaData(Resources res,
+            XmlPullParser parser, AttributeSet attrs,
+            Bundle data, String[] outError)
+            throws XmlPullParserException, IOException {
+
+        TypedArray sa = res.obtainAttributes(attrs,
+                com.android.internal.R.styleable.AndroidManifestMetaData);
+
+        if (data == null) {
+            data = new Bundle();
+        }
+
+        String name = sa.getNonResourceString(
+                com.android.internal.R.styleable.AndroidManifestMetaData_name);
+        if (name == null) {
+            outError[0] = "<meta-data> requires an android:name attribute";
+            sa.recycle();
+            return null;
+        }
+
+        boolean success = true;
+
+        TypedValue v = sa.peekValue(
+                com.android.internal.R.styleable.AndroidManifestMetaData_resource);
+        if (v != null && v.resourceId != 0) {
+            //Log.i(TAG, "Meta data ref " + name + ": " + v);
+            data.putInt(name, v.resourceId);
+        } else {
+            v = sa.peekValue(
+                    com.android.internal.R.styleable.AndroidManifestMetaData_value);
+            //Log.i(TAG, "Meta data " + name + ": " + v);
+            if (v != null) {
+                if (v.type == TypedValue.TYPE_STRING) {
+                    CharSequence cs = v.coerceToString();
+                    data.putString(name, cs != null ? cs.toString() : null);
+                } else if (v.type == TypedValue.TYPE_INT_BOOLEAN) {
+                    data.putBoolean(name, v.data != 0);
+                } else if (v.type >= TypedValue.TYPE_FIRST_INT
+                        && v.type <= TypedValue.TYPE_LAST_INT) {
+                    data.putInt(name, v.data);
+                } else if (v.type == TypedValue.TYPE_FLOAT) {
+                    data.putFloat(name, v.getFloat());
+                } else {
+                    if (!RIGID_PARSER) {
+                        Log.w(TAG, "Problem in package " + mArchiveSourcePath + ":");
+                        Log.w(TAG, "<meta-data> only supports string, integer, float, color, boolean, and resource reference types");
+                    } else {
+                        outError[0] = "<meta-data> only supports string, integer, float, color, boolean, and resource reference types";
+                        data = null;
+                    }
+                }
+            } else {
+                outError[0] = "<meta-data> requires an android:value or android:resource attribute";
+                data = null;
+            }
+        }
+
+        sa.recycle();
+
+        XmlUtils.skipCurrentTag(parser);
+
+        return data;
+    }
+
+    private static final String ANDROID_RESOURCES
+            = "http://schemas.android.com/apk/res/android";
+
+    private boolean parseIntent(Resources res,
+            XmlPullParser parser, AttributeSet attrs, int flags,
+            IntentInfo outInfo, String[] outError, boolean isActivity)
+            throws XmlPullParserException, IOException {
+
+        TypedArray sa = res.obtainAttributes(attrs,
+                com.android.internal.R.styleable.AndroidManifestIntentFilter);
+
+        int priority = sa.getInt(
+                com.android.internal.R.styleable.AndroidManifestIntentFilter_priority, 0);
+        if (priority > 0 && isActivity && (flags&PARSE_IS_SYSTEM) == 0) {
+            Log.w(TAG, "Activity with priority > 0, forcing to 0 at "
+                    + parser.getPositionDescription());
+            priority = 0;
+        }
+        outInfo.setPriority(priority);
+        
+        TypedValue v = sa.peekValue(
+                com.android.internal.R.styleable.AndroidManifestIntentFilter_label);
+        if (v != null && (outInfo.labelRes=v.resourceId) == 0) {
+            outInfo.nonLocalizedLabel = v.coerceToString();
+        }
+
+        outInfo.icon = sa.getResourceId(
+                com.android.internal.R.styleable.AndroidManifestIntentFilter_icon, 0);
+
+        sa.recycle();
+
+        int outerDepth = parser.getDepth();
+        int type;
+        while ((type=parser.next()) != parser.END_DOCUMENT
+               && (type != parser.END_TAG || parser.getDepth() > outerDepth)) {
+            if (type == parser.END_TAG || type == parser.TEXT) {
+                continue;
+            }
+
+            String nodeName = parser.getName();
+            if (nodeName.equals("action")) {
+                String value = attrs.getAttributeValue(
+                        ANDROID_RESOURCES, "name");
+                if (value == null || value == "") {
+                    outError[0] = "No value supplied for <android:name>";
+                    return false;
+                }
+                XmlUtils.skipCurrentTag(parser);
+
+                outInfo.addAction(value);
+            } else if (nodeName.equals("category")) {
+                String value = attrs.getAttributeValue(
+                        ANDROID_RESOURCES, "name");
+                if (value == null || value == "") {
+                    outError[0] = "No value supplied for <android:name>";
+                    return false;
+                }
+                XmlUtils.skipCurrentTag(parser);
+
+                outInfo.addCategory(value);
+
+            } else if (nodeName.equals("data")) {
+                sa = res.obtainAttributes(attrs,
+                        com.android.internal.R.styleable.AndroidManifestData);
+
+                String str = sa.getNonResourceString(
+                        com.android.internal.R.styleable.AndroidManifestData_mimeType);
+                if (str != null) {
+                    try {
+                        outInfo.addDataType(str);
+                    } catch (IntentFilter.MalformedMimeTypeException e) {
+                        outError[0] = e.toString();
+                        sa.recycle();
+                        return false;
+                    }
+                }
+
+                str = sa.getNonResourceString(
+                        com.android.internal.R.styleable.AndroidManifestData_scheme);
+                if (str != null) {
+                    outInfo.addDataScheme(str);
+                }
+
+                String host = sa.getNonResourceString(
+                        com.android.internal.R.styleable.AndroidManifestData_host);
+                String port = sa.getNonResourceString(
+                        com.android.internal.R.styleable.AndroidManifestData_port);
+                if (host != null) {
+                    outInfo.addDataAuthority(host, port);
+                }
+
+                str = sa.getNonResourceString(
+                        com.android.internal.R.styleable.AndroidManifestData_path);
+                if (str != null) {
+                    outInfo.addDataPath(str, PatternMatcher.PATTERN_LITERAL);
+                }
+
+                str = sa.getNonResourceString(
+                        com.android.internal.R.styleable.AndroidManifestData_pathPrefix);
+                if (str != null) {
+                    outInfo.addDataPath(str, PatternMatcher.PATTERN_PREFIX);
+                }
+
+                str = sa.getNonResourceString(
+                        com.android.internal.R.styleable.AndroidManifestData_pathPattern);
+                if (str != null) {
+                    outInfo.addDataPath(str, PatternMatcher.PATTERN_SIMPLE_GLOB);
+                }
+
+                sa.recycle();
+                XmlUtils.skipCurrentTag(parser);
+            } else if (!RIGID_PARSER) {
+                Log.w(TAG, "Problem in package " + mArchiveSourcePath + ":");
+                Log.w(TAG, "Unknown element under <intent-filter>: " + parser.getName());
+                XmlUtils.skipCurrentTag(parser);
+            } else {
+                outError[0] = "Bad element under <intent-filter>: " + parser.getName();
+                return false;
+            }
+        }
+
+        outInfo.hasDefault = outInfo.hasCategory(Intent.CATEGORY_DEFAULT);
+        if (false) {
+            String cats = "";
+            Iterator<String> it = outInfo.categoriesIterator();
+            while (it != null && it.hasNext()) {
+                cats += " " + it.next();
+            }
+            System.out.println("Intent d=" +
+                    outInfo.hasDefault + ", cat=" + cats);
+        }
+
+        return true;
+    }
+
+    public final static class Package {
+        public final String packageName;
+
+        // For now we only support one application per package.
+        public final ApplicationInfo applicationInfo = new ApplicationInfo();
+
+        public final ArrayList<Permission> permissions = new ArrayList<Permission>(0);
+        public final ArrayList<PermissionGroup> permissionGroups = new ArrayList<PermissionGroup>(0);
+        public final ArrayList<Activity> activities = new ArrayList<Activity>(0);
+        public final ArrayList<Activity> receivers = new ArrayList<Activity>(0);
+        public final ArrayList<Provider> providers = new ArrayList<Provider>(0);
+        public final ArrayList<Service> services = new ArrayList<Service>(0);
+        public final ArrayList<Instrumentation> instrumentation = new ArrayList<Instrumentation>(0);
+
+        public final ArrayList<String> requestedPermissions = new ArrayList<String>();
+
+        public final ArrayList<String> usesLibraries = new ArrayList<String>();
+        public String[] usesLibraryFiles = null;
+
+        // We store the application meta-data independently to avoid multiple unwanted references
+        public Bundle mAppMetaData = null;
+
+        // If this is a 3rd party app, this is the path of the zip file.
+        public String mPath;
+
+        // True if this package is part of the system image.
+        public boolean mSystem;
+
+        // The version code declared for this package.
+        public int mVersionCode;
+        
+        // The version name declared for this package.
+        public String mVersionName;
+        
+        // The shared user id that this package wants to use.
+        public String mSharedUserId;
+
+        // The shared user label that this package wants to use.
+        public int mSharedUserLabel;
+
+        // Signatures that were read from the package.
+        public Signature mSignatures[];
+
+        // For use by package manager service for quick lookup of
+        // preferred up order.
+        public int mPreferredOrder = 0;
+
+        // Additional data supplied by callers.
+        public Object mExtras;
+        
+        /*
+         *  Applications hardware preferences
+         */
+        public final ArrayList<ConfigurationInfo> configPreferences =
+                new ArrayList<ConfigurationInfo>();
+
+        public Package(String _name) {
+            packageName = _name;
+            applicationInfo.packageName = _name;
+            applicationInfo.uid = -1;
+        }
+
+        public String toString() {
+            return "Package{"
+                + Integer.toHexString(System.identityHashCode(this))
+                + " " + packageName + "}";
+        }
+    }
+
+    public static class Component<II extends IntentInfo> {
+        public final Package owner;
+        public final ArrayList<II> intents = new ArrayList<II>(0);
+        public ComponentName component;
+        public Bundle metaData;
+
+        public Component(Package _owner) {
+            owner = _owner;
+        }
+
+        public Component(Component<II> clone) {
+            owner = clone.owner;
+            metaData = clone.metaData;
+        }
+    }
+    
+    public final static class Permission extends Component<IntentInfo> {
+        public final PermissionInfo info;
+        public boolean tree;
+        public PermissionGroup group;
+
+        public Permission(Package _owner) {
+            super(_owner);
+            info = new PermissionInfo();
+        }
+
+        public Permission(Package _owner, PermissionInfo _info) {
+            super(_owner);
+            info = _info;
+        }
+
+        public String toString() {
+            return "Permission{"
+                + Integer.toHexString(System.identityHashCode(this))
+                + " " + info.name + "}";
+        }
+    }
+
+    public final static class PermissionGroup extends Component<IntentInfo> {
+        public final PermissionGroupInfo info;
+
+        public PermissionGroup(Package _owner) {
+            super(_owner);
+            info = new PermissionGroupInfo();
+        }
+
+        public PermissionGroup(Package _owner, PermissionGroupInfo _info) {
+            super(_owner);
+            info = _info;
+        }
+
+        public String toString() {
+            return "PermissionGroup{"
+                + Integer.toHexString(System.identityHashCode(this))
+                + " " + info.name + "}";
+        }
+    }
+
+    private static boolean copyNeeded(int flags, Package p, Bundle metaData) {
+        if ((flags & PackageManager.GET_META_DATA) != 0
+                && (metaData != null || p.mAppMetaData != null)) {
+            return true;
+        }
+        if ((flags & PackageManager.GET_SHARED_LIBRARY_FILES) != 0
+                && p.usesLibraryFiles != null) {
+            return true;
+        }
+        return false;
+    }
+
+    public static ApplicationInfo generateApplicationInfo(Package p, int flags) {
+        if (p == null) return null;
+        if (!copyNeeded(flags, p, null)) {
+            return p.applicationInfo;
+        }
+
+        // Make shallow copy so we can store the metadata/libraries safely
+        ApplicationInfo ai = new ApplicationInfo(p.applicationInfo);
+        if ((flags & PackageManager.GET_META_DATA) != 0) {
+            ai.metaData = p.mAppMetaData;
+        }
+        if ((flags & PackageManager.GET_SHARED_LIBRARY_FILES) != 0) {
+            ai.sharedLibraryFiles = p.usesLibraryFiles;
+        }
+        return ai;
+    }
+
+    public static final PermissionInfo generatePermissionInfo(
+            Permission p, int flags) {
+        if (p == null) return null;
+        if ((flags&PackageManager.GET_META_DATA) == 0) {
+            return p.info;
+        }
+        PermissionInfo pi = new PermissionInfo(p.info);
+        pi.metaData = p.metaData;
+        return pi;
+    }
+
+    public static final PermissionGroupInfo generatePermissionGroupInfo(
+            PermissionGroup pg, int flags) {
+        if (pg == null) return null;
+        if ((flags&PackageManager.GET_META_DATA) == 0) {
+            return pg.info;
+        }
+        PermissionGroupInfo pgi = new PermissionGroupInfo(pg.info);
+        pgi.metaData = pg.metaData;
+        return pgi;
+    }
+
+    public final static class Activity extends Component<ActivityIntentInfo> {
+        public final ActivityInfo info =
+                new ActivityInfo();
+
+        public Activity(Package _owner) {
+            super(_owner);
+            info.applicationInfo = owner.applicationInfo;
+        }
+
+        public String toString() {
+            return "Activity{"
+                + Integer.toHexString(System.identityHashCode(this))
+                + " " + component.flattenToString() + "}";
+        }
+    }
+
+    public static final ActivityInfo generateActivityInfo(Activity a,
+            int flags) {
+        if (a == null) return null;
+        if (!copyNeeded(flags, a.owner, a.metaData)) {
+            return a.info;
+        }
+        // Make shallow copies so we can store the metadata safely
+        ActivityInfo ai = new ActivityInfo(a.info);
+        ai.metaData = a.metaData;
+        ai.applicationInfo = generateApplicationInfo(a.owner, flags);
+        return ai;
+    }
+
+    public final static class Service extends Component<ServiceIntentInfo> {
+        public final ServiceInfo info =
+                new ServiceInfo();
+
+        public Service(Package _owner) {
+            super(_owner);
+            info.applicationInfo = owner.applicationInfo;
+        }
+
+        public String toString() {
+            return "Service{"
+                + Integer.toHexString(System.identityHashCode(this))
+                + " " + component.flattenToString() + "}";
+        }
+    }
+
+    public static final ServiceInfo generateServiceInfo(Service s, int flags) {
+        if (s == null) return null;
+        if (!copyNeeded(flags, s.owner, s.metaData)) {
+            return s.info;
+        }
+        // Make shallow copies so we can store the metadata safely
+        ServiceInfo si = new ServiceInfo(s.info);
+        si.metaData = s.metaData;
+        si.applicationInfo = generateApplicationInfo(s.owner, flags);
+        return si;
+    }
+
+    public final static class Provider extends Component {
+        public final ProviderInfo info;
+        public boolean syncable;
+
+        public Provider(Package _owner) {
+            super(_owner);
+            info = new ProviderInfo();
+            info.applicationInfo = owner.applicationInfo;
+            syncable = false;
+        }
+
+        public Provider(Provider existingProvider) {
+            super(existingProvider);
+            this.info = existingProvider.info;
+            this.syncable = existingProvider.syncable;
+        }
+
+        public String toString() {
+            return "Provider{"
+                + Integer.toHexString(System.identityHashCode(this))
+                + " " + info.name + "}";
+        }
+    }
+
+    public static final ProviderInfo generateProviderInfo(Provider p,
+            int flags) {
+        if (p == null) return null;
+        if (!copyNeeded(flags, p.owner, p.metaData)
+                && ((flags & PackageManager.GET_URI_PERMISSION_PATTERNS) != 0
+                        || p.info.uriPermissionPatterns == null)) {
+            return p.info;
+        }
+        // Make shallow copies so we can store the metadata safely
+        ProviderInfo pi = new ProviderInfo(p.info);
+        pi.metaData = p.metaData;
+        if ((flags & PackageManager.GET_URI_PERMISSION_PATTERNS) == 0) {
+            pi.uriPermissionPatterns = null;
+        }
+        pi.applicationInfo = generateApplicationInfo(p.owner, flags);
+        return pi;
+    }
+
+    public final static class Instrumentation extends Component {
+        public final InstrumentationInfo info =
+                new InstrumentationInfo();
+
+        public Instrumentation(Package _owner) {
+            super(_owner);
+        }
+
+        public String toString() {
+            return "Instrumentation{"
+                + Integer.toHexString(System.identityHashCode(this))
+                + " " + component.flattenToString() + "}";
+        }
+    }
+
+    public static final InstrumentationInfo generateInstrumentationInfo(
+            Instrumentation i, int flags) {
+        if (i == null) return null;
+        if ((flags&PackageManager.GET_META_DATA) == 0) {
+            return i.info;
+        }
+        InstrumentationInfo ii = new InstrumentationInfo(i.info);
+        ii.metaData = i.metaData;
+        return ii;
+    }
+
+    public static class IntentInfo extends IntentFilter {
+        public boolean hasDefault;
+        public int labelRes;
+        public CharSequence nonLocalizedLabel;
+        public int icon;
+    }
+
+    public final static class ActivityIntentInfo extends IntentInfo {
+        public final Activity activity;
+
+        public ActivityIntentInfo(Activity _activity) {
+            activity = _activity;
+        }
+
+        public String toString() {
+            return "ActivityIntentInfo{"
+                + Integer.toHexString(System.identityHashCode(this))
+                + " " + activity.info.name + "}";
+        }
+    }
+
+    public final static class ServiceIntentInfo extends IntentInfo {
+        public final Service service;
+
+        public ServiceIntentInfo(Service _service) {
+            service = _service;
+        }
+
+        public String toString() {
+            return "ServiceIntentInfo{"
+                + Integer.toHexString(System.identityHashCode(this))
+                + " " + service.info.name + "}";
+        }
+    }
+}
diff --git a/core/java/android/content/pm/PackageStats.aidl b/core/java/android/content/pm/PackageStats.aidl
new file mode 100755
index 0000000..8c9786f
--- /dev/null
+++ b/core/java/android/content/pm/PackageStats.aidl
@@ -0,0 +1,20 @@
+/* //device/java/android/android/view/WindowManager.aidl
+**
+** Copyright 2007, 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 android.content.pm;
+
+parcelable PackageStats;
diff --git a/core/java/android/content/pm/PackageStats.java b/core/java/android/content/pm/PackageStats.java
new file mode 100755
index 0000000..66c6efd
--- /dev/null
+++ b/core/java/android/content/pm/PackageStats.java
@@ -0,0 +1,63 @@
+package android.content.pm;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Arrays;
+
+/**
+ * implementation of PackageStats associated with a
+ * application package.
+ */
+public class PackageStats implements Parcelable {
+    public String packageName;
+    public long codeSize;
+    public long dataSize;
+    public long cacheSize;
+    
+    public static final Parcelable.Creator<PackageStats> CREATOR
+    = new Parcelable.Creator<PackageStats>() {
+        public PackageStats createFromParcel(Parcel in) {
+            return new PackageStats(in);
+        }
+
+        public PackageStats[] newArray(int size) {
+            return new PackageStats[size];
+        }
+    };
+    
+    public String toString() {
+        return "PackageStats{"
+        + Integer.toHexString(System.identityHashCode(this))
+        + " " + packageName + "}";
+    }
+    
+    public PackageStats(String pkgName) {
+        packageName = pkgName;
+    }
+    
+    public PackageStats(Parcel source) {
+        packageName = source.readString();
+        codeSize = source.readLong();
+        dataSize = source.readLong();
+        cacheSize = source.readLong();
+    }
+    
+    public PackageStats(PackageStats pStats) {
+        packageName = pStats.packageName;
+        codeSize = pStats.codeSize;
+        dataSize = pStats.dataSize;
+        cacheSize = pStats.cacheSize;
+    }
+
+    public int describeContents() {
+        return 0;
+    }
+
+    public void writeToParcel(Parcel dest, int parcelableFlags){
+        dest.writeString(packageName);
+        dest.writeLong(codeSize);
+        dest.writeLong(dataSize);
+        dest.writeLong(cacheSize);
+    }
+}
diff --git a/core/java/android/content/pm/PermissionGroupInfo.aidl b/core/java/android/content/pm/PermissionGroupInfo.aidl
new file mode 100755
index 0000000..9f215f1
--- /dev/null
+++ b/core/java/android/content/pm/PermissionGroupInfo.aidl
@@ -0,0 +1,20 @@
+/* //device/java/android/android/view/WindowManager.aidl
+**
+** Copyright 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 android.content.pm;
+
+parcelable PermissionGroupInfo;
diff --git a/core/java/android/content/pm/PermissionGroupInfo.java b/core/java/android/content/pm/PermissionGroupInfo.java
new file mode 100644
index 0000000..02eb816
--- /dev/null
+++ b/core/java/android/content/pm/PermissionGroupInfo.java
@@ -0,0 +1,108 @@
+/*
+ * 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 android.content.pm;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+
+/**
+ * Information you can retrieve about a particular security permission
+ * group known to the system.  This corresponds to information collected from the
+ * AndroidManifest.xml's &lt;permission-group&gt; tags.
+ */
+public class PermissionGroupInfo extends PackageItemInfo implements Parcelable {
+    /**
+     * A string resource identifier (in the package's resources) of this
+     * permission's description.  From the "description" attribute or,
+     * if not set, 0.
+     */
+    public int descriptionRes;
+
+    /**
+     * The description string provided in the AndroidManifest file, if any.  You
+     * probably don't want to use this, since it will be null if the description
+     * is in a resource.  You probably want
+     * {@link PermissionInfo#loadDescription} instead.
+     */
+    public CharSequence nonLocalizedDescription;
+
+    public PermissionGroupInfo() {
+    }
+
+    public PermissionGroupInfo(PermissionGroupInfo orig) {
+        super(orig);
+        descriptionRes = orig.descriptionRes;
+        nonLocalizedDescription = orig.nonLocalizedDescription;
+    }
+
+    /**
+     * Retrieve the textual description of this permission.  This
+     * will call back on the given PackageManager to load the description from
+     * the application.
+     *
+     * @param pm A PackageManager from which the label can be loaded; usually
+     * the PackageManager from which you originally retrieved this item.
+     *
+     * @return Returns a CharSequence containing the permission's description.
+     * If there is no description, null is returned.
+     */
+    public CharSequence loadDescription(PackageManager pm) {
+        if (nonLocalizedDescription != null) {
+            return nonLocalizedDescription;
+        }
+        if (descriptionRes != 0) {
+            CharSequence label = pm.getText(packageName, descriptionRes, null);
+            if (label != null) {
+                return label;
+            }
+        }
+        return null;
+    }
+
+    public String toString() {
+        return "PermissionGroupInfo{"
+            + Integer.toHexString(System.identityHashCode(this))
+            + " " + name + "}";
+    }
+
+    public int describeContents() {
+        return 0;
+    }
+
+    public void writeToParcel(Parcel dest, int parcelableFlags) {
+        super.writeToParcel(dest, parcelableFlags);
+        dest.writeInt(descriptionRes);
+        TextUtils.writeToParcel(nonLocalizedDescription, dest, parcelableFlags);
+    }
+
+    public static final Creator<PermissionGroupInfo> CREATOR =
+            new Creator<PermissionGroupInfo>() {
+        public PermissionGroupInfo createFromParcel(Parcel source) {
+            return new PermissionGroupInfo(source);
+        }
+        public PermissionGroupInfo[] newArray(int size) {
+            return new PermissionGroupInfo[size];
+        }
+    };
+
+    private PermissionGroupInfo(Parcel source) {
+        super(source);
+        descriptionRes = source.readInt();
+        nonLocalizedDescription = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source);
+    }
+}
diff --git a/core/java/android/content/pm/PermissionInfo.aidl b/core/java/android/content/pm/PermissionInfo.aidl
new file mode 100755
index 0000000..5a7d4f4
--- /dev/null
+++ b/core/java/android/content/pm/PermissionInfo.aidl
@@ -0,0 +1,20 @@
+/* //device/java/android/android/view/WindowManager.aidl
+**
+** Copyright 2007, 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 android.content.pm;
+
+parcelable PermissionInfo;
diff --git a/core/java/android/content/pm/PermissionInfo.java b/core/java/android/content/pm/PermissionInfo.java
new file mode 100644
index 0000000..3cc884b
--- /dev/null
+++ b/core/java/android/content/pm/PermissionInfo.java
@@ -0,0 +1,156 @@
+/*
+ * 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 android.content.pm;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+
+/**
+ * Information you can retrieve about a particular security permission
+ * known to the system.  This corresponds to information collected from the
+ * AndroidManifest.xml's &lt;permission&gt; tags.
+ */
+public class PermissionInfo extends PackageItemInfo implements Parcelable {
+    /**
+     * A normal application value for {@link #protectionLevel}, corresponding
+     * to the <code>normal</code> value of
+     * {@link android.R.attr#protectionLevel}.
+     */
+    public static final int PROTECTION_NORMAL = 0;
+
+    /**
+     * Dangerous value for {@link #protectionLevel}, corresponding
+     * to the <code>dangerous</code> value of
+     * {@link android.R.attr#protectionLevel}.
+     */
+    public static final int PROTECTION_DANGEROUS = 1;
+
+    /**
+     * System-level value for {@link #protectionLevel}, corresponding
+     * to the <code>signature</code> value of
+     * {@link android.R.attr#protectionLevel}.
+     */
+    public static final int PROTECTION_SIGNATURE = 2;
+
+    /**
+     * System-level value for {@link #protectionLevel}, corresponding
+     * to the <code>signatureOrSystem</code> value of
+     * {@link android.R.attr#protectionLevel}.
+     */
+    public static final int PROTECTION_SIGNATURE_OR_SYSTEM = 3;
+
+    /**
+     * The group this permission is a part of, as per
+     * {@link android.R.attr#permissionGroup}.
+     */
+    public String group;
+    
+    /**
+     * A string resource identifier (in the package's resources) of this
+     * permission's description.  From the "description" attribute or,
+     * if not set, 0.
+     */
+    public int descriptionRes;
+
+    /**
+     * The description string provided in the AndroidManifest file, if any.  You
+     * probably don't want to use this, since it will be null if the description
+     * is in a resource.  You probably want
+     * {@link PermissionInfo#loadDescription} instead.
+     */
+    public CharSequence nonLocalizedDescription;
+
+    /**
+     * The level of access this permission is protecting, as per
+     * {@link android.R.attr#protectionLevel}.  Values may be
+     * {@link #PROTECTION_NORMAL}, {@link #PROTECTION_DANGEROUS}, or
+     * {@link #PROTECTION_SIGNATURE}.
+     */
+    public int protectionLevel;
+
+    public PermissionInfo() {
+    }
+
+    public PermissionInfo(PermissionInfo orig) {
+        super(orig);
+        group = orig.group;
+        descriptionRes = orig.descriptionRes;
+        protectionLevel = orig.protectionLevel;
+        nonLocalizedDescription = orig.nonLocalizedDescription;
+    }
+
+    /**
+     * Retrieve the textual description of this permission.  This
+     * will call back on the given PackageManager to load the description from
+     * the application.
+     *
+     * @param pm A PackageManager from which the label can be loaded; usually
+     * the PackageManager from which you originally retrieved this item.
+     *
+     * @return Returns a CharSequence containing the permission's description.
+     * If there is no description, null is returned.
+     */
+    public CharSequence loadDescription(PackageManager pm) {
+        if (nonLocalizedDescription != null) {
+            return nonLocalizedDescription;
+        }
+        if (descriptionRes != 0) {
+            CharSequence label = pm.getText(packageName, descriptionRes, null);
+            if (label != null) {
+                return label;
+            }
+        }
+        return null;
+    }
+
+    public String toString() {
+        return "PermissionInfo{"
+            + Integer.toHexString(System.identityHashCode(this))
+            + " " + name + "}";
+    }
+
+    public int describeContents() {
+        return 0;
+    }
+
+    public void writeToParcel(Parcel dest, int parcelableFlags) {
+        super.writeToParcel(dest, parcelableFlags);
+        dest.writeString(group);
+        dest.writeInt(descriptionRes);
+        dest.writeInt(protectionLevel);
+        TextUtils.writeToParcel(nonLocalizedDescription, dest, parcelableFlags);
+    }
+
+    public static final Creator<PermissionInfo> CREATOR =
+        new Creator<PermissionInfo>() {
+        public PermissionInfo createFromParcel(Parcel source) {
+            return new PermissionInfo(source);
+        }
+        public PermissionInfo[] newArray(int size) {
+            return new PermissionInfo[size];
+        }
+    };
+
+    private PermissionInfo(Parcel source) {
+        super(source);
+        group = source.readString();
+        descriptionRes = source.readInt();
+        protectionLevel = source.readInt();
+        nonLocalizedDescription = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source);
+    }
+}
diff --git a/core/java/android/content/pm/ProviderInfo.aidl b/core/java/android/content/pm/ProviderInfo.aidl
new file mode 100755
index 0000000..18fbc8a
--- /dev/null
+++ b/core/java/android/content/pm/ProviderInfo.aidl
@@ -0,0 +1,20 @@
+/* //device/java/android/android/view/WindowManager.aidl
+**
+** Copyright 2007, 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 android.content.pm;
+
+parcelable ProviderInfo;
diff --git a/core/java/android/content/pm/ProviderInfo.java b/core/java/android/content/pm/ProviderInfo.java
new file mode 100644
index 0000000..b67ddf6
--- /dev/null
+++ b/core/java/android/content/pm/ProviderInfo.java
@@ -0,0 +1,129 @@
+/*
+ * 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 android.content.pm;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.PatternMatcher;
+
+/**
+ * Holds information about a specific
+ * {@link android.content.ContentProvider content provider}. This is returned by
+ * {@link android.content.pm.PackageManager#resolveContentProvider(java.lang.String, int)
+ * PackageManager.resolveContentProvider()}.
+ */
+public final class ProviderInfo extends ComponentInfo
+        implements Parcelable {
+    /** The name provider is published under content:// */
+    public String authority = null;
+    
+    /** Optional permission required for read-only access this content
+     * provider. */
+    public String readPermission = null;
+    
+    /** Optional permission required for read/write access this content
+     * provider. */
+    public String writePermission = null;
+    
+    /** If true, additional permissions to specific Uris in this content
+     * provider can be granted, as per the
+     * {@link android.R.styleable#AndroidManifestProvider_grantUriPermissions
+     * grantUriPermissions} attribute.
+     */
+    public boolean grantUriPermissions = false;
+    
+    /**
+     * If non-null, these are the patterns that are allowed for granting URI
+     * permissions.  Any URI that does not match one of these patterns will not
+     * allowed to be granted.  If null, all URIs are allowed.  The
+     * {@link PackageManager#GET_URI_PERMISSION_PATTERNS
+     * PackageManager.GET_URI_PERMISSION_PATTERNS} flag must be specified for
+     * this field to be filled in.
+     */
+    public PatternMatcher[] uriPermissionPatterns = null;
+    
+    /** If true, this content provider allows multiple instances of itself
+     *  to run in different process.  If false, a single instances is always
+     *  run in {@link #processName}. */
+    public boolean multiprocess = false;
+    
+    /** Used to control initialization order of single-process providers
+     *  running in the same process.  Higher goes first. */
+    public int initOrder = 0;
+    
+    /** Whether or not this provider is syncable. */
+    public boolean isSyncable = false;
+
+    public ProviderInfo() {
+    }
+
+    public ProviderInfo(ProviderInfo orig) {
+        super(orig);
+        authority = orig.authority;
+        readPermission = orig.readPermission;
+        writePermission = orig.writePermission;
+        grantUriPermissions = orig.grantUriPermissions;
+        uriPermissionPatterns = orig.uriPermissionPatterns;
+        multiprocess = orig.multiprocess;
+        initOrder = orig.initOrder;
+        isSyncable = orig.isSyncable;
+    }
+    
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override public void writeToParcel(Parcel out, int parcelableFlags) {
+        super.writeToParcel(out, parcelableFlags);
+        out.writeString(authority);
+        out.writeString(readPermission);
+        out.writeString(writePermission);
+        out.writeInt(grantUriPermissions ? 1 : 0);
+        out.writeTypedArray(uriPermissionPatterns, parcelableFlags);
+        out.writeInt(multiprocess ? 1 : 0);
+        out.writeInt(initOrder);
+        out.writeInt(isSyncable ? 1 : 0);
+    }
+
+    public static final Parcelable.Creator<ProviderInfo> CREATOR
+            = new Parcelable.Creator<ProviderInfo>() {
+        public ProviderInfo createFromParcel(Parcel in) {
+            return new ProviderInfo(in);
+        }
+
+        public ProviderInfo[] newArray(int size) {
+            return new ProviderInfo[size];
+        }
+    };
+
+    public String toString() {
+        return "ContentProviderInfo{name=" + authority + " className=" + name
+            + " isSyncable=" + (isSyncable ? "true" : "false") + "}";
+    }
+
+    private ProviderInfo(Parcel in) {
+        super(in);
+        authority = in.readString();
+        readPermission = in.readString();
+        writePermission = in.readString();
+        grantUriPermissions = in.readInt() != 0;
+        uriPermissionPatterns = in.createTypedArray(PatternMatcher.CREATOR);
+        multiprocess = in.readInt() != 0;
+        initOrder = in.readInt();
+        isSyncable = in.readInt() != 0;
+    }
+}
diff --git a/core/java/android/content/pm/ResolveInfo.aidl b/core/java/android/content/pm/ResolveInfo.aidl
new file mode 100755
index 0000000..b4e7f8b
--- /dev/null
+++ b/core/java/android/content/pm/ResolveInfo.aidl
@@ -0,0 +1,20 @@
+/* //device/java/android/android/view/WindowManager.aidl
+**
+** Copyright 2007, 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 android.content.pm;
+
+parcelable ResolveInfo;
diff --git a/core/java/android/content/pm/ResolveInfo.java b/core/java/android/content/pm/ResolveInfo.java
new file mode 100644
index 0000000..ee49c02
--- /dev/null
+++ b/core/java/android/content/pm/ResolveInfo.java
@@ -0,0 +1,280 @@
+package android.content.pm;
+
+import android.content.IntentFilter;
+import android.graphics.drawable.Drawable;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+import android.util.Printer;
+
+import java.text.Collator;
+import java.util.Comparator;
+
+/**
+ * Information that is returned from resolving an intent
+ * against an IntentFilter. This partially corresponds to
+ * information collected from the AndroidManifest.xml's
+ * &lt;intent&gt; tags.
+ */
+public class ResolveInfo implements Parcelable {
+    /**
+     * The activity that corresponds to this resolution match, if this
+     * resolution is for an activity.  One and only one of this and
+     * serviceInfo must be non-null.
+     */
+    public ActivityInfo activityInfo;
+    
+    /**
+     * The service that corresponds to this resolution match, if this
+     * resolution is for a service. One and only one of this and
+     * activityInfo must be non-null.
+     */
+    public ServiceInfo serviceInfo;
+    
+    /**
+     * The IntentFilter that was matched for this ResolveInfo.
+     */
+    public IntentFilter filter;
+    
+    /**
+     * The declared priority of this match.  Comes from the "priority"
+     * attribute or, if not set, defaults to 0.  Higher values are a higher
+     * priority.
+     */
+    public int priority;
+    
+    /**
+     * Order of result according to the user's preference.  If the user
+     * has not set a preference for this result, the value is 0; higher
+     * values are a higher priority.
+     */
+    public int preferredOrder;
+    
+    /**
+     * The system's evaluation of how well the activity matches the
+     * IntentFilter.  This is a match constant, a combination of
+     * {@link IntentFilter#MATCH_CATEGORY_MASK IntentFilter.MATCH_CATEGORY_MASK}
+     * and {@link IntentFilter#MATCH_ADJUSTMENT_MASK IntentFiler.MATCH_ADJUSTMENT_MASK}.
+     */
+    public int match;
+    
+    /**
+     * Only set when returned by
+     * {@link PackageManager#queryIntentActivityOptions}, this tells you
+     * which of the given specific intents this result came from.  0 is the
+     * first in the list, < 0 means it came from the generic Intent query.
+     */
+    public int specificIndex = -1;
+    
+    /**
+     * This filter has specified the Intent.CATEGORY_DEFAULT, meaning it
+     * would like to be considered a default action that the user can
+     * perform on this data.
+     */
+    public boolean isDefault;
+    
+    /**
+     * A string resource identifier (in the package's resources) of this
+     * match's label.  From the "label" attribute or, if not set, 0.
+     */
+    public int labelRes;
+    
+    /**
+     * The actual string retrieve from <var>labelRes</var> or null if none
+     * was provided.
+     */
+    public CharSequence nonLocalizedLabel;
+    
+    /**
+     * A drawable resource identifier (in the package's resources) of this
+     * match's icon.  From the "icon" attribute or, if not set, 0.
+     */
+    public int icon;
+
+    /**
+     * Retrieve the current textual label associated with this resolution.  This
+     * will call back on the given PackageManager to load the label from
+     * the application.
+     * 
+     * @param pm A PackageManager from which the label can be loaded; usually
+     * the PackageManager from which you originally retrieved this item.
+     * 
+     * @return Returns a CharSequence containing the resolutions's label.  If the
+     * item does not have a label, its name is returned.
+     */
+    public CharSequence loadLabel(PackageManager pm) {
+        if (nonLocalizedLabel != null) {
+            return nonLocalizedLabel;
+        }
+        ComponentInfo ci = activityInfo != null ? activityInfo : serviceInfo;
+        ApplicationInfo ai = ci.applicationInfo;
+        CharSequence label;
+        if (labelRes != 0) {
+            label = pm.getText(ci.packageName, labelRes, ai);
+            if (label != null) {
+                return label;
+            }
+        }
+        return ci.loadLabel(pm);
+    }
+    
+    /**
+     * Retrieve the current graphical icon associated with this resolution.  This
+     * will call back on the given PackageManager to load the icon from
+     * the application.
+     * 
+     * @param pm A PackageManager from which the icon can be loaded; usually
+     * the PackageManager from which you originally retrieved this item.
+     * 
+     * @return Returns a Drawable containing the resolution's icon.  If the
+     * item does not have an icon, the default activity icon is returned.
+     */
+    public Drawable loadIcon(PackageManager pm) {
+        ComponentInfo ci = activityInfo != null ? activityInfo : serviceInfo;
+        ApplicationInfo ai = ci.applicationInfo;
+        Drawable dr;
+        if (icon != 0) {
+            dr = pm.getDrawable(ci.packageName, icon, ai);
+            if (dr != null) {
+                return dr;
+            }
+        }
+        return ci.loadIcon(pm);
+    }
+    
+    /**
+     * Return the icon resource identifier to use for this match.  If the
+     * match defines an icon, that is used; else if the activity defines
+     * an icon, that is used; else, the application icon is used.
+     * 
+     * @return The icon associated with this match.
+     */
+    public final int getIconResource() {
+        if (icon != 0) return icon;
+        if (activityInfo != null) return activityInfo.getIconResource();
+        if (serviceInfo != null) return serviceInfo.getIconResource();
+        return 0;
+    }
+
+    public void dump(Printer pw, String prefix) {
+        if (filter != null) {
+            pw.println(prefix + "Filter:");
+            filter.dump(pw, prefix + "  ");
+        } else {
+            pw.println(prefix + "Filter: null");
+        }
+        pw.println(prefix + "priority=" + priority
+                + " preferredOrder=" + preferredOrder
+                + " match=0x" + Integer.toHexString(match)
+                + " specificIndex=" + specificIndex
+                + " isDefault=" + isDefault);
+        pw.println(prefix + "labelRes=0x" + Integer.toHexString(labelRes)
+                + " nonLocalizedLabel=" + nonLocalizedLabel
+                + " icon=0x" + Integer.toHexString(icon));
+        if (activityInfo != null) {
+            pw.println(prefix + "ActivityInfo:");
+            activityInfo.dump(pw, prefix + "  ");
+        } else if (serviceInfo != null) {
+            pw.println(prefix + "ServiceInfo:");
+            // TODO
+            //serviceInfo.dump(pw, prefix + "  ");
+        }
+    }
+    
+    public ResolveInfo() {
+    }
+
+    public String toString() {
+        ComponentInfo ci = activityInfo != null ? activityInfo : serviceInfo;
+        return "ResolveInfo{"
+            + Integer.toHexString(System.identityHashCode(this))
+            + " " + ci.name + " p=" + priority + " o="
+            + preferredOrder + " m=0x" + Integer.toHexString(match) + "}";
+    }
+
+    public int describeContents() {
+        return 0;
+    }
+
+    public void writeToParcel(Parcel dest, int parcelableFlags) {
+        if (activityInfo != null) {
+            dest.writeInt(1);
+            activityInfo.writeToParcel(dest, parcelableFlags);
+        } else if (serviceInfo != null) {
+            dest.writeInt(2);
+            serviceInfo.writeToParcel(dest, parcelableFlags);
+        } else {
+            dest.writeInt(0);
+        }
+        if (filter != null) {
+            dest.writeInt(1);
+            filter.writeToParcel(dest, parcelableFlags);
+        } else {
+            dest.writeInt(0);
+        }
+        dest.writeInt(priority);
+        dest.writeInt(preferredOrder);
+        dest.writeInt(match);
+        dest.writeInt(specificIndex);
+        dest.writeInt(labelRes);
+        TextUtils.writeToParcel(nonLocalizedLabel, dest, parcelableFlags);
+        dest.writeInt(icon);
+    }
+
+    public static final Creator<ResolveInfo> CREATOR
+            = new Creator<ResolveInfo>() {
+        public ResolveInfo createFromParcel(Parcel source) {
+            return new ResolveInfo(source);
+        }
+        public ResolveInfo[] newArray(int size) {
+            return new ResolveInfo[size];
+        }
+    };
+
+    private ResolveInfo(Parcel source) {
+        switch (source.readInt()) {
+            case 1:
+                activityInfo = ActivityInfo.CREATOR.createFromParcel(source);
+                serviceInfo = null;
+                break;
+            case 2:
+                serviceInfo = ServiceInfo.CREATOR.createFromParcel(source);
+                activityInfo = null;
+                break;
+            default:
+                activityInfo = null;
+                serviceInfo = null;
+                break;
+        }
+        if (source.readInt() != 0) {
+            filter = IntentFilter.CREATOR.createFromParcel(source);
+        }
+        priority = source.readInt();
+        preferredOrder = source.readInt();
+        match = source.readInt();
+        specificIndex = source.readInt();
+        labelRes = source.readInt();
+        nonLocalizedLabel
+                = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source);
+        icon = source.readInt();
+    }
+    
+    public static class DisplayNameComparator
+            implements Comparator<ResolveInfo> {
+        public DisplayNameComparator(PackageManager pm) {
+            mPM = pm;
+        }
+
+        public final int compare(ResolveInfo a, ResolveInfo b) {
+            CharSequence  sa = a.loadLabel(mPM);
+            if (sa == null) sa = a.activityInfo.name;
+            CharSequence  sb = b.loadLabel(mPM);
+            if (sb == null) sb = b.activityInfo.name;
+            
+            return sCollator.compare(sa.toString(), sb.toString());
+        }
+
+        private final Collator   sCollator = Collator.getInstance();
+        private PackageManager   mPM;
+    }
+}
diff --git a/core/java/android/content/pm/ServiceInfo.aidl b/core/java/android/content/pm/ServiceInfo.aidl
new file mode 100755
index 0000000..5ddae1a
--- /dev/null
+++ b/core/java/android/content/pm/ServiceInfo.aidl
@@ -0,0 +1,20 @@
+/* //device/java/android/android/view/WindowManager.aidl
+**
+** Copyright 2007, 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 android.content.pm;
+
+parcelable ServiceInfo;
diff --git a/core/java/android/content/pm/ServiceInfo.java b/core/java/android/content/pm/ServiceInfo.java
new file mode 100644
index 0000000..b60650c
--- /dev/null
+++ b/core/java/android/content/pm/ServiceInfo.java
@@ -0,0 +1,56 @@
+package android.content.pm;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Information you can retrieve about a particular application
+ * service. This corresponds to information collected from the
+ * AndroidManifest.xml's &lt;service&gt; tags.
+ */
+public class ServiceInfo extends ComponentInfo
+        implements Parcelable {
+    /**
+     * Optional name of a permission required to be able to access this
+     * Service.  From the "permission" attribute.
+     */
+    public String permission;
+
+    public ServiceInfo() {
+    }
+
+    public ServiceInfo(ServiceInfo orig) {
+        super(orig);
+        permission = orig.permission;
+    }
+
+    public String toString() {
+        return "ServiceInfo{"
+            + Integer.toHexString(System.identityHashCode(this))
+            + " " + name + "}";
+    }
+
+    public int describeContents() {
+        return 0;
+    }
+
+    public void writeToParcel(Parcel dest, int parcelableFlags) {
+        super.writeToParcel(dest, parcelableFlags);
+        dest.writeString(permission);
+    }
+
+    public static final Creator<ServiceInfo> CREATOR =
+        new Creator<ServiceInfo>() {
+        public ServiceInfo createFromParcel(Parcel source) {
+            return new ServiceInfo(source);
+        }
+        public ServiceInfo[] newArray(int size) {
+            return new ServiceInfo[size];
+        }
+    };
+
+    private ServiceInfo(Parcel source) {
+        super(source);
+        permission = source.readString();
+    }
+}
diff --git a/core/java/android/content/pm/Signature.aidl b/core/java/android/content/pm/Signature.aidl
new file mode 100755
index 0000000..3a0d775
--- /dev/null
+++ b/core/java/android/content/pm/Signature.aidl
@@ -0,0 +1,20 @@
+/* //device/java/android/android/view/WindowManager.aidl
+**
+** Copyright 2007, 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 android.content.pm;
+
+parcelable Signature;
diff --git a/core/java/android/content/pm/Signature.java b/core/java/android/content/pm/Signature.java
new file mode 100644
index 0000000..1bb3857
--- /dev/null
+++ b/core/java/android/content/pm/Signature.java
@@ -0,0 +1,158 @@
+/*
+ * 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 android.content.pm;
+
+import android.content.ComponentName;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Arrays;
+
+/**
+ * Opaque, immutable representation of a signature associated with an
+ * application package.
+ */
+public class Signature implements Parcelable {
+    private final byte[] mSignature;
+    private int mHashCode;
+    private boolean mHaveHashCode;
+    private String mString;
+
+    /**
+     * Create Signature from an existing raw byte array.
+     */
+    public Signature(byte[] signature) {
+        mSignature = signature.clone();
+    }
+
+    /**
+     * Create Signature from a text representation previously returned by
+     * {@link #toChars} or {@link #toCharsString()}.
+     */
+    public Signature(String text) {
+        final int N = text.length()/2;
+        byte[] sig = new byte[N];
+        for (int i=0; i<N; i++) {
+            char c = text.charAt(i*2);
+            byte b = (byte)(
+                    (c >= 'a' ? (c - 'a' + 10) : (c - '0'))<<4);
+            c = text.charAt(i*2 + 1);
+            b |= (byte)(c >= 'a' ? (c - 'a' + 10) : (c - '0'));
+            sig[i] = b;
+        }
+        mSignature = sig;
+    }
+
+    /**
+     * Encode the Signature as ASCII text.
+     */
+    public char[] toChars() {
+        return toChars(null, null);
+    }
+
+    /**
+     * Encode the Signature as ASCII text in to an existing array.
+     *
+     * @param existingArray Existing char array or null.
+     * @param outLen Output parameter for the number of characters written in
+     * to the array.
+     * @return Returns either <var>existingArray</var> if it was large enough
+     * to hold the ASCII representation, or a newly created char[] array if
+     * needed.
+     */
+    public char[] toChars(char[] existingArray, int[] outLen) {
+        byte[] sig = mSignature;
+        final int N = sig.length;
+        final int N2 = N*2;
+        char[] text = existingArray == null || N2 > existingArray.length
+                ? new char[N2] : existingArray;
+        for (int j=0; j<N; j++) {
+            byte v = sig[j];
+            int d = (v>>4)&0xf;
+            text[j*2] = (char)(d >= 10 ? ('a' + d - 10) : ('0' + d));
+            d = v&0xf;
+            text[j*2+1] = (char)(d >= 10 ? ('a' + d - 10) : ('0' + d));
+        }
+        if (outLen != null) outLen[0] = N;
+        return text;
+    }
+
+    /**
+     * Return the result of {@link #toChars()} as a String.  This result is
+     * cached so future calls will return the same String.
+     */
+    public String toCharsString() {
+        if (mString != null) return mString;
+        String str = new String(toChars());
+        mString = str;
+        return mString;
+    }
+
+    /**
+     * @return the contents of this signature as a byte array.
+     */
+    public byte[] toByteArray() {
+        byte[] bytes = new byte[mSignature.length];
+        System.arraycopy(mSignature, 0, bytes, 0, mSignature.length);
+        return bytes;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        try {
+            if (obj != null) {
+                Signature other = (Signature)obj;
+                return Arrays.equals(mSignature, other.mSignature);
+            }
+        } catch (ClassCastException e) {
+        }
+        return false;
+    }
+
+    @Override
+    public int hashCode() {
+        if (mHaveHashCode) {
+            return mHashCode;
+        }
+        mHashCode = Arrays.hashCode(mSignature);
+        mHaveHashCode = true;
+        return mHashCode;
+    }
+
+    public int describeContents() {
+        return 0;
+    }
+
+    public void writeToParcel(Parcel dest, int parcelableFlags) {
+        dest.writeByteArray(mSignature);
+    }
+
+    public static final Parcelable.Creator<Signature> CREATOR
+            = new Parcelable.Creator<Signature>() {
+        public Signature createFromParcel(Parcel source) {
+            return new Signature(source);
+        }
+
+        public Signature[] newArray(int size) {
+            return new Signature[size];
+        }
+    };
+
+    private Signature(Parcel source) {
+        mSignature = source.createByteArray();
+    }
+}
diff --git a/core/java/android/content/pm/package.html b/core/java/android/content/pm/package.html
new file mode 100644
index 0000000..766b7dd
--- /dev/null
+++ b/core/java/android/content/pm/package.html
@@ -0,0 +1,7 @@
+<HTML>
+<BODY>
+Contains classes for accessing information about an 
+application package, including information about its activities, 
+permissions, services, signatures, and providers.
+</BODY>
+</HTML>
\ No newline at end of file
diff --git a/core/java/android/content/res/AssetFileDescriptor.java b/core/java/android/content/res/AssetFileDescriptor.java
new file mode 100644
index 0000000..231e3e2
--- /dev/null
+++ b/core/java/android/content/res/AssetFileDescriptor.java
@@ -0,0 +1,348 @@
+/*
+ * 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 android.content.res;
+
+import android.os.Parcel;
+import android.os.ParcelFileDescriptor;
+import android.os.Parcelable;
+
+import java.io.FileDescriptor;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+
+/**
+ * File descriptor of an entry in the AssetManager.  This provides your own
+ * opened FileDescriptor that can be used to read the data, as well as the
+ * offset and length of that entry's data in the file.
+ */
+public class AssetFileDescriptor implements Parcelable {
+    /**
+     * Length used with {@link #AssetFileDescriptor(ParcelFileDescriptor, long, long)}
+     * and {@link #getDeclaredLength} when a length has not been declared.  This means
+     * the data extends to the end of the file.
+     */
+    public static final long UNKNOWN_LENGTH = -1;
+    
+    private final ParcelFileDescriptor mFd;
+    private final long mStartOffset;
+    private final long mLength;
+    
+    /**
+     * Create a new AssetFileDescriptor from the given values.
+     * @param fd The underlying file descriptor.
+     * @param startOffset The location within the file that the asset starts.
+     * This must be 0 if length is UNKNOWN_LENGTH.
+     * @param length The number of bytes of the asset, or
+     * {@link #UNKNOWN_LENGTH if it extends to the end of the file.
+     */
+    public AssetFileDescriptor(ParcelFileDescriptor fd, long startOffset,
+            long length) {
+        if (length < 0 && startOffset != 0) {
+            throw new IllegalArgumentException(
+                    "startOffset must be 0 when using UNKNOWN_LENGTH");
+        }
+        mFd = fd;
+        mStartOffset = startOffset;
+        mLength = length;
+    }
+    
+    /**
+     * The AssetFileDescriptor contains its own ParcelFileDescriptor, which
+     * in addition to the normal FileDescriptor object also allows you to close
+     * the descriptor when you are done with it.
+     */
+    public ParcelFileDescriptor getParcelFileDescriptor() {
+        return mFd;
+    }
+    
+    /**
+     * Returns the FileDescriptor that can be used to read the data in the
+     * file.
+     */
+    public FileDescriptor getFileDescriptor() {
+        return mFd.getFileDescriptor();
+    }
+    
+    /**
+     * Returns the byte offset where this asset entry's data starts.
+     */
+    public long getStartOffset() {
+        return mStartOffset;
+    }
+    
+    /**
+     * Returns the total number of bytes of this asset entry's data.  May be
+     * {@link #UNKNOWN_LENGTH} if the asset extends to the end of the file.
+     * If the AssetFileDescriptor was constructed with {@link #UNKNOWN_LENGTH},
+     * this will use {@link ParcelFileDescriptor#getStatSize()
+     * ParcelFileDescriptor.getStatSize()} to find the total size of the file,
+     * returning that number if found or {@link #UNKNOWN_LENGTH} if it could
+     * not be determined.
+     * 
+     * @see #getDeclaredLength()
+     */
+    public long getLength() {
+        if (mLength >= 0) {
+            return mLength;
+        }
+        long len = mFd.getStatSize();
+        return len >= 0 ? len : UNKNOWN_LENGTH;
+    }
+    
+    /**
+     * Return the actual number of bytes that were declared when the
+     * AssetFileDescriptor was constructed.  Will be
+     * {@link #UNKNOWN_LENGTH} if the length was not declared, meaning data
+     * should be read to the end of the file.
+     * 
+     * @see #getDeclaredLength()
+     */
+    public long getDeclaredLength() {
+        return mLength;
+    }
+    
+    /**
+     * Convenience for calling <code>getParcelFileDescriptor().close()</code>.
+     */
+    public void close() throws IOException {
+        mFd.close();
+    }
+    
+    /**
+     * Create and return a new auto-close input stream for this asset.  This
+     * will either return a full asset {@link AutoCloseInputStream}, or
+     * an underlying {@link ParcelFileDescriptor.AutoCloseInputStream
+     * ParcelFileDescriptor.AutoCloseInputStream} depending on whether the
+     * the object represents a complete file or sub-section of a file.  You
+     * should only call this once for a particular asset.
+     */
+    public FileInputStream createInputStream() throws IOException {
+        if (mLength < 0) {
+            return new ParcelFileDescriptor.AutoCloseInputStream(mFd);
+        }
+        return new AutoCloseInputStream(this);
+    }
+    
+    /**
+     * Create and return a new auto-close output stream for this asset.  This
+     * will either return a full asset {@link AutoCloseOutputStream}, or
+     * an underlying {@link ParcelFileDescriptor.AutoCloseOutputStream
+     * ParcelFileDescriptor.AutoCloseOutputStream} depending on whether the
+     * the object represents a complete file or sub-section of a file.  You
+     * should only call this once for a particular asset.
+     */
+    public FileOutputStream createOutputStream() throws IOException {
+        if (mLength < 0) {
+            return new ParcelFileDescriptor.AutoCloseOutputStream(mFd);
+        }
+        return new AutoCloseOutputStream(this);
+    }
+    
+    @Override
+    public String toString() {
+        return "{AssetFileDescriptor: " + mFd
+                + " start=" + mStartOffset + " len=" + mLength + "}";
+    }
+    
+    /**
+     * An InputStream you can create on a ParcelFileDescriptor, which will
+     * take care of calling {@link ParcelFileDescriptor#close
+     * ParcelFileDescritor.close()} for you when the stream is closed.
+     */
+    public static class AutoCloseInputStream
+            extends ParcelFileDescriptor.AutoCloseInputStream {
+        private long mRemaining;
+        
+        public AutoCloseInputStream(AssetFileDescriptor fd) throws IOException {
+            super(fd.getParcelFileDescriptor());
+            super.skip(fd.getStartOffset());
+            mRemaining = (int)fd.getLength();
+        }
+
+        @Override
+        public int available() throws IOException {
+            return mRemaining >= 0
+                    ? (mRemaining < 0x7fffffff ? (int)mRemaining : 0x7fffffff)
+                    : super.available();
+        }
+
+        @Override
+        public int read() throws IOException {
+            if (mRemaining >= 0) {
+                if (mRemaining == 0) return -1;
+                int res = super.read();
+                if (res >= 0) mRemaining--;
+                return res;
+            }
+            
+            return super.read();
+        }
+
+        @Override
+        public int read(byte[] buffer, int offset, int count) throws IOException {
+            if (mRemaining >= 0) {
+                if (mRemaining == 0) return -1;
+                if (count > mRemaining) count = (int)mRemaining;
+                int res = super.read(buffer, offset, count);
+                if (res >= 0) mRemaining -= res;
+                return res;
+            }
+            
+            return super.read(buffer, offset, count);
+        }
+
+        @Override
+        public int read(byte[] buffer) throws IOException {
+            if (mRemaining >= 0) {
+                if (mRemaining == 0) return -1;
+                int count = buffer.length;
+                if (count > mRemaining) count = (int)mRemaining;
+                int res = super.read(buffer, 0, count);
+                if (res >= 0) mRemaining -= res;
+                return res;
+            }
+            
+            return super.read(buffer);
+        }
+
+        @Override
+        public long skip(long count) throws IOException {
+            if (mRemaining >= 0) {
+                if (mRemaining == 0) return -1;
+                if (count > mRemaining) count = mRemaining;
+                long res = super.skip(count);
+                if (res >= 0) mRemaining -= res;
+                return res;
+            }
+            
+            // TODO Auto-generated method stub
+            return super.skip(count);
+        }
+
+        @Override
+        public void mark(int readlimit) {
+            if (mRemaining >= 0) {
+                // Not supported.
+                return;
+            }
+            super.mark(readlimit);
+        }
+
+        @Override
+        public boolean markSupported() {
+            if (mRemaining >= 0) {
+                return false;
+            }
+            return super.markSupported();
+        }
+
+        @Override
+        public synchronized void reset() throws IOException {
+            if (mRemaining >= 0) {
+                // Not supported.
+                return;
+            }
+            super.reset();
+        }
+    }
+    
+    /**
+     * An OutputStream you can create on a ParcelFileDescriptor, which will
+     * take care of calling {@link ParcelFileDescriptor#close
+     * ParcelFileDescritor.close()} for you when the stream is closed.
+     */
+    public static class AutoCloseOutputStream
+            extends ParcelFileDescriptor.AutoCloseOutputStream {
+        private long mRemaining;
+        
+        public AutoCloseOutputStream(AssetFileDescriptor fd) throws IOException {
+            super(fd.getParcelFileDescriptor());
+            if (fd.getParcelFileDescriptor().seekTo(fd.getStartOffset()) < 0) {
+                throw new IOException("Unable to seek");
+            }
+            mRemaining = (int)fd.getLength();
+        }
+
+        @Override
+        public void write(byte[] buffer, int offset, int count) throws IOException {
+            if (mRemaining >= 0) {
+                if (mRemaining == 0) return;
+                if (count > mRemaining) count = (int)mRemaining;
+                super.write(buffer, offset, count);
+                mRemaining -= count;
+                return;
+            }
+            
+            super.write(buffer, offset, count);
+        }
+
+        @Override
+        public void write(byte[] buffer) throws IOException {
+            if (mRemaining >= 0) {
+                if (mRemaining == 0) return;
+                int count = buffer.length;
+                if (count > mRemaining) count = (int)mRemaining;
+                super.write(buffer);
+                mRemaining -= count;
+                return;
+            }
+            
+            super.write(buffer);
+        }
+
+        @Override
+        public void write(int oneByte) throws IOException {
+            if (mRemaining >= 0) {
+                if (mRemaining == 0) return;
+                super.write(oneByte);
+                mRemaining--;
+                return;
+            }
+            
+            super.write(oneByte);
+        }
+    }
+    
+    
+    /* Parcelable interface */
+    public int describeContents() {
+        return mFd.describeContents();
+    }
+
+    public void writeToParcel(Parcel out, int flags) {
+        mFd.writeToParcel(out, flags);
+        out.writeLong(mStartOffset);
+        out.writeLong(mLength);
+    }
+
+    AssetFileDescriptor(Parcel src) {
+        mFd = ParcelFileDescriptor.CREATOR.createFromParcel(src);
+        mStartOffset = src.readLong();
+        mLength = src.readLong();
+    }
+    
+    public static final Parcelable.Creator<AssetFileDescriptor> CREATOR
+            = new Parcelable.Creator<AssetFileDescriptor>() {
+        public AssetFileDescriptor createFromParcel(Parcel in) {
+            return new AssetFileDescriptor(in);
+        }
+        public AssetFileDescriptor[] newArray(int size) {
+            return new AssetFileDescriptor[size];
+        }
+    };
+}
diff --git a/core/java/android/content/res/AssetManager.java b/core/java/android/content/res/AssetManager.java
new file mode 100644
index 0000000..fadcb35
--- /dev/null
+++ b/core/java/android/content/res/AssetManager.java
@@ -0,0 +1,697 @@
+/*
+ * 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 android.content.res;
+
+import android.os.ParcelFileDescriptor;
+import android.util.Config;
+import android.util.Log;
+import android.util.TypedValue;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Locale;
+
+/**
+ * Provides access to an application's raw asset files; see {@link Resources}
+ * for the way most applications will want to retrieve their resource data.
+ * This class presents a lower-level API that allows you to open and read raw
+ * files that have been bundled with the application as a simple stream of
+ * bytes.
+ */
+public final class AssetManager {
+    /* modes used when opening an asset */
+
+    /**
+     * Mode for {@link #open(String, int)}: no specific information about how
+     * data will be accessed.
+     */
+    public static final int ACCESS_UNKNOWN = 0;
+    /**
+     * Mode for {@link #open(String, int)}: Read chunks, and seek forward and
+     * backward.
+     */
+    public static final int ACCESS_RANDOM = 1;
+    /**
+     * Mode for {@link #open(String, int)}: Read sequentially, with an
+     * occasional forward seek.
+     */
+    public static final int ACCESS_STREAMING = 2;
+    /**
+     * Mode for {@link #open(String, int)}: Attempt to load contents into
+     * memory, for fast small reads.
+     */
+    public static final int ACCESS_BUFFER = 3;
+
+    private static final String TAG = "AssetManager";
+    private static final boolean localLOGV = Config.LOGV || false;
+    
+    private static final Object mSync = new Object();
+    private static final TypedValue mValue = new TypedValue();
+    private static final long[] mOffsets = new long[2];
+    private static AssetManager mSystem = null;
+
+    // For communication with native code.
+    private int mObject;
+
+    private StringBlock mStringBlocks[] = null;
+    
+    private int mNumRefs = 1;
+    private boolean mOpen = true;
+    private String mAssetDir;
+    private String mAppName;
+
+    /**
+     * Create a new AssetManager containing only the basic system assets.
+     * Applications will not generally use this method, instead retrieving the
+     * appropriate asset manager with {@link Resources#getAssets}.    Not for
+     * use by applications.
+     * {@hide}
+     */
+    public AssetManager() {
+        synchronized (mSync) {
+            init();
+            if (localLOGV) Log.v(TAG, "New asset manager: " + this);
+            ensureSystemAssets();
+        }
+    }
+
+    private static void ensureSystemAssets() {
+        synchronized (mSync) {
+            if (mSystem == null) {
+                AssetManager system = new AssetManager(true);
+                system.makeStringBlocks(false);
+                mSystem = system;
+            }
+        }
+    }
+    
+    private AssetManager(boolean isSystem) {
+        init();
+        if (localLOGV) Log.v(TAG, "New asset manager: " + this);
+    }
+
+    /**
+     * Return a global shared asset manager that provides access to only
+     * system assets (no application assets).
+     * {@hide}
+     */
+    public static AssetManager getSystem() {
+        ensureSystemAssets();
+        return mSystem;
+    }
+
+    /**
+     * Close this asset manager.
+     */
+    public void close() {
+        synchronized(mSync) {
+            //System.out.println("Release: num=" + mNumRefs
+            //                   + ", released=" + mReleased);
+            if (mOpen) {
+                mOpen = false;
+                decRefsLocked();
+            }
+        }
+    }
+
+    /**
+     * Retrieve the string value associated with a particular resource
+     * identifier for the current configuration / skin.
+     */
+    /*package*/ final CharSequence getResourceText(int ident) {
+        synchronized (mSync) {
+            TypedValue tmpValue = mValue;
+            int block = loadResourceValue(ident, tmpValue, true);
+            if (block >= 0) {
+                if (tmpValue.type == TypedValue.TYPE_STRING) {
+                    return mStringBlocks[block].get(tmpValue.data);
+                }
+                return tmpValue.coerceToString();
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Retrieve the string value associated with a particular resource
+     * identifier for the current configuration / skin.
+     */
+    /*package*/ final CharSequence getResourceBagText(int ident, int bagEntryId) {
+        synchronized (mSync) {
+            TypedValue tmpValue = mValue;
+            int block = loadResourceBagValue(ident, bagEntryId, tmpValue, true);
+            if (block >= 0) {
+                if (tmpValue.type == TypedValue.TYPE_STRING) {
+                    return mStringBlocks[block].get(tmpValue.data);
+                }
+                return tmpValue.coerceToString();
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Retrieve the string array associated with a particular resource
+     * identifier.
+     * @param id Resource id of the string array
+     */
+    /*package*/ final String[] getResourceStringArray(final int id) {
+        String[] retArray = getArrayStringResource(id);
+        return retArray;
+    }
+
+
+    /*package*/ final boolean getResourceValue(int ident,
+                                               TypedValue outValue,
+                                               boolean resolveRefs)
+    {
+        int block = loadResourceValue(ident, outValue, resolveRefs);
+        if (block >= 0) {
+            if (outValue.type != TypedValue.TYPE_STRING) {
+                return true;
+            }
+            outValue.string = mStringBlocks[block].get(outValue.data);
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Retrieve the text array associated with a particular resource
+     * identifier.
+     * @param id Resource id of the string array
+     */
+    /*package*/ final CharSequence[] getResourceTextArray(final int id) {
+        int[] rawInfoArray = getArrayStringInfo(id);
+        int rawInfoArrayLen = rawInfoArray.length;
+        final int infoArrayLen = rawInfoArrayLen / 2;
+        int block;
+        int index;
+        CharSequence[] retArray = new CharSequence[infoArrayLen];
+        for (int i = 0, j = 0; i < rawInfoArrayLen; i = i + 2, j++) {
+            block = rawInfoArray[i];
+            index = rawInfoArray[i + 1];
+            retArray[j] = index >= 0 ? mStringBlocks[block].get(index) : null;
+        }
+        return retArray;
+    }
+    
+    /*package*/ final boolean getThemeValue(int theme, int ident,
+            TypedValue outValue, boolean resolveRefs) {
+        int block = loadThemeAttributeValue(theme, ident, outValue, resolveRefs);
+        if (block >= 0) {
+            if (outValue.type != TypedValue.TYPE_STRING) {
+                return true;
+            }
+            StringBlock[] blocks = mStringBlocks;
+            if (blocks == null) {
+                ensureStringBlocks();
+            }
+            outValue.string = blocks[block].get(outValue.data);
+            return true;
+        }
+        return false;
+    }
+
+    /*package*/ final void ensureStringBlocks() {
+        if (mStringBlocks == null) {
+            synchronized (mSync) {
+                if (mStringBlocks == null) {
+                    makeStringBlocks(true);
+                }
+            }
+        }
+    }
+
+    private final void makeStringBlocks(boolean copyFromSystem) {
+        final int sysNum = copyFromSystem ? mSystem.mStringBlocks.length : 0;
+        final int num = getStringBlockCount();
+        mStringBlocks = new StringBlock[num];
+        if (localLOGV) Log.v(TAG, "Making string blocks for " + this
+                + ": " + num);
+        for (int i=0; i<num; i++) {
+            if (i < sysNum) {
+                mStringBlocks[i] = mSystem.mStringBlocks[i];
+            } else {
+                mStringBlocks[i] = new StringBlock(getNativeStringBlock(i), true);
+            }
+        }
+    }
+
+    /*package*/ final CharSequence getPooledString(int block, int id) {
+        //System.out.println("Get pooled: block=" + block
+        //                   + ", id=#" + Integer.toHexString(id)
+        //                   + ", blocks=" + mStringBlocks);
+        return mStringBlocks[block-1].get(id);
+    }
+
+    /**
+     * Open an asset using ACCESS_STREAMING mode.  This provides access to
+     * files that have been bundled with an application as assets -- that is,
+     * files placed in to the "assets" directory.
+     * 
+     * @param fileName The name of the asset to open.  This name can be
+     *                 hierarchical.
+     * 
+     * @see #open(String, int)
+     * @see #list
+     */
+    public final InputStream open(String fileName) throws IOException {
+        return open(fileName, ACCESS_STREAMING);
+    }
+
+    /**
+     * Open an asset using an explicit access mode, returning an InputStream to
+     * read its contents.  This provides access to files that have been bundled
+     * with an application as assets -- that is, files placed in to the
+     * "assets" directory.
+     * 
+     * @param fileName The name of the asset to open.  This name can be
+     *                 hierarchical.
+     * @param accessMode Desired access mode for retrieving the data.
+     * 
+     * @see #ACCESS_UNKNOWN
+     * @see #ACCESS_STREAMING
+     * @see #ACCESS_RANDOM
+     * @see #ACCESS_BUFFER
+     * @see #open(String)
+     * @see #list
+     */
+    public final InputStream open(String fileName, int accessMode)
+        throws IOException {
+        synchronized (mSync) {
+            if (!mOpen) {
+                throw new RuntimeException("Assetmanager has been closed");
+            }
+            int asset = openAsset(fileName, accessMode);
+            if (asset != 0) {
+                mNumRefs++;
+                return new AssetInputStream(asset);
+            }
+        }
+        throw new FileNotFoundException("Asset file: " + fileName);
+    }
+
+    public final AssetFileDescriptor openFd(String fileName)
+            throws IOException {
+        synchronized (mSync) {
+            if (!mOpen) {
+                throw new RuntimeException("Assetmanager has been closed");
+            }
+            ParcelFileDescriptor pfd = openAssetFd(fileName, mOffsets);
+            if (pfd != null) {
+                return new AssetFileDescriptor(pfd, mOffsets[0], mOffsets[1]);
+            }
+        }
+        throw new FileNotFoundException("Asset file: " + fileName);
+    }
+
+    /**
+     * Return a String array of all the assets at the given path.
+     * 
+     * @param path A relative path within the assets, i.e., "docs/home.html".
+     * 
+     * @return String[] Array of strings, one for each asset.  These file
+     *         names are relative to 'path'.  You can open the file by
+     *         concatenating 'path' and a name in the returned string (via
+     *         File) and passing that to open().
+     * 
+     * @see #open
+     */
+    public native final String[] list(String path)
+        throws IOException;
+
+    /**
+     * {@hide}
+     * Open a non-asset file as an asset using ACCESS_STREAMING mode.  This
+     * provides direct access to all of the files included in an application
+     * package (not only its assets).  Applications should not normally use
+     * this.
+     * 
+     * @see #open(String)
+     */
+    public final InputStream openNonAsset(String fileName) throws IOException {
+        return openNonAsset(0, fileName, ACCESS_STREAMING);
+    }
+
+    /**
+     * {@hide}
+     * Open a non-asset file as an asset using a specific access mode.  This
+     * provides direct access to all of the files included in an application
+     * package (not only its assets).  Applications should not normally use
+     * this.
+     * 
+     * @see #open(String, int)
+     */
+    public final InputStream openNonAsset(String fileName, int accessMode)
+        throws IOException {
+        return openNonAsset(0, fileName, accessMode);
+    }
+
+    /**
+     * {@hide}
+     * Open a non-asset in a specified package.  Not for use by applications.
+     * 
+     * @param cookie Identifier of the package to be opened.
+     * @param fileName Name of the asset to retrieve.
+     */
+    public final InputStream openNonAsset(int cookie, String fileName)
+        throws IOException {
+        return openNonAsset(cookie, fileName, ACCESS_STREAMING);
+    }
+
+    /**
+     * {@hide}
+     * Open a non-asset in a specified package.  Not for use by applications.
+     * 
+     * @param cookie Identifier of the package to be opened.
+     * @param fileName Name of the asset to retrieve.
+     * @param accessMode Desired access mode for retrieving the data.
+     */
+    public final InputStream openNonAsset(int cookie, String fileName, int accessMode)
+        throws IOException {
+        synchronized (mSync) {
+            if (!mOpen) {
+                throw new RuntimeException("Assetmanager has been closed");
+            }
+            int asset = openNonAssetNative(cookie, fileName, accessMode);
+            if (asset != 0) {
+                mNumRefs++;
+                return new AssetInputStream(asset);
+            }
+        }
+        throw new FileNotFoundException("Asset absolute file: " + fileName);
+    }
+
+    public final AssetFileDescriptor openNonAssetFd(String fileName)
+            throws IOException {
+        return openNonAssetFd(0, fileName);
+    }
+    
+    public final AssetFileDescriptor openNonAssetFd(int cookie,
+            String fileName) throws IOException {
+        synchronized (mSync) {
+            if (!mOpen) {
+                throw new RuntimeException("Assetmanager has been closed");
+            }
+            ParcelFileDescriptor pfd = openNonAssetFdNative(cookie,
+                    fileName, mOffsets);
+            if (pfd != null) {
+                return new AssetFileDescriptor(pfd, mOffsets[0], mOffsets[1]);
+            }
+        }
+        throw new FileNotFoundException("Asset absolute file: " + fileName);
+    }
+    
+    /**
+     * Retrieve a parser for a compiled XML file.
+     * 
+     * @param fileName The name of the file to retrieve.
+     */
+    public final XmlResourceParser openXmlResourceParser(String fileName)
+            throws IOException {
+        return openXmlResourceParser(0, fileName);
+    }
+    
+    /**
+     * Retrieve a parser for a compiled XML file.
+     * 
+     * @param cookie Identifier of the package to be opened.
+     * @param fileName The name of the file to retrieve.
+     */
+    public final XmlResourceParser openXmlResourceParser(int cookie,
+            String fileName) throws IOException {
+        XmlBlock block = openXmlBlockAsset(cookie, fileName);
+        XmlResourceParser rp = block.newParser();
+        block.close();
+        return rp;
+    }
+
+    /**
+     * {@hide}
+     * Retrieve a non-asset as a compiled XML file.  Not for use by
+     * applications.
+     * 
+     * @param fileName The name of the file to retrieve.
+     */
+    /*package*/ final XmlBlock openXmlBlockAsset(String fileName)
+            throws IOException {
+        return openXmlBlockAsset(0, fileName);
+    }
+
+    /**
+     * {@hide}
+     * Retrieve a non-asset as a compiled XML file.  Not for use by
+     * applications.
+     * 
+     * @param cookie Identifier of the package to be opened.
+     * @param fileName Name of the asset to retrieve.
+     */
+    /*package*/ final XmlBlock openXmlBlockAsset(int cookie, String fileName)
+        throws IOException {
+        synchronized (mSync) {
+            if (!mOpen) {
+                throw new RuntimeException("Assetmanager has been closed");
+            }
+            int xmlBlock = openXmlAssetNative(cookie, fileName);
+            if (xmlBlock != 0) {
+                mNumRefs++;
+                return new XmlBlock(this, xmlBlock);
+            }
+        }
+        throw new FileNotFoundException("Asset XML file: " + fileName);
+    }
+
+    /*package*/ void xmlBlockGone() {
+        synchronized (mSync) {
+            decRefsLocked();
+        }
+    }
+
+    /*package*/ final int createTheme() {
+        synchronized (mSync) {
+            if (!mOpen) {
+                throw new RuntimeException("Assetmanager has been closed");
+            }
+            mNumRefs++;
+            return newTheme();
+        }
+    }
+
+    /*package*/ final void releaseTheme(int theme) {
+        synchronized (mSync) {
+            deleteTheme(theme);
+            decRefsLocked();
+        }
+    }
+
+    protected void finalize() throws Throwable {
+        destroy();
+    }
+    
+    public final class AssetInputStream extends InputStream {
+        public final int getAssetInt() {
+            return mAsset;
+        }
+        private AssetInputStream(int asset)
+        {
+            mAsset = asset;
+            mLength = getAssetLength(asset);
+        }
+        public final int read() throws IOException {
+            return readAssetChar(mAsset);
+        }
+        public final boolean markSupported() {
+            return true;
+        }
+        public final int available() throws IOException {
+            long len = getAssetRemainingLength(mAsset);
+            return len > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int)len;
+        }
+        public final void close() throws IOException {
+            synchronized (AssetManager.mSync) {
+                if (mAsset != 0) {
+                    destroyAsset(mAsset);
+                    mAsset = 0;
+                    decRefsLocked();
+                }
+            }
+        }
+        public final void mark(int readlimit) {
+            mMarkPos = seekAsset(mAsset, 0, 0);
+        }
+        public final void reset() throws IOException {
+            seekAsset(mAsset, mMarkPos, -1);
+        }
+        public final int read(byte[] b) throws IOException {
+            return readAsset(mAsset, b, 0, b.length);
+        }
+        public final int read(byte[] b, int off, int len) throws IOException {
+            return readAsset(mAsset, b, off, len);
+        }
+        public final long skip(long n) throws IOException {
+            long pos = seekAsset(mAsset, 0, 0);
+            if ((pos+n) > mLength) {
+                n = mLength-pos;
+            }
+            if (n > 0) {
+                seekAsset(mAsset, n, 0);
+            }
+            return n;
+        }
+
+        protected void finalize() throws Throwable
+        {
+            close();
+        }
+
+        private int mAsset;
+        private long mLength;
+        private long mMarkPos;
+    }
+
+    /**
+     * Add an additional set of assets to the asset manager.  This can be
+     * either a directory or ZIP file.  Not for use by applications.  A
+     * zero return value indicates failure.
+     * {@hide}
+     */
+    public native final int addAssetPath(String path);
+
+    /**
+     * Determine whether the state in this asset manager is up-to-date with
+     * the files on the filesystem.  If false is returned, you need to
+     * instantiate a new AssetManager class to see the new data.
+     * {@hide}
+     */
+    public native final boolean isUpToDate();
+
+    /**
+     * Change the locale being used by this asset manager.  Not for use by
+     * applications.
+     * {@hide}
+     */
+    public native final void setLocale(String locale);
+
+    /**
+     * Get the locales that this asset manager contains data for.
+     */
+    public native final String[] getLocales();
+
+    /**
+     * Change the configuation used when retrieving resources.  Not for use by
+     * applications.
+     * {@hide}
+     */
+    public native final void setConfiguration(int mcc, int mnc, String locale,
+            int orientation, int touchscreen, int density, int keyboard,
+            int keyboardHidden, int navigation, int screenWidth, int screenHeight,
+            int majorVersion);
+
+    /**
+     * Retrieve the resource identifier for the given resource name.
+     */
+    /*package*/ native final int getResourceIdentifier(String type,
+                                                       String name,
+                                                       String defPackage);
+
+    /*package*/ native final String getResourceName(int resid);
+    /*package*/ native final String getResourcePackageName(int resid);
+    /*package*/ native final String getResourceTypeName(int resid);
+    /*package*/ native final String getResourceEntryName(int resid);
+    
+    private native final int openAsset(String fileName, int accessMode);
+    private final native ParcelFileDescriptor openAssetFd(String fileName,
+            long[] outOffsets) throws IOException;
+    private native final int openNonAssetNative(int cookie, String fileName,
+            int accessMode);
+    private native ParcelFileDescriptor openNonAssetFdNative(int cookie,
+            String fileName, long[] outOffsets) throws IOException;
+    private native final void destroyAsset(int asset);
+    private native final int readAssetChar(int asset);
+    private native final int readAsset(int asset, byte[] b, int off, int len);
+    private native final long seekAsset(int asset, long offset, int whence);
+    private native final long getAssetLength(int asset);
+    private native final long getAssetRemainingLength(int asset);
+
+    /** Returns true if the resource was found, filling in mRetStringBlock and
+     *  mRetData. */
+    private native final int loadResourceValue(int ident, TypedValue outValue,
+                                               boolean resolve);
+    /** Returns true if the resource was found, filling in mRetStringBlock and
+     *  mRetData. */
+    private native final int loadResourceBagValue(int ident, int bagEntryId, TypedValue outValue,
+                                               boolean resolve);
+    /*package*/ static final int STYLE_NUM_ENTRIES = 5;
+    /*package*/ static final int STYLE_TYPE = 0;
+    /*package*/ static final int STYLE_DATA = 1;
+    /*package*/ static final int STYLE_ASSET_COOKIE = 2;
+    /*package*/ static final int STYLE_RESOURCE_ID = 3;
+    /*package*/ static final int STYLE_CHANGING_CONFIGURATIONS = 4;
+    /*package*/ native static final boolean applyStyle(int theme,
+            int defStyleAttr, int defStyleRes, int xmlParser,
+            int[] inAttrs, int[] outValues, int[] outIndices);
+    /*package*/ native final boolean retrieveAttributes(
+            int xmlParser, int[] inAttrs, int[] outValues, int[] outIndices);
+    /*package*/ native final int getArraySize(int resource);
+    /*package*/ native final int retrieveArray(int resource, int[] outValues);
+    private native final int getStringBlockCount();
+    private native final int getNativeStringBlock(int block);
+
+    /**
+     * {@hide}
+     */
+    public native final String getCookieName(int cookie);
+
+    /**
+     * {@hide}
+     */
+    public native static final int getGlobalAssetCount();
+    
+    /**
+     * {@hide}
+     */
+    public native static final int getGlobalAssetManagerCount();
+    
+    private native final int newTheme();
+    private native final void deleteTheme(int theme);
+    /*package*/ native static final void applyThemeStyle(int theme, int styleRes, boolean force);
+    /*package*/ native static final void copyTheme(int dest, int source);
+    /*package*/ native static final int loadThemeAttributeValue(int theme, int ident,
+                                                                TypedValue outValue,
+                                                                boolean resolve);
+    /*package*/ native static final void dumpTheme(int theme, int priority, String tag, String prefix);
+
+    private native final int openXmlAssetNative(int cookie, String fileName);
+
+    private native final String[] getArrayStringResource(int arrayRes);
+    private native final int[] getArrayStringInfo(int arrayRes);
+    /*package*/ native final int[] getArrayIntResource(int arrayRes);
+
+    private native final void init();
+    private native final void destroy();
+
+    private final void decRefsLocked() {
+        mNumRefs--;
+        //System.out.println("Dec streams: mNumRefs=" + mNumRefs
+        //                   + " mReleased=" + mReleased);
+        if (mNumRefs == 0) {
+            destroy();
+        }
+    }
+}
diff --git a/core/java/android/content/res/ColorStateList.java b/core/java/android/content/res/ColorStateList.java
new file mode 100644
index 0000000..0f3f270
--- /dev/null
+++ b/core/java/android/content/res/ColorStateList.java
@@ -0,0 +1,327 @@
+/*
+ * Copyright (C) 2007 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 android.content.res;
+
+import com.android.internal.util.ArrayUtils;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import android.util.AttributeSet;
+import android.util.SparseArray;
+import android.util.StateSet;
+import android.util.Xml;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.io.IOException;
+import java.lang.ref.WeakReference;
+import java.util.Arrays;
+
+/**
+ *
+ * Lets you map {@link android.view.View} state sets to colors.
+ *
+ * {@link android.content.res.ColorStateList}s are created from XML resource files defined in the
+ * "color" subdirectory directory of an application's resource directory.  The XML file contains
+ * a single "selector" element with a number of "item" elements inside.  For example:
+ *
+ * <pre>
+ * &lt;selector xmlns:android="http://schemas.android.com/apk/res/android"&gt;
+ *   &lt;item android:state_focused="true" android:color="@color/testcolor1"/&gt;
+ *   &lt;item android:state_pressed="true" android:state_enabled="false" android:color="@color/testcolor2" /&gt;
+ *   &lt;item android:state_enabled="false" android:colore="@color/testcolor3" /&gt;
+ *   &lt;item android:state_active="true" android:color="@color/testcolor4" /&gt;
+ *   &lt;item android:color="@color/testcolor5"/&gt;
+ * &lt;/selector&gt;
+ * </pre>
+ *
+ * This defines a set of state spec / color pairs where each state spec specifies a set of
+ * states that a view must either be in or not be in and the color specifies the color associated
+ * with that spec.  The list of state specs will be processed in order of the items in the XML file.
+ * An item with no state spec is considered to match any set of states and is generally useful as
+ * a final item to be used as a default.  Note that if you have such an item before any other items
+ * in the list then any subsequent items will end up being ignored.
+ */
+public class ColorStateList implements Parcelable {
+
+    private int[][] mStateSpecs; // must be parallel to mColors
+    private int[] mColors;      // must be parallel to mStateSpecs
+    private int mDefaultColor = 0xffff0000;
+
+    private static final int[][] EMPTY = new int[][] { new int[0] };
+    private static final SparseArray<WeakReference<ColorStateList>> sCache =
+                            new SparseArray<WeakReference<ColorStateList>>();
+
+    private ColorStateList() { }
+
+    /**
+     * Creates a ColorStateList that returns the specified mapping from
+     * states to colors.
+     */
+    public ColorStateList(int[][] states, int[] colors) {
+        mStateSpecs = states;
+        mColors = colors;
+
+        if (states.length > 0) {
+            mDefaultColor = colors[0];
+
+            for (int i = 0; i < states.length; i++) {
+                if (states[i].length == 0) {
+                    mDefaultColor = colors[i];
+                }
+            }
+        }
+    }
+
+    /**
+     * Creates or retrieves a ColorStateList that always returns a single color.
+     */
+    public static ColorStateList valueOf(int color) {
+        // TODO: should we collect these eventually?
+        synchronized (sCache) {
+            WeakReference<ColorStateList> ref = sCache.get(color);
+            ColorStateList csl = ref != null ? ref.get() : null;
+
+            if (csl != null) {
+                return csl;
+            }
+
+            csl = new ColorStateList(EMPTY, new int[] { color });
+            sCache.put(color, new WeakReference<ColorStateList>(csl));
+            return csl;
+        }
+    }
+
+    /**
+     * Create a ColorStateList from an XML document, given a set of {@link Resources}.
+     */
+    public static ColorStateList createFromXml(Resources r, XmlPullParser parser)
+            throws XmlPullParserException, IOException {
+
+        AttributeSet attrs = Xml.asAttributeSet(parser);
+
+        int type;
+        while ((type=parser.next()) != XmlPullParser.START_TAG
+                   && type != XmlPullParser.END_DOCUMENT) {
+        }
+
+        if (type != XmlPullParser.START_TAG) {
+            throw new XmlPullParserException("No start tag found");
+        }
+
+        return createFromXmlInner(r, parser, attrs);
+    }
+
+    /* Create from inside an XML document.  Called on a parser positioned at
+     * a tag in an XML document, tries to create a ColorStateList from that tag.
+     * Returns null if the tag is not a valid ColorStateList.
+     */
+    private static ColorStateList createFromXmlInner(Resources r, XmlPullParser parser,
+            AttributeSet attrs) throws XmlPullParserException, IOException {
+
+        ColorStateList colorStateList;
+
+        final String name = parser.getName();
+
+        if (name.equals("selector")) {
+            colorStateList = new ColorStateList();
+        } else {
+            throw new XmlPullParserException(
+                parser.getPositionDescription() + ": invalid drawable tag " + name);
+        }
+
+        colorStateList.inflate(r, parser, attrs);
+        return colorStateList;
+    }
+
+    /**
+     * Creates a new ColorStateList that has the same states and
+     * colors as this one but where each color has the specified alpha value
+     * (0-255).
+     */
+    public ColorStateList withAlpha(int alpha) {
+        int[] colors = new int[mColors.length];
+
+        int len = colors.length;
+        for (int i = 0; i < len; i++) {
+            colors[i] = (mColors[i] & 0xFFFFFF) | (alpha << 24);
+        }
+
+        return new ColorStateList(mStateSpecs, colors);
+    }
+
+    /**
+     * Fill in this object based on the contents of an XML "selector" element.
+     */
+    private void inflate(Resources r, XmlPullParser parser, AttributeSet attrs)
+        throws XmlPullParserException, IOException {
+
+        int type;
+
+        final int innerDepth = parser.getDepth()+1;
+        int depth;
+
+        int listAllocated = 20;
+        int listSize = 0;
+        int[] colorList = new int[listAllocated];
+        int[][] stateSpecList = new int[listAllocated][];
+
+        while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
+               && ((depth=parser.getDepth()) >= innerDepth
+                   || type != XmlPullParser.END_TAG)) {
+            if (type != XmlPullParser.START_TAG) {
+                continue;
+            }
+
+            if (depth > innerDepth || !parser.getName().equals("item")) {
+                continue;
+            }
+
+            int colorRes = 0;
+            int color = 0xffff0000;
+            boolean haveColor = false;
+
+            int i;
+            int j = 0;
+            final int numAttrs = attrs.getAttributeCount();
+            int[] stateSpec = new int[numAttrs];
+            for (i = 0; i < numAttrs; i++) {
+                final int stateResId = attrs.getAttributeNameResource(i);
+                if (stateResId == 0) break;
+                if (stateResId == com.android.internal.R.attr.color) {
+                    colorRes = attrs.getAttributeResourceValue(i, 0);
+
+                    if (colorRes == 0) {
+                        color = attrs.getAttributeIntValue(i, color);
+                        haveColor = true;
+                    }
+                } else {
+                    stateSpec[j++] = attrs.getAttributeBooleanValue(i, false)
+                                  ? stateResId
+                                  : -stateResId;
+                }
+            }
+            stateSpec = StateSet.trimStateSet(stateSpec, j);
+
+            if (colorRes != 0) {
+                color = r.getColor(colorRes);
+            } else if (!haveColor) {
+                throw new XmlPullParserException(
+                        parser.getPositionDescription()
+                        + ": <item> tag requires a 'android:color' attribute.");
+            }
+
+            if (listSize == 0 || stateSpec.length == 0) {
+                mDefaultColor = color;
+            }
+            
+            if (listSize + 1 >= listAllocated) {
+                listAllocated = ArrayUtils.idealIntArraySize(listSize + 1);
+
+                int[] ncolor = new int[listAllocated];
+                System.arraycopy(colorList, 0, ncolor, 0, listSize);
+
+                int[][] nstate = new int[listAllocated][];
+                System.arraycopy(stateSpecList, 0, nstate, 0, listSize);
+
+                colorList = ncolor;
+                stateSpecList = nstate;
+            }
+
+            colorList[listSize] = color;
+            stateSpecList[listSize] = stateSpec;
+            listSize++;
+        }
+
+        mColors = new int[listSize];
+        mStateSpecs = new int[listSize][];
+        System.arraycopy(colorList, 0, mColors, 0, listSize);
+        System.arraycopy(stateSpecList, 0, mStateSpecs, 0, listSize);
+    }
+
+    public boolean isStateful() {
+        return mStateSpecs.length > 1;
+    }
+    
+    /**
+     * Return the color associated with the given set of {@link android.view.View} states.
+     *
+     * @param stateSet an array of {@link android.view.View} states
+     * @param defaultColor the color to return if there's not state spec in this
+     * {@link ColorStateList} that matches the stateSet.
+     *
+     * @return the color associated with that set of states in this {@link ColorStateList}.
+     */
+    public int getColorForState(int[] stateSet, int defaultColor) {
+        final int setLength = mStateSpecs.length;
+        for (int i = 0; i < setLength; i++) {
+            int[] stateSpec = mStateSpecs[i];
+            if (StateSet.stateSetMatches(stateSpec, stateSet)) {
+                return mColors[i];
+            }
+        }
+        return defaultColor;
+    }
+
+    /**
+     * Return the default color in this {@link ColorStateList}.
+     *
+     * @return the default color in this {@link ColorStateList}.
+     */
+    public int getDefaultColor() {
+        return mDefaultColor;
+    }
+
+    public String toString() {
+        return "ColorStateList{" +
+               "mStateSpecs=" + Arrays.deepToString(mStateSpecs) +
+               "mColors=" + Arrays.toString(mColors) +
+               "mDefaultColor=" + mDefaultColor + '}';
+    }
+
+    public int describeContents() {
+        return 0;
+    }
+
+    public void writeToParcel(Parcel dest, int flags) {
+        final int N = mStateSpecs.length;
+        dest.writeInt(N);
+        for (int i=0; i<N; i++) {
+            dest.writeIntArray(mStateSpecs[i]);
+        }
+        dest.writeArray(mStateSpecs);
+        dest.writeIntArray(mColors);
+    }
+
+    public static final Parcelable.Creator<ColorStateList> CREATOR =
+            new Parcelable.Creator<ColorStateList>() {
+        public ColorStateList[] newArray(int size) {
+            return new ColorStateList[size];
+        }
+
+        public ColorStateList createFromParcel(Parcel source) {
+            final int N = source.readInt();
+            int[][] stateSpecs = new int[N][];
+            for (int i=0; i<N; i++) {
+                stateSpecs[i] = source.createIntArray();
+            }
+            int[] colors = source.createIntArray();
+            return new ColorStateList(stateSpecs, colors);
+        }
+    };
+}
diff --git a/core/java/android/content/res/Configuration.aidl b/core/java/android/content/res/Configuration.aidl
new file mode 100755
index 0000000..bb7f2dd
--- /dev/null
+++ b/core/java/android/content/res/Configuration.aidl
@@ -0,0 +1,21 @@
+/* //device/java/android/android/view/WindowManager.aidl
+**
+** Copyright 2007, 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 android.content.res;
+
+parcelable Configuration;
+
diff --git a/core/java/android/content/res/Configuration.java b/core/java/android/content/res/Configuration.java
new file mode 100644
index 0000000..7e4b7ac
--- /dev/null
+++ b/core/java/android/content/res/Configuration.java
@@ -0,0 +1,436 @@
+package android.content.res;
+
+import android.content.pm.ActivityInfo;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Locale;
+
+/**
+ * This class describes all device configuration information that can
+ * impact the resources the application retrieves.  This includes both
+ * user-specified configuration options (locale and scaling) as well
+ * as dynamic device configuration (various types of input devices).
+ */
+public final class Configuration implements Parcelable, Comparable<Configuration> {
+    /**
+     * Current user preference for the scaling factor for fonts, relative
+     * to the base density scaling.
+     */
+    public float fontScale;
+
+    /**
+     * IMSI MCC (Mobile Country Code).  0 if undefined.
+     */
+    public int mcc;
+    
+    /**
+     * IMSI MNC (Mobile Network Code).  0 if undefined.
+     */
+    public int mnc;
+    
+    /**
+     * Current user preference for the locale.
+     */
+    public Locale locale;
+
+    /**
+     * Locale should persist on setting
+     * @hide pending API council approval
+     */
+    public boolean userSetLocale;
+
+    public static final int TOUCHSCREEN_UNDEFINED = 0;
+    public static final int TOUCHSCREEN_NOTOUCH = 1;
+    public static final int TOUCHSCREEN_STYLUS = 2;
+    public static final int TOUCHSCREEN_FINGER = 3;
+    
+    /**
+     * The kind of touch screen attached to the device.
+     * One of: {@link #TOUCHSCREEN_NOTOUCH}, {@link #TOUCHSCREEN_STYLUS}, 
+     * {@link #TOUCHSCREEN_FINGER}. 
+     */
+    public int touchscreen;
+    
+    public static final int KEYBOARD_UNDEFINED = 0;
+    public static final int KEYBOARD_NOKEYS = 1;
+    public static final int KEYBOARD_QWERTY = 2;
+    public static final int KEYBOARD_12KEY = 3;
+    
+    /**
+     * The kind of keyboard attached to the device.
+     * One of: {@link #KEYBOARD_QWERTY}, {@link #KEYBOARD_12KEY}.
+     */
+    public int keyboard;
+    
+    public static final int KEYBOARDHIDDEN_UNDEFINED = 0;
+    public static final int KEYBOARDHIDDEN_NO = 1;
+    public static final int KEYBOARDHIDDEN_YES = 2;
+    /** Constant matching actual resource implementation. {@hide} */
+    public static final int KEYBOARDHIDDEN_SOFT = 3;
+    
+    /**
+     * A flag indicating whether any keyboard is available.  Unlike
+     * {@link #hardKeyboardHidden}, this also takes into account a soft
+     * keyboard, so if the hard keyboard is hidden but there is soft
+     * keyboard available, it will be set to NO.  Value is one of:
+     * {@link #KEYBOARDHIDDEN_NO}, {@link #KEYBOARDHIDDEN_YES}.
+     */
+    public int keyboardHidden;
+    
+    public static final int HARDKEYBOARDHIDDEN_UNDEFINED = 0;
+    public static final int HARDKEYBOARDHIDDEN_NO = 1;
+    public static final int HARDKEYBOARDHIDDEN_YES = 2;
+    
+    /**
+     * A flag indicating whether the hard keyboard has been hidden.  This will
+     * be set on a device with a mechanism to hide the keyboard from the
+     * user, when that mechanism is closed.  One of:
+     * {@link #HARDKEYBOARDHIDDEN_NO}, {@link #HARDKEYBOARDHIDDEN_YES}.
+     */
+    public int hardKeyboardHidden;
+    
+    public static final int NAVIGATION_UNDEFINED = 0;
+    public static final int NAVIGATION_NONAV = 1;
+    public static final int NAVIGATION_DPAD = 2;
+    public static final int NAVIGATION_TRACKBALL = 3;
+    public static final int NAVIGATION_WHEEL = 4;
+    
+    /**
+     * The kind of navigation method available on the device.
+     * One of: {@link #NAVIGATION_DPAD}, {@link #NAVIGATION_TRACKBALL}, 
+     * {@link #NAVIGATION_WHEEL}. 
+     */
+    public int navigation;
+    
+    public static final int ORIENTATION_UNDEFINED = 0;
+    public static final int ORIENTATION_PORTRAIT = 1;
+    public static final int ORIENTATION_LANDSCAPE = 2;
+    public static final int ORIENTATION_SQUARE = 3;
+    
+    /**
+     * Overall orientation of the screen.  May be one of
+     * {@link #ORIENTATION_LANDSCAPE}, {@link #ORIENTATION_PORTRAIT},
+     * or {@link #ORIENTATION_SQUARE}.
+     */
+    public int orientation;
+    
+    /**
+     * Construct an invalid Configuration.  You must call {@link #setToDefaults}
+     * for this object to be valid.  {@more}
+     */
+    public Configuration() {
+        setToDefaults();
+    }
+
+    /**
+     * Makes a deep copy suitable for modification.
+     */
+    public Configuration(Configuration o) {
+        fontScale = o.fontScale;
+        mcc = o.mcc;
+        mnc = o.mnc;
+        if (o.locale != null) {
+            locale = (Locale) o.locale.clone();
+        }
+        userSetLocale = o.userSetLocale;
+        touchscreen = o.touchscreen;
+        keyboard = o.keyboard;
+        keyboardHidden = o.keyboardHidden;
+        hardKeyboardHidden = o.hardKeyboardHidden;
+        navigation = o.navigation;
+        orientation = o.orientation;
+    }
+
+    public String toString() {
+        return "{ scale=" + fontScale + " imsi=" + mcc + "/" + mnc
+                + " locale=" + locale
+                + " touch=" + touchscreen + " key=" + keyboard + "/"
+                + keyboardHidden + "/" + hardKeyboardHidden
+                + " nav=" + navigation + " orien=" + orientation + " }";
+    }
+
+    /**
+     * Set this object to the system defaults.
+     */
+    public void setToDefaults() {
+        fontScale = 1;
+        mcc = mnc = 0;
+        locale = Locale.getDefault();
+        userSetLocale = false;
+        touchscreen = TOUCHSCREEN_UNDEFINED;
+        keyboard = KEYBOARD_UNDEFINED;
+        keyboardHidden = KEYBOARDHIDDEN_UNDEFINED;
+        hardKeyboardHidden = HARDKEYBOARDHIDDEN_UNDEFINED;
+        navigation = NAVIGATION_UNDEFINED;
+        orientation = ORIENTATION_UNDEFINED;
+    }
+
+    /** {@hide} */
+    @Deprecated public void makeDefault() {
+        setToDefaults();
+    }
+    
+    /**
+     * Copy the fields from delta into this Configuration object, keeping
+     * track of which ones have changed.  Any undefined fields in
+     * <var>delta</var> are ignored and not copied in to the current
+     * Configuration.
+     * @return Returns a bit mask of the changed fields, as per
+     * {@link #diff}.
+     */
+    public int updateFrom(Configuration delta) {
+        int changed = 0;
+        if (delta.fontScale > 0 && fontScale != delta.fontScale) {
+            changed |= ActivityInfo.CONFIG_FONT_SCALE;
+            fontScale = delta.fontScale;
+        }
+        if (delta.mcc != 0 && mcc != delta.mcc) {
+            changed |= ActivityInfo.CONFIG_MCC;
+            mcc = delta.mcc;
+        }
+        if (delta.mnc != 0 && mnc != delta.mnc) {
+            changed |= ActivityInfo.CONFIG_MNC;
+            mnc = delta.mnc;
+        }
+        if (delta.locale != null
+                && (locale == null || !locale.equals(delta.locale))) {
+            changed |= ActivityInfo.CONFIG_LOCALE;
+            locale = delta.locale != null
+                    ? (Locale) delta.locale.clone() : null;
+        }
+        if (delta.userSetLocale && (!userSetLocale || ((changed & ActivityInfo.CONFIG_LOCALE) != 0)))
+        {
+            userSetLocale = true;
+            changed |= ActivityInfo.CONFIG_LOCALE;
+        }
+        if (delta.touchscreen != TOUCHSCREEN_UNDEFINED
+                && touchscreen != delta.touchscreen) {
+            changed |= ActivityInfo.CONFIG_TOUCHSCREEN;
+            touchscreen = delta.touchscreen;
+        }
+        if (delta.keyboard != KEYBOARD_UNDEFINED
+                && keyboard != delta.keyboard) {
+            changed |= ActivityInfo.CONFIG_KEYBOARD;
+            keyboard = delta.keyboard;
+        }
+        if (delta.keyboardHidden != KEYBOARDHIDDEN_UNDEFINED
+                && keyboardHidden != delta.keyboardHidden) {
+            changed |= ActivityInfo.CONFIG_KEYBOARD_HIDDEN;
+            keyboardHidden = delta.keyboardHidden;
+        }
+        if (delta.hardKeyboardHidden != HARDKEYBOARDHIDDEN_UNDEFINED
+                && hardKeyboardHidden != delta.hardKeyboardHidden) {
+            changed |= ActivityInfo.CONFIG_KEYBOARD_HIDDEN;
+            hardKeyboardHidden = delta.hardKeyboardHidden;
+        }
+        if (delta.navigation != NAVIGATION_UNDEFINED
+                && navigation != delta.navigation) {
+            changed |= ActivityInfo.CONFIG_NAVIGATION;
+            navigation = delta.navigation;
+        }
+        if (delta.orientation != ORIENTATION_UNDEFINED
+                && orientation != delta.orientation) {
+            changed |= ActivityInfo.CONFIG_ORIENTATION;
+            orientation = delta.orientation;
+        }
+        
+        return changed;
+    }
+
+    /**
+     * Return a bit mask of the differences between this Configuration
+     * object and the given one.  Does not change the values of either.  Any
+     * undefined fields in <var>delta</var> are ignored.
+     * @return Returns a bit mask indicating which configuration
+     * values has changed, containing any combination of
+     * {@link android.content.pm.ActivityInfo#CONFIG_FONT_SCALE
+     * PackageManager.ActivityInfo.CONFIG_FONT_SCALE},
+     * {@link android.content.pm.ActivityInfo#CONFIG_MCC
+     * PackageManager.ActivityInfo.CONFIG_MCC},
+     * {@link android.content.pm.ActivityInfo#CONFIG_MNC
+     * PackageManager.ActivityInfo.CONFIG_MNC},
+     * {@link android.content.pm.ActivityInfo#CONFIG_LOCALE
+     * PackageManager.ActivityInfo.CONFIG_LOCALE},
+     * {@link android.content.pm.ActivityInfo#CONFIG_TOUCHSCREEN
+     * PackageManager.ActivityInfo.CONFIG_TOUCHSCREEN},
+     * {@link android.content.pm.ActivityInfo#CONFIG_KEYBOARD
+     * PackageManager.ActivityInfo.CONFIG_KEYBOARD},
+     * {@link android.content.pm.ActivityInfo#CONFIG_NAVIGATION
+     * PackageManager.ActivityInfo.CONFIG_NAVIGATION}, or
+     * {@link android.content.pm.ActivityInfo#CONFIG_ORIENTATION
+     * PackageManager.ActivityInfo.CONFIG_ORIENTATION}.
+     */
+    public int diff(Configuration delta) {
+        int changed = 0;
+        if (delta.fontScale > 0 && fontScale != delta.fontScale) {
+            changed |= ActivityInfo.CONFIG_FONT_SCALE;
+        }
+        if (delta.mcc != 0 && mcc != delta.mcc) {
+            changed |= ActivityInfo.CONFIG_MCC;
+        }
+        if (delta.mnc != 0 && mnc != delta.mnc) {
+            changed |= ActivityInfo.CONFIG_MNC;
+        }
+        if (delta.locale != null
+                && (locale == null || !locale.equals(delta.locale))) {
+            changed |= ActivityInfo.CONFIG_LOCALE;
+        }
+        if (delta.touchscreen != TOUCHSCREEN_UNDEFINED
+                && touchscreen != delta.touchscreen) {
+            changed |= ActivityInfo.CONFIG_TOUCHSCREEN;
+        }
+        if (delta.keyboard != KEYBOARD_UNDEFINED
+                && keyboard != delta.keyboard) {
+            changed |= ActivityInfo.CONFIG_KEYBOARD;
+        }
+        if (delta.keyboardHidden != KEYBOARDHIDDEN_UNDEFINED
+                && keyboardHidden != delta.keyboardHidden) {
+            changed |= ActivityInfo.CONFIG_KEYBOARD_HIDDEN;
+        }
+        if (delta.hardKeyboardHidden != HARDKEYBOARDHIDDEN_UNDEFINED
+                && hardKeyboardHidden != delta.hardKeyboardHidden) {
+            changed |= ActivityInfo.CONFIG_KEYBOARD_HIDDEN;
+        }
+        if (delta.navigation != NAVIGATION_UNDEFINED
+                && navigation != delta.navigation) {
+            changed |= ActivityInfo.CONFIG_NAVIGATION;
+        }
+        if (delta.orientation != ORIENTATION_UNDEFINED
+                && orientation != delta.orientation) {
+            changed |= ActivityInfo.CONFIG_ORIENTATION;
+        }
+        
+        return changed;
+    }
+
+    /**
+     * Determine if a new resource needs to be loaded from the bit set of
+     * configuration changes returned by {@link #updateFrom(Configuration)}.
+     * 
+     * @param configChanges The mask of changes configurations as returned by
+     * {@link #updateFrom(Configuration)}.
+     * @param interestingChanges The configuration changes that the resource
+     * can handled, as given in {@link android.util.TypedValue#changingConfigurations}.
+     * 
+     * @return Return true if the resource needs to be loaded, else false.
+     */
+    public static boolean needNewResources(int configChanges, int interestingChanges) {
+        return (configChanges & (interestingChanges|ActivityInfo.CONFIG_FONT_SCALE)) != 0;
+    }
+    
+    /**
+     * Parcelable methods
+     */
+    public int describeContents() {
+        return 0;
+    }
+
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeFloat(fontScale);
+        dest.writeInt(mcc);
+        dest.writeInt(mnc);
+        if (locale == null) {
+            dest.writeInt(0);
+        } else {
+            dest.writeInt(1);
+            dest.writeString(locale.getLanguage());
+            dest.writeString(locale.getCountry());
+            dest.writeString(locale.getVariant());
+        }
+        if(userSetLocale) {
+            dest.writeInt(1);
+        } else {
+            dest.writeInt(0);
+        }
+        dest.writeInt(touchscreen);
+        dest.writeInt(keyboard);
+        dest.writeInt(keyboardHidden);
+        dest.writeInt(hardKeyboardHidden);
+        dest.writeInt(navigation);
+        dest.writeInt(orientation);
+    }
+
+    public static final Parcelable.Creator<Configuration> CREATOR
+            = new Parcelable.Creator<Configuration>() {
+        public Configuration createFromParcel(Parcel source) {
+            return new Configuration(source);
+        }
+
+        public Configuration[] newArray(int size) {
+            return new Configuration[size];
+        }
+    };
+
+    /**
+     * Construct this Configuration object, reading from the Parcel.
+     */
+    private Configuration(Parcel source) {
+        fontScale = source.readFloat();
+        mcc = source.readInt();
+        mnc = source.readInt();
+        if (source.readInt() != 0) {
+            locale = new Locale(source.readString(), source.readString(),
+                    source.readString());
+        }
+        userSetLocale = (source.readInt()==1);
+        touchscreen = source.readInt();
+        keyboard = source.readInt();
+        keyboardHidden = source.readInt();
+        hardKeyboardHidden = source.readInt();
+        navigation = source.readInt();
+        orientation = source.readInt();
+    }
+
+    public int compareTo(Configuration that) {
+        int n;
+        float a = this.fontScale;
+        float b = that.fontScale;
+        if (a < b) return -1;
+        if (a > b) return 1;
+        n = this.mcc - that.mcc;
+        if (n != 0) return n;
+        n = this.mnc - that.mnc;
+        if (n != 0) return n;
+        n = this.locale.getLanguage().compareTo(that.locale.getLanguage());
+        if (n != 0) return n;
+        n = this.locale.getCountry().compareTo(that.locale.getCountry());
+        if (n != 0) return n;
+        n = this.locale.getVariant().compareTo(that.locale.getVariant());
+        if (n != 0) return n;
+        n = this.touchscreen - that.touchscreen;
+        if (n != 0) return n;
+        n = this.keyboard - that.keyboard;
+        if (n != 0) return n;
+        n = this.keyboardHidden - that.keyboardHidden;
+        if (n != 0) return n;
+        n = this.hardKeyboardHidden - that.hardKeyboardHidden;
+        if (n != 0) return n;
+        n = this.navigation - that.navigation;
+        if (n != 0) return n;
+        n = this.orientation - that.orientation;
+        //if (n != 0) return n;
+        return n;
+    }
+
+    public boolean equals(Configuration that) {
+        if (that == null) return false;
+        if (that == this) return true;
+        return this.compareTo(that) == 0;
+    }
+
+    public boolean equals(Object that) {
+        try {
+            return equals((Configuration)that);
+        } catch (ClassCastException e) {
+        }
+        return false;
+    }
+    
+    public int hashCode() {
+        return ((int)this.fontScale) + this.mcc + this.mnc
+                + this.locale.hashCode() + this.touchscreen
+                + this.keyboard + this.keyboardHidden + this.hardKeyboardHidden
+                + this.navigation + this.orientation;
+    }
+}
diff --git a/core/java/android/content/res/PluralRules.java b/core/java/android/content/res/PluralRules.java
new file mode 100644
index 0000000..2dce3c1
--- /dev/null
+++ b/core/java/android/content/res/PluralRules.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2007 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 android.content.res;
+
+import java.util.Locale;
+
+/*
+ * Yuck-o.  This is not the right way to implement this.  When the ICU PluralRules
+ * object has been integrated to android, we should switch to that.  For now, yuck-o.
+ */
+
+abstract class PluralRules {
+
+    static final int QUANTITY_OTHER = 0x0000;
+    static final int QUANTITY_ZERO  = 0x0001;
+    static final int QUANTITY_ONE   = 0x0002;
+    static final int QUANTITY_TWO   = 0x0004;
+    static final int QUANTITY_FEW   = 0x0008;
+    static final int QUANTITY_MANY  = 0x0010;
+
+    static final int ID_OTHER = 0x01000004;
+
+    abstract int quantityForNumber(int n);
+
+    final int attrForNumber(int n) {
+        return PluralRules.attrForQuantity(quantityForNumber(n));
+    }
+
+    static final int attrForQuantity(int quantity) {
+        // see include/utils/ResourceTypes.h
+        switch (quantity) {
+            case QUANTITY_ZERO: return 0x01000005;
+            case QUANTITY_ONE:  return 0x01000006;
+            case QUANTITY_TWO:  return 0x01000007;
+            case QUANTITY_FEW:  return 0x01000008;
+            case QUANTITY_MANY: return 0x01000009;
+            default:            return ID_OTHER;
+        }
+    }
+
+    static final String stringForQuantity(int quantity) {
+        switch (quantity) {
+            case QUANTITY_ZERO:
+                return "zero";
+            case QUANTITY_ONE:
+                return "one";
+            case QUANTITY_TWO:
+                return "two";
+            case QUANTITY_FEW:
+                return "few";
+            case QUANTITY_MANY:
+                return "many";
+            default:
+                return "other";
+        }
+    }
+
+    static final PluralRules ruleForLocale(Locale locale) {
+        String lang = locale.getLanguage();
+        if ("cs".equals(lang)) {
+            if (cs == null) cs = new cs();
+            return cs;
+        }
+        else {
+            if (en == null) en = new en();
+            return en;
+        }
+    }
+
+    private static PluralRules cs;
+    private static class cs extends PluralRules {
+        int quantityForNumber(int n) {
+            if (n == 1) {
+                return QUANTITY_ONE;
+            }
+            else if (n >= 2 && n <= 4) {
+                return QUANTITY_FEW;
+            }
+            else {
+                return QUANTITY_OTHER;
+            }
+        }
+    }
+
+    private static PluralRules en;
+    private static class en extends PluralRules {
+        int quantityForNumber(int n) {
+            if (n == 1) {
+                return QUANTITY_ONE;
+            }
+            else {
+                return QUANTITY_OTHER;
+            }
+        }
+    }
+}
+
diff --git a/core/java/android/content/res/Resources.java b/core/java/android/content/res/Resources.java
new file mode 100644
index 0000000..1a963f6
--- /dev/null
+++ b/core/java/android/content/res/Resources.java
@@ -0,0 +1,1890 @@
+/*
+ * 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 android.content.res;
+
+
+import com.android.internal.util.XmlUtils;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import android.graphics.Movie;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.ColorDrawable;
+import android.os.Bundle;
+import android.os.SystemProperties;
+import android.util.AttributeSet;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import android.util.SparseArray;
+import android.util.TypedValue;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.ref.WeakReference;
+
+/**
+ * Class for accessing an application's resources.  This sits on top of the
+ * asset manager of the application (accessible through getAssets()) and
+ * provides a higher-level API for getting typed data from the assets.
+ */
+public class Resources {
+    static final String TAG = "Resources";
+    private static final boolean DEBUG_LOAD = false;
+    private static final boolean DEBUG_CONFIG = false;
+    private static final boolean TRACE_FOR_PRELOAD = false;
+
+    private static final int sSdkVersion = SystemProperties.getInt(
+            "ro.build.version.sdk", 0);
+    private static final Object mSync = new Object();
+    private static Resources mSystem = null;
+    
+    // Information about preloaded resources.  Note that they are not
+    // protected by a lock, because while preloading in zygote we are all
+    // single-threaded, and after that these are immutable.
+    private static final SparseArray<Drawable.ConstantState> mPreloadedDrawables
+            = new SparseArray<Drawable.ConstantState>();
+    private static final SparseArray<ColorStateList> mPreloadedColorStateLists
+            = new SparseArray<ColorStateList>();
+    private static boolean mPreloaded;
+
+    /*package*/ final TypedValue mTmpValue = new TypedValue();
+
+    // These are protected by the mTmpValue lock.
+    private final SparseArray<WeakReference<Drawable.ConstantState> > mDrawableCache
+            = new SparseArray<WeakReference<Drawable.ConstantState> >();
+    private final SparseArray<WeakReference<ColorStateList> > mColorStateListCache
+            = new SparseArray<WeakReference<ColorStateList> >();
+    private boolean mPreloading;
+
+    /*package*/ TypedArray mCachedStyledAttributes = null;
+
+    private int mLastCachedXmlBlockIndex = -1;
+    private final int[] mCachedXmlBlockIds = { 0, 0, 0, 0 };
+    private final XmlBlock[] mCachedXmlBlocks = new XmlBlock[4];
+
+    /*package*/ final AssetManager mAssets;
+    private final Configuration mConfiguration = new Configuration();
+    /*package*/ final DisplayMetrics mMetrics = new DisplayMetrics();
+    PluralRules mPluralRule;
+
+    /**
+     * This exception is thrown by the resource APIs when a requested resource
+     * can not be found.
+     */
+    public static class NotFoundException extends RuntimeException {
+        public NotFoundException() {
+        }
+
+        public NotFoundException(String name) {
+            super(name);
+        }
+    }
+
+    /**
+     * Create a new Resources object on top of an existing set of assets in an
+     * AssetManager.
+     * 
+     * @param assets Previously created AssetManager. 
+     * @param metrics Current display metrics to consider when 
+     *                selecting/computing resource values.
+     * @param config Desired device configuration to consider when 
+     *               selecting/computing resource values (optional).
+     */
+    public Resources(AssetManager assets, DisplayMetrics metrics,
+            Configuration config) {
+        mAssets = assets;
+        mConfiguration.setToDefaults();
+        mMetrics.setToDefaults();
+        updateConfiguration(config, metrics);
+        assets.ensureStringBlocks();
+    }
+
+    /**
+     * Return a global shared Resources object that provides access to only
+     * system resources (no application resources), and is not configured for 
+     * the current screen (can not use dimension units, does not change based 
+     * on orientation, etc). 
+     */
+    public static Resources getSystem() {
+        synchronized (mSync) {
+            Resources ret = mSystem;
+            if (ret == null) {
+                ret = new Resources();
+                mSystem = ret;
+            }
+
+            return ret;
+        }
+    }
+
+    /**
+     * Return the string value associated with a particular resource ID.  The
+     * returned object will be a String if this is a plain string; it will be
+     * some other type of CharSequence if it is styled.
+     * {@more}
+     *
+     * @param id The desired resource identifier, as generated by the aapt
+     *           tool. This integer encodes the package, type, and resource
+     *           entry. The value 0 is an invalid identifier.
+     *
+     * @throws NotFoundException Throws NotFoundException if the given ID does not exist.
+     *
+     * @return CharSequence The string data associated with the resource, plus
+     *         possibly styled text information.
+     */
+    public CharSequence getText(int id) throws NotFoundException {
+        CharSequence res = mAssets.getResourceText(id);
+        if (res != null) {
+            return res;
+        }
+        throw new NotFoundException("String resource ID #0x"
+                                    + Integer.toHexString(id));
+    }
+
+    /**
+     * @param id The desired resource identifier, as generated by the aapt
+     *           tool. This integer encodes the package, type, and resource
+     *           entry. The value 0 is an invalid identifier.
+     *
+     * @throws NotFoundException Throws NotFoundException if the given ID does not exist.
+     *
+     * @return CharSequence The string data associated with the resource, plus
+     *         possibly styled text information.
+     */
+    public CharSequence getQuantityText(int id, int quantity) throws NotFoundException {
+        PluralRules rule = getPluralRule();
+        CharSequence res = mAssets.getResourceBagText(id, rule.attrForNumber(quantity));
+        if (res != null) {
+            return res;
+        }
+        res = mAssets.getResourceBagText(id, PluralRules.ID_OTHER);
+        if (res != null) {
+            return res;
+        }
+        throw new NotFoundException("Plural resource ID #0x" + Integer.toHexString(id)
+                + " quantity=" + quantity
+                + " item=" + PluralRules.stringForQuantity(rule.quantityForNumber(quantity)));
+    }
+
+    private PluralRules getPluralRule() {
+        synchronized (mSync) {
+            if (mPluralRule == null) {
+                mPluralRule = PluralRules.ruleForLocale(mConfiguration.locale);
+            }
+            return mPluralRule;
+        }
+    }
+
+    /**
+     * Return the string value associated with a particular resource ID.  It
+     * will be stripped of any styled text information.
+     * {@more}
+     *
+     * @param id The desired resource identifier, as generated by the aapt
+     *           tool. This integer encodes the package, type, and resource
+     *           entry. The value 0 is an invalid identifier.
+     *
+     * @throws NotFoundException Throws NotFoundException if the given ID does not exist.
+     *
+     * @return String The string data associated with the resource,
+     * stripped of styled text information.
+     */
+    public String getString(int id) throws NotFoundException {
+        CharSequence res = getText(id);
+        if (res != null) {
+            return res.toString();
+        }
+        throw new NotFoundException("String resource ID #0x"
+                                    + Integer.toHexString(id));
+    }
+
+
+    /**
+     * Return the string value associated with a particular resource ID,
+     * substituting the format arguments as defined in {@link java.util.Formatter}
+     * and {@link java.lang.String#format}. It will be stripped of any styled text
+     * information.
+     * {@more}
+     *
+     * @param id The desired resource identifier, as generated by the aapt
+     *           tool. This integer encodes the package, type, and resource
+     *           entry. The value 0 is an invalid identifier.
+     *           
+     * @param formatArgs The format arguments that will be used for substitution.
+     *
+     * @throws NotFoundException Throws NotFoundException if the given ID does not exist.
+     *
+     * @return String The string data associated with the resource,
+     * stripped of styled text information.
+     */
+    public String getString(int id, Object... formatArgs) throws NotFoundException {
+        String raw = getString(id);
+        return String.format(mConfiguration.locale, raw, formatArgs);
+    }
+
+    /**
+     * Return the string value associated with a particular resource ID for a particular
+     * numerical quantity, substituting the format arguments as defined in
+     * {@link java.util.Formatter} and {@link java.lang.String#format}. It will be
+     * stripped of any styled text information.
+     * {@more}
+     *
+     * @param id The desired resource identifier, as generated by the aapt
+     *           tool. This integer encodes the package, type, and resource
+     *           entry. The value 0 is an invalid identifier.
+     * @param quantity The number used to get the correct string for the current language's
+     *           plural rules.
+     * @param formatArgs The format arguments that will be used for substitution.
+     *
+     * @throws NotFoundException Throws NotFoundException if the given ID does not exist.
+     *
+     * @return String The string data associated with the resource,
+     * stripped of styled text information.
+     */
+    public String getQuantityString(int id, int quantity, Object... formatArgs)
+            throws NotFoundException {
+        String raw = getQuantityText(id, quantity).toString();
+        return String.format(mConfiguration.locale, raw, formatArgs);
+    }
+
+    /**
+     * Return the string value associated with a particular resource ID for a particular
+     * numerical quantity.
+     *
+     * @param id The desired resource identifier, as generated by the aapt
+     *           tool. This integer encodes the package, type, and resource
+     *           entry. The value 0 is an invalid identifier.
+     * @param quantity The number used to get the correct string for the current language's
+     *           plural rules.
+     *
+     * @throws NotFoundException Throws NotFoundException if the given ID does not exist.
+     *
+     * @return String The string data associated with the resource,
+     * stripped of styled text information.
+     */
+    public String getQuantityString(int id, int quantity) throws NotFoundException {
+        return getQuantityText(id, quantity).toString();
+    }
+
+    /**
+     * Return the string value associated with a particular resource ID.  The
+     * returned object will be a String if this is a plain string; it will be
+     * some other type of CharSequence if it is styled.
+     * 
+     * @param id The desired resource identifier, as generated by the aapt
+     *           tool. This integer encodes the package, type, and resource
+     *           entry. The value 0 is an invalid identifier.
+     * 
+     * @param def The default CharSequence to return.
+     *
+     * @return CharSequence The string data associated with the resource, plus
+     *         possibly styled text information, or def if id is 0 or not found.
+     */
+    public CharSequence getText(int id, CharSequence def) {
+        CharSequence res = id != 0 ? mAssets.getResourceText(id) : null;
+        return res != null ? res : def;
+    }
+
+    /**
+     * Return the styled text array associated with a particular resource ID.
+     *
+     * @param id The desired resource identifier, as generated by the aapt
+     *           tool. This integer encodes the package, type, and resource
+     *           entry. The value 0 is an invalid identifier.
+     *
+     * @throws NotFoundException Throws NotFoundException if the given ID does not exist.
+     *
+     * @return The styled text array associated with the resource.
+     */
+    public CharSequence[] getTextArray(int id) throws NotFoundException {
+        CharSequence[] res = mAssets.getResourceTextArray(id);
+        if (res != null) {
+            return res;
+        }
+        throw new NotFoundException("Text array resource ID #0x"
+                                    + Integer.toHexString(id));
+    }
+
+    /**
+     * Return the string array associated with a particular resource ID.
+     *
+     * @param id The desired resource identifier, as generated by the aapt
+     *           tool. This integer encodes the package, type, and resource
+     *           entry. The value 0 is an invalid identifier.
+     *
+     * @throws NotFoundException Throws NotFoundException if the given ID does not exist.
+     *
+     * @return The string array associated with the resource.
+     */
+    public String[] getStringArray(int id) throws NotFoundException {
+        String[] res = mAssets.getResourceStringArray(id);
+        if (res != null) {
+            return res;
+        }
+        throw new NotFoundException("String array resource ID #0x"
+                                    + Integer.toHexString(id));
+    }
+
+    /**
+     * Return the int array associated with a particular resource ID.
+     *
+     * @param id The desired resource identifier, as generated by the aapt
+     *           tool. This integer encodes the package, type, and resource
+     *           entry. The value 0 is an invalid identifier.
+     *
+     * @throws NotFoundException Throws NotFoundException if the given ID does not exist.
+     *
+     * @return The int array associated with the resource.
+     */
+    public int[] getIntArray(int id) throws NotFoundException {
+        int[] res = mAssets.getArrayIntResource(id);
+        if (res != null) {
+            return res;
+        }
+        throw new NotFoundException("Int array resource ID #0x"
+                                    + Integer.toHexString(id));
+    }
+
+    /**
+     * Return an array of heterogeneous values.
+     *
+     * @param id The desired resource identifier, as generated by the aapt
+     *           tool. This integer encodes the package, type, and resource
+     *           entry. The value 0 is an invalid identifier.
+     *
+     * @throws NotFoundException Throws NotFoundException if the given ID does not exist.
+     *
+     * @return Returns a TypedArray holding an array of the array values.
+     * Be sure to call {@link TypedArray#recycle() TypedArray.recycle()}
+     * when done with it.
+     */
+    public TypedArray obtainTypedArray(int id) throws NotFoundException {
+        int len = mAssets.getArraySize(id);
+        if (len < 0) {
+            throw new NotFoundException("Array resource ID #0x"
+                                        + Integer.toHexString(id));
+        }
+        
+        TypedArray array = getCachedStyledAttributes(len);
+        array.mLength = mAssets.retrieveArray(id, array.mData);
+        array.mIndices[0] = 0;
+        
+        return array;
+    }
+
+    /**
+     * Retrieve a dimensional for a particular resource ID.  Unit 
+     * conversions are based on the current {@link DisplayMetrics} associated
+     * with the resources.
+     * 
+     * @param id The desired resource identifier, as generated by the aapt
+     *           tool. This integer encodes the package, type, and resource
+     *           entry. The value 0 is an invalid identifier.
+     * 
+     * @return Resource dimension value multiplied by the appropriate 
+     * metric.
+     * 
+     * @throws NotFoundException Throws NotFoundException if the given ID does not exist.
+     *
+     * @see #getDimensionPixelOffset
+     * @see #getDimensionPixelSize
+     */
+    public float getDimension(int id) throws NotFoundException {
+        synchronized (mTmpValue) {
+            TypedValue value = mTmpValue;
+            getValue(id, value, true);
+            if (value.type == TypedValue.TYPE_DIMENSION) {
+                return TypedValue.complexToDimension(value.data, mMetrics);
+            }
+            throw new NotFoundException(
+                    "Resource ID #0x" + Integer.toHexString(id) + " type #0x"
+                    + Integer.toHexString(value.type) + " is not valid");
+        }
+    }
+
+    /**
+     * Retrieve a dimensional for a particular resource ID for use
+     * as an offset in raw pixels.  This is the same as
+     * {@link #getDimension}, except the returned value is converted to
+     * integer pixels for you.  An offset conversion involves simply
+     * truncating the base value to an integer.
+     * 
+     * @param id The desired resource identifier, as generated by the aapt
+     *           tool. This integer encodes the package, type, and resource
+     *           entry. The value 0 is an invalid identifier.
+     * 
+     * @return Resource dimension value multiplied by the appropriate 
+     * metric and truncated to integer pixels.
+     * 
+     * @throws NotFoundException Throws NotFoundException if the given ID does not exist.
+     *
+     * @see #getDimension
+     * @see #getDimensionPixelSize
+     */
+    public int getDimensionPixelOffset(int id) throws NotFoundException {
+        synchronized (mTmpValue) {
+            TypedValue value = mTmpValue;
+            getValue(id, value, true);
+            if (value.type == TypedValue.TYPE_DIMENSION) {
+                return TypedValue.complexToDimensionPixelOffset(
+                        value.data, mMetrics);
+            }
+            throw new NotFoundException(
+                    "Resource ID #0x" + Integer.toHexString(id) + " type #0x"
+                    + Integer.toHexString(value.type) + " is not valid");
+        }
+    }
+
+    /**
+     * Retrieve a dimensional for a particular resource ID for use
+     * as a size in raw pixels.  This is the same as
+     * {@link #getDimension}, except the returned value is converted to
+     * integer pixels for use as a size.  A size conversion involves
+     * rounding the base value, and ensuring that a non-zero base value
+     * is at least one pixel in size.
+     * 
+     * @param id The desired resource identifier, as generated by the aapt
+     *           tool. This integer encodes the package, type, and resource
+     *           entry. The value 0 is an invalid identifier.
+     * 
+     * @return Resource dimension value multiplied by the appropriate 
+     * metric and truncated to integer pixels.
+     *  
+     * @throws NotFoundException Throws NotFoundException if the given ID does not exist.
+     *
+     * @see #getDimension
+     * @see #getDimensionPixelOffset
+     */
+    public int getDimensionPixelSize(int id) throws NotFoundException {
+        synchronized (mTmpValue) {
+            TypedValue value = mTmpValue;
+            getValue(id, value, true);
+            if (value.type == TypedValue.TYPE_DIMENSION) {
+                return TypedValue.complexToDimensionPixelSize(
+                        value.data, mMetrics);
+            }
+            throw new NotFoundException(
+                    "Resource ID #0x" + Integer.toHexString(id) + " type #0x"
+                    + Integer.toHexString(value.type) + " is not valid");
+        }
+    }
+
+    /**
+     * Retrieve a fractional unit for a particular resource ID.
+     * 
+     * @param id The desired resource identifier, as generated by the aapt
+     *           tool. This integer encodes the package, type, and resource
+     *           entry. The value 0 is an invalid identifier.
+     * @param base The base value of this fraction.  In other words, a 
+     *             standard fraction is multiplied by this value.
+     * @param pbase The parent base value of this fraction.  In other 
+     *             words, a parent fraction (nn%p) is multiplied by this
+     *             value.
+     * 
+     * @return Attribute fractional value multiplied by the appropriate 
+     * base value.
+     *  
+     * @throws NotFoundException Throws NotFoundException if the given ID does not exist.
+     */
+    public float getFraction(int id, int base, int pbase) {
+        synchronized (mTmpValue) {
+            TypedValue value = mTmpValue;
+            getValue(id, value, true);
+            if (value.type == TypedValue.TYPE_FRACTION) {
+                return TypedValue.complexToFraction(value.data, base, pbase);
+            }
+            throw new NotFoundException(
+                    "Resource ID #0x" + Integer.toHexString(id) + " type #0x"
+                    + Integer.toHexString(value.type) + " is not valid");
+        }
+    }
+    
+    /**
+     * Return a drawable object associated with a particular resource ID.
+     * Various types of objects will be returned depending on the underlying
+     * resource -- for example, a solid color, PNG image, scalable image, etc.
+     * The Drawable API hides these implementation details.
+     * 
+     * @param id The desired resource identifier, as generated by the aapt
+     *           tool. This integer encodes the package, type, and resource
+     *           entry. The value 0 is an invalid identifier.
+     *
+     * @throws NotFoundException Throws NotFoundException if the given ID does not exist.
+     * 
+     * @return Drawable An object that can be used to draw this resource.
+     */
+    public Drawable getDrawable(int id) throws NotFoundException {
+        synchronized (mTmpValue) {
+            TypedValue value = mTmpValue;
+            getValue(id, value, true);
+            return loadDrawable(value, id);
+        }
+    }
+
+    /**
+     * Return a movie object associated with the particular resource ID.
+     * @param id The desired resource identifier, as generated by the aapt
+     *           tool. This integer encodes the package, type, and resource
+     *           entry. The value 0 is an invalid identifier.
+     * @throws NotFoundException Throws NotFoundException if the given ID does not exist.
+     * 
+     */
+    public Movie getMovie(int id) throws NotFoundException {
+        InputStream is = openRawResource(id);
+        Movie movie = Movie.decodeStream(is);
+        try {
+            is.close();
+        }
+        catch (java.io.IOException e) {
+            // don't care, since the return value is valid
+        }
+        return movie;
+    }
+
+    /**
+     * Return a color integer associated with a particular resource ID.
+     * If the resource holds a complex
+     * {@link android.content.res.ColorStateList}, then the default color from
+     * the set is returned.
+     *
+     * @param id The desired resource identifier, as generated by the aapt
+     *           tool. This integer encodes the package, type, and resource
+     *           entry. The value 0 is an invalid identifier.
+     *
+     * @throws NotFoundException Throws NotFoundException if the given ID does not exist.
+     *
+     * @return Returns a single color value in the form 0xAARRGGBB.
+     */
+    public int getColor(int id) throws NotFoundException {
+        synchronized (mTmpValue) {
+            TypedValue value = mTmpValue;
+            getValue(id, value, true);
+            if (value.type >= TypedValue.TYPE_FIRST_INT
+                && value.type <= TypedValue.TYPE_LAST_INT) {
+                return value.data;
+            } else if (value.type == TypedValue.TYPE_STRING) {
+                ColorStateList csl = loadColorStateList(mTmpValue, id);
+                return csl.getDefaultColor();
+            }
+            throw new NotFoundException(
+                "Resource ID #0x" + Integer.toHexString(id) + " type #0x"
+                + Integer.toHexString(value.type) + " is not valid");
+        }
+    }
+
+    /**
+     * Return a color state list associated with a particular resource ID.  The
+     * resource may contain either a single raw color value, or a complex
+     * {@link android.content.res.ColorStateList} holding multiple possible colors.
+     *
+     * @param id The desired resource identifier of a {@link ColorStateList},
+     *        as generated by the aapt tool. This integer encodes the package, type, and resource
+     *        entry. The value 0 is an invalid identifier.
+     *
+     * @throws NotFoundException Throws NotFoundException if the given ID does not exist.
+     *
+     * @return Returns a ColorStateList object containing either a single
+     * solid color or multiple colors that can be selected based on a state.
+     */
+    public ColorStateList getColorStateList(int id) throws NotFoundException {
+        synchronized (mTmpValue) {
+            TypedValue value = mTmpValue;
+            getValue(id, value, true);
+            return loadColorStateList(value, id);
+        }
+    }
+
+    /**
+     * Return a boolean associated with a particular resource ID.  This can be
+     * used with any integral resource value, and will return true if it is
+     * non-zero.
+     *
+     * @param id The desired resource identifier, as generated by the aapt
+     *           tool. This integer encodes the package, type, and resource
+     *           entry. The value 0 is an invalid identifier.
+     *
+     * @throws NotFoundException Throws NotFoundException if the given ID does not exist.
+     *
+     * @return Returns the boolean value contained in the resource.
+     */
+    public boolean getBoolean(int id) throws NotFoundException {
+        synchronized (mTmpValue) {
+            TypedValue value = mTmpValue;
+            getValue(id, value, true);
+            if (value.type >= TypedValue.TYPE_FIRST_INT
+                && value.type <= TypedValue.TYPE_LAST_INT) {
+                return value.data != 0;
+            }
+            throw new NotFoundException(
+                "Resource ID #0x" + Integer.toHexString(id) + " type #0x"
+                + Integer.toHexString(value.type) + " is not valid");
+        }
+    }
+
+    /**
+     * Return an integer associated with a particular resource ID.
+     *
+     * @param id The desired resource identifier, as generated by the aapt
+     *           tool. This integer encodes the package, type, and resource
+     *           entry. The value 0 is an invalid identifier.
+     *
+     * @throws NotFoundException Throws NotFoundException if the given ID does not exist.
+     *
+     * @return Returns the integer value contained in the resource.
+     */
+    public int getInteger(int id) throws NotFoundException {
+        synchronized (mTmpValue) {
+            TypedValue value = mTmpValue;
+            getValue(id, value, true);
+            if (value.type >= TypedValue.TYPE_FIRST_INT
+                && value.type <= TypedValue.TYPE_LAST_INT) {
+                return value.data;
+            }
+            throw new NotFoundException(
+                "Resource ID #0x" + Integer.toHexString(id) + " type #0x"
+                + Integer.toHexString(value.type) + " is not valid");
+        }
+    }
+
+    /**
+     * Return an XmlResourceParser through which you can read a view layout
+     * description for the given resource ID.  This parser has limited
+     * functionality -- in particular, you can't change its input, and only
+     * the high-level events are available.
+     * 
+     * <p>This function is really a simple wrapper for calling
+     * {@link #getXml} with a layout resource.
+     * 
+     * @param id The desired resource identifier, as generated by the aapt
+     *           tool. This integer encodes the package, type, and resource
+     *           entry. The value 0 is an invalid identifier.
+     *
+     * @throws NotFoundException Throws NotFoundException if the given ID does not exist.
+     * 
+     * @return A new parser object through which you can read
+     *         the XML data.
+     *         
+     * @see #getXml
+     */
+    public XmlResourceParser getLayout(int id) throws NotFoundException {
+        return loadXmlResourceParser(id, "layout");
+    }
+
+    /**
+     * Return an XmlResourceParser through which you can read an animation
+     * description for the given resource ID.  This parser has limited
+     * functionality -- in particular, you can't change its input, and only
+     * the high-level events are available.
+     * 
+     * <p>This function is really a simple wrapper for calling
+     * {@link #getXml} with an animation resource.
+     * 
+     * @param id The desired resource identifier, as generated by the aapt
+     *           tool. This integer encodes the package, type, and resource
+     *           entry. The value 0 is an invalid identifier.
+     *
+     * @throws NotFoundException Throws NotFoundException if the given ID does not exist.
+     * 
+     * @return A new parser object through which you can read
+     *         the XML data.
+     *         
+     * @see #getXml
+     */
+    public XmlResourceParser getAnimation(int id) throws NotFoundException {
+        return loadXmlResourceParser(id, "anim");
+    }
+
+    /**
+     * Return an XmlResourceParser through which you can read a generic XML
+     * resource for the given resource ID.
+     * 
+     * <p>The XmlPullParser implementation returned here has some limited
+     * functionality.  In particular, you can't change its input, and only
+     * high-level parsing events are available (since the document was
+     * pre-parsed for you at build time, which involved merging text and
+     * stripping comments).
+     * 
+     * @param id The desired resource identifier, as generated by the aapt
+     *           tool. This integer encodes the package, type, and resource
+     *           entry. The value 0 is an invalid identifier.
+     *
+     * @throws NotFoundException Throws NotFoundException if the given ID does not exist.
+     * 
+     * @return A new parser object through which you can read
+     *         the XML data.
+     *         
+     * @see android.util.AttributeSet
+     */
+    public XmlResourceParser getXml(int id) throws NotFoundException {
+        return loadXmlResourceParser(id, "xml");
+    }
+
+    /**
+     * Open a data stream for reading a raw resource.  This can only be used
+     * with resources whose value is the name of an asset files -- that is, it can be
+     * used to open drawable, sound, and raw resources; it will fail on string
+     * and color resources.
+     * 
+     * @param id The resource identifier to open, as generated by the appt
+     *           tool.
+     * 
+     * @return InputStream Access to the resource data.
+     *
+     * @throws NotFoundException Throws NotFoundException if the given ID does not exist.
+     * 
+     */
+    public InputStream openRawResource(int id) throws NotFoundException {
+        synchronized (mTmpValue) {
+            return openRawResource(id, mTmpValue);
+        }
+    }
+
+    /**
+     * Open a data stream for reading a raw resource.  This can only be used
+     * with resources whose value is the name of an asset files -- that is, it can be
+     * used to open drawable, sound, and raw resources; it will fail on string
+     * and color resources.
+     *
+     * @param id The resource identifier to open, as generated by the appt tool.
+     * @param value The TypedValue object to hold the resource information.
+     *
+     * @return InputStream Access to the resource data.
+     *
+     * @throws NotFoundException Throws NotFoundException if the given ID does not exist.
+     *
+     * @hide Pending API council approval
+     */
+    public InputStream openRawResource(int id, TypedValue value) throws NotFoundException {
+        getValue(id, value, true);
+
+        try {
+            return mAssets.openNonAsset(value.assetCookie, value.string.toString(),
+                    AssetManager.ACCESS_STREAMING);
+        } catch (Exception e) {
+            NotFoundException rnf = new NotFoundException("File " + value.string.toString() +
+                    " from drawable resource ID #0x" + Integer.toHexString(id));
+            rnf.initCause(e);
+            throw rnf;
+        }
+    }
+
+    /**
+     * Open a file descriptor for reading a raw resource.  This can only be used
+     * with resources whose value is the name of an asset files -- that is, it can be
+     * used to open drawable, sound, and raw resources; it will fail on string
+     * and color resources.
+     * 
+     * <p>This function only works for resources that are stored in the package
+     * as uncompressed data, which typically includes things like mp3 files
+     * and png images.
+     * 
+     * @param id The resource identifier to open, as generated by the appt
+     *           tool.
+     * 
+     * @return AssetFileDescriptor A new file descriptor you can use to read
+     * the resource.  This includes the file descriptor itself, as well as the
+     * offset and length of data where the resource appears in the file.  A
+     * null is returned if the file exists but is compressed.
+     *
+     * @throws NotFoundException Throws NotFoundException if the given ID does not exist.
+     * 
+     */
+    public AssetFileDescriptor openRawResourceFd(int id) throws NotFoundException {
+        synchronized (mTmpValue) {
+            TypedValue value = mTmpValue;
+            getValue(id, value, true);
+
+            try {
+                return mAssets.openNonAssetFd(
+                    value.assetCookie, value.string.toString());
+            } catch (Exception e) {
+                NotFoundException rnf = new NotFoundException(
+                    "File " + value.string.toString()
+                    + " from drawable resource ID #0x"
+                    + Integer.toHexString(id));
+                rnf.initCause(e);
+                throw rnf;
+            }
+
+        }
+    }
+
+    /**
+     * Return the raw data associated with a particular resource ID.
+     * 
+     * @param id The desired resource identifier, as generated by the aapt
+     *           tool. This integer encodes the package, type, and resource
+     *           entry. The value 0 is an invalid identifier.
+     * @param outValue Object in which to place the resource data.
+     * @param resolveRefs If true, a resource that is a reference to another
+     *                    resource will be followed so that you receive the
+     *                    actual final resource data.  If false, the TypedValue
+     *                    will be filled in with the reference itself.
+     *
+     * @throws NotFoundException Throws NotFoundException if the given ID does not exist.
+     *
+     */
+    public void getValue(int id, TypedValue outValue, boolean resolveRefs)
+            throws NotFoundException {
+        boolean found = mAssets.getResourceValue(id, outValue, resolveRefs);
+        if (found) {
+            return;
+        }
+        throw new NotFoundException("Resource ID #0x"
+                                    + Integer.toHexString(id));
+    }
+
+    /**
+     * Return the raw data associated with a particular resource ID.
+     * See getIdentifier() for information on how names are mapped to resource
+     * IDs, and getString(int) for information on how string resources are
+     * retrieved.
+     * 
+     * <p>Note: use of this function is discouraged.  It is much more
+     * efficient to retrieve resources by identifier than by name.
+     * 
+     * @param name The name of the desired resource.  This is passed to
+     *             getIdentifier() with a default type of "string".
+     * @param outValue Object in which to place the resource data.
+     * @param resolveRefs If true, a resource that is a reference to another
+     *                    resource will be followed so that you receive the
+     *                    actual final resource data.  If false, the TypedValue
+     *                    will be filled in with the reference itself.
+     *
+     * @throws NotFoundException Throws NotFoundException if the given ID does not exist.
+     *
+     */
+    public void getValue(String name, TypedValue outValue, boolean resolveRefs)
+            throws NotFoundException {
+        int id = getIdentifier(name, "string", null);
+        if (id != 0) {
+            getValue(id, outValue, resolveRefs);
+            return;
+        }
+        throw new NotFoundException("String resource name " + name);
+    }
+
+    /**
+     * This class holds the current attribute values for a particular theme.
+     * In other words, a Theme is a set of values for resource attributes;
+     * these are used in conjunction with {@link TypedArray}
+     * to resolve the final value for an attribute.
+     * 
+     * <p>The Theme's attributes come into play in two ways: (1) a styled
+     * attribute can explicit reference a value in the theme through the
+     * "?themeAttribute" syntax; (2) if no value has been defined for a
+     * particular styled attribute, as a last resort we will try to find that
+     * attribute's value in the Theme.
+     * 
+     * <p>You will normally use the {@link #obtainStyledAttributes} APIs to
+     * retrieve XML attributes with style and theme information applied.
+     */
+    public final class Theme {
+        /**
+         * Place new attribute values into the theme.  The style resource
+         * specified by <var>resid</var> will be retrieved from this Theme's
+         * resources, its values placed into the Theme object.
+         * 
+         * <p>The semantics of this function depends on the <var>force</var>
+         * argument:  If false, only values that are not already defined in
+         * the theme will be copied from the system resource; otherwise, if
+         * any of the style's attributes are already defined in the theme, the
+         * current values in the theme will be overwritten.
+         * 
+         * @param resid The resource ID of a style resource from which to
+         *              obtain attribute values.
+         * @param force If true, values in the style resource will always be
+         *              used in the theme; otherwise, they will only be used
+         *              if not already defined in the theme.
+         */
+        public void applyStyle(int resid, boolean force) {
+            AssetManager.applyThemeStyle(mTheme, resid, force);
+        }
+
+        /**
+         * Set this theme to hold the same contents as the theme
+         * <var>other</var>.  If both of these themes are from the same
+         * Resources object, they will be identical after this function
+         * returns.  If they are from different Resources, only the resources
+         * they have in common will be set in this theme.
+         * 
+         * @param other The existing Theme to copy from.
+         */
+        public void setTo(Theme other) {
+            AssetManager.copyTheme(mTheme, other.mTheme);
+        }
+
+        /**
+         * Return a StyledAttributes holding the values defined by
+         * <var>Theme</var> which are listed in <var>attrs</var>.
+         * 
+         * <p>Be sure to call StyledAttributes.recycle() when you are done with
+         * the array.
+         * 
+         * @param attrs The desired attributes.
+         *
+         * @throws NotFoundException Throws NotFoundException if the given ID does not exist.
+         * 
+         * @return Returns a TypedArray holding an array of the attribute values.
+         * Be sure to call {@link TypedArray#recycle() TypedArray.recycle()}
+         * when done with it.
+         * 
+         * @see Resources#obtainAttributes
+         * @see #obtainStyledAttributes(int, int[])
+         * @see #obtainStyledAttributes(AttributeSet, int[], int, int)
+         */
+        public TypedArray obtainStyledAttributes(int[] attrs) {
+            int len = attrs.length;
+            TypedArray array = getCachedStyledAttributes(len);
+            array.mRsrcs = attrs;
+            AssetManager.applyStyle(mTheme, 0, 0, 0, attrs,
+                    array.mData, array.mIndices);
+            return array;
+        }
+
+        /**
+         * Return a StyledAttributes holding the values defined by the style
+         * resource <var>resid</var> which are listed in <var>attrs</var>.
+         * 
+         * <p>Be sure to call StyledAttributes.recycle() when you are done with
+         * the array.
+         * 
+         * @param resid The desired style resource.
+         * @param attrs The desired attributes in the style.
+         * 
+         * @throws NotFoundException Throws NotFoundException if the given ID does not exist.
+         * 
+         * @return Returns a TypedArray holding an array of the attribute values.
+         * Be sure to call {@link TypedArray#recycle() TypedArray.recycle()}
+         * when done with it.
+         * 
+         * @see Resources#obtainAttributes
+         * @see #obtainStyledAttributes(int[])
+         * @see #obtainStyledAttributes(AttributeSet, int[], int, int)
+         */
+        public TypedArray obtainStyledAttributes(int resid, int[] attrs)
+                throws NotFoundException {
+            int len = attrs.length;
+            TypedArray array = getCachedStyledAttributes(len);
+            array.mRsrcs = attrs;
+
+            AssetManager.applyStyle(mTheme, 0, resid, 0, attrs,
+                    array.mData, array.mIndices);
+            if (false) {
+                int[] data = array.mData;
+                
+                System.out.println("**********************************************************");
+                System.out.println("**********************************************************");
+                System.out.println("**********************************************************");
+                System.out.println("Attributes:");
+                String s = "  Attrs:";
+                int i;
+                for (i=0; i<attrs.length; i++) {
+                    s = s + " 0x" + Integer.toHexString(attrs[i]);
+                }
+                System.out.println(s);
+                s = "  Found:";
+                TypedValue value = new TypedValue();
+                for (i=0; i<attrs.length; i++) {
+                    int d = i*AssetManager.STYLE_NUM_ENTRIES;
+                    value.type = data[d+AssetManager.STYLE_TYPE];
+                    value.data = data[d+AssetManager.STYLE_DATA];
+                    value.assetCookie = data[d+AssetManager.STYLE_ASSET_COOKIE];
+                    value.resourceId = data[d+AssetManager.STYLE_RESOURCE_ID];
+                    s = s + " 0x" + Integer.toHexString(attrs[i])
+                        + "=" + value;
+                }
+                System.out.println(s);
+            }
+            return array;
+        }
+
+        /**
+         * Return a StyledAttributes holding the attribute values in
+         * <var>set</var>
+         * that are listed in <var>attrs</var>.  In addition, if the given
+         * AttributeSet specifies a style class (through the "style" attribute),
+         * that style will be applied on top of the base attributes it defines.
+         * 
+         * <p>Be sure to call StyledAttributes.recycle() when you are done with
+         * the array.
+         * 
+         * <p>When determining the final value of a particular attribute, there
+         * are four inputs that come into play:</p>
+         * 
+         * <ol>
+         *     <li> Any attribute values in the given AttributeSet.
+         *     <li> The style resource specified in the AttributeSet (named
+         *     "style").
+         *     <li> The default style specified by <var>defStyleAttr</var> and
+         *     <var>defStyleRes</var>
+         *     <li> The base values in this theme.
+         * </ol>
+         * 
+         * <p>Each of these inputs is considered in-order, with the first listed
+         * taking precedence over the following ones.  In other words, if in the
+         * AttributeSet you have supplied <code>&lt;Button
+         * textColor="#ff000000"&gt;</code>, then the button's text will
+         * <em>always</em> be black, regardless of what is specified in any of
+         * the styles.
+         * 
+         * @param set The base set of attribute values.  May be null.
+         * @param attrs The desired attributes to be retrieved.
+         * @param defStyleAttr An attribute in the current theme that contains a
+         *                     reference to a style resource that supplies
+         *                     defaults values for the StyledAttributes.  Can be
+         *                     0 to not look for defaults.
+         * @param defStyleRes A resource identifier of a style resource that
+         *                    supplies default values for the StyledAttributes,
+         *                    used only if defStyleAttr is 0 or can not be found
+         *                    in the theme.  Can be 0 to not look for defaults.
+         * 
+         * @return Returns a TypedArray holding an array of the attribute values.
+         * Be sure to call {@link TypedArray#recycle() TypedArray.recycle()}
+         * when done with it.
+         * 
+         * @see Resources#obtainAttributes
+         * @see #obtainStyledAttributes(int[])
+         * @see #obtainStyledAttributes(int, int[])
+         */
+        public TypedArray obtainStyledAttributes(AttributeSet set,
+                int[] attrs, int defStyleAttr, int defStyleRes) {
+            int len = attrs.length;
+            TypedArray array = getCachedStyledAttributes(len);
+
+            // XXX note that for now we only work with compiled XML files.
+            // To support generic XML files we will need to manually parse
+            // out the attributes from the XML file (applying type information
+            // contained in the resources and such).
+            XmlBlock.Parser parser = (XmlBlock.Parser)set;
+            AssetManager.applyStyle(
+                mTheme, defStyleAttr, defStyleRes,
+                parser != null ? parser.mParseState : 0, attrs,
+                        array.mData, array.mIndices);
+
+            array.mRsrcs = attrs;
+            array.mXml = parser;
+
+            if (false) {
+                int[] data = array.mData;
+                
+                System.out.println("Attributes:");
+                String s = "  Attrs:";
+                int i;
+                for (i=0; i<set.getAttributeCount(); i++) {
+                    s = s + " " + set.getAttributeName(i);
+                    int id = set.getAttributeNameResource(i);
+                    if (id != 0) {
+                        s = s + "(0x" + Integer.toHexString(id) + ")";
+                    }
+                    s = s + "=" + set.getAttributeValue(i);
+                }
+                System.out.println(s);
+                s = "  Found:";
+                TypedValue value = new TypedValue();
+                for (i=0; i<attrs.length; i++) {
+                    int d = i*AssetManager.STYLE_NUM_ENTRIES;
+                    value.type = data[d+AssetManager.STYLE_TYPE];
+                    value.data = data[d+AssetManager.STYLE_DATA];
+                    value.assetCookie = data[d+AssetManager.STYLE_ASSET_COOKIE];
+                    value.resourceId = data[d+AssetManager.STYLE_RESOURCE_ID];
+                    s = s + " 0x" + Integer.toHexString(attrs[i])
+                        + "=" + value;
+                }
+                System.out.println(s);
+            }
+
+            return array;
+        }
+
+        /**
+         * Retrieve the value of an attribute in the Theme.  The contents of
+         * <var>outValue</var> are ultimately filled in by
+         * {@link Resources#getValue}.
+         * 
+         * @param resid The resource identifier of the desired theme
+         *              attribute.
+         * @param outValue Filled in with the ultimate resource value supplied
+         *                 by the attribute.
+         * @param resolveRefs If true, resource references will be walked; if
+         *                    false, <var>outValue</var> may be a
+         *                    TYPE_REFERENCE.  In either case, it will never
+         *                    be a TYPE_ATTRIBUTE.
+         * 
+         * @return boolean Returns true if the attribute was found and
+         *         <var>outValue</var> is valid, else false.
+         */
+        public boolean resolveAttribute(int resid, TypedValue outValue,
+                boolean resolveRefs) {
+            boolean got = mAssets.getThemeValue(mTheme, resid, outValue, resolveRefs);
+            if (false) {
+                System.out.println(
+                    "resolveAttribute #" + Integer.toHexString(resid)
+                    + " got=" + got + ", type=0x" + Integer.toHexString(outValue.type)
+                    + ", data=0x" + Integer.toHexString(outValue.data));
+            }
+            return got;
+        }
+
+        /**
+         * Print contents of this theme out to the log.  For debugging only.
+         * 
+         * @param priority The log priority to use.
+         * @param tag The log tag to use.
+         * @param prefix Text to prefix each line printed.
+         */
+        public void dump(int priority, String tag, String prefix) {
+            AssetManager.dumpTheme(mTheme, priority, tag, prefix);
+        }
+        
+        protected void finalize() throws Throwable {
+            super.finalize();
+            mAssets.releaseTheme(mTheme);
+        }
+
+        /*package*/ Theme() {
+            mAssets = Resources.this.mAssets;
+            mTheme = mAssets.createTheme();
+        }
+
+        private final AssetManager mAssets;
+        private final int mTheme;
+    }
+
+    /**
+     * Generate a new Theme object for this set of Resources.  It initially
+     * starts out empty.
+     * 
+     * @return Theme The newly created Theme container.
+     */
+    public final Theme newTheme() {
+        return new Theme();
+    }
+
+    /**
+     * Retrieve a set of basic attribute values from an AttributeSet, not
+     * performing styling of them using a theme and/or style resources.
+     * 
+     * @param set The current attribute values to retrieve.
+     * @param attrs The specific attributes to be retrieved.
+     * @return Returns a TypedArray holding an array of the attribute values.
+     * Be sure to call {@link TypedArray#recycle() TypedArray.recycle()}
+     * when done with it.
+     * 
+     * @see Theme#obtainStyledAttributes(AttributeSet, int[], int, int)
+     */
+    public TypedArray obtainAttributes(AttributeSet set, int[] attrs) {
+        int len = attrs.length;
+        TypedArray array = getCachedStyledAttributes(len);
+
+        // XXX note that for now we only work with compiled XML files.
+        // To support generic XML files we will need to manually parse
+        // out the attributes from the XML file (applying type information
+        // contained in the resources and such).
+        XmlBlock.Parser parser = (XmlBlock.Parser)set;
+        mAssets.retrieveAttributes(parser.mParseState, attrs,
+                array.mData, array.mIndices);
+
+        array.mRsrcs = attrs;
+        array.mXml = parser;
+
+        return array;
+    }
+    
+    /**
+     * Store the newly updated configuration.
+     */
+    public void updateConfiguration(Configuration config,
+            DisplayMetrics metrics) {
+        synchronized (mTmpValue) {
+            int configChanges = 0xfffffff;
+            if (config != null) {
+                configChanges = mConfiguration.updateFrom(config);
+            }
+            if (metrics != null) {
+                mMetrics.setTo(metrics);
+            }
+            mMetrics.scaledDensity = mMetrics.density * mConfiguration.fontScale;
+            String locale = null;
+            if (mConfiguration.locale != null) {
+                locale = mConfiguration.locale.getLanguage();
+                if (mConfiguration.locale.getCountry() != null) {
+                    locale += "-" + mConfiguration.locale.getCountry();
+                }
+            }
+            int width, height;
+            if (mMetrics.widthPixels >= mMetrics.heightPixels) {
+                width = mMetrics.widthPixels;
+                height = mMetrics.heightPixels;
+            } else {
+                //noinspection SuspiciousNameCombination
+                width = mMetrics.heightPixels;
+                //noinspection SuspiciousNameCombination
+                height = mMetrics.widthPixels;
+            }
+            int keyboardHidden = mConfiguration.keyboardHidden;
+            if (keyboardHidden == Configuration.HARDKEYBOARDHIDDEN_NO
+                    && mConfiguration.hardKeyboardHidden
+                            == Configuration.HARDKEYBOARDHIDDEN_YES) {
+                keyboardHidden = Configuration.KEYBOARDHIDDEN_SOFT;
+            }
+            mAssets.setConfiguration(mConfiguration.mcc, mConfiguration.mnc,
+                    locale, mConfiguration.orientation,
+                    mConfiguration.touchscreen,
+                    (int)(mMetrics.density*160), mConfiguration.keyboard,
+                    keyboardHidden, mConfiguration.navigation, width, height,
+                    sSdkVersion);
+            int N = mDrawableCache.size();
+            if (DEBUG_CONFIG) {
+                Log.d(TAG, "Cleaning up drawables config changes: 0x"
+                        + Integer.toHexString(configChanges));
+            }
+            for (int i=0; i<N; i++) {
+                WeakReference<Drawable.ConstantState> ref = mDrawableCache.valueAt(i);
+                if (ref != null) {
+                    Drawable.ConstantState cs = ref.get();
+                    if (cs != null) {
+                        if (Configuration.needNewResources(
+                                configChanges, cs.getChangingConfigurations())) {
+                            if (DEBUG_CONFIG) {
+                                Log.d(TAG, "FLUSHING #0x"
+                                        + Integer.toHexString(mDrawableCache.keyAt(i))
+                                        + " / " + cs + " with changes: 0x"
+                                        + Integer.toHexString(cs.getChangingConfigurations()));
+                            }
+                            mDrawableCache.setValueAt(i, null);
+                        } else if (DEBUG_CONFIG) {
+                            Log.d(TAG, "(Keeping #0x"
+                                    + Integer.toHexString(mDrawableCache.keyAt(i))
+                                    + " / " + cs + " with changes: 0x"
+                                    + Integer.toHexString(cs.getChangingConfigurations())
+                                    + ")");
+                        }
+                    }
+                }
+            }
+            mDrawableCache.clear();
+            mColorStateListCache.clear();
+            flushLayoutCache();
+        }
+        synchronized (mSync) {
+            if (mPluralRule != null) {
+                mPluralRule = PluralRules.ruleForLocale(config.locale);
+            }
+        }
+    }
+
+    /**
+     * Update the system resources configuration if they have previously
+     * been initialized.
+     *
+     * @hide
+     */
+    public static void updateSystemConfiguration(Configuration config, DisplayMetrics metrics) {
+        if (mSystem != null) {
+            mSystem.updateConfiguration(config, metrics);
+            //Log.i(TAG, "Updated system resources " + mSystem
+            //        + ": " + mSystem.getConfiguration());
+        }
+    }
+
+    /**
+     * Return the current display metrics that are in effect for this resource 
+     * object.  The returned object should be treated as read-only.
+     * 
+     * @return The resource's current display metrics. 
+     */
+    public DisplayMetrics getDisplayMetrics() {
+        return mMetrics;
+    }
+
+    /**
+     * Return the current configuration that is in effect for this resource 
+     * object.  The returned object should be treated as read-only.
+     * 
+     * @return The resource's current configuration. 
+     */
+    public Configuration getConfiguration() {
+        return mConfiguration;
+    }
+
+    /**
+     * Return a resource identifier for the given resource name.  A fully
+     * qualified resource name is of the form "package:type/entry".  The first
+     * two components (package and type) are optional if defType and
+     * defPackage, respectively, are specified here.
+     * 
+     * <p>Note: use of this function is discouraged.  It is much more
+     * efficient to retrieve resources by identifier than by name.
+     * 
+     * @param name The name of the desired resource.
+     * @param defType Optional default resource type to find, if "type/" is
+     *                not included in the name.  Can be null to require an
+     *                explicit type.
+     * @param defPackage Optional default package to find, if "package:" is
+     *                   not included in the name.  Can be null to require an
+     *                   explicit package.
+     * 
+     * @return int The associated resource identifier.  Returns 0 if no such
+     *         resource was found.  (0 is not a valid resource ID.)
+     */
+    public int getIdentifier(String name, String defType, String defPackage) {
+        try {
+            return Integer.parseInt(name);
+        } catch (Exception e) {
+            // Ignore
+        }
+        return mAssets.getResourceIdentifier(name, defType, defPackage);
+    }
+
+    /**
+     * Return the full name for a given resource identifier.  This name is
+     * a single string of the form "package:type/entry".
+     * 
+     * @param resid The resource identifier whose name is to be retrieved.
+     * 
+     * @return A string holding the name of the resource.
+     * 
+     * @throws NotFoundException Throws NotFoundException if the given ID does not exist.
+     * 
+     * @see #getResourcePackageName
+     * @see #getResourceTypeName
+     * @see #getResourceEntryName
+     */
+    public String getResourceName(int resid) throws NotFoundException {
+        String str = mAssets.getResourceName(resid);
+        if (str != null) return str;
+        throw new NotFoundException("Unable to find resource ID #0x"
+                + Integer.toHexString(resid));
+    }
+    
+    /**
+     * Return the package name for a given resource identifier.
+     * 
+     * @param resid The resource identifier whose package name is to be
+     * retrieved.
+     * 
+     * @return A string holding the package name of the resource.
+     * 
+     * @throws NotFoundException Throws NotFoundException if the given ID does not exist.
+     * 
+     * @see #getResourceName
+     */
+    public String getResourcePackageName(int resid) throws NotFoundException {
+        String str = mAssets.getResourcePackageName(resid);
+        if (str != null) return str;
+        throw new NotFoundException("Unable to find resource ID #0x"
+                + Integer.toHexString(resid));
+    }
+    
+    /**
+     * Return the type name for a given resource identifier.
+     * 
+     * @param resid The resource identifier whose type name is to be
+     * retrieved.
+     * 
+     * @return A string holding the type name of the resource.
+     * 
+     * @throws NotFoundException Throws NotFoundException if the given ID does not exist.
+     * 
+     * @see #getResourceName
+     */
+    public String getResourceTypeName(int resid) throws NotFoundException {
+        String str = mAssets.getResourceTypeName(resid);
+        if (str != null) return str;
+        throw new NotFoundException("Unable to find resource ID #0x"
+                + Integer.toHexString(resid));
+    }
+    
+    /**
+     * Return the entry name for a given resource identifier.
+     * 
+     * @param resid The resource identifier whose entry name is to be
+     * retrieved.
+     * 
+     * @return A string holding the entry name of the resource.
+     * 
+     * @throws NotFoundException Throws NotFoundException if the given ID does not exist.
+     * 
+     * @see #getResourceName
+     */
+    public String getResourceEntryName(int resid) throws NotFoundException {
+        String str = mAssets.getResourceEntryName(resid);
+        if (str != null) return str;
+        throw new NotFoundException("Unable to find resource ID #0x"
+                + Integer.toHexString(resid));
+    }
+    
+    /**
+     * Parse a series of {@link android.R.styleable#Extra &lt;extra&gt;} tags from
+     * an XML file.  You call this when you are at the parent tag of the
+     * extra tags, and it return once all of the child tags have been parsed.
+     * This will call {@link #parseBundleExtra} for each extra tag encountered.
+     * 
+     * @param parser The parser from which to retrieve the extras.
+     * @param outBundle A Bundle in which to place all parsed extras.
+     * @throws XmlPullParserException
+     * @throws IOException
+     */
+    public void parseBundleExtras(XmlResourceParser parser, Bundle outBundle)
+            throws XmlPullParserException, IOException {
+        int outerDepth = parser.getDepth();
+        int type;
+        while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
+               && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
+            if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+                continue;
+            }
+            
+            String nodeName = parser.getName();
+            if (nodeName.equals("extra")) {
+                parseBundleExtra("extra", parser, outBundle);
+                XmlUtils.skipCurrentTag(parser);
+
+            } else {
+                XmlUtils.skipCurrentTag(parser);
+            }
+        }        
+    }
+    
+    /**
+     * Parse a name/value pair out of an XML tag holding that data.  The
+     * AttributeSet must be holding the data defined by
+     * {@link android.R.styleable#Extra}.  The following value types are supported:
+     * <ul>
+     * <li> {@link TypedValue#TYPE_STRING}:
+     * {@link Bundle#putCharSequence Bundle.putCharSequence()}
+     * <li> {@link TypedValue#TYPE_INT_BOOLEAN}:
+     * {@link Bundle#putCharSequence Bundle.putBoolean()}
+     * <li> {@link TypedValue#TYPE_FIRST_INT}-{@link TypedValue#TYPE_LAST_INT}:
+     * {@link Bundle#putCharSequence Bundle.putBoolean()}
+     * <li> {@link TypedValue#TYPE_FLOAT}:
+     * {@link Bundle#putCharSequence Bundle.putFloat()}
+     * </ul>
+     * 
+     * @param tagName The name of the tag these attributes come from; this is
+     * only used for reporting error messages.
+     * @param attrs The attributes from which to retrieve the name/value pair.
+     * @param outBundle The Bundle in which to place the parsed value.
+     * @throws XmlPullParserException If the attributes are not valid.
+     */
+    public void parseBundleExtra(String tagName, AttributeSet attrs,
+            Bundle outBundle) throws XmlPullParserException {
+        TypedArray sa = obtainAttributes(attrs,
+                com.android.internal.R.styleable.Extra);
+
+        String name = sa.getString(
+                com.android.internal.R.styleable.Extra_name);
+        if (name == null) {
+            sa.recycle();
+            throw new XmlPullParserException("<" + tagName
+                    + "> requires an android:name attribute at "
+                    + attrs.getPositionDescription());
+        }
+
+        TypedValue v = sa.peekValue(
+                com.android.internal.R.styleable.Extra_value);
+        if (v != null) {
+            if (v.type == TypedValue.TYPE_STRING) {
+                CharSequence cs = v.coerceToString();
+                outBundle.putCharSequence(name, cs);
+            } else if (v.type == TypedValue.TYPE_INT_BOOLEAN) {
+                outBundle.putBoolean(name, v.data != 0);
+            } else if (v.type >= TypedValue.TYPE_FIRST_INT
+                    && v.type <= TypedValue.TYPE_LAST_INT) {
+                outBundle.putInt(name, v.data);
+            } else if (v.type == TypedValue.TYPE_FLOAT) {
+                outBundle.putFloat(name, v.getFloat());
+            } else {
+                sa.recycle();
+                throw new XmlPullParserException("<" + tagName
+                        + "> only supports string, integer, float, color, and boolean at "
+                        + attrs.getPositionDescription());
+            }
+        } else {
+            sa.recycle();
+            throw new XmlPullParserException("<" + tagName
+                    + "> requires an android:value or android:resource attribute at "
+                    + attrs.getPositionDescription());
+        }
+
+        sa.recycle();
+    }
+    
+    /**
+     * Retrieve underlying AssetManager storage for these resources.
+     */
+    public final AssetManager getAssets() {
+        return mAssets;
+    }
+
+    /**
+     * Call this to remove all cached loaded layout resources from the
+     * Resources object.  Only intended for use with performance testing
+     * tools.
+     */
+    public final void flushLayoutCache() {
+        synchronized (mCachedXmlBlockIds) {
+            // First see if this block is in our cache.
+            final int num = mCachedXmlBlockIds.length;
+            for (int i=0; i<num; i++) {
+                mCachedXmlBlockIds[i] = -0;
+                XmlBlock oldBlock = mCachedXmlBlocks[i];
+                if (oldBlock != null) {
+                    oldBlock.close();
+                }
+                mCachedXmlBlocks[i] = null;
+            }
+        }
+    }
+
+    /**
+     * Start preloading of resource data using this Resources object.  Only
+     * for use by the zygote process for loading common system resources.
+     * {@hide}
+     */
+    public final void startPreloading() {
+        synchronized (mSync) {
+            if (mPreloaded) {
+                throw new IllegalStateException("Resources already preloaded");
+            }
+            mPreloaded = true;
+            mPreloading = true;
+        }
+    }
+    
+    /**
+     * Called by zygote when it is done preloading resources, to change back
+     * to normal Resources operation.
+     */
+    public final void finishPreloading() {
+        if (mPreloading) {
+            mPreloading = false;
+            flushLayoutCache();
+        }
+    }
+    
+    /*package*/ Drawable loadDrawable(TypedValue value, int id)
+            throws NotFoundException {
+
+        if (TRACE_FOR_PRELOAD) {
+            // Log only framework resources
+            if ((id >>> 24) == 0x1) {
+                final String name = getResourceName(id);
+                if (name != null) android.util.Log.d("PreloadDrawable", name);
+            }
+        }
+
+        final int key = (value.assetCookie << 24) | value.data;
+        Drawable dr = getCachedDrawable(key);
+
+        if (dr != null) {
+            return dr;
+        }
+
+        Drawable.ConstantState cs = mPreloadedDrawables.get(key);
+        if (cs != null) {
+            dr = cs.newDrawable();
+        } else {
+            if (value.type >= TypedValue.TYPE_FIRST_COLOR_INT &&
+                    value.type <= TypedValue.TYPE_LAST_COLOR_INT) {
+                dr = new ColorDrawable(value.data);
+            }
+
+            if (dr == null) {
+                if (value.string == null) {
+                    throw new NotFoundException(
+                            "Resource is not a Drawable (color or path): " + value);
+                }
+
+                String file = value.string.toString();
+
+                if (DEBUG_LOAD) Log.v(TAG, "Loading drawable for cookie "
+                        + value.assetCookie + ": " + file);
+
+                if (file.endsWith(".xml")) {
+                    try {
+                        XmlResourceParser rp = loadXmlResourceParser(
+                                file, id, value.assetCookie, "drawable");
+                        dr = Drawable.createFromXml(this, rp);
+                        rp.close();
+                    } catch (Exception e) {
+                        NotFoundException rnf = new NotFoundException(
+                            "File " + file + " from drawable resource ID #0x"
+                            + Integer.toHexString(id));
+                        rnf.initCause(e);
+                        throw rnf;
+                    }
+
+                } else {
+                    try {
+                        InputStream is = mAssets.openNonAsset(
+                                value.assetCookie, file, AssetManager.ACCESS_BUFFER);
+        //                System.out.println("Opened file " + file + ": " + is);
+                        dr = Drawable.createFromResourceStream(this, value, is, file);
+                        is.close();
+        //                System.out.println("Created stream: " + dr);
+                    } catch (Exception e) {
+                        NotFoundException rnf = new NotFoundException(
+                            "File " + file + " from drawable resource ID #0x"
+                            + Integer.toHexString(id));
+                        rnf.initCause(e);
+                        throw rnf;
+                    }
+                }
+            }
+        }
+
+        if (dr != null) {
+            dr.setChangingConfigurations(value.changingConfigurations);
+            cs = dr.getConstantState();
+            if (cs != null) {
+                if (mPreloading) {
+                    mPreloadedDrawables.put(key, cs);
+                } else {
+                    synchronized (mTmpValue) {
+                        //Log.i(TAG, "Saving cached drawable @ #" +
+                        //        Integer.toHexString(key.intValue())
+                        //        + " in " + this + ": " + cs);
+                        mDrawableCache.put(key, new WeakReference<Drawable.ConstantState>(cs));
+                    }
+                }
+            }
+        }
+
+        return dr;
+    }
+
+    private Drawable getCachedDrawable(int key) {
+        synchronized (mTmpValue) {
+            WeakReference<Drawable.ConstantState> wr = mDrawableCache.get(key);
+            if (wr != null) {   // we have the key
+                Drawable.ConstantState entry = wr.get();
+                if (entry != null) {
+                    //Log.i(TAG, "Returning cached drawable @ #" +
+                    //        Integer.toHexString(((Integer)key).intValue())
+                    //        + " in " + this + ": " + entry);
+                    return entry.newDrawable();
+                }
+                else {  // our entry has been purged
+                    mDrawableCache.delete(key);
+                }
+            }
+        }
+        return null;
+    }
+
+    /*package*/ ColorStateList loadColorStateList(TypedValue value, int id)
+            throws NotFoundException {
+        if (TRACE_FOR_PRELOAD) {
+            // Log only framework resources
+            if ((id >>> 24) == 0x1) {
+                final String name = getResourceName(id);
+                if (name != null) android.util.Log.d("PreloadColorStateList", name);
+            }
+        }
+
+        final int key = (value.assetCookie << 24) | value.data;
+
+        ColorStateList csl;
+
+        if (value.type >= TypedValue.TYPE_FIRST_COLOR_INT &&
+                value.type <= TypedValue.TYPE_LAST_COLOR_INT) {
+
+            csl = mPreloadedColorStateLists.get(key);
+            if (csl != null) {
+                return csl;
+            }
+
+            csl = ColorStateList.valueOf(value.data);
+            if (mPreloading) {
+                mPreloadedColorStateLists.put(key, csl);
+            }
+
+            return csl;
+        }
+
+        csl = getCachedColorStateList(key);
+        if (csl != null) {
+            return csl;
+        }
+
+        csl = mPreloadedColorStateLists.get(key);
+        if (csl != null) {
+            return csl;
+        }
+
+        if (value.string == null) {
+            throw new NotFoundException(
+                    "Resource is not a ColorStateList (color or path): " + value);
+        }
+        
+        String file = value.string.toString();
+
+        if (file.endsWith(".xml")) {
+            try {
+                XmlResourceParser rp = loadXmlResourceParser(
+                        file, id, value.assetCookie, "colorstatelist"); 
+                csl = ColorStateList.createFromXml(this, rp);
+                rp.close();
+            } catch (Exception e) {
+                NotFoundException rnf = new NotFoundException(
+                    "File " + file + " from color state list resource ID #0x"
+                    + Integer.toHexString(id));
+                rnf.initCause(e);
+                throw rnf;
+            }
+        } else {
+            throw new NotFoundException(
+                    "File " + file + " from drawable resource ID #0x"
+                    + Integer.toHexString(id) + ": .xml extension required");
+        }
+
+        if (csl != null) {
+            if (mPreloading) {
+                mPreloadedColorStateLists.put(key, csl);
+            } else {
+                synchronized (mTmpValue) {
+                    //Log.i(TAG, "Saving cached color state list @ #" +
+                    //        Integer.toHexString(key.intValue())
+                    //        + " in " + this + ": " + csl);
+                    mColorStateListCache.put(
+                        key, new WeakReference<ColorStateList>(csl));
+                }
+            }
+        }
+
+        return csl;
+    }
+
+    private ColorStateList getCachedColorStateList(int key) {
+        synchronized (mTmpValue) {
+            WeakReference<ColorStateList> wr = mColorStateListCache.get(key);
+            if (wr != null) {   // we have the key
+                ColorStateList entry = wr.get();
+                if (entry != null) {
+                    //Log.i(TAG, "Returning cached color state list @ #" +
+                    //        Integer.toHexString(((Integer)key).intValue())
+                    //        + " in " + this + ": " + entry);
+                    return entry;
+                }
+                else {  // our entry has been purged
+                    mColorStateListCache.delete(key);
+                }
+            }
+        }
+        return null;
+    }
+
+    /*package*/ XmlResourceParser loadXmlResourceParser(int id, String type)
+            throws NotFoundException {
+        synchronized (mTmpValue) {
+            TypedValue value = mTmpValue;
+            getValue(id, value, true);
+            if (value.type == TypedValue.TYPE_STRING) {
+                return loadXmlResourceParser(value.string.toString(), id,
+                        value.assetCookie, type);
+            }
+            throw new NotFoundException(
+                    "Resource ID #0x" + Integer.toHexString(id) + " type #0x"
+                    + Integer.toHexString(value.type) + " is not valid");
+        }
+    }
+    
+    /*package*/ XmlResourceParser loadXmlResourceParser(String file, int id,
+            int assetCookie, String type) throws NotFoundException {
+        if (id != 0) {
+            try {
+                // These may be compiled...
+                synchronized (mCachedXmlBlockIds) {
+                    // First see if this block is in our cache.
+                    final int num = mCachedXmlBlockIds.length;
+                    for (int i=0; i<num; i++) {
+                        if (mCachedXmlBlockIds[i] == id) {
+                            //System.out.println("**** REUSING XML BLOCK!  id="
+                            //                   + id + ", index=" + i);
+                            return mCachedXmlBlocks[i].newParser();
+                        }
+                    }
+
+                    // Not in the cache, create a new block and put it at
+                    // the next slot in the cache.
+                    XmlBlock block = mAssets.openXmlBlockAsset(
+                            assetCookie, file);
+                    if (block != null) {
+                        int pos = mLastCachedXmlBlockIndex+1;
+                        if (pos >= num) pos = 0;
+                        mLastCachedXmlBlockIndex = pos;
+                        XmlBlock oldBlock = mCachedXmlBlocks[pos];
+                        if (oldBlock != null) {
+                            oldBlock.close();
+                        }
+                        mCachedXmlBlockIds[pos] = id;
+                        mCachedXmlBlocks[pos] = block;
+                        //System.out.println("**** CACHING NEW XML BLOCK!  id="
+                        //                   + id + ", index=" + pos);
+                        return block.newParser();
+                    }
+                }
+            } catch (Exception e) {
+                NotFoundException rnf = new NotFoundException(
+                        "File " + file + " from xml type " + type + " resource ID #0x"
+                        + Integer.toHexString(id));
+                rnf.initCause(e);
+                throw rnf;
+            }
+        }
+
+        throw new NotFoundException(
+                "File " + file + " from xml type " + type + " resource ID #0x"
+                + Integer.toHexString(id));
+    }
+
+    private TypedArray getCachedStyledAttributes(int len) {
+        synchronized (mTmpValue) {
+            TypedArray attrs = mCachedStyledAttributes;
+            if (attrs != null) {
+                mCachedStyledAttributes = null;
+
+                attrs.mLength = len;
+                int fullLen = len * AssetManager.STYLE_NUM_ENTRIES;
+                if (attrs.mData.length >= fullLen) {
+                    return attrs;
+                }
+                attrs.mData = new int[fullLen];
+                attrs.mIndices = new int[1+len];
+                return attrs;
+            }
+            return new TypedArray(this,
+                    new int[len*AssetManager.STYLE_NUM_ENTRIES],
+                    new int[1+len], len);
+        }
+    }
+
+    private Resources() {
+        mAssets = AssetManager.getSystem();
+        // NOTE: Intentionally leaving this uninitialized (all values set
+        // to zero), so that anyone who tries to do something that requires
+        // metrics will get a very wrong value.
+        mConfiguration.setToDefaults();
+        mMetrics.setToDefaults();
+        updateConfiguration(null, null);
+        mAssets.ensureStringBlocks();
+    }
+}
+
diff --git a/core/java/android/content/res/StringBlock.java b/core/java/android/content/res/StringBlock.java
new file mode 100644
index 0000000..e684cb8
--- /dev/null
+++ b/core/java/android/content/res/StringBlock.java
@@ -0,0 +1,395 @@
+/*
+ * 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 android.content.res;
+
+import android.text.*;
+import android.text.style.*;
+import android.util.Config;
+import android.util.Log;
+import android.util.SparseArray;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.graphics.Typeface;
+import com.android.internal.util.XmlUtils;
+
+/**
+ * Conveniences for retrieving data out of a compiled string resource.
+ *
+ * {@hide}
+ */
+final class StringBlock {
+    private static final String TAG = "AssetManager";
+    private static final boolean localLOGV = Config.LOGV || false;
+
+    private final int mNative;
+    private final boolean mUseSparse;
+    private final boolean mOwnsNative;
+    private CharSequence[] mStrings;
+    private SparseArray<CharSequence> mSparseStrings;
+    StyleIDs mStyleIDs = null;
+
+    public StringBlock(byte[] data, boolean useSparse) {
+        mNative = nativeCreate(data, 0, data.length);
+        mUseSparse = useSparse;
+        mOwnsNative = true;
+        if (localLOGV) Log.v(TAG, "Created string block " + this
+                + ": " + nativeGetSize(mNative));
+    }
+
+    public StringBlock(byte[] data, int offset, int size, boolean useSparse) {
+        mNative = nativeCreate(data, offset, size);
+        mUseSparse = useSparse;
+        mOwnsNative = true;
+        if (localLOGV) Log.v(TAG, "Created string block " + this
+                + ": " + nativeGetSize(mNative));
+    }
+
+    public CharSequence get(int idx) {
+        synchronized (this) {
+            if (mStrings != null) {
+                CharSequence res = mStrings[idx];
+                if (res != null) {
+                    return res;
+                }
+            } else if (mSparseStrings != null) {
+                CharSequence res = mSparseStrings.get(idx);
+                if (res != null) {
+                    return res;
+                }
+            } else {
+                final int num = nativeGetSize(mNative);
+                if (mUseSparse && num > 250) {
+                    mSparseStrings = new SparseArray<CharSequence>();
+                } else {
+                    mStrings = new CharSequence[num];
+                }
+            }
+            String str = nativeGetString(mNative, idx);
+            CharSequence res = str;
+            int[] style = nativeGetStyle(mNative, idx);
+            if (localLOGV) Log.v(TAG, "Got string: " + str);
+            if (localLOGV) Log.v(TAG, "Got styles: " + style);
+            if (style != null) {
+                if (mStyleIDs == null) {
+                    mStyleIDs = new StyleIDs();
+                    mStyleIDs.boldId = nativeIndexOfString(mNative, "b");
+                    mStyleIDs.italicId = nativeIndexOfString(mNative, "i");
+                    mStyleIDs.underlineId = nativeIndexOfString(mNative, "u");
+                    mStyleIDs.ttId = nativeIndexOfString(mNative, "tt");
+                    mStyleIDs.bigId = nativeIndexOfString(mNative, "big");
+                    mStyleIDs.smallId = nativeIndexOfString(mNative, "small");
+                    mStyleIDs.supId = nativeIndexOfString(mNative, "sup");
+                    mStyleIDs.subId = nativeIndexOfString(mNative, "sub");
+                    mStyleIDs.strikeId = nativeIndexOfString(mNative, "strike");
+                    mStyleIDs.listItemId = nativeIndexOfString(mNative, "li");
+                    mStyleIDs.marqueeId = nativeIndexOfString(mNative, "marquee");
+
+                    if (localLOGV) Log.v(TAG, "BoldId=" + mStyleIDs.boldId
+                            + ", ItalicId=" + mStyleIDs.italicId
+                            + ", UnderlineId=" + mStyleIDs.underlineId);
+                }
+
+                res = applyStyles(str, style, mStyleIDs);
+            }
+            if (mStrings != null) mStrings[idx] = res;
+            else mSparseStrings.put(idx, res);
+            return res;
+        }
+    }
+
+    protected void finalize() throws Throwable {
+        if (mOwnsNative) {
+            nativeDestroy(mNative);
+        }
+    }
+
+    static final class StyleIDs {
+        private int boldId;
+        private int italicId;
+        private int underlineId;
+        private int ttId;
+        private int bigId;
+        private int smallId;
+        private int subId;
+        private int supId;
+        private int strikeId;
+        private int listItemId;
+        private int marqueeId;
+    }
+
+    private CharSequence applyStyles(String str, int[] style, StyleIDs ids) {
+        if (style.length == 0)
+            return str;
+
+        SpannableString buffer = new SpannableString(str);
+        int i=0;
+        while (i < style.length) {
+            int type = style[i];
+            if (localLOGV) Log.v(TAG, "Applying style span id=" + type
+                    + ", start=" + style[i+1] + ", end=" + style[i+2]);
+
+
+            if (type == ids.boldId) {
+                buffer.setSpan(new StyleSpan(Typeface.BOLD),
+                               style[i+1], style[i+2]+1,
+                               Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+            } else if (type == ids.italicId) {
+                buffer.setSpan(new StyleSpan(Typeface.ITALIC),
+                               style[i+1], style[i+2]+1,
+                               Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+            } else if (type == ids.underlineId) {
+                buffer.setSpan(new UnderlineSpan(),
+                               style[i+1], style[i+2]+1,
+                               Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+            } else if (type == ids.ttId) {
+                buffer.setSpan(new TypefaceSpan("monospace"),
+                               style[i+1], style[i+2]+1,
+                               Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+            } else if (type == ids.bigId) {
+                buffer.setSpan(new RelativeSizeSpan(1.25f),
+                               style[i+1], style[i+2]+1,
+                               Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+            } else if (type == ids.smallId) {
+                buffer.setSpan(new RelativeSizeSpan(0.8f),
+                               style[i+1], style[i+2]+1,
+                               Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+            } else if (type == ids.subId) {
+                buffer.setSpan(new SubscriptSpan(),
+                               style[i+1], style[i+2]+1,
+                               Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+            } else if (type == ids.supId) {
+                buffer.setSpan(new SuperscriptSpan(),
+                               style[i+1], style[i+2]+1,
+                               Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+            } else if (type == ids.strikeId) {
+                buffer.setSpan(new StrikethroughSpan(),
+                               style[i+1], style[i+2]+1,
+                               Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+            } else if (type == ids.listItemId) {
+                addParagraphSpan(buffer, new BulletSpan(10),
+                                style[i+1], style[i+2]+1);
+            } else if (type == ids.marqueeId) {
+                buffer.setSpan(TextUtils.TruncateAt.MARQUEE,
+                               style[i+1], style[i+2]+1,
+                               Spannable.SPAN_INCLUSIVE_INCLUSIVE);
+            } else {
+                String tag = nativeGetString(mNative, type);
+
+                if (tag.startsWith("font;")) {
+                    String sub;
+
+                    sub = subtag(tag, ";height=");
+                    if (sub != null) {
+                        int size = Integer.parseInt(sub);
+                        addParagraphSpan(buffer, new Height(size),
+                                       style[i+1], style[i+2]+1);
+                    }
+
+                    sub = subtag(tag, ";size=");
+                    if (sub != null) {
+                        int size = Integer.parseInt(sub);
+                        buffer.setSpan(new AbsoluteSizeSpan(size),
+                                       style[i+1], style[i+2]+1,
+                                       Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+                    }
+
+                    sub = subtag(tag, ";fgcolor=");
+                    if (sub != null) {
+                        int color = XmlUtils.convertValueToUnsignedInt(sub, -1);
+                        buffer.setSpan(new ForegroundColorSpan(color),
+                                       style[i+1], style[i+2]+1,
+                                       Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+                    }
+
+                    sub = subtag(tag, ";bgcolor=");
+                    if (sub != null) {
+                        int color = XmlUtils.convertValueToUnsignedInt(sub, -1);
+                        buffer.setSpan(new BackgroundColorSpan(color),
+                                       style[i+1], style[i+2]+1,
+                                       Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+                    }
+                } else if (tag.startsWith("a;")) {
+                    String sub;
+
+                    sub = subtag(tag, ";href=");
+                    if (sub != null) {
+                        buffer.setSpan(new URLSpan(sub),
+                                       style[i+1], style[i+2]+1,
+                                       Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+                    }
+                } else if (tag.startsWith("annotation;")) {
+                    int len = tag.length();
+                    int next;
+
+                    for (int t = tag.indexOf(';'); t < len; t = next) {
+                        int eq = tag.indexOf('=', t);
+                        if (eq < 0) {
+                            break;
+                        }
+
+                        next = tag.indexOf(';', eq);
+                        if (next < 0) {
+                            next = len;
+                        }
+
+                        String key = tag.substring(t + 1, eq);
+                        String value = tag.substring(eq + 1, next);
+
+                        buffer.setSpan(new Annotation(key, value),
+                                       style[i+1], style[i+2]+1,
+                                       Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+                    }
+                }
+            }
+
+            i += 3;
+        }
+        return new SpannedString(buffer);
+    }
+
+    /**
+     * If a translator has messed up the edges of paragraph-level markup,
+     * fix it to actually cover the entire paragraph that it is attached to
+     * instead of just whatever range they put it on.
+     */
+    private static void addParagraphSpan(Spannable buffer, Object what,
+                                         int start, int end) {
+        int len = buffer.length();
+
+        if (start != 0 && start != len && buffer.charAt(start - 1) != '\n') {
+            for (start--; start > 0; start--) {
+                if (buffer.charAt(start - 1) == '\n') {
+                    break;
+                }
+            }
+        }
+
+        if (end != 0 && end != len && buffer.charAt(end - 1) != '\n') {
+            for (end++; end < len; end++) {
+                if (buffer.charAt(end - 1) == '\n') {
+                    break;
+                }
+            }
+        }
+
+        buffer.setSpan(what, start, end, Spannable.SPAN_PARAGRAPH);
+    }
+
+    private static String subtag(String full, String attribute) {
+        int start = full.indexOf(attribute);
+        if (start < 0) {
+            return null;
+        }
+
+        start += attribute.length();
+        int end = full.indexOf(';', start);
+
+        if (end < 0) {
+            return full.substring(start);
+        } else {
+            return full.substring(start, end);
+        }
+    }
+
+    /**
+     * Forces the text line to be the specified height, shrinking/stretching
+     * the ascent if possible, or the descent if shrinking the ascent further
+     * will make the text unreadable.
+     */
+    private static class Height implements LineHeightSpan {
+        private int mSize;
+        private static float sProportion = 0;
+
+        public Height(int size) {
+            mSize = size;
+        }
+
+        public void chooseHeight(CharSequence text, int start, int end,
+                                 int spanstartv, int v,
+                                 Paint.FontMetricsInt fm) {
+            if (fm.bottom - fm.top < mSize) {
+                fm.top = fm.bottom - mSize;
+                fm.ascent = fm.ascent - mSize;
+            } else {
+                if (sProportion == 0) {
+                    /*
+                     * Calculate what fraction of the nominal ascent
+                     * the height of a capital letter actually is,
+                     * so that we won't reduce the ascent to less than
+                     * that unless we absolutely have to.
+                     */
+
+                    Paint p = new Paint();
+                    p.setTextSize(100);
+                    Rect r = new Rect();
+                    p.getTextBounds("ABCDEFG", 0, 7, r);
+
+                    sProportion = (r.top) / p.ascent();
+                }
+
+                int need = (int) Math.ceil(-fm.top * sProportion);
+
+                if (mSize - fm.descent >= need) {
+                    /*
+                     * It is safe to shrink the ascent this much.
+                     */
+
+                    fm.top = fm.bottom - mSize;
+                    fm.ascent = fm.descent - mSize;
+                } else if (mSize >= need) {
+                    /*
+                     * We can't show all the descent, but we can at least
+                     * show all the ascent.
+                     */
+
+                    fm.top = fm.ascent = -need;
+                    fm.bottom = fm.descent = fm.top + mSize;
+                } else {
+                    /*
+                     * Show as much of the ascent as we can, and no descent.
+                     */
+
+                    fm.top = fm.ascent = -mSize;
+                    fm.bottom = fm.descent = 0;
+                }
+            }
+        }
+    }
+
+    /**
+     * Create from an existing string block native object.  This is
+     * -extremely- dangerous -- only use it if you absolutely know what you
+     *  are doing!  The given native object must exist for the entire lifetime
+     *  of this newly creating StringBlock.
+     */
+    StringBlock(int obj, boolean useSparse) {
+        mNative = obj;
+        mUseSparse = useSparse;
+        mOwnsNative = false;
+        if (localLOGV) Log.v(TAG, "Created string block " + this
+                + ": " + nativeGetSize(mNative));
+    }
+
+    private static final native int nativeCreate(byte[] data,
+                                                 int offset,
+                                                 int size);
+    private static final native int nativeGetSize(int obj);
+    private static final native String nativeGetString(int obj, int idx);
+    private static final native int[] nativeGetStyle(int obj, int idx);
+    private static final native int nativeIndexOfString(int obj, String str);
+    private static final native void nativeDestroy(int obj);
+}
diff --git a/core/java/android/content/res/TypedArray.java b/core/java/android/content/res/TypedArray.java
new file mode 100644
index 0000000..3a32c03
--- /dev/null
+++ b/core/java/android/content/res/TypedArray.java
@@ -0,0 +1,688 @@
+package android.content.res;
+
+import android.graphics.drawable.Drawable;
+import android.util.AttributeSet;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import android.util.TypedValue;
+import com.android.internal.util.XmlUtils;
+
+import java.util.Arrays;
+
+/**
+ * Container for an array of values that were retrieved with
+ * {@link Resources.Theme#obtainStyledAttributes(AttributeSet, int[], int, int)}
+ * or {@link Resources#obtainAttributes}.  Be
+ * sure to call {@link #recycle} when done with them.
+ * 
+ * The indices used to retrieve values from this structure correspond to
+ * the positions of the attributes given to obtainStyledAttributes.
+ */
+public class TypedArray {
+    private final Resources mResources;
+    /*package*/ XmlBlock.Parser mXml;
+    /*package*/ int[] mRsrcs;
+    /*package*/ int[] mData;
+    /*package*/ int[] mIndices;
+    /*package*/ int mLength;
+    private TypedValue mValue = new TypedValue();
+   
+    /**
+     * Return the number of values in this array.
+     */
+    public int length() {
+        return mLength;
+    }
+    
+    /**
+     * Return the number of indices in the array that actually have data.
+     */
+    public int getIndexCount() {
+        return mIndices[0];
+    }
+    
+    /**
+     * Return an index in the array that has data.
+     * 
+     * @param at The index you would like to returned, ranging from 0 to
+     * {@link #getIndexCount()}.
+     * 
+     * @return The index at the given offset, which can be used with
+     * {@link #getValue} and related APIs.
+     */
+    public int getIndex(int at) {
+        return mIndices[1+at];
+    }
+    
+    /**
+     * Return the Resources object this array was loaded from.
+     */
+    public Resources getResources() {
+        return mResources;
+    }
+    
+    /**
+     * Retrieve the styled string value for the attribute at <var>index</var>.
+     * 
+     * @param index Index of attribute to retrieve.
+     * 
+     * @return CharSequence holding string data.  May be styled.  Returns 
+     *         null if the attribute is not defined.
+     */
+    public CharSequence getText(int index) {
+        index *= AssetManager.STYLE_NUM_ENTRIES;
+        final int[] data = mData;
+        final int type = data[index+AssetManager.STYLE_TYPE];
+        if (type == TypedValue.TYPE_NULL) {
+            return null;
+        } else if (type == TypedValue.TYPE_STRING) {
+            return loadStringValueAt(index);
+        }
+
+        TypedValue v = mValue;
+        if (getValueAt(index, v)) {
+            Log.w(Resources.TAG, "Converting to string: " + v);
+            return v.coerceToString();
+        }
+        Log.w(Resources.TAG, "getString of bad type: 0x"
+              + Integer.toHexString(type));
+        return null;
+    }
+
+    /**
+     * Retrieve the string value for the attribute at <var>index</var>.
+     * 
+     * @param index Index of attribute to retrieve.
+     * 
+     * @return String holding string data.  Any styling information is
+     * removed.  Returns null if the attribute is not defined.
+     */
+    public String getString(int index) {
+        index *= AssetManager.STYLE_NUM_ENTRIES;
+        final int[] data = mData;
+        final int type = data[index+AssetManager.STYLE_TYPE];
+        if (type == TypedValue.TYPE_NULL) {
+            return null;
+        } else if (type == TypedValue.TYPE_STRING) {
+            return loadStringValueAt(index).toString();
+        }
+
+        TypedValue v = mValue;
+        if (getValueAt(index, v)) {
+            Log.w(Resources.TAG, "Converting to string: " + v);
+            CharSequence cs = v.coerceToString();
+            return cs != null ? cs.toString() : null;
+        }
+        Log.w(Resources.TAG, "getString of bad type: 0x"
+              + Integer.toHexString(type));
+        return null;
+    }
+
+    /**
+     * Retrieve the string value for the attribute at <var>index</var>, but
+     * only if that string comes from an immediate value in an XML file.  That
+     * is, this does not allow references to string resources, string
+     * attributes, or conversions from other types.  As such, this method
+     * will only return strings for TypedArray objects that come from
+     * attributes in an XML file.
+     * 
+     * @param index Index of attribute to retrieve.
+     * 
+     * @return String holding string data.  Any styling information is
+     * removed.  Returns null if the attribute is not defined or is not
+     * an immediate string value.
+     */
+    public String getNonResourceString(int index) {
+        index *= AssetManager.STYLE_NUM_ENTRIES;
+        final int[] data = mData;
+        final int type = data[index+AssetManager.STYLE_TYPE];
+        if (type == TypedValue.TYPE_STRING) {
+            final int cookie = data[index+AssetManager.STYLE_ASSET_COOKIE];
+            if (cookie < 0) {
+                return mXml.getPooledString(
+                    data[index+AssetManager.STYLE_DATA]).toString();
+            }
+        }
+        return null;
+    }
+    
+    /**
+     * Retrieve the boolean value for the attribute at <var>index</var>.
+     * 
+     * @param index Index of attribute to retrieve.
+     * @param defValue Value to return if the attribute is not defined.
+     * 
+     * @return Attribute boolean value, or defValue if not defined.
+     */
+    public boolean getBoolean(int index, boolean defValue) {
+        index *= AssetManager.STYLE_NUM_ENTRIES;
+        final int[] data = mData;
+        final int type = data[index+AssetManager.STYLE_TYPE];
+        if (type == TypedValue.TYPE_NULL) {
+            return defValue;
+        } else if (type >= TypedValue.TYPE_FIRST_INT
+            && type <= TypedValue.TYPE_LAST_INT) {
+            return data[index+AssetManager.STYLE_DATA] != 0;
+        }
+
+        TypedValue v = mValue;
+        if (getValueAt(index, v)) {
+            Log.w(Resources.TAG, "Converting to boolean: " + v);
+            return XmlUtils.convertValueToBoolean(
+                v.coerceToString(), defValue);
+        }
+        Log.w(Resources.TAG, "getBoolean of bad type: 0x"
+              + Integer.toHexString(type));
+        return defValue;
+    }
+
+    /**
+     * Retrieve the integer value for the attribute at <var>index</var>.
+     * 
+     * @param index Index of attribute to retrieve.
+     * @param defValue Value to return if the attribute is not defined.
+     * 
+     * @return Attribute int value, or defValue if not defined.
+     */
+    public int getInt(int index, int defValue) {
+        index *= AssetManager.STYLE_NUM_ENTRIES;
+        final int[] data = mData;
+        final int type = data[index+AssetManager.STYLE_TYPE];
+        if (type == TypedValue.TYPE_NULL) {
+            return defValue;
+        } else if (type >= TypedValue.TYPE_FIRST_INT
+            && type <= TypedValue.TYPE_LAST_INT) {
+            return data[index+AssetManager.STYLE_DATA];
+        }
+
+        TypedValue v = mValue;
+        if (getValueAt(index, v)) {
+            Log.w(Resources.TAG, "Converting to int: " + v);
+            return XmlUtils.convertValueToInt(
+                v.coerceToString(), defValue);
+        }
+        Log.w(Resources.TAG, "getInt of bad type: 0x"
+              + Integer.toHexString(type));
+        return defValue;
+    }
+
+    /**
+     * Retrieve the float value for the attribute at <var>index</var>.
+     * 
+     * @param index Index of attribute to retrieve.
+     * 
+     * @return Attribute float value, or defValue if not defined..
+     */
+    public float getFloat(int index, float defValue) {
+        index *= AssetManager.STYLE_NUM_ENTRIES;
+        final int[] data = mData;
+        final int type = data[index+AssetManager.STYLE_TYPE];
+        if (type == TypedValue.TYPE_NULL) {
+            return defValue;
+        } else if (type == TypedValue.TYPE_FLOAT) {
+            return Float.intBitsToFloat(data[index+AssetManager.STYLE_DATA]);
+        } else if (type >= TypedValue.TYPE_FIRST_INT
+            && type <= TypedValue.TYPE_LAST_INT) {
+            return data[index+AssetManager.STYLE_DATA];
+        }
+
+        TypedValue v = mValue;
+        if (getValueAt(index, v)) {
+            Log.w(Resources.TAG, "Converting to float: " + v);
+            CharSequence str = v.coerceToString();
+            if (str != null) {
+                return Float.parseFloat(str.toString());
+            }
+        }
+        Log.w(Resources.TAG, "getFloat of bad type: 0x"
+              + Integer.toHexString(type));
+        return defValue;
+    }
+    
+    /**
+     * Retrieve the color value for the attribute at <var>index</var>.  If
+     * the attribute references a color resource holding a complex
+     * {@link android.content.res.ColorStateList}, then the default color from
+     * the set is returned.
+     * 
+     * @param index Index of attribute to retrieve.
+     * @param defValue Value to return if the attribute is not defined or
+     *                 not a resource.
+     * 
+     * @return Attribute color value, or defValue if not defined.
+     */
+    public int getColor(int index, int defValue) {
+        index *= AssetManager.STYLE_NUM_ENTRIES;
+        final int[] data = mData;
+        final int type = data[index+AssetManager.STYLE_TYPE];
+        if (type == TypedValue.TYPE_NULL) {
+            return defValue;
+        } else if (type >= TypedValue.TYPE_FIRST_INT
+            && type <= TypedValue.TYPE_LAST_INT) {
+            return data[index+AssetManager.STYLE_DATA];
+        } else if (type == TypedValue.TYPE_STRING) {
+            final TypedValue value = mValue;
+            if (getValueAt(index, value)) {
+                ColorStateList csl = mResources.loadColorStateList(
+                        value, value.resourceId);
+                return csl.getDefaultColor();
+            }
+            return defValue;
+        }
+
+        throw new UnsupportedOperationException("Can't convert to color: type=0x"
+                + Integer.toHexString(type));
+    }
+
+    /**
+     * Retrieve the ColorStateList for the attribute at <var>index</var>.
+     * The value may be either a single solid color or a reference to
+     * a color or complex {@link android.content.res.ColorStateList} description.
+     * 
+     * @param index Index of attribute to retrieve.
+     * 
+     * @return ColorStateList for the attribute, or null if not defined.
+     */
+    public ColorStateList getColorStateList(int index) {
+        final TypedValue value = mValue;
+        if (getValueAt(index*AssetManager.STYLE_NUM_ENTRIES, value)) {
+            return mResources.loadColorStateList(value, value.resourceId);
+        }
+        return null;
+    }
+
+    /**
+     * Retrieve the integer value for the attribute at <var>index</var>.
+     * 
+     * @param index Index of attribute to retrieve.
+     * @param defValue Value to return if the attribute is not defined or
+     *                 not a resource.
+     * 
+     * @return Attribute integer value, or defValue if not defined.
+     */
+    public int getInteger(int index, int defValue) {
+        index *= AssetManager.STYLE_NUM_ENTRIES;
+        final int[] data = mData;
+        final int type = data[index+AssetManager.STYLE_TYPE];
+        if (type == TypedValue.TYPE_NULL) {
+            return defValue;
+        } else if (type >= TypedValue.TYPE_FIRST_INT
+            && type <= TypedValue.TYPE_LAST_INT) {
+            return data[index+AssetManager.STYLE_DATA];
+        }
+
+        throw new UnsupportedOperationException("Can't convert to integer: type=0x"
+                + Integer.toHexString(type));
+    }
+
+    /**
+     * Retrieve a dimensional unit attribute at <var>index</var>.  Unit 
+     * conversions are based on the current {@link DisplayMetrics} 
+     * associated with the resources this {@link TypedArray} object 
+     * came from. 
+     * 
+     * @param index Index of attribute to retrieve.
+     * @param defValue Value to return if the attribute is not defined or
+     *                 not a resource.
+     * 
+     * @return Attribute dimension value multiplied by the appropriate 
+     * metric, or defValue if not defined.
+     * 
+     * @see #getDimensionPixelOffset
+     * @see #getDimensionPixelSize
+     */
+    public float getDimension(int index, float defValue) {
+        index *= AssetManager.STYLE_NUM_ENTRIES;
+        final int[] data = mData;
+        final int type = data[index+AssetManager.STYLE_TYPE];
+        if (type == TypedValue.TYPE_NULL) {
+            return defValue;
+        } else if (type == TypedValue.TYPE_DIMENSION) {
+            return TypedValue.complexToDimension(
+                data[index+AssetManager.STYLE_DATA], mResources.mMetrics);
+        }
+
+        throw new UnsupportedOperationException("Can't convert to dimension: type=0x"
+                + Integer.toHexString(type));
+    }
+
+    /**
+     * Retrieve a dimensional unit attribute at <var>index</var> for use
+     * as an offset in raw pixels.  This is the same as
+     * {@link #getDimension}, except the returned value is converted to
+     * integer pixels for you.  An offset conversion involves simply
+     * truncating the base value to an integer.
+     * 
+     * @param index Index of attribute to retrieve.
+     * @param defValue Value to return if the attribute is not defined or
+     *                 not a resource.
+     * 
+     * @return Attribute dimension value multiplied by the appropriate 
+     * metric and truncated to integer pixels, or defValue if not defined.
+     * 
+     * @see #getDimension
+     * @see #getDimensionPixelSize
+     */
+    public int getDimensionPixelOffset(int index, int defValue) {
+        index *= AssetManager.STYLE_NUM_ENTRIES;
+        final int[] data = mData;
+        final int type = data[index+AssetManager.STYLE_TYPE];
+        if (type == TypedValue.TYPE_NULL) {
+            return defValue;
+        } else if (type == TypedValue.TYPE_DIMENSION) {
+            return TypedValue.complexToDimensionPixelOffset(
+                data[index+AssetManager.STYLE_DATA], mResources.mMetrics);
+        }
+
+        throw new UnsupportedOperationException("Can't convert to dimension: type=0x"
+                + Integer.toHexString(type));
+    }
+
+    /**
+     * Retrieve a dimensional unit attribute at <var>index</var> for use
+     * as a size in raw pixels.  This is the same as
+     * {@link #getDimension}, except the returned value is converted to
+     * integer pixels for use as a size.  A size conversion involves
+     * rounding the base value, and ensuring that a non-zero base value
+     * is at least one pixel in size.
+     * 
+     * @param index Index of attribute to retrieve.
+     * @param defValue Value to return if the attribute is not defined or
+     *                 not a resource.
+     * 
+     * @return Attribute dimension value multiplied by the appropriate 
+     * metric and truncated to integer pixels, or defValue if not defined.
+     *  
+     * @see #getDimension
+     * @see #getDimensionPixelOffset
+     */
+    public int getDimensionPixelSize(int index, int defValue) {
+        index *= AssetManager.STYLE_NUM_ENTRIES;
+        final int[] data = mData;
+        final int type = data[index+AssetManager.STYLE_TYPE];
+        if (type == TypedValue.TYPE_NULL) {
+            return defValue;
+        } else if (type == TypedValue.TYPE_DIMENSION) {
+            return TypedValue.complexToDimensionPixelSize(
+                data[index+AssetManager.STYLE_DATA], mResources.mMetrics);
+        }
+
+        throw new UnsupportedOperationException("Can't convert to dimension: type=0x"
+                + Integer.toHexString(type));
+    }
+
+    /**
+     * Special version of {@link #getDimensionPixelSize} for retrieving
+     * {@link android.view.ViewGroup}'s layout_width and layout_height
+     * attributes.  This is only here for performance reasons; applications
+     * should use {@link #getDimensionPixelSize}.
+     * 
+     * @param index Index of the attribute to retrieve.
+     * @param name Textual name of attribute for error reporting.
+     * 
+     * @return Attribute dimension value multiplied by the appropriate 
+     * metric and truncated to integer pixels.
+     */
+    public int getLayoutDimension(int index, String name) {
+        index *= AssetManager.STYLE_NUM_ENTRIES;
+        final int[] data = mData;
+        final int type = data[index+AssetManager.STYLE_TYPE];
+        if (type >= TypedValue.TYPE_FIRST_INT
+                && type <= TypedValue.TYPE_LAST_INT) {
+            return data[index+AssetManager.STYLE_DATA];
+        } else if (type == TypedValue.TYPE_DIMENSION) {
+            return TypedValue.complexToDimensionPixelSize(
+                data[index+AssetManager.STYLE_DATA], mResources.mMetrics);
+        }
+
+        throw new RuntimeException(getPositionDescription()
+                + ": You must supply a " + name + " attribute.");
+    }
+    
+    /**
+     * Special version of {@link #getDimensionPixelSize} for retrieving
+     * {@link android.view.ViewGroup}'s layout_width and layout_height
+     * attributes.  This is only here for performance reasons; applications
+     * should use {@link #getDimensionPixelSize}.
+     * 
+     * @param index Index of the attribute to retrieve.
+     * @param defValue The default value to return if this attribute is not
+     * default or contains the wrong type of data.
+     * 
+     * @return Attribute dimension value multiplied by the appropriate 
+     * metric and truncated to integer pixels.
+     */
+    public int getLayoutDimension(int index, int defValue) {
+        index *= AssetManager.STYLE_NUM_ENTRIES;
+        final int[] data = mData;
+        final int type = data[index+AssetManager.STYLE_TYPE];
+        if (type >= TypedValue.TYPE_FIRST_INT
+                && type <= TypedValue.TYPE_LAST_INT) {
+            return data[index+AssetManager.STYLE_DATA];
+        } else if (type == TypedValue.TYPE_DIMENSION) {
+            return TypedValue.complexToDimensionPixelSize(
+                data[index+AssetManager.STYLE_DATA], mResources.mMetrics);
+        }
+
+        return defValue;
+    }
+
+    /**
+     * Retrieve a fractional unit attribute at <var>index</var>.
+     * 
+     * @param index Index of attribute to retrieve. 
+     * @param base The base value of this fraction.  In other words, a 
+     *             standard fraction is multiplied by this value.
+     * @param pbase The parent base value of this fraction.  In other 
+     *             words, a parent fraction (nn%p) is multiplied by this
+     *             value.
+     * @param defValue Value to return if the attribute is not defined or
+     *                 not a resource.
+     * 
+     * @return Attribute fractional value multiplied by the appropriate 
+     * base value, or defValue if not defined. 
+     */
+    public float getFraction(int index, int base, int pbase, float defValue) {
+        index *= AssetManager.STYLE_NUM_ENTRIES;
+        final int[] data = mData;
+        final int type = data[index+AssetManager.STYLE_TYPE];
+        if (type == TypedValue.TYPE_NULL) {
+            return defValue;
+        } else if (type == TypedValue.TYPE_FRACTION) {
+            return TypedValue.complexToFraction(
+                data[index+AssetManager.STYLE_DATA], base, pbase);
+        }
+
+        throw new UnsupportedOperationException("Can't convert to fraction: type=0x"
+                + Integer.toHexString(type));
+    }
+
+    /**
+     * Retrieve the resource identifier for the attribute at
+     * <var>index</var>.  Note that attribute resource as resolved when 
+     * the overall {@link TypedArray} object is retrieved.  As a 
+     * result, this function will return the resource identifier of the 
+     * final resource value that was found, <em>not</em> necessarily the 
+     * original resource that was specified by the attribute. 
+     * 
+     * @param index Index of attribute to retrieve.
+     * @param defValue Value to return if the attribute is not defined or
+     *                 not a resource.
+     * 
+     * @return Attribute resource identifier, or defValue if not defined.
+     */
+    public int getResourceId(int index, int defValue) {
+        index *= AssetManager.STYLE_NUM_ENTRIES;
+        final int[] data = mData;
+        if (data[index+AssetManager.STYLE_TYPE] != TypedValue.TYPE_NULL) {
+            final int resid = data[index+AssetManager.STYLE_RESOURCE_ID];
+            if (resid != 0) {
+                return resid;
+            }
+        }
+        return defValue;
+    }
+
+    /**
+     * Retrieve the Drawable for the attribute at <var>index</var>.  This
+     * gets the resource ID of the selected attribute, and uses
+     * {@link Resources#getDrawable Resources.getDrawable} of the owning
+     * Resources object to retrieve its Drawable.
+     * 
+     * @param index Index of attribute to retrieve.
+     * 
+     * @return Drawable for the attribute, or null if not defined.
+     */
+    public Drawable getDrawable(int index) {
+        final TypedValue value = mValue;
+        if (getValueAt(index*AssetManager.STYLE_NUM_ENTRIES, value)) {
+            if (false) {
+                System.out.println("******************************************************************");
+                System.out.println("Got drawable resource: type="
+                                   + value.type
+                                   + " str=" + value.string
+                                   + " int=0x" + Integer.toHexString(value.data)
+                                   + " cookie=" + value.assetCookie);
+                System.out.println("******************************************************************");
+            }
+            return mResources.loadDrawable(value, value.resourceId);
+        }
+        return null;
+    }
+
+    /**
+     * Retrieve the CharSequence[] for the attribute at <var>index</var>.
+     * This gets the resource ID of the selected attribute, and uses
+     * {@link Resources#getTextArray Resources.getTextArray} of the owning
+     * Resources object to retrieve its String[].
+     * 
+     * @param index Index of attribute to retrieve.
+     * 
+     * @return CharSequence[] for the attribute, or null if not defined.
+     */
+    public CharSequence[] getTextArray(int index) {
+        final TypedValue value = mValue;
+        if (getValueAt(index*AssetManager.STYLE_NUM_ENTRIES, value)) {
+            if (false) {
+                System.out.println("******************************************************************");
+                System.out.println("Got drawable resource: type="
+                                   + value.type
+                                   + " str=" + value.string
+                                   + " int=0x" + Integer.toHexString(value.data)
+                                   + " cookie=" + value.assetCookie);
+                System.out.println("******************************************************************");
+            }
+            return mResources.getTextArray(value.resourceId);
+        }
+        return null;
+    }
+
+    /**
+     * Retrieve the raw TypedValue for the attribute at <var>index</var>.
+     * 
+     * @param index Index of attribute to retrieve.
+     * @param outValue TypedValue object in which to place the attribute's
+     *                 data.
+     * 
+     * @return Returns true if the value was retrieved, else false. 
+     */
+    public boolean getValue(int index, TypedValue outValue) {
+        return getValueAt(index*AssetManager.STYLE_NUM_ENTRIES, outValue);
+    }
+
+    /**
+     * Determines whether there is an attribute at <var>index</var>.
+     * 
+     * @param index Index of attribute to retrieve.
+     * 
+     * @return True if the attribute has a value, false otherwise.
+     */
+    public boolean hasValue(int index) {
+        index *= AssetManager.STYLE_NUM_ENTRIES;
+        final int[] data = mData;
+        final int type = data[index+AssetManager.STYLE_TYPE];
+        return type != TypedValue.TYPE_NULL;
+    }
+    
+    /**
+     * Retrieve the raw TypedValue for the attribute at <var>index</var> 
+     * and return a temporary object holding its data.  This object is only 
+     * valid until the next call on to {@link TypedArray}. 
+     * 
+     * @param index Index of attribute to retrieve.
+     * 
+     * @return Returns a TypedValue object if the attribute is defined, 
+     *         containing its data; otherwise returns null.  (You will not
+     *         receive a TypedValue whose type is TYPE_NULL.)
+     */
+    public TypedValue peekValue(int index) {
+        final TypedValue value = mValue;
+        if (getValueAt(index*AssetManager.STYLE_NUM_ENTRIES, value)) {
+            return value;
+        }
+        return null;
+    }
+
+    /**
+     * Returns a message about the parser state suitable for printing error messages.
+     */
+    public String getPositionDescription() {
+        return mXml != null ? mXml.getPositionDescription() : "<internal>";
+    }
+
+    /**
+     * Give back a previously retrieved StyledAttributes, for later re-use.
+     */
+    public void recycle() {
+        synchronized (mResources.mTmpValue) {
+            TypedArray cached = mResources.mCachedStyledAttributes;
+            if (cached == null || cached.mData.length < mData.length) {
+                mXml = null;
+                mResources.mCachedStyledAttributes = this;
+            }
+        }
+    }
+
+    private boolean getValueAt(int index, TypedValue outValue) {
+        final int[] data = mData;
+        final int type = data[index+AssetManager.STYLE_TYPE];
+        if (type == TypedValue.TYPE_NULL) {
+            return false;
+        }
+        outValue.type = type;
+        outValue.data = data[index+AssetManager.STYLE_DATA];
+        outValue.assetCookie = data[index+AssetManager.STYLE_ASSET_COOKIE];
+        outValue.resourceId = data[index+AssetManager.STYLE_RESOURCE_ID];
+        outValue.changingConfigurations = data[index+AssetManager.STYLE_CHANGING_CONFIGURATIONS];
+        if (type == TypedValue.TYPE_STRING) {
+            outValue.string = loadStringValueAt(index);
+        }
+        return true;
+    }
+
+    private CharSequence loadStringValueAt(int index) {
+        final int[] data = mData;
+        final int cookie = data[index+AssetManager.STYLE_ASSET_COOKIE];
+        if (cookie < 0) {
+            if (mXml != null) {
+                return mXml.getPooledString(
+                    data[index+AssetManager.STYLE_DATA]);
+            }
+            return null;
+        }
+        //System.out.println("Getting pooled from: " + v);
+        return mResources.mAssets.getPooledString(
+            cookie, data[index+AssetManager.STYLE_DATA]);
+    }
+
+    /*package*/ TypedArray(Resources resources, int[] data, int[] indices, int len) {
+        mResources = resources;
+        mData = data;
+        mIndices = indices;
+        mLength = len;
+    }
+
+    public String toString() {
+        return Arrays.toString(mData);
+    }
+}
\ No newline at end of file
diff --git a/core/java/android/content/res/XmlBlock.java b/core/java/android/content/res/XmlBlock.java
new file mode 100644
index 0000000..6336678
--- /dev/null
+++ b/core/java/android/content/res/XmlBlock.java
@@ -0,0 +1,515 @@
+/*
+ * 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 android.content.res;
+
+import android.util.TypedValue;
+import com.android.internal.util.XmlUtils;
+
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.Reader;
+
+/**
+ * Wrapper around a compiled XML file.
+ * 
+ * {@hide}
+ */
+final class XmlBlock {
+    private static final boolean DEBUG=false;
+
+    public XmlBlock(byte[] data) {
+        mAssets = null;
+        mNative = nativeCreate(data, 0, data.length);
+        mStrings = new StringBlock(nativeGetStringBlock(mNative), false);
+    }
+
+    public XmlBlock(byte[] data, int offset, int size) {
+        mAssets = null;
+        mNative = nativeCreate(data, offset, size);
+        mStrings = new StringBlock(nativeGetStringBlock(mNative), false);
+    }
+
+    public void close() {
+        synchronized (this) {
+            if (mOpen) {
+                mOpen = false;
+                decOpenCountLocked();
+            }
+        }
+    }
+
+    private void decOpenCountLocked() {
+        mOpenCount--;
+        if (mOpenCount == 0) {
+            nativeDestroy(mNative);
+            if (mAssets != null) {
+                mAssets.xmlBlockGone();
+            }
+        }
+    }
+
+    public XmlResourceParser newParser() {
+        synchronized (this) {
+            if (mNative != 0) {
+                return new Parser(nativeCreateParseState(mNative), this);
+            }
+            return null;
+        }
+    }
+
+    /*package*/ final class Parser implements XmlResourceParser {
+        Parser(int parseState, XmlBlock block) {
+            mParseState = parseState;
+            mBlock = block;
+            block.mOpenCount++;
+        }
+
+        public void setFeature(String name, boolean state) throws XmlPullParserException {
+            if (FEATURE_PROCESS_NAMESPACES.equals(name) && state) {
+                return;
+            }
+            if (FEATURE_REPORT_NAMESPACE_ATTRIBUTES.equals(name) && state) {
+                return;
+            }
+            throw new XmlPullParserException("Unsupported feature: " + name);
+        }
+        public boolean getFeature(String name) {
+            if (FEATURE_PROCESS_NAMESPACES.equals(name)) {
+                return true;
+            }
+            if (FEATURE_REPORT_NAMESPACE_ATTRIBUTES.equals(name)) {
+                return true;
+            }
+            return false;
+        }
+        public void setProperty(String name, Object value) throws XmlPullParserException {
+            throw new XmlPullParserException("setProperty() not supported");
+        }
+        public Object getProperty(String name) {
+            return null;
+        }
+        public void setInput(Reader in) throws XmlPullParserException {
+            throw new XmlPullParserException("setInput() not supported");
+        }
+        public void setInput(InputStream inputStream, String inputEncoding) throws XmlPullParserException {
+            throw new XmlPullParserException("setInput() not supported");
+        }
+        public void defineEntityReplacementText(String entityName, String replacementText) throws XmlPullParserException {
+            throw new XmlPullParserException("defineEntityReplacementText() not supported");
+        }
+        public String getNamespacePrefix(int pos) throws XmlPullParserException {
+            throw new XmlPullParserException("getNamespacePrefix() not supported");
+        }
+        public String getInputEncoding() {
+            return null;
+        }
+        public String getNamespace(String prefix) {
+            throw new RuntimeException("getNamespace() not supported");
+        }
+        public int getNamespaceCount(int depth) throws XmlPullParserException {
+            throw new XmlPullParserException("getNamespaceCount() not supported");
+        }
+        public String getPositionDescription() {
+            return "Binary XML file line #" + getLineNumber();
+        }
+        public String getNamespaceUri(int pos) throws XmlPullParserException {
+            throw new XmlPullParserException("getNamespaceUri() not supported");
+        }
+        public int getColumnNumber() {
+            return -1;
+        }
+        public int getDepth() {
+            return mDepth;
+        }
+        public String getText() {
+            int id = nativeGetText(mParseState);
+            return id >= 0 ? mStrings.get(id).toString() : null;
+        }
+        public int getLineNumber() {
+            return nativeGetLineNumber(mParseState);
+        }
+        public int getEventType() throws XmlPullParserException {
+            return mEventType;
+        }
+        public boolean isWhitespace() throws XmlPullParserException {
+            // whitespace was stripped by aapt.
+            return false;
+        }
+        public String getPrefix() {
+            throw new RuntimeException("getPrefix not supported");
+        }
+        public char[] getTextCharacters(int[] holderForStartAndLength) {
+            String txt = getText();
+            char[] chars = null;
+            if (txt != null) {
+                holderForStartAndLength[0] = 0;
+                holderForStartAndLength[1] = txt.length();
+                chars = new char[txt.length()];
+                txt.getChars(0, txt.length(), chars, 0);
+            }
+            return chars;
+        }
+        public String getNamespace() {
+            int id = nativeGetNamespace(mParseState);
+            return id >= 0 ? mStrings.get(id).toString() : "";
+        }
+        public String getName() {
+            int id = nativeGetName(mParseState);
+            return id >= 0 ? mStrings.get(id).toString() : null;
+        }
+        public String getAttributeNamespace(int index) {
+            int id = nativeGetAttributeNamespace(mParseState, index);
+            if (DEBUG) System.out.println("getAttributeNamespace of " + index + " = " + id);
+            if (id >= 0) return mStrings.get(id).toString();
+            else if (id == -1) return "";
+            throw new IndexOutOfBoundsException(String.valueOf(index));
+        }
+        public String getAttributeName(int index) {
+            int id = nativeGetAttributeName(mParseState, index);
+            if (DEBUG) System.out.println("getAttributeName of " + index + " = " + id);
+            if (id >= 0) return mStrings.get(id).toString();
+            throw new IndexOutOfBoundsException(String.valueOf(index));
+        }
+        public String getAttributePrefix(int index) {
+            throw new RuntimeException("getAttributePrefix not supported");
+        }
+        public boolean isEmptyElementTag() throws XmlPullParserException {
+            // XXX Need to detect this.
+            return false;
+        }
+        public int getAttributeCount() {
+            return mEventType == START_TAG ? nativeGetAttributeCount(mParseState) : -1;
+        }
+        public String getAttributeValue(int index) {
+            int id = nativeGetAttributeStringValue(mParseState, index);
+            if (DEBUG) System.out.println("getAttributeValue of " + index + " = " + id);
+            if (id >= 0) return mStrings.get(id).toString();
+
+            // May be some other type...  check and try to convert if so.
+            int t = nativeGetAttributeDataType(mParseState, index);
+            if (t == TypedValue.TYPE_NULL) {
+                throw new IndexOutOfBoundsException(String.valueOf(index));
+            }
+
+            int v = nativeGetAttributeData(mParseState, index);
+            return TypedValue.coerceToString(t, v);
+        }
+        public String getAttributeType(int index) {
+            return "CDATA";
+        }
+        public boolean isAttributeDefault(int index) {
+            return false;
+        }
+        public int nextToken() throws XmlPullParserException,IOException {
+            return next();
+        }
+        public String getAttributeValue(String namespace, String name) {
+            int idx = nativeGetAttributeIndex(mParseState, namespace, name);
+            if (idx >= 0) {
+                if (DEBUG) System.out.println("getAttributeName of "
+                        + namespace + ":" + name + " index = " + idx);
+                if (DEBUG) System.out.println(
+                        "Namespace=" + getAttributeNamespace(idx)
+                        + "Name=" + getAttributeName(idx)
+                        + ", Value=" + getAttributeValue(idx));
+                return getAttributeValue(idx);
+            }
+            return null;
+        }
+        public int next() throws XmlPullParserException,IOException {
+            if (!mStarted) {
+                mStarted = true;
+                return START_DOCUMENT;
+            }
+            if (mParseState == 0) {
+                return END_DOCUMENT;
+            }
+            int ev = nativeNext(mParseState);
+            if (mDecNextDepth) {
+                mDepth--;
+                mDecNextDepth = false;
+            }
+            switch (ev) {
+            case START_TAG:
+                mDepth++;
+                break;
+            case END_TAG:
+                mDecNextDepth = true;
+                break;
+            }
+            mEventType = ev;
+            if (ev == END_DOCUMENT) {
+                // Automatically close the parse when we reach the end of
+                // a document, since the standard XmlPullParser interface
+                // doesn't have such an API so most clients will leave us
+                // dangling.
+                close();
+            }
+            return ev;
+        }
+        public void require(int type, String namespace, String name) throws XmlPullParserException,IOException {
+            if (type != getEventType()
+                || (namespace != null && !namespace.equals( getNamespace () ) )
+                || (name != null && !name.equals( getName() ) ) )
+                throw new XmlPullParserException( "expected "+ TYPES[ type ]+getPositionDescription());
+        }
+        public String nextText() throws XmlPullParserException,IOException {
+            if(getEventType() != START_TAG) {
+               throw new XmlPullParserException(
+                 getPositionDescription()
+                 + ": parser must be on START_TAG to read next text", this, null);
+            }
+            int eventType = next();
+            if(eventType == TEXT) {
+               String result = getText();
+               eventType = next();
+               if(eventType != END_TAG) {
+                 throw new XmlPullParserException(
+                    getPositionDescription()
+                    + ": event TEXT it must be immediately followed by END_TAG", this, null);
+                }
+                return result;
+            } else if(eventType == END_TAG) {
+               return "";
+            } else {
+               throw new XmlPullParserException(
+                 getPositionDescription()
+                 + ": parser must be on START_TAG or TEXT to read text", this, null);
+            }
+        }
+        public int nextTag() throws XmlPullParserException,IOException {
+            int eventType = next();
+            if(eventType == TEXT && isWhitespace()) {   // skip whitespace
+               eventType = next();
+            }
+            if (eventType != START_TAG && eventType != END_TAG) {
+               throw new XmlPullParserException(
+                   getPositionDescription() 
+                   + ": expected start or end tag", this, null);
+            }
+            return eventType;
+        }
+    
+        public int getAttributeNameResource(int index) {
+            return nativeGetAttributeResource(mParseState, index);
+        }
+    
+        public int getAttributeListValue(String namespace, String attribute,
+                String[] options, int defaultValue) {
+            int idx = nativeGetAttributeIndex(mParseState, namespace, attribute);
+            if (idx >= 0) {
+                return getAttributeListValue(idx, options, defaultValue);
+            }
+            return defaultValue;
+        }
+        public boolean getAttributeBooleanValue(String namespace, String attribute,
+                boolean defaultValue) {
+            int idx = nativeGetAttributeIndex(mParseState, namespace, attribute);
+            if (idx >= 0) {
+                return getAttributeBooleanValue(idx, defaultValue);
+            }
+            return defaultValue;
+        }
+        public int getAttributeResourceValue(String namespace, String attribute,
+                int defaultValue) {
+            int idx = nativeGetAttributeIndex(mParseState, namespace, attribute);
+            if (idx >= 0) {
+                return getAttributeResourceValue(idx, defaultValue);
+            }
+            return defaultValue;
+        }
+        public int getAttributeIntValue(String namespace, String attribute,
+                int defaultValue) {
+            int idx = nativeGetAttributeIndex(mParseState, namespace, attribute);
+            if (idx >= 0) {
+                return getAttributeIntValue(idx, defaultValue);
+            }
+            return defaultValue;
+        }
+        public int getAttributeUnsignedIntValue(String namespace, String attribute,
+                                                int defaultValue)
+        {
+            int idx = nativeGetAttributeIndex(mParseState, namespace, attribute);
+            if (idx >= 0) {
+                return getAttributeUnsignedIntValue(idx, defaultValue);
+            }
+            return defaultValue;
+        }
+        public float getAttributeFloatValue(String namespace, String attribute,
+                float defaultValue) {
+            int idx = nativeGetAttributeIndex(mParseState, namespace, attribute);
+            if (idx >= 0) {
+                return getAttributeFloatValue(idx, defaultValue);
+            }
+            return defaultValue;
+        }
+
+        public int getAttributeListValue(int idx,
+                String[] options, int defaultValue) {
+            int t = nativeGetAttributeDataType(mParseState, idx);
+            int v = nativeGetAttributeData(mParseState, idx);
+            if (t == TypedValue.TYPE_STRING) {
+                return XmlUtils.convertValueToList(
+                    mStrings.get(v), options, defaultValue);
+            }
+            return v;
+        }
+        public boolean getAttributeBooleanValue(int idx,
+                boolean defaultValue) {
+            int t = nativeGetAttributeDataType(mParseState, idx);
+            // Note: don't attempt to convert any other types, because
+            // we want to count on appt doing the conversion for us.
+            if (t >= TypedValue.TYPE_FIRST_INT &&
+                t <= TypedValue.TYPE_LAST_INT) {
+                return nativeGetAttributeData(mParseState, idx) != 0;
+            }
+            return defaultValue;
+        }
+        public int getAttributeResourceValue(int idx, int defaultValue) {
+            int t = nativeGetAttributeDataType(mParseState, idx);
+            // Note: don't attempt to convert any other types, because
+            // we want to count on appt doing the conversion for us.
+            if (t == TypedValue.TYPE_REFERENCE) {
+                return nativeGetAttributeData(mParseState, idx);
+            }
+            return defaultValue;
+        }
+        public int getAttributeIntValue(int idx, int defaultValue) {
+            int t = nativeGetAttributeDataType(mParseState, idx);
+            // Note: don't attempt to convert any other types, because
+            // we want to count on appt doing the conversion for us.
+            if (t >= TypedValue.TYPE_FIRST_INT &&
+                t <= TypedValue.TYPE_LAST_INT) {
+                return nativeGetAttributeData(mParseState, idx);
+            }
+            return defaultValue;
+        }
+        public int getAttributeUnsignedIntValue(int idx, int defaultValue) {
+            int t = nativeGetAttributeDataType(mParseState, idx);
+            // Note: don't attempt to convert any other types, because
+            // we want to count on appt doing the conversion for us.
+            if (t >= TypedValue.TYPE_FIRST_INT &&
+                t <= TypedValue.TYPE_LAST_INT) {
+                return nativeGetAttributeData(mParseState, idx);
+            }
+            return defaultValue;
+        }
+        public float getAttributeFloatValue(int idx, float defaultValue) {
+            int t = nativeGetAttributeDataType(mParseState, idx);
+            // Note: don't attempt to convert any other types, because
+            // we want to count on appt doing the conversion for us.
+            if (t == TypedValue.TYPE_FLOAT) {
+                return Float.intBitsToFloat(
+                    nativeGetAttributeData(mParseState, idx));
+            }
+            throw new RuntimeException("not a float!");
+        }
+
+        public String getIdAttribute() {
+            int id = nativeGetIdAttribute(mParseState);
+            return id >= 0 ? mStrings.get(id).toString() : null;
+        }
+        public String getClassAttribute() {
+            int id = nativeGetClassAttribute(mParseState);
+            return id >= 0 ? mStrings.get(id).toString() : null;
+        }
+
+        public int getIdAttributeResourceValue(int defaultValue) {
+            //todo: create and use native method
+            return getAttributeResourceValue(null, "id", defaultValue);
+        }
+
+        public int getStyleAttribute() {
+            return nativeGetStyleAttribute(mParseState);
+        }
+
+        public void close() {
+            synchronized (mBlock) {
+                if (mParseState != 0) {
+                    nativeDestroyParseState(mParseState);
+                    mParseState = 0;
+                    mBlock.decOpenCountLocked();
+                }
+            }
+        }
+        
+        protected void finalize() throws Throwable {
+            close();
+        }
+
+        /*package*/ final CharSequence getPooledString(int id) {
+            return mStrings.get(id);
+        }
+
+        /*package*/ int mParseState;
+        private final XmlBlock mBlock;
+        private boolean mStarted = false;
+        private boolean mDecNextDepth = false;
+        private int mDepth = 0;
+        private int mEventType = START_DOCUMENT;
+    }
+
+    protected void finalize() throws Throwable {
+        close();
+    }
+
+    /**
+     * Create from an existing xml block native object.  This is
+     * -extremely- dangerous -- only use it if you absolutely know what you
+     *  are doing!  The given native object must exist for the entire lifetime
+     *  of this newly creating XmlBlock.
+     */
+    XmlBlock(AssetManager assets, int xmlBlock) {
+        mAssets = assets;
+        mNative = xmlBlock;
+        mStrings = new StringBlock(nativeGetStringBlock(xmlBlock), false);
+    }
+
+    private final AssetManager mAssets;
+    private final int mNative;
+    private final StringBlock mStrings;
+    private boolean mOpen = true;
+    private int mOpenCount = 1;
+
+    private static final native int nativeCreate(byte[] data,
+                                                 int offset,
+                                                 int size);
+    private static final native int nativeGetStringBlock(int obj);
+
+    private static final native int nativeCreateParseState(int obj);
+    private static final native int nativeNext(int state);
+    private static final native int nativeGetNamespace(int state);
+    private static final native int nativeGetName(int state);
+    private static final native int nativeGetText(int state);
+    private static final native int nativeGetLineNumber(int state);
+    private static final native int nativeGetAttributeCount(int state);
+    private static final native int nativeGetAttributeNamespace(int state, int idx);
+    private static final native int nativeGetAttributeName(int state, int idx);
+    private static final native int nativeGetAttributeResource(int state, int idx);
+    private static final native int nativeGetAttributeDataType(int state, int idx);
+    private static final native int nativeGetAttributeData(int state, int idx);
+    private static final native int nativeGetAttributeStringValue(int state, int idx);
+    private static final native int nativeGetIdAttribute(int state);
+    private static final native int nativeGetClassAttribute(int state);
+    private static final native int nativeGetStyleAttribute(int state);
+    private static final native int nativeGetAttributeIndex(int state, String namespace, String name);
+    private static final native void nativeDestroyParseState(int state);
+
+    private static final native void nativeDestroy(int obj);
+}
diff --git a/core/java/android/content/res/XmlResourceParser.java b/core/java/android/content/res/XmlResourceParser.java
new file mode 100644
index 0000000..c59e6d4
--- /dev/null
+++ b/core/java/android/content/res/XmlResourceParser.java
@@ -0,0 +1,36 @@
+/*
+ * 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 android.content.res;
+
+import org.xmlpull.v1.XmlPullParser;
+
+import android.util.AttributeSet;
+
+/**
+ * The XML parsing interface returned for an XML resource.  This is a standard
+ * XmlPullParser interface, as well as an extended AttributeSet interface and
+ * an additional close() method on this interface for the client to indicate
+ * when it is done reading the resource.
+ */
+public interface XmlResourceParser extends XmlPullParser, AttributeSet {
+    /**
+     * Close this interface to the resource.  Calls on the interface are no
+     * longer value after this call.
+     */
+    public void close();
+}
+
diff --git a/core/java/android/content/res/package.html b/core/java/android/content/res/package.html
new file mode 100644
index 0000000..bb09dc7
--- /dev/null
+++ b/core/java/android/content/res/package.html
@@ -0,0 +1,8 @@
+<HTML>
+<BODY>
+Contains classes for accessing application resources, 
+such as raw asset files, colors, drawables, media or other other files 
+in the package, plus important device configuration details 
+(orientation, input types, etc.) that affect how the application may behave.
+</BODY>
+</HTML>
\ No newline at end of file
diff --git a/core/java/android/database/AbstractCursor.java b/core/java/android/database/AbstractCursor.java
new file mode 100644
index 0000000..76f0860
--- /dev/null
+++ b/core/java/android/database/AbstractCursor.java
@@ -0,0 +1,636 @@
+/*
+ * 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 android.database;
+
+import android.content.ContentResolver;
+import android.net.Uri;
+import android.util.Config;
+import android.util.Log;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.os.Message;
+
+import java.lang.ref.WeakReference;
+import java.lang.UnsupportedOperationException;
+import java.util.HashMap;
+import java.util.Map;
+
+
+/**
+ * This is an abstract cursor class that handles a lot of the common code
+ * that all cursors need to deal with and is provided for convenience reasons.
+ */
+public abstract class AbstractCursor implements CrossProcessCursor {
+    private static final String TAG = "Cursor";
+
+    DataSetObservable mDataSetObservable = new DataSetObservable();
+    ContentObservable mContentObservable = new ContentObservable();
+
+    /* -------------------------------------------------------- */
+    /* These need to be implemented by subclasses */
+    abstract public int getCount();
+
+    abstract public String[] getColumnNames();
+
+    abstract public String getString(int column);
+    abstract public short getShort(int column);
+    abstract public int getInt(int column);
+    abstract public long getLong(int column);
+    abstract public float getFloat(int column);
+    abstract public double getDouble(int column);
+    abstract public boolean isNull(int column);
+
+    // TODO implement getBlob in all cursor types
+    public byte[] getBlob(int column) {
+        throw new UnsupportedOperationException("getBlob is not supported");
+    }
+    /* -------------------------------------------------------- */
+    /* Methods that may optionally be implemented by subclasses */
+
+    /**
+     * returns a pre-filled window, return NULL if no such window
+     */
+    public CursorWindow getWindow() {
+        return null;
+    }
+
+    public int getColumnCount() {
+        return getColumnNames().length;
+    }
+    
+    public void deactivate() {
+        deactivateInternal();
+    }
+    
+    /**
+     * @hide
+     */
+    public void deactivateInternal() {
+        if (mSelfObserver != null) {
+            mContentResolver.unregisterContentObserver(mSelfObserver);
+            mSelfObserverRegistered = false;
+        }
+        mDataSetObservable.notifyInvalidated();
+    }
+    
+    public boolean requery() {
+        if (mSelfObserver != null && mSelfObserverRegistered == false) {
+            mContentResolver.registerContentObserver(mNotifyUri, true, mSelfObserver);
+            mSelfObserverRegistered = true;
+        }
+        mDataSetObservable.notifyChanged();
+        return true;
+    }
+
+    public boolean isClosed() {
+        return mClosed;
+    }
+    
+    public void close() {
+        mClosed = true;
+        mContentObservable.unregisterAll();
+        deactivateInternal();
+    }
+
+    /**
+     * @hide
+     * @deprecated
+     */
+    public boolean commitUpdates(Map<? extends Long,? extends Map<String,Object>> values) {
+        return false;
+    }
+
+    /**
+     * @hide
+     * @deprecated
+     */
+    public boolean deleteRow() {
+        return false;
+    }
+
+    /**
+     * This function is called every time the cursor is successfully scrolled
+     * to a new position, giving the subclass a chance to update any state it
+     * may have. If it returns false the move function will also do so and the
+     * cursor will scroll to the beforeFirst position.
+     *
+     * @param oldPosition the position that we're moving from
+     * @param newPosition the position that we're moving to
+     * @return true if the move is successful, false otherwise
+     */
+    public boolean onMove(int oldPosition, int newPosition) {
+        return true;
+    }
+
+    
+    public void copyStringToBuffer(int columnIndex, CharArrayBuffer buffer) {
+        // Default implementation, uses getString
+        String result = getString(columnIndex);
+        if (result != null) {
+            char[] data = buffer.data;
+            if (data == null || data.length < result.length()) {
+                buffer.data = result.toCharArray();
+            } else {
+                result.getChars(0, result.length(), data, 0);
+            }
+            buffer.sizeCopied = result.length();
+        }
+    }
+    
+    /* -------------------------------------------------------- */
+    /* Implementation */
+    public AbstractCursor() {
+        mPos = -1;
+        mRowIdColumnIndex = -1;
+        mCurrentRowID = null;
+        mUpdatedRows = new HashMap<Long, Map<String, Object>>();
+    }
+
+    public final int getPosition() {
+        return mPos;
+    }
+
+    public final boolean moveToPosition(int position) {
+        // Make sure position isn't past the end of the cursor
+        final int count = getCount();
+        if (position >= count) {
+            mPos = count;
+            return false;
+        }
+
+        // Make sure position isn't before the beginning of the cursor
+        if (position < 0) {
+            mPos = -1;
+            return false;
+        }
+
+        // Check for no-op moves, and skip the rest of the work for them
+        if (position == mPos) {
+            return true;
+        }
+
+        boolean result = onMove(mPos, position);
+        if (result == false) {
+            mPos = -1;
+        } else {
+            mPos = position;
+            if (mRowIdColumnIndex != -1) {
+                mCurrentRowID = Long.valueOf(getLong(mRowIdColumnIndex));
+            }
+        }
+
+        return result;
+    }
+    
+    /**
+     * Copy data from cursor to CursorWindow
+     * @param position start position of data
+     * @param window
+     */
+    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++) {
+                    String field = getString(i);
+                    if (field != null) {
+                        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();
+        }
+    }
+
+    public final boolean move(int offset) {
+        return moveToPosition(mPos + offset);
+    }
+
+    public final boolean moveToFirst() {
+        return moveToPosition(0);
+    }
+
+    public final boolean moveToLast() {
+        return moveToPosition(getCount() - 1);
+    }
+
+    public final boolean moveToNext() {
+        return moveToPosition(mPos + 1);
+    }
+
+    public final boolean moveToPrevious() {
+        return moveToPosition(mPos - 1);
+    }
+
+    public final boolean isFirst() {
+        return mPos == 0 && getCount() != 0;
+    }
+
+    public final boolean isLast() {
+        int cnt = getCount();
+        return mPos == (cnt - 1) && cnt != 0;
+    }
+
+    public final boolean isBeforeFirst() {
+        if (getCount() == 0) {
+            return true;
+        }
+        return mPos == -1;
+    }
+
+    public final boolean isAfterLast() {
+        if (getCount() == 0) {
+            return true;
+        }
+        return mPos == getCount();
+    }
+
+    public int getColumnIndex(String columnName) {
+        // Hack according to bug 903852
+        final int periodIndex = columnName.lastIndexOf('.');
+        if (periodIndex != -1) {
+            Exception e = new Exception();
+            Log.e(TAG, "requesting column name with table name -- " + columnName, e);
+            columnName = columnName.substring(periodIndex + 1);
+        }
+
+        String columnNames[] = getColumnNames();
+        int length = columnNames.length;
+        for (int i = 0; i < length; i++) {
+            if (columnNames[i].equalsIgnoreCase(columnName)) {
+                return i;
+            }
+        }
+
+        if (Config.LOGV) {
+            if (getCount() > 0) {
+                Log.w("AbstractCursor", "Unknown column " + columnName);
+            }
+        }
+        return -1;
+    }
+
+    public int getColumnIndexOrThrow(String columnName) {
+        final int index = getColumnIndex(columnName);
+        if (index < 0) {
+            throw new IllegalArgumentException("column '" + columnName + "' does not exist");
+        }
+        return index;
+    }
+
+    public String getColumnName(int columnIndex) {
+        return getColumnNames()[columnIndex];
+    }
+
+    /**
+     * @hide
+     * @deprecated
+     */
+    public boolean updateBlob(int columnIndex, byte[] value) {
+        return update(columnIndex, value);
+    }
+
+    /**
+     * @hide
+     * @deprecated
+     */
+    public boolean updateString(int columnIndex, String value) {
+        return update(columnIndex, value);
+    }
+
+    /**
+     * @hide
+     * @deprecated
+     */
+    public boolean updateShort(int columnIndex, short value) {
+        return update(columnIndex, Short.valueOf(value));
+    }
+
+    /**
+     * @hide
+     * @deprecated
+     */
+    public boolean updateInt(int columnIndex, int value) {
+        return update(columnIndex, Integer.valueOf(value));
+    }
+
+    /**
+     * @hide
+     * @deprecated
+     */
+    public boolean updateLong(int columnIndex, long value) {
+        return update(columnIndex, Long.valueOf(value));
+    }
+
+    /**
+     * @hide
+     * @deprecated
+     */
+    public boolean updateFloat(int columnIndex, float value) {
+        return update(columnIndex, Float.valueOf(value));
+    }
+
+    /**
+     * @hide
+     * @deprecated
+     */
+    public boolean updateDouble(int columnIndex, double value) {
+        return update(columnIndex, Double.valueOf(value));
+    }
+
+    /**
+     * @hide
+     * @deprecated
+     */
+    public boolean updateToNull(int columnIndex) {
+        return update(columnIndex, null);
+    }
+
+    /**
+     * @hide
+     * @deprecated
+     */
+    public boolean update(int columnIndex, Object obj) {
+        if (!supportsUpdates()) {
+            return false;
+        }
+
+        // Long.valueOf() returns null sometimes!
+//        Long rowid = Long.valueOf(getLong(mRowIdColumnIndex));
+        Long rowid = new Long(getLong(mRowIdColumnIndex));
+        if (rowid == null) {
+            throw new IllegalStateException("null rowid. mRowIdColumnIndex = " + mRowIdColumnIndex);
+        }
+
+        synchronized(mUpdatedRows) {
+            Map<String, Object> row = mUpdatedRows.get(rowid);
+            if (row == null) {
+                row = new HashMap<String, Object>();
+                mUpdatedRows.put(rowid, row);
+            }
+            row.put(getColumnNames()[columnIndex], obj);
+        }
+
+        return true;
+    }
+
+    /**
+     * Returns <code>true</code> if there are pending updates that have not yet been committed.
+     * 
+     * @return <code>true</code> if there are pending updates that have not yet been committed.
+     * @hide
+     * @deprecated
+     */
+    public boolean hasUpdates() {
+        synchronized(mUpdatedRows) {
+            return mUpdatedRows.size() > 0;
+        }
+    }
+
+    /**
+     * @hide
+     * @deprecated
+     */
+    public void abortUpdates() {
+        synchronized(mUpdatedRows) {
+            mUpdatedRows.clear();
+        }
+    }
+
+    /**
+     * @hide
+     * @deprecated
+     */
+    public boolean commitUpdates() {
+        return commitUpdates(null);
+    }
+
+    /**
+     * @hide
+     * @deprecated
+     */
+    public boolean supportsUpdates() {
+        return mRowIdColumnIndex != -1;
+    }
+
+    public void registerContentObserver(ContentObserver observer) {
+        mContentObservable.registerObserver(observer);
+    }
+
+    public void unregisterContentObserver(ContentObserver observer) {
+        // cursor will unregister all observers when it close
+        if (!mClosed) {
+            mContentObservable.unregisterObserver(observer);
+        }
+    }
+    
+    /**
+     * @hide pending API council approval
+     */
+    protected void notifyDataSetChange() {
+        mDataSetObservable.notifyChanged();
+    }
+    
+    /**
+     * @hide pending API council approval
+     */
+    protected DataSetObservable getDataSetObservable() {
+        return mDataSetObservable;
+        
+    }
+    public void registerDataSetObserver(DataSetObserver observer) {
+        mDataSetObservable.registerObserver(observer);
+        
+    }
+
+    public void unregisterDataSetObserver(DataSetObserver observer) {
+        mDataSetObservable.unregisterObserver(observer);
+    }
+
+    /**
+     * Subclasses must call this method when they finish committing updates to notify all
+     * observers.
+     *
+     * @param selfChange
+     */
+    protected void onChange(boolean selfChange) {
+        synchronized (mSelfObserverLock) {
+            mContentObservable.dispatchChange(selfChange);
+            if (mNotifyUri != null && selfChange) {
+                mContentResolver.notifyChange(mNotifyUri, mSelfObserver);
+            }
+        }
+    }
+
+    /**
+     * Specifies a content URI to watch for changes.
+     *
+     * @param cr The content resolver from the caller's context.
+     * @param notifyUri The URI to watch for changes. This can be a
+     * specific row URI, or a base URI for a whole class of content.
+     */
+    public void setNotificationUri(ContentResolver cr, Uri notifyUri) {
+        synchronized (mSelfObserverLock) {
+            mNotifyUri = notifyUri;
+            mContentResolver = cr;
+            if (mSelfObserver != null) {
+                mContentResolver.unregisterContentObserver(mSelfObserver);
+            }
+            mSelfObserver = new SelfContentObserver(this);
+            mContentResolver.registerContentObserver(mNotifyUri, true, mSelfObserver);
+            mSelfObserverRegistered = true;
+        }
+    }
+
+    public boolean getWantsAllOnMoveCalls() {
+        return false;
+    }
+
+    public Bundle getExtras() {
+        return Bundle.EMPTY;
+    }
+
+    public Bundle respond(Bundle extras) {
+        return Bundle.EMPTY;
+    }
+
+    /**
+     * This function returns true if the field has been updated and is
+     * used in conjunction with {@link #getUpdatedField} to allow subclasses to
+     * support reading uncommitted updates. NOTE: This function and
+     * {@link #getUpdatedField} should be called together inside of a
+     * block synchronized on mUpdatedRows.
+     *
+     * @param columnIndex the column index of the field to check
+     * @return true if the field has been updated, false otherwise
+     */
+    protected boolean isFieldUpdated(int columnIndex) {
+        if (mRowIdColumnIndex != -1 && mUpdatedRows.size() > 0) {
+            Map<String, Object> updates = mUpdatedRows.get(mCurrentRowID);
+            if (updates != null && updates.containsKey(getColumnNames()[columnIndex])) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * This function returns the uncommitted updated value for the field
+     * at columnIndex.  NOTE: This function and {@link #isFieldUpdated} should
+     * be called together inside of a block synchronized on mUpdatedRows.
+     *
+     * @param columnIndex the column index of the field to retrieve
+     * @return the updated value
+     */
+    protected Object getUpdatedField(int columnIndex) {
+        Map<String, Object> updates = mUpdatedRows.get(mCurrentRowID);
+        return updates.get(getColumnNames()[columnIndex]);
+    }
+
+    /**
+     * This function throws CursorIndexOutOfBoundsException if
+     * the cursor position is out of bounds. Subclass implementations of
+     * the get functions should call this before attempting
+     * to retrieve data.
+     *
+     * @throws CursorIndexOutOfBoundsException
+     */
+    protected void checkPosition() {
+        if (-1 == mPos || getCount() == mPos) {
+            throw new CursorIndexOutOfBoundsException(mPos, getCount());
+        }
+    }
+
+    @Override
+    protected void finalize() {
+        if (mSelfObserver != null && mSelfObserverRegistered == true) {
+            mContentResolver.unregisterContentObserver(mSelfObserver);
+        }
+    }
+
+    /**
+     * Cursors use this class to track changes others make to their URI.
+     */
+    protected static class SelfContentObserver extends ContentObserver {
+        WeakReference<AbstractCursor> mCursor;
+
+        public SelfContentObserver(AbstractCursor cursor) {
+            super(null);
+            mCursor = new WeakReference<AbstractCursor>(cursor);
+        }
+
+        @Override
+        public boolean deliverSelfNotifications() {
+            return false;
+        }
+
+        @Override
+        public void onChange(boolean selfChange) {
+            AbstractCursor cursor = mCursor.get();
+            if (cursor != null) {
+                cursor.onChange(false);
+            }
+        }
+    }
+
+    /**
+     * This HashMap contains a mapping from Long rowIDs to another Map
+     * that maps from String column names to new values. A NULL value means to
+     * remove an existing value, and all numeric values are in their class
+     * forms, i.e. Integer, Long, Float, etc.
+     */
+    protected HashMap<Long, Map<String, Object>> mUpdatedRows;
+
+    /**
+     * This must be set to the index of the row ID column by any
+     * subclass that wishes to support updates.
+     */
+    protected int mRowIdColumnIndex;
+
+    protected int mPos;
+    protected Long mCurrentRowID;
+    protected ContentResolver mContentResolver;
+    protected boolean mClosed = false;
+    private Uri mNotifyUri;
+    private ContentObserver mSelfObserver;
+    final private Object mSelfObserverLock = new Object();
+    private boolean mSelfObserverRegistered;
+}
diff --git a/core/java/android/database/AbstractWindowedCursor.java b/core/java/android/database/AbstractWindowedCursor.java
new file mode 100644
index 0000000..4ac0aef
--- /dev/null
+++ b/core/java/android/database/AbstractWindowedCursor.java
@@ -0,0 +1,204 @@
+/*
+ * 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 android.database;
+
+/**
+ * A base class for Cursors that store their data in {@link CursorWindow}s.
+ */
+public abstract class AbstractWindowedCursor extends AbstractCursor
+{
+    @Override
+    public byte[] getBlob(int columnIndex)
+    {
+        checkPosition();
+
+        synchronized(mUpdatedRows) {
+            if (isFieldUpdated(columnIndex)) {
+                return (byte[])getUpdatedField(columnIndex);
+            }
+        }
+
+        return mWindow.getBlob(mPos, columnIndex);
+    }
+
+    @Override
+    public String getString(int columnIndex)
+    {
+        checkPosition();
+
+        synchronized(mUpdatedRows) {
+            if (isFieldUpdated(columnIndex)) {
+                return (String)getUpdatedField(columnIndex);
+            }
+        }
+
+        return mWindow.getString(mPos, columnIndex);
+    }
+    
+    @Override
+    public void copyStringToBuffer(int columnIndex, CharArrayBuffer buffer)
+    {
+        checkPosition();
+        
+        synchronized(mUpdatedRows) {
+            if (isFieldUpdated(columnIndex)) {
+                super.copyStringToBuffer(columnIndex, buffer);
+            }
+        }
+        
+        mWindow.copyStringToBuffer(mPos, columnIndex, buffer);
+    }
+
+    @Override
+    public short getShort(int columnIndex)
+    {
+        checkPosition();
+
+        synchronized(mUpdatedRows) {
+            if (isFieldUpdated(columnIndex)) {
+                Number value = (Number)getUpdatedField(columnIndex);
+                return value.shortValue();
+            }
+        }
+
+        return mWindow.getShort(mPos, columnIndex);
+    }
+
+    @Override
+    public int getInt(int columnIndex)
+    {
+        checkPosition();
+
+        synchronized(mUpdatedRows) {
+            if (isFieldUpdated(columnIndex)) {
+                Number value = (Number)getUpdatedField(columnIndex);
+                return value.intValue();
+            }
+        }
+
+        return mWindow.getInt(mPos, columnIndex);
+    }
+
+    @Override
+    public long getLong(int columnIndex)
+    {
+        checkPosition();
+
+        synchronized(mUpdatedRows) {
+            if (isFieldUpdated(columnIndex)) {
+                Number value = (Number)getUpdatedField(columnIndex);
+                return value.longValue();
+            }
+        }
+
+        return mWindow.getLong(mPos, columnIndex);
+    }
+
+    @Override
+    public float getFloat(int columnIndex)
+    {
+        checkPosition();
+
+        synchronized(mUpdatedRows) {
+            if (isFieldUpdated(columnIndex)) {
+                Number value = (Number)getUpdatedField(columnIndex);
+                return value.floatValue();
+            }
+        }
+
+        return mWindow.getFloat(mPos, columnIndex);
+    }
+
+    @Override
+    public double getDouble(int columnIndex)
+    {
+        checkPosition();
+
+        synchronized(mUpdatedRows) {
+            if (isFieldUpdated(columnIndex)) {
+                Number value = (Number)getUpdatedField(columnIndex);
+                return value.doubleValue();
+            }
+        }
+
+        return mWindow.getDouble(mPos, columnIndex);
+    }
+
+    @Override
+    public boolean isNull(int columnIndex)
+    {
+        checkPosition();
+
+        synchronized(mUpdatedRows) {
+            if (isFieldUpdated(columnIndex)) {
+                return getUpdatedField(columnIndex) == null;
+            }
+        }
+
+        return mWindow.isNull(mPos, columnIndex);
+    }
+
+    public boolean isBlob(int columnIndex)
+    {
+        checkPosition();
+
+        synchronized(mUpdatedRows) {
+            if (isFieldUpdated(columnIndex)) {
+                Object object = getUpdatedField(columnIndex);
+                return object == null || object instanceof byte[];
+            }
+        }
+
+        return mWindow.isBlob(mPos, columnIndex);
+    }
+
+    @Override
+    protected void checkPosition()
+    {
+        super.checkPosition();
+        
+        if (mWindow == null) {
+            throw new StaleDataException("Access closed cursor");
+        }
+    }
+
+    @Override
+    public CursorWindow getWindow() {
+        return mWindow;
+    }
+    
+    /**
+     * Set a new cursor window to cursor, usually set a remote cursor window
+     * @param window cursor window
+     */
+    public void setWindow(CursorWindow window) {
+        if (mWindow != null) {
+            mWindow.close();
+        }
+        mWindow = window;
+    }
+    
+    public boolean hasWindow() {
+        return mWindow != null;
+    }
+
+    /**
+     * This needs be updated in {@link #onMove} by subclasses, and
+     * needs to be set to NULL when the contents of the cursor change.
+     */
+    protected CursorWindow mWindow;
+}
diff --git a/core/java/android/database/BulkCursorNative.java b/core/java/android/database/BulkCursorNative.java
new file mode 100644
index 0000000..baa94d8
--- /dev/null
+++ b/core/java/android/database/BulkCursorNative.java
@@ -0,0 +1,440 @@
+/*
+ * 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 android.database;
+
+import android.os.Binder;
+import android.os.RemoteException;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.Bundle;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Native implementation of the bulk cursor. This is only for use in implementing
+ * IPC, application code should use the Cursor interface.
+ * 
+ * {@hide}
+ */
+public abstract class BulkCursorNative extends Binder implements IBulkCursor
+{
+    public BulkCursorNative()
+    {
+        attachInterface(this, descriptor);
+    }
+
+    /**
+     * Cast a Binder object into a content resolver interface, generating
+     * a proxy if needed.
+     */
+    static public IBulkCursor asInterface(IBinder obj)
+    {
+        if (obj == null) {
+            return null;
+        }
+        IBulkCursor in = (IBulkCursor)obj.queryLocalInterface(descriptor);
+        if (in != null) {
+            return in;
+        }
+
+        return new BulkCursorProxy(obj);
+    }
+    
+    @Override
+    public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
+            throws RemoteException {
+        try {
+            switch (code) {
+                case GET_CURSOR_WINDOW_TRANSACTION: {
+                    data.enforceInterface(IBulkCursor.descriptor);
+                    int startPos = data.readInt();
+                    CursorWindow window = getWindow(startPos);
+                    if (window == null) {
+                        reply.writeInt(0);
+                        return true;
+                    }
+                    reply.writeNoException();
+                    reply.writeInt(1);
+                    window.writeToParcel(reply, 0);
+                    return true;
+                }
+
+                case COUNT_TRANSACTION: {
+                    data.enforceInterface(IBulkCursor.descriptor);
+                    int count = count();
+                    reply.writeNoException();
+                    reply.writeInt(count);
+                    return true;
+                }
+
+                case GET_COLUMN_NAMES_TRANSACTION: {
+                    data.enforceInterface(IBulkCursor.descriptor);
+                    String[] columnNames = getColumnNames();
+                    reply.writeNoException();
+                    reply.writeInt(columnNames.length);
+                    int length = columnNames.length;
+                    for (int i = 0; i < length; i++) {
+                        reply.writeString(columnNames[i]);
+                    }
+                    return true;
+                }
+
+                case DEACTIVATE_TRANSACTION: {
+                    data.enforceInterface(IBulkCursor.descriptor);
+                    deactivate();
+                    reply.writeNoException();
+                    return true;
+                }
+                
+                case CLOSE_TRANSACTION: {
+                    data.enforceInterface(IBulkCursor.descriptor);
+                    close();
+                    reply.writeNoException();
+                    return true;
+                }
+
+                case REQUERY_TRANSACTION: {
+                    data.enforceInterface(IBulkCursor.descriptor);
+                    IContentObserver observer =
+                        IContentObserver.Stub.asInterface(data.readStrongBinder());
+                    CursorWindow window = CursorWindow.CREATOR.createFromParcel(data);
+                    int count = requery(observer, window);
+                    reply.writeNoException();
+                    reply.writeInt(count);
+                    reply.writeBundle(getExtras());
+                    return true;
+                }
+
+                case UPDATE_ROWS_TRANSACTION: {
+                    data.enforceInterface(IBulkCursor.descriptor);
+                    // TODO - what ClassLoader should be passed to readHashMap?
+                    // TODO - switch to Bundle
+                    HashMap<Long, Map<String, Object>> values = data.readHashMap(null);
+                    boolean result = updateRows(values);
+                    reply.writeNoException();
+                    reply.writeInt((result == true ? 1 : 0));
+                    return true;
+                }
+
+                case DELETE_ROW_TRANSACTION: {
+                    data.enforceInterface(IBulkCursor.descriptor);
+                    int position = data.readInt();
+                    boolean result = deleteRow(position);
+                    reply.writeNoException();
+                    reply.writeInt((result == true ? 1 : 0));
+                    return true;
+                }
+
+                case ON_MOVE_TRANSACTION: {
+                    data.enforceInterface(IBulkCursor.descriptor);
+                    int position = data.readInt();
+                    onMove(position);
+                    reply.writeNoException();
+                    return true;
+                }
+
+                case WANTS_ON_MOVE_TRANSACTION: {
+                    data.enforceInterface(IBulkCursor.descriptor);
+                    boolean result = getWantsAllOnMoveCalls();
+                    reply.writeNoException();
+                    reply.writeInt(result ? 1 : 0);
+                    return true;
+                }
+
+                case GET_EXTRAS_TRANSACTION: {
+                    data.enforceInterface(IBulkCursor.descriptor);
+                    Bundle extras = getExtras();
+                    reply.writeNoException();
+                    reply.writeBundle(extras);
+                    return true;
+                }
+
+                case RESPOND_TRANSACTION: {
+                    data.enforceInterface(IBulkCursor.descriptor);
+                    Bundle extras = data.readBundle();
+                    Bundle returnExtras = respond(extras);
+                    reply.writeNoException();
+                    reply.writeBundle(returnExtras);
+                    return true;
+                }
+            }
+        } catch (Exception e) {
+            DatabaseUtils.writeExceptionToParcel(reply, e);
+            return true;
+        }
+
+        return super.onTransact(code, data, reply, flags);
+    }
+
+    public IBinder asBinder()
+    {
+        return this;
+    }
+}
+
+
+final class BulkCursorProxy implements IBulkCursor {
+    private IBinder mRemote;
+    private Bundle mExtras;
+
+    public BulkCursorProxy(IBinder remote)
+    {
+        mRemote = remote;
+        mExtras = null;
+    }
+
+    public IBinder asBinder()
+    {
+        return mRemote;
+    }
+
+    public CursorWindow getWindow(int startPos) throws RemoteException
+    {
+        Parcel data = Parcel.obtain();
+        Parcel reply = Parcel.obtain();
+
+        data.writeInterfaceToken(IBulkCursor.descriptor);
+
+        data.writeInt(startPos);
+
+        mRemote.transact(GET_CURSOR_WINDOW_TRANSACTION, data, reply, 0);
+
+        DatabaseUtils.readExceptionFromParcel(reply);
+        
+        CursorWindow window = null;
+        if (reply.readInt() == 1) {
+            window = CursorWindow.newFromParcel(reply);
+        }
+
+        data.recycle();
+        reply.recycle();
+
+        return window;
+    }
+
+    public void onMove(int position) throws RemoteException {
+        Parcel data = Parcel.obtain();
+        Parcel reply = Parcel.obtain();
+
+        data.writeInterfaceToken(IBulkCursor.descriptor);
+
+        data.writeInt(position);
+
+        mRemote.transact(ON_MOVE_TRANSACTION, data, reply, 0);
+
+        DatabaseUtils.readExceptionFromParcel(reply);
+
+        data.recycle();
+        reply.recycle();
+    }
+
+    public int count() throws RemoteException
+    {
+        Parcel data = Parcel.obtain();
+        Parcel reply = Parcel.obtain();
+
+        data.writeInterfaceToken(IBulkCursor.descriptor);
+
+        boolean result = mRemote.transact(COUNT_TRANSACTION, data, reply, 0);
+
+        DatabaseUtils.readExceptionFromParcel(reply);
+        
+        int count;
+        if (result == false) {
+            count = -1;
+        } else {
+            count = reply.readInt();
+        }
+        data.recycle();
+        reply.recycle();
+        return count;
+    }
+
+    public String[] getColumnNames() throws RemoteException
+    {
+        Parcel data = Parcel.obtain();
+        Parcel reply = Parcel.obtain();
+
+        data.writeInterfaceToken(IBulkCursor.descriptor);
+
+        mRemote.transact(GET_COLUMN_NAMES_TRANSACTION, data, reply, 0);
+
+        DatabaseUtils.readExceptionFromParcel(reply);
+        
+        String[] columnNames = null;
+        int numColumns = reply.readInt();
+        columnNames = new String[numColumns];
+        for (int i = 0; i < numColumns; i++) {
+            columnNames[i] = reply.readString();
+        }
+        
+        data.recycle();
+        reply.recycle();
+        return columnNames;
+    }
+
+    public void deactivate() throws RemoteException
+    {
+        Parcel data = Parcel.obtain();
+        Parcel reply = Parcel.obtain();
+
+        data.writeInterfaceToken(IBulkCursor.descriptor);
+
+        mRemote.transact(DEACTIVATE_TRANSACTION, data, reply, 0);
+        DatabaseUtils.readExceptionFromParcel(reply);
+
+        data.recycle();
+        reply.recycle();
+    }
+
+    public void close() throws RemoteException
+    {
+        Parcel data = Parcel.obtain();
+        Parcel reply = Parcel.obtain();
+
+        data.writeInterfaceToken(IBulkCursor.descriptor);
+
+        mRemote.transact(CLOSE_TRANSACTION, data, reply, 0);
+        DatabaseUtils.readExceptionFromParcel(reply);
+
+        data.recycle();
+        reply.recycle();
+    }
+    
+    public int requery(IContentObserver observer, CursorWindow window) throws RemoteException {
+        Parcel data = Parcel.obtain();
+        Parcel reply = Parcel.obtain();
+
+        data.writeInterfaceToken(IBulkCursor.descriptor);
+
+        data.writeStrongInterface(observer);
+        window.writeToParcel(data, 0);
+
+        boolean result = mRemote.transact(REQUERY_TRANSACTION, data, reply, 0);
+        
+        DatabaseUtils.readExceptionFromParcel(reply);
+
+        int count;
+        if (!result) {
+            count = -1;
+        } else {
+            count = reply.readInt();
+            mExtras = reply.readBundle();
+        }
+
+        data.recycle();
+        reply.recycle();
+
+        return count;
+    }
+
+    public boolean updateRows(Map values) throws RemoteException
+    {
+        Parcel data = Parcel.obtain();
+        Parcel reply = Parcel.obtain();
+
+        data.writeInterfaceToken(IBulkCursor.descriptor);
+
+        data.writeMap(values);
+
+        mRemote.transact(UPDATE_ROWS_TRANSACTION, data, reply, 0);
+
+        DatabaseUtils.readExceptionFromParcel(reply);
+        
+        boolean result = (reply.readInt() == 1 ? true : false);
+
+        data.recycle();
+        reply.recycle();
+
+        return result;
+    }
+
+    public boolean deleteRow(int position) throws RemoteException
+    {
+        Parcel data = Parcel.obtain();
+        Parcel reply = Parcel.obtain();
+
+        data.writeInterfaceToken(IBulkCursor.descriptor);
+
+        data.writeInt(position);
+
+        mRemote.transact(DELETE_ROW_TRANSACTION, data, reply, 0);
+
+        DatabaseUtils.readExceptionFromParcel(reply);
+        
+        boolean result = (reply.readInt() == 1 ? true : false);
+
+        data.recycle();
+        reply.recycle();
+
+        return result;
+    }
+
+    public boolean getWantsAllOnMoveCalls() throws RemoteException {
+        Parcel data = Parcel.obtain();
+        Parcel reply = Parcel.obtain();
+
+        data.writeInterfaceToken(IBulkCursor.descriptor);
+
+        mRemote.transact(WANTS_ON_MOVE_TRANSACTION, data, reply, 0);
+
+        DatabaseUtils.readExceptionFromParcel(reply);
+
+        int result = reply.readInt();
+        data.recycle();
+        reply.recycle();
+        return result != 0;
+    }
+
+    public Bundle getExtras() throws RemoteException {
+        if (mExtras == null) {
+            Parcel data = Parcel.obtain();
+            Parcel reply = Parcel.obtain();
+
+            data.writeInterfaceToken(IBulkCursor.descriptor);
+
+            mRemote.transact(GET_EXTRAS_TRANSACTION, data, reply, 0);
+
+            DatabaseUtils.readExceptionFromParcel(reply);
+
+            mExtras = reply.readBundle();
+            data.recycle();
+            reply.recycle();
+        }
+        return mExtras;
+    }
+
+    public Bundle respond(Bundle extras) throws RemoteException {
+        Parcel data = Parcel.obtain();
+        Parcel reply = Parcel.obtain();
+
+        data.writeInterfaceToken(IBulkCursor.descriptor);
+
+        data.writeBundle(extras);
+
+        mRemote.transact(RESPOND_TRANSACTION, data, reply, 0);
+
+        DatabaseUtils.readExceptionFromParcel(reply);
+
+        Bundle returnExtras = reply.readBundle();
+        data.recycle();
+        reply.recycle();
+        return returnExtras;
+    }
+}
+
diff --git a/core/java/android/database/BulkCursorToCursorAdaptor.java b/core/java/android/database/BulkCursorToCursorAdaptor.java
new file mode 100644
index 0000000..c26810a
--- /dev/null
+++ b/core/java/android/database/BulkCursorToCursorAdaptor.java
@@ -0,0 +1,256 @@
+/*
+ * 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 android.database;
+
+import android.os.RemoteException;
+import android.os.Bundle;
+import android.util.Log;
+
+import java.util.Map;
+
+/**
+ * Adapts an {@link IBulkCursor} to a {@link Cursor} for use in the local
+ * process.
+ *
+ * {@hide}
+ */
+public final class BulkCursorToCursorAdaptor extends AbstractWindowedCursor {
+    private static final String TAG = "BulkCursor";
+
+    private SelfContentObserver mObserverBridge;
+    private IBulkCursor mBulkCursor;
+    private int mCount;
+    private String[] mColumns;
+    private boolean mWantsAllOnMoveCalls;
+
+    public void set(IBulkCursor bulkCursor) {
+        mBulkCursor = bulkCursor;
+
+        try {
+            mCount = mBulkCursor.count();
+            mWantsAllOnMoveCalls = mBulkCursor.getWantsAllOnMoveCalls();
+    
+            // Search for the rowID column index and set it for our parent
+            mColumns = mBulkCursor.getColumnNames();
+            int length = mColumns.length;
+            for (int i = 0; i < length; i++) {
+                if (mColumns[i].equals("_id")) {
+                    mRowIdColumnIndex = i;
+                    break;
+                }
+            }
+        } catch (RemoteException ex) {
+            Log.e(TAG, "Setup failed because the remote process is dead");
+        }
+    }
+
+    /**
+     * Gets a SelfDataChangeOberserver that can be sent to a remote
+     * process to receive change notifications over IPC.
+     * 
+     * @return A SelfContentObserver hooked up to this Cursor
+     */
+    public synchronized IContentObserver getObserver() {
+        if (mObserverBridge == null) {
+            mObserverBridge = new SelfContentObserver(this);
+        }
+        return mObserverBridge.getContentObserver();
+    }
+
+    @Override
+    public int getCount() {
+        return mCount;
+    }
+
+    @Override
+    public boolean onMove(int oldPosition, int newPosition) {
+        try {
+            // Make sure we have the proper window
+            if (mWindow != null) {
+                if (newPosition < mWindow.getStartPosition() ||
+                        newPosition >= (mWindow.getStartPosition() + mWindow.getNumRows())) {
+                    mWindow = mBulkCursor.getWindow(newPosition);
+                } else if (mWantsAllOnMoveCalls) {
+                    mBulkCursor.onMove(newPosition);
+                }
+            } else {
+                mWindow = mBulkCursor.getWindow(newPosition);
+            }
+        } catch (RemoteException ex) {
+            // We tried to get a window and failed
+            Log.e(TAG, "Unable to get window because the remote process is dead");
+            return false;
+        }
+
+        // Couldn't obtain a window, something is wrong
+        if (mWindow == null) {
+            return false;
+        }
+
+        return true;
+    }
+
+    @Override
+    public void deactivate() {
+        // This will call onInvalidated(), so make sure to do it before calling release,
+        // which is what actually makes the data set invalid.
+        super.deactivate();
+
+        try {
+            mBulkCursor.deactivate();
+        } catch (RemoteException ex) {
+            Log.w(TAG, "Remote process exception when deactivating");
+        }
+        mWindow = null;
+    }
+    
+    @Override
+    public void close() {
+        super.close();
+        try {
+            mBulkCursor.close();
+        } catch (RemoteException ex) {
+            Log.w(TAG, "Remote process exception when closing");
+        }
+        mWindow = null;        
+    }
+
+    @Override
+    public boolean requery() {
+        try {
+            int oldCount = mCount;
+            //TODO get the window from a pool somewhere to avoid creating the memory dealer
+            mCount = mBulkCursor.requery(getObserver(), new CursorWindow(
+                    false /* the window will be accessed across processes */));
+            if (mCount != -1) {
+                mPos = -1;
+                mWindow = null;
+
+                // super.requery() will call onChanged. Do it here instead of relying on the
+                // observer from the far side so that observers can see a correct value for mCount
+                // when responding to onChanged.
+                super.requery();
+                return true;
+            } else {
+                deactivate();
+                return false;
+            }
+        } catch (Exception ex) {
+            Log.e(TAG, "Unable to requery because the remote process exception " + ex.getMessage());
+            deactivate();
+            return false;
+        }
+    }
+
+    /**
+     * @hide
+     * @deprecated
+     */
+    @Override
+    public boolean deleteRow() {
+        try {
+            boolean result = mBulkCursor.deleteRow(mPos);
+            if (result != false) {
+                // The window contains the old value, discard it
+                mWindow = null;
+    
+                // Fix up the position
+                mCount = mBulkCursor.count();
+                if (mPos < mCount) {
+                    int oldPos = mPos;
+                    mPos = -1;
+                    moveToPosition(oldPos);
+                } else {
+                    mPos = mCount;
+                }
+
+                // Send the change notification
+                onChange(true);
+            }
+            return result;
+        } catch (RemoteException ex) {
+            Log.e(TAG, "Unable to delete row because the remote process is dead");
+            return false;
+        }
+    }
+
+    @Override
+    public String[] getColumnNames() {
+        return mColumns;
+    }
+
+    /**
+     * @hide
+     * @deprecated
+     */
+    @Override
+    public boolean commitUpdates(Map<? extends Long,
+            ? extends Map<String,Object>> additionalValues) {
+        if (!supportsUpdates()) {
+            Log.e(TAG, "commitUpdates not supported on this cursor, did you include the _id column?");
+            return false;
+        }
+
+        synchronized(mUpdatedRows) {
+            if (additionalValues != null) {
+                mUpdatedRows.putAll(additionalValues);
+            }
+
+            if (mUpdatedRows.size() <= 0) {
+                return false;
+            }
+
+            try {
+                boolean result = mBulkCursor.updateRows(mUpdatedRows);
+    
+                if (result == true) {
+                    mUpdatedRows.clear();
+
+                    // Send the change notification
+                    onChange(true);
+                }
+                return result;
+            } catch (RemoteException ex) {
+                Log.e(TAG, "Unable to commit updates because the remote process is dead");
+                return false;
+            }
+        }
+    }
+
+    @Override
+    public Bundle getExtras() {
+        try {
+            return mBulkCursor.getExtras();
+        } catch (RemoteException e) {
+            // This should never happen because the system kills processes that are using remote
+            // cursors when the provider process is killed.
+            throw new RuntimeException(e);
+        }
+    }
+
+    @Override
+    public Bundle respond(Bundle extras) {
+        try {
+            return mBulkCursor.respond(extras);
+        } catch (RemoteException e) {
+            // This should never happen because the system kills processes that are using remote
+            // cursors when the provider process is killed.
+            throw new RuntimeException(e);
+        }
+    }
+}
+
diff --git a/core/java/android/database/CharArrayBuffer.java b/core/java/android/database/CharArrayBuffer.java
new file mode 100644
index 0000000..73781b7
--- /dev/null
+++ b/core/java/android/database/CharArrayBuffer.java
@@ -0,0 +1,33 @@
+/*
+ * 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 android.database;
+
+/**
+ * This is used for {@link Cursor#copyStringToBuffer}
+ */
+public final class CharArrayBuffer {
+    public CharArrayBuffer(int size) {
+        data = new char[size];
+    }
+    
+    public CharArrayBuffer(char[] buf) {
+        data = buf;
+    }
+    
+    public char[] data; // In and out parameter
+    public int sizeCopied; // Out parameter
+}
diff --git a/core/java/android/database/ContentObservable.java b/core/java/android/database/ContentObservable.java
new file mode 100644
index 0000000..8d7b7c5
--- /dev/null
+++ b/core/java/android/database/ContentObservable.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2007 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 android.database;
+
+/**
+ * A specialization of Observable for ContentObserver that provides methods for
+ * invoking the various callback methods of ContentObserver.
+ */
+public class ContentObservable extends Observable<ContentObserver> {
+
+    @Override
+    public void registerObserver(ContentObserver observer) {
+        super.registerObserver(observer);
+    }
+
+    /**
+     * invokes dispatchUpdate on each observer, unless the observer doesn't want
+     * self-notifications and the update is from a self-notification
+     * @param selfChange
+     */
+    public void dispatchChange(boolean selfChange) {
+        synchronized(mObservers) {
+            for (ContentObserver observer : mObservers) {
+                if (!selfChange || observer.deliverSelfNotifications()) {
+                    observer.dispatchChange(selfChange);
+                }
+            }
+        }
+    }
+
+    /**
+     * invokes onChange on each observer
+     * @param selfChange
+     */
+    public void notifyChange(boolean selfChange) {
+        synchronized(mObservers) {
+            for (ContentObserver observer : mObservers) {
+                observer.onChange(selfChange);
+            }
+        }
+    }
+}
diff --git a/core/java/android/database/ContentObserver.java b/core/java/android/database/ContentObserver.java
new file mode 100644
index 0000000..3b829a3
--- /dev/null
+++ b/core/java/android/database/ContentObserver.java
@@ -0,0 +1,138 @@
+/*
+ * Copyright (C) 2007 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 android.database;
+
+import android.os.Handler;
+
+/**
+ * Receives call backs for changes to content. Must be implemented by objects which are added
+ * to a {@link ContentObservable}.
+ */
+public abstract class ContentObserver {
+
+    private Transport mTransport;
+
+    // Protects mTransport
+    private Object lock = new Object();
+
+    /* package */ Handler mHandler;
+
+    private final class NotificationRunnable implements Runnable {
+
+        private boolean mSelf;
+
+        public NotificationRunnable(boolean self) {
+            mSelf = self;
+        }
+
+        public void run() {
+            ContentObserver.this.onChange(mSelf);
+        }
+    }
+
+    private static final class Transport extends IContentObserver.Stub {
+        ContentObserver mContentObserver;
+
+        public Transport(ContentObserver contentObserver) {
+            mContentObserver = contentObserver;
+        }
+
+        public boolean deliverSelfNotifications() {
+            ContentObserver contentObserver = mContentObserver;
+            if (contentObserver != null) {
+                return contentObserver.deliverSelfNotifications();
+            }
+            return false;
+        }
+
+        public void onChange(boolean selfChange) {
+            ContentObserver contentObserver = mContentObserver;
+            if (contentObserver != null) {
+                contentObserver.dispatchChange(selfChange);
+            }
+        }
+
+        public void releaseContentObserver() {
+            mContentObserver = null;
+        }
+    }
+
+    /**
+     * onChange() will happen on the provider Handler.
+     *
+     * @param handler The handler to run {@link #onChange} on.
+     */
+    public ContentObserver(Handler handler) {
+        mHandler = handler;
+    }
+
+    /**
+     * Gets access to the binder transport object. Not for public consumption.
+     *
+     * {@hide}
+     */
+    public IContentObserver getContentObserver() {
+        synchronized(lock) {
+            if (mTransport == null) {
+                mTransport = new Transport(this);
+            }
+            return mTransport;
+        }
+    }
+
+    /**
+     * Gets access to the binder transport object, and unlinks the transport object
+     * from the ContentObserver. Not for public consumption.
+     *
+     * {@hide}
+     */
+    public IContentObserver releaseContentObserver() {
+        synchronized(lock) {
+            Transport oldTransport = mTransport;
+            if (oldTransport != null) {
+                oldTransport.releaseContentObserver();
+                mTransport = null;
+            }
+            return oldTransport;
+        }
+    }
+
+    /**
+     * Returns true if this observer is interested in notifications for changes
+     * made through the cursor the observer is registered with.
+     */
+    public boolean deliverSelfNotifications() {
+        return false;
+    }
+
+    /**
+     * This method is called when a change occurs to the cursor that
+     * is being observed.
+     *  
+     * @param selfChange true if the update was caused by a call to <code>commit</code> on the
+     *  cursor that is being observed.
+     */
+    public void onChange(boolean selfChange) {}
+
+    public final void dispatchChange(boolean selfChange) {
+        if (mHandler == null) {
+            onChange(selfChange);
+        } else {
+            mHandler.post(new NotificationRunnable(selfChange));
+        }
+    }
+}
diff --git a/core/java/android/database/CrossProcessCursor.java b/core/java/android/database/CrossProcessCursor.java
new file mode 100644
index 0000000..77ba3a5
--- /dev/null
+++ b/core/java/android/database/CrossProcessCursor.java
@@ -0,0 +1,42 @@
+/*
+ * 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 android.database;
+
+public interface CrossProcessCursor extends Cursor{
+    /**
+     * returns a pre-filled window, return NULL if no such window
+     */
+    CursorWindow getWindow();
+
+    /**
+     * copies cursor data into the window start at pos
+     */
+    void fillWindow(int pos, CursorWindow winow);
+
+    /**
+     * This function is called every time the cursor is successfully scrolled
+     * to a new position, giving the subclass a chance to update any state it
+     * may have. If it returns false the move function will also do so and the
+     * cursor will scroll to the beforeFirst position.
+     *
+     * @param oldPosition the position that we're moving from
+     * @param newPosition the position that we're moving to
+     * @return true if the move is successful, false otherwise
+     */
+    boolean onMove(int oldPosition, int newPosition); 
+    
+}
diff --git a/core/java/android/database/Cursor.java b/core/java/android/database/Cursor.java
new file mode 100644
index 0000000..79178f4
--- /dev/null
+++ b/core/java/android/database/Cursor.java
@@ -0,0 +1,587 @@
+/*
+ * 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 android.database;
+
+import android.content.ContentResolver;
+import android.net.Uri;
+import android.os.Bundle;
+
+import java.util.Map;
+
+/**
+ * This interface provides random read-write access to the result set returned
+ * by a database query.
+ */
+public interface Cursor {
+    /**
+     * Returns the numbers of rows in the cursor.
+     *
+     * @return the number of rows in the cursor.
+     */
+    int getCount();
+
+    /**
+     * Returns the current position of the cursor in the row set.
+     * The value is zero-based. When the row set is first returned the cursor
+     * will be at positon -1, which is before the first row. After the
+     * last row is returned another call to next() will leave the cursor past
+     * the last entry, at a position of count().
+     *
+     * @return the current cursor position.
+     */
+    int getPosition();
+
+    /**
+     * Move the cursor by a relative amount, forward or backward, from the
+     * current position. Positive offsets move forwards, negative offsets move
+     * backwards. If the final position is outside of the bounds of the result
+     * set then the resultant position will be pinned to -1 or count() depending
+     * on whether the value is off the front or end of the set, respectively.
+     *
+     * <p>This method will return true if the requested destination was
+     * reachable, otherwise, it returns false. For example, if the cursor is at
+     * currently on the second entry in the result set and move(-5) is called,
+     * the position will be pinned at -1, and false will be returned.
+     *
+     * @param offset the offset to be applied from the current position.
+     * @return whether the requested move fully succeeded.
+     */
+    boolean move(int offset);
+
+    /**
+     * Move the cursor to an absolute position. The valid
+     * range of values is -1 &lt;= position &lt;= count.
+     *
+     * <p>This method will return true if the request destination was reachable, 
+     * otherwise, it returns false.
+     *
+     * @param position the zero-based position to move to.
+     * @return whether the requested move fully succeeded.
+     */
+    boolean moveToPosition(int position);
+
+    /**
+     * Move the cursor to the first row.
+     *
+     * <p>This method will return false if the cursor is empty.
+     *
+     * @return whether the move succeeded.
+     */
+    boolean moveToFirst();
+
+    /**
+     * Move the cursor to the last row.
+     *
+     * <p>This method will return false if the cursor is empty.
+     *
+     * @return whether the move succeeded.
+     */
+    boolean moveToLast();
+
+    /**
+     * Move the cursor to the next row.
+     *
+     * <p>This method will return false if the cursor is already past the
+     * last entry in the result set.
+     *
+     * @return whether the move succeeded.
+     */
+    boolean moveToNext();
+
+    /**
+     * Move the cursor to the previous row.
+     *
+     * <p>This method will return false if the cursor is already before the
+     * first entry in the result set.
+     *
+     * @return whether the move succeeded.
+     */
+    boolean moveToPrevious();
+
+    /**
+     * Returns whether the cursor is pointing to the first row.
+     *
+     * @return whether the cursor is pointing at the first entry.
+     */
+    boolean isFirst();
+
+    /**
+     * Returns whether the cursor is pointing to the last row.
+     *
+     * @return whether the cursor is pointing at the last entry.
+     */
+    boolean isLast();
+
+    /**
+     * Returns whether the cursor is pointing to the position before the first
+     * row.
+     *
+     * @return whether the cursor is before the first result.
+     */
+    boolean isBeforeFirst();
+
+    /**
+     * Returns whether the cursor is pointing to the position after the last
+     * row.
+     *
+     * @return whether the cursor is after the last result.
+     */
+    boolean isAfterLast();
+
+    /**
+     * Removes the row at the current cursor position from the underlying data
+     * store. After this method returns the cursor will be pointing to the row
+     * after the row that is deleted. This has the side effect of decrementing
+     * the result of count() by one.
+     * <p>
+     * The query must have the row ID column in its selection, otherwise this
+     * call will fail.
+     * 
+     * @hide
+     * @return whether the record was successfully deleted.
+     * @deprecated use {@link ContentResolver#delete(Uri, String, String[])}
+     */
+    @Deprecated
+    boolean deleteRow();
+
+    /**
+     * Returns the zero-based index for the given column name, or -1 if the column doesn't exist.
+     * If you expect the column to exist use {@link #getColumnIndexOrThrow(String)} instead, which
+     * will make the error more clear.
+     *
+     * @param columnName the name of the target column.
+     * @return the zero-based column index for the given column name, or -1 if
+     * the column name does not exist.
+     * @see #getColumnIndexOrThrow(String)
+     */
+    int getColumnIndex(String columnName);
+
+    /**
+     * Returns the zero-based index for the given column name, or throws
+     * {@link IllegalArgumentException} if the column doesn't exist. If you're not sure if
+     * a column will exist or not use {@link #getColumnIndex(String)} and check for -1, which
+     * is more efficient than catching the exceptions.
+     *
+     * @param columnName the name of the target column.
+     * @return the zero-based column index for the given column name
+     * @see #getColumnIndex(String)
+     * @throws IllegalArgumentException if the column does not exist
+     */
+    int getColumnIndexOrThrow(String columnName) throws IllegalArgumentException;
+
+    /**
+     * Returns the column name at the given zero-based column index.
+     *
+     * @param columnIndex the zero-based index of the target column.
+     * @return the column name for the given column index.
+     */
+    String getColumnName(int columnIndex);
+
+    /**
+     * Returns a string array holding the names of all of the columns in the
+     * result set in the order in which they were listed in the result.
+     *
+     * @return the names of the columns returned in this query.
+     */
+    String[] getColumnNames();
+
+    /**
+     * Return total number of columns
+     * @return number of columns 
+     */
+    int getColumnCount();
+    
+    /**
+     * Returns the value of the requested column as a byte array.
+     *
+     * <p>If the native content of that column is not blob exception may throw
+     *
+     * @param columnIndex the zero-based index of the target column.
+     * @return the value of that column as a byte array.
+     */
+    byte[] getBlob(int columnIndex);
+
+    /**
+     * Returns the value of the requested column as a String.
+     *
+     * <p>If the native content of that column is not text the result will be
+     * the result of passing the column value to String.valueOf(x).
+     *
+     * @param columnIndex the zero-based index of the target column.
+     * @return the value of that column as a String.
+     */
+    String getString(int columnIndex);
+    
+    /**
+     * Retrieves the requested column text and stores it in the buffer provided.
+     * If the buffer size is not sufficient, a new char buffer will be allocated 
+     * and assigned to CharArrayBuffer.data
+     * @param columnIndex the zero-based index of the target column.
+     *        if the target column is null, return buffer
+     * @param buffer the buffer to copy the text into. 
+     */
+    void copyStringToBuffer(int columnIndex, CharArrayBuffer buffer);
+    
+    /**
+     * Returns the value of the requested column as a short.
+     *
+     * <p>If the native content of that column is not numeric the result will be
+     * the result of passing the column value to Short.valueOf(x).
+     *
+     * @param columnIndex the zero-based index of the target column.
+     * @return the value of that column as a short.
+     */
+    short getShort(int columnIndex);
+
+    /**
+     * Returns the value of the requested column as an int.
+     *
+     * <p>If the native content of that column is not numeric the result will be
+     * the result of passing the column value to Integer.valueOf(x).
+     *
+     * @param columnIndex the zero-based index of the target column.
+     * @return the value of that column as an int.
+     */
+    int getInt(int columnIndex);
+
+    /**
+     * Returns the value of the requested column as a long.
+     *
+     * <p>If the native content of that column is not numeric the result will be
+     * the result of passing the column value to Long.valueOf(x).
+     *
+     * @param columnIndex the zero-based index of the target column.
+     * @return the value of that column as a long.
+     */
+    long getLong(int columnIndex);
+
+    /**
+     * Returns the value of the requested column as a float.
+     *
+     * <p>If the native content of that column is not numeric the result will be
+     * the result of passing the column value to Float.valueOf(x).
+     *
+     * @param columnIndex the zero-based index of the target column.
+     * @return the value of that column as a float.
+     */
+    float getFloat(int columnIndex);
+
+    /**
+     * Returns the value of the requested column as a double.
+     *
+     * <p>If the native content of that column is not numeric the result will be
+     * the result of passing the column value to Double.valueOf(x).
+     *
+     * @param columnIndex the zero-based index of the target column.
+     * @return the value of that column as a double.
+     */
+    double getDouble(int columnIndex);
+
+    /**
+     * Returns <code>true</code> if the value in the indicated column is null.
+     *
+     * @param columnIndex the zero-based index of the target column.
+     * @return whether the column value is null.
+     */
+    boolean isNull(int columnIndex);
+
+    /**
+     * Returns <code>true</code> if the cursor supports updates.
+     *
+     * @return whether the cursor supports updates.
+     * @hide
+     * @deprecated use the {@link ContentResolver} update methods instead of the Cursor
+     * update methods
+     */
+    @Deprecated
+    boolean supportsUpdates();
+
+    /**
+     * Returns <code>true</code> if there are pending updates that have not yet been committed.
+     * 
+     * @return <code>true</code> if there are pending updates that have not yet been committed.
+     * @hide
+     * @deprecated use the {@link ContentResolver} update methods instead of the Cursor
+     * update methods
+     */
+    @Deprecated
+    boolean hasUpdates();
+
+    /**
+     * Updates the value for the given column in the row the cursor is
+     * currently pointing at. Updates are not committed to the backing store
+     * until {@link #commitUpdates()} is called.
+     *
+     * @param columnIndex the zero-based index of the target column.
+     * @param value the new value.
+     * @return whether the operation succeeded.
+     * @hide
+     * @deprecated use the {@link ContentResolver} update methods instead of the Cursor
+     * update methods
+     */
+    @Deprecated
+    boolean updateBlob(int columnIndex, byte[] value);
+
+    /**
+     * Updates the value for the given column in the row the cursor is
+     * currently pointing at. Updates are not committed to the backing store
+     * until {@link #commitUpdates()} is called.
+     *
+     * @param columnIndex the zero-based index of the target column.
+     * @param value the new value.
+     * @return whether the operation succeeded.
+     * @hide
+     * @deprecated use the {@link ContentResolver} update methods instead of the Cursor
+     * update methods
+     */
+    @Deprecated
+    boolean updateString(int columnIndex, String value);
+
+    /**
+     * Updates the value for the given column in the row the cursor is
+     * currently pointing at. Updates are not committed to the backing store
+     * until {@link #commitUpdates()} is called.
+     *
+     * @param columnIndex the zero-based index of the target column.
+     * @param value the new value.
+     * @return whether the operation succeeded.
+     * @hide
+     * @deprecated use the {@link ContentResolver} update methods instead of the Cursor
+     * update methods
+     */
+    @Deprecated
+    boolean updateShort(int columnIndex, short value);
+
+    /**
+     * Updates the value for the given column in the row the cursor is
+     * currently pointing at. Updates are not committed to the backing store
+     * until {@link #commitUpdates()} is called.
+     *
+     * @param columnIndex the zero-based index of the target column.
+     * @param value the new value.
+     * @return whether the operation succeeded.
+     * @hide
+     * @deprecated use the {@link ContentResolver} update methods instead of the Cursor
+     * update methods
+     */
+    @Deprecated
+    boolean updateInt(int columnIndex, int value);
+
+    /**
+     * Updates the value for the given column in the row the cursor is
+     * currently pointing at. Updates are not committed to the backing store
+     * until {@link #commitUpdates()} is called.
+     *
+     * @param columnIndex the zero-based index of the target column.
+     * @param value the new value.
+     * @return whether the operation succeeded.
+     * @hide
+     * @deprecated use the {@link ContentResolver} update methods instead of the Cursor
+     * update methods
+     */
+    @Deprecated
+    boolean updateLong(int columnIndex, long value);
+
+    /**
+     * Updates the value for the given column in the row the cursor is
+     * currently pointing at. Updates are not committed to the backing store
+     * until {@link #commitUpdates()} is called.
+     *
+     * @param columnIndex the zero-based index of the target column.
+     * @param value the new value.
+     * @return whether the operation succeeded.
+     * @hide
+     * @deprecated use the {@link ContentResolver} update methods instead of the Cursor
+     * update methods
+     */
+    @Deprecated
+    boolean updateFloat(int columnIndex, float value);
+
+    /**
+     * Updates the value for the given column in the row the cursor is
+     * currently pointing at. Updates are not committed to the backing store
+     * until {@link #commitUpdates()} is called.
+     *
+     * @param columnIndex the zero-based index of the target column.
+     * @param value the new value.
+     * @return whether the operation succeeded.
+     * @hide
+     * @deprecated use the {@link ContentResolver} update methods instead of the Cursor
+     * update methods
+     */
+    @Deprecated
+    boolean updateDouble(int columnIndex, double value);
+
+    /**
+     * Removes the value for the given column in the row the cursor is
+     * currently pointing at. Updates are not committed to the backing store
+     * until {@link #commitUpdates()} is called.
+     *
+     * @param columnIndex the zero-based index of the target column.
+     * @return whether the operation succeeded.
+     * @hide
+     * @deprecated use the {@link ContentResolver} update methods instead of the Cursor
+     * update methods
+     */
+    @Deprecated
+    boolean updateToNull(int columnIndex);
+
+    /**
+     * Atomically commits all updates to the backing store. After completion,
+     * this method leaves the data in an inconsistent state and you should call
+     * {@link #requery} before reading data from the cursor again.
+     *
+     * @return whether the operation succeeded.
+     * @hide
+     * @deprecated use the {@link ContentResolver} update methods instead of the Cursor
+     * update methods
+     */
+    @Deprecated
+    boolean commitUpdates();
+
+    /**
+     * Atomically commits all updates to the backing store, as well as the
+     * updates included in values. After completion,
+     * this method leaves the data in an inconsistent state and you should call
+     * {@link #requery} before reading data from the cursor again.
+     *
+     * @param values A map from row IDs to Maps associating column names with
+     *               updated values. A null value indicates the field should be
+                     removed.
+     * @return whether the operation succeeded.
+     * @hide
+     * @deprecated use the {@link ContentResolver} update methods instead of the Cursor
+     * update methods
+     */
+    @Deprecated
+    boolean commitUpdates(Map<? extends Long,
+            ? extends Map<String,Object>> values);
+
+    /**
+     * Reverts all updates made to the cursor since the last call to
+     * commitUpdates.
+     * @hide
+     * @deprecated use the {@link ContentResolver} update methods instead of the Cursor
+     * update methods
+     */
+    @Deprecated
+    void abortUpdates();
+
+    /**
+     * Deactivates the Cursor, making all calls on it fail until {@link #requery} is called.
+     * Inactive Cursors use fewer resources than active Cursors.
+     * Calling {@link #requery} will make the cursor active again.
+     */
+    void deactivate();
+
+    /**
+     * Performs the query that created the cursor again, refreshing its 
+     * contents. This may be done at any time, including after a call to {@link
+     * #deactivate}.
+     *
+     * @return true if the requery succeeded, false if not, in which case the
+     *         cursor becomes invalid.
+     */
+    boolean requery();
+
+    /**
+     * Closes the Cursor, releasing all of its resources and making it completely invalid.
+     * Unlike {@link #deactivate()} a call to {@link #requery()} will not make the Cursor valid
+     * again.
+     */
+    void close();
+
+    /**
+     * return true if the cursor is closed
+     * @return true if the cursor is closed.
+     */
+    boolean isClosed();
+    
+    /**
+     * Register an observer that is called when changes happen to the content backing this cursor.
+     * Typically the data set won't change until {@link #requery()} is called.
+     *
+     * @param observer the object that gets notified when the content backing the cursor changes.
+     * @see #unregisterContentObserver(ContentObserver)
+     */
+    void registerContentObserver(ContentObserver observer);
+
+    /**
+     * Unregister an observer that has previously been registered with this
+     * cursor via {@link #registerContentObserver}.
+     *
+     * @param observer the object to unregister.
+     * @see #registerContentObserver(ContentObserver)
+     */
+    void unregisterContentObserver(ContentObserver observer);
+    
+    /**
+     * Register an observer that is called when changes happen to the contents
+     * of the this cursors data set, for example, when the data set is changed via
+     * {@link #requery()}, {@link #deactivate()}, or {@link #close()}.
+     *
+     * @param observer the object that gets notified when the cursors data set changes.
+     * @see #unregisterDataSetObserver(DataSetObserver)
+     */
+    void registerDataSetObserver(DataSetObserver observer);
+
+    /**
+     * Unregister an observer that has previously been registered with this
+     * cursor via {@link #registerContentObserver}.
+     *
+     * @param observer the object to unregister.
+     * @see #registerDataSetObserver(DataSetObserver)
+     */
+    void unregisterDataSetObserver(DataSetObserver observer);
+
+    /**
+     * Register to watch a content URI for changes. This can be the URI of a specific data row (for 
+     * example, "content://my_provider_type/23"), or a a generic URI for a content type.
+     * 
+     * @param cr The content resolver from the caller's context. The listener attached to 
+     * this resolver will be notified.
+     * @param uri The content URI to watch.
+     */
+    void setNotificationUri(ContentResolver cr, Uri uri);
+
+    /**
+     * onMove() will only be called across processes if this method returns true.
+     * @return whether all cursor movement should result in a call to onMove().
+     */
+    boolean getWantsAllOnMoveCalls();
+
+    /**
+     * Returns a bundle of extra values. This is an optional way for cursors to provide out-of-band
+     * metadata to their users. One use of this is for reporting on the progress of network requests
+     * that are required to fetch data for the cursor.
+     *
+     * <p>These values may only change when requery is called.
+     * @return cursor-defined values, or Bundle.EMTPY if there are no values. Never null.
+     */
+    Bundle getExtras();
+
+    /**
+     * This is an out-of-band way for the the user of a cursor to communicate with the cursor. The
+     * structure of each bundle is entirely defined by the cursor.
+     *
+     * <p>One use of this is to tell a cursor that it should retry its network request after it
+     * reported an error.
+     * @param extras extra values, or Bundle.EMTPY. Never null.
+     * @return extra values, or Bundle.EMTPY. Never null.
+     */
+    Bundle respond(Bundle extras);
+}
diff --git a/core/java/android/database/CursorIndexOutOfBoundsException.java b/core/java/android/database/CursorIndexOutOfBoundsException.java
new file mode 100644
index 0000000..1f77d00
--- /dev/null
+++ b/core/java/android/database/CursorIndexOutOfBoundsException.java
@@ -0,0 +1,31 @@
+/*
+ * 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 android.database;
+
+/**
+ * An exception indicating that a cursor is out of bounds.
+ */
+public class CursorIndexOutOfBoundsException extends IndexOutOfBoundsException {
+
+    public CursorIndexOutOfBoundsException(int index, int size) {
+        super("Index " + index + " requested, with a size of " + size);
+    }
+
+    public CursorIndexOutOfBoundsException(String message) {
+        super(message);
+    }
+}
diff --git a/core/java/android/database/CursorJoiner.java b/core/java/android/database/CursorJoiner.java
new file mode 100644
index 0000000..e3c2988
--- /dev/null
+++ b/core/java/android/database/CursorJoiner.java
@@ -0,0 +1,265 @@
+/*
+ * 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 android.database;
+
+import java.util.Iterator;
+
+/**
+ * Does a join on two cursors using the specified columns. The cursors must already
+ * be sorted on each of the specified columns in ascending order. This joiner only
+ * supports the case where the tuple of key column values is unique.
+ * <p>
+ * Typical usage:
+ *
+ * <pre>
+ * CursorJoiner joiner = new CursorJoiner(cursorA, keyColumnsofA, cursorB, keyColumnsofB);
+ * for (CursorJointer.Result joinerResult : joiner) {
+ *     switch (joinerResult) {
+ *         case LEFT:
+ *             // handle case where a row in cursorA is unique
+ *             break;
+ *         case RIGHT:
+ *             // handle case where a row in cursorB is unique
+ *             break;
+ *         case BOTH:
+ *             // handle case where a row with the same key is in both cursors
+ *             break;
+ *     }
+ * }
+ * </pre>
+ */
+public final class CursorJoiner
+        implements Iterator<CursorJoiner.Result>, Iterable<CursorJoiner.Result> {
+    private Cursor mCursorLeft;
+    private Cursor mCursorRight;
+    private boolean mCompareResultIsValid;
+    private Result mCompareResult;
+    private int[] mColumnsLeft;
+    private int[] mColumnsRight;
+    private String[] mValues;
+
+    /**
+     * The result of a call to next().
+     */
+    public enum Result {
+        /** The row currently pointed to by the left cursor is unique */
+        RIGHT,
+        /** The row currently pointed to by the right cursor is unique */
+        LEFT,
+        /** The rows pointed to by both cursors are the same */
+        BOTH
+    }
+
+    /**
+     * Initializes the CursorJoiner and resets the cursors to the first row. The left and right
+     * column name arrays must have the same number of columns.
+     * @param cursorLeft The left cursor to compare
+     * @param columnNamesLeft The column names to compare from the left cursor
+     * @param cursorRight The right cursor to compare
+     * @param columnNamesRight The column names to compare from the right cursor
+     */
+    public CursorJoiner(
+            Cursor cursorLeft, String[] columnNamesLeft,
+            Cursor cursorRight, String[] columnNamesRight) {
+        if (columnNamesLeft.length != columnNamesRight.length) {
+            throw new IllegalArgumentException(
+                    "you must have the same number of columns on the left and right, "
+                            + columnNamesLeft.length + " != " + columnNamesRight.length);
+        }
+
+        mCursorLeft = cursorLeft;
+        mCursorRight = cursorRight;
+
+        mCursorLeft.moveToFirst();
+        mCursorRight.moveToFirst();
+
+        mCompareResultIsValid = false;
+
+        mColumnsLeft = buildColumnIndiciesArray(cursorLeft, columnNamesLeft);
+        mColumnsRight = buildColumnIndiciesArray(cursorRight, columnNamesRight);
+
+        mValues = new String[mColumnsLeft.length * 2];
+    }
+
+    public Iterator<Result> iterator() {
+        return this;
+    }
+
+    /**
+     * Lookup the indicies of the each column name and return them in an array.
+     * @param cursor the cursor that contains the columns
+     * @param columnNames the array of names to lookup
+     * @return an array of column indices
+     */
+    private int[] buildColumnIndiciesArray(Cursor cursor, String[] columnNames) {
+        int[] columns = new int[columnNames.length];
+        for (int i = 0; i < columnNames.length; i++) {
+            columns[i] = cursor.getColumnIndexOrThrow(columnNames[i]);
+        }
+        return columns;
+    }
+
+    /**
+     * Returns whether or not there are more rows to compare using next().
+     * @return true if there are more rows to compare
+     */
+    public boolean hasNext() {
+        if (mCompareResultIsValid) {
+            switch (mCompareResult) {
+                case BOTH:
+                    return !mCursorLeft.isLast() || !mCursorRight.isLast();
+
+                case LEFT:
+                    return !mCursorLeft.isLast() || !mCursorRight.isAfterLast();
+
+                case RIGHT:
+                    return !mCursorLeft.isAfterLast() || !mCursorRight.isLast();
+
+                default:
+                    throw new IllegalStateException("bad value for mCompareResult, "
+                            + mCompareResult);
+            }
+        } else {
+            return !mCursorLeft.isAfterLast() || !mCursorRight.isAfterLast();
+        }
+    }
+
+    /**
+     * Returns the comparison result of the next row from each cursor. If one cursor
+     * has no more rows but the other does then subsequent calls to this will indicate that
+     * the remaining rows are unique.
+     * <p>
+     * The caller must check that hasNext() returns true before calling this.
+     * <p>
+     * Once next() has been called the cursors specified in the result of the call to
+     * next() are guaranteed to point to the row that was indicated. Reading values
+     * from the cursor that was not indicated in the call to next() will result in
+     * undefined behavior.
+     * @return LEFT, if the row pointed to by the left cursor is unique, RIGHT
+     *   if the row pointed to by the right cursor is unique, BOTH if the rows in both
+     *   cursors are the same.
+     */
+    public Result next() {
+        if (!hasNext()) {
+            throw new IllegalStateException("you must only call next() when hasNext() is true");
+        }
+        incrementCursors();
+        assert hasNext();
+
+        boolean hasLeft = !mCursorLeft.isAfterLast();
+        boolean hasRight = !mCursorRight.isAfterLast();
+
+        if (hasLeft && hasRight) {
+            populateValues(mValues, mCursorLeft, mColumnsLeft, 0 /* start filling at index 0 */);
+            populateValues(mValues, mCursorRight, mColumnsRight, 1 /* start filling at index 1 */);
+            switch (compareStrings(mValues)) {
+                case -1:
+                    mCompareResult = Result.LEFT;
+                    break;
+                case 0:
+                    mCompareResult = Result.BOTH;
+                    break;
+                case 1:
+                    mCompareResult = Result.RIGHT;
+                    break;
+            }
+        } else if (hasLeft) {
+            mCompareResult = Result.LEFT;
+        } else  {
+            assert hasRight;
+            mCompareResult = Result.RIGHT;
+        }
+        mCompareResultIsValid = true;
+        return mCompareResult;
+    }
+
+    public void remove() {
+        throw new UnsupportedOperationException("not implemented");
+    }
+
+    /**
+     * Reads the strings from the cursor that are specifed in the columnIndicies
+     * array and saves them in values beginning at startingIndex, skipping a slot
+     * for each value. If columnIndicies has length 3 and startingIndex is 1, the
+     * values will be stored in slots 1, 3, and 5.
+     * @param values the String[] to populate
+     * @param cursor the cursor from which to read
+     * @param columnIndicies the indicies of the values to read from the cursor
+     * @param startingIndex the slot in which to start storing values, and must be either 0 or 1.
+     */
+    private static void populateValues(String[] values, Cursor cursor, int[] columnIndicies,
+            int startingIndex) {
+        assert startingIndex == 0 || startingIndex == 1;
+        for (int i = 0; i < columnIndicies.length; i++) {
+            values[startingIndex + i*2] = cursor.getString(columnIndicies[i]);
+        }
+    }
+
+    /**
+     * Increment the cursors past the rows indicated in the most recent call to next().
+     * This will only have an affect once per call to next().
+     */
+    private void incrementCursors() {
+        if (mCompareResultIsValid) {
+            switch (mCompareResult) {
+                case LEFT:
+                    mCursorLeft.moveToNext();
+                    break;
+                case RIGHT:
+                    mCursorRight.moveToNext();
+                    break;
+                case BOTH:
+                    mCursorLeft.moveToNext();
+                    mCursorRight.moveToNext();
+                    break;
+            }
+            mCompareResultIsValid = false;
+        }
+    }
+
+    /**
+     * Compare the values. Values contains n pairs of strings. If all the pairs of strings match
+     * then returns 0. Otherwise returns the comparison result of the first non-matching pair
+     * of values, -1 if the first of the pair is less than the second of the pair or 1 if it
+     * is greater.
+     * @param values the n pairs of values to compare
+     * @return -1, 0, or 1 as described above.
+     */
+    private static int compareStrings(String... values) {
+        if ((values.length % 2) != 0) {
+            throw new IllegalArgumentException("you must specify an even number of values");
+        }
+
+        for (int index = 0; index < values.length; index+=2) {
+            if (values[index] == null) {
+                if (values[index+1] == null) continue;
+                return -1;
+            }
+
+            if (values[index+1] == null) {
+                return 1;
+            }
+
+            int comp = values[index].compareTo(values[index+1]);
+            if (comp != 0) {
+                return comp < 0 ? -1 : 1;
+            }
+        }
+
+        return 0;
+    }
+}
diff --git a/core/java/android/database/CursorToBulkCursorAdaptor.java b/core/java/android/database/CursorToBulkCursorAdaptor.java
new file mode 100644
index 0000000..19ad946
--- /dev/null
+++ b/core/java/android/database/CursorToBulkCursorAdaptor.java
@@ -0,0 +1,233 @@
+/*
+ * 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 android.database;
+
+import android.database.sqlite.SQLiteMisuseException;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Config;
+import android.util.Log;
+
+import java.util.Map;
+
+
+/**
+ * Wraps a BulkCursor around an existing Cursor making it remotable.
+ *
+ * {@hide}
+ */
+public final class CursorToBulkCursorAdaptor extends BulkCursorNative 
+        implements IBinder.DeathRecipient {
+    private static final String TAG = "Cursor";
+    private final CrossProcessCursor mCursor;
+    private CursorWindow mWindow;
+    private final String mProviderName;
+    private final boolean mReadOnly;
+    private ContentObserverProxy mObserver;
+
+    private static final class ContentObserverProxy extends ContentObserver 
+            {
+        protected IContentObserver mRemote;
+
+        public ContentObserverProxy(IContentObserver remoteObserver, DeathRecipient recipient) {
+            super(null);
+            mRemote = remoteObserver;
+            try {
+                remoteObserver.asBinder().linkToDeath(recipient, 0);
+            } catch (RemoteException e) {
+                // Do nothing, the far side is dead
+            }
+        }
+        
+        public boolean unlinkToDeath(DeathRecipient recipient) {
+            return mRemote.asBinder().unlinkToDeath(recipient, 0);
+        }
+
+        @Override
+        public boolean deliverSelfNotifications() {
+            // The far side handles the self notifications.
+            return false;
+        }
+
+        @Override
+        public void onChange(boolean selfChange) {
+            try {
+                mRemote.onChange(selfChange);
+            } catch (RemoteException ex) {
+                // Do nothing, the far side is dead
+            }
+        }
+    }
+
+    public CursorToBulkCursorAdaptor(Cursor cursor, IContentObserver observer, String providerName,
+            boolean allowWrite, CursorWindow window) {
+        try {
+            mCursor = (CrossProcessCursor) cursor;
+            if (mCursor instanceof AbstractWindowedCursor) {
+                AbstractWindowedCursor windowedCursor = (AbstractWindowedCursor) cursor;
+                if (windowedCursor.hasWindow()) {
+                    if (Log.isLoggable(TAG, Log.VERBOSE) || Config.LOGV) {
+                        Log.v(TAG, "Cross process cursor has a local window before setWindow in "
+                                + providerName, new RuntimeException());
+                    }
+                }
+                windowedCursor.setWindow(window);
+            } else {
+                mWindow = window;
+                mCursor.fillWindow(0, window);
+            }
+        } catch (ClassCastException e) {
+            // TODO Implement this case.
+            throw new UnsupportedOperationException(
+                    "Only CrossProcessCursor cursors are supported across process for now", e);
+        }
+        mProviderName = providerName;
+        mReadOnly = !allowWrite;
+
+        createAndRegisterObserverProxy(observer);
+    }
+    
+    public void binderDied() {
+        mCursor.close();
+        if (mWindow != null) {
+            mWindow.close();
+        }
+    }
+    
+    public CursorWindow getWindow(int startPos) {
+        mCursor.moveToPosition(startPos);
+        
+        if (mWindow != null) {
+            if (startPos < mWindow.getStartPosition() ||
+                    startPos >= (mWindow.getStartPosition() + mWindow.getNumRows())) {
+                mCursor.fillWindow(startPos, mWindow);
+            }            
+            return mWindow;
+        } else {
+            return ((AbstractWindowedCursor)mCursor).getWindow();
+        }
+    }
+
+    public void onMove(int position) {
+        mCursor.onMove(mCursor.getPosition(), position);
+    }
+
+    public int count() {
+        return mCursor.getCount();
+    }
+
+    public String[] getColumnNames() {
+        return mCursor.getColumnNames();
+    }
+
+    public void deactivate() {
+        maybeUnregisterObserverProxy();
+        mCursor.deactivate();
+    }
+
+    public void close() {
+        maybeUnregisterObserverProxy();
+        mCursor.deactivate();       
+        
+    }
+
+    public int requery(IContentObserver observer, CursorWindow window) {
+        if (mWindow == null) {
+            ((AbstractWindowedCursor)mCursor).setWindow(window);
+        }
+        try {
+            if (!mCursor.requery()) {
+                return -1;
+            }
+        } catch (IllegalStateException e) {
+            IllegalStateException leakProgram = new IllegalStateException(
+                    mProviderName + " Requery misuse db, mCursor isClosed:" +
+                    mCursor.isClosed(), e);
+            throw leakProgram;
+        }
+        
+        if (mWindow != null) {
+            mCursor.fillWindow(0, window);
+            mWindow = window;
+        }
+        maybeUnregisterObserverProxy();
+        createAndRegisterObserverProxy(observer);
+        return mCursor.getCount();
+    }
+
+    public boolean getWantsAllOnMoveCalls() {
+        return mCursor.getWantsAllOnMoveCalls();
+    }
+
+    /**
+     * Create a ContentObserver from the observer and register it as an observer on the
+     * underlying cursor.
+     * @param observer the IContentObserver that wants to monitor the cursor
+     * @throws IllegalStateException if an observer is already registered
+     */
+    private void createAndRegisterObserverProxy(IContentObserver observer) {
+        if (mObserver != null) {
+            throw new IllegalStateException("an observer is already registered");
+        }
+        mObserver = new ContentObserverProxy(observer, this);
+        mCursor.registerContentObserver(mObserver);
+    }
+
+    /** Unregister the observer if it is already registered. */
+    private void maybeUnregisterObserverProxy() {
+        if (mObserver != null) {
+            mCursor.unregisterContentObserver(mObserver);
+            mObserver.unlinkToDeath(this);
+            mObserver = null;
+        }
+    }
+
+    public boolean updateRows(Map<? extends Long, ? extends Map<String, Object>> values) {
+        if (mReadOnly) {
+            Log.w("ContentProvider", "Permission Denial: modifying "
+                    + mProviderName
+                    + " from pid=" + Binder.getCallingPid()
+                    + ", uid=" + Binder.getCallingUid());
+            return false;
+        }
+        return mCursor.commitUpdates(values);
+    }
+
+    public boolean deleteRow(int position) {
+        if (mReadOnly) {
+            Log.w("ContentProvider", "Permission Denial: modifying "
+                    + mProviderName
+                    + " from pid=" + Binder.getCallingPid()
+                    + ", uid=" + Binder.getCallingUid());
+            return false;
+        }
+        if (mCursor.moveToPosition(position) == false) {
+            return false;
+        }
+        return mCursor.deleteRow();
+    }
+
+    public Bundle getExtras() {
+        return mCursor.getExtras();
+    }
+
+    public Bundle respond(Bundle extras) {
+        return mCursor.respond(extras);
+    }
+}
diff --git a/core/java/android/database/CursorWindow.java b/core/java/android/database/CursorWindow.java
new file mode 100644
index 0000000..8e26730
--- /dev/null
+++ b/core/java/android/database/CursorWindow.java
@@ -0,0 +1,483 @@
+/*
+ * 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 android.database;
+
+import android.database.sqlite.SQLiteClosable;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * A buffer containing multiple cursor rows.
+ */
+public class CursorWindow extends SQLiteClosable implements Parcelable {
+    /** The pointer to the native window class */
+    @SuppressWarnings("unused")
+    private int nWindow;
+
+    private int mStartPos;
+
+    /**
+     * Creates a new empty window.
+     *
+     * @param localWindow true if this window will be used in this process only
+     */
+    public CursorWindow(boolean localWindow) {
+        mStartPos = 0;
+        native_init(localWindow);
+    }
+
+    /**
+     * Returns the starting position of this window within the entire
+     * Cursor's result set.
+     * 
+     * @return the starting position of this window within the entire
+     * Cursor's result set.
+     */
+    public int getStartPosition() {
+        return mStartPos;
+    }
+
+    /**
+     * Set the start position of cursor window
+     * @param pos
+     */
+    public void setStartPosition(int pos) {
+        mStartPos = pos;
+    }    
+ 
+    /**
+     * Returns the number of rows in this window.
+     * 
+     * @return the number of rows in this window.
+     */
+    public int getNumRows() {
+        acquireReference();
+        try {
+            return getNumRows_native();
+        } finally {
+            releaseReference();
+        }
+    }
+    
+    private native int getNumRows_native();
+    /**
+     * Set number of Columns 
+     * @param columnNum
+     * @return true if success
+     */
+    public boolean setNumColumns(int columnNum) {
+        acquireReference();
+        try {
+            return setNumColumns_native(columnNum);
+        } finally {
+            releaseReference();
+        }
+    }
+    
+    private native boolean setNumColumns_native(int columnNum);
+    
+    /**
+     * Allocate a row in cursor window
+     * @return false if cursor window is out of memory
+     */
+    public boolean allocRow(){
+        acquireReference();
+        try {
+            return allocRow_native();
+        } finally {
+            releaseReference();
+        }
+    }
+    
+    private native boolean allocRow_native();    
+    
+    /**
+     * Free the last row
+     */
+    public void freeLastRow(){
+        acquireReference();
+        try {
+            freeLastRow_native();
+        } finally {
+            releaseReference();
+        }
+    }
+    
+    private native void freeLastRow_native();
+
+    /**
+     * copy byte array to cursor window
+     * @param value
+     * @param row
+     * @param col
+     * @return false if fail to copy
+     */
+    public boolean putBlob(byte[] value, int row, int col) {
+        acquireReference();
+        try {
+            return putBlob_native(value, row - mStartPos, col);
+        } finally {
+            releaseReference();
+        }
+    }
+    
+    private native boolean putBlob_native(byte[] value, int row, int col);    
+
+    /**
+     * Copy String to cursor window
+     * @param value
+     * @param row
+     * @param col
+     * @return false if fail to copy
+     */
+    public boolean putString(String value, int row, int col) {
+        acquireReference();
+        try {
+            return putString_native(value, row - mStartPos, col);
+        } finally {
+            releaseReference();
+        }
+    }
+    
+    private native boolean putString_native(String value, int row, int col);    
+    
+    /**
+     * Copy integer to cursor window
+     * @param value
+     * @param row
+     * @param col
+     * @return false if fail to copy
+     */
+    public boolean putLong(long value, int row, int col) {
+        acquireReference();
+        try {
+            return putLong_native(value, row - mStartPos, col);
+        } finally {
+            releaseReference();
+        }
+    }
+    
+    private native boolean putLong_native(long value, int row, int col);
+    
+
+    /**
+     * Copy double to cursor window 
+     * @param value
+     * @param row
+     * @param col
+     * @return false if fail to copy
+     */
+    public boolean putDouble(double value, int row, int col) {
+        acquireReference();
+        try {
+            return putDouble_native(value, row - mStartPos, col);
+        } finally {
+            releaseReference();
+        }
+    }
+    
+    private native boolean putDouble_native(double value, int row, int col);    
+
+    /**
+     * Set the [row, col] value to NULL
+     * @param row
+     * @param col
+     * @return false if fail to copy
+     */
+    public boolean putNull(int row, int col) {
+        acquireReference();
+        try {
+            return putNull_native(row - mStartPos, col);
+        } finally {
+            releaseReference();
+        }
+    }
+    
+    private native boolean putNull_native(int row, int col);
+    
+
+    /**
+     * Returns {@code true} if given field is {@code NULL}.
+     * 
+     * @param row the row to read from, row - getStartPosition() being the actual row in the window
+     * @param col the column to read from
+     * @return {@code true} if given field is {@code NULL}
+     */
+    public boolean isNull(int row, int col) {
+        acquireReference();
+        try {
+            return isNull_native(row - mStartPos, col);
+        } finally {
+            releaseReference();
+        }
+    }
+    
+    private native boolean isNull_native(int row, int col);
+    
+    /**
+     * Returns a byte array for the given field.
+     *
+     * @param row the row to read from, row - getStartPosition() being the actual row in the window
+     * @param col the column to read from
+     * @return a String value for the given field
+     */
+    public byte[] getBlob(int row, int col) {
+        acquireReference();
+        try {
+            return getBlob_native(row - mStartPos, col);
+        } finally {
+            releaseReference();
+        }
+    }
+
+    private native byte[] getBlob_native(int row, int col);
+
+    /**
+     * Checks if a field contains either a blob or is null.
+     *
+     * @param row the row to read from, row - getStartPosition() being the actual row in the window
+     * @param col the column to read from
+     * @return {@code true} if given field is {@code NULL} or a blob
+     */
+    public boolean isBlob(int row, int col) {
+        acquireReference();
+        try {
+            return isBlob_native(row - mStartPos, col);
+        } finally {
+            releaseReference();
+        }
+    }
+
+    private native boolean isBlob_native(int row, int col);
+
+    /**
+     * Returns a String for the given field.
+     * 
+     * @param row the row to read from, row - getStartPosition() being the actual row in the window 
+     * @param col the column to read from
+     * @return a String value for the given field
+     */
+    public String getString(int row, int col) {
+        acquireReference();
+        try {
+            return getString_native(row - mStartPos, col);
+        } finally {
+            releaseReference();
+        }
+    }
+    
+    private native String getString_native(int row, int col);
+
+    /**
+     * copy the text for the given field in the provided char array.
+     * 
+     * @param row the row to read from, row - getStartPosition() being the actual row in the window 
+     * @param col the column to read from
+     * @param buffer the CharArrayBuffer to copy the text into,      
+     * If the requested string is larger than the buffer 
+     * a new char buffer will be created to hold the string. and assigne to
+     * CharArrayBuffer.data
+      */
+    public void copyStringToBuffer(int row, int col, CharArrayBuffer buffer) {
+        if (buffer == null) {
+            throw new IllegalArgumentException("CharArrayBuffer should not be null");
+        }
+        if (buffer.data == null) {
+            buffer.data = new char[64];
+        }
+        acquireReference();
+        try {
+            char[] newbuf = copyStringToBuffer_native(
+                    row - mStartPos, col, buffer.data.length, buffer);
+            if (newbuf != null) {
+                buffer.data = newbuf;
+            }
+        } finally {
+            releaseReference();
+        }
+    }
+    
+    private native char[] copyStringToBuffer_native(
+            int row, int col, int bufferSize, CharArrayBuffer buffer);
+    
+    /**
+     * Returns a long for the given field.
+     * row is 0 based
+     * 
+     * @param row the row to read from, row - getStartPosition() being the actual row in the window 
+     * @param col the column to read from
+     * @return a long value for the given field
+     */
+    public long getLong(int row, int col) {
+        acquireReference();
+        try {
+            return getLong_native(row - mStartPos, col);
+        } finally {
+            releaseReference();
+        }
+    }
+    
+    private native long getLong_native(int row, int col);
+
+    /**
+     * Returns a double for the given field.
+     * row is 0 based
+     * 
+     * @param row the row to read from, row - getStartPosition() being the actual row in the window 
+     * @param col the column to read from
+     * @return a double value for the given field
+     */
+    public double getDouble(int row, int col) {
+        acquireReference();
+        try {
+            return getDouble_native(row - mStartPos, col);
+        } finally {
+            releaseReference();
+        }
+    }
+    
+    private native double getDouble_native(int row, int col);
+
+    /**
+     * Returns a short for the given field.
+     * row is 0 based
+     * 
+     * @param row the row to read from, row - getStartPosition() being the actual row in the window 
+     * @param col the column to read from
+     * @return a short value for the given field
+     */
+    public short getShort(int row, int col) {
+        acquireReference();
+        try {
+            return (short) getLong_native(row - mStartPos, col);
+        } finally {
+            releaseReference();
+        }
+    }
+
+    /**
+     * Returns an int for the given field.
+     * 
+     * @param row the row to read from, row - getStartPosition() being the actual row in the window 
+     * @param col the column to read from
+     * @return an int value for the given field
+     */
+    public int getInt(int row, int col) {
+        acquireReference();
+        try {
+            return (int) getLong_native(row - mStartPos, col);
+        } finally {
+            releaseReference();
+        }
+    }
+    
+    /**
+     * Returns a float for the given field.
+     * row is 0 based
+     * 
+     * @param row the row to read from, row - getStartPosition() being the actual row in the window 
+     * @param col the column to read from
+     * @return a float value for the given field
+     */
+    public float getFloat(int row, int col) {
+        acquireReference();
+        try {
+            return (float) getDouble_native(row - mStartPos, col);
+        } finally {
+            releaseReference();
+        }
+    } 
+    
+    /**
+     * Clears out the existing contents of the window, making it safe to reuse
+     * for new data. Note that the number of columns in the window may NOT
+     * change across a call to clear().
+     */
+    public void clear() {
+        acquireReference();
+        try {
+            mStartPos = 0;        
+            native_clear();
+        } finally {
+            releaseReference();
+        }
+    }
+
+    /** Clears out the native side of things */
+    private native void native_clear();
+
+    /**
+     * Cleans up the native resources associated with the window.
+     */
+    public void close() {
+        releaseReference();
+    }
+    
+    private native void close_native();
+
+    @Override
+    protected void finalize() {
+        // Just in case someone forgot to call close...
+        close_native();
+    }
+    
+    public static final Parcelable.Creator<CursorWindow> CREATOR
+            = new Parcelable.Creator<CursorWindow>() {
+        public CursorWindow createFromParcel(Parcel source) {
+            return new CursorWindow(source);
+        }
+
+        public CursorWindow[] newArray(int size) {
+            return new CursorWindow[size];
+        }
+    };
+
+    public static CursorWindow newFromParcel(Parcel p) {
+        return CREATOR.createFromParcel(p);
+    }
+
+    public int describeContents() {
+        return 0;
+    }
+
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeStrongBinder(native_getBinder());
+        dest.writeInt(mStartPos);
+    }
+
+    private CursorWindow(Parcel source) {
+        IBinder nativeBinder = source.readStrongBinder();
+        mStartPos = source.readInt();
+
+        native_init(nativeBinder);
+    }
+
+    /** Get the binder for the native side of the window */
+    private native IBinder native_getBinder();
+
+    /** Does the native side initialization for an empty window */
+    private native void native_init(boolean localOnly);
+
+    /** Does the native side initialization with an existing binder from another process */
+    private native void native_init(IBinder nativeBinder);
+
+    @Override
+    protected void onAllReferencesReleased() {
+        close_native();        
+    }
+}
diff --git a/core/java/android/database/CursorWrapper.java b/core/java/android/database/CursorWrapper.java
new file mode 100644
index 0000000..f0aa7d7
--- /dev/null
+++ b/core/java/android/database/CursorWrapper.java
@@ -0,0 +1,305 @@
+/*
+ * 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 android.database;
+
+import android.content.ContentResolver;
+import android.database.CharArrayBuffer;
+import android.net.Uri;
+import android.os.Bundle;
+
+import java.util.Map;
+
+/**
+ * Wrapper class for Cursor that delegates all calls to the actual cursor object
+ */
+
+public class CursorWrapper implements Cursor {
+
+    public CursorWrapper(Cursor cursor) {
+        mCursor = cursor;
+    }
+    
+    /**
+     * @hide
+     * @deprecated
+     */
+    public void abortUpdates() {
+        mCursor.abortUpdates();
+    }
+
+    public void close() {
+        mCursor.close(); 
+    }
+ 
+    public boolean isClosed() {
+        return mCursor.isClosed();
+    }
+
+    /**
+     * @hide
+     * @deprecated
+     */
+    public boolean commitUpdates() {
+        return mCursor.commitUpdates();
+    }
+
+    /**
+     * @hide
+     * @deprecated
+     */
+    public boolean commitUpdates(
+            Map<? extends Long, ? extends Map<String, Object>> values) {
+        return mCursor.commitUpdates(values);
+    }
+
+    public int getCount() {
+        return mCursor.getCount();
+    }
+
+    public void deactivate() {
+        mCursor.deactivate();
+    }
+
+    /**
+     * @hide
+     * @deprecated
+     */
+    public boolean deleteRow() {
+        return mCursor.deleteRow();
+    }
+
+    public boolean moveToFirst() {
+        return mCursor.moveToFirst();
+    }
+
+    public int getColumnCount() {
+        return mCursor.getColumnCount();
+    }
+
+    public int getColumnIndex(String columnName) {
+        return mCursor.getColumnIndex(columnName);
+    }
+
+    public int getColumnIndexOrThrow(String columnName)
+            throws IllegalArgumentException {
+        return mCursor.getColumnIndexOrThrow(columnName);
+    }
+
+    public String getColumnName(int columnIndex) {
+         return mCursor.getColumnName(columnIndex);
+    }
+
+    public String[] getColumnNames() {
+        return mCursor.getColumnNames();
+    }
+
+    public double getDouble(int columnIndex) {
+        return mCursor.getDouble(columnIndex);
+    }
+
+    public Bundle getExtras() {
+        return mCursor.getExtras();
+    }
+
+    public float getFloat(int columnIndex) {
+        return mCursor.getFloat(columnIndex);
+    }
+
+    public int getInt(int columnIndex) {
+        return mCursor.getInt(columnIndex);
+    }
+
+    public long getLong(int columnIndex) {
+        return mCursor.getLong(columnIndex);
+    }
+
+    public short getShort(int columnIndex) {
+        return mCursor.getShort(columnIndex);
+    }
+
+    public String getString(int columnIndex) {
+        return mCursor.getString(columnIndex);
+    }
+    
+    public void copyStringToBuffer(int columnIndex, CharArrayBuffer buffer) {
+        mCursor.copyStringToBuffer(columnIndex, buffer);
+    }
+
+    public byte[] getBlob(int columnIndex) {
+        return mCursor.getBlob(columnIndex);
+    }
+    
+    public boolean getWantsAllOnMoveCalls() {
+        return mCursor.getWantsAllOnMoveCalls();
+    }
+
+    /**
+     * @hide
+     * @deprecated
+     */
+    public boolean hasUpdates() {
+        return mCursor.hasUpdates();
+    }
+
+    public boolean isAfterLast() {
+        return mCursor.isAfterLast();
+    }
+
+    public boolean isBeforeFirst() {
+        return mCursor.isBeforeFirst();
+    }
+
+    public boolean isFirst() {
+        return mCursor.isFirst();
+    }
+
+    public boolean isLast() {
+        return mCursor.isLast();
+    }
+
+    public boolean isNull(int columnIndex) {
+        return mCursor.isNull(columnIndex);
+    }
+
+    public boolean moveToLast() {
+        return mCursor.moveToLast();
+    }
+
+    public boolean move(int offset) {
+        return mCursor.move(offset);
+    }
+
+    public boolean moveToPosition(int position) {
+        return mCursor.moveToPosition(position);
+    }
+
+    public boolean moveToNext() {
+        return mCursor.moveToNext();
+    }
+
+    public int getPosition() {
+        return mCursor.getPosition();
+    }
+
+    public boolean moveToPrevious() {
+        return mCursor.moveToPrevious();
+    }
+
+    public void registerContentObserver(ContentObserver observer) {
+        mCursor.registerContentObserver(observer);   
+    }
+
+    public void registerDataSetObserver(DataSetObserver observer) {
+        mCursor.registerDataSetObserver(observer);   
+    }
+
+    public boolean requery() {
+        return mCursor.requery();
+    }
+
+    public Bundle respond(Bundle extras) {
+        return mCursor.respond(extras);
+    }
+
+    public void setNotificationUri(ContentResolver cr, Uri uri) {
+        mCursor.setNotificationUri(cr, uri);        
+    }
+
+    /**
+     * @hide
+     * @deprecated
+     */
+    public boolean supportsUpdates() {
+        return mCursor.supportsUpdates();
+    }
+
+    public void unregisterContentObserver(ContentObserver observer) {
+        mCursor.unregisterContentObserver(observer);        
+    }
+
+    public void unregisterDataSetObserver(DataSetObserver observer) {
+        mCursor.unregisterDataSetObserver(observer);
+    }
+
+    /**
+     * @hide
+     * @deprecated
+     */
+    public boolean updateDouble(int columnIndex, double value) {
+        return mCursor.updateDouble(columnIndex, value);
+    }
+
+    /**
+     * @hide
+     * @deprecated
+     */
+    public boolean updateFloat(int columnIndex, float value) {
+        return mCursor.updateFloat(columnIndex, value);
+    }
+
+    /**
+     * @hide
+     * @deprecated
+     */
+    public boolean updateInt(int columnIndex, int value) {
+        return mCursor.updateInt(columnIndex, value);
+    }
+
+    /**
+     * @hide
+     * @deprecated
+     */
+    public boolean updateLong(int columnIndex, long value) {
+        return mCursor.updateLong(columnIndex, value);
+    }
+
+    /**
+     * @hide
+     * @deprecated
+     */
+    public boolean updateShort(int columnIndex, short value) {
+        return mCursor.updateShort(columnIndex, value);
+    }
+
+    /**
+     * @hide
+     * @deprecated
+     */
+    public boolean updateString(int columnIndex, String value) {
+        return mCursor.updateString(columnIndex, value);
+    }
+
+    /**
+     * @hide
+     * @deprecated
+     */
+    public boolean updateBlob(int columnIndex, byte[] value) {
+        return mCursor.updateBlob(columnIndex, value);
+    }
+
+    /**
+     * @hide
+     * @deprecated
+     */
+    public boolean updateToNull(int columnIndex) {
+        return mCursor.updateToNull(columnIndex);
+    }
+    
+    private Cursor mCursor;
+    
+}
+
diff --git a/core/java/android/database/DataSetObservable.java b/core/java/android/database/DataSetObservable.java
new file mode 100644
index 0000000..9200e81
--- /dev/null
+++ b/core/java/android/database/DataSetObservable.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2007 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 android.database;
+
+/**
+ * A specialization of Observable for DataSetObserver that provides methods for
+ * invoking the various callback methods of DataSetObserver.
+ */
+public class DataSetObservable extends Observable<DataSetObserver> {
+    /**
+     * Invokes onChanged on each observer. Called when the data set being observed has
+     * changed, and which when read contains the new state of the data.
+     */
+    public void notifyChanged() {
+        synchronized(mObservers) {
+            for (DataSetObserver observer : mObservers) {
+                observer.onChanged();
+            }
+        }
+    }
+
+    /**
+     * Invokes onInvalidated on each observer. Called when the data set being monitored
+     * has changed such that it is no longer valid.
+     */
+    public void notifyInvalidated() {
+        synchronized (mObservers) {
+            for (DataSetObserver observer : mObservers) {
+                observer.onInvalidated();
+            }
+        }
+    }
+}
diff --git a/core/java/android/database/DataSetObserver.java b/core/java/android/database/DataSetObserver.java
new file mode 100644
index 0000000..28616c8
--- /dev/null
+++ b/core/java/android/database/DataSetObserver.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2007 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 android.database;
+
+/**
+ * Receives call backs when a data set has been changed, or made invalid. The typically data sets
+ * that are observed are {@link Cursor}s or {@link android.widget.Adapter}s.
+ * DataSetObserver must be implemented by objects which are added to a DataSetObservable.
+ */
+public abstract class DataSetObserver {
+    /**
+     * This method is called when the entire data set has changed,
+     * most likely through a call to {@link Cursor#requery()} on a {@link Cursor}.
+     */
+    public void onChanged() {
+        // Do nothing
+    }
+
+    /**
+     * This method is called when the entire data becomes invalid,
+     * most likely through a call to {@link Cursor#deactivate()} or {@link Cursor#close()} on a
+     * {@link Cursor}.
+     */
+    public void onInvalidated() {
+        // Do nothing
+    }
+}
diff --git a/core/java/android/database/DatabaseUtils.java b/core/java/android/database/DatabaseUtils.java
new file mode 100644
index 0000000..10f3806
--- /dev/null
+++ b/core/java/android/database/DatabaseUtils.java
@@ -0,0 +1,1018 @@
+/*
+ * 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 android.database;
+
+import org.apache.commons.codec.binary.Hex;
+
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.sqlite.SQLiteAbortException;
+import android.database.sqlite.SQLiteConstraintException;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteDatabaseCorruptException;
+import android.database.sqlite.SQLiteDiskIOException;
+import android.database.sqlite.SQLiteException;
+import android.database.sqlite.SQLiteFullException;
+import android.database.sqlite.SQLiteProgram;
+import android.database.sqlite.SQLiteStatement;
+import android.os.Parcel;
+import android.text.TextUtils;
+import android.util.Config;
+import android.util.Log;
+
+import java.io.FileNotFoundException;
+import java.io.PrintStream;
+import java.text.Collator;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Static utility methods for dealing with databases and {@link Cursor}s.
+ */
+public class DatabaseUtils {
+    private static final String TAG = "DatabaseUtils";
+
+    private static final boolean DEBUG = false;
+    private static final boolean LOCAL_LOGV = DEBUG ? Config.LOGD : Config.LOGV;
+
+    private static final String[] countProjection = new String[]{"count(*)"};
+
+    /**
+     * Special function for writing an exception result at the header of
+     * a parcel, to be used when returning an exception from a transaction.
+     * exception will be re-thrown by the function in another process
+     * @param reply Parcel to write to
+     * @param e The Exception to be written.
+     * @see Parcel#writeNoException
+     * @see Parcel#writeException
+     */
+    public static final void writeExceptionToParcel(Parcel reply, Exception e) {
+        int code = 0;
+        boolean logException = true;
+        if (e instanceof FileNotFoundException) {
+            code = 1;
+            logException = false;
+        } else if (e instanceof IllegalArgumentException) {
+            code = 2;
+        } else if (e instanceof UnsupportedOperationException) {
+            code = 3;
+        } else if (e instanceof SQLiteAbortException) {
+            code = 4;
+        } else if (e instanceof SQLiteConstraintException) {
+            code = 5;
+        } else if (e instanceof SQLiteDatabaseCorruptException) {
+            code = 6;
+        } else if (e instanceof SQLiteFullException) {
+            code = 7;
+        } else if (e instanceof SQLiteDiskIOException) {
+            code = 8;
+        } else if (e instanceof SQLiteException) {
+            code = 9;
+        } else {
+            reply.writeException(e);
+            Log.e(TAG, "Writing exception to parcel", e);
+            return;
+        }
+        reply.writeInt(code);
+        reply.writeString(e.getMessage());
+
+        if (logException) {
+            Log.e(TAG, "Writing exception to parcel", e);
+        }
+    }
+
+    /**
+     * Special function for reading an exception result from the header of
+     * a parcel, to be used after receiving the result of a transaction.  This
+     * will throw the exception for you if it had been written to the Parcel,
+     * otherwise return and let you read the normal result data from the Parcel.
+     * @param reply Parcel to read from
+     * @see Parcel#writeNoException
+     * @see Parcel#readException
+     */
+    public static final void readExceptionFromParcel(Parcel reply) {
+        int code = reply.readInt();
+        if (code == 0) return;
+        String msg = reply.readString();
+        DatabaseUtils.readExceptionFromParcel(reply, msg, code);
+    }
+
+    public static void readExceptionWithFileNotFoundExceptionFromParcel(
+            Parcel reply) throws FileNotFoundException {
+        int code = reply.readInt();
+        if (code == 0) return;
+        String msg = reply.readString();
+        if (code == 1) {
+            throw new FileNotFoundException(msg);
+        } else {
+            DatabaseUtils.readExceptionFromParcel(reply, msg, code);
+        }
+    }
+
+    private static final void readExceptionFromParcel(Parcel reply, String msg, int code) {
+        switch (code) {
+            case 2:
+                throw new IllegalArgumentException(msg);
+            case 3:
+                throw new UnsupportedOperationException(msg);
+            case 4:
+                throw new SQLiteAbortException(msg);
+            case 5:
+                throw new SQLiteConstraintException(msg);
+            case 6:
+                throw new SQLiteDatabaseCorruptException(msg);
+            case 7:
+                throw new SQLiteFullException(msg);
+            case 8:
+                throw new SQLiteDiskIOException(msg);
+            case 9:
+                throw new SQLiteException(msg);
+            default:
+                reply.readException(code, msg);
+        }
+    }
+
+    /**
+     * Binds the given Object to the given SQLiteProgram using the proper
+     * typing. For example, bind numbers as longs/doubles, and everything else
+     * as a string by call toString() on it.
+     *
+     * @param prog the program to bind the object to
+     * @param index the 1-based index to bind at
+     * @param value the value to bind
+     */
+    public static void bindObjectToProgram(SQLiteProgram prog, int index,
+            Object value) {
+        if (value == null) {
+            prog.bindNull(index);
+        } else if (value instanceof Double || value instanceof Float) {
+            prog.bindDouble(index, ((Number)value).doubleValue());
+        } else if (value instanceof Number) {
+            prog.bindLong(index, ((Number)value).longValue());
+        } else if (value instanceof Boolean) {
+            Boolean bool = (Boolean)value;
+            if (bool) {
+                prog.bindLong(index, 1);
+            } else {
+                prog.bindLong(index, 0);
+            }
+        } else if (value instanceof byte[]){
+            prog.bindBlob(index, (byte[]) value);
+        } else {
+            prog.bindString(index, value.toString());
+        }
+    }
+
+    /**
+     * Appends an SQL string to the given StringBuilder, including the opening
+     * and closing single quotes. Any single quotes internal to sqlString will
+     * be escaped.
+     *
+     * This method is deprecated because we want to encourage everyone
+     * to use the "?" binding form.  However, when implementing a
+     * ContentProvider, one may want to add WHERE clauses that were
+     * not provided by the caller.  Since "?" is a positional form,
+     * using it in this case could break the caller because the
+     * indexes would be shifted to accomodate the ContentProvider's
+     * internal bindings.  In that case, it may be necessary to
+     * construct a WHERE clause manually.  This method is useful for
+     * those cases.
+     *
+     * @param sb the StringBuilder that the SQL string will be appended to
+     * @param sqlString the raw string to be appended, which may contain single
+     *                  quotes
+     */
+    public static void appendEscapedSQLString(StringBuilder sb, String sqlString) {
+        sb.append('\'');
+        if (sqlString.indexOf('\'') != -1) {
+            int length = sqlString.length();
+            for (int i = 0; i < length; i++) {
+                char c = sqlString.charAt(i);
+                if (c == '\'') {
+                    sb.append('\'');
+                }
+                sb.append(c);
+            }
+        } else
+            sb.append(sqlString);
+        sb.append('\'');
+    }
+    
+    /**
+     * SQL-escape a string.
+     */
+    public static String sqlEscapeString(String value) {
+        StringBuilder escaper = new StringBuilder();
+
+        DatabaseUtils.appendEscapedSQLString(escaper, value);
+
+        return escaper.toString();
+    }
+
+    /**
+     * Appends an Object to an SQL string with the proper escaping, etc.
+     */
+    public static final void appendValueToSql(StringBuilder sql, Object value) {
+        if (value == null) {
+            sql.append("NULL");
+        } else if (value instanceof Boolean) {
+            Boolean bool = (Boolean)value;
+            if (bool) {
+                sql.append('1');
+            } else {
+                sql.append('0');
+            }
+        } else {
+            appendEscapedSQLString(sql, value.toString());
+        }
+    }
+    
+    /**
+     * Concatenates two SQL WHERE clauses, handling empty or null values.
+     * @hide
+     */
+    public static String concatenateWhere(String a, String b) {
+        if (TextUtils.isEmpty(a)) {
+            return b;
+        }
+        if (TextUtils.isEmpty(b)) {
+            return a;
+        }
+            
+        return "(" + a + ") AND (" + b + ")";
+    }
+    
+    /**
+     * return the collation key 
+     * @param name
+     * @return the collation key
+     */
+    public static String getCollationKey(String name) {
+        byte [] arr = getCollationKeyInBytes(name);
+        try {
+            return new String(arr, 0, getKeyLen(arr), "ISO8859_1");
+        } catch (Exception ex) {
+            return "";
+        }
+    }
+    
+    /**
+     * return the collation key in hex format
+     * @param name
+     * @return the collation key in hex format
+     */
+    public static String getHexCollationKey(String name) {
+        byte [] arr = getCollationKeyInBytes(name);
+        char[] keys = Hex.encodeHex(arr);
+        return new String(keys, 0, getKeyLen(arr) * 2);
+    }
+    
+    private static int getKeyLen(byte[] arr) {
+        if (arr[arr.length - 1] != 0) {
+            return arr.length;
+        } else {
+            // remove zero "termination"
+            return arr.length-1;
+        }
+    }
+    
+    private static byte[] getCollationKeyInBytes(String name) {
+        if (mColl == null) {
+            mColl = Collator.getInstance();
+            mColl.setStrength(Collator.PRIMARY);
+        }
+        return mColl.getCollationKey(name).toByteArray();        
+    }
+    
+    private static Collator mColl = null;    
+    /**
+     * Prints the contents of a Cursor to System.out. The position is restored
+     * after printing.
+     *
+     * @param cursor the cursor to print
+     */
+    public static void dumpCursor(Cursor cursor) {
+        dumpCursor(cursor, System.out);
+    }
+
+    /**
+     * Prints the contents of a Cursor to a PrintSteam. The position is restored
+     * after printing.
+     *
+     * @param cursor the cursor to print
+     * @param stream the stream to print to
+     */
+    public static void dumpCursor(Cursor cursor, PrintStream stream) {
+        stream.println(">>>>> Dumping cursor " + cursor);
+        if (cursor != null) {
+            int startPos = cursor.getPosition();
+
+            cursor.moveToPosition(-1);
+            while (cursor.moveToNext()) {
+                dumpCurrentRow(cursor, stream);
+            }
+            cursor.moveToPosition(startPos);
+        }
+        stream.println("<<<<<");
+    }
+
+    /**
+     * Prints the contents of a Cursor to a StringBuilder. The position
+     * is restored after printing.
+     *
+     * @param cursor the cursor to print
+     * @param sb the StringBuilder to print to
+     */
+    public static void dumpCursor(Cursor cursor, StringBuilder sb) {
+        sb.append(">>>>> Dumping cursor " + cursor + "\n");
+        if (cursor != null) {
+            int startPos = cursor.getPosition();
+
+            cursor.moveToPosition(-1);
+            while (cursor.moveToNext()) {
+                dumpCurrentRow(cursor, sb);
+            }
+            cursor.moveToPosition(startPos);
+        }
+        sb.append("<<<<<\n");
+    }
+
+    /**
+     * Prints the contents of a Cursor to a String. The position is restored
+     * after printing.
+     *
+     * @param cursor the cursor to print
+     * @return a String that contains the dumped cursor
+     */
+    public static String dumpCursorToString(Cursor cursor) {
+        StringBuilder sb = new StringBuilder();
+        dumpCursor(cursor, sb);
+        return sb.toString();
+    }
+
+    /**
+     * Prints the contents of a Cursor's current row to System.out.
+     *
+     * @param cursor the cursor to print from
+     */
+    public static void dumpCurrentRow(Cursor cursor) {
+        dumpCurrentRow(cursor, System.out);
+    }
+
+    /**
+     * Prints the contents of a Cursor's current row to a PrintSteam.
+     *
+     * @param cursor the cursor to print
+     * @param stream the stream to print to
+     */
+    public static void dumpCurrentRow(Cursor cursor, PrintStream stream) {
+        String[] cols = cursor.getColumnNames();
+        stream.println("" + cursor.getPosition() + " {");
+        int length = cols.length;
+        for (int i = 0; i< length; i++) {
+            String value;
+            try {
+                value = cursor.getString(i);
+            } catch (SQLiteException e) {
+                // assume that if the getString threw this exception then the column is not
+                // representable by a string, e.g. it is a BLOB.
+                value = "<unprintable>";
+            }
+            stream.println("   " + cols[i] + '=' + value);
+        }
+        stream.println("}");
+    }
+
+    /**
+     * Prints the contents of a Cursor's current row to a StringBuilder.
+     *
+     * @param cursor the cursor to print
+     * @param sb the StringBuilder to print to
+     */
+    public static void dumpCurrentRow(Cursor cursor, StringBuilder sb) {
+        String[] cols = cursor.getColumnNames();
+        sb.append("" + cursor.getPosition() + " {\n");
+        int length = cols.length;
+        for (int i = 0; i < length; i++) {
+            String value;
+            try {
+                value = cursor.getString(i);
+            } catch (SQLiteException e) {
+                // assume that if the getString threw this exception then the column is not
+                // representable by a string, e.g. it is a BLOB.
+                value = "<unprintable>";
+            }
+            sb.append("   " + cols[i] + '=' + value + "\n");
+        }
+        sb.append("}\n");
+    }
+
+    /**
+     * Dump the contents of a Cursor's current row to a String.
+     *
+     * @param cursor the cursor to print
+     * @return a String that contains the dumped cursor row
+     */
+    public static String dumpCurrentRowToString(Cursor cursor) {
+        StringBuilder sb = new StringBuilder();
+        dumpCurrentRow(cursor, sb);
+        return sb.toString();
+    }
+
+    /**
+     * Reads a String out of a field in a Cursor and writes it to a Map.
+     *
+     * @param cursor The cursor to read from
+     * @param field The TEXT field to read
+     * @param values The {@link ContentValues} to put the value into, with the field as the key
+     */
+    public static void cursorStringToContentValues(Cursor cursor, String field,
+            ContentValues values) {
+        cursorStringToContentValues(cursor, field, values, field);
+    }
+
+    /**
+     * Reads a String out of a field in a Cursor and writes it to an InsertHelper.
+     *
+     * @param cursor The cursor to read from
+     * @param field The TEXT field to read
+     * @param inserter The InsertHelper to bind into
+     * @param index the index of the bind entry in the InsertHelper
+     */
+    public static void cursorStringToInsertHelper(Cursor cursor, String field,
+            InsertHelper inserter, int index) {
+        inserter.bind(index, cursor.getString(cursor.getColumnIndexOrThrow(field)));
+    }
+
+    /**
+     * Reads a String out of a field in a Cursor and writes it to a Map.
+     *
+     * @param cursor The cursor to read from
+     * @param field The TEXT field to read
+     * @param values The {@link ContentValues} to put the value into, with the field as the key
+     * @param key The key to store the value with in the map
+     */
+    public static void cursorStringToContentValues(Cursor cursor, String field,
+            ContentValues values, String key) {
+        values.put(key, cursor.getString(cursor.getColumnIndexOrThrow(field)));
+    }
+
+    /**
+     * Reads an Integer out of a field in a Cursor and writes it to a Map.
+     *
+     * @param cursor The cursor to read from
+     * @param field The INTEGER field to read
+     * @param values The {@link ContentValues} to put the value into, with the field as the key
+     */
+    public static void cursorIntToContentValues(Cursor cursor, String field, ContentValues values) {
+        cursorIntToContentValues(cursor, field, values, field);
+    }
+
+    /**
+     * Reads a Integer out of a field in a Cursor and writes it to a Map.
+     *
+     * @param cursor The cursor to read from
+     * @param field The INTEGER field to read
+     * @param values The {@link ContentValues} to put the value into, with the field as the key
+     * @param key The key to store the value with in the map
+     */
+    public static void cursorIntToContentValues(Cursor cursor, String field, ContentValues values,
+            String key) {
+        int colIndex = cursor.getColumnIndex(field);
+        if (!cursor.isNull(colIndex)) {
+            values.put(key, cursor.getInt(colIndex));
+        } else {
+            values.put(key, (Integer) null);
+        }
+    }
+
+    /**
+     * Reads a Long out of a field in a Cursor and writes it to a Map.
+     *
+     * @param cursor The cursor to read from
+     * @param field The INTEGER field to read
+     * @param values The {@link ContentValues} to put the value into, with the field as the key
+     */
+    public static void cursorLongToContentValues(Cursor cursor, String field, ContentValues values)
+    {
+        cursorLongToContentValues(cursor, field, values, field);
+    }
+
+    /**
+     * Reads a Long out of a field in a Cursor and writes it to a Map.
+     *
+     * @param cursor The cursor to read from
+     * @param field The INTEGER field to read
+     * @param values The {@link ContentValues} to put the value into
+     * @param key The key to store the value with in the map
+     */
+    public static void cursorLongToContentValues(Cursor cursor, String field, ContentValues values,
+            String key) {
+        int colIndex = cursor.getColumnIndex(field);
+        if (!cursor.isNull(colIndex)) {
+            Long value = Long.valueOf(cursor.getLong(colIndex));
+            values.put(key, value);
+        } else {
+            values.put(key, (Long) null);
+        }
+    }
+
+    /**
+     * Reads a Double out of a field in a Cursor and writes it to a Map.
+     *
+     * @param cursor The cursor to read from
+     * @param field The REAL field to read
+     * @param values The {@link ContentValues} to put the value into
+     */
+    public static void cursorDoubleToCursorValues(Cursor cursor, String field, ContentValues values)
+    {
+        cursorDoubleToContentValues(cursor, field, values, field);
+    }
+
+    /**
+     * Reads a Double out of a field in a Cursor and writes it to a Map.
+     *
+     * @param cursor The cursor to read from
+     * @param field The REAL field to read
+     * @param values The {@link ContentValues} to put the value into
+     * @param key The key to store the value with in the map
+     */
+    public static void cursorDoubleToContentValues(Cursor cursor, String field,
+            ContentValues values, String key) {
+        int colIndex = cursor.getColumnIndex(field);
+        if (!cursor.isNull(colIndex)) {
+            values.put(key, cursor.getDouble(colIndex));
+        } else {
+            values.put(key, (Double) null);
+        }
+    }
+
+    /**
+     * Read the entire contents of a cursor row and store them in a ContentValues.
+     *
+     * @param cursor the cursor to read from.
+     * @param values the {@link ContentValues} to put the row into.
+     */
+    public static void cursorRowToContentValues(Cursor cursor, ContentValues values) {
+        AbstractWindowedCursor awc =
+                (cursor instanceof AbstractWindowedCursor) ? (AbstractWindowedCursor) cursor : null;
+
+        String[] columns = cursor.getColumnNames();
+        int length = columns.length;
+        for (int i = 0; i < length; i++) {
+            if (awc != null && awc.isBlob(i)) {
+                values.put(columns[i], cursor.getBlob(i));
+            } else {
+                values.put(columns[i], cursor.getString(i));
+            }
+        }
+    }
+
+    /**
+     * Query the table for the number of rows in the table.
+     * @param db the database the table is in
+     * @param table the name of the table to query
+     * @return the number of rows in the table
+     */
+    public static long queryNumEntries(SQLiteDatabase db, String table) {
+        Cursor cursor = db.query(table, countProjection,
+                null, null, null, null, null);
+        cursor.moveToFirst();
+        long count = cursor.getLong(0);
+        cursor.deactivate();
+        return count;
+    }
+
+    /**
+     * Utility method to run the query on the db and return the value in the
+     * first column of the first row.
+     */
+    public static long longForQuery(SQLiteDatabase db, String query, String[] selectionArgs) {
+        SQLiteStatement prog = db.compileStatement(query);
+        try {
+            return longForQuery(prog, selectionArgs);
+        } finally {
+            prog.close();
+        }
+    }
+
+    /**
+     * Utility method to run the pre-compiled query and return the value in the
+     * first column of the first row.
+     */
+    public static long longForQuery(SQLiteStatement prog, String[] selectionArgs) {
+        if (selectionArgs != null) {
+            int size = selectionArgs.length;
+            for (int i = 0; i < size; i++) {
+                bindObjectToProgram(prog, i + 1, selectionArgs[i]);
+            }
+        }
+        long value = prog.simpleQueryForLong();
+        return value;
+    }
+
+    /**
+     * Utility method to run the query on the db and return the value in the
+     * first column of the first row.
+     */
+    public static String stringForQuery(SQLiteDatabase db, String query, String[] selectionArgs) {
+        SQLiteStatement prog = db.compileStatement(query);
+        try {
+            return stringForQuery(prog, selectionArgs);
+        } finally {
+            prog.close();
+        }
+    }
+
+    /**
+     * Utility method to run the pre-compiled query and return the value in the
+     * first column of the first row.
+     */
+    public static String stringForQuery(SQLiteStatement prog, String[] selectionArgs) {
+        if (selectionArgs != null) {
+            int size = selectionArgs.length;
+            for (int i = 0; i < size; i++) {
+                bindObjectToProgram(prog, i + 1, selectionArgs[i]);
+            }
+        }
+        String value = prog.simpleQueryForString();
+        return value;
+    }
+
+    /**
+     * This class allows users to do multiple inserts into a table but
+     * compile the SQL insert statement only once, which may increase
+     * performance.
+     */
+    public static class InsertHelper {
+        private final SQLiteDatabase mDb;
+        private final String mTableName;
+        private HashMap<String, Integer> mColumns;
+        private String mInsertSQL = null;
+        private SQLiteStatement mInsertStatement = null;
+        private SQLiteStatement mReplaceStatement = null;
+        private SQLiteStatement mPreparedStatement = null;
+
+        /**
+         * {@hide}
+         *
+         * These are the columns returned by sqlite's "PRAGMA
+         * table_info(...)" command that we depend on.
+         */
+        public static final int TABLE_INFO_PRAGMA_COLUMNNAME_INDEX = 1;
+        public static final int TABLE_INFO_PRAGMA_DEFAULT_INDEX = 4;
+
+        /**
+         * @param db the SQLiteDatabase to insert into
+         * @param tableName the name of the table to insert into
+         */
+        public InsertHelper(SQLiteDatabase db, String tableName) {
+            mDb = db;
+            mTableName = tableName;
+        }
+
+        private void buildSQL() throws SQLException {
+            StringBuilder sb = new StringBuilder(128);
+            sb.append("INSERT INTO ");
+            sb.append(mTableName);
+            sb.append(" (");
+
+            StringBuilder sbv = new StringBuilder(128);
+            sbv.append("VALUES (");
+
+            int i = 1;
+            Cursor cur = null;
+            try {
+                cur = mDb.rawQuery("PRAGMA table_info(" + mTableName + ")", null);
+                mColumns = new HashMap<String, Integer>(cur.getCount());
+                while (cur.moveToNext()) {
+                    String columnName = cur.getString(TABLE_INFO_PRAGMA_COLUMNNAME_INDEX);
+                    String defaultValue = cur.getString(TABLE_INFO_PRAGMA_DEFAULT_INDEX);
+
+                    mColumns.put(columnName, i);
+                    sb.append("'");
+                    sb.append(columnName);
+                    sb.append("'");
+
+                    if (defaultValue == null) {
+                        sbv.append("?");
+                    } else {
+                        sbv.append("COALESCE(?, ");
+                        sbv.append(defaultValue);
+                        sbv.append(")");
+                    }
+
+                    sb.append(i == cur.getCount() ? ") " : ", ");
+                    sbv.append(i == cur.getCount() ? ");" : ", ");
+                    ++i;
+                }
+            } finally {
+                if (cur != null) cur.close();
+            }
+
+            sb.append(sbv);
+
+            mInsertSQL = sb.toString();
+            if (LOCAL_LOGV) Log.v(TAG, "insert statement is " + mInsertSQL);
+        }
+
+        private SQLiteStatement getStatement(boolean allowReplace) throws SQLException {
+            if (allowReplace) {
+                if (mReplaceStatement == null) {
+                    if (mInsertSQL == null) buildSQL();
+                    // chop "INSERT" off the front and prepend "INSERT OR REPLACE" instead.
+                    String replaceSQL = "INSERT OR REPLACE" + mInsertSQL.substring(6);
+                    mReplaceStatement = mDb.compileStatement(replaceSQL);
+                }
+                return mReplaceStatement;
+            } else {
+                if (mInsertStatement == null) {
+                    if (mInsertSQL == null) buildSQL();
+                    mInsertStatement = mDb.compileStatement(mInsertSQL);
+                }
+                return mInsertStatement;
+            }
+        }
+
+        /**
+         * Performs an insert, adding a new row with the given values.
+         *
+         * @param values the set of values with which  to populate the
+         * new row
+         * @param allowReplace if true, the statement does "INSERT OR
+         *   REPLACE" instead of "INSERT", silently deleting any
+         *   previously existing rows that would cause a conflict
+         *
+         * @return the row ID of the newly inserted row, or -1 if an
+         * error occurred
+         */
+        private synchronized long insertInternal(ContentValues values, boolean allowReplace) {
+            try {
+                SQLiteStatement stmt = getStatement(allowReplace);
+                stmt.clearBindings();
+                if (LOCAL_LOGV) Log.v(TAG, "--- inserting in table " + mTableName);
+                for (Map.Entry<String, Object> e: values.valueSet()) {
+                    final String key = e.getKey();
+                    int i = getColumnIndex(key);
+                    DatabaseUtils.bindObjectToProgram(stmt, i, e.getValue());
+                    if (LOCAL_LOGV) {
+                        Log.v(TAG, "binding " + e.getValue() + " to column " +
+                              i + " (" + key + ")");
+                    }
+                }
+                return stmt.executeInsert();
+            } catch (SQLException e) {
+                Log.e(TAG, "Error inserting " + values + " into table  " + mTableName, e);
+                return -1;
+            }
+        }
+
+        /**
+         * Returns the index of the specified column. This is index is suitagble for use
+         * in calls to bind().
+         * @param key the column name
+         * @return the index of the column
+         */
+        public int getColumnIndex(String key) {
+            getStatement(false);
+            final Integer index = mColumns.get(key);
+            if (index == null) {
+                throw new IllegalArgumentException("column '" + key + "' is invalid");
+            }
+            return index;
+        }
+
+        /**
+         * Bind the value to an index. A prepareForInsert() or prepareForReplace()
+         * without a matching execute() must have already have been called.
+         * @param index the index of the slot to which to bind
+         * @param value the value to bind
+         */
+        public void bind(int index, double value) {
+            mPreparedStatement.bindDouble(index, value);
+        }
+
+        /**
+         * Bind the value to an index. A prepareForInsert() or prepareForReplace()
+         * without a matching execute() must have already have been called.
+         * @param index the index of the slot to which to bind
+         * @param value the value to bind
+         */
+        public void bind(int index, float value) {
+            mPreparedStatement.bindDouble(index, value);
+        }
+
+        /**
+         * Bind the value to an index. A prepareForInsert() or prepareForReplace()
+         * without a matching execute() must have already have been called.
+         * @param index the index of the slot to which to bind
+         * @param value the value to bind
+         */
+        public void bind(int index, long value) {
+            mPreparedStatement.bindLong(index, value);
+        }
+
+        /**
+         * Bind the value to an index. A prepareForInsert() or prepareForReplace()
+         * without a matching execute() must have already have been called.
+         * @param index the index of the slot to which to bind
+         * @param value the value to bind
+         */
+        public void bind(int index, int value) {
+            mPreparedStatement.bindLong(index, value);
+        }
+
+        /**
+         * Bind the value to an index. A prepareForInsert() or prepareForReplace()
+         * without a matching execute() must have already have been called.
+         * @param index the index of the slot to which to bind
+         * @param value the value to bind
+         */
+        public void bind(int index, boolean value) {
+            mPreparedStatement.bindLong(index, value ? 1 : 0);
+        }
+
+        /**
+         * Bind null to an index. A prepareForInsert() or prepareForReplace()
+         * without a matching execute() must have already have been called.
+         * @param index the index of the slot to which to bind
+         */
+        public void bindNull(int index) {
+            mPreparedStatement.bindNull(index);
+        }
+
+        /**
+         * Bind the value to an index. A prepareForInsert() or prepareForReplace()
+         * without a matching execute() must have already have been called.
+         * @param index the index of the slot to which to bind
+         * @param value the value to bind
+         */
+        public void bind(int index, byte[] value) {
+            if (value == null) {
+                mPreparedStatement.bindNull(index);
+            } else {
+                mPreparedStatement.bindBlob(index, value);
+            }
+        }
+
+        /**
+         * Bind the value to an index. A prepareForInsert() or prepareForReplace()
+         * without a matching execute() must have already have been called.
+         * @param index the index of the slot to which to bind
+         * @param value the value to bind
+         */
+        public void bind(int index, String value) {
+            if (value == null) {
+                mPreparedStatement.bindNull(index);
+            } else {
+                mPreparedStatement.bindString(index, value);
+            }
+        }
+
+        /**
+         * Performs an insert, adding a new row with the given values.
+         * If the table contains conflicting rows, an error is
+         * returned.
+         *
+         * @param values the set of values with which to populate the
+         * new row
+         *
+         * @return the row ID of the newly inserted row, or -1 if an
+         * error occurred
+         */
+        public long insert(ContentValues values) {
+            return insertInternal(values, false);
+        }
+
+        /**
+         * Execute the previously prepared insert or replace using the bound values
+         * since the last call to prepareForInsert or prepareForReplace.
+         *
+         * <p>Note that calling bind() and then execute() is not thread-safe. The only thread-safe
+         * way to use this class is to call insert() or replace().
+         *
+         * @return the row ID of the newly inserted row, or -1 if an
+         * error occurred
+         */
+        public long execute() {
+            if (mPreparedStatement == null) {
+                throw new IllegalStateException("you must prepare this inserter before calling "
+                        + "execute");
+            }
+            try {
+                if (LOCAL_LOGV) Log.v(TAG, "--- doing insert or replace in table " + mTableName);
+                return mPreparedStatement.executeInsert();
+            } catch (SQLException e) {
+                Log.e(TAG, "Error executing InsertHelper with table " + mTableName, e);
+                return -1;
+            } finally {
+                // you can only call this once per prepare
+                mPreparedStatement = null;
+            }
+        }
+
+        /**
+         * Prepare the InsertHelper for an insert. The pattern for this is:
+         * <ul>
+         * <li>prepareForInsert()
+         * <li>bind(index, value);
+         * <li>bind(index, value);
+         * <li>...
+         * <li>bind(index, value);
+         * <li>execute();
+         * </ul>
+         */
+        public void prepareForInsert() {
+            mPreparedStatement = getStatement(false);
+            mPreparedStatement.clearBindings();
+        }
+
+        /**
+         * Prepare the InsertHelper for a replace. The pattern for this is:
+         * <ul>
+         * <li>prepareForReplace()
+         * <li>bind(index, value);
+         * <li>bind(index, value);
+         * <li>...
+         * <li>bind(index, value);
+         * <li>execute();
+         * </ul>
+         */
+        public void prepareForReplace() {
+            mPreparedStatement = getStatement(true);
+            mPreparedStatement.clearBindings();
+        }
+
+        /**
+         * Performs an insert, adding a new row with the given values.
+         * If the table contains conflicting rows, they are deleted
+         * and replaced with the new row.
+         *
+         * @param values the set of values with which to populate the
+         * new row
+         *
+         * @return the row ID of the newly inserted row, or -1 if an
+         * error occurred
+         */
+        public long replace(ContentValues values) {
+            return insertInternal(values, true);
+        }
+
+        /**
+         * Close this object and release any resources associated with
+         * it.  The behavior of calling <code>insert()</code> after
+         * calling this method is undefined.
+         */
+        public void close() {
+            if (mInsertStatement != null) {
+                mInsertStatement.close();
+                mInsertStatement = null;
+            }
+            if (mReplaceStatement != null) {
+                mReplaceStatement.close();
+                mReplaceStatement = null;
+            }
+            mInsertSQL = null;
+            mColumns = null;
+        }
+    }
+
+    /**
+     * Creates a db and populates it with the sql statements in sqlStatements.
+     *
+     * @param context the context to use to create the db
+     * @param dbName the name of the db to create
+     * @param dbVersion the version to set on the db
+     * @param sqlStatements the statements to use to populate the db. This should be a single string
+     *   of the form returned by sqlite3's <tt>.dump</tt> command (statements separated by
+     *   semicolons)
+     */
+    static public void createDbFromSqlStatements(
+            Context context, String dbName, int dbVersion, String sqlStatements) {
+        SQLiteDatabase db = context.openOrCreateDatabase(dbName, 0, null);
+        // TODO: this is not quite safe since it assumes that all semicolons at the end of a line
+        // terminate statements. It is possible that a text field contains ;\n. We will have to fix
+        // this if that turns out to be a problem.
+        String[] statements = TextUtils.split(sqlStatements, ";\n");
+        for (String statement : statements) {
+            if (TextUtils.isEmpty(statement)) continue;
+            db.execSQL(statement);
+        }
+        db.setVersion(dbVersion);
+        db.close();
+    }
+}
diff --git a/core/java/android/database/IBulkCursor.java b/core/java/android/database/IBulkCursor.java
new file mode 100644
index 0000000..24354fd
--- /dev/null
+++ b/core/java/android/database/IBulkCursor.java
@@ -0,0 +1,90 @@
+/*
+ * 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 android.database;
+
+import android.os.RemoteException;
+import android.os.IBinder;
+import android.os.IInterface;
+import android.os.Bundle;
+
+import java.util.Map;
+
+/**
+ * This interface provides a low-level way to pass bulk cursor data across
+ * both process and language boundries. Application code should use the Cursor
+ * interface directly.
+ * 
+ * {@hide}
+ */
+public interface IBulkCursor extends IInterface 
+{
+    /**
+     * Returns a BulkCursorWindow, which either has a reference to a shared
+     * memory segment with the rows, or an array of JSON strings.
+     */
+    public CursorWindow getWindow(int startPos) throws RemoteException;
+
+    public void onMove(int position) throws RemoteException;
+
+    /**
+     * Returns the number of rows in the cursor.
+     *
+     * @return the number of rows in the cursor.
+     */
+    public int count() throws RemoteException;
+
+    /**
+     * Returns a string array holding the names of all of the columns in the
+     * cursor in the order in which they were listed in the result.
+     *
+     * @return the names of the columns returned in this query.
+     */
+    public String[] getColumnNames() throws RemoteException;
+
+    public boolean updateRows(Map<? extends Long, ? extends Map<String, Object>> values) throws RemoteException;
+
+    public boolean deleteRow(int position) throws RemoteException;
+
+    public void deactivate() throws RemoteException;
+    
+    public void close() throws RemoteException;
+
+    public int requery(IContentObserver observer, CursorWindow window) throws RemoteException;
+
+    boolean getWantsAllOnMoveCalls() throws RemoteException;
+
+    Bundle getExtras() throws RemoteException;
+
+    Bundle respond(Bundle extras) throws RemoteException;
+
+    /* IPC constants */
+    static final String descriptor = "android.content.IBulkCursor";
+
+    static final int GET_CURSOR_WINDOW_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION;
+    static final int COUNT_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 1;
+    static final int GET_COLUMN_NAMES_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 2;
+    static final int UPDATE_ROWS_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 3;
+    static final int DELETE_ROW_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 4;
+    static final int DEACTIVATE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 5;
+    static final int REQUERY_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 6;
+    static final int ON_MOVE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 7;
+    static final int WANTS_ON_MOVE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 8;
+    static final int GET_EXTRAS_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 9;
+    static final int RESPOND_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 10;
+    static final int CLOSE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 11;
+}
+
diff --git a/core/java/android/database/IContentObserver.aidl b/core/java/android/database/IContentObserver.aidl
new file mode 100755
index 0000000..ac2f975
--- /dev/null
+++ b/core/java/android/database/IContentObserver.aidl
@@ -0,0 +1,31 @@
+/*
+**
+** Copyright 2007, 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 android.database;
+
+/**
+ * @hide
+ */
+interface IContentObserver
+{
+    /**
+     * This method is called when an update occurs to the cursor that is being
+     * observed. selfUpdate is true if the update was caused by a call to
+     * commit on the cursor that is being observed.
+     */
+    oneway void onChange(boolean selfUpdate);
+}
diff --git a/core/java/android/database/MatrixCursor.java b/core/java/android/database/MatrixCursor.java
new file mode 100644
index 0000000..cf5a573
--- /dev/null
+++ b/core/java/android/database/MatrixCursor.java
@@ -0,0 +1,267 @@
+/*
+ * Copyright (C) 2007 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 android.database;
+
+import java.util.ArrayList;
+
+/**
+ * A mutable cursor implementation backed by an array of {@code Object}s. Use
+ * {@link #newRow()} to add rows. Automatically expands internal capacity
+ * as needed.
+ */
+public class MatrixCursor extends AbstractCursor {
+
+    private final String[] columnNames;
+    private Object[] data;
+    private int rowCount = 0;
+    private final int columnCount;
+
+    /**
+     * Constructs a new cursor with the given initial capacity.
+     *
+     * @param columnNames names of the columns, the ordering of which
+     *  determines column ordering elsewhere in this cursor
+     * @param initialCapacity in rows
+     */
+    public MatrixCursor(String[] columnNames, int initialCapacity) {
+        this.columnNames = columnNames;
+        this.columnCount = columnNames.length;
+
+        if (initialCapacity < 1) {
+            initialCapacity = 1;
+        }
+
+        this.data = new Object[columnCount * initialCapacity];
+    }
+
+    /**
+     * Constructs a new cursor.
+     *
+     * @param columnNames names of the columns, the ordering of which
+     *  determines column ordering elsewhere in this cursor
+     */
+    public MatrixCursor(String[] columnNames) {
+        this(columnNames, 16);
+    }
+
+    /**
+     * Gets value at the given column for the current row.
+     */
+    private Object get(int column) {
+        if (column < 0 || column >= columnCount) {
+            throw new CursorIndexOutOfBoundsException("Requested column: "
+                    + column + ", # of columns: " +  columnCount);
+        }
+        if (mPos < 0) {
+            throw new CursorIndexOutOfBoundsException("Before first row.");
+        }
+        if (mPos >= rowCount) {
+            throw new CursorIndexOutOfBoundsException("After last row.");
+        }
+        return data[mPos * columnCount + column];
+    }
+
+    /**
+     * Adds a new row to the end and returns a builder for that row. Not safe
+     * for concurrent use.
+     *
+     * @return builder which can be used to set the column values for the new
+     *  row
+     */
+    public RowBuilder newRow() {
+        rowCount++;
+        int endIndex = rowCount * columnCount;
+        ensureCapacity(endIndex);
+        int start = endIndex - columnCount;
+        return new RowBuilder(start, endIndex);
+    }
+
+    /**
+     * Adds a new row to the end with the given column values. Not safe
+     * for concurrent use.
+     *
+     * @throws IllegalArgumentException if {@code columnValues.length !=
+     *  columnNames.length}
+     * @param columnValues in the same order as the the column names specified
+     *  at cursor construction time
+     */
+    public void addRow(Object[] columnValues) {
+        if (columnValues.length != columnCount) {
+            throw new IllegalArgumentException("columnNames.length = "
+                    + columnCount + ", columnValues.length = "
+                    + columnValues.length);
+        }
+
+        int start = rowCount++ * columnCount;
+        ensureCapacity(start + columnCount);
+        System.arraycopy(columnValues, 0, data, start, columnCount);
+    }
+
+    /**
+     * Adds a new row to the end with the given column values. Not safe
+     * for concurrent use.
+     *
+     * @throws IllegalArgumentException if {@code columnValues.size() !=
+     *  columnNames.length}
+     * @param columnValues in the same order as the the column names specified
+     *  at cursor construction time
+     */
+    public void addRow(Iterable<?> columnValues) {
+        int start = rowCount * columnCount;
+        int end = start + columnCount;
+        ensureCapacity(end);
+
+        if (columnValues instanceof ArrayList<?>) {
+            addRow((ArrayList<?>) columnValues, start);
+            return;
+        }
+
+        int current = start;
+        Object[] localData = data;
+        for (Object columnValue : columnValues) {
+            if (current == end) {
+                // TODO: null out row?
+                throw new IllegalArgumentException(
+                        "columnValues.size() > columnNames.length");
+            }
+            localData[current++] = columnValue;
+        }
+
+        if (current != end) {
+            // TODO: null out row?
+            throw new IllegalArgumentException(
+                    "columnValues.size() < columnNames.length");
+        }
+
+        // Increase row count here in case we encounter an exception.
+        rowCount++;
+    }
+
+    /** Optimization for {@link ArrayList}. */
+    private void addRow(ArrayList<?> columnValues, int start) {
+        int size = columnValues.size();
+        if (size != columnCount) {
+            throw new IllegalArgumentException("columnNames.length = "
+                    + columnCount + ", columnValues.size() = " + size);
+        }
+
+        rowCount++;
+        Object[] localData = data;
+        for (int i = 0; i < size; i++) {
+            localData[start + i] = columnValues.get(i);
+        }
+    }
+
+    /** Ensures that this cursor has enough capacity. */
+    private void ensureCapacity(int size) {
+        if (size > data.length) {
+            Object[] oldData = this.data;
+            int newSize = data.length * 2;
+            if (newSize < size) {
+                newSize = size;
+            }
+            this.data = new Object[newSize];
+            System.arraycopy(oldData, 0, this.data, 0, oldData.length);
+        }
+    }
+
+    /**
+     * Builds a row, starting from the left-most column and adding one column
+     * value at a time. Follows the same ordering as the column names specified
+     * at cursor construction time.
+     */
+    public class RowBuilder {
+
+        private int index;
+        private final int endIndex;
+
+        RowBuilder(int index, int endIndex) {
+            this.index = index;
+            this.endIndex = endIndex;
+        }
+
+        /**
+         * Sets the next column value in this row.
+         *
+         * @throws CursorIndexOutOfBoundsException if you try to add too many
+         *  values
+         * @return this builder to support chaining
+         */
+        public RowBuilder add(Object columnValue) {
+            if (index == endIndex) {
+                throw new CursorIndexOutOfBoundsException(
+                        "No more columns left.");
+            }
+
+            data[index++] = columnValue;
+            return this;
+        }
+    }
+
+    // AbstractCursor implementation.
+
+    public int getCount() {
+        return rowCount;
+    }
+
+    public String[] getColumnNames() {
+        return columnNames;
+    }
+
+    public String getString(int column) {
+        return String.valueOf(get(column));
+    }
+
+    public short getShort(int column) {
+        Object value = get(column);
+        return (value instanceof String)
+                ? Short.valueOf((String) value)
+                : ((Number) value).shortValue();
+    }
+
+    public int getInt(int column) {
+        Object value = get(column);
+        return (value instanceof String)
+                ? Integer.valueOf((String) value)
+                : ((Number) value).intValue();
+    }
+
+    public long getLong(int column) {
+        Object value = get(column);
+        return (value instanceof String)
+                ? Long.valueOf((String) value)
+                : ((Number) value).longValue();
+    }
+
+    public float getFloat(int column) {
+        Object value = get(column);
+        return (value instanceof String)
+                ? Float.valueOf((String) value)
+                : ((Number) value).floatValue();
+    }
+
+    public double getDouble(int column) {
+        Object value = get(column);
+        return (value instanceof String)
+                ? Double.valueOf((String) value)
+                : ((Number) value).doubleValue();
+    }
+
+    public boolean isNull(int column) {
+        return get(column) == null;
+    }
+}
diff --git a/core/java/android/database/MergeCursor.java b/core/java/android/database/MergeCursor.java
new file mode 100644
index 0000000..7e91159
--- /dev/null
+++ b/core/java/android/database/MergeCursor.java
@@ -0,0 +1,257 @@
+/*
+ * 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 android.database;
+
+/**
+ * A convience class that lets you present an array of Cursors as a single linear Cursor.
+ * The schema of the cursors presented is entirely up to the creator of the MergeCursor, and
+ * may be different if that is desired. Calls to getColumns, getColumnIndex, etc will return the
+ * value for the row that the MergeCursor is currently pointing at.
+ */
+public class MergeCursor extends AbstractCursor
+{
+    private DataSetObserver mObserver = new DataSetObserver() {
+
+        @Override
+        public void onChanged() {
+            // Reset our position so the optimizations in move-related code
+            // don't screw us over
+            mPos = -1;
+        }
+
+        @Override
+        public void onInvalidated() {
+            mPos = -1;
+        }
+    };
+    
+    public MergeCursor(Cursor[] cursors)
+    {
+        mCursors = cursors;
+        mCursor = cursors[0];
+        
+        for (int i = 0; i < mCursors.length; i++) {
+            if (mCursors[i] == null) continue;
+            
+            mCursors[i].registerDataSetObserver(mObserver);
+        }
+    }
+    
+    @Override
+    public int getCount()
+    {
+        int count = 0;
+        int length = mCursors.length;
+        for (int i = 0 ; i < length ; i++) {
+            if (mCursors[i] != null) {
+                count += mCursors[i].getCount();
+            }
+        }
+        return count;
+    }
+
+    @Override
+    public boolean onMove(int oldPosition, int newPosition)
+    {
+        /* Find the right cursor */
+        mCursor = null;
+        int cursorStartPos = 0;
+        int length = mCursors.length;
+        for (int i = 0 ; i < length; i++) {
+            if (mCursors[i] == null) {
+                continue;
+            }
+            
+            if (newPosition < (cursorStartPos + mCursors[i].getCount())) {
+                mCursor = mCursors[i];
+                break;
+            }
+
+            cursorStartPos += mCursors[i].getCount();
+        }
+
+        /* Move it to the right position */
+        if (mCursor != null) {
+            boolean ret = mCursor.moveToPosition(newPosition - cursorStartPos);
+            return ret;
+        }
+        return false;
+    }
+
+    /**
+     * @hide
+     * @deprecated
+     */
+    @Override
+    public boolean deleteRow()
+    {
+        return mCursor.deleteRow();
+    }
+    
+    /**
+     * @hide
+     * @deprecated
+     */
+    @Override
+    public boolean commitUpdates() {
+        int length = mCursors.length;
+        for (int i = 0 ; i < length ; i++) {
+            if (mCursors[i] != null) {
+                mCursors[i].commitUpdates();
+            }
+        }
+        onChange(true);
+        return true;
+    }
+
+    @Override
+    public String getString(int column)
+    {
+        return mCursor.getString(column);
+    }
+
+    @Override
+    public short getShort(int column)
+    {
+        return mCursor.getShort(column);
+    }
+
+    @Override
+    public int getInt(int column)
+    {
+        return mCursor.getInt(column);
+    }
+
+    @Override
+    public long getLong(int column)
+    {
+        return mCursor.getLong(column);
+    }
+
+    @Override
+    public float getFloat(int column)
+    {
+        return mCursor.getFloat(column);
+    }
+
+    @Override
+    public double getDouble(int column)
+    {
+        return mCursor.getDouble(column);
+    }
+
+    @Override
+    public boolean isNull(int column)
+    {
+        return mCursor.isNull(column);
+    }
+
+    @Override
+    public byte[] getBlob(int column)
+    {
+        return mCursor.getBlob(column);   
+    }
+
+    @Override
+    public String[] getColumnNames()
+    {
+        if (mCursor != null) {
+            return mCursor.getColumnNames();
+        } else {
+            return new String[0];
+        }
+    }
+    
+    @Override
+    public void deactivate()
+    {
+        int length = mCursors.length;
+        for (int i = 0 ; i < length ; i++) {
+            if (mCursors[i] != null) {
+                mCursors[i].deactivate();
+            }
+        }
+    }
+
+    @Override
+    public void close() {
+        int length = mCursors.length;
+        for (int i = 0 ; i < length ; i++) {
+            if (mCursors[i] == null) continue;
+            mCursors[i].close();
+        }
+    }
+
+    @Override
+    public void registerContentObserver(ContentObserver observer) {
+        int length = mCursors.length;
+        for (int i = 0 ; i < length ; i++) {
+            if (mCursors[i] != null) {
+                mCursors[i].registerContentObserver(observer);
+            }
+        }
+    }
+    @Override
+    public void unregisterContentObserver(ContentObserver observer) {
+        int length = mCursors.length;
+        for (int i = 0 ; i < length ; i++) {
+            if (mCursors[i] != null) {
+                mCursors[i].unregisterContentObserver(observer);
+            }
+        }
+    }
+    
+    @Override
+    public void registerDataSetObserver(DataSetObserver observer) {
+        int length = mCursors.length;
+        for (int i = 0 ; i < length ; i++) {
+            if (mCursors[i] != null) {
+                mCursors[i].registerDataSetObserver(observer);
+            }
+        }
+    }
+    
+    @Override
+    public void unregisterDataSetObserver(DataSetObserver observer) {
+        int length = mCursors.length;
+        for (int i = 0 ; i < length ; i++) {
+            if (mCursors[i] != null) {
+                mCursors[i].unregisterDataSetObserver(observer);
+            }
+        }
+    }
+
+    @Override
+    public boolean requery()
+    {
+        int length = mCursors.length;
+        for (int i = 0 ; i < length ; i++) {
+            if (mCursors[i] == null) {
+                continue;
+            }
+
+            if (mCursors[i].requery() == false) {
+                return false;
+            }
+        }
+
+        return true;
+    }
+
+    private Cursor mCursor; // updated in onMove
+    private Cursor[] mCursors;
+}
diff --git a/core/java/android/database/Observable.java b/core/java/android/database/Observable.java
new file mode 100644
index 0000000..b6fecab
--- /dev/null
+++ b/core/java/android/database/Observable.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2007 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 android.database;
+
+import java.util.ArrayList;
+
+/**
+ * Provides methods for (un)registering arbitrary observers in an ArrayList.
+ */
+public abstract class Observable<T> {
+    /**
+     * The list of observers.  An observer can be in the list at most
+     * once and will never be null.
+     */
+    protected final ArrayList<T> mObservers = new ArrayList<T>();
+
+    /**
+     * Adds an observer to the list. The observer cannot be null and it must not already
+     * be registered.
+     * @param observer the observer to register
+     * @throws IllegalArgumentException the observer is null
+     * @throws IllegalStateException the observer is already registered
+     */
+    public void registerObserver(T observer) {
+        if (observer == null) {
+            throw new IllegalArgumentException("The observer is null.");
+        }
+        synchronized(mObservers) {
+            if (mObservers.contains(observer)) {
+                throw new IllegalStateException("Observer " + observer + " is already registered.");
+            }
+            mObservers.add(observer);
+        }
+    }
+
+    /**
+     * Removes a previously registered observer. The observer must not be null and it
+     * must already have been registered.
+     * @param observer the observer to unregister
+     * @throws IllegalArgumentException the observer is null
+     * @throws IllegalStateException the observer is not yet registered
+     */
+    public void unregisterObserver(T observer) {
+        if (observer == null) {
+            throw new IllegalArgumentException("The observer is null.");
+        }
+        synchronized(mObservers) {
+            int index = mObservers.indexOf(observer);
+            if (index == -1) {
+                throw new IllegalStateException("Observer " + observer + " was not registered.");
+            }
+            mObservers.remove(index);
+        }
+    }
+    
+    /**
+     * Remove all registered observer
+     */
+    public void unregisterAll() {
+        synchronized(mObservers) {
+            mObservers.clear();
+        }        
+    }
+}
diff --git a/core/java/android/database/SQLException.java b/core/java/android/database/SQLException.java
new file mode 100644
index 0000000..0386af0
--- /dev/null
+++ b/core/java/android/database/SQLException.java
@@ -0,0 +1,30 @@
+/*
+ * 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 android.database;
+
+/**
+ * An exception that indicates there was an error with SQL parsing or execution.
+ */
+public class SQLException extends RuntimeException
+{
+    public SQLException() {}
+
+    public SQLException(String error)
+    {
+        super(error);
+    }
+}
diff --git a/core/java/android/database/StaleDataException.java b/core/java/android/database/StaleDataException.java
new file mode 100644
index 0000000..ee70beb
--- /dev/null
+++ b/core/java/android/database/StaleDataException.java
@@ -0,0 +1,34 @@
+/*
+ * 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 android.database;
+
+/**
+ * This exception is thrown when a Cursor contains stale data and must be
+ * requeried before being used again.
+ */
+public class StaleDataException extends java.lang.RuntimeException
+{
+    public StaleDataException()
+    {
+        super();
+    }
+
+    public StaleDataException(String description)
+    {
+        super(description);
+    }
+}
diff --git a/core/java/android/database/package.html b/core/java/android/database/package.html
new file mode 100644
index 0000000..1f76d9f
--- /dev/null
+++ b/core/java/android/database/package.html
@@ -0,0 +1,14 @@
+<HTML>
+<BODY>
+Contains classes to explore data returned through a content provider.
+<p>
+If you need to manage data in a private database, use the {@link
+android.database.sqlite} classes. These classes are used to manage the {@link
+android.database.Cursor} object returned from a content provider query. Databases
+are usually created and opened with {@link android.content.Context#openOrCreateDatabase}
+To make requests through
+content providers, you can use the {@link android.content.ContentResolver
+content.ContentResolver} class.
+<p>All databases are stored on the device in <code>/data/data/&lt;package_name&gt;/databases</code>
+</BODY>
+</HTML>
diff --git a/core/java/android/database/sqlite/SQLiteAbortException.java b/core/java/android/database/sqlite/SQLiteAbortException.java
new file mode 100644
index 0000000..64dc4b7
--- /dev/null
+++ b/core/java/android/database/sqlite/SQLiteAbortException.java
@@ -0,0 +1,30 @@
+/*
+ * 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 android.database.sqlite;
+
+/**
+ * An exception that indicates that the SQLite program was aborted.
+ * This can happen either through a call to ABORT in a trigger,
+ * or as the result of using the ABORT conflict clause.
+ */
+public class SQLiteAbortException extends SQLiteException {
+    public SQLiteAbortException() {}
+
+    public SQLiteAbortException(String error) {
+        super(error);
+    }
+}
diff --git a/core/java/android/database/sqlite/SQLiteClosable.java b/core/java/android/database/sqlite/SQLiteClosable.java
new file mode 100644
index 0000000..f64261c
--- /dev/null
+++ b/core/java/android/database/sqlite/SQLiteClosable.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2007 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 android.database.sqlite;
+
+/**
+ * An object create from a SQLiteDatabase that can be closed.
+ */
+public abstract class SQLiteClosable {    
+    private int mReferenceCount = 1;
+    private Object mLock = new Object();
+    protected abstract void onAllReferencesReleased();
+    protected void onAllReferencesReleasedFromContainer(){}
+    
+    public void acquireReference() {
+        synchronized(mLock) {
+            if (mReferenceCount <= 0) {
+                throw new IllegalStateException(
+                        "attempt to acquire a reference on a close SQLiteClosable");
+            }
+            mReferenceCount++;     
+        }
+    }
+    
+    public void releaseReference() {
+        synchronized(mLock) {
+            mReferenceCount--;
+            if (mReferenceCount == 0) {
+                onAllReferencesReleased();
+            }
+        }
+    }
+    
+    public void releaseReferenceFromContainer() {
+        synchronized(mLock) {
+            mReferenceCount--;
+            if (mReferenceCount == 0) {
+                onAllReferencesReleasedFromContainer();
+            }
+        }        
+    }
+}
diff --git a/core/java/android/database/sqlite/SQLiteConstraintException.java b/core/java/android/database/sqlite/SQLiteConstraintException.java
new file mode 100644
index 0000000..e3119eb
--- /dev/null
+++ b/core/java/android/database/sqlite/SQLiteConstraintException.java
@@ -0,0 +1,28 @@
+/*
+ * 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 android.database.sqlite;
+
+/**
+ * An exception that indicates that an integrity constraint was violated.
+ */
+public class SQLiteConstraintException extends SQLiteException {
+    public SQLiteConstraintException() {}
+
+    public SQLiteConstraintException(String error) {
+        super(error);
+    }
+}
diff --git a/core/java/android/database/sqlite/SQLiteCursor.java b/core/java/android/database/sqlite/SQLiteCursor.java
new file mode 100644
index 0000000..70b9b83
--- /dev/null
+++ b/core/java/android/database/sqlite/SQLiteCursor.java
@@ -0,0 +1,606 @@
+/*
+ * 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 android.database.sqlite;
+
+import android.database.AbstractWindowedCursor;
+import android.database.CursorWindow;
+import android.database.DataSetObserver;
+import android.database.SQLException;
+
+import android.os.Handler;
+import android.os.Message;
+import android.os.Process;
+import android.text.TextUtils;
+import android.util.Config;
+import android.util.Log;
+
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.concurrent.locks.ReentrantLock;
+
+/**
+ * A Cursor implementation that exposes results from a query on a
+ * {@link SQLiteDatabase}.
+ */
+public class SQLiteCursor extends AbstractWindowedCursor {
+    static final String TAG = "Cursor";
+    static final int NO_COUNT = -1;
+
+    /** The name of the table to edit */
+    private String mEditTable;
+
+    /** The names of the columns in the rows */
+    private String[] mColumns;
+
+    /** The query object for the cursor */
+    private SQLiteQuery mQuery;
+
+    /** The database the cursor was created from */
+    private SQLiteDatabase mDatabase;
+
+    /** The compiled query this cursor came from */
+    private SQLiteCursorDriver mDriver;
+
+    /** The number of rows in the cursor */
+    private int mCount = NO_COUNT;
+
+    /** A mapping of column names to column indices, to speed up lookups */
+    private Map<String, Integer> mColumnNameMap;
+
+    /** Used to find out where a cursor was allocated in case it never got
+     * released. */
+    private StackTraceElement[] mStackTraceElements;
+    
+    /** 
+     *  mMaxRead is the max items that each cursor window reads 
+     *  default to a very high value
+     */
+    private int mMaxRead = Integer.MAX_VALUE;
+    private int mInitialRead = Integer.MAX_VALUE;
+    private int mCursorState = 0;
+    private ReentrantLock mLock = null;
+    private boolean mPendingData = false;
+    
+    /**
+     *  support for a cursor variant that doesn't always read all results
+     *  initialRead is the initial number of items that cursor window reads 
+     *  if query contains more than this number of items, a thread will be
+     *  created and handle the left over items so that caller can show 
+     *  results as soon as possible 
+     * @param initialRead initial number of items that cursor read
+     * @param maxRead leftover items read at maxRead items per time
+     * @hide
+     */
+    public void setLoadStyle(int initialRead, int maxRead) {
+        mMaxRead = maxRead;
+        mInitialRead = initialRead;
+        mLock = new ReentrantLock(true);
+    }
+    
+    private void queryThreadLock() {
+        if (mLock != null) {
+            mLock.lock();            
+        }
+    }
+    
+    private void queryThreadUnlock() {
+        if (mLock != null) {
+            mLock.unlock();            
+        }
+    }
+    
+    
+    /**
+     * @hide
+     */
+    final private class QueryThread implements Runnable {
+        private final int mThreadState;
+        QueryThread(int version) {
+            mThreadState = version;
+        }
+        private void sendMessage() {
+            if (mNotificationHandler != null) {
+                mNotificationHandler.sendEmptyMessage(1);
+                mPendingData = false;
+            } else {
+                mPendingData = true;
+            }
+            
+        }
+        public void run() {
+             // use cached mWindow, to avoid get null mWindow
+            CursorWindow cw = mWindow;
+            Process.setThreadPriority(Process.myTid(), Process.THREAD_PRIORITY_BACKGROUND);
+            // the cursor's state doesn't change
+            while (true) {
+                mLock.lock();
+                if (mCursorState != mThreadState) {
+                    mLock.unlock();
+                    break;
+                }
+                try {
+                    int count = mQuery.fillWindow(cw, mMaxRead, mCount);
+                    // return -1 means not finished
+                    if (count != 0) {
+                        if (count == NO_COUNT){
+                            mCount += mMaxRead;
+                            sendMessage();
+                        } else {                                
+                            mCount = count;
+                            sendMessage();
+                            break;
+                        }
+                    } else {
+                        break;
+                    }
+                } catch (Exception e) {
+                    // end the tread when the cursor is close
+                    break;
+                } finally {
+                    mLock.unlock();
+                }
+            }
+        }        
+    }
+    
+    /**
+     * @hide
+     */   
+    protected class MainThreadNotificationHandler extends Handler {
+        public void handleMessage(Message msg) {
+            notifyDataSetChange();
+        }
+    }
+    
+    /**
+     * @hide
+     */
+    protected MainThreadNotificationHandler mNotificationHandler;    
+    
+    public void registerDataSetObserver(DataSetObserver observer) {
+        super.registerDataSetObserver(observer);
+        if ((Integer.MAX_VALUE != mMaxRead || Integer.MAX_VALUE != mInitialRead) && 
+                mNotificationHandler == null) {
+            queryThreadLock();
+            try {
+                mNotificationHandler = new MainThreadNotificationHandler();
+                if (mPendingData) {
+                    notifyDataSetChange();
+                    mPendingData = false;
+                }
+            } finally {
+                queryThreadUnlock();
+            }
+        }
+        
+    }
+    
+    /**
+     * Execute a query and provide access to its result set through a Cursor
+     * interface. For a query such as: {@code SELECT name, birth, phone FROM
+     * myTable WHERE ... LIMIT 1,20 ORDER BY...} the column names (name, birth,
+     * phone) would be in the projection argument and everything from
+     * {@code FROM} onward would be in the params argument. This constructor
+     * has package scope.
+     *
+     * @param db a reference to a Database object that is already constructed
+     *     and opened
+     * @param editTable the name of the table used for this query
+     * @param query the rest of the query terms
+     *     cursor is finalized
+     */
+    public SQLiteCursor(SQLiteDatabase db, SQLiteCursorDriver driver,
+            String editTable, SQLiteQuery query) {
+        // The AbstractCursor constructor needs to do some setup.
+        super();
+
+        if (SQLiteDebug.DEBUG_ACTIVE_CURSOR_FINALIZATION) {
+            mStackTraceElements = new Exception().getStackTrace();
+        }
+
+        mDatabase = db;
+        mDriver = driver;
+        mEditTable = editTable;
+        mColumnNameMap = null;
+        mQuery = query;
+
+        try {
+            db.lock();
+
+            // Setup the list of columns
+            int columnCount = mQuery.columnCountLocked();
+            mColumns = new String[columnCount];
+
+            // Read in all column names
+            for (int i = 0; i < columnCount; i++) {
+                String columnName = mQuery.columnNameLocked(i);
+                mColumns[i] = columnName;
+                if (Config.LOGV) {
+                    Log.v("DatabaseWindow", "mColumns[" + i + "] is "
+                            + mColumns[i]);
+                }
+    
+                // Make note of the row ID column index for quick access to it
+                if ("_id".equals(columnName)) {
+                    mRowIdColumnIndex = i;
+                }
+            }
+        } finally {
+            db.unlock();
+        }
+    }
+
+    /**
+     * @return the SQLiteDatabase that this cursor is associated with.
+     */
+    public SQLiteDatabase getDatabase() {
+        return mDatabase;
+    }
+
+    @Override
+    public boolean onMove(int oldPosition, int newPosition) {
+        // Make sure the row at newPosition is present in the window
+        if (mWindow == null || newPosition < mWindow.getStartPosition() ||
+                newPosition >= (mWindow.getStartPosition() + mWindow.getNumRows())) {
+            fillWindow(newPosition);
+        }
+
+        return true;
+    }
+
+    @Override
+    public int getCount() {
+        if (mCount == NO_COUNT) {
+            fillWindow(0);
+        }
+        return mCount;
+    }
+
+    private void fillWindow (int startPos) {
+        if (mWindow == null) {
+            // If there isn't a window set already it will only be accessed locally
+            mWindow = new CursorWindow(true /* the window is local only */);
+        } else {
+            mCursorState++;
+                queryThreadLock();
+                try {
+                    mWindow.clear();
+                } finally {
+                    queryThreadUnlock();
+                }
+        }
+        mWindow.setStartPosition(startPos);
+        mCount = mQuery.fillWindow(mWindow, mInitialRead, 0);
+        // return -1 means not finished
+        if (mCount == NO_COUNT){
+            mCount = startPos + mInitialRead;
+            Thread t = new Thread(new QueryThread(mCursorState), "query thread");
+            t.start();
+        } 
+    }
+
+    @Override
+    public int getColumnIndex(String columnName) {
+        // Create mColumnNameMap on demand
+        if (mColumnNameMap == null) {
+            String[] columns = mColumns;
+            int columnCount = columns.length;
+            HashMap<String, Integer> map = new HashMap<String, Integer>(columnCount, 1);
+            for (int i = 0; i < columnCount; i++) {
+                map.put(columns[i], i);
+            }
+            mColumnNameMap = map;
+        }
+
+        // Hack according to bug 903852
+        final int periodIndex = columnName.lastIndexOf('.');
+        if (periodIndex != -1) {
+            Exception e = new Exception();
+            Log.e(TAG, "requesting column name with table name -- " + columnName, e);
+            columnName = columnName.substring(periodIndex + 1);
+        }
+
+        Integer i = mColumnNameMap.get(columnName);
+        if (i != null) {
+            return i.intValue();
+        } else {
+            return -1;
+        }
+    }
+
+    /**
+     * @hide
+     * @deprecated
+     */
+    @Override
+    public boolean deleteRow() {
+        checkPosition();
+
+        // Only allow deletes if there is an ID column, and the ID has been read from it
+        if (mRowIdColumnIndex == -1 || mCurrentRowID == null) {
+            Log.e(TAG,
+                    "Could not delete row because either the row ID column is not available or it" +
+                    "has not been read.");
+            return false;
+        }
+
+        boolean success;
+
+        /*
+         * Ensure we don't change the state of the database when another
+         * thread is holding the database lock. requery() and moveTo() are also
+         * synchronized here to make sure they get the state of the database
+         * immediately following the DELETE.
+         */
+        mDatabase.lock();
+        try {
+            try {
+                mDatabase.delete(mEditTable, mColumns[mRowIdColumnIndex] + "=?",
+                        new String[] {mCurrentRowID.toString()});
+                success = true;
+            } catch (SQLException e) {
+                success = false;
+            }
+
+            int pos = mPos;
+            requery();
+
+            /*
+             * Ensure proper cursor state. Note that mCurrentRowID changes
+             * in this call.
+             */
+            moveToPosition(pos);
+        } finally {
+            mDatabase.unlock();
+        }
+
+        if (success) {
+            onChange(true);
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    @Override
+    public String[] getColumnNames() {
+        return mColumns;
+    }
+
+    /**
+     * @hide
+     * @deprecated
+     */
+    @Override
+    public boolean supportsUpdates() {
+        return super.supportsUpdates() && !TextUtils.isEmpty(mEditTable);
+    }
+
+    /**
+     * @hide
+     * @deprecated
+     */
+    @Override
+    public boolean commitUpdates(Map<? extends Long,
+            ? extends Map<String, Object>> additionalValues) {
+        if (!supportsUpdates()) {
+            Log.e(TAG, "commitUpdates not supported on this cursor, did you "
+                    + "include the _id column?");
+            return false;
+        }
+
+        /*
+         * Prevent other threads from changing the updated rows while they're
+         * being processed here.
+         */
+        synchronized (mUpdatedRows) {
+            if (additionalValues != null) {
+                mUpdatedRows.putAll(additionalValues);
+            }
+
+            if (mUpdatedRows.size() == 0) {
+                return true;
+            }
+
+            /*
+             * Prevent other threads from changing the database state while
+             * we process the updated rows, and prevents us from changing the
+             * database behind the back of another thread.
+             */
+            mDatabase.beginTransaction();
+            try {
+                StringBuilder sql = new StringBuilder(128);
+
+                // For each row that has been updated
+                for (Map.Entry<Long, Map<String, Object>> rowEntry :
+                        mUpdatedRows.entrySet()) {
+                    Map<String, Object> values = rowEntry.getValue();
+                    Long rowIdObj = rowEntry.getKey();
+
+                    if (rowIdObj == null || values == null) {
+                        throw new IllegalStateException("null rowId or values found! rowId = "
+                                + rowIdObj + ", values = " + values);
+                    }
+
+                    if (values.size() == 0) {
+                        continue;
+                    }
+
+                    long rowId = rowIdObj.longValue();
+
+                    Iterator<Map.Entry<String, Object>> valuesIter =
+                            values.entrySet().iterator();
+
+                    sql.setLength(0);
+                    sql.append("UPDATE " + mEditTable + " SET ");
+
+                    // For each column value that has been updated
+                    Object[] bindings = new Object[values.size()];
+                    int i = 0;
+                    while (valuesIter.hasNext()) {
+                        Map.Entry<String, Object> entry = valuesIter.next();
+                        sql.append(entry.getKey());
+                        sql.append("=?");
+                        bindings[i] = entry.getValue();
+                        if (valuesIter.hasNext()) {
+                            sql.append(", ");
+                        }
+                        i++;
+                    }
+
+                    sql.append(" WHERE " + mColumns[mRowIdColumnIndex]
+                            + '=' + rowId);
+                    sql.append(';');
+                    mDatabase.execSQL(sql.toString(), bindings);
+                    mDatabase.rowUpdated(mEditTable, rowId);
+                }
+                mDatabase.setTransactionSuccessful();
+            } finally {
+                mDatabase.endTransaction();
+            }
+
+            mUpdatedRows.clear();
+        }
+
+        // Let any change observers know about the update
+        onChange(true);
+
+        return true;
+    }
+
+    private void deactivateCommon() {
+        if (Config.LOGV) Log.v(TAG, "<<< Releasing cursor " + this);
+        mCursorState = 0;
+        if (mWindow != null) {
+            mWindow.close();
+            mWindow = null;
+        }
+        if (Config.LOGV) Log.v("DatabaseWindow", "closing window in release()");
+    }
+
+    @Override
+    public void deactivate() {
+        super.deactivate();
+        deactivateCommon();
+        mDriver.cursorDeactivated();
+    }
+
+    @Override
+    public void close() {
+        super.close();
+        deactivateCommon();
+        mQuery.close();
+        mDriver.cursorClosed();
+    }
+
+    @Override
+    public boolean requery() {
+        if (isClosed()) {
+            return false;
+        }
+        long timeStart = 0;
+        if (Config.LOGV) {
+            timeStart = System.currentTimeMillis();
+        }
+        /*
+         * Synchronize on the database lock to ensure that mCount matches the
+         * results of mQuery.requery().
+         */
+        mDatabase.lock();
+        try {
+            if (mWindow != null) {
+                mWindow.clear();
+            }
+            mPos = -1;
+            // This one will recreate the temp table, and get its count
+            mDriver.cursorRequeried(this);
+            mCount = NO_COUNT;
+            mCursorState++;
+            queryThreadLock();
+            try {
+                mQuery.requery();
+            } finally {
+                queryThreadUnlock();
+            }
+        } finally {
+            mDatabase.unlock();
+        }
+
+        if (Config.LOGV) {
+            Log.v("DatabaseWindow", "closing window in requery()");
+            Log.v(TAG, "--- Requery()ed cursor " + this + ": " + mQuery);
+        }
+
+        boolean result = super.requery();
+        if (Config.LOGV) {
+            long timeEnd = System.currentTimeMillis();
+            Log.v(TAG, "requery (" + (timeEnd - timeStart) + " ms): " + mDriver.toString());
+        }
+        return result;
+    }
+
+    @Override
+    public void setWindow(CursorWindow window) {        
+        if (mWindow != null) {
+            mCursorState++;
+            queryThreadLock();
+            try {
+                mWindow.close();
+            } finally {
+                queryThreadUnlock();
+            }
+            mCount = NO_COUNT;
+        }
+        mWindow = window;
+    }
+
+    /**
+     * Changes the selection arguments. The new values take effect after a call to requery().
+     */
+    public void setSelectionArguments(String[] selectionArgs) {
+        mDriver.setBindArguments(selectionArgs);
+    }
+
+    /**
+     * Release the native resources, if they haven't been released yet.
+     */
+    @Override
+    protected void finalize() {
+        try {
+            if (mWindow != null) {
+                close();
+                String message = "Finalizing cursor " + this + " on " + mEditTable
+                        + " that has not been deactivated or closed";
+                if (SQLiteDebug.DEBUG_ACTIVE_CURSOR_FINALIZATION) {
+                    Log.d(TAG, message + "\nThis cursor was created in:");
+                    for (StackTraceElement ste : mStackTraceElements) {
+                        Log.d(TAG, "      " + ste);
+                    }
+                }
+                SQLiteDebug.notifyActiveCursorFinalized();
+                throw new IllegalStateException(message);
+            } else {
+                if (Config.LOGV) {
+                    Log.v(TAG, "Finalizing cursor " + this + " on " + mEditTable);
+                }
+            }
+        } finally {
+            super.finalize();
+        }
+    }
+}
diff --git a/core/java/android/database/sqlite/SQLiteCursorDriver.java b/core/java/android/database/sqlite/SQLiteCursorDriver.java
new file mode 100644
index 0000000..eda1b78
--- /dev/null
+++ b/core/java/android/database/sqlite/SQLiteCursorDriver.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2007 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 android.database.sqlite;
+
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase.CursorFactory;
+
+/**
+ * A driver for SQLiteCursors that is used to create them and gets notified
+ * by the cursors it creates on significant events in their lifetimes.
+ */
+public interface SQLiteCursorDriver {
+    /**
+     * Executes the query returning a Cursor over the result set.
+     * 
+     * @param factory The CursorFactory to use when creating the Cursors, or
+     *         null if standard SQLiteCursors should be returned.
+     * @return a Cursor over the result set
+     */
+    Cursor query(CursorFactory factory, String[] bindArgs);
+
+    /**
+     * Called by a SQLiteCursor when it is released.
+     */
+    void cursorDeactivated();
+
+    /**
+     * Called by a SQLiteCursor when it is requeryed.
+     * 
+     * @return The new count value.
+     */
+    void cursorRequeried(Cursor cursor);
+
+    /**
+     * Called by a SQLiteCursor when it it closed to destroy this object as well.
+     */
+    void cursorClosed();
+
+    /**
+     * Set new bind arguments. These will take effect in cursorRequeried().
+     * @param bindArgs the new arguments
+     */
+    public void setBindArguments(String[] bindArgs);
+}
diff --git a/core/java/android/database/sqlite/SQLiteDatabase.java b/core/java/android/database/sqlite/SQLiteDatabase.java
new file mode 100644
index 0000000..2af080a
--- /dev/null
+++ b/core/java/android/database/sqlite/SQLiteDatabase.java
@@ -0,0 +1,1675 @@
+/*
+ * 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 android.database.sqlite;
+
+import android.content.ContentValues;
+import android.database.Cursor;
+import android.database.DatabaseUtils;
+import android.database.SQLException;
+import android.os.Debug;
+import android.os.SystemClock;
+import android.text.TextUtils;
+import android.util.Config;
+import android.util.Log;
+import android.util.EventLog;
+
+import java.io.File;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+import java.util.WeakHashMap;
+import java.util.concurrent.locks.ReentrantLock;
+
+/**
+ * Exposes methods to manage a SQLite database.
+ * <p>SQLiteDatabase has methods to create, delete, execute SQL commands, and
+ * perform other common database management tasks.
+ * <p>See the Notepad sample application in the SDK for an example of creating
+ * and managing a database.
+ * <p> Database names must be unique within an application, not across all
+ * applications.
+ *
+ * <h3>Localized Collation - ORDER BY</h3>
+ * <p>In addition to SQLite's default <code>BINARY</code> collator, Android supplies
+ * two more, <code>LOCALIZED</code>, which changes with the system's current locale
+ * if you wire it up correctly (XXX a link needed!), and <code>UNICODE</code>, which
+ * is the Unicode Collation Algorithm and not tailored to the current locale.
+ */
+public class SQLiteDatabase extends SQLiteClosable {
+    private static final String TAG = "Database";
+    private static final int DB_OPERATION_EVENT = 52000;
+
+    /**
+     * Algorithms used in ON CONFLICT clause
+     * http://www.sqlite.org/lang_conflict.html
+     * @hide
+     */
+    public enum ConflictAlgorithm {
+        /**
+         *  When a constraint violation occurs, an immediate ROLLBACK occurs, 
+         * thus ending the current transaction, and the command aborts with a 
+         * return code of SQLITE_CONSTRAINT. If no transaction is active 
+         * (other than the implied transaction that is created on every command)
+         *  then this algorithm works the same as ABORT.
+         */
+        ROLLBACK("ROLLBACK"),
+        
+        /**
+         * When a constraint violation occurs,no ROLLBACK is executed 
+         * so changes from prior commands within the same transaction 
+         * are preserved. This is the default behavior.
+         */
+        ABORT("ABORT"),
+        
+        /**
+         * When a constraint violation occurs, the command aborts with a return 
+         * code SQLITE_CONSTRAINT. But any changes to the database that 
+         * the command made prior to encountering the constraint violation 
+         * are preserved and are not backed out.
+         */
+        FAIL("FAIL"),
+        
+        /**
+         * When a constraint violation occurs, the one row that contains 
+         * the constraint violation is not inserted or changed. 
+         * But the command continues executing normally. Other rows before and 
+         * after the row that contained the constraint violation continue to be 
+         * inserted or updated normally. No error is returned.
+         */
+        IGNORE("IGNORE"),
+        
+        /**
+         * When a UNIQUE constraint violation occurs, the pre-existing rows that
+         * are causing the constraint violation are removed prior to inserting 
+         * or updating the current row. Thus the insert or update always occurs.
+         * The command continues executing normally. No error is returned. 
+         * If a NOT NULL constraint violation occurs, the NULL value is replaced
+         * by the default value for that column. If the column has no default 
+         * value, then the ABORT algorithm is used. If a CHECK constraint 
+         * violation occurs then the IGNORE algorithm is used. When this conflict 
+         * resolution strategy deletes rows in order to satisfy a constraint, 
+         * it does not invoke delete triggers on those rows.
+         *  This behavior might change in a future release.
+         */
+        REPLACE("REPLACE");
+        
+        private final String mValue;
+        ConflictAlgorithm(String value) {
+            mValue = value;
+        }
+        public String value() {
+            return mValue;
+        }
+    }
+    
+    /**
+     * Maximum Length Of A LIKE Or GLOB Pattern
+     * The pattern matching algorithm used in the default LIKE and GLOB implementation
+     * of SQLite can exhibit O(N^2) performance (where N is the number of characters in
+     * the pattern) for certain pathological cases. To avoid denial-of-service attacks
+     * the length of the LIKE or GLOB pattern is limited to SQLITE_MAX_LIKE_PATTERN_LENGTH bytes.
+     * The default value of this limit is 50000. A modern workstation can evaluate
+     * even a pathological LIKE or GLOB pattern of 50000 bytes relatively quickly.
+     * The denial of service problem only comes into play when the pattern length gets
+     * into millions of bytes. Nevertheless, since most useful LIKE or GLOB patterns
+     * are at most a few dozen bytes in length, paranoid application developers may
+     * want to reduce this parameter to something in the range of a few hundred
+     * if they know that external users are able to generate arbitrary patterns.
+     */
+    public static final int SQLITE_MAX_LIKE_PATTERN_LENGTH = 50000;
+
+    /**
+     * Flag for {@link #openDatabase} to open the database for reading and writing.
+     * If the disk is full, this may fail even before you actually write anything.
+     *
+     * {@more} Note that the value of this flag is 0, so it is the default.
+     */
+    public static final int OPEN_READWRITE = 0x00000000;          // update native code if changing
+
+    /**
+     * Flag for {@link #openDatabase} to open the database for reading only.
+     * This is the only reliable way to open a database if the disk may be full.
+     */
+    public static final int OPEN_READONLY = 0x00000001;           // update native code if changing
+
+    private static final int OPEN_READ_MASK = 0x00000001;         // update native code if changing
+
+    /**
+     * Flag for {@link #openDatabase} to open the database without support for localized collators.
+     *
+     * {@more} This causes the collator <code>LOCALIZED</code> not to be created.
+     * You must be consistent when using this flag to use the setting the database was
+     * created with.  If this is set, {@link #setLocale} will do nothing.
+     */
+    public static final int NO_LOCALIZED_COLLATORS = 0x00000010;  // update native code if changing
+
+    /**
+     * Flag for {@link #openDatabase} to create the database file if it does not already exist.
+     */
+    public static final int CREATE_IF_NECESSARY = 0x10000000;     // update native code if changing
+
+    /**
+     * Indicates whether the most-recently started transaction has been marked as successful.
+     */
+    private boolean mInnerTransactionIsSuccessful;
+
+    /**
+     * Valid during the life of a transaction, and indicates whether the entire transaction (the
+     * outer one and all of the inner ones) so far has been successful.
+     */
+    private boolean mTransactionIsSuccessful;
+
+    /** Synchronize on this when accessing the database */
+    private final ReentrantLock mLock = new ReentrantLock(true);
+
+    private long mLockAcquiredWallTime = 0L;
+    private long mLockAcquiredThreadTime = 0L;
+    
+    // limit the frequency of complaints about each database to one within 20 sec
+    // unless run command adb shell setprop log.tag.Database VERBOSE  
+    private static final int LOCK_WARNING_WINDOW_IN_MS = 20000;
+    /** If the lock is held this long then a warning will be printed when it is released. */
+    private static final int LOCK_ACQUIRED_WARNING_TIME_IN_MS = 300;
+    private static final int LOCK_ACQUIRED_WARNING_THREAD_TIME_IN_MS = 100;
+    private static final int LOCK_ACQUIRED_WARNING_TIME_IN_MS_ALWAYS_PRINT = 2000;
+
+    private long mLastLockMessageTime = 0L;
+    
+    /** Used by native code, do not rename */
+    /* package */ int mNativeHandle = 0;
+
+    /** Used to make temp table names unique */
+    /* package */ int mTempTableSequence = 0;
+
+    /** The path for the database file */
+    private String mPath;
+
+    /** The flags passed to open/create */
+    private int mFlags;
+
+    /** The optional factory to use when creating new Cursors */
+    private CursorFactory mFactory;
+    
+    private WeakHashMap<SQLiteClosable, Object> mPrograms;
+ 
+    private final RuntimeException mLeakedException;
+
+    // package visible, since callers will access directly to minimize overhead in the case
+    // that logging is not enabled.
+    /* package */ final boolean mLogStats;
+    
+    /**
+     * @param closable
+     */
+    void addSQLiteClosable(SQLiteClosable closable) {
+        lock();
+        try {
+            mPrograms.put(closable, null);
+        } finally {
+            unlock();
+        }
+    }
+    
+    void removeSQLiteClosable(SQLiteClosable closable) {
+        lock();
+        try {
+            mPrograms.remove(closable);
+        } finally {
+            unlock();
+        }
+    }    
+   
+    @Override
+    protected void onAllReferencesReleased() {
+        if (isOpen()) {
+            dbclose();
+        }
+    }
+
+    /**
+     * Attempts to release memory that SQLite holds but does not require to
+     * operate properly. Typically this memory will come from the page cache.
+     * 
+     * @return the number of bytes actually released
+     */
+    static public native int releaseMemory(); 
+
+    /**
+     * Control whether or not the SQLiteDatabase is made thread-safe by using locks
+     * around critical sections. This is pretty expensive, so if you know that your
+     * DB will only be used by a single thread then you should set this to false.
+     * The default is true.
+     * @param lockingEnabled set to true to enable locks, false otherwise
+     */
+    public void setLockingEnabled(boolean lockingEnabled) {
+        mLockingEnabled = lockingEnabled;
+    }
+
+    /**
+     * If set then the SQLiteDatabase is made thread-safe by using locks
+     * around critical sections
+     */
+    private boolean mLockingEnabled = true;
+
+    /* package */ void onCorruption() {
+        try {
+            // Close the database (if we can), which will cause subsequent operations to fail.
+            close();
+        } finally {
+            Log.e(TAG, "Removing corrupt database: " + mPath);
+            // Delete the corrupt file.  Don't re-create it now -- that would just confuse people
+            // -- but the next time someone tries to open it, they can set it up from scratch.
+            new File(mPath).delete();
+        }
+    }
+
+    /**
+     * Locks the database for exclusive access. The database lock must be held when
+     * touch the native sqlite3* object since it is single threaded and uses
+     * a polling lock contention algorithm. The lock is recursive, and may be acquired
+     * multiple times by the same thread. This is a no-op if mLockingEnabled is false.
+     * 
+     * @see #unlock()
+     */
+    /* package */ void lock() {
+        if (!mLockingEnabled) return;
+        mLock.lock();
+        if (SQLiteDebug.DEBUG_LOCK_TIME_TRACKING) {
+            if (mLock.getHoldCount() == 1) {
+                // Use elapsed real-time since the CPU may sleep when waiting for IO
+                mLockAcquiredWallTime = SystemClock.elapsedRealtime();
+                mLockAcquiredThreadTime = Debug.threadCpuTimeNanos();
+            }
+        }
+    }
+
+    /**
+     * Locks the database for exclusive access. The database lock must be held when
+     * touch the native sqlite3* object since it is single threaded and uses
+     * a polling lock contention algorithm. The lock is recursive, and may be acquired
+     * multiple times by the same thread.
+     *
+     * @see #unlockForced()
+     */
+    private void lockForced() {
+        mLock.lock();
+        if (SQLiteDebug.DEBUG_LOCK_TIME_TRACKING) {
+            if (mLock.getHoldCount() == 1) {
+                // Use elapsed real-time since the CPU may sleep when waiting for IO
+                mLockAcquiredWallTime = SystemClock.elapsedRealtime();
+                mLockAcquiredThreadTime = Debug.threadCpuTimeNanos();
+            }
+        }
+    }
+
+    /**
+     * Releases the database lock. This is a no-op if mLockingEnabled is false.
+     * 
+     * @see #unlock()
+     */
+    /* package */ void unlock() {
+        if (!mLockingEnabled) return;
+        if (SQLiteDebug.DEBUG_LOCK_TIME_TRACKING) {
+            if (mLock.getHoldCount() == 1) {
+                checkLockHoldTime();
+            }
+        }
+        mLock.unlock();
+    }
+
+    /**
+     * Releases the database lock.
+     *
+     * @see #unlockForced()
+     */
+    private void unlockForced() {
+        if (SQLiteDebug.DEBUG_LOCK_TIME_TRACKING) {
+            if (mLock.getHoldCount() == 1) {
+                checkLockHoldTime();
+            }
+        }
+        mLock.unlock();
+    }
+
+    private void checkLockHoldTime() {
+        // Use elapsed real-time since the CPU may sleep when waiting for IO
+        long elapsedTime = SystemClock.elapsedRealtime();
+        long lockedTime = elapsedTime - mLockAcquiredWallTime;                
+        if (lockedTime < LOCK_ACQUIRED_WARNING_TIME_IN_MS_ALWAYS_PRINT &&
+                !Log.isLoggable(TAG, Log.VERBOSE) &&
+                (elapsedTime - mLastLockMessageTime) < LOCK_WARNING_WINDOW_IN_MS) {
+            return;
+        }
+        if (lockedTime > LOCK_ACQUIRED_WARNING_TIME_IN_MS) {
+            int threadTime = (int)
+                    ((Debug.threadCpuTimeNanos() - mLockAcquiredThreadTime) / 1000000);
+            if (threadTime > LOCK_ACQUIRED_WARNING_THREAD_TIME_IN_MS ||
+                    lockedTime > LOCK_ACQUIRED_WARNING_TIME_IN_MS_ALWAYS_PRINT) {
+                mLastLockMessageTime = elapsedTime;
+                String msg = "lock held on " + mPath + " for " + lockedTime + "ms. Thread time was "
+                        + threadTime + "ms";
+                if (SQLiteDebug.DEBUG_LOCK_TIME_TRACKING_STACK_TRACE) {
+                    Log.d(TAG, msg, new Exception());
+                } else {
+                    Log.d(TAG, msg);
+                }
+            }
+        }
+    }
+
+    /**
+     * Begins a transaction. Transactions can be nested. When the outer transaction is ended all of
+     * the work done in that transaction and all of the nested transactions will be committed or
+     * rolled back. The changes will be rolled back if any transaction is ended without being
+     * marked as clean (by calling setTransactionSuccessful). Otherwise they will be committed.
+     *
+     * <p>Here is the standard idiom for transactions:
+     *
+     * <pre>
+     *   db.beginTransaction();
+     *   try {
+     *     ...
+     *     db.setTransactionSuccessful();
+     *   } finally {
+     *     db.endTransaction();
+     *   }
+     * </pre>
+     */
+    public void beginTransaction() {
+        lockForced();
+        boolean ok = false;
+        try {
+            // If this thread already had the lock then get out
+            if (mLock.getHoldCount() > 1) {
+                if (mInnerTransactionIsSuccessful) {
+                    String msg = "Cannot call beginTransaction between "
+                            + "calling setTransactionSuccessful and endTransaction";
+                    IllegalStateException e = new IllegalStateException(msg);
+                    Log.e(TAG, "beginTransaction() failed", e);
+                    throw e;
+                }
+                ok = true;
+                return;
+            }
+
+            // This thread didn't already have the lock, so begin a database
+            // transaction now.
+            execSQL("BEGIN EXCLUSIVE;");
+            mTransactionIsSuccessful = true;
+            mInnerTransactionIsSuccessful = false;
+            ok = true;
+        } finally {
+            if (!ok) {
+                // beginTransaction is called before the try block so we must release the lock in
+                // the case of failure.
+                unlockForced();
+            }
+        }
+    }
+
+    /**
+     * End a transaction. See beginTransaction for notes about how to use this and when transactions
+     * are committed and rolled back.
+     */
+    public void endTransaction() {
+        if (!mLock.isHeldByCurrentThread()) {
+            throw new IllegalStateException("no transaction pending");
+        }
+        try {
+            if (mInnerTransactionIsSuccessful) {
+                mInnerTransactionIsSuccessful = false;
+            } else {
+                mTransactionIsSuccessful = false;
+            }
+            if (mLock.getHoldCount() != 1) {
+                return;
+            }
+            if (mTransactionIsSuccessful) {
+                execSQL("COMMIT;");
+            } else {
+                try {
+                    execSQL("ROLLBACK;");
+                } catch (SQLException e) {
+                    if (Config.LOGD) {
+                        Log.d(TAG, "exception during rollback, maybe the DB previously "
+                                + "performed an auto-rollback");
+                    }
+                }
+            }
+        } finally {
+            unlockForced();
+            if (Config.LOGV) {
+                Log.v(TAG, "unlocked " + Thread.currentThread()
+                        + ", holdCount is " + mLock.getHoldCount());
+            }
+        }
+    }
+
+    /**
+     * Marks the current transaction as successful. Do not do any more database work between
+     * calling this and calling endTransaction. Do as little non-database work as possible in that
+     * situation too. If any errors are encountered between this and endTransaction the transaction
+     * will still be committed.
+     *
+     * @throws IllegalStateException if the current thread is not in a transaction or the
+     * transaction is already marked as successful.
+     */
+    public void setTransactionSuccessful() {
+        if (!mLock.isHeldByCurrentThread()) {
+            throw new IllegalStateException("no transaction pending");
+        }
+        if (mInnerTransactionIsSuccessful) {
+            throw new IllegalStateException(
+                    "setTransactionSuccessful may only be called once per call to beginTransaction");
+        }
+        mInnerTransactionIsSuccessful = true;
+    }
+
+    /**
+     * return true if there is a transaction pending
+     */
+    public boolean inTransaction() {
+        return mLock.getHoldCount() > 0;
+    }
+
+    /**
+     * Checks if the database lock is held by this thread.
+     *
+     * @return true, if this thread is holding the database lock.
+     */
+    public boolean isDbLockedByCurrentThread() {
+        return mLock.isHeldByCurrentThread();
+    }
+
+    /**
+     * Checks if the database is locked by another thread. This is
+     * just an estimate, since this status can change at any time,
+     * including after the call is made but before the result has
+     * been acted upon.
+     *
+     * @return true, if the database is locked by another thread
+     */
+    public boolean isDbLockedByOtherThreads() {
+        return !mLock.isHeldByCurrentThread() && mLock.isLocked();
+    }
+
+    /**
+     * Temporarily end the transaction to let other threads run. The transaction is assumed to be
+     * successful so far. Do not call setTransactionSuccessful before calling this. When this
+     * returns a new transaction will have been created but not marked as successful.
+     * @return true if the transaction was yielded
+     * @deprecated if the db is locked more than once (becuase of nested transactions) then the lock
+     *   will not be yielded. Use yieldIfContendedSafely instead.
+     */
+    public boolean yieldIfContended() {
+        return yieldIfContendedHelper(false /* do not check yielding */);
+    }
+
+    /**
+     * Temporarily end the transaction to let other threads run. The transaction is assumed to be
+     * successful so far. Do not call setTransactionSuccessful before calling this. When this
+     * returns a new transaction will have been created but not marked as successful. This assumes
+     * that there are no nested transactions (beginTransaction has only been called once) and will
+     * through an exception if that is not the case.
+     * @return true if the transaction was yielded
+     */
+    public boolean yieldIfContendedSafely() {
+        return yieldIfContendedHelper(true /* check yielding */);
+    }
+
+    private boolean yieldIfContendedHelper(boolean checkFullyYielded) {
+        if (mLock.getQueueLength() == 0) {
+            // Reset the lock acquire time since we know that the thread was willing to yield
+            // the lock at this time.
+            mLockAcquiredWallTime = SystemClock.elapsedRealtime();
+            mLockAcquiredThreadTime = Debug.threadCpuTimeNanos();
+            return false;
+        }
+        setTransactionSuccessful();
+        endTransaction();
+        if (checkFullyYielded) {
+            if (this.isDbLockedByCurrentThread()) {
+                throw new IllegalStateException(
+                        "Db locked more than once. yielfIfContended cannot yield");
+            }
+        }
+        beginTransaction();
+        return true;
+    }
+
+    /** Maps table names to info about what to which _sync_time column to set
+     * to NULL on an update. This is used to support syncing. */
+    private final Map<String, SyncUpdateInfo> mSyncUpdateInfo =
+            new HashMap<String, SyncUpdateInfo>();
+
+    public Map<String, String> getSyncedTables() {
+        synchronized(mSyncUpdateInfo) {
+            HashMap<String, String> tables = new HashMap<String, String>();
+            for (String table : mSyncUpdateInfo.keySet()) {
+                SyncUpdateInfo info = mSyncUpdateInfo.get(table);
+                if (info.deletedTable != null) {
+                    tables.put(table, info.deletedTable);
+                }
+            }
+            return tables;
+        }
+    }
+
+    /**
+     * Internal class used to keep track what needs to be marked as changed
+     * when an update occurs. This is used for syncing, so the sync engine
+     * knows what data has been updated locally.
+     */
+    static private class SyncUpdateInfo {
+        /**
+         * Creates the SyncUpdateInfo class.
+         *
+         * @param masterTable The table to set _sync_time to NULL in
+         * @param deletedTable The deleted table that corresponds to the
+         *          master table
+         * @param foreignKey The key that refers to the primary key in table
+         */
+        SyncUpdateInfo(String masterTable, String deletedTable,
+                String foreignKey) {
+            this.masterTable = masterTable;
+            this.deletedTable = deletedTable;
+            this.foreignKey = foreignKey;
+        }
+
+        /** The table containing the _sync_time column */
+        String masterTable;
+
+        /** The deleted table that corresponds to the master table */
+        String deletedTable;
+
+        /** The key in the local table the row in table. It may be _id, if table
+         * is the local table. */
+        String foreignKey;
+    }
+
+    /**
+     * Used to allow returning sub-classes of {@link Cursor} when calling query.
+     */
+    public interface CursorFactory {
+        /**
+         * See
+         * {@link SQLiteCursor#SQLiteCursor(SQLiteDatabase, SQLiteCursorDriver,
+         * String, SQLiteQuery)}.
+         */
+        public Cursor newCursor(SQLiteDatabase db,
+                SQLiteCursorDriver masterQuery, String editTable,
+                SQLiteQuery query);
+    }
+
+    /**
+     * Open the database according to the flags {@link #OPEN_READWRITE}
+     * {@link #OPEN_READONLY} {@link #CREATE_IF_NECESSARY} and/or {@link #NO_LOCALIZED_COLLATORS}.
+     *
+     * <p>Sets the locale of the database to the  the system's current locale.
+     * Call {@link #setLocale} if you would like something else.</p>
+     *
+     * @param path to database file to open and/or create
+     * @param factory an optional factory class that is called to instantiate a
+     *            cursor when query is called, or null for default
+     * @param flags to control database access mode
+     * @return the newly opened database
+     * @throws SQLiteException if the database cannot be opened
+     */
+    public static SQLiteDatabase openDatabase(String path, CursorFactory factory, int flags) {
+        SQLiteDatabase db = null;
+        try {
+            // Open the database.
+            return new SQLiteDatabase(path, factory, flags);
+        } catch (SQLiteDatabaseCorruptException e) {
+            // Try to recover from this, if we can.
+            // TODO: should we do this for other open failures?
+            Log.e(TAG, "Deleting and re-creating corrupt database " + path, e);
+            new File(path).delete();
+            return new SQLiteDatabase(path, factory, flags);
+        }
+    }
+
+    /**
+     * Equivalent to openDatabase(file.getPath(), factory, CREATE_IF_NECESSARY).
+     */
+    public static SQLiteDatabase openOrCreateDatabase(File file, CursorFactory factory) {
+        return openOrCreateDatabase(file.getPath(), factory);
+    }
+
+    /**
+     * Equivalent to openDatabase(path, factory, CREATE_IF_NECESSARY).
+     */
+    public static SQLiteDatabase openOrCreateDatabase(String path, CursorFactory factory) {
+        return openDatabase(path, factory, CREATE_IF_NECESSARY);
+    }
+
+    /**
+     * Create a memory backed SQLite database.  Its contents will be destroyed
+     * when the database is closed.
+     *
+     * <p>Sets the locale of the database to the  the system's current locale.
+     * Call {@link #setLocale} if you would like something else.</p>
+     *
+     * @param factory an optional factory class that is called to instantiate a
+     *            cursor when query is called
+     * @return a SQLiteDatabase object, or null if the database can't be created
+     */
+    public static SQLiteDatabase create(CursorFactory factory) {
+        // This is a magic string with special meaning for SQLite.
+        return openDatabase(":memory:", factory, CREATE_IF_NECESSARY);
+    }
+
+    /**
+     * Close the database.
+     */
+    public void close() {
+        lock();
+        try {
+            closeClosable();
+            releaseReference();
+        } finally {
+            unlock();
+        }
+    }
+
+    private void closeClosable() {
+        Iterator<Map.Entry<SQLiteClosable, Object>> iter = mPrograms.entrySet().iterator();
+        while (iter.hasNext()) {
+            Map.Entry<SQLiteClosable, Object> entry = iter.next();
+            SQLiteClosable program = entry.getKey();
+            if (program != null) {
+                program.onAllReferencesReleasedFromContainer();
+            }
+        }        
+    }
+    
+    /**
+     * Native call to close the database.
+     */
+    private native void dbclose();
+
+    /**
+     * Gets the database version.
+     *
+     * @return the database version
+     */
+    public int getVersion() {
+        SQLiteStatement prog = null;
+        lock();
+        try {
+            prog = new SQLiteStatement(this, "PRAGMA user_version;");
+            long version = prog.simpleQueryForLong();
+            return (int) version;
+        } finally {
+            if (prog != null) prog.close();
+            unlock();
+        }
+    }
+
+    /**
+     * Sets the database version.
+     *
+     * @param version the new database version
+     */
+    public void setVersion(int version) {
+        execSQL("PRAGMA user_version = " + version);
+    }
+
+    /**
+     * Returns the maximum size the database may grow to.
+     *
+     * @return the new maximum database size
+     */
+    public long getMaximumSize() {
+        SQLiteStatement prog = null;
+        lock();
+        try {
+            prog = new SQLiteStatement(this,
+                    "PRAGMA max_page_count;");
+            long pageCount = prog.simpleQueryForLong();
+            return pageCount * getPageSize();
+        } finally {
+            if (prog != null) prog.close();
+            unlock();
+        }
+    }
+
+    /**
+     * Sets the maximum size the database will grow to. The maximum size cannot
+     * be set below the current size.
+     *
+     * @param numBytes the maximum database size, in bytes
+     * @return the new maximum database size
+     */
+    public long setMaximumSize(long numBytes) {
+        SQLiteStatement prog = null;
+        lock();
+        try {
+            long pageSize = getPageSize();
+            long numPages = numBytes / pageSize;
+            // If numBytes isn't a multiple of pageSize, bump up a page
+            if ((numBytes % pageSize) != 0) {
+                numPages++;
+            }
+            prog = new SQLiteStatement(this,
+                    "PRAGMA max_page_count = " + numPages);
+            long newPageCount = prog.simpleQueryForLong();
+            return newPageCount * pageSize;
+        } finally {
+            if (prog != null) prog.close();
+            unlock();
+        }
+    }
+
+    /**
+     * Returns the current database page size, in bytes.
+     *
+     * @return the database page size, in bytes
+     */
+    public long getPageSize() {
+        SQLiteStatement prog = null;
+        lock();
+        try {
+            prog = new SQLiteStatement(this,
+                    "PRAGMA page_size;");
+            long size = prog.simpleQueryForLong();
+            return size;
+        } finally {
+            if (prog != null) prog.close();
+            unlock();
+        }
+    }
+
+    /**
+     * Sets the database page size. The page size must be a power of two. This
+     * method does not work if any data has been written to the database file,
+     * and must be called right after the database has been created.
+     *
+     * @param numBytes the database page size, in bytes
+     */
+    public void setPageSize(long numBytes) {
+        execSQL("PRAGMA page_size = " + numBytes);
+    }
+
+    /**
+     * Mark this table as syncable. When an update occurs in this table the
+     * _sync_dirty field will be set to ensure proper syncing operation.
+     *
+     * @param table the table to mark as syncable
+     * @param deletedTable The deleted table that corresponds to the
+     *          syncable table
+     */
+    public void markTableSyncable(String table, String deletedTable) {
+        markTableSyncable(table, "_id", table, deletedTable);
+    }
+
+    /**
+     * Mark this table as syncable, with the _sync_dirty residing in another
+     * table. When an update occurs in this table the _sync_dirty field of the
+     * row in updateTable with the _id in foreignKey will be set to
+     * ensure proper syncing operation.
+     *
+     * @param table an update on this table will trigger a sync time removal
+     * @param foreignKey this is the column in table whose value is an _id in
+     *          updateTable
+     * @param updateTable this is the table that will have its _sync_dirty
+     */
+    public void markTableSyncable(String table, String foreignKey,
+            String updateTable) {
+        markTableSyncable(table, foreignKey, updateTable, null);
+    }
+
+    /**
+     * Mark this table as syncable, with the _sync_dirty residing in another
+     * table. When an update occurs in this table the _sync_dirty field of the
+     * row in updateTable with the _id in foreignKey will be set to
+     * ensure proper syncing operation.
+     *
+     * @param table an update on this table will trigger a sync time removal
+     * @param foreignKey this is the column in table whose value is an _id in
+     *          updateTable
+     * @param updateTable this is the table that will have its _sync_dirty
+     * @param deletedTable The deleted table that corresponds to the
+     *          updateTable
+     */
+    private void markTableSyncable(String table, String foreignKey,
+            String updateTable, String deletedTable) {
+        lock();
+        try {
+            native_execSQL("SELECT _sync_dirty FROM " + updateTable
+                    + " LIMIT 0");
+            native_execSQL("SELECT " + foreignKey + " FROM " + table
+                    + " LIMIT 0");
+        } finally {
+            unlock();
+        }
+
+        SyncUpdateInfo info = new SyncUpdateInfo(updateTable, deletedTable,
+                foreignKey);
+        synchronized (mSyncUpdateInfo) {
+            mSyncUpdateInfo.put(table, info);
+        }
+    }
+
+    /**
+     * Call for each row that is updated in a cursor.
+     *
+     * @param table the table the row is in
+     * @param rowId the row ID of the updated row
+     */
+    /* package */ void rowUpdated(String table, long rowId) {
+        SyncUpdateInfo info;
+        synchronized (mSyncUpdateInfo) {
+            info = mSyncUpdateInfo.get(table);
+        }
+        if (info != null) {
+            execSQL("UPDATE " + info.masterTable
+                    + " SET _sync_dirty=1 WHERE _id=(SELECT " + info.foreignKey
+                    + " FROM " + table + " WHERE _id=" + rowId + ")");
+        }
+    }
+
+    /**
+     * Finds the name of the first table, which is editable.
+     *
+     * @param tables a list of tables
+     * @return the first table listed
+     */
+    public static String findEditTable(String tables) {
+        if (!TextUtils.isEmpty(tables)) {
+            // find the first word terminated by either a space or a comma
+            int spacepos = tables.indexOf(' ');
+            int commapos = tables.indexOf(',');
+
+            if (spacepos > 0 && (spacepos < commapos || commapos < 0)) {
+                return tables.substring(0, spacepos);
+            } else if (commapos > 0 && (commapos < spacepos || spacepos < 0) ) {
+                return tables.substring(0, commapos);
+            }
+            return tables;
+        } else {
+            throw new IllegalStateException("Invalid tables");
+        }
+    }
+
+    /**
+     * Compiles an SQL statement into a reusable pre-compiled statement object.
+     * The parameters are identical to {@link #execSQL(String)}. You may put ?s in the
+     * statement and fill in those values with {@link SQLiteProgram#bindString}
+     * and {@link SQLiteProgram#bindLong} each time you want to run the
+     * statement. Statements may not return result sets larger than 1x1.
+     *
+     * @param sql The raw SQL statement, may contain ? for unknown values to be
+     *            bound later.
+     * @return a pre-compiled statement object.
+     */
+    public SQLiteStatement compileStatement(String sql) throws SQLException {
+        lock();
+        try {
+            return new SQLiteStatement(this, sql);
+        } finally {
+            unlock();
+        }
+    }
+
+    /**
+     * Query the given URL, returning a {@link Cursor} over the result set.
+     *
+     * @param distinct true if you want each row to be unique, false otherwise.
+     * @param table The table name to compile the query against.
+     * @param columns A list of which columns to return. Passing null will
+     *            return all columns, which is discouraged to prevent reading
+     *            data from storage that isn't going to be used.
+     * @param selection A filter declaring which rows to return, formatted as an
+     *            SQL WHERE clause (excluding the WHERE itself). Passing null
+     *            will return all rows for the given table.
+     * @param selectionArgs You may include ?s in selection, which will be
+     *         replaced by the values from selectionArgs, in order that they
+     *         appear in the selection. The values will be bound as Strings.
+     * @param groupBy A filter declaring how to group rows, formatted as an SQL
+     *            GROUP BY clause (excluding the GROUP BY itself). Passing null
+     *            will cause the rows to not be grouped.
+     * @param having A filter declare which row groups to include in the cursor,
+     *            if row grouping is being used, formatted as an SQL HAVING
+     *            clause (excluding the HAVING itself). Passing null will cause
+     *            all row groups to be included, and is required when row
+     *            grouping is not being used.
+     * @param orderBy How to order the rows, formatted as an SQL ORDER BY clause
+     *            (excluding the ORDER BY itself). Passing null will use the
+     *            default sort order, which may be unordered.
+     * @param limit Limits the number of rows returned by the query,
+     *            formatted as LIMIT clause. Passing null denotes no LIMIT clause.
+     * @return A Cursor object, which is positioned before the first entry
+     * @see Cursor
+     */
+    public Cursor query(boolean distinct, String table, String[] columns,
+            String selection, String[] selectionArgs, String groupBy,
+            String having, String orderBy, String limit) {
+        return queryWithFactory(null, distinct, table, columns, selection, selectionArgs,
+                groupBy, having, orderBy, limit);
+    }
+
+    /**
+     * Query the given URL, returning a {@link Cursor} over the result set.
+     *
+     * @param cursorFactory the cursor factory to use, or null for the default factory
+     * @param distinct true if you want each row to be unique, false otherwise.
+     * @param table The table name to compile the query against.
+     * @param columns A list of which columns to return. Passing null will
+     *            return all columns, which is discouraged to prevent reading
+     *            data from storage that isn't going to be used.
+     * @param selection A filter declaring which rows to return, formatted as an
+     *            SQL WHERE clause (excluding the WHERE itself). Passing null
+     *            will return all rows for the given table.
+     * @param selectionArgs You may include ?s in selection, which will be
+     *         replaced by the values from selectionArgs, in order that they
+     *         appear in the selection. The values will be bound as Strings.
+     * @param groupBy A filter declaring how to group rows, formatted as an SQL
+     *            GROUP BY clause (excluding the GROUP BY itself). Passing null
+     *            will cause the rows to not be grouped.
+     * @param having A filter declare which row groups to include in the cursor,
+     *            if row grouping is being used, formatted as an SQL HAVING
+     *            clause (excluding the HAVING itself). Passing null will cause
+     *            all row groups to be included, and is required when row
+     *            grouping is not being used.
+     * @param orderBy How to order the rows, formatted as an SQL ORDER BY clause
+     *            (excluding the ORDER BY itself). Passing null will use the
+     *            default sort order, which may be unordered.
+     * @param limit Limits the number of rows returned by the query,
+     *            formatted as LIMIT clause. Passing null denotes no LIMIT clause.
+     * @return A Cursor object, which is positioned before the first entry
+     * @see Cursor
+     */
+    public Cursor queryWithFactory(CursorFactory cursorFactory,
+            boolean distinct, String table, String[] columns,
+            String selection, String[] selectionArgs, String groupBy,
+            String having, String orderBy, String limit) {
+        String sql = SQLiteQueryBuilder.buildQueryString(
+                distinct, table, columns, selection, groupBy, having, orderBy, limit);
+
+        return rawQueryWithFactory(
+                cursorFactory, sql, selectionArgs, findEditTable(table));
+    }
+
+    /**
+     * Query the given table, returning a {@link Cursor} over the result set.
+     *
+     * @param table The table name to compile the query against.
+     * @param columns A list of which columns to return. Passing null will
+     *            return all columns, which is discouraged to prevent reading
+     *            data from storage that isn't going to be used.
+     * @param selection A filter declaring which rows to return, formatted as an
+     *            SQL WHERE clause (excluding the WHERE itself). Passing null
+     *            will return all rows for the given table.
+     * @param selectionArgs You may include ?s in selection, which will be
+     *         replaced by the values from selectionArgs, in order that they
+     *         appear in the selection. The values will be bound as Strings.
+     * @param groupBy A filter declaring how to group rows, formatted as an SQL
+     *            GROUP BY clause (excluding the GROUP BY itself). Passing null
+     *            will cause the rows to not be grouped.
+     * @param having A filter declare which row groups to include in the cursor,
+     *            if row grouping is being used, formatted as an SQL HAVING
+     *            clause (excluding the HAVING itself). Passing null will cause
+     *            all row groups to be included, and is required when row
+     *            grouping is not being used.
+     * @param orderBy How to order the rows, formatted as an SQL ORDER BY clause
+     *            (excluding the ORDER BY itself). Passing null will use the
+     *            default sort order, which may be unordered.
+     * @return A {@link Cursor} object, which is positioned before the first entry
+     * @see Cursor
+     */
+    public Cursor query(String table, String[] columns, String selection,
+            String[] selectionArgs, String groupBy, String having,
+            String orderBy) {
+
+        return query(false, table, columns, selection, selectionArgs, groupBy,
+                having, orderBy, null /* limit */);
+    }
+
+    /**
+     * Query the given table, returning a {@link Cursor} over the result set.
+     *
+     * @param table The table name to compile the query against.
+     * @param columns A list of which columns to return. Passing null will
+     *            return all columns, which is discouraged to prevent reading
+     *            data from storage that isn't going to be used.
+     * @param selection A filter declaring which rows to return, formatted as an
+     *            SQL WHERE clause (excluding the WHERE itself). Passing null
+     *            will return all rows for the given table.
+     * @param selectionArgs You may include ?s in selection, which will be
+     *         replaced by the values from selectionArgs, in order that they
+     *         appear in the selection. The values will be bound as Strings.
+     * @param groupBy A filter declaring how to group rows, formatted as an SQL
+     *            GROUP BY clause (excluding the GROUP BY itself). Passing null
+     *            will cause the rows to not be grouped.
+     * @param having A filter declare which row groups to include in the cursor,
+     *            if row grouping is being used, formatted as an SQL HAVING
+     *            clause (excluding the HAVING itself). Passing null will cause
+     *            all row groups to be included, and is required when row
+     *            grouping is not being used.
+     * @param orderBy How to order the rows, formatted as an SQL ORDER BY clause
+     *            (excluding the ORDER BY itself). Passing null will use the
+     *            default sort order, which may be unordered.
+     * @param limit Limits the number of rows returned by the query,
+     *            formatted as LIMIT clause. Passing null denotes no LIMIT clause.
+     * @return A {@link Cursor} object, which is positioned before the first entry
+     * @see Cursor
+     */
+    public Cursor query(String table, String[] columns, String selection,
+            String[] selectionArgs, String groupBy, String having,
+            String orderBy, String limit) {
+
+        return query(false, table, columns, selection, selectionArgs, groupBy,
+                having, orderBy, limit);
+    }
+
+    /**
+     * Runs the provided SQL and returns a {@link Cursor} over the result set.
+     *
+     * @param sql the SQL query. The SQL string must not be ; terminated
+     * @param selectionArgs You may include ?s in where clause in the query,
+     *     which will be replaced by the values from selectionArgs. The
+     *     values will be bound as Strings.
+     * @return A {@link Cursor} object, which is positioned before the first entry
+     */
+    public Cursor rawQuery(String sql, String[] selectionArgs) {
+        return rawQueryWithFactory(null, sql, selectionArgs, null);
+    }
+
+    /**
+     * Runs the provided SQL and returns a cursor over the result set.
+     *
+     * @param cursorFactory the cursor factory to use, or null for the default factory
+     * @param sql the SQL query. The SQL string must not be ; terminated
+     * @param selectionArgs You may include ?s in where clause in the query,
+     *     which will be replaced by the values from selectionArgs. The
+     *     values will be bound as Strings.
+     * @param editTable the name of the first table, which is editable
+     * @return A {@link Cursor} object, which is positioned before the first entry
+     */
+    public Cursor rawQueryWithFactory(
+            CursorFactory cursorFactory, String sql, String[] selectionArgs,
+            String editTable) {
+        long timeStart = 0;
+
+        if (Config.LOGV) {
+            timeStart = System.currentTimeMillis();
+        }
+
+        SQLiteCursorDriver driver = new SQLiteDirectCursorDriver(this, sql, editTable);
+
+        try {
+            return driver.query(
+                    cursorFactory != null ? cursorFactory : mFactory,
+                    selectionArgs);
+        } finally {
+            if (Config.LOGV) {
+                long duration = System.currentTimeMillis() - timeStart;
+
+                Log.v(SQLiteCursor.TAG,
+                      "query (" + duration + " ms): " + driver.toString() + ", args are "
+                              + (selectionArgs != null
+                              ? TextUtils.join(",", selectionArgs)
+                              : "<null>"));
+            }
+        }
+    }
+
+    /**
+     * Runs the provided SQL and returns a cursor over the result set.
+     * The cursor will read an initial set of rows and the return to the caller. 
+     * It will continue to read in batches and send data changed notifications 
+     * when the later batches are ready.
+     * @param sql the SQL query. The SQL string must not be ; terminated
+     * @param selectionArgs You may include ?s in where clause in the query,
+     *     which will be replaced by the values from selectionArgs. The
+     *     values will be bound as Strings.
+     * @param initialRead set the initial count of items to read from the cursor
+     * @param maxRead set the count of items to read on each iteration after the first
+     * @return A {@link Cursor} object, which is positioned before the first entry
+     * @hide pending API council approval
+     */
+    public Cursor rawQuery(String sql, String[] selectionArgs, 
+            int initialRead, int maxRead) {
+        SQLiteCursor c = (SQLiteCursor)rawQueryWithFactory(
+                null, sql, selectionArgs, null);
+        c.setLoadStyle(initialRead, maxRead);
+        return c;
+    }
+    
+    /**
+     * Convenience method for inserting a row into the database.
+     *
+     * @param table the table to insert the row into
+     * @param nullColumnHack SQL doesn't allow inserting a completely empty row,
+     *            so if initialValues is empty this column will explicitly be
+     *            assigned a NULL value
+     * @param values this map contains the initial column values for the
+     *            row. The keys should be the column names and the values the
+     *            column values
+     * @return the row ID of the newly inserted row, or -1 if an error occurred
+     */
+    public long insert(String table, String nullColumnHack, ContentValues values) {
+        try {
+            return insertWithOnConflict(table, nullColumnHack, values, null);
+        } catch (SQLException e) {
+            Log.e(TAG, "Error inserting " + values, e);
+            return -1;
+        }
+    }
+
+    /**
+     * Convenience method for inserting a row into the database.
+     *
+     * @param table the table to insert the row into
+     * @param nullColumnHack SQL doesn't allow inserting a completely empty row,
+     *            so if initialValues is empty this column will explicitly be
+     *            assigned a NULL value
+     * @param values this map contains the initial column values for the
+     *            row. The keys should be the column names and the values the
+     *            column values
+     * @throws SQLException
+     * @return the row ID of the newly inserted row, or -1 if an error occurred
+     */
+    public long insertOrThrow(String table, String nullColumnHack, ContentValues values)
+            throws SQLException {
+        return insertWithOnConflict(table, nullColumnHack, values, null);
+    }
+
+    /**
+     * Convenience method for replacing a row in the database.
+     *
+     * @param table the table in which to replace the row
+     * @param nullColumnHack SQL doesn't allow inserting a completely empty row,
+     *            so if initialValues is empty this row will explicitly be
+     *            assigned a NULL value
+     * @param initialValues this map contains the initial column values for
+     *   the row. The key
+     * @return the row ID of the newly inserted row, or -1 if an error occurred
+     */
+    public long replace(String table, String nullColumnHack, ContentValues initialValues) {
+        try {
+            return insertWithOnConflict(table, nullColumnHack, initialValues, 
+                    ConflictAlgorithm.REPLACE);
+        } catch (SQLException e) {
+            Log.e(TAG, "Error inserting " + initialValues, e);
+            return -1;
+        }
+    }
+
+    /**
+     * Convenience method for replacing a row in the database.
+     *
+     * @param table the table in which to replace the row
+     * @param nullColumnHack SQL doesn't allow inserting a completely empty row,
+     *            so if initialValues is empty this row will explicitly be
+     *            assigned a NULL value
+     * @param initialValues this map contains the initial column values for
+     *   the row. The key
+     * @throws SQLException
+     * @return the row ID of the newly inserted row, or -1 if an error occurred
+     */
+    public long replaceOrThrow(String table, String nullColumnHack,
+            ContentValues initialValues) throws SQLException {
+        return insertWithOnConflict(table, nullColumnHack, initialValues, 
+                ConflictAlgorithm.REPLACE);
+    }
+
+    /**
+     * General method for inserting a row into the database.
+     *
+     * @param table the table to insert the row into
+     * @param nullColumnHack SQL doesn't allow inserting a completely empty row,
+     *            so if initialValues is empty this column will explicitly be
+     *            assigned a NULL value
+     * @param initialValues this map contains the initial column values for the
+     *            row. The keys should be the column names and the values the
+     *            column values
+     * @param algorithm  {@link ConflictAlgorithm} for insert conflict resolver
+     * @return the row ID of the newly inserted row, or -1 if an error occurred
+     * @hide
+     */
+    public long insertWithOnConflict(String table, String nullColumnHack,
+            ContentValues initialValues, ConflictAlgorithm algorithm) {
+        if (!isOpen()) {
+            throw new IllegalStateException("database not open");
+        }
+
+        // Measurements show most sql lengths <= 152
+        StringBuilder sql = new StringBuilder(152);
+        sql.append("INSERT");
+        if (algorithm != null) {
+            sql.append(" OR ");
+            sql.append(algorithm.value());
+        }
+        sql.append(" INTO ");
+        sql.append(table);
+        // Measurements show most values lengths < 40
+        StringBuilder values = new StringBuilder(40);
+
+        Set<Map.Entry<String, Object>> entrySet = null;
+        if (initialValues != null && initialValues.size() > 0) {
+            entrySet = initialValues.valueSet();
+            Iterator<Map.Entry<String, Object>> entriesIter = entrySet.iterator();
+            sql.append('(');
+
+            boolean needSeparator = false;
+            while (entriesIter.hasNext()) {
+                if (needSeparator) {
+                    sql.append(", ");
+                    values.append(", ");
+                }
+                needSeparator = true;
+                Map.Entry<String, Object> entry = entriesIter.next();
+                sql.append(entry.getKey());
+                values.append('?');
+            }
+
+            sql.append(')');
+        } else {
+            sql.append("(" + nullColumnHack + ") ");
+            values.append("NULL");
+        }
+
+        sql.append(" VALUES(");
+        sql.append(values);
+        sql.append(");");
+
+        lock();
+        SQLiteStatement statement = null;
+        try {
+            statement = compileStatement(sql.toString());
+
+            // Bind the values
+            if (entrySet != null) {
+                int size = entrySet.size();
+                Iterator<Map.Entry<String, Object>> entriesIter = entrySet.iterator();
+                for (int i = 0; i < size; i++) {
+                    Map.Entry<String, Object> entry = entriesIter.next();
+                    DatabaseUtils.bindObjectToProgram(statement, i + 1, entry.getValue());
+                }
+            }
+
+            // Run the program and then cleanup
+            statement.execute();
+
+            long insertedRowId = lastInsertRow();
+            if (insertedRowId == -1) {
+                Log.e(TAG, "Error inserting " + initialValues + " using " + sql);
+            } else {
+                if (Config.LOGD && Log.isLoggable(TAG, Log.VERBOSE)) {
+                    Log.v(TAG, "Inserting row " + insertedRowId + " from "
+                            + initialValues + " using " + sql);
+                }
+            }
+            return insertedRowId;
+        } catch (SQLiteDatabaseCorruptException e) {
+            onCorruption();
+            throw e;
+        } finally {
+            if (statement != null) {
+                statement.close();
+            }
+            unlock();
+        }
+    }
+
+    /**
+     * Convenience method for deleting rows in the database.
+     *
+     * @param table the table to delete from
+     * @param whereClause the optional WHERE clause to apply when deleting.
+     *            Passing null will delete all rows.
+     * @return the number of rows affected if a whereClause is passed in, 0
+     *         otherwise. To remove all rows and get a count pass "1" as the
+     *         whereClause.
+     */
+    public int delete(String table, String whereClause, String[] whereArgs) {
+        if (!isOpen()) {
+            throw new IllegalStateException("database not open");
+        }
+        lock();
+        SQLiteStatement statement = null;
+        try {
+            statement = compileStatement("DELETE FROM " + table
+                    + (!TextUtils.isEmpty(whereClause)
+                    ? " WHERE " + whereClause : ""));
+            if (whereArgs != null) {
+                int numArgs = whereArgs.length;
+                for (int i = 0; i < numArgs; i++) {
+                    DatabaseUtils.bindObjectToProgram(statement, i + 1, whereArgs[i]);
+                }
+            }
+            statement.execute();
+            statement.close();
+            return lastChangeCount();
+        } catch (SQLiteDatabaseCorruptException e) {
+            onCorruption();
+            throw e;
+        } finally {
+            if (statement != null) {
+                statement.close();
+            }
+            unlock();
+        }
+    }
+
+    /**
+     * Convenience method for updating rows in the database.
+     *
+     * @param table the table to update in
+     * @param values a map from column names to new column values. null is a
+     *            valid value that will be translated to NULL.
+     * @param whereClause the optional WHERE clause to apply when updating.
+     *            Passing null will update all rows.
+     * @return the number of rows affected
+     */
+    public int update(String table, ContentValues values, String whereClause, String[] whereArgs) {
+        return updateWithOnConflict(table, values, whereClause, whereArgs, null);
+    }
+    
+    /**
+     * Convenience method for updating rows in the database.
+     *
+     * @param table the table to update in
+     * @param values a map from column names to new column values. null is a
+     *            valid value that will be translated to NULL.
+     * @param whereClause the optional WHERE clause to apply when updating.
+     *            Passing null will update all rows.
+     * @param algorithm  {@link ConflictAlgorithm} for update conflict resolver
+     * @return the number of rows affected
+     * @hide
+     */
+    public int updateWithOnConflict(String table, ContentValues values, 
+            String whereClause, String[] whereArgs, ConflictAlgorithm algorithm) {
+        if (!isOpen()) {
+            throw new IllegalStateException("database not open");
+        }
+
+        if (values == null || values.size() == 0) {
+            throw new IllegalArgumentException("Empty values");
+        }
+
+        StringBuilder sql = new StringBuilder(120);
+        sql.append("UPDATE ");
+        if (algorithm != null) {
+            sql.append(" OR ");
+            sql.append(algorithm.value());
+        }
+        
+        sql.append(table);
+        sql.append(" SET ");
+
+        Set<Map.Entry<String, Object>> entrySet = values.valueSet();
+        Iterator<Map.Entry<String, Object>> entriesIter = entrySet.iterator();
+
+        while (entriesIter.hasNext()) {
+            Map.Entry<String, Object> entry = entriesIter.next();
+            sql.append(entry.getKey());
+            sql.append("=?");
+            if (entriesIter.hasNext()) {
+                sql.append(", ");
+            }
+        }
+
+        if (!TextUtils.isEmpty(whereClause)) {
+            sql.append(" WHERE ");
+            sql.append(whereClause);
+        }
+
+        lock();
+        SQLiteStatement statement = null;
+        try {
+            statement = compileStatement(sql.toString());
+
+            // Bind the values
+            int size = entrySet.size();
+            entriesIter = entrySet.iterator();
+            int bindArg = 1;
+            for (int i = 0; i < size; i++) {
+                Map.Entry<String, Object> entry = entriesIter.next();
+                DatabaseUtils.bindObjectToProgram(statement, bindArg, entry.getValue());
+                bindArg++;
+            }
+
+            if (whereArgs != null) {
+                size = whereArgs.length;
+                for (int i = 0; i < size; i++) {
+                    statement.bindString(bindArg, whereArgs[i]);
+                    bindArg++;
+                }
+            }
+
+            // Run the program and then cleanup
+            statement.execute();
+            statement.close();
+            int numChangedRows = lastChangeCount();
+            if (Config.LOGD && Log.isLoggable(TAG, Log.VERBOSE)) {
+                Log.v(TAG, "Updated " + numChangedRows + " using " + values + " and " + sql);
+            }
+            return numChangedRows;
+        } catch (SQLiteDatabaseCorruptException e) {
+            onCorruption();
+            throw e;
+        } catch (SQLException e) {
+            Log.e(TAG, "Error updating " + values + " using " + sql);
+            throw e;
+        } finally {
+            if (statement != null) {
+                statement.close();
+            }
+            unlock();
+        }
+    }
+
+    /**
+     * Execute a single SQL statement that is not a query. For example, CREATE
+     * TABLE, DELETE, INSERT, etc. Multiple statements separated by ;s are not
+     * supported. it takes a write lock
+     *
+     * @throws SQLException If the SQL string is invalid for some reason
+     */
+    public void execSQL(String sql) throws SQLException {
+        boolean logStats = mLogStats;
+        long timeStart = logStats ? SystemClock.elapsedRealtime() : 0;
+        lock();
+        try {
+            native_execSQL(sql);
+        } catch (SQLiteDatabaseCorruptException e) {
+            onCorruption();
+            throw e;
+        } finally {
+            unlock();
+        }
+        if (logStats) {
+            logTimeStat(false /* not a read */, timeStart, SystemClock.elapsedRealtime());
+        }
+    }
+
+    /**
+     * Execute a single SQL statement that is not a query. For example, CREATE
+     * TABLE, DELETE, INSERT, etc. Multiple statements separated by ;s are not
+     * supported. it takes a write lock,
+     *
+     * @param sql
+     * @param bindArgs only byte[], String, Long and Double are supported in bindArgs.
+     * @throws SQLException If the SQL string is invalid for some reason
+     */
+    public void execSQL(String sql, Object[] bindArgs) throws SQLException {
+        if (bindArgs == null) {
+            throw new IllegalArgumentException("Empty bindArgs");
+        }
+
+        boolean logStats = mLogStats;
+        long timeStart = logStats ? SystemClock.elapsedRealtime() : 0;
+        lock();
+        SQLiteStatement statement = null;
+        try {
+            statement = compileStatement(sql);
+            if (bindArgs != null) {
+                int numArgs = bindArgs.length;
+                for (int i = 0; i < numArgs; i++) {
+                    DatabaseUtils.bindObjectToProgram(statement, i + 1, bindArgs[i]);
+                }
+            }
+            statement.execute();
+        } catch (SQLiteDatabaseCorruptException e) {
+            onCorruption();
+            throw e;
+        } finally {
+            if (statement != null) {
+                statement.close();
+            }
+            unlock();
+        }
+        if (logStats) {
+            logTimeStat(false /* not a read */, timeStart, SystemClock.elapsedRealtime());
+        }
+    }
+
+    @Override
+    protected void finalize() {
+        if (isOpen()) {
+            if (mPrograms.isEmpty()) {
+                Log.e(TAG, "Leak found", mLeakedException);
+            } else {
+                IllegalStateException leakProgram = new IllegalStateException(
+                        "mPrograms size " + mPrograms.size(), mLeakedException);
+                Log.e(TAG, "Leak found", leakProgram);
+            }
+            closeClosable();
+            onAllReferencesReleased();
+        }
+    }
+
+    /**
+     * Private constructor. See {@link #create} and {@link #openDatabase}.
+     *
+     * @param path The full path to the database
+     * @param factory The factory to use when creating cursors, may be NULL.
+     * @param flags 0 or {@link #NO_LOCALIZED_COLLATORS}.  If the database file already
+     *              exists, mFlags will be updated appropriately.
+     */
+    private SQLiteDatabase(String path, CursorFactory factory, int flags) {
+        if (path == null) {
+            throw new IllegalArgumentException("path should not be null");
+        }
+        mFlags = flags;
+        mPath = path;
+        mLogStats = "1".equals(android.os.SystemProperties.get("db.logstats"));
+        
+        mLeakedException = new IllegalStateException(path +
+            " SQLiteDatabase created and never closed");
+        mFactory = factory;
+        dbopen(mPath, mFlags);
+        mPrograms = new WeakHashMap<SQLiteClosable,Object>();
+        try {
+            setLocale(Locale.getDefault());
+        } catch (RuntimeException e) {
+            Log.e(TAG, "Failed to setLocale() when constructing, closing the database", e);
+            dbclose();
+            throw e;
+        }
+    }
+
+    /**
+     * return whether the DB is opened as read only.
+     * @return true if DB is opened as read only
+     */
+    public boolean isReadOnly() {
+        return (mFlags & OPEN_READ_MASK) == OPEN_READONLY;
+    }
+
+    /**
+     * @return true if the DB is currently open (has not been closed)
+     */
+    public boolean isOpen() {
+        return mNativeHandle != 0;
+    }
+
+    public boolean needUpgrade(int newVersion) {
+        return newVersion > getVersion();
+    }
+
+    /**
+     * Getter for the path to the database file.
+     *
+     * @return the path to our database file.
+     */
+    public final String getPath() {
+        return mPath;
+    }
+
+    /* package */ void logTimeStat(boolean read, long begin, long end) {
+        EventLog.writeEvent(DB_OPERATION_EVENT, mPath, read ? 0 : 1, end - begin);
+    }
+
+    /**
+     * Sets the locale for this database.  Does nothing if this database has
+     * the NO_LOCALIZED_COLLATORS flag set or was opened read only.
+     * @throws SQLException if the locale could not be set.  The most common reason
+     * for this is that there is no collator available for the locale you requested.
+     * In this case the database remains unchanged.
+     */
+    public void setLocale(Locale locale) {
+        lock();
+        try {
+            native_setLocale(locale.toString(), mFlags);
+        } finally {
+            unlock();
+        }
+    }
+
+    /**
+     * Native call to open the database.
+     *
+     * @param path The full path to the database
+     */
+    private native void dbopen(String path, int flags);
+
+    /**
+     * Native call to execute a raw SQL statement. {@link #lock} must be held
+     * when calling this method.
+     *
+     * @param sql The raw SQL string
+     * @throws SQLException
+     */
+    /* package */ native void native_execSQL(String sql) throws SQLException;
+
+    /**
+     * Native call to set the locale.  {@link #lock} must be held when calling
+     * this method.
+     * @throws SQLException
+     */
+    /* package */ native void native_setLocale(String loc, int flags);
+
+    /**
+     * Returns the row ID of the last row inserted into the database.
+     *
+     * @return the row ID of the last row inserted into the database.
+     */
+    /* package */ native long lastInsertRow();
+
+    /**
+     * Returns the number of changes made in the last statement executed.
+     *
+     * @return the number of changes made in the last statement executed.
+     */
+    /* package */ native int lastChangeCount();
+}
diff --git a/core/java/android/database/sqlite/SQLiteDatabaseCorruptException.java b/core/java/android/database/sqlite/SQLiteDatabaseCorruptException.java
new file mode 100644
index 0000000..73b6c0c
--- /dev/null
+++ b/core/java/android/database/sqlite/SQLiteDatabaseCorruptException.java
@@ -0,0 +1,28 @@
+/*
+ * 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 android.database.sqlite;
+
+/**
+ * An exception that indicates that the SQLite database file is corrupt.
+ */
+public class SQLiteDatabaseCorruptException extends SQLiteException {
+    public SQLiteDatabaseCorruptException() {}
+
+    public SQLiteDatabaseCorruptException(String error) {
+        super(error);
+    }
+}
diff --git a/core/java/android/database/sqlite/SQLiteDebug.java b/core/java/android/database/sqlite/SQLiteDebug.java
new file mode 100644
index 0000000..d04afb0
--- /dev/null
+++ b/core/java/android/database/sqlite/SQLiteDebug.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2007 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 android.database.sqlite;
+
+import android.util.Config;
+
+/**
+ * Provides debugging info about all SQLite databases running in the current process.
+ * 
+ * {@hide}
+ */
+public final class SQLiteDebug {
+    /**
+     * Controls the printing of SQL statements as they are executed.
+     */
+    public static final boolean DEBUG_SQL_STATEMENTS = Config.LOGV;
+
+    /**
+     * Controls the stack trace reporting of active cursors being
+     * finalized.
+     */
+    public static final boolean DEBUG_ACTIVE_CURSOR_FINALIZATION = Config.LOGV;
+
+    /**
+     * Controls the tracking of time spent holding the database lock. 
+     */
+    public static final boolean DEBUG_LOCK_TIME_TRACKING = false;
+
+    /**
+     * Controls the printing of stack traces when tracking the time spent holding the database lock. 
+     */
+    public static final boolean DEBUG_LOCK_TIME_TRACKING_STACK_TRACE = false;
+
+    /**
+     * Contains statistics about the active pagers in the current process.
+     * 
+     * @see #getPagerStats(PagerStats)
+     */
+    public static class PagerStats {
+        /** The total number of bytes in all pagers in the current process */
+        public long totalBytes;
+        /** The number of bytes in referenced pages in all pagers in the current process */
+        public long referencedBytes;
+        /** The number of bytes in all database files opened in the current process */
+        public long databaseBytes;
+        /** The number of pagers opened in the current process */
+        public int numPagers;
+    }
+
+    /**
+     * Gathers statistics about all pagers in the current process.
+     */
+    public static native void getPagerStats(PagerStats stats);
+
+    /**
+     * Returns the size of the SQLite heap.
+     * @return The size of the SQLite heap in bytes.
+     */
+    public static native long getHeapSize();
+    
+    /**
+     * Returns the amount of allocated memory in the SQLite heap.
+     * @return The allocated size in bytes.
+     */
+    public static native long getHeapAllocatedSize();
+    
+    /**
+     * Returns the amount of free memory in the SQLite heap.
+     * @return The freed size in bytes.
+     */
+    public static native long getHeapFreeSize();
+
+    /**
+     * Determines the number of dirty belonging to the SQLite
+     * heap segments of this process.  pages[0] returns the number of
+     * shared pages, pages[1] returns the number of private pages
+     */
+    public static native void getHeapDirtyPages(int[] pages);
+
+    private static int sNumActiveCursorsFinalized = 0;
+
+    /**
+     * Returns the number of active cursors that have been finalized. This depends on the GC having
+     * run but is still useful for tests.
+     */
+    public static int getNumActiveCursorsFinalized() {
+        return sNumActiveCursorsFinalized;
+    }
+
+    static synchronized void notifyActiveCursorFinalized() {
+        sNumActiveCursorsFinalized++;
+    }
+}
diff --git a/core/java/android/database/sqlite/SQLiteDirectCursorDriver.java b/core/java/android/database/sqlite/SQLiteDirectCursorDriver.java
new file mode 100644
index 0000000..ca64aca
--- /dev/null
+++ b/core/java/android/database/sqlite/SQLiteDirectCursorDriver.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2007 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 android.database.sqlite;
+
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase.CursorFactory;
+import android.util.Log;
+
+/**
+ * A cursor driver that uses the given query directly.
+ * 
+ * @hide
+ */
+public class SQLiteDirectCursorDriver implements SQLiteCursorDriver {
+    private static String TAG = "SQLiteDirectCursorDriver";
+    private String mEditTable; 
+    private SQLiteDatabase mDatabase;
+    private Cursor mCursor;
+    private String mSql;
+    private SQLiteQuery mQuery;
+
+    public SQLiteDirectCursorDriver(SQLiteDatabase db, String sql, String editTable) {
+        mDatabase = db;
+        mEditTable = editTable;
+        //TODO remove all callers that end in ; and remove this check
+        if (sql.charAt(sql.length() - 1) == ';') {
+            Log.w(TAG, "Found SQL string that ends in ; -- " + sql);
+            sql = sql.substring(0, sql.length() - 1);
+        }
+        mSql = sql;
+    }
+
+    public Cursor query(CursorFactory factory, String[] selectionArgs) {
+        // Compile the query
+        SQLiteQuery query = new SQLiteQuery(mDatabase, mSql, 0, selectionArgs);
+
+        try {
+            // Arg binding
+            int numArgs = selectionArgs == null ? 0 : selectionArgs.length;
+            for (int i = 0; i < numArgs; i++) {
+                query.bindString(i + 1, selectionArgs[i]);
+            }
+
+            // Create the cursor
+            if (factory == null) {
+                mCursor = new SQLiteCursor(mDatabase, this, mEditTable, query);
+            } else {
+                mCursor = factory.newCursor(mDatabase, this, mEditTable, query);
+            }
+
+            mQuery = query;
+            query = null;
+            return mCursor;
+        } finally {
+            // Make sure this object is cleaned up if something happens
+            if (query != null) query.close();
+        }
+    }
+
+    public void cursorClosed() {
+        mCursor = null;
+    }
+
+    public void setBindArguments(String[] bindArgs) {
+        final int numArgs = bindArgs.length;
+        for (int i = 0; i < numArgs; i++) {
+            mQuery.bindString(i + 1, bindArgs[i]);
+        }
+    }
+
+    public void cursorDeactivated() {
+        // Do nothing
+    }
+
+    public void cursorRequeried(Cursor cursor) {
+        // Do nothing
+    }
+
+    @Override
+    public String toString() {
+        return "SQLiteDirectCursorDriver: " + mSql;
+    }
+}
diff --git a/core/java/android/database/sqlite/SQLiteDiskIOException.java b/core/java/android/database/sqlite/SQLiteDiskIOException.java
new file mode 100644
index 0000000..01b2069
--- /dev/null
+++ b/core/java/android/database/sqlite/SQLiteDiskIOException.java
@@ -0,0 +1,29 @@
+/*
+ * 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 android.database.sqlite;
+
+/**
+ * An exception that indicates that an IO error occured while accessing the 
+ * SQLite database file.
+ */
+public class SQLiteDiskIOException extends SQLiteException {
+    public SQLiteDiskIOException() {}
+
+    public SQLiteDiskIOException(String error) {
+        super(error);
+    }
+}
diff --git a/core/java/android/database/sqlite/SQLiteDoneException.java b/core/java/android/database/sqlite/SQLiteDoneException.java
new file mode 100644
index 0000000..d6d3f66
--- /dev/null
+++ b/core/java/android/database/sqlite/SQLiteDoneException.java
@@ -0,0 +1,31 @@
+/*
+ * 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 android.database.sqlite;
+
+/**
+ * An exception that indicates that the SQLite program is done.
+ * Thrown when an operation that expects a row (such as {@link
+ * SQLiteStatement#simpleQueryForString} or {@link
+ * SQLiteStatement#simpleQueryForLong}) does not get one.
+ */
+public class SQLiteDoneException extends SQLiteException {
+    public SQLiteDoneException() {}
+
+    public SQLiteDoneException(String error) {
+        super(error);
+    }
+}
diff --git a/core/java/android/database/sqlite/SQLiteException.java b/core/java/android/database/sqlite/SQLiteException.java
new file mode 100644
index 0000000..3a97bfb
--- /dev/null
+++ b/core/java/android/database/sqlite/SQLiteException.java
@@ -0,0 +1,30 @@
+/*
+ * 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 android.database.sqlite;
+
+import android.database.SQLException;
+
+/**
+ * A SQLite exception that indicates there was an error with SQL parsing or execution.
+ */
+public class SQLiteException extends SQLException {
+    public SQLiteException() {}
+
+    public SQLiteException(String error) {
+        super(error);
+    }
+}
diff --git a/core/java/android/database/sqlite/SQLiteFullException.java b/core/java/android/database/sqlite/SQLiteFullException.java
new file mode 100644
index 0000000..582d930
--- /dev/null
+++ b/core/java/android/database/sqlite/SQLiteFullException.java
@@ -0,0 +1,28 @@
+/*
+ * 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 android.database.sqlite;
+
+/**
+ * An exception that indicates that the SQLite database is full.
+ */
+public class SQLiteFullException extends SQLiteException {
+    public SQLiteFullException() {}
+
+    public SQLiteFullException(String error) {
+        super(error);
+    }
+}
diff --git a/core/java/android/database/sqlite/SQLiteMisuseException.java b/core/java/android/database/sqlite/SQLiteMisuseException.java
new file mode 100644
index 0000000..685f3ea
--- /dev/null
+++ b/core/java/android/database/sqlite/SQLiteMisuseException.java
@@ -0,0 +1,25 @@
+/*
+ * 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 android.database.sqlite;
+
+public class SQLiteMisuseException extends SQLiteException {
+    public SQLiteMisuseException() {}
+
+    public SQLiteMisuseException(String error) {
+        super(error);
+    }
+}
diff --git a/core/java/android/database/sqlite/SQLiteOpenHelper.java b/core/java/android/database/sqlite/SQLiteOpenHelper.java
new file mode 100644
index 0000000..52aac3a
--- /dev/null
+++ b/core/java/android/database/sqlite/SQLiteOpenHelper.java
@@ -0,0 +1,229 @@
+/*
+ * Copyright (C) 2007 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 android.database.sqlite;
+
+import android.content.Context;
+import android.database.sqlite.SQLiteDatabase.CursorFactory;
+import android.util.Log;
+
+/**
+ * A helper class to manage database creation and version management.
+ * You create a subclass implementing {@link #onCreate}, {@link #onUpgrade} and
+ * optionally {@link #onOpen}, and this class takes care of opening the database
+ * if it exists, creating it if it does not, and upgrading it as necessary.
+ * Transactions are used to make sure the database is always in a sensible state.
+ * <p>For an example, see the NotePadProvider class in the NotePad sample application,
+ * in the <em>samples/</em> directory of the SDK.</p>
+ */
+public abstract class SQLiteOpenHelper {
+    private static final String TAG = SQLiteOpenHelper.class.getSimpleName();
+
+    private final Context mContext;
+    private final String mName;
+    private final CursorFactory mFactory;
+    private final int mNewVersion;
+
+    private SQLiteDatabase mDatabase = null;
+    private boolean mIsInitializing = false;
+
+    /**
+     * Create a helper object to create, open, and/or manage a database.
+     * The database is not actually created or opened until one of
+     * {@link #getWritableDatabase} or {@link #getReadableDatabase} is called.
+     *
+     * @param context to use to open or create the database
+     * @param name of the database file, or null for an in-memory database
+     * @param factory to use for creating cursor objects, or null for the default
+     * @param version number of the database (starting at 1); if the database is older,
+     *     {@link #onUpgrade} will be used to upgrade the database
+     */
+    public SQLiteOpenHelper(Context context, String name, CursorFactory factory, int version) {
+        if (version < 1) throw new IllegalArgumentException("Version must be >= 1, was " + version);
+
+        mContext = context;
+        mName = name;
+        mFactory = factory;
+        mNewVersion = version;
+    }
+
+    /**
+     * Create and/or open a database that will be used for reading and writing.
+     * Once opened successfully, the database is cached, so you can call this
+     * method every time you need to write to the database.  Make sure to call
+     * {@link #close} when you no longer need it.
+     *
+     * <p>Errors such as bad permissions or a full disk may cause this operation
+     * to fail, but future attempts may succeed if the problem is fixed.</p>
+     *
+     * @throws SQLiteException if the database cannot be opened for writing
+     * @return a read/write database object valid until {@link #close} is called
+     */
+    public synchronized SQLiteDatabase getWritableDatabase() {
+        if (mDatabase != null && mDatabase.isOpen() && !mDatabase.isReadOnly()) {
+            return mDatabase;  // The database is already open for business
+        }
+
+        if (mIsInitializing) {
+            throw new IllegalStateException("getWritableDatabase called recursively");
+        }
+
+        // If we have a read-only database open, someone could be using it
+        // (though they shouldn't), which would cause a lock to be held on
+        // the file, and our attempts to open the database read-write would
+        // fail waiting for the file lock.  To prevent that, we acquire the
+        // lock on the read-only database, which shuts out other users.
+
+        boolean success = false;
+        SQLiteDatabase db = null;
+        if (mDatabase != null) mDatabase.lock();
+        try {
+            mIsInitializing = true;
+            if (mName == null) {
+                db = SQLiteDatabase.create(null);
+            } else {
+                db = mContext.openOrCreateDatabase(mName, 0, mFactory);
+            }
+
+            int version = db.getVersion();
+            if (version != mNewVersion) {
+                db.beginTransaction();
+                try {
+                    if (version == 0) {
+                        onCreate(db);
+                    } else {
+                        onUpgrade(db, version, mNewVersion);
+                    }
+                    db.setVersion(mNewVersion);
+                    db.setTransactionSuccessful();
+                } finally {
+                    db.endTransaction();
+                }
+            }
+
+            onOpen(db);
+            success = true;
+            return db;
+        } finally {
+            mIsInitializing = false;
+            if (success) {
+                if (mDatabase != null) {
+                    try { mDatabase.close(); } catch (Exception e) { }
+                    mDatabase.unlock();
+                }
+                mDatabase = db;
+            } else {
+                if (mDatabase != null) mDatabase.unlock();
+                if (db != null) db.close();
+            }
+        }
+    }
+
+    /**
+     * Create and/or open a database.  This will be the same object returned by
+     * {@link #getWritableDatabase} unless some problem, such as a full disk,
+     * requires the database to be opened read-only.  In that case, a read-only
+     * database object will be returned.  If the problem is fixed, a future call
+     * to {@link #getWritableDatabase} may succeed, in which case the read-only
+     * database object will be closed and the read/write object will be returned
+     * in the future.
+     *
+     * @throws SQLiteException if the database cannot be opened
+     * @return a database object valid until {@link #getWritableDatabase}
+     *     or {@link #close} is called.
+     */
+    public synchronized SQLiteDatabase getReadableDatabase() {
+        if (mDatabase != null && mDatabase.isOpen()) {
+            return mDatabase;  // The database is already open for business
+        }
+
+        if (mIsInitializing) {
+            throw new IllegalStateException("getReadableDatabase called recursively");
+        }
+
+        try {
+            return getWritableDatabase();
+        } catch (SQLiteException e) {
+            if (mName == null) throw e;  // Can't open a temp database read-only!
+            Log.e(TAG, "Couldn't open " + mName + " for writing (will try read-only):", e);
+        }
+
+        SQLiteDatabase db = null;
+        try {
+            mIsInitializing = true;
+            String path = mContext.getDatabasePath(mName).getPath();
+            db = SQLiteDatabase.openDatabase(path, mFactory, SQLiteDatabase.OPEN_READONLY);
+            if (db.getVersion() != mNewVersion) {
+                throw new SQLiteException("Can't upgrade read-only database from version " +
+                        db.getVersion() + " to " + mNewVersion + ": " + path);
+            }
+
+            onOpen(db);
+            Log.w(TAG, "Opened " + mName + " in read-only mode");
+            mDatabase = db;
+            return mDatabase;
+        } finally {
+            mIsInitializing = false;
+            if (db != null && db != mDatabase) db.close();
+        }
+    }
+
+    /**
+     * Close any open database object.
+     */
+    public synchronized void close() {
+        if (mIsInitializing) throw new IllegalStateException("Closed during initialization");
+
+        if (mDatabase != null && mDatabase.isOpen()) {
+            mDatabase.close();
+            mDatabase = null;
+        }
+    }
+
+    /**
+     * Called when the database is created for the first time. This is where the
+     * creation of tables and the initial population of the tables should happen.
+     *
+     * @param db The database.
+     */
+    public abstract void onCreate(SQLiteDatabase db);
+
+    /**
+     * Called when the database needs to be upgraded. The implementation
+     * should use this method to drop tables, add tables, or do anything else it
+     * needs to upgrade to the new schema version.
+     *
+     * <p>The SQLite ALTER TABLE documentation can be found
+     * <a href="http://sqlite.org/lang_altertable.html">here</a>. If you add new columns
+     * you can use ALTER TABLE to insert them into a live table. If you rename or remove columns
+     * you can use ALTER TABLE to rename the old table, then create the new table and then
+     * populate the new table with the contents of the old table.
+     *
+     * @param db The database.
+     * @param oldVersion The old database version.
+     * @param newVersion The new database version.
+     */
+    public abstract void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion);
+
+    /**
+     * Called when the database has been opened.
+     * Override method should check {@link SQLiteDatabase#isReadOnly} before
+     * updating the database.
+     *
+     * @param db The database.
+     */
+    public void onOpen(SQLiteDatabase db) {}
+}
diff --git a/core/java/android/database/sqlite/SQLiteProgram.java b/core/java/android/database/sqlite/SQLiteProgram.java
new file mode 100644
index 0000000..f89c87d
--- /dev/null
+++ b/core/java/android/database/sqlite/SQLiteProgram.java
@@ -0,0 +1,264 @@
+/*
+ * 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 android.database.sqlite;
+
+import android.util.Log;
+
+/**
+ * A base class for compiled SQLite programs.
+ */
+public abstract class SQLiteProgram extends SQLiteClosable {
+    static final String TAG = "SQLiteProgram";
+
+    /** The database this program is compiled against. */
+    protected SQLiteDatabase mDatabase;
+
+    /**
+     * Native linkage, do not modify. This comes from the database and should not be modified
+     * in here or in the native code.
+     */
+    protected int nHandle = 0;
+
+    /**
+     * Native linkage, do not modify. When non-0 this holds a reference to a valid
+     * sqlite3_statement object. It is only updated by the native code, but may be
+     * checked in this class when the database lock is held to determine if there
+     * is a valid native-side program or not.
+     */
+    protected int nStatement = 0;
+
+    /**
+     * Used to find out where a cursor was allocated in case it never got
+     * released.
+     */
+    private StackTraceElement[] mStackTraceElements;    
+ 
+    /* package */ SQLiteProgram(SQLiteDatabase db, String sql) {
+        if (SQLiteDebug.DEBUG_SQL_STATEMENTS) {
+            mStackTraceElements = new Exception().getStackTrace();
+        }
+        
+        mDatabase = db;
+        db.acquireReference();
+        db.addSQLiteClosable(this);
+        this.nHandle = db.mNativeHandle;
+        compile(sql, false);
+    }    
+    
+    @Override
+    protected void onAllReferencesReleased() {
+        // Note that native_finalize() checks to make sure that nStatement is
+        // non-null before destroying it.
+        native_finalize();
+        mDatabase.releaseReference();
+        mDatabase.removeSQLiteClosable(this);
+    }
+    
+    @Override
+    protected void onAllReferencesReleasedFromContainer(){
+        // Note that native_finalize() checks to make sure that nStatement is
+        // non-null before destroying it.
+        native_finalize();
+        mDatabase.releaseReference();        
+    }
+
+    /**
+     * Returns a unique identifier for this program.
+     * 
+     * @return a unique identifier for this program
+     */
+    public final int getUniqueId() {
+        return nStatement;
+    }
+
+    /**
+     * Compiles the given SQL into a SQLite byte code program using sqlite3_prepare_v2(). If
+     * this method has been called previously without a call to close and forCompilation is set
+     * to false the previous compilation will be used. Setting forceCompilation to true will
+     * always re-compile the program and should be done if you pass differing SQL strings to this
+     * method.
+     *
+     * <P>Note: this method acquires the database lock.</P>
+     *
+     * @param sql the SQL string to compile
+     * @param forceCompilation forces the SQL to be recompiled in the event that there is an
+     *  existing compiled SQL program already around
+     */
+    protected void compile(String sql, boolean forceCompilation) {
+        // Only compile if we don't have a valid statement already or the caller has
+        // explicitly requested a recompile. 
+        if (nStatement == 0 || forceCompilation) {
+            mDatabase.lock();
+            try {
+                // Note that the native_compile() takes care of destroying any previously
+                // existing programs before it compiles.
+                acquireReference();                
+                native_compile(sql);
+            } finally {
+                releaseReference();
+                mDatabase.unlock();
+            }        
+        }
+    } 
+  
+    /**
+     * Bind a NULL value to this statement. The value remains bound until
+     * {@link #clearBindings} is called.
+     *
+     * @param index The 1-based index to the parameter to bind null to
+     */
+    public void bindNull(int index) {
+        acquireReference();
+        try {
+            native_bind_null(index);
+        } finally {
+            releaseReference();
+        }
+    }
+
+    /**
+     * Bind a long value to this statement. The value remains bound until
+     * {@link #clearBindings} is called.
+     *
+     * @param index The 1-based index to the parameter to bind
+     * @param value The value to bind
+     */
+    public void bindLong(int index, long value) {
+        acquireReference();
+        try {
+            native_bind_long(index, value);
+        } finally {
+            releaseReference();
+        }
+    }
+
+    /**
+     * Bind a double value to this statement. The value remains bound until
+     * {@link #clearBindings} is called.
+     *
+     * @param index The 1-based index to the parameter to bind
+     * @param value The value to bind
+     */
+    public void bindDouble(int index, double value) {
+        acquireReference();
+        try {
+            native_bind_double(index, value);
+        } finally {
+            releaseReference();
+        }
+    }
+
+    /**
+     * Bind a String value to this statement. The value remains bound until
+     * {@link #clearBindings} is called.
+     *
+     * @param index The 1-based index to the parameter to bind
+     * @param value The value to bind
+     */
+    public void bindString(int index, String value) {
+        if (value == null) {
+            throw new IllegalArgumentException("the bind value at index " + index + " is null");
+        }
+        acquireReference();
+        try {
+            native_bind_string(index, value);
+        } finally {
+            releaseReference();
+        }
+    }
+
+    /**
+     * Bind a byte array value to this statement. The value remains bound until
+     * {@link #clearBindings} is called.
+     *
+     * @param index The 1-based index to the parameter to bind
+     * @param value The value to bind
+     */
+    public void bindBlob(int index, byte[] value) {
+        if (value == null) {
+            throw new IllegalArgumentException("the bind value at index " + index + " is null");
+        }
+        acquireReference();
+        try {
+            native_bind_blob(index, value);
+        } finally {
+            releaseReference();
+        }
+    }
+
+    /**
+     * Clears all existing bindings. Unset bindings are treated as NULL.
+     */
+    public void clearBindings() {
+        acquireReference();
+        try {
+            native_clear_bindings();
+        } finally {
+            releaseReference();
+        }
+    }
+
+    /**
+     * Release this program's resources, making it invalid.
+     */
+    public void close() {
+        mDatabase.lock();
+        try {
+            releaseReference();
+        } finally {
+            mDatabase.unlock();
+        }        
+    }
+    
+    /**
+     * Make sure that the native resource is cleaned up.
+     */
+    @Override
+    protected void finalize() {
+        if (nStatement != 0) {
+            if (SQLiteDebug.DEBUG_SQL_STATEMENTS) {
+                String message = "Finalizing " + this +  
+                    " that has not been closed";
+
+                Log.d(TAG, message + "\nThis cursor was created in:");
+                for (StackTraceElement ste : mStackTraceElements) {
+                    Log.d(TAG, "      " + ste);
+                }
+            }
+            // when in finalize() it is already removed from weakhashmap
+            // so it is safe to not removed itself from db
+            onAllReferencesReleasedFromContainer();
+        }
+    }
+
+    /**
+     * Compiles SQL into a SQLite program.
+     * 
+     * <P>The database lock must be held when calling this method.
+     * @param sql The SQL to compile.
+     */
+    protected final native void native_compile(String sql);
+    protected final native void native_finalize();
+
+    protected final native void native_bind_null(int index);
+    protected final native void native_bind_long(int index, long value);
+    protected final native void native_bind_double(int index, double value);
+    protected final native void native_bind_string(int index, String value);
+    protected final native void native_bind_blob(int index, byte[] value);
+    private final native void native_clear_bindings();
+}
+
diff --git a/core/java/android/database/sqlite/SQLiteQuery.java b/core/java/android/database/sqlite/SQLiteQuery.java
new file mode 100644
index 0000000..5bfa0e8
--- /dev/null
+++ b/core/java/android/database/sqlite/SQLiteQuery.java
@@ -0,0 +1,194 @@
+/*
+ * 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 android.database.sqlite;
+
+import android.database.CursorWindow;
+import android.os.SystemClock;
+
+/**
+ * A SQLite program that represents a query that reads the resulting rows into a CursorWindow.
+ * This class is used by SQLiteCursor and isn't useful itself.
+ */
+public class SQLiteQuery extends SQLiteProgram {
+    //private static final String TAG = "Cursor";
+
+    /** The index of the unbound OFFSET parameter */
+    private int mOffsetIndex;
+    
+    /** The SQL used to create this query */
+    private String mQuery;
+
+    /** Args to bind on requery */
+    private String[] mBindArgs;
+
+    private boolean mClosed = false;
+
+    /**
+     * Create a persistent query object.
+     * 
+     * @param db The database that this query object is associated with
+     * @param query The SQL string for this query. 
+     * @param offsetIndex The 1-based index to the OFFSET parameter, 
+     */
+    /* package */ SQLiteQuery(SQLiteDatabase db, String query, int offsetIndex, String[] bindArgs) {
+        super(db, query);
+
+        mOffsetIndex = offsetIndex;
+        mQuery = query;
+        mBindArgs = bindArgs;
+    }
+
+    /**
+     * Reads rows into a buffer. This method acquires the database lock.
+     * 
+     * @param window The window to fill into
+     * @return number of total rows in the query
+     */
+    /* package */ int fillWindow(CursorWindow window,  
+            int maxRead, int lastPos) {
+        mDatabase.lock();
+
+        boolean logStats = mDatabase.mLogStats;
+        long startTime = logStats ? SystemClock.elapsedRealtime() : 0;
+        try {
+            acquireReference();
+            try {
+                window.acquireReference();
+                // if the start pos is not equal to 0, then most likely window is 
+                // too small for the data set, loading by another thread
+                // is not safe in this situation. the native code will ignore maxRead
+                int numRows = native_fill_window(window, window.getStartPosition(), mOffsetIndex,
+                        maxRead, lastPos);
+                if (logStats) {
+                    mDatabase.logTimeStat(true /* read */, startTime,
+                            SystemClock.elapsedRealtime());
+                }
+                return numRows;
+            } catch (IllegalStateException e){
+                // simply ignore it
+                return 0;
+            } catch (SQLiteDatabaseCorruptException e) {
+                mDatabase.onCorruption();
+                throw e;
+            } finally {
+                window.releaseReference();                
+            }
+        } finally {
+            releaseReference();
+            mDatabase.unlock();
+        }
+    }
+
+    /**
+     * Get the column count for the statement. Only valid on query based
+     * statements. The database must be locked
+     * when calling this method.
+     * 
+     * @return The number of column in the statement's result set.
+     */
+    /* package */ int columnCountLocked() {
+        acquireReference();
+        try {
+            return native_column_count();
+        } finally {
+            releaseReference();
+        }
+    }
+
+    /**
+     * Retrieves the column name for the given column index. The database must be locked
+     * when calling this method.
+     * 
+     * @param columnIndex the index of the column to get the name for
+     * @return The requested column's name
+     */
+    /* package */ String columnNameLocked(int columnIndex) {
+        acquireReference();
+        try {
+            return native_column_name(columnIndex);
+        } finally {
+            releaseReference();
+        }
+    }
+    
+    /** {@hide pending API Council approval} */
+    @Override
+    public String toString() {
+        return "SQLiteQuery: " + mQuery;
+    }
+    
+    @Override
+    public void close() {
+        super.close();
+        mClosed = true;
+    }
+
+    /**
+     * Called by SQLiteCursor when it is requeried.
+     */
+    /* package */ void requery() {
+        if (mBindArgs != null) {
+            int len = mBindArgs.length;
+            try {
+                for (int i = 0; i < len; i++) {
+                    super.bindString(i + 1, mBindArgs[i]);
+                }
+            } catch (SQLiteMisuseException e) {
+                StringBuilder errMsg = new StringBuilder("mQuery " + mQuery);
+                for (int i = 0; i < len; i++) {
+                    errMsg.append(" ");
+                    errMsg.append(mBindArgs[i]);
+                }
+                errMsg.append(" ");
+                IllegalStateException leakProgram = new IllegalStateException(
+                        errMsg.toString(), e);
+                throw leakProgram;                
+            }
+        }
+    }
+
+    @Override
+    public void bindNull(int index) {
+        mBindArgs[index - 1] = null;
+        if (!mClosed) super.bindNull(index);
+    }
+
+    @Override
+    public void bindLong(int index, long value) {
+        mBindArgs[index - 1] = Long.toString(value);
+        if (!mClosed) super.bindLong(index, value);
+    }
+
+    @Override
+    public void bindDouble(int index, double value) {
+        mBindArgs[index - 1] = Double.toString(value);
+        if (!mClosed) super.bindDouble(index, value);
+    }
+
+    @Override
+    public void bindString(int index, String value) {
+        mBindArgs[index - 1] = value;
+        if (!mClosed) super.bindString(index, value);
+    }
+
+    private final native int native_fill_window(CursorWindow window, 
+            int startPos, int offsetParam, int maxRead, int lastPos);
+
+    private final native int native_column_count();
+
+    private final native String native_column_name(int columnIndex);
+}
diff --git a/core/java/android/database/sqlite/SQLiteQueryBuilder.java b/core/java/android/database/sqlite/SQLiteQueryBuilder.java
new file mode 100644
index 0000000..519a81c
--- /dev/null
+++ b/core/java/android/database/sqlite/SQLiteQueryBuilder.java
@@ -0,0 +1,520 @@
+/*
+ * 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 android.database.sqlite;
+
+import android.database.Cursor;
+import android.database.DatabaseUtils;
+import android.database.sqlite.SQLiteDatabase;
+import android.provider.BaseColumns;
+import android.text.TextUtils;
+import android.util.Config;
+import android.util.Log;
+
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+import java.util.Map.Entry;
+
+/**
+ * This is a convience class that helps build SQL queries to be sent to
+ * {@link SQLiteDatabase} objects.
+ */
+public class SQLiteQueryBuilder
+{
+    private static final String TAG = "SQLiteQueryBuilder";
+
+    private Map<String, String> mProjectionMap = null;
+    private String mTables = "";
+    private StringBuilder mWhereClause = new StringBuilder(64);
+    private boolean mDistinct;
+    private SQLiteDatabase.CursorFactory mFactory;
+
+    public SQLiteQueryBuilder() {
+        mDistinct = false;
+        mFactory = null;
+    }
+
+    /**
+     * Mark the query as DISTINCT.
+     *
+     * @param distinct if true the query is DISTINCT, otherwise it isn't
+     */
+    public void setDistinct(boolean distinct) {
+        mDistinct = distinct;
+    }
+
+    /**
+     * Returns the list of tables being queried
+     *
+     * @return the list of tables being queried
+     */
+    public String getTables() {
+        return mTables;
+    }
+
+    /**
+     * Sets the list of tables to query. Multiple tables can be specified to perform a join.
+     * For example:
+     *   setTables("foo, bar")
+     *   setTables("foo LEFT OUTER JOIN bar ON (foo.id = bar.foo_id)")
+     *
+     * @param inTables the list of tables to query on
+     */
+    public void setTables(String inTables) {
+        mTables = inTables;
+    }
+
+    /**
+     * Append a chunk to the WHERE clause of the query. All chunks appended are surrounded
+     * by parenthesis and ANDed with the selection passed to {@link #query}. The final
+     * WHERE clause looks like:
+     *
+     * WHERE (&lt;append chunk 1>&lt;append chunk2>) AND (&lt;query() selection parameter>)
+     *
+     * @param inWhere the chunk of text to append to the WHERE clause.
+     */
+    public void appendWhere(CharSequence inWhere) {
+        if (mWhereClause.length() == 0) {
+            mWhereClause.append('(');
+        }
+        mWhereClause.append(inWhere);
+    }
+
+    /**
+     * Append a chunk to the WHERE clause of the query. All chunks appended are surrounded
+     * by parenthesis and ANDed with the selection passed to {@link #query}. The final
+     * WHERE clause looks like:
+     *
+     * WHERE (&lt;append chunk 1>&lt;append chunk2>) AND (&lt;query() selection parameter>)
+     *
+     * @param inWhere the chunk of text to append to the WHERE clause. it will be escaped
+     * to avoid SQL injection attacks
+     */
+    public void appendWhereEscapeString(String inWhere) {
+        if (mWhereClause.length() == 0) {
+            mWhereClause.append('(');
+        }
+        DatabaseUtils.appendEscapedSQLString(mWhereClause, inWhere);
+    }
+
+    /**
+     * Sets the projection map for the query.  The projection map maps
+     * from column names that the caller passes into query to database
+     * column names. This is useful for renaming columns as well as
+     * disambiguating column names when doing joins. For example you
+     * could map "name" to "people.name".  If a projection map is set
+     * it must contain all column names the user may request, even if
+     * the key and value are the same.
+     *
+     * @param columnMap maps from the user column names to the database column names
+     */
+    public void setProjectionMap(Map<String, String> columnMap) {
+        mProjectionMap = columnMap;
+    }
+
+    /**
+     * Sets the cursor factory to be used for the query.  You can use
+     * one factory for all queries on a database but it is normally
+     * easier to specify the factory when doing this query.  @param
+     * factory the factor to use
+     */
+    public void setCursorFactory(SQLiteDatabase.CursorFactory factory) {
+        mFactory = factory;
+    }
+
+    /**
+     * Build an SQL query string from the given clauses.
+     *
+     * @param distinct true if you want each row to be unique, false otherwise.
+     * @param tables The table names to compile the query against.
+     * @param columns A list of which columns to return. Passing null will
+     *            return all columns, which is discouraged to prevent reading
+     *            data from storage that isn't going to be used.
+     * @param where A filter declaring which rows to return, formatted as an SQL
+     *            WHERE clause (excluding the WHERE itself). Passing null will
+     *            return all rows for the given URL.
+     * @param groupBy A filter declaring how to group rows, formatted as an SQL
+     *            GROUP BY clause (excluding the GROUP BY itself). Passing null
+     *            will cause the rows to not be grouped.
+     * @param having A filter declare which row groups to include in the cursor,
+     *            if row grouping is being used, formatted as an SQL HAVING
+     *            clause (excluding the HAVING itself). Passing null will cause
+     *            all row groups to be included, and is required when row
+     *            grouping is not being used.
+     * @param orderBy How to order the rows, formatted as an SQL ORDER BY clause
+     *            (excluding the ORDER BY itself). Passing null will use the
+     *            default sort order, which may be unordered.
+     * @param limit Limits the number of rows returned by the query,
+     *            formatted as LIMIT clause. Passing null denotes no LIMIT clause.
+     * @return the SQL query string
+     */
+    public static String buildQueryString(
+            boolean distinct, String tables, String[] columns, String where,
+            String groupBy, String having, String orderBy, String limit) {
+        if (TextUtils.isEmpty(groupBy) && !TextUtils.isEmpty(having)) {
+            throw new IllegalArgumentException(
+                    "HAVING clauses are only permitted when using a groupBy clause");
+        }
+
+        StringBuilder query = new StringBuilder(120);
+
+        query.append("SELECT ");
+        if (distinct) {
+            query.append("DISTINCT ");
+        }
+        if (columns != null && columns.length != 0) {
+            appendColumns(query, columns);
+        } else {
+            query.append("* ");
+        }
+        query.append("FROM ");
+        query.append(tables);
+        appendClause(query, " WHERE ", where);
+        appendClause(query, " GROUP BY ", groupBy);
+        appendClause(query, " HAVING ", having);
+        appendClause(query, " ORDER BY ", orderBy);
+        appendClauseEscapeClause(query, " LIMIT ", limit);
+
+        return query.toString();
+    }
+
+    private static void appendClause(StringBuilder s, String name, String clause) {
+        if (!TextUtils.isEmpty(clause)) {
+            s.append(name);
+            s.append(clause);
+        }
+    }
+
+    private static void appendClauseEscapeClause(StringBuilder s, String name, String clause) {
+        if (!TextUtils.isEmpty(clause)) {
+            s.append(name);
+            DatabaseUtils.appendEscapedSQLString(s, clause);
+        }
+    }
+
+    /**
+     * Add the names that are non-null in columns to s, separating
+     * them with commas.
+     */
+    public static void appendColumns(StringBuilder s, String[] columns) {
+        int n = columns.length;
+
+        for (int i = 0; i < n; i++) {
+            String column = columns[i];
+
+            if (column != null) {
+                if (i > 0) {
+                    s.append(", ");
+                }
+                s.append(column);
+            }
+        }
+        s.append(' ');
+    }
+
+    /**
+     * Perform a query by combining all current settings and the
+     * information passed into this method.
+     *
+     * @param db the database to query on
+     * @param projectionIn A list of which columns to return. Passing
+     *   null will return all columns, which is discouraged to prevent
+     *   reading data from storage that isn't going to be used.
+     * @param selection A filter declaring which rows to return,
+     *   formatted as an SQL WHERE clause (excluding the WHERE
+     *   itself). Passing null will return all rows for the given URL.
+     * @param selectionArgs You may include ?s in selection, which
+     *   will be replaced by the values from selectionArgs, in order
+     *   that they appear in the selection. The values will be bound
+     *   as Strings.
+     * @param groupBy A filter declaring how to group rows, formatted
+     *   as an SQL GROUP BY clause (excluding the GROUP BY
+     *   itself). Passing null will cause the rows to not be grouped.
+     * @param having A filter declare which row groups to include in
+     *   the cursor, if row grouping is being used, formatted as an
+     *   SQL HAVING clause (excluding the HAVING itself).  Passing
+     *   null will cause all row groups to be included, and is
+     *   required when row grouping is not being used.
+     * @param sortOrder How to order the rows, formatted as an SQL
+     *   ORDER BY clause (excluding the ORDER BY itself). Passing null
+     *   will use the default sort order, which may be unordered.
+     * @return a cursor over the result set
+     * @see android.content.ContentResolver#query(android.net.Uri, String[],
+     *      String, String[], String)
+     */
+    public Cursor query(SQLiteDatabase db, String[] projectionIn,
+            String selection, String[] selectionArgs, String groupBy,
+            String having, String sortOrder) {
+        return query(db, projectionIn, selection, selectionArgs, groupBy, having, sortOrder,
+                null /* limit */);
+    }
+
+    /**
+     * Perform a query by combining all current settings and the
+     * information passed into this method.
+     *
+     * @param db the database to query on
+     * @param projectionIn A list of which columns to return. Passing
+     *   null will return all columns, which is discouraged to prevent
+     *   reading data from storage that isn't going to be used.
+     * @param selection A filter declaring which rows to return,
+     *   formatted as an SQL WHERE clause (excluding the WHERE
+     *   itself). Passing null will return all rows for the given URL.
+     * @param selectionArgs You may include ?s in selection, which
+     *   will be replaced by the values from selectionArgs, in order
+     *   that they appear in the selection. The values will be bound
+     *   as Strings.
+     * @param groupBy A filter declaring how to group rows, formatted
+     *   as an SQL GROUP BY clause (excluding the GROUP BY
+     *   itself). Passing null will cause the rows to not be grouped.
+     * @param having A filter declare which row groups to include in
+     *   the cursor, if row grouping is being used, formatted as an
+     *   SQL HAVING clause (excluding the HAVING itself).  Passing
+     *   null will cause all row groups to be included, and is
+     *   required when row grouping is not being used.
+     * @param sortOrder How to order the rows, formatted as an SQL
+     *   ORDER BY clause (excluding the ORDER BY itself). Passing null
+     *   will use the default sort order, which may be unordered.
+     * @param limit Limits the number of rows returned by the query,
+     *   formatted as LIMIT clause. Passing null denotes no LIMIT clause.
+     * @return a cursor over the result set
+     * @see android.content.ContentResolver#query(android.net.Uri, String[],
+     *      String, String[], String)
+     */
+    public Cursor query(SQLiteDatabase db, String[] projectionIn,
+            String selection, String[] selectionArgs, String groupBy,
+            String having, String sortOrder, String limit) {
+        if (mTables == null) {
+            return null;
+        }
+
+        String sql = buildQuery(
+                projectionIn, selection, selectionArgs, groupBy, having,
+                sortOrder, limit);
+
+        if (Log.isLoggable(TAG, Log.DEBUG)) {
+            Log.d(TAG, "Performing query: " + sql);
+        }
+        return db.rawQueryWithFactory(
+                mFactory, sql, selectionArgs,
+                SQLiteDatabase.findEditTable(mTables));
+    }
+
+    /**
+     * Construct a SELECT statement suitable for use in a group of
+     * SELECT statements that will be joined through UNION operators
+     * in buildUnionQuery.
+     *
+     * @param projectionIn A list of which columns to return. Passing
+     *    null will return all columns, which is discouraged to
+     *    prevent reading data from storage that isn't going to be
+     *    used.
+     * @param selection A filter declaring which rows to return,
+     *   formatted as an SQL WHERE clause (excluding the WHERE
+     *   itself).  Passing null will return all rows for the given
+     *   URL.
+     * @param selectionArgs You may include ?s in selection, which
+     *   will be replaced by the values from selectionArgs, in order
+     *   that they appear in the selection.  The values will be bound
+     *   as Strings.
+     * @param groupBy A filter declaring how to group rows, formatted
+     *   as an SQL GROUP BY clause (excluding the GROUP BY itself).
+     *   Passing null will cause the rows to not be grouped.
+     * @param having A filter declare which row groups to include in
+     *   the cursor, if row grouping is being used, formatted as an
+     *   SQL HAVING clause (excluding the HAVING itself).  Passing
+     *   null will cause all row groups to be included, and is
+     *   required when row grouping is not being used.
+     * @param sortOrder How to order the rows, formatted as an SQL
+     *   ORDER BY clause (excluding the ORDER BY itself). Passing null
+     *   will use the default sort order, which may be unordered.
+     * @param limit Limits the number of rows returned by the query,
+     *   formatted as LIMIT clause. Passing null denotes no LIMIT clause.
+     * @return the resulting SQL SELECT statement
+     */
+    public String buildQuery(
+            String[] projectionIn, String selection, String[] selectionArgs,
+            String groupBy, String having, String sortOrder, String limit) {
+        String[] projection = computeProjection(projectionIn);
+
+        if (mWhereClause.length() > 0) {
+            mWhereClause.append(')');
+        }
+
+        // Tack on the user's selection, if present.
+        if (selection != null && selection.length() > 0) {
+            if (mWhereClause.length() > 0) {
+                mWhereClause.append(" AND ");
+            }
+
+            mWhereClause.append('(');
+            mWhereClause.append(selection);
+            mWhereClause.append(')');
+        }
+
+        return buildQueryString(
+                mDistinct, mTables, projection, mWhereClause.toString(),
+                groupBy, having, sortOrder, limit);
+    }
+
+    /**
+     * Construct a SELECT statement suitable for use in a group of
+     * SELECT statements that will be joined through UNION operators
+     * in buildUnionQuery.
+     *
+     * @param typeDiscriminatorColumn the name of the result column
+     *   whose cells will contain the name of the table from which
+     *   each row was drawn.
+     * @param unionColumns the names of the columns to appear in the
+     *   result.  This may include columns that do not appear in the
+     *   table this SELECT is querying (i.e. mTables), but that do
+     *   appear in one of the other tables in the UNION query that we
+     *   are constructing.
+     * @param columnsPresentInTable a Set of the names of the columns
+     *   that appear in this table (i.e. in the table whose name is
+     *   mTables).  Since columns in unionColumns include columns that
+     *   appear only in other tables, we use this array to distinguish
+     *   which ones actually are present.  Other columns will have
+     *   NULL values for results from this subquery.
+     * @param computedColumnsOffset all columns in unionColumns before
+     *   this index are included under the assumption that they're
+     *   computed and therefore won't appear in columnsPresentInTable,
+     *   e.g. "date * 1000 as normalized_date"
+     * @param typeDiscriminatorValue the value used for the
+     *   type-discriminator column in this subquery
+     * @param selection A filter declaring which rows to return,
+     *   formatted as an SQL WHERE clause (excluding the WHERE
+     *   itself).  Passing null will return all rows for the given
+     *   URL.
+     * @param selectionArgs You may include ?s in selection, which
+     *   will be replaced by the values from selectionArgs, in order
+     *   that they appear in the selection.  The values will be bound
+     *   as Strings.
+     * @param groupBy A filter declaring how to group rows, formatted
+     *   as an SQL GROUP BY clause (excluding the GROUP BY itself).
+     *   Passing null will cause the rows to not be grouped.
+     * @param having A filter declare which row groups to include in
+     *   the cursor, if row grouping is being used, formatted as an
+     *   SQL HAVING clause (excluding the HAVING itself).  Passing
+     *   null will cause all row groups to be included, and is
+     *   required when row grouping is not being used.
+     * @return the resulting SQL SELECT statement
+     */
+    public String buildUnionSubQuery(
+            String typeDiscriminatorColumn,
+            String[] unionColumns,
+            Set<String> columnsPresentInTable,
+            int computedColumnsOffset,
+            String typeDiscriminatorValue,
+            String selection,
+            String[] selectionArgs,
+            String groupBy,
+            String having) {
+        int unionColumnsCount = unionColumns.length;
+        String[] projectionIn = new String[unionColumnsCount];
+
+        for (int i = 0; i < unionColumnsCount; i++) {
+            String unionColumn = unionColumns[i];
+
+            if (unionColumn.equals(typeDiscriminatorColumn)) {
+                projectionIn[i] = "'" + typeDiscriminatorValue + "' AS "
+                        + typeDiscriminatorColumn;
+            } else if (i <= computedColumnsOffset
+                       || columnsPresentInTable.contains(unionColumn)) {
+                projectionIn[i] = unionColumn;
+            } else {
+                projectionIn[i] = "NULL AS " + unionColumn;
+            }
+        }
+        return buildQuery(
+                projectionIn, selection, selectionArgs, groupBy, having,
+                null /* sortOrder */,
+                null /* limit */);
+    }
+
+    /**
+     * Given a set of subqueries, all of which are SELECT statements,
+     * construct a query that returns the union of what those
+     * subqueries return.
+     * @param subQueries an array of SQL SELECT statements, all of
+     *   which must have the same columns as the same positions in
+     *   their results
+     * @param sortOrder How to order the rows, formatted as an SQL
+     *   ORDER BY clause (excluding the ORDER BY itself).  Passing
+     *   null will use the default sort order, which may be unordered.
+     * @param limit The limit clause, which applies to the entire union result set
+     *
+     * @return the resulting SQL SELECT statement
+     */
+    public String buildUnionQuery(String[] subQueries, String sortOrder, String limit) {
+        StringBuilder query = new StringBuilder(128);
+        int subQueryCount = subQueries.length;
+        String unionOperator = mDistinct ? " UNION " : " UNION ALL ";
+
+        for (int i = 0; i < subQueryCount; i++) {
+            if (i > 0) {
+                query.append(unionOperator);
+            }
+            query.append(subQueries[i]);
+        }
+        appendClause(query, " ORDER BY ", sortOrder);
+        appendClause(query, " LIMIT ", limit);
+        return query.toString();
+    }
+
+    private String[] computeProjection(String[] projectionIn) {
+        if (projectionIn != null && projectionIn.length > 0) {
+            if (mProjectionMap != null) {
+                String[] projection = new String[projectionIn.length];
+                int length = projectionIn.length;
+
+                for (int i = 0; i < length; i++) {
+                    String userColumn = projectionIn[i];
+                    String column = mProjectionMap.get(userColumn);
+
+                    if (column == null) {
+                        throw new IllegalArgumentException(
+                                    "Invalid column " + projectionIn[i]);
+                    } else {
+                        projection[i] = column;
+                    }
+                }
+                return projection;
+            } else {
+                return projectionIn;
+            }
+        } else if (mProjectionMap != null) {
+            // Return all columns in projection map.
+            Set<Entry<String, String>> entrySet = mProjectionMap.entrySet();
+            String[] projection = new String[entrySet.size()];
+            Iterator<Entry<String, String>> entryIter = entrySet.iterator();
+            int i = 0;
+
+            while (entryIter.hasNext()) {
+                Entry<String, String> entry = entryIter.next();
+
+                // Don't include the _count column when people ask for no projection.
+                if (entry.getKey().equals(BaseColumns._COUNT)) {
+                    continue;
+                }
+                projection[i++] = entry.getValue();
+            }
+            return projection;
+        }
+        return null;
+    }
+}
diff --git a/core/java/android/database/sqlite/SQLiteStatement.java b/core/java/android/database/sqlite/SQLiteStatement.java
new file mode 100644
index 0000000..5889ad9
--- /dev/null
+++ b/core/java/android/database/sqlite/SQLiteStatement.java
@@ -0,0 +1,168 @@
+/*
+ * 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 android.database.sqlite;
+
+import android.os.SystemClock;
+import android.util.Log;
+
+/**
+ * A pre-compiled statement against a {@link SQLiteDatabase} that can be reused.
+ * The statement cannot return multiple rows, but 1x1 result sets are allowed.
+ * Don't use SQLiteStatement constructor directly, please use
+ * {@link SQLiteDatabase#compileStatement(String)}
+ */
+public class SQLiteStatement extends SQLiteProgram
+{
+    private static final String TAG = "SQLiteStatement";
+
+    private final String mSql;
+
+    /**
+     * Don't use SQLiteStatement constructor directly, please use
+     * {@link SQLiteDatabase#compileStatement(String)}
+     * @param db
+     * @param sql
+     */
+    /* package */ SQLiteStatement(SQLiteDatabase db, String sql) {
+        super(db, sql);
+        if (SQLiteDebug.DEBUG_SQL_STATEMENTS) {
+            mSql = sql;
+        } else {
+            mSql = null;
+        }
+    }
+
+    /**
+     * Execute this SQL statement, if it is not a query. For example,
+     * CREATE TABLE, DELTE, INSERT, etc.
+     *
+     * @throws android.database.SQLException If the SQL string is invalid for
+     *         some reason
+     */
+    public void execute() {
+        mDatabase.lock();
+        boolean logStats = mDatabase.mLogStats;
+        long startTime = logStats ? SystemClock.elapsedRealtime() : 0;
+
+        acquireReference();
+        try {
+            if (SQLiteDebug.DEBUG_SQL_STATEMENTS) {
+                Log.v(TAG, "execute() for [" + mSql + "]");
+            }
+            native_execute();
+            if (logStats) {
+                mDatabase.logTimeStat(false /* write */, startTime, SystemClock.elapsedRealtime());
+            }
+        } finally {                    
+            releaseReference();
+            mDatabase.unlock();
+        }
+    }
+
+    /**
+     * Execute this SQL statement and return the ID of the most
+     * recently inserted row.  The SQL statement should probably be an
+     * INSERT for this to be a useful call.
+     *
+     * @return the row ID of the last row inserted.
+     *
+     * @throws android.database.SQLException If the SQL string is invalid for
+     *         some reason
+     */
+    public long executeInsert() {
+        mDatabase.lock();
+        boolean logStats = mDatabase.mLogStats;
+        long startTime = logStats ? SystemClock.elapsedRealtime() : 0;
+
+        acquireReference();
+        try {
+            if (SQLiteDebug.DEBUG_SQL_STATEMENTS) {
+                Log.v(TAG, "executeInsert() for [" + mSql + "]");
+            }
+            native_execute();
+            if (logStats) {
+                mDatabase.logTimeStat(false /* write */, startTime, SystemClock.elapsedRealtime());
+            }
+            return mDatabase.lastInsertRow();
+        } finally {
+            releaseReference();
+            mDatabase.unlock();
+        }
+    }
+
+    /**
+     * Execute a statement that returns a 1 by 1 table with a numeric value.
+     * For example, SELECT COUNT(*) FROM table;
+     *
+     * @return The result of the query.
+     *
+     * @throws android.database.sqlite.SQLiteDoneException if the query returns zero rows
+     */
+    public long simpleQueryForLong() {
+        mDatabase.lock();
+        boolean logStats = mDatabase.mLogStats;
+        long startTime = logStats ? SystemClock.elapsedRealtime() : 0;
+
+        acquireReference();
+        try {
+            if (SQLiteDebug.DEBUG_SQL_STATEMENTS) {
+                Log.v(TAG, "simpleQueryForLong() for [" + mSql + "]");
+            }
+            long retValue = native_1x1_long();
+            if (logStats) {
+                mDatabase.logTimeStat(false /* write */, startTime, SystemClock.elapsedRealtime());
+            }
+            return retValue;
+        } finally {
+            releaseReference();
+            mDatabase.unlock();
+        }
+    }
+
+    /**
+     * Execute a statement that returns a 1 by 1 table with a text value.
+     * For example, SELECT COUNT(*) FROM table;
+     *
+     * @return The result of the query.
+     *
+     * @throws android.database.sqlite.SQLiteDoneException if the query returns zero rows
+     */
+    public String simpleQueryForString() {
+        mDatabase.lock();
+        boolean logStats = mDatabase.mLogStats;
+        long startTime = logStats ? SystemClock.elapsedRealtime() : 0;
+
+        acquireReference();
+        try {
+            if (SQLiteDebug.DEBUG_SQL_STATEMENTS) {
+                Log.v(TAG, "simpleQueryForString() for [" + mSql + "]");
+            }
+            String retValue = native_1x1_string();
+            if (logStats) {
+                mDatabase.logTimeStat(false /* write */, startTime, SystemClock.elapsedRealtime());
+            }
+            return retValue;
+        } finally {
+            releaseReference();
+            mDatabase.unlock();
+        }
+    }
+
+    private final native void native_execute();
+    private final native long native_1x1_long();
+    private final native String native_1x1_string();
+}
diff --git a/core/java/android/database/sqlite/package.html b/core/java/android/database/sqlite/package.html
new file mode 100644
index 0000000..ff0f9f5
--- /dev/null
+++ b/core/java/android/database/sqlite/package.html
@@ -0,0 +1,20 @@
+<HTML>
+<BODY>
+Contains the SQLite database management
+classes that an application would use to manage its own private database.
+<p>
+Applications use these classes to maange private databases. If creating a
+content provider, you will probably have to use these classes to create and
+manage your own database to store content. See <a
+href="{@docRoot}guide/topics/providers/content-providers.html">Content Providers</a> to learn
+the conventions for implementing a content provider. See the
+NotePadProvider class in the NotePad sample application in the SDK for an
+example of a content provider. Android ships with SQLite version 3.4.0
+<p>If you are working with data sent to you by a provider, you will not use
+these SQLite classes, but instead use the generic {@link android.database}
+classes.
+<p>Android ships with the sqlite3 database tool in the <code>tools/</code>
+folder. You can use this tool to browse or run SQL commands on the device. Run by
+typing <code>sqlite3</code> in a shell window.
+</BODY>
+</HTML>
diff --git a/core/java/android/ddm/DdmHandleAppName.java b/core/java/android/ddm/DdmHandleAppName.java
new file mode 100644
index 0000000..4a57d12
--- /dev/null
+++ b/core/java/android/ddm/DdmHandleAppName.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2007 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 android.ddm;
+
+import org.apache.harmony.dalvik.ddmc.Chunk;
+import org.apache.harmony.dalvik.ddmc.ChunkHandler;
+import org.apache.harmony.dalvik.ddmc.DdmServer;
+import android.util.Config;
+import android.util.Log;
+import java.nio.ByteBuffer;
+
+
+/**
+ * Track our app name.  We don't (currently) handle any inbound packets.
+ */
+public class DdmHandleAppName extends ChunkHandler {
+
+    public static final int CHUNK_APNM = type("APNM");
+
+    private volatile static String mAppName = "";
+
+    private static DdmHandleAppName mInstance = new DdmHandleAppName();
+
+
+    /* singleton, do not instantiate */
+    private DdmHandleAppName() {}
+
+    /**
+     * Register for the messages we're interested in.
+     */
+    public static void register() {}
+
+    /**
+     * Called when the DDM server connects.  The handler is allowed to
+     * send messages to the server.
+     */
+    public void connected() {}
+
+    /**
+     * Called when the DDM server disconnects.  Can be used to disable
+     * periodic transmissions or clean up saved state.
+     */
+    public void disconnected() {}
+
+    /**
+     * Handle a chunk of data.
+     */
+    public Chunk handleChunk(Chunk request) {
+        return null;
+    }
+
+
+
+    /**
+     * Set the application name.  Called when we get named, which may be
+     * before or after DDMS connects.  For the latter we need to send up
+     * an APNM message.
+     */
+    public static void setAppName(String name) {
+        if (name == null || name.length() == 0)
+            return;
+
+        mAppName = name;
+
+        // if DDMS is already connected, send the app name up
+        sendAPNM(name);
+    }
+
+    public static String getAppName() {
+        return mAppName;
+    }
+
+    /*
+     * Send an APNM (APplication NaMe) chunk.
+     */
+    private static void sendAPNM(String appName) {
+        if (Config.LOGV)
+            Log.v("ddm", "Sending app name");
+
+        ByteBuffer out = ByteBuffer.allocate(4 + appName.length()*2);
+        out.order(ChunkHandler.CHUNK_ORDER);
+        out.putInt(appName.length());
+        putString(out, appName);
+
+        Chunk chunk = new Chunk(CHUNK_APNM, out);
+        DdmServer.sendChunk(chunk);
+    }
+
+}
+
diff --git a/core/java/android/ddm/DdmHandleExit.java b/core/java/android/ddm/DdmHandleExit.java
new file mode 100644
index 0000000..8a0b9a4
--- /dev/null
+++ b/core/java/android/ddm/DdmHandleExit.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2007 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 android.ddm;
+
+import org.apache.harmony.dalvik.ddmc.Chunk;
+import org.apache.harmony.dalvik.ddmc.ChunkHandler;
+import org.apache.harmony.dalvik.ddmc.DdmServer;
+import android.util.Config;
+import android.util.Log;
+import java.nio.ByteBuffer;
+
+/**
+ * Handle an EXIT chunk.
+ */
+public class DdmHandleExit extends ChunkHandler {
+
+    public static final int CHUNK_EXIT = type("EXIT");
+
+    private static DdmHandleExit mInstance = new DdmHandleExit();
+
+
+    /* singleton, do not instantiate */
+    private DdmHandleExit() {}
+
+    /**
+     * Register for the messages we're interested in.
+     */
+    public static void register() {
+        DdmServer.registerHandler(CHUNK_EXIT, mInstance);
+    }
+
+    /**
+     * Called when the DDM server connects.  The handler is allowed to
+     * send messages to the server.
+     */
+    public void connected() {}
+
+    /**
+     * Called when the DDM server disconnects.  Can be used to disable
+     * periodic transmissions or clean up saved state.
+     */
+    public void disconnected() {}
+
+    /**
+     * Handle a chunk of data.  We're only registered for "EXIT".
+     */
+    public Chunk handleChunk(Chunk request) {
+        if (Config.LOGV)
+            Log.v("ddm-exit", "Handling " + name(request.type) + " chunk");
+
+        /*
+         * Process the request.
+         */
+        ByteBuffer in = wrapChunk(request);
+
+        int statusCode = in.getInt();
+
+        Runtime.getRuntime().halt(statusCode);
+
+        // if that doesn't work, return an empty message
+        return null;
+    }
+}
+
diff --git a/core/java/android/ddm/DdmHandleHeap.java b/core/java/android/ddm/DdmHandleHeap.java
new file mode 100644
index 0000000..54457c2
--- /dev/null
+++ b/core/java/android/ddm/DdmHandleHeap.java
@@ -0,0 +1,194 @@
+/*
+ * Copyright (C) 2007 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 android.ddm;
+
+import org.apache.harmony.dalvik.ddmc.Chunk;
+import org.apache.harmony.dalvik.ddmc.ChunkHandler;
+import org.apache.harmony.dalvik.ddmc.DdmServer;
+import org.apache.harmony.dalvik.ddmc.DdmVmInternal;
+import android.util.Config;
+import android.util.Log;
+import java.nio.ByteBuffer;
+
+/**
+ * Handle thread-related traffic.
+ */
+public class DdmHandleHeap extends ChunkHandler {
+
+    public static final int CHUNK_HPIF = type("HPIF");
+    public static final int CHUNK_HPSG = type("HPSG");
+    public static final int CHUNK_NHSG = type("NHSG");
+    public static final int CHUNK_HPGC = type("HPGC");
+    public static final int CHUNK_REAE = type("REAE");
+    public static final int CHUNK_REAQ = type("REAQ");
+    public static final int CHUNK_REAL = type("REAL");
+
+    private static DdmHandleHeap mInstance = new DdmHandleHeap();
+
+
+    /* singleton, do not instantiate */
+    private DdmHandleHeap() {}
+
+    /**
+     * Register for the messages we're interested in.
+     */
+    public static void register() {
+        DdmServer.registerHandler(CHUNK_HPIF, mInstance);
+        DdmServer.registerHandler(CHUNK_HPSG, mInstance);
+        DdmServer.registerHandler(CHUNK_NHSG, mInstance);
+        DdmServer.registerHandler(CHUNK_HPGC, mInstance);
+        DdmServer.registerHandler(CHUNK_REAE, mInstance);
+        DdmServer.registerHandler(CHUNK_REAQ, mInstance);
+        DdmServer.registerHandler(CHUNK_REAL, mInstance);
+    }
+
+    /**
+     * Called when the DDM server connects.  The handler is allowed to
+     * send messages to the server.
+     */
+    public void connected() {}
+
+    /**
+     * Called when the DDM server disconnects.  Can be used to disable
+     * periodic transmissions or clean up saved state.
+     */
+    public void disconnected() {}
+
+    /**
+     * Handle a chunk of data.
+     */
+    public Chunk handleChunk(Chunk request) {
+        if (Config.LOGV)
+            Log.v("ddm-heap", "Handling " + name(request.type) + " chunk");
+        int type = request.type;
+
+        if (type == CHUNK_HPIF) {
+            return handleHPIF(request);
+        } else if (type == CHUNK_HPSG) {
+            return handleHPSGNHSG(request, false);
+        } else if (type == CHUNK_NHSG) {
+            return handleHPSGNHSG(request, true);
+        } else if (type == CHUNK_HPGC) {
+            return handleHPGC(request);
+        } else if (type == CHUNK_REAE) {
+            return handleREAE(request);
+        } else if (type == CHUNK_REAQ) {
+            return handleREAQ(request);
+        } else if (type == CHUNK_REAL) {
+            return handleREAL(request);
+        } else {
+            throw new RuntimeException("Unknown packet "
+                + ChunkHandler.name(type));
+        }
+    }
+
+    /*
+     * Handle a "HeaP InFo request".
+     */
+    private Chunk handleHPIF(Chunk request) {
+        ByteBuffer in = wrapChunk(request);
+
+        int when = in.get();
+        if (Config.LOGV)
+            Log.v("ddm-heap", "Heap segment enable: when=" + when);
+
+        boolean ok = DdmVmInternal.heapInfoNotify(when);
+        if (!ok) {
+            return createFailChunk(1, "Unsupported HPIF what");
+        } else {
+            return null;        // empty response
+        }
+    }
+
+    /*
+     * Handle a "HeaP SeGment" or "Native Heap SeGment" request.
+     */
+    private Chunk handleHPSGNHSG(Chunk request, boolean isNative) {
+        ByteBuffer in = wrapChunk(request);
+
+        int when = in.get();
+        int what = in.get();
+        if (Config.LOGV)
+            Log.v("ddm-heap", "Heap segment enable: when=" + when
+                + ", what=" + what + ", isNative=" + isNative);
+
+        boolean ok = DdmVmInternal.heapSegmentNotify(when, what, isNative);
+        if (!ok) {
+            return createFailChunk(1, "Unsupported HPSG what/when");
+        } else {
+            // TODO: if "when" is non-zero and we want to see a dump
+            //       right away, initiate a GC.
+            return null;        // empty response
+        }
+    }
+
+    /*
+     * Handle a "HeaP Garbage Collection" request.
+     */
+    private Chunk handleHPGC(Chunk request) {
+        //ByteBuffer in = wrapChunk(request);
+
+        if (Config.LOGD)
+            Log.d("ddm-heap", "Heap GC request");
+        System.gc();
+
+        return null;        // empty response
+    }
+
+    /*
+     * Handle a "REcent Allocation Enable" request.
+     */
+    private Chunk handleREAE(Chunk request) {
+        ByteBuffer in = wrapChunk(request);
+        boolean enable;
+
+        enable = (in.get() != 0);
+
+        if (Config.LOGD)
+            Log.d("ddm-heap", "Recent allocation enable request: " + enable);
+
+        DdmVmInternal.enableRecentAllocations(enable);
+
+        return null;        // empty response
+    }
+
+    /*
+     * Handle a "REcent Allocation Query" request.
+     */
+    private Chunk handleREAQ(Chunk request) {
+        //ByteBuffer in = wrapChunk(request);
+
+        byte[] reply = new byte[1];
+        reply[0] = DdmVmInternal.getRecentAllocationStatus() ? (byte)1 :(byte)0;
+        return new Chunk(CHUNK_REAQ, reply, 0, reply.length);
+    }
+
+    /*
+     * Handle a "REcent ALlocations" request.
+     */
+    private Chunk handleREAL(Chunk request) {
+        //ByteBuffer in = wrapChunk(request);
+
+        if (Config.LOGD)
+            Log.d("ddm-heap", "Recent allocations request");
+
+        /* generate the reply in a ready-to-go format */
+        byte[] reply = DdmVmInternal.getRecentAllocations();
+        return new Chunk(CHUNK_REAL, reply, 0, reply.length);
+    }
+}
+
diff --git a/core/java/android/ddm/DdmHandleHello.java b/core/java/android/ddm/DdmHandleHello.java
new file mode 100644
index 0000000..e4d630e
--- /dev/null
+++ b/core/java/android/ddm/DdmHandleHello.java
@@ -0,0 +1,138 @@
+/*
+ * Copyright (C) 2007 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 android.ddm;
+
+import org.apache.harmony.dalvik.ddmc.Chunk;
+import org.apache.harmony.dalvik.ddmc.ChunkHandler;
+import org.apache.harmony.dalvik.ddmc.DdmServer;
+import android.util.Config;
+import android.util.Log;
+import android.os.Debug;
+
+import java.nio.ByteBuffer;
+
+/**
+ * Handle a HELO chunk.
+ */
+public class DdmHandleHello extends ChunkHandler {
+
+    public static final int CHUNK_HELO = type("HELO");
+    public static final int CHUNK_WAIT = type("WAIT");
+
+    private static DdmHandleHello mInstance = new DdmHandleHello();
+
+
+    /* singleton, do not instantiate */
+    private DdmHandleHello() {}
+
+    /**
+     * Register for the messages we're interested in.
+     */
+    public static void register() {
+        DdmServer.registerHandler(CHUNK_HELO, mInstance);
+    }
+
+    /**
+     * Called when the DDM server connects.  The handler is allowed to
+     * send messages to the server.
+     */
+    public void connected() {
+        if (Config.LOGV)
+            Log.v("ddm-hello", "Connected!");
+
+        if (true) {
+            /* test spontaneous transmission */
+            byte[] data = new byte[] { 0, 1, 2, 3, 4, -4, -3, -2, -1, 127 };
+            Chunk testChunk =
+                new Chunk(ChunkHandler.type("TEST"), data, 1, data.length-2);
+            DdmServer.sendChunk(testChunk);
+        }
+    }
+
+    /**
+     * Called when the DDM server disconnects.  Can be used to disable
+     * periodic transmissions or clean up saved state.
+     */
+    public void disconnected() {
+        if (Config.LOGV)
+            Log.v("ddm-hello", "Disconnected!");
+    }
+
+    /**
+     * Handle a chunk of data.  We're only registered for "HELO".
+     */
+    public Chunk handleChunk(Chunk request) {
+        if (Config.LOGV)
+            Log.v("ddm-hello", "Handling " + name(request.type) + " chunk");
+
+        if (false)
+            return createFailChunk(123, "This is a test");
+
+        /*
+         * Process the request.
+         */
+        ByteBuffer in = wrapChunk(request);
+
+        int serverProtoVers = in.getInt();
+        if (Config.LOGV)
+            Log.v("ddm-hello", "Server version is " + serverProtoVers);
+
+        /*
+         * Create a response.
+         */
+        String vmName = System.getProperty("java.vm.name", "?");
+        String vmVersion = System.getProperty("java.vm.version", "?");
+        String vmIdent = vmName + " v" + vmVersion;
+
+        //String appName = android.app.ActivityThread.currentPackageName();
+        //if (appName == null)
+        //    appName = "unknown";
+        String appName = DdmHandleAppName.getAppName();
+
+        ByteBuffer out = ByteBuffer.allocate(16
+                            + vmIdent.length()*2 + appName.length()*2);
+        out.order(ChunkHandler.CHUNK_ORDER);
+        out.putInt(DdmServer.CLIENT_PROTOCOL_VERSION);
+        out.putInt(android.os.Process.myPid());
+        out.putInt(vmIdent.length());
+        out.putInt(appName.length());
+        putString(out, vmIdent);
+        putString(out, appName);
+
+        Chunk reply = new Chunk(CHUNK_HELO, out);
+
+        /*
+         * Take the opportunity to inform DDMS if we are waiting for a
+         * debugger to attach.
+         */
+        if (Debug.waitingForDebugger())
+            sendWAIT(0);
+
+        return reply;
+    }
+
+    /**
+     * Send up a WAIT chunk.  The only currently defined value for "reason"
+     * is zero, which means "waiting for a debugger".
+     */
+    public static void sendWAIT(int reason) {
+        byte[] data = new byte[] { (byte) reason };
+        Chunk waitChunk = new Chunk(CHUNK_WAIT, data, 0, 1);
+        DdmServer.sendChunk(waitChunk);
+    }
+}
+
diff --git a/core/java/android/ddm/DdmHandleNativeHeap.java b/core/java/android/ddm/DdmHandleNativeHeap.java
new file mode 100644
index 0000000..6bd65aa
--- /dev/null
+++ b/core/java/android/ddm/DdmHandleNativeHeap.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2007 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 android.ddm;
+
+import org.apache.harmony.dalvik.ddmc.Chunk;
+import org.apache.harmony.dalvik.ddmc.ChunkHandler;
+import org.apache.harmony.dalvik.ddmc.DdmServer;
+import android.util.Log;
+import java.nio.ByteBuffer;
+
+/**
+ * Handle thread-related traffic.
+ */
+public class DdmHandleNativeHeap extends ChunkHandler {
+
+    public static final int CHUNK_NHGT = type("NHGT");
+
+    private static DdmHandleNativeHeap mInstance = new DdmHandleNativeHeap();
+
+
+    /* singleton, do not instantiate */
+    private DdmHandleNativeHeap() {}
+
+    /**
+     * Register for the messages we're interested in.
+     */
+    public static void register() {
+        DdmServer.registerHandler(CHUNK_NHGT, mInstance);
+    }
+
+    /**
+     * Called when the DDM server connects.  The handler is allowed to
+     * send messages to the server.
+     */
+    public void connected() {}
+
+    /**
+     * Called when the DDM server disconnects.  Can be used to disable
+     * periodic transmissions or clean up saved state.
+     */
+    public void disconnected() {}
+
+    /**
+     * Handle a chunk of data.
+     */
+    public Chunk handleChunk(Chunk request) {
+        Log.i("ddm-nativeheap", "Handling " + name(request.type) + " chunk");
+        int type = request.type;
+
+        if (type == CHUNK_NHGT) {
+            return handleNHGT(request);
+        } else {
+            throw new RuntimeException("Unknown packet "
+                + ChunkHandler.name(type));
+        }
+    }
+
+    /*
+     * Handle a "Native Heap GeT" request.
+     */
+    private Chunk handleNHGT(Chunk request) {
+        //ByteBuffer in = wrapChunk(request);
+
+        byte[] data = getLeakInfo();
+
+        if (data != null) {
+            // wrap & return
+            Log.i("ddm-nativeheap", "Sending " + data.length + " bytes");
+            return new Chunk(ChunkHandler.type("NHGT"), data, 0, data.length);
+        } else {
+            // failed, return a failure error code and message
+            return createFailChunk(1, "Something went wrong");
+        }
+    }
+    
+    private native byte[] getLeakInfo();
+}
+
diff --git a/core/java/android/ddm/DdmHandleThread.java b/core/java/android/ddm/DdmHandleThread.java
new file mode 100644
index 0000000..c307988
--- /dev/null
+++ b/core/java/android/ddm/DdmHandleThread.java
@@ -0,0 +1,182 @@
+/*
+ * Copyright (C) 2007 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 android.ddm;
+
+import org.apache.harmony.dalvik.ddmc.Chunk;
+import org.apache.harmony.dalvik.ddmc.ChunkHandler;
+import org.apache.harmony.dalvik.ddmc.DdmServer;
+import org.apache.harmony.dalvik.ddmc.DdmVmInternal;
+import android.util.Config;
+import android.util.Log;
+import java.nio.ByteBuffer;
+
+/**
+ * Handle thread-related traffic.
+ */
+public class DdmHandleThread extends ChunkHandler {
+
+    public static final int CHUNK_THEN = type("THEN");
+    public static final int CHUNK_THCR = type("THCR");
+    public static final int CHUNK_THDE = type("THDE");
+    public static final int CHUNK_THST = type("THST");
+    public static final int CHUNK_STKL = type("STKL");
+
+    private static DdmHandleThread mInstance = new DdmHandleThread();
+
+
+    /* singleton, do not instantiate */
+    private DdmHandleThread() {}
+
+    /**
+     * Register for the messages we're interested in.
+     */
+    public static void register() {
+        DdmServer.registerHandler(CHUNK_THEN, mInstance);
+        DdmServer.registerHandler(CHUNK_THST, mInstance);
+        DdmServer.registerHandler(CHUNK_STKL, mInstance);
+    }
+
+    /**
+     * Called when the DDM server connects.  The handler is allowed to
+     * send messages to the server.
+     */
+    public void connected() {}
+
+    /**
+     * Called when the DDM server disconnects.  Can be used to disable
+     * periodic transmissions or clean up saved state.
+     */
+    public void disconnected() {}
+
+    /**
+     * Handle a chunk of data.
+     */
+    public Chunk handleChunk(Chunk request) {
+        if (Config.LOGV)
+            Log.v("ddm-thread", "Handling " + name(request.type) + " chunk");
+        int type = request.type;
+
+        if (type == CHUNK_THEN) {
+            return handleTHEN(request);
+        } else if (type == CHUNK_THST) {
+            return handleTHST(request);
+        } else if (type == CHUNK_STKL) {
+            return handleSTKL(request);
+        } else {
+            throw new RuntimeException("Unknown packet "
+                + ChunkHandler.name(type));
+        }
+    }
+
+    /*
+     * Handle a "THread notification ENable" request.
+     */
+    private Chunk handleTHEN(Chunk request) {
+        ByteBuffer in = wrapChunk(request);
+
+        boolean enable = (in.get() != 0);
+        //Log.i("ddm-thread", "Thread notify enable: " + enable);
+
+        DdmVmInternal.threadNotify(enable);
+        return null;        // empty response
+    }
+
+    /*
+     * Handle a "THread STatus" request.  This is constructed by the VM.
+     */
+    private Chunk handleTHST(Chunk request) {
+        ByteBuffer in = wrapChunk(request);
+        // currently nothing to read from "in"
+
+        //Log.d("ddm-thread", "Thread status request");
+
+        byte[] status = DdmVmInternal.getThreadStats();
+        if (status != null)
+            return new Chunk(CHUNK_THST, status, 0, status.length);
+        else
+            return createFailChunk(1, "Can't build THST chunk");
+    }
+
+    /*
+     * Handle a STacK List request.
+     *
+     * This is done by threadId, which isn't great since those are
+     * recycled.  We need a thread serial ID.  The Linux tid is an okay
+     * answer as it's unlikely to recycle at the exact wrong moment.
+     * However, we're using the short threadId in THST messages, so we
+     * use them here for consistency.  (One thought is to keep the current
+     * thread ID in the low 16 bits and somehow serialize the top 16 bits.)
+     */
+    private Chunk handleSTKL(Chunk request) {
+        ByteBuffer in = wrapChunk(request);
+        int threadId;
+
+        threadId = in.getInt();
+
+        //Log.d("ddm-thread", "Stack list request " + threadId);
+
+        StackTraceElement[] trace = DdmVmInternal.getStackTraceById(threadId);
+        if (trace == null) {
+            return createFailChunk(1, "Stack trace unavailable");
+        } else {
+            return createStackChunk(trace, threadId);
+        }
+    }
+
+    /*
+     * Serialize a StackTraceElement[] into an STKL chunk.
+     *
+     * We include the threadId in the response so the other side doesn't have
+     * to match up requests and responses as carefully.
+     */
+    private Chunk createStackChunk(StackTraceElement[] trace, int threadId) {
+        int bufferSize = 0;
+
+        bufferSize += 4;            // version, flags, whatever
+        bufferSize += 4;            // thread ID
+        bufferSize += 4;            // frame count
+        for (StackTraceElement elem : trace) {
+            bufferSize += 4 + elem.getClassName().length() * 2;
+            bufferSize += 4 + elem.getMethodName().length() * 2;
+            bufferSize += 4;
+            if (elem.getFileName() != null)
+                bufferSize += elem.getFileName().length() * 2;
+            bufferSize += 4;        // line number
+        }
+
+        ByteBuffer out = ByteBuffer.allocate(bufferSize);
+        out.putInt(0);
+        out.putInt(threadId);
+        out.putInt(trace.length);
+        for (StackTraceElement elem : trace) {
+            out.putInt(elem.getClassName().length());
+            putString(out, elem.getClassName());
+            out.putInt(elem.getMethodName().length());
+            putString(out, elem.getMethodName());
+            if (elem.getFileName() != null) {
+                out.putInt(elem.getFileName().length());
+                putString(out, elem.getFileName());
+            } else {
+                out.putInt(0);
+            }
+            out.putInt(elem.getLineNumber());
+        }
+
+        return new Chunk(CHUNK_STKL, out);
+    }
+}
+
diff --git a/core/java/android/ddm/DdmRegister.java b/core/java/android/ddm/DdmRegister.java
new file mode 100644
index 0000000..b7f1ab8
--- /dev/null
+++ b/core/java/android/ddm/DdmRegister.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2007 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 android.ddm;
+
+import org.apache.harmony.dalvik.ddmc.DdmServer;
+import android.util.Config;
+import android.util.Log;
+
+/**
+ * Just a place to stick handler registrations, instead of scattering
+ * them around.
+ */
+public class DdmRegister {
+
+    private DdmRegister() {}
+
+    /**
+     * Register handlers for all known chunk types.
+     *
+     * If you write a handler, add a registration call here.
+     *
+     * Note that this is invoked by the application (usually through a
+     * static initializer in the main class), not the VM.  It's done this
+     * way so that the handlers can use Android classes with native calls
+     * that aren't registered until after the VM is initialized (e.g.
+     * logging).  It also allows debugging of DDM handler initialization.
+     *
+     * The chunk dispatcher will pause until we call registrationComplete(),
+     * so that we don't have a race that causes us to drop packets before
+     * we finish here.
+     */
+    public static void registerHandlers() {
+        if (Config.LOGV)
+            Log.v("ddm", "Registering DDM message handlers");
+        DdmHandleHello.register();
+        DdmHandleThread.register();
+        DdmHandleHeap.register();
+        DdmHandleNativeHeap.register();
+        DdmHandleExit.register();
+
+        DdmServer.registrationComplete();
+    }
+}
+
diff --git a/core/java/android/ddm/README.txt b/core/java/android/ddm/README.txt
new file mode 100644
index 0000000..a8e645d
--- /dev/null
+++ b/core/java/android/ddm/README.txt
@@ -0,0 +1,6 @@
+Some classes that handle DDM traffic.
+
+It's not necessary to put all DDM-related code in this package; this just
+has the essentials.  Subclass org.apache.harmony.dalvik.ddmc.ChunkHandler and add a new
+registration call in DdmRegister.java.
+
diff --git a/core/java/android/ddm/package.html b/core/java/android/ddm/package.html
new file mode 100755
index 0000000..1c9bf9d
--- /dev/null
+++ b/core/java/android/ddm/package.html
@@ -0,0 +1,5 @@
+<html>
+<body>
+    {@hide}
+</body>
+</html>
diff --git a/core/java/android/debug/JNITest.java b/core/java/android/debug/JNITest.java
new file mode 100644
index 0000000..2ce374a
--- /dev/null
+++ b/core/java/android/debug/JNITest.java
@@ -0,0 +1,48 @@
+/*
+ * 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 android.debug;
+
+/**
+ * Simple JNI verification test.
+ */
+public class JNITest {
+
+    public JNITest() {
+    }
+
+    public int test(int intArg, double doubleArg, String stringArg) {
+        int[] intArray = { 42, 53, 65, 127 };
+
+        return part1(intArg, doubleArg, stringArg, intArray);
+    }
+
+    private native int part1(int intArg, double doubleArg, String stringArg,
+        int[] arrayArg);
+
+    private int part2(double doubleArg, int fromArray, String stringArg) {
+        int result;
+
+        System.out.println(stringArg + " : " + (float) doubleArg + " : " +
+            fromArray);
+        result = part3(stringArg);
+
+        return result + 6;
+    }
+
+    private static native int part3(String stringArg);
+}
+
diff --git a/core/java/android/debug/package.html b/core/java/android/debug/package.html
new file mode 100755
index 0000000..c9f96a6
--- /dev/null
+++ b/core/java/android/debug/package.html
@@ -0,0 +1,5 @@
+<body>
+
+{@hide}
+
+</body>
diff --git a/core/java/android/gadget/GadgetHost.java b/core/java/android/gadget/GadgetHost.java
new file mode 100644
index 0000000..3d88b58
--- /dev/null
+++ b/core/java/android/gadget/GadgetHost.java
@@ -0,0 +1,249 @@
+/*
+ * 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 android.gadget;
+
+import android.content.Context;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.widget.RemoteViews;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+
+import com.android.internal.gadget.IGadgetHost;
+import com.android.internal.gadget.IGadgetService;
+
+/**
+ * GadgetHost provides the interaction with the Gadget Service for apps,
+ * like the home screen, that want to embed gadgets in their UI.
+ */
+public class GadgetHost {
+
+    static final int HANDLE_UPDATE = 1;
+    static final int HANDLE_PROVIDER_CHANGED = 2;
+
+    static Object sServiceLock = new Object();
+    static IGadgetService sService;
+
+    Context mContext;
+    String mPackageName;
+
+    class Callbacks extends IGadgetHost.Stub {
+        public void updateGadget(int gadgetId, RemoteViews views) {
+            Message msg = mHandler.obtainMessage(HANDLE_UPDATE);
+            msg.arg1 = gadgetId;
+            msg.obj = views;
+            msg.sendToTarget();
+        }
+
+        public void providerChanged(int gadgetId, GadgetProviderInfo info) {
+            Message msg = mHandler.obtainMessage(HANDLE_PROVIDER_CHANGED);
+            msg.arg1 = gadgetId;
+            msg.obj = info;
+            msg.sendToTarget();
+        }
+    }
+
+    class UpdateHandler extends Handler {
+        public UpdateHandler(Looper looper) {
+            super(looper);
+        }
+        
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case HANDLE_UPDATE: {
+                    updateGadgetView(msg.arg1, (RemoteViews)msg.obj);
+                    break;
+                }
+                case HANDLE_PROVIDER_CHANGED: {
+                    onProviderChanged(msg.arg1, (GadgetProviderInfo)msg.obj);
+                    break;
+                }
+            }
+        }
+    }
+    
+    Handler mHandler;
+
+    int mHostId;
+    Callbacks mCallbacks = new Callbacks();
+    HashMap<Integer,GadgetHostView> mViews = new HashMap();
+
+    public GadgetHost(Context context, int hostId) {
+        mContext = context;
+        mHostId = hostId;
+        mHandler = new UpdateHandler(context.getMainLooper());
+        synchronized (sServiceLock) {
+            if (sService == null) {
+                IBinder b = ServiceManager.getService(Context.GADGET_SERVICE);
+                sService = IGadgetService.Stub.asInterface(b);
+            }
+        }
+    }
+
+    /**
+     * Start receiving onGadgetChanged calls for your gadgets.  Call this when your activity
+     * becomes visible, i.e. from onStart() in your Activity.
+     */
+    public void startListening() {
+        int[] updatedIds = null;
+        ArrayList<RemoteViews> updatedViews = new ArrayList();
+        
+        try {
+            if (mPackageName == null) {
+                mPackageName = mContext.getPackageName();
+            }
+            updatedIds = sService.startListening(mCallbacks, mPackageName, mHostId, updatedViews);
+        }
+        catch (RemoteException e) {
+            throw new RuntimeException("system server dead?", e);
+        }
+
+        final int N = updatedIds.length;
+        for (int i=0; i<N; i++) {
+            updateGadgetView(updatedIds[i], updatedViews.get(i));
+        }
+    }
+
+    /**
+     * Stop receiving onGadgetChanged calls for your gadgets.  Call this when your activity is
+     * no longer visible, i.e. from onStop() in your Activity.
+     */
+    public void stopListening() {
+        try {
+            sService.stopListening(mHostId);
+        }
+        catch (RemoteException e) {
+            throw new RuntimeException("system server dead?", e);
+        }
+    }
+
+    /**
+     * Get a gadgetId for a host in the calling process.
+     *
+     * @return a gadgetId
+     */
+    public int allocateGadgetId() {
+        try {
+            if (mPackageName == null) {
+                mPackageName = mContext.getPackageName();
+            }
+            return sService.allocateGadgetId(mPackageName, mHostId);
+        }
+        catch (RemoteException e) {
+            throw new RuntimeException("system server dead?", e);
+        }
+    }
+
+    /**
+     * Stop listening to changes for this gadget.  
+     */
+    public void deleteGadgetId(int gadgetId) {
+        synchronized (mViews) {
+            mViews.remove(gadgetId);
+            try {
+                sService.deleteGadgetId(gadgetId);
+            }
+            catch (RemoteException e) {
+                throw new RuntimeException("system server dead?", e);
+            }
+        }
+    }
+
+    /**
+     * Remove all records about this host from the gadget manager.
+     * <ul>
+     *   <li>Call this when initializing your database, as it might be because of a data wipe.</li>
+     *   <li>Call this to have the gadget manager release all resources associated with your
+     *   host.  Any future calls about this host will cause the records to be re-allocated.</li>
+     * </ul>
+     */
+    public void deleteHost() {
+        try {
+            sService.deleteHost(mHostId);
+        }
+        catch (RemoteException e) {
+            throw new RuntimeException("system server dead?", e);
+        }
+    }
+
+    /**
+     * Remove all records about all hosts for your package.
+     * <ul>
+     *   <li>Call this when initializing your database, as it might be because of a data wipe.</li>
+     *   <li>Call this to have the gadget manager release all resources associated with your
+     *   host.  Any future calls about this host will cause the records to be re-allocated.</li>
+     * </ul>
+     */
+    public static void deleteAllHosts() {
+        try {
+            sService.deleteAllHosts();
+        }
+        catch (RemoteException e) {
+            throw new RuntimeException("system server dead?", e);
+        }
+    }
+
+    public final GadgetHostView createView(Context context, int gadgetId,
+            GadgetProviderInfo gadget) {
+        GadgetHostView view = onCreateView(context, gadgetId, gadget);
+        view.setGadget(gadgetId, gadget);
+        synchronized (mViews) {
+            mViews.put(gadgetId, view);
+        }
+        RemoteViews views = null;
+        try {
+            views = sService.getGadgetViews(gadgetId);
+        } catch (RemoteException e) {
+            throw new RuntimeException("system server dead?", e);
+        }
+        view.updateGadget(views);
+        return view;
+    }
+
+    /**
+     * Called to create the GadgetHostView.  Override to return a custom subclass if you
+     * need it.  {@more}
+     */
+    protected GadgetHostView onCreateView(Context context, int gadgetId,
+            GadgetProviderInfo gadget) {
+        return new GadgetHostView(context);
+    }
+    
+    /**
+     * Called when the gadget provider for a gadget has been upgraded to a new apk.
+     */
+    protected void onProviderChanged(int gadgetId, GadgetProviderInfo gadget) {
+    }
+
+    void updateGadgetView(int gadgetId, RemoteViews views) {
+        GadgetHostView v;
+        synchronized (mViews) {
+            v = mViews.get(gadgetId);
+        }
+        if (v != null) {
+            v.updateGadget(views);
+        }
+    }
+}
+
+
diff --git a/core/java/android/gadget/GadgetHostView.java b/core/java/android/gadget/GadgetHostView.java
new file mode 100644
index 0000000..5cbd988
--- /dev/null
+++ b/core/java/android/gadget/GadgetHostView.java
@@ -0,0 +1,312 @@
+/*
+ * 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 android.gadget;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.os.Handler;
+import android.os.Message;
+import android.os.SystemClock;
+import android.util.Config;
+import android.util.Log;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.animation.Animation;
+import android.widget.FrameLayout;
+import android.widget.RemoteViews;
+import android.widget.TextView;
+
+/**
+ * Provides the glue to show gadget views. This class offers automatic animation
+ * between updates, and will try recycling old views for each incoming
+ * {@link RemoteViews}.
+ */
+public class GadgetHostView extends FrameLayout {
+    static final String TAG = "GadgetHostView";
+    static final boolean LOGD = false;
+    static final boolean CROSSFADE = false;
+
+    static final int VIEW_MODE_NOINIT = 0;
+    static final int VIEW_MODE_CONTENT = 1;
+    static final int VIEW_MODE_ERROR = 2;
+    static final int VIEW_MODE_DEFAULT = 3;
+
+    static final int FADE_DURATION = 1000;
+
+    // When we're inflating the initialLayout for a gadget, we only allow
+    // views that are allowed in RemoteViews.
+    static final LayoutInflater.Filter sInflaterFilter = new LayoutInflater.Filter() {
+        public boolean onLoadClass(Class clazz) {
+            return clazz.isAnnotationPresent(RemoteViews.RemoteView.class);
+        }
+    };
+
+    Context mContext;
+    
+    int mGadgetId;
+    GadgetProviderInfo mInfo;
+    View mView;
+    int mViewMode = VIEW_MODE_NOINIT;
+    int mLayoutId = -1;
+    long mFadeStartTime = -1;
+    Bitmap mOld;
+    Paint mOldPaint = new Paint();
+    
+    /**
+     * Create a host view.  Uses default fade animations.
+     */
+    public GadgetHostView(Context context) {
+        this(context, android.R.anim.fade_in, android.R.anim.fade_out);
+    }
+
+    /**
+     * Create a host view. Uses specified animations when pushing
+     * {@link #updateGadget(RemoteViews)}.
+     * 
+     * @param animationIn Resource ID of in animation to use
+     * @param animationOut Resource ID of out animation to use
+     */
+    public GadgetHostView(Context context, int animationIn, int animationOut) {
+        super(context);
+        mContext = context;
+    }
+    
+    /**
+     * Set the gadget that will be displayed by this view.
+     */
+    public void setGadget(int gadgetId, GadgetProviderInfo info) {
+        mGadgetId = gadgetId;
+        mInfo = info;
+    }
+    
+    public int getGadgetId() {
+        return mGadgetId;
+    }
+    
+    public GadgetProviderInfo getGadgetInfo() {
+        return mInfo;
+    }
+
+    /**
+     * Process a set of {@link RemoteViews} coming in as an update from the
+     * gadget provider. Will animate into these new views as needed.
+     */
+    public void updateGadget(RemoteViews remoteViews) {
+        if (LOGD) Log.d(TAG, "updateGadget called mOld=" + mOld);
+        
+        boolean recycled = false;
+        View content = null;
+        Exception exception = null;
+        
+        // Capture the old view into a bitmap so we can do the crossfade.
+        if (CROSSFADE) {
+            if (mFadeStartTime < 0) {
+                if (mView != null) {
+                    final int width = mView.getWidth();
+                    final int height = mView.getHeight();
+                    try {
+                        mOld = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
+                    } catch (OutOfMemoryError e) {
+                        // we just won't do the fade
+                        mOld = null;
+                    }
+                    if (mOld != null) {
+                        //mView.drawIntoBitmap(mOld);
+                    }
+                }
+            }
+        }
+        
+        if (remoteViews == null) {
+            if (mViewMode == VIEW_MODE_DEFAULT) {
+                // We've already done this -- nothing to do.
+                return;
+            }
+            content = getDefaultView();
+            mLayoutId = -1;
+            mViewMode = VIEW_MODE_DEFAULT;
+        } else {
+            int layoutId = remoteViews.getLayoutId();
+
+            // If our stale view has been prepared to match active, and the new
+            // layout matches, try recycling it
+            if (content == null && layoutId == mLayoutId) {
+                try {
+                    remoteViews.reapply(mContext, mView);
+                    content = mView;
+                    recycled = true;
+                    if (LOGD) Log.d(TAG, "was able to recycled existing layout");
+                } catch (RuntimeException e) {
+                    exception = e;
+                }
+            }
+            
+            // Try normal RemoteView inflation
+            if (content == null) {
+                try {
+                    content = remoteViews.apply(mContext, this);
+                    if (LOGD) Log.d(TAG, "had to inflate new layout");
+                } catch (RuntimeException e) {
+                    exception = e;
+                }
+            }
+
+            mLayoutId = layoutId;
+            mViewMode = VIEW_MODE_CONTENT;
+        }
+        
+        if (content == null) {
+            if (mViewMode == VIEW_MODE_ERROR) {
+                // We've already done this -- nothing to do.
+                return ;
+            }
+            Log.w(TAG, "updateGadget couldn't find any view, using error view", exception);
+            content = getErrorView();
+            mViewMode = VIEW_MODE_ERROR;
+        }
+        
+        if (!recycled) {
+            prepareView(content);
+            addView(content);
+        }
+
+        if (mView != content) {
+            removeView(mView);
+            mView = content;
+        }
+
+        if (CROSSFADE) {
+            if (mFadeStartTime < 0) {
+                // if there is already an animation in progress, don't do anything --
+                // the new view will pop in on top of the old one during the cross fade,
+                // and that looks okay.
+                mFadeStartTime = SystemClock.uptimeMillis();
+                invalidate();
+            }
+        }
+    }
+
+    protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
+        if (CROSSFADE) {
+            int alpha;
+            int l = child.getLeft();
+            int t = child.getTop();
+            if (mFadeStartTime > 0) {
+                alpha = (int)(((drawingTime-mFadeStartTime)*255)/FADE_DURATION);
+                if (alpha > 255) {
+                    alpha = 255;
+                }
+                Log.d(TAG, "drawChild alpha=" + alpha + " l=" + l + " t=" + t
+                        + " w=" + child.getWidth());
+                if (alpha != 255 && mOld != null) {
+                    mOldPaint.setAlpha(255-alpha);
+                    //canvas.drawBitmap(mOld, l, t, mOldPaint);
+                }
+            } else {
+                alpha = 255;
+            }
+            int restoreTo = canvas.saveLayerAlpha(l, t, child.getWidth(), child.getHeight(), alpha,
+                    Canvas.HAS_ALPHA_LAYER_SAVE_FLAG | Canvas.CLIP_TO_LAYER_SAVE_FLAG);
+            boolean rv = super.drawChild(canvas, child, drawingTime);
+            canvas.restoreToCount(restoreTo);
+            if (alpha < 255) {
+                invalidate();
+            } else {
+                mFadeStartTime = -1;
+                if (mOld != null) {
+                    mOld.recycle();
+                    mOld = null;
+                }
+            }
+            return rv;
+        } else {
+            return super.drawChild(canvas, child, drawingTime);
+        }
+    }
+    
+    /**
+     * Prepare the given view to be shown. This might include adjusting
+     * {@link FrameLayout.LayoutParams} before inserting.
+     */
+    protected void prepareView(View view) {
+        // Take requested dimensions from parent, but apply default gravity.
+        ViewGroup.LayoutParams requested = view.getLayoutParams();
+        if (requested == null) {
+            requested = new FrameLayout.LayoutParams(LayoutParams.FILL_PARENT,
+                    LayoutParams.FILL_PARENT);
+        }
+        
+        FrameLayout.LayoutParams params =
+            new FrameLayout.LayoutParams(requested.width, requested.height);
+        params.gravity = Gravity.CENTER;
+        view.setLayoutParams(params);
+    }
+    
+    /**
+     * Inflate and return the default layout requested by gadget provider.
+     */
+    protected View getDefaultView() {
+        View defaultView = null;
+        Exception exception = null;
+        
+        try {
+            if (mInfo != null) {
+                Context theirContext = mContext.createPackageContext(
+                        mInfo.provider.getPackageName(), 0 /* no flags */);
+                LayoutInflater inflater = (LayoutInflater)
+                        theirContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+                inflater = inflater.cloneInContext(theirContext);
+                inflater.setFilter(sInflaterFilter);
+                defaultView = inflater.inflate(mInfo.initialLayout, this, false);
+            } else {
+                Log.w(TAG, "can't inflate defaultView because mInfo is missing");
+            }
+        } catch (PackageManager.NameNotFoundException e) {
+            exception = e;
+        } catch (RuntimeException e) {
+            exception = e;
+        }
+        
+        if (exception != null && LOGD) {
+            Log.w(TAG, "Error inflating gadget " + mInfo, exception);
+        }
+        
+        if (defaultView == null) {
+            if (LOGD) Log.d(TAG, "getDefaultView couldn't find any view, so inflating error");
+            defaultView = getErrorView();
+        }
+        
+        return defaultView;
+    }
+    
+    /**
+     * Inflate and return a view that represents an error state.
+     */
+    protected View getErrorView() {
+        TextView tv = new TextView(mContext);
+        tv.setText(com.android.internal.R.string.gadget_host_error_inflating);
+        // TODO: get this color from somewhere.
+        tv.setBackgroundColor(Color.argb(127, 0, 0, 0));
+        return tv;
+    }
+}
diff --git a/core/java/android/gadget/GadgetManager.java b/core/java/android/gadget/GadgetManager.java
new file mode 100644
index 0000000..d2c4055
--- /dev/null
+++ b/core/java/android/gadget/GadgetManager.java
@@ -0,0 +1,320 @@
+/*
+ * 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 android.gadget;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.util.Log;
+import android.widget.RemoteViews;
+
+import com.android.internal.gadget.IGadgetService;
+
+import java.lang.ref.WeakReference;
+import java.util.List;
+import java.util.WeakHashMap;
+
+/**
+ * Updates gadget state; gets information about installed gadget providers and other
+ * gadget related state.
+ */
+public class GadgetManager {
+    static final String TAG = "GadgetManager";
+
+    /**
+     * Send this from your gadget host activity when you want to pick a gadget to display.
+     * The gadget picker activity will be launched.
+     * <p>
+     * You must supply the following extras:
+     * <table>
+     *   <tr>
+     *     <td>{@link #EXTRA_GADGET_ID}</td>
+     *     <td>A newly allocated gadgetId, which will be bound to the gadget provider
+     *         once the user has selected one.</td>
+     *  </tr>
+     * </table>
+     *
+     * <p>
+     * The system will respond with an onActivityResult call with the following extras in
+     * the intent:
+     * <table>
+     *   <tr>
+     *     <td>{@link #EXTRA_GADGET_ID}</td>
+     *     <td>The gadgetId that you supplied in the original intent.</td>
+     *  </tr>
+     * </table>
+     * <p>
+     * When you receive the result from the gadget pick activity, if the resultCode is
+     * {@link android.app.Activity#RESULT_OK}, a gadget has been selected.  You should then
+     * check the GadgetProviderInfo for the returned gadget, and if it has one, launch its configuration
+     * activity.  If {@link android.app.Activity#RESULT_CANCELED} is returned, you should delete
+     * the gadgetId.
+     *
+     * @see #ACTION_GADGET_CONFIGURE
+     */
+    public static final String ACTION_GADGET_PICK = "android.gadget.action.GADGET_PICK";
+
+    /**
+     * Sent when it is time to configure your gadget while it is being added to a host.
+     * This action is not sent as a broadcast to the gadget provider, but as a startActivity
+     * to the activity specified in the {@link GadgetProviderInfo GadgetProviderInfo meta-data}.
+     *
+     * <p>
+     * The intent will contain the following extras:
+     * <table>
+     *   <tr>
+     *     <td>{@link #EXTRA_GADGET_ID}</td>
+     *     <td>The gadgetId to configure.</td>
+     *  </tr>
+     * </table>
+     *
+     * <p>If you return {@link android.app.Activity#RESULT_OK} using
+     * {@link android.app.Activity#setResult Activity.setResult()}, the gadget will be added,
+     * and you will receive an {@link #ACTION_GADGET_UPDATE} broadcast for this gadget.
+     * If you return {@link android.app.Activity#RESULT_CANCELED}, the host will cancel the add
+     * and not display this gadget, and you will receive a {@link #ACTION_GADGET_DELETED} broadcast.
+     */
+    public static final String ACTION_GADGET_CONFIGURE = "android.gadget.action.GADGET_CONFIGURE";
+
+    /**
+     * An intent extra that contains one gadgetId.
+     * <p>
+     * The value will be an int that can be retrieved like this:
+     * {@sample frameworks/base/tests/gadgets/GadgetHostTest/src/com/android/tests/gadgethost/GadgetHostActivity.java getExtra_EXTRA_GADGET_ID}
+     */
+    public static final String EXTRA_GADGET_ID = "gadgetId";
+
+    /**
+     * An intent extra that contains multiple gadgetIds.
+     * <p>
+     * The value will be an int array that can be retrieved like this:
+     * {@sample frameworks/base/tests/gadgets/GadgetHostTest/src/com/android/tests/gadgethost/TestGadgetProvider.java getExtra_EXTRA_GADGET_IDS}
+     */
+    public static final String EXTRA_GADGET_IDS = "gadgetIds";
+
+    /**
+     * A sentiel value that the gadget manager will never return as a gadgetId.
+     */
+    public static final int INVALID_GADGET_ID = 0;
+
+    /**
+     * Sent when it is time to update your gadget.
+     *
+     * <p>This may be sent in response to a new instance for this gadget provider having
+     * been instantiated, the requested {@link GadgetProviderInfo#updatePeriodMillis update interval}
+     * having lapsed, or the system booting.
+     *
+     * <p>
+     * The intent will contain the following extras:
+     * <table>
+     *   <tr>
+     *     <td>{@link #EXTRA_GADGET_IDS}</td>
+     *     <td>The gadgetIds to update.  This may be all of the gadgets created for this
+     *     provider, or just a subset.  The system tries to send updates for as few gadget
+     *     instances as possible.</td>
+     *  </tr>
+     * </table>
+     * 
+     * @see GadgetProvider#onUpdate GadgetProvider.onUpdate(Context context, GadgetManager gadgetManager, int[] gadgetIds)
+     */
+    public static final String ACTION_GADGET_UPDATE = "android.gadget.action.GADGET_UPDATE";
+
+    /**
+     * Sent when an instance of a gadget is deleted from its host.
+     *
+     * @see GadgetProvider#onDeleted GadgetProvider.onDeleted(Context context, int[] gadgetIds)
+     */
+    public static final String ACTION_GADGET_DELETED = "android.gadget.action.GADGET_DELETED";
+
+    /**
+     * Sent when an instance of a gadget is removed from the last host.
+     * 
+     * @see GadgetProvider#onEnabled GadgetProvider.onEnabled(Context context)
+     */
+    public static final String ACTION_GADGET_DISABLED = "android.gadget.action.GADGET_DISABLED";
+
+    /**
+     * Sent when an instance of a gadget is added to a host for the first time.
+     * This broadcast is sent at boot time if there is a gadget host installed with
+     * an instance for this provider.
+     * 
+     * @see GadgetProvider#onEnabled GadgetProvider.onEnabled(Context context)
+     */
+    public static final String ACTION_GADGET_ENABLED = "android.gadget.action.GADGET_ENABLED";
+
+    /**
+     * Field for the manifest meta-data tag.
+     *
+     * @see GadgetProviderInfo
+     */
+    public static final String META_DATA_GADGET_PROVIDER = "android.gadget.provider";
+
+    static WeakHashMap<Context, WeakReference<GadgetManager>> sManagerCache = new WeakHashMap();
+    static IGadgetService sService;
+    
+    Context mContext;
+
+    /**
+     * Get the GadgetManager instance to use for the supplied {@link android.content.Context
+     * Context} object.
+     */
+    public static GadgetManager getInstance(Context context) {
+        synchronized (sManagerCache) {
+            if (sService == null) {
+                IBinder b = ServiceManager.getService(Context.GADGET_SERVICE);
+                sService = IGadgetService.Stub.asInterface(b);
+            }
+
+            WeakReference<GadgetManager> ref = sManagerCache.get(context);
+            GadgetManager result = null;
+            if (ref != null) {
+                result = ref.get();
+            }
+            if (result == null) {
+                result = new GadgetManager(context);
+                sManagerCache.put(context, new WeakReference(result));
+            }
+            return result;
+        }
+    }
+
+    private GadgetManager(Context context) {
+        mContext = context;
+    }
+
+    /**
+     * Set the RemoteViews to use for the specified gadgetIds.
+     *
+     * <p>
+     * It is okay to call this method both inside an {@link #ACTION_GADGET_UPDATE} broadcast,
+     * and outside of the handler.
+     * This method will only work when called from the uid that owns the gadget provider.
+     *
+     * @param gadgetIds     The gadget instances for which to set the RemoteViews.
+     * @param views         The RemoteViews object to show.
+     */
+    public void updateGadget(int[] gadgetIds, RemoteViews views) {
+        try {
+            sService.updateGadgetIds(gadgetIds, views);
+        }
+        catch (RemoteException e) {
+            throw new RuntimeException("system server dead?", e);
+        }
+    }
+
+    /**
+     * Set the RemoteViews to use for the specified gadgetId.
+     *
+     * <p>
+     * It is okay to call this method both inside an {@link #ACTION_GADGET_UPDATE} broadcast,
+     * and outside of the handler.
+     * This method will only work when called from the uid that owns the gadget provider.
+     *
+     * @param gadgetId      The gadget instance for which to set the RemoteViews.
+     * @param views         The RemoteViews object to show.
+     */
+    public void updateGadget(int gadgetId, RemoteViews views) {
+        updateGadget(new int[] { gadgetId }, views);
+    }
+
+    /**
+     * Set the RemoteViews to use for all gadget instances for the supplied gadget provider.
+     *
+     * <p>
+     * It is okay to call this method both inside an {@link #ACTION_GADGET_UPDATE} broadcast,
+     * and outside of the handler.
+     * This method will only work when called from the uid that owns the gadget provider.
+     *
+     * @param provider      The {@link ComponentName} for the {@link
+     * android.content.BroadcastReceiver BroadcastReceiver} provider
+     *                      for your gadget.
+     * @param views         The RemoteViews object to show.
+     */
+    public void updateGadget(ComponentName provider, RemoteViews views) {
+        try {
+            sService.updateGadgetProvider(provider, views);
+        }
+        catch (RemoteException e) {
+            throw new RuntimeException("system server dead?", e);
+        }
+    }
+
+    /**
+     * Return a list of the gadget providers that are currently installed.
+     */
+    public List<GadgetProviderInfo> getInstalledProviders() {
+        try {
+            return sService.getInstalledProviders();
+        }
+        catch (RemoteException e) {
+            throw new RuntimeException("system server dead?", e);
+        }
+    }
+
+    /**
+     * Get the available info about the gadget.
+     *
+     * @return A gadgetId.  If the gadgetId has not been bound to a provider yet, or
+     * you don't have access to that gadgetId, null is returned.
+     */
+    public GadgetProviderInfo getGadgetInfo(int gadgetId) {
+        try {
+            return sService.getGadgetInfo(gadgetId);
+        }
+        catch (RemoteException e) {
+            throw new RuntimeException("system server dead?", e);
+        }
+    }
+
+    /**
+     * Set the component for a given gadgetId.
+     *
+     * <p class="note">You need the GADGET_LIST permission.  This method is to be used by the
+     * gadget picker.
+     *
+     * @param gadgetId     The gadget instance for which to set the RemoteViews.
+     * @param provider      The {@link android.content.BroadcastReceiver} that will be the gadget
+     *                      provider for this gadget.
+     */
+    public void bindGadgetId(int gadgetId, ComponentName provider) {
+        try {
+            sService.bindGadgetId(gadgetId, provider);
+        }
+        catch (RemoteException e) {
+            throw new RuntimeException("system server dead?", e);
+        }
+    }
+
+    /**
+     * Get the list of gadgetIds that have been bound to the given gadget
+     * provider.
+     * 
+     * @param provider The {@link android.content.BroadcastReceiver} that is the
+     *            gadget provider to find gadgetIds for.
+     */
+    public int[] getGadgetIds(ComponentName provider) {
+        try {
+            return sService.getGadgetIds(provider);
+        }
+        catch (RemoteException e) {
+            throw new RuntimeException("system server dead?", e);
+        }
+    }
+}
+
diff --git a/core/java/android/gadget/GadgetProvider.java b/core/java/android/gadget/GadgetProvider.java
new file mode 100755
index 0000000..7e10e78
--- /dev/null
+++ b/core/java/android/gadget/GadgetProvider.java
@@ -0,0 +1,154 @@
+/*
+ * 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 android.gadget;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+
+/**
+ * A conveience class to aid in implementing a gadget provider.
+ * Everything you can do with GadgetProvider, you can do with a regular {@link BroadcastReceiver}.
+ * GadgetProvider merely parses the relevant fields out of the Intent that is received in
+ * {@link #onReceive(Context,Intent) onReceive(Context,Intent)}, and calls hook methods
+ * with the received extras.
+ *
+ * <p>Extend this class and override one or more of the {@link #onUpdate}, {@link #onDeleted},
+ * {@link #onEnabled} or {@link #onDisabled} methods to implement your own gadget functionality.
+ *
+ * <h3>Sample Code</h3>
+ * For an example of how to write a gadget provider, see the
+ * <a href="{@toroot}reference/android/gadget/package-descr.html#providers">android.gadget
+ * package overview</a>.
+ */
+public class GadgetProvider extends BroadcastReceiver {
+    /**
+     * Constructor to initialize GadgetProvider.
+     */
+    public GadgetProvider() {
+    }
+
+    /**
+     * Implements {@link BroadcastReceiver#onReceive} to dispatch calls to the various
+     * other methods on GadgetProvider.  
+     *
+     * @param context The Context in which the receiver is running.
+     * @param intent The Intent being received.
+     */
+    // BEGIN_INCLUDE(onReceive)
+    public void onReceive(Context context, Intent intent) {
+        // Protect against rogue update broadcasts (not really a security issue,
+        // just filter bad broacasts out so subclasses are less likely to crash).
+        String action = intent.getAction();
+        if (GadgetManager.ACTION_GADGET_UPDATE.equals(action)) {
+            Bundle extras = intent.getExtras();
+            if (extras != null) {
+                int[] gadgetIds = extras.getIntArray(GadgetManager.EXTRA_GADGET_IDS);
+                if (gadgetIds != null && gadgetIds.length > 0) {
+                    this.onUpdate(context, GadgetManager.getInstance(context), gadgetIds);
+                }
+            }
+        }
+        else if (GadgetManager.ACTION_GADGET_DELETED.equals(action)) {
+            Bundle extras = intent.getExtras();
+            if (extras != null) {
+                int[] gadgetIds = extras.getIntArray(GadgetManager.EXTRA_GADGET_IDS);
+                if (gadgetIds != null && gadgetIds.length > 0) {
+                    this.onDeleted(context, gadgetIds);
+                }
+            }
+        }
+        else if (GadgetManager.ACTION_GADGET_ENABLED.equals(action)) {
+            this.onEnabled(context);
+        }
+        else if (GadgetManager.ACTION_GADGET_DISABLED.equals(action)) {
+            this.onDisabled(context);
+        }
+    }
+    // END_INCLUDE(onReceive)
+    
+    /**
+     * Called in response to the {@link GadgetManager#ACTION_GADGET_UPDATE} broadcast when
+     * this gadget provider is being asked to provide {@link android.widget.RemoteViews RemoteViews}
+     * for a set of gadgets.  Override this method to implement your own gadget functionality.
+     *
+     * {@more}
+     * 
+     * @param context   The {@link android.content.Context Context} in which this receiver is
+     *                  running.
+     * @param gadgetManager A {@link GadgetManager} object you can call {@link
+     *                  GadgetManager#updateGadget} on.
+     * @param gadgetIds The gadgetsIds for which an update is needed.  Note that this
+     *                  may be all of the gadget instances for this provider, or just
+     *                  a subset of them.
+     *
+     * @see GadgetManager#ACTION_GADGET_UPDATE
+     */
+    public void onUpdate(Context context, GadgetManager gadgetManager, int[] gadgetIds) {
+    }
+    
+    /**
+     * Called in response to the {@link GadgetManager#ACTION_GADGET_DELETED} broadcast when
+     * one or more gadget instances have been deleted.  Override this method to implement
+     * your own gadget functionality.
+     *
+     * {@more}
+     * 
+     * @param context   The {@link android.content.Context Context} in which this receiver is
+     *                  running.
+     * @param gadgetIds The gadgetsIds that have been deleted from their host.
+     *
+     * @see GadgetManager#ACTION_GADGET_DELETED
+     */
+    public void onDeleted(Context context, int[] gadgetIds) {
+    }
+
+    /**
+     * Called in response to the {@link GadgetManager#ACTION_GADGET_ENABLED} broadcast when
+     * the a gadget for this provider is instantiated.  Override this method to implement your
+     * own gadget functionality.
+     *
+     * {@more}
+     * When the last gadget for this provider is deleted,
+     * {@link GadgetManager#ACTION_GADGET_DISABLED} is sent by the gadget manager, and
+     * {@link #onDisabled} is called.  If after that, a gadget for this provider is created
+     * again, onEnabled() will be called again.
+     *
+     * @param context   The {@link android.content.Context Context} in which this receiver is
+     *                  running.
+     *
+     * @see GadgetManager#ACTION_GADGET_ENABLED
+     */
+    public void onEnabled(Context context) {
+    }
+
+    /**
+     * Called in response to the {@link GadgetManager#ACTION_GADGET_DISABLED} broadcast, which
+     * is sent when the last gadget instance for this provider is deleted.  Override this method
+     * to implement your own gadget functionality.
+     *
+     * {@more}
+     * 
+     * @param context   The {@link android.content.Context Context} in which this receiver is
+     *                  running.
+     *
+     * @see GadgetManager#ACTION_GADGET_DISABLED
+     */
+    public void onDisabled(Context context) {
+    }
+}
diff --git a/core/java/android/gadget/GadgetProviderInfo.aidl b/core/java/android/gadget/GadgetProviderInfo.aidl
new file mode 100644
index 0000000..589f886
--- /dev/null
+++ b/core/java/android/gadget/GadgetProviderInfo.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (c) 2007, 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 android.gadget;
+
+parcelable GadgetProviderInfo;
diff --git a/core/java/android/gadget/GadgetProviderInfo.java b/core/java/android/gadget/GadgetProviderInfo.java
new file mode 100644
index 0000000..95c0432
--- /dev/null
+++ b/core/java/android/gadget/GadgetProviderInfo.java
@@ -0,0 +1,168 @@
+/*
+ * 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 android.gadget;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.content.ComponentName;
+
+/**
+ * Describes the meta data for an installed gadget provider.  The fields in this class
+ * correspond to the fields in the <code>&lt;gadget-provider&gt;</code> xml tag.
+ */
+public class GadgetProviderInfo implements Parcelable {
+    /**
+     * Identity of this gadget component.  This component should be a {@link
+     * android.content.BroadcastReceiver}, and it will be sent the Gadget intents
+     * {@link android.gadget as described in the gadget package documentation}.
+     *
+     * <p>This field corresponds to the <code>android:name</code> attribute in
+     * the <code>&lt;receiver&gt;</code> element in the AndroidManifest.xml file.
+     */
+    public ComponentName provider;
+
+    /**
+     * Minimum width of the gadget, in dp.
+     *
+     * <p>This field corresponds to the <code>android:minWidth</code> attribute in
+     * the gadget meta-data file.
+     */
+    public int minWidth;
+
+    /**
+     * Minimum height of the gadget, in dp.
+     *
+     * <p>This field corresponds to the <code>android:minHeight</code> attribute in
+     * the gadget meta-data file.
+     */
+    public int minHeight;
+
+    /**
+     * How often, in milliseconds, that this gadget wants to be updated.
+     * The gadget manager may place a limit on how often a gadget is updated.
+     *
+     * <p>This field corresponds to the <code>android:updatePeriodMillis</code> attribute in
+     * the gadget meta-data file.
+     */
+    public int updatePeriodMillis;
+
+    /**
+     * The resource id of the initial layout for this gadget.  This should be
+     * displayed until the RemoteViews for the gadget is available.
+     *
+     * <p>This field corresponds to the <code>android:initialLayout</code> attribute in
+     * the gadget meta-data file.
+     */
+    public int initialLayout;
+
+    /**
+     * The activity to launch that will configure the gadget.
+     *
+     * <p>This class name of field corresponds to the <code>android:configure</code> attribute in
+     * the gadget meta-data file.  The package name always corresponds to the package containing
+     * the gadget provider.
+     */
+    public ComponentName configure;
+
+    /**
+     * The label to display to the user in the gadget picker.  If not supplied in the
+     * xml, the application label will be used.
+     *
+     * <p>This field corresponds to the <code>android:label</code> attribute in
+     * the <code>&lt;receiver&gt;</code> element in the AndroidManifest.xml file.
+     */
+    public String label;
+
+    /**
+     * The icon to display for this gadget in the gadget picker.  If not supplied in the
+     * xml, the application icon will be used.
+     *
+     * <p>This field corresponds to the <code>android:icon</code> attribute in
+     * the <code>&lt;receiver&gt;</code> element in the AndroidManifest.xml file.
+     */
+    public int icon;
+
+    public GadgetProviderInfo() {
+    }
+
+    /**
+     * Unflatten the GadgetProviderInfo from a parcel.
+     */
+    public GadgetProviderInfo(Parcel in) {
+        if (0 != in.readInt()) {
+            this.provider = new ComponentName(in);
+        }
+        this.minWidth = in.readInt();
+        this.minHeight = in.readInt();
+        this.updatePeriodMillis = in.readInt();
+        this.initialLayout = in.readInt();
+        if (0 != in.readInt()) {
+            this.configure = new ComponentName(in);
+        }
+        this.label = in.readString();
+        this.icon = in.readInt();
+    }
+
+
+    public void writeToParcel(android.os.Parcel out, int flags) {
+        if (this.provider != null) {
+            out.writeInt(1);
+            this.provider.writeToParcel(out, flags);
+        } else {
+            out.writeInt(0);
+        }
+        out.writeInt(this.minWidth);
+        out.writeInt(this.minHeight);
+        out.writeInt(this.updatePeriodMillis);
+        out.writeInt(this.initialLayout);
+        if (this.configure != null) {
+            out.writeInt(1);
+            this.configure.writeToParcel(out, flags);
+        } else {
+            out.writeInt(0);
+        }
+        out.writeString(this.label);
+        out.writeInt(this.icon);
+    }
+
+    public int describeContents() {
+        return 0;
+    }
+
+    /**
+     * Parcelable.Creator that instantiates GadgetProviderInfo objects
+     */
+    public static final Parcelable.Creator<GadgetProviderInfo> CREATOR
+            = new Parcelable.Creator<GadgetProviderInfo>()
+    {
+        public GadgetProviderInfo createFromParcel(Parcel parcel)
+        {
+            return new GadgetProviderInfo(parcel);
+        }
+
+        public GadgetProviderInfo[] newArray(int size)
+        {
+            return new GadgetProviderInfo[size];
+        }
+    };
+
+    public String toString() {
+        return "GadgetProviderInfo(provider=" + this.provider + ")";
+    }
+}
+
+
diff --git a/core/java/android/gadget/package.html b/core/java/android/gadget/package.html
new file mode 100644
index 0000000..4c04396
--- /dev/null
+++ b/core/java/android/gadget/package.html
@@ -0,0 +1,136 @@
+<body>
+<p>Android allows applications to publish views to be embedded in other applications.  These
+views are called gadgets, and are published by "gadget providers."  The component that can
+contain gadgets is called a "gadget host."
+</p>
+<h3><a href="package-descr.html#providers">Gadget Providers</a></h3>
+<ul>
+  <li><a href="package-descr.html#provider_manifest">Declaring a gadget in the AndroidManifest</a></li>
+  <li><a href="package-descr.html#provider_meta_data">Adding the GadgetProviderInfo meta-data</a></li>
+  <li><a href="package-descr.html#provider_GadgetProvider">Using the GadgetProvider class</a></li>
+  <li><a href="package-descr.html#provider_configuration">Gadget Configuration UI</a></li>
+  <li><a href="package-descr.html#provider_broadcasts">Gadget Broadcast Intents</a></li>
+</ul>
+<h3><a href="package-descr.html#">Gadget Hosts</a></h3>
+
+
+{@more}
+
+
+<h2><a name="providers"></a>Gadget Providers</h2>
+<p>
+Any application can publish gadgets.  All an application needs to do to publish a gadget is
+to have a {@link android.content.BroadcastReceiver} that receives the {@link
+android.gadget.GadgetManager#ACTION_GADGET_UPDATE GadgetManager.ACTION_GADGET_UPDATE} intent,
+and provide some meta-data about the gadget.  Android provides the
+{@link android.gadget.GadgetProvider} class, which extends BroadcastReceiver, as a convenience
+class to aid in handling the broadcasts.
+
+<h3><a name="provider_manifest"></a>Declaring a gadget in the AndroidManifest</h3>
+
+<p>
+First, declare the {@link android.content.BroadcastReceiver} in your application's
+<code>AndroidManifest.xml</code> file.
+
+{@sample frameworks/base/tests/gadgets/GadgetHostTest/AndroidManifest.xml GadgetProvider}
+
+<p>
+The <b><code>&lt;receiver&gt;</b> element has the following attributes:
+<ul>
+  <li><b><code>android:name</code> -</b> which specifies the
+        {@link android.content.BroadcastReceiver} or {@link android.gadget.GadgetProvider}
+        class.</li>
+  <li><b><code>android:label</code> -</b> which specifies the string resource that
+        will be shown by the gadget picker as the label.</li>
+  <li><b><code>android:icon</code> -</b> which specifies the drawable resource that
+        will be shown by the gadget picker as the icon.</li>
+</ul>
+
+<p>
+The <b><code>&lt;intent-filter&gt;</b> element tells the {@link android.content.pm.PackageManager}
+that this {@link android.content.BroadcastReceiver} receives the {@link
+android.gadget.GadgetManager#ACTION_GADGET_UPDATE GadgetManager.ACTION_GADGET_UPDATE} broadcast.
+The gadget manager will send other broadcasts directly to your gadget provider as required.
+It is only necessary to explicitly declare that you accept the {@link
+android.gadget.GadgetManager#ACTION_GADGET_UPDATE GadgetManager.ACTION_GADGET_UPDATE} broadcast.
+
+<p>
+The <b><code>&lt;meta-data&gt;</code></b> element tells the gadget manager which xml resource to
+read to find the {@link android.gadget.GadgetProviderInfo} for your gadget provider.  It has the following
+attributes:
+<ul>
+  <li><b><code>android:name="android.gadget.provider"</code> -</b> identifies this meta-data
+        as the {@link android.gadget.GadgetProviderInfo} descriptor.</li>
+  <li><b><code>android:resource</code> -</b> is the xml resource to use as that descriptor.</li>
+</ul>
+
+
+<h3><a name="provider_meta_data"></a>Adding the {@link android.gadget.GadgetProviderInfo GadgetProviderInfo} meta-data</h3>
+
+<p>
+For a gadget, the values in the {@link android.gadget.GadgetProviderInfo} structure are supplied
+in an XML resource.  In the example above, the xml resource is referenced with
+<code>android:resource="@xml/gadget_info"</code>.  That XML file would go in your application's
+directory at <code>res/xml/gadget_info.xml</code>.  Here is a simple example.
+
+{@sample frameworks/base/tests/gadgets/GadgetHostTest/res/xml/gadget_info.xml GadgetProviderInfo}
+
+<p>
+The attributes are as documented in the {@link android.gadget.GadgetProviderInfo GagetInfo} class.  (86400000 milliseconds means once per day)
+
+
+<h3><a name="provider_GadgetProvider"></a>Using the {@link android.gadget.GadgetProvider GadgetProvider} class</h3>
+
+<p>The GadgetProvider class is the easiest way to handle the gadget provider intent broadcasts.
+See the <code>src/com/example/android/apis/gadget/ExampleGadgetProvider.java</code>
+sample class in ApiDemos for an example.
+
+<p class="note">Keep in mind that since the the GadgetProvider is a BroadcastReceiver,
+your process is not guaranteed to keep running after the callback methods return.  See
+<a href="../../../guide/topics/fundamentals.html#broadlife">Application Fundamentals &gt;
+Broadcast Receiver Lifecycle</a> for more information.
+
+
+
+<h3><a name="provider_configuration"></a>Gadget Configuration UI</h3>
+
+<p>
+Gadget hosts have the ability to start a configuration activity when a gadget is instantiated.
+The activity should be declared as normal in AndroidManifest.xml, and it should be listed in
+the GadgetProviderInfo XML file in the <code>android:configure</code> attribute.
+
+<p>The activity you specified will be launched with the {@link
+android.gadget.GadgetManager#ACTION_GADGET_CONFIGURE} action.  See the documentation for that
+action for more info.
+
+<p>See the <code>src/com/example/android/apis/gadget/ExampleGadgetConfigure.java</code>
+sample class in ApiDemos for an example.
+
+
+
+<h3><a name="providers_broadcasts"></a>Gadget Broadcast Intents</h3>
+
+<p>{@link android.gadget.GadgetProvider} is just a convenience class.  If you would like
+to receive the gadget broadcasts directly, you can.  The four intents you need to care about are:
+<ul>
+  <li>{@link android.gadget.GadgetManager#ACTION_GADGET_UPDATE}</li>
+  <li>{@link android.gadget.GadgetManager#ACTION_GADGET_DELETED}</li>
+  <li>{@link android.gadget.GadgetManager#ACTION_GADGET_ENABLED}</li>
+  <li>{@link android.gadget.GadgetManager#ACTION_GADGET_DISABLED}</li>
+</ul>
+
+<p>By way of example, the implementation of
+{@link android.gadget.GadgetProvider#onReceive} is quite simple:</p>
+
+{@sample frameworks/base/core/java/android/gadget/GadgetProvider.java onReceive}
+
+
+<h2>Gadget Hosts</h3>
+<p>Gadget hosts are the containers in which gadgets can be placed.  Most of the look and feel
+details are left up to the gadget hosts.  For example, the home screen has one way of viewing
+gadgets, but the lock screen could also contain gadgets, and it would have a different way of
+adding, removing and otherwise managing gadgets.</p>
+<p>For more information on implementing your own gadget host, see the
+{@link android.gadget.GadgetHost GadgetHost} class.</p>
+</body>
+
diff --git a/core/java/android/hardware/Camera.java b/core/java/android/hardware/Camera.java
new file mode 100644
index 0000000..106c920
--- /dev/null
+++ b/core/java/android/hardware/Camera.java
@@ -0,0 +1,781 @@
+/*
+ * 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 android.hardware;
+
+import java.lang.ref.WeakReference;
+import java.util.HashMap;
+import java.util.StringTokenizer;
+import java.io.IOException;
+
+import android.util.Log;
+import android.view.Surface;
+import android.view.SurfaceHolder;
+import android.graphics.PixelFormat;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+
+/**
+ * The Camera class is used to connect/disconnect with the camera service,
+ * set capture settings, start/stop preview, snap a picture, and retrieve
+ * frames for encoding for video.
+ * <p>There is no default constructor for this class. Use {@link #open()} to
+ * get a Camera object.</p>
+ */
+public class Camera {
+    private static final String TAG = "Camera";
+    
+    // These match the enum in libs/android_runtime/android_hardware_Camera.cpp
+    private static final int SHUTTER_CALLBACK = 0;
+    private static final int RAW_PICTURE_CALLBACK = 1;
+    private static final int JPEG_PICTURE_CALLBACK = 2;
+    private static final int PREVIEW_CALLBACK = 3;
+    private static final int AUTOFOCUS_CALLBACK = 4;
+    private static final int ERROR_CALLBACK = 5;
+
+    private int mNativeContext; // accessed by native methods
+    private EventHandler mEventHandler;
+    private ShutterCallback mShutterCallback;
+    private PictureCallback mRawImageCallback;
+    private PictureCallback mJpegCallback;
+    private PreviewCallback mPreviewCallback;
+    private AutoFocusCallback mAutoFocusCallback;
+    private ErrorCallback mErrorCallback;
+    private boolean mOneShot;
+    
+    /**
+     * Returns a new Camera object.
+     */
+    public static Camera open() { 
+        return new Camera(); 
+    }
+
+    Camera() {
+        mShutterCallback = null;
+        mRawImageCallback = null;
+        mJpegCallback = null;
+        mPreviewCallback = null;
+
+        Looper looper;
+        if ((looper = Looper.myLooper()) != null) {
+            mEventHandler = new EventHandler(this, looper);
+        } else if ((looper = Looper.getMainLooper()) != null) {
+            mEventHandler = new EventHandler(this, looper);
+        } else {
+            mEventHandler = null;
+        }
+
+        native_setup(new WeakReference<Camera>(this));
+    }
+    
+    protected void finalize() { 
+        native_release(); 
+    }
+    
+    private native final void native_setup(Object camera_this);
+    private native final void native_release();
+    
+
+    /**
+     * Disconnects and releases the Camera object resources.
+     * <p>It is recommended that you call this as soon as you're done with the 
+     * Camera object.</p>
+     */
+    public final void release() { 
+        native_release();
+    }
+
+    /**
+     * Reconnect to the camera after passing it to MediaRecorder. To save
+     * setup/teardown time, a client of Camera can pass an initialized Camera
+     * object to a MediaRecorder to use for video recording. Once the 
+     * MediaRecorder is done with the Camera, this method can be used to
+     * re-establish a connection with the camera hardware. NOTE: The Camera
+     * object must first be unlocked by the process that owns it before it
+     * can be connected to another proces.
+     *
+     * @throws IOException if the method fails.
+     *
+     * FIXME: Unhide after approval
+     * @hide
+     */
+    public native final void reconnect() throws IOException;
+    
+    /**
+     * Lock the camera to prevent other processes from accessing it. To save
+     * setup/teardown time, a client of Camera can pass an initialized Camera
+     * object to another process. This method is used to re-lock the Camera
+     * object prevent other processes from accessing it. By default, the
+     * Camera object is locked. Locking it again from the same process will
+     * have no effect. Attempting to lock it from another process if it has
+     * not been unlocked will fail.
+     * Returns 0 if lock was successful.
+     *
+     * FIXME: Unhide after approval
+     * @hide
+     */
+    public native final int lock();
+    
+    /**
+     * Unlock the camera to allow aother process to access it. To save
+     * setup/teardown time, a client of Camera can pass an initialized Camera
+     * object to another process. This method is used to unlock the Camera
+     * object before handing off the Camera object to the other process.
+
+     * Returns 0 if unlock was successful.
+     *
+     * FIXME: Unhide after approval
+     * @hide
+     */
+    public native final int unlock();
+    
+    /**
+     * Sets the SurfaceHolder to be used for a picture preview. If the surface
+     * changed since the last call, the screen will blank. Nothing happens
+     * if the same surface is re-set.
+     * 
+     * @param holder the SurfaceHolder upon which to place the picture preview
+     * @throws IOException if the method fails.
+     */
+    public final void setPreviewDisplay(SurfaceHolder holder) throws IOException {
+        setPreviewDisplay(holder.getSurface());
+    }
+
+    private native final void setPreviewDisplay(Surface surface);
+
+    /**
+     * Used to get a copy of each preview frame.
+     */
+    public interface PreviewCallback
+    {
+        /**
+         * The callback that delivers the preview frames.
+         *
+         * @param data The contents of the preview frame in getPreviewFormat()
+         *             format.
+         * @param camera The Camera service object.
+         */
+        void onPreviewFrame(byte[] data, Camera camera);
+    };
+    
+    /**
+     * Start drawing preview frames to the surface.
+     */
+    public native final void startPreview();
+    
+    /**
+     * Stop drawing preview frames to the surface.
+     */
+    public native final void stopPreview();
+    
+    /**
+     * Return current preview state.
+     *
+     * FIXME: Unhide before release
+     * @hide
+     */
+    public native final boolean previewEnabled();
+    
+    /**
+     * Can be called at any time to instruct the camera to use a callback for
+     * each preview frame in addition to displaying it.
+     *
+     * @param cb A callback object that receives a copy of each preview frame.
+     *           Pass null to stop receiving callbacks at any time.
+     */
+    public final void setPreviewCallback(PreviewCallback cb) {
+        mPreviewCallback = cb;
+        mOneShot = false;
+        setHasPreviewCallback(cb != null, false);
+    }
+
+    /**
+     * Installs a callback to retrieve a single preview frame, after which the
+     * callback is cleared.
+     *
+     * @param cb A callback object that receives a copy of the preview frame.
+     */
+    public final void setOneShotPreviewCallback(PreviewCallback cb) {
+        if (cb != null) {
+            mPreviewCallback = cb;
+            mOneShot = true;
+            setHasPreviewCallback(true, true);
+        }
+    }
+
+    private native final void setHasPreviewCallback(boolean installed, boolean oneshot);
+
+    private class EventHandler extends Handler
+    {
+        private Camera mCamera;
+
+        public EventHandler(Camera c, Looper looper) {
+            super(looper);
+            mCamera = c;
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            switch(msg.what) {
+            case SHUTTER_CALLBACK:
+                if (mShutterCallback != null) {
+                    mShutterCallback.onShutter();
+                }
+                return;
+            case RAW_PICTURE_CALLBACK:
+                if (mRawImageCallback != null)
+                    mRawImageCallback.onPictureTaken((byte[])msg.obj, mCamera);
+                return;
+
+            case JPEG_PICTURE_CALLBACK:
+                if (mJpegCallback != null)
+                    mJpegCallback.onPictureTaken((byte[])msg.obj, mCamera);
+                return;
+            
+            case PREVIEW_CALLBACK:
+                if (mPreviewCallback != null) {
+                    mPreviewCallback.onPreviewFrame((byte[])msg.obj, mCamera);
+                    if (mOneShot) {
+                        mPreviewCallback = null;
+                    }
+                }
+                return;
+
+            case AUTOFOCUS_CALLBACK:
+                if (mAutoFocusCallback != null)
+                    mAutoFocusCallback.onAutoFocus(msg.arg1 == 0 ? false : true, mCamera);
+                return;
+
+            case ERROR_CALLBACK:
+                Log.e(TAG, "Error " + msg.arg1);
+                if (mErrorCallback != null)
+                    mErrorCallback.onError(msg.arg1, mCamera);
+                return;
+
+            default:
+                Log.e(TAG, "Unknown message type " + msg.what);
+                return;
+            }
+        }
+    }
+
+    private static void postEventFromNative(Object camera_ref,
+                                            int what, int arg1, int arg2, Object obj)
+    {
+        Camera c = (Camera)((WeakReference)camera_ref).get();
+        if (c == null)
+            return;
+
+        if (c.mEventHandler != null) {
+            Message m = c.mEventHandler.obtainMessage(what, arg1, arg2, obj);
+            c.mEventHandler.sendMessage(m);
+        }
+    }
+
+    /**
+     * Handles the callback for the camera auto focus.
+     */
+    public interface AutoFocusCallback
+    {
+        /**
+         * Callback for the camera auto focus.
+         * 
+         * @param success true if focus was successful, false if otherwise
+         * @param camera  the Camera service object
+         */
+        void onAutoFocus(boolean success, Camera camera);
+    };
+
+    /**
+     * Starts auto-focus function and registers a callback function to
+     * run when camera is focused. Only valid after startPreview() has
+     * been called.
+     * 
+     * @param cb the callback to run
+     */
+    public final void autoFocus(AutoFocusCallback cb)
+    {
+        mAutoFocusCallback = cb;
+        native_autoFocus();
+    }
+    private native final void native_autoFocus();
+
+    /**
+     * An interface which contains a callback for the shutter closing after taking a picture.
+     */
+    public interface ShutterCallback
+    {
+        /**
+         * Can be used to play a shutter sound as soon as the image has been captured, but before
+         * the data is available.
+         */
+        void onShutter();
+    }
+
+    /**
+     * Handles the callback for when a picture is taken.
+     */
+    public interface PictureCallback {
+        /**
+         * Callback for when a picture is taken.
+         * 
+         * @param data   a byte array of the picture data
+         * @param camera the Camera service object
+         */
+        void onPictureTaken(byte[] data, Camera camera);
+    };
+
+    /**
+     * Triggers an asynchronous image capture. The camera service
+     * will initiate a series of callbacks to the application as the
+     * image capture progresses. The shutter callback occurs after
+     * the image is captured. This can be used to trigger a sound
+     * to let the user know that image has been captured. The raw
+     * callback occurs when the raw image data is available. The jpeg
+     * callback occurs when the compressed image is available. If the
+     * application does not need a particular callback, a null can be
+     * passed instead of a callback method.
+     * 
+     * @param shutter   callback after the image is captured, may be null
+     * @param raw       callback with raw image data, may be null
+     * @param jpeg      callback with jpeg image data, may be null
+     */
+    public final void takePicture(ShutterCallback shutter, PictureCallback raw,
+            PictureCallback jpeg) {
+        mShutterCallback = shutter;
+        mRawImageCallback = raw;
+        mJpegCallback = jpeg;
+        native_takePicture();
+    }
+    private native final void native_takePicture();
+
+    // These match the enum in libs/android_runtime/android_hardware_Camera.cpp
+    /** Unspecified camerar error.  @see #ErrorCallback */
+    public static final int CAMERA_ERROR_UNKNOWN = 1;
+    /** Media server died. In this case, the application must release the
+     * Camera object and instantiate a new one. @see #ErrorCallback */
+    public static final int CAMERA_ERROR_SERVER_DIED = 100;
+    
+    /**
+     * Handles the camera error callback.
+     */
+    public interface ErrorCallback
+    {
+        /**
+         * Callback for camera errors.
+         * @param error   error code:
+         * <ul>
+         * <li>{@link #CAMERA_ERROR_UNKNOWN}
+         * <li>{@link #CAMERA_ERROR_SERVER_DIED}
+         * </ul>
+         * @param camera  the Camera service object
+         */
+        void onError(int error, Camera camera);
+    };
+
+    /**
+     * Registers a callback to be invoked when an error occurs.
+     * @param cb the callback to run
+     */
+    public final void setErrorCallback(ErrorCallback cb)
+    {
+        mErrorCallback = cb;
+    }
+    
+    private native final void native_setParameters(String params);
+    private native final String native_getParameters();
+
+    /**
+     * Sets the Parameters for pictures from this Camera service.
+     * 
+     * @param params the Parameters to use for this Camera service
+     */
+    public void setParameters(Parameters params) {
+        Log.e(TAG, "setParameters()");
+        //params.dump();
+        native_setParameters(params.flatten());
+    }
+
+    /**
+     * Returns the picture Parameters for this Camera service.
+     */
+    public Parameters getParameters() {
+        Parameters p = new Parameters();
+        String s = native_getParameters();
+        Log.e(TAG, "_getParameters: " + s);
+        p.unflatten(s);
+        return p;
+    }
+
+    /**
+     * Handles the picture size (dimensions).
+     */
+    public class Size {
+        /**
+         * Sets the dimensions for pictures.
+         * 
+         * @param w the photo width (pixels)
+         * @param h the photo height (pixels)
+         */
+        public Size(int w, int h) {
+            width = w;
+            height = h;
+        }
+        /** width of the picture */
+        public int width;
+        /** height of the picture */
+        public int height;
+    };
+
+    /**
+     * Handles the parameters for pictures created by a Camera service.
+     */
+    public class Parameters {
+        private HashMap<String, String> mMap;
+
+        private Parameters() {
+            mMap = new HashMap<String, String>();
+        }
+
+        /**
+         * Writes the current Parameters to the log.
+         * @hide
+         * @deprecated
+         */
+        public void dump() {
+            Log.e(TAG, "dump: size=" + mMap.size());
+            for (String k : mMap.keySet()) {
+                Log.e(TAG, "dump: " + k + "=" + mMap.get(k));
+            }
+        }
+
+        /**
+         * Creates a single string with all the parameters set in
+         * this Parameters object.
+         * <p>The {@link #unflatten(String)} method does the reverse.</p>
+         * 
+         * @return a String with all values from this Parameters object, in
+         *         semi-colon delimited key-value pairs
+         */
+        public String flatten() {
+            StringBuilder flattened = new StringBuilder();
+            for (String k : mMap.keySet()) {
+                flattened.append(k);
+                flattened.append("=");
+                flattened.append(mMap.get(k));
+                flattened.append(";");
+            }
+            // chop off the extra semicolon at the end
+            flattened.deleteCharAt(flattened.length()-1);
+            return flattened.toString();
+        }
+
+        /**
+         * Takes a flattened string of parameters and adds each one to 
+         * this Parameters object.
+         * <p>The {@link #flatten()} method does the reverse.</p>
+         * 
+         * @param flattened a String of parameters (key-value paired) that 
+         *                  are semi-colon delimited
+         */
+        public void unflatten(String flattened) {
+            mMap.clear();
+            
+            StringTokenizer tokenizer = new StringTokenizer(flattened, ";");
+            while (tokenizer.hasMoreElements()) {
+                String kv = tokenizer.nextToken();
+                int pos = kv.indexOf('=');
+                if (pos == -1) {
+                    continue;
+                }
+                String k = kv.substring(0, pos);
+                String v = kv.substring(pos + 1);
+                mMap.put(k, v);
+            }
+        }
+        
+        public void remove(String key) {
+            mMap.remove(key);
+        }
+
+        /**
+         * Sets a String parameter.
+         * 
+         * @param key   the key name for the parameter
+         * @param value the String value of the parameter
+         */
+        public void set(String key, String value) {
+            if (key.indexOf('=') != -1 || key.indexOf(';') != -1) {
+                Log.e(TAG, "Key \"" + key + "\" contains invalid character (= or ;)");
+                return;
+            }
+            if (value.indexOf('=') != -1 || value.indexOf(';') != -1) {
+                Log.e(TAG, "Value \"" + value + "\" contains invalid character (= or ;)");
+                return;
+            }
+
+            mMap.put(key, value);
+        }
+
+        /**
+         * Sets an integer parameter.
+         * 
+         * @param key   the key name for the parameter
+         * @param value the int value of the parameter
+         */
+        public void set(String key, int value) {
+            mMap.put(key, Integer.toString(value));
+        }
+
+        /**
+         * Returns the value of a String parameter.
+         * 
+         * @param key the key name for the parameter
+         * @return the String value of the parameter
+         */
+        public String get(String key) {
+            return mMap.get(key);
+        }
+
+        /**
+         * Returns the value of an integer parameter.
+         * 
+         * @param key the key name for the parameter
+         * @return the int value of the parameter
+         */
+        public int getInt(String key) {
+            return Integer.parseInt(mMap.get(key));
+        }
+
+        /**
+         * Sets the dimensions for preview pictures.
+         * 
+         * @param width  the width of the pictures, in pixels
+         * @param height the height of the pictures, in pixels
+         */
+        public void setPreviewSize(int width, int height) {
+            String v = Integer.toString(width) + "x" + Integer.toString(height);
+            set("preview-size", v);
+        }
+
+        /**
+         * Returns the dimensions setting for preview pictures.
+         * 
+         * @return a Size object with the height and width setting 
+         *          for the preview picture
+         */
+        public Size getPreviewSize() {
+            String pair = get("preview-size");
+            if (pair == null)
+                return null;
+            String[] dims = pair.split("x");
+            if (dims.length != 2)
+                return null;
+
+            return new Size(Integer.parseInt(dims[0]),
+                            Integer.parseInt(dims[1]));
+
+        }
+
+        /**
+         * Sets the dimensions for EXIF thumbnails.
+         * 
+         * @param width  the width of the thumbnail, in pixels
+         * @param height the height of the thumbnail, in pixels
+         *
+         * FIXME: unhide before release
+         * @hide
+         */
+        public void setThumbnailSize(int width, int height) {
+            set("jpeg-thumbnail-width", width);
+            set("jpeg-thumbnail-height", height);
+        }
+
+        /**
+         * Returns the dimensions for EXIF thumbnail
+         * 
+         * @return a Size object with the height and width setting 
+         *          for the EXIF thumbnails
+         *
+         * FIXME: unhide before release
+         * @hide
+         */
+        public Size getThumbnailSize() {
+            return new Size(getInt("jpeg-thumbnail-width"),
+                            getInt("jpeg-thumbnail-height"));
+        }
+
+        /**
+         * Sets the quality of the EXIF thumbnail
+         * 
+         * @param quality the JPEG quality of the EXIT thumbnail
+         *
+         * FIXME: unhide before release
+         * @hide
+         */
+        public void setThumbnailQuality(int quality) {
+            set("jpeg-thumbnail-quality", quality);
+        }
+
+        /**
+         * Returns the quality setting for the EXIF thumbnail
+         * 
+         * @return the JPEG quality setting of the EXIF thumbnail
+         *
+         * FIXME: unhide before release
+         * @hide
+         */
+        public int getThumbnailQuality() {
+            return getInt("jpeg-thumbnail-quality");
+        }
+
+        /**
+         * Sets the rate at which preview frames are received.
+         * 
+         * @param fps the frame rate (frames per second)
+         */
+        public void setPreviewFrameRate(int fps) {
+            set("preview-frame-rate", fps);
+        }
+
+        /**
+         * Returns the setting for the rate at which preview frames
+         * are received.
+         * 
+         * @return the frame rate setting (frames per second)
+         */
+        public int getPreviewFrameRate() {
+            return getInt("preview-frame-rate");
+        }
+
+        /**
+         * Sets the image format for preview pictures.
+         * 
+         * @param pixel_format the desired preview picture format 
+         *                     (<var>PixelFormat.YCbCr_420_SP</var>,
+         *                      <var>PixelFormat.RGB_565</var>, or
+         *                      <var>PixelFormat.JPEG</var>)
+         * @see android.graphics.PixelFormat
+         */
+        public void setPreviewFormat(int pixel_format) {
+            String s = cameraFormatForPixelFormat(pixel_format);
+            if (s == null) {
+                throw new IllegalArgumentException();
+            }
+
+            set("preview-format", s);
+        }
+
+        /**
+         * Returns the image format for preview pictures.
+         * 
+         * @return the PixelFormat int representing the preview picture format
+         */
+        public int getPreviewFormat() {
+            return pixelFormatForCameraFormat(get("preview-format"));
+        }
+
+        /**
+         * Sets the dimensions for pictures.
+         * 
+         * @param width  the width for pictures, in pixels
+         * @param height the height for pictures, in pixels
+         */
+        public void setPictureSize(int width, int height) {
+            String v = Integer.toString(width) + "x" + Integer.toString(height);
+            set("picture-size", v);
+        }
+
+        /**
+         * Returns the dimension setting for pictures.
+         * 
+         * @return a Size object with the height and width setting 
+         *          for pictures
+         */
+        public Size getPictureSize() {
+            String pair = get("picture-size");
+            if (pair == null)
+                return null;
+            String[] dims = pair.split("x");
+            if (dims.length != 2)
+                return null;
+
+            return new Size(Integer.parseInt(dims[0]),
+                            Integer.parseInt(dims[1]));
+
+        }
+
+        /**
+         * Sets the image format for pictures.
+         * 
+         * @param pixel_format the desired picture format 
+         *                     (<var>PixelFormat.YCbCr_420_SP</var>,
+         *                      <var>PixelFormat.RGB_565</var>, or
+         *                      <var>PixelFormat.JPEG</var>)
+         * @see android.graphics.PixelFormat
+         */
+        public void setPictureFormat(int pixel_format) {
+            String s = cameraFormatForPixelFormat(pixel_format);
+            if (s == null) {
+                throw new IllegalArgumentException();
+            }
+
+            set("picture-format", s);
+        }
+
+        /**
+         * Returns the image format for pictures.
+         * 
+         * @return the PixelFormat int representing the picture format
+         */
+        public int getPictureFormat() {
+            return pixelFormatForCameraFormat(get("picture-format"));
+        }
+
+        private String cameraFormatForPixelFormat(int pixel_format) {
+            switch(pixel_format) {
+            case PixelFormat.YCbCr_422_SP: return "yuv422sp";
+            case PixelFormat.YCbCr_420_SP: return "yuv420sp";
+            case PixelFormat.RGB_565:      return "rgb565";
+            case PixelFormat.JPEG:         return "jpeg";
+            default:                       return null;
+            }
+        }
+
+        private int pixelFormatForCameraFormat(String format) {
+            if (format == null)
+                return PixelFormat.UNKNOWN;
+
+            if (format.equals("yuv422sp"))
+                return PixelFormat.YCbCr_422_SP;
+
+            if (format.equals("yuv420sp"))
+                return PixelFormat.YCbCr_420_SP;
+
+            if (format.equals("rgb565"))
+                return PixelFormat.RGB_565;
+
+            if (format.equals("jpeg"))
+                return PixelFormat.JPEG;
+
+            return PixelFormat.UNKNOWN;
+        }
+
+    };
+}
+
+
diff --git a/core/java/android/hardware/GeomagneticField.java b/core/java/android/hardware/GeomagneticField.java
new file mode 100644
index 0000000..b4c04b1
--- /dev/null
+++ b/core/java/android/hardware/GeomagneticField.java
@@ -0,0 +1,409 @@
+/*
+ * 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 android.hardware;
+
+import java.util.GregorianCalendar;
+
+/**
+ * This class is used to estimated estimate magnetic field at a given point on
+ * Earth, and in particular, to compute the magnetic declination from true
+ * north.
+ *
+ * <p>This uses the World Magnetic Model produced by the United States National
+ * Geospatial-Intelligence Agency.  More details about the model can be found at
+ * <a href="http://www.ngdc.noaa.gov/geomag/WMM/DoDWMM.shtml">http://www.ngdc.noaa.gov/geomag/WMM/DoDWMM.shtml</a>.
+ * This class currently uses WMM-2005 which is valid until 2010, but should
+ * produce acceptable results for several years after that.
+ */
+public class GeomagneticField {
+    // The magnetic field at a given point, in nonoteslas in geodetic
+    // coordinates.
+    private float mX;
+    private float mY;
+    private float mZ;
+
+    // Geocentric coordinates -- set by computeGeocentricCoordinates.
+    private float mGcLatitudeRad;
+    private float mGcLongitudeRad;
+    private float mGcRadiusKm;
+
+    // Constants from WGS84 (the coordinate system used by GPS)
+    static private final float EARTH_SEMI_MAJOR_AXIS_KM = 6378.137f;
+    static private final float EARTH_SEMI_MINOR_AXIS_KM = 6356.7523f;
+    static private final float EARTH_REFERENCE_RADIUS_KM = 6371.2f;
+
+    // These coefficients and the formulae used below are from:
+    // NOAA Technical Report: The US/UK World Magnetic Model for 2005-2010
+    static private final float[][] G_COEFF = new float[][] {
+        { 0f },
+        { -29556.8f, -1671.7f },
+        { -2340.6f, 3046.9f, 1657.0f },
+        { 1335.4f, -2305.1f, 1246.7f, 674.0f },
+        { 919.8f, 798.1f, 211.3f, -379.4f, 100.0f },
+        { -227.4f, 354.6f, 208.7f, -136.5f, -168.3f, -14.1f },
+        { 73.2f, 69.7f, 76.7f, -151.2f, -14.9f, 14.6f, -86.3f },
+        { 80.1f, -74.5f, -1.4f, 38.5f, 12.4f, 9.5f, 5.7f, 1.8f },
+        { 24.9f, 7.7f, -11.6f, -6.9f, -18.2f, 10.0f, 9.2f, -11.6f, -5.2f },
+        { 5.6f, 9.9f, 3.5f, -7.0f, 5.1f, -10.8f, -1.3f, 8.8f, -6.7f, -9.1f },
+        { -2.3f, -6.3f, 1.6f, -2.6f, 0.0f, 3.1f, 0.4f, 2.1f, 3.9f, -0.1f, -2.3f },
+        { 2.8f, -1.6f, -1.7f, 1.7f, -0.1f, 0.1f, -0.7f, 0.7f, 1.8f, 0.0f, 1.1f, 4.1f },
+        { -2.4f, -0.4f, 0.2f, 0.8f, -0.3f, 1.1f, -0.5f, 0.4f, -0.3f, -0.3f, -0.1f,
+          -0.3f, -0.1f } };
+
+    static private final float[][] H_COEFF = new float[][] {
+        { 0f },
+        { 0.0f, 5079.8f },
+        { 0.0f, -2594.7f, -516.7f },
+        { 0.0f, -199.9f, 269.3f, -524.2f },
+        { 0.0f, 281.5f, -226.0f, 145.8f, -304.7f },
+        { 0.0f, 42.4f, 179.8f, -123.0f, -19.5f, 103.6f },
+        { 0.0f, -20.3f, 54.7f, 63.6f, -63.4f, -0.1f, 50.4f },
+        { 0.0f, -61.5f, -22.4f, 7.2f, 25.4f, 11.0f, -26.4f, -5.1f },
+        { 0.0f, 11.2f, -21.0f, 9.6f, -19.8f, 16.1f, 7.7f, -12.9f, -0.2f },
+        { 0.0f, -20.1f, 12.9f, 12.6f, -6.7f, -8.1f, 8.0f, 2.9f, -7.9f, 6.0f },
+        { 0.0f, 2.4f, 0.2f, 4.4f, 4.8f, -6.5f, -1.1f, -3.4f, -0.8f, -2.3f, -7.9f },
+        { 0.0f, 0.3f, 1.2f, -0.8f, -2.5f, 0.9f, -0.6f, -2.7f, -0.9f, -1.3f, -2.0f, -1.2f },
+        { 0.0f, -0.4f, 0.3f, 2.4f, -2.6f, 0.6f, 0.3f, 0.0f, 0.0f, 0.3f, -0.9f, -0.4f,
+          0.8f } };
+
+    static private final float[][] DELTA_G = new float[][] {
+        { 0f },
+        { 8.0f, 10.6f },
+        { -15.1f, -7.8f, -0.8f },
+        { 0.4f, -2.6f, -1.2f, -6.5f },
+        { -2.5f, 2.8f, -7.0f, 6.2f, -3.8f },
+        { -2.8f, 0.7f, -3.2f, -1.1f, 0.1f, -0.8f },
+        { -0.7f, 0.4f, -0.3f, 2.3f, -2.1f, -0.6f, 1.4f },
+        { 0.2f, -0.1f, -0.3f, 1.1f, 0.6f, 0.5f, -0.4f, 0.6f },
+        { 0.1f, 0.3f, -0.4f, 0.3f, -0.3f, 0.2f, 0.4f, -0.7f, 0.4f },
+        { 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f },
+        { 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f },
+        { 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f },
+        { 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f } };
+
+    static private final float[][] DELTA_H = new float[][] {
+        { 0f },
+        { 0.0f, -20.9f },
+        { 0.0f, -23.2f, -14.6f },
+        { 0.0f, 5.0f, -7.0f, -0.6f },
+        { 0.0f, 2.2f, 1.6f, 5.8f, 0.1f },
+        { 0.0f, 0.0f, 1.7f, 2.1f, 4.8f, -1.1f },
+        { 0.0f, -0.6f, -1.9f, -0.4f, -0.5f, -0.3f, 0.7f },
+        { 0.0f, 0.6f, 0.4f, 0.2f, 0.3f, -0.8f, -0.2f, 0.1f },
+        { 0.0f, -0.2f, 0.1f, 0.3f, 0.4f, 0.1f, -0.2f, 0.4f, 0.4f },
+        { 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f },
+        { 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f },
+        { 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f },
+        { 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f } };
+
+    static private final long BASE_TIME =
+        new GregorianCalendar(2005, 1, 1).getTimeInMillis();
+
+    // The ratio between the Gauss-normalized associated Legendre functions and
+    // the Schmid quasi-normalized ones. Compute these once staticly since they
+    // don't depend on input variables at all.
+    static private final float[][] SCHMIDT_QUASI_NORM_FACTORS =
+        computeSchmidtQuasiNormFactors(G_COEFF.length);
+
+    /**
+     * Estimate the magnetic field at a given point and time.
+     *
+     * @param gdLatitudeDeg
+     *            Latitude in WGS84 geodetic coordinates -- positive is east.
+     * @param gdLongitudeDeg
+     *            Longitude in WGS84 geodetic coordinates -- positive is north.
+     * @param altitudeMeters
+     *            Altitude in WGS84 geodetic coordinates, in meters.
+     * @param timeMillis
+     *            Time at which to evaluate the declination, in milliseconds
+     *            since January 1, 1970. (approximate is fine -- the declination
+     *            changes very slowly).
+     */
+    public GeomagneticField(float gdLatitudeDeg,
+                            float gdLongitudeDeg,
+                            float altitudeMeters,
+                            long timeMillis) {
+        final int MAX_N = G_COEFF.length; // Maximum degree of the coefficients.
+
+        // We don't handle the north and south poles correctly -- pretend that
+        // we're not quite at them to avoid crashing.
+        gdLatitudeDeg = Math.min(90.0f - 1e-5f,
+                                 Math.max(-90.0f + 1e-5f, gdLatitudeDeg));
+        computeGeocentricCoordinates(gdLatitudeDeg,
+                                     gdLongitudeDeg,
+                                     altitudeMeters);
+
+        assert G_COEFF.length == H_COEFF.length;
+
+        // Note: LegendreTable computes associated Legendre functions for
+        // cos(theta).  We want the associated Legendre functions for
+        // sin(latitude), which is the same as cos(PI/2 - latitude), except the
+        // derivate will be negated.
+        LegendreTable legendre =
+            new LegendreTable(MAX_N - 1,
+                              (float) (Math.PI / 2.0 - mGcLatitudeRad));
+
+        // Compute a table of (EARTH_REFERENCE_RADIUS_KM / radius)^n for i in
+        // 0..MAX_N-2 (this is much faster than calling Math.pow MAX_N+1 times).
+        float[] relativeRadiusPower = new float[MAX_N + 2];
+        relativeRadiusPower[0] = 1.0f;
+        relativeRadiusPower[1] = EARTH_REFERENCE_RADIUS_KM / mGcRadiusKm;
+        for (int i = 2; i < relativeRadiusPower.length; ++i) {
+            relativeRadiusPower[i] = relativeRadiusPower[i - 1] *
+                relativeRadiusPower[1];
+        }
+
+        // Compute tables of sin(lon * m) and cos(lon * m) for m = 0..MAX_N --
+        // this is much faster than calling Math.sin and Math.com MAX_N+1 times.
+        float[] sinMLon = new float[MAX_N];
+        float[] cosMLon = new float[MAX_N];
+        sinMLon[0] = 0.0f;
+        cosMLon[0] = 1.0f;
+        sinMLon[1] = (float) Math.sin(mGcLongitudeRad);
+        cosMLon[1] = (float) Math.cos(mGcLongitudeRad);
+
+        for (int m = 2; m < MAX_N; ++m) {
+            // Standard expansions for sin((m-x)*theta + x*theta) and
+            // cos((m-x)*theta + x*theta).
+            int x = m >> 1;
+            sinMLon[m] = sinMLon[m-x] * cosMLon[x] + cosMLon[m-x] * sinMLon[x];
+            cosMLon[m] = cosMLon[m-x] * cosMLon[x] - sinMLon[m-x] * sinMLon[x];
+        }
+
+        float inverseCosLatitude = 1.0f / (float) Math.cos(mGcLatitudeRad);
+        float yearsSinceBase =
+            (timeMillis - BASE_TIME) / (365f * 24f * 60f * 60f * 1000f);
+
+        // We now compute the magnetic field strength given the geocentric
+        // location. The magnetic field is the derivative of the potential
+        // function defined by the model. See NOAA Technical Report: The US/UK
+        // World Magnetic Model for 2005-2010 for the derivation.
+        float gcX = 0.0f;  // Geocentric northwards component.
+        float gcY = 0.0f;  // Geocentric eastwards component.
+        float gcZ = 0.0f;  // Geocentric downwards component.
+
+        for (int n = 1; n < MAX_N; n++) {
+            for (int m = 0; m <= n; m++) {
+                // Adjust the coefficients for the current date.
+                float g = G_COEFF[n][m] + yearsSinceBase * DELTA_G[n][m];
+                float h = H_COEFF[n][m] + yearsSinceBase * DELTA_H[n][m];
+
+                // Negative derivative with respect to latitude, divided by
+                // radius.  This looks like the negation of the version in the
+                // NOAA Techincal report because that report used
+                // P_n^m(sin(theta)) and we use P_n^m(cos(90 - theta)), so the
+                // derivative with respect to theta is negated.
+                gcX += relativeRadiusPower[n+2]
+                    * (g * cosMLon[m] + h * sinMLon[m])
+                    * legendre.mPDeriv[n][m]
+                    * SCHMIDT_QUASI_NORM_FACTORS[n][m];
+
+                // Negative derivative with respect to longitude, divided by
+                // radius.
+                gcY += relativeRadiusPower[n+2] * m
+                    * (g * sinMLon[m] - h * cosMLon[m])
+                    * legendre.mP[n][m]
+                    * SCHMIDT_QUASI_NORM_FACTORS[n][m]
+                    * inverseCosLatitude;
+
+                // Negative derivative with respect to radius.
+                gcZ -= (n + 1) * relativeRadiusPower[n+2]
+                    * (g * cosMLon[m] + h * sinMLon[m])
+                    * legendre.mP[n][m]
+                    * SCHMIDT_QUASI_NORM_FACTORS[n][m];
+            }
+        }
+
+        // Convert back to geodetic coordinates.  This is basically just a
+        // rotation around the Y-axis by the difference in latitudes between the
+        // geocentric frame and the geodetic frame.
+        double latDiffRad = Math.toRadians(gdLatitudeDeg) - mGcLatitudeRad;
+        mX = (float) (gcX * Math.cos(latDiffRad)
+                      + gcZ * Math.sin(latDiffRad));
+        mY = gcY;
+        mZ = (float) (- gcX * Math.sin(latDiffRad)
+                      + gcZ * Math.cos(latDiffRad));
+    }
+
+    /**
+     * @return The X (northward) component of the magnetic field in nanoteslas.
+     */
+    public float getX() {
+        return mX;
+    }
+
+    /**
+     * @return The Y (eastward) component of the magnetic field in nanoteslas.
+     */
+    public float getY() {
+        return mY;
+    }
+
+    /**
+     * @return The Z (downward) component of the magnetic field in nanoteslas.
+     */
+    public float getZ() {
+        return mZ;
+    }
+
+    /**
+     * @return The declination of the horizontal component of the magnetic
+     *         field from true north, in degrees (i.e. positive means the
+     *         magnetic field is rotated east that much from true north).
+     */
+    public float getDeclination() {
+        return (float) Math.toDegrees(Math.atan2(mY, mX));
+    }
+
+    /**
+     * @return The inclination of the magnetic field in degrees -- positive
+     *         means the magnetic field is rotated downwards.
+     */
+    public float getInclination() {
+        return (float) Math.toDegrees(Math.atan2(mZ,
+                                                 getHorizontalStrength()));
+    }
+
+    /**
+     * @return  Horizontal component of the field strength in nonoteslas.
+     */
+    public float getHorizontalStrength() {
+        return (float) Math.sqrt(mX * mX + mY * mY);
+    }
+
+    /**
+     * @return  Total field strength in nanoteslas.
+     */
+    public float getFieldStrength() {
+        return (float) Math.sqrt(mX * mX + mY * mY + mZ * mZ);
+    }
+
+    /**
+     * @param gdLatitudeDeg
+     *            Latitude in WGS84 geodetic coordinates.
+     * @param gdLongitudeDeg
+     *            Longitude in WGS84 geodetic coordinates.
+     * @param altitudeMeters
+     *            Altitude above sea level in WGS84 geodetic coordinates.
+     * @return Geocentric latitude (i.e. angle between closest point on the
+     *         equator and this point, at the center of the earth.
+     */
+    private void computeGeocentricCoordinates(float gdLatitudeDeg,
+                                              float gdLongitudeDeg,
+                                              float altitudeMeters) {
+        float altitudeKm = altitudeMeters / 1000.0f;
+        float a2 = EARTH_SEMI_MAJOR_AXIS_KM * EARTH_SEMI_MAJOR_AXIS_KM;
+        float b2 = EARTH_SEMI_MINOR_AXIS_KM * EARTH_SEMI_MINOR_AXIS_KM;
+        double gdLatRad = Math.toRadians(gdLatitudeDeg);
+        float clat = (float) Math.cos(gdLatRad);
+        float slat = (float) Math.sin(gdLatRad);
+        float tlat = slat / clat;
+        float latRad =
+            (float) Math.sqrt(a2 * clat * clat + b2 * slat * slat);
+
+        mGcLatitudeRad = (float) Math.atan(tlat * (latRad * altitudeKm + b2)
+                                           / (latRad * altitudeKm + a2));
+
+        mGcLongitudeRad = (float) Math.toRadians(gdLongitudeDeg);
+
+        float radSq = altitudeKm * altitudeKm
+            + 2 * altitudeKm * (float) Math.sqrt(a2 * clat * clat +
+                                                 b2 * slat * slat)
+            + (a2 * a2 * clat * clat + b2 * b2 * slat * slat)
+            / (a2 * clat * clat + b2 * slat * slat);
+        mGcRadiusKm = (float) Math.sqrt(radSq);
+    }
+
+
+    /**
+     * Utility class to compute a table of Gauss-normalized associated Legendre
+     * functions P_n^m(cos(theta))
+     */
+    static private class LegendreTable {
+        // These are the Gauss-normalized associated Legendre functions -- that
+        // is, they are normal Legendre functions multiplied by
+        // (n-m)!/(2n-1)!! (where (2n-1)!! = 1*3*5*...*2n-1)
+        public final float[][] mP;
+
+        // Derivative of mP, with respect to theta.
+        public final float[][] mPDeriv;
+
+        /**
+         * @param maxN
+         *            The maximum n- and m-values to support
+         * @param thetaRad
+         *            Returned functions will be Gauss-normalized
+         *            P_n^m(cos(thetaRad)), with thetaRad in radians.
+         */
+        public LegendreTable(int maxN, float thetaRad) {
+            // Compute the table of Gauss-normalized associated Legendre
+            // functions using standard recursion relations. Also compute the
+            // table of derivatives using the derivative of the recursion
+            // relations.
+            float cos = (float) Math.cos(thetaRad);
+            float sin = (float) Math.sin(thetaRad);
+
+            mP = new float[maxN + 1][];
+            mPDeriv = new float[maxN + 1][];
+            mP[0] = new float[] { 1.0f };
+            mPDeriv[0] = new float[] { 0.0f };
+            for (int n = 1; n <= maxN; n++) {
+            	mP[n] = new float[n + 1];
+                mPDeriv[n] = new float[n + 1];
+                for (int m = 0; m <= n; m++) {
+                    if (n == m) {
+                        mP[n][m] = sin * mP[n - 1][m - 1];
+                        mPDeriv[n][m] = cos * mP[n - 1][m - 1]
+                            + sin * mPDeriv[n - 1][m - 1];
+                    } else if (n == 1 || m == n - 1) {
+                        mP[n][m] = cos * mP[n - 1][m];
+                        mPDeriv[n][m] = -sin * mP[n - 1][m]
+                            + cos * mPDeriv[n - 1][m];
+                    } else {
+                        assert n > 1 && m < n - 1;
+                        float k = ((n - 1) * (n - 1) - m * m)
+                            / (float) ((2 * n - 1) * (2 * n - 3));
+                        mP[n][m] = cos * mP[n - 1][m] - k * mP[n - 2][m];
+                        mPDeriv[n][m] = -sin * mP[n - 1][m]
+                            + cos * mPDeriv[n - 1][m] - k * mPDeriv[n - 2][m];
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Compute the ration between the Gauss-normalized associated Legendre
+     * functions and the Schmidt quasi-normalized version. This is equivalent to
+     * sqrt((m==0?1:2)*(n-m)!/(n+m!))*(2n-1)!!/(n-m)!
+     */
+    private static float[][] computeSchmidtQuasiNormFactors(int maxN) {
+        float[][] schmidtQuasiNorm = new float[maxN + 1][];
+        schmidtQuasiNorm[0] = new float[] { 1.0f };
+        for (int n = 1; n <= maxN; n++) {
+            schmidtQuasiNorm[n] = new float[n + 1];
+            schmidtQuasiNorm[n][0] =
+                schmidtQuasiNorm[n - 1][0] * (2 * n - 1) / (float) n;
+            for (int m = 1; m <= n; m++) {
+                schmidtQuasiNorm[n][m] = schmidtQuasiNorm[n][m - 1]
+                    * (float) Math.sqrt((n - m + 1) * (m == 1 ? 2 : 1)
+                                / (float) (n + m));
+            }
+        }
+        return schmidtQuasiNorm;
+    }
+}
\ No newline at end of file
diff --git a/core/java/android/hardware/ISensorService.aidl b/core/java/android/hardware/ISensorService.aidl
new file mode 100644
index 0000000..04af2ae
--- /dev/null
+++ b/core/java/android/hardware/ISensorService.aidl
@@ -0,0 +1,29 @@
+/* //device/java/android/android/hardware/ISensorService.aidl
+**
+** Copyright 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 android.hardware;
+
+import android.os.ParcelFileDescriptor;
+
+/**
+ * {@hide}
+ */
+interface ISensorService
+{
+    ParcelFileDescriptor getDataChanel();
+    boolean enableSensor(IBinder listener, String name, int sensor, int enable);
+}
diff --git a/core/java/android/hardware/Sensor.java b/core/java/android/hardware/Sensor.java
new file mode 100644
index 0000000..0ce2f7b
--- /dev/null
+++ b/core/java/android/hardware/Sensor.java
@@ -0,0 +1,146 @@
+/*
+ * 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 android.hardware;
+
+/** 
+ * Class representing a sensor. Use {@link SensorManager#getSensorList}
+ * to get the list of available Sensors.
+ */
+public class Sensor {
+
+    /** 
+     * A constant describing an accelerometer sensor type.
+     * See {@link android.hardware.SensorEvent SensorEvent}
+     * for more details.
+     */
+    public static final int TYPE_ACCELEROMETER  = 1;
+
+    /** 
+     * A constant describing a magnetic field sensor type.
+     * See {@link android.hardware.SensorEvent SensorEvent}
+     * for more details.
+     */
+    public static final int TYPE_MAGNETIC_FIELD = 2;
+    
+    /** 
+     * A constant describing an orientation sensor type.
+     * See {@link android.hardware.SensorEvent SensorEvent}
+     * for more details.
+     */
+    public static final int TYPE_ORIENTATION    = 3;
+
+    /** A constant describing a gyroscope sensor type */
+    public static final int TYPE_GYROSCOPE      = 4;
+    /** A constant describing a light sensor type */
+    public static final int TYPE_LIGHT          = 5;
+    /** A constant describing a pressure sensor type */
+    public static final int TYPE_PRESSURE       = 6;
+    /** A constant describing a temperature sensor type */
+    public static final int TYPE_TEMPERATURE    = 7;
+    /** A constant describing a proximity sensor type */
+    public static final int TYPE_PROXIMITY      = 8;
+
+    
+    /** 
+     * A constant describing all sensor types.
+     */
+    public static final int TYPE_ALL             = -1;
+
+    /* Some of these fields are set only by the native bindings in 
+     * SensorManager.
+     */
+    private String  mName;
+    private String  mVendor;
+    private int     mVersion;
+    private int     mHandle;
+    private int     mType;
+    private float   mMaxRange;
+    private float   mResolution;
+    private float   mPower;
+    private int     mLegacyType;
+    
+    
+    Sensor() {
+    }
+
+    /**
+     * @return name string of the sensor.
+     */
+    public String getName() {
+        return mName;
+    }
+
+    /**
+     * @return vendor string of this sensor.
+     */
+    public String getVendor() {
+        return mVendor;
+    }
+    
+    /**
+     * @return generic type of this sensor.
+     */
+    public int getType() {
+        return mType;
+    }
+    
+    /**
+     * @return version of the sensor's module.
+     */
+    public int getVersion() {
+        return mVersion;
+    }
+    
+    /**
+     * @return maximum range of the sensor in the sensor's unit.
+     */
+    public float getMaximumRange() {
+        return mMaxRange;
+    }
+    
+    /**
+     * @return resolution of the sensor in the sensor's unit.
+     */
+    public float getResolution() {
+        return mResolution;
+    }
+    
+    /**
+     * @return the power in mA used by this sensor while in use
+     */
+    public float getPower() {
+        return mPower;
+    }
+    
+    int getHandle() {
+        return mHandle;
+    }
+    
+    void setRange(float max, float res) {
+        mMaxRange = max;
+        mResolution = res;
+    }
+    
+    void setLegacyType(int legacyType) {
+        mLegacyType = legacyType;
+    }
+
+    int getLegacyType() {
+        return mLegacyType;
+    }
+}
diff --git a/core/java/android/hardware/SensorEvent.java b/core/java/android/hardware/SensorEvent.java
new file mode 100644
index 0000000..cf939c5
--- /dev/null
+++ b/core/java/android/hardware/SensorEvent.java
@@ -0,0 +1,146 @@
+/*
+ * 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 android.hardware;
+
+/**
+ * This class represents a sensor event and holds informations such as the
+ * sensor type (eg: accelerometer, orientation, etc...), the time-stamp, 
+ * accuracy and of course the sensor's {@link SensorEvent#values data}.
+ *
+ * <p><u>Definition of the coordinate system used by the SensorEvent API.</u><p>
+ * 
+ * <pre>
+ * The coordinate space is defined relative to the screen of the phone 
+ * in its default orientation. The axes are not swapped when the device's
+ * screen orientation changes.
+ * 
+ * The OpenGL ES coordinate system is used. The origin is in the
+ * lower-left corner  with respect to the screen, with the X axis horizontal
+ * and pointing  right, the Y axis vertical and pointing up and the Z axis
+ * pointing outside the front face of the screen. In this system, coordinates
+ * behind the screen have negative Z values.
+ * 
+ * <b>Note:</b> This coordinate system is different from the one used in the
+ * Android 2D APIs where the origin is in the top-left corner. 
+ *
+ *   x<0         x>0
+ *                ^
+ *                |
+ *    +-----------+-->  y>0
+ *    |           |
+ *    |           |
+ *    |           |
+ *    |           |   / z<0
+ *    |           |  /
+ *    |           | /
+ *    O-----------+/
+ *    |[]  [ ]  []/
+ *    +----------/+     y<0
+ *              /
+ *             /
+ *           |/ z>0 (toward the sky)
+ *
+ *    O: Origin (x=0,y=0,z=0)
+ * </pre>
+ */
+
+public class SensorEvent {
+    /**
+     * The length and contents of the values array vary depending on which
+     * sensor type is being monitored (see also {@link SensorEvent} for a 
+     * definition of the coordinate system used):
+     * 
+     * <p>{@link android.hardware.Sensor#TYPE_ORIENTATION Sensor.TYPE_ORIENTATION}:<p>
+     *  All values are angles in degrees.
+     * 
+     * <p>values[0]: Azimuth, angle between the magnetic north direction and
+     * the Y axis, around the Z axis (0 to 359). 
+     * 0=North, 90=East, 180=South, 270=West
+     * 
+     * <p>values[1]: Pitch, rotation around X axis (-180 to 180), 
+     * with positive values when the z-axis moves <b>toward</b> the y-axis.
+     *
+     * <p>values[2]: Roll, rotation around Y axis (-90 to 90), with 
+     * positive values  when the x-axis moves <b>away</b> from the z-axis.
+     * 
+     * <p><b>Note:</b> This definition is different from <b>yaw, pitch and 
+     * roll</b> used in aviation where the X axis is along the long side of
+     * the plane (tail to nose).
+     * 
+     * <p><b>Note:</b> It is preferable to use 
+     * {@link android.hardware.SensorManager#getRotationMatrix 
+     *      getRotationMatrix()} in conjunction with
+     * {@link android.hardware.SensorManager#remapCoordinateSystem 
+     *      remapCoordinateSystem()} and
+     * {@link android.hardware.SensorManager#getOrientation getOrientation()}
+     * to compute these values; while it may be more expensive, it is usually 
+     * more accurate.
+     *
+     * <p>{@link android.hardware.Sensor#TYPE_ACCELEROMETER Sensor.TYPE_ACCELEROMETER}:<p>
+     *  All values are in SI units (m/s^2) and measure the acceleration applied
+     *  to the phone minus the force of gravity.
+     *  
+     *  <p>values[0]: Acceleration minus Gx on the x-axis 
+     *  <p>values[1]: Acceleration minus Gy on the y-axis 
+     *  <p>values[2]: Acceleration minus Gz on the z-axis
+     *  
+     *  <p><u>Examples</u>:
+     *    <li>When the device lies flat on a table and is pushed on its left 
+     *    side toward the right, the x acceleration value is positive.</li>
+     *    
+     *    <li>When the device lies flat on a table, the acceleration value is 
+     *    +9.81, which correspond to the acceleration of the device (0 m/s^2) 
+     *    minus the force of gravity (-9.81 m/s^2).</li>
+     *    
+     *    <li>When the device lies flat on a table and is pushed toward the sky
+     *    with an acceleration of A m/s^2, the acceleration value is equal to 
+     *    A+9.81 which correspond to the acceleration of the 
+     *    device (+A m/s^2) minus the force of gravity (-9.81 m/s^2).</li>
+     * 
+     * 
+     * <p>{@link android.hardware.Sensor#TYPE_MAGNETIC_FIELD Sensor.TYPE_MAGNETIC_FIELD}:<p>
+     *  All values are in micro-Tesla (uT) and measure the ambient magnetic
+     *  field in the X, Y and Z axis.
+     *  
+     */    
+    public final float[] values;
+
+    /**
+     * The sensor that generated this event.
+     * See {@link android.hardware.SensorManager SensorManager}
+     * for details.
+     */
+    public Sensor sensor;
+    
+    /**
+     * The accuracy of this event.
+     * See {@link android.hardware.SensorManager SensorManager}
+     * for details.
+     */
+    public int accuracy;
+    
+    
+    /**
+     * The time in nanosecond at which the event happened
+     */
+    public long timestamp;
+
+    
+    SensorEvent(int size) {
+        values = new float[size];
+    }
+}
diff --git a/core/java/android/hardware/SensorEventListener.java b/core/java/android/hardware/SensorEventListener.java
new file mode 100644
index 0000000..716d0d4
--- /dev/null
+++ b/core/java/android/hardware/SensorEventListener.java
@@ -0,0 +1,49 @@
+/*
+ * 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 android.hardware;
+
+/**
+ * Used for receiving notifications from the SensorManager when
+ * sensor values have changed.
+ */
+public interface SensorEventListener {
+
+    /**
+     * Called when sensor values have changed.
+     * <p>See {@link android.hardware.SensorManager SensorManager}
+     * for details on possible sensor types.
+     * <p>See also {@link android.hardware.SensorEvent SensorEvent}.
+     * 
+     * <p><b>NOTE:</b> The application doesn't own the
+     * {@link android.hardware.SensorEvent event}
+     * object passed as a parameter and therefore cannot hold on o it.
+     * The object may be part of an internal pool and may be reused by
+     * the framework.
+     *
+     * @param event the {@link android.hardware.SensorEvent SensorEvent}. 
+     */
+    public void onSensorChanged(SensorEvent event);
+
+    /**
+     * Called when the accuracy of a sensor has changed.
+     * <p>See {@link android.hardware.SensorManager SensorManager}
+     * for details.
+     *
+     * @param accuracy The new accuracy of this sensor
+     */
+    public void onAccuracyChanged(Sensor sensor, int accuracy);    
+}
diff --git a/core/java/android/hardware/SensorListener.java b/core/java/android/hardware/SensorListener.java
new file mode 100644
index 0000000..cfa184b
--- /dev/null
+++ b/core/java/android/hardware/SensorListener.java
@@ -0,0 +1,102 @@
+/*
+ * 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 android.hardware;
+
+/**
+ * Used for receiving notifications from the SensorManager when
+ * sensor values have changed.
+ * 
+ * This interface is deprecated, use 
+ * {@link android.hardware.SensorEventListener SensorEventListener} instead.
+ * 
+ */
+@Deprecated
+public interface SensorListener {
+
+    /**
+     * <p>Called when sensor values have changed.
+     * The length and contents of the values array vary
+     * depending on which sensor is being monitored.
+     * See {@link android.hardware.SensorManager SensorManager}
+     * for details on possible sensor types.
+     *
+     * <p><u>Definition of the coordinate system used below.</u><p>
+     * <p>The X axis refers to the screen's horizontal axis
+     * (the small edge in portrait mode, the long edge in landscape mode) and
+     * points to the right. 
+     * <p>The Y axis refers to the screen's vertical axis and points towards
+     * the top of the screen (the origin is in the lower-left corner).
+     * <p>The Z axis points toward the sky when the device is lying on its back
+     * on a table.
+     * <p> <b>IMPORTANT NOTE:</b> The axis <b><u>are swapped</u></b> when the
+     * device's screen orientation changes. To access the unswapped values,
+     * use indices 3, 4 and 5 in values[].
+     * 
+     * <p>{@link android.hardware.SensorManager#SENSOR_ORIENTATION SENSOR_ORIENTATION},
+     * {@link android.hardware.SensorManager#SENSOR_ORIENTATION_RAW SENSOR_ORIENTATION_RAW}:<p>
+     *  All values are angles in degrees.
+     * 
+     * <p>values[0]: Azimuth, rotation around the Z axis (0<=azimuth<360).
+     * 0 = North, 90 = East, 180 = South, 270 = West
+     * 
+     * <p>values[1]: Pitch, rotation around X axis (-180<=pitch<=180), with positive
+     * values when the z-axis moves toward the y-axis.
+     *
+     * <p>values[2]: Roll, rotation around Y axis (-90<=roll<=90), with positive values 
+     * when the z-axis moves toward the x-axis.
+     *
+     * <p>Note that this definition of yaw, pitch and roll is different from the
+     * traditional definition used in aviation where the X axis is along the long
+     * side of the plane (tail to nose).
+     *
+     * <p>{@link android.hardware.SensorManager#SENSOR_ACCELEROMETER SENSOR_ACCELEROMETER}:<p>
+     *  All values are in SI units (m/s^2) and measure contact forces.
+     *  
+     *  <p>values[0]: force applied by the device on the x-axis 
+     *  <p>values[1]: force applied by the device on the y-axis 
+     *  <p>values[2]: force applied by the device on the z-axis
+     *  
+     *  <p><u>Examples</u>:
+     *    <li>When the device is pushed on its left side toward the right, the
+     *    x acceleration value is negative (the device applies a reaction force
+     *    to the push toward the left)</li>
+     *    
+     *    <li>When the device lies flat on a table, the acceleration value is 
+     *    {@link android.hardware.SensorManager#STANDARD_GRAVITY -STANDARD_GRAVITY},
+     *    which correspond to the force the device applies on the table in reaction
+     *    to gravity.</li>
+     *
+     * <p>{@link android.hardware.SensorManager#SENSOR_MAGNETIC_FIELD SENSOR_MAGNETIC_FIELD}:<p>
+     *  All values are in micro-Tesla (uT) and measure the ambient magnetic
+     *  field in the X, Y and -Z axis.
+     *  <p><b><u>Note:</u></b> the magnetic field's Z axis is inverted.
+     *  
+     * @param sensor The ID of the sensor being monitored
+     * @param values The new values for the sensor.
+     */
+    public void onSensorChanged(int sensor, float[] values);
+
+    /**
+     * Called when the accuracy of a sensor has changed.
+     * See {@link android.hardware.SensorManager SensorManager}
+     * for details.
+     *
+     * @param sensor The ID of the sensor being monitored
+     * @param accuracy The new accuracy of this sensor.
+     */
+    public void onAccuracyChanged(int sensor, int accuracy);    
+}
diff --git a/core/java/android/hardware/SensorManager.java b/core/java/android/hardware/SensorManager.java
new file mode 100644
index 0000000..e232c2c
--- /dev/null
+++ b/core/java/android/hardware/SensorManager.java
@@ -0,0 +1,1462 @@
+/*
+ * 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 android.hardware;
+
+import android.content.Context;
+import android.os.Binder;
+import android.os.Looper;
+import android.os.ParcelFileDescriptor;
+import android.os.Process;
+import android.os.RemoteException;
+import android.os.Handler;
+import android.os.Message;
+import android.os.ServiceManager;
+import android.util.Log;
+import android.util.SparseArray;
+import android.view.IRotationWatcher;
+import android.view.IWindowManager;
+import android.view.Surface;
+
+import java.io.FileDescriptor;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+
+/**
+ * Class that lets you access the device's sensors. Get an instance of this
+ * class by calling {@link android.content.Context#getSystemService(java.lang.String)
+ * Context.getSystemService()} with an argument of {@link android.content.Context#SENSOR_SERVICE}.
+ */
+public class SensorManager extends IRotationWatcher.Stub
+{
+    private static final String TAG = "SensorManager";
+    private static final float[] mTempMatrix = new float[16];
+
+    /* NOTE: sensor IDs must be a power of 2 */
+
+    /**
+     * A constant describing an orientation sensor.
+     * See {@link android.hardware.SensorListener SensorListener} for more details.
+     * @deprecated use {@link android.hardware.Sensor Sensor} instead.
+     */
+    @Deprecated
+    public static final int SENSOR_ORIENTATION = 1 << 0;
+
+    /**
+     * A constant describing an accelerometer.
+     * See {@link android.hardware.SensorListener SensorListener} for more details.
+     * @deprecated use {@link android.hardware.Sensor Sensor} instead.
+     */
+    @Deprecated
+    public static final int SENSOR_ACCELEROMETER = 1 << 1;
+
+    /**
+     * A constant describing a temperature sensor
+     * See {@link android.hardware.SensorListener SensorListener} for more details.
+     * @deprecated use {@link android.hardware.Sensor Sensor} instead.
+     */
+    @Deprecated
+    public static final int SENSOR_TEMPERATURE = 1 << 2;
+
+    /**
+     * A constant describing a magnetic sensor
+     * See {@link android.hardware.SensorListener SensorListener} for more details.
+     * @deprecated use {@link android.hardware.Sensor Sensor} instead.
+     */
+    @Deprecated
+    public static final int SENSOR_MAGNETIC_FIELD = 1 << 3;
+
+    /**
+     * A constant describing an ambient light sensor
+     * See {@link android.hardware.SensorListener SensorListener} for more details.
+     * @deprecated use {@link android.hardware.Sensor Sensor} instead.
+     */
+    @Deprecated
+    public static final int SENSOR_LIGHT = 1 << 4;
+
+    /**
+     * A constant describing a proximity sensor
+     * See {@link android.hardware.SensorListener SensorListener} for more details.
+     * @deprecated use {@link android.hardware.Sensor Sensor} instead.
+     */
+    @Deprecated
+    public static final int SENSOR_PROXIMITY = 1 << 5;
+
+    /**
+     * A constant describing a Tricorder
+     * See {@link android.hardware.SensorListener SensorListener} for more details.
+     * @deprecated use {@link android.hardware.Sensor Sensor} instead.
+     */
+    @Deprecated
+    public static final int SENSOR_TRICORDER = 1 << 6;
+
+    /**
+     * A constant describing an orientation sensor.
+     * See {@link android.hardware.SensorListener SensorListener} for more details.
+     * @deprecated use {@link android.hardware.Sensor Sensor} instead.
+     */
+    @Deprecated
+    public static final int SENSOR_ORIENTATION_RAW = 1 << 7;
+
+    /** A constant that includes all sensors */
+    @Deprecated
+    public static final int SENSOR_ALL = 0x7F;
+
+    /** Smallest sensor ID */
+    @Deprecated
+    public static final int SENSOR_MIN = SENSOR_ORIENTATION;
+
+    /** Largest sensor ID */
+    @Deprecated
+    public static final int SENSOR_MAX = ((SENSOR_ALL + 1)>>1);
+
+
+    /** Index of the X value in the array returned by
+     * {@link android.hardware.SensorListener#onSensorChanged} */
+    @Deprecated
+    public static final int DATA_X = 0;
+    /** Index of the Y value in the array returned by
+     * {@link android.hardware.SensorListener#onSensorChanged} */
+    @Deprecated
+    public static final int DATA_Y = 1;
+    /** Index of the Z value in the array returned by
+     * {@link android.hardware.SensorListener#onSensorChanged} */
+    @Deprecated
+    public static final int DATA_Z = 2;
+
+    /** Offset to the untransformed values in the array returned by
+     * {@link android.hardware.SensorListener#onSensorChanged} */
+    @Deprecated
+    public static final int RAW_DATA_INDEX = 3;
+
+    /** Index of the untransformed X value in the array returned by
+     * {@link android.hardware.SensorListener#onSensorChanged} */
+    @Deprecated
+    public static final int RAW_DATA_X = 3;
+    /** Index of the untransformed Y value in the array returned by
+     * {@link android.hardware.SensorListener#onSensorChanged} */
+    @Deprecated
+    public static final int RAW_DATA_Y = 4;
+    /** Index of the untransformed Z value in the array returned by
+     * {@link android.hardware.SensorListener#onSensorChanged} */
+    @Deprecated
+    public static final int RAW_DATA_Z = 5;
+
+
+    /** Standard gravity (g) on Earth. This value is equivalent to 1G */
+    public static final float STANDARD_GRAVITY = 9.80665f;
+
+    /** values returned by the accelerometer in various locations in the universe.
+     * all values are in SI units (m/s^2) */
+    public static final float GRAVITY_SUN             = 275.0f;
+    public static final float GRAVITY_MERCURY         = 3.70f;
+    public static final float GRAVITY_VENUS           = 8.87f;
+    public static final float GRAVITY_EARTH           = 9.80665f;
+    public static final float GRAVITY_MOON            = 1.6f;
+    public static final float GRAVITY_MARS            = 3.71f;
+    public static final float GRAVITY_JUPITER         = 23.12f;
+    public static final float GRAVITY_SATURN          = 8.96f;
+    public static final float GRAVITY_URANUS          = 8.69f;
+    public static final float GRAVITY_NEPTUNE         = 11.0f;
+    public static final float GRAVITY_PLUTO           = 0.6f;
+    public static final float GRAVITY_DEATH_STAR_I    = 0.000000353036145f;
+    public static final float GRAVITY_THE_ISLAND      = 4.815162342f;
+
+
+    /** Maximum magnetic field on Earth's surface */
+    public static final float MAGNETIC_FIELD_EARTH_MAX = 60.0f;
+
+    /** Minimum magnetic field on Earth's surface */
+    public static final float MAGNETIC_FIELD_EARTH_MIN = 30.0f;
+
+
+    /** Various luminance values during the day (lux) */
+    public static final float LIGHT_SUNLIGHT_MAX = 120000.0f;
+    public static final float LIGHT_SUNLIGHT     = 110000.0f;
+    public static final float LIGHT_SHADE        = 20000.0f;
+    public static final float LIGHT_OVERCAST     = 10000.0f;
+    public static final float LIGHT_SUNRISE      = 400.0f;
+    public static final float LIGHT_CLOUDY       = 100.0f;
+    /** Various luminance values during the night (lux) */
+    public static final float LIGHT_FULLMOON     = 0.25f;
+    public static final float LIGHT_NO_MOON      = 0.001f;
+
+    /** get sensor data as fast as possible */
+    public static final int SENSOR_DELAY_FASTEST = 0;
+    /** rate suitable for games */
+    public static final int SENSOR_DELAY_GAME = 1;
+    /** rate suitable for the user interface  */
+    public static final int SENSOR_DELAY_UI = 2;
+    /** rate (default) suitable for screen orientation changes */
+    public static final int SENSOR_DELAY_NORMAL = 3;
+
+
+    /** The values returned by this sensor cannot be trusted, calibration
+     * is needed or the environment doesn't allow readings */
+    public static final int SENSOR_STATUS_UNRELIABLE = 0;
+
+    /** This sensor is reporting data with low accuracy, calibration with the
+     * environment is needed */
+    public static final int SENSOR_STATUS_ACCURACY_LOW = 1;
+
+    /** This sensor is reporting data with an average level of accuracy,
+     * calibration with the environment may improve the readings */
+    public static final int SENSOR_STATUS_ACCURACY_MEDIUM = 2;
+
+    /** This sensor is reporting data with maximum accuracy */
+    public static final int SENSOR_STATUS_ACCURACY_HIGH = 3;
+
+    /** see {@link #remapCoordinateSystem} */
+    public static final int AXIS_X = 1;
+    /** see {@link #remapCoordinateSystem} */
+    public static final int AXIS_Y = 2;
+    /** see {@link #remapCoordinateSystem} */
+    public static final int AXIS_Z = 3;
+    /** see {@link #remapCoordinateSystem} */
+    public static final int AXIS_MINUS_X = AXIS_X | 0x80;
+    /** see {@link #remapCoordinateSystem} */
+    public static final int AXIS_MINUS_Y = AXIS_Y | 0x80;
+    /** see {@link #remapCoordinateSystem} */
+    public static final int AXIS_MINUS_Z = AXIS_Z | 0x80;
+
+    /*-----------------------------------------------------------------------*/
+
+    private ISensorService mSensorService;
+    Looper mMainLooper;
+    @SuppressWarnings("deprecation")
+    private HashMap<SensorListener, LegacyListener> mLegacyListenersMap =
+        new HashMap<SensorListener, LegacyListener>();
+
+    /*-----------------------------------------------------------------------*/
+
+    private static final int SENSOR_DISABLE = -1;
+    private static boolean sSensorModuleInitialized = false;
+    private static ArrayList<Sensor> sFullSensorsList = new ArrayList<Sensor>();
+    private static SparseArray<List<Sensor>> sSensorListByType = new SparseArray<List<Sensor>>();
+    private static IWindowManager sWindowManager;
+    private static int sRotation = Surface.ROTATION_0;
+    /* The thread and the sensor list are global to the process
+     * but the actual thread is spawned on demand */
+    private static SensorThread sSensorThread;
+
+    // Used within this module from outside SensorManager, don't make private
+    static SparseArray<Sensor> sHandleToSensor = new SparseArray<Sensor>();
+    static final ArrayList<ListenerDelegate> sListeners =
+        new ArrayList<ListenerDelegate>();
+
+    /*-----------------------------------------------------------------------*/
+
+    static private class SensorThread {
+
+        Thread mThread;
+
+        SensorThread() {
+            // this gets to the sensor module. We can have only one per process.
+            sensors_data_init();
+        }
+
+        @Override
+        protected void finalize() {
+            sensors_data_uninit();
+        }
+
+        // must be called with sListeners lock
+        void startLocked(ISensorService service) {
+            try {
+                if (mThread == null) {
+                    ParcelFileDescriptor fd = service.getDataChanel();
+                    mThread = new Thread(new SensorThreadRunnable(fd),
+                            SensorThread.class.getName());
+                    mThread.start();
+                }
+            } catch (RemoteException e) {
+                Log.e(TAG, "RemoteException in startLocked: ", e);
+            }
+        }
+
+        private class SensorThreadRunnable implements Runnable {
+            private ParcelFileDescriptor mSensorDataFd;
+            SensorThreadRunnable(ParcelFileDescriptor fd) {
+                mSensorDataFd = fd;
+            }
+            public void run() {
+                //Log.d(TAG, "entering main sensor thread");
+                final float[] values = new float[3];
+                final int[] status = new int[1];
+                final long timestamp[] = new long[1];
+                Process.setThreadPriority(Process.THREAD_PRIORITY_DISPLAY);
+
+                if (mSensorDataFd == null) {
+                    Log.e(TAG, "mSensorDataFd == NULL, exiting");
+                    return;
+                }
+                // this thread is guaranteed to be unique
+                sensors_data_open(mSensorDataFd.getFileDescriptor());
+                try {
+                    mSensorDataFd.close();
+                } catch (IOException e) {
+                    // *shrug*
+                    Log.e(TAG, "IOException: ", e);
+                }
+                mSensorDataFd = null;
+
+
+                while (true) {
+                    // wait for an event
+                    final int sensor = sensors_data_poll(values, status, timestamp);
+
+                    if (sensor == -1) {
+                        // we lost the connection to the event stream. this happens
+                        // when the last listener is removed.
+                        Log.d(TAG, "_sensors_data_poll() failed, we bail out.");
+                        break;
+                    }
+
+                    int accuracy = status[0];
+                    synchronized (sListeners) {
+                        if (sListeners.isEmpty()) {
+                            // we have no more listeners, terminate the thread
+                            sensors_data_close();
+                            mThread = null;
+                            break;
+                        }
+                        final Sensor sensorObject = sHandleToSensor.get(sensor);
+                        if (sensorObject != null) {
+                            // report the sensor event to all listeners that
+                            // care about it.
+                            final int size = sListeners.size();
+                            for (int i=0 ; i<size ; i++) {
+                                ListenerDelegate listener = sListeners.get(i);
+                                if (listener.hasSensor(sensorObject)) {
+                                    // this is asynchronous (okay to call
+                                    // with sListeners lock held).
+                                    listener.onSensorChangedLocked(sensorObject,
+                                            values, timestamp, accuracy);
+                                }
+                            }
+                        }
+                    }
+                }
+                //Log.d(TAG, "exiting main sensor thread");
+            }
+        }
+    }
+
+    /*-----------------------------------------------------------------------*/
+
+    private class ListenerDelegate extends Binder {
+        final SensorEventListener mSensorEventListener;
+        private final ArrayList<Sensor> mSensorList = new ArrayList<Sensor>();
+        private final Handler mHandler;
+        private SensorEvent mValuesPool;
+        public int mSensors;
+
+        ListenerDelegate(SensorEventListener listener, Sensor sensor, Handler handler) {
+            mSensorEventListener = listener;
+            Looper looper = (handler != null) ? handler.getLooper() : mMainLooper;
+            // currently we create one Handler instance per listener, but we could
+            // have one per looper (we'd need to pass the ListenerDelegate
+            // instance to handleMessage and keep track of them separately).
+            mHandler = new Handler(looper) {
+                @Override
+                public void handleMessage(Message msg) {
+                    SensorEvent t = (SensorEvent)msg.obj;
+                    if (t.accuracy >= 0) {
+                        mSensorEventListener.onAccuracyChanged(t.sensor, t.accuracy);
+                    }
+                    mSensorEventListener.onSensorChanged(t);
+                    returnToPool(t);
+                }
+            };
+            addSensor(sensor);
+        }
+
+        protected SensorEvent createSensorEvent() {
+            // maximal size for all legacy events is 3
+            return new SensorEvent(3);
+        }
+
+        protected SensorEvent getFromPool() {
+            SensorEvent t = null;
+            synchronized (this) {
+                // remove the array from the pool
+                t = mValuesPool;
+                mValuesPool = null;
+            }
+            if (t == null) {
+                // the pool was empty, we need a new one
+                t = createSensorEvent();
+            }
+            return t;
+        }
+
+        protected void returnToPool(SensorEvent t) {
+            synchronized (this) {
+                // put back the array into the pool
+                if (mValuesPool == null) {
+                    mValuesPool = t;
+                }
+            }
+        }
+
+        Object getListener() {
+            return mSensorEventListener;
+        }
+
+        int addSensor(Sensor sensor) {
+            mSensors |= 1<<sensor.getHandle();
+            mSensorList.add(sensor);
+            return mSensors;
+        }
+        int removeSensor(Sensor sensor) {
+            mSensors &= ~(1<<sensor.getHandle());
+            mSensorList.remove(sensor);
+            return mSensors;
+        }
+        boolean hasSensor(Sensor sensor) {
+            return ((mSensors & (1<<sensor.getHandle())) != 0);
+        }
+        List<Sensor> getSensors() {
+            return mSensorList;
+        }
+
+        void onSensorChangedLocked(Sensor sensor, float[] values, long[] timestamp, int accuracy) {
+            SensorEvent t = getFromPool();
+            final float[] v = t.values;
+            v[0] = values[0];
+            v[1] = values[1];
+            v[2] = values[2];
+            t.timestamp = timestamp[0];
+            t.accuracy = accuracy;
+            t.sensor = sensor;
+            Message msg = Message.obtain();
+            msg.what = 0;
+            msg.obj = t;
+            mHandler.sendMessage(msg);
+        }
+    }
+
+    /**
+     * {@hide}
+     */
+    public SensorManager(Looper mainLooper) {
+        mSensorService = ISensorService.Stub.asInterface(
+                ServiceManager.getService(Context.SENSOR_SERVICE));
+        mMainLooper = mainLooper;
+
+
+        synchronized(sListeners) {
+            if (!sSensorModuleInitialized) {
+                sSensorModuleInitialized = true;
+
+                nativeClassInit();
+
+                sWindowManager = IWindowManager.Stub.asInterface(
+                        ServiceManager.getService("window"));
+                if (sWindowManager != null) {
+                    // if it's null we're running in the system process
+                    // which won't get the rotated values
+                    try {
+                        sRotation = sWindowManager.watchRotation(this);
+                    } catch (RemoteException e) {
+                    }
+                }
+
+                // initialize the sensor list
+                sensors_module_init();
+                final ArrayList<Sensor> fullList = sFullSensorsList;
+                int i = 0;
+                do {
+                    Sensor sensor = new Sensor();
+                    i = sensors_module_get_next_sensor(sensor, i);
+
+                    if (i>=0) {
+                        Log.d(TAG, "found sensor: " + sensor.getName() +
+                                ", handle=" + sensor.getHandle());
+                        sensor.setLegacyType(getLegacySensorType(sensor.getType()));
+                        fullList.add(sensor);
+                        sHandleToSensor.append(sensor.getHandle(), sensor);
+                    }
+                } while (i>0);
+
+                sSensorThread = new SensorThread();
+            }
+        }
+    }
+
+    private int getLegacySensorType(int type) {
+        switch (type) {
+            case Sensor.TYPE_ACCELEROMETER:
+                return SENSOR_ACCELEROMETER;
+            case Sensor.TYPE_MAGNETIC_FIELD:
+                return SENSOR_MAGNETIC_FIELD;
+            case Sensor.TYPE_ORIENTATION:
+                return SENSOR_ORIENTATION_RAW;
+            case Sensor.TYPE_TEMPERATURE:
+                return SENSOR_TEMPERATURE;
+        }
+        return 0;
+    }
+
+    /** @return available sensors.
+     * @deprecated This method is deprecated, use
+     * {@link SensorManager#getSensorList(int)} instead
+     */
+    @Deprecated
+    public int getSensors() {
+        int result = 0;
+        final ArrayList<Sensor> fullList = sFullSensorsList;
+        for (Sensor i : fullList) {
+            switch (i.getType()) {
+                case Sensor.TYPE_ACCELEROMETER:
+                    result |= SensorManager.SENSOR_ACCELEROMETER;
+                    break;
+                case Sensor.TYPE_MAGNETIC_FIELD:
+                    result |= SensorManager.SENSOR_MAGNETIC_FIELD;
+                    break;
+                case Sensor.TYPE_ORIENTATION:
+                    result |= SensorManager.SENSOR_ORIENTATION |
+                                    SensorManager.SENSOR_ORIENTATION_RAW;
+                    break;
+            }
+        }
+        return result;
+    }
+
+    /**
+     * Use this method to get the list of available sensors of a certain
+     * type. Make multiple calls to get sensors of different types or use
+     * {@link android.hardware.Sensor#TYPE_ALL Sensor.TYPE_ALL} to get all
+     * the sensors.
+     *
+     * @param type of sensors requested
+     * @return a list of sensors matching the asked type.
+     */
+    public List<Sensor> getSensorList(int type) {
+        // cache the returned lists the first time
+        List<Sensor> list;
+        final ArrayList<Sensor> fullList = sFullSensorsList;
+        synchronized(fullList) {
+            list = sSensorListByType.get(type);
+            if (list == null) {
+                if (type == Sensor.TYPE_ALL) {
+                    list = fullList;
+                } else {
+                    list = new ArrayList<Sensor>();
+                    for (Sensor i : fullList) {
+                        if (i.getType() == type)
+                            list.add(i);
+                    }
+                }
+                list = Collections.unmodifiableList(list);
+                sSensorListByType.append(type, list);
+            }
+        }
+        return list;
+    }
+
+    /**
+     * Use this method to get the default sensor for a given type. Note that
+     * the returned sensor could be a composite sensor, and its data could be
+     * averaged or filtered. If you need to access the raw sensors use
+     * {@link SensorManager#getSensorList(int) getSensorList}.
+     *
+     *
+     * @param type of sensors requested
+     * @return the default sensors matching the asked type.
+     */
+    public Sensor getDefaultSensor(int type) {
+        // TODO: need to be smarter, for now, just return the 1st sensor
+        List<Sensor> l = getSensorList(type);
+        return l.isEmpty() ? null : l.get(0);
+    }
+
+
+    /**
+     * Registers a listener for given sensors.
+     * @deprecated This method is deprecated, use
+     * {@link SensorManager#registerListener(SensorEventListener, Sensor, int)}
+     * instead.
+     *
+     * @param listener sensor listener object
+     * @param sensors a bit masks of the sensors to register to
+     *
+     * @return true if the sensor is supported and successfully enabled
+     */
+    @Deprecated
+    public boolean registerListener(SensorListener listener, int sensors) {
+        return registerListener(listener, sensors, SENSOR_DELAY_NORMAL);
+    }
+
+    /**
+     * Registers a SensorListener for given sensors.
+     * @deprecated This method is deprecated, use
+     * {@link SensorManager#registerListener(SensorEventListener, Sensor, int)}
+     * instead.
+     *
+     * @param listener sensor listener object
+     * @param sensors a bit masks of the sensors to register to
+     * @param rate rate of events. This is only a hint to the system. events
+     * may be received faster or slower than the specified rate. Usually events
+     * are received faster.
+     *
+     * @return true if the sensor is supported and successfully enabled
+     */
+    @Deprecated
+    public boolean registerListener(SensorListener listener, int sensors, int rate) {
+        if (listener == null) {
+            return false;
+        }
+        boolean result = false;
+        result = registerLegacyListener(SENSOR_ACCELEROMETER, Sensor.TYPE_ACCELEROMETER,
+                listener, sensors, rate) || result;
+        result = registerLegacyListener(SENSOR_MAGNETIC_FIELD, Sensor.TYPE_MAGNETIC_FIELD,
+                listener, sensors, rate) || result;
+        result = registerLegacyListener(SENSOR_ORIENTATION_RAW, Sensor.TYPE_ORIENTATION,
+                listener, sensors, rate) || result;
+        result = registerLegacyListener(SENSOR_ORIENTATION, Sensor.TYPE_ORIENTATION,
+                listener, sensors, rate) || result;
+        result = registerLegacyListener(SENSOR_TEMPERATURE, Sensor.TYPE_TEMPERATURE,
+                listener, sensors, rate) || result;
+        return result;
+    }
+
+    @SuppressWarnings("deprecation")
+    private boolean registerLegacyListener(int legacyType, int type,
+            SensorListener listener, int sensors, int rate)
+    {
+        if (listener == null) {
+            return false;
+        }
+        boolean result = false;
+        // Are we activating this legacy sensor?
+        if ((sensors & legacyType) != 0) {
+            // if so, find a suitable Sensor
+            Sensor sensor = getDefaultSensor(type);
+            if (sensor != null) {
+                // If we don't already have one, create a LegacyListener
+                // to wrap this listener and process the events as
+                // they are expected by legacy apps.
+                LegacyListener legacyListener = null;
+                synchronized (mLegacyListenersMap) {
+                    legacyListener = mLegacyListenersMap.get(listener);
+                    if (legacyListener == null) {
+                        // we didn't find a LegacyListener for this client,
+                        // create one, and put it in our list.
+                        legacyListener = new LegacyListener(listener);
+                        mLegacyListenersMap.put(listener, legacyListener);
+                    }
+                }
+                // register this legacy sensor with this legacy listener
+                legacyListener.registerSensor(legacyType);
+                // and finally, register the legacy listener with the new apis
+                result = registerListener(legacyListener, sensor, rate);
+            }
+        }
+        return result;
+    }
+
+    /**
+     * Unregisters a listener for the sensors with which it is registered.
+     * @deprecated This method is deprecated, use
+     * {@link SensorManager#unregisterListener(SensorEventListener, Sensor)}
+     * instead.
+     *
+     * @param listener a SensorListener object
+     * @param sensors a bit masks of the sensors to unregister from
+     */
+    @Deprecated
+    public void unregisterListener(SensorListener listener, int sensors) {
+        unregisterLegacyListener(SENSOR_ACCELEROMETER, Sensor.TYPE_ACCELEROMETER,
+                listener, sensors);
+        unregisterLegacyListener(SENSOR_MAGNETIC_FIELD, Sensor.TYPE_MAGNETIC_FIELD,
+                listener, sensors);
+        unregisterLegacyListener(SENSOR_ORIENTATION_RAW, Sensor.TYPE_ORIENTATION,
+                listener, sensors);
+        unregisterLegacyListener(SENSOR_ORIENTATION, Sensor.TYPE_ORIENTATION,
+                listener, sensors);
+        unregisterLegacyListener(SENSOR_TEMPERATURE, Sensor.TYPE_TEMPERATURE,
+                listener, sensors);
+    }
+
+    @SuppressWarnings("deprecation")
+    private void unregisterLegacyListener(int legacyType, int type,
+            SensorListener listener, int sensors)
+    {
+        if (listener == null) {
+            return;
+        }
+        // do we know about this listener?
+        LegacyListener legacyListener = null;
+        synchronized (mLegacyListenersMap) {
+            legacyListener = mLegacyListenersMap.get(listener);
+        }
+        if (legacyListener != null) {
+            // Are we deactivating this legacy sensor?
+            if ((sensors & legacyType) != 0) {
+                // if so, find the corresponding Sensor
+                Sensor sensor = getDefaultSensor(type);
+                if (sensor != null) {
+                    // unregister this legacy sensor and if we don't
+                    // need the corresponding Sensor, unregister it too
+                    if (legacyListener.unregisterSensor(legacyType)) {
+                        // corresponding sensor not needed, unregister
+                        unregisterListener(legacyListener, sensor);
+                        // finally check if we still need the legacyListener
+                        // in our mapping, if not, get rid of it too.
+                        synchronized(sListeners) {
+                            boolean found = false;
+                            for (ListenerDelegate i : sListeners) {
+                                if (i.getListener() == legacyListener) {
+                                    found = true;
+                                    break;
+                                }
+                            }
+                            if (!found) {
+                                synchronized (mLegacyListenersMap) {
+                                    mLegacyListenersMap.remove(listener);
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Unregisters a listener for all sensors.
+     * @deprecated This method is deprecated, use
+     * {@link SensorManager#unregisterListener(SensorEventListener)}
+     * instead.
+     *
+     * @param listener a SensorListener object
+     */
+    @Deprecated
+    public void unregisterListener(SensorListener listener) {
+        unregisterListener(listener, SENSOR_ALL | SENSOR_ORIENTATION_RAW);
+    }
+
+    /**
+     * Unregisters a listener for the sensors with which it is registered.
+     *
+     * @param listener a SensorEventListener object
+     * @param sensor the sensor to unregister from
+     *
+     */
+    public void unregisterListener(SensorEventListener listener, Sensor sensor) {
+        unregisterListener((Object)listener, sensor);
+    }
+
+    /**
+     * Unregisters a listener for all sensors.
+     *
+     * @param listener a SensorListener object
+     *
+     */
+    public void unregisterListener(SensorEventListener listener) {
+        unregisterListener((Object)listener);
+    }
+
+
+    /**
+     * Registers a {@link android.hardware.SensorEventListener SensorEventListener}
+     * for the given sensor.
+     *
+     * @param listener A {@link android.hardware.SensorEventListener SensorEventListener} object.
+     * @param sensor The {@link android.hardware.Sensor Sensor} to register to.
+     * @param rate The rate {@link android.hardware.SensorEvent sensor events} are delivered at.
+     * This is only a hint to the system. Events may be received faster or
+     * slower than the specified rate. Usually events are received faster.
+     *
+     * @return true if the sensor is supported and successfully enabled.
+     *
+     */
+    public boolean registerListener(SensorEventListener listener, Sensor sensor, int rate) {
+        return registerListener(listener, sensor, rate, null);
+    }
+
+    /**
+     * Registers a {@link android.hardware.SensorEventListener SensorEventListener}
+     * for the given sensor.
+     *
+     * @param listener A {@link android.hardware.SensorEventListener SensorEventListener} object.
+     * @param sensor The {@link android.hardware.Sensor Sensor} to register to.
+     * @param rate The rate {@link android.hardware.SensorEvent sensor events} are delivered at.
+     * This is only a hint to the system. Events may be received faster or
+     * slower than the specified rate. Usually events are received faster.
+     * @param handler The {@link android.os.Handler Handler} the
+     * {@link android.hardware.SensorEvent sensor events} will be delivered to.
+     *
+     * @return true if the sensor is supported and successfully enabled.
+     *
+     */
+    public boolean registerListener(SensorEventListener listener, Sensor sensor, int rate,
+            Handler handler) {
+        if (listener == null || sensor == null) {
+            return false;
+        }
+        boolean result;
+        int delay = -1;
+        switch (rate) {
+            case SENSOR_DELAY_FASTEST:
+                delay = 0;
+                break;
+            case SENSOR_DELAY_GAME:
+                delay = 20;
+                break;
+            case SENSOR_DELAY_UI:
+                delay = 60;
+                break;
+            case SENSOR_DELAY_NORMAL:
+                delay = 200;
+                break;
+            default:
+                return false;
+        }
+
+        try {
+            synchronized (sListeners) {
+                ListenerDelegate l = null;
+                for (ListenerDelegate i : sListeners) {
+                    if (i.getListener() == listener) {
+                        l = i;
+                        break;
+                    }
+                }
+
+                String name = sensor.getName();
+                int handle = sensor.getHandle();
+                if (l == null) {
+                    l = new ListenerDelegate(listener, sensor, handler);
+                    result = mSensorService.enableSensor(l, name, handle, delay);
+                    if (result) {
+                        sListeners.add(l);
+                        sListeners.notify();
+                    }
+                    if (!sListeners.isEmpty()) {
+                        sSensorThread.startLocked(mSensorService);
+                    }
+                } else {
+                    result = mSensorService.enableSensor(l, name, handle, delay);
+                    if (result) {
+                        l.addSensor(sensor);
+                    }
+                }
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "RemoteException in registerListener: ", e);
+            result = false;
+        }
+        return result;
+    }
+
+    private void unregisterListener(Object listener, Sensor sensor) {
+        if (listener == null || sensor == null) {
+            return;
+        }
+        try {
+            synchronized (sListeners) {
+                final int size = sListeners.size();
+                for (int i=0 ; i<size ; i++) {
+                    ListenerDelegate l = sListeners.get(i);
+                    if (l.getListener() == listener) {
+                        // disable these sensors
+                        String name = sensor.getName();
+                        int handle = sensor.getHandle();
+                        mSensorService.enableSensor(l, name, handle, SENSOR_DISABLE);
+                        // if we have no more sensors enabled on this listener,
+                        // take it off the list.
+                        if (l.removeSensor(sensor) == 0) {
+                            sListeners.remove(i);
+                        }
+                        break;
+                    }
+                }
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "RemoteException in unregisterListener: ", e);
+        }
+    }
+
+    private void unregisterListener(Object listener) {
+        if (listener == null) {
+            return;
+        }
+        try {
+            synchronized (sListeners) {
+                final int size = sListeners.size();
+                for (int i=0 ; i<size ; i++) {
+                    ListenerDelegate l = sListeners.get(i);
+                    if (l.getListener() == listener) {
+                        // disable all sensors for this listener
+                        for (Sensor sensor : l.getSensors()) {
+                            String name = sensor.getName();
+                            int handle = sensor.getHandle();
+                            mSensorService.enableSensor(l, name, handle, SENSOR_DISABLE);
+                        }
+                        sListeners.remove(i);
+                        break;
+                    }
+                }
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "RemoteException in unregisterListener: ", e);
+        }
+    }
+
+    /**
+     * Computes the inclination matrix <b>I</b> as well as the rotation
+     * matrix <b>R</b> transforming a vector from the
+     * device coordinate system to the world's coordinate system which is
+     * defined as a direct orthonormal basis, where:
+     * 
+     * <li>X is defined as the vector product <b>Y.Z</b> (It is tangential to
+     * the ground at the device's current location and roughly points East).</li>
+     * <li>Y is tangential to the ground at the device's current location and
+     * points towards the magnetic North Pole.</li>
+     * <li>Z points towards the sky and is perpendicular to the ground.</li>
+     * <p>
+     * <hr>
+     * <p>By definition:
+     * <p>[0 0 g] = <b>R</b> * <b>gravity</b> (g = magnitude of gravity)
+     * <p>[0 m 0] = <b>I</b> * <b>R</b> * <b>geomagnetic</b>
+     * (m = magnitude of geomagnetic field)
+     * <p><b>R</b> is the identity matrix when the device is aligned with the
+     * world's coordinate system, that is, when the device's X axis points
+     * toward East, the Y axis points to the North Pole and the device is facing
+     * the sky.
+     *
+     * <p><b>I</b> is a rotation matrix transforming the geomagnetic
+     * vector into the same coordinate space as gravity (the world's coordinate
+     * space). <b>I</b> is a simple rotation around the X axis.
+     * The inclination angle in radians can be computed with
+     * {@link #getInclination}.
+     * <hr>
+     * 
+     * <p> Each matrix is returned either as a 3x3 or 4x4 row-major matrix
+     * depending on the length of the passed array:
+     * <p><u>If the array length is 16:</u>
+     * <pre>
+     *   /  M[ 0]   M[ 1]   M[ 2]   M[ 3]  \
+     *   |  M[ 4]   M[ 5]   M[ 6]   M[ 7]  |
+     *   |  M[ 8]   M[ 9]   M[10]   M[11]  |
+     *   \  M[12]   M[13]   M[14]   M[15]  /
+     *</pre>
+     * This matrix is ready to be used by OpenGL ES's 
+     * {@link javax.microedition.khronos.opengles.GL10#glLoadMatrixf(float[], int) 
+     * glLoadMatrixf(float[], int)}. 
+     * <p>Note that because OpenGL matrices are column-major matrices you must
+     * transpose the matrix before using it. However, since the matrix is a 
+     * rotation matrix, its transpose is also its inverse, conveniently, it is
+     * often the inverse of the rotation that is needed for rendering; it can
+     * therefore be used with OpenGL ES directly.
+     * <p>
+     * Also note that the returned matrices always have this form:
+     * <pre>
+     *   /  M[ 0]   M[ 1]   M[ 2]   0  \
+     *   |  M[ 4]   M[ 5]   M[ 6]   0  |
+     *   |  M[ 8]   M[ 9]   M[10]   0  |
+     *   \      0       0       0   1  /
+     *</pre>
+     * <p><u>If the array length is 9:</u>
+     * <pre>
+     *   /  M[ 0]   M[ 1]   M[ 2]  \
+     *   |  M[ 3]   M[ 4]   M[ 5]  |
+     *   \  M[ 6]   M[ 7]   M[ 8]  /
+     *</pre>
+     *
+     * <hr>
+     * <p>The inverse of each matrix can be computed easily by taking its
+     * transpose.
+     *
+     * <p>The matrices returned by this function are meaningful only when the
+     * device is not free-falling and it is not close to the magnetic north.
+     * If the device is accelerating, or placed into a strong magnetic field,
+     * the returned matrices may be inaccurate.
+     *
+     * @param R is an array of 9 floats holding the rotation matrix <b>R</b>
+     * when this function returns. R can be null.<p>
+     * @param I is an array of 9 floats holding the rotation matrix <b>I</b>
+     * when this function returns. I can be null.<p>
+     * @param gravity is an array of 3 floats containing the gravity vector
+     * expressed in the device's coordinate. You can simply use the
+     * {@link android.hardware.SensorEvent#values values}
+     * returned by a {@link android.hardware.SensorEvent SensorEvent} of a
+     * {@link android.hardware.Sensor Sensor} of type
+     * {@link android.hardware.Sensor#TYPE_ACCELEROMETER TYPE_ACCELEROMETER}.<p>
+     * @param geomagnetic is an array of 3 floats containing the geomagnetic
+     * vector expressed in the device's coordinate. You can simply use the
+     * {@link android.hardware.SensorEvent#values values}
+     * returned by a {@link android.hardware.SensorEvent SensorEvent} of a
+     * {@link android.hardware.Sensor Sensor} of type
+     * {@link android.hardware.Sensor#TYPE_MAGNETIC_FIELD TYPE_MAGNETIC_FIELD}.
+     * @return
+     *   true on success<p>
+     *   false on failure (for instance, if the device is in free fall).
+     *   On failure the output matrices are not modified.
+     */
+
+    public static boolean getRotationMatrix(float[] R, float[] I,
+            float[] gravity, float[] geomagnetic) {
+        // TODO: move this to native code for efficiency
+        float Ax = gravity[0];
+        float Ay = gravity[1];
+        float Az = gravity[2];
+        final float Ex = geomagnetic[0];
+        final float Ey = geomagnetic[1];
+        final float Ez = geomagnetic[2];
+        float Hx = Ey*Az - Ez*Ay;
+        float Hy = Ez*Ax - Ex*Az;
+        float Hz = Ex*Ay - Ey*Ax;
+        final float normH = (float)Math.sqrt(Hx*Hx + Hy*Hy + Hz*Hz);
+        if (normH < 0.1f) {
+            // device is close to free fall (or in space?), or close to
+            // magnetic north pole. Typical values are  > 100.
+            return false;
+        }
+        final float invH = 1.0f / normH;
+        Hx *= invH;
+        Hy *= invH;
+        Hz *= invH;
+        final float invA = 1.0f / (float)Math.sqrt(Ax*Ax + Ay*Ay + Az*Az);
+        Ax *= invA;
+        Ay *= invA;
+        Az *= invA;
+        final float Mx = Ay*Hz - Az*Hy;
+        final float My = Az*Hx - Ax*Hz;
+        final float Mz = Ax*Hy - Ay*Hx;
+        if (R != null) {
+            if (R.length == 9) {
+                R[0] = Hx;     R[1] = Hy;     R[2] = Hz;
+                R[3] = Mx;     R[4] = My;     R[5] = Mz;
+                R[6] = Ax;     R[7] = Ay;     R[8] = Az;
+            } else if (R.length == 16) {
+                R[0]  = Hx;    R[1]  = Hy;    R[2]  = Hz;   R[3]  = 0;
+                R[4]  = Mx;    R[5]  = My;    R[6]  = Mz;   R[7]  = 0;
+                R[8]  = Ax;    R[9]  = Ay;    R[10] = Az;   R[11] = 0;
+                R[12] = 0;     R[13] = 0;     R[14] = 0;    R[15] = 1;
+            }
+        }
+        if (I != null) {
+            // compute the inclination matrix by projecting the geomagnetic
+            // vector onto the Z (gravity) and X (horizontal component
+            // of geomagnetic vector) axes.
+            final float invE = 1.0f / (float)Math.sqrt(Ex*Ex + Ey*Ey + Ez*Ez);
+            final float c = (Ex*Mx + Ey*My + Ez*Mz) * invE;
+            final float s = (Ex*Ax + Ey*Ay + Ez*Az) * invE;
+            if (I.length == 9) {
+                I[0] = 1;     I[1] = 0;     I[2] = 0;
+                I[3] = 0;     I[4] = c;     I[5] = s;
+                I[6] = 0;     I[7] =-s;     I[8] = c;
+            } else if (I.length == 16) {
+                I[0] = 1;     I[1] = 0;     I[2] = 0;
+                I[4] = 0;     I[5] = c;     I[6] = s;
+                I[8] = 0;     I[9] =-s;     I[10]= c;
+                I[3] = I[7] = I[11] = I[12] = I[13] = I[14] = 0;
+                I[15] = 1;
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Computes the geomagnetic inclination angle in radians from the
+     * inclination matrix <b>I</b> returned by {@link #getRotationMatrix}.
+     * @param I inclination matrix see {@link #getRotationMatrix}.
+     * @return The geomagnetic inclination angle in radians.
+     */
+    public static float getInclination(float[] I) {
+        if (I.length == 9) {
+            return (float)Math.atan2(I[5], I[4]);
+        } else {
+            return (float)Math.atan2(I[6], I[5]);            
+        }
+    }
+
+    /**
+     * Rotates the supplied rotation matrix so it is expressed in a
+     * different coordinate system. This is typically used when an application
+     * needs to compute the three orientation angles of the device (see
+     * {@link #getOrientation}) in a different coordinate system.
+     * 
+     * <p>When the rotation matrix is used for drawing (for instance with 
+     * OpenGL ES), it usually <b>doesn't need</b> to be transformed by this 
+     * function, unless the screen is physically rotated, such as when used
+     * in landscape mode. 
+     *
+     * <p><u>Examples:</u><p>
+     *
+     * <li>Using the camera (Y axis along the camera's axis) for an augmented 
+     * reality application where the rotation angles are needed :</li><p>
+     *
+     * <code>remapCoordinateSystem(inR, AXIS_X, AXIS_Z, outR);</code><p>
+     *
+     * <li>Using the device as a mechanical compass in landscape mode:</li><p>
+     *
+     * <code>remapCoordinateSystem(inR, AXIS_Y, AXIS_MINUS_X, outR);</code><p>
+     *
+     * Beware of the above example. This call is needed only if the device is
+     * physically used in landscape mode to calculate the rotation angles (see 
+     * {@link #getOrientation}).
+     * If the rotation matrix is also used for rendering, it may not need to 
+     * be transformed, for instance if your {@link android.app.Activity
+     * Activity} is running in landscape mode.
+     *
+     * <p>Since the resulting coordinate system is orthonormal, only two axes
+     * need to be specified.
+     *
+     * @param inR the rotation matrix to be transformed. Usually it is the
+     * matrix returned by {@link #getRotationMatrix}.
+     * @param X defines on which world axis and direction the X axis of the
+     *        device is mapped.
+     * @param Y defines on which world axis and direction the Y axis of the
+     *        device is mapped.
+     * @param outR the transformed rotation matrix. inR and outR can be the same
+     *        array, but it is not recommended for performance reason.
+     * @return true on success. false if the input parameters are incorrect, for
+     * instance if X and Y define the same axis. Or if inR and outR don't have 
+     * the same length.
+     */
+
+    public static boolean remapCoordinateSystem(float[] inR, int X, int Y,
+            float[] outR)
+    {
+        if (inR == outR) {
+            final float[] temp = mTempMatrix;
+            synchronized(temp) {
+                // we don't expect to have a lot of contention
+                if (remapCoordinateSystemImpl(inR, X, Y, temp)) {
+                    final int size = outR.length;
+                    for (int i=0 ; i<size ; i++)
+                        outR[i] = temp[i];
+                    return true;
+                }
+            }
+        }
+        return remapCoordinateSystemImpl(inR, X, Y, outR);
+    }
+
+    private static boolean remapCoordinateSystemImpl(float[] inR, int X, int Y,
+            float[] outR)
+    {
+        /*
+         * X and Y define a rotation matrix 'r':
+         *
+         *  (X==1)?((X&0x80)?-1:1):0    (X==2)?((X&0x80)?-1:1):0    (X==3)?((X&0x80)?-1:1):0
+         *  (Y==1)?((Y&0x80)?-1:1):0    (Y==2)?((Y&0x80)?-1:1):0    (Y==3)?((X&0x80)?-1:1):0
+         *                              r[0] ^ r[1]
+         *
+         * where the 3rd line is the vector product of the first 2 lines
+         *
+         */
+
+        final int length = outR.length;
+        if (inR.length != length)
+            return false;   // invalid parameter
+        if ((X & 0x7C)!=0 || (Y & 0x7C)!=0)
+            return false;   // invalid parameter
+        if (((X & 0x3)==0) || ((Y & 0x3)==0))
+            return false;   // no axis specified
+        if ((X & 0x3) == (Y & 0x3))
+            return false;   // same axis specified
+
+        // Z is "the other" axis, its sign is either +/- sign(X)*sign(Y)
+        // this can be calculated by exclusive-or'ing X and Y; except for
+        // the sign inversion (+/-) which is calculated below.
+        int Z = X ^ Y;
+
+        // extract the axis (remove the sign), offset in the range 0 to 2.
+        final int x = (X & 0x3)-1;
+        final int y = (Y & 0x3)-1;
+        final int z = (Z & 0x3)-1;
+
+        // compute the sign of Z (whether it needs to be inverted)
+        final int axis_y = (z+1)%3;
+        final int axis_z = (z+2)%3;
+        if (((x^axis_y)|(y^axis_z)) != 0)
+            Z ^= 0x80;
+
+        final boolean sx = (X>=0x80);
+        final boolean sy = (Y>=0x80);
+        final boolean sz = (Z>=0x80);
+
+        // Perform R * r, in avoiding actual muls and adds.
+        final int rowLength = ((length==16)?4:3);
+        for (int j=0 ; j<3 ; j++) {
+            final int offset = j*rowLength;
+            for (int i=0 ; i<3 ; i++) {
+                if (x==i)   outR[offset+i] = sx ? -inR[offset+0] : inR[offset+0];
+                if (y==i)   outR[offset+i] = sy ? -inR[offset+1] : inR[offset+1];
+                if (z==i)   outR[offset+i] = sz ? -inR[offset+2] : inR[offset+2];
+            }
+        }
+        if (length == 16) {
+            outR[3] = outR[7] = outR[11] = outR[12] = outR[13] = outR[14] = 0;
+            outR[15] = 1;
+        }
+        return true;
+    }
+
+    /**
+     * Computes the device's orientation based on the rotation matrix.
+     * <p> When it returns, the array values is filled with the result:
+     * <li>values[0]: <i>azimuth</i>, rotation around the Z axis.</li>
+     * <li>values[1]: <i>pitch</i>, rotation around the X axis.</li>
+     * <li>values[2]: <i>roll</i>, rotation around the Y axis.</li>
+     * <p>
+     *
+     * @param R rotation matrix see {@link #getRotationMatrix}.
+     * @param values an array of 3 floats to hold the result.
+     * @return The array values passed as argument.
+     */
+    public static float[] getOrientation(float[] R, float values[]) {
+        /*
+         * 4x4 (length=16) case:
+         *   /  R[ 0]   R[ 1]   R[ 2]   0  \
+         *   |  R[ 4]   R[ 5]   R[ 6]   0  |
+         *   |  R[ 8]   R[ 9]   R[10]   0  |
+         *   \      0       0       0   1  /
+         *   
+         * 3x3 (length=9) case:
+         *   /  R[ 0]   R[ 1]   R[ 2]  \
+         *   |  R[ 3]   R[ 4]   R[ 5]  |
+         *   \  R[ 6]   R[ 7]   R[ 8]  /
+         * 
+         */
+        if (R.length == 9) {
+            values[0] = (float)Math.atan2(R[1], R[4]);
+            values[1] = (float)Math.asin(-R[7]);
+            values[2] = (float)Math.atan2(-R[6], R[8]);
+        } else {
+            values[0] = (float)Math.atan2(R[1], R[5]);
+            values[1] = (float)Math.asin(-R[9]);
+            values[2] = (float)Math.atan2(-R[8], R[10]);
+        }
+        return values;
+    }
+
+
+    /**
+     * {@hide}
+     */
+    public void onRotationChanged(int rotation) {
+        synchronized(sListeners) {
+            sRotation  = rotation;
+        }
+    }
+
+    static int getRotation() {
+        synchronized(sListeners) {
+            return sRotation;
+        }
+    }
+
+    private class LegacyListener implements SensorEventListener {
+        private float mValues[] = new float[6];
+        @SuppressWarnings("deprecation")
+        private SensorListener mTarget;
+        private int mSensors;
+        private final LmsFilter mYawfilter = new LmsFilter();
+
+        @SuppressWarnings("deprecation")
+        LegacyListener(SensorListener target) {
+            mTarget = target;
+            mSensors = 0;
+        }
+
+        void registerSensor(int legacyType) {
+            mSensors |= legacyType;
+        }
+
+        boolean unregisterSensor(int legacyType) {
+            mSensors &= ~legacyType;
+            int mask = SENSOR_ORIENTATION|SENSOR_ORIENTATION_RAW;
+            if (((legacyType&mask)!=0) && ((mSensors&mask)!=0)) {
+                return false;
+            }
+            return true;
+        }
+
+        @SuppressWarnings("deprecation")
+        public void onAccuracyChanged(Sensor sensor, int accuracy) {
+            try {
+                mTarget.onAccuracyChanged(sensor.getLegacyType(), accuracy);
+            } catch (AbstractMethodError e) {
+                // old app that doesn't implement this method
+                // just ignore it.
+            }
+        }
+
+        @SuppressWarnings("deprecation")
+        public void onSensorChanged(SensorEvent event) {
+            final float v[] = mValues;
+            v[0] = event.values[0];
+            v[1] = event.values[1];
+            v[2] = event.values[2];
+            int legacyType = event.sensor.getLegacyType();
+            mapSensorDataToWindow(legacyType, v, SensorManager.getRotation());
+            if (event.sensor.getType() == Sensor.TYPE_ORIENTATION) {
+                if ((mSensors & SENSOR_ORIENTATION_RAW)!=0) {
+                    mTarget.onSensorChanged(SENSOR_ORIENTATION_RAW, v);
+                }
+                if ((mSensors & SENSOR_ORIENTATION)!=0) {
+                    v[0] = mYawfilter.filter(event.timestamp, v[0]);
+                    mTarget.onSensorChanged(SENSOR_ORIENTATION, v);
+                }
+            } else {
+                mTarget.onSensorChanged(legacyType, v);
+            }
+        }
+
+        /*
+         * Helper function to convert the specified sensor's data to the windows's
+         * coordinate space from the device's coordinate space.
+         *
+         * output: 3,4,5: values in the old API format
+         *         0,1,2: transformed values in the old API format
+         *
+         */
+        private void mapSensorDataToWindow(int sensor,
+                float[] values, int orientation) {
+            float x = values[0];
+            float y = values[1];
+            float z = values[2];
+
+            switch (sensor) {
+                case SensorManager.SENSOR_ORIENTATION:
+                case SensorManager.SENSOR_ORIENTATION_RAW:
+                    z = -z;
+                    break;
+                case SensorManager.SENSOR_ACCELEROMETER:
+                    x = -x;
+                    y = -y;
+                    z = -z;
+                    break;
+                case SensorManager.SENSOR_MAGNETIC_FIELD:
+                    x = -x;
+                    y = -y;
+                    break;
+            }
+            values[0] = x;
+            values[1] = y;
+            values[2] = z;
+            values[3] = x;
+            values[4] = y;
+            values[5] = z;
+            // TODO: add support for 180 and 270 orientations
+            if (orientation == Surface.ROTATION_90) {
+                switch (sensor) {
+                    case SENSOR_ACCELEROMETER:
+                    case SENSOR_MAGNETIC_FIELD:
+                        values[0] =-y;
+                        values[1] = x;
+                        values[2] = z;
+                        break;
+                    case SENSOR_ORIENTATION:
+                    case SENSOR_ORIENTATION_RAW:
+                        values[0] = x + ((x < 270) ? 90 : -270);
+                        values[1] = z;
+                        values[2] = y;
+                        break;
+                }
+            }
+        }
+    }
+
+    class LmsFilter {
+        private static final int SENSORS_RATE_MS = 20;
+        private static final int COUNT = 12;
+        private static final float PREDICTION_RATIO = 1.0f/3.0f;
+        private static final float PREDICTION_TIME = (SENSORS_RATE_MS*COUNT/1000.0f)*PREDICTION_RATIO;
+        private float mV[] = new float[COUNT*2];
+        private float mT[] = new float[COUNT*2];
+        private int mIndex;
+
+        public LmsFilter() {
+            mIndex = COUNT;
+        }
+
+        public float filter(long time, float in) {
+            float v = in;
+            final float ns = 1.0f / 1000000000.0f;
+            final float t = time*ns;
+            float v1 = mV[mIndex];
+            if ((v-v1) > 180) {
+                v -= 360;
+            } else if ((v1-v) > 180) {
+                v += 360;
+            }
+            /* Manage the circular buffer, we write the data twice spaced
+             * by COUNT values, so that we don't have to copy the array
+             * when it's full
+             */
+            mIndex++;
+            if (mIndex >= COUNT*2)
+                mIndex = COUNT;
+            mV[mIndex] = v;
+            mT[mIndex] = t;
+            mV[mIndex-COUNT] = v;
+            mT[mIndex-COUNT] = t;
+
+            float A, B, C, D, E;
+            float a, b;
+            int i;
+
+            A = B = C = D = E = 0;
+            for (i=0 ; i<COUNT-1 ; i++) {
+                final int j = mIndex - 1 - i;
+                final float Z = mV[j];
+                final float T = 0.5f*(mT[j] + mT[j+1]) - t;
+                float dT = mT[j] - mT[j+1];
+                dT *= dT;
+                A += Z*dT;
+                B += T*(T*dT);
+                C +=   (T*dT);
+                D += Z*(T*dT);
+                E += dT;
+            }
+            b = (A*B + C*D) / (E*B + C*C);
+            a = (E*b - A) / C;
+            float f = b + PREDICTION_TIME*a;
+
+            // Normalize
+            f *= (1.0f / 360.0f);
+            if (((f>=0)?f:-f) >= 0.5f)
+                f = f - (float)Math.ceil(f + 0.5f) + 1.0f;
+            if (f < 0)
+                f += 1.0f;
+            f *= 360.0f;
+            return f;
+        }
+    }
+
+
+    private static native void nativeClassInit();
+
+    private static native int sensors_module_init();
+    private static native int sensors_module_get_next_sensor(Sensor sensor, int next);
+
+    // Used within this module from outside SensorManager, don't make private
+    static native int sensors_data_init();
+    static native int sensors_data_uninit();
+    static native int sensors_data_open(FileDescriptor fd);
+    static native int sensors_data_close();
+    static native int sensors_data_poll(float[] values, int[] status, long[] timestamp);
+}
diff --git a/core/java/android/hardware/package.html b/core/java/android/hardware/package.html
new file mode 100644
index 0000000..06788a6
--- /dev/null
+++ b/core/java/android/hardware/package.html
@@ -0,0 +1,5 @@
+<HTML>
+<BODY>
+Provides support for hardware devices that may not be present on every Android device.
+</BODY>
+</HTML>
diff --git a/core/java/android/inputmethodservice/AbstractInputMethodService.java b/core/java/android/inputmethodservice/AbstractInputMethodService.java
new file mode 100644
index 0000000..eedcc35
--- /dev/null
+++ b/core/java/android/inputmethodservice/AbstractInputMethodService.java
@@ -0,0 +1,180 @@
+/*
+ * Copyright (C) 2007-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 android.inputmethodservice;
+
+import android.app.Service;
+import android.content.Intent;
+import android.os.IBinder;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+import android.view.inputmethod.InputMethod;
+import android.view.inputmethod.InputMethodSession;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+
+/**
+ * AbstractInputMethodService provides a abstract base class for input methods.
+ * Normal input method implementations will not derive from this directly,
+ * instead building on top of {@link InputMethodService} or another more
+ * complete base class.  Be sure to read {@link InputMethod} for more
+ * information on the basics of writing input methods.
+ * 
+ * <p>This class combines a Service (representing the input method component
+ * to the system with the InputMethod interface that input methods must
+ * implement.  This base class takes care of reporting your InputMethod from
+ * the service when clients bind to it, but provides no standard implementation
+ * of the InputMethod interface itself.  Derived classes must implement that
+ * interface.
+ */
+public abstract class AbstractInputMethodService extends Service
+        implements KeyEvent.Callback {
+    private InputMethod mInputMethod;
+    
+    /**
+     * Base class for derived classes to implement their {@link InputMethod}
+     * interface.  This takes care of basic maintenance of the input method,
+     * but most behavior must be implemented in a derived class.
+     */
+    public abstract class AbstractInputMethodImpl implements InputMethod {
+        /**
+         * Instantiate a new client session for the input method, by calling
+         * back to {@link AbstractInputMethodService#onCreateInputMethodSessionInterface()
+         * AbstractInputMethodService.onCreateInputMethodSessionInterface()}.
+         */
+        public void createSession(SessionCallback callback) {
+            callback.sessionCreated(onCreateInputMethodSessionInterface());
+        }
+        
+        /**
+         * Take care of enabling or disabling an existing session by calling its
+         * {@link AbstractInputMethodSessionImpl#revokeSelf()
+         * AbstractInputMethodSessionImpl.setEnabled()} method.
+         */
+        public void setSessionEnabled(InputMethodSession session, boolean enabled) {
+            ((AbstractInputMethodSessionImpl)session).setEnabled(enabled);
+        }
+        
+        /**
+         * Take care of killing an existing session by calling its
+         * {@link AbstractInputMethodSessionImpl#revokeSelf()
+         * AbstractInputMethodSessionImpl.revokeSelf()} method.
+         */
+        public void revokeSession(InputMethodSession session) {
+            ((AbstractInputMethodSessionImpl)session).revokeSelf();
+        }
+    }
+    
+    /**
+     * Base class for derived classes to implement their {@link InputMethodSession}
+     * interface.  This takes care of basic maintenance of the session,
+     * but most behavior must be implemented in a derived class.
+     */
+    public abstract class AbstractInputMethodSessionImpl implements InputMethodSession {
+        boolean mEnabled = true;
+        boolean mRevoked;
+        
+        /**
+         * Check whether this session has been enabled by the system.  If not
+         * enabled, you should not execute any calls on to it.
+         */
+        public boolean isEnabled() {
+            return mEnabled;
+        }
+        
+        /**
+         * Check whether this session has been revoked by the system.  Revoked
+         * session is also always disabled, so there is generally no need to
+         * explicitly check for this.
+         */
+        public boolean isRevoked() {
+            return mRevoked;
+        }
+        
+        /**
+         * Change the enabled state of the session.  This only works if the
+         * session has not been revoked.
+         */
+        public void setEnabled(boolean enabled) {
+            if (!mRevoked) {
+                mEnabled = enabled;
+            }
+        }
+        
+        /**
+         * Revoke the session from the client.  This disabled the session, and
+         * prevents it from ever being enabled again.
+         */
+        public void revokeSelf() {
+            mRevoked = true;
+            mEnabled = false;
+        }
+        
+        /**
+         * Take care of dispatching incoming key events to the appropriate
+         * callbacks on the service, and tell the client when this is done.
+         */
+        public void dispatchKeyEvent(int seq, KeyEvent event, EventCallback callback) {
+            boolean handled = event.dispatch(AbstractInputMethodService.this);
+            if (callback != null) {
+                callback.finishedEvent(seq, handled);
+            }
+        }
+
+        /**
+         * Take care of dispatching incoming trackball events to the appropriate
+         * callbacks on the service, and tell the client when this is done.
+         */
+        public void dispatchTrackballEvent(int seq, MotionEvent event, EventCallback callback) {
+            boolean handled = onTrackballEvent(event);
+            if (callback != null) {
+                callback.finishedEvent(seq, handled);
+            }
+        }
+    }
+    
+    /**
+     * Called by the framework during initialization, when the InputMethod
+     * interface for this service needs to be created.
+     */
+    public abstract AbstractInputMethodImpl onCreateInputMethodInterface();
+    
+    /**
+     * Called by the framework when a new InputMethodSession interface is
+     * needed for a new client of the input method.
+     */
+    public abstract AbstractInputMethodSessionImpl onCreateInputMethodSessionInterface();
+    
+    /**
+     * Implement this to handle {@link android.os.Binder#dump Binder.dump()}
+     * calls on your input method.
+     */
+    protected void dump(FileDescriptor fd, PrintWriter fout, String[] args) {
+    }
+
+    @Override
+    final public IBinder onBind(Intent intent) {
+        if (mInputMethod == null) {
+            mInputMethod = onCreateInputMethodInterface();
+        }
+        return new IInputMethodWrapper(this, mInputMethod);
+    }
+    
+    public boolean onTrackballEvent(MotionEvent event) {
+        return false;
+    }
+}
diff --git a/core/java/android/inputmethodservice/ExtractEditText.java b/core/java/android/inputmethodservice/ExtractEditText.java
new file mode 100644
index 0000000..0295f69
--- /dev/null
+++ b/core/java/android/inputmethodservice/ExtractEditText.java
@@ -0,0 +1,130 @@
+package android.inputmethodservice;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.inputmethod.ExtractedText;
+import android.widget.EditText;
+
+/***
+ * Specialization of {@link EditText} for showing and interacting with the
+ * extracted text in a full-screen input method.
+ */
+public class ExtractEditText extends EditText {
+    private InputMethodService mIME;
+    private int mSettingExtractedText;
+    
+    public ExtractEditText(Context context) {
+        super(context, null);
+    }
+
+    public ExtractEditText(Context context, AttributeSet attrs) {
+        super(context, attrs, com.android.internal.R.attr.editTextStyle);
+    }
+
+    public ExtractEditText(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+    }
+    
+    void setIME(InputMethodService ime) {
+        mIME = ime;
+    }
+    
+    /**
+     * Start making changes that will not be reported to the client.  That
+     * is, {@link #onSelectionChanged(int, int)} will not result in sending
+     * the new selection to the client
+     */
+    public void startInternalChanges() {
+        mSettingExtractedText += 1;
+    }
+    
+    /**
+     * Finish making changes that will not be reported to the client.  That
+     * is, {@link #onSelectionChanged(int, int)} will not result in sending
+     * the new selection to the client
+     */
+    public void finishInternalChanges() {
+        mSettingExtractedText -= 1;
+    }
+    
+    /**
+     * Implement just to keep track of when we are setting text from the
+     * client (vs. seeing changes in ourself from the user).
+     */
+    @Override public void setExtractedText(ExtractedText text) {
+        try {
+            mSettingExtractedText++;
+            super.setExtractedText(text);
+        } finally {
+            mSettingExtractedText--;
+        }
+    }
+    
+    /**
+     * Report to the underlying text editor about selection changes.
+     */
+    @Override protected void onSelectionChanged(int selStart, int selEnd) {
+        if (mSettingExtractedText == 0 && mIME != null && selStart >= 0 && selEnd >= 0) {
+            mIME.onExtractedSelectionChanged(selStart, selEnd);
+        }
+    }
+    
+    /**
+     * Redirect clicks to the IME for handling there.  First allows any
+     * on click handler to run, though.
+     */
+    @Override public boolean performClick() {
+        if (!super.performClick() && mIME != null) {
+            mIME.onExtractedTextClicked();
+            return true;
+        }
+        return false;
+    }
+    
+    @Override public boolean onTextContextMenuItem(int id) {
+        if (mIME != null) {
+            if (mIME.onExtractTextContextMenuItem(id)) {
+                return true;
+            }
+        }
+        return super.onTextContextMenuItem(id);
+    }
+    
+    /**
+     * We are always considered to be an input method target.
+     */
+    public boolean isInputMethodTarget() {
+        return true;
+    }
+    
+    /**
+     * Return true if the edit text is currently showing a scroll bar.
+     */
+    public boolean hasVerticalScrollBar() {
+        return computeVerticalScrollRange() > computeVerticalScrollExtent();
+    }
+    
+    /**
+     * Pretend like the window this view is in always has focus, so its
+     * highlight and cursor will be displayed.
+     */
+    @Override public boolean hasWindowFocus() {
+        return this.isEnabled() ? true : false;
+    }
+
+    /**
+     * Pretend like this view always has focus, so its
+     * highlight and cursor will be displayed.
+     */
+    @Override public boolean isFocused() {
+        return this.isEnabled() ? true : false;
+    }
+
+    /**
+     * Pretend like this view always has focus, so its
+     * highlight and cursor will be displayed.
+     */
+    @Override public boolean hasFocus() {
+        return this.isEnabled() ? true : false;
+    }
+}
diff --git a/core/java/android/inputmethodservice/IInputMethodSessionWrapper.java b/core/java/android/inputmethodservice/IInputMethodSessionWrapper.java
new file mode 100644
index 0000000..5a85c66
--- /dev/null
+++ b/core/java/android/inputmethodservice/IInputMethodSessionWrapper.java
@@ -0,0 +1,152 @@
+package android.inputmethodservice;
+
+import com.android.internal.os.HandlerCaller;
+import com.android.internal.view.IInputMethodCallback;
+import com.android.internal.view.IInputMethodSession;
+
+import android.content.Context;
+import android.graphics.Rect;
+import android.os.Bundle;
+import android.os.Message;
+import android.os.RemoteException;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+import android.view.inputmethod.CompletionInfo;
+import android.view.inputmethod.ExtractedText;
+import android.view.inputmethod.InputMethodSession;
+import android.view.inputmethod.EditorInfo;
+
+class IInputMethodSessionWrapper extends IInputMethodSession.Stub
+        implements HandlerCaller.Callback {
+    private static final String TAG = "InputMethodWrapper";
+    private static final boolean DEBUG = false;
+    
+    private static final int DO_FINISH_INPUT = 60;
+    private static final int DO_DISPLAY_COMPLETIONS = 65;
+    private static final int DO_UPDATE_EXTRACTED_TEXT = 67;
+    private static final int DO_DISPATCH_KEY_EVENT = 70;
+    private static final int DO_DISPATCH_TRACKBALL_EVENT = 80;
+    private static final int DO_UPDATE_SELECTION = 90;
+    private static final int DO_UPDATE_CURSOR = 95;
+    private static final int DO_APP_PRIVATE_COMMAND = 100;
+   
+    final HandlerCaller mCaller;
+    final InputMethodSession mInputMethodSession;
+    
+    // NOTE: we should have a cache of these.
+    static class InputMethodEventCallbackWrapper implements InputMethodSession.EventCallback {
+        final IInputMethodCallback mCb;
+        InputMethodEventCallbackWrapper(IInputMethodCallback cb) {
+            mCb = cb;
+        }
+        public void finishedEvent(int seq, boolean handled) {
+            try {
+                mCb.finishedEvent(seq, handled);
+            } catch (RemoteException e) {
+            }
+        }
+    }
+    
+    public IInputMethodSessionWrapper(Context context,
+            InputMethodSession inputMethodSession) {
+        mCaller = new HandlerCaller(context, this);
+        mInputMethodSession = inputMethodSession;
+    }
+
+    public InputMethodSession getInternalInputMethodSession() {
+        return mInputMethodSession;
+    }
+
+    public void executeMessage(Message msg) {
+        switch (msg.what) {
+            case DO_FINISH_INPUT:
+                mInputMethodSession.finishInput();
+                return;
+            case DO_DISPLAY_COMPLETIONS:
+                mInputMethodSession.displayCompletions((CompletionInfo[])msg.obj);
+                return;
+            case DO_UPDATE_EXTRACTED_TEXT:
+                mInputMethodSession.updateExtractedText(msg.arg1,
+                        (ExtractedText)msg.obj);
+                return;
+            case DO_DISPATCH_KEY_EVENT: {
+                HandlerCaller.SomeArgs args = (HandlerCaller.SomeArgs)msg.obj;
+                mInputMethodSession.dispatchKeyEvent(msg.arg1,
+                        (KeyEvent)args.arg1,
+                        new InputMethodEventCallbackWrapper(
+                                (IInputMethodCallback)args.arg2));
+                mCaller.recycleArgs(args);
+                return;
+            }
+            case DO_DISPATCH_TRACKBALL_EVENT: {
+                HandlerCaller.SomeArgs args = (HandlerCaller.SomeArgs)msg.obj;
+                mInputMethodSession.dispatchTrackballEvent(msg.arg1,
+                        (MotionEvent)args.arg1,
+                        new InputMethodEventCallbackWrapper(
+                                (IInputMethodCallback)args.arg2));
+                mCaller.recycleArgs(args);
+                return;
+            }
+            case DO_UPDATE_SELECTION: {
+                HandlerCaller.SomeArgs args = (HandlerCaller.SomeArgs)msg.obj;
+                mInputMethodSession.updateSelection(args.argi1, args.argi2,
+                        args.argi3, args.argi4, args.argi5, args.argi6);
+                mCaller.recycleArgs(args);
+                return;
+            }
+            case DO_UPDATE_CURSOR: {
+                mInputMethodSession.updateCursor((Rect)msg.obj);
+                return;
+            }
+            case DO_APP_PRIVATE_COMMAND: {
+                HandlerCaller.SomeArgs args = (HandlerCaller.SomeArgs)msg.obj;
+                mInputMethodSession.appPrivateCommand((String)args.arg1,
+                        (Bundle)args.arg2);
+                mCaller.recycleArgs(args);
+                return;
+            }
+        }
+        Log.w(TAG, "Unhandled message code: " + msg.what);
+    }
+    
+    public void finishInput() {
+        mCaller.executeOrSendMessage(mCaller.obtainMessage(DO_FINISH_INPUT));
+    }
+
+    public void displayCompletions(CompletionInfo[] completions) {
+        mCaller.executeOrSendMessage(mCaller.obtainMessageO(
+                DO_DISPLAY_COMPLETIONS, completions));
+    }
+    
+    public void updateExtractedText(int token, ExtractedText text) {
+        mCaller.executeOrSendMessage(mCaller.obtainMessageIO(
+                DO_UPDATE_EXTRACTED_TEXT, token, text));
+    }
+    
+    public void dispatchKeyEvent(int seq, KeyEvent event, IInputMethodCallback callback) {
+        mCaller.executeOrSendMessage(mCaller.obtainMessageIOO(DO_DISPATCH_KEY_EVENT, seq,
+                event, callback));
+    }
+
+    public void dispatchTrackballEvent(int seq, MotionEvent event, IInputMethodCallback callback) {
+        mCaller.executeOrSendMessage(mCaller.obtainMessageIOO(DO_DISPATCH_TRACKBALL_EVENT, seq,
+                event, callback));
+    }
+
+    public void updateSelection(int oldSelStart, int oldSelEnd,
+            int newSelStart, int newSelEnd, int candidatesStart, int candidatesEnd) {
+        mCaller.executeOrSendMessage(mCaller.obtainMessageIIIIII(DO_UPDATE_SELECTION,
+                oldSelStart, oldSelEnd, newSelStart, newSelEnd,
+                candidatesStart, candidatesEnd));
+    }
+    
+    public void updateCursor(Rect newCursor) {
+        mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_UPDATE_CURSOR,
+                newCursor));
+    }
+    
+    public void appPrivateCommand(String action, Bundle data) {
+        mCaller.executeOrSendMessage(mCaller.obtainMessageOO(DO_APP_PRIVATE_COMMAND, action, data));
+    }
+}
diff --git a/core/java/android/inputmethodservice/IInputMethodWrapper.java b/core/java/android/inputmethodservice/IInputMethodWrapper.java
new file mode 100644
index 0000000..a2c75b5
--- /dev/null
+++ b/core/java/android/inputmethodservice/IInputMethodWrapper.java
@@ -0,0 +1,236 @@
+package android.inputmethodservice;
+
+import com.android.internal.os.HandlerCaller;
+import com.android.internal.view.IInputContext;
+import com.android.internal.view.IInputMethod;
+import com.android.internal.view.IInputMethodCallback;
+import com.android.internal.view.IInputMethodSession;
+import com.android.internal.view.InputConnectionWrapper;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.Message;
+import android.os.RemoteException;
+import android.util.Log;
+import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.InputBinding;
+import android.view.inputmethod.InputConnection;
+import android.view.inputmethod.InputMethod;
+import android.view.inputmethod.InputMethodSession;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Implements the internal IInputMethod interface to convert incoming calls
+ * on to it back to calls on the public InputMethod interface, scheduling
+ * them on the main thread of the process.
+ */
+class IInputMethodWrapper extends IInputMethod.Stub
+        implements HandlerCaller.Callback {
+    private static final String TAG = "InputMethodWrapper";
+    private static final boolean DEBUG = false;
+    
+    private static final int DO_DUMP = 1;
+    private static final int DO_ATTACH_TOKEN = 10;
+    private static final int DO_SET_INPUT_CONTEXT = 20;
+    private static final int DO_UNSET_INPUT_CONTEXT = 30;
+    private static final int DO_START_INPUT = 32;
+    private static final int DO_RESTART_INPUT = 34;
+    private static final int DO_CREATE_SESSION = 40;
+    private static final int DO_SET_SESSION_ENABLED = 45;
+    private static final int DO_REVOKE_SESSION = 50;
+    private static final int DO_SHOW_SOFT_INPUT = 60;
+    private static final int DO_HIDE_SOFT_INPUT = 70;
+   
+    final AbstractInputMethodService mTarget;
+    final HandlerCaller mCaller;
+    final InputMethod mInputMethod;
+    
+    static class Notifier {
+        boolean notified;
+    }
+    
+    // NOTE: we should have a cache of these.
+    static class InputMethodSessionCallbackWrapper implements InputMethod.SessionCallback {
+        final Context mContext;
+        final IInputMethodCallback mCb;
+        InputMethodSessionCallbackWrapper(Context context, IInputMethodCallback cb) {
+            mContext = context;
+            mCb = cb;
+        }
+        public void sessionCreated(InputMethodSession session) {
+            try {
+                if (session != null) {
+                    IInputMethodSessionWrapper wrap =
+                            new IInputMethodSessionWrapper(mContext, session);
+                    mCb.sessionCreated(wrap);
+                } else {
+                    mCb.sessionCreated(null);
+                }
+            } catch (RemoteException e) {
+            }
+        }
+    }
+    
+    public IInputMethodWrapper(AbstractInputMethodService context,
+            InputMethod inputMethod) {
+        mTarget = context;
+        mCaller = new HandlerCaller(context, this);
+        mInputMethod = inputMethod;
+    }
+
+    public InputMethod getInternalInputMethod() {
+        return mInputMethod;
+    }
+
+    public void executeMessage(Message msg) {
+        switch (msg.what) {
+            case DO_DUMP: {
+                HandlerCaller.SomeArgs args = (HandlerCaller.SomeArgs)msg.obj;
+                try {
+                    mTarget.dump((FileDescriptor)args.arg1,
+                            (PrintWriter)args.arg2, (String[])args.arg3);
+                } catch (RuntimeException e) {
+                    ((PrintWriter)args.arg2).println("Exception: " + e);
+                }
+                synchronized (args.arg4) {
+                    ((CountDownLatch)args.arg4).countDown();
+                }
+                return;
+            }
+            
+            case DO_ATTACH_TOKEN: {
+                mInputMethod.attachToken((IBinder)msg.obj);
+                return;
+            }
+            case DO_SET_INPUT_CONTEXT: {
+                mInputMethod.bindInput((InputBinding)msg.obj);
+                return;
+            }
+            case DO_UNSET_INPUT_CONTEXT:
+                mInputMethod.unbindInput();
+                return;
+            case DO_START_INPUT: {
+                HandlerCaller.SomeArgs args = (HandlerCaller.SomeArgs)msg.obj;
+                IInputContext inputContext = (IInputContext)args.arg1;
+                InputConnection ic = inputContext != null
+                        ? new InputConnectionWrapper(inputContext) : null;
+                mInputMethod.startInput(ic, (EditorInfo)args.arg2);
+                return;
+            }
+            case DO_RESTART_INPUT: {
+                HandlerCaller.SomeArgs args = (HandlerCaller.SomeArgs)msg.obj;
+                IInputContext inputContext = (IInputContext)args.arg1;
+                InputConnection ic = inputContext != null
+                        ? new InputConnectionWrapper(inputContext) : null;
+                mInputMethod.restartInput(ic, (EditorInfo)args.arg2);
+                return;
+            }
+            case DO_CREATE_SESSION: {
+                mInputMethod.createSession(new InputMethodSessionCallbackWrapper(
+                        mCaller.mContext, (IInputMethodCallback)msg.obj));
+                return;
+            }
+            case DO_SET_SESSION_ENABLED:
+                mInputMethod.setSessionEnabled((InputMethodSession)msg.obj,
+                        msg.arg1 != 0);
+                return;
+            case DO_REVOKE_SESSION:
+                mInputMethod.revokeSession((InputMethodSession)msg.obj);
+                return;
+            case DO_SHOW_SOFT_INPUT:
+                mInputMethod.showSoftInput(msg.arg1);
+                return;
+            case DO_HIDE_SOFT_INPUT:
+                mInputMethod.hideSoftInput();
+                return;
+        }
+        Log.w(TAG, "Unhandled message code: " + msg.what);
+    }
+    
+    @Override protected void dump(FileDescriptor fd, PrintWriter fout, String[] args) {
+        if (mTarget.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
+                != PackageManager.PERMISSION_GRANTED) {
+            
+            fout.println("Permission Denial: can't dump InputMethodManager from from pid="
+                    + Binder.getCallingPid()
+                    + ", uid=" + Binder.getCallingUid());
+            return;
+        }
+
+        CountDownLatch latch = new CountDownLatch(1);
+        mCaller.executeOrSendMessage(mCaller.obtainMessageOOOO(DO_DUMP,
+                fd, fout, args, latch));
+        try {
+            if (!latch.await(5, TimeUnit.SECONDS)) {
+                fout.println("Timeout waiting for dump");
+            }
+        } catch (InterruptedException e) {
+            fout.println("Interrupted waiting for dump");
+        }
+    }
+
+    public void attachToken(IBinder token) {
+        mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_ATTACH_TOKEN, token));
+    }
+    
+    public void bindInput(InputBinding binding) {
+        InputConnection ic = new InputConnectionWrapper(
+                IInputContext.Stub.asInterface(binding.getConnectionToken()));
+        InputBinding nu = new InputBinding(ic, binding);
+        mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_SET_INPUT_CONTEXT, nu));
+    }
+
+    public void unbindInput() {
+        mCaller.executeOrSendMessage(mCaller.obtainMessage(DO_UNSET_INPUT_CONTEXT));
+    }
+
+    public void startInput(IInputContext inputContext, EditorInfo attribute) {
+        mCaller.executeOrSendMessage(mCaller.obtainMessageOO(DO_START_INPUT,
+                inputContext, attribute));
+    }
+
+    public void restartInput(IInputContext inputContext, EditorInfo attribute) {
+        mCaller.executeOrSendMessage(mCaller.obtainMessageOO(DO_RESTART_INPUT,
+                inputContext, attribute));
+    }
+
+    public void createSession(IInputMethodCallback callback) {
+        mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_CREATE_SESSION, callback));
+    }
+
+    public void setSessionEnabled(IInputMethodSession session, boolean enabled) {
+        try {
+            InputMethodSession ls = ((IInputMethodSessionWrapper)
+                    session).getInternalInputMethodSession();
+            mCaller.executeOrSendMessage(mCaller.obtainMessageIO(
+                    DO_SET_SESSION_ENABLED, enabled ? 1 : 0, ls));
+        } catch (ClassCastException e) {
+            Log.w(TAG, "Incoming session not of correct type: " + session, e);
+        }
+    }
+    
+    public void revokeSession(IInputMethodSession session) {
+        try {
+            InputMethodSession ls = ((IInputMethodSessionWrapper)
+                    session).getInternalInputMethodSession();
+            mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_REVOKE_SESSION, ls));
+        } catch (ClassCastException e) {
+            Log.w(TAG, "Incoming session not of correct type: " + session, e);
+        }
+    }
+    
+    public void showSoftInput(int flags) {
+        mCaller.executeOrSendMessage(mCaller.obtainMessageI(DO_SHOW_SOFT_INPUT,
+                flags));
+    }
+    
+    public void hideSoftInput() {
+        mCaller.executeOrSendMessage(mCaller.obtainMessage(DO_HIDE_SOFT_INPUT));
+    }
+}
diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java
new file mode 100644
index 0000000..1e2e2f3
--- /dev/null
+++ b/core/java/android/inputmethodservice/InputMethodService.java
@@ -0,0 +1,1880 @@
+/*
+ * Copyright (C) 2007-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 android.inputmethodservice;
+
+import static android.view.ViewGroup.LayoutParams.FILL_PARENT;
+import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
+
+import android.app.Dialog;
+import android.content.Context;
+import android.content.res.Configuration;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.SystemClock;
+import android.provider.Settings;
+import android.text.InputType;
+import android.text.Layout;
+import android.text.Spannable;
+import android.text.method.MovementMethod;
+import android.util.Log;
+import android.util.PrintWriterPrinter;
+import android.util.Printer;
+import android.view.KeyEvent;
+import android.view.LayoutInflater;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewTreeObserver;
+import android.view.Window;
+import android.view.WindowManager;
+import android.view.inputmethod.CompletionInfo;
+import android.view.inputmethod.ExtractedText;
+import android.view.inputmethod.ExtractedTextRequest;
+import android.view.inputmethod.InputBinding;
+import android.view.inputmethod.InputConnection;
+import android.view.inputmethod.InputMethod;
+import android.view.inputmethod.InputMethodManager;
+import android.view.inputmethod.EditorInfo;
+import android.widget.Button;
+import android.widget.FrameLayout;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+
+/**
+ * InputMethodService provides a standard implementation of an InputMethod,
+ * which final implementations can derive from and customize.  See the
+ * base class {@link AbstractInputMethodService} and the {@link InputMethod}
+ * interface for more information on the basics of writing input methods.
+ * 
+ * <p>In addition to the normal Service lifecycle methods, this class
+ * introduces some new specific callbacks that most subclasses will want
+ * to make use of:</p>
+ * <ul>
+ * <li> {@link #onInitializeInterface()} for user-interface initialization,
+ * in particular to deal with configuration changes while the service is
+ * running.
+ * <li> {@link #onBindInput} to find out about switching to a new client.
+ * <li> {@link #onStartInput} to deal with an input session starting with
+ * the client.
+ * <li> {@link #onCreateInputView()}, {@link #onCreateCandidatesView()},
+ * and {@link #onCreateExtractTextView()} for non-demand generation of the UI.
+ * <li> {@link #onStartInputView(EditorInfo, boolean)} to deal with input
+ * starting within the input area of the IME.
+ * </ul>
+ * 
+ * <p>An input method has significant discretion in how it goes about its
+ * work: the {@link android.inputmethodservice.InputMethodService} provides
+ * a basic framework for standard UI elements (input view, candidates view,
+ * and running in fullscreen mode), but it is up to a particular implementor
+ * to decide how to use them.  For example, one input method could implement
+ * an input area with a keyboard, another could allow the user to draw text,
+ * while a third could have no input area (and thus not be visible to the
+ * user) but instead listen to audio and perform text to speech conversion.</p>
+ * 
+ * <p>In the implementation provided here, all of these elements are placed
+ * together in a single window managed by the InputMethodService.  It will
+ * execute callbacks as it needs information about them, and provides APIs for
+ * programmatic control over them.  They layout of these elements is explicitly
+ * defined:</p>
+ * 
+ * <ul>
+ * <li>The soft input view, if available, is placed at the bottom of the
+ * screen.
+ * <li>The candidates view, if currently shown, is placed above the soft
+ * input view.
+ * <li>If not running fullscreen, the application is moved or resized to be
+ * above these views; if running fullscreen, the window will completely cover
+ * the application and its top part will contain the extract text of what is
+ * currently being edited by the application.
+ * </ul>
+ * 
+ * 
+ * <a name="SoftInputView"></a>
+ * <h3>Soft Input View</h3>
+ * 
+ * <p>Central to most input methods is the soft input view.  This is where most
+ * user interaction occurs: pressing on soft keys, drawing characters, or
+ * however else your input method wants to generate text.  Most implementations
+ * will simply have their own view doing all of this work, and return a new
+ * instance of it when {@link #onCreateInputView()} is called.  At that point,
+ * as long as the input view is visible, you will see user interaction in
+ * that view and can call back on the InputMethodService to interact with the
+ * application as appropriate.</p>
+ * 
+ * <p>There are some situations where you want to decide whether or not your
+ * soft input view should be shown to the user.  This is done by implementing
+ * the {@link #onEvaluateInputViewShown()} to return true or false based on
+ * whether it should be shown in the current environment.  If any of your
+ * state has changed that may impact this, call
+ * {@link #updateInputViewShown()} to have it re-evaluated.  The default
+ * implementation always shows the input view unless there is a hard
+ * keyboard available, which is the appropriate behavior for most input
+ * methods.</p>
+ * 
+ * 
+ * <a name="CandidatesView"></a>
+ * <h3>Candidates View</h3>
+ * 
+ * <p>Often while the user is generating raw text, an input method wants to
+ * provide them with a list of possible interpretations of that text that can
+ * be selected for use.  This is accomplished with the candidates view, and
+ * like the soft input view you implement {@link #onCreateCandidatesView()}
+ * to instantiate your own view implementing your candidates UI.</p>
+ * 
+ * <p>Management of the candidates view is a little different than the input
+ * view, because the candidates view tends to be more transient, being shown
+ * only when there are possible candidates for the current text being entered
+ * by the user.  To control whether the candidates view is shown, you use
+ * {@link #setCandidatesViewShown(boolean)}.  Note that because the candidate
+ * view tends to be shown and hidden a lot, it does not impact the application
+ * UI in the same way as the soft input view: it will never cause application
+ * windows to resize, only cause them to be panned if needed for the user to
+ * see the current focus.</p>
+ * 
+ * 
+ * <a name="FullscreenMode"></a>
+ * <h3>Fullscreen Mode</h3>
+ * 
+ * <p>Sometimes your input method UI is too large to integrate with the
+ * application UI, so you just want to take over the screen.  This is
+ * accomplished by switching to full-screen mode, causing the input method
+ * window to fill the entire screen and add its own "extracted text" editor
+ * showing the user the text that is being typed.  Unlike the other UI elements,
+ * there is a standard implementation for the extract editor that you should
+ * not need to change.  The editor is placed at the top of the IME, above the
+ * input and candidates views.</p>
+ * 
+ * <p>Similar to the input view, you control whether the IME is running in
+ * fullscreen mode by implementing {@link #onEvaluateFullscreenMode()}
+ * to return true or false based on
+ * whether it should be fullscreen in the current environment.  If any of your
+ * state has changed that may impact this, call
+ * {@link #updateFullscreenMode()} to have it re-evaluated.  The default
+ * implementation selects fullscreen mode when the screen is in a landscape
+ * orientation, which is appropriate behavior for most input methods that have
+ * a significant input area.</p>
+ * 
+ * <p>When in fullscreen mode, you have some special requirements because the
+ * user can not see the application UI.  In particular, you should implement
+ * {@link #onDisplayCompletions(CompletionInfo[])} to show completions
+ * generated by your application, typically in your candidates view like you
+ * would normally show candidates.
+ * 
+ * 
+ * <a name="GeneratingText"></a>
+ * <h3>Generating Text</h3>
+ * 
+ * <p>The key part of an IME is of course generating text for the application.
+ * This is done through calls to the
+ * {@link android.view.inputmethod.InputConnection} interface to the
+ * application, which can be retrieved from {@link #getCurrentInputConnection()}.
+ * This interface allows you to generate raw key events or, if the target
+ * supports it, directly edit in strings of candidates and committed text.</p>
+ * 
+ * <p>Information about what the target is expected and supports can be found
+ * through the {@link android.view.inputmethod.EditorInfo} class, which is
+ * retrieved with {@link #getCurrentInputEditorInfo()} method.  The most
+ * important part of this is {@link android.view.inputmethod.EditorInfo#inputType
+ * EditorInfo.inputType}; in particular, if this is
+ * {@link android.view.inputmethod.EditorInfo#TYPE_NULL EditorInfo.TYPE_NULL},
+ * then the target does not support complex edits and you need to only deliver
+ * raw key events to it.  An input method will also want to look at other
+ * values here, to for example detect password mode, auto complete text views,
+ * phone number entry, etc.</p>
+ * 
+ * <p>When the user switches between input targets, you will receive calls to
+ * {@link #onFinishInput()} and {@link #onStartInput(EditorInfo, boolean)}.
+ * You can use these to reset and initialize your input state for the current
+ * target.  For example, you will often want to clear any input state, and
+ * update a soft keyboard to be appropriate for the new inputType.</p>
+ */
+public class InputMethodService extends AbstractInputMethodService {
+    static final String TAG = "InputMethodService";
+    static final boolean DEBUG = false;
+    
+    InputMethodManager mImm;
+    
+    LayoutInflater mInflater;
+    View mRootView;
+    SoftInputWindow mWindow;
+    boolean mInitialized;
+    boolean mWindowCreated;
+    boolean mWindowAdded;
+    boolean mWindowVisible;
+    FrameLayout mExtractFrame;
+    FrameLayout mCandidatesFrame;
+    FrameLayout mInputFrame;
+    
+    IBinder mToken;
+    
+    InputBinding mInputBinding;
+    InputConnection mInputConnection;
+    boolean mInputStarted;
+    boolean mInputViewStarted;
+    boolean mCandidatesViewStarted;
+    InputConnection mStartedInputConnection;
+    EditorInfo mInputEditorInfo;
+    
+    int mShowInputFlags;
+    boolean mShowInputRequested;
+    boolean mLastShowInputRequested;
+    int mCandidatesVisibility;
+    CompletionInfo[] mCurCompletions;
+    
+    boolean mShowInputForced;
+    
+    boolean mFullscreenApplied;
+    boolean mIsFullscreen;
+    View mExtractView;
+    ExtractEditText mExtractEditText;
+    ViewGroup mExtractAccessories;
+    Button mExtractAction;
+    ExtractedText mExtractedText;
+    int mExtractedToken;
+    
+    View mInputView;
+    boolean mIsInputViewShown;
+    
+    int mStatusIcon;
+
+    final Insets mTmpInsets = new Insets();
+    final int[] mTmpLocation = new int[2];
+    
+    final ViewTreeObserver.OnComputeInternalInsetsListener mInsetsComputer =
+            new ViewTreeObserver.OnComputeInternalInsetsListener() {
+        public void onComputeInternalInsets(ViewTreeObserver.InternalInsetsInfo info) {
+            if (isFullscreenMode()) {
+                // In fullscreen mode, we just say the window isn't covering
+                // any content so we don't impact whatever is behind.
+                View decor = getWindow().getWindow().getDecorView();
+                info.contentInsets.top = info.visibleInsets.top
+                        = decor.getHeight();
+                info.setTouchableInsets(ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_FRAME);
+            } else {
+                onComputeInsets(mTmpInsets);
+                info.contentInsets.top = mTmpInsets.contentTopInsets;
+                info.visibleInsets.top = mTmpInsets.visibleTopInsets;
+                info.setTouchableInsets(mTmpInsets.touchableInsets);
+            }
+        }
+    };
+
+    final View.OnClickListener mActionClickListener = new View.OnClickListener() {
+        public void onClick(View v) {
+            final EditorInfo ei = getCurrentInputEditorInfo();
+            final InputConnection ic = getCurrentInputConnection();
+            if (ei != null && ic != null) {
+                if (ei.actionId != 0) {
+                    ic.performEditorAction(ei.actionId);
+                } else if ((ei.imeOptions&EditorInfo.IME_MASK_ACTION)
+                        != EditorInfo.IME_ACTION_NONE) {
+                    ic.performEditorAction(ei.imeOptions&EditorInfo.IME_MASK_ACTION);
+                }
+            }
+        }
+    };
+    
+    /**
+     * Concrete implementation of
+     * {@link AbstractInputMethodService.AbstractInputMethodImpl} that provides
+     * all of the standard behavior for an input method.
+     */
+    public class InputMethodImpl extends AbstractInputMethodImpl {
+        /**
+         * Take care of attaching the given window token provided by the system.
+         */
+        public void attachToken(IBinder token) {
+            if (mToken == null) {
+                mToken = token;
+                mWindow.setToken(token);
+            }
+        }
+        
+        /**
+         * Handle a new input binding, calling
+         * {@link InputMethodService#onBindInput InputMethodService.onBindInput()}
+         * when done.
+         */
+        public void bindInput(InputBinding binding) {
+            mInputBinding = binding;
+            mInputConnection = binding.getConnection();
+            if (DEBUG) Log.v(TAG, "bindInput(): binding=" + binding
+                    + " ic=" + mInputConnection);
+            InputConnection ic = getCurrentInputConnection();
+            if (ic != null) ic.reportFullscreenMode(mIsFullscreen);
+            initialize();
+            onBindInput();
+        }
+
+        /**
+         * Clear the current input binding.
+         */
+        public void unbindInput() {
+            if (DEBUG) Log.v(TAG, "unbindInput(): binding=" + mInputBinding
+                    + " ic=" + mInputConnection);
+            onUnbindInput();
+            mInputStarted = false;
+            mInputBinding = null;
+            mInputConnection = null;
+        }
+
+        public void startInput(InputConnection ic, EditorInfo attribute) {
+            if (DEBUG) Log.v(TAG, "startInput(): editor=" + attribute);
+            doStartInput(ic, attribute, false);
+        }
+
+        public void restartInput(InputConnection ic, EditorInfo attribute) {
+            if (DEBUG) Log.v(TAG, "restartInput(): editor=" + attribute);
+            doStartInput(ic, attribute, true);
+        }
+
+        /**
+         * Handle a request by the system to hide the soft input area.
+         */
+        public void hideSoftInput() {
+            if (DEBUG) Log.v(TAG, "hideSoftInput()");
+            mShowInputFlags = 0;
+            mShowInputRequested = false;
+            mShowInputForced = false;
+            hideWindow();
+        }
+
+        /**
+         * Handle a request by the system to show the soft input area.
+         */
+        public void showSoftInput(int flags) {
+            if (DEBUG) Log.v(TAG, "showSoftInput()");
+            mShowInputFlags = 0;
+            if (onShowInputRequested(flags, false)) {
+                showWindow(true);
+            }
+        }
+    }
+    
+    /**
+     * Concrete implementation of
+     * {@link AbstractInputMethodService.AbstractInputMethodSessionImpl} that provides
+     * all of the standard behavior for an input method session.
+     */
+    public class InputMethodSessionImpl extends AbstractInputMethodSessionImpl {
+        public void finishInput() {
+            if (!isEnabled()) {
+                return;
+            }
+            if (DEBUG) Log.v(TAG, "finishInput() in " + this);
+            doFinishInput();
+        }
+
+        /**
+         * Call {@link InputMethodService#onDisplayCompletions
+         * InputMethodService.onDisplayCompletions()}.
+         */
+        public void displayCompletions(CompletionInfo[] completions) {
+            if (!isEnabled()) {
+                return;
+            }
+            mCurCompletions = completions;
+            onDisplayCompletions(completions);
+        }
+        
+        /**
+         * Call {@link InputMethodService#onUpdateExtractedText
+         * InputMethodService.onUpdateExtractedText()}.
+         */
+        public void updateExtractedText(int token, ExtractedText text) {
+            if (!isEnabled()) {
+                return;
+            }
+            onUpdateExtractedText(token, text);
+        }
+        
+        /**
+         * Call {@link InputMethodService#onUpdateSelection
+         * InputMethodService.onUpdateSelection()}.
+         */
+        public void updateSelection(int oldSelStart, int oldSelEnd,
+                int newSelStart, int newSelEnd,
+                int candidatesStart, int candidatesEnd) {
+            if (!isEnabled()) {
+                return;
+            }
+            InputMethodService.this.onUpdateSelection(oldSelStart, oldSelEnd,
+                    newSelStart, newSelEnd, candidatesStart, candidatesEnd);
+        }
+        
+        /**
+         * Call {@link InputMethodService#onUpdateCursor
+         * InputMethodService.onUpdateCursor()}.
+         */
+        public void updateCursor(Rect newCursor) {
+            if (!isEnabled()) {
+                return;
+            }
+            InputMethodService.this.onUpdateCursor(newCursor);
+        }
+        
+        /**
+         * Call {@link InputMethodService#onAppPrivateCommand
+         * InputMethodService.onAppPrivateCommand()}.
+         */
+        public void appPrivateCommand(String action, Bundle data) {
+            if (!isEnabled()) {
+                return;
+            }
+            InputMethodService.this.onAppPrivateCommand(action, data);
+        }
+    }
+    
+    /**
+     * Information about where interesting parts of the input method UI appear.
+     */
+    public static final class Insets {
+        /**
+         * This is the top part of the UI that is the main content.  It is
+         * used to determine the basic space needed, to resize/pan the
+         * application behind.  It is assumed that this inset does not
+         * change very much, since any change will cause a full resize/pan
+         * of the application behind.  This value is relative to the top edge
+         * of the input method window.
+         */
+        public int contentTopInsets;
+        
+        /**
+         * This is the top part of the UI that is visibly covering the
+         * application behind it.  This provides finer-grained control over
+         * visibility, allowing you to change it relatively frequently (such
+         * as hiding or showing candidates) without disrupting the underlying
+         * UI too much.  For example, this will never resize the application
+         * UI, will only pan if needed to make the current focus visible, and
+         * will not aggressively move the pan position when this changes unless
+         * needed to make the focus visible.  This value is relative to the top edge
+         * of the input method window.
+         */
+        public int visibleTopInsets;
+        
+        /**
+         * Option for {@link #touchableInsets}: the entire window frame
+         * can be touched.
+         */
+        public static final int TOUCHABLE_INSETS_FRAME
+                = ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_FRAME;
+        
+        /**
+         * Option for {@link #touchableInsets}: the area inside of
+         * the content insets can be touched.
+         */
+        public static final int TOUCHABLE_INSETS_CONTENT
+                = ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_CONTENT;
+        
+        /**
+         * Option for {@link #touchableInsets}: the area inside of
+         * the visible insets can be touched.
+         */
+        public static final int TOUCHABLE_INSETS_VISIBLE
+                = ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_VISIBLE;
+        
+        /**
+         * Determine which area of the window is touchable by the user.  May
+         * be one of: {@link #TOUCHABLE_INSETS_FRAME},
+         * {@link #TOUCHABLE_INSETS_CONTENT}, or {@link #TOUCHABLE_INSETS_VISIBLE}. 
+         */
+        public int touchableInsets;
+    }
+    
+    @Override public void onCreate() {
+        super.onCreate();
+        mImm = (InputMethodManager)getSystemService(INPUT_METHOD_SERVICE);
+        mInflater = (LayoutInflater)getSystemService(
+                Context.LAYOUT_INFLATER_SERVICE);
+        mWindow = new SoftInputWindow(this);
+        initViews();
+        mWindow.getWindow().setLayout(FILL_PARENT, WRAP_CONTENT);
+    }
+    
+    /**
+     * This is a hook that subclasses can use to perform initialization of
+     * their interface.  It is called for you prior to any of your UI objects
+     * being created, both after the service is first created and after a
+     * configuration change happens.
+     */
+    public void onInitializeInterface() {
+    }
+    
+    void initialize() {
+        if (!mInitialized) {
+            mInitialized = true;
+            onInitializeInterface();
+        }
+    }
+    
+    void initViews() {
+        mInitialized = false;
+        mWindowCreated = false;
+        mShowInputRequested = false;
+        mShowInputForced = false;
+        
+        mRootView = mInflater.inflate(
+                com.android.internal.R.layout.input_method, null);
+        mWindow.setContentView(mRootView);
+        mRootView.getViewTreeObserver().addOnComputeInternalInsetsListener(mInsetsComputer);
+        if (Settings.System.getInt(getContentResolver(),
+                Settings.System.FANCY_IME_ANIMATIONS, 0) != 0) {
+            mWindow.getWindow().setWindowAnimations(
+                    com.android.internal.R.style.Animation_InputMethodFancy);
+        }
+        mExtractFrame = (FrameLayout)mRootView.findViewById(android.R.id.extractArea);
+        mExtractView = null;
+        mExtractEditText = null;
+        mExtractAccessories = null;
+        mExtractAction = null;
+        mFullscreenApplied = false;
+        
+        mCandidatesFrame = (FrameLayout)mRootView.findViewById(android.R.id.candidatesArea);
+        mInputFrame = (FrameLayout)mRootView.findViewById(android.R.id.inputArea);
+        mInputView = null;
+        mIsInputViewShown = false;
+        
+        mExtractFrame.setVisibility(View.GONE);
+        mCandidatesVisibility = getCandidatesHiddenVisibility();
+        mCandidatesFrame.setVisibility(mCandidatesVisibility);
+        mInputFrame.setVisibility(View.GONE);
+    }
+    
+    @Override public void onDestroy() {
+        super.onDestroy();
+        mRootView.getViewTreeObserver().removeOnComputeInternalInsetsListener(
+                mInsetsComputer);
+        if (mWindowAdded) {
+            mWindow.dismiss();
+        }
+    }
+    
+    /**
+     * Take care of handling configuration changes.  Subclasses of
+     * InputMethodService generally don't need to deal directly with
+     * this on their own; the standard implementation here takes care of
+     * regenerating the input method UI as a result of the configuration
+     * change, so you can rely on your {@link #onCreateInputView} and
+     * other methods being called as appropriate due to a configuration change.
+     * 
+     * <p>When a configuration change does happen,
+     * {@link #onInitializeInterface()} is guaranteed to be called the next
+     * time prior to any of the other input or UI creation callbacks.  The
+     * following will be called immediately depending if appropriate for current 
+     * state: {@link #onStartInput} if input is active, and
+     * {@link #onCreateInputView} and {@link #onStartInputView} and related
+     * appropriate functions if the UI is displayed.
+     */
+    @Override public void onConfigurationChanged(Configuration newConfig) {
+        super.onConfigurationChanged(newConfig);
+        
+        boolean visible = mWindowVisible;
+        int showFlags = mShowInputFlags;
+        boolean showingInput = mShowInputRequested;
+        CompletionInfo[] completions = mCurCompletions;
+        initViews();
+        mInputViewStarted = false;
+        mCandidatesViewStarted = false;
+        if (mInputStarted) {
+            doStartInput(getCurrentInputConnection(),
+                    getCurrentInputEditorInfo(), true);
+        }
+        if (visible) {
+            if (showingInput) {
+                // If we were last showing the soft keyboard, try to do so again.
+                if (onShowInputRequested(showFlags, true)) {
+                    showWindow(true);
+                    if (completions != null) {
+                        mCurCompletions = completions;
+                        onDisplayCompletions(completions);
+                    }
+                } else {
+                    hideWindow();
+                }
+            } else if (mCandidatesVisibility == View.VISIBLE) {
+                // If the candidates are currently visible, make sure the
+                // window is shown for them.
+                showWindow(false);
+            } else {
+                // Otherwise hide the window.
+                hideWindow();
+            }
+        }
+    }
+
+    /**
+     * Implement to return our standard {@link InputMethodImpl}.  Subclasses
+     * can override to provide their own customized version.
+     */
+    public AbstractInputMethodImpl onCreateInputMethodInterface() {
+        return new InputMethodImpl();
+    }
+    
+    /**
+     * Implement to return our standard {@link InputMethodSessionImpl}.  Subclasses
+     * can override to provide their own customized version.
+     */
+    public AbstractInputMethodSessionImpl onCreateInputMethodSessionInterface() {
+        return new InputMethodSessionImpl();
+    }
+    
+    public LayoutInflater getLayoutInflater() {
+        return mInflater;
+    }
+    
+    public Dialog getWindow() {
+        return mWindow;
+    }
+    
+    /**
+     * Return the maximum width, in pixels, available the input method.
+     * Input methods are positioned at the bottom of the screen and, unless
+     * running in fullscreen, will generally want to be as short as possible
+     * so should compute their height based on their contents.  However, they
+     * can stretch as much as needed horizontally.  The function returns to
+     * you the maximum amount of space available horizontally, which you can
+     * use if needed for UI placement.
+     * 
+     * <p>In many cases this is not needed, you can just rely on the normal
+     * view layout mechanisms to position your views within the full horizontal
+     * space given to the input method.
+     * 
+     * <p>Note that this value can change dynamically, in particular when the
+     * screen orientation changes.
+     */
+    public int getMaxWidth() {
+        WindowManager wm = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
+        return wm.getDefaultDisplay().getWidth();
+    }
+    
+    /**
+     * Return the currently active InputBinding for the input method, or
+     * null if there is none.
+     */
+    public InputBinding getCurrentInputBinding() {
+        return mInputBinding;
+    }
+    
+    /**
+     * Retrieve the currently active InputConnection that is bound to
+     * the input method, or null if there is none.
+     */
+    public InputConnection getCurrentInputConnection() {
+        InputConnection ic = mStartedInputConnection;
+        if (ic != null) {
+            return ic;
+        }
+        return mInputConnection;
+    }
+    
+    public boolean getCurrentInputStarted() {
+        return mInputStarted;
+    }
+    
+    public EditorInfo getCurrentInputEditorInfo() {
+        return mInputEditorInfo;
+    }
+    
+    /**
+     * Re-evaluate whether the input method should be running in fullscreen
+     * mode, and update its UI if this has changed since the last time it
+     * was evaluated.  This will call {@link #onEvaluateFullscreenMode()} to
+     * determine whether it should currently run in fullscreen mode.  You
+     * can use {@link #isFullscreenMode()} to determine if the input method
+     * is currently running in fullscreen mode.
+     */
+    public void updateFullscreenMode() {
+        boolean isFullscreen = mShowInputRequested && onEvaluateFullscreenMode();
+        boolean changed = mLastShowInputRequested != mShowInputRequested;
+        if (mIsFullscreen != isFullscreen || !mFullscreenApplied) {
+            changed = true;
+            mIsFullscreen = isFullscreen;
+            InputConnection ic = getCurrentInputConnection();
+            if (ic != null) ic.reportFullscreenMode(isFullscreen);
+            mFullscreenApplied = true;
+            initialize();
+            Drawable bg = onCreateBackgroundDrawable();
+            if (bg == null) {
+                // We need to give the window a real drawable, so that it
+                // correctly sets its mode.
+                bg = getResources().getDrawable(android.R.color.transparent);
+            }
+            mWindow.getWindow().setBackgroundDrawable(bg);
+            mExtractFrame.setVisibility(isFullscreen ? View.VISIBLE : View.GONE);
+            if (isFullscreen) {
+                if (mExtractView == null) {
+                    View v = onCreateExtractTextView();
+                    if (v != null) {
+                        setExtractView(v);
+                    }
+                }
+                startExtractingText(false);
+            }
+        }
+        
+        if (changed) {
+            onConfigureWindow(mWindow.getWindow(), isFullscreen,
+                    !mShowInputRequested);
+            mLastShowInputRequested = mShowInputRequested;
+        }
+    }
+    
+    /**
+     * Update the given window's parameters for the given mode.  This is called
+     * when the window is first displayed and each time the fullscreen or
+     * candidates only mode changes.
+     * 
+     * <p>The default implementation makes the layout for the window
+     * FILL_PARENT x FILL_PARENT when in fullscreen mode, and
+     * FILL_PARENT x WRAP_CONTENT when in non-fullscreen mode.
+     * 
+     * @param win The input method's window.
+     * @param isFullscreen If true, the window is running in fullscreen mode
+     * and intended to cover the entire application display.
+     * @param isCandidatesOnly If true, the window is only showing the
+     * candidates view and none of the rest of its UI.  This is mutually
+     * exclusive with fullscreen mode.
+     */
+    public void onConfigureWindow(Window win, boolean isFullscreen,
+            boolean isCandidatesOnly) {
+        if (isFullscreen) {
+            mWindow.getWindow().setLayout(FILL_PARENT, FILL_PARENT);
+        } else {
+            mWindow.getWindow().setLayout(FILL_PARENT, WRAP_CONTENT);
+        }
+    }
+    
+    /**
+     * Return whether the input method is <em>currently</em> running in
+     * fullscreen mode.  This is the mode that was last determined and
+     * applied by {@link #updateFullscreenMode()}.
+     */
+    public boolean isFullscreenMode() {
+        return mIsFullscreen;
+    }
+    
+    /**
+     * Override this to control when the input method should run in
+     * fullscreen mode.  The default implementation runs in fullsceen only
+     * when the screen is in landscape mode.  If you change what
+     * this returns, you will need to call {@link #updateFullscreenMode()}
+     * yourself whenever the returned value may have changed to have it
+     * re-evaluated and applied.
+     */
+    public boolean onEvaluateFullscreenMode() {
+        Configuration config = getResources().getConfiguration();
+        return config.orientation == Configuration.ORIENTATION_LANDSCAPE;
+    }
+    
+    /**
+     * Compute the interesting insets into your UI.  The default implementation
+     * uses the top of the candidates frame for the visible insets, and the
+     * top of the input frame for the content insets.  The default touchable
+     * insets are {@link Insets#TOUCHABLE_INSETS_VISIBLE}.
+     * 
+     * <p>Note that this method is not called when in fullscreen mode, since
+     * in that case the application is left as-is behind the input method and
+     * not impacted by anything in its UI.
+     * 
+     * @param outInsets Fill in with the current UI insets.
+     */
+    public void onComputeInsets(Insets outInsets) {
+        int[] loc = mTmpLocation;
+        if (mInputFrame.getVisibility() == View.VISIBLE) {
+            mInputFrame.getLocationInWindow(loc);
+        } else {
+            loc[1] = 0;
+        }
+        outInsets.contentTopInsets = loc[1];
+        if (mCandidatesFrame.getVisibility() == View.VISIBLE) {
+            mCandidatesFrame.getLocationInWindow(loc);
+        }
+        outInsets.visibleTopInsets = loc[1];
+        outInsets.touchableInsets = Insets.TOUCHABLE_INSETS_VISIBLE;
+    }
+    
+    /**
+     * Re-evaluate whether the soft input area should currently be shown, and
+     * update its UI if this has changed since the last time it
+     * was evaluated.  This will call {@link #onEvaluateInputViewShown()} to
+     * determine whether the input view should currently be shown.  You
+     * can use {@link #isInputViewShown()} to determine if the input view
+     * is currently shown.
+     */
+    public void updateInputViewShown() {
+        boolean isShown = mShowInputRequested && onEvaluateInputViewShown();
+        if (mIsInputViewShown != isShown && mWindowVisible) {
+            mIsInputViewShown = isShown;
+            mInputFrame.setVisibility(isShown ? View.VISIBLE : View.GONE);
+            if (mInputView == null) {
+                initialize();
+                View v = onCreateInputView();
+                if (v != null) {
+                    setInputView(v);
+                }
+            }
+        }
+    }
+    
+    /**
+     * Returns true if we have been asked to show our input view.
+     */
+    public boolean isShowInputRequested() {
+        return mShowInputRequested;
+    }
+    
+    /**
+     * Return whether the soft input view is <em>currently</em> shown to the
+     * user.  This is the state that was last determined and
+     * applied by {@link #updateInputViewShown()}.
+     */
+    public boolean isInputViewShown() {
+        return mIsInputViewShown && mWindowVisible;
+    }
+    
+    /**
+     * Override this to control when the soft input area should be shown to
+     * the user.  The default implementation only shows the input view when
+     * there is no hard keyboard or the keyboard is hidden.  If you change what
+     * this returns, you will need to call {@link #updateInputViewShown()}
+     * yourself whenever the returned value may have changed to have it
+     * re-evalauted and applied.
+     */
+    public boolean onEvaluateInputViewShown() {
+        Configuration config = getResources().getConfiguration();
+        return config.keyboard == Configuration.KEYBOARD_NOKEYS
+                || config.hardKeyboardHidden == Configuration.KEYBOARDHIDDEN_YES;
+    }
+    
+    /**
+     * Controls the visibility of the candidates display area.  By default
+     * it is hidden.
+     */
+    public void setCandidatesViewShown(boolean shown) {
+        int vis = shown ? View.VISIBLE : getCandidatesHiddenVisibility();
+        if (mCandidatesVisibility != vis) {
+            mCandidatesFrame.setVisibility(vis);
+            mCandidatesVisibility = vis;
+        }
+        if (!mShowInputRequested && mWindowVisible != shown) {
+            // If we are being asked to show the candidates view while the app
+            // has not asked for the input view to be shown, then we need
+            // to update whether the window is shown.
+            if (shown) {
+                showWindow(false);
+            } else {
+                hideWindow();
+            }
+        }
+    }
+    
+    /**
+     * Returns the visibility mode (either {@link View#INVISIBLE View.INVISIBLE}
+     * or {@link View#GONE View.GONE}) of the candidates view when it is not
+     * shown.  The default implementation returns GONE when in fullscreen mode,
+     * otherwise VISIBLE.  Be careful if you change this to return GONE in
+     * other situations -- if showing or hiding the candidates view causes
+     * your window to resize, this can cause temporary drawing artifacts as
+     * the resize takes place.
+     */
+    public int getCandidatesHiddenVisibility() {
+        return isFullscreenMode() ? View.GONE : View.INVISIBLE;
+    }
+    
+    public void showStatusIcon(int iconResId) {
+        mStatusIcon = iconResId;
+        mImm.showStatusIcon(mToken, getPackageName(), iconResId);
+    }
+    
+    public void hideStatusIcon() {
+        mStatusIcon = 0;
+        mImm.hideStatusIcon(mToken);
+    }
+    
+    /**
+     * Force switch to a new input method, as identified by <var>id</var>.  This
+     * input method will be destroyed, and the requested one started on the
+     * current input field.
+     * 
+     * @param id Unique identifier of the new input method ot start.
+     */
+    public void switchInputMethod(String id) {
+        mImm.setInputMethod(mToken, id);
+    }
+    
+    public void setExtractView(View view) {
+        mExtractFrame.removeAllViews();
+        mExtractFrame.addView(view, new FrameLayout.LayoutParams(
+                ViewGroup.LayoutParams.FILL_PARENT,
+                ViewGroup.LayoutParams.FILL_PARENT));
+        mExtractView = view;
+        if (view != null) {
+            mExtractEditText = (ExtractEditText)view.findViewById(
+                    com.android.internal.R.id.inputExtractEditText);
+            mExtractEditText.setIME(this);
+            mExtractAction = (Button)view.findViewById(
+                    com.android.internal.R.id.inputExtractAction);
+            if (mExtractAction != null) {
+                mExtractAccessories = (ViewGroup)view.findViewById(
+                        com.android.internal.R.id.inputExtractAccessories);
+            }
+            startExtractingText(false);
+        } else {
+            mExtractEditText = null;
+            mExtractAccessories = null;
+            mExtractAction = null;
+        }
+    }
+    
+    /**
+     * Replaces the current candidates view with a new one.  You only need to
+     * call this when dynamically changing the view; normally, you should
+     * implement {@link #onCreateCandidatesView()} and create your view when
+     * first needed by the input method.
+     */
+    public void setCandidatesView(View view) {
+        mCandidatesFrame.removeAllViews();
+        mCandidatesFrame.addView(view, new FrameLayout.LayoutParams(
+                ViewGroup.LayoutParams.FILL_PARENT,
+                ViewGroup.LayoutParams.WRAP_CONTENT));
+    }
+    
+    /**
+     * Replaces the current input view with a new one.  You only need to
+     * call this when dynamically changing the view; normally, you should
+     * implement {@link #onCreateInputView()} and create your view when
+     * first needed by the input method.
+     */
+    public void setInputView(View view) {
+        mInputFrame.removeAllViews();
+        mInputFrame.addView(view, new FrameLayout.LayoutParams(
+                ViewGroup.LayoutParams.FILL_PARENT,
+                ViewGroup.LayoutParams.WRAP_CONTENT));
+        mInputView = view;
+    }
+    
+    /**
+     * Called by the framework to create a Drawable for the background of
+     * the input method window.  May return null for no background.  The default
+     * implementation returns a non-null standard background only when in
+     * fullscreen mode.  This is called each time the fullscreen mode changes.
+     */
+    public Drawable onCreateBackgroundDrawable() {
+        if (isFullscreenMode()) {
+            return getResources().getDrawable(
+                    com.android.internal.R.drawable.input_method_fullscreen_background);
+        }
+        return null;
+    }
+    
+    /**
+     * Called by the framework to create the layout for showing extacted text.
+     * Only called when in fullscreen mode.  The returned view hierarchy must
+     * have an {@link ExtractEditText} whose ID is 
+     * {@link android.R.id#inputExtractEditText}.
+     */
+    public View onCreateExtractTextView() {
+        return mInflater.inflate(
+                com.android.internal.R.layout.input_method_extract_view, null);
+    }
+    
+    /**
+     * Create and return the view hierarchy used to show candidates.  This will
+     * be called once, when the candidates are first displayed.  You can return
+     * null to have no candidates view; the default implementation returns null.
+     * 
+     * <p>To control when the candidates view is displayed, use
+     * {@link #setCandidatesViewShown(boolean)}.
+     * To change the candidates view after the first one is created by this
+     * function, use {@link #setCandidatesView(View)}.
+     */
+    public View onCreateCandidatesView() {
+        return null;
+    }
+    
+    /**
+     * Create and return the view hierarchy used for the input area (such as
+     * a soft keyboard).  This will be called once, when the input area is
+     * first displayed.  You can return null to have no input area; the default
+     * implementation returns null.
+     * 
+     * <p>To control when the input view is displayed, implement
+     * {@link #onEvaluateInputViewShown()}.
+     * To change the input view after the first one is created by this
+     * function, use {@link #setInputView(View)}.
+     */
+    public View onCreateInputView() {
+        return null;
+    }
+    
+    /**
+     * Called when the input view is being shown and input has started on
+     * a new editor.  This will always be called after {@link #onStartInput},
+     * allowing you to do your general setup there and just view-specific
+     * setup here.  You are guaranteed that {@link #onCreateInputView()} will
+     * have been called some time before this function is called.
+     * 
+     * @param info Description of the type of text being edited.
+     * @param restarting Set to true if we are restarting input on the
+     * same text field as before.
+     */
+    public void onStartInputView(EditorInfo info, boolean restarting) {
+    }
+    
+    /**
+     * Called when the input view is being hidden from the user.  This will
+     * be called either prior to hiding the window, or prior to switching to
+     * another target for editing.
+     * 
+     * <p>The default
+     * implementation uses the InputConnection to clear any active composing
+     * text; you can override this (not calling the base class implementation)
+     * to perform whatever behavior you would like.
+     * 
+     * @boolean finishingInput If true, {@link #onFinishInput} will be
+     * called immediately after.
+     */
+    public void onFinishInputView(boolean finishingInput) {
+        if (!finishingInput) {
+            InputConnection ic = getCurrentInputConnection();
+            if (ic != null) {
+                ic.finishComposingText();
+            }
+        }
+    }
+    
+    /**
+     * Called when only the candidates view has been shown for showing
+     * processing as the user enters text through a hard keyboard.
+     * This will always be called after {@link #onStartInput},
+     * allowing you to do your general setup there and just view-specific
+     * setup here.  You are guaranteed that {@link #onCreateCandidatesView()}
+     * will have been called some time before this function is called.
+     * 
+     * <p>Note that this will <em>not</em> be called when the input method
+     * is running in full editing mode, and thus receiving
+     * {@link #onStartInputView} to initiate that operation.  This is only
+     * for the case when candidates are being shown while the input method
+     * editor is hidden but wants to show its candidates UI as text is
+     * entered through some other mechanism.
+     * 
+     * @param info Description of the type of text being edited.
+     * @param restarting Set to true if we are restarting input on the
+     * same text field as before.
+     */
+    public void onStartCandidatesView(EditorInfo info, boolean restarting) {
+    }
+    
+    /**
+     * Called when the candidates view is being hidden from the user.  This will
+     * be called either prior to hiding the window, or prior to switching to
+     * another target for editing.
+     * 
+     * <p>The default
+     * implementation uses the InputConnection to clear any active composing
+     * text; you can override this (not calling the base class implementation)
+     * to perform whatever behavior you would like.
+     * 
+     * @boolean finishingInput If true, {@link #onFinishInput} will be
+     * called immediately after.
+     */
+    public void onFinishCandidatesView(boolean finishingInput) {
+        if (!finishingInput) {
+            InputConnection ic = getCurrentInputConnection();
+            if (ic != null) {
+                ic.finishComposingText();
+            }
+        }
+    }
+    
+    /**
+     * The system has decided that it may be time to show your input method.
+     * This is called due to a corresponding call to your
+     * {@link InputMethod#showSoftInput(int) InputMethod.showSoftInput(int)}
+     * method.  The default implementation uses
+     * {@link #onEvaluateInputViewShown()}, {@link #onEvaluateFullscreenMode()},
+     * and the current configuration to decide whether the input view should
+     * be shown at this point.
+     * 
+     * @param flags Provides additional information about the show request,
+     * as per {@link InputMethod#showSoftInput(int) InputMethod.showSoftInput(int)}.
+     * @param configChange This is true if we are re-showing due to a
+     * configuration change.
+     * @return Returns true to indicate that the window should be shown.
+     */
+    public boolean onShowInputRequested(int flags, boolean configChange) {
+        if (!onEvaluateInputViewShown()) {
+            return false;
+        }
+        if ((flags&InputMethod.SHOW_EXPLICIT) == 0) {
+            if (!configChange && onEvaluateFullscreenMode()) {
+                // Don't show if this is not explicitly requested by the user and
+                // the input method is fullscreen.  That would be too disruptive.
+                // However, we skip this change for a config change, since if
+                // the IME is already shown we do want to go into fullscreen
+                // mode at this point.
+                return false;
+            }
+            Configuration config = getResources().getConfiguration();
+            if (config.keyboard != Configuration.KEYBOARD_NOKEYS) {
+                // And if the device has a hard keyboard, even if it is
+                // currently hidden, don't show the input method implicitly.
+                // These kinds of devices don't need it that much.
+                return false;
+            }
+        }
+        if ((flags&InputMethod.SHOW_FORCED) != 0) {
+            mShowInputForced = true;
+        }
+        return true;
+    }
+    
+    public void showWindow(boolean showInput) {
+        if (DEBUG) Log.v(TAG, "Showing window: showInput=" + showInput
+                + " mShowInputRequested=" + mShowInputRequested
+                + " mWindowAdded=" + mWindowAdded
+                + " mWindowCreated=" + mWindowCreated
+                + " mWindowVisible=" + mWindowVisible
+                + " mInputStarted=" + mInputStarted);
+        boolean doShowInput = false;
+        boolean wasVisible = mWindowVisible;
+        mWindowVisible = true;
+        if (!mShowInputRequested) {
+            if (mInputStarted) {
+                if (showInput) {
+                    doShowInput = true;
+                    mShowInputRequested = true;
+                }
+            }
+        } else {
+            showInput = true;
+        }
+        
+        if (DEBUG) Log.v(TAG, "showWindow: updating UI");
+        initialize();
+        updateFullscreenMode();
+        updateInputViewShown();
+        
+        if (!mWindowAdded || !mWindowCreated) {
+            mWindowAdded = true;
+            mWindowCreated = true;
+            initialize();
+            if (DEBUG) Log.v(TAG, "CALL: onCreateCandidatesView");
+            View v = onCreateCandidatesView();
+            if (DEBUG) Log.v(TAG, "showWindow: candidates=" + v);
+            if (v != null) {
+                setCandidatesView(v);
+            }
+        }
+        if (mShowInputRequested) {
+            if (!mInputViewStarted) {
+                if (DEBUG) Log.v(TAG, "CALL: onStartInputView");
+                mInputViewStarted = true;
+                onStartInputView(mInputEditorInfo, false);
+            }
+        } else if (!mCandidatesViewStarted) {
+            if (DEBUG) Log.v(TAG, "CALL: onStartCandidatesView");
+            mCandidatesViewStarted = true;
+            onStartCandidatesView(mInputEditorInfo, false);
+        }
+        
+        if (doShowInput) {
+            startExtractingText(false);
+        }
+        
+        if (!wasVisible) {
+            if (DEBUG) Log.v(TAG, "showWindow: showing!");
+            onWindowShown();
+            mWindow.show();
+        }
+    }
+    
+    public void hideWindow() {
+        if (mInputViewStarted) {
+            if (DEBUG) Log.v(TAG, "CALL: onFinishInputView");
+            onFinishInputView(false);
+        } else if (mCandidatesViewStarted) {
+            if (DEBUG) Log.v(TAG, "CALL: onFinishCandidatesView");
+            onFinishCandidatesView(false);
+        }
+        mInputViewStarted = false;
+        mCandidatesViewStarted = false;
+        if (mWindowVisible) {
+            mWindow.hide();
+            mWindowVisible = false;
+            onWindowHidden();
+        }
+    }
+    
+    /**
+     * Called when the input method window has been shown to the user, after
+     * previously not being visible.  This is done after all of the UI setup
+     * for the window has occurred (creating its views etc).
+     */
+    public void onWindowShown() {
+    }
+    
+    /**
+     * Called when the input method window has been hidden from the user,
+     * after previously being visible.
+     */
+    public void onWindowHidden() {
+    }
+    
+    /**
+     * Called when a new client has bound to the input method.  This
+     * may be followed by a series of {@link #onStartInput(EditorInfo, boolean)}
+     * and {@link #onFinishInput()} calls as the user navigates through its
+     * UI.  Upon this call you know that {@link #getCurrentInputBinding}
+     * and {@link #getCurrentInputConnection} return valid objects.
+     */
+    public void onBindInput() {
+    }
+    
+    /**
+     * Called when the previous bound client is no longer associated
+     * with the input method.  After returning {@link #getCurrentInputBinding}
+     * and {@link #getCurrentInputConnection} will no longer return
+     * valid objects.
+     */
+    public void onUnbindInput() {
+    }
+    
+    /**
+     * Called to inform the input method that text input has started in an
+     * editor.  You should use this callback to initialize the state of your
+     * input to match the state of the editor given to it.
+     * 
+     * @param attribute The attributes of the editor that input is starting
+     * in.
+     * @param restarting Set to true if input is restarting in the same
+     * editor such as because the application has changed the text in
+     * the editor.  Otherwise will be false, indicating this is a new
+     * session with the editor.
+     */
+    public void onStartInput(EditorInfo attribute, boolean restarting) {
+    }
+    
+    void doFinishInput() {
+        if (mInputViewStarted) {
+            if (DEBUG) Log.v(TAG, "CALL: onFinishInputView");
+            onFinishInputView(true);
+        } else if (mCandidatesViewStarted) {
+            if (DEBUG) Log.v(TAG, "CALL: onFinishCandidatesView");
+            onFinishCandidatesView(true);
+        }
+        mInputViewStarted = false;
+        mCandidatesViewStarted = false;
+        if (mInputStarted) {
+            if (DEBUG) Log.v(TAG, "CALL: onFinishInput");
+            onFinishInput();
+        }
+        mInputStarted = false;
+        mStartedInputConnection = null;
+        mCurCompletions = null;
+    }
+    
+    void doStartInput(InputConnection ic, EditorInfo attribute, boolean restarting) {
+        if (!restarting) {
+            doFinishInput();
+        }
+        mInputStarted = true;
+        mStartedInputConnection = ic;
+        mInputEditorInfo = attribute;
+        initialize();
+        if (DEBUG) Log.v(TAG, "CALL: onStartInput");
+        onStartInput(attribute, restarting);
+        if (mWindowVisible) {
+            if (mShowInputRequested) {
+                if (DEBUG) Log.v(TAG, "CALL: onStartInputView");
+                mInputViewStarted = true;
+                onStartInputView(mInputEditorInfo, restarting);
+                startExtractingText(true);
+            } else if (mCandidatesVisibility == View.VISIBLE) {
+                if (DEBUG) Log.v(TAG, "CALL: onStartCandidatesView");
+                mCandidatesViewStarted = true;
+                onStartCandidatesView(mInputEditorInfo, restarting);
+            }
+        }
+    }
+    
+    /**
+     * Called to inform the input method that text input has finished in
+     * the last editor.  At this point there may be a call to
+     * {@link #onStartInput(EditorInfo, boolean)} to perform input in a
+     * new editor, or the input method may be left idle.  This method is
+     * <em>not</em> called when input restarts in the same editor.
+     * 
+     * <p>The default
+     * implementation uses the InputConnection to clear any active composing
+     * text; you can override this (not calling the base class implementation)
+     * to perform whatever behavior you would like.
+     */
+    public void onFinishInput() {
+        InputConnection ic = getCurrentInputConnection();
+        if (ic != null) {
+            ic.finishComposingText();
+        }
+    }
+    
+    /**
+     * Called when the application has reported auto-completion candidates that
+     * it would like to have the input method displayed.  Typically these are
+     * only used when an input method is running in full-screen mode, since
+     * otherwise the user can see and interact with the pop-up window of
+     * completions shown by the application.
+     * 
+     * <p>The default implementation here does nothing.
+     */
+    public void onDisplayCompletions(CompletionInfo[] completions) {
+    }
+    
+    /**
+     * Called when the application has reported new extracted text to be shown
+     * due to changes in its current text state.  The default implementation
+     * here places the new text in the extract edit text, when the input
+     * method is running in fullscreen mode.
+     */
+    public void onUpdateExtractedText(int token, ExtractedText text) {
+        if (mExtractedToken != token) {
+            return;
+        }
+        if (mExtractEditText != null && text != null) {
+            mExtractedText = text;
+            mExtractEditText.setExtractedText(text);
+        }
+    }
+    
+    /**
+     * Called when the application has reported a new selection region of
+     * the text.  This is called whether or not the input method has requested
+     * extracted text updates, although if so it will not receive this call
+     * if the extracted text has changed as well.
+     * 
+     * <p>The default implementation takes care of updating the cursor in
+     * the extract text, if it is being shown.
+     */
+    public void onUpdateSelection(int oldSelStart, int oldSelEnd,
+            int newSelStart, int newSelEnd,
+            int candidatesStart, int candidatesEnd) {
+        final ExtractEditText eet = mExtractEditText;
+        if (eet != null && mExtractedText != null) {
+            final int off = mExtractedText.startOffset;
+            eet.startInternalChanges();
+            eet.setSelection(newSelStart-off, newSelEnd-off);
+            eet.finishInternalChanges();
+        }
+    }
+
+    /**
+     * Called when the application has reported a new location of its text
+     * cursor.  This is only called if explicitly requested by the input method.
+     * The default implementation does nothing.
+     */
+    public void onUpdateCursor(Rect newCursor) {
+    }
+
+    /**
+     * Close this input method's soft input area, removing it from the display.
+     * The input method will continue running, but the user can no longer use
+     * it to generate input by touching the screen.
+     * @param flags Provides additional operating flags.  Currently may be
+     * 0 or have the {@link InputMethodManager#HIDE_IMPLICIT_ONLY
+     * InputMethodManager.HIDE_IMPLICIT_ONLY} bit set.
+     */
+    public void dismissSoftInput(int flags) {
+        mImm.hideSoftInputFromInputMethod(mToken, flags);
+    }
+    
+    /**
+     * Override this to intercept key down events before they are processed by the
+     * application.  If you return true, the application will not itself
+     * process the event.  If you return true, the normal application processing
+     * will occur as if the IME had not seen the event at all.
+     * 
+     * <p>The default implementation intercepts {@link KeyEvent#KEYCODE_BACK
+     * KeyEvent.KEYCODE_BACK} to hide the current IME UI if it is shown.  In
+     * additional, in fullscreen mode only, it will consume DPAD movement
+     * events to move the cursor in the extracted text view, not allowing
+     * them to perform navigation in the underlying application.
+     */
+    public boolean onKeyDown(int keyCode, KeyEvent event) {
+        if (event.getKeyCode() == KeyEvent.KEYCODE_BACK
+                && event.getRepeatCount() == 0) {
+            if (mShowInputRequested) {
+                // If the soft input area is shown, back closes it and we
+                // consume the back key.
+                dismissSoftInput(0);
+                return true;
+            } else if (mWindowVisible) {
+                if (mCandidatesVisibility == View.VISIBLE) {
+                    // If we are showing candidates even if no input area, then
+                    // hide them.
+                    setCandidatesViewShown(false);
+                    return true;
+                } else {
+                    // If we have the window visible for some other reason --
+                    // most likely to show candidates -- then just get rid
+                    // of it.  This really shouldn't happen, but just in case...
+                    hideWindow();
+                    return true;
+                }
+            }
+        }
+        
+        return doMovementKey(keyCode, event, MOVEMENT_DOWN);
+    }
+
+    /**
+     * Override this to intercept special key multiple events before they are
+     * processed by the
+     * application.  If you return true, the application will not itself
+     * process the event.  If you return true, the normal application processing
+     * will occur as if the IME had not seen the event at all.
+     * 
+     * <p>The default implementation always returns false, except when
+     * in fullscreen mode, where it will consume DPAD movement
+     * events to move the cursor in the extracted text view, not allowing
+     * them to perform navigation in the underlying application.
+     */
+    public boolean onKeyMultiple(int keyCode, int count, KeyEvent event) {
+        return doMovementKey(keyCode, event, count);
+    }
+
+    /**
+     * Override this to intercept key up events before they are processed by the
+     * application.  If you return true, the application will not itself
+     * process the event.  If you return true, the normal application processing
+     * will occur as if the IME had not seen the event at all.
+     * 
+     * <p>The default implementation always returns false, except when
+     * in fullscreen mode, where it will consume DPAD movement
+     * events to move the cursor in the extracted text view, not allowing
+     * them to perform navigation in the underlying application.
+     */
+    public boolean onKeyUp(int keyCode, KeyEvent event) {
+        return doMovementKey(keyCode, event, MOVEMENT_UP);
+    }
+
+    public boolean onTrackballEvent(MotionEvent event) {
+        return false;
+    }
+
+    public void onAppPrivateCommand(String action, Bundle data) {
+    }
+    
+    static final int MOVEMENT_DOWN = -1;
+    static final int MOVEMENT_UP = -2;
+    
+    void reportExtractedMovement(int keyCode, int count) {
+        int dx = 0, dy = 0;
+        switch (keyCode) {
+            case KeyEvent.KEYCODE_DPAD_LEFT:
+                dx = -count;
+                break;
+            case KeyEvent.KEYCODE_DPAD_RIGHT:
+                dx = count;
+                break;
+            case KeyEvent.KEYCODE_DPAD_UP:
+                dy = -count;
+                break;
+            case KeyEvent.KEYCODE_DPAD_DOWN:
+                dy = count;
+                break;
+        }
+        onExtractedCursorMovement(dx, dy);       
+    }
+    
+    boolean doMovementKey(int keyCode, KeyEvent event, int count) {
+        final ExtractEditText eet = mExtractEditText;
+        if (isFullscreenMode() && isInputViewShown() && eet != null) {
+            // If we are in fullscreen mode, the cursor will move around
+            // the extract edit text, but should NOT cause focus to move
+            // to other fields.
+            MovementMethod movement = eet.getMovementMethod();
+            Layout layout = eet.getLayout();
+            if (movement != null && layout != null) {
+                // We want our own movement method to handle the key, so the
+                // cursor will properly move in our own word wrapping.
+                if (count == MOVEMENT_DOWN) {
+                    if (movement.onKeyDown(eet,
+                            (Spannable)eet.getText(), keyCode, event)) {
+                        reportExtractedMovement(keyCode, 1);
+                        return true;
+                    }
+                } else if (count == MOVEMENT_UP) {
+                    if (movement.onKeyUp(eet,
+                            (Spannable)eet.getText(), keyCode, event)) {
+                        return true;
+                    }
+                } else {
+                    if (movement.onKeyOther(eet, (Spannable)eet.getText(), event)) {
+                        reportExtractedMovement(keyCode, count);
+                    } else {
+                        KeyEvent down = new KeyEvent(event, KeyEvent.ACTION_DOWN);
+                        if (movement.onKeyDown(eet,
+                                (Spannable)eet.getText(), keyCode, down)) {
+                            KeyEvent up = new KeyEvent(event, KeyEvent.ACTION_UP);
+                            movement.onKeyUp(eet,
+                                    (Spannable)eet.getText(), keyCode, up);
+                            while (--count > 0) {
+                                movement.onKeyDown(eet,
+                                        (Spannable)eet.getText(), keyCode, down);
+                                movement.onKeyUp(eet,
+                                        (Spannable)eet.getText(), keyCode, up);
+                            }
+                            reportExtractedMovement(keyCode, count);
+                        }
+                    }
+                }
+            }
+            // Regardless of whether the movement method handled the key,
+            // we never allow DPAD navigation to the application.
+            switch (keyCode) {
+                case KeyEvent.KEYCODE_DPAD_LEFT:
+                case KeyEvent.KEYCODE_DPAD_RIGHT:
+                case KeyEvent.KEYCODE_DPAD_UP:
+                case KeyEvent.KEYCODE_DPAD_DOWN:
+                    return true;
+            }
+        }
+        
+        return false;
+    }
+    
+    /**
+     * Send the given key event code (as defined by {@link KeyEvent}) to the
+     * current input connection is a key down + key up event pair.  The sent
+     * events have {@link KeyEvent#FLAG_SOFT_KEYBOARD KeyEvent.FLAG_SOFT_KEYBOARD}
+     * set, so that the recipient can identify them as coming from a software
+     * input method, and
+     * {@link KeyEvent#FLAG_KEEP_TOUCH_MODE KeyEvent.FLAG_KEEP_TOUCH_MODE}, so
+     * that they don't impact the current touch mode of the UI.
+     *
+     * @param keyEventCode The raw key code to send, as defined by
+     * {@link KeyEvent}.
+     */
+    public void sendDownUpKeyEvents(int keyEventCode) {
+        InputConnection ic = getCurrentInputConnection();
+        if (ic == null) return;
+        long eventTime = SystemClock.uptimeMillis();
+        ic.sendKeyEvent(new KeyEvent(eventTime, eventTime,
+                KeyEvent.ACTION_DOWN, keyEventCode, 0, 0, 0, 0,
+                KeyEvent.FLAG_SOFT_KEYBOARD|KeyEvent.FLAG_KEEP_TOUCH_MODE));
+        ic.sendKeyEvent(new KeyEvent(SystemClock.uptimeMillis(), eventTime,
+                KeyEvent.ACTION_UP, keyEventCode, 0, 0, 0, 0,
+                KeyEvent.FLAG_SOFT_KEYBOARD|KeyEvent.FLAG_KEEP_TOUCH_MODE));
+    }
+    
+    /**
+     * Ask the input target to execute its default action via
+     * {@link InputConnection#performEditorAction
+     * InputConnection.performEditorAction()}.
+     * 
+     * @param fromEnterKey If true, this will be executed as if the user had
+     * pressed an enter key on the keyboard, that is it will <em>not</em>
+     * be done if the editor has set {@link EditorInfo#IME_FLAG_NO_ENTER_ACTION
+     * EditorInfo.IME_FLAG_NO_ENTER_ACTION}.  If false, the action will be
+     * sent regardless of how the editor has set that flag.
+     * 
+     * @return Returns a boolean indicating whether an action has been sent.
+     * If false, either the editor did not specify a default action or it
+     * does not want an action from the enter key.  If true, the action was
+     * sent (or there was no input connection at all).
+     */
+    public boolean sendDefaultEditorAction(boolean fromEnterKey) {
+        EditorInfo ei = getCurrentInputEditorInfo();
+        if (ei != null &&
+                (!fromEnterKey || (ei.imeOptions &
+                        EditorInfo.IME_FLAG_NO_ENTER_ACTION) == 0) &&
+                (ei.imeOptions & EditorInfo.IME_MASK_ACTION) !=
+                    EditorInfo.IME_ACTION_NONE) {
+            // If the enter key was pressed, and the editor has a default
+            // action associated with pressing enter, then send it that
+            // explicit action instead of the key event.
+            InputConnection ic = getCurrentInputConnection();
+            if (ic != null) {
+                ic.performEditorAction(ei.imeOptions&EditorInfo.IME_MASK_ACTION);
+            }
+            return true;
+        }
+        
+        return false;
+    }
+    
+    /**
+     * Send the given UTF-16 character to the current input connection.  Most
+     * characters will be delivered simply by calling
+     * {@link InputConnection#commitText InputConnection.commitText()} with
+     * the character; some, however, may be handled different.  In particular,
+     * the enter character ('\n') will either be delivered as an action code
+     * or a raw key event, as appropriate.
+     * 
+     * @param charCode The UTF-16 character code to send.
+     */
+    public void sendKeyChar(char charCode) {
+        switch (charCode) {
+            case '\n': // Apps may be listening to an enter key to perform an action
+                if (!sendDefaultEditorAction(true)) {
+                    sendDownUpKeyEvents(KeyEvent.KEYCODE_ENTER);
+                }
+                break;
+            default:
+                // Make sure that digits go through any text watcher on the client side.
+                if (charCode >= '0' && charCode <= '9') {
+                    sendDownUpKeyEvents(charCode - '0' + KeyEvent.KEYCODE_0);
+                } else {
+                    InputConnection ic = getCurrentInputConnection();
+                    if (ic != null) {
+                        ic.commitText(String.valueOf((char) charCode), 1);
+                    }
+                }
+                break;
+        }
+    }
+    
+    /**
+     * This is called when the user has moved the cursor in the extracted
+     * text view, when running in fullsreen mode.  The default implementation
+     * performs the corresponding selection change on the underlying text
+     * editor.
+     */
+    public void onExtractedSelectionChanged(int start, int end) {
+        InputConnection conn = getCurrentInputConnection();
+        if (conn != null) {
+            conn.setSelection(start, end);
+        }
+    }
+    
+    /**
+     * This is called when the user has clicked on the extracted text view,
+     * when running in fullscreen mode.  The default implementation hides
+     * the candidates view when this happens, but only if the extracted text
+     * editor has a vertical scroll bar because its text doesn't fit.
+     * Re-implement this to provide whatever behavior you want.
+     */
+    public void onExtractedTextClicked() {
+        if (mExtractEditText == null) {
+            return;
+        }
+        if (mExtractEditText.hasVerticalScrollBar()) {
+            setCandidatesViewShown(false);
+        }
+    }
+    
+    /**
+     * This is called when the user has performed a cursor movement in the
+     * extracted text view, when it is running in fullscreen mode.  The default
+     * implementation hides the candidates view when a vertical movement
+     * happens, but only if the extracted text editor has a vertical scroll bar
+     * because its text doesn't fit.
+     * Re-implement this to provide whatever behavior you want.
+     * @param dx The amount of cursor movement in the x dimension.
+     * @param dy The amount of cursor movement in the y dimension.
+     */
+    public void onExtractedCursorMovement(int dx, int dy) {
+        if (mExtractEditText == null || dy == 0) {
+            return;
+        }
+        if (mExtractEditText.hasVerticalScrollBar()) {
+            setCandidatesViewShown(false);
+        }
+    }
+    
+    /**
+     * This is called when the user has selected a context menu item from the
+     * extracted text view, when running in fullscreen mode.  The default
+     * implementation sends this action to the current InputConnection's
+     * {@link InputConnection#performContextMenuAction(int)}, for it
+     * to be processed in underlying "real" editor.  Re-implement this to
+     * provide whatever behavior you want.
+     */
+    public boolean onExtractTextContextMenuItem(int id) {
+        InputConnection ic = getCurrentInputConnection();
+        if (ic != null) {
+            ic.performContextMenuAction(id);
+        }
+        return true;
+    }
+    
+    /**
+     * Return text that can be used as a button label for the given
+     * {@link EditorInfo#imeOptions EditorInfo.imeOptions}.  Returns null
+     * if there is no action requested.  Note that there is no guarantee that
+     * the returned text will be relatively short, so you probably do not
+     * want to use it as text on a soft keyboard key label.
+     * 
+     * @param imeOptions The value from @link EditorInfo#imeOptions EditorInfo.imeOptions}.
+     * 
+     * @return Returns a label to use, or null if there is no action.
+     */
+    public CharSequence getTextForImeAction(int imeOptions) {
+        switch (imeOptions&EditorInfo.IME_MASK_ACTION) {
+            case EditorInfo.IME_ACTION_NONE:
+                return null;
+            case EditorInfo.IME_ACTION_GO:
+                return getText(com.android.internal.R.string.ime_action_go);
+            case EditorInfo.IME_ACTION_SEARCH:
+                return getText(com.android.internal.R.string.ime_action_search);
+            case EditorInfo.IME_ACTION_SEND:
+                return getText(com.android.internal.R.string.ime_action_send);
+            case EditorInfo.IME_ACTION_NEXT:
+                return getText(com.android.internal.R.string.ime_action_next);
+            default:
+                return getText(com.android.internal.R.string.ime_action_default);
+        }
+    }
+    
+    /**
+     * Called when it is time to update the actions available from a full-screen
+     * IME.  You do not need to deal with this if you are using the standard
+     * full screen extract UI.  If replacing it, you will need to re-implement
+     * this to put the action in your own UI and handle it.
+     */
+    public void onUpdateExtractingAccessories(EditorInfo ei) {
+        if (mExtractAccessories == null) {
+            return;
+        }
+        final boolean hasAction = ei.actionLabel != null || (
+                (ei.imeOptions&EditorInfo.IME_MASK_ACTION) != EditorInfo.IME_ACTION_NONE &&
+                (ei.imeOptions&EditorInfo.IME_FLAG_NO_ENTER_ACTION) != 0);
+        if (hasAction) {
+            mExtractAccessories.setVisibility(View.VISIBLE);
+            if (ei.actionLabel != null) {
+                mExtractAction.setText(ei.actionLabel);
+            } else {
+                mExtractAction.setText(getTextForImeAction(ei.imeOptions));
+            }
+            mExtractAction.setOnClickListener(mActionClickListener);
+        } else {
+            mExtractAccessories.setVisibility(View.GONE);
+            mExtractAction.setOnClickListener(null);
+        }
+    }
+    
+    /**
+     * This is called when, while currently displayed in extract mode, the
+     * current input target changes.  The default implementation will
+     * auto-hide the IME if the new target is not a full editor, since this
+     * can be an confusing experience for the user.
+     */
+    public void onExtractingInputChanged(EditorInfo ei) {
+        if (ei.inputType == InputType.TYPE_NULL) {
+            dismissSoftInput(InputMethodManager.HIDE_NOT_ALWAYS);
+        }
+    }
+    
+    void startExtractingText(boolean inputChanged) {
+        final ExtractEditText eet = mExtractEditText;
+        if (eet != null && getCurrentInputStarted()
+                && isFullscreenMode()) {
+            mExtractedToken++;
+            ExtractedTextRequest req = new ExtractedTextRequest();
+            req.token = mExtractedToken;
+            req.flags = InputConnection.GET_TEXT_WITH_STYLES;
+            req.hintMaxLines = 10;
+            req.hintMaxChars = 10000;
+            mExtractedText = getCurrentInputConnection().getExtractedText(req,
+                    InputConnection.GET_EXTRACTED_TEXT_MONITOR);
+            
+            final EditorInfo ei = getCurrentInputEditorInfo();
+            
+            try {
+                eet.startInternalChanges();
+                onUpdateExtractingAccessories(ei);
+                int inputType = ei.inputType;
+                if ((inputType&EditorInfo.TYPE_MASK_CLASS)
+                        == EditorInfo.TYPE_CLASS_TEXT) {
+                    if ((inputType&EditorInfo.TYPE_TEXT_FLAG_IME_MULTI_LINE) != 0) {
+                        inputType |= EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE;
+                    }
+                }
+                eet.setInputType(inputType);
+                eet.setHint(ei.hintText);
+                if (mExtractedText != null) {
+                    eet.setEnabled(true);
+                    eet.setExtractedText(mExtractedText);
+                } else {
+                    eet.setEnabled(false);
+                    eet.setText("");
+                }
+            } finally {
+                eet.finishInternalChanges();
+            }
+            
+            if (inputChanged) {
+                onExtractingInputChanged(ei);
+            }
+        }
+    }
+    
+    /**
+     * Performs a dump of the InputMethodService's internal state.  Override
+     * to add your own information to the dump.
+     */
+    @Override protected void dump(FileDescriptor fd, PrintWriter fout, String[] args) {
+        final Printer p = new PrintWriterPrinter(fout);
+        p.println("Input method service state for " + this + ":");
+        p.println("  mWindowCreated=" + mWindowCreated
+                + " mWindowAdded=" + mWindowAdded
+                + " mWindowVisible=" + mWindowVisible);
+        p.println("  Configuration=" + getResources().getConfiguration());
+        p.println("  mToken=" + mToken);
+        p.println("  mInputBinding=" + mInputBinding);
+        p.println("  mInputConnection=" + mInputConnection);
+        p.println("  mStartedInputConnection=" + mStartedInputConnection);
+        p.println("  mInputStarted=" + mInputStarted
+                + " mInputViewStarted=" + mInputViewStarted
+                + " mCandidatesViewStarted=" + mCandidatesViewStarted);
+        
+        if (mInputEditorInfo != null) {
+            p.println("  mInputEditorInfo:");
+            mInputEditorInfo.dump(p, "    ");
+        } else {
+            p.println("  mInputEditorInfo: null");
+        }
+        
+        p.println("  mShowInputRequested=" + mShowInputRequested
+                + " mLastShowInputRequested=" + mLastShowInputRequested
+                + " mShowInputForced=" + mShowInputForced
+                + " mShowInputFlags=0x" + Integer.toHexString(mShowInputFlags));
+        p.println("  mCandidatesVisibility=" + mCandidatesVisibility
+                + " mFullscreenApplied=" + mFullscreenApplied
+                + " mIsFullscreen=" + mIsFullscreen);
+        
+        if (mExtractedText != null) {
+            p.println("  mExtractedText:");
+            p.println("    text=" + mExtractedText.text.length() + " chars"
+                    + " startOffset=" + mExtractedText.startOffset);
+            p.println("    selectionStart=" + mExtractedText.selectionStart
+                    + " selectionEnd=" + mExtractedText.selectionEnd
+                    + " flags=0x" + Integer.toHexString(mExtractedText.flags));
+        } else {
+            p.println("  mExtractedText: null");
+        }
+        p.println("  mExtractedToken=" + mExtractedToken);
+        p.println("  mIsInputViewShown=" + mIsInputViewShown
+                + " mStatusIcon=" + mStatusIcon);
+        p.println("Last computed insets:");
+        p.println("  contentTopInsets=" + mTmpInsets.contentTopInsets
+                + " visibleTopInsets=" + mTmpInsets.visibleTopInsets
+                + " touchableInsets=" + mTmpInsets.touchableInsets);
+    }
+}
diff --git a/core/java/android/inputmethodservice/Keyboard.java b/core/java/android/inputmethodservice/Keyboard.java
new file mode 100755
index 0000000..6a560ce
--- /dev/null
+++ b/core/java/android/inputmethodservice/Keyboard.java
@@ -0,0 +1,815 @@
+/*
+ * Copyright (C) 2008-2009 Google Inc.
+ * 
+ * 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 android.inputmethodservice;
+
+import org.xmlpull.v1.XmlPullParserException;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.content.res.XmlResourceParser;
+import android.graphics.drawable.Drawable;
+import android.text.TextUtils;
+import android.util.Log;
+import android.util.TypedValue;
+import android.util.Xml;
+import android.view.Display;
+import android.view.WindowManager;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.StringTokenizer;
+
+
+/**
+ * Loads an XML description of a keyboard and stores the attributes of the keys. A keyboard
+ * consists of rows of keys.
+ * <p>The layout file for a keyboard contains XML that looks like the following snippet:</p>
+ * <pre>
+ * &lt;Keyboard
+ *         android:keyWidth="%10p"
+ *         android:keyHeight="50px"
+ *         android:horizontalGap="2px"
+ *         android:verticalGap="2px" &gt;
+ *     &lt;Row android:keyWidth="32px" &gt;
+ *         &lt;Key android:keyLabel="A" /&gt;
+ *         ...
+ *     &lt;/Row&gt;
+ *     ...
+ * &lt;/Keyboard&gt;
+ * </pre>
+ * @attr ref android.R.styleable#Keyboard_keyWidth
+ * @attr ref android.R.styleable#Keyboard_keyHeight
+ * @attr ref android.R.styleable#Keyboard_horizontalGap
+ * @attr ref android.R.styleable#Keyboard_verticalGap
+ */
+public class Keyboard {
+
+    static final String TAG = "Keyboard";
+    
+    // Keyboard XML Tags
+    private static final String TAG_KEYBOARD = "Keyboard";
+    private static final String TAG_ROW = "Row";
+    private static final String TAG_KEY = "Key";
+
+    public static final int EDGE_LEFT = 0x01;
+    public static final int EDGE_RIGHT = 0x02;
+    public static final int EDGE_TOP = 0x04;
+    public static final int EDGE_BOTTOM = 0x08;
+
+    public static final int KEYCODE_SHIFT = -1;
+    public static final int KEYCODE_MODE_CHANGE = -2;
+    public static final int KEYCODE_CANCEL = -3;
+    public static final int KEYCODE_DONE = -4;
+    public static final int KEYCODE_DELETE = -5;
+    public static final int KEYCODE_ALT = -6;
+    
+    /** Keyboard label **/
+    private CharSequence mLabel;
+
+    /** Horizontal gap default for all rows */
+    private int mDefaultHorizontalGap;
+    
+    /** Default key width */
+    private int mDefaultWidth;
+
+    /** Default key height */
+    private int mDefaultHeight;
+
+    /** Default gap between rows */
+    private int mDefaultVerticalGap;
+
+    /** Is the keyboard in the shifted state */
+    private boolean mShifted;
+    
+    /** Key instance for the shift key, if present */
+    private Key mShiftKey;
+    
+    /** Key index for the shift key, if present */
+    private int mShiftKeyIndex = -1;
+    
+    /** Current key width, while loading the keyboard */
+    private int mKeyWidth;
+    
+    /** Current key height, while loading the keyboard */
+    private int mKeyHeight;
+    
+    /** Total height of the keyboard, including the padding and keys */
+    private int mTotalHeight;
+    
+    /** 
+     * Total width of the keyboard, including left side gaps and keys, but not any gaps on the
+     * right side.
+     */
+    private int mTotalWidth;
+    
+    /** List of keys in this keyboard */
+    private List<Key> mKeys;
+    
+    /** List of modifier keys such as Shift & Alt, if any */
+    private List<Key> mModifierKeys;
+    
+    /** Width of the screen available to fit the keyboard */
+    private int mDisplayWidth;
+
+    /** Height of the screen */
+    private int mDisplayHeight;
+
+    /** Keyboard mode, or zero, if none.  */
+    private int mKeyboardMode;
+
+    // Variables for pre-computing nearest keys.
+    
+    private static final int GRID_WIDTH = 10;
+    private static final int GRID_HEIGHT = 5;
+    private static final int GRID_SIZE = GRID_WIDTH * GRID_HEIGHT;
+    private int mCellWidth;
+    private int mCellHeight;
+    private int[][] mGridNeighbors;
+    private int mProximityThreshold;
+    /** Number of key widths from current touch point to search for nearest keys. */
+    private static float SEARCH_DISTANCE = 1.4f;
+
+    /**
+     * Container for keys in the keyboard. All keys in a row are at the same Y-coordinate. 
+     * Some of the key size defaults can be overridden per row from what the {@link Keyboard}
+     * defines. 
+     * @attr ref android.R.styleable#Keyboard_keyWidth
+     * @attr ref android.R.styleable#Keyboard_keyHeight
+     * @attr ref android.R.styleable#Keyboard_horizontalGap
+     * @attr ref android.R.styleable#Keyboard_verticalGap
+     * @attr ref android.R.styleable#Keyboard_Row_rowEdgeFlags
+     * @attr ref android.R.styleable#Keyboard_Row_keyboardMode
+     */
+    public static class Row {
+        /** Default width of a key in this row. */
+        public int defaultWidth;
+        /** Default height of a key in this row. */
+        public int defaultHeight;
+        /** Default horizontal gap between keys in this row. */
+        public int defaultHorizontalGap;
+        /** Vertical gap following this row. */
+        public int verticalGap;
+        /**
+         * Edge flags for this row of keys. Possible values that can be assigned are
+         * {@link Keyboard#EDGE_TOP EDGE_TOP} and {@link Keyboard#EDGE_BOTTOM EDGE_BOTTOM}  
+         */
+        public int rowEdgeFlags;
+        
+        /** The keyboard mode for this row */
+        public int mode;
+        
+        private Keyboard parent;
+
+        public Row(Keyboard parent) {
+            this.parent = parent;
+        }
+        
+        public Row(Resources res, Keyboard parent, XmlResourceParser parser) {
+            this.parent = parent;
+            TypedArray a = res.obtainAttributes(Xml.asAttributeSet(parser), 
+                    com.android.internal.R.styleable.Keyboard);
+            defaultWidth = getDimensionOrFraction(a, 
+                    com.android.internal.R.styleable.Keyboard_keyWidth, 
+                    parent.mDisplayWidth, parent.mDefaultWidth);
+            defaultHeight = getDimensionOrFraction(a, 
+                    com.android.internal.R.styleable.Keyboard_keyHeight, 
+                    parent.mDisplayHeight, parent.mDefaultHeight);
+            defaultHorizontalGap = getDimensionOrFraction(a,
+                    com.android.internal.R.styleable.Keyboard_horizontalGap, 
+                    parent.mDisplayWidth, parent.mDefaultHorizontalGap);
+            verticalGap = getDimensionOrFraction(a, 
+                    com.android.internal.R.styleable.Keyboard_verticalGap, 
+                    parent.mDisplayHeight, parent.mDefaultVerticalGap);
+            a.recycle();
+            a = res.obtainAttributes(Xml.asAttributeSet(parser),
+                    com.android.internal.R.styleable.Keyboard_Row);
+            rowEdgeFlags = a.getInt(com.android.internal.R.styleable.Keyboard_Row_rowEdgeFlags, 0);
+            mode = a.getResourceId(com.android.internal.R.styleable.Keyboard_Row_keyboardMode,
+                    0);
+        }
+    }
+
+    /**
+     * Class for describing the position and characteristics of a single key in the keyboard.
+     * 
+     * @attr ref android.R.styleable#Keyboard_keyWidth
+     * @attr ref android.R.styleable#Keyboard_keyHeight
+     * @attr ref android.R.styleable#Keyboard_horizontalGap
+     * @attr ref android.R.styleable#Keyboard_Key_codes
+     * @attr ref android.R.styleable#Keyboard_Key_keyIcon
+     * @attr ref android.R.styleable#Keyboard_Key_keyLabel
+     * @attr ref android.R.styleable#Keyboard_Key_iconPreview
+     * @attr ref android.R.styleable#Keyboard_Key_isSticky
+     * @attr ref android.R.styleable#Keyboard_Key_isRepeatable
+     * @attr ref android.R.styleable#Keyboard_Key_isModifier
+     * @attr ref android.R.styleable#Keyboard_Key_popupKeyboard
+     * @attr ref android.R.styleable#Keyboard_Key_popupCharacters
+     * @attr ref android.R.styleable#Keyboard_Key_keyOutputText
+     * @attr ref android.R.styleable#Keyboard_Key_keyEdgeFlags
+     */
+    public static class Key {
+        /** 
+         * All the key codes (unicode or custom code) that this key could generate, zero'th 
+         * being the most important.
+         */
+        public int[] codes;
+        
+        /** Label to display */
+        public CharSequence label;
+        
+        /** Icon to display instead of a label. Icon takes precedence over a label */
+        public Drawable icon;
+        /** Preview version of the icon, for the preview popup */
+        public Drawable iconPreview;
+        /** Width of the key, not including the gap */
+        public int width;
+        /** Height of the key, not including the gap */
+        public int height;
+        /** The horizontal gap before this key */
+        public int gap;
+        /** Whether this key is sticky, i.e., a toggle key */
+        public boolean sticky;
+        /** X coordinate of the key in the keyboard layout */
+        public int x;
+        /** Y coordinate of the key in the keyboard layout */
+        public int y;
+        /** The current pressed state of this key */
+        public boolean pressed;
+        /** If this is a sticky key, is it on? */
+        public boolean on;
+        /** Text to output when pressed. This can be multiple characters, like ".com" */
+        public CharSequence text;
+        /** Popup characters */
+        public CharSequence popupCharacters;
+        
+        /** 
+         * Flags that specify the anchoring to edges of the keyboard for detecting touch events
+         * that are just out of the boundary of the key. This is a bit mask of 
+         * {@link Keyboard#EDGE_LEFT}, {@link Keyboard#EDGE_RIGHT}, {@link Keyboard#EDGE_TOP} and
+         * {@link Keyboard#EDGE_BOTTOM}.
+         */
+        public int edgeFlags;
+        /** Whether this is a modifier key, such as Shift or Alt */
+        public boolean modifier;
+        /** The keyboard that this key belongs to */
+        private Keyboard keyboard;
+        /** 
+         * If this key pops up a mini keyboard, this is the resource id for the XML layout for that
+         * keyboard.
+         */
+        public int popupResId;
+        /** Whether this key repeats itself when held down */
+        public boolean repeatable;
+
+        
+        private final static int[] KEY_STATE_NORMAL_ON = { 
+            android.R.attr.state_checkable, 
+            android.R.attr.state_checked
+        };
+        
+        private final static int[] KEY_STATE_PRESSED_ON = { 
+            android.R.attr.state_pressed, 
+            android.R.attr.state_checkable, 
+            android.R.attr.state_checked 
+        };
+        
+        private final static int[] KEY_STATE_NORMAL_OFF = { 
+            android.R.attr.state_checkable 
+        };
+        
+        private final static int[] KEY_STATE_PRESSED_OFF = { 
+            android.R.attr.state_pressed, 
+            android.R.attr.state_checkable 
+        };
+        
+        private final static int[] KEY_STATE_NORMAL = {
+        };
+        
+        private final static int[] KEY_STATE_PRESSED = {
+            android.R.attr.state_pressed
+        };
+
+        /** Create an empty key with no attributes. */
+        public Key(Row parent) {
+            keyboard = parent.parent;
+        }
+        
+        /** Create a key with the given top-left coordinate and extract its attributes from
+         * the XML parser.
+         * @param res resources associated with the caller's context
+         * @param parent the row that this key belongs to. The row must already be attached to
+         * a {@link Keyboard}.
+         * @param x the x coordinate of the top-left
+         * @param y the y coordinate of the top-left
+         * @param parser the XML parser containing the attributes for this key
+         */
+        public Key(Resources res, Row parent, int x, int y, XmlResourceParser parser) {
+            this(parent);
+
+            this.x = x;
+            this.y = y;
+            
+            TypedArray a = res.obtainAttributes(Xml.asAttributeSet(parser), 
+                    com.android.internal.R.styleable.Keyboard);
+
+            width = getDimensionOrFraction(a, 
+                    com.android.internal.R.styleable.Keyboard_keyWidth,
+                    keyboard.mDisplayWidth, parent.defaultWidth);
+            height = getDimensionOrFraction(a, 
+                    com.android.internal.R.styleable.Keyboard_keyHeight,
+                    keyboard.mDisplayHeight, parent.defaultHeight);
+            gap = getDimensionOrFraction(a, 
+                    com.android.internal.R.styleable.Keyboard_horizontalGap,
+                    keyboard.mDisplayWidth, parent.defaultHorizontalGap);
+            a.recycle();
+            a = res.obtainAttributes(Xml.asAttributeSet(parser),
+                    com.android.internal.R.styleable.Keyboard_Key);
+            this.x += gap;
+            TypedValue codesValue = new TypedValue();
+            a.getValue(com.android.internal.R.styleable.Keyboard_Key_codes, 
+                    codesValue);
+            if (codesValue.type == TypedValue.TYPE_INT_DEC 
+                    || codesValue.type == TypedValue.TYPE_INT_HEX) {
+                codes = new int[] { codesValue.data };
+            } else if (codesValue.type == TypedValue.TYPE_STRING) {
+                codes = parseCSV(codesValue.string.toString());
+            }
+            
+            iconPreview = a.getDrawable(com.android.internal.R.styleable.Keyboard_Key_iconPreview);
+            if (iconPreview != null) {
+                iconPreview.setBounds(0, 0, iconPreview.getIntrinsicWidth(), 
+                        iconPreview.getIntrinsicHeight());
+            }
+            popupCharacters = a.getText(
+                    com.android.internal.R.styleable.Keyboard_Key_popupCharacters);
+            popupResId = a.getResourceId(
+                    com.android.internal.R.styleable.Keyboard_Key_popupKeyboard, 0);
+            repeatable = a.getBoolean(
+                    com.android.internal.R.styleable.Keyboard_Key_isRepeatable, false);
+            modifier = a.getBoolean(
+                    com.android.internal.R.styleable.Keyboard_Key_isModifier, false);
+            sticky = a.getBoolean(
+                    com.android.internal.R.styleable.Keyboard_Key_isSticky, false);
+            edgeFlags = a.getInt(com.android.internal.R.styleable.Keyboard_Key_keyEdgeFlags, 0);
+            edgeFlags |= parent.rowEdgeFlags;
+
+            icon = a.getDrawable(
+                    com.android.internal.R.styleable.Keyboard_Key_keyIcon);
+            if (icon != null) {
+                icon.setBounds(0, 0, icon.getIntrinsicWidth(), icon.getIntrinsicHeight());
+            }
+            label = a.getText(com.android.internal.R.styleable.Keyboard_Key_keyLabel);
+            text = a.getText(com.android.internal.R.styleable.Keyboard_Key_keyOutputText);
+            
+            if (codes == null && !TextUtils.isEmpty(label)) {
+                codes = new int[] { label.charAt(0) };
+            }
+            a.recycle();
+        }
+        
+        /**
+         * Informs the key that it has been pressed, in case it needs to change its appearance or
+         * state.
+         * @see #onReleased(boolean)
+         */
+        public void onPressed() {
+            pressed = !pressed;
+        }
+        
+        /**
+         * Changes the pressed state of the key. If it is a sticky key, it will also change the
+         * toggled state of the key if the finger was release inside.
+         * @param inside whether the finger was released inside the key
+         * @see #onPressed()
+         */
+        public void onReleased(boolean inside) {
+            pressed = !pressed;
+            if (sticky) {
+                on = !on;
+            }
+        }
+
+        int[] parseCSV(String value) {
+            int count = 0;
+            int lastIndex = 0;
+            if (value.length() > 0) {
+                count++;
+                while ((lastIndex = value.indexOf(",", lastIndex + 1)) > 0) {
+                    count++;
+                }
+            }
+            int[] values = new int[count];
+            count = 0;
+            StringTokenizer st = new StringTokenizer(value, ",");
+            while (st.hasMoreTokens()) {
+                try {
+                    values[count++] = Integer.parseInt(st.nextToken());
+                } catch (NumberFormatException nfe) {
+                    Log.e(TAG, "Error parsing keycodes " + value);
+                }
+            }
+            return values;
+        }
+
+        /**
+         * Detects if a point falls inside this key.
+         * @param x the x-coordinate of the point 
+         * @param y the y-coordinate of the point
+         * @return whether or not the point falls inside the key. If the key is attached to an edge,
+         * it will assume that all points between the key and the edge are considered to be inside
+         * the key.
+         */
+        public boolean isInside(int x, int y) {
+            boolean leftEdge = (edgeFlags & EDGE_LEFT) > 0;
+            boolean rightEdge = (edgeFlags & EDGE_RIGHT) > 0;
+            boolean topEdge = (edgeFlags & EDGE_TOP) > 0;
+            boolean bottomEdge = (edgeFlags & EDGE_BOTTOM) > 0;
+            if ((x >= this.x || (leftEdge && x <= this.x + this.width)) 
+                    && (x < this.x + this.width || (rightEdge && x >= this.x)) 
+                    && (y >= this.y || (topEdge && y <= this.y + this.height))
+                    && (y < this.y + this.height || (bottomEdge && y >= this.y))) {
+                return true;
+            } else {
+                return false;
+            }
+        }
+
+        /**
+         * Returns the square of the distance between the center of the key and the given point.
+         * @param x the x-coordinate of the point
+         * @param y the y-coordinate of the point
+         * @return the square of the distance of the point from the center of the key
+         */
+        public int squaredDistanceFrom(int x, int y) {
+            int xDist = this.x + width / 2 - x;
+            int yDist = this.y + height / 2 - y;
+            return xDist * xDist + yDist * yDist;
+        }
+        
+        /**
+         * Returns the drawable state for the key, based on the current state and type of the key.
+         * @return the drawable state of the key.
+         * @see android.graphics.drawable.StateListDrawable#setState(int[])
+         */
+        public int[] getCurrentDrawableState() {
+            int[] states = KEY_STATE_NORMAL;
+
+            if (on) {
+                if (pressed) {
+                    states = KEY_STATE_PRESSED_ON;
+                } else {
+                    states = KEY_STATE_NORMAL_ON;
+                }
+            } else {
+                if (sticky) {
+                    if (pressed) {
+                        states = KEY_STATE_PRESSED_OFF;
+                    } else {
+                        states = KEY_STATE_NORMAL_OFF;
+                    }
+                } else {
+                    if (pressed) {
+                        states = KEY_STATE_PRESSED;
+                    }
+                }
+            }
+            return states;
+        }
+    }
+
+    /**
+     * Creates a keyboard from the given xml key layout file.
+     * @param context the application or service context
+     * @param xmlLayoutResId the resource file that contains the keyboard layout and keys.
+     */
+    public Keyboard(Context context, int xmlLayoutResId) {
+        this(context, xmlLayoutResId, 0);
+    }
+    
+    /**
+     * Creates a keyboard from the given xml key layout file. Weeds out rows
+     * that have a keyboard mode defined but don't match the specified mode. 
+     * @param context the application or service context
+     * @param xmlLayoutResId the resource file that contains the keyboard layout and keys.
+     * @param modeId keyboard mode identifier
+     */
+    public Keyboard(Context context, int xmlLayoutResId, int modeId) {
+        WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
+        final Display display = wm.getDefaultDisplay();
+        mDisplayWidth = display.getWidth();
+        mDisplayHeight = display.getHeight();
+        mDefaultHorizontalGap = 0;
+        mDefaultWidth = mDisplayWidth / 10;
+        mDefaultVerticalGap = 0;
+        mDefaultHeight = mDefaultWidth;
+        mKeys = new ArrayList<Key>();
+        mModifierKeys = new ArrayList<Key>();
+        mKeyboardMode = modeId;
+        loadKeyboard(context, context.getResources().getXml(xmlLayoutResId));
+    }
+
+    /**
+     * <p>Creates a blank keyboard from the given resource file and populates it with the specified
+     * characters in left-to-right, top-to-bottom fashion, using the specified number of columns.
+     * </p>
+     * <p>If the specified number of columns is -1, then the keyboard will fit as many keys as
+     * possible in each row.</p>
+     * @param context the application or service context
+     * @param layoutTemplateResId the layout template file, containing no keys.
+     * @param characters the list of characters to display on the keyboard. One key will be created
+     * for each character.
+     * @param columns the number of columns of keys to display. If this number is greater than the 
+     * number of keys that can fit in a row, it will be ignored. If this number is -1, the 
+     * keyboard will fit as many keys as possible in each row.
+     */
+    public Keyboard(Context context, int layoutTemplateResId, 
+            CharSequence characters, int columns, int horizontalPadding) {
+        this(context, layoutTemplateResId);
+        int x = 0;
+        int y = 0;
+        int column = 0;
+        mTotalWidth = 0;
+        
+        Row row = new Row(this);
+        row.defaultHeight = mDefaultHeight;
+        row.defaultWidth = mDefaultWidth;
+        row.defaultHorizontalGap = mDefaultHorizontalGap;
+        row.verticalGap = mDefaultVerticalGap;
+        row.rowEdgeFlags = EDGE_TOP | EDGE_BOTTOM;
+        final int maxColumns = columns == -1 ? Integer.MAX_VALUE : columns;
+        for (int i = 0; i < characters.length(); i++) {
+            char c = characters.charAt(i);
+            if (column >= maxColumns 
+                    || x + mDefaultWidth + horizontalPadding > mDisplayWidth) {
+                x = 0;
+                y += mDefaultVerticalGap + mDefaultHeight;
+                column = 0;
+            }
+            final Key key = new Key(row);
+            key.x = x;
+            key.y = y;
+            key.width = mDefaultWidth;
+            key.height = mDefaultHeight;
+            key.gap = mDefaultHorizontalGap;
+            key.label = String.valueOf(c);
+            key.codes = new int[] { c };
+            column++;
+            x += key.width + key.gap;
+            mKeys.add(key);
+            if (x > mTotalWidth) {
+                mTotalWidth = x;
+            }
+        }
+        mTotalHeight = y + mDefaultHeight; 
+    }
+    
+    public List<Key> getKeys() {
+        return mKeys;
+    }
+    
+    public List<Key> getModifierKeys() {
+        return mModifierKeys;
+    }
+    
+    protected int getHorizontalGap() {
+        return mDefaultHorizontalGap;
+    }
+    
+    protected void setHorizontalGap(int gap) {
+        mDefaultHorizontalGap = gap;
+    }
+
+    protected int getVerticalGap() {
+        return mDefaultVerticalGap;
+    }
+
+    protected void setVerticalGap(int gap) {
+        mDefaultVerticalGap = gap;
+    }
+
+    protected int getKeyHeight() {
+        return mDefaultHeight;
+    }
+
+    protected void setKeyHeight(int height) {
+        mDefaultHeight = height;
+    }
+
+    protected int getKeyWidth() {
+        return mDefaultWidth;
+    }
+    
+    protected void setKeyWidth(int width) {
+        mDefaultWidth = width;
+    }
+
+    /**
+     * Returns the total height of the keyboard
+     * @return the total height of the keyboard
+     */
+    public int getHeight() {
+        return mTotalHeight;
+    }
+    
+    public int getMinWidth() {
+        return mTotalWidth;
+    }
+
+    public boolean setShifted(boolean shiftState) {
+        if (mShiftKey != null) {
+            mShiftKey.on = shiftState;
+        }
+        if (mShifted != shiftState) {
+            mShifted = shiftState;
+            return true;
+        }
+        return false;
+    }
+
+    public boolean isShifted() {
+        return mShifted;
+    }
+
+    public int getShiftKeyIndex() {
+        return mShiftKeyIndex;
+    }
+    
+    private void computeNearestNeighbors() {
+        // Round-up so we don't have any pixels outside the grid
+        mCellWidth = (getMinWidth() + GRID_WIDTH - 1) / GRID_WIDTH;
+        mCellHeight = (getHeight() + GRID_HEIGHT - 1) / GRID_HEIGHT;
+        mGridNeighbors = new int[GRID_SIZE][];
+        int[] indices = new int[mKeys.size()];
+        final int gridWidth = GRID_WIDTH * mCellWidth;
+        final int gridHeight = GRID_HEIGHT * mCellHeight;
+        for (int x = 0; x < gridWidth; x += mCellWidth) {
+            for (int y = 0; y < gridHeight; y += mCellHeight) {
+                int count = 0;
+                for (int i = 0; i < mKeys.size(); i++) {
+                    final Key key = mKeys.get(i);
+                    if (key.squaredDistanceFrom(x, y) < mProximityThreshold ||
+                            key.squaredDistanceFrom(x + mCellWidth - 1, y) < mProximityThreshold ||
+                            key.squaredDistanceFrom(x + mCellWidth - 1, y + mCellHeight - 1) 
+                                < mProximityThreshold ||
+                            key.squaredDistanceFrom(x, y + mCellHeight - 1) < mProximityThreshold) {
+                        indices[count++] = i;
+                    }
+                }
+                int [] cell = new int[count];
+                System.arraycopy(indices, 0, cell, 0, count);
+                mGridNeighbors[(y / mCellHeight) * GRID_WIDTH + (x / mCellWidth)] = cell;
+            }
+        }
+    }
+    
+    /**
+     * Returns the indices of the keys that are closest to the given point.
+     * @param x the x-coordinate of the point
+     * @param y the y-coordinate of the point
+     * @return the array of integer indices for the nearest keys to the given point. If the given
+     * point is out of range, then an array of size zero is returned.
+     */
+    public int[] getNearestKeys(int x, int y) {
+        if (mGridNeighbors == null) computeNearestNeighbors();
+        if (x >= 0 && x < getMinWidth() && y >= 0 && y < getHeight()) {
+            int index = (y / mCellHeight) * GRID_WIDTH + (x / mCellWidth);
+            if (index < GRID_SIZE) {
+                return mGridNeighbors[index];
+            }
+        }
+        return new int[0];
+    }
+
+    protected Row createRowFromXml(Resources res, XmlResourceParser parser) {
+        return new Row(res, this, parser);
+    }
+    
+    protected Key createKeyFromXml(Resources res, Row parent, int x, int y, 
+            XmlResourceParser parser) {
+        return new Key(res, parent, x, y, parser);
+    }
+
+    private void loadKeyboard(Context context, XmlResourceParser parser) {
+        boolean inKey = false;
+        boolean inRow = false;
+        boolean leftMostKey = false;
+        int row = 0;
+        int x = 0;
+        int y = 0;
+        Key key = null;
+        Row currentRow = null;
+        Resources res = context.getResources();
+        boolean skipRow = false;
+        
+        try {
+            int event;
+            while ((event = parser.next()) != XmlResourceParser.END_DOCUMENT) {
+                if (event == XmlResourceParser.START_TAG) {
+                    String tag = parser.getName();
+                    if (TAG_ROW.equals(tag)) {
+                        inRow = true;
+                        x = 0;
+                        currentRow = createRowFromXml(res, parser);
+                        skipRow = currentRow.mode != 0 && currentRow.mode != mKeyboardMode;
+                        if (skipRow) {
+                            skipToEndOfRow(parser);
+                            inRow = false;
+                        }
+                   } else if (TAG_KEY.equals(tag)) {
+                        inKey = true;
+                        key = createKeyFromXml(res, currentRow, x, y, parser);
+                        mKeys.add(key);
+                        if (key.codes[0] == KEYCODE_SHIFT) {
+                            mShiftKey = key;
+                            mShiftKeyIndex = mKeys.size()-1;
+                            mModifierKeys.add(key);
+                        } else if (key.codes[0] == KEYCODE_ALT) {
+                            mModifierKeys.add(key);
+                        }
+                    } else if (TAG_KEYBOARD.equals(tag)) {
+                        parseKeyboardAttributes(res, parser);
+                    }
+                } else if (event == XmlResourceParser.END_TAG) {
+                    if (inKey) {
+                        inKey = false;
+                        x += key.gap + key.width;
+                        if (x > mTotalWidth) {
+                            mTotalWidth = x;
+                        }
+                    } else if (inRow) {
+                        inRow = false;
+                        y += currentRow.verticalGap;
+                        y += currentRow.defaultHeight;
+                        row++;
+                    } else {
+                        // TODO: error or extend?
+                    }
+                }
+            }
+        } catch (Exception e) {
+            Log.e(TAG, "Parse error:" + e);
+            e.printStackTrace();
+        }
+        mTotalHeight = y - mDefaultVerticalGap;
+    }
+
+    private void skipToEndOfRow(XmlResourceParser parser) 
+            throws XmlPullParserException, IOException {
+        int event;
+        while ((event = parser.next()) != XmlResourceParser.END_DOCUMENT) {
+            if (event == XmlResourceParser.END_TAG 
+                    && parser.getName().equals(TAG_ROW)) {
+                break;
+            }
+        }
+    }
+    
+    private void parseKeyboardAttributes(Resources res, XmlResourceParser parser) {
+        TypedArray a = res.obtainAttributes(Xml.asAttributeSet(parser), 
+                com.android.internal.R.styleable.Keyboard);
+
+        mDefaultWidth = getDimensionOrFraction(a,
+                com.android.internal.R.styleable.Keyboard_keyWidth,
+                mDisplayWidth, mDisplayWidth / 10);
+        mDefaultHeight = getDimensionOrFraction(a,
+                com.android.internal.R.styleable.Keyboard_keyHeight,
+                mDisplayHeight, 50);
+        mDefaultHorizontalGap = getDimensionOrFraction(a,
+                com.android.internal.R.styleable.Keyboard_horizontalGap,
+                mDisplayWidth, 0);
+        mDefaultVerticalGap = getDimensionOrFraction(a,
+                com.android.internal.R.styleable.Keyboard_verticalGap,
+                mDisplayHeight, 0);
+        mProximityThreshold = (int) (mDefaultWidth * SEARCH_DISTANCE);
+        mProximityThreshold = mProximityThreshold * mProximityThreshold; // Square it for comparison
+        a.recycle();
+    }
+    
+    static int getDimensionOrFraction(TypedArray a, int index, int base, int defValue) {
+        TypedValue value = a.peekValue(index);
+        if (value == null) return defValue;
+        if (value.type == TypedValue.TYPE_DIMENSION) {
+            return a.getDimensionPixelOffset(index, defValue);
+        } else if (value.type == TypedValue.TYPE_FRACTION) {
+            // Round it to avoid values like 47.9999 from getting truncated
+            return Math.round(a.getFraction(index, base, base, defValue));
+        }
+        return defValue;
+    }
+}
diff --git a/core/java/android/inputmethodservice/KeyboardView.java b/core/java/android/inputmethodservice/KeyboardView.java
new file mode 100755
index 0000000..c838779
--- /dev/null
+++ b/core/java/android/inputmethodservice/KeyboardView.java
@@ -0,0 +1,1164 @@
+/*
+ * Copyright (C) 2008-2009 Google Inc.
+ * 
+ * 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 android.inputmethodservice;
+
+import com.android.internal.R;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.content.res.TypedArray;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.graphics.Typeface;
+import android.graphics.Paint.Align;
+import android.graphics.drawable.Drawable;
+import android.inputmethodservice.Keyboard.Key;
+import android.os.Handler;
+import android.os.Message;
+import android.os.Vibrator;
+import android.preference.PreferenceManager;
+import android.util.AttributeSet;
+import android.view.GestureDetector;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewConfiguration;
+import android.view.ViewGroup.LayoutParams;
+import android.widget.Button;
+import android.widget.PopupWindow;
+import android.widget.TextView;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * A view that renders a virtual {@link Keyboard}. It handles rendering of keys and
+ * detecting key presses and touch movements.
+ * 
+ * @attr ref android.R.styleable#KeyboardView_keyBackground
+ * @attr ref android.R.styleable#KeyboardView_keyPreviewLayout
+ * @attr ref android.R.styleable#KeyboardView_keyPreviewOffset
+ * @attr ref android.R.styleable#KeyboardView_labelTextSize
+ * @attr ref android.R.styleable#KeyboardView_keyTextSize
+ * @attr ref android.R.styleable#KeyboardView_keyTextColor
+ * @attr ref android.R.styleable#KeyboardView_verticalCorrection
+ * @attr ref android.R.styleable#KeyboardView_popupLayout
+ */
+public class KeyboardView extends View implements View.OnClickListener {
+
+    /**
+     * Listener for virtual keyboard events.
+     */
+    public interface OnKeyboardActionListener {
+        
+        /**
+         * Called when the user presses a key. This is sent before the {@link #onKey} is called.
+         * For keys that repeat, this is only called once.
+         * @param primaryCode the unicode of the key being pressed. If the touch is not on a valid
+         * key, the value will be zero.
+         */
+        void onPress(int primaryCode);
+        
+        /**
+         * Called when the user releases a key. This is sent after the {@link #onKey} is called.
+         * For keys that repeat, this is only called once.
+         * @param primaryCode the code of the key that was released
+         */
+        void onRelease(int primaryCode);
+
+        /**
+         * Send a key press to the listener.
+         * @param primaryCode this is the key that was pressed
+         * @param keyCodes the codes for all the possible alternative keys
+         * with the primary code being the first. If the primary key code is
+         * a single character such as an alphabet or number or symbol, the alternatives
+         * will include other characters that may be on the same key or adjacent keys.
+         * These codes are useful to correct for accidental presses of a key adjacent to
+         * the intended key.
+         */
+        void onKey(int primaryCode, int[] keyCodes);
+
+        /**
+         * Sends a sequence of characters to the listener.
+         * @param text the sequence of characters to be displayed.
+         */
+        void onText(CharSequence text);
+        
+        /**
+         * Called when the user quickly moves the finger from right to left.
+         */
+        void swipeLeft();
+        
+        /**
+         * Called when the user quickly moves the finger from left to right.
+         */
+        void swipeRight();
+        
+        /**
+         * Called when the user quickly moves the finger from up to down.
+         */
+        void swipeDown();
+        
+        /**
+         * Called when the user quickly moves the finger from down to up.
+         */
+        void swipeUp();
+    }
+
+    private static final boolean DEBUG = false;
+    private static final int NOT_A_KEY = -1;
+    private static final int[] KEY_DELETE = { Keyboard.KEYCODE_DELETE };
+    private static final int[] LONG_PRESSABLE_STATE_SET = { R.attr.state_long_pressable };   
+    
+    private Keyboard mKeyboard;
+    private int mCurrentKeyIndex = NOT_A_KEY;
+    private int mLabelTextSize;
+    private int mKeyTextSize;
+    private int mKeyTextColor;
+    private float mShadowRadius;
+    private int mShadowColor;
+    private float mBackgroundDimAmount;
+    
+    private TextView mPreviewText;
+    private PopupWindow mPreviewPopup;
+    private int mPreviewTextSizeLarge;
+    private int mPreviewOffset;
+    private int mPreviewHeight;
+    private int[] mOffsetInWindow;
+
+    private PopupWindow mPopupKeyboard;
+    private View mMiniKeyboardContainer;
+    private KeyboardView mMiniKeyboard;
+    private boolean mMiniKeyboardOnScreen;
+    private View mPopupParent;
+    private int mMiniKeyboardOffsetX;
+    private int mMiniKeyboardOffsetY;
+    private Map<Key,View> mMiniKeyboardCache;
+    private int[] mWindowOffset;
+    private Key[] mKeys;
+
+    /** Listener for {@link OnKeyboardActionListener}. */
+    private OnKeyboardActionListener mKeyboardActionListener;
+    
+    private static final int MSG_SHOW_PREVIEW = 1;
+    private static final int MSG_REMOVE_PREVIEW = 2;
+    private static final int MSG_REPEAT = 3;
+    private static final int MSG_LONGPRESS = 4;
+    
+    private static final int DELAY_BEFORE_PREVIEW = 70;
+    private static final int DELAY_AFTER_PREVIEW = 60;
+    
+    private int mVerticalCorrection;
+    private int mProximityThreshold;
+
+    private boolean mPreviewCentered = false;
+    private boolean mShowPreview = true;
+    private boolean mShowTouchPoints = true;
+    private int mPopupPreviewX;
+    private int mPopupPreviewY;
+
+    private int mLastX;
+    private int mLastY;
+    private int mStartX;
+    private int mStartY;
+
+    private boolean mProximityCorrectOn;
+    
+    private Paint mPaint;
+    private Rect mPadding;
+    
+    private long mDownTime;
+    private long mLastMoveTime;
+    private int mLastKey;
+    private int mLastCodeX;
+    private int mLastCodeY;
+    private int mCurrentKey = NOT_A_KEY;
+    private long mLastKeyTime;
+    private long mCurrentKeyTime;
+    private int[] mKeyIndices = new int[12];
+    private GestureDetector mGestureDetector;
+    private int mPopupX;
+    private int mPopupY;
+    private int mRepeatKeyIndex = NOT_A_KEY;
+    private int mPopupLayout;
+    private boolean mAbortKey;
+    private Key mInvalidatedKey;
+    private Rect mClipRegion = new Rect(0, 0, 0, 0);
+    
+    private Drawable mKeyBackground;
+
+    private static final int REPEAT_INTERVAL = 50; // ~20 keys per second
+    private static final int REPEAT_START_DELAY = 400;
+    private static final int LONGPRESS_TIMEOUT = 800;
+    // Deemed to be too short : ViewConfiguration.getLongPressTimeout();
+
+    private static int MAX_NEARBY_KEYS = 12;
+    private int[] mDistances = new int[MAX_NEARBY_KEYS];
+
+    // For multi-tap
+    private int mLastSentIndex;
+    private int mTapCount;
+    private long mLastTapTime;
+    private boolean mInMultiTap;
+    private static final int MULTITAP_INTERVAL = 800; // milliseconds
+    private StringBuilder mPreviewLabel = new StringBuilder(1);
+
+    Handler mHandler = new Handler() {
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case MSG_SHOW_PREVIEW:
+                    showKey(msg.arg1);
+                    break;
+                case MSG_REMOVE_PREVIEW:
+                    mPreviewText.setVisibility(INVISIBLE);
+                    break;
+                case MSG_REPEAT:
+                    if (repeatKey()) {
+                        Message repeat = Message.obtain(this, MSG_REPEAT);
+                        sendMessageDelayed(repeat, REPEAT_INTERVAL);                        
+                    }
+                    break;
+                case MSG_LONGPRESS:
+                    openPopupIfRequired((MotionEvent) msg.obj);
+                    break;
+            }
+        }
+    };
+
+    public KeyboardView(Context context, AttributeSet attrs) {
+        this(context, attrs, com.android.internal.R.attr.keyboardViewStyle);
+    }
+
+    public KeyboardView(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+
+        TypedArray a =
+            context.obtainStyledAttributes(
+                attrs, android.R.styleable.KeyboardView, defStyle, 0);
+
+        LayoutInflater inflate =
+                (LayoutInflater) context
+                        .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+
+        int previewLayout = 0;
+        int keyTextSize = 0;
+
+        int n = a.getIndexCount();
+        
+        for (int i = 0; i < n; i++) {
+            int attr = a.getIndex(i);
+
+            switch (attr) {
+            case com.android.internal.R.styleable.KeyboardView_keyBackground:
+                mKeyBackground = a.getDrawable(attr);
+                break;
+            case com.android.internal.R.styleable.KeyboardView_verticalCorrection:
+                mVerticalCorrection = a.getDimensionPixelOffset(attr, 0);
+                break;
+            case com.android.internal.R.styleable.KeyboardView_keyPreviewLayout:
+                previewLayout = a.getResourceId(attr, 0);
+                break;
+            case com.android.internal.R.styleable.KeyboardView_keyPreviewOffset:
+                mPreviewOffset = a.getDimensionPixelOffset(attr, 0);
+                break;
+            case com.android.internal.R.styleable.KeyboardView_keyPreviewHeight:
+                mPreviewHeight = a.getDimensionPixelSize(attr, 80);
+                break;
+            case com.android.internal.R.styleable.KeyboardView_keyTextSize:
+                mKeyTextSize = a.getDimensionPixelSize(attr, 18);
+                break;
+            case com.android.internal.R.styleable.KeyboardView_keyTextColor:
+                mKeyTextColor = a.getColor(attr, 0xFF000000);
+                break;
+            case com.android.internal.R.styleable.KeyboardView_labelTextSize:
+                mLabelTextSize = a.getDimensionPixelSize(attr, 14);
+                break;
+            case com.android.internal.R.styleable.KeyboardView_popupLayout:
+                mPopupLayout = a.getResourceId(attr, 0);
+                break;
+            case com.android.internal.R.styleable.KeyboardView_shadowColor:
+                mShadowColor = a.getColor(attr, 0);
+                break;
+            case com.android.internal.R.styleable.KeyboardView_shadowRadius:
+                mShadowRadius = a.getFloat(attr, 0f);
+                break;
+            }
+        }
+        
+        a = mContext.obtainStyledAttributes(
+                com.android.internal.R.styleable.Theme);
+        mBackgroundDimAmount = a.getFloat(android.R.styleable.Theme_backgroundDimAmount, 0.5f);
+
+        mPreviewPopup = new PopupWindow(context);
+        if (previewLayout != 0) {
+            mPreviewText = (TextView) inflate.inflate(previewLayout, null);
+            mPreviewTextSizeLarge = (int) mPreviewText.getTextSize();
+            mPreviewPopup.setContentView(mPreviewText);
+            mPreviewPopup.setBackgroundDrawable(null);
+        } else {
+            mShowPreview = false;
+        }
+        
+        mPreviewPopup.setTouchable(false);
+        
+        mPopupKeyboard = new PopupWindow(context);
+        mPopupKeyboard.setBackgroundDrawable(null);
+        //mPopupKeyboard.setClippingEnabled(false);
+        
+        mPopupParent = this;
+        //mPredicting = true;
+        
+        mPaint = new Paint();
+        mPaint.setAntiAlias(true);
+        mPaint.setTextSize(keyTextSize);
+        mPaint.setTextAlign(Align.CENTER);
+
+        mPadding = new Rect(0, 0, 0, 0);
+        mMiniKeyboardCache = new HashMap<Key,View>();
+        mKeyBackground.getPadding(mPadding);
+        
+        resetMultiTap();
+        initGestureDetector();
+    }
+
+    private void initGestureDetector() {
+        mGestureDetector = new GestureDetector(getContext(), new GestureDetector.SimpleOnGestureListener() {
+            @Override
+            public boolean onFling(MotionEvent me1, MotionEvent me2, 
+                    float velocityX, float velocityY) {
+                final float absX = Math.abs(velocityX);
+                final float absY = Math.abs(velocityY);
+                if (velocityX > 500 && absY < absX) {
+                    swipeRight();
+                    return true;
+                } else if (velocityX < -500 && absY < absX) {
+                    swipeLeft();
+                    return true;
+                } else if (velocityY < -500 && absX < absY) {
+                    swipeUp();
+                    return true;
+                } else if (velocityY > 500 && absX < 200) {
+                    swipeDown();
+                    return true;
+                } else if (absX > 800 || absY > 800) {
+                    return true;
+                }
+                return false;
+            }
+        });
+
+        mGestureDetector.setIsLongpressEnabled(false);
+    }
+
+    public void setOnKeyboardActionListener(OnKeyboardActionListener listener) {
+        mKeyboardActionListener = listener;
+    }
+
+    /**
+     * Returns the {@link OnKeyboardActionListener} object.
+     * @return the listener attached to this keyboard
+     */
+    protected OnKeyboardActionListener getOnKeyboardActionListener() {
+        return mKeyboardActionListener;
+    }
+
+    /**
+     * Attaches a keyboard to this view. The keyboard can be switched at any time and the
+     * view will re-layout itself to accommodate the keyboard.
+     * @see Keyboard
+     * @see #getKeyboard()
+     * @param keyboard the keyboard to display in this view
+     */
+    public void setKeyboard(Keyboard keyboard) {
+        if (mKeyboard != null) {
+            showPreview(NOT_A_KEY);
+        }
+        mKeyboard = keyboard;
+        List<Key> keys = mKeyboard.getKeys();
+        mKeys = keys.toArray(new Key[keys.size()]);
+        requestLayout();
+        invalidate();
+        computeProximityThreshold(keyboard);
+        mMiniKeyboardCache.clear(); // Not really necessary to do every time, but will free up views
+    }
+
+    /**
+     * Returns the current keyboard being displayed by this view.
+     * @return the currently attached keyboard
+     * @see #setKeyboard(Keyboard)
+     */
+    public Keyboard getKeyboard() {
+        return mKeyboard;
+    }
+    
+    /**
+     * Sets the state of the shift key of the keyboard, if any.
+     * @param shifted whether or not to enable the state of the shift key
+     * @return true if the shift key state changed, false if there was no change
+     * @see KeyboardView#isShifted()
+     */
+    public boolean setShifted(boolean shifted) {
+        if (mKeyboard != null) {
+            if (mKeyboard.setShifted(shifted)) {
+                // The whole keyboard probably needs to be redrawn
+                invalidate();
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Returns the state of the shift key of the keyboard, if any.
+     * @return true if the shift is in a pressed state, false otherwise. If there is
+     * no shift key on the keyboard or there is no keyboard attached, it returns false.
+     * @see KeyboardView#setShifted(boolean)
+     */
+    public boolean isShifted() {
+        if (mKeyboard != null) {
+            return mKeyboard.isShifted();
+        }
+        return false;
+    }
+
+    /**
+     * Enables or disables the key feedback popup. This is a popup that shows a magnified
+     * version of the depressed key. By default the preview is enabled. 
+     * @param previewEnabled whether or not to enable the key feedback popup
+     * @see #isPreviewEnabled()
+     */
+    public void setPreviewEnabled(boolean previewEnabled) {
+        mShowPreview = previewEnabled;
+    }
+
+    /**
+     * Returns the enabled state of the key feedback popup.
+     * @return whether or not the key feedback popup is enabled
+     * @see #setPreviewEnabled(boolean)
+     */
+    public boolean isPreviewEnabled() {
+        return mShowPreview;
+    }
+    
+    public void setVerticalCorrection(int verticalOffset) {
+        
+    }
+    public void setPopupParent(View v) {
+        mPopupParent = v;
+    }
+    
+    public void setPopupOffset(int x, int y) {
+        mMiniKeyboardOffsetX = x;
+        mMiniKeyboardOffsetY = y;
+        if (mPreviewPopup.isShowing()) {
+            mPreviewPopup.dismiss();
+        }
+    }
+
+    /**
+     * Enables or disables proximity correction. When enabled, {@link OnKeyboardActionListener#onKey}
+     * gets called with key codes for adjacent keys. Otherwise only the primary code is returned.
+     * @param enabled whether or not the proximity correction is enabled
+     * @hide Pending API Council approval
+     */
+    public void setProximityCorrectionEnabled(boolean enabled) {
+        mProximityCorrectOn = enabled;
+    }
+
+    /**
+     * Returns the enabled state of the proximity correction.
+     * @return true if proximity correction is enabled, false otherwise
+     * @hide Pending API Council approval
+     */
+    public boolean isProximityCorrectionEnabled() {
+        return mProximityCorrectOn;
+    }
+
+    /** 
+     * Popup keyboard close button clicked.
+     * @hide 
+     */
+    public void onClick(View v) {
+        dismissPopupKeyboard();
+    }
+
+    private CharSequence adjustCase(CharSequence label) {
+        if (mKeyboard.isShifted() && label != null && label.length() == 1
+                && Character.isLowerCase(label.charAt(0))) {
+            label = label.toString().toUpperCase();
+        }
+        return label;
+    }
+
+    @Override
+    public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        // Round up a little
+        if (mKeyboard == null) {
+            setMeasuredDimension(mPaddingLeft + mPaddingRight, mPaddingTop + mPaddingBottom);
+        } else {
+            int width = mKeyboard.getMinWidth() + mPaddingLeft + mPaddingRight;
+            if (MeasureSpec.getSize(widthMeasureSpec) < width + 10) {
+                width = MeasureSpec.getSize(widthMeasureSpec);
+            }
+            setMeasuredDimension(width, mKeyboard.getHeight() + mPaddingTop + mPaddingBottom);
+        }
+    }
+
+    /**
+     * Compute the average distance between adjacent keys (horizontally and vertically)
+     * and square it to get the proximity threshold. We use a square here and in computing
+     * the touch distance from a key's center to avoid taking a square root.
+     * @param keyboard
+     */
+    private void computeProximityThreshold(Keyboard keyboard) {
+        if (keyboard == null) return;
+        final Key[] keys = mKeys;
+        if (keys == null) return;
+        int length = keys.length;
+        int dimensionSum = 0;
+        for (int i = 0; i < length; i++) {
+            Key key = keys[i];
+            dimensionSum += Math.min(key.width, key.height) + key.gap;
+        }
+        if (dimensionSum < 0 || length == 0) return;
+        mProximityThreshold = (int) (dimensionSum * 1.4f / length);
+        mProximityThreshold *= mProximityThreshold; // Square it
+    }
+
+    @Override
+    public void onDraw(Canvas canvas) {
+        super.onDraw(canvas);
+        if (mKeyboard == null) return;
+        
+        final Paint paint = mPaint;
+        final Drawable keyBackground = mKeyBackground;
+        final Rect clipRegion = mClipRegion;
+        final Rect padding = mPadding;
+        final int kbdPaddingLeft = mPaddingLeft;
+        final int kbdPaddingTop = mPaddingTop;
+        final Key[] keys = mKeys;
+        final Key invalidKey = mInvalidatedKey;
+        //canvas.translate(0, mKeyboardPaddingTop);
+        paint.setAlpha(255);
+        paint.setColor(mKeyTextColor);
+        boolean drawSingleKey = false;
+        if (invalidKey != null && canvas.getClipBounds(clipRegion)) {
+//            System.out.println("Key bounds = " + (invalidKey.x + mPaddingLeft) + ","
+//                    + (invalidKey.y + mPaddingTop) + "," 
+//                    + (invalidKey.x + invalidKey.width + mPaddingLeft) + "," 
+//                    + (invalidKey.y + invalidKey.height + mPaddingTop));
+//            System.out.println("Clip bounds =" + clipRegion.toShortString());
+            // Is clipRegion completely contained within the invalidated key?
+            if (invalidKey.x + kbdPaddingLeft - 1 <= clipRegion.left &&
+                    invalidKey.y + kbdPaddingTop - 1 <= clipRegion.top &&
+                    invalidKey.x + invalidKey.width + kbdPaddingLeft + 1 >= clipRegion.right &&
+                    invalidKey.y + invalidKey.height + kbdPaddingTop + 1 >= clipRegion.bottom) {
+                drawSingleKey = true;
+            }
+        }
+        final int keyCount = keys.length;
+        for (int i = 0; i < keyCount; i++) {
+            final Key key = keys[i];
+            if (drawSingleKey && invalidKey != key) {
+                continue;
+            }
+            int[] drawableState = key.getCurrentDrawableState();
+            keyBackground.setState(drawableState);
+            
+            // Switch the character to uppercase if shift is pressed
+            String label = key.label == null? null : adjustCase(key.label).toString();
+            
+            final Rect bounds = keyBackground.getBounds();
+            if (key.width != bounds.right || 
+                    key.height != bounds.bottom) {
+                keyBackground.setBounds(0, 0, key.width, key.height);
+            }
+            canvas.translate(key.x + kbdPaddingLeft, key.y + kbdPaddingTop);
+            keyBackground.draw(canvas);
+            
+            if (label != null) {
+                // For characters, use large font. For labels like "Done", use small font.
+                if (label.length() > 1 && key.codes.length < 2) {
+                    paint.setTextSize(mLabelTextSize);
+                    paint.setTypeface(Typeface.DEFAULT_BOLD);
+                } else {
+                    paint.setTextSize(mKeyTextSize);
+                    paint.setTypeface(Typeface.DEFAULT);
+                }
+                // Draw a drop shadow for the text
+                paint.setShadowLayer(mShadowRadius, 0, 0, mShadowColor);
+                // Draw the text
+                canvas.drawText(label,
+                    (key.width - padding.left - padding.right) / 2
+                            + padding.left,
+                    (key.height - padding.top - padding.bottom) / 2
+                            + (paint.getTextSize() - paint.descent()) / 2 + padding.top,
+                    paint);
+                // Turn off drop shadow
+                paint.setShadowLayer(0, 0, 0, 0);
+            } else if (key.icon != null) {
+                final int drawableX = (key.width - padding.left - padding.right 
+                                - key.icon.getIntrinsicWidth()) / 2 + padding.left;
+                final int drawableY = (key.height - padding.top - padding.bottom 
+                        - key.icon.getIntrinsicHeight()) / 2 + padding.top;
+                canvas.translate(drawableX, drawableY);
+                key.icon.setBounds(0, 0, 
+                        key.icon.getIntrinsicWidth(), key.icon.getIntrinsicHeight());
+                key.icon.draw(canvas);
+                canvas.translate(-drawableX, -drawableY);
+            }
+            canvas.translate(-key.x - kbdPaddingLeft, -key.y - kbdPaddingTop);
+        }
+        mInvalidatedKey = null;
+        // Overlay a dark rectangle to dim the keyboard
+        if (mMiniKeyboardOnScreen) {
+            paint.setColor((int) (mBackgroundDimAmount * 0xFF) << 24);
+            canvas.drawRect(0, 0, getWidth(), getHeight(), paint);
+        }
+
+        if (DEBUG && mShowTouchPoints) {
+            paint.setAlpha(128);
+            paint.setColor(0xFFFF0000);
+            canvas.drawCircle(mStartX, mStartY, 3, paint);
+            canvas.drawLine(mStartX, mStartY, mLastX, mLastY, paint);
+            paint.setColor(0xFF0000FF);
+            canvas.drawCircle(mLastX, mLastY, 3, paint);
+            paint.setColor(0xFF00FF00);
+            canvas.drawCircle((mStartX + mLastX) / 2, (mStartY + mLastY) / 2, 2, paint);
+        }
+    }
+
+    private int getKeyIndices(int x, int y, int[] allKeys) {
+        final Key[] keys = mKeys;
+        final boolean shifted = mKeyboard.isShifted();
+        int primaryIndex = NOT_A_KEY;
+        int closestKey = NOT_A_KEY;
+        int closestKeyDist = mProximityThreshold + 1;
+        java.util.Arrays.fill(mDistances, Integer.MAX_VALUE);
+        int [] nearestKeyIndices = mKeyboard.getNearestKeys(x, y);
+        final int keyCount = nearestKeyIndices.length;
+        for (int i = 0; i < keyCount; i++) {
+            final Key key = keys[nearestKeyIndices[i]];
+            int dist = 0;
+            boolean isInside = key.isInside(x,y);
+            if (((mProximityCorrectOn 
+                    && (dist = key.squaredDistanceFrom(x, y)) < mProximityThreshold) 
+                    || isInside)
+                    && key.codes[0] > 32) {
+                // Find insertion point
+                final int nCodes = key.codes.length;
+                if (dist < closestKeyDist) {
+                    closestKeyDist = dist;
+                    closestKey = nearestKeyIndices[i];
+                }
+                
+                if (allKeys == null) continue;
+                
+                for (int j = 0; j < mDistances.length; j++) {
+                    if (mDistances[j] > dist) {
+                        // Make space for nCodes codes
+                        System.arraycopy(mDistances, j, mDistances, j + nCodes,
+                                mDistances.length - j - nCodes);
+                        System.arraycopy(allKeys, j, allKeys, j + nCodes,
+                                allKeys.length - j - nCodes);
+                        for (int c = 0; c < nCodes; c++) {
+                            allKeys[j + c] = key.codes[c];
+                            mDistances[j + c] = dist;
+                        }
+                        break;
+                    }
+                }
+            }
+            
+            if (isInside) {
+                primaryIndex = nearestKeyIndices[i];
+            }
+        }
+        if (primaryIndex == NOT_A_KEY) {
+            primaryIndex = closestKey;
+        }
+        return primaryIndex;
+    }
+
+    private void detectAndSendKey(int x, int y, long eventTime) {
+        int index = mCurrentKey;
+        if (index != NOT_A_KEY && index < mKeys.length) {
+            final Key key = mKeys[index];
+            if (key.text != null) {
+                mKeyboardActionListener.onText(key.text);
+                mKeyboardActionListener.onRelease(NOT_A_KEY);
+            } else {
+                int code = key.codes[0];
+                //TextEntryState.keyPressedAt(key, x, y);
+                int[] codes = new int[MAX_NEARBY_KEYS];
+                Arrays.fill(codes, NOT_A_KEY);
+                getKeyIndices(x, y, codes);
+                // Multi-tap
+                if (mInMultiTap) {
+                    if (mTapCount != -1) {
+                        mKeyboardActionListener.onKey(Keyboard.KEYCODE_DELETE, KEY_DELETE);
+                    } else {
+                        mTapCount = 0;
+                    }
+                    code = key.codes[mTapCount];
+                }
+                mKeyboardActionListener.onKey(code, codes);
+                mKeyboardActionListener.onRelease(code);
+            }
+            mLastSentIndex = index;
+            mLastTapTime = eventTime;
+        }
+    }
+
+    /**
+     * Handle multi-tap keys by producing the key label for the current multi-tap state.
+     */
+    private CharSequence getPreviewText(Key key) {
+        if (mInMultiTap) {
+            // Multi-tap
+            mPreviewLabel.setLength(0);
+            mPreviewLabel.append((char) key.codes[mTapCount < 0 ? 0 : mTapCount]);
+            return adjustCase(mPreviewLabel);
+        } else {
+            return adjustCase(key.label);
+        }
+    }
+    
+    private void showPreview(int keyIndex) {
+        int oldKeyIndex = mCurrentKeyIndex;
+        final PopupWindow previewPopup = mPreviewPopup;
+        
+        mCurrentKeyIndex = keyIndex;
+        // Release the old key and press the new key
+        final Key[] keys = mKeys;
+        if (oldKeyIndex != mCurrentKeyIndex) {
+            if (oldKeyIndex != NOT_A_KEY && keys.length > oldKeyIndex) {
+                keys[oldKeyIndex].onReleased(mCurrentKeyIndex == NOT_A_KEY);
+                invalidateKey(oldKeyIndex);
+            }
+            if (mCurrentKeyIndex != NOT_A_KEY && keys.length > mCurrentKeyIndex) {
+                keys[mCurrentKeyIndex].onPressed();
+                invalidateKey(mCurrentKeyIndex);
+            }
+        }
+        // If key changed and preview is on ...
+        if (oldKeyIndex != mCurrentKeyIndex && mShowPreview) {
+            mHandler.removeMessages(MSG_SHOW_PREVIEW);
+            if (previewPopup.isShowing()) {
+                if (keyIndex == NOT_A_KEY) {
+                    mHandler.sendMessageDelayed(mHandler
+                            .obtainMessage(MSG_REMOVE_PREVIEW), 
+                            DELAY_AFTER_PREVIEW);
+                }
+            }
+            if (keyIndex != NOT_A_KEY) {
+                if (previewPopup.isShowing() && mPreviewText.getVisibility() == VISIBLE) {
+                    // Show right away, if it's already visible and finger is moving around
+                    showKey(keyIndex);
+                } else {
+                    mHandler.sendMessageDelayed(
+                            mHandler.obtainMessage(MSG_SHOW_PREVIEW, keyIndex, 0), 
+                            DELAY_BEFORE_PREVIEW);
+                }
+            }
+        }
+    }
+    
+    private void showKey(final int keyIndex) {
+        final PopupWindow previewPopup = mPreviewPopup;
+        final Key[] keys = mKeys;
+        Key key = keys[keyIndex];
+        if (key.icon != null) {
+            mPreviewText.setCompoundDrawables(null, null, null, 
+                    key.iconPreview != null ? key.iconPreview : key.icon);
+            mPreviewText.setText(null);
+        } else {
+            mPreviewText.setCompoundDrawables(null, null, null, null);
+            mPreviewText.setText(getPreviewText(key));
+            if (key.label.length() > 1 && key.codes.length < 2) {
+                mPreviewText.setTextSize(mKeyTextSize);
+                mPreviewText.setTypeface(Typeface.DEFAULT_BOLD);
+            } else {
+                mPreviewText.setTextSize(mPreviewTextSizeLarge);
+                mPreviewText.setTypeface(Typeface.DEFAULT);
+            }
+        }
+        mPreviewText.measure(MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED), 
+                MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
+        int popupWidth = Math.max(mPreviewText.getMeasuredWidth(), key.width 
+                + mPreviewText.getPaddingLeft() + mPreviewText.getPaddingRight());
+        final int popupHeight = mPreviewHeight;
+        LayoutParams lp = mPreviewText.getLayoutParams();
+        if (lp != null) {
+            lp.width = popupWidth;
+            lp.height = popupHeight;
+        }
+        if (!mPreviewCentered) {
+            mPopupPreviewX = key.x - mPreviewText.getPaddingLeft() + mPaddingLeft;
+            mPopupPreviewY = key.y - popupHeight + mPreviewOffset;
+        } else {
+            // TODO: Fix this if centering is brought back
+            mPopupPreviewX = 160 - mPreviewText.getMeasuredWidth() / 2;
+            mPopupPreviewY = - mPreviewText.getMeasuredHeight();
+        }
+        mHandler.removeMessages(MSG_REMOVE_PREVIEW);
+        if (mOffsetInWindow == null) {
+            mOffsetInWindow = new int[2];
+            getLocationInWindow(mOffsetInWindow);
+            mOffsetInWindow[0] += mMiniKeyboardOffsetX; // Offset may be zero
+            mOffsetInWindow[1] += mMiniKeyboardOffsetY; // Offset may be zero
+        }
+        // Set the preview background state
+        mPreviewText.getBackground().setState(
+                key.popupResId != 0 ? LONG_PRESSABLE_STATE_SET : EMPTY_STATE_SET);
+        if (previewPopup.isShowing()) {
+            previewPopup.update(mPopupPreviewX + mOffsetInWindow[0],
+                    mPopupPreviewY + mOffsetInWindow[1], 
+                    popupWidth, popupHeight);
+        } else {
+            previewPopup.setWidth(popupWidth);
+            previewPopup.setHeight(popupHeight);
+            previewPopup.showAtLocation(mPopupParent, Gravity.NO_GRAVITY, 
+                    mPopupPreviewX + mOffsetInWindow[0], 
+                    mPopupPreviewY + mOffsetInWindow[1]);
+        }
+        mPreviewText.setVisibility(VISIBLE);
+    }
+
+    private void invalidateKey(int keyIndex) {
+        if (keyIndex < 0 || keyIndex >= mKeys.length) {
+            return;
+        }
+        final Key key = mKeys[keyIndex];
+        mInvalidatedKey = key;
+        invalidate(key.x + mPaddingLeft, key.y + mPaddingTop, 
+                key.x + key.width + mPaddingLeft, key.y + key.height + mPaddingTop);
+    }
+
+    private boolean openPopupIfRequired(MotionEvent me) {
+        // Check if we have a popup layout specified first.
+        if (mPopupLayout == 0) {
+            return false;
+        }
+        if (mCurrentKey < 0 || mCurrentKey >= mKeys.length) {
+            return false;
+        }
+
+        Key popupKey = mKeys[mCurrentKey];        
+        boolean result = onLongPress(popupKey);
+        if (result) {
+            mAbortKey = true;
+            showPreview(NOT_A_KEY);
+        }
+        return result;
+    }
+
+    /**
+     * Called when a key is long pressed. By default this will open any popup keyboard associated
+     * with this key through the attributes popupLayout and popupCharacters.
+     * @param popupKey the key that was long pressed
+     * @return true if the long press is handled, false otherwise. Subclasses should call the
+     * method on the base class if the subclass doesn't wish to handle the call.
+     */
+    protected boolean onLongPress(Key popupKey) {
+        int popupKeyboardId = popupKey.popupResId;
+
+        if (popupKeyboardId != 0) {
+            mMiniKeyboardContainer = mMiniKeyboardCache.get(popupKey);
+            if (mMiniKeyboardContainer == null) {
+                LayoutInflater inflater = (LayoutInflater) getContext().getSystemService(
+                        Context.LAYOUT_INFLATER_SERVICE);
+                mMiniKeyboardContainer = inflater.inflate(mPopupLayout, null);
+                mMiniKeyboard = (KeyboardView) mMiniKeyboardContainer.findViewById(
+                        com.android.internal.R.id.keyboardView);
+                View closeButton = mMiniKeyboardContainer.findViewById(
+                        com.android.internal.R.id.button_close);
+                if (closeButton != null) closeButton.setOnClickListener(this);
+                mMiniKeyboard.setOnKeyboardActionListener(new OnKeyboardActionListener() {
+                    public void onKey(int primaryCode, int[] keyCodes) {
+                        mKeyboardActionListener.onKey(primaryCode, keyCodes);
+                        dismissPopupKeyboard();
+                    }
+                    
+                    public void onText(CharSequence text) {
+                        mKeyboardActionListener.onText(text);
+                        dismissPopupKeyboard();
+                    }
+                    
+                    public void swipeLeft() { }
+                    public void swipeRight() { }
+                    public void swipeUp() { }
+                    public void swipeDown() { }
+                    public void onPress(int primaryCode) {
+                        mKeyboardActionListener.onPress(primaryCode);
+                    }
+                    public void onRelease(int primaryCode) {
+                        mKeyboardActionListener.onRelease(primaryCode);
+                    }
+                });
+                //mInputView.setSuggest(mSuggest);
+                Keyboard keyboard;
+                if (popupKey.popupCharacters != null) {
+                    keyboard = new Keyboard(getContext(), popupKeyboardId, 
+                            popupKey.popupCharacters, -1, getPaddingLeft() + getPaddingRight());
+                } else {
+                    keyboard = new Keyboard(getContext(), popupKeyboardId);
+                }
+                mMiniKeyboard.setKeyboard(keyboard);
+                mMiniKeyboard.setPopupParent(this);
+                mMiniKeyboardContainer.measure(
+                        MeasureSpec.makeMeasureSpec(getWidth(), MeasureSpec.AT_MOST), 
+                        MeasureSpec.makeMeasureSpec(getHeight(), MeasureSpec.AT_MOST));
+                
+                mMiniKeyboardCache.put(popupKey, mMiniKeyboardContainer);
+            } else {
+                mMiniKeyboard = (KeyboardView) mMiniKeyboardContainer.findViewById(
+                        com.android.internal.R.id.keyboardView);
+            }
+            if (mWindowOffset == null) {
+                mWindowOffset = new int[2];
+                getLocationInWindow(mWindowOffset);
+            }
+            mPopupX = popupKey.x + mPaddingLeft;
+            mPopupY = popupKey.y + mPaddingTop;
+            mPopupX = mPopupX + popupKey.width - mMiniKeyboardContainer.getMeasuredWidth();
+            mPopupY = mPopupY - mMiniKeyboardContainer.getMeasuredHeight();
+            final int x = mPopupX + mMiniKeyboardContainer.getPaddingRight() + mWindowOffset[0];
+            final int y = mPopupY + mMiniKeyboardContainer.getPaddingBottom() + mWindowOffset[1];
+            mMiniKeyboard.setPopupOffset(x < 0 ? 0 : x, y);
+            mMiniKeyboard.setShifted(isShifted());
+            mPopupKeyboard.setContentView(mMiniKeyboardContainer);
+            mPopupKeyboard.setWidth(mMiniKeyboardContainer.getMeasuredWidth());
+            mPopupKeyboard.setHeight(mMiniKeyboardContainer.getMeasuredHeight());
+            mPopupKeyboard.showAtLocation(this, Gravity.NO_GRAVITY, x, y);
+            mMiniKeyboardOnScreen = true;
+            //mMiniKeyboard.onTouchEvent(getTranslatedEvent(me));
+            invalidate();
+            return true;
+        }
+        return false;
+    }
+    
+    @Override
+    public boolean onTouchEvent(MotionEvent me) {
+        int touchX = (int) me.getX() - mPaddingLeft;
+        int touchY = (int) me.getY() + mVerticalCorrection - mPaddingTop;
+        int action = me.getAction();
+        long eventTime = me.getEventTime();
+        int keyIndex = getKeyIndices(touchX, touchY, null);
+        
+        if (mGestureDetector.onTouchEvent(me)) {
+            showPreview(NOT_A_KEY);
+            mHandler.removeMessages(MSG_REPEAT);
+            mHandler.removeMessages(MSG_LONGPRESS);            
+            return true;
+        }
+        
+        // Needs to be called after the gesture detector gets a turn, as it may have
+        // displayed the mini keyboard
+        if (mMiniKeyboardOnScreen) {
+            return true;
+        }
+
+        switch (action) {
+            case MotionEvent.ACTION_DOWN:
+                mAbortKey = false;
+                mStartX = touchX;
+                mStartY = touchY;
+                mLastCodeX = touchX;
+                mLastCodeY = touchY;
+                mLastKeyTime = 0;
+                mCurrentKeyTime = 0;
+                mLastKey = NOT_A_KEY;
+                mCurrentKey = keyIndex;
+                mDownTime = me.getEventTime();
+                mLastMoveTime = mDownTime;
+                checkMultiTap(eventTime, keyIndex);
+                mKeyboardActionListener.onPress(keyIndex != NOT_A_KEY ? 
+                        mKeys[keyIndex].codes[0] : 0);
+                if (mCurrentKey >= 0 && mKeys[mCurrentKey].repeatable) {
+                    mRepeatKeyIndex = mCurrentKey;
+                    repeatKey();
+                    Message msg = mHandler.obtainMessage(MSG_REPEAT);
+                    mHandler.sendMessageDelayed(msg, REPEAT_START_DELAY);
+                }
+                if (mCurrentKey != NOT_A_KEY) {
+                    Message msg = mHandler.obtainMessage(MSG_LONGPRESS, me);
+                    mHandler.sendMessageDelayed(msg, LONGPRESS_TIMEOUT);
+                }
+                showPreview(keyIndex);
+                break;
+
+            case MotionEvent.ACTION_MOVE:
+                boolean continueLongPress = false;
+                if (keyIndex != NOT_A_KEY) {
+                    if (mCurrentKey == NOT_A_KEY) {
+                        mCurrentKey = keyIndex;
+                        mCurrentKeyTime = eventTime - mDownTime;
+                    } else {
+                        if (keyIndex == mCurrentKey) {
+                            mCurrentKeyTime += eventTime - mLastMoveTime;
+                            continueLongPress = true;
+                        } else {
+                            resetMultiTap();
+                            mLastKey = mCurrentKey;
+                            mLastCodeX = mLastX;
+                            mLastCodeY = mLastY;
+                            mLastKeyTime =
+                                    mCurrentKeyTime + eventTime - mLastMoveTime;
+                            mCurrentKey = keyIndex;
+                            mCurrentKeyTime = 0;
+                        }
+                    }
+                    if (keyIndex != mRepeatKeyIndex) {
+                        mHandler.removeMessages(MSG_REPEAT);
+                        mRepeatKeyIndex = NOT_A_KEY;
+                    }
+                }
+                if (!continueLongPress) {
+                    // Cancel old longpress
+                    mHandler.removeMessages(MSG_LONGPRESS);
+                    // Start new longpress if key has changed
+                    if (keyIndex != NOT_A_KEY) {
+                        Message msg = mHandler.obtainMessage(MSG_LONGPRESS, me);
+                        mHandler.sendMessageDelayed(msg, LONGPRESS_TIMEOUT);
+                    }
+                }
+                showPreview(keyIndex);
+                break;
+
+            case MotionEvent.ACTION_UP:
+                mHandler.removeMessages(MSG_SHOW_PREVIEW);
+                mHandler.removeMessages(MSG_REPEAT);
+                mHandler.removeMessages(MSG_LONGPRESS);
+                if (keyIndex == mCurrentKey) {
+                    mCurrentKeyTime += eventTime - mLastMoveTime;
+                } else {
+                    resetMultiTap();
+                    mLastKey = mCurrentKey;
+                    mLastKeyTime = mCurrentKeyTime + eventTime - mLastMoveTime;
+                    mCurrentKey = keyIndex;
+                    mCurrentKeyTime = 0;
+                }
+                if (mCurrentKeyTime < mLastKeyTime && mLastKey != NOT_A_KEY) {
+                    mCurrentKey = mLastKey;
+                    touchX = mLastCodeX;
+                    touchY = mLastCodeY;
+                }
+                showPreview(NOT_A_KEY);
+                Arrays.fill(mKeyIndices, NOT_A_KEY);
+                invalidateKey(keyIndex);
+                // If we're not on a repeating key (which sends on a DOWN event)
+                if (mRepeatKeyIndex == NOT_A_KEY && !mMiniKeyboardOnScreen && !mAbortKey) {
+                    detectAndSendKey(touchX, touchY, eventTime);
+                }
+                mRepeatKeyIndex = NOT_A_KEY;
+                break;
+        }
+        mLastX = touchX;
+        mLastY = touchY;
+        return true;
+    }
+
+    private boolean repeatKey() {
+        Key key = mKeys[mRepeatKeyIndex];
+        detectAndSendKey(key.x, key.y, mLastTapTime);
+        return true;
+    }
+    
+    protected void swipeRight() {
+        mKeyboardActionListener.swipeRight();
+    }
+    
+    protected void swipeLeft() {
+        mKeyboardActionListener.swipeLeft();
+    }
+
+    protected void swipeUp() {
+        mKeyboardActionListener.swipeUp();
+    }
+
+    protected void swipeDown() {
+        mKeyboardActionListener.swipeDown();
+    }
+
+    public void closing() {
+        if (mPreviewPopup.isShowing()) {
+            mPreviewPopup.dismiss();
+        }
+        mHandler.removeMessages(MSG_REPEAT);
+        mHandler.removeMessages(MSG_LONGPRESS);
+        mHandler.removeMessages(MSG_SHOW_PREVIEW);
+        
+        dismissPopupKeyboard();
+        
+        mMiniKeyboardCache.clear();
+    }
+    
+    @Override
+    public void onDetachedFromWindow() {
+        super.onDetachedFromWindow();
+        closing();
+    }
+
+    private void dismissPopupKeyboard() {
+        if (mPopupKeyboard.isShowing()) {
+            mPopupKeyboard.dismiss();
+            mMiniKeyboardOnScreen = false;
+            invalidate();
+        }
+    }
+    
+    public boolean handleBack() {
+        if (mPopupKeyboard.isShowing()) {
+            dismissPopupKeyboard();
+            return true;
+        }
+        return false;
+    }
+
+    private void resetMultiTap() {
+        mLastSentIndex = NOT_A_KEY;
+        mTapCount = 0;
+        mLastTapTime = -1;
+        mInMultiTap = false;
+    }
+    
+    private void checkMultiTap(long eventTime, int keyIndex) {
+        if (keyIndex == NOT_A_KEY) return;
+        Key key = mKeys[keyIndex];
+        if (key.codes.length > 1) {
+            mInMultiTap = true;
+            if (eventTime < mLastTapTime + MULTITAP_INTERVAL
+                    && keyIndex == mLastSentIndex) {
+                mTapCount = (mTapCount + 1) % key.codes.length;
+                return;
+            } else {
+                mTapCount = -1;
+                return;
+            }
+        }
+        if (eventTime > mLastTapTime + MULTITAP_INTERVAL || keyIndex != mLastSentIndex) {
+            resetMultiTap();
+        }
+    }
+}
diff --git a/core/java/android/inputmethodservice/SoftInputWindow.java b/core/java/android/inputmethodservice/SoftInputWindow.java
new file mode 100644
index 0000000..c37845f
--- /dev/null
+++ b/core/java/android/inputmethodservice/SoftInputWindow.java
@@ -0,0 +1,155 @@
+/*
+ * Copyright (C) 2007-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 android.inputmethodservice;
+
+import android.app.Dialog;
+import android.content.Context;
+import android.content.pm.ActivityInfo;
+import android.os.IBinder;
+import android.view.Gravity;
+import android.view.WindowManager;
+
+/**
+ * A SoftInputWindow is a Dialog that is intended to be used for a top-level input
+ * method window.  It will be displayed along the edge of the screen, moving
+ * the application user interface away from it so that the focused item is
+ * always visible.
+ */
+class SoftInputWindow extends Dialog {
+
+    /**
+     * Create a DockWindow that uses the default style.
+     * 
+     * @param context The Context the DockWindow is to run it. In particular, it
+     *        uses the window manager and theme in this context to present its
+     *        UI.
+     */
+    public SoftInputWindow(Context context) {
+        super(context, com.android.internal.R.style.Theme_InputMethod);
+        initDockWindow();
+    }
+
+    public void setToken(IBinder token) {
+        WindowManager.LayoutParams lp = getWindow().getAttributes();
+        lp.token = token;
+        getWindow().setAttributes(lp);
+    }
+    
+    /**
+     * Create a DockWindow that uses a custom style.
+     * 
+     * @param context The Context in which the DockWindow should run. In
+     *        particular, it uses the window manager and theme from this context
+     *        to present its UI.
+     * @param theme A style resource describing the theme to use for the window.
+     *        See <a href="{@docRoot}reference/available-resources.html#stylesandthemes">Style
+     *        and Theme Resources</a> for more information about defining and
+     *        using styles. This theme is applied on top of the current theme in
+     *        <var>context</var>. If 0, the default dialog theme will be used.
+     */
+    public SoftInputWindow(Context context, int theme) {
+        super(context, theme);
+        initDockWindow();
+    }
+
+    /**
+     * Get the size of the DockWindow.
+     * 
+     * @return If the DockWindow sticks to the top or bottom of the screen, the
+     *         return value is the height of the DockWindow, and its width is
+     *         equal to the width of the screen; If the DockWindow sticks to the
+     *         left or right of the screen, the return value is the width of the
+     *         DockWindow, and its height is equal to the height of the screen.
+     */
+    public int getSize() {
+        WindowManager.LayoutParams lp = getWindow().getAttributes();
+
+        if (lp.gravity == Gravity.TOP || lp.gravity == Gravity.BOTTOM) {
+            return lp.height;
+        } else {
+            return lp.width;
+        }
+    }
+
+    /**
+     * Set the size of the DockWindow.
+     * 
+     * @param size If the DockWindow sticks to the top or bottom of the screen,
+     *        <var>size</var> is the height of the DockWindow, and its width is
+     *        equal to the width of the screen; If the DockWindow sticks to the
+     *        left or right of the screen, <var>size</var> is the width of the
+     *        DockWindow, and its height is equal to the height of the screen.
+     */
+    public void setSize(int size) {
+        WindowManager.LayoutParams lp = getWindow().getAttributes();
+
+        if (lp.gravity == Gravity.TOP || lp.gravity == Gravity.BOTTOM) {
+            lp.width = -1;
+            lp.height = size;
+        } else {
+            lp.width = size;
+            lp.height = -1;
+        }
+        getWindow().setAttributes(lp);
+    }
+
+    /**
+     * Set which boundary of the screen the DockWindow sticks to.
+     * 
+     * @param gravity The boundary of the screen to stick. See {#link
+     *        android.view.Gravity.LEFT}, {#link android.view.Gravity.TOP},
+     *        {#link android.view.Gravity.BOTTOM}, {#link
+     *        android.view.Gravity.RIGHT}.
+     */
+    public void setGravity(int gravity) {
+        WindowManager.LayoutParams lp = getWindow().getAttributes();
+
+        boolean oldIsVertical = (lp.gravity == Gravity.TOP || lp.gravity == Gravity.BOTTOM);
+
+        lp.gravity = gravity;
+
+        boolean newIsVertical = (lp.gravity == Gravity.TOP || lp.gravity == Gravity.BOTTOM);
+
+        if (oldIsVertical != newIsVertical) {
+            int tmp = lp.width;
+            lp.width = lp.height;
+            lp.height = tmp;
+            getWindow().setAttributes(lp);
+        }
+    }
+
+    private void initDockWindow() {
+        WindowManager.LayoutParams lp = getWindow().getAttributes();
+
+        lp.type = WindowManager.LayoutParams.TYPE_INPUT_METHOD;
+        lp.setTitle("InputMethod");
+
+        lp.gravity = Gravity.BOTTOM;
+        lp.width = -1;
+        // Let the input method window's orientation follow sensor based rotation
+        // Turn this off for now, it is very problematic.
+        //lp.screenOrientation = ActivityInfo.SCREEN_ORIENTATION_USER;
+
+        getWindow().setAttributes(lp);
+        getWindow().setFlags(
+                WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN |
+                WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
+                WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN |
+                WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE |
+                WindowManager.LayoutParams.FLAG_DIM_BEHIND);
+    }
+}
diff --git a/core/java/android/inputmethodservice/package.html b/core/java/android/inputmethodservice/package.html
new file mode 100644
index 0000000..164349b
--- /dev/null
+++ b/core/java/android/inputmethodservice/package.html
@@ -0,0 +1,8 @@
+<html>
+<body>
+Base classes for writing input methods.  These APIs are not for use by
+normal applications, they are a framework specifically for writing input
+method components.  Implementations will typically derive from
+{@link android.inputmethodservice.InputMethodService}.
+</body>
+</html>
diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java
new file mode 100644
index 0000000..1429bc1
--- /dev/null
+++ b/core/java/android/net/ConnectivityManager.java
@@ -0,0 +1,294 @@
+/*
+ * 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 android.net;
+
+import android.annotation.SdkConstant;
+import android.annotation.SdkConstant.SdkConstantType;
+import android.os.RemoteException;
+
+/**
+ * Class that answers queries about the state of network connectivity. It also
+ * notifies applications when network connectivity changes. Get an instance
+ * of this class by calling
+ * {@link android.content.Context#getSystemService(String) Context.getSystemService(Context.CONNECTIVITY_SERVICE)}.
+ * <p>
+ * The primary responsibilities of this class are to:
+ * <ol>
+ * <li>Monitor network connections (Wi-Fi, GPRS, UMTS, etc.)</li>
+ * <li>Send broadcast intents when network connectivity changes</li>
+ * <li>Attempt to "fail over" to another network when connectivity to a network
+ * is lost</li>
+ * <li>Provide an API that allows applications to query the coarse-grained or fine-grained
+ * state of the available networks</li>
+ * </ol>
+ */
+public class ConnectivityManager
+{
+    /**
+     * A change in network connectivity has occurred. A connection has either
+     * been established or lost. The NetworkInfo for the affected network is
+     * sent as an extra; it should be consulted to see what kind of
+     * connectivity event occurred.
+     * <p/>
+     * If this is a connection that was the result of failing over from a
+     * disconnected network, then the FAILOVER_CONNECTION boolean extra is
+     * set to true.
+     * <p/>
+     * For a loss of connectivity, if the connectivity manager is attempting
+     * to connect (or has already connected) to another network, the
+     * NetworkInfo for the new network is also passed as an extra. This lets
+     * any receivers of the broadcast know that they should not necessarily
+     * tell the user that no data traffic will be possible. Instead, the
+     * reciever should expect another broadcast soon, indicating either that
+     * the failover attempt succeeded (and so there is still overall data
+     * connectivity), or that the failover attempt failed, meaning that all
+     * connectivity has been lost.
+     * <p/>
+     * For a disconnect event, the boolean extra EXTRA_NO_CONNECTIVITY
+     * is set to {@code true} if there are no connected networks at all.
+     */
+    public static final String CONNECTIVITY_ACTION = "android.net.conn.CONNECTIVITY_CHANGE";
+    /**
+     * The lookup key for a {@link NetworkInfo} object. Retrieve with
+     * {@link android.content.Intent#getParcelableExtra(String)}.
+     */
+    public static final String EXTRA_NETWORK_INFO = "networkInfo";
+    /**
+     * The lookup key for a boolean that indicates whether a connect event
+     * is for a network to which the connectivity manager was failing over
+     * following a disconnect on another network.
+     * Retrieve it with {@link android.content.Intent#getBooleanExtra(String,boolean)}.
+     */
+    public static final String EXTRA_IS_FAILOVER = "isFailover";
+    /**
+     * The lookup key for a {@link NetworkInfo} object. This is supplied when
+     * there is another network that it may be possible to connect to. Retrieve with
+     * {@link android.content.Intent#getParcelableExtra(String)}.
+     */
+    public static final String EXTRA_OTHER_NETWORK_INFO = "otherNetwork";
+    /**
+     * The lookup key for a boolean that indicates whether there is a
+     * complete lack of connectivity, i.e., no network is available.
+     * Retrieve it with {@link android.content.Intent#getBooleanExtra(String,boolean)}.
+     */
+    public static final String EXTRA_NO_CONNECTIVITY = "noConnectivity";
+    /**
+     * The lookup key for a string that indicates why an attempt to connect
+     * to a network failed. The string has no particular structure. It is
+     * intended to be used in notifications presented to users. Retrieve
+     * it with {@link android.content.Intent#getStringExtra(String)}.
+     */
+    public static final String EXTRA_REASON = "reason";
+    /**
+     * The lookup key for a string that provides optionally supplied
+     * extra information about the network state. The information
+     * may be passed up from the lower networking layers, and its
+     * meaning may be specific to a particular network type. Retrieve
+     * it with {@link android.content.Intent#getStringExtra(String)}.
+     */
+    public static final String EXTRA_EXTRA_INFO = "extraInfo";
+
+    /**
+     * Broadcast Action: The setting for background data usage has changed
+     * values. Use {@link #getBackgroundDataSetting()} to get the current value.
+     * <p>
+     * If an application uses the network in the background, it should listen
+     * for this broadcast and stop using the background data if the value is
+     * false.
+     */
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String ACTION_BACKGROUND_DATA_SETTING_CHANGED =
+            "android.net.conn.BACKGROUND_DATA_SETTING_CHANGED";
+
+    public static final int TYPE_MOBILE = 0;
+    public static final int TYPE_WIFI   = 1;
+
+    public static final int DEFAULT_NETWORK_PREFERENCE = TYPE_WIFI;
+
+    private IConnectivityManager mService;
+
+    static public boolean isNetworkTypeValid(int networkType) {
+        return networkType == TYPE_WIFI || networkType == TYPE_MOBILE;
+    }
+
+    public void setNetworkPreference(int preference) {
+        try {
+            mService.setNetworkPreference(preference);
+        } catch (RemoteException e) {
+        }
+    }
+
+    public int getNetworkPreference() {
+        try {
+            return mService.getNetworkPreference();
+        } catch (RemoteException e) {
+            return -1;
+        }
+    }
+
+    public NetworkInfo getActiveNetworkInfo() {
+        try {
+            return mService.getActiveNetworkInfo();
+        } catch (RemoteException e) {
+            return null;
+        }
+    }
+
+    public NetworkInfo getNetworkInfo(int networkType) {
+        try {
+            return mService.getNetworkInfo(networkType);
+        } catch (RemoteException e) {
+            return null;
+        }
+    }
+
+    public NetworkInfo[] getAllNetworkInfo() {
+        try {
+            return mService.getAllNetworkInfo();
+        } catch (RemoteException e) {
+            return null;
+        }
+    }
+
+    /** {@hide} */
+    public boolean setRadios(boolean turnOn) {
+        try {
+            return mService.setRadios(turnOn);
+        } catch (RemoteException e) {
+            return false;
+        }
+    }
+
+    /** {@hide} */
+    public boolean setRadio(int networkType, boolean turnOn) {
+        try {
+            return mService.setRadio(networkType, turnOn);
+        } catch (RemoteException e) {
+            return false;
+        }
+    }
+
+    /**
+     * Tells the underlying networking system that the caller wants to
+     * begin using the named feature. The interpretation of {@code feature}
+     * is completely up to each networking implementation.
+     * @param networkType specifies which network the request pertains to
+     * @param feature the name of the feature to be used
+     * @return an integer value representing the outcome of the request.
+     * The interpretation of this value is specific to each networking
+     * implementation+feature combination, except that the value {@code -1}
+     * always indicates failure.
+     */
+    public int startUsingNetworkFeature(int networkType, String feature) {
+        try {
+            return mService.startUsingNetworkFeature(networkType, feature);
+        } catch (RemoteException e) {
+            return -1;
+        }
+    }
+
+    /**
+     * Tells the underlying networking system that the caller is finished
+     * using the named feature. The interpretation of {@code feature}
+     * is completely up to each networking implementation.
+     * @param networkType specifies which network the request pertains to
+     * @param feature the name of the feature that is no longer needed
+     * @return an integer value representing the outcome of the request.
+     * The interpretation of this value is specific to each networking
+     * implementation+feature combination, except that the value {@code -1}
+     * always indicates failure.
+     */
+    public int stopUsingNetworkFeature(int networkType, String feature) {
+        try {
+            return mService.stopUsingNetworkFeature(networkType, feature);
+        } catch (RemoteException e) {
+            return -1;
+        }
+    }
+
+    /**
+     * Ensure that a network route exists to deliver traffic to the specified
+     * host via the specified network interface. An attempt to add a route that
+     * already exists is ignored, but treated as successful.
+     * @param networkType the type of the network over which traffic to the specified
+     * host is to be routed
+     * @param hostAddress the IP address of the host to which the route is desired
+     * @return {@code true} on success, {@code false} on failure
+     */
+    public boolean requestRouteToHost(int networkType, int hostAddress) {
+        try {
+            return mService.requestRouteToHost(networkType, hostAddress);
+        } catch (RemoteException e) {
+            return false;
+        }
+    }
+
+    /**
+     * Returns the value of the setting for background data usage. If false,
+     * applications should not use the network if the application is not in the
+     * foreground. Developers should respect this setting, and check the value
+     * of this before performing any background data operations.
+     * <p>
+     * All applications that have background services that use the network
+     * should listen to {@link #ACTION_BACKGROUND_DATA_SETTING_CHANGED}.
+     * 
+     * @return Whether background data usage is allowed.
+     */
+    public boolean getBackgroundDataSetting() {
+        try {
+            return mService.getBackgroundDataSetting();
+        } catch (RemoteException e) {
+            // Err on the side of safety 
+            return false;
+        }
+    }
+
+    /**
+     * Sets the value of the setting for background data usage.
+     * 
+     * @param allowBackgroundData Whether an application should use data while
+     *            it is in the background.
+     *            
+     * @attr ref android.Manifest.permission#CHANGE_BACKGROUND_DATA_SETTING
+     * @see #getBackgroundDataSetting()
+     * @hide
+     */
+    public void setBackgroundDataSetting(boolean allowBackgroundData) {
+        try {
+            mService.setBackgroundDataSetting(allowBackgroundData);
+        } catch (RemoteException e) {
+        }
+    }
+    
+    /**
+     * Don't allow use of default constructor.
+     */
+    @SuppressWarnings({"UnusedDeclaration"})
+    private ConnectivityManager() {
+    }
+
+    /**
+     * {@hide}
+     */
+    public ConnectivityManager(IConnectivityManager service) {
+        if (service == null) {
+            throw new IllegalArgumentException(
+                "ConnectivityManager() cannot be constructed with null service");
+        }
+        mService = service;
+    }
+}
diff --git a/core/java/android/net/Credentials.java b/core/java/android/net/Credentials.java
new file mode 100644
index 0000000..7f6cf9d
--- /dev/null
+++ b/core/java/android/net/Credentials.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2007 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 android.net;
+
+/**
+ * A class for representing UNIX credentials passed via ancillary data
+ * on UNIX domain sockets. See "man 7 unix" on a desktop linux distro.
+ */
+public class Credentials {
+    /** pid of process. root peers may lie. */
+    private final int pid;
+    /** uid of process. root peers may lie. */
+    private final int uid;
+    /** gid of process. root peers may lie. */
+    private final int gid;
+
+    public Credentials (int pid, int uid, int gid) {
+        this.pid = pid;
+        this.uid = uid;
+        this.gid = gid;
+    }
+
+    public int getPid() {
+        return pid;
+    }
+
+    public int getUid() {
+        return uid;
+    }
+    
+    public int getGid() {
+        return gid;
+    }
+}
diff --git a/core/java/android/net/DhcpInfo.aidl b/core/java/android/net/DhcpInfo.aidl
new file mode 100644
index 0000000..29cd21f
--- /dev/null
+++ b/core/java/android/net/DhcpInfo.aidl
@@ -0,0 +1,19 @@
+/**
+ * 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 android.net;
+
+parcelable DhcpInfo;
diff --git a/core/java/android/net/DhcpInfo.java b/core/java/android/net/DhcpInfo.java
new file mode 100644
index 0000000..1178bec
--- /dev/null
+++ b/core/java/android/net/DhcpInfo.java
@@ -0,0 +1,96 @@
+/*
+ * 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 android.net;
+
+import android.os.Parcelable;
+import android.os.Parcel;
+
+/**
+ * A simple object for retrieving the results of a DHCP request.
+ */
+public class DhcpInfo implements Parcelable {
+    public int ipAddress;
+    public int gateway;
+    public int netmask;
+
+    public int dns1;
+    public int dns2;
+
+    public int serverAddress;
+    public int leaseDuration;
+
+    public DhcpInfo() {
+        super();
+    }
+
+    public String toString() {
+        StringBuffer str = new StringBuffer();
+
+        str.append("ipaddr "); putAddress(str, ipAddress);
+        str.append(" gateway "); putAddress(str, gateway);
+        str.append(" netmask "); putAddress(str, netmask);
+        str.append(" dns1 "); putAddress(str, dns1);
+        str.append(" dns2 "); putAddress(str, dns2);
+        str.append(" DHCP server "); putAddress(str, serverAddress);
+        str.append(" lease ").append(leaseDuration).append(" seconds");
+
+        return str.toString();
+    }
+
+    private static void putAddress(StringBuffer buf, int addr) {
+        buf.append(addr  & 0xff).append('.').
+            append((addr >>>= 8) & 0xff).append('.').
+            append((addr >>>= 8) & 0xff).append('.').
+            append((addr >>>= 8) & 0xff);
+    }
+
+    /** Implement the Parcelable interface {@hide} */
+    public int describeContents() {
+        return 0;
+    }
+
+    /** Implement the Parcelable interface {@hide} */
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeInt(ipAddress);
+        dest.writeInt(gateway);
+        dest.writeInt(netmask);
+        dest.writeInt(dns1);
+        dest.writeInt(dns2);
+        dest.writeInt(serverAddress);
+        dest.writeInt(leaseDuration);
+    }
+
+    /** Implement the Parcelable interface {@hide} */
+    public static final Creator<DhcpInfo> CREATOR =
+        new Creator<DhcpInfo>() {
+            public DhcpInfo createFromParcel(Parcel in) {
+                DhcpInfo info = new DhcpInfo();
+                info.ipAddress = in.readInt();
+                info.gateway = in.readInt();
+                info.netmask = in.readInt();
+                info.dns1 = in.readInt();
+                info.dns2 = in.readInt();
+                info.serverAddress = in.readInt();
+                info.leaseDuration = in.readInt();
+                return info;
+            }
+
+            public DhcpInfo[] newArray(int size) {
+                return new DhcpInfo[size];
+            }
+        };
+}
diff --git a/core/java/android/net/IConnectivityManager.aidl b/core/java/android/net/IConnectivityManager.aidl
new file mode 100644
index 0000000..de68598
--- /dev/null
+++ b/core/java/android/net/IConnectivityManager.aidl
@@ -0,0 +1,51 @@
+/**
+ * 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 android.net;
+
+import android.net.NetworkInfo;
+
+/**
+ * Interface that answers queries about, and allows changing, the
+ * state of network connectivity.
+ */
+/** {@hide} */
+interface IConnectivityManager
+{
+    void setNetworkPreference(int pref);
+
+    int getNetworkPreference();
+
+    NetworkInfo getActiveNetworkInfo();
+
+    NetworkInfo getNetworkInfo(int networkType);
+
+    NetworkInfo[] getAllNetworkInfo();
+
+    boolean setRadios(boolean onOff);
+
+    boolean setRadio(int networkType, boolean turnOn);
+
+    int startUsingNetworkFeature(int networkType, in String feature);
+
+    int stopUsingNetworkFeature(int networkType, in String feature);
+
+    boolean requestRouteToHost(int networkType, int hostAddress);
+
+    boolean getBackgroundDataSetting();
+
+    void setBackgroundDataSetting(boolean allowBackgroundData);
+}
diff --git a/core/java/android/net/LocalServerSocket.java b/core/java/android/net/LocalServerSocket.java
new file mode 100644
index 0000000..2b93fc2
--- /dev/null
+++ b/core/java/android/net/LocalServerSocket.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2007 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 android.net;
+
+import java.io.IOException;
+import java.io.FileDescriptor;
+
+/**
+ * non-standard class for creating inbound UNIX-domain socket
+ * on the Android platform, this is created in the Linux non-filesystem
+ * namespace.
+ *
+ * On simulator platforms, this may be created in a temporary directory on
+ * the filesystem
+ */
+public class LocalServerSocket {
+    private final LocalSocketImpl impl;
+    private final LocalSocketAddress localAddress;
+
+    /** 50 seems a bit much, but it's what was here */
+    private static final int LISTEN_BACKLOG = 50;
+
+    /**
+     * Crewates a new server socket listening at specified name.
+     * On the Android platform, the name is created in the Linux
+     * abstract namespace (instead of on the filesystem).
+     * 
+     * @param name address for socket
+     * @throws IOException
+     */
+    public LocalServerSocket(String name) throws IOException
+    {
+        impl = new LocalSocketImpl();
+
+        impl.create(true);
+
+        localAddress = new LocalSocketAddress(name);
+        impl.bind(localAddress);
+
+        impl.listen(LISTEN_BACKLOG);
+    }
+
+    /**
+     * Create a LocalServerSocket from a file descriptor that's already
+     * been created and bound. listen() will be called immediately on it.
+     * Used for cases where file descriptors are passed in via environment
+     * variables
+     *
+     * @param fd bound file descriptor
+     * @throws IOException
+     */
+    public LocalServerSocket(FileDescriptor fd) throws IOException
+    {
+        impl = new LocalSocketImpl(fd);
+        impl.listen(LISTEN_BACKLOG);
+        localAddress = impl.getSockAddress();
+    }
+
+    /**
+     * Obtains the socket's local address
+     *
+     * @return local address
+     */
+    public LocalSocketAddress getLocalSocketAddress()
+    {
+        return localAddress;
+    }
+
+    /**
+     * Accepts a new connection to the socket. Blocks until a new
+     * connection arrives.
+     *
+     * @return a socket representing the new connection.
+     * @throws IOException
+     */
+    public LocalSocket accept() throws IOException
+    {
+        LocalSocketImpl acceptedImpl = new LocalSocketImpl();
+
+        impl.accept (acceptedImpl);
+
+        return new LocalSocket(acceptedImpl);
+    }
+
+    /**
+     * Returns file descriptor or null if not yet open/already closed
+     *
+     * @return fd or null
+     */
+    public FileDescriptor getFileDescriptor() {
+        return impl.getFileDescriptor();
+    }
+
+    /**
+     * Closes server socket.
+     * 
+     * @throws IOException
+     */
+    public void close() throws IOException
+    {
+        impl.close();
+    }
+}
diff --git a/core/java/android/net/LocalSocket.java b/core/java/android/net/LocalSocket.java
new file mode 100644
index 0000000..4039a69
--- /dev/null
+++ b/core/java/android/net/LocalSocket.java
@@ -0,0 +1,288 @@
+/*
+ * Copyright (C) 2007 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 android.net;
+
+import java.io.FileDescriptor;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.SocketOptions;
+
+/**
+ * Creates a (non-server) socket in the UNIX-domain namespace. The interface
+ * here is not entirely unlike that of java.net.Socket
+ */
+public class LocalSocket {
+
+    private LocalSocketImpl impl;
+    private volatile boolean implCreated;
+    private LocalSocketAddress localAddress;
+    private boolean isBound;
+    private boolean isConnected;
+
+    /**
+     * Creates a AF_LOCAL/UNIX domain stream socket.
+     */
+    public LocalSocket() {
+        this(new LocalSocketImpl());
+        isBound = false;
+        isConnected = false;
+    }
+
+    /**
+     * for use with AndroidServerSocket
+     * @param impl a SocketImpl
+     */
+    /*package*/ LocalSocket(LocalSocketImpl impl) {
+        this.impl = impl;
+        this.isConnected = false;
+        this.isBound = false;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public String toString() {
+        return super.toString() + " impl:" + impl;
+    }
+
+    /**
+     * It's difficult to discern from the spec when impl.create() should be
+     * called, but it seems like a reasonable rule is "as soon as possible,
+     * but not in a context where IOException cannot be thrown"
+     *
+     * @throws IOException from SocketImpl.create()
+     */
+    private void implCreateIfNeeded() throws IOException {
+        if (!implCreated) {
+            synchronized (this) {
+                if (!implCreated) {
+                    implCreated = true;
+                    impl.create(true);
+                }
+            }
+        }
+    }
+
+    /**
+     * Connects this socket to an endpoint. May only be called on an instance
+     * that has not yet been connected.
+     *
+     * @param endpoint endpoint address
+     * @throws IOException if socket is in invalid state or the address does
+     * not exist.
+     */
+    public void connect(LocalSocketAddress endpoint) throws IOException {
+        synchronized (this) {
+            if (isConnected) {
+                throw new IOException("already connected");
+            }
+
+            implCreateIfNeeded();
+            impl.connect(endpoint, 0);
+            isConnected = true;
+            isBound = true;
+        }
+    }
+
+    /**
+     * Binds this socket to an endpoint name. May only be called on an instance
+     * that has not yet been bound.
+     *
+     * @param bindpoint endpoint address
+     * @throws IOException
+     */
+    public void bind(LocalSocketAddress bindpoint) throws IOException {
+        implCreateIfNeeded();
+
+        synchronized (this) {
+            if (isBound) {
+                throw new IOException("already bound");
+            }
+
+            localAddress = bindpoint;
+            impl.bind(localAddress);
+            isBound = true;
+        }
+    }
+
+    /**
+     * Retrieves the name that this socket is bound to, if any.
+     *
+     * @return Local address or null if anonymous
+     */
+    public LocalSocketAddress getLocalSocketAddress() {
+        return localAddress;
+    }
+
+    /**
+     * Retrieves the input stream for this instance.
+     *
+     * @return input stream
+     * @throws IOException if socket has been closed or cannot be created.
+     */
+    public InputStream getInputStream() throws IOException {
+        implCreateIfNeeded();
+        return impl.getInputStream();
+    }
+
+    /**
+     * Retrieves the output stream for this instance.
+     *
+     * @return output stream
+     * @throws IOException if socket has been closed or cannot be created.
+     */
+    public OutputStream getOutputStream() throws IOException {
+        implCreateIfNeeded();
+        return impl.getOutputStream();
+    }
+
+    /**
+     * Closes the socket.
+     *
+     * @throws IOException
+     */
+    public void close() throws IOException {
+        implCreateIfNeeded();
+        impl.close();
+    }
+
+    /**
+     * Shuts down the input side of the socket.
+     *
+     * @throws IOException
+     */
+    public void shutdownInput() throws IOException {
+        implCreateIfNeeded();
+        impl.shutdownInput();
+    }
+
+    /**
+     * Shuts down the output side of the socket.
+     *
+     * @throws IOException
+     */
+    public void shutdownOutput() throws IOException {
+        implCreateIfNeeded();
+        impl.shutdownOutput();
+    }
+    
+    public void setReceiveBufferSize(int size) throws IOException {
+        impl.setOption(SocketOptions.SO_RCVBUF, Integer.valueOf(size));
+    }
+    
+    public int getReceiveBufferSize() throws IOException {
+        return ((Integer) impl.getOption(SocketOptions.SO_RCVBUF)).intValue();
+    }
+
+    public void setSoTimeout(int n) throws IOException {
+        impl.setOption(SocketOptions.SO_TIMEOUT, Integer.valueOf(n));
+    }
+    
+    public int getSoTimeout() throws IOException {
+        return ((Integer) impl.getOption(SocketOptions.SO_TIMEOUT)).intValue();
+    }
+
+    public void setSendBufferSize(int n) throws IOException {
+        impl.setOption(SocketOptions.SO_SNDBUF, Integer.valueOf(n));
+    }
+    
+    public int getSendBufferSize() throws IOException {
+        return ((Integer) impl.getOption(SocketOptions.SO_SNDBUF)).intValue();
+    }
+
+    //???SEC
+    public LocalSocketAddress getRemoteSocketAddress() {
+        throw new UnsupportedOperationException();
+    }
+
+    //???SEC
+    public synchronized boolean isConnected() {
+        return isConnected;
+    }
+
+    //???SEC
+    public boolean isClosed() {
+        throw new UnsupportedOperationException();
+    }
+
+    //???SEC
+    public synchronized boolean isBound() {
+        return isBound;
+    }
+
+    //???SEC
+    public boolean isOutputShutdown() {
+        throw new UnsupportedOperationException();
+    }
+
+    //???SEC
+    public boolean isInputShutdown() {
+        throw new UnsupportedOperationException();
+    }
+
+    //???SEC
+    public void connect(LocalSocketAddress endpoint, int timeout)
+            throws IOException {
+        throw new UnsupportedOperationException();
+    }
+
+    /**
+     * Enqueues a set of file descriptors to send to the peer. The queue
+     * is one deep. The file descriptors will be sent with the next write
+     * of normal data, and will be delivered in a single ancillary message.
+     * See "man 7 unix" SCM_RIGHTS on a desktop Linux machine.
+     *
+     * @param fds non-null; file descriptors to send.
+     */
+    public void setFileDescriptorsForSend(FileDescriptor[] fds) {
+        impl.setFileDescriptorsForSend(fds);
+    }
+
+    /**
+     * Retrieves a set of file descriptors that a peer has sent through
+     * an ancillary message. This method retrieves the most recent set sent,
+     * and then returns null until a new set arrives.
+     * File descriptors may only be passed along with regular data, so this
+     * method can only return a non-null after a read operation.
+     *
+     * @return null or file descriptor array
+     * @throws IOException
+     */
+    public FileDescriptor[] getAncillaryFileDescriptors() throws IOException {
+        return impl.getAncillaryFileDescriptors();
+    }
+
+    /**
+     * Retrieves the credentials of this socket's peer. Only valid on
+     * connected sockets.
+     *
+     * @return non-null; peer credentials
+     * @throws IOException
+     */
+    public Credentials getPeerCredentials() throws IOException {
+        return impl.getPeerCredentials();
+    }
+
+    /**
+     * Returns file descriptor or null if not yet open/already closed
+     *
+     * @return fd or null
+     */
+    public FileDescriptor getFileDescriptor() {
+        return impl.getFileDescriptor();
+    }    
+}
diff --git a/core/java/android/net/LocalSocketAddress.java b/core/java/android/net/LocalSocketAddress.java
new file mode 100644
index 0000000..8265b85
--- /dev/null
+++ b/core/java/android/net/LocalSocketAddress.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2007 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 android.net;
+
+/**
+ * A UNIX-domain (AF_LOCAL) socket address. For use with
+ * android.net.LocalSocket and android.net.LocalServerSocket.
+ *
+ * On the Android system, these names refer to names in the Linux
+ * abstract (non-filesystem) UNIX domain namespace.
+ */
+public class LocalSocketAddress
+{
+    /**
+     * The namespace that this address exists in. See also
+     * include/cutils/sockets.h ANDROID_SOCKET_NAMESPACE_*
+     */
+    public enum Namespace {
+        /** A socket in the Linux abstract namespace */
+        ABSTRACT(0),
+        /**
+         * A socket in the Android reserved namespace in /dev/socket.
+         * Only the init process may create a socket here.
+         */
+        RESERVED(1),
+        /**
+         * A socket named with a normal filesystem path.
+         */
+        FILESYSTEM(2);
+
+        /** The id matches with a #define in include/cutils/sockets.h */
+        private int id;
+        Namespace (int id) {
+            this.id = id;
+        }
+
+        /**
+         * @return int constant shared with native code
+         */
+        /*package*/ int getId() {
+            return id;
+        }
+    }
+
+    private final String name;
+    private final Namespace namespace;
+
+    /**
+     * Creates an instance with a given name.
+     *
+     * @param name non-null name
+     * @param namespace namespace the name should be created in.
+     */
+    public LocalSocketAddress(String name, Namespace namespace) {
+        this.name = name;
+        this.namespace = namespace;
+    }
+
+    /**
+     * Creates an instance with a given name in the {@link Namespace#ABSTRACT}
+     * namespace
+     *
+     * @param name non-null name
+     */
+    public LocalSocketAddress(String name) {
+        this(name,Namespace.ABSTRACT);
+    }
+
+    /**
+     * Retrieves the string name of this address
+     * @return string name
+     */
+    public String getName()
+    {
+        return name;
+    }
+
+    /**
+     * Returns the namespace used by this address.
+     *
+     * @return non-null a namespace
+     */
+    public Namespace getNamespace() {
+        return namespace;
+    }
+}
diff --git a/core/java/android/net/LocalSocketImpl.java b/core/java/android/net/LocalSocketImpl.java
new file mode 100644
index 0000000..6c36a7d
--- /dev/null
+++ b/core/java/android/net/LocalSocketImpl.java
@@ -0,0 +1,490 @@
+/*
+ * Copyright (C) 2007 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 android.net;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.InputStream;
+import java.io.FileDescriptor;
+import java.net.SocketOptions;
+
+/**
+ * Socket implementation used for android.net.LocalSocket and
+ * android.net.LocalServerSocket. Supports only AF_LOCAL sockets.
+ */
+class LocalSocketImpl
+{
+    private SocketInputStream fis;
+    private SocketOutputStream fos;
+    private Object readMonitor = new Object();
+    private Object writeMonitor = new Object();
+
+    /** null if closed or not yet created */
+    private FileDescriptor fd;
+
+    // These fields are accessed by native code;
+    /** file descriptor array received during a previous read */
+    FileDescriptor[] inboundFileDescriptors;
+    /** file descriptor array that should be written during next write */
+    FileDescriptor[] outboundFileDescriptors;
+
+    /**
+     * An input stream for local sockets. Needed because we may
+     * need to read ancillary data.
+     */
+    class SocketInputStream extends InputStream {
+        /** {@inheritDoc} */
+        @Override
+        public int available() throws IOException {
+            return available_native(fd);
+        }
+
+        /** {@inheritDoc} */
+        @Override
+        public void close() throws IOException {
+            LocalSocketImpl.this.close();
+        }
+
+        /** {@inheritDoc} */
+        @Override
+        public int read() throws IOException {
+            int ret;
+            synchronized (readMonitor) {
+                FileDescriptor myFd = fd;
+                if (myFd == null) throw new IOException("socket closed");
+
+                ret = read_native(myFd);
+                return ret;
+            }
+        }
+
+        /** {@inheritDoc} */
+        @Override
+        public int read(byte[] b) throws IOException {
+            return read(b, 0, b.length);
+        }
+
+        /** {@inheritDoc} */
+        @Override
+        public int read(byte[] b, int off, int len) throws IOException {
+            synchronized (readMonitor) {
+                FileDescriptor myFd = fd;
+                if (myFd == null) throw new IOException("socket closed");
+
+                if (off < 0 || len < 0 || (off + len) > b.length ) {
+                    throw new ArrayIndexOutOfBoundsException();
+                }
+
+                int ret = readba_native(b, off, len, myFd);
+
+                return ret;
+            }
+        }
+    }
+
+    /**
+     * An output stream for local sockets. Needed because we may
+     * need to read ancillary data.
+     */
+    class SocketOutputStream extends OutputStream {
+        /** {@inheritDoc} */
+        @Override
+        public void close() throws IOException {
+            LocalSocketImpl.this.close();
+        }
+
+        /** {@inheritDoc} */
+        @Override
+        public void write (byte[] b) throws IOException {
+            write(b, 0, b.length);
+        }
+        
+        /** {@inheritDoc} */
+        @Override
+        public void write (byte[] b, int off, int len) throws IOException {
+            synchronized (writeMonitor) {
+                FileDescriptor myFd = fd;
+                if (myFd == null) throw new IOException("socket closed");
+
+                if (off < 0 || len < 0 || (off + len) > b.length ) {
+                    throw new ArrayIndexOutOfBoundsException();
+                }
+                writeba_native(b, off, len, myFd);
+            }
+        }
+
+        /** {@inheritDoc} */
+        @Override
+        public void write (int b) throws IOException {
+            synchronized (writeMonitor) {
+                FileDescriptor myFd = fd;
+                if (myFd == null) throw new IOException("socket closed");
+                write_native(b, myFd);
+            }
+        }
+    }
+
+    private native int available_native(FileDescriptor fd) throws IOException;
+    private native void close_native(FileDescriptor fd) throws IOException;
+    private native int read_native(FileDescriptor fd) throws IOException;
+    private native int readba_native(byte[] b, int off, int len,
+            FileDescriptor fd) throws IOException;
+    private native void writeba_native(byte[] b, int off, int len,
+            FileDescriptor fd) throws IOException;
+    private native void write_native(int b, FileDescriptor fd)
+            throws IOException;
+    private native void connectLocal(FileDescriptor fd, String name,
+            int namespace) throws IOException;
+    private native void bindLocal(FileDescriptor fd, String name, int namespace)
+            throws IOException;
+    private native FileDescriptor create_native(boolean stream)
+            throws IOException;
+    private native void listen_native(FileDescriptor fd, int backlog)
+            throws IOException;
+    private native void shutdown(FileDescriptor fd, boolean shutdownInput);
+    private native Credentials getPeerCredentials_native(
+            FileDescriptor fd) throws IOException;
+    private native int getOption_native(FileDescriptor fd, int optID)
+            throws IOException;
+    private native void setOption_native(FileDescriptor fd, int optID,
+            int b, int value) throws IOException;
+
+//    private native LocalSocketAddress getSockName_native
+//            (FileDescriptor fd) throws IOException;
+
+    /**
+     * Accepts a connection on a server socket.
+     *
+     * @param fd file descriptor of server socket
+     * @param s socket implementation that will become the new socket
+     * @return file descriptor of new socket
+     */
+    private native FileDescriptor accept
+            (FileDescriptor fd, LocalSocketImpl s) throws IOException;
+
+    /**
+     * Create a new instance.
+     */
+    /*package*/ LocalSocketImpl()
+    {
+    }
+
+    /**
+     * Create a new instance from a file descriptor representing
+     * a bound socket. The state of the file descriptor is not checked here
+     *  but the caller can verify socket state by calling listen().
+     *
+     * @param fd non-null; bound file descriptor
+     */
+    /*package*/ LocalSocketImpl(FileDescriptor fd) throws IOException
+    {
+        this.fd = fd;
+    }
+
+    public String toString() {
+        return super.toString() + " fd:" + fd;
+    }
+
+    /**
+     * Creates a socket in the underlying OS.
+     *
+     * @param stream true if this should be a stream socket, false for
+     * datagram.
+     * @throws IOException
+     */
+    public void create (boolean stream) throws IOException {
+        // no error if socket already created
+        // need this for LocalServerSocket.accept()
+        if (fd == null) {
+            fd = create_native(stream);
+        }
+    }
+
+    /**
+     * Closes the socket.
+     *
+     * @throws IOException
+     */
+    public void close() throws IOException {
+        synchronized (LocalSocketImpl.this) {
+            if (fd == null) return;
+            close_native(fd);
+            fd = null;
+        }
+    }
+
+    /** note timeout presently ignored */
+    protected void connect(LocalSocketAddress address, int timeout)
+                        throws IOException
+    {        
+        if (fd == null) {
+            throw new IOException("socket not created");
+        }
+
+        connectLocal(fd, address.getName(), address.getNamespace().getId());
+    }
+
+    /**
+     * Binds this socket to an endpoint name. May only be called on an instance
+     * that has not yet been bound.
+     *
+     * @param endpoint endpoint address
+     * @throws IOException
+     */
+    public void bind(LocalSocketAddress endpoint) throws IOException
+    {
+        if (fd == null) {
+            throw new IOException("socket not created");
+        }
+
+        bindLocal(fd, endpoint.getName(), endpoint.getNamespace().getId());
+    }
+
+    protected void listen(int backlog) throws IOException
+    {
+        if (fd == null) {
+            throw new IOException("socket not created");
+        }
+
+        listen_native(fd, backlog);
+    }
+
+    /**
+     * Accepts a new connection to the socket. Blocks until a new
+     * connection arrives.
+     *
+     * @param s a socket that will be used to represent the new connection.
+     * @throws IOException
+     */
+    protected void accept(LocalSocketImpl s) throws IOException
+    {
+        if (fd == null) {
+            throw new IOException("socket not created");
+        }
+
+        s.fd = accept(fd, s);
+    }
+
+    /**
+     * Retrieves the input stream for this instance.
+     *
+     * @return input stream
+     * @throws IOException if socket has been closed or cannot be created.
+     */
+    protected InputStream getInputStream() throws IOException
+    {
+        if (fd == null) {
+            throw new IOException("socket not created");
+        }
+
+        synchronized (this) {
+            if (fis == null) {
+                fis = new SocketInputStream();
+            }
+
+            return fis;
+        }
+    }
+
+    /**
+     * Retrieves the output stream for this instance.
+     *
+     * @return output stream
+     * @throws IOException if socket has been closed or cannot be created.
+     */
+    protected OutputStream getOutputStream() throws IOException
+    { 
+        if (fd == null) {
+            throw new IOException("socket not created");
+        }
+
+        synchronized (this) {
+            if (fos == null) {
+                fos = new SocketOutputStream();
+            }
+
+            return fos;
+        }
+    }
+
+    /**
+     * Returns the number of bytes available for reading without blocking.
+     *
+     * @return >= 0 count bytes available
+     * @throws IOException
+     */
+    protected int available() throws IOException
+    {
+        return getInputStream().available();
+    }
+
+    /**
+     * Shuts down the input side of the socket.
+     *
+     * @throws IOException
+     */
+    protected void shutdownInput() throws IOException
+    {
+        if (fd == null) {
+            throw new IOException("socket not created");
+        }
+
+        shutdown(fd, true);
+    }
+
+    /**
+     * Shuts down the output side of the socket.
+     *
+     * @throws IOException
+     */
+    protected void shutdownOutput() throws IOException
+    {
+        if (fd == null) {
+            throw new IOException("socket not created");
+        }
+
+        shutdown(fd, false);
+    }
+
+    protected FileDescriptor getFileDescriptor()
+    {
+        return fd;
+    }
+
+    protected boolean supportsUrgentData()
+    {
+        return false;
+    }
+
+    protected void sendUrgentData(int data) throws IOException
+    {
+        throw new RuntimeException ("not impled");
+    }
+
+    public Object getOption(int optID) throws IOException
+    {
+        if (fd == null) {
+            throw new IOException("socket not created");
+        }
+
+        if (optID == SocketOptions.SO_TIMEOUT) {
+            return 0;
+        }
+        
+        int value = getOption_native(fd, optID);
+        switch (optID)
+        {
+            case SocketOptions.SO_RCVBUF:
+            case SocketOptions.SO_SNDBUF:
+                return value;
+            case SocketOptions.SO_REUSEADDR:
+            default:
+                return value;
+        }
+    }
+
+    public void setOption(int optID, Object value)
+            throws IOException {
+        /*
+         * Boolean.FALSE is used to disable some options, so it
+         * is important to distinguish between FALSE and unset.
+         * We define it here that -1 is unset, 0 is FALSE, and 1
+         * is TRUE.
+         */
+        int boolValue = -1;
+        int intValue = 0;
+
+        if (fd == null) {
+            throw new IOException("socket not created");
+        }
+
+        if (value instanceof Integer) {
+            intValue = (Integer)value;
+        } else if (value instanceof Boolean) {
+            boolValue = ((Boolean) value)? 1 : 0;
+        } else {
+            throw new IOException("bad value: " + value);
+        }
+
+        setOption_native(fd, optID, boolValue, intValue);
+    }
+
+    /**
+     * Enqueues a set of file descriptors to send to the peer. The queue
+     * is one deep. The file descriptors will be sent with the next write
+     * of normal data, and will be delivered in a single ancillary message.
+     * See "man 7 unix" SCM_RIGHTS on a desktop Linux machine.
+     *
+     * @param fds non-null; file descriptors to send.
+     * @throws IOException
+     */
+    public void setFileDescriptorsForSend(FileDescriptor[] fds) {
+        synchronized(writeMonitor) {
+            outboundFileDescriptors = fds;
+        }
+    }
+
+    /**
+     * Retrieves a set of file descriptors that a peer has sent through
+     * an ancillary message. This method retrieves the most recent set sent,
+     * and then returns null until a new set arrives.
+     * File descriptors may only be passed along with regular data, so this
+     * method can only return a non-null after a read operation.
+     *
+     * @return null or file descriptor array
+     * @throws IOException
+     */
+    public FileDescriptor[] getAncillaryFileDescriptors() throws IOException {
+        synchronized(readMonitor) {
+            FileDescriptor[] result = inboundFileDescriptors;
+
+            inboundFileDescriptors = null;
+            return result;
+        }
+    }
+
+    /**
+     * Retrieves the credentials of this socket's peer. Only valid on
+     * connected sockets.
+     *
+     * @return non-null; peer credentials
+     * @throws IOException
+     */
+    public Credentials getPeerCredentials() throws IOException
+    {
+        return getPeerCredentials_native(fd);
+    }
+
+    /**
+     * Retrieves the socket name from the OS.
+     *
+     * @return non-null; socket name
+     * @throws IOException on failure
+     */
+    public LocalSocketAddress getSockAddress() throws IOException
+    {
+        return null;
+        //TODO implement this
+        //return getSockName_native(fd);
+    }
+
+    @Override
+    protected void finalize() throws IOException {
+        close();
+    }
+}
+
diff --git a/core/java/android/net/MailTo.java b/core/java/android/net/MailTo.java
new file mode 100644
index 0000000..ca28f86
--- /dev/null
+++ b/core/java/android/net/MailTo.java
@@ -0,0 +1,172 @@
+/*
+ * 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 android.net;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ *
+ * MailTo URL parser
+ *
+ * This class parses a mailto scheme URL and then can be queried for
+ * the parsed parameters. This implements RFC 2368.
+ *
+ */
+public class MailTo {
+    
+    static public final String MAILTO_SCHEME = "mailto:";
+    
+    // All the parsed content is added to the headers.
+    private HashMap<String, String> mHeaders;
+    
+    // Well known headers
+    static private final String TO = "to";
+    static private final String BODY = "body";
+    static private final String CC = "cc";
+    static private final String SUBJECT = "subject";
+
+    
+    /**
+     * Test to see if the given string is a mailto URL
+     * @param url string to be tested
+     * @return true if the string is a mailto URL
+     */
+    public static boolean isMailTo(String url) {
+        if (url != null && url.startsWith(MAILTO_SCHEME)) {
+            return true;
+        }
+        return false;
+    }
+    
+    /**
+     * Parse and decode a mailto scheme string. This parser implements
+     * RFC 2368. The returned object can be queried for the parsed parameters.
+     * @param url String containing a mailto URL
+     * @return MailTo object
+     * @exception ParseException if the scheme is not a mailto URL
+     */
+    public static MailTo parse(String url) throws ParseException {
+        if (url == null) {
+            throw new NullPointerException();
+        }
+        if (!isMailTo(url)) {
+             throw new ParseException("Not a mailto scheme");
+        }
+        // Strip the scheme as the Uri parser can't cope with it.
+        String noScheme = url.substring(MAILTO_SCHEME.length());
+        Uri email = Uri.parse(noScheme);
+        MailTo m = new MailTo();
+        
+        // Parse out the query parameters
+        String query = email.getQuery();
+        if (query != null ) {
+            String[] queries = query.split("&");
+            for (String q : queries) {
+                String[] nameval = q.split("=");
+                if (nameval.length == 0) {
+                    continue;
+                }
+                // insert the headers with the name in lowercase so that
+                // we can easily find common headers
+                m.mHeaders.put(Uri.decode(nameval[0]).toLowerCase(), 
+                        nameval.length > 1 ? Uri.decode(nameval[1]) : null);
+            }
+        }
+        
+        // Address can be specified in both the headers and just after the
+        // mailto line. Join the two together.
+        String address = email.getPath();
+        if (address != null) {
+            String addr = m.getTo();
+            if (addr != null) {
+                address += ", " + addr;
+            }
+            m.mHeaders.put(TO, address);
+        }
+        
+        return m;
+    }
+    
+    /**
+     * Retrieve the To address line from the parsed mailto URL. This could be
+     * several email address that are comma-space delimited.
+     * If no To line was specified, then null is return
+     * @return comma delimited email addresses or null
+     */
+    public String getTo() {
+        return mHeaders.get(TO);
+    }
+    
+    /**
+     * Retrieve the CC address line from the parsed mailto URL. This could be
+     * several email address that are comma-space delimited.
+     * If no CC line was specified, then null is return
+     * @return comma delimited email addresses or null
+     */
+    public String getCc() {
+        return mHeaders.get(CC);
+    }
+    
+    /**
+     * Retrieve the subject line from the parsed mailto URL.
+     * If no subject line was specified, then null is return
+     * @return subject or null
+     */
+    public String getSubject() {
+        return mHeaders.get(SUBJECT);
+    }
+    
+    /**
+     * Retrieve the body line from the parsed mailto URL.
+     * If no body line was specified, then null is return
+     * @return body or null
+     */
+    public String getBody() {
+        return mHeaders.get(BODY);
+    }
+    
+    /**
+     * Retrieve all the parsed email headers from the mailto URL
+     * @return map containing all parsed values
+     */
+    public Map<String, String> getHeaders() {
+        return mHeaders;
+    }
+    
+    @Override
+    public String toString() {
+        StringBuilder sb = new StringBuilder(MAILTO_SCHEME);
+        sb.append('?');
+        for (Map.Entry<String,String> header : mHeaders.entrySet()) {
+            sb.append(Uri.encode(header.getKey()));
+            sb.append('=');
+            sb.append(Uri.encode(header.getValue()));
+            sb.append('&');
+        }
+        return sb.toString();
+    }
+    
+    /**
+     * Private constructor. The only way to build a Mailto object is through
+     * the parse() method.
+     */
+    private MailTo() {
+        mHeaders = new HashMap<String, String>();
+    }
+}
diff --git a/core/java/android/net/MobileDataStateTracker.java b/core/java/android/net/MobileDataStateTracker.java
new file mode 100644
index 0000000..1d939e1
--- /dev/null
+++ b/core/java/android/net/MobileDataStateTracker.java
@@ -0,0 +1,493 @@
+/*
+ * 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 android.net;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.RemoteException;
+import android.os.Handler;
+import android.os.ServiceManager;
+import android.os.SystemProperties;
+import com.android.internal.telephony.ITelephony;
+import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.TelephonyIntents;
+import android.net.NetworkInfo.DetailedState;
+import android.telephony.TelephonyManager;
+import android.util.Log;
+import android.text.TextUtils;
+
+import java.util.List;
+import java.util.ArrayList;
+
+/**
+ * Track the state of mobile data connectivity. This is done by
+ * receiving broadcast intents from the Phone process whenever
+ * the state of data connectivity changes.
+ *
+ * {@hide}
+ */
+public class MobileDataStateTracker extends NetworkStateTracker {
+
+    private static final String TAG = "MobileDataStateTracker";
+    private static final boolean DBG = false;
+
+    private Phone.DataState mMobileDataState;
+    private ITelephony mPhoneService;
+    private static final String[] sDnsPropNames = {
+          "net.rmnet0.dns1",
+          "net.rmnet0.dns2",
+          "net.eth0.dns1",
+          "net.eth0.dns2",
+          "net.eth0.dns3",
+          "net.eth0.dns4",
+          "net.gprs.dns1",
+          "net.gprs.dns2"
+    };
+    private List<String> mDnsServers;
+    private String mInterfaceName;
+    private int mDefaultGatewayAddr;
+    private int mLastCallingPid = -1;
+
+    /**
+     * Create a new MobileDataStateTracker
+     * @param context the application context of the caller
+     * @param target a message handler for getting callbacks about state changes
+     */
+    public MobileDataStateTracker(Context context, Handler target) {
+        super(context, target, ConnectivityManager.TYPE_MOBILE,
+              TelephonyManager.getDefault().getNetworkType(), "MOBILE",
+              TelephonyManager.getDefault().getNetworkTypeName());
+        mPhoneService = null;
+        mDnsServers = new ArrayList<String>();
+    }
+
+    /**
+     * Begin monitoring mobile data connectivity.
+     */
+    public void startMonitoring() {
+        IntentFilter filter =
+                new IntentFilter(TelephonyIntents.ACTION_ANY_DATA_CONNECTION_STATE_CHANGED);
+        filter.addAction(TelephonyIntents.ACTION_DATA_CONNECTION_FAILED);
+        filter.addAction(TelephonyIntents.ACTION_SERVICE_STATE_CHANGED);
+
+        Intent intent = mContext.registerReceiver(new MobileDataStateReceiver(), filter);
+        if (intent != null)
+            mMobileDataState = getMobileDataState(intent);
+        else
+            mMobileDataState = Phone.DataState.DISCONNECTED;
+    }
+
+    private static Phone.DataState getMobileDataState(Intent intent) {
+        String str = intent.getStringExtra(Phone.STATE_KEY);
+        if (str != null)
+            return Enum.valueOf(Phone.DataState.class, str);
+        else
+            return Phone.DataState.DISCONNECTED;
+    }
+
+    private class MobileDataStateReceiver extends BroadcastReceiver {
+        public void onReceive(Context context, Intent intent) {
+            if (intent.getAction().equals(TelephonyIntents.ACTION_ANY_DATA_CONNECTION_STATE_CHANGED)) {
+                Phone.DataState state = getMobileDataState(intent);
+                String reason = intent.getStringExtra(Phone.STATE_CHANGE_REASON_KEY);
+                String apnName = intent.getStringExtra(Phone.DATA_APN_KEY);
+                boolean unavailable = intent.getBooleanExtra(Phone.NETWORK_UNAVAILABLE_KEY, false);
+                if (DBG) Log.d(TAG, "Received " + intent.getAction() +
+                    " broadcast - state = " + state
+                    + ", unavailable = " + unavailable
+                    + ", reason = " + (reason == null ? "(unspecified)" : reason));
+                mNetworkInfo.setIsAvailable(!unavailable);
+                if (mMobileDataState != state) {
+                    mMobileDataState = state;
+
+                    switch (state) {
+                        case DISCONNECTED:
+                            setDetailedState(DetailedState.DISCONNECTED, reason, apnName);
+                            if (mInterfaceName != null) {
+                                NetworkUtils.resetConnections(mInterfaceName);
+                            }
+                            mInterfaceName = null;
+                            mDefaultGatewayAddr = 0;
+                            break;
+                        case CONNECTING:
+                            setDetailedState(DetailedState.CONNECTING, reason, apnName);
+                            break;
+                        case SUSPENDED:
+                            setDetailedState(DetailedState.SUSPENDED, reason, apnName);
+                            break;
+                        case CONNECTED:
+                            mInterfaceName = intent.getStringExtra(Phone.DATA_IFACE_NAME_KEY);
+                            if (mInterfaceName == null) {
+                                Log.d(TAG, "CONNECTED event did not supply interface name.");
+                            }
+                            setupDnsProperties();
+                            setDetailedState(DetailedState.CONNECTED, reason, apnName);
+                            break;
+                    }
+                }
+            } else if (intent.getAction().equals(TelephonyIntents.ACTION_DATA_CONNECTION_FAILED)) {
+                String reason = intent.getStringExtra(Phone.FAILURE_REASON_KEY);
+                String apnName = intent.getStringExtra(Phone.DATA_APN_KEY);
+                if (DBG) Log.d(TAG, "Received " + intent.getAction() + " broadcast" +
+                    reason == null ? "" : "(" + reason + ")");
+                setDetailedState(DetailedState.FAILED, reason, apnName);
+            }
+            TelephonyManager tm = TelephonyManager.getDefault();
+            setRoamingStatus(tm.isNetworkRoaming());
+            setSubtype(tm.getNetworkType(), tm.getNetworkTypeName());
+        }
+    }
+
+    /**
+     * Make sure that route(s) exist to the carrier DNS server(s).
+     */
+    public void addPrivateRoutes() {
+        if (mInterfaceName != null) {
+            for (String addrString : mDnsServers) {
+                int addr = NetworkUtils.lookupHost(addrString);
+                if (addr != -1) {
+                    NetworkUtils.addHostRoute(mInterfaceName, addr);
+                }
+            }
+        }
+    }
+
+    public void removePrivateRoutes() {
+        if(mInterfaceName != null) {
+            NetworkUtils.removeHostRoutes(mInterfaceName);
+        }
+    }
+
+    public void removeDefaultRoute() {
+        if(mInterfaceName != null) {
+            mDefaultGatewayAddr = NetworkUtils.getDefaultRoute(mInterfaceName);
+            NetworkUtils.removeDefaultRoute(mInterfaceName);
+        }
+    }
+
+    public void restoreDefaultRoute() {
+        // 0 is not a valid address for a gateway
+        if (mInterfaceName != null && mDefaultGatewayAddr != 0) {
+            NetworkUtils.setDefaultRoute(mInterfaceName, mDefaultGatewayAddr);
+        }
+    }
+
+    private void getPhoneService(boolean forceRefresh) {
+        if ((mPhoneService == null) || forceRefresh) {
+            mPhoneService = ITelephony.Stub.asInterface(ServiceManager.getService("phone"));
+        }
+    }
+
+    /**
+     * Report whether data connectivity is possible.
+     */
+    public boolean isAvailable() {
+        getPhoneService(false);
+        
+        /*
+         * If the phone process has crashed in the past, we'll get a
+         * RemoteException and need to re-reference the service.
+         */
+        for (int retry = 0; retry < 2; retry++) {
+            if (mPhoneService == null) break;
+
+            try {
+                return mPhoneService.isDataConnectivityPossible();
+            } catch (RemoteException e) {
+                // First-time failed, get the phone service again
+                if (retry == 0) getPhoneService(true);
+            }
+        }
+        
+        return false;
+    }
+
+    /**
+     * Return the IP addresses of the DNS servers available for the mobile data
+     * network interface.
+     * @return a list of DNS addresses, with no holes.
+     */
+    public String[] getNameServers() {
+        return getNameServerList(sDnsPropNames);
+    }
+
+    /**
+     * {@inheritDoc}
+     * The mobile data network subtype indicates what generation network technology is in effect,
+     * e.g., GPRS, EDGE, UMTS, etc.
+     */
+    public int getNetworkSubtype() {
+        return TelephonyManager.getDefault().getNetworkType();
+    }
+
+    /**
+     * Return the system properties name associated with the tcp buffer sizes
+     * for this network.
+     */
+    public String getTcpBufferSizesPropName() {
+      String networkTypeStr = "unknown";
+        TelephonyManager tm = new TelephonyManager(mContext);
+        switch(tm.getNetworkType()) {
+          case TelephonyManager.NETWORK_TYPE_GPRS:
+            networkTypeStr = "gprs";
+            break;
+          case TelephonyManager.NETWORK_TYPE_EDGE:
+            networkTypeStr = "edge";
+            break;
+          case TelephonyManager.NETWORK_TYPE_UMTS:
+            networkTypeStr = "umts";
+            break;
+        }
+        return "net.tcp.buffersize." + networkTypeStr;
+    }
+
+    /**
+     * Tear down mobile data connectivity, i.e., disable the ability to create
+     * mobile data connections.
+     */
+    @Override
+    public boolean teardown() {
+        getPhoneService(false);
+        /*
+         * If the phone process has crashed in the past, we'll get a
+         * RemoteException and need to re-reference the service.
+         */
+        for (int retry = 0; retry < 2; retry++) {
+            if (mPhoneService == null) {
+                Log.w(TAG,
+                    "Ignoring mobile data teardown request because could not acquire PhoneService");
+                break;
+            }
+
+            try {
+                return mPhoneService.disableDataConnectivity();
+            } catch (RemoteException e) {
+                if (retry == 0) getPhoneService(true);
+            }
+        }
+        
+        Log.w(TAG, "Failed to tear down mobile data connectivity");
+        return false;
+    }
+
+    /**
+     * Re-enable mobile data connectivity after a {@link #teardown()}.
+     */
+    public boolean reconnect() {
+        getPhoneService(false);
+        /*
+         * If the phone process has crashed in the past, we'll get a
+         * RemoteException and need to re-reference the service.
+         */
+        for (int retry = 0; retry < 2; retry++) {
+            if (mPhoneService == null) {
+                Log.w(TAG,
+                    "Ignoring mobile data connect request because could not acquire PhoneService");
+                break;
+            }
+
+            try {
+                return mPhoneService.enableDataConnectivity();
+            } catch (RemoteException e) {
+                if (retry == 0) getPhoneService(true);
+            }
+        }
+
+        Log.w(TAG, "Failed to set up mobile data connectivity");
+        return false;
+    }
+
+    /**
+     * Turn on or off the mobile radio. No connectivity will be possible while the
+     * radio is off. The operation is a no-op if the radio is already in the desired state.
+     * @param turnOn {@code true} if the radio should be turned on, {@code false} if
+     */
+    public boolean setRadio(boolean turnOn) {
+        getPhoneService(false);
+        /*
+         * If the phone process has crashed in the past, we'll get a
+         * RemoteException and need to re-reference the service.
+         */
+        for (int retry = 0; retry < 2; retry++) {
+            if (mPhoneService == null) {
+                Log.w(TAG,
+                    "Ignoring mobile radio request because could not acquire PhoneService");
+                break;
+            }
+            
+            try {
+                return mPhoneService.setRadio(turnOn);
+            } catch (RemoteException e) {
+                if (retry == 0) getPhoneService(true);
+            }
+        }
+
+        Log.w(TAG, "Could not set radio power to " + (turnOn ? "on" : "off"));
+        return false;
+    }
+
+    /**
+     * Tells the phone sub-system that the caller wants to
+     * begin using the named feature. The only supported feature at
+     * this time is {@code Phone.FEATURE_ENABLE_MMS}, which allows an application
+     * to specify that it wants to send and/or receive MMS data.
+     * @param feature the name of the feature to be used
+     * @param callingPid the process ID of the process that is issuing this request
+     * @param callingUid the user ID of the process that is issuing this request
+     * @return an integer value representing the outcome of the request.
+     * The interpretation of this value is feature-specific.
+     * specific, except that the value {@code -1}
+     * always indicates failure. For {@code Phone.FEATURE_ENABLE_MMS},
+     * the other possible return values are
+     * <ul>
+     * <li>{@code Phone.APN_ALREADY_ACTIVE}</li>
+     * <li>{@code Phone.APN_REQUEST_STARTED}</li>
+     * <li>{@code Phone.APN_TYPE_NOT_AVAILABLE}</li>
+     * <li>{@code Phone.APN_REQUEST_FAILED}</li>
+     * </ul>
+     */
+    public int startUsingNetworkFeature(String feature, int callingPid, int callingUid) {
+        if (TextUtils.equals(feature, Phone.FEATURE_ENABLE_MMS)) {
+            mLastCallingPid = callingPid;
+            return setEnableApn(Phone.APN_TYPE_MMS, true);
+        } else {
+            return -1;
+        }
+    }
+
+    /**
+     * Tells the phone sub-system that the caller is finished
+     * using the named feature. The only supported feature at
+     * this time is {@code Phone.FEATURE_ENABLE_MMS}, which allows an application
+     * to specify that it wants to send and/or receive MMS data.
+     * @param feature the name of the feature that is no longer needed
+     * @param callingPid the process ID of the process that is issuing this request
+     * @param callingUid the user ID of the process that is issuing this request
+     * @return an integer value representing the outcome of the request.
+     * The interpretation of this value is feature-specific, except that
+     * the value {@code -1} always indicates failure.
+     */
+    public int stopUsingNetworkFeature(String feature, int callingPid, int callingUid) {
+        if (TextUtils.equals(feature, Phone.FEATURE_ENABLE_MMS)) {
+            return setEnableApn(Phone.APN_TYPE_MMS, false);
+        } else {
+            return -1;
+        }
+    }
+
+    /**
+     * Ensure that a network route exists to deliver traffic to the specified
+     * host via the mobile data network.
+     * @param hostAddress the IP address of the host to which the route is desired,
+     * in network byte order.
+     * @return {@code true} on success, {@code false} on failure
+     */
+    @Override
+    public boolean requestRouteToHost(int hostAddress) {
+        if (mInterfaceName != null && hostAddress != -1) {
+            if (DBG) {
+                Log.d(TAG, "Requested host route to " + Integer.toHexString(hostAddress));
+            }
+            return NetworkUtils.addHostRoute(mInterfaceName, hostAddress) == 0;
+        } else {
+            return false;
+        }
+    }
+
+    @Override
+    public String toString() {
+        StringBuffer sb = new StringBuffer("Mobile data state: ");
+
+        sb.append(mMobileDataState);
+        return sb.toString();
+    }
+
+    private void setupDnsProperties() {
+        mDnsServers.clear();
+        // Set up per-process DNS server list on behalf of the MMS process
+        int i = 1;
+        if (mInterfaceName != null) {
+            for (String propName : sDnsPropNames) {
+                if (propName.indexOf(mInterfaceName) != -1) {
+                    String propVal = SystemProperties.get(propName);
+                    if (propVal != null && propVal.length() != 0 && !propVal.equals("0.0.0.0")) {
+                        mDnsServers.add(propVal);
+                        if (mLastCallingPid != -1) {
+                            SystemProperties.set("net.dns"  + i + "." + mLastCallingPid, propVal);
+                        }
+                        ++i;
+                    }
+                }
+            }
+        }
+        if (i == 1) {
+            Log.d(TAG, "DNS server addresses are not known.");
+        } else if (mLastCallingPid != -1) {
+            /*
+            * Bump the property that tells the name resolver library
+            * to reread the DNS server list from the properties.
+            */
+            String propVal = SystemProperties.get("net.dnschange");
+            if (propVal.length() != 0) {
+                try {
+                    int n = Integer.parseInt(propVal);
+                    SystemProperties.set("net.dnschange", "" + (n+1));
+                } catch (NumberFormatException e) {
+                }
+            }
+        }
+        mLastCallingPid = -1;
+    }
+
+   /**
+     * Internal method supporting the ENABLE_MMS feature.
+     * @param apnType the type of APN to be enabled or disabled (e.g., mms)
+     * @param enable {@code true} to enable the specified APN type,
+     * {@code false} to disable it.
+     * @return an integer value representing the outcome of the request.
+     */
+    private int setEnableApn(String apnType, boolean enable) {
+        getPhoneService(false);
+        /*
+         * If the phone process has crashed in the past, we'll get a
+         * RemoteException and need to re-reference the service.
+         */
+        for (int retry = 0; retry < 2; retry++) {
+            if (mPhoneService == null) {
+                Log.w(TAG,
+                    "Ignoring feature request because could not acquire PhoneService");
+                break;
+            }
+
+            try {
+                if (enable) {
+                    return mPhoneService.enableApnType(apnType);
+                } else {
+                    return mPhoneService.disableApnType(apnType);
+                }
+            } catch (RemoteException e) {
+                if (retry == 0) getPhoneService(true);
+            }
+        }
+
+        Log.w(TAG, "Could not " + (enable ? "enable" : "disable")
+                + " APN type \"" + apnType + "\"");
+        return Phone.APN_REQUEST_FAILED;
+    }
+}
diff --git a/core/java/android/net/NetworkConnectivityListener.java b/core/java/android/net/NetworkConnectivityListener.java
new file mode 100644
index 0000000..858fc77
--- /dev/null
+++ b/core/java/android/net/NetworkConnectivityListener.java
@@ -0,0 +1,220 @@
+/*
+ * 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 android.net;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+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.).
+ * {@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/core/java/android/net/NetworkInfo.aidl b/core/java/android/net/NetworkInfo.aidl
new file mode 100644
index 0000000..f501873
--- /dev/null
+++ b/core/java/android/net/NetworkInfo.aidl
@@ -0,0 +1,19 @@
+/**
+ * Copyright (c) 2007, 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 android.net;
+
+parcelable NetworkInfo;
diff --git a/core/java/android/net/NetworkInfo.java b/core/java/android/net/NetworkInfo.java
new file mode 100644
index 0000000..8c82212
--- /dev/null
+++ b/core/java/android/net/NetworkInfo.java
@@ -0,0 +1,380 @@
+/*
+ * 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 android.net;
+
+import android.os.Parcelable;
+import android.os.Parcel;
+
+import java.util.EnumMap;
+
+/**
+ * Describes the status of a network interface of a given type
+ * (currently either Mobile or Wifi).
+ */
+public class NetworkInfo implements Parcelable {
+
+    /**
+     * Coarse-grained network state. This is probably what most applications should
+     * use, rather than {@link android.net.NetworkInfo.DetailedState DetailedState}.
+     * The mapping between the two is as follows:
+     * <br/><br/>
+     * <table>
+     * <tr><td><b>Detailed state</b></td><td><b>Coarse-grained state</b></td></tr>
+     * <tr><td><code>IDLE</code></td><td><code>DISCONNECTED</code></td></tr>
+     * <tr><td><code>SCANNING</code></td><td><code>CONNECTING</code></td></tr>
+     * <tr><td><code>CONNECTING</code></td><td><code>CONNECTING</code></td></tr>
+     * <tr><td><code>AUTHENTICATING</code></td><td><code>CONNECTING</code></td></tr>
+     * <tr><td><code>CONNECTED</code></td><td<code>CONNECTED</code></td></tr>
+     * <tr><td><code>DISCONNECTING</code></td><td><code>DISCONNECTING</code></td></tr>
+     * <tr><td><code>DISCONNECTED</code></td><td><code>DISCONNECTED</code></td></tr>
+     * <tr><td><code>UNAVAILABLE</code></td><td><code>DISCONNECTED</code></td></tr>
+     * <tr><td><code>FAILED</code></td><td><code>DISCONNECTED</code></td></tr>
+     * </table>
+     */
+    public enum State {
+        CONNECTING, CONNECTED, SUSPENDED, DISCONNECTING, DISCONNECTED, UNKNOWN
+    }
+
+    /**
+     * The fine-grained state of a network connection. This level of detail
+     * is probably of interest to few applications. Most should use
+     * {@link android.net.NetworkInfo.State State} instead.
+     */
+    public enum DetailedState {
+        /** Ready to start data connection setup. */
+        IDLE,
+        /** Searching for an available access point. */
+        SCANNING,
+        /** Currently setting up data connection. */
+        CONNECTING,
+        /** Network link established, performing authentication. */
+        AUTHENTICATING,
+        /** Awaiting response from DHCP server in order to assign IP address information. */
+        OBTAINING_IPADDR,
+        /** IP traffic should be available. */
+        CONNECTED,
+        /** IP traffic is suspended */
+        SUSPENDED,
+        /** Currently tearing down data connection. */
+        DISCONNECTING,
+        /** IP traffic not available. */
+        DISCONNECTED,
+        /** Attempt to connect failed. */
+        FAILED
+    }
+
+    /**
+     * This is the map described in the Javadoc comment above. The positions
+     * of the elements of the array must correspond to the ordinal values
+     * of <code>DetailedState</code>.
+     */
+    private static final EnumMap<DetailedState, State> stateMap =
+        new EnumMap<DetailedState, State>(DetailedState.class);
+
+    static {
+        stateMap.put(DetailedState.IDLE, State.DISCONNECTED);
+        stateMap.put(DetailedState.SCANNING, State.DISCONNECTED);
+        stateMap.put(DetailedState.CONNECTING, State.CONNECTING);
+        stateMap.put(DetailedState.AUTHENTICATING, State.CONNECTING);
+        stateMap.put(DetailedState.OBTAINING_IPADDR, State.CONNECTING);
+        stateMap.put(DetailedState.CONNECTED, State.CONNECTED);
+        stateMap.put(DetailedState.SUSPENDED, State.SUSPENDED);
+        stateMap.put(DetailedState.DISCONNECTING, State.DISCONNECTING);
+        stateMap.put(DetailedState.DISCONNECTED, State.DISCONNECTED);
+        stateMap.put(DetailedState.FAILED, State.DISCONNECTED);
+    }
+    
+    private int mNetworkType;
+    private int mSubtype;
+    private String mTypeName;
+    private String mSubtypeName;
+    private State mState;
+    private DetailedState mDetailedState;
+    private String mReason;
+    private String mExtraInfo;
+    private boolean mIsFailover;
+    private boolean mIsRoaming;
+    /**
+     * Indicates whether network connectivity is possible:
+     */
+    private boolean mIsAvailable;
+
+    /**
+     * TODO This is going away as soon as API council review happens.
+     * @param type network type
+     */
+    public NetworkInfo(int type) {}
+
+    NetworkInfo(int type, int subtype, String typeName, String subtypeName) {
+        if (!ConnectivityManager.isNetworkTypeValid(type)) {
+            throw new IllegalArgumentException("Invalid network type: " + type);
+        }
+        mNetworkType = type;
+        mSubtype = subtype;
+        mTypeName = typeName;
+        mSubtypeName = subtypeName;
+        setDetailedState(DetailedState.IDLE, null, null);
+        mState = State.UNKNOWN;
+        mIsAvailable = true;
+        mIsRoaming = false;
+    }
+
+    /**
+     * Reports the type of network (currently mobile or Wi-Fi) to which the
+     * info in this object pertains.
+     * @return the network type
+     */
+    public int getType() {
+        return mNetworkType;
+    }
+
+    /**
+     * Return a network-type-specific integer describing the subtype
+     * of the network.
+     * @return the network subtype
+     *
+     * @hide pending API council review
+     */
+    public int getSubtype() {
+        return mSubtype;
+    }
+
+    void setSubtype(int subtype, String subtypeName) {
+        mSubtype = subtype;
+        mSubtypeName = subtypeName;
+    }
+
+    /**
+     * Return a human-readable name describe the type of the network,
+     * for example "WIFI" or "MOBILE".
+     * @return the name of the network type
+     */
+    public String getTypeName() {
+        return mTypeName;
+    }
+
+    /**
+     * Return a human-readable name describing the subtype of the network.
+     * @return the name of the network subtype
+     * 
+     * @hide pending API council review
+     */
+    public String getSubtypeName() {
+        return mSubtypeName;
+    }
+
+    /**
+     * Indicates whether network connectivity exists or is in the process
+     * of being established. This is good for applications that need to
+     * do anything related to the network other than read or write data.
+     * For the latter, call {@link #isConnected()} instead, which guarantees
+     * that the network is fully usable.
+     * @return {@code true} if network connectivity exists or is in the process
+     * of being established, {@code false} otherwise.
+     */
+    public boolean isConnectedOrConnecting() {
+        return mState == State.CONNECTED || mState == State.CONNECTING;
+    }
+
+    /**
+     * Indicates whether network connectivity exists and it is possible to establish
+     * connections and pass data.
+     * @return {@code true} if network connectivity exists, {@code false} otherwise.
+     */
+    public boolean isConnected() {
+        return mState == State.CONNECTED;
+    }
+
+    /**
+     * Indicates whether network connectivity is possible. A network is unavailable
+     * when a persistent or semi-persistent condition prevents the possibility
+     * of connecting to that network. Examples include
+     * <ul>
+     * <li>The device is out of the coverage area for any network of this type.</li>
+     * <li>The device is on a network other than the home network (i.e., roaming), and
+     * data roaming has been disabled.</li>
+     * <li>The device's radio is turned off, e.g., because airplane mode is enabled.</li>
+     * </ul>
+     * @return {@code true} if the network is available, {@code false} otherwise
+     */
+    public boolean isAvailable() {
+        return mIsAvailable;
+    }
+
+    /**
+     * Sets if the network is available, ie, if the connectivity is possible.
+     * @param isAvailable the new availability value.
+     *
+     * @hide
+     */
+    public void setIsAvailable(boolean isAvailable) {
+        mIsAvailable = isAvailable;
+    }
+
+    /**
+     * Indicates whether the current attempt to connect to the network
+     * resulted from the ConnectivityManager trying to fail over to this
+     * network following a disconnect from another network.
+     * @return {@code true} if this is a failover attempt, {@code false}
+     * otherwise.
+     */
+    public boolean isFailover() {
+        return mIsFailover;
+    }
+
+    /**
+     * Set the failover boolean.
+     * @param isFailover {@code true} to mark the current connection attempt
+     * as a failover.
+     * @hide
+     */
+    public void setFailover(boolean isFailover) {
+        mIsFailover = isFailover;
+    }
+
+    /**
+     * Indicates whether the device is currently roaming on this network.
+     * When {@code true}, it suggests that use of data on this network
+     * may incur extra costs.
+     * @return {@code true} if roaming is in effect, {@code false} otherwise.
+     *
+     * @hide pending API council
+     */
+    public boolean isRoaming() {
+        return mIsRoaming;
+    }
+
+    void setRoaming(boolean isRoaming) {
+        mIsRoaming = isRoaming;
+    }
+
+    /**
+     * Reports the current coarse-grained state of the network.
+     * @return the coarse-grained state
+     */
+    public State getState() {
+        return mState;
+    }
+
+    /**
+     * Reports the current fine-grained state of the network.
+     * @return the fine-grained state
+     */
+    public DetailedState getDetailedState() {
+        return mDetailedState;
+    }
+
+    /**
+     * Sets the fine-grained state of the network.
+     * @param detailedState the {@link DetailedState}.
+     * @param reason a {@code String} indicating the reason for the state change,
+     * if one was supplied. May be {@code null}.
+     * @param extraInfo an optional {@code String} providing addditional network state
+     * information passed up from the lower networking layers.
+     */
+    void setDetailedState(DetailedState detailedState, String reason, String extraInfo) {
+        this.mDetailedState = detailedState;
+        this.mState = stateMap.get(detailedState);
+        this.mReason = reason;
+        this.mExtraInfo = extraInfo;
+    }
+
+    /**
+     * Report the reason an attempt to establish connectivity failed,
+     * if one is available.
+     * @return the reason for failure, or null if not available
+     */
+    public String getReason() {
+        return mReason;
+    }
+
+    /**
+     * Report the extra information about the network state, if any was
+     * provided by the lower networking layers.,
+     * if one is available.
+     * @return the extra information, or null if not available
+     */
+    public String getExtraInfo() {
+        return mExtraInfo;
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder builder = new StringBuilder("NetworkInfo: ");
+        builder.append("type: ").append(getTypeName()).append("[").append(getSubtypeName()).
+                append("], state: ").append(mState).append("/").append(mDetailedState).
+                append(", reason: ").append(mReason == null ? "(unspecified)" : mReason).
+                append(", extra: ").append(mExtraInfo == null ? "(none)" : mExtraInfo).
+                append(", roaming: ").append(mIsRoaming).
+                append(", failover: ").append(mIsFailover).
+                append(", isAvailable: ").append(mIsAvailable);
+        return builder.toString();
+    }
+
+    /**
+     * Implement the Parcelable interface
+     * @hide
+     */
+    public int describeContents() {
+        return 0;
+    }
+
+    /**
+     * Implement the Parcelable interface.
+     * @hide
+     */
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeInt(mNetworkType);
+        dest.writeInt(mSubtype);
+        dest.writeString(mTypeName);
+        dest.writeString(mSubtypeName);
+        dest.writeString(mState.name());
+        dest.writeString(mDetailedState.name());
+        dest.writeInt(mIsFailover ? 1 : 0);
+        dest.writeInt(mIsAvailable ? 1 : 0);
+        dest.writeInt(mIsRoaming ? 1 : 0);
+        dest.writeString(mReason);
+        dest.writeString(mExtraInfo);
+    }
+
+    /**
+     * Implement the Parcelable interface.
+     * @hide
+     */
+    public static final Creator<NetworkInfo> CREATOR =
+        new Creator<NetworkInfo>() {
+            public NetworkInfo createFromParcel(Parcel in) {
+                int netType = in.readInt();
+                int subtype = in.readInt();
+                String typeName = in.readString();
+                String subtypeName = in.readString();
+                NetworkInfo netInfo = new NetworkInfo(netType, subtype, typeName, subtypeName);
+                netInfo.mState = State.valueOf(in.readString());
+                netInfo.mDetailedState = DetailedState.valueOf(in.readString());
+                netInfo.mIsFailover = in.readInt() != 0;
+                netInfo.mIsAvailable = in.readInt() != 0;
+                netInfo.mIsRoaming = in.readInt() != 0;
+                netInfo.mReason = in.readString();
+                netInfo.mExtraInfo = in.readString();
+                return netInfo;
+            }
+
+            public NetworkInfo[] newArray(int size) {
+                return new NetworkInfo[size];
+            }
+        };
+}
diff --git a/core/java/android/net/NetworkStateTracker.java b/core/java/android/net/NetworkStateTracker.java
new file mode 100644
index 0000000..37087ac
--- /dev/null
+++ b/core/java/android/net/NetworkStateTracker.java
@@ -0,0 +1,348 @@
+/*
+ * 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 android.net;
+
+import java.io.FileWriter;
+import java.io.IOException;
+
+import android.os.Handler;
+import android.os.Message;
+import android.os.SystemProperties;
+import android.content.Context;
+import android.text.TextUtils;
+import android.util.Config;
+import android.util.Log;
+
+/**
+ * Each subclass of this class keeps track of the state of connectivity
+ * of a network interface. All state information for a network should
+ * be kept in a Tracker class. This superclass manages the
+ * network-type-independent aspects of network state.
+ *
+ * {@hide}
+ */
+public abstract class NetworkStateTracker extends Handler {
+
+    protected NetworkInfo mNetworkInfo;
+    protected Context mContext;
+    protected Handler mTarget;
+    private boolean mTeardownRequested;
+
+    private static boolean DBG = Config.LOGV; 
+    private static final String TAG = "NetworkStateTracker";
+    
+    public static final int EVENT_STATE_CHANGED = 1;
+    public static final int EVENT_SCAN_RESULTS_AVAILABLE = 2;
+    /**
+     * arg1: 1 to show, 0 to hide
+     * arg2: ID of the notification
+     * obj: Notification (if showing)
+     */
+    public static final int EVENT_NOTIFICATION_CHANGED = 3;
+    public static final int EVENT_CONFIGURATION_CHANGED = 4;
+    public static final int EVENT_ROAMING_CHANGED = 5;
+    public static final int EVENT_NETWORK_SUBTYPE_CHANGED = 6;
+
+    public NetworkStateTracker(Context context,
+            Handler target,
+            int networkType,
+            int subType,
+            String typeName,
+            String subtypeName) {
+        super();
+        mContext = context;
+        mTarget = target;
+        mTeardownRequested = false;
+        this.mNetworkInfo = new NetworkInfo(networkType, subType, typeName, subtypeName);
+    }
+
+    public NetworkInfo getNetworkInfo() {
+        return mNetworkInfo;
+    }
+
+    /**
+     * Return the list of DNS servers associated with this network.
+     * @return a list of the IP addresses of the DNS servers available
+     * for the network.
+     */
+    public abstract String[] getNameServers();
+
+    /**
+     * Return the system properties name associated with the tcp buffer sizes
+     * for this network.
+     */
+    public abstract String getTcpBufferSizesPropName();
+
+    /**
+     * Return the IP addresses of the DNS servers available for this
+     * network interface.
+     * @param propertyNames the names of the system properties whose values
+     * give the IP addresses. Properties with no values are skipped.
+     * @return an array of {@code String}s containing the IP addresses
+     * of the DNS servers, in dot-notation. This may have fewer
+     * non-null entries than the list of names passed in, since
+     * some of the passed-in names may have empty values.
+     */
+    static protected String[] getNameServerList(String[] propertyNames) {
+        String[] dnsAddresses = new String[propertyNames.length];
+        int i, j;
+
+        for (i = 0, j = 0; i < propertyNames.length; i++) {
+            String value = SystemProperties.get(propertyNames[i]);
+            // The GSM layer sometimes sets a bogus DNS server address of
+            // 0.0.0.0
+            if (!TextUtils.isEmpty(value) && !TextUtils.equals(value, "0.0.0.0")) {
+                dnsAddresses[j++] = value;
+            }
+        }
+        return dnsAddresses;
+    }
+
+    /**
+     * Reads the network specific TCP buffer sizes from SystemProperties
+     * net.tcp.buffersize.[default|wifi|umts|edge|gprs] and set them for system
+     * wide use
+     */
+   public void updateNetworkSettings() {
+        String key = getTcpBufferSizesPropName();
+        String bufferSizes = SystemProperties.get(key);
+
+        if (bufferSizes.length() == 0) {
+            Log.e(TAG, key + " not found in system properties. Using defaults");
+
+            // Setting to default values so we won't be stuck to previous values
+            key = "net.tcp.buffersize.default";
+            bufferSizes = SystemProperties.get(key);
+        }
+
+        // Set values in kernel
+        if (bufferSizes.length() != 0) {
+            if (DBG) {
+                Log.v(TAG, "Setting TCP values: [" + bufferSizes
+                        + "] which comes from [" + key + "]");
+            }
+            setBufferSize(bufferSizes);
+        }
+    }
+
+    /**
+     * Release the wakelock, if any, that may be held while handling a
+     * disconnect operation.
+     */
+    public void releaseWakeLock() {
+    }
+
+    /**
+     * Writes TCP buffer sizes to /sys/kernel/ipv4/tcp_[r/w]mem_[min/def/max]
+     * which maps to /proc/sys/net/ipv4/tcp_rmem and tcpwmem
+     * 
+     * @param bufferSizes in the format of "readMin, readInitial, readMax,
+     *        writeMin, writeInitial, writeMax"
+     */
+    private void setBufferSize(String bufferSizes) {
+        try {
+            String[] values = bufferSizes.split(",");
+
+            if (values.length == 6) {
+              final String prefix = "/sys/kernel/ipv4/tcp_";
+                stringToFile(prefix + "rmem_min", values[0]);
+                stringToFile(prefix + "rmem_def", values[1]);
+                stringToFile(prefix + "rmem_max", values[2]);
+                stringToFile(prefix + "wmem_min", values[3]);
+                stringToFile(prefix + "wmem_def", values[4]);
+                stringToFile(prefix + "wmem_max", values[5]);
+            } else {
+                Log.e(TAG, "Invalid buffersize string: " + bufferSizes);
+            }
+        } catch (IOException e) {
+            Log.e(TAG, "Can't set tcp buffer sizes:" + e);
+        }
+    }
+
+    /**
+     * Writes string to file. Basically same as "echo -n $string > $filename"
+     * 
+     * @param filename
+     * @param string
+     * @throws IOException
+     */
+    private void stringToFile(String filename, String string) throws IOException {
+        FileWriter out = new FileWriter(filename);
+        try {
+            out.write(string);
+        } finally {
+            out.close();
+        }
+    }
+
+    /**
+     * Record the detailed state of a network, and if it is a
+     * change from the previous state, send a notification to
+     * any listeners.
+     * @param state the new @{code DetailedState}
+     */
+    public void setDetailedState(NetworkInfo.DetailedState state) {
+        setDetailedState(state, null, null);
+    }
+
+    /**
+     * Record the detailed state of a network, and if it is a
+     * change from the previous state, send a notification to
+     * any listeners.
+     * @param state the new @{code DetailedState}
+     * @param reason a {@code String} indicating a reason for the state change,
+     * if one was supplied. May be {@code null}.
+     * @param extraInfo optional {@code String} providing extra information about the state change
+     */
+    public void setDetailedState(NetworkInfo.DetailedState state, String reason, String extraInfo) {
+        if (state != mNetworkInfo.getDetailedState()) {
+            boolean wasConnecting = (mNetworkInfo.getState() == NetworkInfo.State.CONNECTING);
+            String lastReason = mNetworkInfo.getReason();
+            /*
+             * If a reason was supplied when the CONNECTING state was entered, and no
+             * reason was supplied for entering the CONNECTED state, then retain the
+             * reason that was supplied when going to CONNECTING.
+             */
+            if (wasConnecting && state == NetworkInfo.DetailedState.CONNECTED && reason == null
+                    && lastReason != null)
+                reason = lastReason;
+            mNetworkInfo.setDetailedState(state, reason, extraInfo);
+            Message msg = mTarget.obtainMessage(EVENT_STATE_CHANGED, mNetworkInfo);
+            msg.sendToTarget();
+        }
+    }
+
+    protected void setDetailedStateInternal(NetworkInfo.DetailedState state) {
+        mNetworkInfo.setDetailedState(state, null, null);
+    }
+
+    public void setTeardownRequested(boolean isRequested) {
+        mTeardownRequested = isRequested;
+    }
+
+    public boolean isTeardownRequested() {
+        return mTeardownRequested;
+    }
+    
+    /**
+     * Send a  notification that the results of a scan for network access
+     * points has completed, and results are available.
+     */
+    protected void sendScanResultsAvailable() {
+        Message msg = mTarget.obtainMessage(EVENT_SCAN_RESULTS_AVAILABLE, mNetworkInfo);
+        msg.sendToTarget();
+    }
+
+    /**
+     * Record the roaming status of the device, and if it is a change from the previous
+     * status, send a notification to any listeners.
+     * @param isRoaming {@code true} if the device is now roaming, {@code false}
+     * if it is no longer roaming.
+     */
+    protected void setRoamingStatus(boolean isRoaming) {
+        if (isRoaming != mNetworkInfo.isRoaming()) {
+            mNetworkInfo.setRoaming(isRoaming);
+            Message msg = mTarget.obtainMessage(EVENT_ROAMING_CHANGED, mNetworkInfo);
+            msg.sendToTarget();
+        }
+    }
+
+    protected void setSubtype(int subtype, String subtypeName) {
+        if (mNetworkInfo.isConnected()) {
+            int oldSubtype = mNetworkInfo.getSubtype();
+            if (subtype != oldSubtype) {
+                mNetworkInfo.setSubtype(subtype, subtypeName);
+                Message msg = mTarget.obtainMessage(
+                        EVENT_NETWORK_SUBTYPE_CHANGED, oldSubtype, 0, mNetworkInfo);
+                msg.sendToTarget();
+            }
+        }
+    }
+
+    public abstract void startMonitoring();
+
+    /**
+     * Disable connectivity to a network
+     * @return {@code true} if a teardown occurred, {@code false} if the
+     * teardown did not occur.
+     */
+    public abstract boolean teardown();
+
+    /**
+     * Reenable connectivity to a network after a {@link #teardown()}.
+     */
+    public abstract boolean reconnect();
+
+    /**
+     * Turn the wireless radio off for a network.
+     * @param turnOn {@code true} to turn the radio on, {@code false}
+     */
+    public abstract boolean setRadio(boolean turnOn);
+
+    /**
+     * Returns an indication of whether this network is available for
+     * connections. A value of {@code false} means that some quasi-permanent
+     * condition prevents connectivity to this network.
+     */
+    public abstract boolean isAvailable();
+
+    /**
+     * Tells the underlying networking system that the caller wants to
+     * begin using the named feature. The interpretation of {@code feature}
+     * is completely up to each networking implementation.
+     * @param feature the name of the feature to be used
+     * @param callingPid the process ID of the process that is issuing this request
+     * @param callingUid the user ID of the process that is issuing this request
+     * @return an integer value representing the outcome of the request.
+     * The interpretation of this value is specific to each networking
+     * implementation+feature combination, except that the value {@code -1}
+     * always indicates failure.
+     */
+    public abstract int startUsingNetworkFeature(String feature, int callingPid, int callingUid);
+
+    /**
+     * Tells the underlying networking system that the caller is finished
+     * using the named feature. The interpretation of {@code feature}
+     * is completely up to each networking implementation.
+     * @param feature the name of the feature that is no longer needed.
+     * @param callingPid the process ID of the process that is issuing this request
+     * @param callingUid the user ID of the process that is issuing this request
+     * @return an integer value representing the outcome of the request.
+     * The interpretation of this value is specific to each networking
+     * implementation+feature combination, except that the value {@code -1}
+     * always indicates failure.
+     */
+    public abstract int stopUsingNetworkFeature(String feature, int callingPid, int callingUid);
+
+    /**
+     * Ensure that a network route exists to deliver traffic to the specified
+     * host via this network interface.
+     * @param hostAddress the IP address of the host to which the route is desired
+     * @return {@code true} on success, {@code false} on failure
+     */
+    public boolean requestRouteToHost(int hostAddress) {
+        return false;
+    }
+
+    /**
+     * Interprets scan results. This will be called at a safe time for
+     * processing, and from a safe thread.
+     */
+    public void interpretScanResultsAvailable() {
+    }
+
+}
diff --git a/core/java/android/net/NetworkUtils.java b/core/java/android/net/NetworkUtils.java
new file mode 100644
index 0000000..1153648
--- /dev/null
+++ b/core/java/android/net/NetworkUtils.java
@@ -0,0 +1,128 @@
+/*
+ * 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 android.net;
+
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+
+/**
+ * Native methods for managing network interfaces.
+ *
+ * {@hide}
+ */
+public class NetworkUtils {
+    /** Bring the named network interface down. */
+    public native static int disableInterface(String interfaceName);
+
+    /** Add a route to the specified host via the named interface. */
+    public native static int addHostRoute(String interfaceName, int hostaddr);
+
+    /** Add a default route for the named interface. */
+    public native static int setDefaultRoute(String interfaceName, int gwayAddr);
+
+    /** Return the gateway address for the default route for the named interface. */
+    public native static int getDefaultRoute(String interfaceName);
+
+    /** Remove host routes that uses the named interface. */
+    public native static int removeHostRoutes(String interfaceName);
+
+    /** Remove the default route for the named interface. */
+    public native static int removeDefaultRoute(String interfaceName);
+
+    /** Reset any sockets that are connected via the named interface. */
+    public native static int resetConnections(String interfaceName);
+
+    /**
+     * Start the DHCP client daemon, in order to have it request addresses
+     * for the named interface, and then configure the interface with those
+     * addresses. This call blocks until it obtains a result (either success
+     * or failure) from the daemon.
+     * @param interfaceName the name of the interface to configure
+     * @param ipInfo if the request succeeds, this object is filled in with
+     * the IP address information.
+     * @return {@code true} for success, {@code false} for failure
+     */
+    public native static boolean runDhcp(String interfaceName, DhcpInfo ipInfo);
+
+    /**
+     * Shut down the DHCP client daemon.
+     * @param interfaceName the name of the interface for which the daemon
+     * should be stopped
+     * @return {@code true} for success, {@code false} for failure
+     */
+    public native static boolean stopDhcp(String interfaceName);
+
+    /**
+     * Release the current DHCP lease.
+     * @param interfaceName the name of the interface for which the lease should
+     * be released
+     * @return {@code true} for success, {@code false} for failure
+     */
+    public native static boolean releaseDhcpLease(String interfaceName);
+
+    /**
+     * Return the last DHCP-related error message that was recorded.
+     * <p/>NOTE: This string is not localized, but currently it is only
+     * used in logging.
+     * @return the most recent error message, if any
+     */
+    public native static String getDhcpError();
+
+    /**
+     * When static IP configuration has been specified, configure the network
+     * interface according to the values supplied.
+     * @param interfaceName the name of the interface to configure
+     * @param ipInfo the IP address, default gateway, and DNS server addresses
+     * with which to configure the interface.
+     * @return {@code true} for success, {@code false} for failure
+     */
+    public static boolean configureInterface(String interfaceName, DhcpInfo ipInfo) {
+        return configureNative(interfaceName,
+            ipInfo.ipAddress,
+            ipInfo.netmask,
+            ipInfo.gateway,
+            ipInfo.dns1,
+            ipInfo.dns2);
+    }
+
+    private native static boolean configureNative(
+        String interfaceName, int ipAddress, int netmask, int gateway, int dns1, int dns2);
+
+    /**
+     * Look up a host name and return the result as an int. Works if the argument
+     * is an IP address in dot notation. Obviously, this can only be used for IPv4
+     * addresses.
+     * @param hostname the name of the host (or the IP address)
+     * @return the IP address as an {@code int} in network byte order
+     */
+    public static int lookupHost(String hostname) {
+        InetAddress inetAddress;
+        try {
+            inetAddress = InetAddress.getByName(hostname);
+        } catch (UnknownHostException e) {
+            return -1;
+        }
+        byte[] addrBytes;
+        int addr;
+        addrBytes = inetAddress.getAddress();
+        addr = ((addrBytes[3] & 0xff) << 24)
+                | ((addrBytes[2] & 0xff) << 16)
+                | ((addrBytes[1] & 0xff) << 8)
+                |  (addrBytes[0] & 0xff);
+        return addr;
+    }
+}
diff --git a/core/java/android/net/ParseException.java b/core/java/android/net/ParseException.java
new file mode 100644
index 0000000..000fa68
--- /dev/null
+++ b/core/java/android/net/ParseException.java
@@ -0,0 +1,30 @@
+/*
+ * 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 android.net;
+
+/**
+ * 
+ *
+ * When WebAddress Parser Fails, this exception is thrown
+ */
+public class ParseException extends RuntimeException {
+    public String response;
+
+    ParseException(String response) {
+        this.response = response;
+    }
+}
diff --git a/core/java/android/net/Proxy.java b/core/java/android/net/Proxy.java
new file mode 100644
index 0000000..9f07c0a
--- /dev/null
+++ b/core/java/android/net/Proxy.java
@@ -0,0 +1,120 @@
+/*
+ * Copyright (C) 2007 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 android.net;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.os.SystemProperties;
+import android.provider.Settings;
+import android.util.Log;
+
+import junit.framework.Assert;
+
+/**
+ * A convenience class for accessing the user and default proxy
+ * settings.
+ */
+final public class Proxy {
+
+    static final public String PROXY_CHANGE_ACTION =
+        "android.intent.action.PROXY_CHANGE";
+
+    /**
+     * Return the proxy host set by the user.
+     * @param ctx A Context used to get the settings for the proxy host.
+     * @return String containing the host name. If the user did not set a host
+     *         name it returns the default host. A null value means that no
+     *         host is to be used.
+     */
+    static final public String getHost(Context ctx) {
+        ContentResolver contentResolver = ctx.getContentResolver();
+        Assert.assertNotNull(contentResolver);
+        String host = Settings.Secure.getString(
+                contentResolver,
+                Settings.Secure.HTTP_PROXY);
+        if (host != null) {
+            int i = host.indexOf(':');
+            if (i == -1) {
+                if (android.util.Config.DEBUG) {
+                    Assert.assertTrue(host.length() == 0);
+                }
+                return null;
+            }
+            return host.substring(0, i);
+        }
+        return getDefaultHost();
+    }
+
+    /**
+     * Return the proxy port set by the user.
+     * @param ctx A Context used to get the settings for the proxy port.
+     * @return The port number to use or -1 if no proxy is to be used.
+     */
+    static final public int getPort(Context ctx) {
+        ContentResolver contentResolver = ctx.getContentResolver();
+        Assert.assertNotNull(contentResolver);
+        String host = Settings.Secure.getString(
+                contentResolver,
+                Settings.Secure.HTTP_PROXY);
+        if (host != null) {
+            int i = host.indexOf(':');
+            if (i == -1) {
+                if (android.util.Config.DEBUG) {
+                    Assert.assertTrue(host.length() == 0);
+                }
+                return -1;
+            }
+            if (android.util.Config.DEBUG) {
+                Assert.assertTrue(i < host.length());
+            }
+            return Integer.parseInt(host.substring(i+1));
+        }
+        return getDefaultPort();
+    }
+
+    /**
+     * Return the default proxy host specified by the carrier.
+     * @return String containing the host name or null if there is no proxy for
+     * this carrier.
+     */
+    static final public String getDefaultHost() {
+        String host = SystemProperties.get("net.gprs.http-proxy");
+        if (host != null) {
+            Uri u = Uri.parse(host);
+            host = u.getHost();
+            return host;
+        } else {
+            return null;
+        }
+    }
+
+    /**
+     * Return the default proxy port specified by the carrier.
+     * @return The port number to be used with the proxy host or -1 if there is
+     * no proxy for this carrier.
+     */
+    static final public int getDefaultPort() {
+        String host = SystemProperties.get("net.gprs.http-proxy");
+        if (host != null) {
+            Uri u = Uri.parse(host);
+            return u.getPort();
+        } else {
+            return -1;
+        }
+    }
+
+};
diff --git a/core/java/android/net/SSLCertificateSocketFactory.java b/core/java/android/net/SSLCertificateSocketFactory.java
new file mode 100644
index 0000000..deaa3c3
--- /dev/null
+++ b/core/java/android/net/SSLCertificateSocketFactory.java
@@ -0,0 +1,290 @@
+/*
+ * 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 android.net;
+
+import android.net.http.DomainNameChecker;
+import android.os.SystemProperties;
+import android.util.Config;
+import android.util.Log;
+
+import java.io.IOException;
+import java.net.InetAddress;
+import java.net.Socket;
+import java.security.GeneralSecurityException;
+import java.security.KeyManagementException;
+import java.security.KeyStore;
+import java.security.KeyStoreException;
+import java.security.NoSuchAlgorithmException;
+import java.security.cert.Certificate;
+import java.security.cert.X509Certificate;
+
+import javax.net.SocketFactory;
+import javax.net.ssl.SSLSocket;
+import javax.net.ssl.SSLSocketFactory;
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.TrustManagerFactory;
+import javax.net.ssl.X509TrustManager;
+
+import org.apache.harmony.xnet.provider.jsse.SSLClientSessionCache;
+import org.apache.harmony.xnet.provider.jsse.SSLContextImpl;
+
+/**
+ * SSLSocketFactory that provides optional (on debug devices, only) skipping of ssl certificfate
+ * chain validation and custom read timeouts used just when connecting to the server/negotiating
+ * an ssl session.
+ *
+ * You can skip the ssl certificate checking at runtime by setting socket.relaxsslcheck=yes on
+ * devices that do not have have ro.secure set.
+ */
+public class SSLCertificateSocketFactory extends SSLSocketFactory {
+
+    private static final String LOG_TAG = "SSLCertificateSocketFactory";
+
+    private static X509TrustManager sDefaultTrustManager;
+
+    static {
+        try {
+            TrustManagerFactory tmf = TrustManagerFactory.getInstance("X509");
+            tmf.init((KeyStore)null);
+            TrustManager[] tms = tmf.getTrustManagers();
+            if (tms != null) {
+                for (TrustManager tm : tms) {
+                    if (tm instanceof X509TrustManager) {
+                        sDefaultTrustManager = (X509TrustManager)tm;
+                        break;
+                    }
+                }
+            }
+        } catch (NoSuchAlgorithmException e) {
+            Log.e(LOG_TAG, "Unable to get X509 Trust Manager ", e);
+        } catch (KeyStoreException e) {
+            Log.e(LOG_TAG, "Key Store exception while initializing TrustManagerFactory ", e);
+        }
+    }
+
+    private static final TrustManager[] TRUST_MANAGER = new TrustManager[] {
+        new X509TrustManager() {
+            public X509Certificate[] getAcceptedIssuers() {
+                return null;
+            }
+
+            public void checkClientTrusted(X509Certificate[] certs,
+                    String authType) { }
+
+            public void checkServerTrusted(X509Certificate[] certs,
+                    String authType) { }
+        }
+    };
+
+    private final SSLSocketFactory mFactory;
+
+    private final int mSocketReadTimeoutForSslHandshake;
+
+    /**
+     * Do not use this constructor (will be deprecated).  Use {@link #getDefault(int)} instead.
+     */
+    public SSLCertificateSocketFactory(int socketReadTimeoutForSslHandshake)
+            throws NoSuchAlgorithmException, KeyManagementException {
+        this(socketReadTimeoutForSslHandshake, null /* cache */);
+    }
+
+    private SSLCertificateSocketFactory(int socketReadTimeoutForSslHandshake,
+            SSLClientSessionCache cache) throws NoSuchAlgorithmException, KeyManagementException {
+        SSLContextImpl sslContext = new SSLContextImpl();
+        sslContext.engineInit(null /* kms */,
+            TRUST_MANAGER, new java.security.SecureRandom(),
+            cache /* client cache */, null /* server cache */);
+        this.mFactory = sslContext.engineGetSocketFactory();
+        this.mSocketReadTimeoutForSslHandshake = socketReadTimeoutForSslHandshake;
+    }
+
+    /**
+     * Returns a new instance of a socket factory using the specified socket read
+     * timeout while connecting with the server/negotiating an ssl session.
+     *
+     * @param socketReadTimeoutForSslHandshake the socket read timeout used for performing
+     *        ssl handshake. The socket read timeout is set back to 0 after the handshake.
+     * @return a new SocketFactory, or null on error
+     */
+    public static SocketFactory getDefault(int socketReadTimeoutForSslHandshake) {
+        return getDefault(socketReadTimeoutForSslHandshake, null /* cache */);
+    }
+
+    /**
+     * Returns a new instance of a socket factory using the specified socket read
+     * timeout while connecting with the server/negotiating an ssl session.
+     * Persists ssl sessions using the provided {@link SSLClientSessionCache}.
+     *
+     * @param socketReadTimeoutForSslHandshake the socket read timeout used for performing
+     *        ssl handshake. The socket read timeout is set back to 0 after the handshake.
+     * @param cache The {@link SSLClientSessionCache} to use, if any.
+     * @return a new SocketFactory, or null on error
+     *
+     * @hide
+    */
+    public static SocketFactory getDefault(int socketReadTimeoutForSslHandshake,
+            SSLClientSessionCache cache) {
+        try {
+            return new SSLCertificateSocketFactory(socketReadTimeoutForSslHandshake, cache);
+        } catch (NoSuchAlgorithmException e) {
+            Log.e(LOG_TAG, 
+                    "SSLCertifcateSocketFactory.getDefault" +
+                    " NoSuchAlgorithmException " , e);
+            return null;
+        } catch (KeyManagementException e) {
+            Log.e(LOG_TAG, 
+                    "SSLCertifcateSocketFactory.getDefault" +
+                    " KeyManagementException " , e);
+            return null; 
+        }
+    }
+
+    private boolean hasValidCertificateChain(Certificate[] certs) 
+            throws IOException {
+        if (sDefaultTrustManager == null) {
+            if (Config.LOGD) {
+                Log.d(LOG_TAG,"hasValidCertificateChain():" +
+                          " null default trust manager!");
+            }
+            throw new IOException("null default trust manager");
+        }
+
+        boolean trusted = (certs != null && (certs.length > 0));
+
+        if (trusted) {
+            try {
+                // the authtype we pass in doesn't actually matter
+                sDefaultTrustManager.checkServerTrusted((X509Certificate[]) certs, "RSA");
+            } catch (GeneralSecurityException e) { 
+                String exceptionMessage = e != null ? e.getMessage() : "none";
+                if (Config.LOGD) {
+                    Log.d(LOG_TAG,"hasValidCertificateChain(): sec. exception: "
+                         + exceptionMessage);
+                }
+                trusted = false;
+            }
+        }
+
+        return trusted;
+    }
+
+    private void validateSocket(SSLSocket sslSock, String destHost) 
+            throws IOException
+    {
+        if (Config.LOGV) {
+            Log.v(LOG_TAG,"validateSocket() to host "+destHost);
+        }
+
+        String relaxSslCheck = SystemProperties.get("socket.relaxsslcheck");
+        String secure = SystemProperties.get("ro.secure");
+
+        // only allow relaxing the ssl check on non-secure builds where the relaxation is
+        // specifically requested.
+        if ("0".equals(secure) && "yes".equals(relaxSslCheck)) {
+            if (Config.LOGD) {
+                Log.d(LOG_TAG,"sys prop socket.relaxsslcheck is set," +
+                        " ignoring invalid certs");
+            }
+            return;
+        }
+
+        Certificate[] certs = null;
+        sslSock.setUseClientMode(true);
+        sslSock.startHandshake();
+        certs = sslSock.getSession().getPeerCertificates();
+
+        // check that the root certificate in the chain belongs to
+        // a CA we trust
+        if (certs == null) {
+            Log.e(LOG_TAG, 
+                    "[SSLCertificateSocketFactory] no trusted root CA");
+            throw new IOException("no trusted root CA");
+        }
+
+        if (Config.LOGV) {
+            Log.v(LOG_TAG,"validateSocket # certs = " +certs.length);
+        }
+
+        if (!hasValidCertificateChain(certs)) {
+            if (Config.LOGD) {
+                Log.d(LOG_TAG,"validateSocket(): certificate untrusted!");
+            }
+            throw new IOException("Certificate untrusted");
+        }
+
+        X509Certificate lastChainCert = (X509Certificate) certs[0];
+
+        if (!DomainNameChecker.match(lastChainCert, destHost)) {
+            if (Config.LOGD) {
+                Log.d(LOG_TAG,"validateSocket(): domain name check failed");
+            }
+            throw new IOException("Domain Name check failed");
+        }
+    }
+
+    public Socket createSocket(Socket socket, String s, int i, boolean flag)
+            throws IOException
+    {
+        throw new IOException("Cannot validate certification without a hostname");       
+    }
+
+    public Socket createSocket(InetAddress inaddr, int i, InetAddress inaddr2, int j)
+            throws IOException
+    {
+        throw new IOException("Cannot validate certification without a hostname");       
+    }
+
+    public Socket createSocket(InetAddress inaddr, int i) throws IOException {
+        throw new IOException("Cannot validate certification without a hostname");       
+    }
+
+    public Socket createSocket(String s, int i, InetAddress inaddr, int j) throws IOException {
+        SSLSocket sslSock = (SSLSocket) mFactory.createSocket(s, i, inaddr, j);
+
+        if (mSocketReadTimeoutForSslHandshake >= 0) {
+            sslSock.setSoTimeout(mSocketReadTimeoutForSslHandshake);
+        }
+
+        validateSocket(sslSock,s);
+        sslSock.setSoTimeout(0);
+        
+        return sslSock;
+    }
+
+    public Socket createSocket(String s, int i) throws IOException {
+        SSLSocket sslSock = (SSLSocket) mFactory.createSocket(s, i);
+
+        if (mSocketReadTimeoutForSslHandshake >= 0) {
+            sslSock.setSoTimeout(mSocketReadTimeoutForSslHandshake);
+        }
+        
+        validateSocket(sslSock,s);
+        sslSock.setSoTimeout(0);
+
+        return sslSock;
+    }
+
+    public String[] getDefaultCipherSuites() {
+        return mFactory.getSupportedCipherSuites();
+    }
+
+    public String[] getSupportedCipherSuites() {
+        return mFactory.getSupportedCipherSuites();
+    }
+}
+
+
diff --git a/core/java/android/net/SntpClient.java b/core/java/android/net/SntpClient.java
new file mode 100644
index 0000000..28134b2
--- /dev/null
+++ b/core/java/android/net/SntpClient.java
@@ -0,0 +1,201 @@
+/*
+ * 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 android.net;
+
+import android.os.SystemClock;
+import android.util.Config;
+import android.util.Log;
+
+import java.io.IOException;
+import java.net.DatagramPacket;
+import java.net.DatagramSocket;
+import java.net.InetAddress;
+
+/**
+ * {@hide}
+ *
+ * Simple SNTP client class for retrieving network time.
+ *
+ * Sample usage:
+ * <pre>SntpClient client = new SntpClient();
+ * if (client.requestTime("time.foo.com")) {
+ *     long now = client.getNtpTime() + SystemClock.elapsedRealtime() - client.getNtpTimeReference();
+ * }
+ * </pre>
+ */
+public class SntpClient
+{
+    private static final String TAG = "SntpClient";
+
+    private static final int REFERENCE_TIME_OFFSET = 16;
+    private static final int ORIGINATE_TIME_OFFSET = 24;
+    private static final int RECEIVE_TIME_OFFSET = 32;
+    private static final int TRANSMIT_TIME_OFFSET = 40;
+    private static final int NTP_PACKET_SIZE = 48;
+
+    private static final int NTP_PORT = 123;
+    private static final int NTP_MODE_CLIENT = 3;
+    private static final int NTP_VERSION = 3;
+
+    // Number of seconds between Jan 1, 1900 and Jan 1, 1970
+    // 70 years plus 17 leap days
+    private static final long OFFSET_1900_TO_1970 = ((365L * 70L) + 17L) * 24L * 60L * 60L;
+
+    // system time computed from NTP server response
+    private long mNtpTime;
+
+    // value of SystemClock.elapsedRealtime() corresponding to mNtpTime
+    private long mNtpTimeReference;
+
+    // round trip time in milliseconds
+    private long mRoundTripTime;
+
+    /**
+     * Sends an SNTP request to the given host and processes the response.
+     *
+     * @param host host name of the server.
+     * @param timeout network timeout in milliseconds.
+     * @return true if the transaction was successful.
+     */
+    public boolean requestTime(String host, int timeout) {
+        try {
+            DatagramSocket socket = new DatagramSocket();
+            socket.setSoTimeout(timeout);
+            InetAddress address = InetAddress.getByName(host);
+            byte[] buffer = new byte[NTP_PACKET_SIZE];
+            DatagramPacket request = new DatagramPacket(buffer, buffer.length, address, NTP_PORT);
+
+            // set mode = 3 (client) and version = 3                                                                          
+            // mode is in low 3 bits of first byte
+            // version is in bits 3-5 of first byte
+            buffer[0] = NTP_MODE_CLIENT | (NTP_VERSION << 3);
+
+            // get current time and write it to the request packet
+            long requestTime = System.currentTimeMillis();
+            long requestTicks = SystemClock.elapsedRealtime();
+            writeTimeStamp(buffer, TRANSMIT_TIME_OFFSET, requestTime);
+
+            socket.send(request);
+            
+            // read the response
+            DatagramPacket response = new DatagramPacket(buffer, buffer.length);
+            socket.receive(response);
+            long responseTicks = SystemClock.elapsedRealtime();
+            long responseTime = requestTime + (responseTicks - requestTicks);
+            socket.close();
+
+            // extract the results
+            long originateTime = readTimeStamp(buffer, ORIGINATE_TIME_OFFSET);
+            long receiveTime = readTimeStamp(buffer, RECEIVE_TIME_OFFSET);
+            long transmitTime = readTimeStamp(buffer, TRANSMIT_TIME_OFFSET);
+            long roundTripTime = responseTicks - requestTicks - (transmitTime - receiveTime);
+            long clockOffset = (receiveTime - originateTime) + (transmitTime - responseTime);
+            if (Config.LOGD) Log.d(TAG, "round trip: " + roundTripTime + " ms");
+            if (Config.LOGD) Log.d(TAG, "clock offset: " + clockOffset + " ms");
+
+            // save our results
+            mNtpTime = requestTime + clockOffset;
+            mNtpTimeReference = requestTicks;
+            mRoundTripTime = roundTripTime;
+        } catch (Exception e) {
+            if (Config.LOGD) Log.d(TAG, "request time failed: " + e);
+            return false;
+        }
+
+        return true;
+    }
+
+    /**
+     * Returns the time computed from the NTP transaction.
+     *
+     * @return time value computed from NTP server response.
+     */
+    public long getNtpTime() {
+        return mNtpTime;
+    }
+
+    /**
+     * Returns the reference clock value (value of SystemClock.elapsedRealtime())
+     * corresponding to the NTP time.
+     *
+     * @return reference clock corresponding to the NTP time.
+     */
+    public long getNtpTimeReference() {
+        return mNtpTimeReference;
+    }
+
+    /**
+     * Returns the round trip time of the NTP transaction
+     *
+     * @return round trip time in milliseconds.
+     */
+    public long getRoundTripTime() {
+        return mRoundTripTime;
+    }
+
+    /**
+     * Reads an unsigned 32 bit big endian number from the given offset in the buffer.
+     */
+    private long read32(byte[] buffer, int offset) {
+        byte b0 = buffer[offset];
+        byte b1 = buffer[offset+1];
+        byte b2 = buffer[offset+2];
+        byte b3 = buffer[offset+3];
+
+        // convert signed bytes to unsigned values
+        int i0 = ((b0 & 0x80) == 0x80 ? (b0 & 0x7F) + 0x80 : b0);
+        int i1 = ((b1 & 0x80) == 0x80 ? (b1 & 0x7F) + 0x80 : b1);
+        int i2 = ((b2 & 0x80) == 0x80 ? (b2 & 0x7F) + 0x80 : b2);
+        int i3 = ((b3 & 0x80) == 0x80 ? (b3 & 0x7F) + 0x80 : b3);
+
+        return ((long)i0 << 24) + ((long)i1 << 16) + ((long)i2 << 8) + (long)i3;
+    }
+
+    /**
+     * Reads the NTP time stamp at the given offset in the buffer and returns 
+     * it as a system time (milliseconds since January 1, 1970).
+     */    
+    private long readTimeStamp(byte[] buffer, int offset) {
+        long seconds = read32(buffer, offset);
+        long fraction = read32(buffer, offset + 4);
+        return ((seconds - OFFSET_1900_TO_1970) * 1000) + ((fraction * 1000L) / 0x100000000L);        
+    }
+
+    /**
+     * Writes system time (milliseconds since January 1, 1970) as an NTP time stamp 
+     * at the given offset in the buffer.
+     */    
+    private void writeTimeStamp(byte[] buffer, int offset, long time) {
+        long seconds = time / 1000L;
+        long milliseconds = time - seconds * 1000L;
+        seconds += OFFSET_1900_TO_1970;
+
+        // write seconds in big endian format
+        buffer[offset++] = (byte)(seconds >> 24);
+        buffer[offset++] = (byte)(seconds >> 16);
+        buffer[offset++] = (byte)(seconds >> 8);
+        buffer[offset++] = (byte)(seconds >> 0);
+
+        long fraction = milliseconds * 0x100000000L / 1000L;
+        // write fraction in big endian format
+        buffer[offset++] = (byte)(fraction >> 24);
+        buffer[offset++] = (byte)(fraction >> 16);
+        buffer[offset++] = (byte)(fraction >> 8);
+        // low order bits should be random data
+        buffer[offset++] = (byte)(Math.random() * 255.0);
+    }
+}
diff --git a/core/java/android/net/Uri.aidl b/core/java/android/net/Uri.aidl
new file mode 100755
index 0000000..6bd3be5
--- /dev/null
+++ b/core/java/android/net/Uri.aidl
@@ -0,0 +1,19 @@
+/**
+ * Copyright (c) 2007, 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 android.net;
+
+parcelable Uri;
diff --git a/core/java/android/net/Uri.java b/core/java/android/net/Uri.java
new file mode 100644
index 0000000..c23df21
--- /dev/null
+++ b/core/java/android/net/Uri.java
@@ -0,0 +1,2252 @@
+/*
+ * Copyright (C) 2007 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 android.net;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.Log;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.io.ByteArrayOutputStream;
+import java.net.URLEncoder;
+import java.util.AbstractList;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.RandomAccess;
+
+/**
+ * Immutable URI reference. A URI reference includes a URI and a fragment, the
+ * component of the URI following a '#'. Builds and parses URI references
+ * which conform to
+ * <a href="http://www.faqs.org/rfcs/rfc2396.html">RFC 2396</a>.
+ *
+ * <p>In the interest of performance, this class performs little to no
+ * validation. Behavior is undefined for invalid input. This class is very
+ * forgiving--in the face of invalid input, it will return garbage
+ * rather than throw an exception unless otherwise specified.
+ */
+public abstract class Uri implements Parcelable, Comparable<Uri> {
+
+    /*
+
+    This class aims to do as little up front work as possible. To accomplish
+    that, we vary the implementation dependending on what the user passes in.
+    For example, we have one implementation if the user passes in a
+    URI string (StringUri) and another if the user passes in the
+    individual components (OpaqueUri).
+
+    *Concurrency notes*: Like any truly immutable object, this class is safe
+    for concurrent use. This class uses a caching pattern in some places where
+    it doesn't use volatile or synchronized. This is safe to do with ints
+    because getting or setting an int is atomic. It's safe to do with a String
+    because the internal fields are final and the memory model guarantees other
+    threads won't see a partially initialized instance. We are not guaranteed
+    that some threads will immediately see changes from other threads on
+    certain platforms, but we don't mind if those threads reconstruct the
+    cached result. As a result, we get thread safe caching with no concurrency
+    overhead, which means the most common case, access from a single thread,
+    is as fast as possible.
+
+    From the Java Language spec.:
+
+    "17.5 Final Field Semantics
+
+    ... when the object is seen by another thread, that thread will always
+    see the correctly constructed version of that object's final fields.
+    It will also see versions of any object or array referenced by
+    those final fields that are at least as up-to-date as the final fields
+    are."
+
+    In that same vein, all non-transient fields within Uri
+    implementations should be final and immutable so as to ensure true
+    immutability for clients even when they don't use proper concurrency
+    control.
+
+    For reference, from RFC 2396:
+
+    "4.3. Parsing a URI Reference
+
+       A URI reference is typically parsed according to the four main
+       components and fragment identifier in order to determine what
+       components are present and whether the reference is relative or
+       absolute.  The individual components are then parsed for their
+       subparts and, if not opaque, to verify their validity.
+
+       Although the BNF defines what is allowed in each component, it is
+       ambiguous in terms of differentiating between an authority component
+       and a path component that begins with two slash characters.  The
+       greedy algorithm is used for disambiguation: the left-most matching
+       rule soaks up as much of the URI reference string as it is capable of
+       matching.  In other words, the authority component wins."
+
+    The "four main components" of a hierarchical URI consist of
+    <scheme>://<authority><path>?<query>
+
+    */
+
+    /** Log tag. */
+    private static final String LOG = Uri.class.getSimpleName();
+
+    /**
+     * The empty URI, equivalent to "".
+     */
+    public static final Uri EMPTY = new HierarchicalUri(null, Part.NULL,
+            PathPart.EMPTY, Part.NULL, Part.NULL);
+
+    /**
+     * Prevents external subclassing.
+     */
+    private Uri() {}
+
+    /**
+     * Returns true if this URI is hierarchical like "http://google.com".
+     * Absolute URIs are hierarchical if the scheme-specific part starts with
+     * a '/'. Relative URIs are always hierarchical.
+     */
+    public abstract boolean isHierarchical();
+
+    /**
+     * Returns true if this URI is opaque like "mailto:nobody@google.com". The
+     * scheme-specific part of an opaque URI cannot start with a '/'.
+     */
+    public boolean isOpaque() {
+        return !isHierarchical();
+    }
+
+    /**
+     * Returns true if this URI is relative, i.e. if it doesn't contain an
+     * explicit scheme.
+     *
+     * @return true if this URI is relative, false if it's absolute
+     */
+    public abstract boolean isRelative();
+
+    /**
+     * Returns true if this URI is absolute, i.e. if it contains an
+     * explicit scheme.
+     *
+     * @return true if this URI is absolute, false if it's relative
+     */
+    public boolean isAbsolute() {
+        return !isRelative();
+    }
+
+    /**
+     * Gets the scheme of this URI. Example: "http"
+     *
+     * @return the scheme or null if this is a relative URI
+     */
+    public abstract String getScheme();
+
+    /**
+     * Gets the scheme-specific part of this URI, i.e. everything between the
+     * scheme separator ':' and the fragment separator '#'. If this is a
+     * relative URI, this method returns the entire URI. Decodes escaped octets.
+     *
+     * <p>Example: "//www.google.com/search?q=android"
+     *
+     * @return the decoded scheme-specific-part
+     */
+    public abstract String getSchemeSpecificPart();
+
+    /**
+     * Gets the scheme-specific part of this URI, i.e. everything between the
+     * scheme separator ':' and the fragment separator '#'. If this is a
+     * relative URI, this method returns the entire URI. Leaves escaped octets
+     * intact.
+     *
+     * <p>Example: "//www.google.com/search?q=android"
+     *
+     * @return the decoded scheme-specific-part
+     */
+    public abstract String getEncodedSchemeSpecificPart();
+
+    /**
+     * Gets the decoded authority part of this URI. For
+     * server addresses, the authority is structured as follows:
+     * {@code [ userinfo '@' ] host [ ':' port ]}
+     *
+     * <p>Examples: "google.com", "bob@google.com:80"
+     *
+     * @return the authority for this URI or null if not present
+     */
+    public abstract String getAuthority();
+
+    /**
+     * Gets the encoded authority part of this URI. For
+     * server addresses, the authority is structured as follows:
+     * {@code [ userinfo '@' ] host [ ':' port ]}
+     *
+     * <p>Examples: "google.com", "bob@google.com:80"
+     *
+     * @return the authority for this URI or null if not present
+     */
+    public abstract String getEncodedAuthority();
+
+    /**
+     * Gets the decoded user information from the authority.
+     * For example, if the authority is "nobody@google.com", this method will
+     * return "nobody".
+     *
+     * @return the user info for this URI or null if not present
+     */
+    public abstract String getUserInfo();
+
+    /**
+     * Gets the encoded user information from the authority.
+     * For example, if the authority is "nobody@google.com", this method will
+     * return "nobody".
+     *
+     * @return the user info for this URI or null if not present
+     */
+    public abstract String getEncodedUserInfo();
+
+    /**
+     * Gets the encoded host from the authority for this URI. For example,
+     * if the authority is "bob@google.com", this method will return
+     * "google.com".
+     *
+     * @return the host for this URI or null if not present
+     */
+    public abstract String getHost();
+
+    /**
+     * Gets the port from the authority for this URI. For example,
+     * if the authority is "google.com:80", this method will return 80.
+     *
+     * @return the port for this URI or -1 if invalid or not present
+     */
+    public abstract int getPort();
+
+    /**
+     * Gets the decoded path.
+     *
+     * @return the decoded path, or null if this is not a hierarchical URI
+     * (like "mailto:nobody@google.com") or the URI is invalid
+     */
+    public abstract String getPath();
+
+    /**
+     * Gets the encoded path.
+     *
+     * @return the encoded path, or null if this is not a hierarchical URI
+     * (like "mailto:nobody@google.com") or the URI is invalid
+     */
+    public abstract String getEncodedPath();
+
+    /**
+     * Gets the decoded query component from this URI. The query comes after
+     * the query separator ('?') and before the fragment separator ('#'). This
+     * method would return "q=android" for
+     * "http://www.google.com/search?q=android".
+     *
+     * @return the decoded query or null if there isn't one
+     */
+    public abstract String getQuery();
+
+    /**
+     * Gets the encoded query component from this URI. The query comes after
+     * the query separator ('?') and before the fragment separator ('#'). This
+     * method would return "q=android" for
+     * "http://www.google.com/search?q=android".
+     *
+     * @return the encoded query or null if there isn't one
+     */
+    public abstract String getEncodedQuery();
+
+    /**
+     * Gets the decoded fragment part of this URI, everything after the '#'.
+     *
+     * @return the decoded fragment or null if there isn't one
+     */
+    public abstract String getFragment();
+
+    /**
+     * Gets the encoded fragment part of this URI, everything after the '#'.
+     *
+     * @return the encoded fragment or null if there isn't one
+     */
+    public abstract String getEncodedFragment();
+
+    /**
+     * Gets the decoded path segments.
+     *
+     * @return decoded path segments, each without a leading or trailing '/'
+     */
+    public abstract List<String> getPathSegments();
+
+    /**
+     * Gets the decoded last segment in the path.
+     *
+     * @return the decoded last segment or null if the path is empty
+     */
+    public abstract String getLastPathSegment();
+
+    /**
+     * Compares this Uri to another object for equality. Returns true if the
+     * encoded string representations of this Uri and the given Uri are
+     * equal. Case counts. Paths are not normalized. If one Uri specifies a
+     * default port explicitly and the other leaves it implicit, they will not
+     * be considered equal.
+     */
+    public boolean equals(Object o) {
+        if (!(o instanceof Uri)) {
+            return false;
+        }
+
+        Uri other = (Uri) o;
+
+        return toString().equals(other.toString());
+    }
+
+    /**
+     * Hashes the encoded string represention of this Uri consistently with
+     * {@link #equals(Object)}.
+     */
+    public int hashCode() {
+        return toString().hashCode();
+    }
+
+    /**
+     * Compares the string representation of this Uri with that of
+     * another.
+     */
+    public int compareTo(Uri other) {
+        return toString().compareTo(other.toString());
+    }
+
+    /**
+     * Returns the encoded string representation of this URI.
+     * Example: "http://google.com/"
+     */
+    public abstract String toString();
+
+    /**
+     * Constructs a new builder, copying the attributes from this Uri.
+     */
+    public abstract Builder buildUpon();
+
+    /** Index of a component which was not found. */
+    private final static int NOT_FOUND = -1;
+
+    /** Placeholder value for an index which hasn't been calculated yet. */
+    private final static int NOT_CALCULATED = -2;
+
+    /**
+     * Placeholder for strings which haven't been cached. This enables us
+     * to cache null. We intentionally create a new String instance so we can
+     * compare its identity and there is no chance we will confuse it with
+     * user data.
+     */
+    @SuppressWarnings("RedundantStringConstructorCall")
+    private static final String NOT_CACHED = new String("NOT CACHED");
+
+    /**
+     * Error message presented when a user tries to treat an opaque URI as
+     * hierarchical.
+     */
+    private static final String NOT_HIERARCHICAL
+            = "This isn't a hierarchical URI.";
+
+    /** Default encoding. */
+    private static final String DEFAULT_ENCODING = "UTF-8";
+
+    /**
+     * Creates a Uri which parses the given encoded URI string.
+     *
+     * @param uriString an RFC 3296-compliant, encoded URI
+     * @throws NullPointerException if uriString is null
+     * @return Uri for this given uri string
+     */
+    public static Uri parse(String uriString) {
+        return new StringUri(uriString);
+    }
+
+    /**
+     * Creates a Uri from a file. The URI has the form
+     * "file://<absolute path>". Encodes path characters with the exception of
+     * '/'.
+     *
+     * <p>Example: "file:///tmp/android.txt"
+     *
+     * @throws NullPointerException if file is null
+     * @return a Uri for the given file
+     */
+    public static Uri fromFile(File file) {
+        if (file == null) {
+            throw new NullPointerException("file");
+        }
+
+        PathPart path = PathPart.fromDecoded(file.getAbsolutePath());
+        return new HierarchicalUri(
+                "file", Part.EMPTY, path, Part.NULL, Part.NULL);
+    }
+
+    /**
+     * An implementation which wraps a String URI. This URI can be opaque or
+     * hierarchical, but we extend AbstractHierarchicalUri in case we need
+     * the hierarchical functionality.
+     */
+    private static class StringUri extends AbstractHierarchicalUri {
+
+        /** Used in parcelling. */
+        static final int TYPE_ID = 1;
+
+        /** URI string representation. */
+        private final String uriString;
+
+        private StringUri(String uriString) {
+            if (uriString == null) {
+                throw new NullPointerException("uriString");
+            }
+
+            this.uriString = uriString;
+        }
+
+        static Uri readFrom(Parcel parcel) {
+            return new StringUri(parcel.readString());
+        }
+
+        public int describeContents() {
+            return 0;
+        }
+
+        public void writeToParcel(Parcel parcel, int flags) {
+            parcel.writeInt(TYPE_ID);
+            parcel.writeString(uriString);
+        }
+
+        /** Cached scheme separator index. */
+        private volatile int cachedSsi = NOT_CALCULATED;
+
+        /** Finds the first ':'. Returns -1 if none found. */
+        private int findSchemeSeparator() {
+            return cachedSsi == NOT_CALCULATED
+                    ? cachedSsi = uriString.indexOf(':')
+                    : cachedSsi;
+        }
+
+        /** Cached fragment separator index. */
+        private volatile int cachedFsi = NOT_CALCULATED;
+
+        /** Finds the first '#'. Returns -1 if none found. */
+        private int findFragmentSeparator() {
+            return cachedFsi == NOT_CALCULATED
+                    ? cachedFsi = uriString.indexOf('#', findSchemeSeparator())
+                    : cachedFsi;
+        }
+
+        public boolean isHierarchical() {
+            int ssi = findSchemeSeparator();
+
+            if (ssi == NOT_FOUND) {
+                // All relative URIs are hierarchical.
+                return true;
+            }
+
+            if (uriString.length() == ssi + 1) {
+                // No ssp.
+                return false;
+            }
+
+            // If the ssp starts with a '/', this is hierarchical.
+            return uriString.charAt(ssi + 1) == '/';
+        }
+
+        public boolean isRelative() {
+            // Note: We return true if the index is 0
+            return findSchemeSeparator() == NOT_FOUND;
+        }
+
+        private volatile String scheme = NOT_CACHED;
+
+        public String getScheme() {
+            @SuppressWarnings("StringEquality")
+            boolean cached = (scheme != NOT_CACHED);
+            return cached ? scheme : (scheme = parseScheme());
+        }
+
+        private String parseScheme() {
+            int ssi = findSchemeSeparator();
+            return ssi == NOT_FOUND ? null : uriString.substring(0, ssi);
+        }
+
+        private Part ssp;
+
+        private Part getSsp() {
+            return ssp == null ? ssp = Part.fromEncoded(parseSsp()) : ssp;
+        }
+
+        public String getEncodedSchemeSpecificPart() {
+            return getSsp().getEncoded();
+        }
+
+        public String getSchemeSpecificPart() {
+            return getSsp().getDecoded();
+        }
+
+        private String parseSsp() {
+            int ssi = findSchemeSeparator();
+            int fsi = findFragmentSeparator();
+
+            // Return everything between ssi and fsi.
+            return fsi == NOT_FOUND
+                    ? uriString.substring(ssi + 1)
+                    : uriString.substring(ssi + 1, fsi);
+        }
+
+        private Part authority;
+
+        private Part getAuthorityPart() {
+            if (authority == null) {
+                String encodedAuthority
+                        = parseAuthority(this.uriString, findSchemeSeparator());
+                return authority = Part.fromEncoded(encodedAuthority);
+            }
+
+            return authority;
+        }
+
+        public String getEncodedAuthority() {
+            return getAuthorityPart().getEncoded();
+        }
+
+        public String getAuthority() {
+            return getAuthorityPart().getDecoded();
+        }
+
+        private PathPart path;
+
+        private PathPart getPathPart() {
+            return path == null
+                    ? path = PathPart.fromEncoded(parsePath())
+                    : path;
+        }
+
+        public String getPath() {
+            return getPathPart().getDecoded();
+        }
+
+        public String getEncodedPath() {
+            return getPathPart().getEncoded();
+        }
+
+        public List<String> getPathSegments() {
+            return getPathPart().getPathSegments();
+        }
+
+        private String parsePath() {
+            String uriString = this.uriString;
+            int ssi = findSchemeSeparator();
+
+            // If the URI is absolute.
+            if (ssi > -1) {
+                // Is there anything after the ':'?
+                boolean schemeOnly = ssi + 1 == uriString.length();
+                if (schemeOnly) {
+                    // Opaque URI.
+                    return null;
+                }
+
+                // A '/' after the ':' means this is hierarchical.
+                if (uriString.charAt(ssi + 1) != '/') {
+                    // Opaque URI.
+                    return null;
+                }
+            } else {
+                // All relative URIs are hierarchical.
+            }
+
+            return parsePath(uriString, ssi);
+        }
+
+        private Part query;
+
+        private Part getQueryPart() {
+            return query == null
+                    ? query = Part.fromEncoded(parseQuery()) : query;
+        }
+
+        public String getEncodedQuery() {
+            return getQueryPart().getEncoded();
+        }
+
+        private String parseQuery() {
+            // It doesn't make sense to cache this index. We only ever
+            // calculate it once.
+            int qsi = uriString.indexOf('?', findSchemeSeparator());
+            if (qsi == NOT_FOUND) {
+                return null;
+            }
+
+            int fsi = findFragmentSeparator();
+
+            if (fsi == NOT_FOUND) {
+                return uriString.substring(qsi + 1);
+            }
+
+            if (fsi < qsi) {
+                // Invalid.
+                return null;
+            }
+
+            return uriString.substring(qsi + 1, fsi);
+        }
+
+        public String getQuery() {
+            return getQueryPart().getDecoded();
+        }
+
+        private Part fragment;
+
+        private Part getFragmentPart() {
+            return fragment == null
+                    ? fragment = Part.fromEncoded(parseFragment()) : fragment;
+        }
+
+        public String getEncodedFragment() {
+            return getFragmentPart().getEncoded();
+        }
+
+        private String parseFragment() {
+            int fsi = findFragmentSeparator();
+            return fsi == NOT_FOUND ? null : uriString.substring(fsi + 1);
+        }
+
+        public String getFragment() {
+            return getFragmentPart().getDecoded();
+        }
+
+        public String toString() {
+            return uriString;
+        }
+
+        /**
+         * Parses an authority out of the given URI string.
+         *
+         * @param uriString URI string
+         * @param ssi scheme separator index, -1 for a relative URI
+         *
+         * @return the authority or null if none is found
+         */
+        static String parseAuthority(String uriString, int ssi) {
+            int length = uriString.length();
+
+            // If "//" follows the scheme separator, we have an authority.
+            if (length > ssi + 2
+                    && uriString.charAt(ssi + 1) == '/'
+                    && uriString.charAt(ssi + 2) == '/') {
+                // We have an authority.
+
+                // Look for the start of the path, query, or fragment, or the
+                // end of the string.
+                int end = ssi + 3;
+                LOOP: while (end < length) {
+                    switch (uriString.charAt(end)) {
+                        case '/': // Start of path
+                        case '?': // Start of query
+                        case '#': // Start of fragment
+                            break LOOP;
+                    }
+                    end++;
+                }
+
+                return uriString.substring(ssi + 3, end);
+            } else {
+                return null;
+            }
+
+        }
+
+        /**
+         * Parses a path out of this given URI string.
+         *
+         * @param uriString URI string
+         * @param ssi scheme separator index, -1 for a relative URI
+         *
+         * @return the path
+         */
+        static String parsePath(String uriString, int ssi) {
+            int length = uriString.length();
+
+            // Find start of path.
+            int pathStart;
+            if (length > ssi + 2
+                    && uriString.charAt(ssi + 1) == '/'
+                    && uriString.charAt(ssi + 2) == '/') {
+                // Skip over authority to path.
+                pathStart = ssi + 3;
+                LOOP: while (pathStart < length) {
+                    switch (uriString.charAt(pathStart)) {
+                        case '?': // Start of query
+                        case '#': // Start of fragment
+                            return ""; // Empty path.
+                        case '/': // Start of path!
+                            break LOOP;
+                    }
+                    pathStart++;
+                }
+            } else {
+                // Path starts immediately after scheme separator.
+                pathStart = ssi + 1;
+            }
+
+            // Find end of path.
+            int pathEnd = pathStart;
+            LOOP: while (pathEnd < length) {
+                switch (uriString.charAt(pathEnd)) {
+                    case '?': // Start of query
+                    case '#': // Start of fragment
+                        break LOOP;
+                }
+                pathEnd++;
+            }
+
+            return uriString.substring(pathStart, pathEnd);
+        }
+
+        public Builder buildUpon() {
+            if (isHierarchical()) {
+                return new Builder()
+                        .scheme(getScheme())
+                        .authority(getAuthorityPart())
+                        .path(getPathPart())
+                        .query(getQueryPart())
+                        .fragment(getFragmentPart());
+            } else {
+                return new Builder()
+                        .scheme(getScheme())
+                        .opaquePart(getSsp())
+                        .fragment(getFragmentPart());
+            }
+        }
+    }
+
+    /**
+     * Creates an opaque Uri from the given components. Encodes the ssp
+     * which means this method cannot be used to create hierarchical URIs.
+     *
+     * @param scheme of the URI
+     * @param ssp scheme-specific-part, everything between the
+     *  scheme separator (':') and the fragment separator ('#'), which will
+     *  get encoded
+     * @param fragment fragment, everything after the '#', null if undefined,
+     *  will get encoded
+     *
+     * @throws NullPointerException if scheme or ssp is null
+     * @return Uri composed of the given scheme, ssp, and fragment
+     *
+     * @see Builder if you don't want the ssp and fragment to be encoded
+     */
+    public static Uri fromParts(String scheme, String ssp,
+            String fragment) {
+        if (scheme == null) {
+            throw new NullPointerException("scheme");
+        }
+        if (ssp == null) {
+            throw new NullPointerException("ssp");
+        }
+
+        return new OpaqueUri(scheme, Part.fromDecoded(ssp),
+                Part.fromDecoded(fragment));
+    }
+
+    /**
+     * Opaque URI.
+     */
+    private static class OpaqueUri extends Uri {
+
+        /** Used in parcelling. */
+        static final int TYPE_ID = 2;
+
+        private final String scheme;
+        private final Part ssp;
+        private final Part fragment;
+
+        private OpaqueUri(String scheme, Part ssp, Part fragment) {
+            this.scheme = scheme;
+            this.ssp = ssp;
+            this.fragment = fragment == null ? Part.NULL : fragment;
+        }
+
+        static Uri readFrom(Parcel parcel) {
+            return new OpaqueUri(
+                parcel.readString(),
+                Part.readFrom(parcel),
+                Part.readFrom(parcel)
+            );
+        }
+
+        public int describeContents() {
+            return 0;
+        }
+
+        public void writeToParcel(Parcel parcel, int flags) {
+            parcel.writeInt(TYPE_ID);
+            parcel.writeString(scheme);
+            ssp.writeTo(parcel);
+            fragment.writeTo(parcel);
+        }
+
+        public boolean isHierarchical() {
+            return false;
+        }
+
+        public boolean isRelative() {
+            return scheme == null;
+        }
+
+        public String getScheme() {
+            return this.scheme;
+        }
+
+        public String getEncodedSchemeSpecificPart() {
+            return ssp.getEncoded();
+        }
+
+        public String getSchemeSpecificPart() {
+            return ssp.getDecoded();
+        }
+
+        public String getAuthority() {
+            return null;
+        }
+
+        public String getEncodedAuthority() {
+            return null;
+        }
+
+        public String getPath() {
+            return null;
+        }
+
+        public String getEncodedPath() {
+            return null;
+        }
+
+        public String getQuery() {
+            return null;
+        }
+
+        public String getEncodedQuery() {
+            return null;
+        }
+
+        public String getFragment() {
+            return fragment.getDecoded();
+        }
+
+        public String getEncodedFragment() {
+            return fragment.getEncoded();
+        }
+
+        public List<String> getPathSegments() {
+            return Collections.emptyList();
+        }
+
+        public String getLastPathSegment() {
+            return null;
+        }
+
+        public String getUserInfo() {
+            return null;
+        }
+
+        public String getEncodedUserInfo() {
+            return null;
+        }
+
+        public String getHost() {
+            return null;
+        }
+
+        public int getPort() {
+            return -1;
+        }
+
+        private volatile String cachedString = NOT_CACHED;
+
+        public String toString() {
+            @SuppressWarnings("StringEquality")
+            boolean cached = cachedString != NOT_CACHED;
+            if (cached) {
+                return cachedString;
+            }
+
+            StringBuilder sb = new StringBuilder();
+
+            sb.append(scheme).append(':');
+            sb.append(getEncodedSchemeSpecificPart());
+
+            if (!fragment.isEmpty()) {
+                sb.append('#').append(fragment.getEncoded());
+            }
+
+            return cachedString = sb.toString();
+        }
+
+        public Builder buildUpon() {
+            return new Builder()
+                    .scheme(this.scheme)
+                    .opaquePart(this.ssp)
+                    .fragment(this.fragment);
+        }
+    }
+
+    /**
+     * Wrapper for path segment array.
+     */
+    static class PathSegments extends AbstractList<String>
+            implements RandomAccess {
+
+        static final PathSegments EMPTY = new PathSegments(null, 0);
+
+        final String[] segments;
+        final int size;
+
+        PathSegments(String[] segments, int size) {
+            this.segments = segments;
+            this.size = size;
+        }
+
+        public String get(int index) {
+            if (index >= size) {
+                throw new IndexOutOfBoundsException();
+            }
+
+            return segments[index];
+        }
+
+        public int size() {
+            return this.size;
+        }
+    }
+
+    /**
+     * Builds PathSegments.
+     */
+    static class PathSegmentsBuilder {
+
+        String[] segments;
+        int size = 0;
+
+        void add(String segment) {
+            if (segments == null) {
+                segments = new String[4];
+            } else if (size + 1 == segments.length) {
+                String[] expanded = new String[segments.length * 2];
+                System.arraycopy(segments, 0, expanded, 0, segments.length);
+                segments = expanded;
+            }
+
+            segments[size++] = segment;
+        }
+
+        PathSegments build() {
+            if (segments == null) {
+                return PathSegments.EMPTY;
+            }
+
+            try {
+                return new PathSegments(segments, size);
+            } finally {
+                // Makes sure this doesn't get reused.
+                segments = null;
+            }
+        }
+    }
+
+    /**
+     * Support for hierarchical URIs.
+     */
+    private abstract static class AbstractHierarchicalUri extends Uri {
+
+        public String getLastPathSegment() {
+            // TODO: If we haven't parsed all of the segments already, just
+            // grab the last one directly so we only allocate one string.
+
+            List<String> segments = getPathSegments();
+            int size = segments.size();
+            if (size == 0) {
+                return null;
+            }
+            return segments.get(size - 1);
+        }
+
+        private Part userInfo;
+
+        private Part getUserInfoPart() {
+            return userInfo == null
+                    ? userInfo = Part.fromEncoded(parseUserInfo()) : userInfo;
+        }
+
+        public final String getEncodedUserInfo() {
+            return getUserInfoPart().getEncoded();
+        }
+
+        private String parseUserInfo() {
+            String authority = getEncodedAuthority();
+            if (authority == null) {
+                return null;
+            }
+
+            int end = authority.indexOf('@');
+            return end == NOT_FOUND ? null : authority.substring(0, end);
+        }
+
+        public String getUserInfo() {
+            return getUserInfoPart().getDecoded();
+        }
+
+        private volatile String host = NOT_CACHED;
+
+        public String getHost() {
+            @SuppressWarnings("StringEquality")
+            boolean cached = (host != NOT_CACHED);
+            return cached ? host
+                    : (host = parseHost());
+        }
+
+        private String parseHost() {
+            String authority = getAuthority();
+            if (authority == null) {
+                return null;
+            }
+
+            // Parse out user info and then port.
+            int userInfoSeparator = authority.indexOf('@');
+            int portSeparator = authority.indexOf(':', userInfoSeparator);
+
+            return portSeparator == NOT_FOUND
+                    ? authority.substring(userInfoSeparator + 1)
+                    : authority.substring(userInfoSeparator + 1, portSeparator);
+        }
+
+        private volatile int port = NOT_CALCULATED;
+
+        public int getPort() {
+            return port == NOT_CALCULATED
+                    ? port = parsePort()
+                    : port;
+        }
+
+        private int parsePort() {
+            String authority = getAuthority();
+            if (authority == null) {
+                return -1;
+            }
+
+            // Make sure we look for the port separtor *after* the user info
+            // separator. We have URLs with a ':' in the user info.
+            int userInfoSeparator = authority.indexOf('@');
+            int portSeparator = authority.indexOf(':', userInfoSeparator);
+
+            if (portSeparator == NOT_FOUND) {
+                return -1;
+            }
+
+            String portString = authority.substring(portSeparator + 1);
+            try {
+                return Integer.parseInt(portString);
+            } catch (NumberFormatException e) {
+                Log.w(LOG, "Error parsing port string.", e);
+                return -1;
+            }
+        }
+    }
+
+    /**
+     * Hierarchical Uri.
+     */
+    private static class HierarchicalUri extends AbstractHierarchicalUri {
+
+        /** Used in parcelling. */
+        static final int TYPE_ID = 3;
+
+        private final String scheme;
+        private final Part authority;
+        private final PathPart path;
+        private final Part query;
+        private final Part fragment;
+
+        private HierarchicalUri(String scheme, Part authority, PathPart path,
+                Part query, Part fragment) {
+            this.scheme = scheme;
+            this.authority = authority;
+            this.path = path;
+            this.query = query;
+            this.fragment = fragment;
+        }
+
+        static Uri readFrom(Parcel parcel) {
+            return new HierarchicalUri(
+                parcel.readString(),
+                Part.readFrom(parcel),
+                PathPart.readFrom(parcel),
+                Part.readFrom(parcel),
+                Part.readFrom(parcel)
+            );
+        }
+
+        public int describeContents() {
+            return 0;
+        }
+
+        public void writeToParcel(Parcel parcel, int flags) {
+            parcel.writeInt(TYPE_ID);
+            parcel.writeString(scheme);
+            authority.writeTo(parcel);
+            path.writeTo(parcel);
+            query.writeTo(parcel);
+            fragment.writeTo(parcel);
+        }
+
+        public boolean isHierarchical() {
+            return true;
+        }
+
+        public boolean isRelative() {
+            return scheme == null;
+        }
+
+        public String getScheme() {
+            return scheme;
+        }
+
+        private Part ssp;
+
+        private Part getSsp() {
+            return ssp == null
+                    ? ssp = Part.fromEncoded(makeSchemeSpecificPart()) : ssp;
+        }
+
+        public String getEncodedSchemeSpecificPart() {
+            return getSsp().getEncoded();
+        }
+
+        public String getSchemeSpecificPart() {
+            return getSsp().getDecoded();
+        }
+
+        /**
+         * Creates the encoded scheme-specific part from its sub parts.
+         */
+        private String makeSchemeSpecificPart() {
+            StringBuilder builder = new StringBuilder();
+            appendSspTo(builder);
+            return builder.toString();
+        }
+
+        private void appendSspTo(StringBuilder builder) {
+            if (authority != null) {
+                String encodedAuthority = authority.getEncoded();
+                if (encodedAuthority != null) {
+                    // Even if the authority is "", we still want to append "//".
+                    builder.append("//").append(encodedAuthority);
+                }
+            }
+
+            // path is never null.
+            String encodedPath = path.getEncoded();
+            if (encodedPath != null) {
+                builder.append(encodedPath);
+            }
+
+            if (query != null && !query.isEmpty()) {
+                builder.append('?').append(query.getEncoded());
+            }
+        }
+
+        public String getAuthority() {
+            return this.authority.getDecoded();
+        }
+
+        public String getEncodedAuthority() {
+            return this.authority.getEncoded();
+        }
+
+        public String getEncodedPath() {
+            return this.path.getEncoded();
+        }
+
+        public String getPath() {
+            return this.path.getDecoded();
+        }
+
+        public String getQuery() {
+            return this.query.getDecoded();
+        }
+
+        public String getEncodedQuery() {
+            return this.query.getEncoded();
+        }
+
+        public String getFragment() {
+            return this.fragment.getDecoded();
+        }
+
+        public String getEncodedFragment() {
+            return this.fragment.getEncoded();
+        }
+
+        public List<String> getPathSegments() {
+            return this.path.getPathSegments();
+        }
+
+        private volatile String uriString = NOT_CACHED;
+
+        @Override
+        public String toString() {
+            @SuppressWarnings("StringEquality")
+            boolean cached = (uriString != NOT_CACHED);
+            return cached ? uriString
+                    : (uriString = makeUriString());
+        }
+
+        private String makeUriString() {
+            StringBuilder builder = new StringBuilder();
+
+            if (scheme != null) {
+                builder.append(scheme).append(':');
+            }
+
+            appendSspTo(builder);
+
+            if (fragment != null && !fragment.isEmpty()) {
+                builder.append('#').append(fragment.getEncoded());
+            }
+
+            return builder.toString();
+        }
+
+        public Builder buildUpon() {
+            return new Builder()
+                    .scheme(scheme)
+                    .authority(authority)
+                    .path(path)
+                    .query(query)
+                    .fragment(fragment);
+        }
+    }
+
+    /**
+     * Helper class for building or manipulating URI references. Not safe for
+     * concurrent use.
+     *
+     * <p>An absolute hierarchical URI reference follows the pattern:
+     * {@code &lt;scheme&gt;://&lt;authority&gt;&lt;absolute path&gt;?&lt;query&gt;#&lt;fragment&gt;}
+     *
+     * <p>Relative URI references (which are always hierarchical) follow one
+     * of two patterns: {@code &lt;relative or absolute path&gt;?&lt;query&gt;#&lt;fragment&gt;}
+     * or {@code //&lt;authority&gt;&lt;absolute path&gt;?&lt;query&gt;#&lt;fragment&gt;}
+     *
+     * <p>An opaque URI follows this pattern:
+     * {@code &lt;scheme&gt;:&lt;opaque part&gt;#&lt;fragment&gt;}
+     */
+    public static final class Builder {
+
+        private String scheme;
+        private Part opaquePart;
+        private Part authority;
+        private PathPart path;
+        private Part query;
+        private Part fragment;
+
+        /**
+         * Constructs a new Builder.
+         */
+        public Builder() {}
+
+        /**
+         * Sets the scheme.
+         *
+         * @param scheme name or {@code null} if this is a relative Uri
+         */
+        public Builder scheme(String scheme) {
+            this.scheme = scheme;
+            return this;
+        }
+
+        Builder opaquePart(Part opaquePart) {
+            this.opaquePart = opaquePart;
+            return this;
+        }
+
+        /**
+         * Encodes and sets the given opaque scheme-specific-part.
+         *
+         * @param opaquePart decoded opaque part
+         */
+        public Builder opaquePart(String opaquePart) {
+            return opaquePart(Part.fromDecoded(opaquePart));
+        }
+
+        /**
+         * Sets the previously encoded opaque scheme-specific-part.
+         *
+         * @param opaquePart encoded opaque part
+         */
+        public Builder encodedOpaquePart(String opaquePart) {
+            return opaquePart(Part.fromEncoded(opaquePart));
+        }
+
+        Builder authority(Part authority) {
+            // This URI will be hierarchical.
+            this.opaquePart = null;
+
+            this.authority = authority;
+            return this;
+        }
+
+        /**
+         * Encodes and sets the authority.
+         */
+        public Builder authority(String authority) {
+            return authority(Part.fromDecoded(authority));
+        }
+
+        /**
+         * Sets the previously encoded authority.
+         */
+        public Builder encodedAuthority(String authority) {
+            return authority(Part.fromEncoded(authority));
+        }
+
+        Builder path(PathPart path) {
+            // This URI will be hierarchical.
+            this.opaquePart = null;
+
+            this.path = path;
+            return this;
+        }
+
+        /**
+         * Sets the path. Leaves '/' characters intact but encodes others as
+         * necessary.
+         *
+         * <p>If the path is not null and doesn't start with a '/', and if
+         * you specify a scheme and/or authority, the builder will prepend the
+         * given path with a '/'.
+         */
+        public Builder path(String path) {
+            return path(PathPart.fromDecoded(path));
+        }
+
+        /**
+         * Sets the previously encoded path.
+         *
+         * <p>If the path is not null and doesn't start with a '/', and if
+         * you specify a scheme and/or authority, the builder will prepend the
+         * given path with a '/'.
+         */
+        public Builder encodedPath(String path) {
+            return path(PathPart.fromEncoded(path));
+        }
+
+        /**
+         * Encodes the given segment and appends it to the path.
+         */
+        public Builder appendPath(String newSegment) {
+            return path(PathPart.appendDecodedSegment(path, newSegment));
+        }
+
+        /**
+         * Appends the given segment to the path.
+         */
+        public Builder appendEncodedPath(String newSegment) {
+            return path(PathPart.appendEncodedSegment(path, newSegment));
+        }
+
+        Builder query(Part query) {
+            // This URI will be hierarchical.
+            this.opaquePart = null;
+
+            this.query = query;
+            return this;
+        }
+
+        /**
+         * Encodes and sets the query.
+         */
+        public Builder query(String query) {
+            return query(Part.fromDecoded(query));
+        }
+
+        /**
+         * Sets the previously encoded query.
+         */
+        public Builder encodedQuery(String query) {
+            return query(Part.fromEncoded(query));
+        }
+
+        Builder fragment(Part fragment) {
+            this.fragment = fragment;
+            return this;
+        }
+
+        /**
+         * Encodes and sets the fragment.
+         */
+        public Builder fragment(String fragment) {
+            return fragment(Part.fromDecoded(fragment));
+        }
+
+        /**
+         * Sets the previously encoded fragment.
+         */
+        public Builder encodedFragment(String fragment) {
+            return fragment(Part.fromEncoded(fragment));
+        }
+
+        /**
+         * Encodes the key and value and then appends the parameter to the
+         * query string.
+         *
+         * @param key which will be encoded
+         * @param value which will be encoded
+         */
+        public Builder appendQueryParameter(String key, String value) {
+            // This URI will be hierarchical.
+            this.opaquePart = null;
+
+            String encodedParameter = encode(key, null) + "="
+                    + encode(value, null);
+
+            if (query == null) {
+                query = Part.fromEncoded(encodedParameter);
+                return this;
+            }
+
+            String oldQuery = query.getEncoded();
+            if (oldQuery == null || oldQuery.length() == 0) {
+                query = Part.fromEncoded(encodedParameter);
+            } else {
+                query = Part.fromEncoded(oldQuery + "&" + encodedParameter);
+            }
+
+            return this;
+        }
+
+        /**
+         * Constructs a Uri with the current attributes.
+         *
+         * @throws UnsupportedOperationException if the URI is opaque and the
+         *  scheme is null
+         */
+        public Uri build() {
+            if (opaquePart != null) {
+                if (this.scheme == null) {
+                    throw new UnsupportedOperationException(
+                            "An opaque URI must have a scheme.");
+                }
+
+                return new OpaqueUri(scheme, opaquePart, fragment);
+            } else {
+                // Hierarchical URIs should not return null for getPath().
+                PathPart path = this.path;
+                if (path == null || path == PathPart.NULL) {
+                    path = PathPart.EMPTY;
+                } else {
+                    // If we have a scheme and/or authority, the path must
+                    // be absolute. Prepend it with a '/' if necessary.
+                    if (hasSchemeOrAuthority()) {
+                        path = PathPart.makeAbsolute(path);
+                    }
+                }
+
+                return new HierarchicalUri(
+                        scheme, authority, path, query, fragment);
+            }
+        }
+
+        private boolean hasSchemeOrAuthority() {
+            return scheme != null
+                    || (authority != null && authority != Part.NULL);
+
+        }
+
+        @Override
+        public String toString() {
+            return build().toString();
+        }
+    }
+
+    /**
+     * Searches the query string for parameter values with the given key.
+     *
+     * @param key which will be encoded
+     *
+     * @throws UnsupportedOperationException if this isn't a hierarchical URI
+     * @throws NullPointerException if key is null
+     *
+     * @return a list of decoded values
+     */
+    public List<String> getQueryParameters(String key) {
+        if (isOpaque()) {
+            throw new UnsupportedOperationException(NOT_HIERARCHICAL);
+        }
+
+        String query = getQuery();
+        if (query == null) {
+            return Collections.emptyList();
+        }
+
+        String encodedKey;
+        try {
+            encodedKey = URLEncoder.encode(key, DEFAULT_ENCODING);
+        } catch (UnsupportedEncodingException e) {
+            throw new AssertionError(e);
+        }
+
+        // Prepend query with "&" making the first parameter the same as the
+        // rest.
+        query = "&" + query;
+
+        // Parameter prefix.
+        String prefix = "&" + encodedKey + "=";
+
+        ArrayList<String> values = new ArrayList<String>();
+
+        int start = 0;
+        int length = query.length();
+        while (start < length) {
+            start = query.indexOf(prefix, start);
+
+            if (start == -1) {
+                // No more values.
+                break;
+            }
+
+            // Move start to start of value.
+            start += prefix.length();
+
+            // Find end of value.
+            int end = query.indexOf('&', start);
+            if (end == -1) {
+                end = query.length();
+            }
+
+            String value = query.substring(start, end);
+            values.add(decode(value));
+
+            start = end;
+        }
+
+        return Collections.unmodifiableList(values);
+    }
+
+    /**
+     * Searches the query string for the first value with the given key.
+     *
+     * @param key which will be encoded
+     * @throws UnsupportedOperationException if this isn't a hierarchical URI
+     * @throws NullPointerException if key is null
+     *
+     * @return the decoded value or null if no parameter is found
+     */
+    public String getQueryParameter(String key) {
+        if (isOpaque()) {
+            throw new UnsupportedOperationException(NOT_HIERARCHICAL);
+        }
+
+        String query = getQuery();
+
+        if (query == null) {
+            return null;
+        }
+
+        String encodedKey;
+        try {
+            encodedKey = URLEncoder.encode(key, DEFAULT_ENCODING);
+        } catch (UnsupportedEncodingException e) {
+            throw new AssertionError(e);
+        }
+
+        String prefix = encodedKey + "=";
+
+        if (query.length() < prefix.length()) {
+            return null;
+        }
+
+        int start;
+        if (query.startsWith(prefix)) {
+            // It's the first parameter.
+            start = prefix.length();
+        } else {
+            // It must be later in the query string.
+            prefix = "&" + prefix;
+            start = query.indexOf(prefix);
+
+            if (start == -1) {
+                // Not found.
+                return null;
+            }
+
+            start += prefix.length();
+        }
+
+        // Find end of value.
+        int end = query.indexOf('&', start);
+        if (end == -1) {
+            end = query.length();
+        }
+
+        String value = query.substring(start, end);
+        return decode(value);
+    }
+
+    /** Identifies a null parcelled Uri. */
+    private static final int NULL_TYPE_ID = 0;
+
+    /**
+     * Reads Uris from Parcels.
+     */
+    public static final Parcelable.Creator<Uri> CREATOR
+            = new Parcelable.Creator<Uri>() {
+        public Uri createFromParcel(Parcel in) {
+            int type = in.readInt();
+            switch (type) {
+                case NULL_TYPE_ID: return null;
+                case StringUri.TYPE_ID: return StringUri.readFrom(in);
+                case OpaqueUri.TYPE_ID: return OpaqueUri.readFrom(in);
+                case HierarchicalUri.TYPE_ID:
+                    return HierarchicalUri.readFrom(in);
+            }
+
+            throw new AssertionError("Unknown URI type: " + type);
+        }
+
+        public Uri[] newArray(int size) {
+            return new Uri[size];
+        }
+    };
+
+    /**
+     * Writes a Uri to a Parcel.
+     *
+     * @param out parcel to write to
+     * @param uri to write, can be null
+     */
+    public static void writeToParcel(Parcel out, Uri uri) {
+        if (uri == null) {
+            out.writeInt(NULL_TYPE_ID);
+        } else {
+            uri.writeToParcel(out, 0);
+        }
+    }
+
+    private static final char[] HEX_DIGITS = "0123456789ABCDEF".toCharArray();
+
+    /**
+     * Encodes characters in the given string as '%'-escaped octets
+     * using the UTF-8 scheme. Leaves letters ("A-Z", "a-z"), numbers
+     * ("0-9"), and unreserved characters ("_-!.~'()*") intact. Encodes
+     * all other characters.
+     *
+     * @param s string to encode
+     * @return an encoded version of s suitable for use as a URI component,
+     *  or null if s is null
+     */
+    public static String encode(String s) {
+        return encode(s, null);
+    }
+
+    /**
+     * Encodes characters in the given string as '%'-escaped octets
+     * using the UTF-8 scheme. Leaves letters ("A-Z", "a-z"), numbers
+     * ("0-9"), and unreserved characters ("_-!.~'()*") intact. Encodes
+     * all other characters with the exception of those specified in the
+     * allow argument.
+     *
+     * @param s string to encode
+     * @param allow set of additional characters to allow in the encoded form,
+     *  null if no characters should be skipped
+     * @return an encoded version of s suitable for use as a URI component,
+     *  or null if s is null
+     */
+    public static String encode(String s, String allow) {
+        if (s == null) {
+            return null;
+        }
+
+        // Lazily-initialized buffers.
+        StringBuilder encoded = null;
+
+        int oldLength = s.length();
+
+        // This loop alternates between copying over allowed characters and
+        // encoding in chunks. This results in fewer method calls and
+        // allocations than encoding one character at a time.
+        int current = 0;
+        while (current < oldLength) {
+            // Start in "copying" mode where we copy over allowed chars.
+
+            // Find the next character which needs to be encoded.
+            int nextToEncode = current;
+            while (nextToEncode < oldLength
+                    && isAllowed(s.charAt(nextToEncode), allow)) {
+                nextToEncode++;
+            }
+
+            // If there's nothing more to encode...
+            if (nextToEncode == oldLength) {
+                if (current == 0) {
+                    // We didn't need to encode anything!
+                    return s;
+                } else {
+                    // Presumably, we've already done some encoding.
+                    encoded.append(s, current, oldLength);
+                    return encoded.toString();
+                }
+            }
+
+            if (encoded == null) {
+                encoded = new StringBuilder();
+            }
+
+            if (nextToEncode > current) {
+                // Append allowed characters leading up to this point.
+                encoded.append(s, current, nextToEncode);
+            } else {
+                // assert nextToEncode == current
+            }
+
+            // Switch to "encoding" mode.
+
+            // Find the next allowed character.
+            current = nextToEncode;
+            int nextAllowed = current + 1;
+            while (nextAllowed < oldLength
+                    && !isAllowed(s.charAt(nextAllowed), allow)) {
+                nextAllowed++;
+            }
+
+            // Convert the substring to bytes and encode the bytes as
+            // '%'-escaped octets.
+            String toEncode = s.substring(current, nextAllowed);
+            try {
+                byte[] bytes = toEncode.getBytes(DEFAULT_ENCODING);
+                int bytesLength = bytes.length;
+                for (int i = 0; i < bytesLength; i++) {
+                    encoded.append('%');
+                    encoded.append(HEX_DIGITS[(bytes[i] & 0xf0) >> 4]);
+                    encoded.append(HEX_DIGITS[bytes[i] & 0xf]);
+                }
+            } catch (UnsupportedEncodingException e) {
+                throw new AssertionError(e);
+            }
+
+            current = nextAllowed;
+        }
+
+        // Encoded could still be null at this point if s is empty.
+        return encoded == null ? s : encoded.toString();
+    }
+
+    /**
+     * Returns true if the given character is allowed.
+     *
+     * @param c character to check
+     * @param allow characters to allow
+     * @return true if the character is allowed or false if it should be
+     *  encoded
+     */
+    private static boolean isAllowed(char c, String allow) {
+        return (c >= 'A' && c <= 'Z')
+                || (c >= 'a' && c <= 'z')
+                || (c >= '0' && c <= '9')
+                || "_-!.~'()*".indexOf(c) != NOT_FOUND
+                || (allow != null && allow.indexOf(c) != NOT_FOUND);
+    }
+
+    /** Unicode replacement character: \\uFFFD. */
+    private static final byte[] REPLACEMENT = { (byte) 0xFF, (byte) 0xFD };
+
+    /**
+     * Decodes '%'-escaped octets in the given string using the UTF-8 scheme.
+     * Replaces invalid octets with the unicode replacement character
+     * ("\\uFFFD").
+     *
+     * @param s encoded string to decode
+     * @return the given string with escaped octets decoded, or null if
+     *  s is null
+     */
+    public static String decode(String s) {
+        /*
+        Compared to java.net.URLEncoderDecoder.decode(), this method decodes a
+        chunk at a time instead of one character at a time, and it doesn't
+        throw exceptions. It also only allocates memory when necessary--if
+        there's nothing to decode, this method won't do much.
+        */
+
+        if (s == null) {
+            return null;
+        }
+
+        // Lazily-initialized buffers.
+        StringBuilder decoded = null;
+        ByteArrayOutputStream out = null;
+
+        int oldLength = s.length();
+
+        // This loop alternates between copying over normal characters and
+        // escaping in chunks. This results in fewer method calls and
+        // allocations than decoding one character at a time.
+        int current = 0;
+        while (current < oldLength) {
+            // Start in "copying" mode where we copy over normal characters.
+
+            // Find the next escape sequence.
+            int nextEscape = s.indexOf('%', current);
+
+            if (nextEscape == NOT_FOUND) {
+                if (decoded == null) {
+                    // We didn't actually decode anything.
+                    return s;
+                } else {
+                    // Append the remainder and return the decoded string.
+                    decoded.append(s, current, oldLength);
+                    return decoded.toString();
+                }
+            }
+
+            // Prepare buffers.
+            if (decoded == null) {
+                // Looks like we're going to need the buffers...
+                // We know the new string will be shorter. Using the old length
+                // may overshoot a bit, but it will save us from resizing the
+                // buffer.
+                decoded = new StringBuilder(oldLength);
+                out = new ByteArrayOutputStream(4);
+            } else {
+                // Clear decoding buffer.
+                out.reset();
+            }
+
+            // Append characters leading up to the escape.
+            if (nextEscape > current) {
+                decoded.append(s, current, nextEscape);
+
+                current = nextEscape;
+            } else {
+                // assert current == nextEscape
+            }
+
+            // Switch to "decoding" mode where we decode a string of escape
+            // sequences.
+
+            // Decode and append escape sequences. Escape sequences look like
+            // "%ab" where % is literal and a and b are hex digits.
+            try {
+                do {
+                    if (current + 2 >= oldLength) {
+                        // Truncated escape sequence.
+                        out.write(REPLACEMENT);
+                    } else {
+                        int a = Character.digit(s.charAt(current + 1), 16);
+                        int b = Character.digit(s.charAt(current + 2), 16);
+
+                        if (a == -1 || b == -1) {
+                            // Non hex digits.
+                            out.write(REPLACEMENT);
+                        } else {
+                            // Combine the hex digits into one byte and write.
+                            out.write((a << 4) + b);
+                        }
+                    }
+
+                    // Move passed the escape sequence.
+                    current += 3;
+                } while (current < oldLength && s.charAt(current) == '%');
+
+                // Decode UTF-8 bytes into a string and append it.
+                decoded.append(out.toString(DEFAULT_ENCODING));
+            } catch (UnsupportedEncodingException e) {
+                throw new AssertionError(e);
+            } catch (IOException e) {
+                throw new AssertionError(e);
+            }
+        }
+
+        // If we don't have a buffer, we didn't have to decode anything.
+        return decoded == null ? s : decoded.toString();
+    }
+
+    /**
+     * Support for part implementations.
+     */
+    static abstract class AbstractPart {
+
+        /**
+         * Enum which indicates which representation of a given part we have.
+         */
+        static class Representation {
+            static final int BOTH = 0;
+            static final int ENCODED = 1;
+            static final int DECODED = 2;
+        }
+
+        volatile String encoded;
+        volatile String decoded;
+
+        AbstractPart(String encoded, String decoded) {
+            this.encoded = encoded;
+            this.decoded = decoded;
+        }
+
+        abstract String getEncoded();
+
+        final String getDecoded() {
+            @SuppressWarnings("StringEquality")
+            boolean hasDecoded = decoded != NOT_CACHED;
+            return hasDecoded ? decoded : (decoded = decode(encoded));
+        }
+
+        final void writeTo(Parcel parcel) {
+            @SuppressWarnings("StringEquality")
+            boolean hasEncoded = encoded != NOT_CACHED;
+
+            @SuppressWarnings("StringEquality")
+            boolean hasDecoded = decoded != NOT_CACHED;
+
+            if (hasEncoded && hasDecoded) {
+                parcel.writeInt(Representation.BOTH);
+                parcel.writeString(encoded);
+                parcel.writeString(decoded);
+            } else if (hasEncoded) {
+                parcel.writeInt(Representation.ENCODED);
+                parcel.writeString(encoded);
+            } else if (hasDecoded) {
+                parcel.writeInt(Representation.DECODED);
+                parcel.writeString(decoded);
+            } else {
+                throw new AssertionError();
+            }
+        }
+    }
+
+    /**
+     * Immutable wrapper of encoded and decoded versions of a URI part. Lazily
+     * creates the encoded or decoded version from the other.
+     */
+    static class Part extends AbstractPart {
+
+        /** A part with null values. */
+        static final Part NULL = new EmptyPart(null);
+
+        /** A part with empty strings for values. */
+        static final Part EMPTY = new EmptyPart("");
+
+        private Part(String encoded, String decoded) {
+            super(encoded, decoded);
+        }
+
+        boolean isEmpty() {
+            return false;
+        }
+
+        String getEncoded() {
+            @SuppressWarnings("StringEquality")
+            boolean hasEncoded = encoded != NOT_CACHED;
+            return hasEncoded ? encoded : (encoded = encode(decoded));
+        }
+
+        static Part readFrom(Parcel parcel) {
+            int representation = parcel.readInt();
+            switch (representation) {
+                case Representation.BOTH:
+                    return from(parcel.readString(), parcel.readString());
+                case Representation.ENCODED:
+                    return fromEncoded(parcel.readString());
+                case Representation.DECODED:
+                    return fromDecoded(parcel.readString());
+                default:
+                    throw new AssertionError();
+            }
+        }
+
+        /**
+         * Returns given part or {@link #NULL} if the given part is null.
+         */
+        static Part nonNull(Part part) {
+            return part == null ? NULL : part;
+        }
+
+        /**
+         * Creates a part from the encoded string.
+         *
+         * @param encoded part string
+         */
+        static Part fromEncoded(String encoded) {
+            return from(encoded, NOT_CACHED);
+        }
+
+        /**
+         * Creates a part from the decoded string.
+         *
+         * @param decoded part string
+         */
+        static Part fromDecoded(String decoded) {
+            return from(NOT_CACHED, decoded);
+        }
+
+        /**
+         * Creates a part from the encoded and decoded strings.
+         *
+         * @param encoded part string
+         * @param decoded part string
+         */
+        static Part from(String encoded, String decoded) {
+            // We have to check both encoded and decoded in case one is
+            // NOT_CACHED.
+
+            if (encoded == null) {
+                return NULL;
+            }
+            if (encoded.length() == 0) {
+                return EMPTY;
+            }
+
+            if (decoded == null) {
+                return NULL;
+            }
+            if (decoded .length() == 0) {
+                return EMPTY;
+            }
+
+            return new Part(encoded, decoded);
+        }
+
+        private static class EmptyPart extends Part {
+            public EmptyPart(String value) {
+                super(value, value);
+            }
+
+            @Override
+            boolean isEmpty() {
+                return true;
+            }
+        }
+    }
+
+    /**
+     * Immutable wrapper of encoded and decoded versions of a path part. Lazily
+     * creates the encoded or decoded version from the other.
+     */
+    static class PathPart extends AbstractPart {
+
+        /** A part with null values. */
+        static final PathPart NULL = new PathPart(null, null);
+
+        /** A part with empty strings for values. */
+        static final PathPart EMPTY = new PathPart("", "");
+
+        private PathPart(String encoded, String decoded) {
+            super(encoded, decoded);
+        }
+
+        String getEncoded() {
+            @SuppressWarnings("StringEquality")
+            boolean hasEncoded = encoded != NOT_CACHED;
+
+            // Don't encode '/'.
+            return hasEncoded ? encoded : (encoded = encode(decoded, "/"));
+        }
+
+        /**
+         * Cached path segments. This doesn't need to be volatile--we don't
+         * care if other threads see the result.
+         */
+        private PathSegments pathSegments;
+
+        /**
+         * Gets the individual path segments. Parses them if necessary.
+         *
+         * @return parsed path segments or null if this isn't a hierarchical
+         *  URI
+         */
+        PathSegments getPathSegments() {
+            if (pathSegments != null) {
+                return pathSegments;
+            }
+
+            String path = getEncoded();
+            if (path == null) {
+                return pathSegments = PathSegments.EMPTY;
+            }
+
+            PathSegmentsBuilder segmentBuilder = new PathSegmentsBuilder();
+
+            int previous = 0;
+            int current;
+            while ((current = path.indexOf('/', previous)) > -1) {
+                // This check keeps us from adding a segment if the path starts
+                // '/' and an empty segment for "//".
+                if (previous < current) {
+                    String decodedSegment
+                            = decode(path.substring(previous, current));
+                    segmentBuilder.add(decodedSegment);
+                }
+                previous = current + 1;
+            }
+
+            // Add in the final path segment.
+            if (previous < path.length()) {
+                segmentBuilder.add(decode(path.substring(previous)));
+            }
+
+            return pathSegments = segmentBuilder.build();
+        }
+
+        static PathPart appendEncodedSegment(PathPart oldPart,
+                String newSegment) {
+            // If there is no old path, should we make the new path relative
+            // or absolute? I pick absolute.
+
+            if (oldPart == null) {
+                // No old path.
+                return fromEncoded("/" + newSegment);
+            }
+
+            String oldPath = oldPart.getEncoded();
+
+            if (oldPath == null) {
+                oldPath = "";
+            }
+
+            int oldPathLength = oldPath.length();
+            String newPath;
+            if (oldPathLength == 0) {
+                // No old path.
+                newPath = "/" + newSegment;
+            } else if (oldPath.charAt(oldPathLength - 1) == '/') {
+                newPath = oldPath + newSegment;
+            } else {
+                newPath = oldPath + "/" + newSegment;
+            }
+
+            return fromEncoded(newPath);
+        }
+
+        static PathPart appendDecodedSegment(PathPart oldPart, String decoded) {
+            String encoded = encode(decoded);
+
+            // TODO: Should we reuse old PathSegments? Probably not.
+            return appendEncodedSegment(oldPart, encoded);
+        }
+
+        static PathPart readFrom(Parcel parcel) {
+            int representation = parcel.readInt();
+            switch (representation) {
+                case Representation.BOTH:
+                    return from(parcel.readString(), parcel.readString());
+                case Representation.ENCODED:
+                    return fromEncoded(parcel.readString());
+                case Representation.DECODED:
+                    return fromDecoded(parcel.readString());
+                default:
+                    throw new AssertionError();
+            }
+        }
+
+        /**
+         * Creates a path from the encoded string.
+         *
+         * @param encoded part string
+         */
+        static PathPart fromEncoded(String encoded) {
+            return from(encoded, NOT_CACHED);
+        }
+
+        /**
+         * Creates a path from the decoded string.
+         *
+         * @param decoded part string
+         */
+        static PathPart fromDecoded(String decoded) {
+            return from(NOT_CACHED, decoded);
+        }
+
+        /**
+         * Creates a path from the encoded and decoded strings.
+         *
+         * @param encoded part string
+         * @param decoded part string
+         */
+        static PathPart from(String encoded, String decoded) {
+            if (encoded == null) {
+                return NULL;
+            }
+
+            if (encoded.length() == 0) {
+                return EMPTY;
+            }
+
+            return new PathPart(encoded, decoded);
+        }
+
+        /**
+         * Prepends path values with "/" if they're present, not empty, and
+         * they don't already start with "/".
+         */
+        static PathPart makeAbsolute(PathPart oldPart) {
+            @SuppressWarnings("StringEquality")
+            boolean encodedCached = oldPart.encoded != NOT_CACHED;
+
+            // We don't care which version we use, and we don't want to force
+            // unneccessary encoding/decoding.
+            String oldPath = encodedCached ? oldPart.encoded : oldPart.decoded;
+
+            if (oldPath == null || oldPath.length() == 0
+                    || oldPath.startsWith("/")) {
+                return oldPart;
+            }
+
+            // Prepend encoded string if present.
+            String newEncoded = encodedCached
+                    ? "/" + oldPart.encoded : NOT_CACHED;
+
+            // Prepend decoded string if present.
+            @SuppressWarnings("StringEquality")
+            boolean decodedCached = oldPart.decoded != NOT_CACHED;
+            String newDecoded = decodedCached
+                    ? "/" + oldPart.decoded
+                    : NOT_CACHED;
+
+            return new PathPart(newEncoded, newDecoded);
+        }
+    }
+
+    /**
+     * Creates a new Uri by appending an already-encoded path segment to a
+     * base Uri.
+     *
+     * @param baseUri Uri to append path segment to
+     * @param pathSegment encoded path segment to append
+     * @return a new Uri based on baseUri with the given segment appended to 
+     *  the path
+     * @throws NullPointerException if baseUri is null
+     */
+    public static Uri withAppendedPath(Uri baseUri, String pathSegment) {
+        Builder builder = baseUri.buildUpon();
+        builder = builder.appendEncodedPath(pathSegment);
+        return builder.build();
+    }
+}
diff --git a/core/java/android/net/UrlQuerySanitizer.java b/core/java/android/net/UrlQuerySanitizer.java
new file mode 100644
index 0000000..a6efcdd
--- /dev/null
+++ b/core/java/android/net/UrlQuerySanitizer.java
@@ -0,0 +1,913 @@
+/*
+ * Copyright (C) 2007 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 android.net;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Set;
+import java.util.StringTokenizer;
+
+/**
+ *
+ * Sanitizes the Query portion of a URL. Simple example:
+ * <code>
+ * UrlQuerySanitizer sanitizer = new UrlQuerySanitizer();
+ * sanitizer.setAllowUnregisteredParamaters(true);
+ * sanitizer.parseUrl("http://example.com/?name=Joe+User");
+ * String name = sanitizer.getValue("name"));
+ * // name now contains "Joe_User"
+ * </code>
+ *
+ * Register ValueSanitizers to customize the way individual
+ * parameters are sanitized:
+ * <code>
+ * UrlQuerySanitizer sanitizer = new UrlQuerySanitizer();
+ * sanitizer.registerParamater("name", UrlQuerySanitizer.createSpaceLegal());
+ * sanitizer.parseUrl("http://example.com/?name=Joe+User");
+ * String name = sanitizer.getValue("name"));
+ * // name now contains "Joe User". (The string is first decoded, which
+ * // converts the '+' to a ' '. Then the string is sanitized, which
+ * // converts the ' ' to an '_'. (The ' ' is converted because the default
+ * unregistered parameter sanitizer does not allow any special characters,
+ * and ' ' is a special character.)
+ * </code>
+ *
+ * There are several ways to create ValueSanitizers. In order of increasing
+ * sophistication:
+ * <ol>
+ * <li>Call one of the UrlQuerySanitizer.createXXX() methods.
+ * <li>Construct your own instance of
+ * UrlQuerySanitizer.IllegalCharacterValueSanitizer.
+ * <li>Subclass UrlQuerySanitizer.ValueSanitizer to define your own value
+ * sanitizer.
+ * </ol>
+ *
+ */
+public class UrlQuerySanitizer {
+
+    /**
+     * A simple tuple that holds parameter-value pairs.
+     *
+     */
+    public class ParameterValuePair {
+        /**
+         * Construct a parameter-value tuple.
+         * @param parameter an unencoded parameter
+         * @param value an unencoded value
+         */
+        public ParameterValuePair(String parameter,
+                String value) {
+            mParameter = parameter;
+            mValue = value;
+        }
+        /**
+         * The unencoded parameter
+         */
+        public String mParameter;
+        /**
+         * The unencoded value
+         */
+        public String mValue;
+    }
+
+    final private HashMap<String, ValueSanitizer> mSanitizers =
+        new HashMap<String, ValueSanitizer>();
+    final private HashMap<String, String> mEntries =
+        new HashMap<String, String>();
+    final private ArrayList<ParameterValuePair> mEntriesList =
+        new ArrayList<ParameterValuePair>();
+    private boolean mAllowUnregisteredParamaters;
+    private boolean mPreferFirstRepeatedParameter;
+    private ValueSanitizer mUnregisteredParameterValueSanitizer =
+        getAllIllegal();
+
+    /**
+     * A functor used to sanitize a single query value.
+     *
+     */
+    public static interface ValueSanitizer {
+        /**
+         * Sanitize an unencoded value.
+         * @param value
+         * @return the sanitized unencoded value
+         */
+        public String sanitize(String value);
+    }
+
+    /**
+     * Sanitize values based on which characters they contain. Illegal
+     * characters are replaced with either space or '_', depending upon
+     * whether space is a legal character or not.
+     */
+    public static class IllegalCharacterValueSanitizer implements
+        ValueSanitizer {
+        private int mFlags;
+
+        /**
+         * Allow space (' ') characters.
+         */
+        public final static int SPACE_OK =              1 << 0;
+        /**
+         * Allow whitespace characters other than space. The
+         * other whitespace characters are
+         * '\t' '\f' '\n' '\r' and '\0x000b' (vertical tab)
+         */
+        public final static int OTHER_WHITESPACE_OK =  1 << 1;
+        /**
+         * Allow characters with character codes 128 to 255.
+         */
+        public final static int NON_7_BIT_ASCII_OK =    1 << 2;
+        /**
+         * Allow double quote characters. ('"')
+         */
+        public final static int DQUOTE_OK =             1 << 3;
+        /**
+         * Allow single quote characters. ('\'')
+         */
+        public final static int SQUOTE_OK =             1 << 4;
+        /**
+         * Allow less-than characters. ('<')
+         */
+        public final static int LT_OK =                 1 << 5;
+        /**
+         * Allow greater-than characters. ('>')
+         */
+        public final static int GT_OK =                 1 << 6;
+        /**
+         * Allow ampersand characters ('&')
+         */
+        public final static int AMP_OK =                1 << 7;
+        /**
+         * Allow percent-sign characters ('%')
+         */
+        public final static int PCT_OK =                1 << 8;
+        /**
+         * Allow nul characters ('\0')
+         */
+        public final static int NUL_OK =                1 << 9;
+        /**
+         * Allow text to start with a script URL
+         * such as "javascript:" or "vbscript:"
+         */
+        public final static int SCRIPT_URL_OK =         1 << 10;
+
+        /**
+         * Mask with all fields set to OK
+         */
+        public final static int ALL_OK =                0x7ff;
+
+        /**
+         * Mask with both regular space and other whitespace OK
+         */
+        public final static int ALL_WHITESPACE_OK =
+            SPACE_OK | OTHER_WHITESPACE_OK;
+
+
+        // Common flag combinations:
+
+        /**
+         * <ul>
+         * <li>Deny all special characters.
+         * <li>Deny script URLs.
+         * </ul>
+         */
+        public final static int ALL_ILLEGAL =
+            0;
+        /**
+         * <ul>
+         * <li>Allow all special characters except Nul. ('\0').
+         * <li>Allow script URLs.
+         * </ul>
+         */
+        public final static int ALL_BUT_NUL_LEGAL =
+            ALL_OK & ~NUL_OK;
+        /**
+         * <ul>
+         * <li>Allow all special characters except for:
+         * <ul>
+         *  <li>whitespace characters
+         *  <li>Nul ('\0')
+         * </ul>
+         * <li>Allow script URLs.
+         * </ul>
+         */
+        public final static int ALL_BUT_WHITESPACE_LEGAL =
+            ALL_OK & ~(ALL_WHITESPACE_OK | NUL_OK);
+        /**
+         * <ul>
+         * <li>Allow characters used by encoded URLs.
+         * <li>Deny script URLs.
+         * </ul>
+         */
+        public final static int URL_LEGAL =
+            NON_7_BIT_ASCII_OK | SQUOTE_OK | AMP_OK | PCT_OK;
+        /**
+         * <ul>
+         * <li>Allow characters used by encoded URLs.
+         * <li>Allow spaces.
+         * <li>Deny script URLs.
+         * </ul>
+         */
+        public final static int URL_AND_SPACE_LEGAL =
+            URL_LEGAL | SPACE_OK;
+        /**
+         * <ul>
+         * <li>Allow ampersand.
+         * <li>Deny script URLs.
+         * </ul>
+         */
+        public final static int AMP_LEGAL =
+            AMP_OK;
+        /**
+         * <ul>
+         * <li>Allow ampersand.
+         * <li>Allow space.
+         * <li>Deny script URLs.
+         * </ul>
+         */
+        public final static int AMP_AND_SPACE_LEGAL =
+            AMP_OK | SPACE_OK;
+        /**
+         * <ul>
+         * <li>Allow space.
+         * <li>Deny script URLs.
+         * </ul>
+         */
+        public final static int SPACE_LEGAL =
+            SPACE_OK;
+        /**
+         * <ul>
+         * <li>Allow all but.
+         * <ul>
+         *  <li>Nul ('\0')
+         *  <li>Angle brackets ('<', '>')
+         * </ul>
+         * <li>Deny script URLs.
+         * </ul>
+         */
+        public final static int ALL_BUT_NUL_AND_ANGLE_BRACKETS_LEGAL =
+            ALL_OK & ~(NUL_OK | LT_OK | GT_OK);
+
+        /**
+         *  Script URL definitions
+         */
+
+        private final static String JAVASCRIPT_PREFIX = "javascript:";
+
+        private final static String VBSCRIPT_PREFIX = "vbscript:";
+
+        private final static int MIN_SCRIPT_PREFIX_LENGTH = Math.min(
+                JAVASCRIPT_PREFIX.length(), VBSCRIPT_PREFIX.length());
+
+        /**
+         * Construct a sanitizer. The parameters set the behavior of the
+         * sanitizer.
+         * @param flags some combination of the XXX_OK flags.
+         */
+        public IllegalCharacterValueSanitizer(
+            int flags) {
+            mFlags = flags;
+        }
+        /**
+         * Sanitize a value.
+         * <ol>
+         * <li>If script URLs are not OK, the will be removed.
+         * <li>If neither spaces nor other white space is OK, then
+         * white space will be trimmed from the beginning and end of
+         * the URL. (Just the actual white space characters are trimmed, not
+         * other control codes.)
+         * <li> Illegal characters will be replaced with
+         * either ' ' or '_', depending on whether a space is itself a
+         * legal character.
+         * </ol>
+         * @param value
+         * @return the sanitized value
+         */
+        public String sanitize(String value) {
+            if (value == null) {
+                return null;
+            }
+            int length = value.length();
+            if ((mFlags & SCRIPT_URL_OK) != 0) {
+                if (length >= MIN_SCRIPT_PREFIX_LENGTH) {
+                    String asLower = value.toLowerCase();
+                    if (asLower.startsWith(JAVASCRIPT_PREFIX)  ||
+                        asLower.startsWith(VBSCRIPT_PREFIX)) {
+                        return "";
+                    }
+                }
+            }
+
+            // If whitespace isn't OK, get rid of whitespace at beginning
+            // and end of value.
+            if ( (mFlags & ALL_WHITESPACE_OK) == 0) {
+                value = trimWhitespace(value);
+                // The length could have changed, so we need to correct
+                // the length variable.
+                length = value.length();
+            }
+
+            StringBuilder stringBuilder = new StringBuilder(length);
+            for(int i = 0; i < length; i++) {
+                char c = value.charAt(i);
+                if (!characterIsLegal(c)) {
+                    if ((mFlags & SPACE_OK) != 0) {
+                        c = ' ';
+                    }
+                    else {
+                        c = '_';
+                    }
+                }
+                stringBuilder.append(c);
+            }
+            return stringBuilder.toString();
+        }
+
+        /**
+         * Trim whitespace from the beginning and end of a string.
+         * <p>
+         * Note: can't use {@link String#trim} because {@link String#trim} has a
+         * different definition of whitespace than we want.
+         * @param value the string to trim
+         * @return the trimmed string
+         */
+        private String trimWhitespace(String value) {
+            int start = 0;
+            int last = value.length() - 1;
+            int end = last;
+            while (start <= end && isWhitespace(value.charAt(start))) {
+                start++;
+            }
+            while (end >= start && isWhitespace(value.charAt(end))) {
+                end--;
+            }
+            if (start == 0 && end == last) {
+                return value;
+            }
+            return value.substring(start, end + 1);
+        }
+
+        /**
+         * Check if c is whitespace.
+         * @param c character to test
+         * @return true if c is a whitespace character
+         */
+        private boolean isWhitespace(char c) {
+            switch(c) {
+            case ' ':
+            case '\t':
+            case '\f':
+            case '\n':
+            case '\r':
+            case 11: /* VT */
+                return true;
+            default:
+                return false;
+            }
+        }
+
+        /**
+         * Check whether an individual character is legal. Uses the
+         * flag bit-set passed into the constructor.
+         * @param c
+         * @return true if c is a legal character
+         */
+        private boolean characterIsLegal(char c) {
+            switch(c) {
+            case ' ' : return (mFlags & SPACE_OK) != 0;
+            case '\t': case '\f': case '\n': case '\r': case 11: /* VT */
+              return (mFlags & OTHER_WHITESPACE_OK) != 0;
+            case '\"': return (mFlags & DQUOTE_OK) != 0;
+            case '\'': return (mFlags & SQUOTE_OK) != 0;
+            case '<' : return (mFlags & LT_OK) != 0;
+            case '>' : return (mFlags & GT_OK) != 0;
+            case '&' : return (mFlags & AMP_OK) != 0;
+            case '%' : return (mFlags & PCT_OK) != 0;
+            case '\0': return (mFlags & NUL_OK) != 0;
+            default  : return (c >= 32 && c < 127) ||
+                ((c >= 128) && ((mFlags & NON_7_BIT_ASCII_OK) != 0));
+            }
+        }
+    }
+
+    /**
+     * Get the current value sanitizer used when processing
+     * unregistered parameter values.
+     * <p>
+     * <b>Note:</b> The default unregistered parameter value sanitizer is
+     * one that doesn't allow any special characters, similar to what
+     * is returned by calling createAllIllegal.
+     *
+     * @return the current ValueSanitizer used to sanitize unregistered
+     * parameter values.
+     */
+    public ValueSanitizer getUnregisteredParameterValueSanitizer() {
+        return mUnregisteredParameterValueSanitizer;
+    }
+
+    /**
+     * Set the value sanitizer used when processing unregistered
+     * parameter values.
+     * @param sanitizer set the ValueSanitizer used to sanitize unregistered
+     * parameter values.
+     */
+    public void setUnregisteredParameterValueSanitizer(
+            ValueSanitizer sanitizer) {
+        mUnregisteredParameterValueSanitizer = sanitizer;
+    }
+
+
+    // Private fields for singleton sanitizers:
+
+    private static final ValueSanitizer sAllIllegal =
+        new IllegalCharacterValueSanitizer(
+                IllegalCharacterValueSanitizer.ALL_ILLEGAL);
+
+    private static final ValueSanitizer sAllButNulLegal =
+        new IllegalCharacterValueSanitizer(
+                IllegalCharacterValueSanitizer.ALL_BUT_NUL_LEGAL);
+
+    private static final ValueSanitizer sAllButWhitespaceLegal =
+        new IllegalCharacterValueSanitizer(
+                IllegalCharacterValueSanitizer.ALL_BUT_WHITESPACE_LEGAL);
+
+    private static final ValueSanitizer sURLLegal =
+        new IllegalCharacterValueSanitizer(
+                IllegalCharacterValueSanitizer.URL_LEGAL);
+
+    private static final ValueSanitizer sUrlAndSpaceLegal =
+        new IllegalCharacterValueSanitizer(
+                IllegalCharacterValueSanitizer.URL_AND_SPACE_LEGAL);
+
+    private static final ValueSanitizer sAmpLegal =
+        new IllegalCharacterValueSanitizer(
+                IllegalCharacterValueSanitizer.AMP_LEGAL);
+
+    private static final ValueSanitizer sAmpAndSpaceLegal =
+        new IllegalCharacterValueSanitizer(
+                IllegalCharacterValueSanitizer.AMP_AND_SPACE_LEGAL);
+
+    private static final ValueSanitizer sSpaceLegal =
+        new IllegalCharacterValueSanitizer(
+                IllegalCharacterValueSanitizer.SPACE_LEGAL);
+
+    private static final ValueSanitizer sAllButNulAndAngleBracketsLegal =
+        new IllegalCharacterValueSanitizer(
+                IllegalCharacterValueSanitizer.ALL_BUT_NUL_AND_ANGLE_BRACKETS_LEGAL);
+
+    /**
+     * Return a value sanitizer that does not allow any special characters,
+     * and also does not allow script URLs.
+     * @return a value sanitizer
+     */
+    public static final ValueSanitizer getAllIllegal() {
+        return sAllIllegal;
+    }
+
+    /**
+     * Return a value sanitizer that allows everything except Nul ('\0')
+     * characters. Script URLs are allowed.
+     * @return a value sanitizer
+     */
+    public static final ValueSanitizer getAllButNulLegal() {
+        return sAllButNulLegal;
+    }
+    /**
+     * Return a value sanitizer that allows everything except Nul ('\0')
+     * characters, space (' '), and other whitespace characters.
+     * Script URLs are allowed.
+     * @return a value sanitizer
+     */
+    public static final ValueSanitizer getAllButWhitespaceLegal() {
+        return sAllButWhitespaceLegal;
+    }
+    /**
+     * Return a value sanitizer that allows all the characters used by
+     * encoded URLs. Does not allow script URLs.
+     * @return a value sanitizer
+     */
+    public static final ValueSanitizer getUrlLegal() {
+        return sURLLegal;
+    }
+    /**
+     * Return a value sanitizer that allows all the characters used by
+     * encoded URLs and allows spaces, which are not technically legal
+     * in encoded URLs, but commonly appear anyway.
+     * Does not allow script URLs.
+     * @return a value sanitizer
+     */
+    public static final ValueSanitizer getUrlAndSpaceLegal() {
+        return sUrlAndSpaceLegal;
+    }
+    /**
+     * Return a value sanitizer that does not allow any special characters
+     * except ampersand ('&'). Does not allow script URLs.
+     * @return a value sanitizer
+     */
+    public static final ValueSanitizer getAmpLegal() {
+        return sAmpLegal;
+    }
+    /**
+     * Return a value sanitizer that does not allow any special characters
+     * except ampersand ('&') and space (' '). Does not allow script URLs.
+     * @return a value sanitizer
+     */
+    public static final ValueSanitizer getAmpAndSpaceLegal() {
+        return sAmpAndSpaceLegal;
+    }
+    /**
+     * Return a value sanitizer that does not allow any special characters
+     * except space (' '). Does not allow script URLs.
+     * @return a value sanitizer
+     */
+    public static final ValueSanitizer getSpaceLegal() {
+        return sSpaceLegal;
+    }
+    /**
+     * Return a value sanitizer that allows any special characters
+     * except angle brackets ('<' and '>') and Nul ('\0').
+     * Allows script URLs.
+     * @return a value sanitizer
+     */
+    public static final ValueSanitizer getAllButNulAndAngleBracketsLegal() {
+        return sAllButNulAndAngleBracketsLegal;
+    }
+
+    /**
+     * Constructs a UrlQuerySanitizer.
+     * <p>
+     * Defaults:
+     * <ul>
+     * <li>unregistered parameters are not allowed.
+     * <li>the last instance of a repeated parameter is preferred.
+     * <li>The default value sanitizer is an AllIllegal value sanitizer.
+     * <ul>
+     */
+    public UrlQuerySanitizer() {
+    }
+
+    /**
+     * Constructs a UrlQuerySanitizer and parse a URL.
+     * This constructor is provided for convenience when the
+     * default parsing behavior is acceptable.
+     * <p>
+     * Because the URL is parsed before the constructor returns, there isn't
+     * a chance to configure the sanitizer to change the parsing behavior.
+     * <p>
+     * <code>
+     * UrlQuerySanitizer sanitizer = new UrlQuerySanitizer(myUrl);
+     * String name = sanitizer.getValue("name");
+     * </code>
+     * <p>
+     * Defaults:
+     * <ul>
+     * <li>unregistered parameters <em>are</em> allowed.
+     * <li>the last instance of a repeated parameter is preferred.
+     * <li>The default value sanitizer is an AllIllegal value sanitizer.
+     * <ul>
+     */
+    public UrlQuerySanitizer(String url) {
+        setAllowUnregisteredParamaters(true);
+        parseUrl(url);
+    }
+
+    /**
+     * Parse the query parameters out of an encoded URL.
+     * Works by extracting the query portion from the URL and then
+     * calling parseQuery(). If there is no query portion it is
+     * treated as if the query portion is an empty string.
+     * @param url the encoded URL to parse.
+     */
+    public void parseUrl(String url) {
+        int queryIndex = url.indexOf('?');
+        String query;
+        if (queryIndex >= 0) {
+            query = url.substring(queryIndex + 1);
+        }
+        else {
+            query = "";
+        }
+        parseQuery(query);
+    }
+
+    /**
+     * Parse a query. A query string is any number of parameter-value clauses
+     * separated by any non-zero number of ampersands. A parameter-value clause
+     * is a parameter followed by an equal sign, followed by a value. If the
+     * equal sign is missing, the value is assumed to be the empty string.
+     * @param query the query to parse.
+     */
+    public void parseQuery(String query) {
+        clear();
+        // Split by '&'
+        StringTokenizer tokenizer = new StringTokenizer(query, "&");
+        while(tokenizer.hasMoreElements()) {
+            String attributeValuePair = tokenizer.nextToken();
+            if (attributeValuePair.length() > 0) {
+                int assignmentIndex = attributeValuePair.indexOf('=');
+                if (assignmentIndex < 0) {
+                    // No assignment found, treat as if empty value
+                    parseEntry(attributeValuePair, "");
+                }
+                else {
+                    parseEntry(attributeValuePair.substring(0, assignmentIndex),
+                            attributeValuePair.substring(assignmentIndex + 1));
+                }
+            }
+        }
+    }
+
+    /**
+     * Get a set of all of the parameters found in the sanitized query.
+     * <p>
+     * Note: Do not modify this set. Treat it as a read-only set.
+     * @return all the parameters found in the current query.
+     */
+    public Set<String> getParameterSet() {
+        return mEntries.keySet();
+    }
+
+    /**
+     * An array list of all of the parameter value pairs in the sanitized
+     * query, in the order they appeared in the query. May contain duplicate
+     * parameters.
+     * <p class="note"><b>Note:</b> Do not modify this list. Treat it as a read-only list.</p>
+     */
+    public List<ParameterValuePair> getParameterList() {
+        return mEntriesList;
+    }
+
+    /**
+     * Check if a parameter exists in the current sanitized query.
+     * @param parameter the unencoded name of a parameter.
+     * @return true if the paramater exists in the current sanitized queary.
+     */
+    public boolean hasParameter(String parameter) {
+        return mEntries.containsKey(parameter);
+    }
+
+    /**
+     * Get the value for a parameter in the current sanitized query.
+     * Returns null if the parameter does not
+     * exit.
+     * @param parameter the unencoded name of a parameter.
+     * @return the sanitized unencoded value of the parameter,
+     * or null if the parameter does not exist.
+     */
+    public String getValue(String parameter) {
+        return mEntries.get(parameter);
+    }
+
+    /**
+     * Register a value sanitizer for a particular parameter. Can also be used
+     * to replace or remove an already-set value sanitizer.
+     * <p>
+     * Registering a non-null value sanitizer for a particular parameter
+     * makes that parameter a registered parameter.
+     * @param parameter an unencoded parameter name
+     * @param valueSanitizer the value sanitizer to use for a particular
+     * parameter. May be null in order to unregister that parameter.
+     * @see #getAllowUnregisteredParamaters()
+     */
+    public void registerParameter(String parameter,
+            ValueSanitizer valueSanitizer) {
+        if (valueSanitizer == null) {
+            mSanitizers.remove(parameter);
+        }
+        mSanitizers.put(parameter, valueSanitizer);
+    }
+
+    /**
+     * Register a value sanitizer for an array of parameters.
+     * @param parameters An array of unencoded parameter names.
+     * @param valueSanitizer
+     * @see #registerParameter
+     */
+    public void registerParameters(String[] parameters,
+            ValueSanitizer valueSanitizer) {
+        int length = parameters.length;
+        for(int i = 0; i < length; i++) {
+            mSanitizers.put(parameters[i], valueSanitizer);
+        }
+    }
+
+    /**
+     * Set whether or not unregistered parameters are allowed. If they
+     * are not allowed, then they will be dropped when a query is sanitized.
+     * <p>
+     * Defaults to false.
+     * @param allowUnregisteredParamaters true to allow unregistered parameters.
+     * @see #getAllowUnregisteredParamaters()
+     */
+    public void setAllowUnregisteredParamaters(
+            boolean allowUnregisteredParamaters) {
+        mAllowUnregisteredParamaters = allowUnregisteredParamaters;
+    }
+
+    /**
+     * Get whether or not unregistered parameters are allowed. If not
+     * allowed, they will be dropped when a query is parsed.
+     * @return true if unregistered parameters are allowed.
+     * @see #setAllowUnregisteredParamaters(boolean)
+     */
+    public boolean getAllowUnregisteredParamaters() {
+        return mAllowUnregisteredParamaters;
+    }
+
+    /**
+     * Set whether or not the first occurrence of a repeated parameter is
+     * preferred. True means the first repeated parameter is preferred.
+     * False means that the last repeated parameter is preferred.
+     * <p>
+     * The preferred parameter is the one that is returned when getParameter
+     * is called.
+     * <p>
+     * defaults to false.
+     * @param preferFirstRepeatedParameter True if the first repeated
+     * parameter is preferred.
+     * @see #getPreferFirstRepeatedParameter()
+     */
+    public void setPreferFirstRepeatedParameter(
+            boolean preferFirstRepeatedParameter) {
+        mPreferFirstRepeatedParameter = preferFirstRepeatedParameter;
+    }
+
+    /**
+     * Get whether or not the first occurrence of a repeated parameter is
+     * preferred.
+     * @return true if the first occurrence of a repeated parameter is
+     * preferred.
+     * @see #setPreferFirstRepeatedParameter(boolean)
+     */
+    public boolean getPreferFirstRepeatedParameter() {
+        return mPreferFirstRepeatedParameter;
+    }
+
+    /**
+     * Parse an escaped parameter-value pair. The default implementation
+     * unescapes both the parameter and the value, then looks up the
+     * effective value sanitizer for the parameter and uses it to sanitize
+     * the value. If all goes well then addSanitizedValue is called with
+     * the unescaped parameter and the sanitized unescaped value.
+     * @param parameter an escaped parameter
+     * @param value an unsanitzied escaped value
+     */
+    protected void parseEntry(String parameter, String value) {
+        String unescapedParameter = unescape(parameter);
+         ValueSanitizer valueSanitizer =
+            getEffectiveValueSanitizer(unescapedParameter);
+
+        if (valueSanitizer == null) {
+            return;
+        }
+        String unescapedValue = unescape(value);
+        String sanitizedValue = valueSanitizer.sanitize(unescapedValue);
+        addSanitizedEntry(unescapedParameter, sanitizedValue);
+    }
+
+    /**
+     * Record a sanitized parameter-value pair. Override if you want to
+     * do additional filtering or validation.
+     * @param parameter an unescaped parameter
+     * @param value a sanitized unescaped value
+     */
+    protected void addSanitizedEntry(String parameter, String value) {
+        mEntriesList.add(
+                new ParameterValuePair(parameter, value));
+        if (mPreferFirstRepeatedParameter) {
+            if (mEntries.containsKey(parameter)) {
+                return;
+            }
+        }
+        mEntries.put(parameter, value);
+    }
+
+    /**
+     * Get the value sanitizer for a parameter. Returns null if there
+     * is no value sanitizer registered for the parameter.
+     * @param parameter the unescaped parameter
+     * @return the currently registered value sanitizer for this parameter.
+     * @see #registerParameter(String, android.net.UrlQuerySanitizer.ValueSanitizer)
+     */
+    public ValueSanitizer getValueSanitizer(String parameter) {
+        return mSanitizers.get(parameter);
+    }
+
+    /**
+     * Get the effective value sanitizer for a parameter. Like getValueSanitizer,
+     * except if there is no value sanitizer registered for a parameter, and
+     * unregistered paramaters are allowed, then the default value sanitizer is
+     * returned.
+     * @param parameter an unescaped parameter
+     * @return the effective value sanitizer for a parameter.
+     */
+    public ValueSanitizer getEffectiveValueSanitizer(String parameter) {
+        ValueSanitizer sanitizer = getValueSanitizer(parameter);
+        if (sanitizer == null && mAllowUnregisteredParamaters) {
+            sanitizer = getUnregisteredParameterValueSanitizer();
+        }
+        return sanitizer;
+    }
+
+    /**
+     * Unescape an escaped string.
+     * <ul>
+     * <li>'+' characters are replaced by
+     * ' ' characters.
+     * <li>Valid "%xx" escape sequences are replaced by the
+     * corresponding unescaped character.
+     * <li>Invalid escape sequences such as %1z", are passed through unchanged.
+     * <ol>
+     * @param string the escaped string
+     * @return the unescaped string.
+     */
+    public String unescape(String string) {
+        // Early exit if no escaped characters.
+        int firstEscape = string.indexOf('%');
+        if ( firstEscape < 0) {
+            firstEscape = string.indexOf('+');
+            if (firstEscape < 0) {
+                return string;
+            }
+        }
+
+        int length = string.length();
+
+        StringBuilder stringBuilder = new StringBuilder(length);
+        stringBuilder.append(string.substring(0, firstEscape));
+        for (int i = firstEscape; i < length; i++) {
+            char c = string.charAt(i);
+            if (c == '+') {
+                c = ' ';
+            }
+            else if ( c == '%' && i + 2 < length) {
+                char c1 = string.charAt(i + 1);
+                char c2 = string.charAt(i + 2);
+                if (isHexDigit(c1) && isHexDigit(c2)) {
+                    c = (char) (decodeHexDigit(c1) * 16 + decodeHexDigit(c2));
+                    i += 2;
+                }
+            }
+            stringBuilder.append(c);
+        }
+        return stringBuilder.toString();
+    }
+
+    /**
+     * Test if a character is a hexidecimal digit. Both upper case and lower
+     * case hex digits are allowed.
+     * @param c the character to test
+     * @return true if c is a hex digit.
+     */
+    protected boolean isHexDigit(char c) {
+        return decodeHexDigit(c) >= 0;
+    }
+
+    /**
+     * Convert a character that represents a hexidecimal digit into an integer.
+     * If the character is not a hexidecimal digit, then -1 is returned.
+     * Both upper case and lower case hex digits are allowed.
+     * @param c the hexidecimal digit.
+     * @return the integer value of the hexidecimal digit.
+     */
+
+    protected int decodeHexDigit(char c) {
+        if (c >= '0' && c <= '9') {
+            return c - '0';
+        }
+        else if (c >= 'A' && c <= 'F') {
+            return c - 'A' + 10;
+        }
+        else if (c >= 'a' && c <= 'f') {
+            return c - 'a' + 10;
+        }
+        else {
+            return -1;
+        }
+    }
+
+    /**
+     * Clear the existing entries. Called to get ready to parse a new
+     * query string.
+     */
+    protected void clear() {
+        mEntries.clear();
+        mEntriesList.clear();
+    }
+}
+
diff --git a/core/java/android/net/WebAddress.java b/core/java/android/net/WebAddress.java
new file mode 100644
index 0000000..f4a2a6a
--- /dev/null
+++ b/core/java/android/net/WebAddress.java
@@ -0,0 +1,134 @@
+/*
+ * 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 android.net;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * {@hide}
+ *
+ * Web Address Parser
+ *
+ * This is called WebAddress, rather than URL or URI, because it
+ * attempts to parse the stuff that a user will actually type into a
+ * browser address widget.
+ *
+ * Unlike java.net.uri, this parser will not choke on URIs missing
+ * schemes.  It will only throw a ParseException if the input is
+ * really hosed.
+ *
+ * If given an https scheme but no port, fills in port
+ *
+ */
+public class WebAddress {
+
+    private final static String LOGTAG = "http";
+
+    public String mScheme;
+    public String mHost;
+    public int mPort;
+    public String mPath;
+    public String mAuthInfo;
+
+    static final int MATCH_GROUP_SCHEME = 1;
+    static final int MATCH_GROUP_AUTHORITY = 2;
+    static final int MATCH_GROUP_HOST = 3;
+    static final int MATCH_GROUP_PORT = 4;
+    static final int MATCH_GROUP_PATH = 5;
+
+    static Pattern sAddressPattern = Pattern.compile(
+            /* scheme    */ "(?:(http|HTTP|https|HTTPS|file|FILE)\\:\\/\\/)?" +
+            /* authority */ "(?:([-A-Za-z0-9$_.+!*'(),;?&=]+(?:\\:[-A-Za-z0-9$_.+!*'(),;?&=]+)?)@)?" +
+            /* host      */ "([-A-Za-z0-9%]+(?:\\.[-A-Za-z0-9%]+)*)?" +
+            /* port      */ "(?:\\:([0-9]+))?" +
+            /* path      */ "(\\/?.*)?");
+
+    /** parses given uriString. */
+    public WebAddress(String address) throws ParseException {
+        if (address == null) {
+            throw new NullPointerException();
+        }
+
+        // android.util.Log.d(LOGTAG, "WebAddress: " + address);
+
+        mScheme = "";
+        mHost = "";
+        mPort = -1;
+        mPath = "/";
+        mAuthInfo = "";
+
+        Matcher m = sAddressPattern.matcher(address);
+        String t;
+        if (m.matches()) {
+            t = m.group(MATCH_GROUP_SCHEME);
+            if (t != null) mScheme = t;
+            t = m.group(MATCH_GROUP_AUTHORITY);
+            if (t != null) mAuthInfo = t;
+            t = m.group(MATCH_GROUP_HOST);
+            if (t != null) mHost = t;
+            t = m.group(MATCH_GROUP_PORT);
+            if (t != null) {
+                try {
+                    mPort = Integer.parseInt(t);
+                } catch (NumberFormatException ex) {
+                    throw new ParseException("Bad port");
+                }
+            }
+            t = m.group(MATCH_GROUP_PATH);
+            if (t != null && t.length() > 0) {
+                /* handle busted myspace frontpage redirect with
+                   missing initial "/" */
+                if (t.charAt(0) == '/') {
+                    mPath = t;
+                } else {
+                    mPath = "/" + t;
+                }
+            }
+
+        } else {
+            // nothing found... outa here
+            throw new ParseException("Bad address");
+        }
+
+        /* Get port from scheme or scheme from port, if necessary and
+           possible */
+        if (mPort == 443 && mScheme.equals("")) {
+            mScheme = "https";
+        } else if (mPort == -1) {
+            if (mScheme.equals("https"))
+                mPort = 443;
+            else
+                mPort = 80; // default
+        }
+        if (mScheme.equals("")) mScheme = "http";
+    }
+
+    public String toString() {
+        String port = "";
+        if ((mPort != 443 && mScheme.equals("https")) ||
+            (mPort != 80 && mScheme.equals("http"))) {
+            port = ":" + Integer.toString(mPort);
+        }
+        String authInfo = "";
+        if (mAuthInfo.length() > 0) {
+            authInfo = mAuthInfo + "@";
+        }
+
+        return mScheme + "://" + authInfo + mHost + port + mPath;
+    }
+}
diff --git a/core/java/android/net/http/AndroidHttpClient.java b/core/java/android/net/http/AndroidHttpClient.java
new file mode 100644
index 0000000..0c4fcda
--- /dev/null
+++ b/core/java/android/net/http/AndroidHttpClient.java
@@ -0,0 +1,505 @@
+/*
+ * Copyright (C) 2007 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 android.net.http;
+
+import org.apache.http.Header;
+import org.apache.http.HttpEntity;
+import org.apache.http.HttpEntityEnclosingRequest;
+import org.apache.http.HttpException;
+import org.apache.http.HttpHost;
+import org.apache.http.HttpRequest;
+import org.apache.http.HttpRequestInterceptor;
+import org.apache.http.HttpResponse;
+import org.apache.http.entity.AbstractHttpEntity;
+import org.apache.http.entity.ByteArrayEntity;
+import org.apache.http.client.HttpClient;
+import org.apache.http.client.ResponseHandler;
+import org.apache.http.client.ClientProtocolException;
+import org.apache.http.client.protocol.ClientContext;
+import org.apache.http.client.methods.HttpUriRequest;
+import org.apache.http.client.params.HttpClientParams;
+import org.apache.http.conn.ClientConnectionManager;
+import org.apache.http.conn.scheme.PlainSocketFactory;
+import org.apache.http.conn.scheme.Scheme;
+import org.apache.http.conn.scheme.SchemeRegistry;
+import org.apache.http.conn.ssl.SSLSocketFactory;
+import org.apache.http.impl.client.DefaultHttpClient;
+import org.apache.http.impl.client.RequestWrapper;
+import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager;
+import org.apache.http.params.BasicHttpParams;
+import org.apache.http.params.HttpConnectionParams;
+import org.apache.http.params.HttpParams;
+import org.apache.http.params.HttpProtocolParams;
+import org.apache.http.protocol.BasicHttpProcessor;
+import org.apache.http.protocol.HttpContext;
+import org.apache.http.protocol.BasicHttpContext;
+import org.apache.harmony.xnet.provider.jsse.SSLClientSessionCache;
+import org.apache.harmony.xnet.provider.jsse.SSLContextImpl;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.OutputStream;
+import java.util.zip.GZIPInputStream;
+import java.util.zip.GZIPOutputStream;
+import java.net.URI;
+import java.security.KeyManagementException;
+
+import android.util.Log;
+import android.content.ContentResolver;
+import android.provider.Settings;
+import android.text.TextUtils;
+
+/**
+ * Subclass of the Apache {@link DefaultHttpClient} that is configured with
+ * reasonable default settings and registered schemes for Android, and
+ * also lets the user add {@link HttpRequestInterceptor} classes.
+ * Don't create this directly, use the {@link #newInstance} factory method.
+ *
+ * <p>This client processes cookies but does not retain them by default.
+ * To retain cookies, simply add a cookie store to the HttpContext:</p>
+ *
+ * <pre>context.setAttribute(ClientContext.COOKIE_STORE, cookieStore);</pre>
+ * 
+ * {@hide}
+ */
+public final class AndroidHttpClient implements HttpClient {
+        
+    // Gzip of data shorter than this probably won't be worthwhile
+    public static long DEFAULT_SYNC_MIN_GZIP_BYTES = 256;
+
+    private static final String TAG = "AndroidHttpClient";
+
+
+    /** Set if HTTP requests are blocked from being executed on this thread */
+    private static final ThreadLocal<Boolean> sThreadBlocked =
+            new ThreadLocal<Boolean>();
+
+    /** Interceptor throws an exception if the executing thread is blocked */
+    private static final HttpRequestInterceptor sThreadCheckInterceptor =
+            new HttpRequestInterceptor() {
+        public void process(HttpRequest request, HttpContext context) {
+            if (sThreadBlocked.get() != null && sThreadBlocked.get()) {
+                throw new RuntimeException("This thread forbids HTTP requests");
+            }
+        }
+    };
+
+    /**
+     * Create a new HttpClient with reasonable defaults (which you can update).
+     *
+     * @param userAgent to report in your HTTP requests.
+     * @param sessionCache persistent session cache
+     * @return AndroidHttpClient for you to use for all your requests.
+     */
+    public static AndroidHttpClient newInstance(String userAgent,
+            SSLClientSessionCache sessionCache) {
+        HttpParams params = new BasicHttpParams();
+
+        // Turn off stale checking.  Our connections break all the time anyway,
+        // and it's not worth it to pay the penalty of checking every time.
+        HttpConnectionParams.setStaleCheckingEnabled(params, false);
+
+        // Default connection and socket timeout of 20 seconds.  Tweak to taste.
+        HttpConnectionParams.setConnectionTimeout(params, 20 * 1000);
+        HttpConnectionParams.setSoTimeout(params, 20 * 1000);
+        HttpConnectionParams.setSocketBufferSize(params, 8192);
+
+        // Don't handle redirects -- return them to the caller.  Our code
+        // often wants to re-POST after a redirect, which we must do ourselves.
+        HttpClientParams.setRedirecting(params, false);
+
+        // Set the specified user agent and register standard protocols.
+        HttpProtocolParams.setUserAgent(params, userAgent);
+        SchemeRegistry schemeRegistry = new SchemeRegistry();
+        schemeRegistry.register(new Scheme("http",
+                PlainSocketFactory.getSocketFactory(), 80));
+        schemeRegistry.register(new Scheme("https",
+                socketFactoryWithCache(sessionCache), 443));
+
+        ClientConnectionManager manager =
+                new ThreadSafeClientConnManager(params, schemeRegistry);
+
+        // We use a factory method to modify superclass initialization
+        // parameters without the funny call-a-static-method dance.
+        return new AndroidHttpClient(manager, params);
+    }
+
+    /**
+     * Returns a socket factory backed by the given persistent session cache.
+     *
+     * @param sessionCache to retrieve sessions from, null for no cache
+     */
+    private static SSLSocketFactory socketFactoryWithCache(
+            SSLClientSessionCache sessionCache) {
+        if (sessionCache == null) {
+            // Use the default factory which doesn't support persistent
+            // caching.
+            return SSLSocketFactory.getSocketFactory();
+        }
+
+        // Create a new SSL context backed by the cache.
+        // TODO: Keep a weak *identity* hash map of caches to engines. In the
+        // mean time, if we have two engines for the same cache, they'll still
+        // share sessions but will have to do so through the persistent cache.
+        SSLContextImpl sslContext = new SSLContextImpl();
+        try {
+            sslContext.engineInit(null, null, null, sessionCache, null);
+        } catch (KeyManagementException e) {
+            throw new AssertionError(e);
+        }
+        return new SSLSocketFactory(sslContext.engineGetSocketFactory());
+    }
+
+    /**
+     * Create a new HttpClient with reasonable defaults (which you can update).
+     * @param userAgent to report in your HTTP requests.
+     * @return AndroidHttpClient for you to use for all your requests.
+     */
+    public static AndroidHttpClient newInstance(String userAgent) {
+        return newInstance(userAgent, null /* session cache */);
+    }
+
+    private final HttpClient delegate;
+
+    private RuntimeException mLeakedException = new IllegalStateException(
+            "AndroidHttpClient created and never closed");
+
+    private AndroidHttpClient(ClientConnectionManager ccm, HttpParams params) {
+        this.delegate = new DefaultHttpClient(ccm, params) {
+            @Override
+            protected BasicHttpProcessor createHttpProcessor() {
+                // Add interceptor to prevent making requests from main thread.
+                BasicHttpProcessor processor = super.createHttpProcessor();
+                processor.addRequestInterceptor(sThreadCheckInterceptor);
+                processor.addRequestInterceptor(new CurlLogger());
+
+                return processor;
+            }
+
+            @Override
+            protected HttpContext createHttpContext() {
+                // Same as DefaultHttpClient.createHttpContext() minus the
+                // cookie store.
+                HttpContext context = new BasicHttpContext();
+                context.setAttribute(
+                        ClientContext.AUTHSCHEME_REGISTRY,
+                        getAuthSchemes());
+                context.setAttribute(
+                        ClientContext.COOKIESPEC_REGISTRY,
+                        getCookieSpecs());
+                context.setAttribute(
+                        ClientContext.CREDS_PROVIDER,
+                        getCredentialsProvider());
+                return context;
+            }
+        };
+    }
+
+    @Override
+    protected void finalize() throws Throwable {
+        super.finalize();
+        if (mLeakedException != null) {
+            Log.e(TAG, "Leak found", mLeakedException);
+            mLeakedException = null;
+        }
+    }
+
+    /**
+     * Block this thread from executing HTTP requests.
+     * Used to guard against HTTP requests blocking the main application thread.
+     * @param blocked if HTTP requests run on this thread should be denied
+     */
+    public static void setThreadBlocked(boolean blocked) {
+        sThreadBlocked.set(blocked);
+    }
+
+    /**
+     * Modifies a request to indicate to the server that we would like a
+     * gzipped response.  (Uses the "Accept-Encoding" HTTP header.)
+     * @param request the request to modify
+     * @see #getUngzippedContent
+     */
+    public static void modifyRequestToAcceptGzipResponse(HttpRequest request) {
+        request.addHeader("Accept-Encoding", "gzip");
+    }
+
+    /**
+     * Gets the input stream from a response entity.  If the entity is gzipped
+     * then this will get a stream over the uncompressed data.
+     *
+     * @param entity the entity whose content should be read
+     * @return the input stream to read from
+     * @throws IOException
+     */
+    public static InputStream getUngzippedContent(HttpEntity entity)
+            throws IOException {
+        InputStream responseStream = entity.getContent();
+        if (responseStream == null) return responseStream;
+        Header header = entity.getContentEncoding();
+        if (header == null) return responseStream;
+        String contentEncoding = header.getValue();
+        if (contentEncoding == null) return responseStream;
+        if (contentEncoding.contains("gzip")) responseStream
+                = new GZIPInputStream(responseStream);
+        return responseStream;
+    }
+
+    /**
+     * Release resources associated with this client.  You must call this,
+     * or significant resources (sockets and memory) may be leaked.
+     */
+    public void close() {
+        if (mLeakedException != null) {
+            getConnectionManager().shutdown();
+            mLeakedException = null;
+        }
+    }
+
+    public HttpParams getParams() {
+        return delegate.getParams();
+    }
+
+    public ClientConnectionManager getConnectionManager() {
+        return delegate.getConnectionManager();
+    }
+
+    public HttpResponse execute(HttpUriRequest request) throws IOException {
+        return delegate.execute(request);
+    }
+
+    public HttpResponse execute(HttpUriRequest request, HttpContext context)
+            throws IOException {
+        return delegate.execute(request, context);
+    }
+
+    public HttpResponse execute(HttpHost target, HttpRequest request)
+            throws IOException {
+        return delegate.execute(target, request);
+    }
+
+    public HttpResponse execute(HttpHost target, HttpRequest request,
+            HttpContext context) throws IOException {
+        return delegate.execute(target, request, context);
+    }
+
+    public <T> T execute(HttpUriRequest request, 
+            ResponseHandler<? extends T> responseHandler)
+            throws IOException, ClientProtocolException {
+        return delegate.execute(request, responseHandler);
+    }
+
+    public <T> T execute(HttpUriRequest request,
+            ResponseHandler<? extends T> responseHandler, HttpContext context)
+            throws IOException, ClientProtocolException {
+        return delegate.execute(request, responseHandler, context);
+    }
+
+    public <T> T execute(HttpHost target, HttpRequest request,
+            ResponseHandler<? extends T> responseHandler) throws IOException,
+            ClientProtocolException {
+        return delegate.execute(target, request, responseHandler);
+    }
+
+    public <T> T execute(HttpHost target, HttpRequest request,
+            ResponseHandler<? extends T> responseHandler, HttpContext context)
+            throws IOException, ClientProtocolException {
+        return delegate.execute(target, request, responseHandler, context);
+    }
+
+    /**
+     * Compress data to send to server.
+     * Creates a Http Entity holding the gzipped data.
+     * The data will not be compressed if it is too short.
+     * @param data The bytes to compress
+     * @return Entity holding the data
+     */
+    public static AbstractHttpEntity getCompressedEntity(byte data[], ContentResolver resolver)
+            throws IOException {
+        AbstractHttpEntity entity;
+        if (data.length < getMinGzipSize(resolver)) {
+            entity = new ByteArrayEntity(data);
+        } else {
+            ByteArrayOutputStream arr = new ByteArrayOutputStream();
+            OutputStream zipper = new GZIPOutputStream(arr);
+            zipper.write(data);
+            zipper.close();
+            entity = new ByteArrayEntity(arr.toByteArray());
+            entity.setContentEncoding("gzip");
+        }
+        return entity;
+    }
+
+    /**
+     * Retrieves the minimum size for compressing data.
+     * Shorter data will not be compressed.
+     */
+    public static long getMinGzipSize(ContentResolver resolver) {
+        String sMinGzipBytes = Settings.Gservices.getString(resolver,
+                Settings.Gservices.SYNC_MIN_GZIP_BYTES);
+
+        if (!TextUtils.isEmpty(sMinGzipBytes)) {
+            try {
+                return Long.parseLong(sMinGzipBytes);
+            } catch (NumberFormatException nfe) {
+                Log.w(TAG, "Unable to parse " +
+                        Settings.Gservices.SYNC_MIN_GZIP_BYTES + " " +
+                        sMinGzipBytes, nfe);
+            }
+        }
+        return DEFAULT_SYNC_MIN_GZIP_BYTES;
+    }
+
+    /* cURL logging support. */
+
+    /**
+     * Logging tag and level.
+     */
+    private static class LoggingConfiguration {
+
+        private final String tag;
+        private final int level;
+
+        private LoggingConfiguration(String tag, int level) {
+            this.tag = tag;
+            this.level = level;
+        }
+
+        /**
+         * Returns true if logging is turned on for this configuration.
+         */
+        private boolean isLoggable() {
+            return Log.isLoggable(tag, level);
+        }
+
+        /**
+         * Returns true if auth logging is turned on for this configuration.
+         */
+        private boolean isAuthLoggable() {
+            return Log.isLoggable(tag + "-auth", level);
+        }
+
+        /**
+         * Prints a message using this configuration.
+         */
+        private void println(String message) {
+            Log.println(level, tag, message);
+        }
+    }
+
+    /** cURL logging configuration. */
+    private volatile LoggingConfiguration curlConfiguration;
+
+    /**
+     * Enables cURL request logging for this client.
+     *
+     * @param name to log messages with
+     * @param level at which to log messages (see {@link android.util.Log})
+     */
+    public void enableCurlLogging(String name, int level) {
+        if (name == null) {
+            throw new NullPointerException("name");
+        }
+        if (level < Log.VERBOSE || level > Log.ASSERT) {
+            throw new IllegalArgumentException("Level is out of range ["
+                + Log.VERBOSE + ".." + Log.ASSERT + "]");    
+        }
+
+        curlConfiguration = new LoggingConfiguration(name, level);
+    }
+
+    /**
+     * Disables cURL logging for this client.
+     */
+    public void disableCurlLogging() {
+        curlConfiguration = null;
+    }
+
+    /**
+     * Logs cURL commands equivalent to requests.
+     */
+    private class CurlLogger implements HttpRequestInterceptor {
+        public void process(HttpRequest request, HttpContext context)
+                throws HttpException, IOException {
+            LoggingConfiguration configuration = curlConfiguration;
+            if (configuration != null
+                    && configuration.isLoggable()
+                    && request instanceof HttpUriRequest) {
+                configuration.println(toCurl((HttpUriRequest) request,
+                        configuration.isAuthLoggable()));
+            }
+        }
+    }
+
+    /**
+     * Generates a cURL command equivalent to the given request.
+     */
+    private static String toCurl(HttpUriRequest request, boolean logAuthToken) throws IOException {
+        StringBuilder builder = new StringBuilder();
+
+        builder.append("curl ");
+
+        for (Header header: request.getAllHeaders()) {
+            if (!logAuthToken
+                    && (header.getName().equals("Authorization") ||
+                        header.getName().equals("Cookie"))) {
+                continue;
+            }
+            builder.append("--header \"");
+            builder.append(header.toString().trim());
+            builder.append("\" ");
+        }
+
+        URI uri = request.getURI();
+
+        // If this is a wrapped request, use the URI from the original
+        // request instead. getURI() on the wrapper seems to return a
+        // relative URI. We want an absolute URI.
+        if (request instanceof RequestWrapper) {
+            HttpRequest original = ((RequestWrapper) request).getOriginal();
+            if (original instanceof HttpUriRequest) {
+                uri = ((HttpUriRequest) original).getURI();
+            }
+        }
+
+        builder.append("\"");
+        builder.append(uri);
+        builder.append("\"");
+
+        if (request instanceof HttpEntityEnclosingRequest) {
+            HttpEntityEnclosingRequest entityRequest =
+                    (HttpEntityEnclosingRequest) request;
+            HttpEntity entity = entityRequest.getEntity();
+            if (entity != null && entity.isRepeatable()) {
+                if (entity.getContentLength() < 1024) {
+                    ByteArrayOutputStream stream = new ByteArrayOutputStream();
+                    entity.writeTo(stream);
+                    String entityString = stream.toString();
+
+                    // TODO: Check the content type, too.
+                    builder.append(" --data-ascii \"")
+                            .append(entityString)
+                            .append("\"");
+                } else {
+                    builder.append(" [TOO MUCH DATA TO INCLUDE]");
+                }
+            }
+        }
+
+        return builder.toString();
+    }
+}
diff --git a/core/java/android/net/http/AndroidHttpClientConnection.java b/core/java/android/net/http/AndroidHttpClientConnection.java
new file mode 100644
index 0000000..eb96679
--- /dev/null
+++ b/core/java/android/net/http/AndroidHttpClientConnection.java
@@ -0,0 +1,464 @@
+/*
+ * 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 android.net.http;
+
+import org.apache.http.Header;
+
+import org.apache.http.HttpConnection;
+import org.apache.http.HttpClientConnection;
+import org.apache.http.HttpConnectionMetrics;
+import org.apache.http.HttpEntity;
+import org.apache.http.HttpEntityEnclosingRequest;
+import org.apache.http.HttpException;
+import org.apache.http.HttpInetConnection;
+import org.apache.http.HttpRequest;
+import org.apache.http.HttpResponse;
+import org.apache.http.HttpResponseFactory;
+import org.apache.http.NoHttpResponseException;
+import org.apache.http.StatusLine;
+import org.apache.http.entity.BasicHttpEntity;
+import org.apache.http.entity.ContentLengthStrategy;
+import org.apache.http.impl.DefaultHttpResponseFactory;
+import org.apache.http.impl.HttpConnectionMetricsImpl;
+import org.apache.http.impl.entity.EntitySerializer;
+import org.apache.http.impl.entity.StrictContentLengthStrategy;
+import org.apache.http.impl.io.ChunkedInputStream;
+import org.apache.http.impl.io.ContentLengthInputStream;
+import org.apache.http.impl.io.HttpRequestWriter;
+import org.apache.http.impl.io.IdentityInputStream;
+import org.apache.http.impl.io.SocketInputBuffer;
+import org.apache.http.impl.io.SocketOutputBuffer;
+import org.apache.http.io.HttpMessageWriter;
+import org.apache.http.io.SessionInputBuffer;
+import org.apache.http.io.SessionOutputBuffer;
+import org.apache.http.message.BasicLineParser;
+import org.apache.http.message.ParserCursor;
+import org.apache.http.params.CoreConnectionPNames;
+import org.apache.http.params.HttpConnectionParams;
+import org.apache.http.params.HttpParams;
+import org.apache.http.ParseException;
+import org.apache.http.util.CharArrayBuffer;
+
+import java.io.IOException;
+import java.net.InetAddress;
+import java.net.Socket;
+import java.net.SocketException;
+
+/**
+ * A alternate class for (@link DefaultHttpClientConnection).
+ * It has better performance than DefaultHttpClientConnection
+ * 
+ * {@hide}
+ */
+public class AndroidHttpClientConnection
+        implements HttpInetConnection, HttpConnection {
+
+    private SessionInputBuffer inbuffer = null;
+    private SessionOutputBuffer outbuffer = null;
+    private int maxHeaderCount;
+    // store CoreConnectionPNames.MAX_LINE_LENGTH for performance
+    private int maxLineLength;
+
+    private final EntitySerializer entityserializer;
+
+    private HttpMessageWriter requestWriter = null;
+    private HttpConnectionMetricsImpl metrics = null;
+    private volatile boolean open;
+    private Socket socket = null;
+
+    public AndroidHttpClientConnection() {
+        this.entityserializer =  new EntitySerializer(
+                new StrictContentLengthStrategy());
+    }
+
+    /**
+     * Bind socket and set HttpParams to AndroidHttpClientConnection
+     * @param socket outgoing socket
+     * @param params HttpParams
+     * @throws IOException
+      */
+    public void bind(
+            final Socket socket,
+            final HttpParams params) throws IOException {
+        if (socket == null) {
+            throw new IllegalArgumentException("Socket may not be null");
+        }
+        if (params == null) {
+            throw new IllegalArgumentException("HTTP parameters may not be null");
+        }
+        assertNotOpen();
+        socket.setTcpNoDelay(HttpConnectionParams.getTcpNoDelay(params));
+        socket.setSoTimeout(HttpConnectionParams.getSoTimeout(params));
+
+        int linger = HttpConnectionParams.getLinger(params);
+        if (linger >= 0) {
+            socket.setSoLinger(linger > 0, linger);
+        }
+        this.socket = socket;
+
+        int buffersize = HttpConnectionParams.getSocketBufferSize(params);
+        this.inbuffer = new SocketInputBuffer(socket, buffersize, params);
+        this.outbuffer = new SocketOutputBuffer(socket, buffersize, params);
+
+        maxHeaderCount = params.getIntParameter(
+                CoreConnectionPNames.MAX_HEADER_COUNT, -1);
+        maxLineLength = params.getIntParameter(
+                CoreConnectionPNames.MAX_LINE_LENGTH, -1);
+
+        this.requestWriter = new HttpRequestWriter(outbuffer, null, params);
+
+        this.metrics = new HttpConnectionMetricsImpl(
+                inbuffer.getMetrics(),
+                outbuffer.getMetrics());
+
+        this.open = true;
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder buffer = new StringBuilder();
+        buffer.append(getClass().getSimpleName()).append("[");
+        if (isOpen()) {
+            buffer.append(getRemotePort());
+        } else {
+            buffer.append("closed");
+        }
+        buffer.append("]");
+        return buffer.toString();
+    }
+
+
+    private void assertNotOpen() {
+        if (this.open) {
+            throw new IllegalStateException("Connection is already open");
+        }
+    }
+
+    private void assertOpen() {
+        if (!this.open) {
+            throw new IllegalStateException("Connection is not open");
+        }
+    }
+
+    public boolean isOpen() {
+        // to make this method useful, we want to check if the socket is connected
+        return (this.open && this.socket != null && this.socket.isConnected());
+    }
+
+    public InetAddress getLocalAddress() {
+        if (this.socket != null) {
+            return this.socket.getLocalAddress();
+        } else {
+            return null;
+        }
+    }
+
+    public int getLocalPort() {
+        if (this.socket != null) {
+            return this.socket.getLocalPort();
+        } else {
+            return -1;
+        }
+    }
+
+    public InetAddress getRemoteAddress() {
+        if (this.socket != null) {
+            return this.socket.getInetAddress();
+        } else {
+            return null;
+        }
+    }
+
+    public int getRemotePort() {
+        if (this.socket != null) {
+            return this.socket.getPort();
+        } else {
+            return -1;
+        }
+    }
+
+    public void setSocketTimeout(int timeout) {
+        assertOpen();
+        if (this.socket != null) {
+            try {
+                this.socket.setSoTimeout(timeout);
+            } catch (SocketException ignore) {
+                // It is not quite clear from the original documentation if there are any
+                // other legitimate cases for a socket exception to be thrown when setting
+                // SO_TIMEOUT besides the socket being already closed
+            }
+        }
+    }
+
+    public int getSocketTimeout() {
+        if (this.socket != null) {
+            try {
+                return this.socket.getSoTimeout();
+            } catch (SocketException ignore) {
+                return -1;
+            }
+        } else {
+            return -1;
+        }
+    }
+
+    public void shutdown() throws IOException {
+        this.open = false;
+        Socket tmpsocket = this.socket;
+        if (tmpsocket != null) {
+            tmpsocket.close();
+        }
+    }
+
+    public void close() throws IOException {
+        if (!this.open) {
+            return;
+        }
+        this.open = false;
+        doFlush();
+        try {
+            try {
+                this.socket.shutdownOutput();
+            } catch (IOException ignore) {
+            }
+            try {
+                this.socket.shutdownInput();
+            } catch (IOException ignore) {
+            }
+        } catch (UnsupportedOperationException ignore) {
+            // if one isn't supported, the other one isn't either
+        }
+        this.socket.close();
+    }
+
+    /**
+     * Sends the request line and all headers over the connection.
+     * @param request the request whose headers to send.
+     * @throws HttpException
+     * @throws IOException
+     */
+    public void sendRequestHeader(final HttpRequest request)
+            throws HttpException, IOException {
+        if (request == null) {
+            throw new IllegalArgumentException("HTTP request may not be null");
+        }
+        assertOpen();
+        this.requestWriter.write(request);
+        this.metrics.incrementRequestCount();
+    }
+
+    /**
+     * Sends the request entity over the connection.
+     * @param request the request whose entity to send.
+     * @throws HttpException
+     * @throws IOException
+     */
+    public void sendRequestEntity(final HttpEntityEnclosingRequest request)
+            throws HttpException, IOException {
+        if (request == null) {
+            throw new IllegalArgumentException("HTTP request may not be null");
+        }
+        assertOpen();
+        if (request.getEntity() == null) {
+            return;
+        }
+        this.entityserializer.serialize(
+                this.outbuffer,
+                request,
+                request.getEntity());
+    }
+
+    protected void doFlush() throws IOException {
+        this.outbuffer.flush();
+    }
+
+    public void flush() throws IOException {
+        assertOpen();
+        doFlush();
+    }
+
+    /**
+     * Parses the response headers and adds them to the
+     * given {@code headers} object, and returns the response StatusLine
+     * @param headers store parsed header to headers.
+     * @throws IOException
+     * @return StatusLine
+     * @see HttpClientConnection#receiveResponseHeader()
+      */
+    public StatusLine parseResponseHeader(Headers headers)
+            throws IOException, ParseException {
+        assertOpen();
+
+        CharArrayBuffer current = new CharArrayBuffer(64);
+
+        if (inbuffer.readLine(current) == -1) {
+            throw new NoHttpResponseException("The target server failed to respond");
+        }
+
+        // Create the status line from the status string
+        StatusLine statusline = BasicLineParser.DEFAULT.parseStatusLine(
+                current, new ParserCursor(0, current.length()));
+        
+        if (HttpLog.LOGV) HttpLog.v("read: " + statusline);
+        int statusCode = statusline.getStatusCode();
+
+        // Parse header body
+        CharArrayBuffer previous = null;
+        int headerNumber = 0;
+        while(true) {
+            if (current == null) {
+                current = new CharArrayBuffer(64);
+            } else {
+                // This must be he buffer used to parse the status
+                current.clear();
+            }
+            int l = inbuffer.readLine(current);
+            if (l == -1 || current.length() < 1) {
+                break;
+            }
+            // Parse the header name and value
+            // Check for folded headers first
+            // Detect LWS-char see HTTP/1.0 or HTTP/1.1 Section 2.2
+            // discussion on folded headers
+            char first = current.charAt(0);
+            if ((first == ' ' || first == '\t') && previous != null) {
+                // we have continuation folded header
+                // so append value
+                int start = 0;
+                int length = current.length();
+                while (start < length) {
+                    char ch = current.charAt(start);
+                    if (ch != ' ' && ch != '\t') {
+                        break;
+                    }
+                    start++;
+                }
+                if (maxLineLength > 0 &&
+                        previous.length() + 1 + current.length() - start >
+                            maxLineLength) {
+                    throw new IOException("Maximum line length limit exceeded");
+                }
+                previous.append(' ');
+                previous.append(current, start, current.length() - start);
+            } else {
+                if (previous != null) {
+                    headers.parseHeader(previous);
+                }
+                headerNumber++;
+                previous = current;
+                current = null;
+            }
+            if (maxHeaderCount > 0 && headerNumber >= maxHeaderCount) {
+                throw new IOException("Maximum header count exceeded");
+            }
+        }
+
+        if (previous != null) {
+            headers.parseHeader(previous);
+        }
+
+        if (statusCode >= 200) {
+            this.metrics.incrementResponseCount();
+        }
+        return statusline;
+    }
+
+    /**
+     * Return the next response entity.
+     * @param headers contains values for parsing entity
+     * @see HttpClientConnection#receiveResponseEntity(HttpResponse response)
+     */
+    public HttpEntity receiveResponseEntity(final Headers headers) {
+        assertOpen();
+        BasicHttpEntity entity = new BasicHttpEntity();
+
+        long len = determineLength(headers);
+        if (len == ContentLengthStrategy.CHUNKED) {
+            entity.setChunked(true);
+            entity.setContentLength(-1);
+            entity.setContent(new ChunkedInputStream(inbuffer));
+        } else if (len == ContentLengthStrategy.IDENTITY) {
+            entity.setChunked(false);
+            entity.setContentLength(-1);
+            entity.setContent(new IdentityInputStream(inbuffer));
+        } else {
+            entity.setChunked(false);
+            entity.setContentLength(len);
+            entity.setContent(new ContentLengthInputStream(inbuffer, len));
+        }
+
+        String contentTypeHeader = headers.getContentType();
+        if (contentTypeHeader != null) {
+            entity.setContentType(contentTypeHeader);
+        }
+        String contentEncodingHeader = headers.getContentEncoding();
+        if (contentEncodingHeader != null) {
+            entity.setContentEncoding(contentEncodingHeader);
+        }
+
+       return entity;
+    }
+
+    private long determineLength(final Headers headers) {
+        long transferEncoding = headers.getTransferEncoding();
+        // We use Transfer-Encoding if present and ignore Content-Length.
+        // RFC2616, 4.4 item number 3
+        if (transferEncoding < Headers.NO_TRANSFER_ENCODING) {
+            return transferEncoding;
+        } else {
+            long contentlen = headers.getContentLength();
+            if (contentlen > Headers.NO_CONTENT_LENGTH) {
+                return contentlen;
+            } else {
+                return ContentLengthStrategy.IDENTITY;
+            }
+        }
+    }
+
+    /**
+     * Checks whether this connection has gone down.
+     * Network connections may get closed during some time of inactivity
+     * for several reasons. The next time a read is attempted on such a
+     * connection it will throw an IOException.
+     * This method tries to alleviate this inconvenience by trying to
+     * find out if a connection is still usable. Implementations may do
+     * that by attempting a read with a very small timeout. Thus this
+     * method may block for a small amount of time before returning a result.
+     * It is therefore an <i>expensive</i> operation.
+     *
+     * @return  <code>true</code> if attempts to use this connection are
+     *          likely to succeed, or <code>false</code> if they are likely
+     *          to fail and this connection should be closed
+     */
+    public boolean isStale() {
+        assertOpen();
+        try {
+            this.inbuffer.isDataAvailable(1);
+            return false;
+        } catch (IOException ex) {
+            return true;
+        }
+    }
+
+    /**
+     * Returns a collection of connection metrcis
+     * @return HttpConnectionMetrics
+     */
+    public HttpConnectionMetrics getMetrics() {
+        return this.metrics;
+    }
+}
diff --git a/core/java/android/net/http/CertificateChainValidator.java b/core/java/android/net/http/CertificateChainValidator.java
new file mode 100644
index 0000000..0edbe5b
--- /dev/null
+++ b/core/java/android/net/http/CertificateChainValidator.java
@@ -0,0 +1,354 @@
+/*
+ * 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 android.net.http;
+
+import java.io.IOException;
+
+import java.security.cert.Certificate;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateExpiredException;
+import java.security.cert.CertificateNotYetValidException;
+import java.security.cert.X509Certificate;
+import java.security.GeneralSecurityException;
+import java.security.KeyStore;
+
+import javax.net.ssl.SSLHandshakeException;
+import javax.net.ssl.SSLSession;
+import javax.net.ssl.SSLSocket;
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.TrustManagerFactory;
+import javax.net.ssl.X509TrustManager;
+
+/**
+ * Class responsible for all server certificate validation functionality
+ * 
+ * {@hide}
+ */
+class CertificateChainValidator {
+
+    /**
+     * The singleton instance of the certificate chain validator
+     */
+    private static CertificateChainValidator sInstance;
+
+    /**
+     * Default trust manager (used to perform CA certificate validation)
+     */
+    private X509TrustManager mDefaultTrustManager;
+
+    /**
+     * @return The singleton instance of the certificator chain validator
+     */
+    public static CertificateChainValidator getInstance() {
+        if (sInstance == null) {
+            sInstance = new CertificateChainValidator();
+        }
+
+        return sInstance;
+    }
+
+    /**
+     * Creates a new certificate chain validator. This is a pivate constructor.
+     * If you need a Certificate chain validator, call getInstance().
+     */
+    private CertificateChainValidator() {
+        try {
+            TrustManagerFactory trustManagerFactory
+                = TrustManagerFactory.getInstance("X509");
+            trustManagerFactory.init((KeyStore)null);
+            TrustManager[] trustManagers =
+                trustManagerFactory.getTrustManagers();
+            if (trustManagers != null && trustManagers.length > 0) {
+                for (TrustManager trustManager : trustManagers) {
+                    if (trustManager instanceof X509TrustManager) {
+                        mDefaultTrustManager = (X509TrustManager)(trustManager);
+                        break;
+                    }
+                }
+            }
+        } catch (Exception exc) {
+            if (HttpLog.LOGV) {
+                HttpLog.v("CertificateChainValidator():" +
+                          " failed to initialize the trust manager");
+            }
+        }
+    }
+
+    /**
+     * Performs the handshake and server certificates validation
+     * @param sslSocket The secure connection socket
+     * @param domain The website domain
+     * @return An SSL error object if there is an error and null otherwise
+     */
+    public SslError doHandshakeAndValidateServerCertificates(
+            HttpsConnection connection, SSLSocket sslSocket, String domain)
+            throws IOException {
+        X509Certificate[] serverCertificates = null;
+
+        // start handshake, close the socket if we fail
+        try {
+            sslSocket.setUseClientMode(true);
+            sslSocket.startHandshake();
+        } catch (IOException e) {
+            closeSocketThrowException(
+                sslSocket, e.getMessage(),
+                "failed to perform SSL handshake");
+        }
+
+        // retrieve the chain of the server peer certificates
+        Certificate[] peerCertificates =
+            sslSocket.getSession().getPeerCertificates();
+
+        if (peerCertificates == null || peerCertificates.length <= 0) {
+            closeSocketThrowException(
+                sslSocket, "failed to retrieve peer certificates");
+        } else {
+            serverCertificates =
+                new X509Certificate[peerCertificates.length];
+            for (int i = 0; i < peerCertificates.length; ++i) {
+                serverCertificates[i] =
+                    (X509Certificate)(peerCertificates[i]);
+            }
+
+            // update the SSL certificate associated with the connection
+            if (connection != null) {
+                if (serverCertificates[0] != null) {
+                    connection.setCertificate(
+                        new SslCertificate(serverCertificates[0]));
+                }
+            }
+        }
+
+        // check if the first certificate in the chain is for this site
+        X509Certificate currCertificate = serverCertificates[0];
+        if (currCertificate == null) {
+            closeSocketThrowException(
+                sslSocket, "certificate for this site is null");
+        } else {
+            if (!DomainNameChecker.match(currCertificate, domain)) {
+                String errorMessage = "certificate not for this host: " + domain;
+
+                if (HttpLog.LOGV) {
+                    HttpLog.v(errorMessage);
+                }
+
+                sslSocket.getSession().invalidate();
+                return new SslError(
+                    SslError.SSL_IDMISMATCH, currCertificate);
+            }
+        }
+
+        // first, we validate the chain using the standard validation
+        // solution; if we do not find any errors, we are done; if we
+        // fail the standard validation, we re-validate again below,
+        // this time trying to retrieve any individual errors we can
+        // report back to the user.
+        //
+        try {
+            synchronized (mDefaultTrustManager) {
+                mDefaultTrustManager.checkServerTrusted(
+                    serverCertificates, "RSA");
+
+                // no errors!!!
+                return null;
+            }
+        } catch (CertificateException e) {
+            if (HttpLog.LOGV) {
+                HttpLog.v(
+                    "failed to pre-validate the certificate chain, error: " +
+                    e.getMessage());
+            }
+        }
+
+        sslSocket.getSession().invalidate();
+
+        SslError error = null;
+
+        // we check the root certificate separately from the rest of the
+        // chain; this is because we need to know what certificate in
+        // the chain resulted in an error if any
+        currCertificate =
+            serverCertificates[serverCertificates.length - 1];
+        if (currCertificate == null) {
+            closeSocketThrowException(
+                sslSocket, "root certificate is null");
+        }
+
+        // check if the last certificate in the chain (root) is trusted
+        X509Certificate[] rootCertificateChain = { currCertificate };
+        try {
+            synchronized (mDefaultTrustManager) {
+                mDefaultTrustManager.checkServerTrusted(
+                    rootCertificateChain, "RSA");
+            }
+        } catch (CertificateExpiredException e) {
+            String errorMessage = e.getMessage();
+            if (errorMessage == null) {
+                errorMessage = "root certificate has expired";
+            }
+
+            if (HttpLog.LOGV) {
+                HttpLog.v(errorMessage);
+            }
+
+            error = new SslError(
+                SslError.SSL_EXPIRED, currCertificate);
+        } catch (CertificateNotYetValidException e) {
+            String errorMessage = e.getMessage();
+            if (errorMessage == null) {
+                errorMessage = "root certificate not valid yet";
+            }
+
+            if (HttpLog.LOGV) {
+                HttpLog.v(errorMessage);
+            }
+
+            error = new SslError(
+                SslError.SSL_NOTYETVALID, currCertificate);
+        } catch (CertificateException e) {
+            String errorMessage = e.getMessage();
+            if (errorMessage == null) {
+                errorMessage = "root certificate not trusted";
+            }
+
+            if (HttpLog.LOGV) {
+                HttpLog.v(errorMessage);
+            }
+
+            return new SslError(
+                SslError.SSL_UNTRUSTED, currCertificate);
+        }
+
+        // Then go through the certificate chain checking that each
+        // certificate trusts the next and that each certificate is
+        // within its valid date range. Walk the chain in the order
+        // from the CA to the end-user
+        X509Certificate prevCertificate =
+            serverCertificates[serverCertificates.length - 1];
+
+        for (int i = serverCertificates.length - 2; i >= 0; --i) {
+            currCertificate = serverCertificates[i];
+
+            // if a certificate is null, we cannot verify the chain
+            if (currCertificate == null) {
+                closeSocketThrowException(
+                    sslSocket, "null certificate in the chain");
+            }
+
+            // verify if trusted by chain
+            if (!prevCertificate.getSubjectDN().equals(
+                    currCertificate.getIssuerDN())) {
+                String errorMessage = "not trusted by chain";
+
+                if (HttpLog.LOGV) {
+                    HttpLog.v(errorMessage);
+                }
+
+                return new SslError(
+                    SslError.SSL_UNTRUSTED, currCertificate);
+            }
+
+            try {
+                currCertificate.verify(prevCertificate.getPublicKey());
+            } catch (GeneralSecurityException e) {
+                String errorMessage = e.getMessage();
+                if (errorMessage == null) {
+                    errorMessage = "not trusted by chain";
+                }
+
+                if (HttpLog.LOGV) {
+                    HttpLog.v(errorMessage);
+                }
+
+                return new SslError(
+                    SslError.SSL_UNTRUSTED, currCertificate);
+            }
+
+            // verify if the dates are valid
+            try {
+              currCertificate.checkValidity();
+            } catch (CertificateExpiredException e) {
+                String errorMessage = e.getMessage();
+                if (errorMessage == null) {
+                    errorMessage = "certificate expired";
+                }
+
+                if (HttpLog.LOGV) {
+                    HttpLog.v(errorMessage);
+                }
+
+                if (error == null ||
+                    error.getPrimaryError() < SslError.SSL_EXPIRED) {
+                    error = new SslError(
+                        SslError.SSL_EXPIRED, currCertificate);
+                }
+            } catch (CertificateNotYetValidException e) {
+                String errorMessage = e.getMessage();
+                if (errorMessage == null) {
+                    errorMessage = "certificate not valid yet";
+                }
+
+                if (HttpLog.LOGV) {
+                    HttpLog.v(errorMessage);
+                }
+
+                if (error == null ||
+                    error.getPrimaryError() < SslError.SSL_NOTYETVALID) {
+                    error = new SslError(
+                        SslError.SSL_NOTYETVALID, currCertificate);
+                }
+            }
+
+            prevCertificate = currCertificate;
+        }
+
+        // if we do not have an error to report back to the user, throw
+        // an exception (a generic error will be reported instead)
+        if (error == null) {
+            closeSocketThrowException(
+                sslSocket,
+                "failed to pre-validate the certificate chain due to a non-standard error");
+        }
+
+        return error;
+    }
+
+    private void closeSocketThrowException(
+            SSLSocket socket, String errorMessage, String defaultErrorMessage)
+            throws IOException {
+        closeSocketThrowException(
+            socket, errorMessage != null ? errorMessage : defaultErrorMessage);
+    }
+
+    private void closeSocketThrowException(SSLSocket socket,
+            String errorMessage) throws IOException {
+        if (HttpLog.LOGV) {
+            HttpLog.v("validation error: " + errorMessage);
+        }
+
+        if (socket != null) {
+            SSLSession session = socket.getSession();
+            if (session != null) {
+                session.invalidate();
+            }
+
+            socket.close();
+        }
+
+        throw new SSLHandshakeException(errorMessage);
+    }
+}
diff --git a/core/java/android/net/http/CertificateValidatorCache.java b/core/java/android/net/http/CertificateValidatorCache.java
new file mode 100644
index 0000000..54a1dbe
--- /dev/null
+++ b/core/java/android/net/http/CertificateValidatorCache.java
@@ -0,0 +1,254 @@
+/*
+ * 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 android.net.http;
+
+import android.os.SystemClock;
+
+import android.security.Sha1MessageDigest;
+
+import java.security.cert.Certificate;
+import java.security.cert.CertificateFactory;
+import java.security.cert.CertPath;
+import java.security.GeneralSecurityException;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Random;
+
+
+/**
+ * Validator cache used to speed-up certificate chain validation. The idea is
+ * to keep each secure domain name associated with a cryptographically secure
+ * hash of the certificate chain successfully used to validate the domain. If
+ * we establish connection with the domain more than once and each time receive
+ * the same list of certificates, we do not have to re-validate.
+ * 
+ * {@hide}
+ */
+class CertificateValidatorCache {
+
+    // TODO: debug only!
+    public static long mSave = 0;
+    public static long mCost = 0;
+    // TODO: debug only!
+
+    /**
+     * The cache-entry lifetime in milliseconds (here, 10 minutes)
+     */
+    private static final long CACHE_ENTRY_LIFETIME = 10 * 60 * 1000;
+
+    /**
+     * The certificate factory
+     */
+    private static CertificateFactory sCertificateFactory;
+
+    /**
+     * The certificate validator cache map (domain to a cache entry)
+     */
+    private HashMap<Integer, CacheEntry> mCacheMap;
+
+    /**
+     * Random salt
+     */
+    private int mBigScrew;
+
+    /**
+     * @param certificate The array of server certificates to compute a
+     * secure hash from
+     * @return The secure hash computed from server certificates
+     */
+    public static byte[] secureHash(Certificate[] certificates) {
+        byte[] secureHash = null;
+
+        // TODO: debug only!
+        long beg = SystemClock.uptimeMillis();
+        // TODO: debug only!
+
+        if (certificates != null && certificates.length != 0) {
+            byte[] encodedCertPath = null;
+            try {
+                synchronized (CertificateValidatorCache.class) {
+                    if (sCertificateFactory == null) {
+                        try {
+                            sCertificateFactory =
+                                CertificateFactory.getInstance("X.509");
+                        } catch(GeneralSecurityException e) {
+                            if (HttpLog.LOGV) {
+                                HttpLog.v("CertificateValidatorCache:" +
+                                          " failed to create the certificate factory");
+                            }
+                        }
+                    }
+                }
+
+                CertPath certPath =
+                    sCertificateFactory.generateCertPath(Arrays.asList(certificates));
+                if (certPath != null) {
+                    encodedCertPath = certPath.getEncoded();
+                    if (encodedCertPath != null) {
+                      Sha1MessageDigest messageDigest =
+                          new Sha1MessageDigest();
+                      secureHash = messageDigest.digest(encodedCertPath);
+                    }
+                }
+            } catch (GeneralSecurityException e) {}
+        }
+
+        // TODO: debug only!
+        long end = SystemClock.uptimeMillis();
+        mCost += (end - beg);
+        // TODO: debug only!
+
+        return secureHash;
+    }
+
+    /**
+     * Creates a new certificate-validator cache
+     */
+    public CertificateValidatorCache() {
+        Random random = new Random();
+        mBigScrew = random.nextInt();
+
+        mCacheMap = new HashMap<Integer, CacheEntry>();
+    }
+
+     /**
+     * @param domain The domain to check against
+     * @param secureHash The secure hash to check against
+     * @return True iff there is a valid (not expired) cache entry
+     * associated with the domain and the secure hash
+     */
+    public boolean has(String domain, byte[] secureHash) {
+        boolean rval = false;
+
+        if (domain != null && domain.length() != 0) {
+            if (secureHash != null && secureHash.length != 0) {
+                CacheEntry cacheEntry = (CacheEntry)mCacheMap.get(
+                    new Integer(mBigScrew ^ domain.hashCode()));
+                if (cacheEntry != null) {
+                    if (!cacheEntry.expired()) {
+                        rval = cacheEntry.has(domain, secureHash);
+                        // TODO: debug only!
+                        if (rval) {
+                            mSave += cacheEntry.mSave;
+                        }
+                        // TODO: debug only!
+                    } else {
+                        mCacheMap.remove(cacheEntry);
+                    }
+                }
+            }
+        }
+
+        return rval;
+    }
+
+    /**
+     * Adds the (domain, secureHash) tuple to the cache
+     * @param domain The domain to be added to the cache
+     * @param secureHash The secure hash to be added to the cache
+     * @return True iff succeeds
+     */
+    public boolean put(String domain, byte[] secureHash, long save) {
+        if (domain != null && domain.length() != 0) {
+            if (secureHash != null && secureHash.length != 0) {
+                mCacheMap.put(
+                    new Integer(mBigScrew ^ domain.hashCode()),
+                    new CacheEntry(domain, secureHash, save));
+
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    /**
+     * Certificate-validator cache entry. We have one per domain
+     */
+    private class CacheEntry {
+
+        /**
+         * The hash associated with this cache entry
+         */
+        private byte[] mHash;
+
+        /**
+         * The time associated with this cache entry
+         */
+        private long mTime;
+
+        // TODO: debug only!
+        public long mSave;
+        // TODO: debug only!
+
+        /**
+         * The host associated with this cache entry
+         */
+        private String mDomain;
+
+        /**
+         * Creates a new certificate-validator cache entry
+         * @param domain The domain to be associated with this cache entry
+         * @param secureHash The secure hash to be associated with this cache
+         * entry
+         */
+        public CacheEntry(String domain, byte[] secureHash, long save) {
+            mDomain = domain;
+            mHash = secureHash;
+            // TODO: debug only!
+            mSave = save;
+            // TODO: debug only!
+            mTime = SystemClock.uptimeMillis();
+        }
+
+        /**
+         * @return True iff the cache item has expired
+         */
+        public boolean expired() {
+            return CACHE_ENTRY_LIFETIME < SystemClock.uptimeMillis() - mTime;
+        }
+
+        /**
+         * @param domain The domain to check
+         * @param secureHash The secure hash to check
+         * @return True iff the given domain and hash match those associated
+         * with this entry
+         */
+        public boolean has(String domain, byte[] secureHash) {
+            if (domain != null && 0 < domain.length()) {
+                if (!mDomain.equals(domain)) {
+                    return false;
+                }
+            }
+
+            int hashLength = secureHash.length;
+            if (secureHash != null && 0 < hashLength) {
+                if (hashLength == mHash.length) {
+                    for (int i = 0; i < hashLength; ++i) {
+                        if (secureHash[i] != mHash[i]) {
+                            return false;
+                        }
+                    }
+                    return true;
+                }
+            }
+
+            return false;
+        }
+    }
+};
diff --git a/core/java/android/net/http/CharArrayBuffers.java b/core/java/android/net/http/CharArrayBuffers.java
new file mode 100644
index 0000000..77d45f6
--- /dev/null
+++ b/core/java/android/net/http/CharArrayBuffers.java
@@ -0,0 +1,89 @@
+/*
+ * 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 android.net.http;
+
+import org.apache.http.util.CharArrayBuffer;
+import org.apache.http.protocol.HTTP;
+
+/**
+ * Utility methods for working on CharArrayBuffers.
+ * 
+ * {@hide}
+ */
+class CharArrayBuffers {
+
+    static final char uppercaseAddon = 'a' - 'A';
+
+    /**
+     * Returns true if the buffer contains the given string. Ignores leading
+     * whitespace and case.
+     *
+     * @param buffer to search
+     * @param beginIndex index at which we should start
+     * @param str to search for
+     */
+    static boolean containsIgnoreCaseTrimmed(CharArrayBuffer buffer,
+            int beginIndex, final String str) {
+        int len = buffer.length();
+        char[] chars = buffer.buffer();
+        while (beginIndex < len && HTTP.isWhitespace(chars[beginIndex])) {
+            beginIndex++;
+        }
+        int size = str.length();
+        boolean ok = len >= beginIndex + size;
+        for (int j=0; ok && (j<size); j++) {
+            char a = chars[beginIndex+j];
+            char b = str.charAt(j);
+            if (a != b) {
+                a = toLower(a);
+                b = toLower(b);
+                ok = a == b;
+            }
+        }
+        return ok;
+    }
+
+    /**
+     * Returns index of first occurence ch. Lower cases characters leading up
+     * to first occurrence of ch.
+     */
+    static int setLowercaseIndexOf(CharArrayBuffer buffer, final int ch) {
+
+        int beginIndex = 0;
+        int endIndex = buffer.length();
+        char[] chars = buffer.buffer();
+
+        for (int i = beginIndex; i < endIndex; i++) {
+            char current = chars[i];
+            if (current == ch) {
+                return i;
+            } else if (current >= 'A' && current <= 'Z'){
+                // make lower case
+                current += uppercaseAddon;
+                chars[i] = current;
+            }
+        }
+        return -1;
+    }
+
+    private static char toLower(char c) {
+        if (c >= 'A' && c <= 'Z'){
+            c += uppercaseAddon;
+        }
+        return c;
+    }
+}
diff --git a/core/java/android/net/http/Connection.java b/core/java/android/net/http/Connection.java
new file mode 100644
index 0000000..563634f
--- /dev/null
+++ b/core/java/android/net/http/Connection.java
@@ -0,0 +1,528 @@
+/*
+ * Copyright (C) 2007 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 android.net.http;
+
+import android.content.Context;
+import android.os.SystemClock;
+
+import java.io.IOException;
+import java.net.UnknownHostException;
+import java.util.ListIterator;
+import java.util.LinkedList;
+
+import javax.net.ssl.SSLHandshakeException;
+
+import org.apache.http.ConnectionReuseStrategy;
+import org.apache.http.HttpEntity;
+import org.apache.http.HttpException;
+import org.apache.http.HttpHost;
+import org.apache.http.HttpVersion;
+import org.apache.http.ParseException;
+import org.apache.http.ProtocolVersion;
+import org.apache.http.protocol.ExecutionContext;
+import org.apache.http.protocol.HttpContext;
+import org.apache.http.protocol.BasicHttpContext;
+
+/**
+ * {@hide}
+ */
+abstract class Connection {
+
+    /**
+     * Allow a TCP connection 60 idle seconds before erroring out
+     */
+    static final int SOCKET_TIMEOUT = 60000;
+
+    private static final int SEND = 0;
+    private static final int READ = 1;
+    private static final int DRAIN = 2;
+    private static final int DONE = 3;
+    private static final String[] states = {"SEND",  "READ", "DRAIN", "DONE"};
+
+    Context mContext;
+
+    /** The low level connection */
+    protected AndroidHttpClientConnection mHttpClientConnection = null;
+
+    /**
+     * The server SSL certificate associated with this connection
+     * (null if the connection is not secure)
+     * It would be nice to store the whole certificate chain, but
+     * we want to keep things as light-weight as possible
+     */
+    protected SslCertificate mCertificate = null;
+
+    /**
+     * The host this connection is connected to.  If using proxy,
+     * this is set to the proxy address
+     */
+    HttpHost mHost;
+
+    /** true if the connection can be reused for sending more requests */
+    private boolean mCanPersist;
+
+    /** context required by ConnectionReuseStrategy. */
+    private HttpContext mHttpContext;
+
+    /** set when cancelled */
+    private static int STATE_NORMAL = 0;
+    private static int STATE_CANCEL_REQUESTED = 1;
+    private int mActive = STATE_NORMAL;
+
+    /** The number of times to try to re-connect (if connect fails). */
+    private final static int RETRY_REQUEST_LIMIT = 2;
+
+    private static final int MIN_PIPE = 2;
+    private static final int MAX_PIPE = 3;
+
+    /**
+     * Doesn't seem to exist anymore in the new HTTP client, so copied here.
+     */
+    private static final String HTTP_CONNECTION = "http.connection";
+
+    RequestQueue.ConnectionManager mConnectionManager;
+    RequestFeeder mRequestFeeder;
+
+    /**
+     * Buffer for feeding response blocks to webkit.  One block per
+     * connection reduces memory churn.
+     */
+    private byte[] mBuf;
+
+    protected Connection(Context context, HttpHost host,
+                         RequestQueue.ConnectionManager connectionManager,
+                         RequestFeeder requestFeeder) {
+        mContext = context;
+        mHost = host;
+        mConnectionManager = connectionManager;
+        mRequestFeeder = requestFeeder;
+
+        mCanPersist = false;
+        mHttpContext = new BasicHttpContext(null);
+    }
+
+    HttpHost getHost() {
+        return mHost;
+    }
+
+    /**
+     * connection factory: returns an HTTP or HTTPS connection as
+     * necessary
+     */
+    static Connection getConnection(
+            Context context, HttpHost host,
+            RequestQueue.ConnectionManager connectionManager,
+            RequestFeeder requestFeeder) {
+
+        if (host.getSchemeName().equals("http")) {
+            return new HttpConnection(context, host, connectionManager,
+                                      requestFeeder);
+        }
+
+        // Otherwise, default to https
+        return new HttpsConnection(context, host, connectionManager,
+                                   requestFeeder);
+    }
+
+    /**
+     * @return The server SSL certificate associated with this
+     * connection (null if the connection is not secure)
+     */
+    /* package */ SslCertificate getCertificate() {
+        return mCertificate;
+    }
+
+    /**
+     * Close current network connection
+     * Note: this runs in non-network thread
+     */
+    void cancel() {
+        mActive = STATE_CANCEL_REQUESTED;
+        closeConnection();
+        if (HttpLog.LOGV) HttpLog.v(
+            "Connection.cancel(): connection closed " + mHost);
+    }
+
+    /**
+     * Process requests in queue
+     * pipelines requests
+     */
+    void processRequests(Request firstRequest) {
+        Request req = null;
+        boolean empty;
+        int error = EventHandler.OK;
+        Exception exception = null;
+
+        LinkedList<Request> pipe = new LinkedList<Request>();
+
+        int minPipe = MIN_PIPE, maxPipe = MAX_PIPE;
+        int state = SEND;
+
+        while (state != DONE) {
+            if (HttpLog.LOGV) HttpLog.v(
+                    states[state] + " pipe " + pipe.size());
+
+            /* If a request was cancelled, give other cancel requests
+               some time to go through so we don't uselessly restart
+               connections */
+            if (mActive == STATE_CANCEL_REQUESTED) {
+                try {
+                    Thread.sleep(100);
+                } catch (InterruptedException x) { /* ignore */ }
+                mActive = STATE_NORMAL;
+            }
+
+            switch (state) {
+                case SEND: {
+                    if (pipe.size() == maxPipe) {
+                        state = READ;
+                        break;
+                    }
+                    /* get a request */
+                    if (firstRequest == null) {
+                        req = mRequestFeeder.getRequest(mHost);
+                    } else {
+                        req = firstRequest;
+                        firstRequest = null;
+                    }
+                    if (req == null) {
+                        state = DRAIN;
+                        break;
+                    }
+                    req.setConnection(this);
+
+                    /* Don't work on cancelled requests. */
+                    if (req.mCancelled) {
+                        if (HttpLog.LOGV) HttpLog.v(
+                                "processRequests(): skipping cancelled request "
+                                + req);
+                        req.complete();
+                        break;
+                    }
+
+                    if (mHttpClientConnection == null ||
+                        !mHttpClientConnection.isOpen()) {
+                        /* If this call fails, the address is bad or
+                           the net is down.  Punt for now.
+
+                           FIXME: blow out entire queue here on
+                           connection failure if net up? */
+
+                        if (!openHttpConnection(req)) {
+                            state = DONE;
+                            break;
+                        }
+                    }
+
+                    try {
+                        /* FIXME: don't increment failure count if old
+                           connection?  There should not be a penalty for
+                           attempting to reuse an old connection */
+                        req.sendRequest(mHttpClientConnection);
+                    } catch (HttpException e) {
+                        exception = e;
+                        error = EventHandler.ERROR;
+                    } catch (IOException e) {
+                        exception = e;
+                        error = EventHandler.ERROR_IO;
+                    } catch (IllegalStateException e) {
+                        exception = e;
+                        error = EventHandler.ERROR_IO;
+                    }
+                    if (exception != null) {
+                        if (httpFailure(req, error, exception) &&
+                            !req.mCancelled) {
+                            /* retry request if not permanent failure
+                               or cancelled */
+                            pipe.addLast(req);
+                        }
+                        exception = null;
+                        state = (clearPipe(pipe) ||
+                                 !mConnectionManager.isNetworkConnected()) ?
+                                DONE : SEND;
+                        minPipe = maxPipe = 1;
+                        break;
+                    }
+
+                    pipe.addLast(req);
+                    if (!mCanPersist) state = READ;
+                    break;
+
+                }
+                case DRAIN:
+                case READ: {
+                    empty = !mRequestFeeder.haveRequest(mHost);
+                    int pipeSize = pipe.size();
+                    if (state != DRAIN && pipeSize < minPipe &&
+                        !empty && mCanPersist) {
+                        state = SEND;
+                        break;
+                    } else if (pipeSize == 0) {
+                        /* Done if no other work to do */
+                        state = empty ? DONE : SEND;
+                        break;
+                    }
+
+                    req = (Request)pipe.removeFirst();
+                    if (HttpLog.LOGV) HttpLog.v(
+                            "processRequests() reading " + req);
+
+                    try {
+                        req.readResponse(mHttpClientConnection);
+                    } catch (ParseException e) {
+                        exception = e;
+                        error = EventHandler.ERROR_IO;
+                    } catch (IOException e) {
+                        exception = e;
+                        error = EventHandler.ERROR_IO;
+                    } catch (IllegalStateException e) {
+                        exception = e;
+                        error = EventHandler.ERROR_IO;
+                    }
+                    if (exception != null) {
+                        if (httpFailure(req, error, exception) &&
+                            !req.mCancelled) {
+                            /* retry request if not permanent failure
+                               or cancelled */
+                            req.reset();
+                            pipe.addFirst(req);
+                        }
+                        exception = null;
+                        mCanPersist = false;
+                    }
+                    if (!mCanPersist) {
+                        if (HttpLog.LOGV) HttpLog.v(
+                                "processRequests(): no persist, closing " +
+                                mHost);
+
+                        closeConnection();
+
+                        mHttpContext.removeAttribute(HTTP_CONNECTION);
+                        clearPipe(pipe);
+                        minPipe = maxPipe = 1;
+                        /* If network active continue to service this queue */
+                        state = mConnectionManager.isNetworkConnected() ?
+                                SEND : DONE;
+                    }
+                    break;
+                }
+            }
+        }
+    }
+
+    /**
+     * After a send/receive failure, any pipelined requests must be
+     * cleared back to the mRequest queue
+     * @return true if mRequests is empty after pipe cleared
+     */
+    private boolean clearPipe(LinkedList<Request> pipe) {
+        boolean empty = true;
+        if (HttpLog.LOGV) HttpLog.v(
+                "Connection.clearPipe(): clearing pipe " + pipe.size());
+        synchronized (mRequestFeeder) {
+            Request tReq;
+            while (!pipe.isEmpty()) {
+                tReq = (Request)pipe.removeLast();
+                if (HttpLog.LOGV) HttpLog.v(
+                        "clearPipe() adding back " + mHost + " " + tReq);
+                mRequestFeeder.requeueRequest(tReq);
+                empty = false;
+            }
+            if (empty) empty = mRequestFeeder.haveRequest(mHost);
+        }
+        return empty;
+    }
+
+    /**
+     * @return true on success
+     */
+    private boolean openHttpConnection(Request req) {
+
+        long now = SystemClock.uptimeMillis();
+        int error = EventHandler.OK;
+        Exception exception = null;
+
+        try {
+            // reset the certificate to null before opening a connection
+            mCertificate = null;
+            mHttpClientConnection = openConnection(req);
+            if (mHttpClientConnection != null) {
+                mHttpClientConnection.setSocketTimeout(SOCKET_TIMEOUT);
+                mHttpContext.setAttribute(HTTP_CONNECTION,
+                                          mHttpClientConnection);
+            } else {
+                // we tried to do SSL tunneling, failed,
+                // and need to drop the request;
+                // we have already informed the handler
+                req.mFailCount = RETRY_REQUEST_LIMIT;
+                return false;
+            }
+        } catch (UnknownHostException e) {
+            if (HttpLog.LOGV) HttpLog.v("Failed to open connection");
+            error = EventHandler.ERROR_LOOKUP;
+            exception = e;
+        } catch (IllegalArgumentException e) {
+            if (HttpLog.LOGV) HttpLog.v("Illegal argument exception");
+            error = EventHandler.ERROR_CONNECT;
+            req.mFailCount = RETRY_REQUEST_LIMIT;
+            exception = e;
+        } catch (SSLConnectionClosedByUserException e) {
+            // hack: if we have an SSL connection failure,
+            // we don't want to reconnect
+            req.mFailCount = RETRY_REQUEST_LIMIT;
+            // no error message
+            return false;
+        } catch (SSLHandshakeException e) {
+            // hack: if we have an SSL connection failure,
+            // we don't want to reconnect
+            req.mFailCount = RETRY_REQUEST_LIMIT;
+            if (HttpLog.LOGV) HttpLog.v(
+                    "SSL exception performing handshake");
+            error = EventHandler.ERROR_FAILED_SSL_HANDSHAKE;
+            exception = e;
+        } catch (IOException e) {
+            error = EventHandler.ERROR_CONNECT;
+            exception = e;
+        }
+
+        if (HttpLog.LOGV) {
+            long now2 = SystemClock.uptimeMillis();
+            HttpLog.v("Connection.openHttpConnection() " +
+                      (now2 - now) + " " + mHost);
+        }
+
+        if (error == EventHandler.OK) {
+            return true;
+        } else {
+            if (mConnectionManager.isNetworkConnected() == false ||
+                req.mFailCount < RETRY_REQUEST_LIMIT) {
+                // requeue
+                mRequestFeeder.requeueRequest(req);
+                req.mFailCount++;
+            } else {
+                httpFailure(req, error, exception);
+            }
+            return error == EventHandler.OK;
+        }
+    }
+
+    /**
+     * Helper.  Calls the mEventHandler's error() method only if
+     * request failed permanently.  Increments mFailcount on failure.
+     *
+     * Increments failcount only if the network is believed to be
+     * connected
+     *
+     * @return true if request can be retried (less than
+     * RETRY_REQUEST_LIMIT failures have occurred).
+     */
+    private boolean httpFailure(Request req, int errorId, Exception e) {
+        boolean ret = true;
+        boolean networkConnected = mConnectionManager.isNetworkConnected();
+
+        // e.printStackTrace();
+        if (HttpLog.LOGV) HttpLog.v(
+                "httpFailure() ******* " + e + " count " + req.mFailCount +
+                " networkConnected " + networkConnected + " " + mHost + " " + req.getUri());
+
+        if (networkConnected && ++req.mFailCount >= RETRY_REQUEST_LIMIT) {
+            ret = false;
+            String error;
+            if (errorId < 0) {
+                error = mContext.getText(
+                        EventHandler.errorStringResources[-errorId]).toString();
+            } else {
+                Throwable cause = e.getCause();
+                error = cause != null ? cause.toString() : e.getMessage();
+            }
+            req.mEventHandler.error(errorId, error);
+            req.complete();
+        }
+
+        closeConnection();
+        mHttpContext.removeAttribute(HTTP_CONNECTION);
+
+        return ret;
+    }
+
+    HttpContext getHttpContext() {
+        return mHttpContext;
+    }
+
+    /**
+     * Use same logic as ConnectionReuseStrategy
+     * @see ConnectionReuseStrategy
+     */
+    private boolean keepAlive(HttpEntity entity,
+            ProtocolVersion ver, int connType, final HttpContext context) {
+        org.apache.http.HttpConnection conn = (org.apache.http.HttpConnection)
+            context.getAttribute(ExecutionContext.HTTP_CONNECTION);
+
+        if (conn != null && !conn.isOpen())
+            return false;
+        // do NOT check for stale connection, that is an expensive operation
+
+        if (entity != null) {
+            if (entity.getContentLength() < 0) {
+                if (!entity.isChunked() || ver.lessEquals(HttpVersion.HTTP_1_0)) {
+                    // if the content length is not known and is not chunk
+                    // encoded, the connection cannot be reused
+                    return false;
+                }
+            }
+        }
+        // Check for 'Connection' directive
+        if (connType == Headers.CONN_CLOSE) {
+            return false;
+        } else if (connType == Headers.CONN_KEEP_ALIVE) {
+            return true;
+        }
+        // Resorting to protocol version default close connection policy
+        return !ver.lessEquals(HttpVersion.HTTP_1_0);
+    }
+
+    void setCanPersist(HttpEntity entity, ProtocolVersion ver, int connType) {
+        mCanPersist = keepAlive(entity, ver, connType, mHttpContext);
+    }
+
+    void setCanPersist(boolean canPersist) {
+        mCanPersist = canPersist;
+    }
+
+    boolean getCanPersist() {
+        return mCanPersist;
+    }
+
+    /** typically http or https... set by subclass */
+    abstract String getScheme();
+    abstract void closeConnection();
+    abstract AndroidHttpClientConnection openConnection(Request req) throws IOException;
+
+    /**
+     * Prints request queue to log, for debugging.
+     * returns request count
+     */
+    public synchronized String toString() {
+        return mHost.toString();
+    }
+
+    byte[] getBuf() {
+        if (mBuf == null) mBuf = new byte[8192];
+        return mBuf;
+    }
+
+}
diff --git a/core/java/android/net/http/ConnectionThread.java b/core/java/android/net/http/ConnectionThread.java
new file mode 100644
index 0000000..8e759e2
--- /dev/null
+++ b/core/java/android/net/http/ConnectionThread.java
@@ -0,0 +1,137 @@
+/*
+ * 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 android.net.http;
+
+import android.content.Context;
+import android.os.SystemClock;
+
+import org.apache.http.HttpHost;
+
+import java.lang.Thread;
+
+/**
+ * {@hide}
+ */
+class ConnectionThread extends Thread {
+
+    static final int WAIT_TIMEOUT = 5000;
+    static final int WAIT_TICK = 1000;
+
+    // Performance probe
+    long mStartThreadTime;
+    long mCurrentThreadTime;
+
+    private boolean mWaiting;
+    private volatile boolean mRunning = true;
+    private Context mContext;
+    private RequestQueue.ConnectionManager mConnectionManager;
+    private RequestFeeder mRequestFeeder;
+
+    private int mId;
+    Connection mConnection;
+
+    ConnectionThread(Context context,
+                     int id,
+                     RequestQueue.ConnectionManager connectionManager,
+                     RequestFeeder requestFeeder) {
+        super();
+        mContext = context;
+        setName("http" + id);
+        mId = id;
+        mConnectionManager = connectionManager;
+        mRequestFeeder = requestFeeder;
+    }
+
+    void requestStop() {
+        synchronized (mRequestFeeder) {
+            mRunning = false;
+            mRequestFeeder.notify();
+        }
+    }
+
+    /**
+     * Loop until app shutdown. Runs connections in priority
+     * order.
+     */
+    public void run() {
+        android.os.Process.setThreadPriority(
+                android.os.Process.THREAD_PRIORITY_LESS_FAVORABLE);
+
+        mStartThreadTime = -1;
+        mCurrentThreadTime = SystemClock.currentThreadTimeMillis();
+
+        while (mRunning) {
+            Request request;
+
+            /* Get a request to process */
+            request = mRequestFeeder.getRequest();
+
+            /* wait for work */
+            if (request == null) {
+                synchronized(mRequestFeeder) {
+                    if (HttpLog.LOGV) HttpLog.v("ConnectionThread: Waiting for work");
+                    mWaiting = true;
+                    try {
+                        if (mStartThreadTime != -1) {
+                            mCurrentThreadTime = SystemClock
+                                    .currentThreadTimeMillis();
+                        }
+                        mRequestFeeder.wait();
+                    } catch (InterruptedException e) {
+                    }
+                    mWaiting = false;
+                }
+            } else {
+                if (HttpLog.LOGV) HttpLog.v("ConnectionThread: new request " +
+                                            request.mHost + " " + request );
+
+                HttpHost proxy = mConnectionManager.getProxyHost();
+
+                HttpHost host;
+                if (false) {
+                    // Allow https proxy
+                    host = proxy == null ? request.mHost : proxy;
+                } else {
+                    // Disallow https proxy -- tmob proxy server
+                    // serves a request loop for https reqs
+                    host = (proxy == null ||
+                            request.mHost.getSchemeName().equals("https")) ?
+                            request.mHost : proxy;
+                }
+                mConnection = mConnectionManager.getConnection(mContext, host);
+                mConnection.processRequests(request);
+                if (mConnection.getCanPersist()) {
+                    if (!mConnectionManager.recycleConnection(host,
+                                mConnection)) {
+                        mConnection.closeConnection();
+                    }
+                } else {
+                    mConnection.closeConnection();
+                }
+                mConnection = null;
+            }
+
+        }
+    }
+
+    public synchronized String toString() {
+        String con = mConnection == null ? "" : mConnection.toString();
+        String active = mWaiting ? "w" : "a";
+        return "cid " + mId + " " + active + " "  + con;
+    }
+
+}
diff --git a/core/java/android/net/http/DomainNameChecker.java b/core/java/android/net/http/DomainNameChecker.java
new file mode 100644
index 0000000..e4c8009
--- /dev/null
+++ b/core/java/android/net/http/DomainNameChecker.java
@@ -0,0 +1,277 @@
+/*
+ * 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 android.net.http;
+
+import org.bouncycastle.asn1.x509.X509Name;
+
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.security.cert.X509Certificate;
+import java.security.cert.CertificateParsingException;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.regex.PatternSyntaxException;
+import java.util.Vector;
+
+/**
+ * Implements basic domain-name validation as specified by RFC2818.
+ * 
+ * {@hide}
+ */
+public class DomainNameChecker {
+    private static Pattern QUICK_IP_PATTERN;
+    static {
+        try {
+            QUICK_IP_PATTERN = Pattern.compile("^[a-f0-9\\.:]+$");
+        } catch (PatternSyntaxException e) {}
+    }
+
+    private static final int ALT_DNS_NAME = 2;
+    private static final int ALT_IPA_NAME = 7;
+
+    /**
+     * Checks the site certificate against the domain name of the site being visited
+     * @param certificate The certificate to check
+     * @param thisDomain The domain name of the site being visited
+     * @return True iff if there is a domain match as specified by RFC2818
+     */
+    public static boolean match(X509Certificate certificate, String thisDomain) {
+        if (certificate == null || thisDomain == null || thisDomain.length() == 0) {
+            return false;
+        }
+
+        thisDomain = thisDomain.toLowerCase();
+        if (!isIpAddress(thisDomain)) {
+            return matchDns(certificate, thisDomain);
+        } else {
+            return matchIpAddress(certificate, thisDomain);
+        }
+    }
+
+    /**
+     * @return True iff the domain name is specified as an IP address
+     */
+    private static boolean isIpAddress(String domain) {
+        boolean rval = (domain != null && domain.length() != 0);
+        if (rval) {
+            try {
+                // do a quick-dirty IP match first to avoid DNS lookup
+                rval = QUICK_IP_PATTERN.matcher(domain).matches();
+                if (rval) {
+                    rval = domain.equals(
+                        InetAddress.getByName(domain).getHostAddress());
+                }
+            } catch (UnknownHostException e) {
+                String errorMessage = e.getMessage();
+                if (errorMessage == null) {
+                  errorMessage = "unknown host exception";
+                }
+
+                if (HttpLog.LOGV) {
+                    HttpLog.v("DomainNameChecker.isIpAddress(): " + errorMessage);
+                }
+
+                rval = false;
+            }
+        }
+
+        return rval;
+    }
+
+    /**
+     * Checks the site certificate against the IP domain name of the site being visited
+     * @param certificate The certificate to check
+     * @param thisDomain The DNS domain name of the site being visited
+     * @return True iff if there is a domain match as specified by RFC2818
+     */
+    private static boolean matchIpAddress(X509Certificate certificate, String thisDomain) {
+        if (HttpLog.LOGV) {
+            HttpLog.v("DomainNameChecker.matchIpAddress(): this domain: " + thisDomain);
+        }
+
+        try {
+            Collection subjectAltNames = certificate.getSubjectAlternativeNames();
+            if (subjectAltNames != null) {
+                Iterator i = subjectAltNames.iterator();
+                while (i.hasNext()) {
+                    List altNameEntry = (List)(i.next());
+                    if (altNameEntry != null && 2 <= altNameEntry.size()) {
+                        Integer altNameType = (Integer)(altNameEntry.get(0));
+                        if (altNameType != null) {
+                            if (altNameType.intValue() == ALT_IPA_NAME) {
+                                String altName = (String)(altNameEntry.get(1));
+                                if (altName != null) {
+                                    if (HttpLog.LOGV) {
+                                        HttpLog.v("alternative IP: " + altName);
+                                    }
+                                    if (thisDomain.equalsIgnoreCase(altName)) {
+                                        return true;
+                                    }
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+        } catch (CertificateParsingException e) {}
+
+        return false;
+    }
+
+    /**
+     * Checks the site certificate against the DNS domain name of the site being visited
+     * @param certificate The certificate to check
+     * @param thisDomain The DNS domain name of the site being visited
+     * @return True iff if there is a domain match as specified by RFC2818
+     */
+    private static boolean matchDns(X509Certificate certificate, String thisDomain) {
+        boolean hasDns = false;
+        try {
+            Collection subjectAltNames = certificate.getSubjectAlternativeNames();
+            if (subjectAltNames != null) {
+                Iterator i = subjectAltNames.iterator();
+                while (i.hasNext()) {
+                    List altNameEntry = (List)(i.next());
+                    if (altNameEntry != null && 2 <= altNameEntry.size()) {
+                        Integer altNameType = (Integer)(altNameEntry.get(0));
+                        if (altNameType != null) {
+                            if (altNameType.intValue() == ALT_DNS_NAME) {
+                                hasDns = true;
+                                String altName = (String)(altNameEntry.get(1));
+                                if (altName != null) {
+                                    if (matchDns(thisDomain, altName)) {
+                                        return true;
+                                    }
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+        } catch (CertificateParsingException e) {
+            // one way we can get here is if an alternative name starts with
+            // '*' character, which is contrary to one interpretation of the
+            // spec (a valid DNS name must start with a letter); there is no
+            // good way around this, and in order to be compatible we proceed
+            // to check the common name (ie, ignore alternative names)
+            if (HttpLog.LOGV) {
+                String errorMessage = e.getMessage();
+                if (errorMessage == null) {
+                    errorMessage = "failed to parse certificate";
+                }
+
+                if (HttpLog.LOGV) {
+                    HttpLog.v("DomainNameChecker.matchDns(): " + errorMessage);
+                }
+            }
+        }
+
+        if (!hasDns) {
+            X509Name xName = new X509Name(certificate.getSubjectDN().getName());
+            Vector val = xName.getValues();
+            Vector oid = xName.getOIDs();
+            for (int i = 0; i < oid.size(); i++) {
+                if (oid.elementAt(i).equals(X509Name.CN)) {
+                    return matchDns(thisDomain, (String)(val.elementAt(i)));
+                }
+            }
+        }
+
+        return false;
+    }
+
+    /**
+     * @param thisDomain The domain name of the site being visited
+     * @param thatDomain The domain name from the certificate
+     * @return True iff thisDomain matches thatDomain as specified by RFC2818
+     */
+    private static boolean matchDns(String thisDomain, String thatDomain) {
+        if (HttpLog.LOGV) {
+            HttpLog.v("DomainNameChecker.matchDns():" +
+                      " this domain: " + thisDomain +
+                      " that domain: " + thatDomain);
+        }
+
+        if (thisDomain == null || thisDomain.length() == 0 ||
+            thatDomain == null || thatDomain.length() == 0) {
+            return false;
+        }
+
+        thatDomain = thatDomain.toLowerCase();
+
+        // (a) domain name strings are equal, ignoring case: X matches X
+        boolean rval = thisDomain.equals(thatDomain);
+        if (!rval) {
+            String[] thisDomainTokens = thisDomain.split("\\.");
+            String[] thatDomainTokens = thatDomain.split("\\.");
+
+            int thisDomainTokensNum = thisDomainTokens.length;
+            int thatDomainTokensNum = thatDomainTokens.length;
+
+            // (b) OR thatHost is a '.'-suffix of thisHost: Z.Y.X matches X
+            if (thisDomainTokensNum >= thatDomainTokensNum) {
+                for (int i = thatDomainTokensNum - 1; i >= 0; --i) {
+                    rval = thisDomainTokens[i].equals(thatDomainTokens[i]);
+                    if (!rval) {
+                        // (c) OR we have a special *-match:
+                        // Z.Y.X matches *.Y.X but does not match *.X
+                        rval = (i == 0 && thisDomainTokensNum == thatDomainTokensNum);
+                        if (rval) {
+                            rval = thatDomainTokens[0].equals("*");
+                            if (!rval) {
+                                // (d) OR we have a *-component match:
+                                // f*.com matches foo.com but not bar.com
+                                rval = domainTokenMatch(
+                                    thisDomainTokens[0], thatDomainTokens[0]);
+                            }
+                        }
+
+                        break;
+                    }
+                }
+            }
+        }
+
+        return rval;
+    }
+
+    /**
+     * @param thisDomainToken The domain token from the current domain name
+     * @param thatDomainToken The domain token from the certificate
+     * @return True iff thisDomainToken matches thatDomainToken, using the
+     * wildcard match as specified by RFC2818-3.1. For example, f*.com must
+     * match foo.com but not bar.com
+     */
+    private static boolean domainTokenMatch(String thisDomainToken, String thatDomainToken) {
+        if (thisDomainToken != null && thatDomainToken != null) {
+            int starIndex = thatDomainToken.indexOf('*');
+            if (starIndex >= 0) {
+                if (thatDomainToken.length() - 1 <= thisDomainToken.length()) {
+                    String prefix = thatDomainToken.substring(0,  starIndex);
+                    String suffix = thatDomainToken.substring(starIndex + 1);
+
+                    return thisDomainToken.startsWith(prefix) && thisDomainToken.endsWith(suffix);
+                }
+            }
+        }
+
+        return false;
+    }
+}
diff --git a/core/java/android/net/http/EventHandler.java b/core/java/android/net/http/EventHandler.java
new file mode 100644
index 0000000..830d1f1
--- /dev/null
+++ b/core/java/android/net/http/EventHandler.java
@@ -0,0 +1,147 @@
+/*
+ * 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 android.net.http;
+
+
+/**
+ * Callbacks in this interface are made as an HTTP request is
+ * processed. The normal order of callbacks is status(), headers(),
+ * then multiple data() then endData().  handleSslErrorRequest(), if
+ * there is an SSL certificate error. error() can occur anywhere
+ * in the transaction.
+ * 
+ * {@hide}
+ */
+
+public interface EventHandler {
+
+    /**
+     * Error codes used in the error() callback.  Positive error codes
+     * are reserved for codes sent by http servers.  Negative error
+     * codes are connection/parsing failures, etc.
+     */
+
+    /** Success */
+    public static final int OK = 0;
+    /** Generic error */
+    public static final int ERROR = -1;
+    /** Server or proxy hostname lookup failed */
+    public static final int ERROR_LOOKUP = -2;
+    /** Unsupported authentication scheme (ie, not basic or digest) */
+    public static final int ERROR_UNSUPPORTED_AUTH_SCHEME = -3;
+    /** User authentication failed on server */
+    public static final int ERROR_AUTH = -4;
+    /** User authentication failed on proxy */
+    public static final int ERROR_PROXYAUTH = -5;
+    /** Could not connect to server */
+    public static final int ERROR_CONNECT = -6;
+    /** Failed to write to or read from server */
+    public static final int ERROR_IO = -7;
+    /** Connection timed out */
+    public static final int ERROR_TIMEOUT = -8;
+    /** Too many redirects */
+    public static final int ERROR_REDIRECT_LOOP = -9;
+    /** Unsupported URI scheme (ie, not http, https, etc) */
+    public static final int ERROR_UNSUPPORTED_SCHEME = -10;
+    /** Failed to perform SSL handshake */
+    public static final int ERROR_FAILED_SSL_HANDSHAKE = -11;
+    /** Bad URL */
+    public static final int ERROR_BAD_URL = -12;
+    /** Generic file error for file:/// loads */
+    public static final int FILE_ERROR = -13;
+    /** File not found error for file:/// loads */
+    public static final int FILE_NOT_FOUND_ERROR = -14;
+    /** Too many requests queued */
+    public static final int TOO_MANY_REQUESTS_ERROR = -15;
+
+    final static int[] errorStringResources = {
+        com.android.internal.R.string.httpErrorOk,
+        com.android.internal.R.string.httpError,
+        com.android.internal.R.string.httpErrorLookup,
+        com.android.internal.R.string.httpErrorUnsupportedAuthScheme,
+        com.android.internal.R.string.httpErrorAuth,
+        com.android.internal.R.string.httpErrorProxyAuth,
+        com.android.internal.R.string.httpErrorConnect,
+        com.android.internal.R.string.httpErrorIO,
+        com.android.internal.R.string.httpErrorTimeout,
+        com.android.internal.R.string.httpErrorRedirectLoop,
+        com.android.internal.R.string.httpErrorUnsupportedScheme,
+        com.android.internal.R.string.httpErrorFailedSslHandshake,
+        com.android.internal.R.string.httpErrorBadUrl,
+        com.android.internal.R.string.httpErrorFile,
+        com.android.internal.R.string.httpErrorFileNotFound,
+        com.android.internal.R.string.httpErrorTooManyRequests
+    };
+
+    /**
+     * Called after status line has been sucessfully processed.
+     * @param major_version HTTP version advertised by server.  major
+     * is the part before the "."
+     * @param minor_version HTTP version advertised by server.  minor
+     * is the part after the "."
+     * @param code HTTP Status code.  See RFC 2616.
+     * @param reason_phrase Textual explanation sent by server
+     */
+    public void status(int major_version,
+                       int minor_version,
+                       int code,
+                       String reason_phrase);
+
+    /**
+     * Called after all headers are successfully processed.
+     */
+    public void headers(Headers headers);
+
+    /**
+     * An array containing all or part of the http body as read from
+     * the server.
+     * @param data A byte array containing the content
+     * @param len The length of valid content in data
+     *
+     * Note: chunked and compressed encodings are handled within
+     * android.net.http.  Decoded data is passed through this
+     * interface.
+     */
+    public void data(byte[] data, int len);
+
+    /**
+     * Called when the document is completely read.  No more data()
+     * callbacks will be made after this call
+     */
+    public void endData();
+
+    /**
+     * SSL certificate callback called every time a resource is
+     * loaded via a secure connection
+     */
+    public void certificate(SslCertificate certificate);
+
+    /**
+     * There was trouble.
+     * @param id One of the error codes defined below
+     * @param description of error
+     */
+    public void error(int id, String description);
+
+    /**
+     * SSL certificate error callback. Handles SSL error(s) on the way
+     * up to the user. The callback has to make sure that restartConnection() is called,
+     * otherwise the connection will be suspended indefinitely.
+     */
+    public void handleSslErrorRequest(SslError error);
+
+}
diff --git a/core/java/android/net/http/Headers.java b/core/java/android/net/http/Headers.java
new file mode 100644
index 0000000..b0923d1
--- /dev/null
+++ b/core/java/android/net/http/Headers.java
@@ -0,0 +1,447 @@
+/*
+ * 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 android.net.http;
+
+import android.util.Config;
+import android.util.Log;
+
+import java.util.ArrayList;
+
+import org.apache.http.HeaderElement;
+import org.apache.http.entity.ContentLengthStrategy;
+import org.apache.http.message.BasicHeaderValueParser;
+import org.apache.http.message.ParserCursor;
+import org.apache.http.protocol.HTTP;
+import org.apache.http.util.CharArrayBuffer;
+
+/**
+ * Manages received headers
+ *
+ * {@hide}
+ */
+public final class Headers {
+    private static final String LOGTAG = "Http";
+
+    // header parsing constant
+    /**
+     * indicate HTTP 1.0 connection close after the response
+     */
+    public final static int CONN_CLOSE = 1;
+    /**
+     * indicate HTTP 1.1 connection keep alive
+     */
+    public final static int CONN_KEEP_ALIVE = 2;
+
+    // initial values.
+    public final static int NO_CONN_TYPE = 0;
+    public final static long NO_TRANSFER_ENCODING = 0;
+    public final static long NO_CONTENT_LENGTH = -1;
+
+    // header strings
+    public final static String TRANSFER_ENCODING = "transfer-encoding";
+    public final static String CONTENT_LEN = "content-length";
+    public final static String CONTENT_TYPE = "content-type";
+    public final static String CONTENT_ENCODING = "content-encoding";
+    public final static String CONN_DIRECTIVE = "connection";
+
+    public final static String LOCATION = "location";
+    public final static String PROXY_CONNECTION = "proxy-connection";
+
+    public final static String WWW_AUTHENTICATE = "www-authenticate";
+    public final static String PROXY_AUTHENTICATE = "proxy-authenticate";
+    public final static String CONTENT_DISPOSITION = "content-disposition";
+    public final static String ACCEPT_RANGES = "accept-ranges";
+    public final static String EXPIRES = "expires";
+    public final static String CACHE_CONTROL = "cache-control";
+    public final static String LAST_MODIFIED = "last-modified";
+    public final static String ETAG = "etag";
+    public final static String SET_COOKIE = "set-cookie";
+    public final static String PRAGMA = "pragma";
+    public final static String REFRESH = "refresh";
+
+    // following hash are generated by String.hashCode()
+    private final static int HASH_TRANSFER_ENCODING = 1274458357;
+    private final static int HASH_CONTENT_LEN = -1132779846;
+    private final static int HASH_CONTENT_TYPE = 785670158;
+    private final static int HASH_CONTENT_ENCODING = 2095084583;
+    private final static int HASH_CONN_DIRECTIVE = -775651618;
+    private final static int HASH_LOCATION = 1901043637;
+    private final static int HASH_PROXY_CONNECTION = 285929373;
+    private final static int HASH_WWW_AUTHENTICATE = -243037365;
+    private final static int HASH_PROXY_AUTHENTICATE = -301767724;
+    private final static int HASH_CONTENT_DISPOSITION = -1267267485;
+    private final static int HASH_ACCEPT_RANGES = 1397189435;
+    private final static int HASH_EXPIRES = -1309235404;
+    private final static int HASH_CACHE_CONTROL = -208775662;
+    private final static int HASH_LAST_MODIFIED = 150043680;
+    private final static int HASH_ETAG = 3123477;
+    private final static int HASH_SET_COOKIE = 1237214767;
+    private final static int HASH_PRAGMA = -980228804;
+    private final static int HASH_REFRESH = 1085444827;
+
+    // keep any headers that require direct access in a presized
+    // string array
+    private final static int IDX_TRANSFER_ENCODING = 0;
+    private final static int IDX_CONTENT_LEN = 1;
+    private final static int IDX_CONTENT_TYPE = 2;
+    private final static int IDX_CONTENT_ENCODING = 3;
+    private final static int IDX_CONN_DIRECTIVE = 4;
+    private final static int IDX_LOCATION = 5;
+    private final static int IDX_PROXY_CONNECTION = 6;
+    private final static int IDX_WWW_AUTHENTICATE = 7;
+    private final static int IDX_PROXY_AUTHENTICATE = 8;
+    private final static int IDX_CONTENT_DISPOSITION = 9;
+    private final static int IDX_ACCEPT_RANGES = 10;
+    private final static int IDX_EXPIRES = 11;
+    private final static int IDX_CACHE_CONTROL = 12;
+    private final static int IDX_LAST_MODIFIED = 13;
+    private final static int IDX_ETAG = 14;
+    private final static int IDX_SET_COOKIE = 15;
+    private final static int IDX_PRAGMA = 16;
+    private final static int IDX_REFRESH = 17;
+
+    private final static int HEADER_COUNT = 18;
+
+    /* parsed values */
+    private long transferEncoding;
+    private long contentLength; // Content length of the incoming data
+    private int connectionType;
+    private ArrayList<String> cookies = new ArrayList<String>(2);
+
+    private String[] mHeaders = new String[HEADER_COUNT];
+    private final static String[] sHeaderNames = {
+        TRANSFER_ENCODING,
+        CONTENT_LEN,
+        CONTENT_TYPE,
+        CONTENT_ENCODING,
+        CONN_DIRECTIVE,
+        LOCATION,
+        PROXY_CONNECTION,
+        WWW_AUTHENTICATE,
+        PROXY_AUTHENTICATE,
+        CONTENT_DISPOSITION,
+        ACCEPT_RANGES,
+        EXPIRES,
+        CACHE_CONTROL,
+        LAST_MODIFIED,
+        ETAG,
+        SET_COOKIE,
+        PRAGMA,
+        REFRESH
+    };
+
+    // Catch-all for headers not explicitly handled
+    private ArrayList<String> mExtraHeaderNames = new ArrayList<String>(4);
+    private ArrayList<String> mExtraHeaderValues = new ArrayList<String>(4);
+
+    public Headers() {
+        transferEncoding = NO_TRANSFER_ENCODING;
+        contentLength = NO_CONTENT_LENGTH;
+        connectionType = NO_CONN_TYPE;
+    }
+
+    public void parseHeader(CharArrayBuffer buffer) {
+        int pos = CharArrayBuffers.setLowercaseIndexOf(buffer, ':');
+        if (pos == -1) {
+            return;
+        }
+        String name = buffer.substringTrimmed(0, pos);
+        if (name.length() == 0) {
+            return;
+        }
+        pos++;
+
+        String val = buffer.substringTrimmed(pos, buffer.length());
+        if (HttpLog.LOGV) {
+            HttpLog.v("hdr " + buffer.length() + " " + buffer);
+        }
+
+        switch (name.hashCode()) {
+        case HASH_TRANSFER_ENCODING:
+            if (name.equals(TRANSFER_ENCODING)) {
+                mHeaders[IDX_TRANSFER_ENCODING] = val;
+                HeaderElement[] encodings = BasicHeaderValueParser.DEFAULT
+                        .parseElements(buffer, new ParserCursor(pos,
+                                buffer.length()));
+                // The chunked encoding must be the last one applied RFC2616,
+                // 14.41
+                int len = encodings.length;
+                if (HTTP.IDENTITY_CODING.equalsIgnoreCase(val)) {
+                    transferEncoding = ContentLengthStrategy.IDENTITY;
+                } else if ((len > 0)
+                        && (HTTP.CHUNK_CODING
+                                .equalsIgnoreCase(encodings[len - 1].getName()))) {
+                    transferEncoding = ContentLengthStrategy.CHUNKED;
+                } else {
+                    transferEncoding = ContentLengthStrategy.IDENTITY;
+                }
+            }
+            break;
+        case HASH_CONTENT_LEN:
+            if (name.equals(CONTENT_LEN)) {
+                mHeaders[IDX_CONTENT_LEN] = val;
+                try {
+                    contentLength = Long.parseLong(val);
+                } catch (NumberFormatException e) {
+                    if (Config.LOGV) {
+                        Log.v(LOGTAG, "Headers.headers(): error parsing"
+                                + " content length: " + buffer.toString());
+                    }
+                }
+            }
+            break;
+        case HASH_CONTENT_TYPE:
+            if (name.equals(CONTENT_TYPE)) {
+                mHeaders[IDX_CONTENT_TYPE] = val;
+            }
+            break;
+        case HASH_CONTENT_ENCODING:
+            if (name.equals(CONTENT_ENCODING)) {
+                mHeaders[IDX_CONTENT_ENCODING] = val;
+            }
+            break;
+        case HASH_CONN_DIRECTIVE:
+            if (name.equals(CONN_DIRECTIVE)) {
+                mHeaders[IDX_CONN_DIRECTIVE] = val;
+                setConnectionType(buffer, pos);
+            }
+            break;
+        case HASH_LOCATION:
+            if (name.equals(LOCATION)) {
+                mHeaders[IDX_LOCATION] = val;
+            }
+            break;
+        case HASH_PROXY_CONNECTION:
+            if (name.equals(PROXY_CONNECTION)) {
+                mHeaders[IDX_PROXY_CONNECTION] = val;
+                setConnectionType(buffer, pos);
+            }
+            break;
+        case HASH_WWW_AUTHENTICATE:
+            if (name.equals(WWW_AUTHENTICATE)) {
+                mHeaders[IDX_WWW_AUTHENTICATE] = val;
+            }
+            break;
+        case HASH_PROXY_AUTHENTICATE:
+            if (name.equals(PROXY_AUTHENTICATE)) {
+                mHeaders[IDX_PROXY_AUTHENTICATE] = val;
+            }
+            break;
+        case HASH_CONTENT_DISPOSITION:
+            if (name.equals(CONTENT_DISPOSITION)) {
+                mHeaders[IDX_CONTENT_DISPOSITION] = val;
+            }
+            break;
+        case HASH_ACCEPT_RANGES:
+            if (name.equals(ACCEPT_RANGES)) {
+                mHeaders[IDX_ACCEPT_RANGES] = val;
+            }
+            break;
+        case HASH_EXPIRES:
+            if (name.equals(EXPIRES)) {
+                mHeaders[IDX_EXPIRES] = val;
+            }
+            break;
+        case HASH_CACHE_CONTROL:
+            if (name.equals(CACHE_CONTROL)) {
+                mHeaders[IDX_CACHE_CONTROL] = val;
+            }
+            break;
+        case HASH_LAST_MODIFIED:
+            if (name.equals(LAST_MODIFIED)) {
+                mHeaders[IDX_LAST_MODIFIED] = val;
+            }
+            break;
+        case HASH_ETAG:
+            if (name.equals(ETAG)) {
+                mHeaders[IDX_ETAG] = val;
+            }
+            break;
+        case HASH_SET_COOKIE:
+            if (name.equals(SET_COOKIE)) {
+                mHeaders[IDX_SET_COOKIE] = val;
+                cookies.add(val);
+            }
+            break;
+        case HASH_PRAGMA:
+            if (name.equals(PRAGMA)) {
+                mHeaders[IDX_PRAGMA] = val;
+            }
+            break;
+        case HASH_REFRESH:
+            if (name.equals(REFRESH)) {
+                mHeaders[IDX_REFRESH] = val;
+            }
+            break;
+        default:
+            mExtraHeaderNames.add(name);
+            mExtraHeaderValues.add(val);
+        }
+    }
+
+    public long getTransferEncoding() {
+        return transferEncoding;
+    }
+
+    public long getContentLength() {
+        return contentLength;
+    }
+
+    public int getConnectionType() {
+        return connectionType;
+    }
+
+    public String getContentType() {
+        return mHeaders[IDX_CONTENT_TYPE];
+    }
+
+    public String getContentEncoding() {
+        return mHeaders[IDX_CONTENT_ENCODING];
+    }
+
+    public String getLocation() {
+        return mHeaders[IDX_LOCATION];
+    }
+
+    public String getWwwAuthenticate() {
+        return mHeaders[IDX_WWW_AUTHENTICATE];
+    }
+
+    public String getProxyAuthenticate() {
+        return mHeaders[IDX_PROXY_AUTHENTICATE];
+    }
+
+    public String getContentDisposition() {
+        return mHeaders[IDX_CONTENT_DISPOSITION];
+    }
+
+    public String getAcceptRanges() {
+        return mHeaders[IDX_ACCEPT_RANGES];
+    }
+
+    public String getExpires() {
+        return mHeaders[IDX_EXPIRES];
+    }
+
+    public String getCacheControl() {
+        return mHeaders[IDX_CACHE_CONTROL];
+    }
+
+    public String getLastModified() {
+        return mHeaders[IDX_LAST_MODIFIED];
+    }
+
+    public String getEtag() {
+        return mHeaders[IDX_ETAG];
+    }
+
+    public ArrayList<String> getSetCookie() {
+        return this.cookies;
+    }
+
+    public String getPragma() {
+        return mHeaders[IDX_PRAGMA];
+    }
+
+    public String getRefresh() {
+        return mHeaders[IDX_REFRESH];
+    }
+
+    public void setContentLength(long value) {
+        this.contentLength = value;
+    }
+
+    public void setContentType(String value) {
+        mHeaders[IDX_CONTENT_TYPE] = value;
+    }
+
+    public void setContentEncoding(String value) {
+        mHeaders[IDX_CONTENT_ENCODING] = value;
+    }
+
+    public void setLocation(String value) {
+        mHeaders[IDX_LOCATION] = value;
+    }
+
+    public void setWwwAuthenticate(String value) {
+        mHeaders[IDX_WWW_AUTHENTICATE] = value;
+    }
+
+    public void setProxyAuthenticate(String value) {
+        mHeaders[IDX_PROXY_AUTHENTICATE] = value;
+    }
+
+    public void setContentDisposition(String value) {
+        mHeaders[IDX_CONTENT_DISPOSITION] = value;
+    }
+
+    public void setAcceptRanges(String value) {
+        mHeaders[IDX_ACCEPT_RANGES] = value;
+    }
+
+    public void setExpires(String value) {
+        mHeaders[IDX_EXPIRES] = value;
+    }
+
+    public void setCacheControl(String value) {
+        mHeaders[IDX_CACHE_CONTROL] = value;
+    }
+
+    public void setLastModified(String value) {
+        mHeaders[IDX_LAST_MODIFIED] = value;
+    }
+
+    public void setEtag(String value) {
+        mHeaders[IDX_ETAG] = value;
+    }
+
+    public interface HeaderCallback {
+        public void header(String name, String value);
+    }
+
+    /**
+     * Reports all non-null headers to the callback
+     */
+    public void getHeaders(HeaderCallback hcb) {
+        for (int i = 0; i < HEADER_COUNT; i++) {
+            String h = mHeaders[i];
+            if (h != null) {
+                hcb.header(sHeaderNames[i], h);
+            }
+        }
+        int extraLen = mExtraHeaderNames.size();
+        for (int i = 0; i < extraLen; i++) {
+            if (Config.LOGV) {
+                HttpLog.v("Headers.getHeaders() extra: " + i + " " +
+                          mExtraHeaderNames.get(i) + " " + mExtraHeaderValues.get(i));
+            }
+            hcb.header(mExtraHeaderNames.get(i),
+                       mExtraHeaderValues.get(i));
+        }
+
+    }
+
+    private void setConnectionType(CharArrayBuffer buffer, int pos) {
+        if (CharArrayBuffers.containsIgnoreCaseTrimmed(
+                buffer, pos, HTTP.CONN_CLOSE)) {
+            connectionType = CONN_CLOSE;
+        } else if (CharArrayBuffers.containsIgnoreCaseTrimmed(
+                buffer, pos, HTTP.CONN_KEEP_ALIVE)) {
+            connectionType = CONN_KEEP_ALIVE;
+        }
+    }
+}
diff --git a/core/java/android/net/http/HttpAuthHeader.java b/core/java/android/net/http/HttpAuthHeader.java
new file mode 100644
index 0000000..d41284c
--- /dev/null
+++ b/core/java/android/net/http/HttpAuthHeader.java
@@ -0,0 +1,422 @@
+/*
+ * Copyright (C) 2007 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 android.net.http;
+
+/**
+ * HttpAuthHeader: a class to store HTTP authentication-header parameters.
+ * For more information, see: RFC 2617: HTTP Authentication.
+ * 
+ * {@hide}
+ */
+public class HttpAuthHeader {
+    /**
+     * Possible HTTP-authentication header tokens to search for:
+     */
+    public final static String BASIC_TOKEN = "Basic";
+    public final static String DIGEST_TOKEN = "Digest";
+
+    private final static String REALM_TOKEN = "realm";
+    private final static String NONCE_TOKEN = "nonce";
+    private final static String STALE_TOKEN = "stale";
+    private final static String OPAQUE_TOKEN = "opaque";
+    private final static String QOP_TOKEN = "qop";
+    private final static String ALGORITHM_TOKEN = "algorithm";
+
+    /**
+     * An authentication scheme. We currently support two different schemes:
+     * HttpAuthHeader.BASIC  - basic, and
+     * HttpAuthHeader.DIGEST - digest (algorithm=MD5, QOP="auth").
+     */
+    private int mScheme;
+
+    public static final int UNKNOWN = 0;
+    public static final int BASIC = 1;
+    public static final int DIGEST = 2;
+
+    /**
+     * A flag, indicating that the previous request from the client was
+     * rejected because the nonce value was stale. If stale is TRUE
+     * (case-insensitive), the client may wish to simply retry the request
+     * with a new encrypted response, without reprompting the user for a
+     * new username and password.
+     */
+    private boolean mStale;
+
+    /**
+     * A string to be displayed to users so they know which username and
+     * password to use.
+     */
+    private String mRealm;
+
+    /**
+     * A server-specified data string which should be uniquely generated
+     * each time a 401 response is made.
+     */
+    private String mNonce;
+
+    /**
+     * A string of data, specified by the server, which should be returned
+     *  by the client unchanged in the Authorization header of subsequent
+     * requests with URIs in the same protection space.
+     */
+    private String mOpaque;
+
+    /**
+     * This directive is optional, but is made so only for backward
+     * compatibility with RFC 2069 [6]; it SHOULD be used by all
+     * implementations compliant with this version of the Digest scheme.
+     * If present, it is a quoted string of one or more tokens indicating
+     * the "quality of protection" values supported by the server.  The
+     * value "auth" indicates authentication; the value "auth-int"
+     * indicates authentication with integrity protection.
+     */
+    private String mQop;
+
+    /**
+     * A string indicating a pair of algorithms used to produce the digest
+     * and a checksum. If this is not present it is assumed to be "MD5".
+     */
+    private String mAlgorithm;
+
+    /**
+     * Is this authentication request a proxy authentication request?
+     */
+    private boolean mIsProxy;
+
+    /**
+     * Username string we get from the user.
+     */
+    private String mUsername;
+
+    /**
+     * Password string we get from the user.
+     */
+    private String mPassword;
+
+    /**
+     * Creates a new HTTP-authentication header object from the
+     * input header string.
+     * The header string is assumed to contain parameters of at
+     * most one authentication-scheme (ensured by the caller).
+     */
+    public HttpAuthHeader(String header) {
+        if (header != null) {
+            parseHeader(header);
+        }
+    }
+
+    /**
+     * @return True iff this is a proxy authentication header.
+     */
+    public boolean isProxy() {
+        return mIsProxy;
+    }
+
+    /**
+     * Marks this header as a proxy authentication header.
+     */
+    public void setProxy() {
+        mIsProxy = true;
+    }
+
+    /**
+     * @return The username string.
+     */
+    public String getUsername() {
+        return mUsername;
+    }
+
+    /**
+     * Sets the username string.
+     */
+    public void setUsername(String username) {
+        mUsername = username;
+    }
+
+    /**
+     * @return The password string.
+     */
+    public String getPassword() {
+        return mPassword;
+    }
+
+    /**
+     * Sets the password string.
+     */
+    public void setPassword(String password) {
+        mPassword = password;
+    }
+
+    /**
+     * @return True iff this is the  BASIC-authentication request.
+     */
+    public boolean isBasic () {
+        return mScheme == BASIC;
+    }
+
+    /**
+     * @return True iff this is the DIGEST-authentication request.
+     */
+    public boolean isDigest() {
+        return mScheme == DIGEST;
+    }
+
+    /**
+     * @return The authentication scheme requested. We currently
+     * support two schemes:
+     * HttpAuthHeader.BASIC  - basic, and
+     * HttpAuthHeader.DIGEST - digest (algorithm=MD5, QOP="auth").
+     */
+    public int getScheme() {
+        return mScheme;
+    }
+
+    /**
+     * @return True if indicating that the previous request from
+     * the client was rejected because the nonce value was stale.
+     */
+    public boolean getStale() {
+        return mStale;
+    }
+
+    /**
+     * @return The realm value or null if there is none.
+     */
+    public String getRealm() {
+        return mRealm;
+    }
+
+    /**
+     * @return The nonce value or null if there is none.
+     */
+    public String getNonce() {
+        return mNonce;
+    }
+
+    /**
+     * @return The opaque value or null if there is none.
+     */
+    public String getOpaque() {
+        return mOpaque;
+    }
+
+    /**
+     * @return The QOP ("quality-of_protection") value or null if
+     * there is none. The QOP value is always lower-case.
+     */
+    public String getQop() {
+        return mQop;
+    }
+
+    /**
+     * @return The name of the algorithm used or null if there is
+     * none. By default, MD5 is used.
+     */
+    public String getAlgorithm() {
+        return mAlgorithm;
+    }
+
+    /**
+     * @return True iff the authentication scheme requested by the
+     * server is supported; currently supported schemes:
+     * BASIC,
+     * DIGEST (only algorithm="md5", no qop or qop="auth).
+     */
+    public boolean isSupportedScheme() {
+        // it is a good idea to enforce non-null realms!
+        if (mRealm != null) {
+            if (mScheme == BASIC) {
+                return true;
+            } else {
+                if (mScheme == DIGEST) {
+                    return
+                        mAlgorithm.equals("md5") &&
+                        (mQop == null || mQop.equals("auth"));
+                }
+            }
+        }
+
+        return false;
+    }
+
+    /**
+     * Parses the header scheme name and then scheme parameters if
+     * the scheme is supported.
+     */
+    private void parseHeader(String header) {
+        if (HttpLog.LOGV) {
+            HttpLog.v("HttpAuthHeader.parseHeader(): header: " + header);
+        }
+
+        if (header != null) {
+            String parameters = parseScheme(header);
+            if (parameters != null) {
+                // if we have a supported scheme
+                if (mScheme != UNKNOWN) {
+                    parseParameters(parameters);
+                }
+            }
+        }
+    }
+
+    /**
+     * Parses the authentication scheme name. If we have a Digest
+     * scheme, sets the algorithm value to the default of MD5.
+     * @return The authentication scheme parameters string to be
+     * parsed later (if the scheme is supported) or null if failed
+     * to parse the scheme (the header value is null?).
+     */
+    private String parseScheme(String header) {
+        if (header != null) {
+            int i = header.indexOf(' ');
+            if (i >= 0) {
+                String scheme = header.substring(0, i).trim();
+                if (scheme.equalsIgnoreCase(DIGEST_TOKEN)) {
+                    mScheme = DIGEST;
+
+                    // md5 is the default algorithm!!!
+                    mAlgorithm = "md5";
+                } else {
+                    if (scheme.equalsIgnoreCase(BASIC_TOKEN)) {
+                        mScheme = BASIC;
+                    }
+                }
+
+                return header.substring(i + 1);
+            }
+        }
+
+        return null;
+    }
+
+    /**
+     * Parses a comma-separated list of authentification scheme
+     * parameters.
+     */
+    private void parseParameters(String parameters) {
+        if (HttpLog.LOGV) {
+            HttpLog.v("HttpAuthHeader.parseParameters():" +
+                      " parameters: " + parameters);
+        }
+
+        if (parameters != null) {
+            int i;
+            do {
+                i = parameters.indexOf(',');
+                if (i < 0) {
+                    // have only one parameter
+                    parseParameter(parameters);
+                } else {
+                    parseParameter(parameters.substring(0, i));
+                    parameters = parameters.substring(i + 1);
+                }
+            } while (i >= 0);
+        }
+    }
+
+    /**
+     * Parses a single authentication scheme parameter. The parameter
+     * string is expected to follow the format: PARAMETER=VALUE.
+     */
+    private void parseParameter(String parameter) {
+        if (parameter != null) {
+            // here, we are looking for the 1st occurence of '=' only!!!
+            int i = parameter.indexOf('=');
+            if (i >= 0) {
+                String token = parameter.substring(0, i).trim();
+                String value =
+                    trimDoubleQuotesIfAny(parameter.substring(i + 1).trim());
+
+                if (HttpLog.LOGV) {
+                    HttpLog.v("HttpAuthHeader.parseParameter():" +
+                              " token: " + token +
+                              " value: " + value);
+                }
+
+                if (token.equalsIgnoreCase(REALM_TOKEN)) {
+                    mRealm = value;
+                } else {
+                    if (mScheme == DIGEST) {
+                        parseParameter(token, value);
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * If the token is a known parameter name, parses and initializes
+     * the token value.
+     */
+    private void parseParameter(String token, String value) {
+        if (token != null && value != null) {
+            if (token.equalsIgnoreCase(NONCE_TOKEN)) {
+                mNonce = value;
+                return;
+            }
+
+            if (token.equalsIgnoreCase(STALE_TOKEN)) {
+                parseStale(value);
+                return;
+            }
+
+            if (token.equalsIgnoreCase(OPAQUE_TOKEN)) {
+                mOpaque = value;
+                return;
+            }
+
+            if (token.equalsIgnoreCase(QOP_TOKEN)) {
+                mQop = value.toLowerCase();
+                return;
+            }
+
+            if (token.equalsIgnoreCase(ALGORITHM_TOKEN)) {
+                mAlgorithm = value.toLowerCase();
+                return;
+            }
+        }
+    }
+
+    /**
+     * Parses and initializes the 'stale' paramer value. Any value
+     * different from case-insensitive "true" is considered "false".
+     */
+    private void parseStale(String value) {
+        if (value != null) {
+            if (value.equalsIgnoreCase("true")) {
+                mStale = true;
+            }
+        }
+    }
+
+    /**
+     * Trims double-quotes around a parameter value if there are any.
+     * @return The string value without the outermost pair of double-
+     * quotes or null if the original value is null.
+     */
+    static private String trimDoubleQuotesIfAny(String value) {
+        if (value != null) {
+            int len = value.length();
+            if (len > 2 &&
+                value.charAt(0) == '\"' && value.charAt(len - 1) == '\"') {
+                return value.substring(1, len - 1);
+            }
+        }
+
+        return value;
+    }
+}
diff --git a/core/java/android/net/http/HttpConnection.java b/core/java/android/net/http/HttpConnection.java
new file mode 100644
index 0000000..8b12d0b
--- /dev/null
+++ b/core/java/android/net/http/HttpConnection.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2007 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 android.net.http;
+
+import android.content.Context;
+
+import java.net.Socket;
+import java.io.IOException;
+
+import org.apache.http.HttpClientConnection;
+import org.apache.http.HttpHost;
+import org.apache.http.impl.DefaultHttpClientConnection;
+import org.apache.http.params.BasicHttpParams;
+import org.apache.http.params.HttpConnectionParams;
+
+/**
+ * A requestConnection connecting to a normal (non secure) http server
+ * 
+ * {@hide}
+ */
+class HttpConnection extends Connection {
+
+    HttpConnection(Context context, HttpHost host,
+                   RequestQueue.ConnectionManager connectionManager,
+                   RequestFeeder requestFeeder) {
+        super(context, host, connectionManager, requestFeeder);
+    }
+
+    /**
+     * Opens the connection to a http server
+     *
+     * @return the opened low level connection
+     * @throws IOException if the connection fails for any reason.
+     */
+    @Override
+    AndroidHttpClientConnection openConnection(Request req) throws IOException {
+
+        // Update the certificate info (connection not secure - set to null)
+        EventHandler eventHandler = req.getEventHandler();
+        mCertificate = null;
+        eventHandler.certificate(mCertificate);
+
+        AndroidHttpClientConnection conn = new AndroidHttpClientConnection();
+        BasicHttpParams params = new BasicHttpParams();
+        Socket sock = new Socket(mHost.getHostName(), mHost.getPort());
+        params.setIntParameter(HttpConnectionParams.SOCKET_BUFFER_SIZE, 8192);
+        conn.bind(sock, params);
+        return conn;
+    }
+
+    /**
+     * Closes the low level connection.
+     *
+     * If an exception is thrown then it is assumed that the
+     * connection will have been closed (to the extent possible)
+     * anyway and the caller does not need to take any further action.
+     *
+     */
+    void closeConnection() {
+        try {
+            if (mHttpClientConnection != null && mHttpClientConnection.isOpen()) {
+                mHttpClientConnection.close();
+            }
+        } catch (IOException e) {
+            if (HttpLog.LOGV) HttpLog.v(
+                    "closeConnection(): failed closing connection " +
+                    mHost);
+            e.printStackTrace();
+        }
+    }
+
+    /**
+     * Restart a secure connection suspended waiting for user interaction.
+     */
+    void restartConnection(boolean abort) {
+        // not required for plain http connections
+    }
+
+    String getScheme() {
+        return "http";
+    }
+}
diff --git a/core/java/android/net/http/HttpLog.java b/core/java/android/net/http/HttpLog.java
new file mode 100644
index 0000000..30bf647
--- /dev/null
+++ b/core/java/android/net/http/HttpLog.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2007 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-level logging flag
+ */
+
+package android.net.http;
+
+import android.os.SystemClock;
+
+import android.util.Log;
+import android.util.Config;
+
+/**
+ * {@hide}
+ */
+class HttpLog {
+    private final static String LOGTAG = "http";
+
+    private static final boolean DEBUG = false;
+    static final boolean LOGV = DEBUG ? Config.LOGD : Config.LOGV;
+
+    static void v(String logMe) {
+        Log.v(LOGTAG, SystemClock.uptimeMillis() + " " + Thread.currentThread().getName() + " " + logMe);
+    }
+
+    static void e(String logMe) {
+        Log.e(LOGTAG, logMe);
+    }
+}
diff --git a/core/java/android/net/http/HttpsConnection.java b/core/java/android/net/http/HttpsConnection.java
new file mode 100644
index 0000000..fe02d3e
--- /dev/null
+++ b/core/java/android/net/http/HttpsConnection.java
@@ -0,0 +1,427 @@
+/*
+ * Copyright (C) 2007 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 android.net.http;
+
+import android.content.Context;
+
+import junit.framework.Assert;
+
+import java.io.IOException;
+
+import java.security.cert.X509Certificate;
+
+import java.net.Socket;
+import java.net.InetSocketAddress;
+
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLException;
+import javax.net.ssl.SSLSocket;
+import javax.net.ssl.SSLSocketFactory;
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.X509TrustManager;
+
+import org.apache.http.Header;
+import org.apache.http.HttpClientConnection;
+import org.apache.http.HttpException;
+import org.apache.http.HttpHost;
+import org.apache.http.HttpRequest;
+import org.apache.http.HttpResponse;
+import org.apache.http.HttpStatus;
+import org.apache.http.ParseException;
+import org.apache.http.ProtocolVersion;
+import org.apache.http.StatusLine;
+import org.apache.http.impl.DefaultHttpClientConnection;
+import org.apache.http.message.BasicHttpRequest;
+import org.apache.http.params.BasicHttpParams;
+import org.apache.http.params.HttpParams;
+import org.apache.http.params.HttpConnectionParams;
+
+/**
+ * Simple exception we throw if the SSL connection is closed by the user.
+ * 
+ * {@hide}
+ */
+class SSLConnectionClosedByUserException extends SSLException {
+
+    public SSLConnectionClosedByUserException(String reason) {
+        super(reason);
+    }
+}
+
+/**
+ * A Connection connecting to a secure http server or tunneling through
+ * a http proxy server to a https server.
+ */
+class HttpsConnection extends Connection {
+
+    /**
+     * SSL context
+     */
+    private static SSLContext mSslContext = null;
+
+    /**
+     * SSL socket factory
+     */
+    private static SSLSocketFactory mSslSocketFactory = null;
+
+    static {
+        // initialize the socket factory
+        try {
+            mSslContext = SSLContext.getInstance("TLS");
+            if (mSslContext != null) {
+                // here, trust managers is a single trust-all manager
+                TrustManager[] trustManagers = new TrustManager[] {
+                    new X509TrustManager() {
+                        public X509Certificate[] getAcceptedIssuers() {
+                            return null;
+                        }
+
+                        public void checkClientTrusted(
+                            X509Certificate[] certs, String authType) {
+                        }
+
+                        public void checkServerTrusted(
+                            X509Certificate[] certs, String authType) {
+                        }
+                    }
+                };
+
+                mSslContext.init(null, trustManagers, null);
+                mSslSocketFactory = mSslContext.getSocketFactory();
+            }
+        } catch (Exception t) {
+            if (HttpLog.LOGV) {
+                HttpLog.v("HttpsConnection: failed to initialize the socket factory");
+            }
+        }
+    }
+
+    /**
+     * @return The shared SSL context.
+     */
+    /*package*/ static SSLContext getContext() {
+        return mSslContext;
+    }
+
+    /**
+     * Object to wait on when suspending the SSL connection
+     */
+    private Object mSuspendLock = new Object();
+
+    /**
+     * True if the connection is suspended pending the result of asking the
+     * user about an error.
+     */
+    private boolean mSuspended = false;
+
+    /**
+     * True if the connection attempt should be aborted due to an ssl
+     * error.
+     */
+    private boolean mAborted = false;
+
+    /**
+     * Contructor for a https connection.
+     */
+    HttpsConnection(Context context, HttpHost host,
+                    RequestQueue.ConnectionManager connectionManager,
+                    RequestFeeder requestFeeder) {
+        super(context, host, connectionManager, requestFeeder);
+    }
+
+    /**
+     * Sets the server SSL certificate associated with this
+     * connection.
+     * @param certificate The SSL certificate
+     */
+    /* package */ void setCertificate(SslCertificate certificate) {
+        mCertificate = certificate;
+    }
+
+    /**
+     * Opens the connection to a http server or proxy.
+     *
+     * @return the opened low level connection
+     * @throws IOException if the connection fails for any reason.
+     */
+    @Override
+    AndroidHttpClientConnection openConnection(Request req) throws IOException {
+        SSLSocket sslSock = null;
+
+        HttpHost proxyHost = mConnectionManager.getProxyHost();
+        if (proxyHost != null) {
+            // If we have a proxy set, we first send a CONNECT request
+            // to the proxy; if the proxy returns 200 OK, we negotiate
+            // a secure connection to the target server via the proxy.
+            // If the request fails, we drop it, but provide the event
+            // handler with the response status and headers. The event
+            // handler is then responsible for cancelling the load or
+            // issueing a new request.
+            AndroidHttpClientConnection proxyConnection = null;
+            Socket proxySock = null;
+            try {
+                proxySock = new Socket
+                    (proxyHost.getHostName(), proxyHost.getPort());
+
+                proxySock.setSoTimeout(60 * 1000);
+
+                proxyConnection = new AndroidHttpClientConnection();
+                HttpParams params = new BasicHttpParams();
+                HttpConnectionParams.setSocketBufferSize(params, 8192);
+
+                proxyConnection.bind(proxySock, params);
+            } catch(IOException e) {
+                if (proxyConnection != null) {
+                    proxyConnection.close();
+                }
+
+                String errorMessage = e.getMessage();
+                if (errorMessage == null) {
+                    errorMessage =
+                        "failed to establish a connection to the proxy";
+                }
+
+                throw new IOException(errorMessage);
+            }
+
+            StatusLine statusLine = null;
+            int statusCode = 0;
+            Headers headers = new Headers();
+            try {
+                BasicHttpRequest proxyReq = new BasicHttpRequest
+                    ("CONNECT", mHost.toHostString());
+
+                // add all 'proxy' headers from the original request
+                for (Header h : req.mHttpRequest.getAllHeaders()) {
+                    String headerName = h.getName().toLowerCase();
+                    if (headerName.startsWith("proxy") || headerName.equals("keep-alive")) {
+                        proxyReq.addHeader(h);
+                    }
+                }
+
+                proxyConnection.sendRequestHeader(proxyReq);
+                proxyConnection.flush();
+
+                // it is possible to receive informational status
+                // codes prior to receiving actual headers;
+                // all those status codes are smaller than OK 200
+                // a loop is a standard way of dealing with them
+                do {
+                    statusLine = proxyConnection.parseResponseHeader(headers);
+                    statusCode = statusLine.getStatusCode();
+                } while (statusCode < HttpStatus.SC_OK);
+            } catch (ParseException e) {
+                String errorMessage = e.getMessage();
+                if (errorMessage == null) {
+                    errorMessage =
+                        "failed to send a CONNECT request";
+                }
+
+                throw new IOException(errorMessage);
+            } catch (HttpException e) {
+                String errorMessage = e.getMessage();
+                if (errorMessage == null) {
+                    errorMessage =
+                        "failed to send a CONNECT request";
+                }
+
+                throw new IOException(errorMessage);
+            } catch (IOException e) {
+                String errorMessage = e.getMessage();
+                if (errorMessage == null) {
+                    errorMessage =
+                        "failed to send a CONNECT request";
+                }
+
+                throw new IOException(errorMessage);
+            }
+
+            if (statusCode == HttpStatus.SC_OK) {
+                try {
+                    synchronized (mSslSocketFactory) {
+                        sslSock = (SSLSocket) mSslSocketFactory.createSocket(
+                            proxySock, mHost.getHostName(), mHost.getPort(), true);
+                    }
+                } catch(IOException e) {
+                    if (sslSock != null) {
+                        sslSock.close();
+                    }
+
+                    String errorMessage = e.getMessage();
+                    if (errorMessage == null) {
+                        errorMessage =
+                            "failed to create an SSL socket";
+                    }
+                    throw new IOException(errorMessage);
+                }
+            } else {
+                // if the code is not OK, inform the event handler
+                ProtocolVersion version = statusLine.getProtocolVersion();
+
+                req.mEventHandler.status(version.getMajor(),
+                                         version.getMinor(),
+                                         statusCode,
+                                         statusLine.getReasonPhrase());
+                req.mEventHandler.headers(headers);
+                req.mEventHandler.endData();
+
+                proxyConnection.close();
+
+                // here, we return null to indicate that the original
+                // request needs to be dropped
+                return null;
+            }
+        } else {
+            // if we do not have a proxy, we simply connect to the host
+            try {
+                synchronized (mSslSocketFactory) {
+                    sslSock = (SSLSocket) mSslSocketFactory.createSocket();
+                    
+                    sslSock.setSoTimeout(SOCKET_TIMEOUT);
+                    sslSock.connect(new InetSocketAddress(mHost.getHostName(),
+                            mHost.getPort()));
+                    
+                }
+            } catch(IOException e) {
+                if (sslSock != null) {
+                    sslSock.close();
+                }
+
+                String errorMessage = e.getMessage();
+                if (errorMessage == null) {
+                    errorMessage = "failed to create an SSL socket";
+                }
+
+                throw new IOException(errorMessage);
+            }
+        }
+
+        // do handshake and validate server certificates
+        SslError error = CertificateChainValidator.getInstance().
+            doHandshakeAndValidateServerCertificates(this, sslSock, mHost.getHostName());
+
+        EventHandler eventHandler = req.getEventHandler();
+
+        // Update the certificate info (to be consistent, it is better to do it
+        // here, before we start handling SSL errors, if any)
+        eventHandler.certificate(mCertificate);
+
+        // Inform the user if there is a problem
+        if (error != null) {
+            // handleSslErrorRequest may immediately unsuspend if it wants to
+            // allow the certificate anyway.
+            // So we mark the connection as suspended, call handleSslErrorRequest
+            // then check if we're still suspended and only wait if we actually
+            // need to.
+            synchronized (mSuspendLock) {
+                mSuspended = true;
+            }
+            // don't hold the lock while calling out to the event handler
+            eventHandler.handleSslErrorRequest(error);
+            synchronized (mSuspendLock) {
+                if (mSuspended) {
+                    try {
+                        // Put a limit on how long we are waiting; if the timeout
+                        // expires (which should never happen unless you choose
+                        // to ignore the SSL error dialog for a very long time),
+                        // we wake up the thread and abort the request. This is
+                        // to prevent us from stalling the network if things go
+                        // very bad.
+                        mSuspendLock.wait(10 * 60 * 1000);
+                        if (mSuspended) {
+                            // mSuspended is true if we have not had a chance to
+                            // restart the connection yet (ie, the wait timeout
+                            // has expired)
+                            mSuspended = false;
+                            mAborted = true;
+                            if (HttpLog.LOGV) {
+                                HttpLog.v("HttpsConnection.openConnection():" +
+                                          " SSL timeout expired and request was cancelled!!!");
+                            }
+                        }
+                    } catch (InterruptedException e) {
+                        // ignore
+                    }
+                }
+                if (mAborted) {
+                    // The user decided not to use this unverified connection
+                    // so close it immediately.
+                    sslSock.close();
+                    throw new SSLConnectionClosedByUserException("connection closed by the user");
+                }
+            }
+        }
+
+        // All went well, we have an open, verified connection.
+        AndroidHttpClientConnection conn = new AndroidHttpClientConnection();
+        BasicHttpParams params = new BasicHttpParams();
+        params.setIntParameter(HttpConnectionParams.SOCKET_BUFFER_SIZE, 8192);
+        conn.bind(sslSock, params);
+        return conn;
+    }
+
+    /**
+     * Closes the low level connection.
+     *
+     * If an exception is thrown then it is assumed that the connection will
+     * have been closed (to the extent possible) anyway and the caller does not
+     * need to take any further action.
+     *
+     */
+    @Override
+    void closeConnection() {
+        // if the connection has been suspended due to an SSL error
+        if (mSuspended) {
+            // wake up the network thread
+            restartConnection(false);
+        }
+
+        try {
+            if (mHttpClientConnection != null && mHttpClientConnection.isOpen()) {
+                mHttpClientConnection.close();
+            }
+        } catch (IOException e) {
+            if (HttpLog.LOGV)
+                HttpLog.v("HttpsConnection.closeConnection():" +
+                          " failed closing connection " + mHost);
+            e.printStackTrace();
+        }
+    }
+
+    /**
+     * Restart a secure connection suspended waiting for user interaction.
+     */
+    void restartConnection(boolean proceed) {
+        if (HttpLog.LOGV) {
+            HttpLog.v("HttpsConnection.restartConnection():" +
+                      " proceed: " + proceed);
+        }
+
+        synchronized (mSuspendLock) {
+            if (mSuspended) {
+                mSuspended = false;
+                mAborted = !proceed;
+                mSuspendLock.notify();
+            }
+        }
+    }
+
+    @Override
+    String getScheme() {
+        return "https";
+    }
+}
diff --git a/core/java/android/net/http/IdleCache.java b/core/java/android/net/http/IdleCache.java
new file mode 100644
index 0000000..fda6009
--- /dev/null
+++ b/core/java/android/net/http/IdleCache.java
@@ -0,0 +1,175 @@
+/*
+ * 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.
+ */
+
+/**
+ * Hangs onto idle live connections for a little while
+ */
+
+package android.net.http;
+
+import org.apache.http.HttpHost;
+
+import android.os.SystemClock;
+
+/**
+ * {@hide}
+ */
+class IdleCache {
+
+    class Entry {
+        HttpHost mHost;
+        Connection mConnection;
+        long mTimeout;
+    };
+
+    private final static int IDLE_CACHE_MAX = 8;
+
+    /* Allow five consecutive empty queue checks before shutdown */
+    private final static int EMPTY_CHECK_MAX = 5;
+
+    /* six second timeout for connections */
+    private final static int TIMEOUT = 6 * 1000;
+    private final static int CHECK_INTERVAL = 2 * 1000;
+    private Entry[] mEntries = new Entry[IDLE_CACHE_MAX];
+
+    private int mCount = 0;
+
+    private IdleReaper mThread = null;
+
+    /* stats */
+    private int mCached = 0;
+    private int mReused = 0;
+
+    IdleCache() {
+        for (int i = 0; i < IDLE_CACHE_MAX; i++) {
+            mEntries[i] = new Entry();
+        }
+    }
+
+    /**
+     * Caches connection, if there is room.
+     * @return true if connection cached
+     */
+    synchronized boolean cacheConnection(
+            HttpHost host, Connection connection) {
+
+        boolean ret = false;
+
+        if (HttpLog.LOGV) {
+            HttpLog.v("IdleCache size " + mCount + " host "  + host);
+        }
+
+        if (mCount < IDLE_CACHE_MAX) {
+            long time = SystemClock.uptimeMillis();
+            for (int i = 0; i < IDLE_CACHE_MAX; i++) {
+                Entry entry = mEntries[i];
+                if (entry.mHost == null) {
+                    entry.mHost = host;
+                    entry.mConnection = connection;
+                    entry.mTimeout = time + TIMEOUT;
+                    mCount++;
+                    if (HttpLog.LOGV) mCached++;
+                    ret = true;
+                    if (mThread == null) {
+                        mThread = new IdleReaper();
+                        mThread.start();
+                    }
+                    break;
+                }
+            }
+        }
+        return ret;
+    }
+
+    synchronized Connection getConnection(HttpHost host) {
+        Connection ret = null;
+
+        if (mCount > 0) {
+            for (int i = 0; i < IDLE_CACHE_MAX; i++) {
+                Entry entry = mEntries[i];
+                HttpHost eHost = entry.mHost;
+                if (eHost != null && eHost.equals(host)) {
+                    ret = entry.mConnection;
+                    entry.mHost = null;
+                    entry.mConnection = null;
+                    mCount--;
+                    if (HttpLog.LOGV) mReused++;
+                    break;
+                }
+            }
+        }
+        return ret;
+    }
+
+    synchronized void clear() {
+        for (int i = 0; mCount > 0 && i < IDLE_CACHE_MAX; i++) {
+            Entry entry = mEntries[i];
+            if (entry.mHost != null) {
+                entry.mHost = null;
+                entry.mConnection.closeConnection();
+                entry.mConnection = null;
+                mCount--;
+            }
+        }
+    }
+
+    private synchronized void clearIdle() {
+        if (mCount > 0) {
+            long time = SystemClock.uptimeMillis();
+            for (int i = 0; i < IDLE_CACHE_MAX; i++) {
+                Entry entry = mEntries[i];
+                if (entry.mHost != null && time > entry.mTimeout) {
+                    entry.mHost = null;
+                    entry.mConnection.closeConnection();
+                    entry.mConnection = null;
+                    mCount--;
+                }
+            }
+        }
+    }
+
+    private class IdleReaper extends Thread {
+
+        public void run() {
+            int check = 0;
+
+            setName("IdleReaper");
+            android.os.Process.setThreadPriority(
+                    android.os.Process.THREAD_PRIORITY_BACKGROUND);
+            synchronized (IdleCache.this) {
+                while (check < EMPTY_CHECK_MAX) {
+                    try {
+                        IdleCache.this.wait(CHECK_INTERVAL);
+                    } catch (InterruptedException ex) {
+                    }
+                    if (mCount == 0) {
+                        check++;
+                    } else {
+                        check = 0;
+                        clearIdle();
+                    }
+                }
+                mThread = null;
+            }
+            if (HttpLog.LOGV) {
+                HttpLog.v("IdleCache IdleReaper shutdown: cached " + mCached +
+                          " reused " + mReused);
+                mCached = 0;
+                mReused = 0;
+            }
+        }
+    }
+}
diff --git a/core/java/android/net/http/LoggingEventHandler.java b/core/java/android/net/http/LoggingEventHandler.java
new file mode 100644
index 0000000..1b18651
--- /dev/null
+++ b/core/java/android/net/http/LoggingEventHandler.java
@@ -0,0 +1,90 @@
+/*
+ * 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.
+ */
+
+/**
+ * A test EventHandler: Logs everything received
+ */
+
+package android.net.http;
+
+import android.net.http.Headers;
+
+/**
+ * {@hide}
+ */
+public class LoggingEventHandler implements EventHandler {
+
+    public void requestSent() {
+        HttpLog.v("LoggingEventHandler:requestSent()");
+    }
+
+    public void status(int major_version,
+                       int minor_version,
+                       int code, /* Status-Code value */
+                       String reason_phrase) {
+        if (HttpLog.LOGV) {
+            HttpLog.v("LoggingEventHandler:status() major: " + major_version +
+                  " minor: " + minor_version +
+                  " code: " + code +
+                  " reason: " + reason_phrase);
+        }
+    }
+
+    public void headers(Headers headers) {
+        if (HttpLog.LOGV) {
+            HttpLog.v("LoggingEventHandler:headers()");
+            HttpLog.v(headers.toString());
+        }
+    }
+
+    public void locationChanged(String newLocation, boolean permanent) {
+        if (HttpLog.LOGV) {
+            HttpLog.v("LoggingEventHandler: locationChanged() " + newLocation +
+                      " permanent " + permanent);
+        }
+    }
+
+    public void data(byte[] data, int len) {
+        if (HttpLog.LOGV) {
+            HttpLog.v("LoggingEventHandler: data() " + len + " bytes");
+        }
+        // HttpLog.v(new String(data, 0, len));
+    }
+    public void endData() {
+        if (HttpLog.LOGV) {
+            HttpLog.v("LoggingEventHandler: endData() called");
+        }
+    }
+
+    public void certificate(SslCertificate certificate) {
+         if (HttpLog.LOGV) {
+             HttpLog.v("LoggingEventHandler: certificate(): " + certificate);
+         }
+    }
+
+    public void error(int id, String description) {
+        if (HttpLog.LOGV) {
+            HttpLog.v("LoggingEventHandler: error() called Id:" + id +
+                      " description " + description);
+        }
+    }
+
+    public void handleSslErrorRequest(SslError error) {
+        if (HttpLog.LOGV) {
+            HttpLog.v("LoggingEventHandler: handleSslErrorRequest():" + error);
+        }
+    }
+}
diff --git a/core/java/android/net/http/Request.java b/core/java/android/net/http/Request.java
new file mode 100644
index 0000000..df4fff0
--- /dev/null
+++ b/core/java/android/net/http/Request.java
@@ -0,0 +1,462 @@
+/*
+ * 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 android.net.http;
+
+import java.io.EOFException;
+import java.io.InputStream;
+import java.io.IOException;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.zip.GZIPInputStream;
+
+import org.apache.http.entity.InputStreamEntity;
+import org.apache.http.Header;
+import org.apache.http.HttpClientConnection;
+import org.apache.http.HttpEntity;
+import org.apache.http.HttpEntityEnclosingRequest;
+import org.apache.http.HttpException;
+import org.apache.http.HttpHost;
+import org.apache.http.HttpRequest;
+import org.apache.http.HttpResponse;
+import org.apache.http.HttpStatus;
+import org.apache.http.ParseException;
+import org.apache.http.ProtocolVersion;
+
+import org.apache.http.StatusLine;
+import org.apache.http.message.BasicHttpRequest;
+import org.apache.http.message.BasicHttpEntityEnclosingRequest;
+import org.apache.http.protocol.RequestContent;
+
+/**
+ * Represents an HTTP request for a given host.
+ * 
+ * {@hide}
+ */
+
+class Request {
+
+    /** The eventhandler to call as the request progresses */
+    EventHandler mEventHandler;
+
+    private Connection mConnection;
+
+    /** The Apache http request */
+    BasicHttpRequest mHttpRequest;
+
+    /** The path component of this request */
+    String mPath;
+
+    /** Host serving this request */
+    HttpHost mHost;
+
+    /** Set if I'm using a proxy server */
+    HttpHost mProxyHost;
+
+    /** True if request is .html, .js, .css */
+    boolean mHighPriority;
+
+    /** True if request has been cancelled */
+    volatile boolean mCancelled = false;
+
+    int mFailCount = 0;
+
+    private InputStream mBodyProvider;
+    private int mBodyLength;
+
+    private final static String HOST_HEADER = "Host";
+    private final static String ACCEPT_ENCODING_HEADER = "Accept-Encoding";
+    private final static String CONTENT_LENGTH_HEADER = "content-length";
+
+    /* Used to synchronize waitUntilComplete() requests */
+    private final Object mClientResource = new Object();
+
+    /**
+     * Processor used to set content-length and transfer-encoding
+     * headers.
+     */
+    private static RequestContent requestContentProcessor =
+            new RequestContent();
+
+    /**
+     * Instantiates a new Request.
+     * @param method GET/POST/PUT
+     * @param host The server that will handle this request
+     * @param path path part of URI
+     * @param bodyProvider InputStream providing HTTP body, null if none
+     * @param bodyLength length of body, must be 0 if bodyProvider is null
+     * @param eventHandler request will make progress callbacks on
+     * this interface
+     * @param headers reqeust headers
+     * @param highPriority true for .html, css, .cs
+     */
+    Request(String method, HttpHost host, HttpHost proxyHost, String path,
+            InputStream bodyProvider, int bodyLength,
+            EventHandler eventHandler,
+            Map<String, String> headers, boolean highPriority) {
+        mEventHandler = eventHandler;
+        mHost = host;
+        mProxyHost = proxyHost;
+        mPath = path;
+        mHighPriority = highPriority;
+        mBodyProvider = bodyProvider;
+        mBodyLength = bodyLength;
+
+        if (bodyProvider == null) {
+            mHttpRequest = new BasicHttpRequest(method, getUri());
+        } else {
+            mHttpRequest = new BasicHttpEntityEnclosingRequest(
+                    method, getUri());
+            setBodyProvider(bodyProvider, bodyLength);
+        }
+        addHeader(HOST_HEADER, getHostPort());
+
+        /* FIXME: if webcore will make the root document a
+           high-priority request, we can ask for gzip encoding only on
+           high priority reqs (saving the trouble for images, etc) */
+        addHeader(ACCEPT_ENCODING_HEADER, "gzip");
+        addHeaders(headers);
+    }
+
+    /**
+     * @param connection Request served by this connection
+     */
+    void setConnection(Connection connection) {
+        mConnection = connection;
+    }
+
+    /* package */ EventHandler getEventHandler() {
+        return mEventHandler;
+    }
+
+    /**
+     * Add header represented by given pair to request.  Header will
+     * be formatted in request as "name: value\r\n".
+     * @param name of header
+     * @param value of header
+     */
+    void addHeader(String name, String value) {
+        if (name == null) {
+            String damage = "Null http header name";
+            HttpLog.e(damage);
+            throw new NullPointerException(damage);
+        }
+        if (value == null || value.length() == 0) {
+            String damage = "Null or empty value for header \"" + name + "\"";
+            HttpLog.e(damage);
+            throw new RuntimeException(damage);
+        }
+        mHttpRequest.addHeader(name, value);
+    }
+
+    /**
+     * Add all headers in given map to this request.  This is a helper
+     * method: it calls addHeader for each pair in the map.
+     */
+    void addHeaders(Map<String, String> headers) {
+        if (headers == null) {
+            return;
+        }
+
+        Entry<String, String> entry;
+        Iterator<Entry<String, String>> i = headers.entrySet().iterator();
+        while (i.hasNext()) {
+            entry = i.next();
+            addHeader(entry.getKey(), entry.getValue());
+        }
+    }
+
+    /**
+     * Send the request line and headers
+     */
+    void sendRequest(AndroidHttpClientConnection httpClientConnection)
+            throws HttpException, IOException {
+
+        if (mCancelled) return; // don't send cancelled requests
+
+        if (HttpLog.LOGV) {
+            HttpLog.v("Request.sendRequest() " + mHost.getSchemeName() + "://" + getHostPort());
+            // HttpLog.v(mHttpRequest.getRequestLine().toString());
+            if (false) {
+                Iterator i = mHttpRequest.headerIterator();
+                while (i.hasNext()) {
+                    Header header = (Header)i.next();
+                    HttpLog.v(header.getName() + ": " + header.getValue());
+                }
+            }
+        }
+
+        requestContentProcessor.process(mHttpRequest,
+                                        mConnection.getHttpContext());
+        httpClientConnection.sendRequestHeader(mHttpRequest);
+        if (mHttpRequest instanceof HttpEntityEnclosingRequest) {
+            httpClientConnection.sendRequestEntity(
+                    (HttpEntityEnclosingRequest) mHttpRequest);
+        }
+
+        if (HttpLog.LOGV) {
+            HttpLog.v("Request.requestSent() " + mHost.getSchemeName() + "://" + getHostPort() + mPath);
+        }
+    }
+
+
+    /**
+     * Receive a single http response.
+     *
+     * @param httpClientConnection the request to receive the response for.
+     */
+    void readResponse(AndroidHttpClientConnection httpClientConnection)
+            throws IOException, ParseException {
+
+        if (mCancelled) return; // don't send cancelled requests
+
+        StatusLine statusLine = null;
+        boolean hasBody = false;
+        boolean reuse = false;
+        httpClientConnection.flush();
+        int statusCode = 0;
+
+        Headers header = new Headers();
+        do {
+            statusLine = httpClientConnection.parseResponseHeader(header);
+            statusCode = statusLine.getStatusCode();
+        } while (statusCode < HttpStatus.SC_OK);
+        if (HttpLog.LOGV) HttpLog.v(
+                "Request.readResponseStatus() " +
+                statusLine.toString().length() + " " + statusLine);
+
+        ProtocolVersion v = statusLine.getProtocolVersion();
+        mEventHandler.status(v.getMajor(), v.getMinor(),
+                statusCode, statusLine.getReasonPhrase());
+        mEventHandler.headers(header);
+        HttpEntity entity = null;
+        hasBody = canResponseHaveBody(mHttpRequest, statusCode);
+
+        if (hasBody)
+            entity = httpClientConnection.receiveResponseEntity(header);
+
+        if (entity != null) {
+            InputStream is = entity.getContent();
+
+            // process gzip content encoding
+            Header contentEncoding = entity.getContentEncoding();
+            InputStream nis = null;
+            try {
+                if (contentEncoding != null &&
+                    contentEncoding.getValue().equals("gzip")) {
+                    nis = new GZIPInputStream(is);
+                } else {
+                    nis = is;
+                }
+
+                /* accumulate enough data to make it worth pushing it
+                 * up the stack */
+                byte[] buf = mConnection.getBuf();
+                int len = 0;
+                int count = 0;
+                int lowWater = buf.length / 2;
+                while (len != -1) {
+                    len = nis.read(buf, count, buf.length - count);
+                    if (len != -1) {
+                        count += len;
+                    }
+                    if (len == -1 || count >= lowWater) {
+                        if (HttpLog.LOGV) HttpLog.v("Request.readResponse() " + count);
+                        mEventHandler.data(buf, count);
+                        count = 0;
+                    }
+                }
+            } catch (EOFException e) {
+                /* InflaterInputStream throws an EOFException when the
+                   server truncates gzipped content.  Handle this case
+                   as we do truncated non-gzipped content: no error */
+                if (HttpLog.LOGV) HttpLog.v( "readResponse() handling " + e);
+            } catch(IOException e) {
+                // don't throw if we have a non-OK status code
+                if (statusCode == HttpStatus.SC_OK) {
+                    throw e;
+                }
+            } finally {
+                if (nis != null) {
+                    nis.close();
+                }
+            }
+        }
+        mConnection.setCanPersist(entity, statusLine.getProtocolVersion(),
+                header.getConnectionType());
+        mEventHandler.endData();
+        complete();
+
+        if (HttpLog.LOGV) HttpLog.v("Request.readResponse(): done " +
+                                    mHost.getSchemeName() + "://" + getHostPort() + mPath);
+    }
+
+    /**
+     * Data will not be sent to or received from server after cancel()
+     * call.  Does not close connection--use close() below for that.
+     *
+     * Called by RequestHandle from non-network thread
+     */
+    void cancel() {
+        if (HttpLog.LOGV) {
+            HttpLog.v("Request.cancel(): " + getUri());
+        }
+        mCancelled = true;
+        if (mConnection != null) {
+            mConnection.cancel();
+        }
+    }
+
+    String getHostPort() {
+        String myScheme = mHost.getSchemeName();
+        int myPort = mHost.getPort();
+
+        // Only send port when we must... many servers can't deal with it
+        if (myPort != 80 && myScheme.equals("http") ||
+            myPort != 443 && myScheme.equals("https")) {
+            return mHost.toHostString();
+        } else {
+            return mHost.getHostName();
+        }
+    }
+
+    String getUri() {
+        if (mProxyHost == null ||
+            mHost.getSchemeName().equals("https")) {
+            return mPath;
+        }
+        return mHost.getSchemeName() + "://" + getHostPort() + mPath;
+    }
+
+    /**
+     * for debugging
+     */
+    public String toString() {
+        return (mHighPriority ? "P*" : "") + mPath;
+    }
+
+
+    /**
+     * If this request has been sent once and failed, it must be reset
+     * before it can be sent again.
+     */
+    void reset() {
+        /* clear content-length header */
+        mHttpRequest.removeHeaders(CONTENT_LENGTH_HEADER);
+
+        if (mBodyProvider != null) {
+            try {
+                mBodyProvider.reset();
+            } catch (IOException ex) {
+                if (HttpLog.LOGV) HttpLog.v(
+                        "failed to reset body provider " +
+                        getUri());
+            }
+            setBodyProvider(mBodyProvider, mBodyLength);
+        }
+    }
+
+    /**
+     * Pause thread request completes.  Used for synchronous requests,
+     * and testing
+     */
+    void waitUntilComplete() {
+        synchronized (mClientResource) {
+            try {
+                if (HttpLog.LOGV) HttpLog.v("Request.waitUntilComplete()");
+                mClientResource.wait();
+                if (HttpLog.LOGV) HttpLog.v("Request.waitUntilComplete() done waiting");
+            } catch (InterruptedException e) {
+            }
+        }
+    }
+
+    void complete() {
+        synchronized (mClientResource) {
+            mClientResource.notifyAll();
+        }
+    }
+
+    /**
+     * Decide whether a response comes with an entity.
+     * The implementation in this class is based on RFC 2616.
+     * Unknown methods and response codes are supposed to
+     * indicate responses with an entity.
+     * <br/>
+     * Derived executors can override this method to handle
+     * methods and response codes not specified in RFC 2616.
+     *
+     * @param request   the request, to obtain the executed method
+     * @param response  the response, to obtain the status code
+     */
+
+    private static boolean canResponseHaveBody(final HttpRequest request,
+                                               final int status) {
+
+        if ("HEAD".equalsIgnoreCase(request.getRequestLine().getMethod())) {
+            return false;
+        }
+        return status >= HttpStatus.SC_OK
+            && status != HttpStatus.SC_NO_CONTENT
+            && status != HttpStatus.SC_NOT_MODIFIED
+            && status != HttpStatus.SC_RESET_CONTENT;
+    }
+
+    /**
+     * Supply an InputStream that provides the body of a request.  It's
+     * not great that the caller must also provide the length of the data
+     * returned by that InputStream, but the client needs to know up
+     * front, and I'm not sure how to get this out of the InputStream
+     * itself without a costly readthrough.  I'm not sure skip() would
+     * do what we want.  If you know a better way, please let me know.
+     */
+    private void setBodyProvider(InputStream bodyProvider, int bodyLength) {
+        if (!bodyProvider.markSupported()) {
+            throw new IllegalArgumentException(
+                    "bodyProvider must support mark()");
+        }
+        // Mark beginning of stream
+        bodyProvider.mark(Integer.MAX_VALUE);
+
+        ((BasicHttpEntityEnclosingRequest)mHttpRequest).setEntity(
+                new InputStreamEntity(bodyProvider, bodyLength));
+    }
+
+
+    /**
+     * Handles SSL error(s) on the way down from the user (the user
+     * has already provided their feedback).
+     */
+    public void handleSslErrorResponse(boolean proceed) {
+        HttpsConnection connection = (HttpsConnection)(mConnection);
+        if (connection != null) {
+            connection.restartConnection(proceed);
+        }
+    }
+
+    /**
+     * Helper: calls error() on eventhandler with appropriate message
+     * This should not be called before the mConnection is set.
+     */
+    void error(int errorId, int resourceId) {
+        mEventHandler.error(
+                errorId,
+                mConnection.mContext.getText(
+                        resourceId).toString());
+    }
+
+}
diff --git a/core/java/android/net/http/RequestFeeder.java b/core/java/android/net/http/RequestFeeder.java
new file mode 100644
index 0000000..34ca267
--- /dev/null
+++ b/core/java/android/net/http/RequestFeeder.java
@@ -0,0 +1,42 @@
+/*
+ * 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.
+ */
+
+/**
+ * Supplies Requests to a Connection
+ */
+
+package android.net.http;
+
+import org.apache.http.HttpHost;
+
+/**
+ * {@hide}
+ */
+interface RequestFeeder {
+
+    Request getRequest();
+    Request getRequest(HttpHost host);
+
+    /**
+     * @return true if a request for this host is available
+     */
+    boolean haveRequest(HttpHost host);
+
+    /**
+     * Put request back on head of queue
+     */
+    void requeueRequest(Request request);
+}
diff --git a/core/java/android/net/http/RequestHandle.java b/core/java/android/net/http/RequestHandle.java
new file mode 100644
index 0000000..c4ee5b0
--- /dev/null
+++ b/core/java/android/net/http/RequestHandle.java
@@ -0,0 +1,424 @@
+/*
+ * 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 android.net.http;
+
+import android.net.ParseException;
+import android.net.WebAddress;
+import android.security.Md5MessageDigest;
+import junit.framework.Assert;
+import android.webkit.CookieManager;
+
+import org.apache.commons.codec.binary.Base64;
+
+import java.io.InputStream;
+import java.lang.Math;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Random;
+
+/**
+ * RequestHandle: handles a request session that may include multiple
+ * redirects, HTTP authentication requests, etc.
+ * 
+ * {@hide}
+ */
+public class RequestHandle {
+
+    private String        mUrl;
+    private WebAddress    mUri;
+    private String        mMethod;
+    private Map<String, String> mHeaders;
+
+    private RequestQueue  mRequestQueue;
+
+    private Request       mRequest;
+
+    private InputStream   mBodyProvider;
+    private int           mBodyLength;
+
+    private int           mRedirectCount = 0;
+
+    private final static String AUTHORIZATION_HEADER = "Authorization";
+    private final static String PROXY_AUTHORIZATION_HEADER = "Proxy-Authorization";
+
+    public final static int MAX_REDIRECT_COUNT = 16;
+
+    /**
+     * Creates a new request session.
+     */
+    public RequestHandle(RequestQueue requestQueue, String url, WebAddress uri,
+            String method, Map<String, String> headers,
+            InputStream bodyProvider, int bodyLength, Request request) {
+
+        if (headers == null) {
+            headers = new HashMap<String, String>();
+        }
+        mHeaders = headers;
+        mBodyProvider = bodyProvider;
+        mBodyLength = bodyLength;
+        mMethod = method == null? "GET" : method;
+
+        mUrl = url;
+        mUri = uri;
+
+        mRequestQueue = requestQueue;
+
+        mRequest = request;
+    }
+
+    /**
+     * Cancels this request
+     */
+    public void cancel() {
+        if (mRequest != null) {
+            mRequest.cancel();
+        }
+    }
+
+    /**
+     * Handles SSL error(s) on the way down from the user (the user
+     * has already provided their feedback).
+     */
+    public void handleSslErrorResponse(boolean proceed) {
+        if (mRequest != null) {
+            mRequest.handleSslErrorResponse(proceed);
+        }
+    }
+
+    /**
+     * @return true if we've hit the max redirect count
+     */
+    public boolean isRedirectMax() {
+        return mRedirectCount >= MAX_REDIRECT_COUNT;
+    }
+
+    public int getRedirectCount() {
+        return mRedirectCount;
+    }
+
+    public void setRedirectCount(int count) {
+        mRedirectCount = count;
+    }
+
+    /**
+     * Create and queue a redirect request.
+     *
+     * @param redirectTo URL to redirect to
+     * @param statusCode HTTP status code returned from original request
+     * @param cacheHeaders Cache header for redirect URL
+     * @return true if setup succeeds, false otherwise (redirect loop
+     * count exceeded, body provider unable to rewind on 307 redirect)
+     */
+    public boolean setupRedirect(String redirectTo, int statusCode,
+            Map<String, String> cacheHeaders) {
+        if (HttpLog.LOGV) {
+            HttpLog.v("RequestHandle.setupRedirect(): redirectCount " +
+                  mRedirectCount);
+        }
+
+        // be careful and remove authentication headers, if any
+        mHeaders.remove(AUTHORIZATION_HEADER);
+        mHeaders.remove(PROXY_AUTHORIZATION_HEADER);
+
+        if (++mRedirectCount == MAX_REDIRECT_COUNT) {
+            // Way too many redirects -- fail out
+            if (HttpLog.LOGV) HttpLog.v(
+                    "RequestHandle.setupRedirect(): too many redirects " +
+                    mRequest);
+            mRequest.error(EventHandler.ERROR_REDIRECT_LOOP,
+                           com.android.internal.R.string.httpErrorRedirectLoop);
+            return false;
+        }
+
+        if (mUrl.startsWith("https:") && redirectTo.startsWith("http:")) {
+            // implement http://www.w3.org/Protocols/rfc2616/rfc2616-sec15.html#sec15.1.3
+            if (HttpLog.LOGV) {
+                HttpLog.v("blowing away the referer on an https -> http redirect");
+            }
+            mHeaders.remove("Referer");
+        }
+
+        mUrl = redirectTo;
+        try {
+            mUri = new WebAddress(mUrl);
+        } catch (ParseException e) {
+            e.printStackTrace();
+        }
+
+        // update the "cookie" header based on the redirected url
+        mHeaders.remove("cookie");
+        String cookie = CookieManager.getInstance().getCookie(mUri);
+        if (cookie != null && cookie.length() > 0) {
+            mHeaders.put("cookie", cookie);
+        }
+
+        if ((statusCode == 302 || statusCode == 303) && mMethod.equals("POST")) {
+            if (HttpLog.LOGV) {
+                HttpLog.v("replacing POST with GET on redirect to " + redirectTo);
+            }
+            mMethod = "GET";
+        }
+        /* Only repost content on a 307.  If 307, reset the body
+           provider so we can replay the body */
+        if (statusCode == 307) {
+            try {
+                if (mBodyProvider != null) mBodyProvider.reset();
+            } catch (java.io.IOException ex) {
+                if (HttpLog.LOGV) {
+                    HttpLog.v("setupAuthResponse() failed to reset body provider");
+                }
+                return false;
+            }
+
+        } else {
+            mHeaders.remove("Content-Type");
+            mBodyProvider = null;
+        }
+
+        // Update the cache headers for this URL
+        mHeaders.putAll(cacheHeaders);
+
+        createAndQueueNewRequest();
+        return true;
+    }
+
+    /**
+     * Create and queue an HTTP authentication-response (basic) request.
+     */
+    public void setupBasicAuthResponse(boolean isProxy, String username, String password) {
+        String response = computeBasicAuthResponse(username, password);
+        if (HttpLog.LOGV) {
+            HttpLog.v("setupBasicAuthResponse(): response: " + response);
+        }
+        mHeaders.put(authorizationHeader(isProxy), "Basic " + response);
+        setupAuthResponse();
+    }
+
+    /**
+     * Create and queue an HTTP authentication-response (digest) request.
+     */
+    public void setupDigestAuthResponse(boolean isProxy,
+                                        String username,
+                                        String password,
+                                        String realm,
+                                        String nonce,
+                                        String QOP,
+                                        String algorithm,
+                                        String opaque) {
+
+        String response = computeDigestAuthResponse(
+                username, password, realm, nonce, QOP, algorithm, opaque);
+        if (HttpLog.LOGV) {
+            HttpLog.v("setupDigestAuthResponse(): response: " + response);
+        }
+        mHeaders.put(authorizationHeader(isProxy), "Digest " + response);
+        setupAuthResponse();
+    }
+
+    private void setupAuthResponse() {
+        try {
+            if (mBodyProvider != null) mBodyProvider.reset();
+        } catch (java.io.IOException ex) {
+            if (HttpLog.LOGV) {
+                HttpLog.v("setupAuthResponse() failed to reset body provider");
+            }
+        }
+        createAndQueueNewRequest();
+    }
+
+    /**
+     * @return HTTP request method (GET, PUT, etc).
+     */
+    public String getMethod() {
+        return mMethod;
+    }
+
+    /**
+     * @return Basic-scheme authentication response: BASE64(username:password).
+     */
+    public static String computeBasicAuthResponse(String username, String password) {
+        Assert.assertNotNull(username);
+        Assert.assertNotNull(password);
+
+        // encode username:password to base64
+        return new String(Base64.encodeBase64((username + ':' + password).getBytes()));
+    }
+
+    public void waitUntilComplete() {
+        mRequest.waitUntilComplete();
+    }
+
+    /**
+     * @return Digest-scheme authentication response.
+     */
+    private String computeDigestAuthResponse(String username,
+                                             String password,
+                                             String realm,
+                                             String nonce,
+                                             String QOP,
+                                             String algorithm,
+                                             String opaque) {
+
+        Assert.assertNotNull(username);
+        Assert.assertNotNull(password);
+        Assert.assertNotNull(realm);
+
+        String A1 = username + ":" + realm + ":" + password;
+        String A2 = mMethod  + ":" + mUrl;
+
+        // because we do not preemptively send authorization headers, nc is always 1
+        String nc = "000001";
+        String cnonce = computeCnonce();
+        String digest = computeDigest(A1, A2, nonce, QOP, nc, cnonce);
+
+        String response = "";
+        response += "username=" + doubleQuote(username) + ", ";
+        response += "realm="    + doubleQuote(realm)    + ", ";
+        response += "nonce="    + doubleQuote(nonce)    + ", ";
+        response += "uri="      + doubleQuote(mUrl)     + ", ";
+        response += "response=" + doubleQuote(digest) ;
+
+        if (opaque     != null) {
+            response += ", opaque=" + doubleQuote(opaque);
+        }
+
+         if (algorithm != null) {
+            response += ", algorithm=" +  algorithm;
+        }
+
+        if (QOP        != null) {
+            response += ", qop=" + QOP + ", nc=" + nc + ", cnonce=" + doubleQuote(cnonce);
+        }
+
+        return response;
+    }
+
+    /**
+     * @return The right authorization header (dependeing on whether it is a proxy or not).
+     */
+    public static String authorizationHeader(boolean isProxy) {
+        if (!isProxy) {
+            return AUTHORIZATION_HEADER;
+        } else {
+            return PROXY_AUTHORIZATION_HEADER;
+        }
+    }
+
+    /**
+     * @return Double-quoted MD5 digest.
+     */
+    private String computeDigest(
+        String A1, String A2, String nonce, String QOP, String nc, String cnonce) {
+        if (HttpLog.LOGV) {
+            HttpLog.v("computeDigest(): QOP: " + QOP);
+        }
+
+        if (QOP == null) {
+            return KD(H(A1), nonce + ":" + H(A2));
+        } else {
+            if (QOP.equalsIgnoreCase("auth")) {
+                return KD(H(A1), nonce + ":" + nc + ":" + cnonce + ":" + QOP + ":" + H(A2));
+            }
+        }
+
+        return null;
+    }
+
+    /**
+     * @return MD5 hash of concat(secret, ":", data).
+     */
+    private String KD(String secret, String data) {
+        return H(secret + ":" + data);
+    }
+
+    /**
+     * @return MD5 hash of param.
+     */
+    private String H(String param) {
+        if (param != null) {
+            Md5MessageDigest md5 = new Md5MessageDigest();
+
+            byte[] d = md5.digest(param.getBytes());
+            if (d != null) {
+                return bufferToHex(d);
+            }
+        }
+
+        return null;
+    }
+
+    /**
+     * @return HEX buffer representation.
+     */
+    private String bufferToHex(byte[] buffer) {
+        final char hexChars[] =
+            { '0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f' };
+
+        if (buffer != null) {
+            int length = buffer.length;
+            if (length > 0) {
+                StringBuilder hex = new StringBuilder(2 * length);
+
+                for (int i = 0; i < length; ++i) {
+                    byte l = (byte) (buffer[i] & 0x0F);
+                    byte h = (byte)((buffer[i] & 0xF0) >> 4);
+
+                    hex.append(hexChars[h]);
+                    hex.append(hexChars[l]);
+                }
+
+                return hex.toString();
+            } else {
+                return "";
+            }
+        }
+
+        return null;
+    }
+
+    /**
+     * Computes a random cnonce value based on the current time.
+     */
+    private String computeCnonce() {
+        Random rand = new Random();
+        int nextInt = rand.nextInt();
+        nextInt = (nextInt == Integer.MIN_VALUE) ?
+                Integer.MAX_VALUE : Math.abs(nextInt);
+        return Integer.toString(nextInt, 16);
+    }
+
+    /**
+     * "Double-quotes" the argument.
+     */
+    private String doubleQuote(String param) {
+        if (param != null) {
+            return "\"" + param + "\"";
+        }
+
+        return null;
+    }
+
+    /**
+     * Creates and queues new request.
+     */
+    private void createAndQueueNewRequest() {
+        mRequest = mRequestQueue.queueRequest(
+                mUrl, mUri, mMethod, mHeaders, mRequest.mEventHandler,
+                mBodyProvider,
+                mBodyLength, mRequest.mHighPriority).mRequest;
+    }
+}
diff --git a/core/java/android/net/http/RequestQueue.java b/core/java/android/net/http/RequestQueue.java
new file mode 100644
index 0000000..66d5722
--- /dev/null
+++ b/core/java/android/net/http/RequestQueue.java
@@ -0,0 +1,647 @@
+/*
+ * 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.
+ */
+
+/**
+ * High level HTTP Interface
+ * Queues requests as necessary
+ */
+
+package android.net.http;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.net.ConnectivityManager;
+import android.net.NetworkConnectivityListener;
+import android.net.NetworkInfo;
+import android.net.Proxy;
+import android.net.WebAddress;
+import android.os.Handler;
+import android.os.Message;
+import android.os.SystemProperties;
+import android.text.TextUtils;
+import android.util.Log;
+
+import java.io.InputStream;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.LinkedList;
+import java.util.ListIterator;
+import java.util.Map;
+
+import org.apache.http.HttpHost;
+
+/**
+ * {@hide}
+ */
+public class RequestQueue implements RequestFeeder {
+
+    private Context mContext;
+
+    /**
+     * Requests, indexed by HttpHost (scheme, host, port)
+     */
+    private LinkedHashMap<HttpHost, LinkedList<Request>> mPending;
+
+    /* Support for notifying a client when queue is empty */
+    private boolean mClientWaiting = false;
+
+    /** true if connected */
+    boolean mNetworkConnected = true;
+
+    private HttpHost mProxyHost = null;
+    private BroadcastReceiver mProxyChangeReceiver;
+
+    private ActivePool mActivePool;
+
+    /* default simultaneous connection count */
+    private static final int CONNECTION_COUNT = 4;
+
+    /**
+     * This intent broadcast when http is paused or unpaused due to
+     * net availability toggling
+     */
+    public final static String HTTP_NETWORK_STATE_CHANGED_INTENT =
+            "android.net.http.NETWORK_STATE";
+    public final static String HTTP_NETWORK_STATE_UP = "up";
+
+    /**
+     * Listen to platform network state.  On a change,
+     * (1) kick stack on or off as appropriate
+     * (2) send an intent to my host app telling
+     *     it what I've done
+     */
+    private NetworkStateTracker mNetworkStateTracker;
+    class NetworkStateTracker {
+
+        final static int EVENT_DATA_STATE_CHANGED = 100;
+
+        Context mContext;
+        NetworkConnectivityListener mConnectivityListener;
+        NetworkInfo.State mLastNetworkState = NetworkInfo.State.CONNECTED;
+        int mCurrentNetworkType;
+
+        NetworkStateTracker(Context context) {
+            mContext = context;
+        }
+
+        /**
+         * register for updates
+         */
+        protected void enable() {
+            if (mConnectivityListener == null) {
+                /*
+                 * Initializing the network type is really unnecessary,
+                 * since as soon as we register with the NCL, we'll
+                 * get a CONNECTED event for the active network, and
+                 * we'll configure the HTTP proxy accordingly. However,
+                 * as a fallback in case that doesn't happen for some
+                 * reason, initializing to type WIFI would mean that
+                 * we'd start out without a proxy. This seems better
+                 * than thinking we have a proxy (which is probably
+                 * private to the carrier network and therefore
+                 * unreachable outside of that network) when we really
+                 * shouldn't.
+                 */
+                mCurrentNetworkType = ConnectivityManager.TYPE_WIFI;
+                mConnectivityListener = new NetworkConnectivityListener();
+                mConnectivityListener.registerHandler(mHandler, EVENT_DATA_STATE_CHANGED);
+                mConnectivityListener.startListening(mContext);
+            }
+        }
+
+        protected void disable() {
+            if (mConnectivityListener != null) {
+                mConnectivityListener.unregisterHandler(mHandler);
+                mConnectivityListener.stopListening();
+                mConnectivityListener = null;
+            }
+        }
+
+        private Handler mHandler = new Handler() {
+            public void handleMessage(Message msg) {
+                switch (msg.what) {
+                    case EVENT_DATA_STATE_CHANGED:
+                        networkStateChanged();
+                        break;
+                }
+            }
+        };
+
+        int getCurrentNetworkType() {
+            return mCurrentNetworkType;
+        }
+
+        void networkStateChanged() {
+            if (mConnectivityListener == null)
+                return;
+
+            
+            NetworkConnectivityListener.State connectivityState = mConnectivityListener.getState();
+            NetworkInfo info = mConnectivityListener.getNetworkInfo();
+            if (info == null) {
+                /**
+                 * We've been seeing occasional NPEs here. I believe recent changes
+                 * have made this impossible, but in the interest of being totally
+                 * paranoid, check and log this here.
+                 */
+                HttpLog.v("NetworkStateTracker: connectivity broadcast"
+                    + " has null network info - ignoring");
+                return;
+            }
+            NetworkInfo.State state = info.getState();
+
+            if (HttpLog.LOGV) {
+                HttpLog.v("NetworkStateTracker " + info.getTypeName() +
+                " state= " + state + " last= " + mLastNetworkState +
+                " connectivityState= " + connectivityState.toString());
+            }
+
+            boolean newConnection =
+                state != mLastNetworkState && state == NetworkInfo.State.CONNECTED;
+
+            if (state == NetworkInfo.State.CONNECTED) {
+                mCurrentNetworkType = info.getType();
+                setProxyConfig();
+            }
+
+            mLastNetworkState = state;
+            if (connectivityState == NetworkConnectivityListener.State.NOT_CONNECTED) {
+                setNetworkState(false);
+                broadcastState(false);
+            } else if (newConnection) {
+                setNetworkState(true);
+                broadcastState(true);
+            }
+
+        }
+
+        void broadcastState(boolean connected) {
+            Intent intent = new Intent(HTTP_NETWORK_STATE_CHANGED_INTENT);
+            intent.putExtra(HTTP_NETWORK_STATE_UP, connected);
+            mContext.sendBroadcast(intent);
+        }
+    }
+
+    /**
+     * This class maintains active connection threads
+     */
+    class ActivePool implements ConnectionManager {
+        /** Threads used to process requests */
+        ConnectionThread[] mThreads;
+
+        IdleCache mIdleCache;
+
+        private int mTotalRequest;
+        private int mTotalConnection;
+        private int mConnectionCount;
+
+        ActivePool(int connectionCount) {
+            mIdleCache = new IdleCache();
+            mConnectionCount = connectionCount;
+            mThreads = new ConnectionThread[mConnectionCount];
+
+            for (int i = 0; i < mConnectionCount; i++) {
+                mThreads[i] = new ConnectionThread(
+                        mContext, i, this, RequestQueue.this);
+            }
+        }
+
+        void startup() {
+            for (int i = 0; i < mConnectionCount; i++) {
+                mThreads[i].start();
+            }
+        }
+
+        void shutdown() {
+            for (int i = 0; i < mConnectionCount; i++) {
+                mThreads[i].requestStop();
+            }
+        }
+
+        public boolean isNetworkConnected() {
+            return mNetworkConnected;
+        }
+
+        void startConnectionThread() {
+            synchronized (RequestQueue.this) {
+                RequestQueue.this.notify();
+            }
+        }
+
+        public void startTiming() {
+            for (int i = 0; i < mConnectionCount; i++) {
+                mThreads[i].mStartThreadTime = mThreads[i].mCurrentThreadTime;
+            }
+            mTotalRequest = 0;
+            mTotalConnection = 0;
+        }
+
+        public void stopTiming() {
+            int totalTime = 0;
+            for (int i = 0; i < mConnectionCount; i++) {
+                ConnectionThread rt = mThreads[i];
+                totalTime += (rt.mCurrentThreadTime - rt.mStartThreadTime);
+                rt.mStartThreadTime = -1;
+            }
+            Log.d("Http", "Http thread used " + totalTime + " ms " + " for "
+                    + mTotalRequest + " requests and " + mTotalConnection
+                    + " connections");
+        }
+
+        void logState() {
+            StringBuilder dump = new StringBuilder();
+            for (int i = 0; i < mConnectionCount; i++) {
+                dump.append(mThreads[i] + "\n");
+            }
+            HttpLog.v(dump.toString());
+        }
+
+
+        public HttpHost getProxyHost() {
+            return mProxyHost;
+        }
+
+        /**
+         * Turns off persistence on all live connections
+         */
+        void disablePersistence() {
+            for (int i = 0; i < mConnectionCount; i++) {
+                Connection connection = mThreads[i].mConnection;
+                if (connection != null) connection.setCanPersist(false);
+            }
+            mIdleCache.clear();
+        }
+
+        /* Linear lookup -- okay for small thread counts.  Might use
+           private HashMap<HttpHost, LinkedList<ConnectionThread>> mActiveMap;
+           if this turns out to be a hotspot */
+        ConnectionThread getThread(HttpHost host) {
+            synchronized(RequestQueue.this) {
+                for (int i = 0; i < mThreads.length; i++) {
+                    ConnectionThread ct = mThreads[i];
+                    Connection connection = ct.mConnection;
+                    if (connection != null && connection.mHost.equals(host)) {
+                        return ct;
+                    }
+                }
+            }
+            return null;
+        }
+
+        public Connection getConnection(Context context, HttpHost host) {
+            Connection con = mIdleCache.getConnection(host);
+            if (con == null) {
+                mTotalConnection++;
+                con = Connection.getConnection(
+                        mContext, host, this, RequestQueue.this);
+            }
+            return con;
+        }
+        public boolean recycleConnection(HttpHost host, Connection connection) {
+            return mIdleCache.cacheConnection(host, connection);
+        }
+
+    }
+
+    /**
+     * A RequestQueue class instance maintains a set of queued
+     * requests.  It orders them, makes the requests against HTTP
+     * servers, and makes callbacks to supplied eventHandlers as data
+     * is read.  It supports request prioritization, connection reuse
+     * and pipelining.
+     *
+     * @param context application context
+     */
+    public RequestQueue(Context context) {
+        this(context, CONNECTION_COUNT);
+    }
+
+    /**
+     * A RequestQueue class instance maintains a set of queued
+     * requests.  It orders them, makes the requests against HTTP
+     * servers, and makes callbacks to supplied eventHandlers as data
+     * is read.  It supports request prioritization, connection reuse
+     * and pipelining.
+     *
+     * @param context application context
+     * @param connectionCount The number of simultaneous connections 
+     */
+    public RequestQueue(Context context, int connectionCount) {
+        mContext = context;
+
+        mPending = new LinkedHashMap<HttpHost, LinkedList<Request>>(32);
+
+        mActivePool = new ActivePool(connectionCount);
+        mActivePool.startup();
+    }
+
+    /**
+     * Enables data state and proxy tracking
+     */
+    public synchronized void enablePlatformNotifications() {
+        if (HttpLog.LOGV) HttpLog.v("RequestQueue.enablePlatformNotifications() network");
+
+        if (mProxyChangeReceiver == null) {
+            mProxyChangeReceiver =
+                    new BroadcastReceiver() {
+                        @Override
+                        public void onReceive(Context ctx, Intent intent) {
+                            setProxyConfig();
+                        }
+                    };
+            mContext.registerReceiver(mProxyChangeReceiver,
+                                      new IntentFilter(Proxy.PROXY_CHANGE_ACTION));
+        }
+
+        /* Network state notification is broken on the simulator
+           don't register for notifications on SIM */
+        String device = SystemProperties.get("ro.product.device");
+        boolean simulation = TextUtils.isEmpty(device);
+
+        if (!simulation) {
+            if (mNetworkStateTracker == null) {
+                mNetworkStateTracker = new NetworkStateTracker(mContext);
+            }
+            mNetworkStateTracker.enable();
+        }
+    }
+
+    /**
+     * If platform notifications have been enabled, call this method
+     * to disable before destroying RequestQueue
+     */
+    public synchronized void disablePlatformNotifications() {
+        if (HttpLog.LOGV) HttpLog.v("RequestQueue.disablePlatformNotifications() network");
+
+        if (mNetworkStateTracker != null) {
+            mNetworkStateTracker.disable();
+        }
+
+        if (mProxyChangeReceiver != null) {
+            mContext.unregisterReceiver(mProxyChangeReceiver);
+            mProxyChangeReceiver = null;
+        }
+    }
+
+    /**
+     * Because our IntentReceiver can run within a different thread,
+     * synchronize setting the proxy
+     */
+    private synchronized void setProxyConfig() {
+        if (mNetworkStateTracker.getCurrentNetworkType() == ConnectivityManager.TYPE_WIFI) {
+            mProxyHost = null;
+        } else {
+            String host = Proxy.getHost(mContext);
+            if (HttpLog.LOGV) HttpLog.v("RequestQueue.setProxyConfig " + host);
+            if (host == null) {
+                mProxyHost = null;
+            } else {
+                mActivePool.disablePersistence();
+                mProxyHost = new HttpHost(host, Proxy.getPort(mContext), "http");
+            }
+        }
+    }
+
+    /**
+     * used by webkit
+     * @return proxy host if set, null otherwise
+     */
+    public HttpHost getProxyHost() {
+        return mProxyHost;
+    }
+
+    /**
+     * Queues an HTTP request
+     * @param url The url to load.
+     * @param method "GET" or "POST."
+     * @param headers A hashmap of http headers.
+     * @param eventHandler The event handler for handling returned
+     * data.  Callbacks will be made on the supplied instance.
+     * @param bodyProvider InputStream providing HTTP body, null if none
+     * @param bodyLength length of body, must be 0 if bodyProvider is null
+     * @param highPriority If true, queues before low priority
+     *     requests if possible
+     */
+    public RequestHandle queueRequest(
+            String url, String method,
+            Map<String, String> headers, EventHandler eventHandler,
+            InputStream bodyProvider, int bodyLength, boolean highPriority) {
+        WebAddress uri = new WebAddress(url);
+        return queueRequest(url, uri, method, headers, eventHandler,
+                            bodyProvider, bodyLength, highPriority);
+    }
+
+    /**
+     * Queues an HTTP request
+     * @param url The url to load.
+     * @param uri The uri of the url to load.
+     * @param method "GET" or "POST."
+     * @param headers A hashmap of http headers.
+     * @param eventHandler The event handler for handling returned
+     * data.  Callbacks will be made on the supplied instance.
+     * @param bodyProvider InputStream providing HTTP body, null if none
+     * @param bodyLength length of body, must be 0 if bodyProvider is null
+     * @param highPriority If true, queues before low priority
+     *     requests if possible
+     */
+    public RequestHandle queueRequest(
+            String url, WebAddress uri, String method, Map<String, String> headers,
+            EventHandler eventHandler,
+            InputStream bodyProvider, int bodyLength,
+            boolean highPriority) {
+
+        if (HttpLog.LOGV) HttpLog.v("RequestQueue.queueRequest " + uri);
+
+        // Ensure there is an eventHandler set
+        if (eventHandler == null) {
+            eventHandler = new LoggingEventHandler();
+        }
+
+        /* Create and queue request */
+        Request req;
+        HttpHost httpHost = new HttpHost(uri.mHost, uri.mPort, uri.mScheme);
+
+        // set up request
+        req = new Request(method, httpHost, mProxyHost, uri.mPath, bodyProvider,
+                          bodyLength, eventHandler, headers, highPriority);
+
+        queueRequest(req, highPriority);
+
+        mActivePool.mTotalRequest++;
+
+        // dump();
+        mActivePool.startConnectionThread();
+
+        return new RequestHandle(
+                this, url, uri, method, headers, bodyProvider, bodyLength,
+                req);
+    }
+
+    /**
+     * Called by the NetworkStateTracker -- updates when network connectivity
+     * is lost/restored.
+     *
+     * If isNetworkConnected is true, start processing requests
+     */
+    public void setNetworkState(boolean isNetworkConnected) {
+        if (HttpLog.LOGV) HttpLog.v("RequestQueue.setNetworkState() " + isNetworkConnected);
+        mNetworkConnected = isNetworkConnected;
+        if (isNetworkConnected)
+            mActivePool.startConnectionThread();
+    }
+
+    /**
+     * @return true iff there are any non-active requests pending
+     */
+    synchronized boolean requestsPending() {
+        return !mPending.isEmpty();
+    }
+
+
+    /**
+     * debug tool: prints request queue to log
+     */
+    synchronized void dump() {
+        HttpLog.v("dump()");
+        StringBuilder dump = new StringBuilder();
+        int count = 0;
+        Iterator<Map.Entry<HttpHost, LinkedList<Request>>> iter;
+
+        // mActivePool.log(dump);
+
+        if (!mPending.isEmpty()) {
+            iter = mPending.entrySet().iterator();
+            while (iter.hasNext()) {
+                Map.Entry<HttpHost, LinkedList<Request>> entry = iter.next();
+                String hostName = entry.getKey().getHostName();
+                StringBuilder line = new StringBuilder("p" + count++ + " " + hostName + " ");
+
+                LinkedList<Request> reqList = entry.getValue();
+                ListIterator reqIter = reqList.listIterator(0);
+                while (iter.hasNext()) {
+                    Request request = (Request)iter.next();
+                    line.append(request + " ");
+                }
+                dump.append(line);
+                dump.append("\n");
+            }
+        }
+        HttpLog.v(dump.toString());
+    }
+
+    /*
+     * RequestFeeder implementation
+     */
+    public synchronized Request getRequest() {
+        Request ret = null;
+
+        if (mNetworkConnected && !mPending.isEmpty()) {
+            ret = removeFirst(mPending);
+        }
+        if (HttpLog.LOGV) HttpLog.v("RequestQueue.getRequest() => " + ret);
+        return ret;
+    }
+
+    /**
+     * @return a request for given host if possible
+     */
+    public synchronized Request getRequest(HttpHost host) {
+        Request ret = null;
+
+        if (mNetworkConnected && mPending.containsKey(host)) {
+            LinkedList<Request> reqList = mPending.get(host);
+            ret = reqList.removeFirst();
+            if (reqList.isEmpty()) {
+                mPending.remove(host);
+            }
+        }
+        if (HttpLog.LOGV) HttpLog.v("RequestQueue.getRequest(" + host + ") => " + ret);
+        return ret;
+    }
+
+    /**
+     * @return true if a request for this host is available
+     */
+    public synchronized boolean haveRequest(HttpHost host) {
+        return mPending.containsKey(host);
+    }
+
+    /**
+     * Put request back on head of queue
+     */
+    public void requeueRequest(Request request) {
+        queueRequest(request, true);
+    }
+
+    /**
+     * This must be called to cleanly shutdown RequestQueue
+     */
+    public void shutdown() {
+        mActivePool.shutdown();
+    }
+
+    protected synchronized void queueRequest(Request request, boolean head) {
+        HttpHost host = request.mProxyHost == null ? request.mHost : request.mProxyHost;
+        LinkedList<Request> reqList;
+        if (mPending.containsKey(host)) {
+            reqList = mPending.get(host);
+        } else {
+            reqList = new LinkedList<Request>();
+            mPending.put(host, reqList);
+        }
+        if (head) {
+            reqList.addFirst(request);
+        } else {
+            reqList.add(request);
+        }
+    }
+
+
+    public void startTiming() {
+        mActivePool.startTiming();
+    }
+
+    public void stopTiming() {
+        mActivePool.stopTiming();
+    }
+
+    /* helper */
+    private Request removeFirst(LinkedHashMap<HttpHost, LinkedList<Request>> requestQueue) {
+        Request ret = null;
+        Iterator<Map.Entry<HttpHost, LinkedList<Request>>> iter = requestQueue.entrySet().iterator();
+        if (iter.hasNext()) {
+            Map.Entry<HttpHost, LinkedList<Request>> entry = iter.next();
+            LinkedList<Request> reqList = entry.getValue();
+            ret = reqList.removeFirst();
+            if (reqList.isEmpty()) {
+                requestQueue.remove(entry.getKey());
+            }
+        }
+        return ret;
+    }
+
+    /**
+     * This interface is exposed to each connection
+     */
+    interface ConnectionManager {
+        boolean isNetworkConnected();
+        HttpHost getProxyHost();
+        Connection getConnection(Context context, HttpHost host);
+        boolean recycleConnection(HttpHost host, Connection connection);
+    }
+}
diff --git a/core/java/android/net/http/SslCertificate.java b/core/java/android/net/http/SslCertificate.java
new file mode 100644
index 0000000..46b2bee
--- /dev/null
+++ b/core/java/android/net/http/SslCertificate.java
@@ -0,0 +1,251 @@
+/*
+ * 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 android.net.http;
+
+import android.os.Bundle;
+
+import java.text.DateFormat;
+import java.util.Vector;
+
+import java.security.cert.X509Certificate;
+
+import org.bouncycastle.asn1.DERObjectIdentifier;
+import org.bouncycastle.asn1.x509.X509Name;
+
+/**
+ * SSL certificate info (certificate details) class
+ */
+public class SslCertificate {
+
+    /**
+     * Name of the entity this certificate is issued to
+     */
+    private DName mIssuedTo;
+
+    /**
+     * Name of the entity this certificate is issued by
+     */
+    private DName mIssuedBy;
+
+    /**
+     * Not-before date from the validity period
+     */
+    private String mValidNotBefore;
+
+    /**
+     * Not-after date from the validity period
+     */
+    private String mValidNotAfter;
+
+     /**
+     * Bundle key names
+     */
+    private static final String ISSUED_TO = "issued-to";
+    private static final String ISSUED_BY = "issued-by";
+    private static final String VALID_NOT_BEFORE = "valid-not-before";
+    private static final String VALID_NOT_AFTER = "valid-not-after";
+
+    /**
+     * Saves the certificate state to a bundle
+     * @param certificate The SSL certificate to store
+     * @return A bundle with the certificate stored in it or null if fails
+     */
+    public static Bundle saveState(SslCertificate certificate) {
+        Bundle bundle = null;
+
+        if (certificate != null) {
+            bundle = new Bundle();
+
+            bundle.putString(ISSUED_TO, certificate.getIssuedTo().getDName());
+            bundle.putString(ISSUED_BY, certificate.getIssuedBy().getDName());
+
+            bundle.putString(VALID_NOT_BEFORE, certificate.getValidNotBefore());
+            bundle.putString(VALID_NOT_AFTER, certificate.getValidNotAfter());
+        }
+
+        return bundle;
+    }
+
+    /**
+     * Restores the certificate stored in the bundle
+     * @param bundle The bundle with the certificate state stored in it
+     * @return The SSL certificate stored in the bundle or null if fails
+     */
+    public static SslCertificate restoreState(Bundle bundle) {
+        if (bundle != null) {
+            return new SslCertificate(
+                bundle.getString(ISSUED_TO),
+                bundle.getString(ISSUED_BY),
+                bundle.getString(VALID_NOT_BEFORE),
+                bundle.getString(VALID_NOT_AFTER));
+        }
+
+        return null;
+    }
+
+    /**
+     * Creates a new SSL certificate object
+     * @param issuedTo The entity this certificate is issued to
+     * @param issuedBy The entity that issued this certificate
+     * @param validNotBefore The not-before date from the certificate validity period
+     * @param validNotAfter The not-after date from the certificate validity period
+     */
+    public SslCertificate(
+        String issuedTo, String issuedBy, String validNotBefore, String validNotAfter) {
+        mIssuedTo = new DName(issuedTo);
+        mIssuedBy = new DName(issuedBy);
+
+        mValidNotBefore = validNotBefore;
+        mValidNotAfter = validNotAfter;
+    }
+
+    /**
+     * Creates a new SSL certificate object from an X509 certificate
+     * @param certificate X509 certificate
+     */
+    public SslCertificate(X509Certificate certificate) {
+        this(certificate.getSubjectDN().getName(),
+             certificate.getIssuerDN().getName(),
+             DateFormat.getInstance().format(certificate.getNotBefore()),
+             DateFormat.getInstance().format(certificate.getNotAfter()));
+    }
+
+    /**
+     * @return Not-before date from the certificate validity period or
+     * "" if none has been set
+     */
+    public String getValidNotBefore() {
+        return mValidNotBefore != null ? mValidNotBefore : "";
+    }
+
+    /**
+     * @return Not-after date from the certificate validity period or
+     * "" if none has been set
+     */
+    public String getValidNotAfter() {
+        return mValidNotAfter != null ? mValidNotAfter : "";
+    }
+
+    /**
+     * @return Issued-to distinguished name or null if none has been set
+     */
+    public DName getIssuedTo() {
+        return mIssuedTo;
+    }
+
+    /**
+     * @return Issued-by distinguished name or null if none has been set
+     */
+    public DName getIssuedBy() {
+        return mIssuedBy;
+    }
+
+    /**
+     * @return A string representation of this certificate for debugging
+     */
+    public String toString() {
+        return
+            "Issued to: " + mIssuedTo.getDName() + ";\n" +
+            "Issued by: " + mIssuedBy.getDName() + ";\n";
+    }
+
+    /**
+     * A distinguished name helper class: a 3-tuple of:
+     * - common name (CN),
+     * - organization (O),
+     * - organizational unit (OU)
+     */
+    public class DName {
+        /**
+         * Distinguished name (normally includes CN, O, and OU names)
+         */
+        private String mDName;
+
+        /**
+         * Common-name (CN) component of the name
+         */
+        private String mCName;
+
+        /**
+         * Organization (O) component of the name
+         */
+        private String mOName;
+
+        /**
+         * Organizational Unit (OU) component of the name
+         */
+        private String mUName;
+
+        /**
+         * Creates a new distinguished name
+         * @param dName The distinguished name
+         */
+        public DName(String dName) {
+            if (dName != null) {
+                X509Name x509Name = new X509Name(mDName = dName);
+
+                Vector val = x509Name.getValues();
+                Vector oid = x509Name.getOIDs();
+
+                for (int i = 0; i < oid.size(); i++) {
+                    if (oid.elementAt(i).equals(X509Name.CN)) {
+                        mCName = (String) val.elementAt(i);
+                        continue;
+                    }
+
+                    if (oid.elementAt(i).equals(X509Name.O)) {
+                        mOName = (String) val.elementAt(i);
+                        continue;
+                    }
+
+                    if (oid.elementAt(i).equals(X509Name.OU)) {
+                        mUName = (String) val.elementAt(i);
+                        continue;
+                    }
+                }
+            }
+        }
+
+        /**
+         * @return The distinguished name (normally includes CN, O, and OU names)
+         */
+        public String getDName() {
+            return mDName != null ? mDName : "";
+        }
+
+        /**
+         * @return The Common-name (CN) component of this name
+         */
+        public String getCName() {
+            return mCName != null ? mCName : "";
+        }
+
+        /**
+         * @return The Organization (O) component of this name
+         */
+        public String getOName() {
+            return mOName != null ? mOName : "";
+        }
+
+        /**
+         * @return The Organizational Unit (OU) component of this name
+         */
+        public String getUName() {
+            return mUName != null ? mUName : "";
+        }
+    }
+}
diff --git a/core/java/android/net/http/SslError.java b/core/java/android/net/http/SslError.java
new file mode 100644
index 0000000..2788cb1
--- /dev/null
+++ b/core/java/android/net/http/SslError.java
@@ -0,0 +1,144 @@
+/*
+ * 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 android.net.http;
+
+import java.security.cert.X509Certificate;
+
+/**
+ * One or more individual SSL errors and the associated SSL certificate
+ * 
+ * {@hide}
+ */
+public class SslError {
+
+    /**
+     * Individual SSL errors (in the order from the least to the most severe):
+     */
+
+    /**
+     * The certificate is not yet valid
+     */
+  public static final int SSL_NOTYETVALID = 0;
+    /**
+     * The certificate has expired
+     */
+    public static final int SSL_EXPIRED = 1;
+    /**
+     * Hostname mismatch
+     */
+    public static final int SSL_IDMISMATCH = 2;
+    /**
+     * The certificate authority is not trusted
+     */
+    public static final int SSL_UNTRUSTED = 3;
+
+
+    /**
+     * The number of different SSL errors (update if you add a new SSL error!!!)
+     */
+    public static final int SSL_MAX_ERROR = 4;
+
+    /**
+     * The SSL error set bitfield (each individual error is an bit index;
+     * multiple individual errors can be OR-ed)
+     */
+    int mErrors;
+
+    /**
+     * The SSL certificate associated with the error set
+     */
+    SslCertificate mCertificate;
+
+    /**
+     * Creates a new SSL error set object
+     * @param error The SSL error
+     * @param certificate The associated SSL certificate
+     */
+    public SslError(int error, SslCertificate certificate) {
+        addError(error);
+        mCertificate = certificate;
+    }
+
+    /**
+     * Creates a new SSL error set object
+     * @param error The SSL error
+     * @param certificate The associated SSL certificate
+     */
+    public SslError(int error, X509Certificate certificate) {
+        addError(error);
+        mCertificate = new SslCertificate(certificate);
+    }
+
+    /**
+     * @return The SSL certificate associated with the error set
+     */
+    public SslCertificate getCertificate() {
+        return mCertificate;
+    }
+
+    /**
+     * Adds the SSL error to the error set
+     * @param error The SSL error to add
+     * @return True iff the error being added is a known SSL error
+     */
+    public boolean addError(int error) {
+        boolean rval = (0 <= error && error < SslError.SSL_MAX_ERROR);
+        if (rval) {
+            mErrors |= (0x1 << error);
+        }
+
+        return rval;
+    }
+
+    /**
+     * @param error The SSL error to check
+     * @return True iff the set includes the error
+     */
+    public boolean hasError(int error) {
+        boolean rval = (0 <= error && error < SslError.SSL_MAX_ERROR);
+        if (rval) {
+            rval = ((mErrors & (0x1 << error)) != 0);
+        }
+
+        return rval;
+    }
+
+    /**
+     * @return The primary, most severe, SSL error in the set
+     */
+    public int getPrimaryError() {
+        if (mErrors != 0) {
+            // go from the most to the least severe errors
+            for (int error = SslError.SSL_MAX_ERROR - 1; error >= 0; --error) {
+                if ((mErrors & (0x1 << error)) != 0) {
+                    return error;
+                }
+            }
+        }
+
+        return 0;
+    }
+
+    /**
+     * @return A String representation of this SSL error object
+     * (used mostly for debugging).
+     */
+    public String toString() {
+        return "primary error: " + getPrimaryError() +
+            " certificate: " + getCertificate();
+    }
+}
diff --git a/core/java/android/net/http/Timer.java b/core/java/android/net/http/Timer.java
new file mode 100644
index 0000000..cc15a30
--- /dev/null
+++ b/core/java/android/net/http/Timer.java
@@ -0,0 +1,41 @@
+/*
+ * 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 android.net.http;
+
+import android.os.SystemClock;
+
+/**
+ * {@hide}
+ * Debugging tool
+ */
+class Timer {
+
+    private long mStart;
+    private long mLast;
+
+    public Timer() {
+        mStart = mLast = SystemClock.uptimeMillis();
+    }
+
+    public void mark(String message) {
+        long now = SystemClock.uptimeMillis();
+        if (HttpLog.LOGV) {
+            HttpLog.v(message + " " + (now - mLast) + " total " + (now - mStart));
+        }
+        mLast = now;
+    }
+}
diff --git a/core/java/android/net/http/package.html b/core/java/android/net/http/package.html
new file mode 100755
index 0000000..a81cbce
--- /dev/null
+++ b/core/java/android/net/http/package.html
@@ -0,0 +1,2 @@
+<body>
+</body>
diff --git a/core/java/android/net/package.html b/core/java/android/net/package.html
new file mode 100755
index 0000000..47c57e6
--- /dev/null
+++ b/core/java/android/net/package.html
@@ -0,0 +1,5 @@
+<body>
+
+Classes that help with network access, beyond the normal java.net.* APIs.
+
+</body>
diff --git a/core/java/android/os/AsyncResult.java b/core/java/android/os/AsyncResult.java
new file mode 100644
index 0000000..5bad09d
--- /dev/null
+++ b/core/java/android/os/AsyncResult.java
@@ -0,0 +1,68 @@
+/*
+ * 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 android.os;
+
+import android.os.Message;
+
+/** @hide */
+public class AsyncResult
+{
+
+    /*************************** Instance Variables **************************/
+
+    // Expect either exception or result to be null
+    public Object userObj;
+    public Throwable exception;
+    public Object result;
+
+    /***************************** Class Methods *****************************/
+
+    /** Saves and sets m.obj */
+    public static AsyncResult 
+    forMessage(Message m, Object r, Throwable ex)
+    {
+        AsyncResult ret;
+
+        ret = new AsyncResult (m.obj, r, ex);
+
+        m.obj = ret; 
+
+        return ret;
+    }
+
+    /** Saves and sets m.obj */
+    public static AsyncResult 
+    forMessage(Message m)
+    {
+        AsyncResult ret;
+
+        ret = new AsyncResult (m.obj, null, null);
+
+        m.obj = ret; 
+
+        return ret;
+    }
+
+    /** please note, this sets m.obj to be this */
+    public 
+    AsyncResult (Object uo, Object r, Throwable ex)
+    {
+        userObj = uo;
+        result = r;
+        exception = ex;
+    }
+}
diff --git a/core/java/android/os/AsyncTask.java b/core/java/android/os/AsyncTask.java
new file mode 100644
index 0000000..ee4e897
--- /dev/null
+++ b/core/java/android/os/AsyncTask.java
@@ -0,0 +1,454 @@
+/*
+ * 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 android.os;
+
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.Callable;
+import java.util.concurrent.FutureTask;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeoutException;
+import java.util.concurrent.CancellationException;
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * <p>AsyncTask enables proper and easy use of the UI thread. This class allows to
+ * perform background operations and publish results on the UI thread without
+ * having to manipulate threads and/or handlers.</p>
+ *
+ * <p>An asynchronous task is defined by a computation that runs on a background thread and
+ * whose result is published on the UI thread. An asynchronous task is defined by 3 generic
+ * types, called <code>Params</code>, <code>Progress</code> and <code>Result</code>,
+ * and 4 steps, called <code>begin</code>, <code>doInBackground</code>,
+ * <code>processProgress<code> and <code>end</code>.</p>
+ *
+ * <h2>Usage</h2>
+ * <p>AsyncTask must be subclassed to be used. The subclass will override at least
+ * one method ({@link #doInBackground}), and most often will override a
+ * second one ({@link #onPostExecute}.)</p>
+ *
+ * <p>Here is an example of subclassing:</p>
+ * <pre class="prettyprint">
+ * private class DownloadFilesTask extends AsyncTask&lt;URL, Integer, Long&gt; {
+ *     protected Long doInBackground(URL... urls) {
+ *         int count = urls.length;
+ *         long totalSize = 0;
+ *         for (int i = 0; i < count; i++) {
+ *             totalSize += Downloader.downloadFile(urls[i]);
+ *             publishProgress((int) ((i / (float) count) * 100));
+ *         }
+ *         return totalSize;
+ *     }
+ *
+ *     protected void onProgressUpdate(Integer... progress) {
+ *         setProgressPercent(progress[0]);
+ *     }
+ *
+ *     protected void onPostExecute(Long result) {
+ *         showDialog("Downloaded " + result + " bytes");
+ *     }
+ * }
+ * </pre>
+ *
+ * <p>Once created, a task is executed very simply:</p>
+ * <pre class="prettyprint">
+ * new DownloadFilesTask().execute(url1, url2, url3);
+ * </pre>
+ *
+ * <h2>AsyncTask's generic types</h2>
+ * <p>The three types used by an asynchronous task are the following:</p>
+ * <ol>
+ *     <li><code>Params</code>, the type of the parameters sent to the task upon
+ *     execution.</li>
+ *     <li><code>Progress</code>, the type of the progress units published during
+ *     the background computation.</li>
+ *     <li><code>Result</code>, the type of the result of the background
+ *     computation.</li>
+ * </ol>
+ * <p>Not all types are always used by am asynchronous task. To mark a type as unused,
+ * simply use the type {@link Void}:</p>
+ * <pre>
+ * private class MyTask extends AsyncTask<Void, Void, Void) { ... }
+ * </pre>
+ *
+ * <h2>The 4 steps</h2>
+ * <p>When an asynchronous task is executed, the task goes through 4 steps:</p>
+ * <ol>
+ *     <li>{@link #onPreExecute()}, invoked on the UI thread immediately after the task
+ *     is executed. This step is normally used to setup the task, for instance by
+ *     showing a progress bar in the user interface.</li>
+ *     <li>{@link #doInBackground}, invoked on the background thread
+ *     immediately after {@link #onPreExecute()} finishes executing. This step is used
+ *     to perform background computation that can take a long time. The parameters
+ *     of the asynchronous task are passed to this step. The result of the computation must
+ *     be returned by this step and will be passed back to the last step. This step
+ *     can also use {@link #publishProgress} to publish one or more units
+ *     of progress. These values are published on the UI thread, in the
+ *     {@link #onProgressUpdate} step.</li>
+ *     <li>{@link #onProgressUpdate}, invoked on the UI thread after a
+ *     call to {@link #publishProgress}. The timing of the execution is
+ *     undefined. This method is used to display any form of progress in the user
+ *     interface while the background computation is still executing. For instance,
+ *     it can be used to animate a progress bar or show logs in a text field.</li>
+ *     <li>{@link #onPostExecute}, invoked on the UI thread after the background
+ *     computation finishes. The result of the background computation is passed to
+ *     this step as a parameter.</li>
+ * </ol>
+ *
+ * <h2>Threading rules</h2>
+ * <p>There are a few threading rules that must be followed for this class to
+ * work properly:</p>
+ * <ul>
+ *     <li>The task instance must be created on the UI thread.</li>
+ *     <li>{@link #execute} must be invoked on the UI thread.</li>
+ *     <li>Do not call {@link #onPreExecute()}, {@link #onPostExecute},
+ *     {@link #doInBackground}, {@link #onProgressUpdate} manually.</li>
+ *     <li>The task can be executed only once (an exception will be thrown if
+ *     a second execution is attempted.)</li>
+ * </ul>
+ */
+public abstract class AsyncTask<Params, Progress, Result> {
+    private static final String LOG_TAG = "AsyncTask";
+
+    private static final int CORE_POOL_SIZE = 1;
+    private static final int MAXIMUM_POOL_SIZE = 10;
+    private static final int KEEP_ALIVE = 10;
+
+    private static final BlockingQueue<Runnable> sWorkQueue =
+            new LinkedBlockingQueue<Runnable>(MAXIMUM_POOL_SIZE);
+
+    private static final ThreadFactory sThreadFactory = new ThreadFactory() {
+        private final AtomicInteger mCount = new AtomicInteger(1);
+
+        public Thread newThread(Runnable r) {
+            return new Thread(r, "AsyncTask #" + mCount.getAndIncrement());
+        }
+    };
+
+    private static final ThreadPoolExecutor sExecutor = new ThreadPoolExecutor(CORE_POOL_SIZE,
+            MAXIMUM_POOL_SIZE, KEEP_ALIVE, TimeUnit.SECONDS, sWorkQueue, sThreadFactory);
+
+    private static final int MESSAGE_POST_RESULT = 0x1;
+    private static final int MESSAGE_POST_PROGRESS = 0x2;
+    private static final int MESSAGE_POST_CANCEL = 0x3;
+
+    private static final InternalHandler sHandler = new InternalHandler();
+
+    private final WorkerRunnable<Params, Result> mWorker;
+    private final FutureTask<Result> mFuture;
+
+    private volatile Status mStatus = Status.PENDING;
+
+    /**
+     * Indicates the current status of the task. Each status will be set only once
+     * during the lifetime of a task.
+     */
+    public enum Status {
+        /**
+         * Indicates that the task has not been executed yet.
+         */
+        PENDING,
+        /**
+         * Indicates that the task is running.
+         */
+        RUNNING,
+        /**
+         * Indicates that {@link AsyncTask#onPostExecute} has finished.
+         */
+        FINISHED,
+    }
+
+    /**
+     * Creates a new asynchronous task. This constructor must be invoked on the UI thread.
+     */
+    public AsyncTask() {
+        mWorker = new WorkerRunnable<Params, Result>() {
+            public Result call() throws Exception {
+                Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
+                return doInBackground(mParams);
+            }
+        };
+
+        mFuture = new FutureTask<Result>(mWorker) {
+            @Override
+            protected void done() {
+                Message message;
+                Result result = null;
+
+                try {
+                    result = get();
+                } catch (InterruptedException e) {
+                    android.util.Log.w(LOG_TAG, e);
+                } catch (ExecutionException e) {
+                    throw new RuntimeException("An error occured while executing doInBackground()",
+                            e.getCause());
+                } catch (CancellationException e) {
+                    message = sHandler.obtainMessage(MESSAGE_POST_CANCEL,
+                            new AsyncTaskResult<Result>(AsyncTask.this, (Result[]) null));
+                    message.sendToTarget();
+                    return;
+                } catch (Throwable t) {
+                    throw new RuntimeException("An error occured while executing "
+                            + "doInBackground()", t);
+                }
+
+                message = sHandler.obtainMessage(MESSAGE_POST_RESULT,
+                        new AsyncTaskResult<Result>(AsyncTask.this, result));
+                message.sendToTarget();
+            }
+        };
+    }
+
+    /**
+     * Returns the current status of this task.
+     *
+     * @return The current status.
+     */
+    public final Status getStatus() {
+        return mStatus;
+    }
+
+    /**
+     * Override this method to perform a computation on a background thread. The
+     * specified parameters are the parameters passed to {@link #execute}
+     * by the caller of this task.
+     *
+     * This method can call {@link #publishProgress} to publish updates
+     * on the UI thread.
+     *
+     * @param params The parameters of the task.
+     *
+     * @return A result, defined by the subclass of this task.
+     *
+     * @see #onPreExecute()
+     * @see #onPostExecute
+     * @see #publishProgress
+     */
+    protected abstract Result doInBackground(Params... params);
+
+    /**
+     * Runs on the UI thread before {@link #doInBackground}.
+     *
+     * @see #onPostExecute
+     * @see #doInBackground
+     */
+    protected void onPreExecute() {
+    }
+
+    /**
+     * Runs on the UI thread after {@link #doInBackground}. The
+     * specified result is the value returned by {@link #doInBackground}
+     * or null if the task was cancelled or an exception occured.
+     *
+     * @param result The result of the operation computed by {@link #doInBackground}.
+     *
+     * @see #onPreExecute
+     * @see #doInBackground
+     */
+    @SuppressWarnings({"UnusedDeclaration"})
+    protected void onPostExecute(Result result) {
+    }
+
+    /**
+     * Runs on the UI thread after {@link #publishProgress} is invoked.
+     * The specified values are the values passed to {@link #publishProgress}.
+     *
+     * @param values The values indicating progress.
+     *
+     * @see #publishProgress
+     * @see #doInBackground
+     */
+    @SuppressWarnings({"UnusedDeclaration"})
+    protected void onProgressUpdate(Progress... values) {
+    }
+
+    /**
+     * Runs on the UI thread after {@link #cancel(boolean)} is invoked.
+     *
+     * @see #cancel(boolean)
+     * @see #isCancelled()
+     */
+    protected void onCancelled() {
+    }
+
+    /**
+     * Returns <tt>true</tt> if this task was cancelled before it completed
+     * normally.
+     *
+     * @return <tt>true</tt> if task was cancelled before it completed
+     *
+     * @see #cancel(boolean)
+     */
+    public final boolean isCancelled() {
+        return mFuture.isCancelled();
+    }
+
+    /**
+     * Attempts to cancel execution of this task.  This attempt will
+     * fail if the task has already completed, already been cancelled,
+     * or could not be cancelled for some other reason. If successful,
+     * and this task has not started when <tt>cancel</tt> is called,
+     * this task should never run.  If the task has already started,
+     * then the <tt>mayInterruptIfRunning</tt> parameter determines
+     * whether the thread executing this task should be interrupted in
+     * an attempt to stop the task.
+     *
+     * @param mayInterruptIfRunning <tt>true</tt> if the thread executing this
+     *        task should be interrupted; otherwise, in-progress tasks are allowed
+     *        to complete.
+     *
+     * @return <tt>false</tt> if the task could not be cancelled,
+     *         typically because it has already completed normally;
+     *         <tt>true</tt> otherwise
+     *
+     * @see #isCancelled()
+     * @see #onCancelled()
+     */
+    public final boolean cancel(boolean mayInterruptIfRunning) {
+        return mFuture.cancel(mayInterruptIfRunning);
+    }
+
+    /**
+     * Waits if necessary for the computation to complete, and then
+     * retrieves its result.
+     *
+     * @return The computed result.
+     *
+     * @throws CancellationException If the computation was cancelled.
+     * @throws ExecutionException If the computation threw an exception.
+     * @throws InterruptedException If the current thread was interrupted
+     *         while waiting.
+     */
+    public final Result get() throws InterruptedException, ExecutionException {
+        return mFuture.get();
+    }
+
+    /**
+     * Waits if necessary for at most the given time for the computation
+     * to complete, and then retrieves its result.
+     *
+     * @param timeout Time to wait before cancelling the operation.
+     * @param unit The time unit for the timeout.
+     *
+     * @return The computed result.
+     *
+     * @throws CancellationException If the computation was cancelled.
+     * @throws ExecutionException If the computation threw an exception.
+     * @throws InterruptedException If the current thread was interrupted
+     *         while waiting.
+     * @throws TimeoutException If the wait timed out.
+     */
+    public final Result get(long timeout, TimeUnit unit) throws InterruptedException,
+            ExecutionException, TimeoutException {
+        return mFuture.get(timeout, unit);
+    }
+
+    /**
+     * Executes the task with the specified parameters. The task returns
+     * itself (this) so that the caller can keep a reference to it.
+     *
+     * This method must be invoked on the UI thread.
+     *
+     * @param params The parameters of the task.
+     *
+     * @return This instance of AsyncTask.
+     *
+     * @throws IllegalStateException If {@link #getStatus()} returns either
+     *         {@link AsyncTask.Status#RUNNING} or {@link AsyncTask.Status#FINISHED}.
+     */
+    public final AsyncTask<Params, Progress, Result> execute(Params... params) {
+        if (mStatus != Status.PENDING) {
+            switch (mStatus) {
+                case RUNNING:
+                    throw new IllegalStateException("Cannot execute task:"
+                            + " the task is already running.");
+                case FINISHED:
+                    throw new IllegalStateException("Cannot execute task:"
+                            + " the task has already been executed "
+                            + "(a task can be executed only once)");
+            }
+        }
+
+        mStatus = Status.RUNNING;
+
+        onPreExecute();
+
+        mWorker.mParams = params;
+        sExecutor.execute(mFuture);
+
+        return this;
+    }
+
+    /**
+     * This method can be invoked from {@link #doInBackground} to
+     * publish updates on the UI thread while the background computation is
+     * still running. Each call to this method will trigger the execution of
+     * {@link #onProgressUpdate} on the UI thread.
+     *
+     * @param values The progress values to update the UI with.
+     *
+     * @see #onProgressUpdate
+     * @see #doInBackground
+     */
+    protected final void publishProgress(Progress... values) {
+        sHandler.obtainMessage(MESSAGE_POST_PROGRESS,
+                new AsyncTaskResult<Progress>(this, values)).sendToTarget();
+    }
+
+    private void finish(Result result) {
+        onPostExecute(result);
+        mStatus = Status.FINISHED;
+    }
+
+    private static class InternalHandler extends Handler {
+        @SuppressWarnings({"unchecked", "RawUseOfParameterizedType"})
+        @Override
+        public void handleMessage(Message msg) {
+            AsyncTaskResult result = (AsyncTaskResult) msg.obj;
+            switch (msg.what) {
+                case MESSAGE_POST_RESULT:
+                    // There is only one result
+                    result.mTask.finish(result.mData[0]);
+                    break;
+                case MESSAGE_POST_PROGRESS:
+                    result.mTask.onProgressUpdate(result.mData);
+                    break;
+                case MESSAGE_POST_CANCEL:
+                    result.mTask.onCancelled();
+                    break;
+            }
+        }
+    }
+
+    private static abstract class WorkerRunnable<Params, Result> implements Callable<Result> {
+        Params[] mParams;
+    }
+
+    @SuppressWarnings({"RawUseOfParameterizedType"})
+    private static class AsyncTaskResult<Data> {
+        final AsyncTask mTask;
+        final Data[] mData;
+
+        AsyncTaskResult(AsyncTask task, Data... data) {
+            mTask = task;
+            mData = data;
+        }
+    }
+}
diff --git a/core/java/android/os/BadParcelableException.java b/core/java/android/os/BadParcelableException.java
new file mode 100644
index 0000000..a1c5bb2
--- /dev/null
+++ b/core/java/android/os/BadParcelableException.java
@@ -0,0 +1,31 @@
+/*
+ * 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 android.os;
+import android.util.AndroidRuntimeException;
+
+/**
+ * The object you are calling has died, because its hosting process
+ * no longer exists.
+ */
+public class BadParcelableException extends AndroidRuntimeException {
+    public BadParcelableException(String msg) {
+        super(msg);
+    }
+    public BadParcelableException(Exception cause) {
+        super(cause);
+    }
+}
diff --git a/core/java/android/os/Base64Utils.java b/core/java/android/os/Base64Utils.java
new file mode 100644
index 0000000..684a469
--- /dev/null
+++ b/core/java/android/os/Base64Utils.java
@@ -0,0 +1,31 @@
+/*
+ * 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 android.os;
+
+/**
+ * {@hide}
+ */
+public class Base64Utils
+{
+    // TODO add encode api here if possible
+    
+    public static byte [] decodeBase64(String data) {
+        return decodeBase64Native(data);
+    }
+    private static native byte[] decodeBase64Native(String data);
+}
+
diff --git a/core/java/android/os/BatteryManager.java b/core/java/android/os/BatteryManager.java
new file mode 100644
index 0000000..8f1a756
--- /dev/null
+++ b/core/java/android/os/BatteryManager.java
@@ -0,0 +1,46 @@
+/*
+ * 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 android.os;
+
+/**
+ * The BatteryManager class contains strings and constants used for values
+ * in the ACTION_BATTERY_CHANGED Intent.
+ */
+public class BatteryManager {
+
+    // values for "status" field in the ACTION_BATTERY_CHANGED Intent
+    public static final int BATTERY_STATUS_UNKNOWN = 1;
+    public static final int BATTERY_STATUS_CHARGING = 2;
+    public static final int BATTERY_STATUS_DISCHARGING = 3;
+    public static final int BATTERY_STATUS_NOT_CHARGING = 4;
+    public static final int BATTERY_STATUS_FULL = 5;
+
+    // values for "health" field in the ACTION_BATTERY_CHANGED Intent
+    public static final int BATTERY_HEALTH_UNKNOWN = 1;
+    public static final int BATTERY_HEALTH_GOOD = 2;
+    public static final int BATTERY_HEALTH_OVERHEAT = 3;
+    public static final int BATTERY_HEALTH_DEAD = 4;
+    public static final int BATTERY_HEALTH_OVER_VOLTAGE = 5;
+    public static final int BATTERY_HEALTH_UNSPECIFIED_FAILURE = 6;
+
+    // values of the "plugged" field in the ACTION_BATTERY_CHANGED intent.
+    // These must be powers of 2.
+    /** Power source is an AC charger. */
+    public static final int BATTERY_PLUGGED_AC = 1;
+    /** Power source is a USB port. */
+    public static final int BATTERY_PLUGGED_USB = 2;
+}
diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java
new file mode 100644
index 0000000..7590bfe
--- /dev/null
+++ b/core/java/android/os/BatteryStats.java
@@ -0,0 +1,828 @@
+package android.os;
+
+import java.io.PrintWriter;
+import java.util.Formatter;
+import java.util.Map;
+
+import android.util.Log;
+import android.util.Printer;
+import android.util.SparseArray;
+
+/**
+ * A class providing access to battery usage statistics, including information on
+ * wakelocks, processes, packages, and services.  All times are represented in microseconds
+ * except where indicated otherwise.
+ * @hide
+ */
+public abstract class BatteryStats implements Parcelable {
+
+    private static final boolean LOCAL_LOGV = false;
+    
+    /**
+     * A constant indicating a partial wake lock timer.
+     */
+    public static final int WAKE_TYPE_PARTIAL = 0;
+
+    /**
+     * A constant indicating a full wake lock timer.
+     */
+    public static final int WAKE_TYPE_FULL = 1;
+
+    /**
+     * A constant indicating a window wake lock timer.
+     */
+    public static final int WAKE_TYPE_WINDOW = 2;
+    
+    /**
+     * A constant indicating a sensor timer.
+     * 
+     * {@hide}
+     */
+    public static final int SENSOR = 3;
+
+    /**
+     * Include all of the data in the stats, including previously saved data.
+     */
+    public static final int STATS_TOTAL = 0;
+
+    /**
+     * Include only the last run in the stats.
+     */
+    public static final int STATS_LAST = 1;
+
+    /**
+     * Include only the current run in the stats.
+     */
+    public static final int STATS_CURRENT = 2;
+
+    /**
+     * Include only the run since the last time the device was unplugged in the stats.
+     */
+    public static final int STATS_UNPLUGGED = 3;
+    
+    /**
+     * Bump the version on this if the checkin format changes.
+     */
+    private static final int BATTERY_STATS_CHECKIN_VERSION = 1;
+    
+    // TODO: Update this list if you add/change any stats above.
+    private static final String[] STAT_NAMES = { "total", "last", "current", "unplugged" };
+
+    private static final String APK_DATA = "apk";
+    private static final String PROCESS_DATA = "process";
+    private static final String SENSOR_DATA = "sensor";
+    private static final String WAKELOCK_DATA = "wakelock";
+    private static final String NETWORK_DATA = "network";
+    private static final String BATTERY_DATA = "battery";
+    private static final String MISC_DATA = "misc";
+
+    private final StringBuilder mFormatBuilder = new StringBuilder(8);
+    private final Formatter mFormatter = new Formatter(mFormatBuilder);
+
+    /**
+     * State for keeping track of timing information.
+     */
+    public static abstract class Timer {
+
+        /**
+         * Returns the count associated with this Timer for the
+         * selected type of statistics.
+         *
+         * @param which one of STATS_TOTAL, STATS_LAST, or STATS_CURRENT
+         */
+        public abstract int getCount(int which);
+
+        /**
+         * Returns the total time in microseconds associated with this Timer for the
+         * selected type of statistics.
+         *
+         * @param batteryRealtime system realtime on  battery in microseconds
+         * @param which one of STATS_TOTAL, STATS_LAST, or STATS_CURRENT
+         * @return a time in microseconds
+         */
+        public abstract long getTotalTime(long batteryRealtime, int which);
+        
+        /**
+         * Temporary for debugging.
+         */
+        public abstract void logState();
+    }
+
+    /**
+     * The statistics associated with a particular uid.
+     */
+    public static abstract class Uid {
+
+        /**
+         * Returns a mapping containing wakelock statistics.
+         *
+         * @return a Map from Strings to Uid.Wakelock objects.
+         */
+        public abstract Map<String, ? extends Wakelock> getWakelockStats();
+
+        /**
+         * The statistics associated with a particular wake lock.
+         */
+        public static abstract class Wakelock {
+            public abstract Timer getWakeTime(int type);
+        }
+
+        /**
+         * Returns a mapping containing sensor statistics.
+         *
+         * @return a Map from Integer sensor ids to Uid.Sensor objects.
+         */
+        public abstract Map<Integer, ? extends Sensor> getSensorStats();
+
+        /**
+         * Returns a mapping containing process statistics.
+         *
+         * @return a Map from Strings to Uid.Proc objects.
+         */
+        public abstract Map<String, ? extends Proc> getProcessStats();
+
+        /**
+         * Returns a mapping containing package statistics.
+         *
+         * @return a Map from Strings to Uid.Pkg objects.
+         */
+        public abstract Map<String, ? extends Pkg> getPackageStats();
+        
+        /**
+         * {@hide}
+         */
+        public abstract int getUid();
+        
+        /**
+         * {@hide}
+         */
+        public abstract long getTcpBytesReceived(int which);
+        
+        /**
+         * {@hide}
+         */
+        public abstract long getTcpBytesSent(int which);
+
+        public static abstract class Sensor {
+            // Magic sensor number for the GPS.
+            public static final int GPS = -10000;
+            
+            public abstract int getHandle();
+            
+            public abstract Timer getSensorTime();
+        }
+
+        /**
+         * The statistics associated with a particular process.
+         */
+        public static abstract class Proc {
+
+            /**
+             * Returns the total time (in 1/100 sec) spent executing in user code.
+             *
+             * @param which one of STATS_TOTAL, STATS_LAST, or STATS_CURRENT.
+             */
+            public abstract long getUserTime(int which);
+
+            /**
+             * Returns the total time (in 1/100 sec) spent executing in system code.
+             *
+             * @param which one of STATS_TOTAL, STATS_LAST, or STATS_CURRENT.
+             */
+            public abstract long getSystemTime(int which);
+
+            /**
+             * Returns the number of times the process has been started.
+             *
+             * @param which one of STATS_TOTAL, STATS_LAST, or STATS_CURRENT.
+             */
+            public abstract int getStarts(int which);
+        }
+
+        /**
+         * The statistics associated with a particular package.
+         */
+        public static abstract class Pkg {
+
+            /**
+             * Returns the number of times this package has done something that could wake up the
+             * device from sleep.
+             *
+             * @param which one of STATS_TOTAL, STATS_LAST, or STATS_CURRENT.
+             */
+            public abstract int getWakeups(int which);
+
+            /**
+             * Returns a mapping containing service statistics.
+             */
+            public abstract Map<String, ? extends Serv> getServiceStats();
+
+            /**
+             * The statistics associated with a particular service.
+             */
+            public abstract class Serv {
+
+                /**
+                 * Returns the amount of time spent started.
+                 *
+                 * @param batteryUptime elapsed uptime on battery in microseconds.
+                 * @param which one of STATS_TOTAL, STATS_LAST, or STATS_CURRENT.
+                 * @return
+                 */
+                public abstract long getStartTime(long batteryUptime, int which);
+
+                /**
+                 * Returns the total number of times startService() has been called.
+                 *
+                 * @param which one of STATS_TOTAL, STATS_LAST, or STATS_CURRENT.
+                 */
+                public abstract int getStarts(int which);
+
+                /**
+                 * Returns the total number times the service has been launched.
+                 *
+                 * @param which one of STATS_TOTAL, STATS_LAST, or STATS_CURRENT.
+                 */
+                public abstract int getLaunches(int which);
+            }
+        }
+    }
+
+    /**
+     * Returns the number of times the device has been started.
+     */
+    public abstract int getStartCount();
+    
+    /**
+     * Returns the time in milliseconds that the screen has been on while the device was
+     * running on battery.
+     * 
+     * {@hide}
+     */
+    public abstract long getScreenOnTime(long batteryRealtime, int which);
+    
+    /**
+     * Returns the time in milliseconds that the phone has been on while the device was
+     * running on battery.
+     * 
+     * {@hide}
+     */
+    public abstract long getPhoneOnTime(long batteryRealtime, int which);
+    
+    /**
+     * Return whether we are currently running on battery.
+     */
+    public abstract boolean getIsOnBattery();
+    
+    /**
+     * Returns a SparseArray containing the statistics for each uid.
+     */
+    public abstract SparseArray<? extends Uid> getUidStats();
+
+    /**
+     * Returns the current battery uptime in microseconds.
+     *
+     * @param curTime the amount of elapsed realtime in microseconds.
+     */
+    public abstract long getBatteryUptime(long curTime);
+
+    /**
+     * Returns the current battery realtime in microseconds.
+     *
+     * @param curTime the amount of elapsed realtime in microseconds.
+     */
+    public abstract long getBatteryRealtime(long curTime);
+
+    /**
+     * Returns the total, last, or current battery uptime in microseconds.
+     *
+     * @param curTime the elapsed realtime in microseconds.
+     * @param which one of STATS_TOTAL, STATS_LAST, or STATS_CURRENT.
+     */
+    public abstract long computeBatteryUptime(long curTime, int which);
+
+    /**
+     * Returns the total, last, or current battery realtime in microseconds.
+     *
+     * @param curTime the current elapsed realtime in microseconds.
+     * @param which one of STATS_TOTAL, STATS_LAST, or STATS_CURRENT.
+     */
+    public abstract long computeBatteryRealtime(long curTime, int which);
+
+    /**
+     * Returns the total, last, or current uptime in microseconds.
+     *
+     * @param curTime the current elapsed realtime in microseconds.
+     * @param which one of STATS_TOTAL, STATS_LAST, or STATS_CURRENT.
+     */
+    public abstract long computeUptime(long curTime, int which);
+
+    /**
+     * Returns the total, last, or current realtime in microseconds.
+     * *
+     * @param curTime the current elapsed realtime in microseconds.
+     * @param which one of STATS_TOTAL, STATS_LAST, or STATS_CURRENT.
+     */
+    public abstract long computeRealtime(long curTime, int which);
+
+    private final static void formatTime(StringBuilder out, long seconds) {
+        long days = seconds / (60 * 60 * 24);
+        if (days != 0) {
+            out.append(days);
+            out.append("d ");
+        }
+        long used = days * 60 * 60 * 24;
+
+        long hours = (seconds - used) / (60 * 60);
+        if (hours != 0 || used != 0) {
+            out.append(hours);
+            out.append("h ");
+        }
+        used += hours * 60 * 60;
+
+        long mins = (seconds-used) / 60;
+        if (mins != 0 || used != 0) {
+            out.append(mins);
+            out.append("m ");
+        }
+        used += mins * 60;
+
+        if (seconds != 0 || used != 0) {
+            out.append(seconds-used);
+            out.append("s ");
+        }
+    }
+
+    private final static String formatTime(long time) {
+        long sec = time / 100;
+        StringBuilder sb = new StringBuilder();
+        formatTime(sb, sec);
+        sb.append((time - (sec * 100)) * 10);
+        sb.append("ms ");
+        return sb.toString();
+    }
+
+    private final static String formatTimeMs(long time) {
+        long sec = time / 1000;
+        StringBuilder sb = new StringBuilder();
+        formatTime(sb, sec);
+        sb.append(time - (sec * 1000));
+        sb.append("ms ");
+        return sb.toString();
+    }
+
+    private final String formatRatioLocked(long num, long den) {
+        if (den == 0L) {
+            return "---%";
+        }
+        float perc = ((float)num) / ((float)den) * 100;
+        mFormatBuilder.setLength(0);
+        mFormatter.format("%.1f%%", perc);
+        return mFormatBuilder.toString();
+    }
+
+    /**
+     *
+     * @param sb a StringBuilder object.
+     * @param timer a Timer object contining the wakelock times.
+     * @param batteryRealtime the current on-battery time in microseconds.
+     * @param name the name of the wakelock.
+     * @param which which one of STATS_TOTAL, STATS_LAST, or STATS_CURRENT.
+     * @param linePrefix a String to be prepended to each line of output.
+     * @return the line prefix
+     */
+    private static final String printWakeLock(StringBuilder sb, Timer timer,
+            long batteryRealtime, String name, int which, String linePrefix) {
+        
+        if (timer != null) {
+            // Convert from microseconds to milliseconds with rounding
+            long totalTimeMicros = timer.getTotalTime(batteryRealtime, which);
+            long totalTimeMillis = (totalTimeMicros + 500) / 1000;
+            
+            int count = timer.getCount(which);
+            if (totalTimeMillis != 0) {
+                sb.append(linePrefix);
+                sb.append(formatTimeMs(totalTimeMillis));
+                sb.append(name);
+                sb.append(' ');
+                sb.append('(');
+                sb.append(count);
+                sb.append(" times)");
+                return ", ";
+            }
+        }
+        return linePrefix;
+    }
+    
+    /**
+     * Checkin version of wakelock printer. Prints simple comma-separated list.
+     * 
+     * @param sb a StringBuilder object.
+     * @param timer a Timer object contining the wakelock times.
+     * @param now the current time in microseconds.
+     * @param name the name of the wakelock.
+     * @param which which one of STATS_TOTAL, STATS_LAST, or STATS_CURRENT.
+     * @param linePrefix a String to be prepended to each line of output.
+     * @return the line prefix
+     */
+    private static final String printWakeLockCheckin(StringBuilder sb, Timer timer, long now,
+        String name, int which, String linePrefix) {
+        long totalTimeMicros = 0;
+        int count = 0;
+        if (timer != null) {
+            totalTimeMicros = timer.getTotalTime(now, which);
+            count = timer.getCount(which); 
+        }
+        sb.append(linePrefix);
+        sb.append((totalTimeMicros + 500) / 1000); // microseconds to milliseconds with rounding
+        sb.append(',');
+        sb.append(name);
+        sb.append(',');
+        sb.append(count);
+        return ",";
+    }
+    
+    /**
+     * Dump a comma-separated line of values for terse checkin mode.
+     * 
+     * @param pw the PageWriter to dump log to
+     * @param category category of data (e.g. "total", "last", "unplugged", "current" )
+     * @param type type of data (e.g. "wakelock", "sensor", "process", "apk" ,  "process", "network")
+     * @param args type-dependent data arguments
+     */
+    private static final void dumpLine(PrintWriter pw, int uid, String category, String type, 
+           Object... args ) {
+        pw.print(BATTERY_STATS_CHECKIN_VERSION); pw.print(',');
+        pw.print(uid); pw.print(',');
+        pw.print(category); pw.print(',');
+        pw.print(type); 
+        
+        for (Object arg : args) {  
+            pw.print(','); 
+            pw.print(arg); 
+        }
+        pw.print('\n');
+    }
+    
+    /**
+     * Checkin server version of dump to produce more compact, computer-readable log.
+     * 
+     * NOTE: all times are expressed in 'ms'.
+     * @param fd
+     * @param pw
+     * @param which
+     */
+    private final void dumpCheckinLocked(PrintWriter pw, int which) {
+        final long rawUptime = SystemClock.uptimeMillis() * 1000;
+        final long rawRealtime = SystemClock.elapsedRealtime() * 1000;
+        final long batteryUptime = getBatteryUptime(rawUptime);
+        final long batteryRealtime = getBatteryRealtime(rawRealtime);
+        final long whichBatteryUptime = computeBatteryUptime(rawUptime, which);
+        final long whichBatteryRealtime = computeBatteryRealtime(rawRealtime, which);
+        final long totalRealtime = computeRealtime(rawRealtime, which);
+        final long totalUptime = computeUptime(rawUptime, which);
+        final long screenOnTime = getScreenOnTime(batteryRealtime, which);
+        final long phoneOnTime = getPhoneOnTime(batteryRealtime, which);
+       
+        StringBuilder sb = new StringBuilder(128);
+        
+        String category = STAT_NAMES[which];
+        
+        // Dump "battery" stat
+        dumpLine(pw, 0 /* uid */, category, BATTERY_DATA, 
+                which == STATS_TOTAL ? getStartCount() : "N/A",
+                whichBatteryUptime / 1000, whichBatteryRealtime / 1000, 
+                totalUptime / 1000, totalRealtime / 1000); 
+        
+        // Dump misc stats
+        dumpLine(pw, 0 /* uid */, category, MISC_DATA,
+                screenOnTime / 1000, phoneOnTime / 1000);
+        
+        SparseArray<? extends Uid> uidStats = getUidStats();
+        final int NU = uidStats.size();
+        for (int iu = 0; iu < NU; iu++) {
+            final int uid = uidStats.keyAt(iu);
+            Uid u = uidStats.valueAt(iu);
+            // Dump Network stats per uid, if any
+            long rx = u.getTcpBytesReceived(which);
+            long tx = u.getTcpBytesSent(which);
+            if (rx > 0 || tx > 0) dumpLine(pw, uid, category, NETWORK_DATA, rx, tx);
+
+            Map<String, ? extends BatteryStats.Uid.Wakelock> wakelocks = u.getWakelockStats();
+            if (wakelocks.size() > 0) {
+                for (Map.Entry<String, ? extends BatteryStats.Uid.Wakelock> ent
+                        : wakelocks.entrySet()) {
+                    Uid.Wakelock wl = ent.getValue();
+                    String linePrefix = "";
+                    sb.setLength(0);
+                    linePrefix = printWakeLockCheckin(sb, wl.getWakeTime(WAKE_TYPE_FULL), batteryRealtime,
+                            "full", which, linePrefix);
+                    linePrefix = printWakeLockCheckin(sb, wl.getWakeTime(WAKE_TYPE_PARTIAL), batteryRealtime,
+                            "partial", which, linePrefix);
+                    linePrefix = printWakeLockCheckin(sb, wl.getWakeTime(WAKE_TYPE_WINDOW), batteryRealtime,
+                            "window", which, linePrefix);
+                    
+                    // Only log if we had at lease one wakelock...
+                    if (sb.length() > 0) {
+                       dumpLine(pw, uid, category, WAKELOCK_DATA, ent.getKey(), sb.toString());
+                    }
+                }
+            }
+                
+            Map<Integer, ? extends BatteryStats.Uid.Sensor> sensors = u.getSensorStats();
+            if (sensors.size() > 0)  {
+                for (Map.Entry<Integer, ? extends BatteryStats.Uid.Sensor> ent
+                        : sensors.entrySet()) {
+                    Uid.Sensor se = ent.getValue();
+                    int sensorNumber = ent.getKey();
+                    Timer timer = se.getSensorTime();
+                    if (timer != null) {
+                        // Convert from microseconds to milliseconds with rounding
+                        long totalTime = (timer.getTotalTime(batteryRealtime, which) + 500) / 1000;
+                        int count = timer.getCount(which);
+                        if (totalTime != 0) {
+                            dumpLine(pw, uid, category, SENSOR_DATA, sensorNumber, totalTime, count);
+                        }
+                    } 
+                }
+            }
+
+            Map<String, ? extends BatteryStats.Uid.Proc> processStats = u.getProcessStats();
+            if (processStats.size() > 0) {
+                for (Map.Entry<String, ? extends BatteryStats.Uid.Proc> ent
+                        : processStats.entrySet()) {
+                    Uid.Proc ps = ent.getValue();
+    
+                    long userTime = ps.getUserTime(which);
+                    long systemTime = ps.getSystemTime(which);
+                    int starts = ps.getStarts(which);
+    
+                    if (userTime != 0 || systemTime != 0 || starts != 0) {
+                        dumpLine(pw, uid, category, PROCESS_DATA, 
+                                ent.getKey(), // proc
+                                userTime * 10, // cpu time in ms
+                                systemTime * 10, // user time in ms
+                                starts); // process starts
+                    }
+                }
+            }
+
+            Map<String, ? extends BatteryStats.Uid.Pkg> packageStats = u.getPackageStats();
+            if (packageStats.size() > 0) {
+                for (Map.Entry<String, ? extends BatteryStats.Uid.Pkg> ent
+                        : packageStats.entrySet()) {
+              
+                    Uid.Pkg ps = ent.getValue();
+                    int wakeups = ps.getWakeups(which);
+                    Map<String, ? extends  Uid.Pkg.Serv> serviceStats = ps.getServiceStats();
+                    for (Map.Entry<String, ? extends BatteryStats.Uid.Pkg.Serv> sent
+                            : serviceStats.entrySet()) {
+                        BatteryStats.Uid.Pkg.Serv ss = sent.getValue();
+                        long startTime = ss.getStartTime(batteryUptime, which);
+                        int starts = ss.getStarts(which);
+                        int launches = ss.getLaunches(which);
+                        if (startTime != 0 || starts != 0 || launches != 0) {
+                            dumpLine(pw, uid, category, APK_DATA, 
+                                    wakeups, // wakeup alarms
+                                    ent.getKey(), // Apk
+                                    sent.getKey(), // service
+                                    startTime / 1000, // time spent started, in ms
+                                    starts,
+                                    launches);
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    @SuppressWarnings("unused")
+    private final void dumpLocked(Printer pw, String prefix, int which) {
+        final long rawUptime = SystemClock.uptimeMillis() * 1000;
+        final long rawRealtime = SystemClock.elapsedRealtime() * 1000;
+        final long batteryUptime = getBatteryUptime(rawUptime);
+        final long batteryRealtime = getBatteryUptime(rawRealtime);
+
+        final long whichBatteryUptime = computeBatteryUptime(rawUptime, which);
+        final long whichBatteryRealtime = computeBatteryRealtime(rawRealtime, which);
+        final long totalRealtime = computeRealtime(rawRealtime, which);
+        final long totalUptime = computeUptime(rawUptime, which);
+        
+        StringBuilder sb = new StringBuilder(128);
+
+        pw.println(prefix
+                + "  Time on battery: " + formatTimeMs(whichBatteryUptime / 1000)
+                + "(" + formatRatioLocked(whichBatteryUptime, totalRealtime)
+                + ") uptime, "
+                + formatTimeMs(whichBatteryRealtime / 1000) + "("
+                + formatRatioLocked(whichBatteryRealtime, totalRealtime)
+                + ") realtime");
+        pw.println(prefix
+                + "  Total: "
+                + formatTimeMs(totalUptime / 1000)
+                + "uptime, "
+                + formatTimeMs(totalRealtime / 1000)
+                + "realtime");
+        
+        long screenOnTime = getScreenOnTime(batteryRealtime, which);
+        long phoneOnTime = getPhoneOnTime(batteryRealtime, which);
+        pw.println(prefix
+                + "  Time with screen on: " + formatTimeMs(screenOnTime / 1000)
+                + "(" + formatRatioLocked(screenOnTime, whichBatteryRealtime)
+                + "), time with phone on: " + formatTimeMs(phoneOnTime / 1000)
+                + "(" + formatRatioLocked(phoneOnTime, whichBatteryRealtime) + ")");
+        
+        pw.println(" ");
+
+        SparseArray<? extends Uid> uidStats = getUidStats();
+        final int NU = uidStats.size();
+        for (int iu=0; iu<NU; iu++) {
+            final int uid = uidStats.keyAt(iu);
+            Uid u = uidStats.valueAt(iu);
+            pw.println(prefix + "  #" + uid + ":");
+            boolean uidActivity = false;
+            
+            long tcpReceived = u.getTcpBytesReceived(which);
+            long tcpSent = u.getTcpBytesSent(which);
+            if (tcpReceived != 0 || tcpSent != 0) {
+                pw.println(prefix + "    Network: " + tcpReceived + " bytes received, "
+                        + tcpSent + " bytes sent");
+            }
+
+            Map<String, ? extends BatteryStats.Uid.Wakelock> wakelocks = u.getWakelockStats();
+            if (wakelocks.size() > 0) {
+                for (Map.Entry<String, ? extends BatteryStats.Uid.Wakelock> ent
+                    : wakelocks.entrySet()) {
+                    Uid.Wakelock wl = ent.getValue();
+                    String linePrefix = ": ";
+                    sb.setLength(0);
+                    sb.append(prefix);
+                    sb.append("    Wake lock ");
+                    sb.append(ent.getKey());
+                    linePrefix = printWakeLock(sb, wl.getWakeTime(WAKE_TYPE_FULL), batteryRealtime,
+                            "full", which, linePrefix);
+                    linePrefix = printWakeLock(sb, wl.getWakeTime(WAKE_TYPE_PARTIAL), batteryRealtime,
+                            "partial", which, linePrefix);
+                    linePrefix = printWakeLock(sb, wl.getWakeTime(WAKE_TYPE_WINDOW), batteryRealtime,
+                            "window", which, linePrefix);
+                    if (!linePrefix.equals(": ")) {
+                        sb.append(" realtime");
+                    } else {
+                        sb.append(": (nothing executed)");
+                    }
+                    pw.println(sb.toString());
+                    uidActivity = true;
+                }
+            }
+
+            Map<Integer, ? extends BatteryStats.Uid.Sensor> sensors = u.getSensorStats();
+            if (sensors.size() > 0) {
+                for (Map.Entry<Integer, ? extends BatteryStats.Uid.Sensor> ent
+                    : sensors.entrySet()) {
+                    Uid.Sensor se = ent.getValue();
+                    int sensorNumber = ent.getKey();
+                    sb.setLength(0);
+                    sb.append(prefix);
+                    sb.append("    Sensor ");
+                    int handle = se.getHandle();
+                    if (handle == Uid.Sensor.GPS) {
+                        sb.append("GPS");
+                    } else {
+                        sb.append(handle);
+                    }
+                    sb.append(": ");
+
+                    Timer timer = se.getSensorTime();
+                    if (timer != null) {
+                        // Convert from microseconds to milliseconds with rounding
+                        long totalTime = (timer.getTotalTime(batteryRealtime, which) + 500) / 1000;
+                        int count = timer.getCount(which);
+                        //timer.logState();
+                        if (totalTime != 0) {
+                            sb.append(formatTimeMs(totalTime));
+                            sb.append("realtime (");
+                            sb.append(count);
+                            sb.append(" times)");
+                        } else {
+                            sb.append("(not used)");
+                        }
+                    } else {
+                        sb.append("(not used)");
+                    }
+
+                    pw.println(sb.toString());
+                    uidActivity = true;
+                }
+            }
+
+            Map<String, ? extends BatteryStats.Uid.Proc> processStats = u.getProcessStats();
+            if (processStats.size() > 0) {
+                for (Map.Entry<String, ? extends BatteryStats.Uid.Proc> ent
+                    : processStats.entrySet()) {
+                    Uid.Proc ps = ent.getValue();
+                    long userTime;
+                    long systemTime;
+                    int starts;
+
+                    userTime = ps.getUserTime(which);
+                    systemTime = ps.getSystemTime(which);
+                    starts = ps.getStarts(which);
+
+                    if (userTime != 0 || systemTime != 0 || starts != 0) {
+                        pw.println(prefix + "    Proc " + ent.getKey() + ":");
+                        pw.println(prefix + "      CPU: " + formatTime(userTime) + "user + "
+                                + formatTime(systemTime) + "kernel");
+                        pw.println(prefix + "      " + starts + " process starts");
+                        uidActivity = true;
+                    }
+                }
+            }
+
+            Map<String, ? extends BatteryStats.Uid.Pkg> packageStats = u.getPackageStats();
+            if (packageStats.size() > 0) {
+                for (Map.Entry<String, ? extends BatteryStats.Uid.Pkg> ent
+                    : packageStats.entrySet()) {
+                    pw.println(prefix + "    Apk " + ent.getKey() + ":");
+                    boolean apkActivity = false;
+                    Uid.Pkg ps = ent.getValue();
+                    int wakeups = ps.getWakeups(which);
+                    if (wakeups != 0) {
+                        pw.println(prefix + "      " + wakeups + " wakeup alarms");
+                        apkActivity = true;
+                    }
+                    Map<String, ? extends  Uid.Pkg.Serv> serviceStats = ps.getServiceStats();
+                    if (serviceStats.size() > 0) {
+                        for (Map.Entry<String, ? extends BatteryStats.Uid.Pkg.Serv> sent
+                                : serviceStats.entrySet()) {
+                            BatteryStats.Uid.Pkg.Serv ss = sent.getValue();
+                            long startTime = ss.getStartTime(batteryUptime, which);
+                            int starts = ss.getStarts(which);
+                            int launches = ss.getLaunches(which);
+                            if (startTime != 0 || starts != 0 || launches != 0) {
+                                pw.println(prefix + "      Service " + sent.getKey() + ":");
+                                pw.println(prefix + "        Created for: "
+                                        + formatTimeMs(startTime / 1000)
+                                        + " uptime");
+                                pw.println(prefix + "        Starts: " + starts
+                                        + ", launches: " + launches);
+                                apkActivity = true;
+                            }
+                        }
+                    }
+                    if (!apkActivity) {
+                        pw.println(prefix + "      (nothing executed)");
+                    }
+                    uidActivity = true;
+                }
+            }
+            if (!uidActivity) {
+                pw.println(prefix + "    (nothing executed)");
+            }
+        }
+    }
+
+    /**
+     * Dumps a human-readable summary of the battery statistics to the given PrintWriter.
+     *
+     * @param pw a Printer to receive the dump output.
+     */
+    @SuppressWarnings("unused")
+    public void dumpLocked(Printer pw) {
+        pw.println("Total Statistics (Current and Historic):");
+        pw.println("  System starts: " + getStartCount()
+                + ", currently on battery: " + getIsOnBattery());
+        dumpLocked(pw, "", STATS_TOTAL);
+        pw.println("");
+        pw.println("Last Run Statistics (Previous run of system):");
+        dumpLocked(pw, "", STATS_LAST);
+        pw.println("");
+        pw.println("Current Battery Statistics (Currently running system):");
+        dumpLocked(pw, "", STATS_CURRENT);
+        pw.println("");
+        pw.println("Unplugged Statistics (Since last unplugged from power):");
+        dumpLocked(pw, "", STATS_UNPLUGGED);
+    }
+    
+    @SuppressWarnings("unused")
+    public void dumpCheckinLocked(PrintWriter pw, String[] args) {
+        boolean isUnpluggedOnly = false;
+        
+        for (String arg : args) {
+            if ("-u".equals(arg)) {
+                if (LOCAL_LOGV) Log.v("BatteryStats", "Dumping unplugged data");
+                isUnpluggedOnly = true;
+            }
+        }
+        
+        if (isUnpluggedOnly) {
+            dumpCheckinLocked(pw, STATS_UNPLUGGED);
+        }
+        else {
+            dumpCheckinLocked(pw, STATS_TOTAL);
+            dumpCheckinLocked(pw, STATS_LAST);
+            dumpCheckinLocked(pw, STATS_UNPLUGGED);
+            dumpCheckinLocked(pw, STATS_CURRENT);
+        }
+    }
+    
+}
diff --git a/core/java/android/os/Binder.java b/core/java/android/os/Binder.java
new file mode 100644
index 0000000..df10c6a
--- /dev/null
+++ b/core/java/android/os/Binder.java
@@ -0,0 +1,355 @@
+/*
+ * 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 android.os;
+
+import android.util.Config;
+import android.util.Log;
+
+import java.io.FileDescriptor;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.lang.ref.WeakReference;
+import java.lang.reflect.Modifier;
+
+/**
+ * Base class for a remotable object, the core part of a lightweight
+ * remote procedure call mechanism defined by {@link IBinder}.
+ * This class is an implementation of IBinder that provides
+ * the standard support creating a local implementation of such an object.
+ * 
+ * <p>Most developers will not implement this class directly, instead using the
+ * <a href="{@docRoot}guide/developing/tools/aidl.html">aidl</a> tool to describe the desired
+ * interface, having it generate the appropriate Binder subclass.  You can,
+ * however, derive directly from Binder to implement your own custom RPC
+ * protocol or simply instantiate a raw Binder object directly to use as a
+ * token that can be shared across processes.
+ * 
+ * @see IBinder
+ */
+public class Binder implements IBinder {
+    /*
+     * Set this flag to true to detect anonymous, local or member classes
+     * that extend this Binder class and that are not static. These kind
+     * of classes can potentially create leaks.
+     */
+    private static final boolean FIND_POTENTIAL_LEAKS = false;
+    private static final String TAG = "Binder";
+
+    private int mObject;
+    private IInterface mOwner;
+    private String mDescriptor;
+    
+    /**
+     * Return the ID of the process that sent you the current transaction
+     * that is being processed.  This pid can be used with higher-level
+     * system services to determine its identity and check permissions.
+     * If the current thread is not currently executing an incoming transaction,
+     * then its own pid is returned.
+     */
+    public static final native int getCallingPid();
+    
+    /**
+     * Return the ID of the user assigned to the process that sent you the
+     * current transaction that is being processed.  This uid can be used with
+     * higher-level system services to determine its identity and check
+     * permissions.  If the current thread is not currently executing an
+     * incoming transaction, then its own uid is returned.
+     */
+    public static final native int getCallingUid();
+    
+    /**
+     * Reset the identity of the incoming IPC to the local process.  This can
+     * be useful if, while handling an incoming call, you will be calling
+     * on interfaces of other objects that may be local to your process and
+     * need to do permission checks on the calls coming into them (so they
+     * will check the permission of your own local process, and not whatever
+     * process originally called you).
+     * 
+     * @return Returns an opaque token that can be used to restore the
+     * original calling identity by passing it to
+     * {@link #restoreCallingIdentity(long)}.
+     * 
+     * @see #getCallingPid()
+     * @see #getCallingUid()
+     * @see #restoreCallingIdentity(long)
+     */
+    public static final native long clearCallingIdentity();
+    
+    /**
+     * Restore the identity of the incoming IPC back to a previously identity
+     * that was returned by {@link #clearCallingIdentity}.
+     * 
+     * @param token The opaque token that was previously returned by
+     * {@link #clearCallingIdentity}.
+     * 
+     * @see #clearCallingIdentity
+     */
+    public static final native void restoreCallingIdentity(long token);
+    
+    /**
+     * Flush any Binder commands pending in the current thread to the kernel
+     * driver.  This can be
+     * useful to call before performing an operation that may block for a long
+     * time, to ensure that any pending object references have been released
+     * in order to prevent the process from holding on to objects longer than
+     * it needs to.
+     */
+    public static final native void flushPendingCommands();
+    
+    /**
+     * Add the calling thread to the IPC thread pool.  This function does
+     * not return until the current process is exiting.
+     */
+    public static final native void joinThreadPool();
+    
+    /**
+     * Default constructor initializes the object.
+     */
+    public Binder() {
+        init();
+
+        if (FIND_POTENTIAL_LEAKS) {
+            final Class<? extends Binder> klass = getClass();
+            if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
+                    (klass.getModifiers() & Modifier.STATIC) == 0) {
+                Log.w(TAG, "The following Binder class should be static or leaks might occur: " +
+                    klass.getCanonicalName());
+            }
+        }
+    }
+    
+    /**
+     * Convenience method for associating a specific interface with the Binder.
+     * After calling, queryLocalInterface() will be implemented for you
+     * to return the given owner IInterface when the corresponding
+     * descriptor is requested.
+     */
+    public void attachInterface(IInterface owner, String descriptor) {
+        mOwner = owner;
+        mDescriptor = descriptor;
+    }
+    
+    /**
+     * Default implementation returns an empty interface name.
+     */
+    public String getInterfaceDescriptor() {
+        return mDescriptor;
+    }
+
+    /**
+     * Default implementation always returns true -- if you got here,
+     * the object is alive.
+     */
+    public boolean pingBinder() {
+        return true;
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * Note that if you're calling on a local binder, this always returns true
+     * because your process is alive if you're calling it.
+     */
+    public boolean isBinderAlive() {
+        return true;
+    }
+    
+    /**
+     * Use information supplied to attachInterface() to return the
+     * associated IInterface if it matches the requested
+     * descriptor.
+     */
+    public IInterface queryLocalInterface(String descriptor) {
+        if (mDescriptor.equals(descriptor)) {
+            return mOwner;
+        }
+        return null;
+    }
+    
+    /**
+     * Default implementation is a stub that returns false.  You will want
+     * to override this to do the appropriate unmarshalling of transactions.
+     *
+     * <p>If you want to call this, call transact().
+     */
+    protected boolean onTransact(int code, Parcel data, Parcel reply,
+            int flags) throws RemoteException {
+        if (code == INTERFACE_TRANSACTION) {
+            reply.writeString(getInterfaceDescriptor());
+            return true;
+        } else if (code == DUMP_TRANSACTION) {
+            ParcelFileDescriptor fd = data.readFileDescriptor();
+            String[] args = data.readStringArray();
+            if (fd != null) {
+                try {
+                    dump(fd.getFileDescriptor(), args);
+                } finally {
+                    try {
+                        fd.close();
+                    } catch (IOException e) {
+                    }
+                }
+            }
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Implemented to call the more convenient version
+     * {@link #dump(FileDescriptor, PrintWriter, String[])}.
+     */
+    public void dump(FileDescriptor fd, String[] args) {
+        FileOutputStream fout = new FileOutputStream(fd);
+        PrintWriter pw = new PrintWriter(fout);
+        try {
+            dump(fd, pw, args);
+        } finally {
+            pw.flush();
+        }
+    }
+    
+    /**
+     * Print the object's state into the given stream.
+     * 
+     * @param fd The raw file descriptor that the dump is being sent to.
+     * @param fout The file to which you should dump your state.  This will be
+     * closed for you after you return.
+     * @param args additional arguments to the dump request.
+     */
+    protected void dump(FileDescriptor fd, PrintWriter fout, String[] args) {
+    }
+
+    /**
+     * Default implementation rewinds the parcels and calls onTransact.  On
+     * the remote side, transact calls into the binder to do the IPC.
+     */
+    public final boolean transact(int code, Parcel data, Parcel reply,
+            int flags) throws RemoteException {
+        if (Config.LOGV) Log.v("Binder", "Transact: " + code + " to " + this);
+        if (data != null) {
+            data.setDataPosition(0);
+        }
+        boolean r = onTransact(code, data, reply, flags);
+        if (reply != null) {
+            reply.setDataPosition(0);
+        }
+        return r;
+    }
+    
+    /**
+     * Local implementation is a no-op.
+     */
+    public void linkToDeath(DeathRecipient recipient, int flags) {
+    }
+
+    /**
+     * Local implementation is a no-op.
+     */
+    public boolean unlinkToDeath(DeathRecipient recipient, int flags) {
+        return true;
+    }
+    
+    protected void finalize() throws Throwable {
+        try {
+            destroy();
+        } finally {
+            super.finalize();
+        }
+    }
+    
+    private native final void init();
+    private native final void destroy();
+    private boolean execTransact(int code, int dataObj, int replyObj,
+            int flags) {
+        Parcel data = Parcel.obtain(dataObj);
+        Parcel reply = Parcel.obtain(replyObj);
+        // theoretically, we should call transact, which will call onTransact,
+        // but all that does is rewind it, and we just got these from an IPC,
+        // so we'll just call it directly.
+        boolean res;
+        try {
+            res = onTransact(code, data, reply, flags);
+        } catch (RemoteException e) {
+            reply.writeException(e);
+            res = true;
+        } catch (RuntimeException e) {
+            reply.writeException(e);
+            res = true;
+        }
+        reply.recycle();
+        data.recycle();
+        return res;
+    }
+}
+
+final class BinderProxy implements IBinder {
+    public native boolean pingBinder();
+    public native boolean isBinderAlive();
+    
+    public IInterface queryLocalInterface(String descriptor) {
+        return null;
+    }
+    
+    public native String getInterfaceDescriptor() throws RemoteException;
+    public native boolean transact(int code, Parcel data, Parcel reply,
+            int flags) throws RemoteException;
+    public native void linkToDeath(DeathRecipient recipient, int flags)
+            throws RemoteException;
+    public native boolean unlinkToDeath(DeathRecipient recipient, int flags);
+
+    public void dump(FileDescriptor fd, String[] args) throws RemoteException {
+        Parcel data = Parcel.obtain();
+        data.writeFileDescriptor(fd);
+        data.writeStringArray(args);
+        try {
+            transact(DUMP_TRANSACTION, data, null, 0);
+        } finally {
+            data.recycle();
+        }
+    }
+    
+    BinderProxy() {
+        mSelf = new WeakReference(this);
+    }
+    
+    @Override
+    protected void finalize() throws Throwable {
+        try {
+            destroy();
+        } finally {
+            super.finalize();
+        }
+    }
+    
+    private native final void destroy();
+    
+    private static final void sendDeathNotice(DeathRecipient recipient) {
+        if (Config.LOGV) Log.v("JavaBinder", "sendDeathNotice to " + recipient);
+        try {
+            recipient.binderDied();
+        }
+        catch (RuntimeException exc) {
+            Log.w("BinderNative", "Uncaught exception from death notification",
+                    exc);
+        }
+    }
+    
+    final private WeakReference mSelf;
+    private int mObject;
+}
diff --git a/core/java/android/os/Broadcaster.java b/core/java/android/os/Broadcaster.java
new file mode 100644
index 0000000..96dc61a
--- /dev/null
+++ b/core/java/android/os/Broadcaster.java
@@ -0,0 +1,212 @@
+/*
+ * 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 android.os;
+
+/** @hide */
+public class Broadcaster
+{
+    public Broadcaster()
+    {
+    }
+
+    /**
+     *  Sign up for notifications about something.
+     *
+     *  When this broadcaster pushes a message with senderWhat in the what field,
+     *  target will be sent a copy of that message with targetWhat in the what field.
+     */
+    public void request(int senderWhat, Handler target, int targetWhat)
+    {
+        synchronized (this) {
+            Registration r = null;
+            if (mReg == null) {
+                r = new Registration();
+                r.senderWhat = senderWhat;
+                r.targets = new Handler[1];
+                r.targetWhats = new int[1];
+                r.targets[0] = target;
+                r.targetWhats[0] = targetWhat;
+                mReg = r;
+                r.next = r;
+                r.prev = r;
+            } else {
+                // find its place in the map
+                Registration start = mReg;
+                r = start;
+                do {
+                    if (r.senderWhat >= senderWhat) {
+                        break;
+                    }
+                    r = r.next;
+                } while (r != start);
+                int n;
+                if (r.senderWhat != senderWhat) {
+                    // we didn't find a senderWhat match, but r is right
+                    // after where it goes
+                    Registration reg = new Registration();
+                    reg.senderWhat = senderWhat;
+                    reg.targets = new Handler[1];
+                    reg.targetWhats = new int[1];
+                    reg.next = r;
+                    reg.prev = r.prev;
+                    r.prev.next = reg;
+                    r.prev = reg;
+
+                    if (r == mReg && r.senderWhat > reg.senderWhat) {
+                        mReg = reg;
+                    }
+                    
+                    r = reg;
+                    n = 0;
+                } else {
+                    n = r.targets.length;
+                    Handler[] oldTargets = r.targets;
+                    int[] oldWhats = r.targetWhats;
+                    // check for duplicates, and don't do it if we are dup.
+                    for (int i=0; i<n; i++) {
+                        if (oldTargets[i] == target && oldWhats[i] == targetWhat) {
+                            return;
+                        }
+                    }
+                    r.targets = new Handler[n+1];
+                    System.arraycopy(oldTargets, 0, r.targets, 0, n);
+                    r.targetWhats = new int[n+1];
+                    System.arraycopy(oldWhats, 0, r.targetWhats, 0, n);
+                }
+                r.targets[n] = target;
+                r.targetWhats[n] = targetWhat;
+            }
+        }
+    }
+    
+    /**
+     * Unregister for notifications for this senderWhat/target/targetWhat tuple.
+     */
+    public void cancelRequest(int senderWhat, Handler target, int targetWhat)
+    {
+        synchronized (this) {
+            Registration start = mReg;
+            Registration r = start;
+            
+            if (r == null) {
+                return;
+            }
+            
+            do {
+                if (r.senderWhat >= senderWhat) {
+                    break;
+                }
+                r = r.next;
+            } while (r != start);
+            
+            if (r.senderWhat == senderWhat) {
+                Handler[] targets = r.targets;
+                int[] whats = r.targetWhats;
+                int oldLen = targets.length;
+                for (int i=0; i<oldLen; i++) {
+                    if (targets[i] == target && whats[i] == targetWhat) {
+                        r.targets = new Handler[oldLen-1];
+                        r.targetWhats = new int[oldLen-1];
+                        if (i > 0) {
+                            System.arraycopy(targets, 0, r.targets, 0, i);
+                            System.arraycopy(whats, 0, r.targetWhats, 0, i);
+                        }
+
+                        int remainingLen = oldLen-i-1;
+                        if (remainingLen != 0) {
+                            System.arraycopy(targets, i+1, r.targets, i,
+                                    remainingLen);
+                            System.arraycopy(whats, i+1, r.targetWhats, i,
+                                    remainingLen);
+                        }
+                        break;
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * For debugging purposes, print the registrations to System.out
+     */
+    public void dumpRegistrations()
+    {
+        synchronized (this) {
+            Registration start = mReg;
+            System.out.println("Broadcaster " + this + " {");
+            if (start != null) {
+                Registration r = start;
+                do {
+                    System.out.println("    senderWhat=" + r.senderWhat);
+                    int n = r.targets.length;
+                    for (int i=0; i<n; i++) {
+                        System.out.println("        [" + r.targetWhats[i]
+                                        + "] " + r.targets[i]);
+                    }
+                    r = r.next;
+                } while (r != start);
+            }
+            System.out.println("}");
+        }
+    }
+
+    /**
+     * Send out msg.  Anyone who has registered via the request() method will be
+     * sent the message.
+     */
+    public void broadcast(Message msg)
+    {
+        synchronized (this) {
+        	if (mReg == null) {
+        		return;
+        	}
+        	
+            int senderWhat = msg.what;
+            Registration start = mReg;
+            Registration r = start;
+            do {
+                if (r.senderWhat >= senderWhat) {
+                    break;
+                }
+                r = r.next;
+            } while (r != start);
+            if (r.senderWhat == senderWhat) {
+                Handler[] targets = r.targets;
+                int[] whats = r.targetWhats;
+                int n = targets.length;
+                for (int i=0; i<n; i++) {
+                    Handler target = targets[i];
+                    Message m = Message.obtain();
+                    m.copyFrom(msg);
+                    m.what = whats[i];
+                    target.sendMessage(m);
+                }
+            }
+        }
+    }
+
+    private class Registration
+    {
+        Registration next;
+        Registration prev;
+
+        int senderWhat;
+        Handler[] targets;
+        int[] targetWhats;
+    }
+    private Registration mReg;
+}
diff --git a/core/java/android/os/Build.java b/core/java/android/os/Build.java
new file mode 100644
index 0000000..467c17f
--- /dev/null
+++ b/core/java/android/os/Build.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2007 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 android.os;
+
+/**
+ * Information about the current build, extracted from system properties.
+ */
+public class Build {
+    /** Value used for when a build property is unknown. */
+    private static final String UNKNOWN = "unknown";
+
+    /** Either a changelist number, or a label like "M4-rc20". */
+    public static final String ID = getString("ro.build.id");
+
+    /** A build ID string meant for displaying to the user */
+    public static final String DISPLAY = getString("ro.build.display.id");
+
+    /** The name of the overall product. */
+    public static final String PRODUCT = getString("ro.product.name");
+
+    /** The name of the industrial design. */
+    public static final String DEVICE = getString("ro.product.device");
+
+    /** The name of the underlying board, like "goldfish". */
+    public static final String BOARD = getString("ro.product.board");
+
+    /** The brand (e.g., carrier) the software is customized for, if any. */
+    public static final String BRAND = getString("ro.product.brand");
+
+    /** The end-user-visible name for the end product. */
+    public static final String MODEL = getString("ro.product.model");
+
+    /** Various version strings. */
+    public static class VERSION {
+        /**
+         * The internal value used by the underlying source control to
+         * represent this build.  E.g., a perforce changelist number
+         * or a git hash.
+         */
+        public static final String INCREMENTAL = getString("ro.build.version.incremental");
+
+        /**
+         * The user-visible version string.  E.g., "1.0" or "3.4b5".
+         */
+        public static final String RELEASE = getString("ro.build.version.release");
+
+        /**
+         * The user-visible SDK version of the framework. It is an integer starting at 1.
+         */
+        public static final String SDK = getString("ro.build.version.sdk");
+    }
+
+    /** The type of build, like "user" or "eng". */
+    public static final String TYPE = getString("ro.build.type");
+
+    /** Comma-separated tags describing the build, like "unsigned,debug". */
+    public static final String TAGS = getString("ro.build.tags");
+
+    /** A string that uniquely identifies this build.  Do not attempt to parse this value. */
+    public static final String FINGERPRINT = getString("ro.build.fingerprint");
+
+    // The following properties only make sense for internal engineering builds.
+    public static final long TIME = getLong("ro.build.date.utc") * 1000;
+    public static final String USER = getString("ro.build.user");
+    public static final String HOST = getString("ro.build.host");
+
+    private static String getString(String property) {
+        return SystemProperties.get(property, UNKNOWN);
+    }
+
+    private static long getLong(String property) {
+        try {
+            return Long.parseLong(SystemProperties.get(property));
+        } catch (NumberFormatException e) {
+            return -1;
+        }
+    }
+}
diff --git a/core/java/android/os/Bundle.aidl b/core/java/android/os/Bundle.aidl
new file mode 100644
index 0000000..b9e1224
--- /dev/null
+++ b/core/java/android/os/Bundle.aidl
@@ -0,0 +1,20 @@
+/* //device/java/android/android/os/Bundle.aidl
+**
+** Copyright 2007, 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 android.os;
+
+parcelable Bundle;
diff --git a/core/java/android/os/Bundle.java b/core/java/android/os/Bundle.java
new file mode 100644
index 0000000..b669fa2
--- /dev/null
+++ b/core/java/android/os/Bundle.java
@@ -0,0 +1,1452 @@
+/*
+ * Copyright (C) 2007 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 android.os;
+
+import android.util.Log;
+import android.util.SparseArray;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * A mapping from String values to various Parcelable types.
+ *
+ */
+public final class Bundle implements Parcelable, Cloneable {
+    private static final String LOG_TAG = "Bundle";
+    public static final Bundle EMPTY;
+
+    static {
+        EMPTY = new Bundle();
+        EMPTY.mMap = Collections.unmodifiableMap(new HashMap<String, Object>());
+    }
+
+    // Invariant - exactly one of mMap / mParcelledData will be null
+    // (except inside a call to unparcel)
+
+    /* package */ Map<String, Object> mMap = null;
+
+    /*
+     * If mParcelledData is non-null, then mMap will be null and the
+     * data are stored as a Parcel containing a Bundle.  When the data
+     * are unparcelled, mParcelledData willbe set to null.
+     */
+    /* package */ Parcel mParcelledData = null;
+
+    private boolean mHasFds = false;
+    private boolean mFdsKnown = true;
+
+    /**
+     * The ClassLoader used when unparcelling data from mParcelledData.
+     */
+    private ClassLoader mClassLoader;
+
+    /**
+     * Constructs a new, empty Bundle.
+     */
+    public Bundle() {
+        mMap = new HashMap<String, Object>();
+        mClassLoader = getClass().getClassLoader();
+    }
+
+    /**
+     * Constructs a Bundle whose data is stored as a Parcel.  The data
+     * will be unparcelled on first contact, using the assigned ClassLoader.
+     *
+     * @param parcelledData a Parcel containing a Bundle
+     */
+    Bundle(Parcel parcelledData) {
+        readFromParcel(parcelledData);
+    }
+
+    /**
+     * Constructs a new, empty Bundle that uses a specific ClassLoader for
+     * instantiating Parcelable and Serializable objects.
+     *
+     * @param loader An explicit ClassLoader to use when instantiating objects
+     * inside of the Bundle.
+     */
+    public Bundle(ClassLoader loader) {
+        mMap = new HashMap<String, Object>();
+        mClassLoader = loader;
+    }
+
+    /**
+     * Constructs a new, empty Bundle sized to hold the given number of
+     * elements. The Bundle will grow as needed.
+     *
+     * @param capacity the initial capacity of the Bundle
+     */
+    public Bundle(int capacity) {
+        mMap = new HashMap<String, Object>(capacity);
+        mClassLoader = getClass().getClassLoader();
+    }
+
+    /**
+     * Constructs a Bundle containing a copy of the mappings from the given
+     * Bundle.
+     *
+     * @param b a Bundle to be copied.
+     */
+    public Bundle(Bundle b) {
+        if (b.mParcelledData != null) {
+            mParcelledData = Parcel.obtain();
+            mParcelledData.appendFrom(b.mParcelledData, 0, b.mParcelledData.dataSize());
+            mParcelledData.setDataPosition(0);
+        } else {
+            mParcelledData = null;
+        }
+
+        if (b.mMap != null) {
+            mMap = new HashMap<String, Object>(b.mMap);
+        } else {
+            mMap = null;
+        }
+
+        mHasFds = b.mHasFds;
+        mFdsKnown = b.mFdsKnown;
+        mClassLoader = b.mClassLoader;
+    }
+
+    /**
+     * Changes the ClassLoader this Bundle uses when instantiating objects.
+     *
+     * @param loader An explicit ClassLoader to use when instantiating objects
+     * inside of the Bundle.
+     */
+    public void setClassLoader(ClassLoader loader) {
+        mClassLoader = loader;
+    }
+
+    /**
+     * Clones the current Bundle. The internal map is cloned, but the keys and
+     * values to which it refers are copied by reference.
+     */
+    @Override
+    public Object clone() {
+        return new Bundle(this);
+    }
+
+    /**
+     * If the underlying data are stored as a Parcel, unparcel them
+     * using the currently assigned class loader.
+     */
+    /* package */ synchronized void unparcel() {
+        if (mParcelledData == null) {
+            return;
+        }
+
+        mParcelledData.setDataPosition(0);
+        Bundle b = mParcelledData.readBundleUnpacked(mClassLoader);
+        mMap = b.mMap;
+
+        mHasFds = mParcelledData.hasFileDescriptors();
+        mFdsKnown = true;
+        
+        mParcelledData.recycle();
+        mParcelledData = null;
+    }
+
+    /**
+     * Returns the number of mappings contained in this Bundle.
+     *
+     * @return the number of mappings as an int.
+     */
+    public int size() {
+        unparcel();
+        return mMap.size();
+    }
+
+    /**
+     * Returns true if the mapping of this Bundle is empty, false otherwise.
+     */
+    public boolean isEmpty() {
+        unparcel();
+        return mMap.isEmpty();
+    }
+
+    /**
+     * Removes all elements from the mapping of this Bundle.
+     */
+    public void clear() {
+        unparcel();
+        mMap.clear();
+        mHasFds = false;
+        mFdsKnown = true;
+    }
+
+    /**
+     * Returns true if the given key is contained in the mapping
+     * of this Bundle.
+     *
+     * @param key a String key
+     * @return true if the key is part of the mapping, false otherwise
+     */
+    public boolean containsKey(String key) {
+        unparcel();
+        return mMap.containsKey(key);
+    }
+
+    /**
+     * Returns the entry with the given key as an object.
+     *
+     * @param key a String key
+     * @return an Object, or null
+     */
+    public Object get(String key) {
+        unparcel();
+        return mMap.get(key);
+    }
+
+    /**
+     * Removes any entry with the given key from the mapping of this Bundle.
+     *
+     * @param key a String key
+     */
+    public void remove(String key) {
+        unparcel();
+        mMap.remove(key);
+    }
+
+    /**
+     * Inserts all mappings from the given Bundle into this Bundle.
+     *
+     * @param map a Bundle
+     */
+    public void putAll(Bundle map) {
+        unparcel();
+        map.unparcel();
+        mMap.putAll(map.mMap);
+
+        // fd state is now known if and only if both bundles already knew
+        mHasFds |= map.mHasFds;
+        mFdsKnown = mFdsKnown && map.mFdsKnown;
+    }
+
+    /**
+     * Returns a Set containing the Strings used as keys in this Bundle.
+     *
+     * @return a Set of String keys
+     */
+    public Set<String> keySet() {
+        unparcel();
+        return mMap.keySet();
+    }
+
+    /**
+     * Reports whether the bundle contains any parcelled file descriptors.
+     */
+    public boolean hasFileDescriptors() {
+        if (!mFdsKnown) {
+            boolean fdFound = false;    // keep going until we find one or run out of data
+            
+            if (mParcelledData != null) {
+                if (mParcelledData.hasFileDescriptors()) {
+                    fdFound = true;
+                }
+            } else {
+                // It's been unparcelled, so we need to walk the map
+                Iterator<Map.Entry<String, Object>> iter = mMap.entrySet().iterator();
+                while (!fdFound && iter.hasNext()) {
+                    Object obj = iter.next().getValue();
+                    if (obj instanceof Parcelable) {
+                        if ((((Parcelable)obj).describeContents()
+                                & Parcelable.CONTENTS_FILE_DESCRIPTOR) != 0) {
+                            fdFound = true;
+                            break;
+                        }
+                    } else if (obj instanceof Parcelable[]) {
+                        Parcelable[] array = (Parcelable[]) obj;
+                        for (int n = array.length - 1; n >= 0; n--) {
+                            if ((array[n].describeContents()
+                                    & Parcelable.CONTENTS_FILE_DESCRIPTOR) != 0) {
+                                fdFound = true;
+                                break;
+                            }
+                        }
+                    } else if (obj instanceof SparseArray) {
+                        SparseArray<? extends Parcelable> array =
+                                (SparseArray<? extends Parcelable>) obj;
+                        for (int n = array.size() - 1; n >= 0; n--) {
+                            if ((array.get(n).describeContents()
+                                    & Parcelable.CONTENTS_FILE_DESCRIPTOR) != 0) {
+                                fdFound = true;
+                                break;
+                            }
+                        }
+                    } else if (obj instanceof ArrayList) {
+                        ArrayList array = (ArrayList) obj;
+                        // an ArrayList here might contain either Strings or
+                        // Parcelables; only look inside for Parcelables
+                        if ((array.size() > 0)
+                                && (array.get(0) instanceof Parcelable)) {
+                            for (int n = array.size() - 1; n >= 0; n--) {
+                                Parcelable p = (Parcelable) array.get(n);
+                                if (p != null && ((p.describeContents()
+                                        & Parcelable.CONTENTS_FILE_DESCRIPTOR) != 0)) {
+                                    fdFound = true;
+                                    break;
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+
+            mHasFds = fdFound;
+            mFdsKnown = true;
+        }
+        return mHasFds;
+    }
+    
+    /**
+     * Inserts a Boolean value into the mapping of this Bundle, replacing
+     * any existing value for the given key.  Either key or value may be null.
+     *
+     * @param key a String, or null
+     * @param value a Boolean, or null
+     */
+    public void putBoolean(String key, boolean value) {
+        unparcel();
+        mMap.put(key, value);
+    }
+
+    /**
+     * Inserts a byte value into the mapping of this Bundle, replacing
+     * any existing value for the given key.
+     *
+     * @param key a String, or null
+     * @param value a byte
+     */
+    public void putByte(String key, byte value) {
+        unparcel();
+        mMap.put(key, value);
+    }
+
+    /**
+     * Inserts a char value into the mapping of this Bundle, replacing
+     * any existing value for the given key.
+     *
+     * @param key a String, or null
+     * @param value a char, or null
+     */
+    public void putChar(String key, char value) {
+        unparcel();
+        mMap.put(key, value);
+    }
+
+    /**
+     * Inserts a short value into the mapping of this Bundle, replacing
+     * any existing value for the given key.
+     *
+     * @param key a String, or null
+     * @param value a short
+     */
+    public void putShort(String key, short value) {
+        unparcel();
+        mMap.put(key, value);
+    }
+
+    /**
+     * Inserts an int value into the mapping of this Bundle, replacing
+     * any existing value for the given key.
+     *
+     * @param key a String, or null
+     * @param value an int, or null
+     */
+    public void putInt(String key, int value) {
+        unparcel();
+        mMap.put(key, value);
+    }
+
+    /**
+     * Inserts a long value into the mapping of this Bundle, replacing
+     * any existing value for the given key.
+     *
+     * @param key a String, or null
+     * @param value a long
+     */
+    public void putLong(String key, long value) {
+        unparcel();
+        mMap.put(key, value);
+    }
+
+    /**
+     * Inserts a float value into the mapping of this Bundle, replacing
+     * any existing value for the given key.
+     *
+     * @param key a String, or null
+     * @param value a float
+     */
+    public void putFloat(String key, float value) {
+        unparcel();
+        mMap.put(key, value);
+    }
+
+    /**
+     * Inserts a double value into the mapping of this Bundle, replacing
+     * any existing value for the given key.
+     *
+     * @param key a String, or null
+     * @param value a double
+     */
+    public void putDouble(String key, double value) {
+        unparcel();
+        mMap.put(key, value);
+    }
+
+    /**
+     * Inserts a String value into the mapping of this Bundle, replacing
+     * any existing value for the given key.  Either key or value may be null.
+     *
+     * @param key a String, or null
+     * @param value a String, or null
+     */
+    public void putString(String key, String value) {
+        unparcel();
+        mMap.put(key, value);
+    }
+
+    /**
+     * Inserts a CharSequence value into the mapping of this Bundle, replacing
+     * any existing value for the given key.  Either key or value may be null.
+     *
+     * @param key a String, or null
+     * @param value a CharSequence, or null
+     */
+    public void putCharSequence(String key, CharSequence value) {
+        unparcel();
+        mMap.put(key, value);
+    }
+
+    /**
+     * Inserts a Parcelable value into the mapping of this Bundle, replacing
+     * any existing value for the given key.  Either key or value may be null.
+     *
+     * @param key a String, or null
+     * @param value a Parcelable object, or null
+     */
+    public void putParcelable(String key, Parcelable value) {
+        unparcel();
+        mMap.put(key, value);
+        mFdsKnown = false;
+    }
+
+    /**
+     * Inserts an array of Parcelable values into the mapping of this Bundle,
+     * replacing any existing value for the given key.  Either key or value may
+     * be null.
+     *
+     * @param key a String, or null
+     * @param value an array of Parcelable objects, or null
+     */
+    public void putParcelableArray(String key, Parcelable[] value) {
+        unparcel();
+        mMap.put(key, value);
+        mFdsKnown = false;
+    }
+
+    /**
+     * Inserts a List of Parcelable values into the mapping of this Bundle,
+     * replacing any existing value for the given key.  Either key or value may
+     * be null.
+     *
+     * @param key a String, or null
+     * @param value an ArrayList of Parcelable objects, or null
+     */
+    public void putParcelableArrayList(String key,
+        ArrayList<? extends Parcelable> value) {
+        unparcel();
+        mMap.put(key, value);
+        mFdsKnown = false;
+    }
+
+    /**
+     * Inserts a SparceArray of Parcelable values into the mapping of this
+     * Bundle, replacing any existing value for the given key.  Either key
+     * or value may be null.
+     *
+     * @param key a String, or null
+     * @param value a SparseArray of Parcelable objects, or null
+     */
+    public void putSparseParcelableArray(String key,
+            SparseArray<? extends Parcelable> value) {
+        unparcel();
+        mMap.put(key, value);
+        mFdsKnown = false;
+    }
+
+    /**
+     * Inserts an ArrayList<Integer> value into the mapping of this Bundle, replacing
+     * any existing value for the given key.  Either key or value may be null.
+     *
+     * @param key a String, or null
+     * @param value an ArrayList<Integer> object, or null
+     */
+    public void putIntegerArrayList(String key, ArrayList<Integer> value) {
+        unparcel();
+        mMap.put(key, value);
+    }
+
+    /**
+     * Inserts an ArrayList<String> value into the mapping of this Bundle, replacing
+     * any existing value for the given key.  Either key or value may be null.
+     *
+     * @param key a String, or null
+     * @param value an ArrayList<String> object, or null
+     */
+    public void putStringArrayList(String key, ArrayList<String> value) {
+        unparcel();
+        mMap.put(key, value);
+    }
+
+    /**
+     * Inserts a Serializable value into the mapping of this Bundle, replacing
+     * any existing value for the given key.  Either key or value may be null.
+     *
+     * @param key a String, or null
+     * @param value a Serializable object, or null
+     */
+    public void putSerializable(String key, Serializable value) {
+        unparcel();
+        mMap.put(key, value);
+    }
+
+    /**
+     * Inserts a boolean array value into the mapping of this Bundle, replacing
+     * any existing value for the given key.  Either key or value may be null.
+     *
+     * @param key a String, or null
+     * @param value a boolean array object, or null
+     */
+    public void putBooleanArray(String key, boolean[] value) {
+        unparcel();
+        mMap.put(key, value);
+    }
+
+    /**
+     * Inserts a byte array value into the mapping of this Bundle, replacing
+     * any existing value for the given key.  Either key or value may be null.
+     *
+     * @param key a String, or null
+     * @param value a byte array object, or null
+     */
+    public void putByteArray(String key, byte[] value) {
+        unparcel();
+        mMap.put(key, value);
+    }
+
+    /**
+     * Inserts a short array value into the mapping of this Bundle, replacing
+     * any existing value for the given key.  Either key or value may be null.
+     *
+     * @param key a String, or null
+     * @param value a short array object, or null
+     */
+    public void putShortArray(String key, short[] value) {
+        unparcel();
+        mMap.put(key, value);
+    }
+
+    /**
+     * Inserts a char array value into the mapping of this Bundle, replacing
+     * any existing value for the given key.  Either key or value may be null.
+     *
+     * @param key a String, or null
+     * @param value a char array object, or null
+     */
+    public void putCharArray(String key, char[] value) {
+        unparcel();
+        mMap.put(key, value);
+    }
+
+    /**
+     * Inserts an int array value into the mapping of this Bundle, replacing
+     * any existing value for the given key.  Either key or value may be null.
+     *
+     * @param key a String, or null
+     * @param value an int array object, or null
+     */
+    public void putIntArray(String key, int[] value) {
+        unparcel();
+        mMap.put(key, value);
+    }
+
+    /**
+     * Inserts a long array value into the mapping of this Bundle, replacing
+     * any existing value for the given key.  Either key or value may be null.
+     *
+     * @param key a String, or null
+     * @param value a long array object, or null
+     */
+    public void putLongArray(String key, long[] value) {
+        unparcel();
+        mMap.put(key, value);
+    }
+
+    /**
+     * Inserts a float array value into the mapping of this Bundle, replacing
+     * any existing value for the given key.  Either key or value may be null.
+     *
+     * @param key a String, or null
+     * @param value a float array object, or null
+     */
+    public void putFloatArray(String key, float[] value) {
+        unparcel();
+        mMap.put(key, value);
+    }
+
+    /**
+     * Inserts a double array value into the mapping of this Bundle, replacing
+     * any existing value for the given key.  Either key or value may be null.
+     *
+     * @param key a String, or null
+     * @param value a double array object, or null
+     */
+    public void putDoubleArray(String key, double[] value) {
+        unparcel();
+        mMap.put(key, value);
+    }
+
+    /**
+     * Inserts a String array value into the mapping of this Bundle, replacing
+     * any existing value for the given key.  Either key or value may be null.
+     *
+     * @param key a String, or null
+     * @param value a String array object, or null
+     */
+    public void putStringArray(String key, String[] value) {
+        unparcel();
+        mMap.put(key, value);
+    }
+
+    /**
+     * Inserts a Bundle value into the mapping of this Bundle, replacing
+     * any existing value for the given key.  Either key or value may be null.
+     *
+     * @param key a String, or null
+     * @param value a Bundle object, or null
+     */
+    public void putBundle(String key, Bundle value) {
+        unparcel();
+        mMap.put(key, value);
+    }
+
+    /**
+     * Inserts an IBinder value into the mapping of this Bundle, replacing
+     * any existing value for the given key.  Either key or value may be null.
+     *
+     * @param key a String, or null
+     * @param value an IBinder object, or null
+     *
+     * @deprecated
+     * @hide
+     */
+    @Deprecated
+    public void putIBinder(String key, IBinder value) {
+        unparcel();
+        mMap.put(key, value);
+    }
+
+    /**
+     * Returns the value associated with the given key, or false if
+     * no mapping of the desired type exists for the given key.
+     *
+     * @param key a String
+     * @return a boolean value
+     */
+    public boolean getBoolean(String key) {
+        unparcel();
+        return getBoolean(key, false);
+    }
+
+    // Log a message if the value was non-null but not of the expected type
+    private void typeWarning(String key, Object value, String className,
+        Object defaultValue, ClassCastException e) {
+        StringBuilder sb = new StringBuilder();
+        sb.append("Key ");
+        sb.append(key);
+        sb.append(" expected ");
+        sb.append(className);
+        sb.append(" but value was a ");
+        sb.append(value.getClass().getName());
+        sb.append(".  The default value ");
+        sb.append(defaultValue);
+        sb.append(" was returned.");
+        Log.w(LOG_TAG, sb.toString());
+        Log.w(LOG_TAG, "Attempt to cast generated internal exception:", e);
+    }
+
+    private void typeWarning(String key, Object value, String className,
+        ClassCastException e) {
+        typeWarning(key, value, className, "<null>", e);
+    }
+
+    /**
+     * Returns the value associated with the given key, or defaultValue if
+     * no mapping of the desired type exists for the given key.
+     *
+     * @param key a String
+     * @return a boolean value
+     */
+    public boolean getBoolean(String key, boolean defaultValue) {
+        unparcel();
+        Object o = mMap.get(key);
+        if (o == null) {
+            return defaultValue;
+        }
+        try {
+            return (Boolean) o;
+        } catch (ClassCastException e) {
+            typeWarning(key, o, "Boolean", defaultValue, e);
+            return defaultValue;
+        }
+    }
+
+    /**
+     * Returns the value associated with the given key, or (byte) 0 if
+     * no mapping of the desired type exists for the given key.
+     *
+     * @param key a String
+     * @return a byte value
+     */
+    public byte getByte(String key) {
+        unparcel();
+        return getByte(key, (byte) 0);
+    }
+
+    /**
+     * Returns the value associated with the given key, or defaultValue if
+     * no mapping of the desired type exists for the given key.
+     *
+     * @param key a String
+     * @return a byte value
+     */
+    public Byte getByte(String key, byte defaultValue) {
+        unparcel();
+        Object o = mMap.get(key);
+        if (o == null) {
+            return defaultValue;
+        }
+        try {
+            return (Byte) o;
+        } catch (ClassCastException e) {
+            typeWarning(key, o, "Byte", defaultValue, e);
+            return defaultValue;
+        }
+    }
+
+    /**
+     * Returns the value associated with the given key, or false if
+     * no mapping of the desired type exists for the given key.
+     *
+     * @param key a String
+     * @return a char value
+     */
+    public char getChar(String key) {
+        unparcel();
+        return getChar(key, (char) 0);
+    }
+
+    /**
+     * Returns the value associated with the given key, or (char) 0 if
+     * no mapping of the desired type exists for the given key.
+     *
+     * @param key a String
+     * @return a char value
+     */
+    public char getChar(String key, char defaultValue) {
+        unparcel();
+        Object o = mMap.get(key);
+        if (o == null) {
+            return defaultValue;
+        }
+        try {
+            return (Character) o;
+        } catch (ClassCastException e) {
+            typeWarning(key, o, "Character", defaultValue, e);
+            return defaultValue;
+        }
+    }
+
+    /**
+     * Returns the value associated with the given key, or (short) 0 if
+     * no mapping of the desired type exists for the given key.
+     *
+     * @param key a String
+     * @return a short value
+     */
+    public short getShort(String key) {
+        unparcel();
+        return getShort(key, (short) 0);
+    }
+
+    /**
+     * Returns the value associated with the given key, or defaultValue if
+     * no mapping of the desired type exists for the given key.
+     *
+     * @param key a String
+     * @return a short value
+     */
+    public short getShort(String key, short defaultValue) {
+        unparcel();
+        Object o = mMap.get(key);
+        if (o == null) {
+            return defaultValue;
+        }
+        try {
+            return (Short) o;
+        } catch (ClassCastException e) {
+            typeWarning(key, o, "Short", defaultValue, e);
+            return defaultValue;
+        }
+    }
+
+    /**
+     * Returns the value associated with the given key, or 0 if
+     * no mapping of the desired type exists for the given key.
+     *
+     * @param key a String
+     * @return an int value
+     */
+    public int getInt(String key) {
+        unparcel();
+        return getInt(key, 0);
+    }
+
+    /**
+     * Returns the value associated with the given key, or defaultValue if
+     * no mapping of the desired type exists for the given key.
+     *
+     * @param key a String
+     * @return an int value
+     */
+    public int getInt(String key, int defaultValue) {
+        unparcel();
+        Object o = mMap.get(key);
+        if (o == null) {
+            return defaultValue;
+        }
+        try {
+            return (Integer) o;
+        } catch (ClassCastException e) {
+            typeWarning(key, o, "Integer", defaultValue, e);
+            return defaultValue;
+        }
+    }
+
+    /**
+     * Returns the value associated with the given key, or 0L if
+     * no mapping of the desired type exists for the given key.
+     *
+     * @param key a String
+     * @return a long value
+     */
+    public long getLong(String key) {
+        unparcel();
+        return getLong(key, 0L);
+    }
+
+    /**
+     * Returns the value associated with the given key, or defaultValue if
+     * no mapping of the desired type exists for the given key.
+     *
+     * @param key a String
+     * @return a long value
+     */
+    public long getLong(String key, long defaultValue) {
+        unparcel();
+        Object o = mMap.get(key);
+        if (o == null) {
+            return defaultValue;
+        }
+        try {
+            return (Long) o;
+        } catch (ClassCastException e) {
+            typeWarning(key, o, "Long", defaultValue, e);
+            return defaultValue;
+        }
+    }
+
+    /**
+     * Returns the value associated with the given key, or 0.0f if
+     * no mapping of the desired type exists for the given key.
+     *
+     * @param key a String
+     * @return a float value
+     */
+    public float getFloat(String key) {
+        unparcel();
+        return getFloat(key, 0.0f);
+    }
+
+    /**
+     * Returns the value associated with the given key, or defaultValue if
+     * no mapping of the desired type exists for the given key.
+     *
+     * @param key a String
+     * @return a float value
+     */
+    public float getFloat(String key, float defaultValue) {
+        unparcel();
+        Object o = mMap.get(key);
+        if (o == null) {
+            return defaultValue;
+        }
+        try {
+            return (Float) o;
+        } catch (ClassCastException e) {
+            typeWarning(key, o, "Float", defaultValue, e);
+            return defaultValue;
+        }
+    }
+
+    /**
+     * Returns the value associated with the given key, or 0.0 if
+     * no mapping of the desired type exists for the given key.
+     *
+     * @param key a String
+     * @return a double value
+     */
+    public double getDouble(String key) {
+        unparcel();
+        return getDouble(key, 0.0);
+    }
+
+    /**
+     * Returns the value associated with the given key, or defaultValue if
+     * no mapping of the desired type exists for the given key.
+     *
+     * @param key a String
+     * @return a double value
+     */
+    public double getDouble(String key, double defaultValue) {
+        unparcel();
+        Object o = mMap.get(key);
+        if (o == null) {
+            return defaultValue;
+        }
+        try {
+            return (Double) o;
+        } catch (ClassCastException e) {
+            typeWarning(key, o, "Double", defaultValue, e);
+            return defaultValue;
+        }
+    }
+
+
+    /**
+     * Returns the value associated with the given key, or null if
+     * no mapping of the desired type exists for the given key or a null
+     * value is explicitly associated with the key.
+     *
+     * @param key a String, or null
+     * @return a String value, or null
+     */
+    public String getString(String key) {
+        unparcel();
+        Object o = mMap.get(key);
+        if (o == null) {
+            return null;
+        }
+        try {
+            return (String) o;
+        } catch (ClassCastException e) {
+            typeWarning(key, o, "String", e);
+            return null;
+        }
+    }
+
+    /**
+     * Returns the value associated with the given key, or null if
+     * no mapping of the desired type exists for the given key or a null
+     * value is explicitly associated with the key.
+     *
+     * @param key a String, or null
+     * @return a CharSequence value, or null
+     */
+    public CharSequence getCharSequence(String key) {
+        unparcel();
+        Object o = mMap.get(key);
+        if (o == null) {
+            return null;
+        }
+        try {
+            return (CharSequence) o;
+        } catch (ClassCastException e) {
+            typeWarning(key, o, "CharSequence", e);
+            return null;
+        }
+    }
+
+    /**
+     * Returns the value associated with the given key, or null if
+     * no mapping of the desired type exists for the given key or a null
+     * value is explicitly associated with the key.
+     *
+     * @param key a String, or null
+     * @return a Bundle value, or null
+     */
+    public Bundle getBundle(String key) {
+        unparcel();
+        Object o = mMap.get(key);
+        if (o == null) {
+            return null;
+        }
+        try {
+            return (Bundle) o;
+        } catch (ClassCastException e) {
+            typeWarning(key, o, "Bundle", e);
+            return null;
+        }
+    }
+
+    /**
+     * Returns the value associated with the given key, or null if
+     * no mapping of the desired type exists for the given key or a null
+     * value is explicitly associated with the key.
+     *
+     * @param key a String, or null
+     * @return a Parcelable value, or null
+     */
+    public <T extends Parcelable> T getParcelable(String key) {
+        unparcel();
+        Object o = mMap.get(key);
+        if (o == null) {
+            return null;
+        }
+        try {
+            return (T) o;
+        } catch (ClassCastException e) {
+            typeWarning(key, o, "Parcelable", e);
+            return null;
+        }
+    }
+
+    /**
+     * Returns the value associated with the given key, or null if
+     * no mapping of the desired type exists for the given key or a null
+     * value is explicitly associated with the key.
+     *
+     * @param key a String, or null
+     * @return a Parcelable[] value, or null
+     */
+    public Parcelable[] getParcelableArray(String key) {
+        unparcel();
+        Object o = mMap.get(key);
+        if (o == null) {
+            return null;
+        }
+        try {
+            return (Parcelable[]) o;
+        } catch (ClassCastException e) {
+            typeWarning(key, o, "Parcelable[]", e);
+            return null;
+        }
+    }
+
+    /**
+     * Returns the value associated with the given key, or null if
+     * no mapping of the desired type exists for the given key or a null
+     * value is explicitly associated with the key.
+     *
+     * @param key a String, or null
+     * @return an ArrayList<T> value, or null
+     */
+    public <T extends Parcelable> ArrayList<T> getParcelableArrayList(String key) {
+        unparcel();
+        Object o = mMap.get(key);
+        if (o == null) {
+            return null;
+        }
+        try {
+            return (ArrayList<T>) o;
+        } catch (ClassCastException e) {
+            typeWarning(key, o, "ArrayList", e);
+            return null;
+        }
+    }
+
+    /**
+     * Returns the value associated with the given key, or null if
+     * no mapping of the desired type exists for the given key or a null
+     * value is explicitly associated with the key.
+     *
+     * @param key a String, or null
+     *
+     * @return a SparseArray of T values, or null
+     */
+    public <T extends Parcelable> SparseArray<T> getSparseParcelableArray(String key) {
+        unparcel();
+        Object o = mMap.get(key);
+        if (o == null) {
+            return null;
+        }
+        try {
+            return (SparseArray<T>) o;
+        } catch (ClassCastException e) {
+            typeWarning(key, o, "SparseArray", e);
+            return null;
+        }
+    }
+
+    /**
+     * Returns the value associated with the given key, or null if
+     * no mapping of the desired type exists for the given key or a null
+     * value is explicitly associated with the key.
+     *
+     * @param key a String, or null
+     * @return a Serializable value, or null
+     */
+    public Serializable getSerializable(String key) {
+        unparcel();
+        Object o = mMap.get(key);
+        if (o == null) {
+            return null;
+        }
+        try {
+            return (Serializable) o;
+        } catch (ClassCastException e) {
+            typeWarning(key, o, "Serializable", e);
+            return null;
+        }
+    }
+
+    /**
+     * Returns the value associated with the given key, or null if
+     * no mapping of the desired type exists for the given key or a null
+     * value is explicitly associated with the key.
+     *
+     * @param key a String, or null
+     * @return an ArrayList<String> value, or null
+     */
+    public ArrayList<Integer> getIntegerArrayList(String key) {
+        unparcel();
+        Object o = mMap.get(key);
+        if (o == null) {
+            return null;
+        }
+        try {
+            return (ArrayList<Integer>) o;
+        } catch (ClassCastException e) {
+            typeWarning(key, o, "ArrayList<Integer>", e);
+            return null;
+        }
+    }
+
+    /**
+     * Returns the value associated with the given key, or null if
+     * no mapping of the desired type exists for the given key or a null
+     * value is explicitly associated with the key.
+     *
+     * @param key a String, or null
+     * @return an ArrayList<String> value, or null
+     */
+    public ArrayList<String> getStringArrayList(String key) {
+        unparcel();
+        Object o = mMap.get(key);
+        if (o == null) {
+            return null;
+        }
+        try {
+            return (ArrayList<String>) o;
+        } catch (ClassCastException e) {
+            typeWarning(key, o, "ArrayList<String>", e);
+            return null;
+        }
+    }
+
+    /**
+     * Returns the value associated with the given key, or null if
+     * no mapping of the desired type exists for the given key or a null
+     * value is explicitly associated with the key.
+     *
+     * @param key a String, or null
+     * @return a boolean[] value, or null
+     */
+    public boolean[] getBooleanArray(String key) {
+        unparcel();
+        Object o = mMap.get(key);
+        if (o == null) {
+            return null;
+        }
+        try {
+            return (boolean[]) o;
+        } catch (ClassCastException e) {
+            typeWarning(key, o, "byte[]", e);
+            return null;
+        }
+    }
+
+    /**
+     * Returns the value associated with the given key, or null if
+     * no mapping of the desired type exists for the given key or a null
+     * value is explicitly associated with the key.
+     *
+     * @param key a String, or null
+     * @return a byte[] value, or null
+     */
+    public byte[] getByteArray(String key) {
+        unparcel();
+        Object o = mMap.get(key);
+        if (o == null) {
+            return null;
+        }
+        try {
+            return (byte[]) o;
+        } catch (ClassCastException e) {
+            typeWarning(key, o, "byte[]", e);
+            return null;
+        }
+    }
+
+    /**
+     * Returns the value associated with the given key, or null if
+     * no mapping of the desired type exists for the given key or a null
+     * value is explicitly associated with the key.
+     *
+     * @param key a String, or null
+     * @return a short[] value, or null
+     */
+    public short[] getShortArray(String key) {
+        unparcel();
+        Object o = mMap.get(key);
+        if (o == null) {
+            return null;
+        }
+        try {
+            return (short[]) o;
+        } catch (ClassCastException e) {
+            typeWarning(key, o, "short[]", e);
+            return null;
+        }
+    }
+
+    /**
+     * Returns the value associated with the given key, or null if
+     * no mapping of the desired type exists for the given key or a null
+     * value is explicitly associated with the key.
+     *
+     * @param key a String, or null
+     * @return a char[] value, or null
+     */
+    public char[] getCharArray(String key) {
+        unparcel();
+        Object o = mMap.get(key);
+        if (o == null) {
+            return null;
+        }
+        try {
+            return (char[]) o;
+        } catch (ClassCastException e) {
+            typeWarning(key, o, "char[]", e);
+            return null;
+        }
+    }
+
+    /**
+     * Returns the value associated with the given key, or null if
+     * no mapping of the desired type exists for the given key or a null
+     * value is explicitly associated with the key.
+     *
+     * @param key a String, or null
+     * @return an int[] value, or null
+     */
+    public int[] getIntArray(String key) {
+        unparcel();
+        Object o = mMap.get(key);
+        if (o == null) {
+            return null;
+        }
+        try {
+            return (int[]) o;
+        } catch (ClassCastException e) {
+            typeWarning(key, o, "int[]", e);
+            return null;
+        }
+    }
+
+    /**
+     * Returns the value associated with the given key, or null if
+     * no mapping of the desired type exists for the given key or a null
+     * value is explicitly associated with the key.
+     *
+     * @param key a String, or null
+     * @return a long[] value, or null
+     */
+    public long[] getLongArray(String key) {
+        unparcel();
+        Object o = mMap.get(key);
+        if (o == null) {
+            return null;
+        }
+        try {
+            return (long[]) o;
+        } catch (ClassCastException e) {
+            typeWarning(key, o, "long[]", e);
+            return null;
+        }
+    }
+
+    /**
+     * Returns the value associated with the given key, or null if
+     * no mapping of the desired type exists for the given key or a null
+     * value is explicitly associated with the key.
+     *
+     * @param key a String, or null
+     * @return a float[] value, or null
+     */
+    public float[] getFloatArray(String key) {
+        unparcel();
+        Object o = mMap.get(key);
+        if (o == null) {
+            return null;
+        }
+        try {
+            return (float[]) o;
+        } catch (ClassCastException e) {
+            typeWarning(key, o, "float[]", e);
+            return null;
+        }
+    }
+
+    /**
+     * Returns the value associated with the given key, or null if
+     * no mapping of the desired type exists for the given key or a null
+     * value is explicitly associated with the key.
+     *
+     * @param key a String, or null
+     * @return a double[] value, or null
+     */
+    public double[] getDoubleArray(String key) {
+        unparcel();
+        Object o = mMap.get(key);
+        if (o == null) {
+            return null;
+        }
+        try {
+            return (double[]) o;
+        } catch (ClassCastException e) {
+            typeWarning(key, o, "double[]", e);
+            return null;
+        }
+    }
+
+    /**
+     * Returns the value associated with the given key, or null if
+     * no mapping of the desired type exists for the given key or a null
+     * value is explicitly associated with the key.
+     *
+     * @param key a String, or null
+     * @return a String[] value, or null
+     */
+    public String[] getStringArray(String key) {
+        unparcel();
+        Object o = mMap.get(key);
+        if (o == null) {
+            return null;
+        }
+        try {
+            return (String[]) o;
+        } catch (ClassCastException e) {
+            typeWarning(key, o, "String[]", e);
+            return null;
+        }
+    }
+
+    /**
+     * Returns the value associated with the given key, or null if
+     * no mapping of the desired type exists for the given key or a null
+     * value is explicitly associated with the key.
+     *
+     * @param key a String, or null
+     * @return an IBinder value, or null
+     *
+     * @deprecated
+     * @hide
+     */
+    @Deprecated
+    public IBinder getIBinder(String key) {
+        unparcel();
+        Object o = mMap.get(key);
+        if (o == null) {
+            return null;
+        }
+        try {
+            return (IBinder) o;
+        } catch (ClassCastException e) {
+            typeWarning(key, o, "IBinder", e);
+            return null;
+        }
+    }
+
+    public static final Parcelable.Creator<Bundle> CREATOR =
+        new Parcelable.Creator<Bundle>() {
+        public Bundle createFromParcel(Parcel in) {
+            return in.readBundle();
+        }
+
+        public Bundle[] newArray(int size) {
+            return new Bundle[size];
+        }
+    };
+
+    /**
+     * Report the nature of this Parcelable's contents
+     */
+    public int describeContents() {
+        int mask = 0;
+        if (hasFileDescriptors()) {
+            mask |= Parcelable.CONTENTS_FILE_DESCRIPTOR;
+        }
+        return mask;
+    }
+    
+    /**
+     * Writes the Bundle contents to a Parcel, typically in order for
+     * it to be passed through an IBinder connection.
+     * @param parcel The parcel to copy this bundle to.
+     */
+    public void writeToParcel(Parcel parcel, int flags) {
+        parcel.writeBundle(this);
+    }
+
+    /**
+     * Reads the Parcel contents into this Bundle, typically in order for
+     * it to be passed through an IBinder connection.
+     * @param parcel The parcel to overwrite this bundle from.
+     */
+    public void readFromParcel(Parcel parcel) {
+        mParcelledData = parcel;
+        mHasFds = mParcelledData.hasFileDescriptors();
+        mFdsKnown = true;
+    }
+
+    @Override
+    public synchronized String toString() {
+        if (mParcelledData != null) {
+            return "Bundle[mParcelledData.dataSize=" +
+                    mParcelledData.dataSize() + "]";
+        }
+        return "Bundle[" + mMap.toString() + "]";
+    }
+}
diff --git a/core/java/android/os/ConditionVariable.java b/core/java/android/os/ConditionVariable.java
new file mode 100644
index 0000000..95a9259
--- /dev/null
+++ b/core/java/android/os/ConditionVariable.java
@@ -0,0 +1,141 @@
+/*
+ * 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 android.os;
+
+/**
+ * Class that implements the condition variable locking paradigm.
+ *
+ * <p>
+ * This differs from the built-in java.lang.Object wait() and notify()
+ * in that this class contains the condition to wait on itself.  That means
+ * open(), close() and block() are sticky.  If open() is called before block(),
+ * block() will not block, and instead return immediately.
+ *
+ * <p>
+ * This class uses itself is at the object to wait on, so if you wait()
+ * or notify() on a ConditionVariable, the results are undefined.
+ */
+public class ConditionVariable
+{
+    private volatile boolean mCondition;
+
+    /**
+     * Create the ConditionVariable in the default closed state.
+     */
+    public ConditionVariable()
+    {
+        mCondition = false;
+    }
+
+    /**
+     * Create the ConditionVariable with the given state.
+     * 
+     * <p>
+     * Pass true for opened and false for closed.
+     */
+    public ConditionVariable(boolean state)
+    {
+        mCondition = state;
+    }
+
+    /**
+     * Open the condition, and release all threads that are blocked.
+     *
+     * <p>
+     * Any threads that later approach block() will not block unless close()
+     * is called.
+     */
+    public void open()
+    {
+        synchronized (this) {
+            boolean old = mCondition;
+            mCondition = true;
+            if (!old) {
+                this.notifyAll();
+            }
+        }
+    }
+
+    /**
+     * Reset the condition to the closed state.
+     *
+     * <p>
+     * Any threads that call block() will block until someone calls open.
+     */
+    public void close()
+    {
+        synchronized (this) {
+            mCondition = false;
+        }
+    }
+
+    /**
+     * Block the current thread until the condition is opened.
+     *
+     * <p>
+     * If the condition is already opened, return immediately.
+     */
+    public void block()
+    {
+        synchronized (this) {
+            while (!mCondition) {
+                try {
+                    this.wait();
+                }
+                catch (InterruptedException e) {
+                }
+            }
+        }
+    }
+
+    /**
+     * Block the current thread until the condition is opened or until
+     * timeout milliseconds have passed.
+     *
+     * <p>
+     * If the condition is already opened, return immediately.
+     *
+     * @param timeout the minimum time to wait in milliseconds.
+     *
+     * @return true if the condition was opened, false if the call returns
+     * because of the timeout.
+     */
+    public boolean block(long timeout)
+    {
+        // Object.wait(0) means wait forever, to mimic this, we just
+        // call the other block() method in that case.  It simplifies
+        // this code for the common case.
+        if (timeout != 0) {
+            synchronized (this) {
+                long now = System.currentTimeMillis();
+                long end = now + timeout;
+                while (!mCondition && now < end) {
+                    try {
+                        this.wait(end-now);
+                    }
+                    catch (InterruptedException e) {
+                    }
+                    now = System.currentTimeMillis();
+                }
+                return mCondition;
+            }
+        } else {
+            this.block();
+            return true;
+        }
+    }
+}
diff --git a/core/java/android/os/CountDownTimer.java b/core/java/android/os/CountDownTimer.java
new file mode 100644
index 0000000..0c5c615
--- /dev/null
+++ b/core/java/android/os/CountDownTimer.java
@@ -0,0 +1,138 @@
+/*
+ * 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 android.os;
+
+import android.util.Log;
+
+/**
+ * Schedule a countdown until a time in the future, with
+ * regular notifications on intervals along the way.
+ *
+ * Example of showing a 30 second countdown in a text field:
+ *
+ * <pre class="prettyprint">
+ * new CountdownTimer(30000, 1000) {
+ *
+ *     public void onTick(long millisUntilFinished) {
+ *         mTextField.setText("seconds remaining: " + millisUntilFinished / 1000);
+ *     }
+ *
+ *     public void onFinish() {
+ *         mTextField.setText("done!");
+ *     }
+ *  }.start();
+ * </pre>
+ *
+ * The calls to {@link #onTick(long)} are synchronized to this object so that
+ * one call to {@link #onTick(long)} won't ever occur before the previous
+ * callback is complete.  This is only relevant when the implementation of
+ * {@link #onTick(long)} takes an amount of time to execute that is significant
+ * compared to the countdown interval.
+ */
+public abstract class CountDownTimer {
+
+    /**
+     * Millis since epoch when alarm should stop.
+     */
+    private final long mMillisInFuture;
+
+    /**
+     * The interval in millis that the user receives callbacks
+     */
+    private final long mCountdownInterval;
+
+    private long mStopTimeInFuture;
+
+    /**
+     * @param millisInFuture The number of millis in the future from the call
+     *   to {@link #start()} until the countdown is done and {@link #onFinish()}
+     *   is called.
+     * @param countDownInterval The interval along the way to receive
+     *   {@link #onTick(long)} callbacks.
+     */
+    public CountDownTimer(long millisInFuture, long countDownInterval) {
+        mMillisInFuture = millisInFuture;
+        mCountdownInterval = countDownInterval;
+    }
+
+    /**
+     * Cancel the countdown.
+     */
+    public final void cancel() {
+        mHandler.removeMessages(MSG);
+    }
+
+    /**
+     * Start the countdown.
+     */
+    public synchronized final CountDownTimer start() {
+        if (mMillisInFuture <= 0) {
+            onFinish();
+            return this;
+        }
+        mStopTimeInFuture = SystemClock.elapsedRealtime() + mMillisInFuture;
+        mHandler.sendMessage(mHandler.obtainMessage(MSG));
+        return this;
+    }
+
+
+    /**
+     * Callback fired on regular interval.
+     * @param millisUntilFinished The amount of time until finished.
+     */
+    public abstract void onTick(long millisUntilFinished);
+
+    /**
+     * Callback fired when the time is up.
+     */
+    public abstract void onFinish();
+
+
+    private static final int MSG = 1;
+
+
+    // handles counting down
+    private Handler mHandler = new Handler() {
+
+        @Override
+        public void handleMessage(Message msg) {
+
+            synchronized (CountDownTimer.this) {
+                final long millisLeft = mStopTimeInFuture - SystemClock.elapsedRealtime();
+
+                if (millisLeft <= 0) {
+                    onFinish();
+                } else if (millisLeft < mCountdownInterval) {
+                    // no tick, just delay until done
+                    sendMessageDelayed(obtainMessage(MSG), millisLeft);
+                } else {
+                    long lastTickStart = SystemClock.elapsedRealtime();
+                    onTick(millisLeft);
+
+                    // take into account user's onTick taking time to execute
+                    long delay = lastTickStart + mCountdownInterval - SystemClock.elapsedRealtime();
+
+                    // special case: user's onTick took more than interval to
+                    // complete, skip to next interval
+                    while (delay < 0) delay += mCountdownInterval;
+
+                    sendMessageDelayed(obtainMessage(MSG), delay);
+                }
+            }
+        }
+    };
+}
diff --git a/core/java/android/os/DeadObjectException.java b/core/java/android/os/DeadObjectException.java
new file mode 100644
index 0000000..94c5387
--- /dev/null
+++ b/core/java/android/os/DeadObjectException.java
@@ -0,0 +1,28 @@
+/*
+ * 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 android.os;
+import android.os.RemoteException;
+
+/**
+ * The object you are calling has died, because its hosting process
+ * no longer exists.
+ */
+public class DeadObjectException extends RemoteException {
+    public DeadObjectException() {
+        super();
+    }
+}
diff --git a/core/java/android/os/Debug.java b/core/java/android/os/Debug.java
new file mode 100644
index 0000000..950bb09
--- /dev/null
+++ b/core/java/android/os/Debug.java
@@ -0,0 +1,717 @@
+/*
+ * Copyright (C) 2007 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 android.os;
+
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.io.PrintWriter;
+
+import org.apache.harmony.dalvik.ddmc.Chunk;
+import org.apache.harmony.dalvik.ddmc.ChunkHandler;
+import org.apache.harmony.dalvik.ddmc.DdmServer;
+
+import dalvik.bytecode.Opcodes;
+import dalvik.system.VMDebug;
+
+
+/**
+ * Provides various debugging functions for Android applications, including
+ * tracing and allocation counts.
+ * <p><strong>Logging Trace Files</strong></p>
+ * <p>Debug can create log files that give details about an application, such as
+ * a call stack and start/stop times for any running methods. See <a
+href="{@docRoot}guide/developing/tools/traceview.html">Traceview: A Graphical Log Viewer</a> for
+ * information about reading trace files. To start logging trace files, call one
+ * of the startMethodTracing() methods. To stop tracing, call
+ * {@link #stopMethodTracing()}.
+ */
+public final class Debug
+{
+    /**
+     * Flags for startMethodTracing().  These can be ORed together.
+     *
+     * TRACE_COUNT_ALLOCS adds the results from startAllocCounting to the
+     * trace key file.
+     */
+    public static final int TRACE_COUNT_ALLOCS  = VMDebug.TRACE_COUNT_ALLOCS;
+
+    /**
+     * Flags for printLoadedClasses().  Default behavior is to only show
+     * the class name.
+     */
+    public static final int SHOW_FULL_DETAIL    = 1;
+    public static final int SHOW_CLASSLOADER    = (1 << 1);
+    public static final int SHOW_INITIALIZED    = (1 << 2);
+
+    // set/cleared by waitForDebugger()
+    private static volatile boolean mWaiting = false;
+
+    private Debug() {}
+
+    /*
+     * How long to wait for the debugger to finish sending requests.  I've
+     * seen this hit 800msec on the device while waiting for a response
+     * to travel over USB and get processed, so we take that and add
+     * half a second.
+     */
+    private static final int MIN_DEBUGGER_IDLE = 1300;      // msec
+
+    /* how long to sleep when polling for activity */
+    private static final int SPIN_DELAY = 200;              // msec
+
+    /**
+     * Default trace file path and file
+     */
+    private static final String DEFAULT_TRACE_PATH_PREFIX = "/sdcard/";
+    private static final String DEFAULT_TRACE_BODY = "dmtrace";
+    private static final String DEFAULT_TRACE_EXTENSION = ".trace";
+    private static final String DEFAULT_TRACE_FILE_PATH =
+        DEFAULT_TRACE_PATH_PREFIX + DEFAULT_TRACE_BODY
+        + DEFAULT_TRACE_EXTENSION;
+
+
+    /**
+     * This class is used to retrieved various statistics about the memory mappings for this
+     * process. The returns info broken down by dalvik, native, and other. All results are in kB.
+     */
+    public static class MemoryInfo {
+        /** The proportional set size for dalvik. */
+        public int dalvikPss;
+        /** The private dirty pages used by dalvik. */
+        public int dalvikPrivateDirty;
+        /** The shared dirty pages used by dalvik. */
+        public int dalvikSharedDirty;
+
+        /** The proportional set size for the native heap. */
+        public int nativePss;
+        /** The private dirty pages used by the native heap. */
+        public int nativePrivateDirty;
+        /** The shared dirty pages used by the native heap. */
+        public int nativeSharedDirty;
+
+        /** The proportional set size for everything else. */
+        public int otherPss;
+        /** The private dirty pages used by everything else. */
+        public int otherPrivateDirty;
+        /** The shared dirty pages used by everything else. */
+        public int otherSharedDirty;
+    }
+
+
+    /**
+     * Wait until a debugger attaches.  As soon as the debugger attaches,
+     * this returns, so you will need to place a breakpoint after the
+     * waitForDebugger() call if you want to start tracing immediately.
+     */
+    public static void waitForDebugger() {
+        if (!VMDebug.isDebuggingEnabled()) {
+            //System.out.println("debugging not enabled, not waiting");
+            return;
+        }
+        if (isDebuggerConnected())
+            return;
+
+        // if DDMS is listening, inform them of our plight
+        System.out.println("Sending WAIT chunk");
+        byte[] data = new byte[] { 0 };     // 0 == "waiting for debugger"
+        Chunk waitChunk = new Chunk(ChunkHandler.type("WAIT"), data, 0, 1);
+        DdmServer.sendChunk(waitChunk);
+
+        mWaiting = true;
+        while (!isDebuggerConnected()) {
+            try { Thread.sleep(SPIN_DELAY); }
+            catch (InterruptedException ie) {}
+        }
+        mWaiting = false;
+
+        System.out.println("Debugger has connected");
+
+        /*
+         * There is no "ready to go" signal from the debugger, and we're
+         * not allowed to suspend ourselves -- the debugger expects us to
+         * be running happily, and gets confused if we aren't.  We need to
+         * allow the debugger a chance to set breakpoints before we start
+         * running again.
+         *
+         * Sit and spin until the debugger has been idle for a short while.
+         */
+        while (true) {
+            long delta = VMDebug.lastDebuggerActivity();
+            if (delta < 0) {
+                System.out.println("debugger detached?");
+                break;
+            }
+
+            if (delta < MIN_DEBUGGER_IDLE) {
+                System.out.println("waiting for debugger to settle...");
+                try { Thread.sleep(SPIN_DELAY); }
+                catch (InterruptedException ie) {}
+            } else {
+                System.out.println("debugger has settled (" + delta + ")");
+                break;
+            }
+        }
+    }
+
+    /**
+     * Returns "true" if one or more threads is waiting for a debugger
+     * to attach.
+     */
+    public static boolean waitingForDebugger() {
+        return mWaiting;
+    }
+
+    /**
+     * Determine if a debugger is currently attached.
+     */
+    public static boolean isDebuggerConnected() {
+        return VMDebug.isDebuggerConnected();
+    }
+
+    /**
+     * Change the JDWP port.
+     *
+     * @deprecated no longer needed or useful
+     */
+    @Deprecated
+    public static void changeDebugPort(int port) {}
+
+    /**
+     * This is the pathname to the sysfs file that enables and disables
+     * tracing on the qemu emulator.
+     */
+    private static final String SYSFS_QEMU_TRACE_STATE = "/sys/qemu_trace/state";
+
+    /**
+     * Enable qemu tracing. For this to work requires running everything inside
+     * the qemu emulator; otherwise, this method will have no effect. The trace
+     * file is specified on the command line when the emulator is started. For
+     * example, the following command line <br />
+     * <code>emulator -trace foo</code><br />
+     * will start running the emulator and create a trace file named "foo". This
+     * method simply enables writing the trace records to the trace file.
+     *
+     * <p>
+     * The main differences between this and {@link #startMethodTracing()} are
+     * that tracing in the qemu emulator traces every cpu instruction of every
+     * process, including kernel code, so we have more complete information,
+     * including all context switches. We can also get more detailed information
+     * such as cache misses. The sequence of calls is determined by
+     * post-processing the instruction trace. The qemu tracing is also done
+     * without modifying the application or perturbing the timing of calls
+     * because no instrumentation is added to the application being traced.
+     * </p>
+     *
+     * <p>
+     * One limitation of using this method compared to using
+     * {@link #startMethodTracing()} on the real device is that the emulator
+     * does not model all of the real hardware effects such as memory and
+     * bus contention.  The emulator also has a simple cache model and cannot
+     * capture all the complexities of a real cache.
+     * </p>
+     */
+    public static void startNativeTracing() {
+        // Open the sysfs file for writing and write "1" to it.
+        PrintWriter outStream = null;
+        try {
+            FileOutputStream fos = new FileOutputStream(SYSFS_QEMU_TRACE_STATE);
+            outStream = new PrintWriter(new OutputStreamWriter(fos));
+            outStream.println("1");
+        } catch (Exception e) {
+        } finally {
+            if (outStream != null)
+                outStream.close();
+        }
+
+        VMDebug.startEmulatorTracing();
+    }
+
+    /**
+     * Stop qemu tracing.  See {@link #startNativeTracing()} to start tracing.
+     *
+     * <p>Tracing can be started and stopped as many times as desired.  When
+     * the qemu emulator itself is stopped then the buffered trace records
+     * are flushed and written to the trace file.  In fact, it is not necessary
+     * to call this method at all; simply killing qemu is sufficient.  But
+     * starting and stopping a trace is useful for examining a specific
+     * region of code.</p>
+     */
+    public static void stopNativeTracing() {
+        VMDebug.stopEmulatorTracing();
+
+        // Open the sysfs file for writing and write "0" to it.
+        PrintWriter outStream = null;
+        try {
+            FileOutputStream fos = new FileOutputStream(SYSFS_QEMU_TRACE_STATE);
+            outStream = new PrintWriter(new OutputStreamWriter(fos));
+            outStream.println("0");
+        } catch (Exception e) {
+            // We could print an error message here but we probably want
+            // to quietly ignore errors if we are not running in the emulator.
+        } finally {
+            if (outStream != null)
+                outStream.close();
+        }
+    }
+
+    /**
+     * Enable "emulator traces", in which information about the current
+     * method is made available to the "emulator -trace" feature.  There
+     * is no corresponding "disable" call -- this is intended for use by
+     * the framework when tracing should be turned on and left that way, so
+     * that traces captured with F9/F10 will include the necessary data.
+     *
+     * This puts the VM into "profile" mode, which has performance
+     * consequences.
+     *
+     * To temporarily enable tracing, use {@link #startNativeTracing()}.
+     */
+    public static void enableEmulatorTraceOutput() {
+        VMDebug.startEmulatorTracing();
+    }
+
+    /**
+     * Start method tracing with default log name and buffer size. See <a
+href="{@docRoot}guide/developing/tools/traceview.html">Traceview: A Graphical Log Viewer</a> for
+     * information about reading these files. Call stopMethodTracing() to stop
+     * tracing.
+     */
+    public static void startMethodTracing() {
+        VMDebug.startMethodTracing(DEFAULT_TRACE_FILE_PATH, 0, 0);
+    }
+
+    /**
+     * Start method tracing, specifying the trace log file name.  The trace
+     * file will be put under "/sdcard" unless an absolute path is given.
+     * See <a
+       href="{@docRoot}guide/developing/tools/traceview.html">Traceview: A Graphical Log Viewer</a> for
+     * information about reading trace files.
+     *
+     * @param traceName Name for the trace log file to create.
+     * If no name argument is given, this value defaults to "/sdcard/dmtrace.trace".
+     * If the files already exist, they will be truncated.
+     * If the trace file given does not end in ".trace", it will be appended for you.
+     */
+    public static void startMethodTracing(String traceName) {
+        startMethodTracing(traceName, 0, 0);
+    }
+
+    /**
+     * Start method tracing, specifying the trace log file name and the
+     * buffer size. The trace files will be put under "/sdcard" unless an
+     * absolute path is given. See <a
+       href="{@docRoot}guide/developing/tools/traceview.html">Traceview: A Graphical Log Viewer</a> for
+     * information about reading trace files.
+     * @param traceName    Name for the trace log file to create.
+     * If no name argument is given, this value defaults to "/sdcard/dmtrace.trace".
+     * If the files already exist, they will be truncated.
+     * If the trace file given does not end in ".trace", it will be appended for you.
+     *
+     * @param bufferSize    The maximum amount of trace data we gather. If not given, it defaults to 8MB.
+     */
+    public static void startMethodTracing(String traceName, int bufferSize) {
+        startMethodTracing(traceName, bufferSize, 0);
+    }
+
+    /**
+     * Start method tracing, specifying the trace log file name and the
+     * buffer size. The trace files will be put under "/sdcard" unless an
+     * absolute path is given. See <a
+       href="{@docRoot}guide/developing/tools/traceview.html">Traceview: A Graphical Log Viewer</a> for
+     * information about reading trace files.
+     *
+     * <p>
+     * When method tracing is enabled, the VM will run more slowly than
+     * usual, so the timings from the trace files should only be considered
+     * in relative terms (e.g. was run #1 faster than run #2).  The times
+     * for native methods will not change, so don't try to use this to
+     * compare the performance of interpreted and native implementations of the
+     * same method.  As an alternative, consider using "native" tracing
+     * in the emulator via {@link #startNativeTracing()}.
+     * </p>
+     *
+     * @param traceName    Name for the trace log file to create.
+     * If no name argument is given, this value defaults to "/sdcard/dmtrace.trace".
+     * If the files already exist, they will be truncated.
+     * If the trace file given does not end in ".trace", it will be appended for you.
+     * @param bufferSize    The maximum amount of trace data we gather. If not given, it defaults to 8MB.
+     */
+    public static void startMethodTracing(String traceName, int bufferSize,
+        int flags) {
+
+        String pathName = traceName;
+        if (pathName.charAt(0) != '/')
+            pathName = DEFAULT_TRACE_PATH_PREFIX + pathName;
+        if (!pathName.endsWith(DEFAULT_TRACE_EXTENSION))
+            pathName = pathName + DEFAULT_TRACE_EXTENSION;
+
+        VMDebug.startMethodTracing(pathName, bufferSize, flags);
+    }
+
+    /**
+     * Stop method tracing.
+     */
+    public static void stopMethodTracing() {
+        VMDebug.stopMethodTracing();
+    }
+
+    /**
+     * Get an indication of thread CPU usage.  The value returned
+     * indicates the amount of time that the current thread has spent
+     * executing code or waiting for certain types of I/O.
+     *
+     * The time is expressed in nanoseconds, and is only meaningful
+     * when compared to the result from an earlier call.  Note that
+     * nanosecond resolution does not imply nanosecond accuracy.
+     *
+     * On system which don't support this operation, the call returns -1.
+     */
+    public static long threadCpuTimeNanos() {
+        return VMDebug.threadCpuTimeNanos();
+    }
+
+    /**
+     * Count the number and aggregate size of memory allocations between
+     * two points.
+     *
+     * The "start" function resets the counts and enables counting.  The
+     * "stop" function disables the counting so that the analysis code
+     * doesn't cause additional allocations.  The "get" function returns
+     * the specified value.
+     *
+     * Counts are kept for the system as a whole and for each thread.
+     * The per-thread counts for threads other than the current thread
+     * are not cleared by the "reset" or "start" calls.
+     */
+    public static void startAllocCounting() {
+        VMDebug.startAllocCounting();
+    }
+    public static void stopAllocCounting() {
+        VMDebug.stopAllocCounting();
+    }
+
+    public static int getGlobalAllocCount() {
+        return VMDebug.getAllocCount(VMDebug.KIND_GLOBAL_ALLOCATED_OBJECTS);
+    }
+    public static int getGlobalAllocSize() {
+        return VMDebug.getAllocCount(VMDebug.KIND_GLOBAL_ALLOCATED_BYTES);
+    }
+    public static int getGlobalFreedCount() {
+        return VMDebug.getAllocCount(VMDebug.KIND_GLOBAL_FREED_OBJECTS);
+    }
+    public static int getGlobalFreedSize() {
+        return VMDebug.getAllocCount(VMDebug.KIND_GLOBAL_FREED_BYTES);
+    }
+    public static int getGlobalExternalAllocCount() {
+        return VMDebug.getAllocCount(VMDebug.KIND_GLOBAL_EXT_ALLOCATED_OBJECTS);
+    }
+    public static int getGlobalExternalAllocSize() {
+        return VMDebug.getAllocCount(VMDebug.KIND_GLOBAL_EXT_ALLOCATED_BYTES);
+    }
+    public static int getGlobalExternalFreedCount() {
+        return VMDebug.getAllocCount(VMDebug.KIND_GLOBAL_EXT_FREED_OBJECTS);
+    }
+    public static int getGlobalExternalFreedSize() {
+        return VMDebug.getAllocCount(VMDebug.KIND_GLOBAL_EXT_FREED_BYTES);
+    }
+    public static int getGlobalGcInvocationCount() {
+        return VMDebug.getAllocCount(VMDebug.KIND_GLOBAL_GC_INVOCATIONS);
+    }
+    public static int getThreadAllocCount() {
+        return VMDebug.getAllocCount(VMDebug.KIND_THREAD_ALLOCATED_OBJECTS);
+    }
+    public static int getThreadAllocSize() {
+        return VMDebug.getAllocCount(VMDebug.KIND_THREAD_ALLOCATED_BYTES);
+    }
+    public static int getThreadExternalAllocCount() {
+        return VMDebug.getAllocCount(VMDebug.KIND_THREAD_EXT_ALLOCATED_OBJECTS);
+    }
+    public static int getThreadExternalAllocSize() {
+        return VMDebug.getAllocCount(VMDebug.KIND_THREAD_EXT_ALLOCATED_BYTES);
+    }
+    public static int getThreadGcInvocationCount() {
+        return VMDebug.getAllocCount(VMDebug.KIND_THREAD_GC_INVOCATIONS);
+    }
+
+    public static void resetGlobalAllocCount() {
+        VMDebug.resetAllocCount(VMDebug.KIND_GLOBAL_ALLOCATED_OBJECTS);
+    }
+    public static void resetGlobalAllocSize() {
+        VMDebug.resetAllocCount(VMDebug.KIND_GLOBAL_ALLOCATED_BYTES);
+    }
+    public static void resetGlobalFreedCount() {
+        VMDebug.resetAllocCount(VMDebug.KIND_GLOBAL_FREED_OBJECTS);
+    }
+    public static void resetGlobalFreedSize() {
+        VMDebug.resetAllocCount(VMDebug.KIND_GLOBAL_FREED_BYTES);
+    }
+    public static void resetGlobalExternalAllocCount() {
+        VMDebug.resetAllocCount(VMDebug.KIND_GLOBAL_EXT_ALLOCATED_OBJECTS);
+    }
+    public static void resetGlobalExternalAllocSize() {
+        VMDebug.resetAllocCount(VMDebug.KIND_GLOBAL_EXT_ALLOCATED_BYTES);
+    }
+    public static void resetGlobalExternalFreedCount() {
+        VMDebug.resetAllocCount(VMDebug.KIND_GLOBAL_EXT_FREED_OBJECTS);
+    }
+    public static void resetGlobalExternalFreedSize() {
+        VMDebug.resetAllocCount(VMDebug.KIND_GLOBAL_EXT_FREED_BYTES);
+    }
+    public static void resetGlobalGcInvocationCount() {
+        VMDebug.resetAllocCount(VMDebug.KIND_GLOBAL_GC_INVOCATIONS);
+    }
+    public static void resetThreadAllocCount() {
+        VMDebug.resetAllocCount(VMDebug.KIND_THREAD_ALLOCATED_OBJECTS);
+    }
+    public static void resetThreadAllocSize() {
+        VMDebug.resetAllocCount(VMDebug.KIND_THREAD_ALLOCATED_BYTES);
+    }
+    public static void resetThreadExternalAllocCount() {
+        VMDebug.resetAllocCount(VMDebug.KIND_THREAD_EXT_ALLOCATED_OBJECTS);
+    }
+    public static void resetThreadExternalAllocSize() {
+        VMDebug.resetAllocCount(VMDebug.KIND_THREAD_EXT_ALLOCATED_BYTES);
+    }
+    public static void resetThreadGcInvocationCount() {
+        VMDebug.resetAllocCount(VMDebug.KIND_THREAD_GC_INVOCATIONS);
+    }
+    public static void resetAllCounts() {
+        VMDebug.resetAllocCount(VMDebug.KIND_ALL_COUNTS);
+    }
+
+    /**
+     * Returns the size of the native heap.
+     * @return The size of the native heap in bytes.
+     */
+    public static native long getNativeHeapSize();
+
+    /**
+     * Returns the amount of allocated memory in the native heap.
+     * @return The allocated size in bytes.
+     */
+    public static native long getNativeHeapAllocatedSize();
+
+    /**
+     * Returns the amount of free memory in the native heap.
+     * @return The freed size in bytes.
+     */
+    public static native long getNativeHeapFreeSize();
+
+    /**
+     * Retrieves information about this processes memory usages. This information is broken down by
+     * how much is in use by dalivk, the native heap, and everything else.
+     */
+    public static native void getMemoryInfo(MemoryInfo memoryInfo);
+
+    /**
+     * Establish an object allocation limit in the current thread.  Useful
+     * for catching regressions in code that is expected to operate
+     * without causing any allocations.
+     *
+     * Pass in the maximum number of allowed allocations.  Use -1 to disable
+     * the limit.  Returns the previous limit.
+     *
+     * The preferred way to use this is:
+     *
+     *  int prevLimit = -1;
+     *  try {
+     *      prevLimit = Debug.setAllocationLimit(0);
+     *      ... do stuff that's not expected to allocate memory ...
+     *  } finally {
+     *      Debug.setAllocationLimit(prevLimit);
+     *  }
+     *
+     * This allows limits to be nested.  The try/finally ensures that the
+     * limit is reset if something fails.
+     *
+     * Exceeding the limit causes a dalvik.system.AllocationLimitError to
+     * be thrown from a memory allocation call.  The limit is reset to -1
+     * when this happens.
+     *
+     * The feature may be disabled in the VM configuration.  If so, this
+     * call has no effect, and always returns -1.
+     */
+    public static int setAllocationLimit(int limit) {
+        return VMDebug.setAllocationLimit(limit);
+    }
+
+    /**
+     * Establish a global object allocation limit.  This is similar to
+     * {@link #setAllocationLimit(int)} but applies to all threads in
+     * the VM.  It will coexist peacefully with per-thread limits.
+     *
+     * [ The value of "limit" is currently restricted to 0 (no allocations
+     *   allowed) or -1 (no global limit).  This may be changed in a future
+     *   release. ]
+     */
+    public static int setGlobalAllocationLimit(int limit) {
+        if (limit != 0 && limit != -1)
+            throw new IllegalArgumentException("limit must be 0 or -1");
+        return VMDebug.setGlobalAllocationLimit(limit);
+    }
+
+    /**
+     * Dump a list of all currently loaded class to the log file.
+     *
+     * @param flags See constants above.
+     */
+    public static void printLoadedClasses(int flags) {
+        VMDebug.printLoadedClasses(flags);
+    }
+
+    /**
+     * Get the number of loaded classes.
+     * @return the number of loaded classes.
+     */
+    public static int getLoadedClassCount() {
+        return VMDebug.getLoadedClassCount();
+    }
+
+    /**
+     * Dump "hprof" data to the specified file.  This will cause a GC.
+     *
+     * @param fileName Full pathname of output file (e.g. "/sdcard/dump.hprof").
+     * @throws UnsupportedOperationException if the VM was built without
+     *         HPROF support.
+     * @throws IOException if an error occurs while opening or writing files.
+     */
+    public static void dumpHprofData(String fileName) throws IOException {
+        VMDebug.dumpHprofData(fileName);
+    }
+
+    /**
+     * Returns the number of sent transactions from this process.
+     * @return The number of sent transactions or -1 if it could not read t.
+     */
+    public static native int getBinderSentTransactions();
+
+    /**
+     * Returns the number of received transactions from the binder driver.
+     * @return The number of received transactions or -1 if it could not read the stats.
+     */
+    public static native int getBinderReceivedTransactions();
+
+    /**
+     * Returns the number of active local Binder objects that exist in the
+     * current process.
+     */
+    public static final native int getBinderLocalObjectCount();
+
+    /**
+     * Returns the number of references to remote proxy Binder objects that
+     * exist in the current process.
+     */
+    public static final native int getBinderProxyObjectCount();
+
+    /**
+     * Returns the number of death notification links to Binder objects that
+     * exist in the current process.
+     */
+    public static final native int getBinderDeathObjectCount();
+
+    /**
+     * API for gathering and querying instruction counts.
+     *
+     * Example usage:
+     *   Debug.InstructionCount icount = new Debug.InstructionCount();
+     *   icount.resetAndStart();
+     *    [... do lots of stuff ...]
+     *   if (icount.collect()) {
+     *       System.out.println("Total instructions executed: "
+     *           + icount.globalTotal());
+     *       System.out.println("Method invocations: "
+     *           + icount.globalMethodInvocations());
+     *   }
+     */
+    public static class InstructionCount {
+        private static final int NUM_INSTR = 256;
+
+        private int[] mCounts;
+
+        public InstructionCount() {
+            mCounts = new int[NUM_INSTR];
+        }
+
+        /**
+         * Reset counters and ensure counts are running.  Counts may
+         * have already been running.
+         *
+         * @return true if counting was started
+         */
+        public boolean resetAndStart() {
+            try {
+                VMDebug.startInstructionCounting();
+                VMDebug.resetInstructionCount();
+            } catch (UnsupportedOperationException uoe) {
+                return false;
+            }
+            return true;
+        }
+
+        /**
+         * Collect instruction counts.  May or may not stop the
+         * counting process.
+         */
+        public boolean collect() {
+            try {
+                VMDebug.stopInstructionCounting();
+                VMDebug.getInstructionCount(mCounts);
+            } catch (UnsupportedOperationException uoe) {
+                return false;
+            }
+            return true;
+        }
+
+        /**
+         * Return the total number of instructions executed globally (i.e. in
+         * all threads).
+         */
+        public int globalTotal() {
+            int count = 0;
+            for (int i = 0; i < NUM_INSTR; i++)
+                count += mCounts[i];
+            return count;
+        }
+
+        /**
+         * Return the total number of method-invocation instructions
+         * executed globally.
+         */
+        public int globalMethodInvocations() {
+            int count = 0;
+
+            //count += mCounts[Opcodes.OP_EXECUTE_INLINE];
+            count += mCounts[Opcodes.OP_INVOKE_VIRTUAL];
+            count += mCounts[Opcodes.OP_INVOKE_SUPER];
+            count += mCounts[Opcodes.OP_INVOKE_DIRECT];
+            count += mCounts[Opcodes.OP_INVOKE_STATIC];
+            count += mCounts[Opcodes.OP_INVOKE_INTERFACE];
+            count += mCounts[Opcodes.OP_INVOKE_VIRTUAL_RANGE];
+            count += mCounts[Opcodes.OP_INVOKE_SUPER_RANGE];
+            count += mCounts[Opcodes.OP_INVOKE_DIRECT_RANGE];
+            count += mCounts[Opcodes.OP_INVOKE_STATIC_RANGE];
+            count += mCounts[Opcodes.OP_INVOKE_INTERFACE_RANGE];
+            //count += mCounts[Opcodes.OP_INVOKE_DIRECT_EMPTY];
+            count += mCounts[Opcodes.OP_INVOKE_VIRTUAL_QUICK];
+            count += mCounts[Opcodes.OP_INVOKE_VIRTUAL_QUICK_RANGE];
+            count += mCounts[Opcodes.OP_INVOKE_SUPER_QUICK];
+            count += mCounts[Opcodes.OP_INVOKE_SUPER_QUICK_RANGE];
+            return count;
+        }
+    };
+}
diff --git a/core/java/android/os/Environment.java b/core/java/android/os/Environment.java
new file mode 100644
index 0000000..f761e8e
--- /dev/null
+++ b/core/java/android/os/Environment.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright (C) 2007 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 android.os;
+
+import java.io.File;
+
+/**
+ * Provides access to environment variables.
+ */
+public class Environment {
+
+    private static final File ROOT_DIRECTORY
+            = getDirectory("ANDROID_ROOT", "/system");
+
+    /**
+     * Gets the Android root directory.
+     */
+    public static File getRootDirectory() {
+        return ROOT_DIRECTORY;
+    }
+
+    private static final File DATA_DIRECTORY
+            = getDirectory("ANDROID_DATA", "/data");
+
+    private static final File EXTERNAL_STORAGE_DIRECTORY
+            = getDirectory("EXTERNAL_STORAGE", "/sdcard");
+
+    private static final File DOWNLOAD_CACHE_DIRECTORY
+            = getDirectory("DOWNLOAD_CACHE", "/cache");
+
+    /**
+     * Gets the Android data directory.
+     */
+    public static File getDataDirectory() {
+        return DATA_DIRECTORY;
+    }
+
+    /**
+     * Gets the Android external storage directory.
+     */
+    public static File getExternalStorageDirectory() {
+        return EXTERNAL_STORAGE_DIRECTORY;
+    }
+
+    /**
+     * Gets the Android Download/Cache content directory.
+     */
+    public static File getDownloadCacheDirectory() {
+        return DOWNLOAD_CACHE_DIRECTORY;
+    }
+
+    /**
+     * getExternalStorageState() returns MEDIA_REMOVED if the media is not present. 
+     */
+    public static final String MEDIA_REMOVED = "removed";
+     
+    /**
+     * getExternalStorageState() returns MEDIA_UNMOUNTED if the media is present
+     * but not mounted. 
+     */
+    public static final String MEDIA_UNMOUNTED = "unmounted";
+
+    /**
+     * getExternalStorageState() returns MEDIA_CHECKING if the media is present
+     * and being disk-checked
+     */
+    public static final String MEDIA_CHECKING = "checking";
+
+    /**
+     * getExternalStorageState() returns MEDIA_NOFS if the media is present
+     * but is blank or is using an unsupported filesystem
+     */
+    public static final String MEDIA_NOFS = "nofs";
+
+    /**
+     * getExternalStorageState() returns MEDIA_MOUNTED if the media is present
+     * and mounted at its mount point with read/write access. 
+     */
+    public static final String MEDIA_MOUNTED = "mounted";
+
+    /**
+     * getExternalStorageState() returns MEDIA_MOUNTED_READ_ONLY if the media is present
+     * and mounted at its mount point with read only access. 
+     */
+    public static final String MEDIA_MOUNTED_READ_ONLY = "mounted_ro";
+
+    /**
+     * getExternalStorageState() returns MEDIA_SHARED if the media is present
+     * not mounted, and shared via USB mass storage. 
+     */
+    public static final String MEDIA_SHARED = "shared";
+
+    /**
+     * getExternalStorageState() returns MEDIA_BAD_REMOVAL if the media was
+     * removed before it was unmounted. 
+     */
+    public static final String MEDIA_BAD_REMOVAL = "bad_removal";
+
+    /**
+     * getExternalStorageState() returns MEDIA_UNMOUNTABLE if the media is present
+     * but cannot be mounted.  Typically this happens if the file system on the
+     * media is corrupted. 
+     */
+    public static final String MEDIA_UNMOUNTABLE = "unmountable";
+
+    /**
+     * Gets the current state of the external storage device.
+     */
+    public static String getExternalStorageState() {
+        return SystemProperties.get("EXTERNAL_STORAGE_STATE", MEDIA_REMOVED);
+    }
+
+    static File getDirectory(String variableName, String defaultPath) {
+        String path = System.getenv(variableName);
+        return path == null ? new File(defaultPath) : new File(path);
+    }
+}
diff --git a/core/java/android/os/Exec.java b/core/java/android/os/Exec.java
new file mode 100644
index 0000000..a50d5fe
--- /dev/null
+++ b/core/java/android/os/Exec.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2007 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 android.os;
+
+import java.io.FileDescriptor;
+
+/**
+ * @hide
+ * Tools for executing commands.  Not for public consumption.
+ */
+
+public class Exec
+{
+    /**
+     * @param cmd The command to execute
+     * @param arg0 The first argument to the command, may be null
+     * @param arg1 the second argument to the command, may be null
+     * @return the file descriptor of the started process.
+     * 
+     */
+    public static FileDescriptor createSubprocess(
+        String cmd, String arg0, String arg1) {
+        return createSubprocess(cmd, arg0, arg1, null);
+    }
+    
+    /**
+     * @param cmd The command to execute
+     * @param arg0 The first argument to the command, may be null
+     * @param arg1 the second argument to the command, may be null
+     * @param processId A one-element array to which the process ID of the
+     * started process will be written.
+     * @return the file descriptor of the started process.
+     * 
+     */
+     public static native FileDescriptor createSubprocess(
+        String cmd, String arg0, String arg1, int[] processId);
+    
+     public static native void setPtyWindowSize(FileDescriptor fd,
+       int row, int col, int xpixel, int ypixel);
+    /**
+     * Causes the calling thread to wait for the process associated with the
+     * receiver to finish executing.
+     * 
+     * @return The exit value of the Process being waited on
+     * 
+     */
+    public static native int waitFor(int processId);
+}
+
diff --git a/core/java/android/os/FileObserver.java b/core/java/android/os/FileObserver.java
new file mode 100644
index 0000000..d9804ea
--- /dev/null
+++ b/core/java/android/os/FileObserver.java
@@ -0,0 +1,146 @@
+/*
+ * 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 android.os;
+
+import android.util.Log;
+
+import com.android.internal.os.RuntimeInit;
+
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.HashMap;
+
+public abstract class FileObserver {
+    public static final int ACCESS = 0x00000001; /* File was accessed */
+    public static final int MODIFY = 0x00000002; /* File was modified */
+    public static final int ATTRIB = 0x00000004; /* Metadata changed */
+    public static final int CLOSE_WRITE = 0x00000008; /*  Writtable file was  closed */
+    public static final int CLOSE_NOWRITE = 0x00000010; /* Unwrittable file closed */
+    public static final int OPEN = 0x00000020; /* File was opened */
+    public static final int MOVED_FROM = 0x00000040; /* File was moved from X */
+    public static final int MOVED_TO = 0x00000080; /* File was moved to Y */
+    public static final int CREATE = 0x00000100; /* Subfile was created */
+    public static final int DELETE = 0x00000200; /* Subfile was deleted */
+    public static final int DELETE_SELF = 0x00000400; /* Self was deleted */
+    public static final int MOVE_SELF = 0x00000800; /* Self was moved */
+    public static final int ALL_EVENTS = ACCESS | MODIFY | ATTRIB | CLOSE_WRITE 
+            | CLOSE_NOWRITE | OPEN | MOVED_FROM | MOVED_TO | DELETE | CREATE
+	    | DELETE_SELF | MOVE_SELF;
+    
+    private static final String LOG_TAG = "FileObserver";
+
+    private static class ObserverThread extends Thread {
+	private HashMap<Integer, WeakReference> m_observers = new HashMap<Integer, WeakReference>();
+	private int m_fd;
+
+	public ObserverThread() {
+	    super("FileObserver");
+	    m_fd = init();
+	}
+
+	public void run() {
+	    observe(m_fd);
+	}
+
+	public int startWatching(String path, int mask, FileObserver observer) {
+	    int wfd = startWatching(m_fd, path, mask);
+
+	    Integer i = new Integer(wfd);
+	    if (wfd >= 0) {
+		synchronized (m_observers) {
+		    m_observers.put(i, new WeakReference(observer));
+		}
+	    }
+
+	    return i;
+	}
+
+	public void stopWatching(int descriptor) {
+	    stopWatching(m_fd, descriptor);
+	}
+
+    public void onEvent(int wfd, int mask, String path) {
+        // look up our observer, fixing up the map if necessary...
+        FileObserver observer;
+
+        synchronized (m_observers) {
+            WeakReference weak = m_observers.get(wfd);
+            observer = (FileObserver) weak.get();
+            if (observer == null) {
+                m_observers.remove(wfd);
+            }
+        }
+
+        // ...then call out to the observer without the sync lock held
+        if (observer != null) {
+            try {
+                observer.onEvent(mask, path);
+            } catch (Throwable throwable) {
+                Log.e(LOG_TAG, "Unhandled throwable " + throwable.toString() + 
+                        " (returned by observer " + observer + ")", throwable);
+                RuntimeInit.crash("FileObserver", throwable);
+            }
+        }
+    }
+
+	private native int init();
+	private native void observe(int fd);
+	private native int startWatching(int fd, String path, int mask);
+	private native void stopWatching(int fd, int wfd);
+    }
+
+    private static ObserverThread s_observerThread;
+
+    static {
+	s_observerThread = new ObserverThread();
+	s_observerThread.start();
+    }
+
+    // instance
+    private String m_path;
+    private Integer m_descriptor;
+    private int m_mask;
+
+    public FileObserver(String path) {
+	this(path, ALL_EVENTS);
+    }
+
+    public FileObserver(String path, int mask) {
+	m_path = path;
+	m_mask = mask;
+	m_descriptor = -1;
+    }
+
+    protected void finalize() {
+	stopWatching();
+    }
+
+    public void startWatching() {
+	if (m_descriptor < 0) {
+	    m_descriptor = s_observerThread.startWatching(m_path, m_mask, this);
+	}
+    }
+
+    public void stopWatching() {
+	if (m_descriptor >= 0) {
+	    s_observerThread.stopWatching(m_descriptor);
+	    m_descriptor = -1;
+	}
+    }
+
+    public abstract void onEvent(int event, String path);
+}
diff --git a/core/java/android/os/FileUtils.java b/core/java/android/os/FileUtils.java
new file mode 100644
index 0000000..51dfb5b
--- /dev/null
+++ b/core/java/android/os/FileUtils.java
@@ -0,0 +1,197 @@
+/*
+ * 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 android.os;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.regex.Pattern;
+
+
+/**
+ * Tools for managing files.  Not for public consumption.
+ * @hide
+ */
+public class FileUtils
+{
+    public static final int S_IRWXU = 00700;
+    public static final int S_IRUSR = 00400;
+    public static final int S_IWUSR = 00200;
+    public static final int S_IXUSR = 00100;
+
+    public static final int S_IRWXG = 00070;
+    public static final int S_IRGRP = 00040;
+    public static final int S_IWGRP = 00020;
+    public static final int S_IXGRP = 00010;
+
+    public static final int S_IRWXO = 00007;
+    public static final int S_IROTH = 00004;
+    public static final int S_IWOTH = 00002;
+    public static final int S_IXOTH = 00001;
+    
+    
+    /**
+     * File status information. This class maps directly to the POSIX stat structure.
+     * @hide
+     */
+    public static final class FileStatus {
+        public int dev;
+        public int ino;
+        public int mode;
+        public int nlink;
+        public int uid;
+        public int gid;
+        public int rdev;
+        public long size;
+        public int blksize;
+        public long blocks;
+        public long atime;
+        public long mtime;
+        public long ctime;
+    }
+    
+    /**
+     * Get the status for the given path. This is equivalent to the POSIX stat(2) system call. 
+     * @param path The path of the file to be stat'd.
+     * @param status Optional argument to fill in. It will only fill in the status if the file
+     * exists. 
+     * @return true if the file exists and false if it does not exist. If you do not have 
+     * permission to stat the file, then this method will return false.
+     */
+    public static native boolean getFileStatus(String path, FileStatus status);
+
+    /** Regular expression for safe filenames: no spaces or metacharacters */
+    private static final Pattern SAFE_FILENAME_PATTERN = Pattern.compile("[\\w%+,./=_-]+");
+
+    public static native int setPermissions(String file, int mode, int uid, int gid);
+
+    public static native int getPermissions(String file, int[] outPermissions);
+
+    /** returns the FAT file system volume ID for the volume mounted 
+     * at the given mount point, or -1 for failure
+     * @param mount point for FAT volume
+     * @return volume ID or -1
+     */
+    public static native int getFatVolumeId(String mountPoint);
+        
+    // copy a file from srcFile to destFile, return true if succeed, return
+    // false if fail
+    public static boolean copyFile(File srcFile, File destFile) {
+        boolean result = false;
+        try {
+            InputStream in = new FileInputStream(srcFile);
+            try {
+                result = copyToFile(in, destFile);
+            } finally  {
+                in.close();
+            }
+        } catch (IOException e) {
+            result = false;
+        }
+        return result;
+    }
+    
+    /**
+     * Copy data from a source stream to destFile.
+     * Return true if succeed, return false if failed.
+     */
+    public static boolean copyToFile(InputStream inputStream, File destFile) {
+        try {
+            OutputStream out = new FileOutputStream(destFile);
+            try {
+                byte[] buffer = new byte[4096];
+                int bytesRead;
+                while ((bytesRead = inputStream.read(buffer)) >= 0) {
+                    out.write(buffer, 0, bytesRead);
+                }
+            } finally {
+                out.close();
+            }
+            return true;
+        } catch (IOException e) {
+            return false;
+        }
+    }
+
+    /**
+     * Check if a filename is "safe" (no metacharacters or spaces).
+     * @param file  The file to check
+     */
+    public static boolean isFilenameSafe(File file) {
+        // Note, we check whether it matches what's known to be safe,
+        // rather than what's known to be unsafe.  Non-ASCII, control
+        // characters, etc. are all unsafe by default.
+        return SAFE_FILENAME_PATTERN.matcher(file.getPath()).matches();
+    }
+
+    /**
+     * Read a text file into a String, optionally limiting the length.
+     * @param file to read (will not seek, so things like /proc files are OK)
+     * @param max length (positive for head, negative of tail, 0 for no limit)
+     * @param ellipsis to add of the file was truncated (can be null)
+     * @return the contents of the file, possibly truncated
+     * @throws IOException if something goes wrong reading the file
+     */
+    public static String readTextFile(File file, int max, String ellipsis) throws IOException {
+        InputStream input = new FileInputStream(file);
+        try {
+            if (max > 0) {  // "head" mode: read the first N bytes
+                byte[] data = new byte[max + 1];
+                int length = input.read(data);
+                if (length <= 0) return "";
+                if (length <= max) return new String(data, 0, length);
+                if (ellipsis == null) return new String(data, 0, max);
+                return new String(data, 0, max) + ellipsis;
+            } else if (max < 0) {  // "tail" mode: read it all, keep the last N
+                int len;
+                boolean rolled = false;
+                byte[] last = null, data = null;
+                do {
+                    if (last != null) rolled = true;
+                    byte[] tmp = last; last = data; data = tmp;
+                    if (data == null) data = new byte[-max];
+                    len = input.read(data);
+                } while (len == data.length);
+
+                if (last == null && len <= 0) return "";
+                if (last == null) return new String(data, 0, len);
+                if (len > 0) {
+                    rolled = true;
+                    System.arraycopy(last, len, last, 0, last.length - len);
+                    System.arraycopy(data, 0, last, last.length - len, len);
+                }
+                if (ellipsis == null || !rolled) return new String(last);
+                return ellipsis + new String(last);
+            } else {  // "cat" mode: read it all
+                ByteArrayOutputStream contents = new ByteArrayOutputStream();
+                int len;
+                byte[] data = new byte[1024];
+                do {
+                    len = input.read(data);
+                    if (len > 0) contents.write(data, 0, len);
+                } while (len == data.length);
+                return contents.toString();
+            }
+        } finally {
+            input.close();
+        }
+    }
+}
diff --git a/core/java/android/os/Handler.java b/core/java/android/os/Handler.java
new file mode 100644
index 0000000..2a32e54
--- /dev/null
+++ b/core/java/android/os/Handler.java
@@ -0,0 +1,594 @@
+/*
+ * 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 android.os;
+
+import android.util.Log;
+import android.util.Printer;
+
+import java.lang.reflect.Modifier;
+
+/**
+ * A Handler allows you to send and process {@link Message} and Runnable
+ * objects associated with a thread's {@link MessageQueue}.  Each Handler
+ * instance is associated with a single thread and that thread's message
+ * queue.  When you create a new Handler, it is bound to the thread /
+ * message queue of the thread that is creating it -- from that point on,
+ * it will deliver messages and runnables to that message queue and execute
+ * them as they come out of the message queue.
+ * 
+ * <p>There are two main uses for a Handler: (1) to schedule messages and
+ * runnables to be executed as some point in the future; and (2) to enqueue
+ * an action to be performed on a different thread than your own.
+ * 
+ * <p>Scheduling messages is accomplished with the
+ * {@link #post}, {@link #postAtTime(Runnable, long)},
+ * {@link #postDelayed}, {@link #sendEmptyMessage},
+ * {@link #sendMessage}, {@link #sendMessageAtTime}, and
+ * {@link #sendMessageDelayed} methods.  The <em>post</em> versions allow
+ * you to enqueue Runnable objects to be called by the message queue when
+ * they are received; the <em>sendMessage</em> versions allow you to enqueue
+ * a {@link Message} object containing a bundle of data that will be
+ * processed by the Handler's {@link #handleMessage} method (requiring that
+ * you implement a subclass of Handler).
+ * 
+ * <p>When posting or sending to a Handler, you can either
+ * allow the item to be processed as soon as the message queue is ready
+ * to do so, or specify a delay before it gets processed or absolute time for
+ * it to be processed.  The latter two allow you to implement timeouts,
+ * ticks, and other timing-based behavior.
+ * 
+ * <p>When a
+ * process is created for your application, its main thread is dedicated to
+ * running a message queue that takes care of managing the top-level
+ * application objects (activities, broadcast receivers, etc) and any windows
+ * they create.  You can create your own threads, and communicate back with
+ * the main application thread through a Handler.  This is done by calling
+ * the same <em>post</em> or <em>sendMessage</em> methods as before, but from
+ * your new thread.  The given Runnable or Message will than be scheduled
+ * in the Handler's message queue and processed when appropriate.
+ */
+public class Handler {
+    /*
+     * Set this flag to true to detect anonymous, local or member classes
+     * that extend this Handler class and that are not static. These kind
+     * of classes can potentially create leaks.
+     */
+    private static final boolean FIND_POTENTIAL_LEAKS = false;
+    private static final String TAG = "Handler";
+
+    /**
+     * Callback interface you can use when instantiating a Handler to avoid
+     * having to implement your own subclass of Handler.
+     */
+    public interface Callback {
+        public boolean handleMessage(Message msg);
+    }
+    
+    /**
+     * Subclasses must implement this to receive messages.
+     */
+    public void handleMessage(Message msg) {
+    }
+    
+    /**
+     * Handle system messages here.
+     */
+    public void dispatchMessage(Message msg) {
+        if (msg.callback != null) {
+            handleCallback(msg);
+        } else {
+            if (mCallback != null) {
+                if (mCallback.handleMessage(msg)) {
+                    return;
+                }
+            }
+            handleMessage(msg);
+        }
+    }
+
+    /**
+     * Default constructor associates this handler with the queue for the
+     * current thread.
+     *
+     * If there isn't one, this handler won't be able to receive messages.
+     */
+    public Handler() {
+        if (FIND_POTENTIAL_LEAKS) {
+            final Class<? extends Handler> klass = getClass();
+            if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
+                    (klass.getModifiers() & Modifier.STATIC) == 0) {
+                Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
+                    klass.getCanonicalName());
+            }
+        }
+
+        mLooper = Looper.myLooper();
+        if (mLooper == null) {
+            throw new RuntimeException(
+                "Can't create handler inside thread that has not called Looper.prepare()");
+        }
+        mQueue = mLooper.mQueue;
+        mCallback = null;
+    }
+
+    /**
+     * Constructor associates this handler with the queue for the
+     * current thread and takes a callback interface in which you can handle
+     * messages.
+     */
+    public Handler(Callback callback) {
+        if (FIND_POTENTIAL_LEAKS) {
+            final Class<? extends Handler> klass = getClass();
+            if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
+                    (klass.getModifiers() & Modifier.STATIC) == 0) {
+                Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
+                    klass.getCanonicalName());
+            }
+        }
+
+        mLooper = Looper.myLooper();
+        if (mLooper == null) {
+            throw new RuntimeException(
+                "Can't create handler inside thread that has not called Looper.prepare()");
+        }
+        mQueue = mLooper.mQueue;
+        mCallback = callback;
+    }
+
+    /**
+     * Use the provided queue instead of the default one.
+     */
+    public Handler(Looper looper) {
+        mLooper = looper;
+        mQueue = looper.mQueue;
+        mCallback = null;
+    }
+
+    /**
+     * Use the provided queue instead of the default one and take a callback
+     * interface in which to handle messages.
+     */
+    public Handler(Looper looper, Callback callback) {
+        mLooper = looper;
+        mQueue = looper.mQueue;
+        mCallback = callback;
+    }
+
+    /**
+     * Returns a new {@link android.os.Message Message} from the global message pool. More efficient than
+     * creating and allocating new instances. The retrieved message has its handler set to this instance (Message.target == this).
+     *  If you don't want that facility, just call Message.obtain() instead.
+     */
+    public final Message obtainMessage()
+    {
+        return Message.obtain(this);
+    }
+
+    /**
+     * Same as {@link #obtainMessage()}, except that it also sets the what member of the returned Message.
+     * 
+     * @param what Value to assign to the returned Message.what field.
+     * @return A Message from the global message pool.
+     */
+    public final Message obtainMessage(int what)
+    {
+        return Message.obtain(this, what);
+    }
+    
+    /**
+     * 
+     * Same as {@link #obtainMessage()}, except that it also sets the what and obj members 
+     * of the returned Message.
+     * 
+     * @param what Value to assign to the returned Message.what field.
+     * @param obj Value to assign to the returned Message.obj field.
+     * @return A Message from the global message pool.
+     */
+    public final Message obtainMessage(int what, Object obj)
+    {
+        return Message.obtain(this, what, obj);
+    }
+
+    /**
+     * 
+     * Same as {@link #obtainMessage()}, except that it also sets the what, arg1 and arg2 members of the returned
+     * Message.
+     * @param what Value to assign to the returned Message.what field.
+     * @param arg1 Value to assign to the returned Message.arg1 field.
+     * @param arg2 Value to assign to the returned Message.arg2 field.
+     * @return A Message from the global message pool.
+     */
+    public final Message obtainMessage(int what, int arg1, int arg2)
+    {
+        return Message.obtain(this, what, arg1, arg2);
+    }
+    
+    /**
+     * 
+     * Same as {@link #obtainMessage()}, except that it also sets the what, obj, arg1,and arg2 values on the 
+     * returned Message.
+     * @param what Value to assign to the returned Message.what field.
+     * @param arg1 Value to assign to the returned Message.arg1 field.
+     * @param arg2 Value to assign to the returned Message.arg2 field.
+     * @param obj Value to assign to the returned Message.obj field.
+     * @return A Message from the global message pool.
+     */
+    public final Message obtainMessage(int what, int arg1, int arg2, Object obj)
+    {
+        return Message.obtain(this, what, arg1, arg2, obj);
+    }
+
+    /**
+     * Causes the Runnable r to be added to the message queue.
+     * The runnable will be run on the thread to which this handler is 
+     * attached. 
+     *  
+     * @param r The Runnable that will be executed.
+     * 
+     * @return Returns true if the Runnable was successfully placed in to the 
+     *         message queue.  Returns false on failure, usually because the
+     *         looper processing the message queue is exiting.
+     */
+    public final boolean post(Runnable r)
+    {
+       return  sendMessageDelayed(getPostMessage(r), 0);
+    }
+    
+    /**
+     * Causes the Runnable r to be added to the message queue, to be run
+     * at a specific time given by <var>uptimeMillis</var>.
+     * <b>The time-base is {@link android.os.SystemClock#uptimeMillis}.</b>
+     * The runnable will be run on the thread to which this handler is attached.
+     *
+     * @param r The Runnable that will be executed.
+     * @param uptimeMillis The absolute time at which the callback should run,
+     *         using the {@link android.os.SystemClock#uptimeMillis} time-base.
+     *  
+     * @return Returns true if the Runnable was successfully placed in to the 
+     *         message queue.  Returns false on failure, usually because the
+     *         looper processing the message queue is exiting.  Note that a
+     *         result of true does not mean the Runnable will be processed -- if
+     *         the looper is quit before the delivery time of the message
+     *         occurs then the message will be dropped.
+     */
+    public final boolean postAtTime(Runnable r, long uptimeMillis)
+    {
+        return sendMessageAtTime(getPostMessage(r), uptimeMillis);
+    }
+    
+    /**
+     * Causes the Runnable r to be added to the message queue, to be run
+     * at a specific time given by <var>uptimeMillis</var>.
+     * <b>The time-base is {@link android.os.SystemClock#uptimeMillis}.</b>
+     * The runnable will be run on the thread to which this handler is attached.
+     *
+     * @param r The Runnable that will be executed.
+     * @param uptimeMillis The absolute time at which the callback should run,
+     *         using the {@link android.os.SystemClock#uptimeMillis} time-base.
+     * 
+     * @return Returns true if the Runnable was successfully placed in to the 
+     *         message queue.  Returns false on failure, usually because the
+     *         looper processing the message queue is exiting.  Note that a
+     *         result of true does not mean the Runnable will be processed -- if
+     *         the looper is quit before the delivery time of the message
+     *         occurs then the message will be dropped.
+     *         
+     * @see android.os.SystemClock#uptimeMillis
+     */
+    public final boolean postAtTime(Runnable r, Object token, long uptimeMillis)
+    {
+        return sendMessageAtTime(getPostMessage(r, token), uptimeMillis);
+    }
+    
+    /**
+     * Causes the Runnable r to be added to the message queue, to be run
+     * after the specified amount of time elapses.
+     * The runnable will be run on the thread to which this handler
+     * is attached.
+     *  
+     * @param r The Runnable that will be executed.
+     * @param delayMillis The delay (in milliseconds) until the Runnable
+     *        will be executed.
+     *        
+     * @return Returns true if the Runnable was successfully placed in to the 
+     *         message queue.  Returns false on failure, usually because the
+     *         looper processing the message queue is exiting.  Note that a
+     *         result of true does not mean the Runnable will be processed --
+     *         if the looper is quit before the delivery time of the message
+     *         occurs then the message will be dropped.
+     */
+    public final boolean postDelayed(Runnable r, long delayMillis)
+    {
+        return sendMessageDelayed(getPostMessage(r), delayMillis);
+    }
+    
+    /**
+     * Posts a message to an object that implements Runnable.
+     * Causes the Runnable r to executed on the next iteration through the
+     * message queue. The runnable will be run on the thread to which this
+     * handler is attached.
+     * <b>This method is only for use in very special circumstances -- it
+     * can easily starve the message queue, cause ordering problems, or have
+     * other unexpected side-effects.</b>
+     *  
+     * @param r The Runnable that will be executed.
+     * 
+     * @return Returns true if the message was successfully placed in to the 
+     *         message queue.  Returns false on failure, usually because the
+     *         looper processing the message queue is exiting.
+     */
+    public final boolean postAtFrontOfQueue(Runnable r)
+    {
+        return sendMessageAtFrontOfQueue(getPostMessage(r));
+    }
+
+    /**
+     * Remove any pending posts of Runnable r that are in the message queue.
+     */
+    public final void removeCallbacks(Runnable r)
+    {
+        mQueue.removeMessages(this, r, null);
+    }
+
+    /**
+     * Remove any pending posts of Runnable <var>r</var> with Object
+     * <var>token</var> that are in the message queue.
+     */
+    public final void removeCallbacks(Runnable r, Object token)
+    {
+        mQueue.removeMessages(this, r, token);
+    }
+
+    /**
+     * Pushes a message onto the end of the message queue after all pending messages
+     * before the current time. It will be received in {@link #handleMessage},
+     * in the thread attached to this handler.
+     *  
+     * @return Returns true if the message was successfully placed in to the 
+     *         message queue.  Returns false on failure, usually because the
+     *         looper processing the message queue is exiting.
+     */
+    public final boolean sendMessage(Message msg)
+    {
+        return sendMessageDelayed(msg, 0);
+    }
+
+    /**
+     * Sends a Message containing only the what value.
+     *  
+     * @return Returns true if the message was successfully placed in to the 
+     *         message queue.  Returns false on failure, usually because the
+     *         looper processing the message queue is exiting.
+     */
+    public final boolean sendEmptyMessage(int what)
+    {
+        return sendEmptyMessageDelayed(what, 0);
+    }
+
+    /**
+     * Sends a Message containing only the what value, to be delivered
+     * after the specified amount of time elapses.
+     * @see #sendMessageDelayed(android.os.Message, long) 
+     * 
+     * @return Returns true if the message was successfully placed in to the 
+     *         message queue.  Returns false on failure, usually because the
+     *         looper processing the message queue is exiting.
+     */
+    public final boolean sendEmptyMessageDelayed(int what, long delayMillis) {
+        Message msg = Message.obtain();
+        msg.what = what;
+        return sendMessageDelayed(msg, delayMillis);
+    }
+
+    /**
+     * Sends a Message containing only the what value, to be delivered 
+     * at a specific time.
+     * @see #sendMessageAtTime(android.os.Message, long)
+     *  
+     * @return Returns true if the message was successfully placed in to the 
+     *         message queue.  Returns false on failure, usually because the
+     *         looper processing the message queue is exiting.
+     */
+
+    public final boolean sendEmptyMessageAtTime(int what, long uptimeMillis) {
+        Message msg = Message.obtain();
+        msg.what = what;
+        return sendMessageAtTime(msg, uptimeMillis);
+    }
+
+    /**
+     * Enqueue a message into the message queue after all pending messages
+     * before (current time + delayMillis). You will receive it in
+     * {@link #handleMessage}, in the thread attached to this handler.
+     *  
+     * @return Returns true if the message was successfully placed in to the 
+     *         message queue.  Returns false on failure, usually because the
+     *         looper processing the message queue is exiting.  Note that a
+     *         result of true does not mean the message will be processed -- if
+     *         the looper is quit before the delivery time of the message
+     *         occurs then the message will be dropped.
+     */
+    public final boolean sendMessageDelayed(Message msg, long delayMillis)
+    {
+        if (delayMillis < 0) {
+            delayMillis = 0;
+        }
+        return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
+    }
+
+    /**
+     * Enqueue a message into the message queue after all pending messages
+     * before the absolute time (in milliseconds) <var>uptimeMillis</var>.
+     * <b>The time-base is {@link android.os.SystemClock#uptimeMillis}.</b>
+     * You will receive it in {@link #handleMessage}, in the thread attached
+     * to this handler.
+     * 
+     * @param uptimeMillis The absolute time at which the message should be
+     *         delivered, using the
+     *         {@link android.os.SystemClock#uptimeMillis} time-base.
+     *         
+     * @return Returns true if the message was successfully placed in to the 
+     *         message queue.  Returns false on failure, usually because the
+     *         looper processing the message queue is exiting.  Note that a
+     *         result of true does not mean the message will be processed -- if
+     *         the looper is quit before the delivery time of the message
+     *         occurs then the message will be dropped.
+     */
+    public boolean sendMessageAtTime(Message msg, long uptimeMillis)
+    {
+        boolean sent = false;
+        MessageQueue queue = mQueue;
+        if (queue != null) {
+            msg.target = this;
+            sent = queue.enqueueMessage(msg, uptimeMillis);
+        }
+        else {
+            RuntimeException e = new RuntimeException(
+                this + " sendMessageAtTime() called with no mQueue");
+            Log.w("Looper", e.getMessage(), e);
+        }
+        return sent;
+    }
+
+    /**
+     * Enqueue a message at the front of the message queue, to be processed on
+     * the next iteration of the message loop.  You will receive it in
+     * {@link #handleMessage}, in the thread attached to this handler.
+     * <b>This method is only for use in very special circumstances -- it
+     * can easily starve the message queue, cause ordering problems, or have
+     * other unexpected side-effects.</b>
+     *  
+     * @return Returns true if the message was successfully placed in to the 
+     *         message queue.  Returns false on failure, usually because the
+     *         looper processing the message queue is exiting.
+     */
+    public final boolean sendMessageAtFrontOfQueue(Message msg)
+    {
+        boolean sent = false;
+        MessageQueue queue = mQueue;
+        if (queue != null) {
+            msg.target = this;
+            sent = queue.enqueueMessage(msg, 0);
+        }
+        else {
+            RuntimeException e = new RuntimeException(
+                this + " sendMessageAtTime() called with no mQueue");
+            Log.w("Looper", e.getMessage(), e);
+        }
+        return sent;
+    }
+
+    /**
+     * Remove any pending posts of messages with code 'what' that are in the
+     * message queue.
+     */
+    public final void removeMessages(int what) {
+        mQueue.removeMessages(this, what, null, true);
+    }
+
+    /**
+     * Remove any pending posts of messages with code 'what' and whose obj is
+     * 'object' that are in the message queue.
+     */
+    public final void removeMessages(int what, Object object) {
+        mQueue.removeMessages(this, what, object, true);
+    }
+
+    /**
+     * Remove any pending posts of callbacks and sent messages whose
+     * <var>obj</var> is <var>token</var>.
+     */
+    public final void removeCallbacksAndMessages(Object token) {
+        mQueue.removeCallbacksAndMessages(this, token);
+    }
+
+    /**
+     * Check if there are any pending posts of messages with code 'what' in
+     * the message queue.
+     */
+    public final boolean hasMessages(int what) {
+        return mQueue.removeMessages(this, what, null, false);
+    }
+
+    /**
+     * Check if there are any pending posts of messages with code 'what' and
+     * whose obj is 'object' in the message queue.
+     */
+    public final boolean hasMessages(int what, Object object) {
+        return mQueue.removeMessages(this, what, object, false);
+    }
+
+    // if we can get rid of this method, the handler need not remember its loop
+    // we could instead export a getMessageQueue() method... 
+    public final Looper getLooper() {
+        return mLooper;
+    }
+
+    public final void dump(Printer pw, String prefix) {
+        pw.println(prefix + this + " @ " + SystemClock.uptimeMillis());
+        if (mLooper == null) {
+            pw.println(prefix + "looper uninitialized");
+        } else {
+            mLooper.dump(pw, prefix + "  ");
+        }
+    }
+
+    @Override
+    public String toString() {
+        return "Handler{"
+        + Integer.toHexString(System.identityHashCode(this))
+        + "}";
+    }
+
+    final IMessenger getIMessenger() {
+        synchronized (mQueue) {
+            if (mMessenger != null) {
+                return mMessenger;
+            }
+            mMessenger = new MessengerImpl();
+            return mMessenger;
+        }
+    }
+    
+    private final class MessengerImpl extends IMessenger.Stub {
+        public void send(Message msg) {
+            Handler.this.sendMessage(msg);
+        }
+    }
+    
+    private final Message getPostMessage(Runnable r) {
+        Message m = Message.obtain();
+        m.callback = r;
+        return m;
+    }
+
+    private final Message getPostMessage(Runnable r, Object token) {
+        Message m = Message.obtain();
+        m.obj = token;
+        m.callback = r;
+        return m;
+    }
+
+    private final void handleCallback(Message message) {
+        message.callback.run();
+    }
+
+    final MessageQueue mQueue;
+    final Looper mLooper;
+    final Callback mCallback;
+    IMessenger mMessenger;
+}
diff --git a/core/java/android/os/HandlerState.java b/core/java/android/os/HandlerState.java
new file mode 100644
index 0000000..0708f7d
--- /dev/null
+++ b/core/java/android/os/HandlerState.java
@@ -0,0 +1,33 @@
+/*
+ * 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 android.os;
+
+/**
+ * {@hide}
+ */
+public abstract class HandlerState {
+    public HandlerState() {
+    }
+
+    public void enter(Message message) {
+    }
+
+    public abstract void processMessage(Message message);
+
+    public void exit(Message message) {
+    }
+}
diff --git a/core/java/android/os/HandlerStateMachine.java b/core/java/android/os/HandlerStateMachine.java
new file mode 100644
index 0000000..d004a25
--- /dev/null
+++ b/core/java/android/os/HandlerStateMachine.java
@@ -0,0 +1,290 @@
+/*
+ * 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 android.os;
+
+import android.util.Log;
+import android.util.LogPrinter;
+
+/**
+ * {@hide}
+ *
+ * Implement a state machine where each state is an object,
+ * HandlerState. Each HandlerState must implement processMessage
+ * and optionally enter/exit. When a state machine is created
+ * the initial state must be set. When messages are sent to
+ * a state machine the current state's processMessage method is
+ * invoked. If this is the first message for this state the
+ * enter method is called prior to processMessage and when
+ * transtionTo is invoked the state's exit method will be
+ * called after returning from processMessage.
+ *
+ * If a message should be handled in a different state the
+ * processMessage method may call deferMessage. This causes
+ * the message to be saved on a list until transitioning
+ * to a new state, at which time all of the deferred messages
+ * will be put on the front of the state machines queue and
+ * processed by the new current state's processMessage
+ * method.
+ *
+ * Below is an example state machine with two state's, S1 and S2.
+ * The initial state is S1 which defers all messages and only
+ * transition to S2 when message.what == TEST_WHAT_2. State S2
+ * will process each messages until it receives TEST_WHAT_2
+ * where it will transition back to S1:
+<code>
+     class StateMachine1 extends HandlerStateMachine {
+        private static final int TEST_WHAT_1 = 1;
+        private static final int TEST_WHAT_2 = 2;
+
+        StateMachine1(String name) {
+            super(name);
+            setInitialState(mS1);
+        }
+
+        class S1 extends HandlerState {
+            @Override public void enter(Message message) {
+            }
+
+            @Override public void processMessage(Message message) {
+                deferMessage(message);
+                if (message.what == TEST_WHAT_2) {
+                    transitionTo(mS2);
+                }
+            }
+
+            @Override public void exit(Message message) {
+            }
+        }
+
+        class S2 extends HandlerState {
+            @Override public void processMessage(Message message) {
+                // Do some processing
+                if (message.what == TEST_WHAT_2) {
+                    transtionTo(mS1);
+                }
+            }
+        }
+
+        private S1 mS1 = new S1();
+        private S2 mS2 = new S2();
+    }
+</code>
+ */
+public class HandlerStateMachine {
+
+    private boolean mDbg = false;
+    private static final String TAG = "HandlerStateMachine";
+    private String mName;
+    private SmHandler mHandler;
+    private HandlerThread mHandlerThread;
+
+    /**
+     * Handle messages sent to the state machine by calling
+     * the current state's processMessage. It also handles
+     * the enter/exit calls and placing any deferred messages
+     * back onto the queue when transitioning to a new state.
+     */
+    class SmHandler extends Handler {
+
+        SmHandler(Looper looper) {
+          super(looper);
+        }
+
+        /**
+         * This will dispatch the message to the
+         * current state's processMessage.
+         */
+        @Override
+        final public void handleMessage(Message msg) {
+            if (mDbg) Log.d(TAG, "SmHandler.handleMessage E");
+            if (mDestState != null) {
+                if (mDbg) Log.d(TAG, "SmHandler.handleMessage; new destation call enter");
+                mCurrentState = mDestState;
+                mDestState = null;
+                mCurrentState.enter(msg);
+            }
+            if (mCurrentState != null) {
+                if (mDbg) Log.d(TAG, "SmHandler.handleMessage; call processMessage");
+                mCurrentState.processMessage(msg);
+            } else {
+                /* Strange no state to execute */
+                Log.e(TAG, "handleMessage: no current state, did you call setInitialState");
+            }
+
+            if (mDestState != null) {
+                if (mDbg) Log.d(TAG, "SmHandler.handleMessage; new destination call exit");
+                mCurrentState.exit(msg);
+
+                /**
+                 * Place the messages from the deferred queue:t
+                 * on to the Handler's message queue in the
+                 * same order that they originally arrived.
+                 *
+                 * We set cur.when = 0 to circumvent the check
+                 * that this message has already been sent.
+                 */
+                while (mDeferredMessages != null) {
+                    Message cur = mDeferredMessages;
+                    mDeferredMessages = mDeferredMessages.next;
+                    cur.when = 0;
+                    if (mDbg) Log.d(TAG, "SmHandler.handleMessage; queue deferred message what="
+                                                + cur.what + " target=" + cur.target);
+                    sendMessageAtFrontOfQueue(cur);
+                }
+                if (mDbg) Log.d(TAG, "SmHandler.handleMessage X");
+            }
+        }
+
+        public HandlerState mCurrentState;
+        public HandlerState mDestState;
+        public Message mDeferredMessages;
+    }
+
+    /**
+     * Create an active StateMachine, one that has a
+     * dedicated thread/looper/queue.
+     */
+    public HandlerStateMachine(String name) {
+        mName = name;
+        mHandlerThread =  new HandlerThread(name);
+        mHandlerThread.start();
+        mHandler = new SmHandler(mHandlerThread.getLooper());
+    }
+
+    /**
+     * Get a message and set Message.target = this.
+     */
+    public final Message obtainMessage()
+    {
+        Message msg = Message.obtain(mHandler);
+        if (mDbg) Log.d(TAG, "StateMachine.obtainMessage() EX target=" + msg.target);
+        return msg;
+    }
+
+    /**
+     * Get a message and set Message.target = this and
+     * Message.what = what.
+     */
+    public final Message obtainMessage(int what) {
+        Message msg = Message.obtain(mHandler, what);
+        if (mDbg) {
+            Log.d(TAG, "StateMachine.obtainMessage(what) EX what=" + msg.what +
+                       " target=" + msg.target);
+        }
+        return msg;
+    }
+
+    /**
+     * Enqueue a message to this state machine.
+     */
+    public final void sendMessage(Message msg) {
+        if (mDbg) Log.d(TAG, "StateMachine.sendMessage EX msg.what=" + msg.what);
+        mHandler.sendMessage(msg);
+    }
+
+    /**
+     * Enqueue a message to this state machine after a delay.
+     */
+    public final void sendMessageDelayed(Message msg, long delayMillis) {
+        if (mDbg) {
+            Log.d(TAG, "StateMachine.sendMessageDelayed EX msg.what="
+                            + msg.what + " delay=" + delayMillis);
+        }
+        mHandler.sendMessageDelayed(msg, delayMillis);
+    }
+
+    /**
+     * Set the initial state. This must be invoked before
+     * and messages are sent to the state machine.
+     */
+    public void setInitialState(HandlerState initialState) {
+        if (mDbg) {
+            Log.d(TAG, "StateMachine.setInitialState EX initialState"
+                            + initialState.getClass().getName());
+        }
+        mHandler.mDestState = initialState;
+    }
+
+    /**
+     * transition to destination state. Upon returning
+     * from processMessage the current state's exit will
+     * be executed and upon the next message arriving
+     * destState.enter will be invoked.
+     */
+    final public void transitionTo(HandlerState destState) {
+        if (mDbg) {
+            Log.d(TAG, "StateMachine.transitionTo EX destState"
+                            + destState.getClass().getName());
+        }
+        mHandler.mDestState = destState;
+    }
+
+    /**
+     * Defer this message until next state transition.
+     * Upon transitioning all deferred messages will be
+     * placed on the queue and reprocessed in the original
+     * order. (i.e. The next state the oldest messages will
+     * be processed first)
+     */
+    final public void deferMessage(Message msg) {
+        if (mDbg) {
+            Log.d(TAG, "StateMachine.deferMessage EX mDeferredMessages="
+                            + mHandler.mDeferredMessages);
+        }
+
+        /* Copy the "msg" to "newMsg" as "msg" will be recycled */
+        Message newMsg = obtainMessage();
+        newMsg.copyFrom(msg);
+
+        /* Place on front of queue */
+        newMsg.next = mHandler.mDeferredMessages;
+        mHandler.mDeferredMessages = newMsg;
+    }
+
+    /**
+     * @return the name
+     */
+    public String getName() {
+        return mName;
+    }
+
+    /**
+     * @return Handler
+     */
+    public Handler getHandler() {
+        return mHandler;
+    }
+
+    /**
+     * @return if debugging is enabled
+     */
+    public boolean isDbg() {
+        return mDbg;
+    }
+
+    /**
+     * Set debug enable/disabled.
+     */
+    public void setDbg(boolean dbg) {
+        mDbg = dbg;
+        if (mDbg) {
+            mHandlerThread.getLooper().setMessageLogging(new LogPrinter(Log.VERBOSE, TAG));
+        } else {
+            mHandlerThread.getLooper().setMessageLogging(null);
+        }
+   }
+}
diff --git a/core/java/android/os/HandlerThread.java b/core/java/android/os/HandlerThread.java
new file mode 100644
index 0000000..0ce86db
--- /dev/null
+++ b/core/java/android/os/HandlerThread.java
@@ -0,0 +1,93 @@
+/*
+ * 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 android.os;
+
+/**
+ * Handy class for starting a new thread that has a looper. The looper can then be 
+ * used to create handler classes. Note that start() must still be called.
+ */
+public class HandlerThread extends Thread {
+    private int mPriority;
+    private int mTid = -1;
+    private Looper mLooper;
+
+    public HandlerThread(String name) {
+        super(name);
+        mPriority = Process.THREAD_PRIORITY_DEFAULT;
+    }
+    
+    /**
+     * Constructs a HandlerThread.
+     * @param name
+     * @param priority The priority to run the thread at. The value supplied must be from 
+     * {@link android.os.Process} and not from java.lang.Thread.
+     */
+    public HandlerThread(String name, int priority) {
+        super(name);
+        mPriority = priority;
+    }
+    
+    /**
+     * Call back method that can be explicitly over ridden if needed to execute some
+     * setup before Looper loops.
+     */
+    protected void onLooperPrepared() {
+    }
+
+    public void run() {
+        mTid = Process.myTid();
+        Looper.prepare();
+        synchronized (this) {
+            mLooper = Looper.myLooper();
+            Process.setThreadPriority(mPriority);
+            notifyAll();
+        }
+        onLooperPrepared();
+        Looper.loop();
+        mTid = -1;
+    }
+    
+    /**
+     * This method returns the Looper associated with this thread. If this thread not been started
+     * or for any reason is isAlive() returns false, this method will return null. If this thread 
+     * has been started, this method will blocked until the looper has been initialized.  
+     * @return The looper.
+     */
+    public Looper getLooper() {
+        if (!isAlive()) {
+            return null;
+        }
+        
+        // If the thread has been started, wait until the looper has been created.
+        synchronized (this) {
+            while (isAlive() && mLooper == null) {
+                try {
+                    wait();
+                } catch (InterruptedException e) {
+                }
+            }
+        }
+        return mLooper;
+    }
+    
+    /**
+     * Returns the identifier of this thread. See Process.myTid().
+     */
+    public int getThreadId() {
+        return mTid;
+    }
+}
diff --git a/core/java/android/os/Hardware.java b/core/java/android/os/Hardware.java
new file mode 100644
index 0000000..3b6c9d7
--- /dev/null
+++ b/core/java/android/os/Hardware.java
@@ -0,0 +1,42 @@
+/*
+ * 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 android.os;
+
+/**
+ * {@hide}
+ */
+public class Hardware 
+{
+    /**
+     * Control the LED.
+     */
+    public static native int setLedState(int colorARGB, int onMS, int offMS);
+    
+    /**
+     * Control the Flashlight
+     */
+    public static native boolean getFlashlightEnabled();
+    public static native void setFlashlightEnabled(boolean on);
+    public static native void enableCameraFlash(int milliseconds);
+
+    /**
+     * Control the backlights
+     */
+    public static native void setScreenBacklight(int brightness);
+    public static native void setKeyboardBacklight(boolean on);
+    public static native void setButtonBacklight(boolean on);
+}
diff --git a/core/java/android/os/IBinder.java b/core/java/android/os/IBinder.java
new file mode 100644
index 0000000..5c40c9a0
--- /dev/null
+++ b/core/java/android/os/IBinder.java
@@ -0,0 +1,223 @@
+/*
+ * 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 android.os;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+
+/**
+ * Base interface for a remotable object, the core part of a lightweight
+ * remote procedure call mechanism designed for high performance when
+ * performing in-process and cross-process calls.  This
+ * interface describes the abstract protocol for interacting with a
+ * remotable object.  Do not implement this interface directly, instead
+ * extend from {@link Binder}.
+ * 
+ * <p>The key IBinder API is {@link #transact transact()} matched by
+ * {@link Binder#onTransact Binder.onTransact()}.  These
+ * methods allow you to send a call to an IBinder object and receive a
+ * call coming in to a Binder object, respectively.  This transaction API
+ * is synchronous, such that a call to {@link #transact transact()} does not
+ * return until the target has returned from
+ * {@link Binder#onTransact Binder.onTransact()}; this is the
+ * expected behavior when calling an object that exists in the local
+ * process, and the underlying inter-process communication (IPC) mechanism
+ * ensures that these same semantics apply when going across processes.
+ * 
+ * <p>The data sent through transact() is a {@link Parcel}, a generic buffer
+ * of data that also maintains some meta-data about its contents.  The meta
+ * data is used to manage IBinder object references in the buffer, so that those
+ * references can be maintained as the buffer moves across processes.  This
+ * mechanism ensures that when an IBinder is written into a Parcel and sent to
+ * another process, if that other process sends a reference to that same IBinder
+ * back to the original process, then the original process will receive the
+ * same IBinder object back.  These semantics allow IBinder/Binder objects to
+ * be used as a unique identity (to serve as a token or for other purposes)
+ * that can be managed across processes.
+ * 
+ * <p>The system maintains a pool of transaction threads in each process that
+ * it runs in.  These threads are used to dispatch all
+ * IPCs coming in from other processes.  For example, when an IPC is made from
+ * process A to process B, the calling thread in A blocks in transact() as
+ * it sends the transaction to process B.  The next available pool thread in
+ * B receives the incoming transaction, calls Binder.onTransact() on the target
+ * object, and replies with the result Parcel.  Upon receiving its result, the
+ * thread in process A returns to allow its execution to continue.  In effect,
+ * other processes appear to use as additional threads that you did not create
+ * executing in your own process.
+ * 
+ * <p>The Binder system also supports recursion across processes.  For example
+ * if process A performs a transaction to process B, and process B while
+ * handling that transaction calls transact() on an IBinder that is implemented
+ * in A, then the thread in A that is currently waiting for the original
+ * transaction to finish will take care of calling Binder.onTransact() on the
+ * object being called by B.  This ensures that the recursion semantics when
+ * calling remote binder object are the same as when calling local objects.
+ * 
+ * <p>When working with remote objects, you often want to find out when they
+ * are no longer valid.  There are three ways this can be determined:
+ * <ul>
+ * <li> The {@link #transact transact()} method will throw a
+ * {@link RemoteException} exception if you try to call it on an IBinder
+ * whose process no longer exists.
+ * <li> The {@link #pingBinder()} method can be called, and will return false
+ * if the remote process no longer exists.
+ * <li> The {@link #linkToDeath linkToDeath()} method can be used to register
+ * a {@link DeathRecipient} with the IBinder, which will be called when its
+ * containing process goes away.
+ * </ul>
+ * 
+ * @see Binder
+ */
+public interface IBinder {
+    /**
+     * The first transaction code available for user commands.
+     */
+    int FIRST_CALL_TRANSACTION  = 0x00000001;
+    /**
+     * The last transaction code available for user commands.
+     */
+    int LAST_CALL_TRANSACTION   = 0x00ffffff;
+    
+    /**
+     * IBinder protocol transaction code: pingBinder().
+     */
+    int PING_TRANSACTION        = ('_'<<24)|('P'<<16)|('N'<<8)|'G';
+    
+    /**
+     * IBinder protocol transaction code: dump internal state.
+     */
+    int DUMP_TRANSACTION        = ('_'<<24)|('D'<<16)|('M'<<8)|'P';
+    
+    /**
+     * IBinder protocol transaction code: interrogate the recipient side
+     * of the transaction for its canonical interface descriptor.
+     */
+    int INTERFACE_TRANSACTION   = ('_'<<24)|('N'<<16)|('T'<<8)|'F';
+
+    /**
+     * Flag to {@link #transact}: this is a one-way call, meaning that the
+     * caller returns immediately, without waiting for a result from the
+     * callee.
+     */
+    int FLAG_ONEWAY             = 0x00000001;
+    
+    /**
+     * Get the canonical name of the interface supported by this binder.
+     */
+    public String getInterfaceDescriptor() throws RemoteException;
+
+    /**
+     * Check to see if the object still exists.
+     * 
+     * @return Returns false if the
+     * hosting process is gone, otherwise the result (always by default
+     * true) returned by the pingBinder() implementation on the other
+     * side.
+     */
+    public boolean pingBinder();
+
+    /**
+     * Check to see if the process that the binder is in is still alive.
+     *
+     * @return false if the process is not alive.  Note that if it returns
+     * true, the process may have died while the call is returning.
+     */
+    public boolean isBinderAlive();
+    
+    /**
+     * Attempt to retrieve a local implementation of an interface
+     * for this Binder object.  If null is returned, you will need
+     * to instantiate a proxy class to marshall calls through
+     * the transact() method.
+     */
+    public IInterface queryLocalInterface(String descriptor);
+    
+    /**
+     * Print the object's state into the given stream.
+     * 
+     * @param fd The raw file descriptor that the dump is being sent to.
+     * @param args additional arguments to the dump request.
+     */
+    public void dump(FileDescriptor fd, String[] args) throws RemoteException;
+    
+    /**
+     * Perform a generic operation with the object.
+     * 
+     * @param code The action to perform.  This should
+     * be a number between {@link #FIRST_CALL_TRANSACTION} and
+     * {@link #LAST_CALL_TRANSACTION}.
+     * @param data Marshalled data to send to the target.  Most not be null.
+     * If you are not sending any data, you must create an empty Parcel
+     * that is given here.
+     * @param reply Marshalled data to be received from the target.  May be
+     * null if you are not interested in the return value.
+     * @param flags Additional operation flags.  Either 0 for a normal
+     * RPC, or {@link #FLAG_ONEWAY} for a one-way RPC.
+     */
+    public boolean transact(int code, Parcel data, Parcel reply, int flags)
+        throws RemoteException;
+
+    /**
+     * Interface for receiving a callback when the process hosting an IBinder
+     * has gone away.
+     * 
+     * @see #linkToDeath
+     */
+    public interface DeathRecipient {
+        public void binderDied();
+    }
+
+    /**
+     * Register the recipient for a notification if this binder
+     * goes away.  If this binder object unexpectedly goes away
+     * (typically because its hosting process has been killed),
+     * then the given {@link DeathRecipient}'s
+     * {@link DeathRecipient#binderDied DeathRecipient.binderDied()} method
+     * will be called.
+     * 
+     * <p>You will only receive death notifications for remote binders,
+     * as local binders by definition can't die without you dying as well.
+     * 
+     * @throws Throws {@link RemoteException} if the target IBinder's
+     * process has already died.
+     * 
+     * @see #unlinkToDeath
+     */
+    public void linkToDeath(DeathRecipient recipient, int flags)
+            throws RemoteException;
+
+    /**
+     * Remove a previously registered death notification.
+     * The recipient will no longer be called if this object
+     * dies.
+     * 
+     * @return Returns true if the <var>recipient</var> is successfully
+     * unlinked, assuring you that its
+     * {@link DeathRecipient#binderDied DeathRecipient.binderDied()} method
+     * will not be called.  Returns false if the target IBinder has already
+     * died, meaning the method has been (or soon will be) called.
+     * 
+     * @throws Throws {@link java.util.NoSuchElementException} if the given
+     * <var>recipient</var> has not been registered with the IBinder, and
+     * the IBinder is still alive.  Note that if the <var>recipient</var>
+     * was never registered, but the IBinder has already died, then this
+     * exception will <em>not</em> be thrown, and you will receive a false
+     * return value instead.
+     */
+    public boolean unlinkToDeath(DeathRecipient recipient, int flags);
+}
diff --git a/core/java/android/os/ICheckinService.aidl b/core/java/android/os/ICheckinService.aidl
new file mode 100644
index 0000000..e56b55d
--- /dev/null
+++ b/core/java/android/os/ICheckinService.aidl
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 2007 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 android.os;
+
+import android.os.IParentalControlCallback;
+
+/**
+ * System private API for direct access to the checkin service.
+ * Users should use the content provider instead.
+ *
+ * @see android.provider.Checkin
+ * {@hide}
+ */
+interface ICheckinService {
+    /** Synchronously attempt a checkin with the server, return true
+      * on success.
+      * @throws IllegalStateException whenever an error occurs.  The
+      * cause of the exception will be the real exception:
+      * IOException for network errors, JSONException for invalid
+      * server responses, etc.
+      */
+    boolean checkin();
+
+    /** Direct submission of crash data; returns after writing the crash. */
+    void reportCrashSync(in byte[] crashData);
+
+    /** Asynchronous "fire and forget" version of crash reporting. */
+    oneway void reportCrashAsync(in byte[] crashData);
+
+    /** Reboot into the recovery system and wipe all user data. */
+    void masterClear();
+
+    /**
+     * Determine if the device is under parental control. Return null if
+     * we are unable to check the parental control status.
+     */
+    void getParentalControlState(IParentalControlCallback p,
+                                 String requestingApp);
+}
diff --git a/core/java/android/os/IHardwareService.aidl b/core/java/android/os/IHardwareService.aidl
new file mode 100755
index 0000000..4f6029f
--- /dev/null
+++ b/core/java/android/os/IHardwareService.aidl
@@ -0,0 +1,40 @@
+/**
+ * Copyright (c) 2007, 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 android.os;
+
+/** {@hide} */
+interface IHardwareService
+{
+    // Vibrator support
+    void vibrate(long milliseconds);
+    void vibratePattern(in long[] pattern, int repeat, IBinder token);
+    void cancelVibrate();
+    
+    // flashlight support
+    boolean getFlashlightEnabled();
+    void setFlashlightEnabled(boolean on);
+    void enableCameraFlash(int milliseconds);
+    
+    // backlight support
+    void setScreenBacklight(int brightness);
+    void setKeyboardBacklight(boolean on);
+    void setButtonBacklight(boolean on);
+    
+    // LED support
+    void setLedState(int colorARGB, int onMS, int offMS);
+}
+
diff --git a/core/java/android/os/IInterface.java b/core/java/android/os/IInterface.java
new file mode 100644
index 0000000..2a2605a
--- /dev/null
+++ b/core/java/android/os/IInterface.java
@@ -0,0 +1,31 @@
+/*
+ * 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 android.os;
+
+/**
+ * Base class for Binder interfaces.  When defining a new interface,
+ * you must derive it from IInterface.
+ */
+public interface IInterface
+{
+    /**
+     * Retrieve the Binder object associated with this interface.
+     * You must use this instead of a plain cast, so that proxy objects
+     * can return the correct result.
+     */
+    public IBinder asBinder();
+}
diff --git a/core/java/android/os/IMessenger.aidl b/core/java/android/os/IMessenger.aidl
new file mode 100644
index 0000000..e4a8431
--- /dev/null
+++ b/core/java/android/os/IMessenger.aidl
@@ -0,0 +1,25 @@
+/* //device/java/android/android/app/IActivityPendingResult.aidl
+**
+** Copyright 2007, 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 android.os;
+
+import android.os.Message;
+
+/** @hide */
+oneway interface IMessenger {
+    void send(in Message msg);
+}
diff --git a/core/java/android/os/IMountService.aidl b/core/java/android/os/IMountService.aidl
new file mode 100644
index 0000000..88dae85
--- /dev/null
+++ b/core/java/android/os/IMountService.aidl
@@ -0,0 +1,66 @@
+/* //device/java/android/android/os/IUsb.aidl
+**
+** Copyright 2007, 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 android.os;
+
+/** WARNING! Update IMountService.h and IMountService.cpp if you change this file.
+ * In particular, the ordering of the methods below must match the 
+ * _TRANSACTION enum in IMountService.cpp
+ * @hide
+ */
+interface IMountService
+{
+    /**
+     * Is mass storage support enabled?
+     */
+    boolean getMassStorageEnabled();
+
+    /**
+     * Enable or disable mass storage support.
+     */
+    void setMassStorageEnabled(boolean enabled);
+
+    /**
+     * Is mass storage connected?
+     */
+    boolean getMassStorageConnected();
+    
+    /**
+     * Mount external storage at given mount point.
+     */
+    void mountMedia(String mountPoint);
+
+    /**
+     * Safely unmount external storage at given mount point.
+     */
+    void unmountMedia(String mountPoint);
+
+    /**
+     * Format external storage given a mount point
+     */
+    void formatMedia(String mountPoint);
+
+    /**
+     * Returns true if media notification sounds are enabled.
+     */
+    boolean getPlayNotificationSounds();
+
+    /**
+     * Sets whether or not media notification sounds are played.
+     */
+    void setPlayNotificationSounds(boolean value);
+}
diff --git a/core/java/android/os/INetStatService.aidl b/core/java/android/os/INetStatService.aidl
new file mode 100644
index 0000000..a8f3de0
--- /dev/null
+++ b/core/java/android/os/INetStatService.aidl
@@ -0,0 +1,35 @@
+/*
+ * 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 android.os;
+
+/**
+ * Retrieves packet and byte counts for the phone data interface,
+ * and for all interfaces.
+ * Used for the data activity icon and the phone status in Settings.
+ *
+ * {@hide}
+ */
+interface INetStatService {
+    long getMobileTxPackets();
+    long getMobileRxPackets();
+    long getMobileTxBytes();
+    long getMobileRxBytes();
+    long getTotalTxPackets();
+    long getTotalRxPackets();
+    long getTotalTxBytes();
+    long getTotalRxBytes();
+}
diff --git a/core/java/android/os/IParentalControlCallback.aidl b/core/java/android/os/IParentalControlCallback.aidl
new file mode 100644
index 0000000..2f1a563
--- /dev/null
+++ b/core/java/android/os/IParentalControlCallback.aidl
@@ -0,0 +1,27 @@
+/*
+ * 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 android.os;
+
+import com.google.android.net.ParentalControlState;
+
+/**
+ * This callback interface is used to deliver the parental control state to the calling application.
+ * {@hide}
+ */
+oneway interface IParentalControlCallback {
+	void onResult(in ParentalControlState state);
+}
diff --git a/core/java/android/os/IPermissionController.aidl b/core/java/android/os/IPermissionController.aidl
new file mode 100644
index 0000000..73a68f1
--- /dev/null
+++ b/core/java/android/os/IPermissionController.aidl
@@ -0,0 +1,23 @@
+/* //device/java/android/android/os/IPowerManager.aidl
+**
+** Copyright 2007, 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 android.os;
+
+/** @hide */
+interface IPermissionController {
+    boolean checkPermission(String permission, int pid, int uid);
+}
diff --git a/core/java/android/os/IPowerManager.aidl b/core/java/android/os/IPowerManager.aidl
new file mode 100644
index 0000000..5486920
--- /dev/null
+++ b/core/java/android/os/IPowerManager.aidl
@@ -0,0 +1,33 @@
+/* //device/java/android/android/os/IPowerManager.aidl
+**
+** Copyright 2007, 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 android.os;
+
+/** @hide */
+interface IPowerManager
+{
+    void acquireWakeLock(int flags, IBinder lock, String tag);
+    void goToSleep(long time);
+    void releaseWakeLock(IBinder lock);
+    void userActivity(long when, boolean noChangeLights);
+    void userActivityWithForce(long when, boolean noChangeLights, boolean force);
+    void setPokeLock(int pokey, IBinder lock, String tag);
+    void setStayOnSetting(int val);
+    long getScreenOnTime();
+    void preventScreenOn(boolean prevent);
+    void setScreenBrightnessOverride(int brightness);
+}
diff --git a/core/java/android/os/IServiceManager.java b/core/java/android/os/IServiceManager.java
new file mode 100644
index 0000000..9a5ff47
--- /dev/null
+++ b/core/java/android/os/IServiceManager.java
@@ -0,0 +1,70 @@
+/*
+ * 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 android.os;
+
+/**
+ * Basic interface for finding and publishing system services.
+ * 
+ * An implementation of this interface is usually published as the
+ * global context object, which can be retrieved via
+ * BinderNative.getContextObject().  An easy way to retrieve this
+ * is with the static method BnServiceManager.getDefault().
+ * 
+ * @hide
+ */
+public interface IServiceManager extends IInterface
+{
+    /**
+     * Retrieve an existing service called @a name from the
+     * service manager.  Blocks for a few seconds waiting for it to be
+     * published if it does not already exist.
+     */
+    public IBinder getService(String name) throws RemoteException;
+    
+    /**
+     * Retrieve an existing service called @a name from the
+     * service manager.  Non-blocking.
+     */
+    public IBinder checkService(String name) throws RemoteException;
+
+    /**
+     * Place a new @a service called @a name into the service
+     * manager.
+     */
+    public void addService(String name, IBinder service) throws RemoteException;
+
+    /**
+     * Return a list of all currently running services.
+     */
+    public String[] listServices() throws RemoteException;
+
+    /**
+     * Assign a permission controller to the service manager.  After set, this
+     * interface is checked before any services are added.
+     */
+    public void setPermissionController(IPermissionController controller)
+            throws RemoteException;
+    
+    static final String descriptor = "android.os.IServiceManager";
+
+    int GET_SERVICE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION;
+    int CHECK_SERVICE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+1;
+    int ADD_SERVICE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+2;
+    int LIST_SERVICES_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+3;
+    int CHECK_SERVICES_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+4;
+    int SET_PERMISSION_CONTROLLER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+5;
+}
diff --git a/core/java/android/os/LocalPowerManager.java b/core/java/android/os/LocalPowerManager.java
new file mode 100644
index 0000000..55d7972
--- /dev/null
+++ b/core/java/android/os/LocalPowerManager.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2007 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 android.os;
+
+/** @hide */
+public interface LocalPowerManager {
+    public static final int OTHER_EVENT = 0;
+    public static final int CHEEK_EVENT = 1;
+    public static final int TOUCH_EVENT = 2;
+    public static final int BUTTON_EVENT = 3;  // Button and trackball events.
+
+    public static final int POKE_LOCK_IGNORE_CHEEK_EVENTS = 0x1;
+    public static final int POKE_LOCK_SHORT_TIMEOUT = 0x2;
+    public static final int POKE_LOCK_MEDIUM_TIMEOUT = 0x4;
+
+    public static final int POKE_LOCK_TIMEOUT_MASK = 0x6;
+
+    void goToSleep(long time);
+    
+    // notify power manager when keyboard is opened/closed
+    void setKeyboardVisibility(boolean visible);
+
+    // when the keyguard is up, it manages the power state, and userActivity doesn't do anything.
+    void enableUserActivity(boolean enabled);
+
+    // the same as the method on PowerManager
+    public void userActivity(long time, boolean noChangeLights, int eventType);
+}
diff --git a/core/java/android/os/Looper.java b/core/java/android/os/Looper.java
new file mode 100644
index 0000000..9581893
--- /dev/null
+++ b/core/java/android/os/Looper.java
@@ -0,0 +1,226 @@
+/*
+ * 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 android.os;
+
+import android.util.Config;
+import android.util.Printer;
+
+/**
+  * Class used to run a message loop for a thread.  Threads by default do
+  * not have a message loop associated with them; to create one, call
+  * {@link #prepare} in the thread that is to run the loop, and then
+  * {@link #loop} to have it process messages until the loop is stopped.
+  * 
+  * <p>Most interaction with a message loop is through the
+  * {@link Handler} class.
+  * 
+  * <p>This is a typical example of the implementation of a Looper thread,
+  * using the separation of {@link #prepare} and {@link #loop} to create an
+  * initial Handler to communicate with the Looper.
+  * 
+  * <pre>
+  *  class LooperThread extends Thread {
+  *      public Handler mHandler;
+  *      
+  *      public void run() {
+  *          Looper.prepare();
+  *          
+  *          mHandler = new Handler() {
+  *              public void handleMessage(Message msg) {
+  *                  // process incoming messages here
+  *              }
+  *          };
+  *          
+  *          Looper.loop();
+  *      }
+  *  }</pre>
+  */
+public class Looper {
+    private static final boolean DEBUG = false;
+    private static final boolean localLOGV = DEBUG ? Config.LOGD : Config.LOGV;
+
+    // sThreadLocal.get() will return null unless you've called prepare().
+    private static final ThreadLocal sThreadLocal = new ThreadLocal();
+
+    final MessageQueue mQueue;
+    volatile boolean mRun;
+    Thread mThread;
+    private Printer mLogging = null;
+    private static Looper mMainLooper = null;
+    
+     /** Initialize the current thread as a looper.
+      * This gives you a chance to create handlers that then reference
+      * this looper, before actually starting the loop. Be sure to call
+      * {@link #loop()} after calling this method, and end it by calling
+      * {@link #quit()}.
+      */
+    public static final void prepare() {
+        if (sThreadLocal.get() != null) {
+            throw new RuntimeException("Only one Looper may be created per thread");
+        }
+        sThreadLocal.set(new Looper());
+    }
+    
+    /** Initialize the current thread as a looper, marking it as an application's main 
+     *  looper. The main looper for your application is created by the Android environment,
+     *  so you should never need to call this function yourself.
+     * {@link #prepare()}
+     */
+     
+    public static final void prepareMainLooper() {
+        prepare();
+        setMainLooper(myLooper());
+        if (Process.supportsProcesses()) {
+            myLooper().mQueue.mQuitAllowed = false;
+        }
+    }
+
+    private synchronized static void setMainLooper(Looper looper) {
+        mMainLooper = looper;
+    }
+    
+    /** Returns the application's main looper, which lives in the main thread of the application.
+     */
+    public synchronized static final Looper getMainLooper() {
+        return mMainLooper;
+    }
+
+    /**
+     *  Run the message queue in this thread. Be sure to call
+     * {@link #quit()} to end the loop.
+     */
+    public static final void loop() {
+        Looper me = myLooper();
+        MessageQueue queue = me.mQueue;
+        while (true) {
+            Message msg = queue.next(); // might block
+            //if (!me.mRun) {
+            //    break;
+            //}
+            if (msg != null) {
+                if (msg.target == null) {
+                    // No target is a magic identifier for the quit message.
+                    return;
+                }
+                if (me.mLogging!= null) me.mLogging.println(
+                        ">>>>> Dispatching to " + msg.target + " "
+                        + msg.callback + ": " + msg.what
+                        );
+                msg.target.dispatchMessage(msg);
+                if (me.mLogging!= null) me.mLogging.println(
+                        "<<<<< Finished to    " + msg.target + " "
+                        + msg.callback);
+                msg.recycle();
+            }
+        }
+    }
+
+    /**
+     * Return the Looper object associated with the current thread.  Returns
+     * null if the calling thread is not associated with a Looper.
+     */
+    public static final Looper myLooper() {
+        return (Looper)sThreadLocal.get();
+    }
+
+    /**
+     * Control logging of messages as they are processed by this Looper.  If
+     * enabled, a log message will be written to <var>printer</var> 
+     * at the beginning and ending of each message dispatch, identifying the
+     * target Handler and message contents.
+     * 
+     * @param printer A Printer object that will receive log messages, or
+     * null to disable message logging.
+     */
+    public void setMessageLogging(Printer printer) {
+        mLogging = printer;
+    }
+    
+    /**
+     * Return the {@link MessageQueue} object associated with the current
+     * thread.  This must be called from a thread running a Looper, or a
+     * NullPointerException will be thrown.
+     */
+    public static final MessageQueue myQueue() {
+        return myLooper().mQueue;
+    }
+
+    private Looper() {
+        mQueue = new MessageQueue();
+        mRun = true;
+        mThread = Thread.currentThread();
+    }
+
+    public void quit() {
+        Message msg = Message.obtain();
+        // NOTE: By enqueueing directly into the message queue, the
+        // message is left with a null target.  This is how we know it is
+        // a quit message.
+        mQueue.enqueueMessage(msg, 0);
+    }
+
+    /**
+     * Return the Thread associated with this Looper.
+     * 
+     * @since CURRENT
+     * {@hide pending API Council approval}
+     */
+    public Thread getThread() {
+        return mThread;
+    }
+    
+    public void dump(Printer pw, String prefix) {
+        pw.println(prefix + this);
+        pw.println(prefix + "mRun=" + mRun);
+        pw.println(prefix + "mThread=" + mThread);
+        pw.println(prefix + "mQueue=" + ((mQueue != null) ? mQueue : "(null"));
+        if (mQueue != null) {
+            synchronized (mQueue) {
+                Message msg = mQueue.mMessages;
+                int n = 0;
+                while (msg != null) {
+                    pw.println(prefix + "  Message " + n + ": " + msg);
+                    n++;
+                    msg = msg.next;
+                }
+                pw.println(prefix + "(Total messages: " + n + ")");
+            }
+        }
+    }
+
+    public String toString() {
+        return "Looper{"
+            + Integer.toHexString(System.identityHashCode(this))
+            + "}";
+    }
+
+    static class HandlerException extends Exception {
+
+        HandlerException(Message message, Throwable cause) {
+            super(createMessage(cause), cause);
+        }
+
+        static String createMessage(Throwable cause) {
+            String causeMsg = cause.getMessage();
+            if (causeMsg == null) {
+                causeMsg = cause.toString();
+            }
+            return causeMsg;
+        }
+    }
+}
+
diff --git a/core/java/android/os/MailboxNotAvailableException.java b/core/java/android/os/MailboxNotAvailableException.java
new file mode 100644
index 0000000..574adbd
--- /dev/null
+++ b/core/java/android/os/MailboxNotAvailableException.java
@@ -0,0 +1,37 @@
+/*
+ * 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 android.os;
+
+/** @hide */
+public class MailboxNotAvailableException extends Throwable
+{
+  /**
+   * This exception represents the case when a request for a
+   * named, published mailbox fails because the requested name has not been published
+   */
+
+    public
+    MailboxNotAvailableException()
+    {
+    }
+
+    public
+    MailboxNotAvailableException(String s)
+    {
+        super(s);
+    }
+}
diff --git a/core/java/android/os/MemoryFile.java b/core/java/android/os/MemoryFile.java
new file mode 100644
index 0000000..76e4f47
--- /dev/null
+++ b/core/java/android/os/MemoryFile.java
@@ -0,0 +1,258 @@
+/*
+ * 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 android.os;
+
+import android.util.Log;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+
+/**
+ * MemoryFile is a wrapper for the Linux ashmem driver.
+ * MemoryFiles are backed by shared memory, which can be optionally
+ * set to be purgeable.
+ * Purgeable files may have their contents reclaimed by the kernel 
+ * in low memory conditions (only if allowPurging is set to true).
+ * After a file is purged, attempts to read or write the file will
+ * cause an IOException to be thrown.
+ */
+public class MemoryFile
+{
+    private static String TAG = "MemoryFile";
+ 
+    // returns fd
+    private native int native_open(String name, int length);
+    // returns memory address for ashmem region
+    private native int native_mmap(int fd, int length);
+    private native void native_close(int fd);
+    private native int native_read(int fd, int address, byte[] buffer, 
+            int srcOffset, int destOffset, int count, boolean isUnpinned);
+    private native void native_write(int fd, int address, byte[] buffer, 
+            int srcOffset, int destOffset, int count, boolean isUnpinned);
+    private native void native_pin(int fd, boolean pin);
+
+    private int mFD;        // ashmem file descriptor
+    private int mAddress;   // address of ashmem memory
+    private int mLength;    // total length of our ashmem region
+    private boolean mAllowPurging = false;  // true if our ashmem region is unpinned
+
+    /**
+     * MemoryFile constructor.
+     *
+     * @param name optional name for the file (can be null).
+     * @param length of the memory file in bytes.
+     */
+    public MemoryFile(String name, int length) {
+        mLength = length;
+        mFD = native_open(name, length);
+        mAddress = native_mmap(mFD, length);
+    }
+
+    /**
+     * Closes and releases all resources for the memory file.
+     */
+    public void close() {
+        if (mFD > 0) {
+            native_close(mFD);
+            mFD = 0;
+        }
+    }
+
+    @Override
+    protected void finalize() {
+        if (mFD > 0) {
+            Log.e(TAG, "MemoryFile.finalize() called while ashmem still open");
+            close();
+        }
+    }
+   
+    /**
+     * Returns the length of the memory file.
+     *
+     * @return file length.
+     */
+    public int length() {
+        return mLength;
+    }
+
+    /**
+     * Is memory file purging enabled?
+     *
+     * @return true if the file may be purged.
+     */
+    public boolean isPurgingAllowed() {
+        return mAllowPurging;
+    }
+
+    /**
+     * Enables or disables purging of the memory file.
+     *
+     * @param allowPurging true if the operating system can purge the contents
+     * of the file in low memory situations
+     * @return previous value of allowPurging
+     */
+    synchronized public boolean allowPurging(boolean allowPurging) throws IOException {
+        boolean oldValue = mAllowPurging;
+        if (oldValue != allowPurging) {
+            native_pin(mFD, !allowPurging);
+            mAllowPurging = allowPurging;
+        }
+        return oldValue;
+    }
+
+    /**
+     * Creates a new InputStream for reading from the memory file.
+     *
+     @return InputStream
+     */
+    public InputStream getInputStream() {
+        return new MemoryInputStream();
+    }
+
+    /**
+     * Creates a new OutputStream for writing to the memory file.
+     *
+     @return OutputStream
+     */
+     public OutputStream getOutputStream() {
+
+        return new MemoryOutputStream();
+    }
+
+    /**
+     * Reads bytes from the memory file.
+     * Will throw an IOException if the file has been purged.
+     *
+     * @param buffer byte array to read bytes into.
+     * @param srcOffset offset into the memory file to read from.
+     * @param destOffset offset into the byte array buffer to read into.
+     * @param count number of bytes to read.
+     * @return number of bytes read.
+     */
+    public int readBytes(byte[] buffer, int srcOffset, int destOffset, int count) 
+            throws IOException {
+        if (destOffset < 0 || destOffset > buffer.length || count < 0
+                || count > buffer.length - destOffset
+                || srcOffset < 0 || srcOffset > mLength
+                || count > mLength - srcOffset) {
+            throw new IndexOutOfBoundsException();
+        }
+        return native_read(mFD, mAddress, buffer, srcOffset, destOffset, count, mAllowPurging);
+    }
+
+    /**
+     * Write bytes to the memory file.
+     * Will throw an IOException if the file has been purged.
+     *
+     * @param buffer byte array to write bytes from.
+     * @param srcOffset offset into the byte array buffer to write from.
+     * @param destOffset offset  into the memory file to write to.
+     * @param count number of bytes to write.
+     */
+    public void writeBytes(byte[] buffer, int srcOffset, int destOffset, int count)
+            throws IOException {
+        if (srcOffset < 0 || srcOffset > buffer.length || count < 0
+                || count > buffer.length - srcOffset
+                || destOffset < 0 || destOffset > mLength
+                || count > mLength - destOffset) {
+            throw new IndexOutOfBoundsException();
+        }
+        native_write(mFD, mAddress, buffer, srcOffset, destOffset, count, mAllowPurging);
+    }
+
+    private class MemoryInputStream extends InputStream {
+
+        private int mMark = 0;
+        private int mOffset = 0;
+        private byte[] mSingleByte;
+
+        @Override
+        public int available() throws IOException {
+            if (mOffset >= mLength) {
+                return 0;
+            }
+            return mLength - mOffset;
+        }
+
+        @Override
+        public boolean markSupported() {
+            return true;
+        }
+
+        @Override
+        public void mark(int readlimit) {
+            mMark = mOffset;
+        }
+
+        @Override
+        public void reset() throws IOException {
+            mOffset = mMark;
+        }
+
+        @Override
+        public int read() throws IOException {
+            if (mSingleByte == null) {
+                mSingleByte = new byte[1];
+            }
+            int result = read(mSingleByte, 0, 1);
+            if (result != 1) {
+                throw new IOException("read() failed");
+            }
+            return mSingleByte[0];
+        }
+
+        @Override
+        public int read(byte buffer[], int offset, int count) throws IOException {
+            int result = readBytes(buffer, mOffset, offset, count);
+            if (result > 0) {
+                mOffset += result;
+            }
+            return result;
+        }
+
+        @Override
+        public long skip(long n) throws IOException {
+            if (mOffset + n > mLength) {
+                n = mLength - mOffset;
+            }
+            mOffset += n;
+            return n;
+        }
+    }
+
+    private class MemoryOutputStream extends OutputStream {
+
+        private int mOffset = 0;
+        private byte[] mSingleByte;
+
+        @Override
+        public void write(byte buffer[], int offset, int count) throws IOException {
+            writeBytes(buffer, offset, mOffset, count);
+        }
+
+        @Override
+        public void write(int oneByte) throws IOException {
+            if (mSingleByte == null) {
+                mSingleByte = new byte[1];
+            }
+            mSingleByte[0] = (byte)oneByte;
+            write(mSingleByte, 0, 1);
+        }
+    }
+}
diff --git a/core/java/android/os/Message.aidl b/core/java/android/os/Message.aidl
new file mode 100644
index 0000000..e8dbb5a
--- /dev/null
+++ b/core/java/android/os/Message.aidl
@@ -0,0 +1,20 @@
+/* //device/java/android/android/content/Intent.aidl
+**
+** Copyright 2007, 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 android.os;
+
+parcelable Message;
diff --git a/core/java/android/os/Message.java b/core/java/android/os/Message.java
new file mode 100644
index 0000000..4130109
--- /dev/null
+++ b/core/java/android/os/Message.java
@@ -0,0 +1,405 @@
+/*
+ * 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 android.os;
+
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * 
+ * Defines a message containing a description and arbitrary data object that can be
+ * sent to a {@link Handler}.  This object contains two extra int fields and an
+ * extra object field that allow you to not do allocations in many cases.  
+ *
+ * <p class="note">While the constructor of Message is public, the best way to get
+ * one of these is to call {@link #obtain Message.obtain()} or one of the
+ * {@link Handler#obtainMessage Handler.obtainMessage()} methods, which will pull
+ * them from a pool of recycled objects.</p>
+ */
+public final class Message implements Parcelable {
+    /**
+     * User-defined message code so that the recipient can identify 
+     * what this message is about. Each {@link Handler} has its own name-space
+     * for message codes, so you do not need to worry about yours conflicting
+     * with other handlers.
+     */
+    public int what;
+
+    // Use these fields instead of using the class's Bundle if you can. 
+    /** arg1 and arg2 are lower-cost alternatives to using {@link #setData(Bundle) setData()}
+    if you only need to store a few integer values. */
+    public int arg1; 
+
+    /** arg1 and arg2 are lower-cost alternatives to using {@link #setData(Bundle) setData()}
+    if you only need to store a few integer values.*/ 
+    public int arg2;
+
+    /** An arbitrary object to send to the recipient.  This must be null when
+     * sending messages across processes. */
+    public Object obj;
+
+    /** Optional Messenger where replies to this message can be sent.
+     */
+    public Messenger replyTo;
+    
+    /*package*/ long when;
+    
+    /*package*/ Bundle data;
+    
+    /*package*/ Handler target;     
+    
+    /*package*/ Runnable callback;   
+    
+    // sometimes we store linked lists of these things
+    /*package*/ Message next;
+
+    private static Object mPoolSync = new Object();
+    private static Message mPool;
+    private static int mPoolSize = 0;
+
+    private static final int MAX_POOL_SIZE = 10;
+    
+    /**
+     * Return a new Message instance from the global pool. Allows us to
+     * avoid allocating new objects in many cases.
+     */
+    public static Message obtain() {
+        synchronized (mPoolSync) {
+            if (mPool != null) {
+                Message m = mPool;
+                mPool = m.next;
+                m.next = null;
+                return m;
+            }
+        }
+        return new Message();
+    }
+
+    /**
+     * Same as {@link #obtain()}, but copies the values of an existing
+     * message (including its target) into the new one.
+     * @param orig Original message to copy.
+     * @return A Message object from the global pool.
+     */
+    public static Message obtain(Message orig) {
+        Message m = obtain();
+        m.what = orig.what;
+        m.arg1 = orig.arg1;
+        m.arg2 = orig.arg2;
+        m.obj = orig.obj;
+        m.replyTo = orig.replyTo;
+        if (orig.data != null) {
+            m.data = new Bundle(orig.data);
+        }
+        m.target = orig.target;
+        m.callback = orig.callback;
+
+        return m;
+    }
+
+    /**
+     * Same as {@link #obtain()}, but sets the value for the <em>target</em> member on the Message returned.
+     * @param h  Handler to assign to the returned Message object's <em>target</em> member.
+     * @return A Message object from the global pool.
+     */
+    public static Message obtain(Handler h) {
+        Message m = obtain();
+        m.target = h;
+
+        return m;
+    }
+
+    /**
+     * Same as {@link #obtain(Handler)}, but assigns a callback Runnable on
+     * the Message that is returned.
+     * @param h  Handler to assign to the returned Message object's <em>target</em> member.
+     * @param callback Runnable that will execute when the message is handled.
+     * @return A Message object from the global pool.
+     */
+    public static Message obtain(Handler h, Runnable callback) {
+        Message m = obtain();
+        m.target = h;
+        m.callback = callback;
+
+        return m;
+    }
+
+    /**
+     * Same as {@link #obtain()}, but sets the values for both <em>target</em> and
+     * <em>what</em> members on the Message.
+     * @param h  Value to assign to the <em>target</em> member.
+     * @param what  Value to assign to the <em>what</em> member.
+     * @return A Message object from the global pool.
+     */
+    public static Message obtain(Handler h, int what) {
+        Message m = obtain();
+        m.target = h;
+        m.what = what;
+
+        return m;
+    }
+
+    /**
+     * Same as {@link #obtain()}, but sets the values of the <em>target</em>, <em>what</em>, and <em>obj</em>
+     * members.
+     * @param h  The <em>target</em> value to set.
+     * @param what  The <em>what</em> value to set.
+     * @param obj  The <em>object</em> method to set.
+     * @return  A Message object from the global pool.
+     */
+    public static Message obtain(Handler h, int what, Object obj) {
+        Message m = obtain();
+        m.target = h;
+        m.what = what;
+        m.obj = obj;
+
+        return m;
+    }
+
+    /**
+     * Same as {@link #obtain()}, but sets the values of the <em>target</em>, <em>what</em>, 
+     * <em>arg1</em>, and <em>arg2</em> members.
+     * 
+     * @param h  The <em>target</em> value to set.
+     * @param what  The <em>what</em> value to set.
+     * @param arg1  The <em>arg1</em> value to set.
+     * @param arg2  The <em>arg2</em> value to set.
+     * @return  A Message object from the global pool.
+     */
+    public static Message obtain(Handler h, int what, int arg1, int arg2) {
+        Message m = obtain();
+        m.target = h;
+        m.what = what;
+        m.arg1 = arg1;
+        m.arg2 = arg2;
+
+        return m;
+    }
+
+    /**
+     * Same as {@link #obtain()}, but sets the values of the <em>target</em>, <em>what</em>, 
+     * <em>arg1</em>, <em>arg2</em>, and <em>obj</em> members.
+     * 
+     * @param h  The <em>target</em> value to set.
+     * @param what  The <em>what</em> value to set.
+     * @param arg1  The <em>arg1</em> value to set.
+     * @param arg2  The <em>arg2</em> value to set.
+     * @param obj  The <em>obj</em> value to set.
+     * @return  A Message object from the global pool.
+     */
+    public static Message obtain(Handler h, int what, 
+            int arg1, int arg2, Object obj) {
+        Message m = obtain();
+        m.target = h;
+        m.what = what;
+        m.arg1 = arg1;
+        m.arg2 = arg2;
+        m.obj = obj;
+
+        return m;
+    }
+
+    /**
+     * Return a Message instance to the global pool.  You MUST NOT touch
+     * the Message after calling this function -- it has effectively been
+     * freed.
+     */
+    public void recycle() {
+        synchronized (mPoolSync) {
+            if (mPoolSize < MAX_POOL_SIZE) {
+                clearForRecycle();
+                
+                next = mPool;
+                mPool = this;
+            }
+        }
+    }
+
+    /**
+     * Make this message like o.  Performs a shallow copy of the data field.
+     * Does not copy the linked list fields, nor the timestamp or
+     * target/callback of the original message.
+     */
+    public void copyFrom(Message o) {
+        this.what = o.what;
+        this.arg1 = o.arg1;
+        this.arg2 = o.arg2;
+        this.obj = o.obj;
+        this.replyTo = o.replyTo;
+
+        if (o.data != null) {
+            this.data = (Bundle) o.data.clone();
+        } else {
+            this.data = null;
+        }
+    }
+
+    /**
+     * Return the targeted delivery time of this message, in milliseconds.
+     */
+    public long getWhen() {
+        return when;
+    }
+    
+    public void setTarget(Handler target) {
+        this.target = target;
+    }
+
+    /**
+     * Retrieve the a {@link android.os.Handler Handler} implementation that
+     * will receive this message. The object must implement
+     * {@link android.os.Handler#handleMessage(android.os.Message)
+     * Handler.handleMessage()}. Each Handler has its own name-space for
+     * message codes, so you do not need to
+     * worry about yours conflicting with other handlers.
+     */
+    public Handler getTarget() {
+        return target;
+    }
+
+    /**
+     * Retrieve callback object that will execute when this message is handled.
+     * This object must implement Runnable. This is called by
+     * the <em>target</em> {@link Handler} that is receiving this Message to
+     * dispatch it.  If
+     * not set, the message will be dispatched to the receiving Handler's
+     * {@link Handler#handleMessage(Message Handler.handleMessage())}. */
+    public Runnable getCallback() {
+        return callback;
+    }
+    
+    /** 
+     * Obtains a Bundle of arbitrary data associated with this
+     * event, lazily creating it if necessary. Set this value by calling {@link #setData(Bundle)}.
+     */
+    public Bundle getData() {
+        if (data == null) {
+            data = new Bundle();
+        }
+        
+        return data;
+    }
+
+    /** 
+     * Like getData(), but does not lazily create the Bundle.  A null
+     * is returned if the Bundle does not already exist.
+     */
+    public Bundle peekData() {
+        return data;
+    }
+
+    /** Sets a Bundle of arbitrary data values. Use arg1 and arg1 members 
+     * as a lower cost way to send a few simple integer values, if you can. */
+    public void setData(Bundle data) {
+        this.data = data;
+    }
+
+    /**
+     * Sends this Message to the Handler specified by {@link #getTarget}.
+     * Throws a null pointer exception if this field has not been set.
+     */
+    public void sendToTarget() {
+        target.sendMessage(this);
+    }
+
+    /*package*/ void clearForRecycle() {
+        what = 0;
+        arg1 = 0;
+        arg2 = 0;
+        obj = null;
+        replyTo = null;
+        when = 0;
+        target = null;
+        callback = null;
+        data = null;
+    }
+
+    /** Constructor (but the preferred way to get a Message is to call {@link #obtain() Message.obtain()}).
+    */
+    public Message() {
+    }
+
+    public String toString() {
+        StringBuilder   b = new StringBuilder();
+        
+        b.append("{ what=");
+        b.append(what);
+
+        b.append(" when=");
+        b.append(when);
+
+        if (arg1 != 0) {
+            b.append(" arg1=");
+            b.append(arg1);
+        }
+
+        if (arg2 != 0) {
+            b.append(" arg2=");
+            b.append(arg2);
+        }
+
+        if (obj != null) {
+            b.append(" obj=");
+            b.append(obj);
+        }
+
+        b.append(" }");
+        
+        return b.toString();
+    }
+
+    public static final Parcelable.Creator<Message> CREATOR
+            = new Parcelable.Creator<Message>() {
+        public Message createFromParcel(Parcel source) {
+            Message msg = Message.obtain();
+            msg.readFromParcel(source);
+            return msg;
+        }
+        
+        public Message[] newArray(int size) {
+            return new Message[size];
+        }
+    };
+        
+    public int describeContents() {
+        return 0;
+    }
+
+    public void writeToParcel(Parcel dest, int flags) {
+        if (obj != null || callback != null) {
+            throw new RuntimeException(
+                "Can't marshal objects across processes.");
+        }
+        dest.writeInt(what);
+        dest.writeInt(arg1);
+        dest.writeInt(arg2);
+        dest.writeLong(when);
+        dest.writeBundle(data);
+        Messenger.writeMessengerOrNullToParcel(replyTo, dest);
+    }
+
+    private final void readFromParcel(Parcel source) {
+        what = source.readInt();
+        arg1 = source.readInt();
+        arg2 = source.readInt();
+        when = source.readLong();
+        data = source.readBundle();
+        replyTo = Messenger.readMessengerOrNullFromParcel(source);
+    }
+}
+
diff --git a/core/java/android/os/MessageQueue.java b/core/java/android/os/MessageQueue.java
new file mode 100644
index 0000000..caf0923
--- /dev/null
+++ b/core/java/android/os/MessageQueue.java
@@ -0,0 +1,329 @@
+/*
+ * 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 android.os;
+
+import java.util.ArrayList;
+
+import android.util.AndroidRuntimeException;
+import android.util.Config;
+import android.util.Log;
+
+import com.android.internal.os.RuntimeInit;
+
+/**
+ * Low-level class holding the list of messages to be dispatched by a
+ * {@link Looper}.  Messages are not added directly to a MessageQueue,
+ * but rather through {@link Handler} objects associated with the Looper.
+ * 
+ * <p>You can retrieve the MessageQueue for the current thread with
+ * {@link Looper#myQueue() Looper.myQueue()}.
+ */
+public class MessageQueue {
+    Message mMessages;
+    private final ArrayList mIdleHandlers = new ArrayList();
+    private boolean mQuiting = false;
+    boolean mQuitAllowed = true;
+    
+    /**
+     * Callback interface for discovering when a thread is going to block
+     * waiting for more messages.
+     */
+    public static interface IdleHandler {
+        /**
+         * Called when the message queue has run out of messages and will now
+         * wait for more.  Return true to keep your idle handler active, false
+         * to have it removed.  This may be called if there are still messages
+         * pending in the queue, but they are all scheduled to be dispatched
+         * after the current time.
+         */
+        boolean queueIdle();
+    }
+
+    /**
+     * Add a new {@link IdleHandler} to this message queue.  This may be
+     * removed automatically for you by returning false from
+     * {@link IdleHandler#queueIdle IdleHandler.queueIdle()} when it is
+     * invoked, or explicitly removing it with {@link #removeIdleHandler}.
+     * 
+     * <p>This method is safe to call from any thread.
+     * 
+     * @param handler The IdleHandler to be added.
+     */
+    public final void addIdleHandler(IdleHandler handler) {
+        if (handler == null) {
+            throw new NullPointerException("Can't add a null IdleHandler");
+        }
+        synchronized (this) {
+            mIdleHandlers.add(handler);
+        }
+    }
+
+    /**
+     * Remove an {@link IdleHandler} from the queue that was previously added
+     * with {@link #addIdleHandler}.  If the given object is not currently
+     * in the idle list, nothing is done.
+     * 
+     * @param handler The IdleHandler to be removed.
+     */
+    public final void removeIdleHandler(IdleHandler handler) {
+        synchronized (this) {
+            mIdleHandlers.remove(handler);
+        }
+    }
+
+    MessageQueue() {
+    }
+
+    final Message next() {
+        boolean tryIdle = true;
+
+        while (true) {
+            long now;
+            Object[] idlers = null;
+    
+            // Try to retrieve the next message, returning if found.
+            synchronized (this) {
+                now = SystemClock.uptimeMillis();
+                Message msg = pullNextLocked(now);
+                if (msg != null) return msg;
+                if (tryIdle && mIdleHandlers.size() > 0) {
+                    idlers = mIdleHandlers.toArray();
+                }
+            }
+    
+            // There was no message so we are going to wait...  but first,
+            // if there are any idle handlers let them know.
+            boolean didIdle = false;
+            if (idlers != null) {
+                for (Object idler : idlers) {
+                    boolean keep = false;
+                    try {
+                        didIdle = true;
+                        keep = ((IdleHandler)idler).queueIdle();
+                    } catch (Throwable t) {
+                        Log.e("MessageQueue",
+                              "IdleHandler threw exception", t);
+                        RuntimeInit.crash("MessageQueue", t);
+                    }
+
+                    if (!keep) {
+                        synchronized (this) {
+                            mIdleHandlers.remove(idler);
+                        }
+                    }
+                }
+            }
+            
+            // While calling an idle handler, a new message could have been
+            // delivered...  so go back and look again for a pending message.
+            if (didIdle) {
+                tryIdle = false;
+                continue;
+            }
+
+            synchronized (this) {
+                // No messages, nobody to tell about it...  time to wait!
+                try {
+                    if (mMessages != null) {
+                        if (mMessages.when-now > 0) {
+                            Binder.flushPendingCommands();
+                            this.wait(mMessages.when-now);
+                        }
+                    } else {
+                        Binder.flushPendingCommands();
+                        this.wait();
+                    }
+                }
+                catch (InterruptedException e) {
+                }
+            }
+        }
+    }
+
+    final Message pullNextLocked(long now) {
+        Message msg = mMessages;
+        if (msg != null) {
+            if (now >= msg.when) {
+                mMessages = msg.next;
+                if (Config.LOGV) Log.v(
+                    "MessageQueue", "Returning message: " + msg);
+                return msg;
+            }
+        }
+
+        return null;
+    }
+
+    final boolean enqueueMessage(Message msg, long when) {
+        if (msg.when != 0) {
+            throw new AndroidRuntimeException(msg
+                    + " This message is already in use.");
+        }
+        if (msg.target == null && !mQuitAllowed) {
+            throw new RuntimeException("Main thread not allowed to quit");
+        }
+        synchronized (this) {
+            if (mQuiting) {
+                RuntimeException e = new RuntimeException(
+                    msg.target + " sending message to a Handler on a dead thread");
+                Log.w("MessageQueue", e.getMessage(), e);
+                return false;
+            } else if (msg.target == null) {
+                mQuiting = true;
+            }
+
+            msg.when = when;
+            //Log.d("MessageQueue", "Enqueing: " + msg);
+            Message p = mMessages;
+            if (p == null || when == 0 || when < p.when) {
+                msg.next = p;
+                mMessages = msg;
+                this.notify();
+            } else {
+                Message prev = null;
+                while (p != null && p.when <= when) {
+                    prev = p;
+                    p = p.next;
+                }
+                msg.next = prev.next;
+                prev.next = msg;
+                this.notify();
+            }
+        }
+        return true;
+    }
+
+    final boolean removeMessages(Handler h, int what, Object object,
+            boolean doRemove) {
+        synchronized (this) {
+            Message p = mMessages;
+            boolean found = false;
+
+            // Remove all messages at front.
+            while (p != null && p.target == h && p.what == what
+                   && (object == null || p.obj == object)) {
+                if (!doRemove) return true;
+                found = true;
+                Message n = p.next;
+                mMessages = n;
+                p.recycle();
+                p = n;
+            }
+
+            // Remove all messages after front.
+            while (p != null) {
+                Message n = p.next;
+                if (n != null) {
+                    if (n.target == h && n.what == what
+                        && (object == null || n.obj == object)) {
+                        if (!doRemove) return true;
+                        found = true;
+                        Message nn = n.next;
+                        n.recycle();
+                        p.next = nn;
+                        continue;
+                    }
+                }
+                p = n;
+            }
+            
+            return found;
+        }
+    }
+
+    final void removeMessages(Handler h, Runnable r, Object object) {
+        if (r == null) {
+            return;
+        }
+
+        synchronized (this) {
+            Message p = mMessages;
+
+            // Remove all messages at front.
+            while (p != null && p.target == h && p.callback == r
+                   && (object == null || p.obj == object)) {
+                Message n = p.next;
+                mMessages = n;
+                p.recycle();
+                p = n;
+            }
+
+            // Remove all messages after front.
+            while (p != null) {
+                Message n = p.next;
+                if (n != null) {
+                    if (n.target == h && n.callback == r
+                        && (object == null || n.obj == object)) {
+                        Message nn = n.next;
+                        n.recycle();
+                        p.next = nn;
+                        continue;
+                    }
+                }
+                p = n;
+            }
+        }
+    }
+
+    final void removeCallbacksAndMessages(Handler h, Object object) {
+        synchronized (this) {
+            Message p = mMessages;
+
+            // Remove all messages at front.
+            while (p != null && p.target == h
+                    && (object == null || p.obj == object)) {
+                Message n = p.next;
+                mMessages = n;
+                p.recycle();
+                p = n;
+            }
+
+            // Remove all messages after front.
+            while (p != null) {
+                Message n = p.next;
+                if (n != null) {
+                    if (n.target == h && (object == null || n.obj == object)) {
+                        Message nn = n.next;
+                        n.recycle();
+                        p.next = nn;
+                        continue;
+                    }
+                }
+                p = n;
+            }
+        }
+    }
+
+    /*
+    private void dumpQueue_l()
+    {
+        Message p = mMessages;
+        System.out.println(this + "  queue is:");
+        while (p != null) {
+            System.out.println("            " + p);
+            p = p.next;
+        }
+    }
+    */
+
+    void poke()
+    {
+        synchronized (this) {
+            this.notify();
+        }
+    }
+}
diff --git a/core/java/android/os/Messenger.aidl b/core/java/android/os/Messenger.aidl
new file mode 100644
index 0000000..e6b8886
--- /dev/null
+++ b/core/java/android/os/Messenger.aidl
@@ -0,0 +1,20 @@
+/* //device/java/android/android/content/Intent.aidl
+**
+** Copyright 2007, 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 android.os;
+
+parcelable Messenger;
diff --git a/core/java/android/os/Messenger.java b/core/java/android/os/Messenger.java
new file mode 100644
index 0000000..1bc554e
--- /dev/null
+++ b/core/java/android/os/Messenger.java
@@ -0,0 +1,141 @@
+/*
+ * 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 android.os;
+
+/**
+ * Reference to a Handler, which others can use to send messages to it.
+ * This allows for the implementation of message-based communication across
+ * processes, by creating a Messenger pointing to a Handler in one process,
+ * and handing that Messenger to another process.
+ */
+public final class Messenger implements Parcelable {
+    private final IMessenger mTarget;
+
+    /**
+     * Create a new Messenger pointing to the given Handler.  Any Message
+     * objects sent through this Messenger will appear in the Handler as if
+     * {@link Handler#sendMessage(Message) Handler.sendMessage(Message)} had
+     * be called directly.
+     * 
+     * @param target The Handler that will receive sent messages.
+     */
+    public Messenger(Handler target) {
+        mTarget = target.getIMessenger();
+    }
+    
+    /**
+     * Send a Message to this Messenger's Handler.
+     * 
+     * @param message The Message to send.  Usually retrieved through
+     * {@link Message#obtain() Message.obtain()}.
+     * 
+     * @throws RemoteException Throws DeadObjectException if the target
+     * Handler no longer exists.
+     */
+    public void send(Message message) throws RemoteException {
+        mTarget.send(message);
+    }
+    
+    /**
+     * Retrieve the IBinder that this Messenger is using to communicate with
+     * its associated Handler.
+     * 
+     * @return Returns the IBinder backing this Messenger.
+     */
+    public IBinder getBinder() {
+        return mTarget.asBinder();
+    }
+    
+    /**
+     * Comparison operator on two Messenger objects, such that true
+     * is returned then they both point to the same Handler.
+     */
+    public boolean equals(Object otherObj) {
+        if (otherObj == null) {
+            return false;
+        }
+        try {
+            return mTarget.asBinder().equals(((Messenger)otherObj)
+                    .mTarget.asBinder());
+        } catch (ClassCastException e) {
+        }
+        return false;
+    }
+
+    public int hashCode() {
+        return mTarget.asBinder().hashCode();
+    }
+    
+    public int describeContents() {
+        return 0;
+    }
+
+    public void writeToParcel(Parcel out, int flags) {
+        out.writeStrongBinder(mTarget.asBinder());
+    }
+
+    public static final Parcelable.Creator<Messenger> CREATOR
+            = new Parcelable.Creator<Messenger>() {
+        public Messenger createFromParcel(Parcel in) {
+            IBinder target = in.readStrongBinder();
+            return target != null ? new Messenger(target) : null;
+        }
+
+        public Messenger[] newArray(int size) {
+            return new Messenger[size];
+        }
+    };
+
+    /**
+     * Convenience function for writing either a Messenger or null pointer to
+     * a Parcel.  You must use this with {@link #readMessengerOrNullFromParcel}
+     * for later reading it.
+     * 
+     * @param messenger The Messenger to write, or null.
+     * @param out Where to write the Messenger.
+     */
+    public static void writeMessengerOrNullToParcel(Messenger messenger,
+            Parcel out) {
+        out.writeStrongBinder(messenger != null ? messenger.mTarget.asBinder()
+                : null);
+    }
+    
+    /**
+     * Convenience function for reading either a Messenger or null pointer from
+     * a Parcel.  You must have previously written the Messenger with
+     * {@link #writeMessengerOrNullToParcel}.
+     * 
+     * @param in The Parcel containing the written Messenger.
+     * 
+     * @return Returns the Messenger read from the Parcel, or null if null had
+     * been written.
+     */
+    public static Messenger readMessengerOrNullFromParcel(Parcel in) {
+        IBinder b = in.readStrongBinder();
+        return b != null ? new Messenger(b) : null;
+    }
+    
+    /**
+     * Create a Messenger from a raw IBinder, which had previously been
+     * retrieved with {@link #getBinder}.
+     * 
+     * @param target The IBinder this Messenger should communicate with.
+     */
+    public Messenger(IBinder target) {
+        mTarget = IMessenger.Stub.asInterface(target);
+    }
+}
diff --git a/core/java/android/os/NetStat.java b/core/java/android/os/NetStat.java
new file mode 100644
index 0000000..e294cdf
--- /dev/null
+++ b/core/java/android/os/NetStat.java
@@ -0,0 +1,247 @@
+/*
+ * Copyright (C) 2007 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 android.os;
+
+import android.util.Log;
+
+import java.io.File;
+import java.io.RandomAccessFile;
+import java.io.IOException;
+
+/** @hide */
+public class NetStat {
+
+    // Logging tag.
+    private final static String TAG = "netstat";
+
+    // We pre-create all the File objects so we don't spend a lot of
+    // CPU at runtime converting from Java Strings to byte[] for the
+    // kernel calls.
+    private final static File[] MOBILE_TX_PACKETS = mobileFiles("tx_packets");
+    private final static File[] MOBILE_RX_PACKETS = mobileFiles("rx_packets");
+    private final static File[] MOBILE_TX_BYTES = mobileFiles("tx_bytes");
+    private final static File[] MOBILE_RX_BYTES = mobileFiles("rx_bytes");
+    private final static File SYS_CLASS_NET_DIR = new File("/sys/class/net");
+
+    /**
+     * Get total number of tx packets sent through rmnet0 or ppp0
+     *
+     * @return number of Tx packets through rmnet0 or ppp0
+     */
+    public static long getMobileTxPkts() {
+        return getMobileStat(MOBILE_TX_PACKETS);
+    }
+
+    /**
+     *  Get total number of rx packets received through rmnet0 or ppp0
+     *
+     * @return number of Rx packets through rmnet0 or ppp0
+     */
+    public static long getMobileRxPkts() {
+        return getMobileStat(MOBILE_RX_PACKETS);
+    }
+
+    /**
+     *  Get total number of tx bytes received through rmnet0 or ppp0
+     *
+     * @return number of Tx bytes through rmnet0 or ppp0
+     */
+      public static long getMobileTxBytes() {
+          return getMobileStat(MOBILE_TX_BYTES);
+      }
+
+    /**
+     *  Get total number of rx bytes received through rmnet0 or ppp0
+     *
+     * @return number of Rx bytes through rmnet0 or ppp0
+     */
+    public static long getMobileRxBytes() {
+        return getMobileStat(MOBILE_RX_BYTES);
+    }
+
+    /**
+     * Get the total number of packets sent through all network interfaces.
+     *
+     * @return the number of packets sent through all network interfaces
+     */
+    public static long getTotalTxPkts() {
+        return getTotalStat("tx_packets");
+    }
+
+    /**
+     * Get the total number of packets received through all network interfaces.
+     *
+     * @return the number of packets received through all network interfaces
+     */
+    public static long getTotalRxPkts() {
+        return getTotalStat("rx_packets");
+    }
+
+    /**
+     * Get the total number of bytes sent through all network interfaces.
+     *
+     * @return the number of bytes sent through all network interfaces
+     */
+    public static long getTotalTxBytes() {
+        return getTotalStat("tx_bytes");
+    }
+
+    /**
+     * Get the total number of bytes received through all network interfaces.
+     *
+     * @return the number of bytes received through all network interfaces
+     */
+    public static long getTotalRxBytes() {
+        return getTotalStat("rx_bytes");
+    }
+
+    /**
+     * Gets network bytes sent for this UID.
+     * The statistics are across all interfaces.
+     * The statistics come from /proc/uid_stat.
+     *
+     * {@see android.os.Process#myUid()}.
+     *
+     * @param uid
+     * @return byte count
+     */
+    public static long getUidTxBytes(int uid) {
+        return getNumberFromFilePath("/proc/uid_stat/" + uid + "/tcp_snd");
+    }
+
+    /**
+     * Gets network bytes received for this UID.
+     * The statistics are across all interfaces.
+     * The statistics come from /proc/uid_stat.
+     *
+     * {@see android.os.Process#myUid()}.
+     *
+     * @param uid
+     * @return byte count
+     */
+    public static long getUidRxBytes(int uid) {
+        return getNumberFromFilePath("/proc/uid_stat/" + uid + "/tcp_rcv");
+    }
+
+    /**
+     * Returns the array of two possible File locations for a given
+     * statistic.
+     */
+    private static File[] mobileFiles(String whatStat) {
+        // Note that we stat them at runtime to see which is
+        // available, rather than here, to guard against the files
+        // coming & going later as modules shut down (e.g. airplane
+        // mode) and whatnot.  The runtime stat() isn't expensive compared
+        // to the previous charset conversion that happened before we
+        // were reusing File instances.
+        File[] files = new File[2];
+        files[0] = new File("/sys/class/net/rmnet0/statistics/" + whatStat);
+        files[1] = new File("/sys/class/net/ppp0/statistics/" + whatStat);
+        return files;
+    }
+
+    private static long getTotalStat(String whatStat) {
+        File netdir = new File("/sys/class/net");
+
+        File[] nets = SYS_CLASS_NET_DIR.listFiles();
+        if (nets == null) {
+            return 0;
+        }
+        long total = 0;
+        StringBuffer strbuf = new StringBuffer();
+        for (File net : nets) {
+            strbuf.append(net.getPath()).append(File.separator).append("statistics")
+                    .append(File.separator).append(whatStat);
+            total += getNumberFromFilePath(strbuf.toString());
+            strbuf.setLength(0);
+        }
+        return total;
+    }
+
+    private static long getMobileStat(File[] files) {
+        for (int i = 0; i < files.length; i++) {
+            File file = files[i];
+            if (!file.exists()) {
+                continue;
+            }
+            try {
+                RandomAccessFile raf = new RandomAccessFile(file, "r");
+                return getNumberFromFile(raf, file.getAbsolutePath());
+            } catch (IOException e) {
+                Log.w(TAG,
+                      "Exception opening TCP statistics file " + file.getAbsolutePath(),
+                      e);
+            }
+        }
+        return 0L;
+    }
+
+    // File will have format <number><newline>
+    private static long getNumberFromFilePath(String filename) {
+        RandomAccessFile raf = getFile(filename);
+        if (raf == null) {
+            return 0L;
+        }
+        return getNumberFromFile(raf, filename);
+    }
+
+    // Private buffer for getNumberFromFile.  Safe for re-use because
+    // getNumberFromFile is synchronized.
+    private final static byte[] buf = new byte[16];
+
+    private static synchronized long getNumberFromFile(RandomAccessFile raf, String filename) {
+        try {
+            raf.read(buf);
+            raf.close();
+        } catch (IOException e) {
+            Log.w(TAG, "Exception getting TCP bytes from " + filename, e);
+            return 0L;
+        } finally {
+            if (raf != null) {
+                try {
+                    raf.close();
+                } catch (IOException e) {
+                    Log.w(TAG, "Exception closing " + filename, e);
+                }
+            }
+        }
+
+        long num = 0L;
+        for (int i = 0; i < buf.length; i++) {
+            if (buf[i] < '0' || buf[i] > '9') {
+                break;
+            }
+            num *= 10;
+            num += buf[i] - '0';
+        }
+        return num;
+    }
+
+    private static RandomAccessFile getFile(String filename) {
+        File f = new File(filename);
+        if (!f.canRead()) {
+            return null;
+        }
+
+        try {
+            return new RandomAccessFile(f, "r");
+        } catch (IOException e) {
+            Log.w(TAG, "Exception opening TCP statistics file " + filename, e);
+            return null;
+        }
+    }
+}
diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java
new file mode 100644
index 0000000..9a71f6e
--- /dev/null
+++ b/core/java/android/os/Parcel.java
@@ -0,0 +1,2051 @@
+/*
+ * 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 android.os;
+
+import android.text.TextUtils;
+import android.util.Log;
+import android.util.SparseArray;
+import android.util.SparseBooleanArray;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.FileDescriptor;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.io.Serializable;
+import java.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Container for a message (data and object references) that can
+ * be sent through an IBinder.  A Parcel can contain both flattened data
+ * that will be unflattened on the other side of the IPC (using the various
+ * methods here for writing specific types, or the general
+ * {@link Parcelable} interface), and references to live {@link IBinder}
+ * objects that will result in the other side receiving a proxy IBinder
+ * connected with the original IBinder in the Parcel.
+ *
+ * <p class="note">Parcel is <strong>not</strong> a general-purpose
+ * serialization mechanism.  This class (and the corresponding
+ * {@link Parcelable} API for placing arbitrary objects into a Parcel) is
+ * designed as a high-performance IPC transport.  As such, it is not
+ * appropriate to place any Parcel data in to persistent storage: changes
+ * in the underlying implementation of any of the data in the Parcel can
+ * render older data unreadable.</p>
+ * 
+ * <p>The bulk of the Parcel API revolves around reading and writing data
+ * of various types.  There are six major classes of such functions available.</p>
+ * 
+ * <h3>Primitives</h3>
+ * 
+ * <p>The most basic data functions are for writing and reading primitive
+ * data types: {@link #writeByte}, {@link #readByte}, {@link #writeDouble},
+ * {@link #readDouble}, {@link #writeFloat}, {@link #readFloat}, {@link #writeInt},
+ * {@link #readInt}, {@link #writeLong}, {@link #readLong},
+ * {@link #writeString}, {@link #readString}.  Most other
+ * data operations are built on top of these.  The given data is written and
+ * read using the endianess of the host CPU.</p>
+ * 
+ * <h3>Primitive Arrays</h3>
+ * 
+ * <p>There are a variety of methods for reading and writing raw arrays
+ * of primitive objects, which generally result in writing a 4-byte length
+ * followed by the primitive data items.  The methods for reading can either
+ * read the data into an existing array, or create and return a new array.
+ * These available types are:</p>
+ * 
+ * <ul>
+ * <li> {@link #writeBooleanArray(boolean[])},
+ * {@link #readBooleanArray(boolean[])}, {@link #createBooleanArray()}
+ * <li> {@link #writeByteArray(byte[])},
+ * {@link #writeByteArray(byte[], int, int)}, {@link #readByteArray(byte[])},
+ * {@link #createByteArray()}
+ * <li> {@link #writeCharArray(char[])}, {@link #readCharArray(char[])},
+ * {@link #createCharArray()}
+ * <li> {@link #writeDoubleArray(double[])}, {@link #readDoubleArray(double[])},
+ * {@link #createDoubleArray()}
+ * <li> {@link #writeFloatArray(float[])}, {@link #readFloatArray(float[])},
+ * {@link #createFloatArray()}
+ * <li> {@link #writeIntArray(int[])}, {@link #readIntArray(int[])},
+ * {@link #createIntArray()}
+ * <li> {@link #writeLongArray(long[])}, {@link #readLongArray(long[])},
+ * {@link #createLongArray()}
+ * <li> {@link #writeStringArray(String[])}, {@link #readStringArray(String[])},
+ * {@link #createStringArray()}.
+ * <li> {@link #writeSparseBooleanArray(SparseBooleanArray)},
+ * {@link #readSparseBooleanArray()}.
+ * </ul>
+ * 
+ * <h3>Parcelables</h3>
+ * 
+ * <p>The {@link Parcelable} protocol provides an extremely efficient (but
+ * low-level) protocol for objects to write and read themselves from Parcels.
+ * You can use the direct methods {@link #writeParcelable(Parcelable, int)}
+ * and {@link #readParcelable(ClassLoader)} or
+ * {@link #writeParcelableArray} and
+ * {@link #readParcelableArray(ClassLoader)} to write or read.  These
+ * methods write both the class type and its data to the Parcel, allowing
+ * that class to be reconstructed from the appropriate class loader when
+ * later reading.</p>
+ * 
+ * <p>There are also some methods that provide a more efficient way to work
+ * with Parcelables: {@link #writeTypedArray},
+ * {@link #writeTypedList(List)},
+ * {@link #readTypedArray} and {@link #readTypedList}.  These methods
+ * do not write the class information of the original object: instead, the
+ * caller of the read function must know what type to expect and pass in the
+ * appropriate {@link Parcelable.Creator Parcelable.Creator} instead to
+ * properly construct the new object and read its data.  (To more efficient
+ * write and read a single Parceable object, you can directly call
+ * {@link Parcelable#writeToParcel Parcelable.writeToParcel} and
+ * {@link Parcelable.Creator#createFromParcel Parcelable.Creator.createFromParcel}
+ * yourself.)</p>
+ * 
+ * <h3>Bundles</h3>
+ * 
+ * <p>A special type-safe container, called {@link Bundle}, is available
+ * for key/value maps of heterogeneous values.  This has many optimizations
+ * for improved performance when reading and writing data, and its type-safe
+ * API avoids difficult to debug type errors when finally marshalling the
+ * data contents into a Parcel.  The methods to use are
+ * {@link #writeBundle(Bundle)}, {@link #readBundle()}, and
+ * {@link #readBundle(ClassLoader)}.
+ * 
+ * <h3>Active Objects</h3>
+ * 
+ * <p>An unusual feature of Parcel is the ability to read and write active
+ * objects.  For these objects the actual contents of the object is not
+ * written, rather a special token referencing the object is written.  When
+ * reading the object back from the Parcel, you do not get a new instance of
+ * the object, but rather a handle that operates on the exact same object that
+ * was originally written.  There are two forms of active objects available.</p>
+ * 
+ * <p>{@link Binder} objects are a core facility of Android's general cross-process
+ * communication system.  The {@link IBinder} interface describes an abstract
+ * protocol with a Binder object.  Any such interface can be written in to
+ * a Parcel, and upon reading you will receive either the original object
+ * implementing that interface or a special proxy implementation
+ * that communicates calls back to the original object.  The methods to use are
+ * {@link #writeStrongBinder(IBinder)},
+ * {@link #writeStrongInterface(IInterface)}, {@link #readStrongBinder()},
+ * {@link #writeBinderArray(IBinder[])}, {@link #readBinderArray(IBinder[])},
+ * {@link #createBinderArray()},
+ * {@link #writeBinderList(List)}, {@link #readBinderList(List)},
+ * {@link #createBinderArrayList()}.</p>
+ * 
+ * <p>FileDescriptor objects, representing raw Linux file descriptor identifiers,
+ * can be written and {@link ParcelFileDescriptor} objects returned to operate
+ * on the original file descriptor.  The returned file descriptor is a dup
+ * of the original file descriptor: the object and fd is different, but
+ * operating on the same underlying file stream, with the same position, etc.
+ * The methods to use are {@link #writeFileDescriptor(FileDescriptor)},
+ * {@link #readFileDescriptor()}.
+ * 
+ * <h3>Untyped Containers</h3>
+ * 
+ * <p>A final class of methods are for writing and reading standard Java
+ * containers of arbitrary types.  These all revolve around the
+ * {@link #writeValue(Object)} and {@link #readValue(ClassLoader)} methods
+ * which define the types of objects allowed.  The container methods are
+ * {@link #writeArray(Object[])}, {@link #readArray(ClassLoader)},
+ * {@link #writeList(List)}, {@link #readList(List, ClassLoader)},
+ * {@link #readArrayList(ClassLoader)},
+ * {@link #writeMap(Map)}, {@link #readMap(Map, ClassLoader)},
+ * {@link #writeSparseArray(SparseArray)},
+ * {@link #readSparseArray(ClassLoader)}.
+ */
+public final class Parcel {
+    private static final boolean DEBUG_RECYCLE = false;
+
+    @SuppressWarnings({"UnusedDeclaration"})
+    private int mObject; // used by native code
+    @SuppressWarnings({"UnusedDeclaration"})
+    private int mOwnObject; // used by native code
+    private RuntimeException mStack;
+
+    private static final int POOL_SIZE = 6;
+    private static final Parcel[] sOwnedPool = new Parcel[POOL_SIZE];
+    private static final Parcel[] sHolderPool = new Parcel[POOL_SIZE];
+
+    private static final int VAL_NULL = -1;
+    private static final int VAL_STRING = 0;
+    private static final int VAL_INTEGER = 1;
+    private static final int VAL_MAP = 2;
+    private static final int VAL_BUNDLE = 3;
+    private static final int VAL_PARCELABLE = 4;
+    private static final int VAL_SHORT = 5;
+    private static final int VAL_LONG = 6;
+    private static final int VAL_FLOAT = 7;
+    private static final int VAL_DOUBLE = 8;
+    private static final int VAL_BOOLEAN = 9;
+    private static final int VAL_CHARSEQUENCE = 10;
+    private static final int VAL_LIST  = 11;
+    private static final int VAL_SPARSEARRAY = 12;
+    private static final int VAL_BYTEARRAY = 13;
+    private static final int VAL_STRINGARRAY = 14;
+    private static final int VAL_IBINDER = 15;
+    private static final int VAL_PARCELABLEARRAY = 16;
+    private static final int VAL_OBJECTARRAY = 17;
+    private static final int VAL_INTARRAY = 18;
+    private static final int VAL_LONGARRAY = 19;
+    private static final int VAL_BYTE = 20;
+    private static final int VAL_SERIALIZABLE = 21;
+    private static final int VAL_SPARSEBOOLEANARRAY = 22;
+    private static final int VAL_BOOLEANARRAY = 23;
+
+    private static final int EX_SECURITY = -1;
+    private static final int EX_BAD_PARCELABLE = -2;
+    private static final int EX_ILLEGAL_ARGUMENT = -3;
+    private static final int EX_NULL_POINTER = -4;
+    private static final int EX_ILLEGAL_STATE = -5;
+    
+    public final static Parcelable.Creator<String> STRING_CREATOR
+             = new Parcelable.Creator<String>() {
+        public String createFromParcel(Parcel source) {
+            return source.readString();
+        }
+        public String[] newArray(int size) {
+            return new String[size];
+        }
+    };
+
+    /**
+     * Retrieve a new Parcel object from the pool.
+     */
+    public static Parcel obtain() {
+        final Parcel[] pool = sOwnedPool;
+        synchronized (pool) {
+            Parcel p;
+            for (int i=0; i<POOL_SIZE; i++) {
+                p = pool[i];
+                if (p != null) {
+                    pool[i] = null;
+                    if (DEBUG_RECYCLE) {
+                        p.mStack = new RuntimeException();
+                    }
+                    return p;
+                }
+            }
+        }
+        return new Parcel(0);
+    }
+
+    /**
+     * Put a Parcel object back into the pool.  You must not touch
+     * the object after this call.
+     */
+    public final void recycle() {
+        if (DEBUG_RECYCLE) mStack = null;
+        freeBuffer();
+        final Parcel[] pool = mOwnObject != 0 ? sOwnedPool : sHolderPool;
+        synchronized (pool) {
+            for (int i=0; i<POOL_SIZE; i++) {
+                if (pool[i] == null) {
+                    pool[i] = this;
+                    return;
+                }
+            }
+        }
+    }
+
+    /**
+     * Returns the total amount of data contained in the parcel.
+     */
+    public final native int dataSize();
+
+    /**
+     * Returns the amount of data remaining to be read from the
+     * parcel.  That is, {@link #dataSize}-{@link #dataPosition}.
+     */
+    public final native int dataAvail();
+
+    /**
+     * Returns the current position in the parcel data.  Never
+     * more than {@link #dataSize}.
+     */
+    public final native int dataPosition();
+
+    /**
+     * Returns the total amount of space in the parcel.  This is always
+     * >= {@link #dataSize}.  The difference between it and dataSize() is the
+     * amount of room left until the parcel needs to re-allocate its
+     * data buffer.
+     */
+    public final native int dataCapacity();
+
+    /**
+     * Change the amount of data in the parcel.  Can be either smaller or
+     * larger than the current size.  If larger than the current capacity,
+     * more memory will be allocated.
+     *
+     * @param size The new number of bytes in the Parcel.
+     */
+    public final native void setDataSize(int size);
+
+    /**
+     * Move the current read/write position in the parcel.
+     * @param pos New offset in the parcel; must be between 0 and
+     * {@link #dataSize}.
+     */
+    public final native void setDataPosition(int pos);
+
+    /**
+     * Change the capacity (current available space) of the parcel.
+     *
+     * @param size The new capacity of the parcel, in bytes.  Can not be
+     * less than {@link #dataSize} -- that is, you can not drop existing data
+     * with this method.
+     */
+    public final native void setDataCapacity(int size);
+
+    /**
+     * Returns the raw bytes of the parcel.
+     *
+     * <p class="note">The data you retrieve here <strong>must not</strong>
+     * be placed in any kind of persistent storage (on local disk, across
+     * a network, etc).  For that, you should use standard serialization
+     * or another kind of general serialization mechanism.  The Parcel
+     * marshalled representation is highly optimized for local IPC, and as
+     * such does not attempt to maintain compatibility with data created
+     * in different versions of the platform.
+     */
+    public final native byte[] marshall();
+
+    /**
+     * Set the bytes in data to be the raw bytes of this Parcel.
+     */
+    public final native void unmarshall(byte[] data, int offest, int length);
+
+    public final native void appendFrom(Parcel parcel, int offset, int length);
+
+    /**
+     * Report whether the parcel contains any marshalled file descriptors.
+     */
+    public final native boolean hasFileDescriptors();
+
+    /**
+     * Store or read an IBinder interface token in the parcel at the current
+     * {@link #dataPosition}.  This is used to validate that the marshalled
+     * transaction is intended for the target interface.
+     */
+    public final native void writeInterfaceToken(String interfaceName);
+    public final native void enforceInterface(String interfaceName);
+
+    /**
+     * Write a byte array into the parcel at the current {#link #dataPosition},
+     * growing {@link #dataCapacity} if needed.
+     * @param b Bytes to place into the parcel.
+     */
+    public final void writeByteArray(byte[] b) {
+        writeByteArray(b, 0, (b != null) ? b.length : 0);
+    }
+
+    /**
+     * Write an byte array into the parcel at the current {#link #dataPosition},
+     * growing {@link #dataCapacity} if needed.
+     * @param b Bytes to place into the parcel.
+     * @param offset Index of first byte to be written.
+     * @param len Number of bytes to write.
+     */
+    public final void writeByteArray(byte[] b, int offset, int len) {
+        if (b == null) {
+            writeInt(-1);
+            return;
+        }
+        if (b.length < offset + len || len < 0 || offset < 0) {
+            throw new ArrayIndexOutOfBoundsException();
+        }
+        writeNative(b, offset, len);
+    }
+
+    private native void writeNative(byte[] b, int offset, int len);
+
+    /**
+     * Write an integer value into the parcel at the current dataPosition(),
+     * growing dataCapacity() if needed.
+     */
+    public final native void writeInt(int val);
+
+    /**
+     * Write a long integer value into the parcel at the current dataPosition(),
+     * growing dataCapacity() if needed.
+     */
+    public final native void writeLong(long val);
+
+    /**
+     * Write a floating point value into the parcel at the current
+     * dataPosition(), growing dataCapacity() if needed.
+     */
+    public final native void writeFloat(float val);
+
+    /**
+     * Write a double precision floating point value into the parcel at the
+     * current dataPosition(), growing dataCapacity() if needed.
+     */
+    public final native void writeDouble(double val);
+
+    /**
+     * Write a string value into the parcel at the current dataPosition(),
+     * growing dataCapacity() if needed.
+     */
+    public final native void writeString(String val);
+
+    /**
+     * Write an object into the parcel at the current dataPosition(),
+     * growing dataCapacity() if needed.
+     */
+    public final native void writeStrongBinder(IBinder val);
+
+    /**
+     * Write an object into the parcel at the current dataPosition(),
+     * growing dataCapacity() if needed.
+     */
+    public final void writeStrongInterface(IInterface val) {
+        writeStrongBinder(val == null ? null : val.asBinder());
+    }
+
+    /**
+     * Write a FileDescriptor into the parcel at the current dataPosition(),
+     * growing dataCapacity() if needed.
+     */
+    public final native void writeFileDescriptor(FileDescriptor val);
+
+    /**
+     * Write an byte value into the parcel at the current dataPosition(),
+     * growing dataCapacity() if needed.
+     */
+    public final void writeByte(byte val) {
+        writeInt(val);
+    }
+
+    /**
+     * Please use {@link #writeBundle} instead.  Flattens a Map into the parcel
+     * at the current dataPosition(),
+     * growing dataCapacity() if needed.  The Map keys must be String objects.
+     * The Map values are written using {@link #writeValue} and must follow
+     * the specification there.
+     * 
+     * <p>It is strongly recommended to use {@link #writeBundle} instead of
+     * this method, since the Bundle class provides a type-safe API that
+     * allows you to avoid mysterious type errors at the point of marshalling.
+     */
+    public final void writeMap(Map val) {
+        writeMapInternal((Map<String,Object>) val);
+    }
+
+    /**
+     * Flatten a Map into the parcel at the current dataPosition(),
+     * growing dataCapacity() if needed.  The Map keys must be String objects.
+     */
+    private void writeMapInternal(Map<String,Object> val) {
+        if (val == null) {
+            writeInt(-1);
+            return;
+        }
+        Set<Map.Entry<String,Object>> entries = val.entrySet();
+        writeInt(entries.size());
+        for (Map.Entry<String,Object> e : entries) {
+            writeValue(e.getKey());
+            writeValue(e.getValue());
+        }
+    }
+
+    /**
+     * Flatten a Bundle into the parcel at the current dataPosition(),
+     * growing dataCapacity() if needed.
+     */
+    public final void writeBundle(Bundle val) {
+        if (val == null) {
+            writeInt(-1);
+            return;
+        }
+
+        if (val.mParcelledData != null) {
+            int length = val.mParcelledData.dataSize();
+            appendFrom(val.mParcelledData, 0, length);
+        } else {
+            writeInt(-1); // dummy, will hold length
+            int oldPos = dataPosition();
+            writeInt(0x4C444E42); // 'B' 'N' 'D' 'L'
+
+            writeMapInternal(val.mMap);
+            int newPos = dataPosition();
+
+            // Backpatch length
+            setDataPosition(oldPos - 4);
+            int length = newPos - oldPos;
+            writeInt(length);
+            setDataPosition(newPos);
+        }
+    }
+
+    /**
+     * Flatten a List into the parcel at the current dataPosition(), growing
+     * dataCapacity() if needed.  The List values are written using
+     * {@link #writeValue} and must follow the specification there.
+     */
+    public final void writeList(List val) {
+        if (val == null) {
+            writeInt(-1);
+            return;
+        }
+        int N = val.size();
+        int i=0;
+        writeInt(N);
+        while (i < N) {
+            writeValue(val.get(i));
+            i++;
+        }
+    }
+
+    /**
+     * Flatten an Object array into the parcel at the current dataPosition(),
+     * growing dataCapacity() if needed.  The array values are written using
+     * {@link #writeValue} and must follow the specification there.
+     */
+    public final void writeArray(Object[] val) {
+        if (val == null) {
+            writeInt(-1);
+            return;
+        }
+        int N = val.length;
+        int i=0;
+        writeInt(N);
+        while (i < N) {
+            writeValue(val[i]);
+            i++;
+        }
+    }
+
+    /**
+     * Flatten a generic SparseArray into the parcel at the current
+     * dataPosition(), growing dataCapacity() if needed.  The SparseArray
+     * values are written using {@link #writeValue} and must follow the
+     * specification there.
+     */
+    public final void writeSparseArray(SparseArray<Object> val) {
+        if (val == null) {
+            writeInt(-1);
+            return;
+        }
+        int N = val.size();
+        writeInt(N);
+        int i=0;
+        while (i < N) {
+            writeInt(val.keyAt(i));
+            writeValue(val.valueAt(i));
+            i++;
+        }
+    }
+
+    public final void writeSparseBooleanArray(SparseBooleanArray val) {
+        if (val == null) {
+            writeInt(-1);
+            return;
+        }
+        int N = val.size();
+        writeInt(N);
+        int i=0;
+        while (i < N) {
+            writeInt(val.keyAt(i));
+            writeByte((byte)(val.valueAt(i) ? 1 : 0));
+            i++;
+        }
+    }
+
+    public final void writeBooleanArray(boolean[] val) {
+        if (val != null) {
+            int N = val.length;
+            writeInt(N);
+            for (int i=0; i<N; i++) {
+                writeInt(val[i] ? 1 : 0);
+            }
+        } else {
+            writeInt(-1);
+        }
+    }
+
+    public final boolean[] createBooleanArray() {
+        int N = readInt();
+        // >>2 as a fast divide-by-4 works in the create*Array() functions
+        // because dataAvail() will never return a negative number.  4 is
+        // the size of a stored boolean in the stream.
+        if (N >= 0 && N <= (dataAvail() >> 2)) {
+            boolean[] val = new boolean[N];
+            for (int i=0; i<N; i++) {
+                val[i] = readInt() != 0;
+            }
+            return val;
+        } else {
+            return null;
+        }
+    }
+
+    public final void readBooleanArray(boolean[] val) {
+        int N = readInt();
+        if (N == val.length) {
+            for (int i=0; i<N; i++) {
+                val[i] = readInt() != 0;
+            }
+        } else {
+            throw new RuntimeException("bad array lengths");
+        }
+    }
+
+    public final void writeCharArray(char[] val) {
+        if (val != null) {
+            int N = val.length;
+            writeInt(N);
+            for (int i=0; i<N; i++) {
+                writeInt((int)val[i]);
+            }
+        } else {
+            writeInt(-1);
+        }
+    }
+
+    public final char[] createCharArray() {
+        int N = readInt();
+        if (N >= 0 && N <= (dataAvail() >> 2)) {
+            char[] val = new char[N];
+            for (int i=0; i<N; i++) {
+                val[i] = (char)readInt();
+            }
+            return val;
+        } else {
+            return null;
+        }
+    }
+
+    public final void readCharArray(char[] val) {
+        int N = readInt();
+        if (N == val.length) {
+            for (int i=0; i<N; i++) {
+                val[i] = (char)readInt();
+            }
+        } else {
+            throw new RuntimeException("bad array lengths");
+        }
+    }
+
+    public final void writeIntArray(int[] val) {
+        if (val != null) {
+            int N = val.length;
+            writeInt(N);
+            for (int i=0; i<N; i++) {
+                writeInt(val[i]);
+            }
+        } else {
+            writeInt(-1);
+        }
+    }
+
+    public final int[] createIntArray() {
+        int N = readInt();
+        if (N >= 0 && N <= (dataAvail() >> 2)) {
+            int[] val = new int[N];
+            for (int i=0; i<N; i++) {
+                val[i] = readInt();
+            }
+            return val;
+        } else {
+            return null;
+        }
+    }
+
+    public final void readIntArray(int[] val) {
+        int N = readInt();
+        if (N == val.length) {
+            for (int i=0; i<N; i++) {
+                val[i] = readInt();
+            }
+        } else {
+            throw new RuntimeException("bad array lengths");
+        }
+    }
+
+    public final void writeLongArray(long[] val) {
+        if (val != null) {
+            int N = val.length;
+            writeInt(N);
+            for (int i=0; i<N; i++) {
+                writeLong(val[i]);
+            }
+        } else {
+            writeInt(-1);
+        }
+    }
+
+    public final long[] createLongArray() {
+        int N = readInt();
+        // >>3 because stored longs are 64 bits
+        if (N >= 0 && N <= (dataAvail() >> 3)) {
+            long[] val = new long[N];
+            for (int i=0; i<N; i++) {
+                val[i] = readLong();
+            }
+            return val;
+        } else {
+            return null;
+        }
+    }
+
+    public final void readLongArray(long[] val) {
+        int N = readInt();
+        if (N == val.length) {
+            for (int i=0; i<N; i++) {
+                val[i] = readLong();
+            }
+        } else {
+            throw new RuntimeException("bad array lengths");
+        }
+    }
+
+    public final void writeFloatArray(float[] val) {
+        if (val != null) {
+            int N = val.length;
+            writeInt(N);
+            for (int i=0; i<N; i++) {
+                writeFloat(val[i]);
+            }
+        } else {
+            writeInt(-1);
+        }
+    }
+
+    public final float[] createFloatArray() {
+        int N = readInt();
+        // >>2 because stored floats are 4 bytes
+        if (N >= 0 && N <= (dataAvail() >> 2)) {
+            float[] val = new float[N];
+            for (int i=0; i<N; i++) {
+                val[i] = readFloat();
+            }
+            return val;
+        } else {
+            return null;
+        }
+    }
+
+    public final void readFloatArray(float[] val) {
+        int N = readInt();
+        if (N == val.length) {
+            for (int i=0; i<N; i++) {
+                val[i] = readFloat();
+            }
+        } else {
+            throw new RuntimeException("bad array lengths");
+        }
+    }
+
+    public final void writeDoubleArray(double[] val) {
+        if (val != null) {
+            int N = val.length;
+            writeInt(N);
+            for (int i=0; i<N; i++) {
+                writeDouble(val[i]);
+            }
+        } else {
+            writeInt(-1);
+        }
+    }
+
+    public final double[] createDoubleArray() {
+        int N = readInt();
+        // >>3 because stored doubles are 8 bytes
+        if (N >= 0 && N <= (dataAvail() >> 3)) {
+            double[] val = new double[N];
+            for (int i=0; i<N; i++) {
+                val[i] = readDouble();
+            }
+            return val;
+        } else {
+            return null;
+        }
+    }
+
+    public final void readDoubleArray(double[] val) {
+        int N = readInt();
+        if (N == val.length) {
+            for (int i=0; i<N; i++) {
+                val[i] = readDouble();
+            }
+        } else {
+            throw new RuntimeException("bad array lengths");
+        }
+    }
+
+    public final void writeStringArray(String[] val) {
+        if (val != null) {
+            int N = val.length;
+            writeInt(N);
+            for (int i=0; i<N; i++) {
+                writeString(val[i]);
+            }
+        } else {
+            writeInt(-1);
+        }
+    }
+
+    public final String[] createStringArray() {
+        int N = readInt();
+        if (N >= 0) {
+            String[] val = new String[N];
+            for (int i=0; i<N; i++) {
+                val[i] = readString();
+            }
+            return val;
+        } else {
+            return null;
+        }
+    }
+
+    public final void readStringArray(String[] val) {
+        int N = readInt();
+        if (N == val.length) {
+            for (int i=0; i<N; i++) {
+                val[i] = readString();
+            }
+        } else {
+            throw new RuntimeException("bad array lengths");
+        }
+    }
+
+    public final void writeBinderArray(IBinder[] val) {
+        if (val != null) {
+            int N = val.length;
+            writeInt(N);
+            for (int i=0; i<N; i++) {
+                writeStrongBinder(val[i]);
+            }
+        } else {
+            writeInt(-1);
+        }
+    }
+
+    public final IBinder[] createBinderArray() {
+        int N = readInt();
+        if (N >= 0) {
+            IBinder[] val = new IBinder[N];
+            for (int i=0; i<N; i++) {
+                val[i] = readStrongBinder();
+            }
+            return val;
+        } else {
+            return null;
+        }
+    }
+
+    public final void readBinderArray(IBinder[] val) {
+        int N = readInt();
+        if (N == val.length) {
+            for (int i=0; i<N; i++) {
+                val[i] = readStrongBinder();
+            }
+        } else {
+            throw new RuntimeException("bad array lengths");
+        }
+    }
+
+    /**
+     * Flatten a List containing a particular object type into the parcel, at
+     * the current dataPosition() and growing dataCapacity() if needed.  The
+     * type of the objects in the list must be one that implements Parcelable.
+     * Unlike the generic writeList() method, however, only the raw data of the
+     * objects is written and not their type, so you must use the corresponding
+     * readTypedList() to unmarshall them.
+     *
+     * @param val The list of objects to be written.
+     *
+     * @see #createTypedArrayList
+     * @see #readTypedList
+     * @see Parcelable
+     */
+    public final <T extends Parcelable> void writeTypedList(List<T> val) {
+        if (val == null) {
+            writeInt(-1);
+            return;
+        }
+        int N = val.size();
+        int i=0;
+        writeInt(N);
+        while (i < N) {
+            T item = val.get(i);
+            if (item != null) {
+                writeInt(1);
+                item.writeToParcel(this, 0);
+            } else {
+                writeInt(0);
+            }
+            i++;
+        }
+    }
+
+    /**
+     * Flatten a List containing String objects into the parcel, at
+     * the current dataPosition() and growing dataCapacity() if needed.  They
+     * can later be retrieved with {@link #createStringArrayList} or
+     * {@link #readStringList}.
+     *
+     * @param val The list of strings to be written.
+     *
+     * @see #createStringArrayList
+     * @see #readStringList
+     */
+    public final void writeStringList(List<String> val) {
+        if (val == null) {
+            writeInt(-1);
+            return;
+        }
+        int N = val.size();
+        int i=0;
+        writeInt(N);
+        while (i < N) {
+            writeString(val.get(i));
+            i++;
+        }
+    }
+
+    /**
+     * Flatten a List containing IBinder objects into the parcel, at
+     * the current dataPosition() and growing dataCapacity() if needed.  They
+     * can later be retrieved with {@link #createBinderArrayList} or
+     * {@link #readBinderList}.
+     *
+     * @param val The list of strings to be written.
+     *
+     * @see #createBinderArrayList
+     * @see #readBinderList
+     */
+    public final void writeBinderList(List<IBinder> val) {
+        if (val == null) {
+            writeInt(-1);
+            return;
+        }
+        int N = val.size();
+        int i=0;
+        writeInt(N);
+        while (i < N) {
+            writeStrongBinder(val.get(i));
+            i++;
+        }
+    }
+
+    /**
+     * Flatten a heterogeneous array containing a particular object type into
+     * the parcel, at
+     * the current dataPosition() and growing dataCapacity() if needed.  The
+     * type of the objects in the array must be one that implements Parcelable.
+     * Unlike the {@link #writeParcelableArray} method, however, only the
+     * raw data of the objects is written and not their type, so you must use
+     * {@link #readTypedArray} with the correct corresponding
+     * {@link Parcelable.Creator} implementation to unmarshall them.
+     *
+     * @param val The array of objects to be written.
+     * @param parcelableFlags Contextual flags as per
+     * {@link Parcelable#writeToParcel(Parcel, int) Parcelable.writeToParcel()}.
+     *
+     * @see #readTypedArray
+     * @see #writeParcelableArray
+     * @see Parcelable.Creator
+     */
+    public final <T extends Parcelable> void writeTypedArray(T[] val,
+            int parcelableFlags) {
+        if (val != null) {
+            int N = val.length;
+            writeInt(N);
+            for (int i=0; i<N; i++) {
+                T item = val[i];
+                if (item != null) {
+                    writeInt(1);
+                    item.writeToParcel(this, parcelableFlags);
+                } else {
+                    writeInt(0);
+                }
+            }
+        } else {
+            writeInt(-1);
+        }
+    }
+
+    /**
+     * Flatten a generic object in to a parcel.  The given Object value may
+     * currently be one of the following types:
+     * 
+     * <ul>
+     * <li> null
+     * <li> String
+     * <li> Byte
+     * <li> Short
+     * <li> Integer
+     * <li> Long
+     * <li> Float
+     * <li> Double
+     * <li> Boolean
+     * <li> String[]
+     * <li> boolean[]
+     * <li> byte[]
+     * <li> int[]
+     * <li> long[]
+     * <li> Object[] (supporting objects of the same type defined here).
+     * <li> {@link Bundle}
+     * <li> Map (as supported by {@link #writeMap}).
+     * <li> Any object that implements the {@link Parcelable} protocol.
+     * <li> Parcelable[]
+     * <li> CharSequence (as supported by {@link TextUtils#writeToParcel}).
+     * <li> List (as supported by {@link #writeList}).
+     * <li> {@link SparseArray} (as supported by {@link #writeSparseArray}).
+     * <li> {@link IBinder}
+     * <li> Any object that implements Serializable (but see
+     *      {@link #writeSerializable} for caveats).  Note that all of the
+     *      previous types have relatively efficient implementations for
+     *      writing to a Parcel; having to rely on the generic serialization
+     *      approach is much less efficient and should be avoided whenever
+     *      possible.
+     * </ul>
+     */
+    public final void writeValue(Object v) {
+        if (v == null) {
+            writeInt(VAL_NULL);
+        } else if (v instanceof String) {
+            writeInt(VAL_STRING);
+            writeString((String) v);
+        } else if (v instanceof Integer) {
+            writeInt(VAL_INTEGER);
+            writeInt((Integer) v);
+        } else if (v instanceof Map) {
+            writeInt(VAL_MAP);
+            writeMap((Map) v);
+        } else if (v instanceof Bundle) {
+            // Must be before Parcelable
+            writeInt(VAL_BUNDLE);
+            writeBundle((Bundle) v);
+        } else if (v instanceof Parcelable) {
+            writeInt(VAL_PARCELABLE);
+            writeParcelable((Parcelable) v, 0);
+        } else if (v instanceof Short) {
+            writeInt(VAL_SHORT);
+            writeInt(((Short) v).intValue());
+        } else if (v instanceof Long) {
+            writeInt(VAL_LONG);
+            writeLong((Long) v);
+        } else if (v instanceof Float) {
+            writeInt(VAL_FLOAT);
+            writeFloat((Float) v);
+        } else if (v instanceof Double) {
+            writeInt(VAL_DOUBLE);
+            writeDouble((Double) v);
+        } else if (v instanceof Boolean) {
+            writeInt(VAL_BOOLEAN);
+            writeInt((Boolean) v ? 1 : 0);
+        } else if (v instanceof CharSequence) {
+            // Must be after String
+            writeInt(VAL_CHARSEQUENCE);
+            TextUtils.writeToParcel((CharSequence) v, this, 0);
+        } else if (v instanceof List) {
+            writeInt(VAL_LIST);
+            writeList((List) v);
+        } else if (v instanceof SparseArray) {
+            writeInt(VAL_SPARSEARRAY);
+            writeSparseArray((SparseArray) v);
+        } else if (v instanceof boolean[]) {
+            writeInt(VAL_BOOLEANARRAY);
+            writeBooleanArray((boolean[]) v);
+        } else if (v instanceof byte[]) {
+            writeInt(VAL_BYTEARRAY);
+            writeByteArray((byte[]) v);
+        } else if (v instanceof String[]) {
+            writeInt(VAL_STRINGARRAY);
+            writeStringArray((String[]) v);
+        } else if (v instanceof IBinder) {
+            writeInt(VAL_IBINDER);
+            writeStrongBinder((IBinder) v);
+        } else if (v instanceof Parcelable[]) {
+            writeInt(VAL_PARCELABLEARRAY);
+            writeParcelableArray((Parcelable[]) v, 0);
+        } else if (v instanceof Object[]) {
+            writeInt(VAL_OBJECTARRAY);
+            writeArray((Object[]) v);
+        } else if (v instanceof int[]) {
+            writeInt(VAL_INTARRAY);
+            writeIntArray((int[]) v);
+        } else if (v instanceof long[]) {
+            writeInt(VAL_LONGARRAY);
+            writeLongArray((long[]) v);
+        } else if (v instanceof Byte) {
+            writeInt(VAL_BYTE);
+            writeInt((Byte) v);
+        } else if (v instanceof Serializable) {
+            // Must be last
+            writeInt(VAL_SERIALIZABLE);
+            writeSerializable((Serializable) v);
+        } else {
+            throw new RuntimeException("Parcel: unable to marshal value " + v);
+        }
+    }
+
+    /**
+     * Flatten the name of the class of the Parcelable and its contents
+     * into the parcel.
+     * 
+     * @param p The Parcelable object to be written.
+     * @param parcelableFlags Contextual flags as per
+     * {@link Parcelable#writeToParcel(Parcel, int) Parcelable.writeToParcel()}.
+     */
+    public final void writeParcelable(Parcelable p, int parcelableFlags) {
+        if (p == null) {
+            writeString(null);
+            return;
+        }
+        String name = p.getClass().getName();
+        writeString(name);
+        p.writeToParcel(this, parcelableFlags);
+    }
+
+    /**
+     * Write a generic serializable object in to a Parcel.  It is strongly
+     * recommended that this method be avoided, since the serialization
+     * overhead is extremely large, and this approach will be much slower than
+     * using the other approaches to writing data in to a Parcel.
+     */
+    public final void writeSerializable(Serializable s) {
+        if (s == null) {
+            writeString(null);
+            return;
+        }
+        String name = s.getClass().getName();
+        writeString(name);
+
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        try {
+            ObjectOutputStream oos = new ObjectOutputStream(baos);
+            oos.writeObject(s);
+            oos.close();
+
+            writeByteArray(baos.toByteArray());
+        } catch (IOException ioe) {
+            throw new RuntimeException("Parcelable encountered " +
+                "IOException writing serializable object (name = " + name +
+                ")", ioe);
+        }
+    }
+
+    /**
+     * Special function for writing an exception result at the header of
+     * a parcel, to be used when returning an exception from a transaction.
+     * Note that this currently only supports a few exception types; any other
+     * exception will be re-thrown by this function as a RuntimeException
+     * (to be caught by the system's last-resort exception handling when
+     * dispatching a transaction).
+     * 
+     * <p>The supported exception types are:
+     * <ul>
+     * <li>{@link BadParcelableException}
+     * <li>{@link IllegalArgumentException}
+     * <li>{@link IllegalStateException}
+     * <li>{@link NullPointerException}
+     * <li>{@link SecurityException}
+     * </ul>
+     * 
+     * @param e The Exception to be written.
+     *
+     * @see #writeNoException
+     * @see #readException
+     */
+    public final void writeException(Exception e) {
+        int code = 0;
+        if (e instanceof SecurityException) {
+            code = EX_SECURITY;
+        } else if (e instanceof BadParcelableException) {
+            code = EX_BAD_PARCELABLE;
+        } else if (e instanceof IllegalArgumentException) {
+            code = EX_ILLEGAL_ARGUMENT;
+        } else if (e instanceof NullPointerException) {
+            code = EX_NULL_POINTER;
+        } else if (e instanceof IllegalStateException) {
+            code = EX_ILLEGAL_STATE;
+        }
+        writeInt(code);
+        if (code == 0) {
+            if (e instanceof RuntimeException) {
+                throw (RuntimeException) e;
+            }
+            throw new RuntimeException(e);
+        }
+        writeString(e.getMessage());
+    }
+
+    /**
+     * Special function for writing information at the front of the Parcel
+     * indicating that no exception occurred.
+     *
+     * @see #writeException
+     * @see #readException
+     */
+    public final void writeNoException() {
+        writeInt(0);
+    }
+
+    /**
+     * Special function for reading an exception result from the header of
+     * a parcel, to be used after receiving the result of a transaction.  This
+     * will throw the exception for you if it had been written to the Parcel,
+     * otherwise return and let you read the normal result data from the Parcel.
+     *
+     * @see #writeException
+     * @see #writeNoException
+     */
+    public final void readException() {
+        int code = readInt();
+        if (code == 0) return;
+        String msg = readString();
+        readException(code, msg);
+    }
+
+    /**
+     * Use this function for customized exception handling.
+     * customized method call this method for all unknown case
+     * @param code exception code
+     * @param msg exception message
+     */
+    public final void readException(int code, String msg) {
+        switch (code) {
+            case EX_SECURITY:
+                throw new SecurityException(msg);
+            case EX_BAD_PARCELABLE:
+                throw new BadParcelableException(msg);
+            case EX_ILLEGAL_ARGUMENT:
+                throw new IllegalArgumentException(msg);
+            case EX_NULL_POINTER:
+                throw new NullPointerException(msg);
+            case EX_ILLEGAL_STATE:
+                throw new IllegalStateException(msg);
+        }
+        throw new RuntimeException("Unknown exception code: " + code
+                + " msg " + msg);
+    }
+
+    /**
+     * Read an integer value from the parcel at the current dataPosition().
+     */
+    public final native int readInt();
+
+    /**
+     * Read a long integer value from the parcel at the current dataPosition().
+     */
+    public final native long readLong();
+
+    /**
+     * Read a floating point value from the parcel at the current
+     * dataPosition().
+     */
+    public final native float readFloat();
+
+    /**
+     * Read a double precision floating point value from the parcel at the
+     * current dataPosition().
+     */
+    public final native double readDouble();
+
+    /**
+     * Read a string value from the parcel at the current dataPosition().
+     */
+    public final native String readString();
+
+    /**
+     * Read an object from the parcel at the current dataPosition().
+     */
+    public final native IBinder readStrongBinder();
+
+    /**
+     * Read a FileDescriptor from the parcel at the current dataPosition().
+     */
+    public final ParcelFileDescriptor readFileDescriptor() {
+        FileDescriptor fd = internalReadFileDescriptor();
+        return fd != null ? new ParcelFileDescriptor(fd) : null;
+    }
+
+    private native FileDescriptor internalReadFileDescriptor();
+    /*package*/ static native FileDescriptor openFileDescriptor(String file,
+            int mode) throws FileNotFoundException;
+    /*package*/ static native void closeFileDescriptor(FileDescriptor desc)
+            throws IOException;
+
+    /**
+     * Read a byte value from the parcel at the current dataPosition().
+     */
+    public final byte readByte() {
+        return (byte)(readInt() & 0xff);
+    }
+
+    /**
+     * Please use {@link #readBundle(ClassLoader)} instead (whose data must have
+     * been written with {@link #writeBundle}.  Read into an existing Map object
+     * from the parcel at the current dataPosition().
+     */
+    public final void readMap(Map outVal, ClassLoader loader) {
+        int N = readInt();
+        readMapInternal(outVal, N, loader);
+    }
+
+    /**
+     * Read into an existing List object from the parcel at the current
+     * dataPosition(), using the given class loader to load any enclosed
+     * Parcelables.  If it is null, the default class loader is used.
+     */
+    public final void readList(List outVal, ClassLoader loader) {
+        int N = readInt();
+        readListInternal(outVal, N, loader);
+    }
+
+    /**
+     * Please use {@link #readBundle(ClassLoader)} instead (whose data must have
+     * been written with {@link #writeBundle}.  Read and return a new HashMap
+     * object from the parcel at the current dataPosition(), using the given
+     * class loader to load any enclosed Parcelables.  Returns null if
+     * the previously written map object was null.
+     */
+    public final HashMap readHashMap(ClassLoader loader)
+    {
+        int N = readInt();
+        if (N < 0) {
+            return null;
+        }
+        HashMap m = new HashMap(N);
+        readMapInternal(m, N, loader);
+        return m;
+    }
+
+    /**
+     * Read and return a new Bundle object from the parcel at the current
+     * dataPosition().  Returns null if the previously written Bundle object was
+     * null.
+     */
+    public final Bundle readBundle() {
+        return readBundle(null);
+    }
+
+    /**
+     * Read and return a new Bundle object from the parcel at the current
+     * dataPosition(), using the given class loader to initialize the class
+     * loader of the Bundle for later retrieval of Parcelable objects.
+     * Returns null if the previously written Bundle object was null.
+     */
+    public final Bundle readBundle(ClassLoader loader) {
+        int offset = dataPosition();
+        int length = readInt();
+        if (length < 0) {
+            return null;
+        }
+        int magic = readInt();
+        if (magic != 0x4C444E42) {
+            //noinspection ThrowableInstanceNeverThrown
+            String st = Log.getStackTraceString(new RuntimeException());
+            Log.e("Bundle", "readBundle: bad magic number");
+            Log.e("Bundle", "readBundle: trace = " + st);
+        }
+
+        // Advance within this Parcel
+        setDataPosition(offset + length + 4);
+
+        Parcel p = new Parcel(0);
+        p.setDataPosition(0);
+        p.appendFrom(this, offset, length + 4);
+        p.setDataPosition(0);
+        final Bundle bundle = new Bundle(p);
+        if (loader != null) {
+            bundle.setClassLoader(loader);
+        }
+        return bundle;
+    }
+
+    /**
+     * Read and return a new Bundle object from the parcel at the current
+     * dataPosition().  Returns null if the previously written Bundle object was
+     * null.  The returned bundle will have its contents fully unpacked using
+     * the given ClassLoader.
+     */
+    /* package */ Bundle readBundleUnpacked(ClassLoader loader) {
+        int length = readInt();
+        if (length == -1) {
+            return null;
+        }
+        int magic = readInt();
+        if (magic != 0x4C444E42) {
+            //noinspection ThrowableInstanceNeverThrown
+            String st = Log.getStackTraceString(new RuntimeException());
+            Log.e("Bundle", "readBundleUnpacked: bad magic number");
+            Log.e("Bundle", "readBundleUnpacked: trace = " + st);
+        }
+        Bundle m = new Bundle(loader);
+        int N = readInt();
+        if (N < 0) {
+            return null;
+        }
+        readMapInternal(m.mMap, N, loader);
+        return m;
+    }
+
+    /**
+     * Read and return a byte[] object from the parcel.
+     */
+    public final native byte[] createByteArray();
+
+    /**
+     * Read a byte[] object from the parcel and copy it into the
+     * given byte array.
+     */
+    public final void readByteArray(byte[] val) {
+        // TODO: make this a native method to avoid the extra copy.
+        byte[] ba = createByteArray();
+        if (ba.length == val.length) {
+           System.arraycopy(ba, 0, val, 0, ba.length);
+        } else {
+            throw new RuntimeException("bad array lengths");
+        }
+    }
+
+    /**
+     * Read and return a String[] object from the parcel.
+     * {@hide}
+     */
+    public final String[] readStringArray() {
+        String[] array = null;
+
+        int length = readInt();
+        if (length >= 0)
+        {
+            array = new String[length];
+
+            for (int i = 0 ; i < length ; i++)
+            {
+                array[i] = readString();
+            }
+        }
+
+        return array;
+    }
+
+    /**
+     * Read and return a new ArrayList object from the parcel at the current
+     * dataPosition().  Returns null if the previously written list object was
+     * null.  The given class loader will be used to load any enclosed
+     * Parcelables.
+     */
+    public final ArrayList readArrayList(ClassLoader loader) {
+        int N = readInt();
+        if (N < 0) {
+            return null;
+        }
+        ArrayList l = new ArrayList(N);
+        readListInternal(l, N, loader);
+        return l;
+    }
+
+    /**
+     * Read and return a new Object array from the parcel at the current
+     * dataPosition().  Returns null if the previously written array was
+     * null.  The given class loader will be used to load any enclosed
+     * Parcelables.
+     */
+    public final Object[] readArray(ClassLoader loader) {
+        int N = readInt();
+        if (N < 0) {
+            return null;
+        }
+        Object[] l = new Object[N];
+        readArrayInternal(l, N, loader);
+        return l;
+    }
+
+    /**
+     * Read and return a new SparseArray object from the parcel at the current
+     * dataPosition().  Returns null if the previously written list object was
+     * null.  The given class loader will be used to load any enclosed
+     * Parcelables.
+     */
+    public final SparseArray readSparseArray(ClassLoader loader) {
+        int N = readInt();
+        if (N < 0) {
+            return null;
+        }
+        SparseArray sa = new SparseArray(N);
+        readSparseArrayInternal(sa, N, loader);
+        return sa;
+    }
+
+    /**
+     * Read and return a new SparseBooleanArray object from the parcel at the current
+     * dataPosition().  Returns null if the previously written list object was
+     * null.
+     */
+    public final SparseBooleanArray readSparseBooleanArray() {
+        int N = readInt();
+        if (N < 0) {
+            return null;
+        }
+        SparseBooleanArray sa = new SparseBooleanArray(N);
+        readSparseBooleanArrayInternal(sa, N);
+        return sa;
+    }
+
+    /**
+     * Read and return a new ArrayList containing a particular object type from
+     * the parcel that was written with {@link #writeTypedList} at the
+     * current dataPosition().  Returns null if the
+     * previously written list object was null.  The list <em>must</em> have
+     * previously been written via {@link #writeTypedList} with the same object
+     * type.
+     *
+     * @return A newly created ArrayList containing objects with the same data
+     *         as those that were previously written.
+     *
+     * @see #writeTypedList
+     */
+    public final <T> ArrayList<T> createTypedArrayList(Parcelable.Creator<T> c) {
+        int N = readInt();
+        if (N < 0) {
+            return null;
+        }
+        ArrayList<T> l = new ArrayList<T>(N);
+        while (N > 0) {
+            if (readInt() != 0) {
+                l.add(c.createFromParcel(this));
+            } else {
+                l.add(null);
+            }
+            N--;
+        }
+        return l;
+    }
+
+    /**
+     * Read into the given List items containing a particular object type
+     * that were written with {@link #writeTypedList} at the
+     * current dataPosition().  The list <em>must</em> have
+     * previously been written via {@link #writeTypedList} with the same object
+     * type.
+     *
+     * @return A newly created ArrayList containing objects with the same data
+     *         as those that were previously written.
+     *
+     * @see #writeTypedList
+     */
+    public final <T> void readTypedList(List<T> list, Parcelable.Creator<T> c) {
+        int M = list.size();
+        int N = readInt();
+        int i = 0;
+        for (; i < M && i < N; i++) {
+            if (readInt() != 0) {
+                list.set(i, c.createFromParcel(this));
+            } else {
+                list.set(i, null);
+            }
+        }
+        for (; i<N; i++) {
+            if (readInt() != 0) {
+                list.add(c.createFromParcel(this));
+            } else {
+                list.add(null);
+            }
+        }
+        for (; i<M; i++) {
+            list.remove(N);
+        }
+    }
+
+    /**
+     * Read and return a new ArrayList containing String objects from
+     * the parcel that was written with {@link #writeStringList} at the
+     * current dataPosition().  Returns null if the
+     * previously written list object was null.
+     *
+     * @return A newly created ArrayList containing strings with the same data
+     *         as those that were previously written.
+     *
+     * @see #writeStringList
+     */
+    public final ArrayList<String> createStringArrayList() {
+        int N = readInt();
+        if (N < 0) {
+            return null;
+        }
+        ArrayList<String> l = new ArrayList<String>(N);
+        while (N > 0) {
+            l.add(readString());
+            N--;
+        }
+        return l;
+    }
+
+    /**
+     * Read and return a new ArrayList containing IBinder objects from
+     * the parcel that was written with {@link #writeBinderList} at the
+     * current dataPosition().  Returns null if the
+     * previously written list object was null.
+     *
+     * @return A newly created ArrayList containing strings with the same data
+     *         as those that were previously written.
+     *
+     * @see #writeBinderList
+     */
+    public final ArrayList<IBinder> createBinderArrayList() {
+        int N = readInt();
+        if (N < 0) {
+            return null;
+        }
+        ArrayList<IBinder> l = new ArrayList<IBinder>(N);
+        while (N > 0) {
+            l.add(readStrongBinder());
+            N--;
+        }
+        return l;
+    }
+
+    /**
+     * Read into the given List items String objects that were written with
+     * {@link #writeStringList} at the current dataPosition().
+     *
+     * @return A newly created ArrayList containing strings with the same data
+     *         as those that were previously written.
+     *
+     * @see #writeStringList
+     */
+    public final void readStringList(List<String> list) {
+        int M = list.size();
+        int N = readInt();
+        int i = 0;
+        for (; i < M && i < N; i++) {
+            list.set(i, readString());
+        }
+        for (; i<N; i++) {
+            list.add(readString());
+        }
+        for (; i<M; i++) {
+            list.remove(N);
+        }
+    }
+
+    /**
+     * Read into the given List items IBinder objects that were written with
+     * {@link #writeBinderList} at the current dataPosition().
+     *
+     * @return A newly created ArrayList containing strings with the same data
+     *         as those that were previously written.
+     *
+     * @see #writeBinderList
+     */
+    public final void readBinderList(List<IBinder> list) {
+        int M = list.size();
+        int N = readInt();
+        int i = 0;
+        for (; i < M && i < N; i++) {
+            list.set(i, readStrongBinder());
+        }
+        for (; i<N; i++) {
+            list.add(readStrongBinder());
+        }
+        for (; i<M; i++) {
+            list.remove(N);
+        }
+    }
+
+    /**
+     * Read and return a new array containing a particular object type from
+     * the parcel at the current dataPosition().  Returns null if the
+     * previously written array was null.  The array <em>must</em> have
+     * previously been written via {@link #writeTypedArray} with the same
+     * object type.
+     *
+     * @return A newly created array containing objects with the same data
+     *         as those that were previously written.
+     *
+     * @see #writeTypedArray
+     */
+    public final <T> T[] createTypedArray(Parcelable.Creator<T> c) {
+        int N = readInt();
+        if (N < 0) {
+            return null;
+        }
+        T[] l = c.newArray(N);
+        for (int i=0; i<N; i++) {
+            if (readInt() != 0) {
+                l[i] = c.createFromParcel(this);
+            }
+        }
+        return l;
+    }
+
+    public final <T> void readTypedArray(T[] val, Parcelable.Creator<T> c) {
+        int N = readInt();
+        if (N == val.length) {
+            for (int i=0; i<N; i++) {
+                if (readInt() != 0) {
+                    val[i] = c.createFromParcel(this);
+                } else {
+                    val[i] = null;
+                }
+            }
+        } else {
+            throw new RuntimeException("bad array lengths");
+        }
+    }
+
+    /**
+     * @deprecated
+     * @hide
+     */
+    @Deprecated
+    public final <T> T[] readTypedArray(Parcelable.Creator<T> c) {
+        return createTypedArray(c);
+    }
+
+    /**
+     * Write a heterogeneous array of Parcelable objects into the Parcel.
+     * Each object in the array is written along with its class name, so
+     * that the correct class can later be instantiated.  As a result, this
+     * has significantly more overhead than {@link #writeTypedArray}, but will
+     * correctly handle an array containing more than one type of object.
+     *
+     * @param value The array of objects to be written.
+     * @param parcelableFlags Contextual flags as per
+     * {@link Parcelable#writeToParcel(Parcel, int) Parcelable.writeToParcel()}.
+     *
+     * @see #writeTypedArray
+     */
+    public final <T extends Parcelable> void writeParcelableArray(T[] value,
+            int parcelableFlags) {
+        if (value != null) {
+            int N = value.length;
+            writeInt(N);
+            for (int i=0; i<N; i++) {
+                writeParcelable(value[i], parcelableFlags);
+            }
+        } else {
+            writeInt(-1);
+        }
+    }
+
+    /**
+     * Read a typed object from a parcel.  The given class loader will be
+     * used to load any enclosed Parcelables.  If it is null, the default class
+     * loader will be used.
+     */
+    public final Object readValue(ClassLoader loader) {
+        int type = readInt();
+
+        switch (type) {
+        case VAL_NULL:
+            return null;
+
+        case VAL_STRING:
+            return readString();
+
+        case VAL_INTEGER:
+            return readInt();
+
+        case VAL_MAP:
+            return readHashMap(loader);
+
+        case VAL_PARCELABLE:
+            return readParcelable(loader);
+
+        case VAL_SHORT:
+            return (short) readInt();
+
+        case VAL_LONG:
+            return readLong();
+
+        case VAL_FLOAT:
+            return readFloat();
+
+        case VAL_DOUBLE:
+            return readDouble();
+
+        case VAL_BOOLEAN:
+            return readInt() == 1;
+
+        case VAL_CHARSEQUENCE:
+            return TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(this);
+
+        case VAL_LIST:
+            return readArrayList(loader);
+
+        case VAL_BOOLEANARRAY:
+            return createBooleanArray();        
+
+        case VAL_BYTEARRAY:
+            return createByteArray();
+
+        case VAL_STRINGARRAY:
+            return readStringArray();
+
+        case VAL_IBINDER:
+            return readStrongBinder();
+
+        case VAL_OBJECTARRAY:
+            return readArray(loader);
+
+        case VAL_INTARRAY:
+            return createIntArray();
+
+        case VAL_LONGARRAY:
+            return createLongArray();
+
+        case VAL_BYTE:
+            return readByte();
+
+        case VAL_SERIALIZABLE:
+            return readSerializable();
+
+        case VAL_PARCELABLEARRAY:
+            return readParcelableArray(loader);
+
+        case VAL_SPARSEARRAY:
+            return readSparseArray(loader);
+
+        case VAL_SPARSEBOOLEANARRAY:
+            return readSparseBooleanArray();
+
+        case VAL_BUNDLE:
+            return readBundle(loader); // loading will be deferred
+
+        default:
+            int off = dataPosition() - 4;
+            throw new RuntimeException(
+                "Parcel " + this + ": Unmarshalling unknown type code " + type + " at offset " + off);
+        }
+    }
+
+    /**
+     * Read and return a new Parcelable from the parcel.  The given class loader
+     * will be used to load any enclosed Parcelables.  If it is null, the default
+     * class loader will be used.
+     * @param loader A ClassLoader from which to instantiate the Parcelable
+     * object, or null for the default class loader.
+     * @return Returns the newly created Parcelable, or null if a null
+     * object has been written.
+     * @throws BadParcelableException Throws BadParcelableException if there
+     * was an error trying to instantiate the Parcelable.
+     */
+    public final <T extends Parcelable> T readParcelable(ClassLoader loader) {
+        String name = readString();
+        if (name == null) {
+            return null;
+        }
+        Parcelable.Creator<T> creator;
+        synchronized (mCreators) {
+            HashMap<String,Parcelable.Creator> map = mCreators.get(loader);
+            if (map == null) {
+                map = new HashMap<String,Parcelable.Creator>();
+                mCreators.put(loader, map);
+            }
+            creator = map.get(name);
+            if (creator == null) {
+                try {
+                    Class c = loader == null ?
+                        Class.forName(name) : Class.forName(name, true, loader);
+                    Field f = c.getField("CREATOR");
+                    creator = (Parcelable.Creator)f.get(null);
+                }
+                catch (IllegalAccessException e) {
+                    Log.e("Parcel", "Class not found when unmarshalling: "
+                                        + name + ", e: " + e);
+                    throw new BadParcelableException(
+                            "IllegalAccessException when unmarshalling: " + name);
+                }
+                catch (ClassNotFoundException e) {
+                    Log.e("Parcel", "Class not found when unmarshalling: "
+                                        + name + ", e: " + e);
+                    throw new BadParcelableException(
+                            "ClassNotFoundException when unmarshalling: " + name);
+                }
+                catch (ClassCastException e) {
+                    throw new BadParcelableException("Parcelable protocol requires a "
+                                        + "Parcelable.Creator object called "
+                                        + " CREATOR on class " + name);
+                }
+                catch (NoSuchFieldException e) {
+                    throw new BadParcelableException("Parcelable protocol requires a "
+                                        + "Parcelable.Creator object called "
+                                        + " CREATOR on class " + name);
+                }
+                if (creator == null) {
+                    throw new BadParcelableException("Parcelable protocol requires a "
+                                        + "Parcelable.Creator object called "
+                                        + " CREATOR on class " + name);
+                }
+
+                map.put(name, creator);
+            }
+        }
+
+        return creator.createFromParcel(this);
+    }
+
+    /**
+     * Read and return a new Parcelable array from the parcel.
+     * The given class loader will be used to load any enclosed
+     * Parcelables.
+     * @return the Parcelable array, or null if the array is null
+     */
+    public final Parcelable[] readParcelableArray(ClassLoader loader) {
+        int N = readInt();
+        if (N < 0) {
+            return null;
+        }
+        Parcelable[] p = new Parcelable[N];
+        for (int i = 0; i < N; i++) {
+            p[i] = (Parcelable) readParcelable(loader);
+        }
+        return p;
+    }
+
+    /**
+     * Read and return a new Serializable object from the parcel.
+     * @return the Serializable object, or null if the Serializable name
+     * wasn't found in the parcel.
+     */
+    public final Serializable readSerializable() {
+        String name = readString();
+        if (name == null) {
+            // For some reason we were unable to read the name of the Serializable (either there
+            // is nothing left in the Parcel to read, or the next value wasn't a String), so
+            // return null, which indicates that the name wasn't found in the parcel.
+            return null;
+        }
+
+        byte[] serializedData = createByteArray();
+        ByteArrayInputStream bais = new ByteArrayInputStream(serializedData);
+        try {
+            ObjectInputStream ois = new ObjectInputStream(bais);
+            return (Serializable) ois.readObject();
+        } catch (IOException ioe) {
+            throw new RuntimeException("Parcelable encountered " +
+                "IOException reading a Serializable object (name = " + name +
+                ")", ioe);
+        } catch (ClassNotFoundException cnfe) {
+            throw new RuntimeException("Parcelable encountered" +
+                "ClassNotFoundException reading a Serializable object (name = "
+                + name + ")", cnfe);
+        }
+    }
+
+    // Cache of previously looked up CREATOR.createFromParcel() methods for
+    // particular classes.  Keys are the names of the classes, values are
+    // Method objects.
+    private static final HashMap<ClassLoader,HashMap<String,Parcelable.Creator>>
+        mCreators = new HashMap<ClassLoader,HashMap<String,Parcelable.Creator>>();
+
+    static protected final Parcel obtain(int obj) {
+        final Parcel[] pool = sHolderPool;
+        synchronized (pool) {
+            Parcel p;
+            for (int i=0; i<POOL_SIZE; i++) {
+                p = pool[i];
+                if (p != null) {
+                    pool[i] = null;
+                    if (DEBUG_RECYCLE) {
+                        p.mStack = new RuntimeException();
+                    }
+                    p.init(obj);
+                    return p;
+                }
+            }
+        }
+        return new Parcel(obj);
+    }
+
+    private Parcel(int obj) {
+        if (DEBUG_RECYCLE) {
+            mStack = new RuntimeException();
+        }
+        //Log.i("Parcel", "Initializing obj=0x" + Integer.toHexString(obj), mStack);
+        init(obj);
+    }
+
+    @Override
+    protected void finalize() throws Throwable {
+        if (DEBUG_RECYCLE) {
+            if (mStack != null) {
+                Log.w("Parcel", "Client did not call Parcel.recycle()", mStack);
+            }
+        }
+        destroy();
+    }
+
+    private native void freeBuffer();
+    private native void init(int obj);
+    private native void destroy();
+
+    private void readMapInternal(Map outVal, int N,
+        ClassLoader loader) {
+        while (N > 0) {
+            Object key = readValue(loader);
+            Object value = readValue(loader);
+            outVal.put(key, value);
+            N--;
+        }
+    }
+
+    private void readListInternal(List outVal, int N,
+        ClassLoader loader) {
+        while (N > 0) {
+            Object value = readValue(loader);
+            //Log.d("Parcel", "Unmarshalling value=" + value);
+            outVal.add(value);
+            N--;
+        }
+    }
+
+    private void readArrayInternal(Object[] outVal, int N,
+        ClassLoader loader) {
+        for (int i = 0; i < N; i++) {
+            Object value = readValue(loader);
+            //Log.d("Parcel", "Unmarshalling value=" + value);
+            outVal[i] = value;
+        }
+    }
+
+    private void readSparseArrayInternal(SparseArray outVal, int N,
+        ClassLoader loader) {
+        while (N > 0) {
+            int key = readInt();
+            Object value = readValue(loader);
+            //Log.i("Parcel", "Unmarshalling key=" + key + " value=" + value);
+            outVal.append(key, value);
+            N--;
+        }
+    }
+
+
+    private void readSparseBooleanArrayInternal(SparseBooleanArray outVal, int N) {
+        while (N > 0) {
+            int key = readInt();
+            boolean value = this.readByte() == 1;
+            //Log.i("Parcel", "Unmarshalling key=" + key + " value=" + value);
+            outVal.append(key, value);
+            N--;
+        }
+    }
+}
diff --git a/core/java/android/os/ParcelFileDescriptor.aidl b/core/java/android/os/ParcelFileDescriptor.aidl
new file mode 100644
index 0000000..5857aae
--- /dev/null
+++ b/core/java/android/os/ParcelFileDescriptor.aidl
@@ -0,0 +1,20 @@
+/* //device/java/android/android/os/ParcelFileDescriptor.aidl
+**
+** Copyright 2007, 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 android.os;
+
+parcelable ParcelFileDescriptor;
diff --git a/core/java/android/os/ParcelFileDescriptor.java b/core/java/android/os/ParcelFileDescriptor.java
new file mode 100644
index 0000000..3fcb18e
--- /dev/null
+++ b/core/java/android/os/ParcelFileDescriptor.java
@@ -0,0 +1,268 @@
+/*
+ * 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 android.os;
+import java.io.File;
+import java.io.FileDescriptor;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.net.Socket;
+
+/**
+ * The FileDescriptor returned by {@link Parcel#readFileDescriptor}, allowing
+ * you to close it when done with it.
+ */
+public class ParcelFileDescriptor implements Parcelable {
+    private final FileDescriptor mFileDescriptor;
+    private boolean mClosed;
+    //this field is to create wrapper for ParcelFileDescriptor using another
+    //PartialFileDescriptor but avoid invoking close twice
+    //consider ParcelFileDescriptor A(fileDescriptor fd),  ParcelFileDescriptor B(A)
+    //in this particular case fd.close might be invoked twice.
+    private final ParcelFileDescriptor mParcelDescriptor;
+    
+    /**
+     * For use with {@link #open}: if {@link #MODE_CREATE} has been supplied
+     * and this file doesn't already exist, then create the file with
+     * permissions such that any application can read it.
+     */
+    public static final int MODE_WORLD_READABLE = 0x00000001;
+    
+    /**
+     * For use with {@link #open}: if {@link #MODE_CREATE} has been supplied
+     * and this file doesn't already exist, then create the file with
+     * permissions such that any application can write it.
+     */
+    public static final int MODE_WORLD_WRITEABLE = 0x00000002;
+    
+    /**
+     * For use with {@link #open}: open the file with read-only access.
+     */
+    public static final int MODE_READ_ONLY = 0x10000000;
+    
+    /**
+     * For use with {@link #open}: open the file with write-only access.
+     */
+    public static final int MODE_WRITE_ONLY = 0x20000000;
+    
+    /**
+     * For use with {@link #open}: open the file with read and write access.
+     */
+    public static final int MODE_READ_WRITE = 0x30000000;
+    
+    /**
+     * For use with {@link #open}: create the file if it doesn't already exist.
+     */
+    public static final int MODE_CREATE = 0x08000000;
+    
+    /**
+     * For use with {@link #open}: erase contents of file when opening.
+     */
+    public static final int MODE_TRUNCATE = 0x04000000;
+    
+    /**
+     * For use with {@link #open}: append to end of file while writing.
+     */
+    public static final int MODE_APPEND = 0x02000000;
+    
+    /**
+     * Create a new ParcelFileDescriptor accessing a given file.
+     * 
+     * @param file The file to be opened.
+     * @param mode The desired access mode, must be one of
+     * {@link #MODE_READ_ONLY}, {@link #MODE_WRITE_ONLY}, or
+     * {@link #MODE_READ_WRITE}; may also be any combination of
+     * {@link #MODE_CREATE}, {@link #MODE_TRUNCATE},
+     * {@link #MODE_WORLD_READABLE}, and {@link #MODE_WORLD_WRITEABLE}.
+     * 
+     * @return Returns a new ParcelFileDescriptor pointing to the given
+     * file.
+     * 
+     * @throws FileNotFoundException Throws FileNotFoundException if the given
+     * file does not exist or can not be opened with the requested mode.
+     */
+    public static ParcelFileDescriptor open(File file, int mode)
+            throws FileNotFoundException {
+        String path = file.getPath();
+        SecurityManager security = System.getSecurityManager();
+        if (security != null) {
+            security.checkRead(path);
+            if ((mode&MODE_WRITE_ONLY) != 0) {
+                security.checkWrite(path);
+            }
+        }
+        
+        if ((mode&MODE_READ_WRITE) == 0) {
+            throw new IllegalArgumentException(
+                    "Must specify MODE_READ_ONLY, MODE_WRITE_ONLY, or MODE_READ_WRITE");
+        }
+        
+        FileDescriptor fd = Parcel.openFileDescriptor(path, mode);
+        return new ParcelFileDescriptor(fd);
+    }
+
+    /**
+     * Create a new ParcelFileDescriptor from the specified Socket.
+     *
+     * @param socket The Socket whose FileDescriptor is used to create
+     *               a new ParcelFileDescriptor.
+     *
+     * @return A new ParcelFileDescriptor with the FileDescriptor of the
+     *         specified Socket.
+     */
+    public static ParcelFileDescriptor fromSocket(Socket socket) {
+        FileDescriptor fd = getFileDescriptorFromSocket(socket);
+        return new ParcelFileDescriptor(fd);
+    }
+
+    // Extracts the file descriptor from the specified socket and returns it untouched
+    private static native FileDescriptor getFileDescriptorFromSocket(Socket socket);
+
+    /**
+     * Retrieve the actual FileDescriptor associated with this object.
+     * 
+     * @return Returns the FileDescriptor associated with this object.
+     */
+    public FileDescriptor getFileDescriptor() {
+        return mFileDescriptor;
+    }
+    
+    /**
+     * Return the total size of the file representing this fd, as determined
+     * by stat().  Returns -1 if the fd is not a file.
+     */
+    public native long getStatSize();
+    
+    /**
+     * This is needed for implementing AssetFileDescriptor.AutoCloseOutputStream,
+     * and I really don't think we want it to be public.
+     * @hide
+     */
+    public native long seekTo(long pos);
+    
+    /**
+     * Close the ParcelFileDescriptor. This implementation closes the underlying
+     * OS resources allocated to represent this stream.
+     * 
+     * @throws IOException
+     *             If an error occurs attempting to close this ParcelFileDescriptor.
+     */
+    public void close() throws IOException {
+        mClosed = true;
+        if (mParcelDescriptor != null) {
+            // If this is a proxy to another file descriptor, just call through to its
+            // close method.
+            mParcelDescriptor.close();
+        } else {
+            Parcel.closeFileDescriptor(mFileDescriptor);
+        }
+    }
+    
+    /**
+     * An InputStream you can create on a ParcelFileDescriptor, which will
+     * take care of calling {@link ParcelFileDescriptor#close
+     * ParcelFileDescritor.close()} for you when the stream is closed.
+     */
+    public static class AutoCloseInputStream extends FileInputStream {
+        private final ParcelFileDescriptor mFd;
+        
+        public AutoCloseInputStream(ParcelFileDescriptor fd) {
+            super(fd.getFileDescriptor());
+            mFd = fd;
+        }
+
+        @Override
+        public void close() throws IOException {
+            mFd.close();
+        }
+    }
+    
+    /**
+     * An OutputStream you can create on a ParcelFileDescriptor, which will
+     * take care of calling {@link ParcelFileDescriptor#close
+     * ParcelFileDescritor.close()} for you when the stream is closed.
+     */
+    public static class AutoCloseOutputStream extends FileOutputStream {
+        private final ParcelFileDescriptor mFd;
+        
+        public AutoCloseOutputStream(ParcelFileDescriptor fd) {
+            super(fd.getFileDescriptor());
+            mFd = fd;
+        }
+
+        @Override
+        public void close() throws IOException {
+            mFd.close();
+        }
+    }
+    
+    @Override
+    public String toString() {
+        return "{ParcelFileDescriptor: " + mFileDescriptor + "}";
+    }
+    
+    @Override
+    protected void finalize() throws Throwable {
+        try {
+            if (!mClosed) {
+                close();
+            }
+        } finally {
+            super.finalize();
+        }
+    }
+    
+    public ParcelFileDescriptor(ParcelFileDescriptor descriptor) {
+        super();
+        mParcelDescriptor = descriptor;
+        mFileDescriptor = mParcelDescriptor.mFileDescriptor;
+    }
+    
+    /*package */ParcelFileDescriptor(FileDescriptor descriptor) {
+        super();
+        mFileDescriptor = descriptor;
+        mParcelDescriptor = null;
+    }
+    
+    /* Parcelable interface */
+    public int describeContents() {
+        return Parcelable.CONTENTS_FILE_DESCRIPTOR;
+    }
+
+    public void writeToParcel(Parcel out, int flags) {
+        out.writeFileDescriptor(mFileDescriptor);
+        if ((flags&PARCELABLE_WRITE_RETURN_VALUE) != 0 && !mClosed) {
+            try {
+                close();
+            } catch (IOException e) {
+                // Empty
+            }
+        }
+    }
+
+    public static final Parcelable.Creator<ParcelFileDescriptor> CREATOR
+            = new Parcelable.Creator<ParcelFileDescriptor>() {
+        public ParcelFileDescriptor createFromParcel(Parcel in) {
+            return in.readFileDescriptor();
+        }
+        public ParcelFileDescriptor[] newArray(int size) {
+            return new ParcelFileDescriptor[size];
+        }
+    };
+
+}
diff --git a/core/java/android/os/ParcelFormatException.java b/core/java/android/os/ParcelFormatException.java
new file mode 100644
index 0000000..8b6fda0
--- /dev/null
+++ b/core/java/android/os/ParcelFormatException.java
@@ -0,0 +1,31 @@
+/*
+ * 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 android.os;
+
+/**
+ * The contents of a Parcel (usually during unmarshalling) does not
+ * contain the expected data.
+ */
+public class ParcelFormatException extends RuntimeException {
+    public ParcelFormatException() {
+        super();
+    }
+
+    public ParcelFormatException(String reason) {
+        super(reason);
+    }
+}
diff --git a/core/java/android/os/Parcelable.java b/core/java/android/os/Parcelable.java
new file mode 100644
index 0000000..aee1e0b
--- /dev/null
+++ b/core/java/android/os/Parcelable.java
@@ -0,0 +1,112 @@
+/*
+ * 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 android.os;
+
+/**
+ * Interface for classes whose instances can be written to
+ * and restored from a {@link Parcel}.  Classes implementing the Parcelable
+ * interface must also have a static field called <code>CREATOR</code>, which
+ * is an object implementing the {@link Parcelable.Creator Parcelable.Creator}
+ * interface.
+ * 
+ * <p>A typical implementation of Parcelable is:</p>
+ * 
+ * <pre>
+ * public class MyParcelable implements Parcelable {
+ *     private int mData;
+ *     
+ *     public void writeToParcel(Parcel out, int flags) {
+ *         out.writeInt(mData);
+ *     }
+ *
+ *     public static final Parcelable.Creator<MyParcelable> CREATOR
+ *             = new Parcelable.Creator<MyParcelable>() {
+ *         public MyParcelable createFromParcel(Parcel in) {
+ *             return new MyParcelable(in);
+ *         }
+ *
+ *         public MyParcelable[] newArray(int size) {
+ *             return new MyParcelable[size];
+ *         }
+ *     }
+ *     
+ *     private MyParcelable(Parcel in) {
+ *         mData = in.readInt();
+ *     }
+ * }</pre>
+ */
+public interface Parcelable {
+    /**
+     * Flag for use with {@link #writeToParcel}: the object being written
+     * is a return value, that is the result of a function such as
+     * "<code>Parcelable someFunction()</code>",
+     * "<code>void someFunction(out Parcelable)</code>", or
+     * "<code>void someFunction(inout Parcelable)</code>".  Some implementations
+     * may want to release resources at this point.
+     */
+    public static final int PARCELABLE_WRITE_RETURN_VALUE = 0x0001;
+    
+    /**
+     * Bit masks for use with {@link #describeContents}: each bit represents a
+     * kind of object considered to have potential special significance when
+     * marshalled.
+     */
+    public static final int CONTENTS_FILE_DESCRIPTOR = 0x0001;
+    
+    /**
+     * Describe the kinds of special objects contained in this Parcelable's
+     * marshalled representation.
+     *  
+     * @return a bitmask indicating the set of special object types marshalled
+     * by the Parcelable.
+     */
+    public int describeContents();
+    
+    /**
+     * Flatten this object in to a Parcel.
+     * 
+     * @param dest The Parcel in which the object should be written.
+     * @param flags Additional flags about how the object should be written.
+     * May be 0 or {@link #PARCELABLE_WRITE_RETURN_VALUE}.
+     */
+    public void writeToParcel(Parcel dest, int flags);
+
+    /**
+     * Interface that must be implemented and provided as a public CREATOR
+     * field that generates instances of your Parcelable class from a Parcel.
+     */
+    public interface Creator<T> {
+        /**
+         * Create a new instance of the Parcelable class, instantiating it
+         * from the given Parcel whose data had previously been written by
+         * {@link Parcelable#writeToParcel Parcelable.writeToParcel()}.
+         * 
+         * @param source The Parcel to read the object's data from.
+         * @return Returns a new instance of the Parcelable class.
+         */
+        public T createFromParcel(Parcel source);
+        
+        /**
+         * Create a new array of the Parcelable class.
+         * 
+         * @param size Size of the array.
+         * @return Returns an array of the Parcelable class, with every entry
+         * initialized to null.
+         */
+        public T[] newArray(int size);
+    }
+}
diff --git a/core/java/android/os/PatternMatcher.aidl b/core/java/android/os/PatternMatcher.aidl
new file mode 100644
index 0000000..86309f1
--- /dev/null
+++ b/core/java/android/os/PatternMatcher.aidl
@@ -0,0 +1,19 @@
+/*
+ * 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 android.os;
+
+parcelable PatternMatcher;
diff --git a/core/java/android/os/PatternMatcher.java b/core/java/android/os/PatternMatcher.java
new file mode 100644
index 0000000..56dc837
--- /dev/null
+++ b/core/java/android/os/PatternMatcher.java
@@ -0,0 +1,197 @@
+/*
+ * 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 android.os;
+
+/**
+ * A simple pattern matcher, which is safe to use on untrusted data: it does
+ * not provide full reg-exp support, only simple globbing that can not be
+ * used maliciously.
+ */
+public class PatternMatcher implements Parcelable {
+    /**
+     * Pattern type: the given pattern must exactly match the string it is
+     * tested against.
+     */
+    public static final int PATTERN_LITERAL = 0;
+    
+    /**
+     * Pattern type: the given pattern must match the
+     * beginning of the string it is tested against.
+     */
+    public static final int PATTERN_PREFIX = 1;
+    
+    /**
+     * Pattern type: the given pattern is interpreted with a
+     * simple glob syntax for matching against the string it is tested against.
+     * In this syntax, you can use the '*' character to match against zero or
+     * more occurrences of the character immediately before.  If the
+     * character before it is '.' it will match any character.  The character
+     * '\' can be used as an escape.  This essentially provides only the '*'
+     * wildcard part of a normal regexp. 
+     */
+    public static final int PATTERN_SIMPLE_GLOB = 2;
+    
+    private final String mPattern;
+    private final int mType;
+    
+    public PatternMatcher(String pattern, int type) {
+        mPattern = pattern;
+        mType = type;
+    }
+
+    public final String getPath() {
+        return mPattern;
+    }
+    
+    public final int getType() {
+        return mType;
+    }
+    
+    public boolean match(String str) {
+        return matchPattern(mPattern, str, mType);
+    }
+
+    public String toString() {
+        String type = "? ";
+        switch (mType) {
+            case PATTERN_LITERAL:
+                type = "LITERAL: ";
+                break;
+            case PATTERN_PREFIX:
+                type = "PREFIX: ";
+                break;
+            case PATTERN_SIMPLE_GLOB:
+                type = "GLOB: ";
+                break;
+        }
+        return "PatternMatcher{" + type + mPattern + "}";
+    }
+    
+    public int describeContents() {
+        return 0;
+    }
+
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeString(mPattern);
+        dest.writeInt(mType);
+    }
+    
+    public PatternMatcher(Parcel src) {
+        mPattern = src.readString();
+        mType = src.readInt();
+    }
+    
+    public static final Parcelable.Creator<PatternMatcher> CREATOR
+            = new Parcelable.Creator<PatternMatcher>() {
+        public PatternMatcher createFromParcel(Parcel source) {
+            return new PatternMatcher(source);
+        }
+
+        public PatternMatcher[] newArray(int size) {
+            return new PatternMatcher[size];
+        }
+    };
+    
+    static boolean matchPattern(String pattern, String match, int type) {
+        if (match == null) return false;
+        if (type == PATTERN_LITERAL) {
+            return pattern.equals(match);
+        } if (type == PATTERN_PREFIX) {
+            return match.startsWith(pattern);
+        } else if (type != PATTERN_SIMPLE_GLOB) {
+            return false;
+        }
+        
+        final int NP = pattern.length();
+        if (NP <= 0) {
+            return match.length() <= 0;
+        }
+        final int NM = match.length();
+        int ip = 0, im = 0;
+        char nextChar = pattern.charAt(0);
+        while ((ip<NP) && (im<NM)) {
+            char c = nextChar;
+            ip++;
+            nextChar = ip < NP ? pattern.charAt(ip) : 0;
+            final boolean escaped = (c == '\\');
+            if (escaped) {
+                c = nextChar;
+                ip++;
+                nextChar = ip < NP ? pattern.charAt(ip) : 0;
+            }
+            if (nextChar == '*') {
+                if (!escaped && c == '.') {
+                    if (ip >= (NP-1)) {
+                        // at the end with a pattern match, so
+                        // all is good without checking!
+                        return true;
+                    }
+                    ip++;
+                    nextChar = pattern.charAt(ip);
+                    // Consume everything until the next character in the
+                    // pattern is found.
+                    if (nextChar == '\\') {
+                        ip++;
+                        nextChar = ip < NP ? pattern.charAt(ip) : 0;
+                    }
+                    do {
+                        if (match.charAt(im) == nextChar) {
+                            break;
+                        }
+                        im++;
+                    } while (im < NM);
+                    if (im == NM) {
+                        // Whoops, the next character in the pattern didn't
+                        // exist in the match.
+                        return false;
+                    }
+                    ip++;
+                    nextChar = ip < NP ? pattern.charAt(ip) : 0;
+                    im++;
+                } else {
+                    // Consume only characters matching the one before '*'.
+                    do {
+                        if (match.charAt(im) != c) {
+                            break;
+                        }
+                        im++;
+                    } while (im < NM);
+                    ip++;
+                    nextChar = ip < NP ? pattern.charAt(ip) : 0;
+                }
+            } else {
+                if (c != '.' && match.charAt(im) != c) return false;
+                im++;
+            }
+        }
+        
+        if (ip >= NP && im >= NM) {
+            // Reached the end of both strings, all is good!
+            return true;
+        }
+        
+        // One last check: we may have finished the match string, but still
+        // have a '.*' at the end of the pattern, which should still count
+        // as a match.
+        if (ip == NP-2 && pattern.charAt(ip) == '.'
+            && pattern.charAt(ip+1) == '*') {
+            return true;
+        }
+        
+        return false;
+    }
+}
diff --git a/core/java/android/os/Power.java b/core/java/android/os/Power.java
new file mode 100644
index 0000000..b53e227
--- /dev/null
+++ b/core/java/android/os/Power.java
@@ -0,0 +1,130 @@
+/*
+ * Copyright (C) 2007 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 android.os;
+
+import java.io.IOException;
+
+/**
+ * Class that provides access to some of the power management functions.
+ *
+ * {@hide}
+ */
+public class Power
+{
+    // can't instantiate this class
+    private Power()
+    {
+    }
+
+    /**
+     * Wake lock that ensures that the CPU is running.  The screen might
+     * not be on.
+     */
+    public static final int PARTIAL_WAKE_LOCK = 1;
+
+    /**
+     * Wake lock that ensures that the screen is on.
+     */
+    public static final int FULL_WAKE_LOCK = 2;
+
+    public static native void acquireWakeLock(int lock, String id);
+    public static native void releaseWakeLock(String id);
+
+    /**
+     * Flag to turn on and off the keyboard light.
+     */
+    public static final int KEYBOARD_LIGHT = 0x00000001;
+
+    /**
+     * Flag to turn on and off the screen backlight.
+     */
+    public static final int SCREEN_LIGHT = 0x00000002;
+
+    /**
+     * Flag to turn on and off the button backlight.
+     */
+    public static final int BUTTON_LIGHT = 0x00000004;
+
+    /**
+     * Flags to turn on and off all the backlights.
+     */
+    public static final int ALL_LIGHTS = (KEYBOARD_LIGHT|SCREEN_LIGHT|BUTTON_LIGHT);
+
+    /**
+     * Brightness value for fully off
+     */
+    public static final int BRIGHTNESS_OFF = 0;
+
+    /**
+     * Brightness value for dim backlight
+     */
+    public static final int BRIGHTNESS_DIM = 20;
+
+    /**
+     * Brightness value for fully on
+     */
+    public static final int BRIGHTNESS_ON = 255;
+
+    /**
+     * Brightness value to use when battery is low
+     */
+    public static final int BRIGHTNESS_LOW_BATTERY = 10;
+
+    /**
+     * Threshold for BRIGHTNESS_LOW_BATTERY (percentage)
+     * Screen will stay dim if battery level is <= LOW_BATTERY_THRESHOLD
+     */
+    public static final int LOW_BATTERY_THRESHOLD = 10;
+
+    /**
+     * Set the brightness for one or more lights
+     *
+     * @param mask flags indicating which lights to change brightness
+     * @param brightness new brightness value (0 = off, 255 = fully bright)
+     */
+    public static native int setLightBrightness(int mask, int brightness);
+
+    /**
+     * Turn the screen on or off
+     *
+     * @param on Whether you want the screen on or off
+     */
+    public static native int setScreenState(boolean on);
+
+    public static native int setLastUserActivityTimeout(long ms);
+
+    /**
+     * Turn the device off.
+     *
+     * This method is considered deprecated in favor of
+     * {@link android.policy.ShutdownThread.shutdownAfterDisablingRadio()}.
+     *
+     * @deprecated
+     * @hide
+     */
+    @Deprecated
+    public static native void shutdown();
+
+    /**
+     * Reboot the device.
+     * @param reason code to pass to the kernel (e.g. "recovery"), or null.
+     *
+     * @throws IOException if reboot fails for some reason (eg, lack of
+     *         permission)
+     */
+    public static native void reboot(String reason) throws IOException;
+}
diff --git a/core/java/android/os/PowerManager.java b/core/java/android/os/PowerManager.java
new file mode 100644
index 0000000..bfcf2fc
--- /dev/null
+++ b/core/java/android/os/PowerManager.java
@@ -0,0 +1,392 @@
+/*
+ * Copyright (C) 2007 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 android.os;
+
+import android.util.Log;
+
+import com.android.internal.os.RuntimeInit;
+
+/**
+ * This class gives you control of the power state of the device.  
+ * 
+ * <p><b>Device battery life will be significantly affected by the use of this API.</b>  Do not
+ * acquire WakeLocks unless you really need them, use the minimum levels possible, and be sure
+ * to release it as soon as you can.
+ * 
+ * <p>You can obtain an instance of this class by calling 
+ * {@link android.content.Context#getSystemService(java.lang.String) Context.getSystemService()}.
+ * 
+ * <p>The primary API you'll use is {@link #newWakeLock(int, String) newWakeLock()}.  This will
+ * create a {@link PowerManager.WakeLock} object.  You can then use methods on this object to 
+ * control the power state of the device.  In practice it's quite simple:
+ * 
+ * {@samplecode
+ * PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
+ * PowerManager.WakeLock wl = pm.newWakeLock(PowerManager.SCREEN_DIM_WAKE_LOCK, "My Tag");
+ * wl.acquire();
+ *   ..screen will stay on during this section..
+ * wl.release();
+ * }
+ * 
+ * <p>The following flags are defined, with varying effects on system power.  <i>These flags are
+ * mutually exclusive - you may only specify one of them.</i>
+ * <table border="2" width="85%" align="center" frame="hsides" rules="rows">
+ *
+ *     <thead>
+ *     <tr><th>Flag Value</th> 
+ *     <th>CPU</th> <th>Screen</th> <th>Keyboard</th></tr>
+ *     </thead>
+ *
+ *     <tbody>
+ *     <tr><th>{@link #PARTIAL_WAKE_LOCK}</th>
+ *         <td>On*</td> <td>Off</td> <td>Off</td> 
+ *     </tr>
+ *     
+ *     <tr><th>{@link #SCREEN_DIM_WAKE_LOCK}</th>
+ *         <td>On</td> <td>Dim</td> <td>Off</td> 
+ *     </tr>
+ *
+ *     <tr><th>{@link #SCREEN_BRIGHT_WAKE_LOCK}</th>
+ *         <td>On</td> <td>Bright</td> <td>Off</td> 
+ *     </tr>
+ *     
+ *     <tr><th>{@link #FULL_WAKE_LOCK}</th>
+ *         <td>On</td> <td>Bright</td> <td>Bright</td> 
+ *     </tr>
+ *     </tbody>
+ * </table>
+ * 
+ * <p>*<i>If you hold a partial wakelock, the CPU will continue to run, irrespective of any timers 
+ * and even after the user presses the power button.  In all other wakelocks, the CPU will run, but
+ * the user can still put the device to sleep using the power button.</i>
+ * 
+ * <p>In addition, you can add two more flags, which affect behavior of the screen only.  <i>These
+ * flags have no effect when combined with a {@link #PARTIAL_WAKE_LOCK}.</i>
+ * <table border="2" width="85%" align="center" frame="hsides" rules="rows">
+ *
+ *     <thead>
+ *     <tr><th>Flag Value</th> <th>Description</th></tr>
+ *     </thead>
+ *
+ *     <tbody>
+ *     <tr><th>{@link #ACQUIRE_CAUSES_WAKEUP}</th>
+ *         <td>Normal wake locks don't actually turn on the illumination.  Instead, they cause
+ *         the illumination to remain on once it turns on (e.g. from user activity).  This flag
+ *         will force the screen and/or keyboard to turn on immediately, when the WakeLock is
+ *         acquired.  A typical use would be for notifications which are important for the user to
+ *         see immediately.</td> 
+ *     </tr>
+ *     
+ *     <tr><th>{@link #ON_AFTER_RELEASE}</th>
+ *         <td>If this flag is set, the user activity timer will be reset when the WakeLock is
+ *         released, causing the illumination to remain on a bit longer.  This can be used to 
+ *         reduce flicker if you are cycling between wake lock conditions.</td> 
+ *     </tr>
+ *     </tbody>
+ * </table>
+ * 
+ * 
+ */
+public class PowerManager
+{
+    private static final String TAG = "PowerManager";
+    
+    /**
+     * These internal values define the underlying power elements that we might
+     * want to control individually.  Eventually we'd like to expose them.
+     */
+    private static final int WAKE_BIT_CPU_STRONG = 1;
+    private static final int WAKE_BIT_CPU_WEAK = 2;
+    private static final int WAKE_BIT_SCREEN_DIM = 4;
+    private static final int WAKE_BIT_SCREEN_BRIGHT = 8;
+    private static final int WAKE_BIT_KEYBOARD_BRIGHT = 16;
+    
+    private static final int LOCK_MASK = WAKE_BIT_CPU_STRONG
+                                        | WAKE_BIT_CPU_WEAK
+                                        | WAKE_BIT_SCREEN_DIM
+                                        | WAKE_BIT_SCREEN_BRIGHT
+                                        | WAKE_BIT_KEYBOARD_BRIGHT;
+
+    /**
+     * Wake lock that ensures that the CPU is running.  The screen might
+     * not be on.
+     */
+    public static final int PARTIAL_WAKE_LOCK = WAKE_BIT_CPU_STRONG;
+
+    /**
+     * Wake lock that ensures that the screen and keyboard are on at
+     * full brightness.
+     */
+    public static final int FULL_WAKE_LOCK = WAKE_BIT_CPU_WEAK | WAKE_BIT_SCREEN_BRIGHT 
+                                            | WAKE_BIT_KEYBOARD_BRIGHT;
+
+    /**
+     * Wake lock that ensures that the screen is on at full brightness;
+     * the keyboard backlight will be allowed to go off.
+     */
+    public static final int SCREEN_BRIGHT_WAKE_LOCK = WAKE_BIT_CPU_WEAK | WAKE_BIT_SCREEN_BRIGHT;
+
+    /**
+     * Wake lock that ensures that the screen is on (but may be dimmed);
+     * the keyboard backlight will be allowed to go off.
+     */
+    public static final int SCREEN_DIM_WAKE_LOCK = WAKE_BIT_CPU_WEAK | WAKE_BIT_SCREEN_DIM;
+
+    /**
+     * Normally wake locks don't actually wake the device, they just cause
+     * it to remain on once it's already on.  Think of the video player
+     * app as the normal behavior.  Notifications that pop up and want
+     * the device to be on are the exception; use this flag to be like them.
+     * <p> 
+     * Does not work with PARTIAL_WAKE_LOCKs.
+     */
+    public static final int ACQUIRE_CAUSES_WAKEUP = 0x10000000;
+
+    /**
+     * When this wake lock is released, poke the user activity timer
+     * so the screen stays on for a little longer.
+     * <p>
+     * Will not turn the screen on if it is not already on.  See {@link #ACQUIRE_CAUSES_WAKEUP}
+     * if you want that.
+     * <p>
+     * Does not work with PARTIAL_WAKE_LOCKs.
+     */
+    public static final int ON_AFTER_RELEASE = 0x20000000;
+    
+    /**
+     * Class lets you say that you need to have the device on.
+     *
+     * <p>Call release when you are done and don't need the lock anymore.
+     */
+    public class WakeLock
+    {
+        static final int RELEASE_WAKE_LOCK = 1;
+
+        Runnable mReleaser = new Runnable() {
+            public void run() {
+                release();
+            }
+        };
+	
+        int mFlags;
+        String mTag;
+        IBinder mToken;
+        int mCount = 0;
+        boolean mRefCounted = true;
+        boolean mHeld = false;
+
+        WakeLock(int flags, String tag)
+        {
+            switch (flags & LOCK_MASK) {
+            case PARTIAL_WAKE_LOCK:
+            case SCREEN_DIM_WAKE_LOCK:
+            case SCREEN_BRIGHT_WAKE_LOCK:
+            case FULL_WAKE_LOCK:
+                break;
+            default:
+                throw new IllegalArgumentException();
+            }
+
+            mFlags = flags;
+            mTag = tag;
+            mToken = new Binder();
+        }
+
+        /**
+         * Sets whether this WakeLock is ref counted.
+         *
+         * @param value true for ref counted, false for not ref counted.
+         */
+        public void setReferenceCounted(boolean value)
+        {
+            mRefCounted = value;
+        }
+
+        /**
+         * Makes sure the device is on at the level you asked when you created
+         * the wake lock.
+         */
+        public void acquire()
+        {
+            synchronized (mToken) {
+                if (!mRefCounted || mCount++ == 0) {
+                    try {
+                        mService.acquireWakeLock(mFlags, mToken, mTag);
+                    } catch (RemoteException e) {
+                    }
+                    mHeld = true;
+                }
+            }
+        }
+        
+        /**
+         * Makes sure the device is on at the level you asked when you created
+         * the wake lock. The lock will be released after the given timeout.
+         * 
+         * @param timeout Release the lock after the give timeout in milliseconds.
+         */
+        public void acquire(long timeout) {
+            acquire();
+            mHandler.postDelayed(mReleaser, timeout);
+        }
+        
+
+        /**
+         * Release your claim to the CPU or screen being on.
+         *
+         * <p>
+         * It may turn off shortly after you release it, or it may not if there
+         * are other wake locks held.
+         */
+        public void release()
+        {
+            synchronized (mToken) {
+                if (!mRefCounted || --mCount == 0) {
+                    try {
+                        mService.releaseWakeLock(mToken);
+                    } catch (RemoteException e) {
+                    }
+                    mHeld = false;
+                }
+                if (mCount < 0) {
+                    throw new RuntimeException("WakeLock under-locked " + mTag);
+                }
+            }
+        }
+
+        public boolean isHeld()
+        {
+            synchronized (mToken) {
+                return mHeld;
+            }
+        }
+
+        public String toString() {
+            synchronized (mToken) {
+                return "WakeLock{"
+                    + Integer.toHexString(System.identityHashCode(this))
+                    + " held=" + mHeld + ", refCount=" + mCount + "}";
+            }
+        }
+
+        @Override
+        protected void finalize() throws Throwable
+        {
+            synchronized (mToken) {
+                if (mHeld) {
+                    try {
+                        mService.releaseWakeLock(mToken);
+                    } catch (RemoteException e) {
+                    }
+                    RuntimeInit.crash(TAG, new Exception(
+                                "WakeLock finalized while still held: "+mTag));
+                }
+            }
+        }
+    }
+
+    /**
+     * Get a wake lock at the level of the flags parameter.  Call
+     * {@link WakeLock#acquire() acquire()} on the object to acquire the
+     * wake lock, and {@link WakeLock#release release()} when you are done.
+     *
+     * {@samplecode
+     *PowerManager pm = (PowerManager)mContext.getSystemService(
+     *                                          Context.POWER_SERVICE);
+     *PowerManager.WakeLock wl = pm.newWakeLock(
+     *                                      PowerManager.SCREEN_DIM_WAKE_LOCK
+     *                                      | PowerManager.ON_AFTER_RELEASE,
+     *                                      TAG);
+     *wl.acquire();
+     * // ...
+     *wl.release();
+     * }
+     *
+     * @param flags Combination of flag values defining the requested behavior of the WakeLock.
+     * @param tag Your class name (or other tag) for debugging purposes.
+     *
+     * @see WakeLock#acquire()
+     * @see WakeLock#release()
+     */
+    public WakeLock newWakeLock(int flags, String tag)
+    {
+        return new WakeLock(flags, tag);
+    }
+
+    /**
+     * User activity happened.
+     * <p>
+     * Turns the device from whatever state it's in to full on, and resets
+     * the auto-off timer.
+     *
+     * @param when is used to order this correctly with the wake lock calls.
+     *          This time should be in the {@link SystemClock#uptimeMillis
+     *          SystemClock.uptimeMillis()} time base.
+     * @param noChangeLights should be true if you don't want the lights to
+     *          turn on because of this event.  This is set when the power
+     *          key goes down.  We want the device to stay on while the button
+     *          is down, but we're about to turn off.  Otherwise the lights
+     *          flash on and then off and it looks weird.
+     */
+    public void userActivity(long when, boolean noChangeLights)
+    {
+        try {
+            mService.userActivity(when, noChangeLights);
+        } catch (RemoteException e) {
+        }
+    }
+
+   /**
+     * Force the device to go to sleep. Overrides all the wake locks that are
+     * held.
+     * 
+     * @param time is used to order this correctly with the wake lock calls. 
+     *          The time  should be in the {@link SystemClock#uptimeMillis 
+     *          SystemClock.uptimeMillis()} time base.
+     */
+    public void goToSleep(long time) 
+    {
+        try {
+            mService.goToSleep(time);
+        } catch (RemoteException e) {
+        }
+    }
+    
+    private PowerManager()
+    {
+    }
+
+    /**
+     * {@hide}
+     */
+    public PowerManager(IPowerManager service, Handler handler)
+    {
+        mService = service;
+        mHandler = handler;
+    }
+
+    /**
+     *  TODO: It would be nice to be able to set the poke lock here,
+     *  but I'm not sure what would be acceptable as an interface -
+     *  either a PokeLock object (like WakeLock) or, possibly just a
+     *  method call to set the poke lock. 
+     */
+    
+    IPowerManager mService;
+    Handler mHandler;
+}
+
diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java
new file mode 100644
index 0000000..cd86fbe
--- /dev/null
+++ b/core/java/android/os/Process.java
@@ -0,0 +1,706 @@
+/*
+ * 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 android.os;
+
+import android.net.LocalSocketAddress;
+import android.net.LocalSocket;
+import android.util.Log;
+import dalvik.system.Zygote;
+
+import java.io.BufferedWriter;
+import java.io.DataInputStream;
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.util.ArrayList;
+
+/*package*/ class ZygoteStartFailedEx extends Exception {
+    /**
+     * Something prevented the zygote process startup from happening normally
+     */
+
+    ZygoteStartFailedEx() {};
+    ZygoteStartFailedEx(String s) {super(s);}
+    ZygoteStartFailedEx(Throwable cause) {super(cause);}
+}
+
+/**
+ * Tools for managing OS processes.
+ */
+public class Process {
+    private static final String LOG_TAG = "Process";
+
+    private static final String ZYGOTE_SOCKET = "zygote";
+
+    /**
+     * Name of a process for running the platform's media services.
+     * {@hide}
+     */
+    public static final String ANDROID_SHARED_MEDIA = "com.android.process.media";
+
+    /**
+     * Name of the process that Google content providers can share.
+     * {@hide}
+     */
+    public static final String GOOGLE_SHARED_APP_CONTENT = "com.google.process.content";
+
+    /**
+     * Defines the UID/GID under which system code runs.
+     */
+    public static final int SYSTEM_UID = 1000;
+
+    /**
+     * Defines the UID/GID under which the telephony code runs.
+     */
+    public static final int PHONE_UID = 1001;
+
+    /**
+     * Defines the start of a range of UIDs (and GIDs), going from this
+     * number to {@link #LAST_APPLICATION_UID} that are reserved for assigning
+     * to applications.
+     */
+    public static final int FIRST_APPLICATION_UID = 10000;
+    /**
+     * Last of application-specific UIDs starting at
+     * {@link #FIRST_APPLICATION_UID}.
+     */
+    public static final int LAST_APPLICATION_UID = 99999;
+
+    /**
+     * Defines a secondary group id for access to the bluetooth hardware.
+     */
+    public static final int BLUETOOTH_GID = 2000;
+    
+    /**
+     * Standard priority of application threads.
+     * Use with {@link #setThreadPriority(int)} and
+     * {@link #setThreadPriority(int, int)}, <b>not</b> with the normal
+     * {@link java.lang.Thread} class.
+     */
+    public static final int THREAD_PRIORITY_DEFAULT = 0;
+
+    /*
+     * ***************************************
+     * ** Keep in sync with utils/threads.h **
+     * ***************************************
+     */
+    
+    /**
+     * Lowest available thread priority.  Only for those who really, really
+     * don't want to run if anything else is happening.
+     * Use with {@link #setThreadPriority(int)} and
+     * {@link #setThreadPriority(int, int)}, <b>not</b> with the normal
+     * {@link java.lang.Thread} class.
+     */
+    public static final int THREAD_PRIORITY_LOWEST = 19;
+    
+    /**
+     * Standard priority background threads.  This gives your thread a slightly
+     * lower than normal priority, so that it will have less chance of impacting
+     * the responsiveness of the user interface.
+     * Use with {@link #setThreadPriority(int)} and
+     * {@link #setThreadPriority(int, int)}, <b>not</b> with the normal
+     * {@link java.lang.Thread} class.
+     */
+    public static final int THREAD_PRIORITY_BACKGROUND = 10;
+    
+    /**
+     * Standard priority of threads that are currently running a user interface
+     * that the user is interacting with.  Applications can not normally
+     * change to this priority; the system will automatically adjust your
+     * application threads as the user moves through the UI.
+     * Use with {@link #setThreadPriority(int)} and
+     * {@link #setThreadPriority(int, int)}, <b>not</b> with the normal
+     * {@link java.lang.Thread} class.
+     */
+    public static final int THREAD_PRIORITY_FOREGROUND = -2;
+    
+    /**
+     * Standard priority of system display threads, involved in updating
+     * the user interface.  Applications can not
+     * normally change to this priority.
+     * Use with {@link #setThreadPriority(int)} and
+     * {@link #setThreadPriority(int, int)}, <b>not</b> with the normal
+     * {@link java.lang.Thread} class.
+     */
+    public static final int THREAD_PRIORITY_DISPLAY = -4;
+    
+    /**
+     * Standard priority of the most important display threads, for compositing
+     * the screen and retrieving input events.  Applications can not normally
+     * change to this priority.
+     * Use with {@link #setThreadPriority(int)} and
+     * {@link #setThreadPriority(int, int)}, <b>not</b> with the normal
+     * {@link java.lang.Thread} class.
+     */
+    public static final int THREAD_PRIORITY_URGENT_DISPLAY = -8;
+
+    /**
+     * Standard priority of audio threads.  Applications can not normally
+     * change to this priority.
+     * Use with {@link #setThreadPriority(int)} and
+     * {@link #setThreadPriority(int, int)}, <b>not</b> with the normal
+     * {@link java.lang.Thread} class.
+     */
+    public static final int THREAD_PRIORITY_AUDIO = -16;
+
+    /**
+     * Standard priority of the most important audio threads.
+     * Applications can not normally change to this priority.
+     * Use with {@link #setThreadPriority(int)} and
+     * {@link #setThreadPriority(int, int)}, <b>not</b> with the normal
+     * {@link java.lang.Thread} class.
+     */
+    public static final int THREAD_PRIORITY_URGENT_AUDIO = -19;
+
+    /**
+     * Minimum increment to make a priority more favorable.
+     */
+    public static final int THREAD_PRIORITY_MORE_FAVORABLE = -1;
+
+    /**
+     * Minimum increment to make a priority less favorable.
+     */
+    public static final int THREAD_PRIORITY_LESS_FAVORABLE = +1;
+
+    public static final int SIGNAL_QUIT = 3;
+    public static final int SIGNAL_KILL = 9;
+    public static final int SIGNAL_USR1 = 10;
+    
+    // State for communicating with zygote process
+
+    static LocalSocket sZygoteSocket;
+    static DataInputStream sZygoteInputStream;
+    static BufferedWriter sZygoteWriter;
+
+    /** true if previous zygote open failed */
+    static boolean sPreviousZygoteOpenFailed;
+
+    /**
+     * Start a new process.
+     * 
+     * <p>If processes are enabled, a new process is created and the
+     * static main() function of a <var>processClass</var> is executed there.
+     * The process will continue running after this function returns.
+     * 
+     * <p>If processes are not enabled, a new thread in the caller's
+     * process is created and main() of <var>processClass</var> called there.
+     * 
+     * <p>The niceName parameter, if not an empty string, is a custom name to
+     * give to the process instead of using processClass.  This allows you to
+     * make easily identifyable processes even if you are using the same base
+     * <var>processClass</var> to start them.
+     * 
+     * @param processClass The class to use as the process's main entry
+     *                     point.
+     * @param niceName A more readable name to use for the process.
+     * @param uid The user-id under which the process will run.
+     * @param gid The group-id under which the process will run.
+     * @param gids Additional group-ids associated with the process.
+     * @param enableDebugger True if debugging should be enabled for this process.
+     * @param zygoteArgs Additional arguments to supply to the zygote process.
+     * 
+     * @return int If > 0 the pid of the new process; if 0 the process is
+     *         being emulated by a thread
+     * @throws RuntimeException on fatal start failure
+     * 
+     * {@hide}
+     */
+    public static final int start(final String processClass,
+                                  final String niceName,
+                                  int uid, int gid, int[] gids,
+                                  int debugFlags,
+                                  String[] zygoteArgs)
+    {
+        if (supportsProcesses()) {
+            try {
+                return startViaZygote(processClass, niceName, uid, gid, gids,
+                        debugFlags, zygoteArgs);
+            } catch (ZygoteStartFailedEx ex) {
+                Log.e(LOG_TAG,
+                        "Starting VM process through Zygote failed");
+                throw new RuntimeException(
+                        "Starting VM process through Zygote failed", ex);
+            }
+        } else {
+            // Running in single-process mode
+            
+            Runnable runnable = new Runnable() {
+                        public void run() {
+                            Process.invokeStaticMain(processClass);
+                        }
+            };
+            
+            // Thread constructors must not be called with null names (see spec). 
+            if (niceName != null) {
+                new Thread(runnable, niceName).start();
+            } else {
+                new Thread(runnable).start();
+            }
+            
+            return 0;
+        }
+    }
+    
+    /**
+     * Start a new process.  Don't supply a custom nice name.
+     * {@hide}
+     */
+    public static final int start(String processClass, int uid, int gid,
+            int[] gids, int debugFlags, String[] zygoteArgs) {
+        return start(processClass, "", uid, gid, gids, 
+                debugFlags, zygoteArgs);
+    }
+
+    private static void invokeStaticMain(String className) {
+        Class cl;
+        Object args[] = new Object[1];
+
+        args[0] = new String[0];     //this is argv
+   
+        try {
+            cl = Class.forName(className);
+            cl.getMethod("main", new Class[] { String[].class })
+                    .invoke(null, args);            
+        } catch (Exception ex) {
+            // can be: ClassNotFoundException,
+            // NoSuchMethodException, SecurityException,
+            // IllegalAccessException, IllegalArgumentException
+            // InvocationTargetException
+            // or uncaught exception from main()
+
+            Log.e(LOG_TAG, "Exception invoking static main on " 
+                    + className, ex);
+
+            throw new RuntimeException(ex);
+        }
+
+    }
+
+    /** retry interval for opening a zygote socket */
+    static final int ZYGOTE_RETRY_MILLIS = 500;
+
+    /**
+     * Tries to open socket to Zygote process if not already open. If
+     * already open, does nothing.  May block and retry.
+     */
+    private static void openZygoteSocketIfNeeded() 
+            throws ZygoteStartFailedEx {
+
+        int retryCount;
+
+        if (sPreviousZygoteOpenFailed) {
+            /*
+             * If we've failed before, expect that we'll fail again and
+             * don't pause for retries.
+             */
+            retryCount = 0;
+        } else {
+            retryCount = 10;            
+        }
+
+        /*
+         * See bug #811181: Sometimes runtime can make it up before zygote.
+         * Really, we'd like to do something better to avoid this condition,
+         * but for now just wait a bit...
+         */
+        for (int retry = 0
+                ; (sZygoteSocket == null) && (retry < (retryCount + 1))
+                ; retry++ ) {
+
+            if (retry > 0) {
+                try {
+                    Log.i("Zygote", "Zygote not up yet, sleeping...");
+                    Thread.sleep(ZYGOTE_RETRY_MILLIS);
+                } catch (InterruptedException ex) {
+                    // should never happen
+                }
+            }
+
+            try {
+                sZygoteSocket = new LocalSocket();
+
+                sZygoteSocket.connect(new LocalSocketAddress(ZYGOTE_SOCKET, 
+                        LocalSocketAddress.Namespace.RESERVED));
+
+                sZygoteInputStream
+                        = new DataInputStream(sZygoteSocket.getInputStream());
+
+                sZygoteWriter =
+                    new BufferedWriter(
+                            new OutputStreamWriter(
+                                    sZygoteSocket.getOutputStream()),
+                            256);
+
+                Log.i("Zygote", "Process: zygote socket opened");
+
+                sPreviousZygoteOpenFailed = false;
+                break;
+            } catch (IOException ex) {
+                if (sZygoteSocket != null) {
+                    try {
+                        sZygoteSocket.close();
+                    } catch (IOException ex2) {
+                        Log.e(LOG_TAG,"I/O exception on close after exception",
+                                ex2);
+                    }
+                }
+
+                sZygoteSocket = null;
+            }
+        }
+
+        if (sZygoteSocket == null) {
+            sPreviousZygoteOpenFailed = true;
+            throw new ZygoteStartFailedEx("connect failed");                 
+        }
+    }
+
+    /**
+     * Sends an argument list to the zygote process, which starts a new child
+     * and returns the child's pid. Please note: the present implementation
+     * replaces newlines in the argument list with spaces.
+     * @param args argument list
+     * @return PID of new child process
+     * @throws ZygoteStartFailedEx if process start failed for any reason
+     */
+    private static int zygoteSendArgsAndGetPid(ArrayList<String> args)
+            throws ZygoteStartFailedEx {
+
+        int pid;
+
+        openZygoteSocketIfNeeded();
+
+        try {
+            /**
+             * See com.android.internal.os.ZygoteInit.readArgumentList()
+             * Presently the wire format to the zygote process is:
+             * a) a count of arguments (argc, in essence)
+             * b) a number of newline-separated argument strings equal to count
+             *
+             * After the zygote process reads these it will write the pid of
+             * the child or -1 on failure.
+             */
+
+            sZygoteWriter.write(Integer.toString(args.size()));
+            sZygoteWriter.newLine();
+
+            int sz = args.size();
+            for (int i = 0; i < sz; i++) {
+                String arg = args.get(i);
+                if (arg.indexOf('\n') >= 0) {
+                    throw new ZygoteStartFailedEx(
+                            "embedded newlines not allowed");
+                }
+                sZygoteWriter.write(arg);
+                sZygoteWriter.newLine();
+            }
+
+            sZygoteWriter.flush();
+
+            // Should there be a timeout on this?
+            pid = sZygoteInputStream.readInt();
+
+            if (pid < 0) {
+                throw new ZygoteStartFailedEx("fork() failed");
+            }
+        } catch (IOException ex) {
+            try {
+                if (sZygoteSocket != null) {
+                    sZygoteSocket.close();
+                }
+            } catch (IOException ex2) {
+                // we're going to fail anyway
+                Log.e(LOG_TAG,"I/O exception on routine close", ex2);
+            }
+
+            sZygoteSocket = null;
+
+            throw new ZygoteStartFailedEx(ex);
+        }
+
+        return pid;
+    }
+
+    /**
+     * Starts a new process via the zygote mechanism.
+     *
+     * @param processClass Class name whose static main() to run
+     * @param niceName 'nice' process name to appear in ps
+     * @param uid a POSIX uid that the new process should setuid() to
+     * @param gid a POSIX gid that the new process shuold setgid() to
+     * @param gids null-ok; a list of supplementary group IDs that the
+     * new process should setgroup() to.
+     * @param enableDebugger True if debugging should be enabled for this process.
+     * @param extraArgs Additional arguments to supply to the zygote process.
+     * @return PID
+     * @throws ZygoteStartFailedEx if process start failed for any reason
+     */
+    private static int startViaZygote(final String processClass,
+                                  final String niceName,
+                                  final int uid, final int gid,
+                                  final int[] gids,
+                                  int debugFlags,
+                                  String[] extraArgs)
+                                  throws ZygoteStartFailedEx {
+        int pid;
+
+        synchronized(Process.class) {
+            ArrayList<String> argsForZygote = new ArrayList<String>();
+
+            // --runtime-init, --setuid=, --setgid=,
+            // and --setgroups= must go first
+            argsForZygote.add("--runtime-init");
+            argsForZygote.add("--setuid=" + uid);
+            argsForZygote.add("--setgid=" + gid);
+            if ((debugFlags & Zygote.DEBUG_ENABLE_DEBUGGER) != 0) {
+                argsForZygote.add("--enable-debugger");
+            }
+            if ((debugFlags & Zygote.DEBUG_ENABLE_CHECKJNI) != 0) {
+                argsForZygote.add("--enable-checkjni");
+            }
+            if ((debugFlags & Zygote.DEBUG_ENABLE_ASSERT) != 0) {
+                argsForZygote.add("--enable-assert");
+            }
+
+            //TODO optionally enable debuger
+            //argsForZygote.add("--enable-debugger");
+
+            // --setgroups is a comma-separated list
+            if (gids != null && gids.length > 0) {
+                StringBuilder sb = new StringBuilder();
+                sb.append("--setgroups=");
+
+                int sz = gids.length;
+                for (int i = 0; i < sz; i++) {
+                    if (i != 0) {
+                        sb.append(',');
+                    }
+                    sb.append(gids[i]);
+                }
+
+                argsForZygote.add(sb.toString());
+            }
+
+            if (niceName != null) {
+                argsForZygote.add("--nice-name=" + niceName);
+            }
+
+            argsForZygote.add(processClass);
+
+            if (extraArgs != null) {
+                for (String arg : extraArgs) {
+                    argsForZygote.add(arg);
+                }
+            }
+            
+            pid = zygoteSendArgsAndGetPid(argsForZygote);
+        }
+
+        if (pid <= 0) {
+            throw new ZygoteStartFailedEx("zygote start failed:" + pid);
+        }
+
+        return pid;
+    }
+    
+    /**
+     * Returns elapsed milliseconds of the time this process has run.
+     * @return  Returns the number of milliseconds this process has return.
+     */
+    public static final native long getElapsedCpuTime();
+    
+    /**
+     * Returns the identifier of this process, which can be used with
+     * {@link #killProcess} and {@link #sendSignal}.
+     */
+    public static final native int myPid();
+
+    /**
+     * Returns the identifier of the calling thread, which be used with
+     * {@link #setThreadPriority(int, int)}.
+     */
+    public static final native int myTid();
+
+    /**
+     * Returns the identifier of this process's user.
+     */
+    public static final native int myUid();
+
+    /**
+     * Returns the UID assigned to a particular user name, or -1 if there is
+     * none.  If the given string consists of only numbers, it is converted
+     * directly to a uid.
+     */
+    public static final native int getUidForName(String name);
+    
+    /**
+     * Returns the GID assigned to a particular user name, or -1 if there is
+     * none.  If the given string consists of only numbers, it is converted
+     * directly to a gid.
+     */
+    public static final native int getGidForName(String name);
+    
+    /**
+     * Set the priority of a thread, based on Linux priorities.
+     * 
+     * @param tid The identifier of the thread/process to change.
+     * @param priority A Linux priority level, from -20 for highest scheduling
+     * priority to 19 for lowest scheduling priority.
+     * 
+     * @throws IllegalArgumentException Throws IllegalArgumentException if
+     * <var>tid</var> does not exist.
+     * @throws SecurityException Throws SecurityException if your process does
+     * not have permission to modify the given thread, or to use the given
+     * priority.
+     */
+    public static final native void setThreadPriority(int tid, int priority)
+            throws IllegalArgumentException, SecurityException;
+    
+    /**
+     * Set the priority of the calling thread, based on Linux priorities.  See
+     * {@link #setThreadPriority(int, int)} for more information.
+     * 
+     * @param priority A Linux priority level, from -20 for highest scheduling
+     * priority to 19 for lowest scheduling priority.
+     * 
+     * @throws IllegalArgumentException Throws IllegalArgumentException if
+     * <var>tid</var> does not exist.
+     * @throws SecurityException Throws SecurityException if your process does
+     * not have permission to modify the given thread, or to use the given
+     * priority.
+     * 
+     * @see #setThreadPriority(int, int)
+     */
+    public static final native void setThreadPriority(int priority)
+            throws IllegalArgumentException, SecurityException;
+    
+    /**
+     * Return the current priority of a thread, based on Linux priorities.
+     * 
+     * @param tid The identifier of the thread/process to change.
+     * 
+     * @return Returns the current priority, as a Linux priority level,
+     * from -20 for highest scheduling priority to 19 for lowest scheduling
+     * priority.
+     * 
+     * @throws IllegalArgumentException Throws IllegalArgumentException if
+     * <var>tid</var> does not exist.
+     */
+    public static final native int getThreadPriority(int tid)
+            throws IllegalArgumentException;
+    
+    /**
+     * Determine whether the current environment supports multiple processes.
+     * 
+     * @return Returns true if the system can run in multiple processes, else
+     * false if everything is running in a single process.
+     */
+    public static final native boolean supportsProcesses();
+
+    /**
+     * Set the out-of-memory badness adjustment for a process.
+     * 
+     * @param pid The process identifier to set.
+     * @param amt Adjustment value -- linux allows -16 to +15.
+     * 
+     * @return Returns true if the underlying system supports this
+     *         feature, else false.
+     *         
+     * {@hide}
+     */
+    public static final native boolean setOomAdj(int pid, int amt);
+
+    /**
+     * Change this process's argv[0] parameter.  This can be useful to show
+     * more descriptive information in things like the 'ps' command.
+     * 
+     * @param text The new name of this process.
+     * 
+     * {@hide}
+     */
+    public static final native void setArgV0(String text);
+
+    /**
+     * Kill the process with the given PID.
+     * Note that, though this API allows us to request to
+     * kill any process based on its PID, the kernel will
+     * still impose standard restrictions on which PIDs you
+     * are actually able to kill.  Typically this means only
+     * the process running the caller's packages/application
+     * and any additional processes created by that app; packages
+     * sharing a common UID will also be able to kill each
+     * other's processes.
+     */
+    public static final void killProcess(int pid) {
+        sendSignal(pid, SIGNAL_KILL);
+    }
+
+    /** @hide */
+    public static final native int setUid(int uid);
+
+    /** @hide */
+    public static final native int setGid(int uid);
+
+    /**
+     * Send a signal to the given process.
+     * 
+     * @param pid The pid of the target process.
+     * @param signal The signal to send.
+     */
+    public static final native void sendSignal(int pid, int signal);
+    
+    /** @hide */
+    public static final native int getFreeMemory();
+    
+    /** @hide */
+    public static final native void readProcLines(String path,
+            String[] reqFields, long[] outSizes);
+    
+    /** @hide */
+    public static final native int[] getPids(String path, int[] lastArray);
+    
+    /** @hide */
+    public static final int PROC_TERM_MASK = 0xff;
+    /** @hide */
+    public static final int PROC_ZERO_TERM = 0;
+    /** @hide */
+    public static final int PROC_SPACE_TERM = (int)' ';
+    /** @hide */
+    public static final int PROC_COMBINE = 0x100;
+    /** @hide */
+    public static final int PROC_PARENS = 0x200;
+    /** @hide */
+    public static final int PROC_OUT_STRING = 0x1000;
+    /** @hide */
+    public static final int PROC_OUT_LONG = 0x2000;
+    /** @hide */
+    public static final int PROC_OUT_FLOAT = 0x4000;
+    
+    /** @hide */
+    public static final native boolean readProcFile(String file, int[] format,
+            String[] outStrings, long[] outLongs, float[] outFloats);
+
+    /**
+     * Gets the total Pss value for a given process, in bytes.
+     * 
+     * @param pid the process to the Pss for
+     * @return the total Pss value for the given process in bytes,
+     *  or -1 if the value cannot be determined 
+     * @hide
+     */
+    public static final native long getPss(int pid);
+}
diff --git a/core/java/android/os/Registrant.java b/core/java/android/os/Registrant.java
new file mode 100644
index 0000000..c1780b9
--- /dev/null
+++ b/core/java/android/os/Registrant.java
@@ -0,0 +1,124 @@
+/*
+ * 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 android.os;
+
+import android.os.Handler;
+import android.os.Message;
+
+import java.lang.ref.WeakReference;
+import java.util.HashMap;
+
+/** @hide */
+public class Registrant
+{
+    public
+    Registrant(Handler h, int what, Object obj)
+    {
+        refH = new WeakReference(h);
+        this.what = what;
+        userObj = obj;
+    }
+
+    public void
+    clear()
+    {
+        refH = null;
+        userObj = null;
+    }
+
+    public void
+    notifyRegistrant()
+    {
+        internalNotifyRegistrant (null, null);
+    }
+    
+    public void
+    notifyResult(Object result)
+    {
+        internalNotifyRegistrant (result, null);
+    }
+
+    public void
+    notifyException(Throwable exception)
+    {
+        internalNotifyRegistrant (null, exception);
+    }
+
+    /**
+     * This makes a copy of @param ar
+     */
+    public void
+    notifyRegistrant(AsyncResult ar)
+    {
+        internalNotifyRegistrant (ar.result, ar.exception);
+    }
+
+    /*package*/ void
+    internalNotifyRegistrant (Object result, Throwable exception)
+    {
+        Handler h = getHandler();
+
+        if (h == null) {
+            clear();
+        } else {
+            Message msg = Message.obtain();
+
+            msg.what = what;
+            
+            msg.obj = new AsyncResult(userObj, result, exception);
+            
+            h.sendMessage(msg);
+        }
+    }
+
+    /**
+     * NOTE: May return null if weak reference has been collected
+     */
+
+    public Message
+    messageForRegistrant()
+    {
+        Handler h = getHandler();
+
+        if (h == null) {
+            clear();
+
+            return null;
+        } else {
+            Message msg = h.obtainMessage();
+
+            msg.what = what;
+            msg.obj = userObj;
+
+            return msg;
+        }
+    }
+
+    public Handler
+    getHandler()
+    {
+        if (refH == null)
+            return null;
+
+        return (Handler) refH.get();
+    }
+
+    WeakReference   refH;
+    int             what;
+    Object          userObj;
+}
+
diff --git a/core/java/android/os/RegistrantList.java b/core/java/android/os/RegistrantList.java
new file mode 100644
index 0000000..56b9e2b
--- /dev/null
+++ b/core/java/android/os/RegistrantList.java
@@ -0,0 +1,128 @@
+/*
+ * 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 android.os;
+
+import android.os.Handler;         
+import android.os.Message;         
+
+import java.util.ArrayList;
+import java.util.HashMap;
+
+/** @hide */
+public class RegistrantList
+{
+    ArrayList   registrants = new ArrayList();      // of Registrant
+
+    public synchronized void
+    add(Handler h, int what, Object obj)
+    {
+        add(new Registrant(h, what, obj));
+    }
+
+    public synchronized void
+    addUnique(Handler h, int what, Object obj)
+    {
+        // if the handler is already in the registrant list, remove it
+        remove(h);
+        add(new Registrant(h, what, obj));        
+    }
+    
+    public synchronized void
+    add(Registrant r)
+    {
+        removeCleared();
+        registrants.add(r);
+    }
+
+    public synchronized void
+    removeCleared()
+    {
+        for (int i = registrants.size() - 1; i >= 0 ; i--) {
+            Registrant  r = (Registrant) registrants.get(i);
+            
+            if (r.refH == null) {
+                registrants.remove(i);
+            }
+        }
+    }
+
+    public synchronized int
+    size()
+    {
+        return registrants.size();
+    }
+
+    public synchronized Object
+    get(int index)
+    {
+        return registrants.get(index);
+    }
+
+    private synchronized void
+    internalNotifyRegistrants (Object result, Throwable exception)
+    {
+       for (int i = 0, s = registrants.size(); i < s ; i++) {
+            Registrant  r = (Registrant) registrants.get(i);
+            r.internalNotifyRegistrant(result, exception);
+       }
+    }
+    
+    public /*synchronized*/ void
+    notifyRegistrants()
+    {
+        internalNotifyRegistrants(null, null);
+    }
+
+    public /*synchronized*/ void
+    notifyException(Throwable exception)
+    {
+        internalNotifyRegistrants (null, exception);
+    }
+
+    public /*synchronized*/ void
+    notifyResult(Object result)
+    {
+        internalNotifyRegistrants (result, null);
+    }
+
+    
+    public /*synchronized*/ void
+    notifyRegistrants(AsyncResult ar)
+    {
+        internalNotifyRegistrants(ar.result, ar.exception);
+    }
+    
+    public synchronized void
+    remove(Handler h)
+    {
+        for (int i = 0, s = registrants.size() ; i < s ; i++) {
+            Registrant  r = (Registrant) registrants.get(i);
+            Handler     rh;
+
+            rh = r.getHandler();
+
+            /* Clean up both the requested registrant and
+             * any now-collected registrants
+             */
+            if (rh == null || rh == h) {
+                r.clear();
+            }
+        }
+
+        removeCleared();
+    }
+}
diff --git a/core/java/android/os/RemoteCallbackList.java b/core/java/android/os/RemoteCallbackList.java
new file mode 100644
index 0000000..04e7ef0
--- /dev/null
+++ b/core/java/android/os/RemoteCallbackList.java
@@ -0,0 +1,259 @@
+/*
+ * 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 android.os;
+
+import java.util.HashMap;
+
+/**
+ * Takes care of the grunt work of maintaining a list of remote interfaces,
+ * typically for the use of performing callbacks from a
+ * {@link android.app.Service} to its clients.  In particular, this:
+ * 
+ * <ul>
+ * <li> Keeps track of a set of registered {@link IInterface} callbacks,
+ * taking care to identify them through their underlying unique {@link IBinder}
+ * (by calling {@link IInterface#asBinder IInterface.asBinder()}.
+ * <li> Attaches a {@link IBinder.DeathRecipient IBinder.DeathRecipient} to
+ * each registered interface, so that it can be cleaned out of the list if its
+ * process goes away.
+ * <li> Performs locking of the underlying list of interfaces to deal with
+ * multithreaded incoming calls, and a thread-safe way to iterate over a
+ * snapshot of the list without holding its lock.
+ * </ul>
+ * 
+ * <p>To use this class, simply create a single instance along with your
+ * service, and call its {@link #register} and {@link #unregister} methods
+ * as client register and unregister with your service.  To call back on to
+ * the registered clients, use {@link #beginBroadcast},
+ * {@link #getBroadcastItem}, and {@link #finishBroadcast}.
+ * 
+ * <p>If a registered callback's process goes away, this class will take
+ * care of automatically removing it from the list.  If you want to do
+ * additional work in this situation, you can create a subclass that
+ * implements the {@link #onCallbackDied} method.
+ */
+public class RemoteCallbackList<E extends IInterface> {
+    /*package*/ HashMap<IBinder, Callback> mCallbacks
+            = new HashMap<IBinder, Callback>();
+    private IInterface[] mActiveBroadcast;
+    private boolean mKilled = false;
+    
+    private final class Callback implements IBinder.DeathRecipient {
+        final E mCallback;
+        
+        Callback(E callback) {
+            mCallback = callback;
+        }
+        
+        public void binderDied() {
+            synchronized (mCallbacks) {
+                mCallbacks.remove(mCallback.asBinder());
+            }
+            onCallbackDied(mCallback);
+        }
+    }
+    
+    /**
+     * Add a new callback to the list.  This callback will remain in the list
+     * until a corresponding call to {@link #unregister} or its hosting process
+     * goes away.  If the callback was already registered (determined by
+     * checking to see if the {@link IInterface#asBinder callback.asBinder()}
+     * object is already in the list), then it will be left as-is.
+     * Registrations are not counted; a single call to {@link #unregister}
+     * will remove a callback after any number calls to register it.
+     * 
+     * @param callback The callback interface to be added to the list.  Must
+     * not be null -- passing null here will cause a NullPointerException.
+     * Most services will want to check for null before calling this with
+     * an object given from a client, so that clients can't crash the
+     * service with bad data.
+     * 
+     * @return Returns true if the callback was successfully added to the list.
+     * Returns false if it was not added, either because {@link #kill} had
+     * previously been called or the callback's process has gone away.
+     * 
+     * @see #unregister
+     * @see #kill
+     * @see #onCallbackDied
+     */
+    public boolean register(E callback) {
+        synchronized (mCallbacks) {
+            if (mKilled) {
+                return false;
+            }
+            IBinder binder = callback.asBinder();
+            try {
+                Callback cb = new Callback(callback);
+                binder.linkToDeath(cb, 0);
+                mCallbacks.put(binder, cb);
+                return true;
+            } catch (RemoteException e) {
+                return false;
+            }
+        }
+    }
+    
+    /**
+     * Remove from the list a callback that was previously added with
+     * {@link #register}.  This uses the
+     * {@link IInterface#asBinder callback.asBinder()} object to correctly
+     * find the previous registration.
+     * Registrations are not counted; a single unregister call will remove
+     * a callback after any number calls to {@link #register} for it.
+     * 
+     * @param callback The callback to be removed from the list.  Passing
+     * null here will cause a NullPointerException, so you will generally want
+     * to check for null before calling.
+     * 
+     * @return Returns true if the callback was found and unregistered.  Returns
+     * false if the given callback was not found on the list.
+     * 
+     * @see #register
+     */
+    public boolean unregister(E callback) {
+        synchronized (mCallbacks) {
+            Callback cb = mCallbacks.remove(callback.asBinder());
+            if (cb != null) {
+                cb.mCallback.asBinder().unlinkToDeath(cb, 0);
+                return true;
+            }
+            return false;
+        }
+    }
+    
+    /**
+     * Disable this callback list.  All registered callbacks are unregistered,
+     * and the list is disabled so that future calls to {@link #register} will
+     * fail.  This should be used when a Service is stopping, to prevent clients
+     * from registering callbacks after it is stopped.
+     * 
+     * @see #register
+     */
+    public void kill() {
+        synchronized (mCallbacks) {
+            for (Callback cb : mCallbacks.values()) {
+                cb.mCallback.asBinder().unlinkToDeath(cb, 0);
+            }
+            mCallbacks.clear();
+            mKilled = true;
+        }
+    }
+    
+    /**
+     * Called when the process hosting a callback in the list has gone away.
+     * The default implementation does nothing.
+     * 
+     * @param callback The callback whose process has died.  Note that, since
+     * its process has died, you can not make any calls on to this interface.
+     * You can, however, retrieve its IBinder and compare it with another
+     * IBinder to see if it is the same object.
+     * 
+     * @see #register
+     */
+    public void onCallbackDied(E callback) {
+    }
+    
+    /**
+     * Prepare to start making calls to the currently registered callbacks.
+     * This creates a copy of the callback list, which you can retrieve items
+     * from using {@link #getBroadcastItem}.  Note that only one broadcast can
+     * be active at a time, so you must be sure to always call this from the
+     * same thread (usually by scheduling with {@link Handler} or
+     * do your own synchronization.  You must call {@link #finishBroadcast}
+     * when done.
+     * 
+     * <p>A typical loop delivering a broadcast looks like this:
+     * 
+     * <pre>
+     * final int N = callbacks.beginBroadcast();
+     * for (int i=0; i<N; i++) {
+     *     try {
+     *         callbacks.getBroadcastItem(i).somethingHappened();
+     *     } catch (RemoteException e) {
+     *         // The RemoteCallbackList will take care of removing
+     *         // the dead object for us.
+     *     }
+     * }
+     * callbacks.finishBroadcast();</pre>
+     * 
+     * @return Returns the number of callbacks in the broadcast, to be used
+     * with {@link #getBroadcastItem} to determine the range of indices you
+     * can supply.
+     * 
+     * @see #getBroadcastItem
+     * @see #finishBroadcast
+     */
+    public int beginBroadcast() {
+        synchronized (mCallbacks) {
+            final int N = mCallbacks.size();
+            if (N <= 0) {
+                return 0;
+            }
+            IInterface[] active = mActiveBroadcast;
+            if (active == null || active.length < N) {
+                mActiveBroadcast = active = new IInterface[N];
+            }
+            int i=0;
+            for (Callback cb : mCallbacks.values()) {
+                active[i++] = cb.mCallback;
+            }
+            return N;
+        }
+    }
+    
+    /**
+     * Retrieve an item in the active broadcast that was previously started
+     * with {@link #beginBroadcast}.  This can <em>only</em> be called after
+     * the broadcast is started, and its data is no longer valid after
+     * calling {@link #finishBroadcast}.
+     * 
+     * <p>Note that it is possible for the process of one of the returned
+     * callbacks to go away before you call it, so you will need to catch
+     * {@link RemoteException} when calling on to the returned object.
+     * The callback list itself, however, will take care of unregistering
+     * these objects once it detects that it is no longer valid, so you can
+     * handle such an exception by simply ignoring it.
+     * 
+     * @param index Which of the registered callbacks you would like to
+     * retrieve.  Ranges from 0 to 1-{@link #beginBroadcast}.
+     * 
+     * @return Returns the callback interface that you can call.  This will
+     * always be non-null.
+     * 
+     * @see #beginBroadcast
+     */
+    public E getBroadcastItem(int index) {
+        return (E)mActiveBroadcast[index];
+    }
+    
+    /**
+     * Clean up the state of a broadcast previously initiated by calling
+     * {@link #beginBroadcast}.  This must always be called when you are done
+     * with a broadcast.
+     * 
+     * @see #beginBroadcast
+     */
+    public void finishBroadcast() {
+        IInterface[] active = mActiveBroadcast;
+        if (active != null) {
+            final int N = active.length;
+            for (int i=0; i<N; i++) {
+                active[i] = null;
+            }
+        }
+    }
+}
diff --git a/core/java/android/os/RemoteException.java b/core/java/android/os/RemoteException.java
new file mode 100644
index 0000000..9d76156
--- /dev/null
+++ b/core/java/android/os/RemoteException.java
@@ -0,0 +1,27 @@
+/*
+ * 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 android.os;
+import android.util.AndroidException;
+
+/**
+ * Parent exception for all Binder remote-invocation errors
+ */
+public class RemoteException extends AndroidException {
+    public RemoteException() {
+        super();
+    }
+}
diff --git a/core/java/android/os/RemoteMailException.java b/core/java/android/os/RemoteMailException.java
new file mode 100644
index 0000000..1ac96d1
--- /dev/null
+++ b/core/java/android/os/RemoteMailException.java
@@ -0,0 +1,31 @@
+/*
+ * 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 android.os;
+
+/** @hide */
+public class RemoteMailException extends Exception
+{
+    public RemoteMailException()
+    {
+    }
+
+    public RemoteMailException(String s)
+    {
+        super(s);
+    }
+}
+
diff --git a/core/java/android/os/ServiceManager.java b/core/java/android/os/ServiceManager.java
new file mode 100644
index 0000000..b721665
--- /dev/null
+++ b/core/java/android/os/ServiceManager.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2007 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 android.os;
+
+import com.android.internal.os.BinderInternal;
+
+import android.util.Log;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/** @hide */
+public final class ServiceManager {
+    private static final String TAG = "ServiceManager";
+
+    private static IServiceManager sServiceManager;
+    private static HashMap<String, IBinder> sCache = new HashMap<String, IBinder>();
+
+    private static IServiceManager getIServiceManager() {
+        if (sServiceManager != null) {
+            return sServiceManager;
+        }
+
+        // Find the service manager
+        sServiceManager = ServiceManagerNative.asInterface(BinderInternal.getContextObject());
+        return sServiceManager;
+    }
+
+    /**
+     * Returns a reference to a service with the given name.
+     * 
+     * @param name the name of the service to get
+     * @return a reference to the service, or <code>null</code> if the service doesn't exist
+     */
+    public static IBinder getService(String name) {
+        try {
+            IBinder service = sCache.get(name);
+            if (service != null) {
+                return service;
+            } else {
+                return getIServiceManager().getService(name);
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "error in getService", e);
+        }
+        return null;
+    }
+
+    /**
+     * Place a new @a service called @a name into the service
+     * manager.
+     * 
+     * @param name the name of the new service
+     * @param service the service object
+     */
+    public static void addService(String name, IBinder service) {
+        try {
+            getIServiceManager().addService(name, service);
+        } catch (RemoteException e) {
+            Log.e(TAG, "error in addService", e);
+        }
+    }
+    
+    /**
+     * Retrieve an existing service called @a name from the
+     * service manager.  Non-blocking.
+     */
+    public static IBinder checkService(String name) {
+        try {
+            IBinder service = sCache.get(name);
+            if (service != null) {
+                return service;
+            } else {
+                return getIServiceManager().checkService(name);
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "error in checkService", e);
+            return null;
+        }
+    }
+
+    /**
+     * Return a list of all currently running services.
+     */
+    public static String[] listServices() throws RemoteException {
+        try {
+            return getIServiceManager().listServices();
+        } catch (RemoteException e) {
+            Log.e(TAG, "error in listServices", e);
+            return null;
+        }
+    }
+
+    /**
+     * This is only intended to be called when the process is first being brought
+     * up and bound by the activity manager. There is only one thread in the process
+     * at that time, so no locking is done.
+     * 
+     * @param cache the cache of service references
+     * @hide
+     */
+    public static void initServiceCache(Map<String, IBinder> cache) {
+        if (sCache.size() != 0 && Process.supportsProcesses()) {
+            throw new IllegalStateException("setServiceCache may only be called once");
+        }
+        sCache.putAll(cache);
+    }
+}
diff --git a/core/java/android/os/ServiceManagerNative.java b/core/java/android/os/ServiceManagerNative.java
new file mode 100644
index 0000000..2aab0e6
--- /dev/null
+++ b/core/java/android/os/ServiceManagerNative.java
@@ -0,0 +1,174 @@
+/*
+ * 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 android.os;
+
+
+/**
+ * Native implementation of the service manager.  Most clients will only
+ * care about getDefault() and possibly asInterface().
+ * @hide
+ */
+public abstract class ServiceManagerNative extends Binder implements IServiceManager
+{
+    /**
+     * Cast a Binder object into a service manager interface, generating
+     * a proxy if needed.
+     */
+    static public IServiceManager asInterface(IBinder obj)
+    {
+        if (obj == null) {
+            return null;
+        }
+        IServiceManager in =
+            (IServiceManager)obj.queryLocalInterface(descriptor);
+        if (in != null) {
+            return in;
+        }
+        
+        return new ServiceManagerProxy(obj);
+    }
+    
+    public ServiceManagerNative()
+    {
+        attachInterface(this, descriptor);
+    }
+    
+    public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
+    {
+        try {
+            switch (code) {
+            case IServiceManager.GET_SERVICE_TRANSACTION: {
+                data.enforceInterface(IServiceManager.descriptor);
+                String name = data.readString();
+                IBinder service = getService(name);
+                reply.writeStrongBinder(service);
+                return true;
+            }
+    
+            case IServiceManager.CHECK_SERVICE_TRANSACTION: {
+                data.enforceInterface(IServiceManager.descriptor);
+                String name = data.readString();
+                IBinder service = checkService(name);
+                reply.writeStrongBinder(service);
+                return true;
+            }
+    
+            case IServiceManager.ADD_SERVICE_TRANSACTION: {
+                data.enforceInterface(IServiceManager.descriptor);
+                String name = data.readString();
+                IBinder service = data.readStrongBinder();
+                addService(name, service);
+                return true;
+            }
+    
+            case IServiceManager.LIST_SERVICES_TRANSACTION: {
+                data.enforceInterface(IServiceManager.descriptor);
+                String[] list = listServices();
+                reply.writeStringArray(list);
+                return true;
+            }
+            
+            case IServiceManager.SET_PERMISSION_CONTROLLER_TRANSACTION: {
+                data.enforceInterface(IServiceManager.descriptor);
+                IPermissionController controller
+                        = IPermissionController.Stub.asInterface(
+                                data.readStrongBinder());
+                setPermissionController(controller);
+                return true;
+            }
+            }
+        } catch (RemoteException e) {
+        }
+        
+        return false;
+    }
+
+    public IBinder asBinder()
+    {
+        return this;
+    }
+}
+
+class ServiceManagerProxy implements IServiceManager {
+    public ServiceManagerProxy(IBinder remote) {
+        mRemote = remote;
+    }
+    
+    public IBinder asBinder() {
+        return mRemote;
+    }
+    
+    public IBinder getService(String name) throws RemoteException {
+        Parcel data = Parcel.obtain();
+        Parcel reply = Parcel.obtain();
+        data.writeInterfaceToken(IServiceManager.descriptor);
+        data.writeString(name);
+        mRemote.transact(GET_SERVICE_TRANSACTION, data, reply, 0);
+        IBinder binder = reply.readStrongBinder();
+        reply.recycle();
+        data.recycle();
+        return binder;
+    }
+
+    public IBinder checkService(String name) throws RemoteException {
+        Parcel data = Parcel.obtain();
+        Parcel reply = Parcel.obtain();
+        data.writeInterfaceToken(IServiceManager.descriptor);
+        data.writeString(name);
+        mRemote.transact(CHECK_SERVICE_TRANSACTION, data, reply, 0);
+        IBinder binder = reply.readStrongBinder();
+        reply.recycle();
+        data.recycle();
+        return binder;
+    }
+
+    public void addService(String name, IBinder service)
+            throws RemoteException {
+        Parcel data = Parcel.obtain();
+        Parcel reply = Parcel.obtain();
+        data.writeInterfaceToken(IServiceManager.descriptor);
+        data.writeString(name);
+        data.writeStrongBinder(service);
+        mRemote.transact(ADD_SERVICE_TRANSACTION, data, reply, 0);
+        reply.recycle();
+        data.recycle();
+    }
+    
+    public String[] listServices() throws RemoteException {
+        Parcel data = Parcel.obtain();
+        Parcel reply = Parcel.obtain();
+        data.writeInterfaceToken(IServiceManager.descriptor);
+        mRemote.transact(LIST_SERVICES_TRANSACTION, data, reply, 0);
+        String[] list = reply.readStringArray();
+        reply.recycle();
+        data.recycle();
+        return list;
+    }
+
+    public void setPermissionController(IPermissionController controller)
+            throws RemoteException {
+        Parcel data = Parcel.obtain();
+        Parcel reply = Parcel.obtain();
+        data.writeInterfaceToken(IServiceManager.descriptor);
+        data.writeStrongBinder(controller.asBinder());
+        mRemote.transact(SET_PERMISSION_CONTROLLER_TRANSACTION, data, reply, 0);
+        reply.recycle();
+        data.recycle();
+    }
+
+    private IBinder mRemote;
+}
diff --git a/core/java/android/os/StatFs.java b/core/java/android/os/StatFs.java
new file mode 100644
index 0000000..912bfdf
--- /dev/null
+++ b/core/java/android/os/StatFs.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2007 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 android.os;
+
+/**
+ * Retrieve overall information about the space on a filesystem.  This is a
+ * Wrapper for Unix statfs().
+ */
+public class StatFs {
+    /**
+     * Construct a new StatFs for looking at the stats of the
+     * filesystem at <var>path</var>.  Upon construction, the stat of
+     * the file system will be performed, and the values retrieved available
+     * from the methods on this class.
+     * 
+     * @param path A path in the desired file system to state.
+     */
+    public StatFs(String path) { native_setup(path); }
+    
+    /**
+     * Perform a restat of the file system referenced by this object.  This
+     * is the same as re-constructing the object with the same file system
+     * path, and the new stat values are available upon return.
+     */
+    public void restat(String path) { native_restat(path); }
+
+    @Override
+    protected void finalize() { native_finalize(); }
+
+    /**
+     * The size, in bytes, of a block on the file system.  This corresponds
+     * to the Unix statfs.f_bsize field.
+     */
+    public native int getBlockSize();
+
+    /**
+     * The total number of blocks on the file system.  This corresponds
+     * to the Unix statfs.f_blocks field.
+     */
+    public native int getBlockCount();
+
+    /**
+     * The total number of blocks that are free on the file system, including
+     * reserved blocks (that are not available to normal applications).  This
+     * corresponds to the Unix statfs.f_bfree field.  Most applications will
+     * want to use {@link #getAvailableBlocks()} instead.
+     */
+    public native int getFreeBlocks();
+
+    /**
+     * The number of blocks that are free on the file system and available to
+     * applications.  This corresponds to the Unix statfs.f_bavail field.
+     */
+    public native int getAvailableBlocks();    
+    
+    private int mNativeContext;
+    private native void native_restat(String path);
+    private native void native_setup(String path);
+    private native void native_finalize();
+}
diff --git a/core/java/android/os/SystemClock.java b/core/java/android/os/SystemClock.java
new file mode 100644
index 0000000..2b57b39
--- /dev/null
+++ b/core/java/android/os/SystemClock.java
@@ -0,0 +1,154 @@
+/*
+ * 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 android.os;
+
+
+/**
+ * Core timekeeping facilities.
+ *
+ * <p> Three different clocks are available, and they should not be confused:
+ *
+ * <ul>
+ *     <li> <p> {@link System#currentTimeMillis System.currentTimeMillis()}
+ *     is the standard "wall" clock (time and date) expressing milliseconds
+ *     since the epoch.  The wall clock can be set by the user or the phone
+ *     network (see {@link #setCurrentTimeMillis}), so the time may jump
+ *     backwards or forwards unpredictably.  This clock should only be used
+ *     when correspondence with real-world dates and times is important, such
+ *     as in a calendar or alarm clock application.  Interval or elapsed
+ *     time measurements should use a different clock.
+ *
+ *     <li> <p> {@link #uptimeMillis} is counted in milliseconds since the
+ *     system was booted.  This clock stops when the system enters deep
+ *     sleep (CPU off, display dark, device waiting for external input),
+ *     but is not affected by clock scaling, idle, or other power saving
+ *     mechanisms.  This is the basis for most interval timing
+ *     such as {@link Thread#sleep(long) Thread.sleep(millls)},
+ *     {@link Object#wait(long) Object.wait(millis)}, and
+ *     {@link System#nanoTime System.nanoTime()}.  This clock is guaranteed
+ *     to be monotonic, and is the recommended basis for the general purpose
+ *     interval timing of user interface events, performance measurements,
+ *     and anything else that does not need to measure elapsed time during
+ *     device sleep.  Most methods that accept a timestamp value expect the
+ *     {@link #uptimeMillis} clock.
+ *
+ *     <li> <p> {@link #elapsedRealtime} is counted in milliseconds since the
+ *     system was booted, including deep sleep.  This clock should be used
+ *     when measuring time intervals that may span periods of system sleep.
+ * </ul>
+ *
+ * There are several mechanisms for controlling the timing of events:
+ *
+ * <ul>
+ *     <li> <p> Standard functions like {@link Thread#sleep(long)
+ *     Thread.sleep(millis)} and {@link Object#wait(long) Object.wait(millis)}
+ *     are always available.  These functions use the {@link #uptimeMillis}
+ *     clock; if the device enters sleep, the remainder of the time will be
+ *     postponed until the device wakes up.  These synchronous functions may
+ *     be interrupted with {@link Thread#interrupt Thread.interrupt()}, and
+ *     you must handle {@link InterruptedException}.
+ *
+ *     <li> <p> {@link #sleep SystemClock.sleep(millis)} is a utility function
+ *     very similar to {@link Thread#sleep(long) Thread.sleep(millis)}, but it
+ *     ignores {@link InterruptedException}.  Use this function for delays if
+ *     you do not use {@link Thread#interrupt Thread.interrupt()}, as it will
+ *     preserve the interrupted state of the thread.
+ *
+ *     <li> <p> The {@link android.os.Handler} class can schedule asynchronous
+ *     callbacks at an absolute or relative time.  Handler objects also use the
+ *     {@link #uptimeMillis} clock, and require an {@link android.os.Looper
+ *     event loop} (normally present in any GUI application).
+ *
+ *     <li> <p> The {@link android.app.AlarmManager} can trigger one-time or
+ *     recurring events which occur even when the device is in deep sleep
+ *     or your application is not running.  Events may be scheduled with your
+ *     choice of {@link java.lang.System#currentTimeMillis} (RTC) or
+ *     {@link #elapsedRealtime} (ELAPSED_REALTIME), and cause an
+ *     {@link android.content.Intent} broadcast when they occur.
+ * </ul>
+ */
+public final class SystemClock {
+    /**
+     * This class is uninstantiable.
+     */
+    private SystemClock() {
+        // This space intentionally left blank.
+    }
+
+    /**
+     * Waits a given number of milliseconds (of uptimeMillis) before returning.
+     * Similar to {@link java.lang.Thread#sleep(long)}, but does not throw
+     * {@link InterruptedException}; {@link Thread#interrupt()} events are
+     * deferred until the next interruptible operation.  Does not return until
+     * at least the specified number of milliseconds has elapsed.
+     *
+     * @param ms to sleep before returning, in milliseconds of uptime.
+     */
+    public static void sleep(long ms)
+    {
+        long start = uptimeMillis();
+        long duration = ms;
+        boolean interrupted = false;
+        do {
+            try {
+                Thread.sleep(duration);
+            }
+            catch (InterruptedException e) {
+                interrupted = true;
+            }
+            duration = start + ms - uptimeMillis();
+        } while (duration > 0);
+        
+        if (interrupted) {
+            // Important: we don't want to quietly eat an interrupt() event,
+            // so we make sure to re-interrupt the thread so that the next
+            // call to Thread.sleep() or Object.wait() will be interrupted.
+            Thread.currentThread().interrupt();
+        }
+    }
+    
+    /**
+     * Sets the current wall time, in milliseconds.  Requires the calling
+     * process to have appropriate permissions.
+     *
+     * @return if the clock was successfully set to the specified time.
+     */
+    native public static boolean setCurrentTimeMillis(long millis);
+
+    /**
+     * Returns milliseconds since boot, not counting time spent in deep sleep.
+     * <b>Note:</b> This value may get reset occasionally (before it would
+     * otherwise wrap around).
+     *
+     * @return milliseconds of non-sleep uptime since boot.
+     */
+    native public static long uptimeMillis();
+
+    /**
+     * Returns milliseconds since boot, including time spent in sleep.
+     *
+     * @return elapsed milliseconds since boot.
+     */
+    native public static long elapsedRealtime();
+    
+    /**
+     * Returns milliseconds running in the current thread.
+     * 
+     * @return elapsed milliseconds in the thread
+     */
+    public static native long currentThreadTimeMillis();
+}
diff --git a/core/java/android/os/SystemProperties.java b/core/java/android/os/SystemProperties.java
new file mode 100644
index 0000000..c3ae3c2
--- /dev/null
+++ b/core/java/android/os/SystemProperties.java
@@ -0,0 +1,143 @@
+/*
+ * 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 android.os;
+
+
+/**
+ * Gives access to the system properties store.  The system properties
+ * store contains a list of string key-value pairs.
+ *
+ * {@hide}
+ */
+public class SystemProperties
+{
+    public static final int PROP_NAME_MAX = 31;
+    public static final int PROP_VALUE_MAX = 91;
+
+    private static native String native_get(String key);
+    private static native String native_get(String key, String def);
+    private static native void native_set(String key, String def);
+
+    /**
+     * Get the value for the given key.
+     * @return an empty string if the key isn't found
+     * @throws IllegalArgumentException if the key exceeds 32 characters
+     */
+    public static String get(String key) {
+        if (key.length() > PROP_NAME_MAX) {
+            throw new IllegalArgumentException("key.length > " + PROP_NAME_MAX);
+        }
+        return native_get(key);
+    }
+
+    /**
+     * Get the value for the given key.
+     * @return if the key isn't found, return def if it isn't null, or an empty string otherwise
+     * @throws IllegalArgumentException if the key exceeds 32 characters
+     */
+    public static String get(String key, String def) {
+        if (key.length() > PROP_NAME_MAX) {
+            throw new IllegalArgumentException("key.length > " + PROP_NAME_MAX);
+        }
+        return native_get(key, def);
+    }
+
+    /**
+     * Get the value for the given key, and return as an integer.
+     * @param key the key to lookup
+     * @param def a default value to return
+     * @return the key parsed as an integer, or def if the key isn't found or
+     *         cannot be parsed
+     * @throws IllegalArgumentException if the key exceeds 32 characters
+     */
+    public static int getInt(String key, int def) {
+        try {
+            return Integer.parseInt(get(key));
+        } catch (NumberFormatException e) {
+            return def;
+        }
+    }
+
+    /**
+     * Get the value for the given key, and return as a long.
+     * @param key the key to lookup
+     * @param def a default value to return
+     * @return the key parsed as a long, or def if the key isn't found or
+     *         cannot be parsed
+     * @throws IllegalArgumentException if the key exceeds 32 characters
+     */
+    public static long getLong(String key, long def) {
+        try {
+            return Long.parseLong(get(key));
+        } catch (NumberFormatException e) {
+            return def;
+        }
+    }
+
+    /**
+     * Get the value for the given key, returned as a boolean.
+     * Values 'n', 'no', '0', 'false' or 'off' are considered false.
+     * Values 'y', 'yes', '1', 'true' or 'on' are considered true.
+     * (case insensitive).
+     * If the key does not exist, or has any other value, then the default
+     * result is returned.
+     * @param key the key to lookup
+     * @param def a default value to return
+     * @return the key parsed as a boolean, or def if the key isn't found or is
+     *         not able to be parsed as a boolean.
+     * @throws IllegalArgumentException if the key exceeds 32 characters
+     */
+    public static boolean getBoolean(String key, boolean def) {
+        String value = get(key);
+        // Deal with these quick cases first: not found, 0 and 1
+        if (value.equals("")) {
+            return def;
+        } else if (value.equals("0")) {
+            return false;
+        } else if (value.equals("1")) {
+            return true;
+        // now for slower (and hopefully less common) cases
+        } else if (value.equalsIgnoreCase("n") ||
+                   value.equalsIgnoreCase("no") ||
+                   value.equalsIgnoreCase("false") ||
+                   value.equalsIgnoreCase("off")) {
+            return false;
+        } else if (value.equalsIgnoreCase("y") ||
+                   value.equalsIgnoreCase("yes") ||
+                   value.equalsIgnoreCase("true") ||
+                   value.equalsIgnoreCase("on")) {
+            return true;
+        }
+        return def;
+    }
+
+    /**
+     * Set the value for the given key.
+     * @throws IllegalArgumentException if the key exceeds 32 characters
+     * @throws IllegalArgumentException if the value exceeds 92 characters
+     */
+    public static void set(String key, String val) {
+        if (key.length() > PROP_NAME_MAX) {
+            throw new IllegalArgumentException("key.length > " + PROP_NAME_MAX);
+        }
+        if (val != null && val.length() > PROP_VALUE_MAX) {
+            throw new IllegalArgumentException("val.length > " +
+                PROP_VALUE_MAX);
+        }
+        native_set(key, val);
+    }
+}
diff --git a/core/java/android/os/SystemService.java b/core/java/android/os/SystemService.java
new file mode 100644
index 0000000..447cd1f
--- /dev/null
+++ b/core/java/android/os/SystemService.java
@@ -0,0 +1,31 @@
+/*
+ * 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 android.os;
+
+/** @hide */
+public class SystemService
+{
+        /** Request that the init daemon start a named service. */
+    public static void start(String name) {
+        SystemProperties.set("ctl.start", name);
+    }
+    
+        /** Request that the init daemon stop a named service. */
+    public static void stop(String name) {
+        SystemProperties.set("ctl.stop", name);
+    }
+}
diff --git a/core/java/android/os/TokenWatcher.java b/core/java/android/os/TokenWatcher.java
new file mode 100755
index 0000000..ac3cc92
--- /dev/null
+++ b/core/java/android/os/TokenWatcher.java
@@ -0,0 +1,197 @@
+/*
+ * Copyright (C) 2007 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 android.os;
+
+import java.util.WeakHashMap;
+import java.util.Set;
+import android.util.Log;
+
+/**
+ * Helper class that helps you use IBinder objects as reference counted
+ * tokens.  IBinders make good tokens because we find out when they are
+ * removed
+ *
+ */
+public abstract class TokenWatcher
+{
+    /**
+     * Construct the TokenWatcher
+     *
+     * @param h A handler to call {@link #acquired} and {@link #released}
+     * on.  If you don't care, just call it like this, although your thread
+     * will have to be a Looper thread.
+     * <code>new TokenWatcher(new Handler())</code>
+     * @param tag A debugging tag for this TokenWatcher
+     */
+    public TokenWatcher(Handler h, String tag)
+    {
+        mHandler = h;
+        mTag = tag != null ? tag : "TokenWatcher";
+    }
+
+    /**
+     * Called when the number of active tokens goes from 0 to 1.
+     */
+    public abstract void acquired();
+
+    /**
+     * Called when the number of active tokens goes from 1 to 0.
+     */
+    public abstract void released();
+
+    /**
+     * Record that this token has been acquired.  When acquire is called, and
+     * the current count is 0, the acquired method is called on the given
+     * handler.
+     * 
+     * @param token An IBinder object.  If this token has already been acquired,
+     *              no action is taken.
+     * @param tag   A string used by the {@link #dump} method for debugging,
+     *              to see who has references.
+     */
+    public void acquire(IBinder token, String tag)
+    {
+        synchronized (mTokens) {
+            // explicitly checked to avoid bogus sendNotification calls because
+            // of the WeakHashMap and the GC
+            int oldSize = mTokens.size();
+
+            Death d = new Death(token, tag);
+            try {
+                token.linkToDeath(d, 0);
+            } catch (RemoteException e) {
+                return;
+            }
+            mTokens.put(token, d);
+
+            if (oldSize == 0 && !mAcquired) {
+                sendNotificationLocked(true);
+                mAcquired = true;
+            }
+        }
+    }
+
+    public void cleanup(IBinder token, boolean unlink)
+    {
+        synchronized (mTokens) {
+            Death d = mTokens.remove(token);
+            if (unlink && d != null) {
+                d.token.unlinkToDeath(d, 0);
+                d.token = null;
+            }
+
+            if (mTokens.size() == 0 && mAcquired) {
+                sendNotificationLocked(false);
+                mAcquired = false;
+            }
+        }
+    }
+
+    public void release(IBinder token)
+    {
+        cleanup(token, true);
+    }
+
+    public boolean isAcquired()
+    {
+        synchronized (mTokens) {
+            return mAcquired;
+        }
+    }
+
+    public void dump()
+    {
+        synchronized (mTokens) {
+            Set<IBinder> keys = mTokens.keySet();
+            Log.i(mTag, "Token count: " + mTokens.size());
+            int i = 0;
+            for (IBinder b: keys) {
+                Log.i(mTag, "[" + i + "] " + mTokens.get(b).tag + " - " + b);
+                i++;
+            }
+        }
+    }
+
+    private Runnable mNotificationTask = new Runnable() {
+        public void run()
+        {
+            int value;
+            synchronized (mTokens) {
+                value = mNotificationQueue;
+                mNotificationQueue = -1;
+            }
+            if (value == 1) {
+                acquired();
+            }
+            else if (value == 0) {
+                released();
+            }
+        }
+    };
+
+    private void sendNotificationLocked(boolean on)
+    {
+        int value = on ? 1 : 0;
+        if (mNotificationQueue == -1) {
+            // empty
+            mNotificationQueue = value;
+            mHandler.post(mNotificationTask);
+        }
+        else if (mNotificationQueue != value) {
+            // it's a pair, so cancel it
+            mNotificationQueue = -1;
+            mHandler.removeCallbacks(mNotificationTask);
+        }
+        // else, same so do nothing -- maybe we should warn?
+    }
+
+    private class Death implements IBinder.DeathRecipient
+    {
+        IBinder token;
+        String tag;
+
+        Death(IBinder token, String tag)
+        {
+            this.token = token;
+            this.tag = tag;
+        }
+
+        public void binderDied()
+        {
+            cleanup(token, false);
+        }
+
+        protected void finalize() throws Throwable
+        {
+            try {
+                if (token != null) {
+                    Log.w(mTag, "cleaning up leaked reference: " + tag);
+                    release(token);
+                }
+            }
+            finally {
+                super.finalize();
+            }
+        }
+    }
+
+    private WeakHashMap<IBinder,Death> mTokens = new WeakHashMap<IBinder,Death>();
+    private Handler mHandler;
+    private String mTag;
+    private int mNotificationQueue = -1;
+    private volatile boolean mAcquired = false;
+}
diff --git a/core/java/android/os/UEventObserver.java b/core/java/android/os/UEventObserver.java
new file mode 100644
index 0000000..b924e84
--- /dev/null
+++ b/core/java/android/os/UEventObserver.java
@@ -0,0 +1,191 @@
+/*
+ * 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 android.os;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+
+/**
+ * UEventObserver is an abstract class that receives UEvent's from the kernel.<p>
+ *
+ * Subclass UEventObserver, implementing onUEvent(UEvent event), then call
+ * startObserving() with a match string. The UEvent thread will then call your
+ * onUEvent() method when a UEvent occurs that contains your match string.<p>
+ *
+ * Call stopObserving() to stop receiving UEvent's.<p>
+ *
+ * There is only one UEvent thread per process, even if that process has
+ * multiple UEventObserver subclass instances. The UEvent thread starts when
+ * the startObserving() is called for the first time in that process. Once
+ * started the UEvent thread will not stop (although it can stop notifying
+ * UEventObserver's via stopObserving()).<p>
+ *
+ * @hide
+*/
+public abstract class UEventObserver {
+    private static final String TAG = UEventObserver.class.getSimpleName();
+
+    /**
+     * Representation of a UEvent.
+     */
+    static public class UEvent {
+        // collection of key=value pairs parsed from the uevent message
+        public HashMap<String,String> mMap = new HashMap<String,String>();
+
+        public UEvent(String message) {
+            int offset = 0;
+            int length = message.length();
+
+            while (offset < length) {
+                int equals = message.indexOf('=', offset);
+                int at = message.indexOf(0, offset);
+                if (at < 0) break;
+
+                if (equals > offset && equals < at) {
+                    // key is before the equals sign, and value is after
+                    mMap.put(message.substring(offset, equals),
+                            message.substring(equals + 1, at));
+                }
+
+                offset = at + 1;
+            }
+        }
+
+        public String get(String key) {
+            return mMap.get(key);
+        }
+
+        public String get(String key, String defaultValue) {
+            String result = mMap.get(key);
+            return (result == null ? defaultValue : result);
+        }
+
+        public String toString() {
+            return mMap.toString();
+        }
+    }
+
+    private static UEventThread sThread;
+    private static boolean sThreadStarted = false;
+
+    private static class UEventThread extends Thread {
+        /** Many to many mapping of string match to observer.
+         *  Multimap would be better, but not available in android, so use
+         *  an ArrayList where even elements are the String match and odd
+         *  elements the corresponding UEventObserver observer */
+        private ArrayList<Object> mObservers = new ArrayList<Object>();
+        
+        UEventThread() {
+            super("UEventObserver");
+        }
+        
+        public void run() {
+            native_setup();
+
+            byte[] buffer = new byte[1024];
+            int len;
+            while (true) {
+                len = next_event(buffer);
+                if (len > 0) {
+                    String bufferStr = new String(buffer, 0, len);  // easier to search a String
+                    synchronized (mObservers) {
+                        for (int i = 0; i < mObservers.size(); i += 2) {
+                            if (bufferStr.indexOf((String)mObservers.get(i)) != -1) {
+                                ((UEventObserver)mObservers.get(i+1))
+                                        .onUEvent(new UEvent(bufferStr));
+                            }
+                        }
+                    }
+                }
+            }
+        }
+        public void addObserver(String match, UEventObserver observer) {
+            synchronized(mObservers) {
+                mObservers.add(match);
+                mObservers.add(observer);
+            }
+        }
+        /** Removes every key/value pair where value=observer from mObservers */
+        public void removeObserver(UEventObserver observer) {
+            synchronized(mObservers) {
+                boolean found = true;
+                while (found) {
+                    found = false;
+                    for (int i = 0; i < mObservers.size(); i += 2) {
+                        if (mObservers.get(i+1) == observer) {
+                            mObservers.remove(i+1);
+                            mObservers.remove(i);
+                            found = true;
+                            break;
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    private static native void native_setup();
+    private static native int next_event(byte[] buffer);
+
+    private static final synchronized void ensureThreadStarted() {
+        if (sThreadStarted == false) {
+            sThread = new UEventThread();
+            sThread.start();
+            sThreadStarted = true;
+        }
+    }
+
+    /**
+     * Begin observation of UEvent's.<p>
+     * This method will cause the UEvent thread to start if this is the first
+     * invocation of startObserving in this process.<p>
+     * Once called, the UEvent thread will call onUEvent() when an incoming
+     * UEvent matches the specified string.<p>
+     * This method can be called multiple times to register multiple matches.
+     * Only one call to stopObserving is required even with multiple registered
+     * matches.
+     * @param match A substring of the UEvent to match. Use "" to match all
+     *              UEvent's
+     */
+    public final synchronized void startObserving(String match) {
+        ensureThreadStarted();
+        sThread.addObserver(match, this);
+    }
+
+    /**
+     * End observation of UEvent's.<p>
+     * This process's UEvent thread will never call onUEvent() on this
+     * UEventObserver after this call. Repeated calls have no effect.
+     */
+    public final synchronized void stopObserving() {
+        sThread.removeObserver(this);
+    }
+
+    /**
+     * Subclasses of UEventObserver should override this method to handle
+     * UEvents.
+     */
+    public abstract void onUEvent(UEvent event);
+
+    protected void finalize() throws Throwable {
+        try {
+            stopObserving();
+        } finally {
+            super.finalize();
+        }
+    }
+}
diff --git a/core/java/android/os/Vibrator.java b/core/java/android/os/Vibrator.java
new file mode 100644
index 0000000..0f75289
--- /dev/null
+++ b/core/java/android/os/Vibrator.java
@@ -0,0 +1,86 @@
+/*
+ * 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 android.os;
+
+/**
+ * Class that operates the vibrator on the device.
+ * <p>
+ * If your process exits, any vibration you started with will stop.
+ */
+public class Vibrator
+{
+    IHardwareService mService;
+
+    /** @hide */
+    public Vibrator()
+    {
+        mService = IHardwareService.Stub.asInterface(
+                ServiceManager.getService("hardware"));
+    }
+
+    /**
+     * Turn the vibrator on.
+     *
+     * @param milliseconds How long to vibrate for.
+     */
+    public void vibrate(long milliseconds)
+    {
+        try {
+            mService.vibrate(milliseconds);
+        } catch (RemoteException e) {
+        }
+    }
+
+    /**
+     * Vibrate with a given pattern.
+     *
+     * <p>
+     * Pass in an array of ints that are the times at which to turn on or off
+     * the vibrator.  The first one is how long to wait before turning it on,
+     * and then after that it alternates.  If you want to repeat, pass the
+     * index into the pattern at which to start the repeat.
+     *
+     * @param pattern an array of longs of times to turn the vibrator on or off.
+     * @param repeat the index into pattern at which to repeat, or -1 if
+     *        you don't want to repeat.
+     */
+    public void vibrate(long[] pattern, int repeat)
+    {
+        // catch this here because the server will do nothing.  pattern may
+        // not be null, let that be checked, because the server will drop it
+        // anyway
+        if (repeat < pattern.length) {
+            try {
+                mService.vibratePattern(pattern, repeat, new Binder());
+            } catch (RemoteException e) {
+            }
+        } else {
+            throw new ArrayIndexOutOfBoundsException();
+        }
+    }
+
+    /**
+     * Turn the vibrator off.
+     */
+    public void cancel()
+    {
+        try {
+            mService.cancelVibrate();
+        } catch (RemoteException e) {
+        }
+    }
+}
diff --git a/core/java/android/os/package.html b/core/java/android/os/package.html
new file mode 100644
index 0000000..fb0ecda
--- /dev/null
+++ b/core/java/android/os/package.html
@@ -0,0 +1,6 @@
+<HTML>
+<BODY>
+Provides basic operating system services, message passing, and inter-process
+communication on the device.
+</BODY>
+</HTML>
\ No newline at end of file
diff --git a/core/java/android/package.html b/core/java/android/package.html
new file mode 100644
index 0000000..1f1be2d
--- /dev/null
+++ b/core/java/android/package.html
@@ -0,0 +1,10 @@
+<HTML>
+<BODY>
+Contains the resource classes used by standard Android applications.
+<p>
+This package contains the resource classes that Android defines to be used in 
+Android applications. Third party developers can use many of them also for their applications.
+To learn more about how to use these classes, and what a 
+resource is, see <a href="{@docRoot}guide/topics/resources/index.html">Resources and Assets</a>. 
+</BODY>
+</HTML>
diff --git a/core/java/android/pim/ContactsAsyncHelper.java b/core/java/android/pim/ContactsAsyncHelper.java
new file mode 100644
index 0000000..a21281e
--- /dev/null
+++ b/core/java/android/pim/ContactsAsyncHelper.java
@@ -0,0 +1,336 @@
+/*
+ * 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 android.pim;
+
+import com.android.internal.telephony.CallerInfo;
+import com.android.internal.telephony.Connection;
+
+import android.content.ContentUris;
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.os.Message;
+import android.provider.Contacts;
+import android.provider.Contacts.People;
+import android.util.Log;
+import android.view.View;
+import android.widget.ImageView;
+
+import java.io.InputStream;
+
+/**
+ * Helper class for async access of images.
+ */
+public class ContactsAsyncHelper extends Handler {
+    
+    private static final boolean DBG = false;
+    private static final String LOG_TAG = "ContactsAsyncHelper";
+    
+    /**
+     * Interface for a WorkerHandler result return.
+     */
+    public interface OnImageLoadCompleteListener {
+        /**
+         * Called when the image load is complete.
+         * 
+         * @param imagePresent true if an image was found
+         */  
+        public void onImageLoadComplete(int token, Object cookie, ImageView iView,
+                boolean imagePresent);
+    }
+     
+    // constants
+    private static final int EVENT_LOAD_IMAGE = 1;
+    private static final int DEFAULT_TOKEN = -1;
+    
+    // static objects
+    private static Handler sThreadHandler;
+    private static ContactsAsyncHelper sInstance;
+    
+    static {
+        sInstance = new ContactsAsyncHelper();
+    }
+    
+    private static final class WorkerArgs {
+        public Context context;
+        public ImageView view;
+        public Uri uri;
+        public int defaultResource;
+        public Object result;
+        public Object cookie;
+        public OnImageLoadCompleteListener listener;
+        public CallerInfo info;
+    }
+    
+    /**
+     * public inner class to help out the ContactsAsyncHelper callers 
+     * with tracking the state of the CallerInfo Queries and image 
+     * loading.
+     * 
+     * Logic contained herein is used to remove the race conditions
+     * that exist as the CallerInfo queries run and mix with the image
+     * loads, which then mix with the Phone state changes.
+     */
+    public static class ImageTracker {
+
+        // Image display states
+        public static final int DISPLAY_UNDEFINED = 0;
+        public static final int DISPLAY_IMAGE = -1;
+        public static final int DISPLAY_DEFAULT = -2;
+        
+        // State of the image on the imageview.
+        private CallerInfo mCurrentCallerInfo;
+        private int displayMode;
+        
+        public ImageTracker() {
+            mCurrentCallerInfo = null;
+            displayMode = DISPLAY_UNDEFINED;
+        }
+
+        /**
+         * Used to see if the requested call / connection has a
+         * different caller attached to it than the one we currently
+         * have in the CallCard. 
+         */
+        public boolean isDifferentImageRequest(CallerInfo ci) {
+            // note, since the connections are around for the lifetime of the
+            // call, and the CallerInfo-related items as well, we can 
+            // definitely use a simple != comparison.
+            return (mCurrentCallerInfo != ci);
+        }
+        
+        public boolean isDifferentImageRequest(Connection connection) {
+            // if the connection does not exist, see if the 
+            // mCurrentCallerInfo is also null to match.
+            if (connection == null) {
+                if (DBG) Log.d(LOG_TAG, "isDifferentImageRequest: connection is null");
+                return (mCurrentCallerInfo != null);
+            }
+            Object o = connection.getUserData();
+
+            // if the call does NOT have a callerInfo attached
+            // then it is ok to query.
+            boolean runQuery = true;
+            if (o instanceof CallerInfo) {
+                runQuery = isDifferentImageRequest((CallerInfo) o);
+            }
+            return runQuery;
+        }
+        
+        /**
+         * Simple setter for the CallerInfo object. 
+         */
+        public void setPhotoRequest(CallerInfo ci) {
+            mCurrentCallerInfo = ci; 
+        }
+        
+        /**
+         * Convenience method used to retrieve the URI 
+         * representing the Photo file recorded in the attached 
+         * CallerInfo Object. 
+         */
+        public Uri getPhotoUri() {
+            if (mCurrentCallerInfo != null) {
+                return ContentUris.withAppendedId(People.CONTENT_URI, 
+                        mCurrentCallerInfo.person_id);
+            }
+            return null; 
+        }
+        
+        /**
+         * Simple setter for the Photo state. 
+         */
+        public void setPhotoState(int state) {
+            displayMode = state;
+        }
+        
+        /**
+         * Simple getter for the Photo state. 
+         */
+        public int getPhotoState() {
+            return displayMode;
+        }
+    }
+    
+    /**
+     * Thread worker class that handles the task of opening the stream and loading 
+     * the images.
+     */
+    private class WorkerHandler extends Handler {
+        public WorkerHandler(Looper looper) {
+            super(looper);
+        }
+        
+        public void handleMessage(Message msg) {
+            WorkerArgs args = (WorkerArgs) msg.obj;
+            
+            switch (msg.arg1) {
+                case EVENT_LOAD_IMAGE:
+                    InputStream inputStream = Contacts.People.openContactPhotoInputStream(
+                            args.context.getContentResolver(), args.uri);
+                    if (inputStream != null) {
+                        args.result = Drawable.createFromStream(inputStream, args.uri.toString());
+
+                        if (DBG) Log.d(LOG_TAG, "Loading image: " + msg.arg1 +
+                                " token: " + msg.what + " image URI: " + args.uri);
+                    } else {
+                        args.result = null;
+                        if (DBG) Log.d(LOG_TAG, "Problem with image: " + msg.arg1 + 
+                                " token: " + msg.what + " image URI: " + args.uri + 
+                                ", using default image.");
+                    }
+                    break;
+                default:
+            }
+            
+            // send the reply to the enclosing class. 
+            Message reply = ContactsAsyncHelper.this.obtainMessage(msg.what);
+            reply.arg1 = msg.arg1;
+            reply.obj = msg.obj;
+            reply.sendToTarget();
+        }
+    }
+    
+    /**
+     * Private constructor for static class
+     */
+    private ContactsAsyncHelper() {
+        HandlerThread thread = new HandlerThread("ContactsAsyncWorker");
+        thread.start();
+        sThreadHandler = new WorkerHandler(thread.getLooper());
+    }
+    
+    /**
+     * Convenience method for calls that do not want to deal with listeners and tokens.
+     */
+    public static final void updateImageViewWithContactPhotoAsync(Context context, 
+            ImageView imageView, Uri person, int placeholderImageResource) {
+        // Added additional Cookie field in the callee.
+        updateImageViewWithContactPhotoAsync (null, DEFAULT_TOKEN, null, null, context, 
+                imageView, person, placeholderImageResource);
+    }
+
+    /**
+     * Convenience method for calls that do not want to deal with listeners and tokens, but have
+     * a CallerInfo object to cache the image to.
+     */
+    public static final void updateImageViewWithContactPhotoAsync(CallerInfo info, Context context, 
+            ImageView imageView, Uri person, int placeholderImageResource) {
+        // Added additional Cookie field in the callee.
+        updateImageViewWithContactPhotoAsync (info, DEFAULT_TOKEN, null, null, context, 
+                imageView, person, placeholderImageResource);
+    }
+
+    
+    /**
+     * Start an image load, attach the result to the specified CallerInfo object.
+     * Note, when the query is started, we make the ImageView INVISIBLE if the
+     * placeholderImageResource value is -1.  When we're given a valid (!= -1)
+     * placeholderImageResource value, we make sure the image is visible.
+     */
+    public static final void updateImageViewWithContactPhotoAsync(CallerInfo info, int token, 
+            OnImageLoadCompleteListener listener, Object cookie, Context context, 
+            ImageView imageView, Uri person, int placeholderImageResource) {
+        
+        // in case the source caller info is null, the URI will be null as well.
+        // just update using the placeholder image in this case.
+        if (person == null) {
+            if (DBG) Log.d(LOG_TAG, "target image is null, just display placeholder.");
+            imageView.setVisibility(View.VISIBLE);
+            imageView.setImageResource(placeholderImageResource);
+            return;
+        }
+        
+        // Added additional Cookie field in the callee to handle arguments
+        // sent to the callback function.
+        
+        // setup arguments
+        WorkerArgs args = new WorkerArgs();
+        args.cookie = cookie;
+        args.context = context;
+        args.view = imageView;
+        args.uri = person;
+        args.defaultResource = placeholderImageResource;
+        args.listener = listener;
+        args.info = info;
+        
+        // setup message arguments
+        Message msg = sThreadHandler.obtainMessage(token);
+        msg.arg1 = EVENT_LOAD_IMAGE;
+        msg.obj = args;
+        
+        if (DBG) Log.d(LOG_TAG, "Begin loading image: " + args.uri + 
+                ", displaying default image for now.");
+        
+        // set the default image first, when the query is complete, we will
+        // replace the image with the correct one.
+        if (placeholderImageResource != -1) {
+            imageView.setVisibility(View.VISIBLE);
+            imageView.setImageResource(placeholderImageResource);
+        } else {
+            imageView.setVisibility(View.INVISIBLE);
+        }
+        
+        // notify the thread to begin working
+        sThreadHandler.sendMessage(msg);
+    }
+    
+    /**
+     * Called when loading is done.
+     */
+    @Override
+    public void handleMessage(Message msg) {
+        WorkerArgs args = (WorkerArgs) msg.obj;
+        switch (msg.arg1) {
+            case EVENT_LOAD_IMAGE:
+                boolean imagePresent = false;
+
+                // if the image has been loaded then display it, otherwise set default.
+                // in either case, make sure the image is visible.
+                if (args.result != null) {
+                    args.view.setVisibility(View.VISIBLE);
+                    args.view.setImageDrawable((Drawable) args.result);
+                    // make sure the cached photo data is updated.
+                    if (args.info != null) {
+                        args.info.cachedPhoto = (Drawable) args.result;
+                    }
+                    imagePresent = true;
+                } else if (args.defaultResource != -1) {
+                    args.view.setVisibility(View.VISIBLE);
+                    args.view.setImageResource(args.defaultResource);
+                }
+                
+                // Note that the data is cached.
+                if (args.info != null) {
+                    args.info.isCachedPhotoCurrent = true;
+                }
+                
+                // notify the listener if it is there.
+                if (args.listener != null) {
+                    if (DBG) Log.d(LOG_TAG, "Notifying listener: " + args.listener.toString() + 
+                            " image: " + args.uri + " completed");
+                    args.listener.onImageLoadComplete(msg.what, args.cookie, args.view,
+                            imagePresent);
+                }
+                break;
+            default:    
+        }
+    }
+}
diff --git a/core/java/android/pim/DateException.java b/core/java/android/pim/DateException.java
new file mode 100644
index 0000000..90bfe7f
--- /dev/null
+++ b/core/java/android/pim/DateException.java
@@ -0,0 +1,26 @@
+/*
+ * 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 android.pim;
+
+public class DateException extends Exception
+{
+    public DateException(String message)
+    {
+        super(message);
+    }
+}
+
diff --git a/core/java/android/pim/EventRecurrence.java b/core/java/android/pim/EventRecurrence.java
new file mode 100644
index 0000000..edf69ee
--- /dev/null
+++ b/core/java/android/pim/EventRecurrence.java
@@ -0,0 +1,421 @@
+/*
+ * 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 android.pim;
+
+import android.content.res.Resources;
+import android.text.TextUtils;
+import android.text.format.Time;
+
+import java.util.Calendar;
+
+public class EventRecurrence
+{
+    /**
+     * Thrown when a recurrence string provided can not be parsed according
+     * to RFC2445.
+     */
+    public static class InvalidFormatException extends RuntimeException
+    {
+        InvalidFormatException(String s) {
+            super(s);
+        }
+    }
+
+    public EventRecurrence()
+    {
+        wkst = MO;
+    }
+    
+    /**
+     * Parse an iCalendar/RFC2445 recur type according to Section 4.3.10.
+     */
+    public native void parse(String recur);
+
+    public void setStartDate(Time date) {
+        startDate = date;
+    }
+    
+    public static final int SECONDLY = 1;
+    public static final int MINUTELY = 2;
+    public static final int HOURLY = 3;
+    public static final int DAILY = 4;
+    public static final int WEEKLY = 5;
+    public static final int MONTHLY = 6;
+    public static final int YEARLY = 7;
+
+    public static final int SU = 0x00010000;
+    public static final int MO = 0x00020000;
+    public static final int TU = 0x00040000;
+    public static final int WE = 0x00080000;
+    public static final int TH = 0x00100000;
+    public static final int FR = 0x00200000;
+    public static final int SA = 0x00400000;
+
+    public Time      startDate;
+    public int       freq;
+    public String    until;
+    public int       count;
+    public int       interval;
+    public int       wkst;
+
+    public int[]     bysecond;
+    public int       bysecondCount;
+    public int[]     byminute;
+    public int       byminuteCount;
+    public int[]     byhour;
+    public int       byhourCount;
+    public int[]     byday;
+    public int[]     bydayNum;
+    public int       bydayCount;   
+    public int[]     bymonthday;
+    public int       bymonthdayCount;
+    public int[]     byyearday;
+    public int       byyeardayCount;
+    public int[]     byweekno;
+    public int       byweeknoCount;
+    public int[]     bymonth;
+    public int       bymonthCount;
+    public int[]     bysetpos;
+    public int       bysetposCount;
+
+    /**
+     * Converts one of the Calendar.SUNDAY constants to the SU, MO, etc.
+     * constants.  btw, I think we should switch to those here too, to
+     * get rid of this function, if possible.
+     */
+    public static int calendarDay2Day(int day)
+    {
+        switch (day)
+        {
+            case Calendar.SUNDAY:
+                return SU;
+            case Calendar.MONDAY:
+                return MO;
+            case Calendar.TUESDAY:
+                return TU;
+            case Calendar.WEDNESDAY:
+                return WE;
+            case Calendar.THURSDAY:
+                return TH;
+            case Calendar.FRIDAY:
+                return FR;
+            case Calendar.SATURDAY:
+                return SA;
+            default:
+                throw new RuntimeException("bad day of week: " + day);
+        }
+    }
+    
+    public static int timeDay2Day(int day)
+    {
+        switch (day)
+        {
+            case Time.SUNDAY:
+                return SU;
+            case Time.MONDAY:
+                return MO;
+            case Time.TUESDAY:
+                return TU;
+            case Time.WEDNESDAY:
+                return WE;
+            case Time.THURSDAY:
+                return TH;
+            case Time.FRIDAY:
+                return FR;
+            case Time.SATURDAY:
+                return SA;
+            default:
+                throw new RuntimeException("bad day of week: " + day);
+        }
+    }
+    public static int day2TimeDay(int day)
+    {
+        switch (day)
+        {
+            case SU:
+                return Time.SUNDAY;
+            case MO:
+                return Time.MONDAY;
+            case TU:
+                return Time.TUESDAY;
+            case WE:
+                return Time.WEDNESDAY;
+            case TH:
+                return Time.THURSDAY;
+            case FR:
+                return Time.FRIDAY;
+            case SA:
+                return Time.SATURDAY;
+            default:
+                throw new RuntimeException("bad day of week: " + day);
+        }
+    }
+
+    /**
+     * Converts one of the SU, MO, etc. constants to the Calendar.SUNDAY
+     * constants.  btw, I think we should switch to those here too, to
+     * get rid of this function, if possible.
+     */
+    public static int day2CalendarDay(int day)
+    {
+        switch (day)
+        {
+            case SU:
+                return Calendar.SUNDAY;
+            case MO:
+                return Calendar.MONDAY;
+            case TU:
+                return Calendar.TUESDAY;
+            case WE:
+                return Calendar.WEDNESDAY;
+            case TH:
+                return Calendar.THURSDAY;
+            case FR:
+                return Calendar.FRIDAY;
+            case SA:
+                return Calendar.SATURDAY;
+            default:
+                throw new RuntimeException("bad day of week: " + day);
+        }
+    }
+    
+    /**
+     * Converts one of the internal day constants (SU, MO, etc.) to the
+     * two-letter string representing that constant.
+     * 
+     * @throws IllegalArgumentException Thrown if the day argument is not one of
+     * the defined day constants.
+     * 
+     * @param day one the internal constants SU, MO, etc.
+     * @return the two-letter string for the day ("SU", "MO", etc.)
+     */
+    private static String day2String(int day) {
+        switch (day) {
+        case SU:
+            return "SU";
+        case MO:
+            return "MO";
+        case TU:
+            return "TU";
+        case WE:
+            return "WE";
+        case TH:
+            return "TH";
+        case FR:
+            return "FR";
+        case SA:
+            return "SA";
+        default:
+            throw new IllegalArgumentException("bad day argument: " + day);
+        }
+    }
+
+    private static void appendNumbers(StringBuilder s, String label,
+                                        int count, int[] values)
+    {
+        if (count > 0) {
+            s.append(label);
+            count--;
+            for (int i=0; i<count; i++) {
+                s.append(values[i]);
+                s.append(",");
+            }
+            s.append(values[count]);
+        }
+    }
+
+    private void appendByDay(StringBuilder s, int i)
+    {
+        int n = this.bydayNum[i];
+        if (n != 0) {
+            s.append(n);
+        }
+
+        String str = day2String(this.byday[i]);
+        s.append(str);
+    }
+
+    @Override
+    public String toString()
+    {
+        StringBuilder s = new StringBuilder();
+
+        s.append("FREQ=");
+        switch (this.freq)
+        {
+            case SECONDLY:
+                s.append("SECONDLY");
+                break;
+            case MINUTELY:
+                s.append("MINUTELY");
+                break;
+            case HOURLY:
+                s.append("HOURLY");
+                break;
+            case DAILY:
+                s.append("DAILY");
+                break;
+            case WEEKLY:
+                s.append("WEEKLY");
+                break;
+            case MONTHLY:
+                s.append("MONTHLY");
+                break;
+            case YEARLY:
+                s.append("YEARLY");
+                break;
+        }
+
+        if (!TextUtils.isEmpty(this.until)) {
+            s.append(";UNTIL=");
+            s.append(until);
+        }
+        
+        if (this.count != 0) {
+            s.append(";COUNT=");
+            s.append(this.count);
+        }
+
+        if (this.interval != 0) {
+            s.append(";INTERVAL=");
+            s.append(this.interval);
+        }
+
+        if (this.wkst != 0) {
+            s.append(";WKST=");
+            s.append(day2String(this.wkst));
+        }
+
+        appendNumbers(s, ";BYSECOND=", this.bysecondCount, this.bysecond);
+        appendNumbers(s, ";BYMINUTE=", this.byminuteCount, this.byminute);
+        appendNumbers(s, ";BYSECOND=", this.byhourCount, this.byhour);
+
+        // day
+        int count = this.bydayCount;
+        if (count > 0) {
+            s.append(";BYDAY=");
+            count--;
+            for (int i=0; i<count; i++) {
+                appendByDay(s, i);
+                s.append(",");
+            }
+            appendByDay(s, count);
+        }
+
+        appendNumbers(s, ";BYMONTHDAY=", this.bymonthdayCount, this.bymonthday);
+        appendNumbers(s, ";BYYEARDAY=", this.byyeardayCount, this.byyearday);
+        appendNumbers(s, ";BYWEEKNO=", this.byweeknoCount, this.byweekno);
+        appendNumbers(s, ";BYMONTH=", this.bymonthCount, this.bymonth);
+        appendNumbers(s, ";BYSETPOS=", this.bysetposCount, this.bysetpos);
+
+        return s.toString();
+    }
+    
+    public String getRepeatString() {
+        Resources r = Resources.getSystem();
+        
+        // TODO Implement "Until" portion of string, as well as custom settings
+        switch (this.freq) {
+            case DAILY:
+                return r.getString(com.android.internal.R.string.daily);
+            case WEEKLY: {
+                if (repeatsOnEveryWeekDay()) {
+                    return r.getString(com.android.internal.R.string.every_weekday);
+                } else {
+                    String format = r.getString(com.android.internal.R.string.weekly);
+                    StringBuilder days = new StringBuilder();
+                
+                    // Do one less iteration in the loop so the last element is added out of the
+                    // loop. This is done so the comma is not placed after the last item.
+                    int count = this.bydayCount - 1;
+                    if (count >= 0) {
+                        for (int i = 0 ; i < count ; i++) {
+                            days.append(dayToString(r, this.byday[i]));
+                            days.append(",");
+                        }
+                        days.append(dayToString(r, this.byday[count]));
+                    
+                        return String.format(format, days.toString());
+                    }
+                    
+                    // There is no "BYDAY" specifier, so use the day of the
+                    // first event.  For this to work, the setStartDate()
+                    // method must have been used by the caller to set the
+                    // date of the first event in the recurrence.
+                    if (startDate == null) {
+                        return null;
+                    }
+                    
+                    int day = timeDay2Day(startDate.weekDay);
+                    return String.format(format, dayToString(r, day));
+                }
+            }
+            case MONTHLY: {
+                return r.getString(com.android.internal.R.string.monthly);
+            }
+            case YEARLY:
+                return r.getString(com.android.internal.R.string.yearly);
+        }
+
+        return null;
+    }
+    
+    public boolean repeatsOnEveryWeekDay() {
+        if (this.freq != WEEKLY) {
+            return false; 
+        }
+        
+        int count = this.bydayCount;
+        if (count != 5) {
+            return false;
+        }
+        
+        for (int i = 0 ; i < count ; i++) {
+            int day = byday[i];
+            if (day == SU || day == SA) {
+                return false;
+            }
+        }
+        
+        return true;
+    }
+    
+    public boolean repeatsMonthlyOnDayCount() {
+        if (this.freq != MONTHLY) {
+            return false;
+        }
+        
+        if (bydayCount != 1 || bymonthdayCount != 0) {
+            return false;
+        }
+        
+        return true;
+    }
+    
+    private String dayToString(Resources r, int day) {
+        switch (day) {
+        case SU: return r.getString(com.android.internal.R.string.sunday);
+        case MO: return r.getString(com.android.internal.R.string.monday);
+        case TU: return r.getString(com.android.internal.R.string.tuesday);
+        case WE: return r.getString(com.android.internal.R.string.wednesday);
+        case TH: return r.getString(com.android.internal.R.string.thursday);
+        case FR: return r.getString(com.android.internal.R.string.friday);
+        case SA: return r.getString(com.android.internal.R.string.saturday);
+        default: throw new IllegalArgumentException("bad day argument: " + day);
+        }
+    }
+}
diff --git a/core/java/android/pim/ICalendar.java b/core/java/android/pim/ICalendar.java
new file mode 100644
index 0000000..cc0f45e
--- /dev/null
+++ b/core/java/android/pim/ICalendar.java
@@ -0,0 +1,644 @@
+/*
+ * Copyright (C) 2007 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 android.pim;
+
+import android.util.Log;
+import android.util.Config;
+
+import java.util.LinkedHashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Set;
+import java.util.ArrayList;
+
+/**
+ * Parses RFC 2445 iCalendar objects.
+ */
+public class ICalendar {
+
+    private static final String TAG = "Sync";
+
+    // TODO: keep track of VEVENT, VTODO, VJOURNAL, VFREEBUSY, VTIMEZONE, VALARM
+    // components, by type field or by subclass?  subclass would allow us to
+    // enforce grammars.
+
+    /**
+     * Exception thrown when an iCalendar object has invalid syntax.
+     */
+    public static class FormatException extends Exception {
+        public FormatException() {
+            super();
+        }
+
+        public FormatException(String msg) {
+            super(msg);
+        }
+
+        public FormatException(String msg, Throwable cause) {
+            super(msg, cause);
+        }
+    }
+
+    /**
+     * A component within an iCalendar (VEVENT, VTODO, VJOURNAL, VFEEBUSY,
+     * VTIMEZONE, VALARM).
+     */
+    public static class Component {
+
+        // components
+        private static final String BEGIN = "BEGIN";
+        private static final String END = "END";
+        private static final String NEWLINE = "\n";
+        public static final String VCALENDAR = "VCALENDAR";
+        public static final String VEVENT = "VEVENT";
+        public static final String VTODO = "VTODO";
+        public static final String VJOURNAL = "VJOURNAL";
+        public static final String VFREEBUSY = "VFREEBUSY";
+        public static final String VTIMEZONE = "VTIMEZONE";
+        public static final String VALARM = "VALARM";
+
+        private final String mName;
+        private final Component mParent; // see if we can get rid of this
+        private LinkedList<Component> mChildren = null;
+        private final LinkedHashMap<String, ArrayList<Property>> mPropsMap =
+                new LinkedHashMap<String, ArrayList<Property>>();
+
+        /**
+         * Creates a new component with the provided name.
+         * @param name The name of the component.
+         */
+        public Component(String name, Component parent) {
+            mName = name;
+            mParent = parent;
+        }
+
+        /**
+         * Returns the name of the component.
+         * @return The name of the component.
+         */
+        public String getName() {
+            return mName;
+        }
+
+        /**
+         * Returns the parent of this component.
+         * @return The parent of this component.
+         */
+        public Component getParent() {
+            return mParent;
+        }
+
+        /**
+         * Helper that lazily gets/creates the list of children.
+         * @return The list of children.
+         */
+        protected LinkedList<Component> getOrCreateChildren() {
+            if (mChildren == null) {
+                mChildren = new LinkedList<Component>();
+            }
+            return mChildren;
+        }
+
+        /**
+         * Adds a child component to this component.
+         * @param child The child component.
+         */
+        public void addChild(Component child) {
+            getOrCreateChildren().add(child);
+        }
+
+        /**
+         * Returns a list of the Component children of this component.  May be
+         * null, if there are no children.
+         *
+         * @return A list of the children.
+         */
+        public List<Component> getComponents() {
+            return mChildren;
+        }
+
+        /**
+         * Adds a Property to this component.
+         * @param prop
+         */
+        public void addProperty(Property prop) {
+            String name= prop.getName();
+            ArrayList<Property> props = mPropsMap.get(name);
+            if (props == null) {
+                props = new ArrayList<Property>();
+                mPropsMap.put(name, props);
+            }
+            props.add(prop);
+        }
+
+        /**
+         * Returns a set of the property names within this component.
+         * @return A set of property names within this component.
+         */
+        public Set<String> getPropertyNames() {
+            return mPropsMap.keySet();
+        }
+
+        /**
+         * Returns a list of properties with the specified name.  Returns null
+         * if there are no such properties.
+         * @param name The name of the property that should be returned.
+         * @return A list of properties with the requested name.
+         */
+        public List<Property> getProperties(String name) {
+            return mPropsMap.get(name);
+        }
+
+        /**
+         * Returns the first property with the specified name.  Returns null
+         * if there is no such property.
+         * @param name The name of the property that should be returned.
+         * @return The first property with the specified name.
+         */
+        public Property getFirstProperty(String name) {
+            List<Property> props = mPropsMap.get(name);
+            if (props == null || props.size() == 0) {
+                return null;
+            }
+            return props.get(0);
+        }
+
+        @Override
+        public String toString() {
+            StringBuilder sb = new StringBuilder();
+            toString(sb);
+            sb.append(NEWLINE);
+            return sb.toString();
+        }
+
+        /**
+         * Helper method that appends this component to a StringBuilder.  The
+         * caller is responsible for appending a newline at the end of the
+         * component.
+         */
+        public void toString(StringBuilder sb) {
+            sb.append(BEGIN);
+            sb.append(":");
+            sb.append(mName);
+            sb.append(NEWLINE);
+
+            // append the properties
+            for (String propertyName : getPropertyNames()) {
+                for (Property property : getProperties(propertyName)) {
+                    property.toString(sb);
+                    sb.append(NEWLINE);
+                }
+            }
+
+            // append the sub-components
+            if (mChildren != null) {
+                for (Component component : mChildren) {
+                    component.toString(sb);
+                    sb.append(NEWLINE);
+                }
+            }
+
+            sb.append(END);
+            sb.append(":");
+            sb.append(mName);
+        }
+    }
+
+    /**
+     * A property within an iCalendar component (e.g., DTSTART, DTEND, etc.,
+     * within a VEVENT).
+     */
+    public static class Property {
+        // properties
+        // TODO: do we want to list these here?  the complete list is long.
+        public static final String DTSTART = "DTSTART";
+        public static final String DTEND = "DTEND";
+        public static final String DURATION = "DURATION";
+        public static final String RRULE = "RRULE";
+        public static final String RDATE = "RDATE";
+        public static final String EXRULE = "EXRULE";
+        public static final String EXDATE = "EXDATE";
+        // ... need to add more.
+        
+        private final String mName;
+        private LinkedHashMap<String, ArrayList<Parameter>> mParamsMap =
+                new LinkedHashMap<String, ArrayList<Parameter>>();
+        private String mValue; // TODO: make this final?
+
+        /**
+         * Creates a new property with the provided name.
+         * @param name The name of the property.
+         */
+        public Property(String name) {
+            mName = name;
+        }
+
+        /**
+         * Creates a new property with the provided name and value.
+         * @param name The name of the property.
+         * @param value The value of the property.
+         */
+        public Property(String name, String value) {
+            mName = name;
+            mValue = value;
+        }
+
+        /**
+         * Returns the name of the property.
+         * @return The name of the property.
+         */
+        public String getName() {
+            return mName;
+        }
+
+        /**
+         * Returns the value of this property.
+         * @return The value of this property.
+         */
+        public String getValue() {
+            return mValue;
+        }
+
+        /**
+         * Sets the value of this property.
+         * @param value The desired value for this property.
+         */
+        public void setValue(String value) {
+            mValue = value;
+        }        
+
+        /**
+         * Adds a {@link Parameter} to this property.
+         * @param param The parameter that should be added.
+         */
+        public void addParameter(Parameter param) {
+            ArrayList<Parameter> params = mParamsMap.get(param.name);
+            if (params == null) {
+                params = new ArrayList<Parameter>();
+                mParamsMap.put(param.name, params);
+            }
+            params.add(param);
+        }
+
+        /**
+         * Returns the set of parameter names for this property.
+         * @return The set of parameter names for this property.
+         */
+        public Set<String> getParameterNames() {
+            return mParamsMap.keySet();
+        }
+
+        /**
+         * Returns the list of parameters with the specified name.  May return
+         * null if there are no such parameters.
+         * @param name The name of the parameters that should be returned.
+         * @return The list of parameters with the specified name.
+         */
+        public List<Parameter> getParameters(String name) {
+            return mParamsMap.get(name);
+        }
+
+        /**
+         * Returns the first parameter with the specified name.  May return
+         * nll if there is no such parameter.
+         * @param name The name of the parameter that should be returned.
+         * @return The first parameter with the specified name.
+         */
+        public Parameter getFirstParameter(String name) {
+            ArrayList<Parameter> params = mParamsMap.get(name);
+            if (params == null || params.size() == 0) {
+                return null;
+            }
+            return params.get(0);
+        }
+
+        @Override
+        public String toString() {
+            StringBuilder sb = new StringBuilder();
+            toString(sb);
+            return sb.toString();
+        }
+
+        /**
+         * Helper method that appends this property to a StringBuilder.  The
+         * caller is responsible for appending a newline after this property.
+         */
+        public void toString(StringBuilder sb) {
+            sb.append(mName);
+            Set<String> parameterNames = getParameterNames();
+            for (String parameterName : parameterNames) {
+                for (Parameter param : getParameters(parameterName)) {
+                    sb.append(";");
+                    param.toString(sb);
+                }
+            }
+            sb.append(":");
+            sb.append(mValue);
+        }
+    }
+
+    /**
+     * A parameter defined for an iCalendar property.
+     */
+    // TODO: make this a proper class rather than a struct?
+    public static class Parameter {
+        public String name;
+        public String value;
+
+        /**
+         * Creates a new empty parameter.
+         */
+        public Parameter() {
+        }
+
+        /**
+         * Creates a new parameter with the specified name and value.
+         * @param name The name of the parameter.
+         * @param value The value of the parameter.
+         */
+        public Parameter(String name, String value) {
+            this.name = name;
+            this.value = value;
+        }
+
+        @Override
+        public String toString() {
+            StringBuilder sb = new StringBuilder();
+            toString(sb);
+            return sb.toString();
+        }
+
+        /**
+         * Helper method that appends this parameter to a StringBuilder.
+         */
+        public void toString(StringBuilder sb) {
+            sb.append(name);
+            sb.append("=");
+            sb.append(value);
+        }
+    }
+
+    private static final class ParserState {
+        // public int lineNumber = 0;
+        public String line; // TODO: just point to original text
+        public int index;
+    }
+
+    // use factory method
+    private ICalendar() {
+    }
+
+    // TODO: get rid of this -- handle all of the parsing in one pass through
+    // the text.
+    private static String normalizeText(String text) {
+        // it's supposed to be \r\n, but not everyone does that
+        text = text.replaceAll("\r\n", "\n");
+        text = text.replaceAll("\r", "\n");
+
+        // we deal with line folding, by replacing all "\n " strings
+        // with nothing.  The RFC specifies "\r\n " to be folded, but
+        // we handle "\n " and "\r " too because we can get those.
+        text = text.replaceAll("\n ", "");
+
+        return text;
+    }
+
+    /**
+     * Parses text into an iCalendar component.  Parses into the provided
+     * component, if not null, or parses into a new component.  In the latter
+     * case, expects a BEGIN as the first line.  Returns the provided or newly
+     * created top-level component.
+     */
+    // TODO: use an index into the text, so we can make this a recursive
+    // function?
+    private static Component parseComponentImpl(Component component,
+                                                String text)
+            throws FormatException {
+        Component current = component;
+        ParserState state = new ParserState();
+        state.index = 0;
+
+        // split into lines
+        String[] lines = text.split("\n");
+
+        // each line is of the format:
+        // name *(";" param) ":" value
+        for (String line : lines) {
+            try {
+                current = parseLine(line, state, current);
+                // if the provided component was null, we will return the root
+                // NOTE: in this case, if the first line is not a BEGIN, a
+                // FormatException will get thrown.   
+                if (component == null) {
+                    component = current;
+                }
+            } catch (FormatException fe) {
+                if (Config.LOGV) {
+                    Log.v(TAG, "Cannot parse " + line, fe);
+                }
+                // for now, we ignore the parse error.  Google Calendar seems
+                // to be emitting some misformatted iCalendar objects.
+            }
+            continue;
+        }
+        return component;
+    }
+
+    /**
+     * Parses a line into the provided component.  Creates a new component if
+     * the line is a BEGIN, adding the newly created component to the provided
+     * parent.  Returns whatever component is the current one (to which new
+     * properties will be added) in the parse.
+     */
+    private static Component parseLine(String line, ParserState state,
+                                       Component component)
+            throws FormatException {
+        state.line = line;
+        int len = state.line.length();
+
+        // grab the name
+        char c = 0;
+        for (state.index = 0; state.index < len; ++state.index) {
+            c = line.charAt(state.index);
+            if (c == ';' || c == ':') {
+                break;
+            }
+        }
+        String name = line.substring(0, state.index);
+
+        if (component == null) {
+            if (!Component.BEGIN.equals(name)) {
+                throw new FormatException("Expected BEGIN");
+            }
+        }
+
+        Property property;
+        if (Component.BEGIN.equals(name)) {
+            // start a new component
+            String componentName = extractValue(state);
+            Component child = new Component(componentName, component);
+            if (component != null) {
+                component.addChild(child);
+            }
+            return child;
+        } else if (Component.END.equals(name)) {
+            // finish the current component
+            String componentName = extractValue(state);
+            if (component == null ||
+                    !componentName.equals(component.getName())) {
+                throw new FormatException("Unexpected END " + componentName);
+            }
+            return component.getParent();
+        } else {
+            property = new Property(name);
+        }
+
+        if (c == ';') {
+            Parameter parameter = null;
+            while ((parameter = extractParameter(state)) != null) {
+                property.addParameter(parameter);
+            }
+        }
+        String value = extractValue(state);
+        property.setValue(value);
+        component.addProperty(property);
+        return component;
+    }
+
+    /**
+     * Extracts the value ":..." on the current line.  The first character must
+     * be a ':'.
+     */
+    private static String extractValue(ParserState state)
+            throws FormatException {
+        String line = state.line;
+        if (state.index >= line.length() || line.charAt(state.index) != ':') {
+            throw new FormatException("Expected ':' before end of line in "
+                    + line);
+        }
+        String value = line.substring(state.index + 1);
+        state.index = line.length() - 1;
+        return value;
+    }
+
+    /**
+     * Extracts the next parameter from the line, if any.  If there are no more
+     * parameters, returns null.
+     */
+    private static Parameter extractParameter(ParserState state)
+            throws FormatException {
+        String text = state.line;
+        int len = text.length();
+        Parameter parameter = null;
+        int startIndex = -1;
+        int equalIndex = -1;
+        while (state.index < len) {
+            char c = text.charAt(state.index);
+            if (c == ':') {
+                if (parameter != null) {
+                    if (equalIndex == -1) {
+                        throw new FormatException("Expected '=' within "
+                                + "parameter in " + text);
+                    }
+                    parameter.value = text.substring(equalIndex + 1,
+                                                     state.index);
+                }
+                return parameter; // may be null
+            } else if (c == ';') {
+                if (parameter != null) {
+                    if (equalIndex == -1) {
+                        throw new FormatException("Expected '=' within "
+                                + "parameter in " + text);
+                    }
+                    parameter.value = text.substring(equalIndex + 1,
+                                                     state.index);
+                    return parameter;
+                } else {
+                    parameter = new Parameter();
+                    startIndex = state.index;
+                }
+            } else if (c == '=') {
+                equalIndex = state.index;
+                if ((parameter == null) || (startIndex == -1)) {
+                    throw new FormatException("Expected ';' before '=' in "
+                            + text);
+                }
+                parameter.name = text.substring(startIndex + 1, equalIndex);
+            }
+            ++state.index;
+        }
+        throw new FormatException("Expected ':' before end of line in " + text);
+    }
+
+    /**
+     * Parses the provided text into an iCalendar object.  The top-level
+     * component must be of type VCALENDAR.
+     * @param text The text to be parsed.
+     * @return The top-level VCALENDAR component.
+     * @throws FormatException Thrown if the text could not be parsed into an
+     * iCalendar VCALENDAR object.
+     */
+    public static Component parseCalendar(String text) throws FormatException {
+        Component calendar = parseComponent(null, text);
+        if (calendar == null || !Component.VCALENDAR.equals(calendar.getName())) {
+            throw new FormatException("Expected " + Component.VCALENDAR);
+        }
+        return calendar;
+    }
+
+    /**
+     * Parses the provided text into an iCalendar event.  The top-level
+     * component must be of type VEVENT.
+     * @param text The text to be parsed.
+     * @return The top-level VEVENT component.
+     * @throws FormatException Thrown if the text could not be parsed into an
+     * iCalendar VEVENT.
+     */
+    public static Component parseEvent(String text) throws FormatException {
+        Component event = parseComponent(null, text);
+        if (event == null || !Component.VEVENT.equals(event.getName())) {
+            throw new FormatException("Expected " + Component.VEVENT);
+        }
+        return event;
+    }
+
+    /**
+     * Parses the provided text into an iCalendar component.
+     * @param text The text to be parsed.
+     * @return The top-level component.
+     * @throws FormatException Thrown if the text could not be parsed into an
+     * iCalendar component.
+     */
+    public static Component parseComponent(String text) throws FormatException {
+        return parseComponent(null, text);
+    }
+
+    /**
+     * Parses the provided text, adding to the provided component.
+     * @param component The component to which the parsed iCalendar data should
+     * be added.
+     * @param text The text to be parsed.
+     * @return The top-level component.
+     * @throws FormatException Thrown if the text could not be parsed as an
+     * iCalendar object.
+     */
+    public static Component parseComponent(Component component, String text)
+        throws FormatException {
+        text = normalizeText(text);
+        return parseComponentImpl(component, text);
+    }
+}
diff --git a/core/java/android/pim/RecurrenceSet.java b/core/java/android/pim/RecurrenceSet.java
new file mode 100644
index 0000000..1a287c8
--- /dev/null
+++ b/core/java/android/pim/RecurrenceSet.java
@@ -0,0 +1,398 @@
+/*
+ * Copyright (C) 2007 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 android.pim;
+
+import android.content.ContentValues;
+import android.database.Cursor;
+import android.os.Bundle;
+import android.provider.Calendar;
+import android.text.TextUtils;
+import android.text.format.Time;
+import android.util.Config;
+import android.util.Log;
+
+import java.util.List;
+
+/**
+ * Basic information about a recurrence, following RFC 2445 Section 4.8.5.
+ * Contains the RRULEs, RDATE, EXRULEs, and EXDATE properties.
+ */
+public class RecurrenceSet {
+
+    private final static String TAG = "CalendarProvider";
+
+    private final static String RULE_SEPARATOR = "\n";
+
+    // TODO: make these final?
+    public EventRecurrence[] rrules = null;
+    public long[] rdates = null;
+    public EventRecurrence[] exrules = null;
+    public long[] exdates = null;
+
+    /**
+     * Creates a new RecurrenceSet from information stored in the
+     * events table in the CalendarProvider.
+     * @param values The values retrieved from the Events table.
+     */
+    public RecurrenceSet(ContentValues values) {
+        String rruleStr = values.getAsString(Calendar.Events.RRULE);
+        String rdateStr = values.getAsString(Calendar.Events.RDATE);
+        String exruleStr = values.getAsString(Calendar.Events.EXRULE);
+        String exdateStr = values.getAsString(Calendar.Events.EXDATE);
+        init(rruleStr, rdateStr, exruleStr, exdateStr);
+    }
+
+    /**
+     * Creates a new RecurrenceSet from information stored in a database
+     * {@link Cursor} pointing to the events table in the
+     * CalendarProvider.  The cursor must contain the RRULE, RDATE, EXRULE,
+     * and EXDATE columns.
+     *
+     * @param cursor The cursor containing the RRULE, RDATE, EXRULE, and EXDATE
+     * columns.
+     */
+    public RecurrenceSet(Cursor cursor) {
+        int rruleColumn = cursor.getColumnIndex(Calendar.Events.RRULE);
+        int rdateColumn = cursor.getColumnIndex(Calendar.Events.RDATE);
+        int exruleColumn = cursor.getColumnIndex(Calendar.Events.EXRULE);
+        int exdateColumn = cursor.getColumnIndex(Calendar.Events.EXDATE);
+        String rruleStr = cursor.getString(rruleColumn);
+        String rdateStr = cursor.getString(rdateColumn);
+        String exruleStr = cursor.getString(exruleColumn);
+        String exdateStr = cursor.getString(exdateColumn);
+        init(rruleStr, rdateStr, exruleStr, exdateStr);
+    }
+
+    public RecurrenceSet(String rruleStr, String rdateStr,
+                  String exruleStr, String exdateStr) {
+        init(rruleStr, rdateStr, exruleStr, exdateStr);
+    }
+
+    private void init(String rruleStr, String rdateStr,
+                      String exruleStr, String exdateStr) {
+        if (!TextUtils.isEmpty(rruleStr) || !TextUtils.isEmpty(rdateStr)) {
+
+            if (!TextUtils.isEmpty(rruleStr)) {
+                String[] rruleStrs = rruleStr.split(RULE_SEPARATOR);
+                rrules = new EventRecurrence[rruleStrs.length];
+                for (int i = 0; i < rruleStrs.length; ++i) {
+                    EventRecurrence rrule = new EventRecurrence();
+                    rrule.parse(rruleStrs[i]);
+                    rrules[i] = rrule;
+                }
+            }
+
+            if (!TextUtils.isEmpty(rdateStr)) {
+                rdates = parseRecurrenceDates(rdateStr);
+            }
+
+            if (!TextUtils.isEmpty(exruleStr)) {
+                String[] exruleStrs = exruleStr.split(RULE_SEPARATOR);
+                exrules = new EventRecurrence[exruleStrs.length];
+                for (int i = 0; i < exruleStrs.length; ++i) {
+                    EventRecurrence exrule = new EventRecurrence();
+                    exrule.parse(exruleStr);
+                    exrules[i] = exrule;
+                }
+            }
+
+            if (!TextUtils.isEmpty(exdateStr)) {
+                exdates = parseRecurrenceDates(exdateStr);
+            }
+        }
+    }
+
+    /**
+     * Returns whether or not a recurrence is defined in this RecurrenceSet.
+     * @return Whether or not a recurrence is defined in this RecurrenceSet.
+     */
+    public boolean hasRecurrence() {
+        return (rrules != null || rdates != null);
+    }
+
+    /**
+     * Parses the provided RDATE or EXDATE string into an array of longs
+     * representing each date/time in the recurrence.
+     * @param recurrence The recurrence to be parsed.
+     * @return The list of date/times.
+     */
+    public static long[] parseRecurrenceDates(String recurrence) {
+        // TODO: use "local" time as the default.  will need to handle times
+        // that end in "z" (UTC time) explicitly at that point.
+        String tz = Time.TIMEZONE_UTC;
+        int tzidx = recurrence.indexOf(";");
+        if (tzidx != -1) {
+            tz = recurrence.substring(0, tzidx);
+            recurrence = recurrence.substring(tzidx + 1);
+        }
+        Time time = new Time(tz);
+        String[] rawDates = recurrence.split(",");
+        int n = rawDates.length;
+        long[] dates = new long[n];
+        for (int i = 0; i<n; ++i) {
+            // The timezone is updated to UTC if the time string specified 'Z'.
+            time.parse(rawDates[i]);
+            dates[i] = time.toMillis(false /* use isDst */);
+            time.timezone = tz;
+        }
+        return dates;
+    }
+
+    /**
+     * Populates the database map of values with the appropriate RRULE, RDATE,
+     * EXRULE, and EXDATE values extracted from the parsed iCalendar component.
+     * @param component The iCalendar component containing the desired
+     * recurrence specification.
+     * @param values The db values that should be updated.
+     * @return true if the component contained the necessary information
+     * to specify a recurrence.  The required fields are DTSTART,
+     * one of DTEND/DURATION, and one of RRULE/RDATE.  Returns false if
+     * there was an error, including if the date is out of range.
+     */
+    public static boolean populateContentValues(ICalendar.Component component,
+            ContentValues values) {
+        ICalendar.Property dtstartProperty =
+                component.getFirstProperty("DTSTART");
+        String dtstart = dtstartProperty.getValue();
+        ICalendar.Parameter tzidParam =
+                dtstartProperty.getFirstParameter("TZID");
+        // NOTE: the timezone may be null, if this is a floating time.
+        String tzid = tzidParam == null ? null : tzidParam.value;
+        Time start = new Time(tzidParam == null ? Time.TIMEZONE_UTC : tzid);
+        boolean inUtc = start.parse(dtstart);
+        boolean allDay = start.allDay;
+
+        if (inUtc) {
+            tzid = Time.TIMEZONE_UTC;
+        }
+                
+        String duration = computeDuration(start, component);
+        String rrule = flattenProperties(component, "RRULE");
+        String rdate = extractDates(component.getFirstProperty("RDATE"));
+        String exrule = flattenProperties(component, "EXRULE");
+        String exdate = extractDates(component.getFirstProperty("EXDATE"));
+
+        if ((TextUtils.isEmpty(dtstart))||
+                (TextUtils.isEmpty(duration))||
+                ((TextUtils.isEmpty(rrule))&&
+                        (TextUtils.isEmpty(rdate)))) {
+                if (Config.LOGD) {
+                    Log.d(TAG, "Recurrence missing DTSTART, DTEND/DURATION, "
+                                + "or RRULE/RDATE: "
+                                + component.toString());
+                }
+                return false;
+        }
+        
+        if (allDay) {
+        	// TODO: also change tzid to be UTC?  that would be consistent, but
+        	// that would not reflect the original timezone value back to the
+        	// server.
+        	start.timezone = Time.TIMEZONE_UTC;
+        }
+        long millis = start.toMillis(false /* use isDst */);
+        values.put(Calendar.Events.DTSTART, millis);
+        if (millis == -1) {
+            if (Config.LOGD) {
+                Log.d(TAG, "DTSTART is out of range: " + component.toString());
+            }
+            return false;
+        }
+        
+        values.put(Calendar.Events.RRULE, rrule);
+        values.put(Calendar.Events.RDATE, rdate);
+        values.put(Calendar.Events.EXRULE, exrule);
+        values.put(Calendar.Events.EXDATE, exdate);
+        values.put(Calendar.Events.EVENT_TIMEZONE, tzid);
+        values.put(Calendar.Events.DURATION, duration);
+        values.put(Calendar.Events.ALL_DAY, allDay ? 1 : 0);
+        return true;
+    }
+
+    public static boolean populateComponent(Cursor cursor,
+                                            ICalendar.Component component) {
+        
+        int dtstartColumn = cursor.getColumnIndex(Calendar.Events.DTSTART);
+        int durationColumn = cursor.getColumnIndex(Calendar.Events.DURATION);
+        int tzidColumn = cursor.getColumnIndex(Calendar.Events.EVENT_TIMEZONE);
+        int rruleColumn = cursor.getColumnIndex(Calendar.Events.RRULE);
+        int rdateColumn = cursor.getColumnIndex(Calendar.Events.RDATE);
+        int exruleColumn = cursor.getColumnIndex(Calendar.Events.EXRULE);
+        int exdateColumn = cursor.getColumnIndex(Calendar.Events.EXDATE);
+        int allDayColumn = cursor.getColumnIndex(Calendar.Events.ALL_DAY);
+
+
+        long dtstart = -1;
+        if (!cursor.isNull(dtstartColumn)) {
+            dtstart = cursor.getLong(dtstartColumn);
+        }
+        String duration = cursor.getString(durationColumn);
+        String tzid = cursor.getString(tzidColumn);
+        String rruleStr = cursor.getString(rruleColumn);
+        String rdateStr = cursor.getString(rdateColumn);
+        String exruleStr = cursor.getString(exruleColumn);
+        String exdateStr = cursor.getString(exdateColumn);
+        boolean allDay = cursor.getInt(allDayColumn) == 1;
+
+        if ((dtstart == -1) ||
+            (TextUtils.isEmpty(duration))||
+            ((TextUtils.isEmpty(rruleStr))&&
+                (TextUtils.isEmpty(rdateStr)))) {
+                // no recurrence.
+                return false;
+        }
+
+        ICalendar.Property dtstartProp = new ICalendar.Property("DTSTART");
+        Time dtstartTime = null;
+        if (!TextUtils.isEmpty(tzid)) {
+            if (!allDay) {
+                dtstartProp.addParameter(new ICalendar.Parameter("TZID", tzid));
+            }
+            dtstartTime = new Time(tzid);
+        } else {
+            // use the "floating" timezone
+            dtstartTime = new Time(Time.TIMEZONE_UTC);
+        }
+        
+        dtstartTime.set(dtstart);
+        // make sure the time is printed just as a date, if all day.
+        // TODO: android.pim.Time really should take care of this for us.
+        if (allDay) {
+            dtstartProp.addParameter(new ICalendar.Parameter("VALUE", "DATE"));
+            dtstartTime.allDay = true;
+            dtstartTime.hour = 0;
+            dtstartTime.minute = 0;
+            dtstartTime.second = 0;
+        }
+
+        dtstartProp.setValue(dtstartTime.format2445());
+        component.addProperty(dtstartProp);
+        ICalendar.Property durationProp = new ICalendar.Property("DURATION");
+        durationProp.setValue(duration);
+        component.addProperty(durationProp);
+
+        addPropertiesForRuleStr(component, "RRULE", rruleStr);
+        addPropertyForDateStr(component, "RDATE", rdateStr);
+        addPropertiesForRuleStr(component, "EXRULE", exruleStr);
+        addPropertyForDateStr(component, "EXDATE", exdateStr);
+        return true;
+    }
+
+    private static void addPropertiesForRuleStr(ICalendar.Component component,
+                                                String propertyName,
+                                                String ruleStr) {
+        if (TextUtils.isEmpty(ruleStr)) {
+            return;
+        }
+        String[] rrules = ruleStr.split(RULE_SEPARATOR);
+        for (String rrule : rrules) {
+            ICalendar.Property prop = new ICalendar.Property(propertyName);
+            prop.setValue(rrule);
+            component.addProperty(prop);
+        }
+    }
+
+    private static void addPropertyForDateStr(ICalendar.Component component,
+                                              String propertyName,
+                                              String dateStr) {
+        if (TextUtils.isEmpty(dateStr)) {
+            return;
+        }
+
+        ICalendar.Property prop = new ICalendar.Property(propertyName);
+        String tz = null;
+        int tzidx = dateStr.indexOf(";");
+        if (tzidx != -1) {
+            tz = dateStr.substring(0, tzidx);
+            dateStr = dateStr.substring(tzidx + 1);
+        }
+        if (!TextUtils.isEmpty(tz)) {
+            prop.addParameter(new ICalendar.Parameter("TZID", tz));
+        }
+        prop.setValue(dateStr);
+        component.addProperty(prop);
+    }
+    
+    private static String computeDuration(Time start,
+                                          ICalendar.Component component) {
+        // see if a duration is defined
+        ICalendar.Property durationProperty =
+                component.getFirstProperty("DURATION");
+        if (durationProperty != null) {
+            // just return the duration
+            return durationProperty.getValue();
+        }
+
+        // must compute a duration from the DTEND
+        ICalendar.Property dtendProperty =
+                component.getFirstProperty("DTEND");
+        if (dtendProperty == null) {
+            // no DURATION, no DTEND: 0 second duration
+            return "+P0S";
+        }
+        ICalendar.Parameter endTzidParameter =
+                dtendProperty.getFirstParameter("TZID");
+        String endTzid = (endTzidParameter == null)
+                ? start.timezone : endTzidParameter.value;
+
+        Time end = new Time(endTzid);
+        end.parse(dtendProperty.getValue());
+        long durationMillis = end.toMillis(false /* use isDst */) 
+                - start.toMillis(false /* use isDst */);
+        long durationSeconds = (durationMillis / 1000);
+        return "P" + durationSeconds + "S";
+    }
+
+    private static String flattenProperties(ICalendar.Component component,
+                                            String name) {
+        List<ICalendar.Property> properties = component.getProperties(name);
+        if (properties == null || properties.isEmpty()) {
+            return null;
+        }
+
+        if (properties.size() == 1) {
+            return properties.get(0).getValue();
+        }
+
+        StringBuilder sb = new StringBuilder();
+
+        boolean first = true;
+        for (ICalendar.Property property : component.getProperties(name)) {
+            if (first) {
+                first = false;
+            } else {
+                // TODO: use commas.  our RECUR parsing should handle that
+                // anyway.
+                sb.append(RULE_SEPARATOR);
+            }
+            sb.append(property.getValue());
+        }
+        return sb.toString();
+    }
+
+    private static String extractDates(ICalendar.Property recurrence) {
+        if (recurrence == null) {
+            return null;
+        }
+        ICalendar.Parameter tzidParam =
+                recurrence.getFirstParameter("TZID");
+        if (tzidParam != null) {
+            return tzidParam.value + ";" + recurrence.getValue();
+        }
+        return recurrence.getValue();
+    }
+}
diff --git a/core/java/android/pim/package.html b/core/java/android/pim/package.html
new file mode 100644
index 0000000..75237c9
--- /dev/null
+++ b/core/java/android/pim/package.html
@@ -0,0 +1,7 @@
+<HTML>
+<BODY>
+{@hide}
+Provides helpers for working with PIM (Personal Information Manager) data used
+by contact lists and calendars.
+</BODY>
+</HTML>
\ No newline at end of file
diff --git a/core/java/android/preference/CheckBoxPreference.java b/core/java/android/preference/CheckBoxPreference.java
new file mode 100644
index 0000000..1e9b7ae
--- /dev/null
+++ b/core/java/android/preference/CheckBoxPreference.java
@@ -0,0 +1,295 @@
+/*
+ * Copyright (C) 2007 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 android.preference;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.content.res.TypedArray;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.Checkable;
+import android.widget.TextView;
+
+/**
+ * A {@link Preference} that provides checkbox widget
+ * functionality.
+ * <p>
+ * This preference will store a boolean into the SharedPreferences.
+ * 
+ * @attr ref android.R.styleable#CheckBoxPreference_summaryOff
+ * @attr ref android.R.styleable#CheckBoxPreference_summaryOn
+ * @attr ref android.R.styleable#CheckBoxPreference_disableDependentsState
+ */
+public class CheckBoxPreference extends Preference {
+
+    private CharSequence mSummaryOn;
+    private CharSequence mSummaryOff;
+    
+    private boolean mChecked;
+    
+    private boolean mDisableDependentsState;
+    
+    public CheckBoxPreference(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+        
+        TypedArray a = context.obtainStyledAttributes(attrs,
+                com.android.internal.R.styleable.CheckBoxPreference, defStyle, 0);
+        mSummaryOn = a.getString(com.android.internal.R.styleable.CheckBoxPreference_summaryOn);
+        mSummaryOff = a.getString(com.android.internal.R.styleable.CheckBoxPreference_summaryOff);
+        mDisableDependentsState = a.getBoolean(
+                com.android.internal.R.styleable.CheckBoxPreference_disableDependentsState, false);
+        a.recycle();
+    }
+
+    public CheckBoxPreference(Context context, AttributeSet attrs) {
+        this(context, attrs, com.android.internal.R.attr.checkBoxPreferenceStyle);
+    }
+
+    public CheckBoxPreference(Context context) {
+        this(context, null);
+    }
+    
+    @Override
+    protected void onBindView(View view) {
+        super.onBindView(view);
+        
+        View checkboxView = view.findViewById(com.android.internal.R.id.checkbox);
+        if (checkboxView != null && checkboxView instanceof Checkable) {
+            ((Checkable) checkboxView).setChecked(mChecked);
+        }
+
+        // Sync the summary view
+        TextView summaryView = (TextView) view.findViewById(com.android.internal.R.id.summary);
+        if (summaryView != null) {
+            boolean useDefaultSummary = true;
+            if (mChecked && mSummaryOn != null) {
+                summaryView.setText(mSummaryOn);
+                useDefaultSummary = false;
+            } else if (!mChecked && mSummaryOff != null) {
+                summaryView.setText(mSummaryOff);
+                useDefaultSummary = false;
+            }
+            
+            if (useDefaultSummary) {
+                final CharSequence summary = getSummary();
+                if (summary != null) {
+                    summaryView.setText(summary);
+                    useDefaultSummary = false;
+                }
+            }
+            
+            int newVisibility = View.GONE;
+            if (!useDefaultSummary) {
+                // Someone has written to it
+                newVisibility = View.VISIBLE;
+            }
+            if (newVisibility != summaryView.getVisibility()) {
+                summaryView.setVisibility(newVisibility);
+            }
+        }
+    }
+    
+    @Override
+    protected void onClick() {
+        super.onClick();
+        
+        boolean newValue = !isChecked();
+        
+        if (!callChangeListener(newValue)) {
+            return;
+        }
+        
+        setChecked(newValue);
+    }
+
+    /**
+     * Sets the checked state and saves it to the {@link SharedPreferences}.
+     * 
+     * @param checked The checked state.
+     */
+    public void setChecked(boolean checked) {
+        mChecked = checked;
+
+        persistBoolean(checked);
+     
+        notifyDependencyChange(shouldDisableDependents());
+        
+        notifyChanged();
+    }
+
+    /**
+     * Returns the checked state.
+     * 
+     * @return The checked state.
+     */
+    public boolean isChecked() {
+        return mChecked;
+    }
+    
+    @Override
+    public boolean shouldDisableDependents() {
+        boolean shouldDisable = mDisableDependentsState ? mChecked : !mChecked;
+        return shouldDisable || super.shouldDisableDependents();
+    }
+
+    /**
+     * Sets the summary to be shown when checked.
+     * 
+     * @param summary The summary to be shown when checked.
+     */
+    public void setSummaryOn(CharSequence summary) {
+        mSummaryOn = summary;
+        if (isChecked()) {
+            notifyChanged();
+        }
+    }
+
+    /**
+     * @see #setSummaryOn(CharSequence)
+     * @param summaryResId The summary as a resource.
+     */
+    public void setSummaryOn(int summaryResId) {
+        setSummaryOn(getContext().getString(summaryResId));
+    }
+    
+    /**
+     * Returns the summary to be shown when checked.
+     * @return The summary.
+     */
+    public CharSequence getSummaryOn() {
+        return mSummaryOn;
+    }
+    
+    /**
+     * Sets the summary to be shown when unchecked.
+     * 
+     * @param summary The summary to be shown when unchecked.
+     */
+    public void setSummaryOff(CharSequence summary) {
+        mSummaryOff = summary;
+        if (!isChecked()) {
+            notifyChanged();
+        }
+    }
+
+    /**
+     * @see #setSummaryOff(CharSequence)
+     * @param summaryResId The summary as a resource.
+     */
+    public void setSummaryOff(int summaryResId) {
+        setSummaryOff(getContext().getString(summaryResId));
+    }
+    
+    /**
+     * Returns the summary to be shown when unchecked.
+     * @return The summary.
+     */
+    public CharSequence getSummaryOff() {
+        return mSummaryOff;
+    }
+
+    /**
+     * Returns whether dependents are disabled when this preference is on ({@code true})
+     * or when this preference is off ({@code false}).
+     * 
+     * @return Whether dependents are disabled when this preference is on ({@code true})
+     *         or when this preference is off ({@code false}).
+     */
+    public boolean getDisableDependentsState() {
+        return mDisableDependentsState;
+    }
+
+    /**
+     * Sets whether dependents are disabled when this preference is on ({@code true})
+     * or when this preference is off ({@code false}).
+     * 
+     * @param disableDependentsState The preference state that should disable dependents.
+     */
+    public void setDisableDependentsState(boolean disableDependentsState) {
+        mDisableDependentsState = disableDependentsState;
+    }
+
+    @Override
+    protected Object onGetDefaultValue(TypedArray a, int index) {
+        return a.getBoolean(index, false);
+    }
+
+    @Override
+    protected void onSetInitialValue(boolean restoreValue, Object defaultValue) {
+        setChecked(restoreValue ? getPersistedBoolean(mChecked)
+                : (Boolean) defaultValue);
+    }
+
+    @Override
+    protected Parcelable onSaveInstanceState() {
+        final Parcelable superState = super.onSaveInstanceState();
+        if (isPersistent()) {
+            // No need to save instance state since it's persistent
+            return superState;
+        }
+        
+        final SavedState myState = new SavedState(superState);
+        myState.checked = isChecked();
+        return myState;
+    }
+
+    @Override
+    protected void onRestoreInstanceState(Parcelable state) {
+        if (state == null || !state.getClass().equals(SavedState.class)) {
+            // Didn't save state for us in onSaveInstanceState
+            super.onRestoreInstanceState(state);
+            return;
+        }
+         
+        SavedState myState = (SavedState) state;
+        super.onRestoreInstanceState(myState.getSuperState());
+        setChecked(myState.checked);
+    }
+    
+    private static class SavedState extends BaseSavedState {
+        boolean checked;
+        
+        public SavedState(Parcel source) {
+            super(source);
+            checked = source.readInt() == 1;
+        }
+
+        @Override
+        public void writeToParcel(Parcel dest, int flags) {
+            super.writeToParcel(dest, flags);
+            dest.writeInt(checked ? 1 : 0);
+        }
+
+        public SavedState(Parcelable superState) {
+            super(superState);
+        }
+
+        public static final Parcelable.Creator<SavedState> CREATOR =
+                new Parcelable.Creator<SavedState>() {
+            public SavedState createFromParcel(Parcel in) {
+                return new SavedState(in);
+            }
+
+            public SavedState[] newArray(int size) {
+                return new SavedState[size];
+            }
+        };
+    }
+    
+}
diff --git a/core/java/android/preference/DialogPreference.java b/core/java/android/preference/DialogPreference.java
new file mode 100644
index 0000000..666efae
--- /dev/null
+++ b/core/java/android/preference/DialogPreference.java
@@ -0,0 +1,454 @@
+/*
+ * Copyright (C) 2007 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 android.preference;
+
+
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.SharedPreferences;
+import android.content.res.TypedArray;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+import android.util.AttributeSet;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.TextView;
+
+/**
+ * A base class for {@link Preference} objects that are
+ * dialog-based. These preferences will, when clicked, open a dialog showing the
+ * actual preference controls.
+ * 
+ * @attr ref android.R.styleable#DialogPreference_dialogTitle
+ * @attr ref android.R.styleable#DialogPreference_dialogMessage
+ * @attr ref android.R.styleable#DialogPreference_dialogIcon
+ * @attr ref android.R.styleable#DialogPreference_dialogLayout
+ * @attr ref android.R.styleable#DialogPreference_positiveButtonText
+ * @attr ref android.R.styleable#DialogPreference_negativeButtonText
+ */
+public abstract class DialogPreference extends Preference implements
+        DialogInterface.OnClickListener, DialogInterface.OnDismissListener,
+        PreferenceManager.OnActivityDestroyListener {
+    private AlertDialog.Builder mBuilder;
+    
+    private CharSequence mDialogTitle;
+    private CharSequence mDialogMessage;
+    private Drawable mDialogIcon;
+    private CharSequence mPositiveButtonText;
+    private CharSequence mNegativeButtonText;
+    private int mDialogLayoutResId;
+
+    /** The dialog, if it is showing. */
+    private Dialog mDialog;
+
+    /** Which button was clicked. */
+    private int mWhichButtonClicked;
+    
+    public DialogPreference(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+        
+        TypedArray a = context.obtainStyledAttributes(attrs,
+                com.android.internal.R.styleable.DialogPreference, defStyle, 0);
+        mDialogTitle = a.getString(com.android.internal.R.styleable.DialogPreference_dialogTitle);
+        if (mDialogTitle == null) {
+            // Fallback on the regular title of the preference
+            // (the one that is seen in the list)
+            mDialogTitle = getTitle();
+        }
+        mDialogMessage = a.getString(com.android.internal.R.styleable.DialogPreference_dialogMessage);
+        mDialogIcon = a.getDrawable(com.android.internal.R.styleable.DialogPreference_dialogIcon);
+        mPositiveButtonText = a.getString(com.android.internal.R.styleable.DialogPreference_positiveButtonText);
+        mNegativeButtonText = a.getString(com.android.internal.R.styleable.DialogPreference_negativeButtonText);
+        mDialogLayoutResId = a.getResourceId(com.android.internal.R.styleable.DialogPreference_dialogLayout,
+                mDialogLayoutResId);
+        a.recycle();
+        
+    }
+
+    public DialogPreference(Context context, AttributeSet attrs) {
+        this(context, attrs, com.android.internal.R.attr.dialogPreferenceStyle);
+    }
+    
+    /**
+     * Sets the title of the dialog. This will be shown on subsequent dialogs.
+     * 
+     * @param dialogTitle The title.
+     */
+    public void setDialogTitle(CharSequence dialogTitle) {
+        mDialogTitle = dialogTitle;
+    }
+
+    /**
+     * @see #setDialogTitle(CharSequence)
+     * @param dialogTitleResId The dialog title as a resource.
+     */
+    public void setDialogTitle(int dialogTitleResId) {
+        setDialogTitle(getContext().getString(dialogTitleResId));
+    }
+    
+    /**
+     * Returns the title to be shown on subsequent dialogs.
+     * @return The title.
+     */
+    public CharSequence getDialogTitle() {
+        return mDialogTitle;
+    }
+    
+    /**
+     * Sets the message of the dialog. This will be shown on subsequent dialogs.
+     * <p>
+     * This message forms the content View of the dialog and conflicts with
+     * list-based dialogs, for example. If setting a custom View on a dialog via
+     * {@link #setDialogLayoutResource(int)}, include a text View with ID
+     * {@link android.R.id#message} and it will be populated with this message.
+     * 
+     * @param dialogMessage The message.
+     */
+    public void setDialogMessage(CharSequence dialogMessage) {
+        mDialogMessage = dialogMessage;
+    }
+
+    /**
+     * @see #setDialogMessage(CharSequence)
+     * @param dialogMessageResId The dialog message as a resource.
+     */
+    public void setDialogMessage(int dialogMessageResId) {
+        setDialogMessage(getContext().getString(dialogMessageResId));
+    }
+    
+    /**
+     * Returns the message to be shown on subsequent dialogs.
+     * @return The message.
+     */
+    public CharSequence getDialogMessage() {
+        return mDialogMessage;
+    }
+    
+    /**
+     * Sets the icon of the dialog. This will be shown on subsequent dialogs.
+     * 
+     * @param dialogIcon The icon, as a {@link Drawable}.
+     */
+    public void setDialogIcon(Drawable dialogIcon) {
+        mDialogIcon = dialogIcon;
+    }
+    
+    /**
+     * Sets the icon (resource ID) of the dialog. This will be shown on
+     * subsequent dialogs.
+     * 
+     * @param dialogIconRes The icon, as a resource ID.
+     */
+    public void setDialogIcon(int dialogIconRes) {
+        mDialogIcon = getContext().getResources().getDrawable(dialogIconRes);
+    }
+    
+    /**
+     * Returns the icon to be shown on subsequent dialogs.
+     * @return The icon, as a {@link Drawable}.
+     */
+    public Drawable getDialogIcon() {
+        return mDialogIcon;
+    }
+    
+    /**
+     * Sets the text of the positive button of the dialog. This will be shown on
+     * subsequent dialogs.
+     * 
+     * @param positiveButtonText The text of the positive button.
+     */
+    public void setPositiveButtonText(CharSequence positiveButtonText) {
+        mPositiveButtonText = positiveButtonText;
+    }
+
+    /**
+     * @see #setPositiveButtonText(CharSequence)
+     * @param positiveButtonTextResId The positive button text as a resource.
+     */
+    public void setPositiveButtonText(int positiveButtonTextResId) {
+        setPositiveButtonText(getContext().getString(positiveButtonTextResId));
+    }
+    
+    /**
+     * Returns the text of the positive button to be shown on subsequent
+     * dialogs.
+     * 
+     * @return The text of the positive button.
+     */
+    public CharSequence getPositiveButtonText() {
+        return mPositiveButtonText;
+    }
+    
+    /**
+     * Sets the text of the negative button of the dialog. This will be shown on
+     * subsequent dialogs.
+     * 
+     * @param negativeButtonText The text of the negative button.
+     */
+    public void setNegativeButtonText(CharSequence negativeButtonText) {
+        mNegativeButtonText = negativeButtonText;
+    }
+    
+    /**
+     * @see #setNegativeButtonText(CharSequence)
+     * @param negativeButtonTextResId The negative button text as a resource.
+     */
+    public void setNegativeButtonText(int negativeButtonTextResId) {
+        setNegativeButtonText(getContext().getString(negativeButtonTextResId));
+    }
+    
+    /**
+     * Returns the text of the negative button to be shown on subsequent
+     * dialogs.
+     * 
+     * @return The text of the negative button.
+     */
+    public CharSequence getNegativeButtonText() {
+        return mNegativeButtonText;
+    }
+    
+    /**
+     * Sets the layout resource that is inflated as the {@link View} to be shown
+     * as the content View of subsequent dialogs.
+     * 
+     * @param dialogLayoutResId The layout resource ID to be inflated.
+     * @see #setDialogMessage(CharSequence)
+     */
+    public void setDialogLayoutResource(int dialogLayoutResId) {
+        mDialogLayoutResId = dialogLayoutResId;
+    }
+    
+    /**
+     * Returns the layout resource that is used as the content View for
+     * subsequent dialogs.
+     * 
+     * @return The layout resource.
+     */
+    public int getDialogLayoutResource() {
+        return mDialogLayoutResId;
+    }
+    
+    /**
+     * Prepares the dialog builder to be shown when the preference is clicked.
+     * Use this to set custom properties on the dialog.
+     * <p>
+     * Do not {@link AlertDialog.Builder#create()} or
+     * {@link AlertDialog.Builder#show()}.
+     */
+    protected void onPrepareDialogBuilder(AlertDialog.Builder builder) {
+    }
+    
+    @Override
+    protected void onClick() {
+        showDialog(null);
+    }
+
+    /**
+     * Shows the dialog associated with this Preference. This is normally initiated
+     * automatically on clicking on the preference. Call this method if you need to
+     * show the dialog on some other event.
+     * 
+     * @param state Optional instance state to restore on the dialog
+     */
+    protected void showDialog(Bundle state) {
+        Context context = getContext();
+
+        mWhichButtonClicked = DialogInterface.BUTTON2;
+        
+        mBuilder = new AlertDialog.Builder(context)
+            .setTitle(mDialogTitle)
+            .setIcon(mDialogIcon)
+            .setPositiveButton(mPositiveButtonText, this)
+            .setNegativeButton(mNegativeButtonText, this);
+
+        View contentView = onCreateDialogView();
+        if (contentView != null) {
+            onBindDialogView(contentView);
+            mBuilder.setView(contentView);
+        } else {
+            mBuilder.setMessage(mDialogMessage);
+        }
+        
+        onPrepareDialogBuilder(mBuilder);
+        
+        getPreferenceManager().registerOnActivityDestroyListener(this);
+        
+        // Create the dialog
+        final Dialog dialog = mDialog = mBuilder.create();
+        if (state != null) {
+            dialog.onRestoreInstanceState(state);
+        }
+        dialog.setOnDismissListener(this);
+        dialog.show();
+    }
+    
+    /**
+     * Creates the content view for the dialog (if a custom content view is
+     * required). By default, it inflates the dialog layout resource if it is
+     * set.
+     * 
+     * @return The content View for the dialog.
+     * @see #setLayoutResource(int)
+     */
+    protected View onCreateDialogView() {
+        if (mDialogLayoutResId == 0) {
+            return null;
+        }
+        
+        LayoutInflater inflater = (LayoutInflater) getContext().getSystemService(
+                Context.LAYOUT_INFLATER_SERVICE);
+        return inflater.inflate(mDialogLayoutResId, null);
+    }
+    
+    /**
+     * Binds views in the content View of the dialog to data.
+     * <p>
+     * Make sure to call through to the superclass implementation.
+     * 
+     * @param view The content View of the dialog, if it is custom.
+     */
+    protected void onBindDialogView(View view) {
+        View dialogMessageView = view.findViewById(com.android.internal.R.id.message);
+        
+        if (dialogMessageView != null) {
+            final CharSequence message = getDialogMessage();
+            int newVisibility = View.GONE;
+            
+            if (!TextUtils.isEmpty(message)) {
+                if (dialogMessageView instanceof TextView) {
+                    ((TextView) dialogMessageView).setText(message);
+                }
+                
+                newVisibility = View.VISIBLE;
+            }
+            
+            if (dialogMessageView.getVisibility() != newVisibility) {
+                dialogMessageView.setVisibility(newVisibility);
+            }
+        }
+    }
+    
+    public void onClick(DialogInterface dialog, int which) {
+        mWhichButtonClicked = which;
+    }
+    
+    public void onDismiss(DialogInterface dialog) {
+        
+        getPreferenceManager().unregisterOnActivityDestroyListener(this);
+        
+        mDialog = null;
+        onDialogClosed(mWhichButtonClicked == DialogInterface.BUTTON_POSITIVE);
+    }
+
+    /**
+     * Called when the dialog is dismissed and should be used to save data to
+     * the {@link SharedPreferences}.
+     * 
+     * @param positiveResult Whether the positive button was clicked (true), or
+     *            the negative button was clicked or the dialog was canceled (false).
+     */
+    protected void onDialogClosed(boolean positiveResult) {
+    }
+
+    /**
+     * Gets the dialog that is shown by this preference.
+     * 
+     * @return The dialog, or null if a dialog is not being shown.
+     */
+    public Dialog getDialog() {
+        return mDialog;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void onActivityDestroy() {
+        
+        if (mDialog == null || !mDialog.isShowing()) {
+            return;
+        }
+        
+        mDialog.dismiss();
+    }
+
+    @Override
+    protected Parcelable onSaveInstanceState() {
+        final Parcelable superState = super.onSaveInstanceState();
+        if (mDialog == null || !mDialog.isShowing()) {
+            return superState;
+        }
+
+        final SavedState myState = new SavedState(superState);
+        myState.isDialogShowing = true;
+        myState.dialogBundle = mDialog.onSaveInstanceState();
+        return myState;
+    }
+
+    @Override
+    protected void onRestoreInstanceState(Parcelable state) {
+        if (state == null || !state.getClass().equals(SavedState.class)) {
+            // Didn't save state for us in onSaveInstanceState
+            super.onRestoreInstanceState(state);
+            return;
+        }
+
+        SavedState myState = (SavedState) state;
+        super.onRestoreInstanceState(myState.getSuperState());
+        if (myState.isDialogShowing) {
+            showDialog(myState.dialogBundle);
+        }
+    }
+
+    private static class SavedState extends BaseSavedState {
+        boolean isDialogShowing;
+        Bundle dialogBundle;
+        
+        public SavedState(Parcel source) {
+            super(source);
+            isDialogShowing = source.readInt() == 1;
+            dialogBundle = source.readBundle();
+        }
+
+        @Override
+        public void writeToParcel(Parcel dest, int flags) {
+            super.writeToParcel(dest, flags);
+            dest.writeInt(isDialogShowing ? 1 : 0);
+            dest.writeBundle(dialogBundle);
+        }
+
+        public SavedState(Parcelable superState) {
+            super(superState);
+        }
+
+        public static final Parcelable.Creator<SavedState> CREATOR =
+                new Parcelable.Creator<SavedState>() {
+            public SavedState createFromParcel(Parcel in) {
+                return new SavedState(in);
+            }
+
+            public SavedState[] newArray(int size) {
+                return new SavedState[size];
+            }
+        };
+    }
+    
+}
diff --git a/core/java/android/preference/EditTextPreference.java b/core/java/android/preference/EditTextPreference.java
new file mode 100644
index 0000000..a12704f
--- /dev/null
+++ b/core/java/android/preference/EditTextPreference.java
@@ -0,0 +1,228 @@
+/*
+ * Copyright (C) 2007 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 android.preference;
+
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.content.res.TypedArray;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewParent;
+import android.widget.EditText;
+import android.widget.LinearLayout;
+
+/**
+ * A {@link Preference} that allows for string
+ * input.
+ * <p>
+ * It is a subclass of {@link DialogPreference} and shows the {@link EditText}
+ * in a dialog. This {@link EditText} can be modified either programmatically
+ * via {@link #getEditText()}, or through XML by setting any EditText
+ * attributes on the EditTextPreference.
+ * <p>
+ * This preference will store a string into the SharedPreferences.
+ * <p>
+ * See {@link android.R.styleable#EditText EditText Attributes}.
+ */
+public class EditTextPreference extends DialogPreference {
+    /**
+     * The edit text shown in the dialog.
+     */
+    private EditText mEditText;
+    
+    private String mText;
+    
+    public EditTextPreference(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+        
+        mEditText = new EditText(context, attrs);
+        
+        // Give it an ID so it can be saved/restored
+        mEditText.setId(com.android.internal.R.id.edit);
+        
+        /*
+         * The preference framework and view framework both have an 'enabled'
+         * attribute. Most likely, the 'enabled' specified in this XML is for
+         * the preference framework, but it was also given to the view framework.
+         * We reset the enabled state.
+         */
+        mEditText.setEnabled(true);
+    }
+
+    public EditTextPreference(Context context, AttributeSet attrs) {
+        this(context, attrs, com.android.internal.R.attr.editTextPreferenceStyle);
+    }
+
+    public EditTextPreference(Context context) {
+        this(context, null);
+    }
+    
+    /**
+     * Saves the text to the {@link SharedPreferences}.
+     * 
+     * @param text The text to save
+     */
+    public void setText(String text) {
+        final boolean wasBlocking = shouldDisableDependents();
+        
+        mText = text;
+        
+        persistString(text);
+        
+        final boolean isBlocking = shouldDisableDependents(); 
+        if (isBlocking != wasBlocking) {
+            notifyDependencyChange(isBlocking);
+        }
+    }
+    
+    /**
+     * Gets the text from the {@link SharedPreferences}.
+     * 
+     * @return The current preference value.
+     */
+    public String getText() {
+        return mText;
+    }
+
+    @Override
+    protected void onBindDialogView(View view) {
+        super.onBindDialogView(view);
+
+        EditText editText = mEditText;
+        editText.setText(getText());
+        
+        ViewParent oldParent = editText.getParent();
+        if (oldParent != view) {
+            if (oldParent != null) {
+                ((ViewGroup) oldParent).removeView(editText);
+            }
+            onAddEditTextToDialogView(view, editText);
+        }
+    }
+
+    /**
+     * Adds the EditText widget of this preference to the dialog's view.
+     * 
+     * @param dialogView The dialog view.
+     */
+    protected void onAddEditTextToDialogView(View dialogView, EditText editText) {
+        ViewGroup container = (ViewGroup) dialogView
+                .findViewById(com.android.internal.R.id.edittext_container);
+        if (container != null) {
+            container.addView(editText, ViewGroup.LayoutParams.FILL_PARENT,
+                    ViewGroup.LayoutParams.WRAP_CONTENT);
+        }
+    }
+    
+    @Override
+    protected void onDialogClosed(boolean positiveResult) {
+        super.onDialogClosed(positiveResult);
+        
+        if (positiveResult) {
+            String value = mEditText.getText().toString();
+            if (callChangeListener(value)) {
+                setText(value);
+            }
+        }
+    }
+
+    @Override
+    protected Object onGetDefaultValue(TypedArray a, int index) {
+        return a.getString(index);
+    }
+
+    @Override
+    protected void onSetInitialValue(boolean restoreValue, Object defaultValue) {
+        setText(restoreValue ? getPersistedString(mText) : (String) defaultValue);
+    }
+
+    @Override
+    public boolean shouldDisableDependents() {
+        return TextUtils.isEmpty(mText) || super.shouldDisableDependents();
+    }
+
+    /**
+     * Returns the {@link EditText} widget that will be shown in the dialog.
+     * 
+     * @return The {@link EditText} widget that will be shown in the dialog.
+     */
+    public EditText getEditText() {
+        return mEditText;
+    }
+
+    @Override
+    protected Parcelable onSaveInstanceState() {
+        final Parcelable superState = super.onSaveInstanceState();
+        if (isPersistent()) {
+            // No need to save instance state since it's persistent
+            return superState;
+        }
+        
+        final SavedState myState = new SavedState(superState);
+        myState.text = getText();
+        return myState;
+    }
+
+    @Override
+    protected void onRestoreInstanceState(Parcelable state) {
+        if (state == null || !state.getClass().equals(SavedState.class)) {
+            // Didn't save state for us in onSaveInstanceState
+            super.onRestoreInstanceState(state);
+            return;
+        }
+         
+        SavedState myState = (SavedState) state;
+        super.onRestoreInstanceState(myState.getSuperState());
+        setText(myState.text);
+    }
+    
+    private static class SavedState extends BaseSavedState {
+        String text;
+        
+        public SavedState(Parcel source) {
+            super(source);
+            text = source.readString();
+        }
+
+        @Override
+        public void writeToParcel(Parcel dest, int flags) {
+            super.writeToParcel(dest, flags);
+            dest.writeString(text);
+        }
+
+        public SavedState(Parcelable superState) {
+            super(superState);
+        }
+
+        public static final Parcelable.Creator<SavedState> CREATOR =
+                new Parcelable.Creator<SavedState>() {
+            public SavedState createFromParcel(Parcel in) {
+                return new SavedState(in);
+            }
+
+            public SavedState[] newArray(int size) {
+                return new SavedState[size];
+            }
+        };
+    }
+    
+}
diff --git a/core/java/android/preference/GenericInflater.java b/core/java/android/preference/GenericInflater.java
new file mode 100644
index 0000000..3003290
--- /dev/null
+++ b/core/java/android/preference/GenericInflater.java
@@ -0,0 +1,520 @@
+/*
+ * Copyright (C) 2007 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 android.preference;
+
+import java.io.IOException;
+import java.lang.reflect.Constructor;
+import java.util.HashMap;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import android.content.Context;
+import android.content.res.XmlResourceParser;
+import android.util.AttributeSet;
+import android.util.Xml;
+import android.view.ContextThemeWrapper;
+import android.view.InflateException;
+import android.view.LayoutInflater;
+
+// TODO: fix generics
+/**
+ * Generic XML inflater. This has been adapted from {@link LayoutInflater} and
+ * quickly passed over to use generics.
+ * 
+ * @hide
+ * @param T The type of the items to inflate
+ * @param P The type of parents (that is those items that contain other items).
+ *            Must implement {@link GenericInflater.Parent}
+ */
+abstract class GenericInflater<T, P extends GenericInflater.Parent> {
+    private final boolean DEBUG = false;
+
+    protected final Context mContext;
+
+    // these are optional, set by the caller
+    private boolean mFactorySet;
+    private Factory<T> mFactory;
+
+    private final Object[] mConstructorArgs = new Object[2];
+
+    private static final Class[] mConstructorSignature = new Class[] {
+            Context.class, AttributeSet.class};
+
+    private static final HashMap sConstructorMap = new HashMap();
+
+    private String mDefaultPackage;
+
+    public interface Parent<T> {
+        public void addItemFromInflater(T child);
+    }
+    
+    public interface Factory<T> {
+        /**
+         * Hook you can supply that is called when inflating from a
+         * inflater. You can use this to customize the tag
+         * names available in your XML files.
+         * <p>
+         * Note that it is good practice to prefix these custom names with your
+         * package (i.e., com.coolcompany.apps) to avoid conflicts with system
+         * names.
+         * 
+         * @param name Tag name to be inflated.
+         * @param context The context the item is being created in.
+         * @param attrs Inflation attributes as specified in XML file.
+         * @return Newly created item. Return null for the default behavior.
+         */
+        public T onCreateItem(String name, Context context, AttributeSet attrs);
+    }
+
+    private static class FactoryMerger<T> implements Factory<T> {
+        private final Factory<T> mF1, mF2;
+        
+        FactoryMerger(Factory<T> f1, Factory<T> f2) {
+            mF1 = f1;
+            mF2 = f2;
+        }
+        
+        public T onCreateItem(String name, Context context, AttributeSet attrs) {
+            T v = mF1.onCreateItem(name, context, attrs);
+            if (v != null) return v;
+            return mF2.onCreateItem(name, context, attrs);
+        }
+    }
+    
+    /**
+     * Create a new inflater instance associated with a
+     * particular Context.
+     * 
+     * @param context The Context in which this inflater will
+     *            create its items; most importantly, this supplies the theme
+     *            from which the default values for their attributes are
+     *            retrieved.
+     */
+    protected GenericInflater(Context context) {
+        mContext = context;
+    }
+
+    /**
+     * Create a new inflater instance that is a copy of an
+     * existing inflater, optionally with its Context
+     * changed. For use in implementing {@link #cloneInContext}.
+     * 
+     * @param original The original inflater to copy.
+     * @param newContext The new Context to use.
+     */
+    protected GenericInflater(GenericInflater<T,P> original, Context newContext) {
+        mContext = newContext;
+        mFactory = original.mFactory;
+    }
+    
+    /**
+     * Create a copy of the existing inflater object, with the copy
+     * pointing to a different Context than the original.  This is used by
+     * {@link ContextThemeWrapper} to create a new inflater to go along
+     * with the new Context theme.
+     * 
+     * @param newContext The new Context to associate with the new inflater.
+     * May be the same as the original Context if desired.
+     * 
+     * @return Returns a brand spanking new inflater object associated with
+     * the given Context.
+     */
+    public abstract GenericInflater cloneInContext(Context newContext);
+    
+    /**
+     * Sets the default package that will be searched for classes to construct
+     * for tag names that have no explicit package.
+     * 
+     * @param defaultPackage The default package. This will be prepended to the
+     *            tag name, so it should end with a period.
+     */
+    public void setDefaultPackage(String defaultPackage) {
+        mDefaultPackage = defaultPackage;
+    }
+    
+    /**
+     * Returns the default package, or null if it is not set.
+     * 
+     * @see #setDefaultPackage(String)
+     * @return The default package.
+     */
+    public String getDefaultPackage() {
+        return mDefaultPackage;
+    }
+    
+    /**
+     * Return the context we are running in, for access to resources, class
+     * loader, etc.
+     */
+    public Context getContext() {
+        return mContext;
+    }
+
+    /**
+     * Return the current factory (or null). This is called on each element
+     * name. If the factory returns an item, add that to the hierarchy. If it
+     * returns null, proceed to call onCreateItem(name).
+     */
+    public final Factory<T> getFactory() {
+        return mFactory;
+    }
+
+    /**
+     * Attach a custom Factory interface for creating items while using this
+     * inflater. This must not be null, and can only be set
+     * once; after setting, you can not change the factory. This is called on
+     * each element name as the XML is parsed. If the factory returns an item,
+     * that is added to the hierarchy. If it returns null, the next factory
+     * default {@link #onCreateItem} method is called.
+     * <p>
+     * If you have an existing inflater and want to add your
+     * own factory to it, use {@link #cloneInContext} to clone the existing
+     * instance and then you can use this function (once) on the returned new
+     * instance. This will merge your own factory with whatever factory the
+     * original instance is using.
+     */
+    public void setFactory(Factory<T> factory) {
+        if (mFactorySet) {
+            throw new IllegalStateException("" +
+            		"A factory has already been set on this inflater");
+        }
+        if (factory == null) {
+            throw new NullPointerException("Given factory can not be null");
+        }
+        mFactorySet = true;
+        if (mFactory == null) {
+            mFactory = factory;
+        } else {
+            mFactory = new FactoryMerger<T>(factory, mFactory);
+        }
+    }
+
+
+    /**
+     * Inflate a new item hierarchy from the specified xml resource. Throws
+     * InflaterException if there is an error.
+     * 
+     * @param resource ID for an XML resource to load (e.g.,
+     *        <code>R.layout.main_page</code>)
+     * @param root Optional parent of the generated hierarchy.
+     * @return The root of the inflated hierarchy. If root was supplied,
+     *         this is the root item; otherwise it is the root of the inflated
+     *         XML file.
+     */
+    public T inflate(int resource, P root) {
+        return inflate(resource, root, root != null);
+    }
+
+    /**
+     * Inflate a new hierarchy from the specified xml node. Throws
+     * InflaterException if there is an error. *
+     * <p>
+     * <em><strong>Important</strong></em>&nbsp;&nbsp;&nbsp;For performance
+     * reasons, inflation relies heavily on pre-processing of XML files
+     * that is done at build time. Therefore, it is not currently possible to
+     * use inflater with an XmlPullParser over a plain XML file at runtime.
+     * 
+     * @param parser XML dom node containing the description of the
+     *        hierarchy.
+     * @param root Optional parent of the generated hierarchy.
+     * @return The root of the inflated hierarchy. If root was supplied,
+     *         this is the that; otherwise it is the root of the inflated
+     *         XML file.
+     */
+    public T inflate(XmlPullParser parser, P root) {
+        return inflate(parser, root, root != null);
+    }
+
+    /**
+     * Inflate a new hierarchy from the specified xml resource. Throws
+     * InflaterException if there is an error.
+     * 
+     * @param resource ID for an XML resource to load (e.g.,
+     *        <code>R.layout.main_page</code>)
+     * @param root Optional root to be the parent of the generated hierarchy (if
+     *        <em>attachToRoot</em> is true), or else simply an object that
+     *        provides a set of values for root of the returned
+     *        hierarchy (if <em>attachToRoot</em> is false.)
+     * @param attachToRoot Whether the inflated hierarchy should be attached to
+     *        the root parameter?
+     * @return The root of the inflated hierarchy. If root was supplied and
+     *         attachToRoot is true, this is root; otherwise it is the root of
+     *         the inflated XML file.
+     */
+    public T inflate(int resource, P root, boolean attachToRoot) {
+        if (DEBUG) System.out.println("INFLATING from resource: " + resource);
+        XmlResourceParser parser = getContext().getResources().getXml(resource);
+        try {
+            return inflate(parser, root, attachToRoot);
+        } finally {
+            parser.close();
+        }
+    }
+
+    /**
+     * Inflate a new hierarchy from the specified XML node. Throws
+     * InflaterException if there is an error.
+     * <p>
+     * <em><strong>Important</strong></em>&nbsp;&nbsp;&nbsp;For performance
+     * reasons, inflation relies heavily on pre-processing of XML files
+     * that is done at build time. Therefore, it is not currently possible to
+     * use inflater with an XmlPullParser over a plain XML file at runtime.
+     * 
+     * @param parser XML dom node containing the description of the
+     *        hierarchy.
+     * @param root Optional to be the parent of the generated hierarchy (if
+     *        <em>attachToRoot</em> is true), or else simply an object that
+     *        provides a set of values for root of the returned
+     *        hierarchy (if <em>attachToRoot</em> is false.)
+     * @param attachToRoot Whether the inflated hierarchy should be attached to
+     *        the root parameter?
+     * @return The root of the inflated hierarchy. If root was supplied and
+     *         attachToRoot is true, this is root; otherwise it is the root of
+     *         the inflated XML file.
+     */
+    public T inflate(XmlPullParser parser, P root,
+            boolean attachToRoot) {
+        synchronized (mConstructorArgs) {
+            final AttributeSet attrs = Xml.asAttributeSet(parser);
+            mConstructorArgs[0] = mContext;
+            T result = (T) root;
+
+            try {
+                // Look for the root node.
+                int type;
+                while ((type = parser.next()) != parser.START_TAG
+                        && type != parser.END_DOCUMENT) {
+                    ;
+                }
+
+                if (type != parser.START_TAG) {
+                    throw new InflateException(parser.getPositionDescription()
+                            + ": No start tag found!");
+                }
+
+                if (DEBUG) {
+                    System.out.println("**************************");
+                    System.out.println("Creating root: "
+                            + parser.getName());
+                    System.out.println("**************************");
+                }
+                // Temp is the root that was found in the xml
+                T xmlRoot = createItemFromTag(parser, parser.getName(),
+                        attrs);
+
+                result = (T) onMergeRoots(root, attachToRoot, (P) xmlRoot);
+                
+                if (DEBUG) {
+                    System.out.println("-----> start inflating children");
+                }
+                // Inflate all children under temp
+                rInflate(parser, result, attrs);
+                if (DEBUG) {
+                    System.out.println("-----> done inflating children");
+                }
+
+            } catch (InflateException e) {
+                throw e;
+
+            } catch (XmlPullParserException e) {
+                InflateException ex = new InflateException(e.getMessage());
+                ex.initCause(e);
+                throw ex;
+            } catch (IOException e) {
+                InflateException ex = new InflateException(
+                        parser.getPositionDescription()
+                        + ": " + e.getMessage());
+                ex.initCause(e);
+                throw ex;
+            }
+
+            return result;
+        }
+    }
+
+    /**
+     * Low-level function for instantiating by name. This attempts to
+     * instantiate class of the given <var>name</var> found in this
+     * inflater's ClassLoader.
+     * 
+     * <p>
+     * There are two things that can happen in an error case: either the
+     * exception describing the error will be thrown, or a null will be
+     * returned. You must deal with both possibilities -- the former will happen
+     * the first time createItem() is called for a class of a particular name,
+     * the latter every time there-after for that class name.
+     * 
+     * @param name The full name of the class to be instantiated.
+     * @param attrs The XML attributes supplied for this instance.
+     * 
+     * @return The newly instantied item, or null.
+     */
+    public final T createItem(String name, String prefix, AttributeSet attrs)
+            throws ClassNotFoundException, InflateException {
+        Constructor constructor = (Constructor) sConstructorMap.get(name);
+
+        try {
+            if (null == constructor) {
+                // Class not found in the cache, see if it's real,
+                // and try to add it
+                Class clazz = mContext.getClassLoader().loadClass(
+                        prefix != null ? (prefix + name) : name);
+                constructor = clazz.getConstructor(mConstructorSignature);
+                sConstructorMap.put(name, constructor);
+            }
+
+            Object[] args = mConstructorArgs;
+            args[1] = attrs;
+            return (T) constructor.newInstance(args);
+
+        } catch (NoSuchMethodException e) {
+            InflateException ie = new InflateException(attrs
+                    .getPositionDescription()
+                    + ": Error inflating class "
+                    + (prefix != null ? (prefix + name) : name));
+            ie.initCause(e);
+            throw ie;
+
+        } catch (ClassNotFoundException e) {
+            // If loadClass fails, we should propagate the exception.
+            throw e;
+        } catch (Exception e) {
+            InflateException ie = new InflateException(attrs
+                    .getPositionDescription()
+                    + ": Error inflating class "
+                    + constructor.getClass().getName());
+            ie.initCause(e);
+            throw ie;
+        }
+    }
+
+    /**
+     * This routine is responsible for creating the correct subclass of item
+     * given the xml element name. Override it to handle custom item objects. If
+     * you override this in your subclass be sure to call through to
+     * super.onCreateItem(name) for names you do not recognize.
+     * 
+     * @param name The fully qualified class name of the item to be create.
+     * @param attrs An AttributeSet of attributes to apply to the item.
+     * @return The item created.
+     */
+    protected T onCreateItem(String name, AttributeSet attrs) throws ClassNotFoundException {
+        return createItem(name, mDefaultPackage, attrs);
+    }
+
+    private final T createItemFromTag(XmlPullParser parser, String name, AttributeSet attrs) {
+        if (DEBUG) System.out.println("******** Creating item: " + name);
+
+        try {
+            T item = (mFactory == null) ? null : mFactory.onCreateItem(name, mContext, attrs);
+
+            if (item == null) {
+                if (-1 == name.indexOf('.')) {
+                    item = onCreateItem(name, attrs);
+                } else {
+                    item = createItem(name, null, attrs);
+                }
+            }
+
+            if (DEBUG) System.out.println("Created item is: " + item);
+            return item;
+
+        } catch (InflateException e) {
+            throw e;
+
+        } catch (ClassNotFoundException e) {
+            InflateException ie = new InflateException(attrs
+                    .getPositionDescription()
+                    + ": Error inflating class " + name);
+            ie.initCause(e);
+            throw ie;
+
+        } catch (Exception e) {
+            InflateException ie = new InflateException(attrs
+                    .getPositionDescription()
+                    + ": Error inflating class " + name);
+            ie.initCause(e);
+            throw ie;
+        }
+    }
+
+    /**
+     * Recursive method used to descend down the xml hierarchy and instantiate
+     * items, instantiate their children, and then call onFinishInflate().
+     */
+    private void rInflate(XmlPullParser parser, T parent, final AttributeSet attrs)
+            throws XmlPullParserException, IOException {
+        final int depth = parser.getDepth();
+
+        int type;
+        while (((type = parser.next()) != parser.END_TAG || 
+                parser.getDepth() > depth) && type != parser.END_DOCUMENT) {
+
+            if (type != parser.START_TAG) {
+                continue;
+            }
+
+            if (onCreateCustomFromTag(parser, parent, attrs)) {
+                continue;
+            }
+
+            if (DEBUG) {
+                System.out.println("Now inflating tag: " + parser.getName());
+            }
+            String name = parser.getName();
+
+            T item = createItemFromTag(parser, name, attrs);
+
+            if (DEBUG) {
+                System.out
+                        .println("Creating params from parent: " + parent);
+            }
+
+            ((P) parent).addItemFromInflater(item);
+
+            if (DEBUG) {
+                System.out.println("-----> start inflating children");
+            }
+            rInflate(parser, item, attrs);
+            if (DEBUG) {
+                System.out.println("-----> done inflating children");
+            }
+        }
+
+    }
+    
+    /**
+     * Before this inflater tries to create an item from the tag, this method
+     * will be called. The parser will be pointing to the start of a tag, you
+     * must stop parsing and return when you reach the end of this element!
+     * 
+     * @param parser XML dom node containing the description of the hierarchy.
+     * @param parent The item that should be the parent of whatever you create.
+     * @param attrs An AttributeSet of attributes to apply to the item.
+     * @return Whether you created a custom object (true), or whether this
+     *         inflater should proceed to create an item.
+     */
+    protected boolean onCreateCustomFromTag(XmlPullParser parser, T parent,
+            final AttributeSet attrs) throws XmlPullParserException {
+        return false;
+    }
+    
+    protected P onMergeRoots(P givenRoot, boolean attachToGivenRoot, P xmlRoot) {
+        return xmlRoot;
+    }
+}
diff --git a/core/java/android/preference/ListPreference.java b/core/java/android/preference/ListPreference.java
new file mode 100644
index 0000000..f842d75
--- /dev/null
+++ b/core/java/android/preference/ListPreference.java
@@ -0,0 +1,291 @@
+/*
+ * Copyright (C) 2007 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 android.preference;
+
+
+import android.app.AlertDialog.Builder;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.res.TypedArray;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.AttributeSet;
+
+/**
+ * A {@link Preference} that displays a list of entries as
+ * a dialog.
+ * <p>
+ * This preference will store a string into the SharedPreferences. This string will be the value
+ * from the {@link #setEntryValues(CharSequence[])} array.
+ * 
+ * @attr ref android.R.styleable#ListPreference_entries
+ * @attr ref android.R.styleable#ListPreference_entryValues
+ */
+public class ListPreference extends DialogPreference {
+    private CharSequence[] mEntries;
+    private CharSequence[] mEntryValues;
+    private String mValue;
+    private int mClickedDialogEntryIndex;
+    
+    public ListPreference(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        
+        TypedArray a = context.obtainStyledAttributes(attrs,
+                com.android.internal.R.styleable.ListPreference, 0, 0);
+        mEntries = a.getTextArray(com.android.internal.R.styleable.ListPreference_entries);
+        mEntryValues = a.getTextArray(com.android.internal.R.styleable.ListPreference_entryValues);
+        a.recycle();
+    }
+    
+    public ListPreference(Context context) {
+        this(context, null);
+    }
+
+    /**
+     * Sets the human-readable entries to be shown in the list. This will be
+     * shown in subsequent dialogs.
+     * <p>
+     * Each entry must have a corresponding index in
+     * {@link #setEntryValues(CharSequence[])}.
+     * 
+     * @param entries The entries.
+     * @see #setEntryValues(CharSequence[])
+     */
+    public void setEntries(CharSequence[] entries) {
+        mEntries = entries;
+    }
+    
+    /**
+     * @see #setEntries(CharSequence[])
+     * @param entriesResId The entries array as a resource.
+     */
+    public void setEntries(int entriesResId) {
+        setEntries(getContext().getResources().getTextArray(entriesResId));
+    }
+    
+    /**
+     * The list of entries to be shown in the list in subsequent dialogs.
+     * 
+     * @return The list as an array.
+     */
+    public CharSequence[] getEntries() {
+        return mEntries;
+    }
+    
+    /**
+     * The array to find the value to save for a preference when an entry from
+     * entries is selected. If a user clicks on the second item in entries, the
+     * second item in this array will be saved to the preference.
+     * 
+     * @param entryValues The array to be used as values to save for the preference.
+     */
+    public void setEntryValues(CharSequence[] entryValues) {
+        mEntryValues = entryValues;
+    }
+
+    /**
+     * @see #setEntryValues(CharSequence[])
+     * @param entryValuesResId The entry values array as a resource.
+     */
+    public void setEntryValues(int entryValuesResId) {
+        setEntryValues(getContext().getResources().getTextArray(entryValuesResId));
+    }
+    
+    /**
+     * Returns the array of values to be saved for the preference.
+     * 
+     * @return The array of values.
+     */
+    public CharSequence[] getEntryValues() {
+        return mEntryValues;
+    }
+
+    /**
+     * Sets the value of the key. This should be one of the entries in
+     * {@link #getEntryValues()}.
+     * 
+     * @param value The value to set for the key.
+     */
+    public void setValue(String value) {
+        mValue = value;
+        
+        persistString(value);
+    }
+
+    /**
+     * Sets the value to the given index from the entry values.
+     * 
+     * @param index The index of the value to set.
+     */
+    public void setValueIndex(int index) {
+        if (mEntryValues != null) {
+            setValue(mEntryValues[index].toString());
+        }
+    }
+    
+    /**
+     * Returns the value of the key. This should be one of the entries in
+     * {@link #getEntryValues()}.
+     * 
+     * @return The value of the key.
+     */
+    public String getValue() {
+        return mValue; 
+    }
+    
+    /**
+     * Returns the entry corresponding to the current value.
+     * 
+     * @return The entry corresponding to the current value, or null.
+     */
+    public CharSequence getEntry() {
+        int index = getValueIndex();
+        return index >= 0 && mEntries != null ? mEntries[index] : null;
+    }
+    
+    /**
+     * Returns the index of the given value (in the entry values array).
+     * 
+     * @param value The value whose index should be returned.
+     * @return The index of the value, or -1 if not found.
+     */
+    public int findIndexOfValue(String value) {
+        if (value != null && mEntryValues != null) {
+            for (int i = mEntryValues.length - 1; i >= 0; i--) {
+                if (mEntryValues[i].equals(value)) {
+                    return i;
+                }
+            }
+        }
+        return -1;
+    }
+    
+    private int getValueIndex() {
+        return findIndexOfValue(mValue);
+    }
+    
+    @Override
+    protected void onPrepareDialogBuilder(Builder builder) {
+        super.onPrepareDialogBuilder(builder);
+        
+        if (mEntries == null || mEntryValues == null) {
+            throw new IllegalStateException(
+                    "ListPreference requires an entries array and an entryValues array.");
+        }
+
+        mClickedDialogEntryIndex = getValueIndex();
+        builder.setSingleChoiceItems(mEntries, mClickedDialogEntryIndex, 
+                new DialogInterface.OnClickListener() {
+                    public void onClick(DialogInterface dialog, int which) {
+                        mClickedDialogEntryIndex = which;
+
+                        /*
+                         * Clicking on an item simulates the positive button
+                         * click, and dismisses the dialog.
+                         */
+                        ListPreference.this.onClick(dialog, DialogInterface.BUTTON_POSITIVE);
+                        dialog.dismiss();
+                    }
+        });
+        
+        /*
+         * The typical interaction for list-based dialogs is to have
+         * click-on-an-item dismiss the dialog instead of the user having to
+         * press 'Ok'.
+         */
+        builder.setPositiveButton(null, null);
+    }
+
+    @Override
+    protected void onDialogClosed(boolean positiveResult) {
+        super.onDialogClosed(positiveResult);
+        
+        if (positiveResult && mClickedDialogEntryIndex >= 0 && mEntryValues != null) {
+            String value = mEntryValues[mClickedDialogEntryIndex].toString();
+            if (callChangeListener(value)) {
+                setValue(value);
+            }
+        }
+    }
+
+    @Override
+    protected Object onGetDefaultValue(TypedArray a, int index) {
+        return a.getString(index);
+    }
+
+    @Override
+    protected void onSetInitialValue(boolean restoreValue, Object defaultValue) {
+        setValue(restoreValue ? getPersistedString(mValue) : (String) defaultValue);
+    }
+    
+    @Override
+    protected Parcelable onSaveInstanceState() {
+        final Parcelable superState = super.onSaveInstanceState();
+        if (isPersistent()) {
+            // No need to save instance state since it's persistent
+            return superState;
+        }
+        
+        final SavedState myState = new SavedState(superState);
+        myState.value = getValue();
+        return myState;
+    }
+
+    @Override
+    protected void onRestoreInstanceState(Parcelable state) {
+        if (state == null || !state.getClass().equals(SavedState.class)) {
+            // Didn't save state for us in onSaveInstanceState
+            super.onRestoreInstanceState(state);
+            return;
+        }
+         
+        SavedState myState = (SavedState) state;
+        super.onRestoreInstanceState(myState.getSuperState());
+        setValue(myState.value);
+    }
+    
+    private static class SavedState extends BaseSavedState {
+        String value;
+        
+        public SavedState(Parcel source) {
+            super(source);
+            value = source.readString();
+        }
+
+        @Override
+        public void writeToParcel(Parcel dest, int flags) {
+            super.writeToParcel(dest, flags);
+            dest.writeString(value);
+        }
+
+        public SavedState(Parcelable superState) {
+            super(superState);
+        }
+
+        public static final Parcelable.Creator<SavedState> CREATOR =
+                new Parcelable.Creator<SavedState>() {
+            public SavedState createFromParcel(Parcel in) {
+                return new SavedState(in);
+            }
+
+            public SavedState[] newArray(int size) {
+                return new SavedState[size];
+            }
+        };
+    }
+    
+}
diff --git a/core/java/android/preference/OnDependencyChangeListener.java b/core/java/android/preference/OnDependencyChangeListener.java
new file mode 100644
index 0000000..ce25e34
--- /dev/null
+++ b/core/java/android/preference/OnDependencyChangeListener.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2007 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 android.preference;
+
+/**
+ * Interface definition for a callback to be invoked when this
+ * {@link Preference} changes with respect to enabling/disabling
+ * dependents.
+ */
+interface OnDependencyChangeListener {
+    /**
+     * Called when this preference has changed in a way that dependents should
+     * care to change their state.
+     * 
+     * @param disablesDependent Whether the dependent should be disabled.
+     */
+    void onDependencyChanged(Preference dependency, boolean disablesDependent);
+}
diff --git a/core/java/android/preference/Preference.java b/core/java/android/preference/Preference.java
new file mode 100644
index 0000000..a255438
--- /dev/null
+++ b/core/java/android/preference/Preference.java
@@ -0,0 +1,1586 @@
+/*
+ * Copyright (C) 2007 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 android.preference;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.content.res.TypedArray;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+import android.util.AttributeSet;
+import com.android.internal.util.CharSequences;
+import android.view.AbsSavedState;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ListView;
+import android.widget.TextView;
+
+/**
+ * Represents the basic Preference UI building
+ * block displayed by a {@link PreferenceActivity} in the form of a
+ * {@link ListView}. This class provides the {@link View} to be displayed in
+ * the activity and associates with a {@link SharedPreferences} to
+ * store/retrieve the preference data.
+ * <p>
+ * When specifying a preference hierarchy in XML, each element can point to a
+ * subclass of {@link Preference}, similar to the view hierarchy and layouts.
+ * <p>
+ * This class contains a {@code key} that will be used as the key into the
+ * {@link SharedPreferences}. It is up to the subclass to decide how to store
+ * the value.
+ * 
+ * @attr ref android.R.styleable#Preference_key
+ * @attr ref android.R.styleable#Preference_title
+ * @attr ref android.R.styleable#Preference_summary
+ * @attr ref android.R.styleable#Preference_order
+ * @attr ref android.R.styleable#Preference_layout
+ * @attr ref android.R.styleable#Preference_widgetLayout
+ * @attr ref android.R.styleable#Preference_enabled
+ * @attr ref android.R.styleable#Preference_selectable
+ * @attr ref android.R.styleable#Preference_dependency
+ * @attr ref android.R.styleable#Preference_persistent
+ * @attr ref android.R.styleable#Preference_defaultValue
+ * @attr ref android.R.styleable#Preference_shouldDisableView
+ */
+public class Preference implements Comparable<Preference>, OnDependencyChangeListener { 
+    /**
+     * Specify for {@link #setOrder(int)} if a specific order is not required.
+     */
+    public static final int DEFAULT_ORDER = Integer.MAX_VALUE;
+
+    private Context mContext;
+    private PreferenceManager mPreferenceManager;
+    
+    /**
+     * Set when added to hierarchy since we need a unique ID within that
+     * hierarchy.
+     */
+    private long mId;
+    
+    private OnPreferenceChangeListener mOnChangeListener;
+    private OnPreferenceClickListener mOnClickListener;
+
+    private int mOrder = DEFAULT_ORDER;
+    private CharSequence mTitle;
+    private CharSequence mSummary;
+    private String mKey;
+    private Intent mIntent;
+    private boolean mEnabled = true;
+    private boolean mSelectable = true;
+    private boolean mRequiresKey;
+    private boolean mPersistent = true;
+    private String mDependencyKey;
+    private Object mDefaultValue;
+    
+    /**
+     * @see #setShouldDisableView(boolean)
+     */
+    private boolean mShouldDisableView = true;
+    
+    private int mLayoutResId = com.android.internal.R.layout.preference;
+    private int mWidgetLayoutResId;
+    private boolean mHasSpecifiedLayout = false;
+    
+    private OnPreferenceChangeInternalListener mListener;
+    
+    private List<Preference> mDependents;
+    
+    private boolean mBaseMethodCalled;
+    
+    /**
+     * Interface definition for a callback to be invoked when the value of this
+     * {@link Preference} has been changed by the user and is
+     * about to be set and/or persisted.  This gives the client a chance
+     * to prevent setting and/or persisting the value.
+     */
+    public interface OnPreferenceChangeListener {
+        /**
+         * Called when a Preference has been changed by the user. This is
+         * called before the state of the Preference is about to be updated and
+         * before the state is persisted.
+         * 
+         * @param preference The changed Preference.
+         * @param newValue The new value of the Preference.
+         * @return True to update the state of the Preference with the new value.
+         */
+        boolean onPreferenceChange(Preference preference, Object newValue);
+    }
+
+    /**
+     * Interface definition for a callback to be invoked when a {@link Preference} is
+     * clicked.
+     */
+    public interface OnPreferenceClickListener {
+        /**
+         * Called when a Preference has been clicked.
+         *
+         * @param preference The Preference that was clicked.
+         * @return True if the click was handled.
+         */
+        boolean onPreferenceClick(Preference preference);
+    }
+
+    /**
+     * Interface definition for a callback to be invoked when this
+     * {@link Preference} is changed or, if this is a group, there is an
+     * addition/removal of {@link Preference}(s). This is used internally.
+     */
+    interface OnPreferenceChangeInternalListener {
+        /**
+         * Called when this Preference has changed.
+         * 
+         * @param preference This preference.
+         */
+        void onPreferenceChange(Preference preference);
+        
+        /**
+         * Called when this group has added/removed {@link Preference}(s).
+         * 
+         * @param preference This Preference.
+         */
+        void onPreferenceHierarchyChange(Preference preference);
+    }
+
+    /**
+     * Perform inflation from XML and apply a class-specific base style. This
+     * constructor of Preference allows subclasses to use their own base
+     * style when they are inflating. For example, a {@link CheckBoxPreference}
+     * constructor calls this version of the super class constructor and
+     * supplies {@code android.R.attr.checkBoxPreferenceStyle} for <var>defStyle</var>.
+     * This allows the theme's checkbox preference style to modify all of the base
+     * preference attributes as well as the {@link CheckBoxPreference} class's
+     * attributes.
+     * 
+     * @param context The Context this is associated with, through which it can
+     *            access the current theme, resources, {@link SharedPreferences},
+     *            etc.
+     * @param attrs The attributes of the XML tag that is inflating the preference.
+     * @param defStyle The default style to apply to this preference. If 0, no style
+     *            will be applied (beyond what is included in the theme). This
+     *            may either be an attribute resource, whose value will be
+     *            retrieved from the current theme, or an explicit style
+     *            resource.
+     * @see #Preference(Context, AttributeSet)
+     */
+    public Preference(Context context, AttributeSet attrs, int defStyle) {
+        mContext = context;
+
+        TypedArray a = context.obtainStyledAttributes(attrs,
+                com.android.internal.R.styleable.Preference);
+        if (a.hasValue(com.android.internal.R.styleable.Preference_layout) ||
+                a.hasValue(com.android.internal.R.styleable.Preference_widgetLayout)) {
+            // This preference has a custom layout defined (not one taken from
+            // the default style)
+            mHasSpecifiedLayout = true;
+        }
+        a.recycle();
+        
+        a = context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.Preference,
+                defStyle, 0);
+        for (int i = a.getIndexCount(); i >= 0; i--) {
+            int attr = a.getIndex(i); 
+            switch (attr) {
+                case com.android.internal.R.styleable.Preference_key:
+                    mKey = a.getString(attr);
+                    break;
+                    
+                case com.android.internal.R.styleable.Preference_title:
+                    mTitle = a.getString(attr);
+                    break;
+                    
+                case com.android.internal.R.styleable.Preference_summary:
+                    mSummary = a.getString(attr);
+                    break;
+                    
+                case com.android.internal.R.styleable.Preference_order:
+                    mOrder = a.getInt(attr, mOrder);
+                    break;
+
+                case com.android.internal.R.styleable.Preference_layout:
+                    mLayoutResId = a.getResourceId(attr, mLayoutResId);
+                    break;
+
+                case com.android.internal.R.styleable.Preference_widgetLayout:
+                    mWidgetLayoutResId = a.getResourceId(attr, mWidgetLayoutResId);
+                    break;
+                    
+                case com.android.internal.R.styleable.Preference_enabled:
+                    mEnabled = a.getBoolean(attr, true);
+                    break;
+                    
+                case com.android.internal.R.styleable.Preference_selectable:
+                    mSelectable = a.getBoolean(attr, true);
+                    break;
+                    
+                case com.android.internal.R.styleable.Preference_persistent:
+                    mPersistent = a.getBoolean(attr, mPersistent);
+                    break;
+                    
+                case com.android.internal.R.styleable.Preference_dependency:
+                    mDependencyKey = a.getString(attr);
+                    break;
+                    
+                case com.android.internal.R.styleable.Preference_defaultValue:
+                    mDefaultValue = onGetDefaultValue(a, attr);
+                    break;
+                    
+                case com.android.internal.R.styleable.Preference_shouldDisableView:
+                    mShouldDisableView = a.getBoolean(attr, mShouldDisableView);
+                    break;
+            }
+        }
+        a.recycle();
+    }
+    
+    /**
+     * Constructor that is called when inflating a Preference from XML. This is
+     * called when a Preference is being constructed from an XML file, supplying
+     * attributes that were specified in the XML file. This version uses a
+     * default style of 0, so the only attribute values applied are those in the
+     * Context's Theme and the given AttributeSet.
+     * 
+     * @param context The Context this is associated with, through which it can
+     *            access the current theme, resources, {@link SharedPreferences},
+     *            etc.
+     * @param attrs The attributes of the XML tag that is inflating the
+     *            preference.
+     * @see #Preference(Context, AttributeSet, int)
+     */
+    public Preference(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    /**
+     * Constructor to create a Preference.
+     * 
+     * @param context The Context in which to store Preference values.
+     */
+    public Preference(Context context) {
+        this(context, null);
+    }
+
+    /**
+     * Called when a Preference is being inflated and the default value
+     * attribute needs to be read. Since different Preference types have
+     * different value types, the subclass should get and return the default
+     * value which will be its value type.
+     * <p>
+     * For example, if the value type is String, the body of the method would
+     * proxy to {@link TypedArray#getString(int)}.
+     * 
+     * @param a The set of attributes.
+     * @param index The index of the default value attribute.
+     * @return The default value of this preference type.
+     */
+    protected Object onGetDefaultValue(TypedArray a, int index) {
+        return null;
+    }
+    
+    /**
+     * Sets an {@link Intent} to be used for
+     * {@link Context#startActivity(Intent)} when this Preference is clicked.
+     * 
+     * @param intent The intent associated with this Preference.
+     */
+    public void setIntent(Intent intent) {
+        mIntent = intent;
+    }
+    
+    /**
+     * Return the {@link Intent} associated with this Preference.
+     * 
+     * @return The {@link Intent} last set via {@link #setIntent(Intent)} or XML. 
+     */
+    public Intent getIntent() {
+        return mIntent;
+    }
+
+    /**
+     * Sets the layout resource that is inflated as the {@link View} to be shown
+     * for this Preference. In most cases, the default layout is sufficient for
+     * custom Preference objects and only the widget layout needs to be changed.
+     * <p>
+     * This layout should contain a {@link ViewGroup} with ID
+     * {@link android.R.id#widget_frame} to be the parent of the specific widget
+     * for this Preference. It should similarly contain
+     * {@link android.R.id#title} and {@link android.R.id#summary}.
+     * 
+     * @param layoutResId The layout resource ID to be inflated and returned as
+     *            a {@link View}.
+     * @see #setWidgetLayoutResource(int)
+     */
+    public void setLayoutResource(int layoutResId) {
+        
+        if (!mHasSpecifiedLayout) {
+            mHasSpecifiedLayout = true;
+        }
+        
+        mLayoutResId = layoutResId;
+    }
+    
+    /**
+     * Gets the layout resource that will be shown as the {@link View} for this Preference.
+     * 
+     * @return The layout resource ID.
+     */
+    public int getLayoutResource() {
+        return mLayoutResId;
+    }
+    
+    /**
+     * Sets The layout for the controllable widget portion of this Preference. This
+     * is inflated into the main layout. For example, a {@link CheckBoxPreference}
+     * would specify a custom layout (consisting of just the CheckBox) here,
+     * instead of creating its own main layout.
+     * 
+     * @param widgetLayoutResId The layout resource ID to be inflated into the
+     *            main layout.
+     * @see #setLayoutResource(int)
+     */
+    public void setWidgetLayoutResource(int widgetLayoutResId) {
+        mWidgetLayoutResId = widgetLayoutResId;
+    }
+
+    /**
+     * Gets the layout resource for the controllable widget portion of this Preference.
+     * 
+     * @return The layout resource ID.
+     */
+    public int getWidgetLayoutResource() {
+        return mWidgetLayoutResId;
+    }
+    
+    /**
+     * Gets the View that will be shown in the {@link PreferenceActivity}.
+     * 
+     * @param convertView The old View to reuse, if possible. Note: You should
+     *            check that this View is non-null and of an appropriate type
+     *            before using. If it is not possible to convert this View to
+     *            display the correct data, this method can create a new View.
+     * @param parent The parent that this View will eventually be attached to.
+     * @return Returns the same Preference object, for chaining multiple calls
+     *         into a single statement.
+     * @see #onCreateView(ViewGroup)
+     * @see #onBindView(View)
+     */
+    public View getView(View convertView, ViewGroup parent) {
+        if (convertView == null) {
+            convertView = onCreateView(parent);
+        }
+        onBindView(convertView);
+        return convertView;
+    }
+    
+    /**
+     * Creates the View to be shown for this Preference in the
+     * {@link PreferenceActivity}. The default behavior is to inflate the main
+     * layout of this Preference (see {@link #setLayoutResource(int)}. If
+     * changing this behavior, please specify a {@link ViewGroup} with ID
+     * {@link android.R.id#widget_frame}.
+     * <p>
+     * Make sure to call through to the superclass's implementation.
+     * 
+     * @param parent The parent that this View will eventually be attached to.
+     * @return The View that displays this Preference.
+     * @see #onBindView(View)
+     */
+    protected View onCreateView(ViewGroup parent) {
+        final LayoutInflater layoutInflater =
+            (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+        
+        final View layout = layoutInflater.inflate(mLayoutResId, parent, false); 
+        
+        if (mWidgetLayoutResId != 0) {
+            final ViewGroup widgetFrame = (ViewGroup)layout.findViewById(com.android.internal.R.id.widget_frame);
+            layoutInflater.inflate(mWidgetLayoutResId, widgetFrame);
+        }
+
+        return layout;
+    }
+    
+    /**
+     * Binds the created View to the data for this Preference.
+     * <p>
+     * This is a good place to grab references to custom Views in the layout and
+     * set properties on them.
+     * <p>
+     * Make sure to call through to the superclass's implementation.
+     * 
+     * @param view The View that shows this Preference.
+     * @see #onCreateView(ViewGroup)
+     */
+    protected void onBindView(View view) {
+        TextView textView = (TextView) view.findViewById(com.android.internal.R.id.title); 
+        if (textView != null) {
+            textView.setText(getTitle());
+        }
+        
+        textView = (TextView) view.findViewById(com.android.internal.R.id.summary);
+        if (textView != null) {
+            final CharSequence summary = getSummary();
+            if (!TextUtils.isEmpty(summary)) {
+                if (textView.getVisibility() != View.VISIBLE) {
+                    textView.setVisibility(View.VISIBLE);
+                }
+                
+                textView.setText(getSummary());
+            } else {
+                if (textView.getVisibility() != View.GONE) {
+                    textView.setVisibility(View.GONE);
+                }
+            }
+        }
+        
+        if (mShouldDisableView) {
+            setEnabledStateOnViews(view, isEnabled());
+        }
+    }
+    
+    /**
+     * Makes sure the view (and any children) get the enabled state changed.
+     */
+    private void setEnabledStateOnViews(View v, boolean enabled) {
+        v.setEnabled(enabled);
+        
+        if (v instanceof ViewGroup) {
+            final ViewGroup vg = (ViewGroup) v;
+            for (int i = vg.getChildCount() - 1; i >= 0; i--) {
+                setEnabledStateOnViews(vg.getChildAt(i), enabled);
+            }
+        }
+    }
+    
+    /**
+     * Sets the order of this Preference with respect to other
+     * Preference objects on the same level. If this is not specified, the
+     * default behavior is to sort alphabetically. The
+     * {@link PreferenceGroup#setOrderingAsAdded(boolean)} can be used to order
+     * Preference objects based on the order they appear in the XML.
+     * 
+     * @param order The order for this Preference. A lower value will be shown
+     *            first. Use {@link #DEFAULT_ORDER} to sort alphabetically or
+     *            allow ordering from XML.
+     * @see PreferenceGroup#setOrderingAsAdded(boolean)
+     * @see #DEFAULT_ORDER
+     */
+    public void setOrder(int order) {
+        if (order != mOrder) {
+            mOrder = order;
+
+            // Reorder the list 
+            notifyHierarchyChanged();
+        }
+    }
+    
+    /**
+     * Gets the order of this Preference with respect to other Preference objects
+     * on the same level.
+     * 
+     * @return The order of this Preference.
+     * @see #setOrder(int)
+     */
+    public int getOrder() {
+        return mOrder;
+    }
+
+    /**
+     * Sets the title for this Preference with a CharSequence. 
+     * This title will be placed into the ID
+     * {@link android.R.id#title} within the View created by
+     * {@link #onCreateView(ViewGroup)}.
+     * 
+     * @param title The title for this Preference.
+     */
+    public void setTitle(CharSequence title) {
+        if (title == null && mTitle != null || title != null && !title.equals(mTitle)) {
+            mTitle = title;
+            notifyChanged();
+        }
+    }
+    
+    /**
+     * Sets the title for this Preference with a resource ID. 
+     * 
+     * @see #setTitle(CharSequence)
+     * @param titleResId The title as a resource ID.
+     */
+    public void setTitle(int titleResId) {
+        setTitle(mContext.getString(titleResId));
+    }
+    
+    /**
+     * Returns the title of this Preference.
+     * 
+     * @return The title.
+     * @see #setTitle(CharSequence)
+     */
+    public CharSequence getTitle() {
+        return mTitle;
+    }
+
+    /**
+     * Returns the summary of this Preference.
+     * 
+     * @return The summary.
+     * @see #setSummary(CharSequence)
+     */
+    public CharSequence getSummary() {
+        return mSummary;
+    }
+
+    /**
+     * Sets the summary for this Preference with a CharSequence. 
+     * 
+     * @param summary The summary for the preference.
+     */
+    public void setSummary(CharSequence summary) {
+        if (summary == null && mSummary != null || summary != null && !summary.equals(mSummary)) {
+            mSummary = summary;
+            notifyChanged();
+        }
+    }
+
+    /**
+     * Sets the summary for this Preference with a resource ID. 
+     * 
+     * @see #setSummary(CharSequence)
+     * @param summaryResId The summary as a resource.
+     */
+    public void setSummary(int summaryResId) {
+        setSummary(mContext.getString(summaryResId));
+    }
+    
+    /**
+     * Sets whether this Preference is enabled. If disabled, it will
+     * not handle clicks.
+     * 
+     * @param enabled Set true to enable it.
+     */
+    public void setEnabled(boolean enabled) {
+        if (mEnabled != enabled) {
+            mEnabled = enabled;
+
+            // Enabled state can change dependent preferences' states, so notify
+            notifyDependencyChange(shouldDisableDependents());
+
+            notifyChanged();
+        }
+    }
+    
+    /**
+     * Checks whether this Preference should be enabled in the list.
+     * 
+     * @return True if this Preference is enabled, false otherwise.
+     */
+    public boolean isEnabled() {
+        return mEnabled;
+    }
+
+    /**
+     * Sets whether this Preference is selectable.
+     * 
+     * @param selectable Set true to make it selectable.
+     */
+    public void setSelectable(boolean selectable) {
+        if (mSelectable != selectable) {
+            mSelectable = selectable;
+            notifyChanged();
+        }
+    }
+    
+    /**
+     * Checks whether this Preference should be selectable in the list.
+     * 
+     * @return True if it is selectable, false otherwise.
+     */
+    public boolean isSelectable() {
+        return mSelectable;
+    }
+
+    /**
+     * Sets whether this Preference should disable its view when it gets
+     * disabled.
+     * <p>
+     * For example, set this and {@link #setEnabled(boolean)} to false for
+     * preferences that are only displaying information and 1) should not be
+     * clickable 2) should not have the view set to the disabled state.
+     * 
+     * @param shouldDisableView Set true if this preference should disable its view
+     *            when the preference is disabled.
+     */
+    public void setShouldDisableView(boolean shouldDisableView) {
+        mShouldDisableView = shouldDisableView;
+        notifyChanged();
+    }
+    
+    /**
+     * Checks whether this Preference should disable its view when it's action is disabled.
+     * @see #setShouldDisableView(boolean)
+     * @return True if it should disable the view. 
+     */
+    public boolean getShouldDisableView() {
+        return mShouldDisableView;
+    }
+
+    /**
+     * Returns a unique ID for this Preference.  This ID should be unique across all
+     * Preference objects in a hierarchy.
+     * 
+     * @return A unique ID for this Preference.
+     */
+    long getId() {
+        return mId;
+    }
+    
+    /**
+     * Processes a click on the preference. This includes saving the value to
+     * the {@link SharedPreferences}. However, the overridden method should
+     * call {@link #callChangeListener(Object)} to make sure the client wants to
+     * update the preference's state with the new value.
+     */
+    protected void onClick() {
+    }
+    
+    /**
+     * Sets the key for this Preference, which is used as a key to the
+     * {@link SharedPreferences}. This should be unique for the package.
+     * 
+     * @param key The key for the preference.
+     */
+    public void setKey(String key) {
+        mKey = key;
+        
+        if (mRequiresKey && !hasKey()) {
+            requireKey();
+        }
+    }
+    
+    /**
+     * Gets the key for this Preference, which is also the key used for storing
+     * values into SharedPreferences.
+     * 
+     * @return The key.
+     */
+    public String getKey() {
+        return mKey;
+    }
+    
+    /**
+     * Checks whether the key is present, and if it isn't throws an
+     * exception. This should be called by subclasses that store preferences in
+     * the {@link SharedPreferences}.
+     * 
+     * @throws IllegalStateException If there is no key assigned.
+     */
+    void requireKey() {
+        if (mKey == null) {
+            throw new IllegalStateException("Preference does not have a key assigned.");
+        }
+        
+        mRequiresKey = true;
+    }
+    
+    /**
+     * Checks whether this Preference has a valid key.
+     * 
+     * @return True if the key exists and is not a blank string, false otherwise.
+     */
+    public boolean hasKey() {
+        return !TextUtils.isEmpty(mKey);
+    }
+    
+    /**
+     * Checks whether this Preference is persistent. If it is, it stores its value(s) into
+     * the persistent {@link SharedPreferences} storage.
+     * 
+     * @return True if it is persistent.
+     */
+    public boolean isPersistent() {
+        return mPersistent;
+    }
+    
+    /**
+     * Checks whether, at the given time this method is called,
+     * this Preference should store/restore its value(s) into the
+     * {@link SharedPreferences}. This, at minimum, checks whether this
+     * Preference is persistent and it currently has a key. Before you
+     * save/restore from the {@link SharedPreferences}, check this first.
+     * 
+     * @return True if it should persist the value.
+     */
+    protected boolean shouldPersist() {
+        return mPreferenceManager != null && isPersistent() && hasKey();
+    }
+    
+    /**
+     * Sets whether this Preference is persistent. When persistent,
+     * it stores its value(s) into the persistent {@link SharedPreferences}
+     * storage.
+     * 
+     * @param persistent Set true if it should store its value(s) into the {@link SharedPreferences}.
+     */
+    public void setPersistent(boolean persistent) {
+        mPersistent = persistent;
+    }
+    
+    /**
+     * Call this method after the user changes the preference, but before the
+     * internal state is set. This allows the client to ignore the user value.
+     * 
+     * @param newValue The new value of this Preference.
+     * @return True if the user value should be set as the preference
+     *         value (and persisted).
+     */
+    protected boolean callChangeListener(Object newValue) {
+        return mOnChangeListener == null ? true : mOnChangeListener.onPreferenceChange(this, newValue);
+    }
+    
+    /**
+     * Sets the callback to be invoked when this Preference is changed by the
+     * user (but before the internal state has been updated).
+     * 
+     * @param onPreferenceChangeListener The callback to be invoked.
+     */
+    public void setOnPreferenceChangeListener(OnPreferenceChangeListener onPreferenceChangeListener) {
+        mOnChangeListener = onPreferenceChangeListener;
+    }
+
+    /**
+     * Returns the callback to be invoked when this Preference is changed by the
+     * user (but before the internal state has been updated).
+     * 
+     * @return The callback to be invoked.
+     */
+    public OnPreferenceChangeListener getOnPreferenceChangeListener() {
+        return mOnChangeListener;
+    }
+
+    /**
+     * Sets the callback to be invoked when this Preference is clicked.
+     * 
+     * @param onPreferenceClickListener The callback to be invoked.
+     */
+    public void setOnPreferenceClickListener(OnPreferenceClickListener onPreferenceClickListener) {
+        mOnClickListener = onPreferenceClickListener;
+    }
+
+    /**
+     * Returns the callback to be invoked when this Preference is clicked.
+     * 
+     * @return The callback to be invoked.
+     */
+    public OnPreferenceClickListener getOnPreferenceClickListener() {
+        return mOnClickListener;
+    }
+
+    /**
+     * Called when a click should be performed.
+     * 
+     * @param preferenceScreen A {@link PreferenceScreen} whose hierarchy click
+     *            listener should be called in the proper order (between other
+     *            processing). May be null.
+     */
+    void performClick(PreferenceScreen preferenceScreen) {
+        
+        if (!isEnabled()) {
+            return;
+        }
+        
+        onClick();
+        
+        if (mOnClickListener != null && mOnClickListener.onPreferenceClick(this)) {
+            return;
+        }
+        
+        PreferenceManager preferenceManager = getPreferenceManager();
+        if (preferenceManager != null) {
+            PreferenceManager.OnPreferenceTreeClickListener listener = preferenceManager
+                    .getOnPreferenceTreeClickListener();
+            if (preferenceScreen != null && listener != null
+                    && listener.onPreferenceTreeClick(preferenceScreen, this)) {
+                return;
+            }
+        }
+        
+        if (mIntent != null) {
+            Context context = getContext();
+            context.startActivity(mIntent);
+        }
+    }
+    
+    /**
+     * Returns the {@link android.content.Context} of this Preference. 
+     * Each Preference in a Preference hierarchy can be
+     * from different Context (for example, if multiple activities provide preferences into a single
+     * {@link PreferenceActivity}). This Context will be used to save the Preference values.
+     * 
+     * @return The Context of this Preference.
+     */
+    public Context getContext() {
+        return mContext;
+    }
+    
+    /**
+     * Returns the {@link SharedPreferences} where this Preference can read its
+     * value(s). Usually, it's easier to use one of the helper read methods:
+     * {@link #getPersistedBoolean(boolean)}, {@link #getPersistedFloat(float)},
+     * {@link #getPersistedInt(int)}, {@link #getPersistedLong(long)},
+     * {@link #getPersistedString(String)}. To save values, see
+     * {@link #getEditor()}.
+     * <p>
+     * In some cases, writes to the {@link #getEditor()} will not be committed
+     * right away and hence not show up in the returned
+     * {@link SharedPreferences}, this is intended behavior to improve
+     * performance.
+     * 
+     * @return The {@link SharedPreferences} where this Preference reads its
+     *         value(s), or null if it isn't attached to a Preference hierarchy.
+     * @see #getEditor()
+     */
+    public SharedPreferences getSharedPreferences() {
+        if (mPreferenceManager == null) {
+            return null;
+        }
+        
+        return mPreferenceManager.getSharedPreferences();
+    }
+    
+    /**
+     * Returns an {@link SharedPreferences.Editor} where this Preference can
+     * save its value(s). Usually it's easier to use one of the helper save
+     * methods: {@link #persistBoolean(boolean)}, {@link #persistFloat(float)},
+     * {@link #persistInt(int)}, {@link #persistLong(long)},
+     * {@link #persistString(String)}. To read values, see
+     * {@link #getSharedPreferences()}. If {@link #shouldCommit()} returns
+     * true, it is this Preference's responsibility to commit.
+     * <p>
+     * In some cases, writes to this will not be committed right away and hence
+     * not show up in the SharedPreferences, this is intended behavior to
+     * improve performance.
+     * 
+     * @return A {@link SharedPreferences.Editor} where this preference saves
+     *         its value(s), or null if it isn't attached to a Preference
+     *         hierarchy.
+     * @see #shouldCommit()
+     * @see #getSharedPreferences()
+     */
+    public SharedPreferences.Editor getEditor() {
+        if (mPreferenceManager == null) {
+            return null;
+        }
+        
+        return mPreferenceManager.getEditor();
+    }
+    
+    /**
+     * Returns whether the {@link Preference} should commit its saved value(s) in
+     * {@link #getEditor()}. This may return false in situations where batch
+     * committing is being done (by the manager) to improve performance.
+     * 
+     * @return Whether the Preference should commit its saved value(s).
+     * @see #getEditor()
+     */
+    public boolean shouldCommit() {
+        if (mPreferenceManager == null) {
+            return false;
+        }
+        
+        return mPreferenceManager.shouldCommit();
+    }
+    
+    /**
+     * Compares Preference objects based on order (if set), otherwise alphabetically on the titles.
+     * 
+     * @param another The Preference to compare to this one.
+     * @return 0 if the same; less than 0 if this Preference sorts ahead of <var>another</var>;
+     *          greater than 0 if this Preference sorts after <var>another</var>.
+     */
+    public int compareTo(Preference another) {
+        if (mOrder != DEFAULT_ORDER
+                || (mOrder == DEFAULT_ORDER && another.mOrder != DEFAULT_ORDER)) {
+            // Do order comparison
+            return mOrder - another.mOrder; 
+        } else if (mTitle == null) {
+            return 1;
+        } else if (another.mTitle == null) {
+            return -1;
+        } else {
+            // Do name comparison
+            return CharSequences.compareToIgnoreCase(mTitle, another.mTitle);
+        }
+    }
+    
+    /**
+     * Sets the internal change listener.
+     * 
+     * @param listener The listener.
+     * @see #notifyChanged()
+     */
+    final void setOnPreferenceChangeInternalListener(OnPreferenceChangeInternalListener listener) {
+        mListener = listener;
+    }
+
+    /**
+     * Should be called when the data of this {@link Preference} has changed.
+     */
+    protected void notifyChanged() {
+        if (mListener != null) {
+            mListener.onPreferenceChange(this);
+        }
+    }
+    
+    /**
+     * Should be called when a Preference has been
+     * added/removed from this group, or the ordering should be
+     * re-evaluated.
+     */
+    protected void notifyHierarchyChanged() {
+        if (mListener != null) {
+            mListener.onPreferenceHierarchyChange(this);
+        }
+    }
+
+    /**
+     * Gets the {@link PreferenceManager} that manages this Preference object's tree.
+     * 
+     * @return The {@link PreferenceManager}.
+     */
+    public PreferenceManager getPreferenceManager() {
+        return mPreferenceManager;
+    }
+    
+    /**
+     * Called when this Preference has been attached to a Preference hierarchy.
+     * Make sure to call the super implementation.
+     * 
+     * @param preferenceManager The PreferenceManager of the hierarchy.
+     */
+    protected void onAttachedToHierarchy(PreferenceManager preferenceManager) {
+        mPreferenceManager = preferenceManager;
+        
+        mId = preferenceManager.getNextId();
+        
+        dispatchSetInitialValue();
+    }
+    
+    /**
+     * Called when the Preference hierarchy has been attached to the
+     * {@link PreferenceActivity}. This can also be called when this
+     * Preference has been attached to a group that was already attached
+     * to the {@link PreferenceActivity}.
+     */
+    protected void onAttachedToActivity() {
+        // At this point, the hierarchy that this preference is in is connected
+        // with all other preferences.
+        registerDependency();
+    }
+
+    private void registerDependency() {
+        
+        if (TextUtils.isEmpty(mDependencyKey)) return;
+        
+        Preference preference = findPreferenceInHierarchy(mDependencyKey);
+        if (preference != null) {
+            preference.registerDependent(this);
+        } else {
+            throw new IllegalStateException("Dependency \"" + mDependencyKey
+                    + "\" not found for preference \"" + mKey + "\" (title: \"" + mTitle + "\"");
+        }
+    }
+
+    private void unregisterDependency() {
+        if (mDependencyKey != null) {
+            final Preference oldDependency = findPreferenceInHierarchy(mDependencyKey);
+            if (oldDependency != null) {
+                oldDependency.unregisterDependent(this);
+            }
+        }
+    }
+    
+    /**
+     * Finds a Preference in this hierarchy (the whole thing,
+     * even above/below your {@link PreferenceScreen} screen break) with the given
+     * key.
+     * <p>
+     * This only functions after we have been attached to a hierarchy.
+     * 
+     * @param key The key of the Preference to find.
+     * @return The Preference that uses the given key.
+     */
+    protected Preference findPreferenceInHierarchy(String key) {
+        if (TextUtils.isEmpty(key) || mPreferenceManager == null) {
+            return null;
+        }
+        
+        return mPreferenceManager.findPreference(key);
+    }
+    
+    /**
+     * Adds a dependent Preference on this Preference so we can notify it.
+     * Usually, the dependent Preference registers itself (it's good for it to
+     * know it depends on something), so please use
+     * {@link Preference#setDependency(String)} on the dependent Preference.
+     * 
+     * @param dependent The dependent Preference that will be enabled/disabled
+     *            according to the state of this Preference.
+     */
+    private void registerDependent(Preference dependent) {
+        if (mDependents == null) {
+            mDependents = new ArrayList<Preference>();
+        }
+        
+        mDependents.add(dependent);
+        
+        dependent.onDependencyChanged(this, shouldDisableDependents());
+    }
+    
+    /**
+     * Removes a dependent Preference on this Preference.
+     * 
+     * @param dependent The dependent Preference that will be enabled/disabled
+     *            according to the state of this Preference.
+     * @return Returns the same Preference object, for chaining multiple calls
+     *         into a single statement.
+     */
+    private void unregisterDependent(Preference dependent) {
+        if (mDependents != null) {
+            mDependents.remove(dependent);
+        }
+    }
+    
+    /**
+     * Notifies any listening dependents of a change that affects the
+     * dependency.
+     * 
+     * @param disableDependents Whether this Preference should disable
+     *            its dependents.
+     */
+    public void notifyDependencyChange(boolean disableDependents) {
+        final List<Preference> dependents = mDependents;
+        
+        if (dependents == null) {
+            return;
+        }
+        
+        final int dependentsCount = dependents.size();
+        for (int i = 0; i < dependentsCount; i++) {
+            dependents.get(i).onDependencyChanged(this, disableDependents);
+        }
+    }
+
+    /**
+     * Called when the dependency changes.
+     * 
+     * @param dependency The Preference that this Preference depends on.
+     * @param disableDependent Set true to disable this Preference.
+     */
+    public void onDependencyChanged(Preference dependency, boolean disableDependent) {
+        setEnabled(!disableDependent);
+    }
+    
+    /**
+     * Checks whether this preference's dependents should currently be
+     * disabled.
+     * 
+     * @return True if the dependents should be disabled, otherwise false.
+     */
+    public boolean shouldDisableDependents() {
+        return !isEnabled();
+    }
+    
+    /**
+     * Sets the key of a Preference that this Preference will depend on. If that
+     * Preference is not set or is off, this Preference will be disabled.
+     * 
+     * @param dependencyKey The key of the Preference that this depends on.
+     */
+    public void setDependency(String dependencyKey) {
+        // Unregister the old dependency, if we had one
+        unregisterDependency();
+        
+        // Register the new
+        mDependencyKey = dependencyKey;
+        registerDependency();
+    }
+    
+    /**
+     * Returns the key of the dependency on this Preference.
+     * 
+     * @return The key of the dependency.
+     * @see #setDependency(String)
+     */
+    public String getDependency() {
+        return mDependencyKey;
+    }
+    
+    /**
+     * Called when this Preference is being removed from the hierarchy. You
+     * should remove any references to this Preference that you know about. Make
+     * sure to call through to the superclass implementation.
+     */
+    protected void onPrepareForRemoval() {
+        unregisterDependency();
+    }
+    
+    /**
+     * Sets the default value for this Preference, which will be set either if
+     * persistence is off or persistence is on and the preference is not found
+     * in the persistent storage.
+     * 
+     * @param defaultValue The default value.
+     */
+    public void setDefaultValue(Object defaultValue) {
+        mDefaultValue = defaultValue;
+    }
+    
+    private void dispatchSetInitialValue() {
+        // By now, we know if we are persistent.
+        final boolean shouldPersist = shouldPersist();
+        if (!shouldPersist || !getSharedPreferences().contains(mKey)) {
+            if (mDefaultValue != null) {
+                onSetInitialValue(false, mDefaultValue);
+            }
+        } else {
+            onSetInitialValue(true, null);
+        }
+    }
+    
+    /**
+     * Implement this to set the initial value of the Preference. 
+     * <p>
+     * If <var>restorePersistedValue</var> is true, you should restore the 
+     * Preference value from the {@link android.content.SharedPreferences}. If 
+     * <var>restorePersistedValue</var> is false, you should set the Preference 
+     * value to defaultValue that is given (and possibly store to SharedPreferences 
+     * if {@link #shouldPersist()} is true).
+     * <p>
+     * This may not always be called. One example is if it should not persist
+     * but there is no default value given.
+     * 
+     * @param restorePersistedValue True to restore the persisted value;
+     *            false to use the given <var>defaultValue</var>.
+     * @param defaultValue The default value for this Preference. Only use this
+     *            if <var>restorePersistedValue</var> is false.
+     */
+    protected void onSetInitialValue(boolean restorePersistedValue, Object defaultValue) {
+    }
+
+    private void tryCommit(SharedPreferences.Editor editor) {
+        if (mPreferenceManager.shouldCommit()) {
+            editor.commit();
+        }
+    }
+    
+    /**
+     * Attempts to persist a String to the {@link android.content.SharedPreferences}.
+     * <p>
+     * This will check if this Preference is persistent, get an editor from
+     * the {@link PreferenceManager}, put in the string, and check if we should commit (and
+     * commit if so).
+     * 
+     * @param value The value to persist.
+     * @return True if the Preference is persistent. (This is not whether the
+     *         value was persisted, since we may not necessarily commit if there
+     *         will be a batch commit later.)
+     * @see #getPersistedString(String)
+     */
+    protected boolean persistString(String value) {
+        if (shouldPersist()) {
+            // Shouldn't store null
+            if (value == getPersistedString(null)) {
+                // It's already there, so the same as persisting
+                return true;
+            }
+            
+            SharedPreferences.Editor editor = mPreferenceManager.getEditor();
+            editor.putString(mKey, value);
+            tryCommit(editor);
+            return true;
+        }
+        return false;
+    }
+    
+    /**
+     * Attempts to get a persisted String from the {@link android.content.SharedPreferences}.
+     * <p>
+     * This will check if this Preference is persistent, get the SharedPreferences
+     * from the {@link PreferenceManager}, and get the value.
+     * 
+     * @param defaultReturnValue The default value to return if either the
+     *            Preference is not persistent or the Preference is not in the
+     *            shared preferences.
+     * @return The value from the SharedPreferences or the default return
+     *         value.
+     * @see #persistString(String)
+     */
+    protected String getPersistedString(String defaultReturnValue) {
+        if (!shouldPersist()) {
+            return defaultReturnValue;
+        }
+        
+        return mPreferenceManager.getSharedPreferences().getString(mKey, defaultReturnValue);
+    }
+    
+    /**
+     * Attempts to persist an int to the {@link android.content.SharedPreferences}.
+     * 
+     * @param value The value to persist.
+     * @return True if the Preference is persistent. (This is not whether the
+     *         value was persisted, since we may not necessarily commit if there
+     *         will be a batch commit later.)
+     * @see #persistString(String)
+     * @see #getPersistedInt(int)
+     */
+    protected boolean persistInt(int value) {
+        if (shouldPersist()) {
+            if (value == getPersistedInt(~value)) {
+                // It's already there, so the same as persisting
+                return true;
+            }
+            
+            SharedPreferences.Editor editor = mPreferenceManager.getEditor();
+            editor.putInt(mKey, value);
+            tryCommit(editor);
+            return true;
+        }
+        return false;
+    }
+    
+    /**
+     * Attempts to get a persisted int from the {@link android.content.SharedPreferences}.
+     * 
+     * @param defaultReturnValue The default value to return if either this
+     *            Preference is not persistent or this Preference is not in the
+     *            SharedPreferences.
+     * @return The value from the SharedPreferences or the default return
+     *         value.
+     * @see #getPersistedString(String)
+     * @see #persistInt(int)
+     */
+    protected int getPersistedInt(int defaultReturnValue) {
+        if (!shouldPersist()) {
+            return defaultReturnValue;
+        }
+        
+        return mPreferenceManager.getSharedPreferences().getInt(mKey, defaultReturnValue);
+    }
+    
+    /**
+     * Attempts to persist a float to the {@link android.content.SharedPreferences}.
+     * 
+     * @param value The value to persist.
+     * @return True if this Preference is persistent. (This is not whether the
+     *         value was persisted, since we may not necessarily commit if there
+     *         will be a batch commit later.)
+     * @see #persistString(String)
+     * @see #getPersistedFloat(float)
+     */
+    protected boolean persistFloat(float value) {
+        if (shouldPersist()) {
+            if (value == getPersistedFloat(Float.NaN)) {
+                // It's already there, so the same as persisting
+                return true;
+            }
+
+            SharedPreferences.Editor editor = mPreferenceManager.getEditor();
+            editor.putFloat(mKey, value);
+            tryCommit(editor);
+            return true;
+        }
+        return false;
+    }
+    
+    /**
+     * Attempts to get a persisted float from the {@link android.content.SharedPreferences}.
+     * 
+     * @param defaultReturnValue The default value to return if either this
+     *            Preference is not persistent or this Preference is not in the
+     *            SharedPreferences.
+     * @return The value from the SharedPreferences or the default return
+     *         value.
+     * @see #getPersistedString(String)
+     * @see #persistFloat(float)
+     */
+    protected float getPersistedFloat(float defaultReturnValue) {
+        if (!shouldPersist()) {
+            return defaultReturnValue;
+        }
+        
+        return mPreferenceManager.getSharedPreferences().getFloat(mKey, defaultReturnValue);
+    }
+    
+    /**
+     * Attempts to persist a long to the {@link android.content.SharedPreferences}.
+     * 
+     * @param value The value to persist.
+     * @return True if this Preference is persistent. (This is not whether the
+     *         value was persisted, since we may not necessarily commit if there
+     *         will be a batch commit later.)
+     * @see #persistString(String)
+     * @see #getPersistedLong(long)
+     */
+    protected boolean persistLong(long value) {
+        if (shouldPersist()) {
+            if (value == getPersistedLong(~value)) {
+                // It's already there, so the same as persisting
+                return true;
+            }
+            
+            SharedPreferences.Editor editor = mPreferenceManager.getEditor();
+            editor.putLong(mKey, value);
+            tryCommit(editor);
+            return true;
+        }
+        return false;
+    }
+    
+    /**
+     * Attempts to get a persisted long from the {@link android.content.SharedPreferences}.
+     * 
+     * @param defaultReturnValue The default value to return if either this
+     *            Preference is not persistent or this Preference is not in the
+     *            SharedPreferences.
+     * @return The value from the SharedPreferences or the default return
+     *         value.
+     * @see #getPersistedString(String)
+     * @see #persistLong(long)
+     */
+    protected long getPersistedLong(long defaultReturnValue) {
+        if (!shouldPersist()) {
+            return defaultReturnValue;
+        }
+        
+        return mPreferenceManager.getSharedPreferences().getLong(mKey, defaultReturnValue);
+    }
+    
+    /**
+     * Attempts to persist a boolean to the {@link android.content.SharedPreferences}.
+     * 
+     * @param value The value to persist.
+     * @return True if this Preference is persistent. (This is not whether the
+     *         value was persisted, since we may not necessarily commit if there
+     *         will be a batch commit later.)
+     * @see #persistString(String)
+     * @see #getPersistedBoolean(boolean)
+     */
+    protected boolean persistBoolean(boolean value) {
+        if (shouldPersist()) {
+            if (value == getPersistedBoolean(!value)) {
+                // It's already there, so the same as persisting
+                return true;
+            }
+            
+            SharedPreferences.Editor editor = mPreferenceManager.getEditor();
+            editor.putBoolean(mKey, value);
+            tryCommit(editor);
+            return true;
+        }
+        return false;
+    }
+    
+    /**
+     * Attempts to get a persisted boolean from the {@link android.content.SharedPreferences}.
+     * 
+     * @param defaultReturnValue The default value to return if either this
+     *            Preference is not persistent or this Preference is not in the
+     *            SharedPreferences.
+     * @return The value from the SharedPreferences or the default return
+     *         value.
+     * @see #getPersistedString(String)
+     * @see #persistBoolean(boolean)
+     */
+    protected boolean getPersistedBoolean(boolean defaultReturnValue) {
+        if (!shouldPersist()) {
+            return defaultReturnValue;
+        }
+        
+        return mPreferenceManager.getSharedPreferences().getBoolean(mKey, defaultReturnValue);
+    }
+    
+    boolean hasSpecifiedLayout() {
+        return mHasSpecifiedLayout;
+    }
+    
+    @Override
+    public String toString() {
+        return getFilterableStringBuilder().toString();
+    }
+        
+    /**
+     * Returns the text that will be used to filter this Preference depending on
+     * user input.
+     * <p>
+     * If overridding and calling through to the superclass, make sure to prepend
+     * your additions with a space.
+     * 
+     * @return Text as a {@link StringBuilder} that will be used to filter this
+     *         preference. By default, this is the title and summary
+     *         (concatenated with a space).
+     */
+    StringBuilder getFilterableStringBuilder() {
+        StringBuilder sb = new StringBuilder();
+        CharSequence title = getTitle();
+        if (!TextUtils.isEmpty(title)) {
+            sb.append(title).append(' ');
+        }
+        CharSequence summary = getSummary();
+        if (!TextUtils.isEmpty(summary)) {
+            sb.append(summary).append(' ');
+        }
+        // Drop the last space
+        sb.setLength(sb.length() - 1);
+        return sb;
+    }
+
+    /**
+     * Store this Preference hierarchy's frozen state into the given container.
+     * 
+     * @param container The Bundle in which to save the instance of this Preference.
+     * 
+     * @see #restoreHierarchyState
+     * @see #onSaveInstanceState
+     */
+    public void saveHierarchyState(Bundle container) {
+        dispatchSaveInstanceState(container);
+    }
+
+    /**
+     * Called by {@link #saveHierarchyState} to store the instance for this Preference and its children.
+     * May be overridden to modify how the save happens for children. For example, some
+     * Preference objects may want to not store an instance for their children.
+     * 
+     * @param container The Bundle in which to save the instance of this Preference.
+     * 
+     * @see #saveHierarchyState
+     * @see #onSaveInstanceState
+     */
+    void dispatchSaveInstanceState(Bundle container) {
+        if (hasKey()) {
+            mBaseMethodCalled = false;
+            Parcelable state = onSaveInstanceState();
+            if (!mBaseMethodCalled) {
+                throw new IllegalStateException(
+                        "Derived class did not call super.onSaveInstanceState()");
+            }
+            if (state != null) {
+                container.putParcelable(mKey, state);
+            }
+        }
+    }
+
+    /**
+     * Hook allowing a Preference to generate a representation of its internal
+     * state that can later be used to create a new instance with that same
+     * state. This state should only contain information that is not persistent
+     * or can be reconstructed later.
+     * 
+     * @return A Parcelable object containing the current dynamic state of
+     *         this Preference, or null if there is nothing interesting to save.
+     *         The default implementation returns null.
+     * @see #onRestoreInstanceState
+     * @see #saveHierarchyState
+     */
+    protected Parcelable onSaveInstanceState() {
+        mBaseMethodCalled = true;
+        return BaseSavedState.EMPTY_STATE;
+    }
+
+    /**
+     * Restore this Preference hierarchy's previously saved state from the given container.
+     * 
+     * @param container The Bundle that holds the previously saved state.
+     * 
+     * @see #saveHierarchyState
+     * @see #onRestoreInstanceState
+     */
+    public void restoreHierarchyState(Bundle container) {
+        dispatchRestoreInstanceState(container);
+    }
+
+    /**
+     * Called by {@link #restoreHierarchyState} to retrieve the saved state for this
+     * Preference and its children. May be overridden to modify how restoring
+     * happens to the children of a Preference. For example, some Preference objects may
+     * not want to save state for their children.
+     * 
+     * @param container The Bundle that holds the previously saved state.
+     * @see #restoreHierarchyState
+     * @see #onRestoreInstanceState
+     */
+    void dispatchRestoreInstanceState(Bundle container) {
+        if (hasKey()) {
+            Parcelable state = container.getParcelable(mKey);
+            if (state != null) {
+                mBaseMethodCalled = false;
+                onRestoreInstanceState(state);
+                if (!mBaseMethodCalled) {
+                    throw new IllegalStateException(
+                            "Derived class did not call super.onRestoreInstanceState()");
+                }
+            }
+        }
+    }
+
+    /**
+     * Hook allowing a Preference to re-apply a representation of its internal
+     * state that had previously been generated by {@link #onSaveInstanceState}.
+     * This function will never be called with a null state.
+     * 
+     * @param state The saved state that had previously been returned by
+     *            {@link #onSaveInstanceState}.
+     * @see #onSaveInstanceState
+     * @see #restoreHierarchyState
+     */
+    protected void onRestoreInstanceState(Parcelable state) {
+        mBaseMethodCalled = true;
+        if (state != BaseSavedState.EMPTY_STATE && state != null) {
+            throw new IllegalArgumentException("Wrong state class -- expecting Preference State");
+        }
+    }
+
+    /**
+     * A base class for managing the instance state of a {@link Preference}.
+     */
+    public static class BaseSavedState extends AbsSavedState {
+        public BaseSavedState(Parcel source) {
+            super(source);
+        }
+
+        public BaseSavedState(Parcelable superState) {
+            super(superState);
+        }
+        
+        public static final Parcelable.Creator<BaseSavedState> CREATOR =
+                new Parcelable.Creator<BaseSavedState>() {
+            public BaseSavedState createFromParcel(Parcel in) {
+                return new BaseSavedState(in);
+            }
+
+            public BaseSavedState[] newArray(int size) {
+                return new BaseSavedState[size];
+            }
+        };
+    }
+
+}
diff --git a/core/java/android/preference/PreferenceActivity.java b/core/java/android/preference/PreferenceActivity.java
new file mode 100644
index 0000000..837ce91
--- /dev/null
+++ b/core/java/android/preference/PreferenceActivity.java
@@ -0,0 +1,287 @@
+/*
+ * Copyright (C) 2007 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 android.preference;
+
+import android.app.Activity;
+import android.app.ListActivity;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
+import android.view.View;
+import android.view.Window;
+
+/**
+ * Shows a hierarchy of {@link Preference} objects as
+ * lists, possibly spanning multiple screens. These preferences will
+ * automatically save to {@link SharedPreferences} as the user interacts with
+ * them. To retrieve an instance of {@link SharedPreferences} that the
+ * preference hierarchy in this activity will use, call
+ * {@link PreferenceManager#getDefaultSharedPreferences(android.content.Context)}
+ * with a context in the same package as this activity.
+ * <p>
+ * Furthermore, the preferences shown will follow the visual style of system
+ * preferences. It is easy to create a hierarchy of preferences (that can be
+ * shown on multiple screens) via XML. For these reasons, it is recommended to
+ * use this activity (as a superclass) to deal with preferences in applications.
+ * <p>
+ * A {@link PreferenceScreen} object should be at the top of the preference
+ * hierarchy. Furthermore, subsequent {@link PreferenceScreen} in the hierarchy
+ * denote a screen break--that is the preferences contained within subsequent
+ * {@link PreferenceScreen} should be shown on another screen. The preference
+ * framework handles showing these other screens from the preference hierarchy.
+ * <p>
+ * The preference hierarchy can be formed in multiple ways:
+ * <li> From an XML file specifying the hierarchy
+ * <li> From different {@link Activity Activities} that each specify its own
+ * preferences in an XML file via {@link Activity} meta-data
+ * <li> From an object hierarchy rooted with {@link PreferenceScreen}
+ * <p>
+ * To inflate from XML, use the {@link #addPreferencesFromResource(int)}. The
+ * root element should be a {@link PreferenceScreen}. Subsequent elements can point
+ * to actual {@link Preference} subclasses. As mentioned above, subsequent
+ * {@link PreferenceScreen} in the hierarchy will result in the screen break.
+ * <p>
+ * To specify an {@link Intent} to query {@link Activity Activities} that each
+ * have preferences, use {@link #addPreferencesFromIntent}. Each
+ * {@link Activity} can specify meta-data in the manifest (via the key
+ * {@link PreferenceManager#METADATA_KEY_PREFERENCES}) that points to an XML
+ * resource. These XML resources will be inflated into a single preference
+ * hierarchy and shown by this activity.
+ * <p>
+ * To specify an object hierarchy rooted with {@link PreferenceScreen}, use
+ * {@link #setPreferenceScreen(PreferenceScreen)}.
+ * <p>
+ * As a convenience, this activity implements a click listener for any
+ * preference in the current hierarchy, see
+ * {@link #onPreferenceTreeClick(PreferenceScreen, Preference)}.
+ * 
+ * @see Preference
+ * @see PreferenceScreen
+ */
+public abstract class PreferenceActivity extends ListActivity implements
+        PreferenceManager.OnPreferenceTreeClickListener {
+    
+    private static final String PREFERENCES_TAG = "android:preferences";
+    
+    private PreferenceManager mPreferenceManager;
+    
+    /**
+     * The starting request code given out to preference framework.
+     */
+    private static final int FIRST_REQUEST_CODE = 100;
+    
+    private static final int MSG_BIND_PREFERENCES = 0;
+    private Handler mHandler = new Handler() {
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                
+                case MSG_BIND_PREFERENCES:
+                    bindPreferences();
+                    break;
+            }
+        }
+    };
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        setContentView(com.android.internal.R.layout.preference_list_content);
+        
+        mPreferenceManager = onCreatePreferenceManager();
+        getListView().setScrollBarStyle(View.SCROLLBARS_INSIDE_OVERLAY);
+    }
+
+    @Override
+    protected void onStop() {
+        super.onStop();
+        
+        mPreferenceManager.dispatchActivityStop();
+    }
+
+    @Override
+    protected void onDestroy() {
+        super.onDestroy();
+        
+        mPreferenceManager.dispatchActivityDestroy();
+    }
+
+    @Override
+    protected void onSaveInstanceState(Bundle outState) {
+        super.onSaveInstanceState(outState);
+
+        final PreferenceScreen preferenceScreen = getPreferenceScreen();
+        if (preferenceScreen != null) {
+            Bundle container = new Bundle();
+            preferenceScreen.saveHierarchyState(container);
+            outState.putBundle(PREFERENCES_TAG, container);
+        }
+    }
+
+    @Override
+    protected void onRestoreInstanceState(Bundle state) {
+        super.onRestoreInstanceState(state);
+
+        Bundle container = state.getBundle(PREFERENCES_TAG);
+        if (container != null) {
+            final PreferenceScreen preferenceScreen = getPreferenceScreen();
+            if (preferenceScreen != null) {
+                preferenceScreen.restoreHierarchyState(container);
+            }
+        }
+    }
+
+    @Override
+    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+        super.onActivityResult(requestCode, resultCode, data);
+        
+        mPreferenceManager.dispatchActivityResult(requestCode, resultCode, data);
+    }
+
+    @Override
+    public void onContentChanged() {
+        super.onContentChanged();
+        postBindPreferences();
+    }
+
+    /**
+     * Posts a message to bind the preferences to the list view.
+     * <p>
+     * Binding late is preferred as any custom preference types created in
+     * {@link #onCreate(Bundle)} are able to have their views recycled.
+     */
+    private void postBindPreferences() {
+        if (mHandler.hasMessages(MSG_BIND_PREFERENCES)) return;
+        mHandler.obtainMessage(MSG_BIND_PREFERENCES).sendToTarget();
+    }
+    
+    private void bindPreferences() {
+        final PreferenceScreen preferenceScreen = getPreferenceScreen();
+        if (preferenceScreen != null) {
+            preferenceScreen.bind(getListView());
+        }
+    }
+    
+    /**
+     * Creates the {@link PreferenceManager}.
+     * 
+     * @return The {@link PreferenceManager} used by this activity.
+     */
+    private PreferenceManager onCreatePreferenceManager() {
+        PreferenceManager preferenceManager = new PreferenceManager(this, FIRST_REQUEST_CODE);
+        preferenceManager.setOnPreferenceTreeClickListener(this);
+        return preferenceManager;
+    }
+    
+    /**
+     * Returns the {@link PreferenceManager} used by this activity.
+     * @return The {@link PreferenceManager}.
+     */
+    public PreferenceManager getPreferenceManager() {
+        return mPreferenceManager;
+    }
+    
+    private void requirePreferenceManager() {
+        if (mPreferenceManager == null) {
+            throw new RuntimeException("This should be called after super.onCreate.");
+        }
+    }
+
+    /**
+     * Sets the root of the preference hierarchy that this activity is showing.
+     * 
+     * @param preferenceScreen The root {@link PreferenceScreen} of the preference hierarchy.
+     */
+    public void setPreferenceScreen(PreferenceScreen preferenceScreen) {
+        if (mPreferenceManager.setPreferences(preferenceScreen) && preferenceScreen != null) {
+            postBindPreferences();
+            CharSequence title = getPreferenceScreen().getTitle();
+            // Set the title of the activity
+            if (title != null) {
+                setTitle(title);
+            }
+        }
+    }
+    
+    /**
+     * Gets the root of the preference hierarchy that this activity is showing.
+     * 
+     * @return The {@link PreferenceScreen} that is the root of the preference
+     *         hierarchy.
+     */
+    public PreferenceScreen getPreferenceScreen() {
+        return mPreferenceManager.getPreferenceScreen();
+    }
+    
+    /**
+     * Adds preferences from activities that match the given {@link Intent}.
+     * 
+     * @param intent The {@link Intent} to query activities.
+     */
+    public void addPreferencesFromIntent(Intent intent) {
+        requirePreferenceManager();
+        
+        setPreferenceScreen(mPreferenceManager.inflateFromIntent(intent, getPreferenceScreen()));
+    }
+    
+    /**
+     * Inflates the given XML resource and adds the preference hierarchy to the current
+     * preference hierarchy.
+     * 
+     * @param preferencesResId The XML resource ID to inflate.
+     */
+    public void addPreferencesFromResource(int preferencesResId) {
+        requirePreferenceManager();
+        
+        setPreferenceScreen(mPreferenceManager.inflateFromResource(this, preferencesResId,
+                getPreferenceScreen()));
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) {
+        return false;
+    }
+    
+    /**
+     * Finds a {@link Preference} based on its key.
+     * 
+     * @param key The key of the preference to retrieve.
+     * @return The {@link Preference} with the key, or null.
+     * @see PreferenceGroup#findPreference(CharSequence)
+     */
+    public Preference findPreference(CharSequence key) {
+        
+        if (mPreferenceManager == null) {
+            return null;
+        }
+        
+        return mPreferenceManager.findPreference(key);
+    }
+
+    @Override
+    protected void onNewIntent(Intent intent) {
+        if (mPreferenceManager != null) {
+            mPreferenceManager.dispatchNewIntent(intent);
+        }
+    }
+    
+}
diff --git a/core/java/android/preference/PreferenceCategory.java b/core/java/android/preference/PreferenceCategory.java
new file mode 100644
index 0000000..237c5ce
--- /dev/null
+++ b/core/java/android/preference/PreferenceCategory.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2007 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 android.preference;
+
+import java.util.Map;
+
+import android.content.Context;
+import android.util.AttributeSet;
+
+/**
+ * Used to group {@link Preference} objects
+ * and provide a disabled title above the group.
+ */
+public class PreferenceCategory extends PreferenceGroup {
+    private static final String TAG = "PreferenceCategory";
+    
+    public PreferenceCategory(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+    }
+
+    public PreferenceCategory(Context context, AttributeSet attrs) {
+        this(context, attrs, com.android.internal.R.attr.preferenceCategoryStyle);
+    }
+
+    public PreferenceCategory(Context context) {
+        this(context, null);
+    }
+    
+    @Override
+    protected boolean onPrepareAddPreference(Preference preference) {
+        if (preference instanceof PreferenceCategory) {
+            throw new IllegalArgumentException(
+                    "Cannot add a " + TAG + " directly to a " + TAG);
+        }
+        
+        return super.onPrepareAddPreference(preference);
+    }
+
+    @Override
+    public boolean isEnabled() {
+        return false;
+    }
+    
+}
diff --git a/core/java/android/preference/PreferenceGroup.java b/core/java/android/preference/PreferenceGroup.java
new file mode 100644
index 0000000..d008fd6
--- /dev/null
+++ b/core/java/android/preference/PreferenceGroup.java
@@ -0,0 +1,324 @@
+/*
+ * Copyright (C) 2007 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 android.preference;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.os.Bundle;
+import android.os.Parcelable;
+import android.text.TextUtils;
+import android.util.AttributeSet;
+
+/**
+ * A container for multiple
+ * {@link Preference} objects. It is a base class for  Preference objects that are
+ * parents, such as {@link PreferenceCategory} and {@link PreferenceScreen}.
+ * 
+ * @attr ref android.R.styleable#PreferenceGroup_orderingFromXml
+ */
+public abstract class PreferenceGroup extends Preference implements GenericInflater.Parent<Preference> {
+    /**
+     * The container for child {@link Preference}s. This is sorted based on the
+     * ordering, please use {@link #addPreference(Preference)} instead of adding
+     * to this directly.
+     */
+    private List<Preference> mPreferenceList;
+
+    private boolean mOrderingAsAdded = true;
+
+    private int mCurrentPreferenceOrder = 0;
+
+    private boolean mAttachedToActivity = false;
+    
+    public PreferenceGroup(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+
+        mPreferenceList = new ArrayList<Preference>();
+
+        TypedArray a = context.obtainStyledAttributes(attrs,
+                com.android.internal.R.styleable.PreferenceGroup, defStyle, 0);
+        mOrderingAsAdded = a.getBoolean(com.android.internal.R.styleable.PreferenceGroup_orderingFromXml,
+                mOrderingAsAdded);
+        a.recycle();
+    }
+
+    public PreferenceGroup(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    /**
+     * Whether to order the {@link Preference} children of this group as they
+     * are added. If this is false, the ordering will follow each Preference
+     * order and default to alphabetic for those without an order.
+     * <p>
+     * If this is called after preferences are added, they will not be
+     * re-ordered in the order they were added, hence call this method early on.
+     * 
+     * @param orderingAsAdded Whether to order according to the order added.
+     * @see Preference#setOrder(int)
+     */
+    public void setOrderingAsAdded(boolean orderingAsAdded) {
+        mOrderingAsAdded = orderingAsAdded;
+    }
+
+    /**
+     * Whether this group is ordering preferences in the order they are added.
+     * 
+     * @return Whether this group orders based on the order the children are added.
+     * @see #setOrderingAsAdded(boolean)
+     */
+    public boolean isOrderingAsAdded() {
+        return mOrderingAsAdded;
+    }
+
+    /**
+     * Called by the inflater to add an item to this group.
+     */
+    public void addItemFromInflater(Preference preference) {
+        addPreference(preference);
+    }
+
+    /**
+     * Returns the number of children {@link Preference}s.
+     * @return The number of preference children in this group.
+     */
+    public int getPreferenceCount() {
+        return mPreferenceList.size();
+    }
+
+    /**
+     * Returns the {@link Preference} at a particular index.
+     * 
+     * @param index The index of the {@link Preference} to retrieve.
+     * @return The {@link Preference}.
+     */
+    public Preference getPreference(int index) {
+        return mPreferenceList.get(index);
+    }
+
+    /**
+     * Adds a {@link Preference} at the correct position based on the
+     * preference's order.
+     * 
+     * @param preference The preference to add.
+     * @return Whether the preference is now in this group.
+     */
+    public boolean addPreference(Preference preference) {
+        if (mPreferenceList.contains(preference)) {
+            // Exists
+            return true;
+        }
+        
+        if (preference.getOrder() == Preference.DEFAULT_ORDER) {
+            if (mOrderingAsAdded) {
+                preference.setOrder(mCurrentPreferenceOrder++);
+            }
+
+            if (preference instanceof PreferenceGroup) {
+                // TODO: fix (method is called tail recursively when inflating,
+                // so we won't end up properly passing this flag down to children
+                ((PreferenceGroup)preference).setOrderingAsAdded(mOrderingAsAdded);
+            }
+        }
+
+        int insertionIndex = Collections.binarySearch(mPreferenceList, preference);
+        if (insertionIndex < 0) {
+            insertionIndex = insertionIndex * -1 - 1;
+        }
+
+        if (!onPrepareAddPreference(preference)) {
+            return false;
+        }
+
+        synchronized(this) {
+            mPreferenceList.add(insertionIndex, preference);
+        }
+
+        preference.onAttachedToHierarchy(getPreferenceManager());
+        
+        if (mAttachedToActivity) {
+            preference.onAttachedToActivity();
+        }
+        
+        notifyHierarchyChanged();
+
+        return true;
+    }
+
+    /**
+     * Removes a {@link Preference} from this group.
+     * 
+     * @param preference The preference to remove.
+     * @return Whether the preference was found and removed.
+     */
+    public boolean removePreference(Preference preference) {
+        final boolean returnValue = removePreferenceInt(preference);
+        notifyHierarchyChanged();
+        return returnValue;
+    }
+
+    private boolean removePreferenceInt(Preference preference) {
+        synchronized(this) {
+            preference.onPrepareForRemoval();
+            return mPreferenceList.remove(preference);
+        }
+    }
+    
+    /**
+     * Removes all {@link Preference Preferences} from this group.
+     */
+    public void removeAll() {
+        synchronized(this) {
+            List<Preference> preferenceList = mPreferenceList;
+            for (int i = preferenceList.size() - 1; i >= 0; i--) {
+                removePreferenceInt(preferenceList.get(0));
+            }
+        }
+        notifyHierarchyChanged();
+    }
+    
+    /**
+     * Prepares a {@link Preference} to be added to the group.
+     * 
+     * @param preference The preference to add.
+     * @return Whether to allow adding the preference (true), or not (false).
+     */
+    protected boolean onPrepareAddPreference(Preference preference) {
+        if (!super.isEnabled()) {
+            preference.setEnabled(false);
+        }
+        
+        return true;
+    }
+
+    /**
+     * Finds a {@link Preference} based on its key. If two {@link Preference}
+     * share the same key (not recommended), the first to appear will be
+     * returned (to retrieve the other preference with the same key, call this
+     * method on the first preference). If this preference has the key, it will
+     * not be returned.
+     * <p>
+     * This will recursively search for the preference into children that are
+     * also {@link PreferenceGroup PreferenceGroups}.
+     * 
+     * @param key The key of the preference to retrieve.
+     * @return The {@link Preference} with the key, or null.
+     */
+    public Preference findPreference(CharSequence key) {
+        if (TextUtils.equals(getKey(), key)) {
+            return this;
+        }
+        final int preferenceCount = getPreferenceCount();
+        for (int i = 0; i < preferenceCount; i++) {
+            final Preference preference = getPreference(i);
+            final String curKey = preference.getKey();
+
+            if (curKey != null && curKey.equals(key)) {
+                return preference;
+            }
+            
+            if (preference instanceof PreferenceGroup) {
+                final Preference returnedPreference = ((PreferenceGroup)preference)
+                        .findPreference(key);
+                if (returnedPreference != null) {
+                    return returnedPreference;
+                }
+            }
+        }
+
+        return null;
+    }
+
+    /**
+     * Whether this preference group should be shown on the same screen as its
+     * contained preferences.
+     * 
+     * @return True if the contained preferences should be shown on the same
+     *         screen as this preference.
+     */
+    protected boolean isOnSameScreenAsChildren() {
+        return true;
+    }
+    
+    @Override
+    protected void onAttachedToActivity() {
+        super.onAttachedToActivity();
+
+        // Mark as attached so if a preference is later added to this group, we
+        // can tell it we are already attached
+        mAttachedToActivity = true;
+        
+        // Dispatch to all contained preferences
+        final int preferenceCount = getPreferenceCount();
+        for (int i = 0; i < preferenceCount; i++) {
+            getPreference(i).onAttachedToActivity();
+        }
+    }
+
+    @Override
+    protected void onPrepareForRemoval() {
+        super.onPrepareForRemoval();
+        
+        // We won't be attached to the activity anymore
+        mAttachedToActivity = false;
+    }
+
+    @Override
+    public void setEnabled(boolean enabled) {
+        super.setEnabled(enabled);
+        
+        // Dispatch to all contained preferences
+        final int preferenceCount = getPreferenceCount();
+        for (int i = 0; i < preferenceCount; i++) {
+            getPreference(i).setEnabled(enabled);
+        }
+    }
+    
+    void sortPreferences() {
+        synchronized (this) {
+            Collections.sort(mPreferenceList);
+        }
+    }
+
+    @Override
+    protected void dispatchSaveInstanceState(Bundle container) {
+        super.dispatchSaveInstanceState(container);
+
+        // Dispatch to all contained preferences
+        final int preferenceCount = getPreferenceCount();
+        for (int i = 0; i < preferenceCount; i++) {
+            getPreference(i).dispatchSaveInstanceState(container);
+        }
+    }
+    
+    @Override
+    protected void dispatchRestoreInstanceState(Bundle container) {
+        super.dispatchRestoreInstanceState(container);
+
+        // Dispatch to all contained preferences
+        final int preferenceCount = getPreferenceCount();
+        for (int i = 0; i < preferenceCount; i++) {
+            getPreference(i).dispatchRestoreInstanceState(container);
+        }
+    }
+
+}
diff --git a/core/java/android/preference/PreferenceGroupAdapter.java b/core/java/android/preference/PreferenceGroupAdapter.java
new file mode 100644
index 0000000..14c0054
--- /dev/null
+++ b/core/java/android/preference/PreferenceGroupAdapter.java
@@ -0,0 +1,248 @@
+/*
+ * Copyright (C) 2007 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 android.preference;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import android.os.Handler;
+import android.preference.Preference.OnPreferenceChangeInternalListener;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.Adapter;
+import android.widget.BaseAdapter;
+import android.widget.ListView;
+
+/**
+ * An adapter that returns the {@link Preference} contained in this group.
+ * In most cases, this adapter should be the base class for any custom
+ * adapters from {@link Preference#getAdapter()}.
+ * <p>
+ * This adapter obeys the
+ * {@link Preference}'s adapter rule (the
+ * {@link Adapter#getView(int, View, ViewGroup)} should be used instead of
+ * {@link Preference#getView(ViewGroup)} if a {@link Preference} has an
+ * adapter via {@link Preference#getAdapter()}).
+ * <p>
+ * This adapter also propagates data change/invalidated notifications upward.
+ * <p>
+ * This adapter does not include this {@link PreferenceGroup} in the returned
+ * adapter, use {@link PreferenceCategoryAdapter} instead.
+ * 
+ * @see PreferenceCategoryAdapter
+ */
+class PreferenceGroupAdapter extends BaseAdapter implements OnPreferenceChangeInternalListener {
+    
+    private static final String TAG = "PreferenceGroupAdapter";
+
+    /**
+     * The group that we are providing data from.
+     */
+    private PreferenceGroup mPreferenceGroup;
+    
+    /**
+     * Maps a position into this adapter -> {@link Preference}. These
+     * {@link Preference}s don't have to be direct children of this
+     * {@link PreferenceGroup}, they can be grand children or younger)
+     */
+    private List<Preference> mPreferenceList;
+    
+    /**
+     * List of unique Preference and its subclasses' names. This is used to find
+     * out how many types of views this adapter can return. Once the count is
+     * returned, this cannot be modified (since the ListView only checks the
+     * count once--when the adapter is being set). We will not recycle views for
+     * Preference subclasses seen after the count has been returned.
+     */
+    private List<String> mPreferenceClassNames;
+
+    /**
+     * Blocks the mPreferenceClassNames from being changed anymore.
+     */
+    private boolean mHasReturnedViewTypeCount = false;
+    
+    private volatile boolean mIsSyncing = false;
+    
+    private Handler mHandler = new Handler(); 
+    
+    private Runnable mSyncRunnable = new Runnable() {
+        public void run() {
+            syncMyPreferences();
+        }
+    };
+
+    public PreferenceGroupAdapter(PreferenceGroup preferenceGroup) {
+        mPreferenceGroup = preferenceGroup;
+        // If this group gets or loses any children, let us know
+        mPreferenceGroup.setOnPreferenceChangeInternalListener(this);
+        
+        mPreferenceList = new ArrayList<Preference>();
+        mPreferenceClassNames = new ArrayList<String>();
+        
+        syncMyPreferences();
+    }
+
+    private void syncMyPreferences() {
+        synchronized(this) {
+            if (mIsSyncing) {
+                return;
+            }
+        
+            mIsSyncing = true;
+        }
+
+        List<Preference> newPreferenceList = new ArrayList<Preference>(mPreferenceList.size());
+        flattenPreferenceGroup(newPreferenceList, mPreferenceGroup);
+        mPreferenceList = newPreferenceList;
+        
+        notifyDataSetChanged();
+
+        synchronized(this) {
+            mIsSyncing = false;
+            notifyAll();
+        }
+    }
+    
+    private void flattenPreferenceGroup(List<Preference> preferences, PreferenceGroup group) {
+        // TODO: shouldn't always?
+        group.sortPreferences();
+
+        final int groupSize = group.getPreferenceCount();
+        for (int i = 0; i < groupSize; i++) {
+            final Preference preference = group.getPreference(i);
+            
+            preferences.add(preference);
+            
+            if (!mHasReturnedViewTypeCount) {
+                addPreferenceClassName(preference);
+            }
+            
+            if (preference instanceof PreferenceGroup) {
+                final PreferenceGroup preferenceAsGroup = (PreferenceGroup) preference;
+                if (preferenceAsGroup.isOnSameScreenAsChildren()) {
+                    flattenPreferenceGroup(preferences, preferenceAsGroup);
+                }
+            }
+
+            preference.setOnPreferenceChangeInternalListener(this);
+        }
+    }
+
+    private void addPreferenceClassName(Preference preference) {
+        final String name = preference.getClass().getName();
+        int insertPos = Collections.binarySearch(mPreferenceClassNames, name);
+        
+        // Only insert if it doesn't exist (when it is negative).
+        if (insertPos < 0) {
+            // Convert to insert index
+            insertPos = insertPos * -1 - 1;
+            mPreferenceClassNames.add(insertPos, name);
+        }
+    }
+    
+    public int getCount() {
+        return mPreferenceList.size();
+    }
+
+    public Preference getItem(int position) {
+        if (position < 0 || position >= getCount()) return null;
+        return mPreferenceList.get(position);
+    }
+
+    public long getItemId(int position) {
+        if (position < 0 || position >= getCount()) return ListView.INVALID_ROW_ID;
+        return this.getItem(position).getId();
+    }
+
+    public View getView(int position, View convertView, ViewGroup parent) {
+        final Preference preference = this.getItem(position);
+        
+        if (preference.hasSpecifiedLayout()) {
+            // If the preference had specified a layout (as opposed to the
+            // default), don't use convert views.
+            convertView = null;
+        } else {
+            // TODO: better way of doing this
+            final String name = preference.getClass().getName();
+            if (Collections.binarySearch(mPreferenceClassNames, name) < 0) {
+                convertView = null;
+            }
+        }
+        
+        return preference.getView(convertView, parent);
+    }
+
+    @Override
+    public boolean isEnabled(int position) {
+        if (position < 0 || position >= getCount()) return true;
+        return this.getItem(position).isSelectable();
+    }
+
+    @Override
+    public boolean areAllItemsEnabled() {
+        // There should always be a preference group, and these groups are always
+        // disabled
+        return false;
+    }
+
+    public void onPreferenceChange(Preference preference) {
+        notifyDataSetChanged();
+    }
+
+    public void onPreferenceHierarchyChange(Preference preference) {
+        mHandler.removeCallbacks(mSyncRunnable);
+        mHandler.post(mSyncRunnable);
+    }
+
+    @Override
+    public boolean hasStableIds() {
+        return true;
+    }
+
+    @Override
+    public int getItemViewType(int position) {
+        if (!mHasReturnedViewTypeCount) {
+            mHasReturnedViewTypeCount = true;
+        }
+        
+        final Preference preference = this.getItem(position);
+        if (preference.hasSpecifiedLayout()) {
+            return IGNORE_ITEM_VIEW_TYPE;
+        }
+
+        final String name = preference.getClass().getName();
+        int viewType = Collections.binarySearch(mPreferenceClassNames, name);
+        if (viewType < 0) {
+            // This is a class that was seen after we returned the count, so
+            // don't recycle it.
+            return IGNORE_ITEM_VIEW_TYPE;
+        } else {
+            return viewType;
+        }
+    }
+
+    @Override
+    public int getViewTypeCount() {
+        if (!mHasReturnedViewTypeCount) {
+            mHasReturnedViewTypeCount = true;
+        }
+        
+        return Math.max(1, mPreferenceClassNames.size());
+    }
+
+}
diff --git a/core/java/android/preference/PreferenceInflater.java b/core/java/android/preference/PreferenceInflater.java
new file mode 100644
index 0000000..779e746
--- /dev/null
+++ b/core/java/android/preference/PreferenceInflater.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2007 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 android.preference;
+
+import java.io.IOException;
+import java.util.Map;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import android.app.AliasActivity;
+import android.content.Context;
+import android.content.Intent;
+import android.util.AttributeSet;
+import android.util.Log;
+
+/**
+ * The {@link PreferenceInflater} is used to inflate preference hierarchies from
+ * XML files.
+ * <p>
+ * Do not construct this directly, instead use
+ * {@link Context#getSystemService(String)} with
+ * {@link Context#PREFERENCE_INFLATER_SERVICE}.
+ */
+class PreferenceInflater extends GenericInflater<Preference, PreferenceGroup> {
+    private static final String TAG = "PreferenceInflater";
+    private static final String INTENT_TAG_NAME = "intent";
+
+    private PreferenceManager mPreferenceManager;
+    
+    public PreferenceInflater(Context context, PreferenceManager preferenceManager) {
+        super(context);
+        init(preferenceManager);
+    }
+
+    PreferenceInflater(GenericInflater<Preference, PreferenceGroup> original, PreferenceManager preferenceManager, Context newContext) {
+        super(original, newContext);
+        init(preferenceManager);
+    }
+
+    @Override
+    public GenericInflater<Preference, PreferenceGroup> cloneInContext(Context newContext) {
+        return new PreferenceInflater(this, mPreferenceManager, newContext);
+    }
+    
+    private void init(PreferenceManager preferenceManager) {
+        mPreferenceManager = preferenceManager;
+        setDefaultPackage("android.preference.");
+    }
+
+    @Override
+    protected boolean onCreateCustomFromTag(XmlPullParser parser, Preference parentPreference,
+            AttributeSet attrs) throws XmlPullParserException {
+        final String tag = parser.getName();
+        
+        if (tag.equals(INTENT_TAG_NAME)) {
+            Intent intent = null;
+            
+            try {
+                intent = Intent.parseIntent(getContext().getResources(), parser, attrs);
+            } catch (IOException e) {
+                Log.w(TAG, "Could not parse Intent.");
+                Log.w(TAG, e);
+            }
+            
+            if (intent != null) {
+                parentPreference.setIntent(intent);
+            }
+            
+            return true;
+        }
+        
+        return false;
+    }
+
+    @Override
+    protected PreferenceGroup onMergeRoots(PreferenceGroup givenRoot, boolean attachToGivenRoot,
+            PreferenceGroup xmlRoot) {
+        // If we were given a Preferences, use it as the root (ignoring the root
+        // Preferences from the XML file).
+        if (givenRoot == null) {
+            xmlRoot.onAttachedToHierarchy(mPreferenceManager);
+            return xmlRoot;
+        } else {
+            return givenRoot;
+        }
+    }
+    
+}
diff --git a/core/java/android/preference/PreferenceManager.java b/core/java/android/preference/PreferenceManager.java
new file mode 100644
index 0000000..a7a3eef
--- /dev/null
+++ b/core/java/android/preference/PreferenceManager.java
@@ -0,0 +1,799 @@
+/*
+ * Copyright (C) 2007 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 android.preference;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+
+import android.app.Activity;
+import android.app.Dialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.content.pm.ActivityInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.res.XmlResourceParser;
+import android.os.Bundle;
+import android.util.Log;
+
+/**
+ * Used to help create {@link Preference} hierarchies
+ * from activities or XML.
+ * <p>
+ * In most cases, clients should use
+ * {@link PreferenceActivity#addPreferencesFromIntent} or
+ * {@link PreferenceActivity#addPreferencesFromResource(int)}.
+ * 
+ * @see PreferenceActivity
+ */
+public class PreferenceManager {
+    
+    private static final String TAG = "PreferenceManager";
+
+    /**
+     * The Activity meta-data key for its XML preference hierarchy.
+     */
+    public static final String METADATA_KEY_PREFERENCES = "android.preference";
+    
+    public static final String KEY_HAS_SET_DEFAULT_VALUES = "_has_set_default_values";
+    
+    /**
+     * @see #getActivity()
+     */
+    private Activity mActivity;
+
+    /**
+     * The context to use. This should always be set.
+     * 
+     * @see #mActivity
+     */
+    private Context mContext;
+    
+    /**
+     * The counter for unique IDs.
+     */
+    private long mNextId = 0;
+
+    /**
+     * The counter for unique request codes.
+     */
+    private int mNextRequestCode;
+
+    /**
+     * Cached shared preferences.
+     */
+    private SharedPreferences mSharedPreferences;
+    
+    /**
+     * If in no-commit mode, the shared editor to give out (which will be
+     * committed when exiting no-commit mode).
+     */
+    private SharedPreferences.Editor mEditor;
+    
+    /**
+     * Blocks commits from happening on the shared editor. This is used when
+     * inflating the hierarchy. Do not set this directly, use {@link #setNoCommit(boolean)}
+     */
+    private boolean mNoCommit;
+    
+    /**
+     * The SharedPreferences name that will be used for all {@link Preference}s
+     * managed by this instance.
+     */
+    private String mSharedPreferencesName;
+    
+    /**
+     * The SharedPreferences mode that will be used for all {@link Preference}s
+     * managed by this instance.
+     */
+    private int mSharedPreferencesMode;
+    
+    /**
+     * The {@link PreferenceScreen} at the root of the preference hierarchy.
+     */
+    private PreferenceScreen mPreferenceScreen;
+
+    /**
+     * List of activity result listeners.
+     */
+    private List<OnActivityResultListener> mActivityResultListeners;
+
+    /**
+     * List of activity stop listeners.
+     */
+    private List<OnActivityStopListener> mActivityStopListeners;
+
+    /**
+     * List of activity destroy listeners.
+     */
+    private List<OnActivityDestroyListener> mActivityDestroyListeners;
+
+    /**
+     * List of dialogs that should be dismissed when we receive onNewIntent in
+     * our PreferenceActivity.
+     */
+    private List<DialogInterface> mPreferencesScreens;
+    
+    private OnPreferenceTreeClickListener mOnPreferenceTreeClickListener;
+    
+    PreferenceManager(Activity activity, int firstRequestCode) {
+        mActivity = activity;
+        mNextRequestCode = firstRequestCode;
+        
+        init(activity);
+    }
+
+    /**
+     * This constructor should ONLY be used when getting default values from
+     * an XML preference hierarchy.
+     * <p>
+     * The {@link PreferenceManager#PreferenceManager(Activity)}
+     * should be used ANY time a preference will be displayed, since some preference
+     * types need an Activity for managed queries.
+     */
+    private PreferenceManager(Context context) {
+        init(context);
+    }
+
+    private void init(Context context) {
+        mContext = context;
+        
+        setSharedPreferencesName(getDefaultSharedPreferencesName(context));
+    }
+    
+    /**
+     * Returns a list of {@link Activity} (indirectly) that match a given
+     * {@link Intent}.
+     * 
+     * @param queryIntent The Intent to match.
+     * @return The list of {@link ResolveInfo} that point to the matched
+     *         activities.
+     */
+    private List<ResolveInfo> queryIntentActivities(Intent queryIntent) {
+        return mContext.getPackageManager().queryIntentActivities(queryIntent,
+                PackageManager.GET_META_DATA);
+    }
+    
+    /**
+     * Inflates a preference hierarchy from the preference hierarchies of
+     * {@link Activity Activities} that match the given {@link Intent}. An
+     * {@link Activity} defines its preference hierarchy with meta-data using
+     * the {@link #METADATA_KEY_PREFERENCES} key.
+     * <p>
+     * If a preference hierarchy is given, the new preference hierarchies will
+     * be merged in.
+     * 
+     * @param queryIntent The intent to match activities.
+     * @param rootPreferences Optional existing hierarchy to merge the new
+     *            hierarchies into.
+     * @return The root hierarchy (if one was not provided, the new hierarchy's
+     *         root).
+     */
+    PreferenceScreen inflateFromIntent(Intent queryIntent, PreferenceScreen rootPreferences) {
+        final List<ResolveInfo> activities = queryIntentActivities(queryIntent);
+        final HashSet<String> inflatedRes = new HashSet<String>();
+
+        for (int i = activities.size() - 1; i >= 0; i--) {
+            final ActivityInfo activityInfo = activities.get(i).activityInfo;
+            final Bundle metaData = activityInfo.metaData;
+
+            if ((metaData == null) || !metaData.containsKey(METADATA_KEY_PREFERENCES)) {
+                continue;
+            }
+
+            // Need to concat the package with res ID since the same res ID
+            // can be re-used across contexts
+            final String uniqueResId = activityInfo.packageName + ":"
+                    + activityInfo.metaData.getInt(METADATA_KEY_PREFERENCES);
+            
+            if (!inflatedRes.contains(uniqueResId)) {
+                inflatedRes.add(uniqueResId);
+
+                final Context context;
+                try {
+                    context = mContext.createPackageContext(activityInfo.packageName, 0);
+                } catch (NameNotFoundException e) {
+                    Log.w(TAG, "Could not create context for " + activityInfo.packageName + ": "
+                        + Log.getStackTraceString(e));
+                    continue;
+                }
+                
+                final PreferenceInflater inflater = new PreferenceInflater(context, this);
+                final XmlResourceParser parser = activityInfo.loadXmlMetaData(context
+                        .getPackageManager(), METADATA_KEY_PREFERENCES);
+                rootPreferences = (PreferenceScreen) inflater
+                        .inflate(parser, rootPreferences, true);
+                parser.close();
+            }
+        }
+
+        rootPreferences.onAttachedToHierarchy(this);
+        
+        return rootPreferences;
+    }
+
+    /**
+     * Inflates a preference hierarchy from XML. If a preference hierarchy is
+     * given, the new preference hierarchies will be merged in.
+     * 
+     * @param context The context of the resource.
+     * @param resId The resource ID of the XML to inflate.
+     * @param rootPreferences Optional existing hierarchy to merge the new
+     *            hierarchies into.
+     * @return The root hierarchy (if one was not provided, the new hierarchy's
+     *         root).
+     */
+    PreferenceScreen inflateFromResource(Context context, int resId,
+            PreferenceScreen rootPreferences) {
+        // Block commits
+        setNoCommit(true);
+
+        final PreferenceInflater inflater = new PreferenceInflater(context, this);
+        rootPreferences = (PreferenceScreen) inflater.inflate(resId, rootPreferences, true);
+        rootPreferences.onAttachedToHierarchy(this);
+
+        // Unblock commits
+        setNoCommit(false);
+
+        return rootPreferences;
+    }
+    
+    public PreferenceScreen createPreferenceScreen(Context context) {
+        final PreferenceScreen preferenceScreen = new PreferenceScreen(context, null);
+        preferenceScreen.onAttachedToHierarchy(this);
+        return preferenceScreen;
+    }
+    
+    /**
+     * Called by a preference to get a unique ID in its hierarchy.
+     * 
+     * @return A unique ID.
+     */
+    long getNextId() {
+        synchronized (this) {
+            return mNextId++;
+        }
+    }
+    
+    /**
+     * Returns the current name of the SharedPreferences file that preferences managed by
+     * this will use.
+     * 
+     * @return The name that can be passed to {@link Context#getSharedPreferences(String, int)}.
+     * @see Context#getSharedPreferences(String, int)
+     */
+    public String getSharedPreferencesName() {
+        return mSharedPreferencesName;
+    }
+
+    /**
+     * Sets the name of the SharedPreferences file that preferences managed by this
+     * will use.
+     * 
+     * @param sharedPreferencesName The name of the SharedPreferences file.
+     * @see Context#getSharedPreferences(String, int)
+     */
+    public void setSharedPreferencesName(String sharedPreferencesName) {
+        mSharedPreferencesName = sharedPreferencesName;
+        mSharedPreferences = null;
+    }
+
+    /**
+     * Returns the current mode of the SharedPreferences file that preferences managed by
+     * this will use.
+     * 
+     * @return The mode that can be passed to {@link Context#getSharedPreferences(String, int)}.
+     * @see Context#getSharedPreferences(String, int)
+     */
+    public int getSharedPreferencesMode() {
+        return mSharedPreferencesMode;
+    }
+
+    /**
+     * Sets the mode of the SharedPreferences file that preferences managed by this
+     * will use.
+     * 
+     * @param sharedPreferencesMode The mode of the SharedPreferences file.
+     * @see Context#getSharedPreferences(String, int)
+     */
+    public void setSharedPreferencesMode(int sharedPreferencesMode) {
+        mSharedPreferencesMode = sharedPreferencesMode;
+        mSharedPreferences = null;
+    }
+
+    /**
+     * Gets a SharedPreferences instance that preferences managed by this will
+     * use.
+     * 
+     * @return A SharedPreferences instance pointing to the file that contains
+     *         the values of preferences that are managed by this.
+     */
+    public SharedPreferences getSharedPreferences() {
+        if (mSharedPreferences == null) {
+            mSharedPreferences = mContext.getSharedPreferences(mSharedPreferencesName,
+                    mSharedPreferencesMode);
+        }
+        
+        return mSharedPreferences;
+    }
+    
+    /**
+     * Gets a SharedPreferences instance that points to the default file that is
+     * used by the preference framework in the given context.
+     * 
+     * @param context The context of the preferences whose values are wanted.
+     * @return A SharedPreferences instance that can be used to retrieve and
+     *         listen to values of the preferences.
+     */
+    public static SharedPreferences getDefaultSharedPreferences(Context context) {
+        return context.getSharedPreferences(getDefaultSharedPreferencesName(context),
+                getDefaultSharedPreferencesMode());
+    }
+
+    private static String getDefaultSharedPreferencesName(Context context) {
+        return context.getPackageName() + "_preferences";
+    }
+
+    private static int getDefaultSharedPreferencesMode() {
+        return Context.MODE_PRIVATE;
+    }
+
+    /**
+     * Returns the root of the preference hierarchy managed by this class.
+     *  
+     * @return The {@link PreferenceScreen} object that is at the root of the hierarchy.
+     */
+    PreferenceScreen getPreferenceScreen() {
+        return mPreferenceScreen;
+    }
+    
+    /**
+     * Sets the root of the preference hierarchy.
+     * 
+     * @param preferenceScreen The root {@link PreferenceScreen} of the preference hierarchy.
+     * @return Whether the {@link PreferenceScreen} given is different than the previous. 
+     */
+    boolean setPreferences(PreferenceScreen preferenceScreen) {
+        if (preferenceScreen != mPreferenceScreen) {
+            mPreferenceScreen = preferenceScreen;
+            return true;
+        }
+        
+        return false;
+    }
+    
+    /**
+     * Finds a {@link Preference} based on its key.
+     * 
+     * @param key The key of the preference to retrieve.
+     * @return The {@link Preference} with the key, or null.
+     * @see PreferenceGroup#findPreference(CharSequence)
+     */
+    public Preference findPreference(CharSequence key) {
+        if (mPreferenceScreen == null) {
+            return null;
+        }
+        
+        return mPreferenceScreen.findPreference(key);
+    }
+    
+    /**
+     * Sets the default values from a preference hierarchy in XML. This should
+     * be called by the application's main activity.
+     * <p>
+     * If {@code readAgain} is false, this will only set the default values if this
+     * method has never been called in the past (or the
+     * {@link #KEY_HAS_SET_DEFAULT_VALUES} in the default value shared
+     * preferences file is false). To attempt to set the default values again
+     * bypassing this check, set {@code readAgain} to true.
+     * 
+     * @param context The context of the shared preferences.
+     * @param resId The resource ID of the preference hierarchy XML file.
+     * @param readAgain Whether to re-read the default values.
+     *            <p>
+     *            Note: this will NOT reset preferences back to their default
+     *            values. For that functionality, use
+     *            {@link PreferenceManager#getDefaultSharedPreferences(Context)}
+     *            and clear it followed by a call to this method with this
+     *            parameter set to true.
+     */
+    public static void setDefaultValues(Context context, int resId, boolean readAgain) {
+        
+        // Use the default shared preferences name and mode
+        setDefaultValues(context, getDefaultSharedPreferencesName(context),
+                getDefaultSharedPreferencesMode(), resId, readAgain);
+    }
+    
+    /**
+     * Similar to {@link #setDefaultValues(Context, int, boolean)} but allows
+     * the client to provide the filename and mode of the shared preferences
+     * file.
+     * 
+     * @see #setDefaultValues(Context, int, boolean)
+     * @see #setSharedPreferencesName(String)
+     * @see #setSharedPreferencesMode(int)
+     */
+    public static void setDefaultValues(Context context, String sharedPreferencesName,
+            int sharedPreferencesMode, int resId, boolean readAgain) {
+        final SharedPreferences defaultValueSp = context.getSharedPreferences(
+                KEY_HAS_SET_DEFAULT_VALUES, Context.MODE_PRIVATE);
+        
+        if (readAgain || !defaultValueSp.getBoolean(KEY_HAS_SET_DEFAULT_VALUES, false)) {
+            final PreferenceManager pm = new PreferenceManager(context);
+            pm.setSharedPreferencesName(sharedPreferencesName);
+            pm.setSharedPreferencesMode(sharedPreferencesMode);
+            pm.inflateFromResource(context, resId, null);
+
+            defaultValueSp.edit().putBoolean(KEY_HAS_SET_DEFAULT_VALUES, true).commit();
+        }
+    }
+    
+    /**
+     * Returns an editor to use when modifying the shared preferences.
+     * <p>
+     * Do NOT commit unless {@link #shouldCommit()} returns true.
+     * 
+     * @return An editor to use to write to shared preferences.
+     * @see #shouldCommit()
+     */
+    SharedPreferences.Editor getEditor() {
+        
+        if (mNoCommit) {
+            if (mEditor == null) {
+                mEditor = getSharedPreferences().edit();
+            }
+            
+            return mEditor;
+        } else {
+            return getSharedPreferences().edit();
+        }
+    }
+    
+    /**
+     * Whether it is the client's responsibility to commit on the
+     * {@link #getEditor()}. This will return false in cases where the writes
+     * should be batched, for example when inflating preferences from XML.
+     * 
+     * @return Whether the client should commit.
+     */
+    boolean shouldCommit() {
+        return !mNoCommit;
+    }
+    
+    private void setNoCommit(boolean noCommit) {
+        if (!noCommit && mEditor != null) {
+            mEditor.commit();
+        }
+        
+        mNoCommit = noCommit;
+    }
+    
+    /**
+     * Returns the activity that shows the preferences. This is useful for doing
+     * managed queries, but in most cases the use of {@link #getContext()} is
+     * preferred.
+     * <p>
+     * This will return null if this class was instantiated with a Context
+     * instead of Activity. For example, when setting the default values.
+     * 
+     * @return The activity that shows the preferences.
+     * @see #mContext
+     */
+    Activity getActivity() {
+        return mActivity;
+    }
+    
+    /**
+     * Returns the context. This is preferred over {@link #getActivity()} when
+     * possible.
+     * 
+     * @return The context.
+     */
+    Context getContext() {
+        return mContext;
+    }
+
+    /**
+     * Registers a listener.
+     * 
+     * @see OnActivityResultListener
+     */
+    void registerOnActivityResultListener(OnActivityResultListener listener) {
+        synchronized (this) {
+            if (mActivityResultListeners == null) {
+                mActivityResultListeners = new ArrayList<OnActivityResultListener>();
+            }
+            
+            if (!mActivityResultListeners.contains(listener)) {
+                mActivityResultListeners.add(listener);
+            }
+        }
+    }
+
+    /**
+     * Unregisters a listener.
+     * 
+     * @see OnActivityResultListener
+     */
+    void unregisterOnActivityResultListener(OnActivityResultListener listener) {
+        synchronized (this) {
+            if (mActivityResultListeners != null) {
+                mActivityResultListeners.remove(listener);
+            }
+        }
+    }
+
+    /**
+     * Called by the {@link PreferenceManager} to dispatch a subactivity result.
+     */
+    void dispatchActivityResult(int requestCode, int resultCode, Intent data) {
+        List<OnActivityResultListener> list;
+        
+        synchronized (this) {
+            if (mActivityResultListeners == null) return;
+            list = new ArrayList<OnActivityResultListener>(mActivityResultListeners);
+        }
+
+        final int N = list.size();
+        for (int i = 0; i < N; i++) {
+            if (list.get(i).onActivityResult(requestCode, resultCode, data)) {
+                break;
+            }
+        }
+    }
+
+    /**
+     * Registers a listener.
+     * 
+     * @see OnActivityStopListener
+     */
+    void registerOnActivityStopListener(OnActivityStopListener listener) {
+        synchronized (this) {
+            if (mActivityStopListeners == null) {
+                mActivityStopListeners = new ArrayList<OnActivityStopListener>();
+            }
+            
+            if (!mActivityStopListeners.contains(listener)) {
+                mActivityStopListeners.add(listener);
+            }
+        }
+    }
+    
+    /**
+     * Unregisters a listener.
+     * 
+     * @see OnActivityStopListener
+     */
+    void unregisterOnActivityStopListener(OnActivityStopListener listener) {
+        synchronized (this) {
+            if (mActivityStopListeners != null) {
+                mActivityStopListeners.remove(listener);
+            }
+        }
+    }
+    
+    /**
+     * Called by the {@link PreferenceManager} to dispatch the activity stop
+     * event.
+     */
+    void dispatchActivityStop() {
+        List<OnActivityStopListener> list;
+        
+        synchronized (this) {
+            if (mActivityStopListeners == null) return;
+            list = new ArrayList<OnActivityStopListener>(mActivityStopListeners);
+        }
+
+        final int N = list.size();
+        for (int i = 0; i < N; i++) {
+            list.get(i).onActivityStop();
+        }
+    }
+
+    /**
+     * Registers a listener.
+     * 
+     * @see OnActivityDestroyListener
+     */
+    void registerOnActivityDestroyListener(OnActivityDestroyListener listener) {
+        synchronized (this) {
+            if (mActivityDestroyListeners == null) {
+                mActivityDestroyListeners = new ArrayList<OnActivityDestroyListener>();
+            }
+
+            if (!mActivityDestroyListeners.contains(listener)) {
+                mActivityDestroyListeners.add(listener);
+            }
+        }
+    }
+    
+    /**
+     * Unregisters a listener.
+     * 
+     * @see OnActivityDestroyListener
+     */
+    void unregisterOnActivityDestroyListener(OnActivityDestroyListener listener) {
+        synchronized (this) {
+            if (mActivityDestroyListeners != null) {
+                mActivityDestroyListeners.remove(listener);
+            }
+        }
+    }
+    
+    /**
+     * Called by the {@link PreferenceManager} to dispatch the activity destroy
+     * event.
+     */
+    void dispatchActivityDestroy() {
+        List<OnActivityDestroyListener> list = null;
+        
+        synchronized (this) {
+            if (mActivityDestroyListeners != null) {
+                list = new ArrayList<OnActivityDestroyListener>(mActivityDestroyListeners);
+            }
+        }
+
+        if (list != null) {
+            final int N = list.size();
+            for (int i = 0; i < N; i++) {
+                list.get(i).onActivityDestroy();
+            }
+        }
+
+        // Dismiss any PreferenceScreens still showing
+        dismissAllScreens();
+    }
+    
+    /**
+     * Returns a request code that is unique for the activity. Each subsequent
+     * call to this method should return another unique request code.
+     * 
+     * @return A unique request code that will never be used by anyone other
+     *         than the caller of this method.
+     */
+    int getNextRequestCode() {
+        synchronized (this) {
+            return mNextRequestCode++;
+        }
+    }
+    
+    void addPreferencesScreen(DialogInterface screen) {
+        synchronized (this) {
+            
+            if (mPreferencesScreens == null) {
+                mPreferencesScreens = new ArrayList<DialogInterface>();
+            }
+            
+            mPreferencesScreens.add(screen);
+        }
+    }
+    
+    void removePreferencesScreen(DialogInterface screen) {
+        synchronized (this) {
+            
+            if (mPreferencesScreens == null) {
+                return;
+            }
+            
+            mPreferencesScreens.remove(screen);
+        }
+    }
+    
+    /**
+     * Called by {@link PreferenceActivity} to dispatch the new Intent event.
+     * 
+     * @param intent The new Intent.
+     */
+    void dispatchNewIntent(Intent intent) {
+        dismissAllScreens();
+    }
+
+    private void dismissAllScreens() {
+        // Remove any of the previously shown preferences screens
+        ArrayList<DialogInterface> screensToDismiss;
+
+        synchronized (this) {
+            
+            if (mPreferencesScreens == null) {
+                return;
+            }
+            
+            screensToDismiss = new ArrayList<DialogInterface>(mPreferencesScreens);
+            mPreferencesScreens.clear();
+        }
+        
+        for (int i = screensToDismiss.size() - 1; i >= 0; i--) {
+            screensToDismiss.get(i).dismiss();
+        }
+    }
+    
+    /**
+     * Sets the callback to be invoked when a {@link Preference} in the
+     * hierarchy rooted at this {@link PreferenceManager} is clicked.
+     * 
+     * @param listener The callback to be invoked.
+     */
+    void setOnPreferenceTreeClickListener(OnPreferenceTreeClickListener listener) {
+        mOnPreferenceTreeClickListener = listener;
+    }
+
+    OnPreferenceTreeClickListener getOnPreferenceTreeClickListener() {
+        return mOnPreferenceTreeClickListener;
+    }
+    
+    /**
+     * Interface definition for a callback to be invoked when a
+     * {@link Preference} in the hierarchy rooted at this {@link PreferenceScreen} is
+     * clicked.
+     */
+    interface OnPreferenceTreeClickListener {
+        /**
+         * Called when a preference in the tree rooted at this
+         * {@link PreferenceScreen} has been clicked.
+         * 
+         * @param preferenceScreen The {@link PreferenceScreen} that the
+         *        preference is located in.
+         * @param preference The preference that was clicked.
+         * @return Whether the click was handled.
+         */
+        boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference);
+    }
+
+    /**
+     * Interface definition for a class that will be called when the container's activity
+     * receives an activity result.
+     */
+    public interface OnActivityResultListener {
+        
+        /**
+         * See Activity's onActivityResult.
+         * 
+         * @return Whether the request code was handled (in which case
+         *         subsequent listeners will not be called.
+         */
+        boolean onActivityResult(int requestCode, int resultCode, Intent data);
+    }
+    
+    /**
+     * Interface definition for a class that will be called when the container's activity
+     * is stopped.
+     */
+    public interface OnActivityStopListener {
+        
+        /**
+         * See Activity's onStop.
+         */
+        void onActivityStop();
+    }
+
+    /**
+     * Interface definition for a class that will be called when the container's activity
+     * is destroyed.
+     */
+    public interface OnActivityDestroyListener {
+        
+        /**
+         * See Activity's onDestroy.
+         */
+        void onActivityDestroy();
+    }
+
+}
diff --git a/core/java/android/preference/PreferenceScreen.java b/core/java/android/preference/PreferenceScreen.java
new file mode 100644
index 0000000..5353b53
--- /dev/null
+++ b/core/java/android/preference/PreferenceScreen.java
@@ -0,0 +1,252 @@
+/*
+ * Copyright (C) 2007 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 android.preference;
+
+import android.app.Dialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.Adapter;
+import android.widget.AdapterView;
+import android.widget.ListAdapter;
+import android.widget.ListView;
+
+/**
+ * Represents a top-level {@link Preference} that
+ * is the root of a Preference hierarchy. A {@link PreferenceActivity}
+ * points to an instance of this class to show the preferences. To instantiate
+ * this class, use {@link PreferenceManager#createPreferenceScreen(Context)}.
+ * <ul>
+ * This class can appear in two places:
+ * <li> When a {@link PreferenceActivity} points to this, it is used as the root
+ * and is not shown (only the contained preferences are shown).
+ * <li> When it appears inside another preference hierarchy, it is shown and
+ * serves as the gateway to another screen of preferences (either by showing
+ * another screen of preferences as a {@link Dialog} or via a
+ * {@link Context#startActivity(android.content.Intent)} from the
+ * {@link Preference#getIntent()}). The children of this {@link PreferenceScreen}
+ * are NOT shown in the screen that this {@link PreferenceScreen} is shown in.
+ * Instead, a separate screen will be shown when this preference is clicked.
+ * </ul>
+ * <p>Here's an example XML layout of a PreferenceScreen:</p>
+ * <pre>
+&lt;PreferenceScreen
+        xmlns:android="http://schemas.android.com/apk/res/android"
+        android:key="first_preferencescreen"&gt;
+    &lt;CheckBoxPreference
+            android:key="wifi enabled"
+            android:title="WiFi" /&gt;
+    &lt;PreferenceScreen
+            android:key="second_preferencescreen"
+            android:title="WiFi settings"&gt;
+        &lt;CheckBoxPreference
+                android:key="prefer wifi"
+                android:title="Prefer WiFi" /&gt;
+        ... other preferences here ...
+    &lt;/PreferenceScreen&gt;
+&lt;/PreferenceScreen&gt; </pre>
+ * <p>
+ * In this example, the "first_preferencescreen" will be used as the root of the
+ * hierarchy and given to a {@link PreferenceActivity}. The first screen will
+ * show preferences "WiFi" (which can be used to quickly enable/disable WiFi)
+ * and "WiFi settings". The "WiFi settings" is the "second_preferencescreen" and when
+ * clicked will show another screen of preferences such as "Prefer WiFi" (and
+ * the other preferences that are children of the "second_preferencescreen" tag).
+ * 
+ * @see PreferenceCategory
+ */
+public final class PreferenceScreen extends PreferenceGroup implements AdapterView.OnItemClickListener,
+        DialogInterface.OnDismissListener {
+
+    private ListAdapter mRootAdapter;
+    
+    private Dialog mDialog;
+    
+    /**
+     * Do NOT use this constructor, use {@link PreferenceManager#createPreferenceScreen(Context)}.
+     * @hide-
+     */
+    public PreferenceScreen(Context context, AttributeSet attrs) {
+        super(context, attrs, com.android.internal.R.attr.preferenceScreenStyle);
+    }
+
+    /**
+     * Returns an adapter that can be attached to a {@link PreferenceActivity}
+     * to show the preferences contained in this {@link PreferenceScreen}.
+     * <p>
+     * This {@link PreferenceScreen} will NOT appear in the returned adapter, instead
+     * it appears in the hierarchy above this {@link PreferenceScreen}.
+     * <p>
+     * This adapter's {@link Adapter#getItem(int)} should always return a
+     * subclass of {@link Preference}.
+     * 
+     * @return An adapter that provides the {@link Preference} contained in this
+     *         {@link PreferenceScreen}.
+     */
+    public ListAdapter getRootAdapter() {
+        if (mRootAdapter == null) {
+            mRootAdapter = onCreateRootAdapter();
+        }
+        
+        return mRootAdapter;
+    }
+    
+    /**
+     * Creates the root adapter.
+     * 
+     * @return An adapter that contains the preferences contained in this {@link PreferenceScreen}.
+     * @see #getRootAdapter()
+     */
+    protected ListAdapter onCreateRootAdapter() {
+        return new PreferenceGroupAdapter(this);
+    }
+
+    /**
+     * Binds a {@link ListView} to the preferences contained in this {@link PreferenceScreen} via
+     * {@link #getRootAdapter()}. It also handles passing list item clicks to the corresponding
+     * {@link Preference} contained by this {@link PreferenceScreen}.
+     * 
+     * @param listView The list view to attach to.
+     */
+    public void bind(ListView listView) {
+        listView.setOnItemClickListener(this);
+        listView.setAdapter(getRootAdapter());
+        
+        onAttachedToActivity();
+    }
+    
+    @Override
+    protected void onClick() {
+        if (getIntent() != null || getPreferenceCount() == 0) {
+            return;
+        }
+        
+        showDialog(null);
+    }
+    
+    private void showDialog(Bundle state) {
+        Context context = getContext();
+        ListView listView = new ListView(context);
+        bind(listView);
+
+        Dialog dialog = mDialog = new Dialog(context, com.android.internal.R.style.Theme_NoTitleBar);
+        dialog.setContentView(listView);
+        dialog.setOnDismissListener(this);
+        if (state != null) {
+            dialog.onRestoreInstanceState(state);
+        }
+        
+        // Add the screen to the list of preferences screens opened as dialogs
+        getPreferenceManager().addPreferencesScreen(dialog);
+        
+        dialog.show();
+    }
+    
+    public void onDismiss(DialogInterface dialog) {
+        mDialog = null;
+        getPreferenceManager().removePreferencesScreen(dialog);
+    }
+    
+    /**
+     * Used to get a handle to the dialog. 
+     * This is useful for cases where we want to manipulate the dialog
+     * as we would with any other activity or view.
+     */
+    public Dialog getDialog() {
+        return mDialog;
+    }
+
+    public void onItemClick(AdapterView parent, View view, int position, long id) {
+        Object item = getRootAdapter().getItem(position);
+        if (!(item instanceof Preference)) return;
+        
+        final Preference preference = (Preference) item; 
+        preference.performClick(this);
+    }
+
+    @Override
+    protected boolean isOnSameScreenAsChildren() {
+        return false;
+    }
+    
+    @Override
+    protected Parcelable onSaveInstanceState() {
+        final Parcelable superState = super.onSaveInstanceState();
+        final Dialog dialog = mDialog;
+        if (dialog == null || !dialog.isShowing()) {
+            return superState;
+        }
+        
+        final SavedState myState = new SavedState(superState);
+        myState.isDialogShowing = true;
+        myState.dialogBundle = dialog.onSaveInstanceState();
+        return myState;
+    }
+
+    @Override
+    protected void onRestoreInstanceState(Parcelable state) {
+        if (state == null || !state.getClass().equals(SavedState.class)) {
+            // Didn't save state for us in onSaveInstanceState
+            super.onRestoreInstanceState(state);
+            return;
+        }
+         
+        SavedState myState = (SavedState) state;
+        super.onRestoreInstanceState(myState.getSuperState());
+        if (myState.isDialogShowing) {
+            showDialog(myState.dialogBundle);
+        }
+    }
+    
+    private static class SavedState extends BaseSavedState {
+        boolean isDialogShowing;
+        Bundle dialogBundle;
+        
+        public SavedState(Parcel source) {
+            super(source);
+            isDialogShowing = source.readInt() == 1;
+            dialogBundle = source.readBundle();
+        }
+
+        @Override
+        public void writeToParcel(Parcel dest, int flags) {
+            super.writeToParcel(dest, flags);
+            dest.writeInt(isDialogShowing ? 1 : 0);
+            dest.writeBundle(dialogBundle);
+        }
+
+        public SavedState(Parcelable superState) {
+            super(superState);
+        }
+
+        public static final Parcelable.Creator<SavedState> CREATOR =
+                new Parcelable.Creator<SavedState>() {
+            public SavedState createFromParcel(Parcel in) {
+                return new SavedState(in);
+            }
+
+            public SavedState[] newArray(int size) {
+                return new SavedState[size];
+            }
+        };
+    }
+    
+}
diff --git a/core/java/android/preference/RingtonePreference.java b/core/java/android/preference/RingtonePreference.java
new file mode 100644
index 0000000..6beb06d
--- /dev/null
+++ b/core/java/android/preference/RingtonePreference.java
@@ -0,0 +1,242 @@
+/*
+ * Copyright (C) 2007 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 android.preference;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.res.TypedArray;
+import android.media.RingtoneManager;
+import android.net.Uri;
+import android.provider.Settings.System;
+import android.text.TextUtils;
+import android.util.AttributeSet;
+import android.util.Log;
+
+/**
+ * A {@link Preference} that allows the user to choose a ringtone from those on the device. 
+ * The chosen ringtone's URI will be persisted as a string.
+ * <p>
+ * If the user chooses the "Default" item, the saved string will be one of
+ * {@link System#DEFAULT_RINGTONE_URI} or
+ * {@link System#DEFAULT_NOTIFICATION_URI}. If the user chooses the "Silent"
+ * item, the saved string will be an empty string.
+ * 
+ * @attr ref android.R.styleable#RingtonePreference_ringtoneType
+ * @attr ref android.R.styleable#RingtonePreference_showDefault
+ * @attr ref android.R.styleable#RingtonePreference_showSilent
+ */
+public class RingtonePreference extends Preference implements
+        PreferenceManager.OnActivityResultListener {
+
+    private static final String TAG = "RingtonePreference";
+
+    private int mRingtoneType;
+    private boolean mShowDefault;
+    private boolean mShowSilent;
+    
+    private int mRequestCode;
+
+    public RingtonePreference(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+        
+        TypedArray a = context.obtainStyledAttributes(attrs,
+                com.android.internal.R.styleable.RingtonePreference, defStyle, 0);
+        mRingtoneType = a.getInt(com.android.internal.R.styleable.RingtonePreference_ringtoneType,
+                RingtoneManager.TYPE_RINGTONE);
+        mShowDefault = a.getBoolean(com.android.internal.R.styleable.RingtonePreference_showDefault,
+                true);
+        mShowSilent = a.getBoolean(com.android.internal.R.styleable.RingtonePreference_showSilent,
+                true);
+        a.recycle();
+    }
+
+    public RingtonePreference(Context context, AttributeSet attrs) {
+        this(context, attrs, com.android.internal.R.attr.ringtonePreferenceStyle);
+    }
+    
+    public RingtonePreference(Context context) {
+        this(context, null);
+    }
+
+    /**
+     * Returns the sound type(s) that are shown in the picker.
+     * 
+     * @return The sound type(s) that are shown in the picker.
+     * @see #setRingtoneType(int)
+     */
+    public int getRingtoneType() {
+        return mRingtoneType;
+    }
+
+    /**
+     * Sets the sound type(s) that are shown in the picker.
+     * 
+     * @param type The sound type(s) that are shown in the picker.
+     * @see RingtoneManager#EXTRA_RINGTONE_TYPE
+     */
+    public void setRingtoneType(int type) {
+        mRingtoneType = type;
+    }
+
+    /**
+     * Returns whether to a show an item for the default sound/ringtone.
+     * 
+     * @return Whether to show an item for the default sound/ringtone.
+     */
+    public boolean getShowDefault() {
+        return mShowDefault;
+    }
+
+    /**
+     * Sets whether to show an item for the default sound/ringtone. The default
+     * to use will be deduced from the sound type(s) being shown.
+     * 
+     * @param showDefault Whether to show the default or not.
+     * @see RingtoneManager#EXTRA_RINGTONE_SHOW_DEFAULT
+     */
+    public void setShowDefault(boolean showDefault) {
+        mShowDefault = showDefault;
+    }
+
+    /**
+     * Returns whether to a show an item for 'Silent'.
+     * 
+     * @return Whether to show an item for 'Silent'.
+     */
+    public boolean getShowSilent() {
+        return mShowSilent;
+    }
+
+    /**
+     * Sets whether to show an item for 'Silent'.
+     * 
+     * @param showSilent Whether to show 'Silent'.
+     * @see RingtoneManager#EXTRA_RINGTONE_SHOW_SILENT
+     */
+    public void setShowSilent(boolean showSilent) {
+        mShowSilent = showSilent;
+    }
+
+    @Override
+    protected void onClick() {
+        // Launch the ringtone picker
+        Intent intent = new Intent(RingtoneManager.ACTION_RINGTONE_PICKER);
+        onPrepareRingtonePickerIntent(intent);
+        getPreferenceManager().getActivity().startActivityForResult(intent, mRequestCode);
+    }
+
+    /**
+     * Prepares the intent to launch the ringtone picker. This can be modified
+     * to adjust the parameters of the ringtone picker.
+     * 
+     * @param ringtonePickerIntent The ringtone picker intent that can be
+     *            modified by putting extras.
+     */
+    protected void onPrepareRingtonePickerIntent(Intent ringtonePickerIntent) {
+
+        ringtonePickerIntent.putExtra(RingtoneManager.EXTRA_RINGTONE_EXISTING_URI,
+                onRestoreRingtone());
+        
+        ringtonePickerIntent.putExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_DEFAULT, mShowDefault);
+        if (mShowDefault) {
+            ringtonePickerIntent.putExtra(RingtoneManager.EXTRA_RINGTONE_DEFAULT_URI,
+                    RingtoneManager.getDefaultUri(getRingtoneType()));
+        }
+
+        ringtonePickerIntent.putExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_SILENT, mShowSilent);
+        ringtonePickerIntent.putExtra(RingtoneManager.EXTRA_RINGTONE_TYPE, mRingtoneType);
+    }
+    
+    /**
+     * Called when a ringtone is chosen.
+     * <p>
+     * By default, this saves the ringtone URI to the persistent storage as a
+     * string.
+     * 
+     * @param ringtoneUri The chosen ringtone's {@link Uri}. Can be null.
+     */
+    protected void onSaveRingtone(Uri ringtoneUri) {
+        persistString(ringtoneUri != null ? ringtoneUri.toString() : "");
+    }
+
+    /**
+     * Called when the chooser is about to be shown and the current ringtone
+     * should be marked. Can return null to not mark any ringtone.
+     * <p>
+     * By default, this restores the previous ringtone URI from the persistent
+     * storage.
+     * 
+     * @return The ringtone to be marked as the current ringtone.
+     */
+    protected Uri onRestoreRingtone() {
+        final String uriString = getPersistedString(null);
+        return !TextUtils.isEmpty(uriString) ? Uri.parse(uriString) : null;
+    }
+    
+    @Override
+    protected Object onGetDefaultValue(TypedArray a, int index) {
+        return a.getString(index);
+    }
+
+    @Override
+    protected void onSetInitialValue(boolean restorePersistedValue, Object defaultValueObj) {
+        String defaultValue = (String) defaultValueObj;
+        
+        /*
+         * This method is normally to make sure the internal state and UI
+         * matches either the persisted value or the default value. Since we
+         * don't show the current value in the UI (until the dialog is opened)
+         * and we don't keep local state, if we are restoring the persisted
+         * value we don't need to do anything.
+         */
+        if (restorePersistedValue) {
+            return;
+        }
+        
+        // If we are setting to the default value, we should persist it.
+        if (!TextUtils.isEmpty(defaultValue)) {
+            onSaveRingtone(Uri.parse(defaultValue));
+        }
+    }
+
+    @Override
+    protected void onAttachedToHierarchy(PreferenceManager preferenceManager) {
+        super.onAttachedToHierarchy(preferenceManager);
+        
+        preferenceManager.registerOnActivityResultListener(this);
+        mRequestCode = preferenceManager.getNextRequestCode();
+    }
+
+    public boolean onActivityResult(int requestCode, int resultCode, Intent data) {
+        
+        if (requestCode == mRequestCode) {
+            
+            if (data != null) {
+                Uri uri = data.getParcelableExtra(RingtoneManager.EXTRA_RINGTONE_PICKED_URI);
+                
+                if (callChangeListener(uri != null ? uri.toString() : "")) {
+                    onSaveRingtone(uri);
+                }
+            }
+            
+            return true;
+        }
+        
+        return false;
+    }
+
+}
diff --git a/core/java/android/preference/SeekBarPreference.java b/core/java/android/preference/SeekBarPreference.java
new file mode 100644
index 0000000..658c2a7
--- /dev/null
+++ b/core/java/android/preference/SeekBarPreference.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2007 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 android.preference;
+
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.preference.DialogPreference;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.ImageView;
+import android.widget.SeekBar;
+
+/**
+ * @hide
+ */
+public class SeekBarPreference extends DialogPreference {
+    private static final String TAG = "SeekBarPreference";
+    
+    private Drawable mMyIcon;
+
+    public SeekBarPreference(Context context, AttributeSet attrs) {
+        super(context, attrs);
+
+        setDialogLayoutResource(com.android.internal.R.layout.seekbar_dialog);
+        setPositiveButtonText(android.R.string.ok);
+        setNegativeButtonText(android.R.string.cancel);
+        
+        // Steal the XML dialogIcon attribute's value
+        mMyIcon = getDialogIcon();
+        setDialogIcon(null);
+    }
+
+    @Override
+    protected void onBindDialogView(View view) {
+        super.onBindDialogView(view);
+        
+        final ImageView iconView = (ImageView) view.findViewById(android.R.id.icon);
+        if (mMyIcon != null) {
+            iconView.setImageDrawable(mMyIcon);
+        } else {
+            iconView.setVisibility(View.GONE);
+        }
+    }
+
+    protected static SeekBar getSeekBar(View dialogView) {
+        return (SeekBar) dialogView.findViewById(com.android.internal.R.id.seekbar);
+    }
+}
diff --git a/core/java/android/preference/VolumePreference.java b/core/java/android/preference/VolumePreference.java
new file mode 100644
index 0000000..6e215dc
--- /dev/null
+++ b/core/java/android/preference/VolumePreference.java
@@ -0,0 +1,219 @@
+/*
+ * Copyright (C) 2007 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 android.preference;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.database.ContentObserver;
+import android.media.Ringtone;
+import android.media.RingtoneManager;
+import android.media.AudioManager;
+import android.os.Handler;
+import android.preference.PreferenceManager;
+import android.provider.Settings;
+import android.provider.Settings.System;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.SeekBar;
+import android.widget.SeekBar.OnSeekBarChangeListener;
+
+/**
+ * @hide
+ */
+public class VolumePreference extends SeekBarPreference implements 
+        PreferenceManager.OnActivityStopListener {
+
+    private static final String TAG = "VolumePreference";
+    
+    private int mStreamType;
+    
+    /** May be null if the dialog isn't visible. */
+    private SeekBarVolumizer mSeekBarVolumizer;
+    
+    public VolumePreference(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        
+        TypedArray a = context.obtainStyledAttributes(attrs,
+                com.android.internal.R.styleable.VolumePreference, 0, 0);
+        mStreamType = a.getInt(android.R.styleable.VolumePreference_streamType, 0);
+        a.recycle();        
+    }
+    
+    public void setStreamType(int streamType) {
+        mStreamType = streamType;
+    }
+
+    @Override
+    protected void onBindDialogView(View view) {
+        super.onBindDialogView(view);
+    
+        final SeekBar seekBar = (SeekBar) view.findViewById(com.android.internal.R.id.seekbar);
+        mSeekBarVolumizer = new SeekBarVolumizer(getContext(), seekBar, mStreamType);
+        
+        getPreferenceManager().registerOnActivityStopListener(this);
+    }
+
+    @Override
+    protected void onDialogClosed(boolean positiveResult) {
+        super.onDialogClosed(positiveResult);
+        
+        if (!positiveResult && mSeekBarVolumizer != null) {
+            mSeekBarVolumizer.revertVolume();
+        }
+        
+        cleanup();
+    }
+
+    public void onActivityStop() {
+        cleanup();
+    }
+
+    /**
+     * Do clean up.  This can be called multiple times!
+     */
+    private void cleanup() {
+       getPreferenceManager().unregisterOnActivityStopListener(this);
+       
+       if (mSeekBarVolumizer != null) {
+           mSeekBarVolumizer.stop();
+           mSeekBarVolumizer = null;
+       }
+    }
+   
+    protected void onSampleStarting(SeekBarVolumizer volumizer) {
+        if (mSeekBarVolumizer != null && volumizer != mSeekBarVolumizer) {
+            mSeekBarVolumizer.stopSample();
+        }
+    }
+    
+    /**
+     * Turns a {@link SeekBar} into a volume control.
+     */
+    public class SeekBarVolumizer implements OnSeekBarChangeListener, Runnable {
+
+        private Context mContext;
+        private Handler mHandler = new Handler();
+    
+        private AudioManager mAudioManager;
+        private int mStreamType;
+        private int mOriginalStreamVolume; 
+        private Ringtone mRingtone;
+    
+        private int mLastProgress;
+        private SeekBar mSeekBar;
+        
+        private ContentObserver mVolumeObserver = new ContentObserver(mHandler) {
+            @Override
+            public void onChange(boolean selfChange) {
+                super.onChange(selfChange);
+    
+                if (mSeekBar != null) {
+                    mSeekBar.setProgress(System.getInt(mContext.getContentResolver(),
+                            System.VOLUME_SETTINGS[mStreamType], 0));
+                }
+            }
+        };
+    
+        public SeekBarVolumizer(Context context, SeekBar seekBar, int streamType) {
+            mContext = context;
+            mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
+            mStreamType = streamType;
+            mSeekBar = seekBar;
+            
+            initSeekBar(seekBar);
+        }
+
+        private void initSeekBar(SeekBar seekBar) {
+            seekBar.setMax(mAudioManager.getStreamMaxVolume(mStreamType));
+            mOriginalStreamVolume = mAudioManager.getStreamVolume(mStreamType);
+            seekBar.setProgress(mOriginalStreamVolume);
+            seekBar.setOnSeekBarChangeListener(this);
+            
+            mContext.getContentResolver().registerContentObserver(
+                    System.getUriFor(System.VOLUME_SETTINGS[mStreamType]),
+                    false, mVolumeObserver);
+    
+            mRingtone = RingtoneManager.getRingtone(mContext,
+                    mStreamType == AudioManager.STREAM_NOTIFICATION
+                            ? Settings.System.DEFAULT_NOTIFICATION_URI
+                            : Settings.System.DEFAULT_RINGTONE_URI);
+            mRingtone.setStreamType(mStreamType);
+        }
+        
+        public void stop() {
+            stopSample();
+            mContext.getContentResolver().unregisterContentObserver(mVolumeObserver);
+            mSeekBar.setOnSeekBarChangeListener(null);
+        }
+        
+        public void revertVolume() {
+            mAudioManager.setStreamVolume(mStreamType, mOriginalStreamVolume, 0);
+        }
+        
+        public void onProgressChanged(SeekBar seekBar, int progress,
+                boolean fromTouch) {
+            if (!fromTouch) {
+                return;
+            }
+    
+            postSetVolume(progress);
+        }
+
+        private void postSetVolume(int progress) {
+            // Do the volume changing separately to give responsive UI
+            mLastProgress = progress;
+            mHandler.removeCallbacks(this);
+            mHandler.post(this);
+        }
+    
+        public void onStartTrackingTouch(SeekBar seekBar) {
+        }
+
+        public void onStopTrackingTouch(SeekBar seekBar) {
+            if (mRingtone != null && !mRingtone.isPlaying()) {
+                sample();
+            }
+        }
+        
+        public void run() {
+            mAudioManager.setStreamVolume(mStreamType, mLastProgress, 0);
+        }
+        
+        private void sample() {
+    
+            // Only play a preview sample when controlling the ringer stream
+            if (mStreamType != AudioManager.STREAM_RING
+                    && mStreamType != AudioManager.STREAM_NOTIFICATION) {
+                return;
+            }
+    
+            onSampleStarting(this);
+            mRingtone.play();
+        }
+    
+        public void stopSample() {
+            if (mRingtone != null) {
+                mRingtone.stop();
+            }
+        }
+
+        public SeekBar getSeekBar() {
+            return mSeekBar;
+        }
+        
+    }
+}
diff --git a/core/java/android/preference/package.html b/core/java/android/preference/package.html
new file mode 100644
index 0000000..d24d5bb
--- /dev/null
+++ b/core/java/android/preference/package.html
@@ -0,0 +1,23 @@
+<HTML>
+<BODY>
+Provides classes that manage application preferences and implement the preferences UI.
+Using these ensures that all the preferences within each application are maintained 
+in the same manner and the user experience is consistent with that of the system and 
+other applications.
+<p>
+The preferences portion of an application 
+should be ran as a separate {@link android.app.Activity} that extends
+the {@link android.preference.PreferenceActivity} class. In the PreferenceActivity, a 
+{@link android.preference.PreferenceScreen} object should be the root element of the layout. 
+The PreferenceScreen contains {@link android.preference.Preference} elements such as a 
+{@link android.preference.CheckBoxPreference}, {@link android.preference.EditTextPreference}, 
+{@link android.preference.ListPreference}, {@link android.preference.PreferenceCategory},
+or {@link android.preference.RingtonePreference}. </p>
+<p>
+All settings made for a given {@link android.preference.Preference} will be automatically saved
+to the application's instance of {@link android.content.SharedPreferences}. Access to the 
+SharedPreferences is simple with {@link android.preference.Preference#getSharedPreferences()}.</p>
+<p>
+Note that saved preferences are accessible only to the application that created them.</p>
+</BODY>
+</HTML>
diff --git a/core/java/android/provider/BaseColumns.java b/core/java/android/provider/BaseColumns.java
new file mode 100644
index 0000000..f594c19
--- /dev/null
+++ b/core/java/android/provider/BaseColumns.java
@@ -0,0 +1,32 @@
+/*
+ * 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 android.provider;
+
+public interface BaseColumns
+{
+    /**
+     * The unique ID for a row.
+     * <P>Type: INTEGER (long)</P>
+     */
+    public static final String _ID = "_id";
+
+    /**
+     * The count of rows in a directory.
+     * <P>Type: INTEGER</P>
+     */
+    public static final String _COUNT = "_count";
+}
diff --git a/core/java/android/provider/Browser.java b/core/java/android/provider/Browser.java
new file mode 100644
index 0000000..76aa51d
--- /dev/null
+++ b/core/java/android/provider/Browser.java
@@ -0,0 +1,448 @@
+/*
+ * 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 android.provider;
+
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.Intent;
+import android.database.Cursor;
+import android.database.DatabaseUtils;
+import android.net.Uri;
+import android.util.Log;
+import android.webkit.WebIconDatabase;
+
+import java.util.Date;
+
+public class Browser {
+    private static final String LOGTAG = "browser";
+    public static final Uri BOOKMARKS_URI =
+        Uri.parse("content://browser/bookmarks");
+
+    /**
+     * The name of extra data when starting Browser with ACTION_VIEW or
+     * ACTION_SEARCH intent.
+     * <p>
+     * The value should be an integer between 0 and 1000. If not set or set to
+     * 0, the Browser will use default. If set to 100, the Browser will start
+     * with 100%.
+     */
+    public static final String INITIAL_ZOOM_LEVEL = "browser.initialZoomLevel";
+
+    /* if you change column order you must also change indices
+       below */
+    public static final String[] HISTORY_PROJECTION = new String[] {
+        BookmarkColumns._ID, BookmarkColumns.URL, BookmarkColumns.VISITS,
+        BookmarkColumns.DATE, BookmarkColumns.BOOKMARK, BookmarkColumns.TITLE,
+        BookmarkColumns.FAVICON };
+
+    /* these indices dependent on HISTORY_PROJECTION */
+    public static final int HISTORY_PROJECTION_ID_INDEX = 0;
+    public static final int HISTORY_PROJECTION_URL_INDEX = 1;
+    public static final int HISTORY_PROJECTION_VISITS_INDEX = 2;
+    public static final int HISTORY_PROJECTION_DATE_INDEX = 3;
+    public static final int HISTORY_PROJECTION_BOOKMARK_INDEX = 4;
+    public static final int HISTORY_PROJECTION_TITLE_INDEX = 5;
+    public static final int HISTORY_PROJECTION_FAVICON_INDEX = 6;
+
+    /* columns needed to determine whether to truncate history */
+    public static final String[] TRUNCATE_HISTORY_PROJECTION = new String[] {
+        BookmarkColumns._ID, BookmarkColumns.DATE, };
+    public static final int TRUNCATE_HISTORY_PROJECTION_ID_INDEX = 0;
+
+    /* truncate this many history items at a time */
+    public static final int TRUNCATE_N_OLDEST = 5;
+
+    public static final Uri SEARCHES_URI =
+        Uri.parse("content://browser/searches");
+
+    /* if you change column order you must also change indices
+       below */
+    public static final String[] SEARCHES_PROJECTION = new String[] {
+        SearchColumns._ID, SearchColumns.SEARCH, SearchColumns.DATE };
+
+    /* these indices dependent on SEARCHES_PROJECTION */
+    public static final int SEARCHES_PROJECTION_SEARCH_INDEX = 1;
+    public static final int SEARCHES_PROJECTION_DATE_INDEX = 2;
+
+    private static final String SEARCHES_WHERE_CLAUSE = "search = ?";
+
+    /* Set a cap on the count of history items in the history/bookmark
+       table, to prevent db and layout operations from dragging to a
+       crawl.  Revisit this cap when/if db/layout performance
+       improvements are made.  Note: this does not affect bookmark
+       entries -- if the user wants more bookmarks than the cap, they
+       get them. */
+    private static final int MAX_HISTORY_COUNT = 250;
+
+    /**
+     *  Open the AddBookmark activity to save a bookmark.  Launch with
+     *  and/or url, which can be edited by the user before saving.
+     *  @param c        Context used to launch the AddBookmark activity.
+     *  @param title    Title for the bookmark. Can be null or empty string.
+     *  @param url      Url for the bookmark. Can be null or empty string.
+     */
+    public static final void saveBookmark(Context c, 
+                                          String title, 
+                                          String url) {
+        Intent i = new Intent(Intent.ACTION_INSERT, Browser.BOOKMARKS_URI);
+        i.putExtra("title", title);
+        i.putExtra("url", url);
+        c.startActivity(i);
+    }
+
+    public static final void sendString(Context c, String s) {
+        Intent send = new Intent(Intent.ACTION_SEND);
+        send.setType("text/plain");
+        send.putExtra(Intent.EXTRA_TEXT, s);
+        
+        try {
+            c.startActivity(Intent.createChooser(send,
+                    c.getText(com.android.internal.R.string.sendText)));
+        } catch(android.content.ActivityNotFoundException ex) {
+            // if no app handles it, do nothing
+        }
+    }
+
+    /**
+     *  Return a cursor pointing to a list of all the bookmarks.
+     *  @param cr   The ContentResolver used to access the database.
+     */
+    public static final Cursor getAllBookmarks(ContentResolver cr) throws 
+            IllegalStateException {
+        return cr.query(BOOKMARKS_URI,
+                new String[] { BookmarkColumns.URL }, 
+                "bookmark = 1", null, null);
+    }
+
+    /**
+     *  Return a cursor pointing to a list of all visited site urls.
+     *  @param cr   The ContentResolver used to access the database.
+     */
+    public static final Cursor getAllVisitedUrls(ContentResolver cr) throws
+            IllegalStateException {
+        return cr.query(BOOKMARKS_URI,
+                new String[] { BookmarkColumns.URL }, null, null, null);
+    }
+
+    /**
+     *  Update the visited history to acknowledge that a site has been
+     *  visited.
+     *  @param cr   The ContentResolver used to access the database.
+     *  @param url  The site being visited.
+     *  @param real Whether this is an actual visit, and should be added to the
+     *              number of visits.
+     */
+    public static final void updateVisitedHistory(ContentResolver cr,
+                                                  String url, boolean real) {
+        long now = new Date().getTime();
+        try {
+            StringBuilder sb = new StringBuilder(BookmarkColumns.URL + " = ");
+            DatabaseUtils.appendEscapedSQLString(sb, url);
+            Cursor c = cr.query(
+                    BOOKMARKS_URI,
+                    HISTORY_PROJECTION,
+                    sb.toString(),
+                    null,
+                    null);
+            /* We should only get one answer that is exactly the same. */
+            if (c.moveToFirst()) {
+                ContentValues map = new ContentValues();
+                if (real) {
+                    map.put(BookmarkColumns.VISITS, c
+                            .getInt(HISTORY_PROJECTION_VISITS_INDEX) + 1);
+                }
+                map.put(BookmarkColumns.DATE, now);
+                cr.update(BOOKMARKS_URI, map, "_id = " + c.getInt(0), null);
+            } else {
+                truncateHistory(cr);
+                ContentValues map = new ContentValues();
+                map.put(BookmarkColumns.URL, url);
+                map.put(BookmarkColumns.VISITS, real ? 1 : 0);
+                map.put(BookmarkColumns.DATE, now);
+                map.put(BookmarkColumns.BOOKMARK, 0);
+                map.put(BookmarkColumns.TITLE, url);
+                map.put(BookmarkColumns.CREATED, 0);
+                cr.insert(BOOKMARKS_URI, map);
+            }
+            c.deactivate();
+        } catch (IllegalStateException e) {
+            return;
+        }
+    }
+
+    /**
+     * If there are more than MAX_HISTORY_COUNT non-bookmark history
+     * items in the bookmark/history table, delete TRUNCATE_N_OLDEST
+     * of them.  This is used to keep our history table to a
+     * reasonable size.  Note: it does not prune bookmarks.  If the
+     * user wants 1000 bookmarks, the user gets 1000 bookmarks.
+     *
+     * @param cr The ContentResolver used to access the database.
+     */
+    public static final void truncateHistory(ContentResolver cr) {
+        try {
+            // Select non-bookmark history, ordered by date
+            Cursor c = cr.query(
+                    BOOKMARKS_URI,
+                    TRUNCATE_HISTORY_PROJECTION,
+                    "bookmark = 0",
+                    null,
+                    BookmarkColumns.DATE);
+            // Log.v(LOGTAG, "history count " + c.count());
+            if (c.moveToFirst() && c.getCount() >= MAX_HISTORY_COUNT) {
+                /* eliminate oldest history items */
+                for (int i = 0; i < TRUNCATE_N_OLDEST; i++) {
+                    // Log.v(LOGTAG, "truncate history " +
+                    // c.getInt(TRUNCATE_HISTORY_PROJECTION_ID_INDEX));
+                    deleteHistoryWhere(
+                            cr, "_id = " +
+                            c.getInt(TRUNCATE_HISTORY_PROJECTION_ID_INDEX));
+                    if (!c.moveToNext()) break;
+                }
+            }
+            c.deactivate();
+        } catch (IllegalStateException e) {
+            Log.e(LOGTAG, "truncateHistory", e);
+            return;
+        }
+    }
+
+    /**
+     * Returns whether there is any history to clear.
+     * @param cr   The ContentResolver used to access the database.
+     * @return boolean  True if the history can be cleared.
+     */
+    public static final boolean canClearHistory(ContentResolver cr) {
+        try {
+            Cursor c = cr.query(
+                BOOKMARKS_URI,
+                new String [] { BookmarkColumns._ID, 
+                                BookmarkColumns.BOOKMARK,
+                                BookmarkColumns.VISITS },
+                "bookmark = 0 OR visits > 0", 
+                null,
+                null
+                );
+            boolean ret = c.moveToFirst();
+            c.deactivate();
+            return ret;
+        } catch (IllegalStateException e) {
+            return false;
+        }
+    }
+
+    /**
+     *  Delete all entries from the bookmarks/history table which are
+     *  not bookmarks.  Also set all visited bookmarks to unvisited.
+     *  @param cr   The ContentResolver used to access the database.
+     */
+    public static final void clearHistory(ContentResolver cr) {
+        deleteHistoryWhere(cr, null);
+    }
+
+    /**
+     * Helper function to delete all history items and revert all
+     * bookmarks to zero visits which meet the criteria provided.
+     * @param cr   The ContentResolver used to access the database.
+     * @param whereClause   String to limit the items affected.
+     *                      null means all items.
+     */
+    private static final void deleteHistoryWhere(ContentResolver cr,
+            String whereClause) {
+        try {
+            Cursor c = cr.query(BOOKMARKS_URI,
+                HISTORY_PROJECTION,
+                whereClause,
+                null,
+                null);
+            if (!c.moveToFirst()) {
+                c.deactivate();
+                return;
+            }
+
+            final WebIconDatabase iconDb = WebIconDatabase.getInstance();
+            /* Delete favicons, and revert bookmarks which have been visited
+             * to simply bookmarks.
+             */
+            StringBuffer sb = new StringBuffer();
+            boolean firstTime = true;
+            do {
+                String url = c.getString(HISTORY_PROJECTION_URL_INDEX);
+                boolean isBookmark = 
+                    c.getInt(HISTORY_PROJECTION_BOOKMARK_INDEX) == 1;
+                if (isBookmark) {
+                    if (firstTime) {
+                        firstTime = false;
+                    } else {
+                        sb.append(" OR ");
+                    }
+                    sb.append("( _id = ");
+                    sb.append(c.getInt(0));
+                    sb.append(" )");
+                } else {
+                    iconDb.releaseIconForPageUrl(url);
+                }
+            } while (c.moveToNext());
+            c.deactivate();
+
+            if (!firstTime) {
+                ContentValues map = new ContentValues();
+                map.put(BookmarkColumns.VISITS, 0);
+                map.put(BookmarkColumns.DATE, 0);
+                /* FIXME: Should I also remove the title? */
+                cr.update(BOOKMARKS_URI, map, sb.toString(), null);
+            }
+
+            String deleteWhereClause = BookmarkColumns.BOOKMARK + " = 0";
+            if (whereClause != null) {
+                deleteWhereClause += " AND " + whereClause;
+            }
+            cr.delete(BOOKMARKS_URI, deleteWhereClause, null);
+        } catch (IllegalStateException e) {
+            return;
+        }
+    }
+
+    /**
+     * Delete all history items from begin to end.
+     * @param cr    The ContentResolver used to access the database.
+     * @param begin First date to remove.  If -1, all dates before end.
+     *              Inclusive.
+     * @param end   Last date to remove. If -1, all dates after begin.
+     *              Non-inclusive.
+     */
+    public static final void deleteHistoryTimeFrame(ContentResolver cr,
+            long begin, long end) {
+        String whereClause;
+        String date = BookmarkColumns.DATE;
+        if (-1 == begin) {
+            if (-1 == end) {
+                clearHistory(cr);
+                return;
+            }
+            whereClause = date + " < " + Long.toString(end);
+        } else if (-1 == end) {
+            whereClause = date + " >= " + Long.toString(begin);
+        } else {
+            whereClause = date + " >= " + Long.toString(begin) + " AND " + date
+                    + " < " + Long.toString(end);
+        }
+        deleteHistoryWhere(cr, whereClause);
+    }
+
+    /**
+     * Remove a specific url from the history database.
+     * @param cr    The ContentResolver used to access the database.
+     * @param url   url to remove.
+     */
+    public static final void deleteFromHistory(ContentResolver cr, 
+                                               String url) {
+        StringBuilder sb = new StringBuilder(BookmarkColumns.URL + " = ");
+        DatabaseUtils.appendEscapedSQLString(sb, url);
+        String matchesUrl = sb.toString();
+        deleteHistoryWhere(cr, matchesUrl);
+    }
+
+    /**
+     * Add a search string to the searches database.
+     * @param cr   The ContentResolver used to access the database.
+     * @param search    The string to add to the searches database.
+     */
+    public static final void addSearchUrl(ContentResolver cr, String search) {
+        long now = new Date().getTime();
+        try {
+            Cursor c = cr.query(
+                SEARCHES_URI,
+                SEARCHES_PROJECTION,
+                SEARCHES_WHERE_CLAUSE,
+                new String [] { search },
+                null);
+            ContentValues map = new ContentValues();
+            map.put(SearchColumns.SEARCH, search);
+            map.put(SearchColumns.DATE, now);
+            /* We should only get one answer that is exactly the same. */
+            if (c.moveToFirst()) {
+                cr.update(SEARCHES_URI, map, "_id = " + c.getInt(0), null);
+            } else {
+                cr.insert(SEARCHES_URI, map);
+            }
+            c.deactivate();
+        } catch (IllegalStateException e) {
+            Log.e(LOGTAG, "addSearchUrl", e);
+            return;
+        }
+    }
+    /**
+     * Remove all searches from the search database.
+     * @param cr   The ContentResolver used to access the database.
+     */
+    public static final void clearSearches(ContentResolver cr) {
+        // FIXME: Should this clear the urls to which these searches lead?
+        // (i.e. remove google.com/query= blah blah blah)
+        try {
+            cr.delete(SEARCHES_URI, null, null);
+        } catch (IllegalStateException e) {
+            Log.e(LOGTAG, "clearSearches", e);
+        }
+    }
+    
+    /**
+     *  Request all icons from the database.
+     *  @param  cr The ContentResolver used to access the database.
+     *  @param  where Clause to be used to limit the query from the database.
+     *          Must be an allowable string to be passed into a database query.
+     *  @param  listener IconListener that gets the icons once they are 
+     *          retrieved.
+     */
+    public static final void requestAllIcons(ContentResolver cr, String where,
+            WebIconDatabase.IconListener listener) {
+        try {
+            final Cursor c = cr.query(
+                    BOOKMARKS_URI,
+                    HISTORY_PROJECTION,
+                    where, null, null);
+            if (c.moveToFirst()) {
+                final WebIconDatabase db = WebIconDatabase.getInstance();
+                do {
+                    db.requestIconForPageUrl(
+                            c.getString(HISTORY_PROJECTION_URL_INDEX), 
+                            listener);
+                } while (c.moveToNext());
+            }
+            c.deactivate();
+        } catch (IllegalStateException e) {
+            Log.e(LOGTAG, "requestAllIcons", e);
+        }
+    }
+
+    public static class BookmarkColumns implements BaseColumns {
+        public static final String URL = "url";
+        public static final String VISITS = "visits";
+        public static final String DATE = "date";
+        public static final String BOOKMARK = "bookmark";
+        public static final String TITLE = "title";
+        public static final String CREATED = "created";
+        public static final String FAVICON = "favicon";
+    }
+
+    public static class SearchColumns implements BaseColumns {
+        public static final String URL = "url";
+        public static final String SEARCH = "search";
+        public static final String DATE = "date";
+    }
+}
diff --git a/core/java/android/provider/Calendar.java b/core/java/android/provider/Calendar.java
new file mode 100644
index 0000000..d75a25f
--- /dev/null
+++ b/core/java/android/provider/Calendar.java
@@ -0,0 +1,1143 @@
+/*
+ * 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 android.provider;
+
+import android.app.AlarmManager;
+import android.app.PendingIntent;
+import android.content.ContentResolver;
+import android.content.ContentUris;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.Intent;
+import android.database.Cursor;
+import android.net.Uri;
+import android.pim.ICalendar;
+import android.pim.RecurrenceSet;
+import android.text.TextUtils;
+import android.text.format.DateUtils;
+import android.text.format.Time;
+import android.util.Config;
+import android.util.Log;
+import com.android.internal.database.ArrayListCursor;
+import com.google.android.gdata.client.AndroidGDataClient;
+import com.google.android.gdata.client.AndroidXmlParserFactory;
+import com.google.wireless.gdata.calendar.client.CalendarClient;
+import com.google.wireless.gdata.calendar.data.EventEntry;
+import com.google.wireless.gdata.calendar.data.Who;
+import com.google.wireless.gdata.calendar.parser.xml.XmlCalendarGDataParserFactory;
+import com.google.wireless.gdata.data.StringUtils;
+
+import java.util.ArrayList;
+import java.util.Vector;
+
+/**
+ * The Calendar provider contains all calendar events.
+ *
+ * @hide
+ */
+public final class Calendar {
+
+    public static final String TAG = "Calendar";
+
+    /**
+     * Broadcast Action: An event reminder.
+     */
+    public static final String
+            EVENT_REMINDER_ACTION = "android.intent.action.EVENT_REMINDER";
+
+    /**
+     * These are the symbolic names for the keys used in the extra data
+     * passed in the intent for event reminders.
+     */
+    public static final String EVENT_BEGIN_TIME = "beginTime";
+    public static final String EVENT_END_TIME = "endTime";
+
+    public static final String AUTHORITY = "calendar";
+
+    /**
+     * The content:// style URL for the top-level calendar authority
+     */
+    public static final Uri CONTENT_URI =
+        Uri.parse("content://" + AUTHORITY);
+
+    /**
+     * Columns from the Calendars table that other tables join into themselves.
+     */
+    public interface CalendarsColumns
+    {
+        /**
+         * The color of the calendar
+         * <P>Type: INTEGER (color value)</P>
+         */
+        public static final String COLOR = "color";
+
+        /**
+         * The level of access that the user has for the calendar
+         * <P>Type: INTEGER (one of the values below)</P>
+         */
+        public static final String ACCESS_LEVEL = "access_level";
+
+        /** Cannot access the calendar */
+        public static final int NO_ACCESS = 0;
+        /** Can only see free/busy information about the calendar */
+        public static final int FREEBUSY_ACCESS = 100;
+        /** Can read all event details */
+        public static final int READ_ACCESS = 200;
+        public static final int RESPOND_ACCESS = 300;
+        public static final int OVERRIDE_ACCESS = 400;
+        /** Full access to modify the calendar, but not the access control settings */
+        public static final int CONTRIBUTOR_ACCESS = 500;
+        public static final int EDITOR_ACCESS = 600;
+        /** Full access to the calendar */
+        public static final int OWNER_ACCESS = 700;
+        public static final int ROOT_ACCESS = 800;
+
+        /**
+         * Is the calendar selected to be displayed?
+         * <P>Type: INTEGER (boolean)</P>
+         */
+        public static final String SELECTED = "selected";
+
+        /**
+         * The timezone the calendar's events occurs in
+         * <P>Type: TEXT</P>
+         */
+        public static final String TIMEZONE = "timezone";
+
+        /**
+         * If this calendar is in the list of calendars that are selected for
+         * syncing then "sync_events" is 1, otherwise 0.
+         * <p>Type: INTEGER (boolean)</p>
+         */
+        public static final String SYNC_EVENTS = "sync_events";
+    }
+
+    /**
+     * Contains a list of available calendars.
+     */
+    public static class Calendars implements BaseColumns, SyncConstValue, CalendarsColumns
+    {
+        public static final Cursor query(ContentResolver cr, String[] projection,
+                                       String where, String orderBy)
+        {
+            return cr.query(CONTENT_URI, projection, where,
+                                         null, orderBy == null ? DEFAULT_SORT_ORDER : orderBy);
+        }
+
+        /**
+         * Convenience method perform a delete on the Calendar provider
+         *
+         * @param cr the ContentResolver
+         * @param selection the rows to delete
+         * @return the count of rows that were deleted
+         */
+        public static int delete(ContentResolver cr, String selection, String[] selectionArgs)
+        {
+            return cr.delete(CONTENT_URI, selection, selectionArgs);
+        }
+
+        /**
+         * Convenience method to delete all calendars that match the account.
+         *
+         * @param cr the ContentResolver
+         * @param account the account whose rows should be deleted
+         * @return the count of rows that were deleted
+         */
+        public static int deleteCalendarsForAccount(ContentResolver cr,
+                String account) {
+            // delete all calendars that match this account
+            return Calendar.Calendars.delete(cr, Calendar.Calendars._SYNC_ACCOUNT + "=?",
+                    new String[] {account});
+        }
+
+        /**
+         * The content:// style URL for this table
+         */
+        public static final Uri CONTENT_URI =
+            Uri.parse("content://calendar/calendars");
+
+        public static final Uri LIVE_CONTENT_URI =
+            Uri.parse("content://calendar/calendars?update=1");
+
+        /**
+         * The default sort order for this table
+         */
+        public static final String DEFAULT_SORT_ORDER = "displayName";
+
+        /**
+         * The URL to the calendar
+         * <P>Type: TEXT (URL)</P>
+         */
+        public static final String URL = "url";
+
+        /**
+         * The name of the calendar
+         * <P>Type: TEXT</P>
+         */
+        public static final String NAME = "name";
+
+        /**
+         * The display name of the calendar
+         * <P>Type: TEXT</P>
+         */
+        public static final String DISPLAY_NAME = "displayName";
+
+        /**
+         * The location the of the events in the calendar
+         * <P>Type: TEXT</P>
+         */
+        public static final String LOCATION = "location";
+
+        /**
+         * Should the calendar be hidden in the calendar selection panel?
+         * <P>Type: INTEGER (boolean)</P>
+         */
+        public static final String HIDDEN = "hidden";
+    }
+
+    public interface AttendeesColumns {
+
+        /**
+         * The id of the event.
+         * <P>Type: INTEGER</P>
+         */
+        public static final String EVENT_ID = "event_id";
+
+        /**
+         * The name of the attendee.
+         * <P>Type: STRING</P>
+         */
+        public static final String ATTENDEE_NAME = "attendeeName";
+
+        /**
+         * The email address of the attendee.
+         * <P>Type: STRING</P>
+         */
+        public static final String ATTENDEE_EMAIL = "attendeeEmail";
+
+        /**
+         * The relationship of the attendee to the user.
+         * <P>Type: INTEGER (one of {@link #RELATIONSHIP_ATTENDEE}, ...}.
+         */
+        public static final String ATTENDEE_RELATIONSHIP = "attendeeRelationship";
+
+        public static final int RELATIONSHIP_NONE = 0;
+        public static final int RELATIONSHIP_ATTENDEE = 1;
+        public static final int RELATIONSHIP_ORGANIZER = 2;
+        public static final int RELATIONSHIP_PERFORMER = 3;
+        public static final int RELATIONSHIP_SPEAKER = 4;
+
+        /**
+         * The type of attendee.
+         * <P>Type: Integer (one of {@link #TYPE_REQUIRED}, {@link #TYPE_OPTIONAL})
+         */
+        public static final String ATTENDEE_TYPE = "attendeeType";
+
+        public static final int TYPE_NONE = 0;
+        public static final int TYPE_REQUIRED = 1;
+        public static final int TYPE_OPTIONAL = 2;
+
+        /**
+         * The attendance status of the attendee.
+         * <P>Type: Integer (one of {@link #ATTENDEE_STATUS_ACCEPTED}, ...}.
+         */
+        public static final String ATTENDEE_STATUS = "attendeeStatus";
+
+        public static final int ATTENDEE_STATUS_NONE = 0;
+        public static final int ATTENDEE_STATUS_ACCEPTED = 1;
+        public static final int ATTENDEE_STATUS_DECLINED = 2;
+        public static final int ATTENDEE_STATUS_INVITED = 3;
+        public static final int ATTENDEE_STATUS_TENTATIVE = 4;
+    }
+
+    public static final class Attendees implements BaseColumns,
+            AttendeesColumns, EventsColumns {
+        public static final Uri CONTENT_URI =
+                Uri.parse("content://calendar/attendees");
+
+        // TODO: fill out this class when we actually start utilizing attendees
+        // in the calendar application.
+    }
+
+    /**
+     * Columns from the Events table that other tables join into themselves.
+     */
+    public interface EventsColumns
+    {
+        /**
+         * The calendar the event belongs to
+         * <P>Type: INTEGER (foreign key to the Calendars table)</P>
+         */
+        public static final String CALENDAR_ID = "calendar_id";
+
+        /**
+         * The URI for an HTML version of this event.
+         * <P>Type: TEXT</P>
+         */
+        public static final String HTML_URI = "htmlUri";
+
+        /**
+         * The title of the event
+         * <P>Type: TEXT</P>
+         */
+        public static final String TITLE = "title";
+
+        /**
+         * The description of the event
+         * <P>Type: TEXT</P>
+         */
+        public static final String DESCRIPTION = "description";
+
+        /**
+         * Where the event takes place.
+         * <P>Type: TEXT</P>
+         */
+        public static final String EVENT_LOCATION = "eventLocation";
+
+        /**
+         * The event status
+         * <P>Type: INTEGER (int)</P>
+         */
+        public static final String STATUS = "eventStatus";
+
+        public static final int STATUS_TENTATIVE = 0;
+        public static final int STATUS_CONFIRMED = 1;
+        public static final int STATUS_CANCELED = 2;
+
+        /**
+         * This is a copy of the attendee status for the owner of this event.
+         * This field is copied here so that we can efficiently filter out
+         * events that are declined without having to look in the Attendees
+         * table.
+         * 
+         * <P>Type: INTEGER (int)</P>
+         */
+        public static final String SELF_ATTENDEE_STATUS = "selfAttendeeStatus";
+        
+        /**
+         * The comments feed uri.
+         * <P>Type: TEXT</P>
+         */
+        public static final String COMMENTS_URI = "commentsUri";
+
+        /**
+         * The time the event starts
+         * <P>Type: INTEGER (long; millis since epoch)</P>
+         */
+        public static final String DTSTART = "dtstart";
+
+        /**
+         * The time the event ends
+         * <P>Type: INTEGER (long; millis since epoch)</P>
+         */
+        public static final String DTEND = "dtend";
+
+        /**
+         * The duration of the event
+         * <P>Type: TEXT (duration in RFC2445 format)</P>
+         */
+        public static final String DURATION = "duration";
+
+        /**
+         * The timezone for the event.
+         * <P>Type: TEXT
+         */
+        public static final String EVENT_TIMEZONE = "eventTimezone";
+
+        /**
+         * Whether the event lasts all day or not
+         * <P>Type: INTEGER (boolean)</P>
+         */
+        public static final String ALL_DAY = "allDay";
+
+        /**
+         * Visibility for the event.
+         * <P>Type: INTEGER</P>
+         */
+        public static final String VISIBILITY = "visibility";
+
+        public static final int VISIBILITY_DEFAULT = 0;
+        public static final int VISIBILITY_CONFIDENTIAL = 1;
+        public static final int VISIBILITY_PRIVATE = 2;
+        public static final int VISIBILITY_PUBLIC = 3;
+
+        /**
+         * Transparency for the event -- does the event consume time on the calendar?
+         * <P>Type: INTEGER</P>
+         */
+        public static final String TRANSPARENCY = "transparency";
+
+        public static final int TRANSPARENCY_OPAQUE = 0;
+
+        public static final int TRANSPARENCY_TRANSPARENT = 1;
+
+        /**
+         * Whether the event has an alarm or not
+         * <P>Type: INTEGER (boolean)</P>
+         */
+        public static final String HAS_ALARM = "hasAlarm";
+
+        /**
+         * Whether the event has extended properties or not
+         * <P>Type: INTEGER (boolean)</P>
+         */
+        public static final String HAS_EXTENDED_PROPERTIES = "hasExtendedProperties";
+
+        /**
+         * The recurrence rule for the event.
+         * than one.
+         * <P>Type: TEXT</P>
+         */
+        public static final String RRULE = "rrule";
+
+        /**
+         * The recurrence dates for the event.
+         * <P>Type: TEXT</P>
+         */
+        public static final String RDATE = "rdate";
+
+        /**
+         * The recurrence exception rule for the event.
+         * <P>Type: TEXT</P>
+         */
+        public static final String EXRULE = "exrule";
+
+        /**
+         * The recurrence exception dates for the event.
+         * <P>Type: TEXT</P>
+         */
+        public static final String EXDATE = "exdate";
+
+        /**
+         * The _sync_id of the original recurring event for which this event is
+         * an exception.
+         * <P>Type: TEXT</P>
+         */
+        public static final String ORIGINAL_EVENT = "originalEvent";
+
+        /**
+         * The original instance time of the recurring event for which this
+         * event is an exception.
+         * <P>Type: INTEGER (long; millis since epoch)</P>
+         */
+        public static final String ORIGINAL_INSTANCE_TIME = "originalInstanceTime";
+
+        /**
+         * The allDay status (true or false) of the original recurring event
+         * for which this event is an exception.
+         * <P>Type: INTEGER (boolean)</P>
+         */
+        public static final String ORIGINAL_ALL_DAY = "originalAllDay";
+
+        /**
+         * The last date this event repeats on, or NULL if it never ends
+         * <P>Type: INTEGER (long; millis since epoch)</P>
+         */
+        public static final String LAST_DATE = "lastDate";
+    }
+
+    /**
+     * Contains one entry per calendar event. Recurring events show up as a single entry.
+     */
+    public static final class Events implements BaseColumns, SyncConstValue,
+                                                EventsColumns, CalendarsColumns {
+
+        private static final String[] FETCH_ENTRY_COLUMNS =
+                new String[] { Events._SYNC_ACCOUNT, Events._SYNC_ID };
+
+        private static final String[] ATTENDEES_COLUMNS =
+                new String[] { AttendeesColumns.ATTENDEE_NAME,
+                               AttendeesColumns.ATTENDEE_EMAIL,
+                               AttendeesColumns.ATTENDEE_RELATIONSHIP,
+                               AttendeesColumns.ATTENDEE_TYPE,
+                               AttendeesColumns.ATTENDEE_STATUS };
+
+        private static CalendarClient sCalendarClient = null;
+
+        public static final Cursor query(ContentResolver cr, String[] projection) {
+            return cr.query(CONTENT_URI, projection, null, null, DEFAULT_SORT_ORDER);
+        }
+
+        public static final Cursor query(ContentResolver cr, String[] projection,
+                                       String where, String orderBy) {
+            return cr.query(CONTENT_URI, projection, where,
+                                         null, orderBy == null ? DEFAULT_SORT_ORDER : orderBy);
+        }
+
+        private static String extractValue(ICalendar.Component component,
+                                           String propertyName) {
+            ICalendar.Property property =
+                    component.getFirstProperty(propertyName);
+            if (property != null) {
+                return property.getValue();
+            }
+            return null;
+        }
+
+        public static final Uri insertVEvent(ContentResolver cr,
+            ICalendar.Component event, long calendarId, int status,
+            ContentValues values) {
+
+            // TODO: define VEVENT component names as constants in some
+            // appropriate class (ICalendar.Component?).
+
+            values.clear();
+
+            // title
+            String title = extractValue(event, "SUMMARY");
+            if (TextUtils.isEmpty(title)) {
+                if (Config.LOGD) {
+                    Log.d(TAG, "No SUMMARY provided for event.  "
+                            + "Cannot import.");
+                }
+                return null;
+            }
+            values.put(TITLE, title);
+
+            // status
+            values.put(STATUS, status);
+
+            // description
+            String description = extractValue(event, "DESCRIPTION");
+            if (!TextUtils.isEmpty(description)) {
+                values.put(DESCRIPTION, description);
+            }
+
+            // where
+            String where = extractValue(event, "LOCATION");
+            if (!StringUtils.isEmpty(where)) {
+                values.put(EVENT_LOCATION, where);
+            }
+
+            // Calendar ID
+            values.put(CALENDAR_ID, calendarId);
+
+            boolean timesSet = false;
+
+            // TODO: deal with VALARMs
+
+            // dtstart & dtend
+            Time time = new Time(Time.TIMEZONE_UTC);
+            String dtstart = null;
+            String dtend = null;
+            String duration = null;
+            ICalendar.Property dtstartProp = event.getFirstProperty("DTSTART");
+            // TODO: handle "floating" timezone (no timezone specified).
+            if (dtstartProp != null) {
+                dtstart = dtstartProp.getValue();
+                if (!TextUtils.isEmpty(dtstart)) {
+                    ICalendar.Parameter tzidParam =
+                            dtstartProp.getFirstParameter("TZID");
+                    if (tzidParam != null && tzidParam.value != null) {
+                        time.clear(tzidParam.value);
+                    }
+                    try {
+                        time.parse(dtstart);
+                    } catch (Exception e) {
+                        if (Config.LOGD) {
+                            Log.d(TAG, "Cannot parse dtstart " + dtstart, e);
+                        }
+                        return null;
+                    }
+                    if (time.allDay) {
+                        values.put(ALL_DAY, 1);
+                    }
+                    values.put(DTSTART, time.toMillis(false /* use isDst */));
+                    values.put(EVENT_TIMEZONE, time.timezone);
+                }
+
+                ICalendar.Property dtendProp = event.getFirstProperty("DTEND");
+                if (dtendProp != null) {
+                    dtend = dtendProp.getValue();
+                    if (!TextUtils.isEmpty(dtend)) {
+                        // TODO: make sure the timezones are the same for
+                        // start, end.
+                        try {
+                            time.parse(dtend);
+                        } catch (Exception e) {
+                            if (Config.LOGD) {
+                                Log.d(TAG, "Cannot parse dtend " + dtend, e);
+                            }
+                            return null;
+                        }
+                        values.put(DTEND, time.toMillis(false /* use isDst */));
+                    }
+                } else {
+                    // look for a duration
+                    ICalendar.Property durationProp =
+                            event.getFirstProperty("DURATION");
+                    if (durationProp != null) {
+                        duration = durationProp.getValue();
+                        if (!TextUtils.isEmpty(duration)) {
+                            // TODO: check that it is valid?
+                            values.put(DURATION, duration);
+                        }
+                    }
+                }
+            }
+            if (TextUtils.isEmpty(dtstart) ||
+                    (TextUtils.isEmpty(dtend) && TextUtils.isEmpty(duration))) {
+                if (Config.LOGD) {
+                    Log.d(TAG, "No DTSTART or DTEND/DURATION defined.");
+                }
+                return null;
+            }
+
+            // rrule
+            if (!RecurrenceSet.populateContentValues(event, values)) {
+                return null;
+            }
+
+            return cr.insert(CONTENT_URI, values);
+        }
+
+        /**
+         * Returns a singleton instance of the CalendarClient used to fetch entries from the
+         * calendar server.
+         * @param cr The ContentResolver used to lookup the address of the calendar server in the
+         * settings database.
+         * @return The singleton instance of the CalendarClient used to fetch entries from the
+         * calendar server.
+         */
+        private static synchronized CalendarClient getCalendarClient(ContentResolver cr) {
+            if (sCalendarClient == null) {
+                sCalendarClient = new CalendarClient(
+                        new AndroidGDataClient(cr),
+                        new XmlCalendarGDataParserFactory(new AndroidXmlParserFactory()));
+            }
+            return sCalendarClient;
+        }
+
+        /**
+         * Extracts the attendees information out of event and adds it to a new ArrayList of columns
+         * within the supplied ArrayList of rows.  These rows are expected to be used within an
+         * {@link ArrayListCursor}.
+         */
+        private static final void extractAttendeesIntoArrayList(EventEntry event,
+                ArrayList<ArrayList> rows) {
+            Log.d(TAG, "EVENT: " + event.toString());
+            Vector<Who> attendees = (Vector<Who>) event.getAttendees();
+
+            int numAttendees = attendees == null ? 0 : attendees.size();
+
+            for (int i = 0; i < numAttendees; ++i) {
+                Who attendee = attendees.elementAt(i);
+                ArrayList row = new ArrayList();
+                row.add(attendee.getValue());
+                row.add(attendee.getEmail());
+                row.add(attendee.getRelationship());
+                row.add(attendee.getType());
+                row.add(attendee.getStatus());
+                rows.add(row);
+            }
+        }
+
+        /**
+         * The content:// style URL for this table
+         */
+        public static final Uri CONTENT_URI =
+                Uri.parse("content://calendar/events");
+
+        public static final Uri DELETED_CONTENT_URI =
+                Uri.parse("content://calendar/deleted_events");
+
+        /**
+         * The default sort order for this table
+         */
+        public static final String DEFAULT_SORT_ORDER = "";
+    }
+
+    /**
+     * Contains one entry per calendar event instance. Recurring events show up every time
+     * they occur.
+     */
+    public static final class Instances implements BaseColumns, EventsColumns, CalendarsColumns {
+
+        public static final Cursor query(ContentResolver cr, String[] projection,
+                                         long begin, long end) {
+            Uri.Builder builder = CONTENT_URI.buildUpon();
+            ContentUris.appendId(builder, begin);
+            ContentUris.appendId(builder, end);
+            return cr.query(builder.build(), projection, Calendars.SELECTED + "=1",
+                         null, DEFAULT_SORT_ORDER);
+        }
+
+        public static final Cursor query(ContentResolver cr, String[] projection,
+                                         long begin, long end, String where, String orderBy) {
+            Uri.Builder builder = CONTENT_URI.buildUpon();
+            ContentUris.appendId(builder, begin);
+            ContentUris.appendId(builder, end);
+            if (TextUtils.isEmpty(where)) {
+                where = Calendars.SELECTED + "=1";
+            } else {
+                where = "(" + where + ") AND " + Calendars.SELECTED + "=1";
+            }
+            return cr.query(builder.build(), projection, where,
+                         null, orderBy == null ? DEFAULT_SORT_ORDER : orderBy);
+        }
+
+        /**
+         * The content:// style URL for this table
+         */
+        public static final Uri CONTENT_URI = Uri.parse("content://calendar/instances/when");
+
+        /**
+         * The default sort order for this table.
+         */
+        public static final String DEFAULT_SORT_ORDER = "begin ASC";
+
+        /**
+         * The sort order is: events with an earlier start time occur
+         * first and if the start times are the same, then events with
+         * a later end time occur first. The later end time is ordered
+         * first so that long-running events in the calendar views appear
+         * first.  If the start and end times of two events are
+         * the same then we sort alphabetically on the title.  This isn't
+         * required for correctness, it just adds a nice touch.
+         */
+        public static final String SORT_CALENDAR_VIEW = "begin ASC, end DESC, title ASC";
+
+        /**
+         * The beginning time of the instance, in UTC milliseconds
+         * <P>Type: INTEGER (long; millis since epoch)</P>
+         */
+        public static final String BEGIN = "begin";
+
+        /**
+         * The ending time of the instance, in UTC milliseconds
+         * <P>Type: INTEGER (long; millis since epoch)</P>
+         */
+        public static final String END = "end";
+
+        /**
+         * The event for this instance
+         * <P>Type: INTEGER (long, foreign key to the Events table)</P>
+         */
+        public static final String EVENT_ID = "event_id";
+
+        /**
+         * The Julian start day of the instance, relative to the local timezone
+         * <P>Type: INTEGER (int)</P>
+         */
+        public static final String START_DAY = "startDay";
+
+        /**
+         * The Julian end day of the instance, relative to the local timezone
+         * <P>Type: INTEGER (int)</P>
+         */
+        public static final String END_DAY = "endDay";
+
+        /**
+         * The start minute of the instance measured from midnight in the
+         * local timezone.
+         * <P>Type: INTEGER (int)</P>
+         */
+        public static final String START_MINUTE = "startMinute";
+
+        /**
+         * The end minute of the instance measured from midnight in the
+         * local timezone.
+         * <P>Type: INTEGER (int)</P>
+         */
+        public static final String END_MINUTE = "endMinute";
+    }
+
+    /**
+     * A few Calendar globals are needed in the CalendarProvider for expanding
+     * the Instances table and these are all stored in the first (and only)
+     * row of the CalendarMetaData table.
+     */
+    public interface CalendarMetaDataColumns {
+        /**
+         * The local timezone that was used for precomputing the fields
+         * in the Instances table.
+         */
+        public static final String LOCAL_TIMEZONE = "localTimezone";
+
+        /**
+         * The minimum time used in expanding the Instances table,
+         * in UTC milliseconds.
+         * <P>Type: INTEGER</P>
+         */
+        public static final String MIN_INSTANCE = "minInstance";
+
+        /**
+         * The maximum time used in expanding the Instances table,
+         * in UTC milliseconds.
+         * <P>Type: INTEGER</P>
+         */
+        public static final String MAX_INSTANCE = "maxInstance";
+
+        /**
+         * The minimum Julian day in the BusyBits table.
+         * <P>Type: INTEGER</P>
+         */
+        public static final String MIN_BUSYBITS = "minBusyBits";
+
+        /**
+         * The maximum Julian day in the BusyBits table.
+         * <P>Type: INTEGER</P>
+         */
+        public static final String MAX_BUSYBITS = "maxBusyBits";
+    }
+    
+    public static final class CalendarMetaData implements CalendarMetaDataColumns {
+    }
+    
+    public interface BusyBitsColumns {
+        /**
+         * The Julian day number.
+         * <P>Type: INTEGER (int)</P>
+         */
+        public static final String DAY = "day";
+
+        /**
+         * The 24 bits representing the 24 1-hour time slots in a day.
+         * If an event in the Instances table overlaps part of a 1-hour
+         * time slot then the corresponding bit is set.  The first time slot
+         * (12am to 1am) is bit 0.  The last time slot (11pm to midnight)
+         * is bit 23.
+         * <P>Type: INTEGER (int)</P>
+         */
+        public static final String BUSYBITS = "busyBits";
+
+        /**
+         * The number of all-day events that occur on this day.
+         * <P>Type: INTEGER (int)</P>
+         */
+        public static final String ALL_DAY_COUNT = "allDayCount";
+    }
+    
+    public static final class BusyBits implements BusyBitsColumns {
+        public static final Uri CONTENT_URI = Uri.parse("content://calendar/busybits/when");
+
+        public static final String[] PROJECTION = { DAY, BUSYBITS, ALL_DAY_COUNT };
+        
+        // The number of minutes represented by one busy bit
+        public static final int MINUTES_PER_BUSY_INTERVAL = 60;
+        
+        // The number of intervals in a day
+        public static final int INTERVALS_PER_DAY = 24 * 60 / MINUTES_PER_BUSY_INTERVAL;
+
+        /**
+         * Retrieves the busy bits for the Julian days starting at "startDay"
+         * for "numDays".
+         * 
+         * @param cr the ContentResolver
+         * @param startDay the first Julian day in the range
+         * @param numDays the number of days to load (must be at least 1)
+         * @return a database cursor
+         */
+        public static final Cursor query(ContentResolver cr, int startDay, int numDays) {
+            if (numDays < 1) {
+                return null;
+            }
+            int endDay = startDay + numDays - 1;
+            Uri.Builder builder = CONTENT_URI.buildUpon();
+            ContentUris.appendId(builder, startDay);
+            ContentUris.appendId(builder, endDay);
+            return cr.query(builder.build(), PROJECTION, null /* selection */,
+                    null /* selection args */, DAY);
+        }
+    }
+
+    public interface RemindersColumns {
+        /**
+         * The event the reminder belongs to
+         * <P>Type: INTEGER (foreign key to the Events table)</P>
+         */
+        public static final String EVENT_ID = "event_id";
+
+        /**
+         * The minutes prior to the event that the alarm should ring.  -1
+         * specifies that we should use the default value for the system.
+         * <P>Type: INTEGER</P>
+         */
+        public static final String MINUTES = "minutes";
+
+        public static final int MINUTES_DEFAULT = -1;
+
+        /**
+         * The alarm method, as set on the server.  DEFAULT, ALERT, EMAIL, and
+         * SMS are possible values; the device will only process DEFAULT and
+         * ALERT reminders (the other types are simply stored so we can send the
+         * same reminder info back to the server when we make changes).
+         */
+        public static final String METHOD = "method";
+
+        public static final int METHOD_DEFAULT = 0;
+        public static final int METHOD_ALERT = 1;
+        public static final int METHOD_EMAIL = 2;
+        public static final int METHOD_SMS = 3;
+    }
+
+    public static final class Reminders implements BaseColumns, RemindersColumns, EventsColumns {
+        public static final String TABLE_NAME = "Reminders";
+        public static final Uri CONTENT_URI = Uri.parse("content://calendar/reminders");
+    }
+
+    public interface CalendarAlertsColumns {
+        /**
+         * The event that the alert belongs to
+         * <P>Type: INTEGER (foreign key to the Events table)</P>
+         */
+        public static final String EVENT_ID = "event_id";
+
+        /**
+         * The start time of the event, in UTC
+         * <P>Type: INTEGER (long; millis since epoch)</P>
+         */
+        public static final String BEGIN = "begin";
+
+        /**
+         * The end time of the event, in UTC
+         * <P>Type: INTEGER (long; millis since epoch)</P>
+         */
+        public static final String END = "end";
+
+        /**
+         * The alarm time of the event, in UTC
+         * <P>Type: INTEGER (long; millis since epoch)</P>
+         */
+        public static final String ALARM_TIME = "alarmTime";
+
+        /**
+         * The creation time of this database entry, in UTC.
+         * (Useful for debugging missed reminders.)
+         * <P>Type: INTEGER (long; millis since epoch)</P>
+         */
+        public static final String CREATION_TIME = "creationTime";
+
+        /**
+         * The time that the alarm broadcast was received by the Calendar app,
+         * in UTC. (Useful for debugging missed reminders.)
+         * <P>Type: INTEGER (long; millis since epoch)</P>
+         */
+        public static final String RECEIVED_TIME = "receivedTime";
+
+        /**
+         * The time that the notification was created by the Calendar app,
+         * in UTC. (Useful for debugging missed reminders.)
+         * <P>Type: INTEGER (long; millis since epoch)</P>
+         */
+        public static final String NOTIFY_TIME = "notifyTime";
+
+        /**
+         * The state of this alert.  It starts out as SCHEDULED, then when
+         * the alarm goes off, it changes to FIRED, and then when the user
+         * dismisses the alarm it changes to DISMISSED.
+         * <P>Type: INTEGER</P>
+         */
+        public static final String STATE = "state";
+
+        public static final int SCHEDULED = 0;
+        public static final int FIRED = 1;
+        public static final int DISMISSED = 2;
+
+        /**
+         * The number of minutes that this alarm precedes the start time
+         * <P>Type: INTEGER </P>
+         */
+        public static final String MINUTES = "minutes";
+
+        /**
+         * The default sort order for this table
+         */
+        public static final String DEFAULT_SORT_ORDER = "alarmTime ASC,begin ASC,title ASC";
+    }
+
+    public static final class CalendarAlerts implements BaseColumns,
+            CalendarAlertsColumns, EventsColumns, CalendarsColumns {
+        public static final String TABLE_NAME = "CalendarAlerts";
+        public static final Uri CONTENT_URI = Uri.parse("content://calendar/calendar_alerts");
+        
+        /**
+         * This URI is for grouping the query results by event_id and begin
+         * time.  This will return one result per instance of an event.  So
+         * events with multiple alarms will appear just once, but multiple
+         * instances of a repeating event will show up multiple times.
+         */
+        public static final Uri CONTENT_URI_BY_INSTANCE = 
+            Uri.parse("content://calendar/calendar_alerts/by_instance");
+
+        public static final Uri insert(ContentResolver cr, long eventId,
+                long begin, long end, long alarmTime, int minutes) {
+            ContentValues values = new ContentValues();
+            values.put(CalendarAlerts.EVENT_ID, eventId);
+            values.put(CalendarAlerts.BEGIN, begin);
+            values.put(CalendarAlerts.END, end);
+            values.put(CalendarAlerts.ALARM_TIME, alarmTime);
+            long currentTime = System.currentTimeMillis();
+            values.put(CalendarAlerts.CREATION_TIME, currentTime);
+            values.put(CalendarAlerts.RECEIVED_TIME, 0);
+            values.put(CalendarAlerts.NOTIFY_TIME, 0);
+            values.put(CalendarAlerts.STATE, SCHEDULED);
+            values.put(CalendarAlerts.MINUTES, minutes);
+            return cr.insert(CONTENT_URI, values);
+        }
+
+        public static final Cursor query(ContentResolver cr, String[] projection,
+                String selection, String[] selectionArgs) {
+            return cr.query(CONTENT_URI, projection, selection, selectionArgs,
+                    DEFAULT_SORT_ORDER);
+        }
+        
+        /**
+         * Finds the next alarm after (or equal to) the given time and returns
+         * the time of that alarm or -1 if no such alarm exists.
+         * 
+         * @param cr the ContentResolver
+         * @param millis the time in UTC milliseconds
+         * @return the next alarm time greater than or equal to "millis", or -1
+         *     if no such alarm exists.
+         */
+        public static final long findNextAlarmTime(ContentResolver cr, long millis) {
+            String selection = ALARM_TIME + ">=" + millis;
+            // TODO: construct an explicit SQL query so that we can add
+            // "LIMIT 1" to the end and get just one result.
+            String[] projection = new String[] { ALARM_TIME };
+            Cursor cursor = query(cr, projection, selection, null);
+            long alarmTime = -1;
+            try {
+                if (cursor != null && cursor.moveToFirst()) {
+                    alarmTime = cursor.getLong(0);
+                }
+            } finally {
+                if (cursor != null) {
+                    cursor.close();
+                }
+            }
+            return alarmTime;
+        }
+        
+        /**
+         * Searches the CalendarAlerts table for alarms that should have fired
+         * but have not and then reschedules them.  This method can be called
+         * at boot time to restore alarms that may have been lost due to a
+         * phone reboot.
+         * 
+         * @param cr the ContentResolver
+         * @param context the Context
+         * @param manager the AlarmManager
+         */
+        public static final void rescheduleMissedAlarms(ContentResolver cr,
+                Context context, AlarmManager manager) {
+            // Get all the alerts that have been scheduled but have not fired
+            // and should have fired by now and are not too old.
+            long now = System.currentTimeMillis();
+            long ancient = now - 24 * DateUtils.HOUR_IN_MILLIS;
+            String selection = CalendarAlerts.STATE + "=" + CalendarAlerts.SCHEDULED
+                    + " AND " + CalendarAlerts.ALARM_TIME + "<" + now
+                    + " AND " + CalendarAlerts.ALARM_TIME + ">" + ancient
+                    + " AND " + CalendarAlerts.END + ">=" + now;
+            String[] projection = new String[] {
+                    _ID,
+                    BEGIN,
+                    END,
+                    ALARM_TIME,
+            };
+            Cursor cursor = CalendarAlerts.query(cr, projection, selection, null);
+            if (cursor == null) {
+                return;
+            }
+            
+            try {
+                while (cursor.moveToNext()) {
+                    long id = cursor.getLong(0);
+                    long begin = cursor.getLong(1);
+                    long end = cursor.getLong(2);
+                    long alarmTime = cursor.getLong(3);
+                    Uri uri = ContentUris.withAppendedId(CONTENT_URI, id);
+                    Intent intent = new Intent(android.provider.Calendar.EVENT_REMINDER_ACTION);
+                    intent.setData(uri);
+                    intent.putExtra(android.provider.Calendar.EVENT_BEGIN_TIME, begin);
+                    intent.putExtra(android.provider.Calendar.EVENT_END_TIME, end);
+                    PendingIntent sender = PendingIntent.getBroadcast(context,
+                            0, intent, PendingIntent.FLAG_CANCEL_CURRENT);
+                    manager.set(AlarmManager.RTC_WAKEUP, alarmTime, sender);
+                }
+            } finally {
+                cursor.close();
+            }
+            
+        }
+        
+        /**
+         * Searches for an entry in the CalendarAlerts table that matches
+         * the given event id, begin time and alarm time.  If one is found
+         * then this alarm already exists and this method returns true.
+         * 
+         * @param cr the ContentResolver
+         * @param eventId the event id to match
+         * @param begin the start time of the event in UTC millis
+         * @param alarmTime the alarm time of the event in UTC millis
+         * @return true if there is already an alarm for the given event
+         *   with the same start time and alarm time.
+         */
+        public static final boolean alarmExists(ContentResolver cr, long eventId,
+                long begin, long alarmTime) {
+            String selection = CalendarAlerts.EVENT_ID + "=" + eventId
+                    + " AND " + CalendarAlerts.BEGIN + "=" + begin
+                    + " AND " + CalendarAlerts.ALARM_TIME + "=" + alarmTime;
+            // TODO: construct an explicit SQL query so that we can add
+            // "LIMIT 1" to the end and get just one result.
+            String[] projection = new String[] { CalendarAlerts.ALARM_TIME };
+            Cursor cursor = query(cr, projection, selection, null);
+            boolean found = false;
+            try {
+                if (cursor != null && cursor.getCount() > 0) {
+                    found = true;
+                }
+            } finally {
+                if (cursor != null) {
+                    cursor.close();
+                }
+            }
+            return found;
+        }
+    }
+
+    public interface ExtendedPropertiesColumns {
+        /**
+         * The event the extended property belongs to
+         * <P>Type: INTEGER (foreign key to the Events table)</P>
+         */
+        public static final String EVENT_ID = "event_id";
+
+        /**
+         * The name of the extended property.  This is a uri of the form
+         * {scheme}#{local-name} convention.
+         * <P>Type: TEXT</P>
+         */
+        public static final String NAME = "name";
+
+        /**
+         * The value of the extended property.
+         * <P>Type: TEXT</P>
+         */
+        public static final String VALUE = "value";
+    }
+
+   public static final class ExtendedProperties implements BaseColumns,
+            ExtendedPropertiesColumns, EventsColumns {
+        public static final Uri CONTENT_URI =
+                Uri.parse("content://calendar/extendedproperties");
+
+        // TODO: fill out this class when we actually start utilizing extendedproperties
+        // in the calendar application.
+   }
+}
diff --git a/core/java/android/provider/CallLog.java b/core/java/android/provider/CallLog.java
new file mode 100644
index 0000000..10fe3f5
--- /dev/null
+++ b/core/java/android/provider/CallLog.java
@@ -0,0 +1,190 @@
+/*
+ * 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 android.provider;
+
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.content.Context;
+import android.net.Uri;
+import android.provider.Contacts.People;
+import com.android.internal.telephony.CallerInfo;
+import android.text.TextUtils;
+import android.util.Log;
+
+/**
+ * The CallLog provider contains information about placed and received calls.
+ */
+public class CallLog {
+    public static final String AUTHORITY = "call_log";
+
+    /**
+     * The content:// style URL for this provider
+     */
+    public static final Uri CONTENT_URI =
+        Uri.parse("content://" + AUTHORITY);
+
+    /**
+     * Contains the recent calls.
+     */
+    public static class Calls implements BaseColumns {
+        /**
+         * The content:// style URL for this table
+         */
+        public static final Uri CONTENT_URI =
+                Uri.parse("content://call_log/calls");
+
+        /**
+         * The content:// style URL for filtering this table on phone numbers
+         */
+        public static final Uri CONTENT_FILTER_URI =
+                Uri.parse("content://call_log/calls/filter");
+
+        /**
+         * The default sort order for this table
+         */
+        public static final String DEFAULT_SORT_ORDER = "date DESC";
+
+        /**
+         * The MIME type of {@link #CONTENT_URI} and {@link #CONTENT_FILTER_URI}
+         * providing a directory of calls.
+         */
+        public static final String CONTENT_TYPE = "vnd.android.cursor.dir/calls";
+
+        /**
+         * The MIME type of a {@link #CONTENT_URI} sub-directory of a single
+         * call.
+         */
+        public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/calls";
+
+        /**
+         * The type of the the phone number.
+         * <P>Type: INTEGER (int)</P>
+         */
+        public static final String TYPE = "type";
+
+        public static final int INCOMING_TYPE = 1;
+        public static final int OUTGOING_TYPE = 2;
+        public static final int MISSED_TYPE = 3;
+
+        /**
+         * The phone number as the user entered it.
+         * <P>Type: TEXT</P>
+         */
+        public static final String NUMBER = "number";
+
+        /**
+         * The date the call occured, in milliseconds since the epoch
+         * <P>Type: INTEGER (long)</P>
+         */
+        public static final String DATE = "date";
+
+        /**
+         * The duration of the call in seconds
+         * <P>Type: INTEGER (long)</P>
+         */
+        public static final String DURATION = "duration";
+
+        /**
+         * Whether or not the call has been acknowledged
+         * <P>Type: INTEGER (boolean)</P>
+         */
+        public static final String NEW = "new";
+
+        /**
+         * The cached name associated with the phone number, if it exists.
+         * This value is not guaranteed to be current, if the contact information
+         * associated with this number has changed.
+         * <P>Type: TEXT</P>
+         */
+        public static final String CACHED_NAME = "name";
+        
+        /**
+         * The cached number type (Home, Work, etc) associated with the
+         * phone number, if it exists.
+         * This value is not guaranteed to be current, if the contact information
+         * associated with this number has changed.
+         * <P>Type: INTEGER</P> 
+         */
+        public static final String CACHED_NUMBER_TYPE = "numbertype";
+        
+        /**
+         * The cached number label, for a custom number type, associated with the
+         * phone number, if it exists.
+         * This value is not guaranteed to be current, if the contact information
+         * associated with this number has changed.
+         * <P>Type: TEXT</P> 
+         */
+        public static final String CACHED_NUMBER_LABEL = "numberlabel";
+        
+        /**
+         * Adds a call to the call log.
+         *
+         * @param ci the CallerInfo object to get the target contact from.  Can be null
+         * if the contact is unknown.
+         * @param context the context used to get the ContentResolver
+         * @param number the phone number to be added to the calls db
+         * @param isPrivateNumber <code>true</code> if the call was marked as private by the network
+         * @param callType enumerated values for "incoming", "outgoing", or "missed"
+         * @param start time stamp for the call in milliseconds
+         * @param duration call duration in seconds
+         * 
+         * {@hide}
+         */
+        public static Uri addCall(CallerInfo ci, Context context, String number, 
+                boolean isPrivateNumber, int callType, long start, int duration) {
+            final ContentResolver resolver = context.getContentResolver();
+
+            if (TextUtils.isEmpty(number)) {
+                if (isPrivateNumber) {
+                    number = CallerInfo.PRIVATE_NUMBER;
+                } else {
+                    number = CallerInfo.UNKNOWN_NUMBER;
+                }
+            }
+
+            ContentValues values = new ContentValues(5);
+
+            values.put(NUMBER, number);
+            values.put(TYPE, Integer.valueOf(callType));
+            values.put(DATE, Long.valueOf(start));
+            values.put(DURATION, Long.valueOf(duration));
+            values.put(NEW, Integer.valueOf(1));
+            if (ci != null) {
+                values.put(CACHED_NAME, ci.name);
+                values.put(CACHED_NUMBER_TYPE, ci.numberType);
+                values.put(CACHED_NUMBER_LABEL, ci.numberLabel);
+            }
+            
+            if ((ci != null) && (ci.person_id > 0)) {
+                People.markAsContacted(resolver, ci.person_id);
+            }
+            
+            Uri result = resolver.insert(CONTENT_URI, values);
+            
+            removeExpiredEntries(context);
+            
+            return result;
+        }
+        
+        private static void removeExpiredEntries(Context context) {
+            final ContentResolver resolver = context.getContentResolver();
+            resolver.delete(CONTENT_URI, "_id IN " +
+                    "(SELECT _id FROM calls ORDER BY " + DEFAULT_SORT_ORDER 
+                    + " LIMIT -1 OFFSET 500)", null);
+        }
+    }
+}
diff --git a/core/java/android/provider/Checkin.java b/core/java/android/provider/Checkin.java
new file mode 100644
index 0000000..1454089
--- /dev/null
+++ b/core/java/android/provider/Checkin.java
@@ -0,0 +1,314 @@
+/*
+ * 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 android.provider;
+
+import org.apache.commons.codec.binary.Base64;
+
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.database.SQLException;
+import android.net.Uri;
+import android.os.SystemClock;
+import android.server.data.CrashData;
+import android.util.Log;
+
+import java.io.ByteArrayOutputStream;
+import java.io.DataOutputStream;
+
+/**
+ * Contract class for the checkin provider, used to store events and
+ * statistics that will be uploaded to a checkin server eventually.
+ * Describes the exposed database schema, and offers methods to add
+ * events and statistics to be uploaded.
+ *
+ * TODO: Move this to vendor/google when we have a home for it.
+ *
+ * @hide
+ */
+public final class Checkin {
+    public static final String AUTHORITY = "android.server.checkin";
+
+    /**
+     * The events table is a log of important timestamped occurrences.
+     * Each event has a type tag and an optional string value.
+     * If too many events are added before they can be reported, the
+     * content provider will erase older events to limit the table size.
+     */
+    public interface Events extends BaseColumns {
+        public static final String TABLE_NAME = "events";
+        public static final Uri CONTENT_URI =
+            Uri.parse("content://" + AUTHORITY + "/" + TABLE_NAME);
+
+        public static final String TAG = "tag";     // TEXT
+        public static final String VALUE = "value"; // TEXT
+        public static final String DATE = "date";   // INTEGER
+
+        /** Valid tag values.  Extend as necessary for your needs. */
+        public enum Tag {
+            AUTOTEST_FAILURE,
+            AUTOTEST_SEQUENCE_BEGIN,
+            AUTOTEST_SUITE_BEGIN,
+            AUTOTEST_TCPDUMP_BEGIN,
+            AUTOTEST_TCPDUMP_DATA,
+            AUTOTEST_TCPDUMP_END,
+            AUTOTEST_TEST_BEGIN,
+            AUTOTEST_TEST_FAILURE,
+            AUTOTEST_TEST_SUCCESS,
+            BROWSER_BUG_REPORT,
+            CARRIER_BUG_REPORT,
+            CHECKIN_FAILURE,
+            CHECKIN_SUCCESS,
+            FOTA_BEGIN,
+            FOTA_FAILURE,
+            FOTA_INSTALL,
+            FOTA_PROMPT,
+            FOTA_PROMPT_ACCEPT,
+            FOTA_PROMPT_REJECT,
+            FOTA_PROMPT_SKIPPED,
+            GSERVICES_ERROR,
+            GSERVICES_UPDATE,
+            LOGIN_SERVICE_ACCOUNT_TRIED,
+            LOGIN_SERVICE_ACCOUNT_SAVED,
+            LOGIN_SERVICE_AUTHENTICATE,
+            LOGIN_SERVICE_CAPTCHA_ANSWERED,
+            LOGIN_SERVICE_CAPTCHA_SHOWN,
+            LOGIN_SERVICE_PASSWORD_ENTERED,
+            LOGIN_SERVICE_SWITCH_GOOGLE_MAIL,
+            NETWORK_DOWN,
+            NETWORK_UP,
+            PHONE_UI,
+            RADIO_BUG_REPORT,
+            SETUP_COMPLETED,
+            SETUP_INITIATED,
+            SETUP_IO_ERROR,
+            SETUP_NETWORK_ERROR,
+            SETUP_REQUIRED_CAPTCHA,
+            SETUP_RETRIES_EXHAUSTED,
+            SETUP_SERVER_ERROR,
+            SETUP_SERVER_TIMEOUT,
+            SETUP_NO_DATA_NETWORK,
+            SYSTEM_BOOT,
+            SYSTEM_LAST_KMSG,
+            SYSTEM_RECOVERY_LOG,
+            SYSTEM_RESTART,
+            SYSTEM_SERVICE_LOOPING,
+            SYSTEM_TOMBSTONE,
+            TEST, 
+            BATTERY_DISCHARGE_INFO,
+        }
+    }
+
+    /**
+     * The stats table is a list of counter values indexed by a tag name.
+     * Each statistic has a count and sum fields, so it can track averages.
+     * When multiple statistics are inserted with the same tag, the count
+     * and sum fields are added together into a single entry in the database.
+     */
+    public interface Stats extends BaseColumns {
+        public static final String TABLE_NAME = "stats";
+        public static final Uri CONTENT_URI =
+            Uri.parse("content://" + AUTHORITY + "/" + TABLE_NAME);
+
+        public static final String TAG = "tag";      // TEXT UNIQUE
+        public static final String COUNT = "count";  // INTEGER
+        public static final String SUM = "sum";      // REAL
+
+        /** Valid tag values.  Extend as necessary for your needs. */
+        public enum Tag {
+            BROWSER_SNAP_CENTER,
+            BROWSER_TEXT_SIZE_CHANGE,
+            BROWSER_ZOOM_OVERVIEW,
+            BROWSER_ZOOM_RING,
+            BROWSER_ZOOM_RING_DRAG,
+            CRASHES_REPORTED,
+            CRASHES_TRUNCATED,
+            ELAPSED_REALTIME_SEC,
+            ELAPSED_UPTIME_SEC,
+            HTTP_STATUS,
+            PHONE_GSM_REGISTERED,
+            PHONE_GPRS_ATTEMPTED,
+            PHONE_GPRS_CONNECTED,
+            PHONE_RADIO_RESETS,
+            TEST,
+            NETWORK_RX_MOBILE,
+            NETWORK_TX_MOBILE,
+        }
+    }
+
+    /**
+     * The properties table is a set of tagged values sent with every checkin.
+     * Unlike statistics or events, they are not cleared after being uploaded.
+     * Multiple properties inserted with the same tag overwrite each other.
+     */
+    public interface Properties extends BaseColumns {
+        public static final String TABLE_NAME = "properties";
+        public static final Uri CONTENT_URI =
+            Uri.parse("content://" + AUTHORITY + "/" + TABLE_NAME);
+
+        public static final String TAG = "tag";      // TEXT UNIQUE
+        public static final String VALUE = "value";  // TEXT
+
+        /** Valid tag values, to be extended as necessary. */
+        public enum Tag {
+            DESIRED_BUILD,
+            MARKET_CHECKIN,
+        }
+    }
+
+    /**
+     * The crashes table is a log of crash reports, kept separate from the
+     * general event log because crashes are large, important, and bursty.
+     * Like the events table, the crashes table is pruned on insert.
+     */
+    public interface Crashes extends BaseColumns {
+        public static final String TABLE_NAME = "crashes";
+        public static final Uri CONTENT_URI =
+            Uri.parse("content://" + AUTHORITY + "/" + TABLE_NAME);
+
+        // TODO: one or both of these should be a file attachment, not a column
+        public static final String DATA = "data";    // TEXT
+        public static final String LOGS = "logs";    // TEXT
+    }
+
+    /**
+     * Intents with this action cause a checkin attempt.  Normally triggered by
+     * a periodic alarm, these may be sent directly to force immediate checkin.
+     */
+    public interface TriggerIntent {
+        public static final String ACTION = "android.server.checkin.CHECKIN";
+
+        // The category is used for GTalk service messages
+        public static final String CATEGORY = "android.server.checkin.CHECKIN";
+        
+        // If true indicates that the checkin should only transfer market related data
+        public static final String EXTRA_MARKET_ONLY = "market_only";
+    }
+
+    private static final String TAG = "Checkin";
+
+    /**
+     * Helper function to log an event to the database.
+     *
+     * @param resolver from {@link android.content.Context#getContentResolver}
+     * @param tag identifying the type of event being recorded
+     * @param value associated with event, if any
+     * @return URI of the event that was added
+     */
+    static public Uri logEvent(ContentResolver resolver,
+            Events.Tag tag, String value) {
+        try {
+            // Don't specify the date column; the content provider will add that.
+            ContentValues values = new ContentValues();
+            values.put(Events.TAG, tag.toString());
+            if (value != null) values.put(Events.VALUE, value);
+            return resolver.insert(Events.CONTENT_URI, values);
+        } catch (IllegalArgumentException e) {  // thrown when provider is unavailable.
+            Log.w(TAG, "Can't log event " + tag + ": " + e);
+            return null;
+        } catch (SQLException e) {
+            Log.e(TAG, "Can't log event " + tag, e);  // Database errors are not fatal.
+            return null;
+        }
+    }
+
+    /**
+     * Helper function to update statistics in the database.
+     * Note that multiple updates to the same tag will be combined.
+     *
+     * @param tag identifying what is being observed
+     * @param count of occurrences
+     * @param sum of some value over these occurrences
+     * @return URI of the statistic that was returned
+     */
+    static public Uri updateStats(ContentResolver resolver,
+            Stats.Tag tag, int count, double sum) {
+        try {
+            ContentValues values = new ContentValues();
+            values.put(Stats.TAG, tag.toString());
+            if (count != 0) values.put(Stats.COUNT, count);
+            if (sum != 0.0) values.put(Stats.SUM, sum);
+            return resolver.insert(Stats.CONTENT_URI, values);
+        } catch (IllegalArgumentException e) {  // thrown when provider is unavailable.
+            Log.w(TAG, "Can't update stat " + tag + ": " + e);
+            return null;
+        } catch (SQLException e) {
+            Log.e(TAG, "Can't update stat " + tag, e);  // Database errors are not fatal.
+            return null;
+        }
+    }
+
+    /** Minimum time to wait after a crash failure before trying again. */
+    static private final long MIN_CRASH_FAILURE_RETRY = 10000;  // 10 seconds
+
+    /** {@link SystemClock#elapsedRealtime} of the last time a crash report failed. */
+    static private volatile long sLastCrashFailureRealtime = -MIN_CRASH_FAILURE_RETRY;
+
+    /**
+     * Helper function to report a crash.
+     *
+     * @param resolver from {@link android.content.Context#getContentResolver}
+     * @param crash data from {@link android.server.data.CrashData}
+     * @return URI of the crash report that was added
+     */
+    static public Uri reportCrash(ContentResolver resolver, byte[] crash) {
+        try {
+            // If we are in a situation where crash reports fail (such as a full disk),
+            // it's important that we don't get into a loop trying to report failures.
+            // So discard all crash reports for a few seconds after reporting fails.
+            long realtime = SystemClock.elapsedRealtime();
+            if (realtime - sLastCrashFailureRealtime < MIN_CRASH_FAILURE_RETRY) {
+                Log.e(TAG, "Crash logging skipped, too soon after logging failure");
+                return null;
+            }
+
+            // HACK: we don't support BLOB values, so base64 encode it.
+            byte[] encoded = Base64.encodeBase64(crash);
+            ContentValues values = new ContentValues();
+            values.put(Crashes.DATA, new String(encoded));
+            Uri uri = resolver.insert(Crashes.CONTENT_URI, values);
+            if (uri == null) {
+                Log.e(TAG, "Error reporting crash");
+                sLastCrashFailureRealtime = SystemClock.elapsedRealtime();
+            }
+            return uri;
+        } catch (Throwable t) {
+            // To avoid an infinite crash-reporting loop, swallow all errors and exceptions.
+            Log.e(TAG, "Error reporting crash: " + t);
+            sLastCrashFailureRealtime = SystemClock.elapsedRealtime();
+            return null;
+        }
+    }
+
+    /**
+     * Report a crash in CrashData format.
+     *
+     * @param resolver from {@link android.content.Context#getContentResolver}
+     * @param crash data to report
+     * @return URI of the crash report that was added
+     */
+    static public Uri reportCrash(ContentResolver resolver, CrashData crash) {
+        try {
+            ByteArrayOutputStream data = new ByteArrayOutputStream();
+            crash.write(new DataOutputStream(data));
+            return reportCrash(resolver, data.toByteArray());
+        } catch (Throwable t) {
+            // Swallow all errors and exceptions when writing crash report
+            Log.e(TAG, "Error writing crash: " + t);
+            return null;
+        }
+    }
+}
diff --git a/core/java/android/provider/Contacts.java b/core/java/android/provider/Contacts.java
new file mode 100644
index 0000000..3bffaec
--- /dev/null
+++ b/core/java/android/provider/Contacts.java
@@ -0,0 +1,1678 @@
+/*
+ * 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 android.provider;
+
+import com.android.internal.R;
+
+import android.content.ContentResolver;
+import android.content.ContentUris;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.Intent;
+import android.database.Cursor;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.net.Uri;
+import android.text.TextUtils;
+import android.util.Log;
+import android.widget.ImageView;
+
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+
+/**
+ * The Contacts provider stores all information about contacts.
+ */
+public class Contacts {
+    private static final String TAG = "Contacts";
+    
+    public static final String AUTHORITY = "contacts";
+
+    /**
+     * The content:// style URL for this provider
+     */
+    public static final Uri CONTENT_URI =
+        Uri.parse("content://" + AUTHORITY);
+
+    /** Signifies an email address row that is stored in the ContactMethods table */
+    public static final int KIND_EMAIL = 1;
+    /** Signifies a postal address row that is stored in the ContactMethods table */
+    public static final int KIND_POSTAL = 2;
+    /** Signifies an IM address row that is stored in the ContactMethods table */
+    public static final int KIND_IM = 3;
+    /** Signifies an Organization row that is stored in the Organizations table */
+    public static final int KIND_ORGANIZATION = 4;
+    /** Signifies an Phone row that is stored in the Phones table */
+    public static final int KIND_PHONE = 5;
+
+    /**
+     * no public constructor since this is a utility class
+     */
+    private Contacts() {}
+
+    /**
+     * Columns from the Settings table that other columns join into themselves.
+     */
+    public interface SettingsColumns {
+        /**
+         * The _SYNC_ACCOUNT to which this setting corresponds. This may be null.
+         * <P>Type: TEXT</P>
+         */
+        public static final String _SYNC_ACCOUNT = "_sync_account";
+
+        /**
+         * The key of this setting.
+         * <P>Type: TEXT</P>
+         */
+        public static final String KEY = "key";
+
+        /**
+         * The value of this setting.
+         * <P>Type: TEXT</P>
+         */
+        public static final String VALUE = "value";
+    }
+
+    /**
+     * The settings over all of the people
+     */
+    public static final class Settings implements BaseColumns, SettingsColumns {
+        /**
+         * no public constructor since this is a utility class
+         */
+        private Settings() {}
+
+        /**
+         * The content:// style URL for this table
+         */
+        public static final Uri CONTENT_URI =
+            Uri.parse("content://contacts/settings");
+
+        /**
+         * The directory twig for this sub-table
+         */
+        public static final String CONTENT_DIRECTORY = "settings";
+
+        /**
+         * The default sort order for this table
+         */
+        public static final String DEFAULT_SORT_ORDER = "key ASC";
+
+        /**
+         * A setting that is used to indicate if we should sync down all groups for the
+         * specified account. For this setting the _SYNC_ACCOUNT column must be set.
+         * If this isn't set then we will only sync the groups whose SHOULD_SYNC column
+         * is set to true.
+         * <p>
+         * This is a boolean setting. It is true if it is set and it is anything other than the
+         * emptry string or "0".
+         */
+        public static final String SYNC_EVERYTHING = "syncEverything";
+
+        public static String getSetting(ContentResolver cr, String account, String key) {
+            // For now we only support a single account and the UI doesn't know what
+            // the account name is, so we're using a global setting for SYNC_EVERYTHING.
+            // Some day when we add multiple accounts to the UI this should honor the account
+            // that was asked for.
+            String selectString;
+            String[] selectArgs;
+            if (false) {
+                selectString = (account == null)
+                        ? "_sync_account is null AND key=?"
+                        : "_sync_account=? AND key=?";
+                selectArgs = (account == null)
+                ? new String[]{key}
+                : new String[]{account, key};
+            } else {
+                selectString = "key=?";
+                selectArgs = new String[] {key};
+            }
+            Cursor cursor = cr.query(Settings.CONTENT_URI, new String[]{VALUE},
+                    selectString, selectArgs, null);
+            try {
+                if (!cursor.moveToNext()) return null;
+                return cursor.getString(0);
+            } finally {
+                cursor.close();
+            }
+        }
+
+        public static void setSetting(ContentResolver cr, String account, String key,
+                String value) {
+            ContentValues values = new ContentValues();
+            // For now we only support a single account and the UI doesn't know what
+            // the account name is, so we're using a global setting for SYNC_EVERYTHING.
+            // Some day when we add multiple accounts to the UI this should honor the account
+            // that was asked for.
+            //values.put(_SYNC_ACCOUNT, account);
+            values.put(KEY, key);
+            values.put(VALUE, value);
+            cr.update(Settings.CONTENT_URI, values, null, null);
+        }
+    }
+
+    /**
+     * Columns from the People table that other tables join into themselves.
+     */
+    public interface PeopleColumns {
+        /**
+         * The person's name.
+         * <P>Type: TEXT</P>
+         */
+        public static final String NAME = "name";
+
+        /**
+         * Phonetic equivalent of the person's name, in a locale-dependent
+         * character set (e.g. hiragana for Japanese).
+         * Used for pronunciation and/or collation in some languages.
+         * <p>Type: TEXT</P>
+         */
+        public static final String PHONETIC_NAME = "phonetic_name";
+
+        /**
+         * The display name. If name is not null name, else if number is not null number,
+         * else if email is not null email.
+         * <P>Type: TEXT</P>
+         */
+        public static final String DISPLAY_NAME = "display_name";
+
+        /**
+         * Notes about the person.
+         * <P>Type: TEXT</P>
+         */
+        public static final String NOTES = "notes";
+
+        /**
+         * The number of times a person has been contacted
+         * <P>Type: INTEGER</P>
+         */
+        public static final String TIMES_CONTACTED = "times_contacted";
+
+        /**
+         * The last time a person was contacted.
+         * <P>Type: INTEGER</P>
+         */
+        public static final String LAST_TIME_CONTACTED = "last_time_contacted";
+
+        /**
+         * A custom ringtone associated with a person. Not always present.
+         * <P>Type: TEXT (URI to the ringtone)</P>
+         */
+        public static final String CUSTOM_RINGTONE = "custom_ringtone";
+
+        /**
+         * Whether the person should always be sent to voicemail. Not always
+         * present.
+         * <P>Type: INTEGER (0 for false, 1 for true)</P>
+         */
+        public static final String SEND_TO_VOICEMAIL = "send_to_voicemail";
+
+        /**
+         * Is the contact starred?
+         * <P>Type: INTEGER (boolean)</P>
+         */
+        public static final String STARRED = "starred";
+
+        /**
+         * The server version of the photo
+         * <P>Type: TEXT (the version number portion of the photo URI)</P>
+         */
+        public static final String PHOTO_VERSION = "photo_version";
+    }
+
+    /**
+     * This table contains people.
+     */
+    public static final class People implements BaseColumns, SyncConstValue, PeopleColumns,
+            PhonesColumns, PresenceColumns {
+        /**
+         * no public constructor since this is a utility class
+         */
+        private People() {}
+
+        /**
+         * The content:// style URL for this table
+         */
+        public static final Uri CONTENT_URI =
+            Uri.parse("content://contacts/people");
+
+        /**
+         * The content:// style URL for filtering people by name. The filter
+         * argument should be passed as an additional path segment after this URI.
+         */
+        public static final Uri CONTENT_FILTER_URI =
+            Uri.parse("content://contacts/people/filter");
+
+        /**
+         * The content:// style URL for the table that holds the deleted
+         * contacts.
+         */
+        public static final Uri DELETED_CONTENT_URI =
+            Uri.parse("content://contacts/deleted_people");
+
+        /**
+         * The MIME type of {@link #CONTENT_URI} providing a directory of
+         * people.
+         */
+        public static final String CONTENT_TYPE = "vnd.android.cursor.dir/person";
+
+        /**
+         * The MIME type of a {@link #CONTENT_URI} subdirectory of a single
+         * person.
+         */
+        public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/person";
+
+        /**
+         * The default sort order for this table
+         */
+        public static final String DEFAULT_SORT_ORDER = People.NAME + " ASC";
+
+        /**
+         * The ID of the persons preferred phone number.
+         * <P>Type: INTEGER (foreign key to phones table on the _ID field)</P>
+         */
+        public static final String PRIMARY_PHONE_ID = "primary_phone";
+
+        /**
+         * The ID of the persons preferred email.
+         * <P>Type: INTEGER (foreign key to contact_methods table on the
+         * _ID field)</P>
+         */
+        public static final String PRIMARY_EMAIL_ID = "primary_email";
+
+        /**
+         * The ID of the persons preferred organization.
+         * <P>Type: INTEGER (foreign key to organizations table on the
+         * _ID field)</P>
+         */
+        public static final String PRIMARY_ORGANIZATION_ID = "primary_organization";
+
+        /**
+         * Mark a person as having been contacted.
+         *
+         * @param resolver the ContentResolver to use
+         * @param personId the person who was contacted
+         */
+        public static void markAsContacted(ContentResolver resolver, long personId) {
+            Uri uri = ContentUris.withAppendedId(CONTENT_URI, personId);
+            uri = Uri.withAppendedPath(uri, "update_contact_time");
+            ContentValues values = new ContentValues();
+            // There is a trigger in place that will update TIMES_CONTACTED when
+            // LAST_TIME_CONTACTED is modified.
+            values.put(LAST_TIME_CONTACTED, System.currentTimeMillis());
+            resolver.update(uri, values, null, null);
+        }
+
+        /**
+         * Adds a person to the My Contacts group.
+         * 
+         * @param resolver the resolver to use
+         * @param personId the person to add to the group
+         * @return the URI of the group membership row
+         * @throws IllegalStateException if the My Contacts group can't be found
+         */
+        public static Uri addToMyContactsGroup(ContentResolver resolver, long personId) {
+            long groupId = 0;
+            Cursor groupsCursor = resolver.query(Groups.CONTENT_URI, GROUPS_PROJECTION,
+                    Groups.SYSTEM_ID + "='" + Groups.GROUP_MY_CONTACTS + "'", null, null);
+            if (groupsCursor != null) {
+                try {
+                    if (groupsCursor.moveToFirst()) {
+                        groupId = groupsCursor.getLong(0);
+                    }
+                } finally {
+                    groupsCursor.close();
+                }
+            }
+
+            if (groupId == 0) {
+                throw new IllegalStateException("Failed to find the My Contacts group");
+            }
+            
+            return addToGroup(resolver, personId, groupId);
+        }
+
+        /**
+         * Adds a person to a group referred to by name.
+         * 
+         * @param resolver the resolver to use
+         * @param personId the person to add to the group
+         * @param groupName the name of the group to add the contact to
+         * @return the URI of the group membership row
+         * @throws IllegalStateException if the group can't be found
+         */
+        public static Uri addToGroup(ContentResolver resolver, long personId, String groupName) {
+            long groupId = 0;
+            Cursor groupsCursor = resolver.query(Groups.CONTENT_URI, GROUPS_PROJECTION,
+                    Groups.NAME + "=?", new String[] { groupName }, null);
+            if (groupsCursor != null) {
+                try {
+                    if (groupsCursor.moveToFirst()) {
+                        groupId = groupsCursor.getLong(0);
+                    }
+                } finally {
+                    groupsCursor.close();
+                }
+            }
+
+            if (groupId == 0) {
+                throw new IllegalStateException("Failed to find the My Contacts group");
+            }
+            
+            return addToGroup(resolver, personId, groupId);
+        }
+
+        /**
+         * Adds a person to a group.
+         * 
+         * @param resolver the resolver to use
+         * @param personId the person to add to the group
+         * @param groupId the group to add the person to
+         * @return the URI of the group membership row
+         */
+        public static Uri addToGroup(ContentResolver resolver, long personId, long groupId) {
+            ContentValues values = new ContentValues();
+            values.put(GroupMembership.PERSON_ID, personId);
+            values.put(GroupMembership.GROUP_ID, groupId);
+            return resolver.insert(GroupMembership.CONTENT_URI, values);
+        }
+        
+        private static final String[] GROUPS_PROJECTION = new String[] {
+            Groups._ID,
+        };
+
+        /**
+         * Creates a new contacts and adds it to the "My Contacts" group.
+         * 
+         * @param resolver the ContentResolver to use
+         * @param values the values to use when creating the contact
+         * @return the URI of the contact, or null if the operation fails
+         */
+        public static Uri createPersonInMyContactsGroup(ContentResolver resolver,
+                ContentValues values) {
+
+            Uri contactUri = resolver.insert(People.CONTENT_URI, values);
+            if (contactUri == null) {
+                Log.e(TAG, "Failed to create the contact");
+                return null;
+            }
+
+            if (addToMyContactsGroup(resolver, ContentUris.parseId(contactUri)) == null) {
+                resolver.delete(contactUri, null, null);
+                return null;
+            }
+            return contactUri;
+        }
+
+        public static Cursor queryGroups(ContentResolver resolver, long person) {
+            return resolver.query(GroupMembership.CONTENT_URI, null, "person=?",
+                    new String[]{String.valueOf(person)}, Groups.DEFAULT_SORT_ORDER);
+        }
+
+        /**
+         * Set the photo for this person. data may be null
+         * @param cr the ContentResolver to use
+         * @param person the Uri of the person whose photo is to be updated
+         * @param data the byte[] that represents the photo
+         */
+        public static void setPhotoData(ContentResolver cr, Uri person, byte[] data) {
+            Uri photoUri = Uri.withAppendedPath(person, Contacts.Photos.CONTENT_DIRECTORY);
+            ContentValues values = new ContentValues();
+            values.put(Photos.DATA, data);
+            cr.update(photoUri, values, null, null);
+        }
+        
+        /**
+         * Opens an InputStream for the person's photo and returns the photo as a Bitmap.
+         * If the person's photo isn't present returns the placeholderImageResource instead.
+         * @param person the person whose photo should be used
+         */
+        public static InputStream openContactPhotoInputStream(ContentResolver cr, Uri person) {
+            Uri photoUri = Uri.withAppendedPath(person, Contacts.Photos.CONTENT_DIRECTORY);
+            Cursor cursor = cr.query(photoUri, new String[]{Photos.DATA}, null, null, null);
+            try {
+                if (!cursor.moveToNext()) {
+                    return null;
+                }
+                byte[] data = cursor.getBlob(0);
+                if (data == null) {
+                    return null;
+                }
+                return new ByteArrayInputStream(data);
+            } finally {
+                cursor.close();
+            }
+        }
+
+        /**
+         * Opens an InputStream for the person's photo and returns the photo as a Bitmap.
+         * If the person's photo isn't present returns the placeholderImageResource instead.
+         * @param context the Context
+         * @param person the person whose photo should be used
+         * @param placeholderImageResource the image resource to use if the person doesn't
+         *   have a photo
+         * @param options the decoding options, can be set to null
+         */
+        public static Bitmap loadContactPhoto(Context context, Uri person,
+                int placeholderImageResource, BitmapFactory.Options options) {
+            if (person == null) {
+                return loadPlaceholderPhoto(placeholderImageResource, context, options);
+            }
+
+            InputStream stream = openContactPhotoInputStream(context.getContentResolver(), person);
+            Bitmap bm = stream != null ? BitmapFactory.decodeStream(stream, null, options) : null;
+            if (bm == null) {
+                bm = loadPlaceholderPhoto(placeholderImageResource, context, options);
+            }
+            return bm;
+        }
+
+        private static Bitmap loadPlaceholderPhoto(int placeholderImageResource, Context context,
+                BitmapFactory.Options options) {
+            if (placeholderImageResource == 0) {
+                return null;
+            }
+            return BitmapFactory.decodeResource(context.getResources(),
+                    placeholderImageResource, options);
+        }
+
+        /**
+         * A sub directory of a single person that contains all of their Phones.
+         */
+        public static final class Phones implements BaseColumns, PhonesColumns,
+                PeopleColumns {
+            /**
+             * no public constructor since this is a utility class
+             */
+            private Phones() {}
+
+            /**
+             * The directory twig for this sub-table
+             */
+            public static final String CONTENT_DIRECTORY = "phones";
+
+            /**
+             * The default sort order for this table
+             */
+            public static final String DEFAULT_SORT_ORDER = "number ASC";
+        }
+
+        /**
+         * A subdirectory of a single person that contains all of their
+         * ContactMethods.
+         */
+        public static final class ContactMethods
+                implements BaseColumns, ContactMethodsColumns, PeopleColumns {
+            /**
+             * no public constructor since this is a utility class
+             */
+            private ContactMethods() {}
+
+            /**
+             * The directory twig for this sub-table
+             */
+            public static final String CONTENT_DIRECTORY = "contact_methods";
+
+            /**
+             * The default sort order for this table
+             */
+            public static final String DEFAULT_SORT_ORDER = "data ASC";
+        }
+
+        /**
+         * The extensions for a person
+         */
+        public static class Extensions implements BaseColumns, ExtensionsColumns {
+            /**
+             * no public constructor since this is a utility class
+             */
+            private Extensions() {}
+
+            /**
+             * The directory twig for this sub-table
+             */
+            public static final String CONTENT_DIRECTORY = "extensions";
+
+            /**
+             * The default sort order for this table
+             */
+            public static final String DEFAULT_SORT_ORDER = "name ASC";
+
+            /**
+             * The ID of the person this phone number is assigned to.
+             * <P>Type: INTEGER (long)</P>
+             */
+            public static final String PERSON_ID = "person";
+        }
+    }
+
+    /**
+     * Columns from the groups table.
+     */
+    public interface GroupsColumns {
+        /**
+         * The group name.
+         * <P>Type: TEXT</P>
+         */
+        public static final String NAME = "name";
+
+        /**
+         * Notes about the group.
+         * <P>Type: TEXT</P>
+         */
+        public static final String NOTES = "notes";
+
+        /**
+         * Whether this group should be synced if the SYNC_EVERYTHING settings is false
+         * for this group's account.
+         * <P>Type: INTEGER (boolean)</P>
+         */
+        public static final String SHOULD_SYNC = "should_sync";
+
+        /**
+         * The ID of this group if it is a System Group, null otherwise.
+         * <P>Type: TEXT</P>
+         */
+        public static final String SYSTEM_ID = "system_id";
+    }
+
+    /**
+     * This table contains the groups for an account.
+     */
+    public static final class Groups
+            implements BaseColumns, SyncConstValue, GroupsColumns {
+        /**
+         * no public constructor since this is a utility class
+         */
+        private Groups() {}
+
+        /**
+         * The content:// style URL for this table
+         */
+        public static final Uri CONTENT_URI =
+            Uri.parse("content://contacts/groups");
+
+        /**
+         * The content:// style URL for the table that holds the deleted
+         * groups.
+         */
+        public static final Uri DELETED_CONTENT_URI =
+            Uri.parse("content://contacts/deleted_groups");
+
+        /**
+         * The MIME type of {@link #CONTENT_URI} providing a directory of
+         * groups.
+         */
+        public static final String CONTENT_TYPE = "vnd.android.cursor.dir/contactsgroup";
+
+        /**
+         * The MIME type of a {@link #CONTENT_URI} subdirectory of a single
+         * group.
+         */
+        public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/contactsgroup";
+
+        /**
+         * The default sort order for this table
+         */
+        public static final String DEFAULT_SORT_ORDER = NAME + " ASC";
+
+        /**
+         *
+         */
+        public static final String GROUP_ANDROID_STARRED = "Starred in Android";
+
+        /**
+         * The "My Contacts" system group.
+         */
+        public static final String GROUP_MY_CONTACTS = "Contacts";
+    }
+
+    /**
+     * Columns from the Phones table that other columns join into themselves.
+     */
+    public interface PhonesColumns {
+        /**
+         * The type of the the phone number.
+         * <P>Type: INTEGER (one of the constants below)</P>
+         */
+        public static final String TYPE = "type";
+
+        public static final int TYPE_CUSTOM = 0;
+        public static final int TYPE_HOME = 1;
+        public static final int TYPE_MOBILE = 2;
+        public static final int TYPE_WORK = 3;
+        public static final int TYPE_FAX_WORK = 4;
+        public static final int TYPE_FAX_HOME = 5;
+        public static final int TYPE_PAGER = 6;
+        public static final int TYPE_OTHER = 7;
+
+        /**
+         * The user provided label for the phone number, only used if TYPE is TYPE_CUSTOM.
+         * <P>Type: TEXT</P>
+         */
+        public static final String LABEL = "label";
+
+        /**
+         * The phone number as the user entered it.
+         * <P>Type: TEXT</P>
+         */
+        public static final String NUMBER = "number";
+
+        /**
+         * The normalized phone number
+         * <P>Type: TEXT</P>
+         */
+        public static final String NUMBER_KEY = "number_key";
+
+        /**
+         * Whether this is the primary phone number
+         * <P>Type: INTEGER (if set, non-0 means true)</P>
+         */
+        public static final String ISPRIMARY = "isprimary";
+    }
+
+    /**
+     * This table stores phone numbers and a reference to the person that the
+     * contact method belongs to. Phone numbers are stored separately from
+     * other contact methods to make caller ID lookup more efficient.
+     */
+    public static final class Phones
+            implements BaseColumns, PhonesColumns, PeopleColumns {
+        /**
+         * no public constructor since this is a utility class
+         */
+        private Phones() {}
+
+        public static final CharSequence getDisplayLabel(Context context, int type,
+                CharSequence label, CharSequence[] labelArray) {
+            CharSequence display = "";
+
+            if (type != People.Phones.TYPE_CUSTOM) {
+                CharSequence[] labels = labelArray != null? labelArray 
+                        : context.getResources().getTextArray(
+                                com.android.internal.R.array.phoneTypes);
+                try {
+                    display = labels[type - 1];
+                } catch (ArrayIndexOutOfBoundsException e) {
+                    display = labels[People.Phones.TYPE_HOME - 1];
+                }
+            } else {
+                if (!TextUtils.isEmpty(label)) {
+                    display = label;
+                }
+            }
+            return display;
+        }
+
+        public static final CharSequence getDisplayLabel(Context context, int type,
+                CharSequence label) {
+            return getDisplayLabel(context, type, label, null);
+        }
+        
+        /**
+         * The content:// style URL for this table
+         */
+        public static final Uri CONTENT_URI =
+            Uri.parse("content://contacts/phones");
+
+        /**
+         * The content:// style URL for filtering phone numbers
+         */
+        public static final Uri CONTENT_FILTER_URL =
+            Uri.parse("content://contacts/phones/filter");
+
+        /**
+         * The MIME type of {@link #CONTENT_URI} providing a directory of
+         * phones.
+         */
+        public static final String CONTENT_TYPE = "vnd.android.cursor.dir/phone";
+
+        /**
+         * The MIME type of a {@link #CONTENT_URI} subdirectory of a single
+         * phone.
+         */
+        public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/phone";
+
+        /**
+         * The default sort order for this table
+         */
+        public static final String DEFAULT_SORT_ORDER = "name ASC";
+
+        /**
+         * The ID of the person this phone number is assigned to.
+         * <P>Type: INTEGER (long)</P>
+         */
+        public static final String PERSON_ID = "person";
+    }
+
+    public static final class GroupMembership implements BaseColumns, GroupsColumns {
+        /**
+         * no public constructor since this is a utility class
+         */
+        private GroupMembership() {}
+
+        /**
+         * The content:// style URL for this table
+         */
+        public static final Uri CONTENT_URI =
+            Uri.parse("content://contacts/groupmembership");
+
+        /**
+         * The content:// style URL for this table
+         */
+        public static final Uri RAW_CONTENT_URI =
+            Uri.parse("content://contacts/groupmembershipraw");
+
+        /**
+         * The directory twig for this sub-table
+         */
+        public static final String CONTENT_DIRECTORY = "groupmembership";
+        /**
+         * The MIME type of {@link #CONTENT_URI} providing a directory of all
+         * person groups.
+         */
+        public static final String CONTENT_TYPE = "vnd.android.cursor.dir/contactsgroupmembership";
+
+        /**
+         * The MIME type of a {@link #CONTENT_URI} subdirectory of a single
+         * person group.
+         */
+        public static final String CONTENT_ITEM_TYPE =
+                "vnd.android.cursor.item/contactsgroupmembership";
+
+        /**
+         * The default sort order for this table
+         */
+        public static final String DEFAULT_SORT_ORDER = "group_id ASC";
+
+        /**
+         * The row id of the accounts group.
+         * <P>Type: TEXT</P>
+         */
+        public static final String GROUP_ID = "group_id";
+
+        /**
+         * The sync id of the group.
+         * <P>Type: TEXT</P>
+         */
+        public static final String GROUP_SYNC_ID = "group_sync_id";
+
+        /**
+         * The account of the group.
+         * <P>Type: TEXT</P>
+         */
+        public static final String GROUP_SYNC_ACCOUNT = "group_sync_account";
+
+        /**
+         * The row id of the person.
+         * <P>Type: TEXT</P>
+         */
+        public static final String PERSON_ID = "person";
+    }
+
+    /**
+     * Columns from the ContactMethods table that other tables join into
+     * themseleves.
+     */
+    public interface ContactMethodsColumns {
+        /**
+         * The kind of the the contact method. For example, email address,
+         * postal address, etc.
+         * <P>Type: INTEGER (one of the values below)</P>
+         */
+        public static final String KIND = "kind";
+
+        /**
+         * The type of the contact method, must be one of the types below.
+         * <P>Type: INTEGER (one of the values below)</P>
+         */
+        public static final String TYPE = "type";
+        public static final int TYPE_CUSTOM = 0;
+        public static final int TYPE_HOME = 1;
+        public static final int TYPE_WORK = 2;
+        public static final int TYPE_OTHER = 3;
+
+        /**
+         * The user defined label for the the contact method.
+         * <P>Type: TEXT</P>
+         */
+        public static final String LABEL = "label";
+
+        /**
+         * The data for the contact method.
+         * <P>Type: TEXT</P>
+         */
+        public static final String DATA = "data";
+
+        /**
+         * Auxiliary data for the contact method.
+         * <P>Type: TEXT</P>
+         */
+        public static final String AUX_DATA = "aux_data";
+
+        /**
+         * Whether this is the primary organization
+         * <P>Type: INTEGER (if set, non-0 means true)</P>
+         */
+        public static final String ISPRIMARY = "isprimary";
+    }
+
+    /**
+     * This table stores all non-phone contact methods and a reference to the
+     * person that the contact method belongs to.
+     */
+    public static final class ContactMethods
+            implements BaseColumns, ContactMethodsColumns, PeopleColumns {
+        /**
+         * The column with latitude data for postal locations
+         * <P>Type: REAL</P>
+         */
+        public static final String POSTAL_LOCATION_LATITUDE = DATA;
+
+        /**
+         * The column with longitude data for postal locations
+         * <P>Type: REAL</P>
+         */
+        public static final String POSTAL_LOCATION_LONGITUDE = AUX_DATA;
+
+        /**
+         * The predefined IM protocol types. The protocol can either be non-present, one
+         * of these types, or a free-form string. These cases are encoded in the AUX_DATA
+         * column as:
+         *  - null
+         *  - pre:<an integer, one of the protocols below>
+         *  - custom:<a string>
+         */
+        public static final int PROTOCOL_AIM = 0;
+        public static final int PROTOCOL_MSN = 1;
+        public static final int PROTOCOL_YAHOO = 2;
+        public static final int PROTOCOL_SKYPE = 3;
+        public static final int PROTOCOL_QQ = 4;
+        public static final int PROTOCOL_GOOGLE_TALK = 5;
+        public static final int PROTOCOL_ICQ = 6;
+        public static final int PROTOCOL_JABBER = 7;
+
+        public static String encodePredefinedImProtocol(int protocol) {
+            return "pre:" + protocol;
+        }
+
+        public static String encodeCustomImProtocol(String protocolString) {
+            return "custom:" + protocolString;
+        }
+
+        public static Object decodeImProtocol(String encodedString) {
+            if (encodedString == null) {
+                return null;
+            }
+
+            if (encodedString.startsWith("pre:")) {
+                return Integer.parseInt(encodedString.substring(4));
+            }
+
+            if (encodedString.startsWith("custom:")) {
+                return encodedString.substring(7);
+            }
+
+            throw new IllegalArgumentException(
+                    "the value is not a valid encoded protocol, " + encodedString);
+        }
+        
+        /**
+         * This looks up the provider name defined in
+         * {@link android.provider.Im.ProviderNames} from the predefined IM protocol id.
+         * This is used for interacting with the IM application.
+         *
+         * @param protocol the protocol ID
+         * @return the provider name the IM app uses for the given protocol, or null if no
+         * provider is defined for the given protocol
+         * @hide
+         */
+        public static String lookupProviderNameFromId(int protocol) {
+            switch (protocol) {
+                case PROTOCOL_GOOGLE_TALK:
+                    return Im.ProviderNames.GTALK;
+                case PROTOCOL_AIM:
+                    return Im.ProviderNames.AIM;
+                case PROTOCOL_MSN:
+                    return Im.ProviderNames.MSN;
+                case PROTOCOL_YAHOO:
+                    return Im.ProviderNames.YAHOO;
+                case PROTOCOL_ICQ:
+                    return Im.ProviderNames.ICQ;
+                case PROTOCOL_JABBER:
+                    return Im.ProviderNames.JABBER;
+                case PROTOCOL_SKYPE:
+                    return Im.ProviderNames.SKYPE;
+                case PROTOCOL_QQ:
+                    return Im.ProviderNames.QQ;
+            }
+            return null;
+        }
+
+        /**
+         * no public constructor since this is a utility class
+         */
+        private ContactMethods() {}
+
+        public static final CharSequence getDisplayLabel(Context context, int kind,
+                int type, CharSequence label) {
+            CharSequence display = "";
+            switch (kind) {
+                case KIND_EMAIL: {
+                    if (type != People.ContactMethods.TYPE_CUSTOM) {
+                        CharSequence[] labels = context.getResources().getTextArray(
+                                com.android.internal.R.array.emailAddressTypes);
+                        try {
+                            display = labels[type - 1];
+                        } catch (ArrayIndexOutOfBoundsException e) {
+                            display = labels[ContactMethods.TYPE_HOME - 1];
+                        }
+                    } else {
+                        if (!TextUtils.isEmpty(label)) {
+                            display = label;
+                        }
+                    }
+                    break;
+                }
+
+                case KIND_POSTAL: {
+                    if (type != People.ContactMethods.TYPE_CUSTOM) {
+                        CharSequence[] labels = context.getResources().getTextArray(
+                                com.android.internal.R.array.postalAddressTypes);
+                        try {
+                            display = labels[type - 1];
+                        } catch (ArrayIndexOutOfBoundsException e) {
+                            display = labels[ContactMethods.TYPE_HOME - 1];
+                        }
+                    } else {
+                        if (!TextUtils.isEmpty(label)) {
+                            display = label;
+                        }
+                    }
+                    break;
+                }
+
+                default:
+                    display = context.getString(R.string.untitled);
+            }
+            return display;
+        }
+
+        /**
+         * Add a longitude and latitude location to a postal address.
+         *
+         * @param context the context to use when updating the database
+         * @param postalId the address to update
+         * @param latitude the latitude for the address
+         * @param longitude the longitude for the address
+         */
+        public void addPostalLocation(Context context, long postalId,
+                double latitude, double longitude) {
+            final ContentResolver resolver = context.getContentResolver();
+            // Insert the location
+            ContentValues values = new ContentValues(2);
+            values.put(POSTAL_LOCATION_LATITUDE, latitude);
+            values.put(POSTAL_LOCATION_LONGITUDE, longitude);
+            Uri loc = resolver.insert(CONTENT_URI, values);
+            long locId = ContentUris.parseId(loc);
+
+            // Update the postal address
+            values.clear();
+            values.put(AUX_DATA, locId);
+            resolver.update(ContentUris.withAppendedId(CONTENT_URI, postalId), values, null, null);
+        }
+
+        /**
+         * The content:// style URL for this table
+         */
+        public static final Uri CONTENT_URI =
+            Uri.parse("content://contacts/contact_methods");
+
+        /**
+         * The content:// style URL for sub-directory of e-mail addresses.
+         */
+        public static final Uri CONTENT_EMAIL_URI =
+            Uri.parse("content://contacts/contact_methods/email");
+
+        /**
+         * The MIME type of {@link #CONTENT_URI} providing a directory of
+         * phones.
+         */
+        public static final String CONTENT_TYPE = "vnd.android.cursor.dir/contact-methods";
+
+        /**
+         * The MIME type of a {@link #CONTENT_EMAIL_URI} sub-directory of\
+         * multiple {@link Contacts#KIND_EMAIL} entries.
+         */
+        public static final String CONTENT_EMAIL_TYPE = "vnd.android.cursor.dir/email";
+
+        /**
+         * The MIME type of a {@link #CONTENT_EMAIL_URI} sub-directory of\
+         * multiple {@link Contacts#KIND_POSTAL} entries.
+         */
+        public static final String CONTENT_POSTAL_TYPE = "vnd.android.cursor.dir/postal-address";
+
+        /**
+         * The MIME type of a {@link #CONTENT_URI} sub-directory of a single
+         * {@link Contacts#KIND_EMAIL} entry.
+         */
+        public static final String CONTENT_EMAIL_ITEM_TYPE = "vnd.android.cursor.item/email";
+
+        /**
+         * The MIME type of a {@link #CONTENT_URI} sub-directory of a single
+         * {@link Contacts#KIND_POSTAL} entry.
+         */
+        public static final String CONTENT_POSTAL_ITEM_TYPE
+                = "vnd.android.cursor.item/postal-address";
+
+        /**
+         * The MIME type of a {@link #CONTENT_URI} sub-directory of a single
+         * {@link Contacts#KIND_IM} entry.
+         */
+        public static final String CONTENT_IM_ITEM_TYPE = "vnd.android.cursor.item/jabber-im";
+
+        /**
+         * The default sort order for this table
+         */
+        public static final String DEFAULT_SORT_ORDER = "name ASC";
+
+        /**
+         * The ID of the person this contact method is assigned to.
+         * <P>Type: INTEGER (long)</P>
+         */
+        public static final String PERSON_ID = "person";
+    }
+
+    /**
+     * The IM presence columns with some contacts specific columns mixed in.
+     */
+    public interface PresenceColumns extends Im.CommonPresenceColumns {
+        /**
+         * The IM service the presence is coming from. Formatted using either
+         * {@link Contacts.ContactMethods#encodePredefinedImProtocol} or
+         * {@link Contacts.ContactMethods#encodeCustomImProtocol}.
+         * <P>Type: STRING</P>
+         */
+        public static final String IM_PROTOCOL = "im_protocol";
+
+        /**
+         * The IM handle the presence item is for. The handle is scoped to
+         * the {@link #IM_PROTOCOL}.
+         * <P>Type: STRING</P>
+         */
+        public static final String IM_HANDLE = "im_handle";
+
+        /**
+         * The IM account for the local user that the presence data came from.
+         * <P>Type: STRING</P>
+         */
+        public static final String IM_ACCOUNT = "im_account";
+    }
+
+    /**
+     * Contains presence information about contacts.
+     * @hide
+     */
+    public static final class Presence
+            implements BaseColumns, PresenceColumns, PeopleColumns {
+        /**
+         * The content:// style URL for this table
+         */
+        public static final Uri CONTENT_URI =
+            Uri.parse("content://contacts/presence");
+
+        /**
+         * The ID of the person this presence item is assigned to.
+         * <P>Type: INTEGER (long)</P>
+         */
+        public static final String PERSON_ID = "person";
+
+        /**
+         * Gets the resource ID for the proper presence icon.
+         * 
+         * @param status the status to get the icon for
+         * @return the resource ID for the proper presence icon
+         */
+        public static final int getPresenceIconResourceId(int status) {
+            switch (status) {
+                case Contacts.People.AVAILABLE:
+                    return com.android.internal.R.drawable.presence_online;
+    
+                case Contacts.People.IDLE:
+                case Contacts.People.AWAY:
+                    return com.android.internal.R.drawable.presence_away;
+    
+                case Contacts.People.DO_NOT_DISTURB:
+                    return com.android.internal.R.drawable.presence_busy;
+    
+                case Contacts.People.INVISIBLE:
+                    return com.android.internal.R.drawable.presence_invisible;
+                    
+                case Contacts.People.OFFLINE:
+                default:
+                    return com.android.internal.R.drawable.presence_offline;
+            }
+        }
+
+        /**
+         * Sets a presence icon to the proper graphic
+         *
+         * @param icon the icon to to set
+         * @param serverStatus that status
+         */
+        public static final void setPresenceIcon(ImageView icon, int serverStatus) {
+            icon.setImageResource(getPresenceIconResourceId(serverStatus));
+        }
+    }
+
+    /**
+     * Columns from the Organizations table that other columns join into themselves.
+     */
+    public interface OrganizationColumns {
+        /**
+         * The type of the the phone number.
+         * <P>Type: INTEGER (one of the constants below)</P>
+         */
+        public static final String TYPE = "type";
+
+        public static final int TYPE_CUSTOM = 0;
+        public static final int TYPE_WORK = 1;
+        public static final int TYPE_OTHER = 2;
+
+        /**
+         * The user provided label, only used if TYPE is TYPE_CUSTOM.
+         * <P>Type: TEXT</P>
+         */
+        public static final String LABEL = "label";
+
+        /**
+         * The name of the company for this organization.
+         * <P>Type: TEXT</P>
+         */
+        public static final String COMPANY = "company";
+
+        /**
+         * The title within this organization.
+         * <P>Type: TEXT</P>
+         */
+        public static final String TITLE = "title";
+
+        /**
+         * The person this organization is tied to.
+         * <P>Type: TEXT</P>
+         */
+        public static final String PERSON_ID = "person";
+
+        /**
+         * Whether this is the primary organization
+         * <P>Type: INTEGER (if set, non-0 means true)</P>
+         */
+        public static final String ISPRIMARY = "isprimary";
+    }
+
+    /**
+     * A sub directory of a single person that contains all of their Phones.
+     */
+    public static final class Organizations implements BaseColumns, OrganizationColumns {
+        /**
+         * no public constructor since this is a utility class
+         */
+        private Organizations() {}
+
+        public static final CharSequence getDisplayLabel(Context context, int type,
+                CharSequence label) {
+            CharSequence display = "";
+
+            if (type != TYPE_CUSTOM) {
+                CharSequence[] labels = context.getResources().getTextArray(
+                        com.android.internal.R.array.organizationTypes);
+                try {
+                    display = labels[type - 1];
+                } catch (ArrayIndexOutOfBoundsException e) {
+                    display = labels[Organizations.TYPE_WORK - 1];
+                }
+            } else {
+                if (!TextUtils.isEmpty(label)) {
+                    display = label;
+                }
+            }
+            return display;
+        }
+
+        /**
+         * The content:// style URL for this table
+         */
+        public static final Uri CONTENT_URI =
+            Uri.parse("content://contacts/organizations");
+
+        /**
+         * The directory twig for this sub-table
+         */
+        public static final String CONTENT_DIRECTORY = "organizations";
+
+        /**
+         * The default sort order for this table
+         */
+        public static final String DEFAULT_SORT_ORDER = "company, title, isprimary ASC";
+    }
+
+    /**
+     * Columns from the Photos table that other columns join into themselves.
+     */
+    public interface PhotosColumns {
+        /**
+         * The _SYNC_VERSION of the photo that was last downloaded
+         * <P>Type: TEXT</P>
+         */
+        public static final String LOCAL_VERSION = "local_version";
+
+        /**
+         * The person this photo is associated with.
+         * <P>Type: TEXT</P>
+         */
+        public static final String PERSON_ID = "person";
+
+        /**
+         * non-zero if a download is required and the photo isn't marked as a bad resource.
+         * You must specify this in the columns in order to use it in the where clause.
+         * <P>Type: INTEGER(boolean)</P>
+         */
+        public static final String DOWNLOAD_REQUIRED = "download_required";
+
+        /**
+         * non-zero if this photo is known to exist on the server
+         * <P>Type: INTEGER(boolean)</P>
+         */
+        public static final String EXISTS_ON_SERVER = "exists_on_server";
+
+        /**
+         * Contains the description of the upload or download error from
+         * the previous attempt. If null then the previous attempt succeeded.
+         * <P>Type: TEXT</P>
+         */
+        public static final String SYNC_ERROR = "sync_error";
+
+        /**
+         * The image data, or null if there is no image.
+         * <P>Type: BLOB</P>
+         */
+        public static final String DATA = "data";
+
+    }
+
+    /**
+     * The photos over all of the people
+     */
+    public static final class Photos implements BaseColumns, PhotosColumns, SyncConstValue {
+        /**
+         * no public constructor since this is a utility class
+         */
+        private Photos() {}
+
+        /**
+         * The content:// style URL for this table
+         */
+        public static final Uri CONTENT_URI =
+            Uri.parse("content://contacts/photos");
+
+        /**
+         * The directory twig for this sub-table
+         */
+        public static final String CONTENT_DIRECTORY = "photo";
+
+        /**
+         * The default sort order for this table
+         */
+        public static final String DEFAULT_SORT_ORDER = "person ASC";
+    }
+
+    public interface ExtensionsColumns {
+        /**
+         * The name of this extension. May not be null. There may be at most one row for each name.
+         * <P>Type: TEXT</P>
+         */
+        public static final String NAME = "name";
+
+        /**
+         * The value of this extension. May not be null.
+         * <P>Type: TEXT</P>
+         */
+        public static final String VALUE = "value";
+    }
+
+    /**
+     * The extensions for a person
+     */
+    public static final class Extensions implements BaseColumns, ExtensionsColumns {
+        /**
+         * no public constructor since this is a utility class
+         */
+        private Extensions() {}
+
+        /**
+         * The content:// style URL for this table
+         */
+        public static final Uri CONTENT_URI =
+            Uri.parse("content://contacts/extensions");
+
+        /**
+         * The MIME type of {@link #CONTENT_URI} providing a directory of
+         * phones.
+         */
+        public static final String CONTENT_TYPE = "vnd.android.cursor.dir/contact_extensions";
+
+        /**
+         * The MIME type of a {@link #CONTENT_URI} subdirectory of a single
+         * phone.
+         */
+        public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/contact_extensions";
+        /**
+         * The default sort order for this table
+         */
+        public static final String DEFAULT_SORT_ORDER = "person, name ASC";
+
+        /**
+         * The ID of the person this phone number is assigned to.
+         * <P>Type: INTEGER (long)</P>
+         */
+        public static final String PERSON_ID = "person";
+    }
+
+    /**
+     * Contains helper classes used to create or manage {@link android.content.Intent Intents}
+     * that involve contacts.
+     */
+    public static final class Intents {
+        /**
+         * This is the intent that is fired when a search suggestion is clicked on.
+         */
+        public static final String SEARCH_SUGGESTION_CLICKED =
+                "android.provider.Contacts.SEARCH_SUGGESTION_CLICKED";
+
+        /**
+         * This is the intent that is fired when a search suggestion for dialing a number 
+         * is clicked on.
+         */
+        public static final String SEARCH_SUGGESTION_DIAL_NUMBER_CLICKED =
+                "android.provider.Contacts.SEARCH_SUGGESTION_DIAL_NUMBER_CLICKED";
+
+        /**
+         * This is the intent that is fired when a search suggestion for creating a contact
+         * is clicked on.
+         */
+        public static final String SEARCH_SUGGESTION_CREATE_CONTACT_CLICKED =
+                "android.provider.Contacts.SEARCH_SUGGESTION_CREATE_CONTACT_CLICKED";
+
+        /**
+         * Starts an Activity that lets the user pick a contact to attach an image to.
+         * After picking the contact it launches the image cropper in face detection mode.
+         */
+        public static final String ATTACH_IMAGE =
+                "com.android.contacts.action.ATTACH_IMAGE";
+        
+        /**
+         * Intents related to the Contacts app UI.
+         */
+        public static final class UI {
+            /**
+             * The action for the default contacts list tab.
+             */
+            public static final String LIST_DEFAULT =
+                    "com.android.contacts.action.LIST_DEFAULT";
+
+            /**
+             * The action for the contacts list tab.
+             */
+            public static final String LIST_GROUP_ACTION =
+                    "com.android.contacts.action.LIST_GROUP";
+
+            /**
+             * When in LIST_GROUP_ACTION mode, this is the group to display.
+             */
+            public static final String GROUP_NAME_EXTRA_KEY = "com.android.contacts.extra.GROUP";
+            
+            /**
+             * The action for the all contacts list tab.
+             */
+            public static final String LIST_ALL_CONTACTS_ACTION =
+                    "com.android.contacts.action.LIST_ALL_CONTACTS";
+
+            /**
+             * The action for the contacts with phone numbers list tab.
+             */
+            public static final String LIST_CONTACTS_WITH_PHONES_ACTION =
+                    "com.android.contacts.action.LIST_CONTACTS_WITH_PHONES";
+
+            /**
+             * The action for the starred contacts list tab.
+             */
+            public static final String LIST_STARRED_ACTION =
+                    "com.android.contacts.action.LIST_STARRED";
+
+            /**
+             * The action for the frequent contacts list tab.
+             */
+            public static final String LIST_FREQUENT_ACTION =
+                    "com.android.contacts.action.LIST_FREQUENT";
+
+            /**
+             * The action for the "strequent" contacts list tab. It first lists the starred
+             * contacts in alphabetical order and then the frequent contacts in descending
+             * order of the number of times they have been contacted.
+             */
+            public static final String LIST_STREQUENT_ACTION =
+                    "com.android.contacts.action.LIST_STREQUENT";
+
+            /**
+             * A key for to be used as an intent extra to set the activity
+             * title to a custom String value.
+             */
+            public static final String TITLE_EXTRA_KEY =
+                "com.android.contacts.extra.TITLE_EXTRA";
+            
+            /**
+             * Activity Action: Display a filtered list of contacts
+             * <p>
+             * Input: Extra field {@link #FILTER_TEXT_EXTRA_KEY} is the text to use for
+             * filtering
+             * <p>
+             * Output: Nothing.
+             */
+            public static final String FILTER_CONTACTS_ACTION = 
+                "com.android.contacts.action.FILTER_CONTACTS";
+            
+            /**
+             * Used as an int extra field in {@link #FILTER_CONTACTS_ACTION}
+             * intents to supply the text on which to filter.
+             */
+            public static final String FILTER_TEXT_EXTRA_KEY = 
+                "com.android.contacts.extra.FILTER_TEXT";
+        }
+
+        /**
+         * Convenience class that contains string constants used
+         * to create contact {@link android.content.Intent Intents}.
+         */
+        public static final class Insert {
+            /** The action code to use when adding a contact */
+            public static final String ACTION = Intent.ACTION_INSERT;
+
+            /**
+             * If present, forces a bypass of quick insert mode.
+             */
+            public static final String FULL_MODE = "full_mode";
+
+            /**
+             * The extra field for the contact name.
+             * <P>Type: String</P>
+             */
+            public static final String NAME = "name";
+
+            /**
+             * The extra field for the contact phonetic name.
+             * <P>Type: String</P>
+             */
+            public static final String PHONETIC_NAME = "phonetic_name";
+
+            /**
+             * The extra field for the contact company.
+             * <P>Type: String</P>
+             */
+            public static final String COMPANY = "company";
+
+            /**
+             * The extra field for the contact job title.
+             * <P>Type: String</P>
+             */
+            public static final String JOB_TITLE = "job_title";
+
+            /**
+             * The extra field for the contact notes.
+             * <P>Type: String</P>
+             */
+            public static final String NOTES = "notes";
+
+            /**
+             * The extra field for the contact phone number.
+             * <P>Type: String</P>
+             */
+            public static final String PHONE = "phone";
+
+            /**
+             * The extra field for the contact phone number type.
+             * <P>Type: Either an integer value from {@link android.provider.Contacts.PhonesColumns PhonesColumns},
+             *  or a string specifying a custom label.</P>
+             */
+            public static final String PHONE_TYPE = "phone_type";
+
+            /**
+             * The extra field for the phone isprimary flag.
+             * <P>Type: boolean</P>
+             */
+            public static final String PHONE_ISPRIMARY = "phone_isprimary";
+
+            /**
+             * The extra field for an optional second contact phone number.
+             * <P>Type: String</P>
+             */
+            public static final String SECONDARY_PHONE = "secondary_phone";
+
+            /**
+             * The extra field for an optional second contact phone number type.
+             * <P>Type: Either an integer value from {@link android.provider.Contacts.PhonesColumns PhonesColumns},
+             *  or a string specifying a custom label.</P>
+             */
+            public static final String SECONDARY_PHONE_TYPE = "secondary_phone_type";
+
+            /**
+             * The extra field for an optional third contact phone number.
+             * <P>Type: String</P>
+             */
+            public static final String TERTIARY_PHONE = "tertiary_phone";
+
+            /**
+             * The extra field for an optional third contact phone number type.
+             * <P>Type: Either an integer value from {@link android.provider.Contacts.PhonesColumns PhonesColumns},
+             *  or a string specifying a custom label.</P>
+             */
+            public static final String TERTIARY_PHONE_TYPE = "tertiary_phone_type";
+
+            /**
+             * The extra field for the contact email address.
+             * <P>Type: String</P>
+             */
+            public static final String EMAIL = "email";
+
+            /**
+             * The extra field for the contact email type.
+             * <P>Type: Either an integer value from {@link android.provider.Contacts.ContactMethodsColumns ContactMethodsColumns}
+             *  or a string specifying a custom label.</P>
+             */
+            public static final String EMAIL_TYPE = "email_type";
+
+            /**
+             * The extra field for the email isprimary flag.
+             * <P>Type: boolean</P>
+             */
+            public static final String EMAIL_ISPRIMARY = "email_isprimary";
+
+            /**
+             * The extra field for an optional second contact email address.
+             * <P>Type: String</P>
+             */
+            public static final String SECONDARY_EMAIL = "secondary_email";
+
+            /**
+             * The extra field for an optional second contact email type.
+             * <P>Type: Either an integer value from {@link android.provider.Contacts.ContactMethodsColumns ContactMethodsColumns}
+             *  or a string specifying a custom label.</P>
+             */
+            public static final String SECONDARY_EMAIL_TYPE = "secondary_email_type";
+
+            /**
+             * The extra field for an optional third contact email address.
+             * <P>Type: String</P>
+             */
+            public static final String TERTIARY_EMAIL = "tertiary_email";
+
+            /**
+             * The extra field for an optional third contact email type.
+             * <P>Type: Either an integer value from {@link android.provider.Contacts.ContactMethodsColumns ContactMethodsColumns}
+             *  or a string specifying a custom label.</P>
+             */
+            public static final String TERTIARY_EMAIL_TYPE = "tertiary_email_type";
+
+            /**
+             * The extra field for the contact postal address.
+             * <P>Type: String</P>
+             */
+            public static final String POSTAL = "postal";
+
+            /**
+             * The extra field for the contact postal address type.
+             * <P>Type: Either an integer value from {@link android.provider.Contacts.ContactMethodsColumns ContactMethodsColumns}
+             *  or a string specifying a custom label.</P>
+             */
+            public static final String POSTAL_TYPE = "postal_type";
+
+            /**
+             * The extra field for the postal isprimary flag.
+             * <P>Type: boolean</P>
+             */
+            public static final String POSTAL_ISPRIMARY = "postal_isprimary";
+
+            /**
+             * The extra field for an IM handle.
+             * <P>Type: String</P>
+             */
+            public static final String IM_HANDLE = "im_handle";
+
+            /**
+             * The extra field for the IM protocol
+             * <P>Type: the result of {@link Contacts.ContactMethods#encodePredefinedImProtocol}
+             * or {@link Contacts.ContactMethods#encodeCustomImProtocol}.</P>
+             */
+            public static final String IM_PROTOCOL = "im_protocol";
+
+            /**
+             * The extra field for the IM isprimary flag.
+             * <P>Type: boolean</P>
+             */
+            public static final String IM_ISPRIMARY = "im_isprimary";
+        }
+    }
+}
diff --git a/core/java/android/provider/Downloads.java b/core/java/android/provider/Downloads.java
new file mode 100644
index 0000000..4c58e0d
--- /dev/null
+++ b/core/java/android/provider/Downloads.java
@@ -0,0 +1,513 @@
+/*
+ * 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 android.provider;
+
+import android.net.Uri;
+
+/**
+ * Exposes constants used to interact with the download manager's
+ * content provider.
+ * The constants URI ... STATUS are the names of columns in the downloads table.
+ *
+ * @hide
+ */
+// For 1.0 the download manager can't deal with abuse from untrusted apps, so
+// this API is hidden.
+public final class Downloads implements BaseColumns {
+    private Downloads() {}
+
+    /**
+     * The permission to access the download manager
+     */
+    public static final String PERMISSION_ACCESS = "android.permission.ACCESS_DOWNLOAD_MANAGER";
+
+    /**
+     * The permission to access the download manager's advanced functions
+     */
+    public static final String PERMISSION_ACCESS_ADVANCED =
+            "android.permission.ACCESS_DOWNLOAD_MANAGER_ADVANCED";
+
+    /**
+     * The permission to directly access the download manager's cache directory
+     */
+    public static final String PERMISSION_CACHE = "android.permission.ACCESS_CACHE_FILESYSTEM";
+
+    /**
+     * The permission to send broadcasts on download completion
+     */
+    public static final String PERMISSION_SEND_INTENTS =
+            "android.permission.SEND_DOWNLOAD_COMPLETED_INTENTS";
+
+    /**
+     * The content:// URI for the data table in the provider
+     */
+    public static final Uri CONTENT_URI =
+        Uri.parse("content://downloads/download");
+
+    /**
+     * Broadcast Action: this is sent by the download manager to the app
+     * that had initiated a download when that download completes. The
+     * download's content: uri is specified in the intent's data.
+     */
+    public static final String DOWNLOAD_COMPLETED_ACTION =
+            "android.intent.action.DOWNLOAD_COMPLETED";
+
+    /**
+     * Broadcast Action: this is sent by the download manager to the app
+     * that had initiated a download when the user selects the notification
+     * associated with that download. The download's content: uri is specified
+     * in the intent's data if the click is associated with a single download,
+     * or Downloads.CONTENT_URI if the notification is associated with
+     * multiple downloads.
+     * Note: this is not currently sent for downloads that have completed
+     * successfully.
+     */
+    public static final String NOTIFICATION_CLICKED_ACTION =
+            "android.intent.action.DOWNLOAD_NOTIFICATION_CLICKED";
+
+    /**
+     * The name of the column containing the URI of the data being downloaded.
+     * <P>Type: TEXT</P>
+     * <P>Owner can Init/Read</P>
+     */
+    public static final String URI = "uri";
+
+    /**
+     * The name of the column containing application-specific data.
+     * <P>Type: TEXT</P>
+     * <P>Owner can Init/Read/Write</P>
+     */
+    public static final String APP_DATA = "entity";
+
+    /**
+     * The name of the column containing the flags that indicates whether
+     * the initiating application is capable of verifying the integrity of
+     * the downloaded file. When this flag is set, the download manager
+     * performs downloads and reports success even in some situations where
+     * it can't guarantee that the download has completed (e.g. when doing
+     * a byte-range request without an ETag, or when it can't determine
+     * whether a download fully completed).
+     * <P>Type: BOOLEAN</P>
+     * <P>Owner can Init</P>
+     */
+    public static final String NO_INTEGRITY = "no_integrity";
+
+    /**
+     * The name of the column containing the filename that the initiating
+     * application recommends. When possible, the download manager will attempt
+     * to use this filename, or a variation, as the actual name for the file.
+     * <P>Type: TEXT</P>
+     * <P>Owner can Init</P>
+     */
+    public static final String FILENAME_HINT = "hint";
+
+    /**
+     * The name of the column containing the filename where the downloaded data
+     * was actually stored.
+     * <P>Type: TEXT</P>
+     * <P>Owner can Read</P>
+     */
+    public static final String _DATA = "_data";
+
+    /**
+     * The name of the column containing the MIME type of the downloaded data.
+     * <P>Type: TEXT</P>
+     * <P>Owner can Init/Read</P>
+     */
+    public static final String MIMETYPE = "mimetype";
+
+    /**
+     * The name of the column containing the flag that controls the destination
+     * of the download. See the DESTINATION_* constants for a list of legal values.
+     * <P>Type: INTEGER</P>
+     * <P>Owner can Init</P>
+     */
+    public static final String DESTINATION = "destination";
+
+    /**
+     * The name of the column containing the flags that controls whether the
+     * download is displayed by the UI. See the VISIBILITY_* constants for
+     * a list of legal values.
+     * <P>Type: INTEGER</P>
+     * <P>Owner can Init/Read/Write</P>
+     */
+    public static final String VISIBILITY = "visibility";
+
+    /**
+     * The name of the column containing the current control state  of the download.
+     * Applications can write to this to control (pause/resume) the download.
+     * the CONTROL_* constants for a list of legal values.
+     * <P>Type: INTEGER</P>
+     * <P>Owner can Read</P>
+     */
+    public static final String CONTROL = "control";
+
+    /**
+     * The name of the column containing the current status of the download.
+     * Applications can read this to follow the progress of each download. See
+     * the STATUS_* constants for a list of legal values.
+     * <P>Type: INTEGER</P>
+     * <P>Owner can Read</P>
+     */
+    public static final String STATUS = "status";
+
+    /**
+     * The name of the column containing the date at which some interesting
+     * status changed in the download. Stored as a System.currentTimeMillis()
+     * value.
+     * <P>Type: BIGINT</P>
+     * <P>Owner can Read</P>
+     */
+    public static final String LAST_MODIFICATION = "lastmod";
+
+    /**
+     * The name of the column containing the package name of the application
+     * that initiating the download. The download manager will send
+     * notifications to a component in this package when the download completes.
+     * <P>Type: TEXT</P>
+     * <P>Owner can Init/Read</P>
+     */
+    public static final String NOTIFICATION_PACKAGE = "notificationpackage";
+
+    /**
+     * The name of the column containing the component name of the class that
+     * will receive notifications associated with the download. The
+     * package/class combination is passed to
+     * Intent.setClassName(String,String).
+     * <P>Type: TEXT</P>
+     * <P>Owner can Init/Read</P>
+     */
+    public static final String NOTIFICATION_CLASS = "notificationclass";
+
+    /**
+     * If extras are specified when requesting a download they will be provided in the intent that
+     * is sent to the specified class and package when a download has finished.
+     * <P>Type: TEXT</P>
+     * <P>Owner can Init</P>
+     */
+    public static final String NOTIFICATION_EXTRAS = "notificationextras";
+
+    /**
+     * The name of the column contain the values of the cookie to be used for
+     * the download. This is used directly as the value for the Cookie: HTTP
+     * header that gets sent with the request.
+     * <P>Type: TEXT</P>
+     * <P>Owner can Init</P>
+     */
+    public static final String COOKIE_DATA = "cookiedata";
+
+    /**
+     * The name of the column containing the user agent that the initiating
+     * application wants the download manager to use for this download.
+     * <P>Type: TEXT</P>
+     * <P>Owner can Init</P>
+     */
+    public static final String USER_AGENT = "useragent";
+
+    /**
+     * The name of the column containing the referer (sic) that the initiating
+     * application wants the download manager to use for this download.
+     * <P>Type: TEXT</P>
+     * <P>Owner can Init</P>
+     */
+    public static final String REFERER = "referer";
+
+    /**
+     * The name of the column containing the total size of the file being
+     * downloaded.
+     * <P>Type: INTEGER</P>
+     * <P>Owner can Read</P>
+     */
+    public static final String TOTAL_BYTES = "total_bytes";
+
+    /**
+     * The name of the column containing the size of the part of the file that
+     * has been downloaded so far.
+     * <P>Type: INTEGER</P>
+     * <P>Owner can Read</P>
+     */
+    public static final String CURRENT_BYTES = "current_bytes";
+
+    /**
+     * The name of the column where the initiating application can provide the
+     * UID of another application that is allowed to access this download. If
+     * multiple applications share the same UID, all those applications will be
+     * allowed to access this download. This column can be updated after the
+     * download is initiated. This requires the permission
+     * android.permission.ACCESS_DOWNLOAD_MANAGER_ADVANCED.
+     * <P>Type: INTEGER</P>
+     * <P>Owner can Init</P>
+     */
+    public static final String OTHER_UID = "otheruid";
+
+    /**
+     * The name of the column where the initiating application can provided the
+     * title of this download. The title will be displayed ito the user in the
+     * list of downloads.
+     * <P>Type: TEXT</P>
+     * <P>Owner can Init/Read/Write</P>
+     */
+    public static final String TITLE = "title";
+
+    /**
+     * The name of the column where the initiating application can provide the
+     * description of this download. The description will be displayed to the
+     * user in the list of downloads.
+     * <P>Type: TEXT</P>
+     * <P>Owner can Init/Read/Write</P>
+     */
+    public static final String DESCRIPTION = "description";
+
+    /*
+     * Lists the destinations that an application can specify for a download.
+     */
+
+    /**
+     * This download will be saved to the external storage. This is the
+     * default behavior, and should be used for any file that the user
+     * can freely access, copy, delete. Even with that destination,
+     * unencrypted DRM files are saved in secure internal storage.
+     * Downloads to the external destination only write files for which
+     * there is a registered handler. The resulting files are accessible
+     * by filename to all applications.
+     */
+    public static final int DESTINATION_EXTERNAL = 0;
+
+    /**
+     * This download will be saved to the download manager's private
+     * partition. This is the behavior used by applications that want to
+     * download private files that are used and deleted soon after they
+     * get downloaded. All file types are allowed, and only the initiating
+     * application can access the file (indirectly through a content
+     * provider). This requires the
+     * android.permission.ACCESS_DOWNLOAD_MANAGER_ADVANCED permission.
+     */
+    public static final int DESTINATION_CACHE_PARTITION = 1;
+
+    /**
+     * This download will be saved to the download manager's private
+     * partition and will be purged as necessary to make space. This is
+     * for private files (similar to CACHE_PARTITION) that aren't deleted
+     * immediately after they are used, and are kept around by the download
+     * manager as long as space is available.
+     */
+    public static final int DESTINATION_CACHE_PARTITION_PURGEABLE = 2;
+
+    /**
+     * This download will be saved to the download manager's private
+     * partition, as with DESTINATION_CACHE_PARTITION, but the download
+     * will not proceed if the user is on a roaming data connection.
+     */
+    public static final int DESTINATION_CACHE_PARTITION_NOROAMING = 3;
+
+    /**
+     * This download is allowed to run.
+     */
+    public static final int CONTROL_RUN = 0;
+
+    /**
+     * This download must pause at the first opportunity.
+     */
+    public static final int CONTROL_PAUSED = 1;
+
+    /*
+     * Lists the states that the download manager can set on a download
+     * to notify applications of the download progress.
+     * The codes follow the HTTP families:<br>
+     * 1xx: informational<br>
+     * 2xx: success<br>
+     * 3xx: redirects (not used by the download manager)<br>
+     * 4xx: client errors<br>
+     * 5xx: server errors
+     */
+
+    /**
+     * Returns whether the status is informational (i.e. 1xx).
+     */
+    public static boolean isStatusInformational(int status) {
+        return (status >= 100 && status < 200);
+    }
+
+    /**
+     * Returns whether the download is suspended. (i.e. whether the download
+     * won't complete without some action from outside the download
+     * manager).
+     */
+    public static boolean isStatusSuspended(int status) {
+        return (status == STATUS_PENDING_PAUSED || status == STATUS_RUNNING_PAUSED);
+    }
+
+    /**
+     * Returns whether the status is a success (i.e. 2xx).
+     */
+    public static boolean isStatusSuccess(int status) {
+        return (status >= 200 && status < 300);
+    }
+
+    /**
+     * Returns whether the status is an error (i.e. 4xx or 5xx).
+     */
+    public static boolean isStatusError(int status) {
+        return (status >= 400 && status < 600);
+    }
+
+    /**
+     * Returns whether the status is a client error (i.e. 4xx).
+     */
+    public static boolean isStatusClientError(int status) {
+        return (status >= 400 && status < 500);
+    }
+
+    /**
+     * Returns whether the status is a server error (i.e. 5xx).
+     */
+    public static boolean isStatusServerError(int status) {
+        return (status >= 500 && status < 600);
+    }
+
+    /**
+     * Returns whether the download has completed (either with success or
+     * error).
+     */
+    public static boolean isStatusCompleted(int status) {
+        return (status >= 200 && status < 300) || (status >= 400 && status < 600);
+    }
+
+    /**
+     * This download hasn't stated yet
+     */
+    public static final int STATUS_PENDING = 190;
+
+    /**
+     * This download hasn't stated yet and is paused
+     */
+    public static final int STATUS_PENDING_PAUSED = 191;
+
+    /**
+     * This download has started
+     */
+    public static final int STATUS_RUNNING = 192;
+
+    /**
+     * This download has started and is paused
+     */
+    public static final int STATUS_RUNNING_PAUSED = 193;
+
+    /**
+     * This download has successfully completed.
+     * Warning: there might be other status values that indicate success
+     * in the future.
+     * Use isSucccess() to capture the entire category.
+     */
+    public static final int STATUS_SUCCESS = 200;
+
+    /**
+     * This request couldn't be parsed. This is also used when processing
+     * requests with unknown/unsupported URI schemes.
+     */
+    public static final int STATUS_BAD_REQUEST = 400;
+
+    /**
+     * This download can't be performed because the content type cannot be
+     * handled.
+     */
+    public static final int STATUS_NOT_ACCEPTABLE = 406;
+
+    /**
+     * This download cannot be performed because the length cannot be
+     * determined accurately. This is the code for the HTTP error "Length
+     * Required", which is typically used when making requests that require
+     * a content length but don't have one, and it is also used in the
+     * client when a response is received whose length cannot be determined
+     * accurately (therefore making it impossible to know when a download
+     * completes).
+     */
+    public static final int STATUS_LENGTH_REQUIRED = 411;
+
+    /**
+     * This download was interrupted and cannot be resumed.
+     * This is the code for the HTTP error "Precondition Failed", and it is
+     * also used in situations where the client doesn't have an ETag at all.
+     */
+    public static final int STATUS_PRECONDITION_FAILED = 412;
+
+    /**
+     * This download was canceled
+     */
+    public static final int STATUS_CANCELED = 490;
+
+    /**
+     * This download has completed with an error.
+     * Warning: there will be other status values that indicate errors in
+     * the future. Use isStatusError() to capture the entire category.
+     */
+    public static final int STATUS_UNKNOWN_ERROR = 491;
+
+    /**
+     * This download couldn't be completed because of a storage issue.
+     * Typically, that's because the filesystem is missing or full.
+     */
+    public static final int STATUS_FILE_ERROR = 492;
+
+    /**
+     * This download couldn't be completed because of an HTTP
+     * redirect response that the download manager couldn't
+     * handle.
+     */
+    public static final int STATUS_UNHANDLED_REDIRECT = 493;
+
+    /**
+     * This download couldn't be completed because of an
+     * unspecified unhandled HTTP code.
+     */
+    public static final int STATUS_UNHANDLED_HTTP_CODE = 494;
+
+    /**
+     * This download couldn't be completed because of an
+     * error receiving or processing data at the HTTP level.
+     */
+    public static final int STATUS_HTTP_DATA_ERROR = 495;
+
+    /**
+     * This download couldn't be completed because of an
+     * HttpException while setting up the request.
+     */
+    public static final int STATUS_HTTP_EXCEPTION = 496;
+
+    /**
+     * This download couldn't be completed because there were
+     * too many redirects.
+     */
+    public static final int STATUS_TOO_MANY_REDIRECTS = 497;
+
+    /**
+     * This download is visible but only shows in the notifications
+     * while it's in progress.
+     */
+    public static final int VISIBILITY_VISIBLE = 0;
+
+    /**
+     * This download is visible and shows in the notifications while
+     * in progress and after completion.
+     */
+    public static final int VISIBILITY_VISIBLE_NOTIFY_COMPLETED = 1;
+
+    /**
+     * This download doesn't show in the UI or in the notifications.
+     */
+    public static final int VISIBILITY_HIDDEN = 2;
+}
diff --git a/core/java/android/provider/DrmStore.java b/core/java/android/provider/DrmStore.java
new file mode 100644
index 0000000..db71854
--- /dev/null
+++ b/core/java/android/provider/DrmStore.java
@@ -0,0 +1,185 @@
+/*
+ * 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 android.provider;
+
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.content.Context;
+import android.drm.mobile1.DrmRawContent;
+import android.drm.mobile1.DrmRights;
+import android.drm.mobile1.DrmRightsManager;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.net.Uri;
+import android.util.Log;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.InputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+
+/**
+ * The DRM provider contains forward locked DRM content.
+ * 
+ * @hide
+ */
+public final class DrmStore
+{
+    private static final String TAG = "DrmStore";
+
+    public static final String AUTHORITY = "drm";
+    
+    /**
+     * This is in the Manifest class of the drm provider, but that isn't visible
+     * in the framework.
+     */
+    private static final String ACCESS_DRM_PERMISSION = "android.permission.ACCESS_DRM";
+    
+    /**
+     * Fields for DRM database
+     */
+
+    public interface Columns extends BaseColumns {
+        /**
+         * The data stream for the file
+         * <P>Type: DATA STREAM</P>
+         */
+        public static final String DATA = "_data";
+
+        /**
+         * The size of the file in bytes
+         * <P>Type: INTEGER (long)</P>
+         */
+        public static final String SIZE = "_size";
+
+        /**
+         * The title of the file content
+         * <P>Type: TEXT</P>
+         */
+        public static final String TITLE = "title";
+
+        /**
+         * The MIME type of the file
+         * <P>Type: TEXT</P>
+         */
+        public static final String MIME_TYPE = "mime_type";
+
+    }
+
+    public interface Images extends Columns {
+     
+        public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/images");
+    }
+     
+    public interface Audio extends Columns {
+     
+        public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/audio");
+    }
+
+    /**
+     * Utility function for inserting a file into the DRM content provider.
+     * 
+     * @param cr The content resolver to use
+     * @param file The file to insert
+     * @param title The title for the content (or null)
+     * @return uri to the DRM record or null
+     */
+    public static final Intent addDrmFile(ContentResolver cr, File file, String title) {
+        FileInputStream fis = null;
+        OutputStream os = null;
+        Intent result = null;
+
+        try {
+            fis = new FileInputStream(file);
+            DrmRawContent content = new DrmRawContent(fis, (int) file.length(),
+                    DrmRawContent.DRM_MIMETYPE_MESSAGE_STRING);
+            String mimeType = content.getContentType();
+
+            DrmRightsManager manager = manager = DrmRightsManager.getInstance();
+            DrmRights rights = manager.queryRights(content);
+            InputStream stream = content.getContentInputStream(rights);
+            long size = stream.available();
+
+            Uri contentUri = null;
+            if (mimeType.startsWith("audio/")) {
+                contentUri = DrmStore.Audio.CONTENT_URI;
+            } else if (mimeType.startsWith("image/")) {
+                contentUri = DrmStore.Images.CONTENT_URI;
+            } else {
+                Log.w(TAG, "unsupported mime type " + mimeType);
+            }
+
+            if (contentUri != null) {
+                ContentValues values = new ContentValues(3);
+                // compute title from file name, if it is not specified
+                if (title == null) {
+                    title = file.getName();
+                    int lastDot = title.lastIndexOf('.');
+                    if (lastDot > 0) {
+                        title = title.substring(0, lastDot);
+                    }
+                }
+                values.put(DrmStore.Columns.TITLE, title);
+                values.put(DrmStore.Columns.SIZE, size);
+                values.put(DrmStore.Columns.MIME_TYPE, mimeType);
+
+                Uri uri = cr.insert(contentUri, values);
+                if (uri != null) {
+                    os = cr.openOutputStream(uri);
+
+                    byte[] buffer = new byte[1000];
+                    int count;
+
+                    while ((count = stream.read(buffer)) != -1) {
+                        os.write(buffer, 0, count);
+                    }
+                    result = new Intent();
+                    result.setDataAndType(uri, mimeType);
+
+                }
+            }
+        } catch (Exception e) {
+            Log.e(TAG, "pushing file failed", e);
+        } finally {
+            try {
+                if (fis != null)
+                    fis.close();
+                if (os != null)
+                    os.close();
+            } catch (IOException e) {
+                Log.e(TAG, "IOException in DrmTest.onCreate()", e);
+            }
+        }
+
+        return result;
+    }
+
+    /**
+     * Utility function to enforce any permissions required to access DRM
+     * content.
+     * 
+     * @param context A context used for checking calling permission.
+     */
+    public static void enforceAccessDrmPermission(Context context) {
+        if (context.checkCallingOrSelfPermission(ACCESS_DRM_PERMISSION) !=
+                PackageManager.PERMISSION_GRANTED) {
+            throw new SecurityException("Requires DRM permission");
+        }
+    }
+     
+}
diff --git a/core/java/android/provider/Gmail.java b/core/java/android/provider/Gmail.java
new file mode 100644
index 0000000..5b3c223
--- /dev/null
+++ b/core/java/android/provider/Gmail.java
@@ -0,0 +1,2453 @@
+/*
+ * Copyright (C) 2007 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 android.provider;
+
+import com.google.android.collect.Lists;
+import com.google.android.collect.Maps;
+import com.google.android.collect.Sets;
+
+import android.content.AsyncQueryHandler;
+import android.content.ContentQueryMap;
+import android.content.ContentResolver;
+import android.content.ContentUris;
+import android.content.ContentValues;
+import android.database.ContentObserver;
+import android.database.Cursor;
+import android.database.DataSetObserver;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Handler;
+import android.text.Html;
+import android.text.SpannableStringBuilder;
+import android.text.Spanned;
+import android.text.TextUtils;
+import android.text.TextUtils.SimpleStringSplitter;
+import android.text.style.CharacterStyle;
+import android.text.util.Regex;
+import android.util.Config;
+import android.util.Log;
+
+import java.io.UnsupportedEncodingException;
+import java.net.URLEncoder;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Observable;
+import java.util.Observer;
+import java.util.Set;
+import java.util.SortedSet;
+import java.util.TreeSet;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * A thin wrapper over the content resolver for accessing the gmail provider.
+ *
+ * @hide
+ */
+public final class Gmail {
+    public static final String GMAIL_AUTH_SERVICE = "mail";
+    // These constants come from google3/java/com/google/caribou/backend/MailLabel.java.
+    public static final String LABEL_SENT = "^f";
+    public static final String LABEL_INBOX = "^i";
+    public static final String LABEL_DRAFT = "^r";
+    public static final String LABEL_UNREAD = "^u";
+    public static final String LABEL_TRASH = "^k";
+    public static final String LABEL_SPAM = "^s";
+    public static final String LABEL_STARRED = "^t";
+    public static final String LABEL_CHAT = "^b"; // 'b' for 'buzz'
+    public static final String LABEL_VOICEMAIL = "^vm";
+    public static final String LABEL_IGNORED = "^g";
+    public static final String LABEL_ALL = "^all";
+    // These constants (starting with "^^") are only used locally and are not understood by the
+    // server.
+    public static final String LABEL_VOICEMAIL_INBOX = "^^vmi";
+    public static final String LABEL_CACHED = "^^cached";
+    public static final String LABEL_OUTBOX = "^^out";
+
+    public static final String AUTHORITY = "gmail-ls";
+    private static final String TAG = "gmail-ls";
+    private static final String AUTHORITY_PLUS_CONVERSATIONS =
+            "content://" + AUTHORITY + "/conversations/";
+    private static final String AUTHORITY_PLUS_LABELS =
+            "content://" + AUTHORITY + "/labels/";
+    private static final String AUTHORITY_PLUS_MESSAGES =
+            "content://" + AUTHORITY + "/messages/";
+    private static final String AUTHORITY_PLUS_SETTINGS =
+            "content://" + AUTHORITY + "/settings/";
+
+    public static final Uri BASE_URI = Uri.parse(
+            "content://" + AUTHORITY);
+    private static final Uri LABELS_URI =
+            Uri.parse(AUTHORITY_PLUS_LABELS);
+    private static final Uri CONVERSATIONS_URI =
+            Uri.parse(AUTHORITY_PLUS_CONVERSATIONS);
+    private static final Uri SETTINGS_URI =
+            Uri.parse(AUTHORITY_PLUS_SETTINGS);
+
+    /** Separates email addresses in strings in the database. */
+    public static final String EMAIL_SEPARATOR = "\n";
+    public static final Pattern EMAIL_SEPARATOR_PATTERN = Pattern.compile(EMAIL_SEPARATOR);
+
+    /**
+     * Space-separated lists have separators only between items.
+     */
+    private static final char SPACE_SEPARATOR = ' ';
+    public static final Pattern SPACE_SEPARATOR_PATTERN = Pattern.compile(" ");
+
+    /**
+     * Comma-separated lists have separators between each item, before the first and after the last
+     * item. The empty list is <tt>,</tt>.
+     *
+     * <p>This makes them easier to modify with SQL since it is not a special case to add or
+     * remove the last item. Having a separator on each side of each value also makes it safe to use
+     * SQL's REPLACE to remove an item from a string by using REPLACE(',value,', ',').
+     *
+     * <p>We could use the same separator for both lists but this makes it easier to remember which
+     * kind of list one is dealing with.
+     */
+    private static final char COMMA_SEPARATOR = ',';
+    public static final Pattern COMMA_SEPARATOR_PATTERN = Pattern.compile(",");
+
+    /** Separates attachment info parts in strings in the database. */
+    public static final String ATTACHMENT_INFO_SEPARATOR = "\n";
+    public static final Pattern ATTACHMENT_INFO_SEPARATOR_PATTERN =
+            Pattern.compile(ATTACHMENT_INFO_SEPARATOR);
+
+    public static final Character SENDER_LIST_SEPARATOR = '\n';
+    public static final String SENDER_LIST_TOKEN_ELIDED = "e";
+    public static final String SENDER_LIST_TOKEN_NUM_MESSAGES = "n";
+    public static final String SENDER_LIST_TOKEN_NUM_DRAFTS = "d";
+    public static final String SENDER_LIST_TOKEN_LITERAL = "l";
+    public static final String SENDER_LIST_TOKEN_SENDING = "s";
+    public static final String SENDER_LIST_TOKEN_SEND_FAILED = "f";
+
+    /** Used for finding status in a cursor's extras. */
+    public static final String EXTRA_STATUS = "status";
+
+    public static final String RESPOND_INPUT_COMMAND = "command";
+    public static final String COMMAND_RETRY = "retry";
+    public static final String COMMAND_ACTIVATE = "activate";
+    public static final String COMMAND_SET_VISIBLE = "setVisible";
+    public static final String SET_VISIBLE_PARAM_VISIBLE = "visible";
+    public static final String RESPOND_OUTPUT_COMMAND_RESPONSE = "commandResponse";
+    public static final String COMMAND_RESPONSE_OK =  "ok";
+    public static final String COMMAND_RESPONSE_UNKNOWN =  "unknownCommand";
+
+    public static final String INSERT_PARAM_ATTACHMENT_ORIGIN = "origin";
+    public static final String INSERT_PARAM_ATTACHMENT_ORIGIN_EXTRAS = "originExtras";
+
+    private static final Pattern NAME_ADDRESS_PATTERN = Pattern.compile("\"(.*)\"");
+    private static final Pattern UNNAMED_ADDRESS_PATTERN = Pattern.compile("([^<]+)@");
+
+    private static final Map<Integer, Integer> sPriorityToLength = Maps.newHashMap();
+    public static final SimpleStringSplitter sSenderListSplitter = 
+            new SimpleStringSplitter(SENDER_LIST_SEPARATOR);
+    public static String[] sSenderFragments = new String[8];
+
+    /**
+     * Returns the name in an address string
+     * @param addressString such as &quot;bobby&quot; &lt;bob@example.com&gt;
+     * @return returns the quoted name in the addressString, otherwise the username from the email
+     *   address
+     */
+    public static String getNameFromAddressString(String addressString) {
+        Matcher namedAddressMatch = NAME_ADDRESS_PATTERN.matcher(addressString);
+        if (namedAddressMatch.find()) {
+            String name = namedAddressMatch.group(1);
+            if (name.length() > 0) return name;
+            addressString =
+                    addressString.substring(namedAddressMatch.end(), addressString.length());
+        }
+
+        Matcher unnamedAddressMatch = UNNAMED_ADDRESS_PATTERN.matcher(addressString);
+        if (unnamedAddressMatch.find()) {
+            return unnamedAddressMatch.group(1);
+        }
+
+        return addressString;
+    }
+
+    /**
+     * Returns the email address in an address string
+     * @param addressString such as &quot;bobby&quot; &lt;bob@example.com&gt;
+     * @return returns the email address, such as bob@example.com from the example above
+     */
+    public static String getEmailFromAddressString(String addressString) {
+        String result = addressString;
+        Matcher match = Regex.EMAIL_ADDRESS_PATTERN.matcher(addressString);
+        if (match.find()) {
+            result = addressString.substring(match.start(), match.end());
+        }
+
+        return result;
+    }
+
+    /**
+     * Returns whether the label is user-defined (versus system-defined labels such as inbox, whose
+     * names start with "^").
+     */
+    public static boolean isLabelUserDefined(String label) {
+        // TODO: label should never be empty so we should be able to say [label.charAt(0) != '^'].
+        // However, it's a release week and I'm too scared to make that change.
+        return !label.startsWith("^");
+    }
+
+    private static final Set<String> USER_SETTABLE_BUILTIN_LABELS = Sets.newHashSet(
+            Gmail.LABEL_INBOX,
+            Gmail.LABEL_UNREAD,
+            Gmail.LABEL_TRASH,
+            Gmail.LABEL_SPAM,
+            Gmail.LABEL_STARRED,
+            Gmail.LABEL_IGNORED);
+
+    /**
+     * Returns whether the label is user-settable. For example, labels such as LABEL_DRAFT should
+     * only be set internally.
+     */
+    public static boolean isLabelUserSettable(String label) {
+        return USER_SETTABLE_BUILTIN_LABELS.contains(label) || isLabelUserDefined(label);
+    }
+
+    /**
+     * Returns the set of labels using the raw labels from a previous getRawLabels()
+     * as input.
+     * @return a copy of the set of labels. To add or remove labels call
+     * MessageCursor.addOrRemoveLabel on each message in the conversation.
+     */
+    public static Set<Long> getLabelIdsFromLabelIdsString(
+            TextUtils.StringSplitter splitter) {
+        Set<Long> labelIds = Sets.newHashSet();
+        for (String labelIdString : splitter) {
+            labelIds.add(Long.valueOf(labelIdString));
+        }
+        return labelIds;
+    }
+
+    /**
+     * @deprecated remove when the activities stop using canonical names to identify labels
+     */
+    public static Set<String> getCanonicalNamesFromLabelIdsString(
+            LabelMap labelMap, TextUtils.StringSplitter splitter) {
+        Set<String> canonicalNames = Sets.newHashSet();
+        for (long labelId : getLabelIdsFromLabelIdsString(splitter)) {
+            final String canonicalName = labelMap.getCanonicalName(labelId);
+            // We will sometimes see labels that the label map does not yet know about or that
+            // do not have names yet.
+            if (!TextUtils.isEmpty(canonicalName)) {
+                canonicalNames.add(canonicalName);
+            } else {
+                Log.w(TAG, "getCanonicalNamesFromLabelIdsString skipping label id: " + labelId);
+            }
+        }
+        return canonicalNames;
+    }
+
+    /**
+     * @return a StringSplitter that is configured to split message label id strings
+     */
+    public static TextUtils.StringSplitter newMessageLabelIdsSplitter() {
+        return new TextUtils.SimpleStringSplitter(SPACE_SEPARATOR);
+    }
+
+    /**
+     * @return a StringSplitter that is configured to split conversation label id strings
+     */
+    public static TextUtils.StringSplitter newConversationLabelIdsSplitter() {
+        return new CommaStringSplitter();
+    }
+
+    /**
+     * A splitter for strings of the form described in the docs for COMMA_SEPARATOR.
+     */
+    private static class CommaStringSplitter extends TextUtils.SimpleStringSplitter {
+
+        public CommaStringSplitter() {
+            super(COMMA_SEPARATOR);
+        }
+
+        @Override
+        public void setString(String string) {
+            // The string should always be at least a single comma.
+            super.setString(string.substring(1));
+        }
+    }
+
+    /**
+     * Creates a single string of the form that getLabelIdsFromLabelIdsString can split.
+     */
+    public static String getLabelIdsStringFromLabelIds(Set<Long> labelIds) {
+        StringBuilder sb = new StringBuilder();
+        sb.append(COMMA_SEPARATOR);
+        for (Long labelId : labelIds) {
+            sb.append(labelId);
+            sb.append(COMMA_SEPARATOR);
+        }
+        return sb.toString();
+    }
+
+    public static final class ConversationColumns {
+        public static final String ID = "_id";
+        public static final String SUBJECT = "subject";
+        public static final String SNIPPET = "snippet";
+        public static final String FROM = "fromAddress";
+        public static final String DATE = "date";
+        public static final String PERSONAL_LEVEL = "personalLevel";
+        /** A list of label names with a space after each one (including the last one). This makes
+         * it easier remove individual labels from this list using SQL. */
+        public static final String LABEL_IDS = "labelIds";
+        public static final String NUM_MESSAGES = "numMessages";
+        public static final String MAX_MESSAGE_ID = "maxMessageId";
+        public static final String HAS_ATTACHMENTS = "hasAttachments";
+        public static final String HAS_MESSAGES_WITH_ERRORS = "hasMessagesWithErrors";
+        public static final String FORCE_ALL_UNREAD = "forceAllUnread";
+
+        private ConversationColumns() {}
+    }
+
+    public static final class MessageColumns {
+
+        public static final String ID = "_id";
+        public static final String MESSAGE_ID = "messageId";
+        public static final String CONVERSATION_ID = "conversation";
+        public static final String SUBJECT = "subject";
+        public static final String SNIPPET = "snippet";
+        public static final String FROM = "fromAddress";
+        public static final String TO = "toAddresses";
+        public static final String CC = "ccAddresses";
+        public static final String BCC = "bccAddresses";
+        public static final String REPLY_TO = "replyToAddresses";
+        public static final String DATE_SENT_MS = "dateSentMs";
+        public static final String DATE_RECEIVED_MS = "dateReceivedMs";
+        public static final String LIST_INFO = "listInfo";
+        public static final String PERSONAL_LEVEL = "personalLevel";
+        public static final String BODY = "body";
+        public static final String EMBEDS_EXTERNAL_RESOURCES = "bodyEmbedsExternalResources";
+        public static final String LABEL_IDS = "labelIds";
+        public static final String JOINED_ATTACHMENT_INFOS = "joinedAttachmentInfos";
+        public static final String ERROR = "error";
+        // TODO: add a method for accessing this
+        public static final String REF_MESSAGE_ID = "refMessageId";
+
+        // Fake columns used only for saving or sending messages.
+        public static final String FAKE_SAVE = "save";
+        public static final String FAKE_REF_MESSAGE_ID = "refMessageId";
+
+        private MessageColumns() {}
+    }
+
+    public static final class LabelColumns {
+        public static final String CANONICAL_NAME = "canonicalName";
+        public static final String NAME = "name";
+        public static final String NUM_CONVERSATIONS = "numConversations";
+        public static final String NUM_UNREAD_CONVERSATIONS =
+                "numUnreadConversations";
+
+        private LabelColumns() {}
+    }
+
+    public static final class SettingsColumns {
+        public static final String LABELS_INCLUDED = "labelsIncluded";
+        public static final String LABELS_PARTIAL = "labelsPartial";
+        public static final String CONVERSATION_AGE_DAYS =
+                "conversationAgeDays";
+        public static final String MAX_ATTACHMENET_SIZE_MB =
+                "maxAttachmentSize";
+    }
+
+    /**
+     * These flags can be included as Selection Arguments when
+     * querying the provider.
+     */
+    public static class SelectionArguments {
+        private SelectionArguments() {
+            // forbid instantiation
+        }
+
+        /**
+         * Specifies that you do NOT wish the returned cursor to
+         * become the Active Network Cursor.  If you do not include
+         * this flag as a selectionArg, the new cursor will become the
+         * Active Network Cursor by default.
+         */
+        public static final String DO_NOT_BECOME_ACTIVE_NETWORK_CURSOR =
+                "SELECTION_ARGUMENT_DO_NOT_BECOME_ACTIVE_NETWORK_CURSOR";
+    }
+
+    // These are the projections that we need when getting cursors from the
+    // content provider.
+    private static String[] CONVERSATION_PROJECTION = {
+            ConversationColumns.ID,
+            ConversationColumns.SUBJECT,
+            ConversationColumns.SNIPPET,
+            ConversationColumns.FROM,
+            ConversationColumns.DATE,
+            ConversationColumns.PERSONAL_LEVEL,
+            ConversationColumns.LABEL_IDS,
+            ConversationColumns.NUM_MESSAGES,
+            ConversationColumns.MAX_MESSAGE_ID,
+            ConversationColumns.HAS_ATTACHMENTS,
+            ConversationColumns.HAS_MESSAGES_WITH_ERRORS,
+            ConversationColumns.FORCE_ALL_UNREAD};
+    private static String[] MESSAGE_PROJECTION = {
+            MessageColumns.ID,
+            MessageColumns.MESSAGE_ID,
+            MessageColumns.CONVERSATION_ID,
+            MessageColumns.SUBJECT,
+            MessageColumns.SNIPPET,
+            MessageColumns.FROM,
+            MessageColumns.TO,
+            MessageColumns.CC,
+            MessageColumns.BCC,
+            MessageColumns.REPLY_TO,
+            MessageColumns.DATE_SENT_MS,
+            MessageColumns.DATE_RECEIVED_MS,
+            MessageColumns.LIST_INFO,
+            MessageColumns.PERSONAL_LEVEL,
+            MessageColumns.BODY,
+            MessageColumns.EMBEDS_EXTERNAL_RESOURCES,
+            MessageColumns.LABEL_IDS,
+            MessageColumns.JOINED_ATTACHMENT_INFOS,
+            MessageColumns.ERROR};
+    private static String[] LABEL_PROJECTION = {
+            BaseColumns._ID,
+            LabelColumns.CANONICAL_NAME,
+            LabelColumns.NAME,
+            LabelColumns.NUM_CONVERSATIONS,
+            LabelColumns.NUM_UNREAD_CONVERSATIONS};
+    private static String[] SETTINGS_PROJECTION = {
+            SettingsColumns.LABELS_INCLUDED,
+            SettingsColumns.LABELS_PARTIAL,
+            SettingsColumns.CONVERSATION_AGE_DAYS,
+            SettingsColumns.MAX_ATTACHMENET_SIZE_MB,
+    };
+
+    private ContentResolver mContentResolver;
+
+    public Gmail(ContentResolver contentResolver) {
+        mContentResolver = contentResolver;
+    }
+
+    /**
+     * Returns source if source is non-null. Returns the empty string otherwise.
+     */
+    private static String toNonnullString(String source) {
+        if (source == null) {
+            return "";
+        } else {
+            return source;
+        }
+    }
+
+    /**
+     * Behavior for a new cursor: should it become the Active Network
+     * Cursor?  This could potentially lead to bad behavior if someone
+     * else is using the Active Network Cursor, since theirs will stop
+     * being the Active Network Cursor.
+     */
+    public static enum BecomeActiveNetworkCursor {
+        /**
+         * The new cursor should become the one and only Active
+         * Network Cursor.  Any other cursor that might already be the
+         * Active Network Cursor will cease to be so.
+         */
+        YES,
+
+        /**
+         * The new cursor should not become the Active Network
+         * Cursor. Any other cursor that might already be the Active
+         * Network Cursor will continue to be so.
+         */
+        NO
+    }
+
+    /**
+     * Wraps a Cursor in a ConversationCursor
+     *
+     * @param account the account the cursor is associated with
+     * @param cursor The Cursor to wrap
+     * @return a new ConversationCursor
+     */
+    public ConversationCursor getConversationCursorForCursor(String account, Cursor cursor) {
+        if (TextUtils.isEmpty(account)) {
+            throw new IllegalArgumentException("account is empty");
+        }
+        return new ConversationCursor(this, account, cursor);
+    }
+
+    /**
+     * Creates an array of SelectionArguments suitable for passing to the provider's query.
+     * Currently this only handles one flag, but it could be expanded in the future.
+     */
+    private static String[] getSelectionArguments(
+            BecomeActiveNetworkCursor becomeActiveNetworkCursor) {
+        if (BecomeActiveNetworkCursor.NO == becomeActiveNetworkCursor) {
+            return new String[] {SelectionArguments.DO_NOT_BECOME_ACTIVE_NETWORK_CURSOR};
+        } else {
+            // Default behavior; no args required.
+            return null;
+        }
+    }
+
+    /**
+     * Asynchronously gets a cursor over all conversations matching a query. The
+     * query is in Gmail's query syntax. When the operation is complete the handler's
+     * onQueryComplete() method is called with the resulting Cursor.
+     *
+     * @param account run the query on this account
+     * @param handler An AsyncQueryHanlder that will be used to run the query
+     * @param token The token to pass to startQuery, which will be passed back to onQueryComplete
+     * @param query a query in Gmail's query syntax
+     * @param becomeActiveNetworkCursor whether or not the returned
+     * cursor should become the Active Network Cursor
+     */
+    public void runQueryForConversations(String account, AsyncQueryHandler handler, int token,
+            String query, BecomeActiveNetworkCursor becomeActiveNetworkCursor) {
+        if (TextUtils.isEmpty(account)) {
+            throw new IllegalArgumentException("account is empty");
+        }
+        String[] selectionArgs = getSelectionArguments(becomeActiveNetworkCursor);
+        handler.startQuery(token, null, Uri.withAppendedPath(CONVERSATIONS_URI, account),
+                CONVERSATION_PROJECTION, query, selectionArgs, null);
+    }
+
+    /**
+     * Synchronously gets a cursor over all conversations matching a query. The
+     * query is in Gmail's query syntax.
+     *
+     * @param account run the query on this account
+     * @param query a query in Gmail's query syntax
+     * @param becomeActiveNetworkCursor whether or not the returned
+     * cursor should become the Active Network Cursor
+     */
+    public ConversationCursor getConversationCursorForQuery(
+            String account, String query, BecomeActiveNetworkCursor becomeActiveNetworkCursor) {
+        String[] selectionArgs = getSelectionArguments(becomeActiveNetworkCursor);
+        Cursor cursor = mContentResolver.query(
+                Uri.withAppendedPath(CONVERSATIONS_URI, account), CONVERSATION_PROJECTION,
+                query, selectionArgs, null);
+        return new ConversationCursor(this, account, cursor);
+    }
+
+    /**
+     * Gets a message cursor over the single message with the given id.
+     *
+     * @param account get the cursor for messages in this account
+     * @param messageId the id of the message
+     * @return a cursor over the message
+     */
+    public MessageCursor getMessageCursorForMessageId(String account, long messageId) {
+        if (TextUtils.isEmpty(account)) {
+            throw new IllegalArgumentException("account is empty");
+        }
+        Uri uri = Uri.parse(AUTHORITY_PLUS_MESSAGES + account + "/" + messageId);
+        Cursor cursor = mContentResolver.query(uri, MESSAGE_PROJECTION, null, null, null);
+        return new MessageCursor(this, mContentResolver, account, cursor);
+    }
+
+    /**
+     * Gets a message cursor over the messages that match the query. Note that
+     * this simply finds all of the messages that match and returns them. It
+     * does not return all messages in conversations where any message matches.
+     *
+     * @param account get the cursor for messages in this account
+     * @param query a query in GMail's query syntax. Currently only queries of
+     *     the form [label:<label>] are supported
+     * @return a cursor over the messages
+     */
+    public MessageCursor getLocalMessageCursorForQuery(String account, String query) {
+        if (TextUtils.isEmpty(account)) {
+            throw new IllegalArgumentException("account is empty");
+        }
+        Uri uri = Uri.parse(AUTHORITY_PLUS_MESSAGES + account + "/");
+        Cursor cursor = mContentResolver.query(uri, MESSAGE_PROJECTION, query, null, null);
+        return new MessageCursor(this, mContentResolver, account, cursor);
+    }
+
+    /**
+     * Gets a cursor over all of the messages in a conversation.
+     *
+     * @param account get the cursor for messages in this account
+     * @param conversationId the id of the converstion to fetch messages for
+     * @return a cursor over messages in the conversation
+     */
+    public MessageCursor getMessageCursorForConversationId(String account, long conversationId) {
+        if (TextUtils.isEmpty(account)) {
+            throw new IllegalArgumentException("account is empty");
+        }
+        Uri uri = Uri.parse(
+                AUTHORITY_PLUS_CONVERSATIONS + account + "/" + conversationId + "/messages");
+        Cursor cursor = mContentResolver.query(
+                uri, MESSAGE_PROJECTION, null, null, null);
+        return new MessageCursor(this, mContentResolver, account, cursor);
+    }
+
+    /**
+     * Expunge the indicated message. One use of this is to discard drafts.
+     *
+     * @param account the account of the message id
+     * @param messageId the id of the message to expunge
+     */
+    public void expungeMessage(String account, long messageId) {
+        if (TextUtils.isEmpty(account)) {
+            throw new IllegalArgumentException("account is empty");
+        }
+        Uri uri = Uri.parse(AUTHORITY_PLUS_MESSAGES + account + "/" + messageId);
+        mContentResolver.delete(uri, null, null);
+    }
+
+    /**
+     * Adds or removes the label on the conversation.
+     *
+     * @param account the account of the conversation
+     * @param conversationId the conversation
+     * @param maxServerMessageId the highest message id to whose labels should be changed. Note that
+     *   everywhere else in this file messageId means local message id but here you need to use a
+     *   server message id.
+     * @param label the label to add or remove
+     * @param add true to add the label, false to remove it
+     */
+    public void addOrRemoveLabelOnConversation(
+            String account, long conversationId, long maxServerMessageId, String label,
+            boolean add) {
+        if (TextUtils.isEmpty(account)) {
+            throw new IllegalArgumentException("account is empty");
+        }
+        if (add) {
+            Uri uri = Uri.parse(
+                    AUTHORITY_PLUS_CONVERSATIONS + account + "/" + conversationId + "/labels");
+            ContentValues values = new ContentValues();
+            values.put(LabelColumns.CANONICAL_NAME, label);
+            values.put(ConversationColumns.MAX_MESSAGE_ID, maxServerMessageId);
+            mContentResolver.insert(uri, values);
+        } else {
+            String encodedLabel;
+            try {
+                encodedLabel = URLEncoder.encode(label, "utf-8");
+            } catch (UnsupportedEncodingException e) {
+                throw new RuntimeException(e);
+            }
+            Uri uri = Uri.parse(
+                    AUTHORITY_PLUS_CONVERSATIONS + account + "/"
+                            + conversationId + "/labels/" + encodedLabel);
+            mContentResolver.delete(
+                    uri, ConversationColumns.MAX_MESSAGE_ID, new String[]{"" + maxServerMessageId});
+        }
+    }
+
+    /**
+     * Adds or removes the label on the message.
+     *
+     * @param contentResolver the content resolver.
+     * @param account the account of the message
+     * @param conversationId the conversation containing the message
+     * @param messageId the id of the message to whose labels should be changed
+     * @param label the label to add or remove
+     * @param add true to add the label, false to remove it
+     */
+    public static void addOrRemoveLabelOnMessage(ContentResolver contentResolver, String account,
+            long conversationId, long messageId, String label, boolean add) {
+
+        // conversationId is unused but we want to start passing it whereever we pass a message id.
+        if (add) {
+            Uri uri = Uri.parse(
+                    AUTHORITY_PLUS_MESSAGES + account + "/" + messageId + "/labels");
+            ContentValues values = new ContentValues();
+            values.put(LabelColumns.CANONICAL_NAME, label);
+            contentResolver.insert(uri, values);
+        } else {
+            String encodedLabel;
+            try {
+                encodedLabel = URLEncoder.encode(label, "utf-8");
+            } catch (UnsupportedEncodingException e) {
+                throw new RuntimeException(e);
+            }
+            Uri uri = Uri.parse(
+                    AUTHORITY_PLUS_MESSAGES + account + "/" + messageId
+                    + "/labels/" + encodedLabel);
+            contentResolver.delete(uri, null, null);
+        }
+    }
+
+    /**
+     * The mail provider will send an intent when certain changes happen in certain labels.
+     * Currently those labels are inbox and voicemail.
+     *
+     * <p>The intent will have the action ACTION_PROVIDER_CHANGED and the extras mentioned below.
+     * The data for the intent will be content://gmail-ls/unread/<name of label>.
+     *
+     * <p>The goal is to support the following user experience:<ul>
+     *   <li>When present the new mail indicator reports the number of unread conversations in the
+     *   inbox (or some other label).</li>
+     *   <li>When the user views the inbox the indicator is removed immediately. They do not have to
+     *   read all of the conversations.</li>
+     *   <li>If more mail arrives the indicator reappears and shows the total number of unread
+     *   conversations in the inbox.</li>
+     *   <li>If the user reads the new conversations on the web the indicator disappears on the
+     *   phone since there is no unread mail in the inbox that the user hasn't seen.</li>
+     *   <li>The phone should vibrate/etc when it transitions from having no unseen unread inbox
+     *   mail to having some.</li>
+     */
+
+    /** The account in which the change occurred. */
+    static public final String PROVIDER_CHANGED_EXTRA_ACCOUNT = "account";
+
+    /** The number of unread conversations matching the label. */
+    static public final String PROVIDER_CHANGED_EXTRA_COUNT = "count";
+
+    /** Whether to get the user's attention, perhaps by vibrating. */
+    static public final String PROVIDER_CHANGED_EXTRA_GET_ATTENTION = "getAttention";
+
+    /**
+     * A label that is attached to all of the conversations being notified about. This enables the
+     * receiver of a notification to get a list of matching conversations.
+     */
+    static public final String PROVIDER_CHANGED_EXTRA_TAG_LABEL = "tagLabel";
+
+    /**
+     * Settings for which conversations should be synced to the phone.
+     * Conversations are synced if any message matches any of the following
+     * criteria:
+     *
+     * <ul>
+     *   <li>the message has a label in the include set</li>
+     *   <li>the message is no older than conversationAgeDays and has a label in the partial set.
+     *   </li>
+     *   <li>also, pending changes on the server: the message has no user-controllable labels.</li>
+     * </ul>
+     *
+     * <p>A user-controllable label is a user-defined label or star, inbox,
+     * trash, spam, etc. LABEL_UNREAD is not considered user-controllable.
+     */
+    public static class Settings {
+        public long conversationAgeDays;
+        public long maxAttachmentSizeMb;
+        public String[] labelsIncluded;
+        public String[] labelsPartial;
+    }
+
+    /**
+     * Returns the settings.
+     * @param account the account whose setting should be retrieved
+     */
+    public Settings getSettings(String account) {
+        if (TextUtils.isEmpty(account)) {
+            throw new IllegalArgumentException("account is empty");
+        }
+        Settings settings = new Settings();
+        Cursor cursor = mContentResolver.query(
+                Uri.withAppendedPath(SETTINGS_URI, account), SETTINGS_PROJECTION, null, null, null);
+        cursor.moveToNext();
+        settings.labelsIncluded = TextUtils.split(cursor.getString(0), SPACE_SEPARATOR_PATTERN);
+        settings.labelsPartial = TextUtils.split(cursor.getString(1), SPACE_SEPARATOR_PATTERN);
+        settings.conversationAgeDays = Long.parseLong(cursor.getString(2));
+        settings.maxAttachmentSizeMb = Long.parseLong(cursor.getString(3));
+        cursor.close();
+        return settings;
+    }
+
+    /**
+     * Sets the settings. A sync will be scheduled automatically.
+     */
+    public void setSettings(String account, Settings settings) {
+        if (TextUtils.isEmpty(account)) {
+            throw new IllegalArgumentException("account is empty");
+        }
+        ContentValues values = new ContentValues();
+        values.put(
+                SettingsColumns.LABELS_INCLUDED,
+                TextUtils.join(" ", settings.labelsIncluded));
+        values.put(
+                SettingsColumns.LABELS_PARTIAL,
+                TextUtils.join(" ", settings.labelsPartial));
+        values.put(
+                SettingsColumns.CONVERSATION_AGE_DAYS,
+                settings.conversationAgeDays);
+        values.put(
+                SettingsColumns.MAX_ATTACHMENET_SIZE_MB,
+                settings.maxAttachmentSizeMb);
+        mContentResolver.update(Uri.withAppendedPath(SETTINGS_URI, account), values, null, null);
+    }
+
+    /**
+     * Uses sender instructions to build a formatted string.
+     *
+     * <p>Sender list instructions contain compact information about the sender list. Most work that
+     * can be done without knowing how much room will be availble for the sender list is done when
+     * creating the instructions.
+     *
+     * <p>The instructions string consists of tokens separated by SENDER_LIST_SEPARATOR. Here are
+     * the tokens, one per line:<ul>
+     * <li><tt>n</tt></li>
+     * <li><em>int</em>, the number of non-draft messages in the conversation</li>
+     * <li><tt>d</tt</li>
+     * <li><em>int</em>, the number of drafts in the conversation</li>
+     * <li><tt>l</tt></li>
+     * <li><em>literal html to be included in the output</em></li>
+     * <li><tt>s</tt> indicates that the message is sending (in the outbox without errors)</li>
+     * <li><tt>f</tt> indicates that the message failed to send (in the outbox with errors)</li>
+     * <li><em>for each message</em><ul>
+     *   <li><em>int</em>, 0 for read, 1 for unread</li>
+     *   <li><em>int</em>, the priority of the message. Zero is the most important</li>
+     *   <li><em>text</em>, the sender text or blank for messages from 'me'</li>
+     * </ul></li>
+     * <li><tt>e</tt> to indicate that one or more messages have been elided</li>
+     *
+     * <p>The instructions indicate how many messages and drafts are in the conversation and then
+     * describe the most important messages in order, indicating the priority of each message and
+     * whether the message is unread.
+     *
+     * @param instructions instructions as described above
+     * @param sb the SpannableStringBuilder to append to
+     * @param maxChars the number of characters available to display the text
+     * @param unreadStyle the CharacterStyle for unread messages, or null
+     * @param draftsStyle the CharacterStyle for draft messages, or null
+     * @param sendingString the string to use when there are messages scheduled to be sent
+     * @param sendFailedString the string to use when there are messages that mailed to send
+     * @param meString the string to use for messages sent by this user
+     * @param draftString the string to use for "Draft"
+     * @param draftPluralString the string to use for "Drafts" 
+     */
+    public static void getSenderSnippet(
+            String instructions, SpannableStringBuilder sb, int maxChars,
+            CharacterStyle unreadStyle,
+            CharacterStyle draftsStyle,
+            CharSequence meString, CharSequence draftString, CharSequence draftPluralString,
+            CharSequence sendingString, CharSequence sendFailedString,
+            boolean forceAllUnread, boolean forceAllRead) {
+        assert !(forceAllUnread && forceAllRead);
+        boolean unreadStatusIsForced = forceAllUnread || forceAllRead;
+        boolean forcedUnreadStatus = forceAllUnread;
+
+        // Measure each fragment. It's ok to iterate over the entire set of fragments because it is
+        // never a long list, even if there are many senders.
+        final Map<Integer, Integer> priorityToLength = sPriorityToLength;
+        priorityToLength.clear();
+
+        int maxFoundPriority = Integer.MIN_VALUE;
+        int numMessages = 0;
+        int numDrafts = 0;
+        CharSequence draftsFragment = "";
+        CharSequence sendingFragment = "";
+        CharSequence sendFailedFragment = "";
+        
+        sSenderListSplitter.setString(instructions);
+        int numFragments = 0;
+        String[] fragments = sSenderFragments;
+        int currentSize = fragments.length;
+        while (sSenderListSplitter.hasNext()) {
+            fragments[numFragments++] = sSenderListSplitter.next();
+            if (numFragments == currentSize) {
+                sSenderFragments = new String[2 * currentSize];
+                System.arraycopy(fragments, 0, sSenderFragments, 0, currentSize);
+                currentSize *= 2;
+                fragments = sSenderFragments;
+            }
+        }
+        
+        for (int i = 0; i < numFragments;) {
+            String fragment0 = fragments[i++];
+            if ("".equals(fragment0)) {
+                // This should be the final fragment.
+            } else if (Gmail.SENDER_LIST_TOKEN_ELIDED.equals(fragment0)) {
+                // ignore
+            } else if (Gmail.SENDER_LIST_TOKEN_NUM_MESSAGES.equals(fragment0)) {
+                numMessages = Integer.valueOf(fragments[i++]);
+            } else if (Gmail.SENDER_LIST_TOKEN_NUM_DRAFTS.equals(fragment0)) {
+                String numDraftsString = fragments[i++];
+                numDrafts = Integer.parseInt(numDraftsString);
+                draftsFragment = numDrafts == 1 ? draftString :
+                        draftPluralString + " (" + numDraftsString + ")";
+            } else if (Gmail.SENDER_LIST_TOKEN_LITERAL.equals(fragment0)) {
+                sb.append(Html.fromHtml(fragments[i++]));
+                return;
+            } else if (Gmail.SENDER_LIST_TOKEN_SENDING.equals(fragment0)) {
+                sendingFragment = sendingString;
+            } else if (Gmail.SENDER_LIST_TOKEN_SEND_FAILED.equals(fragment0)) {
+                sendFailedFragment = sendFailedString;
+            } else {
+                String priorityString = fragments[i++];
+                CharSequence nameString = fragments[i++];
+                if (nameString.length() == 0) nameString = meString;
+                int priority = Integer.parseInt(priorityString);
+                priorityToLength.put(priority, nameString.length());
+                maxFoundPriority = Math.max(maxFoundPriority, priority);
+            }
+        }
+        String numMessagesFragment =
+                (numMessages != 0) ? " (" + Integer.toString(numMessages + numDrafts) + ")" : "";
+
+        // Don't allocate fixedFragment unless we need it
+        SpannableStringBuilder fixedFragment = null;
+        int fixedFragmentLength = 0;
+        if (draftsFragment.length() != 0) {
+            if (fixedFragment == null) {
+                fixedFragment = new SpannableStringBuilder();
+            }
+            fixedFragment.append(draftsFragment);
+            if (draftsStyle != null) {
+                fixedFragment.setSpan(
+                        CharacterStyle.wrap(draftsStyle),
+                        0, fixedFragment.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+            }
+        }
+        if (sendingFragment.length() != 0) {
+            if (fixedFragment == null) {
+                fixedFragment = new SpannableStringBuilder();
+            }
+            if (fixedFragment.length() != 0) fixedFragment.append(", ");
+            fixedFragment.append(sendingFragment);
+        }
+        if (sendFailedFragment.length() != 0) {
+            if (fixedFragment == null) {
+                fixedFragment = new SpannableStringBuilder();
+            }
+            if (fixedFragment.length() != 0) fixedFragment.append(", ");
+            fixedFragment.append(sendFailedFragment);
+        }
+
+        if (fixedFragment != null) {
+            fixedFragmentLength = fixedFragment.length();
+        }
+
+        final boolean normalMessagesExist =
+                numMessagesFragment.length() != 0 || maxFoundPriority != Integer.MIN_VALUE;
+        String preFixedFragement = "";
+        if (normalMessagesExist && fixedFragmentLength != 0) {
+            preFixedFragement = ", ";
+        }
+        int maxPriorityToInclude = -1; // inclusive
+        int numCharsUsed =
+                numMessagesFragment.length() + preFixedFragement.length() + fixedFragmentLength;
+        int numSendersUsed = 0;
+        while (maxPriorityToInclude < maxFoundPriority) {
+            if (priorityToLength.containsKey(maxPriorityToInclude + 1)) {
+                int length = numCharsUsed + priorityToLength.get(maxPriorityToInclude + 1);
+                if (numCharsUsed > 0) length += 2;
+                // We must show at least two senders if they exist. If we don't have space for both
+                // then we will truncate names.
+                if (length > maxChars && numSendersUsed >= 2) {
+                    break;
+                }
+                numCharsUsed = length;
+                numSendersUsed++;
+            }
+            maxPriorityToInclude++;
+        }
+
+        int numCharsToRemovePerWord = 0;
+        if (numCharsUsed > maxChars) {
+            numCharsToRemovePerWord = (numCharsUsed - maxChars) / numSendersUsed;
+        }
+
+        boolean elided = false;
+        for (int i = 0; i < numFragments;) {
+            String fragment0 = fragments[i++];
+            if ("".equals(fragment0)) {
+                // This should be the final fragment.
+            } else if (SENDER_LIST_TOKEN_ELIDED.equals(fragment0)) {
+                elided = true;
+            } else if (SENDER_LIST_TOKEN_NUM_MESSAGES.equals(fragment0)) {
+                i++;
+            } else if (SENDER_LIST_TOKEN_NUM_DRAFTS.equals(fragment0)) {
+                i++;
+            } else if (SENDER_LIST_TOKEN_SENDING.equals(fragment0)) {
+            } else if (SENDER_LIST_TOKEN_SEND_FAILED.equals(fragment0)) {
+            } else {
+                final String unreadString = fragment0;
+                final String priorityString = fragments[i++];
+                String nameString = fragments[i++];
+                if (nameString.length() == 0) nameString = meString.toString();
+                if (numCharsToRemovePerWord != 0) {
+                    nameString = nameString.substring(
+                            0, Math.max(nameString.length() - numCharsToRemovePerWord, 0));
+                }
+                final boolean unread = unreadStatusIsForced
+                        ? forcedUnreadStatus : Integer.parseInt(unreadString) != 0;
+                final int priority = Integer.parseInt(priorityString);
+                if (priority <= maxPriorityToInclude) {
+                    if (sb.length() != 0) {
+                        sb.append(elided ? " .. " : ", ");
+                    }
+                    elided = false;
+                    int pos = sb.length();
+                    sb.append(nameString);
+                    if (unread && unreadStyle != null) {
+                        sb.setSpan(CharacterStyle.wrap(unreadStyle),
+                                pos, sb.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+                    }
+                } else {
+                    elided = true;
+                }
+            }
+        }
+        sb.append(numMessagesFragment);
+        if (fixedFragmentLength != 0) {
+            sb.append(preFixedFragement);
+            sb.append(fixedFragment);
+        }
+    }
+
+    /**
+     * This is a cursor that only defines methods to move throught the results
+     * and register to hear about changes. All access to the data is left to
+     * subinterfaces.
+     */
+    public static class MailCursor extends ContentObserver {
+
+        // A list of observers of this cursor.
+        private Set<MailCursorObserver> mObservers;
+
+        // Updated values are accumulated here before being written out if the
+        // cursor is asked to persist the changes.
+        private ContentValues mUpdateValues;
+
+        protected Cursor mCursor;
+        protected String mAccount;
+
+        public Cursor getCursor() {
+            return mCursor;
+        }
+
+        /**
+         * Constructs the MailCursor given a regular cursor, registering as a
+         * change observer of the cursor.
+         * @param account the account the cursor is associated with
+         * @param cursor the underlying cursor
+         */
+        protected MailCursor(String account, Cursor cursor) {
+            super(new Handler());
+            mObservers = new HashSet<MailCursorObserver>();
+            mCursor = cursor;
+            mAccount = account;
+            if (mCursor != null) mCursor.registerContentObserver(this);
+        }
+
+        /**
+         * Gets the account associated with this cursor.
+         * @return the account.
+         */
+        public String getAccount() {
+            return mAccount;
+        }
+
+        protected void checkThread() {
+            // Turn this on when activity code no longer runs in the sync thread
+            // after notifications of changes.
+//            Thread currentThread = Thread.currentThread();
+//            if (currentThread != mThread) {
+//                throw new RuntimeException("Accessed from the wrong thread");
+//            }
+        }
+
+        /**
+         * Lazily constructs a map of update values to apply to the database
+         * if requested. This map is cleared out when we move to a different
+         * item in the result set.
+         *
+         * @return a map of values to be applied by an update.
+         */
+        protected ContentValues getUpdateValues() {
+            if (mUpdateValues == null) {
+                mUpdateValues = new ContentValues();
+            }
+            return mUpdateValues;
+        }
+
+        /**
+         * Called whenever mCursor is changed to point to a different row.
+         * Subclasses should override this if they need to clear out state
+         * when this happens.
+         *
+         * Subclasses must call the inherited version if they override this.
+         */
+        protected void onCursorPositionChanged() {
+            mUpdateValues = null;
+        }
+
+        // ********* MailCursor
+
+        /**
+         * Returns the numbers of rows in the cursor.
+         *
+         * @return the number of rows in the cursor.
+         */
+        final public int count() {
+            if (mCursor != null) {
+                return mCursor.getCount();
+            } else {
+                return 0;
+            }
+        }
+
+        /**
+         * @return the current position of this cursor, or -1 if this cursor
+         * has not been initialized.
+         */
+        final public int position() {
+            if (mCursor != null) {
+                return mCursor.getPosition();
+            } else {
+                return -1;
+            }
+        }
+
+        /**
+         * Move the cursor to an absolute position. The valid
+         * range of vaues is -1 &lt;= position &lt;= count.
+         *
+         * <p>This method will return true if the request destination was
+         * reachable, otherwise it returns false.
+         *
+         * @param position the zero-based position to move to.
+         * @return whether the requested move fully succeeded.
+         */
+        final public boolean moveTo(int position) {
+            checkCursor();
+            checkThread();
+            boolean moved = mCursor.moveToPosition(position);
+            if (moved) onCursorPositionChanged();
+            return moved;
+        }
+
+        /**
+         * Move the cursor to the next row.
+         *
+         * <p>This method will return false if the cursor is already past the
+         * last entry in the result set.
+         *
+         * @return whether the move succeeded.
+         */
+        final public boolean next() {
+            checkCursor();
+            checkThread();
+            boolean moved = mCursor.moveToNext();
+            if (moved) onCursorPositionChanged();
+            return moved;
+        }
+
+        /**
+         * Release all resources and locks associated with the cursor. The
+         * cursor will not be valid after this function is called.
+         */
+        final public void release() {
+            if (mCursor != null) {
+                mCursor.unregisterContentObserver(this);
+                mCursor.deactivate();
+            }
+        }
+
+        final public void registerContentObserver(ContentObserver observer) {
+            mCursor.registerContentObserver(observer);
+        }
+
+        final public void unregisterContentObserver(ContentObserver observer) {
+            mCursor.unregisterContentObserver(observer);
+        }
+
+        final public void registerDataSetObserver(DataSetObserver observer) {
+            mCursor.registerDataSetObserver(observer);
+        }
+
+        final public void unregisterDataSetObserver(DataSetObserver observer) {
+            mCursor.unregisterDataSetObserver(observer);
+        }
+
+        /**
+         * Register an observer to hear about changes to the cursor.
+         *
+         * @param observer the observer to register
+         */
+        final public void registerObserver(MailCursorObserver observer) {
+            mObservers.add(observer);
+        }
+
+        /**
+         * Unregister an observer.
+         *
+         * @param observer the observer to unregister
+         */
+        final public void unregisterObserver(MailCursorObserver observer) {
+            mObservers.remove(observer);
+        }
+
+        // ********* ContentObserver
+
+        @Override
+        final public boolean deliverSelfNotifications() {
+            return false;
+        }
+
+        @Override
+        public void onChange(boolean selfChange) {
+            if (Config.DEBUG) {
+                Log.d(TAG, "MailCursor is notifying " + mObservers.size() + " observers");
+            }
+            for (MailCursorObserver o: mObservers) {
+                o.onCursorChanged(this);
+            }
+        }
+
+        protected void checkCursor() {
+            if (mCursor == null) {
+                throw new IllegalStateException(
+                        "cannot read from an insertion cursor");
+            }
+        }
+
+        /**
+         * Returns the string value of the column, or "" if the value is null.
+         */
+        protected String getStringInColumn(int columnIndex) {
+            checkCursor();
+            return toNonnullString(mCursor.getString(columnIndex));
+        }
+    }
+
+    /**
+     * A MailCursor observer is notified of changes to the result set of a
+     * cursor.
+     */
+    public interface MailCursorObserver {
+
+        /**
+         * Called when the result set of a cursor has changed.
+         *
+         * @param cursor the cursor whose result set has changed.
+         */
+        void onCursorChanged(MailCursor cursor);
+    }
+
+    /**
+     * A cursor over labels.
+     */
+    public final class LabelCursor extends MailCursor {
+
+        private int mNameIndex;
+        private int mNumConversationsIndex;
+        private int mNumUnreadConversationsIndex;
+
+        private LabelCursor(String account, Cursor cursor) {
+            super(account, cursor);
+
+            mNameIndex = mCursor.getColumnIndexOrThrow(LabelColumns.CANONICAL_NAME);
+            mNumConversationsIndex =
+                    mCursor.getColumnIndexOrThrow(LabelColumns.NUM_CONVERSATIONS);
+            mNumUnreadConversationsIndex = mCursor.getColumnIndexOrThrow(
+                    LabelColumns.NUM_UNREAD_CONVERSATIONS);
+        }
+
+        /**
+         * Gets the canonical name of the current label.
+         *
+         * @return the current label's name.
+         */
+        public String getName() {
+            return getStringInColumn(mNameIndex);
+        }
+
+        /**
+         * Gets the number of conversations with this label.
+         *
+         * @return the number of conversations with this label.
+         */
+        public int getNumConversations() {
+            return mCursor.getInt(mNumConversationsIndex);
+        }
+
+        /**
+         * Gets the number of unread conversations with this label.
+         *
+         * @return the number of unread conversations with this label.
+         */
+        public int getNumUnreadConversations() {
+            return mCursor.getInt(mNumUnreadConversationsIndex);
+        }
+    }
+
+    /**
+     * This is a map of labels. TODO: make it observable.
+     */
+    public static final class LabelMap extends Observable {
+        private final static ContentValues EMPTY_CONTENT_VALUES = new ContentValues();
+
+        private ContentQueryMap mQueryMap;
+        private SortedSet<String> mSortedUserLabels;
+        private Map<String, Long> mCanonicalNameToId;
+
+        private long mLabelIdSent;
+        private long mLabelIdInbox;
+        private long mLabelIdDraft;
+        private long mLabelIdUnread;
+        private long mLabelIdTrash;
+        private long mLabelIdSpam;
+        private long mLabelIdStarred;
+        private long mLabelIdChat;
+        private long mLabelIdVoicemail;
+        private long mLabelIdIgnored;
+        private long mLabelIdVoicemailInbox;
+        private long mLabelIdCached;
+        private long mLabelIdOutbox;
+
+        private boolean mLabelsSynced = false;
+
+        public LabelMap(ContentResolver contentResolver, String account, boolean keepUpdated) {
+            if (TextUtils.isEmpty(account)) {
+                throw new IllegalArgumentException("account is empty");
+            }
+            Cursor cursor = contentResolver.query(
+                    Uri.withAppendedPath(LABELS_URI, account), LABEL_PROJECTION, null, null, null);
+            init(cursor, keepUpdated);
+        }
+
+        public LabelMap(Cursor cursor, boolean keepUpdated) {
+            init(cursor, keepUpdated);
+        }
+
+        private void init(Cursor cursor, boolean keepUpdated) {
+            mQueryMap = new ContentQueryMap(cursor, BaseColumns._ID, keepUpdated, null);
+            mSortedUserLabels = new TreeSet<String>(java.text.Collator.getInstance());
+            mCanonicalNameToId = Maps.newHashMap();
+            updateDataStructures();
+            mQueryMap.addObserver(new Observer() {
+                public void update(Observable observable, Object data) {
+                    updateDataStructures();
+                    setChanged();
+                    notifyObservers();
+                }
+            });
+        }
+
+        /**
+         * @return whether at least some labels have been synced.
+         */
+        public boolean labelsSynced() {
+            return mLabelsSynced;
+        }
+
+        /**
+         * Updates the data structures that are maintained separately from mQueryMap after the query
+         * map has changed.
+         */
+        private void updateDataStructures() {
+            mSortedUserLabels.clear();
+            mCanonicalNameToId.clear();
+            for (Map.Entry<String, ContentValues> row : mQueryMap.getRows().entrySet()) {
+                long labelId = Long.valueOf(row.getKey());
+                String canonicalName = row.getValue().getAsString(LabelColumns.CANONICAL_NAME);
+                if (isLabelUserDefined(canonicalName)) {
+                    mSortedUserLabels.add(canonicalName);
+                }
+                mCanonicalNameToId.put(canonicalName, labelId);
+
+                if (LABEL_SENT.equals(canonicalName)) {
+                    mLabelIdSent = labelId;
+                } else if (LABEL_INBOX.equals(canonicalName)) {
+                    mLabelIdInbox = labelId;
+                } else if (LABEL_DRAFT.equals(canonicalName)) {
+                    mLabelIdDraft = labelId;
+                } else if (LABEL_UNREAD.equals(canonicalName)) {
+                    mLabelIdUnread = labelId;
+                } else if (LABEL_TRASH.equals(canonicalName)) {
+                    mLabelIdTrash = labelId;
+                } else if (LABEL_SPAM.equals(canonicalName)) {
+                    mLabelIdSpam = labelId;
+                } else if (LABEL_STARRED.equals(canonicalName)) {
+                    mLabelIdStarred = labelId;
+                } else if (LABEL_CHAT.equals(canonicalName)) {
+                    mLabelIdChat = labelId;
+                } else if (LABEL_IGNORED.equals(canonicalName)) {
+                    mLabelIdIgnored = labelId;
+                } else if (LABEL_VOICEMAIL.equals(canonicalName)) {
+                    mLabelIdVoicemail = labelId;
+                } else if (LABEL_VOICEMAIL_INBOX.equals(canonicalName)) {
+                    mLabelIdVoicemailInbox = labelId;
+                } else if (LABEL_CACHED.equals(canonicalName)) {
+                    mLabelIdCached = labelId;
+                } else if (LABEL_OUTBOX.equals(canonicalName)) {
+                    mLabelIdOutbox = labelId;
+                }
+                mLabelsSynced = mLabelIdSent != 0
+                    && mLabelIdInbox != 0
+                    && mLabelIdDraft != 0
+                    && mLabelIdUnread != 0
+                    && mLabelIdTrash != 0
+                    && mLabelIdSpam != 0
+                    && mLabelIdStarred != 0
+                    && mLabelIdChat != 0
+                    && mLabelIdIgnored != 0
+                    && mLabelIdVoicemail != 0;
+            }
+        }
+
+        public long getLabelIdSent() {
+            checkLabelsSynced();
+            return mLabelIdSent;
+        }
+
+        public long getLabelIdInbox() {
+            checkLabelsSynced();
+            return mLabelIdInbox;
+        }
+
+        public long getLabelIdDraft() {
+            checkLabelsSynced();
+            return mLabelIdDraft;
+        }
+
+        public long getLabelIdUnread() {
+            checkLabelsSynced();
+            return mLabelIdUnread;
+        }
+
+        public long getLabelIdTrash() {
+            checkLabelsSynced();
+            return mLabelIdTrash;
+        }
+
+        public long getLabelIdSpam() {
+            checkLabelsSynced();
+            return mLabelIdSpam;
+        }
+
+        public long getLabelIdStarred() {
+            checkLabelsSynced();
+            return mLabelIdStarred;
+        }
+
+        public long getLabelIdChat() {
+            checkLabelsSynced();
+            return mLabelIdChat;
+        }
+
+        public long getLabelIdIgnored() {
+            checkLabelsSynced();
+            return mLabelIdIgnored;
+        }
+
+        public long getLabelIdVoicemail() {
+            checkLabelsSynced();
+            return mLabelIdVoicemail;
+        }
+
+        public long getLabelIdVoicemailInbox() {
+            checkLabelsSynced();
+            return mLabelIdVoicemailInbox;
+        }
+
+        public long getLabelIdCached() {
+            checkLabelsSynced();
+            return mLabelIdCached;
+        }
+
+        public long getLabelIdOutbox() {
+            checkLabelsSynced();
+            return mLabelIdOutbox;
+        }
+
+        private void checkLabelsSynced() {
+            if (!labelsSynced()) {
+                throw new IllegalStateException("LabelMap not initalized");
+            }
+        }
+
+        /** Returns the list of user-defined labels in alphabetical order. */
+        public SortedSet<String> getSortedUserLabels() {
+            return mSortedUserLabels;
+        }
+
+        private static final List<String> SORTED_USER_MEANINGFUL_SYSTEM_LABELS =
+                Lists.newArrayList(
+                        LABEL_INBOX, LABEL_STARRED, LABEL_CHAT, LABEL_SENT,
+                        LABEL_OUTBOX, LABEL_DRAFT, LABEL_ALL,
+                        LABEL_SPAM, LABEL_TRASH);
+
+
+        private static final Set<String> USER_MEANINGFUL_SYSTEM_LABELS_SET =
+                Sets.newHashSet(
+                        SORTED_USER_MEANINGFUL_SYSTEM_LABELS.toArray(
+                                new String[]{}));
+
+        public static List<String> getSortedUserMeaningfulSystemLabels() {
+            return SORTED_USER_MEANINGFUL_SYSTEM_LABELS;
+        }
+
+        public static Set<String> getUserMeaningfulSystemLabelsSet() {
+            return USER_MEANINGFUL_SYSTEM_LABELS_SET;
+        }
+
+        /**
+         * If you are ever tempted to remove outbox or draft from this set make sure you have a
+         * way to stop draft and outbox messages from getting purged before they are sent to the
+         * server.
+         */
+        private static final Set<String> FORCED_INCLUDED_LABELS =
+                Sets.newHashSet(LABEL_OUTBOX, LABEL_DRAFT);
+
+        public static Set<String> getForcedIncludedLabels() {
+            return FORCED_INCLUDED_LABELS;
+        }
+
+        private static final Set<String> FORCED_INCLUDED_OR_PARTIAL_LABELS =
+                Sets.newHashSet(LABEL_INBOX);
+
+        public static Set<String> getForcedIncludedOrPartialLabels() {
+            return FORCED_INCLUDED_OR_PARTIAL_LABELS;
+        }
+
+        private static final Set<String> FORCED_UNSYNCED_LABELS =
+                Sets.newHashSet(LABEL_ALL, LABEL_CHAT, LABEL_SPAM, LABEL_TRASH);
+
+        public static Set<String> getForcedUnsyncedLabels() {
+            return FORCED_UNSYNCED_LABELS;
+        }
+
+        /**
+         * Returns the number of conversation with a given label.
+         * @deprecated
+         */
+        public int getNumConversations(String label) {
+            return getNumConversations(getLabelId(label));
+        }
+
+        /** Returns the number of conversation with a given label. */
+        public int getNumConversations(long labelId) {
+            return getLabelIdValues(labelId).getAsInteger(LabelColumns.NUM_CONVERSATIONS);
+        }
+
+        /**
+         * Returns the number of unread conversation with a given label.
+         * @deprecated
+         */
+        public int getNumUnreadConversations(String label) {
+            return getNumUnreadConversations(getLabelId(label));
+        }
+
+        /** Returns the number of unread conversation with a given label. */
+        public int getNumUnreadConversations(long labelId) {
+            return getLabelIdValues(labelId).getAsInteger(LabelColumns.NUM_UNREAD_CONVERSATIONS);
+        }
+
+        /**
+         * @return the canonical name for a label
+         */
+        public String getCanonicalName(long labelId) {
+            return getLabelIdValues(labelId).getAsString(LabelColumns.CANONICAL_NAME);
+        }
+
+        /**
+         * @return the human name for a label
+         */
+        public String getName(long labelId) {
+            return getLabelIdValues(labelId).getAsString(LabelColumns.NAME);
+        }
+
+        /**
+         * @return whether a given label is known
+         */
+        public boolean hasLabel(long labelId) {
+            return mQueryMap.getRows().containsKey(Long.toString(labelId));
+        }
+
+        /**
+         * @return returns the id of a label given the canonical name
+         * @deprecated this is only needed because most of the UI uses label names instead of ids
+         */
+        public long getLabelId(String canonicalName) {
+            if (mCanonicalNameToId.containsKey(canonicalName)) {
+                return mCanonicalNameToId.get(canonicalName);
+            } else {
+                throw new IllegalArgumentException("Unknown canonical name: " + canonicalName);
+            }
+        }
+
+        private ContentValues getLabelIdValues(long labelId) {
+            final ContentValues values = mQueryMap.getValues(Long.toString(labelId));
+            if (values != null) {
+                return values;
+            } else {
+                return EMPTY_CONTENT_VALUES;
+            }
+        }
+
+        /** Force the map to requery. This should not be necessary outside tests. */
+        public void requery() {
+            mQueryMap.requery();
+        }
+
+        public void close() {
+            mQueryMap.close();
+        }
+    }
+
+    private Map<String, Gmail.LabelMap> mLabelMaps = Maps.newHashMap();
+
+    public LabelMap getLabelMap(String account) {
+        Gmail.LabelMap labelMap = mLabelMaps.get(account);
+        if (labelMap == null) {
+            labelMap = new Gmail.LabelMap(mContentResolver, account, true /* keepUpdated */);
+            mLabelMaps.put(account, labelMap);
+        }
+        return labelMap;
+    }
+
+    public enum PersonalLevel {
+        NOT_TO_ME(0),
+        TO_ME_AND_OTHERS(1),
+        ONLY_TO_ME(2);
+
+        private int mLevel;
+
+        PersonalLevel(int level) {
+            mLevel = level;
+        }
+
+        public int toInt() {
+            return mLevel;
+        }
+
+        public static PersonalLevel fromInt(int level) {
+            switch (level) {
+                case 0: return NOT_TO_ME;
+                case 1: return TO_ME_AND_OTHERS;
+                case 2: return ONLY_TO_ME;
+                default:
+                    throw new IllegalArgumentException(
+                            level + " is not a personal level");
+            }
+        }
+    }
+
+    /**
+     * Indicates a version of an attachment.
+     */
+    public enum AttachmentRendition {
+        /**
+         * The full version of an attachment if it can be handled on the device, otherwise the
+         * preview.
+         */
+        BEST,
+
+        /** A smaller or simpler version of the attachment, such as a scaled-down image or an HTML
+         * version of a document. Not always available.
+         */
+        SIMPLE,
+    }
+
+    /**
+     * The columns that can be requested when querying an attachment's download URI. See
+     * getAttachmentDownloadUri.
+     */
+    public static final class AttachmentColumns implements BaseColumns {
+
+        /** Contains a STATUS value from {@link android.provider.Downloads} */
+        public static final String STATUS = "status";
+
+        /**
+         * The name of the file to open (with ContentProvider.open). If this is empty then continue
+         * to use the attachment's URI.
+         *
+         * TODO: I'm not sure that we need this. See the note in CL 66853-p9.
+         */
+        public static final String FILENAME = "filename";
+    }
+
+    /**
+     * We track where an attachment came from so that we know how to download it and include it
+     * in new messages.
+     */
+    public enum AttachmentOrigin {
+        /** Extras are "<conversationId>-<messageId>-<partId>". */
+        SERVER_ATTACHMENT,
+        /** Extras are "<path>". */
+        LOCAL_FILE;
+
+        private static final String SERVER_EXTRAS_SEPARATOR = "_";
+
+        public static String serverExtras(
+                long conversationId, long messageId, String partId) {
+            return conversationId + SERVER_EXTRAS_SEPARATOR
+                    + messageId + SERVER_EXTRAS_SEPARATOR + partId;
+        }
+
+        /**
+         * @param extras extras as returned by serverExtras
+         * @return an array of conversationId, messageId, partId (all as strings)
+         */
+        public static String[] splitServerExtras(String extras) {
+            return TextUtils.split(extras, SERVER_EXTRAS_SEPARATOR);
+        }
+
+        public static String localFileExtras(Uri path) {
+            return path.toString();
+        }
+    }
+
+    public static final class Attachment {
+        /** Identifies the attachment uniquely when combined wih a message id.*/
+        public String partId;
+
+        /** The intended filename of the attachment.*/
+        public String name;
+
+        /** The native content type.*/
+        public String contentType;
+
+        /** The size of the attachment in its native form.*/
+        public int size;
+
+        /**
+         * The content type of the simple version of the attachment. Blank if no simple version is
+         * available.
+         */
+        public String simpleContentType;
+
+        public AttachmentOrigin origin;
+
+        public String originExtras;
+
+        public String toJoinedString() {
+            return TextUtils.join(
+                "|", Lists.newArrayList(partId == null ? "" : partId,
+                                        name.replace("|", ""), contentType,
+                                        size, simpleContentType,
+                                        origin.toString(), originExtras));
+        }
+
+        public static Attachment parseJoinedString(String joinedString) {
+            String[] fragments = TextUtils.split(joinedString, "\\|");
+            int i = 0;
+            Attachment attachment = new Attachment();
+            attachment.partId = fragments[i++];
+            if (TextUtils.isEmpty(attachment.partId)) {
+                attachment.partId = null;
+            }
+            attachment.name = fragments[i++];
+            attachment.contentType = fragments[i++];
+            attachment.size = Integer.parseInt(fragments[i++]);
+            attachment.simpleContentType = fragments[i++];
+            attachment.origin = AttachmentOrigin.valueOf(fragments[i++]);
+            attachment.originExtras = fragments[i++];
+            return attachment;
+        }
+    }
+
+    /**
+     * Any given attachment can come in two different renditions (see
+     * {@link android.provider.Gmail.AttachmentRendition}) and can be saved to the sd card or to a
+     * cache. The gmail provider automatically syncs some attachments to the cache. Other
+     * attachments can be downloaded on demand. Attachments in the cache will be purged as needed to
+     * save space. Attachments on the SD card must be managed by the user or other software.
+     *
+     * @param account which account to use
+     * @param messageId the id of the mesage with the attachment
+     * @param attachment the attachment
+     * @param rendition the desired rendition
+     * @param saveToSd whether the attachment should be saved to (or loaded from) the sd card or
+     * @return the URI to ask the content provider to open in order to open an attachment.
+     */
+    public static Uri getAttachmentUri(
+            String account, long messageId, Attachment attachment,
+            AttachmentRendition rendition, boolean saveToSd) {
+        if (TextUtils.isEmpty(account)) {
+            throw new IllegalArgumentException("account is empty");
+        }
+        if (attachment.origin == AttachmentOrigin.LOCAL_FILE) {
+            return Uri.parse(attachment.originExtras);
+        } else {
+            return Uri.parse(
+                    AUTHORITY_PLUS_MESSAGES).buildUpon()
+                    .appendPath(account).appendPath(Long.toString(messageId))
+                    .appendPath("attachments").appendPath(attachment.partId)
+                    .appendPath(rendition.toString())
+                    .appendPath(Boolean.toString(saveToSd))
+                    .build();
+        }
+    }
+
+    /**
+     * Return the URI to query in order to find out whether an attachment is downloaded.
+     *
+     * <p>Querying this will also start a download if necessary. The cursor returned by querying
+     * this URI can contain the columns in {@link android.provider.Gmail.AttachmentColumns}.
+     *
+     * <p>Deleting this URI will cancel the download if it was not started automatically by the
+     * provider. It will also remove bookkeeping for saveToSd downloads.
+     *
+     * @param attachmentUri the attachment URI as returned by getAttachmentUri. The URI's authority
+     *   Gmail.AUTHORITY. If it is not then you should open the file directly.
+     */
+    public static Uri getAttachmentDownloadUri(Uri attachmentUri) {
+        if (!"content".equals(attachmentUri.getScheme())) {
+            throw new IllegalArgumentException("Uri's scheme must be 'content': " + attachmentUri);
+        }
+        return attachmentUri.buildUpon().appendPath("download").build();
+    }
+
+    public enum CursorStatus {
+        LOADED,
+        LOADING,
+        ERROR, // A network error occurred.
+    }
+
+    /**
+     * A cursor over messages.
+     */
+    public static final class MessageCursor extends MailCursor {
+
+        private LabelMap mLabelMap;
+
+        private ContentResolver mContentResolver;
+
+        /**
+         * Only valid if mCursor == null, in which case we are inserting a new
+         * message.
+         */
+        long mInReplyToLocalMessageId;
+        boolean mPreserveAttachments;
+
+        private int mIdIndex;
+        private int mConversationIdIndex;
+        private int mSubjectIndex;
+        private int mSnippetIndex;
+        private int mFromIndex;
+        private int mToIndex;
+        private int mCcIndex;
+        private int mBccIndex;
+        private int mReplyToIndex;
+        private int mDateSentMsIndex;
+        private int mDateReceivedMsIndex;
+        private int mListInfoIndex;
+        private int mPersonalLevelIndex;
+        private int mBodyIndex;
+        private int mBodyEmbedsExternalResourcesIndex;
+        private int mLabelIdsIndex;
+        private int mJoinedAttachmentInfosIndex;
+        private int mErrorIndex;
+
+        private TextUtils.StringSplitter mLabelIdsSplitter = newMessageLabelIdsSplitter();
+
+        public MessageCursor(Gmail gmail, ContentResolver cr, String account, Cursor cursor) {
+            super(account, cursor);
+            mLabelMap = gmail.getLabelMap(account);
+            if (cursor == null) {
+                throw new IllegalArgumentException(
+                        "null cursor passed to MessageCursor()");
+            }
+
+            mContentResolver = cr;
+
+            mIdIndex = mCursor.getColumnIndexOrThrow(MessageColumns.ID);
+            mConversationIdIndex =
+                    mCursor.getColumnIndexOrThrow(MessageColumns.CONVERSATION_ID);
+            mSubjectIndex = mCursor.getColumnIndexOrThrow(MessageColumns.SUBJECT);
+            mSnippetIndex = mCursor.getColumnIndexOrThrow(MessageColumns.SNIPPET);
+            mFromIndex = mCursor.getColumnIndexOrThrow(MessageColumns.FROM);
+            mToIndex = mCursor.getColumnIndexOrThrow(MessageColumns.TO);
+            mCcIndex = mCursor.getColumnIndexOrThrow(MessageColumns.CC);
+            mBccIndex = mCursor.getColumnIndexOrThrow(MessageColumns.BCC);
+            mReplyToIndex = mCursor.getColumnIndexOrThrow(MessageColumns.REPLY_TO);
+            mDateSentMsIndex =
+                    mCursor.getColumnIndexOrThrow(MessageColumns.DATE_SENT_MS);
+            mDateReceivedMsIndex =
+                    mCursor.getColumnIndexOrThrow(MessageColumns.DATE_RECEIVED_MS);
+            mListInfoIndex = mCursor.getColumnIndexOrThrow(MessageColumns.LIST_INFO);
+            mPersonalLevelIndex =
+                    mCursor.getColumnIndexOrThrow(MessageColumns.PERSONAL_LEVEL);
+            mBodyIndex = mCursor.getColumnIndexOrThrow(MessageColumns.BODY);
+            mBodyEmbedsExternalResourcesIndex =
+                    mCursor.getColumnIndexOrThrow(MessageColumns.EMBEDS_EXTERNAL_RESOURCES);
+            mLabelIdsIndex = mCursor.getColumnIndexOrThrow(MessageColumns.LABEL_IDS);
+            mJoinedAttachmentInfosIndex =
+                    mCursor.getColumnIndexOrThrow(MessageColumns.JOINED_ATTACHMENT_INFOS);
+            mErrorIndex = mCursor.getColumnIndexOrThrow(MessageColumns.ERROR);
+
+            mInReplyToLocalMessageId = 0;
+            mPreserveAttachments = false;
+        }
+
+        protected MessageCursor(ContentResolver cr, String account, long inReplyToMessageId,
+                boolean preserveAttachments) {
+            super(account, null);
+            mContentResolver = cr;
+            mInReplyToLocalMessageId = inReplyToMessageId;
+            mPreserveAttachments = preserveAttachments;
+        }
+
+        @Override
+        protected void onCursorPositionChanged() {
+            super.onCursorPositionChanged();
+        }
+
+        public CursorStatus getStatus() {
+            Bundle extras = mCursor.getExtras();
+            String stringStatus = extras.getString(EXTRA_STATUS);
+            return CursorStatus.valueOf(stringStatus);
+        }
+
+        /** Retry a network request after errors. */
+        public void retry() {
+            Bundle input = new Bundle();
+            input.putString(RESPOND_INPUT_COMMAND, COMMAND_RETRY);
+            Bundle output = mCursor.respond(input);
+            String response = output.getString(RESPOND_OUTPUT_COMMAND_RESPONSE);
+            assert COMMAND_RESPONSE_OK.equals(response);
+        }
+
+        /**
+         * Gets the message id of the current message. Note that this is an
+         * immutable local message (not, for example, GMail's message id, which
+         * is immutable).
+         *
+         * @return the message's id
+         */
+        public long getMessageId() {
+            checkCursor();
+            return mCursor.getLong(mIdIndex);
+        }
+
+        /**
+         * Gets the message's conversation id. This must be immutable. (For
+         * example, with GMail this should be the original conversation id
+         * rather than the default notion of converation id.)
+         *
+         * @return the message's conversation id
+         */
+        public long getConversationId() {
+            checkCursor();
+            return mCursor.getLong(mConversationIdIndex);
+        }
+
+        /**
+         * Gets the message's subject.
+         *
+         * @return the message's subject
+         */
+        public String getSubject() {
+            return getStringInColumn(mSubjectIndex);
+        }
+
+        /**
+         * Gets the message's snippet (the short piece of the body). The snippet
+         * is generated from the body and cannot be set directly.
+         *
+         * @return the message's snippet
+         */
+        public String getSnippet() {
+            return getStringInColumn(mSnippetIndex);
+        }
+
+        /**
+         * Gets the message's from address.
+         *
+         * @return the message's from address
+         */
+        public String getFromAddress() {
+            return getStringInColumn(mFromIndex);
+        }
+
+        /**
+         * Returns the addresses for the key, if it has been updated, or index otherwise.
+         */
+        private String[] getAddresses(String key, int index) {
+            ContentValues updated = getUpdateValues();
+            String addresses;
+            if (updated.containsKey(key)) {
+                addresses = (String)getUpdateValues().get(key);
+            } else {
+                addresses = getStringInColumn(index);
+            }
+
+            return TextUtils.split(addresses, EMAIL_SEPARATOR_PATTERN);
+        }
+
+        /**
+         * Gets the message's to addresses.
+         * @return the message's to addresses
+         */
+        public String[] getToAddresses() {
+           return getAddresses(MessageColumns.TO, mToIndex);
+        }
+
+        /**
+         * Gets the message's cc addresses.
+         * @return the message's cc addresses
+         */
+        public String[] getCcAddresses() {
+            return getAddresses(MessageColumns.CC, mCcIndex);
+        }
+
+        /**
+         * Gets the message's bcc addresses.
+         * @return the message's bcc addresses
+         */
+        public String[] getBccAddresses() {
+            return getAddresses(MessageColumns.BCC, mBccIndex);
+        }
+
+        /**
+         * Gets the message's replyTo address.
+         *
+         * @return the message's replyTo address
+         */
+        public String[] getReplyToAddress() {
+            return TextUtils.split(getStringInColumn(mReplyToIndex), EMAIL_SEPARATOR_PATTERN);
+        }
+
+        public long getDateSentMs() {
+            checkCursor();
+            return mCursor.getLong(mDateSentMsIndex);
+        }
+
+        public long getDateReceivedMs() {
+            checkCursor();
+            return mCursor.getLong(mDateReceivedMsIndex);
+        }
+
+        public String getListInfo() {
+            return getStringInColumn(mListInfoIndex);
+        }
+
+        public PersonalLevel getPersonalLevel() {
+            checkCursor();
+            int personalLevelInt = mCursor.getInt(mPersonalLevelIndex);
+            return PersonalLevel.fromInt(personalLevelInt);
+        }
+
+        /**
+         * @deprecated
+         */
+        public boolean getExpanded() {
+            return true;
+        }
+
+        /**
+         * Gets the message's body.
+         *
+         * @return the message's body
+         */
+        public String getBody() {
+            return getStringInColumn(mBodyIndex);
+        }
+
+        /**
+         * @return whether the message's body contains embedded references to external resources. In
+         * that case the resources should only be displayed if the user explicitly asks for them to
+         * be
+         */
+        public boolean getBodyEmbedsExternalResources() {
+            checkCursor();
+            return mCursor.getInt(mBodyEmbedsExternalResourcesIndex) != 0;
+        }
+
+        /**
+         * @return a copy of the set of label ids
+         */
+        public Set<Long> getLabelIds() {
+            String labelNames = mCursor.getString(mLabelIdsIndex);
+            mLabelIdsSplitter.setString(labelNames);
+            return getLabelIdsFromLabelIdsString(mLabelIdsSplitter);
+        }
+
+        /**
+         * @return a joined string of labels separated by spaces.
+         */
+        public String getRawLabelIds() {
+            return mCursor.getString(mLabelIdsIndex);
+        }
+
+        /**
+         * Adds a label to a message (if add is true) or removes it (if add is
+         * false).
+         *
+         * @param label the label to add or remove
+         * @param add whether to add or remove the label
+         */
+        public void addOrRemoveLabel(String label, boolean add) {
+            addOrRemoveLabelOnMessage(mContentResolver, mAccount, getConversationId(),
+                    getMessageId(), label, add);
+        }
+
+        public ArrayList<Attachment> getAttachmentInfos() {
+            ArrayList<Attachment> attachments = Lists.newArrayList();
+
+            String joinedAttachmentInfos = mCursor.getString(mJoinedAttachmentInfosIndex);
+            if (joinedAttachmentInfos != null) {
+                for (String joinedAttachmentInfo :
+                        TextUtils.split(joinedAttachmentInfos, ATTACHMENT_INFO_SEPARATOR_PATTERN)) {
+
+                    Attachment attachment = Attachment.parseJoinedString(joinedAttachmentInfo);
+                    attachments.add(attachment);
+                }
+            }
+            return attachments;
+        }
+
+        /**
+         * @return the error text for the message. Error text gets set if the server rejects a
+         * message that we try to save or send. If there is error text then the message is no longer
+         * scheduled to be saved or sent. Calling save() or send() will clear any error as well as
+         * scheduling another atempt to save or send the message.
+         */
+        public String getErrorText() {
+            return mCursor.getString(mErrorIndex);
+        }
+    }
+
+    /**
+     * A helper class for creating or updating messags. Use the putXxx methods to provide initial or
+     * new values for the message. Then save or send the message. To save or send an existing
+     * message without making other changes to it simply provide an emty ContentValues.
+     */
+    public static class MessageModification {
+
+        /**
+         * Sets the message's subject. Only valid for drafts.
+         *
+         * @param values the ContentValues that will be used to create or update the message
+         * @param subject the new subject
+         */
+        public static void putSubject(ContentValues values, String subject) {
+            values.put(MessageColumns.SUBJECT, subject);
+        }
+
+        /**
+         * Sets the message's to address. Only valid for drafts.
+         *
+         * @param values the ContentValues that will be used to create or update the message
+         * @param toAddresses the new to addresses
+         */
+        public static void putToAddresses(ContentValues values, String[] toAddresses) {
+            values.put(MessageColumns.TO, TextUtils.join(EMAIL_SEPARATOR, toAddresses));
+        }
+
+        /**
+         * Sets the message's cc address. Only valid for drafts.
+         *
+         * @param values the ContentValues that will be used to create or update the message
+         * @param ccAddresses the new cc addresses
+         */
+        public static void putCcAddresses(ContentValues values, String[] ccAddresses) {
+            values.put(MessageColumns.CC, TextUtils.join(EMAIL_SEPARATOR, ccAddresses));
+        }
+
+        /**
+         * Sets the message's bcc address. Only valid for drafts.
+         *
+         * @param values the ContentValues that will be used to create or update the message
+         * @param bccAddresses the new bcc addresses
+         */
+        public static void putBccAddresses(ContentValues values, String[] bccAddresses) {
+            values.put(MessageColumns.BCC, TextUtils.join(EMAIL_SEPARATOR, bccAddresses));
+        }
+
+        /**
+         * Saves a new body for the message. Only valid for drafts.
+         *
+         * @param values the ContentValues that will be used to create or update the message
+         * @param body the new body of the message
+         */
+        public static void putBody(ContentValues values, String body) {
+            values.put(MessageColumns.BODY, body);
+        }
+
+        /**
+         * Sets the attachments on a message. Only valid for drafts.
+         *
+         * @param values the ContentValues that will be used to create or update the message
+         * @param attachments
+         */
+        public static void putAttachments(ContentValues values, List<Attachment> attachments) {
+            values.put(
+                    MessageColumns.JOINED_ATTACHMENT_INFOS, joinedAttachmentsString(attachments));
+        }
+
+        /**
+         * Create a new message and save it as a draft or send it.
+         *
+         * @param contentResolver the content resolver to use
+         * @param account the account to use
+         * @param values the values for the new message
+         * @param refMessageId the message that is being replied to or forwarded
+         * @param save whether to save or send the message
+         * @return the id of the new message
+         */
+        public static long sendOrSaveNewMessage(
+                ContentResolver contentResolver, String account,
+                ContentValues values, long refMessageId, boolean save) {
+            values.put(MessageColumns.FAKE_SAVE, save);
+            values.put(MessageColumns.FAKE_REF_MESSAGE_ID, refMessageId);
+            Uri uri = Uri.parse(AUTHORITY_PLUS_MESSAGES + account + "/");
+            Uri result = contentResolver.insert(uri, values);
+            return ContentUris.parseId(result);
+        }
+
+        /**
+         * Update an existing draft and save it as a new draft or send it.
+         *
+         * @param contentResolver the content resolver to use
+         * @param account the account to use
+         * @param messageId the id of the message to update
+         * @param updateValues the values to change. Unspecified fields will not be altered
+         * @param save whether to resave the message as a draft or send it
+         */
+        public static void sendOrSaveExistingMessage(
+                ContentResolver contentResolver, String account, long messageId,
+                ContentValues updateValues, boolean save) {
+            updateValues.put(MessageColumns.FAKE_SAVE, save);
+            updateValues.put(MessageColumns.FAKE_REF_MESSAGE_ID, 0);
+            Uri uri = Uri.parse(
+                    AUTHORITY_PLUS_MESSAGES + account + "/" + messageId);
+            contentResolver.update(uri, updateValues, null, null);
+        }
+
+        /**
+         * The string produced here is parsed by Gmail.MessageCursor#getAttachmentInfos.
+         */
+        public static String joinedAttachmentsString(List<Gmail.Attachment> attachments) {
+            StringBuilder attachmentsSb = new StringBuilder();
+            for (Gmail.Attachment attachment : attachments) {
+                if (attachmentsSb.length() != 0) {
+                    attachmentsSb.append(Gmail.ATTACHMENT_INFO_SEPARATOR);
+                }
+                attachmentsSb.append(attachment.toJoinedString());
+            }
+            return attachmentsSb.toString();
+        }
+
+    }
+
+    /**
+     * A cursor over conversations.
+     *
+     * "Conversation" refers to the information needed to populate a list of
+     * conversations, not all of the messages in a conversation.
+     */
+    public static final class ConversationCursor extends MailCursor {
+
+        private LabelMap mLabelMap;
+
+        private int mConversationIdIndex;
+        private int mSubjectIndex;
+        private int mSnippetIndex;
+        private int mFromIndex;
+        private int mDateIndex;
+        private int mPersonalLevelIndex;
+        private int mLabelIdsIndex;
+        private int mNumMessagesIndex;
+        private int mMaxMessageIdIndex;
+        private int mHasAttachmentsIndex;
+        private int mHasMessagesWithErrorsIndex;
+        private int mForceAllUnreadIndex;
+
+        private TextUtils.StringSplitter mLabelIdsSplitter = newConversationLabelIdsSplitter();
+
+        private ConversationCursor(Gmail gmail, String account, Cursor cursor) {
+            super(account, cursor);
+            mLabelMap = gmail.getLabelMap(account);
+
+            mConversationIdIndex =
+                    mCursor.getColumnIndexOrThrow(ConversationColumns.ID);
+            mSubjectIndex = mCursor.getColumnIndexOrThrow(ConversationColumns.SUBJECT);
+            mSnippetIndex = mCursor.getColumnIndexOrThrow(ConversationColumns.SNIPPET);
+            mFromIndex = mCursor.getColumnIndexOrThrow(ConversationColumns.FROM);
+            mDateIndex = mCursor.getColumnIndexOrThrow(ConversationColumns.DATE);
+            mPersonalLevelIndex =
+                    mCursor.getColumnIndexOrThrow(ConversationColumns.PERSONAL_LEVEL);
+            mLabelIdsIndex =
+                    mCursor.getColumnIndexOrThrow(ConversationColumns.LABEL_IDS);
+            mNumMessagesIndex = mCursor.getColumnIndexOrThrow(ConversationColumns.NUM_MESSAGES);
+            mMaxMessageIdIndex = mCursor.getColumnIndexOrThrow(ConversationColumns.MAX_MESSAGE_ID);
+            mHasAttachmentsIndex =
+                    mCursor.getColumnIndexOrThrow(ConversationColumns.HAS_ATTACHMENTS);
+            mHasMessagesWithErrorsIndex =
+                    mCursor.getColumnIndexOrThrow(ConversationColumns.HAS_MESSAGES_WITH_ERRORS);
+            mForceAllUnreadIndex =
+                    mCursor.getColumnIndexOrThrow(ConversationColumns.FORCE_ALL_UNREAD);
+        }
+
+        @Override
+        protected void onCursorPositionChanged() {
+            super.onCursorPositionChanged();
+        }
+
+        public CursorStatus getStatus() {
+            Bundle extras = mCursor.getExtras();
+            String stringStatus = extras.getString(EXTRA_STATUS);
+            return CursorStatus.valueOf(stringStatus);
+        }
+
+        /** Retry a network request after errors. */
+        public void retry() {
+            Bundle input = new Bundle();
+            input.putString(RESPOND_INPUT_COMMAND, COMMAND_RETRY);
+            Bundle output = mCursor.respond(input);
+            String response = output.getString(RESPOND_OUTPUT_COMMAND_RESPONSE);
+            assert COMMAND_RESPONSE_OK.equals(response);
+        }
+
+        /**
+         * When a conversation cursor is created it becomes the active network cursor, which means
+         * that it will fetch results from the network if it needs to in order to show all mail that
+         * matches its query. If you later want to requery an older cursor and would like that
+         * cursor to be the active cursor you need to call this method before requerying.
+         */
+        public void becomeActiveNetworkCursor() {
+            Bundle input = new Bundle();
+            input.putString(RESPOND_INPUT_COMMAND, COMMAND_ACTIVATE);
+            Bundle output = mCursor.respond(input);
+            String response = output.getString(RESPOND_OUTPUT_COMMAND_RESPONSE);
+            assert COMMAND_RESPONSE_OK.equals(response);
+        }
+
+        /**
+         * Tells the cursor whether its contents are visible to the user. The cursor will
+         * automatically broadcast intents to remove any matching new-mail notifications when this
+         * cursor's results become visible and, if they are visible, when the cursor is requeried.
+         *
+         * Note that contents shown in an activity that is resumed but not focused
+         * (onWindowFocusChanged/hasWindowFocus) then results shown in that activity do not count
+         * as visible. (This happens when the activity is behind the lock screen or a dialog.)
+         *
+         * @param visible whether the contents of this cursor are visible to the user.
+         */
+        public void setContentsVisibleToUser(boolean visible) {
+            Bundle input = new Bundle();
+            input.putString(RESPOND_INPUT_COMMAND, COMMAND_SET_VISIBLE);
+            input.putBoolean(SET_VISIBLE_PARAM_VISIBLE, visible);
+            Bundle output = mCursor.respond(input);
+            String response = output.getString(RESPOND_OUTPUT_COMMAND_RESPONSE);
+            assert COMMAND_RESPONSE_OK.equals(response);
+        }
+
+        /**
+         * Gets the conversation id. This is immutable. (The server calls it the original
+         * conversation id.)
+         *
+         * @return the conversation id
+         */
+        public long getConversationId() {
+            return mCursor.getLong(mConversationIdIndex);
+        }
+
+        /**
+         * Returns the instructions for building from snippets. Pass this to getFromSnippetHtml
+         * in order to actually build the snippets.
+         * @return snippet instructions for use by getFromSnippetHtml()
+         */
+        public String getFromSnippetInstructions() {
+            return getStringInColumn(mFromIndex);
+        }
+
+        /**
+         * Gets the conversation's subject.
+         *
+         * @return the subject
+         */
+        public String getSubject() {
+            return getStringInColumn(mSubjectIndex);
+        }
+
+        /**
+         * Gets the conversation's snippet.
+         *
+         * @return the snippet
+         */
+        public String getSnippet() {
+            return getStringInColumn(mSnippetIndex);
+        }
+
+        /**
+         * Get's the conversation's personal level.
+         *
+         * @return the personal level.
+         */
+        public PersonalLevel getPersonalLevel() {
+            int personalLevelInt = mCursor.getInt(mPersonalLevelIndex);
+            return PersonalLevel.fromInt(personalLevelInt);
+        }
+
+        /**
+         * @return a copy of the set of labels. To add or remove labels call
+         *         MessageCursor.addOrRemoveLabel on each message in the conversation.
+         * @deprecated use getLabelIds
+         */
+        public Set<String> getLabels() {
+            return getLabels(getRawLabelIds(), mLabelMap);
+        }
+
+        /**
+         * @return a copy of the set of labels. To add or remove labels call
+         *         MessageCursor.addOrRemoveLabel on each message in the conversation.
+         */
+        public Set<Long> getLabelIds() {
+            mLabelIdsSplitter.setString(getRawLabelIds());
+            return getLabelIdsFromLabelIdsString(mLabelIdsSplitter);
+        }
+
+        /**
+         * Returns the set of labels using the raw labels from a previous getRawLabels()
+         * as input.
+         * @return a copy of the set of labels. To add or remove labels call
+         * MessageCursor.addOrRemoveLabel on each message in the conversation.
+         */
+        public Set<String> getLabels(String rawLabelIds, LabelMap labelMap) {
+            mLabelIdsSplitter.setString(rawLabelIds);
+            return getCanonicalNamesFromLabelIdsString(labelMap, mLabelIdsSplitter);
+        }
+
+        /**
+         * @return a joined string of labels separated by spaces. Use
+         * getLabels(rawLabels) to convert this to a Set of labels.
+         */
+        public String getRawLabelIds() {
+            return mCursor.getString(mLabelIdsIndex);
+        }
+
+        /**
+         * @return the number of messages in the conversation
+         */
+        public int getNumMessages() {
+            return mCursor.getInt(mNumMessagesIndex);
+        }
+
+        /**
+         * @return the max message id in the conversation
+         */
+        public long getMaxServerMessageId() {
+            return mCursor.getLong(mMaxMessageIdIndex);
+        }
+
+        public long getDateMs() {
+            return mCursor.getLong(mDateIndex);
+        }
+
+        public boolean hasAttachments() {
+            return mCursor.getInt(mHasAttachmentsIndex) != 0;
+        }
+
+        public boolean hasMessagesWithErrors() {
+            return mCursor.getInt(mHasMessagesWithErrorsIndex) != 0;
+        }
+
+        public boolean getForceAllUnread() {
+            return !mCursor.isNull(mForceAllUnreadIndex)
+                    && mCursor.getInt(mForceAllUnreadIndex) != 0;
+        }
+    }
+}
diff --git a/core/java/android/provider/Im.java b/core/java/android/provider/Im.java
new file mode 100644
index 0000000..19ad158
--- /dev/null
+++ b/core/java/android/provider/Im.java
@@ -0,0 +1,2080 @@
+/*
+ * Copyright (C) 2007 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 android.provider;
+
+import android.content.ContentQueryMap;
+import android.content.ContentResolver;
+import android.content.ContentUris;
+import android.content.ContentValues;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Handler;
+
+import java.util.HashMap;
+
+/**
+ * The IM provider stores all information about roster contacts, chat messages, presence, etc.
+ *
+ * @hide
+ */
+public class Im {
+    /**
+     * no public constructor since this is a utility class
+     */
+    private Im() {}
+
+    /**
+     * The Columns for IM providers (i.e. AIM, Y!, GTalk)
+     */
+    public interface ProviderColumns {
+        /**
+         * The name of the IM provider
+         * <P>Type: TEXT</P>
+         */
+        String NAME = "name";
+
+        /**
+         * The full name of the provider
+         * <P>Type: TEXT</P>
+         */
+        String FULLNAME = "fullname";
+
+        /**
+         * The category for the provider, used to form intent.
+         * <P>Type: TEXT</P>
+         */
+        String CATEGORY = "category";
+
+        /**
+         * The url users should visit to create a new account for this provider
+         * <P>Type: TEXT</P>
+         */
+        String SIGNUP_URL = "signup_url";
+    }
+
+    /**
+     * Known names corresponding to the {@link ProviderColumns#NAME} column
+     */
+    public interface ProviderNames {
+        //
+        //NOTE: update Contacts.java with new providers when they're added.
+        //
+        String YAHOO = "Yahoo";
+        String GTALK = "GTalk";
+        String MSN = "MSN";
+        String ICQ = "ICQ";
+        String AIM = "AIM";
+        String XMPP = "XMPP";
+        String JABBER = "JABBER";
+        String SKYPE = "SKYPE";
+        String QQ = "QQ";
+    }
+
+    /**
+     * This table contains the IM providers
+     */
+    public static final class Provider implements BaseColumns, ProviderColumns {
+        private Provider() {}
+
+        public static final long getProviderIdForName(ContentResolver cr, String providerName) {
+            String[] selectionArgs = new String[1];
+            selectionArgs[0] = providerName;
+
+            Cursor cursor = cr.query(CONTENT_URI,
+                    PROVIDER_PROJECTION,
+                    NAME+"=?",
+                    selectionArgs, null);
+
+            long retVal = 0;
+            try {
+                if (cursor.moveToFirst()) {
+                    retVal = cursor.getLong(cursor.getColumnIndexOrThrow(_ID));
+                }
+            } finally {
+                cursor.close();
+            }
+
+            return retVal;
+        }
+
+        public static final String getProviderNameForId(ContentResolver cr, long providerId) {
+            Cursor cursor = cr.query(CONTENT_URI,
+                    PROVIDER_PROJECTION,
+                    _ID + "=" + providerId,
+                    null, null);
+
+            String retVal = null;
+            try {
+                if (cursor.moveToFirst()) {
+                    retVal = cursor.getString(cursor.getColumnIndexOrThrow(NAME));
+                }
+            } finally {
+                cursor.close();
+            }
+
+            return retVal;
+        }
+
+        private static final String[] PROVIDER_PROJECTION = new String[] {
+                _ID,
+                NAME
+        };
+
+        public static final String ACTIVE_ACCOUNT_ID = "account_id";
+        public static final String ACTIVE_ACCOUNT_USERNAME = "account_username";
+        public static final String ACTIVE_ACCOUNT_PW = "account_pw";
+        public static final String ACTIVE_ACCOUNT_LOCKED = "account_locked";
+        public static final String ACCOUNT_PRESENCE_STATUS = "account_presenceStatus";
+        public static final String ACCOUNT_CONNECTION_STATUS = "account_connStatus";
+
+        /**
+         * The content:// style URL for this table
+         */
+        public static final Uri CONTENT_URI =
+            Uri.parse("content://im/providers");
+
+        public static final Uri CONTENT_URI_WITH_ACCOUNT =
+            Uri.parse("content://im/providers/account");
+
+        /**
+         * The MIME type of {@link #CONTENT_URI} providing a directory of
+         * people.
+         */
+        public static final String CONTENT_TYPE =
+                "vnd.android.cursor.dir/im-providers";
+
+        public static final String CONTENT_ITEM_TYPE =
+                "vnd.android.cursor.item/im-providers";
+
+        /**
+         * The default sort order for this table
+         */
+        public static final String DEFAULT_SORT_ORDER = "name ASC";
+    }
+
+    /**
+     * The columns for IM accounts. There can be more than one account for each IM provider.
+     */
+    public interface AccountColumns {
+        /**
+         * The name of the account
+         * <P>Type: TEXT</P>
+         */
+        String NAME = "name";
+
+        /**
+         * The IM provider for this account
+         * <P>Type: INTEGER</P>
+         */
+        String PROVIDER = "provider";
+
+        /**
+         * The username for this account
+         * <P>Type: TEXT</P>
+         */
+        String USERNAME = "username";
+
+        /**
+         * The password for this account
+         * <P>Type: TEXT</P>
+         */
+        String PASSWORD = "pw";
+
+        /**
+         * A boolean value indicates if the account is active.
+         * <P>Type: INTEGER</P>
+         */
+        String ACTIVE = "active";
+
+        /**
+         * A boolean value indicates if the account is locked (not editable)
+         * <P>Type: INTEGER</P>
+         */
+        String LOCKED = "locked";
+
+        /**
+         * A boolean value to indicate whether this account is kept signed in.
+         * <P>Type: INTEGER</P>
+         */
+        String KEEP_SIGNED_IN = "keep_signed_in";
+
+        /**
+         * A boolean value indiciating the last login state for this account
+         * <P>Type: INTEGER</P>
+         */
+        String LAST_LOGIN_STATE = "last_login_state";
+    }
+
+    /**
+     * This table contains the IM accounts.
+     */
+    public static final class Account implements BaseColumns, AccountColumns {
+        private Account() {}
+
+        public static final long getProviderIdForAccount(ContentResolver cr, long accountId) {
+            Cursor cursor = cr.query(CONTENT_URI,
+                    PROVIDER_PROJECTION,
+                    _ID + "=" + accountId,
+                    null /* selection args */,
+                    null /* sort order */);
+
+            long providerId = 0;
+
+            try {
+                if (cursor.moveToFirst()) {
+                    providerId = cursor.getLong(PROVIDER_COLUMN);
+                }
+            } finally {
+                cursor.close();
+            }
+
+            return providerId;
+        }
+
+        private static final String[] PROVIDER_PROJECTION = new String[] { PROVIDER };
+        private static final int PROVIDER_COLUMN = 0;
+
+        /**
+         * The content:// style URL for this table
+         */
+        public static final Uri CONTENT_URI =
+            Uri.parse("content://im/accounts");
+
+        /**
+         * The MIME type of {@link #CONTENT_URI} providing a directory of
+         * account.
+         */
+        public static final String CONTENT_TYPE =
+                "vnd.android.cursor.dir/im-accounts";
+
+        /**
+         * The MIME type of a {@link #CONTENT_URI} subdirectory of a single
+         * account.
+         */
+        public static final String CONTENT_ITEM_TYPE =
+                "vnd.android.cursor.item/im-accounts";
+
+        /**
+         * The default sort order for this table
+         */
+        public static final String DEFAULT_SORT_ORDER = "name ASC";
+
+    }
+
+    /**
+     * Connection status
+     */
+    public interface ConnectionStatus {
+        /**
+         * The connection is offline, not logged in.
+         */
+        int OFFLINE = 0;
+
+        /**
+         * The connection is attempting to connect.
+         */
+        int CONNECTING = 1;
+
+        /**
+         * The connection is suspended due to network not available.
+         */
+        int SUSPENDED = 2;
+
+        /**
+         * The connection is logged in and online.
+         */
+        int ONLINE = 3;
+    }
+
+    public interface AccountStatusColumns {
+        /**
+         * account id
+         * <P>Type: INTEGER</P>
+         */
+        String ACCOUNT = "account";
+
+        /**
+         * User's presence status, see definitions in {#link CommonPresenceColumn}
+         * <P>Type: INTEGER</P>
+         */
+        String PRESENCE_STATUS = "presenceStatus";
+
+        /**
+         * The connection status of this account, see {#link ConnectionStatus}
+         * <P>Type: INTEGER</P>
+         */
+        String CONNECTION_STATUS = "connStatus";
+    }
+
+    public static final class AccountStatus implements BaseColumns, AccountStatusColumns {
+        /**
+         * The content:// style URL for this table
+         */
+        public static final Uri CONTENT_URI =
+            Uri.parse("content://im/accountStatus");
+
+        /**
+         * The MIME type of {@link #CONTENT_URI} providing a directory of account status.
+         */
+        public static final String CONTENT_TYPE =
+                "vnd.android.cursor.dir/im-account-status";
+
+        /**
+         * The MIME type of a {@link #CONTENT_URI} subdirectory of a single account status.
+         */
+        public static final String CONTENT_ITEM_TYPE =
+                "vnd.android.cursor.item/im-account-status";
+
+        /**
+         * The default sort order for this table
+         */
+        public static final String DEFAULT_SORT_ORDER = "name ASC";
+    }
+
+    /**
+     * Columns from the Contacts table.
+     */
+    public interface ContactsColumns {
+        /**
+         * The username
+         * <P>Type: TEXT</P>
+         */
+        String USERNAME = "username";
+
+        /**
+         * The nickname or display name
+         * <P>Type: TEXT</P>
+         */
+        String NICKNAME = "nickname";
+
+        /**
+         * The IM provider for this contact
+         * <P>Type: INTEGER</P>
+         */
+        String PROVIDER = "provider";
+
+        /**
+         * The account (within a IM provider) for this contact
+         * <P>Type: INTEGER</P>
+         */
+        String ACCOUNT = "account";
+
+        /**
+         * The contactList this contact belongs to
+         * <P>Type: INTEGER</P>
+         */
+        String CONTACTLIST = "contactList";
+
+        /**
+         * Contact type
+         * <P>Type: INTEGER</P>
+         */
+        String TYPE = "type";
+
+        /**
+         * normal IM contact
+         */
+        int TYPE_NORMAL = 0;
+        /**
+         * temporary contact, someone not in the list of contacts that we
+         * subscribe presence for. Usually created because of the user is
+         * having a chat session with this contact.
+         */
+        int TYPE_TEMPORARY = 1;
+        /**
+         * temporary contact created for group chat.
+         */
+        int TYPE_GROUP = 2;
+        /**
+         * blocked contact.
+         */
+        int TYPE_BLOCKED = 3;
+        /**
+         * the contact is hidden. The client should always display this contact to the user.
+         */
+        int TYPE_HIDDEN = 4;
+        /**
+         * the contact is pinned. The client should always display this contact to the user.
+         */
+        int TYPE_PINNED = 5;
+
+        /**
+         * Contact subscription status
+         * <P>Type: INTEGER</P>
+         */
+        String SUBSCRIPTION_STATUS = "subscriptionStatus";
+
+        /**
+         * no pending subscription
+         */
+        int SUBSCRIPTION_STATUS_NONE = 0;
+        /**
+         * requested to subscribe
+         */
+        int SUBSCRIPTION_STATUS_SUBSCRIBE_PENDING = 1;
+        /**
+         * requested to unsubscribe
+         */
+        int SUBSCRIPTION_STATUS_UNSUBSCRIBE_PENDING = 2;
+
+        /**
+         * Contact subscription type
+         * <P>Type: INTEGER </P>
+         */
+        String SUBSCRIPTION_TYPE = "subscriptionType";
+
+        /**
+         * The user and contact have no interest in each other's presence.
+         */
+        int SUBSCRIPTION_TYPE_NONE = 0;
+        /**
+         * The user wishes to stop receiving presence updates from the contact.
+         */
+        int SUBSCRIPTION_TYPE_REMOVE = 1;
+        /**
+         * The user is interested in receiving presence updates from the contact.
+         */
+        int SUBSCRIPTION_TYPE_TO = 2;
+        /**
+         * The contact is interested in receiving presence updates from the user.
+         */
+        int SUBSCRIPTION_TYPE_FROM = 3;
+        /**
+         * The user and contact have a mutual interest in each other's presence.
+         */
+        int SUBSCRIPTION_TYPE_BOTH = 4;
+        /**
+         * This is a special type reserved for pending subscription requests
+         */
+        int SUBSCRIPTION_TYPE_INVITATIONS = 5;
+
+        /**
+         * Quick Contact: derived from Google Contact Extension's "message_count" attribute.
+         * <P>Type: INTEGER</P>
+         */
+        String QUICK_CONTACT = "qc";
+
+        /**
+         * Google Contact Extension attribute
+         *
+         * Rejected: a boolean value indicating whether a subscription request from
+         * this client was ever rejected by the user. "true" indicates that it has.
+         * This is provided so that a client can block repeated subscription requests.
+         * <P>Type: INTEGER</P>
+         */
+        String REJECTED = "rejected";
+
+        /**
+         * Off The Record status: 0 for disabled, 1 for enabled
+         * <P>Type: INTEGER </P>
+         */
+        String OTR = "otr";
+    }
+
+    /**
+     * This defines the different type of values of {@link ContactsColumns#OTR}
+     */
+    public interface OffTheRecordType {
+        /*
+         * Off the record not turned on
+         */
+        int DISABLED = 0;
+        /**
+         * Off the record turned on, but we don't know who turned it on
+         */
+        int ENABLED = 1;
+        /**
+         * Off the record turned on by the user
+         */
+        int ENABLED_BY_USER = 2;
+        /**
+         * Off the record turned on by the buddy
+         */
+        int ENABLED_BY_BUDDY = 3;
+    };
+
+    /**
+     * This table contains contacts.
+     */
+    public static final class Contacts implements BaseColumns,
+            ContactsColumns, PresenceColumns, ChatsColumns {
+        /**
+         * no public constructor since this is a utility class
+         */
+        private Contacts() {}
+
+        /**
+         * The content:// style URL for this table
+         */
+        public static final Uri CONTENT_URI =
+            Uri.parse("content://im/contacts");
+
+        /**
+         * The content:// style URL for contacts joined with presence
+         */
+        public static final Uri CONTENT_URI_WITH_PRESENCE =
+            Uri.parse("content://im/contactsWithPresence");
+
+        /**
+         * The content:// style URL for barebone contacts, not joined with any other table
+         */
+        public static final Uri CONTENT_URI_CONTACTS_BAREBONE =
+            Uri.parse("content://im/contactsBarebone");
+
+        /**
+         * The content:// style URL for contacts who have an open chat session
+         */
+        public static final Uri CONTENT_URI_CHAT_CONTACTS =
+            Uri.parse("content://im/contacts/chatting");
+
+        /**
+         * The content:// style URL for contacts who have been blocked
+         */
+        public static final Uri CONTENT_URI_BLOCKED_CONTACTS =
+            Uri.parse("content://im/contacts/blocked");
+
+        /**
+         * The content:// style URL for contacts by provider and account
+         */
+        public static final Uri CONTENT_URI_CONTACTS_BY =
+            Uri.parse("content://im/contacts");
+
+        /**
+         * The content:// style URL for contacts by provider and account,
+         * and who have an open chat session
+         */
+        public static final Uri CONTENT_URI_CHAT_CONTACTS_BY =
+            Uri.parse("content://im/contacts/chatting");
+
+        /**
+         * The content:// style URL for contacts by provider and account,
+         * and who are online
+         */
+        public static final Uri CONTENT_URI_ONLINE_CONTACTS_BY =
+            Uri.parse("content://im/contacts/online");
+
+        /**
+         * The content:// style URL for contacts by provider and account,
+         * and who are offline
+         */
+        public static final Uri CONTENT_URI_OFFLINE_CONTACTS_BY =
+            Uri.parse("content://im/contacts/offline");
+
+        /**
+         * The content:// style URL for operations on bulk contacts
+         */
+        public static final Uri BULK_CONTENT_URI =
+                Uri.parse("content://im/bulk_contacts");
+
+        /**
+         * The content:// style URL for the count of online contacts in each
+         * contact list by provider and account.
+         */
+        public static final Uri CONTENT_URI_ONLINE_COUNT =
+            Uri.parse("content://im/contacts/onlineCount");
+
+        /**
+         * The MIME type of {@link #CONTENT_URI} providing a directory of
+         * people.
+         */
+        public static final String CONTENT_TYPE = "vnd.android.cursor.dir/im-contacts";
+
+        /**
+         * The MIME type of a {@link #CONTENT_URI} subdirectory of a single
+         * person.
+         */
+        public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/im-contacts";
+
+        /**
+         * The default sort order for this table
+         */
+        public static final String DEFAULT_SORT_ORDER =
+                "subscriptionType DESC, last_message_date DESC," +
+                        " mode DESC, nickname COLLATE UNICODE ASC";
+
+        public static final String CHATS_CONTACT = "chats_contact";
+
+        public static final String AVATAR_HASH = "avatars_hash";
+
+        public static final String AVATAR_DATA = "avatars_data";
+    }
+
+    /**
+     * Columns from the ContactList table.
+     */
+    public interface ContactListColumns {
+        String NAME = "name";
+        String PROVIDER = "provider";
+        String ACCOUNT = "account";
+    }
+
+    /**
+     * This table contains the contact lists.
+     */
+    public static final class ContactList implements BaseColumns,
+            ContactListColumns {
+        private ContactList() {}
+
+        /**
+         * The content:// style URL for this table
+         */
+        public static final Uri CONTENT_URI =
+            Uri.parse("content://im/contactLists");
+
+        /**
+         * The MIME type of {@link #CONTENT_URI} providing a directory of
+         * people.
+         */
+        public static final String CONTENT_TYPE =
+                "vnd.android.cursor.dir/im-contactLists";
+
+        /**
+         * The MIME type of a {@link #CONTENT_URI} subdirectory of a single
+         * person.
+         */
+        public static final String CONTENT_ITEM_TYPE =
+                "vnd.android.cursor.item/im-contactLists";
+
+        /**
+         * The default sort order for this table
+         */
+        public static final String DEFAULT_SORT_ORDER = "name COLLATE UNICODE ASC";
+
+        public static final String PROVIDER_NAME = "provider_name";
+
+        public static final String ACCOUNT_NAME = "account_name";
+    }
+
+    /**
+     * Columns from the BlockedList table.
+     */
+    public interface BlockedListColumns {
+        /**
+         * The username of the blocked contact.
+         * <P>Type: TEXT</P>
+         */
+        String USERNAME = "username";
+
+        /**
+         * The nickname of the blocked contact.
+         * <P>Type: TEXT</P>
+         */
+        String NICKNAME = "nickname";
+
+        /**
+         * The provider id of the blocked contact.
+         * <P>Type: INT</P>
+         */
+        String PROVIDER = "provider";
+
+        /**
+         * The account id of the blocked contact.
+         * <P>Type: INT</P>
+         */
+        String ACCOUNT = "account";
+    }
+
+    /**
+     * This table contains blocked lists
+     */
+    public static final class BlockedList implements BaseColumns, BlockedListColumns {
+        private BlockedList() {}
+
+        /**
+         * The content:// style URL for this table
+         */
+        public static final Uri CONTENT_URI =
+            Uri.parse("content://im/blockedList");
+
+        /**
+         * The MIME type of {@link #CONTENT_URI} providing a directory of
+         * people.
+         */
+        public static final String CONTENT_TYPE =
+                "vnd.android.cursor.dir/im-blockedList";
+
+        /**
+         * The MIME type of a {@link #CONTENT_URI} subdirectory of a single
+         * person.
+         */
+        public static final String CONTENT_ITEM_TYPE =
+                "vnd.android.cursor.item/im-blockedList";
+
+        /**
+         * The default sort order for this table
+         */
+        public static final String DEFAULT_SORT_ORDER = "nickname ASC";
+
+        public static final String PROVIDER_NAME = "provider_name";
+
+        public static final String ACCOUNT_NAME = "account_name";
+
+        public static final String AVATAR_DATA = "avatars_data";
+    }
+
+    /**
+     * Columns from the contactsEtag table
+     */
+    public interface ContactsEtagColumns {
+        /**
+         * The roster etag, computed by the server, stored on the client. There is one etag
+         * per account roster.
+         * <P>Type: TEXT</P>
+         */
+        String ETAG = "etag";
+
+        /**
+         * The OTR etag, computed by the server, stored on the client. There is one OTR etag
+         * per account roster.
+         * <P>Type: TEXT</P>
+         */
+        String OTR_ETAG = "otr_etag";
+
+        /**
+         * The account id for the etag.
+         * <P> Type: INTEGER </P>
+         */
+        String ACCOUNT = "account";
+    }
+
+    public static final class ContactsEtag implements BaseColumns, ContactsEtagColumns {
+        private ContactsEtag() {}
+
+        public static final Cursor query(ContentResolver cr,
+                String[] projection) {
+            return cr.query(CONTENT_URI, projection, null, null, null);
+        }
+
+        public static final Cursor query(ContentResolver cr,
+                String[] projection, String where, String orderBy) {
+            return cr.query(CONTENT_URI, projection, where,
+                    null, orderBy == null ? null : orderBy);
+        }
+
+        public static final String getRosterEtag(ContentResolver resolver, long accountId) {
+            String retVal = null;
+
+            Cursor c = resolver.query(CONTENT_URI,
+                    CONTACT_ETAG_PROJECTION,
+                    ACCOUNT + "=" + accountId,
+                    null /* selection args */,
+                    null /* sort order */);
+
+            try {
+                if (c.moveToFirst()) {
+                    retVal = c.getString(COLUMN_ETAG);
+                }
+            } finally {
+                c.close();
+            }
+
+            return retVal;
+        }
+
+        public static final String getOtrEtag(ContentResolver resolver, long accountId) {
+            String retVal = null;
+
+            Cursor c = resolver.query(CONTENT_URI,
+                    CONTACT_OTR_ETAG_PROJECTION,
+                    ACCOUNT + "=" + accountId,
+                    null /* selection args */,
+                    null /* sort order */);
+
+            try {
+                if (c.moveToFirst()) {
+                    retVal = c.getString(COLUMN_OTR_ETAG);
+                }
+            } finally {
+                c.close();
+            }
+
+            return retVal;
+        }
+
+        private static final String[] CONTACT_ETAG_PROJECTION = new String[] {
+                Im.ContactsEtag.ETAG    // 0
+        };
+
+        private static int COLUMN_ETAG = 0;
+
+        private static final String[] CONTACT_OTR_ETAG_PROJECTION = new String[] {
+                Im.ContactsEtag.OTR_ETAG    // 0
+        };
+
+        private static int COLUMN_OTR_ETAG = 0;
+
+        /**
+         * The content:// style URL for this table
+         */
+        public static final Uri CONTENT_URI =
+            Uri.parse("content://im/contactsEtag");
+
+        /**
+         * The MIME type of {@link #CONTENT_URI} providing a directory of
+         * people.
+         */
+        public static final String CONTENT_TYPE =
+                "vnd.android.cursor.dir/im-contactsEtag";
+
+        /**
+         * The MIME type of a {@link #CONTENT_URI} subdirectory of a single
+         * person.
+         */
+        public static final String CONTENT_ITEM_TYPE =
+                "vnd.android.cursor.item/im-contactsEtag";
+    }
+
+    /**
+     * Message type definition
+     */
+    public interface MessageType {
+        /* sent message */
+        int OUTGOING = 0;
+        /* received message */
+        int INCOMING = 1;
+        /* presence became available */
+        int PRESENCE_AVAILABLE = 2;
+        /* presence became away */
+        int PRESENCE_AWAY = 3;
+        /* presence became DND (busy) */
+        int PRESENCE_DND = 4;
+        /* presence became unavailable */
+        int PRESENCE_UNAVAILABLE = 5;
+        /* the message is converted to a group chat */
+        int CONVERT_TO_GROUPCHAT = 6;
+        /* generic status */
+        int STATUS = 7;
+        /* the message cannot be sent now, but will be sent later */
+        int POSTPONED = 8;
+        /* off The Record status is turned off */
+        int OTR_IS_TURNED_OFF = 9;
+        /* off the record status is turned on */
+        int OTR_IS_TURNED_ON = 10;
+        /* off the record status turned on by user */
+        int OTR_TURNED_ON_BY_USER = 11;
+        /* off the record status turned on by buddy */
+        int OTR_TURNED_ON_BY_BUDDY = 12;
+    }
+
+    /**
+     * The common columns for both one-to-one chat messages or group chat messages.
+     */
+    public interface BaseMessageColumns {
+        /**
+         * The user this message belongs to
+         * <P>Type: TEXT</P>
+         */
+        String CONTACT = "contact";
+
+        /**
+         * The body
+         * <P>Type: TEXT</P>
+         */
+        String BODY = "body";
+
+        /**
+         * The date this message is sent or received
+         * <P>Type: INTEGER</P>
+         */
+        String DATE = "date";
+
+        /**
+         * Message Type, see {@link MessageType}
+         * <P>Type: INTEGER</P>
+         */
+        String TYPE = "type";
+
+        /**
+         * Error Code: 0 means no error.
+         * <P>Type: INTEGER </P>
+         */
+        String ERROR_CODE = "err_code";
+
+        /**
+         * Error Message
+         * <P>Type: TEXT</P>
+         */
+        String ERROR_MESSAGE = "err_msg";
+
+        /**
+         * Packet ID, auto assigned by the GTalkService for outgoing messages or the
+         * GTalk server for incoming messages. The packet id field is optional for messages,
+         * so it could be null.
+         * <P>Type: STRING</P>
+         */
+        String PACKET_ID = "packet_id";
+    }
+
+    /**
+     * Columns from the Messages table.
+     */
+    public interface MessagesColumns extends BaseMessageColumns{
+        /**
+         * The provider id
+         * <P> Type: INTEGER </P>
+         */
+        String PROVIDER = "provider";
+
+        /**
+         * The account id
+         * <P> Type: INTEGER </P>
+         */
+        String ACCOUNT = "account";
+    }
+
+    /**
+     * This table contains messages.
+     */
+    public static final class Messages implements BaseColumns, MessagesColumns {
+        /**
+         * no public constructor since this is a utility class
+         */
+        private Messages() {}
+
+        /**
+         * Gets the Uri to query messages by contact.
+         *
+         * @param providerId the provider id of the contact.
+         * @param accountId the account id of the contact.
+         * @param username the user name of the contact.
+         * @return the Uri
+         */
+        public static final Uri getContentUriByContact(long providerId,
+                long accountId, String username) {
+            Uri.Builder builder = CONTENT_URI_MESSAGES_BY.buildUpon();
+            ContentUris.appendId(builder, providerId);
+            ContentUris.appendId(builder, accountId);
+            builder.appendPath(username);
+            return builder.build();
+        }
+
+        /**
+         * The content:// style URL for this table
+         */
+        public static final Uri CONTENT_URI =
+            Uri.parse("content://im/messages");
+
+        /**
+         * The content:// style URL for messages by provider and account
+         */
+        public static final Uri CONTENT_URI_MESSAGES_BY =
+            Uri.parse("content://im/messagesBy");
+
+        /**
+         * The MIME type of {@link #CONTENT_URI} providing a directory of
+         * people.
+         */
+        public static final String CONTENT_TYPE = "vnd.android.cursor.dir/im-messages";
+
+        /**
+         * The MIME type of a {@link #CONTENT_URI} subdirectory of a single
+         * person.
+         */
+        public static final String CONTENT_ITEM_TYPE =
+                "vnd.android.cursor.item/im-messages";
+
+        /**
+         * The default sort order for this table
+         */
+        public static final String DEFAULT_SORT_ORDER = "date ASC";
+
+    }
+
+    /**
+     * Columns for the GroupMember table.
+     */
+    public interface GroupMemberColumns {
+        /**
+         * The id of the group this member belongs to.
+         * <p>Type: INTEGER</p>
+         */
+        String GROUP = "groupId";
+
+        /**
+         * The full name of this member.
+         * <p>Type: TEXT</p>
+         */
+        String USERNAME = "username";
+
+        /**
+         * The nick name of this member.
+         * <p>Type: TEXT</p>
+         */
+        String NICKNAME = "nickname";
+    }
+
+    public final static class GroupMembers implements GroupMemberColumns {
+        private GroupMembers(){}
+
+        public static final Uri CONTENT_URI =
+            Uri.parse("content://im/groupMembers");
+
+        /**
+         * The MIME type of {@link #CONTENT_URI} providing a directory of
+         * group members.
+         */
+        public static final String CONTENT_TYPE =
+            "vnd.android.cursor.dir/im-groupMembers";
+
+        /**
+         * The MIME type of a {@link #CONTENT_URI} subdirectory of a single
+         * group member.
+         */
+        public static final String CONTENT_ITEM_TYPE =
+                "vnd.android.cursor.item/im-groupMembers";
+    }
+
+    /**
+     * Columns from the Invitation table.
+     */
+    public interface InvitationColumns {
+        /**
+         * The provider id.
+         * <p>Type: INTEGER</p>
+         */
+        String PROVIDER = "providerId";
+
+        /**
+         * The account id.
+         * <p>Type: INTEGER</p>
+         */
+        String ACCOUNT = "accountId";
+
+        /**
+         * The invitation id.
+         * <p>Type: TEXT</p>
+         */
+        String INVITE_ID = "inviteId";
+
+        /**
+         * The name of the sender of the invitation.
+         * <p>Type: TEXT</p>
+         */
+        String SENDER = "sender";
+
+        /**
+         * The name of the group which the sender invite you to join.
+         * <p>Type: TEXT</p>
+         */
+        String GROUP_NAME = "groupName";
+
+        /**
+         * A note
+         * <p>Type: TEXT</p>
+         */
+        String NOTE = "note";
+
+        /**
+         * The current status of the invitation.
+         * <p>Type: TEXT</p>
+         */
+        String STATUS = "status";
+
+        int STATUS_PENDING = 0;
+        int STATUS_ACCEPTED = 1;
+        int STATUS_REJECTED = 2;
+    }
+
+    /**
+     * This table contains the invitations received from others.
+     */
+    public final static class Invitation implements InvitationColumns,
+            BaseColumns {
+        private Invitation() {
+        }
+
+        /**
+         * The content:// style URL for this table
+         */
+        public static final Uri CONTENT_URI =
+            Uri.parse("content://im/invitations");
+
+        /**
+         * The MIME type of {@link #CONTENT_URI} providing a directory of
+         * invitations.
+         */
+        public static final String CONTENT_TYPE =
+            "vnd.android.cursor.dir/im-invitations";
+
+        /**
+         * The MIME type of a {@link #CONTENT_URI} subdirectory of a single
+         * invitation.
+         */
+        public static final String CONTENT_ITEM_TYPE =
+                "vnd.android.cursor.item/im-invitations";
+    }
+
+    /**
+     * Columns from the GroupMessages table
+     */
+    public interface GroupMessageColumns extends BaseMessageColumns {
+        /**
+         * The group this message belongs to
+         * <p>Type: TEXT</p>
+         */
+        String GROUP = "groupId";
+    }
+
+    /**
+     * This table contains group messages.
+     */
+    public final static class GroupMessages implements BaseColumns,
+            GroupMessageColumns {
+        private GroupMessages() {}
+
+        /**
+         * Gets the Uri to query group messages by group.
+         *
+         * @param groupId the group id.
+         * @return the Uri
+         */
+        public static final Uri getContentUriByGroup(long groupId) {
+            Uri.Builder builder = CONTENT_URI_GROUP_MESSAGES_BY.buildUpon();
+            ContentUris.appendId(builder, groupId);
+            return builder.build();
+        }
+
+        /**
+         * The content:// style URL for this table
+         */
+        public static final Uri CONTENT_URI =
+            Uri.parse("content://im/groupMessages");
+
+        /**
+         * The content:// style URL for group messages by provider and account
+         */
+        public static final Uri CONTENT_URI_GROUP_MESSAGES_BY =
+            Uri.parse("content://im/groupMessagesBy");
+
+        /**
+         * The MIME type of {@link #CONTENT_URI} providing a directory of
+         * group messages.
+         */
+        public static final String CONTENT_TYPE = "vnd.android.cursor.dir/im-groupMessages";
+
+        /**
+         * The MIME type of a {@link #CONTENT_URI} subdirectory of a single
+         * group message.
+         */
+        public static final String CONTENT_ITEM_TYPE =
+                "vnd.android.cursor.item/im-groupMessages";
+
+        /**
+         * The default sort order for this table
+         */
+        public static final String DEFAULT_SORT_ORDER = "date ASC";
+    }
+
+    /**
+     * Columns from the Avatars table
+     */
+    public interface AvatarsColumns {
+        /**
+         * The contact this avatar belongs to
+         * <P>Type: TEXT</P>
+         */
+        String CONTACT = "contact";
+
+        String PROVIDER = "provider_id";
+
+        String ACCOUNT = "account_id";
+
+        /**
+         * The hash of the image data
+         * <P>Type: TEXT</P>
+         */
+        String HASH = "hash";
+
+        /**
+         * raw image data
+         * <P>Type: BLOB</P>
+         */
+        String DATA = "data";
+    }
+
+    /**
+     * This table contains avatars.
+     */
+    public static final class Avatars implements BaseColumns, AvatarsColumns {
+        /**
+         * no public constructor since this is a utility class
+         */
+        private Avatars() {}
+
+        /**
+         * The content:// style URL for this table
+         */
+        public static final Uri CONTENT_URI = Uri.parse("content://im/avatars");
+
+        /**
+         * The content:// style URL for avatars by provider, account and contact
+         */
+        public static final Uri CONTENT_URI_AVATARS_BY =
+                Uri.parse("content://im/avatarsBy");
+
+        /**
+         * The MIME type of {@link #CONTENT_URI} providing the avatars
+         */
+        public static final String CONTENT_TYPE = "vnd.android.cursor.dir/im-avatars";
+
+        /**
+         * The MIME type of a {@link #CONTENT_URI}
+         */
+        public static final String CONTENT_ITEM_TYPE =
+                "vnd.android.cursor.item/im-avatars";
+
+        /**
+         * The default sort order for this table
+         */
+        public static final String DEFAULT_SORT_ORDER = "contact ASC";
+
+    }
+
+    /**
+     * Common presence columns shared between the IM and contacts presence tables
+     */
+    public interface CommonPresenceColumns {
+        /**
+         * The priority, an integer, used by XMPP presence
+         * <P>Type: INTEGER</P>
+         */
+        String PRIORITY = "priority";
+
+        /**
+         * The server defined status.
+         * <P>Type: INTEGER (one of the values below)</P>
+         */
+        String PRESENCE_STATUS = "mode";
+
+        /**
+         * Presence Status definition
+         */
+        int OFFLINE = 0;
+        int INVISIBLE = 1;
+        int AWAY = 2;
+        int IDLE = 3;
+        int DO_NOT_DISTURB = 4;
+        int AVAILABLE = 5;
+
+        /**
+         * The user defined status line.
+         * <P>Type: TEXT</P>
+         */
+        String PRESENCE_CUSTOM_STATUS = "status";
+    }
+
+    /**
+     * Columns from the Presence table.
+     */
+    public interface PresenceColumns extends CommonPresenceColumns {
+        /**
+         * The contact id
+         * <P>Type: INTEGER</P>
+         */
+        String CONTACT_ID = "contact_id";
+
+        /**
+         * The contact's JID resource, only relevant for XMPP contact
+         * <P>Type: TEXT</P>
+         */
+        String JID_RESOURCE = "jid_resource";
+
+        /**
+         * The contact's client type
+         */
+        String CLIENT_TYPE = "client_type";
+
+        /**
+         * client type definitions
+         */
+        int CLIENT_TYPE_DEFAULT = 0;
+        int CLIENT_TYPE_MOBILE = 1;
+        int CLIENT_TYPE_ANDROID = 2;
+    }
+
+    /**
+     * Contains presence infomation for contacts.
+     */
+    public static final class Presence implements BaseColumns, PresenceColumns {
+        /**
+         * The content:// style URL for this table
+         */
+        public static final Uri CONTENT_URI = Uri.parse("content://im/presence");
+
+        /**
+         * The content URL for IM presences for an account
+         */
+        public static final Uri CONTENT_URI_BY_ACCOUNT = Uri.parse("content://im/presence/account");
+
+        /**
+         * The content:// style URL for operations on bulk contacts
+         */
+        public static final Uri BULK_CONTENT_URI = Uri.parse("content://im/bulk_presence");
+
+        /**
+         * The content:// style URL for seeding presences for a given account id.
+         */
+        public static final Uri SEED_PRESENCE_BY_ACCOUNT_CONTENT_URI =
+                Uri.parse("content://im/seed_presence/account");
+
+        /**
+         * The MIME type of a {@link #CONTENT_URI} providing a directory of presence
+         */
+        public static final String CONTENT_TYPE = "vnd.android.cursor.dir/im-presence";
+
+        /**
+         * The default sort order for this table
+         */
+        public static final String DEFAULT_SORT_ORDER = "mode DESC";
+    }
+
+    /**
+     * Columns from the Chats table.
+     */
+    public interface ChatsColumns {
+        /**
+         * The contact ID this chat belongs to. The value is a long.
+         * <P>Type: INT</P>
+         */
+        String CONTACT_ID = "contact_id";
+
+        /**
+         * The GTalk JID resource. The value is a string.
+         * <P>Type: TEXT</P>
+         */
+        String JID_RESOURCE = "jid_resource";
+
+        /**
+         * Whether this is a groupchat or not.
+         * <P>Type: INT</P>
+         */
+        String GROUP_CHAT = "groupchat";
+
+        /**
+         * The last unread message. This both indicates that there is an
+         * unread message, and what the message is.
+         * <P>Type: TEXT</P>
+         */
+        String LAST_UNREAD_MESSAGE = "last_unread_message";
+
+        /**
+         * The last message timestamp
+         * <P>Type: INT</P>
+         */
+        String LAST_MESSAGE_DATE = "last_message_date";
+
+        /**
+         * A message that is being composed.  This indicates that there was a
+         * message being composed when the chat screen was shutdown, and what the
+         * message is.
+         * <P>Type: TEXT</P>
+         */
+        String UNSENT_COMPOSED_MESSAGE = "unsent_composed_message";
+        
+        /**
+         * A value from 0-9 indicating which quick-switch chat screen slot this
+         * chat is occupying.  If none (for instance, this is the 12th active chat)
+         * then the value is -1.
+         * <P>Type: INT</P>
+         */
+        String SHORTCUT = "shortcut";
+    }
+
+    /**
+     * Contains ongoing chat sessions.
+     */
+    public static final class Chats implements BaseColumns, ChatsColumns {
+        /**
+         * no public constructor since this is a utility class
+         */
+        private Chats() {}
+
+        /**
+         * The content:// style URL for this table
+         */
+        public static final Uri CONTENT_URI =
+            Uri.parse("content://im/chats");
+
+        /**
+         * The content URL for all chats that belong to the account
+         */
+        public static final Uri CONTENT_URI_BY_ACCOUNT = Uri.parse("content://im/chats/account");
+
+        /**
+         * The MIME type of {@link #CONTENT_URI} providing a directory of chats.
+         */
+        public static final String CONTENT_TYPE = "vnd.android.cursor.dir/im-chats";
+
+        /**
+         * The MIME type of a {@link #CONTENT_URI} subdirectory of a single chat.
+         */
+        public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/im-chats";
+
+        /**
+         * The default sort order for this table
+         */
+        public static final String DEFAULT_SORT_ORDER = "last_message_date ASC";
+    }
+
+    /**
+     * Columns from session cookies table. Used for IMPS.
+     */
+    public static interface SessionCookiesColumns {
+        String NAME = "name";
+        String VALUE = "value";
+        String PROVIDER = "provider";
+        String ACCOUNT = "account";
+    }
+
+    /**
+     * Contains IMPS session cookies.
+     */
+    public static class SessionCookies implements SessionCookiesColumns, BaseColumns {
+        private SessionCookies() {
+        }
+
+        /**
+         * The content:// style URI for this table
+         */
+        public static final Uri CONTENT_URI = Uri.parse("content://im/sessionCookies");
+
+        /**
+         * The content:// style URL for session cookies by provider and account
+         */
+        public static final Uri CONTENT_URI_SESSION_COOKIES_BY =
+            Uri.parse("content://im/sessionCookiesBy");
+
+        /**
+         * The MIME type of {@link #CONTENT_URI} providing a directory of
+         * people.
+         */
+        public static final String CONTENT_TYPE = "vnd.android-dir/im-sessionCookies";
+    }
+
+    /**
+     * Columns from ProviderSettings table
+     */
+    public static interface ProviderSettingsColumns {
+         /**
+          * The id in database of the related provider
+          *
+          * <P>Type: INT</P>
+          */
+        String PROVIDER = "provider";
+
+        /**
+         * The name of the setting
+         * <P>Type: TEXT</P>
+         */
+        String NAME = "name";
+
+        /**
+         * The value of the setting
+         * <P>Type: TEXT</P>
+         */
+        String VALUE = "value";
+    }
+
+    public static class ProviderSettings implements ProviderSettingsColumns {
+        private ProviderSettings() {
+        }
+
+        /**
+         * The content:// style URI for this table
+         */
+        public static final Uri CONTENT_URI =
+                Uri.parse("content://im/providerSettings");
+
+        /**
+         * The MIME type of {@link #CONTENT_URI} providing provider settings
+         */
+        public static final String CONTENT_TYPE = "vnd.android-dir/im-providerSettings";
+
+        /**
+         * A boolean value to indicate whether this provider should show the offline contacts
+         */
+        public static final String SHOW_OFFLINE_CONTACTS = "show_offline_contacts";
+
+        /** controls whether or not the GTalk service automatically connect to server. */
+        public static final String SETTING_AUTOMATICALLY_CONNECT_GTALK = "gtalk_auto_connect";
+
+        /** controls whether or not the IM service will be automatically started after boot */
+        public static final String SETTING_AUTOMATICALLY_START_SERVICE = "auto_start_service";
+
+        /** controls whether or not the offline contacts will be hided */
+        public static final String SETTING_HIDE_OFFLINE_CONTACTS = "hide_offline_contacts";
+
+        /** controls whether or not enable the IM notification */
+        public static final String SETTING_ENABLE_NOTIFICATION = "enable_notification";
+
+        /** specifies whether or not to vibrate */
+        public static final String SETTING_VIBRATE = "vibrate";
+
+        /** specifies the Uri string of the ringtone */
+        public static final String SETTING_RINGTONE = "ringtone";
+
+        /** specifies the Uri of the default ringtone */
+        public static final String SETTING_RINGTONE_DEFAULT =
+                "content://settings/system/notification_sound";
+
+        /** specifies whether or not to show mobile indicator to friends */
+        public static final String SETTING_SHOW_MOBILE_INDICATOR = "mobile_indicator";
+
+        /**
+         * Used for reliable message queue (RMQ). This is for storing the last rmq id received
+         * from the GTalk server
+         */
+        public static final String LAST_RMQ_RECEIVED = "last_rmq_rec";
+
+        /**
+         * Query the settings of the provider specified by id
+         *
+         * @param cr
+         *            the relative content resolver
+         * @param providerId
+         *            the specified id of provider
+         * @return a HashMap which contains all the settings for the specified
+         *         provider
+         */
+        public static HashMap<String, String> queryProviderSettings(ContentResolver cr,
+                long providerId) {
+            HashMap<String, String> settings = new HashMap<String, String>();
+
+            String[] projection = { NAME, VALUE };
+            Cursor c = cr.query(ContentUris.withAppendedId(CONTENT_URI, providerId), projection, null, null, null);
+            if (c == null) {
+                return null;
+            }
+
+            while(c.moveToNext()) {
+                settings.put(c.getString(0), c.getString(1));
+            }
+
+            c.close();
+
+            return settings;
+        }
+
+        /**
+         * Get the string value of setting which is specified by provider id and the setting name.
+         *
+         * @param cr The ContentResolver to use to access the settings table.
+         * @param providerId The id of the provider.
+         * @param settingName The name of the setting.
+         * @return The value of the setting if the setting exist, otherwise return null.
+         */
+        public static String getStringValue(ContentResolver cr, long providerId, String settingName) {
+            String ret = null;
+            Cursor c = getSettingValue(cr, providerId, settingName);
+            if (c != null) {
+                ret = c.getString(0);
+                c.close();
+            }
+
+            return ret;
+        }
+
+        /**
+         * Get the boolean value of setting which is specified by provider id and the setting name.
+         *
+         * @param cr The ContentResolver to use to access the settings table.
+         * @param providerId The id of the provider.
+         * @param settingName The name of the setting.
+         * @return The value of the setting if the setting exist, otherwise return false.
+         */
+        public static boolean getBooleanValue(ContentResolver cr, long providerId, String settingName) {
+            boolean ret = false;
+            Cursor c = getSettingValue(cr, providerId, settingName);
+            if (c != null) {
+                ret = c.getInt(0) != 0;
+                c.close();
+            }
+            return ret;
+        }
+
+        private static Cursor getSettingValue(ContentResolver cr, long providerId, String settingName) {
+            Cursor c = cr.query(ContentUris.withAppendedId(CONTENT_URI, providerId), new String[]{VALUE}, NAME + "=?",
+                    new String[]{settingName}, null);
+            if (c != null) {
+                if (!c.moveToFirst()) {
+                    c.close();
+                    return null;
+                }
+            }
+            return c;
+        }
+
+        /**
+         * Save a long value of setting in the table providerSetting.
+         *
+         * @param cr The ContentProvider used to access the providerSetting table.
+         * @param providerId The id of the provider.
+         * @param name The name of the setting.
+         * @param value The value of the setting.
+         */
+        public static void putLongValue(ContentResolver cr, long providerId, String name,
+                long value) {
+            ContentValues v = new ContentValues(3);
+            v.put(PROVIDER, providerId);
+            v.put(NAME, name);
+            v.put(VALUE, value);
+
+            cr.insert(CONTENT_URI, v);
+        }
+
+        /**
+         * Save a boolean value of setting in the table providerSetting.
+         *
+         * @param cr The ContentProvider used to access the providerSetting table.
+         * @param providerId The id of the provider.
+         * @param name The name of the setting.
+         * @param value The value of the setting.
+         */
+        public static void putBooleanValue(ContentResolver cr, long providerId, String name,
+                boolean value) {
+            ContentValues v = new ContentValues(3);
+            v.put(PROVIDER, providerId);
+            v.put(NAME, name);
+            v.put(VALUE, Boolean.toString(value));
+
+            cr.insert(CONTENT_URI, v);
+        }
+
+        /**
+         * Save a string value of setting in the table providerSetting.
+         *
+         * @param cr The ContentProvider used to access the providerSetting table.
+         * @param providerId The id of the provider.
+         * @param name The name of the setting.
+         * @param value The value of the setting.
+         */
+        public static void putStringValue(ContentResolver cr, long providerId, String name,
+                String value) {
+            ContentValues v = new ContentValues(3);
+            v.put(PROVIDER, providerId);
+            v.put(NAME, name);
+            v.put(VALUE, value);
+
+            cr.insert(CONTENT_URI, v);
+        }
+
+        /**
+         * A convenience method to set whether or not the GTalk service should be started
+         * automatically.
+         *
+         * @param contentResolver The ContentResolver to use to access the settings table
+         * @param autoConnect Whether the GTalk service should be started automatically.
+         */
+        public static void setAutomaticallyConnectGTalk(ContentResolver contentResolver,
+                long providerId, boolean autoConnect) {
+            putBooleanValue(contentResolver, providerId, SETTING_AUTOMATICALLY_CONNECT_GTALK,
+                    autoConnect);
+        }
+
+        /**
+         * A convenience method to set whether or not the offline contacts should be hided
+         *
+         * @param contentResolver The ContentResolver to use to access the setting table
+         * @param hideOfflineContacts Whether the offline contacts should be hided
+         */
+        public static void setHideOfflineContacts(ContentResolver contentResolver,
+                long providerId, boolean hideOfflineContacts) {
+            putBooleanValue(contentResolver, providerId, SETTING_HIDE_OFFLINE_CONTACTS,
+                    hideOfflineContacts);
+        }
+
+        /**
+         * A convenience method to set whether or not enable the IM notification.
+         *
+         * @param contentResolver The ContentResolver to use to access the setting table.
+         * @param enable Whether enable the IM notification
+         */
+        public static void setEnableNotification(ContentResolver contentResolver, long providerId,
+                boolean enable) {
+            putBooleanValue(contentResolver, providerId, SETTING_ENABLE_NOTIFICATION, enable);
+        }
+
+        /**
+         * A convenience method to set whether or not to vibrate.
+         *
+         * @param contentResolver The ContentResolver to use to access the setting table.
+         * @param vibrate Whether or not to vibrate
+         */
+        public static void setVibrate(ContentResolver contentResolver, long providerId,
+                boolean vibrate) {
+            putBooleanValue(contentResolver, providerId, SETTING_VIBRATE, vibrate);
+        }
+
+        /**
+         * A convenience method to set the Uri String of the ringtone.
+         *
+         * @param contentResolver The ContentResolver to use to access the setting table.
+         * @param ringtoneUri The Uri String of the ringtone to be set.
+         */
+        public static void setRingtoneURI(ContentResolver contentResolver, long providerId,
+                String ringtoneUri) {
+            putStringValue(contentResolver, providerId, SETTING_RINGTONE, ringtoneUri);
+        }
+
+        /**
+         * A convenience method to set whether or not to show mobile indicator.
+         *
+         * @param contentResolver The ContentResolver to use to access the setting table.
+         * @param showMobileIndicator Whether or not to show mobile indicator.
+         */
+        public static void setShowMobileIndicator(ContentResolver contentResolver, long providerId,
+                boolean showMobileIndicator) {
+            putBooleanValue(contentResolver, providerId, SETTING_SHOW_MOBILE_INDICATOR,
+                    showMobileIndicator);
+        }
+
+        public static class QueryMap extends ContentQueryMap {
+            private ContentResolver mContentResolver;
+            private long mProviderId;
+
+            public QueryMap(ContentResolver contentResolver, long providerId, boolean keepUpdated,
+                    Handler handlerForUpdateNotifications) {
+                super(contentResolver.query(CONTENT_URI,
+                            new String[] {NAME,VALUE},
+                            PROVIDER + "=" + providerId,
+                            null, // no selection args
+                            null), // no sort order
+                        NAME, keepUpdated, handlerForUpdateNotifications);
+                mContentResolver = contentResolver;
+                mProviderId = providerId;
+            }
+
+            /**
+             * Set if the GTalk service should automatically connect to server.
+             *
+             * @param autoConnect if the GTalk service should auto connect to server.
+             */
+            public void setAutomaticallyConnectToGTalkServer(boolean autoConnect) {
+                ProviderSettings.setAutomaticallyConnectGTalk(mContentResolver, mProviderId,
+                        autoConnect);
+            }
+
+            /**
+             * Check if the GTalk service should automatically connect to server.
+             * @return if the GTalk service should automatically connect to server.
+             */
+            public boolean getAutomaticallyConnectToGTalkServer() {
+                return getBoolean(SETTING_AUTOMATICALLY_CONNECT_GTALK,
+                        true /* default to automatically sign in */);
+            }
+
+            /**
+             * Set whether or not the offline contacts should be hided.
+             *
+             * @param hideOfflineContacts Whether or not the offline contacts should be hided.
+             */
+            public void setHideOfflineContacts(boolean hideOfflineContacts) {
+                ProviderSettings.setHideOfflineContacts(mContentResolver, mProviderId,
+                        hideOfflineContacts);
+            }
+
+            /**
+             * Check if the offline contacts should be hided.
+             *
+             * @return Whether or not the offline contacts should be hided.
+             */
+            public boolean getHideOfflineContacts() {
+                return getBoolean(SETTING_HIDE_OFFLINE_CONTACTS,
+                        false/* by default not hide the offline contacts*/);
+            }
+
+            /**
+             * Set whether or not enable the IM notification.
+             *
+             * @param enable Whether or not enable the IM notification.
+             */
+            public void setEnableNotification(boolean enable) {
+                ProviderSettings.setEnableNotification(mContentResolver, mProviderId, enable);
+            }
+
+            /**
+             * Check if the IM notification is enabled.
+             *
+             * @return Whether or not enable the IM notification.
+             */
+            public boolean getEnableNotification() {
+                return getBoolean(SETTING_ENABLE_NOTIFICATION,
+                        true/* by default enable the notification */);
+            }
+
+            /**
+             * Set whether or not to vibrate on IM notification.
+             *
+             * @param vibrate Whether or not to vibrate.
+             */
+            public void setVibrate(boolean vibrate) {
+                ProviderSettings.setVibrate(mContentResolver, mProviderId, vibrate);
+            }
+
+            /**
+             * Gets whether or not to vibrate on IM notification.
+             *
+             * @return Whether or not to vibrate.
+             */
+            public boolean getVibrate() {
+                return getBoolean(SETTING_VIBRATE, false /* by default disable vibrate */);
+            }
+
+            /**
+             * Set the Uri for the ringtone.
+             *
+             * @param ringtoneUri The Uri of the ringtone to be set.
+             */
+            public void setRingtoneURI(String ringtoneUri) {
+                ProviderSettings.setRingtoneURI(mContentResolver, mProviderId, ringtoneUri);
+            }
+
+            /**
+             * Get the Uri String of the current ringtone.
+             *
+             * @return The Uri String of the current ringtone.
+             */
+            public String getRingtoneURI() {
+                return getString(SETTING_RINGTONE, SETTING_RINGTONE_DEFAULT);
+            }
+
+            /**
+             * Set whether or not to show mobile indicator to friends.
+             *
+             * @param showMobile whether or not to show mobile indicator.
+             */
+            public void setShowMobileIndicator(boolean showMobile) {
+                ProviderSettings.setShowMobileIndicator(mContentResolver, mProviderId, showMobile);
+            }
+
+            /**
+             * Gets whether or not to show mobile indicator.
+             *
+             * @return Whether or not to show mobile indicator.
+             */
+            public boolean getShowMobileIndicator() {
+                return getBoolean(SETTING_SHOW_MOBILE_INDICATOR,
+                        true /* by default show mobile indicator */);
+            }
+
+            /**
+             * Convenience function for retrieving a single settings value
+             * as a boolean.
+             *
+             * @param name The name of the setting to retrieve.
+             * @param def Value to return if the setting is not defined.
+             * @return The setting's current value, or 'def' if it is not defined.
+             */
+            private boolean getBoolean(String name, boolean def) {
+                ContentValues values = getValues(name);
+                return values != null ? values.getAsBoolean(VALUE) : def;
+            }
+
+            /**
+             * Convenience function for retrieving a single settings value
+             * as a String.
+             *
+             * @param name The name of the setting to retrieve.
+             * @param def The value to return if the setting is not defined.
+             * @return The setting's current value or 'def' if it is not defined.
+             */
+            private String getString(String name, String def) {
+                ContentValues values = getValues(name);
+                return values != null ? values.getAsString(VALUE) : def;
+            }
+
+            /**
+             * Convenience function for retrieving a single settings value
+             * as an Integer.
+             *
+             * @param name The name of the setting to retrieve.
+             * @param def The value to return if the setting is not defined.
+             * @return The setting's current value or 'def' if it is not defined.
+             */
+            private int getInteger(String name, int def) {
+                ContentValues values = getValues(name);
+                return values != null ? values.getAsInteger(VALUE) : def;
+            }
+        }
+
+    }
+
+    /**
+     * Columns from OutgoingRmq table
+     */
+    public interface OutgoingRmqColumns {
+        String RMQ_ID = "rmq_id";
+        String TYPE = "type";
+        String TIMESTAMP = "ts";
+        String DATA = "data";
+    }
+
+    /**
+     * The table for storing outgoing rmq packets.
+     */
+    public static final class OutgoingRmq implements BaseColumns, OutgoingRmqColumns {
+        private static String[] RMQ_ID_PROJECTION = new String[] {
+                RMQ_ID,
+        };
+
+        /**
+         * queryHighestRmqId
+         *
+         * @param resolver the content resolver
+         * @return the highest rmq id assigned to the rmq packet, or 0 if there are no rmq packets
+         *         in the OutgoingRmq table.
+         */
+        public static final long queryHighestRmqId(ContentResolver resolver) {
+            Cursor cursor = resolver.query(Im.OutgoingRmq.CONTENT_URI_FOR_HIGHEST_RMQ_ID,
+                    RMQ_ID_PROJECTION,
+                    null, // selection
+                    null, // selection args
+                    null  // sort
+                    );
+
+            long retVal = 0;
+            try {
+                //if (DBG) log("initializeRmqid: cursor.count= " + cursor.count());
+
+                if (cursor.moveToFirst()) {
+                    retVal = cursor.getLong(cursor.getColumnIndexOrThrow(RMQ_ID));
+                }
+            } finally {
+                cursor.close();
+            }
+
+            return retVal;
+        }
+
+        /**
+         * The content:// style URL for this table.
+         */
+        public static final Uri CONTENT_URI = Uri.parse("content://im/outgoingRmqMessages");
+
+        /**
+         * The content:// style URL for the highest rmq id for the outgoing rmq messages
+         */
+        public static final Uri CONTENT_URI_FOR_HIGHEST_RMQ_ID =
+                Uri.parse("content://im/outgoingHighestRmqId");
+
+        /**
+         * The default sort order for this table.
+         */
+        public static final String DEFAULT_SORT_ORDER = "rmq_id ASC";
+    }
+
+    /**
+     * Columns for the LastRmqId table, which stores a single row for the last client rmq id
+     * sent to the server.
+     */
+    public interface LastRmqIdColumns {
+        String RMQ_ID = "rmq_id";
+    }
+
+    /**
+     * The table for storing the last client rmq id sent to the server.
+     */
+    public static final class LastRmqId implements BaseColumns, LastRmqIdColumns {
+        private static String[] PROJECTION = new String[] {
+                RMQ_ID,
+        };
+
+        /**
+         * queryLastRmqId
+         *
+         * queries the last rmq id saved in the LastRmqId table.
+         *
+         * @param resolver the content resolver.
+         * @return the last rmq id stored in the LastRmqId table, or 0 if not found.
+         */
+        public static final long queryLastRmqId(ContentResolver resolver) {
+            Cursor cursor = resolver.query(Im.LastRmqId.CONTENT_URI,
+                    PROJECTION,
+                    null, // selection
+                    null, // selection args
+                    null  // sort
+                    );
+
+            long retVal = 0;
+            try {
+                if (cursor.moveToFirst()) {
+                    retVal = cursor.getLong(cursor.getColumnIndexOrThrow(RMQ_ID));
+                }
+            } finally {
+                cursor.close();
+            }
+
+            return retVal;
+        }
+
+        /**
+         * saveLastRmqId
+         *
+         * saves the rmqId to the lastRmqId table. This will override the existing row if any,
+         * as we only keep one row of data in this table.
+         *
+         * @param resolver the content resolver.
+         * @param rmqId the rmq id to be saved.
+         */
+        public static final void saveLastRmqId(ContentResolver resolver, long rmqId) {
+            ContentValues values = new ContentValues();
+
+            // always replace the first row.
+            values.put(_ID, 1);
+            values.put(RMQ_ID, rmqId);
+            resolver.insert(CONTENT_URI, values);
+        }
+
+        /**
+         * The content:// style URL for this table.
+         */
+        public static final Uri CONTENT_URI = Uri.parse("content://im/lastRmqId");
+    }
+
+    /**
+     * Columns for IM branding resource map cache table. This table caches the result of
+     * loading the branding resources to speed up IM landing page start.
+     */
+    public interface BrandingResourceMapCacheColumns {
+        /**
+         * The provider ID
+         * <P>Type: INTEGER</P>
+         */
+        String PROVIDER_ID = "provider_id";
+        /**
+         * The application resource ID
+         * <P>Type: INTEGER</P>
+         */
+        String APP_RES_ID = "app_res_id";
+        /**
+         * The plugin resource ID
+         * <P>Type: INTEGER</P>
+         */
+        String PLUGIN_RES_ID = "plugin_res_id";
+    }
+
+    /**
+     * The table for caching the result of loading IM branding resources.
+     */
+    public static final class BrandingResourceMapCache
+        implements BaseColumns, BrandingResourceMapCacheColumns {
+        /**
+         * The content:// style URL for this table.
+         */
+        public static final Uri CONTENT_URI = Uri.parse("content://im/brandingResMapCache");
+    }
+}
diff --git a/core/java/android/provider/LiveFolders.java b/core/java/android/provider/LiveFolders.java
new file mode 100644
index 0000000..6e95fb7
--- /dev/null
+++ b/core/java/android/provider/LiveFolders.java
@@ -0,0 +1,298 @@
+/*
+ * 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 android.provider;
+
+import android.annotation.SdkConstant;
+
+/**
+ * <p>A LiveFolder is a special folder whose content is provided by a
+ * {@link android.content.ContentProvider}. To create a live folder, two components
+ * are required:</p>
+ * <ul>
+ *  <li>An activity that can respond to the intent action {@link #ACTION_CREATE_LIVE_FOLDER}. The
+ *  activity is responsible for creating the live folder.</li>
+ *  <li>A {@link android.content.ContentProvider} to provide the live folder items.</li>
+ * </ul>
+ *
+ * <h3>Lifecycle</h3>
+ * <p>When a user wants to create a live folder, the system looks for all activities with the
+ * intent filter action {@link #ACTION_CREATE_LIVE_FOLDER} and presents the list to the user.
+ * When the user chooses one of the activities, the activity is invoked with the
+ * {@link #ACTION_CREATE_LIVE_FOLDER} action. The activity then creates the live folder and
+ * passes it back to the system by setting it as an
+ * {@link android.app.Activity#setResult(int, android.content.Intent) activity result}. The
+ * live folder is described by a content provider URI, a name, an icon and a display mode.
+ * Finally, when the user opens the live folder, the system queries the content provider
+ * to retrieve the folder's content.</p>
+ *
+ * <h3>Setting up the live folder activity</h3>
+ * <p>The following code sample shows how to write an activity that creates a live fodler:</p>
+ * <pre>
+ * public static class MyLiveFolder extends Activity {
+ *     public static final Uri CONTENT_URI = Uri.parse("content://my.app/live");
+ *
+ *     @Override
+ *     protected void onCreate(Bundle savedInstanceState) {
+ *         super.onCreate(savedInstanceState);
+ *
+ *         final Intent intent = getIntent();
+ *         final String action = intent.getAction();
+ *
+ *         if (LiveFolders.ACTION_CREATE_LIVE_FOLDER.equals(action)) {
+ *             setResult(RESULT_OK, createLiveFolder(this, CONTENT_URI, "My LiveFolder",
+ *                     R.drawable.ic_launcher_contacts_phones));
+ *         } else {
+ *             setResult(RESULT_CANCELED);
+ *         }
+ *
+ *         finish();
+ *     }
+ *
+ *     private static Intent createLiveFolder(Context context, Uri uri, String name,
+ *             int icon) {
+ *
+ *         final Intent intent = new Intent();
+ *
+ *         intent.setData(uri);
+ *         intent.putExtra(LiveFolders.EXTRA_LIVE_FOLDER_NAME, name);
+ *         intent.putExtra(LiveFolders.EXTRA_LIVE_FOLDER_ICON,
+ *                 Intent.ShortcutIconResource.fromContext(context, icon));
+ *         intent.putExtra(LiveFolders.EXTRA_LIVE_FOLDER_DISPLAY_MODE, LiveFolders.DISPLAY_MODE_LIST);
+ *
+ *         return intent;
+ *     }
+ * }
+ * </pre>
+ * <p>The live folder is described by an {@link android.content.Intent} as follows:</p>
+ * <table border="2" width="85%" align="center" frame="hsides" rules="rows">
+ *     <thead>
+ *     <tr><th>Component</th> <th>Type</th> <th>Description</th> <th>Required</th></tr>
+ *     </thead>
+ *
+ *     <tbody>
+ *     <tr><th>URI</th>
+ *         <td>URI</td>
+ *         <td>The ContentProvider URI</td>
+ *         <td align="center">Yes</td>
+ *     </tr>
+ *     <tr><th>{@link #EXTRA_LIVE_FOLDER_NAME}</th>
+ *         <td>Extra String</td>
+ *         <td>The name of the live folder</td>
+ *         <td align="center">Yes</td>
+ *     </tr>
+ *     <tr><th>{@link #EXTRA_LIVE_FOLDER_ICON}</th>
+ *         <td>Extra {@link android.content.Intent.ShortcutIconResource}</td>
+ *         <td>The icon of the live folder</td>
+ *         <td align="center">Yes</td>
+ *     </tr>
+ *     <tr><th>{@link #EXTRA_LIVE_FOLDER_DISPLAY_MODE}</th>
+ *         <td>Extra int</td>
+ *         <td>The display mode of the live folder. The value must be either
+ *         {@link #DISPLAY_MODE_GRID} or {@link #DISPLAY_MODE_LIST}.</td>
+ *         <td align="center">Yes</td>
+ *     </tr>
+ *     <tr><th>{@link #EXTRA_LIVE_FOLDER_BASE_INTENT}</th>
+ *         <td>Extra Intent</td>
+ *         <td>When the user clicks an item inside a live folder, the system will either fire
+ *         the intent associated with that item or, if present, the live folder's base intent
+ *         with the id of the item appended to the base intent's URI.</td>
+ *         <td align="center">No</td>
+ *     </tr>
+ *     </tbody>
+ * </table>
+ *
+ * <h3>Setting up the content provider</h3>
+ * <p>The live folder's content provider must, upon query, return a {@link android.database.Cursor}
+ * whose columns match the following names:</p>
+ * <table border="2" width="85%" align="center" frame="hsides" rules="rows">
+ *     <thead>
+ *     <tr><th>Column</th> <th>Type</th> <th>Description</th> <th>Required</th></tr>
+ *     </thead>
+ *
+ *     <tbody>
+ *     <tr><th>{@link #NAME}</th>
+ *         <td>String</td>
+ *         <td>The name of the item</td>
+ *         <td align="center">Yes</td>
+ *     </tr>
+ *     <tr><th>{@link #DESCRIPTION}</th>
+ *         <td>String</td>
+ *         <td>The description of the item. The description is ignored when the live folder's
+ *         display mode is {@link #DISPLAY_MODE_GRID}.</td>
+ *         <td align="center">No</td>
+ *     </tr>
+ *     <tr><th>{@link #INTENT}</th>
+ *         <td>{@link android.content.Intent}</td>
+ *         <td>The intent to fire when the item is clicked. Ignored when the live folder defines
+ *         a base intent.</td>
+ *         <td align="center">No</td>
+ *     </tr>
+ *     <tr><th>{@link #ICON_BITMAP}</th>
+ *         <td>Bitmap</td>
+ *         <td>The icon for the item. When this column value is not null, the values for the
+ *         columns {@link #ICON_PACKAGE} and {@link #ICON_RESOURCE} must be null.</td>
+ *         <td align="center">No</td>
+ *     </tr>
+ *     <tr><th>{@link #ICON_PACKAGE}</th>
+ *         <td>String</td>
+ *         <td>The package of the item's icon. When this value is not null, the value for the
+ *         column {@link #ICON_RESOURCE} must be specified and the value for the column
+ *         {@link #ICON_BITMAP} must be null.</td>
+ *         <td align="center">No</td>
+ *     </tr>
+ *     <tr><th>{@link #ICON_RESOURCE}</th>
+ *         <td>String</td>
+ *         <td>The resource name of the item's icon. When this value is not null, the value for the
+ *         column {@link #ICON_PACKAGE} must be specified and the value for the column
+ *         {@link #ICON_BITMAP} must be null.</td>
+ *         <td align="center">No</td>
+ *     </tr>
+ *     </tbody>
+ * </table>
+ */
+public final class LiveFolders implements BaseColumns {
+    /**
+     * <p>Content provider column.</p>
+     * <p>Name of the live folder item.</p>
+     * <p>Required.</p>
+     * <p>Type: String.</p>
+     */
+    public static final String NAME = "name";
+
+    /**
+     * <p>Content provider column.</p>
+     * <p>Description of the live folder item. This value is ignored if the
+     * live folder's display mode is {@link LiveFolders#DISPLAY_MODE_GRID}.</p>
+     * <p>Optional.</p>
+     * <p>Type: String.</p>
+     *
+     * @see LiveFolders#EXTRA_LIVE_FOLDER_DISPLAY_MODE
+     */
+    public static final String DESCRIPTION = "description";
+
+    /**
+     * <p>Content provider column.</p>
+     * <p>Intent of the live folder item.</p>
+     * <p>Optional if the live folder has a base intent.</p>
+     * <p>Type: {@link android.content.Intent}.</p>
+     *
+     * @see LiveFolders#EXTRA_LIVE_FOLDER_BASE_INTENT
+     */
+    public static final String INTENT = "intent";
+
+    /**
+     * <p>Content provider column.</p>
+     * <p>Icon of the live folder item, as a custom bitmap.</p>
+     * <p>Optional.</p>
+     * <p>Type: {@link android.graphics.Bitmap}.</p>
+     */
+    public static final String ICON_BITMAP = "icon_bitmap";
+
+    /**
+     * <p>Content provider column.</p>
+     * <p>Package where to find the icon of the live folder item. This value can be
+     * obtained easily using
+     * {@link android.content.Intent.ShortcutIconResource#fromContext(android.content.Context, int)}.</p>
+     * <p>Optional.</p>
+     * <p>Type: String.</p>
+     *
+     * @see #ICON_RESOURCE
+     * @see android.content.Intent.ShortcutIconResource
+     */
+    public static final String ICON_PACKAGE = "icon_package";
+
+    /**
+     * <p>Content provider column.</p>
+     * <p>Resource name of the live folder item. This value can be obtained easily using
+     * {@link android.content.Intent.ShortcutIconResource#fromContext(android.content.Context, int)}.</p>
+     * <p>Optional.</p>
+     * <p>Type: String.</p>
+     *
+     * @see #ICON_PACKAGE
+     * @see android.content.Intent.ShortcutIconResource
+     */
+    public static final String ICON_RESOURCE = "icon_resource";
+
+    /**
+     * Displays a live folder's content in a grid.
+     *
+     * @see LiveFolders#EXTRA_LIVE_FOLDER_DISPLAY_MODE
+     */
+    public static final int DISPLAY_MODE_GRID = 0x1;
+
+    /**
+     * Displays a live folder's content in a list.
+     *
+     * @see LiveFolders#EXTRA_LIVE_FOLDER_DISPLAY_MODE
+     */
+    public static final int DISPLAY_MODE_LIST = 0x2;
+
+    /**
+     * The name of the extra used to define the name of a live folder.
+     *
+     * @see #ACTION_CREATE_LIVE_FOLDER
+     */
+    public static final String EXTRA_LIVE_FOLDER_NAME = "android.intent.extra.livefolder.NAME";
+
+    /**
+     * The name of the extra used to define the icon of a live folder.
+     *
+     * @see #ACTION_CREATE_LIVE_FOLDER
+     */
+    public static final String EXTRA_LIVE_FOLDER_ICON = "android.intent.extra.livefolder.ICON";
+
+    /**
+     * The name of the extra used to define the display mode of a live folder.
+     *
+     * @see #ACTION_CREATE_LIVE_FOLDER
+     * @see #DISPLAY_MODE_GRID
+     * @see #DISPLAY_MODE_LIST
+     */
+    public static final String EXTRA_LIVE_FOLDER_DISPLAY_MODE =
+            "android.intent.extra.livefolder.DISPLAY_MODE";
+
+    /**
+     * The name of the extra used to define the base Intent of a live folder.
+     *
+     * @see #ACTION_CREATE_LIVE_FOLDER
+     */
+    public static final String EXTRA_LIVE_FOLDER_BASE_INTENT =
+            "android.intent.extra.livefolder.BASE_INTENT";
+
+    /**
+     * Activity Action: Creates a live folder.
+     * <p>Input: Nothing.</p>
+     * <p>Output: An Intent representing the live folder. The intent must contain four
+     * extras: EXTRA_LIVE_FOLDER_NAME (value: String),
+     * EXTRA_LIVE_FOLDER_ICON (value: ShortcutIconResource),
+     * EXTRA_LIVE_FOLDER_URI (value: String) and
+     * EXTRA_LIVE_FOLDER_DISPLAY_MODE (value: int). The Intent can optionnally contain
+     * EXTRA_LIVE_FOLDER_BASE_INTENT (value: Intent).</p>
+     *
+     * @see #EXTRA_LIVE_FOLDER_NAME
+     * @see #EXTRA_LIVE_FOLDER_ICON
+     * @see #EXTRA_LIVE_FOLDER_DISPLAY_MODE
+     * @see #EXTRA_LIVE_FOLDER_BASE_INTENT
+     * @see android.content.Intent.ShortcutIconResource
+     */
+    @SdkConstant(SdkConstant.SdkConstantType.ACTIVITY_INTENT_ACTION)
+    public static final String ACTION_CREATE_LIVE_FOLDER =
+            "android.intent.action.CREATE_LIVE_FOLDER";
+
+    private LiveFolders() {
+    }
+}
diff --git a/core/java/android/provider/MediaStore.java b/core/java/android/provider/MediaStore.java
new file mode 100644
index 0000000..b91bc9d
--- /dev/null
+++ b/core/java/android/provider/MediaStore.java
@@ -0,0 +1,1396 @@
+/*
+ * Copyright (C) 2007 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 android.provider;
+
+import android.annotation.SdkConstant;
+import android.annotation.SdkConstant.SdkConstantType;
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.content.ContentUris;
+import android.database.Cursor;
+import android.database.DatabaseUtils;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Matrix;
+import android.net.Uri;
+import android.os.Environment;
+import android.util.Log;
+
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.UnsupportedEncodingException;
+import java.text.Collator;
+
+/**
+ * The Media provider contains meta data for all available media on both internal
+ * and external storage devices.
+ */
+public final class MediaStore
+{
+    private final static String TAG = "MediaStore";
+
+    public static final String AUTHORITY = "media";
+
+    private static final String CONTENT_AUTHORITY_SLASH = "content://" + AUTHORITY + "/";
+
+    /**
+     * Activity Action: Perform a search for media.
+     * Contains at least the {@link android.app.SearchManager#QUERY} extra.
+     * May also contain any combination of the following extras:
+     * EXTRA_MEDIA_ARTIST, EXTRA_MEDIA_ALBUM, EXTRA_MEDIA_TITLE, EXTRA_MEDIA_FOCUS
+     *
+     * @see android.provider.MediaStore#EXTRA_MEDIA_ARTIST
+     * @see android.provider.MediaStore#EXTRA_MEDIA_ALBUM
+     * @see android.provider.MediaStore#EXTRA_MEDIA_TITLE
+     * @see android.provider.MediaStore#EXTRA_MEDIA_FOCUS
+     */
+    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+    public static final String INTENT_ACTION_MEDIA_SEARCH = "android.intent.action.MEDIA_SEARCH";
+
+    /**
+     * The name of the Intent-extra used to define the artist
+     */
+    public static final String EXTRA_MEDIA_ARTIST = "android.intent.extra.artist";
+    /**
+     * The name of the Intent-extra used to define the album
+     */
+    public static final String EXTRA_MEDIA_ALBUM = "android.intent.extra.album";
+    /**
+     * The name of the Intent-extra used to define the song title
+     */
+    public static final String EXTRA_MEDIA_TITLE = "android.intent.extra.title";
+    /**
+     * The name of the Intent-extra used to define the search focus. The search focus
+     * indicates whether the search should be for things related to the artist, album
+     * or song that is identified by the other extras.
+     */
+    public static final String EXTRA_MEDIA_FOCUS = "android.intent.extra.focus";
+
+    /**
+     * The name of the Intent-extra used to control the orientation of a ViewImage or a MovieView.
+     * This is an int property that overrides the activity's requestedOrientation.
+     * @see android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED
+     */
+    public static final String EXTRA_SCREEN_ORIENTATION = "android.intent.extra.screenOrientation";
+
+    /**
+     * The name of an Intent-extra used to control the UI of a ViewImage.
+     * This is a boolean property that overrides the activity's default fullscreen state.
+     * @hide
+     */
+    public static final String EXTRA_FULL_SCREEN = "android.intent.extra.fullScreen";
+
+    /**
+     * The name of an Intent-extra used to control the UI of a ViewImage.
+     * This is a boolean property that specifies whether or not to show action icons.
+     * @hide
+     */
+    public static final String EXTRA_SHOW_ACTION_ICONS = "android.intent.extra.showActionIcons";
+
+    /**
+     * The name of the Intent-extra used to control the onCompletion behavior of a MovieView.
+     * This is a boolean property that specifies whether or not to finish the MovieView activity
+     * when the movie completes playing. The default value is true, which means to automatically
+     * exit the movie player activity when the movie completes playing.
+     */
+    public static final String EXTRA_FINISH_ON_COMPLETION = "android.intent.extra.finishOnCompletion";
+
+    /**
+     * The name of the Intent action used to launch a camera in still image mode.
+     */
+    public static final String INTENT_ACTION_STILL_IMAGE_CAMERA = "android.media.action.STILL_IMAGE_CAMERA";
+
+    /**
+     * The name of the Intent action used to launch a camera in video mode.
+     */
+    public static final String INTENT_ACTION_VIDEO_CAMERA = "android.media.action.VIDEO_CAMERA";
+
+    /**
+     * Standard Intent action that can be sent to have the camera application
+     * capture an image and return it.
+     * <p>
+     * The caller may pass an extra EXTRA_OUTPUT to control where this image will be written.
+     * If the EXTRA_OUTPUT is not present, then a small sized image is returned as a Bitmap
+     * object in the extra field. This is useful for applications that only need a small image.
+     * If the EXTRA_OUTPUT is present, then the full-sized image will be written to the Uri
+     * value of EXTRA_OUTPUT.
+     * @see #EXTRA_OUTPUT
+     * @see #EXTRA_VIDEO_QUALITY
+     */
+    public final static String ACTION_IMAGE_CAPTURE = "android.media.action.IMAGE_CAPTURE";
+
+    /**
+     * Standard Intent action that can be sent to have the camera application
+     * capture an video and return it.
+     * <p>
+     * The caller may pass in an extra EXTRA_VIDEO_QUALITY to control the video quality.
+     * <p>
+     * The caller may pass in an extra EXTRA_OUTPUT to control
+     * where the video is written. If EXTRA_OUTPUT is not present the video will be
+     * written to the standard location for videos, and the Uri of that location will be
+     * returned in the data field of the Uri.
+     * @see #EXTRA_OUTPUT
+     */
+    public final static String ACTION_VIDEO_CAPTURE = "android.media.action.VIDEO_CAPTURE";
+
+    /**
+     * The name of the Intent-extra used to control the quality of a recorded video. This is an
+     * integer property. Currently value 0 means low quality, suitable for MMS messages, and
+     * value 1 means high quality. In the future other quality levels may be added.
+     */
+    public final static String EXTRA_VIDEO_QUALITY = "android.intent.extra.videoQuality";
+
+    /**
+     * Specify the maximum allowed size.
+     * @hide
+     */
+    public final static String EXTRA_SIZE_LIMIT = "android.intent.extra.sizeLimit";
+
+    /**
+     * The name of the Intent-extra used to indicate a content resolver Uri to be used to
+     * store the requested image or video.
+     */
+    public final static String EXTRA_OUTPUT = "output";
+
+    /**
+     * Common fields for most MediaProvider tables
+     */
+
+     public interface MediaColumns extends BaseColumns {
+        /**
+         * The data stream for the file
+         * <P>Type: DATA STREAM</P>
+         */
+        public static final String DATA = "_data";
+
+        /**
+         * The size of the file in bytes
+         * <P>Type: INTEGER (long)</P>
+         */
+        public static final String SIZE = "_size";
+
+        /**
+         * The display name of the file
+         * <P>Type: TEXT</P>
+         */
+        public static final String DISPLAY_NAME = "_display_name";
+
+        /**
+         * The title of the content
+         * <P>Type: TEXT</P>
+         */
+        public static final String TITLE = "title";
+
+        /**
+         * The time the file was added to the media provider
+         * Units are seconds since 1970.
+         * <P>Type: INTEGER (long)</P>
+         */
+        public static final String DATE_ADDED = "date_added";
+
+        /**
+         * The time the file was last modified
+         * Units are seconds since 1970.
+         * NOTE: This is for internal use by the media scanner.  Do not modify this field.
+         * <P>Type: INTEGER (long)</P>
+         */
+        public static final String DATE_MODIFIED = "date_modified";
+
+        /**
+         * The MIME type of the file
+         * <P>Type: TEXT</P>
+         */
+        public static final String MIME_TYPE = "mime_type";
+     }
+
+    /**
+     * Contains meta data for all available images.
+     */
+    public static final class Images
+    {
+        public interface ImageColumns extends MediaColumns {
+            /**
+             * The description of the image
+             * <P>Type: TEXT</P>
+             */
+            public static final String DESCRIPTION = "description";
+
+            /**
+             * The picasa id of the image
+             * <P>Type: TEXT</P>
+             */
+            public static final String PICASA_ID = "picasa_id";
+
+            /**
+             * Whether the video should be published as public or private
+             * <P>Type: INTEGER</P>
+             */
+            public static final String IS_PRIVATE = "isprivate";
+
+            /**
+             * The latitude where the image was captured.
+             * <P>Type: DOUBLE</P>
+             */
+            public static final String LATITUDE = "latitude";
+
+            /**
+             * The longitude where the image was captured.
+             * <P>Type: DOUBLE</P>
+             */
+            public static final String LONGITUDE = "longitude";
+
+            /**
+             * The date & time that the image was taken in units
+             * of milliseconds since jan 1, 1970.
+             * <P>Type: INTEGER</P>
+             */
+            public static final String DATE_TAKEN = "datetaken";
+
+            /**
+             * The orientation for the image expressed as degrees.
+             * Only degrees 0, 90, 180, 270 will work.
+             * <P>Type: INTEGER</P>
+             */
+            public static final String ORIENTATION = "orientation";
+
+            /**
+             * The mini thumb id.
+             * <P>Type: INTEGER</P>
+             */
+            public static final String MINI_THUMB_MAGIC = "mini_thumb_magic";
+
+            /**
+             * The bucket id of the image. This is a read-only property that
+             * is automatically computed from the DATA column.
+             * <P>Type: TEXT</P>
+             */
+            public static final String BUCKET_ID = "bucket_id";
+
+            /**
+             * The bucket display name of the image. This is a read-only property that
+             * is automatically computed from the DATA column.
+             * <P>Type: TEXT</P>
+             */
+            public static final String BUCKET_DISPLAY_NAME = "bucket_display_name";
+        }
+
+        public static final class Media implements ImageColumns {
+            public static final Cursor query(ContentResolver cr, Uri uri, String[] projection)
+            {
+                return cr.query(uri, projection, null, null, DEFAULT_SORT_ORDER);
+            }
+
+            public static final Cursor query(ContentResolver cr, Uri uri, String[] projection,
+                                           String where, String orderBy)
+            {
+                return cr.query(uri, projection, where,
+                                             null, orderBy == null ? DEFAULT_SORT_ORDER : orderBy);
+            }
+
+            public static final Cursor query(ContentResolver cr, Uri uri, String[] projection,
+                    String selection, String [] selectionArgs, String orderBy)
+            {
+                return cr.query(uri, projection, selection,
+                        selectionArgs, orderBy == null ? DEFAULT_SORT_ORDER : orderBy);
+            }
+
+            /**
+             * Retrieves an image for the given url as a {@link Bitmap}.
+             *
+             * @param cr The content resolver to use
+             * @param url The url of the image
+             * @throws FileNotFoundException
+             * @throws IOException
+             */
+            public static final Bitmap getBitmap(ContentResolver cr, Uri url)
+                    throws FileNotFoundException, IOException
+            {
+                InputStream input = cr.openInputStream(url);
+                Bitmap bitmap = BitmapFactory.decodeStream(input);
+                input.close();
+                return bitmap;
+            }
+
+            /**
+             * Insert an image and create a thumbnail for it.
+             *
+             * @param cr The content resolver to use
+             * @param imagePath The path to the image to insert
+             * @param name The name of the image
+             * @param description The description of the image
+             * @return The URL to the newly created image
+             * @throws FileNotFoundException
+             */
+            public static final String insertImage(ContentResolver cr, String imagePath, String name,
+                                                   String description) throws FileNotFoundException
+            {
+                // Check if file exists with a FileInputStream
+                FileInputStream stream = new FileInputStream(imagePath);
+                try {
+                    return insertImage(cr, BitmapFactory.decodeFile(imagePath), name, description);
+                } finally {
+                    try {
+                        stream.close();
+                    } catch (IOException e) {
+                    }
+                }
+            }
+
+            private static final Bitmap StoreThumbnail(
+                    ContentResolver cr,
+                    Bitmap source,
+                    long id,
+                    float width, float height,
+                    int kind) {
+                // create the matrix to scale it
+                Matrix matrix = new Matrix();
+
+                float scaleX = width / source.getWidth();
+                float scaleY = height / source.getHeight();
+
+                matrix.setScale(scaleX, scaleY);
+
+                Bitmap thumb = Bitmap.createBitmap(source, 0, 0,
+                                                   source.getWidth(),
+                                                   source.getHeight(), matrix,
+                                                   true);
+
+                ContentValues values = new ContentValues(4);
+                values.put(Images.Thumbnails.KIND,     kind);
+                values.put(Images.Thumbnails.IMAGE_ID, (int)id);
+                values.put(Images.Thumbnails.HEIGHT,   thumb.getHeight());
+                values.put(Images.Thumbnails.WIDTH,    thumb.getWidth());
+
+                Uri url = cr.insert(Images.Thumbnails.EXTERNAL_CONTENT_URI, values);
+
+                try {
+                    OutputStream thumbOut = cr.openOutputStream(url);
+
+                    thumb.compress(Bitmap.CompressFormat.JPEG, 100, thumbOut);
+                    thumbOut.close();
+                    return thumb;
+                }
+                catch (FileNotFoundException ex) {
+                    return null;
+                }
+                catch (IOException ex) {
+                    return null;
+                }
+            }
+
+            /**
+             * Insert an image and create a thumbnail for it.
+             *
+             * @param cr The content resolver to use
+             * @param source The stream to use for the image
+             * @param title The name of the image
+             * @param description The description of the image
+             * @return The URL to the newly created image, or <code>null</code> if the image failed to be stored
+             *              for any reason.
+             */
+            public static final String insertImage(ContentResolver cr, Bitmap source,
+                                                   String title, String description)
+            {
+                ContentValues values = new ContentValues();
+                values.put(Images.Media.TITLE, title);
+                values.put(Images.Media.DESCRIPTION, description);
+                values.put(Images.Media.MIME_TYPE, "image/jpeg");
+
+                Uri url = null;
+                String stringUrl = null;    /* value to be returned */
+
+                try
+                {
+                    url = cr.insert(EXTERNAL_CONTENT_URI, values);
+
+                    if (source != null) {
+                        OutputStream imageOut = cr.openOutputStream(url);
+                        try {
+                            source.compress(Bitmap.CompressFormat.JPEG, 50, imageOut);
+                        } finally {
+                            imageOut.close();
+                        }
+
+                        long id = ContentUris.parseId(url);
+                        Bitmap miniThumb  = StoreThumbnail(cr, source, id, 320F, 240F, Images.Thumbnails.MINI_KIND);
+                        Bitmap microThumb = StoreThumbnail(cr, miniThumb, id, 50F, 50F, Images.Thumbnails.MICRO_KIND);
+                    } else {
+                        Log.e(TAG, "Failed to create thumbnail, removing original");
+                        cr.delete(url, null, null);
+                        url = null;
+                    }
+                } catch (Exception e) {
+                    Log.e(TAG, "Failed to insert image", e);
+                    if (url != null) {
+                        cr.delete(url, null, null);
+                        url = null;
+                    }
+                }
+
+                if (url != null) {
+                    stringUrl = url.toString();
+                }
+
+                return stringUrl;
+            }
+
+            /**
+             * Get the content:// style URI for the image media table on the
+             * given volume.
+             *
+             * @param volumeName the name of the volume to get the URI for
+             * @return the URI to the image media table on the given volume
+             */
+            public static Uri getContentUri(String volumeName) {
+                return Uri.parse(CONTENT_AUTHORITY_SLASH + volumeName +
+                        "/images/media");
+            }
+
+            /**
+             * The content:// style URI for the internal storage.
+             */
+            public static final Uri INTERNAL_CONTENT_URI =
+                    getContentUri("internal");
+
+            /**
+             * The content:// style URI for the "primary" external storage
+             * volume.
+             */
+            public static final Uri EXTERNAL_CONTENT_URI =
+                    getContentUri("external");
+
+            /**
+             * The MIME type of of this directory of
+             * images.  Note that each entry in this directory will have a standard
+             * image MIME type as appropriate -- for example, image/jpeg.
+             */
+            public static final String CONTENT_TYPE = "vnd.android.cursor.dir/image";
+
+            /**
+             * The default sort order for this table
+             */
+            public static final String DEFAULT_SORT_ORDER = ImageColumns.BUCKET_DISPLAY_NAME;
+       }
+
+        public static class Thumbnails implements BaseColumns
+        {
+            public static final Cursor query(ContentResolver cr, Uri uri, String[] projection)
+            {
+                return cr.query(uri, projection, null, null, DEFAULT_SORT_ORDER);
+            }
+
+            public static final Cursor queryMiniThumbnails(ContentResolver cr, Uri uri, int kind, String[] projection)
+            {
+                return cr.query(uri, projection, "kind = " + kind, null, DEFAULT_SORT_ORDER);
+            }
+
+            public static final Cursor queryMiniThumbnail(ContentResolver cr, long origId, int kind, String[] projection)
+            {
+                return cr.query(EXTERNAL_CONTENT_URI, projection,
+                        IMAGE_ID + " = " + origId + " AND " + KIND + " = " +
+                        kind, null, null);
+            }
+
+            /**
+             * Get the content:// style URI for the image media table on the
+             * given volume.
+             *
+             * @param volumeName the name of the volume to get the URI for
+             * @return the URI to the image media table on the given volume
+             */
+            public static Uri getContentUri(String volumeName) {
+                return Uri.parse(CONTENT_AUTHORITY_SLASH + volumeName +
+                        "/images/thumbnails");
+            }
+
+            /**
+             * The content:// style URI for the internal storage.
+             */
+            public static final Uri INTERNAL_CONTENT_URI =
+                    getContentUri("internal");
+
+            /**
+             * The content:// style URI for the "primary" external storage
+             * volume.
+             */
+            public static final Uri EXTERNAL_CONTENT_URI =
+                    getContentUri("external");
+
+            /**
+             * The default sort order for this table
+             */
+            public static final String DEFAULT_SORT_ORDER = "image_id ASC";
+
+            /**
+             * The data stream for the thumbnail
+             * <P>Type: DATA STREAM</P>
+             */
+            public static final String DATA = "_data";
+
+            /**
+             * The original image for the thumbnal
+             * <P>Type: INTEGER (ID from Images table)</P>
+             */
+            public static final String IMAGE_ID = "image_id";
+
+            /**
+             * The kind of the thumbnail
+             * <P>Type: INTEGER (One of the values below)</P>
+             */
+            public static final String KIND = "kind";
+
+            public static final int MINI_KIND = 1;
+            public static final int FULL_SCREEN_KIND = 2;
+            public static final int MICRO_KIND = 3;
+
+            /**
+             * The width of the thumbnal
+             * <P>Type: INTEGER (long)</P>
+             */
+            public static final String WIDTH = "width";
+
+            /**
+             * The height of the thumbnail
+             * <P>Type: INTEGER (long)</P>
+             */
+            public static final String HEIGHT = "height";
+        }
+    }
+
+    /**
+     * Container for all audio content.
+     */
+    public static final class Audio {
+        /**
+         * Columns for audio file that show up in multiple tables.
+         */
+        public interface AudioColumns extends MediaColumns {
+
+            /**
+             * A non human readable key calculated from the TITLE, used for
+             * searching, sorting and grouping
+             * <P>Type: TEXT</P>
+             */
+            public static final String TITLE_KEY = "title_key";
+
+            /**
+             * The duration of the audio file, in ms
+             * <P>Type: INTEGER (long)</P>
+             */
+            public static final String DURATION = "duration";
+
+            /**
+             * The position, in ms, playback was at when playback for this file
+             * was last stopped.
+             * <P>Type: INTEGER (long)</P>
+             * @hide
+             */
+            public static final String BOOKMARK = "bookmark";
+
+            /**
+             * The id of the artist who created the audio file, if any
+             * <P>Type: INTEGER (long)</P>
+             */
+            public static final String ARTIST_ID = "artist_id";
+
+            /**
+             * The artist who created the audio file, if any
+             * <P>Type: TEXT</P>
+             */
+            public static final String ARTIST = "artist";
+
+            /**
+             * A non human readable key calculated from the ARTIST, used for
+             * searching, sorting and grouping
+             * <P>Type: TEXT</P>
+             */
+            public static final String ARTIST_KEY = "artist_key";
+
+            /**
+             * The composer of the audio file, if any
+             * <P>Type: TEXT</P>
+             */
+            public static final String COMPOSER = "composer";
+
+            /**
+             * The id of the album the audio file is from, if any
+             * <P>Type: INTEGER (long)</P>
+             */
+            public static final String ALBUM_ID = "album_id";
+
+            /**
+             * The album the audio file is from, if any
+             * <P>Type: TEXT</P>
+             */
+            public static final String ALBUM = "album";
+
+            /**
+             * A non human readable key calculated from the ALBUM, used for
+             * searching, sorting and grouping
+             * <P>Type: TEXT</P>
+             */
+            public static final String ALBUM_KEY = "album_key";
+
+            /**
+             * A URI to the album art, if any
+             * <P>Type: TEXT</P>
+             */
+            public static final String ALBUM_ART = "album_art";
+
+            /**
+             * The track number of this song on the album, if any.
+             * This number encodes both the track number and the
+             * disc number. For multi-disc sets, this number will
+             * be 1xxx for tracks on the first disc, 2xxx for tracks
+             * on the second disc, etc.
+             * <P>Type: INTEGER</P>
+             */
+            public static final String TRACK = "track";
+
+            /**
+             * The year the audio file was recorded, if any
+             * <P>Type: INTEGER</P>
+             */
+            public static final String YEAR = "year";
+
+            /**
+             * Non-zero if the audio file is music
+             * <P>Type: INTEGER (boolean)</P>
+             */
+            public static final String IS_MUSIC = "is_music";
+
+            /**
+             * Non-zero if the audio file is a podcast
+             * <P>Type: INTEGER (boolean)</P>
+             * @hide
+             */
+            public static final String IS_PODCAST = "is_podcast";
+
+            /**
+             * Non-zero id the audio file may be a ringtone
+             * <P>Type: INTEGER (boolean)</P>
+             */
+            public static final String IS_RINGTONE = "is_ringtone";
+
+            /**
+             * Non-zero id the audio file may be an alarm
+             * <P>Type: INTEGER (boolean)</P>
+             */
+            public static final String IS_ALARM = "is_alarm";
+
+            /**
+             * Non-zero id the audio file may be a notification sound
+             * <P>Type: INTEGER (boolean)</P>
+             */
+            public static final String IS_NOTIFICATION = "is_notification";
+        }
+
+        /**
+         * Converts a name to a "key" that can be used for grouping, sorting
+         * and searching.
+         * The rules that govern this conversion are:
+         * - remove 'special' characters like ()[]'!?.,
+         * - remove leading/trailing spaces
+         * - convert everything to lowercase
+         * - remove leading "the ", "an " and "a "
+         * - remove trailing ", the|an|a"
+         * - remove accents. This step leaves us with CollationKey data,
+         *   which is not human readable
+         *
+         * @param name The artist or album name to convert
+         * @return The "key" for the given name.
+         */
+        public static String keyFor(String name) {
+            if (name != null)  {
+                if (name.equals(android.media.MediaFile.UNKNOWN_STRING)) {
+                    return "\001";
+                }
+                name = name.trim().toLowerCase();
+                if (name.startsWith("the ")) {
+                    name = name.substring(4);
+                }
+                if (name.startsWith("an ")) {
+                    name = name.substring(3);
+                }
+                if (name.startsWith("a ")) {
+                    name = name.substring(2);
+                }
+                if (name.endsWith(", the") || name.endsWith(",the") ||
+                    name.endsWith(", an") || name.endsWith(",an") ||
+                    name.endsWith(", a") || name.endsWith(",a")) {
+                    name = name.substring(0, name.lastIndexOf(','));
+                }
+                name = name.replaceAll("[\\[\\]\\(\\)'.,?!]", "").trim();
+                if (name.length() > 0) {
+                    // Insert a separator between the characters to avoid
+                    // matches on a partial character. If we ever change
+                    // to start-of-word-only matches, this can be removed.
+                    StringBuilder b = new StringBuilder();
+                    b.append('.');
+                    int nl = name.length();
+                    for (int i = 0; i < nl; i++) {
+                        b.append(name.charAt(i));
+                        b.append('.');
+                    }
+                    name = b.toString();
+                    return DatabaseUtils.getCollationKey(name);
+               } else {
+                    return "";
+                }
+            }
+            return null;
+        }
+
+        public static final class Media implements AudioColumns {
+            /**
+             * Get the content:// style URI for the audio media table on the
+             * given volume.
+             *
+             * @param volumeName the name of the volume to get the URI for
+             * @return the URI to the audio media table on the given volume
+             */
+            public static Uri getContentUri(String volumeName) {
+                return Uri.parse(CONTENT_AUTHORITY_SLASH + volumeName +
+                        "/audio/media");
+            }
+
+            public static Uri getContentUriForPath(String path) {
+                return (path.startsWith(Environment.getExternalStorageDirectory().getPath()) ?
+                        EXTERNAL_CONTENT_URI : INTERNAL_CONTENT_URI);
+            }
+
+            /**
+             * The content:// style URI for the internal storage.
+             */
+            public static final Uri INTERNAL_CONTENT_URI =
+                    getContentUri("internal");
+
+            /**
+             * The content:// style URI for the "primary" external storage
+             * volume.
+             */
+            public static final Uri EXTERNAL_CONTENT_URI =
+                    getContentUri("external");
+
+            /**
+             * The MIME type for this table.
+             */
+            public static final String CONTENT_TYPE = "vnd.android.cursor.dir/audio";
+
+            /**
+             * The default sort order for this table
+             */
+            public static final String DEFAULT_SORT_ORDER = TITLE;
+
+            /**
+             * Activity Action: Start SoundRecorder application.
+             * <p>Input: nothing.
+             * <p>Output: An uri to the recorded sound stored in the Media Library
+             * if the recording was successful.
+             * May also contain the extra EXTRA_MAX_BYTES.
+             * @see #EXTRA_MAX_BYTES
+             */
+            public static final String RECORD_SOUND_ACTION =
+                    "android.provider.MediaStore.RECORD_SOUND";
+
+            /**
+             * The name of the Intent-extra used to define a maximum file size for
+             * a recording made by the SoundRecorder application.
+             *
+             * @see #RECORD_SOUND_ACTION
+             */
+             public static final String EXTRA_MAX_BYTES =
+                    "android.provider.MediaStore.extra.MAX_BYTES";
+        }
+
+        /**
+         * Columns representing an audio genre
+         */
+        public interface GenresColumns {
+            /**
+             * The name of the genre
+             * <P>Type: TEXT</P>
+             */
+            public static final String NAME = "name";
+        }
+
+        /**
+         * Contains all genres for audio files
+         */
+        public static final class Genres implements BaseColumns, GenresColumns {
+            /**
+             * Get the content:// style URI for the audio genres table on the
+             * given volume.
+             *
+             * @param volumeName the name of the volume to get the URI for
+             * @return the URI to the audio genres table on the given volume
+             */
+            public static Uri getContentUri(String volumeName) {
+                return Uri.parse(CONTENT_AUTHORITY_SLASH + volumeName +
+                        "/audio/genres");
+            }
+
+            /**
+             * The content:// style URI for the internal storage.
+             */
+            public static final Uri INTERNAL_CONTENT_URI =
+                    getContentUri("internal");
+
+            /**
+             * The content:// style URI for the "primary" external storage
+             * volume.
+             */
+            public static final Uri EXTERNAL_CONTENT_URI =
+                    getContentUri("external");
+
+            /**
+             * The MIME type for this table.
+             */
+            public static final String CONTENT_TYPE = "vnd.android.cursor.dir/genre";
+
+            /**
+             * The MIME type for entries in this table.
+             */
+            public static final String ENTRY_CONTENT_TYPE = "vnd.android.cursor.item/genre";
+
+            /**
+             * The default sort order for this table
+             */
+            public static final String DEFAULT_SORT_ORDER = NAME;
+
+            /**
+             * Sub-directory of each genre containing all members.
+             */
+            public static final class Members implements AudioColumns {
+
+                public static final Uri getContentUri(String volumeName,
+                        long genreId) {
+                    return Uri.parse(CONTENT_AUTHORITY_SLASH + volumeName
+                            + "/audio/genres/" + genreId + "/members");
+                }
+
+                /**
+                 * A subdirectory of each genre containing all member audio files.
+                 */
+                public static final String CONTENT_DIRECTORY = "members";
+
+                /**
+                 * The default sort order for this table
+                 */
+                public static final String DEFAULT_SORT_ORDER = TITLE;
+
+                /**
+                 * The ID of the audio file
+                 * <P>Type: INTEGER (long)</P>
+                 */
+                public static final String AUDIO_ID = "audio_id";
+
+                /**
+                 * The ID of the genre
+                 * <P>Type: INTEGER (long)</P>
+                 */
+                public static final String GENRE_ID = "genre_id";
+            }
+        }
+
+        /**
+         * Columns representing a playlist
+         */
+        public interface PlaylistsColumns {
+            /**
+             * The name of the playlist
+             * <P>Type: TEXT</P>
+             */
+            public static final String NAME = "name";
+
+            /**
+             * The data stream for the playlist file
+             * <P>Type: DATA STREAM</P>
+             */
+            public static final String DATA = "_data";
+
+            /**
+             * The time the file was added to the media provider
+             * Units are seconds since 1970.
+             * <P>Type: INTEGER (long)</P>
+             */
+            public static final String DATE_ADDED = "date_added";
+
+            /**
+             * The time the file was last modified
+             * Units are seconds since 1970.
+             * NOTE: This is for internal use by the media scanner.  Do not modify this field.
+             * <P>Type: INTEGER (long)</P>
+             */
+            public static final String DATE_MODIFIED = "date_modified";
+        }
+
+        /**
+         * Contains playlists for audio files
+         */
+        public static final class Playlists implements BaseColumns,
+                PlaylistsColumns {
+            /**
+             * Get the content:// style URI for the audio playlists table on the
+             * given volume.
+             *
+             * @param volumeName the name of the volume to get the URI for
+             * @return the URI to the audio playlists table on the given volume
+             */
+            public static Uri getContentUri(String volumeName) {
+                return Uri.parse(CONTENT_AUTHORITY_SLASH + volumeName +
+                        "/audio/playlists");
+            }
+
+            /**
+             * The content:// style URI for the internal storage.
+             */
+            public static final Uri INTERNAL_CONTENT_URI =
+                    getContentUri("internal");
+
+            /**
+             * The content:// style URI for the "primary" external storage
+             * volume.
+             */
+            public static final Uri EXTERNAL_CONTENT_URI =
+                    getContentUri("external");
+
+            /**
+             * The MIME type for this table.
+             */
+            public static final String CONTENT_TYPE = "vnd.android.cursor.dir/playlist";
+
+            /**
+             * The MIME type for entries in this table.
+             */
+            public static final String ENTRY_CONTENT_TYPE = "vnd.android.cursor.item/playlist";
+
+            /**
+             * The default sort order for this table
+             */
+            public static final String DEFAULT_SORT_ORDER = NAME;
+
+            /**
+             * Sub-directory of each playlist containing all members.
+             */
+            public static final class Members implements AudioColumns {
+                public static final Uri getContentUri(String volumeName,
+                        long playlistId) {
+                    return Uri.parse(CONTENT_AUTHORITY_SLASH + volumeName
+                            + "/audio/playlists/" + playlistId + "/members");
+                }
+
+                /**
+                 * The ID within the playlist.
+                 */
+                public static final String _ID = "_id";
+
+                /**
+                 * A subdirectory of each playlist containing all member audio
+                 * files.
+                 */
+                public static final String CONTENT_DIRECTORY = "members";
+
+                /**
+                 * The ID of the audio file
+                 * <P>Type: INTEGER (long)</P>
+                 */
+                public static final String AUDIO_ID = "audio_id";
+
+                /**
+                 * The ID of the playlist
+                 * <P>Type: INTEGER (long)</P>
+                 */
+                public static final String PLAYLIST_ID = "playlist_id";
+
+                /**
+                 * The order of the songs in the playlist
+                 * <P>Type: INTEGER (long)></P>
+                 */
+                public static final String PLAY_ORDER = "play_order";
+
+                /**
+                 * The default sort order for this table
+                 */
+                public static final String DEFAULT_SORT_ORDER = PLAY_ORDER;
+            }
+        }
+
+        /**
+         * Columns representing an artist
+         */
+        public interface ArtistColumns {
+            /**
+             * The artist who created the audio file, if any
+             * <P>Type: TEXT</P>
+             */
+            public static final String ARTIST = "artist";
+
+            /**
+             * A non human readable key calculated from the ARTIST, used for
+             * searching, sorting and grouping
+             * <P>Type: TEXT</P>
+             */
+            public static final String ARTIST_KEY = "artist_key";
+
+            /**
+             * The number of albums in the database for this artist
+             */
+            public static final String NUMBER_OF_ALBUMS = "number_of_albums";
+
+            /**
+             * The number of albums in the database for this artist
+             */
+            public static final String NUMBER_OF_TRACKS = "number_of_tracks";
+        }
+
+        /**
+         * Contains artists for audio files
+         */
+        public static final class Artists implements BaseColumns, ArtistColumns {
+            /**
+             * Get the content:// style URI for the artists table on the
+             * given volume.
+             *
+             * @param volumeName the name of the volume to get the URI for
+             * @return the URI to the audio artists table on the given volume
+             */
+            public static Uri getContentUri(String volumeName) {
+                return Uri.parse(CONTENT_AUTHORITY_SLASH + volumeName +
+                        "/audio/artists");
+            }
+
+            /**
+             * The content:// style URI for the internal storage.
+             */
+            public static final Uri INTERNAL_CONTENT_URI =
+                    getContentUri("internal");
+
+            /**
+             * The content:// style URI for the "primary" external storage
+             * volume.
+             */
+            public static final Uri EXTERNAL_CONTENT_URI =
+                    getContentUri("external");
+
+            /**
+             * The MIME type for this table.
+             */
+            public static final String CONTENT_TYPE = "vnd.android.cursor.dir/artists";
+
+            /**
+             * The MIME type for entries in this table.
+             */
+            public static final String ENTRY_CONTENT_TYPE = "vnd.android.cursor.item/artist";
+
+            /**
+             * The default sort order for this table
+             */
+            public static final String DEFAULT_SORT_ORDER = ARTIST_KEY;
+
+            /**
+             * Sub-directory of each artist containing all albums on which
+             * a song by the artist appears.
+             */
+            public static final class Albums implements AlbumColumns {
+                public static final Uri getContentUri(String volumeName,
+                        long artistId) {
+                    return Uri.parse(CONTENT_AUTHORITY_SLASH + volumeName
+                            + "/audio/artists/" + artistId + "/albums");
+                }
+            }
+        }
+
+        /**
+         * Columns representing an album
+         */
+        public interface AlbumColumns {
+
+            /**
+             * The id for the album
+             * <P>Type: INTEGER</P>
+             */
+            public static final String ALBUM_ID = "album_id";
+
+            /**
+             * The album on which the audio file appears, if any
+             * <P>Type: TEXT</P>
+             */
+            public static final String ALBUM = "album";
+
+            /**
+             * The artist whose songs appear on this album
+             * <P>Type: TEXT</P>
+             */
+            public static final String ARTIST = "artist";
+
+            /**
+             * The number of songs on this album
+             * <P>Type: INTEGER</P>
+             */
+            public static final String NUMBER_OF_SONGS = "numsongs";
+
+            /**
+             * This column is available when getting album info via artist,
+             * and indicates the number of songs on the album by the given
+             * artist.
+             * <P>Type: INTEGER</P>
+             *
+             * @hide pending API Council approval
+             */
+            public static final String NUMBER_OF_SONGS_FOR_ARTIST = "numsongs_by_artist";
+
+            /**
+             * The year in which the earliest and latest songs
+             * on this album were released. These will often
+             * be the same, but for compilation albums they
+             * might differ.
+             * <P>Type: INTEGER</P>
+             */
+            public static final String FIRST_YEAR = "minyear";
+            public static final String LAST_YEAR = "maxyear";
+
+            /**
+             * A non human readable key calculated from the ALBUM, used for
+             * searching, sorting and grouping
+             * <P>Type: TEXT</P>
+             */
+            public static final String ALBUM_KEY = "album_key";
+
+            /**
+             * Cached album art.
+             * <P>Type: TEXT</P>
+             */
+            public static final String ALBUM_ART = "album_art";
+        }
+
+        /**
+         * Contains artists for audio files
+         */
+        public static final class Albums implements BaseColumns, AlbumColumns {
+            /**
+             * Get the content:// style URI for the albums table on the
+             * given volume.
+             *
+             * @param volumeName the name of the volume to get the URI for
+             * @return the URI to the audio albums table on the given volume
+             */
+            public static Uri getContentUri(String volumeName) {
+                return Uri.parse(CONTENT_AUTHORITY_SLASH + volumeName +
+                        "/audio/albums");
+            }
+
+            /**
+             * The content:// style URI for the internal storage.
+             */
+            public static final Uri INTERNAL_CONTENT_URI =
+                    getContentUri("internal");
+
+            /**
+             * The content:// style URI for the "primary" external storage
+             * volume.
+             */
+            public static final Uri EXTERNAL_CONTENT_URI =
+                    getContentUri("external");
+
+            /**
+             * The MIME type for this table.
+             */
+            public static final String CONTENT_TYPE = "vnd.android.cursor.dir/albums";
+
+            /**
+             * The MIME type for entries in this table.
+             */
+            public static final String ENTRY_CONTENT_TYPE = "vnd.android.cursor.item/album";
+
+            /**
+             * The default sort order for this table
+             */
+            public static final String DEFAULT_SORT_ORDER = ALBUM_KEY;
+        }
+    }
+
+    public static final class Video {
+
+        /**
+         * The default sort order for this table.
+         */
+        public static final String DEFAULT_SORT_ORDER = MediaColumns.DISPLAY_NAME;
+
+        public static final Cursor query(ContentResolver cr, Uri uri, String[] projection)
+        {
+            return cr.query(uri, projection, null, null, DEFAULT_SORT_ORDER);
+        }
+
+        public interface VideoColumns extends MediaColumns {
+
+            /**
+             * The duration of the video file, in ms
+             * <P>Type: INTEGER (long)</P>
+             */
+            public static final String DURATION = "duration";
+
+            /**
+             * The artist who created the video file, if any
+             * <P>Type: TEXT</P>
+             */
+            public static final String ARTIST = "artist";
+
+            /**
+             * The album the video file is from, if any
+             * <P>Type: TEXT</P>
+             */
+            public static final String ALBUM = "album";
+
+            /**
+             * The resolution of the video file, formatted as "XxY"
+             * <P>Type: TEXT</P>
+             */
+            public static final String RESOLUTION = "resolution";
+
+            /**
+             * The description of the video recording
+             * <P>Type: TEXT</P>
+             */
+            public static final String DESCRIPTION = "description";
+
+            /**
+             * Whether the video should be published as public or private
+             * <P>Type: INTEGER</P>
+             */
+            public static final String IS_PRIVATE = "isprivate";
+
+            /**
+             * The user-added tags associated with a video
+             * <P>Type: TEXT</P>
+             */
+            public static final String TAGS = "tags";
+
+            /**
+             * The YouTube category of the video
+             * <P>Type: TEXT</P>
+             */
+            public static final String CATEGORY = "category";
+
+            /**
+             * The language of the video
+             * <P>Type: TEXT</P>
+             */
+            public static final String LANGUAGE = "language";
+
+            /**
+             * The latitude where the image was captured.
+             * <P>Type: DOUBLE</P>
+             */
+            public static final String LATITUDE = "latitude";
+
+            /**
+             * The longitude where the image was captured.
+             * <P>Type: DOUBLE</P>
+             */
+            public static final String LONGITUDE = "longitude";
+
+            /**
+             * The date & time that the image was taken in units
+             * of milliseconds since jan 1, 1970.
+             * <P>Type: INTEGER</P>
+             */
+            public static final String DATE_TAKEN = "datetaken";
+
+            /**
+             * The mini thumb id.
+             * <P>Type: INTEGER</P>
+             */
+            public static final String MINI_THUMB_MAGIC = "mini_thumb_magic";
+
+            /**
+             * The bucket id of the video. This is a read-only property that
+             * is automatically computed from the DATA column.
+             * <P>Type: TEXT</P>
+             */
+            public static final String BUCKET_ID = "bucket_id";
+
+            /**
+             * The bucket display name of the video. This is a read-only property that
+             * is automatically computed from the DATA column.
+             * <P>Type: TEXT</P>
+             */
+            public static final String BUCKET_DISPLAY_NAME = "bucket_display_name";
+
+            /**
+             * The bookmark for the video. Time in ms. Represents the location in the video that the
+             * video should start playing at the next time it is opened. If the value is null or
+             * out of the range 0..DURATION-1 then the video should start playing from the
+             * beginning.
+             * <P>Type: INTEGER</P>
+             */
+            public static final String BOOKMARK = "bookmark";
+        }
+
+        public static final class Media implements VideoColumns {
+            /**
+             * Get the content:// style URI for the video media table on the
+             * given volume.
+             *
+             * @param volumeName the name of the volume to get the URI for
+             * @return the URI to the video media table on the given volume
+             */
+            public static Uri getContentUri(String volumeName) {
+                return Uri.parse(CONTENT_AUTHORITY_SLASH + volumeName +
+                        "/video/media");
+            }
+
+            /**
+             * The content:// style URI for the internal storage.
+             */
+            public static final Uri INTERNAL_CONTENT_URI =
+                    getContentUri("internal");
+
+            /**
+             * The content:// style URI for the "primary" external storage
+             * volume.
+             */
+            public static final Uri EXTERNAL_CONTENT_URI =
+                    getContentUri("external");
+
+            /**
+             * The MIME type for this table.
+             */
+            public static final String CONTENT_TYPE = "vnd.android.cursor.dir/video";
+
+            /**
+             * The default sort order for this table
+             */
+            public static final String DEFAULT_SORT_ORDER = TITLE;
+        }
+    }
+
+    /**
+     * Uri for querying the state of the media scanner.
+     */
+    public static Uri getMediaScannerUri() {
+        return Uri.parse(CONTENT_AUTHORITY_SLASH + "none/media_scanner");
+    }
+
+    /**
+     * Name of current volume being scanned by the media scanner.
+     */
+    public static final String MEDIA_SCANNER_VOLUME = "volume";
+}
diff --git a/core/java/android/provider/OpenableColumns.java b/core/java/android/provider/OpenableColumns.java
new file mode 100644
index 0000000..f548bae
--- /dev/null
+++ b/core/java/android/provider/OpenableColumns.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2007 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 android.provider;
+
+/**
+ * These are standard columns for openable URIs. (See
+ * {@link android.content.Intent#CATEGORY_OPENABLE}.) If possible providers that have openable URIs
+ * should support these columns. To find the content type of a URI use
+ * {@link android.content.ContentResolver#getType(android.net.Uri)} as normal.
+ */
+public interface OpenableColumns {
+
+    /**
+     * The human-friendly name of file. If this is not provided then the name should default to the
+     * the last segment of the file's URI.
+     */
+    public static final String DISPLAY_NAME = "_display_name";
+
+    /**
+     * The number of bytes in the file identified by the openable URI. Null if unknown.
+     */
+    public static final String SIZE = "_size";
+}
diff --git a/core/java/android/provider/SearchRecentSuggestions.java b/core/java/android/provider/SearchRecentSuggestions.java
new file mode 100644
index 0000000..0632d94
--- /dev/null
+++ b/core/java/android/provider/SearchRecentSuggestions.java
@@ -0,0 +1,229 @@
+/*
+ * 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 android.provider;
+
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.SearchRecentSuggestionsProvider;
+import android.database.Cursor;
+import android.net.Uri;
+import android.text.TextUtils;
+import android.util.Log;
+
+/**
+ * This is a utility class providing access to 
+ * {@link android.content.SearchRecentSuggestionsProvider}.
+ * 
+ * <p>Unlike some utility classes, this one must be instantiated and properly initialized, so that
+ * it can be configured to operate with the search suggestions provider that you have created.
+ * 
+ * <p>Typically, you will do this in your searchable activity, each time you receive an incoming
+ * {@link android.content.Intent#ACTION_SEARCH ACTION_SEARCH} Intent.  The code to record each
+ * incoming query is as follows:
+ * <pre class="prettyprint">
+ *      SearchSuggestions suggestions = new SearchSuggestions(this, 
+ *              MySuggestionsProvider.AUTHORITY, MySuggestionsProvider.MODE);
+ *      suggestions.saveRecentQuery(queryString, null);
+ * </pre>
+ * 
+ * <p>For a working example, see SearchSuggestionSampleProvider and SearchQueryResults in
+ * samples/ApiDemos/app.
+ */
+public class SearchRecentSuggestions {
+    // debugging support
+    private static final String LOG_TAG = "SearchSuggestions";
+    // DELETE ME (eventually)
+    private static final int DBG_SUGGESTION_TIMESTAMPS = 0;
+    
+    // This is a superset of all possible column names (need not all be in table)
+    private static class SuggestionColumns implements BaseColumns {
+        public static final String DISPLAY1 = "display1";
+        public static final String DISPLAY2 = "display2";
+        public static final String QUERY = "query";
+        public static final String DATE = "date";
+    }
+    
+    /* if you change column order you must also change indices below */
+    /**
+     * This is the database projection that can be used to view saved queries, when
+     * configured for one-line operation.
+     */
+    public static final String[] QUERIES_PROJECTION_1LINE = new String[] {
+        SuggestionColumns._ID, 
+        SuggestionColumns.DATE,
+        SuggestionColumns.QUERY, 
+        SuggestionColumns.DISPLAY1,
+    };
+    /* if you change column order you must also change indices below */
+    /**
+     * This is the database projection that can be used to view saved queries, when
+     * configured for two-line operation.
+     */
+    public static final String[] QUERIES_PROJECTION_2LINE = new String[] {
+        SuggestionColumns._ID, 
+        SuggestionColumns.DATE,
+        SuggestionColumns.QUERY, 
+        SuggestionColumns.DISPLAY1,
+        SuggestionColumns.DISPLAY2,
+    };
+
+    /* these indices depend on QUERIES_PROJECTION_xxx */
+    /** Index into the provided query projections.  For use with Cursor.update methods. */
+    public static final int QUERIES_PROJECTION_DATE_INDEX = 1;
+    /** Index into the provided query projections.  For use with Cursor.update methods. */
+    public static final int QUERIES_PROJECTION_QUERY_INDEX = 2;
+    /** Index into the provided query projections.  For use with Cursor.update methods. */
+    public static final int QUERIES_PROJECTION_DISPLAY1_INDEX = 3;
+    /** Index into the provided query projections.  For use with Cursor.update methods. */
+    public static final int QUERIES_PROJECTION_DISPLAY2_INDEX = 4;  // only when 2line active
+    
+    /* columns needed to determine whether to truncate history */
+    private static final String[] TRUNCATE_HISTORY_PROJECTION = new String[] {
+        SuggestionColumns._ID, SuggestionColumns.DATE
+    };
+
+    /*
+     * Set a cap on the count of items in the suggestions table, to
+     * prevent db and layout operations from dragging to a crawl. Revisit this
+     * cap when/if db/layout performance improvements are made.
+     */
+    private static final int MAX_HISTORY_COUNT = 250;
+    
+    // client-provided configuration values
+    private Context mContext;
+    private String mAuthority;
+    private boolean mTwoLineDisplay;
+    private Uri mSuggestionsUri;
+    private String[] mQueriesProjection;
+
+    /**
+     * Although provider utility classes are typically static, this one must be constructed
+     * because it needs to be initialized using the same values that you provided in your 
+     * {@link android.content.SearchRecentSuggestionsProvider}.  
+     * 
+     * @param authority This must match the authority that you've declared in your manifest.
+     * @param mode You can use mode flags here to determine certain functional aspects of your
+     * database.  Note, this value should not change from run to run, because when it does change,
+     * your suggestions database may be wiped.
+     * 
+     * @see android.content.SearchRecentSuggestionsProvider
+     * @see android.content.SearchRecentSuggestionsProvider#setupSuggestions
+     */
+    public SearchRecentSuggestions(Context context, String authority, int mode) {
+        if (TextUtils.isEmpty(authority) || 
+                ((mode & SearchRecentSuggestionsProvider.DATABASE_MODE_QUERIES) == 0)) {
+            throw new IllegalArgumentException();
+        }
+        // unpack mode flags
+        mTwoLineDisplay = (0 != (mode & SearchRecentSuggestionsProvider.DATABASE_MODE_2LINES));
+            
+        // saved values
+        mContext = context;
+        mAuthority = new String(authority);
+        
+        // derived values
+        mSuggestionsUri = Uri.parse("content://" + mAuthority + "/suggestions");
+        
+        if (mTwoLineDisplay) {
+            mQueriesProjection = QUERIES_PROJECTION_2LINE;
+        } else {
+            mQueriesProjection = QUERIES_PROJECTION_1LINE;
+        }
+    }
+
+    /**
+     * Add a query to the recent queries list.
+     * 
+     * @param queryString The string as typed by the user.  This string will be displayed as
+     * the suggestion, and if the user clicks on the suggestion, this string will be sent to your
+     * searchable activity (as a new search query).
+     * @param line2 If you have configured your recent suggestions provider with 
+     * {@link android.content.SearchRecentSuggestionsProvider#DATABASE_MODE_2LINES}, you can 
+     * pass a second line of text here.  It will be shown in a smaller font, below the primary
+     * suggestion.  When typing, matches in either line of text will be displayed in the list.
+     * If you did not configure two-line mode, or if a given suggestion does not have any
+     * additional text to display, you can pass null here.
+     */
+    public void saveRecentQuery(String queryString, String line2) {
+        if (TextUtils.isEmpty(queryString)) {
+            return;
+        }
+        if (!mTwoLineDisplay && !TextUtils.isEmpty(line2)) {
+            throw new IllegalArgumentException();
+        }
+        
+        ContentResolver cr = mContext.getContentResolver();
+        long now = System.currentTimeMillis();
+        
+        // Use content resolver (not cursor) to insert/update this query
+        try {
+            ContentValues values = new ContentValues();
+            values.put(SuggestionColumns.DISPLAY1, queryString);
+            if (mTwoLineDisplay) {
+                values.put(SuggestionColumns.DISPLAY2, line2);
+            }
+            values.put(SuggestionColumns.QUERY, queryString);
+            values.put(SuggestionColumns.DATE, now);
+            cr.insert(mSuggestionsUri, values);
+        } catch (RuntimeException e) {
+            Log.e(LOG_TAG, "saveRecentQuery", e);
+        }
+        
+        // Shorten the list (if it has become too long)
+        truncateHistory(cr, MAX_HISTORY_COUNT);
+    }
+    
+    /**
+     * Completely delete the history.  Use this call to implement a "clear history" UI.
+     * 
+     * Any application that implements search suggestions based on previous actions (such as
+     * recent queries, page/items viewed, etc.) should provide a way for the user to clear the
+     * history.  This gives the user a measure of privacy, if they do not wish for their recent
+     * searches to be replayed by other users of the device (via suggestions).
+     */
+    public void clearHistory() {
+        ContentResolver cr = mContext.getContentResolver();
+        truncateHistory(cr, 0);
+    }
+
+    /**
+     * Reduces the length of the history table, to prevent it from growing too large.
+     * 
+     * @param cr Convenience copy of the content resolver.
+     * @param maxEntries Max entries to leave in the table. 0 means remove all entries.
+     */
+    protected void truncateHistory(ContentResolver cr, int maxEntries) {
+        if (maxEntries < 0) {
+            throw new IllegalArgumentException();
+        }
+        
+        try {
+            // null means "delete all".  otherwise "delete but leave n newest"
+            String selection = null;
+            if (maxEntries > 0) {
+                selection = "_id IN " +
+                        "(SELECT _id FROM suggestions" +
+                        " ORDER BY " + SuggestionColumns.DATE + " DESC" +
+                        " LIMIT -1 OFFSET " + String.valueOf(maxEntries) + ")";
+            }
+            cr.delete(mSuggestionsUri, selection, null);
+        } catch (RuntimeException e) {
+            Log.e(LOG_TAG, "truncateHistory", e);
+        }
+    }
+}
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
new file mode 100644
index 0000000..b536b0d
--- /dev/null
+++ b/core/java/android/provider/Settings.java
@@ -0,0 +1,3119 @@
+/*
+ * 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 android.provider;
+
+import com.google.android.collect.Maps;
+
+import org.apache.commons.codec.binary.Base64;
+
+import android.annotation.SdkConstant;
+import android.annotation.SdkConstant.SdkConstantType;
+import android.content.ContentQueryMap;
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.database.Cursor;
+import android.database.SQLException;
+import android.net.Uri;
+import android.os.*;
+import android.telephony.TelephonyManager;
+import android.text.TextUtils;
+import android.util.AndroidException;
+import android.util.Log;
+
+import java.net.URISyntaxException;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.HashMap;
+import java.util.HashSet;
+
+
+/**
+ * The Settings provider contains global system-level device preferences.
+ */
+public final class Settings {
+
+    // Intent actions for Settings
+
+    /**
+     * Activity Action: Show system settings.
+     * <p>
+     * Input: Nothing.
+     * <p>
+     * Output: nothing.
+     */
+    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+    public static final String ACTION_SETTINGS = "android.settings.SETTINGS";
+
+    /**
+     * Activity Action: Show settings to allow configuration of APNs.
+     * <p>
+     * Input: Nothing.
+     * <p>
+     * Output: nothing.
+     */
+    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+    public static final String ACTION_APN_SETTINGS = "android.settings.APN_SETTINGS";
+
+    /**
+     * Activity Action: Show settings to allow configuration of current location
+     * sources.
+     * <p>
+     * In some cases, a matching Activity may not exist, so ensure you
+     * safeguard against this.
+     * <p>
+     * Input: Nothing.
+     * <p>
+     * Output: Nothing.
+     */
+    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+    public static final String ACTION_LOCATION_SOURCE_SETTINGS =
+            "android.settings.LOCATION_SOURCE_SETTINGS";
+
+    /**
+     * Activity Action: Show settings to allow configuration of wireless controls
+     * such as Wi-Fi, Bluetooth and Mobile networks.
+     * <p>
+     * In some cases, a matching Activity may not exist, so ensure you
+     * safeguard against this.
+     * <p>
+     * Input: Nothing.
+     * <p>
+     * Output: Nothing.
+     */
+    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+    public static final String ACTION_WIRELESS_SETTINGS =
+            "android.settings.WIRELESS_SETTINGS";
+
+    /**
+     * Activity Action: Show settings to allow entering/exiting airplane mode.
+     * <p>
+     * In some cases, a matching Activity may not exist, so ensure you
+     * safeguard against this.
+     * <p>
+     * Input: Nothing.
+     * <p>
+     * Output: Nothing.
+     */
+    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+    public static final String ACTION_AIRPLANE_MODE_SETTINGS =
+            "android.settings.AIRPLANE_MODE_SETTINGS";
+
+    /**
+     * Activity Action: Show settings to allow configuration of security and
+     * location privacy.
+     * <p>
+     * In some cases, a matching Activity may not exist, so ensure you
+     * safeguard against this.
+     * <p>
+     * Input: Nothing.
+     * <p>
+     * Output: Nothing.
+     */
+    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+    public static final String ACTION_SECURITY_SETTINGS =
+            "android.settings.SECURITY_SETTINGS";
+
+    /**
+     * Activity Action: Show settings to allow configuration of Wi-Fi.
+
+     * <p>
+     * In some cases, a matching Activity may not exist, so ensure you
+     * safeguard against this.
+     * <p>
+     * Input: Nothing.
+     * <p>
+     * Output: Nothing.
+
+     */
+    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+    public static final String ACTION_WIFI_SETTINGS =
+            "android.settings.WIFI_SETTINGS";
+    
+    /**
+     * Activity Action: Show settings to allow configuration of a static IP
+     * address for Wi-Fi.
+     * <p>
+     * In some cases, a matching Activity may not exist, so ensure you safeguard
+     * against this.
+     * <p>
+     * Input: Nothing.
+     * <p>
+     * Output: Nothing.
+     */
+    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+    public static final String ACTION_WIFI_IP_SETTINGS =
+            "android.settings.WIFI_IP_SETTINGS";
+
+    /**
+     * Activity Action: Show settings to allow configuration of Bluetooth.
+     * <p>
+     * In some cases, a matching Activity may not exist, so ensure you
+     * safeguard against this.
+     * <p>
+     * Input: Nothing.
+     * <p>
+     * Output: Nothing.
+     */
+    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+    public static final String ACTION_BLUETOOTH_SETTINGS =
+            "android.settings.BLUETOOTH_SETTINGS";
+
+    /**
+     * Activity Action: Show settings to allow configuration of date and time.
+     * <p>
+     * In some cases, a matching Activity may not exist, so ensure you
+     * safeguard against this.
+     * <p>
+     * Input: Nothing.
+     * <p>
+     * Output: Nothing.
+     */
+    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+    public static final String ACTION_DATE_SETTINGS =
+            "android.settings.DATE_SETTINGS";
+
+    /**
+     * Activity Action: Show settings to allow configuration of sound and volume.
+     * <p>
+     * In some cases, a matching Activity may not exist, so ensure you
+     * safeguard against this.
+     * <p>
+     * Input: Nothing.
+     * <p>
+     * Output: Nothing.
+     */
+    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+    public static final String ACTION_SOUND_SETTINGS =
+            "android.settings.SOUND_SETTINGS";
+
+    /**
+     * Activity Action: Show settings to allow configuration of display.
+     * <p>
+     * In some cases, a matching Activity may not exist, so ensure you
+     * safeguard against this.
+     * <p>
+     * Input: Nothing.
+     * <p>
+     * Output: Nothing.
+     */
+    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+    public static final String ACTION_DISPLAY_SETTINGS =
+            "android.settings.DISPLAY_SETTINGS";
+
+    /**
+     * Activity Action: Show settings to allow configuration of locale.
+     * <p>
+     * In some cases, a matching Activity may not exist, so ensure you
+     * safeguard against this.
+     * <p>
+     * Input: Nothing.
+     * <p>
+     * Output: Nothing.
+     */
+    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+    public static final String ACTION_LOCALE_SETTINGS =
+            "android.settings.LOCALE_SETTINGS";
+
+    /**
+     * Activity Action: Show settings to configure input methods, in particular
+     * allowing the user to enable input methods.
+     * <p>
+     * In some cases, a matching Activity may not exist, so ensure you
+     * safeguard against this.
+     * <p>
+     * Input: Nothing.
+     * <p>
+     * Output: Nothing.
+     */
+    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+    public static final String ACTION_INPUT_METHOD_SETTINGS =
+            "android.settings.INPUT_METHOD_SETTINGS";
+
+    /**
+     * Activity Action: Show settings to manage the user input dictionary.
+     * <p>
+     * In some cases, a matching Activity may not exist, so ensure you
+     * safeguard against this.
+     * <p>
+     * Input: Nothing.
+     * <p>
+     * Output: Nothing.
+     */
+    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+    public static final String ACTION_USER_DICTIONARY_SETTINGS =
+            "android.settings.USER_DICTIONARY_SETTINGS";
+
+    /**
+     * Activity Action: Show settings to allow configuration of application-related settings.
+     * <p>
+     * In some cases, a matching Activity may not exist, so ensure you
+     * safeguard against this.
+     * <p>
+     * Input: Nothing.
+     * <p>
+     * Output: Nothing.
+     */
+    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+    public static final String ACTION_APPLICATION_SETTINGS =
+            "android.settings.APPLICATION_SETTINGS";
+
+    /**
+     * Activity Action: Show settings to allow configuration of application
+     * development-related settings.
+     * <p>
+     * In some cases, a matching Activity may not exist, so ensure you safeguard
+     * against this.
+     * <p>
+     * Input: Nothing.
+     * <p>
+     * Output: Nothing.
+     */
+    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+    public static final String ACTION_APPLICATION_DEVELOPMENT_SETTINGS =
+            "android.settings.APPLICATION_DEVELOPMENT_SETTINGS";
+
+    /**
+     * Activity Action: Show settings to allow configuration of quick launch shortcuts.
+     * <p>
+     * In some cases, a matching Activity may not exist, so ensure you
+     * safeguard against this.
+     * <p>
+     * Input: Nothing.
+     * <p>
+     * Output: Nothing.
+     */
+    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+    public static final String ACTION_QUICK_LAUNCH_SETTINGS =
+            "android.settings.QUICK_LAUNCH_SETTINGS";
+    
+    /**
+     * Activity Action: Show settings to manage installed applications.
+     * <p>
+     * In some cases, a matching Activity may not exist, so ensure you
+     * safeguard against this.
+     * <p>
+     * Input: Nothing.
+     * <p>
+     * Output: Nothing.
+     */
+    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+    public static final String ACTION_MANAGE_APPLICATIONS_SETTINGS =
+            "android.settings.MANAGE_APPLICATIONS_SETTINGS";
+    
+    /**
+     * Activity Action: Show settings for system update functionality.
+     * <p>
+     * In some cases, a matching Activity may not exist, so ensure you
+     * safeguard against this.
+     * <p>
+     * Input: Nothing.
+     * <p>
+     * Output: Nothing.
+     * 
+     * @hide
+     */
+    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+    public static final String ACTION_SYSTEM_UPDATE_SETTINGS =
+            "android.settings.SYSTEM_UPDATE_SETTINGS";
+
+    /**
+     * Activity Action: Show settings to allow configuration of sync settings.
+     * <p>
+     * In some cases, a matching Activity may not exist, so ensure you
+     * safeguard against this.
+     * <p>
+     * Input: Nothing.
+     * <p>
+     * Output: Nothing.
+     */
+    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+    public static final String ACTION_SYNC_SETTINGS =
+            "android.settings.SYNC_SETTINGS";
+    
+    /**
+     * Activity Action: Show settings for selecting the network operator.
+     * <p>
+     * In some cases, a matching Activity may not exist, so ensure you
+     * safeguard against this.
+     * <p>
+     * Input: Nothing.
+     * <p>
+     * Output: Nothing.
+     */
+    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+    public static final String ACTION_NETWORK_OPERATOR_SETTINGS =
+            "android.settings.NETWORK_OPERATOR_SETTINGS";
+
+    /**
+     * Activity Action: Show settings for selection of 2G/3G.
+     * <p>
+     * In some cases, a matching Activity may not exist, so ensure you
+     * safeguard against this.
+     * <p>
+     * Input: Nothing.
+     * <p>
+     * Output: Nothing.
+     */
+    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+    public static final String ACTION_DATA_ROAMING_SETTINGS =
+            "android.settings.DATA_ROAMING_SETTINGS";
+
+    /**
+     * Activity Action: Show settings for internal storage.
+     * <p>
+     * In some cases, a matching Activity may not exist, so ensure you
+     * safeguard against this.
+     * <p>
+     * Input: Nothing.
+     * <p>
+     * Output: Nothing.
+     */
+    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+    public static final String ACTION_INTERNAL_STORAGE_SETTINGS =
+            "android.settings.INTERNAL_STORAGE_SETTINGS";
+    /**
+     * Activity Action: Show settings for memory card storage.
+     * <p>
+     * In some cases, a matching Activity may not exist, so ensure you
+     * safeguard against this.
+     * <p>
+     * Input: Nothing.
+     * <p>
+     * Output: Nothing.
+     */
+    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+    public static final String ACTION_MEMORY_CARD_SETTINGS =
+            "android.settings.MEMORY_CARD_SETTINGS";
+    
+    // End of Intent actions for Settings
+
+    private static final String JID_RESOURCE_PREFIX = "android";
+
+    public static final String AUTHORITY = "settings";
+
+    private static final String TAG = "Settings";
+
+    private static String sJidResource = null;
+
+    public static class SettingNotFoundException extends AndroidException {
+        public SettingNotFoundException(String msg) {
+            super(msg);
+        }
+    }
+
+    /**
+     * Common base for tables of name/value settings.
+     */
+    public static class NameValueTable implements BaseColumns {
+        public static final String NAME = "name";
+        public static final String VALUE = "value";
+
+        protected static boolean putString(ContentResolver resolver, Uri uri,
+                String name, String value) {
+            // The database will take care of replacing duplicates.
+            try {
+                ContentValues values = new ContentValues();
+                values.put(NAME, name);
+                values.put(VALUE, value);
+                resolver.insert(uri, values);
+                return true;
+            } catch (SQLException e) {
+                Log.e(TAG, "Can't set key " + name + " in " + uri, e);
+                return false;
+            }
+        }
+
+        public static Uri getUriFor(Uri uri, String name) {
+            return Uri.withAppendedPath(uri, name);
+        }
+    }
+
+    private static class NameValueCache {
+        private final String mVersionSystemProperty;
+        private final HashMap<String, String> mValues = Maps.newHashMap();
+        private long mValuesVersion = 0;
+        private final Uri mUri;
+
+        NameValueCache(String versionSystemProperty, Uri uri) {
+            mVersionSystemProperty = versionSystemProperty;
+            mUri = uri;
+        }
+
+        String getString(ContentResolver cr, String name) {
+            long newValuesVersion = SystemProperties.getLong(mVersionSystemProperty, 0);
+            if (mValuesVersion != newValuesVersion) {
+                mValues.clear();
+                mValuesVersion = newValuesVersion;
+            }
+            if (!mValues.containsKey(name)) {
+                String value = null;
+                Cursor c = null;
+                try {
+                    c = cr.query(mUri, new String[] { Settings.NameValueTable.VALUE },
+                            Settings.NameValueTable.NAME + "=?", new String[]{name}, null);
+                    if (c != null && c.moveToNext()) value = c.getString(0);
+                    mValues.put(name, value);
+                } catch (SQLException e) {
+                    // SQL error: return null, but don't cache it.
+                    Log.e(TAG, "Can't get key " + name + " from " + mUri, e);
+                } finally {
+                    if (c != null) c.close();
+                }
+                return value;
+            } else {
+                return mValues.get(name);
+            }
+        }
+    }
+
+    /**
+     * System settings, containing miscellaneous system preferences.  This
+     * table holds simple name/value pairs.  There are convenience
+     * functions for accessing individual settings entries.
+     */
+    public static final class System extends NameValueTable {
+        public static final String SYS_PROP_SETTING_VERSION = "sys.settings_system_version";
+
+        private static volatile NameValueCache mNameValueCache = null;
+        
+        private static final HashSet<String> MOVED_TO_SECURE;
+        static {
+            MOVED_TO_SECURE = new HashSet<String>(30);
+            MOVED_TO_SECURE.add(Secure.ADB_ENABLED);
+            MOVED_TO_SECURE.add(Secure.ANDROID_ID);
+            MOVED_TO_SECURE.add(Secure.BLUETOOTH_ON);
+            MOVED_TO_SECURE.add(Secure.DATA_ROAMING);
+            MOVED_TO_SECURE.add(Secure.DEVICE_PROVISIONED);
+            MOVED_TO_SECURE.add(Secure.HTTP_PROXY);
+            MOVED_TO_SECURE.add(Secure.INSTALL_NON_MARKET_APPS);
+            MOVED_TO_SECURE.add(Secure.LOCATION_PROVIDERS_ALLOWED);
+            MOVED_TO_SECURE.add(Secure.LOGGING_ID);
+            MOVED_TO_SECURE.add(Secure.PARENTAL_CONTROL_ENABLED);
+            MOVED_TO_SECURE.add(Secure.PARENTAL_CONTROL_LAST_UPDATE);
+            MOVED_TO_SECURE.add(Secure.PARENTAL_CONTROL_REDIRECT_URL);
+            MOVED_TO_SECURE.add(Secure.SETTINGS_CLASSNAME);
+            MOVED_TO_SECURE.add(Secure.USB_MASS_STORAGE_ENABLED);
+            MOVED_TO_SECURE.add(Secure.USE_GOOGLE_MAIL);
+            MOVED_TO_SECURE.add(Secure.WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON);
+            MOVED_TO_SECURE.add(Secure.WIFI_NETWORKS_AVAILABLE_REPEAT_DELAY);
+            MOVED_TO_SECURE.add(Secure.WIFI_NUM_OPEN_NETWORKS_KEPT);
+            MOVED_TO_SECURE.add(Secure.WIFI_ON);
+            MOVED_TO_SECURE.add(Secure.WIFI_WATCHDOG_ACCEPTABLE_PACKET_LOSS_PERCENTAGE);
+            MOVED_TO_SECURE.add(Secure.WIFI_WATCHDOG_AP_COUNT);
+            MOVED_TO_SECURE.add(Secure.WIFI_WATCHDOG_BACKGROUND_CHECK_DELAY_MS);
+            MOVED_TO_SECURE.add(Secure.WIFI_WATCHDOG_BACKGROUND_CHECK_ENABLED);
+            MOVED_TO_SECURE.add(Secure.WIFI_WATCHDOG_BACKGROUND_CHECK_TIMEOUT_MS);
+            MOVED_TO_SECURE.add(Secure.WIFI_WATCHDOG_INITIAL_IGNORED_PING_COUNT);
+            MOVED_TO_SECURE.add(Secure.WIFI_WATCHDOG_MAX_AP_CHECKS);
+            MOVED_TO_SECURE.add(Secure.WIFI_WATCHDOG_ON);
+            MOVED_TO_SECURE.add(Secure.WIFI_WATCHDOG_PING_COUNT);
+            MOVED_TO_SECURE.add(Secure.WIFI_WATCHDOG_PING_DELAY_MS);
+            MOVED_TO_SECURE.add(Secure.WIFI_WATCHDOG_PING_TIMEOUT_MS);
+        }
+
+        /**
+         * Look up a name in the database.
+         * @param resolver to access the database with
+         * @param name to look up in the table
+         * @return the corresponding value, or null if not present
+         */
+        public synchronized static String getString(ContentResolver resolver, String name) {
+            if (MOVED_TO_SECURE.contains(name)) {
+                Log.w(TAG, "Setting " + name + " has moved from android.provider.Settings.System"
+                        + " to android.provider.Settings.Secure, returning read-only value.");
+                return Secure.getString(resolver, name);
+            }
+            if (mNameValueCache == null) {
+                mNameValueCache = new NameValueCache(SYS_PROP_SETTING_VERSION, CONTENT_URI);
+            }
+            return mNameValueCache.getString(resolver, name);
+        }
+
+        /**
+         * Store a name/value pair into the database.
+         * @param resolver to access the database with
+         * @param name to store
+         * @param value to associate with the name
+         * @return true if the value was set, false on database errors
+         */
+        public static boolean putString(ContentResolver resolver, String name, String value) {
+            if (MOVED_TO_SECURE.contains(name)) {
+                Log.w(TAG, "Setting " + name + " has moved from android.provider.Settings.System"
+                        + " to android.provider.Settings.Secure, value is unchanged.");
+                return false;
+            }
+            return putString(resolver, CONTENT_URI, name, value);
+        }
+
+        /**
+         * Construct the content URI for a particular name/value pair,
+         * useful for monitoring changes with a ContentObserver.
+         * @param name to look up in the table
+         * @return the corresponding content URI, or null if not present
+         */
+        public static Uri getUriFor(String name) {
+            if (MOVED_TO_SECURE.contains(name)) {
+                Log.w(TAG, "Setting " + name + " has moved from android.provider.Settings.System"
+                    + " to android.provider.Settings.Secure, returning Secure URI.");
+                return Secure.getUriFor(Secure.CONTENT_URI, name);
+            }
+            return getUriFor(CONTENT_URI, name);
+        }
+
+        /**
+         * Convenience function for retrieving a single system settings value
+         * as an integer.  Note that internally setting values are always
+         * stored as strings; this function converts the string to an integer
+         * for you.  The default value will be returned if the setting is
+         * not defined or not an integer.
+         *
+         * @param cr The ContentResolver to access.
+         * @param name The name of the setting to retrieve.
+         * @param def Value to return if the setting is not defined.
+         *
+         * @return The setting's current value, or 'def' if it is not defined
+         * or not a valid integer.
+         */
+        public static int getInt(ContentResolver cr, String name, int def) {
+            String v = getString(cr, name);
+            try {
+                return v != null ? Integer.parseInt(v) : def;
+            } catch (NumberFormatException e) {
+                return def;
+            }
+        }
+
+        /**
+         * Convenience function for retrieving a single system settings value
+         * as an integer.  Note that internally setting values are always
+         * stored as strings; this function converts the string to an integer
+         * for you.
+         * <p>
+         * This version does not take a default value.  If the setting has not
+         * been set, or the string value is not a number,
+         * it throws {@link SettingNotFoundException}.
+         *
+         * @param cr The ContentResolver to access.
+         * @param name The name of the setting to retrieve.
+         *
+         * @throws SettingNotFoundException Thrown if a setting by the given
+         * name can't be found or the setting value is not an integer.
+         *
+         * @return The setting's current value.
+         */
+        public static int getInt(ContentResolver cr, String name)
+                throws SettingNotFoundException {
+            String v = getString(cr, name);
+            try {
+                return Integer.parseInt(v);
+            } catch (NumberFormatException e) {
+                throw new SettingNotFoundException(name);
+            }
+        }
+
+        /**
+         * Convenience function for updating a single settings value as an
+         * integer. This will either create a new entry in the table if the
+         * given name does not exist, or modify the value of the existing row
+         * with that name.  Note that internally setting values are always
+         * stored as strings, so this function converts the given value to a
+         * string before storing it.
+         *
+         * @param cr The ContentResolver to access.
+         * @param name The name of the setting to modify.
+         * @param value The new value for the setting.
+         * @return true if the value was set, false on database errors
+         */
+        public static boolean putInt(ContentResolver cr, String name, int value) {
+            return putString(cr, name, Integer.toString(value));
+        }
+
+        /**
+         * Convenience function for retrieving a single system settings value
+         * as a {@code long}.  Note that internally setting values are always
+         * stored as strings; this function converts the string to a {@code long}
+         * for you.  The default value will be returned if the setting is
+         * not defined or not a {@code long}.
+         *
+         * @param cr The ContentResolver to access.
+         * @param name The name of the setting to retrieve.
+         * @param def Value to return if the setting is not defined.
+         *
+         * @return The setting's current value, or 'def' if it is not defined
+         * or not a valid {@code long}.
+         */
+        public static long getLong(ContentResolver cr, String name, long def) {
+            String valString = getString(cr, name);
+            long value;
+            try {
+                value = valString != null ? Long.parseLong(valString) : def;
+            } catch (NumberFormatException e) {
+                value = def;
+            }
+            return value;
+        }
+
+        /**
+         * Convenience function for retrieving a single system settings value
+         * as a {@code long}.  Note that internally setting values are always
+         * stored as strings; this function converts the string to a {@code long}
+         * for you.
+         * <p>
+         * This version does not take a default value.  If the setting has not
+         * been set, or the string value is not a number,
+         * it throws {@link SettingNotFoundException}.
+         *
+         * @param cr The ContentResolver to access.
+         * @param name The name of the setting to retrieve.
+         *
+         * @return The setting's current value.
+         * @throws SettingNotFoundException Thrown if a setting by the given
+         * name can't be found or the setting value is not an integer.
+         */
+        public static long getLong(ContentResolver cr, String name)
+                throws SettingNotFoundException {
+            String valString = getString(cr, name);
+            try {
+                return Long.parseLong(valString);
+            } catch (NumberFormatException e) {
+                throw new SettingNotFoundException(name);
+            }
+        }
+
+        /**
+         * Convenience function for updating a single settings value as a long
+         * integer. This will either create a new entry in the table if the
+         * given name does not exist, or modify the value of the existing row
+         * with that name.  Note that internally setting values are always
+         * stored as strings, so this function converts the given value to a
+         * string before storing it.
+         *
+         * @param cr The ContentResolver to access.
+         * @param name The name of the setting to modify.
+         * @param value The new value for the setting.
+         * @return true if the value was set, false on database errors
+         */
+        public static boolean putLong(ContentResolver cr, String name, long value) {
+            return putString(cr, name, Long.toString(value));
+        }
+
+        /**
+         * Convenience function for retrieving a single system settings value
+         * as a floating point number.  Note that internally setting values are
+         * always stored as strings; this function converts the string to an
+         * float for you. The default value will be returned if the setting
+         * is not defined or not a valid float.
+         *
+         * @param cr The ContentResolver to access.
+         * @param name The name of the setting to retrieve.
+         * @param def Value to return if the setting is not defined.
+         *
+         * @return The setting's current value, or 'def' if it is not defined
+         * or not a valid float.
+         */
+        public static float getFloat(ContentResolver cr, String name, float def) {
+            String v = getString(cr, name);
+            try {
+                return v != null ? Float.parseFloat(v) : def;
+            } catch (NumberFormatException e) {
+                return def;
+            }
+        }
+
+        /**
+         * Convenience function for retrieving a single system settings value
+         * as a float.  Note that internally setting values are always
+         * stored as strings; this function converts the string to a float
+         * for you.
+         * <p>
+         * This version does not take a default value.  If the setting has not
+         * been set, or the string value is not a number,
+         * it throws {@link SettingNotFoundException}.
+         *
+         * @param cr The ContentResolver to access.
+         * @param name The name of the setting to retrieve.
+         *
+         * @throws SettingNotFoundException Thrown if a setting by the given
+         * name can't be found or the setting value is not a float.
+         *
+         * @return The setting's current value.
+         */
+        public static float getFloat(ContentResolver cr, String name)
+                throws SettingNotFoundException {
+            String v = getString(cr, name);
+            try {
+                return Float.parseFloat(v);
+            } catch (NumberFormatException e) {
+                throw new SettingNotFoundException(name);
+            }
+        }
+
+        /**
+         * Convenience function for updating a single settings value as a
+         * floating point number. This will either create a new entry in the
+         * table if the given name does not exist, or modify the value of the
+         * existing row with that name.  Note that internally setting values
+         * are always stored as strings, so this function converts the given
+         * value to a string before storing it.
+         *
+         * @param cr The ContentResolver to access.
+         * @param name The name of the setting to modify.
+         * @param value The new value for the setting.
+         * @return true if the value was set, false on database errors
+         */
+        public static boolean putFloat(ContentResolver cr, String name, float value) {
+            return putString(cr, name, Float.toString(value));
+        }
+
+        /**
+         * Convenience function to read all of the current
+         * configuration-related settings into a
+         * {@link Configuration} object.
+         *
+         * @param cr The ContentResolver to access.
+         * @param outConfig Where to place the configuration settings.
+         */
+        public static void getConfiguration(ContentResolver cr, Configuration outConfig) {
+            outConfig.fontScale = Settings.System.getFloat(
+                cr, FONT_SCALE, outConfig.fontScale);
+            if (outConfig.fontScale < 0) {
+                outConfig.fontScale = 1;
+            }
+        }
+
+        /**
+         * Convenience function to write a batch of configuration-related
+         * settings from a {@link Configuration} object.
+         *
+         * @param cr The ContentResolver to access.
+         * @param config The settings to write.
+         * @return true if the values were set, false on database errors
+         */
+        public static boolean putConfiguration(ContentResolver cr, Configuration config) {
+            return Settings.System.putFloat(cr, FONT_SCALE, config.fontScale);
+        }
+
+        public static boolean getShowGTalkServiceStatus(ContentResolver cr) {
+            return getInt(cr, SHOW_GTALK_SERVICE_STATUS, 0) != 0;
+        }
+
+        public static void setShowGTalkServiceStatus(ContentResolver cr, boolean flag) {
+            putInt(cr, SHOW_GTALK_SERVICE_STATUS, flag ? 1 : 0);
+        }
+
+        /**
+         * The content:// style URL for this table
+         */
+        public static final Uri CONTENT_URI =
+            Uri.parse("content://" + AUTHORITY + "/system");
+
+        /**
+         * Whether we keep the device on while the device is plugged in.
+         * Supported values are:
+         * <ul>
+         * <li>{@code 0} to never stay on while plugged in</li>
+         * <li>{@link BatteryManager#BATTERY_PLUGGED_AC} to stay on for AC charger</li>
+         * <li>{@link BatteryManager#BATTERY_PLUGGED_USB} to stay on for USB charger</li>
+         * </ul>
+         * These values can be OR-ed together.
+         */
+        public static final String STAY_ON_WHILE_PLUGGED_IN = "stay_on_while_plugged_in";
+
+        /**
+         * What happens when the user presses the end call button if they're not
+         * on a call.<br/>
+         * <b>Values:</b><br/>
+         * 0 - The end button does nothing.<br/>
+         * 1 - The end button goes to the home screen.<br/>
+         * 2 - The end button puts the device to sleep and locks the keyguard.<br/>
+         * 3 - The end button goes to the home screen.  If the user is already on the
+         * home screen, it puts the device to sleep.
+         */
+        public static final String END_BUTTON_BEHAVIOR = "end_button_behavior";
+
+        /**
+         * Whether Airplane Mode is on.
+         */
+        public static final String AIRPLANE_MODE_ON = "airplane_mode_on";
+
+        /**
+         * Constant for use in AIRPLANE_MODE_RADIOS to specify Bluetooth radio.
+         */
+        public static final String RADIO_BLUETOOTH = "bluetooth";
+
+        /**
+         * Constant for use in AIRPLANE_MODE_RADIOS to specify Wi-Fi radio.
+         */
+        public static final String RADIO_WIFI = "wifi";
+
+        /**
+         * Constant for use in AIRPLANE_MODE_RADIOS to specify Cellular radio.
+         */
+        public static final String RADIO_CELL = "cell";
+
+        /**
+         * A comma separated list of radios that need to be disabled when airplane mode
+         * is on. This overrides WIFI_ON and BLUETOOTH_ON, if Wi-Fi and bluetooth are
+         * included in the comma separated list.
+         */
+        public static final String AIRPLANE_MODE_RADIOS = "airplane_mode_radios";
+
+        /**
+         * The policy for deciding when Wi-Fi should go to sleep (which will in
+         * turn switch to using the mobile data as an Internet connection).
+         * <p>
+         * Set to one of {@link #WIFI_SLEEP_POLICY_DEFAULT},
+         * {@link #WIFI_SLEEP_POLICY_NEVER_WHILE_PLUGGED}, or
+         * {@link #WIFI_SLEEP_POLICY_NEVER}.
+         * 
+         * @hide pending API council
+         */
+        public static final String WIFI_SLEEP_POLICY = "wifi_sleep_policy";
+
+        /**
+         * Value for {@link #WIFI_SLEEP_POLICY} to use the default Wi-Fi sleep
+         * policy, which is to sleep shortly after the turning off
+         * according to the {@link #STAY_ON_WHILE_PLUGGED_IN} setting.
+         * 
+         * @hide pending API council
+         */
+        public static final int WIFI_SLEEP_POLICY_DEFAULT = 0;
+
+        /**
+         * Value for {@link #WIFI_SLEEP_POLICY} to use the default policy when
+         * the device is on battery, and never go to sleep when the device is
+         * plugged in.
+         * 
+         * @hide pending API council
+         */
+        public static final int WIFI_SLEEP_POLICY_NEVER_WHILE_PLUGGED = 1;
+        
+        /**
+         * Value for {@link #WIFI_SLEEP_POLICY} to never go to sleep.
+         * 
+         * @hide pending API council
+         */
+        public static final int WIFI_SLEEP_POLICY_NEVER = 2;
+        
+        /**
+         * Whether to use static IP and other static network attributes.
+         * <p>
+         * Set to 1 for true and 0 for false.
+         */
+        public static final String WIFI_USE_STATIC_IP = "wifi_use_static_ip";
+
+        /**
+         * The static IP address.
+         * <p>
+         * Example: "192.168.1.51"
+         */
+        public static final String WIFI_STATIC_IP = "wifi_static_ip";
+
+        /**
+         * If using static IP, the gateway's IP address.
+         * <p>
+         * Example: "192.168.1.1"
+         */
+        public static final String WIFI_STATIC_GATEWAY = "wifi_static_gateway";
+
+        /**
+         * If using static IP, the net mask.
+         * <p>
+         * Example: "255.255.255.0"
+         */
+        public static final String WIFI_STATIC_NETMASK = "wifi_static_netmask";
+
+        /**
+         * If using static IP, the primary DNS's IP address.
+         * <p>
+         * Example: "192.168.1.1"
+         */
+        public static final String WIFI_STATIC_DNS1 = "wifi_static_dns1";
+
+        /**
+         * If using static IP, the secondary DNS's IP address.
+         * <p>
+         * Example: "192.168.1.2"
+         */
+        public static final String WIFI_STATIC_DNS2 = "wifi_static_dns2";
+
+        /**
+         * The number of radio channels that are allowed in the local
+         * 802.11 regulatory domain.
+         * @hide
+         */
+        public static final String WIFI_NUM_ALLOWED_CHANNELS = "wifi_num_allowed_channels";
+
+        /**
+         * Determines whether remote devices may discover and/or connect to
+         * this device.
+         * <P>Type: INT</P>
+         * 2 -- discoverable and connectable
+         * 1 -- connectable but not discoverable
+         * 0 -- neither connectable nor discoverable
+         */
+        public static final String BLUETOOTH_DISCOVERABILITY =
+            "bluetooth_discoverability";
+
+        /**
+         * Bluetooth discoverability timeout.  If this value is nonzero, then
+         * Bluetooth becomes discoverable for a certain number of seconds,
+         * after which is becomes simply connectable.  The value is in seconds.
+         */
+        public static final String BLUETOOTH_DISCOVERABILITY_TIMEOUT =
+            "bluetooth_discoverability_timeout";
+
+        /**
+         * Whether autolock is enabled (0 = false, 1 = true)
+         */
+        public static final String LOCK_PATTERN_ENABLED = "lock_pattern_autolock";
+
+        /**
+         * Whether lock pattern is visible as user enters (0 = false, 1 = true)
+         */
+        public static final String LOCK_PATTERN_VISIBLE = "lock_pattern_visible_pattern";
+
+        /**
+         * Whether lock pattern will vibrate as user enters (0 = false, 1 = true)
+         */
+        public static final String LOCK_PATTERN_TACTILE_FEEDBACK_ENABLED =
+            "lock_pattern_tactile_feedback_enabled";
+
+
+        /**
+         * A formatted string of the next alarm that is set, or the empty string
+         * if there is no alarm set.
+         */
+        public static final String NEXT_ALARM_FORMATTED = "next_alarm_formatted";
+
+        /**
+         * Scaling factor for fonts, float.
+         */
+        public static final String FONT_SCALE = "font_scale";
+
+        /**
+         * Name of an application package to be debugged.
+         */
+        public static final String DEBUG_APP = "debug_app";
+
+        /**
+         * If 1, when launching DEBUG_APP it will wait for the debugger before
+         * starting user code.  If 0, it will run normally.
+         */
+        public static final String WAIT_FOR_DEBUGGER = "wait_for_debugger";
+
+        /**
+         * Whether or not to dim the screen. 0=no  1=yes
+         */
+        public static final String DIM_SCREEN = "dim_screen";
+
+        /**
+         * The timeout before the screen turns off.
+         */
+        public static final String SCREEN_OFF_TIMEOUT = "screen_off_timeout";
+
+        /**
+         * The screen backlight brightness between 0 and 255.
+         */
+        public static final String SCREEN_BRIGHTNESS = "screen_brightness";
+
+        /**
+         * Control whether the process CPU usage meter should be shown.
+         */
+        public static final String SHOW_PROCESSES = "show_processes";
+
+        /**
+         * If 1, the activity manager will aggressively finish activities and
+         * processes as soon as they are no longer needed.  If 0, the normal
+         * extended lifetime is used.
+         */
+        public static final String ALWAYS_FINISH_ACTIVITIES =
+                "always_finish_activities";
+
+
+        /**
+         * Ringer mode. This is used internally, changing this value will not
+         * change the ringer mode. See AudioManager.
+         */
+        public static final String MODE_RINGER = "mode_ringer";
+
+        /**
+         * Determines which streams are affected by ringer mode changes. The
+         * stream type's bit should be set to 1 if it should be muted when going
+         * into an inaudible ringer mode.
+         */
+        public static final String MODE_RINGER_STREAMS_AFFECTED = "mode_ringer_streams_affected";
+
+         /**
+          * Determines which streams are affected by mute. The
+          * stream type's bit should be set to 1 if it should be muted when a mute request
+          * is received.
+          */
+         public static final String MUTE_STREAMS_AFFECTED = "mute_streams_affected";
+
+        /**
+         * Whether vibrate is on for different events. This is used internally,
+         * changing this value will not change the vibrate. See AudioManager.
+         */
+        public static final String VIBRATE_ON = "vibrate_on";
+
+        /**
+         * Ringer volume. This is used internally, changing this value will not
+         * change the volume. See AudioManager.
+         */
+        public static final String VOLUME_RING = "volume_ring";
+
+        /**
+         * System/notifications volume. This is used internally, changing this
+         * value will not change the volume. See AudioManager.
+         */
+        public static final String VOLUME_SYSTEM = "volume_system";
+
+        /**
+         * Voice call volume. This is used internally, changing this value will
+         * not change the volume. See AudioManager.
+         */
+        public static final String VOLUME_VOICE = "volume_voice";
+
+        /**
+         * Music/media/gaming volume. This is used internally, changing this
+         * value will not change the volume. See AudioManager.
+         */
+        public static final String VOLUME_MUSIC = "volume_music";
+
+        /**
+         * Alarm volume. This is used internally, changing this
+         * value will not change the volume. See AudioManager.
+         */
+        public static final String VOLUME_ALARM = "volume_alarm";
+
+        /**
+         * Notification volume. This is used internally, changing this
+         * value will not change the volume. See AudioManager.
+         */
+        public static final String VOLUME_NOTIFICATION = "volume_notification";
+
+        /**
+         * Whether the notifications should use the ring volume (value of 1) or
+         * a separate notification volume (value of 0). In most cases, users
+         * will have this enabled so the notification and ringer volumes will be
+         * the same. However, power users can disable this and use the separate
+         * notification volume control.
+         * <p>
+         * Note: This is a one-off setting that will be removed in the future
+         * when there is profile support. For this reason, it is kept hidden
+         * from the public APIs.
+         * 
+         * @hide
+         */
+        public static final String NOTIFICATIONS_USE_RING_VOLUME = 
+            "notifications_use_ring_volume";
+        
+        /**
+         * The mapping of stream type (integer) to its setting.
+         */
+        public static final String[] VOLUME_SETTINGS = {
+            VOLUME_VOICE, VOLUME_SYSTEM, VOLUME_RING, VOLUME_MUSIC,
+            VOLUME_ALARM, VOLUME_NOTIFICATION
+        };
+
+        /**
+         * Appended to various volume related settings to record the previous
+         * values before they the settings were affected by a silent/vibrate
+         * ringer mode change.
+         */
+        public static final String APPEND_FOR_LAST_AUDIBLE = "_last_audible";
+
+        /**
+         * Persistent store for the system-wide default ringtone URI.
+         * <p>
+         * If you need to play the default ringtone at any given time, it is recommended
+         * you give {@link #DEFAULT_RINGTONE_URI} to the media player.  It will resolve
+         * to the set default ringtone at the time of playing.
+         *
+         * @see #DEFAULT_RINGTONE_URI
+         */
+        public static final String RINGTONE = "ringtone";
+
+        /**
+         * A {@link Uri} that will point to the current default ringtone at any
+         * given time.
+         * <p>
+         * If the current default ringtone is in the DRM provider and the caller
+         * does not have permission, the exception will be a
+         * FileNotFoundException.
+         */
+        public static final Uri DEFAULT_RINGTONE_URI = getUriFor(RINGTONE);
+
+        /**
+         * Persistent store for the system-wide default notification sound.
+         *
+         * @see #RINGTONE
+         * @see #DEFAULT_NOTIFICATION_URI
+         */
+        public static final String NOTIFICATION_SOUND = "notification_sound";
+
+        /**
+         * A {@link Uri} that will point to the current default notification
+         * sound at any given time.
+         *
+         * @see #DEFAULT_RINGTONE_URI
+         */
+        public static final Uri DEFAULT_NOTIFICATION_URI = getUriFor(NOTIFICATION_SOUND);
+
+        /**
+         * Setting to enable Auto Replace (AutoText) in text editors. 1 = On, 0 = Off
+         */
+        public static final String TEXT_AUTO_REPLACE = "auto_replace";
+
+        /**
+         * Setting to enable Auto Caps in text editors. 1 = On, 0 = Off
+         */
+        public static final String TEXT_AUTO_CAPS = "auto_caps";
+
+        /**
+         * Setting to enable Auto Punctuate in text editors. 1 = On, 0 = Off. This
+         * feature converts two spaces to a "." and space.
+         */
+        public static final String TEXT_AUTO_PUNCTUATE = "auto_punctuate";
+        
+        /**
+         * Setting to showing password characters in text editors. 1 = On, 0 = Off
+         */
+        public static final String TEXT_SHOW_PASSWORD = "show_password";
+
+        public static final String SHOW_GTALK_SERVICE_STATUS =
+                "SHOW_GTALK_SERVICE_STATUS";
+
+        /**
+         * Name of activity to use for wallpaper on the home screen.
+         */
+        public static final String WALLPAPER_ACTIVITY = "wallpaper_activity";
+
+        /**
+         * Value to specify if the user prefers the date, time and time zone
+         * to be automatically fetched from the network (NITZ). 1=yes, 0=no
+         */
+        public static final String AUTO_TIME = "auto_time";
+
+        /**
+         * Display times as 12 or 24 hours
+         *   12
+         *   24
+         */
+        public static final String TIME_12_24 = "time_12_24";
+
+        /**
+         * Date format string
+         *   mm/dd/yyyy
+         *   dd/mm/yyyy
+         *   yyyy/mm/dd
+         */
+        public static final String DATE_FORMAT = "date_format";
+
+        /**
+         * Whether the setup wizard has been run before (on first boot), or if
+         * it still needs to be run.
+         *
+         * nonzero = it has been run in the past
+         * 0 = it has not been run in the past
+         */
+        public static final String SETUP_WIZARD_HAS_RUN = "setup_wizard_has_run";
+
+        /**
+         * Scaling factor for normal window animations. Setting to 0 will disable window
+         * animations.
+         */
+        public static final String WINDOW_ANIMATION_SCALE = "window_animation_scale";
+
+        /**
+         * Scaling factor for activity transition animations. Setting to 0 will disable window
+         * animations.
+         */
+        public static final String TRANSITION_ANIMATION_SCALE = "transition_animation_scale";
+
+        /**
+         * Scaling factor for normal window animations. Setting to 0 will disable window
+         * animations.
+         * @hide
+         */
+        public static final String FANCY_IME_ANIMATIONS = "fancy_ime_animations";
+
+        /**
+         * Control whether the accelerometer will be used to change screen
+         * orientation.  If 0, it will not be used unless explicitly requested
+         * by the application; if 1, it will be used by default unless explicitly
+         * disabled by the application.
+         */
+        public static final String ACCELEROMETER_ROTATION = "accelerometer_rotation";
+
+        /**
+         * Whether the audible DTMF tones are played by the dialer when dialing. The value is
+         * boolean (1 or 0).
+         */
+        public static final String DTMF_TONE_WHEN_DIALING = "dtmf_tone";
+
+        /**
+         * Whether the sounds effects (key clicks, lid open ...) are enabled. The value is
+         * boolean (1 or 0).
+         */
+        public static final String SOUND_EFFECTS_ENABLED = "sound_effects_enabled";
+        
+        /**
+         * Whether the haptic feedback (long presses, ...) are enabled. The value is
+         * boolean (1 or 0).
+         */
+        public static final String HAPTIC_FEEDBACK_ENABLED = "haptic_feedback_enabled";
+        
+        // Settings moved to Settings.Secure
+
+        /**
+         * @deprecated Use {@link android.provider.Settings.Secure#LOCATION_PROVIDERS_ALLOWED}
+         * instead
+         */
+        @Deprecated
+        public static final String ADB_ENABLED = Secure.ADB_ENABLED;
+
+        /**
+         * @deprecated Use {@link android.provider.Settings.Secure#ANDROID_ID} instead
+         */
+        @Deprecated
+        public static final String ANDROID_ID = Secure.ANDROID_ID;
+
+        /**
+         * @deprecated Use {@link android.provider.Settings.Secure#BLUETOOTH_ON} instead
+         */
+        @Deprecated
+        public static final String BLUETOOTH_ON = Secure.BLUETOOTH_ON;
+
+        /**
+         * @deprecated Use {@link android.provider.Settings.Secure#DATA_ROAMING} instead
+         */
+        @Deprecated
+        public static final String DATA_ROAMING = Secure.DATA_ROAMING;
+
+        /**
+         * @deprecated Use {@link android.provider.Settings.Secure#DEVICE_PROVISIONED} instead
+         */
+        @Deprecated
+        public static final String DEVICE_PROVISIONED = Secure.DEVICE_PROVISIONED;
+
+        /**
+         * @deprecated Use {@link android.provider.Settings.Secure#HTTP_PROXY} instead
+         */
+        @Deprecated
+        public static final String HTTP_PROXY = Secure.HTTP_PROXY;
+
+        /**
+         * @deprecated Use {@link android.provider.Settings.Secure#INSTALL_NON_MARKET_APPS} instead
+         */
+        @Deprecated
+        public static final String INSTALL_NON_MARKET_APPS = Secure.INSTALL_NON_MARKET_APPS;
+        
+        /**
+         * @deprecated Use {@link android.provider.Settings.Secure#LOCATION_PROVIDERS_ALLOWED}
+         * instead
+         */
+        @Deprecated
+        public static final String LOCATION_PROVIDERS_ALLOWED = Secure.LOCATION_PROVIDERS_ALLOWED;
+
+        /**
+         * @deprecated Use {@link android.provider.Settings.Secure#LOGGING_ID} instead
+         */
+        @Deprecated
+        public static final String LOGGING_ID = Secure.LOGGING_ID;
+        
+        /**
+         * @deprecated Use {@link android.provider.Settings.Secure#NETWORK_PREFERENCE} instead
+         */
+        @Deprecated
+        public static final String NETWORK_PREFERENCE = Secure.NETWORK_PREFERENCE;
+
+        /**
+         * @deprecated Use {@link android.provider.Settings.Secure#PARENTAL_CONTROL_ENABLED}
+         * instead
+         */
+        @Deprecated
+        public static final String PARENTAL_CONTROL_ENABLED = Secure.PARENTAL_CONTROL_ENABLED;
+
+        /**
+         * @deprecated Use {@link android.provider.Settings.Secure#PARENTAL_CONTROL_LAST_UPDATE}
+         * instead
+         */
+        @Deprecated
+        public static final String PARENTAL_CONTROL_LAST_UPDATE = Secure.PARENTAL_CONTROL_LAST_UPDATE;
+
+        /**
+         * @deprecated Use {@link android.provider.Settings.Secure#PARENTAL_CONTROL_REDIRECT_URL}
+         * instead
+         */
+        @Deprecated
+        public static final String PARENTAL_CONTROL_REDIRECT_URL =
+            Secure.PARENTAL_CONTROL_REDIRECT_URL;
+
+        /**
+         * @deprecated Use {@link android.provider.Settings.Secure#SETTINGS_CLASSNAME} instead
+         */
+        @Deprecated
+        public static final String SETTINGS_CLASSNAME = Secure.SETTINGS_CLASSNAME;
+
+        /**
+         * @deprecated Use {@link android.provider.Settings.Secure#USB_MASS_STORAGE_ENABLED} instead
+         */
+        @Deprecated
+        public static final String USB_MASS_STORAGE_ENABLED = Secure.USB_MASS_STORAGE_ENABLED;
+        
+        /**
+         * @deprecated Use {@link android.provider.Settings.Secure#USE_GOOGLE_MAIL} instead
+         */
+        @Deprecated
+        public static final String USE_GOOGLE_MAIL = Secure.USE_GOOGLE_MAIL;
+
+//       /**
+//         * @deprecated Use {@link android.provider.Settings.Secure#WIFI_MAX_DHCP_RETRY_COUNT}
+//         * instead
+//         */
+        @Deprecated
+        public static final String WIFI_MAX_DHCP_RETRY_COUNT = Secure.WIFI_MAX_DHCP_RETRY_COUNT;
+
+//        /**
+//         * @deprecated Use
+//         * {@link android.provider.Settings.Secure#WIFI_MOBILE_DATA_TRANSITION_WAKELOCK_TIMEOUT_MS}
+//         * instead
+//         */
+        @Deprecated
+        public static final String WIFI_MOBILE_DATA_TRANSITION_WAKELOCK_TIMEOUT_MS =
+                Secure.WIFI_MOBILE_DATA_TRANSITION_WAKELOCK_TIMEOUT_MS;
+
+        /**
+         * @deprecated Use
+         * {@link android.provider.Settings.Secure#WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON} instead
+         */
+        @Deprecated
+        public static final String WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON =
+            Secure.WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON;
+
+        /**
+         * @deprecated Use
+         * {@link android.provider.Settings.Secure#WIFI_NETWORKS_AVAILABLE_REPEAT_DELAY} instead
+         */
+        @Deprecated
+        public static final String WIFI_NETWORKS_AVAILABLE_REPEAT_DELAY =
+            Secure.WIFI_NETWORKS_AVAILABLE_REPEAT_DELAY;
+        
+        /**
+         * @deprecated Use {@link android.provider.Settings.Secure#WIFI_NUM_OPEN_NETWORKS_KEPT}
+         * instead
+         */
+        @Deprecated
+        public static final String WIFI_NUM_OPEN_NETWORKS_KEPT = Secure.WIFI_NUM_OPEN_NETWORKS_KEPT;
+
+        /**
+         * @deprecated Use {@link android.provider.Settings.Secure#WIFI_ON} instead
+         */
+        @Deprecated
+        public static final String WIFI_ON = Secure.WIFI_ON;
+
+        /**
+         * @deprecated Use
+         * {@link android.provider.Settings.Secure#WIFI_WATCHDOG_ACCEPTABLE_PACKET_LOSS_PERCENTAGE}
+         * instead
+         */
+        @Deprecated
+        public static final String WIFI_WATCHDOG_ACCEPTABLE_PACKET_LOSS_PERCENTAGE =
+                Secure.WIFI_WATCHDOG_ACCEPTABLE_PACKET_LOSS_PERCENTAGE;
+
+        /**
+         * @deprecated Use {@link android.provider.Settings.Secure#WIFI_WATCHDOG_AP_COUNT} instead
+         */
+        @Deprecated
+        public static final String WIFI_WATCHDOG_AP_COUNT = Secure.WIFI_WATCHDOG_AP_COUNT;
+
+        /**
+         * @deprecated Use
+         * {@link android.provider.Settings.Secure#WIFI_WATCHDOG_BACKGROUND_CHECK_DELAY_MS} instead
+         */
+        @Deprecated
+        public static final String WIFI_WATCHDOG_BACKGROUND_CHECK_DELAY_MS =
+                Secure.WIFI_WATCHDOG_BACKGROUND_CHECK_DELAY_MS;
+        
+        /**
+         * @deprecated Use
+         * {@link android.provider.Settings.Secure#WIFI_WATCHDOG_BACKGROUND_CHECK_ENABLED} instead
+         */
+        @Deprecated
+        public static final String WIFI_WATCHDOG_BACKGROUND_CHECK_ENABLED =
+                Secure.WIFI_WATCHDOG_BACKGROUND_CHECK_ENABLED;
+
+        /**
+         * @deprecated Use
+         * {@link android.provider.Settings.Secure#WIFI_WATCHDOG_BACKGROUND_CHECK_TIMEOUT_MS}
+         * instead
+         */
+        @Deprecated
+        public static final String WIFI_WATCHDOG_BACKGROUND_CHECK_TIMEOUT_MS =
+                Secure.WIFI_WATCHDOG_BACKGROUND_CHECK_TIMEOUT_MS;
+
+        /**
+         * @deprecated Use
+         * {@link android.provider.Settings.Secure#WIFI_WATCHDOG_INITIAL_IGNORED_PING_COUNT} instead
+         */
+        @Deprecated
+        public static final String WIFI_WATCHDOG_INITIAL_IGNORED_PING_COUNT =
+            Secure.WIFI_WATCHDOG_INITIAL_IGNORED_PING_COUNT;
+
+        /**
+         * @deprecated Use {@link android.provider.Settings.Secure#WIFI_WATCHDOG_MAX_AP_CHECKS}
+         * instead
+         */
+        @Deprecated
+        public static final String WIFI_WATCHDOG_MAX_AP_CHECKS = Secure.WIFI_WATCHDOG_MAX_AP_CHECKS;
+
+        /**
+         * @deprecated Use {@link android.provider.Settings.Secure#WIFI_WATCHDOG_ON} instead
+         */
+        @Deprecated
+        public static final String WIFI_WATCHDOG_ON = Secure.WIFI_WATCHDOG_ON;
+
+        /**
+         * @deprecated Use {@link android.provider.Settings.Secure#WIFI_WATCHDOG_PING_COUNT} instead
+         */
+        @Deprecated
+        public static final String WIFI_WATCHDOG_PING_COUNT = Secure.WIFI_WATCHDOG_PING_COUNT;
+
+        /**
+         * @deprecated Use {@link android.provider.Settings.Secure#WIFI_WATCHDOG_PING_DELAY_MS}
+         * instead
+         */
+        @Deprecated
+        public static final String WIFI_WATCHDOG_PING_DELAY_MS = Secure.WIFI_WATCHDOG_PING_DELAY_MS;
+
+        /**
+         * @deprecated Use {@link android.provider.Settings.Secure#WIFI_WATCHDOG_PING_TIMEOUT_MS}
+         * instead
+         */
+        @Deprecated
+        public static final String WIFI_WATCHDOG_PING_TIMEOUT_MS =
+            Secure.WIFI_WATCHDOG_PING_TIMEOUT_MS;
+    }
+
+    /**
+     * Secure system settings, containing system preferences that applications
+     * can read but are not allowed to write.  These are for preferences that
+     * the user must explicitly modify through the system UI or specialized
+     * APIs for those values, not modified directly by applications.
+     */
+    public static final class Secure extends NameValueTable {
+        public static final String SYS_PROP_SETTING_VERSION = "sys.settings_secure_version";
+
+        private static volatile NameValueCache mNameValueCache = null;
+
+        /**
+         * Look up a name in the database.
+         * @param resolver to access the database with
+         * @param name to look up in the table
+         * @return the corresponding value, or null if not present
+         */
+        public synchronized static String getString(ContentResolver resolver, String name) {
+            if (mNameValueCache == null) {
+                mNameValueCache = new NameValueCache(SYS_PROP_SETTING_VERSION, CONTENT_URI);
+            }
+            return mNameValueCache.getString(resolver, name);
+        }
+
+        /**
+         * Store a name/value pair into the database.
+         * @param resolver to access the database with
+         * @param name to store
+         * @param value to associate with the name
+         * @return true if the value was set, false on database errors
+         */
+        public static boolean putString(ContentResolver resolver,
+                String name, String value) {
+            return putString(resolver, CONTENT_URI, name, value);
+        }
+
+        /**
+         * Construct the content URI for a particular name/value pair,
+         * useful for monitoring changes with a ContentObserver.
+         * @param name to look up in the table
+         * @return the corresponding content URI, or null if not present
+         */
+        public static Uri getUriFor(String name) {
+            return getUriFor(CONTENT_URI, name);
+        }
+
+        /**
+         * Convenience function for retrieving a single secure settings value
+         * as an integer.  Note that internally setting values are always
+         * stored as strings; this function converts the string to an integer
+         * for you.  The default value will be returned if the setting is
+         * not defined or not an integer.
+         *
+         * @param cr The ContentResolver to access.
+         * @param name The name of the setting to retrieve.
+         * @param def Value to return if the setting is not defined.
+         *
+         * @return The setting's current value, or 'def' if it is not defined
+         * or not a valid integer.
+         */
+        public static int getInt(ContentResolver cr, String name, int def) {
+            String v = getString(cr, name);
+            try {
+                return v != null ? Integer.parseInt(v) : def;
+            } catch (NumberFormatException e) {
+                return def;
+            }
+        }
+
+        /**
+         * Convenience function for retrieving a single secure settings value
+         * as an integer.  Note that internally setting values are always
+         * stored as strings; this function converts the string to an integer
+         * for you.
+         * <p>
+         * This version does not take a default value.  If the setting has not
+         * been set, or the string value is not a number,
+         * it throws {@link SettingNotFoundException}.
+         *
+         * @param cr The ContentResolver to access.
+         * @param name The name of the setting to retrieve.
+         *
+         * @throws SettingNotFoundException Thrown if a setting by the given
+         * name can't be found or the setting value is not an integer.
+         *
+         * @return The setting's current value.
+         */
+        public static int getInt(ContentResolver cr, String name)
+                throws SettingNotFoundException {
+            String v = getString(cr, name);
+            try {
+                return Integer.parseInt(v);
+            } catch (NumberFormatException e) {
+                throw new SettingNotFoundException(name);
+            }
+        }
+
+        /**
+         * Convenience function for updating a single settings value as an
+         * integer. This will either create a new entry in the table if the
+         * given name does not exist, or modify the value of the existing row
+         * with that name.  Note that internally setting values are always
+         * stored as strings, so this function converts the given value to a
+         * string before storing it.
+         *
+         * @param cr The ContentResolver to access.
+         * @param name The name of the setting to modify.
+         * @param value The new value for the setting.
+         * @return true if the value was set, false on database errors
+         */
+        public static boolean putInt(ContentResolver cr, String name, int value) {
+            return putString(cr, name, Integer.toString(value));
+        }
+
+        /**
+         * Convenience function for retrieving a single secure settings value
+         * as a {@code long}.  Note that internally setting values are always
+         * stored as strings; this function converts the string to a {@code long}
+         * for you.  The default value will be returned if the setting is
+         * not defined or not a {@code long}.
+         *
+         * @param cr The ContentResolver to access.
+         * @param name The name of the setting to retrieve.
+         * @param def Value to return if the setting is not defined.
+         *
+         * @return The setting's current value, or 'def' if it is not defined
+         * or not a valid {@code long}.
+         */
+        public static long getLong(ContentResolver cr, String name, long def) {
+            String valString = getString(cr, name);
+            long value;
+            try {
+                value = valString != null ? Long.parseLong(valString) : def;
+            } catch (NumberFormatException e) {
+                value = def;
+            }
+            return value;
+        }
+
+        /**
+         * Convenience function for retrieving a single secure settings value
+         * as a {@code long}.  Note that internally setting values are always
+         * stored as strings; this function converts the string to a {@code long}
+         * for you.
+         * <p>
+         * This version does not take a default value.  If the setting has not
+         * been set, or the string value is not a number,
+         * it throws {@link SettingNotFoundException}.
+         *
+         * @param cr The ContentResolver to access.
+         * @param name The name of the setting to retrieve.
+         *
+         * @return The setting's current value.
+         * @throws SettingNotFoundException Thrown if a setting by the given
+         * name can't be found or the setting value is not an integer.
+         */
+        public static long getLong(ContentResolver cr, String name)
+                throws SettingNotFoundException {
+            String valString = getString(cr, name);
+            try {
+                return Long.parseLong(valString);
+            } catch (NumberFormatException e) {
+                throw new SettingNotFoundException(name);
+            }
+        }
+
+        /**
+         * Convenience function for updating a secure settings value as a long
+         * integer. This will either create a new entry in the table if the
+         * given name does not exist, or modify the value of the existing row
+         * with that name.  Note that internally setting values are always
+         * stored as strings, so this function converts the given value to a
+         * string before storing it.
+         *
+         * @param cr The ContentResolver to access.
+         * @param name The name of the setting to modify.
+         * @param value The new value for the setting.
+         * @return true if the value was set, false on database errors
+         */
+        public static boolean putLong(ContentResolver cr, String name, long value) {
+            return putString(cr, name, Long.toString(value));
+        }
+
+        /**
+         * Convenience function for retrieving a single secure settings value
+         * as a floating point number.  Note that internally setting values are
+         * always stored as strings; this function converts the string to an
+         * float for you. The default value will be returned if the setting
+         * is not defined or not a valid float.
+         *
+         * @param cr The ContentResolver to access.
+         * @param name The name of the setting to retrieve.
+         * @param def Value to return if the setting is not defined.
+         *
+         * @return The setting's current value, or 'def' if it is not defined
+         * or not a valid float.
+         */
+        public static float getFloat(ContentResolver cr, String name, float def) {
+            String v = getString(cr, name);
+            try {
+                return v != null ? Float.parseFloat(v) : def;
+            } catch (NumberFormatException e) {
+                return def;
+            }
+        }
+
+        /**
+         * Convenience function for retrieving a single secure settings value
+         * as a float.  Note that internally setting values are always
+         * stored as strings; this function converts the string to a float
+         * for you.
+         * <p>
+         * This version does not take a default value.  If the setting has not
+         * been set, or the string value is not a number,
+         * it throws {@link SettingNotFoundException}.
+         *
+         * @param cr The ContentResolver to access.
+         * @param name The name of the setting to retrieve.
+         *
+         * @throws SettingNotFoundException Thrown if a setting by the given
+         * name can't be found or the setting value is not a float.
+         *
+         * @return The setting's current value.
+         */
+        public static float getFloat(ContentResolver cr, String name)
+                throws SettingNotFoundException {
+            String v = getString(cr, name);
+            try {
+                return Float.parseFloat(v);
+            } catch (NumberFormatException e) {
+                throw new SettingNotFoundException(name);
+            }
+        }
+
+        /**
+         * Convenience function for updating a single settings value as a
+         * floating point number. This will either create a new entry in the
+         * table if the given name does not exist, or modify the value of the
+         * existing row with that name.  Note that internally setting values
+         * are always stored as strings, so this function converts the given
+         * value to a string before storing it.
+         *
+         * @param cr The ContentResolver to access.
+         * @param name The name of the setting to modify.
+         * @param value The new value for the setting.
+         * @return true if the value was set, false on database errors
+         */
+        public static boolean putFloat(ContentResolver cr, String name, float value) {
+            return putString(cr, name, Float.toString(value));
+        }
+
+        /**
+         * The content:// style URL for this table
+         */
+        public static final Uri CONTENT_URI =
+            Uri.parse("content://" + AUTHORITY + "/secure");
+
+        /**
+         * Whether ADB is enabled.
+         */
+        public static final String ADB_ENABLED = "adb_enabled";
+
+        /**
+         * Setting to allow mock locations and location provider status to be injected into the
+         * LocationManager service for testing purposes during application development.  These
+         * locations and status values  override actual location and status information generated
+         * by network, gps, or other location providers.
+         */
+        public static final String ALLOW_MOCK_LOCATION = "mock_location";
+
+        /**
+         * The Android ID (a unique 64-bit value) as a hex string.
+         * Identical to that obtained by calling
+         * GoogleLoginService.getAndroidId(); it is also placed here
+         * so you can get it without binding to a service.
+         */
+        public static final String ANDROID_ID = "android_id";
+
+        /**
+         * Whether bluetooth is enabled/disabled
+         * 0=disabled. 1=enabled.
+         */
+        public static final String BLUETOOTH_ON = "bluetooth_on";
+
+        /**
+         * Get the key that retrieves a bluetooth headset's priority.
+         * @hide
+         */
+        public static final String getBluetoothHeadsetPriorityKey(String address) {
+            return ("bluetooth_headset_priority_" + address.toUpperCase());
+        }
+
+        /**
+         * Get the key that retrieves a bluetooth a2dp sink's priority.
+         * @hide
+         */
+        public static final String getBluetoothA2dpSinkPriorityKey(String address) {
+            return ("bluetooth_a2dp_sink_priority_" + address.toUpperCase());
+        }
+
+        /**
+         * Whether or not data roaming is enabled. (0 = false, 1 = true)
+         */
+        public static final String DATA_ROAMING = "data_roaming";
+
+        /**
+         * Setting to record the input method used by default, holding the ID
+         * of the desired method.
+         */
+        public static final String DEFAULT_INPUT_METHOD = "default_input_method";
+
+        /**
+         * Whether the device has been provisioned (0 = false, 1 = true)
+         */
+        public static final String DEVICE_PROVISIONED = "device_provisioned";
+    
+        /**
+         * List of input methods that are currently enabled.  This is a string
+         * containing the IDs of all enabled input methods, each ID separated
+         * by ':'.
+         */
+        public static final String ENABLED_INPUT_METHODS = "enabled_input_methods";
+    
+        /**
+         * Host name and port for a user-selected proxy.
+         */
+        public static final String HTTP_PROXY = "http_proxy";
+    
+        /**
+         * Whether the package installer should allow installation of apps downloaded from
+         * sources other than the Android Market (vending machine).
+         *
+         * 1 = allow installing from other sources
+         * 0 = only allow installing from the Android Market
+         */
+        public static final String INSTALL_NON_MARKET_APPS = "install_non_market_apps";
+    
+        /**
+         * Comma-separated list of location providers that activities may access.
+         */
+        public static final String LOCATION_PROVIDERS_ALLOWED = "location_providers_allowed";
+    
+        /**
+         * The Logging ID (a unique 64-bit value) as a hex string.
+         * Used as a pseudonymous identifier for logging.
+         * @deprecated This identifier is poorly initialized and has
+         * many collisions.  It should not be used.
+         */
+        @Deprecated
+        public static final String LOGGING_ID = "logging_id";
+
+        /**
+         * The Logging ID (a unique 64-bit value) as a hex string.
+         * Used as a pseudonymous identifier for logging.
+         * @hide
+         */
+        public static final String LOGGING_ID2 = "logging_id2";
+
+        /**
+         * User preference for which network(s) should be used. Only the
+         * connectivity service should touch this.
+         */
+        public static final String NETWORK_PREFERENCE = "network_preference";
+    
+        /** 
+         */
+        public static final String PARENTAL_CONTROL_ENABLED = "parental_control_enabled";
+    
+        /** 
+         */
+        public static final String PARENTAL_CONTROL_LAST_UPDATE = "parental_control_last_update";
+    
+        /** 
+         */
+        public static final String PARENTAL_CONTROL_REDIRECT_URL = "parental_control_redirect_url";
+    
+        /**
+         * Settings classname to launch when Settings is clicked from All
+         * Applications.  Needed because of user testing between the old
+         * and new Settings apps.
+         */
+        // TODO: 881807
+        public static final String SETTINGS_CLASSNAME = "settings_classname";
+    
+        /**
+         * USB Mass Storage Enabled
+         */
+        public static final String USB_MASS_STORAGE_ENABLED = "usb_mass_storage_enabled";
+    
+        /**
+         * If this setting is set (to anything), then all references
+         * to Gmail on the device must change to Google Mail.
+         */
+        public static final String USE_GOOGLE_MAIL = "use_google_mail";
+    
+        /**
+         * Whether to notify the user of open networks.
+         * <p>
+         * If not connected and the scan results have an open network, we will
+         * put this notification up. If we attempt to connect to a network or
+         * the open network(s) disappear, we remove the notification. When we
+         * show the notification, we will not show it again for
+         * {@link android.provider.Settings.Secure#WIFI_NETWORKS_AVAILABLE_REPEAT_DELAY} time.
+         */
+        public static final String WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON =
+                "wifi_networks_available_notification_on";
+    
+        /**
+         * Delay (in seconds) before repeating the Wi-Fi networks available notification.
+         * Connecting to a network will reset the timer.
+         */
+        public static final String WIFI_NETWORKS_AVAILABLE_REPEAT_DELAY =
+                "wifi_networks_available_repeat_delay";
+    
+        /**
+         * The number of radio channels that are allowed in the local
+         * 802.11 regulatory domain.
+         * @hide
+         */
+        public static final String WIFI_NUM_ALLOWED_CHANNELS = "wifi_num_allowed_channels";
+    
+        /**
+         * When the number of open networks exceeds this number, the
+         * least-recently-used excess networks will be removed.
+         */
+        public static final String WIFI_NUM_OPEN_NETWORKS_KEPT = "wifi_num_open_networks_kept";
+    
+        /**
+         * Whether the Wi-Fi should be on.  Only the Wi-Fi service should touch this.
+         */
+        public static final String WIFI_ON = "wifi_on";
+    
+        /**
+         * The acceptable packet loss percentage (range 0 - 100) before trying
+         * another AP on the same network.
+         */
+        public static final String WIFI_WATCHDOG_ACCEPTABLE_PACKET_LOSS_PERCENTAGE =
+                "wifi_watchdog_acceptable_packet_loss_percentage";
+    
+        /**
+         * The number of access points required for a network in order for the
+         * watchdog to monitor it.
+         */
+        public static final String WIFI_WATCHDOG_AP_COUNT = "wifi_watchdog_ap_count";
+    
+        /**
+         * The delay between background checks.
+         */
+        public static final String WIFI_WATCHDOG_BACKGROUND_CHECK_DELAY_MS =
+                "wifi_watchdog_background_check_delay_ms";
+    
+        /**
+         * Whether the Wi-Fi watchdog is enabled for background checking even
+         * after it thinks the user has connected to a good access point.
+         */
+        public static final String WIFI_WATCHDOG_BACKGROUND_CHECK_ENABLED =
+                "wifi_watchdog_background_check_enabled";
+    
+        /**
+         * The timeout for a background ping
+         */
+        public static final String WIFI_WATCHDOG_BACKGROUND_CHECK_TIMEOUT_MS =
+                "wifi_watchdog_background_check_timeout_ms";
+    
+        /**
+         * The number of initial pings to perform that *may* be ignored if they
+         * fail. Again, if these fail, they will *not* be used in packet loss
+         * calculation. For example, one network always seemed to time out for
+         * the first couple pings, so this is set to 3 by default.
+         */
+        public static final String WIFI_WATCHDOG_INITIAL_IGNORED_PING_COUNT =
+            "wifi_watchdog_initial_ignored_ping_count";
+    
+        /**
+         * The maximum number of access points (per network) to attempt to test.
+         * If this number is reached, the watchdog will no longer monitor the
+         * initial connection state for the network. This is a safeguard for
+         * networks containing multiple APs whose DNS does not respond to pings.
+         */
+        public static final String WIFI_WATCHDOG_MAX_AP_CHECKS = "wifi_watchdog_max_ap_checks";
+    
+        /**
+         * Whether the Wi-Fi watchdog is enabled.
+         */
+        public static final String WIFI_WATCHDOG_ON = "wifi_watchdog_on";
+
+        /**
+         * A comma-separated list of SSIDs for which the Wi-Fi watchdog should be enabled.
+         * @hide pending API council
+         */
+        public static final String WIFI_WATCHDOG_WATCH_LIST = "wifi_watchdog_watch_list";
+
+        /**
+         * The number of pings to test if an access point is a good connection.
+         */
+        public static final String WIFI_WATCHDOG_PING_COUNT = "wifi_watchdog_ping_count";
+    
+        /**
+         * The delay between pings.
+         */
+        public static final String WIFI_WATCHDOG_PING_DELAY_MS = "wifi_watchdog_ping_delay_ms";
+    
+        /**
+         * The timeout per ping.
+         */
+        public static final String WIFI_WATCHDOG_PING_TIMEOUT_MS = "wifi_watchdog_ping_timeout_ms";
+    
+        /**
+         * The maximum number of times we will retry a connection to an access
+         * point for which we have failed in acquiring an IP address from DHCP.
+         * A value of N means that we will make N+1 connection attempts in all.
+         * 
+         * @hide pending API Council approval
+         */
+        public static final String WIFI_MAX_DHCP_RETRY_COUNT = "wifi_max_dhcp_retry_count";
+    
+        /**
+         * Maximum amount of time in milliseconds to hold a wakelock while waiting for mobile
+         * data connectivity to be established after a disconnect from Wi-Fi.
+         * 
+         * @hide pending API Council approval
+         */
+        public static final String WIFI_MOBILE_DATA_TRANSITION_WAKELOCK_TIMEOUT_MS =
+            "wifi_mobile_data_transition_wakelock_timeout_ms";
+
+        /**
+         * Whether background data usage is allowed by the user. See
+         * ConnectivityManager for more info.
+         * 
+         * @hide pending API council
+         */
+        public static final String BACKGROUND_DATA = "background_data";
+    }
+    
+    /**
+     * Gservices settings, containing the network names for Google's
+     * various services. This table holds simple name/addr pairs.
+     * Addresses can be accessed through the getString() method.
+     *
+     * TODO: This should move to partner/google/... somewhere.
+     *
+     * @hide
+     */
+    public static final class Gservices extends NameValueTable {
+        public static final String SYS_PROP_SETTING_VERSION = "sys.settings_gservices_version";
+
+        /**
+         * Intent action broadcast when the Gservices table is updated by the server.
+         * This is broadcast once after settings change (so many values may have been updated).
+         */
+        @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+        public static final String CHANGED_ACTION =
+            "com.google.gservices.intent.action.GSERVICES_CHANGED";
+
+        private static volatile NameValueCache mNameValueCache = null;
+        private static final Object mNameValueCacheLock = new Object();
+
+        /**
+         * Look up a name in the database.
+         * @param resolver to access the database with
+         * @param name to look up in the table
+         * @return the corresponding value, or null if not present
+         */
+        public static String getString(ContentResolver resolver, String name) {
+            synchronized (mNameValueCacheLock) {
+                if (mNameValueCache == null) {
+                    mNameValueCache = new NameValueCache(SYS_PROP_SETTING_VERSION, CONTENT_URI);
+                }
+                return mNameValueCache.getString(resolver, name);
+            }
+        }
+
+        /**
+         * Store a name/value pair into the database.
+         * @param resolver to access the database with
+         * @param name to store
+         * @param value to associate with the name
+         * @return true if the value was set, false on database errors
+         */
+        public static boolean putString(ContentResolver resolver,
+                String name, String value) {
+            return putString(resolver, CONTENT_URI, name, value);
+        }
+
+        /**
+         * Look up the value for name in the database, convert it to an int using Integer.parseInt
+         * and return it. If it is null or if a NumberFormatException is caught during the
+         * conversion then return defValue.
+         */
+        public static int getInt(ContentResolver resolver, String name, int defValue) {
+            String valString = getString(resolver, name);
+            int value;
+            try {
+                value = valString != null ? Integer.parseInt(valString) : defValue;
+            } catch (NumberFormatException e) {
+                value = defValue;
+            }
+            return value;
+        }
+
+        /**
+         * Look up the value for name in the database, convert it to a long using Long.parseLong
+         * and return it. If it is null or if a NumberFormatException is caught during the
+         * conversion then return defValue.
+         */
+        public static long getLong(ContentResolver resolver, String name, long defValue) {
+            String valString = getString(resolver, name);
+            long value;
+            try {
+                value = valString != null ? Long.parseLong(valString) : defValue;
+            } catch (NumberFormatException e) {
+                value = defValue;
+            }
+            return value;
+        }
+
+        /**
+         * Construct the content URI for a particular name/value pair,
+         * useful for monitoring changes with a ContentObserver.
+         * @param name to look up in the table
+         * @return the corresponding content URI, or null if not present
+         */
+        public static Uri getUriFor(String name) {
+            return getUriFor(CONTENT_URI, name);
+        }
+
+        /**
+         * The content:// style URL for this table
+         */
+        public static final Uri CONTENT_URI =
+                Uri.parse("content://" + AUTHORITY + "/gservices");
+
+        /**
+         * MMS - URL to use for HTTP "x-wap-profile" header
+         */
+        public static final String MMS_X_WAP_PROFILE_URL
+                = "mms_x_wap_profile_url";
+
+        /**
+         * YouTube - the flag to indicate whether to use proxy
+         */
+        public static final String YOUTUBE_USE_PROXY
+                = "youtube_use_proxy";
+
+        /**
+         * MMS - maximum message size in bytes for a MMS message.
+         */
+        public static final String MMS_MAXIMUM_MESSAGE_SIZE
+                = "mms_maximum_message_size";
+
+        /**
+         * Event tags from the kernel event log to upload during checkin.
+         */
+        public static final String CHECKIN_EVENTS = "checkin_events";
+        
+        /**
+         * Event tags for list of services to upload during checkin.
+         */
+        public static final String CHECKIN_DUMPSYS_LIST = "checkin_dumpsys_list";
+
+        /**
+         * The interval (in seconds) between periodic checkin attempts.
+         */
+        public static final String CHECKIN_INTERVAL = "checkin_interval";
+
+        /**
+         * Boolean indicating if the market app should force market only checkins on
+         * install/uninstall. Any non-0 value is considered true.
+         */
+        public static final String MARKET_FORCE_CHECKIN = "market_force_checkin";
+
+        /**
+         * How frequently (in seconds) to check the memory status of the
+         * device.
+         */
+        public static final String MEMCHECK_INTERVAL = "memcheck_interval";
+
+        /**
+         * Max frequency (in seconds) to log memory check stats, in realtime
+         * seconds.  This allows for throttling of logs when the device is
+         * running for large amounts of time.
+         */
+        public static final String MEMCHECK_LOG_REALTIME_INTERVAL =
+                "memcheck_log_realtime_interval";
+
+        /**
+         * Boolean indicating whether rebooting due to system memory checks
+         * is enabled.
+         */
+        public static final String MEMCHECK_SYSTEM_ENABLED = "memcheck_system_enabled";
+
+        /**
+         * How many bytes the system process must be below to avoid scheduling
+         * a soft reboot.  This reboot will happen when it is next determined
+         * to be a good time.
+         */
+        public static final String MEMCHECK_SYSTEM_SOFT_THRESHOLD = "memcheck_system_soft";
+
+        /**
+         * How many bytes the system process must be below to avoid scheduling
+         * a hard reboot.  This reboot will happen immediately.
+         */
+        public static final String MEMCHECK_SYSTEM_HARD_THRESHOLD = "memcheck_system_hard";
+
+        /**
+         * How many bytes the phone process must be below to avoid scheduling
+         * a soft restart.  This restart will happen when it is next determined
+         * to be a good time.
+         */
+        public static final String MEMCHECK_PHONE_SOFT_THRESHOLD = "memcheck_phone_soft";
+
+        /**
+         * How many bytes the phone process must be below to avoid scheduling
+         * a hard restart.  This restart will happen immediately.
+         */
+        public static final String MEMCHECK_PHONE_HARD_THRESHOLD = "memcheck_phone_hard";
+
+        /**
+         * Boolean indicating whether restarting the phone process due to
+         * memory checks is enabled.
+         */
+        public static final String MEMCHECK_PHONE_ENABLED = "memcheck_phone_enabled";
+
+        /**
+         * First time during the day it is okay to kill processes
+         * or reboot the device due to low memory situations.  This number is
+         * in seconds since midnight.
+         */
+        public static final String MEMCHECK_EXEC_START_TIME = "memcheck_exec_start_time";
+
+        /**
+         * Last time during the day it is okay to kill processes
+         * or reboot the device due to low memory situations.  This number is
+         * in seconds since midnight.
+         */
+        public static final String MEMCHECK_EXEC_END_TIME = "memcheck_exec_end_time";
+
+        /**
+         * How long the screen must have been off in order to kill processes
+         * or reboot.  This number is in seconds.  A value of -1 means to
+         * entirely disregard whether the screen is on.
+         */
+        public static final String MEMCHECK_MIN_SCREEN_OFF = "memcheck_min_screen_off";
+
+        /**
+         * How much time there must be until the next alarm in order to kill processes
+         * or reboot.  This number is in seconds.  Note: this value must be
+         * smaller than {@link #MEMCHECK_RECHECK_INTERVAL} or else it will
+         * always see an alarm scheduled within its time.
+         */
+        public static final String MEMCHECK_MIN_ALARM = "memcheck_min_alarm";
+
+        /**
+         * How frequently to check whether it is a good time to restart things,
+         * if the device is in a bad state.  This number is in seconds.  Note:
+         * this value must be larger than {@link #MEMCHECK_MIN_ALARM} or else
+         * the alarm to schedule the recheck will always appear within the
+         * minimum "do not execute now" time.
+         */
+        public static final String MEMCHECK_RECHECK_INTERVAL = "memcheck_recheck_interval";
+
+        /**
+         * How frequently (in DAYS) to reboot the device.  If 0, no reboots
+         * will occur.
+         */
+        public static final String REBOOT_INTERVAL = "reboot_interval";
+
+        /**
+         * First time during the day it is okay to force a reboot of the
+         * device (if REBOOT_INTERVAL is set).  This number is
+         * in seconds since midnight.
+         */
+        public static final String REBOOT_START_TIME = "reboot_start_time";
+
+        /**
+         * The window of time (in seconds) after each REBOOT_INTERVAL in which
+         * a reboot can be executed.  If 0, a reboot will always be executed at
+         * exactly the given time.  Otherwise, it will only be executed if
+         * the device is idle within the window.
+         */
+        public static final String REBOOT_WINDOW = "reboot_window";
+
+        /**
+         * The minimum version of the server that is required in order for the device to accept
+         * the server's recommendations about the initial sync settings to use. When this is unset,
+         * blank or can't be interpreted as an integer then we will not ask the server for a
+         * recommendation.
+         */
+        public static final String GMAIL_CONFIG_INFO_MIN_SERVER_VERSION =
+                "gmail_config_info_min_server_version";
+
+        /**
+         * Controls whether Gmail offers a preview button for images.
+         */
+        public static final String GMAIL_DISALLOW_IMAGE_PREVIEWS = "gmail_disallow_image_previews";
+
+        /**
+         * The timeout in milliseconds that Gmail uses when opening a connection and reading
+         * from it. A missing value or a value of -1 instructs Gmail to use the defaults provided
+         * by GoogleHttpClient.
+         */
+        public static final String GMAIL_TIMEOUT_MS = "gmail_timeout_ms";
+
+        /**
+         * Controls whether Gmail will request an expedited sync when a message is sent. Value must
+         * be an integer where non-zero means true. Defaults to 1.
+         */
+        public static final String GMAIL_SEND_IMMEDIATELY = "gmail_send_immediately";
+
+        /**
+         * Controls whether gmail buffers server responses.  Possible values are "memory", for a
+         * memory-based buffer, or "file", for a temp-file-based buffer.  All other values
+         * (including not set) disable buffering.
+         */
+        public static final String GMAIL_BUFFER_SERVER_RESPONSE = "gmail_buffer_server_response";
+
+        /**
+         * Hostname of the GTalk server.
+         */
+        public static final String GTALK_SERVICE_HOSTNAME = "gtalk_hostname";
+
+        /**
+         * Secure port of the GTalk server.
+         */
+        public static final String GTALK_SERVICE_SECURE_PORT = "gtalk_secure_port";
+
+        /**
+         * The server configurable RMQ acking interval
+         */
+        public static final String GTALK_SERVICE_RMQ_ACK_INTERVAL = "gtalk_rmq_ack_interval";
+
+        /**
+         * The minimum reconnect delay for short network outages or when the network is suspended
+         * due to phone use.
+         */
+        public static final String GTALK_SERVICE_MIN_RECONNECT_DELAY_SHORT =
+                "gtalk_min_reconnect_delay_short";
+
+        /**
+         * The reconnect variant range for short network outages or when the network is suspended
+         * due to phone use. A random number between 0 and this constant is computed and
+         * added to {@link #GTALK_SERVICE_MIN_RECONNECT_DELAY_SHORT} to form the initial reconnect
+         * delay.
+         */
+        public static final String GTALK_SERVICE_RECONNECT_VARIANT_SHORT =
+                "gtalk_reconnect_variant_short";
+
+        /**
+         * The minimum reconnect delay for long network outages
+         */
+        public static final String GTALK_SERVICE_MIN_RECONNECT_DELAY_LONG =
+                "gtalk_min_reconnect_delay_long";
+
+        /**
+         * The reconnect variant range for long network outages.  A random number between 0 and this
+         * constant is computed and added to {@link #GTALK_SERVICE_MIN_RECONNECT_DELAY_LONG} to
+         * form the initial reconnect delay.
+         */
+        public static final String GTALK_SERVICE_RECONNECT_VARIANT_LONG =
+                "gtalk_reconnect_variant_long";
+
+        /**
+         * The maximum reconnect delay time, in milliseconds.
+         */
+        public static final String GTALK_SERVICE_MAX_RECONNECT_DELAY =
+                "gtalk_max_reconnect_delay";
+
+        /**
+         * The network downtime that is considered "short" for the above calculations,
+         * in milliseconds.
+         */
+        public static final String GTALK_SERVICE_SHORT_NETWORK_DOWNTIME =
+                "gtalk_short_network_downtime";
+
+        /**
+         * How frequently we send heartbeat pings to the GTalk server. Receiving a server packet
+         * will reset the heartbeat timer. The away heartbeat should be used when the user is
+         * logged into the GTalk app, but not actively using it.
+         */
+        public static final String GTALK_SERVICE_AWAY_HEARTBEAT_INTERVAL_MS =
+                "gtalk_heartbeat_ping_interval_ms";  // keep the string backward compatible
+
+        /**
+         * How frequently we send heartbeat pings to the GTalk server. Receiving a server packet
+         * will reset the heartbeat timer. The active heartbeat should be used when the user is
+         * actively using the GTalk app.
+         */
+        public static final String GTALK_SERVICE_ACTIVE_HEARTBEAT_INTERVAL_MS =
+                "gtalk_active_heartbeat_ping_interval_ms";
+
+        /**
+         * How frequently we send heartbeat pings to the GTalk server. Receiving a server packet
+         * will reset the heartbeat timer. The sync heartbeat should be used when the user isn't
+         * logged into the GTalk app, but auto-sync is enabled.
+         */
+        public static final String GTALK_SERVICE_SYNC_HEARTBEAT_INTERVAL_MS =
+                "gtalk_sync_heartbeat_ping_interval_ms";
+
+        /**
+         * How frequently we send heartbeat pings to the GTalk server. Receiving a server packet
+         * will reset the heartbeat timer. The no sync heartbeat should be used when the user isn't
+         * logged into the GTalk app, and auto-sync is not enabled.
+         */
+        public static final String GTALK_SERVICE_NOSYNC_HEARTBEAT_INTERVAL_MS =
+                "gtalk_nosync_heartbeat_ping_interval_ms";
+
+        /**
+         * How long we wait to receive a heartbeat ping acknowledgement (or another packet)
+         * from the GTalk server, before deeming the connection dead.
+         */
+        public static final String GTALK_SERVICE_HEARTBEAT_ACK_TIMEOUT_MS =
+                "gtalk_heartbeat_ack_timeout_ms";
+
+        /**
+         * How long after screen is turned off before we consider the user to be idle.
+         */
+        public static final String GTALK_SERVICE_IDLE_TIMEOUT_MS =
+                "gtalk_idle_timeout_ms";
+
+        /**
+         * By default, GTalkService will always connect to the server regardless of the auto-sync
+         * setting. However, if this parameter is true, then GTalkService will only connect
+         * if auto-sync is enabled. Using the GTalk app will trigger the connection too.
+         */
+        public static final String GTALK_SERVICE_CONNECT_ON_AUTO_SYNC =
+                "gtalk_connect_on_auto_sync";
+
+        /**
+         * GTalkService holds a wakelock while broadcasting the intent for data message received.
+         * It then automatically release the wakelock after a timeout. This setting controls what
+         * the timeout should be.
+         */
+        public static final String GTALK_DATA_MESSAGE_WAKELOCK_MS =
+                "gtalk_data_message_wakelock_ms";
+
+        /**
+         * The socket read timeout used to control how long ssl handshake wait for reads before
+         * timing out. This is needed so the ssl handshake doesn't hang for a long time in some
+         * circumstances.
+         */
+        public static final String GTALK_SSL_HANDSHAKE_TIMEOUT_MS =
+                "gtalk_ssl_handshake_timeout_ms";
+
+        /**
+         * Enable use of ssl session caching.
+         * 'db' - save each session in a (per process) database
+         * 'file' - save each session in a (per process) file
+         * not set or any other value - normal java in-memory caching
+         */
+        public static final String SSL_SESSION_CACHE = "ssl_session_cache";
+
+        /**
+         * How many bytes long a message has to be, in order to be gzipped.
+         */
+        public static final String SYNC_MIN_GZIP_BYTES =
+                "sync_min_gzip_bytes";
+
+        /**
+         * The hash value of the current provisioning settings
+         */
+        public static final String PROVISIONING_DIGEST = "digest";
+
+        /**
+         * Provisioning keys to block from server update
+         */
+        public static final String PROVISIONING_OVERRIDE = "override";
+
+        /**
+         * "Generic" service name for  authentication requests.
+         */
+        public static final String GOOGLE_LOGIN_GENERIC_AUTH_SERVICE
+                = "google_login_generic_auth_service";
+
+        /**
+         * Frequency in milliseconds at which we should sync the locally installed Vending Machine
+         * content with the server.
+         */
+        public static final String VENDING_SYNC_FREQUENCY_MS = "vending_sync_frequency_ms";
+
+        /**
+         * Support URL that is opened in a browser when user clicks on 'Help and Info' in Vending
+         * Machine.
+         */
+        public static final String VENDING_SUPPORT_URL = "vending_support_url";
+
+        /**
+         * Indicates if Vending Machine requires a SIM to be in the phone to allow a purchase.
+         *
+         * true = SIM is required
+         * false = SIM is not required
+         */
+        public static final String VENDING_REQUIRE_SIM_FOR_PURCHASE =
+                "vending_require_sim_for_purchase";
+
+        /**
+         * The current version id of the Vending Machine terms of service.
+         */
+        public static final String VENDING_TOS_VERSION = "vending_tos_version";
+
+        /**
+         * URL that points to the terms of service for Vending Machine.
+         */
+        public static final String VENDING_TOS_URL = "vending_tos_url";
+
+        /**
+         * Whether to use sierraqa instead of sierra tokens for the purchase flow in
+         * Vending Machine.
+         *
+         * true = use sierraqa
+         * false = use sierra (default)
+         */
+        public static final String VENDING_USE_CHECKOUT_QA_SERVICE =
+                "vending_use_checkout_qa_service";
+
+        /**
+         * URL that points to the legal terms of service to display in Settings.
+         * <p>
+         * This should be a https URL. For a pretty user-friendly URL, use
+         * {@link #SETTINGS_TOS_PRETTY_URL}.
+         */
+        public static final String SETTINGS_TOS_URL = "settings_tos_url";
+
+        /**
+         * URL that points to the legal terms of service to display in Settings.
+         * <p>
+         * This should be a pretty http URL. For the URL the device will access
+         * via Settings, use {@link #SETTINGS_TOS_URL}.
+         */
+        public static final String SETTINGS_TOS_PRETTY_URL = "settings_tos_pretty_url";
+
+        /**
+         * URL that points to the contributors to display in Settings.
+         * <p>
+         * This should be a https URL. For a pretty user-friendly URL, use
+         * {@link #SETTINGS_CONTRIBUTORS_PRETTY_URL}.
+         */
+        public static final String SETTINGS_CONTRIBUTORS_URL = "settings_contributors_url";
+
+        /**
+         * URL that points to the contributors to display in Settings.
+         * <p>
+         * This should be a pretty http URL. For the URL the device will access
+         * via Settings, use {@link #SETTINGS_CONTRIBUTORS_URL}.
+         */
+        public static final String SETTINGS_CONTRIBUTORS_PRETTY_URL =
+                "settings_contributors_pretty_url";
+
+        /**
+         * URL that points to the Terms Of Service for the device.
+         * <p>
+         * This should be a pretty http URL.
+         */
+        public static final String SETUP_GOOGLE_TOS_URL = "setup_google_tos_url";
+
+        /**
+         * URL that points to the Android privacy policy for the device.
+         * <p>
+         * This should be a pretty http URL.
+         */
+        public static final String SETUP_ANDROID_PRIVACY_URL = "setup_android_privacy_url";
+
+        /**
+         * URL that points to the Google privacy policy for the device.
+         * <p>
+         * This should be a pretty http URL.
+         */
+        public static final String SETUP_GOOGLE_PRIVACY_URL = "setup_google_privacy_url";
+
+        /**
+         * Request an MSISDN token for various Google services.
+         */
+        public static final String USE_MSISDN_TOKEN = "use_msisdn_token";
+
+        /**
+         * RSA public key used to encrypt passwords stored in the database.
+         */
+        public static final String GLS_PUBLIC_KEY = "google_login_public_key";
+
+        /**
+         * Only check parental control status if this is set to "true".
+         */
+        public static final String PARENTAL_CONTROL_CHECK_ENABLED =
+                "parental_control_check_enabled";
+
+        /**
+         * The list of applications we need to block if parental control is
+         * enabled.
+         */
+        public static final String PARENTAL_CONTROL_APPS_LIST =
+                "parental_control_apps_list";
+
+        /**
+         * Duration in which parental control status is valid.
+         */
+        public static final String PARENTAL_CONTROL_TIMEOUT_IN_MS =
+                "parental_control_timeout_in_ms";
+
+        /**
+         * When parental control is off, we expect to get this string from the
+         * litmus url.
+         */
+        public static final String PARENTAL_CONTROL_EXPECTED_RESPONSE =
+                "parental_control_expected_response";
+
+        /**
+         * When the litmus url returns a 302, declare parental control to be on
+         * only if the redirect url matches this regular expression.
+         */
+        public static final String PARENTAL_CONTROL_REDIRECT_REGEX =
+                "parental_control_redirect_regex";
+
+        /**
+         * Threshold for the amount of change in disk free space required to report the amount of
+         * free space. Used to prevent spamming the logs when the disk free space isn't changing
+         * frequently.
+         */
+        public static final String DISK_FREE_CHANGE_REPORTING_THRESHOLD =
+                "disk_free_change_reporting_threshold";
+
+        /**
+         * Prefix for new Google services published by the checkin
+         * server.
+         */
+        public static final String GOOGLE_SERVICES_PREFIX
+                = "google_services:";
+
+        /**
+         * The maximum reconnect delay for short network outages or when the network is suspended
+         * due to phone use.
+         */
+        public static final String SYNC_MAX_RETRY_DELAY_IN_SECONDS =
+                "sync_max_retry_delay_in_seconds";
+
+        /**
+         * Minimum percentage of free storage on the device that is used to determine if
+         * the device is running low on storage.
+         * Say this value is set to 10, the device is considered running low on storage
+         * if 90% or more of the device storage is filled up.
+         */
+        public static final String SYS_STORAGE_THRESHOLD_PERCENTAGE =
+                "sys_storage_threshold_percentage";
+
+        /**
+         * The interval in minutes after which the amount of free storage left on the
+         * device is logged to the event log
+         */
+        public static final String SYS_FREE_STORAGE_LOG_INTERVAL =
+                "sys_free_storage_log_interval";
+
+        /**
+         * The interval in milliseconds at which to check the number of SMS sent
+         * out without asking for use permit, to limit the un-authorized SMS
+         * usage.
+         */
+        public static final String SMS_OUTGOING_CEHCK_INTERVAL_MS =
+                "sms_outgoing_check_interval_ms";
+
+        /**
+         * The number of outgoing SMS sent without asking for user permit
+         * (of {@link #SMS_OUTGOING_CEHCK_INTERVAL_MS}
+         */
+        public static final String SMS_OUTGOING_CEHCK_MAX_COUNT =
+                "sms_outgoing_check_max_count";
+
+        /**
+         * The interval in milliseconds at which to check packet counts on the
+         * mobile data interface when screen is on, to detect possible data
+         * connection problems.
+         */
+        public static final String PDP_WATCHDOG_POLL_INTERVAL_MS =
+                "pdp_watchdog_poll_interval_ms";
+
+        /**
+         * The interval in milliseconds at which to check packet counts on the
+         * mobile data interface when screen is off, to detect possible data
+         * connection problems.
+         */
+        public static final String PDP_WATCHDOG_LONG_POLL_INTERVAL_MS =
+                "pdp_watchdog_long_poll_interval_ms";
+
+        /**
+         * The interval in milliseconds at which to check packet counts on the
+         * mobile data interface after {@link #PDP_WATCHDOG_TRIGGER_PACKET_COUNT}
+         * outgoing packets has been reached without incoming packets.
+         */
+        public static final String PDP_WATCHDOG_ERROR_POLL_INTERVAL_MS =
+                "pdp_watchdog_error_poll_interval_ms";
+
+        /**
+         * The number of outgoing packets sent without seeing an incoming packet
+         * that triggers a countdown (of {@link #PDP_WATCHDOG_ERROR_POLL_COUNT}
+         * device is logged to the event log
+         */
+        public static final String PDP_WATCHDOG_TRIGGER_PACKET_COUNT =
+                "pdp_watchdog_trigger_packet_count";
+
+        /**
+         * The number of polls to perform (at {@link #PDP_WATCHDOG_ERROR_POLL_INTERVAL_MS})
+         * after hitting {@link #PDP_WATCHDOG_TRIGGER_PACKET_COUNT} before
+         * attempting data connection recovery.
+         */
+        public static final String PDP_WATCHDOG_ERROR_POLL_COUNT =
+                "pdp_watchdog_error_poll_count";
+
+        /**
+         * The number of failed PDP reset attempts before moving to something more
+         * drastic: re-registering to the network.
+         */
+        public static final String PDP_WATCHDOG_MAX_PDP_RESET_FAIL_COUNT =
+                "pdp_watchdog_max_pdp_reset_fail_count";
+
+        /**
+         * Address to ping as a last sanity check before attempting any recovery.
+         * Unset or set to "0.0.0.0" to skip this check.
+         */
+        public static final String PDP_WATCHDOG_PING_ADDRESS = "pdp_watchdog_ping_address";
+
+        /**
+         * The "-w deadline" parameter for the ping, ie, the max time in
+         * seconds to spend pinging.
+         */
+        public static final String PDP_WATCHDOG_PING_DEADLINE = "pdp_watchdog_ping_deadline";
+
+        /**
+         * The interval in milliseconds at which to check gprs registration
+         * after the first registration mismatch of gprs and voice service,
+         * to detect possible data network registration problems.
+         *
+         */
+        public static final String GPRS_REGISTER_CHECK_PERIOD_MS =
+                "gprs_register_check_period_ms";
+
+        /**
+         * The interval in milliseconds after which Wi-Fi is considered idle.
+         * When idle, it is possible for the device to be switched from Wi-Fi to
+         * the mobile data network.
+         */
+        public static final String WIFI_IDLE_MS = "wifi_idle_ms";
+
+        /**
+         * Screen timeout in milliseconds corresponding to the
+         * PowerManager's POKE_LOCK_SHORT_TIMEOUT flag (i.e. the fastest
+         * possible screen timeout behavior.)
+         */
+        public static final String SHORT_KEYLIGHT_DELAY_MS =
+                "short_keylight_delay_ms";
+
+        /**
+         * URL that points to the voice search servers. To be factored out of this class.
+         */
+        public static final String VOICE_SEARCH_URL = "voice_search_url";
+
+        /**
+         * Speech encoding used with voice search on 3G networks. To be factored out of this class.
+         */
+        public static final String VOICE_SEARCH_ENCODING_THREE_G = "voice_search_encoding_three_g";
+
+        /**
+         * Speech encoding used with voice search on WIFI networks. To be factored out of this class.
+         */
+        public static final String VOICE_SEARCH_ENCODING_WIFI = "voice_search_encoding_wifi";
+
+        /**
+         * Whether to use automatic gain control in voice search (0 = disable, 1 = enable).
+         * To be factored out of this class.
+         */
+        public static final String VOICE_SEARCH_ENABLE_AGC = "voice_search_enable_agc";
+
+        /**
+         * Whether to use noise suppression in voice search (0 = disable, 1 = enable).
+         * To be factored out of this class.
+         */
+        public static final String VOICE_SEARCH_ENABLE_NS = "voice_search_enable_ns";
+
+        /**
+         * Whether to use the IIR filter in voice search (0 = disable, 1 = enable).
+         * To be factored out of this class.
+         */
+        public static final String VOICE_SEARCH_ENABLE_IIR = "voice_search_enable_iir";
+
+        /**
+         * List of test suites (local disk filename) for the automatic instrumentation test runner.
+         * The file format is similar to automated_suites.xml, see AutoTesterService.
+         * If this setting is missing or empty, the automatic test runner will not start.
+         */
+        public static final String AUTOTEST_SUITES_FILE = "autotest_suites_file";
+
+        /**
+         * Interval between synchronous checkins forced by the automatic test runner.
+         * If you set this to a value smaller than CHECKIN_INTERVAL, then the test runner's
+         * frequent checkins will prevent asynchronous background checkins from interfering
+         * with any performance measurements.
+         */
+        public static final String AUTOTEST_CHECKIN_SECONDS = "autotest_checkin_seconds";
+
+        /**
+         * Interval between reboots forced by the automatic test runner.
+         */
+        public static final String AUTOTEST_REBOOT_SECONDS = "autotest_reboot_seconds";
+
+
+        /**
+         * Threshold values for the duration and level of a discharge cycle, under
+         * which we log discharge cycle info.
+         */
+        public static final String BATTERY_DISCHARGE_DURATION_THRESHOLD =
+                "battery_discharge_duration_threshold";
+        public static final String BATTERY_DISCHARGE_THRESHOLD = "battery_discharge_threshold";
+
+        /**
+         * @deprecated
+         * @hide
+         */
+        @Deprecated  // Obviated by NameValueCache: just fetch the value directly.
+        public static class QueryMap extends ContentQueryMap {
+
+            public QueryMap(ContentResolver contentResolver, Cursor cursor, boolean keepUpdated,
+                    Handler handlerForUpdateNotifications) {
+                super(cursor, NAME, keepUpdated, handlerForUpdateNotifications);
+            }
+
+            public QueryMap(ContentResolver contentResolver, boolean keepUpdated,
+                    Handler handlerForUpdateNotifications) {
+                this(contentResolver,
+                        contentResolver.query(CONTENT_URI, null, null, null, null),
+                        keepUpdated, handlerForUpdateNotifications);
+            }
+
+            public String getString(String name) {
+                ContentValues cv = getValues(name);
+                if (cv == null) return null;
+                return cv.getAsString(VALUE);
+            }
+        }
+
+    }
+
+    /**
+     * User-defined bookmarks and shortcuts.  The target of each bookmark is an
+     * Intent URL, allowing it to be either a web page or a particular
+     * application activity.
+     *
+     * @hide
+     */
+    public static final class Bookmarks implements BaseColumns
+    {
+        private static final String TAG = "Bookmarks";
+
+        /**
+         * The content:// style URL for this table
+         */
+        public static final Uri CONTENT_URI =
+            Uri.parse("content://" + AUTHORITY + "/bookmarks");
+
+        /**
+         * The row ID.
+         * <p>Type: INTEGER</p>
+         */
+        public static final String ID = "_id";
+
+        /**
+         * Descriptive name of the bookmark that can be displayed to the user.
+         * If this is empty, the title should be resolved at display time (use
+         * {@link #getTitle(Context, Cursor)} any time you want to display the
+         * title of a bookmark.)
+         * <P>
+         * Type: TEXT
+         * </P>
+         */
+        public static final String TITLE = "title";
+
+        /**
+         * Arbitrary string (displayed to the user) that allows bookmarks to be
+         * organized into categories.  There are some special names for
+         * standard folders, which all start with '@'.  The label displayed for
+         * the folder changes with the locale (via {@link #getLabelForFolder}) but
+         * the folder name does not change so you can consistently query for
+         * the folder regardless of the current locale.
+         *
+         * <P>Type: TEXT</P>
+         *
+         */
+        public static final String FOLDER = "folder";
+
+        /**
+         * The Intent URL of the bookmark, describing what it points to.  This
+         * value is given to {@link android.content.Intent#getIntent} to create
+         * an Intent that can be launched.
+         * <P>Type: TEXT</P>
+         */
+        public static final String INTENT = "intent";
+
+        /**
+         * Optional shortcut character associated with this bookmark.
+         * <P>Type: INTEGER</P>
+         */
+        public static final String SHORTCUT = "shortcut";
+
+        /**
+         * The order in which the bookmark should be displayed
+         * <P>Type: INTEGER</P>
+         */
+        public static final String ORDERING = "ordering";
+
+        private static final String[] sIntentProjection = { INTENT };
+        private static final String[] sShortcutProjection = { ID, SHORTCUT };
+        private static final String sShortcutSelection = SHORTCUT + "=?";
+
+        /**
+         * Convenience function to retrieve the bookmarked Intent for a
+         * particular shortcut key.
+         *
+         * @param cr The ContentResolver to query.
+         * @param shortcut The shortcut key.
+         *
+         * @return Intent The bookmarked URL, or null if there is no bookmark
+         *         matching the given shortcut.
+         */
+        public static Intent getIntentForShortcut(ContentResolver cr, char shortcut)
+        {
+            Intent intent = null;
+
+            Cursor c = cr.query(CONTENT_URI,
+                    sIntentProjection, sShortcutSelection,
+                    new String[] { String.valueOf((int) shortcut) }, ORDERING);
+            // Keep trying until we find a valid shortcut
+            try {
+                while (intent == null && c.moveToNext()) {
+                    try {
+                        String intentURI = c.getString(c.getColumnIndexOrThrow(INTENT));
+                        intent = Intent.getIntent(intentURI);
+                    } catch (java.net.URISyntaxException e) {
+                        // The stored URL is bad...  ignore it.
+                    } catch (IllegalArgumentException e) {
+                        // Column not found
+                        Log.e(TAG, "Intent column not found", e);
+                    }
+                }
+            } finally {
+                if (c != null) c.close();
+            }
+
+            return intent;
+        }
+
+        /**
+         * Add a new bookmark to the system.
+         * 
+         * @param cr The ContentResolver to query.
+         * @param intent The desired target of the bookmark.
+         * @param title Bookmark title that is shown to the user; null if none
+         *            or it should be resolved to the intent's title.
+         * @param folder Folder in which to place the bookmark; null if none.
+         * @param shortcut Shortcut that will invoke the bookmark; 0 if none. If
+         *            this is non-zero and there is an existing bookmark entry
+         *            with this same shortcut, then that existing shortcut is
+         *            cleared (the bookmark is not removed).
+         * @return The unique content URL for the new bookmark entry.
+         */
+        public static Uri add(ContentResolver cr,
+                                           Intent intent,
+                                           String title,
+                                           String folder,
+                                           char shortcut,
+                                           int ordering)
+        {
+            // If a shortcut is supplied, and it is already defined for
+            // another bookmark, then remove the old definition.
+            if (shortcut != 0) {
+                Cursor c = cr.query(CONTENT_URI,
+                        sShortcutProjection, sShortcutSelection,
+                        new String[] { String.valueOf((int) shortcut) }, null);
+                try {
+                    if (c.moveToFirst()) {
+                        while (c.getCount() > 0) {
+                            if (!c.deleteRow()) {
+                                Log.w(TAG, "Could not delete existing shortcut row");
+                            }
+                        }
+                    }
+                } finally {
+                    if (c != null) c.close();
+                }
+            }
+
+            ContentValues values = new ContentValues();
+            if (title != null) values.put(TITLE, title);
+            if (folder != null) values.put(FOLDER, folder);
+            values.put(INTENT, intent.toURI());
+            if (shortcut != 0) values.put(SHORTCUT, (int) shortcut);
+            values.put(ORDERING, ordering);
+            return cr.insert(CONTENT_URI, values);
+        }
+
+        /**
+         * Return the folder name as it should be displayed to the user.  This
+         * takes care of localizing special folders.
+         *
+         * @param r Resources object for current locale; only need access to
+         *          system resources.
+         * @param folder The value found in the {@link #FOLDER} column.
+         *
+         * @return CharSequence The label for this folder that should be shown
+         *         to the user.
+         */
+        public static CharSequence getLabelForFolder(Resources r, String folder) {
+            return folder;
+        }
+
+        /**
+         * Return the title as it should be displayed to the user. This takes
+         * care of localizing bookmarks that point to activities.
+         * 
+         * @param context A context.
+         * @param cursor A cursor pointing to the row whose title should be
+         *        returned. The cursor must contain at least the {@link #TITLE}
+         *        and {@link #INTENT} columns.
+         * @return A title that is localized and can be displayed to the user,
+         *         or the empty string if one could not be found.
+         */
+        public static CharSequence getTitle(Context context, Cursor cursor) {
+            int titleColumn = cursor.getColumnIndex(TITLE);
+            int intentColumn = cursor.getColumnIndex(INTENT);
+            if (titleColumn == -1 || intentColumn == -1) {
+                throw new IllegalArgumentException(
+                        "The cursor must contain the TITLE and INTENT columns.");
+            }
+            
+            String title = cursor.getString(titleColumn);
+            if (!TextUtils.isEmpty(title)) {
+                return title;
+            }
+            
+            String intentUri = cursor.getString(intentColumn);
+            if (TextUtils.isEmpty(intentUri)) {
+                return "";
+            }
+            
+            Intent intent;
+            try {
+                intent = Intent.getIntent(intentUri);
+            } catch (URISyntaxException e) {
+                return "";
+            }
+            
+            PackageManager packageManager = context.getPackageManager();
+            ResolveInfo info = packageManager.resolveActivity(intent, 0);
+            return info != null ? info.loadLabel(packageManager) : "";
+        }
+    }
+
+    /**
+     * Returns the GTalk JID resource associated with this device.
+     *
+     * @return  String  the JID resource of the device. It uses the device IMEI in the computation
+     * of the JID resource. If IMEI is not ready (i.e. telephony module not ready), we'll return
+     * an empty string.
+     * @hide
+     */
+    // TODO: we shouldn't not have a permenant Jid resource, as that's an easy target for
+    // spams. We should change it once a while, like when we resubscribe to the subscription feeds
+    // server.
+    // (also, should this live in GTalkService?)
+    public static synchronized String getJidResource() {
+        if (sJidResource != null) {
+            return sJidResource;
+        }
+
+        MessageDigest digest;
+        try {
+            digest = MessageDigest.getInstance("SHA-1");
+        } catch (NoSuchAlgorithmException e) {
+            throw new RuntimeException("this should never happen");
+        }
+
+        String imei = TelephonyManager.getDefault().getDeviceId();
+        if (TextUtils.isEmpty(imei)) {
+            return "";
+        }
+
+        byte[] hashedImei = digest.digest(imei.getBytes());
+        String id = new String(Base64.encodeBase64(hashedImei), 0, 12);
+        id = id.replaceAll("/", "_");
+        sJidResource = JID_RESOURCE_PREFIX + id;
+        return sJidResource;
+    }
+
+    /**
+     * Returns the device ID that we should use when connecting to the mobile gtalk server.
+     * This is a string like "android-0x1242", where the hex string is the Android ID obtained
+     * from the GoogleLoginService.
+     *
+     * @param androidId The Android ID for this device.
+     * @return The device ID that should be used when connecting to the mobile gtalk server.
+     * @hide
+     */
+    public static String getGTalkDeviceId(long androidId) {
+        return "android-" + Long.toHexString(androidId);
+    }
+}
diff --git a/core/java/android/provider/SubscribedFeeds.java b/core/java/android/provider/SubscribedFeeds.java
new file mode 100644
index 0000000..4d430d5
--- /dev/null
+++ b/core/java/android/provider/SubscribedFeeds.java
@@ -0,0 +1,204 @@
+/*
+ * 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 android.provider;
+
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.database.Cursor;
+import android.net.Uri;
+
+/**
+ * The SubscribedFeeds provider stores all information about subscribed feeds.
+ * 
+ * @hide
+ */
+public class SubscribedFeeds {
+    private SubscribedFeeds() {}
+    
+    /**
+     * Columns from the Feed table that other tables join into themselves.
+     */
+    public interface FeedColumns {
+        /**
+         * The feed url.
+         * <P>Type: TEXT</P>
+         */
+        public static final String FEED = "feed";
+
+        /**
+         * The authority that cares about the feed.
+         * <P>Type: TEXT</P>
+         */
+        public static final String AUTHORITY = "authority";
+
+        /**
+         * The gaia service this feed is for (used for authentication).
+         * <P>Type: TEXT</P>
+         */
+        public static final String SERVICE = "service";
+    }
+
+    /**
+     * Provides constants to access the Feeds table and some utility methods
+     * to ease using the Feeds content provider.
+     */
+    public static final class Feeds implements BaseColumns, SyncConstValue,
+            FeedColumns {
+        private Feeds() {}
+        
+        public static Cursor query(ContentResolver cr, String[] projection) {
+            return cr.query(CONTENT_URI, projection, null, null, DEFAULT_SORT_ORDER);
+        }
+
+        public static Cursor query(ContentResolver cr, String[] projection,
+                String where, String[] whereArgs, String orderBy) {
+            return cr.query(CONTENT_URI, projection, where,
+                    whereArgs, (orderBy == null) ? DEFAULT_SORT_ORDER : orderBy);
+        }
+
+        /**
+         * The content:// style URL for this table
+         */
+        public static final Uri CONTENT_URI =
+            Uri.parse("content://subscribedfeeds/feeds");
+
+        /**
+         * The content:// style URL for this table
+         */
+        public static final Uri DELETED_CONTENT_URI =
+            Uri.parse("content://subscribedfeeds/deleted_feeds");
+
+        /**
+         * The MIME type of {@link #CONTENT_URI} providing a directory of
+         * subscribed feeds.
+         */
+        public static final String CONTENT_TYPE =
+                "vnd.android.cursor.dir/subscribedfeeds";
+
+        /**
+         * The MIME type of a {@link #CONTENT_URI} subdirectory of a single
+         * subscribed feed.
+         */
+        public static final String CONTENT_ITEM_TYPE =
+                "vnd.android.cursor.item/subscribedfeed";
+
+        /**
+         * The default sort order for this table
+         */
+        public static final String DEFAULT_SORT_ORDER = "_SYNC_ACCOUNT ASC";
+    }
+
+    /**
+     * A convenience method to add a feed to the SubscribedFeeds
+     * content provider. The user specifies the values of the FEED,
+     * _SYNC_ACCOUNT, AUTHORITY. SERVICE, and ROUTING_INFO.
+     * @param resolver          used to access the underlying content provider
+     * @param feed              corresponds to the FEED column
+     * @param account           corresponds to the _SYNC_ACCOUNT column
+     * @param authority         corresponds to the AUTHORITY column
+     * @param service           corresponds to the SERVICE column
+     * @return  the Uri of the feed that was added
+     */
+    public static Uri addFeed(ContentResolver resolver,
+            String feed, String account,
+            String authority, String service) {
+        ContentValues values = new ContentValues();
+        values.put(SubscribedFeeds.Feeds.FEED, feed);
+        values.put(SubscribedFeeds.Feeds._SYNC_ACCOUNT, account);
+        values.put(SubscribedFeeds.Feeds.AUTHORITY, authority);
+        values.put(SubscribedFeeds.Feeds.SERVICE, service);
+        return resolver.insert(SubscribedFeeds.Feeds.CONTENT_URI, values);
+    }
+
+    public static int deleteFeed(ContentResolver resolver,
+            String feed, String account, String authority) {
+        StringBuilder where = new StringBuilder();
+        where.append(SubscribedFeeds.Feeds._SYNC_ACCOUNT + "=?");
+        where.append(" AND " + SubscribedFeeds.Feeds.FEED + "=?");
+        where.append(" AND " + SubscribedFeeds.Feeds.AUTHORITY + "=?");
+        return resolver.delete(SubscribedFeeds.Feeds.CONTENT_URI,
+                where.toString(), new String[] {account, feed, authority});
+    }
+
+    public static int deleteFeeds(ContentResolver resolver,
+            String account, String authority) {
+        StringBuilder where = new StringBuilder();
+        where.append(SubscribedFeeds.Feeds._SYNC_ACCOUNT + "=?");
+        where.append(" AND " + SubscribedFeeds.Feeds.AUTHORITY + "=?");
+        return resolver.delete(SubscribedFeeds.Feeds.CONTENT_URI,
+                where.toString(), new String[] {account, authority});
+    }
+
+    public static String gtalkServiceRoutingInfoFromAccountAndResource(
+            String account, String res) {
+        return Uri.parse("gtalk://" + account + "/" + res).toString();
+    }
+
+    /**
+     * Columns from the Accounts table.
+     */
+    public interface AccountColumns {
+        /**
+         * The account.
+         * <P>Type: TEXT</P>
+         */
+        public static final String _SYNC_ACCOUNT = SyncConstValue._SYNC_ACCOUNT;
+    }
+
+    /**
+     * Provides constants to access the Accounts table and some utility methods
+     * to ease using it.
+     */
+    public static final class Accounts implements BaseColumns, AccountColumns {
+        private Accounts() {}
+
+        public static Cursor query(ContentResolver cr, String[] projection) {
+            return cr.query(CONTENT_URI, projection, null, null, DEFAULT_SORT_ORDER);
+        }
+
+        public static Cursor query(ContentResolver cr, String[] projection,
+                String where, String orderBy) {
+            return cr.query(CONTENT_URI, projection, where,
+                    null, (orderBy == null) ? DEFAULT_SORT_ORDER : orderBy);
+        }
+
+        /**
+         * The content:// style URL for this table
+         */
+        public static final Uri CONTENT_URI =
+            Uri.parse("content://subscribedfeeds/accounts");
+
+        /**
+         * The MIME type of {@link #CONTENT_URI} providing a directory of
+         * accounts that have subscribed feeds.
+         */
+        public static final String CONTENT_TYPE =
+                "vnd.android.cursor.dir/subscribedfeedaccounts";
+
+        /**
+         * The MIME type of a {@link #CONTENT_URI} subdirectory of a single
+         * account in the subscribed feeds.
+         */
+        public static final String CONTENT_ITEM_TYPE =
+                "vnd.android.cursor.item/subscribedfeedaccount";
+
+        /**
+         * The default sort order for this table
+         */
+        public static final String DEFAULT_SORT_ORDER = "_SYNC_ACCOUNT ASC";
+    }
+}
diff --git a/core/java/android/provider/Sync.java b/core/java/android/provider/Sync.java
new file mode 100644
index 0000000..628852f
--- /dev/null
+++ b/core/java/android/provider/Sync.java
@@ -0,0 +1,633 @@
+/*
+ * Copyright (C) 2007 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 android.provider;
+
+import android.content.ContentQueryMap;
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Handler;
+
+import java.util.Map;
+
+/**
+ * The Sync provider stores information used in managing the syncing of the device,
+ * including the history and pending syncs.
+ * 
+ * @hide
+ */
+public final class Sync {
+    // utility class
+    private Sync() {}
+
+    /**
+     * The content url for this provider.
+     */
+    public static final Uri CONTENT_URI = Uri.parse("content://sync");
+
+    /**
+     * Columns from the stats table.
+     */
+    public interface StatsColumns {
+        /**
+         * The sync account.
+         * <P>Type: TEXT</P>
+         */
+        public static final String ACCOUNT = "account";
+
+        /**
+         * The content authority (contacts, calendar, etc.).
+         * <P>Type: TEXT</P>
+         */
+        public static final String AUTHORITY = "authority";
+    }
+
+    /**
+     * Provides constants and utility methods to access and use the stats table.
+     */
+    public static final class Stats implements BaseColumns, StatsColumns {
+
+        // utility class
+        private Stats() {}
+
+        /**
+         * The content url for this table.
+         */
+        public static final Uri CONTENT_URI =
+                Uri.parse("content://sync/stats");
+
+        /** Projection for the _id column in the stats table. */
+        public static final String[] SYNC_STATS_PROJECTION = {_ID};
+    }
+
+    /**
+     * Columns from the history table.
+     */
+    public interface HistoryColumns {
+        /**
+         * The ID of the stats row corresponding to this event.
+         * <P>Type: INTEGER</P>
+         */
+        public static final String STATS_ID = "stats_id";
+
+        /**
+         * The source of the sync event (LOCAL, POLL, USER, SERVER).
+         * <P>Type: INTEGER</P>
+         */
+        public static final String SOURCE = "source";
+
+        /**
+         * The type of sync event (START, STOP).
+         * <P>Type: INTEGER</P>
+         */
+        public static final String EVENT = "event";
+
+        /**
+         * The time of the event.
+         * <P>Type: INTEGER</P>
+         */
+        public static final String EVENT_TIME = "eventTime";
+
+        /**
+         * How long this event took. This is only valid if the EVENT is EVENT_STOP.
+         * <P>Type: INTEGER</P>
+         */
+        public static final String ELAPSED_TIME = "elapsedTime";
+
+        /**
+         * Any additional message associated with this event.
+         * <P>Type: TEXT</P>
+         */
+        public static final String MESG = "mesg";
+
+        /**
+         * How much activity was performed sending data to the server. This is sync adapter
+         * specific, but usually is something like how many record update/insert/delete attempts
+         * were carried out. This is only valid if the EVENT is EVENT_STOP.
+         * <P>Type: INTEGER</P>
+         */
+        public static final String UPSTREAM_ACTIVITY = "upstreamActivity";
+
+        /**
+         * How much activity was performed while receiving data from the server.
+         * This is sync adapter specific, but usually is something like how many
+         * records were received from the server. This is only valid if the
+         * EVENT is EVENT_STOP.
+         * <P>Type: INTEGER</P>
+         */
+        public static final String DOWNSTREAM_ACTIVITY = "downstreamActivity";
+    }
+
+    /**
+     * Columns from the history table.
+     */
+    public interface StatusColumns {
+        /**
+         * How many syncs were completed for this account and authority.
+         * <P>Type: INTEGER</P>
+         */
+        public static final String NUM_SYNCS = "numSyncs";
+
+        /**
+         * How long all the events for this account and authority took.
+         * <P>Type: INTEGER</P>
+         */
+        public static final String TOTAL_ELAPSED_TIME = "totalElapsedTime";
+
+        /**
+         * The number of syncs with SOURCE_POLL.
+         * <P>Type: INTEGER</P>
+         */
+        public static final String NUM_SOURCE_POLL = "numSourcePoll";
+
+        /**
+         * The number of syncs with SOURCE_SERVER.
+         * <P>Type: INTEGER</P>
+         */
+        public static final String NUM_SOURCE_SERVER = "numSourceServer";
+
+        /**
+         * The number of syncs with SOURCE_LOCAL.
+         * <P>Type: INTEGER</P>
+         */
+        public static final String NUM_SOURCE_LOCAL = "numSourceLocal";
+
+        /**
+         * The number of syncs with SOURCE_USER.
+         * <P>Type: INTEGER</P>
+         */
+        public static final String NUM_SOURCE_USER = "numSourceUser";
+
+        /**
+         * The time in ms that the last successful sync ended. Will be null if
+         * there are no successful syncs. A successful sync is defined as one having
+         * MESG=MESG_SUCCESS.
+         * <P>Type: INTEGER</P>
+         */
+        public static final String LAST_SUCCESS_TIME = "lastSuccessTime";
+
+        /**
+         * The SOURCE of the last successful sync. Will be null if
+         * there are no successful syncs. A successful sync is defined
+         * as one having MESG=MESG_SUCCESS.
+         * <P>Type: INTEGER</P>
+         */
+        public static final String LAST_SUCCESS_SOURCE = "lastSuccessSource";
+
+        /**
+         * The end time in ms of the last sync that failed since the last successful sync.
+         * Will be null if there are no syncs or if the last one succeeded. A failed
+         * sync is defined as one where MESG isn't MESG_SUCCESS or MESG_CANCELED.
+         * <P>Type: INTEGER</P>
+         */
+        public static final String LAST_FAILURE_TIME = "lastFailureTime";
+
+        /**
+         * The SOURCE of the last sync that failed since the last successful sync.
+         * Will be null if there are no syncs or if the last one succeeded. A failed
+         * sync is defined as one where MESG isn't MESG_SUCCESS or MESG_CANCELED.
+         * <P>Type: INTEGER</P>
+         */
+        public static final String LAST_FAILURE_SOURCE = "lastFailureSource";
+
+        /**
+         * The MESG of the last sync that failed since the last successful sync.
+         * Will be null if there are no syncs or if the last one succeeded. A failed
+         * sync is defined as one where MESG isn't MESG_SUCCESS or MESG_CANCELED.
+         * <P>Type: STRING</P>
+         */
+        public static final String LAST_FAILURE_MESG = "lastFailureMesg";
+
+        /**
+         * Is set to 1 if a sync is pending, 0 if not.
+         * <P>Type: INTEGER</P>
+         */
+        public static final String PENDING = "pending";
+    }
+
+    /**
+     * Provides constants and utility methods to access and use the history
+     * table.
+     */
+    public static class History implements BaseColumns,
+                                                 StatsColumns,
+                                                 HistoryColumns {
+
+        /**
+         * The content url for this table.
+         */
+        public static final Uri CONTENT_URI =
+                Uri.parse("content://sync/history");
+
+        /** Enum value for a sync start event. */
+        public static final int EVENT_START = 0;
+
+        /** Enum value for a sync stop event. */
+        public static final int EVENT_STOP = 1;
+
+        // TODO: i18n -- grab these out of resources.
+        /** String names for the sync event types. */
+        public static final String[] EVENTS = { "START", "STOP" };
+
+        /** Enum value for a server-initiated sync. */
+        public static final int SOURCE_SERVER = 0;
+
+        /** Enum value for a local-initiated sync. */
+        public static final int SOURCE_LOCAL = 1;
+        /**
+         * Enum value for a poll-based sync (e.g., upon connection to
+         * network)
+         */
+        public static final int SOURCE_POLL = 2;
+
+        /** Enum value for a user-initiated sync. */
+        public static final int SOURCE_USER = 3;
+
+        // TODO: i18n -- grab these out of resources.
+        /** String names for the sync source types. */
+        public static final String[] SOURCES = { "SERVER",
+                                                 "LOCAL",
+                                                 "POLL",
+                                                 "USER" };
+
+        // Error types
+        public static final int ERROR_SYNC_ALREADY_IN_PROGRESS = 1;
+        public static final int ERROR_AUTHENTICATION = 2;
+        public static final int ERROR_IO = 3;
+        public static final int ERROR_PARSE = 4;
+        public static final int ERROR_CONFLICT = 5;
+        public static final int ERROR_TOO_MANY_DELETIONS = 6;
+        public static final int ERROR_TOO_MANY_RETRIES = 7;
+        public static final int ERROR_INTERNAL = 8;
+
+        // The MESG column will contain one of these or one of the Error types.
+        public static final String MESG_SUCCESS = "success";
+        public static final String MESG_CANCELED = "canceled";
+
+        private static final String FINISHED_SINCE_WHERE_CLAUSE = EVENT + "=" + EVENT_STOP
+                + " AND " + EVENT_TIME + ">? AND " + ACCOUNT + "=? AND " + AUTHORITY + "=?";
+
+        public static String mesgToString(String mesg) {
+            if (MESG_SUCCESS.equals(mesg)) return mesg;
+            if (MESG_CANCELED.equals(mesg)) return mesg;
+            switch (Integer.parseInt(mesg)) {
+                case ERROR_SYNC_ALREADY_IN_PROGRESS: return "already in progress";
+                case ERROR_AUTHENTICATION: return "bad authentication";
+                case ERROR_IO: return "network error";
+                case ERROR_PARSE: return "parse error";
+                case ERROR_CONFLICT: return "conflict detected";
+                case ERROR_TOO_MANY_DELETIONS: return "too many deletions";
+                case ERROR_TOO_MANY_RETRIES: return "too many retries";
+                case ERROR_INTERNAL: return "internal error";
+                default: return "unknown error";
+            }
+        }
+
+        // utility class
+        private History() {}
+
+        /**
+         * returns a cursor that queries the sync history in descending event time order
+         * @param contentResolver the ContentResolver to use for the query
+         * @return the cursor on the History table
+         */
+        public static Cursor query(ContentResolver contentResolver) {
+            return contentResolver.query(CONTENT_URI, null, null, null, EVENT_TIME + " desc");
+        }
+
+        public static boolean hasNewerSyncFinished(ContentResolver contentResolver,
+                String account, String authority, long when) {
+            Cursor c = contentResolver.query(CONTENT_URI, new String[]{_ID},
+                    FINISHED_SINCE_WHERE_CLAUSE,
+                    new String[]{Long.toString(when), account, authority}, null);
+            try {
+              return c.getCount() > 0;
+            } finally {
+                c.close();
+            }
+        }
+    }
+
+    /**
+     * Provides constants and utility methods to access and use the authority history
+     * table, which contains information about syncs aggregated by account and authority.
+     * All the HistoryColumns except for EVENT are present, plus the AuthorityHistoryColumns.
+     */
+    public static class Status extends History implements StatusColumns {
+
+        /**
+         * The content url for this table.
+         */
+        public static final Uri CONTENT_URI = Uri.parse("content://sync/status");
+
+        // utility class
+        private Status() {}
+
+        /**
+         * returns a cursor that queries the authority sync history in descending event order of
+         * ACCOUNT, AUTHORITY
+         * @param contentResolver the ContentResolver to use for the query
+         * @return the cursor on the AuthorityHistory table
+         */
+        public static Cursor query(ContentResolver contentResolver) {
+            return contentResolver.query(CONTENT_URI, null, null, null, ACCOUNT + ", " + AUTHORITY);
+        }
+
+        public static class QueryMap extends ContentQueryMap {
+            public QueryMap(ContentResolver contentResolver,
+                    boolean keepUpdated,
+                    Handler handlerForUpdateNotifications) {
+                super(contentResolver.query(CONTENT_URI, null, null, null, null),
+                        _ID, keepUpdated, handlerForUpdateNotifications);
+            }
+
+            public ContentValues get(String account, String authority) {
+                Map<String, ContentValues> rows = getRows();
+                for (ContentValues values : rows.values()) {
+                    if (values.getAsString(ACCOUNT).equals(account)
+                            && values.getAsString(AUTHORITY).equals(authority)) {
+                        return values;
+                    }
+                }
+                return null;
+            }
+        }
+    }
+
+    /**
+     * Provides constants and utility methods to access and use the pending syncs table
+     */
+    public static final class Pending implements BaseColumns,
+                                                 StatsColumns {
+
+        /**
+         * The content url for this table.
+         */
+        public static final Uri CONTENT_URI = Uri.parse("content://sync/pending");
+
+        // utility class
+        private Pending() {}
+
+        public static class QueryMap extends ContentQueryMap {
+            public QueryMap(ContentResolver contentResolver, boolean keepUpdated,
+                    Handler handlerForUpdateNotifications) {
+                super(contentResolver.query(CONTENT_URI, null, null, null, null), _ID, keepUpdated,
+                        handlerForUpdateNotifications);
+            }
+
+            public boolean isPending(String account, String authority) {
+                Map<String, ContentValues> rows = getRows();
+                for (ContentValues values : rows.values()) {
+                    if (values.getAsString(ACCOUNT).equals(account)
+                            && values.getAsString(AUTHORITY).equals(authority)) {
+                        return true;
+                    }
+                }
+                return false;
+            }
+        }
+    }
+
+    /**
+     * Columns from the history table.
+     */
+    public interface ActiveColumns {
+        /**
+         * The wallclock time of when the active sync started.
+         * <P>Type: INTEGER</P>
+         */
+        public static final String START_TIME = "startTime";
+    }
+
+    /**
+     * Provides constants and utility methods to access and use the pending syncs table
+     */
+    public static final class Active implements BaseColumns,
+                                                StatsColumns,
+                                                ActiveColumns {
+
+        /**
+         * The content url for this table.
+         */
+        public static final Uri CONTENT_URI = Uri.parse("content://sync/active");
+
+        // utility class
+        private Active() {}
+
+        public static class QueryMap extends ContentQueryMap {
+            public QueryMap(ContentResolver contentResolver, boolean keepUpdated,
+                    Handler handlerForUpdateNotifications) {
+                super(contentResolver.query(CONTENT_URI, null, null, null, null), _ID, keepUpdated,
+                        handlerForUpdateNotifications);
+            }
+
+            public ContentValues getActiveSyncInfo() {
+                Map<String, ContentValues> rows = getRows();
+                for (ContentValues values : rows.values()) {
+                    return values;
+                }
+                return null;
+            }
+
+            public String getSyncingAccount() {
+                ContentValues values = getActiveSyncInfo();
+                return (values == null) ? null : values.getAsString(ACCOUNT);
+            }
+
+            public String getSyncingAuthority() {
+                ContentValues values = getActiveSyncInfo();
+                return (values == null) ? null : values.getAsString(AUTHORITY);
+            }
+
+            public long getSyncStartTime() {
+                ContentValues values = getActiveSyncInfo();
+                return (values == null) ? -1 : values.getAsLong(START_TIME);
+            }
+        }
+    }
+
+    /**
+     * Columns in the settings table, which holds key/value pairs of settings.
+     */
+    public interface SettingsColumns {
+        /**
+         * The key of the setting
+         * <P>Type: TEXT</P>
+         */
+        public static final String KEY = "name";
+
+        /**
+         * The value of the settings
+         * <P>Type: TEXT</P>
+         */
+        public static final String VALUE = "value";
+    }
+
+    /**
+     * Provides constants and utility methods to access and use the settings
+     * table.
+     */
+    public static final class Settings implements BaseColumns, SettingsColumns {
+        /**
+         * The Uri of the settings table. This table behaves a little differently than
+         * normal tables. Updates are not allowed, only inserts, and inserts cause a replace
+         * to be performed, which first deletes the row if it is already present.
+         */
+        public static final Uri CONTENT_URI = Uri.parse("content://sync/settings");
+
+        /** controls whether or not the device listens for sync tickles */
+        public static final String SETTING_LISTEN_FOR_TICKLES = "listen_for_tickles";
+
+        /** controls whether or not the individual provider is synced when tickles are received */
+        public static final String SETTING_SYNC_PROVIDER_PREFIX = "sync_provider_";
+
+        /** query column project */
+        private static final String[] PROJECTION = { KEY, VALUE };
+
+        /**
+         * Convenience function for updating a single settings value as a
+         * boolean. This will either create a new entry in the table if the
+         * given name does not exist, or modify the value of the existing row
+         * with that name.  Note that internally setting values are always
+         * stored as strings, so this function converts the given value to a
+         * string before storing it.
+         *
+         * @param contentResolver the ContentResolver to use to access the settings table
+         * @param name The name of the setting to modify.
+         * @param val The new value for the setting.
+         */
+        static private void putBoolean(ContentResolver contentResolver, String name, boolean val) {
+            ContentValues values = new ContentValues();
+            values.put(KEY, name);
+            values.put(VALUE, Boolean.toString(val));
+            // this insert is translated into an update by the underlying Sync provider
+            contentResolver.insert(CONTENT_URI, values);
+        }
+
+        /**
+         * Convenience function for getting a setting value as a boolean without using the
+         * QueryMap for light-weight setting querying.
+         * @param contentResolver The ContentResolver for querying the setting.
+         * @param name The name of the setting to query
+         * @param def The default value for the setting.
+         * @return The value of the setting.
+         */
+        static public boolean getBoolean(ContentResolver contentResolver,
+                String name, boolean def) {
+            Cursor cursor = contentResolver.query(
+                    CONTENT_URI,
+                    PROJECTION,
+                    KEY + "=?",
+                    new String[] { name },
+                    null);
+            try {
+                if (cursor != null && cursor.moveToFirst()) {
+                    return Boolean.parseBoolean(cursor.getString(1));
+                }
+            } finally {
+                if (cursor != null) cursor.close();
+            }
+            return def;
+        }
+
+        /**
+         * A convenience method to set whether or not the provider is synced when
+         * it receives a network tickle.
+         *
+         * @param contentResolver the ContentResolver to use to access the settings table
+         * @param providerName the provider whose behavior is being controlled
+         * @param sync true if the provider should be synced when tickles are received for it
+         */
+        static public void setSyncProviderAutomatically(ContentResolver contentResolver,
+                String providerName, boolean sync) {
+            putBoolean(contentResolver, SETTING_SYNC_PROVIDER_PREFIX + providerName, sync);
+        }
+
+        /**
+         * A convenience method to set whether or not the device should listen to tickles.
+         *
+         * @param contentResolver the ContentResolver to use to access the settings table
+         * @param flag true if it should listen.
+         */
+        static public void setListenForNetworkTickles(ContentResolver contentResolver,
+                boolean flag) {
+            putBoolean(contentResolver, SETTING_LISTEN_FOR_TICKLES, flag);
+        }
+
+        public static class QueryMap extends ContentQueryMap {
+            private ContentResolver mContentResolver;
+
+            public QueryMap(ContentResolver contentResolver, boolean keepUpdated,
+                    Handler handlerForUpdateNotifications) {
+                super(contentResolver.query(CONTENT_URI, null, null, null, null), KEY, keepUpdated,
+                        handlerForUpdateNotifications);
+                mContentResolver = contentResolver;
+            }
+
+            /**
+             * Check if the provider should be synced when a network tickle is received
+             * @param providerName the provider whose setting we are querying
+             * @return true of the provider should be synced when a network tickle is received
+             */
+            public boolean getSyncProviderAutomatically(String providerName) {
+                return getBoolean(SETTING_SYNC_PROVIDER_PREFIX + providerName, true);
+            }
+
+            /**
+             * Set whether or not the provider is synced when it receives a network tickle.
+             *
+             * @param providerName the provider whose behavior is being controlled
+             * @param sync true if the provider should be synced when tickles are received for it
+             */
+            public void setSyncProviderAutomatically(String providerName, boolean sync) {
+                Settings.setSyncProviderAutomatically(mContentResolver, providerName, sync);
+            }
+
+            /**
+             * Set whether or not the device should listen for tickles.
+             *
+             * @param flag true if it should listen.
+             */
+            public void setListenForNetworkTickles(boolean flag) {
+                Settings.setListenForNetworkTickles(mContentResolver, flag);
+            }
+
+            /**
+             * Check if the device should listen to tickles.
+
+             * @return true if it should
+             */
+            public boolean getListenForNetworkTickles() {
+                return getBoolean(SETTING_LISTEN_FOR_TICKLES, true);
+            }
+
+            /**
+             * Convenience function for retrieving a single settings value
+             * as a boolean.
+             *
+             * @param name The name of the setting to retrieve.
+             * @param def Value to return if the setting is not defined.
+             * @return The setting's current value, or 'def' if it is not defined.
+             */
+            private boolean getBoolean(String name, boolean def) {
+                ContentValues values = getValues(name);
+                return values != null ? values.getAsBoolean(VALUE) : def;
+            }
+        }
+    }
+}
diff --git a/core/java/android/provider/SyncConstValue.java b/core/java/android/provider/SyncConstValue.java
new file mode 100644
index 0000000..6eb4398
--- /dev/null
+++ b/core/java/android/provider/SyncConstValue.java
@@ -0,0 +1,71 @@
+/*
+ * 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 android.provider;
+
+/**
+ * Columns for tables that are synced to a server.
+ * @hide
+ */
+public interface SyncConstValue
+{
+    /**
+     * The account that was used to sync the entry to the device.
+     * <P>Type: TEXT</P>
+     */
+    public static final String _SYNC_ACCOUNT = "_sync_account";
+
+    /**
+     * The unique ID for a row assigned by the sync source. NULL if the row has never been synced.
+     * <P>Type: TEXT</P>
+     */
+    public static final String _SYNC_ID = "_sync_id";
+
+    /**
+     * The last time, from the sync source's point of view, that this row has been synchronized.
+     * <P>Type: INTEGER (long)</P>
+     */
+    public static final String _SYNC_TIME = "_sync_time";
+
+    /**
+     * The version of the row, as assigned by the server.
+     * <P>Type: TEXT</P>
+     */
+    public static final String _SYNC_VERSION = "_sync_version";
+
+    /**
+     * Used in temporary provider while syncing, always NULL for rows in persistent providers.
+     * <P>Type: INTEGER (long)</P>
+     */
+    public static final String _SYNC_LOCAL_ID = "_sync_local_id";
+
+    /**
+     * Used only in persistent providers, and only during merging.
+     * <P>Type: INTEGER (long)</P>
+     */
+    public static final String _SYNC_MARK = "_sync_mark";
+
+    /**
+     * Used to indicate that local, unsynced, changes are present.
+     * <P>Type: INTEGER (long)</P>
+     */
+    public static final String _SYNC_DIRTY = "_sync_dirty";
+    
+    /**
+     * Used to indicate that this account is not synced
+     */
+    public static final String NON_SYNCABLE_ACCOUNT = "non_syncable";
+}
diff --git a/core/java/android/provider/Telephony.java b/core/java/android/provider/Telephony.java
new file mode 100644
index 0000000..18c64ed
--- /dev/null
+++ b/core/java/android/provider/Telephony.java
@@ -0,0 +1,1574 @@
+/*
+ * 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 android.provider;
+
+import com.google.android.mms.util.SqliteWrapper;
+
+import android.annotation.SdkConstant;
+import android.annotation.SdkConstant.SdkConstantType;
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.Intent;
+import android.database.Cursor;
+import android.net.Uri;
+import android.telephony.gsm.SmsMessage;
+import android.text.TextUtils;
+import android.text.util.Regex;
+import android.util.Config;
+
+import java.util.HashSet;
+import java.util.Set;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * The Telephony provider contains data related to phone operation.
+ *
+ * @hide
+ */
+public final class Telephony {
+    private static final String TAG = "Telephony";
+    private static final boolean DEBUG = false;
+    private static final boolean LOCAL_LOGV = DEBUG ? Config.LOGD : Config.LOGV;
+
+    /**
+     * Base columns for tables that contain text based SMSs.
+     */
+    public interface TextBasedSmsColumns {
+        /**
+         * The type of the message
+         * <P>Type: INTEGER</P>
+         */
+        public static final String TYPE = "type";
+
+        public static final int MESSAGE_TYPE_ALL    = 0;
+        public static final int MESSAGE_TYPE_INBOX  = 1;
+        public static final int MESSAGE_TYPE_SENT   = 2;
+        public static final int MESSAGE_TYPE_DRAFT  = 3;
+        public static final int MESSAGE_TYPE_OUTBOX = 4;
+        public static final int MESSAGE_TYPE_FAILED = 5; // for failed outgoing messages
+        public static final int MESSAGE_TYPE_QUEUED = 6; // for messages to send later
+
+
+        /**
+         * The thread ID of the message
+         * <P>Type: INTEGER</P>
+         */
+        public static final String THREAD_ID = "thread_id";
+
+        /**
+         * The address of the other party
+         * <P>Type: TEXT</P>
+         */
+        public static final String ADDRESS = "address";
+
+        /**
+         * The person ID of the sender
+         * <P>Type: INTEGER (long)</P>
+         */
+        public static final String PERSON_ID = "person";
+
+        /**
+         * The date the message was sent
+         * <P>Type: INTEGER (long)</P>
+         */
+        public static final String DATE = "date";
+
+        /**
+         * Has the message been read
+         * <P>Type: INTEGER (boolean)</P>
+         */
+        public static final String READ = "read";
+
+        /**
+         * The TP-Status value for the message, or -1 if no status has
+         * been received
+         */
+        public static final String STATUS = "status";
+
+        public static final int STATUS_NONE = -1;
+        public static final int STATUS_COMPLETE = 0;
+        public static final int STATUS_PENDING = 64;
+        public static final int STATUS_FAILED = 128;
+
+        /**
+         * The subject of the message, if present
+         * <P>Type: TEXT</P>
+         */
+        public static final String SUBJECT = "subject";
+
+        /**
+         * The body of the message
+         * <P>Type: TEXT</P>
+         */
+        public static final String BODY = "body";
+
+        /**
+         * The id of the sender of the conversation, if present
+         * <P>Type: INTEGER (reference to item in content://contacts/people)</P>
+         */
+        public static final String PERSON = "person";
+
+        /**
+         * The protocol identifier code
+         * <P>Type: INTEGER</P>
+         */
+        public static final String PROTOCOL = "protocol";
+
+        /**
+         * Whether the <code>TP-Reply-Path</code> bit was set on this message
+         * <P>Type: BOOLEAN</P>
+         */
+        public static final String REPLY_PATH_PRESENT = "reply_path_present";
+
+        /**
+         * The service center (SC) through which to send the message, if present
+         * <P>Type: TEXT</P>
+         */
+        public static final String SERVICE_CENTER = "service_center";
+    }
+
+    /**
+     * Contains all text based SMS messages.
+     */
+    public static final class Sms implements BaseColumns, TextBasedSmsColumns {
+        public static final Cursor query(ContentResolver cr, String[] projection) {
+            return cr.query(CONTENT_URI, projection, null, null, DEFAULT_SORT_ORDER);
+        }
+
+        public static final Cursor query(ContentResolver cr, String[] projection,
+                String where, String orderBy) {
+            return cr.query(CONTENT_URI, projection, where,
+                                         null, orderBy == null ? DEFAULT_SORT_ORDER : orderBy);
+        }
+
+        /**
+         * The content:// style URL for this table
+         */
+        public static final Uri CONTENT_URI =
+            Uri.parse("content://sms");
+
+        /**
+         * The default sort order for this table
+         */
+        public static final String DEFAULT_SORT_ORDER = "date DESC";
+
+        /**
+         * Add an SMS to the given URI.
+         *
+         * @param resolver the content resolver to use
+         * @param uri the URI to add the message to
+         * @param address the address of the sender
+         * @param body the body of the message
+         * @param subject the psuedo-subject of the message
+         * @param date the timestamp for the message
+         * @param read true if the message has been read, false if not
+         * @param deliveryReport true if a delivery report was requested, false if not
+         * @return the URI for the new message
+         */
+        public static Uri addMessageToUri(ContentResolver resolver,
+                Uri uri, String address, String body, String subject,
+                Long date, boolean read, boolean deliveryReport) {
+            return addMessageToUri(resolver, uri, address, body, subject,
+                    date, read, deliveryReport, -1L);
+        }
+
+        /**
+         * Add an SMS to the given URI with thread_id specified.
+         *
+         * @param resolver the content resolver to use
+         * @param uri the URI to add the message to
+         * @param address the address of the sender
+         * @param body the body of the message
+         * @param subject the psuedo-subject of the message
+         * @param date the timestamp for the message
+         * @param read true if the message has been read, false if not
+         * @param deliveryReport true if a delivery report was requested, false if not
+         * @param threadId the thread_id of the message
+         * @return the URI for the new message
+         */
+        public static Uri addMessageToUri(ContentResolver resolver,
+                Uri uri, String address, String body, String subject,
+                Long date, boolean read, boolean deliveryReport, long threadId) {
+            ContentValues values = new ContentValues(7);
+
+            values.put(ADDRESS, address);
+            if (date != null) {
+                values.put(DATE, date);
+            }
+            values.put(READ, read ? Integer.valueOf(1) : Integer.valueOf(0));
+            values.put(SUBJECT, subject);
+            values.put(BODY, body);
+            if (deliveryReport) {
+                values.put(STATUS, STATUS_PENDING);
+            }
+            if (threadId != -1L) {
+                values.put(THREAD_ID, threadId);
+            }
+            return resolver.insert(uri, values);
+        }
+
+        /**
+         * Move a message to the given folder.
+         *
+         * @param context the context to use
+         * @param uri the message to move
+         * @param folder the folder to move to
+         * @return true if the operation succeeded
+         */
+        public static boolean moveMessageToFolder(Context context,
+                Uri uri, int folder) {
+            if ((uri == null) || ((folder != MESSAGE_TYPE_INBOX)
+                                  && (folder != MESSAGE_TYPE_OUTBOX)
+                                  && (folder != MESSAGE_TYPE_SENT)
+                                  && (folder != MESSAGE_TYPE_DRAFT)
+                                  && (folder != MESSAGE_TYPE_FAILED)
+                                  && (folder != MESSAGE_TYPE_QUEUED))) {
+                return false;
+            }
+
+            ContentValues values = new ContentValues(1);
+
+            values.put(TYPE, folder);
+            return 1 == SqliteWrapper.update(context, context.getContentResolver(),
+                            uri, values, null, null);
+        }
+
+        /**
+         * Returns true iff the folder (message type) identifies an
+         * outgoing message.
+         */
+        public static boolean isOutgoingFolder(int messageType) {
+            return  (messageType == MESSAGE_TYPE_FAILED)
+                    || (messageType == MESSAGE_TYPE_OUTBOX)
+                    || (messageType == MESSAGE_TYPE_SENT)
+                    || (messageType == MESSAGE_TYPE_QUEUED);
+        }
+
+        /**
+         * Contains all text based SMS messages in the SMS app's inbox.
+         */
+        public static final class Inbox implements BaseColumns, TextBasedSmsColumns {
+            /**
+             * The content:// style URL for this table
+             */
+            public static final Uri CONTENT_URI =
+                Uri.parse("content://sms/inbox");
+
+            /**
+             * The default sort order for this table
+             */
+            public static final String DEFAULT_SORT_ORDER = "date DESC";
+
+            /**
+             * Add an SMS to the Draft box.
+             *
+             * @param resolver the content resolver to use
+             * @param address the address of the sender
+             * @param body the body of the message
+             * @param subject the psuedo-subject of the message
+             * @param date the timestamp for the message
+             * @param read true if the message has been read, false if not
+             * @return the URI for the new message
+             */
+            public static Uri addMessage(ContentResolver resolver,
+                    String address, String body, String subject, Long date,
+                    boolean read) {
+                return addMessageToUri(resolver, CONTENT_URI, address, body,
+                        subject, date, read, false);
+            }
+        }
+
+        /**
+         * Contains all sent text based SMS messages in the SMS app's.
+         */
+        public static final class Sent implements BaseColumns, TextBasedSmsColumns {
+            /**
+             * The content:// style URL for this table
+             */
+            public static final Uri CONTENT_URI =
+                    Uri.parse("content://sms/sent");
+
+            /**
+             * The default sort order for this table
+             */
+            public static final String DEFAULT_SORT_ORDER = "date DESC";
+
+            /**
+             * Add an SMS to the Draft box.
+             *
+             * @param resolver the content resolver to use
+             * @param address the address of the sender
+             * @param body the body of the message
+             * @param subject the psuedo-subject of the message
+             * @param date the timestamp for the message
+             * @return the URI for the new message
+             */
+            public static Uri addMessage(ContentResolver resolver,
+                    String address, String body, String subject, Long date) {
+                return addMessageToUri(resolver, CONTENT_URI, address, body,
+                        subject, date, true, false);
+            }
+        }
+
+        /**
+         * Contains all sent text based SMS messages in the SMS app's.
+         */
+        public static final class Draft implements BaseColumns, TextBasedSmsColumns {
+            /**
+             * The content:// style URL for this table
+             */
+            public static final Uri CONTENT_URI =
+                    Uri.parse("content://sms/draft");
+
+            /**
+             * The default sort order for this table
+             */
+            public static final String DEFAULT_SORT_ORDER = "date DESC";
+
+            /**
+             * Add an SMS to the Draft box.
+             *
+             * @param resolver the content resolver to use
+             * @param address the address of the sender
+             * @param body the body of the message
+             * @param subject the psuedo-subject of the message
+             * @param date the timestamp for the message
+             * @return the URI for the new message
+             */
+            public static Uri addMessage(ContentResolver resolver,
+                    String address, String body, String subject, Long date) {
+                return addMessageToUri(resolver, CONTENT_URI, address, body,
+                        subject, date, true, false);
+            }
+
+            /**
+             * Save over an existing draft message.
+             *
+             * @param resolver the content resolver to use
+             * @param uri of existing message
+             * @param body the new body for the draft message
+             * @return true is successful, false otherwise
+             */
+            public static boolean saveMessage(ContentResolver resolver,
+                    Uri uri, String body) {
+                ContentValues values = new ContentValues(2);
+                values.put(BODY, body);
+                values.put(DATE, System.currentTimeMillis());
+                return resolver.update(uri, values, null, null) == 1;
+            }
+        }
+
+        /**
+         * Contains all pending outgoing text based SMS messages.
+         */
+        public static final class Outbox implements BaseColumns, TextBasedSmsColumns {
+            /**
+             * The content:// style URL for this table
+             */
+            public static final Uri CONTENT_URI =
+                Uri.parse("content://sms/outbox");
+
+            /**
+             * The default sort order for this table
+             */
+            public static final String DEFAULT_SORT_ORDER = "date DESC";
+
+            /**
+             * Add an SMS to the Out box.
+             *
+             * @param resolver the content resolver to use
+             * @param address the address of the sender
+             * @param body the body of the message
+             * @param subject the psuedo-subject of the message
+             * @param date the timestamp for the message
+             * @param deliveryReport whether a delivery report was requested for the message
+             * @return the URI for the new message
+             */
+            public static Uri addMessage(ContentResolver resolver,
+                    String address, String body, String subject, Long date,
+                    boolean deliveryReport, long threadId) {
+                return addMessageToUri(resolver, CONTENT_URI, address, body,
+                        subject, date, true, deliveryReport, threadId);
+            }
+        }
+
+        /**
+         * Contains all sent text-based SMS messages in the SMS app's.
+         */
+        public static final class Conversations
+                implements BaseColumns, TextBasedSmsColumns {
+            /**
+             * The content:// style URL for this table
+             */
+            public static final Uri CONTENT_URI =
+                Uri.parse("content://sms/conversations");
+
+            /**
+             * The default sort order for this table
+             */
+            public static final String DEFAULT_SORT_ORDER = "date DESC";
+
+            /**
+             * The first 45 characters of the body of the message
+             * <P>Type: TEXT</P>
+             */
+            public static final String SNIPPET = "snippet";
+
+            /**
+             * The number of messages in the conversation
+             * <P>Type: INTEGER</P>
+             */
+            public static final String MESSAGE_COUNT = "msg_count";
+        }
+
+        /**
+         * Contains info about SMS related Intents that are broadcast.
+         */
+        public static final class Intents {
+            /**
+             * Broadcast Action: A new text based SMS message has been received
+             * by the device. The intent will have the following extra
+             * values:</p>
+             *
+             * <ul>
+             *   <li><em>pdus</em> - An Object[] od byte[]s containing the PDUs
+             *   that make up the message.</li>
+             * </ul>
+             *
+             * <p>The extra values can be extracted using
+             * {@link #getMessagesFromIntent(Intent)}</p>
+             */
+            @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+            public static final String SMS_RECEIVED_ACTION =
+                    "android.provider.Telephony.SMS_RECEIVED";
+
+            /**
+             * Broadcast Action: A new data based SMS message has been received
+             * by the device. The intent will have the following extra
+             * values:</p>
+             *
+             * <ul>
+             *   <li><em>pdus</em> - An Object[] od byte[]s containing the PDUs
+             *   that make up the message.</li>
+             * </ul>
+             *
+             * <p>The extra values can be extracted using
+             * {@link #getMessagesFromIntent(Intent)}</p>
+             */
+            @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+            public static final String DATA_SMS_RECEIVED_ACTION =
+                    "android.intent.action.DATA_SMS_RECEIVED";
+
+            /**
+             * Broadcast Action: A new WAP PUSH message has been received by the
+             * device. The intent will have the following extra
+             * values:</p>
+             *
+             * <ul>
+             *   <li><em>transactionId (Integer)</em> - The WAP transaction
+             *    ID</li>
+             *   <li><em>pduType (Integer)</em> - The WAP PDU type</li>
+             *   <li><em>data</em> - The data payload of the message</li>
+             * </ul>
+             */
+            @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+            public static final String WAP_PUSH_RECEIVED_ACTION =
+                    "android.provider.Telephony.WAP_PUSH_RECEIVED";
+
+            /**
+             * Broadcast Action: The SIM storage for SMS messages is full.  If
+             * space is not freed, messages targeted for the SIM (class 2) may
+             * not be saved.
+             */
+            @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+            public static final String SIM_FULL_ACTION =
+                "android.provider.Telephony.SIM_FULL";
+
+            /**
+             * Read the PDUs out of an {@link #SMS_RECEIVED_ACTION} or a
+             * {@link #DATA_SMS_RECEIVED_ACTION} intent.
+             *
+             * @param intent the intent to read from
+             * @return an array of SmsMessages for the PDUs
+             */
+            public static final SmsMessage[] getMessagesFromIntent(
+                    Intent intent) {
+                Object[] messages = (Object[]) intent.getSerializableExtra("pdus");
+                byte[][] pduObjs = new byte[messages.length][];
+
+                for (int i = 0; i < messages.length; i++) {
+                    pduObjs[i] = (byte[]) messages[i];
+                }
+                byte[][] pdus = new byte[pduObjs.length][];
+                int pduCount = pdus.length;
+                SmsMessage[] msgs = new SmsMessage[pduCount];
+                for (int i = 0; i < pduCount; i++) {
+                    pdus[i] = pduObjs[i];
+                    msgs[i] = SmsMessage.createFromPdu(pdus[i]);
+                }
+                return msgs;
+            }
+        }
+    }
+
+    /**
+     * Base columns for tables that contain MMSs.
+     */
+    public interface BaseMmsColumns extends BaseColumns {
+
+        public static final int MESSAGE_BOX_ALL    = 0;
+        public static final int MESSAGE_BOX_INBOX  = 1;
+        public static final int MESSAGE_BOX_SENT   = 2;
+        public static final int MESSAGE_BOX_DRAFTS = 3;
+        public static final int MESSAGE_BOX_OUTBOX = 4;
+
+        /**
+         * The date the message was sent.
+         * <P>Type: INTEGER (long)</P>
+         */
+        public static final String DATE = "date";
+
+        /**
+         * The box which the message belong to, for example, MESSAGE_BOX_INBOX.
+         * <P>Type: INTEGER</P>
+         */
+        public static final String MESSAGE_BOX = "msg_box";
+
+        /**
+         * Has the message been read.
+         * <P>Type: INTEGER (boolean)</P>
+         */
+        public static final String READ = "read";
+
+        /**
+         * The Message-ID of the message.
+         * <P>Type: TEXT</P>
+         */
+        public static final String MESSAGE_ID = "m_id";
+
+        /**
+         * The subject of the message, if present.
+         * <P>Type: TEXT</P>
+         */
+        public static final String SUBJECT = "sub";
+
+        /**
+         * The character set of the subject, if present.
+         * <P>Type: INTEGER</P>
+         */
+        public static final String SUBJECT_CHARSET = "sub_cs";
+
+        /**
+         * The Content-Type of the message.
+         * <P>Type: TEXT</P>
+         */
+        public static final String CONTENT_TYPE = "ct_t";
+
+        /**
+         * The Content-Location of the message.
+         * <P>Type: TEXT</P>
+         */
+        public static final String CONTENT_LOCATION = "ct_l";
+
+        /**
+         * The address of the sender.
+         * <P>Type: TEXT</P>
+         */
+        public static final String FROM = "from";
+
+        /**
+         * The address of the recipients.
+         * <P>Type: TEXT</P>
+         */
+        public static final String TO = "to";
+
+        /**
+         * The address of the cc. recipients.
+         * <P>Type: TEXT</P>
+         */
+        public static final String CC = "cc";
+
+        /**
+         * The address of the bcc. recipients.
+         * <P>Type: TEXT</P>
+         */
+        public static final String BCC = "bcc";
+
+        /**
+         * The expiry time of the message.
+         * <P>Type: INTEGER</P>
+         */
+        public static final String EXPIRY = "exp";
+
+        /**
+         * The class of the message.
+         * <P>Type: TEXT</P>
+         */
+        public static final String MESSAGE_CLASS = "m_cls";
+
+        /**
+         * The type of the message defined by MMS spec.
+         * <P>Type: INTEGER</P>
+         */
+        public static final String MESSAGE_TYPE = "m_type";
+
+        /**
+         * The version of specification that this message conform.
+         * <P>Type: INTEGER</P>
+         */
+        public static final String MMS_VERSION = "v";
+
+        /**
+         * The size of the message.
+         * <P>Type: INTEGER</P>
+         */
+        public static final String MESSAGE_SIZE = "m_size";
+
+        /**
+         * The priority of the message.
+         * <P>Type: TEXT</P>
+         */
+        public static final String PRIORITY = "pri";
+
+        /**
+         * The read-report of the message.
+         * <P>Type: TEXT</P>
+         */
+        public static final String READ_REPORT = "rr";
+
+        /**
+         * Whether the report is allowed.
+         * <P>Type: TEXT</P>
+         */
+        public static final String REPORT_ALLOWED = "rpt_a";
+
+        /**
+         * The response-status of the message.
+         * <P>Type: INTEGER</P>
+         */
+        public static final String RESPONSE_STATUS = "resp_st";
+
+        /**
+         * The status of the message.
+         * <P>Type: INTEGER</P>
+         */
+        public static final String STATUS = "st";
+
+        /**
+         * The transaction-id of the message.
+         * <P>Type: TEXT</P>
+         */
+        public static final String TRANSACTION_ID = "tr_id";
+
+        /**
+         * The retrieve-status of the message.
+         * <P>Type: INTEGER</P>
+         */
+        public static final String RETRIEVE_STATUS = "retr_st";
+
+        /**
+         * The retrieve-text of the message.
+         * <P>Type: TEXT</P>
+         */
+        public static final String RETRIEVE_TEXT = "retr_txt";
+
+        /**
+         * The character set of the retrieve-text.
+         * <P>Type: TEXT</P>
+         */
+        public static final String RETRIEVE_TEXT_CHARSET = "retr_txt_cs";
+
+        /**
+         * The read-status of the message.
+         * <P>Type: INTEGER</P>
+         */
+        public static final String READ_STATUS = "read_status";
+
+        /**
+         * The content-class of the message.
+         * <P>Type: INTEGER</P>
+         */
+        public static final String CONTENT_CLASS = "ct_cls";
+
+        /**
+         * The delivery-report of the message.
+         * <P>Type: INTEGER</P>
+         */
+        public static final String DELIVERY_REPORT = "d_rpt";
+
+        /**
+         * The delivery-time-token of the message.
+         * <P>Type: INTEGER</P>
+         */
+        public static final String DELIVERY_TIME_TOKEN = "d_tm_tok";
+
+        /**
+         * The delivery-time of the message.
+         * <P>Type: INTEGER</P>
+         */
+        public static final String DELIVERY_TIME = "d_tm";
+
+        /**
+         * The response-text of the message.
+         * <P>Type: TEXT</P>
+         */
+        public static final String RESPONSE_TEXT = "resp_txt";
+
+        /**
+         * The sender-visibility of the message.
+         * <P>Type: TEXT</P>
+         */
+        public static final String SENDER_VISIBILITY = "s_vis";
+
+        /**
+         * The reply-charging of the message.
+         * <P>Type: INTEGER</P>
+         */
+        public static final String REPLY_CHARGING = "r_chg";
+
+        /**
+         * The reply-charging-deadline-token of the message.
+         * <P>Type: INTEGER</P>
+         */
+        public static final String REPLY_CHARGING_DEADLINE_TOKEN = "r_chg_dl_tok";
+
+        /**
+         * The reply-charging-deadline of the message.
+         * <P>Type: INTEGER</P>
+         */
+        public static final String REPLY_CHARGING_DEADLINE = "r_chg_dl";
+
+        /**
+         * The reply-charging-id of the message.
+         * <P>Type: TEXT</P>
+         */
+        public static final String REPLY_CHARGING_ID = "r_chg_id";
+
+        /**
+         * The reply-charging-size of the message.
+         * <P>Type: INTEGER</P>
+         */
+        public static final String REPLY_CHARGING_SIZE = "r_chg_sz";
+
+        /**
+         * The previously-sent-by of the message.
+         * <P>Type: TEXT</P>
+         */
+        public static final String PREVIOUSLY_SENT_BY = "p_s_by";
+
+        /**
+         * The previously-sent-date of the message.
+         * <P>Type: INTEGER</P>
+         */
+        public static final String PREVIOUSLY_SENT_DATE = "p_s_d";
+
+        /**
+         * The store of the message.
+         * <P>Type: TEXT</P>
+         */
+        public static final String STORE = "store";
+
+        /**
+         * The mm-state of the message.
+         * <P>Type: INTEGER</P>
+         */
+        public static final String MM_STATE = "mm_st";
+
+        /**
+         * The mm-flags-token of the message.
+         * <P>Type: INTEGER</P>
+         */
+        public static final String MM_FLAGS_TOKEN = "mm_flg_tok";
+
+        /**
+         * The mm-flags of the message.
+         * <P>Type: TEXT</P>
+         */
+        public static final String MM_FLAGS = "mm_flg";
+
+        /**
+         * The store-status of the message.
+         * <P>Type: TEXT</P>
+         */
+        public static final String STORE_STATUS = "store_st";
+
+        /**
+         * The store-status-text of the message.
+         * <P>Type: TEXT</P>
+         */
+        public static final String STORE_STATUS_TEXT = "store_st_txt";
+
+        /**
+         * The stored of the message.
+         * <P>Type: TEXT</P>
+         */
+        public static final String STORED = "stored";
+
+        /**
+         * The totals of the message.
+         * <P>Type: TEXT</P>
+         */
+        public static final String TOTALS = "totals";
+
+        /**
+         * The mbox-totals of the message.
+         * <P>Type: TEXT</P>
+         */
+        public static final String MBOX_TOTALS = "mb_t";
+
+        /**
+         * The mbox-totals-token of the message.
+         * <P>Type: INTEGER</P>
+         */
+        public static final String MBOX_TOTALS_TOKEN = "mb_t_tok";
+
+        /**
+         * The quotas of the message.
+         * <P>Type: TEXT</P>
+         */
+        public static final String QUOTAS = "qt";
+
+        /**
+         * The mbox-quotas of the message.
+         * <P>Type: TEXT</P>
+         */
+        public static final String MBOX_QUOTAS = "mb_qt";
+
+        /**
+         * The mbox-quotas-token of the message.
+         * <P>Type: INTEGER</P>
+         */
+        public static final String MBOX_QUOTAS_TOKEN = "mb_qt_tok";
+
+        /**
+         * The message-count of the message.
+         * <P>Type: INTEGER</P>
+         */
+        public static final String MESSAGE_COUNT = "m_cnt";
+
+        /**
+         * The start of the message.
+         * <P>Type: INTEGER</P>
+         */
+        public static final String START = "start";
+
+        /**
+         * The distribution-indicator of the message.
+         * <P>Type: TEXT</P>
+         */
+        public static final String DISTRIBUTION_INDICATOR = "d_ind";
+
+        /**
+         * The element-descriptor of the message.
+         * <P>Type: TEXT</P>
+         */
+        public static final String ELEMENT_DESCRIPTOR = "e_des";
+
+        /**
+         * The limit of the message.
+         * <P>Type: INTEGER</P>
+         */
+        public static final String LIMIT = "limit";
+
+        /**
+         * The recommended-retrieval-mode of the message.
+         * <P>Type: INTEGER</P>
+         */
+        public static final String RECOMMENDED_RETRIEVAL_MODE = "r_r_mod";
+
+        /**
+         * The recommended-retrieval-mode-text of the message.
+         * <P>Type: TEXT</P>
+         */
+        public static final String RECOMMENDED_RETRIEVAL_MODE_TEXT = "r_r_mod_txt";
+
+        /**
+         * The status-text of the message.
+         * <P>Type: TEXT</P>
+         */
+        public static final String STATUS_TEXT = "st_txt";
+
+        /**
+         * The applic-id of the message.
+         * <P>Type: TEXT</P>
+         */
+        public static final String APPLIC_ID = "apl_id";
+
+        /**
+         * The reply-applic-id of the message.
+         * <P>Type: TEXT</P>
+         */
+        public static final String REPLY_APPLIC_ID = "r_apl_id";
+
+        /**
+         * The aux-applic-id of the message.
+         * <P>Type: TEXT</P>
+         */
+        public static final String AUX_APPLIC_ID = "aux_apl_id";
+
+        /**
+         * The drm-content of the message.
+         * <P>Type: TEXT</P>
+         */
+        public static final String DRM_CONTENT = "drm_c";
+
+        /**
+         * The adaptation-allowed of the message.
+         * <P>Type: TEXT</P>
+         */
+        public static final String ADAPTATION_ALLOWED = "adp_a";
+
+        /**
+         * The replace-id of the message.
+         * <P>Type: TEXT</P>
+         */
+        public static final String REPLACE_ID = "repl_id";
+
+        /**
+         * The cancel-id of the message.
+         * <P>Type: TEXT</P>
+         */
+        public static final String CANCEL_ID = "cl_id";
+
+        /**
+         * The cancel-status of the message.
+         * <P>Type: INTEGER</P>
+         */
+        public static final String CANCEL_STATUS = "cl_st";
+
+        /**
+         * The thread ID of the message
+         * <P>Type: INTEGER</P>
+         */
+        public static final String THREAD_ID = "thread_id";
+    }
+
+    /**
+     * Columns for the "canonical_addresses" table used by MMS and
+     * SMS."
+     */
+    public interface CanonicalAddressesColumns extends BaseColumns {
+        /**
+         * An address used in MMS or SMS.  Email addresses are
+         * converted to lower case and are compared by string
+         * equality.  Other addresses are compared using
+         * PHONE_NUMBERS_EQUAL.
+         * <P>Type: TEXT</P>
+         */
+        public static final String ADDRESS = "address";
+    }
+
+    /**
+     * Columns for the "threads" table used by MMS and SMS.
+     */
+    public interface ThreadsColumns extends BaseColumns {
+        /**
+         * The date at which the thread was created.
+         *
+         * <P>Type: INTEGER (long)</P>
+         */
+        public static final String DATE = "date";
+
+        /**
+         * A string encoding of the recipient IDs of the recipients of
+         * the message, in numerical order and separated by spaces.
+         * <P>Type: TEXT</P>
+         */
+        public static final String RECIPIENT_IDS = "recipient_ids";
+
+        /**
+         * The message count of the thread.
+         * <P>Type: INTEGER</P>
+         */
+        public static final String MESSAGE_COUNT = "message_count";
+        /**
+         * Indicates whether all messages of the thread have been read.
+         * <P>Type: INTEGER</P>
+         */
+        public static final String READ = "read";
+        /**
+         * The snippet of the latest message in the thread.
+         * <P>Type: TEXT</P>
+         */
+        public static final String SNIPPET = "snippet";
+        /**
+         * The charset of the snippet.
+         * <P>Type: INTEGER</P>
+         */
+        public static final String SNIPPET_CHARSET = "snippet_cs";
+        /**
+         * Type of the thread, either Threads.COMMON_THREAD or
+         * Threads.BROADCAST_THREAD.
+         * <P>Type: INTEGER</P>
+         */
+        public static final String TYPE = "type";
+        /**
+         * Indicates whether there is a transmission error in the thread.
+         * <P>Type: INTEGER</P>
+         */
+        public static final String ERROR = "error";
+    }
+
+    /**
+     * Helper functions for the "threads" table used by MMS and SMS.
+     */
+    public static final class Threads implements ThreadsColumns {
+        private static final String[] ID_PROJECTION = { BaseColumns._ID };
+        private static final String STANDARD_ENCODING = "UTF-8";
+        private static final Uri THREAD_ID_CONTENT_URI = Uri.parse(
+                "content://mms-sms/threadID");
+        public static final Uri CONTENT_URI = Uri.withAppendedPath(
+                MmsSms.CONTENT_URI, "conversations");
+        public static final Uri OBSOLETE_THREADS_URI = Uri.withAppendedPath(
+                CONTENT_URI, "obsolete");
+
+        public static final int COMMON_THREAD    = 0;
+        public static final int BROADCAST_THREAD = 1;
+
+        // No one should construct an instance of this class.
+        private Threads() {
+        }
+
+        /**
+         * This is a single-recipient version of
+         * getOrCreateThreadId.  It's convenient for use with SMS
+         * messages.
+         */
+        public static long getOrCreateThreadId(Context context, String recipient) {
+            Set<String> recipients = new HashSet<String>();
+
+            recipients.add(recipient);
+            return getOrCreateThreadId(context, recipients);
+        }
+
+        /**
+         * Given the recipients list and subject of an unsaved message,
+         * return its thread ID.  If the message starts a new thread,
+         * allocate a new thread ID.  Otherwise, use the appropriate
+         * existing thread ID.
+         *
+         * Find the thread ID of the same set of recipients (in
+         * any order, without any additions). If one
+         * is found, return it.  Otherwise, return a unique thread ID.
+         */
+        public static long getOrCreateThreadId(
+                Context context, Set<String> recipients) {
+            Uri.Builder uriBuilder = THREAD_ID_CONTENT_URI.buildUpon();
+
+            for (String recipient : recipients) {
+                if (Mms.isEmailAddress(recipient)) {
+                    recipient = Mms.extractAddrSpec(recipient);
+                }
+
+                uriBuilder.appendQueryParameter("recipient", recipient);
+            }
+
+            Cursor cursor = SqliteWrapper.query(context, context.getContentResolver(),
+                    uriBuilder.build(), ID_PROJECTION, null, null, null);
+            if (cursor != null) {
+                try {
+                    if (cursor.moveToFirst()) {
+                        return cursor.getLong(0);
+                    }
+                } finally {
+                    cursor.close();
+                }
+            }
+
+            throw new IllegalArgumentException("Unable to find or allocate a thread ID.");
+        }
+    }
+
+    /**
+     * Contains all MMS messages.
+     */
+    public static final class Mms implements BaseMmsColumns {
+        /**
+         * The content:// style URL for this table
+         */
+        public static final Uri CONTENT_URI = Uri.parse("content://mms");
+
+        public static final Uri REPORT_REQUEST_URI = Uri.withAppendedPath(
+                                            CONTENT_URI, "report-request");
+
+        public static final Uri REPORT_STATUS_URI = Uri.withAppendedPath(
+                                            CONTENT_URI, "report-status");
+
+        /**
+         * The default sort order for this table
+         */
+        public static final String DEFAULT_SORT_ORDER = "date DESC";
+
+        /**
+         * mailbox         =       name-addr
+         * name-addr       =       [display-name] angle-addr
+         * angle-addr      =       [CFWS] "<" addr-spec ">" [CFWS]
+         */
+        public static final Pattern NAME_ADDR_EMAIL_PATTERN =
+                Pattern.compile("\\s*(\"[^\"]*\"|[^<>\"]+)\\s*<([^<>]+)>\\s*");
+
+        /**
+         * quoted-string   =       [CFWS]
+         *                         DQUOTE *([FWS] qcontent) [FWS] DQUOTE
+         *                         [CFWS]
+         */
+        public static final Pattern QUOTED_STRING_PATTERN =
+                Pattern.compile("\\s*\"([^\"]*)\"\\s*");
+
+        public static final Cursor query(
+                ContentResolver cr, String[] projection) {
+            return cr.query(CONTENT_URI, projection, null, null, DEFAULT_SORT_ORDER);
+        }
+
+        public static final Cursor query(
+                ContentResolver cr, String[] projection,
+                String where, String orderBy) {
+            return cr.query(CONTENT_URI, projection,
+                    where, null, orderBy == null ? DEFAULT_SORT_ORDER : orderBy);
+        }
+
+        public static final String getMessageBoxName(int msgBox) {
+            switch (msgBox) {
+                case MESSAGE_BOX_ALL:
+                    return "all";
+                case MESSAGE_BOX_INBOX:
+                    return "inbox";
+                case MESSAGE_BOX_SENT:
+                    return "sent";
+                case MESSAGE_BOX_DRAFTS:
+                    return "drafts";
+                case MESSAGE_BOX_OUTBOX:
+                    return "outbox";
+                default:
+                    throw new IllegalArgumentException("Invalid message box: " + msgBox);
+            }
+        }
+
+        public static String extractAddrSpec(String address) {
+            Matcher match = NAME_ADDR_EMAIL_PATTERN.matcher(address);
+
+            if (match.matches()) {
+                return match.group(2);
+            }
+            return address;
+        }
+
+        /**
+         * Returns true if the address is an email address
+         *
+         * @param address the input address to be tested
+         * @return true if address is an email address
+         */
+        public static boolean isEmailAddress(String address) {
+            if (TextUtils.isEmpty(address)) {
+                return false;
+            }
+
+            String s = extractAddrSpec(address);
+            Matcher match = Regex.EMAIL_ADDRESS_PATTERN.matcher(s);
+            return match.matches();
+        }
+
+        /**
+         * Contains all MMS messages in the MMS app's inbox.
+         */
+        public static final class Inbox implements BaseMmsColumns {
+            /**
+             * The content:// style URL for this table
+             */
+            public static final Uri
+                    CONTENT_URI = Uri.parse("content://mms/inbox");
+
+            /**
+             * The default sort order for this table
+             */
+            public static final String DEFAULT_SORT_ORDER = "date DESC";
+        }
+
+        /**
+         * Contains all MMS messages in the MMS app's sent box.
+         */
+        public static final class Sent implements BaseMmsColumns {
+            /**
+             * The content:// style URL for this table
+             */
+            public static final Uri
+                    CONTENT_URI = Uri.parse("content://mms/sent");
+
+            /**
+             * The default sort order for this table
+             */
+            public static final String DEFAULT_SORT_ORDER = "date DESC";
+        }
+
+        /**
+         * Contains all MMS messages in the MMS app's drafts box.
+         */
+        public static final class Draft implements BaseMmsColumns {
+            /**
+             * The content:// style URL for this table
+             */
+            public static final Uri
+                    CONTENT_URI = Uri.parse("content://mms/drafts");
+
+            /**
+             * The default sort order for this table
+             */
+            public static final String DEFAULT_SORT_ORDER = "date DESC";
+        }
+
+        /**
+         * Contains all MMS messages in the MMS app's outbox.
+         */
+        public static final class Outbox implements BaseMmsColumns {
+            /**
+             * The content:// style URL for this table
+             */
+            public static final Uri
+                    CONTENT_URI = Uri.parse("content://mms/outbox");
+
+            /**
+             * The default sort order for this table
+             */
+            public static final String DEFAULT_SORT_ORDER = "date DESC";
+        }
+
+        public static final class Addr implements BaseColumns {
+            /**
+             * The ID of MM which this address entry belongs to.
+             */
+            public static final String MSG_ID = "msg_id";
+
+            /**
+             * The ID of contact entry in Phone Book.
+             */
+            public static final String CONTACT_ID = "contact_id";
+
+            /**
+             * The address text.
+             */
+            public static final String ADDRESS = "address";
+
+            /**
+             * Type of address, must be one of PduHeaders.BCC,
+             * PduHeaders.CC, PduHeaders.FROM, PduHeaders.TO.
+             */
+            public static final String TYPE = "type";
+
+            /**
+             * Character set of this entry.
+             */
+            public static final String CHARSET = "charset";
+        }
+
+        public static final class Part implements BaseColumns {
+            /**
+             * The identifier of the message which this part belongs to.
+             * <P>Type: INTEGER</P>
+             */
+            public static final String MSG_ID = "mid";
+
+            /**
+             * The order of the part.
+             * <P>Type: INTEGER</P>
+             */
+            public static final String SEQ = "seq";
+
+            /**
+             * The content type of the part.
+             * <P>Type: TEXT</P>
+             */
+            public static final String CONTENT_TYPE = "ct";
+
+            /**
+             * The name of the part.
+             * <P>Type: TEXT</P>
+             */
+            public static final String NAME = "name";
+
+            /**
+             * The charset of the part.
+             * <P>Type: TEXT</P>
+             */
+            public static final String CHARSET = "chset";
+
+            /**
+             * The file name of the part.
+             * <P>Type: TEXT</P>
+             */
+            public static final String FILENAME = "fn";
+
+            /**
+             * The content disposition of the part.
+             * <P>Type: TEXT</P>
+             */
+            public static final String CONTENT_DISPOSITION = "cd";
+
+            /**
+             * The content ID of the part.
+             * <P>Type: INTEGER</P>
+             */
+            public static final String CONTENT_ID = "cid";
+
+            /**
+             * The content location of the part.
+             * <P>Type: INTEGER</P>
+             */
+            public static final String CONTENT_LOCATION = "cl";
+
+            /**
+             * The start of content-type of the message.
+             * <P>Type: INTEGER</P>
+             */
+            public static final String CT_START = "ctt_s";
+
+            /**
+             * The type of content-type of the message.
+             * <P>Type: TEXT</P>
+             */
+            public static final String CT_TYPE = "ctt_t";
+
+            /**
+             * The location(on filesystem) of the binary data of the part.
+             * <P>Type: INTEGER</P>
+             */
+            public static final String _DATA = "_data";
+
+        }
+
+        public static final class Rate {
+            public static final Uri CONTENT_URI = Uri.withAppendedPath(
+                    Mms.CONTENT_URI, "rate");
+            /**
+             * When a message was successfully sent.
+             * <P>Type: INTEGER</P>
+             */
+            public static final String SENT_TIME = "sent_time";
+        }
+
+        public static final class Intents {
+            private Intents() {
+                // Non-instantiatable.
+            }
+
+            /**
+             * The extra field to store the contents of the Intent,
+             * which should be an array of Uri.
+             */
+            public static final String EXTRA_CONTENTS = "contents";
+            /**
+             * The extra field to store the type of the contents,
+             * which should be an array of String.
+             */
+            public static final String EXTRA_TYPES    = "types";
+            /**
+             * The extra field to store the 'Cc' addresses.
+             */
+            public static final String EXTRA_CC       = "cc";
+            /**
+             * The extra field to store the 'Bcc' addresses;
+             */
+            public static final String EXTRA_BCC      = "bcc";
+            /**
+             * The extra field to store the 'Subject'.
+             */
+            public static final String EXTRA_SUBJECT  = "subject";
+            /**
+             * Indicates that the contents of specified URIs were changed.
+             * The application which is showing or caching these contents
+             * should be updated.
+             */
+            public static final String
+            CONTENT_CHANGED_ACTION = "android.intent.action.CONTENT_CHANGED";
+            /**
+             * An extra field which stores the URI of deleted contents.
+             */
+            public static final String DELETED_CONTENTS = "deleted_contents";
+        }
+    }
+
+    /**
+     * Contains all MMS and SMS messages.
+     */
+    public static final class MmsSms implements BaseColumns {
+        /**
+         * The column to distinguish SMS &amp; MMS messages in query results.
+         */
+        public static final String TYPE_DISCRIMINATOR_COLUMN =
+                "transport_type";
+
+        public static final Uri CONTENT_URI = Uri.parse("content://mms-sms/");
+
+        public static final Uri CONTENT_CONVERSATIONS_URI = Uri.parse(
+                "content://mms-sms/conversations");
+
+        public static final Uri CONTENT_FILTER_BYPHONE_URI = Uri.parse(
+                "content://mms-sms/messages/byphone");
+
+        public static final Uri CONTENT_UNDELIVERED_URI = Uri.parse(
+                "content://mms-sms/undelivered");
+
+        public static final Uri CONTENT_DRAFT_URI = Uri.parse(
+                "content://mms-sms/draft");
+
+        // Constants for message protocol types.
+        public static final int SMS_PROTO = 0;
+        public static final int MMS_PROTO = 1;
+
+        // Constants for error types of pending messages.
+        public static final int NO_ERROR                      = 0;
+        public static final int ERR_TYPE_GENERIC              = 1;
+        public static final int ERR_TYPE_SMS_PROTO_TRANSIENT  = 2;
+        public static final int ERR_TYPE_MMS_PROTO_TRANSIENT  = 3;
+        public static final int ERR_TYPE_TRANSPORT_FAILURE    = 4;
+        public static final int ERR_TYPE_GENERIC_PERMANENT    = 10;
+        public static final int ERR_TYPE_SMS_PROTO_PERMANENT  = 11;
+        public static final int ERR_TYPE_MMS_PROTO_PERMANENT  = 12;
+
+        public static final class PendingMessages implements BaseColumns {
+            public static final Uri CONTENT_URI = Uri.withAppendedPath(
+                    MmsSms.CONTENT_URI, "pending");
+            /**
+             * The type of transport protocol(MMS or SMS).
+             * <P>Type: INTEGER</P>
+             */
+            public static final String PROTO_TYPE = "proto_type";
+            /**
+             * The ID of the message to be sent or downloaded.
+             * <P>Type: INTEGER</P>
+             */
+            public static final String MSG_ID = "msg_id";
+            /**
+             * The type of the message to be sent or downloaded.
+             * This field is only valid for MM. For SM, its value is always
+             * set to 0.
+             */
+            public static final String MSG_TYPE = "msg_type";
+            /**
+             * The type of the error code.
+             * <P>Type: INTEGER</P>
+             */
+            public static final String ERROR_TYPE = "err_type";
+            /**
+             * The error code of sending/retrieving process.
+             * <P>Type:  INTEGER</P>
+             */
+            public static final String ERROR_CODE = "err_code";
+            /**
+             * How many times we tried to send or download the message.
+             * <P>Type:  INTEGER</P>
+             */
+            public static final String RETRY_INDEX = "retry_index";
+            /**
+             * The time to do next retry.
+             */
+            public static final String DUE_TIME = "due_time";
+            /**
+             * The time we last tried to send or download the message.
+             */
+            public static final String LAST_TRY = "last_try";
+        }
+    }
+
+    public static final class Carriers implements BaseColumns {
+        /**
+         * The content:// style URL for this table
+         */
+        public static final Uri CONTENT_URI =
+            Uri.parse("content://telephony/carriers");
+
+        /**
+         * The default sort order for this table
+         */
+        public static final String DEFAULT_SORT_ORDER = "name ASC";
+
+        public static final String NAME = "name";
+
+        public static final String APN = "apn";
+
+        public static final String PROXY = "proxy";
+
+        public static final String PORT = "port";
+
+        public static final String MMSPROXY = "mmsproxy";
+
+        public static final String MMSPORT = "mmsport";
+
+        public static final String SERVER = "server";
+
+        public static final String USER = "user";
+
+        public static final String PASSWORD = "password";
+
+        public static final String MMSC = "mmsc";
+
+        public static final String MCC = "mcc";
+
+        public static final String MNC = "mnc";
+
+        public static final String NUMERIC = "numeric";
+
+        public static final String TYPE = "type";
+
+        public static final String CURRENT = "current";
+    }
+
+    public static final class Intents {
+        private Intents() {
+            // Not instantiable
+        }
+
+        /**
+         * Broadcast Action: A "secret code" has been entered in the dialer. Secret codes are
+         * of the form *#*#<code>#*#*. The intent will have the data URI:</p>
+         *
+         * <p><code>android_secret_code://&lt;code&gt;</code></p>
+         */
+        public static final String SECRET_CODE_ACTION =
+                "android.provider.Telephony.SECRET_CODE";
+
+        /**
+         * Broadcast Action: The Service Provider string(s) have been updated.  Activities or
+         * services that use these strings should update their display.
+         * The intent will have the following extra values:</p>
+         * <ul>
+         *   <li><em>showPlmn</em> - Boolean that indicates whether the PLMN should be shown.</li>
+         *   <li><em>plmn</em> - The operator name of the registered network, as a string.</li>
+         *   <li><em>showSpn</em> - Boolean that indicates whether the SPN should be shown.</li>
+         *   <li><em>spn</em> - The service provider name, as a string.</li>
+         * </ul>
+         * Note that <em>showPlmn</em> may indicate that <em>plmn</em> should be displayed, even
+         * though the value for <em>plmn</em> is null.  This can happen, for example, if the phone
+         * has not registered to a network yet.  In this case the receiver may substitute an
+         * appropriate placeholder string (eg, "No service").
+         *
+         * It is recommended to display <em>plmn</em> before / above <em>spn</em> if
+         * both are displayed.
+         */
+        public static final String SPN_STRINGS_UPDATED_ACTION =
+                "android.provider.Telephony.SPN_STRINGS_UPDATED";
+
+        public static final String EXTRA_SHOW_PLMN  = "showPlmn";
+        public static final String EXTRA_PLMN       = "plmn";
+        public static final String EXTRA_SHOW_SPN   = "showSpn";
+        public static final String EXTRA_SPN        = "spn";
+    }
+}
+
+
diff --git a/core/java/android/provider/UserDictionary.java b/core/java/android/provider/UserDictionary.java
new file mode 100644
index 0000000..5a7ef85
--- /dev/null
+++ b/core/java/android/provider/UserDictionary.java
@@ -0,0 +1,138 @@
+/*
+ * 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 android.provider;
+
+import java.util.Locale;
+
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.content.Context;
+import android.net.Uri;
+import android.text.TextUtils;
+
+/**
+ * A provider of user defined words for input methods to use for predictive text input.
+ * Applications and input methods may add words into the dictionary. Words can have associated
+ * frequency information and locale information.
+ */
+public class UserDictionary {
+
+    /** Authority string for this provider. */
+    public static final String AUTHORITY = "user_dictionary";
+
+    /**
+     * The content:// style URL for this provider
+     */
+    public static final Uri CONTENT_URI =
+        Uri.parse("content://" + AUTHORITY);
+
+    /**
+     * Contains the user defined words.
+     */
+    public static class Words implements BaseColumns {
+        /**
+         * The content:// style URL for this table
+         */
+        public static final Uri CONTENT_URI =
+                Uri.parse("content://" + AUTHORITY + "/words");
+
+        /**
+         * The MIME type of {@link #CONTENT_URI} providing a directory of words.
+         */
+        public static final String CONTENT_TYPE = "vnd.android.cursor.dir/vnd.google.userword";
+
+        /**
+         * The MIME type of a {@link #CONTENT_URI} sub-directory of a single word.
+         */
+        public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/vnd.google.userword";
+
+        public static final String _ID = BaseColumns._ID;
+
+        /**
+         * The word column.
+         * <p>TYPE: TEXT</p>
+         */
+        public static final String WORD = "word";
+
+        /**
+         * The frequency column. A value between 1 and 255. Higher values imply higher frequency.
+         * <p>TYPE: INTEGER</p>
+         */
+        public static final String FREQUENCY = "frequency";
+
+        /**
+         * The locale that this word belongs to. Null if it pertains to all
+         * locales. Locale is as defined by the string returned by Locale.toString().
+         * <p>TYPE: TEXT</p>
+         */
+        public static final String LOCALE = "locale";
+
+        /**
+         * The uid of the application that inserted the word.
+         * <p>TYPE: INTEGER</p>
+         */
+        public static final String APP_ID = "appid";
+
+        /** The locale type to specify that the word is common to all locales. */
+        public static final int LOCALE_TYPE_ALL = 0;
+        
+        /** The locale type to specify that the word is for the current locale. */
+        public static final int LOCALE_TYPE_CURRENT = 1;
+        
+        /**
+         * Sort by descending order of frequency.
+         */
+        public static final String DEFAULT_SORT_ORDER = FREQUENCY + " DESC";
+
+        /** Adds a word to the dictionary, with the given frequency and the specified
+         *  specified locale type.
+         *  @param context the current application context
+         *  @param word the word to add to the dictionary. This should not be null or
+         *  empty.
+         *  @param localeType the locale type for this word. It should be one of
+         *  {@link #LOCALE_TYPE_ALL} or {@link #LOCALE_TYPE_CURRENT}.
+         */
+        public static void addWord(Context context, String word, 
+                int frequency, int localeType) {
+            final ContentResolver resolver = context.getContentResolver();
+
+            if (TextUtils.isEmpty(word) || localeType < 0 || localeType > 1) {
+                return;
+            }
+            
+            if (frequency < 0) frequency = 0;
+            if (frequency > 255) frequency = 255;
+
+            String locale = null;
+
+            // TODO: Verify if this is the best way to get the current locale
+            if (localeType == LOCALE_TYPE_CURRENT) {
+                locale = Locale.getDefault().toString();
+            }
+            ContentValues values = new ContentValues(4);
+
+            values.put(WORD, word);
+            values.put(FREQUENCY, frequency);
+            values.put(LOCALE, locale);
+            values.put(APP_ID, 0); // TODO: Get App UID
+
+            Uri result = resolver.insert(CONTENT_URI, values);
+            // It's ok if the insert doesn't succeed because the word
+            // already exists.
+        }
+    }
+}
diff --git a/core/java/android/provider/package.html b/core/java/android/provider/package.html
new file mode 100644
index 0000000..055b037
--- /dev/null
+++ b/core/java/android/provider/package.html
@@ -0,0 +1,11 @@
+<HTML>
+<BODY>
+Provides convenience classes to access the content providers supplied by
+Android.
+<p>Android ships with a number of content providers that store common data such
+as contact informations, calendar information, and media files. These classes
+provide simplified methods of adding or retrieving data from these content
+providers. For information about how to use a content provider, see <a
+href="{@docRoot}guide/topics/providers/content-providers.html">Content Providers</a>.
+</BODY>
+</HTML>
diff --git a/core/java/android/security/Md5MessageDigest.java b/core/java/android/security/Md5MessageDigest.java
new file mode 100644
index 0000000..4fe0cb0
--- /dev/null
+++ b/core/java/android/security/Md5MessageDigest.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2007 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 android.security;
+
+/**
+ * Provides the MD5 hash encryption.
+ */
+public class Md5MessageDigest extends MessageDigest
+{
+    // ptr to native context
+    private int mNativeMd5Context;
+    
+    public Md5MessageDigest()
+    {
+        init();
+    }
+    
+    public byte[] digest(byte[] input)
+    {
+        update(input);
+        return digest();
+    }
+
+    private native void init();
+    public native void update(byte[] input);  
+    public native byte[] digest();
+    native public void reset();
+}
diff --git a/core/java/android/security/MessageDigest.java b/core/java/android/security/MessageDigest.java
new file mode 100644
index 0000000..cf2d0fe
--- /dev/null
+++ b/core/java/android/security/MessageDigest.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2007 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 android.security;
+
+import java.security.NoSuchAlgorithmException;
+
+/**
+ * Base class for producing a message digest from different hash encryptions.
+ */
+public abstract class MessageDigest 
+{    
+    /**
+     * Returns a digest object of the specified type.
+     * 
+     * @param algorithm  The type of hash function to use. Valid values are
+     *                   <em>SHA-1</em> and <em>MD5</em>.
+     * @return The respective MessageDigest object. Either a 
+     *         {@link android.security.Sha1MessageDigest} or
+     *         {@link android.security.Md5MessageDigest} object.
+     * @throws NoSuchAlgorithmException If an invalid <var>algorithm</var>
+     *                                  is given.
+     */
+    public static MessageDigest getInstance(String algorithm) 
+        throws NoSuchAlgorithmException
+    {
+        if (algorithm == null) {
+            return null;
+        }
+        
+        if (algorithm.equals("SHA-1")) {
+            return new Sha1MessageDigest();
+        }
+        else if (algorithm.equals("MD5")) {
+            return new Md5MessageDigest();
+        }
+        
+        throw new NoSuchAlgorithmException();
+    }
+    
+    public abstract void update(byte[] input);    
+    public abstract byte[] digest();
+    
+    /**
+     * Produces a message digest for the given input.
+     * 
+     * @param input  The message to encrypt.
+     * @return The digest (hash sum).
+     */
+    public abstract byte[] digest(byte[] input);
+}
diff --git a/core/java/android/security/Sha1MessageDigest.java b/core/java/android/security/Sha1MessageDigest.java
new file mode 100644
index 0000000..aa01fa6
--- /dev/null
+++ b/core/java/android/security/Sha1MessageDigest.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2007 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 android.security;
+
+/**
+ * Provides the SHA-1 hash encyption.
+ */
+public class Sha1MessageDigest extends MessageDigest
+{
+    // ptr to native context
+    private int mNativeSha1Context;
+    
+    public Sha1MessageDigest()
+    {
+        init();
+    }
+    
+    public byte[] digest(byte[] input)
+    {
+        update(input);
+        return digest();
+    }
+
+    private native void init();
+    public native void update(byte[] input);  
+    public native byte[] digest();
+    native public void reset();
+}
diff --git a/core/java/android/security/package.html b/core/java/android/security/package.html
new file mode 100644
index 0000000..dfc6303
--- /dev/null
+++ b/core/java/android/security/package.html
@@ -0,0 +1,6 @@
+<HTML>
+<BODY>
+Utilities for encrypting messages from hash functions.
+{@hide}
+</BODY>
+</HTML>
diff --git a/core/java/android/server/BluetoothA2dpService.java b/core/java/android/server/BluetoothA2dpService.java
new file mode 100644
index 0000000..f8bc765
--- /dev/null
+++ b/core/java/android/server/BluetoothA2dpService.java
@@ -0,0 +1,460 @@
+/*
+ * 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.
+ */
+
+/**
+ * TODO: Move this to services.jar
+ * and make the contructor package private again.
+ * @hide
+ */
+
+package android.server;
+
+import android.bluetooth.BluetoothA2dp;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothError;
+import android.bluetooth.BluetoothIntent;
+import android.bluetooth.IBluetoothA2dp;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.media.AudioManager;
+import android.os.Binder;
+import android.os.Handler;
+import android.os.Message;
+import android.provider.Settings;
+import android.util.Log;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+
+public class BluetoothA2dpService extends IBluetoothA2dp.Stub {
+    private static final String TAG = "BluetoothA2dpService";
+    private static final boolean DBG = true;
+
+    public static final String BLUETOOTH_A2DP_SERVICE = "bluetooth_a2dp";
+
+    private static final String BLUETOOTH_ADMIN_PERM = android.Manifest.permission.BLUETOOTH_ADMIN;
+    private static final String BLUETOOTH_PERM = android.Manifest.permission.BLUETOOTH;
+
+    private static final String A2DP_SINK_ADDRESS = "a2dp_sink_address";
+    private static final String BLUETOOTH_ENABLED = "bluetooth_enabled";
+
+    private static final int MESSAGE_CONNECT_TO = 1;
+
+    private final Context mContext;
+    private final IntentFilter mIntentFilter;
+    private HashMap<String, SinkState> mAudioDevices;
+    private final AudioManager mAudioManager;
+
+    private class SinkState {
+        public String address;
+        public int state;
+        public SinkState(String a, int s) {address = a; state = s;}
+    }
+
+    public BluetoothA2dpService(Context context) {
+        mContext = context;
+
+        mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
+
+        BluetoothDevice device =
+                (BluetoothDevice)mContext.getSystemService(Context.BLUETOOTH_SERVICE);
+        if (device == null) {
+            throw new RuntimeException("Platform does not support Bluetooth");
+        }
+
+        if (!initNative()) {
+            throw new RuntimeException("Could not init BluetoothA2dpService");
+        }
+
+        mIntentFilter = new IntentFilter(BluetoothIntent.ENABLED_ACTION);
+        mIntentFilter.addAction(BluetoothIntent.DISABLED_ACTION);
+        mIntentFilter.addAction(BluetoothIntent.BOND_STATE_CHANGED_ACTION);
+        mIntentFilter.addAction(BluetoothIntent.REMOTE_DEVICE_CONNECTED_ACTION);
+        mContext.registerReceiver(mReceiver, mIntentFilter);
+
+        if (device.isEnabled()) {
+            onBluetoothEnable();
+        }
+    }
+
+    @Override
+    protected void finalize() throws Throwable {
+        try {
+            cleanupNative();
+        } finally {
+            super.finalize();
+        }
+    }
+
+    private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            String action = intent.getAction();
+            String address = intent.getStringExtra(BluetoothIntent.ADDRESS);
+            if (action.equals(BluetoothIntent.ENABLED_ACTION)) {
+                onBluetoothEnable();
+            } else if (action.equals(BluetoothIntent.DISABLED_ACTION)) {
+                onBluetoothDisable();
+            } else if (action.equals(BluetoothIntent.BOND_STATE_CHANGED_ACTION)) {
+                int bondState = intent.getIntExtra(BluetoothIntent.BOND_STATE,
+                                                   BluetoothError.ERROR);
+                switch(bondState) {
+                case BluetoothDevice.BOND_BONDED:
+                    setSinkPriority(address, BluetoothA2dp.PRIORITY_AUTO);
+                    break;
+                case BluetoothDevice.BOND_BONDING:
+                case BluetoothDevice.BOND_NOT_BONDED:
+                    setSinkPriority(address, BluetoothA2dp.PRIORITY_OFF);
+                    break;
+                }
+            } else if (action.equals(BluetoothIntent.REMOTE_DEVICE_CONNECTED_ACTION)) {
+                if (getSinkPriority(address) > BluetoothA2dp.PRIORITY_OFF) {
+                    // This device is a preferred sink. Make an A2DP connection
+                    // after a delay. We delay to avoid connection collisions,
+                    // and to give other profiles such as HFP a chance to
+                    // connect first.
+                    Message msg = Message.obtain(mHandler, MESSAGE_CONNECT_TO, address);
+                    mHandler.sendMessageDelayed(msg, 6000);
+                }
+            }
+        }
+    };
+
+    private final Handler mHandler = new Handler() {
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+            case MESSAGE_CONNECT_TO:
+                String address = (String)msg.obj;
+                // check device is still preferred, and nothing is currently
+                // connected
+                if (getSinkPriority(address) > BluetoothA2dp.PRIORITY_OFF &&
+                        lookupSinksMatchingStates(new int[] {
+                            BluetoothA2dp.STATE_CONNECTING,
+                            BluetoothA2dp.STATE_CONNECTED,
+                            BluetoothA2dp.STATE_PLAYING,
+                            BluetoothA2dp.STATE_DISCONNECTING}).size() == 0) {
+                    log("Auto-connecting A2DP to sink " + address);
+                    connectSink(address);
+                }
+                break;
+            }
+        }
+    };
+
+    private synchronized void onBluetoothEnable() {
+        mAudioDevices = new HashMap<String, SinkState>();
+        String[] paths = (String[])listHeadsetsNative();
+        if (paths != null) {
+            for (String path : paths) {
+                mAudioDevices.put(path, new SinkState(getAddressNative(path),
+                        isSinkConnectedNative(path) ? BluetoothA2dp.STATE_CONNECTED :
+                                                      BluetoothA2dp.STATE_DISCONNECTED));
+            }
+        }
+        mAudioManager.setParameter(BLUETOOTH_ENABLED, "true");
+    }
+
+    private synchronized void onBluetoothDisable() {
+        if (mAudioDevices != null) {
+            // copy to allow modification during iteration
+            String[] paths = new String[mAudioDevices.size()];
+            paths = mAudioDevices.keySet().toArray(paths);
+            for (String path : paths) {
+                switch (mAudioDevices.get(path).state) {
+                    case BluetoothA2dp.STATE_CONNECTING:
+                    case BluetoothA2dp.STATE_CONNECTED:
+                    case BluetoothA2dp.STATE_PLAYING:
+                        disconnectSinkNative(path);
+                        updateState(path, BluetoothA2dp.STATE_DISCONNECTED);
+                        break;
+                    case BluetoothA2dp.STATE_DISCONNECTING:
+                        updateState(path, BluetoothA2dp.STATE_DISCONNECTED);
+                        break;
+                }
+            }
+            mAudioDevices = null;
+        }
+        mAudioManager.setBluetoothA2dpOn(false);
+        mAudioManager.setParameter(BLUETOOTH_ENABLED, "false");
+    }
+
+    public synchronized int connectSink(String address) {
+        mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
+                                                "Need BLUETOOTH_ADMIN permission");
+        if (DBG) log("connectSink(" + address + ")");
+        if (!BluetoothDevice.checkBluetoothAddress(address)) {
+            return BluetoothError.ERROR;
+        }
+        if (mAudioDevices == null) {
+            return BluetoothError.ERROR;
+        }
+        String path = lookupPath(address);
+        if (path == null) {
+            path = createHeadsetNative(address);
+            if (DBG) log("new bluez sink: " + address + " (" + path + ")");
+        }
+        if (path == null) {
+            return BluetoothError.ERROR;
+        }
+
+        SinkState sink = mAudioDevices.get(path);
+        int state = BluetoothA2dp.STATE_DISCONNECTED;
+        if (sink != null) {
+            state = sink.state;
+        }
+        switch (state) {
+        case BluetoothA2dp.STATE_CONNECTED:
+        case BluetoothA2dp.STATE_PLAYING:
+        case BluetoothA2dp.STATE_DISCONNECTING:
+            return BluetoothError.ERROR;
+        case BluetoothA2dp.STATE_CONNECTING:
+            return BluetoothError.SUCCESS;
+        }
+
+        // State is DISCONNECTED
+        if (!connectSinkNative(path)) {
+            return BluetoothError.ERROR;
+        }
+        updateState(path, BluetoothA2dp.STATE_CONNECTING);
+        return BluetoothError.SUCCESS;
+    }
+
+    public synchronized int disconnectSink(String address) {
+        mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
+                                                "Need BLUETOOTH_ADMIN permission");
+        if (DBG) log("disconnectSink(" + address + ")");
+        if (!BluetoothDevice.checkBluetoothAddress(address)) {
+            return BluetoothError.ERROR;
+        }
+        if (mAudioDevices == null) {
+            return BluetoothError.ERROR;
+        }
+        String path = lookupPath(address);
+        if (path == null) {
+            return BluetoothError.ERROR;
+        }
+        switch (mAudioDevices.get(path).state) {
+        case BluetoothA2dp.STATE_DISCONNECTED:
+            return BluetoothError.ERROR;
+        case BluetoothA2dp.STATE_DISCONNECTING:
+            return BluetoothError.SUCCESS;
+        }
+
+        // State is CONNECTING or CONNECTED or PLAYING
+        if (!disconnectSinkNative(path)) {
+            return BluetoothError.ERROR;
+        } else {
+            updateState(path, BluetoothA2dp.STATE_DISCONNECTING);
+            return BluetoothError.SUCCESS;
+        }
+    }
+
+    public synchronized List<String> listConnectedSinks() {
+        mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+        return lookupSinksMatchingStates(new int[] {BluetoothA2dp.STATE_CONNECTED,
+                                                    BluetoothA2dp.STATE_PLAYING});
+    }
+
+    public synchronized int getSinkState(String address) {
+        mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+        if (!BluetoothDevice.checkBluetoothAddress(address)) {
+            return BluetoothError.ERROR;
+        }
+        if (mAudioDevices == null) {
+            return BluetoothA2dp.STATE_DISCONNECTED;
+        }
+        for (SinkState sink : mAudioDevices.values()) {
+            if (address.equals(sink.address)) {
+                return sink.state;
+            }
+        }
+        return BluetoothA2dp.STATE_DISCONNECTED;
+    }
+
+    public synchronized int getSinkPriority(String address) {
+        mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+        if (!BluetoothDevice.checkBluetoothAddress(address)) {
+            return BluetoothError.ERROR;
+        }
+        return Settings.Secure.getInt(mContext.getContentResolver(),
+                Settings.Secure.getBluetoothA2dpSinkPriorityKey(address),
+                BluetoothA2dp.PRIORITY_OFF);
+    }
+
+    public synchronized int setSinkPriority(String address, int priority) {
+        mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
+                                                "Need BLUETOOTH_ADMIN permission");
+        if (!BluetoothDevice.checkBluetoothAddress(address)) {
+            return BluetoothError.ERROR;
+        }
+        return Settings.Secure.putInt(mContext.getContentResolver(),
+                Settings.Secure.getBluetoothA2dpSinkPriorityKey(address), priority) ?
+                BluetoothError.SUCCESS : BluetoothError.ERROR;
+    }
+
+    private synchronized void onHeadsetCreated(String path) {
+        updateState(path, BluetoothA2dp.STATE_DISCONNECTED);
+    }
+
+    private synchronized void onHeadsetRemoved(String path) {
+        if (mAudioDevices == null) return;
+        mAudioDevices.remove(path);
+    }
+
+    private synchronized void onSinkConnected(String path) {
+        if (mAudioDevices == null) return;
+        // bluez 3.36 quietly disconnects the previous sink when a new sink
+        // is connected, so we need to mark all previously connected sinks as
+        // disconnected
+
+        // copy to allow modification during iteration
+        String[] paths = new String[mAudioDevices.size()];
+        paths = mAudioDevices.keySet().toArray(paths);
+        for (String oldPath : paths) {
+            if (path.equals(oldPath)) {
+                continue;
+            }
+            int state = mAudioDevices.get(oldPath).state;
+            if (state == BluetoothA2dp.STATE_CONNECTED || state == BluetoothA2dp.STATE_PLAYING) {
+                updateState(path, BluetoothA2dp.STATE_DISCONNECTED);
+            }
+        }
+
+        updateState(path, BluetoothA2dp.STATE_CONNECTING);
+        mAudioManager.setParameter(A2DP_SINK_ADDRESS, lookupAddress(path));
+        mAudioManager.setBluetoothA2dpOn(true);
+        updateState(path, BluetoothA2dp.STATE_CONNECTED);
+    }
+
+    private synchronized void onSinkDisconnected(String path) {
+        mAudioManager.setBluetoothA2dpOn(false);
+        updateState(path, BluetoothA2dp.STATE_DISCONNECTED);
+    }
+
+    private synchronized void onSinkPlaying(String path) {
+        updateState(path, BluetoothA2dp.STATE_PLAYING);
+    }
+
+    private synchronized void onSinkStopped(String path) {
+        updateState(path, BluetoothA2dp.STATE_CONNECTED);
+    }
+
+    private synchronized final String lookupAddress(String path) {
+        if (mAudioDevices == null) return null;
+        SinkState sink = mAudioDevices.get(path);
+        if (sink == null) {
+            Log.w(TAG, "lookupAddress() called for unknown device " + path);
+            updateState(path, BluetoothA2dp.STATE_DISCONNECTED);
+        }
+        String address = mAudioDevices.get(path).address;
+        if (address == null) Log.e(TAG, "Can't find address for " + path);
+        return address;
+    }
+
+    private synchronized final String lookupPath(String address) {
+        if (mAudioDevices == null) return null;
+
+        for (String path : mAudioDevices.keySet()) {
+            if (address.equals(mAudioDevices.get(path).address)) {
+                return path;
+            }
+        }
+        return null;
+    }
+
+    private synchronized List<String> lookupSinksMatchingStates(int[] states) {
+        List<String> sinks = new ArrayList<String>();
+        if (mAudioDevices == null) {
+            return sinks;
+        }
+        for (SinkState sink : mAudioDevices.values()) {
+            for (int state : states) {
+                if (sink.state == state) {
+                    sinks.add(sink.address);
+                    break;
+                }
+            }
+        }
+        return sinks;
+    }
+
+    private synchronized void updateState(String path, int state) {
+        if (mAudioDevices == null) return;
+
+        SinkState s = mAudioDevices.get(path);
+        int prevState;
+        String address;
+        if (s == null) {
+            address = getAddressNative(path);
+            mAudioDevices.put(path, new SinkState(address, state));
+            prevState = BluetoothA2dp.STATE_DISCONNECTED;
+        } else {
+            address = lookupAddress(path);
+            prevState = s.state;
+            s.state = state;
+        }
+
+        if (state != prevState) {
+            if (DBG) log("state " + address + " (" + path + ") " + prevState + "->" + state);
+
+            Intent intent = new Intent(BluetoothA2dp.SINK_STATE_CHANGED_ACTION);
+            intent.putExtra(BluetoothIntent.ADDRESS, address);
+            intent.putExtra(BluetoothA2dp.SINK_PREVIOUS_STATE, prevState);
+            intent.putExtra(BluetoothA2dp.SINK_STATE, state);
+            mContext.sendBroadcast(intent, BLUETOOTH_PERM);
+
+            if ((prevState == BluetoothA2dp.STATE_CONNECTED ||
+                 prevState == BluetoothA2dp.STATE_PLAYING) &&
+                    (state != BluetoothA2dp.STATE_CONNECTED &&
+                     state != BluetoothA2dp.STATE_PLAYING)) {
+                // disconnected
+                intent = new Intent(AudioManager.ACTION_AUDIO_BECOMING_NOISY);
+                mContext.sendBroadcast(intent);
+            }
+        }
+    }
+
+    @Override
+    protected synchronized void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        if (mAudioDevices == null) return;
+        pw.println("Cached audio devices:");
+        for (String path : mAudioDevices.keySet()) {
+            SinkState sink = mAudioDevices.get(path);
+            pw.println(path + " " + sink.address + " " + BluetoothA2dp.stateToString(sink.state));
+        }
+    }
+
+    private static void log(String msg) {
+        Log.d(TAG, msg);
+    }
+
+    private native boolean initNative();
+    private native void cleanupNative();
+    private synchronized native String[] listHeadsetsNative();
+    private synchronized native String createHeadsetNative(String address);
+    private synchronized native boolean removeHeadsetNative(String path);
+    private synchronized native String getAddressNative(String path);
+    private synchronized native boolean connectSinkNative(String path);
+    private synchronized native boolean disconnectSinkNative(String path);
+    private synchronized native boolean isSinkConnectedNative(String path);
+}
diff --git a/core/java/android/server/BluetoothDeviceService.java b/core/java/android/server/BluetoothDeviceService.java
new file mode 100644
index 0000000..950ff3a
--- /dev/null
+++ b/core/java/android/server/BluetoothDeviceService.java
@@ -0,0 +1,1129 @@
+/*
+ * 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.
+ */
+
+/**
+ * TODO: Move this to
+ * java/services/com/android/server/BluetoothDeviceService.java
+ * and make the contructor package private again.
+ *
+ * @hide
+ */
+
+package android.server;
+
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothError;
+import android.bluetooth.BluetoothHeadset;
+import android.bluetooth.BluetoothIntent;
+import android.bluetooth.IBluetoothDevice;
+import android.bluetooth.IBluetoothDeviceCallback;
+import android.content.BroadcastReceiver;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Binder;
+import android.os.Handler;
+import android.os.Message;
+import android.os.RemoteException;
+import android.os.SystemService;
+import android.provider.Settings;
+import android.util.Log;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.io.UnsupportedEncodingException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+
+public class BluetoothDeviceService extends IBluetoothDevice.Stub {
+    private static final String TAG = "BluetoothDeviceService";
+    private static final boolean DBG = true;
+
+    private int mNativeData;
+    private BluetoothEventLoop mEventLoop;
+    private IntentFilter mIntentFilter;
+    private boolean mIsAirplaneSensitive;
+    private final BondState mBondState = new BondState();  // local cache of bondings
+    private volatile boolean mIsEnabled;  // local cache of isEnabledNative()
+    private boolean mIsDiscovering;
+
+    private final Context mContext;
+
+    private static final String BLUETOOTH_ADMIN_PERM = android.Manifest.permission.BLUETOOTH_ADMIN;
+    private static final String BLUETOOTH_PERM = android.Manifest.permission.BLUETOOTH;
+
+    static {
+        classInitNative();
+    }
+    private native static void classInitNative();
+
+    public BluetoothDeviceService(Context context) {
+        mContext = context;
+    }
+
+    /** Must be called after construction, and before any other method.
+     */
+    public synchronized void init() {
+        initializeNativeDataNative();
+        mIsEnabled = (isEnabledNative() == 1);
+        if (mIsEnabled) {
+            mBondState.loadBondState();
+        }
+        mIsDiscovering = false;
+        mEventLoop = new BluetoothEventLoop(mContext, this);
+        registerForAirplaneMode();
+    }
+    private native void initializeNativeDataNative();
+
+    @Override
+    protected void finalize() throws Throwable {
+        if (mIsAirplaneSensitive) {
+            mContext.unregisterReceiver(mReceiver);
+        }
+        try {
+            cleanupNativeDataNative();
+        } finally {
+            super.finalize();
+        }
+    }
+    private native void cleanupNativeDataNative();
+
+    public boolean isEnabled() {
+        mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+        return mIsEnabled;
+    }
+    private native int isEnabledNative();
+
+    /**
+     * Bring down bluetooth and disable BT in settings. Returns true on success.
+     */
+    public boolean disable() {
+        return disable(true);
+    }
+
+    /**
+     * Bring down bluetooth. Returns true on success.
+     *
+     * @param saveSetting If true, disable BT in settings
+     *
+     */
+    public synchronized boolean disable(boolean saveSetting) {
+        mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
+                                                "Need BLUETOOTH_ADMIN permission");
+
+        if (mEnableThread != null && mEnableThread.isAlive()) {
+            return false;
+        }
+        if (!mIsEnabled) {
+            return true;
+        }
+        mEventLoop.stop();
+        disableNative();
+
+        // mark in progress bondings as cancelled
+        for (String address : mBondState.listInState(BluetoothDevice.BOND_BONDING)) {
+            mBondState.setBondState(address, BluetoothDevice.BOND_NOT_BONDED,
+                                    BluetoothDevice.UNBOND_REASON_AUTH_CANCELED);
+        }
+
+        // Remove remoteServiceChannelCallbacks
+        HashMap<String, IBluetoothDeviceCallback> callbacksMap =
+            mEventLoop.getRemoteServiceChannelCallbacks();
+        IBluetoothDeviceCallback callback;
+
+        for (String address : callbacksMap.keySet()) {
+            callback = callbacksMap.get(address);
+            try {
+                callback.onGetRemoteServiceChannelResult(address, BluetoothError.ERROR_DISABLED);
+            } catch (RemoteException e) {}
+            callbacksMap.remove(address);
+        }
+
+        // update mode
+        Intent intent = new Intent(BluetoothIntent.SCAN_MODE_CHANGED_ACTION);
+        intent.putExtra(BluetoothIntent.SCAN_MODE, BluetoothDevice.SCAN_MODE_NONE);
+        mContext.sendBroadcast(intent, BLUETOOTH_PERM);
+
+        mIsEnabled = false;
+        if (saveSetting) {
+            persistBluetoothOnSetting(false);
+        }
+        mIsDiscovering = false;
+        intent = new Intent(BluetoothIntent.DISABLED_ACTION);
+        mContext.sendBroadcast(intent, BLUETOOTH_PERM);
+        return true;
+    }
+
+    /**
+     * Bring up bluetooth, asynchronously, and enable BT in settings.
+     * This turns on/off the underlying hardware.
+     *
+     * @return True on success (so far), guaranteeing the callback with be
+     * notified when complete.
+     */
+    public boolean enable(IBluetoothDeviceCallback callback) {
+        return enable(callback, true);
+    }
+
+    /**
+     * Enable this Bluetooth device, asynchronously.
+     * This turns on/off the underlying hardware.
+     *
+     * @param saveSetting If true, enable BT in settings
+     *
+     * @return True on success (so far), guaranteeing the callback with be
+     * notified when complete.
+     */
+    public synchronized boolean enable(IBluetoothDeviceCallback callback,
+            boolean saveSetting) {
+        mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
+                                                "Need BLUETOOTH_ADMIN permission");
+
+        // Airplane mode can prevent Bluetooth radio from being turned on.
+        if (mIsAirplaneSensitive && isAirplaneModeOn()) {
+            return false;
+        }
+        if (mIsEnabled) {
+            return false;
+        }
+        if (mEnableThread != null && mEnableThread.isAlive()) {
+            return false;
+        }
+        mEnableThread = new EnableThread(callback, saveSetting);
+        mEnableThread.start();
+        return true;
+    }
+
+    private static final int REGISTER_SDP_RECORDS = 1;
+    private final Handler mHandler = new Handler() {
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+            case REGISTER_SDP_RECORDS:
+                //TODO: Don't assume HSP/HFP is running, don't use sdptool,
+                if (isEnabled()) {
+                    SystemService.start("hsag");
+                    SystemService.start("hfag");
+                }
+            }
+        }
+    };
+
+    private EnableThread mEnableThread;
+
+    private class EnableThread extends Thread {
+        private final IBluetoothDeviceCallback mEnableCallback;
+        private final boolean mSaveSetting;
+        public EnableThread(IBluetoothDeviceCallback callback, boolean saveSetting) {
+            mEnableCallback = callback;
+            mSaveSetting = saveSetting;
+        }
+        public void run() {
+            boolean res = (enableNative() == 0);
+            if (res) {
+                mEventLoop.start();
+            }
+
+            if (mEnableCallback != null) {
+                try {
+                    mEnableCallback.onEnableResult(res ?
+                                                   BluetoothDevice.RESULT_SUCCESS :
+                                                   BluetoothDevice.RESULT_FAILURE);
+                } catch (RemoteException e) {}
+            }
+
+            if (res) {
+                mIsEnabled = true;
+                if (mSaveSetting) {
+                    persistBluetoothOnSetting(true);
+                }
+                mIsDiscovering = false;
+                Intent intent = new Intent(BluetoothIntent.ENABLED_ACTION);
+                mBondState.loadBondState();
+                mContext.sendBroadcast(intent, BLUETOOTH_PERM);
+                mHandler.sendMessageDelayed(mHandler.obtainMessage(REGISTER_SDP_RECORDS), 3000);
+
+                // Update mode
+                mEventLoop.onModeChanged(getModeNative());
+            }
+            mEnableThread = null;
+        }
+    }
+
+    private void persistBluetoothOnSetting(boolean bluetoothOn) {
+        long origCallerIdentityToken = Binder.clearCallingIdentity();
+        Settings.Secure.putInt(mContext.getContentResolver(), Settings.Secure.BLUETOOTH_ON,
+                bluetoothOn ? 1 : 0);
+        Binder.restoreCallingIdentity(origCallerIdentityToken);
+    }
+
+    private native int enableNative();
+    private native int disableNative();
+
+    /* package */ BondState getBondState() {
+        return mBondState;
+    }
+
+    /** local cache of bonding state.
+    /* we keep our own state to track the intermediate state BONDING, which
+    /* bluez does not track.
+     * All addreses must be passed in upper case.
+     */
+    public class BondState {
+        private final HashMap<String, Integer> mState = new HashMap<String, Integer>();
+        private final HashMap<String, Integer> mPinAttempt = new HashMap<String, Integer>();
+        private final ArrayList<String> mAutoPairingFailures = new ArrayList<String>();
+        // List of all the vendor_id prefix of Bluetooth addresses for which
+        // auto pairing is not attempted
+        private final ArrayList<String>  mAutoPairingBlacklisted =
+                new ArrayList<String>(Arrays.asList(
+                        "00:02:C7", "00:16:FE", "00:19:C1", "00:1B:FB", "00:1E:3D", //ALPS
+                        "00:21:4F", "00:23:06", "00:24:33", "00:A0:79", // ALPS
+                        "00:0E:6D", "00:13:E0", "00:21:E8", "00:60:57"// Murata for Prius 2007
+                        ));
+
+        public synchronized void loadBondState() {
+            if (!mIsEnabled) {
+                return;
+            }
+            String[] bonds = listBondingsNative();
+            if (bonds == null) {
+                return;
+            }
+            mState.clear();
+            if (DBG) log("found " + bonds.length + " bonded devices");
+            for (String address : bonds) {
+                mState.put(address.toUpperCase(), BluetoothDevice.BOND_BONDED);
+            }
+        }
+
+        public synchronized void setBondState(String address, int state) {
+            setBondState(address, state, 0);
+        }
+
+        /** reason is ignored unless state == BOND_NOT_BONDED */
+        public synchronized void setBondState(String address, int state, int reason) {
+            int oldState = getBondState(address);
+            if (oldState == state) {
+                return;
+            }
+            if (DBG) log(address + " bond state " + oldState + " -> " + state + " (" +
+                         reason + ")");
+            Intent intent = new Intent(BluetoothIntent.BOND_STATE_CHANGED_ACTION);
+            intent.putExtra(BluetoothIntent.ADDRESS, address);
+            intent.putExtra(BluetoothIntent.BOND_STATE, state);
+            intent.putExtra(BluetoothIntent.BOND_PREVIOUS_STATE, oldState);
+            if (state == BluetoothDevice.BOND_NOT_BONDED) {
+                if (reason <= 0) {
+                    Log.w(TAG, "setBondState() called to unbond device, but reason code is " +
+                          "invalid. Overriding reason code with BOND_RESULT_REMOVED");
+                    reason = BluetoothDevice.UNBOND_REASON_REMOVED;
+                }
+                intent.putExtra(BluetoothIntent.REASON, reason);
+                mState.remove(address);
+            } else {
+                mState.put(address, state);
+            }
+
+            mContext.sendBroadcast(intent, BLUETOOTH_PERM);
+        }
+
+        public boolean isAutoPairingBlacklisted(String address) {
+            for (String blacklistAddress : mAutoPairingBlacklisted) {
+                if (address.startsWith(blacklistAddress)) return true;
+            }
+            return false;
+        }
+
+        public synchronized int getBondState(String address) {
+            Integer state = mState.get(address);
+            if (state == null) {
+                return BluetoothDevice.BOND_NOT_BONDED;
+            }
+            return state.intValue();
+        }
+
+        private synchronized String[] listInState(int state) {
+            ArrayList<String> result = new ArrayList<String>(mState.size());
+            for (Map.Entry<String, Integer> e : mState.entrySet()) {
+                if (e.getValue().intValue() == state) {
+                    result.add(e.getKey());
+                }
+            }
+            return result.toArray(new String[result.size()]);
+        }
+
+        public synchronized void addAutoPairingFailure(String address) {
+            if (!mAutoPairingFailures.contains(address)) {
+                mAutoPairingFailures.add(address);
+            }
+        }
+
+        public synchronized boolean isAutoPairingAttemptsInProgress(String address) {
+            return getAttempt(address) != 0;
+        }
+
+        public synchronized void clearPinAttempts(String address) {
+            mPinAttempt.remove(address);
+        }
+
+        public synchronized boolean hasAutoPairingFailed(String address) {
+            return mAutoPairingFailures.contains(address);
+        }
+
+        public synchronized int getAttempt(String address) {
+            Integer attempt = mPinAttempt.get(address);
+            if (attempt == null) {
+                return 0;
+            }
+            return attempt.intValue();
+        }
+
+        public synchronized void attempt(String address) {
+            Integer attempt = mPinAttempt.get(address);
+            int newAttempt;
+            if (attempt == null) {
+                newAttempt = 1;
+            } else {
+                newAttempt = attempt.intValue() + 1;
+            }
+            mPinAttempt.put(address, new Integer(newAttempt));
+        }
+
+    }
+    private native String[] listBondingsNative();
+
+    private static String toBondStateString(int bondState) {
+        switch (bondState) {
+        case BluetoothDevice.BOND_NOT_BONDED:
+            return "not bonded";
+        case BluetoothDevice.BOND_BONDING:
+            return "bonding";
+        case BluetoothDevice.BOND_BONDED:
+            return "bonded";
+        default:
+            return "??????";
+        }
+    }
+
+    public synchronized String getAddress() {
+        mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+        return getAddressNative();
+    }
+    private native String getAddressNative();
+
+    public synchronized String getName() {
+        mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+        return getNameNative();
+    }
+    private native String getNameNative();
+
+    public synchronized boolean setName(String name) {
+        mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
+                                                "Need BLUETOOTH_ADMIN permission");
+        if (name == null) {
+            return false;
+        }
+        // hcid handles persistance of the bluetooth name
+        return setNameNative(name);
+    }
+    private native boolean setNameNative(String name);
+
+    /**
+     * Returns the user-friendly name of a remote device.  This value is
+     * retrned from our local cache, which is updated during device discovery.
+     * Do not expect to retrieve the updated remote name immediately after
+     * changing the name on the remote device.
+     *
+     * @param address Bluetooth address of remote device.
+     *
+     * @return The user-friendly name of the specified remote device.
+     */
+    public synchronized String getRemoteName(String address) {
+        mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+        if (!BluetoothDevice.checkBluetoothAddress(address)) {
+            return null;
+        }
+        return getRemoteNameNative(address);
+    }
+    private native String getRemoteNameNative(String address);
+
+    /* pacakge */ native String getAdapterPathNative();
+
+    public synchronized boolean startDiscovery(boolean resolveNames) {
+        mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
+                                                "Need BLUETOOTH_ADMIN permission");
+        return startDiscoveryNative(resolveNames);
+    }
+    private native boolean startDiscoveryNative(boolean resolveNames);
+
+    public synchronized boolean cancelDiscovery() {
+        mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
+                                                "Need BLUETOOTH_ADMIN permission");
+        return cancelDiscoveryNative();
+    }
+    private native boolean cancelDiscoveryNative();
+
+    public synchronized boolean isDiscovering() {
+        mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+        return mIsDiscovering;
+    }
+
+    /* package */ void setIsDiscovering(boolean isDiscovering) {
+        mIsDiscovering = isDiscovering;
+    }
+
+    public synchronized boolean startPeriodicDiscovery() {
+        mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
+                                                "Need BLUETOOTH_ADMIN permission");
+        return startPeriodicDiscoveryNative();
+    }
+    private native boolean startPeriodicDiscoveryNative();
+
+    public synchronized boolean stopPeriodicDiscovery() {
+        mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
+                                                "Need BLUETOOTH_ADMIN permission");
+        return stopPeriodicDiscoveryNative();
+    }
+    private native boolean stopPeriodicDiscoveryNative();
+
+    public synchronized boolean isPeriodicDiscovery() {
+        mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+        return isPeriodicDiscoveryNative();
+    }
+    private native boolean isPeriodicDiscoveryNative();
+
+    /**
+     * Set the discoverability window for the device.  A timeout of zero
+     * makes the device permanently discoverable (if the device is
+     * discoverable).  Setting the timeout to a nonzero value does not make
+     * a device discoverable; you need to call setMode() to make the device
+     * explicitly discoverable.
+     *
+     * @param timeout_s The discoverable timeout in seconds.
+     */
+    public synchronized boolean setDiscoverableTimeout(int timeout) {
+        mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
+                                                "Need BLUETOOTH_ADMIN permission");
+        return setDiscoverableTimeoutNative(timeout);
+    }
+    private native boolean setDiscoverableTimeoutNative(int timeout_s);
+
+    /**
+     * Get the discoverability window for the device.  A timeout of zero
+     * means that the device is permanently discoverable (if the device is
+     * in the discoverable mode).
+     *
+     * @return The discoverability window of the device, in seconds.  A negative
+     *         value indicates an error.
+     */
+    public synchronized int getDiscoverableTimeout() {
+        mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+        return getDiscoverableTimeoutNative();
+    }
+    private native int getDiscoverableTimeoutNative();
+
+    public synchronized boolean isAclConnected(String address) {
+        mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+        if (!BluetoothDevice.checkBluetoothAddress(address)) {
+            return false;
+        }
+        return isConnectedNative(address);
+    }
+    private native boolean isConnectedNative(String address);
+
+    public synchronized int getScanMode() {
+        mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+        return bluezStringToScanMode(getModeNative());
+    }
+    private native String getModeNative();
+
+    public synchronized boolean setScanMode(int mode) {
+        mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
+                                                "Need BLUETOOTH_ADMIN permission");
+        String bluezMode = scanModeToBluezString(mode);
+        if (bluezMode != null) {
+            return setModeNative(bluezMode);
+        }
+        return false;
+    }
+    private native boolean setModeNative(String mode);
+
+    public synchronized boolean disconnectRemoteDeviceAcl(String address) {
+        mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
+                                                "Need BLUETOOTH_ADMIN permission");
+        if (!BluetoothDevice.checkBluetoothAddress(address)) {
+            return false;
+        }
+        return disconnectRemoteDeviceNative(address);
+    }
+    private native boolean disconnectRemoteDeviceNative(String address);
+
+    public synchronized boolean createBond(String address) {
+        mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
+                                                "Need BLUETOOTH_ADMIN permission");
+        if (!BluetoothDevice.checkBluetoothAddress(address)) {
+            return false;
+        }
+        address = address.toUpperCase();
+
+        String[] bonding = mBondState.listInState(BluetoothDevice.BOND_BONDING);
+        if (bonding.length > 0 && !bonding[0].equals(address)) {
+            log("Ignoring createBond(): another device is bonding");
+            // a different device is currently bonding, fail
+            return false;
+        }
+
+        // Check for bond state only if we are not performing auto
+        // pairing exponential back-off attempts.
+        if (!mBondState.isAutoPairingAttemptsInProgress(address) &&
+                mBondState.getBondState(address) != BluetoothDevice.BOND_NOT_BONDED) {
+            log("Ignoring createBond(): this device is already bonding or bonded");
+            return false;
+        }
+
+        if (!createBondingNative(address, 60000 /* 1 minute */)) {
+            return false;
+        }
+
+        mBondState.setBondState(address, BluetoothDevice.BOND_BONDING);
+        return true;
+    }
+    private native boolean createBondingNative(String address, int timeout_ms);
+
+    public synchronized boolean cancelBondProcess(String address) {
+        mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
+                                                "Need BLUETOOTH_ADMIN permission");
+        if (!BluetoothDevice.checkBluetoothAddress(address)) {
+            return false;
+        }
+        address = address.toUpperCase();
+        if (mBondState.getBondState(address) != BluetoothDevice.BOND_BONDING) {
+            return false;
+        }
+
+        mBondState.setBondState(address, BluetoothDevice.BOND_NOT_BONDED,
+                                BluetoothDevice.UNBOND_REASON_AUTH_CANCELED);
+        cancelBondingProcessNative(address);
+        return true;
+    }
+    private native boolean cancelBondingProcessNative(String address);
+
+    public synchronized boolean removeBond(String address) {
+        mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
+                                                "Need BLUETOOTH_ADMIN permission");
+        if (!BluetoothDevice.checkBluetoothAddress(address)) {
+            return false;
+        }
+        return removeBondingNative(address);
+    }
+    private native boolean removeBondingNative(String address);
+
+    public synchronized String[] listBonds() {
+        mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+        return mBondState.listInState(BluetoothDevice.BOND_BONDED);
+    }
+
+    public synchronized int getBondState(String address) {
+        mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+        if (!BluetoothDevice.checkBluetoothAddress(address)) {
+            return BluetoothError.ERROR;
+        }
+        return mBondState.getBondState(address.toUpperCase());
+    }
+
+    public synchronized String[] listAclConnections() {
+        mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+        return listConnectionsNative();
+    }
+    private native String[] listConnectionsNative();
+
+    /**
+     * This method lists all remote devices that this adapter is aware of.
+     * This is a list not only of all most-recently discovered devices, but of
+     * all devices discovered by this adapter up to some point in the past.
+     * Note that many of these devices may not be in the neighborhood anymore,
+     * and attempting to connect to them will result in an error.
+     *
+     * @return An array of strings representing the Bluetooth addresses of all
+     *         remote devices that this adapter is aware of.
+     */
+    public synchronized String[] listRemoteDevices() {
+        mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+        return listRemoteDevicesNative();
+    }
+    private native String[] listRemoteDevicesNative();
+
+    /**
+     * Returns the version of the Bluetooth chip. This version is compiled from
+     * the LMP version. In case of EDR the features attribute must be checked.
+     * Example: "Bluetooth 2.0 + EDR".
+     *
+     * @return a String representation of the this Adapter's underlying
+     *         Bluetooth-chip version.
+     */
+    public synchronized String getVersion() {
+        mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+        return getVersionNative();
+    }
+    private native String getVersionNative();
+
+    /**
+     * Returns the revision of the Bluetooth chip. This is a vendor-specific
+     * value and in most cases it represents the firmware version. This might
+     * derive from the HCI revision and LMP subversion values or via extra
+     * vendord specific commands.
+     * In case the revision of a chip is not available. This method should
+     * return the LMP subversion value as a string.
+     * Example: "HCI 19.2"
+     *
+     * @return The HCI revision of this adapter.
+     */
+    public synchronized String getRevision() {
+        mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+        return getRevisionNative();
+    }
+    private native String getRevisionNative();
+
+    /**
+     * Returns the manufacturer of the Bluetooth chip. If the company id is not
+     * known the sting "Company ID %d" where %d should be replaced with the
+     * numeric value from the manufacturer field.
+     * Example: "Cambridge Silicon Radio"
+     *
+     * @return Manufacturer name.
+     */
+    public synchronized String getManufacturer() {
+        mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+        return getManufacturerNative();
+    }
+    private native String getManufacturerNative();
+
+    /**
+     * Returns the company name from the OUI database of the Bluetooth device
+     * address. This function will need a valid and up-to-date oui.txt from
+     * the IEEE. This value will be different from the manufacturer string in
+     * the most cases.
+     * If the oui.txt file is not present or the OUI part of the Bluetooth
+     * address is not listed, it should return the string "OUI %s" where %s is
+     * the actual OUI.
+     *
+     * Example: "Apple Computer"
+     *
+     * @return company name
+     */
+    public synchronized String getCompany() {
+        mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+        return getCompanyNative();
+    }
+    private native String getCompanyNative();
+
+    /**
+     * Like getVersion(), but for a remote device.
+     *
+     * @param address The Bluetooth address of the remote device.
+     *
+     * @return remote-device Bluetooth version
+     *
+     * @see #getVersion
+     */
+    public synchronized String getRemoteVersion(String address) {
+        mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+        if (!BluetoothDevice.checkBluetoothAddress(address)) {
+            return null;
+        }
+        return getRemoteVersionNative(address);
+    }
+    private native String getRemoteVersionNative(String address);
+
+    /**
+     * Like getRevision(), but for a remote device.
+     *
+     * @param address The Bluetooth address of the remote device.
+     *
+     * @return remote-device HCI revision
+     *
+     * @see #getRevision
+     */
+    public synchronized String getRemoteRevision(String address) {
+        mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+        if (!BluetoothDevice.checkBluetoothAddress(address)) {
+            return null;
+        }
+        return getRemoteRevisionNative(address);
+    }
+    private native String getRemoteRevisionNative(String address);
+
+    /**
+     * Like getManufacturer(), but for a remote device.
+     *
+     * @param address The Bluetooth address of the remote device.
+     *
+     * @return remote-device Bluetooth chip manufacturer
+     *
+     * @see #getManufacturer
+     */
+    public synchronized String getRemoteManufacturer(String address) {
+        mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+        if (!BluetoothDevice.checkBluetoothAddress(address)) {
+            return null;
+        }
+        return getRemoteManufacturerNative(address);
+    }
+    private native String getRemoteManufacturerNative(String address);
+
+    /**
+     * Like getCompany(), but for a remote device.
+     *
+     * @param address The Bluetooth address of the remote device.
+     *
+     * @return remote-device company
+     *
+     * @see #getCompany
+     */
+    public synchronized String getRemoteCompany(String address) {
+        mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+        if (!BluetoothDevice.checkBluetoothAddress(address)) {
+            return null;
+        }
+        return getRemoteCompanyNative(address);
+    }
+    private native String getRemoteCompanyNative(String address);
+
+    /**
+     * Returns the date and time when the specified remote device has been seen
+     * by a discover procedure.
+     * Example: "2006-02-08 12:00:00 GMT"
+     *
+     * @return a String with the timestamp.
+     */
+    public synchronized String lastSeen(String address) {
+        mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+        if (!BluetoothDevice.checkBluetoothAddress(address)) {
+            return null;
+        }
+        return lastSeenNative(address);
+    }
+    private native String lastSeenNative(String address);
+
+    /**
+     * Returns the date and time when the specified remote device has last been
+     * connected to
+     * Example: "2006-02-08 12:00:00 GMT"
+     *
+     * @return a String with the timestamp.
+     */
+    public synchronized String lastUsed(String address) {
+        mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+        if (!BluetoothDevice.checkBluetoothAddress(address)) {
+            return null;
+        }
+        return lastUsedNative(address);
+    }
+    private native String lastUsedNative(String address);
+
+    /**
+     * Gets the remote major, minor, and service classes encoded as a 32-bit
+     * integer.
+     *
+     * Note: this value is retrieved from cache, because we get it during
+     *       remote-device discovery.
+     *
+     * @return 32-bit integer encoding the remote major, minor, and service
+     *         classes.
+     *
+     * @see #getRemoteMajorClass
+     * @see #getRemoteMinorClass
+     * @see #getRemoteServiceClasses
+     */
+    public synchronized int getRemoteClass(String address) {
+        if (!BluetoothDevice.checkBluetoothAddress(address)) {
+        mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+            return -1;
+        }
+        return getRemoteClassNative(address);
+    }
+    private native int getRemoteClassNative(String address);
+
+    /**
+     * Gets the remote features encoded as bit mask.
+     *
+     * Note: This method may be obsoleted soon.
+     *
+     * @return byte array of features.
+     */
+    public synchronized byte[] getRemoteFeatures(String address) {
+        mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+        if (!BluetoothDevice.checkBluetoothAddress(address)) {
+            return null;
+        }
+        return getRemoteFeaturesNative(address);
+    }
+    private native byte[] getRemoteFeaturesNative(String address);
+
+    /**
+     * This method and {@link #getRemoteServiceRecord} query the SDP service
+     * on a remote device.  They do not interpret the data, but simply return
+     * it raw to the user.  To read more about SDP service handles and records,
+     * consult the Bluetooth core documentation (www.bluetooth.com).
+     *
+     * @param address Bluetooth address of remote device.
+     * @param match a String match to narrow down the service-handle search.
+     *        The only supported value currently is "hsp" for the headset
+     *        profile.  To retrieve all service handles, simply pass an empty
+     *        match string.
+     *
+     * @return all service handles corresponding to the string match.
+     *
+     * @see #getRemoteServiceRecord
+     */
+    public synchronized int[] getRemoteServiceHandles(String address, String match) {
+        mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+        if (!BluetoothDevice.checkBluetoothAddress(address)) {
+            return null;
+        }
+        if (match == null) {
+            match = "";
+        }
+        return getRemoteServiceHandlesNative(address, match);
+    }
+    private native int[] getRemoteServiceHandlesNative(String address, String match);
+
+    /**
+     * This method retrieves the service records corresponding to a given
+     * service handle (method {@link #getRemoteServiceHandles} retrieves the
+     * service handles.)
+     *
+     * This method and {@link #getRemoteServiceHandles} do not interpret their
+     * data, but simply return it raw to the user.  To read more about SDP
+     * service handles and records, consult the Bluetooth core documentation
+     * (www.bluetooth.com).
+     *
+     * @param address Bluetooth address of remote device.
+     * @param handle Service handle returned by {@link #getRemoteServiceHandles}
+     *
+     * @return a byte array of all service records corresponding to the
+     *         specified service handle.
+     *
+     * @see #getRemoteServiceHandles
+     */
+    public synchronized byte[] getRemoteServiceRecord(String address, int handle) {
+        mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+        if (!BluetoothDevice.checkBluetoothAddress(address)) {
+            return null;
+        }
+        return getRemoteServiceRecordNative(address, handle);
+    }
+    private native byte[] getRemoteServiceRecordNative(String address, int handle);
+
+    private static final int MAX_OUTSTANDING_ASYNC = 32;
+
+    // AIDL does not yet support short's
+    public synchronized boolean getRemoteServiceChannel(String address, int uuid16,
+            IBluetoothDeviceCallback callback) {
+        mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+        if (!BluetoothDevice.checkBluetoothAddress(address)) {
+            return false;
+        }
+        HashMap<String, IBluetoothDeviceCallback> callbacks =
+            mEventLoop.getRemoteServiceChannelCallbacks();
+        if (callbacks.containsKey(address)) {
+            Log.w(TAG, "SDP request already in progress for " + address);
+            return false;
+        }
+        // Protect from malicious clients - only allow 32 bonding requests per minute.
+        if (callbacks.size() > MAX_OUTSTANDING_ASYNC) {
+            Log.w(TAG, "Too many outstanding SDP requests, dropping request for " + address);
+            return false;
+        }
+        callbacks.put(address, callback);
+
+        if (!getRemoteServiceChannelNative(address, (short)uuid16)) {
+            callbacks.remove(address);
+            return false;
+        }
+        return true;
+    }
+    private native boolean getRemoteServiceChannelNative(String address, short uuid16);
+
+    public synchronized boolean setPin(String address, byte[] pin) {
+        mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
+                                                "Need BLUETOOTH_ADMIN permission");
+        if (pin == null || pin.length <= 0 || pin.length > 16 ||
+            !BluetoothDevice.checkBluetoothAddress(address)) {
+            return false;
+        }
+        address = address.toUpperCase();
+        Integer data = mEventLoop.getPasskeyAgentRequestData().remove(address);
+        if (data == null) {
+            Log.w(TAG, "setPin(" + address + ") called but no native data available, " +
+                  "ignoring. Maybe the PasskeyAgent Request was cancelled by the remote device" +
+                  " or by bluez.\n");
+            return false;
+        }
+        // bluez API wants pin as a string
+        String pinString;
+        try {
+            pinString = new String(pin, "UTF8");
+        } catch (UnsupportedEncodingException uee) {
+            Log.e(TAG, "UTF8 not supported?!?");
+            return false;
+        }
+        return setPinNative(address, pinString, data.intValue());
+    }
+    private native boolean setPinNative(String address, String pin, int nativeData);
+
+    public synchronized boolean cancelPin(String address) {
+        mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
+                                                "Need BLUETOOTH_ADMIN permission");
+        if (!BluetoothDevice.checkBluetoothAddress(address)) {
+            return false;
+        }
+        address = address.toUpperCase();
+        Integer data = mEventLoop.getPasskeyAgentRequestData().remove(address);
+        if (data == null) {
+            Log.w(TAG, "cancelPin(" + address + ") called but no native data available, " +
+                  "ignoring. Maybe the PasskeyAgent Request was already cancelled by the remote " +
+                  "or by bluez.\n");
+            return false;
+        }
+        return cancelPinNative(address, data.intValue());
+    }
+    private native boolean cancelPinNative(String address, int natveiData);
+
+    private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            String action = intent.getAction();
+            if (action.equals(Intent.ACTION_AIRPLANE_MODE_CHANGED)) {
+                ContentResolver resolver = context.getContentResolver();
+                // Query the airplane mode from Settings.System just to make sure that
+                // some random app is not sending this intent and disabling bluetooth
+                boolean enabled = !isAirplaneModeOn();
+                // If bluetooth is currently expected to be on, then enable or disable bluetooth
+                if (Settings.Secure.getInt(resolver, Settings.Secure.BLUETOOTH_ON, 0) > 0) {
+                    if (enabled) {
+                        enable(null, false);
+                    } else {
+                        disable(false);
+                    }
+                }
+            }
+        }
+    };
+
+    private void registerForAirplaneMode() {
+        String airplaneModeRadios = Settings.System.getString(mContext.getContentResolver(),
+                Settings.System.AIRPLANE_MODE_RADIOS);
+        mIsAirplaneSensitive = airplaneModeRadios == null
+                ? true : airplaneModeRadios.contains(Settings.System.RADIO_BLUETOOTH);
+        if (mIsAirplaneSensitive) {
+            mIntentFilter = new IntentFilter(Intent.ACTION_AIRPLANE_MODE_CHANGED);
+            mContext.registerReceiver(mReceiver, mIntentFilter);
+        }
+    }
+
+    /* Returns true if airplane mode is currently on */
+    private final boolean isAirplaneModeOn() {
+        return Settings.System.getInt(mContext.getContentResolver(),
+                Settings.System.AIRPLANE_MODE_ON, 0) == 1;
+    }
+
+    @Override
+    protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        if (mIsEnabled) {
+            pw.println("\nBluetooth ENABLED: " + getAddress() + " (" + getName() + ")");
+            pw.println("\nisDiscovering() = " + isDiscovering());
+
+            BluetoothHeadset headset = new BluetoothHeadset(mContext, null);
+
+            String[] addresses = listRemoteDevices();
+
+            pw.println("\n--Known devices--");
+            for (String address : addresses) {
+                pw.printf("%s %10s (%d) %s\n", address,
+                           toBondStateString(mBondState.getBondState(address)),
+                           mBondState.getAttempt(address),
+                           getRemoteName(address));
+            }
+
+            addresses = listAclConnections();
+            pw.println("\n--ACL connected devices--");
+            for (String address : addresses) {
+                pw.println(address);
+            }
+
+            // Rather not do this from here, but no-where else and I need this
+            // dump
+            pw.println("\n--Headset Service--");
+            switch (headset.getState()) {
+            case BluetoothHeadset.STATE_DISCONNECTED:
+                pw.println("getState() = STATE_DISCONNECTED");
+                break;
+            case BluetoothHeadset.STATE_CONNECTING:
+                pw.println("getState() = STATE_CONNECTING");
+                break;
+            case BluetoothHeadset.STATE_CONNECTED:
+                pw.println("getState() = STATE_CONNECTED");
+                break;
+            case BluetoothHeadset.STATE_ERROR:
+                pw.println("getState() = STATE_ERROR");
+                break;
+            }
+            pw.println("getHeadsetAddress() = " + headset.getHeadsetAddress());
+            headset.close();
+
+        } else {
+            pw.println("\nBluetooth DISABLED");
+        }
+        pw.println("\nmIsAirplaneSensitive = " + mIsAirplaneSensitive);
+    }
+
+    /* package */ static int bluezStringToScanMode(String mode) {
+        if (mode == null) {
+            return BluetoothError.ERROR;
+        }
+        mode = mode.toLowerCase();
+        if (mode.equals("off")) {
+            return BluetoothDevice.SCAN_MODE_NONE;
+        } else if (mode.equals("connectable")) {
+            return BluetoothDevice.SCAN_MODE_CONNECTABLE;
+        } else if (mode.equals("discoverable")) {
+            return BluetoothDevice.SCAN_MODE_CONNECTABLE_DISCOVERABLE;
+        } else {
+            return BluetoothError.ERROR;
+        }
+    }
+
+    /* package */ static String scanModeToBluezString(int mode) {
+        switch (mode) {
+        case BluetoothDevice.SCAN_MODE_NONE:
+            return "off";
+        case BluetoothDevice.SCAN_MODE_CONNECTABLE:
+            return "connectable";
+        case BluetoothDevice.SCAN_MODE_CONNECTABLE_DISCOVERABLE:
+            return "discoverable";
+        }
+        return null;
+    }
+
+    private static void log(String msg) {
+        Log.d(TAG, msg);
+    }
+}
diff --git a/core/java/android/server/BluetoothEventLoop.java b/core/java/android/server/BluetoothEventLoop.java
new file mode 100644
index 0000000..8b09583
--- /dev/null
+++ b/core/java/android/server/BluetoothEventLoop.java
@@ -0,0 +1,373 @@
+/*
+ * 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 android.server;
+
+import android.bluetooth.BluetoothA2dp;
+import android.bluetooth.BluetoothClass;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothError;
+import android.bluetooth.BluetoothIntent;
+import android.bluetooth.IBluetoothDeviceCallback;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Handler;
+import android.os.Message;
+import android.os.RemoteException;
+import android.util.Log;
+
+import java.util.HashMap;
+
+/**
+ * TODO: Move this to
+ * java/services/com/android/server/BluetoothEventLoop.java
+ * and make the contructor package private again.
+ *
+ * @hide
+ */
+class BluetoothEventLoop {
+    private static final String TAG = "BluetoothEventLoop";
+    private static final boolean DBG = false;
+
+    private int mNativeData;
+    private Thread mThread;
+    private boolean mInterrupted;
+    private HashMap<String, Integer> mPasskeyAgentRequestData;
+    private HashMap<String, IBluetoothDeviceCallback> mGetRemoteServiceChannelCallbacks;
+    private BluetoothDeviceService mBluetoothService;
+    private Context mContext;
+
+    private static final int EVENT_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY = 1;
+
+    // The time (in millisecs) to delay the pairing attempt after the first
+    // auto pairing attempt fails. We use an exponential delay with
+    // INIT_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY as the initial value and
+    // MAX_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY as the max value.
+    private static final long INIT_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY = 3000;
+    private static final long MAX_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY = 12000;
+
+    private static final String BLUETOOTH_ADMIN_PERM = android.Manifest.permission.BLUETOOTH_ADMIN;
+    private static final String BLUETOOTH_PERM = android.Manifest.permission.BLUETOOTH;
+
+    private final Handler mHandler = new Handler() {
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+            case EVENT_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY:
+                String address = (String)msg.obj;
+                if (address != null) {
+                    mBluetoothService.createBond(address);
+                    return;
+                }
+                break;
+            }
+        }
+    };
+
+    static { classInitNative(); }
+    private static native void classInitNative();
+
+    /* pacakge */ BluetoothEventLoop(Context context, BluetoothDeviceService bluetoothService) {
+        mBluetoothService = bluetoothService;
+        mContext = context;
+        mPasskeyAgentRequestData = new HashMap();
+        mGetRemoteServiceChannelCallbacks = new HashMap();
+        initializeNativeDataNative();
+    }
+    private native void initializeNativeDataNative();
+
+    protected void finalize() throws Throwable {
+        try {
+            cleanupNativeDataNative();
+        } finally {
+            super.finalize();
+        }
+    }
+    private native void cleanupNativeDataNative();
+
+    /* pacakge */ HashMap<String, IBluetoothDeviceCallback> getRemoteServiceChannelCallbacks() {
+        return mGetRemoteServiceChannelCallbacks;
+    }
+
+    /* pacakge */ HashMap<String, Integer> getPasskeyAgentRequestData() {
+        return mPasskeyAgentRequestData;
+    }
+
+    private synchronized boolean waitForAndDispatchEvent(int timeout_ms) {
+        return waitForAndDispatchEventNative(timeout_ms);
+    }
+    private native boolean waitForAndDispatchEventNative(int timeout_ms);
+
+    /* package */ synchronized void start() {
+
+        if (mThread != null) {
+            // Already running.
+            return;
+        }
+        mThread = new Thread("Bluetooth Event Loop") {
+                @Override
+                public void run() {
+                    try {
+                        if (setUpEventLoopNative()) {
+                            while (!mInterrupted) {
+                                waitForAndDispatchEvent(0);
+                                sleep(500);
+                            }
+                            tearDownEventLoopNative();
+                        }
+                    } catch (InterruptedException e) { }
+                    if (DBG) log("Event Loop thread finished");
+                }
+            };
+        if (DBG) log("Starting Event Loop thread");
+        mInterrupted = false;
+        mThread.start();
+    }
+    private native boolean setUpEventLoopNative();
+    private native void tearDownEventLoopNative();
+
+    public synchronized void stop() {
+        if (mThread != null) {
+            mInterrupted = true;
+            try {
+                mThread.join();
+                mThread = null;
+            } catch (InterruptedException e) {
+                Log.i(TAG, "Interrupted waiting for Event Loop thread to join");
+            }
+        }
+    }
+
+    public synchronized boolean isEventLoopRunning() {
+        return mThread != null;
+    }
+
+    /*package*/ void onModeChanged(String bluezMode) {
+        int mode = BluetoothDeviceService.bluezStringToScanMode(bluezMode);
+        if (mode >= 0) {
+            Intent intent = new Intent(BluetoothIntent.SCAN_MODE_CHANGED_ACTION);
+            intent.putExtra(BluetoothIntent.SCAN_MODE, mode);
+            mContext.sendBroadcast(intent, BLUETOOTH_PERM);
+        }
+    }
+
+    private void onDiscoveryStarted() {
+        mBluetoothService.setIsDiscovering(true);
+        Intent intent = new Intent(BluetoothIntent.DISCOVERY_STARTED_ACTION);
+        mContext.sendBroadcast(intent, BLUETOOTH_PERM);
+    }
+    private void onDiscoveryCompleted() {
+        mBluetoothService.setIsDiscovering(false);
+        Intent intent = new Intent(BluetoothIntent.DISCOVERY_COMPLETED_ACTION);
+        mContext.sendBroadcast(intent, BLUETOOTH_PERM);
+    }
+
+    private void onRemoteDeviceFound(String address, int deviceClass, short rssi) {
+        Intent intent = new Intent(BluetoothIntent.REMOTE_DEVICE_FOUND_ACTION);
+        intent.putExtra(BluetoothIntent.ADDRESS, address);
+        intent.putExtra(BluetoothIntent.CLASS, deviceClass);
+        intent.putExtra(BluetoothIntent.RSSI, rssi);
+        mContext.sendBroadcast(intent, BLUETOOTH_PERM);
+    }
+    private void onRemoteDeviceDisappeared(String address) {
+        Intent intent = new Intent(BluetoothIntent.REMOTE_DEVICE_DISAPPEARED_ACTION);
+        intent.putExtra(BluetoothIntent.ADDRESS, address);
+        mContext.sendBroadcast(intent, BLUETOOTH_PERM);
+    }
+    private void onRemoteClassUpdated(String address, int deviceClass) {
+        Intent intent = new Intent(BluetoothIntent.REMOTE_DEVICE_CLASS_UPDATED_ACTION);
+        intent.putExtra(BluetoothIntent.ADDRESS, address);
+        intent.putExtra(BluetoothIntent.CLASS, deviceClass);
+        mContext.sendBroadcast(intent, BLUETOOTH_PERM);
+    }
+    private void onRemoteDeviceConnected(String address) {
+        Intent intent = new Intent(BluetoothIntent.REMOTE_DEVICE_CONNECTED_ACTION);
+        intent.putExtra(BluetoothIntent.ADDRESS, address);
+        mContext.sendBroadcast(intent, BLUETOOTH_PERM);
+    }
+    private void onRemoteDeviceDisconnectRequested(String address) {
+        Intent intent = new Intent(BluetoothIntent.REMOTE_DEVICE_DISCONNECT_REQUESTED_ACTION);
+        intent.putExtra(BluetoothIntent.ADDRESS, address);
+        mContext.sendBroadcast(intent, BLUETOOTH_PERM);
+    }
+    private void onRemoteDeviceDisconnected(String address) {
+        Intent intent = new Intent(BluetoothIntent.REMOTE_DEVICE_DISCONNECTED_ACTION);
+        intent.putExtra(BluetoothIntent.ADDRESS, address);
+        mContext.sendBroadcast(intent, BLUETOOTH_PERM);
+    }
+    private void onRemoteNameUpdated(String address, String name) {
+        Intent intent = new Intent(BluetoothIntent.REMOTE_NAME_UPDATED_ACTION);
+        intent.putExtra(BluetoothIntent.ADDRESS, address);
+        intent.putExtra(BluetoothIntent.NAME, name);
+        mContext.sendBroadcast(intent, BLUETOOTH_PERM);
+    }
+    private void onRemoteNameFailed(String address) {
+        Intent intent = new Intent(BluetoothIntent.REMOTE_NAME_FAILED_ACTION);
+        intent.putExtra(BluetoothIntent.ADDRESS, address);
+        mContext.sendBroadcast(intent, BLUETOOTH_PERM);
+    }
+    private void onRemoteNameChanged(String address, String name) {
+        Intent intent = new Intent(BluetoothIntent.REMOTE_NAME_UPDATED_ACTION);
+        intent.putExtra(BluetoothIntent.ADDRESS, address);
+        intent.putExtra(BluetoothIntent.NAME, name);
+        mContext.sendBroadcast(intent, BLUETOOTH_PERM);
+    }
+
+    private void onCreateBondingResult(String address, int result) {
+        address = address.toUpperCase();
+        if (result == BluetoothError.SUCCESS) {
+            mBluetoothService.getBondState().setBondState(address, BluetoothDevice.BOND_BONDED);
+            if (mBluetoothService.getBondState().isAutoPairingAttemptsInProgress(address)) {
+                mBluetoothService.getBondState().clearPinAttempts(address);
+            }
+        } else if (result == BluetoothDevice.UNBOND_REASON_AUTH_FAILED &&
+                mBluetoothService.getBondState().getAttempt(address) == 1) {
+            mBluetoothService.getBondState().addAutoPairingFailure(address);
+            pairingAttempt(address, result);
+        } else if (result == BluetoothDevice.UNBOND_REASON_REMOTE_DEVICE_DOWN &&
+                mBluetoothService.getBondState().isAutoPairingAttemptsInProgress(address)) {
+            pairingAttempt(address, result);
+        } else {
+            mBluetoothService.getBondState().setBondState(address,
+                                                          BluetoothDevice.BOND_NOT_BONDED, result);
+            if (mBluetoothService.getBondState().isAutoPairingAttemptsInProgress(address)) {
+                mBluetoothService.getBondState().clearPinAttempts(address);
+            }
+        }
+    }
+
+    private void pairingAttempt(String address, int result) {
+        // This happens when our initial guess of "0000" as the pass key
+        // fails. Try to create the bond again and display the pin dialog
+        // to the user. Use back-off while posting the delayed
+        // message. The initial value is
+        // INIT_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY and the max value is
+        // MAX_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY. If the max value is
+        // reached, display an error to the user.
+        int attempt = mBluetoothService.getBondState().getAttempt(address);
+        if (attempt * INIT_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY >
+                    MAX_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY) {
+            mBluetoothService.getBondState().clearPinAttempts(address);
+            mBluetoothService.getBondState().setBondState(address,
+                    BluetoothDevice.BOND_NOT_BONDED, result);
+            return;
+        }
+
+        Message message = mHandler.obtainMessage(EVENT_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY);
+        message.obj = address;
+        boolean postResult =  mHandler.sendMessageDelayed(message,
+                                        attempt * INIT_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY);
+        if (!postResult) {
+            mBluetoothService.getBondState().clearPinAttempts(address);
+            mBluetoothService.getBondState().setBondState(address,
+                    BluetoothDevice.BOND_NOT_BONDED, result);
+            return;
+        }
+        mBluetoothService.getBondState().attempt(address);
+    }
+
+    private void onBondingCreated(String address) {
+        mBluetoothService.getBondState().setBondState(address.toUpperCase(),
+                                                      BluetoothDevice.BOND_BONDED);
+    }
+
+    private void onBondingRemoved(String address) {
+        mBluetoothService.getBondState().setBondState(address.toUpperCase(),
+                BluetoothDevice.BOND_NOT_BONDED, BluetoothDevice.UNBOND_REASON_REMOVED);
+    }
+
+    private void onNameChanged(String name) {
+        Intent intent = new Intent(BluetoothIntent.NAME_CHANGED_ACTION);
+        intent.putExtra(BluetoothIntent.NAME, name);
+        mContext.sendBroadcast(intent, BLUETOOTH_PERM);
+    }
+
+    private void onPasskeyAgentRequest(String address, int nativeData) {
+        address = address.toUpperCase();
+        mPasskeyAgentRequestData.put(address, new Integer(nativeData));
+
+        if (mBluetoothService.getBondState().getBondState(address) ==
+                BluetoothDevice.BOND_BONDING) {
+            // we initiated the bonding
+            int btClass = mBluetoothService.getRemoteClass(address);
+
+            // try 0000 once if the device looks dumb
+            switch (BluetoothClass.Device.getDevice(btClass)) {
+            case BluetoothClass.Device.AUDIO_VIDEO_WEARABLE_HEADSET:
+            case BluetoothClass.Device.AUDIO_VIDEO_HANDSFREE:
+            case BluetoothClass.Device.AUDIO_VIDEO_HEADPHONES:
+            case BluetoothClass.Device.AUDIO_VIDEO_PORTABLE_AUDIO:
+            case BluetoothClass.Device.AUDIO_VIDEO_CAR_AUDIO:
+            case BluetoothClass.Device.AUDIO_VIDEO_HIFI_AUDIO:
+                if (!mBluetoothService.getBondState().hasAutoPairingFailed(address) &&
+                    !mBluetoothService.getBondState().isAutoPairingBlacklisted(address)) {
+                    mBluetoothService.getBondState().attempt(address);
+                    mBluetoothService.setPin(address, BluetoothDevice.convertPinToBytes("0000"));
+                    return;
+                }
+           }
+        }
+        Intent intent = new Intent(BluetoothIntent.PAIRING_REQUEST_ACTION);
+        intent.putExtra(BluetoothIntent.ADDRESS, address);
+        mContext.sendBroadcast(intent, BLUETOOTH_ADMIN_PERM);
+    }
+
+    private void onPasskeyAgentCancel(String address) {
+        address = address.toUpperCase();
+        mPasskeyAgentRequestData.remove(address);
+        Intent intent = new Intent(BluetoothIntent.PAIRING_CANCEL_ACTION);
+        intent.putExtra(BluetoothIntent.ADDRESS, address);
+        mContext.sendBroadcast(intent, BLUETOOTH_ADMIN_PERM);
+        mBluetoothService.getBondState().setBondState(address, BluetoothDevice.BOND_NOT_BONDED,
+                                                      BluetoothDevice.UNBOND_REASON_AUTH_CANCELED);
+    }
+
+    private boolean onAuthAgentAuthorize(String address, String service, String uuid) {
+        boolean authorized = false;
+        if (service.endsWith("service_audio")) {
+            BluetoothA2dp a2dp = new BluetoothA2dp(mContext);
+            authorized = a2dp.getSinkPriority(address) > BluetoothA2dp.PRIORITY_OFF;
+            if (authorized) {
+                Log.i(TAG, "Allowing incoming A2DP connection from " + address);
+            } else {
+                Log.i(TAG, "Rejecting incoming A2DP connection from " + address);
+            }
+        } else {
+            Log.i(TAG, "Rejecting incoming " + service + " connection from " + address);
+        }
+        return authorized;
+    }
+
+    private void onAuthAgentCancel(String address, String service, String uuid) {
+        // We immediately response to DBUS Authorize() so this should not
+        // usually happen
+        log("onAuthAgentCancel(" + address + ", " + service + ", " + uuid + ")");
+    }
+
+    private void onGetRemoteServiceChannelResult(String address, int channel) {
+        IBluetoothDeviceCallback callback = mGetRemoteServiceChannelCallbacks.get(address);
+        if (callback != null) {
+            mGetRemoteServiceChannelCallbacks.remove(address);
+            try {
+                callback.onGetRemoteServiceChannelResult(address, channel);
+            } catch (RemoteException e) {}
+        }
+    }
+
+    private static void log(String msg) {
+        Log.d(TAG, msg);
+    }
+}
diff --git a/core/java/android/server/data/BuildData.java b/core/java/android/server/data/BuildData.java
new file mode 100644
index 0000000..53ffa3f
--- /dev/null
+++ b/core/java/android/server/data/BuildData.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2007 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 android.server.data;
+
+import android.os.Build;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.IOException;
+
+import static com.android.internal.util.Objects.nonNull;
+
+/**
+ * Build data transfer object. Keep in sync. with the server side version.
+ */
+public class BuildData {
+
+    /** The version of the data returned by write() and understood by the constructor. */
+    private static final int VERSION = 0;
+
+    private final String fingerprint;
+    private final String incrementalVersion;
+    private final long time;    // in *seconds* since the epoch (not msec!)
+
+    public BuildData() {
+        this.fingerprint = "android:" + Build.FINGERPRINT;
+        this.incrementalVersion = Build.VERSION.INCREMENTAL;
+        this.time = Build.TIME / 1000;  // msec -> sec
+    }
+
+    public BuildData(String fingerprint, String incrementalVersion, long time) {
+        this.fingerprint = nonNull(fingerprint);
+        this.incrementalVersion = incrementalVersion;
+        this.time = time;
+    }
+
+    /*package*/ BuildData(DataInput in) throws IOException {
+        int dataVersion = in.readInt();
+        if (dataVersion != VERSION) {
+            throw new IOException("Expected " + VERSION + ". Got: " + dataVersion);
+        }
+
+        this.fingerprint = in.readUTF();
+        this.incrementalVersion = Long.toString(in.readLong());
+        this.time = in.readLong();
+    }
+
+    /*package*/ void write(DataOutput out) throws IOException {
+        out.writeInt(VERSION);
+        out.writeUTF(fingerprint);
+
+        // TODO: change the format/version to expect a string for this field.
+        // Version 0, still used by the server side, expects a long.
+        long changelist;
+        try {
+            changelist = Long.parseLong(incrementalVersion);
+        } catch (NumberFormatException ex) {
+            changelist = -1;
+        }
+        out.writeLong(changelist);
+        out.writeLong(time);
+    }
+
+    public String getFingerprint() {
+        return fingerprint;
+    }
+
+    public String getIncrementalVersion() {
+        return incrementalVersion;
+    }
+
+    public long getTime() {
+        return time;
+    }
+}
diff --git a/core/java/android/server/data/CrashData.java b/core/java/android/server/data/CrashData.java
new file mode 100644
index 0000000..d652bb3
--- /dev/null
+++ b/core/java/android/server/data/CrashData.java
@@ -0,0 +1,145 @@
+/*
+ * 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 android.server.data;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.IOException;
+
+import static com.android.internal.util.Objects.nonNull;
+
+/**
+ * Crash data transfer object. Keep in sync. with the server side version.
+ */
+public class CrashData {
+
+    final String id;
+    final String activity;
+    final long time;
+    final BuildData buildData;
+    final ThrowableData throwableData;
+    final byte[] state;
+
+    public CrashData(String id, String activity, BuildData buildData,
+            ThrowableData throwableData) {
+        this.id = nonNull(id);
+        this.activity = nonNull(activity);
+        this.buildData = nonNull(buildData);
+        this.throwableData = nonNull(throwableData);
+        this.time = System.currentTimeMillis();
+        this.state = null;
+    }
+
+    public CrashData(String id, String activity, BuildData buildData,
+                     ThrowableData throwableData, byte[] state) {
+        this.id = nonNull(id);
+        this.activity = nonNull(activity);
+        this.buildData = nonNull(buildData);
+        this.throwableData = nonNull(throwableData);
+        this.time = System.currentTimeMillis();
+        this.state = state;
+    }
+
+    public CrashData(DataInput in) throws IOException {
+        int dataVersion = in.readInt();
+        if (dataVersion != 0 && dataVersion != 1) {
+            throw new IOException("Expected 0 or 1. Got: " + dataVersion);
+        }
+
+        this.id = in.readUTF();
+        this.activity = in.readUTF();
+        this.time = in.readLong();
+        this.buildData = new BuildData(in);
+        this.throwableData = new ThrowableData(in);
+        if (dataVersion == 1) {
+            int len = in.readInt();
+            if (len == 0) {
+                this.state = null;
+            } else {
+                this.state = new byte[len];
+                in.readFully(this.state, 0, len);
+            }
+        } else {
+            this.state = null;
+        }
+    }
+
+    public CrashData(String tag, Throwable throwable) {
+        id = "";
+        activity = tag;
+        buildData = new BuildData();
+        throwableData = new ThrowableData(throwable);
+        time = System.currentTimeMillis();
+        state = null;
+    }
+
+    public void write(DataOutput out) throws IOException {
+        // version
+        if (this.state == null) {
+            out.writeInt(0);
+        } else {
+            out.writeInt(1);
+        }
+
+        out.writeUTF(this.id);
+        out.writeUTF(this.activity);
+        out.writeLong(this.time);
+        buildData.write(out);
+        throwableData.write(out);
+        if (this.state != null) {
+            out.writeInt(this.state.length);
+            out.write(this.state, 0, this.state.length);
+        }
+    }
+
+    public BuildData getBuildData() {
+        return buildData;
+    }
+
+    public ThrowableData getThrowableData() {
+        return throwableData;
+    }
+
+    public String getId() {
+        return id;
+    }
+
+    public String getActivity() {
+        return activity;
+    }
+
+    public long getTime() {
+        return time;
+    }
+
+    public byte[] getState() {
+        return state;
+    }
+    
+    /**
+     * Return a brief description of this CrashData record.  The details of the
+     * representation are subject to change.
+     * 
+     * @return Returns a String representing the contents of the object.
+     */
+    @Override
+    public String toString() {
+        return "[CrashData: id=" + id + " activity=" + activity + " time=" + time +
+                " buildData=" + buildData.toString() + 
+                " throwableData=" + throwableData.toString() + "]";
+    }
+}
diff --git a/core/java/android/server/data/StackTraceElementData.java b/core/java/android/server/data/StackTraceElementData.java
new file mode 100644
index 0000000..07185a0
--- /dev/null
+++ b/core/java/android/server/data/StackTraceElementData.java
@@ -0,0 +1,80 @@
+/*
+ * 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 android.server.data;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.IOException;
+
+/**
+ * Stack trace element data transfer object. Keep in sync. with the server side
+ * version.
+ */
+public class StackTraceElementData {
+
+    final String className;
+    final String fileName;
+    final String methodName;
+    final int lineNumber;
+
+    public StackTraceElementData(StackTraceElement element) {
+        this.className = element.getClassName();
+
+        String fileName = element.getFileName();
+        this.fileName = fileName == null ? "[unknown source]" : fileName;
+
+        this.methodName = element.getMethodName();
+        this.lineNumber = element.getLineNumber();
+    }
+
+    public StackTraceElementData(DataInput in) throws IOException {
+        int dataVersion = in.readInt();
+        if (dataVersion != 0) {
+            throw new IOException("Expected 0. Got: " + dataVersion);
+        }
+
+        this.className = in.readUTF();
+        this.fileName = in.readUTF();
+        this.methodName = in.readUTF();
+        this.lineNumber = in.readInt();
+    }
+
+    void write(DataOutput out) throws IOException {
+        out.writeInt(0); // version
+
+        out.writeUTF(className);
+        out.writeUTF(fileName);
+        out.writeUTF(methodName);
+        out.writeInt(lineNumber);
+    }
+
+    public String getClassName() {
+        return className;
+    }
+
+    public String getFileName() {
+        return fileName;
+    }
+
+    public String getMethodName() {
+        return methodName;
+    }
+
+    public int getLineNumber() {
+        return lineNumber;
+    }
+}
diff --git a/core/java/android/server/data/ThrowableData.java b/core/java/android/server/data/ThrowableData.java
new file mode 100644
index 0000000..e500aca
--- /dev/null
+++ b/core/java/android/server/data/ThrowableData.java
@@ -0,0 +1,138 @@
+/*
+ * 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 android.server.data;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.IOException;
+
+/**
+ * Throwable data transfer object. Keep in sync. with the server side version.
+ */
+public class ThrowableData {
+
+    final String message;
+    final String type;
+    final StackTraceElementData[] stackTrace;
+    final ThrowableData cause;
+
+    public ThrowableData(Throwable throwable) {
+        this.type = throwable.getClass().getName();
+        String message = throwable.getMessage();
+        this.message = message == null ? "" : message;
+
+        StackTraceElement[] elements = throwable.getStackTrace();
+        this.stackTrace = new StackTraceElementData[elements.length];
+        for (int i = 0; i < elements.length; i++) {
+            this.stackTrace[i] = new StackTraceElementData(elements[i]);
+        }
+
+        Throwable cause = throwable.getCause();
+        this.cause = cause == null ? null : new ThrowableData(cause);
+    }
+
+    public ThrowableData(DataInput in) throws IOException {
+        int dataVersion = in.readInt();
+        if (dataVersion != 0) {
+            throw new IOException("Expected 0. Got: " + dataVersion);
+        }
+
+        this.message = in.readUTF();
+        this.type = in.readUTF();
+
+        int count = in.readInt();
+        this.stackTrace = new StackTraceElementData[count];
+        for (int i = 0; i < count; i++) {
+            this.stackTrace[i] = new StackTraceElementData(in);
+        }
+
+        this.cause = in.readBoolean() ? new ThrowableData(in) : null;
+    }
+
+    public void write(DataOutput out) throws IOException {
+        out.writeInt(0); // version
+
+        out.writeUTF(message);
+        out.writeUTF(type);
+
+        out.writeInt(stackTrace.length);
+        for (StackTraceElementData elementData : stackTrace) {
+            elementData.write(out);
+        }
+
+        out.writeBoolean(cause != null);
+        if (cause != null) {
+            cause.write(out);
+        }
+    }
+
+    public String getMessage() {
+        return message;
+    }
+
+    public String getType() {
+        return type;
+    }
+
+    public StackTraceElementData[] getStackTrace() {
+        return stackTrace;
+    }
+
+    public ThrowableData getCause() {
+        return cause;
+    }
+
+
+    public String toString() {
+        return toString(null);
+    }
+
+    public String toString(String prefix) {
+        StringBuilder builder = new StringBuilder();
+        append(prefix, builder, this);
+        return builder.toString();
+    }
+
+    private static void append(String prefix, StringBuilder builder,
+            ThrowableData throwableData) {
+        if (prefix != null) builder.append(prefix);
+        builder.append(throwableData.getType())
+                .append(": ")
+                .append(throwableData.getMessage())
+                .append('\n');
+        for (StackTraceElementData element : throwableData.getStackTrace()) {
+            if (prefix != null ) builder.append(prefix);
+            builder.append("  at ")
+                    .append(element.getClassName())
+                    .append('.')
+                    .append(element.getMethodName())
+                    .append("(")
+                    .append(element.getFileName())
+                    .append(':')
+                    .append(element.getLineNumber())
+                    .append(")\n");
+
+        }
+
+        ThrowableData cause = throwableData.getCause();
+        if (cause != null) {
+            if (prefix != null ) builder.append(prefix);
+            builder.append("Caused by: ");
+            append(prefix, builder, cause);
+        }
+    }
+}
diff --git a/core/java/android/server/data/package.html b/core/java/android/server/data/package.html
new file mode 100755
index 0000000..1c9bf9d
--- /dev/null
+++ b/core/java/android/server/data/package.html
@@ -0,0 +1,5 @@
+<html>
+<body>
+    {@hide}
+</body>
+</html>
diff --git a/core/java/android/server/package.html b/core/java/android/server/package.html
new file mode 100755
index 0000000..c9f96a6
--- /dev/null
+++ b/core/java/android/server/package.html
@@ -0,0 +1,5 @@
+<body>
+
+{@hide}
+
+</body>
diff --git a/core/java/android/server/search/SearchManagerService.java b/core/java/android/server/search/SearchManagerService.java
new file mode 100644
index 0000000..fe15553
--- /dev/null
+++ b/core/java/android/server/search/SearchManagerService.java
@@ -0,0 +1,156 @@
+/*
+ * Copyright (C) 2007 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 android.server.search;
+
+import android.app.ISearchManager;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Handler;
+import android.util.Config;
+
+/**
+ * This is a simplified version of the Search Manager service.  It no longer handles
+ * presentation (UI).  Its function is to maintain the map & list of "searchable" 
+ * items, which provides a mapping from individual activities (where a user might have
+ * invoked search) to specific searchable activities (where the search will be dispatched).
+ */
+public class SearchManagerService extends ISearchManager.Stub
+{
+        // general debugging support
+    private static final String TAG = "SearchManagerService";
+    private static final boolean DEBUG = false;
+    private static final boolean localLOGV = DEBUG ? Config.LOGD : Config.LOGV;
+    
+        // configuration choices
+    private static final boolean IMMEDIATE_SEARCHABLES_UPDATE = true;
+
+        // class maintenance and general shared data
+    private final Context mContext;
+    private final Handler mHandler;
+    private boolean mSearchablesDirty;
+    
+    /**
+     * Initialize the Search Manager service in the provided system context.
+     * Only one instance of this object should be created!
+     *
+     * @param context to use for accessing DB, window manager, etc.
+     */
+    public SearchManagerService(Context context)  {     
+        mContext = context;
+        mHandler = new Handler();
+        
+        // Setup the infrastructure for updating and maintaining the list
+        // of searchable activities.
+        IntentFilter filter = new IntentFilter();
+        filter.addAction(Intent.ACTION_PACKAGE_ADDED);
+        filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
+        filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
+        filter.addDataScheme("package");
+        mContext.registerReceiver(mIntentReceiver, filter, null, mHandler);
+        mSearchablesDirty = true;
+        
+        // After startup settles down, preload the searchables list,
+        // which will reduce the delay when the search UI is invoked.
+        if (IMMEDIATE_SEARCHABLES_UPDATE) {
+            mHandler.post(mRunUpdateSearchable);
+        }
+    }
+    
+    /**
+     * Listens for intent broadcasts.
+     * 
+     * The primary purpose here is to refresh the "searchables" list
+     * if packages are added/removed.
+     */
+    private BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            String action = intent.getAction();
+            
+            // First, test for intents that matter at any time
+            if (action.equals(Intent.ACTION_PACKAGE_ADDED) ||
+                action.equals(Intent.ACTION_PACKAGE_REMOVED) ||
+                action.equals(Intent.ACTION_PACKAGE_CHANGED)) {
+                mSearchablesDirty = true;
+                if (IMMEDIATE_SEARCHABLES_UPDATE) {
+                    mHandler.post(mRunUpdateSearchable);
+                }
+                return;
+            }
+        }
+    };
+    
+    /**
+     * This runnable (for the main handler / UI thread) will update the searchables list.
+     */
+    private Runnable mRunUpdateSearchable = new Runnable() {
+        public void run() {
+            if (mSearchablesDirty) {
+                updateSearchables();
+            }
+        } 
+    };
+
+    /**
+     * Update the list of searchables, either at startup or in response to
+     * a package add/remove broadcast message.
+     */
+    private void updateSearchables() {
+        SearchableInfo.buildSearchableList(mContext);
+        mSearchablesDirty = false;
+        
+        // TODO This is a hack.  This shouldn't be hardcoded here, it's probably
+        // a policy.
+//      ComponentName defaultSearch = new ComponentName( 
+//              "com.android.contacts", 
+//              "com.android.contacts.ContactsListActivity" );
+        ComponentName defaultSearch = new ComponentName( 
+                "com.android.googlesearch", 
+                "com.android.googlesearch.GoogleSearch" );
+        SearchableInfo.setDefaultSearchable(mContext, defaultSearch);
+    }
+
+    /**
+     * Return the searchableinfo for a given activity
+     *
+     * @param launchActivity The activity from which we're launching this search.
+     * @return Returns a SearchableInfo record describing the parameters of the search,
+     * or null if no searchable metadata was available.
+     * @param globalSearch If false, this will only launch the search that has been specifically
+     * defined by the application (which is usually defined as a local search).  If no default 
+     * search is defined in the current application or activity, no search will be launched.
+     * If true, this will always launch a platform-global (e.g. web-based) search instead.
+     */
+    public SearchableInfo getSearchableInfo(ComponentName launchActivity, boolean globalSearch) {
+        // final check.  however we should try to avoid this, because
+        // it slows down the entry into the UI.
+        if (mSearchablesDirty) {
+            updateSearchables();
+        }
+        SearchableInfo si = null;
+        if (globalSearch) {
+            si = SearchableInfo.getDefaultSearchable();
+        } else {
+            si = SearchableInfo.getSearchableInfo(mContext, launchActivity);
+        }
+
+        return si;
+    }
+}
diff --git a/core/java/android/server/search/SearchableInfo.aidl b/core/java/android/server/search/SearchableInfo.aidl
new file mode 100644
index 0000000..9576c2b
--- /dev/null
+++ b/core/java/android/server/search/SearchableInfo.aidl
@@ -0,0 +1,19 @@
+/**
+ * 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 android.server.search;
+
+parcelable SearchableInfo;
diff --git a/core/java/android/server/search/SearchableInfo.java b/core/java/android/server/search/SearchableInfo.java
new file mode 100644
index 0000000..0c04839
--- /dev/null
+++ b/core/java/android/server/search/SearchableInfo.java
@@ -0,0 +1,866 @@
+/*
+ * Copyright (C) 2007 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 android.server.search;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ProviderInfo;
+import android.content.pm.ResolveInfo;
+import android.content.res.TypedArray;
+import android.content.res.XmlResourceParser;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.InputType;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.util.Xml;
+import android.view.inputmethod.EditorInfo;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+
+public final class SearchableInfo implements Parcelable {
+
+    // general debugging support
+    final static String LOG_TAG = "SearchableInfo";
+    
+    // set this flag to 1 to prevent any apps from providing suggestions
+    final static int DBG_INHIBIT_SUGGESTIONS = 0;
+
+    // static strings used for XML lookups, etc.
+    // TODO how should these be documented for the developer, in a more structured way than 
+    // the current long wordy javadoc in SearchManager.java ?
+    private static final String MD_LABEL_DEFAULT_SEARCHABLE = "android.app.default_searchable";
+    private static final String MD_LABEL_SEARCHABLE = "android.app.searchable";
+    private static final String MD_SEARCHABLE_SYSTEM_SEARCH = "*";
+    private static final String MD_XML_ELEMENT_SEARCHABLE = "searchable";
+    private static final String MD_XML_ELEMENT_SEARCHABLE_ACTION_KEY = "actionkey";
+
+    // class maintenance and general shared data
+    private static HashMap<ComponentName, SearchableInfo> sSearchablesMap = null;
+    private static ArrayList<SearchableInfo> sSearchablesList = null;
+    private static SearchableInfo sDefaultSearchable = null;
+    
+    // true member variables - what we know about the searchability
+    // TO-DO replace public with getters
+    public boolean mSearchable = false;
+    private int mLabelId = 0;
+    public ComponentName mSearchActivity = null;
+    private int mHintId = 0;
+    private int mSearchMode = 0;
+    public boolean mBadgeLabel = false;
+    public boolean mBadgeIcon = false;
+    public boolean mQueryRewriteFromData = false;
+    public boolean mQueryRewriteFromText = false;
+    private int mIconId = 0;
+    private int mSearchButtonText = 0;
+    private int mSearchInputType = 0;
+    private int mSearchImeOptions = 0;
+    private String mSuggestAuthority = null;
+    private String mSuggestPath = null;
+    private String mSuggestSelection = null;
+    private String mSuggestIntentAction = null;
+    private String mSuggestIntentData = null;
+    private ActionKeyInfo mActionKeyList = null;
+    private String mSuggestProviderPackage = null;
+    private Context mCacheActivityContext = null;   // use during setup only - don't hold memory!
+    
+    // Flag values for Searchable_voiceSearchMode
+    private static int VOICE_SEARCH_SHOW_BUTTON = 1;
+    private static int VOICE_SEARCH_LAUNCH_WEB_SEARCH = 2;
+    private static int VOICE_SEARCH_LAUNCH_RECOGNIZER = 4;
+    private int mVoiceSearchMode = 0;
+    private int mVoiceLanguageModeId;       // voiceLanguageModel
+    private int mVoicePromptTextId;         // voicePromptText
+    private int mVoiceLanguageId;           // voiceLanguage
+    private int mVoiceMaxResults;           // voiceMaxResults
+    
+    /**
+     * Set the default searchable activity (when none is specified).
+     */
+    public static void setDefaultSearchable(Context context, 
+                                            ComponentName activity) {
+        synchronized (SearchableInfo.class) {
+            SearchableInfo si = null;
+            if (activity != null) {
+                si = getSearchableInfo(context, activity);
+                if (si != null) {
+                    // move to front of list
+                    sSearchablesList.remove(si);
+                    sSearchablesList.add(0, si);
+                }
+            }
+            sDefaultSearchable = si;
+        }
+    }
+    
+    /**
+     * Provides the system-default search activity, which you can use
+     * whenever getSearchableInfo() returns null;
+     * 
+     * @return Returns the system-default search activity, null if never defined
+     */
+    public static SearchableInfo getDefaultSearchable() {
+        synchronized (SearchableInfo.class) {
+            return sDefaultSearchable;
+        }
+    }
+    
+    /**
+     * Retrieve the authority for obtaining search suggestions.
+     * 
+     * @return Returns a string containing the suggestions authority.
+     */
+    public String getSuggestAuthority() {
+        return mSuggestAuthority;
+    }
+    
+    /**
+     * Retrieve the path for obtaining search suggestions.
+     * 
+     * @return Returns a string containing the suggestions path, or null if not provided.
+     */
+    public String getSuggestPath() {
+        return mSuggestPath;
+    }
+    
+    /**
+     * Retrieve the selection pattern for obtaining search suggestions.  This must
+     * include a single ? which will be used for the user-typed characters.
+     * 
+     * @return Returns a string containing the suggestions authority.
+     */
+    public String getSuggestSelection() {
+        return mSuggestSelection;
+    }
+    
+    /**
+     * Retrieve the (optional) intent action for use with these suggestions.  This is
+     * useful if all intents will have the same action (e.g. "android.intent.action.VIEW").
+     * 
+     * Can be overriden in any given suggestion via the AUTOSUGGEST_COLUMN_INTENT_ACTION column.
+     * 
+     * @return Returns a string containing the default intent action.
+     */
+    public String getSuggestIntentAction() {
+        return mSuggestIntentAction;
+    }
+    
+    /**
+     * Retrieve the (optional) intent data for use with these suggestions.  This is
+     * useful if all intents will have similar data URIs (e.g. "android.intent.action.VIEW"), 
+     * but you'll likely need to provide a specific ID as well via the column
+     * AUTOSUGGEST_COLUMN_INTENT_DATA_ID, which will be appended to the intent data URI.
+     * 
+     * Can be overriden in any given suggestion via the AUTOSUGGEST_COLUMN_INTENT_DATA column.
+     * 
+     * @return Returns a string containing the default intent data.
+     */
+    public String getSuggestIntentData() {
+        return mSuggestIntentData;
+    }
+    
+    /**
+     * Get the context for the searchable activity.  
+     * 
+     * This is fairly expensive so do it on the original scan, or when an app is
+     * selected, but don't hang on to the result forever.
+     * 
+     * @param context You need to supply a context to start with
+     * @return Returns a context related to the searchable activity
+     */
+    public Context getActivityContext(Context context) {
+        Context theirContext = null;
+        try {
+            theirContext = context.createPackageContext(mSearchActivity.getPackageName(), 0);
+        } catch (PackageManager.NameNotFoundException e) {
+            // unexpected, but we deal with this by null-checking theirContext
+        } catch (java.lang.SecurityException e) {
+            // unexpected, but we deal with this by null-checking theirContext
+        }
+        
+        return theirContext;
+    }
+    
+    /**
+     * Get the context for the suggestions provider.  
+     * 
+     * This is fairly expensive so do it on the original scan, or when an app is
+     * selected, but don't hang on to the result forever.
+     * 
+     * @param context You need to supply a context to start with
+     * @param activityContext If we can determine that the provider and the activity are the
+     * same, we'll just return this one.
+     * @return Returns a context related to the context provider
+     */
+    public Context getProviderContext(Context context, Context activityContext) {
+        Context theirContext = null;
+        if (mSearchActivity.getPackageName().equals(mSuggestProviderPackage)) {
+            return activityContext;
+        }
+        if (mSuggestProviderPackage != null)
+        try {
+            theirContext = context.createPackageContext(mSuggestProviderPackage, 0);
+        } catch (PackageManager.NameNotFoundException e) {
+            // unexpected, but we deal with this by null-checking theirContext
+        } catch (java.lang.SecurityException e) {
+            // unexpected, but we deal with this by null-checking theirContext
+        }
+        
+        return theirContext;
+    }
+    
+    /**
+     * Factory.  Look up, or construct, based on the activity.
+     * 
+     * The activities fall into three cases, based on meta-data found in 
+     * the manifest entry:
+     * <ol>
+     * <li>The activity itself implements search.  This is indicated by the
+     * presence of a "android.app.searchable" meta-data attribute.
+     * The value is a reference to an XML file containing search information.</li>
+     * <li>A related activity implements search.  This is indicated by the
+     * presence of a "android.app.default_searchable" meta-data attribute.
+     * The value is a string naming the activity implementing search.  In this
+     * case the factory will "redirect" and return the searchable data.</li>
+     * <li>No searchability data is provided.  We return null here and other
+     * code will insert the "default" (e.g. contacts) search.
+     * 
+     * TODO: cache the result in the map, and check the map first.
+     * TODO: it might make sense to implement the searchable reference as
+     * an application meta-data entry.  This way we don't have to pepper each
+     * and every activity.
+     * TODO: can we skip the constructor step if it's a non-searchable?
+     * TODO: does it make sense to plug the default into a slot here for 
+     * automatic return?  Probably not, but it's one way to do it.
+     *
+     * @param activity The name of the current activity, or null if the 
+     * activity does not define any explicit searchable metadata.
+     */
+    public static SearchableInfo getSearchableInfo(Context context, 
+                                                   ComponentName activity) {
+        // Step 1.  Is the result already hashed?  (case 1)
+        SearchableInfo result;
+        synchronized (SearchableInfo.class) {
+            result = sSearchablesMap.get(activity);
+            if (result != null) return result;
+        }
+        
+        // Step 2.  See if the current activity references a searchable.
+        // Note:  Conceptually, this could be a while(true) loop, but there's
+        // no point in implementing reference chaining here and risking a loop.  
+        // References must point directly to searchable activities.
+       
+        ActivityInfo ai = null;
+        XmlPullParser xml = null;
+        try {
+            ai = context.getPackageManager().
+                       getActivityInfo(activity, PackageManager.GET_META_DATA );
+            String refActivityName = null;
+            
+            // First look for activity-specific reference
+            Bundle md = ai.metaData;
+            if (md != null) {
+                refActivityName = md.getString(MD_LABEL_DEFAULT_SEARCHABLE);
+            }
+            // If not found, try for app-wide reference
+            if (refActivityName == null) {
+                md = ai.applicationInfo.metaData;
+                if (md != null) {
+                    refActivityName = md.getString(MD_LABEL_DEFAULT_SEARCHABLE);
+                }
+            }
+            
+            // Irrespective of source, if a reference was found, follow it.
+            if (refActivityName != null)
+            {
+                // An app or activity can declare that we should simply launch 
+                // "system default search" if search is invoked.
+                if (refActivityName.equals(MD_SEARCHABLE_SYSTEM_SEARCH)) {
+                    return getDefaultSearchable();
+                }
+                String pkg = activity.getPackageName();
+                ComponentName referredActivity;
+                if (refActivityName.charAt(0) == '.') {
+                    referredActivity = new ComponentName(pkg, pkg + refActivityName);
+                } else {
+                    referredActivity = new ComponentName(pkg, refActivityName);
+                }
+
+                // Now try the referred activity, and if found, cache
+                // it against the original name so we can skip the check
+                synchronized (SearchableInfo.class) {
+                    result = sSearchablesMap.get(referredActivity);
+                    if (result != null) {
+                        sSearchablesMap.put(activity, result);
+                        return result;
+                    }
+                }
+            }
+        } catch (PackageManager.NameNotFoundException e) {
+            // case 3: no metadata
+        }
+ 
+        // Step 3.  None found. Return null.
+        return null;
+        
+    }
+    
+    /**
+     * Super-factory.  Builds an entire list (suitable for display) of 
+     * activities that are searchable, by iterating the entire set of 
+     * ACTION_SEARCH intents.  
+     * 
+     * Also clears the hash of all activities -> searches which will
+     * refill as the user clicks "search".
+     * 
+     * This should only be done at startup and again if we know that the
+     * list has changed.
+     * 
+     * TODO: every activity that provides a ACTION_SEARCH intent should
+     * also provide searchability meta-data.  There are a bunch of checks here
+     * that, if data is not found, silently skip to the next activity.  This
+     * won't help a developer trying to figure out why their activity isn't
+     * showing up in the list, but an exception here is too rough.  I would
+     * like to find a better notification mechanism.
+     * 
+     * TODO: sort the list somehow?  UI choice.
+     * 
+     * @param context a context we can use during this work
+     */
+    public static void buildSearchableList(Context context) {
+        
+        // create empty hash & list
+        HashMap<ComponentName, SearchableInfo> newSearchablesMap 
+                                = new HashMap<ComponentName, SearchableInfo>();
+        ArrayList<SearchableInfo> newSearchablesList
+                                = new ArrayList<SearchableInfo>();
+
+        // use intent resolver to generate list of ACTION_SEARCH receivers
+        final PackageManager pm = context.getPackageManager();
+        List<ResolveInfo> infoList;
+        final Intent intent = new Intent(Intent.ACTION_SEARCH);
+        infoList = pm.queryIntentActivities(intent, PackageManager.GET_META_DATA);
+        
+        // analyze each one, generate a Searchables record, and record
+        if (infoList != null) {
+            int count = infoList.size();
+            for (int ii = 0; ii < count; ii++) {
+                // for each component, try to find metadata
+                ResolveInfo info = infoList.get(ii);
+                ActivityInfo ai = info.activityInfo;
+                XmlResourceParser xml = ai.loadXmlMetaData(context.getPackageManager(), 
+                                                       MD_LABEL_SEARCHABLE);
+                if (xml == null) {
+                    continue;
+                }
+                ComponentName cName = new ComponentName(
+                        info.activityInfo.packageName, 
+                        info.activityInfo.name);
+                
+                SearchableInfo searchable = getActivityMetaData(context, xml, cName);
+                xml.close();
+                
+                if (searchable != null) {
+                    // no need to keep the context any longer.  setup time is over.
+                    searchable.mCacheActivityContext  = null;
+                    
+                    newSearchablesList.add(searchable);
+                    newSearchablesMap.put(cName, searchable);
+                }
+            }
+        }
+        
+        // record the final values as a coherent pair
+        synchronized (SearchableInfo.class) {
+            sSearchablesList = newSearchablesList;
+            sSearchablesMap = newSearchablesMap;
+        }
+    }
+    
+    /**
+     * Constructor
+     * 
+     * Given a ComponentName, get the searchability info
+     * and build a local copy of it.  Use the factory, not this.
+     * 
+     * @param context runtime context
+     * @param attr The attribute set we found in the XML file, contains the values that are used to
+     * construct the object.
+     * @param cName The component name of the searchable activity
+     */
+    private SearchableInfo(Context context, AttributeSet attr, final ComponentName cName) {
+        // initialize as an "unsearchable" object
+        mSearchable = false;
+        mSearchActivity = cName;
+        
+        // to access another activity's resources, I need its context.
+        // BE SURE to release the cache sometime after construction - it's a large object to hold
+        mCacheActivityContext = getActivityContext(context);
+        if (mCacheActivityContext != null) {
+            TypedArray a = mCacheActivityContext.obtainStyledAttributes(attr,
+                    com.android.internal.R.styleable.Searchable);
+            mSearchMode = a.getInt(com.android.internal.R.styleable.Searchable_searchMode, 0);
+            mLabelId = a.getResourceId(com.android.internal.R.styleable.Searchable_label, 0);
+            mHintId = a.getResourceId(com.android.internal.R.styleable.Searchable_hint, 0);
+            mIconId = a.getResourceId(com.android.internal.R.styleable.Searchable_icon, 0);
+            mSearchButtonText = a.getResourceId(
+                    com.android.internal.R.styleable.Searchable_searchButtonText, 0);
+            mSearchInputType = a.getInt(com.android.internal.R.styleable.Searchable_inputType, 
+                    InputType.TYPE_CLASS_TEXT |
+                    InputType.TYPE_TEXT_VARIATION_NORMAL);
+            mSearchImeOptions = a.getInt(com.android.internal.R.styleable.Searchable_imeOptions, 
+                    EditorInfo.IME_ACTION_SEARCH);
+
+            setSearchModeFlags();
+            if (DBG_INHIBIT_SUGGESTIONS == 0) {
+                mSuggestAuthority = a.getString(
+                        com.android.internal.R.styleable.Searchable_searchSuggestAuthority);
+                mSuggestPath = a.getString(
+                        com.android.internal.R.styleable.Searchable_searchSuggestPath);
+                mSuggestSelection = a.getString(
+                        com.android.internal.R.styleable.Searchable_searchSuggestSelection);
+                mSuggestIntentAction = a.getString(
+                        com.android.internal.R.styleable.Searchable_searchSuggestIntentAction);
+                mSuggestIntentData = a.getString(
+                        com.android.internal.R.styleable.Searchable_searchSuggestIntentData);
+            }
+            mVoiceSearchMode = 
+                a.getInt(com.android.internal.R.styleable.Searchable_voiceSearchMode, 0);
+            // TODO this didn't work - came back zero from YouTube
+            mVoiceLanguageModeId = 
+                a.getResourceId(com.android.internal.R.styleable.Searchable_voiceLanguageModel, 0);
+            mVoicePromptTextId = 
+                a.getResourceId(com.android.internal.R.styleable.Searchable_voicePromptText, 0);
+            mVoiceLanguageId = 
+                a.getResourceId(com.android.internal.R.styleable.Searchable_voiceLanguage, 0);
+            mVoiceMaxResults = 
+                a.getInt(com.android.internal.R.styleable.Searchable_voiceMaxResults, 0);
+
+            a.recycle();
+
+            // get package info for suggestions provider (if any)
+            if (mSuggestAuthority != null) {
+                ProviderInfo pi =
+                    context.getPackageManager().resolveContentProvider(mSuggestAuthority,
+                            0);
+                if (pi != null) {
+                    mSuggestProviderPackage = pi.packageName;
+                }
+            }
+        }
+
+        // for now, implement some form of rules - minimal data
+        if (mLabelId != 0) {
+            mSearchable = true;
+        } else {
+            // Provide some help for developers instead of just silently discarding
+            Log.w(LOG_TAG, "Insufficient metadata to configure searchability for " + 
+                    cName.flattenToShortString());
+        }
+    }
+
+    /**
+     * Convert searchmode to flags.
+     */
+    private void setSearchModeFlags() {
+        mBadgeLabel = (0 != (mSearchMode & 4));
+        mBadgeIcon = (0 != (mSearchMode & 8)) && (mIconId != 0);
+        mQueryRewriteFromData = (0 != (mSearchMode & 0x10));
+        mQueryRewriteFromText = (0 != (mSearchMode & 0x20));
+    }
+    
+    /**
+     * Private class used to hold the "action key" configuration
+     */
+    public class ActionKeyInfo implements Parcelable {
+        
+        public int mKeyCode = 0;
+        public String mQueryActionMsg;
+        public String mSuggestActionMsg;
+        public String mSuggestActionMsgColumn;
+        private ActionKeyInfo mNext;
+        
+        /**
+         * Create one object using attributeset as input data.
+         * @param context runtime context
+         * @param attr The attribute set we found in the XML file, contains the values that are used to
+         * construct the object.
+         * @param next We'll build these up using a simple linked list (since there are usually
+         * just zero or one).
+         */
+        public ActionKeyInfo(Context context, AttributeSet attr, ActionKeyInfo next) {
+            TypedArray a = mCacheActivityContext.obtainStyledAttributes(attr,
+                    com.android.internal.R.styleable.SearchableActionKey);
+
+            mKeyCode = a.getInt(
+                    com.android.internal.R.styleable.SearchableActionKey_keycode, 0);
+            mQueryActionMsg = a.getString(
+                    com.android.internal.R.styleable.SearchableActionKey_queryActionMsg);
+            if (DBG_INHIBIT_SUGGESTIONS == 0) {
+                mSuggestActionMsg = a.getString(
+                        com.android.internal.R.styleable.SearchableActionKey_suggestActionMsg);
+                mSuggestActionMsgColumn = a.getString(
+                        com.android.internal.R.styleable.SearchableActionKey_suggestActionMsgColumn);
+            }
+            a.recycle();
+
+            // initialize any other fields
+            mNext = next;
+
+            // sanity check.  must have at least one action message, or invalidate the object.
+            if ((mQueryActionMsg == null) && 
+                    (mSuggestActionMsg == null) && 
+                    (mSuggestActionMsgColumn == null)) {
+                mKeyCode = 0;
+            }           
+        }
+
+        /**
+         * Instantiate a new ActionKeyInfo from the data in a Parcel that was
+         * previously written with {@link #writeToParcel(Parcel, int)}.
+         *
+         * @param in The Parcel containing the previously written ActionKeyInfo,
+         * positioned at the location in the buffer where it was written.
+         * @param next The value to place in mNext, creating a linked list
+         */
+        public ActionKeyInfo(Parcel in, ActionKeyInfo next) {
+            mKeyCode = in.readInt();
+            mQueryActionMsg = in.readString();
+            mSuggestActionMsg = in.readString();
+            mSuggestActionMsgColumn = in.readString();
+            mNext = next;
+        }
+
+        public int describeContents() {
+            return 0;
+        }
+
+        public void writeToParcel(Parcel dest, int flags) {
+            dest.writeInt(mKeyCode);
+            dest.writeString(mQueryActionMsg);
+            dest.writeString(mSuggestActionMsg);
+            dest.writeString(mSuggestActionMsgColumn);
+        }
+    }
+    
+    /**
+     * If any action keys were defined for this searchable activity, look up and return.
+     * 
+     * @param keyCode The key that was pressed
+     * @return Returns the ActionKeyInfo record, or null if none defined
+     */
+    public ActionKeyInfo findActionKey(int keyCode) {
+        ActionKeyInfo info = mActionKeyList;
+        while (info != null) {
+            if (info.mKeyCode == keyCode) {
+                return info;
+            }
+            info = info.mNext;
+        }
+        return null;
+    }
+    
+    /**
+     * Get the metadata for a given activity
+     * 
+     * TODO: clean up where we return null vs. where we throw exceptions.
+     * 
+     * @param context runtime context
+     * @param xml XML parser for reading attributes
+     * @param cName The component name of the searchable activity
+     * 
+     * @result A completely constructed SearchableInfo, or null if insufficient XML data for it
+     */
+    private static SearchableInfo getActivityMetaData(Context context, XmlPullParser xml,
+            final ComponentName cName)  {
+        SearchableInfo result = null;
+        
+        // in order to use the attributes mechanism, we have to walk the parser
+        // forward through the file until it's reading the tag of interest.
+        try {
+            int tagType = xml.next();
+            while (tagType != XmlPullParser.END_DOCUMENT) {
+                if (tagType == XmlPullParser.START_TAG) {
+                    if (xml.getName().equals(MD_XML_ELEMENT_SEARCHABLE)) {
+                        AttributeSet attr = Xml.asAttributeSet(xml);
+                        if (attr != null) {
+                            result = new SearchableInfo(context, attr, cName);
+                            // if the constructor returned a bad object, exit now.
+                            if (! result.mSearchable) {
+                                return null;
+                            }
+                        }
+                    } else if (xml.getName().equals(MD_XML_ELEMENT_SEARCHABLE_ACTION_KEY)) {
+                        if (result == null) {
+                            // Can't process an embedded element if we haven't seen the enclosing
+                            return null;
+                        }
+                        AttributeSet attr = Xml.asAttributeSet(xml);
+                        if (attr != null) {
+                            ActionKeyInfo keyInfo = result.new ActionKeyInfo(context, attr, 
+                                    result.mActionKeyList);
+                            // only add to list if it is was useable
+                            if (keyInfo.mKeyCode != 0) {
+                                result.mActionKeyList = keyInfo;
+                            }
+                        }
+                    }
+                }
+                tagType = xml.next();
+            }
+        } catch (XmlPullParserException e) {
+            throw new RuntimeException(e);
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+        return result;
+    }
+    
+    /**
+     * Return the "label" (user-visible name) of this searchable context.  This must be 
+     * accessed using the target (searchable) Activity's resources, not simply the context of the
+     * caller.
+     * 
+     * @return Returns the resource Id
+     */
+    public int getLabelId() {
+        return mLabelId;
+    }
+    
+    /**
+     * Return the resource Id of the hint text.  This must be 
+     * accessed using the target (searchable) Activity's resources, not simply the context of the
+     * caller.
+     * 
+     * @return Returns the resource Id, or 0 if not specified by this package.
+     */
+    public int getHintId() {
+        return mHintId;
+    }
+    
+    /**
+     * Return the icon Id specified by the Searchable_icon meta-data entry.  This must be 
+     * accessed using the target (searchable) Activity's resources, not simply the context of the
+     * caller.
+     * 
+     * @return Returns the resource id.
+     */
+    public int getIconId() {
+        return mIconId;
+    }
+    
+    /**
+     * @return true if android:voiceSearchMode="showVoiceSearchButton"
+     */
+    public boolean getVoiceSearchEnabled() {
+        return 0 != (mVoiceSearchMode & VOICE_SEARCH_SHOW_BUTTON);
+    }
+    
+    /**
+     * @return true if android:voiceSearchMode="launchWebSearch"
+     */
+    public boolean getVoiceSearchLaunchWebSearch() {
+        return 0 != (mVoiceSearchMode & VOICE_SEARCH_LAUNCH_WEB_SEARCH);
+    }
+    
+    /**
+     * @return true if android:voiceSearchMode="launchRecognizer"
+     */
+    public boolean getVoiceSearchLaunchRecognizer() {
+        return 0 != (mVoiceSearchMode & VOICE_SEARCH_LAUNCH_RECOGNIZER);
+    }
+    
+    /**
+     * @return the resource Id of the language model string, if specified in the searchable
+     * activity's metadata, or 0 if not specified.  
+     */
+    public int getVoiceLanguageModeId() {
+        return mVoiceLanguageModeId;
+    }
+    
+    /**
+     * @return the resource Id of the voice prompt text string, if specified in the searchable
+     * activity's metadata, or 0 if not specified.  
+     */
+    public int getVoicePromptTextId() {
+        return mVoicePromptTextId;
+    }
+    
+    /**
+     * @return the resource Id of the spoken langauge, if specified in the searchable
+     * activity's metadata, or 0 if not specified.  
+     */
+    public int getVoiceLanguageId() {
+        return mVoiceLanguageId;
+    }
+    
+    /**
+     * @return the max results count, if specified in the searchable
+     * activity's metadata, or 0 if not specified.  
+     */
+    public int getVoiceMaxResults() {
+        return mVoiceMaxResults;
+    }
+    
+    /**
+     * Return the resource Id of replacement text for the "Search" button.
+     * 
+     * @return Returns the resource Id, or 0 if not specified by this package.
+     */
+    public int getSearchButtonText() {
+        return mSearchButtonText;
+    }
+    
+    /**
+     * Return the input type as specified in the searchable attributes.  This will default to
+     * InputType.TYPE_CLASS_TEXT if not specified (which is appropriate for free text input).
+     * 
+     * @return the input type
+     */
+    public int getInputType() {
+        return mSearchInputType;
+    }
+    
+    /**
+     * Return the input method options specified in the searchable attributes.
+     * This will default to EditorInfo.ACTION_SEARCH if not specified (which is
+     * appropriate for a search box).
+     * 
+     * @return the input type
+     */
+    public int getImeOptions() {
+        return mSearchImeOptions;
+    }
+    
+    /**
+     * Return the list of searchable activities, for use in the drop-down.
+     */
+    public static ArrayList<SearchableInfo> getSearchablesList() {
+        synchronized (SearchableInfo.class) {
+            ArrayList<SearchableInfo> result = new ArrayList<SearchableInfo>(sSearchablesList);
+            return result;
+        }
+    }
+    
+    /**
+     * Support for parcelable and aidl operations.
+     */
+    public static final Parcelable.Creator<SearchableInfo> CREATOR
+    = new Parcelable.Creator<SearchableInfo>() {
+        public SearchableInfo createFromParcel(Parcel in) {
+            return new SearchableInfo(in);
+        }
+
+        public SearchableInfo[] newArray(int size) {
+            return new SearchableInfo[size];
+        }
+    };
+
+    /**
+     * Instantiate a new SearchableInfo from the data in a Parcel that was
+     * previously written with {@link #writeToParcel(Parcel, int)}.
+     *
+     * @param in The Parcel containing the previously written SearchableInfo,
+     * positioned at the location in the buffer where it was written.
+     */
+    public SearchableInfo(Parcel in) {
+        mSearchable = in.readInt() != 0;
+        mLabelId = in.readInt();
+        mSearchActivity = ComponentName.readFromParcel(in);
+        mHintId = in.readInt();
+        mSearchMode = in.readInt();
+        mIconId = in.readInt();
+        mSearchButtonText = in.readInt();
+        mSearchInputType = in.readInt();
+        mSearchImeOptions = in.readInt();
+        setSearchModeFlags();
+
+        mSuggestAuthority = in.readString();
+        mSuggestPath = in.readString();
+        mSuggestSelection = in.readString();
+        mSuggestIntentAction = in.readString();
+        mSuggestIntentData = in.readString();
+
+        mActionKeyList = null;
+        int count = in.readInt();
+        while (count-- > 0) {
+            mActionKeyList = new ActionKeyInfo(in, mActionKeyList);
+        }
+        
+        mSuggestProviderPackage = in.readString();
+        
+        mVoiceSearchMode = in.readInt();
+        mVoiceLanguageModeId = in.readInt();
+        mVoicePromptTextId = in.readInt();
+        mVoiceLanguageId = in.readInt();
+        mVoiceMaxResults = in.readInt();
+    }
+
+    public int describeContents() {
+        return 0;
+    }
+
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeInt(mSearchable ? 1 : 0);
+        dest.writeInt(mLabelId);
+        mSearchActivity.writeToParcel(dest, flags);
+        dest.writeInt(mHintId);
+        dest.writeInt(mSearchMode);
+        dest.writeInt(mIconId);
+        dest.writeInt(mSearchButtonText);
+        dest.writeInt(mSearchInputType);
+        dest.writeInt(mSearchImeOptions);
+        
+        dest.writeString(mSuggestAuthority);
+        dest.writeString(mSuggestPath);
+        dest.writeString(mSuggestSelection);
+        dest.writeString(mSuggestIntentAction);
+        dest.writeString(mSuggestIntentData);
+
+        // This is usually a very short linked list so we'll just pre-count it
+        ActionKeyInfo nextKeyInfo = mActionKeyList;
+        int count = 0;
+        while (nextKeyInfo != null) {
+            ++count;
+            nextKeyInfo = nextKeyInfo.mNext;
+        }
+        dest.writeInt(count);
+        // Now write count of 'em
+        nextKeyInfo = mActionKeyList;
+        while (count-- > 0) {
+            nextKeyInfo.writeToParcel(dest, flags);
+        }
+        
+        dest.writeString(mSuggestProviderPackage);
+
+        dest.writeInt(mVoiceSearchMode);
+        dest.writeInt(mVoiceLanguageModeId);
+        dest.writeInt(mVoicePromptTextId);
+        dest.writeInt(mVoiceLanguageId);
+        dest.writeInt(mVoiceMaxResults);
+    }
+}
diff --git a/core/java/android/server/search/package.html b/core/java/android/server/search/package.html
new file mode 100644
index 0000000..c9f96a6
--- /dev/null
+++ b/core/java/android/server/search/package.html
@@ -0,0 +1,5 @@
+<body>
+
+{@hide}
+
+</body>
diff --git a/core/java/android/speech/RecognizerIntent.java b/core/java/android/speech/RecognizerIntent.java
new file mode 100644
index 0000000..987e763
--- /dev/null
+++ b/core/java/android/speech/RecognizerIntent.java
@@ -0,0 +1,157 @@
+/*
+ * 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 android.speech;
+
+import android.app.Activity;
+import android.content.ActivityNotFoundException;
+import android.content.Intent;
+
+/**
+ * Constants for supporting speech recognition through starting an {@link Intent}
+ */
+public class RecognizerIntent {
+    private RecognizerIntent() {
+        // Not for instantiating.
+    }
+
+    /**
+     * Starts an activity that will prompt the user for speech and sends it through a
+     * speech recognizer.  The results will be returned via activity results, or forwarded
+     * via a PendingIntent if one is provided.
+     * 
+     * <p>Required extras:
+     * <ul>
+     *   <li>{@link #EXTRA_LANGUAGE_MODEL}
+     * </ul>
+     * 
+     * <p>Optional extras:
+     * <ul>
+     *   <li>{@link #EXTRA_PROMPT}
+     *   <li>{@link #EXTRA_LANGUAGE}
+     *   <li>{@link #EXTRA_MAX_RESULTS}
+     *   <li>{@link #EXTRA_RESULTS_PENDINGINTENT}
+     *   <li>{@link #EXTRA_RESULTS_PENDINGINTENT_BUNDLE}
+     * </ul>
+     * 
+     * <p> Result extras:
+     * <ul>
+     *   <li>{@link #EXTRA_RESULTS}
+     * </ul>
+     * 
+     * <p>NOTE: There may not be any applications installed to handle this action, so you should
+     * make sure to catch {@link ActivityNotFoundException}.
+     */
+    public static final String ACTION_RECOGNIZE_SPEECH = "android.speech.action.RECOGNIZE_SPEECH";
+
+    /**
+     * Starts an activity that will prompt the user for speech, sends it through a
+     * speech recognizer, and invokes and displays a web search result.
+     * 
+     * <p>Required extras:
+     * <ul>
+     *   <li>{@link #EXTRA_LANGUAGE_MODEL}
+     * </ul>
+     * 
+     * <p>Optional extras:
+     * <ul>
+     *   <li>{@link #EXTRA_PROMPT}
+     *   <li>{@link #EXTRA_LANGUAGE}
+     *   <li>{@link #EXTRA_MAX_RESULTS}
+     * </ul>
+     * 
+     * <p> Result extras:
+     * <ul>
+     *   <li>{@link #EXTRA_RESULTS}
+     * </ul>
+     * 
+     * <p>NOTE: There may not be any applications installed to handle this action, so you should
+     * make sure to catch {@link ActivityNotFoundException}.
+     */
+    public static final String ACTION_WEB_SEARCH = "android.speech.action.WEB_SEARCH";
+
+    /**
+     * Informs the recognizer which speech model to prefer when performing
+     * {@link #ACTION_RECOGNIZE_SPEECH}. The recognizer uses this
+     * information to fine tune the results. This extra is required. Activities implementing
+     * {@link #ACTION_RECOGNIZE_SPEECH} may interpret the values as they see fit.
+     * 
+     *  @see #LANGUAGE_MODEL_FREE_FORM
+     *  @see #LANGUAGE_MODEL_WEB_SEARCH
+     */
+    public static final String EXTRA_LANGUAGE_MODEL = "android.speech.extra.LANGUAGE_MODEL";
+
+    /** 
+     * Use a language model based on free-form speech recognition.  This is a value to use for 
+     * {@link #EXTRA_LANGUAGE_MODEL}. 
+     * @see #EXTRA_LANGUAGE_MODEL
+     */
+    public static final String LANGUAGE_MODEL_FREE_FORM = "free_form";
+    /** 
+     * Use a language model based on web search terms.  This is a value to use for 
+     * {@link #EXTRA_LANGUAGE_MODEL}. 
+     * @see #EXTRA_LANGUAGE_MODEL
+     */
+    public static final String LANGUAGE_MODEL_WEB_SEARCH = "web_search";
+
+    /** Optional text prompt to show to the user when asking them to speak. */
+    public static final String EXTRA_PROMPT = "android.speech.extra.PROMPT";
+
+    /**
+     * Optional language override to inform the recognizer that it should expect speech in
+     * a language different than the one set in the {@link java.util.Locale#getDefault()}. 
+     */
+    public static final String EXTRA_LANGUAGE = "android.speech.extra.LANGUAGE";
+
+    /** 
+     * Optional limit on the maximum number of results to return. If omitted the recognizer
+     * will choose how many results to return. Must be an integer.
+     */
+    public static final String EXTRA_MAX_RESULTS = "android.speech.extra.MAX_RESULTS";
+    
+    /**
+     * When the intent is {@link #ACTION_RECOGNIZE_SPEECH}, the speech input activity will
+     * return results to you via the activity results mechanism.  Alternatively, if you use this
+     * extra to supply a PendingIntent, the results will be added to its bundle and the 
+     * PendingIntent will be sent to its target.
+     */
+    public static final String EXTRA_RESULTS_PENDINGINTENT = 
+            "android.speech.extra.RESULTS_PENDINGINTENT";
+    /**
+     * If you use {@link #EXTRA_RESULTS_PENDINGINTENT} to supply a forwarding intent, you can
+     * also use this extra to supply additional extras for the final intent.  The search results
+     * will be added to this bundle, and the combined bundle will be sent to the target.
+     */
+    public static final String EXTRA_RESULTS_PENDINGINTENT_BUNDLE = 
+            "android.speech.extra.RESULTS_PENDINGINTENT_BUNDLE";
+
+    /** Result code returned when no matches are found for the given speech */
+    public static final int RESULT_NO_MATCH = Activity.RESULT_FIRST_USER;
+    /** Result code returned when there is a generic client error */
+    public static final int RESULT_CLIENT_ERROR = Activity.RESULT_FIRST_USER + 1;
+    /** Result code returned when the recognition server returns an error */
+    public static final int RESULT_SERVER_ERROR = Activity.RESULT_FIRST_USER + 2;
+    /** Result code returned when a network error was encountered */
+    public static final int RESULT_NETWORK_ERROR = Activity.RESULT_FIRST_USER + 3;
+    /** Result code returned when an audio error was encountered */
+    public static final int RESULT_AUDIO_ERROR = Activity.RESULT_FIRST_USER + 4;
+
+    /**
+     * An ArrayList<String> of the potential results when performing
+     * {@link #ACTION_RECOGNIZE_SPEECH}. Only present when {@link Activity#RESULT_OK} is returned.
+     */
+    public static final String EXTRA_RESULTS = "android.speech.extra.RESULTS";
+}
diff --git a/core/java/android/speech/srec/MicrophoneInputStream.java b/core/java/android/speech/srec/MicrophoneInputStream.java
new file mode 100644
index 0000000..fab77a9
--- /dev/null
+++ b/core/java/android/speech/srec/MicrophoneInputStream.java
@@ -0,0 +1,110 @@
+/*---------------------------------------------------------------------------*
+ *  MicrophoneInputStream.java                                               *
+ *                                                                           *
+ *  Copyright 2007 Nuance Communciations, Inc.                               *
+ *                                                                           *
+ *  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 android.speech.srec;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.IllegalStateException;
+
+
+/**
+ * PCM input stream from the microphone, 16 bits per sample.
+ */
+public final class MicrophoneInputStream extends InputStream {
+    static {
+        System.loadLibrary("srec_jni");
+    }
+    
+    private final static String TAG = "MicrophoneInputStream";
+    private int mAudioRecord = 0;
+    private byte[] mOneByte = new byte[1];
+    
+    /**
+     * MicrophoneInputStream constructor.
+     * @param sampleRate sample rate of the microphone, typically 11025 or 8000.
+     * @param fifoDepth depth of the real time fifo, measured in sampleRate clock ticks.
+     * This determines how long an application may delay before losing data.
+     */
+    public MicrophoneInputStream(int sampleRate, int fifoDepth) throws IOException {
+        mAudioRecord = AudioRecordNew(sampleRate, fifoDepth);
+        if (mAudioRecord == 0) throw new IOException("AudioRecord constructor failed - busy?");
+        int status = AudioRecordStart(mAudioRecord);
+        if (status != 0) {
+            close();
+            throw new IOException("AudioRecord start failed: " + status);
+        }
+    }
+
+    @Override
+    public int read() throws IOException {
+        if (mAudioRecord == 0) throw new IllegalStateException("not open");
+        int rtn = AudioRecordRead(mAudioRecord, mOneByte, 0, 1);
+        return rtn == 1 ? ((int)mOneByte[0] & 0xff) : -1;
+    }
+
+    @Override
+    public int read(byte[] b) throws IOException {
+        if (mAudioRecord == 0) throw new IllegalStateException("not open");
+        return AudioRecordRead(mAudioRecord, b, 0, b.length);
+    }
+    
+    @Override
+    public int read(byte[] b, int offset, int length) throws IOException {
+        if (mAudioRecord == 0) throw new IllegalStateException("not open");
+        // TODO: should we force all reads to be a multiple of the sample size?
+        return AudioRecordRead(mAudioRecord, b, offset, length);
+    }
+    
+    /**
+     * Closes this stream.
+     */
+    @Override
+    public void close() throws IOException {
+        if (mAudioRecord != 0) {
+            try {
+                AudioRecordStop(mAudioRecord);
+            } finally {
+                try {
+                    AudioRecordDelete(mAudioRecord);
+                } finally {
+                    mAudioRecord = 0;
+                }
+            }
+        }
+    }
+    
+    @Override
+    protected void finalize() throws Throwable {
+        if (mAudioRecord != 0) {
+            close();
+            throw new IOException("someone forgot to close MicrophoneInputStream");
+        }
+    }
+    
+    //
+    // AudioRecord JNI interface
+    //
+    private static native int AudioRecordNew(int sampleRate, int fifoDepth);
+    private static native int AudioRecordStart(int audioRecord);
+    private static native int AudioRecordRead(int audioRecord, byte[] b, int offset, int length) throws IOException;
+    private static native void AudioRecordStop(int audioRecord) throws IOException;
+    private static native void AudioRecordDelete(int audioRecord) throws IOException;
+}
diff --git a/core/java/android/speech/srec/Recognizer.java b/core/java/android/speech/srec/Recognizer.java
new file mode 100644
index 0000000..a03a36a
--- /dev/null
+++ b/core/java/android/speech/srec/Recognizer.java
@@ -0,0 +1,719 @@
+/*
+ * ---------------------------------------------------------------------------
+ * Recognizer.java
+ * 
+ * Copyright 2007 Nuance Communciations, Inc.
+ * 
+ * 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 android.speech.srec;
+
+import android.util.Config;
+import android.util.Log;
+
+import java.io.File;
+import java.io.InputStream;
+import java.io.IOException;
+import java.util.Locale;
+
+/**
+ * Simple, synchronous speech recognizer, using the Nuance SREC package.
+ * Usages proceeds as follows:
+ * 
+ * <ul>
+ * <li>Create a <code>Recognizer</code>.
+ * <li>Create a <code>Recognizer.Grammar</code>.
+ * <li>Setup the <code>Recognizer.Grammar</code>.
+ * <li>Reset the <code>Recognizer.Grammar</code> slots, if needed.
+ * <li>Fill the <code>Recognizer.Grammar</code> slots, if needed.
+ * <li>Compile the <code>Recognizer.Grammar</code>, if needed.
+ * <li>Save the filled <code>Recognizer.Grammar</code>, if needed.
+ * <li>Start the <code>Recognizer</code>.
+ * <li>Loop over <code>advance</code> and <code>putAudio</code> until recognition complete.
+ * <li>Fetch and process results, or notify of failure.
+ * <li>Stop the <code>Recognizer</code>.
+ * <li>Destroy the <code>Recognizer</code>.
+ * </ul>
+ * 
+ * <p>Below is example code</p>
+ * 
+ * <pre class="prettyprint">
+ * 
+ * // create and start audio input
+ * InputStream audio = new MicrophoneInputStream(11025, 11025*5);
+ * // create a Recognizer
+ * String cdir = Recognizer.getConfigDir(null);
+ * Recognizer recognizer = new Recognizer(cdir + "/baseline11k.par");
+ * // create and load a Grammar
+ * Recognizer.Grammar grammar = recognizer.new Grammar(cdir + "/grammars/VoiceDialer.g2g");
+ * // setup the Grammar to work with the Recognizer
+ * grammar.setupRecognizer();
+ * // fill the Grammar slots with names and save, if required
+ * grammar.resetAllSlots();
+ * for (String name : names) grammar.addWordToSlot("@Names", name, null, 1, "V=1");
+ * grammar.compile();
+ * grammar.save(".../foo.g2g");
+ * // start the Recognizer
+ * recognizer.start();
+ * // loop over Recognizer events
+ * while (true) {
+ *     switch (recognizer.advance()) {
+ *     case Recognizer.EVENT_INCOMPLETE:
+ *     case Recognizer.EVENT_STARTED:
+ *     case Recognizer.EVENT_START_OF_VOICING:
+ *     case Recognizer.EVENT_END_OF_VOICING:
+ *         // let the Recognizer continue to run
+ *         continue;
+ *     case Recognizer.EVENT_RECOGNITION_RESULT:
+ *         // success, so fetch results here!
+ *         for (int i = 0; i < recognizer.getResultCount(); i++) {
+ *             String result = recognizer.getResult(i, Recognizer.KEY_LITERAL);
+ *         }
+ *         break;
+ *     case Recognizer.EVENT_NEED_MORE_AUDIO:
+ *         // put more audio in the Recognizer
+ *         recognizer.putAudio(audio);
+ *         continue;
+ *     default:
+ *         notifyFailure();
+ *         break;
+ *     }
+ *     break;
+ * }
+ * // stop the Recognizer
+ * recognizer.stop();
+ * // destroy the Recognizer
+ * recognizer.destroy();
+ * // stop the audio device
+ * audio.close();
+ * 
+ * </pre>
+ */
+public final class Recognizer {
+    static {
+        System.loadLibrary("srec_jni");
+    }
+
+    private static String TAG = "Recognizer";
+    
+    /**
+     * Result key corresponding to confidence score.
+     */
+    public static final String KEY_CONFIDENCE = "conf";
+    
+    /**
+     * Result key corresponding to literal text.
+     */
+    public static final String KEY_LITERAL = "literal";
+    
+    /**
+     * Result key corresponding to semantic meaning text.
+     */
+    public static final String KEY_MEANING = "meaning";
+
+    // handle to SR_Vocabulary object
+    private int mVocabulary = 0;
+    
+    // handle to SR_Recognizer object
+    private int mRecognizer = 0;
+    
+    // Grammar currently associated with Recognizer via SR_GrammarSetupRecognizer
+    private Grammar mActiveGrammar = null;
+    
+    /**
+     * Get the pathname of the SREC configuration directory corresponding to the
+     * language indicated by the Locale.
+     * This directory contains dictionaries, speech models,
+     * configuration files, and other data needed by the Recognizer.
+     * @param locale <code>Locale</code> corresponding to the desired language,
+     * or null for default, currently <code>Locale.US</code>.
+     * @return Pathname of the configuration directory.
+     */
+    public static String getConfigDir(Locale locale) {
+        if (locale == null) locale = Locale.US;
+        String dir = "/system/usr/srec/config/" +
+                locale.toString().replace('_', '.').toLowerCase();
+        if ((new File(dir)).isDirectory()) return dir;
+        return null;
+    }
+
+    /**
+     * Create an instance of a SREC speech recognizer.
+     * 
+     * @param configFile pathname of the baseline*.par configuration file,
+     * which in turn contains references to dictionaries, speech models,
+     * and other data needed to configure and operate the recognizer.
+     * A separate config file is needed for each audio sample rate.
+     * Two files, baseline11k.par and baseline8k.par, which correspond to
+     * 11025 and 8000 hz, are present in the directory indicated by
+     * {@link #getConfigDir}.
+     * @throws IOException
+     */
+    public Recognizer(String configFile) throws IOException {
+        PMemInit();
+        SR_SessionCreate(configFile);
+        mRecognizer = SR_RecognizerCreate();
+        SR_RecognizerSetup(mRecognizer);
+        mVocabulary = SR_VocabularyLoad();
+    }
+
+    /**
+     * Represents a grammar loaded into the Recognizer.
+     */
+    public class Grammar {
+        private int mGrammar = 0;
+
+        /**
+         * Create a <code>Grammar</code> instance.
+         * @param g2gFileName pathname of g2g file.
+         */
+        public Grammar(String g2gFileName) throws IOException {
+            mGrammar = SR_GrammarLoad(g2gFileName);
+            SR_GrammarSetupVocabulary(mGrammar, mVocabulary);
+        }
+
+        /**
+         * Reset all slots.
+         */
+        public void resetAllSlots() {
+            SR_GrammarResetAllSlots(mGrammar);
+        }
+
+        /**
+         * Add a word to a slot.
+         * 
+         * @param slot slot name.
+         * @param word word to insert.
+         * @param pron pronunciation, or null to derive from word.
+         * @param weight weight to give the word.  One is normal, 50 is low.
+         * @param tag semantic meaning tag string.
+         */
+        public void addWordToSlot(String slot, String word, String pron, int weight, String tag) {
+            SR_GrammarAddWordToSlot(mGrammar, slot, word, pron, weight, tag); 
+        }
+
+        /**
+         * Compile all slots.
+         */
+        public void compile() {
+            SR_GrammarCompile(mGrammar);
+        }
+
+        /**
+         * Setup <code>Grammar</code> with <code>Recognizer</code>.
+         */
+        public void setupRecognizer() {
+            SR_GrammarSetupRecognizer(mGrammar, mRecognizer);
+            mActiveGrammar = this;
+        }
+
+        /**
+         * Save <code>Grammar</code> to g2g file.
+         * 
+         * @param g2gFileName
+         * @throws IOException
+         */
+        public void save(String g2gFileName) throws IOException {
+            SR_GrammarSave(mGrammar, g2gFileName);
+        }
+
+        /**
+         * Release resources associated with this <code>Grammar</code>.
+         */
+        public void destroy() {
+            // TODO: need to do cleanup and disassociation with Recognizer
+            if (mGrammar != 0) {
+                SR_GrammarDestroy(mGrammar);
+                mGrammar = 0;
+            }
+        }
+
+        /**
+         * Clean up resources.
+         */
+        protected void finalize() {
+            if (mGrammar != 0) {
+                destroy();
+                throw new IllegalStateException("someone forgot to destroy Grammar");
+            }
+        }
+    }
+
+    /**
+     * Start recognition
+     */
+    public void start() {
+        // TODO: shouldn't be here?
+        SR_RecognizerActivateRule(mRecognizer, mActiveGrammar.mGrammar, "trash", 1);
+        SR_RecognizerStart(mRecognizer);
+    }
+    
+    /**
+     * Process some audio and return the current status.
+     * @return recognition event, one of:
+     * <ul>
+     * <li><code>EVENT_INVALID</code>
+     * <li><code>EVENT_NO_MATCH</code>
+     * <li><code>EVENT_INCOMPLETE</code>
+     * <li><code>EVENT_STARTED</code>
+     * <li><code>EVENT_STOPPED</code>
+     * <li><code>EVENT_START_OF_VOICING</code>
+     * <li><code>EVENT_END_OF_VOICING</code>
+     * <li><code>EVENT_SPOKE_TOO_SOON</code>
+     * <li><code>EVENT_RECOGNITION_RESULT</code>
+     * <li><code>EVENT_START_OF_UTTERANCE_TIMEOUT</code>
+     * <li><code>EVENT_RECOGNITION_TIMEOUT</code>
+     * <li><code>EVENT_NEED_MORE_AUDIO</code>
+     * <li><code>EVENT_MAX_SPEECH</code>
+     * </ul>
+     */
+    public int advance() {
+        return SR_RecognizerAdvance(mRecognizer);
+    }
+    
+    /**
+     * Put audio samples into the <code>Recognizer</code>.
+     * @param buf holds the audio samples.
+     * @param offset offset of the first sample.
+     * @param length number of bytes containing samples.
+     * @param isLast indicates no more audio data, normally false.
+     * @return number of bytes accepted.
+     */
+    public int putAudio(byte[] buf, int offset, int length, boolean isLast) {
+        return SR_RecognizerPutAudio(mRecognizer, buf, offset, length, isLast);
+    }
+    
+    /**
+     * Read audio samples from an <code>InputStream</code> and put them in the
+     * <code>Recognizer</code>.
+     * @param audio <code>InputStream</code> containing PCM audio samples.
+     */
+    public void putAudio(InputStream audio) throws IOException {
+        // make sure the audio buffer is allocated
+        if (mPutAudioBuffer == null) mPutAudioBuffer = new byte[512];
+        // read some data
+        int nbytes = audio.read(mPutAudioBuffer);
+        // eof, so signal Recognizer
+        if (nbytes == -1) {
+            SR_RecognizerPutAudio(mRecognizer, mPutAudioBuffer, 0, 0, true);
+        }
+        // put it into the Recognizer
+        else if (nbytes != SR_RecognizerPutAudio(mRecognizer, mPutAudioBuffer, 0, nbytes, false)) {
+            throw new IOException("SR_RecognizerPutAudio failed nbytes=" + nbytes);
+        }
+    }
+    
+    // audio buffer for putAudio(InputStream)
+    private byte[] mPutAudioBuffer = null;
+
+    /**
+     * Get the number of recognition results.  Must be called after
+     * <code>EVENT_RECOGNITION_RESULT</code> is returned by
+     * <code>advance</code>, but before <code>stop</code>.
+     * 
+     * @return number of results in nbest list.
+     */
+    public int getResultCount() {
+        return SR_RecognizerResultGetSize(mRecognizer);
+    }
+
+    /**
+     * Get a set of keys for the result.  Must be called after
+     * <code>EVENT_RECOGNITION_RESULT</code> is returned by
+     * <code>advance</code>, but before <code>stop</code>.
+     * 
+     * @param index index of result.
+     * @return array of keys.
+     */
+    public String[] getResultKeys(int index) {
+        return SR_RecognizerResultGetKeyList(mRecognizer, index);
+    }
+
+    /**
+     * Get a result value.  Must be called after
+     * <code>EVENT_RECOGNITION_RESULT</code> is returned by
+     * <code>advance</code>, but before <code>stop</code>.
+     * 
+     * @param index index of the result.
+     * @param key key of the result.  This is typically one of
+     * <code>KEY_CONFIDENCE</code>, <code>KEY_LITERAL</code>, or
+     * <code>KEY_MEANING</code>, but the user can also define their own keys
+     * in a grxml file, or in the <code>tag</code> slot of
+     * <code>Grammar.addWordToSlot</code>.
+     * @return the result.
+     */
+    public String getResult(int index, String key) {
+        return SR_RecognizerResultGetValue(mRecognizer, index, key);
+    }
+
+    /**
+     * Stop the <code>Recognizer</code>.
+     */
+    public void stop() {
+        SR_RecognizerStop(mRecognizer);
+        SR_RecognizerDeactivateRule(mRecognizer, mActiveGrammar.mGrammar, "trash");
+    }
+    
+    /**
+     * Reset the acoustic state vectorto it's default value.
+     * 
+     * @hide
+     */
+    public void resetAcousticState() {
+        SR_AcousticStateReset(mRecognizer);
+    }
+    
+    /**
+     * Set the acoustic state vector.
+     * @param state String containing the acoustic state vector.
+     * 
+     * @hide
+     */
+    public void setAcousticState(String state) {
+        SR_AcousticStateSet(mRecognizer, state);
+    }
+    
+    /**
+     * Get the acoustic state vector.
+     * @return String containing the acoustic state vector.
+     * 
+     * @hide
+     */
+    public String getAcousticState() {
+        return SR_AcousticStateGet(mRecognizer);
+    }
+
+    /**
+     * Clean up resources.
+     */
+    public void destroy() {
+        try {
+            if (mVocabulary != 0) SR_VocabularyDestroy(mVocabulary);
+        } finally {
+            mVocabulary = 0;
+            try {
+                if (mRecognizer != 0) SR_RecognizerUnsetup(mRecognizer);
+            } finally {
+                try {
+                    if (mRecognizer != 0) SR_RecognizerDestroy(mRecognizer);
+                } finally {
+                    mRecognizer = 0;
+                    try {
+                        SR_SessionDestroy();
+                    } finally {
+                        PMemShutdown();
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Clean up resources.
+     */
+    protected void finalize() throws Throwable {
+        if (mVocabulary != 0 || mRecognizer != 0) {
+            destroy();
+            throw new IllegalStateException("someone forgot to destroy Recognizer");
+        }
+    }
+    
+    /* an example session captured, for reference
+    void doall() {
+        if (PMemInit ( )
+           || lhs_audioinOpen ( WAVE_MAPPER, SREC_TEST_DEFAULT_AUDIO_FREQUENCY, &audio_in_handle )
+           || srec_test_init_application_data ( &applicationData, argc, argv )
+           || SR_SessionCreate ( "/system/usr/srec/config/en.us/baseline11k.par" )
+           || SR_RecognizerCreate ( &applicationData.recognizer )
+           || SR_RecognizerSetup ( applicationData.recognizer)
+           || ESR_SessionGetLCHAR ( L("cmdline.vocabulary"), filename, &flen )
+           || SR_VocabularyLoad ( filename, &applicationData.vocabulary )
+           || SR_VocabularyGetLanguage ( applicationData.vocabulary, &applicationData.locale )
+           || (applicationData.nametag = NULL)
+           || SR_NametagsCreate ( &applicationData.nametags )
+           || (LSTRCPY ( applicationData.grammars [0].grammar_path, "/system/usr/srec/config/en.us/grammars/VoiceDialer.g2g" ), 0)
+           || (LSTRCPY ( applicationData.grammars [0].grammarID, "BothTags" ), 0)
+           || (LSTRCPY ( applicationData.grammars [0].ruleName, "trash" ), 0)
+           || (applicationData.grammars [0].is_ve_grammar = ESR_FALSE, 0)
+           || SR_GrammarLoad (applicationData.grammars [0].grammar_path, &applicationData.grammars [applicationData.grammarCount].grammar )
+           || SR_GrammarSetupVocabulary ( applicationData.grammars [0].grammar, applicationData.vocabulary )
+           || SR_GrammarSetupRecognizer( applicationData.grammars [0].grammar, applicationData.recognizer )
+           || SR_GrammarSetDispatchFunction ( applicationData.grammars [0].grammar, L("myDSMCallback"), NULL, myDSMCallback )
+           || (applicationData.grammarCount++, 0)
+           || SR_RecognizerActivateRule ( applicationData.recognizer, applicationData.grammars [0].grammar,
+                           applicationData.grammars [0].ruleName, 1 )
+           || (applicationData.active_grammar_num = 0, 0)
+           || lhs_audioinStart ( audio_in_handle )
+           || SR_RecognizerStart ( applicationData.recognizer )
+           || strl ( applicationData.grammars [0].grammar, &applicationData, audio_in_handle, &recognition_count )
+           || SR_RecognizerStop ( applicationData.recognizer )
+           || lhs_audioinStop ( audio_in_handle )
+           || SR_RecognizerDeactivateRule ( applicationData.recognizer, applicationData.grammars [0].grammar, applicationData.grammars [0].ruleName )
+           || (applicationData.active_grammar_num = -1, 0)
+           || SR_GrammarDestroy ( applicationData.grammars [0].grammar )
+           || (applicationData.grammarCount--, 0)
+           || SR_NametagsDestroy ( applicationData.nametags )
+           || (applicationData.nametags = NULL, 0)
+           || SR_VocabularyDestroy ( applicationData.vocabulary )
+           || (applicationData.vocabulary = NULL)
+           || SR_RecognizerUnsetup ( applicationData.recognizer) // releases acoustic models
+           || SR_RecognizerDestroy ( applicationData.recognizer )
+           || (applicationData.recognizer = NULL)
+           || SR_SessionDestroy ( )
+           || srec_test_shutdown_application_data ( &applicationData )
+           || lhs_audioinClose ( &audio_in_handle )
+           || PMemShutdown ( )
+    }
+    */
+
+
+    //
+    // PMem native methods
+    //
+    private static native void PMemInit();
+    private static native void PMemShutdown();
+
+
+    //
+    // SR_Session native methods
+    //
+    private static native void SR_SessionCreate(String filename);
+    private static native void SR_SessionDestroy();
+
+
+    //
+    // SR_Recognizer native methods
+    //
+    
+    /**
+     * Reserved value.
+     */
+    public final static int EVENT_INVALID = 0;
+    
+    /**
+     * <code>Recognizer</code> could not find a match for the utterance.
+     */
+    public final static int EVENT_NO_MATCH = 1;
+    
+    /**
+     * <code>Recognizer</code> processed one frame of audio.
+     */
+    public final static int EVENT_INCOMPLETE = 2;
+    
+    /**
+     * <code>Recognizer</code> has just been started.
+     */
+    public final static int EVENT_STARTED = 3;
+    
+    /**
+     * <code>Recognizer</code> is stopped.
+     */
+    public final static int EVENT_STOPPED = 4;
+    
+    /**
+     * Beginning of speech detected.
+     */
+    public final static int EVENT_START_OF_VOICING = 5;
+    
+    /**
+     * End of speech detected.
+     */
+    public final static int EVENT_END_OF_VOICING = 6;
+    
+    /**
+     * Beginning of utterance occured too soon.
+     */
+    public final static int EVENT_SPOKE_TOO_SOON = 7;
+    
+    /**
+     * Recognition match detected.
+     */
+    public final static int EVENT_RECOGNITION_RESULT = 8;
+    
+    /**
+     * Timeout occured before beginning of utterance.
+     */
+    public final static int EVENT_START_OF_UTTERANCE_TIMEOUT = 9;
+    
+    /**
+     * Timeout occured before speech recognition could complete.
+     */
+    public final static int EVENT_RECOGNITION_TIMEOUT = 10;
+    
+    /**
+     * Not enough samples to process one frame.
+     */
+    public final static int EVENT_NEED_MORE_AUDIO = 11;
+    
+    /**
+     * More audio encountered than is allowed by 'swirec_max_speech_duration'.
+     */
+    public final static int EVENT_MAX_SPEECH = 12;
+
+    /**
+     * Produce a displayable string from an <code>advance</code> event.
+     * @param event
+     * @return String representing the event.
+     */
+    public static String eventToString(int event) {
+        switch (event) {
+            case EVENT_INVALID:
+                return "EVENT_INVALID";
+            case EVENT_NO_MATCH:
+                return "EVENT_NO_MATCH";
+            case EVENT_INCOMPLETE:
+                return "EVENT_INCOMPLETE";
+            case EVENT_STARTED:
+                return "EVENT_STARTED";
+            case EVENT_STOPPED:
+                return "EVENT_STOPPED";
+            case EVENT_START_OF_VOICING:
+                return "EVENT_START_OF_VOICING";
+            case EVENT_END_OF_VOICING:
+                return "EVENT_END_OF_VOICING";
+            case EVENT_SPOKE_TOO_SOON:
+                return "EVENT_SPOKE_TOO_SOON";
+            case EVENT_RECOGNITION_RESULT:
+                return "EVENT_RECOGNITION_RESULT";
+            case EVENT_START_OF_UTTERANCE_TIMEOUT:
+                return "EVENT_START_OF_UTTERANCE_TIMEOUT";
+            case EVENT_RECOGNITION_TIMEOUT:
+                return "EVENT_RECOGNITION_TIMEOUT";
+            case EVENT_NEED_MORE_AUDIO:
+                return "EVENT_NEED_MORE_AUDIO";
+            case EVENT_MAX_SPEECH:
+                return "EVENT_MAX_SPEECH";
+        }
+        return "EVENT_" + event;
+    }
+
+    //
+    // SR_Recognizer methods
+    //
+    private static native void SR_RecognizerStart(int recognizer);
+    private static native void SR_RecognizerStop(int recognizer);
+    private static native int SR_RecognizerCreate();
+    private static native void SR_RecognizerDestroy(int recognizer);
+    private static native void SR_RecognizerSetup(int recognizer);
+    private static native void SR_RecognizerUnsetup(int recognizer);
+    private static native boolean SR_RecognizerIsSetup(int recognizer);
+    private static native String SR_RecognizerGetParameter(int recognizer, String key);
+    private static native int SR_RecognizerGetSize_tParameter(int recognizer, String key);
+    private static native boolean SR_RecognizerGetBoolParameter(int recognizer, String key);
+    private static native void SR_RecognizerSetParameter(int recognizer, String key, String value);
+    private static native void SR_RecognizerSetSize_tParameter(int recognizer,
+            String key, int value);
+    private static native void SR_RecognizerSetBoolParameter(int recognizer, String key,
+            boolean value);
+    private static native void SR_RecognizerSetupRule(int recognizer, int grammar,
+            String ruleName);
+    private static native boolean SR_RecognizerHasSetupRules(int recognizer);
+    private static native void SR_RecognizerActivateRule(int recognizer, int grammar,
+            String ruleName, int weight);
+    private static native void SR_RecognizerDeactivateRule(int recognizer, int grammar,
+            String ruleName);
+    private static native void SR_RecognizerDeactivateAllRules(int recognizer);
+    private static native boolean SR_RecognizerIsActiveRule(int recognizer, int grammar,
+            String ruleName);
+    private static native boolean SR_RecognizerCheckGrammarConsistency(int recognizer,
+            int grammar);
+    private static native int SR_RecognizerPutAudio(int recognizer, byte[] buffer, int offset,
+            int length, boolean isLast);
+    private static native int SR_RecognizerAdvance(int recognizer);
+    // private static native void SR_RecognizerLoadUtterance(int recognizer,
+    //         const LCHAR* filename);
+    // private static native void SR_RecognizerLoadWaveFile(int recognizer,
+    //         const LCHAR* filename);
+    // private static native void SR_RecognizerSetLockFunction(int recognizer,
+    //         SR_RecognizerLockFunction function, void* data);
+    private static native boolean SR_RecognizerIsSignalClipping(int recognizer);
+    private static native boolean SR_RecognizerIsSignalDCOffset(int recognizer);
+    private static native boolean SR_RecognizerIsSignalNoisy(int recognizer);
+    private static native boolean SR_RecognizerIsSignalTooQuiet(int recognizer);
+    private static native boolean SR_RecognizerIsSignalTooFewSamples(int recognizer);
+    private static native boolean SR_RecognizerIsSignalTooManySamples(int recognizer);
+    // private static native void SR_Recognizer_Change_Sample_Rate (size_t new_sample_rate);
+    
+    
+    //
+    // SR_AcousticState native methods
+    //
+    private static native void SR_AcousticStateReset(int recognizer);
+    private static native void SR_AcousticStateSet(int recognizer, String state);
+    private static native String SR_AcousticStateGet(int recognizer);
+
+
+    //
+    // SR_Grammar native methods
+    //
+    private static native void SR_GrammarCompile(int grammar);
+    private static native void SR_GrammarAddWordToSlot(int grammar, String slot,
+            String word, String pronunciation, int weight, String tag);
+    private static native void SR_GrammarResetAllSlots(int grammar);
+    // private static native void SR_GrammarAddNametagToSlot(int grammar, String slot,
+    // const struct SR_Nametag_t* nametag, int weight, String tag);
+    private static native void SR_GrammarSetupVocabulary(int grammar, int vocabulary);
+    // private static native void SR_GrammarSetupModels(int grammar, SR_AcousticModels* models);
+    private static native void SR_GrammarSetupRecognizer(int grammar, int recognizer);
+    private static native void SR_GrammarUnsetupRecognizer(int grammar);
+    // private static native void SR_GrammarGetModels(int grammar,SR_AcousticModels** models);
+    private static native int SR_GrammarCreate();
+    private static native void SR_GrammarDestroy(int grammar);
+    private static native int SR_GrammarLoad(String filename);
+    private static native void SR_GrammarSave(int grammar, String filename);
+    // private static native void SR_GrammarSetDispatchFunction(int grammar,
+    //         const LCHAR* name, void* userData, SR_GrammarDispatchFunction function);
+    // private static native void SR_GrammarSetParameter(int grammar, const
+    //         LCHAR* key, void* value);
+    // private static native void SR_GrammarSetSize_tParameter(int grammar,
+    //         const LCHAR* key, size_t value);
+    // private static native void SR_GrammarGetParameter(int grammar, const
+    //         LCHAR* key, void** value);
+    // private static native void SR_GrammarGetSize_tParameter(int grammar,
+    //         const LCHAR* key, size_t* value);
+    // private static native void SR_GrammarCheckParse(int grammar, const LCHAR*
+    //         transcription, SR_SemanticResult** result, size_t* resultCount);
+    private static native void SR_GrammarAllowOnly(int grammar, String transcription);
+    private static native void SR_GrammarAllowAll(int grammar);
+
+
+    //
+    // SR_Vocabulary native methods
+    //
+    // private static native int SR_VocabularyCreate();
+    private static native int SR_VocabularyLoad();
+    // private static native void SR_VocabularySave(SR_Vocabulary* self,
+    //         const LCHAR* filename);
+    // private static native void SR_VocabularyAddWord(SR_Vocabulary* self,
+    //         const LCHAR* word);
+    // private static native void SR_VocabularyGetLanguage(SR_Vocabulary* self,
+    //         ESR_Locale* locale);
+    private static native void SR_VocabularyDestroy(int vocabulary);
+    private static native String SR_VocabularyGetPronunciation(int vocabulary, String word);
+
+
+    //
+    // SR_RecognizerResult native methods
+    //
+    private static native byte[] SR_RecognizerResultGetWaveform(int recognizer);
+    private static native int SR_RecognizerResultGetSize(int recognizer);
+    private static native int SR_RecognizerResultGetKeyCount(int recognizer, int nbest);
+    private static native String[] SR_RecognizerResultGetKeyList(int recognizer, int nbest);
+    private static native String SR_RecognizerResultGetValue(int recognizer,
+            int nbest, String key);
+    // private static native void SR_RecognizerResultGetLocale(int recognizer, ESR_Locale* locale);
+}
diff --git a/core/java/android/speech/srec/UlawEncoderInputStream.java b/core/java/android/speech/srec/UlawEncoderInputStream.java
new file mode 100644
index 0000000..132fe027
--- /dev/null
+++ b/core/java/android/speech/srec/UlawEncoderInputStream.java
@@ -0,0 +1,186 @@
+/*
+ * ---------------------------------------------------------------------------
+ * UlawEncoderInputStream.java
+ *
+ * Copyright 2008 Nuance Communciations, Inc.
+ *
+ * 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 android.speech.srec;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * InputStream which transforms 16 bit pcm data to ulaw data.
+ *
+ * @hide pending API council approval
+ */
+public final class UlawEncoderInputStream extends InputStream {
+    private final static String TAG = "UlawEncoderInputStream";
+    
+    private final static int MAX_ULAW = 8192;
+    private final static int SCALE_BITS = 16;
+    
+    private InputStream mIn;
+    
+    private int mMax = 0;
+    
+    private final byte[] mBuf = new byte[1024];
+    private int mBufCount = 0; // should be 0 or 1
+    
+    private final byte[] mOneByte = new byte[1];
+
+    
+    public static void encode(byte[] pcmBuf, int pcmOffset,
+            byte[] ulawBuf, int ulawOffset, int length, int max) {
+        
+        // from  'ulaw' in wikipedia
+        // +8191 to +8159                          0x80
+        // +8158 to +4063 in 16 intervals of 256   0x80 + interval number
+        // +4062 to +2015 in 16 intervals of 128   0x90 + interval number
+        // +2014 to  +991 in 16 intervals of  64   0xA0 + interval number
+        //  +990 to  +479 in 16 intervals of  32   0xB0 + interval number
+        //  +478 to  +223 in 16 intervals of  16   0xC0 + interval number
+        //  +222 to   +95 in 16 intervals of   8   0xD0 + interval number
+        //   +94 to   +31 in 16 intervals of   4   0xE0 + interval number
+        //   +30 to    +1 in 15 intervals of   2   0xF0 + interval number
+        //     0                                   0xFF
+        
+        //    -1                                   0x7F
+        //   -31 to    -2 in 15 intervals of   2   0x70 + interval number
+        //   -95 to   -32 in 16 intervals of   4   0x60 + interval number
+        //  -223 to   -96 in 16 intervals of   8   0x50 + interval number
+        //  -479 to  -224 in 16 intervals of  16   0x40 + interval number
+        //  -991 to  -480 in 16 intervals of  32   0x30 + interval number
+        // -2015 to  -992 in 16 intervals of  64   0x20 + interval number
+        // -4063 to -2016 in 16 intervals of 128   0x10 + interval number
+        // -8159 to -4064 in 16 intervals of 256   0x00 + interval number
+        // -8192 to -8160                          0x00
+        
+        // set scale factors
+        if (max <= 0) max = MAX_ULAW;
+        
+        int coef = MAX_ULAW * (1 << SCALE_BITS) / max;
+        
+        for (int i = 0; i < length; i++) {
+            int pcm = (0xff & pcmBuf[pcmOffset++]) + (pcmBuf[pcmOffset++] << 8);
+            pcm = (pcm * coef) >> SCALE_BITS;
+            
+            int ulaw;
+            if (pcm >= 0) {
+                ulaw = pcm <= 0 ? 0xff :
+                        pcm <=   30 ? 0xf0 + ((  30 - pcm) >> 1) :
+                        pcm <=   94 ? 0xe0 + ((  94 - pcm) >> 2) :
+                        pcm <=  222 ? 0xd0 + (( 222 - pcm) >> 3) :
+                        pcm <=  478 ? 0xc0 + (( 478 - pcm) >> 4) :
+                        pcm <=  990 ? 0xb0 + (( 990 - pcm) >> 5) :
+                        pcm <= 2014 ? 0xa0 + ((2014 - pcm) >> 6) :
+                        pcm <= 4062 ? 0x90 + ((4062 - pcm) >> 7) :
+                        pcm <= 8158 ? 0x80 + ((8158 - pcm) >> 8) :
+                        0x80;
+            } else {
+                ulaw = -1 <= pcm ? 0x7f :
+                          -31 <= pcm ? 0x70 + ((pcm -   -31) >> 1) :
+                          -95 <= pcm ? 0x60 + ((pcm -   -95) >> 2) :
+                         -223 <= pcm ? 0x50 + ((pcm -  -223) >> 3) :
+                         -479 <= pcm ? 0x40 + ((pcm -  -479) >> 4) :
+                         -991 <= pcm ? 0x30 + ((pcm -  -991) >> 5) :
+                        -2015 <= pcm ? 0x20 + ((pcm - -2015) >> 6) :
+                        -4063 <= pcm ? 0x10 + ((pcm - -4063) >> 7) :
+                        -8159 <= pcm ? 0x00 + ((pcm - -8159) >> 8) :
+                        0x00;
+            }
+            ulawBuf[ulawOffset++] = (byte)ulaw;
+        }
+    }
+    
+    /**
+     * Compute the maximum of the absolute value of the pcm samples.
+     * The return value can be used to set ulaw encoder scaling.
+     * @param pcmBuf array containing 16 bit pcm data.
+     * @param offset offset of start of 16 bit pcm data.
+     * @param length number of pcm samples (not number of input bytes)
+     * @return maximum abs of pcm data values
+     */
+    public static int maxAbsPcm(byte[] pcmBuf, int offset, int length) {
+        int max = 0;
+        for (int i = 0; i < length; i++) {
+            int pcm = (0xff & pcmBuf[offset++]) + (pcmBuf[offset++] << 8);
+            if (pcm < 0) pcm = -pcm;
+            if (pcm > max) max = pcm;
+        }
+        return max;
+    }
+
+    /**
+     * Create an InputStream which takes 16 bit pcm data and produces ulaw data.
+     * @param in InputStream containing 16 bit pcm data.
+     * @param max pcm value corresponding to maximum ulaw value.
+     */
+    public UlawEncoderInputStream(InputStream in, int max) {
+        mIn = in;
+        mMax = max;
+    }
+    
+    @Override
+    public int read(byte[] buf, int offset, int length) throws IOException {
+        if (mIn == null) throw new IllegalStateException("not open");
+
+        // return at least one byte, but try to fill 'length'
+        while (mBufCount < 2) {
+            int n = mIn.read(mBuf, mBufCount, Math.min(length * 2, mBuf.length - mBufCount));
+            if (n == -1) return -1;
+            mBufCount += n;
+        }
+        
+        // compand data
+        int n = Math.min(mBufCount / 2, length);
+        encode(mBuf, 0, buf, offset, n, mMax);
+        
+        // move data to bottom of mBuf
+        mBufCount -= n * 2;
+        for (int i = 0; i < mBufCount; i++) mBuf[i] = mBuf[i + n * 2];
+        
+        return n;
+    }
+    
+    @Override
+    public int read(byte[] buf) throws IOException {
+        return read(buf, 0, buf.length);
+    }
+    
+    @Override
+    public int read() throws IOException {
+        int n = read(mOneByte, 0, 1);
+        if (n == -1) return -1;
+        return 0xff & (int)mOneByte[0];
+    }
+    
+    @Override
+    public void close() throws IOException {
+        if (mIn != null) {
+            InputStream in = mIn;
+            mIn = null;
+            in.close();
+        }
+    }
+    
+    @Override
+    public int available() throws IOException {
+        return (mIn.available() + mBufCount) / 2;
+    }
+}
diff --git a/core/java/android/speech/srec/WaveHeader.java b/core/java/android/speech/srec/WaveHeader.java
new file mode 100644
index 0000000..a99496d
--- /dev/null
+++ b/core/java/android/speech/srec/WaveHeader.java
@@ -0,0 +1,274 @@
+/*
+ * 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 android.speech.srec;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+/**
+ * This class represents the header of a WAVE format audio file, which usually
+ * have a .wav suffix.  The following integer valued fields are contained:
+ * <ul>
+ * <li> format - usually PCM, ALAW or ULAW.
+ * <li> numChannels - 1 for mono, 2 for stereo.
+ * <li> sampleRate - usually 8000, 11025, 16000, 22050, or 44100 hz.
+ * <li> bitsPerSample - usually 16 for PCM, 8 for ALAW, or 8 for ULAW.
+ * <li> numBytes - size of audio data after this header, in bytes.
+ * </ul>
+ * @hide pending API council approval
+ */
+public class WaveHeader {
+    
+    // follows WAVE format in http://ccrma.stanford.edu/courses/422/projects/WaveFormat
+
+    private static final String TAG = "WaveHeader";
+    
+    private static final int HEADER_LENGTH = 44;
+    
+    /** Indicates PCM format. */
+    public static final short FORMAT_PCM = 1;
+    /** Indicates ALAW format. */
+    public static final short FORMAT_ALAW = 6;
+    /** Indicates ULAW format. */
+    public static final short FORMAT_ULAW = 7;
+    
+    private short mFormat;
+    private short mNumChannels;
+    private int mSampleRate;
+    private short mBitsPerSample;
+    private int mNumBytes;
+    
+    /**
+     * Construct a WaveHeader, with all fields defaulting to zero.
+     */
+    public WaveHeader() {
+    }
+    
+    /**
+     * Construct a WaveHeader, with fields initialized.
+     * @param format format of audio data,
+     * one of {@link #FORMAT_PCM}, {@link #FORMAT_ULAW}, or {@link #FORMAT_ALAW}. 
+     * @param numChannels 1 for mono, 2 for stereo.
+     * @param sampleRate typically 8000, 11025, 16000, 22050, or 44100 hz.
+     * @param bitsPerSample usually 16 for PCM, 8 for ULAW or 8 for ALAW.
+     * @param numBytes size of audio data after this header, in bytes.
+     */
+    public WaveHeader(short format, short numChannels, int sampleRate, short bitsPerSample, int numBytes) {
+        mFormat = format;
+        mSampleRate = sampleRate;
+        mNumChannels = numChannels;
+        mBitsPerSample = bitsPerSample;
+        mNumBytes = numBytes;
+    }
+    
+    /**
+     * Get the format field.
+     * @return format field,
+     * one of {@link #FORMAT_PCM}, {@link #FORMAT_ULAW}, or {@link #FORMAT_ALAW}.
+     */
+    public short getFormat() {
+        return mFormat;
+    }
+    
+    /**
+     * Set the format field.
+     * @param format
+     * one of {@link #FORMAT_PCM}, {@link #FORMAT_ULAW}, or {@link #FORMAT_ALAW}.
+     * @return reference to this WaveHeader instance.
+     */
+    public WaveHeader setFormat(short format) {
+        mFormat = format;
+        return this;
+    }
+    
+    /**
+     * Get the number of channels.
+     * @return number of channels, 1 for mono, 2 for stereo.
+     */
+    public short getNumChannels() {
+        return mNumChannels;
+    }
+    
+    /**
+     * Set the number of channels.
+     * @param numChannels 1 for mono, 2 for stereo.
+     * @return reference to this WaveHeader instance.
+     */
+    public WaveHeader setNumChannels(short numChannels) {
+        mNumChannels = numChannels;
+        return this;
+    }
+    
+    /**
+     * Get the sample rate.
+     * @return sample rate, typically 8000, 11025, 16000, 22050, or 44100 hz.
+     */
+    public int getSampleRate() {
+        return mSampleRate;
+    }
+    
+    /**
+     * Set the sample rate.
+     * @param sampleRate sample rate, typically 8000, 11025, 16000, 22050, or 44100 hz.
+     * @return reference to this WaveHeader instance.
+     */
+    public WaveHeader setSampleRate(int sampleRate) {
+        mSampleRate = sampleRate;
+        return this;
+    }
+    
+    /**
+     * Get the number of bits per sample.
+     * @return number of bits per sample,
+     * usually 16 for PCM, 8 for ULAW or 8 for ALAW.
+     */
+    public short getBitsPerSample() {
+        return mBitsPerSample;
+    }
+    
+    /**
+     * Set the number of bits per sample.
+     * @param bitsPerSample number of bits per sample,
+     * usually 16 for PCM, 8 for ULAW or 8 for ALAW.
+     * @return reference to this WaveHeader instance.
+     */
+    public WaveHeader setBitsPerSample(short bitsPerSample) {
+        mBitsPerSample = bitsPerSample;
+        return this;
+    }
+    
+    /**
+     * Get the size of audio data after this header, in bytes.
+     * @return size of audio data after this header, in bytes.
+     */
+    public int getNumBytes() {
+        return mNumBytes;
+    }
+    
+    /**
+     * Set the size of audio data after this header, in bytes.
+     * @param numBytes size of audio data after this header, in bytes.
+     * @return reference to this WaveHeader instance.
+     */
+    public WaveHeader setNumBytes(int numBytes) {
+        mNumBytes = numBytes;
+        return this;
+    }
+    
+    /**
+     * Read and initialize a WaveHeader.
+     * @param in {@link java.io.InputStream} to read from.
+     * @return number of bytes consumed.
+     * @throws IOException
+     */
+    public int read(InputStream in) throws IOException {
+        /* RIFF header */
+        readId(in, "RIFF");
+        int numBytes = readInt(in) - 36;
+        readId(in, "WAVE");
+
+        /* fmt chunk */
+        readId(in, "fmt ");
+        if (16 != readInt(in)) throw new IOException("fmt chunk length not 16");
+        mFormat = readShort(in);
+        mNumChannels = readShort(in);
+        mSampleRate = readInt(in);
+        int byteRate = readInt(in);
+        short blockAlign = readShort(in);
+        mBitsPerSample = readShort(in);
+        if (byteRate != mNumChannels * mSampleRate * mBitsPerSample / 8) {
+            throw new IOException("fmt.ByteRate field inconsistent");
+        }
+        if (blockAlign != mNumChannels * mBitsPerSample / 8) {
+            throw new IOException("fmt.BlockAlign field inconsistent");
+        }
+
+        /* data chunk */
+        readId(in, "data");
+        mNumBytes = readInt(in);
+        
+        return HEADER_LENGTH;
+    }
+
+    private static void readId(InputStream in, String id) throws IOException {
+        for (int i = 0; i < id.length(); i++) {
+            if (id.charAt(i) != in.read()) throw new IOException( id + " tag not present");
+        }
+    }
+
+    private static int readInt(InputStream in) throws IOException {
+        return in.read() | (in.read() << 8) | (in.read() << 16) | (in.read() << 24);
+    }
+
+    private static short readShort(InputStream in) throws IOException {
+        return (short)(in.read() | (in.read() << 8));
+    }
+
+    /**
+     * Write a WAVE file header.
+     * @param out {@link java.io.OutputStream} to receive the header.
+     * @return number of bytes written.
+     * @throws IOException
+     */
+    public int write(OutputStream out) throws IOException {
+        /* RIFF header */
+        writeId(out, "RIFF");
+        writeInt(out, 36 + mNumBytes);
+        writeId(out, "WAVE");
+
+        /* fmt chunk */
+        writeId(out, "fmt ");
+        writeInt(out, 16);
+        writeShort(out, mFormat);
+        writeShort(out, mNumChannels);
+        writeInt(out, mSampleRate);
+        writeInt(out, mNumChannels * mSampleRate * mBitsPerSample / 8);
+        writeShort(out, (short)(mNumChannels * mBitsPerSample / 8));
+        writeShort(out, mBitsPerSample);
+
+        /* data chunk */
+        writeId(out, "data");
+        writeInt(out, mNumBytes);
+        
+        return HEADER_LENGTH;
+    }
+
+    private static void writeId(OutputStream out, String id) throws IOException {
+        for (int i = 0; i < id.length(); i++) out.write(id.charAt(i));
+    }
+
+    private static void writeInt(OutputStream out, int val) throws IOException {
+        out.write(val >> 0);
+        out.write(val >> 8);
+        out.write(val >> 16);
+        out.write(val >> 24);
+    }
+
+    private static void writeShort(OutputStream out, short val) throws IOException {
+        out.write(val >> 0);
+        out.write(val >> 8);
+    }
+    
+    @Override
+    public String toString() {
+        return String.format(
+                "WaveHeader format=%d numChannels=%d sampleRate=%d bitsPerSample=%d numBytes=%d",
+                mFormat, mNumChannels, mSampleRate, mBitsPerSample, mNumBytes);
+    }
+
+}
diff --git a/core/java/android/speech/srec/package.html b/core/java/android/speech/srec/package.html
new file mode 100644
index 0000000..9a99df8
--- /dev/null
+++ b/core/java/android/speech/srec/package.html
@@ -0,0 +1,6 @@
+<HTML>
+<BODY>
+Simple, synchronous SREC speech recognition API.
+@hide
+</BODY>
+</HTML>
diff --git a/core/java/android/syncml/package.html b/core/java/android/syncml/package.html
new file mode 100644
index 0000000..cb4ca46
--- /dev/null
+++ b/core/java/android/syncml/package.html
@@ -0,0 +1,6 @@
+<HTML>
+<BODY>
+Support classes for SyncML.
+{@hide}
+</BODY>
+</HTML>
\ No newline at end of file
diff --git a/core/java/android/syncml/pim/PropertyNode.java b/core/java/android/syncml/pim/PropertyNode.java
new file mode 100644
index 0000000..13d4930
--- /dev/null
+++ b/core/java/android/syncml/pim/PropertyNode.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2007 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 android.syncml.pim;
+
+import java.util.ArrayList;
+import java.util.Collection;
+
+import android.content.ContentValues;
+
+public class PropertyNode {
+
+    public String propName;
+
+    public String propValue = "";
+
+    public Collection<String> propValue_vector;
+
+    /** Store value as byte[],after decode. */
+    public byte[] propValue_byts;
+
+    /** param store: key=paramType, value=paramValue */
+    public ContentValues paraMap = new ContentValues();
+
+    /** Only for TYPE=??? param store. */
+    public ArrayList<String> paraMap_TYPE = new ArrayList<String>();
+}
diff --git a/core/java/android/syncml/pim/VBuilder.java b/core/java/android/syncml/pim/VBuilder.java
new file mode 100644
index 0000000..822c2ce
--- /dev/null
+++ b/core/java/android/syncml/pim/VBuilder.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2007 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 android.syncml.pim;
+
+import java.util.Collection;
+
+public interface VBuilder {
+    void start();
+
+    void end();
+
+    /**
+     * @param type
+     *            VXX <br>
+     *            BEGIN:VXX
+     */
+    void startRecord(String type);
+
+    /** END:VXX */
+    void endRecord();
+
+    void startProperty();
+
+    void endProperty();
+
+    /**
+     * @param name
+     *            a.N <br>
+     *            a.N
+     */
+    void propertyName(String name);
+
+    /**
+     * @param type
+     *            LANGUAGE \ ENCODING <br>
+     *            ;LANGUage= \ ;ENCODING=
+     */
+    void propertyParamType(String type);
+
+    /**
+     * @param value
+     *            FR-EN \ GBK <br>
+     *            FR-EN \ GBK
+     */
+    void propertyParamValue(String value);
+
+    void propertyValues(Collection<String> values);
+}
diff --git a/core/java/android/syncml/pim/VDataBuilder.java b/core/java/android/syncml/pim/VDataBuilder.java
new file mode 100644
index 0000000..f0a0cb9
--- /dev/null
+++ b/core/java/android/syncml/pim/VDataBuilder.java
@@ -0,0 +1,142 @@
+/*
+ * Copyright (C) 2007 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 android.syncml.pim;
+
+import org.apache.commons.codec.binary.Base64;
+import org.apache.commons.codec.net.QuotedPrintableCodec;
+
+import java.util.ArrayList;
+import java.util.Collection;
+
+/**
+ * Store the parse result to custom datastruct: VNode, PropertyNode
+ * Maybe several vcard instance, so use vNodeList to store.
+ * VNode: standy by a vcard instance.
+ * PropertyNode: standy by a property line of a card.
+ */
+public class VDataBuilder implements VBuilder {
+
+    /** type=VNode */
+    public ArrayList<VNode> vNodeList = new ArrayList<VNode>();
+    int nodeListPos = 0;
+    VNode curVNode;
+    PropertyNode curPropNode;
+    String curParamType;
+
+    public void start() {
+    }
+
+    public void end() {
+    }
+
+    public void startRecord(String type) {
+        VNode vnode = new VNode();
+        vnode.parseStatus = 1;
+        vnode.VName = type;
+        vNodeList.add(vnode);
+        nodeListPos = vNodeList.size()-1;
+        curVNode = vNodeList.get(nodeListPos);
+    }
+
+    public void endRecord() {
+        VNode endNode = vNodeList.get(nodeListPos);
+        endNode.parseStatus = 0;
+        while(nodeListPos > 0){
+            nodeListPos--;
+            if((vNodeList.get(nodeListPos)).parseStatus == 1)
+                break;
+        }
+        curVNode = vNodeList.get(nodeListPos);
+    }
+
+    public void startProperty() {
+    //  System.out.println("+ startProperty. ");
+    }
+
+    public void endProperty() {
+    //  System.out.println("- endProperty. ");
+    }
+
+    public void propertyName(String name) {
+        curPropNode = new PropertyNode();
+        curPropNode.propName = name;
+    }
+
+    public void propertyParamType(String type) {
+        curParamType = type;
+    }
+
+    public void propertyParamValue(String value) {
+        if(curParamType == null)
+            curPropNode.paraMap_TYPE.add(value);
+        else if(curParamType.equalsIgnoreCase("TYPE"))
+            curPropNode.paraMap_TYPE.add(value);
+        else
+            curPropNode.paraMap.put(curParamType, value);
+
+        curParamType = null;
+    }
+
+    public void propertyValues(Collection<String> values) {
+        curPropNode.propValue_vector = values;
+        curPropNode.propValue = listToString(values);
+        //decode value string to propValue_byts
+        if(curPropNode.paraMap.containsKey("ENCODING")){
+            if(curPropNode.paraMap.getAsString("ENCODING").
+                                        equalsIgnoreCase("BASE64")){
+                curPropNode.propValue_byts =
+                    Base64.decodeBase64(curPropNode.propValue.
+                            replaceAll(" ","").replaceAll("\t","").
+                            replaceAll("\r\n","").
+                            getBytes());
+            }
+            if(curPropNode.paraMap.getAsString("ENCODING").
+                                        equalsIgnoreCase("QUOTED-PRINTABLE")){
+                try{
+                    curPropNode.propValue_byts =
+                        QuotedPrintableCodec.decodeQuotedPrintable(
+                                curPropNode.propValue.
+                                replaceAll("= ", " ").replaceAll("=\t", "\t").
+                                getBytes() );
+                    curPropNode.propValue =
+                        new String(curPropNode.propValue_byts);
+                }catch(Exception e){
+                    System.out.println("=Decode quoted-printable exception.");
+                    e.printStackTrace();
+                }
+            }
+        }
+        curVNode.propList.add(curPropNode);
+    }
+
+    private String listToString(Collection<String> list){
+        StringBuilder typeListB = new StringBuilder();
+        for (String type : list) {
+            typeListB.append(type).append(";");
+        }
+        int len = typeListB.length();
+        if (len > 0 && typeListB.charAt(len - 1) == ';') {
+            return typeListB.substring(0, len - 1);
+        }
+        return typeListB.toString();
+    }
+
+    public String getResult(){
+        return null;
+    }
+}
+
diff --git a/core/java/android/syncml/pim/VNode.java b/core/java/android/syncml/pim/VNode.java
new file mode 100644
index 0000000..9015415
--- /dev/null
+++ b/core/java/android/syncml/pim/VNode.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2007 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 android.syncml.pim;
+
+import java.util.ArrayList;
+
+public class VNode {
+
+    public String VName;
+
+    public ArrayList<PropertyNode> propList = new ArrayList<PropertyNode>();
+
+    /** 0:parse over. 1:parsing. */
+    public int parseStatus = 1;
+}
diff --git a/core/java/android/syncml/pim/VParser.java b/core/java/android/syncml/pim/VParser.java
new file mode 100644
index 0000000..df93f38
--- /dev/null
+++ b/core/java/android/syncml/pim/VParser.java
@@ -0,0 +1,723 @@
+/*
+ * Copyright (C) 2007 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 android.syncml.pim;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.UnsupportedEncodingException;
+
+/**
+ * This interface is used to parse the V format files, such as VCard & VCal
+ *
+ */
+abstract public class VParser {
+
+    /**
+     * The buffer used to store input stream
+     */
+    protected String mBuffer = null;
+
+    /** The builder to build parsed data */
+    protected VBuilder mBuilder = null;
+
+    /** The encoding type */
+    protected String mEncoding = null;
+
+    protected final int PARSE_ERROR = -1;
+
+    protected final String mDefaultEncoding = "8BIT";
+
+    /**
+     * If offset reach '\r\n' return 2. Else return PARSE_ERROR.
+     */
+    protected int parseCrlf(int offset) {
+        if (offset >= mBuffer.length())
+            return PARSE_ERROR;
+        char ch = mBuffer.charAt(offset);
+        if (ch == '\r') {
+            offset++;
+            ch = mBuffer.charAt(offset);
+            if (ch == '\n') {
+                return 2;
+            }
+        }
+        return PARSE_ERROR;
+    }
+
+    /**
+     * Parse the given stream
+     *
+     * @param is
+     *            The source to parse.
+     * @param encoding
+     *            The encoding type.
+     * @param builder
+     *            The v builder which used to construct data.
+     * @return Return true for success, otherwise false.
+     * @throws IOException
+     */
+    public boolean parse(InputStream is, String encoding, VBuilder builder)
+            throws IOException {
+        setInputStream(is, encoding);
+        mBuilder = builder;
+        int ret = 0, offset = 0, sum = 0;
+
+        if (mBuilder != null) {
+            mBuilder.start();
+        }
+        for (;;) {
+            ret = parseVFile(offset); // for next property length
+            if (PARSE_ERROR == ret) {
+                break;
+            } else {
+                offset += ret;
+                sum += ret;
+            }
+        }
+        if (mBuilder != null) {
+            mBuilder.end();
+        }
+        return (mBuffer.length() == sum);
+    }
+
+    /**
+     * Copy the content of input stream and filter the "folding"
+     */
+    protected void setInputStream(InputStream is, String encoding)
+            throws UnsupportedEncodingException {
+        InputStreamReader reader = new InputStreamReader(is, encoding);
+        StringBuilder b = new StringBuilder();
+
+        int ch;
+        try {
+            while ((ch = reader.read()) != -1) {
+                if (ch == '\r') {
+                    ch = reader.read();
+                    if (ch == '\n') {
+                        ch = reader.read();
+                        if (ch == ' ' || ch == '\t') {
+                            b.append((char) ch);
+                            continue;
+                        }
+                        b.append("\r\n");
+                        if (ch == -1) {
+                            break;
+                        }
+                    } else {
+                        b.append("\r");
+                    }
+                }
+                b.append((char) ch);
+            }
+            mBuffer = b.toString();
+        } catch (Exception e) {
+            return;
+        }
+        return;
+    }
+
+    /**
+     * abstract function, waiting implement.<br>
+     * analyse from offset, return the length of consumed property.
+     */
+    abstract protected int parseVFile(int offset);
+
+    /**
+     * From offset, jump ' ', '\t', '\r\n' sequence, return the length of jump.<br>
+     * 1 * (SPACE / HTAB / CRLF)
+     */
+    protected int parseWsls(int offset) {
+        int ret = 0, sum = 0;
+
+        try {
+            char ch = mBuffer.charAt(offset);
+            if (ch == ' ' || ch == '\t') {
+                sum++;
+                offset++;
+            } else if ((ret = parseCrlf(offset)) != PARSE_ERROR) {
+                offset += ret;
+                sum += ret;
+            } else {
+                return PARSE_ERROR;
+            }
+            for (;;) {
+                ch = mBuffer.charAt(offset);
+                if (ch == ' ' || ch == '\t') {
+                    sum++;
+                    offset++;
+                } else if ((ret = parseCrlf(offset)) != PARSE_ERROR) {
+                    offset += ret;
+                    sum += ret;
+                } else {
+                    break;
+                }
+            }
+        } catch (IndexOutOfBoundsException e) {
+            ;
+        }
+        if (sum > 0)
+            return sum;
+        return PARSE_ERROR;
+    }
+
+    /**
+     * To determine if the given string equals to the start of the current
+     * string.
+     *
+     * @param offset
+     *            The offset in buffer of current string
+     * @param tar
+     *            The given string.
+     * @param ignoreCase
+     *            To determine case sensitive or not.
+     * @return The consumed characters, otherwise return PARSE_ERROR.
+     */
+    protected int parseString(int offset, final String tar, boolean ignoreCase) {
+        int sum = 0;
+        if (tar == null) {
+            return PARSE_ERROR;
+        }
+
+        if (ignoreCase) {
+            int len = tar.length();
+            try {
+                if (mBuffer.substring(offset, offset + len).equalsIgnoreCase(
+                        tar)) {
+                    sum = len;
+                } else {
+                    return PARSE_ERROR;
+                }
+            } catch (IndexOutOfBoundsException e) {
+                return PARSE_ERROR;
+            }
+
+        } else { /* case sensitive */
+            if (mBuffer.startsWith(tar, offset)) {
+                sum = tar.length();
+            } else {
+                return PARSE_ERROR;
+            }
+        }
+        return sum;
+    }
+
+    /**
+     * Skip the white space in string.
+     */
+    protected int removeWs(int offset) {
+        if (offset >= mBuffer.length())
+            return PARSE_ERROR;
+        int sum = 0;
+        char ch;
+        while ((ch = mBuffer.charAt(offset)) == ' ' || ch == '\t') {
+            offset++;
+            sum++;
+        }
+        return sum;
+    }
+
+    /**
+     * "X-" word, and its value. Return consumed length.
+     */
+    protected int parseXWord(int offset) {
+        int ret = 0, sum = 0;
+        ret = parseString(offset, "X-", true);
+        if (PARSE_ERROR == ret)
+            return PARSE_ERROR;
+        offset += ret;
+        sum += ret;
+
+        ret = parseWord(offset);
+        if (PARSE_ERROR == ret) {
+            return PARSE_ERROR;
+        }
+        sum += ret;
+        return sum;
+    }
+
+    /**
+     * From offset, parse as :mEncoding ?= 7bit / 8bit / quoted-printable /
+     * base64
+     */
+    protected int parseValue(int offset) {
+        int ret = 0;
+
+        if (mEncoding == null || mEncoding.equalsIgnoreCase("7BIT")
+                || mEncoding.equalsIgnoreCase("8BIT")
+                || mEncoding.toUpperCase().startsWith("X-")) {
+            ret = parse8bit(offset);
+            if (ret != PARSE_ERROR) {
+                return ret;
+            }
+            return PARSE_ERROR;
+        }
+
+        if (mEncoding.equalsIgnoreCase("QUOTED-PRINTABLE")) {
+            ret = parseQuotedPrintable(offset);
+            if (ret != PARSE_ERROR) {
+                return ret;
+            }
+            return PARSE_ERROR;
+        }
+
+        if (mEncoding.equalsIgnoreCase("BASE64")) {
+            ret = parseBase64(offset);
+            if (ret != PARSE_ERROR) {
+                return ret;
+            }
+            return PARSE_ERROR;
+        }
+        return PARSE_ERROR;
+    }
+
+    /**
+     * Refer to RFC 1521, 8bit text
+     */
+    protected int parse8bit(int offset) {
+        int index = 0;
+
+        index = mBuffer.substring(offset).indexOf("\r\n");
+
+        if (index == -1)
+            return PARSE_ERROR;
+        else
+            return index;
+
+    }
+
+    /**
+     * Refer to RFC 1521, quoted printable text ([*(ptext / SPACE / TAB) ptext]
+     * ["="] CRLF)
+     */
+    protected int parseQuotedPrintable(int offset) {
+        int ret = 0, sum = 0;
+
+        ret = removeWs(offset);
+        offset += ret;
+        sum += ret;
+
+        for (;;) {
+            ret = parsePtext(offset);
+            if (PARSE_ERROR == ret)
+                break;
+            offset += ret;
+            sum += ret;
+
+            ret = removeWs(offset);
+            offset += ret;
+            sum += ret;
+        }
+
+        ret = parseString(offset, "=", false);
+        if (ret != PARSE_ERROR) {
+            // offset += ret;
+            sum += ret;
+        }
+
+        return sum;
+    }
+
+    /**
+     * return 1 or 3 <any ASCII character except "=", SPACE, or TAB>
+     */
+    protected int parsePtext(int offset) {
+        int ret = 0;
+
+        try {
+            char ch = mBuffer.charAt(offset);
+            if (isPrintable(ch) && ch != '=' && ch != ' ' && ch != '\t') {
+                return 1;
+            }
+        } catch (IndexOutOfBoundsException e) {
+            return PARSE_ERROR;
+        }
+
+        ret = parseOctet(offset);
+        if (ret != PARSE_ERROR) {
+            return ret;
+        }
+        return PARSE_ERROR;
+    }
+
+    /**
+     * start with "=" two of (DIGIT / "A" / "B" / "C" / "D" / "E" / "F") <br>
+     * So maybe return 3.
+     */
+    protected int parseOctet(int offset) {
+        int ret = 0, sum = 0;
+
+        ret = parseString(offset, "=", false);
+        if (PARSE_ERROR == ret)
+            return PARSE_ERROR;
+        offset += ret;
+        sum += ret;
+
+        try {
+            int ch = mBuffer.charAt(offset);
+            if (ch == ' ' || ch == '\t')
+                return ++sum;
+            if ((ch >= '0' && ch <= '9') || (ch >= 'A' && ch <= 'F')) {
+                offset++;
+                sum++;
+                ch = mBuffer.charAt(offset);
+                if ((ch >= '0' && ch <= '9') || (ch >= 'A' && ch <= 'F')) {
+                    sum++;
+                    return sum;
+                }
+            }
+        } catch (IndexOutOfBoundsException e) {
+            ;
+        }
+        return PARSE_ERROR;
+    }
+
+    /**
+     * Refer to RFC 1521, base64 text The end of the text is marked with two
+     * CRLF sequences
+     */
+    protected int parseBase64(int offset) {
+        int sum = 0;
+        try {
+            for (;;) {
+                char ch;
+                ch = mBuffer.charAt(offset);
+
+                if (ch == '\r') {
+                    int ret = parseString(offset, "\r\n\r\n", false);
+                    sum += ret;
+                    break;
+                } else {
+                    /* ignore none base64 character */
+                    sum++;
+                    offset++;
+                }
+            }
+        } catch (IndexOutOfBoundsException e) {
+            return PARSE_ERROR;
+        }
+        sum -= 2;/* leave one CRLF to parse the end of this property */
+        return sum;
+    }
+
+    /**
+     * Any printable ASCII sequence except [ ]=:.,;
+     */
+    protected int parseWord(int offset) {
+        int sum = 0;
+        try {
+            for (;;) {
+                char ch = mBuffer.charAt(offset);
+                if (!isPrintable(ch))
+                    break;
+                if (ch == ' ' || ch == '=' || ch == ':' || ch == '.'
+                        || ch == ',' || ch == ';')
+                    break;
+                if (ch == '\\') {
+                    ch = mBuffer.charAt(offset + 1);
+                    if (ch == ';') {
+                        offset++;
+                        sum++;
+                    }
+                }
+                offset++;
+                sum++;
+            }
+        } catch (IndexOutOfBoundsException e) {
+            ;
+        }
+        if (sum == 0)
+            return PARSE_ERROR;
+        return sum;
+    }
+
+    /**
+     * If it is a letter or digit.
+     */
+    protected boolean isLetterOrDigit(char ch) {
+        if (ch >= '0' && ch <= '9')
+            return true;
+        if (ch >= 'a' && ch <= 'z')
+            return true;
+        if (ch >= 'A' && ch <= 'Z')
+            return true;
+        return false;
+    }
+
+    /**
+     * If it is printable in ASCII
+     */
+    protected boolean isPrintable(char ch) {
+        if (ch >= ' ' && ch <= '~')
+            return true;
+        return false;
+    }
+
+    /**
+     * If it is a letter.
+     */
+    protected boolean isLetter(char ch) {
+        if ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z')) {
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Get a word from current position.
+     */
+    protected String getWord(int offset) {
+        StringBuilder word = new StringBuilder();
+        try {
+            for (;;) {
+                char ch = mBuffer.charAt(offset);
+                if (isLetterOrDigit(ch) || ch == '-') {
+                    word.append(ch);
+                    offset++;
+                } else {
+                    break;
+                }
+            }
+        } catch (IndexOutOfBoundsException e) {
+            ;
+        }
+        return word.toString();
+    }
+
+    /**
+     * If is: "INLINE" / "URL" / "CONTENT-ID" / "CID" / "X-" word
+     */
+    protected int parsePValueVal(int offset) {
+        int ret = 0, sum = 0;
+
+        ret = parseString(offset, "INLINE", true);
+        if (ret != PARSE_ERROR) {
+            sum += ret;
+            return sum;
+        }
+
+        ret = parseString(offset, "URL", true);
+        if (ret != PARSE_ERROR) {
+            sum += ret;
+            return sum;
+        }
+
+        ret = parseString(offset, "CONTENT-ID", true);
+        if (ret != PARSE_ERROR) {
+            sum += ret;
+            return sum;
+        }
+
+        ret = parseString(offset, "CID", true);
+        if (ret != PARSE_ERROR) {
+            sum += ret;
+            return sum;
+        }
+
+        ret = parseString(offset, "INLINE", true);
+        if (ret != PARSE_ERROR) {
+            sum += ret;
+            return sum;
+        }
+
+        ret = parseXWord(offset);
+        if (ret != PARSE_ERROR) {
+            sum += ret;
+            return sum;
+        }
+
+        return PARSE_ERROR;
+    }
+
+    /**
+     * If is: "7BIT" / "8BIT" / "QUOTED-PRINTABLE" / "BASE64" / "X-" word and
+     * set mEncoding.
+     */
+    protected int parsePEncodingVal(int offset) {
+        int ret = 0, sum = 0;
+
+        ret = parseString(offset, "7BIT", true);
+        if (ret != PARSE_ERROR) {
+            mEncoding = "7BIT";
+            sum += ret;
+            return sum;
+        }
+
+        ret = parseString(offset, "8BIT", true);
+        if (ret != PARSE_ERROR) {
+            mEncoding = "8BIT";
+            sum += ret;
+            return sum;
+        }
+
+        ret = parseString(offset, "QUOTED-PRINTABLE", true);
+        if (ret != PARSE_ERROR) {
+            mEncoding = "QUOTED-PRINTABLE";
+            sum += ret;
+            return sum;
+        }
+
+        ret = parseString(offset, "BASE64", true);
+        if (ret != PARSE_ERROR) {
+            mEncoding = "BASE64";
+            sum += ret;
+            return sum;
+        }
+
+        ret = parseXWord(offset);
+        if (ret != PARSE_ERROR) {
+            mEncoding = mBuffer.substring(offset).substring(0, ret);
+            sum += ret;
+            return sum;
+        }
+
+        return PARSE_ERROR;
+    }
+
+    /**
+     * Refer to RFC1521, section 7.1<br>
+     * If is: "us-ascii" / "iso-8859-xxx" / "X-" word
+     */
+    protected int parseCharsetVal(int offset) {
+        int ret = 0, sum = 0;
+
+        ret = parseString(offset, "us-ascii", true);
+        if (ret != PARSE_ERROR) {
+            sum += ret;
+            return sum;
+        }
+
+        ret = parseString(offset, "iso-8859-1", true);
+        if (ret != PARSE_ERROR) {
+            sum += ret;
+            return sum;
+        }
+
+        ret = parseString(offset, "iso-8859-2", true);
+        if (ret != PARSE_ERROR) {
+            sum += ret;
+            return sum;
+        }
+
+        ret = parseString(offset, "iso-8859-3", true);
+        if (ret != PARSE_ERROR) {
+            sum += ret;
+            return sum;
+        }
+
+        ret = parseString(offset, "iso-8859-4", true);
+        if (ret != PARSE_ERROR) {
+            sum += ret;
+            return sum;
+        }
+
+        ret = parseString(offset, "iso-8859-5", true);
+        if (ret != PARSE_ERROR) {
+            sum += ret;
+            return sum;
+        }
+
+        ret = parseString(offset, "iso-8859-6", true);
+        if (ret != PARSE_ERROR) {
+            sum += ret;
+            return sum;
+        }
+
+        ret = parseString(offset, "iso-8859-7", true);
+        if (ret != PARSE_ERROR) {
+            sum += ret;
+            return sum;
+        }
+
+        ret = parseString(offset, "iso-8859-8", true);
+        if (ret != PARSE_ERROR) {
+            sum += ret;
+            return sum;
+        }
+
+        ret = parseString(offset, "iso-8859-9", true);
+        if (ret != PARSE_ERROR) {
+            sum += ret;
+            return sum;
+        }
+
+        ret = parseXWord(offset);
+        if (ret != PARSE_ERROR) {
+            sum += ret;
+            return sum;
+        }
+
+        return PARSE_ERROR;
+    }
+
+    /**
+     * Refer to RFC 1766<br>
+     * like: XXX(sequence letters)-XXX(sequence letters)
+     */
+    protected int parseLangVal(int offset) {
+        int ret = 0, sum = 0;
+
+        ret = parseTag(offset);
+        if (PARSE_ERROR == ret) {
+            return PARSE_ERROR;
+        }
+        offset += ret;
+        sum += ret;
+
+        for (;;) {
+            ret = parseString(offset, "-", false);
+            if (PARSE_ERROR == ret) {
+                break;
+            }
+            offset += ret;
+            sum += ret;
+
+            ret = parseTag(offset);
+            if (PARSE_ERROR == ret) {
+                break;
+            }
+            offset += ret;
+            sum += ret;
+        }
+        return sum;
+    }
+
+    /**
+     * From first 8 position, is sequence LETTER.
+     */
+    protected int parseTag(int offset) {
+        int sum = 0, i = 0;
+
+        try {
+            for (i = 0; i < 8; i++) {
+                char ch = mBuffer.charAt(offset);
+                if (!isLetter(ch)) {
+                    break;
+                }
+                sum++;
+                offset++;
+            }
+        } catch (IndexOutOfBoundsException e) {
+            ;
+        }
+        if (i == 0) {
+            return PARSE_ERROR;
+        }
+        return sum;
+    }
+
+}
diff --git a/core/java/android/syncml/pim/package.html b/core/java/android/syncml/pim/package.html
new file mode 100644
index 0000000..cb4ca46
--- /dev/null
+++ b/core/java/android/syncml/pim/package.html
@@ -0,0 +1,6 @@
+<HTML>
+<BODY>
+Support classes for SyncML.
+{@hide}
+</BODY>
+</HTML>
\ No newline at end of file
diff --git a/core/java/android/syncml/pim/vcalendar/CalendarStruct.java b/core/java/android/syncml/pim/vcalendar/CalendarStruct.java
new file mode 100644
index 0000000..3388ada
--- /dev/null
+++ b/core/java/android/syncml/pim/vcalendar/CalendarStruct.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2007 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 android.syncml.pim.vcalendar;
+
+import java.util.List;
+import java.util.ArrayList;
+
+/**
+ * Same comment as ContactStruct.
+ */
+public class CalendarStruct{
+
+    public static class EventStruct{
+        public String description;
+        public String dtend;
+        public String dtstart;
+        public String duration;
+        public String has_alarm;
+        public String last_date;
+        public String rrule;
+        public String status;
+        public String title;
+        public String event_location;
+        public String uid;
+        public List<String> reminderList;
+
+        public void addReminderList(String method){
+            if(reminderList == null)
+                reminderList = new ArrayList<String>();
+            reminderList.add(method);
+        }
+    }
+
+    public String timezone;
+    public List<EventStruct> eventList;
+
+    public void addEventList(EventStruct stru){
+        if(eventList == null)
+            eventList = new ArrayList<EventStruct>();
+        eventList.add(stru);
+    }
+}
diff --git a/core/java/android/syncml/pim/vcalendar/VCalComposer.java b/core/java/android/syncml/pim/vcalendar/VCalComposer.java
new file mode 100644
index 0000000..18b6719
--- /dev/null
+++ b/core/java/android/syncml/pim/vcalendar/VCalComposer.java
@@ -0,0 +1,189 @@
+/*
+ * Copyright (C) 2007 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 android.syncml.pim.vcalendar;
+
+/**
+ * vCalendar string composer class
+ */
+public class VCalComposer {
+
+    public final static String VERSION_VCALENDAR10 = "vcalendar1.0";
+    public final static String VERSION_VCALENDAR20 = "vcalendar2.0";
+
+    public final static int VERSION_VCAL10_INT = 1;
+    public final static int VERSION_VCAL20_INT = 2;
+
+    private static String mNewLine = "\r\n";
+    private String mVersion = null;
+
+    public VCalComposer() {
+    }
+
+    /**
+     * Create a vCalendar String.
+     * @param struct see more from CalendarStruct class
+     * @param vcalversion MUST be VERSION_VCAL10 /VERSION_VCAL20
+     * @return vCalendar string
+     * @throws VcalException if version is invalid or create failed
+     */
+    public String createVCal(CalendarStruct struct, int vcalversion)
+                                                throws VCalException{
+
+        StringBuilder returnStr = new StringBuilder();
+
+        //Version check
+        if(vcalversion != 1 && vcalversion != 2)
+            throw new VCalException("version not match 1.0 or 2.0.");
+        if (vcalversion == 1)
+            mVersion = VERSION_VCALENDAR10;
+        else
+            mVersion = VERSION_VCALENDAR20;
+
+        //Build vCalendar:
+        returnStr.append("BEGIN:VCALENDAR").append(mNewLine);
+
+        if(vcalversion == VERSION_VCAL10_INT)
+            returnStr.append("VERSION:1.0").append(mNewLine);
+        else
+            returnStr.append("VERSION:2.0").append(mNewLine);
+
+        returnStr.append("PRODID:vCal ID default").append(mNewLine);
+
+        if(!isNull(struct.timezone)){
+            if(vcalversion == VERSION_VCAL10_INT)
+                returnStr.append("TZ:").append(struct.timezone).append(mNewLine);
+            else//down here MUST have
+                returnStr.append("BEGIN:VTIMEZONE").append(mNewLine).
+                    append("TZID:vCal default").append(mNewLine).
+                    append("BEGIN:STANDARD").append(mNewLine).
+                    append("DTSTART:16010101T000000").append(mNewLine).
+                    append("TZOFFSETFROM:").append(struct.timezone).append(mNewLine).
+                    append("TZOFFSETTO:").append(struct.timezone).append(mNewLine).
+                    append("END:STANDARD").append(mNewLine).
+                    append("END:VTIMEZONE").append(mNewLine);
+        }
+        //Build VEVNET
+        for(int i = 0; i < struct.eventList.size(); i++){
+            String str = buildEventStr( struct.eventList.get(i) );
+            returnStr.append(str);
+        }
+
+        //Build VTODO
+        //TODO
+
+        returnStr.append("END:VCALENDAR").append(mNewLine).append(mNewLine);
+
+        return returnStr.toString();
+    }
+
+    private String buildEventStr(CalendarStruct.EventStruct stru){
+
+        StringBuilder strbuf = new StringBuilder();
+
+        strbuf.append("BEGIN:VEVENT").append(mNewLine);
+
+        if(!isNull(stru.uid))
+            strbuf.append("UID:").append(stru.uid).append(mNewLine);
+
+        if(!isNull(stru.description))
+            strbuf.append("DESCRIPTION:").
+            append(foldingString(stru.description)).append(mNewLine);
+
+        if(!isNull(stru.dtend))
+            strbuf.append("DTEND:").append(stru.dtend).append(mNewLine);
+
+        if(!isNull(stru.dtstart))
+            strbuf.append("DTSTART:").append(stru.dtstart).append(mNewLine);
+
+        if(!isNull(stru.duration))
+            strbuf.append("DUE:").append(stru.duration).append(mNewLine);
+
+        if(!isNull(stru.event_location))
+            strbuf.append("LOCATION:").append(stru.event_location).append(mNewLine);
+
+        if(!isNull(stru.last_date))
+            strbuf.append("COMPLETED:").append(stru.last_date).append(mNewLine);
+
+        if(!isNull(stru.rrule))
+            strbuf.append("RRULE:").append(stru.rrule).append(mNewLine);
+
+        if(!isNull(stru.title))
+            strbuf.append("SUMMARY:").append(stru.title).append(mNewLine);
+
+        if(!isNull(stru.status)){
+            String stat = "TENTATIVE";
+            switch (Integer.parseInt(stru.status)){
+            case 0://Calendar.Calendars.STATUS_TENTATIVE
+                stat = "TENTATIVE";
+                break;
+            case 1://Calendar.Calendars.STATUS_CONFIRMED
+                stat = "CONFIRMED";
+                break;
+            case 2://Calendar.Calendars.STATUS_CANCELED
+                stat = "CANCELLED";
+                break;
+            }
+            strbuf.append("STATUS:").append(stat).append(mNewLine);
+        }
+        //Alarm
+        if(!isNull(stru.has_alarm)
+            && stru.reminderList != null
+            && stru.reminderList.size() > 0){
+
+            if (mVersion.equals(VERSION_VCALENDAR10)){
+                String prefix = "";
+                for(String method : stru.reminderList){
+                    switch (Integer.parseInt(method)){
+                    case 0:
+                        prefix = "DALARM";
+                        break;
+                    case 1:
+                        prefix = "AALARM";
+                        break;
+                    case 2:
+                        prefix = "MALARM";
+                        break;
+                    case 3:
+                    default:
+                        prefix = "DALARM";
+                        break;
+                    }
+                    strbuf.append(prefix).append(":default").append(mNewLine);
+                }
+            }else {//version 2.0 only support audio-method now.
+                strbuf.append("BEGIN:VALARM").append(mNewLine).
+                       append("ACTION:AUDIO").append(mNewLine).
+                       append("TRIGGER:-PT10M").append(mNewLine).
+                       append("END:VALARM").append(mNewLine);
+            }
+        }
+        strbuf.append("END:VEVENT").append(mNewLine);
+        return strbuf.toString();
+    }
+
+    /** Alter str to folding supported format. */
+    private String foldingString(String str){
+        return str.replaceAll("\r\n", "\n").replaceAll("\n", "\r\n ");
+    }
+
+    /** is null */
+    private boolean isNull(String str){
+        if(str == null || str.trim().equals(""))
+            return true;
+        return false;
+    }
+}
diff --git a/core/java/android/syncml/pim/vcalendar/VCalException.java b/core/java/android/syncml/pim/vcalendar/VCalException.java
new file mode 100644
index 0000000..48ea134
--- /dev/null
+++ b/core/java/android/syncml/pim/vcalendar/VCalException.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2007 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 android.syncml.pim.vcalendar;
+
+public class VCalException extends java.lang.Exception{
+    // constructors
+
+    /**
+     * Constructs a VCalException object
+     */
+
+    public VCalException()
+    {
+    }
+
+    /**
+     * Constructs a VCalException object
+     *
+     * @param message the error message
+     */
+
+    public VCalException( String message )
+    {
+        super( message );
+    }
+
+}
diff --git a/core/java/android/syncml/pim/vcalendar/VCalParser.java b/core/java/android/syncml/pim/vcalendar/VCalParser.java
new file mode 100644
index 0000000..bc2d598
--- /dev/null
+++ b/core/java/android/syncml/pim/vcalendar/VCalParser.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2007 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 android.syncml.pim.vcalendar;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import android.util.Config;
+import android.util.Log;
+
+import android.syncml.pim.VDataBuilder;
+import android.syncml.pim.VParser;
+
+public class VCalParser{
+
+    private final static String TAG = "VCalParser";
+
+    public final static String VERSION_VCALENDAR10 = "vcalendar1.0";
+    public final static String VERSION_VCALENDAR20 = "vcalendar2.0";
+
+    private VParser mParser = null;
+    private String mVersion = null;
+
+    public VCalParser() {
+    }
+
+    public boolean parse(String vcalendarStr, VDataBuilder builder)
+            throws VCalException {
+
+        vcalendarStr = verifyVCal(vcalendarStr);
+        try{
+            boolean isSuccess = mParser.parse(
+                    new ByteArrayInputStream(vcalendarStr.getBytes()),
+                    "US-ASCII", builder);
+
+            if (!isSuccess) {
+                if (mVersion.equals(VERSION_VCALENDAR10)) {
+                    if(Config.LOGD)
+                        Log.d(TAG, "Parse failed for vCal 1.0 parser."
+                            + " Try to use 2.0 parser.");
+                    mVersion = VERSION_VCALENDAR20;
+                    return parse(vcalendarStr, builder);
+                }else
+                    throw new VCalException("parse failed.(even use 2.0 parser)");
+            }
+        }catch (IOException e){
+            throw new VCalException(e.getMessage());
+        }
+        return true;
+    }
+
+    /**
+     * Verify vCalendar string, and initialize mVersion according to it.
+     * */
+    private String verifyVCal(String vcalStr) {
+
+        //Version check
+        judgeVersion(vcalStr);
+
+        vcalStr = vcalStr.replaceAll("\r\n", "\n");
+        String[] strlist = vcalStr.split("\n");
+
+        StringBuilder replacedStr = new StringBuilder();
+
+        for (int i = 0; i < strlist.length; i++) {
+            if (strlist[i].indexOf(":") < 0) {
+                if (strlist[i].length() == 0 && strlist[i + 1].indexOf(":") > 0)
+                    replacedStr.append(strlist[i]).append("\r\n");
+                else
+                    replacedStr.append(" ").append(strlist[i]).append("\r\n");
+            } else
+                replacedStr.append(strlist[i]).append("\r\n");
+        }
+        if(Config.LOGD)Log.d(TAG, "After verify:\r\n" + replacedStr.toString());
+
+        return replacedStr.toString();
+    }
+
+    /**
+     * If version not given. Search from vcal string of the VERSION property.
+     * Then instance mParser to appropriate parser.
+     */
+    private void judgeVersion(String vcalStr) {
+
+        if (mVersion == null) {
+            int versionIdx = vcalStr.indexOf("\nVERSION:");
+
+            mVersion = VERSION_VCALENDAR10;
+
+            if (versionIdx != -1){
+                String versionStr = vcalStr.substring(
+                        versionIdx, vcalStr.indexOf("\n", versionIdx + 1));
+                if (versionStr.indexOf("2.0") > 0)
+                    mVersion = VERSION_VCALENDAR20;
+            }
+        }
+        if (mVersion.equals(VERSION_VCALENDAR10))
+            mParser = new VCalParser_V10();
+        if (mVersion.equals(VERSION_VCALENDAR20))
+            mParser = new VCalParser_V20();
+    }
+}
+
diff --git a/core/java/android/syncml/pim/vcalendar/VCalParser_V10.java b/core/java/android/syncml/pim/vcalendar/VCalParser_V10.java
new file mode 100644
index 0000000..1b251f3
--- /dev/null
+++ b/core/java/android/syncml/pim/vcalendar/VCalParser_V10.java
@@ -0,0 +1,1628 @@
+/*
+ * Copyright (C) 2007 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 android.syncml.pim.vcalendar;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import android.syncml.pim.VParser;
+
+public class VCalParser_V10 extends VParser {
+
+    /*
+     * The names of the properties whose value are not separated by ";"
+     */
+    private static final HashSet<String> mEvtPropNameGroup1 = new HashSet<String>(
+            Arrays.asList("ATTACH", "ATTENDEE", "DCREATED", "COMPLETED",
+                    "DESCRIPTION", "DUE", "DTEND", "EXRULE", "LAST-MODIFIED",
+                    "LOCATION", "RNUM", "PRIORITY", "RELATED-TO", "RRULE",
+                    "SEQUENCE", "DTSTART", "SUMMARY", "TRANSP", "URL", "UID",
+                    // above belong to simprop
+                    "CLASS", "STATUS"));
+
+    /*
+     * The names of properties whose value are separated by ";"
+     */
+    private static final HashSet<String> mEvtPropNameGroup2 = new HashSet<String>(
+            Arrays.asList("AALARM", "CATEGORIES", "DALARM", "EXDATE", "MALARM",
+                    "PALARM", "RDATE", "RESOURCES"));
+
+    private static final HashSet<String> mValueCAT = new HashSet<String>(Arrays
+            .asList("APPOINTMENT", "BUSINESS", "EDUCATION", "HOLIDAY",
+                    "MEETING", "MISCELLANEOUS", "PERSONAL", "PHONE CALL",
+                    "SICK DAY", "SPECIAL OCCASION", "TRAVEL", "VACATION"));
+
+    private static final HashSet<String> mValueCLASS = new HashSet<String>(Arrays
+            .asList("PUBLIC", "PRIVATE", "CONFIDENTIAL"));
+
+    private static final HashSet<String> mValueRES = new HashSet<String>(Arrays
+            .asList("CATERING", "CHAIRS", "EASEL", "PROJECTOR", "VCR",
+                    "VEHICLE"));
+
+    private static final HashSet<String> mValueSTAT = new HashSet<String>(Arrays
+            .asList("ACCEPTED", "NEEDS ACTION", "SENT", "TENTATIVE",
+                    "CONFIRMED", "DECLINED", "COMPLETED", "DELEGATED"));
+
+    /*
+     * The names of properties whose value can contain escape characters
+     */
+    private static final HashSet<String> mEscAllowedProps = new HashSet<String>(
+            Arrays.asList("DESCRIPTION", "SUMMARY", "AALARM", "DALARM",
+                    "MALARM", "PALARM"));
+
+    private static final HashMap<String, HashSet<String>> mSpecialValueSetMap =
+        new HashMap<String, HashSet<String>>();
+
+    static {
+        mSpecialValueSetMap.put("CATEGORIES", mValueCAT);
+        mSpecialValueSetMap.put("CLASS", mValueCLASS);
+        mSpecialValueSetMap.put("RESOURCES", mValueRES);
+        mSpecialValueSetMap.put("STATUS", mValueSTAT);
+    }
+
+    public VCalParser_V10() {
+    }
+
+    protected int parseVFile(int offset) {
+        return parseVCalFile(offset);
+    }
+
+    private int parseVCalFile(int offset) {
+        int ret = 0, sum = 0;
+
+        /* remove wsls */
+        while (PARSE_ERROR != (ret = parseWsls(offset))) {
+            offset += ret;
+            sum += ret;
+        }
+
+        ret = parseVCal(offset); // BEGIN:VCAL ... END:VCAL
+        if (PARSE_ERROR != ret) {
+            offset += ret;
+            sum += ret;
+        } else {
+            return PARSE_ERROR;
+        }
+
+        /* remove wsls */
+        while (PARSE_ERROR != (ret = parseWsls(offset))) {
+            offset += ret;
+            sum += ret;
+        }
+        return sum;
+    }
+
+    /**
+     * "BEGIN" [ws] ":" [ws] "VCALENDAR" [ws] 1*crlf calprop calentities [ws]
+     * *crlf "END" [ws] ":" [ws] "VCALENDAR" [ws] 1*CRLF
+     */
+    private int parseVCal(int offset) {
+        int ret = 0, sum = 0;
+
+        /* BEGIN */
+        ret = parseString(offset, "BEGIN", false);
+        if (PARSE_ERROR == ret) {
+            return PARSE_ERROR;
+        }
+        offset += ret;
+        sum += ret;
+
+        // [ws]
+        ret = removeWs(offset);
+        offset += ret;
+        sum += ret;
+
+        // ":"
+        ret = parseString(offset, ":", false);
+        if (PARSE_ERROR == ret) {
+            return PARSE_ERROR;
+        }
+        offset += ret;
+        sum += ret;
+
+        // [ws]
+        ret = removeWs(offset);
+        offset += ret;
+        sum += ret;
+
+        // "VCALENDAR
+        ret = parseString(offset, "VCALENDAR", false);
+        if (PARSE_ERROR == ret) {
+            return PARSE_ERROR;
+        }
+        offset += ret;
+        sum += ret;
+        if (mBuilder != null) {
+            mBuilder.startRecord("VCALENDAR");
+        }
+
+        /* [ws] */
+        ret = removeWs(offset);
+        offset += ret;
+        sum += ret;
+
+        // 1*CRLF
+        ret = parseCrlf(offset);
+        if (PARSE_ERROR == ret) {
+            return PARSE_ERROR;
+        }
+        offset += ret;
+        sum += ret;
+        while (PARSE_ERROR != (ret = parseCrlf(offset))) {
+            offset += ret;
+            sum += ret;
+        }
+
+        // calprop
+        ret = parseCalprops(offset);
+        if (PARSE_ERROR == ret) {
+            return PARSE_ERROR;
+        }
+        offset += ret;
+        sum += ret;
+
+        // calentities
+        ret = parseCalentities(offset);
+        if (PARSE_ERROR == ret) {
+            return PARSE_ERROR;
+        }
+        offset += ret;
+        sum += ret;
+
+        // [ws]
+        ret = removeWs(offset);
+        offset += ret;
+        sum += ret;
+
+        // *CRLF
+        while (PARSE_ERROR != (ret = parseCrlf(offset))) {
+            offset += ret;
+            sum += ret;
+        }
+
+        // "END"
+        ret = parseString(offset, "END", false);
+        if (PARSE_ERROR == ret) {
+            return PARSE_ERROR;
+        }
+        offset += ret;
+        sum += ret;
+
+        // [ws]
+        ret = removeWs(offset);
+        offset += ret;
+        sum += ret;
+
+        // ":"
+        // ":"
+        ret = parseString(offset, ":", false);
+        if (PARSE_ERROR == ret) {
+            return PARSE_ERROR;
+        }
+        offset += ret;
+        sum += ret;
+
+        // [ws]
+        ret = removeWs(offset);
+        offset += ret;
+        sum += ret;
+
+        // "VCALENDAR"
+        ret = parseString(offset, "VCALENDAR", false);
+        if (PARSE_ERROR == ret) {
+            return PARSE_ERROR;
+        }
+        offset += ret;
+        sum += ret;
+        if (mBuilder != null) {
+            mBuilder.endRecord();
+        }
+
+        // [ws]
+        ret = removeWs(offset);
+        offset += ret;
+        sum += ret;
+
+        // 1 * CRLF
+        ret = parseCrlf(offset);
+        if (PARSE_ERROR == ret) {
+            return PARSE_ERROR;
+        }
+        offset += ret;
+        sum += ret;
+        while (PARSE_ERROR != (ret = parseCrlf(offset))) {
+            offset += ret;
+            sum += ret;
+        }
+
+        return sum;
+    }
+
+    /**
+     * calprops * CRLF calprop / calprop
+     */
+    private int parseCalprops(int offset) {
+        int ret = 0, sum = 0;
+
+        if (mBuilder != null) {
+            mBuilder.startProperty();
+        }
+        ret = parseCalprop(offset);
+        if (PARSE_ERROR == ret) {
+            return PARSE_ERROR;
+        }
+        offset += ret;
+        sum += ret;
+        if (mBuilder != null) {
+            mBuilder.endProperty();
+        }
+
+        for (;;) {
+            /* *CRLF */
+            while (PARSE_ERROR != (ret = parseCrlf(offset))) {
+                offset += ret;
+                sum += ret;
+            }
+            // follow VEVENT ,it wont reach endProperty
+            if (mBuilder != null) {
+                mBuilder.startProperty();
+            }
+            ret = parseCalprop(offset);
+            if (PARSE_ERROR == ret) {
+                break;
+            }
+            offset += ret;
+            sum += ret;
+            if (mBuilder != null) {
+                mBuilder.endProperty();
+            }
+        }
+
+        return sum;
+    }
+
+    /**
+     * calentities *CRLF calentity / calentity
+     */
+    private int parseCalentities(int offset) {
+        int ret = 0, sum = 0;
+
+        ret = parseCalentity(offset);
+        if (PARSE_ERROR == ret) {
+            return PARSE_ERROR;
+        }
+        offset += ret;
+        sum += ret;
+
+        for (;;) {
+            /* *CRLF */
+            while (PARSE_ERROR != (ret = parseCrlf(offset))) {
+                offset += ret;
+                sum += ret;
+            }
+
+            ret = parseCalentity(offset);
+            if (PARSE_ERROR == ret) {
+                break;
+            }
+            offset += ret;
+            sum += ret;
+        }
+
+        return sum;
+    }
+
+    /**
+     * calprop = DAYLIGHT/ GEO/ PRODID/ TZ/ VERSION
+     */
+    private int parseCalprop(int offset) {
+        int ret = 0;
+
+        ret = parseCalprop0(offset, "DAYLIGHT");
+        if (PARSE_ERROR != ret) {
+            return ret;
+        }
+
+        ret = parseCalprop0(offset, "GEO");
+        if (PARSE_ERROR != ret) {
+            return ret;
+        }
+
+        ret = parseCalprop0(offset, "PRODID");
+        if (PARSE_ERROR != ret) {
+            return ret;
+        }
+
+        ret = parseCalprop0(offset, "TZ");
+        if (PARSE_ERROR != ret) {
+            return ret;
+        }
+
+        ret = parseCalprop1(offset);
+        if (PARSE_ERROR != ret) {
+            return ret;
+        }
+        return PARSE_ERROR;
+    }
+
+    /**
+     * evententity / todoentity
+     */
+    private int parseCalentity(int offset) {
+        int ret = 0;
+
+        ret = parseEvententity(offset);
+        if (PARSE_ERROR != ret) {
+            return ret;
+        }
+
+        ret = parseTodoentity(offset);
+        if (PARSE_ERROR != ret) {
+            return ret;
+        }
+        return PARSE_ERROR;
+
+    }
+
+    /**
+     * propName [params] ":" value CRLF
+     */
+    private int parseCalprop0(int offset, String propName) {
+        int ret = 0, sum = 0, start = 0;
+
+        ret = parseString(offset, propName, true);
+        if (PARSE_ERROR == ret) {
+            return PARSE_ERROR;
+        }
+        offset += ret;
+        sum += ret;
+        if (mBuilder != null) {
+            mBuilder.propertyName(propName);
+        }
+
+        ret = parseParams(offset);
+        if (PARSE_ERROR != ret) {
+            offset += ret;
+            sum += ret;
+        }
+
+        ret = parseString(offset, ":", true);
+        if (PARSE_ERROR == ret) {
+            return PARSE_ERROR;
+        }
+        offset += ret;
+        sum += ret;
+
+        start = offset;
+        ret = parseValue(offset);
+        if (PARSE_ERROR == ret) {
+            return PARSE_ERROR;
+        }
+        offset += ret;
+        sum += ret;
+        if (mBuilder != null) {
+            ArrayList<String> v = new ArrayList<String>();
+            v.add(mBuffer.substring(start, offset));
+            mBuilder.propertyValues(v);
+        }
+
+        ret = parseCrlf(offset);
+        if (PARSE_ERROR == ret) {
+            return PARSE_ERROR;
+        }
+        sum += ret;
+
+        return sum;
+    }
+
+    /**
+     * "VERSION" [params] ":" "1.0" CRLF
+     */
+    private int parseCalprop1(int offset) {
+        int ret = 0, sum = 0;
+
+        ret = parseString(offset, "VERSION", true);
+        if (PARSE_ERROR == ret) {
+            return PARSE_ERROR;
+        }
+        offset += ret;
+        sum += ret;
+        if (mBuilder != null) {
+            mBuilder.propertyName("VERSION");
+        }
+
+        ret = parseParams(offset);
+        if (PARSE_ERROR != ret) {
+            offset += ret;
+            sum += ret;
+        }
+
+        ret = parseString(offset, ":", true);
+        if (PARSE_ERROR == ret) {
+            return PARSE_ERROR;
+        }
+        offset += ret;
+        sum += ret;
+
+        ret = parseString(offset, "1.0", true);
+        if (PARSE_ERROR == ret) {
+            return PARSE_ERROR;
+        }
+        offset += ret;
+        sum += ret;
+        if (mBuilder != null) {
+            ArrayList<String> v = new ArrayList<String>();
+            v.add("1.0");
+            mBuilder.propertyValues(v);
+        }
+        ret = parseCrlf(offset);
+        if (PARSE_ERROR == ret) {
+            return PARSE_ERROR;
+        }
+        sum += ret;
+
+        return sum;
+    }
+
+    /**
+     * "BEGIN" [ws] ":" [ws] "VEVENT" [ws] 1*CRLF entprops [ws] *CRLF "END" [ws]
+     * ":" [ws] "VEVENT" [ws] 1*CRLF
+     */
+    private int parseEvententity(int offset) {
+        int ret = 0, sum = 0;
+
+        ret = parseString(offset, "BEGIN", false);
+        if (PARSE_ERROR == ret) {
+            return PARSE_ERROR;
+        }
+        offset += ret;
+        sum += ret;
+
+        // [ws]
+        ret = removeWs(offset);
+        offset += ret;
+        sum += ret;
+
+        // ":"
+        ret = parseString(offset, ":", false);
+        if (PARSE_ERROR == ret) {
+            return PARSE_ERROR;
+        }
+        offset += ret;
+        sum += ret;
+
+        // [ws]
+        ret = removeWs(offset);
+        offset += ret;
+        sum += ret;
+
+        // "VEVNET"
+        ret = parseString(offset, "VEVENT", false);
+        if (PARSE_ERROR == ret) {
+            return PARSE_ERROR;
+        }
+        offset += ret;
+        sum += ret;
+        if (mBuilder != null) {
+            mBuilder.startRecord("VEVENT");
+        }
+
+        /* [ws] */
+        ret = removeWs(offset);
+        offset += ret;
+        sum += ret;
+
+        // 1*CRLF
+        ret = parseCrlf(offset);
+        if (PARSE_ERROR == ret) {
+            return PARSE_ERROR;
+        }
+        offset += ret;
+        sum += ret;
+        while (PARSE_ERROR != (ret = parseCrlf(offset))) {
+            offset += ret;
+            sum += ret;
+        }
+
+        ret = parseEntprops(offset);
+        if (PARSE_ERROR == ret) {
+            return PARSE_ERROR;
+        }
+        offset += ret;
+        sum += ret;
+
+        // [ws]
+        ret = removeWs(offset);
+        offset += ret;
+        sum += ret;
+
+        // *CRLF
+        while (PARSE_ERROR != (ret = parseCrlf(offset))) {
+            offset += ret;
+            sum += ret;
+        }
+
+        // "END"
+        ret = parseString(offset, "END", false);
+        if (PARSE_ERROR == ret) {
+            return PARSE_ERROR;
+        }
+        offset += ret;
+        sum += ret;
+
+        // [ws]
+        ret = removeWs(offset);
+        offset += ret;
+        sum += ret;
+
+        // ":"
+        ret = parseString(offset, ":", false);
+        if (PARSE_ERROR == ret) {
+            return PARSE_ERROR;
+        }
+        offset += ret;
+        sum += ret;
+
+        // [ws]
+        ret = removeWs(offset);
+        offset += ret;
+        sum += ret;
+
+        // "VEVENT"
+        ret = parseString(offset, "VEVENT", false);
+        if (PARSE_ERROR == ret) {
+            return PARSE_ERROR;
+        }
+        offset += ret;
+        sum += ret;
+        if (mBuilder != null) {
+            mBuilder.endRecord();
+        }
+
+        // [ws]
+        ret = removeWs(offset);
+        offset += ret;
+        sum += ret;
+
+        // 1 * CRLF
+        ret = parseCrlf(offset);
+        if (PARSE_ERROR == ret) {
+            return PARSE_ERROR;
+        }
+        offset += ret;
+        sum += ret;
+        while (PARSE_ERROR != (ret = parseCrlf(offset))) {
+            offset += ret;
+            sum += ret;
+        }
+
+        return sum;
+    }
+
+    /**
+     * "BEGIN" [ws] ":" [ws] "VTODO" [ws] 1*CRLF entprops [ws] *CRLF "END" [ws]
+     * ":" [ws] "VTODO" [ws] 1*CRLF
+     */
+    private int parseTodoentity(int offset) {
+        int ret = 0, sum = 0;
+
+        ret = parseString(offset, "BEGIN", false);
+        if (PARSE_ERROR == ret) {
+            return PARSE_ERROR;
+        }
+        offset += ret;
+        sum += ret;
+
+        // [ws]
+        ret = removeWs(offset);
+        offset += ret;
+        sum += ret;
+
+        // ":"
+        ret = parseString(offset, ":", false);
+        if (PARSE_ERROR == ret) {
+            return PARSE_ERROR;
+        }
+        offset += ret;
+        sum += ret;
+
+        // [ws]
+        ret = removeWs(offset);
+        offset += ret;
+        sum += ret;
+
+        // "VTODO"
+        ret = parseString(offset, "VTODO", false);
+        if (PARSE_ERROR == ret) {
+            return PARSE_ERROR;
+        }
+        offset += ret;
+        sum += ret;
+        if (mBuilder != null) {
+            mBuilder.startRecord("VTODO");
+        }
+
+        // 1*CRLF
+        ret = parseCrlf(offset);
+        if (PARSE_ERROR == ret) {
+            return PARSE_ERROR;
+        }
+        offset += ret;
+        sum += ret;
+        while (PARSE_ERROR != (ret = parseCrlf(offset))) {
+            offset += ret;
+            sum += ret;
+        }
+
+        ret = parseEntprops(offset);
+        if (PARSE_ERROR == ret) {
+            return PARSE_ERROR;
+        }
+        offset += ret;
+        sum += ret;
+
+        // [ws]
+        ret = removeWs(offset);
+        offset += ret;
+        sum += ret;
+
+        // *CRLF
+        while (PARSE_ERROR != (ret = parseCrlf(offset))) {
+            offset += ret;
+            sum += ret;
+        }
+
+        // "END"
+        ret = parseString(offset, "END", false);
+        if (PARSE_ERROR == ret) {
+            return PARSE_ERROR;
+        }
+        offset += ret;
+        sum += ret;
+
+        // [ws]
+        ret = removeWs(offset);
+        offset += ret;
+        sum += ret;
+
+        // ":"
+        ret = parseString(offset, ":", false);
+        if (PARSE_ERROR == ret) {
+            return PARSE_ERROR;
+        }
+        offset += ret;
+        sum += ret;
+
+        // [ws]
+        ret = removeWs(offset);
+        offset += ret;
+        sum += ret;
+
+        // "VTODO"
+        ret = parseString(offset, "VTODO", false);
+        if (PARSE_ERROR == ret) {
+            return PARSE_ERROR;
+        }
+        offset += ret;
+        sum += ret;
+        if (mBuilder != null) {
+            mBuilder.endRecord();
+        }
+
+        // [ws]
+        ret = removeWs(offset);
+        offset += ret;
+        sum += ret;
+
+        // 1 * CRLF
+        ret = parseCrlf(offset);
+        if (PARSE_ERROR == ret) {
+            return PARSE_ERROR;
+        }
+        offset += ret;
+        sum += ret;
+        while (PARSE_ERROR != (ret = parseCrlf(offset))) {
+            offset += ret;
+            sum += ret;
+        }
+
+        return sum;
+    }
+
+    /**
+     * entprops *CRLF entprop / entprop
+     */
+    private int parseEntprops(int offset) {
+        int ret = 0, sum = 0;
+        if (mBuilder != null) {
+            mBuilder.startProperty();
+        }
+
+        ret = parseEntprop(offset);
+        if (PARSE_ERROR == ret) {
+            return PARSE_ERROR;
+        }
+        offset += ret;
+        sum += ret;
+        if (mBuilder != null) {
+            mBuilder.endProperty();
+        }
+
+        for (;;) {
+            while (PARSE_ERROR != (ret = parseCrlf(offset))) {
+                offset += ret;
+                sum += ret;
+            }
+            if (mBuilder != null) {
+                mBuilder.startProperty();
+            }
+
+            ret = parseEntprop(offset);
+            if (PARSE_ERROR == ret) {
+                break;
+            }
+            offset += ret;
+            sum += ret;
+            if (mBuilder != null) {
+                mBuilder.endProperty();
+            }
+        }
+        return sum;
+    }
+
+    /**
+     * for VEVENT,VTODO prop. entprop0 / entprop1
+     */
+    private int parseEntprop(int offset) {
+        int ret = 0;
+        ret = parseEntprop0(offset);
+        if (PARSE_ERROR != ret) {
+            return ret;
+        }
+
+        ret = parseEntprop1(offset);
+        if (PARSE_ERROR != ret) {
+            return ret;
+        }
+        return PARSE_ERROR;
+    }
+
+    /**
+     * Same with card. ";" [ws] paramlist
+     */
+    private int parseParams(int offset) {
+        int ret = 0, sum = 0;
+
+        ret = parseString(offset, ";", true);
+        if (PARSE_ERROR == ret) {
+            return PARSE_ERROR;
+        }
+        offset += ret;
+        sum += ret;
+
+        ret = removeWs(offset);
+        offset += ret;
+        sum += ret;
+
+        ret = parseParamlist(offset);
+        if (PARSE_ERROR == ret) {
+            return PARSE_ERROR;
+        }
+        sum += ret;
+
+        return sum;
+    }
+
+    /**
+     * Same with card. paramlist [ws] ";" [ws] param / param
+     */
+    private int parseParamlist(int offset) {
+        int ret = 0, sum = 0;
+
+        ret = parseParam(offset);
+        if (PARSE_ERROR == ret) {
+            return PARSE_ERROR;
+        }
+        offset += ret;
+        sum += ret;
+
+        int offsetTemp = offset;
+        int sumTemp = sum;
+        for (;;) {
+            ret = removeWs(offsetTemp);
+            offsetTemp += ret;
+            sumTemp += ret;
+
+            ret = parseString(offsetTemp, ";", false);
+            if (PARSE_ERROR == ret) {
+                return sum;
+            }
+            offsetTemp += ret;
+            sumTemp += ret;
+
+            ret = removeWs(offsetTemp);
+            offsetTemp += ret;
+            sumTemp += ret;
+
+            ret = parseParam(offsetTemp);
+            if (PARSE_ERROR == ret) {
+                break;
+            }
+            offsetTemp += ret;
+            sumTemp += ret;
+
+            // offset = offsetTemp;
+            sum = sumTemp;
+        }
+        return sum;
+    }
+
+    /**
+     * param0 - param7 / knowntype
+     */
+    private int parseParam(int offset) {
+        int ret = 0;
+
+        ret = parseParam0(offset);
+        if (PARSE_ERROR != ret) {
+            return ret;
+        }
+
+        ret = parseParam1(offset);
+        if (PARSE_ERROR != ret) {
+            return ret;
+        }
+
+        ret = parseParam2(offset);
+        if (PARSE_ERROR != ret) {
+            return ret;
+        }
+
+        ret = parseParam3(offset);
+        if (PARSE_ERROR != ret) {
+            return ret;
+        }
+
+        ret = parseParam4(offset);
+        if (PARSE_ERROR != ret) {
+            return ret;
+        }
+
+        ret = parseParam5(offset);
+        if (PARSE_ERROR != ret) {
+            return ret;
+        }
+
+        ret = parseParam6(offset);
+        if (PARSE_ERROR != ret) {
+            return ret;
+        }
+
+        ret = parseParam7(offset);
+        if (PARSE_ERROR != ret) {
+            return ret;
+        }
+
+        int start = offset;
+        ret = parseKnownType(offset);
+        if (PARSE_ERROR == ret) {
+            return PARSE_ERROR;
+        }
+        offset += ret;
+        if (mBuilder != null) {
+            mBuilder.propertyParamType(null);
+            mBuilder.propertyParamValue(mBuffer.substring(start, offset));
+        }
+
+        return ret;
+    }
+
+    /**
+     * simprop AND "CLASS" AND "STATUS" The value of these properties are not
+     * seperated by ";"
+     *
+     * [ws] simprop [params] ":" value CRLF
+     */
+    private int parseEntprop0(int offset) {
+        int ret = 0, sum = 0, start = 0;
+
+        ret = removeWs(offset);
+        offset += ret;
+        sum += ret;
+
+        String propName = getWord(offset).toUpperCase();
+        if (!mEvtPropNameGroup1.contains(propName)) {
+            if (PARSE_ERROR == parseXWord(offset))
+                return PARSE_ERROR;
+        }
+        ret = propName.length();
+        offset += ret;
+        sum += ret;
+        if (mBuilder != null) {
+            mBuilder.propertyName(propName);
+        }
+
+        ret = parseParams(offset);
+        if (PARSE_ERROR != ret) {
+            offset += ret;
+            sum += ret;
+        }
+
+        ret = parseString(offset, ":", false);
+        if (PARSE_ERROR == ret) {
+            return PARSE_ERROR;
+        }
+        offset += ret;
+        sum += ret;
+
+        start = offset;
+        ret = parseValue(offset);
+        if (PARSE_ERROR == ret) {
+            return PARSE_ERROR;
+        }
+        offset += ret;
+        sum += ret;
+        if (mBuilder != null) {
+            ArrayList<String> v = new ArrayList<String>();
+            v.add(exportEntpropValue(propName, mBuffer.substring(start,
+                            offset)));
+            mBuilder.propertyValues(v);
+            // Filter value,match string, REFER:RFC
+            if (PARSE_ERROR == valueFilter(propName, v))
+                return PARSE_ERROR;
+        }
+
+        ret = parseCrlf(offset);
+        if (PARSE_ERROR == ret) {
+            return PARSE_ERROR;
+        }
+        sum += ret;
+        return sum;
+    }
+
+    /**
+     * other event prop names except simprop AND "CLASS" AND "STATUS" The value
+     * of these properties are seperated by ";" [ws] proper name [params] ":"
+     * value CRLF
+     */
+    private int parseEntprop1(int offset) {
+        int ret = 0, sum = 0;
+
+        ret = removeWs(offset);
+        offset += ret;
+        sum += ret;
+
+        String propName = getWord(offset).toUpperCase();
+        if (!mEvtPropNameGroup2.contains(propName)) {
+            return PARSE_ERROR;
+        }
+        ret = propName.length();
+        offset += ret;
+        sum += ret;
+        if (mBuilder != null) {
+            mBuilder.propertyName(propName);
+        }
+
+        ret = parseParams(offset);
+        if (PARSE_ERROR != ret) {
+            offset += ret;
+            sum += ret;
+        }
+
+        ret = parseString(offset, ":", false);
+        if (PARSE_ERROR == ret) {
+            return PARSE_ERROR;
+        }
+        offset += ret;
+        sum += ret;
+
+        int start = offset;
+        ret = parseValue(offset);
+        if (PARSE_ERROR == ret) {
+            return PARSE_ERROR;
+        }
+        offset += ret;
+        sum += ret;
+        // mutil-values
+        if (mBuilder != null) {
+            int end = 0;
+            ArrayList<String> v = new ArrayList<String>();
+            Pattern p = Pattern
+                    .compile("([^;\\\\]*(\\\\[\\\\;:,])*[^;\\\\]*)(;?)");
+            Matcher m = p.matcher(mBuffer.substring(start, offset));
+            while (m.find()) {
+                String s = exportEntpropValue(propName, m.group(1));
+                v.add(s);
+                end = m.end();
+                if (offset == start + end) {
+                    String endValue = m.group(3);
+                    if (";".equals(endValue)) {
+                        v.add("");
+                    }
+                    break;
+                }
+            }
+            mBuilder.propertyValues(v);
+            // Filter value,match string, REFER:RFC
+            if (PARSE_ERROR == valueFilter(propName, v))
+                return PARSE_ERROR;
+        }
+
+        ret = parseCrlf(offset);
+        if (PARSE_ERROR == ret) {
+            return PARSE_ERROR;
+        }
+        sum += ret;
+        return sum;
+    }
+
+    /**
+     * "TYPE" [ws] = [ws] ptypeval
+     */
+    private int parseParam0(int offset) {
+        int ret = 0, sum = 0, start = offset;
+
+        ret = parseString(offset, "TYPE", true);
+        if (PARSE_ERROR == ret) {
+            return PARSE_ERROR;
+        }
+        offset += ret;
+        sum += ret;
+        if (mBuilder != null) {
+            mBuilder.propertyParamType(mBuffer.substring(start, offset));
+        }
+
+        ret = removeWs(offset);
+        offset += ret;
+        sum += ret;
+
+        ret = parseString(offset, "=", false);
+        if (PARSE_ERROR == ret) {
+            return PARSE_ERROR;
+        }
+        offset += ret;
+        sum += ret;
+
+        ret = removeWs(offset);
+        offset += ret;
+        sum += ret;
+
+        start = offset;
+        ret = parsePtypeval(offset);
+        if (PARSE_ERROR == ret) {
+            return PARSE_ERROR;
+        }
+        offset += ret;
+        sum += ret;
+        if (mBuilder != null) {
+            mBuilder.propertyParamValue(mBuffer.substring(start, offset));
+        }
+        return sum;
+    }
+
+    /**
+     * ["VALUE" [ws] "=" [ws]] pvalueval
+     */
+    private int parseParam1(int offset) {
+        int ret = 0, sum = 0, start = offset;
+        boolean flag = false;
+
+        ret = parseString(offset, "VALUE", true);
+        if (PARSE_ERROR != ret) {
+            offset += ret;
+            sum += ret;
+            flag = true;
+        }
+        if (flag == true && mBuilder != null) {
+            mBuilder.propertyParamType(mBuffer.substring(start, offset));
+        }
+
+        ret = removeWs(offset);
+        offset += ret;
+        sum += ret;
+
+        ret = parseString(offset, "=", true);
+        if (PARSE_ERROR != ret) {
+            if (flag == false) { // "VALUE" does not exist
+                return PARSE_ERROR;
+            }
+            offset += ret;
+            sum += ret;
+        } else {
+            if (flag == true) { // "VALUE" exists
+                return PARSE_ERROR;
+            }
+        }
+
+        ret = removeWs(offset);
+        offset += ret;
+        sum += ret;
+
+        start = offset;
+        ret = parsePValueVal(offset);
+        if (PARSE_ERROR == ret) {
+            return PARSE_ERROR;
+        }
+        offset += ret;
+        sum += ret;
+        if (mBuilder != null) {
+            mBuilder.propertyParamValue(mBuffer.substring(start, offset));
+        }
+
+        return sum;
+    }
+
+    /** ["ENCODING" [ws] "=" [ws]] pencodingval */
+    private int parseParam2(int offset) {
+        int ret = 0, sum = 0, start = offset;
+        boolean flag = false;
+
+        ret = parseString(offset, "ENCODING", true);
+        if (PARSE_ERROR != ret) {
+            offset += ret;
+            sum += ret;
+            flag = true;
+        }
+        if (flag == true && mBuilder != null) {
+            mBuilder.propertyParamType(mBuffer.substring(start, offset));
+        }
+
+        ret = removeWs(offset);
+        offset += ret;
+        sum += ret;
+
+        ret = parseString(offset, "=", true);
+        if (PARSE_ERROR != ret) {
+            if (flag == false) { // "VALUE" does not exist
+                return PARSE_ERROR;
+            }
+            offset += ret;
+            sum += ret;
+        } else {
+            if (flag == true) { // "VALUE" exists
+                return PARSE_ERROR;
+            }
+        }
+
+        ret = removeWs(offset);
+        offset += ret;
+        sum += ret;
+
+        start = offset;
+        ret = parsePEncodingVal(offset);
+        if (PARSE_ERROR == ret) {
+            return PARSE_ERROR;
+        }
+        offset += ret;
+        sum += ret;
+        if (mBuilder != null) {
+            mBuilder.propertyParamValue(mBuffer.substring(start, offset));
+        }
+
+        return sum;
+    }
+
+    /**
+     * "CHARSET" [WS] "=" [WS] charsetval
+     */
+    private int parseParam3(int offset) {
+        int ret = 0, sum = 0, start = offset;
+
+        ret = parseString(offset, "CHARSET", true);
+        if (PARSE_ERROR == ret) {
+            return PARSE_ERROR;
+        }
+        offset += ret;
+        sum += ret;
+        if (mBuilder != null) {
+            mBuilder.propertyParamType(mBuffer.substring(start, offset));
+        }
+
+        ret = removeWs(offset);
+        offset += ret;
+        sum += ret;
+
+        ret = parseString(offset, "=", true);
+        if (PARSE_ERROR == ret) {
+            return PARSE_ERROR;
+        }
+        offset += ret;
+        sum += ret;
+
+        ret = removeWs(offset);
+        offset += ret;
+        sum += ret;
+
+        start = offset;
+        ret = parseCharsetVal(offset);
+        if (PARSE_ERROR == ret) {
+            return PARSE_ERROR;
+        }
+        offset += ret;
+        sum += ret;
+        if (mBuilder != null) {
+            mBuilder.propertyParamValue(mBuffer.substring(start, offset));
+        }
+
+        return sum;
+    }
+
+    /**
+     * "LANGUAGE" [ws] "=" [ws] langval
+     */
+    private int parseParam4(int offset) {
+        int ret = 0, sum = 0, start = offset;
+
+        ret = parseString(offset, "LANGUAGE", true);
+        if (PARSE_ERROR == ret) {
+            return PARSE_ERROR;
+        }
+        offset += ret;
+        sum += ret;
+        if (mBuilder != null) {
+            mBuilder.propertyParamType(mBuffer.substring(start, offset));
+        }
+
+        ret = removeWs(offset);
+        offset += ret;
+        sum += ret;
+
+        ret = parseString(offset, "=", true);
+        if (PARSE_ERROR == ret) {
+            return PARSE_ERROR;
+        }
+        offset += ret;
+        sum += ret;
+
+        ret = removeWs(offset);
+        offset += ret;
+        sum += ret;
+
+        start = offset;
+        ret = parseLangVal(offset);
+        if (PARSE_ERROR == ret) {
+            return PARSE_ERROR;
+        }
+        offset += ret;
+        sum += ret;
+        if (mBuilder != null) {
+            mBuilder.propertyParamValue(mBuffer.substring(start, offset));
+        }
+
+        return sum;
+    }
+
+    /**
+     * "ROLE" [ws] "=" [ws] roleval
+     */
+    private int parseParam5(int offset) {
+        int ret = 0, sum = 0, start = offset;
+
+        ret = parseString(offset, "ROLE", true);
+        if (PARSE_ERROR == ret) {
+            return PARSE_ERROR;
+        }
+        offset += ret;
+        sum += ret;
+        if (mBuilder != null) {
+            mBuilder.propertyParamType(mBuffer.substring(start, offset));
+        }
+
+        ret = removeWs(offset);
+        offset += ret;
+        sum += ret;
+
+        ret = parseString(offset, "=", true);
+        if (PARSE_ERROR == ret) {
+            return PARSE_ERROR;
+        }
+        offset += ret;
+        sum += ret;
+
+        ret = removeWs(offset);
+        offset += ret;
+        sum += ret;
+
+        start = offset;
+        ret = parseRoleVal(offset);
+        if (PARSE_ERROR == ret) {
+            return PARSE_ERROR;
+        }
+        offset += ret;
+        sum += ret;
+        if (mBuilder != null) {
+            mBuilder.propertyParamValue(mBuffer.substring(start, offset));
+        }
+
+        return sum;
+    }
+
+    /**
+     * "STATUS" [ws] = [ws] statuval
+     */
+    private int parseParam6(int offset) {
+        int ret = 0, sum = 0, start = offset;
+
+        ret = parseString(offset, "STATUS", true);
+        if (PARSE_ERROR == ret) {
+            return PARSE_ERROR;
+        }
+        offset += ret;
+        sum += ret;
+        if (mBuilder != null) {
+            mBuilder.propertyParamType(mBuffer.substring(start, offset));
+        }
+
+        ret = removeWs(offset);
+        offset += ret;
+        sum += ret;
+
+        ret = parseString(offset, "=", true);
+        if (PARSE_ERROR == ret) {
+            return PARSE_ERROR;
+        }
+        offset += ret;
+        sum += ret;
+
+        ret = removeWs(offset);
+        offset += ret;
+        sum += ret;
+
+        start = offset;
+        ret = parseStatuVal(offset);
+        if (PARSE_ERROR == ret) {
+            return PARSE_ERROR;
+        }
+        offset += ret;
+        sum += ret;
+        if (mBuilder != null) {
+            mBuilder.propertyParamValue(mBuffer.substring(start, offset));
+        }
+
+        return sum;
+
+    }
+
+    /**
+     * XWord [ws] "=" [ws] word
+     */
+    private int parseParam7(int offset) {
+        int ret = 0, sum = 0, start = offset;
+
+        ret = parseXWord(offset);
+        if (PARSE_ERROR == ret) {
+            return PARSE_ERROR;
+        }
+        offset += ret;
+        sum += ret;
+        if (mBuilder != null) {
+            mBuilder.propertyParamType(mBuffer.substring(start, offset));
+        }
+
+        ret = removeWs(offset);
+        offset += ret;
+        sum += ret;
+
+        ret = parseString(offset, "=", true);
+        if (PARSE_ERROR == ret) {
+            return PARSE_ERROR;
+        }
+        offset += ret;
+        sum += ret;
+
+        ret = removeWs(offset);
+        offset += ret;
+        sum += ret;
+
+        start = offset;
+        ret = parseWord(offset);
+        if (PARSE_ERROR == ret) {
+            return PARSE_ERROR;
+        }
+        offset += ret;
+        sum += ret;
+        if (mBuilder != null) {
+            mBuilder.propertyParamValue(mBuffer.substring(start, offset));
+        }
+
+        return sum;
+
+    }
+
+    /*
+     * "WAVE" / "PCM" / "VCARD" / XWORD
+     */
+    private int parseKnownType(int offset) {
+        int ret = 0;
+
+        ret = parseString(offset, "WAVE", true);
+        if (PARSE_ERROR != ret) {
+            return ret;
+        }
+
+        ret = parseString(offset, "PCM", true);
+        if (PARSE_ERROR != ret) {
+            return ret;
+        }
+
+        ret = parseString(offset, "VCARD", true);
+        if (PARSE_ERROR != ret) {
+            return ret;
+        }
+
+        ret = parseXWord(offset);
+        if (PARSE_ERROR != ret) {
+            return ret;
+        }
+
+        return PARSE_ERROR;
+    }
+
+    /*
+     * knowntype / Xword
+     */
+    private int parsePtypeval(int offset) {
+        int ret = 0;
+
+        ret = parseKnownType(offset);
+        if (PARSE_ERROR != ret) {
+            return ret;
+        }
+
+        ret = parseXWord(offset);
+        if (PARSE_ERROR != ret) {
+            return ret;
+        }
+
+        return PARSE_ERROR;
+    }
+
+    /**
+     * "ATTENDEE" / "ORGANIZER" / "OWNER" / XWORD
+     */
+    private int parseRoleVal(int offset) {
+        int ret = 0;
+
+        ret = parseString(offset, "ATTENDEE", true);
+        if (PARSE_ERROR != ret) {
+            return ret;
+        }
+
+        ret = parseString(offset, "ORGANIZER", true);
+        if (PARSE_ERROR != ret) {
+            return ret;
+        }
+
+        ret = parseString(offset, "OWNER", true);
+        if (PARSE_ERROR != ret) {
+            return ret;
+        }
+
+        ret = parseXWord(offset);
+        if (PARSE_ERROR != ret) {
+            return ret;
+        }
+
+        return PARSE_ERROR;
+    }
+
+    /**
+     * "ACCEPTED" / "NEED ACTION" / "SENT" / "TENTATIVE" / "CONFIRMED" /
+     * "DECLINED" / "COMPLETED" / "DELEGATED / XWORD
+     */
+    private int parseStatuVal(int offset) {
+        int ret = 0;
+
+        ret = parseString(offset, "ACCEPTED", true);
+        if (PARSE_ERROR != ret) {
+            return ret;
+        }
+
+        ret = parseString(offset, "NEED ACTION", true);
+        if (PARSE_ERROR != ret) {
+            return ret;
+        }
+
+        ret = parseString(offset, "TENTATIVE", true);
+        if (PARSE_ERROR != ret) {
+            return ret;
+        }
+        ret = parseString(offset, "CONFIRMED", true);
+        if (PARSE_ERROR != ret) {
+            return ret;
+        }
+        ret = parseString(offset, "DECLINED", true);
+        if (PARSE_ERROR != ret) {
+            return ret;
+        }
+        ret = parseString(offset, "COMPLETED", true);
+        if (PARSE_ERROR != ret) {
+            return ret;
+        }
+        ret = parseString(offset, "DELEGATED", true);
+        if (PARSE_ERROR != ret) {
+            return ret;
+        }
+
+        ret = parseXWord(offset);
+        if (PARSE_ERROR != ret) {
+            return ret;
+        }
+
+        return PARSE_ERROR;
+    }
+
+    /**
+     * Check 4 special propName and it's value to match Hash.
+     *
+     * @return PARSE_ERROR:value not match. 1:go on,like nothing happen.
+     */
+    private int valueFilter(String propName, ArrayList<String> values) {
+        if (propName == null || propName.equals("") || values == null
+                || values.isEmpty())
+            return 1; // go on, like nothing happen.
+
+        if (mSpecialValueSetMap.containsKey(propName)) {
+            for (String value : values) {
+                if (!mSpecialValueSetMap.get(propName).contains(value)) {
+                    if (!value.startsWith("X-"))
+                        return PARSE_ERROR;
+                }
+            }
+        }
+
+        return 1;
+    }
+
+    /**
+     *
+     * Translate escape characters("\\", "\;") which define in vcalendar1.0
+     * spec. But for fault tolerance, we will translate "\:" and "\,", which
+     * isn't define in vcalendar1.0 explicitly, as the same behavior as other
+     * client.
+     *
+     * Though vcalendar1.0 spec does not defined the value of property
+     * "description", "summary", "aalarm", "dalarm", "malarm" and "palarm" could
+     * contain escape characters, we do support escape characters in these
+     * properties.
+     *
+     * @param str:
+     *            the value string will be translated.
+     * @return the string which do not contain any escape character in
+     *         vcalendar1.0
+     */
+    private String exportEntpropValue(String propName, String str) {
+        if (null == propName || null == str)
+            return null;
+        if ("".equals(propName) || "".equals(str))
+            return "";
+
+        if (!mEscAllowedProps.contains(propName))
+            return str;
+
+        String tmp = str.replace("\\\\", "\n\r\n");
+        tmp = tmp.replace("\\;", ";");
+        tmp = tmp.replace("\\:", ":");
+        tmp = tmp.replace("\\,", ",");
+        tmp = tmp.replace("\n\r\n", "\\");
+        return tmp;
+    }
+}
diff --git a/core/java/android/syncml/pim/vcalendar/VCalParser_V20.java b/core/java/android/syncml/pim/vcalendar/VCalParser_V20.java
new file mode 100644
index 0000000..5748379
--- /dev/null
+++ b/core/java/android/syncml/pim/vcalendar/VCalParser_V20.java
@@ -0,0 +1,186 @@
+/*
+ * Copyright (C) 2007 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 android.syncml.pim.vcalendar;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Arrays;
+import java.util.HashSet;
+
+import android.syncml.pim.VBuilder;
+
+public class VCalParser_V20 extends VCalParser_V10 {
+    private static final String V10LINEBREAKER = "\r\n";
+
+    private static final HashSet<String> acceptableComponents = new HashSet<String>(
+            Arrays.asList("VEVENT", "VTODO", "VALARM", "VTIMEZONE"));
+
+    private static final HashSet<String> acceptableV20Props = new HashSet<String>(
+            Arrays.asList("DESCRIPTION", "DTEND", "DTSTART", "DUE",
+                    "COMPLETED", "RRULE", "STATUS", "SUMMARY", "LOCATION"));
+
+    private boolean hasTZ = false; // MUST only have one TZ property
+
+    private String[] lines;
+
+    private int index;
+
+    @Override
+    public boolean parse(InputStream is, String encoding, VBuilder builder)
+            throws IOException {
+        // get useful info for android calendar, and alter to vcal 1.0
+        byte[] bytes = new byte[is.available()];
+        is.read(bytes);
+        String scStr = new String(bytes);
+        StringBuilder v10str = new StringBuilder("");
+
+        lines = splitProperty(scStr);
+        index = 0;
+
+        if ("BEGIN:VCALENDAR".equals(lines[index]))
+            v10str.append("BEGIN:VCALENDAR" + V10LINEBREAKER);
+        else
+            return false;
+        index++;
+        if (false == parseV20Calbody(lines, v10str)
+                || index > lines.length - 1)
+            return false;
+
+        if (lines.length - 1 == index && "END:VCALENDAR".equals(lines[index]))
+            v10str.append("END:VCALENDAR" + V10LINEBREAKER);
+        else
+            return false;
+
+        return super.parse(
+                // use vCal 1.0 parser
+                new ByteArrayInputStream(v10str.toString().getBytes()),
+                encoding, builder);
+    }
+
+    /**
+     * Parse and pick acceptable iCalendar body and translate it to
+     * calendarV1.0 format.
+     * @param lines iCalendar components/properties line list.
+     * @param buffer calendarV10 format string buffer
+     * @return true for success, or false
+     */
+    private boolean parseV20Calbody(String[] lines, StringBuilder buffer) {
+        try {
+            while (!"VERSION:2.0".equals(lines[index]))
+                index++;
+            buffer.append("VERSION:1.0" + V10LINEBREAKER);
+
+            index++;
+            for (; index < lines.length - 1; index++) {
+                String[] keyAndValue = lines[index].split(":", 2);
+                String key = keyAndValue[0];
+                String value = keyAndValue[1];
+
+                if ("BEGIN".equals(key.trim())) {
+                    if (!key.equals(key.trim()))
+                        return false; // MUST be "BEGIN:componentname"
+                    index++;
+                    if (false == parseV20Component(value, buffer))
+                        return false;
+                }
+            }
+        } catch (ArrayIndexOutOfBoundsException e) {
+            return false;
+        }
+
+        return true;
+    }
+
+    /**
+     * Parse and pick acceptable calendar V2.0's component and translate it to
+     * V1.0 format.
+     * @param compName component name
+     * @param buffer calendarV10 format string buffer
+     * @return true for success, or false
+     * @throws ArrayIndexOutOfBoundsException
+     */
+    private boolean parseV20Component(String compName, StringBuilder buffer)
+            throws ArrayIndexOutOfBoundsException {
+        String endTag = "END:" + compName;
+        String[] propAndValue;
+        String propName, value;
+
+        if (acceptableComponents.contains(compName)) {
+            if ("VEVENT".equals(compName) || "VTODO".equals(compName)) {
+                buffer.append("BEGIN:" + compName + V10LINEBREAKER);
+                while (!endTag.equals(lines[index])) {
+                    propAndValue = lines[index].split(":", 2);
+                    propName = propAndValue[0].split(";", 2)[0];
+                    value = propAndValue[1];
+
+                    if ("".equals(lines[index]))
+                        buffer.append(V10LINEBREAKER);
+                    else if (acceptableV20Props.contains(propName)) {
+                        buffer.append(propName + ":" + value + V10LINEBREAKER);
+                    } else if ("BEGIN".equals(propName.trim())) {
+                        // MUST be BEGIN:VALARM
+                        if (propName.equals(propName.trim())
+                                && "VALARM".equals(value)) {
+                            buffer.append("AALARM:default" + V10LINEBREAKER);
+                            while (!"END:VALARM".equals(lines[index]))
+                                index++;
+                        } else
+                            return false;
+                    }
+                    index++;
+                } // end while
+                buffer.append(endTag + V10LINEBREAKER);
+            } else if ("VALARM".equals(compName)) { // VALARM component MUST
+                // only appear within either VEVENT or VTODO
+                return false;
+            } else if ("VTIMEZONE".equals(compName)) {
+                do {
+                    if (false == hasTZ) {// MUST only have 1 time TZ property
+                        propAndValue = lines[index].split(":", 2);
+                        propName = propAndValue[0].split(";", 2)[0];
+
+                        if ("TZOFFSETFROM".equals(propName)) {
+                            value = propAndValue[1];
+                            buffer.append("TZ" + ":" + value + V10LINEBREAKER);
+                            hasTZ = true;
+                        }
+                    }
+                    index++;
+                } while (!endTag.equals(lines[index]));
+            } else
+                return false;
+        } else {
+            while (!endTag.equals(lines[index]))
+                index++;
+        }
+
+        return true;
+    }
+
+    /** split ever property line to String[], not split folding line. */
+    private String[] splitProperty(String scStr) {
+        /*
+         * Property splitted by \n, and unfold folding lines by removing
+         * CRLF+LWSP-char
+         */
+        scStr = scStr.replaceAll("\r\n", "\n").replaceAll("\n ", "")
+                .replaceAll("\n\t", "");
+        String[] strs = scStr.split("\n");
+        return strs;
+    }
+}
diff --git a/core/java/android/syncml/pim/vcalendar/package.html b/core/java/android/syncml/pim/vcalendar/package.html
new file mode 100644
index 0000000..cb4ca46
--- /dev/null
+++ b/core/java/android/syncml/pim/vcalendar/package.html
@@ -0,0 +1,6 @@
+<HTML>
+<BODY>
+Support classes for SyncML.
+{@hide}
+</BODY>
+</HTML>
\ No newline at end of file
diff --git a/core/java/android/syncml/pim/vcard/ContactStruct.java b/core/java/android/syncml/pim/vcard/ContactStruct.java
new file mode 100644
index 0000000..8d9b7fa
--- /dev/null
+++ b/core/java/android/syncml/pim/vcard/ContactStruct.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2007 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 android.syncml.pim.vcard;
+
+import java.util.List;
+import java.util.ArrayList;
+
+/**
+ * The parameter class of VCardCreator.
+ * This class standy by the person-contact in
+ * Android system, we must use this class instance as parameter to transmit to
+ * VCardCreator so that create vCard string.
+ */
+// TODO: rename the class name, next step
+public class ContactStruct {
+    public String company;
+    /** MUST exist */
+    public String name;
+    /** maybe folding */
+    public String notes;
+    /** maybe folding */
+    public String title;
+    /** binary bytes of pic. */
+    public byte[] photoBytes;
+    /** mime_type col of images table */
+    public String photoType;
+    /** Only for GET. Use addPhoneList() to PUT. */
+    public List<PhoneData> phoneList;
+    /** Only for GET. Use addContactmethodList() to PUT. */
+    public List<ContactMethod> contactmethodList;
+
+    public static class PhoneData{
+        /** maybe folding */
+        public String data;
+        public String type;
+        public String label;
+    }
+
+    public static class ContactMethod{
+        public String kind;
+        public String type;
+        public String data;
+        public String label;
+    }
+
+    /**
+     * Add a phone info to phoneList.
+     * @param data phone number
+     * @param type type col of content://contacts/phones
+     * @param label lable col of content://contacts/phones
+     */
+    public void addPhone(String data, String type, String label){
+        if(phoneList == null)
+            phoneList = new ArrayList<PhoneData>();
+        PhoneData st = new PhoneData();
+        st.data = data;
+        st.type = type;
+        st.label = label;
+        phoneList.add(st);
+    }
+    /**
+     * Add a contactmethod info to contactmethodList.
+     * @param data contact data
+     * @param type type col of content://contacts/contact_methods
+     */
+    public void addContactmethod(String kind, String data, String type,
+            String label){
+        if(contactmethodList == null)
+            contactmethodList = new ArrayList<ContactMethod>();
+        ContactMethod st = new ContactMethod();
+        st.kind = kind;
+        st.data = data;
+        st.type = type;
+        st.label = label;
+        contactmethodList.add(st);
+    }
+}
diff --git a/core/java/android/syncml/pim/vcard/VCardComposer.java b/core/java/android/syncml/pim/vcard/VCardComposer.java
new file mode 100644
index 0000000..05e8f40
--- /dev/null
+++ b/core/java/android/syncml/pim/vcard/VCardComposer.java
@@ -0,0 +1,350 @@
+/*
+ * Copyright (C) 2007 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 android.syncml.pim.vcard;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.commons.codec.binary.Base64;
+
+import android.provider.Contacts;
+import android.syncml.pim.vcard.ContactStruct.PhoneData;
+
+/**
+ * Compose VCard string
+ */
+public class VCardComposer {
+    final public static int VERSION_VCARD21_INT = 1;
+
+    final public static int VERSION_VCARD30_INT = 2;
+
+    /**
+     * A new line
+     */
+    private String mNewline;
+
+    /**
+     * The composed string
+     */
+    private StringBuilder mResult;
+
+    /**
+     * The email's type
+     */
+    static final private HashSet<String> emailTypes = new HashSet<String>(
+            Arrays.asList("CELL", "AOL", "APPLELINK", "ATTMAIL", "CIS",
+                    "EWORLD", "INTERNET", "IBMMAIL", "MCIMAIL", "POWERSHARE",
+                    "PRODIGY", "TLX", "X400"));
+
+    static final private HashSet<String> phoneTypes = new HashSet<String>(
+            Arrays.asList("PREF", "WORK", "HOME", "VOICE", "FAX", "MSG",
+                    "CELL", "PAGER", "BBS", "MODEM", "CAR", "ISDN", "VIDEO"));
+
+    static final private String TAG = "VCardComposer";
+
+    public VCardComposer() {
+    }
+
+    private static final HashMap<Integer, String> phoneTypeMap = new HashMap<Integer, String>();
+
+    private static final HashMap<Integer, String> emailTypeMap = new HashMap<Integer, String>();
+
+    static {
+        phoneTypeMap.put(Contacts.Phones.TYPE_HOME, "HOME");
+        phoneTypeMap.put(Contacts.Phones.TYPE_MOBILE, "CELL");
+        phoneTypeMap.put(Contacts.Phones.TYPE_WORK, "WORK");
+        // FAX_WORK not exist in vcard spec. The approximate is the combine of
+        // WORK and FAX, here only map to FAX
+        phoneTypeMap.put(Contacts.Phones.TYPE_FAX_WORK, "WORK;FAX");
+        phoneTypeMap.put(Contacts.Phones.TYPE_FAX_HOME, "HOME;FAX");
+        phoneTypeMap.put(Contacts.Phones.TYPE_PAGER, "PAGER");
+        phoneTypeMap.put(Contacts.Phones.TYPE_OTHER, "X-OTHER");
+        emailTypeMap.put(Contacts.ContactMethods.TYPE_HOME, "HOME");
+        emailTypeMap.put(Contacts.ContactMethods.TYPE_WORK, "WORK");
+    }
+
+    /**
+     * Create a vCard String.
+     *
+     * @param struct
+     *            see more from ContactStruct class
+     * @param vcardversion
+     *            MUST be VERSION_VCARD21 /VERSION_VCARD30
+     * @return vCard string
+     * @throws VCardException
+     *             struct.name is null /vcardversion not match
+     */
+    public String createVCard(ContactStruct struct, int vcardversion)
+            throws VCardException {
+
+        mResult = new StringBuilder();
+        // check exception:
+        if (struct.name == null || struct.name.trim().equals("")) {
+            throw new VCardException(" struct.name MUST have value.");
+        }
+        if (vcardversion == VERSION_VCARD21_INT) {
+            mNewline = "\r\n";
+        } else if (vcardversion == VERSION_VCARD30_INT) {
+            mNewline = "\n";
+        } else {
+            throw new VCardException(
+                    " version not match VERSION_VCARD21 or VERSION_VCARD30.");
+        }
+        // build vcard:
+        mResult.append("BEGIN:VCARD").append(mNewline);
+
+        if (vcardversion == VERSION_VCARD21_INT) {
+            mResult.append("VERSION:2.1").append(mNewline);
+        } else {
+            mResult.append("VERSION:3.0").append(mNewline);
+        }
+
+        if (!isNull(struct.name)) {
+            appendNameStr(struct.name);
+        }
+
+        if (!isNull(struct.company)) {
+            mResult.append("ORG:").append(struct.company).append(mNewline);
+        }
+
+        if (!isNull(struct.notes)) {
+            mResult.append("NOTE:").append(
+                    foldingString(struct.notes, vcardversion)).append(mNewline);
+        }
+
+        if (!isNull(struct.title)) {
+            mResult.append("TITLE:").append(
+                    foldingString(struct.title, vcardversion)).append(mNewline);
+        }
+
+        if (struct.photoBytes != null) {
+            appendPhotoStr(struct.photoBytes, struct.photoType, vcardversion);
+        }
+
+        if (struct.phoneList != null) {
+            appendPhoneStr(struct.phoneList, vcardversion);
+        }
+
+        if (struct.contactmethodList != null) {
+            appendContactMethodStr(struct.contactmethodList, vcardversion);
+        }
+
+        mResult.append("END:VCARD").append(mNewline);
+        return mResult.toString();
+    }
+
+    /**
+     * Alter str to folding supported format.
+     *
+     * @param str
+     *            the string to be folded
+     * @param version
+     *            the vcard version
+     * @return the folded string
+     */
+    private String foldingString(String str, int version) {
+        if (str.endsWith("\r\n")) {
+            str = str.substring(0, str.length() - 2);
+        } else if (str.endsWith("\n")) {
+            str = str.substring(0, str.length() - 1);
+        } else {
+            return null;
+        }
+
+        str = str.replaceAll("\r\n", "\n");
+        if (version == VERSION_VCARD21_INT) {
+            return str.replaceAll("\n", "\r\n ");
+        } else if (version == VERSION_VCARD30_INT) {
+            return str.replaceAll("\n", "\n ");
+        } else {
+            return null;
+        }
+    }
+
+    /**
+     * Build LOGO property. format LOGO's param and encode value as base64.
+     *
+     * @param bytes
+     *            the binary string to be converted
+     * @param type
+     *            the type of the content
+     * @param version
+     *            the version of vcard
+     */
+    private void appendPhotoStr(byte[] bytes, String type, int version)
+            throws VCardException {
+        String value, apptype, encodingStr;
+        try {
+            value = foldingString(new String(Base64.encodeBase64(bytes, true)),
+                    version);
+        } catch (Exception e) {
+            throw new VCardException(e.getMessage());
+        }
+
+        if (isNull(type)) {
+            type = "image/jpeg";
+        }
+        if (type.indexOf("jpeg") > 0) {
+            apptype = "JPEG";
+        } else if (type.indexOf("gif") > 0) {
+            apptype = "GIF";
+        } else if (type.indexOf("bmp") > 0) {
+            apptype = "BMP";
+        } else {
+            apptype = type.substring(type.indexOf("/")).toUpperCase();
+        }
+
+        mResult.append("LOGO;TYPE=").append(apptype);
+        if (version == VERSION_VCARD21_INT) {
+            encodingStr = ";ENCODING=BASE64:";
+            value = value + mNewline;
+        } else if (version == VERSION_VCARD30_INT) {
+            encodingStr = ";ENCODING=b:";
+        } else {
+            return;
+        }
+        mResult.append(encodingStr).append(value).append(mNewline);
+    }
+
+    private boolean isNull(String str) {
+        if (str == null || str.trim().equals("")) {
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Build FN and N property. format N's value.
+     *
+     * @param name
+     *            the name of the contact
+     */
+    private void appendNameStr(String name) {
+        mResult.append("FN:").append(name).append(mNewline);
+        mResult.append("N:").append(name).append(mNewline);
+        /*
+         * if(name.indexOf(";") > 0)
+         * mResult.append("N:").append(name).append(mNewline); else
+         * if(name.indexOf(" ") > 0) mResult.append("N:").append(name.replace(' ',
+         * ';')). append(mNewline); else
+         * mResult.append("N:").append(name).append("; ").append(mNewline);
+         */
+    }
+
+    /** Loop append TEL property. */
+    private void appendPhoneStr(List<ContactStruct.PhoneData> phoneList,
+            int version) {
+        HashMap<String, String> numMap = new HashMap<String, String>();
+        String joinMark = version == VERSION_VCARD21_INT ? ";" : ",";
+
+        for (ContactStruct.PhoneData phone : phoneList) {
+            String type;
+            if (!isNull(phone.data)) {
+                type = getPhoneTypeStr(phone);
+                if (version == VERSION_VCARD30_INT && type.indexOf(";") != -1) {
+                    type = type.replace(";", ",");
+                }
+                if (numMap.containsKey(phone.data)) {
+                    type = numMap.get(phone.data) + joinMark + type;
+                }
+                numMap.put(phone.data, type);
+            }
+        }
+
+        for (Map.Entry<String, String> num : numMap.entrySet()) {
+            if (version == VERSION_VCARD21_INT) {
+                mResult.append("TEL;");
+            } else { // vcard3.0
+                mResult.append("TEL;TYPE=");
+            }
+            mResult.append(num.getValue()).append(":").append(num.getKey())
+                    .append(mNewline);
+        }
+    }
+
+    private String getPhoneTypeStr(PhoneData phone) {
+
+        int phoneType = Integer.parseInt(phone.type);
+        String typeStr, label;
+
+        if (phoneTypeMap.containsKey(phoneType)) {
+            typeStr = phoneTypeMap.get(phoneType);
+        } else if (phoneType == Contacts.Phones.TYPE_CUSTOM) {
+            label = phone.label.toUpperCase();
+            if (phoneTypes.contains(label) || label.startsWith("X-")) {
+                typeStr = label;
+            } else {
+                typeStr = "X-CUSTOM-" + label;
+            }
+        } else {
+            // TODO: need be updated with the provider's future changes
+            typeStr = "VOICE"; // the default type is VOICE in spec.
+        }
+        return typeStr;
+    }
+
+    /** Loop append ADR / EMAIL property. */
+    private void appendContactMethodStr(
+            List<ContactStruct.ContactMethod> contactMList, int version) {
+
+        HashMap<String, String> emailMap = new HashMap<String, String>();
+        String joinMark = version == VERSION_VCARD21_INT ? ";" : ",";
+        for (ContactStruct.ContactMethod contactMethod : contactMList) {
+            // same with v2.1 and v3.0
+            switch (Integer.parseInt(contactMethod.kind)) {
+            case Contacts.KIND_EMAIL:
+                String mailType = "INTERNET";
+                if (!isNull(contactMethod.data)) {
+                    int methodType = new Integer(contactMethod.type).intValue();
+                    if (emailTypeMap.containsKey(methodType)) {
+                        mailType = emailTypeMap.get(methodType);
+                    } else if (emailTypes.contains(contactMethod.label
+                            .toUpperCase())) {
+                        mailType = contactMethod.label.toUpperCase();
+                    }
+                    if (emailMap.containsKey(contactMethod.data)) {
+                        mailType = emailMap.get(contactMethod.data) + joinMark
+                                + mailType;
+                    }
+                    emailMap.put(contactMethod.data, mailType);
+                }
+                break;
+            case Contacts.KIND_POSTAL:
+                if (!isNull(contactMethod.data)) {
+                    mResult.append("ADR;TYPE=POSTAL:").append(
+                            foldingString(contactMethod.data, version)).append(
+                            mNewline);
+                }
+                break;
+            default:
+                break;
+            }
+        }
+        for (Map.Entry<String, String> email : emailMap.entrySet()) {
+            if (version == VERSION_VCARD21_INT) {
+                mResult.append("EMAIL;");
+            } else {
+                mResult.append("EMAIL;TYPE=");
+            }
+            mResult.append(email.getValue()).append(":").append(email.getKey())
+                    .append(mNewline);
+        }
+    }
+}
diff --git a/core/java/android/syncml/pim/vcard/VCardException.java b/core/java/android/syncml/pim/vcard/VCardException.java
new file mode 100644
index 0000000..35b31ec
--- /dev/null
+++ b/core/java/android/syncml/pim/vcard/VCardException.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2007 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 android.syncml.pim.vcard;
+
+public class VCardException extends java.lang.Exception{
+    // constructors
+
+    /**
+     * Constructs a VCardException object
+     */
+
+    public VCardException()
+    {
+    }
+
+    /**
+     * Constructs a VCardException object
+     *
+     * @param message the error message
+     */
+
+    public VCardException( String message )
+    {
+        super( message );
+    }
+
+}
diff --git a/core/java/android/syncml/pim/vcard/VCardParser.java b/core/java/android/syncml/pim/vcard/VCardParser.java
new file mode 100644
index 0000000..3926243c
--- /dev/null
+++ b/core/java/android/syncml/pim/vcard/VCardParser.java
@@ -0,0 +1,142 @@
+/*
+ * 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 android.syncml.pim.vcard;
+
+import android.syncml.pim.VDataBuilder;
+import android.syncml.pim.VParser;
+import android.util.Config;
+import android.util.Log;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+
+public class VCardParser {
+
+    VParser mParser = null;
+
+    public final static String VERSION_VCARD21 = "vcard2.1";
+
+    public final static String VERSION_VCARD30 = "vcard3.0";
+
+    final public static int VERSION_VCARD21_INT = 1;
+
+    final public static int VERSION_VCARD30_INT = 2;
+
+    String mVersion = null;
+
+    static final private String TAG = "VCardParser";
+
+    public VCardParser() {
+    }
+
+    /**
+     * If version not given. Search from vcard string of the VERSION property.
+     * Then instance mParser to appropriate parser.
+     *
+     * @param vcardStr
+     *            the content of vcard data
+     */
+    private void judgeVersion(String vcardStr) {
+        if (mVersion == null) {// auto judge
+            int verIdx = vcardStr.indexOf("\nVERSION:");
+            if (verIdx == -1) // if not have VERSION, v2.1 default
+                mVersion = VERSION_VCARD21;
+            else {
+                String verStr = vcardStr.substring(verIdx, vcardStr.indexOf(
+                        "\n", verIdx + 1));
+                if (verStr.indexOf("2.1") > 0)
+                    mVersion = VERSION_VCARD21;
+                else if (verStr.indexOf("3.0") > 0)
+                    mVersion = VERSION_VCARD30;
+                else
+                    mVersion = VERSION_VCARD21;
+            }
+        }
+        if (mVersion.equals(VERSION_VCARD21))
+            mParser = new VCardParser_V21();
+        if (mVersion.equals(VERSION_VCARD30))
+            mParser = new VCardParser_V30();
+    }
+
+    /**
+     * To make sure the vcard string has proper wrap character
+     *
+     * @param vcardStr
+     *            the string to be checked
+     * @return string after verified
+     */
+    private String verifyVCard(String vcardStr) {
+        this.judgeVersion(vcardStr);
+        // -- indent line:
+        vcardStr = vcardStr.replaceAll("\r\n", "\n");
+        String[] strlist = vcardStr.split("\n");
+        StringBuilder v21str = new StringBuilder("");
+        for (int i = 0; i < strlist.length; i++) {
+            if (strlist[i].indexOf(":") < 0) {
+                if (strlist[i].length() == 0 && strlist[i + 1].indexOf(":") > 0)
+                    v21str.append(strlist[i]).append("\r\n");
+                else
+                    v21str.append(" ").append(strlist[i]).append("\r\n");
+            } else
+                v21str.append(strlist[i]).append("\r\n");
+        }
+        return v21str.toString();
+    }
+
+    /**
+     * Set current version
+     *
+     * @param version
+     *            the new version
+     */
+    private void setVersion(String version) {
+        this.mVersion = version;
+    }
+
+    /**
+     * Parse the given vcard string
+     *
+     * @param vcardStr
+     *            to content to be parsed
+     * @param builder
+     *            the data builder to hold data
+     * @return true if the string is successfully parsed, else return false
+     * @throws VCardException
+     * @throws IOException
+     */
+    public boolean parse(String vcardStr, VDataBuilder builder)
+            throws VCardException, IOException {
+
+        vcardStr = this.verifyVCard(vcardStr);
+
+        boolean isSuccess = mParser.parse(new ByteArrayInputStream(vcardStr
+                .getBytes()), "US-ASCII", builder);
+        if (!isSuccess) {
+            if (mVersion.equals(VERSION_VCARD21)) {
+                if (Config.LOGD)
+                    Log.d(TAG, "Parse failed for vCard 2.1 parser."
+                            + " Try to use 3.0 parser.");
+
+                this.setVersion(VERSION_VCARD30);
+
+                return this.parse(vcardStr, builder);
+            }
+            throw new VCardException("parse failed.(even use 3.0 parser)");
+        }
+        return true;
+    }
+}
diff --git a/core/java/android/syncml/pim/vcard/VCardParser_V21.java b/core/java/android/syncml/pim/vcard/VCardParser_V21.java
new file mode 100644
index 0000000..b6fa032
--- /dev/null
+++ b/core/java/android/syncml/pim/vcard/VCardParser_V21.java
@@ -0,0 +1,970 @@
+/*
+ * 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 android.syncml.pim.vcard;
+
+import android.syncml.pim.VParser;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * This class is used to parse vcard. Please refer to vCard Specification 2.1
+ */
+public class VCardParser_V21 extends VParser {
+
+    /** Store the known-type */
+    private static final HashSet<String> mKnownTypeSet = new HashSet<String>(
+            Arrays.asList("DOM", "INTL", "POSTAL", "PARCEL", "HOME", "WORK",
+                    "PREF", "VOICE", "FAX", "MSG", "CELL", "PAGER", "BBS",
+                    "MODEM", "CAR", "ISDN", "VIDEO", "AOL", "APPLELINK",
+                    "ATTMAIL", "CIS", "EWORLD", "INTERNET", "IBMMAIL",
+                    "MCIMAIL", "POWERSHARE", "PRODIGY", "TLX", "X400", "GIF",
+                    "CGM", "WMF", "BMP", "MET", "PMB", "DIB", "PICT", "TIFF",
+                    "PDF", "PS", "JPEG", "QTIME", "MPEG", "MPEG2", "AVI",
+                    "WAVE", "AIFF", "PCM", "X509", "PGP"));
+
+    /** Store the name */
+    private static final HashSet<String> mName = new HashSet<String>(Arrays
+            .asList("LOGO", "PHOTO", "LABEL", "FN", "TITLE", "SOUND",
+                    "VERSION", "TEL", "EMAIL", "TZ", "GEO", "NOTE", "URL",
+                    "BDAY", "ROLE", "REV", "UID", "KEY", "MAILER"));
+
+    /**
+     * Create a new VCard parser.
+     */
+    public VCardParser_V21() {
+        super();
+    }
+
+    /**
+     * Parse the file at the given position
+     *
+     * @param offset
+     *            the given position to parse
+     * @return vcard length
+     */
+    protected int parseVFile(int offset) {
+        return parseVCardFile(offset);
+    }
+
+    /**
+     * [wsls] vcard [wsls]
+     */
+    int parseVCardFile(int offset) {
+        int ret = 0, sum = 0;
+
+        /* remove \t \r\n */
+        while ((ret = parseWsls(offset)) != PARSE_ERROR) {
+            offset += ret;
+            sum += ret;
+        }
+
+        ret = parseVCard(offset); // BEGIN:VCARD ... END:VCARD
+        if (ret != PARSE_ERROR) {
+            offset += ret;
+            sum += ret;
+        } else {
+            return PARSE_ERROR;
+        }
+
+        /* remove \t \r\n */
+        while ((ret = parseWsls(offset)) != PARSE_ERROR) {
+            offset += ret;
+            sum += ret;
+        }
+        return sum;
+    }
+
+    /**
+     * "BEGIN" [ws] ":" [ws] "VCARD" [ws] 1*CRLF items *CRLF "END" [ws] ":"
+     * "VCARD"
+     */
+    private int parseVCard(int offset) {
+        int ret = 0, sum = 0;
+
+        /* BEGIN */
+        ret = parseString(offset, "BEGIN", false);
+        if (ret == PARSE_ERROR) {
+            return PARSE_ERROR;
+        }
+        offset += ret;
+        sum += ret;
+
+        /* [ws] */
+        ret = removeWs(offset);
+        offset += ret;
+        sum += ret;
+
+        /* colon */
+        ret = parseString(offset, ":", false);
+        if (ret == PARSE_ERROR) {
+            return PARSE_ERROR;
+        }
+        offset += ret;
+        sum += ret;
+
+        /* [ws] */
+        ret = removeWs(offset);
+        offset += ret;
+        sum += ret;
+
+        /* VCARD */
+        ret = parseString(offset, "VCARD", false);
+        if (ret == PARSE_ERROR) {
+            return PARSE_ERROR;
+        }
+        offset += ret;
+        sum += ret;
+        if (mBuilder != null) {
+            mBuilder.startRecord("VCARD");
+        }
+
+        /* [ws] */
+        ret = removeWs(offset);
+        offset += ret;
+        sum += ret;
+
+        /* 1*CRLF */
+        ret = parseCrlf(offset);
+        if (ret == PARSE_ERROR) {
+            return PARSE_ERROR;
+        }
+        offset += ret;
+        sum += ret;
+        while ((ret = parseCrlf(offset)) != PARSE_ERROR) {
+            offset += ret;
+            sum += ret;
+        }
+
+        ret = parseItems(offset);
+        if (ret == PARSE_ERROR) {
+            return PARSE_ERROR;
+        }
+        offset += ret;
+        sum += ret;
+
+        /* *CRLF */
+        while ((ret = parseCrlf(offset)) != PARSE_ERROR) {
+            offset += ret;
+            sum += ret;
+        }
+
+        /* END */
+        ret = parseString(offset, "END", false);
+        if (ret == PARSE_ERROR) {
+            return PARSE_ERROR;
+        }
+        offset += ret;
+        sum += ret;
+
+        /* [ws] */
+        ret = removeWs(offset);
+        offset += ret;
+        sum += ret;
+
+        /* colon */
+        ret = parseString(offset, ":", false);
+        if (ret == PARSE_ERROR) {
+            return PARSE_ERROR;
+        }
+        offset += ret;
+        sum += ret;
+
+        /* [ws] */
+        ret = removeWs(offset);
+        offset += ret;
+        sum += ret;
+
+        /* VCARD */
+        ret = parseString(offset, "VCARD", false);
+        if (ret == PARSE_ERROR) {
+            return PARSE_ERROR;
+        }
+        // offset += ret;
+        sum += ret;
+        if (mBuilder != null) {
+            mBuilder.endRecord();
+        }
+
+        return sum;
+    }
+
+    /**
+     * items *CRLF item / item
+     */
+    private int parseItems(int offset) {
+        /* items *CRLF item / item */
+        int ret = 0, sum = 0;
+
+        if (mBuilder != null) {
+            mBuilder.startProperty();
+        }
+        ret = parseItem(offset);
+        if (ret == PARSE_ERROR) {
+            return PARSE_ERROR;
+        }
+        offset += ret;
+        sum += ret;
+        if (mBuilder != null) {
+            mBuilder.endProperty();
+        }
+
+        for (;;) {
+            while ((ret = parseCrlf(offset)) != PARSE_ERROR) {
+                offset += ret;
+                sum += ret;
+            }
+            // follow VCARD ,it wont reach endProperty
+            if (mBuilder != null) {
+                mBuilder.startProperty();
+            }
+
+            ret = parseItem(offset);
+            if (ret == PARSE_ERROR) {
+                break;
+            }
+            offset += ret;
+            sum += ret;
+            if (mBuilder != null) {
+                mBuilder.endProperty();
+            }
+        }
+
+        return sum;
+    }
+
+    /**
+     * item0 / item1 / item2
+     */
+    private int parseItem(int offset) {
+        int ret = 0, sum = 0;
+        mEncoding = mDefaultEncoding;
+
+        ret = parseItem0(offset);
+        if (ret != PARSE_ERROR) {
+            sum += ret;
+            return sum;
+        }
+
+        ret = parseItem1(offset);
+        if (ret != PARSE_ERROR) {
+            sum += ret;
+            return sum;
+        }
+
+        ret = parseItem2(offset);
+        if (ret != PARSE_ERROR) {
+            sum += ret;
+            return sum;
+        }
+
+        return PARSE_ERROR;
+    }
+
+    /** [groups "."] name [params] ":" value CRLF */
+    private int parseItem0(int offset) {
+        int ret = 0, sum = 0, start = offset;
+        String proName = "", proValue = "";
+
+        ret = parseGroupsWithDot(offset);
+        if (ret != PARSE_ERROR) {
+            offset += ret;
+            sum += ret;
+        }
+
+        ret = parseName(offset);
+        if (ret == PARSE_ERROR) {
+            return PARSE_ERROR;
+        }
+        offset += ret;
+        sum += ret;
+        if (mBuilder != null) {
+            proName = mBuffer.substring(start, offset).trim();
+            mBuilder.propertyName(proName);
+        }
+
+        ret = parseParams(offset);
+        if (ret != PARSE_ERROR) {
+            offset += ret;
+            sum += ret;
+        }
+
+        ret = parseString(offset, ":", false);
+        if (ret == PARSE_ERROR) {
+            return PARSE_ERROR;
+        }
+        offset += ret;
+        sum += ret;
+
+        start = offset;
+        ret = parseValue(offset);
+        if (ret == PARSE_ERROR) {
+            return PARSE_ERROR;
+        }
+        offset += ret;
+        sum += ret;
+        proValue = mBuffer.substring(start, offset);
+        if (proName.equals("VERSION") && !proValue.equals("2.1")) {
+            return PARSE_ERROR;
+        }
+        if (mBuilder != null) {
+            ArrayList<String> v = new ArrayList<String>();
+            v.add(proValue);
+            mBuilder.propertyValues(v);
+        }
+
+        ret = parseCrlf(offset);
+        if (ret == PARSE_ERROR) {
+            return PARSE_ERROR;
+        }
+        sum += ret;
+
+        return sum;
+    }
+
+    /** "ADR" "ORG" "N" with semi-colon separated content */
+    private int parseItem1(int offset) {
+        int ret = 0, sum = 0, start = offset;
+
+        ret = parseGroupsWithDot(offset);
+        if (ret != PARSE_ERROR) {
+            offset += ret;
+            sum += ret;
+        }
+
+        if ((ret = parseString(offset, "ADR", true)) == PARSE_ERROR
+                && (ret = parseString(offset, "ORG", true)) == PARSE_ERROR
+                && (ret = parseString(offset, "N", true)) == PARSE_ERROR) {
+            return PARSE_ERROR;
+        }
+        offset += ret;
+        sum += ret;
+        if (mBuilder != null) {
+            mBuilder.propertyName(mBuffer.substring(start, offset).trim());
+        }
+
+        ret = parseParams(offset);
+        if (ret != PARSE_ERROR) {
+            offset += ret;
+            sum += ret;
+        }
+
+        ret = parseString(offset, ":", false);
+        if (ret == PARSE_ERROR) {
+            return PARSE_ERROR;
+        }
+        offset += ret;
+        sum += ret;
+
+        start = offset;
+        ret = parseValue(offset);
+        if (ret == PARSE_ERROR) {
+            return PARSE_ERROR;
+        }
+        offset += ret;
+        sum += ret;
+        if (mBuilder != null) {
+            int end = 0;
+            ArrayList<String> v = new ArrayList<String>();
+            Pattern p = Pattern
+                    .compile("([^;\\\\]*(\\\\[\\\\;:,])*[^;\\\\]*)(;?)");
+            Matcher m = p.matcher(mBuffer.substring(start, offset));
+            while (m.find()) {
+                String s = escapeTranslator(m.group(1));
+                v.add(s);
+                end = m.end();
+                if (offset == start + end) {
+                    String endValue = m.group(3);
+                    if (";".equals(endValue)) {
+                        v.add("");
+                    }
+                    break;
+                }
+            }
+            mBuilder.propertyValues(v);
+        }
+
+        ret = parseCrlf(offset);
+        if (ret == PARSE_ERROR) {
+            return PARSE_ERROR;
+        }
+        sum += ret;
+
+        return sum;
+    }
+
+    /** [groups] "." "AGENT" [params] ":" vcard CRLF */
+    private int parseItem2(int offset) {
+        int ret = 0, sum = 0, start = offset;
+
+        ret = parseGroupsWithDot(offset);
+        if (ret != PARSE_ERROR) {
+            offset += ret;
+            sum += ret;
+        }
+
+        ret = parseString(offset, "AGENT", true);
+        if (ret == PARSE_ERROR) {
+            return PARSE_ERROR;
+        }
+        offset += ret;
+        sum += ret;
+        if (mBuilder != null) {
+            mBuilder.propertyName(mBuffer.substring(start, offset));
+        }
+
+        ret = parseParams(offset);
+        if (ret != PARSE_ERROR) {
+            offset += ret;
+            sum += ret;
+        }
+
+        ret = parseString(offset, ":", false);
+        if (ret == PARSE_ERROR) {
+            return PARSE_ERROR;
+        }
+        offset += ret;
+        sum += ret;
+
+        ret = parseCrlf(offset);
+        if (ret != PARSE_ERROR) {
+            offset += ret;
+            sum += ret;
+        }
+
+        ret = parseVCard(offset);
+        if (ret == PARSE_ERROR) {
+            return PARSE_ERROR;
+        }
+        offset += ret;
+        sum += ret;
+        if (mBuilder != null) {
+            mBuilder.propertyValues(new ArrayList<String>());
+        }
+
+        ret = parseCrlf(offset);
+        if (ret == PARSE_ERROR) {
+            return PARSE_ERROR;
+        }
+        sum += ret;
+
+        return sum;
+    }
+
+    private int parseGroupsWithDot(int offset) {
+        int ret = 0, sum = 0;
+        /* [groups "."] */
+        ret = parseGroups(offset);
+        if (ret == PARSE_ERROR) {
+            return PARSE_ERROR;
+        }
+        offset += ret;
+        sum += ret;
+
+        ret = parseString(offset, ".", false);
+        if (ret == PARSE_ERROR) {
+            return PARSE_ERROR;
+        }
+        sum += ret;
+
+        return sum;
+    }
+
+    /** ";" [ws] paramlist */
+    private int parseParams(int offset) {
+        int ret = 0, sum = 0;
+
+        ret = parseString(offset, ";", false);
+        if (ret == PARSE_ERROR) {
+            return PARSE_ERROR;
+        }
+        offset += ret;
+        sum += ret;
+
+        ret = removeWs(offset);
+        offset += ret;
+        sum += ret;
+
+        ret = parseParamList(offset);
+        if (ret == PARSE_ERROR) {
+            return PARSE_ERROR;
+        }
+        sum += ret;
+
+        return sum;
+    }
+
+    /**
+     * paramlist [ws] ";" [ws] param / param
+     */
+    private int parseParamList(int offset) {
+        int ret = 0, sum = 0;
+
+        ret = parseParam(offset);
+        if (ret == PARSE_ERROR) {
+            return PARSE_ERROR;
+        }
+        offset += ret;
+        sum += ret;
+
+        int offsetTemp = offset;
+        int sumTemp = sum;
+        for (;;) {
+            ret = removeWs(offsetTemp);
+            offsetTemp += ret;
+            sumTemp += ret;
+
+            ret = parseString(offsetTemp, ";", false);
+            if (ret == PARSE_ERROR) {
+                return sum;
+            }
+            offsetTemp += ret;
+            sumTemp += ret;
+
+            ret = removeWs(offsetTemp);
+            offsetTemp += ret;
+            sumTemp += ret;
+
+            ret = parseParam(offsetTemp);
+            if (ret == PARSE_ERROR) {
+                break;
+            }
+            offsetTemp += ret;
+            sumTemp += ret;
+
+            // offset = offsetTemp;
+            sum = sumTemp;
+        }
+        return sum;
+    }
+
+    /**
+     * param0 / param1 / param2 / param3 / param4 / param5 / knowntype<BR>
+     * TYPE / VALUE / ENDCODING / CHARSET / LANGUAGE ...
+     */
+    private int parseParam(int offset) {
+        int ret = 0, sum = 0;
+
+        ret = parseParam0(offset);
+        if (ret != PARSE_ERROR) {
+            sum += ret;
+            return sum;
+        }
+
+        ret = parseParam1(offset);
+        if (ret != PARSE_ERROR) {
+            sum += ret;
+            return sum;
+        }
+
+        ret = parseParam2(offset);
+        if (ret != PARSE_ERROR) {
+            sum += ret;
+            return sum;
+        }
+
+        ret = parseParam3(offset);
+        if (ret != PARSE_ERROR) {
+            sum += ret;
+            return sum;
+        }
+
+        ret = parseParam4(offset);
+        if (ret != PARSE_ERROR) {
+            sum += ret;
+            return sum;
+        }
+
+        ret = parseParam5(offset);
+        if (ret != PARSE_ERROR) {
+            sum += ret;
+            return sum;
+        }
+
+        int start = offset;
+        ret = parseKnownType(offset);
+        if (ret == PARSE_ERROR) {
+            return PARSE_ERROR;
+        }
+        offset += ret;
+        sum += ret;
+        if (mBuilder != null) {
+            mBuilder.propertyParamType(null);
+            mBuilder.propertyParamValue(mBuffer.substring(start, offset));
+        }
+
+        return sum;
+    }
+
+    /** "TYPE" [ws] "=" [ws] ptypeval */
+    private int parseParam0(int offset) {
+        int ret = 0, sum = 0, start = offset;
+
+        ret = parseString(offset, "TYPE", true);
+        if (ret == PARSE_ERROR) {
+            return PARSE_ERROR;
+        }
+        offset += ret;
+        sum += ret;
+        if (mBuilder != null) {
+            mBuilder.propertyParamType(mBuffer.substring(start, offset));
+        }
+
+        ret = removeWs(offset);
+        offset += ret;
+        sum += ret;
+
+        ret = parseString(offset, "=", false);
+        if (ret == PARSE_ERROR) {
+            return PARSE_ERROR;
+        }
+        offset += ret;
+        sum += ret;
+
+        ret = removeWs(offset);
+        offset += ret;
+        sum += ret;
+
+        start = offset;
+        ret = parsePTypeVal(offset);
+        if (ret == PARSE_ERROR) {
+            return PARSE_ERROR;
+        }
+        offset += ret;
+        sum += ret;
+        if (mBuilder != null) {
+            mBuilder.propertyParamValue(mBuffer.substring(start, offset));
+        }
+
+        return sum;
+
+    }
+
+    /** "VALUE" [ws] "=" [ws] pvalueval */
+    private int parseParam1(int offset) {
+        int ret = 0, sum = 0, start = offset;
+
+        ret = parseString(offset, "VALUE", true);
+        if (ret == PARSE_ERROR) {
+            return PARSE_ERROR;
+        }
+        offset += ret;
+        sum += ret;
+        if (mBuilder != null) {
+            mBuilder.propertyParamType(mBuffer.substring(start, offset));
+        }
+
+        ret = removeWs(offset);
+        offset += ret;
+        sum += ret;
+
+        ret = parseString(offset, "=", false);
+        if (ret == PARSE_ERROR) {
+            return PARSE_ERROR;
+        }
+        offset += ret;
+        sum += ret;
+
+        ret = removeWs(offset);
+        offset += ret;
+        sum += ret;
+
+        start = offset;
+        ret = parsePValueVal(offset);
+        if (ret == PARSE_ERROR) {
+            return PARSE_ERROR;
+        }
+        offset += ret;
+        sum += ret;
+        if (mBuilder != null) {
+            mBuilder.propertyParamValue(mBuffer.substring(start, offset));
+        }
+
+        return sum;
+    }
+
+    /** "ENCODING" [ws] "=" [ws] pencodingval */
+    private int parseParam2(int offset) {
+        int ret = 0, sum = 0, start = offset;
+
+        ret = parseString(offset, "ENCODING", true);
+        if (ret == PARSE_ERROR) {
+            return PARSE_ERROR;
+        }
+        offset += ret;
+        sum += ret;
+        if (mBuilder != null) {
+            mBuilder.propertyParamType(mBuffer.substring(start, offset));
+        }
+
+        ret = removeWs(offset);
+        offset += ret;
+        sum += ret;
+
+        ret = parseString(offset, "=", false);
+        if (ret == PARSE_ERROR) {
+            return PARSE_ERROR;
+        }
+        offset += ret;
+        sum += ret;
+
+        ret = removeWs(offset);
+        offset += ret;
+        sum += ret;
+
+        start = offset;
+        ret = parsePEncodingVal(offset);
+        if (ret == PARSE_ERROR) {
+            return PARSE_ERROR;
+        }
+        offset += ret;
+        sum += ret;
+        if (mBuilder != null) {
+            mBuilder.propertyParamValue(mBuffer.substring(start, offset));
+        }
+
+        return sum;
+
+    }
+
+    /** "CHARSET" [ws] "=" [ws] charsetval */
+    private int parseParam3(int offset) {
+        int ret = 0, sum = 0, start = offset;
+
+        ret = parseString(offset, "CHARSET", true);
+        if (ret == PARSE_ERROR) {
+            return PARSE_ERROR;
+        }
+        offset += ret;
+        sum += ret;
+        if (mBuilder != null) {
+            mBuilder.propertyParamType(mBuffer.substring(start, offset));
+        }
+
+        ret = removeWs(offset);
+        offset += ret;
+        sum += ret;
+
+        ret = parseString(offset, "=", false);
+        if (ret == PARSE_ERROR) {
+            return PARSE_ERROR;
+        }
+        offset += ret;
+        sum += ret;
+
+        ret = removeWs(offset);
+        offset += ret;
+        sum += ret;
+
+        start = offset;
+        ret = parseCharsetVal(offset);
+        if (ret == PARSE_ERROR) {
+            return PARSE_ERROR;
+        }
+        offset += ret;
+        sum += ret;
+        if (mBuilder != null) {
+            mBuilder.propertyParamValue(mBuffer.substring(start, offset));
+        }
+
+        return sum;
+    }
+
+    /** "LANGUAGE" [ws] "=" [ws] langval */
+    private int parseParam4(int offset) {
+        int ret = 0, sum = 0, start = offset;
+
+        ret = parseString(offset, "LANGUAGE", true);
+        if (ret == PARSE_ERROR) {
+            return PARSE_ERROR;
+        }
+        offset += ret;
+        sum += ret;
+        if (mBuilder != null) {
+            mBuilder.propertyParamType(mBuffer.substring(start, offset));
+        }
+
+        ret = removeWs(offset);
+        offset += ret;
+        sum += ret;
+
+        ret = parseString(offset, "=", false);
+        if (ret == PARSE_ERROR) {
+            return PARSE_ERROR;
+        }
+        offset += ret;
+        sum += ret;
+
+        ret = removeWs(offset);
+        offset += ret;
+        sum += ret;
+
+        start = offset;
+        ret = parseLangVal(offset);
+        if (ret == PARSE_ERROR) {
+            return PARSE_ERROR;
+        }
+        offset += ret;
+        sum += ret;
+        if (mBuilder != null) {
+            mBuilder.propertyParamValue(mBuffer.substring(start, offset));
+        }
+
+        return sum;
+
+    }
+
+    /** "X-" word [ws] "=" [ws] word */
+    private int parseParam5(int offset) {
+        int ret = 0, sum = 0, start = offset;
+
+        ret = parseXWord(offset);
+        if (ret == PARSE_ERROR) {
+            return PARSE_ERROR;
+        }
+        offset += ret;
+        sum += ret;
+        if (mBuilder != null) {
+            mBuilder.propertyParamType(mBuffer.substring(start, offset));
+        }
+
+        ret = removeWs(offset);
+        offset += ret;
+        sum += ret;
+
+        ret = parseString(offset, "=", false);
+        if (ret == PARSE_ERROR) {
+            return PARSE_ERROR;
+        }
+        offset += ret;
+        sum += ret;
+
+        ret = removeWs(offset);
+        offset += ret;
+        sum += ret;
+
+        start = offset;
+        ret = parseWord(offset);
+        if (ret == PARSE_ERROR) {
+            return PARSE_ERROR;
+        }
+        offset += ret;
+        sum += ret;
+        if (mBuilder != null) {
+            mBuilder.propertyParamValue(mBuffer.substring(start, offset));
+        }
+
+        return sum;
+    }
+
+    /**
+     * knowntype: "DOM" / "INTL" / ...
+     */
+    private int parseKnownType(int offset) {
+        String word = getWord(offset);
+
+        if (mKnownTypeSet.contains(word.toUpperCase())) {
+            return word.length();
+        }
+        return PARSE_ERROR;
+    }
+
+    /** knowntype / "X-" word */
+    private int parsePTypeVal(int offset) {
+        int ret = 0, sum = 0;
+
+        ret = parseKnownType(offset);
+        if (ret != PARSE_ERROR) {
+            sum += ret;
+            return sum;
+        }
+
+        ret = parseXWord(offset);
+        if (ret != PARSE_ERROR) {
+            sum += ret;
+            return sum;
+        }
+        sum += ret;
+
+        return sum;
+    }
+
+    /** "LOGO" /.../ XWord, case insensitive */
+    private int parseName(int offset) {
+        int ret = 0;
+        ret = parseXWord(offset);
+        if (ret != PARSE_ERROR) {
+            return ret;
+        }
+        String word = getWord(offset).toUpperCase();
+        if (mName.contains(word)) {
+            return word.length();
+        }
+        return PARSE_ERROR;
+    }
+
+    /** groups "." word / word */
+    private int parseGroups(int offset) {
+        int ret = 0, sum = 0;
+
+        ret = parseWord(offset);
+        if (ret == PARSE_ERROR) {
+            return PARSE_ERROR;
+        }
+        offset += ret;
+        sum += ret;
+
+        for (;;) {
+            ret = parseString(offset, ".", false);
+            if (ret == PARSE_ERROR) {
+                break;
+            }
+
+            int ret1 = parseWord(offset);
+            if (ret1 == PARSE_ERROR) {
+                break;
+            }
+            offset += ret + ret1;
+            sum += ret + ret1;
+        }
+        return sum;
+    }
+
+    /**
+     * Translate escape characters("\\", "\;") which define in vcard2.1 spec.
+     * But for fault tolerance, we will translate "\:" and "\,", which isn't
+     * define in vcard2.1 explicitly, as the same behavior as other client.
+     *
+     * @param str:
+     *            the string will be translated.
+     * @return the string which do not contain any escape character in vcard2.1
+     */
+    private String escapeTranslator(String str) {
+        if (null == str)
+            return null;
+
+        String tmp = str.replace("\\\\", "\n\r\n");
+        tmp = tmp.replace("\\;", ";");
+        tmp = tmp.replace("\\:", ":");
+        tmp = tmp.replace("\\,", ",");
+        tmp = tmp.replace("\n\r\n", "\\");
+        return tmp;
+    }
+}
diff --git a/core/java/android/syncml/pim/vcard/VCardParser_V30.java b/core/java/android/syncml/pim/vcard/VCardParser_V30.java
new file mode 100644
index 0000000..c56cfed
--- /dev/null
+++ b/core/java/android/syncml/pim/vcard/VCardParser_V30.java
@@ -0,0 +1,157 @@
+/*
+ * 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 android.syncml.pim.vcard;
+
+import android.syncml.pim.VBuilder;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+
+/**
+ * This class is used to parse vcard3.0. <br>
+ * It get useful data refer from android contact, and alter to vCard 2.1 format.
+ * Then reuse vcard 2.1 parser to analyze the result.<br>
+ * Please refer to vCard Specification 3.0
+ */
+public class VCardParser_V30 extends VCardParser_V21 {
+    private static final String V21LINEBREAKER = "\r\n";
+
+    private static final HashSet<String> acceptablePropsWithParam = new HashSet<String>(
+            Arrays.asList("PHOTO", "LOGO", "TEL", "EMAIL", "ADR"));
+
+    private static final HashSet<String> acceptablePropsWithoutParam = new HashSet<String>(
+            Arrays.asList("ORG", "NOTE", "TITLE", "FN", "N"));
+
+    private static final HashMap<String, String> propV30ToV21Map = new HashMap<String, String>();
+
+    static {
+        propV30ToV21Map.put("PHOTO", "PHOTO");
+        propV30ToV21Map.put("LOGO", "PHOTO");
+    }
+
+    @Override
+    public boolean parse(InputStream is, String encoding, VBuilder builder)
+            throws IOException {
+        // get useful info for android contact, and alter to vCard 2.1
+        byte[] bytes = new byte[is.available()];
+        is.read(bytes);
+        String scStr = new String(bytes);
+        StringBuilder v21str = new StringBuilder("");
+
+        String[] strlist = splitProperty(scStr);
+
+        if ("BEGIN:vCard".equals(strlist[0])
+                || "BEGIN:VCARD".equals(strlist[0])) {
+            v21str.append("BEGIN:VCARD" + V21LINEBREAKER);
+        } else {
+            return false;
+        }
+
+        for (int i = 1; i < strlist.length - 1; i++) {// for ever property
+            // line
+            String propName;
+            String params;
+            String value;
+
+            String line = strlist[i];
+            if ("".equals(line)) { // line breaker is useful in encoding string
+                v21str.append(V21LINEBREAKER);
+                continue;
+            }
+
+            String[] contentline = line.split(":", 2);
+            String propNameAndParam = contentline[0];
+            value = (contentline.length > 1) ? contentline[1] : "";
+            if (propNameAndParam.length() > 0) {
+                String[] nameAndParams = propNameAndParam.split(";", 2);
+                propName = nameAndParams[0];
+                params = (nameAndParams.length > 1) ? nameAndParams[1] : "";
+
+                if (acceptablePropsWithParam.contains(propName)
+                        || acceptablePropsWithoutParam.contains(propName)) {
+                    v21str.append(mapContentlineV30ToV21(propName, params,
+                            value));
+                }
+            }
+        }// end for
+
+        if ("END:vCard".equals(strlist[strlist.length - 1])
+                || "END:VCARD".equals(strlist[strlist.length - 1])) {
+            v21str.append("END:VCARD" + V21LINEBREAKER);
+        } else {
+            return false;
+        }
+
+        return super.parse(
+        // use vCard 2.1 parser
+                new ByteArrayInputStream(v21str.toString().getBytes()),
+                encoding, builder);
+    }
+
+    /**
+     * Convert V30 string to V21 string
+     *
+     * @param propName
+     *            The name of property
+     * @param params
+     *            parameter of property
+     * @param value
+     *            value of property
+     * @return the converted string
+     */
+    private String mapContentlineV30ToV21(String propName, String params,
+            String value) {
+        String result;
+
+        if (propV30ToV21Map.containsKey(propName)) {
+            result = propV30ToV21Map.get(propName);
+        } else {
+            result = propName;
+        }
+        // Alter parameter part of property to vCard 2.1 format
+        if (acceptablePropsWithParam.contains(propName) && params.length() > 0)
+            result = result
+                    + ";"
+                    + params.replaceAll(",", ";").replaceAll("ENCODING=B",
+                            "ENCODING=BASE64").replaceAll("ENCODING=b",
+                            "ENCODING=BASE64");
+
+        return result + ":" + value + V21LINEBREAKER;
+    }
+
+    /**
+     * Split ever property line to Stringp[], not split folding line.
+     *
+     * @param scStr
+     *            the string to be splitted
+     * @return a list of splitted string
+     */
+    private String[] splitProperty(String scStr) {
+        /*
+         * Property splitted by \n, and unfold folding lines by removing
+         * CRLF+LWSP-char
+         */
+        scStr = scStr.replaceAll("\r\n", "\n").replaceAll("\n ", "")
+                .replaceAll("\n\t", "");
+        String[] strs = scStr.split("\n");
+        return strs;
+    }
+}
diff --git a/core/java/android/syncml/pim/vcard/package.html b/core/java/android/syncml/pim/vcard/package.html
new file mode 100644
index 0000000..cb4ca46
--- /dev/null
+++ b/core/java/android/syncml/pim/vcard/package.html
@@ -0,0 +1,6 @@
+<HTML>
+<BODY>
+Support classes for SyncML.
+{@hide}
+</BODY>
+</HTML>
\ No newline at end of file
diff --git a/core/java/android/test/AndroidTestCase.java b/core/java/android/test/AndroidTestCase.java
new file mode 100644
index 0000000..9bafa32
--- /dev/null
+++ b/core/java/android/test/AndroidTestCase.java
@@ -0,0 +1,86 @@
+/*
+ * 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 android.test;
+
+import android.content.Context;
+
+import java.lang.reflect.Field;
+
+import junit.framework.TestCase;
+
+/**
+ * Extend this if you need to access Resources or other things that depend on Activity Context.
+ */
+public class AndroidTestCase extends TestCase {
+
+    protected Context mContext;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        super.tearDown();
+    }
+
+    public void testAndroidTestCaseSetupProperly() {
+        assertNotNull("Context is null. setContext should be called before tests are run",
+                mContext);        
+    }
+
+    public void setContext(Context context) {
+        mContext = context;
+    }
+
+    public Context getContext() {
+        return mContext;
+    }
+
+    /**
+     * This function is called by various TestCase implementations, at tearDown() time, in order
+     * to scrub out any class variables.  This protects against memory leaks in the case where a
+     * test case creates a non-static inner class (thus referencing the test case) and gives it to
+     * someone else to hold onto.
+     * 
+     * @param testCaseClass The class of the derived TestCase implementation.
+     * 
+     * @throws IllegalAccessException
+     */
+    protected void scrubClass(final Class<?> testCaseClass)
+    throws IllegalAccessException {
+        final Field[] fields = getClass().getDeclaredFields();
+        for (Field field : fields) {
+            final Class<?> fieldClass = field.getDeclaringClass();
+            if (testCaseClass.isAssignableFrom(fieldClass) && !field.getType().isPrimitive()) {
+                try {
+                    field.setAccessible(true);
+                    field.set(this, null);
+                } catch (Exception e) {
+                    android.util.Log.d("TestCase", "Error: Could not nullify field!");
+                }
+
+                if (field.get(this) != null) {
+                    android.util.Log.d("TestCase", "Error: Could not nullify field!");
+                }
+            }
+        }
+    }
+
+
+}
diff --git a/core/java/android/test/FlakyTest.java b/core/java/android/test/FlakyTest.java
new file mode 100644
index 0000000..919767f
--- /dev/null
+++ b/core/java/android/test/FlakyTest.java
@@ -0,0 +1,41 @@
+/*
+ * 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 android.test;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import java.lang.annotation.ElementType;
+
+/**
+ * This annotation can be used on an {@link android.test.InstrumentationTestCase}'s
+ * test methods. When the annotation is present, the test method is re-executed if
+ * the test fails. The total number of executions is specified by the tolerance and
+ * defaults to 1.
+ */
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface FlakyTest {
+    /**
+     * Indicates how many times a test can run and fail before being reported
+     * as a failed test. If the tolerance factor is less than 1, the test runs
+     * only once.
+     *
+     * @return The total number of allowed run, the default is 1.
+     */
+    int tolerance() default 1;
+}
diff --git a/core/java/android/test/InstrumentationTestCase.java b/core/java/android/test/InstrumentationTestCase.java
new file mode 100644
index 0000000..82f2ef9
--- /dev/null
+++ b/core/java/android/test/InstrumentationTestCase.java
@@ -0,0 +1,306 @@
+/*
+ * Copyright (C) 2007 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 android.test;
+
+import junit.framework.TestCase;
+
+import android.app.Activity;
+import android.app.Instrumentation;
+import android.content.Intent;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.KeyEvent;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.lang.reflect.InvocationTargetException;
+
+/**
+ * A test case that has access to {@link Instrumentation}.
+ */
+public class InstrumentationTestCase extends TestCase {
+
+    private Instrumentation mInstrumentation;
+
+    /**
+     * Injects instrumentation into this test case. This method is
+     * called by the test runner during test setup.
+     * 
+     * @param instrumentation the instrumentation to use with this instance
+     */
+    public void injectInsrumentation(Instrumentation instrumentation) {
+        mInstrumentation = instrumentation;
+    }
+
+    /**
+     * Inheritors can access the instrumentation using this.
+     * @return instrumentation
+     */
+    public Instrumentation getInstrumentation() {
+        return mInstrumentation;
+    }
+
+    /**
+     * Utility method for launching an activity.  
+     * 
+     * <p>The {@link Intent} used to launch the Activity is:
+     *  action = {@link Intent#ACTION_MAIN}
+     *  extras = null, unless a custom bundle is provided here
+     * All other fields are null or empty.
+     *
+     * @param pkg The package hosting the activity to be launched.
+     * @param activityCls The activity class to launch.
+     * @param extras Optional extra stuff to pass to the activity.
+     * @return The activity, or null if non launched.
+     */
+    @SuppressWarnings("unchecked")
+    public final <T extends Activity> T launchActivity(
+            String pkg,
+            Class<T> activityCls,
+            Bundle extras) {
+        Intent intent = new Intent(Intent.ACTION_MAIN);
+        if (extras != null) {
+            intent.putExtras(extras);
+        }
+        return launchActivityWithIntent(pkg, activityCls, intent);
+    }
+
+    /**
+     * Utility method for launching an activity with a specific Intent.
+     * @param pkg The package hosting the activity to be launched.
+     * @param activityCls The activity class to launch.
+     * @param intent The intent to launch with
+     * @return The activity, or null if non launched.
+     */
+    @SuppressWarnings("unchecked")
+    public final <T extends Activity> T launchActivityWithIntent(
+            String pkg,
+            Class<T> activityCls,
+            Intent intent) {
+        intent.setClassName(pkg, activityCls.getName());
+        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        T activity = (T) getInstrumentation().startActivitySync(intent);
+        getInstrumentation().waitForIdleSync();
+        return activity;
+    }
+    
+    /**
+     * Helper for running portions of a test on the UI thread.
+     * 
+     * Note, in most cases it is simpler to annotate the test method with 
+     * {@link android.test.UiThreadTest}, which will run the entire test method on the UI thread.
+     * Use this method if you need to switch in and out of the UI thread to perform your test.
+     * 
+     * @param r runnable containing test code in the {@link Runnable#run()} method
+     */
+    public void runTestOnUiThread(final Runnable r) throws Throwable {
+        final Throwable[] exceptions = new Throwable[1];
+        getInstrumentation().runOnMainSync(new Runnable() {
+            public void run() {
+                try {
+                    r.run();
+                } catch (Throwable throwable) {
+                    exceptions[0] = throwable;
+                }
+            }
+        });
+        if (exceptions[0] != null) {
+            throw exceptions[0];
+        }
+    }
+
+    /**
+     * Runs the current unit test. If the unit test is annotated with
+     * {@link android.test.UiThreadTest}, the test is run on the UI thread.
+     */
+    @Override
+    protected void runTest() throws Throwable {
+        String fName = getName();
+        assertNotNull(fName);
+        Method method = null;
+        try {
+            // use getMethod to get all public inherited
+            // methods. getDeclaredMethods returns all
+            // methods of this class but excludes the
+            // inherited ones.
+            method = getClass().getMethod(fName, (Class[]) null);
+        } catch (NoSuchMethodException e) {
+            fail("Method \""+fName+"\" not found");
+        }
+
+        if (!Modifier.isPublic(method.getModifiers())) {
+            fail("Method \""+fName+"\" should be public");
+        }
+
+        int runCount = 1;
+        if (method.isAnnotationPresent(FlakyTest.class)) {
+            runCount = method.getAnnotation(FlakyTest.class).tolerance();
+        }
+
+        if (method.isAnnotationPresent(UiThreadTest.class)) {
+            final int tolerance = runCount;
+            final Method testMethod = method;
+            final Throwable[] exceptions = new Throwable[1];
+            getInstrumentation().runOnMainSync(new Runnable() {
+                public void run() {
+                    try {
+                        runMethod(testMethod, tolerance);
+                    } catch (Throwable throwable) {
+                        exceptions[0] = throwable;
+                    }
+                }
+            });
+            if (exceptions[0] != null) {
+                throw exceptions[0];
+            }
+        } else {
+            runMethod(method, runCount);
+        }
+    }
+
+    private void runMethod(Method runMethod, int tolerance) throws Throwable {
+        Throwable exception = null;
+
+        int runCount = 0;
+        do {
+            try {
+                runMethod.invoke(this, (Object[]) null);
+                exception = null;
+            } catch (InvocationTargetException e) {
+                e.fillInStackTrace();
+                exception = e.getTargetException();
+            } catch (IllegalAccessException e) {
+                e.fillInStackTrace();
+                exception = e;
+            } finally {
+                runCount++;
+            }
+        } while ((runCount < tolerance) && (exception != null));
+
+        if (exception != null) {
+            throw exception;
+        }
+    }
+
+    /**
+     * Sends a series of key events through instrumentation and waits for idle. The sequence
+     * of keys is a string containing the key names as specified in KeyEvent, without the
+     * KEYCODE_ prefix. For instance: sendKeys("DPAD_LEFT A B C DPAD_CENTER"). Each key can
+     * be repeated by using the N* prefix. For instance, to send two KEYCODE_DPAD_LEFT, use
+     * the following: sendKeys("2*DPAD_LEFT").
+     *
+     * @param keysSequence The sequence of keys.
+     */
+    public void sendKeys(String keysSequence) {
+        final String[] keys = keysSequence.split(" ");
+        final int count = keys.length;
+
+        final Instrumentation instrumentation = getInstrumentation();
+
+        for (int i = 0; i < count; i++) {
+            String key = keys[i];
+            int repeater = key.indexOf('*');
+
+            int keyCount;
+            try {
+                keyCount = repeater == -1 ? 1 : Integer.parseInt(key.substring(0, repeater));
+            } catch (NumberFormatException e) {
+                Log.w("ActivityTestCase", "Invalid repeat count: " + key);
+                continue;
+            }
+
+            if (repeater != -1) {
+                key = key.substring(repeater + 1);
+            }
+
+            for (int j = 0; j < keyCount; j++) {
+                try {
+                    final Field keyCodeField = KeyEvent.class.getField("KEYCODE_" + key);
+                    final int keyCode = keyCodeField.getInt(null);
+                    instrumentation.sendKeyDownUpSync(keyCode);
+                } catch (NoSuchFieldException e) {
+                    Log.w("ActivityTestCase", "Unknown keycode: KEYCODE_" + key);
+                    break;
+                } catch (IllegalAccessException e) {
+                    Log.w("ActivityTestCase", "Unknown keycode: KEYCODE_" + key);
+                    break;
+                }
+            }
+        }
+
+        instrumentation.waitForIdleSync();
+    }
+
+    /**
+     * Sends a series of key events through instrumentation and waits for idle. For instance:
+     * sendKeys(KEYCODE_DPAD_LEFT, KEYCODE_DPAD_CENTER).
+     *
+     * @param keys The series of key codes to send through instrumentation.
+     */
+    public void sendKeys(int... keys) {
+        final int count = keys.length;
+        final Instrumentation instrumentation = getInstrumentation();
+
+        for (int i = 0; i < count; i++) {
+            instrumentation.sendKeyDownUpSync(keys[i]);
+        }
+
+        instrumentation.waitForIdleSync();
+    }
+
+    /**
+     * Sends a series of key events through instrumentation and waits for idle. Each key code
+     * must be preceded by the number of times the key code must be sent. For instance:
+     * sendRepeatedKeys(1, KEYCODE_DPAD_CENTER, 2, KEYCODE_DPAD_LEFT).
+     *
+     * @param keys The series of key repeats and codes to send through instrumentation.
+     */
+    public void sendRepeatedKeys(int... keys) {
+        final int count = keys.length;
+        if ((count & 0x1) == 0x1) {
+            throw new IllegalArgumentException("The size of the keys array must "
+                    + "be a multiple of 2");
+        }
+
+        final Instrumentation instrumentation = getInstrumentation();
+
+        for (int i = 0; i < count; i += 2) {
+            final int keyCount = keys[i];
+            final int keyCode = keys[i + 1];
+            for (int j = 0; j < keyCount; j++) {
+                instrumentation.sendKeyDownUpSync(keyCode);
+            }
+        }
+
+        instrumentation.waitForIdleSync();
+    }
+    
+    /**
+     * Make sure all resources are cleaned up and garbage collected before moving on to the next
+     * test. Subclasses that override this method should make sure they call super.tearDown()
+     * at the end of the overriding method.
+     * 
+     * @throws Exception
+     */
+    protected void tearDown() throws Exception {
+        Runtime.getRuntime().gc();
+        Runtime.getRuntime().runFinalization();
+        Runtime.getRuntime().gc();
+        super.tearDown();
+    }
+}
diff --git a/core/java/android/test/InstrumentationTestSuite.java b/core/java/android/test/InstrumentationTestSuite.java
new file mode 100644
index 0000000..2ab949e
--- /dev/null
+++ b/core/java/android/test/InstrumentationTestSuite.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2007 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 android.test;
+
+import android.app.Instrumentation;
+
+import junit.framework.TestSuite;
+import junit.framework.Test;
+import junit.framework.TestResult;
+
+/**
+ * A {@link junit.framework.TestSuite} that injects {@link android.app.Instrumentation} into
+ * {@link InstrumentationTestCase} before running them.
+ */
+public class InstrumentationTestSuite extends TestSuite {
+
+    private final Instrumentation mInstrumentation;
+
+    /**
+     * @param instr The instrumentation that will be injected into each
+     *   test before running it.
+     */
+    public InstrumentationTestSuite(Instrumentation instr) {
+        mInstrumentation = instr;
+    }
+
+
+    public InstrumentationTestSuite(String name, Instrumentation instr) {
+        super(name);
+        mInstrumentation = instr;
+    }
+
+    /**
+     * @param theClass Inspected for methods starting with 'test'
+     * @param instr The instrumentation to inject into each test before
+     *   running.
+     */
+    public InstrumentationTestSuite(final Class theClass, Instrumentation instr) {
+        super(theClass);
+        mInstrumentation = instr;
+    }
+
+
+    @Override
+    public void addTestSuite(Class testClass) {
+        addTest(new InstrumentationTestSuite(testClass, mInstrumentation));
+    }
+
+
+    @Override
+    public void runTest(Test test, TestResult result) {
+
+        if (test instanceof InstrumentationTestCase) {
+            ((InstrumentationTestCase) test).injectInsrumentation(mInstrumentation);
+        }
+
+        // run the test as usual
+        super.runTest(test, result);
+    }
+}
diff --git a/core/java/android/test/PerformanceTestCase.java b/core/java/android/test/PerformanceTestCase.java
new file mode 100644
index 0000000..679ad40
--- /dev/null
+++ b/core/java/android/test/PerformanceTestCase.java
@@ -0,0 +1,65 @@
+/*
+ * 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 android.test;
+
+/**
+ * More complex interface performance for test cases.
+ * 
+ * If you want your test to be used as a performance test, you must
+ * implement this interface.
+ */
+public interface PerformanceTestCase
+{
+    /**
+     * Callbacks for {@link PerformanceTestCase}.
+     */
+    public interface Intermediates
+    {
+        void setInternalIterations(int count);
+        void startTiming(boolean realTime);
+        void addIntermediate(String name);
+        void addIntermediate(String name, long timeInNS);
+        void finishTiming(boolean realTime);
+    }
+
+    /**
+     * Set up to begin performance tests.  The 'intermediates' is a
+     * communication channel to send back intermediate performance numbers --
+     * if you use it, you will probably want to ensure your test is only
+     * executed once by returning 1.  Otherwise, return 0 to allow the test
+     * harness to decide the number of iterations.
+     * 
+     * <p>If you return a non-zero iteration count, you should call
+     * {@link Intermediates#startTiming intermediates.startTiming} and
+     * {@link Intermediates#finishTiming intermediates.endTiming} to report the
+     * duration of the test whose performance should actually be measured.
+     * 
+     * @param intermediates Callback for sending intermediate results.
+     * 
+     * @return int Maximum number of iterations to run, or 0 to let the caller
+     *         decide.
+     */
+    int startPerformance(Intermediates intermediates);
+    
+    /**
+     * This method is used to determine what modes this test case can run in.
+     * 
+     * @return true if this test case can only be run in performance mode.
+     */
+    boolean isPerformanceOnly();
+}
+
diff --git a/core/java/android/test/UiThreadTest.java b/core/java/android/test/UiThreadTest.java
new file mode 100644
index 0000000..cd92231
--- /dev/null
+++ b/core/java/android/test/UiThreadTest.java
@@ -0,0 +1,33 @@
+/*
+ * 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 android.test;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import java.lang.annotation.ElementType;
+
+/**
+ * This annotation can be used on an {@link InstrumentationTestCase}'s test methods.
+ * When the annotation is present, the test method is executed on the application's
+ * main thread (or UI thread.) Note that instrumentation methods may not be used
+ * when this annotation is present.
+ */
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface UiThreadTest {
+}
diff --git a/core/java/android/test/package.html b/core/java/android/test/package.html
new file mode 100644
index 0000000..1972bed
--- /dev/null
+++ b/core/java/android/test/package.html
@@ -0,0 +1,5 @@
+<HTML>
+<BODY>
+A framework for writing Android test cases and suites.
+</BODY>
+</HTML>
\ No newline at end of file
diff --git a/core/java/android/test/suitebuilder/annotation/LargeTest.java b/core/java/android/test/suitebuilder/annotation/LargeTest.java
new file mode 100644
index 0000000..a6269e7
--- /dev/null
+++ b/core/java/android/test/suitebuilder/annotation/LargeTest.java
@@ -0,0 +1,30 @@
+/*
+ * 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 android.test.suitebuilder.annotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Marks a test that should run as part of the large tests.
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.METHOD, ElementType.TYPE})
+public @interface LargeTest {
+}
diff --git a/core/java/android/test/suitebuilder/annotation/MediumTest.java b/core/java/android/test/suitebuilder/annotation/MediumTest.java
new file mode 100644
index 0000000..8afeb91
--- /dev/null
+++ b/core/java/android/test/suitebuilder/annotation/MediumTest.java
@@ -0,0 +1,31 @@
+/*
+ * 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 android.test.suitebuilder.annotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Marks a test that should run as part of the medium tests.
+ *
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.METHOD, ElementType.TYPE})
+public @interface MediumTest {
+}
diff --git a/core/java/android/test/suitebuilder/annotation/SmallTest.java b/core/java/android/test/suitebuilder/annotation/SmallTest.java
new file mode 100644
index 0000000..ad530e2
--- /dev/null
+++ b/core/java/android/test/suitebuilder/annotation/SmallTest.java
@@ -0,0 +1,30 @@
+/*
+ * 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 android.test.suitebuilder.annotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Marks a test that should run as part of the small tests.
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.METHOD, ElementType.TYPE})
+public @interface SmallTest {
+}
diff --git a/core/java/android/test/suitebuilder/annotation/Smoke.java b/core/java/android/test/suitebuilder/annotation/Smoke.java
new file mode 100644
index 0000000..237e033
--- /dev/null
+++ b/core/java/android/test/suitebuilder/annotation/Smoke.java
@@ -0,0 +1,34 @@
+/*
+ * 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 android.test.suitebuilder.annotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Marks a test that should run as part of the smoke tests.
+ * The <code>android.test.suitebuilder.SmokeTestSuiteBuilder</code>
+ * will run all tests with this annotation.
+ *
+ * @see android.test.suitebuilder.SmokeTestSuiteBuilder
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.METHOD, ElementType.TYPE})
+public @interface Smoke {
+}
diff --git a/core/java/android/test/suitebuilder/annotation/Suppress.java b/core/java/android/test/suitebuilder/annotation/Suppress.java
new file mode 100644
index 0000000..f16c8fa
--- /dev/null
+++ b/core/java/android/test/suitebuilder/annotation/Suppress.java
@@ -0,0 +1,33 @@
+/*
+ * 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 android.test.suitebuilder.annotation;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import java.lang.annotation.ElementType;
+
+/**
+ * Use this annotation on test classes or test methods that should not be included in a test
+ * suite. If the annotation appears on the class then no tests in that class will be included. If
+ * the annotation appears only on a test method then only that method will be excluded.
+ *
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.METHOD, ElementType.TYPE})
+public @interface Suppress {
+}
diff --git a/core/java/android/text/AlteredCharSequence.java b/core/java/android/text/AlteredCharSequence.java
new file mode 100644
index 0000000..4cc71fd
--- /dev/null
+++ b/core/java/android/text/AlteredCharSequence.java
@@ -0,0 +1,127 @@
+/*
+ * 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 android.text;
+
+// XXX should this really be in the public API at all?
+/**
+ * An AlteredCharSequence is a CharSequence that is largely mirrored from
+ * another CharSequence, except that a specified range of characters are
+ * mirrored from a different char array instead.
+ */
+public class AlteredCharSequence
+implements CharSequence, GetChars
+{
+    /**
+     * Create an AlteredCharSequence whose text (and possibly spans)
+     * are mirrored from <code>source</code>, except that the range of
+     * offsets <code>substart</code> inclusive to <code>subend</code> exclusive
+     * are mirrored instead from <code>sub</code>, beginning at offset 0.
+     */
+    public static AlteredCharSequence make(CharSequence source, char[] sub,
+                                           int substart, int subend) {
+        if (source instanceof Spanned)
+            return new AlteredSpanned(source, sub, substart, subend);
+        else
+            return new AlteredCharSequence(source, sub, substart, subend);
+    }
+
+    private AlteredCharSequence(CharSequence source, char[] sub,
+                                int substart, int subend) {
+        mSource = source;
+        mChars = sub;
+        mStart = substart;
+        mEnd = subend;
+    }
+
+    /* package */ void update(char[] sub, int substart, int subend) {
+        mChars = sub;
+        mStart = substart;
+        mEnd = subend;
+    }
+
+    private static class AlteredSpanned
+    extends AlteredCharSequence
+    implements Spanned
+    {
+        private AlteredSpanned(CharSequence source, char[] sub,
+                               int substart, int subend) {
+            super(source, sub, substart, subend);
+            mSpanned = (Spanned) source;
+        }
+
+        public <T> T[] getSpans(int start, int end, Class<T> kind) {
+            return mSpanned.getSpans(start, end, kind);
+        }
+
+        public int getSpanStart(Object span) {
+            return mSpanned.getSpanStart(span);
+        }
+
+        public int getSpanEnd(Object span) {
+            return mSpanned.getSpanEnd(span);
+        }
+
+        public int getSpanFlags(Object span) {
+            return mSpanned.getSpanFlags(span);
+        }
+
+        public int nextSpanTransition(int start, int end, Class kind) {
+            return mSpanned.nextSpanTransition(start, end, kind);
+        }
+
+        private Spanned mSpanned;
+    }
+
+    public char charAt(int off) {
+        if (off >= mStart && off < mEnd)
+            return mChars[off - mStart];
+        else
+            return mSource.charAt(off);
+    }
+
+    public int length() {
+        return mSource.length();
+    }
+
+    public CharSequence subSequence(int start, int end) {
+        return AlteredCharSequence.make(mSource.subSequence(start, end),
+                                        mChars, mStart - start, mEnd - start);
+    }
+
+    public void getChars(int start, int end, char[] dest, int off) {
+        TextUtils.getChars(mSource, start, end, dest, off);
+
+        start = Math.max(mStart, start);
+        end = Math.min(mEnd, end);
+
+        if (start > end)
+            System.arraycopy(mChars, start - mStart, dest, off, end - start);
+    }
+
+    public String toString() {
+        int len = length();
+
+        char[] ret = new char[len];
+        getChars(0, len, ret, 0);
+        return String.valueOf(ret);
+    }
+
+    private int mStart;
+    private int mEnd;
+    private char[] mChars;
+    private CharSequence mSource;
+}
diff --git a/core/java/android/text/AndroidCharacter.java b/core/java/android/text/AndroidCharacter.java
new file mode 100644
index 0000000..6dfd64d
--- /dev/null
+++ b/core/java/android/text/AndroidCharacter.java
@@ -0,0 +1,45 @@
+/*
+ * 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 android.text;
+
+/**
+ * AndroidCharacter exposes some character properties that are not
+ * easily accessed from java.lang.Character.
+ */
+public class AndroidCharacter
+{
+    /**
+     * Fill in the first <code>count</code> bytes of <code>dest</code> with the
+     * directionalities from the first <code>count</code> chars of <code>src</code>.
+     * This is just like Character.getDirectionality() except it is a
+     * batch operation.
+     */
+    public native static void getDirectionalities(char[] src, byte[] dest,
+                                                  int count);
+    /**
+     * Replace the specified slice of <code>text</code> with the chars'
+     * right-to-left mirrors (if any), returning true if any
+     * replacements were made.
+     */
+    public native static boolean mirror(char[] text, int start, int count);
+
+    /**
+     * Return the right-to-left mirror (or the original char if none)
+     * of the specified char.
+     */
+    public native static char getMirror(char ch);
+}
diff --git a/core/java/android/text/Annotation.java b/core/java/android/text/Annotation.java
new file mode 100644
index 0000000..dbc290b
--- /dev/null
+++ b/core/java/android/text/Annotation.java
@@ -0,0 +1,60 @@
+/*
+ * 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 android.text;
+
+import android.os.Parcel;
+
+/**
+ * Annotations are simple key-value pairs that are preserved across
+ * TextView save/restore cycles and can be used to keep application-specific
+ * data that needs to be maintained for regions of text.
+ */
+public class Annotation implements ParcelableSpan {
+    private final String mKey;
+    private final String mValue;
+
+    public Annotation(String key, String value) {
+        mKey = key;
+        mValue = value;
+    }
+
+    public Annotation(Parcel src) {
+        mKey = src.readString();
+        mValue = src.readString();
+    }
+    
+    public int getSpanTypeId() {
+        return TextUtils.ANNOTATION;
+    }
+    
+    public int describeContents() {
+        return 0;
+    }
+
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeString(mKey);
+        dest.writeString(mValue);
+    }
+
+    public String getKey() {
+        return mKey;
+    }
+
+    public String getValue() {
+        return mValue;
+    }
+}
diff --git a/core/java/android/text/AutoText.java b/core/java/android/text/AutoText.java
new file mode 100644
index 0000000..508d740
--- /dev/null
+++ b/core/java/android/text/AutoText.java
@@ -0,0 +1,246 @@
+/*
+ * Copyright (C) 2007 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 android.text;
+
+import android.content.res.Resources;
+import android.content.res.XmlResourceParser;
+import com.android.internal.util.XmlUtils;
+import android.view.View;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.util.Locale;
+
+/**
+ * This class accesses a dictionary of corrections to frequent misspellings.
+ */
+public class AutoText {
+    // struct trie {
+    //     char c;
+    //     int off;
+    //     struct trie *child;
+    //     struct trie *next;
+    // };
+
+    private static final int TRIE_C = 0;
+    private static final int TRIE_OFF = 1;
+    private static final int TRIE_CHILD = 2;
+    private static final int TRIE_NEXT = 3;
+
+    private static final int TRIE_SIZEOF = 4;
+    private static final char TRIE_NULL = (char) -1;
+    private static final int TRIE_ROOT = 0;
+
+    private static final int INCREMENT = 1024;
+
+    private static final int DEFAULT = 14337; // Size of the Trie 13 Aug 2007
+
+    private static final int RIGHT = 9300; // Size of 'right' 13 Aug 2007
+
+    private static AutoText sInstance = new AutoText(Resources.getSystem());
+    private static Object sLock = new Object();
+
+    // TODO:
+    //
+    // Note the assumption that the destination strings total less than
+    // 64K characters and that the trie for the source side totals less
+    // than 64K chars/offsets/child pointers/next pointers.
+    //
+    // This seems very safe for English (currently 7K of destination,
+    // 14K of trie) but may need to be revisited.
+
+    private char[] mTrie;
+    private char mTrieUsed;
+    private String mText;
+    private Locale mLocale;
+
+    private AutoText(Resources resources) {
+        mLocale = resources.getConfiguration().locale;
+        init(resources);
+    }
+
+    /**
+     * Retrieves a possible spelling correction for the specified range
+     * of text.  Returns null if no correction can be found.
+     * The View is used to get the current Locale and Resources.
+     */
+    public static String get(CharSequence src, final int start, final int end,
+                             View view) {
+        Resources res = view.getContext().getResources();
+        Locale locale = res.getConfiguration().locale;
+        AutoText instance;
+
+        synchronized (sLock) {
+            instance = sInstance;
+
+            if (!locale.equals(instance.mLocale)) {
+                instance = new AutoText(res);
+                sInstance = instance;
+            }
+        }
+
+        return instance.lookup(src, start, end);
+    }
+
+    private String lookup(CharSequence src, final int start, final int end) {
+        int here = mTrie[TRIE_ROOT];
+
+        for (int i = start; i < end; i++) {
+            char c = src.charAt(i);
+
+            for (; here != TRIE_NULL; here = mTrie[here + TRIE_NEXT]) {
+                if (c == mTrie[here + TRIE_C]) {
+                    if ((i == end - 1) 
+                            && (mTrie[here + TRIE_OFF] != TRIE_NULL)) {
+                        int off = mTrie[here + TRIE_OFF];
+                        int len = mText.charAt(off);
+
+                        return mText.substring(off + 1, off + 1 + len);
+                    }
+
+                    here = mTrie[here + TRIE_CHILD];
+                    break;
+                }
+            }
+
+            if (here == TRIE_NULL) {
+                return null;
+            }
+        }
+
+        return null;
+    }
+
+    private void init(Resources r) {
+        XmlResourceParser parser = r.getXml(com.android.internal.R.xml.autotext);
+
+        StringBuilder right = new StringBuilder(RIGHT);
+        mTrie = new char[DEFAULT];
+        mTrie[TRIE_ROOT] = TRIE_NULL;
+        mTrieUsed = TRIE_ROOT + 1;
+
+        try {
+            XmlUtils.beginDocument(parser, "words");
+            String odest = "";
+            char ooff = 0;
+
+            while (true) {
+                XmlUtils.nextElement(parser);
+
+                String element = parser.getName(); 
+                if (element == null || !(element.equals("word"))) {
+                    break;
+                }
+
+                String src = parser.getAttributeValue(null, "src");
+                if (parser.next() == XmlPullParser.TEXT) {
+                    String dest = parser.getText();
+                    char off;
+
+                    if (dest.equals(odest)) {
+                        off = ooff;
+                    } else {
+                        off = (char) right.length();
+                        right.append((char) dest.length());
+                        right.append(dest);
+                    }
+
+                    add(src, off);
+                }
+            }
+
+            // Don't let Resources cache a copy of all these strings.
+            r.flushLayoutCache();
+        } catch (XmlPullParserException e) {
+            throw new RuntimeException(e);
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        } finally {
+            parser.close();
+        }
+
+        mText = right.toString();
+    }
+
+    private void add(String src, char off) {
+        int slen = src.length();
+        int herep = TRIE_ROOT;
+
+        for (int i = 0; i < slen; i++) {
+            char c = src.charAt(i);
+            boolean found = false;
+
+            for (; mTrie[herep] != TRIE_NULL;
+                    herep = mTrie[herep] + TRIE_NEXT) {
+                if (c == mTrie[mTrie[herep] + TRIE_C]) {
+                    // There is a node for this letter, and this is the
+                    // end, so fill in the right hand side fields.
+
+                    if (i == slen - 1) {
+                        mTrie[mTrie[herep] + TRIE_OFF] = off;
+                        return;
+                    }
+
+                    // There is a node for this letter, and we need
+                    // to go deeper into it to fill in the rest.
+
+                    herep = mTrie[herep] + TRIE_CHILD;
+                    found = true;
+                    break;
+                }
+            }
+
+            if (!found) {
+                // No node for this letter yet.  Make one.
+
+                char node = newTrieNode();
+                mTrie[herep] = node;
+
+                mTrie[mTrie[herep] + TRIE_C] = c;
+                mTrie[mTrie[herep] + TRIE_OFF] = TRIE_NULL;
+                mTrie[mTrie[herep] + TRIE_NEXT] = TRIE_NULL;
+                mTrie[mTrie[herep] + TRIE_CHILD] = TRIE_NULL;
+
+                // If this is the end of the word, fill in the offset.
+
+                if (i == slen - 1) {
+                    mTrie[mTrie[herep] + TRIE_OFF] = off;
+                    return;
+                }
+
+                // Otherwise, step in deeper and go to the next letter.
+
+                herep = mTrie[herep] + TRIE_CHILD;
+            }
+        }
+    }
+
+    private char newTrieNode() {
+        if (mTrieUsed + TRIE_SIZEOF > mTrie.length) {
+            char[] copy = new char[mTrie.length + INCREMENT];
+            System.arraycopy(mTrie, 0, copy, 0, mTrie.length);
+            mTrie = copy;
+        }
+
+        char ret = mTrieUsed;
+        mTrieUsed += TRIE_SIZEOF;
+
+        return ret;
+    }
+}
diff --git a/core/java/android/text/BoringLayout.java b/core/java/android/text/BoringLayout.java
new file mode 100644
index 0000000..843754b
--- /dev/null
+++ b/core/java/android/text/BoringLayout.java
@@ -0,0 +1,388 @@
+/*
+ * 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 android.text;
+
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Path;
+import android.util.FloatMath;
+
+/**
+ * A BoringLayout is a very simple Layout implementation for text that
+ * fits on a single line and is all left-to-right characters.
+ * You will probably never want to make one of these yourself;
+ * if you do, be sure to call {@link #isBoring} first to make sure
+ * the text meets the criteria.
+ * <p>This class is used by widgets to control text layout. You should not need
+ * to use this class directly unless you are implementing your own widget
+ * or custom display object, in which case
+ * you are encouraged to use a Layout instead of calling
+ * {@link android.graphics.Canvas#drawText(java.lang.CharSequence, int, int, float, float, android.graphics.Paint)
+ *  Canvas.drawText()} directly.</p>
+ */
+public class BoringLayout extends Layout implements TextUtils.EllipsizeCallback {
+    public static BoringLayout make(CharSequence source,
+                        TextPaint paint, int outerwidth,
+                        Alignment align,
+                        float spacingmult, float spacingadd,
+                        BoringLayout.Metrics metrics, boolean includepad) {
+        return new BoringLayout(source, paint, outerwidth, align,
+                                spacingmult, spacingadd, metrics,
+                                includepad);
+    }
+
+    public static BoringLayout make(CharSequence source,
+                        TextPaint paint, int outerwidth,
+                        Alignment align,
+                        float spacingmult, float spacingadd,
+                        BoringLayout.Metrics metrics, boolean includepad,
+                        TextUtils.TruncateAt ellipsize, int ellipsizedWidth) {
+        return new BoringLayout(source, paint, outerwidth, align,
+                                spacingmult, spacingadd, metrics,
+                                includepad, ellipsize, ellipsizedWidth);
+    }
+
+    /**
+     * Returns a BoringLayout for the specified text, potentially reusing
+     * this one if it is already suitable.  The caller must make sure that
+     * no one is still using this Layout.
+     */
+    public BoringLayout replaceOrMake(CharSequence source, TextPaint paint,
+                                      int outerwidth, Alignment align,
+                                      float spacingmult, float spacingadd,
+                                      BoringLayout.Metrics metrics,
+                                      boolean includepad) {
+        replaceWith(source, paint, outerwidth, align, spacingmult,
+                    spacingadd);
+
+        mEllipsizedWidth = outerwidth;
+        mEllipsizedStart = 0;
+        mEllipsizedCount = 0;
+
+        init(source, paint, outerwidth, align, spacingmult, spacingadd,
+             metrics, includepad, true);
+        return this;
+    }
+
+    /**
+     * Returns a BoringLayout for the specified text, potentially reusing
+     * this one if it is already suitable.  The caller must make sure that
+     * no one is still using this Layout.
+     */
+    public BoringLayout replaceOrMake(CharSequence source, TextPaint paint,
+                                      int outerwidth, Alignment align,
+                                      float spacingmult, float spacingadd,
+                                      BoringLayout.Metrics metrics,
+                                      boolean includepad,
+                                      TextUtils.TruncateAt ellipsize,
+                                      int ellipsizedWidth) {
+        boolean trust;
+
+        if (ellipsize == null || ellipsize == TextUtils.TruncateAt.MARQUEE) {
+            replaceWith(source, paint, outerwidth, align, spacingmult,
+                        spacingadd);
+
+            mEllipsizedWidth = outerwidth;
+            mEllipsizedStart = 0;
+            mEllipsizedCount = 0;
+            trust = true;
+        } else {
+            replaceWith(TextUtils.ellipsize(source, paint, ellipsizedWidth,
+                                           ellipsize, true, this),
+                        paint, outerwidth, align, spacingmult,
+                        spacingadd);
+
+            mEllipsizedWidth = ellipsizedWidth;
+            trust = false;
+        }
+
+        init(getText(), paint, outerwidth, align, spacingmult, spacingadd,
+             metrics, includepad, trust);
+        return this;
+    }
+
+    public BoringLayout(CharSequence source,
+                        TextPaint paint, int outerwidth,
+                        Alignment align,
+                        float spacingmult, float spacingadd,
+                        BoringLayout.Metrics metrics, boolean includepad) {
+        super(source, paint, outerwidth, align, spacingmult, spacingadd);
+
+        mEllipsizedWidth = outerwidth;
+        mEllipsizedStart = 0;
+        mEllipsizedCount = 0;
+
+        init(source, paint, outerwidth, align, spacingmult, spacingadd,
+             metrics, includepad, true);
+    }
+
+    public BoringLayout(CharSequence source,
+                        TextPaint paint, int outerwidth,
+                        Alignment align,
+                        float spacingmult, float spacingadd,
+                        BoringLayout.Metrics metrics, boolean includepad,
+                        TextUtils.TruncateAt ellipsize, int ellipsizedWidth) {
+        /*
+         * It is silly to have to call super() and then replaceWith(),
+         * but we can't use "this" for the callback until the call to
+         * super() finishes.
+         */
+        super(source, paint, outerwidth, align, spacingmult, spacingadd);
+
+        boolean trust;
+
+        if (ellipsize == null || ellipsize == TextUtils.TruncateAt.MARQUEE) {
+            mEllipsizedWidth = outerwidth;
+            mEllipsizedStart = 0;
+            mEllipsizedCount = 0;
+            trust = true;
+        } else {
+            replaceWith(TextUtils.ellipsize(source, paint, ellipsizedWidth,
+                                           ellipsize, true, this),
+                        paint, outerwidth, align, spacingmult,
+                        spacingadd);
+
+
+            mEllipsizedWidth = ellipsizedWidth;
+            trust = false;
+        }
+
+        init(getText(), paint, outerwidth, align, spacingmult, spacingadd,
+             metrics, includepad, trust);
+    }
+
+    /* package */ void init(CharSequence source,
+                            TextPaint paint, int outerwidth,
+                            Alignment align,
+                            float spacingmult, float spacingadd,
+                            BoringLayout.Metrics metrics, boolean includepad,
+                            boolean trustWidth) {
+        int spacing;
+
+        if (source instanceof String && align == Layout.Alignment.ALIGN_NORMAL) {
+            mDirect = source.toString();
+        } else {
+            mDirect = null;
+        }
+
+        mPaint = paint;
+
+        if (includepad) {
+            spacing = metrics.bottom - metrics.top;
+        } else {
+            spacing = metrics.descent - metrics.ascent;
+        }
+
+        if (spacingmult != 1 || spacingadd != 0) {
+            spacing = (int)(spacing * spacingmult + spacingadd + 0.5f);
+        }
+
+        mBottom = spacing;
+
+        if (includepad) {
+            mDesc = spacing + metrics.top;
+        } else {
+            mDesc = spacing + metrics.ascent;
+        }
+
+        if (trustWidth) {
+            mMax = metrics.width;
+        } else {
+            /*
+             * If we have ellipsized, we have to actually calculate the
+             * width because the width that was passed in was for the
+             * full text, not the ellipsized form.
+             */
+            synchronized (sTemp) {
+                mMax = (int) (FloatMath.ceil(Styled.measureText(paint, sTemp,
+                                                source, 0, source.length(),
+                                                null)));
+            }
+        }
+
+        if (includepad) {
+            mTopPadding = metrics.top - metrics.ascent;
+            mBottomPadding = metrics.bottom - metrics.descent;
+        }
+    }
+
+    /**
+     * Returns null if not boring; the width, ascent, and descent if boring.
+     */
+    public static Metrics isBoring(CharSequence text,
+                                   TextPaint paint) {
+        return isBoring(text, paint, null);
+    }
+
+    /**
+     * Returns null if not boring; the width, ascent, and descent in the
+     * provided Metrics object (or a new one if the provided one was null)
+     * if boring.
+     */
+    public static Metrics isBoring(CharSequence text, TextPaint paint,
+                                   Metrics metrics) {
+        char[] temp = TextUtils.obtain(500);
+        int len = text.length();
+        boolean boring = true;
+
+        outer:
+        for (int i = 0; i < len; i += 500) {
+            int j = i + 500;
+
+            if (j > len)
+                j = len;
+
+            TextUtils.getChars(text, i, j, temp, 0);
+
+            int n = j - i;
+
+            for (int a = 0; a < n; a++) {
+                char c = temp[a];
+
+                if (c == '\n' || c == '\t' || c >= FIRST_RIGHT_TO_LEFT) {
+                    boring = false;
+                    break outer;
+                }
+            }
+        }
+
+        TextUtils.recycle(temp);
+
+        if (boring) {
+            Metrics fm = metrics;
+            if (fm == null) {
+                fm = new Metrics();
+            }
+    
+            int wid;
+
+            synchronized (sTemp) {
+                wid = (int) (FloatMath.ceil(Styled.measureText(paint, sTemp,
+                                                text, 0, text.length(), fm)));
+            }
+            fm.width = wid;
+            return fm;
+        } else {
+            return null;
+        }
+    }
+
+    @Override public int getHeight() {
+        return mBottom;
+    }
+
+    @Override public int getLineCount() {
+        return 1;
+    }
+
+    @Override public int getLineTop(int line) {
+        if (line == 0)
+            return 0;
+        else
+            return mBottom;
+    }
+
+    @Override public int getLineDescent(int line) {
+        return mDesc;
+    }
+
+    @Override public int getLineStart(int line) {
+        if (line == 0)
+            return 0;
+        else
+            return getText().length();
+    }
+
+    @Override public int getParagraphDirection(int line) {
+        return DIR_LEFT_TO_RIGHT;
+    }
+
+    @Override public boolean getLineContainsTab(int line) {
+        return false;
+    }
+
+    @Override public float getLineMax(int line) {
+        return mMax;
+    }
+
+    @Override public final Directions getLineDirections(int line) {
+        return Layout.DIRS_ALL_LEFT_TO_RIGHT;
+    }
+
+    public int getTopPadding() {
+        return mTopPadding;
+    }
+
+    public int getBottomPadding() {
+        return mBottomPadding;
+    }
+
+    @Override
+    public int getEllipsisCount(int line) {
+        return mEllipsizedCount;
+    }
+
+    @Override
+    public int getEllipsisStart(int line) {
+        return mEllipsizedStart;
+    }
+
+    @Override
+    public int getEllipsizedWidth() {
+        return mEllipsizedWidth;
+    }
+
+    // Override draw so it will be faster.
+    @Override
+    public void draw(Canvas c, Path highlight, Paint highlightpaint,
+                     int cursorOffset) {
+        if (mDirect != null && highlight == null) {
+            c.drawText(mDirect, 0, mBottom - mDesc, mPaint);
+        } else {
+            super.draw(c, highlight, highlightpaint, cursorOffset);
+        }
+    }
+
+    /**
+     * Callback for the ellipsizer to report what region it ellipsized.
+     */
+    public void ellipsized(int start, int end) {
+        mEllipsizedStart = start;
+        mEllipsizedCount = end - start;
+    }
+
+    private static final char FIRST_RIGHT_TO_LEFT = '\u0590';
+
+    private String mDirect;
+    private Paint mPaint;
+
+    /* package */ int mBottom, mDesc;   // for Direct
+    private int mTopPadding, mBottomPadding;
+    private float mMax;
+    private int mEllipsizedWidth, mEllipsizedStart, mEllipsizedCount;
+
+    private static final TextPaint sTemp =
+                                new TextPaint();
+
+    public static class Metrics extends Paint.FontMetricsInt {
+        public int width;
+        
+        @Override public String toString() {
+            return super.toString() + " width=" + width;
+        }
+    }
+}
diff --git a/core/java/android/text/ClipboardManager.java b/core/java/android/text/ClipboardManager.java
new file mode 100644
index 0000000..52039af
--- /dev/null
+++ b/core/java/android/text/ClipboardManager.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2007 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 android.text;
+
+import android.content.Context;
+import android.os.RemoteException;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.ServiceManager;
+import android.util.Log;
+
+/**
+ * Interface to the clipboard service, for placing and retrieving text in
+ * the global clipboard.
+ *
+ * <p>
+ * You do not instantiate this class directly; instead, retrieve it through
+ * {@link android.content.Context#getSystemService}.
+ *
+ * @see android.content.Context#getSystemService
+ */
+public class ClipboardManager {
+    private static IClipboard sService;
+
+    private Context mContext;
+
+    static private IClipboard getService() {
+        if (sService != null) {
+            return sService;
+        }
+        IBinder b = ServiceManager.getService("clipboard");
+        sService = IClipboard.Stub.asInterface(b);
+        return sService;
+    }
+
+    /** {@hide} */
+    public ClipboardManager(Context context, Handler handler) {
+        mContext = context;
+    }
+
+    /**
+     * Returns the text on the clipboard.  It will eventually be possible
+     * to store types other than text too, in which case this will return
+     * null if the type cannot be coerced to text.
+     */
+    public CharSequence getText() {
+        try {
+            return getService().getClipboardText();
+        } catch (RemoteException e) {
+            return null;
+        }
+    }
+
+    /**
+     * Sets the contents of the clipboard to the specified text.
+     */
+    public void setText(CharSequence text) {
+        try {
+            getService().setClipboardText(text);
+        } catch (RemoteException e) {
+        }
+    }
+
+    /**
+     * Returns true if the clipboard contains text; false otherwise.
+     */
+    public boolean hasText() {
+        try {
+            return getService().hasClipboardText();
+        } catch (RemoteException e) {
+            return false;
+        }
+    }
+}
diff --git a/core/java/android/text/DynamicLayout.java b/core/java/android/text/DynamicLayout.java
new file mode 100644
index 0000000..14e5655
--- /dev/null
+++ b/core/java/android/text/DynamicLayout.java
@@ -0,0 +1,503 @@
+/*
+ * 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 android.text;
+
+import android.graphics.Paint;
+import android.text.style.UpdateLayout;
+import android.text.style.WrapTogetherSpan;
+
+import java.lang.ref.WeakReference;
+
+/**
+ * DynamicLayout is a text layout that updates itself as the text is edited.
+ * <p>This is used by widgets to control text layout. You should not need
+ * to use this class directly unless you are implementing your own widget
+ * or custom display object, or need to call
+ * {@link android.graphics.Canvas#drawText(java.lang.CharSequence, int, int, float, float, android.graphics.Paint)
+ *  Canvas.drawText()} directly.</p>
+ */
+public class DynamicLayout
+extends Layout
+{
+    private static final int PRIORITY = 128;
+
+    /**
+     * Make a layout for the specified text that will be updated as
+     * the text is changed.
+     */
+    public DynamicLayout(CharSequence base,
+                         TextPaint paint,
+                         int width, Alignment align,
+                         float spacingmult, float spacingadd,
+                         boolean includepad) {
+        this(base, base, paint, width, align, spacingmult, spacingadd,
+             includepad);
+    }
+
+    /**
+     * Make a layout for the transformed text (password transformation
+     * being the primary example of a transformation)
+     * that will be updated as the base text is changed.
+     */
+    public DynamicLayout(CharSequence base, CharSequence display,
+                         TextPaint paint,
+                         int width, Alignment align,
+                         float spacingmult, float spacingadd,
+                         boolean includepad) {
+        this(base, display, paint, width, align, spacingmult, spacingadd,
+             includepad, null, 0);
+    }
+
+    /**
+     * Make a layout for the transformed text (password transformation
+     * being the primary example of a transformation)
+     * that will be updated as the base text is changed.
+     * If ellipsize is non-null, the Layout will ellipsize the text
+     * down to ellipsizedWidth.
+     */
+    public DynamicLayout(CharSequence base, CharSequence display,
+                         TextPaint paint,
+                         int width, Alignment align,
+                         float spacingmult, float spacingadd,
+                         boolean includepad,
+                         TextUtils.TruncateAt ellipsize, int ellipsizedWidth) {
+        super((ellipsize == null) 
+                ? display 
+                : (display instanceof Spanned) 
+                    ? new SpannedEllipsizer(display) 
+                    : new Ellipsizer(display),
+              paint, width, align, spacingmult, spacingadd);
+
+        mBase = base;
+        mDisplay = display;
+
+        if (ellipsize != null) {
+            mInts = new PackedIntVector(COLUMNS_ELLIPSIZE);
+            mEllipsizedWidth = ellipsizedWidth;
+            mEllipsizeAt = ellipsize;
+        } else {
+            mInts = new PackedIntVector(COLUMNS_NORMAL);
+            mEllipsizedWidth = width;
+            mEllipsizeAt = ellipsize;
+        }
+
+        mObjects = new PackedObjectVector<Directions>(1);
+
+        mIncludePad = includepad;
+
+        /*
+         * This is annoying, but we can't refer to the layout until
+         * superclass construction is finished, and the superclass
+         * constructor wants the reference to the display text.
+         *
+         * This will break if the superclass constructor ever actually
+         * cares about the content instead of just holding the reference.
+         */
+        if (ellipsize != null) {
+            Ellipsizer e = (Ellipsizer) getText();
+
+            e.mLayout = this;
+            e.mWidth = ellipsizedWidth;
+            e.mMethod = ellipsize;
+            mEllipsize = true;
+        }
+
+        // Initial state is a single line with 0 characters (0 to 0),
+        // with top at 0 and bottom at whatever is natural, and
+        // undefined ellipsis.
+
+        int[] start;
+
+        if (ellipsize != null) {
+            start = new int[COLUMNS_ELLIPSIZE];
+            start[ELLIPSIS_START] = ELLIPSIS_UNDEFINED;
+        } else {
+            start = new int[COLUMNS_NORMAL];
+        }
+
+        Directions[] dirs = new Directions[] { DIRS_ALL_LEFT_TO_RIGHT };
+
+        Paint.FontMetricsInt fm = paint.getFontMetricsInt();
+        int asc = fm.ascent;
+        int desc = fm.descent;
+
+        start[DIR] = DIR_LEFT_TO_RIGHT << DIR_SHIFT;
+        start[TOP] = 0;
+        start[DESCENT] = desc;
+        mInts.insertAt(0, start);
+
+        start[TOP] = desc - asc;
+        mInts.insertAt(1, start);
+
+        mObjects.insertAt(0, dirs);
+
+        // Update from 0 characters to whatever the real text is
+
+        reflow(base, 0, 0, base.length());
+
+        if (base instanceof Spannable) {
+            if (mWatcher == null)
+                mWatcher = new ChangeWatcher(this);
+
+            // Strip out any watchers for other DynamicLayouts.
+            Spannable sp = (Spannable) base;
+            ChangeWatcher[] spans = sp.getSpans(0, sp.length(), ChangeWatcher.class);
+            for (int i = 0; i < spans.length; i++)
+                sp.removeSpan(spans[i]);
+
+            sp.setSpan(mWatcher, 0, base.length(),
+                       Spannable.SPAN_INCLUSIVE_INCLUSIVE |
+                       (PRIORITY << Spannable.SPAN_PRIORITY_SHIFT));
+        }
+    }
+
+    private void reflow(CharSequence s, int where, int before, int after) {
+        if (s != mBase)
+            return;
+
+        CharSequence text = mDisplay;
+        int len = text.length();
+
+        // seek back to the start of the paragraph
+
+        int find = TextUtils.lastIndexOf(text, '\n', where - 1);
+        if (find < 0)
+            find = 0;
+        else
+            find = find + 1;
+
+        {
+            int diff = where - find;
+            before += diff;
+            after += diff;
+            where -= diff;
+        }
+
+        // seek forward to the end of the paragraph
+
+        int look = TextUtils.indexOf(text, '\n', where + after);
+        if (look < 0)
+            look = len;
+        else
+            look++; // we want the index after the \n
+
+        int change = look - (where + after);
+        before += change;
+        after += change;
+
+        // seek further out to cover anything that is forced to wrap together
+
+        if (text instanceof Spanned) {
+            Spanned sp = (Spanned) text;
+            boolean again;
+
+            do {
+                again = false;
+
+                Object[] force = sp.getSpans(where, where + after,
+                                             WrapTogetherSpan.class);
+
+                for (int i = 0; i < force.length; i++) {
+                    int st = sp.getSpanStart(force[i]);
+                    int en = sp.getSpanEnd(force[i]);
+
+                    if (st < where) {
+                        again = true;
+
+                        int diff = where - st;
+                        before += diff;
+                        after += diff;
+                        where -= diff;
+                    }
+
+                    if (en > where + after) {
+                        again = true;
+
+                        int diff = en - (where + after);
+                        before += diff;
+                        after += diff;
+                    }
+                }
+            } while (again);
+        }
+
+        // find affected region of old layout
+
+        int startline = getLineForOffset(where);
+        int startv = getLineTop(startline);
+
+        int endline = getLineForOffset(where + before);
+        if (where + after == len)
+            endline = getLineCount();
+        int endv = getLineTop(endline);
+        boolean islast = (endline == getLineCount());
+
+        // generate new layout for affected text
+
+        StaticLayout reflowed;
+
+        synchronized (sLock) {
+            reflowed = sStaticLayout;
+            sStaticLayout = null;
+        }
+
+        if (reflowed == null)
+            reflowed = new StaticLayout(true);
+
+        reflowed.generate(text, where, where + after,
+                                      getPaint(), getWidth(), getAlignment(),
+                                      getSpacingMultiplier(), getSpacingAdd(),
+                                      false, true, mEllipsize,
+                                      mEllipsizedWidth, mEllipsizeAt);
+        int n = reflowed.getLineCount();
+
+        // If the new layout has a blank line at the end, but it is not
+        // the very end of the buffer, then we already have a line that
+        // starts there, so disregard the blank line.
+
+        if (where + after != len &&
+            reflowed.getLineStart(n - 1) == where + after)
+            n--;
+
+        // remove affected lines from old layout
+
+        mInts.deleteAt(startline, endline - startline);
+        mObjects.deleteAt(startline, endline - startline);
+
+        // adjust offsets in layout for new height and offsets
+
+        int ht = reflowed.getLineTop(n);
+        int toppad = 0, botpad = 0;
+
+        if (mIncludePad && startline == 0) {
+            toppad = reflowed.getTopPadding();
+            mTopPadding = toppad;
+            ht -= toppad;
+        }
+        if (mIncludePad && islast) {
+            botpad = reflowed.getBottomPadding();
+            mBottomPadding = botpad;
+            ht += botpad;
+        }
+
+        mInts.adjustValuesBelow(startline, START, after - before);
+        mInts.adjustValuesBelow(startline, TOP, startv - endv + ht);
+
+        // insert new layout
+
+        int[] ints;
+
+        if (mEllipsize) {
+            ints = new int[COLUMNS_ELLIPSIZE];
+            ints[ELLIPSIS_START] = ELLIPSIS_UNDEFINED;
+        } else {
+            ints = new int[COLUMNS_NORMAL];
+        }
+
+        Directions[] objects = new Directions[1];
+
+
+        for (int i = 0; i < n; i++) {
+            ints[START] = reflowed.getLineStart(i) |
+                          (reflowed.getParagraphDirection(i) << DIR_SHIFT) |
+                          (reflowed.getLineContainsTab(i) ? TAB_MASK : 0);
+
+            int top = reflowed.getLineTop(i) + startv;
+            if (i > 0)
+                top -= toppad;
+            ints[TOP] = top;
+
+            int desc = reflowed.getLineDescent(i);
+            if (i == n - 1)
+                desc += botpad;
+
+            ints[DESCENT] = desc;
+            objects[0] = reflowed.getLineDirections(i);
+
+            if (mEllipsize) {
+                ints[ELLIPSIS_START] = reflowed.getEllipsisStart(i);
+                ints[ELLIPSIS_COUNT] = reflowed.getEllipsisCount(i);
+            }
+
+            mInts.insertAt(startline + i, ints);
+            mObjects.insertAt(startline + i, objects);
+        }
+
+        synchronized (sLock) {
+            sStaticLayout = reflowed;
+        }
+    }
+
+    private void dump(boolean show) {
+        int n = getLineCount();
+
+        for (int i = 0; i < n; i++) {
+            System.out.print("line " + i + ": " + getLineStart(i) + " to " + getLineEnd(i) + " ");
+
+            if (show) {
+                System.out.print(getText().subSequence(getLineStart(i),
+                                                       getLineEnd(i)));
+            }
+
+            System.out.println("");
+        }
+
+        System.out.println("");
+    }
+
+    public int getLineCount() {
+        return mInts.size() - 1;
+    }
+
+    public int getLineTop(int line) {
+        return mInts.getValue(line, TOP);
+    }
+
+    public int getLineDescent(int line) {
+        return mInts.getValue(line, DESCENT);
+    }
+
+    public int getLineStart(int line) {
+        return mInts.getValue(line, START) & START_MASK;
+    }
+
+    public boolean getLineContainsTab(int line) {
+        return (mInts.getValue(line, TAB) & TAB_MASK) != 0;
+    }
+
+    public int getParagraphDirection(int line) {
+        return mInts.getValue(line, DIR) >> DIR_SHIFT;
+    }
+
+    public final Directions getLineDirections(int line) {
+        return mObjects.getValue(line, 0);
+    }
+
+    public int getTopPadding() {
+        return mTopPadding;
+    }
+
+    public int getBottomPadding() {
+        return mBottomPadding;
+    }
+
+    @Override
+    public int getEllipsizedWidth() {
+        return mEllipsizedWidth;
+    }
+
+    private static class ChangeWatcher
+    implements TextWatcher, SpanWatcher
+    {
+        public ChangeWatcher(DynamicLayout layout) {
+            mLayout = new WeakReference(layout);
+        }
+
+        private void reflow(CharSequence s, int where, int before, int after) {
+            DynamicLayout ml = (DynamicLayout) mLayout.get();
+
+            if (ml != null)
+                ml.reflow(s, where, before, after);
+            else if (s instanceof Spannable)
+                ((Spannable) s).removeSpan(this);
+        }
+
+        public void beforeTextChanged(CharSequence s,
+                                      int where, int before, int after) {
+            ;
+        }
+
+        public void onTextChanged(CharSequence s,
+                                  int where, int before, int after) {
+            reflow(s, where, before, after);
+        }
+
+        public void afterTextChanged(Editable s) {
+            ;
+        }
+
+        public void onSpanAdded(Spannable s, Object o, int start, int end) {
+            if (o instanceof UpdateLayout)
+                reflow(s, start, end - start, end - start);
+        }
+
+        public void onSpanRemoved(Spannable s, Object o, int start, int end) {
+            if (o instanceof UpdateLayout)
+                reflow(s, start, end - start, end - start);
+        }
+
+        public void onSpanChanged(Spannable s, Object o, int start, int end,
+                                  int nstart, int nend) {
+            if (o instanceof UpdateLayout) {
+                reflow(s, start, end - start, end - start);
+                reflow(s, nstart, nend - nstart, nend - nstart);
+            }
+        }
+
+        private WeakReference mLayout;
+    }
+
+    public int getEllipsisStart(int line) {
+        if (mEllipsizeAt == null) {
+            return 0;
+        }
+
+        return mInts.getValue(line, ELLIPSIS_START);
+    }
+
+    public int getEllipsisCount(int line) {
+        if (mEllipsizeAt == null) {
+            return 0;
+        }
+
+        return mInts.getValue(line, ELLIPSIS_COUNT);
+    }
+
+    private CharSequence mBase;
+    private CharSequence mDisplay;
+    private ChangeWatcher mWatcher;
+    private boolean mIncludePad;
+    private boolean mEllipsize;
+    private int mEllipsizedWidth;
+    private TextUtils.TruncateAt mEllipsizeAt;
+
+    private PackedIntVector mInts;
+    private PackedObjectVector<Directions> mObjects;
+
+    private int mTopPadding, mBottomPadding;
+
+    private static StaticLayout sStaticLayout = new StaticLayout(true);
+    private static Object sLock = new Object();
+
+    private static final int START = 0;
+    private static final int DIR = START;
+    private static final int TAB = START;
+    private static final int TOP = 1;
+    private static final int DESCENT = 2;
+    private static final int COLUMNS_NORMAL = 3;
+
+    private static final int ELLIPSIS_START = 3;
+    private static final int ELLIPSIS_COUNT = 4;
+    private static final int COLUMNS_ELLIPSIZE = 5;
+
+    private static final int START_MASK = 0x1FFFFFFF;
+    private static final int DIR_MASK   = 0xC0000000;
+    private static final int DIR_SHIFT  = 30;
+    private static final int TAB_MASK   = 0x20000000;
+
+    private static final int ELLIPSIS_UNDEFINED = 0x80000000;
+}
diff --git a/core/java/android/text/Editable.java b/core/java/android/text/Editable.java
new file mode 100644
index 0000000..a284a00
--- /dev/null
+++ b/core/java/android/text/Editable.java
@@ -0,0 +1,142 @@
+/*
+ * 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 android.text;
+
+/**
+ * This is the interface for text whose content and markup
+ * can be changed (as opposed
+ * to immutable text like Strings).  If you make a {@link DynamicLayout}
+ * of an Editable, the layout will be reflowed as the text is changed.
+ */
+public interface Editable
+extends CharSequence, GetChars, Spannable, Appendable
+{
+    /**
+     * Replaces the specified range (<code>st&hellip;en</code>) of text in this
+     * Editable with a copy of the slice <code>start&hellip;end</code> from
+     * <code>source</code>.  The destination slice may be empty, in which case
+     * the operation is an insertion, or the source slice may be empty,
+     * in which case the operation is a deletion.
+     * <p>
+     * Before the change is committed, each filter that was set with
+     * {@link #setFilters} is given the opportunity to modify the
+     * <code>source</code> text.
+     * <p>
+     * If <code>source</code>
+     * is Spanned, the spans from it are preserved into the Editable.
+     * Existing spans within the Editable that entirely cover the replaced
+     * range are retained, but any that were strictly within the range
+     * that was replaced are removed.  As a special case, the cursor
+     * position is preserved even when the entire range where it is
+     * located is replaced.
+     * @return  a reference to this object.
+     */
+    public Editable replace(int st, int en, CharSequence source, int start, int end);
+
+    /**
+     * Convenience for replace(st, en, text, 0, text.length())
+     * @see #replace(int, int, CharSequence, int, int)
+     */
+    public Editable replace(int st, int en, CharSequence text);
+
+    /**
+     * Convenience for replace(where, where, text, start, end)
+     * @see #replace(int, int, CharSequence, int, int)
+     */
+    public Editable insert(int where, CharSequence text, int start, int end);
+
+    /**
+     * Convenience for replace(where, where, text, 0, text.length());
+     * @see #replace(int, int, CharSequence, int, int)
+     */
+    public Editable insert(int where, CharSequence text);
+
+    /**
+     * Convenience for replace(st, en, "", 0, 0)
+     * @see #replace(int, int, CharSequence, int, int)
+     */
+    public Editable delete(int st, int en);
+
+    /**
+     * Convenience for replace(length(), length(), text, 0, text.length())
+     * @see #replace(int, int, CharSequence, int, int)
+     */
+    public Editable append(CharSequence text);
+
+    /**
+     * Convenience for replace(length(), length(), text, start, end)
+     * @see #replace(int, int, CharSequence, int, int)
+     */
+    public Editable append(CharSequence text, int start, int end);
+
+    /**
+     * Convenience for append(String.valueOf(text)).
+     * @see #replace(int, int, CharSequence, int, int)
+     */
+    public Editable append(char text);
+
+    /**
+     * Convenience for replace(0, length(), "", 0, 0)
+     * @see #replace(int, int, CharSequence, int, int)
+     * Note that this clears the text, not the spans;
+     * use {@link #clearSpans} if you need that.
+     */
+    public void clear();
+
+    /**
+     * Removes all spans from the Editable, as if by calling
+     * {@link #removeSpan} on each of them.
+     */
+    public void clearSpans();
+
+    /**
+     * Sets the series of filters that will be called in succession
+     * whenever the text of this Editable is changed, each of which has
+     * the opportunity to limit or transform the text that is being inserted.
+     */
+    public void setFilters(InputFilter[] filters);
+
+    /**
+     * Returns the array of input filters that are currently applied
+     * to changes to this Editable.
+     */
+    public InputFilter[] getFilters();
+
+    /**
+     * Factory used by TextView to create new Editables.  You can subclass
+     * it to provide something other than SpannableStringBuilder.
+     */
+    public static class Factory {
+        private static Editable.Factory sInstance = new Editable.Factory();
+
+        /**
+         * Returns the standard Editable Factory.
+         */
+        public static Editable.Factory getInstance() {
+            return sInstance;
+        }
+
+        /**
+         * Returns a new SpannedStringBuilder from the specified
+         * CharSequence.  You can override this to provide
+         * a different kind of Spanned.
+         */
+        public Editable newEditable(CharSequence source) {
+            return new SpannableStringBuilder(source);
+        }
+    }
+}
diff --git a/core/java/android/text/GetChars.java b/core/java/android/text/GetChars.java
new file mode 100644
index 0000000..348a911
--- /dev/null
+++ b/core/java/android/text/GetChars.java
@@ -0,0 +1,33 @@
+/*
+ * 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 android.text;
+
+/**
+ * Please implement this interface if your CharSequence has a
+ * getChars() method like the one in String that is faster than
+ * calling charAt() multiple times.
+ */
+public interface GetChars
+extends CharSequence
+{
+    /**
+     * Exactly like String.getChars(): copy chars <code>start</code>
+     * through <code>end - 1</code> from this CharSequence into <code>dest</code>
+     * beginning at offset <code>destoff</code>.
+     */
+    public void getChars(int start, int end, char[] dest, int destoff);
+}
diff --git a/core/java/android/text/GraphicsOperations.java b/core/java/android/text/GraphicsOperations.java
new file mode 100644
index 0000000..c3bd0ae
--- /dev/null
+++ b/core/java/android/text/GraphicsOperations.java
@@ -0,0 +1,46 @@
+/*
+ * 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 android.text;
+
+import android.graphics.Canvas;
+import android.graphics.Paint;
+
+/**
+ * Please implement this interface if your CharSequence can do quick
+ * draw/measure/widths calculations from an internal array.
+ * {@hide}
+ */
+public interface GraphicsOperations
+extends CharSequence
+{
+    /**
+     * Just like {@link Canvas#drawText}.
+     */
+    void drawText(Canvas c, int start, int end,
+                         float x, float y, Paint p);
+
+    /**
+     * Just like {@link Paint#measureText}.
+     */
+    float measureText(int start, int end, Paint p);
+
+
+    /**
+     * Just like {@link Paint#getTextWidths}.
+     */
+    public int getTextWidths(int start, int end, float[] widths, Paint p);
+}
diff --git a/core/java/android/text/Html.java b/core/java/android/text/Html.java
new file mode 100644
index 0000000..8495714
--- /dev/null
+++ b/core/java/android/text/Html.java
@@ -0,0 +1,784 @@
+/*
+ * Copyright (C) 2007 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 android.text;
+
+import org.ccil.cowan.tagsoup.HTMLSchema;
+import org.ccil.cowan.tagsoup.Parser;
+import org.xml.sax.Attributes;
+import org.xml.sax.ContentHandler;
+import org.xml.sax.InputSource;
+import org.xml.sax.Locator;
+import org.xml.sax.SAXException;
+import org.xml.sax.XMLReader;
+
+import android.content.res.Resources;
+import android.graphics.Typeface;
+import android.graphics.drawable.Drawable;
+import android.text.style.CharacterStyle;
+import android.text.style.ForegroundColorSpan;
+import android.text.style.ImageSpan;
+import android.text.style.ParagraphStyle;
+import android.text.style.QuoteSpan;
+import android.text.style.RelativeSizeSpan;
+import android.text.style.StrikethroughSpan;
+import android.text.style.StyleSpan;
+import android.text.style.SubscriptSpan;
+import android.text.style.SuperscriptSpan;
+import android.text.style.TypefaceSpan;
+import android.text.style.URLSpan;
+import android.text.style.UnderlineSpan;
+import com.android.internal.util.XmlUtils;
+
+import java.io.IOException;
+import java.io.StringReader;
+import java.nio.CharBuffer;
+
+/**
+ * This class processes HTML strings into displayable styled text.
+ * Not all HTML tags are supported.
+ */
+public class Html {
+    /**
+     * Retrieves images for HTML &lt;img&gt; tags.
+     */
+    public static interface ImageGetter {
+        /**
+         * This methos is called when the HTML parser encounters an
+         * &lt;img&gt; tag.  The <code>source</code> argument is the
+         * string from the "src" attribute; the return value should be
+         * a Drawable representation of the image or <code>null</code>
+         * for a generic replacement image.  Make sure you call
+         * setBounds() on your Drawable if it doesn't already have
+         * its bounds set.
+         */
+        public Drawable getDrawable(String source);
+    }
+
+    /**
+     * Is notified when HTML tags are encountered that the parser does
+     * not know how to interpret.
+     */
+    public static interface TagHandler {
+        /**
+         * This method will be called whenn the HTML parser encounters
+         * a tag that it does not know how to interpret.
+         */
+        public void handleTag(boolean opening, String tag,
+                                 Editable output, XMLReader xmlReader);
+    }
+
+    private Html() { }
+
+    /**
+     * Returns displayable styled text from the provided HTML string.
+     * Any &lt;img&gt; tags in the HTML will display as a generic
+     * replacement image which your program can then go through and
+     * replace with real images.
+     *
+     * <p>This uses TagSoup to handle real HTML, including all of the brokenness found in the wild.
+     */
+    public static Spanned fromHtml(String source) {
+        return fromHtml(source, null, null);
+    }
+
+    /**
+     * Lazy initialization holder for HTML parser. This class will
+     * a) be preloaded by the zygote, or b) not loaded until absolutely
+     * necessary.
+     */
+    private static class HtmlParser {
+        private static final HTMLSchema schema = new HTMLSchema();
+    }
+
+    /**
+     * Returns displayable styled text from the provided HTML string.
+     * Any &lt;img&gt; tags in the HTML will use the specified ImageGetter
+     * to request a representation of the image (use null if you don't
+     * want this) and the specified TagHandler to handle unknown tags
+     * (specify null if you don't want this).
+     *
+     * <p>This uses TagSoup to handle real HTML, including all of the brokenness found in the wild.
+     */
+    public static Spanned fromHtml(String source, ImageGetter imageGetter,
+                                   TagHandler tagHandler) {
+        Parser parser = new Parser();
+        try {
+            parser.setProperty(Parser.schemaProperty, HtmlParser.schema);
+        } catch (org.xml.sax.SAXNotRecognizedException e) {
+            // Should not happen.
+            throw new RuntimeException(e);
+        } catch (org.xml.sax.SAXNotSupportedException e) {
+            // Should not happen.
+            throw new RuntimeException(e);
+        }
+
+        HtmlToSpannedConverter converter =
+                new HtmlToSpannedConverter(source, imageGetter, tagHandler,
+                        parser);
+        return converter.convert();
+    }
+
+    /**
+     * Returns an HTML representation of the provided Spanned text.
+     */
+    public static String toHtml(Spanned text) {
+        StringBuilder out = new StringBuilder();
+        int len = text.length();
+
+        int next;
+        for (int i = 0; i < text.length(); i = next) {
+            next = text.nextSpanTransition(i, len, QuoteSpan.class);
+            QuoteSpan[] quotes = text.getSpans(i, next, QuoteSpan.class);
+
+            for (QuoteSpan quote: quotes) {
+                out.append("<blockquote>");
+            }
+
+            withinBlockquote(out, text, i, next);
+
+            for (QuoteSpan quote: quotes) {
+                out.append("</blockquote>\n");
+            }
+        }
+
+        return out.toString();
+    }
+
+    private static void withinBlockquote(StringBuilder out, Spanned text,
+                                         int start, int end) {
+        out.append("<p>");
+
+        int next;
+        for (int i = start; i < end; i = next) {
+            next = TextUtils.indexOf(text, '\n', i, end);
+            if (next < 0) {
+                next = end;
+            }
+
+            int nl = 0;
+
+            while (next < end && text.charAt(next) == '\n') {
+                nl++;
+                next++;
+            }
+
+            withinParagraph(out, text, i, next - nl, nl, next == end);
+        }
+
+        out.append("</p>\n");
+    }
+
+    private static void withinParagraph(StringBuilder out, Spanned text,
+                                        int start, int end, int nl,
+                                        boolean last) {
+        int next;
+        for (int i = start; i < end; i = next) {
+            next = text.nextSpanTransition(i, end, CharacterStyle.class);
+            CharacterStyle[] style = text.getSpans(i, next,
+                                                   CharacterStyle.class);
+
+            for (int j = 0; j < style.length; j++) {
+                if (style[j] instanceof StyleSpan) {
+                    int s = ((StyleSpan) style[j]).getStyle();
+
+                    if ((s & Typeface.BOLD) != 0) {
+                        out.append("<b>");
+                    }
+                    if ((s & Typeface.ITALIC) != 0) {
+                        out.append("<i>");
+                    }
+                }
+                if (style[j] instanceof TypefaceSpan) {
+                    String s = ((TypefaceSpan) style[j]).getFamily();
+
+                    if (s.equals("monospace")) {
+                        out.append("<tt>");
+                    }
+                }
+                if (style[j] instanceof SuperscriptSpan) {
+                    out.append("<sup>");
+                }
+                if (style[j] instanceof SubscriptSpan) {
+                    out.append("<sub>");
+                }
+                if (style[j] instanceof UnderlineSpan) {
+                    out.append("<u>");
+                }
+                if (style[j] instanceof StrikethroughSpan) {
+                    out.append("<strike>");
+                }
+                if (style[j] instanceof URLSpan) {
+                    out.append("<a href=\"");
+                    out.append(((URLSpan) style[j]).getURL());
+                    out.append("\">");
+                }
+                if (style[j] instanceof ImageSpan) {
+                    out.append("<img src=\"");
+                    out.append(((ImageSpan) style[j]).getSource());
+                    out.append("\">");
+
+                    // Don't output the dummy character underlying the image.
+                    i = next;
+                }
+            }
+
+            withinStyle(out, text, i, next);
+
+            for (int j = style.length - 1; j >= 0; j--) {
+                if (style[j] instanceof URLSpan) {
+                    out.append("</a>");
+                }
+                if (style[j] instanceof StrikethroughSpan) {
+                    out.append("</strike>");
+                }
+                if (style[j] instanceof UnderlineSpan) {
+                    out.append("</u>");
+                }
+                if (style[j] instanceof SubscriptSpan) {
+                    out.append("</sub>");
+                }
+                if (style[j] instanceof SuperscriptSpan) {
+                    out.append("</sup>");
+                }
+                if (style[j] instanceof TypefaceSpan) {
+                    String s = ((TypefaceSpan) style[j]).getFamily();
+
+                    if (s.equals("monospace")) {
+                        out.append("</tt>");
+                    }
+                }
+                if (style[j] instanceof StyleSpan) {
+                    int s = ((StyleSpan) style[j]).getStyle();
+
+                    if ((s & Typeface.BOLD) != 0) {
+                        out.append("</b>");
+                    }
+                    if ((s & Typeface.ITALIC) != 0) {
+                        out.append("</i>");
+                    }
+                }
+            }
+        }
+
+        String p = last ? "" : "</p>\n<p>";
+
+        if (nl == 1) {
+            out.append("<br>\n");
+        } else if (nl == 2) {
+            out.append(p);
+        } else {
+            for (int i = 2; i < nl; i++) {
+                out.append("<br>");
+            }
+
+            out.append(p);
+        }
+    }
+
+    private static void withinStyle(StringBuilder out, Spanned text,
+                                    int start, int end) {
+        for (int i = start; i < end; i++) {
+            char c = text.charAt(i);
+
+            if (c == '<') {
+                out.append("&lt;");
+            } else if (c == '>') {
+                out.append("&gt;");
+            } else if (c == '&') {
+                out.append("&amp;");
+            } else if (c > 0x7E || c < ' ') {
+                out.append("&#" + ((int) c) + ";");
+            } else if (c == ' ') {
+                while (i + 1 < end && text.charAt(i + 1) == ' ') {
+                    out.append("&nbsp;");
+                    i++;
+                }
+
+                out.append(' ');
+            } else {
+                out.append(c);
+            }
+        }
+    }
+}
+
+class HtmlToSpannedConverter implements ContentHandler {
+
+    private static final float[] HEADER_SIZES = {
+        1.5f, 1.4f, 1.3f, 1.2f, 1.1f, 1f,
+    };
+
+    private String mSource;
+    private XMLReader mReader;
+    private SpannableStringBuilder mSpannableStringBuilder;
+    private Html.ImageGetter mImageGetter;
+    private Html.TagHandler mTagHandler;
+
+    public HtmlToSpannedConverter(
+            String source, Html.ImageGetter imageGetter, Html.TagHandler tagHandler,
+            Parser parser) {
+        mSource = source;
+        mSpannableStringBuilder = new SpannableStringBuilder();
+        mImageGetter = imageGetter;
+        mTagHandler = tagHandler;
+        mReader = parser;
+    }
+
+    public Spanned convert() {
+
+        mReader.setContentHandler(this);
+        try {
+            mReader.parse(new InputSource(new StringReader(mSource)));
+        } catch (IOException e) {
+            // We are reading from a string. There should not be IO problems.
+            throw new RuntimeException(e);
+        } catch (SAXException e) {
+            // TagSoup doesn't throw parse exceptions.
+            throw new RuntimeException(e);
+        }
+
+        // Fix flags and range for paragraph-type markup.
+        Object[] obj = mSpannableStringBuilder.getSpans(0, mSpannableStringBuilder.length(), ParagraphStyle.class);
+        for (int i = 0; i < obj.length; i++) {
+            int start = mSpannableStringBuilder.getSpanStart(obj[i]);
+            int end = mSpannableStringBuilder.getSpanEnd(obj[i]);
+
+            // If the last line of the range is blank, back off by one.
+            if (end - 2 >= 0) {
+                if (mSpannableStringBuilder.charAt(end - 1) == '\n' &&
+                    mSpannableStringBuilder.charAt(end - 2) == '\n') {
+                    end--;
+                }
+            }
+
+            if (end == start) {
+                mSpannableStringBuilder.removeSpan(obj[i]);
+            } else {
+                mSpannableStringBuilder.setSpan(obj[i], start, end, Spannable.SPAN_PARAGRAPH);
+            }
+        }
+
+        return mSpannableStringBuilder;
+    }
+
+    private void handleStartTag(String tag, Attributes attributes) {
+        if (tag.equalsIgnoreCase("br")) {
+            // We don't need to handle this. TagSoup will ensure that there's a </br> for each <br>
+            // so we can safely emite the linebreaks when we handle the close tag.
+        } else if (tag.equalsIgnoreCase("p")) {
+            handleP(mSpannableStringBuilder);
+        } else if (tag.equalsIgnoreCase("div")) {
+            handleP(mSpannableStringBuilder);
+        } else if (tag.equalsIgnoreCase("em")) {
+            start(mSpannableStringBuilder, new Bold());
+        } else if (tag.equalsIgnoreCase("b")) {
+            start(mSpannableStringBuilder, new Bold());
+        } else if (tag.equalsIgnoreCase("strong")) {
+            start(mSpannableStringBuilder, new Italic());
+        } else if (tag.equalsIgnoreCase("cite")) {
+            start(mSpannableStringBuilder, new Italic());
+        } else if (tag.equalsIgnoreCase("dfn")) {
+            start(mSpannableStringBuilder, new Italic());
+        } else if (tag.equalsIgnoreCase("i")) {
+            start(mSpannableStringBuilder, new Italic());
+        } else if (tag.equalsIgnoreCase("big")) {
+            start(mSpannableStringBuilder, new Big());
+        } else if (tag.equalsIgnoreCase("small")) {
+            start(mSpannableStringBuilder, new Small());
+        } else if (tag.equalsIgnoreCase("font")) {
+            startFont(mSpannableStringBuilder, attributes);
+        } else if (tag.equalsIgnoreCase("blockquote")) {
+            handleP(mSpannableStringBuilder);
+            start(mSpannableStringBuilder, new Blockquote());
+        } else if (tag.equalsIgnoreCase("tt")) {
+            start(mSpannableStringBuilder, new Monospace());
+        } else if (tag.equalsIgnoreCase("a")) {
+            startA(mSpannableStringBuilder, attributes);
+        } else if (tag.equalsIgnoreCase("u")) {
+            start(mSpannableStringBuilder, new Underline());
+        } else if (tag.equalsIgnoreCase("sup")) {
+            start(mSpannableStringBuilder, new Super());
+        } else if (tag.equalsIgnoreCase("sub")) {
+            start(mSpannableStringBuilder, new Sub());
+        } else if (tag.length() == 2 &&
+                   Character.toLowerCase(tag.charAt(0)) == 'h' &&
+                   tag.charAt(1) >= '1' && tag.charAt(1) <= '6') {
+            handleP(mSpannableStringBuilder);
+            start(mSpannableStringBuilder, new Header(tag.charAt(1) - '1'));
+        } else if (tag.equalsIgnoreCase("img")) {
+            startImg(mSpannableStringBuilder, attributes, mImageGetter);
+        } else if (mTagHandler != null) {
+            mTagHandler.handleTag(true, tag, mSpannableStringBuilder, mReader);
+        }
+    }
+
+    private void handleEndTag(String tag) {
+        if (tag.equalsIgnoreCase("br")) {
+            handleBr(mSpannableStringBuilder);
+        } else if (tag.equalsIgnoreCase("p")) {
+            handleP(mSpannableStringBuilder);
+        } else if (tag.equalsIgnoreCase("div")) {
+            handleP(mSpannableStringBuilder);
+        } else if (tag.equalsIgnoreCase("em")) {
+            end(mSpannableStringBuilder, Bold.class, new StyleSpan(Typeface.BOLD));
+        } else if (tag.equalsIgnoreCase("b")) {
+            end(mSpannableStringBuilder, Bold.class, new StyleSpan(Typeface.BOLD));
+        } else if (tag.equalsIgnoreCase("strong")) {
+            end(mSpannableStringBuilder, Italic.class, new StyleSpan(Typeface.ITALIC));
+        } else if (tag.equalsIgnoreCase("cite")) {
+            end(mSpannableStringBuilder, Italic.class, new StyleSpan(Typeface.ITALIC));
+        } else if (tag.equalsIgnoreCase("dfn")) {
+            end(mSpannableStringBuilder, Italic.class, new StyleSpan(Typeface.ITALIC));
+        } else if (tag.equalsIgnoreCase("i")) {
+            end(mSpannableStringBuilder, Italic.class, new StyleSpan(Typeface.ITALIC));
+        } else if (tag.equalsIgnoreCase("big")) {
+            end(mSpannableStringBuilder, Big.class, new RelativeSizeSpan(1.25f));
+        } else if (tag.equalsIgnoreCase("small")) {
+            end(mSpannableStringBuilder, Small.class, new RelativeSizeSpan(0.8f));
+        } else if (tag.equalsIgnoreCase("font")) {
+            endFont(mSpannableStringBuilder);
+        } else if (tag.equalsIgnoreCase("blockquote")) {
+            handleP(mSpannableStringBuilder);
+            end(mSpannableStringBuilder, Blockquote.class, new QuoteSpan());
+        } else if (tag.equalsIgnoreCase("tt")) {
+            end(mSpannableStringBuilder, Monospace.class,
+                    new TypefaceSpan("monospace"));
+        } else if (tag.equalsIgnoreCase("a")) {
+            endA(mSpannableStringBuilder);
+        } else if (tag.equalsIgnoreCase("u")) {
+            end(mSpannableStringBuilder, Underline.class, new UnderlineSpan());
+        } else if (tag.equalsIgnoreCase("sup")) {
+            end(mSpannableStringBuilder, Super.class, new SuperscriptSpan());
+        } else if (tag.equalsIgnoreCase("sub")) {
+            end(mSpannableStringBuilder, Sub.class, new SubscriptSpan());
+        } else if (tag.length() == 2 &&
+                Character.toLowerCase(tag.charAt(0)) == 'h' &&
+                tag.charAt(1) >= '1' && tag.charAt(1) <= '6') {
+            handleP(mSpannableStringBuilder);
+            endHeader(mSpannableStringBuilder);
+        } else if (mTagHandler != null) {
+            mTagHandler.handleTag(false, tag, mSpannableStringBuilder, mReader);
+        }
+    }
+
+    private static void handleP(SpannableStringBuilder text) {
+        int len = text.length();
+
+        if (len >= 1 && text.charAt(len - 1) == '\n') {
+            if (len >= 2 && text.charAt(len - 2) == '\n') {
+                return;
+            }
+
+            text.append("\n");
+            return;
+        }
+
+        if (len != 0) {
+            text.append("\n\n");
+        }
+    }
+
+    private static void handleBr(SpannableStringBuilder text) {
+        text.append("\n");
+    }
+
+    private static Object getLast(Spanned text, Class kind) {
+        /*
+         * This knows that the last returned object from getSpans()
+         * will be the most recently added.
+         */
+        Object[] objs = text.getSpans(0, text.length(), kind);
+
+        if (objs.length == 0) {
+            return null;
+        } else {
+            return objs[objs.length - 1];
+        }
+    }
+
+    private static void start(SpannableStringBuilder text, Object mark) {
+        int len = text.length();
+        text.setSpan(mark, len, len, Spannable.SPAN_MARK_MARK);
+    }
+
+    private static void end(SpannableStringBuilder text, Class kind,
+                            Object repl) {
+        int len = text.length();
+        Object obj = getLast(text, kind);
+        int where = text.getSpanStart(obj);
+
+        text.removeSpan(obj);
+
+        if (where != len) {
+            text.setSpan(repl, where, len, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+        }
+
+        return;
+    }
+
+    private static void startImg(SpannableStringBuilder text,
+                                 Attributes attributes, Html.ImageGetter img) {
+        String src = attributes.getValue("", "src");
+        Drawable d = null;
+
+        if (img != null) {
+            d = img.getDrawable(src);
+        }
+
+        if (d == null) {
+            d = Resources.getSystem().
+                    getDrawable(com.android.internal.R.drawable.unknown_image);
+            d.setBounds(0, 0, d.getIntrinsicWidth(), d.getIntrinsicHeight());
+        }
+
+        int len = text.length();
+        text.append("\uFFFC");
+
+        text.setSpan(new ImageSpan(d, src), len, text.length(),
+                     Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+    }
+
+    private static void startFont(SpannableStringBuilder text,
+                                  Attributes attributes) {
+        String color = attributes.getValue("", "color");
+        String face = attributes.getValue("", "face");
+
+        int len = text.length();
+        text.setSpan(new Font(color, face), len, len, Spannable.SPAN_MARK_MARK);
+    }
+
+    private static void endFont(SpannableStringBuilder text) {
+        int len = text.length();
+        Object obj = getLast(text, Font.class);
+        int where = text.getSpanStart(obj);
+
+        text.removeSpan(obj);
+
+        if (where != len) {
+            Font f = (Font) obj;
+
+            if (f.mColor != null) {
+                int c = -1;
+
+                if (f.mColor.equalsIgnoreCase("aqua")) {
+                    c = 0x00FFFF;
+                } else if (f.mColor.equalsIgnoreCase("black")) {
+                    c = 0x000000;
+                } else if (f.mColor.equalsIgnoreCase("blue")) {
+                    c = 0x0000FF;
+                } else if (f.mColor.equalsIgnoreCase("fuchsia")) {
+                    c = 0xFF00FF;
+                } else if (f.mColor.equalsIgnoreCase("green")) {
+                    c = 0x008000;
+                } else if (f.mColor.equalsIgnoreCase("grey")) {
+                    c = 0x808080;
+                } else if (f.mColor.equalsIgnoreCase("lime")) {
+                    c = 0x00FF00;
+                } else if (f.mColor.equalsIgnoreCase("maroon")) {
+                    c = 0x800000;
+                } else if (f.mColor.equalsIgnoreCase("navy")) {
+                    c = 0x000080;
+                } else if (f.mColor.equalsIgnoreCase("olive")) {
+                    c = 0x808000;
+                } else if (f.mColor.equalsIgnoreCase("purple")) {
+                    c = 0x800080;
+                } else if (f.mColor.equalsIgnoreCase("red")) {
+                    c = 0xFF0000;
+                } else if (f.mColor.equalsIgnoreCase("silver")) {
+                    c = 0xC0C0C0;
+                } else if (f.mColor.equalsIgnoreCase("teal")) {
+                    c = 0x008080;
+                } else if (f.mColor.equalsIgnoreCase("white")) {
+                    c = 0xFFFFFF;
+                } else if (f.mColor.equalsIgnoreCase("yellow")) {
+                    c = 0xFFFF00;
+                } else {
+                    try {
+                        c = XmlUtils.convertValueToInt(f.mColor, -1);
+                    } catch (NumberFormatException nfe) {
+                        // Can't understand the color, so just drop it.
+                    }
+                }
+
+                if (c != -1) {
+                    text.setSpan(new ForegroundColorSpan(c | 0xFF000000),
+                                 where, len,
+                                 Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+                }
+            }
+
+            if (f.mFace != null) {
+                text.setSpan(new TypefaceSpan(f.mFace), where, len,
+                             Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+            }
+        }
+    }
+
+    private static void startA(SpannableStringBuilder text, Attributes attributes) {
+        String href = attributes.getValue("", "href");
+
+        int len = text.length();
+        text.setSpan(new Href(href), len, len, Spannable.SPAN_MARK_MARK);
+    }
+
+    private static void endA(SpannableStringBuilder text) {
+        int len = text.length();
+        Object obj = getLast(text, Href.class);
+        int where = text.getSpanStart(obj);
+
+        text.removeSpan(obj);
+
+        if (where != len) {
+            Href h = (Href) obj;
+
+            if (h.mHref != null) {
+                text.setSpan(new URLSpan(h.mHref), where, len,
+                             Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+            }
+        }
+    }
+
+    private static void endHeader(SpannableStringBuilder text) {
+        int len = text.length();
+        Object obj = getLast(text, Header.class);
+
+        int where = text.getSpanStart(obj);
+
+        text.removeSpan(obj);
+
+        // Back off not to change only the text, not the blank line.
+        while (len > where && text.charAt(len - 1) == '\n') {
+            len--;
+        }
+
+        if (where != len) {
+            Header h = (Header) obj;
+
+            text.setSpan(new RelativeSizeSpan(HEADER_SIZES[h.mLevel]),
+                         where, len, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+            text.setSpan(new StyleSpan(Typeface.BOLD),
+                         where, len, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+        }
+    }
+
+    public void setDocumentLocator(Locator locator) {
+    }
+
+    public void startDocument() throws SAXException {
+    }
+
+    public void endDocument() throws SAXException {
+    }
+
+    public void startPrefixMapping(String prefix, String uri) throws SAXException {
+    }
+
+    public void endPrefixMapping(String prefix) throws SAXException {
+    }
+
+    public void startElement(String uri, String localName, String qName, Attributes attributes)
+            throws SAXException {
+        handleStartTag(localName, attributes);
+    }
+
+    public void endElement(String uri, String localName, String qName) throws SAXException {
+        handleEndTag(localName);
+    }
+
+    public void characters(char ch[], int start, int length) throws SAXException {
+        StringBuilder sb = new StringBuilder();
+
+        /*
+         * Ignore whitespace that immediately follows other whitespace;
+         * newlines count as spaces.
+         */
+
+        for (int i = 0; i < length; i++) {
+            char c = ch[i + start];
+
+            if (c == ' ' || c == '\n') {
+                char pred;
+                int len = sb.length();
+
+                if (len == 0) {
+                    len = mSpannableStringBuilder.length();
+
+                    if (len == 0) {
+                        pred = '\n';
+                    } else {
+                        pred = mSpannableStringBuilder.charAt(len - 1);
+                    }
+                } else {
+                    pred = sb.charAt(len - 1);
+                }
+
+                if (pred != ' ' && pred != '\n') {
+                    sb.append(' ');
+                }
+            } else {
+                sb.append(c);
+            }
+        }
+
+        mSpannableStringBuilder.append(sb);
+    }
+
+    public void ignorableWhitespace(char ch[], int start, int length) throws SAXException {
+    }
+
+    public void processingInstruction(String target, String data) throws SAXException {
+    }
+
+    public void skippedEntity(String name) throws SAXException {
+    }
+
+    private static class Bold { }
+    private static class Italic { }
+    private static class Underline { }
+    private static class Big { }
+    private static class Small { }
+    private static class Monospace { }
+    private static class Blockquote { }
+    private static class Super { }
+    private static class Sub { }
+
+    private static class Font {
+        public String mColor;
+        public String mFace;
+
+        public Font(String color, String face) {
+            mColor = color;
+            mFace = face;
+        }
+    }
+
+    private static class Href {
+        public String mHref;
+
+        public Href(String href) {
+            mHref = href;
+        }
+    }
+
+    private static class Header {
+        private int mLevel;
+
+        public Header(int level) {
+            mLevel = level;
+        }
+    }
+}
diff --git a/core/java/android/text/IClipboard.aidl b/core/java/android/text/IClipboard.aidl
new file mode 100644
index 0000000..4deb5c8
--- /dev/null
+++ b/core/java/android/text/IClipboard.aidl
@@ -0,0 +1,42 @@
+/**
+ * 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 android.text;
+
+/**
+ * Programming interface to the clipboard, which allows copying and pasting
+ * between applications.
+ * {@hide}
+ */
+interface IClipboard {
+    /**
+     * Returns the text on the clipboard.  It will eventually be possible
+     * to store types other than text too, in which case this will return
+     * null if the type cannot be coerced to text.
+     */
+    CharSequence getClipboardText();
+
+    /**
+     * Sets the contents of the clipboard to the specified text.
+     */
+    void setClipboardText(CharSequence text);
+
+    /**
+     * Returns true if the clipboard contains text; false otherwise.
+     */
+    boolean hasClipboardText();
+}
+
diff --git a/core/java/android/text/InputFilter.java b/core/java/android/text/InputFilter.java
new file mode 100644
index 0000000..2f55677
--- /dev/null
+++ b/core/java/android/text/InputFilter.java
@@ -0,0 +1,97 @@
+/*
+ * 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 android.text;
+
+/**
+ * InputFilters can be attached to {@link Editable}s to constrain the
+ * changes that can be made to them.
+ */
+public interface InputFilter
+{
+    /**
+     * This method is called when the buffer is going to replace the
+     * range <code>dstart &hellip; dend</code> of <code>dest</code>
+     * with the new text from the range <code>start &hellip; end</code>
+     * of <code>source</code>.  Return the CharSequence that you would
+     * like to have placed there instead, including an empty string
+     * if appropriate, or <code>null</code> to accept the original
+     * replacement.  Be careful to not to reject 0-length replacements,
+     * as this is what happens when you delete text.  Also beware that
+     * you should not attempt to make any changes to <code>dest</code>
+     * from this method; you may only examine it for context.
+     * 
+     * Note: If <var>source</var> is an instance of {@link Spanned} or
+     * {@link Spannable}, the span objects in the <var>source</var> should be 
+     * copied into the filtered result (i.e. the non-null return value). 
+     * {@link TextUtils#copySpansFrom} can be used for convenience.
+     */
+    public CharSequence filter(CharSequence source, int start, int end,
+                               Spanned dest, int dstart, int dend);
+
+    /**
+     * This filter will capitalize all the lower case letters that are added
+     * through edits.
+     */
+    public static class AllCaps implements InputFilter {
+        public CharSequence filter(CharSequence source, int start, int end,
+                                   Spanned dest, int dstart, int dend) {
+            for (int i = start; i < end; i++) {
+                if (Character.isLowerCase(source.charAt(i))) {
+                    char[] v = new char[end - start];
+                    TextUtils.getChars(source, start, end, v, 0);
+                    String s = new String(v).toUpperCase();
+
+                    if (source instanceof Spanned) {
+                        SpannableString sp = new SpannableString(s);
+                        TextUtils.copySpansFrom((Spanned) source,
+                                                start, end, null, sp, 0);
+                        return sp;
+                    } else {
+                        return s;
+                    }
+                }
+            }
+
+            return null; // keep original
+        }
+    }
+
+    /**
+     * This filter will constrain edits not to make the length of the text
+     * greater than the specified length.
+     */
+    public static class LengthFilter implements InputFilter {
+        public LengthFilter(int max) {
+            mMax = max;
+        }
+
+        public CharSequence filter(CharSequence source, int start, int end,
+                                   Spanned dest, int dstart, int dend) {
+            int keep = mMax - (dest.length() - (dend - dstart));
+
+            if (keep <= 0) {
+                return "";
+            } else if (keep >= end - start) {
+                return null; // keep original
+            } else {
+                return source.subSequence(start, start + keep);
+            }
+        }
+
+        private int mMax;
+    }
+}
diff --git a/core/java/android/text/InputType.java b/core/java/android/text/InputType.java
new file mode 100644
index 0000000..f643f92
--- /dev/null
+++ b/core/java/android/text/InputType.java
@@ -0,0 +1,249 @@
+/*
+ * 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 android.text;
+
+import android.text.TextUtils;
+
+/**
+ * Bit definitions for an integer defining the basic content type of text
+ * held in an {@link Editable} object.
+ */
+public interface InputType {
+    /**
+     * Mask of bits that determine the overall class
+     * of text being given.  Currently supported classes are:
+     * {@link #TYPE_CLASS_TEXT}, {@link #TYPE_CLASS_NUMBER},
+     * {@link #TYPE_CLASS_PHONE}, {@link #TYPE_CLASS_DATETIME}.
+     * If the class is not one you
+     * understand, assume {@link #TYPE_CLASS_TEXT} with NO variation
+     * or flags.
+     */
+    public static final int TYPE_MASK_CLASS = 0x0000000f;
+    
+    /**
+     * Mask of bits that determine the variation of
+     * the base content class.
+     */
+    public static final int TYPE_MASK_VARIATION = 0x00000ff0;
+    
+    /**
+     * Mask of bits that provide addition bit flags
+     * of options.
+     */
+    public static final int TYPE_MASK_FLAGS = 0x00fff000;
+    
+    /**
+     * Special content type for when no explicit type has been specified.
+     * This should be interpreted to mean that the target input connection
+     * is not rich, it can not process and show things like candidate text nor
+     * retrieve the current text, so the input method will need to run in a
+     * limited "generate key events" mode.
+     */
+    public static final int TYPE_NULL = 0x00000000;
+    
+    // ----------------------------------------------------------------------
+    // ----------------------------------------------------------------------
+    // ----------------------------------------------------------------------
+    
+    /**
+     * Class for normal text.  This class supports the following flags (only
+     * one of which should be set):
+     * {@link #TYPE_TEXT_FLAG_CAP_CHARACTERS},
+     * {@link #TYPE_TEXT_FLAG_CAP_WORDS}, and.
+     * {@link #TYPE_TEXT_FLAG_CAP_SENTENCES}.  It also supports the
+     * following variations:
+     * {@link #TYPE_TEXT_VARIATION_NORMAL}, and
+     * {@link #TYPE_TEXT_VARIATION_URI}.  If you do not recognize the
+     * variation, normal should be assumed.
+     */
+    public static final int TYPE_CLASS_TEXT = 0x00000001;
+    
+    /**
+     * Flag for {@link #TYPE_CLASS_TEXT}: capitalize all characters.  Overrides
+     * {@link #TYPE_TEXT_FLAG_CAP_WORDS} and
+     * {@link #TYPE_TEXT_FLAG_CAP_SENTENCES}.  This value is explicitly defined
+     * to be the same as {@link TextUtils#CAP_MODE_CHARACTERS}.
+     */
+    public static final int TYPE_TEXT_FLAG_CAP_CHARACTERS = 0x00001000;
+    
+    /**
+     * Flag for {@link #TYPE_CLASS_TEXT}: capitalize first character of
+     * all words.  Overrides {@link #TYPE_TEXT_FLAG_CAP_SENTENCES}.  This
+     * value is explicitly defined
+     * to be the same as {@link TextUtils#CAP_MODE_WORDS}.
+     */
+    public static final int TYPE_TEXT_FLAG_CAP_WORDS = 0x00002000;
+    
+    /**
+     * Flag for {@link #TYPE_CLASS_TEXT}: capitalize first character of
+     * each sentence.  This value is explicitly defined
+     * to be the same as {@link TextUtils#CAP_MODE_SENTENCES}.
+     */
+    public static final int TYPE_TEXT_FLAG_CAP_SENTENCES = 0x00004000;
+    
+    /**
+     * Flag for {@link #TYPE_CLASS_TEXT}: the user is entering free-form
+     * text that should have auto-correction applied to it.
+     */
+    public static final int TYPE_TEXT_FLAG_AUTO_CORRECT = 0x00008000;
+    
+    /**
+     * Flag for {@link #TYPE_CLASS_TEXT}: the text editor is performing
+     * auto-completion of the text being entered based on its own semantics,
+     * which it will present to the user as they type.  This generally means
+     * that the input method should not be showing candidates itself, but can
+     * expect for the editor to supply its own completions/candidates from
+     * {@link android.view.inputmethod.InputMethodSession#displayCompletions
+     * InputMethodSession.displayCompletions()} as a result of the editor calling
+     * {@link android.view.inputmethod.InputMethodManager#displayCompletions
+     * InputMethodManager.displayCompletions()}.
+     */
+    public static final int TYPE_TEXT_FLAG_AUTO_COMPLETE = 0x00010000;
+    
+    /**
+     * Flag for {@link #TYPE_CLASS_TEXT}: multiple lines of text can be
+     * entered into the field.  If this flag is not set, the text field 
+     * will be constrained to a single line.
+     */
+    public static final int TYPE_TEXT_FLAG_MULTI_LINE = 0x00020000;
+    
+    /**
+     * Flag for {@link #TYPE_CLASS_TEXT}: the regular text view associated
+     * with this should not be multi-line, but when a fullscreen input method
+     * is providing text it should use multiple lines if it can.
+     */
+    public static final int TYPE_TEXT_FLAG_IME_MULTI_LINE = 0x00040000;
+    
+    // ----------------------------------------------------------------------
+    
+    /**
+     * Default variation of {@link #TYPE_CLASS_TEXT}: plain old normal text.
+     */
+    public static final int TYPE_TEXT_VARIATION_NORMAL = 0x00000000;
+    
+    /**
+     * Variation of {@link #TYPE_CLASS_TEXT}: entering a URI.
+     */
+    public static final int TYPE_TEXT_VARIATION_URI = 0x00000010;
+    
+    /**
+     * Variation of {@link #TYPE_CLASS_TEXT}: entering an e-mail address.
+     */
+    public static final int TYPE_TEXT_VARIATION_EMAIL_ADDRESS = 0x00000020;
+    
+    /**
+     * Variation of {@link #TYPE_CLASS_TEXT}: entering the subject line of
+     * an e-mail.
+     */
+    public static final int TYPE_TEXT_VARIATION_EMAIL_SUBJECT = 0x00000030;
+    
+    /**
+     * Variation of {@link #TYPE_CLASS_TEXT}: entering a short, possibly informal
+     * message such as an instant message or a text message.
+     */
+    public static final int TYPE_TEXT_VARIATION_SHORT_MESSAGE = 0x00000040;
+    
+    /**
+     * Variation of {@link #TYPE_CLASS_TEXT}: entering the content of a long, possibly 
+     * formal message such as the body of an e-mail.
+     */
+    public static final int TYPE_TEXT_VARIATION_LONG_MESSAGE = 0x00000050;
+
+    /**
+     * Variation of {@link #TYPE_CLASS_TEXT}: entering the name of a person.
+     */
+    public static final int TYPE_TEXT_VARIATION_PERSON_NAME = 0x00000060;
+    
+    /**
+     * Variation of {@link #TYPE_CLASS_TEXT}: entering a postal mailing address.
+     */
+    public static final int TYPE_TEXT_VARIATION_POSTAL_ADDRESS = 0x00000070;
+    
+    /**
+     * Variation of {@link #TYPE_CLASS_TEXT}: entering a password.
+     */
+    public static final int TYPE_TEXT_VARIATION_PASSWORD = 0x00000080;
+    
+    /**
+     * Variation of {@link #TYPE_CLASS_TEXT}: entering text inside of a web form.
+     */
+    public static final int TYPE_TEXT_VARIATION_WEB_EDIT_TEXT = 0x00000090;
+    
+    // ----------------------------------------------------------------------
+    // ----------------------------------------------------------------------
+    // ----------------------------------------------------------------------
+    
+    /**
+     * Class for numeric text.  This class supports the following flag:
+     * {@link #TYPE_NUMBER_FLAG_SIGNED} and
+     * {@link #TYPE_NUMBER_FLAG_DECIMAL}.
+     */
+    public static final int TYPE_CLASS_NUMBER = 0x00000002;
+    
+    /**
+     * Flag of {@link #TYPE_CLASS_NUMBER}: the number is signed, allowing
+     * a positive or negative sign at the start.
+     */
+    public static final int TYPE_NUMBER_FLAG_SIGNED = 0x00001000;
+    
+    /**
+     * Flag of {@link #TYPE_CLASS_NUMBER}: the number is decimal, allowing
+     * a decimal point to provide fractional values.
+     */
+    public static final int TYPE_NUMBER_FLAG_DECIMAL = 0x00002000;
+    
+    // ----------------------------------------------------------------------
+    // ----------------------------------------------------------------------
+    // ----------------------------------------------------------------------
+    
+    /**
+     * Class for a phone number.  This class currently supports no variations
+     * or flags.
+     */
+    public static final int TYPE_CLASS_PHONE = 0x00000003;
+    
+    // ----------------------------------------------------------------------
+    // ----------------------------------------------------------------------
+    // ----------------------------------------------------------------------
+    
+    /**
+     * Class for dates and times.  It supports the
+     * following variations:
+     * {@link #TYPE_DATETIME_VARIATION_NORMAL}
+     * {@link #TYPE_DATETIME_VARIATION_DATE}, and
+     * {@link #TYPE_DATETIME_VARIATION_TIME},.
+     */
+    public static final int TYPE_CLASS_DATETIME = 0x00000004;
+    
+    /**
+     * Default variation of {@link #TYPE_CLASS_DATETIME}: allows entering
+     * both a date and time.
+     */
+    public static final int TYPE_DATETIME_VARIATION_NORMAL = 0x00000000;
+    
+    /**
+     * Default variation of {@link #TYPE_CLASS_DATETIME}: allows entering
+     * only a date.
+     */
+    public static final int TYPE_DATETIME_VARIATION_DATE = 0x00000010;
+    
+    /**
+     * Default variation of {@link #TYPE_CLASS_DATETIME}: allows entering
+     * only a time.
+     */
+    public static final int TYPE_DATETIME_VARIATION_TIME = 0x00000020;
+}
diff --git a/core/java/android/text/Layout.java b/core/java/android/text/Layout.java
new file mode 100644
index 0000000..95acf9d
--- /dev/null
+++ b/core/java/android/text/Layout.java
@@ -0,0 +1,1747 @@
+/*
+ * 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 android.text;
+
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.graphics.Path;
+import com.android.internal.util.ArrayUtils;
+import android.util.Config;
+
+import junit.framework.Assert;
+import android.text.style.*;
+import android.text.method.TextKeyListener;
+import android.view.KeyEvent;
+
+/**
+ * A base class that manages text layout in visual elements on 
+ * the screen. 
+ * <p>For text that will be edited, use a {@link DynamicLayout}, 
+ * which will be updated as the text changes.  
+ * For text that will not change, use a {@link StaticLayout}.
+ */
+public abstract class Layout {
+    /**
+     * Return how wide a layout would be necessary to display the
+     * specified text with one line per paragraph.
+     */
+    public static float getDesiredWidth(CharSequence source,
+                                        TextPaint paint) {
+        return getDesiredWidth(source, 0, source.length(), paint);
+    }
+    
+    /**
+     * Return how wide a layout would be necessary to display the
+     * specified text slice with one line per paragraph.
+     */
+    public static float getDesiredWidth(CharSequence source,
+                                        int start, int end,
+                                        TextPaint paint) {
+        float need = 0;
+        TextPaint workPaint = new TextPaint();
+
+        int next;
+        for (int i = start; i <= end; i = next) {
+            next = TextUtils.indexOf(source, '\n', i, end);
+
+            if (next < 0)
+                next = end;
+
+            float w = measureText(paint, workPaint,
+                                  source, i, next, null, true, null);
+
+            if (w > need)
+                need = w;
+
+            next++;
+        }
+
+        return need;
+    }
+
+    /**
+     * Subclasses of Layout use this constructor to set the display text,
+     * width, and other standard properties.
+     */
+    protected Layout(CharSequence text, TextPaint paint,
+                     int width, Alignment align,
+                     float spacingmult, float spacingadd) {
+        if (width < 0)
+            throw new IllegalArgumentException("Layout: " + width + " < 0");
+
+        mText = text;
+        mPaint = paint;
+        mWorkPaint = new TextPaint();
+        mWidth = width;
+        mAlignment = align;
+        mSpacingMult = spacingmult;
+        mSpacingAdd = spacingadd;
+        mSpannedText = text instanceof Spanned;
+    }
+
+    /**
+     * Replace constructor properties of this Layout with new ones.  Be careful.
+     */
+    /* package */ void replaceWith(CharSequence text, TextPaint paint,
+                              int width, Alignment align,
+                              float spacingmult, float spacingadd) {
+        if (width < 0) {
+            throw new IllegalArgumentException("Layout: " + width + " < 0");
+        }
+
+        mText = text;
+        mPaint = paint;
+        mWidth = width;
+        mAlignment = align;
+        mSpacingMult = spacingmult;
+        mSpacingAdd = spacingadd;
+        mSpannedText = text instanceof Spanned;
+    }
+
+    /**
+     * Draw this Layout on the specified Canvas.
+     */
+    public void draw(Canvas c) {
+        draw(c, null, null, 0);
+    }
+
+    /**
+     * Draw the specified rectangle from this Layout on the specified Canvas,
+     * with the specified path drawn between the background and the text.
+     */
+    public void draw(Canvas c, Path highlight, Paint highlightpaint,
+                     int cursorOffsetVertical) {
+        int dtop, dbottom;
+
+        synchronized (sTempRect) {
+            if (!c.getClipBounds(sTempRect)) {
+                return;
+            }
+
+            dtop = sTempRect.top;
+            dbottom = sTempRect.bottom;
+        }
+
+        TextPaint paint = mPaint;
+
+        int top = 0;
+        // getLineBottom(getLineCount() -1) just calls getLineTop(getLineCount) 
+        int bottom = getLineTop(getLineCount());
+
+
+        if (dtop > top) {
+            top = dtop;
+        }
+        if (dbottom < bottom) {
+            bottom = dbottom;
+        }
+        
+        int first = getLineForVertical(top); 
+        int last = getLineForVertical(bottom);
+        
+        int previousLineBottom = getLineTop(first);
+        int previousLineEnd = getLineStart(first);
+        
+        CharSequence buf = mText;
+
+        ParagraphStyle[] nospans = ArrayUtils.emptyArray(ParagraphStyle.class);
+        ParagraphStyle[] spans = nospans;
+        int spanend = 0;
+        int textLength = 0;
+        boolean spannedText = mSpannedText;
+
+        if (spannedText) {
+            spanend = 0;
+            textLength = buf.length();
+            for (int i = first; i <= last; i++) {
+                int start = previousLineEnd;
+                int end = getLineStart(i+1);
+                previousLineEnd = end;
+
+                int ltop = previousLineBottom;
+                int lbottom = getLineTop(i+1);
+                previousLineBottom = lbottom;
+                int lbaseline = lbottom - getLineDescent(i);
+
+                if (start >= spanend) {
+                   Spanned sp = (Spanned) buf;
+                   spanend = sp.nextSpanTransition(start, textLength,
+                                                   LineBackgroundSpan.class);
+                   spans = sp.getSpans(start, spanend,
+                                       LineBackgroundSpan.class);
+                }
+
+                for (int n = 0; n < spans.length; n++) {
+                    LineBackgroundSpan back = (LineBackgroundSpan) spans[n];
+
+                    back.drawBackground(c, paint, 0, mWidth,
+                                       ltop, lbaseline, lbottom,
+                                       buf, start, end,
+                                       i);
+                }
+            }
+            // reset to their original values
+            spanend = 0;
+            previousLineBottom = getLineTop(first);
+            previousLineEnd = getLineStart(first);
+            spans = nospans;
+        } 
+
+        // There can be a highlight even without spans if we are drawing
+        // a non-spanned transformation of a spanned editing buffer.
+        if (highlight != null) {
+            if (cursorOffsetVertical != 0) {
+                c.translate(0, cursorOffsetVertical);
+            }
+
+            c.drawPath(highlight, highlightpaint);
+
+            if (cursorOffsetVertical != 0) {
+                c.translate(0, -cursorOffsetVertical);
+            }
+        }
+
+        Alignment align = mAlignment;
+        
+        for (int i = first; i <= last; i++) {
+            int start = previousLineEnd;
+
+            previousLineEnd = getLineStart(i+1);
+            int end = getLineVisibleEnd(i, start, previousLineEnd);
+
+            int ltop = previousLineBottom;
+            int lbottom = getLineTop(i+1);
+            previousLineBottom = lbottom;
+            int lbaseline = lbottom - getLineDescent(i);
+
+            boolean par = false;
+            if (spannedText) { 
+                if (start == 0 || buf.charAt(start - 1) == '\n') {
+                    par = true;
+                }
+                if (start >= spanend) {
+
+                    Spanned sp = (Spanned) buf;
+
+                    spanend = sp.nextSpanTransition(start, textLength,
+                                                    ParagraphStyle.class);
+                    spans = sp.getSpans(start, spanend, ParagraphStyle.class);
+                    
+                    align = mAlignment;
+                    
+                    for (int n = spans.length-1; n >= 0; n--) {
+                        if (spans[n] instanceof AlignmentSpan) {
+                            align = ((AlignmentSpan) spans[n]).getAlignment();
+                            break;
+                        }
+                    }
+                }
+            }
+            
+            int dir = getParagraphDirection(i);
+            int left = 0;
+            int right = mWidth;
+
+            if (spannedText) {
+                final int length = spans.length;
+                for (int n = 0; n < length; n++) {
+                    if (spans[n] instanceof LeadingMarginSpan) {
+                        LeadingMarginSpan margin = (LeadingMarginSpan) spans[n];
+
+                        if (dir == DIR_RIGHT_TO_LEFT) {
+                            margin.drawLeadingMargin(c, paint, right, dir, ltop,
+                                                     lbaseline, lbottom, buf,
+                                                     start, end, par, this);
+                                
+                            right -= margin.getLeadingMargin(par);
+                        } else {
+                            margin.drawLeadingMargin(c, paint, left, dir, ltop,
+                                                     lbaseline, lbottom, buf,
+                                                     start, end, par, this);
+
+                            left += margin.getLeadingMargin(par);
+                        }
+                    }
+                }
+            }
+
+            int x;
+            if (align == Alignment.ALIGN_NORMAL) {
+                if (dir == DIR_LEFT_TO_RIGHT) {
+                    x = left;
+                } else {
+                    x = right;
+                }
+            } else {
+                int max = (int)getLineMax(i, spans, false);
+                if (align == Alignment.ALIGN_OPPOSITE) {
+                    if (dir == DIR_RIGHT_TO_LEFT) {
+                        x = left + max;
+                    } else {
+                        x = right - max;
+                    }
+                } else {
+                    // Alignment.ALIGN_CENTER
+                    max = max & ~1;
+                    int half = (right - left - max) >> 1;
+                    if (dir == DIR_RIGHT_TO_LEFT) {
+                        x = right - half;
+                    } else {
+                        x = left + half;
+                    }
+                }
+            }
+
+            Directions directions = getLineDirections(i);
+            boolean hasTab = getLineContainsTab(i);
+            if (directions == DIRS_ALL_LEFT_TO_RIGHT &&
+                    !spannedText && !hasTab) {
+                if (Config.DEBUG) {
+                    Assert.assertTrue(dir == DIR_LEFT_TO_RIGHT);
+                    Assert.assertNotNull(c);
+                }
+                c.drawText(buf, start, end, x, lbaseline, paint);
+            } else {
+                drawText(c, buf, start, end, dir, directions,
+                    x, ltop, lbaseline, lbottom, paint, mWorkPaint,
+                    hasTab, spans);
+            }
+        }
+    }
+
+    /**
+     * Return the text that is displayed by this Layout.
+     */
+    public final CharSequence getText() {
+        return mText;
+    }
+
+    /**
+     * Return the base Paint properties for this layout.
+     * Do NOT change the paint, which may result in funny
+     * drawing for this layout.
+     */
+    public final TextPaint getPaint() {
+        return mPaint;
+    }
+
+    /**
+     * Return the width of this layout.
+     */
+    public final int getWidth() {
+        return mWidth;
+    }
+
+    /**
+     * Return the width to which this Layout is ellipsizing, or
+     * {@link #getWidth} if it is not doing anything special.
+     */
+    public int getEllipsizedWidth() {
+        return mWidth;
+    }
+
+    /**
+     * Increase the width of this layout to the specified width.
+     * Be careful to use this only when you know it is appropriate --
+     * it does not cause the text to reflow to use the full new width.
+     */
+    public final void increaseWidthTo(int wid) {
+        if (wid < mWidth) {
+            throw new RuntimeException("attempted to reduce Layout width");
+        }
+
+        mWidth = wid;
+    }
+    
+    /**
+     * Return the total height of this layout.
+     */
+    public int getHeight() {
+        return getLineTop(getLineCount());  // same as getLineBottom(getLineCount() - 1);
+    }
+
+    /**
+     * Return the base alignment of this layout.
+     */
+    public final Alignment getAlignment() {
+        return mAlignment;
+    }
+
+    /**
+     * Return what the text height is multiplied by to get the line height.
+     */
+    public final float getSpacingMultiplier() {
+        return mSpacingMult;
+    }
+
+    /**
+     * Return the number of units of leading that are added to each line.
+     */
+    public final float getSpacingAdd() {
+        return mSpacingAdd;
+    }
+
+    /**
+     * Return the number of lines of text in this layout.
+     */
+    public abstract int getLineCount();
+    
+    /**
+     * Return the baseline for the specified line (0&hellip;getLineCount() - 1)
+     * If bounds is not null, return the top, left, right, bottom extents
+     * of the specified line in it.
+     * @param line which line to examine (0..getLineCount() - 1)
+     * @param bounds Optional. If not null, it returns the extent of the line
+     * @return the Y-coordinate of the baseline
+     */
+    public int getLineBounds(int line, Rect bounds) {
+        if (bounds != null) {
+            bounds.left = 0;     // ???
+            bounds.top = getLineTop(line);
+            bounds.right = mWidth;   // ???
+            bounds.bottom = getLineBottom(line);
+        }
+        return getLineBaseline(line);
+    }
+
+    /**
+     * Return the vertical position of the top of the specified line.
+     * If the specified line is one beyond the last line, returns the
+     * bottom of the last line.
+     */
+    public abstract int getLineTop(int line);
+
+    /**
+     * Return the descent of the specified line.
+     */
+    public abstract int getLineDescent(int line);
+
+    /**
+     * Return the text offset of the beginning of the specified line.
+     * If the specified line is one beyond the last line, returns the
+     * end of the last line.
+     */
+    public abstract int getLineStart(int line);
+
+    /**
+     * Returns the primary directionality of the paragraph containing
+     * the specified line.
+     */
+    public abstract int getParagraphDirection(int line);
+
+    /**
+     * Returns whether the specified line contains one or more tabs.
+     */
+    public abstract boolean getLineContainsTab(int line);
+
+    /**
+     * Returns an array of directionalities for the specified line.
+     * The array alternates counts of characters in left-to-right
+     * and right-to-left segments of the line.
+     */
+    public abstract Directions getLineDirections(int line);
+
+    /**
+     * Returns the (negative) number of extra pixels of ascent padding in the
+     * top line of the Layout.
+     */
+    public abstract int getTopPadding();
+
+    /**
+     * Returns the number of extra pixels of descent padding in the
+     * bottom line of the Layout.
+     */
+    public abstract int getBottomPadding();
+
+    /**
+     * Get the primary horizontal position for the specified text offset.
+     * This is the location where a new character would be inserted in
+     * the paragraph's primary direction.
+     */
+    public float getPrimaryHorizontal(int offset) {
+        return getHorizontal(offset, false, true);
+    }
+
+    /**
+     * Get the secondary horizontal position for the specified text offset.
+     * This is the location where a new character would be inserted in
+     * the direction other than the paragraph's primary direction.
+     */
+    public float getSecondaryHorizontal(int offset) {
+        return getHorizontal(offset, true, true);
+    }
+
+    private float getHorizontal(int offset, boolean trailing, boolean alt) {
+        int line = getLineForOffset(offset);
+
+        return getHorizontal(offset, trailing, alt, line);
+    }
+
+    private float getHorizontal(int offset, boolean trailing, boolean alt,
+                                int line) {
+        int start = getLineStart(line);
+        int end = getLineVisibleEnd(line);
+        int dir = getParagraphDirection(line);
+        boolean tab = getLineContainsTab(line);
+        Directions directions = getLineDirections(line);
+
+        TabStopSpan[] tabs = null;
+        if (tab && mText instanceof Spanned) {
+            tabs = ((Spanned) mText).getSpans(start, end, TabStopSpan.class);
+        }
+
+        float wid = measureText(mPaint, mWorkPaint, mText, start, offset, end,
+                                dir, directions, trailing, alt, tab, tabs);
+
+        if (offset > end) {
+            if (dir == DIR_RIGHT_TO_LEFT)
+                wid -= measureText(mPaint, mWorkPaint,
+                                   mText, end, offset, null, tab, tabs);
+            else
+                wid += measureText(mPaint, mWorkPaint,
+                                   mText, end, offset, null, tab, tabs);
+        }
+
+        Alignment align = getParagraphAlignment(line);
+        int left = getParagraphLeft(line);
+        int right = getParagraphRight(line);
+
+        if (align == Alignment.ALIGN_NORMAL) {
+            if (dir == DIR_RIGHT_TO_LEFT)
+                return right + wid;
+            else
+                return left + wid;
+        }
+
+        float max = getLineMax(line);
+
+        if (align == Alignment.ALIGN_OPPOSITE) {
+            if (dir == DIR_RIGHT_TO_LEFT)
+                return left + max + wid;
+            else
+                return right - max + wid;
+        } else { /* align == Alignment.ALIGN_CENTER */
+            int imax = ((int) max) & ~1;
+
+            if (dir == DIR_RIGHT_TO_LEFT)
+                return right - (((right - left) - imax) / 2) + wid;
+            else
+                return left + ((right - left) - imax) / 2 + wid;
+        }
+    }
+
+    /**
+     * Get the leftmost position that should be exposed for horizontal
+     * scrolling on the specified line.
+     */
+    public float getLineLeft(int line) {
+        int dir = getParagraphDirection(line);
+        Alignment align = getParagraphAlignment(line);
+
+        if (align == Alignment.ALIGN_NORMAL) {
+            if (dir == DIR_RIGHT_TO_LEFT)
+                return getParagraphRight(line) - getLineMax(line);
+            else
+                return 0;
+        } else if (align == Alignment.ALIGN_OPPOSITE) {
+            if (dir == DIR_RIGHT_TO_LEFT)
+                return 0;
+            else
+                return mWidth - getLineMax(line);
+        } else { /* align == Alignment.ALIGN_CENTER */
+            int left = getParagraphLeft(line);
+            int right = getParagraphRight(line);
+            int max = ((int) getLineMax(line)) & ~1;
+
+            return left + ((right - left) - max) / 2;
+        }
+    }
+
+    /**
+     * Get the rightmost position that should be exposed for horizontal
+     * scrolling on the specified line.
+     */
+    public float getLineRight(int line) {
+        int dir = getParagraphDirection(line);
+        Alignment align = getParagraphAlignment(line);
+
+        if (align == Alignment.ALIGN_NORMAL) {
+            if (dir == DIR_RIGHT_TO_LEFT)
+                return mWidth;
+            else
+                return getParagraphLeft(line) + getLineMax(line);
+        } else if (align == Alignment.ALIGN_OPPOSITE) {
+            if (dir == DIR_RIGHT_TO_LEFT)
+                return getLineMax(line);
+            else
+                return mWidth;
+        } else { /* align == Alignment.ALIGN_CENTER */
+            int left = getParagraphLeft(line);
+            int right = getParagraphRight(line);
+            int max = ((int) getLineMax(line)) & ~1;
+
+            return right - ((right - left) - max) / 2;
+        }
+    }
+
+    /**
+     * Gets the horizontal extent of the specified line, excluding
+     * trailing whitespace.
+     */
+    public float getLineMax(int line) {
+        return getLineMax(line, null, false);
+    }
+
+    /**
+     * Gets the horizontal extent of the specified line, including
+     * trailing whitespace.
+     */
+    public float getLineWidth(int line) {
+        return getLineMax(line, null, true);
+    }
+
+    private float getLineMax(int line, Object[] tabs, boolean full) {
+        int start = getLineStart(line);
+        int end;
+
+        if (full) {
+            end = getLineEnd(line);
+        } else {
+            end = getLineVisibleEnd(line);
+        } 
+        boolean tab = getLineContainsTab(line);
+
+        if (tabs == null && tab && mText instanceof Spanned) {
+            tabs = ((Spanned) mText).getSpans(start, end, TabStopSpan.class);
+        }
+
+        return measureText(mPaint, mWorkPaint,
+                           mText, start, end, null, tab, tabs);
+    }
+
+    /**
+     * Get the line number corresponding to the specified vertical position.
+     * If you ask for a position above 0, you get 0; if you ask for a position
+     * below the bottom of the text, you get the last line.
+     */
+    // FIXME: It may be faster to do a linear search for layouts without many lines.
+    public int getLineForVertical(int vertical) {
+        int high = getLineCount(), low = -1, guess;
+
+        while (high - low > 1) {
+            guess = (high + low) / 2;
+
+            if (getLineTop(guess) > vertical)
+                high = guess;
+            else
+                low = guess;
+        }
+
+        if (low < 0)
+            return 0;
+        else
+            return low;
+    }
+
+    /**
+     * Get the line number on which the specified text offset appears.
+     * If you ask for a position before 0, you get 0; if you ask for a position
+     * beyond the end of the text, you get the last line.
+     */
+    public int getLineForOffset(int offset) {
+        int high = getLineCount(), low = -1, guess;
+
+        while (high - low > 1) {
+            guess = (high + low) / 2;
+
+            if (getLineStart(guess) > offset)
+                high = guess;
+            else
+                low = guess;
+        }
+
+        if (low < 0)
+            return 0;
+        else
+            return low;
+    }
+
+    /**
+     * Get the character offset on the specfied line whose position is
+     * closest to the specified horizontal position.
+     */
+    public int getOffsetForHorizontal(int line, float horiz) {
+        int max = getLineEnd(line) - 1;
+        int min = getLineStart(line);
+        Directions dirs = getLineDirections(line);
+
+        if (line == getLineCount() - 1)
+            max++;
+
+        int best = min;
+        float bestdist = Math.abs(getPrimaryHorizontal(best) - horiz);
+
+        int here = min;
+        for (int i = 0; i < dirs.mDirections.length; i++) {
+            int there = here + dirs.mDirections[i];
+            int swap = ((i & 1) == 0) ? 1 : -1;
+
+            if (there > max)
+                there = max;
+
+            int high = there - 1 + 1, low = here + 1 - 1, guess;
+
+            while (high - low > 1) {
+                guess = (high + low) / 2;
+                int adguess = getOffsetAtStartOf(guess);
+
+                if (getPrimaryHorizontal(adguess) * swap >= horiz * swap)
+                    high = guess;
+                else
+                    low = guess;
+            }
+
+            if (low < here + 1)
+                low = here + 1;
+
+            if (low < there) {
+                low = getOffsetAtStartOf(low);
+
+                float dist = Math.abs(getPrimaryHorizontal(low) - horiz);
+
+                int aft = TextUtils.getOffsetAfter(mText, low);
+                if (aft < there) {
+                    float other = Math.abs(getPrimaryHorizontal(aft) - horiz);
+
+                    if (other < dist) {
+                        dist = other;
+                        low = aft;
+                    }
+                }
+
+                if (dist < bestdist) {
+                    bestdist = dist;
+                    best = low;   
+                }
+            }
+
+            float dist = Math.abs(getPrimaryHorizontal(here) - horiz);
+
+            if (dist < bestdist) {
+                bestdist = dist;
+                best = here;
+            }
+
+            here = there;
+        }
+
+        float dist = Math.abs(getPrimaryHorizontal(max) - horiz);
+
+        if (dist < bestdist) {
+            bestdist = dist;
+            best = max;
+        }
+
+        return best;
+    }
+
+    /**
+     * Return the text offset after the last character on the specified line.
+     */
+    public final int getLineEnd(int line) {
+        return getLineStart(line + 1);
+    }
+
+    /** 
+     * Return the text offset after the last visible character (so whitespace
+     * is not counted) on the specified line.
+     */
+    public int getLineVisibleEnd(int line) {
+        return getLineVisibleEnd(line, getLineStart(line), getLineStart(line+1));
+    }
+    
+    private int getLineVisibleEnd(int line, int start, int end) {
+        if (Config.DEBUG) {
+            Assert.assertTrue(getLineStart(line) == start && getLineStart(line+1) == end);
+        }
+
+        CharSequence text = mText;
+        char ch;
+        if (line == getLineCount() - 1) {
+            return end;
+        }
+
+        for (; end > start; end--) {
+            ch = text.charAt(end - 1);
+
+            if (ch == '\n') {
+                return end - 1;
+            }
+
+            if (ch != ' ' && ch != '\t') {
+                break;
+            }
+
+        }
+
+        return end;
+    }
+
+    /**
+     * Return the vertical position of the bottom of the specified line.
+     */
+    public final int getLineBottom(int line) {
+        return getLineTop(line + 1);
+    }
+
+    /**
+     * Return the vertical position of the baseline of the specified line.
+     */
+    public final int getLineBaseline(int line) {
+        // getLineTop(line+1) == getLineTop(line)
+        return getLineTop(line+1) - getLineDescent(line);
+    }
+
+    /**
+     * Get the ascent of the text on the specified line.
+     * The return value is negative to match the Paint.ascent() convention.
+     */
+    public final int getLineAscent(int line) {
+        // getLineTop(line+1) - getLineDescent(line) == getLineBaseLine(line)
+        return getLineTop(line) - (getLineTop(line+1) - getLineDescent(line));
+    }
+
+    /**
+     * Return the text offset that would be reached by moving left
+     * (possibly onto another line) from the specified offset.
+     */
+    public int getOffsetToLeftOf(int offset) {
+        int line = getLineForOffset(offset);
+        int start = getLineStart(line);
+        int end = getLineEnd(line);
+        Directions dirs = getLineDirections(line);
+
+        if (line != getLineCount() - 1)
+            end--;
+
+        float horiz = getPrimaryHorizontal(offset);
+
+        int best = offset;
+        float besth = Integer.MIN_VALUE;
+        int candidate;
+
+        candidate = TextUtils.getOffsetBefore(mText, offset);
+        if (candidate >= start && candidate <= end) {
+            float h = getPrimaryHorizontal(candidate);
+
+            if (h < horiz && h > besth) {
+                best = candidate;
+                besth = h;
+            }
+        }
+
+        candidate = TextUtils.getOffsetAfter(mText, offset);
+        if (candidate >= start && candidate <= end) {
+            float h = getPrimaryHorizontal(candidate);
+
+            if (h < horiz && h > besth) {
+                best = candidate;
+                besth = h;
+            }
+        }
+
+        int here = start;
+        for (int i = 0; i < dirs.mDirections.length; i++) {
+            int there = here + dirs.mDirections[i];
+            if (there > end)
+                there = end;
+
+            float h = getPrimaryHorizontal(here);
+
+            if (h < horiz && h > besth) {
+                best = here;
+                besth = h;
+            }
+
+            candidate = TextUtils.getOffsetAfter(mText, here);
+            if (candidate >= start && candidate <= end) {
+                h = getPrimaryHorizontal(candidate);
+
+                if (h < horiz && h > besth) {
+                    best = candidate;
+                    besth = h;
+                }
+            }
+
+            candidate = TextUtils.getOffsetBefore(mText, there);
+            if (candidate >= start && candidate <= end) {
+                h = getPrimaryHorizontal(candidate);
+
+                if (h < horiz && h > besth) {
+                    best = candidate;
+                    besth = h;
+                }
+            }
+
+            here = there;
+        }
+
+        float h = getPrimaryHorizontal(end);
+
+        if (h < horiz && h > besth) {
+            best = end;
+            besth = h;
+        }
+
+        if (best != offset)
+            return best;
+
+        int dir = getParagraphDirection(line);
+
+        if (dir > 0) {
+            if (line == 0)
+                return best;
+            else
+                return getOffsetForHorizontal(line - 1, 10000);
+        } else {
+            if (line == getLineCount() - 1)
+                return best;
+            else
+                return getOffsetForHorizontal(line + 1, 10000);
+        }
+    }
+
+    /**
+     * Return the text offset that would be reached by moving right
+     * (possibly onto another line) from the specified offset.
+     */
+    public int getOffsetToRightOf(int offset) {
+        int line = getLineForOffset(offset);
+        int start = getLineStart(line);
+        int end = getLineEnd(line);
+        Directions dirs = getLineDirections(line);
+
+        if (line != getLineCount() - 1)
+            end--;
+
+        float horiz = getPrimaryHorizontal(offset);
+
+        int best = offset;
+        float besth = Integer.MAX_VALUE;
+        int candidate;
+
+        candidate = TextUtils.getOffsetBefore(mText, offset);
+        if (candidate >= start && candidate <= end) {
+            float h = getPrimaryHorizontal(candidate);
+
+            if (h > horiz && h < besth) {
+                best = candidate;
+                besth = h;
+            }
+        }
+
+        candidate = TextUtils.getOffsetAfter(mText, offset);
+        if (candidate >= start && candidate <= end) {
+            float h = getPrimaryHorizontal(candidate);
+
+            if (h > horiz && h < besth) {
+                best = candidate;
+                besth = h;
+            }
+        }
+
+        int here = start;
+        for (int i = 0; i < dirs.mDirections.length; i++) {
+            int there = here + dirs.mDirections[i];
+            if (there > end)
+                there = end;
+
+            float h = getPrimaryHorizontal(here);
+
+            if (h > horiz && h < besth) {
+                best = here;
+                besth = h;
+            }
+
+            candidate = TextUtils.getOffsetAfter(mText, here);
+            if (candidate >= start && candidate <= end) {
+                h = getPrimaryHorizontal(candidate);
+
+                if (h > horiz && h < besth) {
+                    best = candidate;
+                    besth = h;
+                }
+            }
+
+            candidate = TextUtils.getOffsetBefore(mText, there);
+            if (candidate >= start && candidate <= end) {
+                h = getPrimaryHorizontal(candidate);
+
+                if (h > horiz && h < besth) {
+                    best = candidate;
+                    besth = h;
+                }
+            }
+
+            here = there;
+        }
+
+        float h = getPrimaryHorizontal(end);
+
+        if (h > horiz && h < besth) {
+            best = end;
+            besth = h;
+        }
+
+        if (best != offset)
+            return best;
+
+        int dir = getParagraphDirection(line);
+
+        if (dir > 0) {
+            if (line == getLineCount() - 1)
+                return best;
+            else
+                return getOffsetForHorizontal(line + 1, -10000);
+        } else {
+            if (line == 0)
+                return best;
+            else
+                return getOffsetForHorizontal(line - 1, -10000);
+        }
+    }
+
+    private int getOffsetAtStartOf(int offset) {
+        if (offset == 0)
+            return 0;
+
+        CharSequence text = mText;
+        char c = text.charAt(offset);
+
+        if (c >= '\uDC00' && c <= '\uDFFF') {
+            char c1 = text.charAt(offset - 1);
+
+            if (c1 >= '\uD800' && c1 <= '\uDBFF')
+                offset -= 1;
+        }
+
+        if (mSpannedText) {
+            ReplacementSpan[] spans = ((Spanned) text).getSpans(offset, offset,
+                                                       ReplacementSpan.class);
+
+            for (int i = 0; i < spans.length; i++) {
+                int start = ((Spanned) text).getSpanStart(spans[i]);
+                int end = ((Spanned) text).getSpanEnd(spans[i]);
+
+                if (start < offset && end > offset)
+                    offset = start;
+            }
+        }
+
+        return offset;
+    }
+
+    /**
+     * Fills in the specified Path with a representation of a cursor
+     * at the specified offset.  This will often be a vertical line
+     * but can be multiple discontinous lines in text with multiple
+     * directionalities.
+     */
+    public void getCursorPath(int point, Path dest,
+                              CharSequence editingBuffer) {
+        dest.reset();
+
+        int line = getLineForOffset(point);
+        int top = getLineTop(line);
+        int bottom = getLineTop(line+1);
+
+        float h1 = getPrimaryHorizontal(point) - 0.5f;
+        float h2 = getSecondaryHorizontal(point) - 0.5f;
+
+        int caps = TextKeyListener.getMetaState(editingBuffer,
+                                                KeyEvent.META_SHIFT_ON) |
+                   TextKeyListener.getMetaState(editingBuffer,
+                                                TextKeyListener.META_SELECTING);
+        int fn = TextKeyListener.getMetaState(editingBuffer,
+                                              KeyEvent.META_ALT_ON);
+        int dist = 0;
+
+        if (caps != 0 || fn != 0) {
+            dist = (bottom - top) >> 2;
+
+            if (fn != 0)
+                top += dist;
+            if (caps != 0)
+                bottom -= dist;
+        }
+
+        if (h1 < 0.5f)
+            h1 = 0.5f;
+        if (h2 < 0.5f)
+            h2 = 0.5f;
+
+        if (h1 == h2) {
+            dest.moveTo(h1, top);
+            dest.lineTo(h1, bottom);
+        } else {
+            dest.moveTo(h1, top);
+            dest.lineTo(h1, (top + bottom) >> 1);
+
+            dest.moveTo(h2, (top + bottom) >> 1);
+            dest.lineTo(h2, bottom);
+        }
+
+        if (caps == 2) {
+            dest.moveTo(h2, bottom);
+            dest.lineTo(h2 - dist, bottom + dist);
+            dest.lineTo(h2, bottom);
+            dest.lineTo(h2 + dist, bottom + dist);
+        } else if (caps == 1) {
+            dest.moveTo(h2, bottom);
+            dest.lineTo(h2 - dist, bottom + dist);
+
+            dest.moveTo(h2 - dist, bottom + dist - 0.5f);
+            dest.lineTo(h2 + dist, bottom + dist - 0.5f);
+
+            dest.moveTo(h2 + dist, bottom + dist);
+            dest.lineTo(h2, bottom);
+        }
+
+        if (fn == 2) {
+            dest.moveTo(h1, top);
+            dest.lineTo(h1 - dist, top - dist);
+            dest.lineTo(h1, top);
+            dest.lineTo(h1 + dist, top - dist);
+        } else if (fn == 1) {
+            dest.moveTo(h1, top);
+            dest.lineTo(h1 - dist, top - dist);
+
+            dest.moveTo(h1 - dist, top - dist + 0.5f);
+            dest.lineTo(h1 + dist, top - dist + 0.5f);
+
+            dest.moveTo(h1 + dist, top - dist);
+            dest.lineTo(h1, top);
+        }
+    }
+
+    private void addSelection(int line, int start, int end,
+                              int top, int bottom, Path dest) {
+        int linestart = getLineStart(line);
+        int lineend = getLineEnd(line);
+        Directions dirs = getLineDirections(line);
+
+        if (lineend > linestart && mText.charAt(lineend - 1) == '\n')
+            lineend--;
+
+        int here = linestart;
+        for (int i = 0; i < dirs.mDirections.length; i++) {
+            int there = here + dirs.mDirections[i];
+            if (there > lineend)
+                there = lineend;
+
+            if (start <= there && end >= here) {
+                int st = Math.max(start, here);
+                int en = Math.min(end, there);
+
+                if (st != en) {
+                    float h1 = getHorizontal(st, false, false, line);
+                    float h2 = getHorizontal(en, true, false, line);
+
+                    dest.addRect(h1, top, h2, bottom, Path.Direction.CW);
+                }
+            }
+
+            here = there;
+        }
+    }
+
+    /**
+     * Fills in the specified Path with a representation of a highlight
+     * between the specified offsets.  This will often be a rectangle
+     * or a potentially discontinuous set of rectangles.  If the start
+     * and end are the same, the returned path is empty.
+     */
+    public void getSelectionPath(int start, int end, Path dest) {
+        dest.reset();
+
+        if (start == end)
+            return;
+
+        if (end < start) {
+            int temp = end;
+            end = start;
+            start = temp;
+        }
+
+        int startline = getLineForOffset(start);
+        int endline = getLineForOffset(end);
+
+        int top = getLineTop(startline);
+        int bottom = getLineBottom(endline);
+
+        if (startline == endline) {
+            addSelection(startline, start, end, top, bottom, dest);
+        } else {
+            final float width = mWidth;
+
+            addSelection(startline, start, getLineEnd(startline),
+                         top, getLineBottom(startline), dest);
+            
+            if (getParagraphDirection(startline) == DIR_RIGHT_TO_LEFT)
+                dest.addRect(getLineLeft(startline), top,
+                              0, getLineBottom(startline), Path.Direction.CW);
+            else
+                dest.addRect(getLineRight(startline), top,
+                              width, getLineBottom(startline), Path.Direction.CW);
+
+            for (int i = startline + 1; i < endline; i++) {
+                top = getLineTop(i);
+                bottom = getLineBottom(i);
+                dest.addRect(0, top, width, bottom, Path.Direction.CW);
+            }
+
+            top = getLineTop(endline);
+            bottom = getLineBottom(endline);
+
+            addSelection(endline, getLineStart(endline), end,
+                         top, bottom, dest);
+
+            if (getParagraphDirection(endline) == DIR_RIGHT_TO_LEFT)
+                dest.addRect(width, top, getLineRight(endline), bottom, Path.Direction.CW);
+            else
+                dest.addRect(0, top, getLineLeft(endline), bottom, Path.Direction.CW);
+        }
+    }
+
+    /**
+     * Get the alignment of the specified paragraph, taking into account
+     * markup attached to it.
+     */
+    public final Alignment getParagraphAlignment(int line) {
+        Alignment align = mAlignment;
+
+        if (mSpannedText) {
+            Spanned sp = (Spanned) mText;
+            AlignmentSpan[] spans = sp.getSpans(getLineStart(line),
+                                                getLineEnd(line),
+                                                AlignmentSpan.class);
+
+            int spanLength = spans.length;
+            if (spanLength > 0) {
+                align = spans[spanLength-1].getAlignment();
+            }
+        }
+
+        return align;
+    }
+
+    /**
+     * Get the left edge of the specified paragraph, inset by left margins.
+     */
+    public final int getParagraphLeft(int line) {
+        int dir = getParagraphDirection(line);
+
+        int left = 0;
+
+        boolean par = false;
+        int off = getLineStart(line);
+        if (off == 0 || mText.charAt(off - 1) == '\n')
+            par = true;
+
+        if (dir == DIR_LEFT_TO_RIGHT) {
+            if (mSpannedText) {
+                Spanned sp = (Spanned) mText;
+                LeadingMarginSpan[] spans = sp.getSpans(getLineStart(line),
+                                                        getLineEnd(line),
+                                                        LeadingMarginSpan.class);
+
+                for (int i = 0; i < spans.length; i++) {
+                    left += spans[i].getLeadingMargin(par);
+                }
+            }
+        }
+
+        return left;
+    }
+
+    /**
+     * Get the right edge of the specified paragraph, inset by right margins.
+     */
+    public final int getParagraphRight(int line) {
+        int dir = getParagraphDirection(line);
+
+        int right = mWidth;
+
+        boolean par = false;
+        int off = getLineStart(line);
+        if (off == 0 || mText.charAt(off - 1) == '\n')
+            par = true;
+
+
+        if (dir == DIR_RIGHT_TO_LEFT) {
+            if (mSpannedText) {
+                Spanned sp = (Spanned) mText;
+                LeadingMarginSpan[] spans = sp.getSpans(getLineStart(line),
+                                                        getLineEnd(line),
+                                                        LeadingMarginSpan.class);
+
+                for (int i = 0; i < spans.length; i++) {
+                    right -= spans[i].getLeadingMargin(par);
+                }
+            }
+        }
+
+        return right;
+    }
+
+    private static void drawText(Canvas canvas,
+                                 CharSequence text, int start, int end,
+                                 int dir, Directions directions,
+                                 float x, int top, int y, int bottom,
+                                 TextPaint paint,
+                                 TextPaint workPaint,
+                                 boolean hasTabs, Object[] parspans) {
+        char[] buf;
+        if (!hasTabs) {
+            if (directions == DIRS_ALL_LEFT_TO_RIGHT) {
+                if (Config.DEBUG) {
+                    Assert.assertTrue(DIR_LEFT_TO_RIGHT == dir);
+                }
+                Styled.drawText(canvas, text, start, end, dir, false, x, top, y, bottom, paint, workPaint, false);
+                return;
+            }
+            buf = null;
+        } else {
+            buf = TextUtils.obtain(end - start);
+            TextUtils.getChars(text, start, end, buf, 0);
+        }
+
+        float h = 0;
+
+        int here = 0;
+        for (int i = 0; i < directions.mDirections.length; i++) {
+            int there = here + directions.mDirections[i];
+            if (there > end - start)
+                there = end - start;
+
+            int segstart = here;
+            for (int j = hasTabs ? here : there; j <= there; j++) {
+                if (j == there || buf[j] == '\t') {
+                    h += Styled.drawText(canvas, text,
+                                         start + segstart, start + j,
+                                         dir, (i & 1) != 0, x + h,
+                                         top, y, bottom, paint, workPaint,
+                                         start + j != end);
+
+                    if (j != there && buf[j] == '\t')
+                        h = dir * nextTab(text, start, end, h * dir, parspans);
+
+                    segstart = j + 1;
+                }
+            }
+
+            here = there;
+        }
+
+        if (hasTabs)
+            TextUtils.recycle(buf);
+    }
+
+    private static float measureText(TextPaint paint,
+                                     TextPaint workPaint,
+                                     CharSequence text,
+                                     int start, int offset, int end,
+                                     int dir, Directions directions,
+                                     boolean trailing, boolean alt,
+                                     boolean hasTabs, Object[] tabs) {
+        char[] buf = null;
+
+        if (hasTabs) {
+            buf = TextUtils.obtain(end - start);
+            TextUtils.getChars(text, start, end, buf, 0);
+        }
+
+        float h = 0;
+
+        if (alt) {
+            if (dir == DIR_RIGHT_TO_LEFT)
+                trailing = !trailing;
+        }
+
+        int here = 0;
+        for (int i = 0; i < directions.mDirections.length; i++) {
+            if (alt)
+                trailing = !trailing;
+
+            int there = here + directions.mDirections[i];
+            if (there > end - start)
+                there = end - start;
+
+            int segstart = here;
+            for (int j = hasTabs ? here : there; j <= there; j++) {
+                if (j == there || buf[j] == '\t') {
+                    float segw;
+
+                    if (offset < start + j ||
+                       (trailing && offset <= start + j)) {
+                        if (dir == DIR_LEFT_TO_RIGHT && (i & 1) == 0) {
+                            h += Styled.measureText(paint, workPaint, text,
+                                                    start + segstart, offset,
+                                                    null);
+                            return h;
+                        }
+
+                        if (dir == DIR_RIGHT_TO_LEFT && (i & 1) != 0) {
+                            h -= Styled.measureText(paint, workPaint, text,
+                                                    start + segstart, offset,
+                                                    null);
+                            return h;
+                        }
+                    }
+
+                    segw = Styled.measureText(paint, workPaint, text,
+                                              start + segstart, start + j,
+                                              null);
+
+                    if (offset < start + j ||
+                        (trailing && offset <= start + j)) {
+                        if (dir == DIR_LEFT_TO_RIGHT) {
+                            h += segw - Styled.measureText(paint, workPaint,
+                                                           text,
+                                                           start + segstart,
+                                                           offset, null);
+                            return h;
+                        }
+
+                        if (dir == DIR_RIGHT_TO_LEFT) {
+                            h -= segw - Styled.measureText(paint, workPaint,
+                                                           text,
+                                                           start + segstart,
+                                                           offset, null);
+                            return h;
+                        }
+                    }
+
+                    if (dir == DIR_RIGHT_TO_LEFT)
+                        h -= segw;
+                    else
+                        h += segw;
+
+                    if (j != there && buf[j] == '\t') {
+                        if (offset == start + j)
+                            return h;
+
+                        h = dir * nextTab(text, start, end, h * dir, tabs);
+                    }
+
+                    segstart = j + 1;
+                }
+            }
+
+            here = there;
+        }
+
+        if (hasTabs)
+            TextUtils.recycle(buf);
+
+        return h;
+    }
+
+    /* package */ static float measureText(TextPaint paint,
+                                           TextPaint workPaint,
+                                           CharSequence text,
+                                           int start, int end,
+                                           Paint.FontMetricsInt fm,
+                                           boolean hasTabs, Object[] tabs) {
+        char[] buf = null;
+  
+        if (hasTabs) {
+            buf = TextUtils.obtain(end - start);
+            TextUtils.getChars(text, start, end, buf, 0);
+        }
+
+        int len = end - start;
+
+        int here = 0;
+        float h = 0;
+        int ab = 0, be = 0;
+        int top = 0, bot = 0;
+
+        if (fm != null) {
+            fm.ascent = 0;
+            fm.descent = 0;
+        }
+
+        for (int i = hasTabs ? 0 : len; i <= len; i++) {
+            if (i == len || buf[i] == '\t') {
+                workPaint.baselineShift = 0;
+
+                h += Styled.measureText(paint, workPaint, text,
+                                        start + here, start + i,
+                                        fm);
+
+                if (fm != null) {
+                    if (workPaint.baselineShift < 0) {
+                        fm.ascent += workPaint.baselineShift;
+                        fm.top += workPaint.baselineShift;
+                    } else {
+                        fm.descent += workPaint.baselineShift;
+                        fm.bottom += workPaint.baselineShift;
+                    }
+                }
+
+                if (i != len)
+                    h = nextTab(text, start, end, h, tabs);
+
+                if (fm != null) {
+                    if (fm.ascent < ab) {
+                        ab = fm.ascent;
+                    }
+                    if (fm.descent > be) {
+                        be = fm.descent;
+                    }
+
+                    if (fm.top < top) {
+                        top = fm.top;
+                    }
+                    if (fm.bottom > bot) {
+                        bot = fm.bottom;
+                    }
+                }
+
+                here = i + 1;
+            }
+        }
+
+        if (fm != null) {
+            fm.ascent = ab;
+            fm.descent = be;
+            fm.top = top;
+            fm.bottom = bot;
+        }
+
+        if (hasTabs)
+            TextUtils.recycle(buf);
+
+        return h;
+    }
+
+    /* package */ static float nextTab(CharSequence text, int start, int end,
+                                       float h, Object[] tabs) {
+        float nh = Float.MAX_VALUE;
+        boolean alltabs = false;
+
+        if (text instanceof Spanned) {
+            if (tabs == null) {
+                tabs = ((Spanned) text).getSpans(start, end, TabStopSpan.class);
+                alltabs = true;
+            }
+
+            for (int i = 0; i < tabs.length; i++) {
+                if (!alltabs) {
+                    if (!(tabs[i] instanceof TabStopSpan))
+                        continue;
+                }
+
+                int where = ((TabStopSpan) tabs[i]).getTabStop();
+
+                if (where < nh && where > h)
+                    nh = where;
+            }
+
+            if (nh != Float.MAX_VALUE)
+                return nh;
+        }
+
+        return ((int) ((h + TAB_INCREMENT) / TAB_INCREMENT)) * TAB_INCREMENT;
+    }
+
+    protected final boolean isSpanned() {
+        return mSpannedText;
+    }
+
+    private void ellipsize(int start, int end, int line,
+                           char[] dest, int destoff) {
+        int ellipsisCount = getEllipsisCount(line);
+
+        if (ellipsisCount == 0) {
+            return;
+        }
+
+        int ellipsisStart = getEllipsisStart(line);
+        int linestart = getLineStart(line);
+
+        for (int i = ellipsisStart; i < ellipsisStart + ellipsisCount; i++) {
+            char c;
+
+            if (i == ellipsisStart) {
+                c = '\u2026'; // ellipsis
+            } else {
+                c = '\uFEFF'; // 0-width space
+            }
+
+            int a = i + linestart;
+
+            if (a >= start && a < end) {
+                dest[destoff + a - start] = c;
+            }
+        }
+    }
+
+    /**
+     * Stores information about bidirectional (left-to-right or right-to-left)
+     * text within the layout of a line.  TODO: This work is not complete
+     * or correct and will be fleshed out in a later revision.
+     */
+    public static class Directions {
+        private short[] mDirections;
+
+        /* package */ Directions(short[] dirs) {
+            mDirections = dirs;
+        }
+    }
+
+    /**
+     * Return the offset of the first character to be ellipsized away,
+     * relative to the start of the line.  (So 0 if the beginning of the
+     * line is ellipsized, not getLineStart().)
+     */
+    public abstract int getEllipsisStart(int line);
+    /**
+     * Returns the number of characters to be ellipsized away, or 0 if
+     * no ellipsis is to take place.
+     */
+    public abstract int getEllipsisCount(int line);
+
+    /* package */ static class Ellipsizer implements CharSequence, GetChars {
+        /* package */ CharSequence mText;
+        /* package */ Layout mLayout;
+        /* package */ int mWidth;
+        /* package */ TextUtils.TruncateAt mMethod;
+
+        public Ellipsizer(CharSequence s) {
+            mText = s;
+        }
+
+        public char charAt(int off) {
+            char[] buf = TextUtils.obtain(1);
+            getChars(off, off + 1, buf, 0);
+            char ret = buf[0];
+
+            TextUtils.recycle(buf);
+            return ret;
+        }
+
+        public void getChars(int start, int end, char[] dest, int destoff) {
+            int line1 = mLayout.getLineForOffset(start);
+            int line2 = mLayout.getLineForOffset(end);
+
+            TextUtils.getChars(mText, start, end, dest, destoff);
+
+            for (int i = line1; i <= line2; i++) {
+                mLayout.ellipsize(start, end, i, dest, destoff);
+            }
+        }
+
+        public int length() {
+            return mText.length();
+        }
+    
+        public CharSequence subSequence(int start, int end) {
+            char[] s = new char[end - start];
+            getChars(start, end, s, 0);
+            return new String(s);
+        }
+
+        public String toString() {
+            char[] s = new char[length()];
+            getChars(0, length(), s, 0);
+            return new String(s);
+        }
+
+    }
+
+    /* package */ static class SpannedEllipsizer
+                    extends Ellipsizer implements Spanned {
+        private Spanned mSpanned;
+
+        public SpannedEllipsizer(CharSequence display) {
+            super(display);
+            mSpanned = (Spanned) display;
+        }
+
+        public <T> T[] getSpans(int start, int end, Class<T> type) {
+            return mSpanned.getSpans(start, end, type);
+        }
+
+        public int getSpanStart(Object tag) {
+            return mSpanned.getSpanStart(tag);
+        }
+
+        public int getSpanEnd(Object tag) {
+            return mSpanned.getSpanEnd(tag);
+        }
+
+        public int getSpanFlags(Object tag) {
+            return mSpanned.getSpanFlags(tag);
+        }
+
+        public int nextSpanTransition(int start, int limit, Class type) {
+            return mSpanned.nextSpanTransition(start, limit, type);
+        }
+
+        public CharSequence subSequence(int start, int end) {
+            char[] s = new char[end - start];
+            getChars(start, end, s, 0);
+
+            SpannableString ss = new SpannableString(new String(s));
+            TextUtils.copySpansFrom(mSpanned, start, end, Object.class, ss, 0);
+            return ss;
+        }
+    }
+
+    private CharSequence mText;
+    private TextPaint mPaint;
+    /* package */ TextPaint mWorkPaint;
+    private int mWidth;
+    private Alignment mAlignment = Alignment.ALIGN_NORMAL;
+    private float mSpacingMult;
+    private float mSpacingAdd;
+    private static Rect sTempRect = new Rect();
+    private boolean mSpannedText;
+
+    public static final int DIR_LEFT_TO_RIGHT = 1;
+    public static final int DIR_RIGHT_TO_LEFT = -1;
+
+    public enum Alignment {
+        ALIGN_NORMAL,
+        ALIGN_OPPOSITE,
+        ALIGN_CENTER,
+        // XXX ALIGN_LEFT,
+        // XXX ALIGN_RIGHT,
+    }
+
+    private static final int TAB_INCREMENT = 20;
+
+    /* package */ static final Directions DIRS_ALL_LEFT_TO_RIGHT =
+                                       new Directions(new short[] { 32767 });
+    /* package */ static final Directions DIRS_ALL_RIGHT_TO_LEFT =
+                                       new Directions(new short[] { 0, 32767 });
+
+}
+
diff --git a/core/java/android/text/LoginFilter.java b/core/java/android/text/LoginFilter.java
new file mode 100644
index 0000000..27c703f
--- /dev/null
+++ b/core/java/android/text/LoginFilter.java
@@ -0,0 +1,219 @@
+/*
+ * 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 android.text;
+
+/**
+ * Abstract class for filtering login-related text (user names and passwords)
+ * 
+ */
+public abstract class LoginFilter implements InputFilter {
+    private boolean mAppendInvalid;  // whether to append or ignore invalid characters
+    /**
+     * Base constructor for LoginFilter
+     * @param appendInvalid whether or not to append invalid characters.
+     */
+    LoginFilter(boolean appendInvalid) {
+        mAppendInvalid = appendInvalid;
+    }
+    
+    /**
+     * Default constructor for LoginFilter doesn't append invalid characters.
+     */
+    LoginFilter() {
+        mAppendInvalid = false;
+    }
+    
+    /**
+     * This method is called when the buffer is going to replace the
+     * range <code>dstart &hellip; dend</code> of <code>dest</code>
+     * with the new text from the range <code>start &hellip; end</code>
+     * of <code>source</code>.  Returns the CharSequence that we want
+     * placed there instead, including an empty string
+     * if appropriate, or <code>null</code> to accept the original
+     * replacement.  Be careful to not to reject 0-length replacements,
+     * as this is what happens when you delete text.
+     */
+    public CharSequence filter(CharSequence source, int start, int end,
+            Spanned dest, int dstart, int dend) {
+        char[] out = new char[end - start]; // reserve enough space for whole string
+        int outidx = 0;
+        boolean changed = false;
+        
+        onStart();
+        
+        // Scan through beginning characters in dest, calling onInvalidCharacter() 
+        // for each invalid character.
+        for (int i = 0; i < dstart; i++) {
+            char c = dest.charAt(i);
+            if (!isAllowed(c)) onInvalidCharacter(c);
+        }
+
+        // Scan through changed characters rejecting disallowed chars
+        for (int i = start; i < end; i++) {
+            char c = source.charAt(i);
+            if (isAllowed(c)) {
+                // Character allowed. Add it to the sequence.
+                out[outidx++] = c;
+            } else {
+                if (mAppendInvalid) out[outidx++] = c;
+                else changed = true; // we changed the original string
+                onInvalidCharacter(c);
+            }
+        }
+        
+        // Scan through remaining characters in dest, calling onInvalidCharacter() 
+        // for each invalid character.
+        for (int i = dend; i < dest.length(); i++) {
+            char c = dest.charAt(i);
+            if (!isAllowed(c)) onInvalidCharacter(c);
+        }
+        
+        onStop();
+
+        if (!changed) {
+            return null;
+        }
+        
+        String s = new String(out, 0, outidx);
+        
+        if (source instanceof Spanned) {
+            SpannableString sp = new SpannableString(s);
+            TextUtils.copySpansFrom((Spanned) source,
+                                    start, end, null, sp, 0);
+            return sp;
+        } else {
+            return s;
+        }
+    }
+    
+    /**
+     * Called when we start processing filter.
+     */
+    public void onStart() {
+        
+    }
+    
+    /**
+     * Called whenever we encounter an invalid character.
+     * @param c the invalid character
+     */
+    public void onInvalidCharacter(char c) {
+        
+    }
+    
+    /**
+     * Called when we're done processing filter
+     */
+    public void onStop() {
+        
+    }
+    
+    /**
+     * Returns whether or not we allow character c. 
+     * Subclasses must override this method.
+     */
+    public abstract boolean isAllowed(char c);
+
+    /**
+     * This filter rejects characters in the user name that are not compatible with GMail 
+     * account creation. It prevents the user from entering user names with characters other than 
+     * [a-zA-Z0-9.]. 
+     * 
+     */
+    public static class UsernameFilterGMail extends LoginFilter {
+        
+        public UsernameFilterGMail() {
+            super(false);
+        }
+        
+        public UsernameFilterGMail(boolean appendInvalid) {
+            super(appendInvalid);
+        }
+        
+        @Override
+        public boolean isAllowed(char c) {
+            // Allow [a-zA-Z0-9@.]
+            if ('0' <= c && c <= '9')
+                return true;
+            if ('a' <= c && c <= 'z')
+                return true;
+            if ('A' <= c && c <= 'Z')
+                return true;
+            if ('.' == c)
+                return true;
+            return false;
+        }
+    }
+
+    /**
+     * This filter rejects characters in the user name that are not compatible with Google login.
+     * It is slightly less restrictive than the above filter in that it allows [a-zA-Z0-9._-]. 
+     * 
+     */
+    public static class UsernameFilterGeneric extends LoginFilter {
+        private static final String mAllowed = "@_-."; // Additional characters
+        
+        public UsernameFilterGeneric() {
+            super(false);
+        }
+        
+        public UsernameFilterGeneric(boolean appendInvalid) {
+            super(appendInvalid);
+        }
+        
+        @Override
+        public boolean isAllowed(char c) {
+            // Allow [a-zA-Z0-9@.]
+            if ('0' <= c && c <= '9')
+                return true;
+            if ('a' <= c && c <= 'z')
+                return true;
+            if ('A' <= c && c <= 'Z')
+                return true;
+            if (mAllowed.indexOf(c) != -1)
+                return true;
+            return false;
+        }
+    }
+
+    /**
+     * This filter is compatible with GMail passwords which restricts characters to 
+     * the Latin-1 (ISO8859-1) char set.
+     * 
+     */
+    public static class PasswordFilterGMail extends LoginFilter {
+        
+        public PasswordFilterGMail() {
+            super(false);
+        }
+        
+        public PasswordFilterGMail(boolean appendInvalid) {
+            super(appendInvalid);
+        }
+        
+        // We should reject anything not in the Latin-1 (ISO8859-1) charset
+        @Override
+        public boolean isAllowed(char c) {
+            if (32 <= c && c <= 127)
+                return true; // standard charset
+            // if (128 <= c && c <= 159) return true;  // nonstandard (Windows(TM)(R)) charset
+            if (160 <= c && c <= 255)
+                return true; // extended charset
+            return false;
+        }
+    }
+}
diff --git a/core/java/android/text/NoCopySpan.java b/core/java/android/text/NoCopySpan.java
new file mode 100644
index 0000000..0855c0b
--- /dev/null
+++ b/core/java/android/text/NoCopySpan.java
@@ -0,0 +1,31 @@
+/*
+ * 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 android.text;
+
+/**
+ * This interface should be added to a span object that should not be copied
+ * into a new Spenned when performing a slice or copy operation on the original
+ * Spanned it was placed in.
+ */
+public interface NoCopySpan {
+    /**
+     * Convenience equivalent for when you would just want a new Object() for
+     * a span but want it to be no-copy.  Use this instead.
+     */
+    public class Concrete implements NoCopySpan {
+    }
+}
diff --git a/core/java/android/text/PackedIntVector.java b/core/java/android/text/PackedIntVector.java
new file mode 100644
index 0000000..d87f600
--- /dev/null
+++ b/core/java/android/text/PackedIntVector.java
@@ -0,0 +1,368 @@
+/*
+ * Copyright (C) 2007 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 android.text;
+
+import com.android.internal.util.ArrayUtils;
+
+
+/**
+ * PackedIntVector stores a two-dimensional array of integers,
+ * optimized for inserting and deleting rows and for
+ * offsetting the values in segments of a given column.
+ */
+class PackedIntVector {
+    private final int mColumns;
+    private int mRows;
+
+    private int mRowGapStart;
+    private int mRowGapLength;
+
+    private int[] mValues;
+    private int[] mValueGap; // starts, followed by lengths
+
+    /**
+     * Creates a new PackedIntVector with the specified width and
+     * a height of 0.
+     *
+     * @param columns the width of the PackedIntVector.
+     */
+    public PackedIntVector(int columns) {
+        mColumns = columns;
+        mRows = 0;
+
+        mRowGapStart = 0;
+        mRowGapLength = mRows;
+
+        mValues = null;
+        mValueGap = new int[2 * columns];
+    }
+
+    /**
+     * Returns the value at the specified row and column.
+     *
+     * @param row the index of the row to return.
+     * @param column the index of the column to return.
+     *
+     * @return the value stored at the specified position.
+     *
+     * @throws IndexOutOfBoundsException if the row is out of range
+     *         (row &lt; 0 || row >= size()) or the column is out of range
+     *         (column &lt; 0 || column >= width()).
+     */
+    public int getValue(int row, int column) {
+        final int columns = mColumns;
+
+        if (((row | column) < 0) || (row >= size()) || (column >= columns)) {
+            throw new IndexOutOfBoundsException(row + ", " + column);
+        }
+
+        if (row >= mRowGapStart) {
+            row += mRowGapLength;
+        }
+
+        int value = mValues[row * columns + column];
+
+        int[] valuegap = mValueGap;
+        if (row >= valuegap[column]) {
+            value += valuegap[column + columns];
+        }
+
+        return value;
+    }
+
+    /**
+     * Sets the value at the specified row and column.
+     *
+     * @param row the index of the row to set.
+     * @param column the index of the column to set.
+     *
+     * @throws IndexOutOfBoundsException if the row is out of range
+     *         (row &lt; 0 || row >= size()) or the column is out of range
+     *         (column &lt; 0 || column >= width()).
+     */
+    public void setValue(int row, int column, int value) {
+        if (((row | column) < 0) || (row >= size()) || (column >= mColumns)) {
+            throw new IndexOutOfBoundsException(row + ", " + column);
+        }
+
+        if (row >= mRowGapStart) {
+            row += mRowGapLength;
+        }
+
+        int[] valuegap = mValueGap;
+        if (row >= valuegap[column]) {
+            value -= valuegap[column + mColumns];
+        }
+
+        mValues[row * mColumns + column] = value;
+    }
+
+    /**
+     * Sets the value at the specified row and column.
+     * Private internal version: does not check args.
+     *
+     * @param row the index of the row to set.
+     * @param column the index of the column to set.
+     *
+     */
+    private void setValueInternal(int row, int column, int value) {
+        if (row >= mRowGapStart) {
+            row += mRowGapLength;
+        }
+
+        int[] valuegap = mValueGap;
+        if (row >= valuegap[column]) {
+            value -= valuegap[column + mColumns];
+        }
+
+        mValues[row * mColumns + column] = value;
+    }
+
+
+    /**
+     * Increments all values in the specified column whose row >= the
+     * specified row by the specified delta.
+     *
+     * @param startRow the row at which to begin incrementing.
+     *        This may be == size(), which case there is no effect.
+     * @param column the index of the column to set.
+     *
+     * @throws IndexOutOfBoundsException if the row is out of range
+     *         (startRow &lt; 0 || startRow > size()) or the column
+     *         is out of range (column &lt; 0 || column >= width()).
+     */
+    public void adjustValuesBelow(int startRow, int column, int delta) {
+        if (((startRow | column) < 0) || (startRow > size()) ||
+                (column >= width())) {
+            throw new IndexOutOfBoundsException(startRow + ", " + column);
+        }
+
+        if (startRow >= mRowGapStart) {
+            startRow += mRowGapLength;
+        }
+
+        moveValueGapTo(column, startRow);
+        mValueGap[column + mColumns] += delta;
+    }
+
+    /**
+     * Inserts a new row of values at the specified row offset.
+     *
+     * @param row the row above which to insert the new row.
+     *        This may be == size(), which case the new row is added
+     *        at the end.
+     * @param values the new values to be added.  If this is null,
+     *        a row of zeroes is added.
+     *
+     * @throws IndexOutOfBoundsException if the row is out of range
+     *         (row &lt; 0 || row > size()) or if the length of the
+     *         values array is too small (values.length < width()).
+     */
+    public void insertAt(int row, int[] values) {
+        if ((row < 0) || (row > size())) {
+            throw new IndexOutOfBoundsException("row " + row);
+        }
+
+        if ((values != null) && (values.length < width())) {
+            throw new IndexOutOfBoundsException("value count " + values.length);
+        }
+
+        moveRowGapTo(row);
+
+        if (mRowGapLength == 0) {
+            growBuffer();
+        }
+
+        mRowGapStart++;
+        mRowGapLength--;
+
+        if (values == null) {
+            for (int i = mColumns - 1; i >= 0; i--) {
+                setValueInternal(row, i, 0);
+            }
+        } else {
+            for (int i = mColumns - 1; i >= 0; i--) {
+                setValueInternal(row, i, values[i]);
+            }
+        }
+    }
+
+    /**
+     * Deletes the specified number of rows starting with the specified
+     * row.
+     *
+     * @param row the index of the first row to be deleted.
+     * @param count the number of rows to delete.
+     *
+     * @throws IndexOutOfBoundsException if any of the rows to be deleted
+     *         are out of range (row &lt; 0 || count &lt; 0 ||
+     *         row + count > size()).
+     */
+    public void deleteAt(int row, int count) {
+        if (((row | count) < 0) || (row + count > size())) {
+            throw new IndexOutOfBoundsException(row + ", " + count);
+        }
+
+        moveRowGapTo(row + count);
+
+        mRowGapStart -= count;
+        mRowGapLength += count;
+
+        // TODO: Reclaim memory when the new height is much smaller
+        // than the allocated size.
+    }
+
+    /**
+     * Returns the number of rows in the PackedIntVector.  This number
+     * will change as rows are inserted and deleted.
+     *
+     * @return the number of rows.
+     */
+    public int size() {
+        return mRows - mRowGapLength;
+    }
+
+    /**
+     * Returns the width of the PackedIntVector.  This number is set
+     * at construction and will not change.
+     *
+     * @return the number of columns.
+     */
+    public int width() {
+        return mColumns;
+    }
+
+    /**
+     * Grows the value and gap arrays to be large enough to store at least
+     * one more than the current number of rows.
+     */
+    private final void growBuffer() {
+        final int columns = mColumns;
+        int newsize = size() + 1;
+        newsize = ArrayUtils.idealIntArraySize(newsize * columns) / columns;
+        int[] newvalues = new int[newsize * columns];
+
+        final int[] valuegap = mValueGap;
+        final int rowgapstart = mRowGapStart;
+
+        int after = mRows - (rowgapstart + mRowGapLength);
+
+        if (mValues != null) {
+            System.arraycopy(mValues, 0, newvalues, 0, columns * rowgapstart);
+            System.arraycopy(mValues, (mRows - after) * columns,
+                             newvalues, (newsize - after) * columns,
+                             after * columns);
+        }
+
+        for (int i = 0; i < columns; i++) {
+            if (valuegap[i] >= rowgapstart) {
+                valuegap[i] += newsize - mRows;
+
+                if (valuegap[i] < rowgapstart) {
+                    valuegap[i] = rowgapstart;
+                }
+            }
+        }
+
+        mRowGapLength += newsize - mRows;
+        mRows = newsize;
+        mValues = newvalues;
+    }
+
+    /**
+     * Moves the gap in the values of the specified column to begin at
+     * the specified row.
+     */
+    private final void moveValueGapTo(int column, int where) {
+        final int[] valuegap = mValueGap;
+        final int[] values = mValues;
+        final int columns = mColumns;
+
+        if (where == valuegap[column]) {
+            return;
+        } else if (where > valuegap[column]) {
+            for (int i = valuegap[column]; i < where; i++) {
+                values[i * columns + column] += valuegap[column + columns];
+            }
+        } else /* where < valuegap[column] */ {
+            for (int i = where; i < valuegap[column]; i++) {
+                values[i * columns + column] -= valuegap[column + columns];
+            }
+        }
+
+        valuegap[column] = where;
+    }
+
+    /**
+     * Moves the gap in the row indices to begin at the specified row.
+     */
+    private final void moveRowGapTo(int where) {
+        if (where == mRowGapStart) {
+            return;
+        } else if (where > mRowGapStart) {
+            int moving = where + mRowGapLength - (mRowGapStart + mRowGapLength);
+            final int columns = mColumns;
+            final int[] valuegap = mValueGap;
+            final int[] values = mValues;
+            final int gapend = mRowGapStart + mRowGapLength;
+
+            for (int i = gapend; i < gapend + moving; i++) {
+                int destrow = i - gapend + mRowGapStart;
+
+                for (int j = 0; j < columns; j++) {
+                    int val = values[i * columns+ j];
+
+                    if (i >= valuegap[j]) {
+                        val += valuegap[j + columns];
+                    }
+
+                    if (destrow >= valuegap[j]) {
+                        val -= valuegap[j + columns];
+                    }
+
+                    values[destrow * columns + j] = val;
+                }
+            }
+        } else /* where < mRowGapStart */ {
+            int moving = mRowGapStart - where;
+            final int columns = mColumns;
+            final int[] valuegap = mValueGap;
+            final int[] values = mValues;
+            final int gapend = mRowGapStart + mRowGapLength;
+
+            for (int i = where + moving - 1; i >= where; i--) {
+                int destrow = i - where + gapend - moving;
+
+                for (int j = 0; j < columns; j++) {
+                    int val = values[i * columns+ j];
+
+                    if (i >= valuegap[j]) {
+                        val += valuegap[j + columns];
+                    }
+
+                    if (destrow >= valuegap[j]) {
+                        val -= valuegap[j + columns];
+                    }
+
+                    values[destrow * columns + j] = val;
+                }
+            }
+        }
+
+        mRowGapStart = where;
+    }
+}
diff --git a/core/java/android/text/PackedObjectVector.java b/core/java/android/text/PackedObjectVector.java
new file mode 100644
index 0000000..a29df09
--- /dev/null
+++ b/core/java/android/text/PackedObjectVector.java
@@ -0,0 +1,188 @@
+/*
+ * 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 android.text;
+
+import com.android.internal.util.ArrayUtils;
+
+class PackedObjectVector<E>
+{
+    private int mColumns;
+    private int mRows;
+
+    private int mRowGapStart;
+    private int mRowGapLength;
+
+    private Object[] mValues;
+
+    public
+    PackedObjectVector(int columns)
+    {
+        mColumns = columns;
+        mRows = ArrayUtils.idealIntArraySize(0) / mColumns;
+
+        mRowGapStart = 0;
+        mRowGapLength = mRows;
+
+        mValues = new Object[mRows * mColumns];
+    }
+
+    public E
+    getValue(int row, int column)
+    {
+        if (row >= mRowGapStart)
+            row += mRowGapLength;
+
+        Object value = mValues[row * mColumns + column];
+
+        return (E) value;
+    }
+
+    public void
+    setValue(int row, int column, E value)
+    {
+        if (row >= mRowGapStart)
+            row += mRowGapLength;
+
+        mValues[row * mColumns + column] = value;
+    }
+
+    public void
+    insertAt(int row, E[] values)
+    {
+        moveRowGapTo(row);
+
+        if (mRowGapLength == 0)
+            growBuffer();
+
+        mRowGapStart++;
+        mRowGapLength--;
+
+        if (values == null)
+            for (int i = 0; i < mColumns; i++)
+                setValue(row, i, null);
+        else
+            for (int i = 0; i < mColumns; i++)
+                setValue(row, i, values[i]);
+    }
+
+    public void
+    deleteAt(int row, int count)
+    {
+        moveRowGapTo(row + count);
+
+        mRowGapStart -= count;
+        mRowGapLength += count;
+
+        if (mRowGapLength > size() * 2)
+        {
+            // dump();
+            // growBuffer();
+        }
+    }
+
+    public int
+    size()
+    {
+        return mRows - mRowGapLength;
+    }
+
+    public int
+    width()
+    {
+        return mColumns;
+    }
+
+    private void
+    growBuffer()
+    {
+        int newsize = size() + 1;
+        newsize = ArrayUtils.idealIntArraySize(newsize * mColumns) / mColumns;
+        Object[] newvalues = new Object[newsize * mColumns];
+
+        int after = mRows - (mRowGapStart + mRowGapLength);
+
+        System.arraycopy(mValues, 0, newvalues, 0, mColumns * mRowGapStart);
+        System.arraycopy(mValues, (mRows - after) * mColumns, newvalues, (newsize - after) * mColumns, after * mColumns);
+
+        mRowGapLength += newsize - mRows;
+        mRows = newsize;
+        mValues = newvalues;
+    }
+
+    private void
+    moveRowGapTo(int where)
+    {
+        if (where == mRowGapStart)
+            return;
+
+        if (where > mRowGapStart)
+        {
+            int moving = where + mRowGapLength - (mRowGapStart + mRowGapLength);
+
+            for (int i = mRowGapStart + mRowGapLength; i < mRowGapStart + mRowGapLength + moving; i++)
+            {
+                int destrow = i - (mRowGapStart + mRowGapLength) + mRowGapStart;
+
+                for (int j = 0; j < mColumns; j++)
+                {
+                    Object val = mValues[i * mColumns + j];
+
+                    mValues[destrow * mColumns + j] = val;
+                }
+            }
+        }
+        else /* where < mRowGapStart */
+        {
+            int moving = mRowGapStart - where;
+
+            for (int i = where + moving - 1; i >= where; i--)
+            {
+                int destrow = i - where + mRowGapStart + mRowGapLength - moving;
+
+                for (int j = 0; j < mColumns; j++)
+                {
+                    Object val = mValues[i * mColumns + j];
+
+                    mValues[destrow * mColumns + j] = val;
+                }
+            }
+        }
+
+        mRowGapStart = where;
+    }
+
+    public void // XXX
+    dump()
+    {
+        for (int i = 0; i < mRows; i++)
+        {
+            for (int j = 0; j < mColumns; j++)
+            {
+                Object val = mValues[i * mColumns + j];
+
+                if (i < mRowGapStart || i >= mRowGapStart + mRowGapLength)
+                    System.out.print(val + " ");
+                else
+                    System.out.print("(" + val + ") ");
+            }
+
+            System.out.print(" << \n");
+        }
+
+        System.out.print("-----\n\n");
+    }
+}
diff --git a/core/java/android/text/ParcelableSpan.java b/core/java/android/text/ParcelableSpan.java
new file mode 100644
index 0000000..224511a
--- /dev/null
+++ b/core/java/android/text/ParcelableSpan.java
@@ -0,0 +1,31 @@
+/*
+ * 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 android.text;
+
+import android.os.Parcelable;
+
+/**
+ * A special kind of Parcelable for objects that will serve as text spans.
+ * This can only be used by code in the framework; it is not intended for
+ * applications to implement their own Parcelable spans.
+ */
+public interface ParcelableSpan extends Parcelable {
+    /**
+     * Return a special type identifier for this span class.
+     */
+    public abstract int getSpanTypeId();
+}
diff --git a/core/java/android/text/Selection.java b/core/java/android/text/Selection.java
new file mode 100644
index 0000000..bb98bce
--- /dev/null
+++ b/core/java/android/text/Selection.java
@@ -0,0 +1,429 @@
+/*
+ * 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 android.text;
+
+
+/**
+ * Utility class for manipulating cursors and selections in CharSequences.
+ * A cursor is a selection where the start and end are at the same offset.
+ */
+public class Selection {
+    private Selection() { /* cannot be instantiated */ }
+
+    /*
+     * Retrieving the selection
+     */
+
+    /**
+     * Return the offset of the selection anchor or cursor, or -1 if
+     * there is no selection or cursor.
+     */
+    public static final int getSelectionStart(CharSequence text) {
+        if (text instanceof Spanned)
+            return ((Spanned) text).getSpanStart(SELECTION_START);
+        else
+            return -1;
+    }
+   
+    /**
+     * Return the offset of the selection edge or cursor, or -1 if
+     * there is no selection or cursor.
+     */
+    public static final int getSelectionEnd(CharSequence text) {
+        if (text instanceof Spanned)
+            return ((Spanned) text).getSpanStart(SELECTION_END);
+        else
+            return -1;
+    }
+
+    /*
+     * Setting the selection
+     */
+
+    // private static int pin(int value, int min, int max) {
+    //     return value < min ? 0 : (value > max ? max : value);
+    // }
+   
+    /**
+     * Set the selection anchor to <code>start</code> and the selection edge
+     * to <code>stop</code>.
+     */
+    public static void setSelection(Spannable text, int start, int stop) {
+        // int len = text.length();
+        // start = pin(start, 0, len);  XXX remove unless we really need it
+        // stop = pin(stop, 0, len);
+
+        int ostart = getSelectionStart(text);
+        int oend = getSelectionEnd(text);
+    
+        if (ostart != start || oend != stop) {
+            text.setSpan(SELECTION_START, start, start,
+                         Spanned.SPAN_POINT_POINT|Spanned.SPAN_INTERMEDIATE);
+            text.setSpan(SELECTION_END, stop, stop,
+                         Spanned.SPAN_POINT_POINT);
+        }
+    }
+
+    /**
+     * Move the cursor to offset <code>index</code>.
+     */
+    public static final void setSelection(Spannable text, int index) {
+        setSelection(text, index, index);
+    }
+
+    /**
+     * Select the entire text.
+     */
+    public static final void selectAll(Spannable text) {
+        setSelection(text, 0, text.length());
+    }
+
+    /**
+     * Move the selection edge to offset <code>index</code>.
+     */
+    public static final void extendSelection(Spannable text, int index) {
+        if (text.getSpanStart(SELECTION_END) != index)
+            text.setSpan(SELECTION_END, index, index, Spanned.SPAN_POINT_POINT);
+    }
+
+    /**
+     * Remove the selection or cursor, if any, from the text.
+     */
+    public static final void removeSelection(Spannable text) {
+        text.removeSpan(SELECTION_START);
+        text.removeSpan(SELECTION_END);
+    }
+
+    /*
+     * Moving the selection within the layout
+     */
+
+    /**
+     * Move the cursor to the buffer offset physically above the current
+     * offset, or return false if the cursor is already on the top line.
+     */
+    public static boolean moveUp(Spannable text, Layout layout) {
+        int start = getSelectionStart(text);
+        int end = getSelectionEnd(text);
+
+        if (start != end) {
+            int min = Math.min(start, end);
+            int max = Math.max(start, end);
+
+            setSelection(text, min);
+
+            if (min == 0 && max == text.length()) {
+                return false;
+            }
+
+            return true;
+        } else {
+            int line = layout.getLineForOffset(end);
+
+            if (line > 0) {
+                int move;
+
+                if (layout.getParagraphDirection(line) ==
+                    layout.getParagraphDirection(line - 1)) {
+                    float h = layout.getPrimaryHorizontal(end);
+                    move = layout.getOffsetForHorizontal(line - 1, h);
+                } else {
+                    move = layout.getLineStart(line - 1);
+                }
+
+                setSelection(text, move);
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    /**
+     * Move the cursor to the buffer offset physically below the current
+     * offset, or return false if the cursor is already on the bottom line.
+     */
+    public static boolean moveDown(Spannable text, Layout layout) {
+        int start = getSelectionStart(text);
+        int end = getSelectionEnd(text);
+
+        if (start != end) {
+            int min = Math.min(start, end);
+            int max = Math.max(start, end);
+
+            setSelection(text, max);
+
+            if (min == 0 && max == text.length()) {
+                return false;
+            }
+
+            return true;
+        } else {
+            int line = layout.getLineForOffset(end);
+
+            if (line < layout.getLineCount() - 1) {
+                int move;
+
+                if (layout.getParagraphDirection(line) ==
+                    layout.getParagraphDirection(line + 1)) {
+                    float h = layout.getPrimaryHorizontal(end);
+                    move = layout.getOffsetForHorizontal(line + 1, h);
+                } else {
+                    move = layout.getLineStart(line + 1);
+                }
+
+                setSelection(text, move);
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    /**
+     * Move the cursor to the buffer offset physically to the left of
+     * the current offset, or return false if the cursor is already
+     * at the left edge of the line and there is not another line to move it to.
+     */
+    public static boolean moveLeft(Spannable text, Layout layout) {
+        int start = getSelectionStart(text);
+        int end = getSelectionEnd(text);
+
+        if (start != end) {
+            setSelection(text, chooseHorizontal(layout, -1, start, end));
+            return true;
+        } else {
+            int to = layout.getOffsetToLeftOf(end);
+
+            if (to != end) {
+                setSelection(text, to);
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    /**
+     * Move the cursor to the buffer offset physically to the right of
+     * the current offset, or return false if the cursor is already at
+     * at the right edge of the line and there is not another line
+     * to move it to.
+     */
+    public static boolean moveRight(Spannable text, Layout layout) {
+        int start = getSelectionStart(text);
+        int end = getSelectionEnd(text);
+
+        if (start != end) {
+            setSelection(text, chooseHorizontal(layout, 1, start, end));
+            return true;
+        } else {
+            int to = layout.getOffsetToRightOf(end);
+
+            if (to != end) {
+                setSelection(text, to);
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    /**
+     * Move the selection end to the buffer offset physically above
+     * the current selection end.
+     */
+    public static boolean extendUp(Spannable text, Layout layout) {
+        int end = getSelectionEnd(text);
+        int line = layout.getLineForOffset(end);
+
+        if (line > 0) {
+            int move;
+
+            if (layout.getParagraphDirection(line) ==
+                layout.getParagraphDirection(line - 1)) {
+                float h = layout.getPrimaryHorizontal(end);
+                move = layout.getOffsetForHorizontal(line - 1, h);
+            } else {
+                move = layout.getLineStart(line - 1);
+            }
+
+            extendSelection(text, move);
+            return true;
+        } else if (end != 0) {
+            extendSelection(text, 0);
+            return true;
+        }
+
+        return true;
+    }
+
+    /**
+     * Move the selection end to the buffer offset physically below
+     * the current selection end.
+     */
+    public static boolean extendDown(Spannable text, Layout layout) {
+        int end = getSelectionEnd(text);
+        int line = layout.getLineForOffset(end);
+
+        if (line < layout.getLineCount() - 1) {
+            int move;
+
+            if (layout.getParagraphDirection(line) ==
+                layout.getParagraphDirection(line + 1)) {
+                float h = layout.getPrimaryHorizontal(end);
+                move = layout.getOffsetForHorizontal(line + 1, h);
+            } else {
+                move = layout.getLineStart(line + 1);
+            }
+
+            extendSelection(text, move);
+            return true;
+        } else if (end != text.length()) {
+            extendSelection(text, text.length());
+            return true;
+        }
+
+        return true;
+    }
+
+    /**
+     * Move the selection end to the buffer offset physically to the left of
+     * the current selection end.
+     */
+    public static boolean extendLeft(Spannable text, Layout layout) {
+        int end = getSelectionEnd(text);
+        int to = layout.getOffsetToLeftOf(end);
+
+        if (to != end) {
+            extendSelection(text, to);
+            return true;
+        }
+
+        return true;
+    }
+
+    /**
+     * Move the selection end to the buffer offset physically to the right of
+     * the current selection end.
+     */
+    public static boolean extendRight(Spannable text, Layout layout) {
+        int end = getSelectionEnd(text);
+        int to = layout.getOffsetToRightOf(end);
+
+        if (to != end) {
+            extendSelection(text, to);
+            return true;
+        }
+
+        return true;
+    }
+
+    public static boolean extendToLeftEdge(Spannable text, Layout layout) {
+        int where = findEdge(text, layout, -1);
+        extendSelection(text, where);
+        return true;
+    }
+
+    public static boolean extendToRightEdge(Spannable text, Layout layout) {
+        int where = findEdge(text, layout, 1);
+        extendSelection(text, where);
+        return true;
+    }
+
+    public static boolean moveToLeftEdge(Spannable text, Layout layout) {
+        int where = findEdge(text, layout, -1);
+        setSelection(text, where);
+        return true;
+    }
+
+    public static boolean moveToRightEdge(Spannable text, Layout layout) {
+        int where = findEdge(text, layout, 1);
+        setSelection(text, where);
+        return true;
+    }
+
+    private static int findEdge(Spannable text, Layout layout, int dir) {
+        int pt = getSelectionEnd(text);
+        int line = layout.getLineForOffset(pt);
+        int pdir = layout.getParagraphDirection(line);
+
+        if (dir * pdir < 0) {
+            return layout.getLineStart(line);
+        } else {
+            int end = layout.getLineEnd(line);
+
+            if (line == layout.getLineCount() - 1)
+                return end;
+            else
+                return end - 1;
+        }
+    }
+
+    private static int chooseHorizontal(Layout layout, int direction,
+                                        int off1, int off2) {
+        int line1 = layout.getLineForOffset(off1);
+        int line2 = layout.getLineForOffset(off2);
+
+        if (line1 == line2) {
+            // same line, so it goes by pure physical direction
+
+            float h1 = layout.getPrimaryHorizontal(off1);
+            float h2 = layout.getPrimaryHorizontal(off2);
+
+            if (direction < 0) {
+                // to left
+
+                if (h1 < h2)
+                    return off1;
+                else
+                    return off2;
+            } else {
+                // to right
+
+                if (h1 > h2)
+                    return off1;
+                else
+                    return off2;
+            }
+        } else {
+            // different line, so which line is "left" and which is "right"
+            // depends upon the directionality of the text
+
+            // This only checks at one end, but it's not clear what the
+            // right thing to do is if the ends don't agree.  Even if it
+            // is wrong it should still not be too bad.
+            int line = layout.getLineForOffset(off1);
+            int textdir = layout.getParagraphDirection(line);
+
+            if (textdir == direction)
+                return Math.max(off1, off2);
+            else
+                return Math.min(off1, off2);
+        }
+    }
+
+    private static final class START implements NoCopySpan { };
+    private static final class END implements NoCopySpan { };
+    
+    /*
+     * Public constants
+     */
+
+    public static final Object SELECTION_START = new START();
+    public static final Object SELECTION_END = new END();
+}
diff --git a/core/java/android/text/SpanWatcher.java b/core/java/android/text/SpanWatcher.java
new file mode 100644
index 0000000..01e82c8
--- /dev/null
+++ b/core/java/android/text/SpanWatcher.java
@@ -0,0 +1,42 @@
+/*
+ * 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 android.text;
+
+/**
+ * When an object of this type is attached to a Spannable, its methods
+ * will be called to notify it that other markup objects have been
+ * added, changed, or removed.
+ */
+public interface SpanWatcher extends NoCopySpan {
+    /**
+     * This method is called to notify you that the specified object
+     * has been attached to the specified range of the text.
+     */
+    public void onSpanAdded(Spannable text, Object what, int start, int end);
+    /**
+     * This method is called to notify you that the specified object
+     * has been detached from the specified range of the text.
+     */
+    public void onSpanRemoved(Spannable text, Object what, int start, int end); 
+    /**
+     * This method is called to notify you that the specified object
+     * has been relocated from the range <code>ostart&hellip;oend</code>
+     * to the new range <code>nstart&hellip;nend</code> of the text.
+     */
+    public void onSpanChanged(Spannable text, Object what, int ostart, int oend,
+                              int nstart, int nend);
+}
diff --git a/core/java/android/text/Spannable.java b/core/java/android/text/Spannable.java
new file mode 100644
index 0000000..ae5d356
--- /dev/null
+++ b/core/java/android/text/Spannable.java
@@ -0,0 +1,70 @@
+/*
+ * 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 android.text;
+
+/**
+ * This is the interface for text to which markup objects can be
+ * attached and detached.  Not all Spannable classes have mutable text;
+ * see {@link Editable} for that.
+ */
+public interface Spannable
+extends Spanned
+{
+    /**
+     * Attach the specified markup object to the range <code>start&hellip;end</code>
+     * of the text, or move the object to that range if it was already
+     * attached elsewhere.  See {@link Spanned} for an explanation of
+     * what the flags mean.  The object can be one that has meaning only
+     * within your application, or it can be one that the text system will
+     * use to affect text display or behavior.  Some noteworthy ones are
+     * the subclasses of {@link android.text.style.CharacterStyle} and
+     * {@link android.text.style.ParagraphStyle}, and
+     * {@link android.text.TextWatcher} and
+     * {@link android.text.SpanWatcher}.
+     */
+    public void setSpan(Object what, int start, int end, int flags);
+
+    /**
+     * Remove the specified object from the range of text to which it
+     * was attached, if any.  It is OK to remove an object that was never
+     * attached in the first place.
+     */
+    public void removeSpan(Object what);
+
+    /**
+     * Factory used by TextView to create new Spannables.  You can subclass
+     * it to provide something other than SpannableString.
+     */
+    public static class Factory {
+        private static Spannable.Factory sInstance = new Spannable.Factory();
+
+        /**
+         * Returns the standard Spannable Factory.
+         */ 
+        public static Spannable.Factory getInstance() {
+            return sInstance;
+        }
+
+        /**
+         * Returns a new SpannableString from the specified CharSequence.
+         * You can override this to provide a different kind of Spannable.
+         */
+        public Spannable newSpannable(CharSequence source) {
+            return new SpannableString(source);
+        }
+    }
+}
diff --git a/core/java/android/text/SpannableString.java b/core/java/android/text/SpannableString.java
new file mode 100644
index 0000000..56d0946
--- /dev/null
+++ b/core/java/android/text/SpannableString.java
@@ -0,0 +1,56 @@
+/*
+ * 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 android.text;
+
+
+/**
+ * This is the class for text whose content is immutable but to which
+ * markup objects can be attached and detached.
+ * For mutable text, see {@link SpannableStringBuilder}.
+ */
+public class SpannableString
+extends SpannableStringInternal
+implements CharSequence, GetChars, Spannable
+{
+    public SpannableString(CharSequence source) {
+        super(source, 0, source.length());
+    }
+
+    private SpannableString(CharSequence source, int start, int end) {
+        super(source, start, end);
+    }
+
+    public static SpannableString valueOf(CharSequence source) {
+        if (source instanceof SpannableString) {
+            return (SpannableString) source;
+        } else {
+            return new SpannableString(source);
+        }
+    }
+
+    public void setSpan(Object what, int start, int end, int flags) {
+        super.setSpan(what, start, end, flags);
+    }
+
+    public void removeSpan(Object what) {
+        super.removeSpan(what);
+    }
+
+    public final CharSequence subSequence(int start, int end) {
+        return new SpannableString(this, start, end);
+    }
+}
diff --git a/core/java/android/text/SpannableStringBuilder.java b/core/java/android/text/SpannableStringBuilder.java
new file mode 100644
index 0000000..caaafa1
--- /dev/null
+++ b/core/java/android/text/SpannableStringBuilder.java
@@ -0,0 +1,1140 @@
+/*
+ * 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 android.text;
+
+import com.android.internal.util.ArrayUtils;
+import android.graphics.Paint;
+import android.graphics.Canvas;
+
+import java.lang.reflect.Array;
+
+/**
+ * This is the class for text whose content and markup can both be changed.
+ */
+public class SpannableStringBuilder
+implements CharSequence, GetChars, Spannable, Editable, Appendable,
+           GraphicsOperations
+{
+    /**
+     * Create a new SpannableStringBuilder with empty contents
+     */
+    public SpannableStringBuilder() {
+        this("");
+    }
+
+    /**
+     * Create a new SpannableStringBuilder containing a copy of the
+     * specified text, including its spans if any.
+     */
+    public SpannableStringBuilder(CharSequence text) {
+        this(text, 0, text.length());
+    }
+
+    /**
+     * Create a new SpannableStringBuilder containing a copy of the
+     * specified slice of the specified text, including its spans if any.
+     */
+    public SpannableStringBuilder(CharSequence text, int start, int end) {
+        int srclen = end - start;
+
+        int len = ArrayUtils.idealCharArraySize(srclen + 1);
+        mText = new char[len];
+        mGapStart = srclen;
+        mGapLength = len - srclen;
+
+        TextUtils.getChars(text, start, end, mText, 0);
+
+        mSpanCount = 0;
+        int alloc = ArrayUtils.idealIntArraySize(0);
+        mSpans = new Object[alloc];
+        mSpanStarts = new int[alloc];
+        mSpanEnds = new int[alloc];
+        mSpanFlags = new int[alloc];
+
+        if (text instanceof Spanned) {
+            Spanned sp = (Spanned) text;
+            Object[] spans = sp.getSpans(start, end, Object.class);
+
+            for (int i = 0; i < spans.length; i++) {
+                if (spans[i] instanceof NoCopySpan) {
+                    continue;
+                }
+                
+                int st = sp.getSpanStart(spans[i]) - start;
+                int en = sp.getSpanEnd(spans[i]) - start;
+                int fl = sp.getSpanFlags(spans[i]);
+
+                if (st < 0)
+                    st = 0;
+                if (st > end - start)
+                    st = end - start;
+
+                if (en < 0)
+                    en = 0;
+                if (en > end - start)
+                    en = end - start;
+
+                setSpan(spans[i], st, en, fl);
+            }
+        }
+    }
+
+    public static SpannableStringBuilder valueOf(CharSequence source) {
+        if (source instanceof SpannableStringBuilder) {
+            return (SpannableStringBuilder) source;
+        } else {
+            return new SpannableStringBuilder(source);
+        }
+    }
+
+    /**
+     * Return the char at the specified offset within the buffer.
+     */
+    public char charAt(int where) {
+        int len = length();
+        if (where < 0) {
+            throw new IndexOutOfBoundsException("charAt: " + where + " < 0");
+        } else if (where >= len) {
+            throw new IndexOutOfBoundsException("charAt: " + where +
+                                                " >= length " + len);
+        }
+
+        if (where >= mGapStart)
+            return mText[where + mGapLength];
+        else
+            return mText[where];
+    }
+
+    /**
+     * Return the number of chars in the buffer.
+     */
+    public int length() {
+        return mText.length - mGapLength;
+    }
+
+    private void resizeFor(int size) {
+        int newlen = ArrayUtils.idealCharArraySize(size + 1);
+        char[] newtext = new char[newlen];
+
+        int after = mText.length - (mGapStart + mGapLength);
+
+        System.arraycopy(mText, 0, newtext, 0, mGapStart);
+        System.arraycopy(mText, mText.length - after,
+                         newtext, newlen - after, after);
+
+        for (int i = 0; i < mSpanCount; i++) {
+            if (mSpanStarts[i] > mGapStart)
+                mSpanStarts[i] += newlen - mText.length;
+            if (mSpanEnds[i] > mGapStart)
+                mSpanEnds[i] += newlen - mText.length;
+        }
+
+        int oldlen = mText.length;
+        mText = newtext;
+        mGapLength += mText.length - oldlen;
+
+        if (mGapLength < 1)
+            new Exception("mGapLength < 1").printStackTrace();
+    }
+
+    private void moveGapTo(int where) {
+        if (where == mGapStart)
+            return;
+
+        boolean atend = (where == length());
+
+        if (where < mGapStart) {
+            int overlap = mGapStart - where;
+
+            System.arraycopy(mText, where,
+                             mText, mGapStart + mGapLength - overlap, overlap);
+        } else /* where > mGapStart */ {
+            int overlap = where - mGapStart;
+
+            System.arraycopy(mText, where + mGapLength - overlap,
+                             mText, mGapStart, overlap);
+        }
+
+        // XXX be more clever
+        for (int i = 0; i < mSpanCount; i++) {
+            int start = mSpanStarts[i];
+            int end = mSpanEnds[i];
+
+            if (start > mGapStart)
+                start -= mGapLength;
+            if (start > where)
+                start += mGapLength;
+            else if (start == where) {
+                int flag = (mSpanFlags[i] & START_MASK) >> START_SHIFT;
+
+                if (flag == POINT || (atend && flag == PARAGRAPH))
+                    start += mGapLength;
+            }
+
+            if (end > mGapStart)
+                end -= mGapLength;
+            if (end > where)
+                end += mGapLength;
+            else if (end == where) {
+                int flag = (mSpanFlags[i] & END_MASK);
+
+                if (flag == POINT || (atend && flag == PARAGRAPH))
+                    end += mGapLength;
+            }
+
+            mSpanStarts[i] = start;
+            mSpanEnds[i] = end;
+        }
+
+        mGapStart = where;
+    }
+
+    // Documentation from interface
+    public SpannableStringBuilder insert(int where, CharSequence tb, int start, int end) {
+        return replace(where, where, tb, start, end);
+    }
+
+    // Documentation from interface
+    public SpannableStringBuilder insert(int where, CharSequence tb) {
+        return replace(where, where, tb, 0, tb.length());
+    }
+
+    // Documentation from interface
+    public SpannableStringBuilder delete(int start, int end) {
+        SpannableStringBuilder ret = replace(start, end, "", 0, 0);
+
+        if (mGapLength > 2 * length())
+            resizeFor(length());
+        
+        return ret; // == this
+    }
+
+    // Documentation from interface
+    public void clear() {
+        replace(0, length(), "", 0, 0);
+    }
+    
+    // Documentation from interface
+    public void clearSpans() {
+        for (int i = mSpanCount - 1; i >= 0; i--) {
+            Object what = mSpans[i];
+            int ostart = mSpanStarts[i];
+            int oend = mSpanEnds[i];
+
+            if (ostart > mGapStart)
+                ostart -= mGapLength;
+            if (oend > mGapStart)
+                oend -= mGapLength;
+
+            mSpanCount = i;
+            mSpans[i] = null;
+
+            sendSpanRemoved(what, ostart, oend);
+        }
+    }
+
+    // Documentation from interface
+    public SpannableStringBuilder append(CharSequence text) {
+        int length = length();
+        return replace(length, length, text, 0, text.length());
+    }
+
+    // Documentation from interface
+    public SpannableStringBuilder append(CharSequence text, int start, int end) {
+        int length = length();
+        return replace(length, length, text, start, end);
+    }
+
+    // Documentation from interface
+    public SpannableStringBuilder append(char text) {
+        return append(String.valueOf(text));
+    }
+
+    private int change(int start, int end,
+                       CharSequence tb, int tbstart, int tbend) {
+        return change(true, start, end, tb, tbstart, tbend);
+    }
+
+    private int change(boolean notify, int start, int end,
+                       CharSequence tb, int tbstart, int tbend) {
+        checkRange("replace", start, end);
+        int ret = tbend - tbstart;
+        TextWatcher[] recipients = null;
+
+        if (notify)
+            recipients = sendTextWillChange(start, end - start,
+                                            tbend - tbstart);
+
+        for (int i = mSpanCount - 1; i >= 0; i--) {
+            if ((mSpanFlags[i] & SPAN_PARAGRAPH) == SPAN_PARAGRAPH) {
+                int st = mSpanStarts[i];
+                if (st > mGapStart)
+                    st -= mGapLength;
+
+                int en = mSpanEnds[i];
+                if (en > mGapStart)
+                    en -= mGapLength;
+
+                int ost = st;
+                int oen = en;
+                int clen = length();
+
+                if (st > start && st <= end) {
+                    for (st = end; st < clen; st++)
+                        if (st > end && charAt(st - 1) == '\n')
+                            break;
+                }
+
+                if (en > start && en <= end) {
+                    for (en = end; en < clen; en++)
+                        if (en > end && charAt(en - 1) == '\n')
+                            break;
+                }
+
+                if (st != ost || en != oen)
+                    setSpan(mSpans[i], st, en, mSpanFlags[i]);
+            }
+        }
+
+        moveGapTo(end);
+
+        if (tbend - tbstart >= mGapLength + (end - start))
+            resizeFor(mText.length - mGapLength +
+                      tbend - tbstart - (end - start));
+
+        mGapStart += tbend - tbstart - (end - start);
+        mGapLength -= tbend - tbstart - (end - start);
+
+        if (mGapLength < 1)
+            new Exception("mGapLength < 1").printStackTrace();
+
+        TextUtils.getChars(tb, tbstart, tbend, mText, start);
+
+        if (tb instanceof Spanned) {
+            Spanned sp = (Spanned) tb;
+            Object[] spans = sp.getSpans(tbstart, tbend, Object.class);
+
+            for (int i = 0; i < spans.length; i++) {
+                int st = sp.getSpanStart(spans[i]);
+                int en = sp.getSpanEnd(spans[i]);
+
+                if (st < tbstart)
+                    st = tbstart;
+                if (en > tbend)
+                    en = tbend;
+
+                if (getSpanStart(spans[i]) < 0) {
+                    setSpan(false, spans[i],
+                            st - tbstart + start,
+                            en - tbstart + start,
+                            sp.getSpanFlags(spans[i]));
+                }
+            }
+        }
+
+        // no need for span fixup on pure insertion
+        if (tbend > tbstart && end - start == 0) {
+            if (notify) {
+                sendTextChange(recipients, start, end - start, tbend - tbstart);
+                sendTextHasChanged(recipients);
+            }
+
+            return ret;
+        }
+
+        boolean atend = (mGapStart + mGapLength == mText.length);
+
+        for (int i = mSpanCount - 1; i >= 0; i--) {
+            if (mSpanStarts[i] >= start &&
+                mSpanStarts[i] < mGapStart + mGapLength) {
+                int flag = (mSpanFlags[i] & START_MASK) >> START_SHIFT;
+
+                if (flag == POINT || (flag == PARAGRAPH && atend))
+                    mSpanStarts[i] = mGapStart + mGapLength;
+                else
+                    mSpanStarts[i] = start;
+            }
+
+            if (mSpanEnds[i] >= start &&
+                mSpanEnds[i] < mGapStart + mGapLength) {
+                int flag = (mSpanFlags[i] & END_MASK);
+
+                if (flag == POINT || (flag == PARAGRAPH && atend))
+                    mSpanEnds[i] = mGapStart + mGapLength;
+                else
+                    mSpanEnds[i] = start;
+            }
+
+            // remove 0-length SPAN_EXCLUSIVE_EXCLUSIVE
+            // XXX send notification on removal
+
+            if (mSpanEnds[i] < mSpanStarts[i]) {
+                System.arraycopy(mSpans, i + 1,
+                                 mSpans, i, mSpanCount - (i + 1));
+                System.arraycopy(mSpanStarts, i + 1,
+                                 mSpanStarts, i, mSpanCount - (i + 1));
+                System.arraycopy(mSpanEnds, i + 1,
+                                 mSpanEnds, i, mSpanCount - (i + 1));
+                System.arraycopy(mSpanFlags, i + 1,
+                                 mSpanFlags, i, mSpanCount - (i + 1));
+
+                mSpanCount--;
+            }
+        }
+
+        if (notify) {
+            sendTextChange(recipients, start, end - start, tbend - tbstart);
+            sendTextHasChanged(recipients);
+        }
+
+        return ret;
+    }
+
+    // Documentation from interface
+    public SpannableStringBuilder replace(int start, int end, CharSequence tb) {
+        return replace(start, end, tb, 0, tb.length());
+    }
+
+    // Documentation from interface
+    public SpannableStringBuilder replace(final int start, final int end,
+                        CharSequence tb, int tbstart, int tbend) {
+        int filtercount = mFilters.length;
+        for (int i = 0; i < filtercount; i++) {
+            CharSequence repl = mFilters[i].filter(tb, tbstart, tbend,
+                                                   this, start, end);
+
+            if (repl != null) {
+                tb = repl;
+                tbstart = 0;
+                tbend = repl.length();
+            }
+        }
+
+        if (end == start && tbstart == tbend) {
+            return this;
+        }
+
+        if (end == start || tbstart == tbend) {
+            change(start, end, tb, tbstart, tbend);
+        } else {
+            int selstart = Selection.getSelectionStart(this);
+            int selend = Selection.getSelectionEnd(this);
+
+            // XXX just make the span fixups in change() do the right thing
+            // instead of this madness!
+
+            checkRange("replace", start, end);
+            moveGapTo(end);
+            TextWatcher[] recipients;
+
+            recipients = sendTextWillChange(start, end - start,
+                                            tbend - tbstart);
+
+            int origlen = end - start;
+
+            if (mGapLength < 2)
+                resizeFor(length() + 1);
+
+            for (int i = mSpanCount - 1; i >= 0; i--) {
+                if (mSpanStarts[i] == mGapStart)
+                    mSpanStarts[i]++;
+
+                if (mSpanEnds[i] == mGapStart)
+                    mSpanEnds[i]++;
+            }
+
+            mText[mGapStart] = ' ';
+            mGapStart++;
+            mGapLength--;
+
+            if (mGapLength < 1)
+                new Exception("mGapLength < 1").printStackTrace();
+
+            int oldlen = (end + 1) - start;
+
+            int inserted = change(false, start + 1, start + 1,
+                                  tb, tbstart, tbend);
+            change(false, start, start + 1, "", 0, 0);
+            change(false, start + inserted, start + inserted + oldlen - 1,
+                   "", 0, 0);
+
+            /*
+             * Special case to keep the cursor in the same position
+             * if it was somewhere in the middle of the replaced region.
+             * If it was at the start or the end or crossing the whole
+             * replacement, it should already be where it belongs.
+             * TODO: Is there some more general mechanism that could
+             * accomplish this?
+             */
+            if (selstart > start && selstart < end) {
+                long off = selstart - start;
+
+                off = off * inserted / (end - start);
+                selstart = (int) off + start;
+
+                setSpan(false, Selection.SELECTION_START, selstart, selstart,
+                        Spanned.SPAN_POINT_POINT);
+            }
+            if (selend > start && selend < end) {
+                long off = selend - start;
+
+                off = off * inserted / (end - start);
+                selend = (int) off + start;
+
+                setSpan(false, Selection.SELECTION_END, selend, selend,
+                        Spanned.SPAN_POINT_POINT);
+            }
+
+            sendTextChange(recipients, start, origlen, inserted);
+            sendTextHasChanged(recipients);
+        }
+        return this; 
+    }
+
+    /**
+     * Mark the specified range of text with the specified object.
+     * The flags determine how the span will behave when text is
+     * inserted at the start or end of the span's range.
+     */
+    public void setSpan(Object what, int start, int end, int flags) {
+        setSpan(true, what, start, end, flags);
+    }
+
+    private void setSpan(boolean send,
+                         Object what, int start, int end, int flags) {
+        int nstart = start;
+        int nend = end;
+
+        checkRange("setSpan", start, end);
+
+        if ((flags & START_MASK) == (PARAGRAPH << START_SHIFT)) {
+            if (start != 0 && start != length()) {
+                char c = charAt(start - 1);
+
+                if (c != '\n')
+                    throw new RuntimeException(
+                            "PARAGRAPH span must start at paragraph boundary");
+            }
+        }
+
+        if ((flags & END_MASK) == PARAGRAPH) {
+            if (end != 0 && end != length()) {
+                char c = charAt(end - 1);
+
+                if (c != '\n')
+                    throw new RuntimeException(
+                            "PARAGRAPH span must end at paragraph boundary");
+            }
+        }
+
+        if (start > mGapStart)
+            start += mGapLength;
+        else if (start == mGapStart) {
+            int flag = (flags & START_MASK) >> START_SHIFT;
+
+            if (flag == POINT || (flag == PARAGRAPH && start == length()))
+                start += mGapLength;
+        }
+
+        if (end > mGapStart)
+            end += mGapLength;
+        else if (end == mGapStart) {
+            int flag = (flags & END_MASK);
+
+            if (flag == POINT || (flag == PARAGRAPH && end == length()))
+                end += mGapLength;
+        }
+
+        int count = mSpanCount;
+        Object[] spans = mSpans;
+
+        for (int i = 0; i < count; i++) {
+            if (spans[i] == what) {
+                int ostart = mSpanStarts[i];
+                int oend = mSpanEnds[i];
+
+                if (ostart > mGapStart)
+                    ostart -= mGapLength;
+                if (oend > mGapStart)
+                    oend -= mGapLength;
+
+                mSpanStarts[i] = start;
+                mSpanEnds[i] = end;
+                mSpanFlags[i] = flags;
+
+                if (send) 
+                    sendSpanChanged(what, ostart, oend, nstart, nend);
+
+                return;
+            }
+        }
+
+        if (mSpanCount + 1 >= mSpans.length) {
+            int newsize = ArrayUtils.idealIntArraySize(mSpanCount + 1);
+            Object[] newspans = new Object[newsize];
+            int[] newspanstarts = new int[newsize];
+            int[] newspanends = new int[newsize];
+            int[] newspanflags = new int[newsize];
+
+            System.arraycopy(mSpans, 0, newspans, 0, mSpanCount);
+            System.arraycopy(mSpanStarts, 0, newspanstarts, 0, mSpanCount);
+            System.arraycopy(mSpanEnds, 0, newspanends, 0, mSpanCount);
+            System.arraycopy(mSpanFlags, 0, newspanflags, 0, mSpanCount);
+
+            mSpans = newspans;
+            mSpanStarts = newspanstarts;
+            mSpanEnds = newspanends;
+            mSpanFlags = newspanflags;
+        }
+
+        mSpans[mSpanCount] = what;
+        mSpanStarts[mSpanCount] = start;
+        mSpanEnds[mSpanCount] = end;
+        mSpanFlags[mSpanCount] = flags;
+        mSpanCount++;
+
+        if (send)
+            sendSpanAdded(what, nstart, nend);
+    }
+
+    /**
+     * Remove the specified markup object from the buffer.
+     */
+    public void removeSpan(Object what) {
+        for (int i = mSpanCount - 1; i >= 0; i--) {
+            if (mSpans[i] == what) {
+                int ostart = mSpanStarts[i];
+                int oend = mSpanEnds[i];
+
+                if (ostart > mGapStart)
+                    ostart -= mGapLength;
+                if (oend > mGapStart)
+                    oend -= mGapLength;
+
+                int count = mSpanCount - (i + 1);
+
+                System.arraycopy(mSpans, i + 1, mSpans, i, count);
+                System.arraycopy(mSpanStarts, i + 1, mSpanStarts, i, count);
+                System.arraycopy(mSpanEnds, i + 1, mSpanEnds, i, count);
+                System.arraycopy(mSpanFlags, i + 1, mSpanFlags, i, count);
+
+                mSpanCount--;
+                mSpans[mSpanCount] = null;
+
+                sendSpanRemoved(what, ostart, oend);
+                return;
+            }
+        }
+    }
+
+    /**
+     * Return the buffer offset of the beginning of the specified
+     * markup object, or -1 if it is not attached to this buffer.
+     */
+    public int getSpanStart(Object what) {
+        int count = mSpanCount;
+        Object[] spans = mSpans;
+
+        for (int i = count - 1; i >= 0; i--) {
+            if (spans[i] == what) {
+                int where = mSpanStarts[i];
+
+                if (where > mGapStart)
+                    where -= mGapLength;
+
+                return where;
+            }
+        }
+
+        return -1;
+    }
+
+    /**
+     * Return the buffer offset of the end of the specified
+     * markup object, or -1 if it is not attached to this buffer.
+     */
+    public int getSpanEnd(Object what) {
+        int count = mSpanCount;
+        Object[] spans = mSpans;
+
+        for (int i = count - 1; i >= 0; i--) {
+            if (spans[i] == what) {
+                int where = mSpanEnds[i];
+
+                if (where > mGapStart)
+                    where -= mGapLength;
+
+                return where;
+            }
+        }
+
+        return -1;
+    }
+
+    /**
+     * Return the flags of the end of the specified
+     * markup object, or 0 if it is not attached to this buffer.
+     */
+    public int getSpanFlags(Object what) {
+        int count = mSpanCount;
+        Object[] spans = mSpans;
+
+        for (int i = count - 1; i >= 0; i--) {
+            if (spans[i] == what) {
+                return mSpanFlags[i];
+            }
+        }
+
+        return 0; 
+    }
+
+    /**
+     * Return an array of the spans of the specified type that overlap
+     * the specified range of the buffer.  The kind may be Object.class to get
+     * a list of all the spans regardless of type.
+     */
+    public <T> T[] getSpans(int queryStart, int queryEnd, Class<T> kind) {
+        int spanCount = mSpanCount;
+        Object[] spans = mSpans;
+        int[] starts = mSpanStarts;
+        int[] ends = mSpanEnds;
+        int[] flags = mSpanFlags;
+        int gapstart = mGapStart;
+        int gaplen = mGapLength;
+
+        int count = 0;
+        Object[] ret = null;
+        Object ret1 = null;
+
+        for (int i = 0; i < spanCount; i++) {
+            int spanStart = starts[i];
+            int spanEnd = ends[i];
+
+            if (spanStart > gapstart) {
+                spanStart -= gaplen;
+            }
+            if (spanEnd > gapstart) {
+                spanEnd -= gaplen;
+            }
+
+            if (spanStart > queryEnd) {
+                continue;
+            }
+            if (spanEnd < queryStart) {
+                continue;
+            }
+
+            if (spanStart != spanEnd && queryStart != queryEnd) {
+                if (spanStart == queryEnd)
+                    continue;
+                if (spanEnd == queryStart)
+                    continue;
+            }
+
+            if (kind != null && !kind.isInstance(spans[i])) {
+                continue;
+            }
+
+            if (count == 0) {
+                ret1 = spans[i];
+                count++;
+            } else {
+                if (count == 1) {
+                    ret = (Object[]) Array.newInstance(kind, spanCount - i + 1);
+                    ret[0] = ret1;
+                }
+
+                int prio = flags[i] & SPAN_PRIORITY;
+                if (prio != 0) {
+                    int j;
+
+                    for (j = 0; j < count; j++) {
+                        int p = getSpanFlags(ret[j]) & SPAN_PRIORITY;
+
+                        if (prio > p) {
+                            break;
+                        }
+                    }
+
+                    System.arraycopy(ret, j, ret, j + 1, count - j);
+                    ret[j] = spans[i];
+                    count++;
+                } else {
+                    ret[count++] = spans[i];
+                }
+            }
+        }
+
+        if (count == 0) {
+            return (T[]) ArrayUtils.emptyArray(kind);
+        }
+        if (count == 1) {
+            ret = (Object[]) Array.newInstance(kind, 1);
+            ret[0] = ret1;
+            return (T[]) ret;
+        }
+        if (count == ret.length) {
+            return (T[]) ret;
+        }
+
+        Object[] nret = (Object[]) Array.newInstance(kind, count);
+        System.arraycopy(ret, 0, nret, 0, count);
+        return (T[]) nret;
+    }
+
+    /**
+     * Return the next offset after <code>start</code> but less than or
+     * equal to <code>limit</code> where a span of the specified type
+     * begins or ends.
+     */
+    public int nextSpanTransition(int start, int limit, Class kind) {
+        int count = mSpanCount;
+        Object[] spans = mSpans;
+        int[] starts = mSpanStarts;
+        int[] ends = mSpanEnds;
+        int gapstart = mGapStart;
+        int gaplen = mGapLength;
+
+        if (kind == null) {
+            kind = Object.class;
+        }
+
+        for (int i = 0; i < count; i++) {
+            int st = starts[i];
+            int en = ends[i];
+
+            if (st > gapstart)
+                st -= gaplen;
+            if (en > gapstart)
+                en -= gaplen;
+
+            if (st > start && st < limit && kind.isInstance(spans[i]))
+                limit = st;
+            if (en > start && en < limit && kind.isInstance(spans[i]))
+                limit = en;
+        }
+
+        return limit;
+    }
+
+    /**
+     * Return a new CharSequence containing a copy of the specified
+     * range of this buffer, including the overlapping spans.
+     */
+    public CharSequence subSequence(int start, int end) {
+        return new SpannableStringBuilder(this, start, end);
+    }
+
+    /**
+     * Copy the specified range of chars from this buffer into the
+     * specified array, beginning at the specified offset.
+     */
+    public void getChars(int start, int end, char[] dest, int destoff) {
+        checkRange("getChars", start, end);
+
+        if (end <= mGapStart) {
+            System.arraycopy(mText, start, dest, destoff, end - start);
+        } else if (start >= mGapStart) {
+            System.arraycopy(mText, start + mGapLength,
+                             dest, destoff, end - start);
+        } else {
+            System.arraycopy(mText, start, dest, destoff, mGapStart - start);
+            System.arraycopy(mText, mGapStart + mGapLength,
+                             dest, destoff + (mGapStart - start),
+                             end - mGapStart);
+        }
+    }
+
+    /**
+     * Return a String containing a copy of the chars in this buffer.
+     */
+    public String toString() {
+        int len = length();
+        char[] buf = new char[len];
+
+        getChars(0, len, buf, 0);
+        return new String(buf);
+    }
+
+    private TextWatcher[] sendTextWillChange(int start, int before, int after) {
+        TextWatcher[] recip = getSpans(start, start + before, TextWatcher.class);
+        int n = recip.length;
+
+        for (int i = 0; i < n; i++) {
+            recip[i].beforeTextChanged(this, start, before, after);
+        }
+
+        return recip;
+    }
+
+    private void sendTextChange(TextWatcher[] recip, int start, int before,
+                                int after) {
+        int n = recip.length;
+
+        for (int i = 0; i < n; i++) {
+            recip[i].onTextChanged(this, start, before, after);
+        }
+    }
+
+    private void sendTextHasChanged(TextWatcher[] recip) {
+        int n = recip.length;
+
+        for (int i = 0; i < n; i++) {
+            recip[i].afterTextChanged(this);
+        }
+    }
+
+    private void sendSpanAdded(Object what, int start, int end) {
+        SpanWatcher[] recip = getSpans(start, end, SpanWatcher.class);
+        int n = recip.length;
+
+        for (int i = 0; i < n; i++) {
+            recip[i].onSpanAdded(this, what, start, end);
+        }
+    }
+
+    private void sendSpanRemoved(Object what, int start, int end) {
+        SpanWatcher[] recip = getSpans(start, end, SpanWatcher.class);
+        int n = recip.length;
+
+        for (int i = 0; i < n; i++) {
+            recip[i].onSpanRemoved(this, what, start, end);
+        }
+    }
+
+    private void sendSpanChanged(Object what, int s, int e, int st, int en) {
+        SpanWatcher[] recip = getSpans(Math.min(s, st), Math.max(e, en),
+                                  SpanWatcher.class);
+        int n = recip.length;
+
+        for (int i = 0; i < n; i++) {
+            recip[i].onSpanChanged(this, what, s, e, st, en);
+        }
+    }
+
+    private static String region(int start, int end) {
+        return "(" + start + " ... " + end + ")";
+    }
+
+    private void checkRange(final String operation, int start, int end) {
+        if (end < start) {
+            throw new IndexOutOfBoundsException(operation + " " +
+                                                region(start, end) +
+                                                " has end before start");
+        }
+
+        int len = length();
+
+        if (start > len || end > len) {
+            throw new IndexOutOfBoundsException(operation + " " +
+                                                region(start, end) +
+                                                " ends beyond length " + len);
+        }
+
+        if (start < 0 || end < 0) {
+            throw new IndexOutOfBoundsException(operation + " " +
+                                                region(start, end) +
+                                                " starts before 0");
+        }
+    }
+
+    private boolean isprint(char c) { // XXX
+        if (c >= ' ' && c <= '~')
+            return true;
+        else
+            return false;
+    }
+
+/*
+    private static final int startFlag(int flag) {
+        return (flag >> 4) & 0x0F;
+    }
+
+    private static final int endFlag(int flag) {
+        return flag & 0x0F;
+    }
+
+    public void dump() { // XXX
+        for (int i = 0; i < mGapStart; i++) {
+            System.out.print('|');
+            System.out.print(' ');
+            System.out.print(isprint(mText[i]) ? mText[i] : '.');
+            System.out.print(' ');
+        }
+
+        for (int i = mGapStart; i < mGapStart + mGapLength; i++) {
+            System.out.print('|');
+            System.out.print('(');
+            System.out.print(isprint(mText[i]) ? mText[i] : '.');
+            System.out.print(')');
+        }
+
+        for (int i = mGapStart + mGapLength; i < mText.length; i++) {
+            System.out.print('|');
+            System.out.print(' ');
+            System.out.print(isprint(mText[i]) ? mText[i] : '.');
+            System.out.print(' ');
+        }
+
+        System.out.print('\n');
+
+        for (int i = 0; i < mText.length + 1; i++) {
+            int found = 0;
+            int wfound = 0;
+
+            for (int j = 0; j < mSpanCount; j++) {
+                if (mSpanStarts[j] == i) {
+                    found = 1;
+                    wfound = j;
+                    break;
+                }
+
+                if (mSpanEnds[j] == i) {
+                    found = 2;
+                    wfound = j;
+                    break;
+                }
+            }
+
+            if (found == 1) {
+                if (startFlag(mSpanFlags[wfound]) == MARK)
+                    System.out.print("(   ");
+                if (startFlag(mSpanFlags[wfound]) == PARAGRAPH)
+                    System.out.print("<   ");
+                else
+                    System.out.print("[   ");
+            } else if (found == 2) {
+                if (endFlag(mSpanFlags[wfound]) == POINT)
+                    System.out.print(")   ");
+                if (endFlag(mSpanFlags[wfound]) == PARAGRAPH)
+                    System.out.print(">   ");
+                else
+                    System.out.print("]   ");
+            } else {
+                System.out.print("    ");
+            }
+        }
+
+        System.out.print("\n");
+    }
+*/
+
+    /**
+     * Don't call this yourself -- exists for Canvas to use internally.
+     * {@hide}
+     */
+    public void drawText(Canvas c, int start, int end,
+                         float x, float y, Paint p) {
+        checkRange("drawText", start, end);
+
+        if (end <= mGapStart) {
+            c.drawText(mText, start, end - start, x, y, p);
+        } else if (start >= mGapStart) {
+            c.drawText(mText, start + mGapLength, end - start, x, y, p);
+        } else {
+            char[] buf = TextUtils.obtain(end - start);
+
+            getChars(start, end, buf, 0);
+            c.drawText(buf, 0, end - start, x, y, p);
+            TextUtils.recycle(buf);
+        }
+    }
+
+    /**
+     * Don't call this yourself -- exists for Paint to use internally.
+     * {@hide}
+     */
+    public float measureText(int start, int end, Paint p) {
+        checkRange("measureText", start, end);
+
+        float ret;
+
+        if (end <= mGapStart) {
+            ret = p.measureText(mText, start, end - start);
+        } else if (start >= mGapStart) {
+            ret = p.measureText(mText, start + mGapLength, end - start);
+        } else {
+            char[] buf = TextUtils.obtain(end - start);
+
+            getChars(start, end, buf, 0);
+            ret = p.measureText(buf, 0, end - start);
+            TextUtils.recycle(buf);
+        }
+
+        return ret;
+    }
+
+    /**
+     * Don't call this yourself -- exists for Paint to use internally.
+     * {@hide}
+     */
+    public int getTextWidths(int start, int end, float[] widths, Paint p) {
+        checkRange("getTextWidths", start, end);
+
+        int ret;
+
+        if (end <= mGapStart) {
+            ret = p.getTextWidths(mText, start, end - start, widths);
+        } else if (start >= mGapStart) {
+            ret = p.getTextWidths(mText, start + mGapLength, end - start,
+                                  widths);
+        } else {
+            char[] buf = TextUtils.obtain(end - start);
+
+            getChars(start, end, buf, 0);
+            ret = p.getTextWidths(buf, 0, end - start, widths);
+            TextUtils.recycle(buf);
+        }
+
+        return ret;
+    }
+
+    // Documentation from interface
+    public void setFilters(InputFilter[] filters) {
+        if (filters == null) {
+            throw new IllegalArgumentException();
+        }
+
+        mFilters = filters;
+    }
+
+    // Documentation from interface
+    public InputFilter[] getFilters() {
+        return mFilters;
+    }
+
+    private static final InputFilter[] NO_FILTERS = new InputFilter[0];
+    private InputFilter[] mFilters = NO_FILTERS;
+
+    private char[] mText;
+    private int mGapStart;
+    private int mGapLength;
+
+    private Object[] mSpans;
+    private int[] mSpanStarts;
+    private int[] mSpanEnds;
+    private int[] mSpanFlags;
+    private int mSpanCount;
+
+    private static final int MARK = 1;
+    private static final int POINT = 2;
+    private static final int PARAGRAPH = 3;
+
+    private static final int START_MASK = 0xF0;
+    private static final int END_MASK = 0x0F;
+    private static final int START_SHIFT = 4;
+}
diff --git a/core/java/android/text/SpannableStringInternal.java b/core/java/android/text/SpannableStringInternal.java
new file mode 100644
index 0000000..0412285
--- /dev/null
+++ b/core/java/android/text/SpannableStringInternal.java
@@ -0,0 +1,372 @@
+/*
+ * 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 android.text;
+
+import com.android.internal.util.ArrayUtils;
+
+import java.lang.reflect.Array;
+
+/* package */ abstract class SpannableStringInternal
+{
+    /* package */ SpannableStringInternal(CharSequence source,
+                                          int start, int end) {
+        if (start == 0 && end == source.length())
+            mText = source.toString();
+        else
+            mText = source.toString().substring(start, end);
+
+        int initial = ArrayUtils.idealIntArraySize(0);
+        mSpans = new Object[initial];
+        mSpanData = new int[initial * 3];
+
+        if (source instanceof Spanned) {
+            Spanned sp = (Spanned) source;
+            Object[] spans = sp.getSpans(start, end, Object.class);
+
+            for (int i = 0; i < spans.length; i++) {
+                int st = sp.getSpanStart(spans[i]);
+                int en = sp.getSpanEnd(spans[i]);
+                int fl = sp.getSpanFlags(spans[i]);
+
+                if (st < start)
+                    st = start;
+                if (en > end)
+                    en = end;
+
+                setSpan(spans[i], st - start, en - start, fl);
+            }
+        }
+    }
+
+    public final int length() {
+        return mText.length();
+    }
+
+    public final char charAt(int i) {
+        return mText.charAt(i);
+    }
+
+    public final String toString() {
+        return mText;
+    }
+
+    /* subclasses must do subSequence() to preserve type */
+
+    public final void getChars(int start, int end, char[] dest, int off) {
+        mText.getChars(start, end, dest, off);
+    }
+
+    /* package */ void setSpan(Object what, int start, int end, int flags) {
+        int nstart = start;
+        int nend = end;
+
+        checkRange("setSpan", start, end);
+
+        if ((flags & Spannable.SPAN_PARAGRAPH) == Spannable.SPAN_PARAGRAPH) {
+            if (start != 0 && start != length()) {
+                char c = charAt(start - 1);
+
+                if (c != '\n')
+                    throw new RuntimeException(
+                            "PARAGRAPH span must start at paragraph boundary" +
+                            " (" + start + " follows " + c + ")");
+            }
+
+            if (end != 0 && end != length()) {
+                char c = charAt(end - 1);
+
+                if (c != '\n')
+                    throw new RuntimeException(
+                            "PARAGRAPH span must end at paragraph boundary" +
+                            " (" + end + " follows " + c + ")");
+            }
+        }
+
+        int count = mSpanCount;
+        Object[] spans = mSpans;
+        int[] data = mSpanData;
+
+        for (int i = 0; i < count; i++) {
+            if (spans[i] == what) {
+                int ostart = data[i * COLUMNS + START];
+                int oend = data[i * COLUMNS + END];
+
+                data[i * COLUMNS + START] = start;
+                data[i * COLUMNS + END] = end;
+                data[i * COLUMNS + FLAGS] = flags;
+
+                sendSpanChanged(what, ostart, oend, nstart, nend);
+                return;
+            }
+        }
+
+        if (mSpanCount + 1 >= mSpans.length) {
+            int newsize = ArrayUtils.idealIntArraySize(mSpanCount + 1);
+            Object[] newtags = new Object[newsize];
+            int[] newdata = new int[newsize * 3];
+
+            System.arraycopy(mSpans, 0, newtags, 0, mSpanCount);
+            System.arraycopy(mSpanData, 0, newdata, 0, mSpanCount * 3);
+
+            mSpans = newtags;
+            mSpanData = newdata;
+        }
+
+        mSpans[mSpanCount] = what;
+        mSpanData[mSpanCount * COLUMNS + START] = start;
+        mSpanData[mSpanCount * COLUMNS + END] = end;
+        mSpanData[mSpanCount * COLUMNS + FLAGS] = flags;
+        mSpanCount++;
+
+        if (this instanceof Spannable)
+            sendSpanAdded(what, nstart, nend);
+    }
+
+    /* package */ void removeSpan(Object what) {
+        int count = mSpanCount;
+        Object[] spans = mSpans;
+        int[] data = mSpanData;
+
+        for (int i = count - 1; i >= 0; i--) {
+            if (spans[i] == what) {
+                int ostart = data[i * COLUMNS + START];
+                int oend = data[i * COLUMNS + END];
+
+                int c = count - (i + 1);
+
+                System.arraycopy(spans, i + 1, spans, i, c);
+                System.arraycopy(data, (i + 1) * COLUMNS,
+                                 data, i * COLUMNS, c * COLUMNS);
+
+                mSpanCount--;
+
+                sendSpanRemoved(what, ostart, oend);
+                return;
+            }
+        }
+    }
+
+    public int getSpanStart(Object what) {
+        int count = mSpanCount;
+        Object[] spans = mSpans;
+        int[] data = mSpanData;
+
+        for (int i = count - 1; i >= 0; i--) {
+            if (spans[i] == what) {
+                return data[i * COLUMNS + START];
+            }
+        }
+
+        return -1;
+    }
+
+    public int getSpanEnd(Object what) {
+        int count = mSpanCount;
+        Object[] spans = mSpans;
+        int[] data = mSpanData;
+
+        for (int i = count - 1; i >= 0; i--) {
+            if (spans[i] == what) {
+                return data[i * COLUMNS + END];
+            }
+        }
+
+        return -1;
+    }
+
+    public int getSpanFlags(Object what) {
+        int count = mSpanCount;
+        Object[] spans = mSpans;
+        int[] data = mSpanData;
+
+        for (int i = count - 1; i >= 0; i--) {
+            if (spans[i] == what) {
+                return data[i * COLUMNS + FLAGS];
+            }
+        }
+
+        return 0; 
+    }
+
+    public <T> T[] getSpans(int queryStart, int queryEnd, Class<T> kind) {
+        int count = 0;
+
+        int spanCount = mSpanCount;
+        Object[] spans = mSpans;
+        int[] data = mSpanData;
+        Object[] ret = null;
+        Object ret1 = null;
+
+        for (int i = 0; i < spanCount; i++) {
+            int spanStart = data[i * COLUMNS + START];
+            int spanEnd = data[i * COLUMNS + END];
+
+            if (spanStart > queryEnd) {
+                continue;
+            }
+            if (spanEnd < queryStart) {
+                continue;
+            }
+
+            if (spanStart != spanEnd && queryStart != queryEnd) {
+                if (spanStart == queryEnd) {
+                    continue;
+                }
+                if (spanEnd == queryStart) {
+                    continue;
+                }
+            }
+
+            if (kind != null && !kind.isInstance(spans[i])) {
+                continue;
+            }
+
+            if (count == 0) {
+                ret1 = spans[i];
+                count++;
+            } else {
+                if (count == 1) {
+                    ret = (Object[]) Array.newInstance(kind, spanCount - i + 1);
+                    ret[0] = ret1;
+                }
+
+                int prio = data[i * COLUMNS + FLAGS] & Spanned.SPAN_PRIORITY;
+                if (prio != 0) {
+                    int j;
+
+                    for (j = 0; j < count; j++) {
+                        int p = getSpanFlags(ret[j]) & Spanned.SPAN_PRIORITY;
+
+                        if (prio > p) {
+                            break;
+                        }
+                    }
+
+                    System.arraycopy(ret, j, ret, j + 1, count - j);
+                    ret[j] = spans[i];
+                    count++;
+                } else {
+                    ret[count++] = spans[i];
+                }
+            }
+        }
+
+        if (count == 0) {
+            return (T[]) ArrayUtils.emptyArray(kind);
+        }
+        if (count == 1) {
+            ret = (Object[]) Array.newInstance(kind, 1);
+            ret[0] = ret1;
+            return (T[]) ret;
+        }
+        if (count == ret.length) {
+            return (T[]) ret;
+        }
+
+        Object[] nret = (Object[]) Array.newInstance(kind, count);
+        System.arraycopy(ret, 0, nret, 0, count);
+        return (T[]) nret;
+    }
+
+    public int nextSpanTransition(int start, int limit, Class kind) {
+        int count = mSpanCount;
+        Object[] spans = mSpans;
+        int[] data = mSpanData;
+
+        if (kind == null) {
+            kind = Object.class;
+        }
+
+        for (int i = 0; i < count; i++) {
+            int st = data[i * COLUMNS + START];
+            int en = data[i * COLUMNS + END];
+
+            if (st > start && st < limit && kind.isInstance(spans[i]))
+                limit = st;
+            if (en > start && en < limit && kind.isInstance(spans[i]))
+                limit = en;
+        }
+
+        return limit;
+    }
+
+    private void sendSpanAdded(Object what, int start, int end) {
+        SpanWatcher[] recip = getSpans(start, end, SpanWatcher.class);
+        int n = recip.length;
+
+        for (int i = 0; i < n; i++) {
+            recip[i].onSpanAdded((Spannable) this, what, start, end);
+        }
+    }
+
+    private void sendSpanRemoved(Object what, int start, int end) {
+        SpanWatcher[] recip = getSpans(start, end, SpanWatcher.class);
+        int n = recip.length;
+
+        for (int i = 0; i < n; i++) {
+            recip[i].onSpanRemoved((Spannable) this, what, start, end);
+        }
+    }
+
+    private void sendSpanChanged(Object what, int s, int e, int st, int en) {
+        SpanWatcher[] recip = getSpans(Math.min(s, st), Math.max(e, en),
+                                       SpanWatcher.class);
+        int n = recip.length;
+
+        for (int i = 0; i < n; i++) {
+            recip[i].onSpanChanged((Spannable) this, what, s, e, st, en);
+        }
+    }
+
+    private static String region(int start, int end) {
+        return "(" + start + " ... " + end + ")";
+    }
+
+    private void checkRange(final String operation, int start, int end) {
+        if (end < start) {
+            throw new IndexOutOfBoundsException(operation + " " +
+                                                region(start, end) +
+                                                " has end before start");
+        }
+
+        int len = length();
+
+        if (start > len || end > len) {
+            throw new IndexOutOfBoundsException(operation + " " +
+                                                region(start, end) +
+                                                " ends beyond length " + len);
+        }
+
+        if (start < 0 || end < 0) {
+            throw new IndexOutOfBoundsException(operation + " " +
+                                                region(start, end) +
+                                                " starts before 0");
+        }
+    }
+
+    private String mText;
+    private Object[] mSpans;
+    private int[] mSpanData;
+    private int mSpanCount;
+
+    /* package */ static final Object[] EMPTY = new Object[0];
+
+    private static final int START = 0;
+    private static final int END = 1;
+    private static final int FLAGS = 2;
+    private static final int COLUMNS = 3;
+}
diff --git a/core/java/android/text/Spanned.java b/core/java/android/text/Spanned.java
new file mode 100644
index 0000000..154497d
--- /dev/null
+++ b/core/java/android/text/Spanned.java
@@ -0,0 +1,182 @@
+/*
+ * 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 android.text;
+
+/**
+ * This is the interface for text that has markup objects attached to
+ * ranges of it.  Not all text classes have mutable markup or text;
+ * see {@link Spannable} for mutable markup and {@link Editable} for
+ * mutable text.
+ */
+public interface Spanned
+extends CharSequence
+{
+    /**
+     * Bitmask of bits that are relevent for controlling point/mark behavior
+     * of spans.
+     */
+    public static final int SPAN_POINT_MARK_MASK = 0x33;
+    
+    /**
+     * 0-length spans with type SPAN_MARK_MARK behave like text marks:
+     * they remain at their original offset when text is inserted
+     * at that offset.
+     */
+    public static final int SPAN_MARK_MARK =   0x11;
+    /**
+     * SPAN_MARK_POINT is a synonym for {@link #SPAN_INCLUSIVE_INCLUSIVE}.
+     */
+    public static final int SPAN_MARK_POINT =  0x12;
+    /**
+     * SPAN_POINT_MARK is a synonym for {@link #SPAN_EXCLUSIVE_EXCLUSIVE}.
+     */
+    public static final int SPAN_POINT_MARK =  0x21;
+
+    /**
+     * 0-length spans with type SPAN_POINT_POINT behave like cursors:
+     * they are pushed forward by the length of the insertion when text
+     * is inserted at their offset.
+     */
+    public static final int SPAN_POINT_POINT = 0x22;
+
+    /**
+     * SPAN_PARAGRAPH behaves like SPAN_INCLUSIVE_EXCLUSIVE
+     * (SPAN_MARK_MARK), except that if either end of the span is
+     * at the end of the buffer, that end behaves like _POINT
+     * instead (so SPAN_INCLUSIVE_INCLUSIVE if it starts in the
+     * middle and ends at the end, or SPAN_EXCLUSIVE_INCLUSIVE
+     * if it both starts and ends at the end).
+     * <p>
+     * Its endpoints must be the start or end of the buffer or
+     * immediately after a \n character, and if the \n
+     * that anchors it is deleted, the endpoint is pulled to the
+     * next \n that follows in the buffer (or to the end of
+     * the buffer).
+     */
+    public static final int SPAN_PARAGRAPH =   0x33;
+
+    /**
+     * Non-0-length spans of type SPAN_INCLUSIVE_EXCLUSIVE expand
+     * to include text inserted at their starting point but not at their
+     * ending point.  When 0-length, they behave like marks.
+     */
+    public static final int SPAN_INCLUSIVE_EXCLUSIVE = SPAN_MARK_MARK;
+
+    /**
+     * Spans of type SPAN_INCLUSIVE_INCLUSIVE expand
+     * to include text inserted at either their starting or ending point.
+     */
+    public static final int SPAN_INCLUSIVE_INCLUSIVE = SPAN_MARK_POINT;
+
+    /**
+     * Spans of type SPAN_EXCLUSIVE_EXCLUSIVE do not expand
+     * to include text inserted at either their starting or ending point.
+     * They can never have a length of 0 and are automatically removed
+     * from the buffer if all the text they cover is removed.
+     */
+    public static final int SPAN_EXCLUSIVE_EXCLUSIVE = SPAN_POINT_MARK;
+
+    /**
+     * Non-0-length spans of type SPAN_INCLUSIVE_EXCLUSIVE expand
+     * to include text inserted at their ending point but not at their
+     * starting point.  When 0-length, they behave like points.
+     */
+    public static final int SPAN_EXCLUSIVE_INCLUSIVE = SPAN_POINT_POINT;
+
+    /**
+     * This flag is set on spans that are being used to apply temporary
+     * styling information on the composing text of an input method, so that
+     * they can be found and removed when the composing text is being
+     * replaced.
+     */
+    public static final int SPAN_COMPOSING = 0x100;
+    
+    /**
+     * This flag will be set for intermediate span changes, meaning there
+     * is guaranteed to be another change following it.  Typically it is
+     * used for {@link Selection} which automatically uses this with the first
+     * offset it sets when updating the selection.
+     */
+    public static final int SPAN_INTERMEDIATE = 0x200;
+    
+    /**
+     * The bits numbered SPAN_USER_SHIFT and above are available
+     * for callers to use to store scalar data associated with their
+     * span object.
+     */
+    public static final int SPAN_USER_SHIFT = 24;
+    /**
+     * The bits specified by the SPAN_USER bitfield are available
+     * for callers to use to store scalar data associated with their
+     * span object.
+     */
+    public static final int SPAN_USER = 0xFFFFFFFF << SPAN_USER_SHIFT;
+
+    /**
+     * The bits numbered just above SPAN_PRIORITY_SHIFT determine the order
+     * of change notifications -- higher numbers go first.  You probably
+     * don't need to set this; it is used so that when text changes, the
+     * text layout gets the chance to update itself before any other
+     * callbacks can inquire about the layout of the text.
+     */
+    public static final int SPAN_PRIORITY_SHIFT = 16;
+    /**
+     * The bits specified by the SPAN_PRIORITY bitmap determine the order
+     * of change notifications -- higher numbers go first.  You probably
+     * don't need to set this; it is used so that when text changes, the
+     * text layout gets the chance to update itself before any other
+     * callbacks can inquire about the layout of the text.
+     */
+    public static final int SPAN_PRIORITY = 0xFF << SPAN_PRIORITY_SHIFT;
+
+    /**
+     * Return an array of the markup objects attached to the specified
+     * slice of this CharSequence and whose type is the specified type
+     * or a subclass of it.  Specify Object.class for the type if you
+     * want all the objects regardless of type.
+     */
+    public <T> T[] getSpans(int start, int end, Class<T> type);
+
+    /**
+     * Return the beginning of the range of text to which the specified
+     * markup object is attached, or -1 if the object is not attached.
+     */
+    public int getSpanStart(Object tag);
+
+    /**
+     * Return the end of the range of text to which the specified
+     * markup object is attached, or -1 if the object is not attached.
+     */
+    public int getSpanEnd(Object tag);
+
+    /**
+     * Return the flags that were specified when {@link Spannable#setSpan} was
+     * used to attach the specified markup object, or 0 if the specified
+     * object has not been attached.
+     */
+    public int getSpanFlags(Object tag);
+
+    /**
+     * Return the first offset greater than or equal to <code>start</code>
+     * where a markup object of class <code>type</code> begins or ends,
+     * or <code>limit</code> if there are no starts or ends greater than or
+     * equal to <code>start</code> but less than <code>limit</code>.  Specify
+     * <code>null</code> or Object.class for the type if you want every
+     * transition regardless of type.
+     */
+    public int nextSpanTransition(int start, int limit, Class type);
+}
diff --git a/core/java/android/text/SpannedString.java b/core/java/android/text/SpannedString.java
new file mode 100644
index 0000000..afed221
--- /dev/null
+++ b/core/java/android/text/SpannedString.java
@@ -0,0 +1,48 @@
+/*
+ * 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 android.text;
+
+
+/**
+ * This is the class for text whose content and markup are immutable.
+ * For mutable markup, see {@link SpannableString}; for mutable text,
+ * see {@link SpannableStringBuilder}.
+ */
+public final class SpannedString
+extends SpannableStringInternal
+implements CharSequence, GetChars, Spanned
+{
+    public SpannedString(CharSequence source) {
+        super(source, 0, source.length());
+    }
+
+    private SpannedString(CharSequence source, int start, int end) {
+        super(source, start, end);
+    }
+
+    public CharSequence subSequence(int start, int end) {
+        return new SpannedString(this, start, end);
+    }
+
+    public static SpannedString valueOf(CharSequence source) {
+        if (source instanceof SpannedString) {
+            return (SpannedString) source;
+        } else {
+            return new SpannedString(source);
+        }
+    }
+}
diff --git a/core/java/android/text/StaticLayout.java b/core/java/android/text/StaticLayout.java
new file mode 100644
index 0000000..0fef40b
--- /dev/null
+++ b/core/java/android/text/StaticLayout.java
@@ -0,0 +1,1195 @@
+/*
+ * 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 android.text;
+
+import android.graphics.Paint;
+import com.android.internal.util.ArrayUtils;
+import android.util.Log;
+import android.text.style.LeadingMarginSpan;
+import android.text.style.LineHeightSpan;
+import android.text.style.MetricAffectingSpan;
+import android.text.style.ReplacementSpan;
+
+/**
+ * StaticLayout is a Layout for text that will not be edited after it
+ * is laid out.  Use {@link DynamicLayout} for text that may change.
+ * <p>This is used by widgets to control text layout. You should not need
+ * to use this class directly unless you are implementing your own widget
+ * or custom display object, or would be tempted to call
+ * {@link android.graphics.Canvas#drawText(java.lang.CharSequence, int, int, float, float, android.graphics.Paint)
+ *  Canvas.drawText()} directly.</p>
+ */
+public class
+StaticLayout
+extends Layout
+{
+    public StaticLayout(CharSequence source, TextPaint paint,
+                        int width,
+                        Alignment align, float spacingmult, float spacingadd,
+                        boolean includepad) {
+        this(source, 0, source.length(), paint, width, align,
+             spacingmult, spacingadd, includepad);
+    }
+
+    public StaticLayout(CharSequence source, int bufstart, int bufend,
+                        TextPaint paint, int outerwidth,
+                        Alignment align,
+                        float spacingmult, float spacingadd,
+                        boolean includepad) {
+        this(source, bufstart, bufend, paint, outerwidth, align,
+             spacingmult, spacingadd, includepad, null, 0);
+    }
+
+    public StaticLayout(CharSequence source, int bufstart, int bufend,
+                        TextPaint paint, int outerwidth,
+                        Alignment align,
+                        float spacingmult, float spacingadd,
+                        boolean includepad,
+                        TextUtils.TruncateAt ellipsize, int ellipsizedWidth) {
+        super((ellipsize == null)
+                ? source 
+                : (source instanceof Spanned)
+                    ? new SpannedEllipsizer(source)
+                    : new Ellipsizer(source),
+              paint, outerwidth, align, spacingmult, spacingadd);
+
+        /*
+         * This is annoying, but we can't refer to the layout until
+         * superclass construction is finished, and the superclass
+         * constructor wants the reference to the display text.
+         * 
+         * This will break if the superclass constructor ever actually
+         * cares about the content instead of just holding the reference.
+         */
+        if (ellipsize != null) {
+            Ellipsizer e = (Ellipsizer) getText();
+
+            e.mLayout = this;
+            e.mWidth = ellipsizedWidth;
+            e.mMethod = ellipsize;
+            mEllipsizedWidth = ellipsizedWidth;
+
+            mColumns = COLUMNS_ELLIPSIZE;
+        } else {
+            mColumns = COLUMNS_NORMAL;
+            mEllipsizedWidth = outerwidth;
+        }
+
+        mLines = new int[ArrayUtils.idealIntArraySize(2 * mColumns)];
+        mLineDirections = new Directions[
+                             ArrayUtils.idealIntArraySize(2 * mColumns)];
+
+        generate(source, bufstart, bufend, paint, outerwidth, align,
+                 spacingmult, spacingadd, includepad, includepad,
+                 ellipsize != null, ellipsizedWidth, ellipsize);
+
+        mChdirs = null;
+        mChs = null;
+        mWidths = null;
+        mFontMetricsInt = null;
+    }
+
+    /* package */ StaticLayout(boolean ellipsize) {
+        super(null, null, 0, null, 0, 0);
+
+        mColumns = COLUMNS_ELLIPSIZE;
+        mLines = new int[ArrayUtils.idealIntArraySize(2 * mColumns)];
+        mLineDirections = new Directions[
+                             ArrayUtils.idealIntArraySize(2 * mColumns)];
+    }
+
+    /* package */ void generate(CharSequence source, int bufstart, int bufend,
+                        TextPaint paint, int outerwidth,
+                        Alignment align,
+                        float spacingmult, float spacingadd,
+                        boolean includepad, boolean trackpad,
+                        boolean breakOnlyAtSpaces,
+                        float ellipsizedWidth, TextUtils.TruncateAt where) {
+        mLineCount = 0;
+
+        int v = 0;
+        boolean needMultiply = (spacingmult != 1 || spacingadd != 0);
+
+        Paint.FontMetricsInt fm = mFontMetricsInt;
+        int[] choosehtv = null;
+
+        int end = TextUtils.indexOf(source, '\n', bufstart, bufend);
+        int bufsiz = end >= 0 ? end - bufstart : bufend - bufstart;
+        boolean first = true;
+
+        if (mChdirs == null) {
+            mChdirs = new byte[ArrayUtils.idealByteArraySize(bufsiz + 1)];
+            mChs = new char[ArrayUtils.idealCharArraySize(bufsiz + 1)];
+            mWidths = new float[ArrayUtils.idealIntArraySize((bufsiz + 1) * 2)];
+        }
+
+        byte[] chdirs = mChdirs;
+        char[] chs = mChs;
+        float[] widths = mWidths;
+
+        AlteredCharSequence alter = null;
+        Spanned spanned = null;
+
+        if (source instanceof Spanned)
+            spanned = (Spanned) source;
+
+        int DEFAULT_DIR = DIR_LEFT_TO_RIGHT; // XXX
+
+        for (int start = bufstart; start <= bufend; start = end) {
+            if (first)
+                first = false;
+            else
+                end = TextUtils.indexOf(source, '\n', start, bufend);
+
+            if (end < 0)
+                end = bufend;
+            else
+                end++;
+
+            int firstwidth = outerwidth;
+            int restwidth = outerwidth;
+
+            LineHeightSpan[] chooseht = null;
+
+            if (spanned != null) {
+                LeadingMarginSpan[] sp;
+
+                sp = spanned.getSpans(start, end, LeadingMarginSpan.class);
+                for (int i = 0; i < sp.length; i++) {
+                    firstwidth -= sp[i].getLeadingMargin(true);
+                    restwidth -= sp[i].getLeadingMargin(false);
+                }
+
+                chooseht = spanned.getSpans(start, end, LineHeightSpan.class);
+
+                if (chooseht.length != 0) {
+                    if (choosehtv == null ||
+                        choosehtv.length < chooseht.length) {
+                        choosehtv = new int[ArrayUtils.idealIntArraySize(
+                                            chooseht.length)];
+                    }
+
+                    for (int i = 0; i < chooseht.length; i++) {
+                        int o = spanned.getSpanStart(chooseht[i]);
+
+                        if (o < start) {
+                            // starts in this layout, before the
+                            // current paragraph
+
+                            choosehtv[i] = getLineTop(getLineForOffset(o)); 
+                        } else {
+                            // starts in this paragraph
+
+                            choosehtv[i] = v;
+                        }
+                    }
+                }
+            }
+
+            if (end - start > chdirs.length) {
+                chdirs = new byte[ArrayUtils.idealByteArraySize(end - start)];
+                mChdirs = chdirs;
+            }
+            if (end - start > chs.length) {
+                chs = new char[ArrayUtils.idealCharArraySize(end - start)];
+                mChs = chs;
+            }
+            if ((end - start) * 2 > widths.length) {
+                widths = new float[ArrayUtils.idealIntArraySize((end - start) * 2)];
+                mWidths = widths;
+            }
+
+            TextUtils.getChars(source, start, end, chs, 0);
+            final int n = end - start;
+
+            boolean easy = true;
+            boolean altered = false;
+            int dir = DEFAULT_DIR; // XXX
+
+            for (int i = 0; i < n; i++) {
+                if (chs[i] >= FIRST_RIGHT_TO_LEFT) {
+                    easy = false;
+                    break;
+                }
+            }
+
+            if (!easy) {
+                AndroidCharacter.getDirectionalities(chs, chdirs, end - start);
+
+                /*
+                 * Determine primary paragraph direction
+                 */
+
+                for (int j = start; j < end; j++) {
+                    int d = chdirs[j - start];
+
+                    if (d == Character.DIRECTIONALITY_LEFT_TO_RIGHT) {
+                        dir = DIR_LEFT_TO_RIGHT;
+                        break;
+                    }
+                    if (d == Character.DIRECTIONALITY_RIGHT_TO_LEFT) {
+                        dir = DIR_RIGHT_TO_LEFT;
+                        break;
+                    }
+                }
+
+                /*
+                 * XXX Explicit overrides should go here
+                 */
+
+                /*
+                 * Weak type resolution
+                 */
+
+                final byte SOR = dir == DIR_LEFT_TO_RIGHT ?
+                                    Character.DIRECTIONALITY_LEFT_TO_RIGHT :
+                                    Character.DIRECTIONALITY_RIGHT_TO_LEFT;
+
+                // dump(chdirs, n, "initial");
+
+                // W1 non spacing marks
+                for (int j = 0; j < n; j++) {
+                    if (chdirs[j] == Character.NON_SPACING_MARK) {
+                        if (j == 0)
+                            chdirs[j] = SOR;
+                        else
+                            chdirs[j] = chdirs[j - 1];
+                    }
+                }
+
+                // dump(chdirs, n, "W1");
+
+                // W2 european numbers
+                byte cur = SOR;
+                for (int j = 0; j < n; j++) {
+                    byte d = chdirs[j];
+
+                    if (d == Character.DIRECTIONALITY_LEFT_TO_RIGHT ||
+                        d == Character.DIRECTIONALITY_RIGHT_TO_LEFT ||
+                        d == Character.DIRECTIONALITY_RIGHT_TO_LEFT_ARABIC)
+                        cur = d;
+                    else if (d == Character.DIRECTIONALITY_EUROPEAN_NUMBER) {
+                         if (cur ==
+                            Character.DIRECTIONALITY_RIGHT_TO_LEFT_ARABIC)
+                            chdirs[j] = Character.DIRECTIONALITY_ARABIC_NUMBER;
+                    }
+                }
+
+                // dump(chdirs, n, "W2");
+
+                // W3 arabic letters
+                for (int j = 0; j < n; j++) {
+                    if (chdirs[j] == Character.DIRECTIONALITY_RIGHT_TO_LEFT_ARABIC)
+                        chdirs[j] = Character.DIRECTIONALITY_RIGHT_TO_LEFT;
+                }
+
+                // dump(chdirs, n, "W3");
+
+                // W4 single separator between numbers
+                for (int j = 1; j < n - 1; j++) {
+                    byte d = chdirs[j];
+                    byte prev = chdirs[j - 1];
+                    byte next = chdirs[j + 1];
+
+                    if (d == Character.DIRECTIONALITY_EUROPEAN_NUMBER_SEPARATOR) {
+                        if (prev == Character.DIRECTIONALITY_EUROPEAN_NUMBER &&
+                            next == Character.DIRECTIONALITY_EUROPEAN_NUMBER)
+                            chdirs[j] = Character.DIRECTIONALITY_EUROPEAN_NUMBER;
+                    } else if (d == Character.DIRECTIONALITY_COMMON_NUMBER_SEPARATOR) {
+                        if (prev == Character.DIRECTIONALITY_EUROPEAN_NUMBER &&
+                            next == Character.DIRECTIONALITY_EUROPEAN_NUMBER)
+                            chdirs[j] = Character.DIRECTIONALITY_EUROPEAN_NUMBER;
+                        if (prev == Character.DIRECTIONALITY_ARABIC_NUMBER &&
+                            next == Character.DIRECTIONALITY_ARABIC_NUMBER)
+                            chdirs[j] = Character.DIRECTIONALITY_ARABIC_NUMBER;
+                    }
+                }
+
+                // dump(chdirs, n, "W4");
+
+                // W5 european number terminators
+                boolean adjacent = false;
+                for (int j = 0; j < n; j++) {
+                    byte d = chdirs[j];
+
+                    if (d == Character.DIRECTIONALITY_EUROPEAN_NUMBER)
+                        adjacent = true;
+                    else if (d == Character.DIRECTIONALITY_EUROPEAN_NUMBER_TERMINATOR && adjacent)
+                        chdirs[j] = Character.DIRECTIONALITY_EUROPEAN_NUMBER;
+                    else
+                        adjacent = false;
+                }
+
+                //dump(chdirs, n, "W5");
+
+                // W5 european number terminators part 2,
+                // W6 separators and terminators
+                adjacent = false;
+                for (int j = n - 1; j >= 0; j--) {
+                    byte d = chdirs[j];
+
+                    if (d == Character.DIRECTIONALITY_EUROPEAN_NUMBER)
+                        adjacent = true;
+                    else if (d == Character.DIRECTIONALITY_EUROPEAN_NUMBER_TERMINATOR) {
+                        if (adjacent)
+                            chdirs[j] = Character.DIRECTIONALITY_EUROPEAN_NUMBER;
+                        else
+                            chdirs[j] = Character.DIRECTIONALITY_OTHER_NEUTRALS;
+                    }
+                    else {
+                        adjacent = false;
+
+                        if (d == Character.DIRECTIONALITY_EUROPEAN_NUMBER_SEPARATOR ||
+                            d == Character.DIRECTIONALITY_COMMON_NUMBER_SEPARATOR ||
+                            d == Character.DIRECTIONALITY_PARAGRAPH_SEPARATOR ||
+                            d == Character.DIRECTIONALITY_SEGMENT_SEPARATOR)
+                            chdirs[j] = Character.DIRECTIONALITY_OTHER_NEUTRALS;
+                    }
+                }
+
+                // dump(chdirs, n, "W6");
+
+                // W7 strong direction of european numbers
+                cur = SOR;
+                for (int j = 0; j < n; j++) {
+                    byte d = chdirs[j];
+
+                    if (d == SOR ||
+                        d == Character.DIRECTIONALITY_LEFT_TO_RIGHT ||
+                        d == Character.DIRECTIONALITY_RIGHT_TO_LEFT)
+                        cur = d;
+
+                    if (d == Character.DIRECTIONALITY_EUROPEAN_NUMBER)
+                        chdirs[j] = cur;
+                }
+
+                // dump(chdirs, n, "W7");
+
+                // N1, N2 neutrals
+                cur = SOR;
+                for (int j = 0; j < n; j++) {
+                    byte d = chdirs[j];
+
+                    if (d == Character.DIRECTIONALITY_LEFT_TO_RIGHT ||
+                        d == Character.DIRECTIONALITY_RIGHT_TO_LEFT) {
+                        cur = d;
+                    } else if (d == Character.DIRECTIONALITY_EUROPEAN_NUMBER ||
+                               d == Character.DIRECTIONALITY_ARABIC_NUMBER) {
+                        cur = Character.DIRECTIONALITY_RIGHT_TO_LEFT;
+                    } else {
+                        byte dd = SOR;
+                        int k;
+
+                        for (k = j + 1; k < n; k++) {
+                            dd = chdirs[k];
+
+                            if (dd == Character.DIRECTIONALITY_LEFT_TO_RIGHT ||
+                                dd == Character.DIRECTIONALITY_RIGHT_TO_LEFT) {
+                                break;
+                            }
+                            if (dd == Character.DIRECTIONALITY_EUROPEAN_NUMBER ||
+                                dd == Character.DIRECTIONALITY_ARABIC_NUMBER) {
+                                dd = Character.DIRECTIONALITY_RIGHT_TO_LEFT;
+                                break;
+                            }
+                        }
+
+                        for (int y = j; y < k; y++) {
+                            if (dd == cur)
+                                chdirs[y] = cur;
+                            else
+                                chdirs[y] = SOR;
+                        }
+
+                        j = k - 1;
+                    }
+                }
+
+                // dump(chdirs, n, "final");
+
+                // extra: enforce that all tabs go the primary direction
+
+                for (int j = 0; j < n; j++) {
+                    if (chs[j] == '\t')
+                        chdirs[j] = SOR;
+                }
+
+                // extra: enforce that object replacements go to the
+                // primary direction
+                // and that none of the underlying characters are treated
+                // as viable breakpoints
+
+                if (source instanceof Spanned) {
+                    Spanned sp = (Spanned) source;
+                    ReplacementSpan[] spans = sp.getSpans(start, end, ReplacementSpan.class);
+
+                    for (int y = 0; y < spans.length; y++) {
+                        int a = sp.getSpanStart(spans[y]);
+                        int b = sp.getSpanEnd(spans[y]);
+
+                        for (int x = a; x < b; x++) {
+                            chdirs[x - start] = SOR;
+                            chs[x - start] = '\uFFFC';
+                        }
+                    }
+                }
+
+                // Do mirroring for right-to-left segments
+
+                for (int i = 0; i < n; i++) {
+                    if (chdirs[i] == Character.DIRECTIONALITY_RIGHT_TO_LEFT) {
+                        int j;
+
+                        for (j = i; j < n; j++) {
+                            if (chdirs[j] !=
+                                Character.DIRECTIONALITY_RIGHT_TO_LEFT)
+                                break;
+                        }
+
+                        if (AndroidCharacter.mirror(chs, i, j - i))
+                            altered = true;
+
+                        i = j - 1;
+                    }
+                }
+            }
+
+            CharSequence sub;
+
+            if (altered) {
+                if (alter == null)
+                    alter = AlteredCharSequence.make(source, chs, start, end);
+                else
+                    alter.update(chs, start, end);
+
+                sub = alter;
+            } else {
+                sub = source;
+            }
+
+            int width = firstwidth;
+
+            float w = 0;
+            int here = start;
+
+            int ok = start;
+            float okwidth = w;
+            int okascent = 0, okdescent = 0, oktop = 0, okbottom = 0;
+
+            int fit = start;
+            float fitwidth = w;
+            int fitascent = 0, fitdescent = 0, fittop = 0, fitbottom = 0;
+
+            boolean tab = false;
+
+            int next;
+            for (int i = start; i < end; i = next) {
+                if (spanned == null)
+                    next = end;
+                else
+                    next = spanned.nextSpanTransition(i, end,
+                                                      MetricAffectingSpan.
+                                                      class);
+
+                if (spanned == null) {
+                    paint.getTextWidths(sub, i, next, widths);
+                    System.arraycopy(widths, 0, widths,
+                                     end - start + (i - start), next - i);
+                                     
+                    paint.getFontMetricsInt(fm);
+                } else {
+                    mWorkPaint.baselineShift = 0;
+
+                    Styled.getTextWidths(paint, mWorkPaint,
+                                         spanned, i, next,
+                                         widths, fm);
+                    System.arraycopy(widths, 0, widths,
+                                     end - start + (i - start), next - i);
+
+                    if (mWorkPaint.baselineShift < 0) {
+                        fm.ascent += mWorkPaint.baselineShift;
+                        fm.top += mWorkPaint.baselineShift;
+                    } else {
+                        fm.descent += mWorkPaint.baselineShift;
+                        fm.bottom += mWorkPaint.baselineShift;
+                    }
+                }
+
+                int fmtop = fm.top;
+                int fmbottom = fm.bottom;
+                int fmascent = fm.ascent;
+                int fmdescent = fm.descent;
+
+                if (false) {
+                    StringBuilder sb = new StringBuilder();
+                    for (int j = i; j < next; j++) {
+                        sb.append(widths[j - start + (end - start)]);
+                        sb.append(' ');
+                    }
+
+                    Log.e("text", sb.toString());
+                }
+
+                for (int j = i; j < next; j++) {
+                    char c = chs[j - start];
+                    float before = w;
+
+                    switch (c) {
+                    case '\n':
+                        break;
+
+                    case '\t':
+                        w = Layout.nextTab(sub, start, end, w, null);
+                        tab = true;
+                        break;
+
+                    default:
+                        w += widths[j - start + (end - start)];
+                    }
+
+                    // Log.e("text", "was " + before + " now " + w + " after " + c + " within " + width);
+
+                    if (w <= width) {
+                        fitwidth = w;
+                        fit = j + 1;
+
+                        if (fmtop < fittop)
+                            fittop = fmtop;
+                        if (fmascent < fitascent)
+                            fitascent = fmascent;
+                        if (fmdescent > fitdescent)
+                            fitdescent = fmdescent;
+                        if (fmbottom > fitbottom)
+                            fitbottom = fmbottom;
+
+                        /*
+                         * From the Unicode Line Breaking Algorithm:
+                         * (at least approximately)
+                         *  
+                         * .,:; are class IS: breakpoints
+                         *      except when adjacent to digits
+                         * /    is class SY: a breakpoint
+                         *      except when followed by a digit.
+                         * -    is class HY: a breakpoint
+                         *      except when followed by a digit.
+                         *
+                         * Ideographs are class ID: breakpoints when adjacent.
+                         */
+
+                        if (c == ' ' || c == '\t' ||
+                            ((c == '.'  || c == ',' || c == ':' || c == ';') &&
+                             (j - 1 < here || !Character.isDigit(chs[j - 1 - start])) &&
+                             (j + 1 >= next || !Character.isDigit(chs[j + 1 - start]))) ||
+                            ((c == '/' || c == '-') &&
+                             (j + 1 >= next || !Character.isDigit(chs[j + 1 - start]))) ||
+                            (c >= FIRST_CJK && isIdeographic(c) &&
+                             j + 1 < next && isIdeographic(chs[j + 1 - start]))) {
+                            okwidth = w;
+                            ok = j + 1;
+
+                            if (fittop < oktop)
+                                oktop = fittop;
+                            if (fitascent < okascent)
+                                okascent = fitascent;
+                            if (fitdescent > okdescent)
+                                okdescent = fitdescent;
+                            if (fitbottom > okbottom)
+                                okbottom = fitbottom;
+                        }
+                    } else if (breakOnlyAtSpaces) {
+                        if (ok != here) {
+                            // Log.e("text", "output ok " + here + " to " +ok);
+
+                            while (ok < next && chs[ok - start] == ' ') {
+                                ok++;
+                            }
+
+                            v = out(source,
+                                    here, ok,
+                                    okascent, okdescent, oktop, okbottom,
+                                    v,
+                                    spacingmult, spacingadd, chooseht,
+                                    choosehtv, fm, tab,
+                                    needMultiply, start, chdirs, dir, easy,
+                                    ok == bufend, includepad, trackpad,
+                                    widths, start, end - start,
+                                    where, ellipsizedWidth, okwidth,
+                                    paint);
+
+                            here = ok;
+                        } else {
+                            // Act like it fit even though it didn't.
+
+                            fitwidth = w;
+                            fit = j + 1;
+
+                            if (fmtop < fittop)
+                                fittop = fmtop;
+                            if (fmascent < fitascent)
+                                fitascent = fmascent;
+                            if (fmdescent > fitdescent)
+                                fitdescent = fmdescent;
+                            if (fmbottom > fitbottom)
+                                fitbottom = fmbottom;
+                        }
+                    } else {
+                        if (ok != here) {
+                            // Log.e("text", "output ok " + here + " to " +ok);
+
+                            while (ok < next && chs[ok - start] == ' ') {
+                                ok++;
+                            }
+
+                            v = out(source,
+                                    here, ok,
+                                    okascent, okdescent, oktop, okbottom,
+                                    v,
+                                    spacingmult, spacingadd, chooseht,
+                                    choosehtv, fm, tab,
+                                    needMultiply, start, chdirs, dir, easy,
+                                    ok == bufend, includepad, trackpad,
+                                    widths, start, end - start,
+                                    where, ellipsizedWidth, okwidth,
+                                    paint);
+
+                            here = ok;
+                        } else if (fit != here) {
+                            // Log.e("text", "output fit " + here + " to " +fit);
+                            v = out(source,
+                                    here, fit,
+                                    fitascent, fitdescent,
+                                    fittop, fitbottom,
+                                    v,
+                                    spacingmult, spacingadd, chooseht,
+                                    choosehtv, fm, tab,
+                                    needMultiply, start, chdirs, dir, easy,
+                                    fit == bufend, includepad, trackpad,
+                                    widths, start, end - start,
+                                    where, ellipsizedWidth, fitwidth,
+                                    paint);
+
+                            here = fit;
+                        } else {
+                            // Log.e("text", "output one " + here + " to " +(here + 1));
+                            measureText(paint, mWorkPaint,
+                                        source, here, here + 1, fm, tab,
+                                        null);
+
+                            v = out(source,
+                                    here, here+1,
+                                    fm.ascent, fm.descent,
+                                    fm.top, fm.bottom,
+                                    v,
+                                    spacingmult, spacingadd, chooseht,
+                                    choosehtv, fm, tab,
+                                    needMultiply, start, chdirs, dir, easy,
+                                    here + 1 == bufend, includepad,
+                                    trackpad,
+                                    widths, start, end - start,
+                                    where, ellipsizedWidth,
+                                    widths[here - start], paint);
+
+                            here = here + 1;
+                        }
+
+                        if (here < i) {
+                            j = next = here; // must remeasure
+                        } else {
+                            j = here - 1;    // continue looping
+                        }
+
+                        ok = fit = here;
+                        w = 0;
+                        fitascent = fitdescent = fittop = fitbottom = 0;
+                        okascent = okdescent = oktop = okbottom = 0;
+
+                        width = restwidth;
+                    }
+                }
+            }
+
+            if (end != here) {
+                if ((fittop | fitbottom | fitdescent | fitascent) == 0) {
+                    paint.getFontMetricsInt(fm);
+
+                    fittop = fm.top;
+                    fitbottom = fm.bottom;
+                    fitascent = fm.ascent;
+                    fitdescent = fm.descent;
+                }
+
+                // Log.e("text", "output rest " + here + " to " + end);
+
+                v = out(source,
+                        here, end, fitascent, fitdescent,
+                        fittop, fitbottom,
+                        v,
+                        spacingmult, spacingadd, chooseht,
+                        choosehtv, fm, tab,
+                        needMultiply, start, chdirs, dir, easy,
+                        end == bufend, includepad, trackpad,
+                        widths, start, end - start,
+                        where, ellipsizedWidth, w, paint);
+            }
+
+            start = end;
+
+            if (end == bufend)
+                break;
+        }
+
+        if (bufend == bufstart || source.charAt(bufend - 1) == '\n') {
+            // Log.e("text", "output last " + bufend);
+
+            paint.getFontMetricsInt(fm);
+
+            v = out(source,
+                    bufend, bufend, fm.ascent, fm.descent,
+                    fm.top, fm.bottom,
+                    v,
+                    spacingmult, spacingadd, null,
+                    null, fm, false,
+                    needMultiply, bufend, chdirs, DEFAULT_DIR, true,
+                    true, includepad, trackpad,
+                    widths, bufstart, 0,
+                    where, ellipsizedWidth, 0, paint);
+        }
+    }
+
+    private static final char FIRST_CJK = '\u2E80';
+    /**
+     * Returns true if the specified character is one of those specified
+     * as being Ideographic (class ID) by the Unicode Line Breaking Algorithm
+     * (http://www.unicode.org/unicode/reports/tr14/), and is therefore OK
+     * to break between a pair of.
+     */
+    private static final boolean isIdeographic(char c) {
+        if (c >= '\u2E80' && c <= '\u2FFF') {
+            return true; // CJK, KANGXI RADICALS, DESCRIPTION SYMBOLS
+        }
+        if (c == '\u3000') {
+            return true; // IDEOGRAPHIC SPACE
+        }
+        if (c >= '\u3040' && c <= '\u309F') {
+            return true; // Hiragana (except small characters)
+        }
+        if (c >= '\u30A0' && c <= '\u30FF') {
+            return true; // Katakana (except small characters)
+        }
+        if (c >= '\u3400' && c <= '\u4DB5') {
+            return true; // CJK UNIFIED IDEOGRAPHS EXTENSION A
+        }
+        if (c >= '\u4E00' && c <= '\u9FBB') {
+            return true; // CJK UNIFIED IDEOGRAPHS
+        }
+        if (c >= '\uF900' && c <= '\uFAD9') {
+            return true; // CJK COMPATIBILITY IDEOGRAPHS
+        }
+        if (c >= '\uA000' && c <= '\uA48F') {
+            return true; // YI SYLLABLES
+        }
+        if (c >= '\uA490' && c <= '\uA4CF') {
+            return true; // YI RADICALS
+        }
+        if (c >= '\uFE62' && c <= '\uFE66') {
+            return true; // SMALL PLUS SIGN to SMALL EQUALS SIGN
+        }
+        if (c >= '\uFF10' && c <= '\uFF19') {
+            return true; // WIDE DIGITS
+        }
+
+        return false;
+    }
+
+/*
+    private static void dump(byte[] data, int count, String label) {
+        if (false) {
+            System.out.print(label);
+
+            for (int i = 0; i < count; i++)
+                System.out.print(" " + data[i]);
+
+            System.out.println();
+        }
+    }
+*/
+
+    private static int getFit(TextPaint paint,
+                              TextPaint workPaint,
+                       CharSequence text, int start, int end,
+                       float wid) {
+        int high = end + 1, low = start - 1, guess;
+
+        while (high - low > 1) {
+            guess = (high + low) / 2;
+
+            if (measureText(paint, workPaint,
+                            text, start, guess, null, true, null) > wid)
+                high = guess;
+            else
+                low = guess;
+        }
+
+        if (low < start)
+            return start;
+        else
+            return low;
+    }
+
+    private int out(CharSequence text, int start, int end,
+                      int above, int below, int top, int bottom, int v,
+                      float spacingmult, float spacingadd,
+                      LineHeightSpan[] chooseht, int[] choosehtv,
+                      Paint.FontMetricsInt fm, boolean tab,
+                      boolean needMultiply, int pstart, byte[] chdirs,
+                      int dir, boolean easy, boolean last,
+                      boolean includepad, boolean trackpad,
+                      float[] widths, int widstart, int widoff,
+                      TextUtils.TruncateAt ellipsize, float ellipsiswidth,
+                      float textwidth, TextPaint paint) {
+        int j = mLineCount;
+        int off = j * mColumns;
+        int want = off + mColumns + TOP;
+        int[] lines = mLines;
+
+        // Log.e("text", "line " + start + " to " + end + (last ? "===" : ""));
+
+        if (want >= lines.length) {
+            int nlen = ArrayUtils.idealIntArraySize(want + 1);
+            int[] grow = new int[nlen];
+            System.arraycopy(lines, 0, grow, 0, lines.length);
+            mLines = grow;
+            lines = grow;
+
+            Directions[] grow2 = new Directions[nlen];
+            System.arraycopy(mLineDirections, 0, grow2, 0,
+                             mLineDirections.length);
+            mLineDirections = grow2;
+        }
+
+        if (chooseht != null) {
+            fm.ascent = above;
+            fm.descent = below;
+            fm.top = top;
+            fm.bottom = bottom;
+
+            for (int i = 0; i < chooseht.length; i++) {
+                chooseht[i].chooseHeight(text, start, end, choosehtv[i], v, fm);
+            }
+
+            above = fm.ascent;
+            below = fm.descent;
+            top = fm.top;
+            bottom = fm.bottom;
+        }
+
+        if (j == 0) {
+            if (trackpad) {
+                mTopPadding = top - above;
+            }
+
+            if (includepad) {
+                above = top;
+            }
+        }
+        if (last) {
+            if (trackpad) {
+                mBottomPadding = bottom - below;
+            }
+
+            if (includepad) {
+                below = bottom;
+            }
+        }
+
+        int extra;
+
+        if (needMultiply) {
+            extra = (int) ((below - above) * (spacingmult - 1)
+                           + spacingadd + 0.5);
+        } else {
+            extra = 0;
+        }
+
+        lines[off + START] = start;
+        lines[off + TOP] = v;
+        lines[off + DESCENT] = below + extra;
+
+        v += (below - above) + extra;
+        lines[off + mColumns + START] = end;
+        lines[off + mColumns + TOP] = v;
+
+        if (tab)
+            lines[off + TAB] |= TAB_MASK;
+
+        {
+            lines[off + DIR] |= dir << DIR_SHIFT;
+
+            int cur = Character.DIRECTIONALITY_LEFT_TO_RIGHT;
+            int count = 0;
+
+            if (!easy) {
+                for (int k = start; k < end; k++) {
+                    if (chdirs[k - pstart] != cur) {
+                        count++;
+                        cur = chdirs[k - pstart];
+                    }
+                }
+            }
+
+            Directions linedirs;
+
+            if (count == 0) {
+                linedirs = DIRS_ALL_LEFT_TO_RIGHT;
+            } else {
+                short[] ld = new short[count + 1];
+
+                cur = Character.DIRECTIONALITY_LEFT_TO_RIGHT;
+                count = 0;
+                int here = start;
+
+                for (int k = start; k < end; k++) {
+                    if (chdirs[k - pstart] != cur) {
+                        // XXX check to make sure we don't
+                        //     overflow short
+                        ld[count++] = (short) (k - here);
+                        cur = chdirs[k - pstart];
+                        here = k;
+                    }
+                }
+
+                ld[count] = (short) (end - here);
+
+                if (count == 1 && ld[0] == 0) {
+                    linedirs = DIRS_ALL_RIGHT_TO_LEFT;
+                } else {
+                    linedirs = new Directions(ld);
+                }
+            }
+
+            mLineDirections[j] = linedirs;
+
+            // If ellipsize is in marquee mode, do not apply ellipsis on the first line
+            if (ellipsize != null && (ellipsize != TextUtils.TruncateAt.MARQUEE || j != 0)) {
+                calculateEllipsis(start, end, widths, widstart, widoff,
+                                  ellipsiswidth, ellipsize, j,
+                                  textwidth, paint);
+            }
+        }
+
+        mLineCount++;
+        return v;
+    }
+
+    private void calculateEllipsis(int linestart, int lineend,
+                                   float[] widths, int widstart, int widoff,
+                                   float avail, TextUtils.TruncateAt where,
+                                   int line, float textwidth, TextPaint paint) {
+        int len = lineend - linestart;
+
+        if (textwidth <= avail) {
+            // Everything fits!
+            mLines[mColumns * line + ELLIPSIS_START] = 0;
+            mLines[mColumns * line + ELLIPSIS_COUNT] = 0;
+            return;
+        }
+
+        float ellipsiswid = paint.measureText("\u2026");
+        int ellipsisStart, ellipsisCount;
+
+        if (where == TextUtils.TruncateAt.START) {
+            float sum = 0;
+            int i;
+
+            for (i = len; i >= 0; i--) {
+                float w = widths[i - 1 + linestart - widstart + widoff];
+
+                if (w + sum + ellipsiswid > avail) {
+                    break;
+                }
+
+                sum += w;
+            }
+
+            ellipsisStart = 0;
+            ellipsisCount = i;
+        } else if (where == TextUtils.TruncateAt.END || where == TextUtils.TruncateAt.MARQUEE) {
+            float sum = 0;
+            int i;
+
+            for (i = 0; i < len; i++) {
+                float w = widths[i + linestart - widstart + widoff];
+
+                if (w + sum + ellipsiswid > avail) {
+                    break;
+                }
+
+                sum += w;
+            }
+
+            ellipsisStart = i;
+            ellipsisCount = len - i;
+        } else /* where = TextUtils.TruncateAt.MIDDLE */ {
+            float lsum = 0, rsum = 0;
+            int left = 0, right = len;
+
+            float ravail = (avail - ellipsiswid) / 2;
+            for (right = len; right >= 0; right--) {
+                float w = widths[right - 1 + linestart - widstart + widoff];
+
+                if (w + rsum > ravail) {
+                    break;
+                }
+
+                rsum += w;
+            }
+
+            float lavail = avail - ellipsiswid - rsum;
+            for (left = 0; left < right; left++) {
+                float w = widths[left + linestart - widstart + widoff];
+
+                if (w + lsum > lavail) {
+                    break;
+                }
+
+                lsum += w;
+            }
+
+            ellipsisStart = left;
+            ellipsisCount = right - left;
+        }
+
+        mLines[mColumns * line + ELLIPSIS_START] = ellipsisStart;
+        mLines[mColumns * line + ELLIPSIS_COUNT] = ellipsisCount;
+    }
+
+    // Override the baseclass so we can directly access our members,
+    // rather than relying on member functions.
+    // The logic mirrors that of Layout.getLineForVertical
+    // FIXME: It may be faster to do a linear search for layouts without many lines.
+    public int getLineForVertical(int vertical) {
+        int high = mLineCount;
+        int low = -1;
+        int guess;
+        int[] lines = mLines;
+        while (high - low > 1) {
+            guess = (high + low) >> 1;
+            if (lines[mColumns * guess + TOP] > vertical){
+                high = guess;
+            } else {
+                low = guess;
+            }
+        }
+        if (low < 0) {
+            return 0;
+        } else {
+            return low;
+        }
+    }
+
+    public int getLineCount() {
+        return mLineCount;
+    }
+
+    public int getLineTop(int line) {
+        return mLines[mColumns * line + TOP];    
+    }
+
+    public int getLineDescent(int line) {
+        return mLines[mColumns * line + DESCENT];   
+    }
+
+    public int getLineStart(int line) {
+        return mLines[mColumns * line + START] & START_MASK;
+    }
+
+    public int getParagraphDirection(int line) {
+        return mLines[mColumns * line + DIR] >> DIR_SHIFT;
+    }
+
+    public boolean getLineContainsTab(int line) {
+        return (mLines[mColumns * line + TAB] & TAB_MASK) != 0;
+    }
+
+    public final Directions getLineDirections(int line) {
+        return mLineDirections[line];
+    }
+
+    public int getTopPadding() {
+        return mTopPadding;
+    }
+
+    public int getBottomPadding() {
+        return mBottomPadding;
+    }
+
+    @Override
+    public int getEllipsisCount(int line) {
+        if (mColumns < COLUMNS_ELLIPSIZE) {
+            return 0;
+        }
+
+        return mLines[mColumns * line + ELLIPSIS_COUNT];
+    }
+
+    @Override
+    public int getEllipsisStart(int line) {
+        if (mColumns < COLUMNS_ELLIPSIZE) {
+            return 0;
+        }
+
+        return mLines[mColumns * line + ELLIPSIS_START];
+    }
+
+    @Override
+    public int getEllipsizedWidth() {
+        return mEllipsizedWidth;
+    }
+
+    private int mLineCount;
+    private int mTopPadding, mBottomPadding;
+    private int mColumns;
+    private int mEllipsizedWidth;
+
+    private static final int COLUMNS_NORMAL = 3;
+    private static final int COLUMNS_ELLIPSIZE = 5;
+    private static final int START = 0;
+    private static final int DIR = START;
+    private static final int TAB = START;
+    private static final int TOP = 1;
+    private static final int DESCENT = 2;
+    private static final int ELLIPSIS_START = 3;
+    private static final int ELLIPSIS_COUNT = 4;
+
+    private int[] mLines;
+    private Directions[] mLineDirections;
+
+    private static final int START_MASK = 0x1FFFFFFF;
+    private static final int DIR_MASK   = 0xC0000000;
+    private static final int DIR_SHIFT  = 30;
+    private static final int TAB_MASK   = 0x20000000;
+
+    private static final char FIRST_RIGHT_TO_LEFT = '\u0590';
+
+    /*
+     * These are reused across calls to generate()
+     */
+    private byte[] mChdirs;
+    private char[] mChs;
+    private float[] mWidths;
+    private Paint.FontMetricsInt mFontMetricsInt = new Paint.FontMetricsInt();
+}
diff --git a/core/java/android/text/Styled.java b/core/java/android/text/Styled.java
new file mode 100644
index 0000000..0aa2004
--- /dev/null
+++ b/core/java/android/text/Styled.java
@@ -0,0 +1,375 @@
+/*
+ * 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 android.text;
+
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.text.style.CharacterStyle;
+import android.text.style.MetricAffectingSpan;
+import android.text.style.ReplacementSpan;
+
+/**
+ * This class provides static methods for drawing and measuring styled texts, like
+ * {@link android.text.Spanned} object with {@link android.text.style.ReplacementSpan}.
+ * @hide
+ */
+public class Styled
+{
+    private static float each(Canvas canvas,
+                              Spanned text, int start, int end,
+                              int dir, boolean reverse,
+                              float x, int top, int y, int bottom,
+                              Paint.FontMetricsInt fmi,
+                              TextPaint paint,
+                              TextPaint workPaint,
+                              boolean needwid) {
+
+        boolean havewid = false;
+        float ret = 0;
+        CharacterStyle[] spans = text.getSpans(start, end, CharacterStyle.class);
+
+        ReplacementSpan replacement = null;
+
+        paint.bgColor = 0;
+        paint.baselineShift = 0;
+        workPaint.set(paint);
+
+		if (spans.length > 0) {
+			for (int i = 0; i < spans.length; i++) {
+				CharacterStyle span = spans[i];
+
+				if (span instanceof ReplacementSpan) {
+					replacement = (ReplacementSpan)span;
+				}
+				else {
+					span.updateDrawState(workPaint);
+				}
+			}
+		}
+
+        if (replacement == null) {
+            CharSequence tmp;
+            int tmpstart, tmpend;
+
+            if (reverse) {
+                tmp = TextUtils.getReverse(text, start, end);
+                tmpstart = 0;
+                tmpend = end - start;
+            } else {
+                tmp = text;
+                tmpstart = start;
+                tmpend = end;
+            }
+
+            if (fmi != null) {
+                workPaint.getFontMetricsInt(fmi);
+            }
+
+            if (canvas != null) {
+                if (workPaint.bgColor != 0) {
+                    int c = workPaint.getColor();
+                    Paint.Style s = workPaint.getStyle();
+                    workPaint.setColor(workPaint.bgColor);
+                    workPaint.setStyle(Paint.Style.FILL);
+
+                    if (!havewid) {
+                        ret = workPaint.measureText(tmp, tmpstart, tmpend);
+                        havewid = true;
+                    }
+
+                    if (dir == Layout.DIR_RIGHT_TO_LEFT)
+                        canvas.drawRect(x - ret, top, x, bottom, workPaint);
+                    else
+                        canvas.drawRect(x, top, x + ret, bottom, workPaint);
+
+                    workPaint.setStyle(s);
+                    workPaint.setColor(c);
+                }
+
+                if (dir == Layout.DIR_RIGHT_TO_LEFT) {
+                    if (!havewid) {
+                        ret = workPaint.measureText(tmp, tmpstart, tmpend);
+                        havewid = true;
+                    }
+
+                    canvas.drawText(tmp, tmpstart, tmpend,
+                                    x - ret, y + workPaint.baselineShift, workPaint);
+                } else {
+                    if (needwid) {
+                        if (!havewid) {
+                            ret = workPaint.measureText(tmp, tmpstart, tmpend);
+                            havewid = true;
+                        }
+                    }
+
+                    canvas.drawText(tmp, tmpstart, tmpend,
+                                    x, y + workPaint.baselineShift, workPaint);
+                }
+            } else {
+                if (needwid && !havewid) {
+                    ret = workPaint.measureText(tmp, tmpstart, tmpend);
+                    havewid = true;
+                }
+            }
+        } else {
+            ret = replacement.getSize(workPaint, text, start, end, fmi);
+
+            if (canvas != null) {
+                if (dir == Layout.DIR_RIGHT_TO_LEFT)
+                    replacement.draw(canvas, text, start, end,
+                                     x - ret, top, y, bottom, workPaint);
+                else
+                    replacement.draw(canvas, text, start, end,
+                                     x, top, y, bottom, workPaint);
+            }
+        }
+
+        if (dir == Layout.DIR_RIGHT_TO_LEFT)
+            return -ret;
+        else
+            return ret;
+    }
+
+    /**
+     * Return the advance widths for the characters in the string.
+     * See also {@link android.graphics.Paint#getTextWidths(CharSequence, int, int, float[])}.
+     * 
+     * @param paint The main {@link TextPaint} object.
+     * @param workPaint The {@link TextPaint} object used for temporal workspace.
+     * @param text The text to measure
+     * @param start The index of the first char to to measure
+     * @param end The end of the text slice to measure
+     * @param widths Array to receive the advance widths of the characters.
+     * Must be at least a large as (end - start).
+     * @param fmi FontMetrics information. Can be null.
+     * @return The actual number of widths returned. 
+     */
+    public static int getTextWidths(TextPaint paint,
+                                    TextPaint workPaint,
+                                    Spanned text, int start, int end,
+                                    float[] widths, Paint.FontMetricsInt fmi) {
+        //  Keep workPaint as is so that developers reuse the workspace.
+        MetricAffectingSpan[] spans = text.getSpans(start, end, MetricAffectingSpan.class);
+
+		ReplacementSpan replacement = null;
+        workPaint.set(paint);
+		
+		for (int i = 0; i < spans.length; i++) {
+			MetricAffectingSpan span = spans[i];
+			if (span instanceof ReplacementSpan) {
+				replacement = (ReplacementSpan)span;
+			}
+			else {
+				span.updateMeasureState(workPaint);
+			}
+		}
+	
+        if (replacement == null) {
+            workPaint.getFontMetricsInt(fmi);
+            workPaint.getTextWidths(text, start, end, widths);
+        } else {
+            int wid = replacement.getSize(workPaint, text, start, end, fmi);
+
+            if (end > start) {
+                widths[0] = wid;
+
+                for (int i = start + 1; i < end; i++)
+                    widths[i - start] = 0;
+            }
+        }
+        return end - start;
+    }
+
+    private static float foreach(Canvas canvas,
+                                 CharSequence text, int start, int end,
+                                 int dir, boolean reverse,
+                                 float x, int top, int y, int bottom,
+                                 Paint.FontMetricsInt fmi,
+                                 TextPaint paint,
+                                 TextPaint workPaint,
+                                 boolean needWidth) {
+        if (! (text instanceof Spanned)) {
+            float ret = 0;
+
+            if (reverse) {
+                CharSequence tmp = TextUtils.getReverse(text, start, end);
+                int tmpend = end - start;
+
+                if (canvas != null || needWidth)
+                    ret = paint.measureText(tmp, 0, tmpend);
+
+                if (canvas != null)
+                    canvas.drawText(tmp, 0, tmpend,
+                                    x - ret, y, paint);
+            } else {
+                if (needWidth)
+                    ret = paint.measureText(text, start, end);
+
+                if (canvas != null)
+                    canvas.drawText(text, start, end, x, y, paint);
+            }
+
+            if (fmi != null) {
+                paint.getFontMetricsInt(fmi);
+            }
+
+            return ret * dir;   //Layout.DIR_RIGHT_TO_LEFT == -1
+        }
+        
+        float ox = x;
+        int asc = 0, desc = 0;
+        int ftop = 0, fbot = 0;
+
+        Spanned sp = (Spanned) text;
+        Class division;
+
+        if (canvas == null)
+            division = MetricAffectingSpan.class;
+        else
+            division = CharacterStyle.class;
+
+        int next;
+        for (int i = start; i < end; i = next) {
+            next = sp.nextSpanTransition(i, end, division);
+
+            x += each(canvas, sp, i, next, dir, reverse,
+                  x, top, y, bottom, fmi, paint, workPaint,
+                  needWidth || next != end);
+
+            if (fmi != null) {
+                if (fmi.ascent < asc)
+                    asc = fmi.ascent;
+                if (fmi.descent > desc)
+                    desc = fmi.descent;
+
+                if (fmi.top < ftop)
+                    ftop = fmi.top;
+                if (fmi.bottom > fbot)
+                    fbot = fmi.bottom;
+            }
+        }
+
+        if (fmi != null) {
+            if (start == end) {
+                paint.getFontMetricsInt(fmi);
+            } else {
+                fmi.ascent = asc;
+                fmi.descent = desc;
+                fmi.top = ftop;
+                fmi.bottom = fbot;
+            }
+        }
+
+        return x - ox;
+    }
+
+
+    /* package */ static float drawText(Canvas canvas,
+                                       CharSequence text, int start, int end,
+                                       int direction, boolean reverse,
+                                       float x, int top, int y, int bottom,
+                                       TextPaint paint,
+                                       TextPaint workPaint,
+                                       boolean needWidth) {
+        if ((direction == Layout.DIR_RIGHT_TO_LEFT && !reverse) ||
+            (reverse && direction == Layout.DIR_LEFT_TO_RIGHT)) {
+            float ch = foreach(null, text, start, end, Layout.DIR_LEFT_TO_RIGHT,
+                         false, 0, 0, 0, 0, null, paint, workPaint,
+                         true);
+
+            ch *= direction;  // DIR_RIGHT_TO_LEFT == -1
+            foreach(canvas, text, start, end, -direction,
+                    reverse, x + ch, top, y, bottom, null, paint,
+                    workPaint, true);
+
+            return ch;
+        }
+
+        return foreach(canvas, text, start, end, direction, reverse,
+                       x, top, y, bottom, null, paint, workPaint,
+                       needWidth);
+    }
+    
+    /**
+     * Draw the specified range of text, specified by start/end, with its origin at (x,y),
+     * in the specified Paint. The origin is interpreted based on the Align setting in the
+     * Paint.
+     *  
+     * This method considers style information in the text
+     * (e.g. Even when text is an instance of {@link android.text.Spanned}, this method
+     * correctly draws the text).
+     * See also
+     * {@link android.graphics.Canvas#drawText(CharSequence, int, int, float, float, Paint)}
+     * and
+     * {@link android.graphics.Canvas#drawRect(float, float, float, float, Paint)}.
+     * 
+     * @param canvas The target canvas.
+     * @param text The text to be drawn
+     * @param start The index of the first character in text to draw
+     * @param end (end - 1) is the index of the last character in text to draw
+     * @param direction The direction of the text. This must be
+     * {@link android.text.Layout#DIR_LEFT_TO_RIGHT} or
+     * {@link android.text.Layout#DIR_RIGHT_TO_LEFT}.
+     * @param x The x-coordinate of origin for where to draw the text
+     * @param top The top side of the rectangle to be drawn
+     * @param y The y-coordinate of origin for where to draw the text
+     * @param bottom The bottom side of the rectangle to be drawn
+     * @param paint The main {@link TextPaint} object.
+     * @param workPaint The {@link TextPaint} object used for temporal workspace.
+     * @param needWidth If true, this method returns the width of drawn text.
+     * @return Width of the drawn text if needWidth is true.
+     */
+    public static float drawText(Canvas canvas,
+                                 CharSequence text, int start, int end,
+                                 int direction,
+                                 float x, int top, int y, int bottom,
+                                 TextPaint paint,
+                                 TextPaint workPaint,
+                                 boolean needWidth) {
+        // For safety.
+        direction = direction >= 0 ? Layout.DIR_LEFT_TO_RIGHT : Layout.DIR_RIGHT_TO_LEFT;
+        /*
+         * Hided "reverse" parameter since it is meaningless for external developers.
+         * Kept workPaint as is so that developers reuse the workspace.
+         */
+        return drawText(canvas, text, start, end, direction, false,
+                        x, top, y, bottom, paint, workPaint, needWidth);
+    }
+    
+    /**
+     * Return the width of the text, considering style information in the text
+     * (e.g. Even when text is an instance of {@link android.text.Spanned}, this method
+     * correctly mesures the width of the text).
+     * 
+     * @param paint The main {@link TextPaint} object.
+     * @param workPaint The {@link TextPaint} object used for temporal workspace.
+     * @param text The text to measure
+     * @param start The index of the first character to start measuring
+     * @param end 1 beyond the index of the last character to measure
+     * @param fmi FontMetrics information. Can be null
+     * @return The width of the text 
+     */
+    public static float measureText(TextPaint paint,
+                                    TextPaint workPaint,
+                                    CharSequence text, int start, int end,
+                                    Paint.FontMetricsInt fmi) {
+        // Keep workPaint as is so that developers reuse the workspace.
+        return foreach(null, text, start, end,
+                       Layout.DIR_LEFT_TO_RIGHT, false,
+                       0, 0, 0, 0, fmi, paint, workPaint, true);
+    }
+}
diff --git a/core/java/android/text/TextPaint.java b/core/java/android/text/TextPaint.java
new file mode 100644
index 0000000..f13820d
--- /dev/null
+++ b/core/java/android/text/TextPaint.java
@@ -0,0 +1,55 @@
+/*
+ * 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 android.text;
+
+import android.graphics.Paint;
+
+/**
+ * TextPaint is an extension of Paint that leaves room for some extra
+ * data used during text measuring and drawing.
+ */
+public class TextPaint extends Paint {
+    public int bgColor;
+    public int baselineShift;
+    public int linkColor;
+    public int[] drawableState;
+
+    public TextPaint() {
+        super();
+    }
+
+    public TextPaint(int flags) {
+        super(flags);
+    }
+
+    public TextPaint(Paint p) {
+        super(p);
+    }
+
+    /**
+     * Copy the fields from tp into this TextPaint, including the
+     * fields inherited from Paint.
+     */
+    public void set(TextPaint tp) {
+        super.set(tp);
+
+        bgColor = tp.bgColor;
+        baselineShift = tp.baselineShift;
+        linkColor = tp.linkColor;
+        drawableState = tp.drawableState;
+    }
+}
diff --git a/core/java/android/text/TextUtils.java b/core/java/android/text/TextUtils.java
new file mode 100644
index 0000000..5b4c380
--- /dev/null
+++ b/core/java/android/text/TextUtils.java
@@ -0,0 +1,1620 @@
+/*
+ * 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 android.text;
+
+import com.android.internal.R;
+
+import android.content.res.ColorStateList;
+import android.content.res.Resources;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.method.TextKeyListener.Capitalize;
+import android.text.style.AbsoluteSizeSpan;
+import android.text.style.AlignmentSpan;
+import android.text.style.BackgroundColorSpan;
+import android.text.style.BulletSpan;
+import android.text.style.CharacterStyle;
+import android.text.style.ForegroundColorSpan;
+import android.text.style.LeadingMarginSpan;
+import android.text.style.MetricAffectingSpan;
+import android.text.style.QuoteSpan;
+import android.text.style.RelativeSizeSpan;
+import android.text.style.ReplacementSpan;
+import android.text.style.ScaleXSpan;
+import android.text.style.StrikethroughSpan;
+import android.text.style.StyleSpan;
+import android.text.style.SubscriptSpan;
+import android.text.style.SuperscriptSpan;
+import android.text.style.TextAppearanceSpan;
+import android.text.style.TypefaceSpan;
+import android.text.style.URLSpan;
+import android.text.style.UnderlineSpan;
+import android.util.Printer;
+
+import com.android.internal.util.ArrayUtils;
+
+import java.util.regex.Pattern;
+import java.util.Iterator;
+
+public class TextUtils {
+    private TextUtils() { /* cannot be instantiated */ }
+
+    private static String[] EMPTY_STRING_ARRAY = new String[]{};
+
+    public static void getChars(CharSequence s, int start, int end,
+                                char[] dest, int destoff) {
+        Class c = s.getClass();
+
+        if (c == String.class)
+            ((String) s).getChars(start, end, dest, destoff);
+        else if (c == StringBuffer.class)
+            ((StringBuffer) s).getChars(start, end, dest, destoff);
+        else if (c == StringBuilder.class)
+            ((StringBuilder) s).getChars(start, end, dest, destoff);
+        else if (s instanceof GetChars)
+            ((GetChars) s).getChars(start, end, dest, destoff);
+        else {
+            for (int i = start; i < end; i++)
+                dest[destoff++] = s.charAt(i);
+        }
+    }
+
+    public static int indexOf(CharSequence s, char ch) {
+        return indexOf(s, ch, 0);
+    }
+
+    public static int indexOf(CharSequence s, char ch, int start) {
+        Class c = s.getClass();
+
+        if (c == String.class)
+            return ((String) s).indexOf(ch, start);
+
+        return indexOf(s, ch, start, s.length());
+    }
+
+    public static int indexOf(CharSequence s, char ch, int start, int end) {
+        Class c = s.getClass();
+
+        if (s instanceof GetChars || c == StringBuffer.class ||
+            c == StringBuilder.class || c == String.class) {
+            final int INDEX_INCREMENT = 500;
+            char[] temp = obtain(INDEX_INCREMENT);
+
+            while (start < end) {
+                int segend = start + INDEX_INCREMENT;
+                if (segend > end)
+                    segend = end;
+
+                getChars(s, start, segend, temp, 0);
+
+                int count = segend - start;
+                for (int i = 0; i < count; i++) {
+                    if (temp[i] == ch) {
+                        recycle(temp);
+                        return i + start;
+                    }
+                }
+
+                start = segend;
+            }
+
+            recycle(temp);
+            return -1;
+        }
+
+        for (int i = start; i < end; i++)
+            if (s.charAt(i) == ch)
+                return i;
+
+        return -1;
+    }
+
+    public static int lastIndexOf(CharSequence s, char ch) {
+        return lastIndexOf(s, ch, s.length() - 1);
+    }
+
+    public static int lastIndexOf(CharSequence s, char ch, int last) {
+        Class c = s.getClass();
+
+        if (c == String.class)
+            return ((String) s).lastIndexOf(ch, last);
+
+        return lastIndexOf(s, ch, 0, last);
+    }
+
+    public static int lastIndexOf(CharSequence s, char ch,
+                                  int start, int last) {
+        if (last < 0)
+            return -1;
+        if (last >= s.length())
+            last = s.length() - 1;
+
+        int end = last + 1;
+
+        Class c = s.getClass();
+
+        if (s instanceof GetChars || c == StringBuffer.class ||
+            c == StringBuilder.class || c == String.class) {
+            final int INDEX_INCREMENT = 500;
+            char[] temp = obtain(INDEX_INCREMENT);
+
+            while (start < end) {
+                int segstart = end - INDEX_INCREMENT;
+                if (segstart < start)
+                    segstart = start;
+
+                getChars(s, segstart, end, temp, 0);
+
+                int count = end - segstart;
+                for (int i = count - 1; i >= 0; i--) {
+                    if (temp[i] == ch) {
+                        recycle(temp);
+                        return i + segstart;
+                    }
+                }
+
+                end = segstart;
+            }
+
+            recycle(temp);
+            return -1;
+        }
+
+        for (int i = end - 1; i >= start; i--)
+            if (s.charAt(i) == ch)
+                return i;
+
+        return -1;
+    }
+
+    public static int indexOf(CharSequence s, CharSequence needle) {
+        return indexOf(s, needle, 0, s.length());
+    }
+
+    public static int indexOf(CharSequence s, CharSequence needle, int start) {
+        return indexOf(s, needle, start, s.length());
+    }
+
+    public static int indexOf(CharSequence s, CharSequence needle,
+                              int start, int end) {
+        int nlen = needle.length();
+        if (nlen == 0)
+            return start;
+
+        char c = needle.charAt(0);
+
+        for (;;) {
+            start = indexOf(s, c, start);
+            if (start > end - nlen) {
+                break;
+            }
+
+            if (start < 0) {
+                return -1;
+            }
+
+            if (regionMatches(s, start, needle, 0, nlen)) {
+                return start;
+            }
+
+            start++;
+        }
+        return -1;
+    }
+
+    public static boolean regionMatches(CharSequence one, int toffset,
+                                        CharSequence two, int ooffset,
+                                        int len) {
+        char[] temp = obtain(2 * len);
+
+        getChars(one, toffset, toffset + len, temp, 0);
+        getChars(two, ooffset, ooffset + len, temp, len);
+
+        boolean match = true;
+        for (int i = 0; i < len; i++) {
+            if (temp[i] != temp[i + len]) {
+                match = false;
+                break;
+            }
+        }
+
+        recycle(temp);
+        return match;
+    }
+
+    /**
+     * Create a new String object containing the given range of characters
+     * from the source string.  This is different than simply calling
+     * {@link CharSequence#subSequence(int, int) CharSequence.subSequence}
+     * in that it does not preserve any style runs in the source sequence,
+     * allowing a more efficient implementation.
+     */
+    public static String substring(CharSequence source, int start, int end) {
+        if (source instanceof String)
+            return ((String) source).substring(start, end);
+        if (source instanceof StringBuilder)
+            return ((StringBuilder) source).substring(start, end);
+        if (source instanceof StringBuffer)
+            return ((StringBuffer) source).substring(start, end);
+
+        char[] temp = obtain(end - start);
+        getChars(source, start, end, temp, 0);
+        String ret = new String(temp, 0, end - start);
+        recycle(temp);
+
+        return ret;
+    }
+
+    /**
+     * Returns a string containing the tokens joined by delimiters.
+     * @param tokens an array objects to be joined. Strings will be formed from
+     *     the objects by calling object.toString().
+     */
+    public static String join(CharSequence delimiter, Object[] tokens) {
+        StringBuilder sb = new StringBuilder();
+        boolean firstTime = true;
+        for (Object token: tokens) {
+            if (firstTime) {
+                firstTime = false;
+            } else {
+                sb.append(delimiter);
+            }
+            sb.append(token);
+        }
+        return sb.toString();
+    }
+
+    /**
+     * Returns a string containing the tokens joined by delimiters.
+     * @param tokens an array objects to be joined. Strings will be formed from
+     *     the objects by calling object.toString().
+     */
+    public static String join(CharSequence delimiter, Iterable tokens) {
+        StringBuilder sb = new StringBuilder();
+        boolean firstTime = true;
+        for (Object token: tokens) {
+            if (firstTime) {
+                firstTime = false;
+            } else {
+                sb.append(delimiter);
+            }
+            sb.append(token);
+        }
+        return sb.toString();
+    }
+
+    /**
+     * String.split() returns [''] when the string to be split is empty. This returns []. This does
+     * not remove any empty strings from the result. For example split("a,", ","  ) returns {"a", ""}.
+     *
+     * @param text the string to split
+     * @param expression the regular expression to match
+     * @return an array of strings. The array will be empty if text is empty
+     *
+     * @throws NullPointerException if expression or text is null
+     */
+    public static String[] split(String text, String expression) {
+        if (text.length() == 0) {
+            return EMPTY_STRING_ARRAY;
+        } else {
+            return text.split(expression, -1);
+        }
+    }
+
+    /**
+     * Splits a string on a pattern. String.split() returns [''] when the string to be
+     * split is empty. This returns []. This does not remove any empty strings from the result.
+     * @param text the string to split
+     * @param pattern the regular expression to match
+     * @return an array of strings. The array will be empty if text is empty
+     *
+     * @throws NullPointerException if expression or text is null
+     */
+    public static String[] split(String text, Pattern pattern) {
+        if (text.length() == 0) {
+            return EMPTY_STRING_ARRAY;
+        } else {
+            return pattern.split(text, -1);
+        }
+    }
+
+    /**
+     * An interface for splitting strings according to rules that are opaque to the user of this
+     * interface. This also has less overhead than split, which uses regular expressions and
+     * allocates an array to hold the results.
+     *
+     * <p>The most efficient way to use this class is:
+     *
+     * <pre>
+     * // Once
+     * TextUtils.StringSplitter splitter = new TextUtils.SimpleStringSplitter(delimiter);
+     *
+     * // Once per string to split
+     * splitter.setString(string);
+     * for (String s : splitter) {
+     *     ...
+     * }
+     * </pre>
+     */
+    public interface StringSplitter extends Iterable<String> {
+        public void setString(String string);
+    }
+
+    /**
+     * A simple string splitter.
+     *
+     * <p>If the final character in the string to split is the delimiter then no empty string will
+     * be returned for the empty string after that delimeter. That is, splitting <tt>"a,b,"</tt> on
+     * comma will return <tt>"a", "b"</tt>, not <tt>"a", "b", ""</tt>.
+     */
+    public static class SimpleStringSplitter implements StringSplitter, Iterator<String> {
+        private String mString;
+        private char mDelimiter;
+        private int mPosition;
+        private int mLength;
+
+        /**
+         * Initializes the splitter. setString may be called later.
+         * @param delimiter the delimeter on which to split
+         */
+        public SimpleStringSplitter(char delimiter) {
+            mDelimiter = delimiter;
+        }
+
+        /**
+         * Sets the string to split
+         * @param string the string to split
+         */
+        public void setString(String string) {
+            mString = string;
+            mPosition = 0;
+            mLength = mString.length();
+        }
+
+        public Iterator<String> iterator() {
+            return this;
+        }
+
+        public boolean hasNext() {
+            return mPosition < mLength;
+        }
+
+        public String next() {
+            int end = mString.indexOf(mDelimiter, mPosition);
+            if (end == -1) {
+                end = mLength;
+            }
+            String nextString = mString.substring(mPosition, end);
+            mPosition = end + 1; // Skip the delimiter.
+            return nextString;
+        }
+
+        public void remove() {
+            throw new UnsupportedOperationException();
+        }
+    }
+
+    public static CharSequence stringOrSpannedString(CharSequence source) {
+        if (source == null)
+            return null;
+        if (source instanceof SpannedString)
+            return source;
+        if (source instanceof Spanned)
+            return new SpannedString(source);
+
+        return source.toString();
+    }
+
+    /**
+     * Returns true if the string is null or 0-length.
+     * @param str the string to be examined
+     * @return true if str is null or zero length
+     */
+    public static boolean isEmpty(CharSequence str) {
+        if (str == null || str.length() == 0)
+            return true;
+        else
+            return false;
+    }
+
+    /**
+     * Returns the length that the specified CharSequence would have if
+     * spaces and control characters were trimmed from the start and end,
+     * as by {@link String#trim}.
+     */
+    public static int getTrimmedLength(CharSequence s) {
+        int len = s.length();
+
+        int start = 0;
+        while (start < len && s.charAt(start) <= ' ') {
+            start++;
+        }
+
+        int end = len;
+        while (end > start && s.charAt(end - 1) <= ' ') {
+            end--;
+        }
+
+        return end - start;
+    }
+
+    /**
+     * Returns true if a and b are equal, including if they are both null.
+     * <p><i>Note: In platform versions 1.1 and earlier, this method only worked well if
+     * both the arguments were instances of String.</i></p>
+     * @param a first CharSequence to check
+     * @param b second CharSequence to check
+     * @return true if a and b are equal
+     */
+    public static boolean equals(CharSequence a, CharSequence b) {
+        if (a == b) return true;
+        int length;
+        if (a != null && b != null && (length = a.length()) == b.length()) {
+            if (a instanceof String && b instanceof String) {
+                return a.equals(b);
+            } else {
+                for (int i = 0; i < length; i++) {
+                    if (a.charAt(i) != b.charAt(i)) return false;
+                }
+                return true;
+            }
+        }
+        return false;
+    }
+
+    // XXX currently this only reverses chars, not spans
+    public static CharSequence getReverse(CharSequence source,
+                                          int start, int end) {
+        return new Reverser(source, start, end);
+    }
+
+    private static class Reverser
+    implements CharSequence, GetChars
+    {
+        public Reverser(CharSequence source, int start, int end) {
+            mSource = source;
+            mStart = start;
+            mEnd = end;
+        }
+
+        public int length() {
+            return mEnd - mStart;
+        }
+
+        public CharSequence subSequence(int start, int end) {
+            char[] buf = new char[end - start];
+
+            getChars(start, end, buf, 0);
+            return new String(buf);
+        }
+
+        public String toString() {
+            return subSequence(0, length()).toString();
+        }
+
+        public char charAt(int off) {
+            return AndroidCharacter.getMirror(mSource.charAt(mEnd - 1 - off));
+        }
+
+        public void getChars(int start, int end, char[] dest, int destoff) {
+            TextUtils.getChars(mSource, start + mStart, end + mStart,
+                               dest, destoff);
+            AndroidCharacter.mirror(dest, 0, end - start);
+
+            int len = end - start;
+            int n = (end - start) / 2;
+            for (int i = 0; i < n; i++) {
+                char tmp = dest[destoff + i];
+
+                dest[destoff + i] = dest[destoff + len - i - 1];
+                dest[destoff + len - i - 1] = tmp;
+            }
+        }
+
+        private CharSequence mSource;
+        private int mStart;
+        private int mEnd;
+    }
+
+    /** @hide */
+    public static final int ALIGNMENT_SPAN = 1;
+    /** @hide */
+    public static final int FOREGROUND_COLOR_SPAN = 2;
+    /** @hide */
+    public static final int RELATIVE_SIZE_SPAN = 3;
+    /** @hide */
+    public static final int SCALE_X_SPAN = 4;
+    /** @hide */
+    public static final int STRIKETHROUGH_SPAN = 5;
+    /** @hide */
+    public static final int UNDERLINE_SPAN = 6;
+    /** @hide */
+    public static final int STYLE_SPAN = 7;
+    /** @hide */
+    public static final int BULLET_SPAN = 8;
+    /** @hide */
+    public static final int QUOTE_SPAN = 9;
+    /** @hide */
+    public static final int LEADING_MARGIN_SPAN = 10;
+    /** @hide */
+    public static final int URL_SPAN = 11;
+    /** @hide */
+    public static final int BACKGROUND_COLOR_SPAN = 12;
+    /** @hide */
+    public static final int TYPEFACE_SPAN = 13;
+    /** @hide */
+    public static final int SUPERSCRIPT_SPAN = 14;
+    /** @hide */
+    public static final int SUBSCRIPT_SPAN = 15;
+    /** @hide */
+    public static final int ABSOLUTE_SIZE_SPAN = 16;
+    /** @hide */
+    public static final int TEXT_APPEARANCE_SPAN = 17;
+    /** @hide */
+    public static final int ANNOTATION = 18;
+
+    /**
+     * Flatten a CharSequence and whatever styles can be copied across processes
+     * into the parcel.
+     */
+    public static void writeToParcel(CharSequence cs, Parcel p,
+            int parcelableFlags) {
+        if (cs instanceof Spanned) {
+            p.writeInt(0);
+            p.writeString(cs.toString());
+
+            Spanned sp = (Spanned) cs;
+            Object[] os = sp.getSpans(0, cs.length(), Object.class);
+
+            // note to people adding to this: check more specific types
+            // before more generic types.  also notice that it uses
+            // "if" instead of "else if" where there are interfaces
+            // so one object can be several.
+
+            for (int i = 0; i < os.length; i++) {
+                Object o = os[i];
+                Object prop = os[i];
+
+                if (prop instanceof CharacterStyle) {
+                    prop = ((CharacterStyle) prop).getUnderlying();
+                }
+
+                if (prop instanceof ParcelableSpan) {
+                    ParcelableSpan ps = (ParcelableSpan)prop;
+                    p.writeInt(ps.getSpanTypeId());
+                    ps.writeToParcel(p, parcelableFlags);
+                    writeWhere(p, sp, o);
+                }
+            }
+
+            p.writeInt(0);
+        } else {
+            p.writeInt(1);
+            if (cs != null) {
+                p.writeString(cs.toString());
+            } else {
+                p.writeString(null);
+            }
+        }
+    }
+
+    private static void writeWhere(Parcel p, Spanned sp, Object o) {
+        p.writeInt(sp.getSpanStart(o));
+        p.writeInt(sp.getSpanEnd(o));
+        p.writeInt(sp.getSpanFlags(o));
+    }
+
+    public static final Parcelable.Creator<CharSequence> CHAR_SEQUENCE_CREATOR
+            = new Parcelable.Creator<CharSequence>() {
+        /**
+         * Read and return a new CharSequence, possibly with styles,
+         * from the parcel.
+         */
+        public  CharSequence createFromParcel(Parcel p) {
+            int kind = p.readInt();
+
+            if (kind == 1)
+                return p.readString();
+
+            SpannableString sp = new SpannableString(p.readString());
+
+            while (true) {
+                kind = p.readInt();
+
+                if (kind == 0)
+                    break;
+
+                switch (kind) {
+                case ALIGNMENT_SPAN:
+                    readSpan(p, sp, new AlignmentSpan.Standard(p));
+                    break;
+
+                case FOREGROUND_COLOR_SPAN:
+                    readSpan(p, sp, new ForegroundColorSpan(p));
+                    break;
+
+                case RELATIVE_SIZE_SPAN:
+                    readSpan(p, sp, new RelativeSizeSpan(p));
+                    break;
+
+                case SCALE_X_SPAN:
+                    readSpan(p, sp, new ScaleXSpan(p));
+                    break;
+
+                case STRIKETHROUGH_SPAN:
+                    readSpan(p, sp, new StrikethroughSpan(p));
+                    break;
+
+                case UNDERLINE_SPAN:
+                    readSpan(p, sp, new UnderlineSpan(p));
+                    break;
+
+                case STYLE_SPAN:
+                    readSpan(p, sp, new StyleSpan(p));
+                    break;
+
+                case BULLET_SPAN:
+                    readSpan(p, sp, new BulletSpan(p));
+                    break;
+
+                case QUOTE_SPAN:
+                    readSpan(p, sp, new QuoteSpan(p));
+                    break;
+
+                case LEADING_MARGIN_SPAN:
+                    readSpan(p, sp, new LeadingMarginSpan.Standard(p));
+                break;
+
+                case URL_SPAN:
+                    readSpan(p, sp, new URLSpan(p));
+                    break;
+
+                case BACKGROUND_COLOR_SPAN:
+                    readSpan(p, sp, new BackgroundColorSpan(p));
+                    break;
+
+                case TYPEFACE_SPAN:
+                    readSpan(p, sp, new TypefaceSpan(p));
+                    break;
+
+                case SUPERSCRIPT_SPAN:
+                    readSpan(p, sp, new SuperscriptSpan(p));
+                    break;
+
+                case SUBSCRIPT_SPAN:
+                    readSpan(p, sp, new SubscriptSpan(p));
+                    break;
+
+                case ABSOLUTE_SIZE_SPAN:
+                    readSpan(p, sp, new AbsoluteSizeSpan(p));
+                    break;
+
+                case TEXT_APPEARANCE_SPAN:
+                    readSpan(p, sp, new TextAppearanceSpan(p));
+                    break;
+
+                case ANNOTATION:
+                    readSpan(p, sp, new Annotation(p));
+                    break;
+
+                default:
+                    throw new RuntimeException("bogus span encoding " + kind);
+                }
+            }
+
+            return sp;
+        }
+
+        public CharSequence[] newArray(int size)
+        {
+            return new CharSequence[size];
+        }
+    };
+
+    /**
+     * Debugging tool to print the spans in a CharSequence.  The output will
+     * be printed one span per line.  If the CharSequence is not a Spanned,
+     * then the entire string will be printed on a single line.
+     */
+    public static void dumpSpans(CharSequence cs, Printer printer, String prefix) {
+        if (cs instanceof Spanned) {
+            Spanned sp = (Spanned) cs;
+            Object[] os = sp.getSpans(0, cs.length(), Object.class);
+
+            for (int i = 0; i < os.length; i++) {
+                Object o = os[i];
+                printer.println(prefix + cs.subSequence(sp.getSpanStart(o),
+                        sp.getSpanEnd(o)) + ": "
+                        + Integer.toHexString(System.identityHashCode(o))
+                        + " " + o.getClass().getCanonicalName()
+                         + " (" + sp.getSpanStart(o) + "-" + sp.getSpanEnd(o)
+                         + ") fl=#" + sp.getSpanFlags(o));
+            }
+        } else {
+            printer.println(prefix + cs + ": (no spans)");
+        }
+    }
+
+    /**
+     * Return a new CharSequence in which each of the source strings is
+     * replaced by the corresponding element of the destinations.
+     */
+    public static CharSequence replace(CharSequence template,
+                                       String[] sources,
+                                       CharSequence[] destinations) {
+        SpannableStringBuilder tb = new SpannableStringBuilder(template);
+
+        for (int i = 0; i < sources.length; i++) {
+            int where = indexOf(tb, sources[i]);
+
+            if (where >= 0)
+                tb.setSpan(sources[i], where, where + sources[i].length(),
+                           Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+        }
+
+        for (int i = 0; i < sources.length; i++) {
+            int start = tb.getSpanStart(sources[i]);
+            int end = tb.getSpanEnd(sources[i]);
+
+            if (start >= 0) {
+                tb.replace(start, end, destinations[i]);
+            }
+        }
+
+        return tb;
+    }
+
+    /**
+     * Replace instances of "^1", "^2", etc. in the
+     * <code>template</code> CharSequence with the corresponding
+     * <code>values</code>.  "^^" is used to produce a single caret in
+     * the output.  Only up to 9 replacement values are supported,
+     * "^10" will be produce the first replacement value followed by a
+     * '0'.
+     *
+     * @param template the input text containing "^1"-style
+     * placeholder values.  This object is not modified; a copy is
+     * returned.
+     *
+     * @param values CharSequences substituted into the template.  The
+     * first is substituted for "^1", the second for "^2", and so on.
+     *
+     * @return the new CharSequence produced by doing the replacement
+     *
+     * @throws IllegalArgumentException if the template requests a
+     * value that was not provided, or if more than 9 values are
+     * provided.
+     */
+    public static CharSequence expandTemplate(CharSequence template,
+                                              CharSequence... values) {
+        if (values.length > 9) {
+            throw new IllegalArgumentException("max of 9 values are supported");
+        }
+
+        SpannableStringBuilder ssb = new SpannableStringBuilder(template);
+
+        try {
+            int i = 0;
+            while (i < ssb.length()) {
+                if (ssb.charAt(i) == '^') {
+                    char next = ssb.charAt(i+1);
+                    if (next == '^') {
+                        ssb.delete(i+1, i+2);
+                        ++i;
+                        continue;
+                    } else if (Character.isDigit(next)) {
+                        int which = Character.getNumericValue(next) - 1;
+                        if (which < 0) {
+                            throw new IllegalArgumentException(
+                                "template requests value ^" + (which+1));
+                        }
+                        if (which >= values.length) {
+                            throw new IllegalArgumentException(
+                                "template requests value ^" + (which+1) +
+                                "; only " + values.length + " provided");
+                        }
+                        ssb.replace(i, i+2, values[which]);
+                        i += values[which].length();
+                        continue;
+                    }
+                }
+                ++i;
+            }
+        } catch (IndexOutOfBoundsException ignore) {
+            // happens when ^ is the last character in the string.
+        }
+        return ssb;
+    }
+
+    public static int getOffsetBefore(CharSequence text, int offset) {
+        if (offset == 0)
+            return 0;
+        if (offset == 1)
+            return 0;
+
+        char c = text.charAt(offset - 1);
+
+        if (c >= '\uDC00' && c <= '\uDFFF') {
+            char c1 = text.charAt(offset - 2);
+
+            if (c1 >= '\uD800' && c1 <= '\uDBFF')
+                offset -= 2;
+            else
+                offset -= 1;
+        } else {
+            offset -= 1;
+        }
+
+        if (text instanceof Spanned) {
+            ReplacementSpan[] spans = ((Spanned) text).getSpans(offset, offset,
+                                                       ReplacementSpan.class);
+
+            for (int i = 0; i < spans.length; i++) {
+                int start = ((Spanned) text).getSpanStart(spans[i]);
+                int end = ((Spanned) text).getSpanEnd(spans[i]);
+
+                if (start < offset && end > offset)
+                    offset = start;
+            }
+        }
+
+        return offset;
+    }
+
+    public static int getOffsetAfter(CharSequence text, int offset) {
+        int len = text.length();
+
+        if (offset == len)
+            return len;
+        if (offset == len - 1)
+            return len;
+
+        char c = text.charAt(offset);
+
+        if (c >= '\uD800' && c <= '\uDBFF') {
+            char c1 = text.charAt(offset + 1);
+
+            if (c1 >= '\uDC00' && c1 <= '\uDFFF')
+                offset += 2;
+            else
+                offset += 1;
+        } else {
+            offset += 1;
+        }
+
+        if (text instanceof Spanned) {
+            ReplacementSpan[] spans = ((Spanned) text).getSpans(offset, offset,
+                                                       ReplacementSpan.class);
+
+            for (int i = 0; i < spans.length; i++) {
+                int start = ((Spanned) text).getSpanStart(spans[i]);
+                int end = ((Spanned) text).getSpanEnd(spans[i]);
+
+                if (start < offset && end > offset)
+                    offset = end;
+            }
+        }
+
+        return offset;
+    }
+
+    private static void readSpan(Parcel p, Spannable sp, Object o) {
+        sp.setSpan(o, p.readInt(), p.readInt(), p.readInt());
+    }
+
+    public static void copySpansFrom(Spanned source, int start, int end,
+                                     Class kind,
+                                     Spannable dest, int destoff) {
+        if (kind == null) {
+            kind = Object.class;
+        }
+
+        Object[] spans = source.getSpans(start, end, kind);
+
+        for (int i = 0; i < spans.length; i++) {
+            int st = source.getSpanStart(spans[i]);
+            int en = source.getSpanEnd(spans[i]);
+            int fl = source.getSpanFlags(spans[i]);
+
+            if (st < start)
+                st = start;
+            if (en > end)
+                en = end;
+
+            dest.setSpan(spans[i], st - start + destoff, en - start + destoff,
+                         fl);
+        }
+    }
+
+    public enum TruncateAt {
+        START,
+        MIDDLE,
+        END,
+        MARQUEE,
+    }
+
+    public interface EllipsizeCallback {
+        /**
+         * This method is called to report that the specified region of
+         * text was ellipsized away by a call to {@link #ellipsize}.
+         */
+        public void ellipsized(int start, int end);
+    }
+
+    private static String sEllipsis = null;
+
+    /**
+     * Returns the original text if it fits in the specified width
+     * given the properties of the specified Paint,
+     * or, if it does not fit, a truncated
+     * copy with ellipsis character added at the specified edge or center.
+     */
+    public static CharSequence ellipsize(CharSequence text,
+                                         TextPaint p,
+                                         float avail, TruncateAt where) {
+        return ellipsize(text, p, avail, where, false, null);
+    }
+
+    /**
+     * Returns the original text if it fits in the specified width
+     * given the properties of the specified Paint,
+     * or, if it does not fit, a copy with ellipsis character added 
+     * at the specified edge or center.
+     * If <code>preserveLength</code> is specified, the returned copy
+     * will be padded with zero-width spaces to preserve the original
+     * length and offsets instead of truncating.
+     * If <code>callback</code> is non-null, it will be called to
+     * report the start and end of the ellipsized range.
+     */
+    public static CharSequence ellipsize(CharSequence text,
+                                         TextPaint p,
+                                         float avail, TruncateAt where,
+                                         boolean preserveLength,
+                                         EllipsizeCallback callback) {
+        if (sEllipsis == null) {
+            Resources r = Resources.getSystem();
+            sEllipsis = r.getString(R.string.ellipsis);
+        }
+
+        int len = text.length();
+
+        // Use Paint.breakText() for the non-Spanned case to avoid having
+        // to allocate memory and accumulate the character widths ourselves.
+
+        if (!(text instanceof Spanned)) {
+            float wid = p.measureText(text, 0, len);
+
+            if (wid <= avail) {
+                if (callback != null) {
+                    callback.ellipsized(0, 0);
+                }
+
+                return text;
+            }
+
+            float ellipsiswid = p.measureText(sEllipsis);
+
+            if (ellipsiswid > avail) {
+                if (callback != null) {
+                    callback.ellipsized(0, len);
+                }
+
+                if (preserveLength) {
+                    char[] buf = obtain(len);
+                    for (int i = 0; i < len; i++) {
+                        buf[i] = '\uFEFF';
+                    }
+                    String ret = new String(buf, 0, len);
+                    recycle(buf);
+                    return ret;
+                } else {
+                    return "";
+                }
+            }
+
+            if (where == TruncateAt.START) {
+                int fit = p.breakText(text, 0, len, false,
+                                      avail - ellipsiswid, null);
+
+                if (callback != null) {
+                    callback.ellipsized(0, len - fit);
+                }
+
+                if (preserveLength) {
+                    return blank(text, 0, len - fit);
+                } else {
+                    return sEllipsis + text.toString().substring(len - fit, len);
+                }
+            } else if (where == TruncateAt.END) {
+                int fit = p.breakText(text, 0, len, true,
+                                      avail - ellipsiswid, null);
+
+                if (callback != null) {
+                    callback.ellipsized(fit, len);
+                }
+
+                if (preserveLength) {
+                    return blank(text, fit, len);
+                } else {
+                    return text.toString().substring(0, fit) + sEllipsis;
+                } 
+            } else /* where == TruncateAt.MIDDLE */ {
+                int right = p.breakText(text, 0, len, false,
+                                        (avail - ellipsiswid) / 2, null);
+                float used = p.measureText(text, len - right, len);
+                int left = p.breakText(text, 0, len - right, true,
+                                       avail - ellipsiswid - used, null);
+
+                if (callback != null) {
+                    callback.ellipsized(left, len - right);
+                }
+
+                if (preserveLength) {
+                    return blank(text, left, len - right);
+                } else {
+                    String s = text.toString();
+                    return s.substring(0, left) + sEllipsis +
+                           s.substring(len - right, len);
+                }
+            }
+        }
+
+        // But do the Spanned cases by hand, because it's such a pain
+        // to iterate the span transitions backwards and getTextWidths()
+        // will give us the information we need.
+
+        // getTextWidths() always writes into the start of the array,
+        // so measure each span into the first half and then copy the
+        // results into the second half to use later.
+
+        float[] wid = new float[len * 2];
+        TextPaint temppaint = new TextPaint();
+        Spanned sp = (Spanned) text;
+
+        int next;
+        for (int i = 0; i < len; i = next) {
+            next = sp.nextSpanTransition(i, len, MetricAffectingSpan.class);
+
+            Styled.getTextWidths(p, temppaint, sp, i, next, wid, null);
+            System.arraycopy(wid, 0, wid, len + i, next - i);
+        }
+
+        float sum = 0;
+        for (int i = 0; i < len; i++) {
+            sum += wid[len + i];
+        }
+
+        if (sum <= avail) {
+            if (callback != null) {
+                callback.ellipsized(0, 0);
+            }
+
+            return text;
+        }
+
+        float ellipsiswid = p.measureText(sEllipsis);
+
+        if (ellipsiswid > avail) {
+            if (callback != null) {
+                callback.ellipsized(0, len);
+            }
+
+            if (preserveLength) {
+                char[] buf = obtain(len);
+                for (int i = 0; i < len; i++) {
+                    buf[i] = '\uFEFF';
+                }
+                SpannableString ss = new SpannableString(new String(buf, 0, len));
+                recycle(buf);
+                copySpansFrom(sp, 0, len, Object.class, ss, 0);
+                return ss;
+            } else {
+                return "";
+            }
+        }
+
+        if (where == TruncateAt.START) {
+            sum = 0;
+            int i;
+
+            for (i = len; i >= 0; i--) {
+                float w = wid[len + i - 1];
+
+                if (w + sum + ellipsiswid > avail) {
+                    break;
+                }
+
+                sum += w;
+            }
+
+            if (callback != null) {
+                callback.ellipsized(0, i);
+            }
+
+            if (preserveLength) {
+                SpannableString ss = new SpannableString(blank(text, 0, i));
+                copySpansFrom(sp, 0, len, Object.class, ss, 0);
+                return ss;
+            } else {
+                SpannableStringBuilder out = new SpannableStringBuilder(sEllipsis);
+                out.insert(1, text, i, len);
+
+                return out;
+            }
+        } else if (where == TruncateAt.END) {
+            sum = 0;
+            int i;
+
+            for (i = 0; i < len; i++) {
+                float w = wid[len + i];
+
+                if (w + sum + ellipsiswid > avail) {
+                    break;
+                }
+
+                sum += w;
+            }
+
+            if (callback != null) {
+                callback.ellipsized(i, len);
+            }
+
+            if (preserveLength) {
+                SpannableString ss = new SpannableString(blank(text, i, len));
+                copySpansFrom(sp, 0, len, Object.class, ss, 0);
+                return ss;
+            } else {
+                SpannableStringBuilder out = new SpannableStringBuilder(sEllipsis);
+                out.insert(0, text, 0, i);
+
+                return out;
+            }
+        } else /* where = TruncateAt.MIDDLE */ {
+            float lsum = 0, rsum = 0;
+            int left = 0, right = len;
+
+            float ravail = (avail - ellipsiswid) / 2;
+            for (right = len; right >= 0; right--) {
+                float w = wid[len + right - 1];
+
+                if (w + rsum > ravail) {
+                    break;
+                }
+
+                rsum += w;
+            }
+
+            float lavail = avail - ellipsiswid - rsum;
+            for (left = 0; left < right; left++) {
+                float w = wid[len + left];
+
+                if (w + lsum > lavail) {
+                    break;
+                }
+
+                lsum += w;
+            }
+
+            if (callback != null) {
+                callback.ellipsized(left, right);
+            }
+
+            if (preserveLength) {
+                SpannableString ss = new SpannableString(blank(text, left, right));
+                copySpansFrom(sp, 0, len, Object.class, ss, 0);
+                return ss;
+            } else {
+                SpannableStringBuilder out = new SpannableStringBuilder(sEllipsis);
+                out.insert(0, text, 0, left);
+                out.insert(out.length(), text, right, len);
+
+                return out;
+            }
+        }
+    }
+
+    private static String blank(CharSequence source, int start, int end) {
+        int len = source.length();
+        char[] buf = obtain(len);
+
+        if (start != 0) {
+            getChars(source, 0, start, buf, 0);
+        }
+        if (end != len) {
+            getChars(source, end, len, buf, end);
+        }
+
+        if (start != end) {
+            buf[start] = '\u2026';
+
+            for (int i = start + 1; i < end; i++) {
+                buf[i] = '\uFEFF';
+            }
+        }
+    
+        String ret = new String(buf, 0, len);
+        recycle(buf);
+
+        return ret;
+    }
+
+    /**
+     * Converts a CharSequence of the comma-separated form "Andy, Bob,
+     * Charles, David" that is too wide to fit into the specified width
+     * into one like "Andy, Bob, 2 more".
+     *
+     * @param text the text to truncate
+     * @param p the Paint with which to measure the text
+     * @param avail the horizontal width available for the text
+     * @param oneMore the string for "1 more" in the current locale
+     * @param more the string for "%d more" in the current locale
+     */
+    public static CharSequence commaEllipsize(CharSequence text,
+                                              TextPaint p, float avail,
+                                              String oneMore,
+                                              String more) {
+        int len = text.length();
+        char[] buf = new char[len];
+        TextUtils.getChars(text, 0, len, buf, 0);
+
+        int commaCount = 0;
+        for (int i = 0; i < len; i++) {
+            if (buf[i] == ',') {
+                commaCount++;
+            }
+        }
+
+        float[] wid;
+
+        if (text instanceof Spanned) {
+            Spanned sp = (Spanned) text;
+            TextPaint temppaint = new TextPaint();
+            wid = new float[len * 2];
+
+            int next;
+            for (int i = 0; i < len; i = next) {
+                next = sp.nextSpanTransition(i, len, MetricAffectingSpan.class);
+
+                Styled.getTextWidths(p, temppaint, sp, i, next, wid, null);
+                System.arraycopy(wid, 0, wid, len + i, next - i);
+            }
+
+            System.arraycopy(wid, len, wid, 0, len);
+        } else {
+            wid = new float[len];
+            p.getTextWidths(text, 0, len, wid);
+        }
+
+        int ok = 0;
+        int okRemaining = commaCount + 1;
+        String okFormat = "";
+
+        int w = 0;
+        int count = 0;
+
+        for (int i = 0; i < len; i++) {
+            w += wid[i];
+
+            if (buf[i] == ',') {
+                count++;
+
+                int remaining = commaCount - count + 1;
+                float moreWid;
+                String format;
+
+                if (remaining == 1) {
+                    format = " " + oneMore;
+                } else {
+                    format = " " + String.format(more, remaining);
+                }
+
+                moreWid = p.measureText(format);
+
+                if (w + moreWid <= avail) {
+                    ok = i + 1;
+                    okRemaining = remaining;
+                    okFormat = format;
+                }
+            }
+        }
+
+        if (w <= avail) {
+            return text;
+        } else {
+            SpannableStringBuilder out = new SpannableStringBuilder(okFormat);
+            out.insert(0, text, 0, ok);
+            return out;
+        }
+    }
+
+    /* package */ static char[] obtain(int len) {
+        char[] buf;
+
+        synchronized (sLock) {
+            buf = sTemp;
+            sTemp = null;
+        }
+
+        if (buf == null || buf.length < len)
+            buf = new char[ArrayUtils.idealCharArraySize(len)];
+
+        return buf;
+    }
+
+    /* package */ static void recycle(char[] temp) {
+        if (temp.length > 1000)
+            return;
+
+        synchronized (sLock) {
+            sTemp = temp;
+        }
+    }
+
+    /**
+     * Html-encode the string.
+     * @param s the string to be encoded
+     * @return the encoded string
+     */
+    public static String htmlEncode(String s) {
+        StringBuilder sb = new StringBuilder();
+        char c;
+        for (int i = 0; i < s.length(); i++) {
+            c = s.charAt(i);
+            switch (c) {
+            case '<':
+                sb.append("&lt;"); //$NON-NLS-1$
+                break;
+            case '>':
+                sb.append("&gt;"); //$NON-NLS-1$
+                break;
+            case '&':
+                sb.append("&amp;"); //$NON-NLS-1$
+                break;
+            case '\'':
+                sb.append("&apos;"); //$NON-NLS-1$
+                break;
+            case '"':
+                sb.append("&quot;"); //$NON-NLS-1$
+                break;
+            default:
+                sb.append(c);
+            }
+        }
+        return sb.toString();
+    }
+
+    /**
+     * Returns a CharSequence concatenating the specified CharSequences,
+     * retaining their spans if any.
+     */
+    public static CharSequence concat(CharSequence... text) {
+        if (text.length == 0) {
+            return "";
+        }
+
+        if (text.length == 1) {
+            return text[0];
+        }
+
+        boolean spanned = false;
+        for (int i = 0; i < text.length; i++) {
+            if (text[i] instanceof Spanned) {
+                spanned = true;
+                break;
+            }
+        }
+
+        StringBuilder sb = new StringBuilder();
+        for (int i = 0; i < text.length; i++) {
+            sb.append(text[i]);
+        }
+
+        if (!spanned) {
+            return sb.toString();
+        }
+
+        SpannableString ss = new SpannableString(sb);
+        int off = 0;
+        for (int i = 0; i < text.length; i++) {
+            int len = text[i].length();
+
+            if (text[i] instanceof Spanned) {
+                copySpansFrom((Spanned) text[i], 0, len, Object.class, ss, off);
+            }
+
+            off += len;
+        }
+
+        return new SpannedString(ss);
+    }
+
+    /**
+     * Returns whether the given CharSequence contains any printable characters.
+     */
+    public static boolean isGraphic(CharSequence str) {
+        final int len = str.length();
+        for (int i=0; i<len; i++) {
+            int gc = Character.getType(str.charAt(i));
+            if (gc != Character.CONTROL
+                    && gc != Character.FORMAT
+                    && gc != Character.SURROGATE
+                    && gc != Character.UNASSIGNED
+                    && gc != Character.LINE_SEPARATOR
+                    && gc != Character.PARAGRAPH_SEPARATOR
+                    && gc != Character.SPACE_SEPARATOR) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Returns whether this character is a printable character.
+     */
+    public static boolean isGraphic(char c) {
+        int gc = Character.getType(c);
+        return     gc != Character.CONTROL
+                && gc != Character.FORMAT
+                && gc != Character.SURROGATE
+                && gc != Character.UNASSIGNED
+                && gc != Character.LINE_SEPARATOR
+                && gc != Character.PARAGRAPH_SEPARATOR
+                && gc != Character.SPACE_SEPARATOR;
+    }
+
+    /**
+     * Returns whether the given CharSequence contains only digits.
+     */
+    public static boolean isDigitsOnly(CharSequence str) {
+        final int len = str.length();
+        for (int i = 0; i < len; i++) {
+            if (!Character.isDigit(str.charAt(i))) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Capitalization mode for {@link #getCapsMode}: capitalize all
+     * characters.  This value is explicitly defined to be the same as
+     * {@link InputType#TYPE_TEXT_FLAG_CAP_CHARACTERS}.
+     */
+    public static final int CAP_MODE_CHARACTERS
+            = InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS;
+    
+    /**
+     * Capitalization mode for {@link #getCapsMode}: capitalize the first
+     * character of all words.  This value is explicitly defined to be the same as
+     * {@link InputType#TYPE_TEXT_FLAG_CAP_WORDS}.
+     */
+    public static final int CAP_MODE_WORDS
+            = InputType.TYPE_TEXT_FLAG_CAP_WORDS;
+    
+    /**
+     * Capitalization mode for {@link #getCapsMode}: capitalize the first
+     * character of each sentence.  This value is explicitly defined to be the same as
+     * {@link InputType#TYPE_TEXT_FLAG_CAP_SENTENCES}.
+     */
+    public static final int CAP_MODE_SENTENCES
+            = InputType.TYPE_TEXT_FLAG_CAP_SENTENCES;
+    
+    /**
+     * Determine what caps mode should be in effect at the current offset in
+     * the text.  Only the mode bits set in <var>reqModes</var> will be
+     * checked.  Note that the caps mode flags here are explicitly defined
+     * to match those in {@link InputType}.
+     * 
+     * @param cs The text that should be checked for caps modes.
+     * @param off Location in the text at which to check.
+     * @param reqModes The modes to be checked: may be any combination of
+     * {@link #CAP_MODE_CHARACTERS}, {@link #CAP_MODE_WORDS}, and
+     * {@link #CAP_MODE_SENTENCES}.
+     * 
+     * @return Returns the actual capitalization modes that can be in effect
+     * at the current position, which is any combination of
+     * {@link #CAP_MODE_CHARACTERS}, {@link #CAP_MODE_WORDS}, and
+     * {@link #CAP_MODE_SENTENCES}.
+     */
+    public static int getCapsMode(CharSequence cs, int off, int reqModes) {
+        int i;
+        char c;
+        int mode = 0;
+
+        if ((reqModes&CAP_MODE_CHARACTERS) != 0) {
+            mode |= CAP_MODE_CHARACTERS;
+        }
+        if ((reqModes&(CAP_MODE_WORDS|CAP_MODE_SENTENCES)) == 0) {
+            return mode;
+        }
+
+        // Back over allowed opening punctuation.
+
+        for (i = off; i > 0; i--) {
+            c = cs.charAt(i - 1);
+
+            if (c != '"' && c != '\'' &&
+                Character.getType(c) != Character.START_PUNCTUATION) {
+                break;
+            }
+        }
+
+        // Start of paragraph, with optional whitespace.
+
+        int j = i;
+        while (j > 0 && ((c = cs.charAt(j - 1)) == ' ' || c == '\t')) {
+            j--;
+        }
+        if (j == 0 || cs.charAt(j - 1) == '\n') {
+            return mode | CAP_MODE_WORDS;
+        }
+
+        // Or start of word if we are that style.
+
+        if ((reqModes&CAP_MODE_SENTENCES) == 0) {
+            if (i != j) mode |= CAP_MODE_WORDS;
+            return mode;
+        }
+
+        // There must be a space if not the start of paragraph.
+
+        if (i == j) {
+            return mode;
+        }
+
+        // Back over allowed closing punctuation.
+
+        for (; j > 0; j--) {
+            c = cs.charAt(j - 1);
+
+            if (c != '"' && c != '\'' &&
+                Character.getType(c) != Character.END_PUNCTUATION) {
+                break;
+            }
+        }
+
+        if (j > 0) {
+            c = cs.charAt(j - 1);
+
+            if (c == '.' || c == '?' || c == '!') {
+                // Do not capitalize if the word ends with a period but
+                // also contains a period, in which case it is an abbreviation.
+
+                if (c == '.') {
+                    for (int k = j - 2; k >= 0; k--) {
+                        c = cs.charAt(k);
+
+                        if (c == '.') {
+                            return mode;
+                        }
+
+                        if (!Character.isLetter(c)) {
+                            break;
+                        }
+                    }
+                }
+
+                return mode | CAP_MODE_SENTENCES;
+            }
+        }
+
+        return mode;
+    }
+    
+    private static Object sLock = new Object();
+    private static char[] sTemp = null;
+}
diff --git a/core/java/android/text/TextWatcher.java b/core/java/android/text/TextWatcher.java
new file mode 100644
index 0000000..bad09f2
--- /dev/null
+++ b/core/java/android/text/TextWatcher.java
@@ -0,0 +1,57 @@
+/*
+ * 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 android.text;
+
+/**
+ * When an object of a type is attached to an Editable, its methods will
+ * be called when the text is changed.
+ */
+public interface TextWatcher extends NoCopySpan {
+    /**
+     * This method is called to notify you that, within <code>s</code>,
+     * the <code>count</code> characters beginning at <code>start</code>
+     * are about to be replaced by new text with length <code>after</code>.
+     * It is an error to attempt to make changes to <code>s</code> from
+     * this callback.
+     */
+    public void beforeTextChanged(CharSequence s, int start,
+                                  int count, int after);
+    /**
+     * This method is called to notify you that, within <code>s</code>,
+     * the <code>count</code> characters beginning at <code>start</code>
+     * have just replaced old text that had length <code>before</code>.
+     * It is an error to attempt to make changes to <code>s</code> from
+     * this callback.
+     */
+    public void onTextChanged(CharSequence s, int start, int before, int count);
+
+    /**
+     * This method is called to notify you that, somewhere within
+     * <code>s</code>, the text has been changed.
+     * It is legitimate to make further changes to <code>s</code> from
+     * this callback, but be careful not to get yourself into an infinite
+     * loop, because any changes you make will cause this method to be
+     * called again recursively.
+     * (You are not told where the change took place because other
+     * afterTextChanged() methods may already have made other changes
+     * and invalidated the offsets.  But if you need to know here,
+     * you can use {@link Spannable#setSpan} in {@link #onTextChanged}
+     * to mark your place and then look up from here where the span
+     * ended up.
+     */
+    public void afterTextChanged(Editable s);
+}
diff --git a/core/java/android/text/format/DateFormat.java b/core/java/android/text/format/DateFormat.java
new file mode 100644
index 0000000..0dc96c3
--- /dev/null
+++ b/core/java/android/text/format/DateFormat.java
@@ -0,0 +1,585 @@
+/*
+ * 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 android.text.format;
+
+import android.content.Context;
+import android.provider.Settings;
+import android.text.SpannableStringBuilder;
+import android.text.Spanned;
+import android.text.SpannedString;
+
+import com.android.internal.R;
+
+import java.util.Calendar;
+import java.util.Date;
+import java.util.GregorianCalendar;
+import java.util.Locale;
+import java.util.TimeZone;
+import java.text.SimpleDateFormat;
+
+/**
+    Utility class for producing strings with formatted date/time.
+
+    <p>
+    This class takes as inputs a format string and a representation of a date/time.
+    The format string controls how the output is generated.
+    </p>
+    <p>
+    Formatting characters may be repeated in order to get more detailed representations
+    of that field.  For instance, the format character &apos;M&apos; is used to
+    represent the month.  Depending on how many times that character is repeated
+    you get a different representation.
+    </p>
+    <p>
+    For the month of September:<br/>
+    M -&gt; 9<br/>
+    MM -&gt; 09<br/>
+    MMM -&gt; Sep<br/>
+    MMMM -&gt; September
+    </p>
+    <p>
+    The effects of the duplication vary depending on the nature of the field.
+    See the notes on the individual field formatters for details.  For purely numeric
+    fields such as <code>HOUR</code> adding more copies of the designator will
+    zero-pad the value to that number of characters.
+    </p>
+    <p>
+    For 7 minutes past the hour:<br/>
+    m -&gt; 7<br/>
+    mm -&gt; 07<br/>
+    mmm -&gt; 007<br/>
+    mmmm -&gt; 0007
+    </p>
+    <p>
+    Examples for April 6, 1970 at 3:23am:<br/>
+    &quot;MM/dd/yy h:mmaa&quot; -&gt; &quot;04/06/70 3:23am&quot<br/>
+    &quot;MMM dd, yyyy h:mmaa&quot; -&gt; &quot;Apr 6, 1970 3:23am&quot<br/>
+    &quot;MMMM dd, yyyy h:mmaa&quot; -&gt; &quot;April 6, 1970 3:23am&quot<br/>
+    &quot;E, MMMM dd, yyyy h:mmaa&quot; -&gt; &quot;Mon, April 6, 1970 3:23am&<br/>
+    &quot;EEEE, MMMM dd, yyyy h:mmaa&quot; -&gt; &quot;Monday, April 6, 1970 3:23am&quot;<br/>
+    &quot;&apos;Noteworthy day: &apos;M/d/yy&quot; -&gt; &quot;Noteworthy day: 4/6/70&quot;
+ */
+
+public class DateFormat {
+    /**
+        Text in the format string that should be copied verbatim rather that
+        interpreted as formatting codes must be surrounded by the <code>QUOTE</code>
+        character.  If you need to embed a literal <code>QUOTE</code> character in
+        the output text then use two in a row.
+     */
+    public  static final char    QUOTE                  =    '\'';
+    
+    /**
+        This designator indicates whether the <code>HOUR</code> field is before
+        or after noon.  The output is lower-case.
+     
+        Examples:
+        a -> a or p
+        aa -> am or pm
+     */
+    public  static final char    AM_PM                  =    'a';
+
+    /**
+        This designator indicates whether the <code>HOUR</code> field is before
+        or after noon.  The output is capitalized.
+     
+        Examples:
+        A -> A or P
+        AA -> AM or PM
+     */
+    public  static final char    CAPITAL_AM_PM          =    'A';
+
+    /**
+        This designator indicates the day of the month.
+         
+        Examples for the 9th of the month:
+        d -> 9
+        dd -> 09
+     */
+    public  static final char    DATE                   =    'd';
+
+    /**
+        This designator indicates the name of the day of the week.
+     
+        Examples for Sunday:
+        E -> Sun
+        EEEE -> Sunday
+     */
+    public  static final char    DAY                    =    'E';
+
+    /**
+        This designator indicates the hour of the day in 12 hour format.
+     
+        Examples for 3pm:
+        h -> 3
+        hh -> 03
+     */
+    public  static final char    HOUR                   =    'h';
+
+    /**
+        This designator indicates the hour of the day in 24 hour format.
+     
+        Example for 3pm:
+        k -> 15
+
+        Examples for midnight:
+        k -> 0
+        kk -> 00
+     */
+    public  static final char    HOUR_OF_DAY            =    'k';
+
+    /**
+        This designator indicates the minute of the hour.
+     
+        Examples for 7 minutes past the hour:
+        m -> 7
+        mm -> 07
+     */
+    public  static final char    MINUTE                 =    'm';
+
+    /**
+        This designator indicates the month of the year
+     
+        Examples for September:
+        M -> 9
+        MM -> 09
+        MMM -> Sep
+        MMMM -> September
+     */
+    public  static final char    MONTH                  =    'M';
+
+    /**
+        This designator indicates the seconds of the minute.
+     
+        Examples for 7 seconds past the minute:
+        s -> 7
+        ss -> 07
+     */
+    public  static final char    SECONDS                =    's';
+
+    /**
+        This designator indicates the offset of the timezone from GMT.
+     
+        Example for US/Pacific timezone:
+        z -> -0800
+        zz -> PST
+     */
+    public  static final char    TIME_ZONE              =    'z';
+
+    /**
+        This designator indicates the year.
+     
+        Examples for 2006
+        y -> 06
+        yyyy -> 2006
+     */
+    public  static final char    YEAR                   =    'y';
+
+
+    private static final Object sLocaleLock = new Object();
+    private static Locale sIs24HourLocale;
+    private static boolean sIs24Hour;
+
+
+    /**
+     * Returns true if user preference is set to 24-hour format.
+     * @param context the context to use for the content resolver
+     * @return true if 24 hour time format is selected, false otherwise.
+     */
+    public static boolean is24HourFormat(Context context) {
+        String value = Settings.System.getString(context.getContentResolver(),
+                Settings.System.TIME_12_24);
+
+        if (value == null) {
+            Locale locale = context.getResources().getConfiguration().locale;
+
+            synchronized (sLocaleLock) {
+                if (sIs24HourLocale != null && sIs24HourLocale.equals(locale)) {
+                    return sIs24Hour;
+                }
+            }
+
+            java.text.DateFormat natural =
+                java.text.DateFormat.getTimeInstance(
+                    java.text.DateFormat.LONG, locale);
+
+            if (natural instanceof SimpleDateFormat) {
+                SimpleDateFormat sdf = (SimpleDateFormat) natural;
+                String pattern = sdf.toPattern();
+
+                if (pattern.indexOf('H') >= 0) {
+                    value = "24";
+                } else {
+                    value = "12";
+                }
+            } else {
+                value = "12";
+            }
+
+            synchronized (sLocaleLock) {
+                sIs24HourLocale = locale;
+                sIs24Hour = !value.equals("12");
+            }
+        }
+
+        boolean b24 =  !(value == null || value.equals("12"));
+        return b24;
+    }
+
+    /**
+     * Returns a {@link java.text.DateFormat} object that can format the time according
+     * to the current user preference. 
+     * @param context the application context
+     * @return the {@link java.text.DateFormat} object that properly formats the time.
+     */
+    public static final java.text.DateFormat getTimeFormat(Context context) {
+        boolean b24 = is24HourFormat(context);
+        int res;
+
+        if (b24) {
+            res = R.string.twenty_four_hour_time_format;
+        } else {
+            res = R.string.twelve_hour_time_format;
+        }
+
+        return new java.text.SimpleDateFormat(context.getString(res));
+    }
+
+    /**
+     * Returns a {@link java.text.DateFormat} object that can format the date according
+     * to the current user preference.
+     * @param context the application context
+     * @return the {@link java.text.DateFormat} object that properly formats the date.
+     */
+    public static final java.text.DateFormat getDateFormat(Context context) {
+        String value = getDateFormatString(context);
+        return new java.text.SimpleDateFormat(value);
+    }
+    
+    /**
+     * Returns a {@link java.text.DateFormat} object that can format the date
+     * in long form (such as December 31, 1999) based on user preference.
+     * @param context the application context
+     * @return the {@link java.text.DateFormat} object that formats the date in long form.
+     */
+    public static final java.text.DateFormat getLongDateFormat(Context context) {
+        String value = getDateFormatString(context);
+        if (value.indexOf('M') < value.indexOf('d')) {
+            value = context.getString(R.string.full_date_month_first);
+        } else {
+            value = context.getString(R.string.full_date_day_first);
+        }
+        return new java.text.SimpleDateFormat(value);
+    }
+
+    /**
+     * Returns a {@link java.text.DateFormat} object that can format the date
+     * in medium form (such as Dec. 31, 1999) based on user preference.
+     * @param context the application context
+     * @return the {@link java.text.DateFormat} object that formats the date in long form.
+     */
+    public static final java.text.DateFormat getMediumDateFormat(Context context) {
+        String value = getDateFormatString(context);
+        if (value.indexOf('M') < value.indexOf('d')) {
+            value = context.getString(R.string.medium_date_month_first);
+        } else {
+            value = context.getString(R.string.medium_date_day_first);
+        }
+        return new java.text.SimpleDateFormat(value);
+    }
+
+    /**
+     * Gets the current date format stored as a char array. The array will contain
+     * 3 elements ({@link #DATE}, {@link #MONTH}, and {@link #YEAR}) in the order    
+     * preferred by the user.
+     */    
+    public static final char[] getDateFormatOrder(Context context) {
+        char[] order = new char[] {DATE, MONTH, YEAR};
+        String value = getDateFormatString(context);
+        int index = 0;
+        boolean foundDate = false;
+        boolean foundMonth = false;
+        boolean foundYear = false;
+
+        for (char c : value.toCharArray()) {
+            if (!foundDate && (c == DATE)) {
+                foundDate = true;
+                order[index] = DATE;
+                index++;
+            }
+
+            if (!foundMonth && (c == MONTH)) {
+                foundMonth = true;
+                order[index] = MONTH;
+                index++;
+            }
+
+            if (!foundYear && (c == YEAR)) {
+                foundYear = true;
+                order[index] = YEAR;
+                index++;
+            }
+        }
+        return order;
+    }
+    
+    private static String getDateFormatString(Context context) {
+        String value = Settings.System.getString(context.getContentResolver(),
+                Settings.System.DATE_FORMAT);
+        if (value == null || value.length() < 6) {
+            /*
+             * No need to localize -- this is an emergency fallback in case
+             * the setting is missing, but it should always be set.
+             */
+            value = "MM-dd-yyyy";
+        }
+        return value;
+    }
+
+    /**
+     * Given a format string and a time in milliseconds since Jan 1, 1970 GMT, returns a 
+     * CharSequence containing the requested date.
+     * @param inFormat the format string, as described in {@link android.text.format.DateFormat}
+     * @param inTimeInMillis in milliseconds since Jan 1, 1970 GMT
+     * @return a {@link CharSequence} containing the requested text
+     */
+    public static final CharSequence format(CharSequence inFormat, long inTimeInMillis) {
+        return format(inFormat, new Date(inTimeInMillis));
+    }
+
+    /**
+     * Given a format string and a {@link java.util.Date} object, returns a CharSequence containing
+     * the requested date.
+     * @param inFormat the format string, as described in {@link android.text.format.DateFormat}
+     * @param inDate the date to format
+     * @return a {@link CharSequence} containing the requested text
+     */
+    public static final CharSequence format(CharSequence inFormat, Date inDate) {
+        Calendar    c = new GregorianCalendar();
+        
+        c.setTime(inDate);
+        
+        return format(inFormat, c);
+    }
+
+    /**
+     * Given a format string and a {@link java.util.Calendar} object, returns a CharSequence 
+     * containing the requested date.
+     * @param inFormat the format string, as described in {@link android.text.format.DateFormat}
+     * @param inDate the date to format
+     * @return a {@link CharSequence} containing the requested text
+     */
+    public static final CharSequence format(CharSequence inFormat, Calendar inDate) {
+        SpannableStringBuilder      s = new SpannableStringBuilder(inFormat);
+        int             c;
+        int             count;
+
+        int len = inFormat.length();
+
+        for (int i = 0; i < len; i += count) {
+            int temp;
+
+            count = 1;
+            c = s.charAt(i);
+
+            if (c == QUOTE) {
+                count = appendQuotedText(s, i, len);
+                len = s.length();
+                continue;
+            }
+
+            while ((i + count < len) && (s.charAt(i + count) == c)) {
+                count++;
+            }
+
+            String replacement;
+
+            switch (c) {
+                case AM_PM:
+                    replacement = DateUtils.getAMPMString(inDate.get(Calendar.AM_PM));
+                    break;
+                                        
+                case CAPITAL_AM_PM:
+                    //FIXME: this is the same as AM_PM? no capital?
+                    replacement = DateUtils.getAMPMString(inDate.get(Calendar.AM_PM));
+                    break;
+                
+                case DATE:
+                    replacement = zeroPad(inDate.get(Calendar.DATE), count);
+                    break;
+                    
+                case DAY:
+                    temp = inDate.get(Calendar.DAY_OF_WEEK);
+                    replacement = DateUtils.getDayOfWeekString(temp,
+                                                               count < 4 ? 
+                                                               DateUtils.LENGTH_MEDIUM : 
+                                                               DateUtils.LENGTH_LONG);
+                    break;
+                    
+                case HOUR:
+                    temp = inDate.get(Calendar.HOUR);
+
+                    if (0 == temp)
+                        temp = 12;
+                    
+                    replacement = zeroPad(temp, count);
+                    break;
+                    
+                case HOUR_OF_DAY:
+                    replacement = zeroPad(inDate.get(Calendar.HOUR_OF_DAY), count);
+                    break;
+                    
+                case MINUTE:
+                    replacement = zeroPad(inDate.get(Calendar.MINUTE), count);
+                    break;
+                    
+                case MONTH:
+                    replacement = getMonthString(inDate, count);
+                    break;
+                    
+                case SECONDS:
+                    replacement = zeroPad(inDate.get(Calendar.SECOND), count);
+                    break;
+                    
+                case TIME_ZONE:
+                    replacement = getTimeZoneString(inDate, count);
+                    break;
+                    
+                case YEAR:
+                    replacement = getYearString(inDate, count);
+                    break;
+
+                default:
+                    replacement = null;
+                    break;
+            }
+            
+            if (replacement != null) {
+                s.replace(i, i + count, replacement);
+                count = replacement.length(); // CARE: count is used in the for loop above
+                len = s.length();
+            }
+        }
+        
+        if (inFormat instanceof Spanned)
+            return new SpannedString(s);
+        else
+            return s.toString();
+    }
+    
+    private static final String getMonthString(Calendar inDate, int count) {
+        int month = inDate.get(Calendar.MONTH);
+        
+        if (count >= 4)
+            return DateUtils.getMonthString(month, DateUtils.LENGTH_LONG);
+        else if (count == 3)
+            return DateUtils.getMonthString(month, DateUtils.LENGTH_MEDIUM);
+        else {
+            // Calendar.JANUARY == 0, so add 1 to month.
+            return zeroPad(month+1, count);
+        }
+    }
+        
+    private static final String getTimeZoneString(Calendar inDate, int count) {
+        TimeZone tz = inDate.getTimeZone();
+        
+        if (count < 2) { // FIXME: shouldn't this be <= 2 ?
+            return formatZoneOffset(inDate.get(Calendar.DST_OFFSET) +
+                                    inDate.get(Calendar.ZONE_OFFSET),
+                                    count);
+        } else {
+            boolean dst = inDate.get(Calendar.DST_OFFSET) != 0;
+            return tz.getDisplayName(dst, TimeZone.SHORT);
+        }
+    }
+
+    private static final String formatZoneOffset(int offset, int count) {
+        offset /= 1000; // milliseconds to seconds
+        StringBuilder tb = new StringBuilder();
+
+        if (offset < 0) {
+            tb.insert(0, "-");
+            offset = -offset;
+        } else {
+            tb.insert(0, "+");
+        }
+
+        int hours = offset / 3600;
+        int minutes = (offset % 3600) / 60;
+
+        tb.append(zeroPad(hours, 2));
+        tb.append(zeroPad(minutes, 2));
+        return tb.toString();
+    }
+    
+    private static final String getYearString(Calendar inDate, int count) {
+        int year = inDate.get(Calendar.YEAR);
+        return (count <= 2) ? zeroPad(year % 100, 2) : String.valueOf(year);
+    }
+   
+    private static final int appendQuotedText(SpannableStringBuilder s, int i, int len) {
+        if (i + 1 < len && s.charAt(i + 1) == QUOTE) {
+            s.delete(i, i + 1);
+            return 1;
+        }
+
+        int count = 0;
+
+        // delete leading quote
+        s.delete(i, i + 1);
+        len--;
+
+        while (i < len) {
+            char c = s.charAt(i);
+
+            if (c == QUOTE) {
+                //  QUOTEQUOTE -> QUOTE
+                if (i + 1 < len && s.charAt(i + 1) == QUOTE) {
+
+                    s.delete(i, i + 1);
+                    len--;
+                    count++;
+                    i++;
+                } else {
+                    //  Closing QUOTE ends quoted text copying
+                    s.delete(i, i + 1);
+                    break;
+                }
+            } else {
+                i++;
+                count++;
+            }
+        }
+
+        return count;
+    }
+
+    private static final String zeroPad(int inValue, int inMinDigits) {
+        String val = String.valueOf(inValue);
+
+        if (val.length() < inMinDigits) {
+            char[] buf = new char[inMinDigits];
+
+            for (int i = 0; i < inMinDigits; i++)
+                buf[i] = '0';
+
+            val.getChars(0, val.length(), buf, inMinDigits - val.length());
+            val = new String(buf);
+        }
+        return val;
+    }
+}
diff --git a/core/java/android/text/format/DateUtils.java b/core/java/android/text/format/DateUtils.java
new file mode 100644
index 0000000..8a7cdd9
--- /dev/null
+++ b/core/java/android/text/format/DateUtils.java
@@ -0,0 +1,1627 @@
+/*
+ * 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 android.text.format;
+
+import com.android.internal.R;
+
+import android.content.Context;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.pim.DateException;
+
+import java.util.Calendar;
+import java.util.Date;
+import java.util.GregorianCalendar;
+import java.util.TimeZone;
+
+/**
+ * This class contains various date-related utilities for creating text for things like
+ * elapsed time and date ranges, strings for days of the week and months, and AM/PM text etc.
+ */
+public class DateUtils
+{
+    private static final Object sLock = new Object();
+    private static final int[] sDaysLong = new int[] {
+            com.android.internal.R.string.day_of_week_long_sunday,
+            com.android.internal.R.string.day_of_week_long_monday,
+            com.android.internal.R.string.day_of_week_long_tuesday,
+            com.android.internal.R.string.day_of_week_long_wednesday,
+            com.android.internal.R.string.day_of_week_long_thursday,
+            com.android.internal.R.string.day_of_week_long_friday,
+            com.android.internal.R.string.day_of_week_long_saturday,
+        };
+    private static final int[] sDaysMedium = new int[] {
+            com.android.internal.R.string.day_of_week_medium_sunday,
+            com.android.internal.R.string.day_of_week_medium_monday,
+            com.android.internal.R.string.day_of_week_medium_tuesday,
+            com.android.internal.R.string.day_of_week_medium_wednesday,
+            com.android.internal.R.string.day_of_week_medium_thursday,
+            com.android.internal.R.string.day_of_week_medium_friday,
+            com.android.internal.R.string.day_of_week_medium_saturday,
+        };
+    private static final int[] sDaysShort = new int[] {
+            com.android.internal.R.string.day_of_week_short_sunday,
+            com.android.internal.R.string.day_of_week_short_monday,
+            com.android.internal.R.string.day_of_week_short_tuesday,
+            com.android.internal.R.string.day_of_week_short_wednesday,
+            com.android.internal.R.string.day_of_week_short_thursday,
+            com.android.internal.R.string.day_of_week_short_friday,
+            com.android.internal.R.string.day_of_week_short_saturday,
+        };
+    private static final int[] sDaysShorter = new int[] {
+            com.android.internal.R.string.day_of_week_shorter_sunday,
+            com.android.internal.R.string.day_of_week_shorter_monday,
+            com.android.internal.R.string.day_of_week_shorter_tuesday,
+            com.android.internal.R.string.day_of_week_shorter_wednesday,
+            com.android.internal.R.string.day_of_week_shorter_thursday,
+            com.android.internal.R.string.day_of_week_shorter_friday,
+            com.android.internal.R.string.day_of_week_shorter_saturday,
+        };
+    private static final int[] sDaysShortest = new int[] {
+            com.android.internal.R.string.day_of_week_shortest_sunday,
+            com.android.internal.R.string.day_of_week_shortest_monday,
+            com.android.internal.R.string.day_of_week_shortest_tuesday,
+            com.android.internal.R.string.day_of_week_shortest_wednesday,
+            com.android.internal.R.string.day_of_week_shortest_thursday,
+            com.android.internal.R.string.day_of_week_shortest_friday,
+            com.android.internal.R.string.day_of_week_shortest_saturday,
+        };
+    private static final int[] sMonthsLong = new int [] {
+            com.android.internal.R.string.month_long_january,
+            com.android.internal.R.string.month_long_february,
+            com.android.internal.R.string.month_long_march,
+            com.android.internal.R.string.month_long_april,
+            com.android.internal.R.string.month_long_may,
+            com.android.internal.R.string.month_long_june,
+            com.android.internal.R.string.month_long_july,
+            com.android.internal.R.string.month_long_august,
+            com.android.internal.R.string.month_long_september,
+            com.android.internal.R.string.month_long_october,
+            com.android.internal.R.string.month_long_november,
+            com.android.internal.R.string.month_long_december,
+        };
+    private static final int[] sMonthsMedium = new int [] {
+            com.android.internal.R.string.month_medium_january,
+            com.android.internal.R.string.month_medium_february,
+            com.android.internal.R.string.month_medium_march,
+            com.android.internal.R.string.month_medium_april,
+            com.android.internal.R.string.month_medium_may,
+            com.android.internal.R.string.month_medium_june,
+            com.android.internal.R.string.month_medium_july,
+            com.android.internal.R.string.month_medium_august,
+            com.android.internal.R.string.month_medium_september,
+            com.android.internal.R.string.month_medium_october,
+            com.android.internal.R.string.month_medium_november,
+            com.android.internal.R.string.month_medium_december,
+        };
+    private static final int[] sMonthsShortest = new int [] {
+            com.android.internal.R.string.month_shortest_january,
+            com.android.internal.R.string.month_shortest_february,
+            com.android.internal.R.string.month_shortest_march,
+            com.android.internal.R.string.month_shortest_april,
+            com.android.internal.R.string.month_shortest_may,
+            com.android.internal.R.string.month_shortest_june,
+            com.android.internal.R.string.month_shortest_july,
+            com.android.internal.R.string.month_shortest_august,
+            com.android.internal.R.string.month_shortest_september,
+            com.android.internal.R.string.month_shortest_october,
+            com.android.internal.R.string.month_shortest_november,
+            com.android.internal.R.string.month_shortest_december,
+        };
+    private static final int[] sAmPm = new int[] {
+            com.android.internal.R.string.am,
+            com.android.internal.R.string.pm,
+        };
+    private static Configuration sLastConfig;
+    private static String sStatusTimeFormat;
+    private static String sElapsedFormatMMSS;
+    private static String sElapsedFormatHMMSS;
+    
+    private static final String FAST_FORMAT_HMMSS = "%1$d:%2$02d:%3$02d";
+    private static final String FAST_FORMAT_MMSS = "%1$02d:%2$02d";
+    private static final char TIME_PADDING = '0';
+    private static final char TIME_SEPARATOR = ':';
+    
+
+    public static final long SECOND_IN_MILLIS = 1000;
+    public static final long MINUTE_IN_MILLIS = SECOND_IN_MILLIS * 60;
+    public static final long HOUR_IN_MILLIS = MINUTE_IN_MILLIS * 60;
+    public static final long DAY_IN_MILLIS = HOUR_IN_MILLIS * 24;
+    public static final long WEEK_IN_MILLIS = DAY_IN_MILLIS * 7;
+    public static final long YEAR_IN_MILLIS = WEEK_IN_MILLIS * 52;
+
+    // The following FORMAT_* symbols are used for specifying the format of
+    // dates and times in the formatDateRange method.
+    public static final int FORMAT_SHOW_TIME = 0x00001;
+    public static final int FORMAT_SHOW_WEEKDAY = 0x00002;
+    public static final int FORMAT_SHOW_YEAR = 0x00004;
+    public static final int FORMAT_NO_YEAR = 0x00008;
+    public static final int FORMAT_SHOW_DATE = 0x00010;
+    public static final int FORMAT_NO_MONTH_DAY = 0x00020;
+    public static final int FORMAT_12HOUR = 0x00040;
+    public static final int FORMAT_24HOUR = 0x00080;
+    public static final int FORMAT_CAP_AMPM = 0x00100;
+    public static final int FORMAT_NO_NOON = 0x00200;
+    public static final int FORMAT_CAP_NOON = 0x00400;
+    public static final int FORMAT_NO_MIDNIGHT = 0x00800;
+    public static final int FORMAT_CAP_MIDNIGHT = 0x01000;
+    public static final int FORMAT_UTC = 0x02000;
+    public static final int FORMAT_ABBREV_TIME = 0x04000;
+    public static final int FORMAT_ABBREV_WEEKDAY = 0x08000;
+    public static final int FORMAT_ABBREV_MONTH = 0x10000;
+    public static final int FORMAT_NUMERIC_DATE = 0x20000;
+    public static final int FORMAT_ABBREV_RELATIVE = 0x40000;
+    public static final int FORMAT_ABBREV_ALL = 0x80000;
+    public static final int FORMAT_CAP_NOON_MIDNIGHT = (FORMAT_CAP_NOON | FORMAT_CAP_MIDNIGHT);
+    public static final int FORMAT_NO_NOON_MIDNIGHT = (FORMAT_NO_NOON | FORMAT_NO_MIDNIGHT);
+
+    // Date and time format strings that are constant and don't need to be
+    // translated.
+    public static final String HOUR_MINUTE_24 = "%H:%M";
+    public static final String MONTH_FORMAT = "%B";
+    public static final String ABBREV_MONTH_FORMAT = "%b";
+    public static final String NUMERIC_MONTH_FORMAT = "%m";
+    public static final String MONTH_DAY_FORMAT = "%-d";
+    public static final String YEAR_FORMAT = "%Y";
+    public static final String YEAR_FORMAT_TWO_DIGITS = "%g";
+    public static final String WEEKDAY_FORMAT = "%A";
+    public static final String ABBREV_WEEKDAY_FORMAT = "%a";
+    
+    // This table is used to lookup the resource string id of a format string
+    // used for formatting a start and end date that fall in the same year.
+    // The index is constructed from a bit-wise OR of the boolean values:
+    // {showTime, showYear, showWeekDay}.  For example, if showYear and
+    // showWeekDay are both true, then the index would be 3.
+    public static final int sameYearTable[] = {
+        com.android.internal.R.string.same_year_md1_md2,
+        com.android.internal.R.string.same_year_wday1_md1_wday2_md2,
+        com.android.internal.R.string.same_year_mdy1_mdy2,
+        com.android.internal.R.string.same_year_wday1_mdy1_wday2_mdy2,
+        com.android.internal.R.string.same_year_md1_time1_md2_time2,
+        com.android.internal.R.string.same_year_wday1_md1_time1_wday2_md2_time2,
+        com.android.internal.R.string.same_year_mdy1_time1_mdy2_time2,
+        com.android.internal.R.string.same_year_wday1_mdy1_time1_wday2_mdy2_time2,
+
+        // Numeric date strings
+        com.android.internal.R.string.numeric_md1_md2,
+        com.android.internal.R.string.numeric_wday1_md1_wday2_md2,
+        com.android.internal.R.string.numeric_mdy1_mdy2,
+        com.android.internal.R.string.numeric_wday1_mdy1_wday2_mdy2,
+        com.android.internal.R.string.numeric_md1_time1_md2_time2,
+        com.android.internal.R.string.numeric_wday1_md1_time1_wday2_md2_time2,
+        com.android.internal.R.string.numeric_mdy1_time1_mdy2_time2,
+        com.android.internal.R.string.numeric_wday1_mdy1_time1_wday2_mdy2_time2,
+    };
+    
+    // This table is used to lookup the resource string id of a format string
+    // used for formatting a start and end date that fall in the same month.
+    // The index is constructed from a bit-wise OR of the boolean values:
+    // {showTime, showYear, showWeekDay}.  For example, if showYear and
+    // showWeekDay are both true, then the index would be 3.
+    public static final int sameMonthTable[] = {
+        com.android.internal.R.string.same_month_md1_md2,
+        com.android.internal.R.string.same_month_wday1_md1_wday2_md2,
+        com.android.internal.R.string.same_month_mdy1_mdy2,
+        com.android.internal.R.string.same_month_wday1_mdy1_wday2_mdy2,
+        com.android.internal.R.string.same_month_md1_time1_md2_time2,
+        com.android.internal.R.string.same_month_wday1_md1_time1_wday2_md2_time2,
+        com.android.internal.R.string.same_month_mdy1_time1_mdy2_time2,
+        com.android.internal.R.string.same_month_wday1_mdy1_time1_wday2_mdy2_time2,
+
+        com.android.internal.R.string.numeric_md1_md2,
+        com.android.internal.R.string.numeric_wday1_md1_wday2_md2,
+        com.android.internal.R.string.numeric_mdy1_mdy2,
+        com.android.internal.R.string.numeric_wday1_mdy1_wday2_mdy2,
+        com.android.internal.R.string.numeric_md1_time1_md2_time2,
+        com.android.internal.R.string.numeric_wday1_md1_time1_wday2_md2_time2,
+        com.android.internal.R.string.numeric_mdy1_time1_mdy2_time2,
+        com.android.internal.R.string.numeric_wday1_mdy1_time1_wday2_mdy2_time2,
+    };
+
+    /**
+     * Request the full spelled-out name. For use with the 'abbrev' parameter of
+     * {@link #getDayOfWeekString} and {@link #getMonthString}.
+     * 
+     * @more <p>
+     *       e.g. "Sunday" or "January"
+     */
+    public static final int LENGTH_LONG = 10;
+
+    /**
+     * Request an abbreviated version of the name. For use with the 'abbrev'
+     * parameter of {@link #getDayOfWeekString} and {@link #getMonthString}.
+     * 
+     * @more <p>
+     *       e.g. "Sun" or "Jan"
+     */
+    public static final int LENGTH_MEDIUM = 20;
+
+    /**
+     * Request a shorter abbreviated version of the name.
+     * For use with the 'abbrev' parameter of {@link #getDayOfWeekString} and {@link #getMonthString}.
+     * @more
+     * <p>e.g. "Su" or "Jan"
+     * <p>In some languages, the results returned for LENGTH_SHORT may be the same as
+     * return for {@link #LENGTH_MEDIUM}.
+     */
+    public static final int LENGTH_SHORT = 30;
+
+    /**
+     * Request an even shorter abbreviated version of the name.
+     * For use with the 'abbrev' parameter of {@link #getDayOfWeekString} and {@link #getMonthString}.
+     * @more
+     * <p>e.g. "M", "Tu", "Th" or "J"
+     * <p>In some languages, the results returned for LENGTH_SHORTEST may be the same as
+     * return for {@link #LENGTH_SHORTER}.
+     */
+    public static final int LENGTH_SHORTER = 40;
+
+    /**
+     * Request an even shorter abbreviated version of the name.
+     * For use with the 'abbrev' parameter of {@link #getDayOfWeekString} and {@link #getMonthString}.
+     * @more
+     * <p>e.g. "S", "T", "T" or "J"
+     * <p>In some languages, the results returned for LENGTH_SHORTEST may be the same as
+     * return for {@link #LENGTH_SHORTER}.
+     */
+    public static final int LENGTH_SHORTEST = 50;
+
+    /**
+     * Return a string for the day of the week.
+     * @param dayOfWeek One of {@link Calendar#SUNDAY Calendar.SUNDAY},
+     *               {@link Calendar#MONDAY Calendar.MONDAY}, etc.
+     * @param abbrev One of {@link #LENGTH_LONG}, {@link #LENGTH_SHORT}, {@link #LENGTH_SHORTER}
+     *               or {@link #LENGTH_SHORTEST}.  For forward compatibility, anything else
+     *               will return the same as {#LENGTH_MEDIUM}.
+     * @throws IndexOutOfBoundsException if the dayOfWeek is out of bounds.
+     */
+    public static String getDayOfWeekString(int dayOfWeek, int abbrev) {
+        int[] list;
+        switch (abbrev) {
+            case LENGTH_LONG:       list = sDaysLong;       break;
+            case LENGTH_MEDIUM:     list = sDaysMedium;     break;
+            case LENGTH_SHORT:      list = sDaysShort;      break;
+            case LENGTH_SHORTER:    list = sDaysShorter;    break;
+            case LENGTH_SHORTEST:   list = sDaysShortest;   break;
+            default:                list = sDaysMedium;     break;
+        }
+
+        Resources r = Resources.getSystem();
+        return r.getString(list[dayOfWeek - Calendar.SUNDAY]);
+    }
+
+    /**
+     * Return a localized string for AM or PM.
+     * @param ampm Either {@link Calendar#AM Calendar.AM} or {@link Calendar#PM Calendar.PM}.
+     * @throws IndexOutOfBoundsException if the ampm is out of bounds.
+     * @return Localized version of "AM" or "PM".
+     */
+    public static String getAMPMString(int ampm) {
+        Resources r = Resources.getSystem();
+        return r.getString(sAmPm[ampm - Calendar.AM]);
+    }
+
+    /**
+     * Return a localized string for the day of the week.
+     * @param month One of {@link Calendar#JANUARY Calendar.JANUARY},
+     *               {@link Calendar#FEBRUARY Calendar.FEBRUARY}, etc.
+     * @param abbrev One of {@link #LENGTH_LONG}, {@link #LENGTH_SHORT}, {@link #LENGTH_SHORTER}
+     *               or {@link #LENGTH_SHORTEST}.  For forward compatibility, anything else
+     *               will return the same as {#LENGTH_MEDIUM}.
+     * @return Localized day of the week.
+     */
+    public static String getMonthString(int month, int abbrev) {
+        // Note that here we use sMonthsMedium for MEDIUM, SHORT and SHORTER. 
+        // This is a shortcut to not spam the translators with too many variations
+        // of the same string.  If we find that in a language the distinction
+        // is necessary, we can can add more without changing this API.
+        int[] list;
+        switch (abbrev) {
+            case LENGTH_LONG:       list = sMonthsLong;     break;
+            case LENGTH_MEDIUM:     list = sMonthsMedium;   break;
+            case LENGTH_SHORT:      list = sMonthsMedium;   break;
+            case LENGTH_SHORTER:    list = sMonthsMedium;   break;
+            case LENGTH_SHORTEST:   list = sMonthsShortest; break;
+            default:                list = sMonthsMedium;   break;
+        }
+
+        Resources r = Resources.getSystem();
+        return r.getString(list[month - Calendar.JANUARY]);
+    }
+
+    /**
+     * Returns a string describing the elapsed time since startTime.
+     * @param startTime some time in the past.
+     * @return a String object containing the elapsed time.
+     * @see #getRelativeTimeSpanString(long, long, long)
+     */
+    public static CharSequence getRelativeTimeSpanString(long startTime) {
+        return getRelativeTimeSpanString(startTime, System.currentTimeMillis(), MINUTE_IN_MILLIS);
+    }
+
+    /**
+     * Returns a string describing 'time' as a time relative to 'now'.
+     * <p>
+     * Time spans in the past are formatted like "42 minutes ago".
+     * Time spans in the future are formatted like "in 42 minutes".
+     *
+     * @param time the time to describe, in milliseconds
+     * @param now the current time in milliseconds
+     * @param minResolution the minimum timespan to report. For example, a time 3 seconds in the
+     *     past will be reported as "0 minutes ago" if this is set to MINUTE_IN_MILLIS. Pass one of
+     *     0, MINUTE_IN_MILLIS, HOUR_IN_MILLIS, DAY_IN_MILLIS, WEEK_IN_MILLIS
+     */
+    public static CharSequence getRelativeTimeSpanString(long time, long now, long minResolution) {
+        int flags = FORMAT_SHOW_DATE | FORMAT_SHOW_YEAR | FORMAT_ABBREV_MONTH;
+        return getRelativeTimeSpanString(time, now, minResolution, flags);
+    }
+
+    /**
+     * Returns a string describing 'time' as a time relative to 'now'.
+     * <p>
+     * Time spans in the past are formatted like "42 minutes ago". Time spans in
+     * the future are formatted like "in 42 minutes".
+     * <p>
+     * Can use {@link #FORMAT_ABBREV_RELATIVE} flag to use abbreviated relative
+     * times, like "42 mins ago".
+     * 
+     * @param time the time to describe, in milliseconds
+     * @param now the current time in milliseconds
+     * @param minResolution the minimum timespan to report. For example, a time
+     *            3 seconds in the past will be reported as "0 minutes ago" if
+     *            this is set to MINUTE_IN_MILLIS. Pass one of 0,
+     *            MINUTE_IN_MILLIS, HOUR_IN_MILLIS, DAY_IN_MILLIS,
+     *            WEEK_IN_MILLIS
+     * @param flags a bit mask of formatting options, such as
+     *            {@link #FORMAT_NUMERIC_DATE} or
+     *            {@link #FORMAT_ABBREV_RELATIVE}
+     */
+    public static CharSequence getRelativeTimeSpanString(long time, long now, long minResolution,
+            int flags) {
+        Resources r = Resources.getSystem();
+        boolean abbrevRelative = (flags & (FORMAT_ABBREV_RELATIVE | FORMAT_ABBREV_ALL)) != 0;
+        
+        boolean past = (now >= time);
+        long duration = Math.abs(now - time);
+
+        int resId;
+        long count;
+        if (duration < MINUTE_IN_MILLIS && minResolution < MINUTE_IN_MILLIS) {
+            count = duration / SECOND_IN_MILLIS;
+            if (past) {
+                if (abbrevRelative) {
+                    resId = com.android.internal.R.plurals.abbrev_num_seconds_ago;
+                } else {
+                    resId = com.android.internal.R.plurals.num_seconds_ago;
+                }
+            } else {
+                if (abbrevRelative) {
+                    resId = com.android.internal.R.plurals.abbrev_in_num_seconds;
+                } else {
+                    resId = com.android.internal.R.plurals.in_num_seconds;
+                }
+            }
+        } else if (duration < HOUR_IN_MILLIS && minResolution < HOUR_IN_MILLIS) {
+            count = duration / MINUTE_IN_MILLIS;
+            if (past) {
+                if (abbrevRelative) {
+                    resId = com.android.internal.R.plurals.abbrev_num_minutes_ago;
+                } else {
+                    resId = com.android.internal.R.plurals.num_minutes_ago;
+                }
+            } else {
+                if (abbrevRelative) {
+                    resId = com.android.internal.R.plurals.abbrev_in_num_minutes;
+                } else {
+                    resId = com.android.internal.R.plurals.in_num_minutes;
+                }
+            }
+        } else if (duration < DAY_IN_MILLIS && minResolution < DAY_IN_MILLIS) {
+            count = duration / HOUR_IN_MILLIS;
+            if (past) {
+                if (abbrevRelative) {
+                    resId = com.android.internal.R.plurals.abbrev_num_hours_ago;
+                } else {
+                    resId = com.android.internal.R.plurals.num_hours_ago;
+                }
+            } else {
+                if (abbrevRelative) {
+                    resId = com.android.internal.R.plurals.abbrev_in_num_hours;
+                } else {
+                    resId = com.android.internal.R.plurals.in_num_hours;
+                }
+            }
+        } else if (duration < WEEK_IN_MILLIS && minResolution < WEEK_IN_MILLIS) {
+            count = duration / DAY_IN_MILLIS;
+            if (past) {
+                if (abbrevRelative) {
+                    resId = com.android.internal.R.plurals.abbrev_num_days_ago;
+                } else {
+                    resId = com.android.internal.R.plurals.num_days_ago;
+                }
+            } else {
+                if (abbrevRelative) {
+                    resId = com.android.internal.R.plurals.abbrev_in_num_days;
+                } else {
+                    resId = com.android.internal.R.plurals.in_num_days;
+                }
+            }
+        } else {
+            // We know that we won't be showing the time, so it is safe to pass
+            // in a null context.
+            return formatDateRange(null, time, time, flags);
+        }
+
+        String format = r.getQuantityString(resId, (int) count);
+        return String.format(format, count);
+    }
+    
+    /**
+     * Return string describing the elapsed time since startTime formatted like
+     * "[relative time/date], [time]".
+     * <p>
+     * Example output strings for the US date format.
+     * <ul>
+     * <li>3 mins ago, 10:15 AM</li>
+     * <li>yesterday, 12:20 PM</li>
+     * <li>Dec 12, 4:12 AM</li>
+     * <li>11/14/2007, 8:20 AM</li>
+     * </ul>
+     * 
+     * @param time some time in the past.
+     * @param minResolution the minimum elapsed time (in milliseconds) to report
+     *            when showing relative times. For example, a time 3 seconds in
+     *            the past will be reported as "0 minutes ago" if this is set to
+     *            {@link #MINUTE_IN_MILLIS}.
+     * @param transitionResolution the elapsed time (in milliseconds) at which
+     *            to stop reporting relative measurements. Elapsed times greater
+     *            than this resolution will default to normal date formatting.
+     *            For example, will transition from "6 days ago" to "Dec 12"
+     *            when using {@link #WEEK_IN_MILLIS}.
+     */
+    public static CharSequence getRelativeDateTimeString(Context c, long time, long minResolution,
+            long transitionResolution, int flags) {
+        Resources r = Resources.getSystem();
+
+        long now = System.currentTimeMillis();
+        long duration = Math.abs(now - time);
+
+        // getRelativeTimeSpanString() doesn't correctly format relative dates
+        // above a week or exact dates below a day, so clamp
+        // transitionResolution as needed.
+        if (transitionResolution > WEEK_IN_MILLIS) {
+            transitionResolution = WEEK_IN_MILLIS;
+        } else if (transitionResolution < DAY_IN_MILLIS) {
+            transitionResolution = DAY_IN_MILLIS;
+        }
+
+        CharSequence timeClause = formatDateRange(c, time, time, FORMAT_SHOW_TIME);
+        
+        String result;
+        if (duration < transitionResolution) {
+            CharSequence relativeClause = getRelativeTimeSpanString(time, now, minResolution, flags);
+            result = r.getString(com.android.internal.R.string.relative_time, relativeClause, timeClause);
+        } else {
+            CharSequence dateClause = getRelativeTimeSpanString(c, time, false);
+            result = r.getString(com.android.internal.R.string.date_time, dateClause, timeClause);
+        }
+
+        return result;
+    }
+
+    /**
+     * Returns a string describing a day relative to the current day. For example if the day is
+     * today this function returns "Today", if the day was a week ago it returns "7 days ago", and
+     * if the day is in 2 weeks it returns "in 14 days".
+     * 
+     * @param r the resources to get the strings from
+     * @param day the relative day to describe in UTC milliseconds
+     * @param today the current time in UTC milliseconds
+     * @return a formatting string
+     */
+    private static final String getRelativeDayString(Resources r, long day, long today) {
+        Time startTime = new Time();
+        startTime.set(day);
+        Time currentTime = new Time();
+        currentTime.set(today);
+
+        int startDay = Time.getJulianDay(day, startTime.gmtoff);
+        int currentDay = Time.getJulianDay(today, currentTime.gmtoff);
+
+        int days = Math.abs(currentDay - startDay);
+        boolean past = (today > day);
+        
+        if (days == 1) {
+            if (past) {
+                return r.getString(com.android.internal.R.string.yesterday);
+            } else {
+                return r.getString(com.android.internal.R.string.tomorrow);
+            }
+        } else if (days == 0) {
+            return r.getString(com.android.internal.R.string.today);
+        }
+
+        int resId;
+        if (past) {
+            resId = com.android.internal.R.plurals.num_days_ago;
+        } else {
+            resId = com.android.internal.R.plurals.in_num_days;
+        }
+        
+        String format = r.getQuantityString(resId, days);
+        return String.format(format, days);
+    }
+
+    private static void initFormatStrings() {
+        synchronized (sLock) {
+            Resources r = Resources.getSystem();
+            Configuration cfg = r.getConfiguration();
+            if (sLastConfig == null || !sLastConfig.equals(cfg)) {
+                sLastConfig = cfg;
+                sStatusTimeFormat = r.getString(com.android.internal.R.string.status_bar_time_format);
+                sElapsedFormatMMSS = r.getString(com.android.internal.R.string.elapsed_time_short_format_mm_ss);
+                sElapsedFormatHMMSS = r.getString(com.android.internal.R.string.elapsed_time_short_format_h_mm_ss);
+            }
+        }
+    }
+
+    /**
+     * Format a time so it appears like it would in the status bar clock.
+     * @deprecated use {@link #DateFormat.getTimeFormat(Context)} instead.
+     * @hide
+     */
+    public static final CharSequence timeString(long millis) {
+        initFormatStrings();
+        return DateFormat.format(sStatusTimeFormat, millis);
+    }
+
+    /**
+     * Formats an elapsed time in the form "MM:SS" or "H:MM:SS"
+     * for display on the call-in-progress screen.
+     * @param elapsedSeconds the elapsed time in seconds.
+     */
+    public static String formatElapsedTime(long elapsedSeconds) {
+        return formatElapsedTime(null, elapsedSeconds);
+    }
+    
+    /**
+     * Formats an elapsed time in the form "MM:SS" or "H:MM:SS"
+     * for display on the call-in-progress screen.
+     * 
+     * @param recycle {@link StringBuilder} to recycle, if possible
+     * @param elapsedSeconds the elapsed time in seconds.
+     */
+    public static String formatElapsedTime(StringBuilder recycle, long elapsedSeconds) {
+        initFormatStrings();
+
+        long hours = 0;
+        long minutes = 0;
+        long seconds = 0;
+
+        if (elapsedSeconds >= 3600) {
+            hours = elapsedSeconds / 3600;
+            elapsedSeconds -= hours * 3600;
+        }
+        if (elapsedSeconds >= 60) {
+            minutes = elapsedSeconds / 60;
+            elapsedSeconds -= minutes * 60;
+        }
+        seconds = elapsedSeconds;
+
+        String result;
+        if (hours > 0) {
+            return formatElapsedTime(recycle, sElapsedFormatHMMSS, hours, minutes, seconds);
+        } else {
+            return formatElapsedTime(recycle, sElapsedFormatMMSS, minutes, seconds);
+        }
+    }
+
+    /**
+     * Fast formatting of h:mm:ss
+     */
+    private static String formatElapsedTime(StringBuilder recycle, String format, long hours,
+            long minutes, long seconds) {
+        if (FAST_FORMAT_HMMSS.equals(format)) {
+            StringBuilder sb = recycle;
+            if (sb == null) {
+                sb = new StringBuilder(8);
+            } else {
+                sb.setLength(0);
+            }
+            sb.append(hours);
+            sb.append(TIME_SEPARATOR);
+            if (minutes < 10) { 
+                sb.append(TIME_PADDING);
+            } else {
+                sb.append(toDigitChar(minutes / 10));
+            }
+            sb.append(toDigitChar(minutes % 10));
+            sb.append(TIME_SEPARATOR);
+            if (seconds < 10) {
+                sb.append(TIME_PADDING);
+            } else {
+                sb.append(toDigitChar(seconds / 10));
+            }
+            sb.append(toDigitChar(seconds % 10));
+            return sb.toString();
+        } else {
+            return String.format(format, hours, minutes, seconds);
+        }
+    }
+
+    /**
+     * Fast formatting of m:ss
+     */
+    private static String formatElapsedTime(StringBuilder recycle, String format, long minutes,
+            long seconds) {
+        if (FAST_FORMAT_MMSS.equals(format)) {
+            StringBuilder sb = recycle;
+            if (sb == null) {
+                sb = new StringBuilder(8);
+            } else {
+                sb.setLength(0);
+            }
+            if (minutes < 10) { 
+                sb.append(TIME_PADDING);
+            } else {
+                sb.append(toDigitChar(minutes / 10));
+            }
+            sb.append(toDigitChar(minutes % 10));
+            sb.append(TIME_SEPARATOR);
+            if (seconds < 10) {
+                sb.append(TIME_PADDING);
+            } else {
+                sb.append(toDigitChar(seconds / 10));
+            }
+            sb.append(toDigitChar(seconds % 10));
+            return sb.toString();
+        } else {
+            return String.format(format, minutes, seconds);
+        }
+    }
+
+    private static char toDigitChar(long digit) {
+        return (char) (digit + '0');
+    }
+    
+    /**
+     * Format a date / time such that if the then is on the same day as now, it shows
+     * just the time and if it's a different day, it shows just the date.
+     * 
+     * <p>The parameters dateFormat and timeFormat should each be one of
+     * {@link java.text.DateFormat#DEFAULT},
+     * {@link java.text.DateFormat#FULL},
+     * {@link java.text.DateFormat#LONG},
+     * {@link java.text.DateFormat#MEDIUM}
+     * or
+     * {@link java.text.DateFormat#SHORT}
+     *
+     * @param then the date to format
+     * @param now the base time
+     * @param dateStyle how to format the date portion.
+     * @param timeStyle how to format the time portion.
+     */
+    public static final CharSequence formatSameDayTime(long then, long now,
+            int dateStyle, int timeStyle) {
+        Calendar thenCal = new GregorianCalendar();
+        thenCal.setTimeInMillis(then);
+        Date thenDate = thenCal.getTime();
+        Calendar nowCal = new GregorianCalendar();
+        nowCal.setTimeInMillis(now);
+
+        java.text.DateFormat f;
+
+        if (thenCal.get(Calendar.YEAR) == nowCal.get(Calendar.YEAR)
+                && thenCal.get(Calendar.MONTH) == nowCal.get(Calendar.MONTH)
+                && thenCal.get(Calendar.DAY_OF_MONTH) == nowCal.get(Calendar.DAY_OF_MONTH)) {
+            f = java.text.DateFormat.getTimeInstance(timeStyle);
+        } else {
+            f = java.text.DateFormat.getDateInstance(dateStyle);
+        }
+        return f.format(thenDate);
+    }
+
+    /**
+     * @hide
+     * @deprecated use {@link android.text.format.Time}
+     */
+    public static Calendar newCalendar(boolean zulu)
+    {
+        if (zulu)
+            return Calendar.getInstance(TimeZone.getTimeZone("GMT"));
+
+        return Calendar.getInstance();
+    }
+
+    /**
+     * @return true if the supplied when is today else false
+     */
+    public static boolean isToday(long when) {
+        Time time = new Time();
+        time.set(when);
+        
+        int thenYear = time.year;
+        int thenMonth = time.month;
+        int thenMonthDay = time.monthDay;
+
+        time.set(System.currentTimeMillis());
+        return (thenYear == time.year)
+                && (thenMonth == time.month) 
+                && (thenMonthDay == time.monthDay);
+    }
+    
+    /**
+     * @hide
+     * @deprecated use {@link android.text.format.Time}
+     */
+    private static final int ctoi(String str, int index)
+                                                throws DateException
+    {
+        char c = str.charAt(index);
+        if (c >= '0' && c <= '9') {
+            return (int)(c - '0');
+        }
+        throw new DateException("Expected numeric character.  Got '" +
+                                            c + "'");
+    }
+
+    /**
+     * @hide
+     * @deprecated use {@link android.text.format.Time}
+     */
+    private static final int check(int lowerBound, int upperBound, int value)
+                                                throws DateException
+    {
+        if (value >= lowerBound && value <= upperBound) {
+            return value;
+        }
+        throw new DateException("field out of bounds.  max=" + upperBound
+                                        + " value=" + value);
+    }
+
+    /**
+     * @hide
+     * @deprecated use {@link android.text.format.Time}
+     * Return true if this date string is local time
+     */
+    public static boolean isUTC(String s)
+    {
+        if (s.length() == 16 && s.charAt(15) == 'Z') {
+            return true;
+        }
+        if (s.length() == 9 && s.charAt(8) == 'Z') {
+            // XXX not sure if this case possible/valid
+            return true;
+        }
+        return false;
+    }
+
+
+    // note that month in Calendar is 0 based and in all other human
+    // representations, it's 1 based.
+    // Returns if the Z was present, meaning that the time is in UTC
+    /**
+     * @hide
+     * @deprecated use {@link android.text.format.Time}
+     */
+    public static boolean parseDateTime(String str, Calendar cal)
+                                                throws DateException
+    {
+        int len = str.length();
+        boolean dateTime = (len == 15 || len == 16) && str.charAt(8) == 'T';
+        boolean justDate = len == 8;
+        if (dateTime || justDate) {
+            cal.clear();
+            cal.set(Calendar.YEAR, 
+                            ctoi(str, 0)*1000 + ctoi(str, 1)*100
+                            + ctoi(str, 2)*10 + ctoi(str, 3));
+            cal.set(Calendar.MONTH,
+                            check(0, 11, ctoi(str, 4)*10 + ctoi(str, 5) - 1));
+            cal.set(Calendar.DAY_OF_MONTH,
+                            check(1, 31, ctoi(str, 6)*10 + ctoi(str, 7)));
+            if (dateTime) {
+                cal.set(Calendar.HOUR_OF_DAY,
+                            check(0, 23, ctoi(str, 9)*10 + ctoi(str, 10)));
+                cal.set(Calendar.MINUTE,
+                            check(0, 59, ctoi(str, 11)*10 + ctoi(str, 12)));
+                cal.set(Calendar.SECOND,
+                            check(0, 59, ctoi(str, 13)*10 + ctoi(str, 14)));
+            }
+            if (justDate) {
+                cal.set(Calendar.HOUR_OF_DAY, 0);
+                cal.set(Calendar.MINUTE, 0);
+                cal.set(Calendar.SECOND, 0);
+                return true;
+            }
+            if (len == 15) {
+                return false;
+            }
+            if (str.charAt(15) == 'Z') {
+                return true;
+            }
+        }
+        throw new DateException("Invalid time (expected "
+                                + "YYYYMMDDThhmmssZ? got '" + str + "').");
+    }
+
+    /**
+     * Given a timezone string which can be null, and a dateTime string,
+     * set that time into a calendar.
+     * @hide
+     * @deprecated use {@link android.text.format.Time}
+     */
+    public static void parseDateTime(String tz, String dateTime, Calendar out)
+                                                throws DateException
+    {
+        TimeZone timezone;
+        if (DateUtils.isUTC(dateTime)) {
+            timezone = TimeZone.getTimeZone("UTC");
+        }
+        else if (tz == null) {
+            timezone = TimeZone.getDefault();
+        }
+        else {
+            timezone = TimeZone.getTimeZone(tz);
+        }
+
+        Calendar local = new GregorianCalendar(timezone);
+        DateUtils.parseDateTime(dateTime, local);
+
+        out.setTimeInMillis(local.getTimeInMillis());
+    }
+
+
+    /**
+     * Return a string containing the date and time in RFC2445 format.
+     * Ensures that the time is written in UTC.  The Calendar class doesn't
+     * really help out with this, so this is slower than it ought to be.
+     *
+     * @param cal the date and time to write
+     * @hide
+     * @deprecated use {@link android.text.format.Time}
+     */
+    public static String writeDateTime(Calendar cal)
+    {
+        TimeZone tz = TimeZone.getTimeZone("GMT");
+        GregorianCalendar c = new GregorianCalendar(tz);
+        c.setTimeInMillis(cal.getTimeInMillis());
+        return writeDateTime(c, true);
+    }
+
+    /**
+     * Return a string containing the date and time in RFC2445 format.
+     *
+     * @param cal the date and time to write
+     * @param zulu If the calendar is in UTC, pass true, and a Z will
+     * be written at the end as per RFC2445.  Otherwise, the time is
+     * considered in localtime.
+     * @hide
+     * @deprecated use {@link android.text.format.Time}
+     */
+    public static String writeDateTime(Calendar cal, boolean zulu)
+    {
+        StringBuilder sb = new StringBuilder();
+        sb.ensureCapacity(16);
+        if (zulu) {
+            sb.setLength(16);
+            sb.setCharAt(15, 'Z');
+        } else {
+            sb.setLength(15);
+        }
+        return writeDateTime(cal, sb);
+    }
+
+    /**
+     * Return a string containing the date and time in RFC2445 format.
+     *
+     * @param cal the date and time to write
+     * @param sb a StringBuilder to use.  It is assumed that setLength
+     *           has already been called on sb to the appropriate length
+     *           which is sb.setLength(zulu ? 16 : 15)
+     * @hide
+     * @deprecated use {@link android.text.format.Time}
+     */
+    public static String writeDateTime(Calendar cal, StringBuilder sb)
+    {
+        int n;
+       
+        n = cal.get(Calendar.YEAR);
+        sb.setCharAt(3, (char)('0'+n%10));
+        n /= 10;
+        sb.setCharAt(2, (char)('0'+n%10));
+        n /= 10;
+        sb.setCharAt(1, (char)('0'+n%10));
+        n /= 10;
+        sb.setCharAt(0, (char)('0'+n%10));
+
+        n = cal.get(Calendar.MONTH) + 1;
+        sb.setCharAt(5, (char)('0'+n%10));
+        n /= 10;
+        sb.setCharAt(4, (char)('0'+n%10));
+
+        n = cal.get(Calendar.DAY_OF_MONTH);
+        sb.setCharAt(7, (char)('0'+n%10));
+        n /= 10;
+        sb.setCharAt(6, (char)('0'+n%10));
+
+        sb.setCharAt(8, 'T');
+
+        n = cal.get(Calendar.HOUR_OF_DAY);
+        sb.setCharAt(10, (char)('0'+n%10));
+        n /= 10;
+        sb.setCharAt(9, (char)('0'+n%10));
+
+        n = cal.get(Calendar.MINUTE);
+        sb.setCharAt(12, (char)('0'+n%10));
+        n /= 10;
+        sb.setCharAt(11, (char)('0'+n%10));
+
+        n = cal.get(Calendar.SECOND);
+        sb.setCharAt(14, (char)('0'+n%10));
+        n /= 10;
+        sb.setCharAt(13, (char)('0'+n%10));
+
+        return sb.toString();
+    }
+
+    /**
+     * @hide
+     * @deprecated use {@link android.text.format.Time}
+     */
+    public static void assign(Calendar lval, Calendar rval)
+    {
+        // there should be a faster way.
+        lval.clear();
+        lval.setTimeInMillis(rval.getTimeInMillis());
+    }
+
+    /**
+     * Formats a date or a time range according to the local conventions.
+     * 
+     * <p>
+     * Example output strings (date formats in these examples are shown using
+     * the US date format convention but that may change depending on the
+     * local settings):
+     * <ul>
+     *   <li>10:15am</li>
+     *   <li>3:00pm - 4:00pm</li>
+     *   <li>3pm - 4pm</li>
+     *   <li>3PM - 4PM</li>
+     *   <li>08:00 - 17:00</li>
+     *   <li>Oct 9</li>
+     *   <li>Tue, Oct 9</li>
+     *   <li>October 9, 2007</li>
+     *   <li>Oct 9 - 10</li>
+     *   <li>Oct 9 - 10, 2007</li>
+     *   <li>Oct 28 - Nov 3, 2007</li>
+     *   <li>Dec 31, 2007 - Jan 1, 2008</li>
+     *   <li>Oct 9, 8:00am - Oct 10, 5:00pm</li>
+     *   <li>12/31/2007 - 01/01/2008</li>
+     * </ul>
+     * 
+     * <p>
+     * The flags argument is a bitmask of options from the following list:
+     * 
+     * <ul>
+     *   <li>FORMAT_SHOW_TIME</li>
+     *   <li>FORMAT_SHOW_WEEKDAY</li>
+     *   <li>FORMAT_SHOW_YEAR</li>
+     *   <li>FORMAT_NO_YEAR</li>
+     *   <li>FORMAT_SHOW_DATE</li>
+     *   <li>FORMAT_NO_MONTH_DAY</li>
+     *   <li>FORMAT_12HOUR</li>
+     *   <li>FORMAT_24HOUR</li>
+     *   <li>FORMAT_CAP_AMPM</li>
+     *   <li>FORMAT_NO_NOON</li>
+     *   <li>FORMAT_CAP_NOON</li>
+     *   <li>FORMAT_NO_MIDNIGHT</li>
+     *   <li>FORMAT_CAP_MIDNIGHT</li>
+     *   <li>FORMAT_UTC</li>
+     *   <li>FORMAT_ABBREV_TIME</li>
+     *   <li>FORMAT_ABBREV_WEEKDAY</li>
+     *   <li>FORMAT_ABBREV_MONTH</li>
+     *   <li>FORMAT_ABBREV_ALL</li>
+     *   <li>FORMAT_NUMERIC_DATE</li>
+     * </ul>
+     * 
+     * <p>
+     * If FORMAT_SHOW_TIME is set, the time is shown as part of the date range.
+     * If the start and end time are the same, then just the start time is
+     * shown.
+     * 
+     * <p>
+     * If FORMAT_SHOW_WEEKDAY is set, then the weekday is shown.
+     * 
+     * <p>
+     * If FORMAT_SHOW_YEAR is set, then the year is always shown.
+     * If FORMAT_NO_YEAR is set, then the year is not shown.
+     * If neither FORMAT_SHOW_YEAR nor FORMAT_NO_YEAR are set, then the year
+     * is shown only if it is different from the current year, or if the start
+     * and end dates fall on different years.  If both are set,
+     * FORMAT_SHOW_YEAR takes precedence.
+     *
+     * <p>
+     * Normally the date is shown unless the start and end day are the same.
+     * If FORMAT_SHOW_DATE is set, then the date is always shown, even for
+     * same day ranges.
+     * 
+     * <p>
+     * If FORMAT_NO_MONTH_DAY is set, then if the date is shown, just the
+     * month name will be shown, not the day of the month.  For example,
+     * "January, 2008" instead of "January 6 - 12, 2008".
+     * 
+     * <p>
+     * If FORMAT_CAP_AMPM is set and 12-hour time is used, then the "AM"
+     * and "PM" are capitalized.
+     * 
+     * <p>
+     * If FORMAT_NO_NOON is set and 12-hour time is used, then "12pm" is
+     * shown instead of "noon".
+     * 
+     * <p>
+     * If FORMAT_CAP_NOON is set and 12-hour time is used, then "Noon" is
+     * shown instead of "noon".
+     * 
+     * <p>
+     * If FORMAT_NO_MIDNIGHT is set and 12-hour time is used, then "12am" is
+     * shown instead of "midnight".
+     * 
+     * <p>
+     * If FORMAT_CAP_NOON is set and 12-hour time is used, then "Midnight" is
+     * shown instead of "midnight".
+     * 
+     * <p>
+     * If FORMAT_12HOUR is set and the time is shown, then the time is
+     * shown in the 12-hour time format. You should not normally set this.
+     * Instead, let the time format be chosen automatically according to the
+     * system settings. If both FORMAT_12HOUR and FORMAT_24HOUR are set, then
+     * FORMAT_24HOUR takes precedence.
+     * 
+     * <p>
+     * If FORMAT_24HOUR is set and the time is shown, then the time is
+     * shown in the 24-hour time format. You should not normally set this.
+     * Instead, let the time format be chosen automatically according to the
+     * system settings. If both FORMAT_12HOUR and FORMAT_24HOUR are set, then
+     * FORMAT_24HOUR takes precedence.
+     * 
+     * <p>
+     * If FORMAT_UTC is set, then the UTC timezone is used for the start
+     * and end milliseconds.
+     * 
+     * <p>
+     * If FORMAT_ABBREV_TIME is set and 12-hour time format is used, then the
+     * start and end times (if shown) are abbreviated by not showing the minutes
+     * if they are zero.  For example, instead of "3:00pm" the time would be
+     * abbreviated to "3pm".
+     * 
+     * <p>
+     * If FORMAT_ABBREV_WEEKDAY is set, then the weekday (if shown) is
+     * abbreviated to a 3-letter string.
+     * 
+     * <p>
+     * If FORMAT_ABBREV_MONTH is set, then the month (if shown) is abbreviated
+     * to a 3-letter string.
+     * 
+     * <p>
+     * If FORMAT_ABBREV_ALL is set, then the weekday and the month (if shown)
+     * are abbreviated to 3-letter strings.
+     * 
+     * <p>
+     * If FORMAT_NUMERIC_DATE is set, then the date is shown in numeric format
+     * instead of using the name of the month.  For example, "12/31/2008"
+     * instead of "December 31, 2008".
+     * 
+     * @param context the context is required only if the time is shown
+     * @param startMillis the start time in UTC milliseconds
+     * @param endMillis the end time in UTC milliseconds
+     * @param flags a bit mask of options
+     *   
+     * @return a string containing the formatted date/time range.
+     */
+    public static String formatDateRange(Context context, long startMillis,
+                long endMillis, int flags) {
+        Resources res = Resources.getSystem();
+        boolean showTime = (flags & FORMAT_SHOW_TIME) != 0;
+        boolean showWeekDay = (flags & FORMAT_SHOW_WEEKDAY) != 0;
+        boolean showYear = (flags & FORMAT_SHOW_YEAR) != 0;
+        boolean noYear = (flags & FORMAT_NO_YEAR) != 0;
+        boolean useUTC = (flags & FORMAT_UTC) != 0;
+        boolean abbrevWeekDay = (flags & (FORMAT_ABBREV_WEEKDAY | FORMAT_ABBREV_ALL)) != 0;
+        boolean abbrevMonth = (flags & (FORMAT_ABBREV_MONTH | FORMAT_ABBREV_ALL)) != 0;
+        boolean noMonthDay = (flags & FORMAT_NO_MONTH_DAY) != 0;
+        boolean numericDate = (flags & FORMAT_NUMERIC_DATE) != 0;
+
+        // If we're getting called with a single instant in time (from
+        // e.g. formatDateTime(), below), then we can skip a lot of
+        // computation below that'd otherwise be thrown out.
+        boolean isInstant = (startMillis == endMillis);
+
+        Time startDate = useUTC ? new Time(Time.TIMEZONE_UTC) : new Time();
+        startDate.set(startMillis);
+
+        Time endDate;
+        int dayDistance;
+        if (isInstant) {
+            endDate = startDate;
+            dayDistance = 0;
+        } else {
+            endDate = useUTC ? new Time(Time.TIMEZONE_UTC) : new Time();
+            endDate.set(endMillis);
+            int startJulianDay = Time.getJulianDay(startMillis, startDate.gmtoff);
+            int endJulianDay = Time.getJulianDay(endMillis, endDate.gmtoff);
+            dayDistance = endJulianDay - startJulianDay;
+        }
+
+        // If the end date ends at 12am at the beginning of a day,
+        // then modify it to make it look like it ends at midnight on
+        // the previous day.  This will allow us to display "8pm - midnight",
+        // for example, instead of "Nov 10, 8pm - Nov 11, 12am". But we only do
+        // this if it is midnight of the same day as the start date because
+        // for multiple-day events, an end time of "midnight on Nov 11" is
+        // ambiguous and confusing (is that midnight the start of Nov 11, or
+        // the end of Nov 11?).
+        // If we are not showing the time then also adjust the end date
+        // for multiple-day events.  This is to allow us to display, for
+        // example, "Nov 10 -11" for an event with a start date of Nov 10
+        // and an end date of Nov 12 at 00:00.
+        // If the start and end time are the same, then skip this and don't
+        // adjust the date.
+        if (!isInstant
+            && (endDate.hour | endDate.minute | endDate.second) == 0
+            && (!showTime || dayDistance <= 1)) {
+            endDate.monthDay -= 1;
+            endDate.normalize(true /* ignore isDst */);
+        }
+
+        int startDay = startDate.monthDay;
+        int startMonthNum = startDate.month;
+        int startYear = startDate.year;
+
+        int endDay = endDate.monthDay;
+        int endMonthNum = endDate.month;
+        int endYear = endDate.year;
+
+        String startWeekDayString = "";
+        String endWeekDayString = "";
+        if (showWeekDay) {
+            String weekDayFormat = "";
+            if (abbrevWeekDay) {
+                weekDayFormat = ABBREV_WEEKDAY_FORMAT;
+            } else {
+                weekDayFormat = WEEKDAY_FORMAT;
+            }
+            startWeekDayString = startDate.format(weekDayFormat);
+            endWeekDayString = isInstant ? startWeekDayString : endDate.format(weekDayFormat);
+        }
+
+        String startTimeString = "";
+        String endTimeString = "";
+        if (showTime) {
+            String startTimeFormat = "";
+            String endTimeFormat = "";
+            boolean force24Hour = (flags & FORMAT_24HOUR) != 0;
+            boolean force12Hour = (flags & FORMAT_12HOUR) != 0;
+            boolean use24Hour;
+            if (force24Hour) {
+                use24Hour = true;
+            } else if (force12Hour) {
+                use24Hour = false;
+            } else {
+                use24Hour = DateFormat.is24HourFormat(context);
+            }
+            if (use24Hour) {
+                startTimeFormat = HOUR_MINUTE_24;
+                endTimeFormat = HOUR_MINUTE_24;
+            } else {
+                boolean abbrevTime = (flags & (FORMAT_ABBREV_TIME | FORMAT_ABBREV_ALL)) != 0;
+                boolean capAMPM = (flags & FORMAT_CAP_AMPM) != 0;
+                boolean noNoon = (flags & FORMAT_NO_NOON) != 0;
+                boolean capNoon = (flags & FORMAT_CAP_NOON) != 0;
+                boolean noMidnight = (flags & FORMAT_NO_MIDNIGHT) != 0;
+                boolean capMidnight = (flags & FORMAT_CAP_MIDNIGHT) != 0;
+
+                boolean startOnTheHour = startDate.minute == 0 && startDate.second == 0;
+                boolean endOnTheHour = endDate.minute == 0 && endDate.second == 0;
+                if (abbrevTime && startOnTheHour) {
+                    if (capAMPM) {
+                        startTimeFormat = res.getString(com.android.internal.R.string.hour_cap_ampm);
+                    } else {
+                        startTimeFormat = res.getString(com.android.internal.R.string.hour_ampm);
+                    }
+                } else {
+                    if (capAMPM) {
+                        startTimeFormat = res.getString(com.android.internal.R.string.hour_minute_cap_ampm);
+                    } else {
+                        startTimeFormat = res.getString(com.android.internal.R.string.hour_minute_ampm);
+                    }
+                }
+
+                // Don't waste time on setting endTimeFormat when
+                // we're dealing with an instant, where we'll never
+                // need the end point.  (It's the same as the start
+                // point)
+                if (!isInstant) {
+                    if (abbrevTime && endOnTheHour) {
+                        if (capAMPM) {
+                            endTimeFormat = res.getString(com.android.internal.R.string.hour_cap_ampm);
+                        } else {
+                            endTimeFormat = res.getString(com.android.internal.R.string.hour_ampm);
+                        }
+                    } else {
+                        if (capAMPM) {
+                            endTimeFormat = res.getString(com.android.internal.R.string.hour_minute_cap_ampm);
+                        } else {
+                            endTimeFormat = res.getString(com.android.internal.R.string.hour_minute_ampm);
+                        }
+                    }
+
+                    if (endDate.hour == 12 && endOnTheHour && !noNoon) {
+                        if (capNoon) {
+                            endTimeFormat = res.getString(com.android.internal.R.string.Noon);
+                        } else {
+                            endTimeFormat = res.getString(com.android.internal.R.string.noon);
+                        }
+                    } else if (endDate.hour == 0 && endOnTheHour && !noMidnight) {
+                        if (capMidnight) {
+                            endTimeFormat = res.getString(com.android.internal.R.string.Midnight);
+                        } else {
+                            endTimeFormat = res.getString(com.android.internal.R.string.midnight);
+                        }
+                    }
+                }
+
+                if (startDate.hour == 12 && startOnTheHour && !noNoon) {
+                    if (capNoon) {
+                        startTimeFormat = res.getString(com.android.internal.R.string.Noon);
+                    } else {
+                        startTimeFormat = res.getString(com.android.internal.R.string.noon);
+                    }
+                    // Don't show the start time starting at midnight.  Show
+                    // 12am instead.
+                }
+            }
+
+            startTimeString = startDate.format(startTimeFormat);
+            endTimeString = isInstant ? startTimeString : endDate.format(endTimeFormat);
+        }
+
+        // Show the year if the user specified FORMAT_SHOW_YEAR or if
+        // the starting and end years are different from each other
+        // or from the current year.  But don't show the year if the
+        // user specified FORMAT_NO_YEAR.
+        if (showYear) {
+            // No code... just a comment for clarity.  Keep showYear
+            // on, as they enabled it with FORMAT_SHOW_YEAR.  This
+            // takes precedence over them setting FORMAT_NO_YEAR.
+        } else if (noYear) {
+            // They explicitly didn't want a year.
+            showYear = false;
+        } else if (startYear != endYear) {
+            showYear = true;
+        } else {
+            // Show the year if it's not equal to the current year.
+            Time currentTime = new Time();
+            currentTime.setToNow();
+            showYear = startYear != currentTime.year;
+        }
+
+        String defaultDateFormat, fullFormat, dateRange;
+        if (numericDate) {
+            defaultDateFormat = res.getString(com.android.internal.R.string.numeric_date);
+        } else if (showYear) {
+            if (abbrevMonth) {
+                if (noMonthDay) {
+                    defaultDateFormat = res.getString(com.android.internal.R.string.abbrev_month_year);
+                } else {
+                    defaultDateFormat = res.getString(com.android.internal.R.string.abbrev_month_day_year);
+                }
+            } else {
+                if (noMonthDay) {
+                    defaultDateFormat = res.getString(com.android.internal.R.string.month_year);
+                } else {
+                    defaultDateFormat = res.getString(com.android.internal.R.string.month_day_year);
+                }
+            }
+        } else {
+            if (abbrevMonth) {
+                if (noMonthDay) {
+                    defaultDateFormat = res.getString(com.android.internal.R.string.abbrev_month);
+                } else {
+                    defaultDateFormat = res.getString(com.android.internal.R.string.abbrev_month_day);
+                }
+            } else {
+                if (noMonthDay) {
+                    defaultDateFormat = res.getString(com.android.internal.R.string.month);
+                } else {
+                    defaultDateFormat = res.getString(com.android.internal.R.string.month_day);
+                }
+            }
+        }
+
+        if (showWeekDay) {
+            if (showTime) {
+                fullFormat = res.getString(com.android.internal.R.string.wday1_date1_time1_wday2_date2_time2);
+            } else {
+                fullFormat = res.getString(com.android.internal.R.string.wday1_date1_wday2_date2);
+            }
+        } else {
+            if (showTime) {
+                fullFormat = res.getString(com.android.internal.R.string.date1_time1_date2_time2);
+            } else {
+                fullFormat = res.getString(com.android.internal.R.string.date1_date2);
+            }
+        }
+
+        if (noMonthDay && startMonthNum == endMonthNum) {
+            // Example: "January, 2008"
+            String startDateString = startDate.format(defaultDateFormat);
+            return startDateString;
+        }
+
+        if (startYear != endYear || noMonthDay) {
+            // Different year or we are not showing the month day number.
+            // Example: "December 31, 2007 - January 1, 2008"
+            // Or: "January - February, 2008"
+            String startDateString = startDate.format(defaultDateFormat);
+            String endDateString = endDate.format(defaultDateFormat);
+
+            // The values that are used in a fullFormat string are specified
+            // by position.
+            dateRange = String.format(fullFormat,
+                    startWeekDayString, startDateString, startTimeString,
+                    endWeekDayString, endDateString, endTimeString);
+            return dateRange;
+        }
+
+        // Get the month, day, and year strings for the start and end dates
+        String monthFormat;
+        if (numericDate) {
+            monthFormat = NUMERIC_MONTH_FORMAT;
+        } else if (abbrevMonth) {
+            monthFormat = ABBREV_MONTH_FORMAT;
+        } else {
+            monthFormat = MONTH_FORMAT;
+        }
+        String startMonthString = startDate.format(monthFormat);
+        String startMonthDayString = startDate.format(MONTH_DAY_FORMAT);
+        String startYearString = startDate.format(YEAR_FORMAT);
+
+        String endMonthString = isInstant ? null : endDate.format(monthFormat);
+        String endMonthDayString = isInstant ? null : endDate.format(MONTH_DAY_FORMAT);
+        String endYearString = isInstant ? null : endDate.format(YEAR_FORMAT);
+
+        if (startMonthNum != endMonthNum) {
+            // Same year, different month.
+            // Example: "October 28 - November 3"
+            // or: "Wed, Oct 31 - Sat, Nov 3, 2007"
+            // or: "Oct 31, 8am - Sat, Nov 3, 2007, 5pm"
+
+            int index = 0;
+            if (showWeekDay) index = 1;
+            if (showYear) index += 2;
+            if (showTime) index += 4;
+            if (numericDate) index += 8;
+            int resId = sameYearTable[index];
+            fullFormat = res.getString(resId);
+
+            // The values that are used in a fullFormat string are specified
+            // by position.
+            dateRange = String.format(fullFormat,
+                    startWeekDayString, startMonthString, startMonthDayString,
+                    startYearString, startTimeString,
+                    endWeekDayString, endMonthString, endMonthDayString,
+                    endYearString, endTimeString);
+            return dateRange;
+        }
+
+        if (startDay != endDay) {
+            // Same month, different day.
+            int index = 0;
+            if (showWeekDay) index = 1;
+            if (showYear) index += 2;
+            if (showTime) index += 4;
+            if (numericDate) index += 8;
+            int resId = sameMonthTable[index];
+            fullFormat = res.getString(resId);
+
+            // The values that are used in a fullFormat string are specified
+            // by position.
+            dateRange = String.format(fullFormat,
+                    startWeekDayString, startMonthString, startMonthDayString,
+                    startYearString, startTimeString,
+                    endWeekDayString, endMonthString, endMonthDayString,
+                    endYearString, endTimeString);
+            return dateRange;
+        }
+
+        // Same start and end day
+        boolean showDate = (flags & FORMAT_SHOW_DATE) != 0;
+
+        // If nothing was specified, then show the date.
+        if (!showTime && !showDate && !showWeekDay) showDate = true;
+
+        // Compute the time string (example: "10:00 - 11:00 am")
+        String timeString = "";
+        if (showTime) {
+            // If the start and end time are the same, then just show the
+            // start time.
+            if (isInstant) {
+                // Same start and end time.
+                // Example: "10:15 AM"
+                timeString = startTimeString;
+            } else {
+                // Example: "10:00 - 11:00 am"
+                String timeFormat = res.getString(com.android.internal.R.string.time1_time2);
+                timeString = String.format(timeFormat, startTimeString, endTimeString);
+            }
+        }
+
+        // Figure out which full format to use.
+        fullFormat = "";
+        String dateString = "";
+        if (showDate) {
+            dateString = startDate.format(defaultDateFormat);
+            if (showWeekDay) {
+                if (showTime) {
+                    // Example: "10:00 - 11:00 am, Tue, Oct 9"
+                    fullFormat = res.getString(com.android.internal.R.string.time_wday_date);
+                } else {
+                    // Example: "Tue, Oct 9"
+                    fullFormat = res.getString(com.android.internal.R.string.wday_date);
+                }
+            } else {
+                if (showTime) {
+                    // Example: "10:00 - 11:00 am, Oct 9"
+                    fullFormat = res.getString(com.android.internal.R.string.time_date);
+                } else {
+                    // Example: "Oct 9"
+                    return dateString;
+                }
+            }
+        } else if (showWeekDay) {
+            if (showTime) {
+                // Example: "10:00 - 11:00 am, Tue"
+                fullFormat = res.getString(com.android.internal.R.string.time_wday);
+            } else {
+                // Example: "Tue"
+                return startWeekDayString;
+            }
+        } else if (showTime) {
+            return timeString;
+        }
+
+        // The values that are used in a fullFormat string are specified
+        // by position.
+        dateRange = String.format(fullFormat, timeString, startWeekDayString, dateString);
+        return dateRange;
+    }
+
+    /**
+     * Formats a date or a time according to the local conventions. There are
+     * lots of options that allow the caller to control, for example, if the
+     * time is shown, if the day of the week is shown, if the month name is
+     * abbreviated, if noon is shown instead of 12pm, and so on. For the
+     * complete list of options, see the documentation for
+     * {@link #formatDateRange}.
+     * <p>
+     * Example output strings (date formats in these examples are shown using
+     * the US date format convention but that may change depending on the
+     * local settings):
+     * <ul>
+     *   <li>10:15am</li>
+     *   <li>3:00pm</li>
+     *   <li>3pm</li>
+     *   <li>3PM</li>
+     *   <li>08:00</li>
+     *   <li>17:00</li>
+     *   <li>noon</li>
+     *   <li>Noon</li>
+     *   <li>midnight</li>
+     *   <li>Midnight</li>
+     *   <li>Oct 31</li>
+     *   <li>Oct 31, 2007</li>
+     *   <li>October 31, 2007</li>
+     *   <li>10am, Oct 31</li>
+     *   <li>17:00, Oct 31</li>
+     *   <li>Wed</li>
+     *   <li>Wednesday</li>
+     *   <li>10am, Wed, Oct 31</li>
+     *   <li>Wed, Oct 31</li>
+     *   <li>Wednesday, Oct 31</li>
+     *   <li>Wed, Oct 31, 2007</li>
+     *   <li>Wed, October 31</li>
+     *   <li>10/31/2007</li>
+     * </ul>
+     * 
+     * @param context the context is required only if the time is shown
+     * @param millis a point in time in UTC milliseconds
+     * @param flags a bit mask of formatting options
+     * @return a string containing the formatted date/time.
+     */
+    public static String formatDateTime(Context context, long millis, int flags) {
+        return formatDateRange(context, millis, millis, flags);
+    }
+
+    /**
+     * @return a relative time string to display the time expressed by millis.  Times
+     * are counted starting at midnight, which means that assuming that the current
+     * time is March 31st, 0:30:
+     * <ul>
+     *   <li>"millis=0:10 today" will be displayed as "0:10"</li> 
+     *   <li>"millis=11:30pm the day before" will be displayed as "Mar 30"</li>
+     * </ul>
+     * If the given millis is in a different year, then the full date is
+     * returned in numeric format (e.g., "10/12/2008").
+     * 
+     * @param withPreposition If true, the string returned will include the correct 
+     * preposition ("at 9:20am", "on 10/12/2008" or "on May 29").
+     */
+    public static CharSequence getRelativeTimeSpanString(Context c, long millis,
+            boolean withPreposition) {
+
+        long now = System.currentTimeMillis();
+        long span = now - millis;
+
+        if (sNowTime == null) {
+            sNowTime = new Time();
+            sThenTime = new Time();
+        }
+
+        sNowTime.set(now);
+        sThenTime.set(millis);
+
+        String result;
+        int prepositionId;
+        if (span < DAY_IN_MILLIS && sNowTime.weekDay == sThenTime.weekDay) {
+            // Same day
+            int flags = FORMAT_SHOW_TIME;
+            result = formatDateRange(c, millis, millis, flags);
+            prepositionId = R.string.preposition_for_time;
+        } else if (sNowTime.year != sThenTime.year) {
+            // Different years
+            int flags = FORMAT_SHOW_DATE | FORMAT_SHOW_YEAR | FORMAT_NUMERIC_DATE;
+            result = formatDateRange(c, millis, millis, flags);
+
+            // This is a date (like "10/31/2008" so use the date preposition)
+            prepositionId = R.string.preposition_for_date;
+        } else {
+            // Default
+            int flags = FORMAT_SHOW_DATE | FORMAT_ABBREV_MONTH;
+            result = formatDateRange(c, millis, millis, flags);
+            prepositionId = R.string.preposition_for_date;
+        }
+        if (withPreposition) {
+            Resources res = c.getResources();
+            result = res.getString(prepositionId, result);
+        }
+        return result;
+    }
+    
+    /**
+     * Convenience function to return relative time string without preposition. 
+     * @param c context for resources
+     * @param millis time in milliseconds
+     * @return {@link CharSequence} containing relative time.
+     * @see #getRelativeTimeSpanString(Context, long, boolean)
+     */
+    public static CharSequence getRelativeTimeSpanString(Context c, long millis) {
+        return getRelativeTimeSpanString(c, millis, false /* no preposition */);
+    }
+    
+    private static Time sNowTime;
+    private static Time sThenTime;
+}
diff --git a/core/java/android/text/format/Formatter.java b/core/java/android/text/format/Formatter.java
new file mode 100644
index 0000000..1b30aa0
--- /dev/null
+++ b/core/java/android/text/format/Formatter.java
@@ -0,0 +1,83 @@
+/*
+ * 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 android.text.format;
+
+import android.content.Context;
+
+/**
+ * Utility class to aid in formatting common values that are not covered
+ * by the standard java.util.Formatter.
+ */
+public final class Formatter {
+
+    /**
+     * Formats a content size to be in the form of bytes, kilobytes, megabytes, etc
+     * 
+     * @param context Context to use to load the localized units
+     * @param number size value to be formated
+     * @return formated string with the number
+     */
+    public static String formatFileSize(Context context, long number) {
+        if (context == null) {
+            return "";
+        }
+
+        float result = number;
+        int suffix = com.android.internal.R.string.byteShort;
+        if (result > 900) {
+            suffix = com.android.internal.R.string.kilobyteShort;
+            result = result / 1024;
+        }
+        if (result > 900) {
+            suffix = com.android.internal.R.string.megabyteShort;
+            result = result / 1024;
+        }
+        if (result > 900) {
+            suffix = com.android.internal.R.string.gigabyteShort;
+            result = result / 1024;
+        }
+        if (result > 900) {
+            suffix = com.android.internal.R.string.terabyteShort;
+            result = result / 1024;
+        }
+        if (result > 900) {
+            suffix = com.android.internal.R.string.petabyteShort;
+            result = result / 1024;
+        }
+        if (result < 100) {
+            return String.format("%.2f%s", result, context.getText(suffix).toString());
+        }
+        return String.format("%.0f%s", result, context.getText(suffix).toString());
+    }
+    
+    /**
+     * Returns a string in the canonical IP format ###.###.###.### from a packed integer containing
+     * the IP address.  The IP address is expected to be in little-endian format (LSB first). That
+     * is, 0x01020304 will return "4.3.2.1".
+     * 
+     * @param addr the IP address as a packed integer with LSB first.
+     * @return string with canonical IP address format.
+     */
+    public static String formatIpAddress(int addr) {
+        StringBuffer buf = new StringBuffer();
+        buf.append(addr  & 0xff).append('.').
+            append((addr >>>= 8) & 0xff).append('.').
+            append((addr >>>= 8) & 0xff).append('.').
+            append((addr >>>= 8) & 0xff);
+        return buf.toString();
+    }
+}
diff --git a/core/java/android/text/format/Time.java b/core/java/android/text/format/Time.java
new file mode 100644
index 0000000..daa99c2
--- /dev/null
+++ b/core/java/android/text/format/Time.java
@@ -0,0 +1,757 @@
+/*
+ * 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 android.text.format;
+
+import android.content.res.Resources;
+
+import java.util.Locale;
+import java.util.TimeZone;
+
+/**
+ * The Time class is a faster replacement for the java.util.Calendar and
+ * java.util.GregorianCalendar classes. An instance of the Time class represents
+ * a moment in time, specified with second precision. It is modelled after
+ * struct tm, and in fact, uses struct tm to implement most of the
+ * functionality.
+ */
+public class Time {
+    private static final String Y_M_D_T_H_M_S_000 = "%Y-%m-%dT%H:%M:%S.000";
+    private static final String Y_M_D_T_H_M_S_000_Z = "%Y-%m-%dT%H:%M:%S.000Z";
+    private static final String Y_M_D = "%Y-%m-%d";
+    
+    public static final String TIMEZONE_UTC = "UTC";
+
+    /**
+     * The Julian day of the epoch, that is, January 1, 1970 on the Gregorian
+     * calendar.
+     */
+    public static final int EPOCH_JULIAN_DAY = 2440588;
+
+    /**
+     * True if this is an allDay event. The hour, minute, second fields are
+     * all zero, and the date is displayed the same in all time zones.
+     */
+    public boolean allDay;
+
+    /**
+     * Seconds [0-61] (2 leap seconds allowed)
+     */
+    public int second;
+
+    /**
+     * Minute [0-59]
+     */
+    public int minute;
+
+    /**
+     * Hour of day [0-23]
+     */
+    public int hour;
+
+    /**
+     * Day of month [1-31]
+     */
+    public int monthDay;
+
+    /**
+     * Month [0-11]
+     */
+    public int month;
+
+    /**
+     * Year. TBD. Is this years since 1900 like in struct tm?
+     */
+    public int year;
+
+    /**
+     * Day of week [0-6]
+     */
+    public int weekDay;
+
+    /**
+     * Day of year [0-365]
+     */
+    public int yearDay;
+
+    /**
+     * This time is in daylight savings time. One of:
+     * <ul>
+     * <li><b>positive</b> - in dst</li>
+     * <li><b>0</b> - not in dst</li>
+     * <li><b>negative</b> - unknown</li>
+     * </ul>
+     */
+    public int isDst;
+
+    /**
+     * Offset from UTC (in seconds).
+     */
+    public long gmtoff;
+
+    /**
+     * The timezone for this Time.  Should not be null.
+     */
+    public String timezone;
+
+    /*
+     * Define symbolic constants for accessing the fields in this class. Used in
+     * getActualMaximum().
+     */
+    public static final int SECOND = 1;
+    public static final int MINUTE = 2;
+    public static final int HOUR = 3;
+    public static final int MONTH_DAY = 4;
+    public static final int MONTH = 5;
+    public static final int YEAR = 6;
+    public static final int WEEK_DAY = 7;
+    public static final int YEAR_DAY = 8;
+    public static final int WEEK_NUM = 9;
+
+    public static final int SUNDAY = 0;
+    public static final int MONDAY = 1;
+    public static final int TUESDAY = 2;
+    public static final int WEDNESDAY = 3;
+    public static final int THURSDAY = 4;
+    public static final int FRIDAY = 5;
+    public static final int SATURDAY = 6;
+
+    /*
+     * The Locale for which date formatting strings have been loaded.
+     */
+    private static Locale sLocale;
+    private static String[] sShortMonths;
+    private static String[] sLongMonths;
+    private static String[] sShortWeekdays;
+    private static String[] sLongWeekdays;
+    private static String sTimeOnlyFormat;
+    private static String sDateOnlyFormat;
+    private static String sDateTimeFormat;
+    private static String sAm;
+    private static String sPm;
+    private static String sDateCommand = "%a %b %e %H:%M:%S %Z %Y";
+
+    /**
+     * Construct a Time object in the timezone named by the string
+     * argument "timezone". The time is initialized to Jan 1, 1970.
+     * @param timezone string containing the timezone to use.
+     * @see TimeZone
+     */
+    public Time(String timezone) {
+        if (timezone == null) {
+            throw new NullPointerException("timezone is null!");
+        }
+        this.timezone = timezone;
+        this.year = 1970;
+        this.monthDay = 1;
+        // Set the daylight-saving indicator to the unknown value -1 so that
+        // it will be recomputed.
+        this.isDst = -1;
+    }
+
+    /**
+     * Construct a Time object in the default timezone. The time is initialized to
+     * Jan 1, 1970.
+     */
+    public Time() {
+        this(TimeZone.getDefault().getID());
+    }
+    
+    /**
+     * A copy constructor.  Construct a Time object by copying the given
+     * Time object.  No normalization occurs.
+     * 
+     * @param other
+     */
+    public Time(Time other) {
+        set(other);
+    }
+
+    /**
+     * Ensures the values in each field are in range. For example if the
+     * current value of this calendar is March 32, normalize() will convert it
+     * to April 1. It also fills in weekDay, yearDay, isDst and gmtoff.
+     * 
+     * <p>
+     * If "ignoreDst" is true, then this method sets the "isDst" field to -1
+     * (the "unknown" value) before normalizing.  It then computes the
+     * correct value for "isDst".
+     * 
+     * <p>
+     * See {@link #toMillis(boolean)} for more information about when to
+     * use <tt>true</tt> or <tt>false</tt> for "ignoreDst".
+     * 
+     * @return the UTC milliseconds since the epoch 
+     */
+    native public long normalize(boolean ignoreDst);
+
+    /**
+     * Convert this time object so the time represented remains the same, but is
+     * instead located in a different timezone. This method automatically calls
+     * normalize() in some cases
+     */
+    native public void switchTimezone(String timezone);
+
+    private static final int[] DAYS_PER_MONTH = { 31, 28, 31, 30, 31, 30, 31,
+            31, 30, 31, 30, 31 };
+
+    /**
+     * Return the maximum possible value for the given field given the value of
+     * the other fields. Requires that it be normalized for MONTH_DAY and
+     * YEAR_DAY.
+     * @param field one of the constants for HOUR, MINUTE, SECOND, etc.
+     * @return the maximum value for the field.
+     */
+    public int getActualMaximum(int field) {
+        switch (field) {
+        case SECOND:
+            return 59; // leap seconds, bah humbug
+        case MINUTE:
+            return 59;
+        case HOUR:
+            return 23;
+        case MONTH_DAY: {
+            int n = DAYS_PER_MONTH[this.month];
+            if (n != 28) {
+                return n;
+            } else {
+                int y = this.year;
+                return ((y % 4) == 0 && ((y % 100) != 0 || (y % 400) == 0)) ? 29 : 28;
+            }
+        }
+        case MONTH:
+            return 11;
+        case YEAR:
+            return 2037;
+        case WEEK_DAY:
+            return 6;
+        case YEAR_DAY: {
+            int y = this.year;
+            // Year days are numbered from 0, so the last one is usually 364.
+            return ((y % 4) == 0 && ((y % 100) != 0 || (y % 400) == 0)) ? 365 : 364;
+        }
+        case WEEK_NUM:
+            throw new RuntimeException("WEEK_NUM not implemented");
+        default:
+            throw new RuntimeException("bad field=" + field);
+        }
+    }
+
+    /**
+     * Clears all values, setting the timezone to the given timezone. Sets isDst
+     * to a negative value to mean "unknown".
+     * @param timezone the timezone to use.
+     */
+    public void clear(String timezone) {
+        if (timezone == null) {
+            throw new NullPointerException("timezone is null!");
+        }
+        this.timezone = timezone;
+        this.allDay = false;
+        this.second = 0;
+        this.minute = 0;
+        this.hour = 0;
+        this.monthDay = 0;
+        this.month = 0;
+        this.year = 0;
+        this.weekDay = 0;
+        this.yearDay = 0;
+        this.gmtoff = 0;
+        this.isDst = -1;
+    }
+
+    /**
+     * return a negative number if a is less than b, a positive number if a is
+     * greater than b, and 0 if they are equal.
+     */
+    native public static int compare(Time a, Time b);
+
+    /**
+     * Print the current value given the format string provided. See man
+     * strftime for what means what. The final string must be less than 256
+     * characters.
+     * @param format a string containing the desired format.
+     * @return a String containing the current time expressed in the current locale.
+     */
+    public String format(String format) {
+        synchronized (Time.class) {
+            Locale locale = Locale.getDefault();
+
+            if (sLocale == null || locale == null || !(locale.equals(sLocale))) {
+                Resources r = Resources.getSystem();
+
+                sShortMonths = new String[] {
+                    r.getString(com.android.internal.R.string.month_medium_january),
+                    r.getString(com.android.internal.R.string.month_medium_february),
+                    r.getString(com.android.internal.R.string.month_medium_march),
+                    r.getString(com.android.internal.R.string.month_medium_april),
+                    r.getString(com.android.internal.R.string.month_medium_may),
+                    r.getString(com.android.internal.R.string.month_medium_june),
+                    r.getString(com.android.internal.R.string.month_medium_july),
+                    r.getString(com.android.internal.R.string.month_medium_august),
+                    r.getString(com.android.internal.R.string.month_medium_september),
+                    r.getString(com.android.internal.R.string.month_medium_october),
+                    r.getString(com.android.internal.R.string.month_medium_november),
+                    r.getString(com.android.internal.R.string.month_medium_december),
+                };
+                sLongMonths = new String[] {
+                    r.getString(com.android.internal.R.string.month_long_january),
+                    r.getString(com.android.internal.R.string.month_long_february),
+                    r.getString(com.android.internal.R.string.month_long_march),
+                    r.getString(com.android.internal.R.string.month_long_april),
+                    r.getString(com.android.internal.R.string.month_long_may),
+                    r.getString(com.android.internal.R.string.month_long_june),
+                    r.getString(com.android.internal.R.string.month_long_july),
+                    r.getString(com.android.internal.R.string.month_long_august),
+                    r.getString(com.android.internal.R.string.month_long_september),
+                    r.getString(com.android.internal.R.string.month_long_october),
+                    r.getString(com.android.internal.R.string.month_long_november),
+                    r.getString(com.android.internal.R.string.month_long_december),
+                };
+                sShortWeekdays = new String[] {
+                    r.getString(com.android.internal.R.string.day_of_week_medium_sunday),
+                    r.getString(com.android.internal.R.string.day_of_week_medium_monday),
+                    r.getString(com.android.internal.R.string.day_of_week_medium_tuesday),
+                    r.getString(com.android.internal.R.string.day_of_week_medium_wednesday),
+                    r.getString(com.android.internal.R.string.day_of_week_medium_thursday),
+                    r.getString(com.android.internal.R.string.day_of_week_medium_friday),
+                    r.getString(com.android.internal.R.string.day_of_week_medium_saturday),
+                };
+                sLongWeekdays = new String[] {
+                    r.getString(com.android.internal.R.string.day_of_week_long_sunday),
+                    r.getString(com.android.internal.R.string.day_of_week_long_monday),
+                    r.getString(com.android.internal.R.string.day_of_week_long_tuesday),
+                    r.getString(com.android.internal.R.string.day_of_week_long_wednesday),
+                    r.getString(com.android.internal.R.string.day_of_week_long_thursday),
+                    r.getString(com.android.internal.R.string.day_of_week_long_friday),
+                    r.getString(com.android.internal.R.string.day_of_week_long_saturday),
+                };
+                sTimeOnlyFormat = r.getString(com.android.internal.R.string.time_of_day);
+                sDateOnlyFormat = r.getString(com.android.internal.R.string.month_day_year);
+                sDateTimeFormat = r.getString(com.android.internal.R.string.date_and_time);
+                sAm = r.getString(com.android.internal.R.string.am);
+                sPm = r.getString(com.android.internal.R.string.pm);
+
+                sLocale = locale;
+            }
+
+            return format1(format);
+        }
+    }
+
+    native private String format1(String format);
+
+    /**
+     * Return the current time in YYYYMMDDTHHMMSS<tz> format
+     */
+    @Override
+    native public String toString();
+
+    /**
+     * Parses a date-time string in either the RFC 2445 format or an abbreviated
+     * format that does not include the "time" field.  For example, all of the
+     * following strings are valid:
+     * 
+     * <ul>
+     *   <li>"20081013T160000Z"</li>
+     *   <li>"20081013T160000"</li>
+     *   <li>"20081013"</li>
+     * </ul>
+     * 
+     * Returns whether or not the time is in UTC (ends with Z).  If the string
+     * ends with "Z" then the timezone is set to UTC.  If the date-time string
+     * included only a date and no time field, then the <code>allDay</code>
+     * field of this Time class is set to true and the <code>hour</code>,
+     * <code>minute</code>, and <code>second</code> fields are set to zero;
+     * otherwise (a time field was included in the date-time string)
+     * <code>allDay</code> is set to false. The fields <code>weekDay</code>,
+     * <code>yearDay</code>, and <code>gmtoff</code> are always set to zero,
+     * and the field <code>isDst</code> is set to -1 (unknown).  To set those
+     * fields, call {@link #normalize(boolean)} after parsing.
+     * 
+     * To parse a date-time string and convert it to UTC milliseconds, do
+     * something like this:
+     * 
+     * <pre>
+     *   Time time = new Time();
+     *   String date = "20081013T160000Z";
+     *   time.parse(date);
+     *   long millis = time.normalize(false);
+     * </pre>
+     *
+     * @param s the string to parse
+     * @return true if the resulting time value is in UTC time
+     * @throws android.util.TimeFormatException if s cannot be parsed.
+     */
+    public boolean parse(String s) {
+        if (nativeParse(s)) {
+            timezone = TIMEZONE_UTC;
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Parse a time in the current zone in YYYYMMDDTHHMMSS format.
+     */
+    native private boolean nativeParse(String s);
+
+    /**
+     * Parse a time in RFC 3339 format.  This method also parses simple dates
+     * (that is, strings that contain no time or time offset).  For example,
+     * all of the following strings are valid:
+     * 
+     * <ul>
+     *   <li>"2008-10-13T16:00:00.000Z"</li>
+     *   <li>"2008-10-13T16:00:00.000+07:00"</li>
+     *   <li>"2008-10-13T16:00:00.000-07:00"</li>
+     *   <li>"2008-10-13"</li>
+     * </ul>
+     * 
+     * <p>
+     * If the string contains a time and time offset, then the time offset will
+     * be used to convert the time value to UTC.
+     * </p>
+     * 
+     * <p>
+     * If the given string contains just a date (with no time field), then
+     * the {@link #allDay} field is set to true and the {@link #hour},
+     * {@link #minute}, and  {@link #second} fields are set to zero.
+     * </p>
+     * 
+     * <p>
+     * Returns true if the resulting time value is in UTC time.
+     * </p>
+     *
+     * @param s the string to parse
+     * @return true if the resulting time value is in UTC time
+     */
+     public boolean parse3339(String s) {
+         if (nativeParse3339(s)) {
+             timezone = TIMEZONE_UTC;
+             return true;
+         }
+         return false;
+     }
+     
+     native private boolean nativeParse3339(String s);
+
+    /**
+     * Returns the timezone string that is currently set for the device.
+     */
+    public static String getCurrentTimezone() {
+        return TimeZone.getDefault().getID();
+    }
+
+    /**
+     * Sets the time of the given Time object to the current time.
+     */
+    native public void setToNow();
+
+    /**
+     * Converts this time to milliseconds. Suitable for interacting with the
+     * standard java libraries. The time is in UTC milliseconds since the epoch.
+     * This does an implicit normalization to compute the milliseconds but does
+     * <em>not</em> change any of the fields in this Time object.  If you want
+     * to normalize the fields in this Time object and also get the milliseconds
+     * then use {@link #normalize(boolean)}.
+     * 
+     * <p>
+     * If "ignoreDst" is false, then this method uses the current setting of the
+     * "isDst" field and will adjust the returned time if the "isDst" field is
+     * wrong for the given time.  See the sample code below for an example of
+     * this.
+     * 
+     * <p>
+     * If "ignoreDst" is true, then this method ignores the current setting of
+     * the "isDst" field in this Time object and will instead figure out the
+     * correct value of "isDst" (as best it can) from the fields in this
+     * Time object.  The only case where this method cannot figure out the
+     * correct value of the "isDst" field is when the time is inherently
+     * ambiguous because it falls in the hour that is repeated when switching
+     * from Daylight-Saving Time to Standard Time.
+     * 
+     * <p>
+     * Here is an example where <tt>toMillis(true)</tt> adjusts the time,
+     * assuming that DST changes at 2am on Sunday, Nov 4, 2007.
+     * 
+     * <pre>
+     * Time time = new Time();
+     * time.set(2007, 10, 4);  // set the date to Nov 4, 2007, 12am
+     * time.normalize();       // this sets isDst = 1
+     * time.monthDay += 1;     // changes the date to Nov 5, 2007, 12am
+     * millis = time.toMillis(false);   // millis is Nov 4, 2007, 11pm
+     * millis = time.toMillis(true);    // millis is Nov 5, 2007, 12am
+     * </pre>
+     * 
+     * <p>
+     * To avoid this problem, use <tt>toMillis(true)</tt>
+     * after adding or subtracting days or explicitly setting the "monthDay"
+     * field.  On the other hand, if you are adding
+     * or subtracting hours or minutes, then you should use
+     * <tt>toMillis(false)</tt>.
+     * 
+     * <p>
+     * You should also use <tt>toMillis(false)</tt> if you want
+     * to read back the same milliseconds that you set with {@link #set(long)}
+     * or {@link #set(Time)} or after parsing a date string.
+     */
+    native public long toMillis(boolean ignoreDst);
+
+    /**
+     * Sets the fields in this Time object given the UTC milliseconds.  After
+     * this method returns, all the fields are normalized.
+     * This also sets the "isDst" field to the correct value.
+     * 
+     * @param millis the time in UTC milliseconds since the epoch.
+     */
+    native public void set(long millis);
+
+    /**
+     * Format according to RFC 2445 DATETIME type.
+     * 
+     * <p>
+     * The same as format("%Y%m%dT%H%M%S").
+     */
+    native public String format2445();
+
+    /**
+     * Copy the value of that to this Time object. No normalization happens.
+     */
+    public void set(Time that) {
+        this.timezone = that.timezone;
+        this.allDay = that.allDay;
+        this.second = that.second;
+        this.minute = that.minute;
+        this.hour = that.hour;
+        this.monthDay = that.monthDay;
+        this.month = that.month;
+        this.year = that.year;
+        this.weekDay = that.weekDay;
+        this.yearDay = that.yearDay;
+        this.isDst = that.isDst;
+        this.gmtoff = that.gmtoff;
+    }
+
+    /**
+     * Sets the fields. Sets weekDay, yearDay and gmtoff to 0, and isDst to -1.
+     * Call {@link #normalize(boolean)} if you need those.
+     */
+    public void set(int second, int minute, int hour, int monthDay, int month, int year) {
+        this.allDay = false;
+        this.second = second;
+        this.minute = minute;
+        this.hour = hour;
+        this.monthDay = monthDay;
+        this.month = month;
+        this.year = year;
+        this.weekDay = 0;
+        this.yearDay = 0;
+        this.isDst = -1;
+        this.gmtoff = 0;
+    }
+
+    /**
+     * Sets the date from the given fields.  Also sets allDay to true.
+     * Sets weekDay, yearDay and gmtoff to 0, and isDst to -1.
+     * Call {@link #normalize(boolean)} if you need those.
+     * 
+     * @param monthDay the day of the month (in the range [1,31])
+     * @param month the zero-based month number (in the range [0,11])
+     * @param year the year
+     */
+    public void set(int monthDay, int month, int year) {
+        this.allDay = true;
+        this.second = 0;
+        this.minute = 0;
+        this.hour = 0;
+        this.monthDay = monthDay;
+        this.month = month;
+        this.year = year;
+        this.weekDay = 0;
+        this.yearDay = 0;
+        this.isDst = -1;
+        this.gmtoff = 0;
+    }
+
+    /**
+     * Returns true if the time represented by this Time object occurs before
+     * the given time.
+     * 
+     * @param that a given Time object to compare against
+     * @return true if this time is less than the given time
+     */
+    public boolean before(Time that) {
+        return Time.compare(this, that) < 0;
+    }
+
+
+    /**
+     * Returns true if the time represented by this Time object occurs after
+     * the given time.
+     * 
+     * @param that a given Time object to compare against
+     * @return true if this time is greater than the given time
+     */
+    public boolean after(Time that) {
+        return Time.compare(this, that) > 0;
+    }
+
+    /**
+     * This array is indexed by the weekDay field (SUNDAY=0, MONDAY=1, etc.)
+     * and gives a number that can be added to the yearDay to give the
+     * closest Thursday yearDay.
+     */
+    private static final int[] sThursdayOffset = { -3, 3, 2, 1, 0, -1, -2 };
+        
+    /**
+     * Computes the week number according to ISO 8601.  The current Time
+     * object must already be normalized because this method uses the
+     * yearDay and weekDay fields.
+     * 
+     * <p>
+     * In IS0 8601, weeks start on Monday.
+     * The first week of the year (week 1) is defined by ISO 8601 as the
+     * first week with four or more of its days in the starting year.
+     * Or equivalently, the week containing January 4.  Or equivalently,
+     * the week with the year's first Thursday in it.
+     * </p>
+     * 
+     * <p>
+     * The week number can be calculated by counting Thursdays.  Week N
+     * contains the Nth Thursday of the year.
+     * </p>
+     *   
+     * @return the ISO week number.
+     */
+    public int getWeekNumber() {
+        // Get the year day for the closest Thursday
+        int closestThursday = yearDay + sThursdayOffset[weekDay];
+
+        // Year days start at 0
+        if (closestThursday >= 0 && closestThursday <= 364) {
+            return closestThursday / 7 + 1;
+        }
+        
+        // The week crosses a year boundary.
+        Time temp = new Time(this);
+        temp.monthDay += sThursdayOffset[weekDay];
+        temp.normalize(true /* ignore isDst */);
+        return temp.yearDay / 7 + 1;
+    }
+
+    /**
+     * Return a string in the RFC 3339 format. 
+     * <p>
+     * If allDay is true, expresses the time as Y-M-D</p>
+     * <p>
+     * Otherwise, if the timezone is UTC, expresses the time as Y-M-D-T-H-M-S UTC</p>
+     * <p>
+     * Otherwise the time is expressed the time as Y-M-D-T-H-M-S +- GMT</p>
+     * @param allDay
+     * @return string in the RFC 3339 format.
+     */
+    public String format3339(boolean allDay) {
+        if (allDay) {
+            return format(Y_M_D);
+        } else if (TIMEZONE_UTC.equals(timezone)) {
+            return format(Y_M_D_T_H_M_S_000_Z);
+        } else {
+            String base = format(Y_M_D_T_H_M_S_000);
+            String sign = (gmtoff < 0) ? "-" : "+";
+            int offset = (int)Math.abs(gmtoff);
+            int minutes = (offset % 3600) / 60;
+            int hours = offset / 3600;
+            
+            return String.format("%s%s%02d:%02d", base, sign, hours, minutes);
+        }
+    }
+    
+    /**
+     * Returns true if the day of the given time is the epoch on the Julian Calendar 
+     * (January 1, 1970 on the Gregorian calendar).
+     *
+     * @param time the time to test
+     * @return true if epoch.
+     */
+    public static boolean isEpoch(Time time) {
+        long millis = time.toMillis(true);
+        return getJulianDay(millis, 0) == EPOCH_JULIAN_DAY;
+    }
+    
+    /**
+     * Computes the Julian day number, given the UTC milliseconds
+     * and the offset (in seconds) from UTC.  The Julian day for a given
+     * date will be the same for every timezone.  For example, the Julian
+     * day for July 1, 2008 is 2454649.  This is the same value no matter
+     * what timezone is being used.  The Julian day is useful for testing
+     * if two events occur on the same day and for determining the relative
+     * time of an event from the present ("yesterday", "3 days ago", etc.).
+     * 
+     * <p>
+     * Use {@link #toMillis(boolean)} to get the milliseconds.
+     * 
+     * @param millis the time in UTC milliseconds
+     * @param gmtoff the offset from UTC in seconds
+     * @return the Julian day
+     */
+    public static int getJulianDay(long millis, long gmtoff) {
+        long offsetMillis = gmtoff * 1000;
+        long julianDay = (millis + offsetMillis) / DateUtils.DAY_IN_MILLIS;
+        return (int) julianDay + EPOCH_JULIAN_DAY;
+    }
+    
+    /**
+     * <p>Sets the time from the given Julian day number, which must be based on
+     * the same timezone that is set in this Time object.  The "gmtoff" field
+     * need not be initialized because the given Julian day may have a different
+     * GMT offset than whatever is currently stored in this Time object anyway.
+     * After this method returns all the fields will be normalized and the time
+     * will be set to 12am at the beginning of the given Julian day.
+     * </p>
+     * 
+     * <p>
+     * The only exception to this is if 12am does not exist for that day because
+     * of daylight saving time.  For example, Cairo, Eqypt moves time ahead one
+     * hour at 12am on April 25, 2008 and there are a few other places that
+     * also change daylight saving time at 12am.  In those cases, the time
+     * will be set to 1am.
+     * </p>
+     * 
+     * @param julianDay the Julian day in the timezone for this Time object
+     * @return the UTC milliseconds for the beginning of the Julian day
+     */
+    public long setJulianDay(int julianDay) {
+        // Don't bother with the GMT offset since we don't know the correct
+        // value for the given Julian day.  Just get close and then adjust
+        // the day.
+        long millis = (julianDay - EPOCH_JULIAN_DAY) * DateUtils.DAY_IN_MILLIS;
+        set(millis);
+        
+        // Figure out how close we are to the requested Julian day.
+        // We can't be off by more than a day.
+        int approximateDay = getJulianDay(millis, gmtoff);
+        int diff = julianDay - approximateDay;
+        monthDay += diff;
+        
+        // Set the time to 12am and re-normalize.
+        hour = 0;
+        minute = 0;
+        second = 0;
+        millis = normalize(true);
+        return millis;
+    }
+}
diff --git a/core/java/android/text/method/ArrowKeyMovementMethod.java b/core/java/android/text/method/ArrowKeyMovementMethod.java
new file mode 100644
index 0000000..6df0b35
--- /dev/null
+++ b/core/java/android/text/method/ArrowKeyMovementMethod.java
@@ -0,0 +1,286 @@
+/*
+ * 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 android.text.method;
+
+import android.util.Log;
+import android.view.KeyEvent;
+import android.text.*;
+import android.widget.TextView;
+import android.view.View;
+import android.view.MotionEvent;
+
+// XXX this doesn't extend MetaKeyKeyListener because the signatures
+// don't match.  Need to figure that out.  Meanwhile the meta keys
+// won't work in fields that don't take input.
+
+public class
+ArrowKeyMovementMethod
+implements MovementMethod
+{
+    private boolean up(TextView widget, Spannable buffer) {
+        boolean cap = (MetaKeyKeyListener.getMetaState(buffer,
+                        KeyEvent.META_SHIFT_ON) == 1) ||
+                      (MetaKeyKeyListener.getMetaState(buffer,
+                        MetaKeyKeyListener.META_SELECTING) != 0);
+        boolean alt = MetaKeyKeyListener.getMetaState(buffer,
+                        KeyEvent.META_ALT_ON) == 1;
+        Layout layout = widget.getLayout();
+
+        if (cap) {
+            if (alt) {
+                Selection.extendSelection(buffer, 0);
+                return true;
+            } else {
+                return Selection.extendUp(buffer, layout);
+            }
+        } else {
+            if (alt) {
+                Selection.setSelection(buffer, 0);
+                return true;
+            } else {
+                return Selection.moveUp(buffer, layout); 
+            }
+        }
+    }
+
+    private boolean down(TextView widget, Spannable buffer) {
+        boolean cap = (MetaKeyKeyListener.getMetaState(buffer,
+                        KeyEvent.META_SHIFT_ON) == 1) ||
+                      (MetaKeyKeyListener.getMetaState(buffer,
+                        MetaKeyKeyListener.META_SELECTING) != 0);
+        boolean alt = MetaKeyKeyListener.getMetaState(buffer,
+                        KeyEvent.META_ALT_ON) == 1;
+        Layout layout = widget.getLayout();
+
+        if (cap) {
+            if (alt) {
+                Selection.extendSelection(buffer, buffer.length());
+                return true;
+            } else {
+                return Selection.extendDown(buffer, layout);
+            }
+        } else {
+            if (alt) {
+                Selection.setSelection(buffer, buffer.length());
+                return true;
+            } else {
+                return Selection.moveDown(buffer, layout); 
+            }
+        }
+    }
+
+    private boolean left(TextView widget, Spannable buffer) {
+        boolean cap = (MetaKeyKeyListener.getMetaState(buffer,
+                        KeyEvent.META_SHIFT_ON) == 1) ||
+                      (MetaKeyKeyListener.getMetaState(buffer,
+                        MetaKeyKeyListener.META_SELECTING) != 0);
+        boolean alt = MetaKeyKeyListener.getMetaState(buffer,
+                        KeyEvent.META_ALT_ON) == 1;
+        Layout layout = widget.getLayout();
+
+        if (cap) {
+            if (alt) {
+                return Selection.extendToLeftEdge(buffer, layout);
+            } else {
+                return Selection.extendLeft(buffer, layout);
+            }
+        } else {
+            if (alt) {
+                return Selection.moveToLeftEdge(buffer, layout);
+            } else {
+                return Selection.moveLeft(buffer, layout); 
+            }
+        }
+    }
+
+    private boolean right(TextView widget, Spannable buffer) {
+        boolean cap = (MetaKeyKeyListener.getMetaState(buffer,
+                        KeyEvent.META_SHIFT_ON) == 1) ||
+                      (MetaKeyKeyListener.getMetaState(buffer,
+                        MetaKeyKeyListener.META_SELECTING) != 0);
+        boolean alt = MetaKeyKeyListener.getMetaState(buffer,
+                        KeyEvent.META_ALT_ON) == 1;
+        Layout layout = widget.getLayout();
+
+        if (cap) {
+            if (alt) {
+                return Selection.extendToRightEdge(buffer, layout);
+            } else {
+                return Selection.extendRight(buffer, layout);
+            }
+        } else {
+            if (alt) {
+                return Selection.moveToRightEdge(buffer, layout);
+            } else {
+                return Selection.moveRight(buffer, layout); 
+            }
+        }
+    }
+
+    public boolean onKeyDown(TextView widget, Spannable buffer, int keyCode, KeyEvent event) {
+        if (executeDown(widget, buffer, keyCode)) {
+            MetaKeyKeyListener.adjustMetaAfterKeypress(buffer);
+            MetaKeyKeyListener.resetLockedMeta(buffer);
+            return true;
+        }
+
+        return false;
+    }
+
+    private boolean executeDown(TextView widget, Spannable buffer, int keyCode) {
+        boolean handled = false;
+
+        switch (keyCode) {
+        case KeyEvent.KEYCODE_DPAD_UP:
+            handled |= up(widget, buffer);
+            break;
+
+        case KeyEvent.KEYCODE_DPAD_DOWN:
+            handled |= down(widget, buffer);
+            break;
+
+        case KeyEvent.KEYCODE_DPAD_LEFT:
+            handled |= left(widget, buffer);
+            break;
+
+        case KeyEvent.KEYCODE_DPAD_RIGHT:
+            handled |= right(widget, buffer);
+            break;
+
+        case KeyEvent.KEYCODE_DPAD_CENTER:
+            if (MetaKeyKeyListener.getMetaState(buffer, MetaKeyKeyListener.META_SELECTING) != 0) {
+                if (widget.showContextMenu()) {
+                    handled = true;
+                }
+            }
+        }
+
+        if (handled) {
+            MetaKeyKeyListener.adjustMetaAfterKeypress(buffer);
+            MetaKeyKeyListener.resetLockedMeta(buffer);
+        }
+
+        return handled;
+    }
+
+    public boolean onKeyUp(TextView widget, Spannable buffer, int keyCode, KeyEvent event) {
+        return false;
+    }
+
+    public boolean onKeyOther(TextView view, Spannable text, KeyEvent event) {
+        int code = event.getKeyCode();
+        if (code != KeyEvent.KEYCODE_UNKNOWN
+                && event.getAction() == KeyEvent.ACTION_MULTIPLE) {
+            int repeat = event.getRepeatCount();
+            boolean handled = false;
+            while ((--repeat) > 0) {
+                handled |= executeDown(view, text, code);
+            }
+            return handled;
+        }
+        return false;
+    }
+    
+    public boolean onTrackballEvent(TextView widget, Spannable text,
+            MotionEvent event) {
+        return false;
+    }
+    
+    public boolean onTouchEvent(TextView widget, Spannable buffer,
+                                MotionEvent event) {
+        boolean handled = Touch.onTouchEvent(widget, buffer, event);
+
+        if (widget.isFocused()) {
+            if (event.getAction() == MotionEvent.ACTION_UP) {
+                int x = (int) event.getX();
+                int y = (int) event.getY();
+
+                x -= widget.getTotalPaddingLeft();
+                y -= widget.getTotalPaddingTop();
+
+                x += widget.getScrollX();
+                y += widget.getScrollY();
+
+                Layout layout = widget.getLayout();
+                int line = layout.getLineForVertical(y);
+                int off = layout.getOffsetForHorizontal(line, x);
+
+                boolean cap = (MetaKeyKeyListener.getMetaState(buffer,
+                                KeyEvent.META_SHIFT_ON) == 1) ||
+                              (MetaKeyKeyListener.getMetaState(buffer,
+                                MetaKeyKeyListener.META_SELECTING) != 0);
+
+                if (cap) {
+                    Selection.extendSelection(buffer, off);
+                } else {
+                    Selection.setSelection(buffer, off);
+                }
+
+                MetaKeyKeyListener.adjustMetaAfterKeypress(buffer);
+                MetaKeyKeyListener.resetLockedMeta(buffer);
+
+                return true;
+            }
+        }
+
+        return handled;
+    }
+
+    public boolean canSelectArbitrarily() {
+        return true;
+    }
+
+    public void initialize(TextView widget, Spannable text) {
+        Selection.setSelection(text, 0);
+    }
+
+    public void onTakeFocus(TextView view, Spannable text, int dir) {
+        if ((dir & (View.FOCUS_FORWARD | View.FOCUS_DOWN)) != 0) {
+            Layout layout = view.getLayout();
+
+            if (layout == null) {
+                /*
+                 * This shouldn't be null, but do something sensible if it is.
+                 */
+                Selection.setSelection(text, text.length());
+            } else {
+                /*
+                 * Put the cursor at the end of the first line, which is
+                 * either the last offset if there is only one line, or the
+                 * offset before the first character of the second line
+                 * if there is more than one line.
+                 */
+                if (layout.getLineCount() == 1) {
+                    Selection.setSelection(text, text.length());
+                } else {
+                    Selection.setSelection(text, layout.getLineStart(1) - 1);
+                }
+            }
+        } else {
+            Selection.setSelection(text, text.length());
+        }
+    }
+
+    public static MovementMethod getInstance() {
+        if (sInstance == null)
+            sInstance = new ArrowKeyMovementMethod();
+
+        return sInstance;
+    }
+
+    private static ArrowKeyMovementMethod sInstance;
+}
diff --git a/core/java/android/text/method/BaseKeyListener.java b/core/java/android/text/method/BaseKeyListener.java
new file mode 100644
index 0000000..6df6a3a
--- /dev/null
+++ b/core/java/android/text/method/BaseKeyListener.java
@@ -0,0 +1,160 @@
+/*
+ * 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 android.text.method;
+
+import android.view.KeyEvent;
+import android.view.View;
+import android.text.*;
+import android.text.method.TextKeyListener.Capitalize;
+import android.widget.TextView;
+
+public abstract class BaseKeyListener
+extends MetaKeyKeyListener
+implements KeyListener {
+    /* package */ static final Object OLD_SEL_START = new NoCopySpan.Concrete();
+
+    /**
+     * Performs the action that happens when you press the DEL key in
+     * a TextView.  If there is a selection, deletes the selection;
+     * otherwise, DEL alone deletes the character before the cursor,
+     * if any;
+     * ALT+DEL deletes everything on the line the cursor is on.
+     *
+     * @return true if anything was deleted; false otherwise.   
+     */
+    public boolean backspace(View view, Editable content, int keyCode,
+                             KeyEvent event) {
+        int selStart, selEnd;
+        boolean result = true;
+
+        {
+            int a = Selection.getSelectionStart(content);
+            int b = Selection.getSelectionEnd(content);
+
+            selStart = Math.min(a, b);
+            selEnd = Math.max(a, b);
+        }
+
+        if (selStart != selEnd) {
+            content.delete(selStart, selEnd);
+        } else if (altBackspace(view, content, keyCode, event)) {
+            result = true;
+        } else {
+            int to = TextUtils.getOffsetBefore(content, selEnd);
+
+            if (to != selEnd) {
+                content.delete(Math.min(to, selEnd), Math.max(to, selEnd));
+            }
+            else {
+                result = false;
+            }
+        }
+
+        if (result)
+            adjustMetaAfterKeypress(content);
+
+        return result;
+    }
+
+    private boolean altBackspace(View view, Editable content, int keyCode,
+                                 KeyEvent event) {
+        if (getMetaState(content, META_ALT_ON) != 1) {
+            return false;
+        }
+
+        if (!(view instanceof TextView)) {
+            return false;
+        }
+
+        Layout layout = ((TextView) view).getLayout();
+
+        if (layout == null) {
+            return false;
+        }
+
+        int l = layout.getLineForOffset(Selection.getSelectionStart(content));
+        int start = layout.getLineStart(l);
+        int end = layout.getLineEnd(l);
+
+        if (end == start) {
+            return false;
+        }
+
+        content.delete(start, end);
+        return true;
+    }
+
+    static int makeTextContentType(Capitalize caps, boolean autoText) {
+        int contentType = InputType.TYPE_CLASS_TEXT;
+        switch (caps) {
+            case CHARACTERS:
+                contentType |= InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS;
+                break;
+            case WORDS:
+                contentType |= InputType.TYPE_TEXT_FLAG_CAP_WORDS;
+                break;
+            case SENTENCES:
+                contentType |= InputType.TYPE_TEXT_FLAG_CAP_SENTENCES;
+                break;
+        }
+        if (autoText) {
+            contentType |= InputType.TYPE_TEXT_FLAG_AUTO_CORRECT;
+        }
+        return contentType;
+    }
+    
+    public boolean onKeyDown(View view, Editable content,
+                             int keyCode, KeyEvent event) {
+        if (keyCode == KeyEvent.KEYCODE_DEL) {
+            backspace(view, content, keyCode, event);
+            return true;
+        }
+        
+        return super.onKeyDown(view, content, keyCode, event);
+    }
+    
+    /**
+     * Base implementation handles ACTION_MULTIPLE KEYCODE_UNKNOWN by inserting
+     * the event's text into the content.
+     */
+    public boolean onKeyOther(View view, Editable content, KeyEvent event) {
+        if (event.getAction() != KeyEvent.ACTION_MULTIPLE
+                || event.getKeyCode() != KeyEvent.KEYCODE_UNKNOWN) {
+            // Not something we are interested in.
+            return false;
+        }
+        
+        int selStart, selEnd;
+
+        {
+            int a = Selection.getSelectionStart(content);
+            int b = Selection.getSelectionEnd(content);
+
+            selStart = Math.min(a, b);
+            selEnd = Math.max(a, b);
+        }
+
+        CharSequence text = event.getCharacters();
+        if (text == null) {
+            return false;
+        }
+        
+        content.replace(selStart, selEnd, text);
+        return true;
+    }
+}
+
diff --git a/core/java/android/text/method/CharacterPickerDialog.java b/core/java/android/text/method/CharacterPickerDialog.java
new file mode 100644
index 0000000..3c406751
--- /dev/null
+++ b/core/java/android/text/method/CharacterPickerDialog.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 android.text.method;
+
+import com.android.internal.R;
+
+import android.app.Dialog;
+import android.content.Context;
+import android.os.Bundle;
+import android.text.*;
+import android.view.LayoutInflater;
+import android.view.View.OnClickListener;
+import android.view.View;
+import android.view.ViewGroup.LayoutParams;
+import android.view.ViewGroup;
+import android.view.WindowManager;
+import android.widget.AdapterView.OnItemClickListener;
+import android.widget.AdapterView;
+import android.widget.BaseAdapter;
+import android.widget.Button;
+import android.widget.GridView;
+import android.widget.TextView;
+
+/**
+ * Dialog for choosing accented characters related to a base character.
+ */
+public class CharacterPickerDialog extends Dialog
+        implements OnItemClickListener, OnClickListener {
+    private View mView;
+    private Editable mText;
+    private String mOptions;
+    private boolean mInsert;
+    private LayoutInflater mInflater;
+
+    /**
+     * Creates a new CharacterPickerDialog that presents the specified
+     * <code>options</code> for insertion or replacement (depending on
+     * the sense of <code>insert</code>) into <code>text</code>.
+     */
+    public CharacterPickerDialog(Context context, View view,
+                                 Editable text, String options,
+                                 boolean insert) {
+        super(context);
+
+        mView = view;
+        mText = text;
+        mOptions = options;
+        mInsert = insert;
+        mInflater = LayoutInflater.from(context);
+    }
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        WindowManager.LayoutParams params = getWindow().getAttributes();
+        params.token = mView.getApplicationWindowToken();
+        params.type = params.TYPE_APPLICATION_ATTACHED_DIALOG;
+
+        setTitle(R.string.select_character);
+        setContentView(R.layout.character_picker);
+
+        GridView grid = (GridView) findViewById(R.id.characterPicker);
+        grid.setAdapter(new OptionsAdapter(getContext()));
+        grid.setOnItemClickListener(this);
+
+        findViewById(R.id.cancel).setOnClickListener(this);
+    }
+
+    /**
+     * Handles clicks on the character buttons.
+     */
+    public void onItemClick(AdapterView parent, View view, int position, long id) {
+        int selEnd = Selection.getSelectionEnd(mText);
+        String result = String.valueOf(mOptions.charAt(position));
+
+        if (mInsert || selEnd == 0) {
+            mText.insert(selEnd, result);
+        } else {
+            mText.replace(selEnd - 1, selEnd, result);
+        }
+
+        dismiss();
+    }
+
+    /**
+     * Handles clicks on the Cancel button.
+     */
+    public void onClick(View v) {
+        dismiss();
+    }
+
+    private class OptionsAdapter extends BaseAdapter {
+        private Context mContext;
+
+        public OptionsAdapter(Context context) {
+            super();
+            mContext = context;
+        }
+
+        public View getView(int position, View convertView, ViewGroup parent) {
+            Button b = (Button)
+                mInflater.inflate(R.layout.character_picker_button, null);
+            b.setText(String.valueOf(mOptions.charAt(position)));
+            return b;
+        }
+
+        public final int getCount() {
+            return mOptions.length();
+        }
+
+        public final Object getItem(int position) {
+            return String.valueOf(mOptions.charAt(position));
+        }
+
+        public final long getItemId(int position) {
+            return position;
+        }
+    }
+}
diff --git a/core/java/android/text/method/DateKeyListener.java b/core/java/android/text/method/DateKeyListener.java
new file mode 100644
index 0000000..7c11434
--- /dev/null
+++ b/core/java/android/text/method/DateKeyListener.java
@@ -0,0 +1,58 @@
+/*
+ * 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 android.text.method;
+
+import android.view.KeyEvent;
+import android.text.InputType;
+
+/**
+ * For entering dates in a text field.
+ */
+public class DateKeyListener extends NumberKeyListener
+{
+    public int getInputType() {
+        return InputType.TYPE_CLASS_DATETIME
+                | InputType.TYPE_DATETIME_VARIATION_DATE;
+    }
+    
+    @Override
+    protected char[] getAcceptedChars()
+    {
+        return CHARACTERS;
+    }
+
+    public static DateKeyListener getInstance() {
+        if (sInstance != null)
+            return sInstance;
+
+        sInstance = new DateKeyListener();
+        return sInstance;
+    }
+
+    /**
+     * The characters that are used.
+     *
+     * @see KeyEvent#getMatch
+     * @see #getAcceptedChars
+     */
+    public static final char[] CHARACTERS = new char[] {
+            '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
+            '/', '-', '.'
+        };
+
+    private static DateKeyListener sInstance;
+}
diff --git a/core/java/android/text/method/DateTimeKeyListener.java b/core/java/android/text/method/DateTimeKeyListener.java
new file mode 100644
index 0000000..f8ebc40
--- /dev/null
+++ b/core/java/android/text/method/DateTimeKeyListener.java
@@ -0,0 +1,58 @@
+/*
+ * 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 android.text.method;
+
+import android.text.InputType;
+import android.view.KeyEvent;
+
+/**
+ * For entering dates and times in the same text field.
+ */
+public class DateTimeKeyListener extends NumberKeyListener
+{
+    public int getInputType() {
+        return InputType.TYPE_CLASS_DATETIME
+                | InputType.TYPE_DATETIME_VARIATION_NORMAL;
+    }
+    
+    @Override
+    protected char[] getAcceptedChars()
+    {
+        return CHARACTERS;
+    }
+
+    public static DateTimeKeyListener getInstance() {
+        if (sInstance != null)
+            return sInstance;
+
+        sInstance = new DateTimeKeyListener();
+        return sInstance;
+    }
+
+    /**
+     * The characters that are used.
+     *
+     * @see KeyEvent#getMatch
+     * @see #getAcceptedChars
+     */
+    public static final char[] CHARACTERS = new char[] {
+            '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'm',
+            'p', ':', '/', '-', ' '
+        };
+
+    private static DateTimeKeyListener sInstance;
+}
diff --git a/core/java/android/text/method/DialerKeyListener.java b/core/java/android/text/method/DialerKeyListener.java
new file mode 100644
index 0000000..b121e60
--- /dev/null
+++ b/core/java/android/text/method/DialerKeyListener.java
@@ -0,0 +1,113 @@
+/*
+ * 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 android.text.method;
+
+import android.view.KeyEvent;
+import android.view.KeyCharacterMap.KeyData;
+import android.text.InputType;
+import android.text.Spannable;
+
+/**
+ * For dialing-only text entry
+ */
+public class DialerKeyListener extends NumberKeyListener
+{
+    @Override
+    protected char[] getAcceptedChars()
+    {
+        return CHARACTERS;
+    }
+
+    public static DialerKeyListener getInstance() {
+        if (sInstance != null)
+            return sInstance;
+
+        sInstance = new DialerKeyListener();
+        return sInstance;
+    }
+
+    public int getInputType() {
+        return InputType.TYPE_CLASS_PHONE;
+    }
+    
+    /**
+     * Overrides the superclass's lookup method to prefer the number field
+     * from the KeyEvent.
+     */
+    protected int lookup(KeyEvent event, Spannable content) {
+        int meta = getMetaState(content);
+        int number = event.getNumber();
+
+        /*
+         * Prefer number if no meta key is active, or if it produces something
+         * valid and the meta lookup does not.
+         */
+        if ((meta & (KeyEvent.META_ALT_ON | KeyEvent.META_SHIFT_ON)) == 0) {
+            if (number != 0) {
+                return number;
+            }
+        }
+
+        int match = super.lookup(event, content);
+
+        if (match != 0) {
+            return match;
+        } else {
+            /*
+             * If a meta key is active but the lookup with the meta key
+             * did not produce anything, try some other meta keys, because
+             * the user might have pressed SHIFT when they meant ALT,
+             * or vice versa.
+             */
+
+            if (meta != 0) {
+                KeyData kd = new KeyData();
+                char[] accepted = getAcceptedChars();
+
+                if (event.getKeyData(kd)) {
+                    for (int i = 1; i < kd.meta.length; i++) {
+                        if (ok(accepted, kd.meta[i])) {
+                            return kd.meta[i];
+                        }
+                    }
+                }
+            }
+
+            /*
+             * Otherwise, use the number associated with the key, since
+             * whatever they wanted to do with the meta key does not
+             * seem to be valid here.
+             */
+
+            return number;
+        }
+    }
+
+
+    /**
+     * The characters that are used.
+     *
+     * @see KeyEvent#getMatch
+     * @see #getAcceptedChars
+     */
+    public static final char[] CHARACTERS = new char[] {
+            '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '#', '*',
+            '+', '-', '(', ')', ',', '/', 'N', '.', ' '
+        };
+
+    private static DialerKeyListener sInstance;
+}
diff --git a/core/java/android/text/method/DigitsKeyListener.java b/core/java/android/text/method/DigitsKeyListener.java
new file mode 100644
index 0000000..f0f072c
--- /dev/null
+++ b/core/java/android/text/method/DigitsKeyListener.java
@@ -0,0 +1,218 @@
+/*
+ * 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 android.text.method;
+
+import android.text.InputType;
+import android.text.Spanned;
+import android.text.SpannableStringBuilder;
+import android.view.KeyEvent;
+
+
+/**
+ * For digits-only text entry
+ */
+public class DigitsKeyListener extends NumberKeyListener
+{
+    private char[] mAccepted;
+    private boolean mSign;
+    private boolean mDecimal;
+
+    private static final int SIGN = 1;
+    private static final int DECIMAL = 2;
+
+    @Override
+    protected char[] getAcceptedChars() {
+        return mAccepted;
+    }
+
+    /**
+     * The characters that are used.
+     *
+     * @see KeyEvent#getMatch
+     * @see #getAcceptedChars
+     */
+    private static final char[][] CHARACTERS = new char[][] {
+        new char[] { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' },
+        new char[] { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-' },
+        new char[] { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '.' },
+        new char[] { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-', '.' },
+    };
+
+    /**
+     * Allocates a DigitsKeyListener that accepts the digits 0 through 9.
+     */
+    public DigitsKeyListener() {
+        this(false, false);
+    }
+
+    /**
+     * Allocates a DigitsKeyListener that accepts the digits 0 through 9,
+     * plus the minus sign (only at the beginning) and/or decimal point
+     * (only one per field) if specified.
+     */
+    public DigitsKeyListener(boolean sign, boolean decimal) {
+        mSign = sign;
+        mDecimal = decimal;
+
+        int kind = (sign ? SIGN : 0) | (decimal ? DECIMAL : 0);
+        mAccepted = CHARACTERS[kind];
+    }
+
+    /**
+     * Returns a DigitsKeyListener that accepts the digits 0 through 9.
+     */
+    public static DigitsKeyListener getInstance() {
+        return getInstance(false, false);
+    }
+
+    /**
+     * Returns a DigitsKeyListener that accepts the digits 0 through 9,
+     * plus the minus sign (only at the beginning) and/or decimal point
+     * (only one per field) if specified.
+     */
+    public static DigitsKeyListener getInstance(boolean sign, boolean decimal) {
+        int kind = (sign ? SIGN : 0) | (decimal ? DECIMAL : 0);
+
+        if (sInstance[kind] != null)
+            return sInstance[kind];
+
+        sInstance[kind] = new DigitsKeyListener(sign, decimal);
+        return sInstance[kind];
+    }
+
+    /**
+     * Returns a DigitsKeyListener that accepts only the characters
+     * that appear in the specified String.  Note that not all characters
+     * may be available on every keyboard.
+     */
+    public static DigitsKeyListener getInstance(String accepted) {
+        // TODO: do we need a cache of these to avoid allocating?
+
+        DigitsKeyListener dim = new DigitsKeyListener();
+
+        dim.mAccepted = new char[accepted.length()];
+        accepted.getChars(0, accepted.length(), dim.mAccepted, 0);
+
+        return dim;
+    }
+
+    public int getInputType() {
+        int contentType = InputType.TYPE_CLASS_NUMBER;
+        if (mSign) {
+            contentType |= InputType.TYPE_NUMBER_FLAG_SIGNED;
+        }
+        if (mDecimal) {
+            contentType |= InputType.TYPE_NUMBER_FLAG_DECIMAL;
+        }
+        return contentType;
+    }
+    
+    @Override
+    public CharSequence filter(CharSequence source, int start, int end,
+                               Spanned dest, int dstart, int dend) {
+        CharSequence out = super.filter(source, start, end, dest, dstart, dend);
+
+        if (mSign == false && mDecimal == false) {
+            return out;
+        }
+
+        if (out != null) {
+            source = out;
+            start = 0;
+            end = out.length();
+        }
+
+        int sign = -1;
+        int decimal = -1;
+        int dlen = dest.length();
+
+        /*
+         * Find out if the existing text has '-' or '.' characters.
+         */
+
+        for (int i = 0; i < dstart; i++) {
+            char c = dest.charAt(i);
+
+            if (c == '-') {
+                sign = i;
+            } else if (c == '.') {
+                decimal = i;
+            }
+        }
+        for (int i = dend; i < dlen; i++) {
+            char c = dest.charAt(i);
+
+            if (c == '-') {
+                return "";    // Nothing can be inserted in front of a '-'.
+            } else if (c == '.') {
+                decimal = i;
+            }
+        }
+
+        /*
+         * If it does, we must strip them out from the source.
+         * In addition, '-' must be the very first character,
+         * and nothing can be inserted before an existing '-'.
+         * Go in reverse order so the offsets are stable.
+         */
+
+        SpannableStringBuilder stripped = null;
+
+        for (int i = end - 1; i >= start; i--) {
+            char c = source.charAt(i);
+            boolean strip = false;
+
+            if (c == '-') {
+                if (i != start || dstart != 0) {
+                    strip = true;
+                } else if (sign >= 0) {
+                    strip = true;
+                } else {
+                    sign = i;
+                }
+            } else if (c == '.') {
+                if (decimal >= 0) {
+                    strip = true;
+                } else {
+                    decimal = i;
+                }
+            }
+
+            if (strip) {
+                if (end == start + 1) {
+                    return "";  // Only one character, and it was stripped.
+                }
+
+                if (stripped == null) {
+                    stripped = new SpannableStringBuilder(source, start, end);
+                }
+
+                stripped.delete(i - start, i + 1 - start);
+            }
+        }
+
+        if (stripped != null) {
+            return stripped;
+        } else if (out != null) {
+            return out;
+        } else {
+            return null;
+        }
+    }
+
+    private static DigitsKeyListener[] sInstance = new DigitsKeyListener[4];
+}
diff --git a/core/java/android/text/method/HideReturnsTransformationMethod.java b/core/java/android/text/method/HideReturnsTransformationMethod.java
new file mode 100644
index 0000000..ce18692
--- /dev/null
+++ b/core/java/android/text/method/HideReturnsTransformationMethod.java
@@ -0,0 +1,59 @@
+/*
+ * 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 android.text.method;
+
+import android.graphics.Rect;
+import android.text.GetChars;
+import android.text.Spanned;
+import android.text.SpannedString;
+import android.text.TextUtils;
+import android.view.View; 
+
+/**
+ * This transformation method causes any carriage return characters (\r)
+ * to be hidden by displaying them as zero-width non-breaking space
+ * characters (\uFEFF).
+ */
+public class HideReturnsTransformationMethod
+extends ReplacementTransformationMethod {
+    private static char[] ORIGINAL = new char[] { '\r' };
+    private static char[] REPLACEMENT = new char[] { '\uFEFF' };
+
+    /**
+     * The character to be replaced is \r.
+     */
+    protected char[] getOriginal() {
+        return ORIGINAL;
+    }
+
+    /**
+     * The character that \r is replaced with is \uFEFF.
+     */
+    protected char[] getReplacement() {
+        return REPLACEMENT;
+    }
+
+    public static HideReturnsTransformationMethod getInstance() {
+        if (sInstance != null)
+            return sInstance;
+
+        sInstance = new HideReturnsTransformationMethod();
+        return sInstance;
+    }
+
+    private static HideReturnsTransformationMethod sInstance;
+}
diff --git a/core/java/android/text/method/KeyListener.java b/core/java/android/text/method/KeyListener.java
new file mode 100644
index 0000000..8594852
--- /dev/null
+++ b/core/java/android/text/method/KeyListener.java
@@ -0,0 +1,79 @@
+/*
+ * 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 android.text.method;
+
+import android.text.Editable;
+import android.view.KeyEvent;
+import android.view.View;
+
+/**
+ * Interface for converting text key events into edit operations on an
+ * Editable class.  Note that for must cases this interface has been
+ * superceded by general soft input methods as defined by
+ * {@link android.view.inputmethod.InputMethod}; it should only be used
+ * for cases where an application has its own on-screen keypad and also wants
+ * to process hard keyboard events to match it.
+ */
+public interface KeyListener {
+    /**
+     * Return the type of text that this key listener is manipulating,
+     * as per {@link android.text.InputType}.  This is used to
+     * determine the mode of the soft keyboard that is shown for the editor.
+     * 
+     * <p>If you return
+     * {@link android.text.InputType#TYPE_NULL}
+     * then <em>no</em> soft keyboard will provided.  In other words, you
+     * must be providing your own key pad for on-screen input and the key
+     * listener will be used to handle input from a hard keyboard.
+     * 
+     * <p>If you
+     * return any other value, a soft input method will be created when the
+     * user puts focus in the editor, which will provide a keypad and also
+     * consume hard key events.  This means that the key listener will generally
+     * not be used, instead the soft input method will take care of managing
+     * key input as per the content type returned here.
+     */
+    public int getInputType();
+    
+    /**
+     * If the key listener wants to handle this key, return true,
+     * otherwise return false and the caller (i.e. the widget host)
+     * will handle the key.
+     */
+    public boolean onKeyDown(View view, Editable text,
+                             int keyCode, KeyEvent event);
+
+    /**
+     * If the key listener wants to handle this key release, return true,
+     * otherwise return false and the caller (i.e. the widget host)
+     * will handle the key.
+     */
+    public boolean onKeyUp(View view, Editable text,
+                           int keyCode, KeyEvent event);
+    
+    /**
+     * If the key listener wants to other kinds of key events, return true,
+     * otherwise return false and the caller (i.e. the widget host)
+     * will handle the key.
+     */
+    public boolean onKeyOther(View view, Editable text, KeyEvent event);
+    
+    /**
+     * Remove the given shift states from the edited text.
+     */
+    public void clearMetaKeyState(View view, Editable content, int states);
+}
diff --git a/core/java/android/text/method/LinkMovementMethod.java b/core/java/android/text/method/LinkMovementMethod.java
new file mode 100644
index 0000000..22e9cc6
--- /dev/null
+++ b/core/java/android/text/method/LinkMovementMethod.java
@@ -0,0 +1,256 @@
+/*
+ * 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 android.text.method;
+
+import android.content.Intent;
+import android.net.Uri;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+import android.text.*;
+import android.text.style.*;
+import android.view.View;
+import android.widget.TextView;
+
+public class
+LinkMovementMethod
+extends ScrollingMovementMethod
+{
+    private static final int CLICK = 1;
+    private static final int UP = 2;
+    private static final int DOWN = 3;
+
+    @Override
+    public boolean onKeyDown(TextView widget, Spannable buffer,
+                             int keyCode, KeyEvent event) {
+        switch (keyCode) {
+        case KeyEvent.KEYCODE_DPAD_CENTER:
+        case KeyEvent.KEYCODE_ENTER:
+            if (event.getRepeatCount() == 0) {
+                if (action(CLICK, widget, buffer)) {
+                    return true;
+                }
+            }
+        }
+
+        return super.onKeyDown(widget, buffer, keyCode, event);
+    }
+
+    @Override
+    protected boolean up(TextView widget, Spannable buffer) {
+        if (action(UP, widget, buffer)) {
+            return true;
+        }
+
+        return super.up(widget, buffer);
+    }
+        
+    @Override
+    protected boolean down(TextView widget, Spannable buffer) {
+        if (action(DOWN, widget, buffer)) {
+            return true;
+        }
+
+        return super.down(widget, buffer);
+    }
+
+    @Override
+    protected boolean left(TextView widget, Spannable buffer) {
+        if (action(UP, widget, buffer)) {
+            return true;
+        }
+
+        return super.left(widget, buffer);
+    }
+
+    @Override
+    protected boolean right(TextView widget, Spannable buffer) {
+        if (action(DOWN, widget, buffer)) {
+            return true;
+        }
+
+        return super.right(widget, buffer);
+    }
+
+    private boolean action(int what, TextView widget, Spannable buffer) {
+        boolean handled = false;
+
+        Layout layout = widget.getLayout();
+
+        int padding = widget.getTotalPaddingTop() +
+                      widget.getTotalPaddingBottom();
+        int areatop = widget.getScrollY();
+        int areabot = areatop + widget.getHeight() - padding;
+
+        int linetop = layout.getLineForVertical(areatop);
+        int linebot = layout.getLineForVertical(areabot);
+
+        int first = layout.getLineStart(linetop);
+        int last = layout.getLineEnd(linebot);
+
+        ClickableSpan[] candidates = buffer.getSpans(first, last, ClickableSpan.class);
+
+        int a = Selection.getSelectionStart(buffer);
+        int b = Selection.getSelectionEnd(buffer);
+
+        int selStart = Math.min(a, b);
+        int selEnd = Math.max(a, b);
+
+        if (selStart < 0) {
+            if (buffer.getSpanStart(FROM_BELOW) >= 0) {
+                selStart = selEnd = buffer.length();
+            }
+        }
+
+        if (selStart > last)
+            selStart = selEnd = Integer.MAX_VALUE;
+        if (selEnd < first)
+            selStart = selEnd = -1;
+
+        switch (what) {
+        case CLICK:
+            if (selStart == selEnd) {
+                return false;
+            }
+
+            ClickableSpan[] link = buffer.getSpans(selStart, selEnd, ClickableSpan.class);
+
+            if (link.length != 1)
+                return false;
+
+            link[0].onClick(widget);
+            break;
+
+        case UP:
+            int beststart, bestend;
+
+            beststart = -1;
+            bestend = -1;
+
+            for (int i = 0; i < candidates.length; i++) {
+                int end = buffer.getSpanEnd(candidates[i]);
+
+                if (end < selEnd || selStart == selEnd) {
+                    if (end > bestend) {
+                        beststart = buffer.getSpanStart(candidates[i]);
+                        bestend = end;
+                    }
+                }
+            }
+
+            if (beststart >= 0) {
+                Selection.setSelection(buffer, bestend, beststart);
+                return true;
+            }
+
+            break;
+
+        case DOWN:
+            beststart = Integer.MAX_VALUE;
+            bestend = Integer.MAX_VALUE;
+
+            for (int i = 0; i < candidates.length; i++) {
+                int start = buffer.getSpanStart(candidates[i]);
+
+                if (start > selStart || selStart == selEnd) {
+                    if (start < beststart) {
+                        beststart = start;
+                        bestend = buffer.getSpanEnd(candidates[i]);
+                    }
+                }
+            }
+
+            if (bestend < Integer.MAX_VALUE) {
+                Selection.setSelection(buffer, beststart, bestend);
+                return true;
+            }
+
+            break;
+        }
+
+        return false;
+    }
+
+    public boolean onKeyUp(TextView widget, Spannable buffer,
+                           int keyCode, KeyEvent event) {
+        return false;
+    }
+
+    @Override
+    public boolean onTouchEvent(TextView widget, Spannable buffer,
+                                MotionEvent event) {
+        int action = event.getAction();
+
+        if (action == MotionEvent.ACTION_UP ||
+            action == MotionEvent.ACTION_DOWN) {
+            int x = (int) event.getX();
+            int y = (int) event.getY();
+
+            x -= widget.getTotalPaddingLeft();
+            y -= widget.getTotalPaddingTop();
+
+            x += widget.getScrollX();
+            y += widget.getScrollY();
+
+            Layout layout = widget.getLayout();
+            int line = layout.getLineForVertical(y);
+            int off = layout.getOffsetForHorizontal(line, x);
+
+            ClickableSpan[] link = buffer.getSpans(off, off, ClickableSpan.class);
+
+            if (link.length != 0) {
+                if (action == MotionEvent.ACTION_UP) {
+                    link[0].onClick(widget);
+                } else if (action == MotionEvent.ACTION_DOWN) {
+                    Selection.setSelection(buffer,
+                                           buffer.getSpanStart(link[0]),
+                                           buffer.getSpanEnd(link[0]));
+                }
+
+                return true;
+            } else {
+                Selection.removeSelection(buffer);
+            }
+        }
+
+        return super.onTouchEvent(widget, buffer, event);
+    }
+
+    public void initialize(TextView widget, Spannable text) {
+        Selection.removeSelection(text);
+        text.removeSpan(FROM_BELOW);
+    }
+
+    public void onTakeFocus(TextView view, Spannable text, int dir) {
+        Selection.removeSelection(text);
+
+        if ((dir & View.FOCUS_BACKWARD) != 0) {
+            text.setSpan(FROM_BELOW, 0, 0, Spannable.SPAN_POINT_POINT);
+        } else {
+            text.removeSpan(FROM_BELOW);
+        }
+    }
+
+    public static MovementMethod getInstance() {
+        if (sInstance == null)
+            sInstance = new LinkMovementMethod();
+
+        return sInstance;
+    }
+
+    private static LinkMovementMethod sInstance;
+    private static Object FROM_BELOW = new NoCopySpan.Concrete();
+}
diff --git a/core/java/android/text/method/MetaKeyKeyListener.java b/core/java/android/text/method/MetaKeyKeyListener.java
new file mode 100644
index 0000000..39ad976
--- /dev/null
+++ b/core/java/android/text/method/MetaKeyKeyListener.java
@@ -0,0 +1,485 @@
+/*
+ * 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 android.text.method;
+
+import android.view.KeyEvent;
+import android.view.View;
+import android.text.*;
+
+/**
+ * This base class encapsulates the behavior for handling the meta keys
+ * (shift and alt) and the pseudo-meta state of selecting text.
+ * Key listeners that care about meta state should
+ * inherit from it; you should not instantiate this class directly in a client.
+ */
+
+public abstract class MetaKeyKeyListener {
+    public static final int META_SHIFT_ON = KeyEvent.META_SHIFT_ON;
+    public static final int META_ALT_ON = KeyEvent.META_ALT_ON;
+    public static final int META_SYM_ON = KeyEvent.META_SYM_ON;
+
+    private static final int LOCKED_SHIFT = 8;
+    
+    public static final int META_CAP_LOCKED = KeyEvent.META_SHIFT_ON << LOCKED_SHIFT;
+    public static final int META_ALT_LOCKED = KeyEvent.META_ALT_ON << LOCKED_SHIFT;
+    public static final int META_SYM_LOCKED = KeyEvent.META_SYM_ON << LOCKED_SHIFT;
+
+    /**
+     * @hide pending API review
+     */
+    public static final int META_SELECTING = 1 << 16;
+
+    private static final int USED_SHIFT = 24;
+    
+    private static final long META_CAP_USED = ((long)KeyEvent.META_SHIFT_ON) << USED_SHIFT;
+    private static final long META_ALT_USED = ((long)KeyEvent.META_ALT_ON) << USED_SHIFT;
+    private static final long META_SYM_USED = ((long)KeyEvent.META_SYM_ON) << USED_SHIFT;
+
+    private static final int PRESSED_SHIFT = 32;
+    
+    private static final long META_CAP_PRESSED = ((long)KeyEvent.META_SHIFT_ON) << PRESSED_SHIFT;
+    private static final long META_ALT_PRESSED = ((long)KeyEvent.META_ALT_ON) << PRESSED_SHIFT;
+    private static final long META_SYM_PRESSED = ((long)KeyEvent.META_SYM_ON) << PRESSED_SHIFT;
+
+    private static final int RELEASED_SHIFT = 40;
+    
+    private static final long META_CAP_RELEASED = ((long)KeyEvent.META_SHIFT_ON) << RELEASED_SHIFT;
+    private static final long META_ALT_RELEASED = ((long)KeyEvent.META_ALT_ON) << RELEASED_SHIFT;
+    private static final long META_SYM_RELEASED = ((long)KeyEvent.META_SYM_ON) << RELEASED_SHIFT;
+
+    private static final long META_SHIFT_MASK = META_SHIFT_ON
+            | META_CAP_LOCKED | META_CAP_USED
+            | META_CAP_PRESSED | META_CAP_RELEASED;
+    private static final long META_ALT_MASK = META_ALT_ON
+            | META_ALT_LOCKED | META_ALT_USED
+            | META_ALT_PRESSED | META_ALT_RELEASED;
+    private static final long META_SYM_MASK = META_SYM_ON
+            | META_SYM_LOCKED | META_SYM_USED
+            | META_SYM_PRESSED | META_SYM_RELEASED;
+    
+    private static final Object CAP = new NoCopySpan.Concrete();
+    private static final Object ALT = new NoCopySpan.Concrete();
+    private static final Object SYM = new NoCopySpan.Concrete();
+    private static final Object SELECTING = new NoCopySpan.Concrete();
+
+    /**
+     * Resets all meta state to inactive.
+     */
+    public static void resetMetaState(Spannable text) {
+        text.removeSpan(CAP);
+        text.removeSpan(ALT);
+        text.removeSpan(SYM);
+        text.removeSpan(SELECTING);
+    }
+
+    /**
+     * Gets the state of the meta keys.
+     * 
+     * @param text the buffer in which the meta key would have been pressed.
+     *
+     * @return an integer in which each bit set to one represents a pressed
+     *         or locked meta key.
+     */
+    public static final int getMetaState(CharSequence text) {
+        return getActive(text, CAP, META_SHIFT_ON, META_CAP_LOCKED) |
+               getActive(text, ALT, META_ALT_ON, META_ALT_LOCKED) |
+               getActive(text, SYM, META_SYM_ON, META_SYM_LOCKED) |
+               getActive(text, SELECTING, META_SELECTING, META_SELECTING);
+    }
+
+    /**
+     * Gets the state of a particular meta key.
+     *
+     * @param meta META_SHIFT_ON, META_ALT_ON, META_SYM_ON, or META_SELECTING
+     * @param text the buffer in which the meta key would have been pressed.
+     *
+     * @return 0 if inactive, 1 if active, 2 if locked.
+     */
+    public static final int getMetaState(CharSequence text, int meta) {
+        switch (meta) {
+            case META_SHIFT_ON:
+                return getActive(text, CAP, 1, 2);
+
+            case META_ALT_ON:
+                return getActive(text, ALT, 1, 2);
+
+            case META_SYM_ON:
+                return getActive(text, SYM, 1, 2);
+
+            case META_SELECTING:
+                return getActive(text, SELECTING, 1, 2);
+
+            default:
+                return 0;
+        }
+    }
+
+    private static int getActive(CharSequence text, Object meta,
+                                 int on, int lock) {
+        if (!(text instanceof Spanned)) {
+            return 0;
+        }
+
+        Spanned sp = (Spanned) text;
+        int flag = sp.getSpanFlags(meta);
+
+        if (flag == LOCKED) {
+            return lock;
+        } else if (flag != 0) {
+            return on;
+        } else {
+            return 0;
+        }
+    }
+
+    /**
+     * Call this method after you handle a keypress so that the meta
+     * state will be reset to unshifted (if it is not still down)
+     * or primed to be reset to unshifted (once it is released).
+     */
+    public static void adjustMetaAfterKeypress(Spannable content) {
+        adjust(content, CAP);
+        adjust(content, ALT);
+        adjust(content, SYM);
+    }
+
+    /**
+     * Returns true if this object is one that this class would use to
+     * keep track of meta state in the specified text.
+     */
+    public static boolean isMetaTracker(CharSequence text, Object what) {
+        return what == CAP || what == ALT || what == SYM ||
+               what == SELECTING;
+    }
+
+    private static void adjust(Spannable content, Object what) {
+        int current = content.getSpanFlags(what);
+
+        if (current == PRESSED)
+            content.setSpan(what, 0, 0, USED);
+        else if (current == RELEASED)
+            content.removeSpan(what);
+    }
+
+    /**
+     * Call this if you are a method that ignores the locked meta state
+     * (arrow keys, for example) and you handle a key.
+     */
+    protected static void resetLockedMeta(Spannable content) {
+        resetLock(content, CAP);
+        resetLock(content, ALT);
+        resetLock(content, SYM);
+        resetLock(content, SELECTING);
+    }
+
+    private static void resetLock(Spannable content, Object what) {
+        int current = content.getSpanFlags(what);
+
+        if (current == LOCKED)
+            content.removeSpan(what);
+    }
+
+    /**
+     * Handles presses of the meta keys.
+     */
+    public boolean onKeyDown(View view, Editable content,
+                             int keyCode, KeyEvent event) {
+        if (keyCode == KeyEvent.KEYCODE_SHIFT_LEFT || keyCode == KeyEvent.KEYCODE_SHIFT_RIGHT) {
+            press(content, CAP);
+            return true;
+        }
+
+        if (keyCode == KeyEvent.KEYCODE_ALT_LEFT || keyCode == KeyEvent.KEYCODE_ALT_RIGHT
+                || keyCode == KeyEvent.KEYCODE_NUM) {
+            press(content, ALT);
+            return true;
+        }
+
+        if (keyCode == KeyEvent.KEYCODE_SYM) {
+            press(content, SYM);
+            return true;
+        }
+
+        return false; // no super to call through to
+    }
+
+    private void press(Editable content, Object what) {
+        int state = content.getSpanFlags(what);
+
+        if (state == PRESSED)
+            ; // repeat before use
+        else if (state == RELEASED)
+            content.setSpan(what, 0, 0, LOCKED);
+        else if (state == USED)
+            ; // repeat after use
+        else if (state == LOCKED)
+            content.removeSpan(what);
+        else
+            content.setSpan(what, 0, 0, PRESSED);
+    }
+
+    /**
+     * Start selecting text.
+     * @hide pending API review
+     */
+    public static void startSelecting(View view, Spannable content) {
+        content.setSpan(SELECTING, 0, 0, PRESSED);
+    }
+
+    /**
+     * Stop selecting text.  This does not actually collapse the selection;
+     * call {@link android.text.Selection#setSelection} too.
+     * @hide pending API review
+     */
+    public static void stopSelecting(View view, Spannable content) {
+        content.removeSpan(SELECTING);
+    }
+
+    /**
+     * Handles release of the meta keys.
+     */
+    public boolean onKeyUp(View view, Editable content, int keyCode,
+                                    KeyEvent event) {
+        if (keyCode == KeyEvent.KEYCODE_SHIFT_LEFT || keyCode == KeyEvent.KEYCODE_SHIFT_RIGHT) {
+            release(content, CAP);
+            return true;
+        }
+
+        if (keyCode == KeyEvent.KEYCODE_ALT_LEFT || keyCode == KeyEvent.KEYCODE_ALT_RIGHT
+                || keyCode == KeyEvent.KEYCODE_NUM) {
+            release(content, ALT);
+            return true;
+        }
+
+        if (keyCode == KeyEvent.KEYCODE_SYM) {
+            release(content, SYM);
+            return true;
+        }
+
+        return false; // no super to call through to
+    }
+
+    private void release(Editable content, Object what) {
+        int current = content.getSpanFlags(what);
+
+        if (current == USED)
+            content.removeSpan(what);
+        else if (current == PRESSED)
+            content.setSpan(what, 0, 0, RELEASED);
+    }
+
+    public void clearMetaKeyState(View view, Editable content, int states) {
+        clearMetaKeyState(content, states);
+    }
+
+    public static void clearMetaKeyState(Editable content, int states) {
+        if ((states&META_SHIFT_ON) != 0) content.removeSpan(CAP);
+        if ((states&META_ALT_ON) != 0) content.removeSpan(ALT);
+        if ((states&META_SYM_ON) != 0) content.removeSpan(SYM);
+        if ((states&META_SELECTING) != 0) content.removeSpan(SELECTING);
+    }
+
+    /**
+     * Call this if you are a method that ignores the locked meta state
+     * (arrow keys, for example) and you handle a key.
+     */
+    public static long resetLockedMeta(long state) {
+        state = resetLock(state, META_SHIFT_ON, META_SHIFT_MASK);
+        state = resetLock(state, META_ALT_ON, META_ALT_MASK);
+        state = resetLock(state, META_SYM_ON, META_SYM_MASK);
+        return state;
+    }
+
+    private static long resetLock(long state, int what, long mask) {
+        if ((state&(((long)what)<<LOCKED_SHIFT)) != 0) {
+            state &= ~mask;
+        }
+        return state;
+    }
+
+    // ---------------------------------------------------------------------
+    // Version of API that operates on a state bit mask
+    // ---------------------------------------------------------------------
+
+    /**
+     * Gets the state of the meta keys.
+     * 
+     * @param state the current meta state bits.
+     *
+     * @return an integer in which each bit set to one represents a pressed
+     *         or locked meta key.
+     */
+    public static final int getMetaState(long state) {
+        return getActive(state, META_SHIFT_ON, META_SHIFT_ON, META_CAP_LOCKED) |
+               getActive(state, META_ALT_ON, META_ALT_ON, META_ALT_LOCKED) |
+               getActive(state, META_SYM_ON, META_SYM_ON, META_SYM_LOCKED);
+    }
+
+    /**
+     * Gets the state of a particular meta key.
+     *
+     * @param state the current state bits.
+     * @param meta META_SHIFT_ON, META_ALT_ON, or META_SYM_ON
+     *
+     * @return 0 if inactive, 1 if active, 2 if locked.
+     */
+    public static final int getMetaState(long state, int meta) {
+        switch (meta) {
+            case META_SHIFT_ON:
+                return getActive(state, meta, 1, 2);
+
+            case META_ALT_ON:
+                return getActive(state, meta, 1, 2);
+
+            case META_SYM_ON:
+                return getActive(state, meta, 1, 2);
+
+            default:
+                return 0;
+        }
+    }
+
+    private static int getActive(long state, int meta, int on, int lock) {
+        if ((state&(meta<<LOCKED_SHIFT)) != 0) {
+            return lock;
+        } else if ((state&meta) != 0) {
+            return on;
+        } else {
+            return 0;
+        }
+    }
+
+    /**
+     * Call this method after you handle a keypress so that the meta
+     * state will be reset to unshifted (if it is not still down)
+     * or primed to be reset to unshifted (once it is released).  Takes
+     * the current state, returns the new state.
+     */
+    public static long adjustMetaAfterKeypress(long state) {
+        state = adjust(state, META_SHIFT_ON, META_SHIFT_MASK);
+        state = adjust(state, META_ALT_ON, META_ALT_MASK);
+        state = adjust(state, META_SYM_ON, META_SYM_MASK);
+        return state;
+    }
+
+    private static long adjust(long state, int what, long mask) {
+        if ((state&(((long)what)<<PRESSED_SHIFT)) != 0)
+            return (state&~mask) | what | ((long)what)<<USED_SHIFT;
+        else if ((state&(((long)what)<<RELEASED_SHIFT)) != 0)
+            return state & ~mask;
+        return state;
+    }
+
+    /**
+     * Handles presses of the meta keys.
+     */
+    public static long handleKeyDown(long state, int keyCode, KeyEvent event) {
+        if (keyCode == KeyEvent.KEYCODE_SHIFT_LEFT || keyCode == KeyEvent.KEYCODE_SHIFT_RIGHT) {
+            return press(state, META_SHIFT_ON, META_SHIFT_MASK);
+        }
+
+        if (keyCode == KeyEvent.KEYCODE_ALT_LEFT || keyCode == KeyEvent.KEYCODE_ALT_RIGHT
+                || keyCode == KeyEvent.KEYCODE_NUM) {
+            return press(state, META_ALT_ON, META_ALT_MASK);
+        }
+
+        if (keyCode == KeyEvent.KEYCODE_SYM) {
+            return press(state, META_SYM_ON, META_SYM_MASK);
+        }
+
+        return state;
+    }
+
+    private static long press(long state, int what, long mask) {
+        if ((state&(((long)what)<<PRESSED_SHIFT)) != 0)
+            ; // repeat before use
+        else if ((state&(((long)what)<<RELEASED_SHIFT)) != 0)
+            state = (state&~mask) | what | (((long)what) << LOCKED_SHIFT);
+        else if ((state&(((long)what)<<USED_SHIFT)) != 0)
+            ; // repeat after use
+        else if ((state&(((long)what)<<LOCKED_SHIFT)) != 0)
+            state = state&~mask;
+        else
+            state = state | what | (((long)what)<<PRESSED_SHIFT);
+        return state;
+    }
+
+    /**
+     * Handles release of the meta keys.
+     */
+    public static long handleKeyUp(long state, int keyCode, KeyEvent event) {
+        if (keyCode == KeyEvent.KEYCODE_SHIFT_LEFT || keyCode == KeyEvent.KEYCODE_SHIFT_RIGHT) {
+            return release(state, META_SHIFT_ON, META_SHIFT_MASK);
+        }
+
+        if (keyCode == KeyEvent.KEYCODE_ALT_LEFT || keyCode == KeyEvent.KEYCODE_ALT_RIGHT
+                || keyCode == KeyEvent.KEYCODE_NUM) {
+            return release(state, META_ALT_ON, META_ALT_MASK);
+        }
+
+        if (keyCode == KeyEvent.KEYCODE_SYM) {
+            return release(state, META_SYM_ON, META_SYM_MASK);
+        }
+
+        return state;
+    }
+
+    private static long release(long state, int what, long mask) {
+        if ((state&(((long)what)<<USED_SHIFT)) != 0)
+            state = state&~mask;
+        else if ((state&(((long)what)<<PRESSED_SHIFT)) != 0)
+            state = state | what | (((long)what)<<RELEASED_SHIFT);
+        return state;
+    }
+
+    public long clearMetaKeyState(long state, int which) {
+        if ((which&META_SHIFT_ON) != 0)
+            state = resetLock(state, META_SHIFT_ON, META_SHIFT_MASK);
+        if ((which&META_ALT_ON) != 0)
+            state = resetLock(state, META_ALT_ON, META_ALT_MASK);
+        if ((which&META_SYM_ON) != 0)
+            state = resetLock(state, META_SYM_ON, META_SYM_MASK);
+        return state;
+    }
+    
+    /**
+     * The meta key has been pressed but has not yet been used.
+     */
+    private static final int PRESSED = 
+        Spannable.SPAN_MARK_MARK | (1 << Spannable.SPAN_USER_SHIFT);
+
+    /**
+     * The meta key has been pressed and released but has still
+     * not yet been used.
+     */
+    private static final int RELEASED = 
+        Spannable.SPAN_MARK_MARK | (2 << Spannable.SPAN_USER_SHIFT);
+
+    /**
+     * The meta key has been pressed and used but has not yet been released.
+     */
+    private static final int USED = 
+        Spannable.SPAN_MARK_MARK | (3 << Spannable.SPAN_USER_SHIFT);
+
+    /**
+     * The meta key has been pressed and released without use, and then
+     * pressed again; it may also have been released again.
+     */
+    private static final int LOCKED = 
+        Spannable.SPAN_MARK_MARK | (4 << Spannable.SPAN_USER_SHIFT);
+}
+
diff --git a/core/java/android/text/method/MovementMethod.java b/core/java/android/text/method/MovementMethod.java
new file mode 100644
index 0000000..29f67a1
--- /dev/null
+++ b/core/java/android/text/method/MovementMethod.java
@@ -0,0 +1,51 @@
+/*
+ * 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 android.text.method;
+
+import android.widget.TextView;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+import android.text.*;
+
+public interface MovementMethod
+{
+    public void initialize(TextView widget, Spannable text);
+    public boolean onKeyDown(TextView widget, Spannable text, int keyCode, KeyEvent event);
+    public boolean onKeyUp(TextView widget, Spannable text, int keyCode, KeyEvent event);
+    
+    /**
+     * If the key listener wants to other kinds of key events, return true,
+     * otherwise return false and the caller (i.e. the widget host)
+     * will handle the key.
+     */
+    public boolean onKeyOther(TextView view, Spannable text, KeyEvent event);
+    
+    public void onTakeFocus(TextView widget, Spannable text, int direction);
+    public boolean onTrackballEvent(TextView widget, Spannable text,
+                                    MotionEvent event);
+    public boolean onTouchEvent(TextView widget, Spannable text,
+                                MotionEvent event);
+
+    /**
+     * Returns true if this movement method allows arbitrary selection
+     * of any text; false if it has no selection (like a movement method
+     * that only scrolls) or a constrained selection (for example
+     * limited to links.  The "Select All" menu item is disabled
+     * if arbitrary selection is not allowed.
+     */
+    public boolean canSelectArbitrarily();
+}
diff --git a/core/java/android/text/method/MultiTapKeyListener.java b/core/java/android/text/method/MultiTapKeyListener.java
new file mode 100644
index 0000000..6d94788
--- /dev/null
+++ b/core/java/android/text/method/MultiTapKeyListener.java
@@ -0,0 +1,286 @@
+/*
+ * 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 android.text.method;
+
+import android.view.KeyEvent;
+import android.view.View;
+import android.os.Handler;
+import android.os.SystemClock;
+import android.text.*;
+import android.text.method.TextKeyListener.Capitalize;
+import android.util.SparseArray;
+
+/**
+ * This is the standard key listener for alphabetic input on 12-key
+ * keyboards.  You should generally not need to instantiate this yourself;
+ * TextKeyListener will do it for you.
+ */
+public class MultiTapKeyListener extends BaseKeyListener
+        implements SpanWatcher {
+    private static MultiTapKeyListener[] sInstance =
+        new MultiTapKeyListener[Capitalize.values().length * 2];
+
+    private static final SparseArray<String> sRecs = new SparseArray<String>();
+
+    private Capitalize mCapitalize;
+    private boolean mAutoText;
+
+    static {
+        sRecs.put(KeyEvent.KEYCODE_1,     ".,1!@#$%^&*:/?'=()");
+        sRecs.put(KeyEvent.KEYCODE_2,     "abc2ABC");
+        sRecs.put(KeyEvent.KEYCODE_3,     "def3DEF");
+        sRecs.put(KeyEvent.KEYCODE_4,     "ghi4GHI");
+        sRecs.put(KeyEvent.KEYCODE_5,     "jkl5JKL");
+        sRecs.put(KeyEvent.KEYCODE_6,     "mno6MNO");
+        sRecs.put(KeyEvent.KEYCODE_7,     "pqrs7PQRS");
+        sRecs.put(KeyEvent.KEYCODE_8,     "tuv8TUV");
+        sRecs.put(KeyEvent.KEYCODE_9,     "wxyz9WXYZ");
+        sRecs.put(KeyEvent.KEYCODE_0,     "0+");
+        sRecs.put(KeyEvent.KEYCODE_POUND, " ");
+    };
+
+    public MultiTapKeyListener(Capitalize cap,
+                               boolean autotext) {
+        mCapitalize = cap;
+        mAutoText = autotext;
+    }
+
+    /**
+     * Returns a new or existing instance with the specified capitalization
+     * and correction properties.
+     */
+    public static MultiTapKeyListener getInstance(boolean autotext,
+                                                  Capitalize cap) {
+        int off = cap.ordinal() * 2 + (autotext ? 1 : 0);
+
+        if (sInstance[off] == null) {
+            sInstance[off] = new MultiTapKeyListener(cap, autotext);
+        }
+
+        return sInstance[off];
+    }
+
+    public int getInputType() {
+        return makeTextContentType(mCapitalize, mAutoText);
+    }
+    
+    public boolean onKeyDown(View view, Editable content,
+                             int keyCode, KeyEvent event) {
+        int selStart, selEnd;
+        int pref = 0;
+
+        if (view != null) {
+            pref = TextKeyListener.getInstance().getPrefs(view.getContext());
+        }
+
+        {
+            int a = Selection.getSelectionStart(content);
+            int b = Selection.getSelectionEnd(content);
+
+            selStart = Math.min(a, b);
+            selEnd = Math.max(a, b);
+        }
+
+        int activeStart = content.getSpanStart(TextKeyListener.ACTIVE);
+        int activeEnd = content.getSpanEnd(TextKeyListener.ACTIVE);
+
+        // now for the multitap cases...
+
+        // Try to increment the character we were working on before
+        // if we have one and it's still the same key.
+
+        int rec = (content.getSpanFlags(TextKeyListener.ACTIVE)
+                    & Spannable.SPAN_USER) >>> Spannable.SPAN_USER_SHIFT;
+
+        if (activeStart == selStart && activeEnd == selEnd &&
+            selEnd - selStart == 1 &&
+            rec >= 0 && rec < sRecs.size()) {
+            if (keyCode == KeyEvent.KEYCODE_STAR) {
+                char current = content.charAt(selStart);
+
+                if (Character.isLowerCase(current)) {
+                    content.replace(selStart, selEnd,
+                                    String.valueOf(current).toUpperCase());
+                    removeTimeouts(content);
+                    Timeout t = new Timeout(content);
+
+                    return true;
+                }
+                if (Character.isUpperCase(current)) {
+                    content.replace(selStart, selEnd,
+                                    String.valueOf(current).toLowerCase());
+                    removeTimeouts(content);
+                    Timeout t = new Timeout(content);
+
+                    return true;
+                }
+            }
+
+            if (sRecs.indexOfKey(keyCode) == rec) {
+                String val = sRecs.valueAt(rec);
+                char ch = content.charAt(selStart);
+                int ix = val.indexOf(ch);
+
+                if (ix >= 0) {
+                    ix = (ix + 1) % (val.length());
+
+                    content.replace(selStart, selEnd, val, ix, ix + 1);
+                    removeTimeouts(content);
+                    Timeout t = new Timeout(content);
+
+                    return true;
+                }
+            }
+
+            // Is this key one we know about at all?  If so, acknowledge
+            // that the selection is our fault but the key has changed
+            // or the text no longer matches, so move the selection over
+            // so that it inserts instead of replaces.
+
+            rec = sRecs.indexOfKey(keyCode);
+
+            if (rec >= 0) {
+                Selection.setSelection(content, selEnd, selEnd);
+                selStart = selEnd;
+            }
+        } else {
+            rec = sRecs.indexOfKey(keyCode);
+        }
+
+        if (rec >= 0) {
+            // We have a valid key.  Replace the selection or insertion point
+            // with the first character for that key, and remember what
+            // record it came from for next time.
+
+            String val = sRecs.valueAt(rec);
+
+            int off = 0;
+            if ((pref & TextKeyListener.AUTO_CAP) != 0 &&
+                TextKeyListener.shouldCap(mCapitalize, content, selStart)) {
+                for (int i = 0; i < val.length(); i++) {
+                    if (Character.isUpperCase(val.charAt(i))) {
+                        off = i;
+                        break;
+                    }
+                }
+            }
+
+            if (selStart != selEnd) {
+                Selection.setSelection(content, selEnd);
+            }
+
+            content.setSpan(OLD_SEL_START, selStart, selStart,
+                            Spannable.SPAN_MARK_MARK);
+
+            content.replace(selStart, selEnd, val, off, off + 1);
+
+            int oldStart = content.getSpanStart(OLD_SEL_START);
+            selEnd = Selection.getSelectionEnd(content);
+
+            if (selEnd != oldStart) {
+                Selection.setSelection(content, oldStart, selEnd);
+
+                content.setSpan(TextKeyListener.LAST_TYPED, 
+                                oldStart, selEnd,
+                                Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+
+                content.setSpan(TextKeyListener.ACTIVE,
+                            oldStart, selEnd,
+                            Spannable.SPAN_EXCLUSIVE_EXCLUSIVE |
+                            (rec << Spannable.SPAN_USER_SHIFT));
+
+            }
+
+            removeTimeouts(content);
+            Timeout t = new Timeout(content);
+
+            // Set up the callback so we can remove the timeout if the
+            // cursor moves.
+
+            if (content.getSpanStart(this) < 0) {
+                KeyListener[] methods = content.getSpans(0, content.length(),
+                                                    KeyListener.class);
+                for (Object method : methods) {
+                    content.removeSpan(method);
+                }
+                content.setSpan(this, 0, content.length(),
+                                Spannable.SPAN_INCLUSIVE_INCLUSIVE);
+            }
+
+            return true;
+        }
+
+        return super.onKeyDown(view, content, keyCode, event);
+    }
+
+    public void onSpanChanged(Spannable buf,
+                              Object what, int s, int e, int start, int stop) {
+        if (what == Selection.SELECTION_END) {
+            buf.removeSpan(TextKeyListener.ACTIVE);
+            removeTimeouts(buf);
+        }
+    }
+
+    private static void removeTimeouts(Spannable buf) {
+        Timeout[] timeout = buf.getSpans(0, buf.length(), Timeout.class);
+
+        for (int i = 0; i < timeout.length; i++) {
+            Timeout t = timeout[i];
+
+            t.removeCallbacks(t);
+            t.mBuffer = null;
+            buf.removeSpan(t);
+        }
+    }
+
+    private class Timeout
+    extends Handler
+    implements Runnable
+    {
+        public Timeout(Editable buffer) {
+            mBuffer = buffer;
+            mBuffer.setSpan(Timeout.this, 0, mBuffer.length(),
+                            Spannable.SPAN_INCLUSIVE_INCLUSIVE);
+
+            postAtTime(this, SystemClock.uptimeMillis() + 2000);
+        }
+
+        public void run() {
+            Spannable buf = mBuffer;
+
+            if (buf != null) {
+                int st = Selection.getSelectionStart(buf);
+                int en = Selection.getSelectionEnd(buf);
+
+                int start = buf.getSpanStart(TextKeyListener.ACTIVE);
+                int end = buf.getSpanEnd(TextKeyListener.ACTIVE);
+
+                if (st == start && en == end) {
+                    Selection.setSelection(buf, Selection.getSelectionEnd(buf));
+                }
+
+                buf.removeSpan(Timeout.this);
+            }
+        }
+
+        private Editable mBuffer;
+    }
+
+    public void onSpanAdded(Spannable s, Object what, int start, int end) { }
+    public void onSpanRemoved(Spannable s, Object what, int start, int end) { }
+}
+
diff --git a/core/java/android/text/method/NumberKeyListener.java b/core/java/android/text/method/NumberKeyListener.java
new file mode 100644
index 0000000..9270ca5
--- /dev/null
+++ b/core/java/android/text/method/NumberKeyListener.java
@@ -0,0 +1,136 @@
+/*
+ * 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 android.text.method;
+
+import android.view.KeyEvent;
+import android.view.View;
+import android.text.Editable;
+import android.text.InputFilter;
+import android.text.Selection;
+import android.text.Spannable;
+import android.text.SpannableStringBuilder;
+import android.text.Spanned;
+
+/**
+ * For numeric text entry
+ */
+public abstract class NumberKeyListener extends BaseKeyListener
+    implements InputFilter
+{
+    /**
+     * You can say which characters you can accept.
+     */
+    protected abstract char[] getAcceptedChars();
+
+    protected int lookup(KeyEvent event, Spannable content) {
+        return event.getMatch(getAcceptedChars(), getMetaState(content));
+    }
+
+    public CharSequence filter(CharSequence source, int start, int end,
+                               Spanned dest, int dstart, int dend) {
+        char[] accept = getAcceptedChars();
+        boolean filter = false;
+
+        int i;
+        for (i = start; i < end; i++) {
+            if (!ok(accept, source.charAt(i))) {
+                break;
+            }
+        }
+
+        if (i == end) {
+            // It was all OK.
+            return null;
+        }
+
+        if (end - start == 1) {
+            // It was not OK, and there is only one char, so nothing remains.
+            return "";
+        }
+
+        SpannableStringBuilder filtered =
+            new SpannableStringBuilder(source, start, end);
+        i -= start;
+        end -= start;
+
+        int len = end - start;
+        // Only count down to i because the chars before that were all OK.
+        for (int j = end - 1; j >= i; j--) {
+            if (!ok(accept, source.charAt(j))) {
+                filtered.delete(j, j + 1);
+            }
+        }
+
+        return filtered;
+    }
+
+    protected static boolean ok(char[] accept, char c) {
+        for (int i = accept.length - 1; i >= 0; i--) {
+            if (accept[i] == c) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+    
+    @Override
+    public boolean onKeyDown(View view, Editable content,
+                             int keyCode, KeyEvent event) {
+        int selStart, selEnd;
+
+        {
+            int a = Selection.getSelectionStart(content);
+            int b = Selection.getSelectionEnd(content);
+
+            selStart = Math.min(a, b);
+            selEnd = Math.max(a, b);
+        }
+
+        if (selStart < 0 || selEnd < 0) {
+            selStart = selEnd = 0;
+            Selection.setSelection(content, 0);
+        }
+
+        int i = event != null ? lookup(event, content) : 0;
+        int repeatCount = event != null ? event.getRepeatCount() : 0;
+        if (repeatCount == 0) {
+            if (i != 0) {
+                if (selStart != selEnd) {
+                    Selection.setSelection(content, selEnd);
+                }
+
+                content.replace(selStart, selEnd, String.valueOf((char) i));
+
+                adjustMetaAfterKeypress(content);
+                return true;
+            }
+        } else if (i == '0' && repeatCount == 1) {
+            // Pretty hackish, it replaces the 0 with the +
+
+            if (selStart == selEnd && selEnd > 0 &&
+                    content.charAt(selStart - 1) == '0') {
+                content.replace(selStart - 1, selEnd, String.valueOf('+'));
+                adjustMetaAfterKeypress(content);
+                return true;
+            }
+        }
+
+        adjustMetaAfterKeypress(content);
+        return super.onKeyDown(view, content, keyCode, event);
+    }
+}
diff --git a/core/java/android/text/method/PasswordTransformationMethod.java b/core/java/android/text/method/PasswordTransformationMethod.java
new file mode 100644
index 0000000..fad4f64
--- /dev/null
+++ b/core/java/android/text/method/PasswordTransformationMethod.java
@@ -0,0 +1,264 @@
+/*
+ * 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 android.text.method;
+
+import android.os.Handler;
+import android.os.SystemClock;
+import android.graphics.Rect;
+import android.view.View;
+import android.text.Editable;
+import android.text.GetChars;
+import android.text.NoCopySpan;
+import android.text.TextUtils;
+import android.text.TextWatcher;
+import android.text.Selection;
+import android.text.Spanned;
+import android.text.Spannable;
+import android.text.style.UpdateLayout;
+
+import java.lang.ref.WeakReference;
+
+public class PasswordTransformationMethod
+implements TransformationMethod, TextWatcher
+{
+    public CharSequence getTransformation(CharSequence source, View view) {
+        if (source instanceof Spannable) {
+            Spannable sp = (Spannable) source;
+
+            /*
+             * Remove any references to other views that may still be
+             * attached.  This will happen when you flip the screen
+             * while a password field is showing; there will still
+             * be references to the old EditText in the text.
+             */
+            ViewReference[] vr = sp.getSpans(0, sp.length(),
+                                             ViewReference.class);
+            for (int i = 0; i < vr.length; i++) {
+                sp.removeSpan(vr[i]);
+            }
+
+            sp.setSpan(new ViewReference(view), 0, 0,
+                       Spannable.SPAN_POINT_POINT);
+        }
+
+        return new PasswordCharSequence(source);
+    }
+
+    public static PasswordTransformationMethod getInstance() {
+        if (sInstance != null)
+            return sInstance;
+
+        sInstance = new PasswordTransformationMethod();
+        return sInstance;
+    }
+
+    public void beforeTextChanged(CharSequence s, int start,
+                                  int count, int after) {
+        // This callback isn't used.
+    }
+
+    public void onTextChanged(CharSequence s, int start,
+                              int before, int count) {
+        if (s instanceof Spannable) {
+            Spannable sp = (Spannable) s;
+            ViewReference[] vr = sp.getSpans(0, s.length(),
+                                             ViewReference.class);
+            if (vr.length == 0) {
+                return;
+            }
+
+            /*
+             * There should generally only be one ViewReference in the text,
+             * but make sure to look through all of them if necessary in case
+             * something strange is going on.  (We might still end up with
+             * multiple ViewReferences if someone moves text from one password
+             * field to another.)
+             */
+            View v = null;
+            for (int i = 0; v == null && i < vr.length; i++) {
+                v = vr[i].get();
+            }
+
+            if (v == null) {
+                return;
+            }
+
+            int pref = TextKeyListener.getInstance().getPrefs(v.getContext());
+            if ((pref & TextKeyListener.SHOW_PASSWORD) != 0) {
+                if (count > 0) {
+                    Visible[] old = sp.getSpans(0, sp.length(), Visible.class);
+                    for (int i = 0; i < old.length; i++) {
+                        sp.removeSpan(old[i]);
+                    }
+
+                    if (count == 1) {
+                        sp.setSpan(new Visible(sp, this), start, start + count,
+                                   Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+                    }
+                }
+            }
+        }
+    }
+
+    public void afterTextChanged(Editable s) {
+        // This callback isn't used.
+    }
+
+    public void onFocusChanged(View view, CharSequence sourceText,
+                               boolean focused, int direction,
+                               Rect previouslyFocusedRect) {
+        if (!focused) {
+            if (sourceText instanceof Spannable) {
+                Spannable sp = (Spannable) sourceText;
+
+                Visible[] old = sp.getSpans(0, sp.length(), Visible.class);
+                for (int i = 0; i < old.length; i++) {
+                    sp.removeSpan(old[i]);
+                }
+            }
+        }
+    }
+
+    private static class PasswordCharSequence
+    implements CharSequence, GetChars
+    {
+        public PasswordCharSequence(CharSequence source) {
+            mSource = source;
+        }
+
+        public int length() {
+            return mSource.length();
+        }
+
+        public char charAt(int i) {
+            if (mSource instanceof Spanned) {
+                Spanned sp = (Spanned) mSource;
+
+                int st = sp.getSpanStart(TextKeyListener.ACTIVE);
+                int en = sp.getSpanEnd(TextKeyListener.ACTIVE);
+
+                if (i >= st && i < en) {
+                    return mSource.charAt(i);
+                }
+
+                Visible[] visible = sp.getSpans(0, sp.length(), Visible.class);
+
+                for (int a = 0; a < visible.length; a++) {
+                    if (sp.getSpanStart(visible[a].mTransformer) >= 0) {
+                        st = sp.getSpanStart(visible[a]);
+                        en = sp.getSpanEnd(visible[a]);
+
+                        if (i >= st && i < en) {
+                            return mSource.charAt(i);
+                        }
+                    }
+                }
+            }
+
+            return DOT;
+        }
+
+        public CharSequence subSequence(int start, int end) {
+            char[] buf = new char[end - start];
+
+            getChars(start, end, buf, 0);
+            return new String(buf);
+        }
+
+        public String toString() {
+            return subSequence(0, length()).toString();
+        }
+
+        public void getChars(int start, int end, char[] dest, int off) {
+            TextUtils.getChars(mSource, start, end, dest, off);
+
+            int st = -1, en = -1;
+            int nvisible = 0;
+            int[] starts = null, ends = null;
+
+            if (mSource instanceof Spanned) {
+                Spanned sp = (Spanned) mSource;
+
+                st = sp.getSpanStart(TextKeyListener.ACTIVE);
+                en = sp.getSpanEnd(TextKeyListener.ACTIVE);
+
+                Visible[] visible = sp.getSpans(0, sp.length(), Visible.class);
+                nvisible = visible.length;
+                starts = new int[nvisible];
+                ends = new int[nvisible];
+
+                for (int i = 0; i < nvisible; i++) {
+                    if (sp.getSpanStart(visible[i].mTransformer) >= 0) {
+                        starts[i] = sp.getSpanStart(visible[i]);
+                        ends[i] = sp.getSpanEnd(visible[i]);
+                    }
+                }
+            }
+
+            for (int i = start; i < end; i++) {
+                if (! (i >= st && i < en)) {
+                    boolean visible = false;
+
+                    for (int a = 0; a < nvisible; a++) {
+                        if (i >= starts[a] && i < ends[a]) {
+                            visible = true;
+                            break;
+                        }
+                    }
+
+                    if (!visible) {
+                        dest[i - start + off] = DOT;
+                    }
+                }
+            }
+        }
+
+        private CharSequence mSource;
+    }
+
+    private static class Visible
+    extends Handler
+    implements UpdateLayout, Runnable
+    {
+        public Visible(Spannable sp, PasswordTransformationMethod ptm) {
+            mText = sp;
+            mTransformer = ptm;
+            postAtTime(this, SystemClock.uptimeMillis() + 1500);
+        }
+
+        public void run() {
+            mText.removeSpan(this);
+        }
+
+        private Spannable mText;
+        private PasswordTransformationMethod mTransformer;
+    }
+
+    /**
+     * Used to stash a reference back to the View in the Editable so we
+     * can use it to check the settings.
+     */
+    private static class ViewReference extends WeakReference<View>
+            implements NoCopySpan {
+        public ViewReference(View v) {
+            super(v);
+        }
+    }
+
+    private static PasswordTransformationMethod sInstance;
+    private static char DOT = '\u2022';
+}
diff --git a/core/java/android/text/method/QwertyKeyListener.java b/core/java/android/text/method/QwertyKeyListener.java
new file mode 100644
index 0000000..0b39517
--- /dev/null
+++ b/core/java/android/text/method/QwertyKeyListener.java
@@ -0,0 +1,451 @@
+/*
+ * 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 android.text.method;
+
+import android.text.*;
+import android.text.method.TextKeyListener.Capitalize;
+import android.util.SparseArray;
+import android.view.KeyCharacterMap;
+import android.view.KeyEvent;
+import android.view.View;
+
+/**
+ * This is the standard key listener for alphabetic input on qwerty
+ * keyboards.  You should generally not need to instantiate this yourself;
+ * TextKeyListener will do it for you.
+ */
+public class QwertyKeyListener extends BaseKeyListener {
+    private static QwertyKeyListener[] sInstance =
+        new QwertyKeyListener[Capitalize.values().length * 2];
+
+    public QwertyKeyListener(Capitalize cap, boolean autotext) {
+        mAutoCap = cap;
+        mAutoText = autotext;
+    }
+
+    /**
+     * Returns a new or existing instance with the specified capitalization
+     * and correction properties.
+     */
+    public static QwertyKeyListener getInstance(boolean autotext,
+                                              Capitalize cap) {
+        int off = cap.ordinal() * 2 + (autotext ? 1 : 0);
+
+        if (sInstance[off] == null) {
+            sInstance[off] = new QwertyKeyListener(cap, autotext);
+        }
+
+        return sInstance[off];
+    }
+
+    public int getInputType() {
+        return makeTextContentType(mAutoCap, mAutoText);
+    }
+    
+    public boolean onKeyDown(View view, Editable content,
+                             int keyCode, KeyEvent event) {
+        int selStart, selEnd;
+        int pref = 0;
+
+        if (view != null) {
+            pref = TextKeyListener.getInstance().getPrefs(view.getContext());
+        }
+
+        {
+            int a = Selection.getSelectionStart(content);
+            int b = Selection.getSelectionEnd(content);
+
+            selStart = Math.min(a, b);
+            selEnd = Math.max(a, b);
+
+            if (selStart < 0 || selEnd < 0) {
+                selStart = selEnd = 0;
+                Selection.setSelection(content, 0, 0);
+            }
+        }
+
+        int activeStart = content.getSpanStart(TextKeyListener.ACTIVE);
+        int activeEnd = content.getSpanEnd(TextKeyListener.ACTIVE);
+
+        // QWERTY keyboard normal case
+
+        int i = event.getUnicodeChar(getMetaState(content));
+
+        int count = event.getRepeatCount();
+        if (count > 0 && selStart == selEnd && selStart > 0) {
+            char c = content.charAt(selStart - 1);
+
+            if (c == i || c == Character.toUpperCase(i) && view != null) {
+                if (showCharacterPicker(view, content, c, false, count)) {
+                    resetMetaState(content);
+                    return true;
+                }
+            }
+        }
+
+        if (i == KeyCharacterMap.PICKER_DIALOG_INPUT) {
+            if (view != null) {
+                showCharacterPicker(view, content,
+                                    KeyCharacterMap.PICKER_DIALOG_INPUT, true, 1);
+            }
+            resetMetaState(content);
+            return true;
+        }
+
+        if (i == KeyCharacterMap.HEX_INPUT) {
+            int start;
+
+            if (selStart == selEnd) {
+                start = selEnd;
+
+                while (start > 0 && selEnd - start < 4 &&
+                       Character.digit(content.charAt(start - 1), 16) >= 0) {
+                    start--;
+                }
+            } else {
+                start = selStart;
+            }
+
+            int ch = -1;
+            try {
+                String hex = TextUtils.substring(content, start, selEnd);
+                ch = Integer.parseInt(hex, 16);
+            } catch (NumberFormatException nfe) { }
+
+            if (ch >= 0) {
+                selStart = start;
+                Selection.setSelection(content, selStart, selEnd);
+                i = ch;
+            } else {
+                i = 0;
+            }
+        }
+
+        if (i != 0) {
+            boolean dead = false;
+
+            if ((i & KeyCharacterMap.COMBINING_ACCENT) != 0) {
+                dead = true;
+                i = i & KeyCharacterMap.COMBINING_ACCENT_MASK;
+            }
+
+            if (activeStart == selStart && activeEnd == selEnd) {
+                boolean replace = false;
+
+                if (selEnd - selStart - 1 == 0) {
+                    char accent = content.charAt(selStart);
+                    int composed = event.getDeadChar(accent, i);
+
+                    if (composed != 0) {
+                        i = composed;
+                        replace = true;
+                    }
+                }
+
+                if (!replace) {
+                    Selection.setSelection(content, selEnd);
+                    content.removeSpan(TextKeyListener.ACTIVE);
+                    selStart = selEnd;
+                }
+            }
+
+            if ((pref & TextKeyListener.AUTO_CAP) != 0 &&
+                Character.isLowerCase(i) && 
+                TextKeyListener.shouldCap(mAutoCap, content, selStart)) {
+                int where = content.getSpanEnd(TextKeyListener.CAPPED);
+                int flags = content.getSpanFlags(TextKeyListener.CAPPED);
+
+                if (where == selStart && (((flags >> 16) & 0xFFFF) == i)) {
+                    content.removeSpan(TextKeyListener.CAPPED);
+                } else {
+                    flags = i << 16;
+                    i = Character.toUpperCase(i);
+
+                    if (selStart == 0)
+                        content.setSpan(TextKeyListener.CAPPED, 0, 0,
+                                        Spannable.SPAN_MARK_MARK | flags);
+                    else
+                        content.setSpan(TextKeyListener.CAPPED,
+                                        selStart - 1, selStart,
+                                        Spannable.SPAN_EXCLUSIVE_EXCLUSIVE |
+                                        flags);
+                }
+            }
+
+            if (selStart != selEnd) {
+                Selection.setSelection(content, selEnd);
+            }
+            content.setSpan(OLD_SEL_START, selStart, selStart,
+                            Spannable.SPAN_MARK_MARK);
+
+            content.replace(selStart, selEnd, String.valueOf((char) i));
+
+            int oldStart = content.getSpanStart(OLD_SEL_START);
+            selEnd = Selection.getSelectionEnd(content);
+
+            if (oldStart < selEnd) {
+                content.setSpan(TextKeyListener.LAST_TYPED,
+                                oldStart, selEnd,
+                                Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+
+                if (dead) {
+                    Selection.setSelection(content, oldStart, selEnd);
+                    content.setSpan(TextKeyListener.ACTIVE, oldStart, selEnd,
+                        Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+                }
+            }
+
+            adjustMetaAfterKeypress(content);
+
+            // potentially do autotext replacement if the character
+            // that was typed was an autotext terminator
+
+            if ((pref & TextKeyListener.AUTO_TEXT) != 0 && mAutoText &&
+                (i == ' ' || i == '\t' || i == '\n' ||
+                 i == ',' || i == '.' || i == '!' || i == '?' ||
+                 i == '"' || Character.getType(i) == Character.END_PUNCTUATION) &&
+                 content.getSpanEnd(TextKeyListener.INHIBIT_REPLACEMENT)
+                     != oldStart) {
+                int x;
+
+                for (x = oldStart; x > 0; x--) {
+                    char c = content.charAt(x - 1);
+                    if (c != '\'' && !Character.isLetter(c)) {
+                        break;
+                    }
+                }
+
+                String rep = getReplacement(content, x, oldStart, view);
+
+                if (rep != null) {
+                    Replaced[] repl = content.getSpans(0, content.length(),
+                                                     Replaced.class);
+                    for (int a = 0; a < repl.length; a++)
+                        content.removeSpan(repl[a]);
+
+                    char[] orig = new char[oldStart - x];
+                    TextUtils.getChars(content, x, oldStart, orig, 0);
+
+                    content.setSpan(new Replaced(orig), x, oldStart,
+                                    Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+                    content.replace(x, oldStart, rep);
+                }
+            }
+
+            // Replace two spaces by a period and a space.
+
+            if ((pref & TextKeyListener.AUTO_PERIOD) != 0 && mAutoText) {
+                selEnd = Selection.getSelectionEnd(content);
+                if (selEnd - 3 >= 0) {
+                    if (content.charAt(selEnd - 1) == ' ' &&
+                        content.charAt(selEnd - 2) == ' ') {
+                        char c = content.charAt(selEnd - 3);
+
+                        for (int j = selEnd - 3; j > 0; j--) {
+                            if (c == '"' ||
+                                Character.getType(c) == Character.END_PUNCTUATION) {
+                                c = content.charAt(j - 1);
+                            } else {
+                                break;
+                            }
+                        }
+
+                        if (Character.isLetter(c) || Character.isDigit(c)) {
+                            content.replace(selEnd - 2, selEnd - 1, ".");
+                        }
+                    }
+                }
+            }
+
+            return true;
+        } else if (keyCode == KeyEvent.KEYCODE_DEL && selStart == selEnd) {
+            // special backspace case for undoing autotext
+
+            int consider = 1;
+
+            // if backspacing over the last typed character,
+            // it undoes the autotext prior to that character
+            // (unless the character typed was newline, in which
+            // case this behavior would be confusing)
+
+            if (content.getSpanEnd(TextKeyListener.LAST_TYPED) == selStart) {
+                if (content.charAt(selStart - 1) != '\n')
+                    consider = 2;
+            }
+
+            Replaced[] repl = content.getSpans(selStart - consider, selStart,
+                                             Replaced.class);
+
+            if (repl.length > 0) {
+                int st = content.getSpanStart(repl[0]);
+                int en = content.getSpanEnd(repl[0]);
+                String old = new String(repl[0].mText);
+
+                content.removeSpan(repl[0]);
+                content.setSpan(TextKeyListener.INHIBIT_REPLACEMENT,
+                                en, en, Spannable.SPAN_POINT_POINT);
+                content.replace(st, en, old);
+
+                en = content.getSpanStart(TextKeyListener.INHIBIT_REPLACEMENT);
+                if (en - 1 >= 0) {
+                    content.setSpan(TextKeyListener.INHIBIT_REPLACEMENT,
+                                    en - 1, en,
+                                    Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+                } else {
+                    content.removeSpan(TextKeyListener.INHIBIT_REPLACEMENT);
+                }
+
+                adjustMetaAfterKeypress(content);
+
+                return true;
+            }
+        }
+
+        return super.onKeyDown(view, content, keyCode, event);
+    }
+
+    private String getReplacement(CharSequence src, int start, int end,
+                                  View view) {
+        int len = end - start;
+        boolean changecase = false;
+        
+        String replacement = AutoText.get(src, start, end, view);
+        
+        if (replacement == null) {
+            String key = TextUtils.substring(src, start, end).toLowerCase();
+            replacement = AutoText.get(key, 0, end - start, view);
+            changecase = true;
+
+            if (replacement == null)
+                return null;
+        }
+        
+        int caps = 0;
+
+        if (changecase) {
+            for (int j = start; j < end; j++) {
+                if (Character.isUpperCase(src.charAt(j)))
+                    caps++;
+            }
+        }
+
+        String out;
+
+        if (caps == 0)
+            out = replacement;
+        else if (caps == 1)
+            out = toTitleCase(replacement);
+        else if (caps == len)
+            out = replacement.toUpperCase();
+        else
+            out = toTitleCase(replacement);
+
+        if (out.length() == len &&
+            TextUtils.regionMatches(src, start, out, 0, len))
+            return null;
+
+        return out;
+    }
+
+    /**
+     * Marks the specified region of <code>content</code> as having
+     * contained <code>original</code> prior to AutoText replacement.
+     * Call this method when you have done or are about to do an
+     * AutoText-style replacement on a region of text and want to let
+     * the same mechanism (the user pressing DEL immediately after the
+     * change) undo the replacement.
+     *
+     * @param content the Editable text where the replacement was made
+     * @param start the start of the replaced region
+     * @param end the end of the replaced region; the location of the cursor
+     * @param original the text to be restored if the user presses DEL
+     */
+    public static void markAsReplaced(Spannable content, int start, int end,
+                                      String original) {
+        Replaced[] repl = content.getSpans(0, content.length(), Replaced.class);
+        for (int a = 0; a < repl.length; a++) {
+            content.removeSpan(repl[a]);
+        }
+
+        int len = original.length();
+        char[] orig = new char[len];
+        original.getChars(0, len, orig, 0);
+
+        content.setSpan(new Replaced(orig), start, end,
+                        Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+    }
+
+    private static SparseArray<String> PICKER_SETS =
+                        new SparseArray<String>();
+    static {
+        PICKER_SETS.put('!', "\u00A1");
+        PICKER_SETS.put('<', "\u00AB");
+        PICKER_SETS.put('>', "\u00BB");
+        PICKER_SETS.put('?', "\u00BF");
+        PICKER_SETS.put('A', "\u00C0\u00C1\u00C2\u00C4\u00C6\u00C3\u00C5");
+        PICKER_SETS.put('C', "\u00C7");
+        PICKER_SETS.put('E', "\u00C8\u00C9\u00CA\u00CB");
+        PICKER_SETS.put('I', "\u00CC\u00CD\u00CE\u00CF");
+        PICKER_SETS.put('N', "\u00D1");
+        PICKER_SETS.put('O', "\u00D8\u0152\u00D5\u00D2\u00D3\u00D4\u00D6");
+        PICKER_SETS.put('U', "\u00D9\u00DA\u00DB\u00DC");
+        PICKER_SETS.put('Y', "\u00DD\u0178");
+        PICKER_SETS.put('a', "\u00E0\u00E1\u00E2\u00E4\u00E6\u00E3\u00E5");
+        PICKER_SETS.put('c', "\u00E7");
+        PICKER_SETS.put('e', "\u00E8\u00E9\u00EA\u00EB");
+        PICKER_SETS.put('i', "\u00EC\u00ED\u00EE\u00EF");
+        PICKER_SETS.put('n', "\u00F1");
+        PICKER_SETS.put('o', "\u00F8\u0153\u00F5\u00F2\u00F3\u00F4\u00F6");
+        PICKER_SETS.put('s', "\u00A7\u00DF");
+        PICKER_SETS.put('u', "\u00F9\u00FA\u00FB\u00FC");
+        PICKER_SETS.put('y', "\u00FD\u00FF");
+        PICKER_SETS.put(KeyCharacterMap.PICKER_DIALOG_INPUT,
+                             "\u2026\u00A5\u2022\u00AE\u00A9\u00B1");
+    };
+
+    private boolean showCharacterPicker(View view, Editable content, char c,
+                                        boolean insert, int count) {
+        String set = PICKER_SETS.get(c);
+        if (set == null) {
+            return false;
+        }
+
+        if (count == 1) {
+            new CharacterPickerDialog(view.getContext(),
+                                      view, content, set, insert).show();
+        }
+
+        return true;
+    }
+
+    private static String toTitleCase(String src) {
+        return Character.toUpperCase(src.charAt(0)) + src.substring(1);
+    }
+
+    /* package */ static class Replaced implements NoCopySpan
+    {
+        public Replaced(char[] text) {
+            mText = text;
+        }
+
+        private char[] mText;
+    }
+
+    private Capitalize mAutoCap;
+    private boolean mAutoText;
+}
+
diff --git a/core/java/android/text/method/ReplacementTransformationMethod.java b/core/java/android/text/method/ReplacementTransformationMethod.java
new file mode 100644
index 0000000..d6f879a
--- /dev/null
+++ b/core/java/android/text/method/ReplacementTransformationMethod.java
@@ -0,0 +1,205 @@
+/*
+ * 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 android.text.method;
+
+import android.graphics.Rect;
+import android.text.Editable;
+import android.text.GetChars;
+import android.text.Spannable;
+import android.text.Spanned;
+import android.text.SpannedString;
+import android.text.TextUtils;
+import android.view.View; 
+
+/**
+ * This transformation method causes the characters in the {@link #getOriginal}
+ * array to be replaced by the corresponding characters in the
+ * {@link #getReplacement} array.
+ */
+public abstract class ReplacementTransformationMethod
+implements TransformationMethod
+{
+    /**
+     * Returns the list of characters that are to be replaced by other
+     * characters when displayed.
+     */
+    protected abstract char[] getOriginal();
+    /**
+     * Returns a parallel array of replacement characters for the ones
+     * that are to be replaced.
+     */
+    protected abstract char[] getReplacement();
+
+    /**
+     * Returns a CharSequence that will mirror the contents of the
+     * source CharSequence but with the characters in {@link #getOriginal}
+     * replaced by ones from {@link #getReplacement}.
+     */
+    public CharSequence getTransformation(CharSequence source, View v) {
+        char[] original = getOriginal();
+        char[] replacement = getReplacement();
+
+        /*
+         * Short circuit for faster display if the text will never change.
+         */
+        if (!(source instanceof Editable)) {
+            /*
+             * Check whether the text does not contain any of the
+             * source characters so can be used unchanged.
+             */
+            boolean doNothing = true;
+            int n = original.length;
+            for (int i = 0; i < n; i++) {
+                if (TextUtils.indexOf(source, original[i]) >= 0) {
+                    doNothing = false;
+                    break;
+                }
+            }
+            if (doNothing) {
+                return source;
+            }
+
+            if (!(source instanceof Spannable)) {
+                /*
+                 * The text contains some of the source characters,
+                 * but they can be flattened out now instead of
+                 * at display time.
+                 */
+                if (source instanceof Spanned) {
+                    return new SpannedString(new SpannedReplacementCharSequence(
+                                                        (Spanned) source,
+                                                        original, replacement));
+                } else {
+                    return new ReplacementCharSequence(source,
+                                                       original,
+                                                       replacement).toString();
+                }
+            }
+        }
+
+        if (source instanceof Spanned) {
+            return new SpannedReplacementCharSequence((Spanned) source,
+                                                      original, replacement);
+        } else {
+            return new ReplacementCharSequence(source, original, replacement);
+        }
+    }
+
+    public void onFocusChanged(View view, CharSequence sourceText,
+                               boolean focused, int direction,
+                               Rect previouslyFocusedRect) {
+        // This callback isn't used.
+    }
+
+    private static class ReplacementCharSequence
+    implements CharSequence, GetChars {
+        private char[] mOriginal, mReplacement;
+
+        public ReplacementCharSequence(CharSequence source, char[] original,
+                                       char[] replacement) {
+            mSource = source;
+            mOriginal = original;
+            mReplacement = replacement;
+        }
+
+        public int length() {
+            return mSource.length();
+        }
+
+        public char charAt(int i) {
+            char c = mSource.charAt(i);
+
+            int n = mOriginal.length;
+            for (int j = 0; j < n; j++) {
+                if (c == mOriginal[j]) {
+                    c = mReplacement[j];
+                }
+            }
+
+            return c;
+        }
+
+        public CharSequence subSequence(int start, int end) {
+            char[] c = new char[end - start];
+
+            getChars(start, end, c, 0);
+            return new String(c);
+        }
+
+        public String toString() {
+            char[] c = new char[length()];
+
+            getChars(0, length(), c, 0);
+            return new String(c);
+        }
+
+        public void getChars(int start, int end, char[] dest, int off) {
+            TextUtils.getChars(mSource, start, end, dest, off);
+            int offend = end - start + off;
+            int n = mOriginal.length;
+
+            for (int i = off; i < offend; i++) {
+                char c = dest[i];
+
+                for (int j = 0; j < n; j++) {
+                    if (c == mOriginal[j]) {
+                        dest[i] = mReplacement[j];
+                    }
+                }
+            }
+        }
+
+        private CharSequence mSource;
+    }
+
+    private static class SpannedReplacementCharSequence
+    extends ReplacementCharSequence
+    implements Spanned
+    {
+        public SpannedReplacementCharSequence(Spanned source, char[] original,
+                                              char[] replacement) {
+            super(source, original, replacement);
+            mSpanned = source;
+        }
+
+        public CharSequence subSequence(int start, int end) {
+            return new SpannedString(this).subSequence(start, end);
+        }
+
+        public <T> T[] getSpans(int start, int end, Class<T> type) {
+            return mSpanned.getSpans(start, end, type);
+        }
+
+        public int getSpanStart(Object tag) {
+            return mSpanned.getSpanStart(tag);
+        }
+
+        public int getSpanEnd(Object tag) {
+            return mSpanned.getSpanEnd(tag);
+        }
+
+        public int getSpanFlags(Object tag) {
+            return mSpanned.getSpanFlags(tag);
+        }
+
+        public int nextSpanTransition(int start, int end, Class type) {
+            return mSpanned.nextSpanTransition(start, end, type);
+        }
+
+        private Spanned mSpanned;
+    }
+}
diff --git a/core/java/android/text/method/ScrollingMovementMethod.java b/core/java/android/text/method/ScrollingMovementMethod.java
new file mode 100644
index 0000000..563ceed
--- /dev/null
+++ b/core/java/android/text/method/ScrollingMovementMethod.java
@@ -0,0 +1,240 @@
+/*
+ * 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 android.text.method;
+
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+import android.text.*;
+import android.widget.TextView;
+import android.view.View;
+
+public class
+ScrollingMovementMethod
+implements MovementMethod
+{
+    /**
+     * Scrolls the text to the left if possible.
+     */
+    protected boolean left(TextView widget, Spannable buffer) {
+        Layout layout = widget.getLayout();
+        
+        int scrolly = widget.getScrollY();
+        int scr = widget.getScrollX();
+        int em = Math.round(layout.getPaint().getFontSpacing());
+
+        int padding = widget.getTotalPaddingTop() +
+                      widget.getTotalPaddingBottom();
+        int top = layout.getLineForVertical(scrolly);
+        int bottom = layout.getLineForVertical(scrolly + widget.getHeight() -
+                                               padding);
+        int left = Integer.MAX_VALUE;
+
+        for (int i = top; i <= bottom; i++) {
+            left = (int) Math.min(left, layout.getLineLeft(i));
+        }
+
+        if (scr > left) {
+            int s = Math.max(scr - em, left);
+            widget.scrollTo(s, widget.getScrollY());
+            return true;
+        }
+
+        return false;
+    }
+
+    /**
+     * Scrolls the text to the right if possible.
+     */
+    protected boolean right(TextView widget, Spannable buffer) {
+        Layout layout = widget.getLayout();
+
+        int scrolly = widget.getScrollY();
+        int scr = widget.getScrollX();
+        int em = Math.round(layout.getPaint().getFontSpacing());
+
+        int padding = widget.getTotalPaddingTop() +
+                      widget.getTotalPaddingBottom();
+        int top = layout.getLineForVertical(scrolly);
+        int bottom = layout.getLineForVertical(scrolly + widget.getHeight() -
+                                               padding);
+        int right = 0;
+
+        for (int i = top; i <= bottom; i++) {
+            right = (int) Math.max(right, layout.getLineRight(i));
+        }
+
+        padding = widget.getTotalPaddingLeft() + widget.getTotalPaddingRight();
+        if (scr < right - (widget.getWidth() - padding)) {
+            int s = Math.min(scr + em, right - (widget.getWidth() - padding));
+            widget.scrollTo(s, widget.getScrollY());
+            return true;
+        }
+
+        return false;
+    }
+
+    /**
+     * Scrolls the text up if possible.
+     */
+    protected boolean up(TextView widget, Spannable buffer) {
+        Layout layout = widget.getLayout();
+
+        int areatop = widget.getScrollY();
+        int line = layout.getLineForVertical(areatop);
+        int linetop = layout.getLineTop(line);
+
+        // If the top line is partially visible, bring it all the way
+        // into view; otherwise, bring the previous line into view.
+        if (areatop == linetop)
+            line--;
+
+        if (line >= 0) {
+            Touch.scrollTo(widget, layout,
+                           widget.getScrollX(), layout.getLineTop(line));
+            return true;
+        }
+
+        return false;
+    }
+
+    /**
+     * Scrolls the text down if possible.
+     */
+    protected boolean down(TextView widget, Spannable buffer) {
+        Layout layout = widget.getLayout();
+
+        int padding = widget.getTotalPaddingTop() +
+                      widget.getTotalPaddingBottom();
+
+        int areabot = widget.getScrollY() + widget.getHeight() - padding;
+        int line = layout.getLineForVertical(areabot);
+
+        if (layout.getLineTop(line+1) < areabot + 1) {
+            // Less than a pixel of this line is out of view,
+            // so we must have tried to make it entirely in view
+            // and now want the next line to be in view instead.
+
+            line++;
+        }
+
+        if (line <= layout.getLineCount() - 1) {
+            widget.scrollTo(widget.getScrollX(), layout.getLineTop(line+1) -
+                            (widget.getHeight() - padding));
+            Touch.scrollTo(widget, layout,
+                                widget.getScrollX(), widget.getScrollY());
+            return true;
+        }
+
+        return false;
+    }
+
+    public boolean onKeyDown(TextView widget, Spannable buffer, int keyCode, KeyEvent event) {
+        return executeDown(widget, buffer, keyCode);
+    }
+
+    private boolean executeDown(TextView widget, Spannable buffer, int keyCode) {
+        boolean handled = false;
+
+        switch (keyCode) {
+        case KeyEvent.KEYCODE_DPAD_LEFT:
+            handled |= left(widget, buffer);
+            break;
+
+        case KeyEvent.KEYCODE_DPAD_RIGHT:
+            handled |= right(widget, buffer);
+            break;
+
+        case KeyEvent.KEYCODE_DPAD_UP:
+            handled |= up(widget, buffer);
+            break;
+
+        case KeyEvent.KEYCODE_DPAD_DOWN:
+            handled |= down(widget, buffer);
+            break;
+        }
+
+        return handled;
+    }
+
+    public boolean onKeyUp(TextView widget, Spannable buffer, int keyCode, KeyEvent event) {
+        return false;
+    }
+
+    public boolean onKeyOther(TextView view, Spannable text, KeyEvent event) {
+        int code = event.getKeyCode();
+        if (code != KeyEvent.KEYCODE_UNKNOWN
+                && event.getAction() == KeyEvent.ACTION_MULTIPLE) {
+            int repeat = event.getRepeatCount();
+            boolean first = true;
+            boolean handled = false;
+            while ((--repeat) > 0) {
+                if (first && executeDown(view, text, code)) {
+                    handled = true;
+                    MetaKeyKeyListener.adjustMetaAfterKeypress(text);
+                    MetaKeyKeyListener.resetLockedMeta(text);
+                }
+                first = false;
+            }
+            return handled;
+        }
+        return false;
+    }
+    
+    public boolean onTrackballEvent(TextView widget, Spannable text,
+            MotionEvent event) {
+        return false;
+    }
+    
+    public boolean onTouchEvent(TextView widget, Spannable buffer,
+                                 MotionEvent event) {
+        return Touch.onTouchEvent(widget, buffer, event);
+    }
+
+    public void initialize(TextView widget, Spannable text) { }
+
+    public boolean canSelectArbitrarily() {
+        return false;
+    }
+
+    public void onTakeFocus(TextView widget, Spannable text, int dir) {
+        Layout layout = widget.getLayout();
+
+        if (layout != null && (dir & View.FOCUS_FORWARD) != 0) {
+            widget.scrollTo(widget.getScrollX(),
+                            layout.getLineTop(0));
+        }
+        if (layout != null && (dir & View.FOCUS_BACKWARD) != 0) {
+            int padding = widget.getTotalPaddingTop() +
+                          widget.getTotalPaddingBottom();
+            int line = layout.getLineCount() - 1;
+
+            widget.scrollTo(widget.getScrollX(),
+                            layout.getLineTop(line+1) -
+                            (widget.getHeight() - padding));
+        }
+    }
+
+    public static MovementMethod getInstance() {
+        if (sInstance == null)
+            sInstance = new ScrollingMovementMethod();
+
+        return sInstance;
+    }
+
+    private static ScrollingMovementMethod sInstance;
+}
diff --git a/core/java/android/text/method/SingleLineTransformationMethod.java b/core/java/android/text/method/SingleLineTransformationMethod.java
new file mode 100644
index 0000000..6a05fe4
--- /dev/null
+++ b/core/java/android/text/method/SingleLineTransformationMethod.java
@@ -0,0 +1,62 @@
+/*
+ * 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 android.text.method;
+
+import android.graphics.Rect;
+import android.text.Editable;
+import android.text.GetChars;
+import android.text.Spannable;
+import android.text.Spanned;
+import android.text.SpannedString;
+import android.text.TextUtils;
+import android.view.View;
+
+/**
+ * This transformation method causes any newline characters (\n) to be
+ * displayed as spaces instead of causing line breaks, and causes
+ * carriage return characters (\r) to have no appearance.
+ */
+public class SingleLineTransformationMethod
+extends ReplacementTransformationMethod {
+    private static char[] ORIGINAL = new char[] { '\n', '\r' };
+    private static char[] REPLACEMENT = new char[] { ' ', '\uFEFF' };
+
+    /**
+     * The characters to be replaced are \n and \r.
+     */
+    protected char[] getOriginal() {
+        return ORIGINAL;
+    }
+
+    /**
+     * The character \n is replaced with is space;
+     * the character \r is replaced with is FEFF (zero width space).
+     */
+    protected char[] getReplacement() {
+        return REPLACEMENT;
+    }
+
+    public static SingleLineTransformationMethod getInstance() {
+        if (sInstance != null)
+            return sInstance;
+
+        sInstance = new SingleLineTransformationMethod();
+        return sInstance;
+    }
+
+    private static SingleLineTransformationMethod sInstance;
+}
diff --git a/core/java/android/text/method/TextKeyListener.java b/core/java/android/text/method/TextKeyListener.java
new file mode 100644
index 0000000..5be2a48
--- /dev/null
+++ b/core/java/android/text/method/TextKeyListener.java
@@ -0,0 +1,297 @@
+/*
+ * Copyright (C) 2007 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 android.text.method;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.database.ContentObserver;
+import android.os.Handler;
+import android.provider.Settings;
+import android.provider.Settings.System;
+import android.text.*;
+import android.view.KeyCharacterMap;
+import android.view.KeyEvent;
+import android.view.View;
+import android.text.InputType;
+
+import java.lang.ref.WeakReference;
+
+/**
+ * This is the key listener for typing normal text.  It delegates to
+ * other key listeners appropriate to the current keyboard and language.
+ */
+public class TextKeyListener extends BaseKeyListener implements SpanWatcher {
+    private static TextKeyListener[] sInstance =
+        new TextKeyListener[Capitalize.values().length * 2];
+
+    /* package */ static final Object ACTIVE = new NoCopySpan.Concrete();
+    /* package */ static final Object CAPPED = new NoCopySpan.Concrete();
+    /* package */ static final Object INHIBIT_REPLACEMENT = new NoCopySpan.Concrete();
+    /* package */ static final Object LAST_TYPED = new NoCopySpan.Concrete();
+
+    private Capitalize mAutoCap;
+    private boolean mAutoText;
+
+    private int mPrefs;
+    private boolean mPrefsInited;
+
+    /* package */ static final int AUTO_CAP = 1;
+    /* package */ static final int AUTO_TEXT = 2;
+    /* package */ static final int AUTO_PERIOD = 4;
+    /* package */ static final int SHOW_PASSWORD = 8;
+    private WeakReference<ContentResolver> mResolver;
+    private TextKeyListener.SettingsObserver mObserver;
+
+    /**
+     * Creates a new TextKeyListener with the specified capitalization
+     * and correction properties.
+     *
+     * @param cap when, if ever, to automatically capitalize.
+     * @param autotext whether to automatically do spelling corrections.
+     */
+    public TextKeyListener(Capitalize cap, boolean autotext) {
+        mAutoCap = cap;
+        mAutoText = autotext;
+    }
+
+    /**
+     * Returns a new or existing instance with the specified capitalization
+     * and correction properties.
+     *
+     * @param cap when, if ever, to automatically capitalize.
+     * @param autotext whether to automatically do spelling corrections.
+     */
+    public static TextKeyListener getInstance(boolean autotext,
+                                              Capitalize cap) {
+        int off = cap.ordinal() * 2 + (autotext ? 1 : 0);
+
+        if (sInstance[off] == null) {
+            sInstance[off] = new TextKeyListener(cap, autotext);
+        }
+
+        return sInstance[off];
+    }
+
+    /**
+     * Returns a new or existing instance with no automatic capitalization
+     * or correction.
+     */
+    public static TextKeyListener getInstance() {
+        return getInstance(false, Capitalize.NONE);
+    }
+
+    /**
+     * Returns whether it makes sense to automatically capitalize at the
+     * specified position in the specified text, with the specified rules.
+     *
+     * @param cap the capitalization rules to consider.
+     * @param cs the text in which an insertion is being made.
+     * @param off the offset into that text where the insertion is being made.
+     *
+     * @return whether the character being inserted should be capitalized.
+     */
+    public static boolean shouldCap(Capitalize cap, CharSequence cs, int off) {
+        int i;
+        char c;
+
+        if (cap == Capitalize.NONE) {
+            return false;
+        }
+        if (cap == Capitalize.CHARACTERS) {
+            return true;
+        }
+
+        return TextUtils.getCapsMode(cs, off, cap == Capitalize.WORDS
+                ? TextUtils.CAP_MODE_WORDS : TextUtils.CAP_MODE_SENTENCES)
+                != 0;
+    }
+
+    public int getInputType() {
+        return makeTextContentType(mAutoCap, mAutoText);
+    }
+    
+    @Override
+    public boolean onKeyDown(View view, Editable content,
+                             int keyCode, KeyEvent event) {
+        KeyListener im = getKeyListener(event);
+
+        return im.onKeyDown(view, content, keyCode, event);
+    }
+
+    @Override
+    public boolean onKeyUp(View view, Editable content,
+                           int keyCode, KeyEvent event) {
+        KeyListener im = getKeyListener(event);
+
+        return im.onKeyUp(view, content, keyCode, event);
+    }
+
+    @Override
+    public boolean onKeyOther(View view, Editable content, KeyEvent event) {
+        KeyListener im = getKeyListener(event);
+
+        return im.onKeyOther(view, content, event);
+    }
+
+    /**
+     * Clear all the input state (autotext, autocap, multitap, undo)
+     * from the specified Editable, going beyond Editable.clear(), which
+     * just clears the text but not the input state.
+     *
+     * @param e the buffer whose text and state are to be cleared.
+     */
+    public static void clear(Editable e) {
+        e.clear();
+        e.removeSpan(ACTIVE);
+        e.removeSpan(CAPPED);
+        e.removeSpan(INHIBIT_REPLACEMENT);
+        e.removeSpan(LAST_TYPED);
+
+        QwertyKeyListener.Replaced[] repl = e.getSpans(0, e.length(),
+                                   QwertyKeyListener.Replaced.class);
+        final int count = repl.length;
+        for (int i = 0; i < count; i++) {
+            e.removeSpan(repl[i]);
+        }
+    }
+
+    public void onSpanAdded(Spannable s, Object what, int start, int end) { }
+    public void onSpanRemoved(Spannable s, Object what, int start, int end) { }
+
+    public void onSpanChanged(Spannable s, Object what, int start, int end,
+                              int st, int en) {
+        if (what == Selection.SELECTION_END) {
+            s.removeSpan(ACTIVE);
+        }
+    }
+
+    private KeyListener getKeyListener(KeyEvent event) {
+        KeyCharacterMap kmap = KeyCharacterMap.load(event.getKeyboardDevice());
+        int kind = kmap.getKeyboardType();
+
+        if (kind == KeyCharacterMap.ALPHA) {
+            return QwertyKeyListener.getInstance(mAutoText, mAutoCap);
+        } else if (kind == KeyCharacterMap.NUMERIC) {
+            return MultiTapKeyListener.getInstance(mAutoText, mAutoCap);
+        }
+
+        return NullKeyListener.getInstance();
+    }
+
+    public enum Capitalize {
+        NONE, SENTENCES, WORDS, CHARACTERS,
+    }
+
+    private static class NullKeyListener implements KeyListener
+    {
+        public int getInputType() {
+            return InputType.TYPE_NULL;
+        }
+        
+        public boolean onKeyDown(View view, Editable content,
+                                 int keyCode, KeyEvent event) {
+            return false;
+        }
+
+        public boolean onKeyUp(View view, Editable content, int keyCode,
+                                        KeyEvent event) {
+            return false;
+        }
+
+        public boolean onKeyOther(View view, Editable content, KeyEvent event) {
+            return false;
+        }
+
+        public void clearMetaKeyState(View view, Editable content, int states) {
+        }
+        
+        public static NullKeyListener getInstance() {
+            if (sInstance != null)
+                return sInstance;
+
+            sInstance = new NullKeyListener();
+            return sInstance;
+        }
+
+        private static NullKeyListener sInstance;
+    }
+
+    public void release() {
+        if (mResolver != null) {
+            final ContentResolver contentResolver = mResolver.get();
+            if (contentResolver != null) {
+                contentResolver.unregisterContentObserver(mObserver);
+                mResolver.clear();
+            }
+            mObserver = null;
+            mResolver = null;
+            mPrefsInited = false;
+        }
+    }
+
+    private void initPrefs(Context context) {
+        final ContentResolver contentResolver = context.getContentResolver();
+        mResolver = new WeakReference<ContentResolver>(contentResolver);
+        mObserver = new SettingsObserver();
+        contentResolver.registerContentObserver(Settings.System.CONTENT_URI, true, mObserver);
+
+        updatePrefs(contentResolver);
+        mPrefsInited = true;
+    }
+
+    private class SettingsObserver extends ContentObserver {
+        public SettingsObserver() {
+            super(new Handler());
+        }
+
+        @Override
+        public void onChange(boolean selfChange) {
+            if (mResolver != null) {
+                final ContentResolver contentResolver = mResolver.get();
+                if (contentResolver == null) {
+                    mPrefsInited = false;
+                } else {
+                    updatePrefs(contentResolver);
+                }
+            } else {
+                mPrefsInited = false;
+            }
+        }
+    }
+
+    private void updatePrefs(ContentResolver resolver) {
+        boolean cap = System.getInt(resolver, System.TEXT_AUTO_CAPS, 1) > 0;
+        boolean text = System.getInt(resolver, System.TEXT_AUTO_REPLACE, 1) > 0;
+        boolean period = System.getInt(resolver, System.TEXT_AUTO_PUNCTUATE, 1) > 0;
+        boolean pw = System.getInt(resolver, System.TEXT_SHOW_PASSWORD, 1) > 0;
+
+        mPrefs = (cap ? AUTO_CAP : 0) |
+                 (text ? AUTO_TEXT : 0) |
+                 (period ? AUTO_PERIOD : 0) |
+                 (pw ? SHOW_PASSWORD : 0);
+    }
+
+    /* package */ int getPrefs(Context context) {
+        synchronized (this) {
+            if (!mPrefsInited || mResolver.get() == null) {
+                initPrefs(context);
+            }
+        }
+
+        return mPrefs;
+    }
+}
diff --git a/core/java/android/text/method/TimeKeyListener.java b/core/java/android/text/method/TimeKeyListener.java
new file mode 100644
index 0000000..3fbfd8c
--- /dev/null
+++ b/core/java/android/text/method/TimeKeyListener.java
@@ -0,0 +1,58 @@
+/*
+ * 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 android.text.method;
+
+import android.view.KeyEvent;
+import android.text.InputType;
+
+/**
+ * For entering times in a text field.
+ */
+public class TimeKeyListener extends NumberKeyListener
+{
+    public int getInputType() {
+        return InputType.TYPE_CLASS_DATETIME
+        | InputType.TYPE_DATETIME_VARIATION_TIME;
+    }
+    
+    @Override
+    protected char[] getAcceptedChars()
+    {
+        return CHARACTERS;
+    }
+
+    public static TimeKeyListener getInstance() {
+        if (sInstance != null)
+            return sInstance;
+
+        sInstance = new TimeKeyListener();
+        return sInstance;
+    }
+
+    /**
+     * The characters that are used.
+     *
+     * @see KeyEvent#getMatch
+     * @see #getAcceptedChars
+     */
+    public static final char[] CHARACTERS = new char[] {
+            '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'm',
+            'p', ':'
+        };
+
+    private static TimeKeyListener sInstance;
+}
diff --git a/core/java/android/text/method/Touch.java b/core/java/android/text/method/Touch.java
new file mode 100644
index 0000000..65036ad
--- /dev/null
+++ b/core/java/android/text/method/Touch.java
@@ -0,0 +1,156 @@
+/*
+ * 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 android.text.method;
+
+import android.text.Layout;
+import android.text.NoCopySpan;
+import android.text.Layout.Alignment;
+import android.text.Spannable;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewConfiguration;
+import android.widget.TextView;
+
+public class Touch {
+    private Touch() { }
+
+    /**
+     * Scrolls the specified widget to the specified coordinates, except
+     * constrains the X scrolling position to the horizontal regions of
+     * the text that will be visible after scrolling to the specified
+     * Y position.
+     */
+    public static void scrollTo(TextView widget, Layout layout, int x, int y) {
+        int padding = widget.getTotalPaddingTop() +
+                      widget.getTotalPaddingBottom();
+        int top = layout.getLineForVertical(y);
+        int bottom = layout.getLineForVertical(y + widget.getHeight() -
+                                               padding);
+
+        int left = Integer.MAX_VALUE;
+        int right = 0;
+        Alignment a = null;
+
+        for (int i = top; i <= bottom; i++) {
+            left = (int) Math.min(left, layout.getLineLeft(i));
+            right = (int) Math.max(right, layout.getLineRight(i));
+
+            if (a == null) {
+                a = layout.getParagraphAlignment(i);
+            }
+        }
+
+        padding = widget.getTotalPaddingLeft() + widget.getTotalPaddingRight();
+        int width = widget.getWidth();
+        int diff = 0;
+
+        if (right - left < width - padding) {
+            if (a == Alignment.ALIGN_CENTER) {
+                diff = (width - padding - (right - left)) / 2;
+            } else if (a == Alignment.ALIGN_OPPOSITE) {
+                diff = width - padding - (right - left);
+            }
+        }
+
+        x = Math.min(x, right - (width - padding) - diff);
+        x = Math.max(x, left - diff);
+
+        widget.scrollTo(x, y);
+    }
+
+    /**
+     * Handles touch events for dragging.  You may want to do other actions
+     * like moving the cursor on touch as well.
+     */
+    public static boolean onTouchEvent(TextView widget, Spannable buffer,
+                                       MotionEvent event) {
+        DragState[] ds;
+
+        switch (event.getAction()) {
+        case MotionEvent.ACTION_DOWN:
+            buffer.setSpan(new DragState(event.getX(), event.getY()),
+                           0, 0, Spannable.SPAN_MARK_MARK);
+            return true;
+
+        case MotionEvent.ACTION_UP:
+            ds = buffer.getSpans(0, buffer.length(), DragState.class);
+
+            for (int i = 0; i < ds.length; i++) {
+                buffer.removeSpan(ds[i]);
+            }
+
+            if (ds.length > 0 && ds[0].mUsed) {
+                return true;
+            } else {
+                return false;
+            }
+
+        case MotionEvent.ACTION_MOVE:
+            ds = buffer.getSpans(0, buffer.length(), DragState.class);
+
+            if (ds.length > 0) {
+                if (ds[0].mFarEnough == false) {
+                    int slop = ViewConfiguration.get(widget.getContext()).getScaledTouchSlop();
+
+                    if (Math.abs(event.getX() - ds[0].mX) >= slop ||
+                        Math.abs(event.getY() - ds[0].mY) >= slop) {
+                        ds[0].mFarEnough = true;
+                    }
+                }
+
+                if (ds[0].mFarEnough) {
+                    ds[0].mUsed = true;
+
+                    float dx = ds[0].mX - event.getX();
+                    float dy = ds[0].mY - event.getY();
+
+                    ds[0].mX = event.getX();
+                    ds[0].mY = event.getY();
+
+                    int nx = widget.getScrollX() + (int) dx;
+                    int ny = widget.getScrollY() + (int) dy;
+
+                    int padding = widget.getTotalPaddingTop() +
+                                  widget.getTotalPaddingBottom();
+                    Layout layout = widget.getLayout();
+
+                    ny = Math.min(ny, layout.getHeight() - (widget.getHeight() -
+                                                            padding));
+                    ny = Math.max(ny, 0);
+        
+                    scrollTo(widget, layout, nx, ny);
+                    widget.cancelLongPress();
+                    return true;
+                }
+            }
+        }
+
+        return false;
+    }
+
+    private static class DragState implements NoCopySpan {
+        public float mX;
+        public float mY;
+        public boolean mFarEnough;
+        public boolean mUsed;
+
+        public DragState(float x, float y) {
+            mX = x;
+            mY = y;
+        }
+    }
+}
diff --git a/core/java/android/text/method/TransformationMethod.java b/core/java/android/text/method/TransformationMethod.java
new file mode 100644
index 0000000..9f51c2a
--- /dev/null
+++ b/core/java/android/text/method/TransformationMethod.java
@@ -0,0 +1,46 @@
+/*
+ * 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 android.text.method;
+
+import android.graphics.Rect;
+import android.view.View;
+import android.widget.TextView;
+
+/**
+ * TextView uses TransformationMethods to do things like replacing the
+ * characters of passwords with dots, or keeping the newline characters
+ * from causing line breaks in single-line text fields.
+ */
+public interface TransformationMethod
+{
+    /**
+     * Returns a CharSequence that is a transformation of the source text --
+     * for example, replacing each character with a dot in a password field.
+     * Beware that the returned text must be exactly the same length as
+     * the source text, and that if the source text is Editable, the returned
+     * text must mirror it dynamically instead of doing a one-time copy.
+     */
+    public CharSequence getTransformation(CharSequence source, View view);
+
+    /**
+     * This method is called when the TextView that uses this
+     * TransformationMethod gains or loses focus.
+     */
+    public void onFocusChanged(View view, CharSequence sourceText,
+                               boolean focused, int direction,
+                               Rect previouslyFocusedRect);
+}
diff --git a/core/java/android/text/method/package.html b/core/java/android/text/method/package.html
new file mode 100644
index 0000000..93698b8
--- /dev/null
+++ b/core/java/android/text/method/package.html
@@ -0,0 +1,21 @@
+<html>
+<body>
+
+<p>Provides classes that monitor or modify keypad input.</p>
+<p>You can use these classes to modify the type of keypad entry
+for your application, or decipher the keypresses entered for your specific
+entry method. For example:</p>
+<pre>
+// Set the text to password display style:
+EditText txtView = (EditText)findViewById(R.id.text);
+txtView.setTransformationMethod(PasswordTransformationMethod.getInstance());
+
+//Set the input style to numbers, rather than qwerty keyboard style.
+txtView.setInputMethod(DigitsInputMethod.getInstance());
+
+// Find out whether the caps lock is on.
+// 0 is no, 1 is yes, 2 is caps lock on.
+int active = MultiTapInputMethod.getCapsActive(txtView.getText());
+</pre>
+</body>
+</html>
diff --git a/core/java/android/text/package.html b/core/java/android/text/package.html
new file mode 100644
index 0000000..162dcd8
--- /dev/null
+++ b/core/java/android/text/package.html
@@ -0,0 +1,13 @@
+<html>
+<body>
+
+<p>Provides classes used to render or track text and text spans on the screen.</p>
+<p>You can use these classes to design your own widgets that manage text, 
+to handle arbitrary text spans for changes, or to handle drawing yourself 
+for an existing widget.</p>
+<p>The Span&hellip; interfaces and classes are used to create or manage spans of 
+text in a View item. You can use these to style the text or background, or to
+listen for changes. If creating your own widget, extend DynamicLayout, to manages 
+the actual wrapping and drawing of your text.  
+</body>
+</html>
diff --git a/core/java/android/text/style/AbsoluteSizeSpan.java b/core/java/android/text/style/AbsoluteSizeSpan.java
new file mode 100644
index 0000000..484f8ce
--- /dev/null
+++ b/core/java/android/text/style/AbsoluteSizeSpan.java
@@ -0,0 +1,61 @@
+/*
+ * 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 android.text.style;
+
+import android.os.Parcel;
+import android.text.ParcelableSpan;
+import android.text.TextPaint;
+import android.text.TextUtils;
+
+public class AbsoluteSizeSpan extends MetricAffectingSpan implements ParcelableSpan {
+
+    private final int mSize;
+
+    public AbsoluteSizeSpan(int size) {
+        mSize = size;
+    }
+
+    public AbsoluteSizeSpan(Parcel src) {
+        mSize = src.readInt();
+    }
+    
+    public int getSpanTypeId() {
+        return TextUtils.ABSOLUTE_SIZE_SPAN;
+    }
+    
+    public int describeContents() {
+        return 0;
+    }
+
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeInt(mSize);
+    }
+
+    public int getSize() {
+        return mSize;
+    }
+
+    @Override
+    public void updateDrawState(TextPaint ds) {
+        ds.setTextSize(mSize);
+    }
+
+    @Override
+    public void updateMeasureState(TextPaint ds) {
+        ds.setTextSize(mSize);
+    }
+}
diff --git a/core/java/android/text/style/AlignmentSpan.java b/core/java/android/text/style/AlignmentSpan.java
new file mode 100644
index 0000000..b8a37da
--- /dev/null
+++ b/core/java/android/text/style/AlignmentSpan.java
@@ -0,0 +1,55 @@
+/*
+ * 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 android.text.style;
+
+import android.os.Parcel;
+import android.text.Layout;
+import android.text.ParcelableSpan;
+import android.text.TextUtils;
+
+public interface AlignmentSpan extends ParagraphStyle {
+    public Layout.Alignment getAlignment();
+
+    public static class Standard
+    implements AlignmentSpan, ParcelableSpan {
+        public Standard(Layout.Alignment align) {
+            mAlignment = align;
+        }
+
+        public Standard(Parcel src) {
+            mAlignment = Layout.Alignment.valueOf(src.readString());
+        }
+        
+        public int getSpanTypeId() {
+            return TextUtils.ALIGNMENT_SPAN;
+        }
+        
+        public int describeContents() {
+            return 0;
+        }
+
+        public void writeToParcel(Parcel dest, int flags) {
+            dest.writeString(mAlignment.name());
+        }
+
+        public Layout.Alignment getAlignment() {
+            return mAlignment;
+        }
+
+        private final Layout.Alignment mAlignment;
+    }
+}
diff --git a/core/java/android/text/style/BackgroundColorSpan.java b/core/java/android/text/style/BackgroundColorSpan.java
new file mode 100644
index 0000000..580a369
--- /dev/null
+++ b/core/java/android/text/style/BackgroundColorSpan.java
@@ -0,0 +1,57 @@
+/*
+ * 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 android.text.style;
+
+import android.os.Parcel;
+import android.text.ParcelableSpan;
+import android.text.TextPaint;
+import android.text.TextUtils;
+
+public class BackgroundColorSpan extends CharacterStyle
+        implements UpdateAppearance, ParcelableSpan {
+
+    private final int mColor;
+
+	public BackgroundColorSpan(int color) {
+		mColor = color;
+	}
+
+    public BackgroundColorSpan(Parcel src) {
+        mColor = src.readInt();
+    }
+    
+    public int getSpanTypeId() {
+        return TextUtils.BACKGROUND_COLOR_SPAN;
+    }
+    
+    public int describeContents() {
+        return 0;
+    }
+
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeInt(mColor);
+    }
+
+	public int getBackgroundColor() {
+		return mColor;
+	}
+
+	@Override
+	public void updateDrawState(TextPaint ds) {
+		ds.bgColor = mColor;
+	}
+}
diff --git a/core/java/android/text/style/BulletSpan.java b/core/java/android/text/style/BulletSpan.java
new file mode 100644
index 0000000..655bd81
--- /dev/null
+++ b/core/java/android/text/style/BulletSpan.java
@@ -0,0 +1,102 @@
+/*
+ * 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 android.text.style;
+
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.os.Parcel;
+import android.text.Layout;
+import android.text.ParcelableSpan;
+import android.text.Spanned;
+import android.text.TextUtils;
+
+public class BulletSpan implements LeadingMarginSpan, ParcelableSpan {
+    private final int mGapWidth;
+    private final boolean mWantColor;
+    private final int mColor;
+
+    private static final int BULLET_RADIUS = 3;
+    public static final int STANDARD_GAP_WIDTH = 2;
+
+    public BulletSpan() {
+        mGapWidth = STANDARD_GAP_WIDTH;
+        mWantColor = false;
+        mColor = 0;
+    }
+
+    public BulletSpan(int gapWidth) {
+        mGapWidth = gapWidth;
+        mWantColor = false;
+        mColor = 0;
+    }
+
+    public BulletSpan(int gapWidth, int color) {
+        mGapWidth = gapWidth;
+        mWantColor = true;
+        mColor = color;
+    }
+
+    public BulletSpan(Parcel src) {
+        mGapWidth = src.readInt();
+        mWantColor = src.readInt() != 0;
+        mColor = src.readInt();
+    }
+    
+    public int getSpanTypeId() {
+        return TextUtils.BULLET_SPAN;
+    }
+    
+    public int describeContents() {
+        return 0;
+    }
+
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeInt(mGapWidth);
+        dest.writeInt(mWantColor ? 1 : 0);
+        dest.writeInt(mColor);
+    }
+
+    public int getLeadingMargin(boolean first) {
+        return 2 * BULLET_RADIUS + mGapWidth;
+    }
+
+    public void drawLeadingMargin(Canvas c, Paint p, int x, int dir,
+                                  int top, int baseline, int bottom,
+                                  CharSequence text, int start, int end,
+                                  boolean first, Layout l) {
+        if (((Spanned) text).getSpanStart(this) == start) {
+            Paint.Style style = p.getStyle();
+            int oldcolor = 0;
+
+            if (mWantColor) {
+                oldcolor = p.getColor();
+                p.setColor(mColor);
+            }
+
+            p.setStyle(Paint.Style.FILL);
+
+            c.drawCircle(x + dir * BULLET_RADIUS, (top + bottom) / 2.0f,
+                         BULLET_RADIUS, p);
+
+            if (mWantColor) {
+                p.setColor(oldcolor);
+            }
+
+            p.setStyle(style);
+        }
+    }
+}
diff --git a/core/java/android/text/style/CharacterStyle.java b/core/java/android/text/style/CharacterStyle.java
new file mode 100644
index 0000000..14dfddd
--- /dev/null
+++ b/core/java/android/text/style/CharacterStyle.java
@@ -0,0 +1,87 @@
+/*
+ * 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 android.text.style;
+
+import android.text.TextPaint;
+
+/**
+ * The classes that affect character-level text formatting extend this
+ * class.  Most extend its subclass {@link MetricAffectingSpan}, but simple
+ * ones may just implement {@link UpdateAppearance}.
+ */
+public abstract class CharacterStyle {
+	public abstract void updateDrawState(TextPaint tp);
+
+    /**
+     * A given CharacterStyle can only applied to a single region of a given
+     * Spanned.  If you need to attach the same CharacterStyle to multiple
+     * regions, you can use this method to wrap it with a new object that
+     * will have the same effect but be a distinct object so that it can
+     * also be attached without conflict.
+     */
+    public static CharacterStyle wrap(CharacterStyle cs) {
+        if (cs instanceof MetricAffectingSpan) {
+            return new MetricAffectingSpan.Passthrough((MetricAffectingSpan) cs);
+        } else {
+            return new Passthrough(cs);
+        }
+    }
+
+    /**
+     * Returns "this" for most CharacterStyles, but for CharacterStyles
+     * that were generated by {@link #wrap}, returns the underlying
+     * CharacterStyle.
+     */
+    public CharacterStyle getUnderlying() {
+        return this;
+    }
+
+    /**
+     * A Passthrough CharacterStyle is one that
+     * passes {@link #updateDrawState} calls through to the
+     * specified CharacterStyle while still being a distinct object,
+     * and is therefore able to be attached to the same Spannable
+     * to which the specified CharacterStyle is already attached.
+     */
+    private static class Passthrough extends CharacterStyle {
+        private CharacterStyle mStyle;
+
+        /**
+         * Creates a new Passthrough of the specfied CharacterStyle.
+         */
+        public Passthrough(CharacterStyle cs) {
+            mStyle = cs;
+        }
+
+        /**
+         * Passes updateDrawState through to the underlying CharacterStyle.
+         */
+        @Override
+        public void updateDrawState(TextPaint tp) {
+            mStyle.updateDrawState(tp);
+        }
+
+        /**
+         * Returns the CharacterStyle underlying this one, or the one
+         * underlying it if it too is a Passthrough.
+         */
+        @Override
+        public CharacterStyle getUnderlying() {
+            return mStyle.getUnderlying();
+        }
+    }
+}
diff --git a/core/java/android/text/style/ClickableSpan.java b/core/java/android/text/style/ClickableSpan.java
new file mode 100644
index 0000000..989ef54
--- /dev/null
+++ b/core/java/android/text/style/ClickableSpan.java
@@ -0,0 +1,43 @@
+/*
+ * 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 android.text.style;
+
+import android.text.TextPaint;
+import android.view.View;
+
+/**
+ * If an object of this type is attached to the text of a TextView
+ * with a movement method of LinkMovementMethod, the affected spans of
+ * text can be selected.  If clicked, the {@link #onClick} method will
+ * be called.
+ */
+public abstract class ClickableSpan extends CharacterStyle implements UpdateAppearance {
+
+    /**
+     * Performs the click action associated with this span.
+     */
+    public abstract void onClick(View widget);
+   
+    /**
+     * Makes the text underlined and in the link color.
+     */
+    @Override
+    public void updateDrawState(TextPaint ds) {
+        ds.setColor(ds.linkColor);
+        ds.setUnderlineText(true);
+    }
+}
diff --git a/core/java/android/text/style/DrawableMarginSpan.java b/core/java/android/text/style/DrawableMarginSpan.java
new file mode 100644
index 0000000..3c471a5
--- /dev/null
+++ b/core/java/android/text/style/DrawableMarginSpan.java
@@ -0,0 +1,79 @@
+/*
+ * 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 android.text.style;
+
+import android.graphics.drawable.Drawable;
+import android.graphics.Paint;
+import android.graphics.Canvas;
+import android.graphics.RectF;
+import android.text.Spanned;
+import android.text.Layout;
+
+public class DrawableMarginSpan
+implements LeadingMarginSpan, LineHeightSpan
+{
+    public DrawableMarginSpan(Drawable b) {
+        mDrawable = b;
+    }
+
+    public DrawableMarginSpan(Drawable b, int pad) {
+        mDrawable = b;
+        mPad = pad;
+    }
+
+    public int getLeadingMargin(boolean first) {
+        return mDrawable.getIntrinsicWidth() + mPad;
+    }
+
+    public void drawLeadingMargin(Canvas c, Paint p, int x, int dir,
+                                  int top, int baseline, int bottom,
+                                  CharSequence text, int start, int end,
+                                  boolean first, Layout layout) {
+        int st = ((Spanned) text).getSpanStart(this);
+        int ix = (int)x;
+        int itop = (int)layout.getLineTop(layout.getLineForOffset(st));
+
+        int dw = mDrawable.getIntrinsicWidth();
+        int dh = mDrawable.getIntrinsicHeight();
+
+        if (dir < 0)
+            x -= dw;
+
+        // XXX What to do about Paint?
+        mDrawable.setBounds(ix, itop, ix+dw, itop+dh);
+        mDrawable.draw(c);
+    }
+
+    public void chooseHeight(CharSequence text, int start, int end,
+                             int istartv, int v,
+                             Paint.FontMetricsInt fm) {
+        if (end == ((Spanned) text).getSpanEnd(this)) {
+            int ht = mDrawable.getIntrinsicHeight();
+
+            int need = ht - (v + fm.descent - fm.ascent - istartv);
+            if (need > 0)
+                fm.descent += need;
+
+            need = ht - (v + fm.bottom - fm.top - istartv);
+            if (need > 0)
+                fm.bottom += need;
+        }
+    }
+
+    private Drawable mDrawable;
+    private int mPad;
+}
diff --git a/core/java/android/text/style/DynamicDrawableSpan.java b/core/java/android/text/style/DynamicDrawableSpan.java
new file mode 100644
index 0000000..89dc45b
--- /dev/null
+++ b/core/java/android/text/style/DynamicDrawableSpan.java
@@ -0,0 +1,128 @@
+/*
+ * 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 android.text.style;
+
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.graphics.Paint.Style;
+import android.graphics.drawable.Drawable;
+import android.util.Log;
+
+import java.lang.ref.WeakReference;
+
+/**
+ *
+ */
+public abstract class DynamicDrawableSpan extends ReplacementSpan {
+    private static final String TAG = "DynamicDrawableSpan";
+    
+    /**
+     * A constant indicating that the bottom of this span should be aligned
+     * with the bottom of the surrounding text, i.e., at the same level as the
+     * lowest descender in the text.
+     */
+    public static final int ALIGN_BOTTOM = 0;
+    
+    /**
+     * A constant indicating that the bottom of this span should be aligned
+     * with the baseline of the surrounding text.
+     */
+    public static final int ALIGN_BASELINE = 1;
+    
+    protected final int mVerticalAlignment;
+    
+    public DynamicDrawableSpan() {
+        mVerticalAlignment = ALIGN_BOTTOM;
+    }
+
+    /**
+     * @param verticalAlignment one of {@link #ALIGN_BOTTOM} or {@link #ALIGN_BASELINE}.
+     */
+    protected DynamicDrawableSpan(int verticalAlignment) {
+        mVerticalAlignment = verticalAlignment;
+    }
+
+    /**
+     * Returns the vertical alignment of this span, one of {@link #ALIGN_BOTTOM} or
+     * {@link #ALIGN_BASELINE}.
+     */
+    public int getVerticalAlignment() {
+        return mVerticalAlignment;
+    }
+
+    /**
+     * Your subclass must implement this method to provide the bitmap   
+     * to be drawn.  The dimensions of the bitmap must be the same
+     * from each call to the next.
+     */
+    public abstract Drawable getDrawable();
+
+    @Override
+    public int getSize(Paint paint, CharSequence text,
+                         int start, int end,
+                         Paint.FontMetricsInt fm) {
+        Drawable d = getCachedDrawable();
+        Rect rect = d.getBounds();
+
+        if (fm != null) {
+            fm.ascent = -rect.bottom; 
+            fm.descent = 0; 
+
+            fm.top = fm.ascent;
+            fm.bottom = 0;
+        }
+
+        return rect.right;
+    }
+
+    @Override
+    public void draw(Canvas canvas, CharSequence text,
+                     int start, int end, float x, 
+                     int top, int y, int bottom, Paint paint) {
+        Drawable b = getCachedDrawable();
+        canvas.save();
+        
+        int transY = bottom - b.getBounds().bottom;
+        if (mVerticalAlignment == ALIGN_BASELINE) {
+            transY -= paint.getFontMetricsInt().descent;
+        }
+
+        canvas.translate(x, transY);
+        b.draw(canvas);
+        canvas.restore();
+    }
+
+    private Drawable getCachedDrawable() {
+        WeakReference<Drawable> wr = mDrawableRef;
+        Drawable d = null;
+
+        if (wr != null)
+            d = wr.get();
+
+        if (d == null) {
+            d = getDrawable();
+            mDrawableRef = new WeakReference<Drawable>(d);
+        }
+
+        return d;
+    }
+
+    private WeakReference<Drawable> mDrawableRef;
+}
+
diff --git a/core/java/android/text/style/ForegroundColorSpan.java b/core/java/android/text/style/ForegroundColorSpan.java
new file mode 100644
index 0000000..476124d
--- /dev/null
+++ b/core/java/android/text/style/ForegroundColorSpan.java
@@ -0,0 +1,57 @@
+/*
+ * 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 android.text.style;
+
+import android.os.Parcel;
+import android.text.ParcelableSpan;
+import android.text.TextPaint;
+import android.text.TextUtils;
+
+public class ForegroundColorSpan extends CharacterStyle
+        implements UpdateAppearance, ParcelableSpan {
+
+    private final int mColor;
+
+	public ForegroundColorSpan(int color) {
+		mColor = color;
+	}
+
+    public ForegroundColorSpan(Parcel src) {
+        mColor = src.readInt();
+    }
+    
+    public int getSpanTypeId() {
+        return TextUtils.FOREGROUND_COLOR_SPAN;
+    }
+    
+    public int describeContents() {
+        return 0;
+    }
+
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeInt(mColor);
+    }
+
+	public int getForegroundColor() {
+		return mColor;
+	}
+
+	@Override
+	public void updateDrawState(TextPaint ds) {
+		ds.setColor(mColor);
+	}
+}
diff --git a/core/java/android/text/style/IconMarginSpan.java b/core/java/android/text/style/IconMarginSpan.java
new file mode 100644
index 0000000..c786a17
--- /dev/null
+++ b/core/java/android/text/style/IconMarginSpan.java
@@ -0,0 +1,73 @@
+/*
+ * 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 android.text.style;
+
+import android.graphics.Paint;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.RectF;
+import android.text.Spanned;
+import android.text.Layout;
+
+public class IconMarginSpan
+implements LeadingMarginSpan, LineHeightSpan
+{
+    public IconMarginSpan(Bitmap b) {
+        mBitmap = b;
+    }
+
+    public IconMarginSpan(Bitmap b, int pad) {
+        mBitmap = b;
+        mPad = pad;
+    }
+
+    public int getLeadingMargin(boolean first) {
+        return mBitmap.getWidth() + mPad;
+    }
+
+    public void drawLeadingMargin(Canvas c, Paint p, int x, int dir,
+                                  int top, int baseline, int bottom,
+                                  CharSequence text, int start, int end,
+                                  boolean first, Layout layout) {
+        int st = ((Spanned) text).getSpanStart(this);
+        int itop = layout.getLineTop(layout.getLineForOffset(st));
+
+        if (dir < 0)
+            x -= mBitmap.getWidth();
+
+        c.drawBitmap(mBitmap, x, itop, p);
+    }
+
+    public void chooseHeight(CharSequence text, int start, int end,
+                             int istartv, int v,
+                             Paint.FontMetricsInt fm) {
+        if (end == ((Spanned) text).getSpanEnd(this)) {
+            int ht = mBitmap.getHeight();
+
+            int need = ht - (v + fm.descent - fm.ascent - istartv);
+            if (need > 0)
+                fm.descent += need;
+
+            need = ht - (v + fm.bottom - fm.top - istartv);
+            if (need > 0)
+                fm.bottom += need;
+        }
+    }
+
+    private Bitmap mBitmap;
+    private int mPad;
+}
diff --git a/core/java/android/text/style/ImageSpan.java b/core/java/android/text/style/ImageSpan.java
new file mode 100644
index 0000000..efb88a0
--- /dev/null
+++ b/core/java/android/text/style/ImageSpan.java
@@ -0,0 +1,144 @@
+/*
+ * 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 android.text.style;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.net.Uri;
+import android.util.Log;
+
+import java.io.InputStream;
+
+public class ImageSpan extends DynamicDrawableSpan {
+    private Drawable mDrawable;
+    private Uri mContentUri;
+    private int mResourceId;
+    private Context mContext;
+    private String mSource;
+
+    public ImageSpan(Bitmap b) {
+        this(b, ALIGN_BOTTOM);
+    }
+
+    /**
+     * @param verticalAlignment one of {@link DynamicDrawableSpan#ALIGN_BOTTOM} or
+     * {@link DynamicDrawableSpan#ALIGN_BASELINE}.
+     */
+    public ImageSpan(Bitmap b, int verticalAlignment) {
+        super(verticalAlignment);
+        mDrawable = new BitmapDrawable(b);
+        int width = mDrawable.getIntrinsicWidth();
+        int height = mDrawable.getIntrinsicHeight();
+        mDrawable.setBounds(0, 0, width > 0 ? width : 0, height > 0 ? height : 0); 
+    }
+
+    public ImageSpan(Drawable d) {
+        this(d, ALIGN_BOTTOM);
+    }
+
+    /**
+     * @param verticalAlignment one of {@link DynamicDrawableSpan#ALIGN_BOTTOM} or
+     * {@link DynamicDrawableSpan#ALIGN_BASELINE}.
+     */
+    public ImageSpan(Drawable d, int verticalAlignment) {
+        super(verticalAlignment);
+        mDrawable = d;
+    }
+
+    public ImageSpan(Drawable d, String source) {
+        this(d, source, ALIGN_BOTTOM);
+    }
+
+    /**
+     * @param verticalAlignment one of {@link DynamicDrawableSpan#ALIGN_BOTTOM} or
+     * {@link DynamicDrawableSpan#ALIGN_BASELINE}.
+     */
+    public ImageSpan(Drawable d, String source, int verticalAlignment) {
+        super(verticalAlignment);
+        mDrawable = d;
+        mSource = source;
+    }
+
+    public ImageSpan(Context context, Uri uri) {
+        this(context, uri, ALIGN_BOTTOM);
+    }
+
+    /**
+     * @param verticalAlignment one of {@link DynamicDrawableSpan#ALIGN_BOTTOM} or
+     * {@link DynamicDrawableSpan#ALIGN_BASELINE}.
+     */
+    public ImageSpan(Context context, Uri uri, int verticalAlignment) {
+        super(verticalAlignment);
+        mContext = context;
+        mContentUri = uri;
+    }
+
+    public ImageSpan(Context context, int resourceId) {
+        this(context, resourceId, ALIGN_BOTTOM);
+    }
+
+    /**
+     * @param verticalAlignment one of {@link DynamicDrawableSpan#ALIGN_BOTTOM} or
+     * {@link DynamicDrawableSpan#ALIGN_BASELINE}.
+     */
+    public ImageSpan(Context context, int resourceId, int verticalAlignment) {
+        super(verticalAlignment);
+        mContext = context;
+        mResourceId = resourceId;
+    }
+
+    @Override
+    public Drawable getDrawable() {
+        Drawable drawable = null;
+        
+        if (mDrawable != null) {
+            drawable = mDrawable;
+        } else  if (mContentUri != null) {
+            Bitmap bitmap = null;
+            try {
+                InputStream is = mContext.getContentResolver().openInputStream(
+                        mContentUri);
+                bitmap = BitmapFactory.decodeStream(is);
+                drawable = new BitmapDrawable(bitmap);
+                is.close();
+            } catch (Exception e) {
+                Log.e("sms", "Failed to loaded content " + mContentUri, e);
+            }
+        } else {
+            try {
+                drawable = mContext.getResources().getDrawable(mResourceId);
+                drawable.setBounds(0, 0, drawable.getIntrinsicWidth(),
+                        drawable.getIntrinsicHeight());
+            } catch (Exception e) {
+                Log.e("sms", "Unable to find resource: " + mResourceId);
+            }                
+        }
+
+        return drawable;
+    }
+
+    /**
+     * Returns the source string that was saved during construction.
+     */
+    public String getSource() {
+        return mSource;
+    }
+
+}
diff --git a/core/java/android/text/style/LeadingMarginSpan.java b/core/java/android/text/style/LeadingMarginSpan.java
new file mode 100644
index 0000000..8e212e3
--- /dev/null
+++ b/core/java/android/text/style/LeadingMarginSpan.java
@@ -0,0 +1,78 @@
+/*
+ * 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 android.text.style;
+
+import android.graphics.Paint;
+import android.graphics.Canvas;
+import android.os.Parcel;
+import android.text.Layout;
+import android.text.ParcelableSpan;
+import android.text.TextUtils;
+
+public interface LeadingMarginSpan
+extends ParagraphStyle
+{
+    public int getLeadingMargin(boolean first);
+    public void drawLeadingMargin(Canvas c, Paint p,
+                                  int x, int dir,
+                                  int top, int baseline, int bottom,
+                                  CharSequence text, int start, int end,
+                                  boolean first, Layout layout);
+
+    public static class Standard implements LeadingMarginSpan, ParcelableSpan {
+        private final int mFirst, mRest;
+        
+        public Standard(int first, int rest) {
+            mFirst = first;
+            mRest = rest;
+        }
+
+        public Standard(int every) {
+            this(every, every);
+        }
+
+        public Standard(Parcel src) {
+            mFirst = src.readInt();
+            mRest = src.readInt();
+        }
+        
+        public int getSpanTypeId() {
+            return TextUtils.LEADING_MARGIN_SPAN;
+        }
+        
+        public int describeContents() {
+            return 0;
+        }
+
+        public void writeToParcel(Parcel dest, int flags) {
+            dest.writeInt(mFirst);
+            dest.writeInt(mRest);
+        }
+
+        public int getLeadingMargin(boolean first) {
+            return first ? mFirst : mRest;
+        }
+
+        public void drawLeadingMargin(Canvas c, Paint p,
+                                      int x, int dir,
+                                      int top, int baseline, int bottom,
+                                      CharSequence text, int start, int end,
+                                      boolean first, Layout layout) {
+            ;
+        }
+    }
+}
diff --git a/core/java/android/text/style/LineBackgroundSpan.java b/core/java/android/text/style/LineBackgroundSpan.java
new file mode 100644
index 0000000..854aeaf
--- /dev/null
+++ b/core/java/android/text/style/LineBackgroundSpan.java
@@ -0,0 +1,30 @@
+/*
+ * 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 android.text.style;
+
+import android.graphics.Paint;
+import android.graphics.Canvas;
+
+public interface LineBackgroundSpan
+extends ParagraphStyle
+{
+    public void drawBackground(Canvas c, Paint p,
+                               int left, int right,
+                               int top, int baseline, int bottom,
+                               CharSequence text, int start, int end,
+                               int lnum);
+}
diff --git a/core/java/android/text/style/LineHeightSpan.java b/core/java/android/text/style/LineHeightSpan.java
new file mode 100644
index 0000000..c0ef97c
--- /dev/null
+++ b/core/java/android/text/style/LineHeightSpan.java
@@ -0,0 +1,29 @@
+/*
+ * 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 android.text.style;
+
+import android.graphics.Paint;
+import android.graphics.Canvas;
+import android.text.Layout;
+
+public interface LineHeightSpan
+extends ParagraphStyle, WrapTogetherSpan
+{
+    public void chooseHeight(CharSequence text, int start, int end,
+                             int spanstartv, int v,
+                             Paint.FontMetricsInt fm);
+}
diff --git a/core/java/android/text/style/MaskFilterSpan.java b/core/java/android/text/style/MaskFilterSpan.java
new file mode 100644
index 0000000..64ab0d8
--- /dev/null
+++ b/core/java/android/text/style/MaskFilterSpan.java
@@ -0,0 +1,38 @@
+/*
+ * 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 android.text.style;
+
+import android.graphics.MaskFilter;
+import android.text.TextPaint;
+
+public class MaskFilterSpan extends CharacterStyle implements UpdateAppearance {
+
+	private MaskFilter mFilter;
+
+	public MaskFilterSpan(MaskFilter filter) {
+		mFilter = filter;
+	}
+
+	public MaskFilter getMaskFilter() {
+		return mFilter;
+	}
+
+	@Override
+	public void updateDrawState(TextPaint ds) {
+		ds.setMaskFilter(mFilter);
+	}
+}
diff --git a/core/java/android/text/style/MetricAffectingSpan.java b/core/java/android/text/style/MetricAffectingSpan.java
new file mode 100644
index 0000000..92558eb
--- /dev/null
+++ b/core/java/android/text/style/MetricAffectingSpan.java
@@ -0,0 +1,85 @@
+/*
+ * 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 android.text.style;
+
+import android.graphics.Paint;
+import android.text.TextPaint;
+
+/**
+ * The classes that affect character-level text formatting in a way that
+ * changes the width or height of characters extend this class.
+ */
+public abstract class MetricAffectingSpan
+extends CharacterStyle
+implements UpdateLayout {
+
+	public abstract void updateMeasureState(TextPaint p);
+
+    /**
+     * Returns "this" for most MetricAffectingSpans, but for 
+     * MetricAffectingSpans that were generated by {@link #wrap},
+     * returns the underlying MetricAffectingSpan.
+     */
+    @Override
+    public MetricAffectingSpan getUnderlying() {
+        return this;
+    }
+
+    /**
+     * A Passthrough MetricAffectingSpan is one that
+     * passes {@link #updateDrawState} and {@link #updateMeasureState}
+     * calls through to the specified MetricAffectingSpan 
+     * while still being a distinct object,
+     * and is therefore able to be attached to the same Spannable
+     * to which the specified MetricAffectingSpan is already attached.
+     */
+    /* package */ static class Passthrough extends MetricAffectingSpan {
+        private MetricAffectingSpan mStyle;
+        
+        /**
+         * Creates a new Passthrough of the specfied MetricAffectingSpan.
+         */
+        public Passthrough(MetricAffectingSpan cs) {
+            mStyle = cs;
+        }
+
+        /**
+         * Passes updateDrawState through to the underlying MetricAffectingSpan.
+         */
+        @Override
+        public void updateDrawState(TextPaint tp) {
+            mStyle.updateDrawState(tp);
+        }
+
+        /**
+         * Passes updateMeasureState through to the underlying MetricAffectingSpan.
+         */
+        @Override
+        public void updateMeasureState(TextPaint tp) {
+            mStyle.updateMeasureState(tp);
+        }
+    
+        /**
+         * Returns the MetricAffectingSpan underlying this one, or the one
+         * underlying it if it too is a Passthrough.
+         */
+        @Override
+        public MetricAffectingSpan getUnderlying() {
+            return mStyle.getUnderlying();
+        }
+    }
+}
diff --git a/core/java/android/text/style/ParagraphStyle.java b/core/java/android/text/style/ParagraphStyle.java
new file mode 100644
index 0000000..423156e
--- /dev/null
+++ b/core/java/android/text/style/ParagraphStyle.java
@@ -0,0 +1,26 @@
+/*
+ * 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 android.text.style;
+
+/**
+ * The classes that affect paragraph-level text formatting implement
+ * this interface.
+ */
+public interface ParagraphStyle
+{
+
+}
diff --git a/core/java/android/text/style/QuoteSpan.java b/core/java/android/text/style/QuoteSpan.java
new file mode 100644
index 0000000..29dd273
--- /dev/null
+++ b/core/java/android/text/style/QuoteSpan.java
@@ -0,0 +1,81 @@
+/*
+ * 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 android.text.style;
+
+import android.graphics.Paint;
+import android.graphics.Canvas;
+import android.os.Parcel;
+import android.text.Layout;
+import android.text.ParcelableSpan;
+import android.text.TextUtils;
+
+public class QuoteSpan implements LeadingMarginSpan, ParcelableSpan {
+    private static final int STRIPE_WIDTH = 2;
+    private static final int GAP_WIDTH = 2;
+
+    private final int mColor;
+
+    public QuoteSpan() {
+        super();
+        mColor = 0xff0000ff;
+    }
+    
+    public QuoteSpan(int color) {
+        super();
+        mColor = color;
+    }
+
+    public QuoteSpan(Parcel src) {
+        mColor = src.readInt();
+    }
+    
+    public int getSpanTypeId() {
+        return TextUtils.QUOTE_SPAN;
+    }
+    
+    public int describeContents() {
+        return 0;
+    }
+
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeInt(mColor);
+    }
+
+    public int getColor() {
+        return mColor;
+    }
+    
+    public int getLeadingMargin(boolean first) {
+        return STRIPE_WIDTH + GAP_WIDTH;
+    }
+
+    public void drawLeadingMargin(Canvas c, Paint p, int x, int dir,
+                                  int top, int baseline, int bottom,
+                                  CharSequence text, int start, int end,
+                                  boolean first, Layout layout) {
+        Paint.Style style = p.getStyle();
+        int color = p.getColor();
+
+        p.setStyle(Paint.Style.FILL);
+        p.setColor(mColor);
+
+        c.drawRect(x, top, x + dir * STRIPE_WIDTH, bottom, p);
+
+        p.setStyle(style);
+        p.setColor(color);
+    }
+}
diff --git a/core/java/android/text/style/RasterizerSpan.java b/core/java/android/text/style/RasterizerSpan.java
new file mode 100644
index 0000000..75b5bcc
--- /dev/null
+++ b/core/java/android/text/style/RasterizerSpan.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2007 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 android.text.style;
+
+import android.graphics.Rasterizer;
+import android.text.TextPaint;
+
+public class RasterizerSpan extends CharacterStyle implements UpdateAppearance {
+
+	private Rasterizer mRasterizer;
+
+	public RasterizerSpan(Rasterizer r) {
+		mRasterizer = r;
+	}
+
+	public Rasterizer getRasterizer() {
+		return mRasterizer;
+	}
+
+	@Override
+	public void updateDrawState(TextPaint ds) {
+		ds.setRasterizer(mRasterizer);
+	}
+}
diff --git a/core/java/android/text/style/RelativeSizeSpan.java b/core/java/android/text/style/RelativeSizeSpan.java
new file mode 100644
index 0000000..9717362
--- /dev/null
+++ b/core/java/android/text/style/RelativeSizeSpan.java
@@ -0,0 +1,61 @@
+/*
+ * 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 android.text.style;
+
+import android.os.Parcel;
+import android.text.ParcelableSpan;
+import android.text.TextPaint;
+import android.text.TextUtils;
+
+public class RelativeSizeSpan extends MetricAffectingSpan implements ParcelableSpan {
+
+	private final float mProportion;
+
+	public RelativeSizeSpan(float proportion) {
+		mProportion = proportion;
+	}
+
+    public RelativeSizeSpan(Parcel src) {
+        mProportion = src.readFloat();
+    }
+    
+    public int getSpanTypeId() {
+        return TextUtils.RELATIVE_SIZE_SPAN;
+    }
+    
+    public int describeContents() {
+        return 0;
+    }
+
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeFloat(mProportion);
+    }
+
+	public float getSizeChange() {
+		return mProportion;
+	}
+
+	@Override
+	public void updateDrawState(TextPaint ds) {
+		ds.setTextSize(ds.getTextSize() * mProportion);
+	}
+
+	@Override
+	public void updateMeasureState(TextPaint ds) {
+		ds.setTextSize(ds.getTextSize() * mProportion);
+	}
+}
diff --git a/core/java/android/text/style/ReplacementSpan.java b/core/java/android/text/style/ReplacementSpan.java
new file mode 100644
index 0000000..26c725f
--- /dev/null
+++ b/core/java/android/text/style/ReplacementSpan.java
@@ -0,0 +1,43 @@
+/*
+ * 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 android.text.style;
+
+import android.graphics.Paint;
+import android.graphics.Canvas;
+import android.text.TextPaint;
+
+public abstract class ReplacementSpan extends MetricAffectingSpan {
+
+    public abstract int getSize(Paint paint, CharSequence text,
+                         int start, int end,
+                         Paint.FontMetricsInt fm);
+    public abstract void draw(Canvas canvas, CharSequence text,
+                     int start, int end, float x,
+                     int top, int y, int bottom, Paint paint);
+
+    /**
+     * This method does nothing, since ReplacementSpans are measured
+     * explicitly instead of affecting Paint properties.
+     */
+    public void updateMeasureState(TextPaint p) { }
+
+    /**
+     * This method does nothing, since ReplacementSpans are drawn
+     * explicitly instead of affecting Paint properties.
+     */
+    public void updateDrawState(TextPaint ds) { }
+}
diff --git a/core/java/android/text/style/ScaleXSpan.java b/core/java/android/text/style/ScaleXSpan.java
new file mode 100644
index 0000000..655064b
--- /dev/null
+++ b/core/java/android/text/style/ScaleXSpan.java
@@ -0,0 +1,61 @@
+/*
+ * 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 android.text.style;
+
+import android.os.Parcel;
+import android.text.ParcelableSpan;
+import android.text.TextPaint;
+import android.text.TextUtils;
+
+public class ScaleXSpan extends MetricAffectingSpan implements ParcelableSpan {
+
+	private final float mProportion;
+
+	public ScaleXSpan(float proportion) {
+		mProportion = proportion;
+	}
+
+    public ScaleXSpan(Parcel src) {
+        mProportion = src.readFloat();
+    }
+    
+    public int getSpanTypeId() {
+        return TextUtils.SCALE_X_SPAN;
+    }
+    
+    public int describeContents() {
+        return 0;
+    }
+
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeFloat(mProportion);
+    }
+
+	public float getScaleX() {
+		return mProportion;
+	}
+
+	@Override
+	public void updateDrawState(TextPaint ds) {
+		ds.setTextScaleX(ds.getTextScaleX() * mProportion);
+	}
+
+	@Override
+	public void updateMeasureState(TextPaint ds) {
+		ds.setTextScaleX(ds.getTextScaleX() * mProportion);
+	}
+}
diff --git a/core/java/android/text/style/StrikethroughSpan.java b/core/java/android/text/style/StrikethroughSpan.java
new file mode 100644
index 0000000..b51363a
--- /dev/null
+++ b/core/java/android/text/style/StrikethroughSpan.java
@@ -0,0 +1,47 @@
+/*
+ * 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 android.text.style;
+
+import android.os.Parcel;
+import android.text.ParcelableSpan;
+import android.text.TextPaint;
+import android.text.TextUtils;
+
+public class StrikethroughSpan extends CharacterStyle
+        implements UpdateAppearance, ParcelableSpan {
+    public StrikethroughSpan() {
+    }
+    
+    public StrikethroughSpan(Parcel src) {
+    }
+    
+    public int getSpanTypeId() {
+        return TextUtils.STRIKETHROUGH_SPAN;
+    }
+    
+    public int describeContents() {
+        return 0;
+    }
+
+    public void writeToParcel(Parcel dest, int flags) {
+    }
+
+	@Override
+	public void updateDrawState(TextPaint ds) {
+		ds.setStrikeThruText(true);
+	}
+}
diff --git a/core/java/android/text/style/StyleSpan.java b/core/java/android/text/style/StyleSpan.java
new file mode 100644
index 0000000..8e6147c
--- /dev/null
+++ b/core/java/android/text/style/StyleSpan.java
@@ -0,0 +1,112 @@
+/*
+ * 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 android.text.style;
+
+import android.graphics.Paint;
+import android.graphics.Typeface;
+import android.os.Parcel;
+import android.text.ParcelableSpan;
+import android.text.TextPaint;
+import android.text.TextUtils;
+
+/**
+ * 
+ * Describes a style in a span.
+ * Note that styles are cumulative -- if both bold and italic are set in
+ * separate spans, or if the base style is bold and a span calls for italic,
+ * you get bold italic.  You can't turn off a style from the base style.
+ *
+ */
+public class StyleSpan extends MetricAffectingSpan implements ParcelableSpan {
+
+	private final int mStyle;
+
+	/**
+	 * 
+	 * @param style An integer constant describing the style for this span. Examples
+	 * include bold, italic, and normal. Values are constants defined 
+	 * in {@link android.graphics.Typeface}.
+	 */
+	public StyleSpan(int style) {
+		mStyle = style;
+	}
+
+    public StyleSpan(Parcel src) {
+        mStyle = src.readInt();
+    }
+    
+    public int getSpanTypeId() {
+        return TextUtils.STYLE_SPAN;
+    }
+    
+    public int describeContents() {
+        return 0;
+    }
+
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeInt(mStyle);
+    }
+
+	/**
+	 * Returns the style constant defined in {@link android.graphics.Typeface}. 
+	 */
+	public int getStyle() {
+		return mStyle;
+	}
+
+	@Override
+    public void updateDrawState(TextPaint ds) {
+        apply(ds, mStyle);
+    }
+
+	@Override
+    public void updateMeasureState(TextPaint paint) {
+        apply(paint, mStyle);
+    }
+
+    private static void apply(Paint paint, int style) {
+        int oldStyle;
+
+        Typeface old = paint.getTypeface();
+        if (old == null) {
+            oldStyle = 0;
+        } else {
+            oldStyle = old.getStyle();
+        }
+
+        int want = oldStyle | style;
+
+        Typeface tf;
+        if (old == null) {
+            tf = Typeface.defaultFromStyle(want);
+        } else {
+            tf = Typeface.create(old, want);
+        }
+
+        int fake = want & ~tf.getStyle();
+
+        if ((fake & Typeface.BOLD) != 0) {
+            paint.setFakeBoldText(true);
+        }
+
+        if ((fake & Typeface.ITALIC) != 0) {
+            paint.setTextSkewX(-0.25f);
+        }
+
+        paint.setTypeface(tf);
+    }
+}
diff --git a/core/java/android/text/style/SubscriptSpan.java b/core/java/android/text/style/SubscriptSpan.java
new file mode 100644
index 0000000..de1d8b2
--- /dev/null
+++ b/core/java/android/text/style/SubscriptSpan.java
@@ -0,0 +1,51 @@
+/*
+ * 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 android.text.style;
+
+import android.os.Parcel;
+import android.text.ParcelableSpan;
+import android.text.TextPaint;
+import android.text.TextUtils;
+
+public class SubscriptSpan extends MetricAffectingSpan implements ParcelableSpan {
+    public SubscriptSpan() {
+    }
+    
+    public SubscriptSpan(Parcel src) {
+    }
+    
+    public int getSpanTypeId() {
+        return TextUtils.SUBSCRIPT_SPAN;
+    }
+    
+    public int describeContents() {
+        return 0;
+    }
+
+    public void writeToParcel(Parcel dest, int flags) {
+    }
+
+    @Override
+    public void updateDrawState(TextPaint tp) {
+        tp.baselineShift -= (int) (tp.ascent() / 2);
+    }
+
+    @Override
+    public void updateMeasureState(TextPaint tp) {
+        tp.baselineShift -= (int) (tp.ascent() / 2);
+    }
+}
diff --git a/core/java/android/text/style/SuperscriptSpan.java b/core/java/android/text/style/SuperscriptSpan.java
new file mode 100644
index 0000000..285fe84
--- /dev/null
+++ b/core/java/android/text/style/SuperscriptSpan.java
@@ -0,0 +1,51 @@
+/*
+ * 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 android.text.style;
+
+import android.os.Parcel;
+import android.text.ParcelableSpan;
+import android.text.TextPaint;
+import android.text.TextUtils;
+
+public class SuperscriptSpan extends MetricAffectingSpan implements ParcelableSpan {
+    public SuperscriptSpan() {
+    }
+    
+    public SuperscriptSpan(Parcel src) {
+    }
+    
+    public int getSpanTypeId() {
+        return TextUtils.SUPERSCRIPT_SPAN;
+    }
+    
+    public int describeContents() {
+        return 0;
+    }
+
+    public void writeToParcel(Parcel dest, int flags) {
+    }
+
+    @Override
+    public void updateDrawState(TextPaint tp) {
+        tp.baselineShift += (int) (tp.ascent() / 2);
+    }
+
+    @Override
+    public void updateMeasureState(TextPaint tp) {
+        tp.baselineShift += (int) (tp.ascent() / 2);
+    }
+}
diff --git a/core/java/android/text/style/TabStopSpan.java b/core/java/android/text/style/TabStopSpan.java
new file mode 100644
index 0000000..e5b7644
--- /dev/null
+++ b/core/java/android/text/style/TabStopSpan.java
@@ -0,0 +1,37 @@
+/*
+ * 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 android.text.style;
+
+public interface TabStopSpan
+extends ParagraphStyle
+{
+    public int getTabStop();
+
+    public static class Standard
+    implements TabStopSpan
+    {
+        public Standard(int where) {
+            mTab = where;
+        }
+
+        public int getTabStop() {
+            return mTab;
+        }
+
+        private int mTab;
+    }
+}
diff --git a/core/java/android/text/style/TextAppearanceSpan.java b/core/java/android/text/style/TextAppearanceSpan.java
new file mode 100644
index 0000000..de929e3
--- /dev/null
+++ b/core/java/android/text/style/TextAppearanceSpan.java
@@ -0,0 +1,250 @@
+/*
+ * 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 android.text.style;
+
+import android.content.Context;
+import android.content.res.ColorStateList;
+import android.content.res.TypedArray;
+import android.graphics.Typeface;
+import android.os.Parcel;
+import android.text.ParcelableSpan;
+import android.text.TextPaint;
+import android.text.TextUtils;
+
+/**
+ * Sets the text color, size, style, and typeface to match a TextAppearance
+ * resource.
+ */
+public class TextAppearanceSpan extends MetricAffectingSpan implements ParcelableSpan {
+    private final String mTypeface;
+    private final int mStyle;
+    private final int mTextSize;
+    private final ColorStateList mTextColor;
+    private final ColorStateList mTextColorLink;
+
+    /**
+     * Uses the specified TextAppearance resource to determine the
+     * text appearance.  The <code>appearance</code> should be, for example,
+     * <code>android.R.style.TextAppearance_Small</code>.
+     */
+    public TextAppearanceSpan(Context context, int appearance) {
+        this(context, appearance, -1);
+    }
+
+    /**
+     * Uses the specified TextAppearance resource to determine the
+     * text appearance, and the specified text color resource
+     * to determine the color.  The <code>appearance</code> should be,
+     * for example, <code>android.R.style.TextAppearance_Small</code>,
+     * and the <code>colorList</code> should be, for example,
+     * <code>android.R.styleable.Theme_textColorDim</code>.
+     */
+    public TextAppearanceSpan(Context context, int appearance,
+                              int colorList) {
+        ColorStateList textColor;
+        
+        TypedArray a =
+            context.obtainStyledAttributes(appearance,
+                                           com.android.internal.R.styleable.TextAppearance);
+
+        textColor = a.getColorStateList(com.android.internal.R.styleable.
+                                        TextAppearance_textColor);
+        mTextColorLink = a.getColorStateList(com.android.internal.R.styleable.
+                                        TextAppearance_textColorLink);
+        mTextSize = a.getDimensionPixelSize(com.android.internal.R.styleable.
+                                        TextAppearance_textSize, -1);
+
+        mStyle = a.getInt(com.android.internal.R.styleable.TextAppearance_textStyle, 0);
+        int tf = a.getInt(com.android.internal.R.styleable.TextAppearance_typeface, 0);
+
+        switch (tf) {
+            case 1:
+                mTypeface = "sans";
+                break;
+
+            case 2:
+                mTypeface = "serif";
+                break;
+
+            case 3:
+                mTypeface = "monospace";
+                break;
+                
+            default:
+                mTypeface = null;
+                break;
+        }
+
+        a.recycle();
+
+        if (colorList >= 0) {
+            a = context.obtainStyledAttributes(com.android.internal.R.style.Theme,
+                                            com.android.internal.R.styleable.Theme);
+
+            textColor = a.getColorStateList(colorList);
+            a.recycle();
+        }
+        
+        mTextColor = textColor;
+    }
+
+    /**
+     * Makes text be drawn with the specified typeface, size, style,
+     * and colors.
+     */
+    public TextAppearanceSpan(String family, int style, int size,
+                              ColorStateList color, ColorStateList linkColor) {
+        mTypeface = family;
+        mStyle = style;
+        mTextSize = size;
+        mTextColor = color;
+        mTextColorLink = linkColor;
+    }
+
+    public TextAppearanceSpan(Parcel src) {
+        mTypeface = src.readString();
+        mStyle = src.readInt();
+        mTextSize = src.readInt();
+        if (src.readInt() != 0) {
+            mTextColor = ColorStateList.CREATOR.createFromParcel(src);
+        } else {
+            mTextColor = null;
+        }
+        if (src.readInt() != 0) {
+            mTextColorLink = ColorStateList.CREATOR.createFromParcel(src);
+        } else {
+            mTextColorLink = null;
+        }
+    }
+    
+    public int getSpanTypeId() {
+        return TextUtils.TEXT_APPEARANCE_SPAN;
+    }
+    
+    public int describeContents() {
+        return 0;
+    }
+
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeString(mTypeface);
+        dest.writeInt(mStyle);
+        dest.writeInt(mTextSize);
+        if (mTextColor != null) {
+            dest.writeInt(1);
+            mTextColor.writeToParcel(dest, flags);
+        } else {
+            dest.writeInt(0);
+        }
+        if (mTextColorLink != null) {
+            dest.writeInt(1);
+            mTextColorLink.writeToParcel(dest, flags);
+        } else {
+            dest.writeInt(0);
+        }
+    }
+
+    /**
+     * Returns the typeface family specified by this span, or <code>null</code>
+     * if it does not specify one.
+     */
+    public String getFamily() {
+        return mTypeface;
+    }
+
+    /**
+     * Returns the text color specified by this span, or <code>null</code>
+     * if it does not specify one.
+     */
+    public ColorStateList getTextColor() {
+        return mTextColor;
+    }
+
+    /**
+     * Returns the link color specified by this span, or <code>null</code>
+     * if it does not specify one.
+     */
+    public ColorStateList getLinkTextColor() {
+        return mTextColorLink;
+    }
+
+    /**
+     * Returns the text size specified by this span, or <code>-1</code>
+     * if it does not specify one.
+     */
+    public int getTextSize() {
+        return mTextSize;
+    }
+
+    /**
+     * Returns the text style specified by this span, or <code>0</code>
+     * if it does not specify one.
+     */
+    public int getTextStyle() {
+        return mStyle;
+    }
+
+    @Override
+    public void updateDrawState(TextPaint ds) {
+        updateMeasureState(ds);
+
+        if (mTextColor != null) {
+            ds.setColor(mTextColor.getColorForState(ds.drawableState, 0));
+        }
+
+        if (mTextColorLink != null) {
+            ds.linkColor = mTextColor.getColorForState(ds.drawableState, 0);
+        }
+    }
+
+    @Override
+    public void updateMeasureState(TextPaint ds) {
+        if (mTypeface != null || mStyle != 0) {
+            Typeface tf = ds.getTypeface();
+            int style = 0;
+
+            if (tf != null) {
+                style = tf.getStyle();
+            }
+
+            style |= mStyle;
+
+            if (mTypeface != null) {
+                tf = Typeface.create(mTypeface, style);
+            } else if (tf == null) {
+                tf = Typeface.defaultFromStyle(style);
+            } else {
+                tf = Typeface.create(tf, style);
+            }
+
+            int fake = style & ~tf.getStyle();
+
+            if ((fake & Typeface.BOLD) != 0) {
+                ds.setFakeBoldText(true);
+            }
+
+            if ((fake & Typeface.ITALIC) != 0) {
+                ds.setTextSkewX(-0.25f);
+            }
+
+            ds.setTypeface(tf);
+        }
+
+        if (mTextSize > 0) {
+            ds.setTextSize(mTextSize);
+        }
+    }
+}
diff --git a/core/java/android/text/style/TypefaceSpan.java b/core/java/android/text/style/TypefaceSpan.java
new file mode 100644
index 0000000..f194060
--- /dev/null
+++ b/core/java/android/text/style/TypefaceSpan.java
@@ -0,0 +1,96 @@
+/*
+ * 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 android.text.style;
+
+import android.graphics.Paint;
+import android.graphics.Typeface;
+import android.os.Parcel;
+import android.text.ParcelableSpan;
+import android.text.TextPaint;
+import android.text.TextUtils;
+
+/**
+ * Changes the typeface family of the text to which the span is attached.
+ */
+public class TypefaceSpan extends MetricAffectingSpan implements ParcelableSpan {
+    private final String mFamily;
+
+    /**
+     * @param family The font family for this typeface.  Examples include
+     * "monospace", "serif", and "sans-serif".
+     */
+    public TypefaceSpan(String family) {
+        mFamily = family;
+    }
+
+    public TypefaceSpan(Parcel src) {
+        mFamily = src.readString();
+    }
+    
+    public int getSpanTypeId() {
+        return TextUtils.TYPEFACE_SPAN;
+    }
+    
+    public int describeContents() {
+        return 0;
+    }
+
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeString(mFamily);
+    }
+
+    /**
+     * Returns the font family name.
+     */
+    public String getFamily() {
+        return mFamily;
+    }
+
+    @Override
+    public void updateDrawState(TextPaint ds) {
+        apply(ds, mFamily);
+    }
+
+    @Override
+    public void updateMeasureState(TextPaint paint) {
+        apply(paint, mFamily);
+    }
+
+    private static void apply(Paint paint, String family) {
+        int oldStyle;
+
+        Typeface old = paint.getTypeface();
+        if (old == null) {
+            oldStyle = 0;
+        } else {
+            oldStyle = old.getStyle();
+        }
+
+        Typeface tf = Typeface.create(family, oldStyle);
+        int fake = oldStyle & ~tf.getStyle();
+
+        if ((fake & Typeface.BOLD) != 0) {
+            paint.setFakeBoldText(true);
+        }
+
+        if ((fake & Typeface.ITALIC) != 0) {
+            paint.setTextSkewX(-0.25f);
+        }
+
+        paint.setTypeface(tf);
+    }
+}
diff --git a/core/java/android/text/style/URLSpan.java b/core/java/android/text/style/URLSpan.java
new file mode 100644
index 0000000..f458611
--- /dev/null
+++ b/core/java/android/text/style/URLSpan.java
@@ -0,0 +1,61 @@
+/*
+ * 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 android.text.style;
+
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Parcel;
+import android.text.ParcelableSpan;
+import android.text.TextUtils;
+import android.view.View;
+
+public class URLSpan extends ClickableSpan implements ParcelableSpan {
+
+    private final String mURL;
+
+    public URLSpan(String url) {
+        mURL = url;
+    }
+
+    public URLSpan(Parcel src) {
+        mURL = src.readString();
+    }
+    
+    public int getSpanTypeId() {
+        return TextUtils.URL_SPAN;
+    }
+    
+    public int describeContents() {
+        return 0;
+    }
+
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeString(mURL);
+    }
+
+    public String getURL() {
+        return mURL;
+    }
+
+    @Override
+    public void onClick(View widget) {
+        Uri uri = Uri.parse(getURL());
+        Intent intent = new Intent(Intent.ACTION_VIEW, uri);
+        intent.addCategory(Intent.CATEGORY_BROWSABLE);
+        widget.getContext().startActivity(intent);
+    }
+}
diff --git a/core/java/android/text/style/UnderlineSpan.java b/core/java/android/text/style/UnderlineSpan.java
new file mode 100644
index 0000000..b0cb0e8
--- /dev/null
+++ b/core/java/android/text/style/UnderlineSpan.java
@@ -0,0 +1,47 @@
+/*
+ * 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 android.text.style;
+
+import android.os.Parcel;
+import android.text.ParcelableSpan;
+import android.text.TextPaint;
+import android.text.TextUtils;
+
+public class UnderlineSpan extends CharacterStyle
+        implements UpdateAppearance, ParcelableSpan {
+    public UnderlineSpan() {
+    }
+    
+    public UnderlineSpan(Parcel src) {
+    }
+    
+    public int getSpanTypeId() {
+        return TextUtils.UNDERLINE_SPAN;
+    }
+    
+    public int describeContents() {
+        return 0;
+    }
+
+    public void writeToParcel(Parcel dest, int flags) {
+    }
+
+	@Override
+	public void updateDrawState(TextPaint ds) {
+		ds.setUnderlineText(true);
+	}
+}
diff --git a/core/java/android/text/style/UpdateAppearance.java b/core/java/android/text/style/UpdateAppearance.java
new file mode 100644
index 0000000..198e4fa
--- /dev/null
+++ b/core/java/android/text/style/UpdateAppearance.java
@@ -0,0 +1,10 @@
+package android.text.style;
+
+/**
+ * The classes that affect character-level text in a way that modifies their
+ * appearance when one is added or removed must implement this interface.  Note
+ * that if the class also impacts size or other metrics, it should instead
+ * implement {@link UpdateLayout}.
+ */
+public interface UpdateAppearance {
+}
diff --git a/core/java/android/text/style/UpdateLayout.java b/core/java/android/text/style/UpdateLayout.java
new file mode 100644
index 0000000..591075e
--- /dev/null
+++ b/core/java/android/text/style/UpdateLayout.java
@@ -0,0 +1,25 @@
+/*
+ * 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 android.text.style;
+
+/**
+ * The classes that affect character-level text formatting in a way that
+ * triggers a text layout update when one is added or removed must implement
+ * this interface.  This interface also includes {@link UpdateAppearance}
+ * since such a change implicitly also impacts the appearance.
+ */
+public interface UpdateLayout extends UpdateAppearance { }
diff --git a/core/java/android/text/style/WrapTogetherSpan.java b/core/java/android/text/style/WrapTogetherSpan.java
new file mode 100644
index 0000000..11721a8
--- /dev/null
+++ b/core/java/android/text/style/WrapTogetherSpan.java
@@ -0,0 +1,23 @@
+/*
+ * 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 android.text.style;
+
+public interface WrapTogetherSpan
+extends ParagraphStyle
+{
+
+}
diff --git a/core/java/android/text/style/package.html b/core/java/android/text/style/package.html
new file mode 100644
index 0000000..0a8520c
--- /dev/null
+++ b/core/java/android/text/style/package.html
@@ -0,0 +1,10 @@
+<html>
+<body>
+
+<p>Provides classes used to view or change the style of a span of text in a View object.
+The classes with a subclass Standard are passed in to {@link android.text.SpannableString#setSpan(java.lang.Object, int, int, int)
+SpannableString.setSpan()} or {@link android.text.SpannableStringBuilder#setSpan(java.lang.Object, int, int, int)
+SpannableStringBuilder.setSpan()} to add a new styled span to a string in a View object.
+
+</body>
+</html>
diff --git a/core/java/android/text/util/Linkify.java b/core/java/android/text/util/Linkify.java
new file mode 100644
index 0000000..d61e888
--- /dev/null
+++ b/core/java/android/text/util/Linkify.java
@@ -0,0 +1,541 @@
+/*
+ * Copyright (C) 2007 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 android.text.util;
+
+import android.text.method.LinkMovementMethod;
+import android.text.method.MovementMethod;
+import android.text.style.URLSpan;
+import android.text.Spannable;
+import android.text.SpannableString;
+import android.text.Spanned;
+import android.webkit.WebView;
+import android.widget.TextView;
+
+import java.io.UnsupportedEncodingException;
+import java.net.URLEncoder;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ *  Linkify take a piece of text and a regular expression and turns all of the
+ *  regex matches in the text into clickable links.  This is particularly
+ *  useful for matching things like email addresses, web urls, etc. and making
+ *  them actionable.
+ *
+ *  Alone with the pattern that is to be matched, a url scheme prefix is also
+ *  required.  Any pattern match that does not begin with the supplied scheme
+ *  will have the scheme prepended to the matched text when the clickable url
+ *  is created.  For instance, if you are matching web urls you would supply
+ *  the scheme <code>http://</code>.  If the pattern matches example.com, which
+ *  does not have a url scheme prefix, the supplied scheme will be prepended to
+ *  create <code>http://example.com</code> when the clickable url link is
+ *  created.
+ */
+
+public class Linkify {
+    /**
+     *  Bit field indicating that web URLs should be matched in methods that
+     *  take an options mask
+     */
+    public static final int WEB_URLS = 0x01;
+
+    /**
+     *  Bit field indicating that email addresses should be matched in methods
+     *  that take an options mask
+     */
+    public static final int EMAIL_ADDRESSES = 0x02;
+
+    /**
+     *  Bit field indicating that phone numbers should be matched in methods that
+     *  take an options mask
+     */
+    public static final int PHONE_NUMBERS = 0x04;
+
+    /**
+     *  Bit field indicating that street addresses should be matched in methods that
+     *  take an options mask
+     */
+    public static final int MAP_ADDRESSES = 0x08;
+
+    /**
+     *  Bit mask indicating that all available patterns should be matched in
+     *  methods that take an options mask
+     */
+    public static final int ALL = WEB_URLS | EMAIL_ADDRESSES | PHONE_NUMBERS | MAP_ADDRESSES;
+
+    /**
+     * Don't treat anything with fewer than this many digits as a
+     * phone number.
+     */
+    private static final int PHONE_NUMBER_MINIMUM_DIGITS = 5;
+
+    /**
+     *  Filters out web URL matches that occur after an at-sign (@).  This is
+     *  to prevent turning the domain name in an email address into a web link.
+     */
+    public static final MatchFilter sUrlMatchFilter = new MatchFilter() {
+        public final boolean acceptMatch(CharSequence s, int start, int end) {
+            if (start == 0) {
+                return true;
+            }
+
+            if (s.charAt(start - 1) == '@') {
+                return false;
+            }
+
+            return true;
+        }
+    };
+
+    /**
+     *  Filters out URL matches that don't have enough digits to be a
+     *  phone number.
+     */
+    public static final MatchFilter sPhoneNumberMatchFilter = new MatchFilter() {
+        public final boolean acceptMatch(CharSequence s, int start, int end) {
+            int digitCount = 0;
+
+            for (int i = start; i < end; i++) {
+                if (Character.isDigit(s.charAt(i))) {
+                    digitCount++;
+                    if (digitCount >= PHONE_NUMBER_MINIMUM_DIGITS) {
+                        return true;
+                    }
+                }
+            }
+            return false;
+        }
+    };
+
+    /**
+     *  Transforms matched phone number text into something suitable
+     *  to be used in a tel: URL.  It does this by removing everything
+     *  but the digits and plus signs.  For instance:
+     *  &apos;+1 (919) 555-1212&apos;
+     *  becomes &apos;+19195551212&apos;
+     */
+    public static final TransformFilter sPhoneNumberTransformFilter = new TransformFilter() {
+        public final String transformUrl(final Matcher match, String url) {
+            return Regex.digitsAndPlusOnly(match);
+        }
+    };
+
+    /**
+     *  MatchFilter enables client code to have more control over
+     *  what is allowed to match and become a link, and what is not.
+     *
+     *  For example:  when matching web urls you would like things like
+     *  http://www.example.com to match, as well as just example.com itelf.
+     *  However, you would not want to match against the domain in
+     *  support@example.com.  So, when matching against a web url pattern you
+     *  might also include a MatchFilter that disallows the match if it is
+     *  immediately preceded by an at-sign (@).
+     */
+    public interface MatchFilter {
+        /**
+         *  Examines the character span matched by the pattern and determines
+         *  if the match should be turned into an actionable link.
+         *
+         *  @param s        The body of text against which the pattern
+         *                  was matched
+         *  @param start    The index of the first character in s that was
+         *                  matched by the pattern - inclusive
+         *  @param end      The index of the last character in s that was
+         *                  matched - exclusive
+         *
+         *  @return         Whether this match should be turned into a link
+         */
+        boolean acceptMatch(CharSequence s, int start, int end);
+    }
+
+    /**
+     *  TransformFilter enables client code to have more control over
+     *  how matched patterns are represented as URLs.
+     *
+     *  For example:  when converting a phone number such as (919)  555-1212
+     *  into a tel: URL the parentheses, white space, and hyphen need to be
+     *  removed to produce tel:9195551212.
+     */
+    public interface TransformFilter {
+        /**
+         *  Examines the matched text and either passes it through or uses the
+         *  data in the Matcher state to produce a replacement.
+         *
+         *  @param match    The regex matcher state that found this URL text
+         *  @param url      The text that was matched
+         *
+         *  @return         The transformed form of the URL
+         */
+        String transformUrl(final Matcher match, String url);
+    }
+
+    /**
+     *  Scans the text of the provided Spannable and turns all occurrences
+     *  of the link types indicated in the mask into clickable links.
+     *  If the mask is nonzero, it also removes any existing URLSpans
+     *  attached to the Spannable, to avoid problems if you call it
+     *  repeatedly on the same text.
+     */
+    public static final boolean addLinks(Spannable text, int mask) {
+        if (mask == 0) {
+            return false;
+        }
+
+        URLSpan[] old = text.getSpans(0, text.length(), URLSpan.class);
+
+        for (int i = old.length - 1; i >= 0; i--) {
+            text.removeSpan(old[i]);
+        }
+
+        ArrayList<LinkSpec> links = new ArrayList<LinkSpec>();
+
+        if ((mask & WEB_URLS) != 0) {
+            gatherLinks(links, text, Regex.WEB_URL_PATTERN,
+                new String[] { "http://", "https://" },
+                sUrlMatchFilter, null);
+        }
+
+        if ((mask & EMAIL_ADDRESSES) != 0) {
+            gatherLinks(links, text, Regex.EMAIL_ADDRESS_PATTERN,
+                new String[] { "mailto:" },
+                null, null);
+        }
+
+        if ((mask & PHONE_NUMBERS) != 0) {
+            gatherLinks(links, text, Regex.PHONE_PATTERN,
+                new String[] { "tel:" },
+                sPhoneNumberMatchFilter, sPhoneNumberTransformFilter);
+        }
+
+        if ((mask & MAP_ADDRESSES) != 0) {
+            gatherMapLinks(links, text);
+        }
+
+        pruneOverlaps(links);
+
+        if (links.size() == 0) {
+            return false;
+        }
+
+        for (LinkSpec link: links) {
+            applyLink(link.url, link.start, link.end, text);
+        }
+
+        return true;
+    }
+
+    /**
+     *  Scans the text of the provided TextView and turns all occurrences of
+     *  the link types indicated in the mask into clickable links.  If matches
+     *  are found the movement method for the TextView is set to
+     *  LinkMovementMethod.
+     */
+    public static final boolean addLinks(TextView text, int mask) {
+        if (mask == 0) {
+            return false;
+        }
+
+        CharSequence t = text.getText();
+
+        if (t instanceof Spannable) {
+            if (addLinks((Spannable) t, mask)) {
+                addLinkMovementMethod(text);
+                return true;
+            }
+
+            return false;
+        } else {
+            SpannableString s = SpannableString.valueOf(t);
+
+            if (addLinks(s, mask)) {
+                addLinkMovementMethod(text);
+                text.setText(s);
+
+                return true;
+            }
+
+            return false;
+        }
+    }
+
+    private static final void addLinkMovementMethod(TextView t) {
+        MovementMethod m = t.getMovementMethod();
+
+        if ((m == null) || !(m instanceof LinkMovementMethod)) {
+            if (t.getLinksClickable()) {
+                t.setMovementMethod(LinkMovementMethod.getInstance());
+            }
+        }
+    }
+
+    /**
+     *  Applies a regex to the text of a TextView turning the matches into
+     *  links.  If links are found then UrlSpans are applied to the link
+     *  text match areas, and the movement method for the text is changed
+     *  to LinkMovementMethod.
+     *
+     *  @param text         TextView whose text is to be marked-up with links
+     *  @param pattern      Regex pattern to be used for finding links
+     *  @param scheme       Url scheme string (eg <code>http://</code> to be
+     *                      prepended to the url of links that do not have
+     *                      a scheme specified in the link text
+     */
+    public static final void addLinks(TextView text, Pattern pattern, String scheme) {
+        addLinks(text, pattern, scheme, null, null);
+    }
+
+    /**
+     *  Applies a regex to the text of a TextView turning the matches into
+     *  links.  If links are found then UrlSpans are applied to the link
+     *  text match areas, and the movement method for the text is changed
+     *  to LinkMovementMethod.
+     *
+     *  @param text         TextView whose text is to be marked-up with links
+     *  @param p            Regex pattern to be used for finding links
+     *  @param scheme       Url scheme string (eg <code>http://</code> to be
+     *                      prepended to the url of links that do not have
+     *                      a scheme specified in the link text
+     *  @param matchFilter  The filter that is used to allow the client code
+     *                      additional control over which pattern matches are
+     *                      to be converted into links.
+     */
+    public static final void addLinks(TextView text, Pattern p, String scheme,
+            MatchFilter matchFilter, TransformFilter transformFilter) {
+        SpannableString s = SpannableString.valueOf(text.getText());
+
+        if (addLinks(s, p, scheme, matchFilter, transformFilter)) {
+            text.setText(s);
+            addLinkMovementMethod(text);
+        }
+    }
+
+    /**
+     *  Applies a regex to a Spannable turning the matches into
+     *  links.
+     *
+     *  @param text         Spannable whose text is to be marked-up with
+     *                      links
+     *  @param pattern      Regex pattern to be used for finding links
+     *  @param scheme       Url scheme string (eg <code>http://</code> to be
+     *                      prepended to the url of links that do not have
+     *                      a scheme specified in the link text
+     */
+    public static final boolean addLinks(Spannable text, Pattern pattern, String scheme) {
+        return addLinks(text, pattern, scheme, null, null);
+    }
+
+    /**
+     *  Applies a regex to a Spannable turning the matches into
+     *  links.
+     *
+     *  @param s            Spannable whose text is to be marked-up with
+     *                      links
+     *  @param p            Regex pattern to be used for finding links
+     *  @param scheme       Url scheme string (eg <code>http://</code> to be
+     *                      prepended to the url of links that do not have
+     *                      a scheme specified in the link text
+     *  @param matchFilter  The filter that is used to allow the client code
+     *                      additional control over which pattern matches are
+     *                      to be converted into links.
+     */
+    public static final boolean addLinks(Spannable s, Pattern p,
+            String scheme, MatchFilter matchFilter,
+            TransformFilter transformFilter) {
+        boolean hasMatches = false;
+        String prefix = (scheme == null) ? "" : scheme.toLowerCase();
+        Matcher m = p.matcher(s);
+
+        while (m.find()) {
+            int start = m.start();
+            int end = m.end();
+            boolean allowed = true;
+
+            if (matchFilter != null) {
+                allowed = matchFilter.acceptMatch(s, start, end);
+            }
+
+            if (allowed) {
+                String url = makeUrl(m.group(0), new String[] { prefix },
+                                     m, transformFilter);
+
+                applyLink(url, start, end, s);
+                hasMatches = true;
+            }
+        }
+
+        return hasMatches;
+    }
+
+    private static final void applyLink(String url, int start, int end, Spannable text) {
+        URLSpan span = new URLSpan(url);
+
+        text.setSpan(span, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+    }
+
+    private static final String makeUrl(String url, String[] prefixes,
+            Matcher m, TransformFilter filter) {
+        if (filter != null) {
+            url = filter.transformUrl(m, url);
+        }
+
+        boolean hasPrefix = false;
+        
+        for (int i = 0; i < prefixes.length; i++) {
+            if (url.regionMatches(true, 0, prefixes[i], 0,
+                                  prefixes[i].length())) {
+                hasPrefix = true;
+
+                // Fix capitalization if necessary
+                if (!url.regionMatches(false, 0, prefixes[i], 0,
+                                       prefixes[i].length())) {
+                    url = prefixes[i] + url.substring(prefixes[i].length());
+                }
+
+                break;
+            }
+        }
+
+        if (!hasPrefix) {
+            url = prefixes[0] + url;
+        }
+
+        return url;
+    }
+
+    private static final void gatherLinks(ArrayList<LinkSpec> links,
+            Spannable s, Pattern pattern, String[] schemes,
+            MatchFilter matchFilter, TransformFilter transformFilter) {
+        Matcher m = pattern.matcher(s);
+
+        while (m.find()) {
+            int start = m.start();
+            int end = m.end();
+
+            if (matchFilter == null || matchFilter.acceptMatch(s, start, end)) {
+                LinkSpec spec = new LinkSpec();
+                String url = makeUrl(m.group(0), schemes, m, transformFilter);
+
+                spec.url = url;
+                spec.start = start;
+                spec.end = end;
+
+                links.add(spec);
+            }
+        }
+    }
+
+    private static final void gatherMapLinks(ArrayList<LinkSpec> links, Spannable s) {
+        String string = s.toString();
+        String address;
+        int base = 0;
+
+        while ((address = WebView.findAddress(string)) != null) {
+            int start = string.indexOf(address);
+
+            if (start < 0) {
+                break;
+            }
+
+            LinkSpec spec = new LinkSpec();
+            int length = address.length();
+            int end = start + length;
+            
+            spec.start = base + start;
+            spec.end = base + end;
+            string = string.substring(end);
+            base += end;
+
+            String encodedAddress = null;
+
+            try {
+                encodedAddress = URLEncoder.encode(address,"UTF-8");
+            } catch (UnsupportedEncodingException e) {
+                continue;
+            }
+
+            spec.url = "geo:0,0?q=" + encodedAddress;
+            links.add(spec);
+        }
+    }
+
+    private static final void pruneOverlaps(ArrayList<LinkSpec> links) {
+        Comparator<LinkSpec>  c = new Comparator<LinkSpec>() {
+            public final int compare(LinkSpec a, LinkSpec b) {
+                if (a.start < b.start) {
+                    return -1;
+                }
+
+                if (a.start > b.start) {
+                    return 1;
+                }
+
+                if (a.end < b.end) {
+                    return 1;
+                }
+
+                if (a.end > b.end) {
+                    return -1;
+                }
+
+                return 0;
+            }
+
+            public final boolean equals(Object o) {
+                return false;
+            }
+        };
+
+        Collections.sort(links, c);
+
+        int len = links.size();
+        int i = 0;
+
+        while (i < len - 1) {
+            LinkSpec a = links.get(i);
+            LinkSpec b = links.get(i + 1);
+            int remove = -1;
+
+            if ((a.start <= b.start) && (a.end > b.start)) {
+                if (b.end <= a.end) {
+                    remove = i + 1;
+                } else if ((a.end - a.start) > (b.end - b.start)) {
+                    remove = i + 1;
+                } else if ((a.end - a.start) < (b.end - b.start)) {
+                    remove = i;
+                }
+
+                if (remove != -1) {
+                    links.remove(remove);
+                    len--;
+                    continue;
+                }
+
+            }
+
+            i++;
+        }
+    }
+}
+
+class LinkSpec {
+    String url;
+    int start;
+    int end;
+}
diff --git a/core/java/android/text/util/Regex.java b/core/java/android/text/util/Regex.java
new file mode 100644
index 0000000..4c128ad
--- /dev/null
+++ b/core/java/android/text/util/Regex.java
@@ -0,0 +1,204 @@
+/*
+ * Copyright (C) 2007 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 android.text.util;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * @hide
+ */
+public class Regex {
+    /**
+     *  Regular expression pattern to match all IANA top-level domains.
+     *  List accurate as of 2007/06/15.  List taken from:
+     *  http://data.iana.org/TLD/tlds-alpha-by-domain.txt
+     *  This pattern is auto-generated by //device/tools/make-iana-tld-pattern.py
+     */
+    public static final Pattern TOP_LEVEL_DOMAIN_PATTERN
+        = Pattern.compile(
+                "((aero|arpa|asia|a[cdefgilmnoqrstuwxz])"
+                + "|(biz|b[abdefghijmnorstvwyz])"
+                + "|(cat|com|coop|c[acdfghiklmnoruvxyz])"
+                + "|d[ejkmoz]"
+                + "|(edu|e[cegrstu])"
+                + "|f[ijkmor]"
+                + "|(gov|g[abdefghilmnpqrstuwy])"
+                + "|h[kmnrtu]"
+                + "|(info|int|i[delmnoqrst])"
+                + "|(jobs|j[emop])"
+                + "|k[eghimnrwyz]"
+                + "|l[abcikrstuvy]"
+                + "|(mil|mobi|museum|m[acdghklmnopqrstuvwxyz])"
+                + "|(name|net|n[acefgilopruz])"
+                + "|(org|om)"
+                + "|(pro|p[aefghklmnrstwy])"
+                + "|qa"
+                + "|r[eouw]"
+                + "|s[abcdeghijklmnortuvyz]"
+                + "|(tel|travel|t[cdfghjklmnoprtvwz])"
+                + "|u[agkmsyz]"
+                + "|v[aceginu]"
+                + "|w[fs]"
+                + "|y[etu]"
+                + "|z[amw])");
+
+    /**
+     *  Regular expression pattern to match RFC 1738 URLs
+     *  List accurate as of 2007/06/15.  List taken from:
+     *  http://data.iana.org/TLD/tlds-alpha-by-domain.txt
+     *  This pattern is auto-generated by //device/tools/make-iana-tld-pattern.py
+     */
+    public static final Pattern WEB_URL_PATTERN
+        = Pattern.compile(
+            "((?:(http|https|Http|Https):\\/\\/(?:(?:[a-zA-Z0-9\\$\\-\\_\\.\\+\\!\\*\\'\\(\\)"
+            + "\\,\\;\\?\\&\\=]|(?:\\%[a-fA-F0-9]{2}))+(?:\\:(?:[a-zA-Z0-9\\$\\-\\_"
+            + "\\.\\+\\!\\*\\'\\(\\)\\,\\;\\?\\&\\=]|(?:\\%[a-fA-F0-9]{2}))+)?\\@)?)?"
+            + "((?:(?:[a-zA-Z0-9][a-zA-Z0-9\\-]*\\.)+"   // named host
+            + "(?:"   // plus top level domain
+            + "(?:aero|arpa|asia|a[cdefgilmnoqrstuwxz])"
+            + "|(?:biz|b[abdefghijmnorstvwyz])"
+            + "|(?:cat|com|coop|c[acdfghiklmnoruvxyz])"
+            + "|d[ejkmoz]"
+            + "|(?:edu|e[cegrstu])"
+            + "|f[ijkmor]"
+            + "|(?:gov|g[abdefghilmnpqrstuwy])"
+            + "|h[kmnrtu]"
+            + "|(?:info|int|i[delmnoqrst])"
+            + "|(?:jobs|j[emop])"
+            + "|k[eghimnrwyz]"
+            + "|l[abcikrstuvy]"
+            + "|(?:mil|mobi|museum|m[acdghklmnopqrstuvwxyz])"
+            + "|(?:name|net|n[acefgilopruz])"
+            + "|(?:org|om)"
+            + "|(?:pro|p[aefghklmnrstwy])"
+            + "|qa"
+            + "|r[eouw]"
+            + "|s[abcdeghijklmnortuvyz]"
+            + "|(?:tel|travel|t[cdfghjklmnoprtvwz])"
+            + "|u[agkmsyz]"
+            + "|v[aceginu]"
+            + "|w[fs]"
+            + "|y[etu]"
+            + "|z[amw]))"
+            + "|(?:(?:25[0-5]|2[0-4]" // or ip address
+            + "[0-9]|[0-1][0-9]{2}|[1-9][0-9]|[1-9])\\.(?:25[0-5]|2[0-4][0-9]"
+            + "|[0-1][0-9]{2}|[1-9][0-9]|[1-9]|0)\\.(?:25[0-5]|2[0-4][0-9]|[0-1]"
+            + "[0-9]{2}|[1-9][0-9]|[1-9]|0)\\.(?:25[0-5]|2[0-4][0-9]|[0-1][0-9]{2}"
+            + "|[1-9][0-9]|[0-9])))"
+            + "(?:\\:\\d{1,5})?)" // plus option port number
+            + "(\\/(?:(?:[a-zA-Z0-9\\;\\/\\?\\:\\@\\&\\=\\#\\~"  // plus option query params
+            + "\\-\\.\\+\\!\\*\\'\\(\\)\\,\\_])|(?:\\%[a-fA-F0-9]{2}))*)?"
+            + "(?:\\b|$)"); // and finally, a word boundary or end of
+                            // input.  This is to stop foo.sure from
+                            // matching as foo.su
+
+    public static final Pattern IP_ADDRESS_PATTERN
+        = Pattern.compile(
+            "((25[0-5]|2[0-4][0-9]|[0-1][0-9]{2}|[1-9][0-9]|[1-9])\\.(25[0-5]|2[0-4]"
+            + "[0-9]|[0-1][0-9]{2}|[1-9][0-9]|[1-9]|0)\\.(25[0-5]|2[0-4][0-9]|[0-1]"
+            + "[0-9]{2}|[1-9][0-9]|[1-9]|0)\\.(25[0-5]|2[0-4][0-9]|[0-1][0-9]{2}"
+            + "|[1-9][0-9]|[0-9]))");
+
+    public static final Pattern DOMAIN_NAME_PATTERN
+        = Pattern.compile(
+            "(((([a-zA-Z0-9][a-zA-Z0-9\\-]*)*[a-zA-Z0-9]\\.)+"
+            + TOP_LEVEL_DOMAIN_PATTERN + ")|"
+            + IP_ADDRESS_PATTERN + ")");
+
+    public static final Pattern EMAIL_ADDRESS_PATTERN
+        = Pattern.compile(
+            "[a-zA-Z0-9\\+\\.\\_\\%\\-]+" +
+            "\\@" +
+            "[a-zA-Z0-9][a-zA-Z0-9\\-]*" +
+            "(" +
+                "\\." +
+                "[a-zA-Z0-9][a-zA-Z0-9\\-]*" +
+            ")+"
+        );
+
+    /**
+     * This pattern is intended for searching for things that look like they
+     * might be phone numbers in arbitrary text, not for validating whether
+     * something is in fact a phone number.  It will miss many things that
+     * are legitimate phone numbers.
+     * 
+     * <p> The pattern matches the following:
+     * <ul>
+     * <li>Optionally, a + sign followed immediately by one or more digits. Spaces, dots, or dashes
+     * may follow.
+     * <li>Optionally, sets of digits in parentheses, separated by spaces, dots, or dashes.
+     * <li>A string starting and ending with a digit, containing digits, spaces, dots, and/or dashes.
+     * </ul>
+     */
+    public static final Pattern PHONE_PATTERN
+        = Pattern.compile(                                  // sdd = space, dot, or dash
+                "(\\+[0-9]+[\\- \\.]*)?"                    // +<digits><sdd>*
+                + "(\\([0-9]+\\)[\\- \\.]*)?"               // (<digits>)<sdd>*
+                + "([0-9][0-9\\- \\.][0-9\\- \\.]+[0-9])"); // <digit><digit|sdd>+<digit> 
+
+    /**
+     *  Convenience method to take all of the non-null matching groups in a
+     *  regex Matcher and return them as a concatenated string.
+     *
+     *  @param matcher      The Matcher object from which grouped text will
+     *                      be extracted
+     *
+     *  @return             A String comprising all of the non-null matched
+     *                      groups concatenated together
+     */
+    public static final String concatGroups(Matcher matcher) {
+        StringBuilder b = new StringBuilder();
+        final int numGroups = matcher.groupCount();
+
+        for (int i = 1; i <= numGroups; i++) {
+            String s = matcher.group(i);
+
+            System.err.println("Group(" + i + ") : " + s);
+
+            if (s != null) {
+                b.append(s);
+            }
+        }
+
+        return b.toString();
+    }
+
+    /**
+     * Convenience method to return only the digits and plus signs
+     * in the matching string.
+     *
+     * @param matcher      The Matcher object from which digits and plus will
+     *                     be extracted
+     *
+     * @return             A String comprising all of the digits and plus in
+     *                     the match
+     */
+    public static final String digitsAndPlusOnly(Matcher matcher) {
+        StringBuilder buffer = new StringBuilder();
+        String matchingRegion = matcher.group();
+
+        for (int i = 0, size = matchingRegion.length(); i < size; i++) {
+            char character = matchingRegion.charAt(i);
+
+            if (character == '+' || Character.isDigit(character)) {
+                buffer.append(character);
+            }
+        }
+        return buffer.toString();
+    }
+}
diff --git a/core/java/android/text/util/Rfc822Token.java b/core/java/android/text/util/Rfc822Token.java
new file mode 100644
index 0000000..e6472df
--- /dev/null
+++ b/core/java/android/text/util/Rfc822Token.java
@@ -0,0 +1,172 @@
+/*
+ * 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 android.text.util;
+
+/**
+ * This class stores an RFC 822-like name, address, and comment,
+ * and provides methods to convert them to quoted strings.
+ */
+public class Rfc822Token {
+    private String mName, mAddress, mComment;
+
+    /**
+     * Creates a new Rfc822Token with the specified name, address,
+     * and comment.
+     */
+    public Rfc822Token(String name, String address, String comment) {
+        mName = name;
+        mAddress = address;
+        mComment = comment;
+    }
+
+    /**
+     * Returns the name part.
+     */
+    public String getName() {
+        return mName;
+    }
+
+    /**
+     * Returns the address part.
+     */
+    public String getAddress() {
+        return mAddress;
+    }
+
+    /**
+     * Returns the comment part.
+     */
+    public String getComment() {
+        return mComment;
+    }
+
+    /**
+     * Changes the name to the specified name.
+     */
+    public void setName(String name) {
+        mName = name;
+    }
+
+    /**
+     * Changes the address to the specified address.
+     */
+    public void setAddress(String address) {
+        mAddress = address;
+    }
+
+    /**
+     * Changes the comment to the specified comment.
+     */
+    public void setComment(String comment) {
+        mComment = comment;
+    }
+
+    /**
+     * Returns the name (with quoting added if necessary),
+     * the comment (in parentheses), and the address (in angle brackets).
+     * This should be suitable for inclusion in an RFC 822 address list.
+     */
+    public String toString() {
+        StringBuilder sb = new StringBuilder();
+
+        if (mName != null && mName.length() != 0) {
+            sb.append(quoteNameIfNecessary(mName));
+            sb.append(' ');
+        }
+
+        if (mComment != null && mComment.length() != 0) {
+            sb.append('(');
+            sb.append(quoteComment(mComment));
+            sb.append(") ");
+        }
+
+        if (mAddress != null && mAddress.length() != 0) {
+            sb.append('<');
+            sb.append(mAddress);
+            sb.append('>');
+        }
+
+        return sb.toString();
+    }
+
+    /**
+     * Returns the name, conservatively quoting it if there are any
+     * characters that are likely to cause trouble outside of a
+     * quoted string, or returning it literally if it seems safe.
+     */
+    public static String quoteNameIfNecessary(String name) {
+        int len = name.length();
+
+        for (int i = 0; i < len; i++) {
+            char c = name.charAt(i);
+
+            if (! ((c >= 'A' && i <= 'Z') ||
+                   (c >= 'a' && c <= 'z') ||
+                   (c == ' ') ||
+                   (c >= '0' && c <= '9'))) {
+                return '"' + quoteName(name) + '"';
+            }
+        }
+
+        return name;
+    }
+
+    /**
+     * Returns the name, with internal backslashes and quotation marks
+     * preceded by backslashes.  The outer quote marks themselves are not
+     * added by this method.
+     */
+    public static String quoteName(String name) {
+        StringBuilder sb = new StringBuilder();
+
+        int len = name.length();
+        for (int i = 0; i < len; i++) {
+            char c = name.charAt(i);
+
+            if (c == '\\' || c == '"') {
+                sb.append('\\');
+            }
+
+            sb.append(c);
+        }
+
+        return sb.toString();
+    }
+
+    /**
+     * Returns the comment, with internal backslashes and parentheses
+     * preceded by backslashes.  The outer parentheses themselves are
+     * not added by this method.
+     */
+    public static String quoteComment(String comment) {
+        int len = comment.length();
+        StringBuilder sb = new StringBuilder();
+
+        for (int i = 0; i < len; i++) {
+            char c = comment.charAt(i);
+
+            if (c == '(' || c == ')' || c == '\\') {
+                sb.append('\\');
+            }
+
+            sb.append(c);
+        }
+
+        return sb.toString();
+    }
+}
+
diff --git a/core/java/android/text/util/Rfc822Tokenizer.java b/core/java/android/text/util/Rfc822Tokenizer.java
new file mode 100644
index 0000000..d4e78b0
--- /dev/null
+++ b/core/java/android/text/util/Rfc822Tokenizer.java
@@ -0,0 +1,292 @@
+/*
+ * 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 android.text.util;
+
+import android.widget.MultiAutoCompleteTextView;
+
+import java.util.ArrayList;
+
+/**
+ * This class works as a Tokenizer for MultiAutoCompleteTextView for
+ * address list fields, and also provides a method for converting
+ * a string of addresses (such as might be typed into such a field)
+ * into a series of Rfc822Tokens.
+ */
+public class Rfc822Tokenizer implements MultiAutoCompleteTextView.Tokenizer {
+    /**
+     * This constructor will try to take a string like
+     * "Foo Bar (something) &lt;foo\@google.com&gt;,
+     * blah\@google.com (something)"
+     * and convert it into one or more Rfc822Tokens.
+     * It does *not* decode MIME encoded-words; charset conversion
+     * must already have taken place if necessary.
+     * It will try to be tolerant of broken syntax instead of
+     * returning an error.
+     */
+    public static Rfc822Token[] tokenize(CharSequence text) {
+        ArrayList<Rfc822Token> out = new ArrayList<Rfc822Token>();
+        StringBuilder name = new StringBuilder();
+        StringBuilder address = new StringBuilder();
+        StringBuilder comment = new StringBuilder();
+
+        int i = 0;
+        int cursor = text.length();
+
+        while (i < cursor) {
+            char c = text.charAt(i);
+
+            if (c == ',' || c == ';') {
+                i++;
+
+                while (i < cursor && text.charAt(i) == ' ') {
+                    i++;
+                }
+
+                crunch(name);
+
+                if (address.length() > 0) {
+                    out.add(new Rfc822Token(name.toString(),
+                                            address.toString(),
+                                            comment.toString()));
+                } else if (name.length() > 0) {
+                    out.add(new Rfc822Token(null,
+                                            name.toString(),
+                                            comment.toString()));
+                }
+
+                name.setLength(0);
+                address.setLength(0);
+                address.setLength(0);
+            } else if (c == '"') {
+                i++;
+
+                while (i < cursor) {
+                    c = text.charAt(i);
+
+                    if (c == '"') {
+                        i++;
+                        break;
+                    } else if (c == '\\') {
+                        name.append(text.charAt(i + 1));
+                        i += 2;
+                    } else {
+                        name.append(c);
+                        i++;
+                    }
+                }
+            } else if (c == '(') {
+                int level = 1;
+                i++;
+
+                while (i < cursor && level > 0) {
+                    c = text.charAt(i);
+
+                    if (c == ')') {
+                        if (level > 1) {
+                            comment.append(c);
+                        }
+
+                        level--;
+                        i++;
+                    } else if (c == '(') {
+                        comment.append(c);
+                        level++;
+                        i++;
+                    } else if (c == '\\') {
+                        comment.append(text.charAt(i + 1));
+                        i += 2;
+                    } else {
+                        comment.append(c);
+                        i++;
+                    }
+                }
+            } else if (c == '<') {
+                i++;
+
+                while (i < cursor) {
+                    c = text.charAt(i);
+
+                    if (c == '>') {
+                        i++;
+                        break;
+                    } else {
+                        address.append(c);
+                        i++;
+                    }
+                }
+            } else if (c == ' ') {
+                name.append('\0');
+                i++;
+            } else {
+                name.append(c);
+                i++;
+            }
+        }
+
+        crunch(name);
+
+        if (address.length() > 0) {
+            out.add(new Rfc822Token(name.toString(),
+                                    address.toString(),
+                                    comment.toString()));
+        } else if (name.length() > 0) {
+            out.add(new Rfc822Token(null,
+                                    name.toString(),
+                                    comment.toString()));
+        }
+
+        return out.toArray(new Rfc822Token[out.size()]);
+    }
+
+    private static void crunch(StringBuilder sb) {
+        int i = 0;
+        int len = sb.length();
+
+        while (i < len) {
+            char c = sb.charAt(i);
+
+            if (c == '\0') {
+                if (i == 0 || i == len - 1 ||
+                    sb.charAt(i - 1) == ' ' ||
+                    sb.charAt(i - 1) == '\0' ||
+                    sb.charAt(i + 1) == ' ' ||
+                    sb.charAt(i + 1) == '\0') {
+                    sb.deleteCharAt(i);
+                    len--;
+                } else {
+                    i++;
+                }
+            } else {
+                i++;
+            }
+        }
+
+        for (i = 0; i < len; i++) {
+            if (sb.charAt(i) == '\0') {
+                sb.setCharAt(i, ' ');
+            }
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public int findTokenStart(CharSequence text, int cursor) {
+        /*
+         * It's hard to search backward, so search forward until
+         * we reach the cursor.
+         */
+
+        int best = 0;
+        int i = 0;
+
+        while (i < cursor) {
+            i = findTokenEnd(text, i);
+
+            if (i < cursor) {
+                i++; // Skip terminating punctuation
+
+                while (i < cursor && text.charAt(i) == ' ') {
+                    i++;
+                }
+
+                if (i < cursor) {
+                    best = i;
+                }
+            }
+        }
+
+        return best;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public int findTokenEnd(CharSequence text, int cursor) {
+        int len = text.length();
+        int i = cursor;
+
+        while (i < len) {
+            char c = text.charAt(i);
+
+            if (c == ',' || c == ';') {
+                return i;
+            } else if (c == '"') {
+                i++;
+
+                while (i < len) {
+                    c = text.charAt(i);
+
+                    if (c == '"') {
+                        i++;
+                        break;
+                    } else if (c == '\\') {
+                        i += 2;
+                    } else {
+                        i++;
+                    }
+                }
+            } else if (c == '(') {
+                int level = 1;
+                i++;
+
+                while (i < len && level > 0) {
+                    c = text.charAt(i);
+
+                    if (c == ')') {
+                        level--;
+                        i++;
+                    } else if (c == '(') {
+                        level++;
+                        i++;
+                    } else if (c == '\\') {
+                        i += 2;
+                    } else {
+                        i++;
+                    }
+                }
+            } else if (c == '<') {
+                i++;
+
+                while (i < len) {
+                    c = text.charAt(i);
+
+                    if (c == '>') {
+                        i++;
+                        break;
+                    } else {
+                        i++;
+                    }
+                }
+            } else {
+                i++;
+            }
+        }
+
+        return i;
+    }
+
+    /**
+     * Terminates the specified address with a comma and space.
+     * This assumes that the specified text already has valid syntax.
+     * The Adapter subclass's convertToString() method must make that
+     * guarantee.
+     */
+    public CharSequence terminateToken(CharSequence text) {
+        return text + ", ";
+    }
+}
diff --git a/core/java/android/text/util/Rfc822Validator.java b/core/java/android/text/util/Rfc822Validator.java
new file mode 100644
index 0000000..6a6bf69
--- /dev/null
+++ b/core/java/android/text/util/Rfc822Validator.java
@@ -0,0 +1,132 @@
+/*
+ * 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 android.text.util;
+
+import android.text.TextUtils;
+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/core/java/android/text/util/package.html b/core/java/android/text/util/package.html
new file mode 100644
index 0000000..d9312aa2
--- /dev/null
+++ b/core/java/android/text/util/package.html
@@ -0,0 +1,6 @@
+<HTML>
+<BODY>
+Utilities for converting identifiable text strings into clickable links 
+and creating RFC 822-type message (SMTP) tokens.
+</BODY>
+</HTML>
\ No newline at end of file
diff --git a/core/java/android/util/AndroidException.java b/core/java/android/util/AndroidException.java
new file mode 100644
index 0000000..a767ea1
--- /dev/null
+++ b/core/java/android/util/AndroidException.java
@@ -0,0 +1,34 @@
+/*
+ * 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 android.util;
+
+/**
+ * Base class for all checked exceptions thrown by the Android frameworks.
+ */
+public class AndroidException extends Exception {
+    public AndroidException() {
+    }
+
+    public AndroidException(String name) {
+        super(name);
+    }
+
+    public AndroidException(Exception cause) {
+        super(cause);
+    }
+};
+
diff --git a/core/java/android/util/AndroidRuntimeException.java b/core/java/android/util/AndroidRuntimeException.java
new file mode 100644
index 0000000..4ed17bc
--- /dev/null
+++ b/core/java/android/util/AndroidRuntimeException.java
@@ -0,0 +1,34 @@
+/*
+ * 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 android.util;
+
+/**
+ * Base class for all unchecked exceptions thrown by the Android frameworks.
+ */
+public class AndroidRuntimeException extends RuntimeException {
+    public AndroidRuntimeException() {
+    }
+
+    public AndroidRuntimeException(String name) {
+        super(name);
+    }
+
+    public AndroidRuntimeException(Exception cause) {
+        super(cause);
+    }
+};
+
diff --git a/core/java/android/util/AttributeSet.java b/core/java/android/util/AttributeSet.java
new file mode 100644
index 0000000..01a7ad4
--- /dev/null
+++ b/core/java/android/util/AttributeSet.java
@@ -0,0 +1,269 @@
+/*
+ * 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 android.util;
+
+
+/**
+ * A collection of attributes, as found associated with a tag in an XML
+ * document.  Often you will not want to use this interface directly, instead
+ * passing it to {@link android.content.res.Resources.Theme#obtainStyledAttributes(AttributeSet, int[], int, int)
+ * Resources.Theme.obtainStyledAttributes()}
+ * which will take care of parsing the attributes for you.  In particular,
+ * the Resources API will convert resource references (attribute values such as
+ * "@string/my_label" in the original XML) to the desired type
+ * for you; if you use AttributeSet directly then you will need to manually
+ * check for resource references
+ * (with {@link #getAttributeResourceValue(int, int)}) and do the resource
+ * lookup yourself if needed.  Direct use of AttributeSet also prevents the
+ * application of themes and styles when retrieving attribute values.
+ * 
+ * <p>This interface provides an efficient mechanism for retrieving
+ * data from compiled XML files, which can be retrieved for a particular
+ * XmlPullParser through {@link Xml#asAttributeSet
+ * Xml.getAttributeSet()}.  Normally this will return an implementation
+ * of the interface that works on top of a generic XmlPullParser, however it
+ * is more useful in conjunction with compiled XML resources:
+ * 
+ * <pre>
+ * XmlPullParser parser = resources.getXml(myResouce);
+ * AttributeSet attributes = Xml.getAttributeSet(parser);</pre>
+ * 
+ * <p>The implementation returned here, unlike using
+ * the implementation on top of a generic XmlPullParser,
+ * is highly optimized by retrieving pre-computed information that was
+ * generated by aapt when compiling your resources.  For example,
+ * the {@link #getAttributeFloatValue(int, float)} method returns a floating
+ * point number previous stored in the compiled resource instead of parsing
+ * at runtime the string originally in the XML file.
+ * 
+ * <p>This interface also provides additional information contained in the
+ * compiled XML resource that is not available in a normal XML file, such
+ * as {@link #getAttributeNameResource(int)} which returns the resource
+ * identifier associated with a particular XML attribute name.
+ */
+public interface AttributeSet {
+    public int getAttributeCount();
+    public String getAttributeName(int index);
+    public String getAttributeValue(int index);
+    public String getAttributeValue(String namespace, String name);
+    public String getPositionDescription();
+
+    /**
+     * Return the resource ID associated with the given attribute name.  This
+     * will be the identifier for an attribute resource, which can be used by
+     * styles.  Returns 0 if there is no resource associated with this
+     * attribute.
+     * 
+     * <p>Note that this is different than {@link #getAttributeResourceValue}
+     * in that it returns a resource identifier for the attribute name; the
+     * other method returns this attribute's value as a resource identifier.
+     * 
+     * @param index Index of the desired attribute, 0...count-1.
+     * 
+     * @return The resource identifier, 0 if none.
+     */
+    public int getAttributeNameResource(int index);
+
+    /**
+     * Return the index of the value of 'attribute' in the list 'options'.
+     * 
+     * @param attribute Name of attribute to retrieve.
+     * @param options List of strings whose values we are checking against.
+     * @param defaultValue Value returned if attribute doesn't exist or no
+     *                     match is found.
+     * 
+     * @return Index in to 'options' or defaultValue.
+     */
+    public int getAttributeListValue(String namespace, String attribute,
+                                     String[] options, int defaultValue);
+
+    /**
+     * Return the boolean value of 'attribute'.
+     * 
+     * @param attribute The attribute to retrieve.
+     * @param defaultValue What to return if the attribute isn't found.
+     * 
+     * @return Resulting value.
+     */
+    public boolean getAttributeBooleanValue(String namespace, String attribute,
+                                            boolean defaultValue);
+
+    /**
+     * Return the value of 'attribute' as a resource identifier.
+     * 
+     * <p>Note that this is different than {@link #getAttributeNameResource}
+     * in that it returns a the value contained in this attribute as a
+     * resource identifier (i.e., a value originally of the form
+     * "@package:type/resource"); the other method returns a resource
+     * identifier that identifies the name of the attribute.
+     * 
+     * @param attribute The attribute to retrieve.
+     * @param defaultValue What to return if the attribute isn't found.
+     * 
+     * @return Resulting value.
+     */
+    public int getAttributeResourceValue(String namespace, String attribute,
+                                         int defaultValue);
+
+    /**
+     * Return the integer value of 'attribute'.
+     * 
+     * @param attribute The attribute to retrieve.
+     * @param defaultValue What to return if the attribute isn't found.
+     * 
+     * @return Resulting value.
+     */
+    public int getAttributeIntValue(String namespace, String attribute,
+                                    int defaultValue);
+
+    /**
+     * Return the boolean value of 'attribute' that is formatted as an
+     * unsigned value.  In particular, the formats 0xn...n and #n...n are
+     * handled.
+     * 
+     * @param attribute The attribute to retrieve.
+     * @param defaultValue What to return if the attribute isn't found.
+     * 
+     * @return Resulting value.
+     */
+    public int getAttributeUnsignedIntValue(String namespace, String attribute,
+                                            int defaultValue);
+
+    /**
+     * Return the float value of 'attribute'.
+     * 
+     * @param attribute The attribute to retrieve.
+     * @param defaultValue What to return if the attribute isn't found.
+     * 
+     * @return Resulting value.
+     */
+    public float getAttributeFloatValue(String namespace, String attribute,
+                                        float defaultValue);
+
+    /**
+     * Return the index of the value of attribute at 'index' in the list 
+     * 'options'. 
+     * 
+     * @param index Index of the desired attribute, 0...count-1.
+     * @param options List of strings whose values we are checking against.
+     * @param defaultValue Value returned if attribute doesn't exist or no
+     *                     match is found.
+     * 
+     * @return Index in to 'options' or defaultValue.
+     */
+    public int getAttributeListValue(int index,
+                                     String[] options, int defaultValue);
+
+    /**
+     * Return the boolean value of attribute at 'index'.
+     * 
+     * @param index Index of the desired attribute, 0...count-1.
+     * @param defaultValue What to return if the attribute isn't found.
+     * 
+     * @return Resulting value.
+     */
+    public boolean getAttributeBooleanValue(int index,
+                                            boolean defaultValue);
+
+    /**
+     * Return the value of attribute at 'index' as a resource identifier.
+     * 
+     * <p>Note that this is different than {@link #getAttributeNameResource}
+     * in that it returns a the value contained in this attribute as a
+     * resource identifier (i.e., a value originally of the form
+     * "@package:type/resource"); the other method returns a resource
+     * identifier that identifies the name of the attribute.
+     * 
+     * @param index Index of the desired attribute, 0...count-1.
+     * @param defaultValue What to return if the attribute isn't found.
+     * 
+     * @return Resulting value.
+     */
+    public int getAttributeResourceValue(int index,
+                                         int defaultValue);
+
+    /**
+     * Return the integer value of attribute at 'index'.
+     * 
+     * @param index Index of the desired attribute, 0...count-1.
+     * @param defaultValue What to return if the attribute isn't found.
+     * 
+     * @return Resulting value.
+     */
+    public int getAttributeIntValue(int index,
+                                    int defaultValue);
+
+    /**
+     * Return the integer value of attribute at 'index' that is formatted as an
+     * unsigned value.  In particular, the formats 0xn...n and #n...n are
+     * handled.
+     * 
+     * @param index Index of the desired attribute, 0...count-1.
+     * @param defaultValue What to return if the attribute isn't found.
+     * 
+     * @return Resulting value.
+     */
+    public int getAttributeUnsignedIntValue(int index,
+                                            int defaultValue);
+
+    /**
+     * Return the float value of attribute at 'index'.
+     * 
+     * @param index Index of the desired attribute, 0...count-1.
+     * @param defaultValue What to return if the attribute isn't found.
+     * 
+     * @return Resulting value.
+     */
+    public float getAttributeFloatValue(int index,
+                                        float defaultValue);
+
+    /**
+     * Return the value of the "id" attribute or null if there is not one.
+     * Equivalent to getAttributeValue(null, "id").
+     * 
+     * @return The id attribute's value or null.
+     */
+    public String getIdAttribute();
+
+    /**
+     * Return the value of the "class" attribute or null if there is not one.
+     * Equivalent to getAttributeValue(null, "class").
+     * 
+     * @return The class attribute's value or null.
+     */
+    public String getClassAttribute();
+
+    /**
+     * Return the integer value of the "id" attribute or defaultValue if there
+     * is none.
+     * Equivalent to getAttributeResourceValue(null, "id", defaultValue);
+     *
+     * @param defaultValue What to return if the "id" attribute isn't found.
+     * @return int Resulting value.
+     */
+    public int getIdAttributeResourceValue(int defaultValue);
+
+    /**
+
+     * Return the value of the "style" attribute or 0 if there is not one.
+     * Equivalent to getAttributeResourceValue(null, "style").
+     * 
+     * @return The style attribute's resource identifier or 0.
+     */
+    public int getStyleAttribute();
+}
+
diff --git a/core/java/android/util/Config.java b/core/java/android/util/Config.java
new file mode 100644
index 0000000..c0b27f8
--- /dev/null
+++ b/core/java/android/util/Config.java
@@ -0,0 +1,51 @@
+/* device/vmlibs-config/release/android/util/Config.java
+**
+** Copyright 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 android.util;
+
+/**
+ * Build configuration.  The constants in this class vary depending
+ * on release vs. debug build.  This is the configuration for release builds.
+ * {@more}
+ */
+public final class Config
+{
+    /**
+     * Is this a release build?
+     */
+    public static final boolean RELEASE = true;
+
+    /**
+     * Is this a debug build?
+     */
+    public static final boolean DEBUG = false;
+
+    /**
+     * Is profiling enabled?
+     */
+    public static final boolean PROFILE = false;
+    
+    /**
+     * Are VERBOSE log messages enabled?
+     */
+    public static final boolean LOGV = false;
+
+    /**
+     * Are DEBUG log messages enabled?
+     */
+    public static final boolean LOGD = true;
+}
diff --git a/core/java/android/util/DayOfMonthCursor.java b/core/java/android/util/DayOfMonthCursor.java
new file mode 100644
index 0000000..393b98e
--- /dev/null
+++ b/core/java/android/util/DayOfMonthCursor.java
@@ -0,0 +1,187 @@
+/*
+ * Copyright (C) 2007 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 android.util;
+
+/**
+ * Helps control and display a month view of a calendar that has a current
+ * selected day.
+ * <ul>
+ *   <li>Keeps track of current month, day, year</li>
+ *   <li>Keeps track of current cursor position (row, column)</li>
+ *   <li>Provides methods to help display the calendar</li>
+ *   <li>Provides methods to move the cursor up / down / left / right.</li>
+ * </ul>
+ *
+ * This should be used by anyone who presents a month view to users and wishes
+ * to behave consistently with other widgets and apps; if we ever change our
+ * mind about when to flip the month, we can change it here only.
+ *
+ * @hide
+ */
+public class DayOfMonthCursor extends MonthDisplayHelper {
+
+    private int mRow;
+    private int mColumn;
+
+    /**
+     * @param year The initial year.
+     * @param month The initial month.
+     * @param dayOfMonth The initial dayOfMonth.
+     * @param weekStartDay What dayOfMonth of the week the week should start,
+     *   in terms of {@link java.util.Calendar} constants such as
+     *   {@link java.util.Calendar#SUNDAY}.
+     */
+    public DayOfMonthCursor(int year, int month, int dayOfMonth, int weekStartDay) {
+        super(year, month, weekStartDay);
+        mRow = getRowOf(dayOfMonth);
+        mColumn = getColumnOf(dayOfMonth);
+    }
+
+
+    public int getSelectedRow() {
+        return mRow;
+    }
+
+    public int getSelectedColumn() {
+        return mColumn;
+    }
+    
+    public void setSelectedRowColumn(int row, int col) {
+        mRow = row;
+        mColumn = col;
+    }
+
+    public int getSelectedDayOfMonth() {
+        return getDayAt(mRow, mColumn);
+    }
+
+    /**
+     * @return 0 if the selection is in the current month, otherwise -1 or +1
+     * depending on whether the selection is in the first or last row.
+     */
+    public int getSelectedMonthOffset() {
+        if (isWithinCurrentMonth(mRow, mColumn)) {
+            return 0;
+        }
+        if (mRow == 0) {
+            return -1;
+        }
+        return 1;
+    }
+    
+    public void setSelectedDayOfMonth(int dayOfMonth) {
+        mRow = getRowOf(dayOfMonth);
+        mColumn = getColumnOf(dayOfMonth);
+    }
+    
+    public boolean isSelected(int row, int column) {
+        return (mRow == row) && (mColumn == column);
+    }
+
+    /**
+     * Move up one box, potentially flipping to the previous month.
+     * @return Whether the month was flipped to the previous month
+     *   due to the move.
+     */
+    public boolean up() {
+        if (isWithinCurrentMonth(mRow - 1, mColumn)) {
+            // within current month, just move up
+            mRow--;
+            return false;
+        }
+        // flip back to previous month, same column, first position within month
+        previousMonth();
+        mRow = 5;
+        while(!isWithinCurrentMonth(mRow, mColumn)) {
+            mRow--;
+        }
+        return true;
+    }
+
+    /**
+     * Move down one box, potentially flipping to the next month.
+     * @return Whether the month was flipped to the next month
+     *   due to the move.
+     */
+    public boolean down() {
+        if (isWithinCurrentMonth(mRow + 1, mColumn)) {
+            // within current month, just move down
+            mRow++;
+            return false;
+        }
+        // flip to next month, same column, first position within month
+        nextMonth();
+        mRow = 0;
+        while (!isWithinCurrentMonth(mRow, mColumn)) {
+            mRow++;
+        }
+        return true;
+    }
+
+    /**
+     * Move left one box, potentially flipping to the previous month.
+     * @return Whether the month was flipped to the previous month
+     *   due to the move.
+     */
+    public boolean left() {
+        if (mColumn == 0) {
+            mRow--;
+            mColumn = 6;
+        } else {
+            mColumn--;
+        }
+
+        if (isWithinCurrentMonth(mRow, mColumn)) {
+            return false;
+        }
+
+        // need to flip to last day of previous month
+        previousMonth();
+        int lastDay = getNumberOfDaysInMonth();
+        mRow = getRowOf(lastDay);
+        mColumn = getColumnOf(lastDay);
+        return true;
+    }
+
+    /**
+     * Move right one box, potentially flipping to the next month.
+     * @return Whether the month was flipped to the next month
+     *   due to the move.
+     */
+    public boolean right() {
+        if (mColumn == 6) {
+            mRow++;
+            mColumn = 0;
+        } else {
+            mColumn++;
+        }
+
+        if (isWithinCurrentMonth(mRow, mColumn)) {
+            return false;
+        }
+
+        // need to flip to first day of next month
+        nextMonth();
+        mRow = 0;
+        mColumn = 0;
+        while (!isWithinCurrentMonth(mRow, mColumn)) {
+            mColumn++;
+        }
+        return true;
+    }
+
+}
diff --git a/core/java/android/util/DebugUtils.java b/core/java/android/util/DebugUtils.java
new file mode 100644
index 0000000..1c5d669
--- /dev/null
+++ b/core/java/android/util/DebugUtils.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2007 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 android.util;
+
+import java.lang.reflect.Method;
+import java.lang.reflect.InvocationTargetException;
+
+/**
+ * <p>Various utilities for debugging and logging.</p> 
+ */
+public class DebugUtils {
+    /**
+     * <p>Filters objects against the <code>ANDROID_OBJECT_FILTER</code>
+     * environment variable. This environment variable can filter objects
+     * based on their class name and attribute values.</p>
+     *
+     * <p>Here is the syntax for <code>ANDROID_OBJECT_FILTER</code>:</p>
+     *
+     * <p><code>ClassName@attribute1=value1@attribute2=value2...</code></p>
+     *
+     * <p>Examples:</p>
+     * <ul>
+     * <li>Select TextView instances: <code>TextView</code></li>
+     * <li>Select TextView instances of text "Loading" and bottom offset of 22:
+     * <code>TextView@text=Loading.*@bottom=22</code></li>
+     * </ul>
+     *
+     * <p>The class name and the values are regular expressions.</p>
+     *
+     * <p>This class is useful for debugging and logging purpose:</p>
+     * <pre>
+     * if (Config.DEBUG) {
+     *   if (DebugUtils.isObjectSelected(childView) && Config.LOGV) {
+     *     Log.v(TAG, "Object " + childView + " logged!");
+     *   }
+     * }
+     * </pre>
+     *
+     * <p><strong>NOTE</strong>: This method is very expensive as it relies
+     * heavily on regular expressions and reflection. Calls to this method
+     * should always be stripped out of the release binaries and avoided
+     * as much as possible in debug mode.</p>
+     *
+     * @param object any object to match against the ANDROID_OBJECT_FILTER
+     *        environement variable
+     * @return true if object is selected by the ANDROID_OBJECT_FILTER
+     *         environment variable, false otherwise
+     */
+    public static boolean isObjectSelected(Object object) {
+        boolean match = false;
+        String s = System.getenv("ANDROID_OBJECT_FILTER");
+        if (s != null && s.length() > 0) {
+            String[] selectors = s.split("@");
+            // first selector == class name
+            if (object.getClass().getSimpleName().matches(selectors[0])) {
+                // check potential attributes
+                for (int i = 1; i < selectors.length; i++) {
+                    String[] pair = selectors[i].split("=");
+                    Class<?> klass = object.getClass();
+                    try {
+                        Method declaredMethod = null;
+                        Class<?> parent = klass;
+                        do {
+                            declaredMethod = parent.getDeclaredMethod("get" +
+                                    pair[0].substring(0, 1).toUpperCase() +
+                                    pair[0].substring(1),
+                                    (Class[]) null);
+                        } while ((parent = klass.getSuperclass()) != null &&
+                                declaredMethod == null);
+
+                        if (declaredMethod != null) {
+                            Object value = declaredMethod
+                                    .invoke(object, (Object[])null);
+                            match |= (value != null ?
+                                    value.toString() : "null").matches(pair[1]);
+                        }
+                    } catch (NoSuchMethodException e) {
+                        e.printStackTrace();
+                    } catch (IllegalAccessException e) {
+                        e.printStackTrace();
+                    } catch (InvocationTargetException e) {
+                        e.printStackTrace();
+                    }
+                }
+            }
+        }
+        return match;
+    }
+
+}
diff --git a/core/java/android/util/DisplayMetrics.java b/core/java/android/util/DisplayMetrics.java
new file mode 100644
index 0000000..9de4cbe
--- /dev/null
+++ b/core/java/android/util/DisplayMetrics.java
@@ -0,0 +1,98 @@
+/*
+ * 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 android.util;
+
+import android.os.*;
+
+
+/**
+ * A structure describing general information about a display, such as its
+ * size, density, and font scaling.
+ */
+public class DisplayMetrics {
+    /**
+     * The reference density used throughout the system.
+     * 
+     * @hide Pending API council approval
+     */
+    public static final int DEFAULT_DENSITY = 160;
+
+    private static final int sLcdDensity = SystemProperties.getInt("ro.sf.lcd_density",
+            DEFAULT_DENSITY);
+
+    /**
+     * The absolute width of the display in pixels.
+     */
+    public int widthPixels;
+    /**
+     * The absolute height of the display in pixels.
+     */
+    public int heightPixels;
+    /**
+     * The logical density of the display.  This is a scaling factor for the
+     * Density Independent Pixel unit, where one DIP is one pixel on an
+     * approximately 160 dpi screen (for example a 240x320, 1.5"x2" screen), 
+     * providing the baseline of the system's display. Thus on a 160dpi screen 
+     * this density value will be 1; on a 106 dpi screen it would be .75; etc.
+     *  
+     * <p>This value does not exactly follow the real screen size (as given by 
+     * {@link #xdpi} and {@link #ydpi}, but rather is used to scale the size of
+     * the overall UI in steps based on gross changes in the display dpi.  For 
+     * example, a 240x320 screen will have a density of 1 even if its width is 
+     * 1.8", 1.3", etc. However, if the screen resolution is increased to 
+     * 320x480 but the screen size remained 1.5"x2" then the density would be 
+     * increased (probably to 1.5).
+     *
+     * @see #DEFAULT_DENSITY
+     */
+    public float density;
+    /**
+     * A scaling factor for fonts displayed on the display.  This is the same
+     * as {@link #density}, except that it may be adjusted in smaller
+     * increments at runtime based on a user preference for the font size.
+     */
+    public float scaledDensity;
+    /**
+     * The exact physical pixels per inch of the screen in the X dimension.
+     */
+    public float xdpi;
+    /**
+     * The exact physical pixels per inch of the screen in the Y dimension.
+     */
+    public float ydpi;
+
+    public DisplayMetrics() {
+    }
+    
+    public void setTo(DisplayMetrics o) {
+        widthPixels = o.widthPixels;
+        heightPixels = o.heightPixels;
+        density = o.density;
+        scaledDensity = o.scaledDensity;
+        xdpi = o.xdpi;
+        ydpi = o.ydpi;
+    }
+    
+    public void setToDefaults() {
+        widthPixels = 0;
+        heightPixels = 0;
+        density = sLcdDensity / (float) DEFAULT_DENSITY;
+        scaledDensity = density;
+        xdpi = sLcdDensity;
+        ydpi = sLcdDensity;
+    }
+}
diff --git a/core/java/android/util/EventLog.java b/core/java/android/util/EventLog.java
new file mode 100644
index 0000000..24b4f73
--- /dev/null
+++ b/core/java/android/util/EventLog.java
@@ -0,0 +1,288 @@
+/*
+ * Copyright (C) 2007 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 android.util;
+
+import com.google.android.collect.Lists;
+
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * {@hide}
+ * Dynamically defined (in terms of event types), space efficient (i.e. "tight") event logging
+ * to help instrument code for large scale stability and performance monitoring.
+ *
+ * Note that this class contains all static methods.  This is done for efficiency reasons.
+ *
+ * Events for the event log are self-describing binary data structures.  They start with a 20 byte
+ * header (generated automatically) which contains all of the following in order:
+ *
+ * <ul>
+ * <li> Payload length: 2 bytes - length of the non-header portion </li>
+ * <li> Padding: 2 bytes - no meaning at this time </li>
+ * <li> Timestamp:
+ *   <ul>
+ *   <li> Seconds: 4 bytes - seconds since Epoch </li>
+ *   <li> Nanoseconds: 4 bytes - plus extra nanoseconds </li>
+ *   </ul></li>
+ * <li> Process ID: 4 bytes - matching {@link android.os.Process#myPid} </li>
+ * <li> Thread ID: 4 bytes - matching {@link android.os.Process#myTid} </li>
+ * </li>
+ * </ul>
+ *
+ * The above is followed by a payload, comprised of the following:
+ * <ul>
+ * <li> Tag: 4 bytes - unique integer used to identify a particular event.  This number is also
+ *                     used as a key to map to a string that can be displayed by log reading tools.
+ * </li>
+ * <li> Type: 1 byte - can be either {@link #INT}, {@link #LONG}, {@link #STRING},
+ *                     or {@link #LIST}. </li>
+ * <li> Event log value: the size and format of which is one of:
+ *   <ul>
+ *   <li> INT: 4 bytes </li>
+ *   <li> LONG: 8 bytes </li>
+ *   <li> STRING:
+ *     <ul>
+ *     <li> Size of STRING: 4 bytes </li>
+ *     <li> The string:  n bytes as specified in the size fields above. </li>
+ *     </ul></li>
+ *   <li> {@link List LIST}:
+ *     <ul>
+ *     <li> Num items: 1 byte </li>
+ *     <li> N value payloads, where N is the number of items specified above. </li>
+ *     </ul></li>
+ *   </ul>
+ * </li>
+ * <li> '\n': 1 byte - an automatically generated newline, used to help detect and recover from log
+ *                     corruption and enable stansard unix tools like grep, tail and wc to operate
+ *                     on event logs. </li>
+ * </ul>
+ *
+ * Note that all output is done in the endian-ness of the device (as determined
+ * by {@link ByteOrder#nativeOrder()}).
+ */
+
+public class EventLog {
+
+    // Value types
+    public static final byte INT    = 0;
+    public static final byte LONG   = 1;
+    public static final byte STRING = 2;
+    public static final byte LIST   = 3;
+
+    /**
+     * An immutable tuple used to log a heterogeneous set of loggable items.
+     * The items can be Integer, Long, String, or {@link List}.
+     * The maximum number of items is 127
+     */
+    public static final class List {
+        private Object[] mItems;
+
+        /**
+         * Get a particular tuple item
+         * @param pos The position of the item in the tuple
+         */
+        public final Object getItem(int pos) {
+            return mItems[pos];
+        }
+
+        /**
+         * Get the number of items in the tuple.
+         */
+        public final byte getNumItems() {
+            return (byte) mItems.length;
+        }
+
+        /**
+         * Create a new tuple.
+         * @param items The items to create the tuple with, as varargs.
+         * @throws IllegalArgumentException if the arguments are too few (0),
+         *         too many, or aren't loggable types.
+         */
+        public List(Object... items) throws IllegalArgumentException {
+            if (items.length > Byte.MAX_VALUE) {
+                throw new IllegalArgumentException(
+                        "A List must have fewer than "
+                        + Byte.MAX_VALUE + " items in it.");
+            }
+            if (items.length < 1) {
+                throw new IllegalArgumentException(
+                        "A List must have at least one item in it.");
+            }
+            for (int i = 0; i < items.length; i++) {
+                final Object item = items[i];
+                if (item == null) {
+                    // Would be nice to be able to write null strings...
+                    items[i] = "";
+                } else if (!(item instanceof List ||
+                      item instanceof String ||
+                      item instanceof Integer ||
+                      item instanceof Long)) {
+                    throw new IllegalArgumentException(
+                            "Attempt to create a List with illegal item type.");
+                }
+            }
+            this.mItems = items;
+        }
+    }
+
+    /**
+     * A previously logged event read from the logs.
+     */
+    public static final class Event {
+        private final ByteBuffer mBuffer;
+
+        // Layout of event log entry received from kernel.
+        private static final int LENGTH_OFFSET = 0;
+        private static final int PROCESS_OFFSET = 4;
+        private static final int THREAD_OFFSET = 8;
+        private static final int SECONDS_OFFSET = 12;
+        private static final int NANOSECONDS_OFFSET = 16;
+
+        private static final int PAYLOAD_START = 20;
+        private static final int TAG_OFFSET = 20;
+        private static final int DATA_START = 24;
+
+        /** @param data containing event, read from the system */
+        public Event(byte[] data) {
+            mBuffer = ByteBuffer.wrap(data);
+            mBuffer.order(ByteOrder.nativeOrder());
+        }
+
+        public int getProcessId() {
+            return mBuffer.getInt(PROCESS_OFFSET);
+        }
+
+        public int getThreadId() {
+            return mBuffer.getInt(THREAD_OFFSET);
+        }
+
+        public long getTimeNanos() {
+            return mBuffer.getInt(SECONDS_OFFSET) * 1000000000l
+                    + mBuffer.getInt(NANOSECONDS_OFFSET);
+        }
+
+        public int getTag() {
+            return mBuffer.getInt(TAG_OFFSET);
+        }
+
+        /** @return one of Integer, Long, String, or List. */
+        public synchronized Object getData() {
+            mBuffer.limit(PAYLOAD_START + mBuffer.getShort(LENGTH_OFFSET));
+            mBuffer.position(DATA_START);  // Just after the tag.
+            return decodeObject();
+        }
+
+        /** @return the loggable item at the current position in mBuffer. */
+        private Object decodeObject() {
+            if (mBuffer.remaining() < 1) return null;
+            switch (mBuffer.get()) {
+            case INT:
+                if (mBuffer.remaining() < 4) return null;
+                return mBuffer.getInt();
+
+            case LONG:
+                if (mBuffer.remaining() < 8) return null;
+                return mBuffer.getLong();
+
+            case STRING:
+                try {
+                    if (mBuffer.remaining() < 4) return null;
+                    int length = mBuffer.getInt();
+                    if (length < 0 || mBuffer.remaining() < length) return null;
+                    int start = mBuffer.position();
+                    mBuffer.position(start + length);
+                    return new String(mBuffer.array(), start, length, "UTF-8");
+                } catch (UnsupportedEncodingException e) {
+                    throw new RuntimeException(e);  // UTF-8 is guaranteed.
+                }
+
+            case LIST:
+                if (mBuffer.remaining() < 1) return null;
+                int length = mBuffer.get();
+                if (length <= 0) return null;
+                Object[] array = new Object[length];
+                for (int i = 0; i < length; ++i) {
+                    array[i] = decodeObject();
+                    if (array[i] == null) return null;
+                }
+                return new List(array);
+
+            default:
+                return null;
+            }
+        }
+    }
+
+    // We assume that the native methods deal with any concurrency issues.
+
+    /**
+     * Send an event log message.
+     * @param tag An event identifer
+     * @param value A value to log
+     * @return The number of bytes written
+     */
+    public static native int writeEvent(int tag, int value);
+
+    /**
+     * Send an event log message.
+     * @param tag An event identifer
+     * @param value A value to log
+     * @return The number of bytes written
+     */
+    public static native int writeEvent(int tag, long value);
+
+    /**
+     * Send an event log message.
+     * @param tag An event identifer
+     * @param str A value to log
+     * @return The number of bytes written
+     */
+    public static native int writeEvent(int tag, String str);
+
+    /**
+     * Send an event log message.
+     * @param tag An event identifer
+     * @param list A {@link List} to log
+     * @return The number of bytes written
+     */
+    public static native int writeEvent(int tag, List list);
+
+    /**
+     * Send an event log message.
+     * @param tag An event identifer
+     * @param list A list of values to log
+     * @return The number of bytes written
+     */
+    public static int writeEvent(int tag, Object... list) {
+        return writeEvent(tag, new List(list));
+    }
+
+    /**
+     * Read events from the log, filtered by type.
+     * @param tags to search for
+     * @param output container to add events into
+     * @throws IOException if something goes wrong reading events
+     */
+    public static native void readEvents(int[] tags, Collection<Event> output)
+            throws IOException;
+}
diff --git a/core/java/android/util/EventLogTags.java b/core/java/android/util/EventLogTags.java
new file mode 100644
index 0000000..be905e3
--- /dev/null
+++ b/core/java/android/util/EventLogTags.java
@@ -0,0 +1,90 @@
+/*
+ * 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 android.util;
+
+import android.util.Log;
+
+import java.io.BufferedReader;
+import java.io.FileReader;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/** Parsed representation of /etc/event-log-tags. */
+public class EventLogTags {
+    private final static String TAG = "EventLogTags";
+
+    private final static String TAGS_FILE = "/etc/event-log-tags";
+
+    public static class Description {
+        public final int mTag;
+        public final String mName;
+        // TODO: Parse parameter descriptions when anyone has a use for them.
+
+        Description(int tag, String name) {
+            mTag = tag;
+            mName = name;
+        }
+    }
+
+    private final static Pattern COMMENT_PATTERN = Pattern.compile(
+            "^\\s*(#.*)?$");
+
+    private final static Pattern TAG_PATTERN = Pattern.compile(
+            "^\\s*(\\d+)\\s+(\\w+)\\s*(\\(.*\\))?\\s*$");
+
+    private final HashMap<String, Description> mNameMap =
+            new HashMap<String, Description>();
+
+    private final HashMap<Integer, Description> mTagMap =
+            new HashMap<Integer, Description>();
+
+    public EventLogTags() throws IOException {
+        this(new BufferedReader(new FileReader(TAGS_FILE), 256));
+    }
+
+    public EventLogTags(BufferedReader input) throws IOException {
+        String line;
+        while ((line = input.readLine()) != null) {
+            Matcher m = COMMENT_PATTERN.matcher(line);
+            if (m.matches()) continue;
+
+            m = TAG_PATTERN.matcher(line);
+            if (m.matches()) {
+                try {
+                    int tag = Integer.parseInt(m.group(1));
+                    Description d = new Description(tag, m.group(2));
+                    mNameMap.put(d.mName, d);
+                    mTagMap.put(d.mTag, d);
+                } catch (NumberFormatException e) {
+                    Log.e(TAG, "Error in event log tags entry: " + line, e);
+                }
+            } else {
+                Log.e(TAG, "Can't parse event log tags entry: " + line);
+            }
+        }
+    }
+
+    public Description get(String name) {
+        return mNameMap.get(name);
+    }
+
+    public Description get(int tag) {
+        return mTagMap.get(tag);
+    }
+}
diff --git a/core/java/android/util/FloatMath.java b/core/java/android/util/FloatMath.java
new file mode 100644
index 0000000..6216638
--- /dev/null
+++ b/core/java/android/util/FloatMath.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2007 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 android.util;
+
+/**
+ * Math routines similar to those found in {@link java.lang.Math}. Performs
+ * computations on {@code float} values directly without incurring the overhead
+ * of conversions to and from {@code double}.
+ *
+ * <p>On one platform, {@code FloatMath.sqrt(100)} executes in one third of the
+ * time required by {@code java.lang.Math.sqrt(100)}.</p>
+ */
+public class FloatMath {
+
+    /** Prevents instantiation. */
+    private FloatMath() {}
+
+    /**
+     * Returns the float conversion of the most positive (i.e. closest to
+     * positive infinity) integer value which is less than the argument.
+     *
+     * @param value to be converted
+     * @return the floor of value
+     */
+    public static native float floor(float value);
+
+    /**
+     * Returns the float conversion of the most negative (i.e. closest to
+     * negative infinity) integer value which is greater than the argument.
+     *
+     * @param value to be converted
+     * @return the ceiling of value
+     */
+    public static native float ceil(float value);
+
+    /**
+     * Returns the closest float approximation of the sine of the argument.
+     *
+     * @param angle to compute the cosine of, in radians
+     * @return the sine of angle
+     */
+    public static native float sin(float angle);
+
+    /**
+     * Returns the closest float approximation of the cosine of the argument.
+     *
+     * @param angle to compute the cosine of, in radians
+     * @return the cosine of angle
+     */
+    public static native float cos(float angle);
+
+    /**
+     * Returns the closest float approximation of the square root of the
+     * argument.
+     *
+     * @param value to compute sqrt of
+     * @return the square root of value
+     */
+    public static native float sqrt(float value);
+}
diff --git a/core/java/android/util/Log.java b/core/java/android/util/Log.java
new file mode 100644
index 0000000..2572679
--- /dev/null
+++ b/core/java/android/util/Log.java
@@ -0,0 +1,247 @@
+/*
+ * 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 android.util;
+
+import com.android.internal.os.RuntimeInit;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+
+/**
+ * API for sending log output.
+ *
+ * <p>Generally, use the Log.v() Log.d() Log.i() Log.w() and Log.e()
+ * methods.
+ *
+ * <p>The order in terms of verbosity, from least to most is
+ * ERROR, WARN, INFO, DEBUG, VERBOSE.  Verbose should never be compiled
+ * into an application except during development.  Debug logs are compiled
+ * in but stripped at runtime.  Error, warning and info logs are always kept.
+ *
+ * <p><b>Tip:</b> A good convention is to declare a <code>TAG</code> constant
+ * in your class:
+ *
+ * <pre>private static final String TAG = "MyActivity";</pre>
+ *
+ * and use that in subsequent calls to the log methods.
+ * </p>
+ *
+ * <p><b>Tip:</b> Don't forget that when you make a call like
+ * <pre>Log.v(TAG, "index=" + i);</pre>
+ * that when you're building the string to pass into Log.d, the compiler uses a
+ * StringBuilder and at least three allocations occur: the StringBuilder
+ * itself, the buffer, and the String object.  Realistically, there is also
+ * another buffer allocation and copy, and even more pressure on the gc.
+ * That means that if your log message is filtered out, you might be doing
+ * significant work and incurring significant overhead.
+ */
+public final class Log {
+
+    /**
+     * Priority constant for the println method; use Log.v.
+     */
+    public static final int VERBOSE = 2;
+
+    /**
+     * Priority constant for the println method; use Log.d.
+     */
+    public static final int DEBUG = 3;
+
+    /**
+     * Priority constant for the println method; use Log.i.
+     */
+    public static final int INFO = 4;
+
+    /**
+     * Priority constant for the println method; use Log.w.
+     */
+    public static final int WARN = 5;
+
+    /**
+     * Priority constant for the println method; use Log.e.
+     */
+    public static final int ERROR = 6;
+
+    /**
+     * Priority constant for the println method.
+     */
+    public static final int ASSERT = 7;
+
+    private Log() {
+    }
+
+    /**
+     * Send a {@link #VERBOSE} log message.
+     * @param tag Used to identify the source of a log message.  It usually identifies
+     *        the class or activity where the log call occurs.
+     * @param msg The message you would like logged.
+     */
+    public static int v(String tag, String msg) {
+        return println(VERBOSE, tag, msg);
+    }
+
+    /**
+     * Send a {@link #VERBOSE} log message and log the exception.
+     * @param tag Used to identify the source of a log message.  It usually identifies
+     *        the class or activity where the log call occurs.
+     * @param msg The message you would like logged.
+     * @param tr An exception to log
+     */
+    public static int v(String tag, String msg, Throwable tr) {
+        return println(VERBOSE, tag, msg + '\n' + getStackTraceString(tr));
+    }
+
+    /**
+     * Send a {@link #DEBUG} log message.
+     * @param tag Used to identify the source of a log message.  It usually identifies
+     *        the class or activity where the log call occurs.
+     * @param msg The message you would like logged.
+     */
+    public static int d(String tag, String msg) {
+        return println(DEBUG, tag, msg);
+    }
+
+    /**
+     * Send a {@link #DEBUG} log message and log the exception.
+     * @param tag Used to identify the source of a log message.  It usually identifies
+     *        the class or activity where the log call occurs.
+     * @param msg The message you would like logged.
+     * @param tr An exception to log
+     */
+    public static int d(String tag, String msg, Throwable tr) {
+        return println(DEBUG, tag, msg + '\n' + getStackTraceString(tr));
+    }
+
+    /**
+     * Send an {@link #INFO} log message.
+     * @param tag Used to identify the source of a log message.  It usually identifies
+     *        the class or activity where the log call occurs.
+     * @param msg The message you would like logged.
+     */
+    public static int i(String tag, String msg) {
+        return println(INFO, tag, msg);
+    }
+
+    /**
+     * Send a {@link #INFO} log message and log the exception.
+     * @param tag Used to identify the source of a log message.  It usually identifies
+     *        the class or activity where the log call occurs.
+     * @param msg The message you would like logged.
+     * @param tr An exception to log
+     */
+    public static int i(String tag, String msg, Throwable tr) {
+        return println(INFO, tag, msg + '\n' + getStackTraceString(tr));
+    }
+
+    /**
+     * Send a {@link #WARN} log message.
+     * @param tag Used to identify the source of a log message.  It usually identifies
+     *        the class or activity where the log call occurs.
+     * @param msg The message you would like logged.
+     */
+    public static int w(String tag, String msg) {
+        return println(WARN, tag, msg);
+    }
+
+    /**
+     * Send a {@link #WARN} log message and log the exception.
+     * @param tag Used to identify the source of a log message.  It usually identifies
+     *        the class or activity where the log call occurs.
+     * @param msg The message you would like logged.
+     * @param tr An exception to log
+     */
+    public static int w(String tag, String msg, Throwable tr) {
+        return println(WARN, tag, msg + '\n' + getStackTraceString(tr));
+    }
+
+    /**
+     * Checks to see whether or not a log for the specified tag is loggable at the specified level.
+     * 
+     *  The default level of any tag is set to INFO. This means that any level above and including
+     *  INFO will be logged. Before you make any calls to a logging method you should check to see
+     *  if your tag should be logged. You can change the default level by setting a system property:
+     *      'setprop log.tag.&lt;YOUR_LOG_TAG> &lt;LEVEL>'
+     *  Where level is either VERBOSE, DEBUG, INFO, WARN, ERROR, ASSERT, or SUPPRESS. SUPRESS will 
+     *  turn off all logging for your tag. You can also create a local.prop file that with the
+     *  following in it:
+     *      'log.tag.&lt;YOUR_LOG_TAG>=&lt;LEVEL>'
+     *  and place that in /data/local.prop.
+     *  
+     * @param tag The tag to check.
+     * @param level The level to check.
+     * @return Whether or not that this is allowed to be logged.
+     * @throws IllegalArgumentException is thrown if the tag.length() > 23.
+     */
+    public static native boolean isLoggable(String tag, int level);
+        
+    /*
+     * Send a {@link #WARN} log message and log the exception.
+     * @param tag Used to identify the source of a log message.  It usually identifies
+     *        the class or activity where the log call occurs.
+     * @param tr An exception to log
+     */
+    public static int w(String tag, Throwable tr) {
+        return println(WARN, tag, getStackTraceString(tr));
+    }
+
+    /**
+     * Send an {@link #ERROR} log message.
+     * @param tag Used to identify the source of a log message.  It usually identifies
+     *        the class or activity where the log call occurs.
+     * @param msg The message you would like logged.
+     */
+    public static int e(String tag, String msg) {
+        return println(ERROR, tag, msg);
+    }
+
+    /**
+     * Send a {@link #ERROR} log message and log the exception.
+     * @param tag Used to identify the source of a log message.  It usually identifies
+     *        the class or activity where the log call occurs.
+     * @param msg The message you would like logged.
+     * @param tr An exception to log
+     */
+    public static int e(String tag, String msg, Throwable tr) {
+        int r = println(ERROR, tag, msg + '\n' + getStackTraceString(tr));
+        RuntimeInit.reportException(tag, tr, false);  // asynchronous
+        return r;
+    }
+
+    /**
+     * Handy function to get a loggable stack trace from a Throwable
+     * @param tr An exception to log
+     */
+    public static String getStackTraceString(Throwable tr) {
+        if (tr == null) {
+            return "";
+        }
+        StringWriter sw = new StringWriter();
+        PrintWriter pw = new PrintWriter(sw);
+        tr.printStackTrace(pw);
+        return sw.toString();
+    }
+
+    /**
+     * Low-level logging call.
+     * @param priority The priority/type of this log message
+     * @param tag Used to identify the source of a log message.  It usually identifies
+     *        the class or activity where the log call occurs.
+     * @param msg The message you would like logged.
+     * @return The number of bytes written.
+     */
+    public static native int println(int priority, String tag, String msg);
+}
diff --git a/core/java/android/util/LogPrinter.java b/core/java/android/util/LogPrinter.java
new file mode 100644
index 0000000..643b8d3
--- /dev/null
+++ b/core/java/android/util/LogPrinter.java
@@ -0,0 +1,47 @@
+/*
+ * 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 android.util;
+
+/**
+ * Implementation of a {@link android.util.Printer} that sends its output
+ * to the system log.
+ */
+public class LogPrinter implements Printer {
+    private final int mPriority;
+    private final String mTag;
+    
+    /**
+     * Create a new Printer that sends to the log with the given priority
+     * and tag.
+     *
+     * @param priority The desired log priority:
+     * {@link android.util.Log#VERBOSE Log.VERBOSE},
+     * {@link android.util.Log#DEBUG Log.DEBUG},
+     * {@link android.util.Log#INFO Log.INFO},
+     * {@link android.util.Log#WARN Log.WARN}, or
+     * {@link android.util.Log#ERROR Log.ERROR}.
+     * @param tag A string tag to associate with each printed log statement.
+     */
+    public LogPrinter(int priority, String tag) {
+        mPriority = priority;
+        mTag = tag;
+    }
+    
+    public void println(String x) {
+        Log.println(mPriority, mTag, x);
+    }
+}
diff --git a/core/java/android/util/MonthDisplayHelper.java b/core/java/android/util/MonthDisplayHelper.java
new file mode 100644
index 0000000..c3f13fc
--- /dev/null
+++ b/core/java/android/util/MonthDisplayHelper.java
@@ -0,0 +1,213 @@
+/*
+ * Copyright (C) 2007 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 android.util;
+
+import java.util.Calendar;
+
+/**
+ * Helps answer common questions that come up when displaying a month in a
+ * 6 row calendar grid format.
+ *
+ * Not thread safe.
+ */
+public class MonthDisplayHelper {
+
+    // display pref
+    private final int mWeekStartDay;
+
+    // holds current month, year, helps compute display
+    private Calendar mCalendar;
+
+    // cached computed stuff that helps with display
+    private int mNumDaysInMonth;
+    private int mNumDaysInPrevMonth;
+    private int mOffset;
+
+
+    /**
+     * @param year The year.
+     * @param month The month.
+     * @param weekStartDay What day of the week the week should start.
+     */
+    public MonthDisplayHelper(int year, int month, int weekStartDay) {
+
+        if (weekStartDay < Calendar.SUNDAY || weekStartDay > Calendar.SATURDAY) {
+            throw new IllegalArgumentException();
+        }
+        mWeekStartDay = weekStartDay;
+
+        mCalendar = Calendar.getInstance();
+        mCalendar.set(Calendar.YEAR, year);
+        mCalendar.set(Calendar.MONTH, month);
+        mCalendar.set(Calendar.DAY_OF_MONTH, 1);
+        mCalendar.set(Calendar.HOUR_OF_DAY, 0);
+        mCalendar.set(Calendar.MINUTE, 0);
+        mCalendar.set(Calendar.SECOND, 0);
+        mCalendar.getTimeInMillis();
+
+        recalculate();
+    }
+
+
+    public MonthDisplayHelper(int year, int month) {
+        this(year, month, Calendar.SUNDAY);
+    }
+
+
+    public int getYear() {
+        return mCalendar.get(Calendar.YEAR);
+    }
+
+    public int getMonth() {
+        return mCalendar.get(Calendar.MONTH);
+    }
+
+
+    public int getWeekStartDay() {
+        return mWeekStartDay;
+    }
+
+    /**
+     * @return The first day of the month using a constants such as
+     *   {@link java.util.Calendar#SUNDAY}.
+     */
+    public int getFirstDayOfMonth() {
+        return mCalendar.get(Calendar.DAY_OF_WEEK);
+    }
+
+    /**
+     * @return The number of days in the month.
+     */
+    public int getNumberOfDaysInMonth() {
+        return mNumDaysInMonth;
+    }
+
+
+    /**
+     * @return The offset from displaying everything starting on the very first
+     *   box.  For example, if the calendar is set to display the first day of
+     *   the week as Sunday, and the month starts on a Wednesday, the offset is 3.
+     */
+    public int getOffset() {
+        return mOffset;
+    }
+
+
+    /**
+     * @param row Which row (0-5).
+     * @return the digits of the month to display in one
+     * of the 6 rows of a calendar month display.
+     */
+    public int[] getDigitsForRow(int row) {
+        if (row < 0 || row > 5) {
+            throw new IllegalArgumentException("row " + row
+                    + " out of range (0-5)");
+        }
+
+        int [] result = new int[7];
+        for (int column = 0; column < 7; column++) {
+            result[column] = getDayAt(row, column);
+        }
+
+        return result;
+    }
+
+    /**
+     * @param row The row, 0-5, starting from the top.
+     * @param column The column, 0-6, starting from the left.
+     * @return The day at a particular row, column
+     */
+    public int getDayAt(int row, int column) {
+
+        if (row == 0 && column < mOffset) {
+            return mNumDaysInPrevMonth + column - mOffset + 1;
+        }
+
+        int day = 7 * row + column - mOffset + 1;
+
+        return (day > mNumDaysInMonth) ?
+                day - mNumDaysInMonth : day;
+    }
+
+    /**
+     * @return Which row day is in.
+     */
+    public int getRowOf(int day) {
+        return (day + mOffset - 1) / 7;
+    }
+
+    /**
+     * @return Which column day is in.
+     */
+    public int getColumnOf(int day) {
+        return (day + mOffset - 1) % 7;
+    }
+
+    /**
+     * Decrement the month.
+     */
+    public void previousMonth() {
+        mCalendar.add(Calendar.MONTH, -1);
+        recalculate();
+    }
+
+    /**
+     * Increment the month.
+     */
+    public void nextMonth() {
+        mCalendar.add(Calendar.MONTH, 1);
+        recalculate();
+    }
+
+    /**
+     * @return Whether the row and column fall within the month.
+     */
+    public boolean isWithinCurrentMonth(int row, int column) {
+
+        if (row < 0 || column < 0 || row > 5 || column > 6) {
+            return false;
+        }
+
+        if (row == 0 && column < mOffset) {
+            return false;
+        }
+
+        int day = 7 * row + column - mOffset + 1;
+        if (day > mNumDaysInMonth) {
+            return false;
+        }
+        return true;
+    }
+
+
+    // helper method that recalculates cached values based on current month / year
+    private void recalculate() {
+
+        mNumDaysInMonth = mCalendar.getActualMaximum(Calendar.DAY_OF_MONTH);
+
+        mCalendar.add(Calendar.MONTH, -1);
+        mNumDaysInPrevMonth = mCalendar.getActualMaximum(Calendar.DAY_OF_MONTH);
+        mCalendar.add(Calendar.MONTH, 1);
+
+        int firstDayOfMonth = getFirstDayOfMonth();
+        int offset = firstDayOfMonth - mWeekStartDay;
+        if (offset < 0) {
+            offset += 7;
+        }
+        mOffset = offset;
+    }
+}
diff --git a/core/java/android/util/PrintStreamPrinter.java b/core/java/android/util/PrintStreamPrinter.java
new file mode 100644
index 0000000..1c11f15
--- /dev/null
+++ b/core/java/android/util/PrintStreamPrinter.java
@@ -0,0 +1,40 @@
+/*
+ * 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 android.util;
+
+import java.io.PrintStream;
+
+/**
+ * Implementation of a {@link android.util.Printer} that sends its output
+ * to a {@link java.io.PrintStream}.
+ */
+public class PrintStreamPrinter implements Printer {
+    private final PrintStream mPS;
+    
+    /**
+     * Create a new Printer that sends to a PrintWriter object.
+     * 
+     * @param pw The PrintWriter where you would like output to go.
+     */
+    public PrintStreamPrinter(PrintStream pw) {
+        mPS = pw;
+    }
+    
+    public void println(String x) {
+        mPS.println(x);
+    }
+}
diff --git a/core/java/android/util/PrintWriterPrinter.java b/core/java/android/util/PrintWriterPrinter.java
new file mode 100644
index 0000000..82c4d03
--- /dev/null
+++ b/core/java/android/util/PrintWriterPrinter.java
@@ -0,0 +1,40 @@
+/*
+ * 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 android.util;
+
+import java.io.PrintWriter;
+
+/**
+ * Implementation of a {@link android.util.Printer} that sends its output
+ * to a {@link java.io.PrintWriter}.
+ */
+public class PrintWriterPrinter implements Printer {
+    private final PrintWriter mPW;
+    
+    /**
+     * Create a new Printer that sends to a PrintWriter object.
+     * 
+     * @param pw The PrintWriter where you would like output to go.
+     */
+    public PrintWriterPrinter(PrintWriter pw) {
+        mPW = pw;
+    }
+    
+    public void println(String x) {
+        mPW.println(x);
+    }
+}
diff --git a/core/java/android/util/Printer.java b/core/java/android/util/Printer.java
new file mode 100644
index 0000000..595cf70
--- /dev/null
+++ b/core/java/android/util/Printer.java
@@ -0,0 +1,31 @@
+/*
+ * 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 android.util;
+
+/**
+ * Simple interface for printing text, allowing redirection to various
+ * targets.  Standard implementations are {@link android.util.LogPrinter},
+ * {@link android.util.StringBuilderPrinter}, and
+ * {@link android.util.PrintWriterPrinter}.
+ */
+public interface Printer {
+    /**
+     * Write a line of text to the output.  There is no need to terminate
+     * the given string with a newline.
+     */
+    void println(String x);
+}
diff --git a/core/java/android/util/SparseArray.java b/core/java/android/util/SparseArray.java
new file mode 100644
index 0000000..1c8b330
--- /dev/null
+++ b/core/java/android/util/SparseArray.java
@@ -0,0 +1,340 @@
+/*
+ * 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 android.util;
+
+import com.android.internal.util.ArrayUtils;
+
+/**
+ * SparseArrays map integers to Objects.  Unlike a normal array of Objects,
+ * there can be gaps in the indices.  It is intended to be more efficient
+ * than using a HashMap to map Integers to Objects.
+ */
+public class SparseArray<E> {
+    private static final Object DELETED = new Object();
+    private boolean mGarbage = false;
+
+    /**
+     * Creates a new SparseArray containing no mappings.
+     */
+    public SparseArray() {
+        this(10);
+    }
+
+    /**
+     * Creates a new SparseArray containing no mappings that will not
+     * require any additional memory allocation to store the specified
+     * number of mappings.
+     */
+    public SparseArray(int initialCapacity) {
+        initialCapacity = ArrayUtils.idealIntArraySize(initialCapacity);
+
+        mKeys = new int[initialCapacity];
+        mValues = new Object[initialCapacity];
+        mSize = 0;
+    }
+
+    /**
+     * Gets the Object mapped from the specified key, or <code>null</code>
+     * if no such mapping has been made.
+     */
+    public E get(int key) {
+        return get(key, null);
+    }
+
+    /**
+     * Gets the Object mapped from the specified key, or the specified Object
+     * if no such mapping has been made.
+     */
+    public E get(int key, E valueIfKeyNotFound) {
+        int i = binarySearch(mKeys, 0, mSize, key);
+
+        if (i < 0 || mValues[i] == DELETED) {
+            return valueIfKeyNotFound;
+        } else {
+            return (E) mValues[i];
+        }
+    }
+
+    /**
+     * Removes the mapping from the specified key, if there was any.
+     */
+    public void delete(int key) {
+        int i = binarySearch(mKeys, 0, mSize, key);
+
+        if (i >= 0) {
+            if (mValues[i] != DELETED) {
+                mValues[i] = DELETED;
+                mGarbage = true;
+            }
+        }
+    }
+
+    /**
+     * Alias for {@link #delete(int)}.
+     */
+    public void remove(int key) {
+        delete(key);
+    }
+
+    private void gc() {
+        // Log.e("SparseArray", "gc start with " + mSize);
+
+        int n = mSize;
+        int o = 0;
+        int[] keys = mKeys;
+        Object[] values = mValues;
+
+        for (int i = 0; i < n; i++) {
+            Object val = values[i];
+
+            if (val != DELETED) {
+                if (i != o) {
+                    keys[o] = keys[i];
+                    values[o] = val;
+                }
+
+                o++;
+            }
+        }
+
+        mGarbage = false;
+        mSize = o;
+
+        // Log.e("SparseArray", "gc end with " + mSize);
+    }
+
+    /**
+     * Adds a mapping from the specified key to the specified value,
+     * replacing the previous mapping from the specified key if there
+     * was one.
+     */
+    public void put(int key, E value) {
+        int i = binarySearch(mKeys, 0, mSize, key);
+
+        if (i >= 0) {
+            mValues[i] = value;
+        } else {
+            i = ~i;
+
+            if (i < mSize && mValues[i] == DELETED) {
+                mKeys[i] = key;
+                mValues[i] = value;
+                return;
+            }
+
+            if (mGarbage && mSize >= mKeys.length) {
+                gc();
+
+                // Search again because indices may have changed.
+                i = ~binarySearch(mKeys, 0, mSize, key);
+            }
+
+            if (mSize >= mKeys.length) {
+                int n = ArrayUtils.idealIntArraySize(mSize + 1);
+
+                int[] nkeys = new int[n];
+                Object[] nvalues = new Object[n];
+
+                // Log.e("SparseArray", "grow " + mKeys.length + " to " + n);
+                System.arraycopy(mKeys, 0, nkeys, 0, mKeys.length);
+                System.arraycopy(mValues, 0, nvalues, 0, mValues.length);
+
+                mKeys = nkeys;
+                mValues = nvalues;
+            }
+
+            if (mSize - i != 0) {
+                // Log.e("SparseArray", "move " + (mSize - i));
+                System.arraycopy(mKeys, i, mKeys, i + 1, mSize - i);
+                System.arraycopy(mValues, i, mValues, i + 1, mSize - i);
+            }
+
+            mKeys[i] = key;
+            mValues[i] = value;
+            mSize++;
+        }
+    }
+
+    /**
+     * Returns the number of key-value mappings that this SparseArray
+     * currently stores.
+     */
+    public int size() {
+        if (mGarbage) {
+            gc();
+        }
+
+        return mSize;
+    }
+
+    /**
+     * Given an index in the range <code>0...size()-1</code>, returns
+     * the key from the <code>index</code>th key-value mapping that this
+     * SparseArray stores.  
+     */
+    public int keyAt(int index) {
+        if (mGarbage) {
+            gc();
+        }
+
+        return mKeys[index];
+    }
+    
+    /**
+     * Given an index in the range <code>0...size()-1</code>, returns
+     * the value from the <code>index</code>th key-value mapping that this
+     * SparseArray stores.  
+     */
+    public E valueAt(int index) {
+        if (mGarbage) {
+            gc();
+        }
+
+        return (E) mValues[index];
+    }
+
+    /**
+     * Given an index in the range <code>0...size()-1</code>, sets a new
+     * value for the <code>index</code>th key-value mapping that this
+     * SparseArray stores.  
+     */
+    public void setValueAt(int index, E value) {
+        if (mGarbage) {
+            gc();
+        }
+
+        mValues[index] = value;
+    }
+    
+    /**
+     * Returns the index for which {@link #keyAt} would return the
+     * specified key, or a negative number if the specified
+     * key is not mapped.
+     */
+    public int indexOfKey(int key) {
+        if (mGarbage) {
+            gc();
+        }
+
+        return binarySearch(mKeys, 0, mSize, key);
+    }
+
+    /**
+     * Returns an index for which {@link #valueAt} would return the
+     * specified key, or a negative number if no keys map to the
+     * specified value.
+     * Beware that this is a linear search, unlike lookups by key,
+     * and that multiple keys can map to the same value and this will
+     * find only one of them.
+     */
+    public int indexOfValue(E value) {
+        if (mGarbage) {
+            gc();
+        }
+
+        for (int i = 0; i < mSize; i++)
+            if (mValues[i] == value)
+                return i;
+
+        return -1;
+    }
+
+    /**
+     * Removes all key-value mappings from this SparseArray.
+     */
+    public void clear() {
+        int n = mSize;
+        Object[] values = mValues;
+
+        for (int i = 0; i < n; i++) {
+            values[i] = null;
+        }
+
+        mSize = 0;
+        mGarbage = false;
+    }
+
+    /**
+     * Puts a key/value pair into the array, optimizing for the case where
+     * the key is greater than all existing keys in the array.
+     */
+    public void append(int key, E value) {
+        if (mSize != 0 && key <= mKeys[mSize - 1]) {
+            put(key, value);
+            return;
+        }
+
+        if (mGarbage && mSize >= mKeys.length) {
+            gc();
+        }
+
+        int pos = mSize;
+        if (pos >= mKeys.length) {
+            int n = ArrayUtils.idealIntArraySize(pos + 1);
+
+            int[] nkeys = new int[n];
+            Object[] nvalues = new Object[n];
+
+            // Log.e("SparseArray", "grow " + mKeys.length + " to " + n);
+            System.arraycopy(mKeys, 0, nkeys, 0, mKeys.length);
+            System.arraycopy(mValues, 0, nvalues, 0, mValues.length);
+
+            mKeys = nkeys;
+            mValues = nvalues;
+        }
+
+        mKeys[pos] = key;
+        mValues[pos] = value;
+        mSize = pos + 1;
+    }
+    
+    private static int binarySearch(int[] a, int start, int len, int key) {
+        int high = start + len, low = start - 1, guess;
+
+        while (high - low > 1) {
+            guess = (high + low) / 2;
+
+            if (a[guess] < key)
+                low = guess;
+            else
+                high = guess;
+        }
+
+        if (high == start + len)
+            return ~(start + len);
+        else if (a[high] == key)
+            return high;
+        else
+            return ~high;
+    }
+
+    private void checkIntegrity() {
+        for (int i = 1; i < mSize; i++) {
+            if (mKeys[i] <= mKeys[i - 1]) {
+                for (int j = 0; j < mSize; j++) {
+                    Log.e("FAIL", j + ": " + mKeys[j] + " -> " + mValues[j]);
+                }
+
+                throw new RuntimeException();
+            }
+        }
+    }
+
+    private int[] mKeys;
+    private Object[] mValues;
+    private int mSize;
+}
diff --git a/core/java/android/util/SparseBooleanArray.java b/core/java/android/util/SparseBooleanArray.java
new file mode 100644
index 0000000..f7799de
--- /dev/null
+++ b/core/java/android/util/SparseBooleanArray.java
@@ -0,0 +1,245 @@
+/*
+ * 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 android.util;
+
+import com.android.internal.util.ArrayUtils;
+
+/**
+ * SparseBooleanArrays map integers to booleans.
+ * Unlike a normal array of booleans
+ * there can be gaps in the indices.  It is intended to be more efficient
+ * than using a HashMap to map Integers to Booleans.
+ */
+public class SparseBooleanArray {
+    /**
+     * Creates a new SparseBooleanArray containing no mappings.
+     */
+    public SparseBooleanArray() {
+        this(10);
+    }
+
+    /**
+     * Creates a new SparseBooleanArray containing no mappings that will not
+     * require any additional memory allocation to store the specified
+     * number of mappings.
+     */
+    public SparseBooleanArray(int initialCapacity) {
+        initialCapacity = ArrayUtils.idealIntArraySize(initialCapacity);
+
+        mKeys = new int[initialCapacity];
+        mValues = new boolean[initialCapacity];
+        mSize = 0;
+    }
+
+    /**
+     * Gets the boolean mapped from the specified key, or <code>false</code>
+     * if no such mapping has been made.
+     */
+    public boolean get(int key) {
+        return get(key, false);
+    }
+
+    /**
+     * Gets the boolean mapped from the specified key, or the specified value
+     * if no such mapping has been made.
+     */
+    public boolean get(int key, boolean valueIfKeyNotFound) {
+        int i = binarySearch(mKeys, 0, mSize, key);
+
+        if (i < 0) {
+            return valueIfKeyNotFound;
+        } else {
+            return mValues[i];
+        }
+    }
+
+    /**
+     * Removes the mapping from the specified key, if there was any.
+     */
+    public void delete(int key) {
+        int i = binarySearch(mKeys, 0, mSize, key);
+
+        if (i >= 0) {
+            System.arraycopy(mKeys, i + 1, mKeys, i, mSize - (i + 1));
+            System.arraycopy(mValues, i + 1, mValues, i, mSize - (i + 1));
+            mSize--;
+        }
+    }
+
+    /**
+     * Adds a mapping from the specified key to the specified value,
+     * replacing the previous mapping from the specified key if there
+     * was one.
+     */
+    public void put(int key, boolean value) {
+        int i = binarySearch(mKeys, 0, mSize, key);
+
+        if (i >= 0) {
+            mValues[i] = value;
+        } else {
+            i = ~i;
+
+            if (mSize >= mKeys.length) {
+                int n = ArrayUtils.idealIntArraySize(mSize + 1);
+
+                int[] nkeys = new int[n];
+                boolean[] nvalues = new boolean[n];
+
+                // Log.e("SparseBooleanArray", "grow " + mKeys.length + " to " + n);
+                System.arraycopy(mKeys, 0, nkeys, 0, mKeys.length);
+                System.arraycopy(mValues, 0, nvalues, 0, mValues.length);
+
+                mKeys = nkeys;
+                mValues = nvalues;
+            }
+
+            if (mSize - i != 0) {
+                // Log.e("SparseBooleanArray", "move " + (mSize - i));
+                System.arraycopy(mKeys, i, mKeys, i + 1, mSize - i);
+                System.arraycopy(mValues, i, mValues, i + 1, mSize - i);
+            }
+
+            mKeys[i] = key;
+            mValues[i] = value;
+            mSize++;
+        }
+    }
+
+    /**
+     * Returns the number of key-value mappings that this SparseBooleanArray
+     * currently stores.
+     */
+    public int size() {
+        return mSize;
+    }
+
+    /**
+     * Given an index in the range <code>0...size()-1</code>, returns
+     * the key from the <code>index</code>th key-value mapping that this
+     * SparseBooleanArray stores.  
+     */
+    public int keyAt(int index) {
+        return mKeys[index];
+    }
+    
+    /**
+     * Given an index in the range <code>0...size()-1</code>, returns
+     * the value from the <code>index</code>th key-value mapping that this
+     * SparseBooleanArray stores.  
+     */
+    public boolean valueAt(int index) {
+        return mValues[index];
+    }
+
+    /**
+     * Returns the index for which {@link #keyAt} would return the
+     * specified key, or a negative number if the specified
+     * key is not mapped.
+     */
+    public int indexOfKey(int key) {
+        return binarySearch(mKeys, 0, mSize, key);
+    }
+
+    /**
+     * Returns an index for which {@link #valueAt} would return the
+     * specified key, or a negative number if no keys map to the
+     * specified value.
+     * Beware that this is a linear search, unlike lookups by key,
+     * and that multiple keys can map to the same value and this will
+     * find only one of them.
+     */
+    public int indexOfValue(boolean value) {
+        for (int i = 0; i < mSize; i++)
+            if (mValues[i] == value)
+                return i;
+
+        return -1;
+    }
+
+    /**
+     * Removes all key-value mappings from this SparseBooleanArray.
+     */
+    public void clear() {
+        mSize = 0;
+    }
+
+    /**
+     * Puts a key/value pair into the array, optimizing for the case where
+     * the key is greater than all existing keys in the array.
+     */
+    public void append(int key, boolean value) {
+        if (mSize != 0 && key <= mKeys[mSize - 1]) {
+            put(key, value);
+            return;
+        }
+
+        int pos = mSize;
+        if (pos >= mKeys.length) {
+            int n = ArrayUtils.idealIntArraySize(pos + 1);
+
+            int[] nkeys = new int[n];
+            boolean[] nvalues = new boolean[n];
+
+            // Log.e("SparseBooleanArray", "grow " + mKeys.length + " to " + n);
+            System.arraycopy(mKeys, 0, nkeys, 0, mKeys.length);
+            System.arraycopy(mValues, 0, nvalues, 0, mValues.length);
+
+            mKeys = nkeys;
+            mValues = nvalues;
+        }
+
+        mKeys[pos] = key;
+        mValues[pos] = value;
+        mSize = pos + 1;
+    }
+    
+    private static int binarySearch(int[] a, int start, int len, int key) {
+        int high = start + len, low = start - 1, guess;
+
+        while (high - low > 1) {
+            guess = (high + low) / 2;
+
+            if (a[guess] < key)
+                low = guess;
+            else
+                high = guess;
+        }
+
+        if (high == start + len)
+            return ~(start + len);
+        else if (a[high] == key)
+            return high;
+        else
+            return ~high;
+    }
+
+    private void checkIntegrity() {
+        for (int i = 1; i < mSize; i++) {
+            if (mKeys[i] <= mKeys[i - 1]) {
+                for (int j = 0; j < mSize; j++) {
+                    Log.e("FAIL", j + ": " + mKeys[j] + " -> " + mValues[j]);
+                }
+
+                throw new RuntimeException();
+            }
+        }
+    }
+
+    private int[] mKeys;
+    private boolean[] mValues;
+    private int mSize;
+}
diff --git a/core/java/android/util/SparseIntArray.java b/core/java/android/util/SparseIntArray.java
new file mode 100644
index 0000000..9ab3b53
--- /dev/null
+++ b/core/java/android/util/SparseIntArray.java
@@ -0,0 +1,251 @@
+/*
+ * 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 android.util;
+
+import com.android.internal.util.ArrayUtils;
+
+/**
+ * SparseIntArrays map integers to integers.  Unlike a normal array of integers,
+ * there can be gaps in the indices.  It is intended to be more efficient
+ * than using a HashMap to map Integers to Integers.
+ */
+public class SparseIntArray {
+    /**
+     * Creates a new SparseIntArray containing no mappings.
+     */
+    public SparseIntArray() {
+        this(10);
+    }
+
+    /**
+     * Creates a new SparseIntArray containing no mappings that will not
+     * require any additional memory allocation to store the specified
+     * number of mappings.
+     */
+    public SparseIntArray(int initialCapacity) {
+        initialCapacity = ArrayUtils.idealIntArraySize(initialCapacity);
+
+        mKeys = new int[initialCapacity];
+        mValues = new int[initialCapacity];
+        mSize = 0;
+    }
+
+    /**
+     * Gets the int mapped from the specified key, or <code>0</code>
+     * if no such mapping has been made.
+     */
+    public int get(int key) {
+        return get(key, 0);
+    }
+
+    /**
+     * Gets the int mapped from the specified key, or the specified value
+     * if no such mapping has been made.
+     */
+    public int get(int key, int valueIfKeyNotFound) {
+        int i = binarySearch(mKeys, 0, mSize, key);
+
+        if (i < 0) {
+            return valueIfKeyNotFound;
+        } else {
+            return mValues[i];
+        }
+    }
+
+    /**
+     * Removes the mapping from the specified key, if there was any.
+     */
+    public void delete(int key) {
+        int i = binarySearch(mKeys, 0, mSize, key);
+
+        if (i >= 0) {
+            removeAt(i);
+        }
+    }
+
+    /**
+     * Removes the mapping at the given index.
+     */
+    public void removeAt(int index) {
+        System.arraycopy(mKeys, index + 1, mKeys, index, mSize - (index + 1));
+        System.arraycopy(mValues, index + 1, mValues, index, mSize - (index + 1));
+        mSize--;
+    }
+
+    /**
+     * Adds a mapping from the specified key to the specified value,
+     * replacing the previous mapping from the specified key if there
+     * was one.
+     */
+    public void put(int key, int value) {
+        int i = binarySearch(mKeys, 0, mSize, key);
+
+        if (i >= 0) {
+            mValues[i] = value;
+        } else {
+            i = ~i;
+
+            if (mSize >= mKeys.length) {
+                int n = ArrayUtils.idealIntArraySize(mSize + 1);
+
+                int[] nkeys = new int[n];
+                int[] nvalues = new int[n];
+
+                // Log.e("SparseIntArray", "grow " + mKeys.length + " to " + n);
+                System.arraycopy(mKeys, 0, nkeys, 0, mKeys.length);
+                System.arraycopy(mValues, 0, nvalues, 0, mValues.length);
+
+                mKeys = nkeys;
+                mValues = nvalues;
+            }
+
+            if (mSize - i != 0) {
+                // Log.e("SparseIntArray", "move " + (mSize - i));
+                System.arraycopy(mKeys, i, mKeys, i + 1, mSize - i);
+                System.arraycopy(mValues, i, mValues, i + 1, mSize - i);
+            }
+
+            mKeys[i] = key;
+            mValues[i] = value;
+            mSize++;
+        }
+    }
+
+    /**
+     * Returns the number of key-value mappings that this SparseIntArray
+     * currently stores.
+     */
+    public int size() {
+        return mSize;
+    }
+
+    /**
+     * Given an index in the range <code>0...size()-1</code>, returns
+     * the key from the <code>index</code>th key-value mapping that this
+     * SparseIntArray stores.  
+     */
+    public int keyAt(int index) {
+        return mKeys[index];
+    }
+    
+    /**
+     * Given an index in the range <code>0...size()-1</code>, returns
+     * the value from the <code>index</code>th key-value mapping that this
+     * SparseIntArray stores.  
+     */
+    public int valueAt(int index) {
+        return mValues[index];
+    }
+
+    /**
+     * Returns the index for which {@link #keyAt} would return the
+     * specified key, or a negative number if the specified
+     * key is not mapped.
+     */
+    public int indexOfKey(int key) {
+        return binarySearch(mKeys, 0, mSize, key);
+    }
+
+    /**
+     * Returns an index for which {@link #valueAt} would return the
+     * specified key, or a negative number if no keys map to the
+     * specified value.
+     * Beware that this is a linear search, unlike lookups by key,
+     * and that multiple keys can map to the same value and this will
+     * find only one of them.
+     */
+    public int indexOfValue(int value) {
+        for (int i = 0; i < mSize; i++)
+            if (mValues[i] == value)
+                return i;
+
+        return -1;
+    }
+
+    /**
+     * Removes all key-value mappings from this SparseIntArray.
+     */
+    public void clear() {
+        mSize = 0;
+    }
+
+    /**
+     * Puts a key/value pair into the array, optimizing for the case where
+     * the key is greater than all existing keys in the array.
+     */
+    public void append(int key, int value) {
+        if (mSize != 0 && key <= mKeys[mSize - 1]) {
+            put(key, value);
+            return;
+        }
+
+        int pos = mSize;
+        if (pos >= mKeys.length) {
+            int n = ArrayUtils.idealIntArraySize(pos + 1);
+
+            int[] nkeys = new int[n];
+            int[] nvalues = new int[n];
+
+            // Log.e("SparseIntArray", "grow " + mKeys.length + " to " + n);
+            System.arraycopy(mKeys, 0, nkeys, 0, mKeys.length);
+            System.arraycopy(mValues, 0, nvalues, 0, mValues.length);
+
+            mKeys = nkeys;
+            mValues = nvalues;
+        }
+
+        mKeys[pos] = key;
+        mValues[pos] = value;
+        mSize = pos + 1;
+    }
+    
+    private static int binarySearch(int[] a, int start, int len, int key) {
+        int high = start + len, low = start - 1, guess;
+
+        while (high - low > 1) {
+            guess = (high + low) / 2;
+
+            if (a[guess] < key)
+                low = guess;
+            else
+                high = guess;
+        }
+
+        if (high == start + len)
+            return ~(start + len);
+        else if (a[high] == key)
+            return high;
+        else
+            return ~high;
+    }
+
+    private void checkIntegrity() {
+        for (int i = 1; i < mSize; i++) {
+            if (mKeys[i] <= mKeys[i - 1]) {
+                for (int j = 0; j < mSize; j++) {
+                    Log.e("FAIL", j + ": " + mKeys[j] + " -> " + mValues[j]);
+                }
+
+                throw new RuntimeException();
+            }
+        }
+    }
+
+    private int[] mKeys;
+    private int[] mValues;
+    private int mSize;
+}
diff --git a/core/java/android/util/StateSet.java b/core/java/android/util/StateSet.java
new file mode 100644
index 0000000..f3d8159
--- /dev/null
+++ b/core/java/android/util/StateSet.java
@@ -0,0 +1,178 @@
+/*
+ * Copyright (C) 2007 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 android.util;
+
+import com.android.internal.R;
+
+/**
+ * State sets are arrays of positive ints where each element
+ * represents the state of a {@link android.view.View} (e.g. focused,
+ * selected, visible, etc.).  A {@link android.view.View} may be in
+ * one or more of those states.
+ *
+ * A state spec is an array of signed ints where each element
+ * represents a required (if positive) or an undesired (if negative)
+ * {@link android.view.View} state.
+ *
+ * Utils dealing with state sets.
+ *
+ * In theory we could encapsulate the state set and state spec arrays
+ * and not have static methods here but there is some concern about
+ * performance since these methods are called during view drawing.
+ */
+
+public class StateSet {
+
+    public static final int[] WILD_CARD = new int[0];
+
+    /**
+     * Return whether the stateSetOrSpec is matched by all StateSets.
+     * 
+     * @param stateSetOrSpec a state set or state spec.
+     */
+    public static boolean isWildCard(int[] stateSetOrSpec) {
+        return stateSetOrSpec.length == 0 || stateSetOrSpec[0] == 0;
+    }
+
+    /**
+     * Return whether the stateSet matches the desired stateSpec.
+     * 
+     * @param stateSpec an array of required (if positive) or
+     *        prohibited (if negative) {@link android.view.View} states.
+     * @param stateSet an array of {@link android.view.View} states
+     */
+    public static boolean stateSetMatches(int[] stateSpec, int[] stateSet) {
+        if (stateSet == null) {
+            return (stateSpec == null || isWildCard(stateSpec));
+        }
+        int stateSpecSize = stateSpec.length;
+        int stateSetSize = stateSet.length;
+        for (int i = 0; i < stateSpecSize; i++) {
+            int stateSpecState = stateSpec[i];
+            if (stateSpecState == 0) {
+                // We've reached the end of the cases to match against.
+                return true;
+            }
+            final boolean mustMatch;
+            if (stateSpecState > 0) {
+                mustMatch = true;
+            } else {
+                // We use negative values to indicate must-NOT-match states.
+                mustMatch = false;
+                stateSpecState = -stateSpecState;
+            }
+            boolean found = false;
+            for (int j = 0; j < stateSetSize; j++) {
+                final int state = stateSet[j];
+                if (state == 0) {
+                    // We've reached the end of states to match.
+                    if (mustMatch) {
+                        // We didn't find this must-match state.
+                        return false;
+                    } else {
+                        // Continue checking other must-not-match states.
+                        break;
+                    }
+                }
+                if (state == stateSpecState) {
+                    if (mustMatch) {
+                        found = true;
+                        // Continue checking other other must-match states.
+                        break;
+                    } else {
+                        // Any match of a must-not-match state returns false.
+                        return false;
+                    }
+                }
+            }
+            if (mustMatch && !found) {
+                // We've reached the end of states to match and we didn't
+                // find a must-match state.
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Return whether the state matches the desired stateSpec.
+     * 
+     * @param stateSpec an array of required (if positive) or
+     *        prohibited (if negative) {@link android.view.View} states.
+     * @param state a {@link android.view.View} state
+     */
+    public static boolean stateSetMatches(int[] stateSpec, int state) {
+        int stateSpecSize = stateSpec.length;
+        for (int i = 0; i < stateSpecSize; i++) {
+            int stateSpecState = stateSpec[i];
+            if (stateSpecState == 0) {
+                // We've reached the end of the cases to match against.
+                return true;
+            }
+            if (stateSpecState > 0) {
+                if (state != stateSpecState) {
+                   return false;
+                }
+            } else {
+                // We use negative values to indicate must-NOT-match states.
+                if (state == -stateSpecState) {
+                    // We matched a must-not-match case.
+                    return false;
+                }
+            }
+        }
+        return true;
+    }
+
+    public static int[] trimStateSet(int[] states, int newSize) {
+        if (states.length == newSize) {
+            return states;
+        }
+
+        int[] trimmedStates = new int[newSize];
+        System.arraycopy(states, 0, trimmedStates, 0, newSize);
+        return trimmedStates;
+    }
+    
+    public static String dump(int[] states) {
+        StringBuilder sb = new StringBuilder();
+        
+        int count = states.length;
+        for (int i = 0; i < count; i++) {
+            
+            switch (states[i]) {
+            case R.attr.state_window_focused:
+                sb.append("W ");
+                break;
+            case R.attr.state_pressed:
+                sb.append("P ");
+                break;
+            case R.attr.state_selected:
+                sb.append("S ");
+                break;
+            case R.attr.state_focused:
+                sb.append("F ");
+                break;
+            case R.attr.state_enabled:
+                sb.append("E ");
+                break;
+            }
+        }
+        
+        return sb.toString();
+    }
+}
diff --git a/core/java/android/util/StringBuilderPrinter.java b/core/java/android/util/StringBuilderPrinter.java
new file mode 100644
index 0000000..d0fc1e7
--- /dev/null
+++ b/core/java/android/util/StringBuilderPrinter.java
@@ -0,0 +1,42 @@
+/*
+ * 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 android.util;
+
+/**
+ * Implementation of a {@link android.util.Printer} that sends its output
+ * to a {@link StringBuilder}.
+ */
+public class StringBuilderPrinter implements Printer {
+    private final StringBuilder mBuilder;
+    
+    /**
+     * Create a new Printer that sends to a StringBuilder object.
+     * 
+     * @param builder The StringBuilder where you would like output to go.
+     */
+    public StringBuilderPrinter(StringBuilder builder) {
+        mBuilder = builder;
+    }
+    
+    public void println(String x) {
+        mBuilder.append(x);
+        int len = x.length();
+        if (len <= 0 || x.charAt(len-1) != '\n') {
+            mBuilder.append('\n');
+        }
+    }
+}
diff --git a/core/java/android/util/TimeFormatException.java b/core/java/android/util/TimeFormatException.java
new file mode 100644
index 0000000..d7a898b
--- /dev/null
+++ b/core/java/android/util/TimeFormatException.java
@@ -0,0 +1,26 @@
+/*
+ * 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 android.util;
+
+public class TimeFormatException extends RuntimeException
+{
+    TimeFormatException(String s)
+    {
+        super(s);
+    }
+}
+
diff --git a/core/java/android/util/TimeUtils.java b/core/java/android/util/TimeUtils.java
new file mode 100644
index 0000000..0fc70d5
--- /dev/null
+++ b/core/java/android/util/TimeUtils.java
@@ -0,0 +1,133 @@
+/*
+ * 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 android.util;
+
+import android.content.res.Resources;
+import android.content.res.XmlResourceParser;
+
+import org.apache.harmony.luni.internal.util.ZoneInfoDB;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.util.TimeZone;
+import java.util.Date;
+
+import com.android.internal.util.XmlUtils;
+
+/**
+ * A class containing utility methods related to time zones.
+ */
+public class TimeUtils {
+    private static final String TAG = "TimeUtils";
+
+    /**
+     * Tries to return a time zone that would have had the specified offset
+     * and DST value at the specified moment in the specified country.
+     * Returns null if no suitable zone could be found.
+     */
+    public static TimeZone getTimeZone(int offset, boolean dst, long when, String country) {
+        if (country == null) {
+            return null;
+        }
+
+        TimeZone best = null;
+
+        Resources r = Resources.getSystem();
+        XmlResourceParser parser = r.getXml(com.android.internal.R.xml.time_zones_by_country);
+        Date d = new Date(when);
+
+        TimeZone current = TimeZone.getDefault();
+        String currentName = current.getID();
+        int currentOffset = current.getOffset(when);
+        boolean currentDst = current.inDaylightTime(d);
+
+        try {
+            XmlUtils.beginDocument(parser, "timezones");
+
+            while (true) {
+                XmlUtils.nextElement(parser);
+
+                String element = parser.getName();
+                if (element == null || !(element.equals("timezone"))) {
+                    break;
+                }
+
+                String code = parser.getAttributeValue(null, "code");
+
+                if (country.equals(code)) {
+                    if (parser.next() == XmlPullParser.TEXT) {
+                        String maybe = parser.getText();
+
+                        // If the current time zone is from the right country
+                        // and meets the other known properties, keep it
+                        // instead of changing to another one.
+
+                        if (maybe.equals(currentName)) {
+                            if (currentOffset == offset && currentDst == dst) {
+                                return current;
+                            }
+                        }
+
+                        // Otherwise, take the first zone from the right
+                        // country that has the correct current offset and DST.
+                        // (Keep iterating instead of returning in case we
+                        // haven't encountered the current time zone yet.)
+
+                        if (best == null) {
+                            TimeZone tz = TimeZone.getTimeZone(maybe);
+
+                            if (tz.getOffset(when) == offset &&
+                                tz.inDaylightTime(d) == dst) {
+                                best = tz;
+                            }
+                        }
+                    }
+                }
+            }
+        } catch (XmlPullParserException e) {
+            Log.e(TAG, "Got exception while getting preferred time zone.", e);
+        } catch (IOException e) {
+            Log.e(TAG, "Got exception while getting preferred time zone.", e);
+        } finally {
+            parser.close();
+        }
+
+        return best;
+    }
+
+    /**
+     * Returns a String indicating the version of the time zone database currently
+     * in use.  The format of the string is dependent on the underlying time zone
+     * database implementation, but will typically contain the year in which the database
+     * was updated plus a letter from a to z indicating changes made within that year.
+     * 
+     * <p>Time zone database updates should be expected to occur periodically due to
+     * political and legal changes that cannot be anticipated in advance.  Therefore,
+     * when computing the UTC time for a future event, applications should be aware that
+     * the results may differ following a time zone database update.  This method allows
+     * applications to detect that a database change has occurred, and to recalculate any
+     * cached times accordingly.
+     * 
+     * <p>The time zone database may be assumed to change only when the device runtime
+     * is restarted.  Therefore, it is not necessary to re-query the database version
+     * during the lifetime of an activity.
+     */
+    public static String getTimeZoneDatabaseVersion() {
+        return ZoneInfoDB.getVersion();
+    }
+}
diff --git a/core/java/android/util/TimingLogger.java b/core/java/android/util/TimingLogger.java
new file mode 100644
index 0000000..0f39c97
--- /dev/null
+++ b/core/java/android/util/TimingLogger.java
@@ -0,0 +1,144 @@
+/*
+ * Copyright (C) 2007 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 android.util;
+
+import java.util.ArrayList;
+
+import android.os.SystemClock;
+
+/**
+ * A utility class to help log timings splits throughout a method call.
+ * Typical usage is:
+ *
+ * TimingLogger timings = new TimingLogger(TAG, "methodA");
+ * ... do some work A ...
+ * timings.addSplit("work A");
+ * ... do some work B ...
+ * timings.addSplit("work B");
+ * ... do some work C ...
+ * timings.addSplit("work C");
+ * timings.dumpToLog();
+ *
+ * The dumpToLog call would add the following to the log:
+ *
+ * D/TAG     ( 3459): methodA: begin
+ * D/TAG     ( 3459): methodA:      9 ms, work A
+ * D/TAG     ( 3459): methodA:      1 ms, work B
+ * D/TAG     ( 3459): methodA:      6 ms, work C
+ * D/TAG     ( 3459): methodA: end, 16 ms
+ */
+public class TimingLogger {
+
+    /**
+     * The Log tag to use for checking Log.isLoggable and for
+     * logging the timings.
+     */
+    private String mTag;
+
+    /** A label to be included in every log. */
+    private String mLabel;
+
+    /** Used to track whether Log.isLoggable was enabled at reset time. */
+    private boolean mDisabled;
+
+    /** Stores the time of each split. */
+    ArrayList<Long> mSplits;
+
+    /** Stores the labels for each split. */
+    ArrayList<String> mSplitLabels;
+
+    /**
+     * Create and initialize a TimingLogger object that will log using
+     * the specific tag. If the Log.isLoggable is not enabled to at
+     * least the Log.VERBOSE level for that tag at creation time then
+     * the addSplit and dumpToLog call will do nothing.
+     * @param tag the log tag to use while logging the timings
+     * @param label a string to be displayed with each log
+     */
+    public TimingLogger(String tag, String label) {
+        reset(tag, label);
+    }
+
+    /**
+     * Clear and initialize a TimingLogger object that will log using
+     * the specific tag. If the Log.isLoggable is not enabled to at
+     * least the Log.VERBOSE level for that tag at creation time then
+     * the addSplit and dumpToLog call will do nothing.
+     * @param tag the log tag to use while logging the timings
+     * @param label a string to be displayed with each log
+     */
+    public void reset(String tag, String label) {
+        mTag = tag;
+        mLabel = label;
+        reset();
+    }
+
+    /**
+     * Clear and initialize a TimingLogger object that will log using
+     * the tag and label that was specified previously, either via
+     * the constructor or a call to reset(tag, label). If the
+     * Log.isLoggable is not enabled to at least the Log.VERBOSE
+     * level for that tag at creation time then the addSplit and
+     * dumpToLog call will do nothing.
+     */
+    public void reset() {
+        mDisabled = !Log.isLoggable(mTag, Log.VERBOSE);
+        if (mDisabled) return;
+        if (mSplits == null) {
+            mSplits = new ArrayList<Long>();
+            mSplitLabels = new ArrayList<String>();
+        } else {
+            mSplits.clear();
+            mSplitLabels.clear();
+        }
+        addSplit(null);
+    }
+
+    /**
+     * Add a split for the current time, labeled with splitLabel. If
+     * Log.isLoggable was not enabled to at least the Log.VERBOSE for
+     * the specified tag at construction or reset() time then this
+     * call does nothing.
+     * @param splitLabel a label to associate with this split.
+     */
+    public void addSplit(String splitLabel) {
+        if (mDisabled) return;
+        long now = SystemClock.elapsedRealtime();
+        mSplits.add(now);
+        mSplitLabels.add(splitLabel);
+    }
+
+    /**
+     * Dumps the timings to the log using Log.d(). If Log.isLoggable was
+     * not enabled to at least the Log.VERBOSE for the specified tag at
+     * construction or reset() time then this call does nothing.
+     */
+    public void dumpToLog() {
+        if (mDisabled) return;
+        Log.d(mTag, mLabel + ": begin");
+        final long first = mSplits.get(0);
+        long now = first;
+        for (int i = 1; i < mSplits.size(); i++) {
+            now = mSplits.get(i);
+            final String splitLabel = mSplitLabels.get(i);
+            final long prev = mSplits.get(i - 1);
+
+            Log.d(mTag, mLabel + ":      " + (now - prev) + " ms, " + splitLabel);
+        }
+        Log.d(mTag, mLabel + ": end, " + (now - first) + " ms");
+    }
+}
diff --git a/core/java/android/util/TypedValue.java b/core/java/android/util/TypedValue.java
new file mode 100644
index 0000000..d4ba9e2
--- /dev/null
+++ b/core/java/android/util/TypedValue.java
@@ -0,0 +1,492 @@
+/*
+ * Copyright (C) 2007 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 android.util;
+
+/**
+ * Container for a dynamically typed data value.  Primarily used with
+ * {@link android.content.res.Resources} for holding resource values.
+ */
+public class TypedValue {
+    /** The value contains no data. */
+    public static final int TYPE_NULL = 0x00;
+
+    /** The <var>data</var> field holds a resource identifier. */
+    public static final int TYPE_REFERENCE = 0x01;
+    /** The <var>data</var> field holds an attribute resource
+     *  identifier (referencing an attribute in the current theme
+     *  style, not a resource entry). */
+    public static final int TYPE_ATTRIBUTE = 0x02;
+    /** The <var>string</var> field holds string data.  In addition, if
+     *  <var>data</var> is non-zero then it is the string block
+     *  index of the string and <var>assetCookie</var> is the set of
+     *  assets the string came from. */
+    public static final int TYPE_STRING = 0x03;
+    /** The <var>data</var> field holds an IEEE 754 floating point number. */
+    public static final int TYPE_FLOAT = 0x04;
+    /** The <var>data</var> field holds a complex number encoding a
+     *  dimension value. */
+    public static final int TYPE_DIMENSION = 0x05;
+    /** The <var>data</var> field holds a complex number encoding a fraction
+     *  of a container. */
+    public static final int TYPE_FRACTION = 0x06;
+
+    /** Identifies the start of plain integer values.  Any type value
+     *  from this to {@link #TYPE_LAST_INT} means the
+     *  <var>data</var> field holds a generic integer value. */
+    public static final int TYPE_FIRST_INT = 0x10;
+
+    /** The <var>data</var> field holds a number that was
+     *  originally specified in decimal. */
+    public static final int TYPE_INT_DEC = 0x10;
+    /** The <var>data</var> field holds a number that was
+     *  originally specified in hexadecimal (0xn). */
+    public static final int TYPE_INT_HEX = 0x11;
+    /** The <var>data</var> field holds 0 or 1 that was originally
+     *  specified as "false" or "true". */
+    public static final int TYPE_INT_BOOLEAN = 0x12;
+
+    /** Identifies the start of integer values that were specified as
+     *  color constants (starting with '#'). */
+    public static final int TYPE_FIRST_COLOR_INT = 0x1c;
+
+    /** The <var>data</var> field holds a color that was originally
+     *  specified as #aarrggbb. */
+    public static final int TYPE_INT_COLOR_ARGB8 = 0x1c;
+    /** The <var>data</var> field holds a color that was originally
+     *  specified as #rrggbb. */
+    public static final int TYPE_INT_COLOR_RGB8 = 0x1d;
+    /** The <var>data</var> field holds a color that was originally
+     *  specified as #argb. */
+    public static final int TYPE_INT_COLOR_ARGB4 = 0x1e;
+    /** The <var>data</var> field holds a color that was originally
+     *  specified as #rgb. */
+    public static final int TYPE_INT_COLOR_RGB4 = 0x1f;
+
+    /** Identifies the end of integer values that were specified as color
+     *  constants. */
+    public static final int TYPE_LAST_COLOR_INT = 0x1f;
+
+    /** Identifies the end of plain integer values. */
+    public static final int TYPE_LAST_INT = 0x1f;
+
+    /* ------------------------------------------------------------ */
+
+    /** Complex data: bit location of unit information. */
+    public static final int COMPLEX_UNIT_SHIFT = 0;
+    /** Complex data: mask to extract unit information (after shifting by
+     *  {@link #COMPLEX_UNIT_SHIFT}). This gives us 16 possible types, as
+     *  defined below. */
+    public static final int COMPLEX_UNIT_MASK = 0xf;
+
+    /** {@link #TYPE_DIMENSION} complex unit: Value is raw pixels. */
+    public static final int COMPLEX_UNIT_PX = 0;
+    /** {@link #TYPE_DIMENSION} complex unit: Value is Device Independent
+     *  Pixels. */
+    public static final int COMPLEX_UNIT_DIP = 1;
+    /** {@link #TYPE_DIMENSION} complex unit: Value is a scaled pixel. */
+    public static final int COMPLEX_UNIT_SP = 2;
+    /** {@link #TYPE_DIMENSION} complex unit: Value is in points. */
+    public static final int COMPLEX_UNIT_PT = 3;
+    /** {@link #TYPE_DIMENSION} complex unit: Value is in inches. */
+    public static final int COMPLEX_UNIT_IN = 4;
+    /** {@link #TYPE_DIMENSION} complex unit: Value is in millimeters. */
+    public static final int COMPLEX_UNIT_MM = 5;
+
+    /** {@link #TYPE_FRACTION} complex unit: A basic fraction of the overall
+     *  size. */
+    public static final int COMPLEX_UNIT_FRACTION = 0;
+    /** {@link #TYPE_FRACTION} complex unit: A fraction of the parent size. */
+    public static final int COMPLEX_UNIT_FRACTION_PARENT = 1;
+
+    /** Complex data: where the radix information is, telling where the decimal
+     *  place appears in the mantissa. */
+    public static final int COMPLEX_RADIX_SHIFT = 4;
+    /** Complex data: mask to extract radix information (after shifting by
+     * {@link #COMPLEX_RADIX_SHIFT}). This give us 4 possible fixed point 
+     * representations as defined below. */ 
+    public static final int COMPLEX_RADIX_MASK = 0x3;
+
+    /** Complex data: the mantissa is an integral number -- i.e., 0xnnnnnn.0 */
+    public static final int COMPLEX_RADIX_23p0 = 0;
+    /** Complex data: the mantissa magnitude is 16 bits -- i.e, 0xnnnn.nn */
+    public static final int COMPLEX_RADIX_16p7 = 1;
+    /** Complex data: the mantissa magnitude is 8 bits -- i.e, 0xnn.nnnn */
+    public static final int COMPLEX_RADIX_8p15 = 2;
+    /** Complex data: the mantissa magnitude is 0 bits -- i.e, 0x0.nnnnnn */
+    public static final int COMPLEX_RADIX_0p23 = 3;
+
+    /** Complex data: bit location of mantissa information. */
+    public static final int COMPLEX_MANTISSA_SHIFT = 8;
+    /** Complex data: mask to extract mantissa information (after shifting by
+     *  {@link #COMPLEX_MANTISSA_SHIFT}). This gives us 23 bits of precision;
+     *  the top bit is the sign. */
+    public static final int COMPLEX_MANTISSA_MASK = 0xffffff;
+
+    /* ------------------------------------------------------------ */
+
+    /**
+     * If {@link #density} is equal to this value, then the density should be
+     * treated as the system's default density value: {@link DisplayMetrics#DEFAULT_DENSITY}.
+     *
+     * @hide Pending API council approval
+     */
+    public static final int DENSITY_DEFAULT = 0;
+
+    /* ------------------------------------------------------------ */
+
+    /** The type held by this value, as defined by the constants here.
+     *  This tells you how to interpret the other fields in the object. */
+    public int type;
+
+    /** If the value holds a string, this is it. */
+    public CharSequence string;
+
+    /** Basic data in the value, interpreted according to {@link #type} */
+    public int data;
+
+    /** Additional information about where the value came from; only
+     *  set for strings. */
+    public int assetCookie;
+
+    /** If Value came from a resource, this holds the corresponding resource id. */
+    public int resourceId;
+
+    /** If Value came from a resource, these are the configurations for which
+     *  its contents can change. */
+    public int changingConfigurations = -1;
+
+    /**
+     * If the Value came from a resource, this holds the corresponding pixel density.
+     * 
+     * @hide Pending API council approval
+     * */
+    public int density;
+
+    /* ------------------------------------------------------------ */
+
+    /** Return the data for this value as a float.  Only use for values
+     *  whose type is {@link #TYPE_FLOAT}. */
+    public final float getFloat() {
+        return Float.intBitsToFloat(data);
+    }
+
+    private static final float MANTISSA_MULT =
+        1.0f / (1<<TypedValue.COMPLEX_MANTISSA_SHIFT);
+    private static final float[] RADIX_MULTS = new float[] {
+        1.0f*MANTISSA_MULT, 1.0f/(1<<7)*MANTISSA_MULT,
+        1.0f/(1<<15)*MANTISSA_MULT, 1.0f/(1<<23)*MANTISSA_MULT
+    };
+
+    /**
+     * Retrieve the base value from a complex data integer.  This uses the 
+     * {@link #COMPLEX_MANTISSA_MASK} and {@link #COMPLEX_RADIX_MASK} fields of 
+     * the data to compute a floating point representation of the number they 
+     * describe.  The units are ignored. 
+     *  
+     * @param complex A complex data value.
+     * 
+     * @return A floating point value corresponding to the complex data.
+     */
+    public static float complexToFloat(int complex)
+    {
+        return (complex&(TypedValue.COMPLEX_MANTISSA_MASK
+                   <<TypedValue.COMPLEX_MANTISSA_SHIFT))
+            * RADIX_MULTS[(complex>>TypedValue.COMPLEX_RADIX_SHIFT)
+                            & TypedValue.COMPLEX_RADIX_MASK];
+    }
+
+    /**
+     * Converts a complex data value holding a dimension to its final floating 
+     * point value. The given <var>data</var> must be structured as a 
+     * {@link #TYPE_DIMENSION}.
+     *  
+     * @param data A complex data value holding a unit, magnitude, and 
+     *             mantissa.
+     * @param metrics Current display metrics to use in the conversion -- 
+     *                supplies display density and scaling information.
+     * 
+     * @return The complex floating point value multiplied by the appropriate 
+     * metrics depending on its unit. 
+     */
+    public static float complexToDimension(int data, DisplayMetrics metrics)
+    {
+        return applyDimension(
+            (data>>COMPLEX_UNIT_SHIFT)&COMPLEX_UNIT_MASK,
+            complexToFloat(data),
+            metrics);
+    }
+
+    /**
+     * Converts a complex data value holding a dimension to its final value
+     * as an integer pixel offset.  This is the same as
+     * {@link #complexToDimension}, except the raw floating point value is
+     * truncated to an integer (pixel) value.
+     * The given <var>data</var> must be structured as a 
+     * {@link #TYPE_DIMENSION}.
+     *  
+     * @param data A complex data value holding a unit, magnitude, and 
+     *             mantissa.
+     * @param metrics Current display metrics to use in the conversion -- 
+     *                supplies display density and scaling information.
+     * 
+     * @return The number of pixels specified by the data and its desired
+     * multiplier and units.
+     */
+    public static int complexToDimensionPixelOffset(int data,
+            DisplayMetrics metrics)
+    {
+        return (int)applyDimension(
+                (data>>COMPLEX_UNIT_SHIFT)&COMPLEX_UNIT_MASK,
+                complexToFloat(data),
+                metrics);
+    }
+
+    /**
+     * Converts a complex data value holding a dimension to its final value
+     * as an integer pixel size.  This is the same as
+     * {@link #complexToDimension}, except the raw floating point value is
+     * converted to an integer (pixel) value for use as a size.  A size
+     * conversion involves rounding the base value, and ensuring that a
+     * non-zero base value is at least one pixel in size.
+     * The given <var>data</var> must be structured as a 
+     * {@link #TYPE_DIMENSION}.
+     *  
+     * @param data A complex data value holding a unit, magnitude, and 
+     *             mantissa.
+     * @param metrics Current display metrics to use in the conversion -- 
+     *                supplies display density and scaling information.
+     * 
+     * @return The number of pixels specified by the data and its desired
+     * multiplier and units.
+     */
+    public static int complexToDimensionPixelSize(int data,
+            DisplayMetrics metrics)
+    {
+        final float value = complexToFloat(data);
+        final float f = applyDimension(
+                (data>>COMPLEX_UNIT_SHIFT)&COMPLEX_UNIT_MASK,
+                value,
+                metrics);
+        final int res = (int)(f+0.5f);
+        if (res != 0) return res;
+        if (value == 0) return 0;
+        if (value > 0) return 1;
+        return -1;
+    }
+
+    public static float complexToDimensionNoisy(int data, DisplayMetrics metrics)
+    {
+        float res = complexToDimension(data, metrics);
+        System.out.println(
+            "Dimension (0x" + ((data>>TypedValue.COMPLEX_MANTISSA_SHIFT)
+                               & TypedValue.COMPLEX_MANTISSA_MASK)
+            + "*" + (RADIX_MULTS[(data>>TypedValue.COMPLEX_RADIX_SHIFT)
+                                & TypedValue.COMPLEX_RADIX_MASK] / MANTISSA_MULT)
+            + ")" + DIMENSION_UNIT_STRS[(data>>COMPLEX_UNIT_SHIFT)
+                                & COMPLEX_UNIT_MASK]
+            + " = " + res);
+        return res;
+    }
+
+    /**
+     * Converts an unpacked complex data value holding a dimension to its final floating 
+     * point value. The two parameters <var>unit</var> and <var>value</var>
+     * are as in {@link #TYPE_DIMENSION}.
+     *  
+     * @param unit The unit to convert from.
+     * @param value The value to apply the unit to.
+     * @param metrics Current display metrics to use in the conversion -- 
+     *                supplies display density and scaling information.
+     * 
+     * @return The complex floating point value multiplied by the appropriate 
+     * metrics depending on its unit. 
+     */
+    public static float applyDimension(int unit, float value,
+                                       DisplayMetrics metrics)
+    {
+        switch (unit) {
+        case COMPLEX_UNIT_PX:
+            return value;
+        case COMPLEX_UNIT_DIP:
+            return value * metrics.density;
+        case COMPLEX_UNIT_SP:
+            return value * metrics.scaledDensity;
+        case COMPLEX_UNIT_PT:
+            return value * metrics.xdpi * (1.0f/72);
+        case COMPLEX_UNIT_IN:
+            return value * metrics.xdpi;
+        case COMPLEX_UNIT_MM:
+            return value * metrics.xdpi * (1.0f/25.4f);
+        }
+        return 0;
+    }
+
+    /**
+     * Return the data for this value as a dimension.  Only use for values 
+     * whose type is {@link #TYPE_DIMENSION}. 
+     * 
+     * @param metrics Current display metrics to use in the conversion -- 
+     *                supplies display density and scaling information.
+     * 
+     * @return The complex floating point value multiplied by the appropriate 
+     * metrics depending on its unit. 
+     */
+    public float getDimension(DisplayMetrics metrics)
+    {
+        return complexToDimension(data, metrics);
+    }
+
+    /**
+     * Converts a complex data value holding a fraction to its final floating 
+     * point value. The given <var>data</var> must be structured as a 
+     * {@link #TYPE_FRACTION}.
+     * 
+     * @param data A complex data value holding a unit, magnitude, and 
+     *             mantissa.
+     * @param base The base value of this fraction.  In other words, a 
+     *             standard fraction is multiplied by this value.
+     * @param pbase The parent base value of this fraction.  In other 
+     *             words, a parent fraction (nn%p) is multiplied by this
+     *             value.
+     * 
+     * @return The complex floating point value multiplied by the appropriate 
+     * base value depending on its unit. 
+     */
+    public static float complexToFraction(int data, float base, float pbase)
+    {
+        switch ((data>>COMPLEX_UNIT_SHIFT)&COMPLEX_UNIT_MASK) {
+        case COMPLEX_UNIT_FRACTION:
+            return complexToFloat(data) * base;
+        case COMPLEX_UNIT_FRACTION_PARENT:
+            return complexToFloat(data) * pbase;
+        }
+        return 0;
+    }
+
+    /**
+     * Return the data for this value as a fraction.  Only use for values whose 
+     * type is {@link #TYPE_FRACTION}. 
+     * 
+     * @param base The base value of this fraction.  In other words, a 
+     *             standard fraction is multiplied by this value.
+     * @param pbase The parent base value of this fraction.  In other 
+     *             words, a parent fraction (nn%p) is multiplied by this
+     *             value.
+     * 
+     * @return The complex floating point value multiplied by the appropriate 
+     * base value depending on its unit. 
+     */
+    public float getFraction(float base, float pbase)
+    {
+        return complexToFraction(data, base, pbase);
+    }
+
+    /**
+     * Regardless of the actual type of the value, try to convert it to a
+     * string value.  For example, a color type will be converted to a
+     * string of the form #aarrggbb.
+     * 
+     * @return CharSequence The coerced string value.  If the value is
+     *         null or the type is not known, null is returned.
+     */
+    public final CharSequence coerceToString()
+    {
+        int t = type;
+        if (t == TYPE_STRING) {
+            return string;
+        }
+        return coerceToString(t, data);
+    }
+
+    private static final String[] DIMENSION_UNIT_STRS = new String[] {
+        "px", "dip", "sp", "pt", "in", "mm"
+    };
+    private static final String[] FRACTION_UNIT_STRS = new String[] {
+        "%", "%p"
+    };
+
+    /**
+     * Perform type conversion as per {@link #coerceToString()} on an
+     * explicitly supplied type and data.
+     * 
+     * @param type The data type identifier.
+     * @param data The data value.
+     * 
+     * @return String The coerced string value.  If the value is
+     *         null or the type is not known, null is returned.
+     */
+    public static final String coerceToString(int type, int data)
+    {
+        switch (type) {
+        case TYPE_NULL:
+            return null;
+        case TYPE_REFERENCE:
+            return "@" + data;
+        case TYPE_ATTRIBUTE:
+            return "?" + data;
+        case TYPE_FLOAT:
+            return Float.toString(Float.intBitsToFloat(data));
+        case TYPE_DIMENSION:
+            return Float.toString(complexToFloat(data)) + DIMENSION_UNIT_STRS[
+                (data>>COMPLEX_UNIT_SHIFT)&COMPLEX_UNIT_MASK];
+        case TYPE_FRACTION:
+            return Float.toString(complexToFloat(data)*100) + FRACTION_UNIT_STRS[
+                (data>>COMPLEX_UNIT_SHIFT)&COMPLEX_UNIT_MASK];
+        case TYPE_INT_HEX:
+            return "0x" + Integer.toHexString(data);
+        case TYPE_INT_BOOLEAN:
+            return data != 0 ? "true" : "false";
+        }
+
+        if (type >= TYPE_FIRST_COLOR_INT && type <= TYPE_LAST_COLOR_INT) {
+            return "#" + Integer.toHexString(data);
+        } else if (type >= TYPE_FIRST_INT && type <= TYPE_LAST_INT) {
+            return Integer.toString(data);
+        }
+
+        return null;
+    }
+
+    public void setTo(TypedValue other)
+    {
+        type = other.type;
+        string = other.string;
+        data = other.data;
+        assetCookie = other.assetCookie;
+        resourceId = other.resourceId;
+        density = other.density;
+    }
+
+    public String toString()
+    {
+        StringBuilder sb = new StringBuilder();
+        sb.append("TypedValue{t=0x").append(Integer.toHexString(type));
+        sb.append("/d=0x").append(Integer.toHexString(data));
+        if (type == TYPE_STRING) {
+            sb.append(" \"").append(string != null ? string : "<null>").append("\"");
+        }
+        if (assetCookie != 0) {
+            sb.append(" a=").append(assetCookie);
+        }
+        if (resourceId != 0) {
+            sb.append(" r=0x").append(Integer.toHexString(resourceId));
+        }
+        sb.append("}");
+        return sb.toString();
+    }
+};
+
diff --git a/core/java/android/util/Xml.java b/core/java/android/util/Xml.java
new file mode 100644
index 0000000..a2b69a5
--- /dev/null
+++ b/core/java/android/util/Xml.java
@@ -0,0 +1,185 @@
+/*
+ * Copyright (C) 2007 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 android.util;
+
+import org.xml.sax.ContentHandler;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+import org.xml.sax.XMLReader;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlSerializer;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlPullParserFactory;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.Reader;
+import java.io.StringReader;
+import java.io.UnsupportedEncodingException;
+
+import org.apache.harmony.xml.ExpatPullParser;
+import org.apache.harmony.xml.ExpatReader;
+
+/**
+ * XML utility methods.
+ */
+public class Xml {
+
+    /**
+     * {@link org.xmlpull.v1.XmlPullParser} "relaxed" feature name.
+     *
+     * @see <a href="http://xmlpull.org/v1/doc/features.html#relaxed">
+     *  specification</a>
+     */
+    public static String FEATURE_RELAXED = ExpatPullParser.FEATURE_RELAXED;
+
+    /**
+     * Parses the given xml string and fires events on the given SAX handler.
+     */
+    public static void parse(String xml, ContentHandler contentHandler)
+            throws SAXException {
+        try {
+            XMLReader reader = new ExpatReader();
+            reader.setContentHandler(contentHandler);
+            reader.parse(new InputSource(new StringReader(xml)));
+        }
+        catch (IOException e) {
+            throw new AssertionError(e);
+        }
+    }
+
+    /**
+     * Parses xml from the given reader and fires events on the given SAX
+     * handler.
+     */
+    public static void parse(Reader in, ContentHandler contentHandler)
+            throws IOException, SAXException {
+        XMLReader reader = new ExpatReader();
+        reader.setContentHandler(contentHandler);
+        reader.parse(new InputSource(in));
+    }
+
+    /**
+     * Parses xml from the given input stream and fires events on the given SAX
+     * handler.
+     */
+    public static void parse(InputStream in, Encoding encoding,
+            ContentHandler contentHandler) throws IOException, SAXException {
+        try {
+            XMLReader reader = new ExpatReader();
+            reader.setContentHandler(contentHandler);
+            InputSource source = new InputSource(in);
+            source.setEncoding(encoding.expatName);
+            reader.parse(source);
+        } catch (IOException e) {
+            throw new AssertionError(e);
+        }
+    }
+
+    /**
+     * Creates a new pull parser with namespace support.
+     *
+     * <p><b>Note:</b> This is actually slower than the SAX parser, and it's not
+     *   fully implemented. If you need a fast, mostly implemented pull parser,
+     *   use this. If you need a complete implementation, use KXML.
+     */
+    public static XmlPullParser newPullParser() {
+        ExpatPullParser parser = new ExpatPullParser();
+        parser.setNamespaceProcessingEnabled(true);
+        return parser;
+    }
+
+    /**
+     * Creates a new xml serializer.
+     */
+    public static XmlSerializer newSerializer() {
+        try {
+            return XmlSerializerFactory.instance.newSerializer();
+        } catch (XmlPullParserException e) {
+            throw new AssertionError(e);
+        }
+    }
+
+    /** Factory for xml serializers. Initialized on demand. */
+    static class XmlSerializerFactory {
+        static final String TYPE
+                = "org.kxml2.io.KXmlParser,org.kxml2.io.KXmlSerializer";
+        static final XmlPullParserFactory instance;
+        static {
+            try {
+                instance = XmlPullParserFactory.newInstance(TYPE, null);
+            } catch (XmlPullParserException e) {
+                throw new AssertionError(e);
+            }
+        }
+    }
+
+    /**
+     * Supported character encodings.
+     */
+    public enum Encoding {
+
+        US_ASCII("US-ASCII"),
+        UTF_8("UTF-8"),
+        UTF_16("UTF-16"),
+        ISO_8859_1("ISO-8859-1");
+
+        final String expatName;
+
+        Encoding(String expatName) {
+            this.expatName = expatName;
+        }
+    }
+
+    /**
+     * Finds an encoding by name. Returns UTF-8 if you pass {@code null}.
+     */
+    public static Encoding findEncodingByName(String encodingName)
+            throws UnsupportedEncodingException {
+        if (encodingName == null) {
+            return Encoding.UTF_8;
+        }
+
+        for (Encoding encoding : Encoding.values()) {
+            if (encoding.expatName.equalsIgnoreCase(encodingName))
+                return encoding;
+        }
+        throw new UnsupportedEncodingException(encodingName);
+    }
+    
+    /**
+     * Return an AttributeSet interface for use with the given XmlPullParser.
+     * If the given parser itself implements AttributeSet, that implementation
+     * is simply returned.  Otherwise a wrapper class is
+     * instantiated on top of the XmlPullParser, as a proxy for retrieving its
+     * attributes, and returned to you.
+     * 
+     * @param parser The existing parser for which you would like an
+     *               AttributeSet.
+     * 
+     * @return An AttributeSet you can use to retrieve the
+     *         attribute values at each of the tags as the parser moves
+     *         through its XML document.
+     *         
+     * @see AttributeSet
+     */
+    public static AttributeSet asAttributeSet(XmlPullParser parser) {
+        return (parser instanceof AttributeSet)
+                ? (AttributeSet) parser
+                : new XmlPullAttributes(parser);
+    }
+}
diff --git a/core/java/android/util/XmlPullAttributes.java b/core/java/android/util/XmlPullAttributes.java
new file mode 100644
index 0000000..12d6dd9
--- /dev/null
+++ b/core/java/android/util/XmlPullAttributes.java
@@ -0,0 +1,146 @@
+/*
+ * 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 android.util;
+
+import org.xmlpull.v1.XmlPullParser;
+
+import android.util.AttributeSet;
+import com.android.internal.util.XmlUtils;
+
+/**
+ * Provides an implementation of AttributeSet on top of an XmlPullParser.
+ */
+class XmlPullAttributes implements AttributeSet {
+    public XmlPullAttributes(XmlPullParser parser) {
+        mParser = parser;
+    }
+
+    public int getAttributeCount() {
+        return mParser.getAttributeCount();
+    }
+
+    public String getAttributeName(int index) {
+        return mParser.getAttributeName(index);
+    }
+
+    public String getAttributeValue(int index) {
+        return mParser.getAttributeValue(index);
+    }
+
+    public String getAttributeValue(String namespace, String name) {
+        return mParser.getAttributeValue(namespace, name);
+    }
+
+    public String getPositionDescription() {
+        return mParser.getPositionDescription();
+    }
+
+    public int getAttributeNameResource(int index) {
+        return 0;
+    }
+
+    public int getAttributeListValue(String namespace, String attribute,
+            String[] options, int defaultValue) {
+        return XmlUtils.convertValueToList(
+            getAttributeValue(namespace, attribute), options, defaultValue);
+    }
+
+    public boolean getAttributeBooleanValue(String namespace, String attribute,
+            boolean defaultValue) {
+        return XmlUtils.convertValueToBoolean(
+            getAttributeValue(namespace, attribute), defaultValue);
+    }
+
+    public int getAttributeResourceValue(String namespace, String attribute,
+            int defaultValue) {
+        return XmlUtils.convertValueToInt(
+            getAttributeValue(namespace, attribute), defaultValue);
+    }
+
+    public int getAttributeIntValue(String namespace, String attribute,
+            int defaultValue) {
+        return XmlUtils.convertValueToInt(
+            getAttributeValue(namespace, attribute), defaultValue);
+    }
+
+    public int getAttributeUnsignedIntValue(String namespace, String attribute,
+            int defaultValue) {
+        return XmlUtils.convertValueToUnsignedInt(
+            getAttributeValue(namespace, attribute), defaultValue);
+    }
+
+    public float getAttributeFloatValue(String namespace, String attribute,
+            float defaultValue) {
+        String s = getAttributeValue(namespace, attribute);
+        if (s != null) {
+            return Float.parseFloat(s);
+        }
+        return defaultValue;
+    }
+
+    public int getAttributeListValue(int index,
+            String[] options, int defaultValue) {
+        return XmlUtils.convertValueToList(
+            getAttributeValue(index), options, defaultValue);
+    }
+
+    public boolean getAttributeBooleanValue(int index, boolean defaultValue) {
+        return XmlUtils.convertValueToBoolean(
+            getAttributeValue(index), defaultValue);
+    }
+
+    public int getAttributeResourceValue(int index, int defaultValue) {
+        return XmlUtils.convertValueToInt(
+            getAttributeValue(index), defaultValue);
+    }
+
+    public int getAttributeIntValue(int index, int defaultValue) {
+        return XmlUtils.convertValueToInt(
+            getAttributeValue(index), defaultValue);
+    }
+
+    public int getAttributeUnsignedIntValue(int index, int defaultValue) {
+        return XmlUtils.convertValueToUnsignedInt(
+            getAttributeValue(index), defaultValue);
+    }
+
+    public float getAttributeFloatValue(int index, float defaultValue) {
+        String s = getAttributeValue(index);
+        if (s != null) {
+            return Float.parseFloat(s);
+        }
+        return defaultValue;
+    }
+
+    public String getIdAttribute() {
+        return getAttributeValue(null, "id");
+    }
+
+    public String getClassAttribute() {
+        return getAttributeValue(null, "class");
+    }
+
+    public int getIdAttributeResourceValue(int defaultValue) {
+        return getAttributeResourceValue(null, "id", defaultValue);
+    }
+
+    public int getStyleAttribute() {
+        return getAttributeResourceValue(null, "style", 0);
+    }
+
+    private XmlPullParser mParser;
+}
diff --git a/core/java/android/util/package.html b/core/java/android/util/package.html
new file mode 100644
index 0000000..d918d69
--- /dev/null
+++ b/core/java/android/util/package.html
@@ -0,0 +1,6 @@
+<HTML>
+<BODY>
+Provides common utility methods such as date/time manipulation, base64 encoders
+and decoders, string and number conversion methods, and XML utilities.
+</BODY>
+</HTML>
\ No newline at end of file
diff --git a/core/java/android/view/AbsSavedState.java b/core/java/android/view/AbsSavedState.java
new file mode 100644
index 0000000..840d7c1
--- /dev/null
+++ b/core/java/android/view/AbsSavedState.java
@@ -0,0 +1,89 @@
+/*
+ * 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 android.view;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * A {@link Parcelable} implementation that should be used by inheritance
+ * hierarchies to ensure the state of all classes along the chain is saved.
+ */
+public abstract class AbsSavedState implements Parcelable {
+    public static final AbsSavedState EMPTY_STATE = new AbsSavedState() {};
+
+    private final Parcelable mSuperState;
+
+    /**
+     * Constructor used to make the EMPTY_STATE singleton
+     */
+    private AbsSavedState() {
+        mSuperState = null;
+    }
+
+    /**
+     * Constructor called by derived classes when creating their SavedState objects
+     * 
+     * @param superState The state of the superclass of this view
+     */
+    protected AbsSavedState(Parcelable superState) {
+        if (superState == null) {
+            throw new IllegalArgumentException("superState must not be null");
+        }
+        mSuperState = superState != EMPTY_STATE ? superState : null;
+    }
+
+    /**
+     * Constructor used when reading from a parcel. Reads the state of the superclass.
+     * 
+     * @param source
+     */
+    protected AbsSavedState(Parcel source) {
+        // FIXME need class loader
+        Parcelable superState = (Parcelable) source.readParcelable(null);
+         
+        mSuperState = superState != null ? superState : EMPTY_STATE;
+    }
+
+    final public Parcelable getSuperState() {
+        return mSuperState;
+    }
+
+    public int describeContents() {
+        return 0;
+    }
+
+    public void writeToParcel(Parcel dest, int flags) {
+         dest.writeParcelable(mSuperState, flags);
+    }
+
+    public static final Parcelable.Creator<AbsSavedState> CREATOR 
+        = new Parcelable.Creator<AbsSavedState>() {
+        
+        public AbsSavedState createFromParcel(Parcel in) {
+            Parcelable superState = (Parcelable) in.readParcelable(null);
+            if (superState != null) {
+                throw new IllegalStateException("superState must be null");
+            }
+            return EMPTY_STATE;
+        }
+
+        public AbsSavedState[] newArray(int size) {
+            return new AbsSavedState[size];
+        }
+    };
+}
diff --git a/core/java/android/view/ContextMenu.java b/core/java/android/view/ContextMenu.java
new file mode 100644
index 0000000..dd1d7db
--- /dev/null
+++ b/core/java/android/view/ContextMenu.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2007 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 android.view;
+
+import android.app.Activity;
+import android.graphics.drawable.Drawable;
+import android.widget.AdapterView;
+
+/**
+ * Extension of {@link Menu} for context menus providing functionality to modify
+ * the header of the context menu.
+ * <p>
+ * Context menus do not support item shortcuts and item icons.
+ * <p>
+ * To show a context menu on long click, most clients will want to call
+ * {@link Activity#registerForContextMenu} and override
+ * {@link Activity#onCreateContextMenu}.
+ */
+public interface ContextMenu extends Menu {
+    /**
+     * Sets the context menu header's title to the title given in <var>titleRes</var>
+     * resource identifier.
+     * 
+     * @param titleRes The string resource identifier used for the title.
+     * @return This ContextMenu so additional setters can be called.
+     */
+    public ContextMenu setHeaderTitle(int titleRes);
+
+    /**
+     * Sets the context menu header's title to the title given in <var>title</var>.
+     * 
+     * @param title The character sequence used for the title.
+     * @return This ContextMenu so additional setters can be called.
+     */
+    public ContextMenu setHeaderTitle(CharSequence title);
+    
+    /**
+     * Sets the context menu header's icon to the icon given in <var>iconRes</var>
+     * resource id.
+     * 
+     * @param iconRes The resource identifier used for the icon.
+     * @return This ContextMenu so additional setters can be called.
+     */
+    public ContextMenu setHeaderIcon(int iconRes);
+
+    /**
+     * Sets the context menu header's icon to the icon given in <var>icon</var>
+     * {@link Drawable}.
+     * 
+     * @param icon The {@link Drawable} used for the icon.
+     * @return This ContextMenu so additional setters can be called.
+     */
+    public ContextMenu setHeaderIcon(Drawable icon);
+    
+    /**
+     * Sets the header of the context menu to the {@link View} given in
+     * <var>view</var>. This replaces the header title and icon (and those
+     * replace this).
+     * 
+     * @param view The {@link View} used for the header.
+     * @return This ContextMenu so additional setters can be called.
+     */
+    public ContextMenu setHeaderView(View view);
+    
+    /**
+     * Clears the header of the context menu.
+     */
+    public void clearHeader();
+    
+    /**
+     * Additional information regarding the creation of the context menu.  For example,
+     * {@link AdapterView}s use this to pass the exact item position within the adapter
+     * that initiated the context menu.
+     */
+    public interface ContextMenuInfo {
+    }
+}
diff --git a/core/java/android/view/ContextThemeWrapper.java b/core/java/android/view/ContextThemeWrapper.java
new file mode 100644
index 0000000..2045a98
--- /dev/null
+++ b/core/java/android/view/ContextThemeWrapper.java
@@ -0,0 +1,103 @@
+/*
+ * 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 android.view;
+
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.content.res.Resources;
+
+/**
+ * A ContextWrapper that allows you to modify the theme from what is in the 
+ * wrapped context. 
+ */
+public class ContextThemeWrapper extends ContextWrapper {
+    private Context mBase;
+    private int mThemeResource;
+    private Resources.Theme mTheme;
+    private LayoutInflater mInflater;
+
+    public ContextThemeWrapper() {
+        super(null);
+    }
+    
+    public ContextThemeWrapper(Context base, int themeres) {
+        super(base);
+        mBase = base;
+        mThemeResource = themeres;
+    }
+
+    @Override protected void attachBaseContext(Context newBase) {
+        super.attachBaseContext(newBase);
+        mBase = newBase;
+    }
+    
+    @Override public void setTheme(int resid) {
+        mThemeResource = resid;
+        initializeTheme();
+    }
+    
+    @Override public Resources.Theme getTheme() {
+        if (mTheme != null) {
+            return mTheme;
+        }
+
+        if (mThemeResource == 0) {
+            mThemeResource = com.android.internal.R.style.Theme;
+        }
+        initializeTheme();
+
+        return mTheme;
+    }
+
+    @Override public Object getSystemService(String name) {
+        if (LAYOUT_INFLATER_SERVICE.equals(name)) {
+            if (mInflater == null) {
+                mInflater = LayoutInflater.from(mBase).cloneInContext(this);
+            }
+            return mInflater;
+        }
+        return mBase.getSystemService(name);
+    }
+    
+    /**
+     * Called by {@link #setTheme} and {@link #getTheme} to apply a theme
+     * resource to the current Theme object.  Can override to change the
+     * default (simple) behavior.  This method will not be called in multiple
+     * threads simultaneously.
+     *
+     * @param theme The Theme object being modified.
+     * @param resid The theme style resource being applied to <var>theme</var>.
+     * @param first Set to true if this is the first time a style is being
+     *              applied to <var>theme</var>.
+     */
+    protected void onApplyThemeResource(Resources.Theme theme, int resid, boolean first) {
+        theme.applyStyle(resid, true);
+    }
+
+    private void initializeTheme() {
+        final boolean first = mTheme == null;
+        if (first) {
+            mTheme = getResources().newTheme();
+            Resources.Theme theme = mBase.getTheme();
+            if (theme != null) {
+                mTheme.setTo(theme);
+            }
+        }
+        onApplyThemeResource(mTheme, mThemeResource, first);
+    }
+}
+
diff --git a/core/java/android/view/Display.java b/core/java/android/view/Display.java
new file mode 100644
index 0000000..09ebeed5
--- /dev/null
+++ b/core/java/android/view/Display.java
@@ -0,0 +1,121 @@
+/*
+ * 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 android.view;
+
+import android.util.DisplayMetrics;
+
+public class Display
+{
+    /**
+     * Specify the default Display
+     */
+    public static final int DEFAULT_DISPLAY = 0;
+
+    
+    /**
+     * Use the WindowManager interface to create a Display object.
+     * Display gives you access to some information about a particular display
+     * connected to the device.
+     */
+    Display(int display) {
+        // initalize the statics when this class is first instansiated. This is
+        // done here instead of in the static block because Zygote
+        synchronized (mStaticInit) {
+            if (!mInitialized) {
+                nativeClassInit();
+                mInitialized = true;
+            }
+        }
+        mDisplay = display;
+        init(display);
+    }
+    
+    /**
+     * @return index of this display.
+     */
+    public int getDisplayId() {
+        return mDisplay;
+    }
+
+    /**
+     * @return the number of displays connected to the device.
+     */
+    native static int getDisplayCount();
+    
+    /**
+     * @return width of this display in pixels.
+     */
+    native public int getWidth();
+    
+    /**
+     * @return height of this display in pixels.
+     */
+    native public int getHeight();
+
+    /**
+     * @return orientation of this display.
+     */
+    native public int getOrientation();
+
+    /**
+     * @return pixel format of this display.
+     */
+    public int getPixelFormat() {
+        return mPixelFormat;
+    }
+    
+    /**
+     * @return refresh rate of this display in frames per second.
+     */
+    public float getRefreshRate() {
+        return mRefreshRate;
+    }
+    
+    /**
+     * Initialize a DisplayMetrics object from this display's data.
+     * 
+     * @param outMetrics
+     */
+    public void getMetrics(DisplayMetrics outMetrics) {
+        outMetrics.widthPixels  = getWidth();
+        outMetrics.heightPixels = getHeight();
+        outMetrics.density      = mDensity;
+        outMetrics.scaledDensity= outMetrics.density;
+        outMetrics.xdpi         = mDpiX;
+        outMetrics.ydpi         = mDpiY;
+    }
+
+    /*
+     * We use a class initializer to allow the native code to cache some
+     * field offsets.
+     */
+    native private static void nativeClassInit();
+    
+    private native void init(int display);
+
+    private int         mDisplay;
+    // Following fields are initialized from native code
+    private int         mPixelFormat;
+    private float       mRefreshRate;
+    private float       mDensity;
+    private float       mDpiX;
+    private float       mDpiY;
+    
+    private static final Object mStaticInit = new Object();
+    private static boolean mInitialized = false;
+}
+
diff --git a/core/java/android/view/FocusFinder.java b/core/java/android/view/FocusFinder.java
new file mode 100644
index 0000000..15fb839
--- /dev/null
+++ b/core/java/android/view/FocusFinder.java
@@ -0,0 +1,480 @@
+/*
+ * Copyright (C) 2007 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 android.view;
+
+import android.graphics.Rect;
+
+import java.util.ArrayList;
+
+/**
+ * The algorithm used for finding the next focusable view in a given direction
+ * from a view that currently has focus.
+ */
+public class FocusFinder {
+
+    private static ThreadLocal<FocusFinder> tlFocusFinder =
+            new ThreadLocal<FocusFinder>() {
+
+                protected FocusFinder initialValue() {
+                    return new FocusFinder();
+                }
+            };
+
+    /**
+     * Get the focus finder for this thread.
+     */
+    public static FocusFinder getInstance() {
+        return tlFocusFinder.get();
+    }
+
+    Rect mFocusedRect = new Rect();
+    Rect mOtherRect = new Rect();
+    Rect mBestCandidateRect = new Rect();
+
+    // enforce thread local access
+    private FocusFinder() {}
+
+    /**
+     * Find the next view to take focus in root's descendants, starting from the view
+     * that currently is focused.
+     * @param root Contains focused
+     * @param focused Has focus now.
+     * @param direction Direction to look.
+     * @return The next focusable view, or null if none exists.
+     */
+    public final View findNextFocus(ViewGroup root, View focused, int direction) {
+
+        if (focused != null) {
+            // check for user specified next focus
+            View userSetNextFocus = focused.findUserSetNextFocus(root, direction);
+            if (userSetNextFocus != null &&
+                userSetNextFocus.isFocusable() &&
+                (!userSetNextFocus.isInTouchMode() ||
+                 userSetNextFocus.isFocusableInTouchMode())) {
+                return userSetNextFocus;
+            }
+
+            // fill in interesting rect from focused
+            focused.getFocusedRect(mFocusedRect);
+            root.offsetDescendantRectToMyCoords(focused, mFocusedRect);
+        } else {
+            // make up a rect at top left or bottom right of root
+            switch (direction) {
+                case View.FOCUS_RIGHT:
+                case View.FOCUS_DOWN:
+                    final int rootTop = root.getScrollY();
+                    final int rootLeft = root.getScrollX();
+                    mFocusedRect.set(rootLeft, rootTop, rootLeft, rootTop);
+                    break;
+
+                case View.FOCUS_LEFT:
+                case View.FOCUS_UP:
+                    final int rootBottom = root.getScrollY() + root.getHeight();
+                    final int rootRight = root.getScrollX() + root.getWidth();
+                    mFocusedRect.set(rootRight, rootBottom,
+                            rootRight, rootBottom);
+                    break;
+            }
+        }
+        return findNextFocus(root, focused, mFocusedRect, direction);
+    }
+
+    /**
+     * Find the next view to take focus in root's descendants, searching from
+     * a particular rectangle in root's coordinates.
+     * @param root Contains focusedRect.
+     * @param focusedRect The starting point of the search.
+     * @param direction Direction to look.
+     * @return The next focusable view, or null if none exists.
+     */
+    public View findNextFocusFromRect(ViewGroup root, Rect focusedRect, int direction) {
+        return findNextFocus(root, null, focusedRect, direction);
+    }
+
+    private View findNextFocus(ViewGroup root, View focused, Rect focusedRect, int direction) {
+        ArrayList<View> focusables = root.getFocusables(direction);
+
+        // initialize the best candidate to something impossible
+        // (so the first plausible view will become the best choice)
+        mBestCandidateRect.set(focusedRect);
+        switch(direction) {
+            case View.FOCUS_LEFT:
+                mBestCandidateRect.offset(focusedRect.width() + 1, 0);
+                break;
+            case View.FOCUS_RIGHT:
+                mBestCandidateRect.offset(-(focusedRect.width() + 1), 0);
+                break;
+            case View.FOCUS_UP:
+                mBestCandidateRect.offset(0, focusedRect.height() + 1);
+                break;
+            case View.FOCUS_DOWN:
+                mBestCandidateRect.offset(0, -(focusedRect.height() + 1));
+        }
+
+        View closest = null;
+
+        int numFocusables = focusables.size();
+        for (int i = 0; i < numFocusables; i++) {
+            View focusable = focusables.get(i);
+
+            // only interested in other non-root views
+            if (focusable == focused || focusable == root) continue;
+
+            // get visible bounds of other view in same coordinate system
+            focusable.getDrawingRect(mOtherRect);
+            root.offsetDescendantRectToMyCoords(focusable, mOtherRect);
+
+            if (isBetterCandidate(direction, focusedRect, mOtherRect, mBestCandidateRect)) {
+                mBestCandidateRect.set(mOtherRect);
+                closest = focusable;
+            }
+        }
+        return closest;
+    }
+
+    /**
+     * Is rect1 a better candidate than rect2 for a focus search in a particular
+     * direction from a source rect?  This is the core routine that determines
+     * the order of focus searching.
+     * @param direction the direction (up, down, left, right)
+     * @param source The source we are searching from
+     * @param rect1 The candidate rectangle
+     * @param rect2 The current best candidate.
+     * @return Whether the candidate is the new best.
+     */
+    boolean isBetterCandidate(int direction, Rect source, Rect rect1, Rect rect2) {
+
+        // to be a better candidate, need to at least be a candidate in the first
+        // place :)
+        if (!isCandidate(source, rect1, direction)) {
+            return false;
+        }
+
+        // we know that rect1 is a candidate.. if rect2 is not a candidate,
+        // rect1 is better
+        if (!isCandidate(source, rect2, direction)) {
+            return true;
+        }
+
+        // if rect1 is better by beam, it wins
+        if (beamBeats(direction, source, rect1, rect2)) {
+            return true;
+        }
+
+        // if rect2 is better, then rect1 cant' be :)
+        if (beamBeats(direction, source, rect2, rect1)) {
+            return false;
+        }
+
+        // otherwise, do fudge-tastic comparison of the major and minor axis
+        return (getWeightedDistanceFor(
+                        majorAxisDistance(direction, source, rect1),
+                        minorAxisDistance(direction, source, rect1))
+                < getWeightedDistanceFor(
+                        majorAxisDistance(direction, source, rect2),
+                        minorAxisDistance(direction, source, rect2)));
+    }
+
+    /**
+     * One rectangle may be another candidate than another by virtue of being
+     * exclusively in the beam of the source rect.
+     * @return Whether rect1 is a better candidate than rect2 by virtue of it being in src's
+     *      beam
+     */
+    boolean beamBeats(int direction, Rect source, Rect rect1, Rect rect2) {
+        final boolean rect1InSrcBeam = beamsOverlap(direction, source, rect1);
+        final boolean rect2InSrcBeam = beamsOverlap(direction, source, rect2);
+
+        // if rect1 isn't exclusively in the src beam, it doesn't win
+        if (rect2InSrcBeam || !rect1InSrcBeam) {
+            return false;
+        }
+
+        // we know rect1 is in the beam, and rect2 is not
+
+        // if rect1 is to the direction of, and rect2 is not, rect1 wins.
+        // for example, for direction left, if rect1 is to the left of the source
+        // and rect2 is below, then we always prefer the in beam rect1, since rect2
+        // could be reached by going down.
+        if (!isToDirectionOf(direction, source, rect2)) {
+            return true;
+        }
+
+        // for horizontal directions, being exclusively in beam always wins
+        if ((direction == View.FOCUS_LEFT || direction == View.FOCUS_RIGHT)) {
+            return true;
+        }        
+
+        // for vertical directions, beams only beat up to a point:
+        // now, as long as rect2 isn't completely closer, rect1 wins
+        // e.g for direction down, completely closer means for rect2's top
+        // edge to be closer to the source's top edge than rect1's bottom edge.
+        return (majorAxisDistance(direction, source, rect1)
+                < majorAxisDistanceToFarEdge(direction, source, rect2));
+    }
+
+    /**
+     * Fudge-factor opportunity: how to calculate distance given major and minor
+     * axis distances.  Warning: this fudge factor is finely tuned, be sure to
+     * run all focus tests if you dare tweak it.
+     */
+    int getWeightedDistanceFor(int majorAxisDistance, int minorAxisDistance) {
+        return 13 * majorAxisDistance * majorAxisDistance
+                + minorAxisDistance * minorAxisDistance;
+    }
+
+    /**
+     * Is destRect a candidate for the next focus given the direction?  This
+     * checks whether the dest is at least partially to the direction of (e.g left of)
+     * from source.
+     *
+     * Includes an edge case for an empty rect (which is used in some cases when
+     * searching from a point on the screen).
+     */
+    boolean isCandidate(Rect srcRect, Rect destRect, int direction) {
+        switch (direction) {
+            case View.FOCUS_LEFT:
+                return (srcRect.right > destRect.right || srcRect.left >= destRect.right) 
+                        && srcRect.left > destRect.left;
+            case View.FOCUS_RIGHT:
+                return (srcRect.left < destRect.left || srcRect.right <= destRect.left)
+                        && srcRect.right < destRect.right;
+            case View.FOCUS_UP:
+                return (srcRect.bottom > destRect.bottom || srcRect.top >= destRect.bottom)
+                        && srcRect.top > destRect.top;
+            case View.FOCUS_DOWN:
+                return (srcRect.top < destRect.top || srcRect.bottom <= destRect.top)
+                        && srcRect.bottom < destRect.bottom;
+        }
+        throw new IllegalArgumentException("direction must be one of "
+                + "{FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, FOCUS_RIGHT}.");
+    }
+
+
+    /**
+     * Do the "beams" w.r.t the given direcition's axos of rect1 and rect2 overlap?
+     * @param direction the direction (up, down, left, right)
+     * @param rect1 The first rectangle
+     * @param rect2 The second rectangle
+     * @return whether the beams overlap
+     */
+    boolean beamsOverlap(int direction, Rect rect1, Rect rect2) {
+        switch (direction) {
+            case View.FOCUS_LEFT:
+            case View.FOCUS_RIGHT:
+                return (rect2.bottom >= rect1.top) && (rect2.top <= rect1.bottom);
+            case View.FOCUS_UP:
+            case View.FOCUS_DOWN:
+                return (rect2.right >= rect1.left) && (rect2.left <= rect1.right);
+        }
+        throw new IllegalArgumentException("direction must be one of "
+                + "{FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, FOCUS_RIGHT}.");
+    }
+
+    /**
+     * e.g for left, is 'to left of'
+     */
+    boolean isToDirectionOf(int direction, Rect src, Rect dest) {
+        switch (direction) {
+            case View.FOCUS_LEFT:
+                return src.left >= dest.right;
+            case View.FOCUS_RIGHT:
+                return src.right <= dest.left;
+            case View.FOCUS_UP:
+                return src.top >= dest.bottom;
+            case View.FOCUS_DOWN:
+                return src.bottom <= dest.top;
+        }
+        throw new IllegalArgumentException("direction must be one of "
+                + "{FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, FOCUS_RIGHT}.");
+    }
+
+    /**
+     * @return The distance from the edge furthest in the given direction
+     *   of source to the edge nearest in the given direction of dest.  If the
+     *   dest is not in the direction from source, return 0.
+     */
+    static int majorAxisDistance(int direction, Rect source, Rect dest) {
+        return Math.max(0, majorAxisDistanceRaw(direction, source, dest));
+    }
+
+    static int majorAxisDistanceRaw(int direction, Rect source, Rect dest) {
+        switch (direction) {
+            case View.FOCUS_LEFT:
+                return source.left - dest.right;
+            case View.FOCUS_RIGHT:
+                return dest.left - source.right;
+            case View.FOCUS_UP:
+                return source.top - dest.bottom;
+            case View.FOCUS_DOWN:
+                return dest.top - source.bottom;
+        }
+        throw new IllegalArgumentException("direction must be one of "
+                + "{FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, FOCUS_RIGHT}.");
+    }
+
+    /**
+     * @return The distance along the major axis w.r.t the direction from the
+     *   edge of source to the far edge of dest. If the
+     *   dest is not in the direction from source, return 1 (to break ties with
+     *   {@link #majorAxisDistance}).
+     */
+    static int majorAxisDistanceToFarEdge(int direction, Rect source, Rect dest) {
+        return Math.max(1, majorAxisDistanceToFarEdgeRaw(direction, source, dest));
+    }
+
+    static int majorAxisDistanceToFarEdgeRaw(int direction, Rect source, Rect dest) {
+        switch (direction) {
+            case View.FOCUS_LEFT:
+                return source.left - dest.left;
+            case View.FOCUS_RIGHT:
+                return dest.right - source.right;
+            case View.FOCUS_UP:
+                return source.top - dest.top;
+            case View.FOCUS_DOWN:
+                return dest.bottom - source.bottom;
+        }
+        throw new IllegalArgumentException("direction must be one of "
+                + "{FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, FOCUS_RIGHT}.");
+    }
+
+    /**
+     * Find the distance on the minor axis w.r.t the direction to the nearest
+     * edge of the destination rectange.
+     * @param direction the direction (up, down, left, right)
+     * @param source The source rect.
+     * @param dest The destination rect.
+     * @return The distance.
+     */
+    static int minorAxisDistance(int direction, Rect source, Rect dest) {
+        switch (direction) {
+            case View.FOCUS_LEFT:
+            case View.FOCUS_RIGHT:
+                // the distance between the center verticals
+                return Math.abs(
+                        ((source.top + source.height() / 2) -
+                        ((dest.top + dest.height() / 2))));
+            case View.FOCUS_UP:
+            case View.FOCUS_DOWN:
+                // the distance between the center horizontals
+                return Math.abs(
+                        ((source.left + source.width() / 2) -
+                        ((dest.left + dest.width() / 2))));
+        }
+        throw new IllegalArgumentException("direction must be one of "
+                + "{FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, FOCUS_RIGHT}.");
+    }
+
+    /**
+     * Find the nearest touchable view to the specified view.
+     * 
+     * @param root The root of the tree in which to search
+     * @param x X coordinate from which to start the search
+     * @param y Y coordinate from which to start the search
+     * @param direction Direction to look
+     * @param deltas Offset from the <x, y> to the edge of the nearest view. Note that this array
+     *        may already be populated with values.
+     * @return The nearest touchable view, or null if none exists.
+     */
+    public View findNearestTouchable(ViewGroup root, int x, int y, int direction, int[] deltas) {
+        ArrayList<View> touchables = root.getTouchables();
+        int minDistance = Integer.MAX_VALUE;
+        View closest = null;
+
+        int numTouchables = touchables.size();
+        
+        int edgeSlop = ViewConfiguration.get(root.mContext).getScaledEdgeSlop();
+        
+        Rect closestBounds = new Rect();
+        Rect touchableBounds = mOtherRect;
+        
+        for (int i = 0; i < numTouchables; i++) {
+            View touchable = touchables.get(i);
+
+            // get visible bounds of other view in same coordinate system
+            touchable.getDrawingRect(touchableBounds);
+            
+            root.offsetRectBetweenParentAndChild(touchable, touchableBounds, true, true);
+
+            if (!isTouchCandidate(x, y, touchableBounds, direction)) {
+                continue;
+            }
+
+            int distance = Integer.MAX_VALUE;
+
+            switch (direction) {
+            case View.FOCUS_LEFT:
+                distance = x - touchableBounds.right + 1;
+                break;
+            case View.FOCUS_RIGHT:
+                distance = touchableBounds.left;
+                break;
+            case View.FOCUS_UP:
+                distance = y - touchableBounds.bottom + 1;
+                break;
+            case View.FOCUS_DOWN:
+                distance = touchableBounds.top;
+                break;
+            }
+
+            if (distance < edgeSlop) {
+                // Give preference to innermost views
+                if (closest == null ||
+                        closestBounds.contains(touchableBounds) ||
+                        (!touchableBounds.contains(closestBounds) && distance < minDistance)) {
+                    minDistance = distance;
+                    closest = touchable;
+                    closestBounds.set(touchableBounds);
+                    switch (direction) {
+                    case View.FOCUS_LEFT:
+                        deltas[0] = -distance;
+                        break;
+                    case View.FOCUS_RIGHT:
+                        deltas[0] = distance;
+                        break;
+                    case View.FOCUS_UP:
+                        deltas[1] = -distance;
+                        break;
+                    case View.FOCUS_DOWN:
+                        deltas[1] = distance;
+                        break;
+                    }
+                }
+            }
+        }
+        return closest;
+    }
+
+
+    /**
+     * Is destRect a candidate for the next touch given the direction?
+     */
+    private boolean isTouchCandidate(int x, int y, Rect destRect, int direction) {
+        switch (direction) {
+            case View.FOCUS_LEFT:
+                return destRect.left <= x && destRect.top <= y && y <= destRect.bottom;
+            case View.FOCUS_RIGHT:
+                return destRect.left >= x && destRect.top <= y && y <= destRect.bottom;
+            case View.FOCUS_UP:
+                return destRect.top <= y && destRect.left <= x && x <= destRect.right;
+            case View.FOCUS_DOWN:
+                return destRect.top >= y && destRect.left <= x && x <= destRect.right;
+        }
+        throw new IllegalArgumentException("direction must be one of "
+                + "{FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, FOCUS_RIGHT}.");
+    }
+}
diff --git a/core/java/android/view/FocusFinderHelper.java b/core/java/android/view/FocusFinderHelper.java
new file mode 100644
index 0000000..69dc056
--- /dev/null
+++ b/core/java/android/view/FocusFinderHelper.java
@@ -0,0 +1,59 @@
+/*
+ * 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 android.view;
+
+import android.graphics.Rect;
+
+/**
+ * A helper class that allows unit tests to access FocusFinder methods.
+ * @hide
+ */
+public class FocusFinderHelper {
+    
+    private FocusFinder mFocusFinder;
+
+    /**
+     * Wrap the FocusFinder object
+     */
+    public FocusFinderHelper(FocusFinder focusFinder) {
+        mFocusFinder = focusFinder;
+    }
+    
+    public boolean isBetterCandidate(int direction, Rect source, Rect rect1, Rect rect2) {
+        return mFocusFinder.isBetterCandidate(direction, source, rect1, rect2);
+    }
+    
+    public boolean beamBeats(int direction, Rect source, Rect rect1, Rect rect2) {
+        return mFocusFinder.beamBeats(direction, source, rect1, rect2);
+    }
+
+    public boolean isCandidate(Rect srcRect, Rect destRect, int direction) {
+        return mFocusFinder.isCandidate(srcRect, destRect, direction);
+    }
+    
+    public boolean beamsOverlap(int direction, Rect rect1, Rect rect2) {
+        return mFocusFinder.beamsOverlap(direction, rect1, rect2);
+    }
+    
+    public static int majorAxisDistance(int direction, Rect source, Rect dest) {
+        return FocusFinder.majorAxisDistance(direction, source, dest);
+    }
+    
+    public static int majorAxisDistanceToFarEdge(int direction, Rect source, Rect dest) {
+        return FocusFinder.majorAxisDistanceToFarEdge(direction, source, dest);
+    }
+}
diff --git a/core/java/android/view/GestureDetector.java b/core/java/android/view/GestureDetector.java
new file mode 100644
index 0000000..e0231a7b
--- /dev/null
+++ b/core/java/android/view/GestureDetector.java
@@ -0,0 +1,564 @@
+/*
+ * 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 android.view;
+
+import android.os.Handler;
+import android.os.Message;
+import android.content.Context;
+
+/**
+ * Detects various gestures and events using the supplied {@link MotionEvent}s.
+ * The {@link OnGestureListener} callback will notify users when a particular
+ * motion event has occurred. This class should only be used with {@link MotionEvent}s
+ * reported via touch (don't use for trackball events).
+ *
+ * To use this class:
+ * <ul>
+ *  <li>Create an instance of the {@code GestureDetector} for your {@link View}
+ *  <li>In the {@link View#onTouchEvent(MotionEvent)} method ensure you call
+ *          {@link #onTouchEvent(MotionEvent)}. The methods defined in your callback
+ *          will be executed when the events occur.
+ * </ul>
+ */
+public class GestureDetector {
+    /**
+     * The listener that is used to notify when gestures occur.
+     * If you want to listen for all the different gestures then implement
+     * this interface. If you only want to listen for a subset it might
+     * be easier to extend {@link SimpleOnGestureListener}.
+     */
+    public interface OnGestureListener {
+
+        /**
+         * Notified when a tap occurs with the down {@link MotionEvent}
+         * that triggered it. This will be triggered immediately for
+         * every down event. All other events should be preceded by this.
+         *
+         * @param e The down motion event.
+         */
+        boolean onDown(MotionEvent e);
+
+        /**
+         * The user has performed a down {@link MotionEvent} and not performed
+         * a move or up yet. This event is commonly used to provide visual
+         * feedback to the user to let them know that their action has been
+         * recognized i.e. highlight an element.
+         *
+         * @param e The down motion event
+         */
+        void onShowPress(MotionEvent e);
+
+        /**
+         * Notified when a tap occurs with the up {@link MotionEvent}
+         * that triggered it.
+         *
+         * @param e The up motion event that completed the first tap
+         * @return true if the event is consumed, else false
+         */
+        boolean onSingleTapUp(MotionEvent e);
+
+        /**
+         * Notified when a scroll occurs with the initial on down {@link MotionEvent} and the
+         * current move {@link MotionEvent}. The distance in x and y is also supplied for
+         * convenience.
+         *
+         * @param e1 The first down motion event that started the scrolling.
+         * @param e2 The move motion event that triggered the current onScroll.
+         * @param distanceX The distance along the X axis that has been scrolled since the last
+         *              call to onScroll. This is NOT the distance between {@code e1}
+         *              and {@code e2}.
+         * @param distanceY The distance along the Y axis that has been scrolled since the last
+         *              call to onScroll. This is NOT the distance between {@code e1}
+         *              and {@code e2}.
+         * @return true if the event is consumed, else false
+         */
+        boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY);
+
+        /**
+         * Notified when a long press occurs with the initial on down {@link MotionEvent}
+         * that trigged it.
+         *
+         * @param e The initial on down motion event that started the longpress.
+         */
+        void onLongPress(MotionEvent e);
+
+        /**
+         * Notified of a fling event when it occurs with the initial on down {@link MotionEvent}
+         * and the matching up {@link MotionEvent}. The calculated velocity is supplied along
+         * the x and y axis in pixels per second.
+         *
+         * @param e1 The first down motion event that started the fling.
+         * @param e2 The move motion event that triggered the current onFling.
+         * @param velocityX The velocity of this fling measured in pixels per second
+         *              along the x axis.
+         * @param velocityY The velocity of this fling measured in pixels per second
+         *              along the y axis.
+         * @return true if the event is consumed, else false
+         */
+        boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY);
+    }
+
+    /**
+     * The listener that is used to notify when a double-tap or a confirmed
+     * single-tap occur.
+     */
+    public interface OnDoubleTapListener {
+        /**
+         * Notified when a single-tap occurs.
+         * <p>
+         * Unlike {@link OnGestureListener#onSingleTapUp(MotionEvent)}, this
+         * will only be called after the detector is confident that the user's
+         * first tap is not followed by a second tap leading to a double-tap
+         * gesture.
+         *
+         * @param e The down motion event of the single-tap.
+         * @return true if the event is consumed, else false
+         */
+        boolean onSingleTapConfirmed(MotionEvent e);
+ 
+        /**
+         * Notified when a double-tap occurs.
+         *
+         * @param e The down motion event of the first tap of the double-tap.
+         * @return true if the event is consumed, else false
+         */
+        boolean onDoubleTap(MotionEvent e);
+
+        /**
+         * Notified when an event within a double-tap gesture occurs, including
+         * the down, move, and up events.
+         *
+         * @param e The motion event that occurred during the double-tap gesture.
+         * @return true if the event is consumed, else false
+         */
+        boolean onDoubleTapEvent(MotionEvent e);
+    }
+
+    /**
+     * A convenience class to extend when you only want to listen for a subset
+     * of all the gestures. This implements all methods in the
+     * {@link OnGestureListener} and {@link OnDoubleTapListener} but does
+     * nothing and return {@code false} for all applicable methods.
+     */
+    public static class SimpleOnGestureListener implements OnGestureListener, OnDoubleTapListener {
+        public boolean onSingleTapUp(MotionEvent e) {
+            return false;
+        }
+
+        public void onLongPress(MotionEvent e) {
+        }
+
+        public boolean onScroll(MotionEvent e1, MotionEvent e2,
+                float distanceX, float distanceY) {
+            return false;
+        }
+
+        public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
+                float velocityY) {
+            return false;
+        }
+
+        public void onShowPress(MotionEvent e) {
+        }
+
+        public boolean onDown(MotionEvent e) {
+            return false;
+        }
+
+        public boolean onDoubleTap(MotionEvent e) {
+            return false;
+        }
+
+        public boolean onDoubleTapEvent(MotionEvent e) {
+            return false;
+        }
+
+        public boolean onSingleTapConfirmed(MotionEvent e) {
+            return false;
+        }
+    }
+
+    // TODO: ViewConfiguration
+    private int mBiggerTouchSlopSquare = 20 * 20;
+
+    private int mTouchSlopSquare;
+    private int mDoubleTapSlopSquare;
+    private int mMinimumFlingVelocity;
+
+    private static final int LONGPRESS_TIMEOUT = ViewConfiguration.getLongPressTimeout();
+    private static final int TAP_TIMEOUT = ViewConfiguration.getTapTimeout();
+    // TODO make new double-tap timeout, and define its events (i.e. either time
+    // between down-down or time between up-down)
+    private static final int DOUBLE_TAP_TIMEOUT = ViewConfiguration.getDoubleTapTimeout();
+
+    // constants for Message.what used by GestureHandler below
+    private static final int SHOW_PRESS = 1;
+    private static final int LONG_PRESS = 2;
+    private static final int TAP = 3;
+
+    private final Handler mHandler;
+    private final OnGestureListener mListener;
+    private OnDoubleTapListener mDoubleTapListener;
+
+    private boolean mStillDown;
+    private boolean mInLongPress;
+    private boolean mAlwaysInTapRegion;
+    private boolean mAlwaysInBiggerTapRegion;
+
+    private MotionEvent mCurrentDownEvent;
+    private MotionEvent mPreviousUpEvent;
+
+    /**
+     * True when the user is still touching for the second tap (down, move, and
+     * up events). Can only be true if there is a double tap listener attached.
+     */
+    private boolean mIsDoubleTapping;
+
+    private float mLastMotionY;
+    private float mLastMotionX;
+
+    private boolean mIsLongpressEnabled;
+
+    /**
+     * Determines speed during touch scrolling
+     */
+    private VelocityTracker mVelocityTracker;
+
+    private class GestureHandler extends Handler {
+        GestureHandler() {
+            super();
+        }
+
+        GestureHandler(Handler handler) {
+            super(handler.getLooper());
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+            case SHOW_PRESS:
+                mListener.onShowPress(mCurrentDownEvent);
+                break;
+                
+            case LONG_PRESS:
+                dispatchLongPress();
+                break;
+                
+            case TAP:
+                // If the user's finger is still down, do not count it as a tap
+                if (mDoubleTapListener != null && !mStillDown) {
+                    mDoubleTapListener.onSingleTapConfirmed(mCurrentDownEvent);
+                }
+                break;
+
+            default:
+                throw new RuntimeException("Unknown message " + msg); //never
+            }
+        }
+    }
+
+    /**
+     * Creates a GestureDetector with the supplied listener.
+     * This variant of the constructor should be used from a non-UI thread 
+     * (as it allows specifying the Handler).
+     * 
+     * @param listener the listener invoked for all the callbacks, this must
+     * not be null.
+     * @param handler the handler to use
+     *
+     * @throws NullPointerException if either {@code listener} or
+     * {@code handler} is null.
+     *
+     * @deprecated Use {@link #GestureDetector(android.content.Context,
+     *      android.view.GestureDetector.OnGestureListener, android.os.Handler)} instead.
+     */
+    @Deprecated
+    public GestureDetector(OnGestureListener listener, Handler handler) {
+        this(null, listener, handler);
+    }
+
+    /**
+     * Creates a GestureDetector with the supplied listener.
+     * You may only use this constructor from a UI thread (this is the usual situation).
+     * @see android.os.Handler#Handler()
+     * 
+     * @param listener the listener invoked for all the callbacks, this must
+     * not be null.
+     * 
+     * @throws NullPointerException if {@code listener} is null.
+     *
+     * @deprecated Use {@link #GestureDetector(android.content.Context,
+     *      android.view.GestureDetector.OnGestureListener)} instead.
+     */
+    @Deprecated
+    public GestureDetector(OnGestureListener listener) {
+        this(null, listener, null);
+    }
+
+    /**
+     * Creates a GestureDetector with the supplied listener.
+     * You may only use this constructor from a UI thread (this is the usual situation).
+     * @see android.os.Handler#Handler()
+     *
+     * @param context the application's context
+     * @param listener the listener invoked for all the callbacks, this must
+     * not be null.
+     *
+     * @throws NullPointerException if {@code listener} is null.
+     */
+    public GestureDetector(Context context, OnGestureListener listener) {
+        this(context, listener, null);
+    }
+
+    /**
+     * Creates a GestureDetector with the supplied listener.
+     * You may only use this constructor from a UI thread (this is the usual situation).
+     * @see android.os.Handler#Handler()
+     *
+     * @param context the application's context
+     * @param listener the listener invoked for all the callbacks, this must
+     * not be null.
+     * @param handler the handler to use     
+     *
+     * @throws NullPointerException if {@code listener} is null.
+     */
+    public GestureDetector(Context context, OnGestureListener listener, Handler handler) {
+        if (handler != null) {
+            mHandler = new GestureHandler(handler);
+        } else {
+            mHandler = new GestureHandler();
+        }
+        mListener = listener;
+        if (listener instanceof OnDoubleTapListener) {
+            setOnDoubleTapListener((OnDoubleTapListener) listener);
+        }
+        init(context);
+    }
+
+    private void init(Context context) {
+        if (mListener == null) {
+            throw new NullPointerException("OnGestureListener must not be null");
+        }
+        mIsLongpressEnabled = true;
+
+        // Fallback to support pre-donuts releases
+        int touchSlop, doubleTapSlop;
+        if (context == null) {
+            //noinspection deprecation
+            touchSlop = ViewConfiguration.getTouchSlop();
+            doubleTapSlop = ViewConfiguration.getDoubleTapSlop();
+            //noinspection deprecation
+            mMinimumFlingVelocity = ViewConfiguration.getMinimumFlingVelocity();
+        } else {
+            final ViewConfiguration configuration = ViewConfiguration.get(context);
+            touchSlop = configuration.getScaledTouchSlop();
+            doubleTapSlop = configuration.getScaledDoubleTapSlop();
+            mMinimumFlingVelocity = configuration.getScaledMinimumFlingVelocity();
+        }
+        mTouchSlopSquare = touchSlop * touchSlop;
+        mDoubleTapSlopSquare = doubleTapSlop * doubleTapSlop;
+    }
+
+    /**
+     * Sets the listener which will be called for double-tap and related
+     * gestures.
+     * 
+     * @param onDoubleTapListener the listener invoked for all the callbacks, or
+     *        null to stop listening for double-tap gestures.
+     */
+    public void setOnDoubleTapListener(OnDoubleTapListener onDoubleTapListener) {
+        mDoubleTapListener = onDoubleTapListener;
+    }
+
+    /**
+     * Set whether longpress is enabled, if this is enabled when a user
+     * presses and holds down you get a longpress event and nothing further.
+     * If it's disabled the user can press and hold down and then later
+     * moved their finger and you will get scroll events. By default
+     * longpress is enabled.
+     *
+     * @param isLongpressEnabled whether longpress should be enabled.
+     */
+    public void setIsLongpressEnabled(boolean isLongpressEnabled) {
+        mIsLongpressEnabled = isLongpressEnabled;
+    }
+
+    /**
+     * @return true if longpress is enabled, else false.
+     */
+    public boolean isLongpressEnabled() {
+        return mIsLongpressEnabled;
+    }
+
+    /**
+     * Analyzes the given motion event and if applicable triggers the
+     * appropriate callbacks on the {@link OnGestureListener} supplied.
+     *
+     * @param ev The current motion event.
+     * @return true if the {@link OnGestureListener} consumed the event,
+     *              else false.
+     */
+    public boolean onTouchEvent(MotionEvent ev) {
+        final int action = ev.getAction();
+        final float y = ev.getY();
+        final float x = ev.getX();
+
+        if (mVelocityTracker == null) {
+            mVelocityTracker = VelocityTracker.obtain();
+        }
+        mVelocityTracker.addMovement(ev);
+
+        boolean handled = false;
+
+        switch (action) {
+        case MotionEvent.ACTION_DOWN:
+            if (mDoubleTapListener != null) {
+                boolean hadTapMessage = mHandler.hasMessages(TAP);
+                if (hadTapMessage) mHandler.removeMessages(TAP);
+                if ((mCurrentDownEvent != null) && (mPreviousUpEvent != null) && hadTapMessage &&
+                        isConsideredDoubleTap(mCurrentDownEvent, mPreviousUpEvent, ev)) {
+                    // This is a second tap
+                    mIsDoubleTapping = true;
+                    // Give a callback with the first tap of the double-tap
+                    handled |= mDoubleTapListener.onDoubleTap(mCurrentDownEvent);
+                    // Give a callback with down event of the double-tap
+                    handled |= mDoubleTapListener.onDoubleTapEvent(ev);
+                } else {
+                    // This is a first tap
+                    mHandler.sendEmptyMessageDelayed(TAP, DOUBLE_TAP_TIMEOUT);
+                }
+            }
+
+            mLastMotionX = x;
+            mLastMotionY = y;
+            mCurrentDownEvent = MotionEvent.obtain(ev);
+            mAlwaysInTapRegion = true;
+            mAlwaysInBiggerTapRegion = true;
+            mStillDown = true;
+            mInLongPress = false;
+            
+            if (mIsLongpressEnabled) {
+                mHandler.removeMessages(LONG_PRESS);
+                mHandler.sendEmptyMessageAtTime(LONG_PRESS, mCurrentDownEvent.getDownTime()
+                        + TAP_TIMEOUT + LONGPRESS_TIMEOUT);
+            }
+            mHandler.sendEmptyMessageAtTime(SHOW_PRESS, mCurrentDownEvent.getDownTime() + TAP_TIMEOUT);
+            handled |= mListener.onDown(ev);
+            break;
+
+        case MotionEvent.ACTION_MOVE:
+            if (mInLongPress) {
+                break;
+            }
+            final float scrollX = mLastMotionX - x;
+            final float scrollY = mLastMotionY - y;
+            if (mIsDoubleTapping) {
+                // Give the move events of the double-tap
+                handled |= mDoubleTapListener.onDoubleTapEvent(ev);
+            } else if (mAlwaysInTapRegion) {
+                final int deltaX = (int) (x - mCurrentDownEvent.getX());
+                final int deltaY = (int) (y - mCurrentDownEvent.getY());
+                int distance = (deltaX * deltaX) + (deltaY * deltaY);
+                if (distance > mTouchSlopSquare) {
+                    handled = mListener.onScroll(mCurrentDownEvent, ev, scrollX, scrollY);
+                    mLastMotionX = x;
+                    mLastMotionY = y;
+                    mAlwaysInTapRegion = false;
+                    mHandler.removeMessages(TAP);
+                    mHandler.removeMessages(SHOW_PRESS);
+                    mHandler.removeMessages(LONG_PRESS);
+                }
+                if (distance > mBiggerTouchSlopSquare) {
+                    mAlwaysInBiggerTapRegion = false;
+                }
+            } else if ((Math.abs(scrollX) >= 1) || (Math.abs(scrollY) >= 1)) {
+                handled = mListener.onScroll(mCurrentDownEvent, ev, scrollX, scrollY);
+                mLastMotionX = x;
+                mLastMotionY = y;
+            }
+            break;
+
+        case MotionEvent.ACTION_UP:
+            mStillDown = false;
+            MotionEvent currentUpEvent = MotionEvent.obtain(ev);
+            if (mIsDoubleTapping) {
+                // Finally, give the up event of the double-tap
+                handled |= mDoubleTapListener.onDoubleTapEvent(ev);
+                mIsDoubleTapping = false;
+                break;
+            } else if (mInLongPress) {
+                mHandler.removeMessages(TAP);
+                mInLongPress = false;
+                break;
+            }
+            if (mAlwaysInTapRegion) {
+                handled = mListener.onSingleTapUp(ev);
+            } else {
+
+                // A fling must travel the minimum tap distance
+                final VelocityTracker velocityTracker = mVelocityTracker;
+                velocityTracker.computeCurrentVelocity(1000);
+                final float velocityY = velocityTracker.getYVelocity();
+                final float velocityX = velocityTracker.getXVelocity();
+
+                if ((Math.abs(velocityY) > mMinimumFlingVelocity)
+                        || (Math.abs(velocityX) > mMinimumFlingVelocity)){
+                    handled = mListener.onFling(mCurrentDownEvent, currentUpEvent, velocityX, velocityY);
+                }
+            }
+            mPreviousUpEvent = MotionEvent.obtain(ev);
+            mVelocityTracker.recycle();
+            mVelocityTracker = null;
+            mHandler.removeMessages(SHOW_PRESS);
+            mHandler.removeMessages(LONG_PRESS);
+            break;
+        case MotionEvent.ACTION_CANCEL:
+            mHandler.removeMessages(SHOW_PRESS);
+            mHandler.removeMessages(LONG_PRESS);
+            mHandler.removeMessages(TAP);
+            mVelocityTracker.recycle();
+            mVelocityTracker = null;
+            mStillDown = false;
+            if (mInLongPress) {
+                mInLongPress = false;
+                break;
+            }
+        }
+        return handled;
+    }
+
+    private boolean isConsideredDoubleTap(MotionEvent firstDown, MotionEvent firstUp,
+            MotionEvent secondDown) {
+        if (!mAlwaysInBiggerTapRegion) {
+            return false;
+        }
+
+        if (secondDown.getEventTime() - firstUp.getEventTime() > DOUBLE_TAP_TIMEOUT) {
+            return false;
+        }
+
+        int deltaX = (int) firstDown.getX() - (int) secondDown.getX();
+        int deltaY = (int) firstDown.getY() - (int) secondDown.getY();
+        return (deltaX * deltaX + deltaY * deltaY < mDoubleTapSlopSquare);
+    }
+
+    private void dispatchLongPress() {
+        mHandler.removeMessages(TAP);
+        mInLongPress = true;
+        mListener.onLongPress(mCurrentDownEvent);
+    }
+}
diff --git a/core/java/android/view/Gravity.java b/core/java/android/view/Gravity.java
new file mode 100644
index 0000000..36d8ce6
--- /dev/null
+++ b/core/java/android/view/Gravity.java
@@ -0,0 +1,306 @@
+/*
+ * 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 android.view;
+import android.graphics.Rect;
+
+/**
+ * Standard constants and tools for placing an object within a potentially
+ * larger container.
+ */
+public class Gravity
+{
+    /** Constant indicating that no gravity has been set **/
+    public static final int NO_GRAVITY = 0x0000;
+    
+    /** Raw bit indicating the gravity for an axis has been specified. */
+    public static final int AXIS_SPECIFIED = 0x0001;
+
+    /** Raw bit controlling how the left/top edge is placed. */
+    public static final int AXIS_PULL_BEFORE = 0x0002;
+    /** Raw bit controlling how the right/bottom edge is placed. */
+    public static final int AXIS_PULL_AFTER = 0x0004;
+    /** Raw bit controlling whether the right/bottom edge is clipped to its
+     * container, based on the gravity direction being applied. */
+    public static final int AXIS_CLIP = 0x0008;
+
+    /** Bits defining the horizontal axis. */
+    public static final int AXIS_X_SHIFT = 0;
+    /** Bits defining the vertical axis. */
+    public static final int AXIS_Y_SHIFT = 4;
+
+    /** Push object to the top of its container, not changing its size. */
+    public static final int TOP = (AXIS_PULL_BEFORE|AXIS_SPECIFIED)<<AXIS_Y_SHIFT;
+    /** Push object to the bottom of its container, not changing its size. */
+    public static final int BOTTOM = (AXIS_PULL_AFTER|AXIS_SPECIFIED)<<AXIS_Y_SHIFT;
+    /** Push object to the left of its container, not changing its size. */
+    public static final int LEFT = (AXIS_PULL_BEFORE|AXIS_SPECIFIED)<<AXIS_X_SHIFT;
+    /** Push object to the right of its container, not changing its size. */
+    public static final int RIGHT = (AXIS_PULL_AFTER|AXIS_SPECIFIED)<<AXIS_X_SHIFT;
+
+    /** Place object in the vertical center of its container, not changing its
+     *  size. */
+    public static final int CENTER_VERTICAL = AXIS_SPECIFIED<<AXIS_Y_SHIFT;
+    /** Grow the vertical size of the object if needed so it completely fills
+     *  its container. */
+    public static final int FILL_VERTICAL = TOP|BOTTOM;
+
+    /** Place object in the horizontal center of its container, not changing its
+     *  size. */
+    public static final int CENTER_HORIZONTAL = AXIS_SPECIFIED<<AXIS_X_SHIFT;
+    /** Grow the horizontal size of the object if needed so it completely fills
+     *  its container. */
+    public static final int FILL_HORIZONTAL = LEFT|RIGHT;
+
+    /** Place the object in the center of its container in both the vertical
+     *  and horizontal axis, not changing its size. */
+    public static final int CENTER = CENTER_VERTICAL|CENTER_HORIZONTAL;
+
+    /** Grow the horizontal and vertical size of the object if needed so it
+     *  completely fills its container. */
+    public static final int FILL = FILL_VERTICAL|FILL_HORIZONTAL;
+
+    /** Flag to clip the edges of the object to its container along the
+     *  vertical axis. */
+    public static final int CLIP_VERTICAL = AXIS_CLIP<<AXIS_Y_SHIFT;
+    
+    /** Flag to clip the edges of the object to its container along the
+     *  horizontal axis. */
+    public static final int CLIP_HORIZONTAL = AXIS_CLIP<<AXIS_X_SHIFT;
+    
+    /**
+     * Binary mask to get the horizontal gravity of a gravity.
+     */
+    public static final int HORIZONTAL_GRAVITY_MASK = (AXIS_SPECIFIED |
+            AXIS_PULL_BEFORE | AXIS_PULL_AFTER) << AXIS_X_SHIFT;
+    /**
+     * Binary mask to get the vertical gravity of a gravity.
+     */
+    public static final int VERTICAL_GRAVITY_MASK = (AXIS_SPECIFIED |
+            AXIS_PULL_BEFORE | AXIS_PULL_AFTER) << AXIS_Y_SHIFT;
+
+    /** Special constant to enable clipping to an overall display along the
+     *  vertical dimension.  This is not applied by default by
+     *  {@link #apply(int, int, int, Rect, int, int, Rect)}; you must do so
+     *  yourself by calling {@link #applyDisplay}.
+     */
+    public static final int DISPLAY_CLIP_VERTICAL = 0x10000000;
+    
+    /** Special constant to enable clipping to an overall display along the
+     *  horizontal dimension.  This is not applied by default by
+     *  {@link #apply(int, int, int, Rect, int, int, Rect)}; you must do so
+     *  yourself by calling {@link #applyDisplay}.
+     */
+    public static final int DISPLAY_CLIP_HORIZONTAL = 0x01000000;
+    
+    /**
+     * Apply a gravity constant to an object.
+     * 
+     * @param gravity The desired placement of the object, as defined by the
+     *                constants in this class.
+     * @param w The horizontal size of the object.
+     * @param h The vertical size of the object.
+     * @param container The frame of the containing space, in which the object
+     *                  will be placed.  Should be large enough to contain the
+     *                  width and height of the object.
+     * @param outRect Receives the computed frame of the object in its
+     *                container.
+     */
+    public static void apply(int gravity, int w, int h, Rect container,
+                             Rect outRect) {
+        apply(gravity, w, h, container, 0, 0, outRect);
+    }
+
+    /**
+     * Apply a gravity constant to an object.
+     * 
+     * @param gravity The desired placement of the object, as defined by the
+     *                constants in this class.
+     * @param w The horizontal size of the object.
+     * @param h The vertical size of the object.
+     * @param container The frame of the containing space, in which the object
+     *                  will be placed.  Should be large enough to contain the
+     *                  width and height of the object.
+     * @param xAdj Offset to apply to the X axis.  If gravity is LEFT this
+     *             pushes it to the right; if gravity is RIGHT it pushes it to
+     *             the left; if gravity is CENTER_HORIZONTAL it pushes it to the
+     *             right or left; otherwise it is ignored.
+     * @param yAdj Offset to apply to the Y axis.  If gravity is TOP this pushes
+     *             it down; if gravity is BOTTOM it pushes it up; if gravity is
+     *             CENTER_VERTICAL it pushes it down or up; otherwise it is
+     *             ignored.
+     * @param outRect Receives the computed frame of the object in its
+     *                container.
+     */
+    public static void apply(int gravity, int w, int h, Rect container,
+                             int xAdj, int yAdj, Rect outRect) {
+        switch (gravity&((AXIS_PULL_BEFORE|AXIS_PULL_AFTER)<<AXIS_X_SHIFT)) {
+            case 0:
+                outRect.left = container.left
+                        + ((container.right - container.left - w)/2) + xAdj;
+                outRect.right = outRect.left + w;
+                if ((gravity&(AXIS_CLIP<<AXIS_X_SHIFT))
+                        == (AXIS_CLIP<<AXIS_X_SHIFT)) {
+                    if (outRect.left < container.left) {
+                        outRect.left = container.left;
+                    }
+                    if (outRect.right > container.right) {
+                        outRect.right = container.right;
+                    }
+                }
+                break;
+            case AXIS_PULL_BEFORE<<AXIS_X_SHIFT:
+                outRect.left = container.left + xAdj;
+                outRect.right = outRect.left + w;
+                if ((gravity&(AXIS_CLIP<<AXIS_X_SHIFT))
+                        == (AXIS_CLIP<<AXIS_X_SHIFT)) {
+                    if (outRect.right > container.right) {
+                        outRect.right = container.right;
+                    }
+                }
+                break;
+            case AXIS_PULL_AFTER<<AXIS_X_SHIFT:
+                outRect.right = container.right - xAdj;
+                outRect.left = outRect.right - w;
+                if ((gravity&(AXIS_CLIP<<AXIS_X_SHIFT))
+                        == (AXIS_CLIP<<AXIS_X_SHIFT)) {
+                    if (outRect.left < container.left) {
+                        outRect.left = container.left;
+                    }
+                }
+                break;
+            default:
+                outRect.left = container.left + xAdj;
+                outRect.right = container.right + xAdj;
+                break;
+        }
+        
+        switch (gravity&((AXIS_PULL_BEFORE|AXIS_PULL_AFTER)<<AXIS_Y_SHIFT)) {
+            case 0:
+                outRect.top = container.top
+                        + ((container.bottom - container.top - h)/2) + yAdj;
+                outRect.bottom = outRect.top + h;
+                if ((gravity&(AXIS_CLIP<<AXIS_Y_SHIFT))
+                        == (AXIS_CLIP<<AXIS_Y_SHIFT)) {
+                    if (outRect.top < container.top) {
+                        outRect.top = container.top;
+                    }
+                    if (outRect.bottom > container.bottom) {
+                        outRect.bottom = container.bottom;
+                    }
+                }
+                break;
+            case AXIS_PULL_BEFORE<<AXIS_Y_SHIFT:
+                outRect.top = container.top + yAdj;
+                outRect.bottom = outRect.top + h;
+                if ((gravity&(AXIS_CLIP<<AXIS_Y_SHIFT))
+                        == (AXIS_CLIP<<AXIS_Y_SHIFT)) {
+                    if (outRect.bottom > container.bottom) {
+                        outRect.bottom = container.bottom;
+                    }
+                }
+                break;
+            case AXIS_PULL_AFTER<<AXIS_Y_SHIFT:
+                outRect.bottom = container.bottom - yAdj;
+                outRect.top = outRect.bottom - h;
+                if ((gravity&(AXIS_CLIP<<AXIS_Y_SHIFT))
+                        == (AXIS_CLIP<<AXIS_Y_SHIFT)) {
+                    if (outRect.top < container.top) {
+                        outRect.top = container.top;
+                    }
+                }
+                break;
+            default:
+                outRect.top = container.top + yAdj;
+                outRect.bottom = container.bottom + yAdj;
+                break;
+        }
+    }
+
+    /**
+     * Apply addition gravity behavior based on the overall "display" that an
+     * object exists in.  This can be used after
+     * {@link #apply(int, int, int, Rect, int, int, Rect)} to place the object
+     * within a visible display.  By default this moves or clips the object
+     * to be visible in the display; the gravity flags
+     * {@link #DISPLAY_CLIP_HORIZONTAL} and {@link #DISPLAY_CLIP_VERTICAL}
+     * can be used to change this behavior.
+     * 
+     * @param gravity Gravity constants to modify the placement within the
+     * display.
+     * @param display The rectangle of the display in which the object is
+     * being placed.
+     * @param inoutObj Supplies the current object position; returns with it
+     * modified if needed to fit in the display.
+     */
+    public static void applyDisplay(int gravity, Rect display, Rect inoutObj) {
+        if ((gravity&DISPLAY_CLIP_VERTICAL) != 0) {
+            if (inoutObj.top < display.top) inoutObj.top = display.top;
+            if (inoutObj.bottom > display.bottom) inoutObj.bottom = display.bottom;
+        } else {
+            int off = 0;
+            if (inoutObj.top < display.top) off = display.top-inoutObj.top;
+            else if (inoutObj.bottom > display.bottom) off = display.bottom-inoutObj.bottom;
+            if (off != 0) {
+                if (inoutObj.height() > (display.bottom-display.top)) {
+                    inoutObj.top = display.top;
+                    inoutObj.bottom = display.bottom;
+                } else {
+                    inoutObj.top += off;
+                    inoutObj.bottom += off;
+                }
+            }
+        }
+        
+        if ((gravity&DISPLAY_CLIP_HORIZONTAL) != 0) {
+            if (inoutObj.left < display.left) inoutObj.left = display.left;
+            if (inoutObj.right > display.right) inoutObj.right = display.right;
+        } else {
+            int off = 0;
+            if (inoutObj.left < display.left) off = display.left-inoutObj.left;
+            else if (inoutObj.right > display.right) off = display.right-inoutObj.right;
+            if (off != 0) {
+                if (inoutObj.width() > (display.right-display.left)) {
+                    inoutObj.left = display.left;
+                    inoutObj.right = display.right;
+                } else {
+                    inoutObj.left += off;
+                    inoutObj.right += off;
+                }
+            }
+        }
+    }
+    
+    /**
+     * <p>Indicate whether the supplied gravity has a vertical pull.</p>
+     *
+     * @param gravity the gravity to check for vertical pull
+     * @return true if the supplied gravity has a vertical pull
+     */
+    public static boolean isVertical(int gravity) {
+        return gravity > 0 && (gravity & VERTICAL_GRAVITY_MASK) != 0;
+    }
+
+    /**
+     * <p>Indicate whether the supplied gravity has an horizontal pull.</p>
+     *
+     * @param gravity the gravity to check for horizontal pull
+     * @return true if the supplied gravity has an horizontal pull
+     */
+    public static boolean isHorizontal(int gravity) {
+        return gravity > 0 && (gravity & HORIZONTAL_GRAVITY_MASK) != 0;
+    }
+}
diff --git a/core/java/android/view/HapticFeedbackConstants.java b/core/java/android/view/HapticFeedbackConstants.java
new file mode 100644
index 0000000..cc3563c
--- /dev/null
+++ b/core/java/android/view/HapticFeedbackConstants.java
@@ -0,0 +1,45 @@
+/*
+ * 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 android.view;
+
+/**
+ * Constants to be used to perform haptic feedback effects via
+ * {@link View#performHapticFeedback(int)} 
+ */
+public class HapticFeedbackConstants {
+
+    private HapticFeedbackConstants() {}
+
+    public static final int LONG_PRESS = 0;
+    
+    /** @hide pending API council */
+    public static final int ZOOM_RING_TICK = 1;
+    
+    /**
+     * Flag for {@link View#performHapticFeedback(int, int)
+     * View.performHapticFeedback(int, int)}: Ignore the setting in the
+     * view for whether to perform haptic feedback, do it always.
+     */
+    public static final int FLAG_IGNORE_VIEW_SETTING = 0x0001;
+    
+    /**
+     * Flag for {@link View#performHapticFeedback(int, int)
+     * View.performHapticFeedback(int, int)}: Ignore the global setting
+     * for whether to perform haptic feedback, do it always.
+     */
+    public static final int FLAG_IGNORE_GLOBAL_SETTING = 0x0002;
+}
diff --git a/core/java/android/view/IApplicationToken.aidl b/core/java/android/view/IApplicationToken.aidl
new file mode 100644
index 0000000..6bff5b3
--- /dev/null
+++ b/core/java/android/view/IApplicationToken.aidl
@@ -0,0 +1,28 @@
+/* //device/java/android/android/view/IApplicationToken.aidl
+**
+** Copyright 2007, 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 android.view;
+
+/** {@hide} */
+interface IApplicationToken
+{
+    void windowsVisible();
+    void windowsGone();
+    boolean keyDispatchingTimedOut();
+    long getKeyDispatchingTimeout();
+}
+
diff --git a/core/java/android/view/IOnKeyguardExitResult.aidl b/core/java/android/view/IOnKeyguardExitResult.aidl
new file mode 100644
index 0000000..47d5220
--- /dev/null
+++ b/core/java/android/view/IOnKeyguardExitResult.aidl
@@ -0,0 +1,25 @@
+/* //device/java/android/android/hardware/ISensorListener.aidl
+**
+** Copyright 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 android.view;
+
+/** @hide */
+oneway interface IOnKeyguardExitResult {
+
+    void onKeyguardExitResult(boolean success);
+
+}
diff --git a/core/java/android/view/IRotationWatcher.aidl b/core/java/android/view/IRotationWatcher.aidl
new file mode 100644
index 0000000..2c83642
--- /dev/null
+++ b/core/java/android/view/IRotationWatcher.aidl
@@ -0,0 +1,25 @@
+/* //device/java/android/android/hardware/ISensorListener.aidl
+**
+** Copyright 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 android.view;
+
+/**
+ * {@hide}
+ */
+oneway interface IRotationWatcher {
+    void onRotationChanged(int rotation);
+}
diff --git a/core/java/android/view/IWindow.aidl b/core/java/android/view/IWindow.aidl
new file mode 100644
index 0000000..99d5c0c
--- /dev/null
+++ b/core/java/android/view/IWindow.aidl
@@ -0,0 +1,59 @@
+/* //device/java/android/android/view/IWindow.aidl
+**
+** Copyright 2007, 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 android.view;
+
+import android.graphics.Rect;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+
+import android.os.ParcelFileDescriptor;
+
+/**
+ * API back to a client window that the Window Manager uses to inform it of
+ * interesting things happening.
+ *
+ * {@hide}
+ */
+oneway interface IWindow {
+    /**
+     * ===== NOTICE =====
+     * The first method must remain the first method. Scripts
+     * and tools rely on their transaction number to work properly.
+     */
+
+    /**
+     * Invoked by the view server to tell a window to execute the specified
+     * command. Any response from the receiver must be sent through the
+     * specified file descriptor.
+     */
+    void executeCommand(String command, String parameters, in ParcelFileDescriptor descriptor);
+
+    void resized(int w, int h, in Rect coveredInsets, in Rect visibleInsets,
+            boolean reportDraw);
+    void dispatchKey(in KeyEvent event);
+    void dispatchPointer(in MotionEvent event, long eventTime);
+    void dispatchTrackball(in MotionEvent event, long eventTime);
+    void dispatchAppVisibility(boolean visible);
+    void dispatchGetNewSurface();
+
+    /**
+     * Tell the window that it is either gaining or losing focus.  Keep it up
+     * to date on the current state showing navigational focus (touch mode) too.
+     */
+    void windowFocusChanged(boolean hasFocus, boolean inTouchMode);
+}
diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
new file mode 100644
index 0000000..a856b24
--- /dev/null
+++ b/core/java/android/view/IWindowManager.aidl
@@ -0,0 +1,136 @@
+/* //device/java/android/android/view/IWindowManager.aidl
+**
+** Copyright 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 android.view;
+
+import com.android.internal.view.IInputContext;
+import com.android.internal.view.IInputMethodClient;
+
+import android.content.res.Configuration;
+import android.view.IApplicationToken;
+import android.view.IOnKeyguardExitResult;
+import android.view.IRotationWatcher;
+import android.view.IWindowSession;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+
+/**
+ * System private interface to the window manager.
+ *
+ * {@hide}
+ */
+interface IWindowManager
+{
+    /**
+     * ===== NOTICE =====
+     * The first three methods must remain the first three methods. Scripts
+     * and tools rely on their transaction number to work properly.
+     */
+    // This is used for debugging
+    boolean startViewServer(int port);   // Transaction #1
+    boolean stopViewServer();            // Transaction #2
+    boolean isViewServerRunning();       // Transaction #3
+
+    IWindowSession openSession(in IInputMethodClient client,
+            in IInputContext inputContext);
+    boolean inputMethodClientHasFocus(IInputMethodClient client);
+    
+    // These can only be called when injecting events to your own window,
+    // or by holding the INJECT_EVENTS permission.
+    boolean injectKeyEvent(in KeyEvent ev, boolean sync);
+    boolean injectPointerEvent(in MotionEvent ev, boolean sync);
+    boolean injectTrackballEvent(in MotionEvent ev, boolean sync);
+    
+    // These can only be called when holding the MANAGE_APP_TOKENS permission.
+    void pauseKeyDispatching(IBinder token);
+    void resumeKeyDispatching(IBinder token);
+    void setEventDispatching(boolean enabled);
+    void addAppToken(int addPos, IApplicationToken token,
+            int groupId, int requestedOrientation, boolean fullscreen);
+    void setAppGroupId(IBinder token, int groupId);
+    Configuration updateOrientationFromAppTokens(IBinder freezeThisOneIfNeeded);
+    void setAppOrientation(IApplicationToken token, int requestedOrientation);
+    int getAppOrientation(IApplicationToken token);
+    void setFocusedApp(IBinder token, boolean moveFocusNow);
+    void prepareAppTransition(int transit);
+    int getPendingAppTransition();
+    void executeAppTransition();
+    void setAppStartingWindow(IBinder token, String pkg, int theme,
+            CharSequence nonLocalizedLabel, int labelRes,
+            int icon, IBinder transferFrom, boolean createIfNeeded);
+    void setAppWillBeHidden(IBinder token);
+    void setAppVisibility(IBinder token, boolean visible);
+    void startAppFreezingScreen(IBinder token, int configChanges);
+    void stopAppFreezingScreen(IBinder token, boolean force);
+    void removeAppToken(IBinder token);
+    void moveAppToken(int index, IBinder token);
+    void moveAppTokensToTop(in List<IBinder> tokens);
+    void moveAppTokensToBottom(in List<IBinder> tokens);
+    void addWindowToken(IBinder token, int type);
+    void removeWindowToken(IBinder token);
+
+    // these require DISABLE_KEYGUARD permission
+    void disableKeyguard(IBinder token, String tag);
+    void reenableKeyguard(IBinder token);
+    void exitKeyguardSecurely(IOnKeyguardExitResult callback);
+    boolean inKeyguardRestrictedInputMode();
+
+    
+    // These can only be called with the SET_ANIMATON_SCALE permission.
+    float getAnimationScale(int which);
+    float[] getAnimationScales();
+    void setAnimationScale(int which, float scale);
+    void setAnimationScales(in float[] scales);
+    
+    // These require the READ_INPUT_STATE permission.
+    int getSwitchState(int sw);
+    int getSwitchStateForDevice(int devid, int sw);
+    int getScancodeState(int sw);
+    int getScancodeStateForDevice(int devid, int sw);
+    int getKeycodeState(int sw);
+    int getKeycodeStateForDevice(int devid, int sw);
+    
+    // Report whether the hardware supports the given keys; returns true if successful
+    boolean hasKeys(in int[] keycodes, inout boolean[] keyExists);
+    
+    // For testing
+    void setInTouchMode(boolean showFocus);
+    
+    // These can only be called with the SET_ORIENTATION permission.
+    /**
+     * Change the current screen rotation, constants as per
+     * {@link android.view.Surface}.
+     * @param rotation the intended rotation.
+     * @param alwaysSendConfiguration Flag to force a new configuration to
+     * be evaluated.  This can be used when there are other parameters in
+     * configuration that are changing.
+     * {@link android.view.Surface}.
+     */
+    void setRotation(int rotation, boolean alwaysSendConfiguration);
+
+    /**
+     * Retrieve the current screen orientation, constants as per
+     * {@link android.view.Surface}.
+     */
+    int getRotation();
+    
+    /**
+     * Watch the rotation of the screen.  Returns the current rotation,
+     * calls back when it changes.
+     */
+    int watchRotation(IRotationWatcher watcher);
+}
diff --git a/core/java/android/view/IWindowSession.aidl b/core/java/android/view/IWindowSession.aidl
new file mode 100644
index 0000000..1156856
--- /dev/null
+++ b/core/java/android/view/IWindowSession.aidl
@@ -0,0 +1,111 @@
+/* //device/java/android/android/view/IWindowSession.aidl
+**
+** Copyright 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 android.view;
+
+import android.graphics.Rect;
+import android.graphics.Region;
+import android.view.IWindow;
+import android.view.MotionEvent;
+import android.view.WindowManager;
+import android.view.Surface;
+
+/**
+ * System private per-application interface to the window manager.
+ *
+ * {@hide}
+ */
+interface IWindowSession {
+    int add(IWindow window, in WindowManager.LayoutParams attrs,
+            in int viewVisibility, out Rect outContentInsets);
+    void remove(IWindow window);
+    
+    /**
+     * Change the parameters of a window.  You supply the
+     * new parameters, it returns the new frame of the window on screen (the
+     * position should be ignored) and surface of the window.  The surface
+     * will be invalid if the window is currently hidden, else you can use it
+     * to draw the window's contents.
+     * 
+     * @param window The window being modified.
+     * @param attrs If non-null, new attributes to apply to the window.
+     * @param requestedWidth The width the window wants to be.
+     * @param requestedHeight The height the window wants to be.
+     * @param viewVisibility Window root view's visibility.
+     * @param insetsPending Set to true if the client will be later giving
+     * internal insets; as a result, the window will not impact other window
+     * layouts until the insets are given.
+     * @param outFrame Rect in which is placed the new position/size on
+     * screen.
+     * @param outContentInsets Rect in which is placed the offsets from
+     * <var>outFrame</var> in which the content of the window should be
+     * placed.  This can be used to modify the window layout to ensure its
+     * contents are visible to the user, taking into account system windows
+     * like the status bar or a soft keyboard.
+     * @param outVisibleInsets Rect in which is placed the offsets from
+     * <var>outFrame</var> in which the window is actually completely visible
+     * to the user.  This can be used to temporarily scroll the window's
+     * contents to make sure the user can see it.  This is different than
+     * <var>outContentInsets</var> in that these insets change transiently,
+     * so complex relayout of the window should not happen based on them.
+     * @param outSurface Object in which is placed the new display surface.
+     * 
+     * @return int Result flags: {@link WindowManagerImpl#RELAYOUT_SHOW_FOCUS},
+     * {@link WindowManagerImpl#RELAYOUT_FIRST_TIME}.
+     */
+    int relayout(IWindow window, in WindowManager.LayoutParams attrs,
+            int requestedWidth, int requestedHeight, int viewVisibility,
+            boolean insetsPending, out Rect outFrame, out Rect outContentInsets,
+            out Rect outVisibleInsets, out Surface outSurface);
+
+    /**
+     * Give the window manager a hint of the part of the window that is
+     * completely transparent, allowing it to work with the surface flinger
+     * to optimize compositing of this part of the window.
+     */
+    void setTransparentRegion(IWindow window, in Region region);
+    
+    /**
+     * Tell the window manager about the content and visible insets of the
+     * given window, which can be used to adjust the <var>outContentInsets</var>
+     * and <var>outVisibleInsets</var> values returned by
+     * {@link #relayout relayout()} for windows behind this one.
+     *
+     * @param touchableInsets Controls which part of the window inside of its
+     * frame can receive pointer events, as defined by
+     * {@link android.view.ViewTreeObserver.InternalInsetsInfo}.
+     */
+    void setInsets(IWindow window, int touchableInsets, in Rect contentInsets,
+            in Rect visibleInsets);
+    
+    /**
+     * Return the current display size in which the window is being laid out,
+     * accounting for screen decorations around it.
+     */
+    void getDisplayFrame(IWindow window, out Rect outDisplayFrame);
+    
+    void finishDrawing(IWindow window);
+
+    void finishKey(IWindow window);
+    MotionEvent getPendingPointerMove(IWindow window);
+    MotionEvent getPendingTrackballMove(IWindow window);
+    
+    void setInTouchMode(boolean showFocus);
+    boolean getInTouchMode();
+    
+    boolean performHapticFeedback(IWindow window, int effectId, boolean always);
+}
diff --git a/core/java/android/view/InflateException.java b/core/java/android/view/InflateException.java
new file mode 100644
index 0000000..7b39d33
--- /dev/null
+++ b/core/java/android/view/InflateException.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2007 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 android.view;
+
+/**
+ * This exception is thrown by an inflater on error conditions.
+ */
+public class InflateException extends RuntimeException {
+
+    public InflateException() {
+        super();
+    }
+
+    public InflateException(String detailMessage, Throwable throwable) {
+        super(detailMessage, throwable);
+    }
+
+    public InflateException(String detailMessage) {
+        super(detailMessage);
+    }
+
+    public InflateException(Throwable throwable) {
+        super(throwable);
+    }
+
+}
diff --git a/core/java/android/view/KeyCharacterMap.java b/core/java/android/view/KeyCharacterMap.java
new file mode 100644
index 0000000..25958aa
--- /dev/null
+++ b/core/java/android/view/KeyCharacterMap.java
@@ -0,0 +1,545 @@
+/*
+ * Copyright (C) 2007 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 android.view;
+
+import android.text.method.MetaKeyKeyListener;
+import android.util.SparseIntArray;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.SystemClock;
+import android.util.SparseArray;
+
+import java.lang.Character;
+import java.lang.ref.WeakReference;
+
+public class KeyCharacterMap
+{
+    /**
+     * The id of the device's primary built in keyboard is always 0.
+     */
+    public static final int BUILT_IN_KEYBOARD = 0;
+
+    /** A numeric (12-key) keyboard. */
+    public static final int NUMERIC = 1;
+
+    /** A keyboard with all the letters, but with more than one letter
+     *  per key. */
+    public static final int PREDICTIVE = 2;
+
+    /** A keyboard with all the letters, and maybe some numbers. */
+    public static final int ALPHA = 3;
+
+    /**
+     * This private-use character is used to trigger Unicode character
+     * input by hex digits.
+     */
+    public static final char HEX_INPUT = '\uEF00';
+
+    /**
+     * This private-use character is used to bring up a character picker for
+     * miscellaneous symbols.
+     */
+    public static final char PICKER_DIALOG_INPUT = '\uEF01';
+
+    private static Object sLock = new Object();
+    private static SparseArray<WeakReference<KeyCharacterMap>> sInstances 
+        = new SparseArray<WeakReference<KeyCharacterMap>>();
+
+    public static KeyCharacterMap load(int keyboard)
+    {
+        synchronized (sLock) {
+            KeyCharacterMap result;
+            WeakReference<KeyCharacterMap> ref = sInstances.get(keyboard);
+            if (ref != null) {
+                result = ref.get();
+                if (result != null) {
+                    return result;
+                }
+            }
+            result = new KeyCharacterMap(keyboard);
+            sInstances.put(keyboard, new WeakReference<KeyCharacterMap>(result));
+            return result;
+        }
+    }
+
+    private KeyCharacterMap(int keyboardDevice)
+    {
+        mKeyboardDevice = keyboardDevice;
+        mPointer = ctor_native(keyboardDevice);
+    }
+
+    /**
+     * <p>
+     * Returns the Unicode character that the specified key would produce
+     * when the specified meta bits (see {@link MetaKeyKeyListener})
+     * were active.
+     * </p><p>
+     * Returns 0 if the key is not one that is used to type Unicode
+     * characters.
+     * </p><p>
+     * If the return value has bit {@link #COMBINING_ACCENT} set, the
+     * key is a "dead key" that should be combined with another to
+     * actually produce a character -- see {@link #getDeadChar} --
+     * after masking with {@link #COMBINING_ACCENT_MASK}.
+     * </p>
+     */
+    public int get(int keyCode, int meta)
+    {
+        if ((meta & MetaKeyKeyListener.META_CAP_LOCKED) != 0) {
+            meta |= KeyEvent.META_SHIFT_ON;
+        }
+        if ((meta & MetaKeyKeyListener.META_ALT_LOCKED) != 0) {
+            meta |= KeyEvent.META_ALT_ON;
+        }
+
+        // Ignore caps lock on keys where alt and shift have the same effect.
+        if ((meta & MetaKeyKeyListener.META_CAP_LOCKED) != 0) {
+            if (get_native(mPointer, keyCode, KeyEvent.META_SHIFT_ON) ==
+                get_native(mPointer, keyCode, KeyEvent.META_ALT_ON)) {
+                meta &= ~KeyEvent.META_SHIFT_ON;
+            }
+        }
+
+        int ret = get_native(mPointer, keyCode, meta);
+        int map = COMBINING.get(ret);
+
+        if (map != 0) {
+            return map;
+        } else {
+            return ret;
+        }
+    }
+
+    /**
+     * Gets the number or symbol associated with the key.  The character value
+     * is returned, not the numeric value.  If the key is not a number, but is
+     * a symbol, the symbol is retuned.
+     */
+    public char getNumber(int keyCode)
+    {
+        return getNumber_native(mPointer, keyCode);
+    }
+
+    /**
+     * The same as {@link #getMatch(int,char[],int) getMatch(keyCode, chars, 0)}.
+     */
+    public char getMatch(int keyCode, char[] chars)
+    {
+        return getMatch(keyCode, chars, 0);
+    }
+
+    /**
+     * If one of the chars in the array can be generated by keyCode,
+     * return the char; otherwise return '\0'.
+     * @param keyCode the key code to look at
+     * @param chars the characters to try to find
+     * @param modifiers the modifier bits to prefer.  If any of these bits
+     *                  are set, if there are multiple choices, that could
+     *                  work, the one for this modifier will be set.
+     */
+    public char getMatch(int keyCode, char[] chars, int modifiers)
+    {
+        if (chars == null) {
+            // catch it here instead of in native
+            throw new NullPointerException();
+        }
+        return getMatch_native(mPointer, keyCode, chars, modifiers);
+    }
+
+    /**
+     * Get the primary character for this key.  In other words, the label
+     * that is physically printed on it.
+     */
+    public char getDisplayLabel(int keyCode)
+    {
+        return getDisplayLabel_native(mPointer, keyCode);
+    }
+
+    /**
+     * Get the character that is produced by putting accent on the character
+     * c.
+     * For example, getDeadChar('`', 'e') returns &egrave;.
+     */
+    public static int getDeadChar(int accent, int c)
+    {
+        return DEAD.get((accent << 16) | c);
+    }
+
+    public static class KeyData {
+        public static final int META_LENGTH = 4;
+
+        /**
+         * The display label (see {@link #getDisplayLabel}).
+         */
+        public char displayLabel;
+        /**
+         * The "number" value (see {@link #getNumber}).
+         */
+        public char number;
+        /**
+         * The character that will be generated in various meta states
+         * (the same ones used for {@link #get} and defined as
+         * {@link KeyEvent#META_SHIFT_ON} and {@link KeyEvent#META_ALT_ON}).
+         *      <table>
+         *          <tr><th>Index</th><th align="left">Value</th></tr>
+         *          <tr><td>0</td><td>no modifiers</td></tr>
+         *          <tr><td>1</td><td>caps</td></tr>
+         *          <tr><td>2</td><td>alt</td></tr>
+         *          <tr><td>3</td><td>caps + alt</td></tr>
+         *      </table>
+         */
+        public char[] meta = new char[META_LENGTH];
+    }
+    
+    /**
+     * Get the characters conversion data for a given keyCode.
+     *
+     * @param keyCode the keyCode to look for
+     * @param results a {@link KeyData} that will be filled with the results.
+     *
+     * @return whether the key was mapped or not.  If the key was not mapped,
+     *         results is not modified.
+     */
+    public boolean getKeyData(int keyCode, KeyData results)
+    {
+        if (results.meta.length >= KeyData.META_LENGTH) {
+            return getKeyData_native(mPointer, keyCode, results);
+        } else {
+            throw new IndexOutOfBoundsException("results.meta.length must be >= " +
+                                                KeyData.META_LENGTH);
+        }
+    }
+
+    /**
+     * Get an array of KeyEvent objects that if put into the input stream
+     * could plausibly generate the provided sequence of characters.  It is
+     * not guaranteed that the sequence is the only way to generate these
+     * events or that it is optimal.
+     * 
+     * @return an array of KeyEvent objects, or null if the given char array
+     *         can not be generated using the current key character map.
+     */
+    public KeyEvent[] getEvents(char[] chars)
+    {
+        if (chars == null) {
+            throw new NullPointerException();
+        }
+
+        long[] keys = getEvents_native(mPointer, chars);
+        if (keys == null) {
+            return null;
+        }
+
+        // how big should the array be
+        int len = keys.length*2;
+        int N = keys.length;
+        for (int i=0; i<N; i++) {
+            int mods = (int)(keys[i] >> 32);
+            if ((mods & KeyEvent.META_ALT_ON) != 0) {
+                len += 2;
+            }
+            if ((mods & KeyEvent.META_SHIFT_ON) != 0) {
+                len += 2;
+            }
+            if ((mods & KeyEvent.META_SYM_ON) != 0) {
+                len += 2;
+            }
+        }
+
+        // create the events
+        KeyEvent[] rv = new KeyEvent[len];
+        int index = 0;
+        long now = SystemClock.uptimeMillis();
+        int device = mKeyboardDevice;
+        for (int i=0; i<N; i++) {
+            int mods = (int)(keys[i] >> 32);
+            int meta = 0;
+
+            if ((mods & KeyEvent.META_ALT_ON) != 0) {
+                meta |= KeyEvent.META_ALT_ON;
+                rv[index] = new KeyEvent(now, now, KeyEvent.ACTION_DOWN,
+                        KeyEvent.KEYCODE_ALT_LEFT, 0, meta, device, 0);
+                index++;
+            }
+            if ((mods & KeyEvent.META_SHIFT_ON) != 0) {
+                meta |= KeyEvent.META_SHIFT_ON;
+                rv[index] = new KeyEvent(now, now, KeyEvent.ACTION_DOWN,
+                        KeyEvent.KEYCODE_SHIFT_LEFT, 0, meta, device, 0);
+                index++;
+            }
+            if ((mods & KeyEvent.META_SYM_ON) != 0) {
+                meta |= KeyEvent.META_SYM_ON;
+                rv[index] = new KeyEvent(now, now, KeyEvent.ACTION_DOWN,
+                        KeyEvent.KEYCODE_SYM, 0, meta, device, 0);
+                index++;
+            }
+
+            int key = (int)(keys[i]);
+            rv[index] = new KeyEvent(now, now, KeyEvent.ACTION_DOWN,
+                    key, 0, meta, device, 0);
+            index++;
+            rv[index] = new KeyEvent(now, now, KeyEvent.ACTION_UP,
+                    key, 0, meta, device, 0);
+            index++;
+
+            if ((mods & KeyEvent.META_ALT_ON) != 0) {
+                meta &= ~KeyEvent.META_ALT_ON;
+                rv[index] = new KeyEvent(now, now, KeyEvent.ACTION_UP,
+                        KeyEvent.KEYCODE_ALT_LEFT, 0, meta, device, 0);
+                index++;
+            }
+            if ((mods & KeyEvent.META_SHIFT_ON) != 0) {
+                meta &= ~KeyEvent.META_SHIFT_ON;
+                rv[index] = new KeyEvent(now, now, KeyEvent.ACTION_UP,
+                        KeyEvent.KEYCODE_SHIFT_LEFT, 0, meta, device, 0);
+                index++;
+            }
+            if ((mods & KeyEvent.META_SYM_ON) != 0) {
+                meta &= ~KeyEvent.META_SYM_ON;
+                rv[index] = new KeyEvent(now, now, KeyEvent.ACTION_UP,
+                        KeyEvent.KEYCODE_SYM, 0, meta, device, 0);
+                index++;
+            }
+        }
+
+        return rv;
+    }
+
+    /**
+     * Does this character key produce a glyph?
+     */
+    public boolean isPrintingKey(int keyCode)
+    {
+        int type = Character.getType(get(keyCode, 0));
+
+        switch (type)
+        {
+            case Character.SPACE_SEPARATOR:
+            case Character.LINE_SEPARATOR:
+            case Character.PARAGRAPH_SEPARATOR:
+            case Character.CONTROL:
+            case Character.FORMAT:
+                return false;
+            default:
+                return true;
+        }
+    }
+
+    protected void finalize() throws Throwable
+    {
+        dtor_native(mPointer);
+    }
+
+    /**
+     * Returns {@link #NUMERIC}, {@link #PREDICTIVE} or {@link #ALPHA}.
+     */
+    public int getKeyboardType()
+    {
+        return getKeyboardType_native(mPointer);
+    }
+
+    /**
+     * Queries the framework about whether any physical keys exist on the
+     * device that are capable of producing the given key codes.
+     */
+    public static boolean deviceHasKey(int keyCode) {
+        int[] codeArray = new int[1];
+        codeArray[0] = keyCode;
+        boolean[] ret = deviceHasKeys(codeArray);
+        return ret[0];
+    }
+    
+    public static boolean[] deviceHasKeys(int[] keyCodes) {
+        boolean[] ret = new boolean[keyCodes.length];
+        IWindowManager wm = IWindowManager.Stub.asInterface(ServiceManager.getService("window"));
+        try {
+            wm.hasKeys(keyCodes, ret);
+        } catch (RemoteException e) {
+            // no fallback; just return the empty array
+        }
+        return ret;
+    }
+
+    private int mPointer;
+    private int mKeyboardDevice;
+
+    private static native int ctor_native(int id);
+    private static native void dtor_native(int ptr);
+    private static native char get_native(int ptr, int keycode,
+                                    int meta);
+    private static native char getNumber_native(int ptr, int keycode);
+    private static native char getMatch_native(int ptr, int keycode,
+                                    char[] chars, int modifiers);
+    private static native char getDisplayLabel_native(int ptr, int keycode);
+    private static native boolean getKeyData_native(int ptr, int keycode,
+                                    KeyData results);
+    private static native int getKeyboardType_native(int ptr);
+    private static native long[] getEvents_native(int ptr, char[] str);
+
+    /**
+     * Maps Unicode combining diacritical to display-form dead key
+     * (display character shifted left 16 bits).
+     */
+    private static SparseIntArray COMBINING = new SparseIntArray();
+
+    /**
+     * Maps combinations of (display-form) dead key and second character
+     * to combined output character.
+     */
+    private static SparseIntArray DEAD = new SparseIntArray();
+
+    /*
+     * TODO: Change the table format to support full 21-bit-wide
+     * accent characters and combined characters if ever necessary.    
+     */
+    private static final int ACUTE = '\u00B4' << 16;
+    private static final int GRAVE = '`' << 16;
+    private static final int CIRCUMFLEX = '^' << 16;
+    private static final int TILDE = '~' << 16;
+    private static final int UMLAUT = '\u00A8' << 16;
+
+    /*
+     * This bit will be set in the return value of {@link #get(int, int)} if the
+     * key is a "dead key."
+     */
+    public static final int COMBINING_ACCENT = 0x80000000;
+    /**
+     * Mask the return value from {@link #get(int, int)} with this value to get
+     * a printable representation of the accent character of a "dead key."
+     */
+    public static final int COMBINING_ACCENT_MASK = 0x7FFFFFFF;
+
+    static {
+        COMBINING.put('\u0300', (GRAVE >> 16) | COMBINING_ACCENT);
+        COMBINING.put('\u0301', (ACUTE >> 16) | COMBINING_ACCENT);
+        COMBINING.put('\u0302', (CIRCUMFLEX >> 16) | COMBINING_ACCENT);
+        COMBINING.put('\u0303', (TILDE >> 16) | COMBINING_ACCENT);
+        COMBINING.put('\u0308', (UMLAUT >> 16) | COMBINING_ACCENT);
+
+        DEAD.put(ACUTE | 'A', '\u00C1');
+        DEAD.put(ACUTE | 'C', '\u0106');
+        DEAD.put(ACUTE | 'E', '\u00C9');
+        DEAD.put(ACUTE | 'G', '\u01F4');
+        DEAD.put(ACUTE | 'I', '\u00CD');
+        DEAD.put(ACUTE | 'K', '\u1E30');
+        DEAD.put(ACUTE | 'L', '\u0139');
+        DEAD.put(ACUTE | 'M', '\u1E3E');
+        DEAD.put(ACUTE | 'N', '\u0143');
+        DEAD.put(ACUTE | 'O', '\u00D3');
+        DEAD.put(ACUTE | 'P', '\u1E54');
+        DEAD.put(ACUTE | 'R', '\u0154');
+        DEAD.put(ACUTE | 'S', '\u015A');
+        DEAD.put(ACUTE | 'U', '\u00DA');
+        DEAD.put(ACUTE | 'W', '\u1E82');
+        DEAD.put(ACUTE | 'Y', '\u00DD');
+        DEAD.put(ACUTE | 'Z', '\u0179');
+        DEAD.put(ACUTE | 'a', '\u00E1');
+        DEAD.put(ACUTE | 'c', '\u0107');
+        DEAD.put(ACUTE | 'e', '\u00E9');
+        DEAD.put(ACUTE | 'g', '\u01F5');
+        DEAD.put(ACUTE | 'i', '\u00ED');
+        DEAD.put(ACUTE | 'k', '\u1E31');
+        DEAD.put(ACUTE | 'l', '\u013A');
+        DEAD.put(ACUTE | 'm', '\u1E3F');
+        DEAD.put(ACUTE | 'n', '\u0144');
+        DEAD.put(ACUTE | 'o', '\u00F3');
+        DEAD.put(ACUTE | 'p', '\u1E55');
+        DEAD.put(ACUTE | 'r', '\u0155');
+        DEAD.put(ACUTE | 's', '\u015B');
+        DEAD.put(ACUTE | 'u', '\u00FA');
+        DEAD.put(ACUTE | 'w', '\u1E83');
+        DEAD.put(ACUTE | 'y', '\u00FD');
+        DEAD.put(ACUTE | 'z', '\u017A');
+        DEAD.put(CIRCUMFLEX | 'A', '\u00C2');
+        DEAD.put(CIRCUMFLEX | 'C', '\u0108');
+        DEAD.put(CIRCUMFLEX | 'E', '\u00CA');
+        DEAD.put(CIRCUMFLEX | 'G', '\u011C');
+        DEAD.put(CIRCUMFLEX | 'H', '\u0124');
+        DEAD.put(CIRCUMFLEX | 'I', '\u00CE');
+        DEAD.put(CIRCUMFLEX | 'J', '\u0134');
+        DEAD.put(CIRCUMFLEX | 'O', '\u00D4');
+        DEAD.put(CIRCUMFLEX | 'S', '\u015C');
+        DEAD.put(CIRCUMFLEX | 'U', '\u00DB');
+        DEAD.put(CIRCUMFLEX | 'W', '\u0174');
+        DEAD.put(CIRCUMFLEX | 'Y', '\u0176');
+        DEAD.put(CIRCUMFLEX | 'Z', '\u1E90');
+        DEAD.put(CIRCUMFLEX | 'a', '\u00E2');
+        DEAD.put(CIRCUMFLEX | 'c', '\u0109');
+        DEAD.put(CIRCUMFLEX | 'e', '\u00EA');
+        DEAD.put(CIRCUMFLEX | 'g', '\u011D');
+        DEAD.put(CIRCUMFLEX | 'h', '\u0125');
+        DEAD.put(CIRCUMFLEX | 'i', '\u00EE');
+        DEAD.put(CIRCUMFLEX | 'j', '\u0135');
+        DEAD.put(CIRCUMFLEX | 'o', '\u00F4');
+        DEAD.put(CIRCUMFLEX | 's', '\u015D');
+        DEAD.put(CIRCUMFLEX | 'u', '\u00FB');
+        DEAD.put(CIRCUMFLEX | 'w', '\u0175');
+        DEAD.put(CIRCUMFLEX | 'y', '\u0177');
+        DEAD.put(CIRCUMFLEX | 'z', '\u1E91');
+        DEAD.put(GRAVE | 'A', '\u00C0');
+        DEAD.put(GRAVE | 'E', '\u00C8');
+        DEAD.put(GRAVE | 'I', '\u00CC');
+        DEAD.put(GRAVE | 'N', '\u01F8');
+        DEAD.put(GRAVE | 'O', '\u00D2');
+        DEAD.put(GRAVE | 'U', '\u00D9');
+        DEAD.put(GRAVE | 'W', '\u1E80');
+        DEAD.put(GRAVE | 'Y', '\u1EF2');
+        DEAD.put(GRAVE | 'a', '\u00E0');
+        DEAD.put(GRAVE | 'e', '\u00E8');
+        DEAD.put(GRAVE | 'i', '\u00EC');
+        DEAD.put(GRAVE | 'n', '\u01F9');
+        DEAD.put(GRAVE | 'o', '\u00F2');
+        DEAD.put(GRAVE | 'u', '\u00F9');
+        DEAD.put(GRAVE | 'w', '\u1E81');
+        DEAD.put(GRAVE | 'y', '\u1EF3');
+        DEAD.put(TILDE | 'A', '\u00C3');
+        DEAD.put(TILDE | 'E', '\u1EBC');
+        DEAD.put(TILDE | 'I', '\u0128');
+        DEAD.put(TILDE | 'N', '\u00D1');
+        DEAD.put(TILDE | 'O', '\u00D5');
+        DEAD.put(TILDE | 'U', '\u0168');
+        DEAD.put(TILDE | 'V', '\u1E7C');
+        DEAD.put(TILDE | 'Y', '\u1EF8');
+        DEAD.put(TILDE | 'a', '\u00E3');
+        DEAD.put(TILDE | 'e', '\u1EBD');
+        DEAD.put(TILDE | 'i', '\u0129');
+        DEAD.put(TILDE | 'n', '\u00F1');
+        DEAD.put(TILDE | 'o', '\u00F5');
+        DEAD.put(TILDE | 'u', '\u0169');
+        DEAD.put(TILDE | 'v', '\u1E7D');
+        DEAD.put(TILDE | 'y', '\u1EF9');
+        DEAD.put(UMLAUT | 'A', '\u00C4');
+        DEAD.put(UMLAUT | 'E', '\u00CB');
+        DEAD.put(UMLAUT | 'H', '\u1E26');
+        DEAD.put(UMLAUT | 'I', '\u00CF');
+        DEAD.put(UMLAUT | 'O', '\u00D6');
+        DEAD.put(UMLAUT | 'U', '\u00DC');
+        DEAD.put(UMLAUT | 'W', '\u1E84');
+        DEAD.put(UMLAUT | 'X', '\u1E8C');
+        DEAD.put(UMLAUT | 'Y', '\u0178');
+        DEAD.put(UMLAUT | 'a', '\u00E4');
+        DEAD.put(UMLAUT | 'e', '\u00EB');
+        DEAD.put(UMLAUT | 'h', '\u1E27');
+        DEAD.put(UMLAUT | 'i', '\u00EF');
+        DEAD.put(UMLAUT | 'o', '\u00F6');
+        DEAD.put(UMLAUT | 't', '\u1E97');
+        DEAD.put(UMLAUT | 'u', '\u00FC');
+        DEAD.put(UMLAUT | 'w', '\u1E85');
+        DEAD.put(UMLAUT | 'x', '\u1E8D');
+        DEAD.put(UMLAUT | 'y', '\u00FF');
+    }
+}
diff --git a/core/java/android/view/KeyEvent.aidl b/core/java/android/view/KeyEvent.aidl
new file mode 100644
index 0000000..dc15ecf
--- /dev/null
+++ b/core/java/android/view/KeyEvent.aidl
@@ -0,0 +1,20 @@
+/* //device/java/android/android.view.KeyEvent.aidl
+**
+** Copyright 2007, 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 android.view;
+
+parcelable KeyEvent;
diff --git a/core/java/android/view/KeyEvent.java b/core/java/android/view/KeyEvent.java
new file mode 100644
index 0000000..430cc71
--- /dev/null
+++ b/core/java/android/view/KeyEvent.java
@@ -0,0 +1,886 @@
+/*
+ * 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 android.view;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.view.KeyCharacterMap;
+import android.view.KeyCharacterMap.KeyData;
+
+/**
+ * Contains constants for key events.
+ */
+public class KeyEvent implements Parcelable {
+    // key codes
+    public static final int KEYCODE_UNKNOWN         = 0;
+    public static final int KEYCODE_SOFT_LEFT       = 1;
+    public static final int KEYCODE_SOFT_RIGHT      = 2;
+    public static final int KEYCODE_HOME            = 3;
+    public static final int KEYCODE_BACK            = 4;
+    public static final int KEYCODE_CALL            = 5;
+    public static final int KEYCODE_ENDCALL         = 6;
+    public static final int KEYCODE_0               = 7;
+    public static final int KEYCODE_1               = 8;
+    public static final int KEYCODE_2               = 9;
+    public static final int KEYCODE_3               = 10;
+    public static final int KEYCODE_4               = 11;
+    public static final int KEYCODE_5               = 12;
+    public static final int KEYCODE_6               = 13;
+    public static final int KEYCODE_7               = 14;
+    public static final int KEYCODE_8               = 15;
+    public static final int KEYCODE_9               = 16;
+    public static final int KEYCODE_STAR            = 17;
+    public static final int KEYCODE_POUND           = 18;
+    public static final int KEYCODE_DPAD_UP         = 19;
+    public static final int KEYCODE_DPAD_DOWN       = 20;
+    public static final int KEYCODE_DPAD_LEFT       = 21;
+    public static final int KEYCODE_DPAD_RIGHT      = 22;
+    public static final int KEYCODE_DPAD_CENTER     = 23;
+    public static final int KEYCODE_VOLUME_UP       = 24;
+    public static final int KEYCODE_VOLUME_DOWN     = 25;
+    public static final int KEYCODE_POWER           = 26;
+    public static final int KEYCODE_CAMERA          = 27;
+    public static final int KEYCODE_CLEAR           = 28;
+    public static final int KEYCODE_A               = 29;
+    public static final int KEYCODE_B               = 30;
+    public static final int KEYCODE_C               = 31;
+    public static final int KEYCODE_D               = 32;
+    public static final int KEYCODE_E               = 33;
+    public static final int KEYCODE_F               = 34;
+    public static final int KEYCODE_G               = 35;
+    public static final int KEYCODE_H               = 36;
+    public static final int KEYCODE_I               = 37;
+    public static final int KEYCODE_J               = 38;
+    public static final int KEYCODE_K               = 39;
+    public static final int KEYCODE_L               = 40;
+    public static final int KEYCODE_M               = 41;
+    public static final int KEYCODE_N               = 42;
+    public static final int KEYCODE_O               = 43;
+    public static final int KEYCODE_P               = 44;
+    public static final int KEYCODE_Q               = 45;
+    public static final int KEYCODE_R               = 46;
+    public static final int KEYCODE_S               = 47;
+    public static final int KEYCODE_T               = 48;
+    public static final int KEYCODE_U               = 49;
+    public static final int KEYCODE_V               = 50;
+    public static final int KEYCODE_W               = 51;
+    public static final int KEYCODE_X               = 52;
+    public static final int KEYCODE_Y               = 53;
+    public static final int KEYCODE_Z               = 54;
+    public static final int KEYCODE_COMMA           = 55;
+    public static final int KEYCODE_PERIOD          = 56;
+    public static final int KEYCODE_ALT_LEFT        = 57;
+    public static final int KEYCODE_ALT_RIGHT       = 58;
+    public static final int KEYCODE_SHIFT_LEFT      = 59;
+    public static final int KEYCODE_SHIFT_RIGHT     = 60;
+    public static final int KEYCODE_TAB             = 61;
+    public static final int KEYCODE_SPACE           = 62;
+    public static final int KEYCODE_SYM             = 63;
+    public static final int KEYCODE_EXPLORER        = 64;
+    public static final int KEYCODE_ENVELOPE        = 65;
+    public static final int KEYCODE_ENTER           = 66;
+    public static final int KEYCODE_DEL             = 67;
+    public static final int KEYCODE_GRAVE           = 68;
+    public static final int KEYCODE_MINUS           = 69;
+    public static final int KEYCODE_EQUALS          = 70;
+    public static final int KEYCODE_LEFT_BRACKET    = 71;
+    public static final int KEYCODE_RIGHT_BRACKET   = 72;
+    public static final int KEYCODE_BACKSLASH       = 73;
+    public static final int KEYCODE_SEMICOLON       = 74;
+    public static final int KEYCODE_APOSTROPHE      = 75;
+    public static final int KEYCODE_SLASH           = 76;
+    public static final int KEYCODE_AT              = 77;
+    public static final int KEYCODE_NUM             = 78;
+    public static final int KEYCODE_HEADSETHOOK     = 79;
+    public static final int KEYCODE_FOCUS           = 80;   // *Camera* focus
+    public static final int KEYCODE_PLUS            = 81;
+    public static final int KEYCODE_MENU            = 82;
+    public static final int KEYCODE_NOTIFICATION    = 83;
+    public static final int KEYCODE_SEARCH          = 84;
+    public static final int KEYCODE_PLAYPAUSE       = 85;
+    public static final int KEYCODE_STOP            = 86;
+    public static final int KEYCODE_NEXTSONG        = 87;
+    public static final int KEYCODE_PREVIOUSSONG    = 88;
+    public static final int KEYCODE_REWIND          = 89;
+    public static final int KEYCODE_FORWARD         = 90;
+    public static final int KEYCODE_MUTE            = 91;
+    private static final int LAST_KEYCODE           = KEYCODE_MUTE;
+
+    // NOTE: If you add a new keycode here you must also add it to:
+    //  isSystem()
+    //  frameworks/base/include/ui/KeycodeLabels.h
+    //  tools/puppet_master/PuppetMaster/nav_keys.py
+    //  frameworks/base/core/res/res/values/attrs.xml
+    //  commands/monkey/Monkey.java
+    //  emulator?
+   
+    /**
+     * @deprecated There are now more than MAX_KEYCODE keycodes.
+     * Use {@link #getMaxKeyCode()} instead.
+     */
+    @Deprecated
+    public static final int MAX_KEYCODE             = 84;
+
+    /**
+     * {@link #getAction} value: the key has been pressed down.
+     */
+    public static final int ACTION_DOWN             = 0;
+    /**
+     * {@link #getAction} value: the key has been released.
+     */
+    public static final int ACTION_UP               = 1;
+    /**
+     * {@link #getAction} value: multiple duplicate key events have
+     * occurred in a row, or a complex string is being delivered.  If the
+     * key code is not {#link {@link #KEYCODE_UNKNOWN} then the
+     * {#link {@link #getRepeatCount()} method returns the number of times
+     * the given key code should be executed.
+     * Otherwise, if the key code {@link #KEYCODE_UNKNOWN}, then
+     * this is a sequence of characters as returned by {@link #getCharacters}.
+     */
+    public static final int ACTION_MULTIPLE         = 2;
+
+    /**
+     * <p>This mask is used to check whether one of the ALT meta keys is pressed.</p>
+     *
+     * @see #isAltPressed()
+     * @see #getMetaState()
+     * @see #KEYCODE_ALT_LEFT
+     * @see #KEYCODE_ALT_RIGHT
+     */
+    public static final int META_ALT_ON = 0x02;
+
+    /**
+     * <p>This mask is used to check whether the left ALT meta key is pressed.</p>
+     *
+     * @see #isAltPressed()
+     * @see #getMetaState()
+     * @see #KEYCODE_ALT_LEFT
+     */
+    public static final int META_ALT_LEFT_ON = 0x10;
+
+    /**
+     * <p>This mask is used to check whether the right the ALT meta key is pressed.</p>
+     *
+     * @see #isAltPressed()
+     * @see #getMetaState()
+     * @see #KEYCODE_ALT_RIGHT
+     */
+    public static final int META_ALT_RIGHT_ON = 0x20;
+
+    /**
+     * <p>This mask is used to check whether one of the SHIFT meta keys is pressed.</p>
+     *
+     * @see #isShiftPressed()
+     * @see #getMetaState()
+     * @see #KEYCODE_SHIFT_LEFT
+     * @see #KEYCODE_SHIFT_RIGHT
+     */
+    public static final int META_SHIFT_ON = 0x1;
+
+    /**
+     * <p>This mask is used to check whether the left SHIFT meta key is pressed.</p>
+     *
+     * @see #isShiftPressed()
+     * @see #getMetaState()
+     * @see #KEYCODE_SHIFT_LEFT
+     */
+    public static final int META_SHIFT_LEFT_ON = 0x40;
+
+    /**
+     * <p>This mask is used to check whether the right SHIFT meta key is pressed.</p>
+     *
+     * @see #isShiftPressed()
+     * @see #getMetaState()
+     * @see #KEYCODE_SHIFT_RIGHT
+     */
+    public static final int META_SHIFT_RIGHT_ON = 0x80;
+
+    /**
+     * <p>This mask is used to check whether the SYM meta key is pressed.</p>
+     *
+     * @see #isSymPressed()
+     * @see #getMetaState()
+     */
+    public static final int META_SYM_ON = 0x4;
+
+    /**
+     * This mask is set if the device woke because of this key event.
+     */
+    public static final int FLAG_WOKE_HERE = 0x1;
+    
+    /**
+     * This mask is set if the key event was generated by a software keyboard.
+     */
+    public static final int FLAG_SOFT_KEYBOARD = 0x2;
+    
+    /**
+     * This mask is set if we don't want the key event to cause us to leave
+     * touch mode.
+     */
+    public static final int FLAG_KEEP_TOUCH_MODE = 0x4;
+    
+    /**
+     * Returns the maximum keycode.
+     */
+    public static int getMaxKeyCode() {
+        return LAST_KEYCODE;
+    }
+
+    /**
+     * Get the character that is produced by putting accent on the character
+     * c.
+     * For example, getDeadChar('`', 'e') returns &egrave;.
+     */
+    public static int getDeadChar(int accent, int c) {
+        return KeyCharacterMap.getDeadChar(accent, c);
+    }
+    
+    private int mMetaState;
+    private int mAction;
+    private int mKeyCode;
+    private int mScancode;
+    private int mRepeatCount;
+    private int mDeviceId;
+    private int mFlags;
+    private long mDownTime;
+    private long mEventTime;
+    private String mCharacters;
+
+    public interface Callback {
+        /**
+         * Called when a key down event has occurred.
+         * 
+         * @param keyCode The value in event.getKeyCode().
+         * @param event Description of the key event.
+         * 
+         * @return If you handled the event, return true.  If you want to allow
+         *         the event to be handled by the next receiver, return false.
+         */
+        boolean onKeyDown(int keyCode, KeyEvent event);
+
+        /**
+         * Called when a key up event has occurred.
+         * 
+         * @param keyCode The value in event.getKeyCode().
+         * @param event Description of the key event.
+         * 
+         * @return If you handled the event, return true.  If you want to allow
+         *         the event to be handled by the next receiver, return false.
+         */
+        boolean onKeyUp(int keyCode, KeyEvent event);
+
+        /**
+         * Called when multiple down/up pairs of the same key have occurred
+         * in a row.
+         * 
+         * @param keyCode The value in event.getKeyCode().
+         * @param count Number of pairs as returned by event.getRepeatCount().
+         * @param event Description of the key event.
+         * 
+         * @return If you handled the event, return true.  If you want to allow
+         *         the event to be handled by the next receiver, return false.
+         */
+        boolean onKeyMultiple(int keyCode, int count, KeyEvent event);
+    }
+
+    /**
+     * Create a new key event.
+     * 
+     * @param action Action code: either {@link #ACTION_DOWN},
+     * {@link #ACTION_UP}, or {@link #ACTION_MULTIPLE}.
+     * @param code The key code.
+     */
+    public KeyEvent(int action, int code) {
+        mAction = action;
+        mKeyCode = code;
+        mRepeatCount = 0;
+    }
+
+    /**
+     * Create a new key event.
+     * 
+     * @param downTime The time (in {@link android.os.SystemClock#uptimeMillis})
+     * at which this key code originally went down.
+     * @param eventTime The time (in {@link android.os.SystemClock#uptimeMillis})
+     * at which this event happened.
+     * @param action Action code: either {@link #ACTION_DOWN},
+     * {@link #ACTION_UP}, or {@link #ACTION_MULTIPLE}.
+     * @param code The key code.
+     * @param repeat A repeat count for down events (> 0 if this is after the
+     * initial down) or event count for multiple events.
+     */
+    public KeyEvent(long downTime, long eventTime, int action,
+                    int code, int repeat) {
+        mDownTime = downTime;
+        mEventTime = eventTime;
+        mAction = action;
+        mKeyCode = code;
+        mRepeatCount = repeat;
+    }
+
+    /**
+     * Create a new key event.
+     * 
+     * @param downTime The time (in {@link android.os.SystemClock#uptimeMillis})
+     * at which this key code originally went down.
+     * @param eventTime The time (in {@link android.os.SystemClock#uptimeMillis})
+     * at which this event happened.
+     * @param action Action code: either {@link #ACTION_DOWN},
+     * {@link #ACTION_UP}, or {@link #ACTION_MULTIPLE}.
+     * @param code The key code.
+     * @param repeat A repeat count for down events (> 0 if this is after the
+     * initial down) or event count for multiple events.
+     * @param metaState Flags indicating which meta keys are currently pressed.
+     */
+    public KeyEvent(long downTime, long eventTime, int action,
+                    int code, int repeat, int metaState) {
+        mDownTime = downTime;
+        mEventTime = eventTime;
+        mAction = action;
+        mKeyCode = code;
+        mRepeatCount = repeat;
+        mMetaState = metaState;
+    }
+
+    /**
+     * Create a new key event.
+     * 
+     * @param downTime The time (in {@link android.os.SystemClock#uptimeMillis})
+     * at which this key code originally went down.
+     * @param eventTime The time (in {@link android.os.SystemClock#uptimeMillis})
+     * at which this event happened.
+     * @param action Action code: either {@link #ACTION_DOWN},
+     * {@link #ACTION_UP}, or {@link #ACTION_MULTIPLE}.
+     * @param code The key code.
+     * @param repeat A repeat count for down events (> 0 if this is after the
+     * initial down) or event count for multiple events.
+     * @param metaState Flags indicating which meta keys are currently pressed.
+     * @param device The device ID that generated the key event.
+     * @param scancode Raw device scan code of the event.
+     */
+    public KeyEvent(long downTime, long eventTime, int action,
+                    int code, int repeat, int metaState,
+                    int device, int scancode) {
+        mDownTime = downTime;
+        mEventTime = eventTime;
+        mAction = action;
+        mKeyCode = code;
+        mRepeatCount = repeat;
+        mMetaState = metaState;
+        mDeviceId = device;
+        mScancode = scancode;
+    }
+
+    /**
+     * Create a new key event.
+     * 
+     * @param downTime The time (in {@link android.os.SystemClock#uptimeMillis})
+     * at which this key code originally went down.
+     * @param eventTime The time (in {@link android.os.SystemClock#uptimeMillis})
+     * at which this event happened.
+     * @param action Action code: either {@link #ACTION_DOWN},
+     * {@link #ACTION_UP}, or {@link #ACTION_MULTIPLE}.
+     * @param code The key code.
+     * @param repeat A repeat count for down events (> 0 if this is after the
+     * initial down) or event count for multiple events.
+     * @param metaState Flags indicating which meta keys are currently pressed.
+     * @param device The device ID that generated the key event.
+     * @param scancode Raw device scan code of the event.
+     * @param flags The flags for this key event
+     */
+    public KeyEvent(long downTime, long eventTime, int action,
+                    int code, int repeat, int metaState,
+                    int device, int scancode, int flags) {
+        mDownTime = downTime;
+        mEventTime = eventTime;
+        mAction = action;
+        mKeyCode = code;
+        mRepeatCount = repeat;
+        mMetaState = metaState;
+        mDeviceId = device;
+        mScancode = scancode;
+        mFlags = flags;
+    }
+
+    /**
+     * Create a new key event for a string of characters.  The key code,
+     * action, and repeat could will automatically be set to
+     * {@link #KEYCODE_UNKNOWN}, {@link #ACTION_MULTIPLE}, and 0 for you.
+     * 
+     * @param time The time (in {@link android.os.SystemClock#uptimeMillis})
+     * at which this event occured.
+     * @param characters The string of characters.
+     * @param device The device ID that generated the key event.
+     * @param flags The flags for this key event
+     */
+    public KeyEvent(long time, String characters, int device, int flags) {
+        mDownTime = time;
+        mEventTime = time;
+        mCharacters = characters;
+        mAction = ACTION_MULTIPLE;
+        mKeyCode = KEYCODE_UNKNOWN;
+        mRepeatCount = 0;
+        mDeviceId = device;
+        mFlags = flags;
+    }
+
+    /**
+     * Copy an existing key event, modifying its time and repeat count.
+     * 
+     * @param origEvent The existing event to be copied.
+     * @param eventTime The new event time
+     * (in {@link android.os.SystemClock#uptimeMillis}) of the event.
+     * @param newRepeat The new repeat count of the event.
+     */
+    public KeyEvent(KeyEvent origEvent, long eventTime, int newRepeat) {
+        mDownTime = origEvent.mDownTime;
+        mEventTime = eventTime;
+        mAction = origEvent.mAction;
+        mKeyCode = origEvent.mKeyCode;
+        mRepeatCount = newRepeat;
+        mMetaState = origEvent.mMetaState;
+        mDeviceId = origEvent.mDeviceId;
+        mScancode = origEvent.mScancode;
+        mFlags = origEvent.mFlags;
+        mCharacters = origEvent.mCharacters;
+    }
+
+    /**
+     * Copy an existing key event, modifying its action.
+     * 
+     * @param origEvent The existing event to be copied.
+     * @param action The new action code of the event.
+     */
+    public KeyEvent(KeyEvent origEvent, int action) {
+        mDownTime = origEvent.mDownTime;
+        mEventTime = origEvent.mEventTime;
+        mAction = action;
+        mKeyCode = origEvent.mKeyCode;
+        mRepeatCount = origEvent.mRepeatCount;
+        mMetaState = origEvent.mMetaState;
+        mDeviceId = origEvent.mDeviceId;
+        mScancode = origEvent.mScancode;
+        mFlags = origEvent.mFlags;
+        // Don't copy mCharacters, since one way or the other we'll lose it
+        // when changing the action.
+    }
+
+    /**
+     * Don't use in new code, instead explicitly check
+     * {@link #getAction()}.
+     * 
+     * @return If the action is ACTION_DOWN, returns true; else false.
+     *
+     * @deprecated
+     * @hide
+     */
+    @Deprecated public final boolean isDown() {
+        return mAction == ACTION_DOWN;
+    }
+
+    /**
+     * Is this a system key?  System keys can not be used for menu shortcuts.
+     * 
+     * TODO: this information should come from a table somewhere.
+     * TODO: should the dpad keys be here?  arguably, because they also shouldn't be menu shortcuts
+     */
+    public final boolean isSystem() {
+        switch (mKeyCode) {
+        case KEYCODE_MENU:
+        case KEYCODE_SOFT_RIGHT:
+        case KEYCODE_HOME:
+        case KEYCODE_BACK:
+        case KEYCODE_CALL:
+        case KEYCODE_ENDCALL:
+        case KEYCODE_VOLUME_UP:
+        case KEYCODE_VOLUME_DOWN:
+        case KEYCODE_MUTE:
+        case KEYCODE_POWER:
+        case KEYCODE_HEADSETHOOK:
+        case KEYCODE_PLAYPAUSE:
+        case KEYCODE_STOP:
+        case KEYCODE_NEXTSONG:
+        case KEYCODE_PREVIOUSSONG:
+        case KEYCODE_REWIND:
+        case KEYCODE_FORWARD:
+        case KEYCODE_CAMERA:
+        case KEYCODE_FOCUS:
+        case KEYCODE_SEARCH:
+            return true;
+        default:
+            return false;
+        }
+    }
+
+
+    /**
+     * <p>Returns the state of the meta keys.</p>
+     *
+     * @return an integer in which each bit set to 1 represents a pressed
+     *         meta key
+     *
+     * @see #isAltPressed()
+     * @see #isShiftPressed()
+     * @see #isSymPressed()
+     * @see #META_ALT_ON
+     * @see #META_SHIFT_ON
+     * @see #META_SYM_ON
+     */
+    public final int getMetaState() {
+        return mMetaState;
+    }
+
+    /**
+     * Returns the flags for this key event.
+     *
+     * @see #FLAG_WOKE_HERE
+     */
+    public final int getFlags() {
+        return mFlags;
+    }
+
+    /**
+     * Returns true if this key code is a modifier key.
+     *
+     * @return whether the provided keyCode is one of
+     * {@link #KEYCODE_SHIFT_LEFT} {@link #KEYCODE_SHIFT_RIGHT},
+     * {@link #KEYCODE_ALT_LEFT}, {@link #KEYCODE_ALT_RIGHT}
+     * or {@link #KEYCODE_SYM}.
+     */
+    public static boolean isModifierKey(int keyCode) {
+        return keyCode == KEYCODE_SHIFT_LEFT || keyCode == KEYCODE_SHIFT_RIGHT
+                || keyCode == KEYCODE_ALT_LEFT || keyCode == KEYCODE_ALT_RIGHT
+                || keyCode == KEYCODE_SYM;
+    }
+
+    /**
+     * <p>Returns the pressed state of the ALT meta key.</p>
+     *
+     * @return true if the ALT key is pressed, false otherwise
+     *
+     * @see #KEYCODE_ALT_LEFT
+     * @see #KEYCODE_ALT_RIGHT
+     * @see #META_ALT_ON
+     */
+    public final boolean isAltPressed() {
+        return (mMetaState & META_ALT_ON) != 0;
+    }
+
+    /**
+     * <p>Returns the pressed state of the SHIFT meta key.</p>
+     *
+     * @return true if the SHIFT key is pressed, false otherwise
+     *
+     * @see #KEYCODE_SHIFT_LEFT
+     * @see #KEYCODE_SHIFT_RIGHT
+     * @see #META_SHIFT_ON
+     */
+    public final boolean isShiftPressed() {
+        return (mMetaState & META_SHIFT_ON) != 0;
+    }
+
+    /**
+     * <p>Returns the pressed state of the SYM meta key.</p>
+     *
+     * @return true if the SYM key is pressed, false otherwise
+     *
+     * @see #KEYCODE_SYM
+     * @see #META_SYM_ON
+     */
+    public final boolean isSymPressed() {
+        return (mMetaState & META_SYM_ON) != 0;
+    }
+
+    /**
+     * Retrieve the action of this key event.  May be either
+     * {@link #ACTION_DOWN}, {@link #ACTION_UP}, or {@link #ACTION_MULTIPLE}.
+     * 
+     * @return The event action: ACTION_DOWN, ACTION_UP, or ACTION_MULTIPLE.
+     */
+    public final int getAction() {
+        return mAction;
+    }
+
+    /**
+     * Retrieve the key code of the key event.  This is the physical key that
+     * was pressed, <em>not</em> the Unicode character.
+     * 
+     * @return The key code of the event.
+     */
+    public final int getKeyCode() {
+        return mKeyCode;
+    }
+
+    /**
+     * For the special case of a {@link #ACTION_MULTIPLE} event with key
+     * code of {@link #KEYCODE_UNKNOWN}, this is a raw string of characters
+     * associated with the event.  In all other cases it is null.
+     * 
+     * @return Returns a String of 1 or more characters associated with
+     * the event.
+     */
+    public final String getCharacters() {
+        return mCharacters;
+    }
+    
+    /**
+     * Retrieve the hardware key id of this key event.  These values are not
+     * reliable and vary from device to device.
+     *
+     * {@more}
+     * Mostly this is here for debugging purposes.
+     */
+    public final int getScanCode() {
+        return mScancode;
+    }
+
+    /**
+     * Retrieve the repeat count of the event.  For both key up and key down
+     * events, this is the number of times the key has repeated with the first
+     * down starting at 0 and counting up from there.  For multiple key
+     * events, this is the number of down/up pairs that have occurred.
+     * 
+     * @return The number of times the key has repeated.
+     */
+    public final int getRepeatCount() {
+        return mRepeatCount;
+    }
+
+    /**
+     * Retrieve the time of the most recent key down event,
+     * in the {@link android.os.SystemClock#uptimeMillis} time base.  If this
+     * is a down event, this will be the same as {@link #getEventTime()}.
+     * Note that when chording keys, this value is the down time of the
+     * most recently pressed key, which may <em>not</em> be the same physical
+     * key of this event.
+     * 
+     * @return Returns the most recent key down time, in the
+     * {@link android.os.SystemClock#uptimeMillis} time base
+     */
+    public final long getDownTime() {
+        return mDownTime;
+    }
+
+    /**
+     * Retrieve the time this event occurred, 
+     * in the {@link android.os.SystemClock#uptimeMillis} time base.
+     * 
+     * @return Returns the time this event occurred, 
+     * in the {@link android.os.SystemClock#uptimeMillis} time base.
+     */
+    public final long getEventTime() {
+        return mEventTime;
+    }
+
+    /**
+     * Return the id for the keyboard that this event came from.  A device
+     * id of 0 indicates the event didn't come from a physical device and
+     * maps to the default keymap.  The other numbers are arbitrary and
+     * you shouldn't depend on the values.
+     * 
+     * @see KeyCharacterMap#load
+     */
+    public final int getDeviceId() {
+        return mDeviceId;
+    }
+
+    /**
+     * Renamed to {@link #getDeviceId}.
+     * 
+     * @hide
+     * @deprecated
+     */
+    public final int getKeyboardDevice() {
+        return mDeviceId;
+    }
+
+    /**
+     * Get the primary character for this key.  In other words, the label
+     * that is physically printed on it.
+     */
+    public char getDisplayLabel() {
+        return KeyCharacterMap.load(mDeviceId).getDisplayLabel(mKeyCode);
+    }
+    
+    /**
+     * <p>
+     * Returns the Unicode character that the key would produce.
+     * </p><p>
+     * Returns 0 if the key is not one that is used to type Unicode
+     * characters.
+     * </p><p>
+     * If the return value has bit 
+     * {@link KeyCharacterMap#COMBINING_ACCENT} 
+     * set, the key is a "dead key" that should be combined with another to
+     * actually produce a character -- see {@link #getDeadChar} --
+     * after masking with 
+     * {@link KeyCharacterMap#COMBINING_ACCENT_MASK}.
+     * </p>
+     */
+    public int getUnicodeChar() {
+        return getUnicodeChar(mMetaState);
+    }
+    
+    /**
+     * <p>
+     * Returns the Unicode character that the key would produce.
+     * </p><p>
+     * Returns 0 if the key is not one that is used to type Unicode
+     * characters.
+     * </p><p>
+     * If the return value has bit 
+     * {@link KeyCharacterMap#COMBINING_ACCENT} 
+     * set, the key is a "dead key" that should be combined with another to
+     * actually produce a character -- see {@link #getDeadChar} -- after masking
+     * with {@link KeyCharacterMap#COMBINING_ACCENT_MASK}.
+     * </p>
+     */
+    public int getUnicodeChar(int meta) {
+        return KeyCharacterMap.load(mDeviceId).get(mKeyCode, meta);
+    }
+    
+    /**
+     * Get the characters conversion data for the key event..
+     *
+     * @param results a {@link KeyData} that will be filled with the results.
+     *
+     * @return whether the key was mapped or not.  If the key was not mapped,
+     *         results is not modified.
+     */
+    public boolean getKeyData(KeyData results) {
+        return KeyCharacterMap.load(mDeviceId).getKeyData(mKeyCode, results);
+    }
+    
+    /**
+     * The same as {@link #getMatch(char[],int) getMatch(chars, 0)}.
+     */
+    public char getMatch(char[] chars) {
+        return getMatch(chars, 0);
+    }
+    
+    /**
+     * If one of the chars in the array can be generated by the keyCode of this
+     * key event, return the char; otherwise return '\0'.
+     * @param chars the characters to try to find
+     * @param modifiers the modifier bits to prefer.  If any of these bits
+     *                  are set, if there are multiple choices, that could
+     *                  work, the one for this modifier will be set.
+     */
+    public char getMatch(char[] chars, int modifiers) {
+        return KeyCharacterMap.load(mDeviceId).getMatch(mKeyCode, chars, modifiers);
+    }
+    
+    /**
+     * Gets the number or symbol associated with the key.  The character value
+     * is returned, not the numeric value.  If the key is not a number, but is
+     * a symbol, the symbol is retuned.
+     */
+    public char getNumber() {
+        return KeyCharacterMap.load(mDeviceId).getNumber(mKeyCode);
+    }
+    
+    /**
+     * Does the key code of this key produce a glyph?
+     */
+    public boolean isPrintingKey() {
+        return KeyCharacterMap.load(mDeviceId).isPrintingKey(mKeyCode);
+    }
+    
+    /**
+     * Deliver this key event to a {@link Callback} interface.  If this is
+     * an ACTION_MULTIPLE event and it is not handled, then an attempt will
+     * be made to deliver a single normal event.
+     * 
+     * @param receiver The Callback that will be given the event.
+     * 
+     * @return The return value from the Callback method that was called.
+     */
+    public final boolean dispatch(Callback receiver) {
+        switch (mAction) {
+            case ACTION_DOWN:
+                return receiver.onKeyDown(mKeyCode, this);
+            case ACTION_UP:
+                return receiver.onKeyUp(mKeyCode, this);
+            case ACTION_MULTIPLE:
+                final int count = mRepeatCount;
+                final int code = mKeyCode;
+                if (receiver.onKeyMultiple(code, count, this)) {
+                    return true;
+                }
+                if (code != KeyEvent.KEYCODE_UNKNOWN) {
+                    mAction = ACTION_DOWN;
+                    mRepeatCount = 0;
+                    boolean handled = receiver.onKeyDown(code, this);
+                    if (handled) {
+                        mAction = ACTION_UP;
+                        receiver.onKeyUp(code, this);
+                    }
+                    mAction = ACTION_MULTIPLE;
+                    mRepeatCount = count;
+                    return handled;
+                }
+        }
+        return false;
+    }
+
+    public String toString() {
+        return "KeyEvent{action=" + mAction + " code=" + mKeyCode
+            + " repeat=" + mRepeatCount
+            + " meta=" + mMetaState + " scancode=" + mScancode
+            + " mFlags=" + mFlags + "}";
+    }
+
+    public static final Parcelable.Creator<KeyEvent> CREATOR
+            = new Parcelable.Creator<KeyEvent>() {
+        public KeyEvent createFromParcel(Parcel in) {
+            return new KeyEvent(in);
+        }
+
+        public KeyEvent[] newArray(int size) {
+            return new KeyEvent[size];
+        }
+    };
+
+    public int describeContents() {
+        return 0;
+    }
+
+    public void writeToParcel(Parcel out, int flags) {
+        out.writeInt(mAction);
+        out.writeInt(mKeyCode);
+        out.writeInt(mRepeatCount);
+        out.writeInt(mMetaState);
+        out.writeInt(mDeviceId);
+        out.writeInt(mScancode);
+        out.writeInt(mFlags);
+        out.writeLong(mDownTime);
+        out.writeLong(mEventTime);
+    }
+
+    private KeyEvent(Parcel in) {
+        mAction = in.readInt();
+        mKeyCode = in.readInt();
+        mRepeatCount = in.readInt();
+        mMetaState = in.readInt();
+        mDeviceId = in.readInt();
+        mScancode = in.readInt();
+        mFlags = in.readInt();
+        mDownTime = in.readLong();
+        mEventTime = in.readLong();
+    }
+}
diff --git a/core/java/android/view/LayoutInflater.java b/core/java/android/view/LayoutInflater.java
new file mode 100644
index 0000000..94acd3f
--- /dev/null
+++ b/core/java/android/view/LayoutInflater.java
@@ -0,0 +1,744 @@
+/*
+ * Copyright (C) 2007 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 android.view;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.content.res.XmlResourceParser;
+import android.util.AttributeSet;
+import android.util.Xml;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.lang.reflect.Constructor;
+import java.util.HashMap;
+
+/**
+ * This class is used to instantiate layout XML file into its corresponding View
+ * objects. It is never be used directly -- use
+ * {@link android.app.Activity#getLayoutInflater()} or
+ * {@link Context#getSystemService} to retrieve a standard LayoutInflater instance
+ * that is already hooked up to the current context and correctly configured
+ * for the device you are running on.  For example:
+ *
+ * <pre>LayoutInflater inflater = (LayoutInflater)context.getSystemService
+ *      Context.LAYOUT_INFLATER_SERVICE);</pre>
+ * 
+ * <p>
+ * To create a new LayoutInflater with an additional {@link Factory} for your
+ * own views, you can use {@link #cloneInContext} to clone an existing
+ * ViewFactory, and then call {@link #setFactory} on it to include your
+ * Factory.
+ * 
+ * <p>
+ * For performance reasons, view inflation relies heavily on pre-processing of
+ * XML files that is done at build time. Therefore, it is not currently possible
+ * to use LayoutInflater with an XmlPullParser over a plain XML file at runtime;
+ * it only works with an XmlPullParser returned from a compiled resource
+ * (R.<em>something</em> file.)
+ * 
+ * @see Context#getSystemService
+ */
+public abstract class LayoutInflater {
+    private final boolean DEBUG = false;
+
+    /**
+     * This field should be made private, so it is hidden from the SDK.
+     * {@hide}
+     */
+    protected final Context mContext;
+
+    // these are optional, set by the caller
+    private boolean mFactorySet;
+    private Factory mFactory;
+    private Filter mFilter;
+
+    private final Object[] mConstructorArgs = new Object[2];
+
+    private static final Class[] mConstructorSignature = new Class[] {
+            Context.class, AttributeSet.class};
+
+    private static final HashMap<String, Constructor> sConstructorMap =
+            new HashMap<String, Constructor>();
+    
+    private HashMap<String, Boolean> mFilterMap;
+
+    private static final String TAG_MERGE = "merge";
+    private static final String TAG_INCLUDE = "include";
+    private static final String TAG_REQUEST_FOCUS = "requestFocus";
+
+    /**
+     * Hook to allow clients of the LayoutInflater to restrict the set of Views that are allowed
+     * to be inflated.
+     * 
+     */
+    public interface Filter {
+        /**
+         * Hook to allow clients of the LayoutInflater to restrict the set of Views 
+         * that are allowed to be inflated.
+         * 
+         * @param clazz The class object for the View that is about to be inflated
+         * 
+         * @return True if this class is allowed to be inflated, or false otherwise
+         */
+        boolean onLoadClass(Class clazz);
+    }
+    
+    public interface Factory {
+        /**
+         * Hook you can supply that is called when inflating from a LayoutInflater.
+         * You can use this to customize the tag names available in your XML
+         * layout files.
+         * 
+         * <p>
+         * Note that it is good practice to prefix these custom names with your
+         * package (i.e., com.coolcompany.apps) to avoid conflicts with system
+         * names.
+         * 
+         * @param name Tag name to be inflated.
+         * @param context The context the view is being created in.
+         * @param attrs Inflation attributes as specified in XML file.
+         * 
+         * @return View Newly created view. Return null for the default
+         *         behavior.
+         */
+        public View onCreateView(String name, Context context, AttributeSet attrs);
+    }
+
+    private static class FactoryMerger implements Factory {
+        private final Factory mF1, mF2;
+        
+        FactoryMerger(Factory f1, Factory f2) {
+            mF1 = f1;
+            mF2 = f2;
+        }
+        
+        public View onCreateView(String name, Context context, AttributeSet attrs) {
+            View v = mF1.onCreateView(name, context, attrs);
+            if (v != null) return v;
+            return mF2.onCreateView(name, context, attrs);
+        }
+    }
+    
+    /**
+     * Create a new LayoutInflater instance associated with a particular Context.
+     * Applications will almost always want to use
+     * {@link Context#getSystemService Context.getSystemService()} to retrieve
+     * the standard {@link Context#LAYOUT_INFLATER_SERVICE Context.INFLATER_SERVICE}.
+     * 
+     * @param context The Context in which this LayoutInflater will create its
+     * Views; most importantly, this supplies the theme from which the default
+     * values for their attributes are retrieved.
+     */
+    protected LayoutInflater(Context context) {
+        mContext = context;
+    }
+
+    /**
+     * Create a new LayoutInflater instance that is a copy of an existing
+     * LayoutInflater, optionally with its Context changed.  For use in
+     * implementing {@link #cloneInContext}.
+     * 
+     * @param original The original LayoutInflater to copy.
+     * @param newContext The new Context to use.
+     */
+    protected LayoutInflater(LayoutInflater original, Context newContext) {
+        mContext = newContext;
+        mFactory = original.mFactory;
+        mFilter = original.mFilter;
+    }
+    
+    /**
+     * Obtains the LayoutInflater from the given context.
+     */
+    public static LayoutInflater from(Context context) {
+        LayoutInflater LayoutInflater =
+                (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+        if (LayoutInflater == null) {
+            throw new AssertionError("LayoutInflater not found.");
+        }
+        return LayoutInflater;
+    }
+
+    /**
+     * Create a copy of the existing LayoutInflater object, with the copy
+     * pointing to a different Context than the original.  This is used by
+     * {@link ContextThemeWrapper} to create a new LayoutInflater to go along
+     * with the new Context theme.
+     * 
+     * @param newContext The new Context to associate with the new LayoutInflater.
+     * May be the same as the original Context if desired.
+     * 
+     * @return Returns a brand spanking new LayoutInflater object associated with
+     * the given Context.
+     */
+    public abstract LayoutInflater cloneInContext(Context newContext);
+    
+    /**
+     * Return the context we are running in, for access to resources, class
+     * loader, etc.
+     */
+    public Context getContext() {
+        return mContext;
+    }
+
+    /**
+     * Return the current factory (or null). This is called on each element
+     * name. If the factory returns a View, add that to the hierarchy. If it
+     * returns null, proceed to call onCreateView(name).
+     */
+    public final Factory getFactory() {
+        return mFactory;
+    }
+
+    /**
+     * Attach a custom Factory interface for creating views while using
+     * this LayoutInflater.  This must not be null, and can only be set once;
+     * after setting, you can not change the factory.  This is
+     * called on each element name as the xml is parsed. If the factory returns
+     * a View, that is added to the hierarchy. If it returns null, the next
+     * factory default {@link #onCreateView} method is called.
+     * 
+     * <p>If you have an existing
+     * LayoutInflater and want to add your own factory to it, use
+     * {@link #cloneInContext} to clone the existing instance and then you
+     * can use this function (once) on the returned new instance.  This will
+     * merge your own factory with whatever factory the original instance is
+     * using.
+     */
+    public void setFactory(Factory factory) {
+        if (mFactorySet) {
+            throw new IllegalStateException("A factory has already been set on this LayoutInflater");
+        }
+        if (factory == null) {
+            throw new NullPointerException("Given factory can not be null");
+        }
+        mFactorySet = true;
+        if (mFactory == null) {
+            mFactory = factory;
+        } else {
+            mFactory = new FactoryMerger(factory, mFactory);
+        }
+    }
+
+    /**
+     * @return The {@link Filter} currently used by this LayoutInflater to restrict the set of Views
+     * that are allowed to be inflated.
+     */
+    public Filter getFilter() {
+        return mFilter;
+    }
+    
+    /**
+     * Sets the {@link Filter} to by this LayoutInflater. If a view is attempted to be inflated
+     * which is not allowed by the {@link Filter}, the {@link #inflate(int, ViewGroup)} call will
+     * throw an {@link InflateException}. This filter will replace any previous filter set on this
+     * LayoutInflater.
+     * 
+     * @param filter The Filter which restricts the set of Views that are allowed to be inflated.
+     *        This filter will replace any previous filter set on this LayoutInflater.
+     */
+    public void setFilter(Filter filter) {
+        mFilter = filter;
+        if (filter != null) {
+            mFilterMap = new HashMap<String, Boolean>();
+        }
+    }
+
+    /**
+     * Inflate a new view hierarchy from the specified xml resource. Throws
+     * {@link InflateException} if there is an error.
+     * 
+     * @param resource ID for an XML layout resource to load (e.g.,
+     *        <code>R.layout.main_page</code>)
+     * @param root Optional view to be the parent of the generated hierarchy.
+     * @return The root View of the inflated hierarchy. If root was supplied,
+     *         this is the root View; otherwise it is the root of the inflated
+     *         XML file.
+     */
+    public View inflate(int resource, ViewGroup root) {
+        return inflate(resource, root, root != null);
+    }
+
+    /**
+     * Inflate a new view hierarchy from the specified xml node. Throws
+     * {@link InflateException} if there is an error. *
+     * <p>
+     * <em><strong>Important</strong></em>&nbsp;&nbsp;&nbsp;For performance
+     * reasons, view inflation relies heavily on pre-processing of XML files
+     * that is done at build time. Therefore, it is not currently possible to
+     * use LayoutInflater with an XmlPullParser over a plain XML file at runtime.
+     * 
+     * @param parser XML dom node containing the description of the view
+     *        hierarchy.
+     * @param root Optional view to be the parent of the generated hierarchy.
+     * @return The root View of the inflated hierarchy. If root was supplied,
+     *         this is the root View; otherwise it is the root of the inflated
+     *         XML file.
+     */
+    public View inflate(XmlPullParser parser, ViewGroup root) {
+        return inflate(parser, root, root != null);
+    }
+
+    /**
+     * Inflate a new view hierarchy from the specified xml resource. Throws
+     * {@link InflateException} if there is an error.
+     * 
+     * @param resource ID for an XML layout resource to load (e.g.,
+     *        <code>R.layout.main_page</code>)
+     * @param root Optional view to be the parent of the generated hierarchy (if
+     *        <em>attachToRoot</em> is true), or else simply an object that
+     *        provides a set of LayoutParams values for root of the returned
+     *        hierarchy (if <em>attachToRoot</em> is false.)
+     * @param attachToRoot Whether the inflated hierarchy should be attached to
+     *        the root parameter? If false, root is only used to create the
+     *        correct subclass of LayoutParams for the root view in the XML.
+     * @return The root View of the inflated hierarchy. If root was supplied and
+     *         attachToRoot is true, this is root; otherwise it is the root of
+     *         the inflated XML file.
+     */
+    public View inflate(int resource, ViewGroup root, boolean attachToRoot) {
+        if (DEBUG) System.out.println("INFLATING from resource: " + resource);
+        XmlResourceParser parser = getContext().getResources().getLayout(resource);
+        try {
+            return inflate(parser, root, attachToRoot);
+        } finally {
+            parser.close();
+        }
+    }
+
+    /**
+     * Inflate a new view hierarchy from the specified XML node. Throws
+     * {@link InflateException} if there is an error.
+     * <p>
+     * <em><strong>Important</strong></em>&nbsp;&nbsp;&nbsp;For performance
+     * reasons, view inflation relies heavily on pre-processing of XML files
+     * that is done at build time. Therefore, it is not currently possible to
+     * use LayoutInflater with an XmlPullParser over a plain XML file at runtime.
+     * 
+     * @param parser XML dom node containing the description of the view
+     *        hierarchy.
+     * @param root Optional view to be the parent of the generated hierarchy (if
+     *        <em>attachToRoot</em> is true), or else simply an object that
+     *        provides a set of LayoutParams values for root of the returned
+     *        hierarchy (if <em>attachToRoot</em> is false.)
+     * @param attachToRoot Whether the inflated hierarchy should be attached to
+     *        the root parameter? If false, root is only used to create the
+     *        correct subclass of LayoutParams for the root view in the XML.
+     * @return The root View of the inflated hierarchy. If root was supplied and
+     *         attachToRoot is true, this is root; otherwise it is the root of
+     *         the inflated XML file.
+     */
+    public View inflate(XmlPullParser parser, ViewGroup root, boolean attachToRoot) {
+        synchronized (mConstructorArgs) {
+            final AttributeSet attrs = Xml.asAttributeSet(parser);
+            mConstructorArgs[0] = mContext;
+            View result = root;
+
+            try {
+                // Look for the root node.
+                int type;
+                while ((type = parser.next()) != XmlPullParser.START_TAG &&
+                        type != XmlPullParser.END_DOCUMENT) {
+                    // Empty
+                }
+
+                if (type != XmlPullParser.START_TAG) {
+                    throw new InflateException(parser.getPositionDescription()
+                            + ": No start tag found!");
+                }
+
+                final String name = parser.getName();
+                
+                if (DEBUG) {
+                    System.out.println("**************************");
+                    System.out.println("Creating root view: "
+                            + name);
+                    System.out.println("**************************");
+                }
+
+                if (TAG_MERGE.equals(name)) {
+                    if (root == null || !attachToRoot) {
+                        throw new InflateException("<merge /> can be used only with a valid "
+                                + "ViewGroup root and attachToRoot=true");
+                    }
+
+                    rInflate(parser, root, attrs);
+                } else {
+                    // Temp is the root view that was found in the xml
+                    View temp = createViewFromTag(name, attrs);
+
+                    ViewGroup.LayoutParams params = null;
+
+                    if (root != null) {
+                        if (DEBUG) {
+                            System.out.println("Creating params from root: " +
+                                    root);
+                        }
+                        // Create layout params that match root, if supplied
+                        params = root.generateLayoutParams(attrs);
+                        if (!attachToRoot) {
+                            // Set the layout params for temp if we are not
+                            // attaching. (If we are, we use addView, below)
+                            temp.setLayoutParams(params);
+                        }
+                    }
+
+                    if (DEBUG) {
+                        System.out.println("-----> start inflating children");
+                    }
+                    // Inflate all children under temp
+                    rInflate(parser, temp, attrs);
+                    if (DEBUG) {
+                        System.out.println("-----> done inflating children");
+                    }
+
+                    // We are supposed to attach all the views we found (int temp)
+                    // to root. Do that now.
+                    if (root != null && attachToRoot) {
+                        root.addView(temp, params);
+                    }
+
+                    // Decide whether to return the root that was passed in or the
+                    // top view found in xml.
+                    if (root == null || !attachToRoot) {
+                        result = temp;
+                    }
+                }
+
+            } catch (XmlPullParserException e) {
+                InflateException ex = new InflateException(e.getMessage());
+                ex.initCause(e);
+                throw ex;
+            } catch (IOException e) {
+                InflateException ex = new InflateException(
+                        parser.getPositionDescription()
+                        + ": " + e.getMessage());
+                ex.initCause(e);
+                throw ex;
+            }
+
+            return result;
+        }
+    }
+
+    /**
+     * Low-level function for instantiating a view by name. This attempts to
+     * instantiate a view class of the given <var>name</var> found in this
+     * LayoutInflater's ClassLoader.
+     * 
+     * <p>
+     * There are two things that can happen in an error case: either the
+     * exception describing the error will be thrown, or a null will be
+     * returned. You must deal with both possibilities -- the former will happen
+     * the first time createView() is called for a class of a particular name,
+     * the latter every time there-after for that class name.
+     * 
+     * @param name The full name of the class to be instantiated.
+     * @param attrs The XML attributes supplied for this instance.
+     * 
+     * @return View The newly instantied view, or null.
+     */
+    public final View createView(String name, String prefix, AttributeSet attrs)
+            throws ClassNotFoundException, InflateException {
+        Constructor constructor = sConstructorMap.get(name);
+
+        try {
+            if (constructor == null) {
+                // Class not found in the cache, see if it's real, and try to add it
+                Class clazz = mContext.getClassLoader().loadClass(
+                        prefix != null ? (prefix + name) : name);
+                
+                if (mFilter != null && clazz != null) {
+                    boolean allowed = mFilter.onLoadClass(clazz);
+                    if (!allowed) {
+                        failNotAllowed(name, prefix, attrs);
+                    }
+                }
+                constructor = clazz.getConstructor(mConstructorSignature);
+                sConstructorMap.put(name, constructor);
+            } else {
+                // If we have a filter, apply it to cached constructor
+                if (mFilter != null) {
+                    // Have we seen this name before?
+                    Boolean allowedState = mFilterMap.get(name);
+                    if (allowedState == null) {
+                        // New class -- remember whether it is allowed
+                        Class clazz = mContext.getClassLoader().loadClass(
+                                prefix != null ? (prefix + name) : name);
+                        
+                        boolean allowed = clazz != null && mFilter.onLoadClass(clazz);
+                        mFilterMap.put(name, allowed);
+                        if (!allowed) {
+                            failNotAllowed(name, prefix, attrs);
+                        }
+                    } else if (allowedState.equals(Boolean.FALSE)) {
+                        failNotAllowed(name, prefix, attrs);
+                    }
+                }
+            }
+
+            Object[] args = mConstructorArgs;
+            args[1] = attrs;
+            return (View) constructor.newInstance(args);
+
+        } catch (NoSuchMethodException e) {
+            InflateException ie = new InflateException(attrs.getPositionDescription()
+                    + ": Error inflating class "
+                    + (prefix != null ? (prefix + name) : name));
+            ie.initCause(e);
+            throw ie;
+
+        } catch (ClassNotFoundException e) {
+            // If loadClass fails, we should propagate the exception.
+            throw e;
+        } catch (Exception e) {
+            InflateException ie = new InflateException(attrs.getPositionDescription()
+                    + ": Error inflating class "
+                    + (constructor == null ? "<unknown>" : constructor.getClass().getName()));
+            ie.initCause(e);
+            throw ie;
+        }
+    }
+
+    /**
+     * Throw an excpetion because the specified class is not allowed to be inflated.
+     */
+    private void failNotAllowed(String name, String prefix, AttributeSet attrs) {
+        InflateException ie = new InflateException(attrs.getPositionDescription()
+                + ": Class not allowed to be inflated "
+                + (prefix != null ? (prefix + name) : name));
+        throw ie;
+    }
+
+    /**
+     * This routine is responsible for creating the correct subclass of View
+     * given the xml element name. Override it to handle custom view objects. If
+     * you override this in your subclass be sure to call through to
+     * super.onCreateView(name) for names you do not recognize.
+     * 
+     * @param name The fully qualified class name of the View to be create.
+     * @param attrs An AttributeSet of attributes to apply to the View.
+     * 
+     * @return View The View created.
+     */
+    protected View onCreateView(String name, AttributeSet attrs)
+            throws ClassNotFoundException {
+        return createView(name, "android.view.", attrs);
+    }
+
+    /*
+     * default visibility so the BridgeInflater can override it.
+     */
+    View createViewFromTag(String name, AttributeSet attrs) {
+        if (name.equals("view")) {
+            name = attrs.getAttributeValue(null, "class");
+        }
+
+        if (DEBUG) System.out.println("******** Creating view: " + name);
+
+        try {
+            View view = (mFactory == null) ? null : mFactory.onCreateView(name,
+                    mContext, attrs);
+
+            if (view == null) {
+                if (-1 == name.indexOf('.')) {
+                    view = onCreateView(name, attrs);
+                } else {
+                    view = createView(name, null, attrs);
+                }
+            }
+
+            if (DEBUG) System.out.println("Created view is: " + view);
+            return view;
+
+        } catch (InflateException e) {
+            throw e;
+
+        } catch (ClassNotFoundException e) {
+            InflateException ie = new InflateException(attrs.getPositionDescription()
+                    + ": Error inflating class " + name);
+            ie.initCause(e);
+            throw ie;
+
+        } catch (Exception e) {
+            InflateException ie = new InflateException(attrs.getPositionDescription()
+                    + ": Error inflating class " + name);
+            ie.initCause(e);
+            throw ie;
+        }
+    }
+
+    /**
+     * Recursive method used to descend down the xml hierarchy and instantiate
+     * views, instantiate their children, and then call onFinishInflate().
+     */
+    private void rInflate(XmlPullParser parser, View parent, final AttributeSet attrs)
+            throws XmlPullParserException, IOException {
+
+        final int depth = parser.getDepth();
+        int type;
+
+        while (((type = parser.next()) != XmlPullParser.END_TAG ||
+                parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {
+
+            if (type != XmlPullParser.START_TAG) {
+                continue;
+            }
+
+            final String name = parser.getName();
+            
+            if (TAG_REQUEST_FOCUS.equals(name)) {
+                parseRequestFocus(parser, parent);
+            } else if (TAG_INCLUDE.equals(name)) {
+                if (parser.getDepth() == 0) {
+                    throw new InflateException("<include /> cannot be the root element");
+                }
+                parseInclude(parser, parent, attrs);
+            } else if (TAG_MERGE.equals(name)) {
+                throw new InflateException("<merge /> must be the root element");
+            } else {
+                final View view = createViewFromTag(name, attrs);
+                final ViewGroup viewGroup = (ViewGroup) parent;
+                final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);
+                rInflate(parser, view, attrs);
+                viewGroup.addView(view, params);
+            }
+        }
+
+        parent.onFinishInflate();
+    }
+
+    private void parseRequestFocus(XmlPullParser parser, View parent)
+            throws XmlPullParserException, IOException {
+        int type;
+        parent.requestFocus();
+        final int currentDepth = parser.getDepth();
+        while (((type = parser.next()) != XmlPullParser.END_TAG ||
+                parser.getDepth() > currentDepth) && type != XmlPullParser.END_DOCUMENT) {
+            // Empty
+        }
+    }
+
+    private void parseInclude(XmlPullParser parser, View parent, AttributeSet attrs)
+            throws XmlPullParserException, IOException {
+
+        int type;
+
+        if (parent instanceof ViewGroup) {
+            final int layout = attrs.getAttributeResourceValue(null, "layout", 0);
+            if (layout == 0) {
+                final String value = attrs.getAttributeValue(null, "layout");
+                if (value == null) {
+                    throw new InflateException("You must specifiy a layout in the"
+                            + " include tag: <include layout=\"@layout/layoutID\" />");
+                } else {
+                    throw new InflateException("You must specifiy a valid layout "
+                            + "reference. The layout ID " + value + " is not valid.");
+                }
+            } else {
+                final XmlResourceParser childParser =
+                        getContext().getResources().getLayout(layout);
+
+                try {
+                    final AttributeSet childAttrs = Xml.asAttributeSet(childParser);
+
+                    while ((type = childParser.next()) != XmlPullParser.START_TAG &&
+                            type != XmlPullParser.END_DOCUMENT) {
+                        // Empty.
+                    }
+
+                    if (type != XmlPullParser.START_TAG) {
+                        throw new InflateException(childParser.getPositionDescription() +
+                                ": No start tag found!");
+                    }
+
+                    final String childName = childParser.getName();
+
+                    if (TAG_MERGE.equals(childName)) {
+                        // Inflate all children.
+                        rInflate(childParser, parent, childAttrs);
+                    } else {
+                        final View view = createViewFromTag(childName, childAttrs);
+                        final ViewGroup group = (ViewGroup) parent;
+
+                        // We try to load the layout params set in the <include /> tag. If
+                        // they don't exist, we will rely on the layout params set in the
+                        // included XML file.
+                        // During a layoutparams generation, a runtime exception is thrown
+                        // if either layout_width or layout_height is missing. We catch
+                        // this exception and set localParams accordingly: true means we
+                        // successfully loaded layout params from the <include /> tag,
+                        // false means we need to rely on the included layout params.
+                        ViewGroup.LayoutParams params = null;
+                        try {
+                            params = group.generateLayoutParams(attrs);
+                        } catch (RuntimeException e) {
+                            params = group.generateLayoutParams(childAttrs);
+                        } finally {
+                            if (params != null) {
+                                view.setLayoutParams(params);
+                            }
+                        }
+
+                        // Inflate all children.
+                        rInflate(childParser, view, childAttrs);
+
+                        // Attempt to override the included layout's android:id with the
+                        // one set on the <include /> tag itself.
+                        TypedArray a = mContext.obtainStyledAttributes(attrs,
+                            com.android.internal.R.styleable.View, 0, 0);
+                        int id = a.getResourceId(com.android.internal.R.styleable.View_id, View.NO_ID);
+                        // While we're at it, let's try to override android:visibility.
+                        int visibility = a.getInt(com.android.internal.R.styleable.View_visibility, -1);
+                        a.recycle();
+
+                        if (id != View.NO_ID) {
+                            view.setId(id);
+                        }
+
+                        switch (visibility) {
+                            case 0:
+                                view.setVisibility(View.VISIBLE);
+                                break;
+                            case 1:
+                                view.setVisibility(View.INVISIBLE);
+                                break;
+                            case 2:
+                                view.setVisibility(View.GONE);
+                                break;
+                        }
+
+                        group.addView(view);
+                    }
+                } finally {
+                    childParser.close();
+                }
+            }
+        } else {
+            throw new InflateException("<include /> can only be used inside of a ViewGroup");
+        }
+
+        final int currentDepth = parser.getDepth();
+        while (((type = parser.next()) != XmlPullParser.END_TAG ||
+                parser.getDepth() > currentDepth) && type != XmlPullParser.END_DOCUMENT) {
+            // Empty
+        }
+    }    
+}
diff --git a/core/java/android/view/Menu.java b/core/java/android/view/Menu.java
new file mode 100644
index 0000000..97825e6
--- /dev/null
+++ b/core/java/android/view/Menu.java
@@ -0,0 +1,441 @@
+/*
+ * 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 android.view;
+
+import android.app.Activity;
+import android.content.ComponentName;
+import android.content.Intent;
+
+/**
+ * Interface for managing the items in a menu.
+ * <p>
+ * By default, every Activity supports an options menu of actions or options.
+ * You can add items to this menu and handle clicks on your additions. The
+ * easiest way of adding menu items is inflating an XML file into the
+ * {@link Menu} via {@link MenuInflater}. The easiest way of attaching code to
+ * clicks is via {@link Activity#onOptionsItemSelected(MenuItem)} and
+ * {@link Activity#onContextItemSelected(MenuItem)}.
+ * <p>
+ * Different menu types support different features:
+ * <ol>
+ * <li><b>Context menus</b>: Do not support item shortcuts and item icons.
+ * <li><b>Options menus</b>: The <b>icon menus</b> do not support item check
+ * marks and only show the item's
+ * {@link MenuItem#setTitleCondensed(CharSequence) condensed title}. The
+ * <b>expanded menus</b> (only available if six or more menu items are visible,
+ * reached via the 'More' item in the icon menu) do not show item icons, and
+ * item check marks are discouraged.
+ * <li><b>Sub menus</b>: Do not support item icons, or nested sub menus.
+ * </ol>
+ */
+public interface Menu {
+
+    /**
+     * This is the part of an order integer that the user can provide.
+     * @hide
+     */
+    static final int USER_MASK = 0x0000ffff;
+    /**
+     * Bit shift of the user portion of the order integer.
+     * @hide
+     */
+    static final int USER_SHIFT = 0;
+
+    /**
+     * This is the part of an order integer that supplies the category of the
+     * item.
+     * @hide
+     */
+    static final int CATEGORY_MASK = 0xffff0000;
+    /**
+     * Bit shift of the category portion of the order integer.
+     * @hide
+     */
+    static final int CATEGORY_SHIFT = 16;
+
+    /**
+     * Value to use for group and item identifier integers when you don't care
+     * about them.
+     */
+    static final int NONE = 0;
+
+    /**
+     * First value for group and item identifier integers.
+     */
+    static final int FIRST = 1;
+
+    // Implementation note: Keep these CATEGORY_* in sync with the category enum
+    // in attrs.xml
+
+    /**
+     * Category code for the order integer for items/groups that are part of a
+     * container -- or/add this with your base value.
+     */
+    static final int CATEGORY_CONTAINER = 0x00010000;
+
+    /**
+     * Category code for the order integer for items/groups that are provided by
+     * the system -- or/add this with your base value.
+     */
+    static final int CATEGORY_SYSTEM = 0x00020000;
+
+    /**
+     * Category code for the order integer for items/groups that are
+     * user-supplied secondary (infrequently used) options -- or/add this with
+     * your base value.
+     */
+    static final int CATEGORY_SECONDARY = 0x00030000;
+
+    /**
+     * Category code for the order integer for items/groups that are 
+     * alternative actions on the data that is currently displayed -- or/add
+     * this with your base value.
+     */
+    static final int CATEGORY_ALTERNATIVE = 0x00040000;
+
+    /**
+     * Flag for {@link #addIntentOptions}: if set, do not automatically remove
+     * any existing menu items in the same group.
+     */
+    static final int FLAG_APPEND_TO_GROUP = 0x0001;
+
+    /**
+     * Flag for {@link #performShortcut}: if set, do not close the menu after
+     * executing the shortcut.
+     */
+    static final int FLAG_PERFORM_NO_CLOSE = 0x0001;
+
+    /**
+     * Flag for {@link #performShortcut(int, KeyEvent, int)}: if set, always
+     * close the menu after executing the shortcut. Closing the menu also resets
+     * the prepared state.
+     */
+    static final int FLAG_ALWAYS_PERFORM_CLOSE = 0x0002;
+    
+    /**
+     * Add a new item to the menu. This item displays the given title for its
+     * label.
+     * 
+     * @param title The text to display for the item.
+     * @return The newly added menu item.
+     */
+    public MenuItem add(CharSequence title);
+    
+    /**
+     * Add a new item to the menu. This item displays the given title for its
+     * label.
+     * 
+     * @param titleRes Resource identifier of title string.
+     * @return The newly added menu item.
+     */
+    public MenuItem add(int titleRes);
+
+    /**
+     * Add a new item to the menu. This item displays the given title for its
+     * label.
+     * 
+     * @param groupId The group identifier that this item should be part of.
+     *        This can be used to define groups of items for batch state
+     *        changes. Normally use {@link #NONE} if an item should not be in a
+     *        group.
+     * @param itemId Unique item ID. Use {@link #NONE} if you do not need a
+     *        unique ID.
+     * @param order The order for the item. Use {@link #NONE} if you do not care
+     *        about the order. See {@link MenuItem#getOrder()}.
+     * @param title The text to display for the item.
+     * @return The newly added menu item.
+     */
+    public MenuItem add(int groupId, int itemId, int order, CharSequence title);
+
+    /**
+     * Variation on {@link #add(int, int, int, CharSequence)} that takes a
+     * string resource identifier instead of the string itself.
+     * 
+     * @param groupId The group identifier that this item should be part of.
+     *        This can also be used to define groups of items for batch state
+     *        changes. Normally use {@link #NONE} if an item should not be in a
+     *        group.
+     * @param itemId Unique item ID. Use {@link #NONE} if you do not need a
+     *        unique ID.
+     * @param order The order for the item. Use {@link #NONE} if you do not care
+     *        about the order. See {@link MenuItem#getOrder()}.
+     * @param titleRes Resource identifier of title string.
+     * @return The newly added menu item.
+     */
+    public MenuItem add(int groupId, int itemId, int order, int titleRes);
+
+    /**
+     * Add a new sub-menu to the menu. This item displays the given title for
+     * its label. To modify other attributes on the submenu's menu item, use
+     * {@link SubMenu#getItem()}.
+     * 
+     * @param title The text to display for the item.
+     * @return The newly added sub-menu
+     */
+    SubMenu addSubMenu(final CharSequence title);
+
+    /**
+     * Add a new sub-menu to the menu. This item displays the given title for
+     * its label. To modify other attributes on the submenu's menu item, use
+     * {@link SubMenu#getItem()}.
+     * 
+     * @param titleRes Resource identifier of title string.
+     * @return The newly added sub-menu
+     */
+    SubMenu addSubMenu(final int titleRes);
+
+    /**
+     * Add a new sub-menu to the menu. This item displays the given
+     * <var>title</var> for its label. To modify other attributes on the
+     * submenu's menu item, use {@link SubMenu#getItem()}.
+     *<p>
+     * Note that you can only have one level of sub-menus, i.e. you cannnot add
+     * a subMenu to a subMenu: An {@link UnsupportedOperationException} will be
+     * thrown if you try.
+     * 
+     * @param groupId The group identifier that this item should be part of.
+     *        This can also be used to define groups of items for batch state
+     *        changes. Normally use {@link #NONE} if an item should not be in a
+     *        group.
+     * @param itemId Unique item ID. Use {@link #NONE} if you do not need a
+     *        unique ID.
+     * @param order The order for the item. Use {@link #NONE} if you do not care
+     *        about the order. See {@link MenuItem#getOrder()}.
+     * @param title The text to display for the item.
+     * @return The newly added sub-menu
+     */
+    SubMenu addSubMenu(final int groupId, final int itemId, int order, final CharSequence title);
+
+    /**
+     * Variation on {@link #addSubMenu(int, int, int, CharSequence)} that takes
+     * a string resource identifier for the title instead of the string itself.
+     * 
+     * @param groupId The group identifier that this item should be part of.
+     *        This can also be used to define groups of items for batch state
+     *        changes. Normally use {@link #NONE} if an item should not be in a group.
+     * @param itemId Unique item ID. Use {@link #NONE} if you do not need a unique ID.
+     * @param order The order for the item. Use {@link #NONE} if you do not care about the
+     *        order. See {@link MenuItem#getOrder()}.
+     * @param titleRes Resource identifier of title string.
+     * @return The newly added sub-menu
+     */
+    SubMenu addSubMenu(int groupId, int itemId, int order, int titleRes);
+
+    /**
+     * Add a group of menu items corresponding to actions that can be performed
+     * for a particular Intent. The Intent is most often configured with a null
+     * action, the data that the current activity is working with, and includes
+     * either the {@link Intent#CATEGORY_ALTERNATIVE} or
+     * {@link Intent#CATEGORY_SELECTED_ALTERNATIVE} to find activities that have
+     * said they would like to be included as optional action. You can, however,
+     * use any Intent you want.
+     * 
+     * <p>
+     * See {@link android.content.pm.PackageManager#queryIntentActivityOptions}
+     * for more * details on the <var>caller</var>, <var>specifics</var>, and
+     * <var>intent</var> arguments. The list returned by that function is used
+     * to populate the resulting menu items.
+     * 
+     * <p>
+     * All of the menu items of possible options for the intent will be added
+     * with the given group and id. You can use the group to control ordering of
+     * the items in relation to other items in the menu. Normally this function
+     * will automatically remove any existing items in the menu in the same
+     * group and place a divider above and below the added items; this behavior
+     * can be modified with the <var>flags</var> parameter. For each of the
+     * generated items {@link MenuItem#setIntent} is called to associate the
+     * appropriate Intent with the item; this means the activity will
+     * automatically be started for you without having to do anything else.
+     * 
+     * @param groupId The group identifier that the items should be part of.
+     *        This can also be used to define groups of items for batch state
+     *        changes. Normally use {@link #NONE} if the items should not be in
+     *        a group.
+     * @param itemId Unique item ID. Use {@link #NONE} if you do not need a
+     *        unique ID.
+     * @param order The order for the items. Use {@link #NONE} if you do not
+     *        care about the order. See {@link MenuItem#getOrder()}.
+     * @param caller The current activity component name as defined by
+     *        queryIntentActivityOptions().
+     * @param specifics Specific items to place first as defined by
+     *        queryIntentActivityOptions().
+     * @param intent Intent describing the kinds of items to populate in the
+     *        list as defined by queryIntentActivityOptions().
+     * @param flags Additional options controlling how the items are added.
+     * @param outSpecificItems Optional array in which to place the menu items
+     *        that were generated for each of the <var>specifics</var> that were
+     *        requested. Entries may be null if no activity was found for that
+     *        specific action.
+     * @return The number of menu items that were added.
+     * 
+     * @see #FLAG_APPEND_TO_GROUP
+     * @see MenuItem#setIntent
+     * @see android.content.pm.PackageManager#queryIntentActivityOptions
+     */
+    public int addIntentOptions(int groupId, int itemId, int order,
+                                ComponentName caller, Intent[] specifics,
+                                Intent intent, int flags, MenuItem[] outSpecificItems);
+
+    /**
+     * Remove the item with the given identifier.
+     *
+     * @param id The item to be removed.  If there is no item with this
+     *           identifier, nothing happens.
+     */
+    public void removeItem(int id);
+
+    /**
+     * Remove all items in the given group.
+     *
+     * @param groupId The group to be removed.  If there are no items in this
+     *           group, nothing happens.
+     */
+    public void removeGroup(int groupId);
+
+    /**
+     * Remove all existing items from the menu, leaving it empty as if it had
+     * just been created.
+     */
+    public void clear();
+
+    /**
+     * Control whether a particular group of items can show a check mark.  This
+     * is similar to calling {@link MenuItem#setCheckable} on all of the menu items
+     * with the given group identifier, but in addition you can control whether
+     * this group contains a mutually-exclusive set items.  This should be called
+     * after the items of the group have been added to the menu.
+     *
+     * @param group The group of items to operate on.
+     * @param checkable Set to true to allow a check mark, false to
+     *                  disallow.  The default is false.
+     * @param exclusive If set to true, only one item in this group can be
+     *                  checked at a time; checking an item will automatically
+     *                  uncheck all others in the group.  If set to false, each
+     *                  item can be checked independently of the others.
+     *
+     * @see MenuItem#setCheckable
+     * @see MenuItem#setChecked
+     */
+    public void setGroupCheckable(int group, boolean checkable, boolean exclusive);
+
+    /**
+     * Show or hide all menu items that are in the given group.
+     *
+     * @param group The group of items to operate on.
+     * @param visible If true the items are visible, else they are hidden.
+     *
+     * @see MenuItem#setVisible
+     */
+    public void setGroupVisible(int group, boolean visible);
+    
+    /**
+     * Enable or disable all menu items that are in the given group.
+     *
+     * @param group The group of items to operate on.
+     * @param enabled If true the items will be enabled, else they will be disabled.
+     *
+     * @see MenuItem#setEnabled
+     */
+    public void setGroupEnabled(int group, boolean enabled);
+    
+    /**
+     * Return whether the menu currently has item items that are visible.
+     *
+     * @return True if there is one or more item visible,
+     *         else false.
+     */
+    public boolean hasVisibleItems();
+
+    /**
+     * Return the menu item with a particular identifier.
+     *
+     * @param id The identifier to find.
+     *
+     * @return The menu item object, or null if there is no item with
+     *         this identifier.
+     */
+    public MenuItem findItem(int id);
+
+    /**
+     * Get the number of items in the menu.  Note that this will change any
+     * times items are added or removed from the menu.
+     *
+     * @return The item count.
+     */
+    public int size();
+
+    /**
+     * Gets the menu item at the given index.
+     * 
+     * @param index The index of the menu item to return.
+     * @return The menu item.
+     * @exception IndexOutOfBoundsException
+     *                when {@code index < 0 || >= size()}
+     */
+    public MenuItem getItem(int index);
+    
+    /**
+     * Closes the menu, if open.
+     */
+    public void close();
+    
+    /**
+     * Execute the menu item action associated with the given shortcut
+     * character.
+     *
+     * @param keyCode The keycode of the shortcut key.
+     * @param event Key event message.
+     * @param flags Additional option flags or 0.
+     *
+     * @return If the given shortcut exists and is shown, returns
+     *         true; else returns false.
+     *
+     * @see #FLAG_PERFORM_NO_CLOSE
+     */
+    public boolean performShortcut(int keyCode, KeyEvent event, int flags);
+
+    /**
+     * Is a keypress one of the defined shortcut keys for this window.
+     * @param keyCode the key code from {@link KeyEvent} to check.
+     * @param event the {@link KeyEvent} to use to help check.
+     */
+    boolean isShortcutKey(int keyCode, KeyEvent event);
+    
+    /**
+     * Execute the menu item action associated with the given menu identifier.
+     * 
+     * @param id Identifier associated with the menu item. 
+     * @param flags Additional option flags or 0.
+     * 
+     * @return If the given identifier exists and is shown, returns
+     *         true; else returns false.
+     * 
+     * @see #FLAG_PERFORM_NO_CLOSE
+     */
+    public boolean performIdentifierAction(int id, int flags);
+
+
+    /**
+     * Control whether the menu should be running in qwerty mode (alphabetic
+     * shortcuts) or 12-key mode (numeric shortcuts).
+     * 
+     * @param isQwerty If true the menu will use alphabetic shortcuts; else it
+     *                 will use numeric shortcuts.
+     */
+    public void setQwertyMode(boolean isQwerty);
+}
+
diff --git a/core/java/android/view/MenuInflater.java b/core/java/android/view/MenuInflater.java
new file mode 100644
index 0000000..46c805c
--- /dev/null
+++ b/core/java/android/view/MenuInflater.java
@@ -0,0 +1,325 @@
+/*
+ * 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 android.view;
+
+import com.android.internal.view.menu.MenuItemImpl;
+
+import java.io.IOException;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.content.res.XmlResourceParser;
+import android.util.AttributeSet;
+import android.util.Xml;
+
+/**
+ * This class is used to instantiate menu XML files into Menu objects.
+ * <p>
+ * For performance reasons, menu inflation relies heavily on pre-processing of
+ * XML files that is done at build time. Therefore, it is not currently possible
+ * to use MenuInflater with an XmlPullParser over a plain XML file at runtime;
+ * it only works with an XmlPullParser returned from a compiled resource (R.
+ * <em>something</em> file.)
+ */
+public class MenuInflater {
+    /** Menu tag name in XML. */
+    private static final String XML_MENU = "menu";
+    
+    /** Group tag name in XML. */
+    private static final String XML_GROUP = "group";
+    
+    /** Item tag name in XML. */
+    private static final String XML_ITEM = "item";
+
+    private static final int NO_ID = 0;
+    
+    private Context mContext;
+    
+    /**
+     * Constructs a menu inflater.
+     * 
+     * @see Activity#getMenuInflater()
+     */
+    public MenuInflater(Context context) {
+        mContext = context;
+    }
+
+    /**
+     * Inflate a menu hierarchy from the specified XML resource. Throws
+     * {@link InflateException} if there is an error.
+     * 
+     * @param menuRes Resource ID for an XML layout resource to load (e.g.,
+     *            <code>R.menu.main_activity</code>)
+     * @param menu The Menu to inflate into. The items and submenus will be
+     *            added to this Menu.
+     */
+    public void inflate(int menuRes, Menu menu) {
+        XmlResourceParser parser = null;
+        try {
+            parser = mContext.getResources().getLayout(menuRes);
+            AttributeSet attrs = Xml.asAttributeSet(parser);
+            
+            parseMenu(parser, attrs, menu);
+        } catch (XmlPullParserException e) {
+            throw new InflateException("Error inflating menu XML", e);
+        } catch (IOException e) {
+            throw new InflateException("Error inflating menu XML", e);
+        } finally {
+            if (parser != null) parser.close();
+        }
+    }
+
+    /**
+     * Called internally to fill the given menu. If a sub menu is seen, it will
+     * call this recursively.
+     */
+    private void parseMenu(XmlPullParser parser, AttributeSet attrs, Menu menu)
+            throws XmlPullParserException, IOException {
+        MenuState menuState = new MenuState(menu);
+
+        int eventType = parser.getEventType();
+        String tagName;
+        boolean lookingForEndOfUnknownTag = false;
+        String unknownTagName = null;
+
+        // This loop will skip to the menu start tag
+        do {
+            if (eventType == XmlPullParser.START_TAG) {
+                tagName = parser.getName();
+                if (tagName.equals(XML_MENU)) {
+                    // Go to next tag
+                    eventType = parser.next();
+                    break;
+                }
+                
+                throw new RuntimeException("Expecting menu, got " + tagName);
+            }
+            eventType = parser.next();
+        } while (eventType != XmlPullParser.END_DOCUMENT);
+        
+        boolean reachedEndOfMenu = false;
+        while (!reachedEndOfMenu) {
+            switch (eventType) {
+                case XmlPullParser.START_TAG:
+                    if (lookingForEndOfUnknownTag) {
+                        break;
+                    }
+                    
+                    tagName = parser.getName();
+                    if (tagName.equals(XML_GROUP)) {
+                        menuState.readGroup(attrs);
+                    } else if (tagName.equals(XML_ITEM)) {
+                        menuState.readItem(attrs);
+                    } else if (tagName.equals(XML_MENU)) {
+                        // A menu start tag denotes a submenu for an item
+                        SubMenu subMenu = menuState.addSubMenuItem();
+
+                        // Parse the submenu into returned SubMenu
+                        parseMenu(parser, attrs, subMenu);
+                    } else {
+                        lookingForEndOfUnknownTag = true;
+                        unknownTagName = tagName;
+                    }
+                    break;
+                    
+                case XmlPullParser.END_TAG:
+                    tagName = parser.getName();
+                    if (lookingForEndOfUnknownTag && tagName.equals(unknownTagName)) {
+                        lookingForEndOfUnknownTag = false;
+                        unknownTagName = null;
+                    } else if (tagName.equals(XML_GROUP)) {
+                        menuState.resetGroup();
+                    } else if (tagName.equals(XML_ITEM)) {
+                        // Add the item if it hasn't been added (if the item was
+                        // a submenu, it would have been added already)
+                        if (!menuState.hasAddedItem()) {
+                            menuState.addItem();
+                        }
+                    } else if (tagName.equals(XML_MENU)) {
+                        reachedEndOfMenu = true;
+                    }
+                    break;
+                    
+                case XmlPullParser.END_DOCUMENT:
+                    throw new RuntimeException("Unexpected end of document");
+            }
+            
+            eventType = parser.next();
+        }
+    }
+    
+    /**
+     * State for the current menu.
+     * <p>
+     * Groups can not be nested unless there is another menu (which will have
+     * its state class).
+     */
+    private class MenuState {
+        private Menu menu;
+
+        /*
+         * Group state is set on items as they are added, allowing an item to
+         * override its group state. (As opposed to set on items at the group end tag.)
+         */
+        private int groupId;
+        private int groupCategory;
+        private int groupOrder;
+        private int groupCheckable;
+        private boolean groupVisible;
+        private boolean groupEnabled;
+
+        private boolean itemAdded;
+        private int itemId;
+        private int itemCategoryOrder;
+        private String itemTitle;
+        private String itemTitleCondensed;
+        private int itemIconResId;
+        private char itemAlphabeticShortcut;
+        private char itemNumericShortcut;
+        /**
+         * Sync to attrs.xml enum:
+         * - 0: none
+         * - 1: all
+         * - 2: exclusive
+         */
+        private int itemCheckable;
+        private boolean itemChecked;
+        private boolean itemVisible;
+        private boolean itemEnabled;
+        
+        private static final int defaultGroupId = NO_ID;
+        private static final int defaultItemId = NO_ID;
+        private static final int defaultItemCategory = 0;
+        private static final int defaultItemOrder = 0;
+        private static final int defaultItemCheckable = 0;
+        private static final boolean defaultItemChecked = false;
+        private static final boolean defaultItemVisible = true;
+        private static final boolean defaultItemEnabled = true;
+        
+        public MenuState(final Menu menu) {
+            this.menu = menu;
+            
+            resetGroup();
+        }
+        
+        public void resetGroup() {
+            groupId = defaultGroupId;
+            groupCategory = defaultItemCategory;
+            groupOrder = defaultItemOrder;
+            groupCheckable = defaultItemCheckable;
+            groupVisible = defaultItemVisible;
+            groupEnabled = defaultItemEnabled;
+        }
+
+        /**
+         * Called when the parser is pointing to a group tag.
+         */
+        public void readGroup(AttributeSet attrs) {
+            TypedArray a = mContext.obtainStyledAttributes(attrs,
+                    com.android.internal.R.styleable.MenuGroup);
+            
+            groupId = a.getResourceId(com.android.internal.R.styleable.MenuGroup_id, defaultGroupId);
+            groupCategory = a.getInt(com.android.internal.R.styleable.MenuGroup_menuCategory, defaultItemCategory);
+            groupOrder = a.getInt(com.android.internal.R.styleable.MenuGroup_orderInCategory, defaultItemOrder);
+            groupCheckable = a.getInt(com.android.internal.R.styleable.MenuGroup_checkableBehavior, defaultItemCheckable);
+            groupVisible = a.getBoolean(com.android.internal.R.styleable.MenuGroup_visible, defaultItemVisible);
+            groupEnabled = a.getBoolean(com.android.internal.R.styleable.MenuGroup_enabled, defaultItemEnabled);
+
+            a.recycle();
+        }
+        
+        /**
+         * Called when the parser is pointing to an item tag.
+         */
+        public void readItem(AttributeSet attrs) {
+            TypedArray a = mContext.obtainStyledAttributes(attrs,
+                    com.android.internal.R.styleable.MenuItem);
+
+            // Inherit attributes from the group as default value
+            itemId = a.getResourceId(com.android.internal.R.styleable.MenuItem_id, defaultItemId);
+            final int category = a.getInt(com.android.internal.R.styleable.MenuItem_menuCategory, groupCategory);
+            final int order = a.getInt(com.android.internal.R.styleable.MenuItem_orderInCategory, groupOrder);
+            itemCategoryOrder = (category & Menu.CATEGORY_MASK) | (order & Menu.USER_MASK);
+            itemTitle = a.getString(com.android.internal.R.styleable.MenuItem_title);
+            itemTitleCondensed = a.getString(com.android.internal.R.styleable.MenuItem_titleCondensed);
+            itemIconResId = a.getResourceId(com.android.internal.R.styleable.MenuItem_icon, 0);
+            itemAlphabeticShortcut =
+                    getShortcut(a.getString(com.android.internal.R.styleable.MenuItem_alphabeticShortcut));
+            itemNumericShortcut =
+                    getShortcut(a.getString(com.android.internal.R.styleable.MenuItem_numericShortcut));
+            if (a.hasValue(com.android.internal.R.styleable.MenuItem_checkable)) {
+                // Item has attribute checkable, use it
+                itemCheckable = a.getBoolean(com.android.internal.R.styleable.MenuItem_checkable, false) ? 1 : 0;
+            } else {
+                // Item does not have attribute, use the group's (group can have one more state
+                // for checkable that represents the exclusive checkable)
+                itemCheckable = groupCheckable;
+            }
+            itemChecked = a.getBoolean(com.android.internal.R.styleable.MenuItem_checked, defaultItemChecked);
+            itemVisible = a.getBoolean(com.android.internal.R.styleable.MenuItem_visible, groupVisible);
+            itemEnabled = a.getBoolean(com.android.internal.R.styleable.MenuItem_enabled, groupEnabled);
+            
+            a.recycle();
+            
+            itemAdded = false;
+        }
+
+        private char getShortcut(String shortcutString) {
+            if (shortcutString == null) {
+                return 0;
+            } else {
+                return shortcutString.charAt(0);
+            }
+        }
+        
+        private void setItem(MenuItem item) {
+            item.setChecked(itemChecked)
+                .setVisible(itemVisible)
+                .setEnabled(itemEnabled)
+                .setCheckable(itemCheckable >= 1)
+                .setTitleCondensed(itemTitleCondensed)
+                .setIcon(itemIconResId)
+                .setAlphabeticShortcut(itemAlphabeticShortcut)
+                .setNumericShortcut(itemNumericShortcut);
+
+            if (itemCheckable >= 2) {
+                ((MenuItemImpl) item).setExclusiveCheckable(true);
+            }
+        }
+        
+        public void addItem() {
+            itemAdded = true;
+            setItem(menu.add(groupId, itemId, itemCategoryOrder, itemTitle));
+        }
+        
+        public SubMenu addSubMenuItem() {
+            itemAdded = true;
+            SubMenu subMenu = menu.addSubMenu(groupId, itemId, itemCategoryOrder, itemTitle);
+            setItem(subMenu.getItem());
+            return subMenu;
+        }
+        
+        public boolean hasAddedItem() {
+            return itemAdded;
+        }
+    }
+    
+}
diff --git a/core/java/android/view/MenuItem.java b/core/java/android/view/MenuItem.java
new file mode 100644
index 0000000..fcebec5
--- /dev/null
+++ b/core/java/android/view/MenuItem.java
@@ -0,0 +1,384 @@
+/*
+ * 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 android.view;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.graphics.drawable.Drawable;
+import android.view.ContextMenu.ContextMenuInfo;
+import android.view.View.OnCreateContextMenuListener;
+
+/**
+ * Interface for direct access to a previously created menu item.
+ * <p>
+ * An Item is returned by calling one of the {@link android.view.Menu#add}
+ * methods.
+ * <p>
+ * For a feature set of specific menu types, see {@link Menu}.
+ */
+public interface MenuItem {
+    /**
+     * Interface definition for a callback to be invoked when a menu item is
+     * clicked.
+     *
+     * @see Activity#onContextItemSelected(MenuItem)
+     * @see Activity#onOptionsItemSelected(MenuItem)
+     */
+    public interface OnMenuItemClickListener {
+        /**
+         * Called when a menu item has been invoked.  This is the first code
+         * that is executed; if it returns true, no other callbacks will be
+         * executed.
+         *
+         * @param item The menu item that was invoked.
+         *
+         * @return Return true to consume this click and prevent others from
+         *         executing.
+         */
+        public boolean onMenuItemClick(MenuItem item);
+    }
+
+    /**
+     * Return the identifier for this menu item.  The identifier can not
+     * be changed after the menu is created.
+     *
+     * @return The menu item's identifier.
+     */
+    public int getItemId();
+
+    /**
+     * Return the group identifier that this menu item is part of. The group
+     * identifier can not be changed after the menu is created.
+     * 
+     * @return The menu item's group identifier.
+     */
+    public int getGroupId();
+
+    /**
+     * Return the category and order within the category of this item. This
+     * item will be shown before all items (within its category) that have
+     * order greater than this value.
+     * <p>
+     * An order integer contains the item's category (the upper bits of the
+     * integer; set by or/add the category with the order within the
+     * category) and the ordering of the item within that category (the
+     * lower bits). Example categories are {@link Menu#CATEGORY_SYSTEM},
+     * {@link Menu#CATEGORY_SECONDARY}, {@link Menu#CATEGORY_ALTERNATIVE},
+     * {@link Menu#CATEGORY_CONTAINER}. See {@link Menu} for a full list.
+     * 
+     * @return The order of this item.
+     */
+    public int getOrder();
+    
+    /**
+     * Change the title associated with this item.
+     *
+     * @param title The new text to be displayed.
+     * @return This Item so additional setters can be called.
+     */
+    public MenuItem setTitle(CharSequence title);
+
+    /**
+     * Change the title associated with this item.
+     * <p>
+     * Some menu types do not sufficient space to show the full title, and
+     * instead a condensed title is preferred. See {@link Menu} for more
+     * information.
+     * 
+     * @param title The resource id of the new text to be displayed.
+     * @return This Item so additional setters can be called.
+     * @see #setTitleCondensed(CharSequence)
+     */
+    
+    public MenuItem setTitle(int title);
+
+    /**
+     * Retrieve the current title of the item.
+     *
+     * @return The title.
+     */
+    public CharSequence getTitle();
+
+    /**
+     * Change the condensed title associated with this item. The condensed
+     * title is used in situations where the normal title may be too long to
+     * be displayed.
+     * 
+     * @param title The new text to be displayed as the condensed title.
+     * @return This Item so additional setters can be called.
+     */
+    public MenuItem setTitleCondensed(CharSequence title);
+
+    /**
+     * Retrieve the current condensed title of the item. If a condensed
+     * title was never set, it will return the normal title.
+     * 
+     * @return The condensed title, if it exists.
+     *         Otherwise the normal title.
+     */
+    public CharSequence getTitleCondensed();
+
+    /**
+     * Change the icon associated with this item. This icon will not always be
+     * shown, so the title should be sufficient in describing this item. See
+     * {@link Menu} for the menu types that support icons.
+     * 
+     * @param icon The new icon (as a Drawable) to be displayed.
+     * @return This Item so additional setters can be called.
+     */
+    public MenuItem setIcon(Drawable icon);
+
+    /**
+     * Change the icon associated with this item. This icon will not always be
+     * shown, so the title should be sufficient in describing this item. See
+     * {@link Menu} for the menu types that support icons.
+     * <p>
+     * This method will set the resource ID of the icon which will be used to
+     * lazily get the Drawable when this item is being shown.
+     * 
+     * @param iconRes The new icon (as a resource ID) to be displayed.
+     * @return This Item so additional setters can be called.
+     */
+    public MenuItem setIcon(int iconRes);
+    
+    /**
+     * Returns the icon for this item as a Drawable (getting it from resources if it hasn't been
+     * loaded before).
+     * 
+     * @return The icon as a Drawable.
+     */
+    public Drawable getIcon();
+    
+    /**
+     * Change the Intent associated with this item.  By default there is no
+     * Intent associated with a menu item.  If you set one, and nothing
+     * else handles the item, then the default behavior will be to call
+     * {@link android.content.Context#startActivity} with the given Intent.
+     *
+     * <p>Note that setIntent() can not be used with the versions of
+     * {@link Menu#add} that take a Runnable, because {@link Runnable#run}
+     * does not return a value so there is no way to tell if it handled the
+     * item.  In this case it is assumed that the Runnable always handles
+     * the item, and the intent will never be started.
+     *
+     * @see #getIntent
+     * @param intent The Intent to associated with the item.  This Intent
+     *               object is <em>not</em> copied, so be careful not to
+     *               modify it later.
+     * @return This Item so additional setters can be called.
+     */
+    public MenuItem setIntent(Intent intent);
+
+    /**
+     * Return the Intent associated with this item.  This returns a
+     * reference to the Intent which you can change as desired to modify
+     * what the Item is holding.
+     *
+     * @see #setIntent
+     * @return Returns the last value supplied to {@link #setIntent}, or
+     *         null.
+     */
+    public Intent getIntent();
+
+    /**
+     * Change both the numeric and alphabetic shortcut associated with this
+     * item. Note that the shortcut will be triggered when the key that
+     * generates the given character is pressed alone or along with with the alt
+     * key. Also note that case is not significant and that alphabetic shortcut
+     * characters will be displayed in lower case.
+     * <p>
+     * See {@link Menu} for the menu types that support shortcuts.
+     * 
+     * @param numericChar The numeric shortcut key. This is the shortcut when
+     *        using a numeric (e.g., 12-key) keyboard.
+     * @param alphaChar The alphabetic shortcut key. This is the shortcut when
+     *        using a keyboard with alphabetic keys.
+     * @return This Item so additional setters can be called.
+     */
+    public MenuItem setShortcut(char numericChar, char alphaChar);
+
+    /**
+     * Change the numeric shortcut associated with this item.
+     * <p>
+     * See {@link Menu} for the menu types that support shortcuts.
+     *
+     * @param numericChar The numeric shortcut key.  This is the shortcut when
+     *                 using a 12-key (numeric) keyboard.
+     * @return This Item so additional setters can be called.
+     */
+    public MenuItem setNumericShortcut(char numericChar);
+
+    /**
+     * Return the char for this menu item's numeric (12-key) shortcut.
+     *
+     * @return Numeric character to use as a shortcut.
+     */
+    public char getNumericShortcut();
+
+    /**
+     * Change the alphabetic shortcut associated with this item. The shortcut
+     * will be triggered when the key that generates the given character is
+     * pressed alone or along with with the alt key. Case is not significant and
+     * shortcut characters will be displayed in lower case. Note that menu items
+     * with the characters '\b' or '\n' as shortcuts will get triggered by the
+     * Delete key or Carriage Return key, respectively.
+     * <p>
+     * See {@link Menu} for the menu types that support shortcuts.
+     * 
+     * @param alphaChar The alphabetic shortcut key. This is the shortcut when
+     *        using a keyboard with alphabetic keys.
+     * @return This Item so additional setters can be called.
+     */
+    public MenuItem setAlphabeticShortcut(char alphaChar);
+
+    /**
+     * Return the char for this menu item's alphabetic shortcut.
+     *
+     * @return Alphabetic character to use as a shortcut.
+     */
+    public char getAlphabeticShortcut();
+
+    /**
+     * Control whether this item can display a check mark. Setting this does
+     * not actually display a check mark (see {@link #setChecked} for that);
+     * rather, it ensures there is room in the item in which to display a
+     * check mark.
+     * <p>
+     * See {@link Menu} for the menu types that support check marks.
+     * 
+     * @param checkable Set to true to allow a check mark, false to
+     *            disallow. The default is false.
+     * @see #setChecked
+     * @see #isCheckable
+     * @see Menu#setGroupCheckable
+     * @return This Item so additional setters can be called.
+     */
+    public MenuItem setCheckable(boolean checkable);
+
+    /**
+     * Return whether the item can currently display a check mark.
+     *
+     * @return If a check mark can be displayed, returns true.
+     *
+     * @see #setCheckable
+     */
+    public boolean isCheckable();
+
+    /**
+     * Control whether this item is shown with a check mark.  Note that you
+     * must first have enabled checking with {@link #setCheckable} or else
+     * the check mark will not appear.  If this item is a member of a group that contains
+     * mutually-exclusive items (set via {@link Menu#setGroupCheckable(int, boolean, boolean)},
+     * the other items in the group will be unchecked.
+     * <p>
+     * See {@link Menu} for the menu types that support check marks.
+     *
+     * @see #setCheckable
+     * @see #isChecked
+     * @see Menu#setGroupCheckable
+     * @param checked Set to true to display a check mark, false to hide
+     *                it.  The default value is false.
+     * @return This Item so additional setters can be called.
+     */
+    public MenuItem setChecked(boolean checked);
+
+    /**
+     * Return whether the item is currently displaying a check mark.
+     *
+     * @return If a check mark is displayed, returns true.
+     *
+     * @see #setChecked
+     */
+    public boolean isChecked();
+
+    /**
+     * Sets the visibility of the menu item. Even if a menu item is not visible,
+     * it may still be invoked via its shortcut (to completely disable an item,
+     * set it to invisible and {@link #setEnabled(boolean) disabled}).
+     * 
+     * @param visible If true then the item will be visible; if false it is
+     *        hidden.
+     * @return This Item so additional setters can be called.
+     */
+    public MenuItem setVisible(boolean visible);
+
+    /**
+     * Return the visibility of the menu item.
+     *
+     * @return If true the item is visible; else it is hidden.
+     */
+    public boolean isVisible();
+
+    /**
+     * Sets whether the menu item is enabled. Disabling a menu item will not
+     * allow it to be invoked via its shortcut. The menu item will still be
+     * visible.
+     * 
+     * @param enabled If true then the item will be invokable; if false it is
+     *        won't be invokable.
+     * @return This Item so additional setters can be called.
+     */
+    public MenuItem setEnabled(boolean enabled);
+
+    /**
+     * Return the enabled state of the menu item.
+     *
+     * @return If true the item is enabled and hence invokable; else it is not.
+     */
+    public boolean isEnabled();
+
+    /**
+     * Check whether this item has an associated sub-menu.  I.e. it is a
+     * sub-menu of another menu.
+     *
+     * @return If true this item has a menu; else it is a
+     *         normal item.
+     */
+    public boolean hasSubMenu();
+
+    /**
+     * Get the sub-menu to be invoked when this item is selected, if it has
+     * one. See {@link #hasSubMenu()}.
+     *
+     * @return The associated menu if there is one, else null
+     */
+    public SubMenu getSubMenu();
+
+    /**
+     * Set a custom listener for invocation of this menu item. In most
+     * situations, it is more efficient and easier to use
+     * {@link Activity#onOptionsItemSelected(MenuItem)} or
+     * {@link Activity#onContextItemSelected(MenuItem)}.
+     * 
+     * @param menuItemClickListener The object to receive invokations.
+     * @return This Item so additional setters can be called.
+     * @see Activity#onOptionsItemSelected(MenuItem)
+     * @see Activity#onContextItemSelected(MenuItem)
+     */
+    public MenuItem setOnMenuItemClickListener(MenuItem.OnMenuItemClickListener menuItemClickListener);
+
+    /**
+     * Gets the extra information linked to this menu item.  This extra
+     * information is set by the View that added this menu item to the
+     * menu.
+     * 
+     * @see OnCreateContextMenuListener
+     * @return The extra information linked to the View that added this
+     *         menu item to the menu. This can be null.
+     */
+    public ContextMenuInfo getMenuInfo();
+}
\ No newline at end of file
diff --git a/core/java/android/view/MotionEvent.aidl b/core/java/android/view/MotionEvent.aidl
new file mode 100644
index 0000000..3c89988c
--- /dev/null
+++ b/core/java/android/view/MotionEvent.aidl
@@ -0,0 +1,20 @@
+/* //device/java/android/android.view.KeyEvent.aidl
+**
+** Copyright 2007, 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 android.view;
+
+parcelable MotionEvent;
diff --git a/core/java/android/view/MotionEvent.java b/core/java/android/view/MotionEvent.java
new file mode 100644
index 0000000..882a079
--- /dev/null
+++ b/core/java/android/view/MotionEvent.java
@@ -0,0 +1,685 @@
+/*
+ * Copyright (C) 2007 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 android.view;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.SystemClock;
+import android.util.Config;
+
+/**
+ * Object used to report movement (mouse, pen, finger, trackball) events.  This
+ * class may hold either absolute or relative movements, depending on what
+ * it is being used for.
+ */
+public final class MotionEvent implements Parcelable {
+    /**
+     * Constant for {@link #getAction}: A pressed gesture has started, the
+     * motion contains the initial starting location.
+     */
+    public static final int ACTION_DOWN             = 0;
+    /**
+     * Constant for {@link #getAction}: A pressed gesture has finished, the
+     * motion contains the final release location as well as any intermediate
+     * points since the last down or move event.
+     */
+    public static final int ACTION_UP               = 1;
+    /**
+     * Constant for {@link #getAction}: A change has happened during a
+     * press gesture (between {@link #ACTION_DOWN} and {@link #ACTION_UP}).
+     * The motion contains the most recent point, as well as any intermediate
+     * points since the last down or move event.
+     */
+    public static final int ACTION_MOVE             = 2;
+    /**
+     * Constant for {@link #getAction}: The current gesture has been aborted.
+     * You will not receive any more points in it.  You should treat this as
+     * an up event, but not perform any action that you normally would.
+     */
+    public static final int ACTION_CANCEL           = 3;
+    /**
+     * Constant for {@link #getAction}: A movement has happened outside of the
+     * normal bounds of the UI element.  This does not provide a full gesture,
+     * but only the initial location of the movement/touch.
+     */
+    public static final int ACTION_OUTSIDE          = 4;
+
+    private static final boolean TRACK_RECYCLED_LOCATION = false;
+    
+    /**
+     * Flag indicating the motion event intersected the top edge of the screen.
+     */
+    public static final int EDGE_TOP = 0x00000001;
+    
+    /**
+     * Flag indicating the motion event intersected the bottom edge of the screen.
+     */
+    public static final int EDGE_BOTTOM = 0x00000002;
+    
+    /**
+     * Flag indicating the motion event intersected the left edge of the screen.
+     */
+    public static final int EDGE_LEFT = 0x00000004;
+    
+    /**
+     * Flag indicating the motion event intersected the right edge of the screen.
+     */
+    public static final int EDGE_RIGHT = 0x00000008;
+    
+    static private final int MAX_RECYCLED = 10;
+    static private Object gRecyclerLock = new Object();
+    static private int gRecyclerUsed = 0;
+    static private MotionEvent gRecyclerTop = null;
+    
+    private long mDownTime;
+    private long mEventTime;
+    private int mAction;
+    private float mX;
+    private float mY;
+    private float mRawX;
+    private float mRawY;
+    private float mPressure;
+    private float mSize;
+    private int mMetaState;
+    private int mNumHistory;
+    private float[] mHistory;
+    private long[] mHistoryTimes;
+    private float mXPrecision;
+    private float mYPrecision;
+    private int mDeviceId;
+    private int mEdgeFlags;
+    
+    private MotionEvent mNext;
+    private RuntimeException mRecycledLocation;
+    private boolean mRecycled;
+
+    private MotionEvent() {
+    }
+    
+    static private MotionEvent obtain() {
+        synchronized (gRecyclerLock) {
+            if (gRecyclerTop == null) {
+                return new MotionEvent();
+            }
+            MotionEvent ev = gRecyclerTop;
+            gRecyclerTop = ev.mNext;
+            gRecyclerUsed--;
+            ev.mRecycledLocation = null;
+            ev.mRecycled = false;
+            return ev;
+        }
+    }
+    
+    /**
+     * Create a new MotionEvent, filling in all of the basic values that
+     * define the motion.
+     * 
+     * @param downTime The time (in ms) when the user originally pressed down to start 
+     * a stream of position events.  This must be obtained from {@link SystemClock#uptimeMillis()}.
+     * @param eventTime  The the time (in ms) when this specific event was generated.  This 
+     * must be obtained from {@link SystemClock#uptimeMillis()}.
+     * @param action The kind of action being performed -- one of either
+     * {@link #ACTION_DOWN}, {@link #ACTION_MOVE}, {@link #ACTION_UP}, or
+     * {@link #ACTION_CANCEL}.
+     * @param x The X coordinate of this event.
+     * @param y The Y coordinate of this event.
+     * @param pressure The current pressure of this event.  The pressure generally 
+     * ranges from 0 (no pressure at all) to 1 (normal pressure), however 
+     * values higher than 1 may be generated depending on the calibration of 
+     * the input device.
+     * @param size A scaled value of the approximate size of the area being pressed when
+     * touched with the finger. The actual value in pixels corresponding to the finger 
+     * touch is normalized with a device specific range of values
+     * and scaled to a value between 0 and 1.
+     * @param metaState The state of any meta / modifier keys that were in effect when
+     * the event was generated.
+     * @param xPrecision The precision of the X coordinate being reported.
+     * @param yPrecision The precision of the Y coordinate being reported.
+     * @param deviceId The id for the device that this event came from.  An id of
+     * zero indicates that the event didn't come from a physical device; other
+     * numbers are arbitrary and you shouldn't depend on the values.
+     * @param edgeFlags A bitfield indicating which edges, if any, where touched by this
+     * MotionEvent.
+     */
+    static public MotionEvent obtain(long downTime, long eventTime, int action,
+            float x, float y, float pressure, float size, int metaState,
+            float xPrecision, float yPrecision, int deviceId, int edgeFlags) {
+        MotionEvent ev = obtain();
+        ev.mDeviceId = deviceId;
+        ev.mEdgeFlags = edgeFlags;
+        ev.mDownTime = downTime;
+        ev.mEventTime = eventTime;
+        ev.mAction = action;
+        ev.mX = ev.mRawX = x;
+        ev.mY = ev.mRawY = y;
+        ev.mPressure = pressure;
+        ev.mSize = size;
+        ev.mMetaState = metaState;
+        ev.mXPrecision = xPrecision;
+        ev.mYPrecision = yPrecision;
+
+        return ev;
+    }
+    
+    /**
+     * Create a new MotionEvent, filling in a subset of the basic motion
+     * values.  Those not specified here are: device id (always 0), pressure
+     * and size (always 1), x and y precision (always 1), and edgeFlags (always 0).
+     * 
+     * @param downTime The time (in ms) when the user originally pressed down to start 
+     * a stream of position events.  This must be obtained from {@link SystemClock#uptimeMillis()}.
+     * @param eventTime  The the time (in ms) when this specific event was generated.  This 
+     * must be obtained from {@link SystemClock#uptimeMillis()}.
+     * @param action The kind of action being performed -- one of either
+     * {@link #ACTION_DOWN}, {@link #ACTION_MOVE}, {@link #ACTION_UP}, or
+     * {@link #ACTION_CANCEL}.
+     * @param x The X coordinate of this event.
+     * @param y The Y coordinate of this event.
+     * @param metaState The state of any meta / modifier keys that were in effect when
+     * the event was generated.
+     */
+    static public MotionEvent obtain(long downTime, long eventTime, int action,
+            float x, float y, int metaState) {
+        MotionEvent ev = obtain();
+        ev.mDeviceId = 0;
+        ev.mEdgeFlags = 0;
+        ev.mDownTime = downTime;
+        ev.mEventTime = eventTime;
+        ev.mAction = action;
+        ev.mX = ev.mRawX = x;
+        ev.mY = ev.mRawY = y;
+        ev.mPressure = 1.0f;
+        ev.mSize = 1.0f;
+        ev.mMetaState = metaState;
+        ev.mXPrecision = 1.0f;
+        ev.mYPrecision = 1.0f;
+
+        return ev;
+    }
+    
+    /**
+     * Create a new MotionEvent, copying from an existing one.
+     */
+    static public MotionEvent obtain(MotionEvent o) {
+        MotionEvent ev = obtain();
+        ev.mDeviceId = o.mDeviceId;
+        ev.mEdgeFlags = o.mEdgeFlags;
+        ev.mDownTime = o.mDownTime;
+        ev.mEventTime = o.mEventTime;
+        ev.mAction = o.mAction;
+        ev.mX = o.mX;
+        ev.mRawX = o.mRawX;
+        ev.mY = o.mY;
+        ev.mRawY = o.mRawY;
+        ev.mPressure = o.mPressure;
+        ev.mSize = o.mSize;
+        ev.mMetaState = o.mMetaState;
+        ev.mXPrecision = o.mXPrecision;
+        ev.mYPrecision = o.mYPrecision;
+        final int N = o.mNumHistory;
+        ev.mNumHistory = N;
+        if (N > 0) {
+            // could be more efficient about this...
+            ev.mHistory = (float[])o.mHistory.clone();
+            ev.mHistoryTimes = (long[])o.mHistoryTimes.clone();
+        }
+        return ev;
+    }
+    
+    /**
+     * Recycle the MotionEvent, to be re-used by a later caller.  After calling
+     * this function you must not ever touch the event again.
+     */
+    public void recycle() {
+        // Ensure recycle is only called once!
+        if (TRACK_RECYCLED_LOCATION) {
+            if (mRecycledLocation != null) {
+                throw new RuntimeException(toString() + " recycled twice!", mRecycledLocation);
+            }
+            mRecycledLocation = new RuntimeException("Last recycled here");
+        } else if (mRecycled) {
+            throw new RuntimeException(toString() + " recycled twice!");
+        }
+
+        //Log.w("MotionEvent", "Recycling event " + this, mRecycledLocation);
+        synchronized (gRecyclerLock) {
+            if (gRecyclerUsed < MAX_RECYCLED) {
+                gRecyclerUsed++;
+                mNumHistory = 0;
+                mNext = gRecyclerTop;
+                gRecyclerTop = this;
+            }
+        }
+    }
+    
+    /**
+     * Return the kind of action being performed -- one of either
+     * {@link #ACTION_DOWN}, {@link #ACTION_MOVE}, {@link #ACTION_UP}, or
+     * {@link #ACTION_CANCEL}.
+     */
+    public final int getAction() {
+        return mAction;
+    }
+
+    /**
+     * Returns the time (in ms) when the user originally pressed down to start 
+     * a stream of position events. 
+     */
+    public final long getDownTime() {
+        return mDownTime;
+    }
+
+    /**
+     * Returns the time (in ms) when this specific event was generated.
+     */
+    public final long getEventTime() {
+        return mEventTime;
+    }
+
+    /**
+     * Returns the X coordinate of this event.  Whole numbers are pixels; the 
+     * value may have a fraction for input devices that are sub-pixel precise. 
+     */
+    public final float getX() {
+        return mX;
+    }
+
+    /**
+     * Returns the Y coordinate of this event.  Whole numbers are pixels; the 
+     * value may have a fraction for input devices that are sub-pixel precise. 
+     */
+    public final float getY() {
+        return mY;
+    }
+
+    /**
+     * Returns the current pressure of this event.  The pressure generally 
+     * ranges from 0 (no pressure at all) to 1 (normal pressure), however 
+     * values higher than 1 may be generated depending on the calibration of 
+     * the input device.
+     */
+    public final float getPressure() {
+        return mPressure;
+    }
+
+    /**
+     * Returns a scaled value of the approximate size, of the area being pressed when
+     * touched with the finger. The actual value in pixels corresponding to the finger 
+     * touch  is normalized with the device specific range of values
+     * and scaled to a value between 0 and 1. The value of size can be used to 
+     * determine fat touch events.
+     */
+    public final float getSize() {
+        return mSize;
+    }
+
+    /**
+     * Returns the state of any meta / modifier keys that were in effect when
+     * the event was generated.  This is the same values as those
+     * returned by {@link KeyEvent#getMetaState() KeyEvent.getMetaState}.
+     *
+     * @return an integer in which each bit set to 1 represents a pressed
+     *         meta key
+     *
+     * @see KeyEvent#getMetaState()
+     */
+    public final int getMetaState() {
+        return mMetaState;
+    }
+
+    /**
+     * Returns the original raw X coordinate of this event.  For touch
+     * events on the screen, this is the original location of the event
+     * on the screen, before it had been adjusted for the containing window
+     * and views.
+     */
+    public final float getRawX() {
+        return mRawX;
+    }
+
+    /**
+     * Returns the original raw Y coordinate of this event.  For touch
+     * events on the screen, this is the original location of the event
+     * on the screen, before it had been adjusted for the containing window
+     * and views.
+     */
+    public final float getRawY() {
+        return mRawY;
+    }
+
+    /**
+     * Return the precision of the X coordinates being reported.  You can
+     * multiple this number with {@link #getX} to find the actual hardware
+     * value of the X coordinate.
+     * @return Returns the precision of X coordinates being reported.
+     */
+    public final float getXPrecision() {
+        return mXPrecision;
+    }
+    
+    /**
+     * Return the precision of the Y coordinates being reported.  You can
+     * multiple this number with {@link #getY} to find the actual hardware
+     * value of the Y coordinate.
+     * @return Returns the precision of Y coordinates being reported.
+     */
+    public final float getYPrecision() {
+        return mYPrecision;
+    }
+    
+    /**
+     * Returns the number of historical points in this event.  These are
+     * movements that have occurred between this event and the previous event.
+     * This only applies to ACTION_MOVE events -- all other actions will have
+     * a size of 0.
+     * 
+     * @return Returns the number of historical points in the event.
+     */
+    public final int getHistorySize() {
+        return mNumHistory;
+    }
+    
+    /**
+     * Returns the time that a historical movement occurred between this event
+     * and the previous event.  Only applies to ACTION_MOVE events.
+     * 
+     * @param pos Which historical value to return; must be less than
+     * {@link #getHistorySize}
+     * 
+     * @see #getHistorySize
+     * @see #getEventTime
+     */
+    public final long getHistoricalEventTime(int pos) {
+        return mHistoryTimes[pos];
+    }
+    
+    /**
+     * Returns a historical X coordinate that occurred between this event
+     * and the previous event.  Only applies to ACTION_MOVE events.
+     * 
+     * @param pos Which historical value to return; must be less than
+     * {@link #getHistorySize}
+     * 
+     * @see #getHistorySize
+     * @see #getX
+     */
+    public final float getHistoricalX(int pos) {
+        return mHistory[pos*4];
+    }
+    
+    /**
+     * Returns a historical Y coordinate that occurred between this event
+     * and the previous event.  Only applies to ACTION_MOVE events.
+     * 
+     * @param pos Which historical value to return; must be less than
+     * {@link #getHistorySize}
+     * 
+     * @see #getHistorySize
+     * @see #getY
+     */
+    public final float getHistoricalY(int pos) {
+        return mHistory[pos*4 + 1];
+    }
+    
+    /**
+     * Returns a historical pressure coordinate that occurred between this event
+     * and the previous event.  Only applies to ACTION_MOVE events.
+     * 
+     * @param pos Which historical value to return; must be less than
+     * {@link #getHistorySize}
+     * 
+     * @see #getHistorySize
+     * @see #getPressure
+     */
+    public final float getHistoricalPressure(int pos) {
+        return mHistory[pos*4 + 2];
+    }
+    
+    /**
+     * Returns a historical size coordinate that occurred between this event
+     * and the previous event.  Only applies to ACTION_MOVE events.
+     * 
+     * @param pos Which historical value to return; must be less than
+     * {@link #getHistorySize}
+     * 
+     * @see #getHistorySize
+     * @see #getSize
+     */
+    public final float getHistoricalSize(int pos) {
+        return mHistory[pos*4 + 3];
+    }
+    
+    /**
+     * Return the id for the device that this event came from.  An id of
+     * zero indicates that the event didn't come from a physical device; other
+     * numbers are arbitrary and you shouldn't depend on the values.
+     */
+    public final int getDeviceId() {
+        return mDeviceId;
+    }
+    
+    /**
+     * Returns a bitfield indicating which edges, if any, where touched by this
+     * MotionEvent. For touch events, clients can use this to determine if the 
+     * user's finger was touching the edge of the display. 
+     * 
+     * @see #EDGE_LEFT
+     * @see #EDGE_TOP
+     * @see #EDGE_RIGHT
+     * @see #EDGE_BOTTOM
+     */
+    public final int getEdgeFlags() {
+        return mEdgeFlags;
+    }
+    
+
+    /**
+     * Sets the bitfield indicating which edges, if any, where touched by this
+     * MotionEvent. 
+     * 
+     * @see #getEdgeFlags()
+     */
+    public final void setEdgeFlags(int flags) {
+        mEdgeFlags = flags;
+    }
+
+    /**
+     * Sets this event's action.
+     */
+    public final void setAction(int action) {
+        mAction = action;
+    }
+
+    /**
+     * Adjust this event's location.
+     * @param deltaX Amount to add to the current X coordinate of the event.
+     * @param deltaY Amount to add to the current Y coordinate of the event.
+     */
+    public final void offsetLocation(float deltaX, float deltaY) {
+        mX += deltaX;
+        mY += deltaY;
+        final int N = mNumHistory*4;
+        if (N <= 0) {
+            return;
+        }
+        final float[] pos = mHistory;
+        for (int i=0; i<N; i+=4) {
+            pos[i] += deltaX;
+            pos[i+1] += deltaY;
+        }
+    }
+ 
+    /**
+     * Set this event's location.  Applies {@link #offsetLocation} with a
+     * delta from the current location to the given new location.
+     * 
+     * @param x New absolute X location.
+     * @param y New absolute Y location.
+     */
+    public final void setLocation(float x, float y) {
+        float deltaX = x-mX;
+        float deltaY = y-mY;
+        if (deltaX != 0 || deltaY != 0) {
+            offsetLocation(deltaX, deltaY);
+        }
+    }
+    
+    /**
+     * Add a new movement to the batch of movements in this event.  The event's
+     * current location, position and size is updated to the new values.  In
+     * the future, the current values in the event will be added to a list of
+     * historic values.
+     * 
+     * @param x The new X position.
+     * @param y The new Y position.
+     * @param pressure The new pressure.
+     * @param size The new size.
+     */
+    public final void addBatch(long eventTime, float x, float y,
+            float pressure, float size, int metaState) {
+        float[] history = mHistory;
+        long[] historyTimes = mHistoryTimes;
+        int N;
+        int avail;
+        if (history == null) {
+            mHistory = history = new float[8*4];
+            mHistoryTimes = historyTimes = new long[8];
+            mNumHistory = N = 0;
+            avail = 8;
+        } else {
+            N = mNumHistory;
+            avail = history.length/4;
+            if (N == avail) {
+                avail += 8;
+                float[] newHistory = new float[avail*4];
+                System.arraycopy(history, 0, newHistory, 0, N*4);
+                mHistory = history = newHistory;
+                long[] newHistoryTimes = new long[avail];
+                System.arraycopy(historyTimes, 0, newHistoryTimes, 0, N);
+                mHistoryTimes = historyTimes = newHistoryTimes;
+            }
+        }
+        
+        historyTimes[N] = mEventTime;
+        
+        final int pos = N*4;
+        history[pos] = mX;
+        history[pos+1] = mY;
+        history[pos+2] = mPressure;
+        history[pos+3] = mSize;
+        mNumHistory = N+1;
+        
+        mEventTime = eventTime;
+        mX = mRawX = x;
+        mY = mRawY = y;
+        mPressure = pressure;
+        mSize = size;
+        mMetaState |= metaState;
+    }
+    
+    @Override
+    public String toString() {
+        return "MotionEvent{" + Integer.toHexString(System.identityHashCode(this))
+            + " action=" + mAction + " x=" + mX
+            + " y=" + mY + " pressure=" + mPressure + " size=" + mSize + "}";
+    }
+
+    public static final Parcelable.Creator<MotionEvent> CREATOR
+            = new Parcelable.Creator<MotionEvent>() {
+        public MotionEvent createFromParcel(Parcel in) {
+            MotionEvent ev = obtain();
+            ev.readFromParcel(in);
+            return ev;
+        }
+
+        public MotionEvent[] newArray(int size) {
+            return new MotionEvent[size];
+        }
+    };
+
+    public int describeContents() {
+        return 0;
+    }
+
+    public void writeToParcel(Parcel out, int flags) {
+        out.writeLong(mDownTime);
+        out.writeLong(mEventTime);
+        out.writeInt(mAction);
+        out.writeFloat(mX);
+        out.writeFloat(mY);
+        out.writeFloat(mPressure);
+        out.writeFloat(mSize);
+        out.writeInt(mMetaState);
+        out.writeFloat(mRawX);
+        out.writeFloat(mRawY);
+        final int N = mNumHistory;
+        out.writeInt(N);
+        if (N > 0) {
+            final int N4 = N*4;
+            int i;
+            float[] history = mHistory;
+            for (i=0; i<N4; i++) {
+                out.writeFloat(history[i]);
+            }
+            long[] times = mHistoryTimes;
+            for (i=0; i<N; i++) {
+                out.writeLong(times[i]);
+            }
+        }
+        out.writeFloat(mXPrecision);
+        out.writeFloat(mYPrecision);
+        out.writeInt(mDeviceId);
+        out.writeInt(mEdgeFlags);
+    }
+
+    private void readFromParcel(Parcel in) {
+        mDownTime = in.readLong();
+        mEventTime = in.readLong();
+        mAction = in.readInt();
+        mX = in.readFloat();
+        mY = in.readFloat();
+        mPressure = in.readFloat();
+        mSize = in.readFloat();
+        mMetaState = in.readInt();
+        mRawX = in.readFloat();
+        mRawY = in.readFloat();
+        final int N = in.readInt();
+        if ((mNumHistory=N) > 0) {
+            final int N4 = N*4;
+            float[] history = mHistory;
+            if (history == null || history.length < N4) {
+                mHistory = history = new float[N4 + (4*4)];
+            }
+            for (int i=0; i<N4; i++) {
+                history[i] = in.readFloat();
+            }
+            long[] times = mHistoryTimes;
+            if (times == null || times.length < N) {
+                mHistoryTimes = times = new long[N + 4];
+            }
+            for (int i=0; i<N; i++) {
+                times[i] = in.readLong();
+            }
+        }
+        mXPrecision = in.readFloat();
+        mYPrecision = in.readFloat();
+        mDeviceId = in.readInt();
+        mEdgeFlags = in.readInt();
+    }
+
+}
+
diff --git a/core/java/android/view/OrientationEventListener.java b/core/java/android/view/OrientationEventListener.java
new file mode 100755
index 0000000..391ba1e
--- /dev/null
+++ b/core/java/android/view/OrientationEventListener.java
@@ -0,0 +1,174 @@
+/*
+ * 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 android.view;
+
+import android.content.Context;
+import android.hardware.Sensor;
+import android.hardware.SensorEvent;
+import android.hardware.SensorEventListener;
+import android.hardware.SensorManager;
+import android.util.Config;
+import android.util.Log;
+
+/**
+ * Helper class for receiving notifications from the SensorManager when
+ * the orientation of the device has changed.
+ */
+public abstract class OrientationEventListener {
+    private static final String TAG = "OrientationEventListener";
+    private static final boolean DEBUG = false;
+    private static final boolean localLOGV = DEBUG ? Config.LOGD : Config.LOGV;
+    private int mOrientation = ORIENTATION_UNKNOWN;
+    private SensorManager mSensorManager;
+    private boolean mEnabled = false;
+    private int mRate;
+    private Sensor mSensor;
+    private SensorEventListener mSensorEventListener;
+    private OrientationListener mOldListener;
+    
+    /**
+     * Returned from onOrientationChanged when the device orientation cannot be determined
+     * (typically when the device is in a close to flat position).
+     *
+     *  @see #onOrientationChanged
+     */
+    public static final int ORIENTATION_UNKNOWN = -1;
+
+    /**
+     * Creates a new OrientationEventListener.
+     * 
+     * @param context for the OrientationEventListener.
+     */
+    public OrientationEventListener(Context context) {
+        this(context, SensorManager.SENSOR_DELAY_NORMAL);
+    }
+    
+    /**
+     * Creates a new OrientationEventListener.
+     * 
+     * @param context for the OrientationEventListener.
+     * @param rate at which sensor events are processed (see also
+     * {@link android.hardware.SensorManager SensorManager}). Use the default
+     * value of {@link android.hardware.SensorManager#SENSOR_DELAY_NORMAL 
+     * SENSOR_DELAY_NORMAL} for simple screen orientation change detection.
+     */
+    public OrientationEventListener(Context context, int rate) {
+        mSensorManager = (SensorManager)context.getSystemService(Context.SENSOR_SERVICE);
+        mRate = rate;
+        mSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
+        if (mSensor != null) {
+            // Create listener only if sensors do exist
+            mSensorEventListener = new SensorEventListenerImpl();
+        }
+    }
+    
+    void registerListener(OrientationListener lis) {
+        mOldListener = lis;
+    }
+
+    /**
+     * Enables the OrientationEventListener so it will monitor the sensor and call
+     * {@link #onOrientationChanged} when the device orientation changes.
+     */
+    public void enable() {
+        if (mSensor == null) {
+            Log.w(TAG, "Cannot detect sensors. Not enabled");
+            return;
+        }
+        if (mEnabled == false) {
+            if (localLOGV) Log.d(TAG, "OrientationEventListener enabled");
+            mSensorManager.registerListener(mSensorEventListener, mSensor, mRate);
+            mEnabled = true;
+        }
+    }
+
+    /**
+     * Disables the OrientationEventListener.
+     */
+    public void disable() {
+        if (mSensor == null) {
+            Log.w(TAG, "Cannot detect sensors. Invalid disable");
+            return;
+        }
+        if (mEnabled == true) {
+            if (localLOGV) Log.d(TAG, "OrientationEventListener disabled");
+            mSensorManager.unregisterListener(mSensorEventListener);
+            mEnabled = false;
+        }
+    }
+
+    class SensorEventListenerImpl implements SensorEventListener {
+        private static final int _DATA_X = 0;
+        private static final int _DATA_Y = 1;
+        private static final int _DATA_Z = 2;
+        
+        public void onSensorChanged(SensorEvent event) {
+            float[] values = event.values;
+            int orientation = ORIENTATION_UNKNOWN;
+            float X = -values[_DATA_X];
+            float Y = -values[_DATA_Y];
+            float Z = -values[_DATA_Z];        
+            float magnitude = X*X + Y*Y;
+            // Don't trust the angle if the magnitude is small compared to the y value
+            if (magnitude * 4 >= Z*Z) {
+                float OneEightyOverPi = 57.29577957855f;
+                float angle = (float)Math.atan2(-Y, X) * OneEightyOverPi;
+                orientation = 90 - (int)Math.round(angle);
+                // normalize to 0 - 359 range
+                while (orientation >= 360) {
+                    orientation -= 360;
+                } 
+                while (orientation < 0) {
+                    orientation += 360;
+                }
+            }
+            if (mOldListener != null) {
+                mOldListener.onSensorChanged(Sensor.TYPE_ACCELEROMETER, event.values);
+            }
+            if (orientation != mOrientation) {
+                mOrientation = orientation;
+                onOrientationChanged(orientation);
+            }
+        }
+
+        public void onAccuracyChanged(Sensor sensor, int accuracy) {
+
+        }
+    }
+    
+    /*
+     * Returns true if sensor is enabled and false otherwise
+     */
+    public boolean canDetectOrientation() {
+        return mSensor != null;
+    }
+
+    /**
+     * Called when the orientation of the device has changed.
+     * orientation parameter is in degrees, ranging from 0 to 359.
+     * orientation is 0 degrees when the device is oriented in its natural position,
+     * 90 degrees when its left side is at the top, 180 degrees when it is upside down, 
+     * and 270 degrees when its right side is to the top.
+     * {@link #ORIENTATION_UNKNOWN} is returned when the device is close to flat
+     * and the orientation cannot be determined.
+     *
+     * @param orientation The new orientation of the device.
+     *
+     *  @see #ORIENTATION_UNKNOWN
+     */
+    abstract public void onOrientationChanged(int orientation);
+}
diff --git a/core/java/android/view/OrientationListener.java b/core/java/android/view/OrientationListener.java
new file mode 100644
index 0000000..ce8074e
--- /dev/null
+++ b/core/java/android/view/OrientationListener.java
@@ -0,0 +1,110 @@
+/*
+ * 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 android.view;
+
+import android.content.Context;
+import android.hardware.SensorListener;
+
+/**
+ * Helper class for receiving notifications from the SensorManager when
+ * the orientation of the device has changed.
+ *  @deprecated use {@link android.view.OrientationEventListener} instead.
+ *  This class internally uses the OrientationEventListener.
+ */
+@Deprecated
+public abstract class OrientationListener implements SensorListener {
+    private OrientationEventListener mOrientationEventLis;
+    
+    /**
+     * Returned from onOrientationChanged when the device orientation cannot be determined
+     * (typically when the device is in a close to flat position).
+     *
+     *  @see #onOrientationChanged
+     */
+    public static final int ORIENTATION_UNKNOWN = OrientationEventListener.ORIENTATION_UNKNOWN;
+
+    /**
+     * Creates a new OrientationListener.
+     * 
+     * @param context for the OrientationListener.
+     */
+    public OrientationListener(Context context) {
+        mOrientationEventLis = new OrientationEventListenerInternal(context);
+    }
+
+    /**
+     * Creates a new OrientationListener.
+     * 
+     * @param context for the OrientationListener.
+     * @param rate at which sensor events are processed (see also
+     * {@link android.hardware.SensorManager SensorManager}). Use the default
+     * value of {@link android.hardware.SensorManager#SENSOR_DELAY_NORMAL 
+     * SENSOR_DELAY_NORMAL} for simple screen orientation change detection.
+     */
+    public OrientationListener(Context context, int rate) {
+        mOrientationEventLis = new OrientationEventListenerInternal(context, rate);
+    }
+    
+    class OrientationEventListenerInternal extends OrientationEventListener {
+        OrientationEventListenerInternal(Context context) {
+            super(context);
+        }
+        
+        OrientationEventListenerInternal(Context context, int rate) {
+            super(context, rate);
+            // register so that onSensorChanged gets invoked
+            registerListener(OrientationListener.this);
+        }
+                
+        public void onOrientationChanged(int orientation) {
+            OrientationListener.this.onOrientationChanged(orientation);
+        }
+    }
+    
+    /**
+     * Enables the OrientationListener so it will monitor the sensor and call
+     * {@link #onOrientationChanged} when the device orientation changes.
+     */
+    public void enable() {
+        mOrientationEventLis.enable();
+    }
+
+    /**
+     * Disables the OrientationListener.
+     */
+    public void disable() {
+        mOrientationEventLis.disable();
+    }
+    
+    public void onAccuracyChanged(int sensor, int accuracy) {
+    }
+    
+    public void onSensorChanged(int sensor, float[] values) {
+        // just ignore the call here onOrientationChanged is invoked anyway
+    }
+
+
+    /**
+     * Look at {@link android.view.OrientationEventListener#onOrientationChanged}
+     * for method description and usage
+     * @param orientation The new orientation of the device.
+     *
+     *  @see #ORIENTATION_UNKNOWN
+     */
+    abstract public void onOrientationChanged(int orientation);
+    
+}
diff --git a/core/java/android/view/RawInputEvent.java b/core/java/android/view/RawInputEvent.java
new file mode 100644
index 0000000..30da83e
--- /dev/null
+++ b/core/java/android/view/RawInputEvent.java
@@ -0,0 +1,170 @@
+/**
+ * 
+ */
+package android.view;
+
+/**
+ * @hide
+ * This really belongs in services.jar; WindowManagerPolicy should go there too.
+ */
+public class RawInputEvent {
+    // Event class as defined by EventHub.
+    public static final int CLASS_KEYBOARD = 0x00000001;
+    public static final int CLASS_ALPHAKEY = 0x00000002;
+    public static final int CLASS_TOUCHSCREEN = 0x00000004;
+    public static final int CLASS_TRACKBALL = 0x00000008;
+    
+    // More special classes for QueuedEvent below.
+    public static final int CLASS_CONFIGURATION_CHANGED = 0x10000000;
+    
+    // Event types.
+
+    public static final int EV_SYN = 0x00;
+    public static final int EV_KEY = 0x01;
+    public static final int EV_REL = 0x02;
+    public static final int EV_ABS = 0x03;
+    public static final int EV_MSC = 0x04;
+    public static final int EV_SW = 0x05;
+    public static final int EV_LED = 0x11;
+    public static final int EV_SND = 0x12;
+    public static final int EV_REP = 0x14;
+    public static final int EV_FF = 0x15;
+    public static final int EV_PWR = 0x16;
+    public static final int EV_FF_STATUS = 0x17;
+
+    // Platform-specific event types.
+    
+    public static final int EV_DEVICE_ADDED = 0x10000000;
+    public static final int EV_DEVICE_REMOVED = 0x20000000;
+    
+    // Special key (EV_KEY) scan codes for pointer buttons.
+
+    public static final int BTN_FIRST = 0x100;
+
+    public static final int BTN_MISC = 0x100;
+    public static final int BTN_0 = 0x100;
+    public static final int BTN_1 = 0x101;
+    public static final int BTN_2 = 0x102;
+    public static final int BTN_3 = 0x103;
+    public static final int BTN_4 = 0x104;
+    public static final int BTN_5 = 0x105;
+    public static final int BTN_6 = 0x106;
+    public static final int BTN_7 = 0x107;
+    public static final int BTN_8 = 0x108;
+    public static final int BTN_9 = 0x109;
+
+    public static final int BTN_MOUSE = 0x110;
+    public static final int BTN_LEFT = 0x110;
+    public static final int BTN_RIGHT = 0x111;
+    public static final int BTN_MIDDLE = 0x112;
+    public static final int BTN_SIDE = 0x113;
+    public static final int BTN_EXTRA = 0x114;
+    public static final int BTN_FORWARD = 0x115;
+    public static final int BTN_BACK = 0x116;
+    public static final int BTN_TASK = 0x117;
+
+    public static final int BTN_JOYSTICK = 0x120;
+    public static final int BTN_TRIGGER = 0x120;
+    public static final int BTN_THUMB = 0x121;
+    public static final int BTN_THUMB2 = 0x122;
+    public static final int BTN_TOP = 0x123;
+    public static final int BTN_TOP2 = 0x124;
+    public static final int BTN_PINKIE = 0x125;
+    public static final int BTN_BASE = 0x126;
+    public static final int BTN_BASE2 = 0x127;
+    public static final int BTN_BASE3 = 0x128;
+    public static final int BTN_BASE4 = 0x129;
+    public static final int BTN_BASE5 = 0x12a;
+    public static final int BTN_BASE6 = 0x12b;
+    public static final int BTN_DEAD = 0x12f;
+
+    public static final int BTN_GAMEPAD = 0x130;
+    public static final int BTN_A = 0x130;
+    public static final int BTN_B = 0x131;
+    public static final int BTN_C = 0x132;
+    public static final int BTN_X = 0x133;
+    public static final int BTN_Y = 0x134;
+    public static final int BTN_Z = 0x135;
+    public static final int BTN_TL = 0x136;
+    public static final int BTN_TR = 0x137;
+    public static final int BTN_TL2 = 0x138;
+    public static final int BTN_TR2 = 0x139;
+    public static final int BTN_SELECT = 0x13a;
+    public static final int BTN_START = 0x13b;
+    public static final int BTN_MODE = 0x13c;
+    public static final int BTN_THUMBL = 0x13d;
+    public static final int BTN_THUMBR = 0x13e;
+
+    public static final int BTN_DIGI = 0x140;
+    public static final int BTN_TOOL_PEN = 0x140;
+    public static final int BTN_TOOL_RUBBER = 0x141;
+    public static final int BTN_TOOL_BRUSH = 0x142;
+    public static final int BTN_TOOL_PENCIL = 0x143;
+    public static final int BTN_TOOL_AIRBRUSH = 0x144;
+    public static final int BTN_TOOL_FINGER = 0x145;
+    public static final int BTN_TOOL_MOUSE = 0x146;
+    public static final int BTN_TOOL_LENS = 0x147;
+    public static final int BTN_TOUCH = 0x14a;
+    public static final int BTN_STYLUS = 0x14b;
+    public static final int BTN_STYLUS2 = 0x14c;
+    public static final int BTN_TOOL_DOUBLETAP = 0x14d;
+    public static final int BTN_TOOL_TRIPLETAP = 0x14e;
+
+    public static final int BTN_WHEEL = 0x150;
+    public static final int BTN_GEAR_DOWN = 0x150;
+    public static final int BTN_GEAR_UP = 0x151;
+
+    public static final int BTN_LAST = 0x15f;
+
+    // Relative axes (EV_REL) scan codes.
+
+    public static final int REL_X = 0x00;
+    public static final int REL_Y = 0x01;
+    public static final int REL_Z = 0x02;
+    public static final int REL_RX = 0x03;
+    public static final int REL_RY = 0x04;
+    public static final int REL_RZ = 0x05;
+    public static final int REL_HWHEEL = 0x06;
+    public static final int REL_DIAL = 0x07;
+    public static final int REL_WHEEL = 0x08;
+    public static final int REL_MISC = 0x09;
+    public static final int REL_MAX = 0x0f;
+
+    // Absolute axes (EV_ABS) scan codes.
+
+    public static final int ABS_X = 0x00;
+    public static final int ABS_Y = 0x01;
+    public static final int ABS_Z = 0x02;
+    public static final int ABS_RX = 0x03;
+    public static final int ABS_RY = 0x04;
+    public static final int ABS_RZ = 0x05;
+    public static final int ABS_THROTTLE = 0x06;
+    public static final int ABS_RUDDER = 0x07;
+    public static final int ABS_WHEEL = 0x08;
+    public static final int ABS_GAS = 0x09;
+    public static final int ABS_BRAKE = 0x0a;
+    public static final int ABS_HAT0X = 0x10;
+    public static final int ABS_HAT0Y = 0x11;
+    public static final int ABS_HAT1X = 0x12;
+    public static final int ABS_HAT1Y = 0x13;
+    public static final int ABS_HAT2X = 0x14;
+    public static final int ABS_HAT2Y = 0x15;
+    public static final int ABS_HAT3X = 0x16;
+    public static final int ABS_HAT3Y = 0x17;
+    public static final int ABS_PRESSURE = 0x18;
+    public static final int ABS_DISTANCE = 0x19;
+    public static final int ABS_TILT_X = 0x1a;
+    public static final int ABS_TILT_Y = 0x1b;
+    public static final int ABS_TOOL_WIDTH = 0x1c;
+    public static final int ABS_VOLUME = 0x20;
+    public static final int ABS_MISC = 0x28;
+    public static final int ABS_MAX = 0x3f;
+
+    public int deviceId;
+    public int type;
+    public int scancode;
+    public int keycode;
+    public int flags;
+    public int value;
+    public long when;
+}
diff --git a/core/java/android/view/RemotableViewMethod.java b/core/java/android/view/RemotableViewMethod.java
new file mode 100644
index 0000000..4318290
--- /dev/null
+++ b/core/java/android/view/RemotableViewMethod.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2007 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 android.view;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * @hide
+ * This annotation indicates that a method on a subclass of View
+ * is alllowed to be used with the {@link android.widget.RemoteViews} mechanism.
+ */
+@Target({ ElementType.METHOD })
+@Retention(RetentionPolicy.RUNTIME)
+public @interface RemotableViewMethod {
+}
+
+
+
diff --git a/core/java/android/view/SoundEffectConstants.java b/core/java/android/view/SoundEffectConstants.java
new file mode 100644
index 0000000..4a77af4
--- /dev/null
+++ b/core/java/android/view/SoundEffectConstants.java
@@ -0,0 +1,57 @@
+/*
+ * 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 android.view;
+
+/**
+ * Constants to be used to play sound effects via {@link View#playSoundEffect(int)} 
+ */
+public class SoundEffectConstants {
+
+    private SoundEffectConstants() {}
+
+    public static final int CLICK = 0;
+
+    public static final int NAVIGATION_LEFT = 1;
+    public static final int NAVIGATION_UP = 2;
+    public static final int NAVIGATION_RIGHT = 3;
+    public static final int NAVIGATION_DOWN = 4;
+
+    /**
+     * Get the sonification constant for the focus directions.
+     * @param direction One of {@link View#FOCUS_UP}, {@link View#FOCUS_DOWN},
+     *     {@link View#FOCUS_LEFT}, {@link View#FOCUS_RIGHT}, {@link View#FOCUS_FORWARD}
+     *     or {@link View#FOCUS_BACKWARD}
+
+     * @return The appropriate sonification constant.
+     */
+    public static int getContantForFocusDirection(int direction) {
+        switch (direction) {
+            case View.FOCUS_RIGHT:
+                return SoundEffectConstants.NAVIGATION_RIGHT;
+            case View.FOCUS_FORWARD:
+            case View.FOCUS_DOWN:
+                return SoundEffectConstants.NAVIGATION_DOWN;
+            case View.FOCUS_LEFT:
+                return SoundEffectConstants.NAVIGATION_LEFT;
+            case View.FOCUS_BACKWARD:
+            case View.FOCUS_UP:
+                return SoundEffectConstants.NAVIGATION_UP;
+        }
+        throw new IllegalArgumentException("direction must be one of "
+                + "{FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, FOCUS_RIGHT, FOCUS_FORWARD, FOCUS_BACKWARD}.");
+    }
+}
diff --git a/core/java/android/view/SubMenu.java b/core/java/android/view/SubMenu.java
new file mode 100644
index 0000000..e981486
--- /dev/null
+++ b/core/java/android/view/SubMenu.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2007 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 android.view;
+
+import android.graphics.drawable.Drawable;
+
+/**
+ * Subclass of {@link Menu} for sub menus.
+ * <p>
+ * Sub menus do not support item icons, or nested sub menus.
+ */
+
+public interface SubMenu extends Menu {
+    /**
+     * Sets the submenu header's title to the title given in <var>titleRes</var>
+     * resource identifier.
+     * 
+     * @param titleRes The string resource identifier used for the title.
+     * @return This SubMenu so additional setters can be called.
+     */
+    public SubMenu setHeaderTitle(int titleRes);
+
+    /**
+     * Sets the submenu header's title to the title given in <var>title</var>.
+     * 
+     * @param title The character sequence used for the title.
+     * @return This SubMenu so additional setters can be called.
+     */
+    public SubMenu setHeaderTitle(CharSequence title);
+    
+    /**
+     * Sets the submenu header's icon to the icon given in <var>iconRes</var>
+     * resource id.
+     * 
+     * @param iconRes The resource identifier used for the icon.
+     * @return This SubMenu so additional setters can be called.
+     */
+    public SubMenu setHeaderIcon(int iconRes);
+
+    /**
+     * Sets the submenu header's icon to the icon given in <var>icon</var>
+     * {@link Drawable}.
+     * 
+     * @param icon The {@link Drawable} used for the icon.
+     * @return This SubMenu so additional setters can be called.
+     */
+    public SubMenu setHeaderIcon(Drawable icon);
+    
+    /**
+     * Sets the header of the submenu to the {@link View} given in
+     * <var>view</var>. This replaces the header title and icon (and those
+     * replace this).
+     * 
+     * @param view The {@link View} used for the header.
+     * @return This SubMenu so additional setters can be called.
+     */
+    public SubMenu setHeaderView(View view);
+    
+    /**
+     * Clears the header of the submenu.
+     */
+    public void clearHeader();
+    
+    /**
+     * Change the icon associated with this submenu's item in its parent menu.
+     * 
+     * @see MenuItem#setIcon(int)
+     * @param iconRes The new icon (as a resource ID) to be displayed.
+     * @return This SubMenu so additional setters can be called.
+     */
+    public SubMenu setIcon(int iconRes);
+    
+    /**
+     * Change the icon associated with this submenu's item in its parent menu.
+     * 
+     * @see MenuItem#setIcon(Drawable)
+     * @param icon The new icon (as a Drawable) to be displayed.
+     * @return This SubMenu so additional setters can be called.
+     */
+    public SubMenu setIcon(Drawable icon);
+    
+    /**
+     * Gets the {@link MenuItem} that represents this submenu in the parent
+     * menu.  Use this for setting additional item attributes.
+     * 
+     * @return The {@link MenuItem} that launches the submenu when invoked.
+     */
+    public MenuItem getItem();
+}
diff --git a/core/java/android/view/Surface.aidl b/core/java/android/view/Surface.aidl
new file mode 100644
index 0000000..90bf37a
--- /dev/null
+++ b/core/java/android/view/Surface.aidl
@@ -0,0 +1,20 @@
+/* //device/java/android/android/view/Surface.aidl
+**
+** Copyright 2007, 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 android.view;
+
+parcelable Surface;
diff --git a/core/java/android/view/Surface.java b/core/java/android/view/Surface.java
new file mode 100644
index 0000000..54ccf33
--- /dev/null
+++ b/core/java/android/view/Surface.java
@@ -0,0 +1,298 @@
+/*
+ * Copyright (C) 2007 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 android.view;
+
+import android.graphics.*;
+import android.os.Parcelable;
+import android.os.Parcel;
+import android.util.Log;
+
+/**
+ * Handle on to a raw buffer that is being managed by the screen compositor.
+ */
+public class Surface implements Parcelable {
+    private static final String LOG_TAG = "Surface";
+    
+    /* flags used in constructor (keep in sync with ISurfaceComposer.h) */
+
+    /** Surface is created hidden */
+    public static final int HIDDEN              = 0x00000004;
+
+    /** The surface is to be used by hardware accelerators or DMA engines */
+    public static final int HARDWARE            = 0x00000010;
+
+    /** Implies "HARDWARE", the surface is to be used by the GPU
+     * additionally the backbuffer is never preserved for these
+     * surfaces. */
+    public static final int GPU                 = 0x00000028;
+
+    /** The surface contains secure content, special measures will
+     * be taken to disallow the surface's content to be copied from
+     * another process. In particular, screenshots and VNC servers will
+     * be disabled, but other measures can take place, for instance the
+     * surface might not be hardware accelerated. */
+    public static final int SECURE              = 0x00000080;
+    
+    /** Creates a surface where color components are interpreted as 
+     *  "non pre-multiplied" by their alpha channel. Of course this flag is
+     *  meaningless for surfaces without an alpha channel. By default
+     *  surfaces are pre-multiplied, which means that each color component is
+     *  already multiplied by its alpha value. In this case the blending
+     *  equation used is:
+     *  
+     *    DEST = SRC + DEST * (1-SRC_ALPHA)
+     *    
+     *  By contrast, non pre-multiplied surfaces use the following equation:
+     *  
+     *    DEST = SRC * SRC_ALPHA * DEST * (1-SRC_ALPHA)
+     *    
+     *  pre-multiplied surfaces must always be used if transparent pixels are
+     *  composited on top of each-other into the surface. A pre-multiplied
+     *  surface can never lower the value of the alpha component of a given
+     *  pixel.
+     *  
+     *  In some rare situations, a non pre-multiplied surface is preferable.
+     *  
+     */
+    public static final int NON_PREMULTIPLIED   = 0x00000100;
+    
+    /**
+     * Creates a surface without a rendering buffer. Instead, the content
+     * of the surface must be pushed by an external entity. This is type
+     * of surface can be used for efficient camera preview or movie
+     * play back.
+     */
+    public static final int PUSH_BUFFERS        = 0x00000200;
+    
+    /** Creates a normal surface. This is the default */
+    public static final int FX_SURFACE_NORMAL   = 0x00000000;
+    
+    /** Creates a Blur surface. Everything behind this surface is blurred
+     * by some amount. The quality and refresh speed of the blur effect
+     * is not settable or guaranteed.
+     * It is an error to lock a Blur surface, since it doesn't have
+     * a backing store.
+     */
+    public static final int FX_SURFACE_BLUR     = 0x00010000;
+    
+    /** Creates a Dim surface. Everything behind this surface is dimmed
+     * by the amount specified in setAlpha(). 
+     * It is an error to lock a Dim surface, since it doesn't have
+     * a backing store.
+     */
+    public static final int FX_SURFACE_DIM     = 0x00020000;
+
+    /** Mask used for FX values above */
+    public static final int FX_SURFACE_MASK     = 0x000F0000;
+
+    /* flags used with setFlags() (keep in sync with ISurfaceComposer.h) */
+    
+    /** Hide the surface. Equivalent to calling hide() */
+    public static final int SURFACE_HIDDEN    = 0x01;
+    
+    /** Freeze the surface. Equivalent to calling freeze() */ 
+    public static final int SURACE_FROZEN     = 0x02;
+    
+    /** Enable dithering when compositing this surface */
+    public static final int SURFACE_DITHER    = 0x04;
+
+    public static final int SURFACE_BLUR_FREEZE= 0x10;
+
+    /* orientations for setOrientation() */
+    public static final int ROTATION_0       = 0;
+    public static final int ROTATION_90      = 1;
+    public static final int ROTATION_180     = 2;
+    public static final int ROTATION_270     = 3;
+    
+    @SuppressWarnings("unused")
+    private int mSurface;
+    @SuppressWarnings("unused")
+    private int mSaveCount;
+    @SuppressWarnings("unused")
+    private Canvas mCanvas;
+
+    /**
+     * Exception thrown when a surface couldn't be created or resized
+     */
+    public static class OutOfResourcesException extends Exception {
+        public OutOfResourcesException() {
+        }
+        public OutOfResourcesException(String name) {
+            super(name);
+        }
+    }
+
+    /*
+     * We use a class initializer to allow the native code to cache some
+     * field offsets.
+     */
+    native private static void nativeClassInit();
+    static { nativeClassInit(); }
+
+    
+    /**
+     * create a surface
+     * {@hide}
+     */
+    public Surface(SurfaceSession s,
+            int pid, int display, int w, int h, int format, int flags)
+        throws OutOfResourcesException {
+        mCanvas = new Canvas();
+        init(s,pid,display,w,h,format,flags);
+    }
+
+    /**
+     * Create an empty surface, which will later be filled in by
+     * readFromParcel().
+     * {@hide}
+     */
+    public Surface() {
+        mCanvas = new Canvas();
+    }
+    
+    /**
+     * Copy another surface to this one.  This surface now holds a reference
+     * to the same data as the original surface, and is -not- the owner.
+     * {@hide}
+     */
+    public native   void copyFrom(Surface o);
+    
+    /**
+     * Does this object hold a valid surface?  Returns true if it holds
+     * a physical surface, so lockCanvas() will succeed.  Otherwise
+     * returns false.
+     */
+    public native   boolean isValid();
+    
+    /** Call this free the surface up. {@hide} */
+    public native   void clear();
+    
+    /** draw into a surface */
+    public Canvas lockCanvas(Rect dirty) throws OutOfResourcesException {
+        /* the dirty rectangle may be expanded to the surface's size, if
+         * for instance it has been resized or if the bits were lost, since
+         * the last call.
+         */
+        return lockCanvasNative(dirty);
+    }
+
+    private native Canvas lockCanvasNative(Rect dirty);
+
+    /** unlock the surface and asks a page flip */
+    public native   void unlockCanvasAndPost(Canvas canvas);
+
+    /** 
+     * unlock the surface. the screen won't be updated until
+     * post() or postAll() is called
+     */
+    public native   void unlockCanvas(Canvas canvas);
+    
+    /** start/end a transaction {@hide} */
+    public static native   void openTransaction();
+    /** {@hide} */
+    public static native   void closeTransaction();
+
+    /**
+     * Freezes the specified display, No updating of the screen will occur
+     * until unfreezeDisplay() is called. Everything else works as usual though,
+     * in particular transactions.
+     * @param display
+     * {@hide}
+     */
+    public static native   void freezeDisplay(int display);
+
+    /**
+     * resume updating the specified display.
+     * @param display
+     * {@hide}
+     */
+    public static native   void unfreezeDisplay(int display);
+
+    /**
+     * set the orientation of the given display.
+     * @param display
+     * @param orientation
+     */
+    public static native   void setOrientation(int display, int orientation);
+
+    /**
+     * set surface parameters.
+     * needs to be inside open/closeTransaction block
+     */
+    public native   void setLayer(int zorder);
+    public native   void setPosition(int x, int y);
+    public native   void setSize(int w, int h);
+
+    public native   void hide();
+    public native   void show();
+    public native   void setTransparentRegionHint(Region region);
+    public native   void setAlpha(float alpha);
+    public native   void setMatrix(float dsdx, float dtdx,
+                                   float dsdy, float dtdy);
+
+    public native   void freeze();
+    public native   void unfreeze();
+
+    public native   void setFreezeTint(int tint);
+
+    public native   void setFlags(int flags, int mask);
+
+    @Override
+    public String toString() {
+        return "Surface(native-token=" + mSurface + ")";
+    }
+
+    private Surface(Parcel source) throws OutOfResourcesException {
+        init(source);
+    }
+    
+    public int describeContents() {
+        return 0;
+    }
+
+    public native   void readFromParcel(Parcel source);
+    public native   void writeToParcel(Parcel dest, int flags);
+
+    public static final Parcelable.Creator<Surface> CREATOR
+            = new Parcelable.Creator<Surface>()
+    {
+        public Surface createFromParcel(Parcel source) {
+            try {
+                return new Surface(source);
+            } catch (Exception e) {
+                Log.e(LOG_TAG, "Exception creating surface from parcel", e);
+            }
+            return null;
+        }
+
+        public Surface[] newArray(int size) {
+            return new Surface[size];
+        }
+    };
+
+    /* no user serviceable parts here ... */
+    @Override
+    protected void finalize() throws Throwable {
+        clear();
+    }
+    
+    private native void init(SurfaceSession s,
+            int pid, int display, int w, int h, int format, int flags)
+            throws OutOfResourcesException;
+
+    private native void init(Parcel source);
+}
diff --git a/core/java/android/view/SurfaceHolder.java b/core/java/android/view/SurfaceHolder.java
new file mode 100644
index 0000000..3d0dda3
--- /dev/null
+++ b/core/java/android/view/SurfaceHolder.java
@@ -0,0 +1,284 @@
+/*
+ * 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 android.view;
+
+import android.graphics.Canvas;
+import android.graphics.Rect;
+import static android.view.WindowManager.LayoutParams.MEMORY_TYPE_NORMAL;
+import static android.view.WindowManager.LayoutParams.MEMORY_TYPE_HARDWARE;
+import static android.view.WindowManager.LayoutParams.MEMORY_TYPE_GPU;
+import static android.view.WindowManager.LayoutParams.MEMORY_TYPE_PUSH_BUFFERS;
+
+/**
+ * Abstract interface to someone holding a display surface.  Allows you to
+ * control the surface size and format, edit the pixels in the surface, and
+ * monitor changes to the surface.  This interface is typically available
+ * through the {@link SurfaceView} class.
+ * 
+ * <p>When using this interface from a thread different than the one running
+ * its {@link SurfaceView}, you will want to carefully read the
+ * {@link #lockCanvas} and {@link Callback#surfaceCreated Callback.surfaceCreated}.
+ */
+public interface SurfaceHolder {
+    /**
+     * Surface type.
+     * 
+     * @see #SURFACE_TYPE_NORMAL
+     * @see #SURFACE_TYPE_HARDWARE
+     * @see #SURFACE_TYPE_GPU
+     * @see #SURFACE_TYPE_PUSH_BUFFERS
+     */
+    
+    /** Surface type: creates a regular surface, usually in main, non
+     * contiguous, cached/buffered RAM. */
+    public static final int SURFACE_TYPE_NORMAL = MEMORY_TYPE_NORMAL;
+    /** Surface type: creates a suited to be used with DMA engines and
+     * hardware accelerators. */
+    public static final int SURFACE_TYPE_HARDWARE = MEMORY_TYPE_HARDWARE;
+    /** Surface type: creates a surface suited to be used with the GPU */
+    public static final int SURFACE_TYPE_GPU = MEMORY_TYPE_GPU;
+    /** Surface type: creates a "push" surface, that is a surface that 
+     * doesn't owns its buffers. With such a surface lockCanvas will fail. */
+    public static final int SURFACE_TYPE_PUSH_BUFFERS = MEMORY_TYPE_PUSH_BUFFERS;
+
+    /**
+     * Exception that is thrown from {@link #lockCanvas} when called on a Surface
+     * whose is SURFACE_TYPE_PUSH_BUFFERS.
+     */
+    public static class BadSurfaceTypeException extends RuntimeException {
+        public BadSurfaceTypeException() {
+        }
+
+        public BadSurfaceTypeException(String name) {
+            super(name);
+        }
+    }
+
+    /**
+     * A client may implement this interface to receive information about
+     * changes to the surface.  When used with a {@link SurfaceView}, the
+     * Surface being held is only available between calls to
+     * {@link #surfaceCreated(SurfaceHolder)} and
+     * {@link #surfaceDestroyed(SurfaceHolder).  The Callback is set with
+     * {@link SurfaceHolder#addCallback SurfaceHolder.addCallback} method.
+     */
+    public interface Callback {
+        /**
+         * This is called immediately after the surface is first created.
+         * Implementations of this should start up whatever rendering code
+         * they desire.  Note that only one thread can ever draw into
+         * a {@link Surface}, so you should not draw into the Surface here
+         * if your normal rendering will be in another thread.
+         * 
+         * @param holder The SurfaceHolder whose surface is being created.
+         */
+        public void surfaceCreated(SurfaceHolder holder);
+
+        /**
+         * This is called immediately after any structural changes (format or
+         * size) have been made to the surface.  You should at this point update
+         * the imagery in the surface.  This method is always called at least
+         * once, after {@link #surfaceCreated}.
+         * 
+         * @param holder The SurfaceHolder whose surface has changed.
+         * @param format The new PixelFormat of the surface.
+         * @param width The new width of the surface.
+         * @param height The new height of the surface.
+         */
+        public void surfaceChanged(SurfaceHolder holder, int format, int width,
+                int height);
+
+        /**
+         * This is called immediately before a surface is being destroyed. After
+         * returning from this call, you should no longer try to access this
+         * surface.  If you have a rendering thread that directly accesses
+         * the surface, you must ensure that thread is no longer touching the 
+         * Surface before returning from this function.
+         * 
+         * @param holder The SurfaceHolder whose surface is being destroyed.
+         */
+        public void surfaceDestroyed(SurfaceHolder holder);
+    }
+
+    /**
+     * Add a Callback interface for this holder.  There can several Callback
+     * interfaces associated to a holder.
+     * 
+     * @param callback The new Callback interface.
+     */
+    public void addCallback(Callback callback);
+
+    /**
+     * Removes a previously added Callback interface from this holder.
+     * 
+     * @param callback The Callback interface to remove.
+     */
+    public void removeCallback(Callback callback);
+
+    /**
+     * Use this method to find out if the surface is in the process of being
+     * created from Callback methods. This is intended to be used with
+     * {@link Callback#surfaceChanged}.
+     * 
+     * @return true if the surface is in the process of being created.
+     */
+    public boolean isCreating();
+    
+    /**
+     * Sets the surface's type. Surfaces intended to be used with OpenGL ES
+     * should be of SURFACE_TYPE_GPU, surfaces accessed by DMA engines and
+     * hardware accelerators should be of type SURFACE_TYPE_HARDWARE.
+     * Failing to set the surface's type appropriately could result in 
+     * degraded performance or failure. 
+     * 
+     * @param type The surface's memory type.
+     */
+    public void setType(int type);
+
+    /**
+     * Make the surface a fixed size.  It will never change from this size.
+     * When working with a {link SurfaceView}, this must be called from the
+     * same thread running the SurfaceView's window.
+     * 
+     * @param width The surface's width.
+     * @param height The surface's height.
+     */
+    public void setFixedSize(int width, int height);
+
+    /**
+     * Allow the surface to resized based on layout of its container (this is
+     * the default).  When this is enabled, you should monitor
+     * {@link Callback#surfaceChanged} for changes to the size of the surface.
+     * When working with a {link SurfaceView}, this must be called from the
+     * same thread running the SurfaceView's window.
+     */
+    public void setSizeFromLayout();
+
+    /**
+     * Set the desired PixelFormat of the surface.  The default is OPAQUE.
+     * When working with a {link SurfaceView}, this must be called from the
+     * same thread running the SurfaceView's window.
+     * 
+     * @param format A constant from PixelFormat.
+     * 
+     * @see android.graphics.PixelFormat
+     */
+    public void setFormat(int format);
+
+    /**
+     * Enable or disable option to keep the screen turned on while this
+     * surface is displayed.  The default is false, allowing it to turn off.
+     * Enabling the option effectivelty.
+     * This is safe to call from any thread.
+     * 
+     * @param screenOn Supply to true to force the screen to stay on, false
+     * to allow it to turn off.
+     */
+    public void setKeepScreenOn(boolean screenOn);
+    
+    /**
+     * Start editing the pixels in the surface.  The returned Canvas can be used
+     * to draw into the surface's bitmap.  A null is returned if the surface has
+     * not been created or otherwise can not be edited.  You will usually need
+     * to implement {@link Callback#surfaceCreated Callback.surfaceCreated}
+     * to find out when the Surface is available for use.
+     * 
+     * <p>The content of the Surface is never preserved between unlockCanvas() and
+     * lockCanvas(), for this reason, every pixel within the Surface area
+     * must be written. The only exception to this rule is when a dirty
+     * rectangle is specified, in which case, non dirty pixels will be
+     * preserved.
+     * 
+     * <p>If you call this repeatedly when the Surface is not ready (before
+     * {@link Callback#surfaceCreated Callback.surfaceCreated} or after
+     * {@link Callback#surfaceDestroyed Callback.surfaceDestroyed}), your calls
+     * will be throttled to a slow rate in order to avoid consuming CPU.
+     * 
+     * <p>If null is not returned, this function internally holds a lock until
+     * the corresponding {@link #unlockCanvasAndPost} call, preventing
+     * {@link SurfaceView} from creating, destroying, or modifying the surface
+     * while it is being drawn.  This can be more convenience than accessing
+     * the Surface directly, as you do not need to do special synchronization
+     * with a drawing thread in {@link Callback#surfaceDestroyed
+     * Callback.surfaceDestroyed}.
+     * 
+     * @return Canvas Use to draw into the surface.
+     */
+    public Canvas lockCanvas();
+
+    
+    /**
+     * Just like {@link #lockCanvas()} but allows to specify a dirty rectangle.
+     * Every
+     * pixel within that rectangle must be written; however pixels outside
+     * the dirty rectangle will be preserved by the next call to lockCanvas().
+     * 
+     * @see android.view.SurfaceHolder#lockCanvas
+     * 
+     * @param dirty Area of the Surface that will be modified.
+     * @return Canvas Use to draw into the surface.
+     */
+    public Canvas lockCanvas(Rect dirty);
+
+    /**
+     * Finish editing pixels in the surface.  After this call, the surface's
+     * current pixels will be shown on the screen, but its content is lost,
+     * in particular there is no guarantee that the content of the Surface
+     * will remain unchanged when lockCanvas() is called again.
+     * 
+     * @see #lockCanvas()
+     *
+     * @param canvas The Canvas previously returned by lockCanvas().
+     */
+    public void unlockCanvasAndPost(Canvas canvas);
+
+    /**
+     * Retrieve the current size of the surface.  Note: do not modify the
+     * returned Rect.  This is only safe to call from the thread of
+     * {@link SurfaceView}'s window, or while inside of
+     * {@link #lockCanvas()}.
+     * 
+     * @return Rect The surface's dimensions.  The left and top are always 0.
+     */
+    public Rect getSurfaceFrame();
+
+    /**
+     * Direct access to the surface object.  The Surface may not always be
+     * available -- for example when using a {@link SurfaceView} the holder's
+     * Surface is not created until the view has been attached to the window
+     * manager and performed a layout in order to determine the dimensions
+     * and screen position of the Surface.    You will thus usually need
+     * to implement {@link Callback#surfaceCreated Callback.surfaceCreated}
+     * to find out when the Surface is available for use.
+     * 
+     * <p>Note that if you directly access the Surface from another thread,
+     * it is critical that you correctly implement
+     * {@link Callback#surfaceCreated Callback.surfaceCreated} and
+     * {@link Callback#surfaceDestroyed Callback.surfaceDestroyed} to ensure
+     * that thread only accesses the Surface while it is valid, and that the
+     * Surface does not get destroyed while the thread is using it.
+     * 
+     * <p>This method is intended to be used by frameworks which often need
+     * direct access to the Surface object (usually to pass it to native code).
+     * When designing APIs always use SurfaceHolder to pass surfaces around
+     * as opposed to the Surface object itself. A rule of thumb is that
+     * application code should never have to call this method.
+     * 
+     * @return Surface The surface.
+     */
+    public Surface getSurface();
+}
diff --git a/core/java/android/view/SurfaceSession.java b/core/java/android/view/SurfaceSession.java
new file mode 100644
index 0000000..2a04675
--- /dev/null
+++ b/core/java/android/view/SurfaceSession.java
@@ -0,0 +1,49 @@
+/*
+ * 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 android.view;
+
+
+/**
+ * An instance of this class represents a connection to the surface
+ * flinger, in which you can create one or more Surface instances that will
+ * be composited to the screen.
+ * {@hide}
+ */
+public class SurfaceSession {
+    /** Create a new connection with the surface flinger. */
+    public SurfaceSession() {
+        init();
+    }
+
+    /** Forcibly detach native resources associated with this object.
+     *  Unlike destroy(), after this call any surfaces that were created
+     *  from the session will no longer work. The session itself is destroyed.
+     */
+    public native void kill();
+
+    /* no user serviceable parts here ... */
+    @Override
+    protected void finalize() throws Throwable {
+        destroy();
+    }
+    
+    private native void init();
+    private native void destroy();
+    
+    private int mClient;
+}
+
diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java
new file mode 100644
index 0000000..e928998
--- /dev/null
+++ b/core/java/android/view/SurfaceView.java
@@ -0,0 +1,614 @@
+/*
+ * 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 android.view;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.PixelFormat;
+import android.graphics.PorterDuff;
+import android.graphics.Rect;
+import android.graphics.Region;
+import android.os.Handler;
+import android.os.Message;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.os.ParcelFileDescriptor;
+import android.util.AttributeSet;
+import android.util.Config;
+import android.util.Log;
+import java.util.ArrayList;
+
+import java.util.concurrent.locks.ReentrantLock;
+import java.lang.ref.WeakReference;
+
+/**
+ * Provides a dedicated drawing surface embedded inside of a view hierarchy.
+ * You can control the format of this surface and, if you like, its size; the
+ * SurfaceView takes care of placing the surface at the correct location on the
+ * screen
+ * 
+ * <p>The surface is Z ordered so that it is behind the window holding its
+ * SurfaceView; the SurfaceView punches a hole in its window to allow its
+ * surface to be displayed.  The view hierarchy will take care of correctly
+ * compositing with the Surface any siblings of the SurfaceView that would
+ * normally appear on top of it.  This can be used to place overlays such as
+ * buttons on top of the Surface, though note however that it can have an
+ * impact on performance since a full alpha-blended composite will be performed
+ * each time the Surface changes.
+ * 
+ * <p>Access to the underlying surface is provided via the SurfaceHolder interface,
+ * which can be retrieved by calling {@link #getHolder}.
+ * 
+ * <p>The Surface will be created for you while the SurfaceView's window is
+ * visible; you should implement {@link SurfaceHolder.Callback#surfaceCreated}
+ * and {@link SurfaceHolder.Callback#surfaceDestroyed} to discover when the
+ * Surface is created and destroyed as the window is shown and hidden.
+ * 
+ * <p>One of the purposes of this class is to provide a surface in which a
+ * secondary thread can render in to the screen.  If you are going to use it
+ * this way, you need to be aware of some threading semantics:
+ * 
+ * <ul>
+ * <li> All SurfaceView and
+ * {@link SurfaceHolder.Callback SurfaceHolder.Callback} methods will be called
+ * from the thread running the SurfaceView's window (typically the main thread
+ * of the application).  They thus need to correctly synchronize with any
+ * state that is also touched by the drawing thread.
+ * <li> You must ensure that the drawing thread only touches the underlying
+ * Surface while it is valid -- between
+ * {@link SurfaceHolder.Callback#surfaceCreated SurfaceHolder.Callback.surfaceCreated()}
+ * and
+ * {@link SurfaceHolder.Callback#surfaceDestroyed SurfaceHolder.Callback.surfaceDestroyed()}.
+ * </ul>
+ */
+public class SurfaceView extends View {
+    static private final String TAG = "SurfaceView";
+    static private final boolean DEBUG = false;
+    static private final boolean localLOGV = DEBUG ? true : Config.LOGV;
+
+    final ArrayList<SurfaceHolder.Callback> mCallbacks
+            = new ArrayList<SurfaceHolder.Callback>();
+
+    final int[] mLocation = new int[2];
+    
+    final ReentrantLock mSurfaceLock = new ReentrantLock();
+    final Surface mSurface = new Surface();
+    boolean mDrawingStopped = true;
+
+    final WindowManager.LayoutParams mLayout
+            = new WindowManager.LayoutParams();
+    IWindowSession mSession;
+    MyWindow mWindow;
+    final Rect mVisibleInsets = new Rect();
+    final Rect mWinFrame = new Rect();
+    final Rect mContentInsets = new Rect();
+
+    static final int KEEP_SCREEN_ON_MSG = 1;
+    static final int GET_NEW_SURFACE_MSG = 2;
+    
+    boolean mIsCreating = false;
+
+    final Handler mHandler = new Handler() {
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case KEEP_SCREEN_ON_MSG: {
+                    setKeepScreenOn(msg.arg1 != 0);
+                } break;
+                case GET_NEW_SURFACE_MSG: {
+                    handleGetNewSurface();
+                } break;
+            }
+        }
+    };
+    
+    boolean mRequestedVisible = false;
+    int mRequestedWidth = -1;
+    int mRequestedHeight = -1;
+    int mRequestedFormat = PixelFormat.OPAQUE;
+    int mRequestedType = -1;
+
+    boolean mHaveFrame = false;
+    boolean mDestroyReportNeeded = false;
+    boolean mNewSurfaceNeeded = false;
+    long mLastLockTime = 0;
+    
+    boolean mVisible = false;
+    int mLeft = -1;
+    int mTop = -1;
+    int mWidth = -1;
+    int mHeight = -1;
+    int mFormat = -1;
+    int mType = -1;
+    final Rect mSurfaceFrame = new Rect();
+
+    public SurfaceView(Context context) {
+        super(context);
+        setWillNotDraw(true);
+    }
+    
+    public SurfaceView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        setWillNotDraw(true);
+    }
+
+    public SurfaceView(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+        setWillNotDraw(true);
+    }
+    
+    /**
+     * Return the SurfaceHolder providing access and control over this
+     * SurfaceView's underlying surface.
+     * 
+     * @return SurfaceHolder The holder of the surface.
+     */
+    public SurfaceHolder getHolder() {
+        return mSurfaceHolder;
+    }
+
+    @Override
+    protected void onAttachedToWindow() {
+        super.onAttachedToWindow();
+        mParent.requestTransparentRegion(this);
+        mSession = getWindowSession();
+        mLayout.token = getWindowToken();
+        mLayout.setTitle("SurfaceView");
+    }
+
+    @Override
+    protected void onWindowVisibilityChanged(int visibility) {
+        super.onWindowVisibilityChanged(visibility);
+        mRequestedVisible = visibility == VISIBLE;
+        updateWindow(false);
+    }
+    
+    @Override
+    protected void onDetachedFromWindow() {
+        mRequestedVisible = false;
+        updateWindow(false);
+        mHaveFrame = false;
+        if (mWindow != null) {
+            try {
+                mSession.remove(mWindow);
+            } catch (RemoteException ex) {
+            }
+            mWindow = null;
+        }
+        mSession = null;
+        mLayout.token = null;
+
+        super.onDetachedFromWindow();
+    }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        int width = getDefaultSize(mRequestedWidth, widthMeasureSpec);
+        int height = getDefaultSize(mRequestedHeight, heightMeasureSpec);
+        setMeasuredDimension(width, height);
+    }
+    
+    @Override
+    protected void onScrollChanged(int l, int t, int oldl, int oldt) {
+        super.onScrollChanged(l, t, oldl, oldt);
+        updateWindow(false);
+    }
+
+    @Override
+    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
+        super.onSizeChanged(w, h, oldw, oldh);
+        updateWindow(false);
+    }
+
+    @Override
+    public boolean gatherTransparentRegion(Region region) {
+        boolean opaque = true;
+        if ((mPrivateFlags & SKIP_DRAW) == 0) {
+            // this view draws, remove it from the transparent region
+            opaque = super.gatherTransparentRegion(region);
+        } else if (region != null) {
+            int w = getWidth();
+            int h = getHeight();
+            if (w>0 && h>0) {
+                getLocationInWindow(mLocation);
+                // otherwise, punch a hole in the whole hierarchy
+                int l = mLocation[0];
+                int t = mLocation[1];
+                region.op(l, t, l+w, t+h, Region.Op.UNION);
+            }
+        }
+        if (PixelFormat.formatHasAlpha(mRequestedFormat)) {
+            opaque = false;
+        }
+        return opaque;
+    }
+
+    @Override
+    public void draw(Canvas canvas) {
+        // draw() is not called when SKIP_DRAW is set
+        if ((mPrivateFlags & SKIP_DRAW) == 0) {
+            // punch a whole in the view-hierarchy below us
+            canvas.drawColor(0, PorterDuff.Mode.CLEAR);
+        }
+        super.draw(canvas);
+    }
+
+    @Override
+    protected void dispatchDraw(Canvas canvas) {
+        // if SKIP_DRAW is cleared, draw() has already punched a hole
+        if ((mPrivateFlags & SKIP_DRAW) == SKIP_DRAW) {
+            // punch a whole in the view-hierarchy below us
+            canvas.drawColor(0, PorterDuff.Mode.CLEAR);
+        }
+        // reposition ourselves where the surface is 
+        mHaveFrame = true;
+        updateWindow(false);
+        super.dispatchDraw(canvas);
+    }
+
+    private void updateWindow(boolean force) {
+        if (!mHaveFrame) {
+            return;
+        }
+        
+        int myWidth = mRequestedWidth;
+        if (myWidth <= 0) myWidth = getWidth();
+        int myHeight = mRequestedHeight;
+        if (myHeight <= 0) myHeight = getHeight();
+        
+        getLocationInWindow(mLocation);
+        final boolean creating = mWindow == null;
+        final boolean formatChanged = mFormat != mRequestedFormat;
+        final boolean sizeChanged = mWidth != myWidth || mHeight != myHeight;
+        final boolean visibleChanged = mVisible != mRequestedVisible
+                || mNewSurfaceNeeded;
+        final boolean typeChanged = mType != mRequestedType;
+        if (force || creating || formatChanged || sizeChanged || visibleChanged
+            || typeChanged || mLeft != mLocation[0] || mTop != mLocation[1]) {
+
+            if (localLOGV) Log.i(TAG, "Changes: creating=" + creating
+                    + " format=" + formatChanged + " size=" + sizeChanged
+                    + " visible=" + visibleChanged
+                    + " left=" + (mLeft != mLocation[0])
+                    + " top=" + (mTop != mLocation[1]));
+            
+            try {
+                final boolean visible = mVisible = mRequestedVisible;
+                mLeft = mLocation[0];
+                mTop = mLocation[1];
+                mWidth = myWidth;
+                mHeight = myHeight;
+                mFormat = mRequestedFormat;
+                mType = mRequestedType;
+
+                mLayout.x = mLeft;
+                mLayout.y = mTop;
+                mLayout.width = getWidth();
+                mLayout.height = getHeight();
+                mLayout.format = mRequestedFormat;
+                mLayout.flags |=WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS
+                              | WindowManager.LayoutParams.FLAG_SCALED
+                              | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
+                              | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
+                              ;
+
+                mLayout.memoryType = mRequestedType;
+
+                if (mWindow == null) {
+                    mWindow = new MyWindow(this);
+                    mLayout.type = WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA;
+                    mLayout.gravity = Gravity.LEFT|Gravity.TOP;
+                    mSession.add(mWindow, mLayout,
+                            mVisible ? VISIBLE : GONE, mContentInsets);
+                }
+                
+                if (visibleChanged && (!visible || mNewSurfaceNeeded)) {
+                    reportSurfaceDestroyed();
+                }
+                
+                mNewSurfaceNeeded = false;
+                
+                mSurfaceLock.lock();
+                mDrawingStopped = !visible;
+                final int relayoutResult = mSession.relayout(
+                        mWindow, mLayout, mWidth, mHeight,
+                        visible ? VISIBLE : GONE, false, mWinFrame, mContentInsets,
+                        mVisibleInsets, mSurface);
+                if (localLOGV) Log.i(TAG, "New surface: " + mSurface
+                        + ", vis=" + visible + ", frame=" + mWinFrame);
+                mSurfaceFrame.left = 0;
+                mSurfaceFrame.top = 0;
+                mSurfaceFrame.right = mWinFrame.width();
+                mSurfaceFrame.bottom = mWinFrame.height();
+                mSurfaceLock.unlock();
+
+                try {
+                    if (visible) {
+                        mDestroyReportNeeded = true;
+
+                        SurfaceHolder.Callback callbacks[];
+                        synchronized (mCallbacks) {
+                            callbacks = new SurfaceHolder.Callback[mCallbacks.size()];
+                            mCallbacks.toArray(callbacks);
+                        }            
+
+                        if (visibleChanged) {
+                            mIsCreating = true;
+                            for (SurfaceHolder.Callback c : callbacks) {
+                                c.surfaceCreated(mSurfaceHolder);
+                            }
+                        }
+                        if (creating || formatChanged || sizeChanged
+                                || visibleChanged) {
+                            for (SurfaceHolder.Callback c : callbacks) {
+                                c.surfaceChanged(mSurfaceHolder, mFormat, mWidth, mHeight);
+                            }
+                        }
+                    }
+                } finally {
+                    mIsCreating = false;
+                    if (creating || (relayoutResult&WindowManagerImpl.RELAYOUT_FIRST_TIME) != 0) {
+                        mSession.finishDrawing(mWindow);
+                    }
+                }
+            } catch (RemoteException ex) {
+            }
+            if (localLOGV) Log.v(
+                TAG, "Layout: x=" + mLayout.x + " y=" + mLayout.y +
+                " w=" + mLayout.width + " h=" + mLayout.height +
+                ", frame=" + mSurfaceFrame);
+        }
+    }
+
+    private void reportSurfaceDestroyed() {
+        if (mDestroyReportNeeded) {
+            mDestroyReportNeeded = false;
+            SurfaceHolder.Callback callbacks[];
+            synchronized (mCallbacks) {
+                callbacks = new SurfaceHolder.Callback[mCallbacks.size()];
+                mCallbacks.toArray(callbacks);
+            }            
+            for (SurfaceHolder.Callback c : callbacks) {
+                c.surfaceDestroyed(mSurfaceHolder);
+            }
+        }
+        super.onDetachedFromWindow();
+    }
+
+    void handleGetNewSurface() {
+        mNewSurfaceNeeded = true;
+        updateWindow(false);
+    }
+
+    private static class MyWindow extends IWindow.Stub {
+        private WeakReference<SurfaceView> mSurfaceView;
+
+        public MyWindow(SurfaceView surfaceView) {
+            mSurfaceView = new WeakReference<SurfaceView>(surfaceView);
+        }
+
+        public void resized(int w, int h, Rect coveredInsets,
+                Rect visibleInsets, boolean reportDraw) {
+            SurfaceView surfaceView = mSurfaceView.get();
+            if (surfaceView != null) {
+                if (localLOGV) Log.v(
+                        "SurfaceView", surfaceView + " got resized: w=" +
+                                w + " h=" + h + ", cur w=" + mCurWidth + " h=" + mCurHeight);
+                synchronized (this) {
+                    if (mCurWidth != w || mCurHeight != h) {
+                        mCurWidth = w;
+                        mCurHeight = h;
+                    }
+                    if (reportDraw) {
+                        try {
+                            surfaceView.mSession.finishDrawing(surfaceView.mWindow);
+                        } catch (RemoteException e) {
+                        }
+                    }
+                }
+            }
+        }
+
+        public void dispatchKey(KeyEvent event) {
+            SurfaceView surfaceView = mSurfaceView.get();
+            if (surfaceView != null) {
+                //Log.w("SurfaceView", "Unexpected key event in surface: " + event);
+                if (surfaceView.mSession != null && surfaceView.mSurface != null) {
+                    try {
+                        surfaceView.mSession.finishKey(surfaceView.mWindow);
+                    } catch (RemoteException ex) {
+                    }
+                }
+            }
+        }
+
+        public void dispatchPointer(MotionEvent event, long eventTime) {
+            Log.w("SurfaceView", "Unexpected pointer event in surface: " + event);
+            //if (mSession != null && mSurface != null) {
+            //    try {
+            //        //mSession.finishKey(mWindow);
+            //    } catch (RemoteException ex) {
+            //    }
+            //}
+        }
+
+        public void dispatchTrackball(MotionEvent event, long eventTime) {
+            Log.w("SurfaceView", "Unexpected trackball event in surface: " + event);
+            //if (mSession != null && mSurface != null) {
+            //    try {
+            //        //mSession.finishKey(mWindow);
+            //    } catch (RemoteException ex) {
+            //    }
+            //}
+        }
+
+        public void dispatchAppVisibility(boolean visible) {
+            // The point of SurfaceView is to let the app control the surface.
+        }
+
+        public void dispatchGetNewSurface() {
+            SurfaceView surfaceView = mSurfaceView.get();
+            if (surfaceView != null) {
+                Message msg = surfaceView.mHandler.obtainMessage(GET_NEW_SURFACE_MSG);
+                surfaceView.mHandler.sendMessage(msg);
+            }
+        }
+
+        public void windowFocusChanged(boolean hasFocus, boolean touchEnabled) {
+            Log.w("SurfaceView", "Unexpected focus in surface: focus=" + hasFocus + ", touchEnabled=" + touchEnabled);
+        }
+
+        public void executeCommand(String command, String parameters, ParcelFileDescriptor out) {
+        }
+
+        int mCurWidth = -1;
+        int mCurHeight = -1;
+    }
+
+    private SurfaceHolder mSurfaceHolder = new SurfaceHolder() {
+        
+        private static final String LOG_TAG = "SurfaceHolder";
+        
+        public boolean isCreating() {
+            return mIsCreating;
+        }
+
+        public void addCallback(Callback callback) {
+            synchronized (mCallbacks) {
+                // This is a linear search, but in practice we'll 
+                // have only a couple callbacks, so it doesn't matter.
+                if (mCallbacks.contains(callback) == false) {      
+                    mCallbacks.add(callback);
+                }
+            }
+        }
+
+        public void removeCallback(Callback callback) {
+            synchronized (mCallbacks) {
+                mCallbacks.remove(callback);
+            }
+        }
+        
+        public void setFixedSize(int width, int height) {
+            if (mRequestedWidth != width || mRequestedHeight != height) {
+                mRequestedWidth = width;
+                mRequestedHeight = height;
+                requestLayout();
+            }
+        }
+
+        public void setSizeFromLayout() {
+            if (mRequestedWidth != -1 || mRequestedHeight != -1) {
+                mRequestedWidth = mRequestedHeight = -1;
+                requestLayout();
+            }
+        }
+
+        public void setFormat(int format) {
+            mRequestedFormat = format;
+            if (mWindow != null) {
+                updateWindow(false);
+            }
+        }
+
+        public void setType(int type) {
+            switch (type) {
+            case SURFACE_TYPE_NORMAL:
+            case SURFACE_TYPE_HARDWARE:
+            case SURFACE_TYPE_GPU:
+            case SURFACE_TYPE_PUSH_BUFFERS:
+                mRequestedType = type;
+                if (mWindow != null) {
+                    updateWindow(false);
+                }
+                break;
+            }
+        }
+
+        public void setKeepScreenOn(boolean screenOn) {
+            Message msg = mHandler.obtainMessage(KEEP_SCREEN_ON_MSG);
+            msg.arg1 = screenOn ? 1 : 0;
+            mHandler.sendMessage(msg);
+        }
+        
+        public Canvas lockCanvas() {
+            return internalLockCanvas(null);
+        }
+
+        public Canvas lockCanvas(Rect dirty) {
+            return internalLockCanvas(dirty);
+        }
+
+        private final Canvas internalLockCanvas(Rect dirty) {
+            if (mType == SURFACE_TYPE_PUSH_BUFFERS) {
+                throw new BadSurfaceTypeException(
+                        "Surface type is SURFACE_TYPE_PUSH_BUFFERS");
+            }
+            mSurfaceLock.lock();
+
+            if (localLOGV) Log.i(TAG, "Locking canvas... stopped="
+                    + mDrawingStopped + ", win=" + mWindow);
+
+            Canvas c = null;
+            if (!mDrawingStopped && mWindow != null) {
+                Rect frame = dirty != null ? dirty : mSurfaceFrame;
+                try {
+                    c = mSurface.lockCanvas(frame);
+                } catch (Exception e) {
+                    Log.e(LOG_TAG, "Exception locking surface", e);
+                }
+            }
+
+            if (localLOGV) Log.i(TAG, "Returned canvas: " + c);
+            if (c != null) {
+                mLastLockTime = SystemClock.uptimeMillis();
+                return c;
+            }
+            
+            // If the Surface is not ready to be drawn, then return null,
+            // but throttle calls to this function so it isn't called more
+            // than every 100ms.
+            long now = SystemClock.uptimeMillis();
+            long nextTime = mLastLockTime + 100;
+            if (nextTime > now) {
+                try {
+                    Thread.sleep(nextTime-now);
+                } catch (InterruptedException e) {
+                }
+                now = SystemClock.uptimeMillis();
+            }
+            mLastLockTime = now;
+            mSurfaceLock.unlock();
+            
+            return null;
+        }
+
+        public void unlockCanvasAndPost(Canvas canvas) {
+            mSurface.unlockCanvasAndPost(canvas);
+            mSurfaceLock.unlock();
+        }
+
+        public Surface getSurface() {
+            return mSurface;
+        }
+
+        public Rect getSurfaceFrame() {
+            return mSurfaceFrame;
+        }
+    };
+}
+
diff --git a/core/java/android/view/TouchDelegate.java b/core/java/android/view/TouchDelegate.java
new file mode 100644
index 0000000..27b49db
--- /dev/null
+++ b/core/java/android/view/TouchDelegate.java
@@ -0,0 +1,153 @@
+/*
+ * 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 android.view;
+
+import android.graphics.Rect;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewConfiguration;
+
+/**
+ * Helper class to handle situations where you want a view to have a larger touch area than its
+ * actual view bounds. The view whose touch area is changed is called the delegate view. This
+ * class should be used by an ancestor of the delegate. To use a TouchDelegate, first create an
+ * instance that specifies the bounds that should be mapped to the delegate and the delegate
+ * view itself.
+ * <p>
+ * The ancestor should then forward all of its touch events received in its
+ * {@link android.view.View#onTouchEvent(MotionEvent)} to {@link #onTouchEvent(MotionEvent)}.
+ * </p>
+ */
+public class TouchDelegate {
+    
+    /**
+     * View that should receive forwarded touch events 
+     */
+    private View mDelegateView;
+    
+    /**
+     * Bounds in local coordinates of the containing view that should be mapped to the delegate
+     * view. This rect is used for initial hit testing.
+     */
+    private Rect mBounds;
+    
+    /**
+     * mBounds inflated to include some slop. This rect is to track whether the motion events
+     * should be considered to be be within the delegate view.
+     */
+    private Rect mSlopBounds;
+    
+    /**
+     * True if the delegate had been targeted on a down event (intersected mBounds).
+     */
+    private boolean mDelegateTargeted;
+
+    /**
+     * The touchable region of the View extends above its actual extent.
+     */
+    public static final int ABOVE = 1;
+
+    /**
+     * The touchable region of the View extends below its actual extent.
+     */
+    public static final int BELOW = 2;
+
+    /**
+     * The touchable region of the View extends to the left of its
+     * actual extent.
+     */
+    public static final int TO_LEFT = 4;
+
+    /**
+     * The touchable region of the View extends to the right of its
+     * actual extent.
+     */
+    public static final int TO_RIGHT = 8;
+
+    private int mSlop;
+
+    /**
+     * Constructor
+     * 
+     * @param bounds Bounds in local coordinates of the containing view that should be mapped to
+     *        the delegate view
+     * @param delegateView The view that should receive motion events
+     */
+    public TouchDelegate(Rect bounds, View delegateView) {
+        mBounds = bounds;
+
+        mSlop = ViewConfiguration.get(delegateView.getContext()).getScaledTouchSlop();
+        mSlopBounds = new Rect(bounds);
+        mSlopBounds.inset(-mSlop, -mSlop);
+        mDelegateView = delegateView;
+    }
+
+    /**
+     * Will forward touch events to the delegate view if the event is within the bounds
+     * specified in the constructor.
+     * 
+     * @param event The touch event to forward
+     * @return True if the event was forwarded to the delegate, false otherwise.
+     */
+    public boolean onTouchEvent(MotionEvent event) {
+        int x = (int)event.getX();
+        int y = (int)event.getY();
+        boolean sendToDelegate = false;
+        boolean hit = true;
+        boolean handled = false;
+
+        switch (event.getAction()) {
+        case MotionEvent.ACTION_DOWN:
+            Rect bounds = mBounds;
+
+            if (bounds.contains(x, y)) {
+                mDelegateTargeted = true;
+                sendToDelegate = true;
+            }
+            break;
+        case MotionEvent.ACTION_UP:
+        case MotionEvent.ACTION_MOVE:
+            sendToDelegate = mDelegateTargeted;
+            if (sendToDelegate) {
+                Rect slopBounds = mSlopBounds;
+                if (!slopBounds.contains(x, y)) {
+                    hit = false;
+                }
+            }
+            break;
+        case MotionEvent.ACTION_CANCEL:
+            sendToDelegate = mDelegateTargeted;
+            mDelegateTargeted = false;
+            break;
+        }
+        if (sendToDelegate) {
+            final View delegateView = mDelegateView;
+            
+            if (hit) {
+                // Offset event coordinates to be inside the target view
+                event.setLocation(delegateView.getWidth() / 2, delegateView.getHeight() / 2);
+            } else {
+                // Offset event coordinates to be outside the target view (in case it does
+                // something like tracking pressed state)
+                int slop = mSlop;
+                event.setLocation(-(slop * 2), -(slop * 2));
+            }
+            handled = delegateView.dispatchTouchEvent(event);
+        }
+        return handled;
+    }
+}
diff --git a/core/java/android/view/VelocityTracker.java b/core/java/android/view/VelocityTracker.java
new file mode 100644
index 0000000..c80167e
--- /dev/null
+++ b/core/java/android/view/VelocityTracker.java
@@ -0,0 +1,215 @@
+/*
+ * 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 android.view;
+
+import android.util.Config;
+import android.util.Log;
+
+/**
+ * Helper for tracking the velocity of touch events, for implementing
+ * flinging and other such gestures.  Use {@link #obtain} to retrieve a
+ * new instance of the class when you are going to begin tracking, put
+ * the motion events you receive into it with {@link #addMovement(MotionEvent)},
+ * and when you want to determine the velocity call
+ * {@link #computeCurrentVelocity(int)} and then {@link #getXVelocity()}
+ * and {@link #getXVelocity()}.
+ */
+public final class VelocityTracker {
+    static final String TAG = "VelocityTracker";
+    static final boolean DEBUG = false;
+    static final boolean localLOGV = DEBUG || Config.LOGV;
+    
+    static final int NUM_PAST = 10;
+    static final int LONGEST_PAST_TIME = 200;
+    
+    static final VelocityTracker[] mPool = new VelocityTracker[1];
+    
+    final float mPastX[] = new float[NUM_PAST];
+    final float mPastY[] = new float[NUM_PAST];
+    final long mPastTime[] = new long[NUM_PAST];
+   
+    float mYVelocity;
+    float mXVelocity;
+    
+    /**
+     * Retrieve a new VelocityTracker object to watch the velocity of a
+     * motion.  Be sure to call {@link #recycle} when done.  You should
+     * generally only maintain an active object while tracking a movement,
+     * so that the VelocityTracker can be re-used elsewhere.
+     * 
+     * @return Returns a new VelocityTracker.
+     */
+    static public VelocityTracker obtain() {
+        synchronized (mPool) {
+            VelocityTracker vt = mPool[0];
+            if (vt != null) {
+                vt.clear();
+                return vt;
+            }
+            return new VelocityTracker();
+        }
+    }
+    
+    /**
+     * Return a VelocityTracker object back to be re-used by others.  You must
+     * not touch the object after calling this function.
+     */
+    public void recycle() {
+        synchronized (mPool) {
+            mPool[0] = this;
+        }
+    }
+    
+    private VelocityTracker() {
+    }
+    
+    /**
+     * Reset the velocity tracker back to its initial state.
+     */
+    public void clear() {
+        mPastTime[0] = 0;
+    }
+    
+    /**
+     * Add a user's movement to the tracker.  You should call this for the
+     * initial {@link MotionEvent#ACTION_DOWN}, the following
+     * {@link MotionEvent#ACTION_MOVE} events that you receive, and the
+     * final {@link MotionEvent#ACTION_UP}.  You can, however, call this
+     * for whichever events you desire.
+     * 
+     * @param ev The MotionEvent you received and would like to track.
+     */
+    public void addMovement(MotionEvent ev) {
+        long time = ev.getEventTime();
+        final int N = ev.getHistorySize();
+        for (int i=0; i<N; i++) {
+            addPoint(ev.getHistoricalX(i), ev.getHistoricalY(i),
+                    ev.getHistoricalEventTime(i));
+        }
+        addPoint(ev.getX(), ev.getY(), time);
+    }
+
+    private void addPoint(float x, float y, long time) {
+        int drop = -1;
+        int i;
+        if (localLOGV) Log.v(TAG, "Adding past y=" + y + " time=" + time);
+        final long[] pastTime = mPastTime;
+        for (i=0; i<NUM_PAST; i++) {
+            if (pastTime[i] == 0) {
+                break;
+            } else if (pastTime[i] < time-LONGEST_PAST_TIME) {
+                if (localLOGV) Log.v(TAG, "Dropping past too old at "
+                        + i + " time=" + pastTime[i]);
+                drop = i;
+            }
+        }
+        if (localLOGV) Log.v(TAG, "Add index: " + i);
+        if (i == NUM_PAST && drop < 0) {
+            drop = 0;
+        }
+        if (drop == i) drop--;
+        final float[] pastX = mPastX;
+        final float[] pastY = mPastY;
+        if (drop >= 0) {
+            if (localLOGV) Log.v(TAG, "Dropping up to #" + drop);
+            final int start = drop+1;
+            final int count = NUM_PAST-drop-1;
+            System.arraycopy(pastX, start, pastX, 0, count);
+            System.arraycopy(pastY, start, pastY, 0, count);
+            System.arraycopy(pastTime, start, pastTime, 0, count);
+            i -= (drop+1);
+        }
+        pastX[i] = x;
+        pastY[i] = y;
+        pastTime[i] = time;
+        i++;
+        if (i < NUM_PAST) {
+            pastTime[i] = 0;
+        }
+    }
+    
+    /**
+     * Compute the current velocity based on the points that have been
+     * collected.  Only call this when you actually want to retrieve velocity
+     * information, as it is relatively expensive.  You can then retrieve
+     * the velocity with {@link #getXVelocity()} and
+     * {@link #getYVelocity()}.
+     * 
+     * @param units The units you would like the velocity in.  A value of 1
+     * provides pixels per millisecond, 1000 provides pixels per second, etc.
+     */
+    public void computeCurrentVelocity(int units) {
+        final float[] pastX = mPastX;
+        final float[] pastY = mPastY;
+        final long[] pastTime = mPastTime;
+        
+        // Kind-of stupid.
+        final float oldestX = pastX[0];
+        final float oldestY = pastY[0];
+        final long oldestTime = pastTime[0];
+        float accumX = 0;
+        float accumY = 0;
+        int N=0;
+        while (N < NUM_PAST) {
+            if (pastTime[N] == 0) {
+                break;
+            }
+            N++;
+        }
+        // Skip the last received event, since it is probably pretty noisy.
+        if (N > 3) N--;
+        
+        for (int i=1; i < N; i++) {
+            final int dur = (int)(pastTime[i] - oldestTime);
+            if (dur == 0) continue;
+            float dist = pastX[i] - oldestX;
+            float vel = (dist/dur) * units;   // pixels/frame.
+            if (accumX == 0) accumX = vel;
+            else accumX = (accumX + vel) * .5f;
+            
+            dist = pastY[i] - oldestY;
+            vel = (dist/dur) * units;   // pixels/frame.
+            if (accumY == 0) accumY = vel;
+            else accumY = (accumY + vel) * .5f;
+        }
+        mXVelocity = accumX;
+        mYVelocity = accumY;
+        
+        if (localLOGV) Log.v(TAG, "Y velocity=" + mYVelocity +" X velocity="
+                + mXVelocity + " N=" + N);
+    }
+    
+    /**
+     * Retrieve the last computed X velocity.  You must first call
+     * {@link #computeCurrentVelocity(int)} before calling this function.
+     * 
+     * @return The previously computed X velocity.
+     */
+    public float getXVelocity() {
+        return mXVelocity;
+    }
+    
+    /**
+     * Retrieve the last computed Y velocity.  You must first call
+     * {@link #computeCurrentVelocity(int)} before calling this function.
+     * 
+     * @return The previously computed Y velocity.
+     */
+    public float getYVelocity() {
+        return mYVelocity;
+    }
+}
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
new file mode 100644
index 0000000..3e762f5
--- /dev/null
+++ b/core/java/android/view/View.java
@@ -0,0 +1,8076 @@
+/*
+ * 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 android.view;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.LinearGradient;
+import android.graphics.Matrix;
+import android.graphics.Paint;
+import android.graphics.PixelFormat;
+import android.graphics.PorterDuff;
+import android.graphics.PorterDuffXfermode;
+import android.graphics.Rect;
+import android.graphics.Region;
+import android.graphics.Shader;
+import android.graphics.Point;
+import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.Drawable;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Message;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.os.SystemProperties;
+import android.util.AttributeSet;
+import android.util.EventLog;
+import android.util.Log;
+import android.util.SparseArray;
+import android.view.ContextMenu.ContextMenuInfo;
+import android.view.animation.Animation;
+import android.view.inputmethod.InputConnection;
+import android.view.inputmethod.InputMethodManager;
+import android.view.inputmethod.EditorInfo;
+import android.widget.ScrollBarDrawable;
+
+import com.android.internal.R;
+import com.android.internal.view.menu.MenuBuilder;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.lang.ref.SoftReference;
+
+/**
+ * <p>
+ * This class represents the basic building block for user interface components. A View
+ * occupies a rectangular area on the screen and is responsible for drawing and
+ * event handling. View is the base class for <em>widgets</em>, which are
+ * used to create interactive UI components (buttons, text fields, etc.). The 
+ * {@link android.view.ViewGroup} subclass is the base class for <em>layouts</em>, which
+ * are invisible containers that hold other Views (or other ViewGroups) and define
+ * their layout properties.
+ * </p>
+ *
+ * <div class="special">
+ * <p>For an introduction to using this class to develop your 
+ * application's user interface, read the Developer Guide documentation on 
+ * <strong><a href="{@docRoot}guide/topics/ui/index.html">User Interface</a></strong>. Special topics
+ * include: 
+ * <br/><a href="{@docRoot}guide/topics/ui/declaring-layout.html">Declaring Layout</a>
+ * <br/><a href="{@docRoot}guide/topics/ui/menus.html">Creating Menus</a>
+ * <br/><a href="{@docRoot}guide/topics/ui/layout-objects.html">Common Layout Objects</a>
+ * <br/><a href="{@docRoot}guide/topics/ui/binding.html">Binding to Data with AdapterView</a>
+ * <br/><a href="{@docRoot}guide/topics/ui/ui-events.html">Handling UI Events</a>
+ * <br/><a href="{@docRoot}guide/topics/ui/themes.html">Applying Styles and Themes</a>
+ * <br/><a href="{@docRoot}guide/topics/ui/custom-components.html">Building Custom Components</a>
+ * <br/><a href="{@docRoot}guide/topics/ui/how-android-draws.html">How Android Draws Views</a>.
+ * </p>
+ * </div>
+ * 
+ * <a name="Using"></a>
+ * <h3>Using Views</h3>
+ * <p>
+ * All of the views in a window are arranged in a single tree. You can add views
+ * either from code or by specifying a tree of views in one or more XML layout
+ * files. There are many specialized subclasses of views that act as controls or
+ * are capable of displaying text, images, or other content.
+ * </p>
+ * <p>
+ * Once you have created a tree of views, there are typically a few types of
+ * common operations you may wish to perform:
+ * <ul>
+ * <li><strong>Set properties:</strong> for example setting the text of a
+ * {@link android.widget.TextView}. The available properties and the methods
+ * that set them will vary among the different subclasses of views. Note that
+ * properties that are known at build time can be set in the XML layout
+ * files.</li>
+ * <li><strong>Set focus:</strong> The framework will handled moving focus in
+ * response to user input. To force focus to a specific view, call
+ * {@link #requestFocus}.</li>
+ * <li><strong>Set up listeners:</strong> Views allow clients to set listeners
+ * that will be notified when something interesting happens to the view. For
+ * example, all views will let you set a listener to be notified when the view
+ * gains or loses focus. You can register such a listener using
+ * {@link #setOnFocusChangeListener}. Other view subclasses offer more
+ * specialized listeners. For example, a Button exposes a listener to notify
+ * clients when the button is clicked.</li>
+ * <li><strong>Set visibility:</strong> You can hide or show views using
+ * {@link #setVisibility}.</li>
+ * </ul>
+ * </p>
+ * <p><em>
+ * Note: The Android framework is responsible for measuring, laying out and
+ * drawing views. You should not call methods that perform these actions on
+ * views yourself unless you are actually implementing a
+ * {@link android.view.ViewGroup}.
+ * </em></p>
+ *
+ * <a name="Lifecycle"></a>
+ * <h3>Implementing a Custom View</h3>
+ *
+ * <p>
+ * To implement a custom view, you will usually begin by providing overrides for
+ * some of the standard methods that the framework calls on all views. You do
+ * not need to override all of these methods. In fact, you can start by just
+ * overriding {@link #onDraw(android.graphics.Canvas)}.
+ * <table border="2" width="85%" align="center" cellpadding="5">
+ *     <thead>
+ *         <tr><th>Category</th> <th>Methods</th> <th>Description</th></tr>
+ *     </thead>
+ *
+ *     <tbody>
+ *     <tr>
+ *         <td rowspan="2">Creation</td>
+ *         <td>Constructors</td>
+ *         <td>There is a form of the constructor that are called when the view
+ *         is created from code and a form that is called when the view is
+ *         inflated from a layout file. The second form should parse and apply
+ *         any attributes defined in the layout file.
+ *         </td>
+ *     </tr>
+ *     <tr>
+ *         <td><code>{@link #onFinishInflate()}</code></td>
+ *         <td>Called after a view and all of its children has been inflated
+ *         from XML.</td>
+ *     </tr>
+ *
+ *     <tr>
+ *         <td rowspan="3">Layout</td>
+ *         <td><code>{@link #onMeasure}</code></td>
+ *         <td>Called to determine the size requirements for this view and all
+ *         of its children.
+ *         </td>
+ *     </tr>
+ *     <tr>
+ *         <td><code>{@link #onLayout}</code></td>
+ *         <td>Called when this view should assign a size and position to all
+ *         of its children.
+ *         </td>
+ *     </tr>
+ *     <tr>
+ *         <td><code>{@link #onSizeChanged}</code></td>
+ *         <td>Called when the size of this view has changed.
+ *         </td>
+ *     </tr>
+ *
+ *     <tr>
+ *         <td>Drawing</td>
+ *         <td><code>{@link #onDraw}</code></td>
+ *         <td>Called when the view should render its content.
+ *         </td>
+ *     </tr>
+ *
+ *     <tr>
+ *         <td rowspan="4">Event processing</td>
+ *         <td><code>{@link #onKeyDown}</code></td>
+ *         <td>Called when a new key event occurs.
+ *         </td>
+ *     </tr>
+ *     <tr>
+ *         <td><code>{@link #onKeyUp}</code></td>
+ *         <td>Called when a key up event occurs.
+ *         </td>
+ *     </tr>
+ *     <tr>
+ *         <td><code>{@link #onTrackballEvent}</code></td>
+ *         <td>Called when a trackball motion event occurs.
+ *         </td>
+ *     </tr>
+ *     <tr>
+ *         <td><code>{@link #onTouchEvent}</code></td>
+ *         <td>Called when a touch screen motion event occurs.
+ *         </td>
+ *     </tr>
+ *
+ *     <tr>
+ *         <td rowspan="2">Focus</td>
+ *         <td><code>{@link #onFocusChanged}</code></td>
+ *         <td>Called when the view gains or loses focus.
+ *         </td>
+ *     </tr>
+ *
+ *     <tr>
+ *         <td><code>{@link #onWindowFocusChanged}</code></td>
+ *         <td>Called when the window containing the view gains or loses focus.
+ *         </td>
+ *     </tr>
+ *
+ *     <tr>
+ *         <td rowspan="3">Attaching</td>
+ *         <td><code>{@link #onAttachedToWindow()}</code></td>
+ *         <td>Called when the view is attached to a window.
+ *         </td>
+ *     </tr>
+ *
+ *     <tr>
+ *         <td><code>{@link #onDetachedFromWindow}</code></td>
+ *         <td>Called when the view is detached from its window.
+ *         </td>
+ *     </tr>
+ *
+ *     <tr>
+ *         <td><code>{@link #onWindowVisibilityChanged}</code></td>
+ *         <td>Called when the visibility of the window containing the view
+ *         has changed.
+ *         </td>
+ *     </tr>
+ *     </tbody>
+ *
+ * </table>
+ * </p>
+ *
+ * <a name="IDs"></a>
+ * <h3>IDs</h3>
+ * Views may have an integer id associated with them. These ids are typically
+ * assigned in the layout XML files, and are used to find specific views within
+ * the view tree. A common pattern is to:
+ * <ul>
+ * <li>Define a Button in the layout file and assign it a unique ID.
+ * <pre>
+ * &lt;Button id="@+id/my_button"
+ *     android:layout_width="wrap_content"
+ *     android:layout_height="wrap_content"
+ *     android:text="@string/my_button_text"/&gt;
+ * </pre></li>
+ * <li>From the onCreate method of an Activity, find the Button
+ * <pre class="prettyprint">
+ *      Button myButton = (Button) findViewById(R.id.my_button);
+ * </pre></li>
+ * </ul>
+ * <p>
+ * View IDs need not be unique throughout the tree, but it is good practice to
+ * ensure that they are at least unique within the part of the tree you are
+ * searching.
+ * </p>
+ *
+ * <a name="Position"></a>
+ * <h3>Position</h3>
+ * <p>
+ * The geometry of a view is that of a rectangle. A view has a location,
+ * expressed as a pair of <em>left</em> and <em>top</em> coordinates, and
+ * two dimensions, expressed as a width and a height. The unit for location
+ * and dimensions is the pixel.
+ * </p>
+ *
+ * <p>
+ * It is possible to retrieve the location of a view by invoking the methods
+ * {@link #getLeft()} and {@link #getTop()}. The former returns the left, or X,
+ * coordinate of the rectangle representing the view. The latter returns the
+ * top, or Y, coordinate of the rectangle representing the view. These methods
+ * both return the location of the view relative to its parent. For instance,
+ * when getLeft() returns 20, that means the view is located 20 pixels to the
+ * right of the left edge of its direct parent.
+ * </p>
+ *
+ * <p>
+ * In addition, several convenience methods are offered to avoid unnecessary
+ * computations, namely {@link #getRight()} and {@link #getBottom()}.
+ * These methods return the coordinates of the right and bottom edges of the
+ * rectangle representing the view. For instance, calling {@link #getRight()}
+ * is similar to the following computation: <code>getLeft() + getWidth()</code>
+ * (see <a href="#SizePaddingMargins">Size</a> for more information about the width.)
+ * </p>
+ *
+ * <a name="SizePaddingMargins"></a>
+ * <h3>Size, padding and margins</h3>
+ * <p>
+ * The size of a view is expressed with a width and a height. A view actually
+ * possess two pairs of width and height values.
+ * </p>
+ *
+ * <p>
+ * The first pair is known as <em>measured width</em> and
+ * <em>measured height</em>. These dimensions define how big a view wants to be
+ * within its parent (see <a href="#Layout">Layout</a> for more details.) The
+ * measured dimensions can be obtained by calling {@link #getMeasuredWidth()}
+ * and {@link #getMeasuredHeight()}.
+ * </p>
+ *
+ * <p>
+ * The second pair is simply known as <em>width</em> and <em>height</em>, or
+ * sometimes <em>drawing width</em> and <em>drawing height</em>. These
+ * dimensions define the actual size of the view on screen, at drawing time and
+ * after layout. These values may, but do not have to, be different from the
+ * measured width and height. The width and height can be obtained by calling
+ * {@link #getWidth()} and {@link #getHeight()}.
+ * </p>
+ *
+ * <p>
+ * To measure its dimensions, a view takes into account its padding. The padding
+ * is expressed in pixels for the left, top, right and bottom parts of the view.
+ * Padding can be used to offset the content of the view by a specific amount of
+ * pixels. For instance, a left padding of 2 will push the view's content by
+ * 2 pixels to the right of the left edge. Padding can be set using the
+ * {@link #setPadding(int, int, int, int)} method and queried by calling
+ * {@link #getPaddingLeft()}, {@link #getPaddingTop()},
+ * {@link #getPaddingRight()} and {@link #getPaddingBottom()}.
+ * </p>
+ *
+ * <p>
+ * Even though a view can define a padding, it does not provide any support for
+ * margins. However, view groups provide such a support. Refer to
+ * {@link android.view.ViewGroup} and
+ * {@link android.view.ViewGroup.MarginLayoutParams} for further information.
+ * </p>
+ *
+ * <a name="Layout"></a>
+ * <h3>Layout</h3>
+ * <p>
+ * Layout is a two pass process: a measure pass and a layout pass. The measuring
+ * pass is implemented in {@link #measure(int, int)} and is a top-down traversal
+ * of the view tree. Each view pushes dimension specifications down the tree
+ * during the recursion. At the end of the measure pass, every view has stored
+ * its measurements. The second pass happens in
+ * {@link #layout(int,int,int,int)} and is also top-down. During
+ * this pass each parent is responsible for positioning all of its children
+ * using the sizes computed in the measure pass.
+ * </p>
+ *
+ * <p>
+ * When a view's measure() method returns, its {@link #getMeasuredWidth()} and
+ * {@link #getMeasuredHeight()} values must be set, along with those for all of
+ * that view's descendants. A view's measured width and measured height values
+ * must respect the constraints imposed by the view's parents. This guarantees
+ * that at the end of the measure pass, all parents accept all of their
+ * children's measurements. A parent view may call measure() more than once on
+ * its children. For example, the parent may measure each child once with
+ * unspecified dimensions to find out how big they want to be, then call
+ * measure() on them again with actual numbers if the sum of all the children's
+ * unconstrained sizes is too big or too small.
+ * </p>
+ *
+ * <p>
+ * The measure pass uses two classes to communicate dimensions. The
+ * {@link MeasureSpec} class is used by views to tell their parents how they
+ * want to be measured and positioned. The base LayoutParams class just
+ * describes how big the view wants to be for both width and height. For each
+ * dimension, it can specify one of:
+ * <ul>
+ * <li> an exact number
+ * <li>FILL_PARENT, which means the view wants to be as big as its parent
+ * (minus padding)
+ * <li> WRAP_CONTENT, which means that the view wants to be just big enough to
+ * enclose its content (plus padding).
+ * </ul>
+ * There are subclasses of LayoutParams for different subclasses of ViewGroup.
+ * For example, AbsoluteLayout has its own subclass of LayoutParams which adds
+ * an X and Y value.
+ * </p>
+ *
+ * <p>
+ * MeasureSpecs are used to push requirements down the tree from parent to
+ * child. A MeasureSpec can be in one of three modes:
+ * <ul>
+ * <li>UNSPECIFIED: This is used by a parent to determine the desired dimension
+ * of a child view. For example, a LinearLayout may call measure() on its child
+ * with the height set to UNSPECIFIED and a width of EXACTLY 240 to find out how
+ * tall the child view wants to be given a width of 240 pixels.
+ * <li>EXACTLY: This is used by the parent to impose an exact size on the
+ * child. The child must use this size, and guarantee that all of its
+ * descendants will fit within this size.
+ * <li>AT_MOST: This is used by the parent to impose a maximum size on the
+ * child. The child must gurantee that it and all of its descendants will fit
+ * within this size.
+ * </ul>
+ * </p>
+ *
+ * <p>
+ * To intiate a layout, call {@link #requestLayout}. This method is typically
+ * called by a view on itself when it believes that is can no longer fit within
+ * its current bounds.
+ * </p>
+ *
+ * <a name="Drawing"></a>
+ * <h3>Drawing</h3>
+ * <p>
+ * Drawing is handled by walking the tree and rendering each view that
+ * intersects the the invalid region. Because the tree is traversed in-order,
+ * this means that parents will draw before (i.e., behind) their children, with
+ * siblings drawn in the order they appear in the tree.
+ * If you set a background drawable for a View, then the View will draw it for you
+ * before calling back to its <code>onDraw()</code> method.
+ * </p>
+ *
+ * <p>
+ * Note that the framework will not draw views that are not in the invalid region. 
+ * </p>
+ *
+ * <p>
+ * To force a view to draw, call {@link #invalidate()}.
+ * </p>
+ *
+ * <a name="EventHandlingThreading"></a>
+ * <h3>Event Handling and Threading</h3>
+ * <p>
+ * The basic cycle of a view is as follows:
+ * <ol>
+ * <li>An event comes in and is dispatched to the appropriate view. The view
+ * handles the event and notifies any listeners.</li>
+ * <li>If in the course of processing the event, the view's bounds may need
+ * to be changed, the view will call {@link #requestLayout()}.</li>
+ * <li>Similarly, if in the course of processing the event the view's appearance
+ * may need to be changed, the view will call {@link #invalidate()}.</li>
+ * <li>If either {@link #requestLayout()} or {@link #invalidate()} were called,
+ * the framework will take care of measuring, laying out, and drawing the tree
+ * as appropriate.</li>
+ * </ol>
+ * </p>
+ *
+ * <p><em>Note: The entire view tree is single threaded. You must always be on
+ * the UI thread when calling any method on any view.</em>
+ * If you are doing work on other threads and want to update the state of a view
+ * from that thread, you should use a {@link Handler}.
+ * </p>
+ *
+ * <a name="FocusHandling"></a>
+ * <h3>Focus Handling</h3>
+ * <p>
+ * The framework will handle routine focus movement in response to user input.
+ * This includes changing the focus as views are removed or hidden, or as new
+ * views become available. Views indicate their willingness to take focus
+ * through the {@link #isFocusable} method. To change whether a view can take
+ * focus, call {@link #setFocusable(boolean)}.  When in touch mode (see notes below)
+ * views indicate whether they still would like focus via {@link #isFocusableInTouchMode}
+ * and can change this via {@link #setFocusableInTouchMode(boolean)}.
+ * </p>
+ * <p>
+ * Focus movement is based on an algorithm which finds the nearest neighbor in a
+ * given direction. In rare cases, the default algorithm may not match the
+ * intended behavior of the developer. In these situations, you can provide
+ * explicit overrides by using these XML attributes in the layout file:
+ * <pre>
+ * nextFocusDown
+ * nextFocusLeft
+ * nextFocusRight
+ * nextFocusUp
+ * </pre>
+ * </p>
+ *
+ *
+ * <p>
+ * To get a particular view to take focus, call {@link #requestFocus()}.
+ * </p>
+ *
+ * <a name="TouchMode"></a>
+ * <h3>Touch Mode</h3>
+ * <p>
+ * When a user is navigating a user interface via directional keys such as a D-pad, it is
+ * necessary to give focus to actionable items such as buttons so the user can see
+ * what will take input.  If the device has touch capabilities, however, and the user
+ * begins interacting with the interface by touching it, it is no longer necessary to
+ * always highlight, or give focus to, a particular view.  This motivates a mode
+ * for interaction named 'touch mode'.
+ * </p>
+ * <p>
+ * For a touch capable device, once the user touches the screen, the device
+ * will enter touch mode.  From this point onward, only views for which
+ * {@link #isFocusableInTouchMode} is true will be focusable, such as text editing widgets.
+ * Other views that are touchable, like buttons, will not take focus when touched; they will
+ * only fire the on click listeners.
+ * </p>
+ * <p>
+ * Any time a user hits a directional key, such as a D-pad direction, the view device will
+ * exit touch mode, and find a view to take focus, so that the user may resume interacting
+ * with the user interface without touching the screen again.
+ * </p>
+ * <p>
+ * The touch mode state is maintained across {@link android.app.Activity}s.  Call
+ * {@link #isInTouchMode} to see whether the device is currently in touch mode.
+ * </p>
+ *
+ * <a name="Scrolling"></a>
+ * <h3>Scrolling</h3>
+ * <p>
+ * The framework provides basic support for views that wish to internally
+ * scroll their content. This includes keeping track of the X and Y scroll
+ * offset as well as mechanisms for drawing scrollbars. See
+ * {@link #scrollBy(int, int)}, {@link #scrollTo(int, int)} for more details.
+ * </p>
+ *
+ * <a name="Tags"></a>
+ * <h3>Tags</h3>
+ * <p>
+ * Unlike IDs, tags are not used to identify views. Tags are essentially an
+ * extra piece of information that can be associated with a view. They are most
+ * often used as a convenience to store data related to views in the views
+ * themselves rather than by putting them in a separate structure.
+ * </p>
+ *
+ * <a name="Animation"></a>
+ * <h3>Animation</h3>
+ * <p>
+ * You can attach an {@link Animation} object to a view using
+ * {@link #setAnimation(Animation)} or
+ * {@link #startAnimation(Animation)}. The animation can alter the scale,
+ * rotation, translation and alpha of a view over time. If the animation is
+ * attached to a view that has children, the animation will affect the entire
+ * subtree rooted by that node. When an animation is started, the framework will
+ * take care of redrawing the appropriate views until the animation completes.
+ * </p>
+ *
+ * @attr ref android.R.styleable#View_fitsSystemWindows
+ * @attr ref android.R.styleable#View_nextFocusDown
+ * @attr ref android.R.styleable#View_nextFocusLeft
+ * @attr ref android.R.styleable#View_nextFocusRight
+ * @attr ref android.R.styleable#View_nextFocusUp
+ * @attr ref android.R.styleable#View_scrollX
+ * @attr ref android.R.styleable#View_scrollY
+ * @attr ref android.R.styleable#View_scrollbarTrackHorizontal
+ * @attr ref android.R.styleable#View_scrollbarThumbHorizontal
+ * @attr ref android.R.styleable#View_scrollbarSize
+ * @attr ref android.R.styleable#View_scrollbars
+ * @attr ref android.R.styleable#View_scrollbarThumbVertical
+ * @attr ref android.R.styleable#View_scrollbarTrackVertical
+ * @attr ref android.R.styleable#View_scrollbarAlwaysDrawHorizontalTrack
+ * @attr ref android.R.styleable#View_scrollbarAlwaysDrawVerticalTrack
+ *
+ * @see android.view.ViewGroup
+ */
+public class View implements Drawable.Callback, KeyEvent.Callback {
+    private static final boolean DBG = false;
+
+    /**
+     * The logging tag used by this class with android.util.Log.
+     */
+    protected static final String VIEW_LOG_TAG = "View";
+
+    /**
+     * Used to mark a View that has no ID.
+     */
+    public static final int NO_ID = -1;
+
+    /**
+     * This view does not want keystrokes. Use with TAKES_FOCUS_MASK when
+     * calling setFlags.
+     */
+    private static final int NOT_FOCUSABLE = 0x00000000;
+
+    /**
+     * This view wants keystrokes. Use with TAKES_FOCUS_MASK when calling
+     * setFlags.
+     */
+    private static final int FOCUSABLE = 0x00000001;
+
+    /**
+     * Mask for use with setFlags indicating bits used for focus.
+     */
+    private static final int FOCUSABLE_MASK = 0x00000001;
+
+    /**
+     * This view will adjust its padding to fit sytem windows (e.g. status bar)
+     */
+    private static final int FITS_SYSTEM_WINDOWS = 0x00000002;
+
+    /**
+     * This view is visible.  Use with {@link #setVisibility}.
+     */
+    public static final int VISIBLE = 0x00000000;
+
+    /**
+     * This view is invisible, but it still takes up space for layout purposes.
+     * Use with {@link #setVisibility}.
+     */
+    public static final int INVISIBLE = 0x00000004;
+
+    /**
+     * This view is invisible, and it doesn't take any space for layout
+     * purposes. Use with {@link #setVisibility}.
+     */
+    public static final int GONE = 0x00000008;
+
+    /**
+     * Mask for use with setFlags indicating bits used for visibility.
+     * {@hide}
+     */
+    static final int VISIBILITY_MASK = 0x0000000C;
+
+    private static final int[] VISIBILITY_FLAGS = {VISIBLE, INVISIBLE, GONE};
+
+    /**
+     * This view is enabled. Intrepretation varies by subclass.
+     * Use with ENABLED_MASK when calling setFlags.
+     * {@hide}
+     */
+    static final int ENABLED = 0x00000000;
+
+    /**
+     * This view is disabled. Intrepretation varies by subclass.
+     * Use with ENABLED_MASK when calling setFlags.
+     * {@hide}
+     */
+    static final int DISABLED = 0x00000020;
+
+   /**
+    * Mask for use with setFlags indicating bits used for indicating whether
+    * this view is enabled
+    * {@hide}
+    */
+    static final int ENABLED_MASK = 0x00000020;
+
+    /**
+     * This view won't draw. {@link #onDraw} won't be called and further
+     * optimizations
+     * will be performed. It is okay to have this flag set and a background.
+     * Use with DRAW_MASK when calling setFlags.
+     * {@hide}
+     */
+    static final int WILL_NOT_DRAW = 0x00000080;
+
+    /**
+     * Mask for use with setFlags indicating bits used for indicating whether
+     * this view is will draw
+     * {@hide}
+     */
+    static final int DRAW_MASK = 0x00000080;
+
+    /**
+     * <p>This view doesn't show scrollbars.</p>
+     * {@hide}
+     */
+    static final int SCROLLBARS_NONE = 0x00000000;
+
+    /**
+     * <p>This view shows horizontal scrollbars.</p>
+     * {@hide}
+     */
+    static final int SCROLLBARS_HORIZONTAL = 0x00000100;
+
+    /**
+     * <p>This view shows vertical scrollbars.</p>
+     * {@hide}
+     */
+    static final int SCROLLBARS_VERTICAL = 0x00000200;
+
+    /**
+     * <p>Mask for use with setFlags indicating bits used for indicating which
+     * scrollbars are enabled.</p>
+     * {@hide}
+     */
+    static final int SCROLLBARS_MASK = 0x00000300;
+
+    // note 0x00000400 and 0x00000800 are now available for next flags...
+
+    /**
+     * <p>This view doesn't show fading edges.</p>
+     * {@hide}
+     */
+    static final int FADING_EDGE_NONE = 0x00000000;
+
+    /**
+     * <p>This view shows horizontal fading edges.</p>
+     * {@hide}
+     */
+    static final int FADING_EDGE_HORIZONTAL = 0x00001000;
+
+    /**
+     * <p>This view shows vertical fading edges.</p>
+     * {@hide}
+     */
+    static final int FADING_EDGE_VERTICAL = 0x00002000;
+
+    /**
+     * <p>Mask for use with setFlags indicating bits used for indicating which
+     * fading edges are enabled.</p>
+     * {@hide}
+     */
+    static final int FADING_EDGE_MASK = 0x00003000;
+
+    /**
+     * <p>Indicates this view can be clicked. When clickable, a View reacts
+     * to clicks by notifying the OnClickListener.<p>
+     * {@hide}
+     */
+    static final int CLICKABLE = 0x00004000;
+
+    /**
+     * <p>Indicates this view is caching its drawing into a bitmap.</p>
+     * {@hide}
+     */
+    static final int DRAWING_CACHE_ENABLED = 0x00008000;
+
+    /**
+     * <p>Indicates that no icicle should be saved for this view.<p>
+     * {@hide}
+     */
+    static final int SAVE_DISABLED = 0x000010000;
+
+    /**
+     * <p>Mask for use with setFlags indicating bits used for the saveEnabled
+     * property.</p>
+     * {@hide}
+     */
+    static final int SAVE_DISABLED_MASK = 0x000010000;
+
+    /**
+     * <p>Indicates that no drawing cache should ever be created for this view.<p>
+     * {@hide}
+     */
+    static final int WILL_NOT_CACHE_DRAWING = 0x000020000;
+
+    /**
+     * <p>Indicates this view can take / keep focus when int touch mode.</p>
+     * {@hide}
+     */
+    static final int FOCUSABLE_IN_TOUCH_MODE = 0x00040000;
+
+    /**
+     * <p>Enables low quality mode for the drawing cache.</p>
+     */
+    public static final int DRAWING_CACHE_QUALITY_LOW = 0x00080000;
+
+    /**
+     * <p>Enables high quality mode for the drawing cache.</p>
+     */
+    public static final int DRAWING_CACHE_QUALITY_HIGH = 0x00100000;
+
+    /**
+     * <p>Enables automatic quality mode for the drawing cache.</p>
+     */
+    public static final int DRAWING_CACHE_QUALITY_AUTO = 0x00000000;
+
+    private static final int[] DRAWING_CACHE_QUALITY_FLAGS = {
+            DRAWING_CACHE_QUALITY_AUTO, DRAWING_CACHE_QUALITY_LOW, DRAWING_CACHE_QUALITY_HIGH
+    };
+
+    /**
+     * <p>Mask for use with setFlags indicating bits used for the cache
+     * quality property.</p>
+     * {@hide}
+     */
+    static final int DRAWING_CACHE_QUALITY_MASK = 0x00180000;
+
+    /**
+     * <p>
+     * Indicates this view can be long clicked. When long clickable, a View
+     * reacts to long clicks by notifying the OnLongClickListener or showing a
+     * context menu.
+     * </p>
+     * {@hide}
+     */
+    static final int LONG_CLICKABLE = 0x00200000;
+
+    /**
+     * <p>Indicates that this view gets its drawable states from its direct parent
+     * and ignores its original internal states.</p>
+     *
+     * @hide
+     */
+    static final int DUPLICATE_PARENT_STATE = 0x00400000;
+
+    /**
+     * The scrollbar style to display the scrollbars inside the content area,
+     * without increasing the padding. The scrollbars will be overlaid with
+     * translucency on the view's content.
+     */
+    public static final int SCROLLBARS_INSIDE_OVERLAY = 0;
+
+    /**
+     * The scrollbar style to display the scrollbars inside the padded area,
+     * increasing the padding of the view. The scrollbars will not overlap the
+     * content area of the view.
+     */
+    public static final int SCROLLBARS_INSIDE_INSET = 0x01000000;
+
+    /**
+     * The scrollbar style to display the scrollbars at the edge of the view,
+     * without increasing the padding. The scrollbars will be overlaid with
+     * translucency.
+     */
+    public static final int SCROLLBARS_OUTSIDE_OVERLAY = 0x02000000;
+
+    /**
+     * The scrollbar style to display the scrollbars at the edge of the view,
+     * increasing the padding of the view. The scrollbars will only overlap the
+     * background, if any.
+     */
+    public static final int SCROLLBARS_OUTSIDE_INSET = 0x03000000;
+
+    /**
+     * Mask to check if the scrollbar style is overlay or inset.
+     * {@hide}
+     */
+    static final int SCROLLBARS_INSET_MASK = 0x01000000;
+
+    /**
+     * Mask to check if the scrollbar style is inside or outside.
+     * {@hide}
+     */
+    static final int SCROLLBARS_OUTSIDE_MASK = 0x02000000;
+
+    /**
+     * Mask for scrollbar style.
+     * {@hide}
+     */
+    static final int SCROLLBARS_STYLE_MASK = 0x03000000;
+
+    /**
+     * View flag indicating that the screen should remain on while the
+     * window containing this view is visible to the user.  This effectively
+     * takes care of automatically setting the WindowManager's
+     * {@link WindowManager.LayoutParams#FLAG_KEEP_SCREEN_ON}.
+     */
+    public static final int KEEP_SCREEN_ON = 0x04000000;
+
+    /**
+     * View flag indicating whether this view should have sound effects enabled
+     * for events such as clicking and touching.
+     */
+    public static final int SOUND_EFFECTS_ENABLED = 0x08000000;
+
+    /**
+     * View flag indicating whether this view should have haptic feedback
+     * enabled for events such as long presses.
+     */
+    public static final int HAPTIC_FEEDBACK_ENABLED = 0x10000000;
+
+    /**
+     * Use with {@link #focusSearch}. Move focus to the previous selectable
+     * item.
+     */
+    public static final int FOCUS_BACKWARD = 0x00000001;
+
+    /**
+     * Use with {@link #focusSearch}. Move focus to the next selectable
+     * item.
+     */
+    public static final int FOCUS_FORWARD = 0x00000002;
+
+    /**
+     * Use with {@link #focusSearch}. Move focus to the left.
+     */
+    public static final int FOCUS_LEFT = 0x00000011;
+
+    /**
+     * Use with {@link #focusSearch}. Move focus up.
+     */
+    public static final int FOCUS_UP = 0x00000021;
+
+    /**
+     * Use with {@link #focusSearch}. Move focus to the right.
+     */
+    public static final int FOCUS_RIGHT = 0x00000042;
+
+    /**
+     * Use with {@link #focusSearch}. Move focus down.
+     */
+    public static final int FOCUS_DOWN = 0x00000082;
+
+    /**
+     * Base View state sets
+     */
+    // Singles
+    /**
+     * Indicates the view has no states set. States are used with
+     * {@link android.graphics.drawable.Drawable} to change the drawing of the
+     * view depending on its state.
+     *
+     * @see android.graphics.drawable.Drawable
+     * @see #getDrawableState()
+     */
+    protected static final int[] EMPTY_STATE_SET = {};
+    /**
+     * Indicates the view is enabled. States are used with
+     * {@link android.graphics.drawable.Drawable} to change the drawing of the
+     * view depending on its state.
+     *
+     * @see android.graphics.drawable.Drawable
+     * @see #getDrawableState()
+     */
+    protected static final int[] ENABLED_STATE_SET = {R.attr.state_enabled};
+    /**
+     * Indicates the view is focused. States are used with
+     * {@link android.graphics.drawable.Drawable} to change the drawing of the
+     * view depending on its state.
+     *
+     * @see android.graphics.drawable.Drawable
+     * @see #getDrawableState()
+     */
+    protected static final int[] FOCUSED_STATE_SET = {R.attr.state_focused};
+    /**
+     * Indicates the view is selected. States are used with
+     * {@link android.graphics.drawable.Drawable} to change the drawing of the
+     * view depending on its state.
+     *
+     * @see android.graphics.drawable.Drawable
+     * @see #getDrawableState()
+     */
+    protected static final int[] SELECTED_STATE_SET = {R.attr.state_selected};
+    /**
+     * Indicates the view is pressed. States are used with
+     * {@link android.graphics.drawable.Drawable} to change the drawing of the
+     * view depending on its state.
+     *
+     * @see android.graphics.drawable.Drawable
+     * @see #getDrawableState()
+     * @hide
+     */
+    protected static final int[] PRESSED_STATE_SET = {R.attr.state_pressed};
+    /**
+     * Indicates the view's window has focus. States are used with
+     * {@link android.graphics.drawable.Drawable} to change the drawing of the
+     * view depending on its state.
+     *
+     * @see android.graphics.drawable.Drawable
+     * @see #getDrawableState()
+     */
+    protected static final int[] WINDOW_FOCUSED_STATE_SET =
+            {R.attr.state_window_focused};
+    // Doubles
+    /**
+     * Indicates the view is enabled and has the focus.
+     *
+     * @see #ENABLED_STATE_SET
+     * @see #FOCUSED_STATE_SET
+     */
+    protected static final int[] ENABLED_FOCUSED_STATE_SET =
+            stateSetUnion(ENABLED_STATE_SET, FOCUSED_STATE_SET);
+    /**
+     * Indicates the view is enabled and selected.
+     *
+     * @see #ENABLED_STATE_SET
+     * @see #SELECTED_STATE_SET
+     */
+    protected static final int[] ENABLED_SELECTED_STATE_SET =
+            stateSetUnion(ENABLED_STATE_SET, SELECTED_STATE_SET);
+    /**
+     * Indicates the view is enabled and that its window has focus.
+     *
+     * @see #ENABLED_STATE_SET
+     * @see #WINDOW_FOCUSED_STATE_SET
+     */
+    protected static final int[] ENABLED_WINDOW_FOCUSED_STATE_SET =
+            stateSetUnion(ENABLED_STATE_SET, WINDOW_FOCUSED_STATE_SET);
+    /**
+     * Indicates the view is focused and selected.
+     *
+     * @see #FOCUSED_STATE_SET
+     * @see #SELECTED_STATE_SET
+     */
+    protected static final int[] FOCUSED_SELECTED_STATE_SET =
+            stateSetUnion(FOCUSED_STATE_SET, SELECTED_STATE_SET);
+    /**
+     * Indicates the view has the focus and that its window has the focus.
+     *
+     * @see #FOCUSED_STATE_SET
+     * @see #WINDOW_FOCUSED_STATE_SET
+     */
+    protected static final int[] FOCUSED_WINDOW_FOCUSED_STATE_SET =
+            stateSetUnion(FOCUSED_STATE_SET, WINDOW_FOCUSED_STATE_SET);
+    /**
+     * Indicates the view is selected and that its window has the focus.
+     *
+     * @see #SELECTED_STATE_SET
+     * @see #WINDOW_FOCUSED_STATE_SET
+     */
+    protected static final int[] SELECTED_WINDOW_FOCUSED_STATE_SET =
+            stateSetUnion(SELECTED_STATE_SET, WINDOW_FOCUSED_STATE_SET);
+    // Triples
+    /**
+     * Indicates the view is enabled, focused and selected.
+     *
+     * @see #ENABLED_STATE_SET
+     * @see #FOCUSED_STATE_SET
+     * @see #SELECTED_STATE_SET
+     */
+    protected static final int[] ENABLED_FOCUSED_SELECTED_STATE_SET =
+            stateSetUnion(ENABLED_FOCUSED_STATE_SET, SELECTED_STATE_SET);
+    /**
+     * Indicates the view is enabled, focused and its window has the focus.
+     *
+     * @see #ENABLED_STATE_SET
+     * @see #FOCUSED_STATE_SET
+     * @see #WINDOW_FOCUSED_STATE_SET
+     */
+    protected static final int[] ENABLED_FOCUSED_WINDOW_FOCUSED_STATE_SET =
+            stateSetUnion(ENABLED_FOCUSED_STATE_SET, WINDOW_FOCUSED_STATE_SET);
+    /**
+     * Indicates the view is enabled, selected and its window has the focus.
+     *
+     * @see #ENABLED_STATE_SET
+     * @see #SELECTED_STATE_SET
+     * @see #WINDOW_FOCUSED_STATE_SET
+     */
+    protected static final int[] ENABLED_SELECTED_WINDOW_FOCUSED_STATE_SET =
+            stateSetUnion(ENABLED_SELECTED_STATE_SET, WINDOW_FOCUSED_STATE_SET);
+    /**
+     * Indicates the view is focused, selected and its window has the focus.
+     *
+     * @see #FOCUSED_STATE_SET
+     * @see #SELECTED_STATE_SET
+     * @see #WINDOW_FOCUSED_STATE_SET
+     */
+    protected static final int[] FOCUSED_SELECTED_WINDOW_FOCUSED_STATE_SET =
+            stateSetUnion(FOCUSED_SELECTED_STATE_SET, WINDOW_FOCUSED_STATE_SET);
+    /**
+     * Indicates the view is enabled, focused, selected and its window
+     * has the focus.
+     *
+     * @see #ENABLED_STATE_SET
+     * @see #FOCUSED_STATE_SET
+     * @see #SELECTED_STATE_SET
+     * @see #WINDOW_FOCUSED_STATE_SET
+     */
+    protected static final int[] ENABLED_FOCUSED_SELECTED_WINDOW_FOCUSED_STATE_SET =
+            stateSetUnion(ENABLED_FOCUSED_SELECTED_STATE_SET,
+                          WINDOW_FOCUSED_STATE_SET);
+
+    /**
+     * Indicates the view is pressed and its window has the focus.
+     *
+     * @see #PRESSED_STATE_SET
+     * @see #WINDOW_FOCUSED_STATE_SET
+     */
+    protected static final int[] PRESSED_WINDOW_FOCUSED_STATE_SET =
+            stateSetUnion(PRESSED_STATE_SET, WINDOW_FOCUSED_STATE_SET);
+
+    /**
+     * Indicates the view is pressed and selected.
+     *
+     * @see #PRESSED_STATE_SET
+     * @see #SELECTED_STATE_SET
+     */
+    protected static final int[] PRESSED_SELECTED_STATE_SET =
+            stateSetUnion(PRESSED_STATE_SET, SELECTED_STATE_SET);
+
+    /**
+     * Indicates the view is pressed, selected and its window has the focus.
+     *
+     * @see #PRESSED_STATE_SET
+     * @see #SELECTED_STATE_SET
+     * @see #WINDOW_FOCUSED_STATE_SET
+     */
+    protected static final int[] PRESSED_SELECTED_WINDOW_FOCUSED_STATE_SET =
+            stateSetUnion(PRESSED_SELECTED_STATE_SET, WINDOW_FOCUSED_STATE_SET);
+
+    /**
+     * Indicates the view is pressed and focused.
+     *
+     * @see #PRESSED_STATE_SET
+     * @see #FOCUSED_STATE_SET
+     */
+    protected static final int[] PRESSED_FOCUSED_STATE_SET =
+            stateSetUnion(PRESSED_STATE_SET, FOCUSED_STATE_SET);
+
+    /**
+     * Indicates the view is pressed, focused and its window has the focus.
+     *
+     * @see #PRESSED_STATE_SET
+     * @see #FOCUSED_STATE_SET
+     * @see #WINDOW_FOCUSED_STATE_SET
+     */
+    protected static final int[] PRESSED_FOCUSED_WINDOW_FOCUSED_STATE_SET =
+            stateSetUnion(PRESSED_FOCUSED_STATE_SET, WINDOW_FOCUSED_STATE_SET);
+
+    /**
+     * Indicates the view is pressed, focused and selected.
+     *
+     * @see #PRESSED_STATE_SET
+     * @see #SELECTED_STATE_SET
+     * @see #FOCUSED_STATE_SET
+     */
+    protected static final int[] PRESSED_FOCUSED_SELECTED_STATE_SET =
+            stateSetUnion(PRESSED_FOCUSED_STATE_SET, SELECTED_STATE_SET);
+
+    /**
+     * Indicates the view is pressed, focused, selected and its window has the focus.
+     *
+     * @see #PRESSED_STATE_SET
+     * @see #FOCUSED_STATE_SET
+     * @see #SELECTED_STATE_SET
+     * @see #WINDOW_FOCUSED_STATE_SET
+     */
+    protected static final int[] PRESSED_FOCUSED_SELECTED_WINDOW_FOCUSED_STATE_SET =
+            stateSetUnion(PRESSED_FOCUSED_SELECTED_STATE_SET, WINDOW_FOCUSED_STATE_SET);
+
+    /**
+     * Indicates the view is pressed and enabled.
+     *
+     * @see #PRESSED_STATE_SET
+     * @see #ENABLED_STATE_SET
+     */
+    protected static final int[] PRESSED_ENABLED_STATE_SET =
+            stateSetUnion(PRESSED_STATE_SET, ENABLED_STATE_SET);
+
+    /**
+     * Indicates the view is pressed, enabled and its window has the focus.
+     *
+     * @see #PRESSED_STATE_SET
+     * @see #ENABLED_STATE_SET
+     * @see #WINDOW_FOCUSED_STATE_SET
+     */
+    protected static final int[] PRESSED_ENABLED_WINDOW_FOCUSED_STATE_SET =
+            stateSetUnion(PRESSED_ENABLED_STATE_SET, WINDOW_FOCUSED_STATE_SET);
+
+    /**
+     * Indicates the view is pressed, enabled and selected.
+     *
+     * @see #PRESSED_STATE_SET
+     * @see #ENABLED_STATE_SET
+     * @see #SELECTED_STATE_SET
+     */
+    protected static final int[] PRESSED_ENABLED_SELECTED_STATE_SET =
+            stateSetUnion(PRESSED_ENABLED_STATE_SET, SELECTED_STATE_SET);
+
+    /**
+     * Indicates the view is pressed, enabled, selected and its window has the
+     * focus.
+     *
+     * @see #PRESSED_STATE_SET
+     * @see #ENABLED_STATE_SET
+     * @see #SELECTED_STATE_SET
+     * @see #WINDOW_FOCUSED_STATE_SET
+     */
+    protected static final int[] PRESSED_ENABLED_SELECTED_WINDOW_FOCUSED_STATE_SET =
+            stateSetUnion(PRESSED_ENABLED_SELECTED_STATE_SET, WINDOW_FOCUSED_STATE_SET);
+
+    /**
+     * Indicates the view is pressed, enabled and focused.
+     *
+     * @see #PRESSED_STATE_SET
+     * @see #ENABLED_STATE_SET
+     * @see #FOCUSED_STATE_SET
+     */
+    protected static final int[] PRESSED_ENABLED_FOCUSED_STATE_SET =
+            stateSetUnion(PRESSED_ENABLED_STATE_SET, FOCUSED_STATE_SET);
+
+    /**
+     * Indicates the view is pressed, enabled, focused and its window has the
+     * focus.
+     *
+     * @see #PRESSED_STATE_SET
+     * @see #ENABLED_STATE_SET
+     * @see #FOCUSED_STATE_SET
+     * @see #WINDOW_FOCUSED_STATE_SET
+     */
+    protected static final int[] PRESSED_ENABLED_FOCUSED_WINDOW_FOCUSED_STATE_SET =
+            stateSetUnion(PRESSED_ENABLED_FOCUSED_STATE_SET, WINDOW_FOCUSED_STATE_SET);
+
+    /**
+     * Indicates the view is pressed, enabled, focused and selected.
+     *
+     * @see #PRESSED_STATE_SET
+     * @see #ENABLED_STATE_SET
+     * @see #SELECTED_STATE_SET
+     * @see #FOCUSED_STATE_SET
+     */
+    protected static final int[] PRESSED_ENABLED_FOCUSED_SELECTED_STATE_SET =
+            stateSetUnion(PRESSED_ENABLED_FOCUSED_STATE_SET, SELECTED_STATE_SET);
+
+    /**
+     * Indicates the view is pressed, enabled, focused, selected and its window
+     * has the focus.
+     *
+     * @see #PRESSED_STATE_SET
+     * @see #ENABLED_STATE_SET
+     * @see #SELECTED_STATE_SET
+     * @see #FOCUSED_STATE_SET
+     * @see #WINDOW_FOCUSED_STATE_SET
+     */
+    protected static final int[] PRESSED_ENABLED_FOCUSED_SELECTED_WINDOW_FOCUSED_STATE_SET =
+            stateSetUnion(PRESSED_ENABLED_FOCUSED_SELECTED_STATE_SET, WINDOW_FOCUSED_STATE_SET);
+
+    /**
+     * The order here is very important to {@link #getDrawableState()}
+     */
+    private static final int[][] VIEW_STATE_SETS = {
+        EMPTY_STATE_SET,                                           // 0 0 0 0 0
+        WINDOW_FOCUSED_STATE_SET,                                  // 0 0 0 0 1
+        SELECTED_STATE_SET,                                        // 0 0 0 1 0
+        SELECTED_WINDOW_FOCUSED_STATE_SET,                         // 0 0 0 1 1
+        FOCUSED_STATE_SET,                                         // 0 0 1 0 0
+        FOCUSED_WINDOW_FOCUSED_STATE_SET,                          // 0 0 1 0 1
+        FOCUSED_SELECTED_STATE_SET,                                // 0 0 1 1 0
+        FOCUSED_SELECTED_WINDOW_FOCUSED_STATE_SET,                 // 0 0 1 1 1
+        ENABLED_STATE_SET,                                         // 0 1 0 0 0
+        ENABLED_WINDOW_FOCUSED_STATE_SET,                          // 0 1 0 0 1
+        ENABLED_SELECTED_STATE_SET,                                // 0 1 0 1 0
+        ENABLED_SELECTED_WINDOW_FOCUSED_STATE_SET,                 // 0 1 0 1 1
+        ENABLED_FOCUSED_STATE_SET,                                 // 0 1 1 0 0
+        ENABLED_FOCUSED_WINDOW_FOCUSED_STATE_SET,                  // 0 1 1 0 1
+        ENABLED_FOCUSED_SELECTED_STATE_SET,                        // 0 1 1 1 0
+        ENABLED_FOCUSED_SELECTED_WINDOW_FOCUSED_STATE_SET,         // 0 1 1 1 1
+        PRESSED_STATE_SET,                                         // 1 0 0 0 0
+        PRESSED_WINDOW_FOCUSED_STATE_SET,                          // 1 0 0 0 1
+        PRESSED_SELECTED_STATE_SET,                                // 1 0 0 1 0
+        PRESSED_SELECTED_WINDOW_FOCUSED_STATE_SET,                 // 1 0 0 1 1
+        PRESSED_FOCUSED_STATE_SET,                                 // 1 0 1 0 0
+        PRESSED_FOCUSED_WINDOW_FOCUSED_STATE_SET,                  // 1 0 1 0 1
+        PRESSED_FOCUSED_SELECTED_STATE_SET,                        // 1 0 1 1 0
+        PRESSED_FOCUSED_SELECTED_WINDOW_FOCUSED_STATE_SET,         // 1 0 1 1 1
+        PRESSED_ENABLED_STATE_SET,                                 // 1 1 0 0 0
+        PRESSED_ENABLED_WINDOW_FOCUSED_STATE_SET,                  // 1 1 0 0 1
+        PRESSED_ENABLED_SELECTED_STATE_SET,                        // 1 1 0 1 0
+        PRESSED_ENABLED_SELECTED_WINDOW_FOCUSED_STATE_SET,         // 1 1 0 1 1
+        PRESSED_ENABLED_FOCUSED_STATE_SET,                         // 1 1 1 0 0
+        PRESSED_ENABLED_FOCUSED_WINDOW_FOCUSED_STATE_SET,          // 1 1 1 0 1
+        PRESSED_ENABLED_FOCUSED_SELECTED_STATE_SET,                // 1 1 1 1 0
+        PRESSED_ENABLED_FOCUSED_SELECTED_WINDOW_FOCUSED_STATE_SET, // 1 1 1 1 1
+    };
+
+    /**
+     * Used by views that contain lists of items. This state indicates that
+     * the view is showing the last item.
+     * @hide
+     */
+    protected static final int[] LAST_STATE_SET = {R.attr.state_last};
+    /**
+     * Used by views that contain lists of items. This state indicates that
+     * the view is showing the first item.
+     * @hide
+     */
+    protected static final int[] FIRST_STATE_SET = {R.attr.state_first};
+    /**
+     * Used by views that contain lists of items. This state indicates that
+     * the view is showing the middle item.
+     * @hide
+     */
+    protected static final int[] MIDDLE_STATE_SET = {R.attr.state_middle};
+    /**
+     * Used by views that contain lists of items. This state indicates that
+     * the view is showing only one item.
+     * @hide
+     */
+    protected static final int[] SINGLE_STATE_SET = {R.attr.state_single};
+    /**
+     * Used by views that contain lists of items. This state indicates that
+     * the view is pressed and showing the last item.
+     * @hide
+     */
+    protected static final int[] PRESSED_LAST_STATE_SET = {R.attr.state_last, R.attr.state_pressed};
+    /**
+     * Used by views that contain lists of items. This state indicates that
+     * the view is pressed and showing the first item.
+     * @hide
+     */
+    protected static final int[] PRESSED_FIRST_STATE_SET = {R.attr.state_first, R.attr.state_pressed};
+    /**
+     * Used by views that contain lists of items. This state indicates that
+     * the view is pressed and showing the middle item.
+     * @hide
+     */
+    protected static final int[] PRESSED_MIDDLE_STATE_SET = {R.attr.state_middle, R.attr.state_pressed};
+    /**
+     * Used by views that contain lists of items. This state indicates that
+     * the view is pressed and showing only one item.
+     * @hide
+     */
+    protected static final int[] PRESSED_SINGLE_STATE_SET = {R.attr.state_single, R.attr.state_pressed};
+
+    /**
+     * Temporary Rect currently for use in setBackground().  This will probably
+     * be extended in the future to hold our own class with more than just
+     * a Rect. :)
+     */
+    static final ThreadLocal<Rect> sThreadLocal = new ThreadLocal<Rect>();
+    
+    /**
+     * The animation currently associated with this view.
+     * @hide
+     */
+    protected Animation mCurrentAnimation = null;
+
+    /**
+     * Width as measured during measure pass.
+     * {@hide}
+     */
+    @ViewDebug.ExportedProperty
+    protected int mMeasuredWidth;
+
+    /**
+     * Height as measured during measure pass.
+     * {@hide}
+     */
+    @ViewDebug.ExportedProperty
+    protected int mMeasuredHeight;
+
+    /**
+     * The view's identifier.
+     * {@hide}
+     *
+     * @see #setId(int)
+     * @see #getId()
+     */
+    @ViewDebug.ExportedProperty(resolveId = true)
+    int mID = NO_ID;
+
+    /**
+     * The view's tag.
+     * {@hide}
+     *
+     * @see #setTag(Object)
+     * @see #getTag()
+     */
+    protected Object mTag;
+
+    // for mPrivateFlags:
+    /** {@hide} */
+    static final int WANTS_FOCUS                    = 0x00000001;
+    /** {@hide} */
+    static final int FOCUSED                        = 0x00000002;
+    /** {@hide} */
+    static final int SELECTED                       = 0x00000004;
+    /** {@hide} */
+    static final int IS_ROOT_NAMESPACE              = 0x00000008;
+    /** {@hide} */
+    static final int HAS_BOUNDS                     = 0x00000010;
+    /** {@hide} */
+    static final int DRAWN                          = 0x00000020;
+    /**
+     * When this flag is set, this view is running an animation on behalf of its
+     * children and should therefore not cancel invalidate requests, even if they
+     * lie outside of this view's bounds.
+     *
+     * {@hide}
+     */
+    static final int DRAW_ANIMATION                 = 0x00000040;
+    /** {@hide} */
+    static final int SKIP_DRAW                      = 0x00000080;
+    /** {@hide} */
+    static final int ONLY_DRAWS_BACKGROUND          = 0x00000100;
+    /** {@hide} */
+    static final int REQUEST_TRANSPARENT_REGIONS    = 0x00000200;
+    /** {@hide} */
+    static final int DRAWABLE_STATE_DIRTY           = 0x00000400;
+    /** {@hide} */
+    static final int MEASURED_DIMENSION_SET         = 0x00000800;
+    /** {@hide} */
+    static final int FORCE_LAYOUT                   = 0x00001000;
+
+    private static final int LAYOUT_REQUIRED        = 0x00002000;
+
+    private static final int PRESSED                = 0x00004000;
+
+    /** {@hide} */
+    static final int DRAWING_CACHE_VALID            = 0x00008000;
+    /**
+     * Flag used to indicate that this view should be drawn once more (and only once
+     * more) after its animation has completed.
+     * {@hide}
+     */
+    static final int ANIMATION_STARTED              = 0x00010000;
+
+    private static final int SAVE_STATE_CALLED      = 0x00020000;
+
+    /**
+     * Indicates that the View returned true when onSetAlpha() was called and that
+     * the alpha must be restored.
+     * {@hide}
+     */
+    static final int ALPHA_SET                      = 0x00040000;
+
+    /**
+     * Set by {@link #setScrollContainer(boolean)}.
+     */
+    static final int SCROLL_CONTAINER               = 0x00080000;
+
+    /**
+     * Set by {@link #setScrollContainer(boolean)}.
+     */
+    static final int SCROLL_CONTAINER_ADDED         = 0x00100000;
+
+    /**
+     * The parent this view is attached to.
+     * {@hide}
+     *
+     * @see #getParent()
+     */
+    protected ViewParent mParent;
+
+    /**
+     * {@hide}
+     */
+    AttachInfo mAttachInfo;
+
+    /**
+     * {@hide}
+     */
+    @ViewDebug.ExportedProperty
+    int mPrivateFlags;
+
+    /**
+     * Count of how many windows this view has been attached to.
+     */
+    int mWindowAttachCount;
+
+    /**
+     * The layout parameters associated with this view and used by the parent
+     * {@link android.view.ViewGroup} to determine how this view should be
+     * laid out.
+     * {@hide}
+     */
+    protected ViewGroup.LayoutParams mLayoutParams;
+
+    /**
+     * The view flags hold various views states.
+     * {@hide}
+     */
+    @ViewDebug.ExportedProperty
+    int mViewFlags;
+
+    /**
+     * The distance in pixels from the left edge of this view's parent
+     * to the left edge of this view.
+     * {@hide}
+     */
+    @ViewDebug.ExportedProperty
+    protected int mLeft;
+    /**
+     * The distance in pixels from the left edge of this view's parent
+     * to the right edge of this view.
+     * {@hide}
+     */
+    @ViewDebug.ExportedProperty
+    protected int mRight;
+    /**
+     * The distance in pixels from the top edge of this view's parent
+     * to the top edge of this view.
+     * {@hide}
+     */
+    @ViewDebug.ExportedProperty
+    protected int mTop;
+    /**
+     * The distance in pixels from the top edge of this view's parent
+     * to the bottom edge of this view.
+     * {@hide}
+     */
+    @ViewDebug.ExportedProperty
+    protected int mBottom;
+
+    /**
+     * The offset, in pixels, by which the content of this view is scrolled
+     * horizontally.
+     * {@hide}
+     */
+    @ViewDebug.ExportedProperty
+    protected int mScrollX;
+    /**
+     * The offset, in pixels, by which the content of this view is scrolled
+     * vertically.
+     * {@hide}
+     */
+    @ViewDebug.ExportedProperty
+    protected int mScrollY;
+
+    /**
+     * The left padding in pixels, that is the distance in pixels between the
+     * left edge of this view and the left edge of its content.
+     * {@hide}
+     */
+    @ViewDebug.ExportedProperty
+    protected int mPaddingLeft;
+    /**
+     * The right padding in pixels, that is the distance in pixels between the
+     * right edge of this view and the right edge of its content.
+     * {@hide}
+     */
+    @ViewDebug.ExportedProperty
+    protected int mPaddingRight;
+    /**
+     * The top padding in pixels, that is the distance in pixels between the
+     * top edge of this view and the top edge of its content.
+     * {@hide}
+     */
+    @ViewDebug.ExportedProperty
+    protected int mPaddingTop;
+    /**
+     * The bottom padding in pixels, that is the distance in pixels between the
+     * bottom edge of this view and the bottom edge of its content.
+     * {@hide}
+     */
+    @ViewDebug.ExportedProperty
+    protected int mPaddingBottom;
+
+    /**
+     * Cache the paddingRight set by the user to append to the scrollbar's size.
+     */
+    @ViewDebug.ExportedProperty
+    int mUserPaddingRight;
+
+    /**
+     * Cache the paddingBottom set by the user to append to the scrollbar's size.
+     */
+    @ViewDebug.ExportedProperty
+    int mUserPaddingBottom;
+
+    private int mOldWidthMeasureSpec = Integer.MIN_VALUE;
+    private int mOldHeightMeasureSpec = Integer.MIN_VALUE;
+
+    private Resources mResources = null;
+
+    private Drawable mBGDrawable;
+
+    private int mBackgroundResource;
+    private boolean mBackgroundSizeChanged;
+
+    /**
+     * Listener used to dispatch focus change events.
+     * This field should be made private, so it is hidden from the SDK.
+     * {@hide}
+     */
+    protected OnFocusChangeListener mOnFocusChangeListener;
+
+    /**
+     * Listener used to dispatch click events.
+     * This field should be made private, so it is hidden from the SDK.
+     * {@hide}
+     */
+    protected OnClickListener mOnClickListener;
+
+    /**
+     * Listener used to dispatch long click events.
+     * This field should be made private, so it is hidden from the SDK.
+     * {@hide}
+     */
+    protected OnLongClickListener mOnLongClickListener;
+
+    /**
+     * Listener used to build the context menu.
+     * This field should be made private, so it is hidden from the SDK.
+     * {@hide}
+     */
+    protected OnCreateContextMenuListener mOnCreateContextMenuListener;
+
+    private OnKeyListener mOnKeyListener;
+
+    private OnTouchListener mOnTouchListener;
+
+    /**
+     * The application environment this view lives in.
+     * This field should be made private, so it is hidden from the SDK.
+     * {@hide}
+     */
+    protected Context mContext;
+
+    private ScrollabilityCache mScrollCache;
+
+    private int[] mDrawableState = null;
+
+    private SoftReference<Bitmap> mDrawingCache;
+
+    /**
+     * When this view has focus and the next focus is {@link #FOCUS_LEFT},
+     * the user may specify which view to go to next.
+     */
+    private int mNextFocusLeftId = View.NO_ID;
+
+    /**
+     * When this view has focus and the next focus is {@link #FOCUS_RIGHT},
+     * the user may specify which view to go to next.
+     */
+    private int mNextFocusRightId = View.NO_ID;
+
+    /**
+     * When this view has focus and the next focus is {@link #FOCUS_UP},
+     * the user may specify which view to go to next.
+     */
+    private int mNextFocusUpId = View.NO_ID;
+
+    /**
+     * When this view has focus and the next focus is {@link #FOCUS_DOWN},
+     * the user may specify which view to go to next.
+     */
+    private int mNextFocusDownId = View.NO_ID;
+
+    private CheckForLongPress mPendingCheckForLongPress;
+    private UnsetPressedState mUnsetPressedState;
+
+    /**
+     * Whether the long press's action has been invoked.  The tap's action is invoked on the
+     * up event while a long press is invoked as soon as the long press duration is reached, so
+     * a long press could be performed before the tap is checked, in which case the tap's action
+     * should not be invoked.
+     */
+    private boolean mHasPerformedLongPress;
+
+    /**
+     * The minimum height of the view. We'll try our best to have the height
+     * of this view to at least this amount.
+     */
+    @ViewDebug.ExportedProperty
+    private int mMinHeight;
+
+    /**
+     * The minimum width of the view. We'll try our best to have the width
+     * of this view to at least this amount.
+     */
+    @ViewDebug.ExportedProperty
+    private int mMinWidth;
+
+    /**
+     * The delegate to handle touch events that are physically in this view
+     * but should be handled by another view.
+     */
+    private TouchDelegate mTouchDelegate = null;
+
+    /**
+     * Solid color to use as a background when creating the drawing cache. Enables
+     * the cache to use 16 bit bitmaps instead of 32 bit.
+     */
+    private int mDrawingCacheBackgroundColor = 0;
+
+    /**
+     * Special tree observer used when mAttachInfo is null.
+     */
+    private ViewTreeObserver mFloatingTreeObserver;
+
+    // Used for debug only
+    static long sInstanceCount = 0;
+
+    /**
+     * Simple constructor to use when creating a view from code.
+     *
+     * @param context The Context the view is running in, through which it can
+     *        access the current theme, resources, etc.
+     */
+    public View(Context context) {
+        mContext = context;
+        mResources = context != null ? context.getResources() : null;
+        mViewFlags = SOUND_EFFECTS_ENABLED|HAPTIC_FEEDBACK_ENABLED;
+        ++sInstanceCount;
+    }
+
+    /**
+     * Constructor that is called when inflating a view from XML. This is called
+     * when a view is being constructed from an XML file, supplying attributes
+     * that were specified in the XML file. This version uses a default style of
+     * 0, so the only attribute values applied are those in the Context's Theme
+     * and the given AttributeSet.
+     *
+     * <p>
+     * The method onFinishInflate() will be called after all children have been
+     * added.
+     *
+     * @param context The Context the view is running in, through which it can
+     *        access the current theme, resources, etc.
+     * @param attrs The attributes of the XML tag that is inflating the view.
+     * @see #View(Context, AttributeSet, int)
+     */
+    public View(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    /**
+     * Perform inflation from XML and apply a class-specific base style. This
+     * constructor of View allows subclasses to use their own base style when
+     * they are inflating. For example, a Button class's constructor would call
+     * this version of the super class constructor and supply
+     * <code>R.attr.buttonStyle</code> for <var>defStyle</var>; this allows
+     * the theme's button style to modify all of the base view attributes (in
+     * particular its background) as well as the Button class's attributes.
+     *
+     * @param context The Context the view is running in, through which it can
+     *        access the current theme, resources, etc.
+     * @param attrs The attributes of the XML tag that is inflating the view.
+     * @param defStyle The default style to apply to this view. If 0, no style
+     *        will be applied (beyond what is included in the theme). This may
+     *        either be an attribute resource, whose value will be retrieved
+     *        from the current theme, or an explicit style resource.
+     * @see #View(Context, AttributeSet)
+     */
+    public View(Context context, AttributeSet attrs, int defStyle) {
+        this(context);
+
+        TypedArray a = context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.View,
+                defStyle, 0);
+
+        Drawable background = null;
+
+        int leftPadding = -1;
+        int topPadding = -1;
+        int rightPadding = -1;
+        int bottomPadding = -1;
+
+        int padding = -1;
+
+        int viewFlagValues = 0;
+        int viewFlagMasks = 0;
+
+        boolean setScrollContainer = false;
+        
+        int x = 0;
+        int y = 0;
+
+        int scrollbarStyle = SCROLLBARS_INSIDE_OVERLAY;
+
+        final int N = a.getIndexCount();
+        for (int i = 0; i < N; i++) {
+            int attr = a.getIndex(i);
+            switch (attr) {
+                case com.android.internal.R.styleable.View_background:
+                    background = a.getDrawable(attr);
+                    break;
+                case com.android.internal.R.styleable.View_padding:
+                    padding = a.getDimensionPixelSize(attr, -1);
+                    break;
+                 case com.android.internal.R.styleable.View_paddingLeft:
+                    leftPadding = a.getDimensionPixelSize(attr, -1);
+                    break;
+                case com.android.internal.R.styleable.View_paddingTop:
+                    topPadding = a.getDimensionPixelSize(attr, -1);
+                    break;
+                case com.android.internal.R.styleable.View_paddingRight:
+                    rightPadding = a.getDimensionPixelSize(attr, -1);
+                    break;
+                case com.android.internal.R.styleable.View_paddingBottom:
+                    bottomPadding = a.getDimensionPixelSize(attr, -1);
+                    break;
+                case com.android.internal.R.styleable.View_scrollX:
+                    x = a.getDimensionPixelOffset(attr, 0);
+                    break;
+                case com.android.internal.R.styleable.View_scrollY:
+                    y = a.getDimensionPixelOffset(attr, 0);
+                    break;
+                case com.android.internal.R.styleable.View_id:
+                    mID = a.getResourceId(attr, NO_ID);
+                    break;
+                case com.android.internal.R.styleable.View_tag:
+                    mTag = a.getText(attr);
+                    break;
+                case com.android.internal.R.styleable.View_fitsSystemWindows:
+                    if (a.getBoolean(attr, false)) {
+                        viewFlagValues |= FITS_SYSTEM_WINDOWS;
+                        viewFlagMasks |= FITS_SYSTEM_WINDOWS;
+                    }
+                    break;
+                case com.android.internal.R.styleable.View_focusable:
+                    if (a.getBoolean(attr, false)) {
+                        viewFlagValues |= FOCUSABLE;
+                        viewFlagMasks |= FOCUSABLE_MASK;
+                    }
+                    break;
+                case com.android.internal.R.styleable.View_focusableInTouchMode:
+                    if (a.getBoolean(attr, false)) {
+                        viewFlagValues |= FOCUSABLE_IN_TOUCH_MODE | FOCUSABLE;
+                        viewFlagMasks |= FOCUSABLE_IN_TOUCH_MODE | FOCUSABLE_MASK;
+                    }
+                    break;
+                case com.android.internal.R.styleable.View_clickable:
+                    if (a.getBoolean(attr, false)) {
+                        viewFlagValues |= CLICKABLE;
+                        viewFlagMasks |= CLICKABLE;
+                    }
+                    break;
+                case com.android.internal.R.styleable.View_longClickable:
+                    if (a.getBoolean(attr, false)) {
+                        viewFlagValues |= LONG_CLICKABLE;
+                        viewFlagMasks |= LONG_CLICKABLE;
+                    }
+                    break;
+                case com.android.internal.R.styleable.View_saveEnabled:
+                    if (!a.getBoolean(attr, true)) {
+                        viewFlagValues |= SAVE_DISABLED;
+                        viewFlagMasks |= SAVE_DISABLED_MASK;
+                    }
+                    break;
+                case com.android.internal.R.styleable.View_duplicateParentState:
+                    if (a.getBoolean(attr, false)) {
+                        viewFlagValues |= DUPLICATE_PARENT_STATE;
+                        viewFlagMasks |= DUPLICATE_PARENT_STATE;
+                    }
+                    break;
+                case com.android.internal.R.styleable.View_visibility:
+                    final int visibility = a.getInt(attr, 0);
+                    if (visibility != 0) {
+                        viewFlagValues |= VISIBILITY_FLAGS[visibility];
+                        viewFlagMasks |= VISIBILITY_MASK;
+                    }
+                    break;
+                case com.android.internal.R.styleable.View_drawingCacheQuality:
+                    final int cacheQuality = a.getInt(attr, 0);
+                    if (cacheQuality != 0) {
+                        viewFlagValues |= DRAWING_CACHE_QUALITY_FLAGS[cacheQuality];
+                        viewFlagMasks |= DRAWING_CACHE_QUALITY_MASK;
+                    }
+                    break;
+                case com.android.internal.R.styleable.View_soundEffectsEnabled:
+                    if (!a.getBoolean(attr, true)) {
+                        viewFlagValues &= ~SOUND_EFFECTS_ENABLED;
+                        viewFlagMasks |= SOUND_EFFECTS_ENABLED;
+                    }
+                case com.android.internal.R.styleable.View_hapticFeedbackEnabled:
+                    if (!a.getBoolean(attr, true)) {
+                        viewFlagValues &= ~HAPTIC_FEEDBACK_ENABLED;
+                        viewFlagMasks |= HAPTIC_FEEDBACK_ENABLED;
+                    }
+                case R.styleable.View_scrollbars:
+                    final int scrollbars = a.getInt(attr, SCROLLBARS_NONE);
+                    if (scrollbars != SCROLLBARS_NONE) {
+                        viewFlagValues |= scrollbars;
+                        viewFlagMasks |= SCROLLBARS_MASK;
+                        initializeScrollbars(a);
+                    }
+                    break;
+                case R.styleable.View_fadingEdge:
+                    final int fadingEdge = a.getInt(attr, FADING_EDGE_NONE);
+                    if (fadingEdge != FADING_EDGE_NONE) {
+                        viewFlagValues |= fadingEdge;
+                        viewFlagMasks |= FADING_EDGE_MASK;
+                        initializeFadingEdge(a);
+                    }
+                    break;
+                case R.styleable.View_scrollbarStyle:
+                    scrollbarStyle = a.getInt(attr, SCROLLBARS_INSIDE_OVERLAY);
+                    if (scrollbarStyle != SCROLLBARS_INSIDE_OVERLAY) {
+                        viewFlagValues |= scrollbarStyle & SCROLLBARS_STYLE_MASK;
+                        viewFlagMasks |= SCROLLBARS_STYLE_MASK;
+                    }
+                    break;
+                case R.styleable.View_isScrollContainer:
+                    setScrollContainer = true;
+                    if (a.getBoolean(attr, false)) {
+                        setScrollContainer(true);
+                    }
+                    break;
+                case com.android.internal.R.styleable.View_keepScreenOn:
+                    if (a.getBoolean(attr, false)) {
+                        viewFlagValues |= KEEP_SCREEN_ON;
+                        viewFlagMasks |= KEEP_SCREEN_ON;
+                    }
+                    break;
+                case R.styleable.View_nextFocusLeft:
+                    mNextFocusLeftId = a.getResourceId(attr, View.NO_ID);
+                    break;
+                case R.styleable.View_nextFocusRight:
+                    mNextFocusRightId = a.getResourceId(attr, View.NO_ID);
+                    break;
+                case R.styleable.View_nextFocusUp:
+                    mNextFocusUpId = a.getResourceId(attr, View.NO_ID);
+                    break;
+                case R.styleable.View_nextFocusDown:
+                    mNextFocusDownId = a.getResourceId(attr, View.NO_ID);
+                    break;
+                case R.styleable.View_minWidth:
+                    mMinWidth = a.getDimensionPixelSize(attr, 0);
+                    break;
+                case R.styleable.View_minHeight:
+                    mMinHeight = a.getDimensionPixelSize(attr, 0);
+                    break;
+            }
+        }
+
+        if (background != null) {
+            setBackgroundDrawable(background);
+        }
+
+        if (padding >= 0) {
+            leftPadding = padding;
+            topPadding = padding;
+            rightPadding = padding;
+            bottomPadding = padding;
+        }
+
+        // If the user specified the padding (either with android:padding or
+        // android:paddingLeft/Top/Right/Bottom), use this padding, otherwise
+        // use the default padding or the padding from the background drawable
+        // (stored at this point in mPadding*)
+        setPadding(leftPadding >= 0 ? leftPadding : mPaddingLeft,
+                topPadding >= 0 ? topPadding : mPaddingTop,
+                rightPadding >= 0 ? rightPadding : mPaddingRight,
+                bottomPadding >= 0 ? bottomPadding : mPaddingBottom);
+
+        if (viewFlagMasks != 0) {
+            setFlags(viewFlagValues, viewFlagMasks);
+        }
+
+        // Needs to be called after mViewFlags is set
+        if (scrollbarStyle != SCROLLBARS_INSIDE_OVERLAY) {
+            recomputePadding();
+        }
+
+        if (x != 0 || y != 0) {
+            scrollTo(x, y);
+        }
+
+        if (!setScrollContainer && (viewFlagValues&SCROLLBARS_VERTICAL) != 0) {
+            setScrollContainer(true);
+        }
+  
+        a.recycle();
+    }
+
+    /**
+     * Non-public constructor for use in testing
+     */
+    View() {
+    }
+
+    @Override
+    protected void finalize() throws Throwable {
+        super.finalize();
+        --sInstanceCount;
+    }
+
+    /**
+     * <p>
+     * Initializes the fading edges from a given set of styled attributes. This
+     * method should be called by subclasses that need fading edges and when an
+     * instance of these subclasses is created programmatically rather than
+     * being inflated from XML. This method is automatically called when the XML
+     * is inflated.
+     * </p>
+     *
+     * @param a the styled attributes set to initialize the fading edges from
+     */
+    protected void initializeFadingEdge(TypedArray a) {
+        initScrollCache();
+
+        mScrollCache.fadingEdgeLength = a.getDimensionPixelSize(
+                R.styleable.View_fadingEdgeLength,
+                ViewConfiguration.get(mContext).getScaledFadingEdgeLength());
+    }
+
+    /**
+     * Returns the size of the vertical faded edges used to indicate that more
+     * content in this view is visible.
+     *
+     * @return The size in pixels of the vertical faded edge or 0 if vertical
+     *         faded edges are not enabled for this view.
+     * @attr ref android.R.styleable#View_fadingEdgeLength
+     */
+    public int getVerticalFadingEdgeLength() {
+        if (isVerticalFadingEdgeEnabled()) {
+            ScrollabilityCache cache = mScrollCache;
+            if (cache != null) {
+                return cache.fadingEdgeLength;
+            }
+        }
+        return 0;
+    }
+
+    /**
+     * Set the size of the faded edge used to indicate that more content in this
+     * view is available.  Will not change whether the fading edge is enabled; use
+     * {@link #setVerticalFadingEdgeEnabled} or {@link #setHorizontalFadingEdgeEnabled}
+     * to enable the fading edge for the vertical or horizontal fading edges.
+     *
+     * @param length The size in pixels of the faded edge used to indicate that more
+     *        content in this view is visible.
+     */
+    public void setFadingEdgeLength(int length) {
+        initScrollCache();
+        mScrollCache.fadingEdgeLength = length;
+    }
+
+    /**
+     * Returns the size of the horizontal faded edges used to indicate that more
+     * content in this view is visible.
+     *
+     * @return The size in pixels of the horizontal faded edge or 0 if horizontal
+     *         faded edges are not enabled for this view.
+     * @attr ref android.R.styleable#View_fadingEdgeLength
+     */
+    public int getHorizontalFadingEdgeLength() {
+        if (isHorizontalFadingEdgeEnabled()) {
+            ScrollabilityCache cache = mScrollCache;
+            if (cache != null) {
+                return cache.fadingEdgeLength;
+            }
+        }
+        return 0;
+    }
+
+    /**
+     * Returns the width of the vertical scrollbar.
+     *
+     * @return The width in pixels of the vertical scrollbar or 0 if there
+     *         is no vertical scrollbar.
+     */
+    public int getVerticalScrollbarWidth() {
+        ScrollabilityCache cache = mScrollCache;
+        if (cache != null) {
+            ScrollBarDrawable scrollBar = cache.scrollBar;
+            if (scrollBar != null) {
+                int size = scrollBar.getSize(true);
+                if (size <= 0) {
+                    size = cache.scrollBarSize;
+                }
+                return size;
+            }
+            return 0;
+        }
+        return 0;
+    }
+
+    /**
+     * Returns the height of the horizontal scrollbar.
+     *
+     * @return The height in pixels of the horizontal scrollbar or 0 if
+     *         there is no horizontal scrollbar.
+     */
+    protected int getHorizontalScrollbarHeight() {
+        ScrollabilityCache cache = mScrollCache;
+        if (cache != null) {
+            ScrollBarDrawable scrollBar = cache.scrollBar;
+            if (scrollBar != null) {
+                int size = scrollBar.getSize(false);
+                if (size <= 0) {
+                    size = cache.scrollBarSize;
+                }
+                return size;
+            }
+            return 0;
+        }
+        return 0;
+    }
+
+    /**
+     * <p>
+     * Initializes the scrollbars from a given set of styled attributes. This
+     * method should be called by subclasses that need scrollbars and when an
+     * instance of these subclasses is created programmatically rather than
+     * being inflated from XML. This method is automatically called when the XML
+     * is inflated.
+     * </p>
+     *
+     * @param a the styled attributes set to initialize the scrollbars from
+     */
+    protected void initializeScrollbars(TypedArray a) {
+        initScrollCache();
+
+        if (mScrollCache.scrollBar == null) {
+            mScrollCache.scrollBar = new ScrollBarDrawable();
+        }
+
+        final ScrollabilityCache scrollabilityCache = mScrollCache;
+
+        scrollabilityCache.scrollBarSize = a.getDimensionPixelSize(
+                com.android.internal.R.styleable.View_scrollbarSize,
+                ViewConfiguration.get(mContext).getScaledScrollBarSize());
+
+        Drawable track = a.getDrawable(R.styleable.View_scrollbarTrackHorizontal);
+        scrollabilityCache.scrollBar.setHorizontalTrackDrawable(track);
+
+        Drawable thumb = a.getDrawable(R.styleable.View_scrollbarThumbHorizontal);
+        if (thumb != null) {
+            scrollabilityCache.scrollBar.setHorizontalThumbDrawable(thumb);
+        }
+
+        boolean alwaysDraw = a.getBoolean(R.styleable.View_scrollbarAlwaysDrawHorizontalTrack,
+                false);
+        if (alwaysDraw) {
+            scrollabilityCache.scrollBar.setAlwaysDrawHorizontalTrack(true);
+        }
+
+        track = a.getDrawable(R.styleable.View_scrollbarTrackVertical);
+        scrollabilityCache.scrollBar.setVerticalTrackDrawable(track);
+
+        thumb = a.getDrawable(R.styleable.View_scrollbarThumbVertical);
+        if (thumb != null) {
+            scrollabilityCache.scrollBar.setVerticalThumbDrawable(thumb);
+        }
+
+        alwaysDraw = a.getBoolean(R.styleable.View_scrollbarAlwaysDrawVerticalTrack,
+                false);
+        if (alwaysDraw) {
+            scrollabilityCache.scrollBar.setAlwaysDrawVerticalTrack(true);
+        }
+
+        // Re-apply user/background padding so that scrollbar(s) get added
+        recomputePadding();
+    }
+
+    /**
+     * <p>
+     * Initalizes the scrollability cache if necessary.
+     * </p>
+     */
+    private void initScrollCache() {
+        if (mScrollCache == null) {
+            mScrollCache = new ScrollabilityCache(ViewConfiguration.get(mContext));
+        }
+    }
+
+    /**
+     * Register a callback to be invoked when focus of this view changed.
+     *
+     * @param l The callback that will run.
+     */
+    public void setOnFocusChangeListener(OnFocusChangeListener l) {
+        mOnFocusChangeListener = l;
+    }
+
+    /**
+     * Returns the focus-change callback registered for this view.
+     *
+     * @return The callback, or null if one is not registered.
+     */
+    public OnFocusChangeListener getOnFocusChangeListener() {
+        return mOnFocusChangeListener;
+    }
+
+    /**
+     * Register a callback to be invoked when this view is clicked. If this view is not
+     * clickable, it becomes clickable.
+     *
+     * @param l The callback that will run
+     *
+     * @see #setClickable(boolean)
+     */
+    public void setOnClickListener(OnClickListener l) {
+        if (!isClickable()) {
+            setClickable(true);
+        }
+        mOnClickListener = l;
+    }
+
+    /**
+     * Register a callback to be invoked when this view is clicked and held. If this view is not
+     * long clickable, it becomes long clickable.
+     *
+     * @param l The callback that will run
+     *
+     * @see #setLongClickable(boolean)
+     */
+    public void setOnLongClickListener(OnLongClickListener l) {
+        if (!isLongClickable()) {
+            setLongClickable(true);
+        }
+        mOnLongClickListener = l;
+    }
+
+    /**
+     * Register a callback to be invoked when the context menu for this view is
+     * being built. If this view is not long clickable, it becomes long clickable.
+     *
+     * @param l The callback that will run
+     *
+     */
+    public void setOnCreateContextMenuListener(OnCreateContextMenuListener l) {
+        if (!isLongClickable()) {
+            setLongClickable(true);
+        }
+        mOnCreateContextMenuListener = l;
+    }
+
+    /**
+     * Call this view's OnClickListener, if it is defined.
+     *
+     * @return True there was an assigned OnClickListener that was called, false
+     *         otherwise is returned.
+     */
+    public boolean performClick() {
+        if (mOnClickListener != null) {
+            playSoundEffect(SoundEffectConstants.CLICK);
+            mOnClickListener.onClick(this);
+            return true;
+        }
+
+        return false;
+    }
+
+    /**
+     * Call this view's OnLongClickListener, if it is defined. Invokes the context menu
+     * if the OnLongClickListener did not consume the event.
+     *
+     * @return True there was an assigned OnLongClickListener that was called, false
+     *         otherwise is returned.
+     */
+    public boolean performLongClick() {
+        boolean handled = false;
+        if (mOnLongClickListener != null) {
+            handled = mOnLongClickListener.onLongClick(View.this);
+        }
+        if (!handled) {
+            handled = showContextMenu();
+        }
+        if (handled) {
+            performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
+        }
+        return handled;
+    }
+
+    /**
+     * Bring up the context menu for this view.
+     *
+     * @return Whether a context menu was displayed.
+     */
+    public boolean showContextMenu() {
+        return getParent().showContextMenuForChild(this);
+    }
+
+    /**
+     * Register a callback to be invoked when a key is pressed in this view.
+     * @param l the key listener to attach to this view
+     */
+    public void setOnKeyListener(OnKeyListener l) {
+        mOnKeyListener = l;
+    }
+
+    /**
+     * Register a callback to be invoked when a touch event is sent to this view.
+     * @param l the touch listener to attach to this view
+     */
+    public void setOnTouchListener(OnTouchListener l) {
+        mOnTouchListener = l;
+    }
+
+    /**
+     * Give this view focus. This will cause {@link #onFocusChanged} to be called.
+     *
+     * Note: this does not check whether this {@link View} should get focus, it just
+     * gives it focus no matter what.  It should only be called internally by framework
+     * code that knows what it is doing, namely {@link #requestFocus(int, Rect)}.
+     *
+     * @param direction values are View.FOCUS_UP, View.FOCUS_DOWN,
+     *        View.FOCUS_LEFT or View.FOCUS_RIGHT. This is the direction which
+     *        focus moved when requestFocus() is called. It may not always
+     *        apply, in which case use the default View.FOCUS_DOWN.
+     * @param previouslyFocusedRect The rectangle of the view that had focus
+     *        prior in this View's coordinate system.
+     */
+    void handleFocusGainInternal(int direction, Rect previouslyFocusedRect) {
+        if (DBG) {
+            System.out.println(this + " requestFocus()");
+        }
+
+        if ((mPrivateFlags & FOCUSED) == 0) {
+            mPrivateFlags |= FOCUSED;
+
+            if (mParent != null) {
+                mParent.requestChildFocus(this, this);
+            }
+
+            onFocusChanged(true, direction, previouslyFocusedRect);
+            refreshDrawableState();
+        }
+    }
+
+    /**
+     * Request that a rectangle of this view be visible on the screen,
+     * scrolling if necessary just enough.
+     *
+     * <p>A View should call this if it maintains some notion of which part
+     * of its content is interesting.  For example, a text editing view
+     * should call this when its cursor moves.
+     *
+     * @param rectangle The rectangle.
+     * @return Whether any parent scrolled.
+     */
+    public boolean requestRectangleOnScreen(Rect rectangle) {
+        return requestRectangleOnScreen(rectangle, false);
+    }
+
+    /**
+     * Request that a rectangle of this view be visible on the screen,
+     * scrolling if necessary just enough.
+     *
+     * <p>A View should call this if it maintains some notion of which part
+     * of its content is interesting.  For example, a text editing view
+     * should call this when its cursor moves.
+     *
+     * <p>When <code>immediate</code> is set to true, scrolling will not be
+     * animated.
+     *
+     * @param rectangle The rectangle.
+     * @param immediate True to forbid animated scrolling, false otherwise
+     * @return Whether any parent scrolled.
+     */
+    public boolean requestRectangleOnScreen(Rect rectangle, boolean immediate) {
+        View child = this;
+        ViewParent parent = mParent;
+        boolean scrolled = false;
+        while (parent != null) {
+            scrolled |= parent.requestChildRectangleOnScreen(child,
+                    rectangle, immediate);
+
+            // offset rect so next call has the rectangle in the
+            // coordinate system of its direct child.
+            rectangle.offset(child.getLeft(), child.getTop());
+            rectangle.offset(-child.getScrollX(), -child.getScrollY());
+
+            if (!(parent instanceof View)) {
+                break;
+            }
+            
+            child = (View) parent;
+            parent = child.getParent();
+        }
+        return scrolled;
+    }
+
+    /**
+     * Called when this view wants to give up focus. This will cause
+     * {@link #onFocusChanged} to be called.
+     */
+    public void clearFocus() {
+        if (DBG) {
+            System.out.println(this + " clearFocus()");
+        }
+
+        if ((mPrivateFlags & FOCUSED) != 0) {
+            mPrivateFlags &= ~FOCUSED;
+
+            if (mParent != null) {
+                mParent.clearChildFocus(this);
+            }
+
+            onFocusChanged(false, 0, null);
+            refreshDrawableState();
+        }
+    }
+
+    /**
+     * Called to clear the focus of a view that is about to be removed.
+     * Doesn't call clearChildFocus, which prevents this view from taking
+     * focus again before it has been removed from the parent
+     */
+    void clearFocusForRemoval() {
+        if ((mPrivateFlags & FOCUSED) != 0) {
+            mPrivateFlags &= ~FOCUSED;
+
+            onFocusChanged(false, 0, null);
+            refreshDrawableState();
+        }
+    }
+
+    /**
+     * Called internally by the view system when a new view is getting focus.
+     * This is what clears the old focus.
+     */
+    void unFocus() {
+        if (DBG) {
+            System.out.println(this + " unFocus()");
+        }
+
+        if ((mPrivateFlags & FOCUSED) != 0) {
+            mPrivateFlags &= ~FOCUSED;
+
+            onFocusChanged(false, 0, null);
+            refreshDrawableState();
+        }
+    }
+
+    /**
+     * Returns true if this view has focus iteself, or is the ancestor of the
+     * view that has focus.
+     *
+     * @return True if this view has or contains focus, false otherwise.
+     */
+    @ViewDebug.ExportedProperty
+    public boolean hasFocus() {
+        return (mPrivateFlags & FOCUSED) != 0;
+    }
+
+    /**
+     * Returns true if this view is focusable or if it contains a reachable View
+     * for which {@link #hasFocusable()} returns true. A "reachable hasFocusable()"
+     * is a View whose parents do not block descendants focus.
+     *
+     * Only {@link #VISIBLE} views are considered focusable.
+     *
+     * @return True if the view is focusable or if the view contains a focusable
+     *         View, false otherwise.
+     *
+     * @see ViewGroup#FOCUS_BLOCK_DESCENDANTS
+     */
+    public boolean hasFocusable() {
+        return (mViewFlags & VISIBILITY_MASK) == VISIBLE && isFocusable();
+    }
+
+    /**
+     * Called by the view system when the focus state of this view changes.
+     * When the focus change event is caused by directional navigation, direction
+     * and previouslyFocusedRect provide insight into where the focus is coming from.
+     * When overriding, be sure to call up through to the super class so that
+     * the standard focus handling will occur.
+     * 
+     * @param gainFocus True if the View has focus; false otherwise.
+     * @param direction The direction focus has moved when requestFocus()
+     *                  is called to give this view focus. Values are
+     *                  View.FOCUS_UP, View.FOCUS_DOWN, View.FOCUS_LEFT or
+     *                  View.FOCUS_RIGHT. It may not always apply, in which
+     *                  case use the default.
+     * @param previouslyFocusedRect The rectangle, in this view's coordinate
+     *        system, of the previously focused view.  If applicable, this will be
+     *        passed in as finer grained information about where the focus is coming
+     *        from (in addition to direction).  Will be <code>null</code> otherwise.
+     */
+    protected void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) {
+        InputMethodManager imm = InputMethodManager.peekInstance();
+        if (!gainFocus) {
+            if (isPressed()) {
+                setPressed(false);
+            }
+            if (imm != null && mAttachInfo != null
+                    && mAttachInfo.mHasWindowFocus) {
+                imm.focusOut(this);
+            }
+        } else if (imm != null && mAttachInfo != null
+                && mAttachInfo.mHasWindowFocus) {
+            imm.focusIn(this);
+        }
+        
+        invalidate();
+        if (mOnFocusChangeListener != null) {
+            mOnFocusChangeListener.onFocusChange(this, gainFocus);
+        }
+    }
+
+    /**
+     * Returns true if this view has focus
+     *
+     * @return True if this view has focus, false otherwise.
+     */
+    @ViewDebug.ExportedProperty
+    public boolean isFocused() {
+        return (mPrivateFlags & FOCUSED) != 0;
+    }
+
+    /**
+     * Find the view in the hierarchy rooted at this view that currently has
+     * focus.
+     *
+     * @return The view that currently has focus, or null if no focused view can
+     *         be found.
+     */
+    public View findFocus() {
+        return (mPrivateFlags & FOCUSED) != 0 ? this : null;
+    }
+
+    /**
+     * Change whether this view is one of the set of scrollable containers in
+     * its window.  This will be used to determine whether the window can
+     * resize or must pan when a soft input area is open -- scrollable
+     * containers allow the window to use resize mode since the container
+     * will appropriately shrink.
+     */
+    public void setScrollContainer(boolean isScrollContainer) {
+        if (isScrollContainer) {
+            if (mAttachInfo != null && (mPrivateFlags&SCROLL_CONTAINER_ADDED) == 0) {
+                mAttachInfo.mScrollContainers.add(this);
+                mPrivateFlags |= SCROLL_CONTAINER_ADDED;
+            }
+            mPrivateFlags |= SCROLL_CONTAINER;
+        } else {
+            if ((mPrivateFlags&SCROLL_CONTAINER_ADDED) != 0) {
+                mAttachInfo.mScrollContainers.remove(this);
+            }
+            mPrivateFlags &= ~(SCROLL_CONTAINER|SCROLL_CONTAINER_ADDED);
+        }
+    }
+
+    /**
+     * Returns the quality of the drawing cache.
+     *
+     * @return One of {@link #DRAWING_CACHE_QUALITY_AUTO},
+     *         {@link #DRAWING_CACHE_QUALITY_LOW}, or {@link #DRAWING_CACHE_QUALITY_HIGH}
+     *
+     * @see #setDrawingCacheQuality(int)
+     * @see #setDrawingCacheEnabled(boolean)
+     * @see #isDrawingCacheEnabled()
+     *
+     * @attr ref android.R.styleable#View_drawingCacheQuality
+     */
+    public int getDrawingCacheQuality() {
+        return mViewFlags & DRAWING_CACHE_QUALITY_MASK;
+    }
+
+    /**
+     * Set the drawing cache quality of this view. This value is used only when the
+     * drawing cache is enabled
+     *
+     * @param quality One of {@link #DRAWING_CACHE_QUALITY_AUTO},
+     *        {@link #DRAWING_CACHE_QUALITY_LOW}, or {@link #DRAWING_CACHE_QUALITY_HIGH}
+     *
+     * @see #getDrawingCacheQuality()
+     * @see #setDrawingCacheEnabled(boolean)
+     * @see #isDrawingCacheEnabled()
+     *
+     * @attr ref android.R.styleable#View_drawingCacheQuality
+     */
+    public void setDrawingCacheQuality(int quality) {
+        setFlags(quality, DRAWING_CACHE_QUALITY_MASK);
+    }
+
+    /**
+     * Returns whether the screen should remain on, corresponding to the current
+     * value of {@link #KEEP_SCREEN_ON}.
+     *
+     * @return Returns true if {@link #KEEP_SCREEN_ON} is set.
+     *
+     * @see #setKeepScreenOn(boolean)
+     *
+     * @attr ref android.R.styleable#View_keepScreenOn
+     */
+    public boolean getKeepScreenOn() {
+        return (mViewFlags & KEEP_SCREEN_ON) != 0;
+    }
+
+    /**
+     * Controls whether the screen should remain on, modifying the
+     * value of {@link #KEEP_SCREEN_ON}.
+     *
+     * @param keepScreenOn Supply true to set {@link #KEEP_SCREEN_ON}.
+     *
+     * @see #getKeepScreenOn()
+     *
+     * @attr ref android.R.styleable#View_keepScreenOn
+     */
+    public void setKeepScreenOn(boolean keepScreenOn) {
+        setFlags(keepScreenOn ? KEEP_SCREEN_ON : 0, KEEP_SCREEN_ON);
+    }
+
+    /**
+     * @return The user specified next focus ID.
+     *
+     * @attr ref android.R.styleable#View_nextFocusLeft
+     */
+    public int getNextFocusLeftId() {
+        return mNextFocusLeftId;
+    }
+
+    /**
+     * Set the id of the view to use for the next focus
+     *
+     * @param nextFocusLeftId
+     *
+     * @attr ref android.R.styleable#View_nextFocusLeft
+     */
+    public void setNextFocusLeftId(int nextFocusLeftId) {
+        mNextFocusLeftId = nextFocusLeftId;
+    }
+
+    /**
+     * @return The user specified next focus ID.
+     *
+     * @attr ref android.R.styleable#View_nextFocusRight
+     */
+    public int getNextFocusRightId() {
+        return mNextFocusRightId;
+    }
+
+    /**
+     * Set the id of the view to use for the next focus
+     *
+     * @param nextFocusRightId
+     *
+     * @attr ref android.R.styleable#View_nextFocusRight
+     */
+    public void setNextFocusRightId(int nextFocusRightId) {
+        mNextFocusRightId = nextFocusRightId;
+    }
+
+    /**
+     * @return The user specified next focus ID.
+     *
+     * @attr ref android.R.styleable#View_nextFocusUp
+     */
+    public int getNextFocusUpId() {
+        return mNextFocusUpId;
+    }
+
+    /**
+     * Set the id of the view to use for the next focus
+     *
+     * @param nextFocusUpId
+     *
+     * @attr ref android.R.styleable#View_nextFocusUp
+     */
+    public void setNextFocusUpId(int nextFocusUpId) {
+        mNextFocusUpId = nextFocusUpId;
+    }
+
+    /**
+     * @return The user specified next focus ID.
+     *
+     * @attr ref android.R.styleable#View_nextFocusDown
+     */
+    public int getNextFocusDownId() {
+        return mNextFocusDownId;
+    }
+
+    /**
+     * Set the id of the view to use for the next focus
+     *
+     * @param nextFocusDownId
+     *
+     * @attr ref android.R.styleable#View_nextFocusDown
+     */
+    public void setNextFocusDownId(int nextFocusDownId) {
+        mNextFocusDownId = nextFocusDownId;
+    }
+
+    /**
+     * Returns the visibility of this view and all of its ancestors
+     *
+     * @return True if this view and all of its ancestors are {@link #VISIBLE}
+     */
+    public boolean isShown() {
+        View current = this;
+        //noinspection ConstantConditions
+        do {
+            if ((current.mViewFlags & VISIBILITY_MASK) != VISIBLE) {
+                return false;
+            }
+            ViewParent parent = current.mParent;
+            if (parent == null) {
+                return false; // We are not attached to the view root
+            }
+            if (!(parent instanceof View)) {
+                return true;
+            }
+            current = (View) parent;
+        } while (current != null);
+
+        return false;
+    }
+
+    /**
+     * Apply the insets for system windows to this view, if the FITS_SYSTEM_WINDOWS flag
+     * is set
+     *
+     * @param insets Insets for system windows
+     *
+     * @return True if this view applied the insets, false otherwise
+     */
+    protected boolean fitSystemWindows(Rect insets) {
+        if ((mViewFlags & FITS_SYSTEM_WINDOWS) == FITS_SYSTEM_WINDOWS) {
+            mPaddingLeft = insets.left;
+            mPaddingTop = insets.top;
+            mPaddingRight = insets.right;
+            mPaddingBottom = insets.bottom;
+            requestLayout();
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Returns the visibility status for this view.
+     *
+     * @return One of {@link #VISIBLE}, {@link #INVISIBLE}, or {@link #GONE}.
+     * @attr ref android.R.styleable#View_visibility
+     */
+    @ViewDebug.ExportedProperty(mapping = {
+        @ViewDebug.IntToString(from = 0, to = "VISIBLE"),
+        @ViewDebug.IntToString(from = 4, to = "INVISIBLE"),
+        @ViewDebug.IntToString(from = 8, to = "GONE")
+    })
+    public int getVisibility() {
+        return mViewFlags & VISIBILITY_MASK;
+    }
+
+    /**
+     * Set the enabled state of this view.
+     *
+     * @param visibility One of {@link #VISIBLE}, {@link #INVISIBLE}, or {@link #GONE}.
+     * @attr ref android.R.styleable#View_visibility
+     */
+    @RemotableViewMethod
+    public void setVisibility(int visibility) {
+        setFlags(visibility, VISIBILITY_MASK);
+        if (mBGDrawable != null) mBGDrawable.setVisible(visibility == VISIBLE, false);
+    }
+
+    /**
+     * Returns the enabled status for this view. The interpretation of the
+     * enabled state varies by subclass.
+     *
+     * @return True if this view is enabled, false otherwise.
+     */
+    @ViewDebug.ExportedProperty
+    public boolean isEnabled() {
+        return (mViewFlags & ENABLED_MASK) == ENABLED;
+    }
+
+    /**
+     * Set the enabled state of this view. The interpretation of the enabled
+     * state varies by subclass.
+     *
+     * @param enabled True if this view is enabled, false otherwise.
+     */
+    public void setEnabled(boolean enabled) {
+        setFlags(enabled ? ENABLED : DISABLED, ENABLED_MASK);
+
+        /*
+         * The View most likely has to change its appearance, so refresh
+         * the drawable state.
+         */
+        refreshDrawableState();
+
+        // Invalidate too, since the default behavior for views is to be
+        // be drawn at 50% alpha rather than to change the drawable.
+        invalidate();
+    }
+
+    /**
+     * Set whether this view can receive the focus.
+     *
+     * Setting this to false will also ensure that this view is not focusable
+     * in touch mode.
+     *
+     * @param focusable If true, this view can receive the focus.
+     *
+     * @see #setFocusableInTouchMode(boolean)
+     * @attr ref android.R.styleable#View_focusable
+     */
+    public void setFocusable(boolean focusable) {
+        if (!focusable) {
+            setFlags(0, FOCUSABLE_IN_TOUCH_MODE);
+        }
+        setFlags(focusable ? FOCUSABLE : NOT_FOCUSABLE, FOCUSABLE_MASK);
+    }
+
+    /**
+     * Set whether this view can receive focus while in touch mode.
+     *
+     * Setting this to true will also ensure that this view is focusable.
+     *
+     * @param focusableInTouchMode If true, this view can receive the focus while
+     *   in touch mode.
+     *
+     * @see #setFocusable(boolean)
+     * @attr ref android.R.styleable#View_focusableInTouchMode
+     */
+    public void setFocusableInTouchMode(boolean focusableInTouchMode) {
+        // Focusable in touch mode should always be set before the focusable flag
+        // otherwise, setting the focusable flag will trigger a focusableViewAvailable()
+        // which, in touch mode, will not successfully request focus on this view
+        // because the focusable in touch mode flag is not set
+        setFlags(focusableInTouchMode ? FOCUSABLE_IN_TOUCH_MODE : 0, FOCUSABLE_IN_TOUCH_MODE);
+        if (focusableInTouchMode) {
+            setFlags(FOCUSABLE, FOCUSABLE_MASK);
+        }
+    }
+
+    /**
+     * Set whether this view should have sound effects enabled for events such as
+     * clicking and touching.
+     *
+     * <p>You may wish to disable sound effects for a view if you already play sounds,
+     * for instance, a dial key that plays dtmf tones.
+     *
+     * @param soundEffectsEnabled whether sound effects are enabled for this view.
+     * @see #isSoundEffectsEnabled()
+     * @see #playSoundEffect(int)
+     * @attr ref android.R.styleable#View_soundEffectsEnabled
+     */
+    public void setSoundEffectsEnabled(boolean soundEffectsEnabled) {
+        setFlags(soundEffectsEnabled ? SOUND_EFFECTS_ENABLED: 0, SOUND_EFFECTS_ENABLED);
+    }
+
+    /**
+     * @return whether this view should have sound effects enabled for events such as
+     *     clicking and touching.
+     *
+     * @see #setSoundEffectsEnabled(boolean)
+     * @see #playSoundEffect(int)
+     * @attr ref android.R.styleable#View_soundEffectsEnabled
+     */
+    @ViewDebug.ExportedProperty
+    public boolean isSoundEffectsEnabled() {
+        return SOUND_EFFECTS_ENABLED == (mViewFlags & SOUND_EFFECTS_ENABLED);
+    }
+
+    /**
+     * Set whether this view should have haptic feedback for events such as
+     * long presses.
+     *
+     * <p>You may wish to disable haptic feedback if your view already controls
+     * its own haptic feedback.
+     *
+     * @param hapticFeedbackEnabled whether haptic feedback enabled for this view.
+     * @see #isHapticFeedbackEnabled()
+     * @see #performHapticFeedback(int)
+     * @attr ref android.R.styleable#View_hapticFeedbackEnabled
+     */
+    public void setHapticFeedbackEnabled(boolean hapticFeedbackEnabled) {
+        setFlags(hapticFeedbackEnabled ? HAPTIC_FEEDBACK_ENABLED: 0, HAPTIC_FEEDBACK_ENABLED);
+    }
+
+    /**
+     * @return whether this view should have haptic feedback enabled for events
+     * long presses.
+     *
+     * @see #setHapticFeedbackEnabled(boolean)
+     * @see #performHapticFeedback(int)
+     * @attr ref android.R.styleable#View_hapticFeedbackEnabled
+     */
+    @ViewDebug.ExportedProperty
+    public boolean isHapticFeedbackEnabled() {
+        return HAPTIC_FEEDBACK_ENABLED == (mViewFlags & HAPTIC_FEEDBACK_ENABLED);
+    }
+
+    /**
+     * If this view doesn't do any drawing on its own, set this flag to
+     * allow further optimizations. By default, this flag is not set on
+     * View, but could be set on some View subclasses such as ViewGroup.
+     *
+     * Typically, if you override {@link #onDraw} you should clear this flag.
+     *
+     * @param willNotDraw whether or not this View draw on its own
+     */
+    public void setWillNotDraw(boolean willNotDraw) {
+        setFlags(willNotDraw ? WILL_NOT_DRAW : 0, DRAW_MASK);
+    }
+
+    /**
+     * Returns whether or not this View draws on its own.
+     *
+     * @return true if this view has nothing to draw, false otherwise
+     */
+    @ViewDebug.ExportedProperty
+    public boolean willNotDraw() {
+        return (mViewFlags & DRAW_MASK) == WILL_NOT_DRAW;
+    }
+
+    /**
+     * When a View's drawing cache is enabled, drawing is redirected to an
+     * offscreen bitmap. Some views, like an ImageView, must be able to
+     * bypass this mechanism if they already draw a single bitmap, to avoid
+     * unnecessary usage of the memory.
+     *
+     * @param willNotCacheDrawing true if this view does not cache its
+     *        drawing, false otherwise
+     */
+    public void setWillNotCacheDrawing(boolean willNotCacheDrawing) {
+        setFlags(willNotCacheDrawing ? WILL_NOT_CACHE_DRAWING : 0, WILL_NOT_CACHE_DRAWING);
+    }
+
+    /**
+     * Returns whether or not this View can cache its drawing or not.
+     *
+     * @return true if this view does not cache its drawing, false otherwise
+     */
+    @ViewDebug.ExportedProperty
+    public boolean willNotCacheDrawing() {
+        return (mViewFlags & WILL_NOT_CACHE_DRAWING) == WILL_NOT_CACHE_DRAWING;
+    }
+
+    /**
+     * Indicates whether this view reacts to click events or not.
+     *
+     * @return true if the view is clickable, false otherwise
+     *
+     * @see #setClickable(boolean)
+     * @attr ref android.R.styleable#View_clickable
+     */
+    @ViewDebug.ExportedProperty
+    public boolean isClickable() {
+        return (mViewFlags & CLICKABLE) == CLICKABLE;
+    }
+
+    /**
+     * Enables or disables click events for this view. When a view
+     * is clickable it will change its state to "pressed" on every click.
+     * Subclasses should set the view clickable to visually react to
+     * user's clicks.
+     *
+     * @param clickable true to make the view clickable, false otherwise
+     *
+     * @see #isClickable()
+     * @attr ref android.R.styleable#View_clickable
+     */
+    public void setClickable(boolean clickable) {
+        setFlags(clickable ? CLICKABLE : 0, CLICKABLE);
+    }
+
+    /**
+     * Indicates whether this view reacts to long click events or not.
+     *
+     * @return true if the view is long clickable, false otherwise
+     *
+     * @see #setLongClickable(boolean)
+     * @attr ref android.R.styleable#View_longClickable
+     */
+    public boolean isLongClickable() {
+        return (mViewFlags & LONG_CLICKABLE) == LONG_CLICKABLE;
+    }
+
+    /**
+     * Enables or disables long click events for this view. When a view is long
+     * clickable it reacts to the user holding down the button for a longer
+     * duration than a tap. This event can either launch the listener or a
+     * context menu.
+     *
+     * @param longClickable true to make the view long clickable, false otherwise
+     * @see #isLongClickable()
+     * @attr ref android.R.styleable#View_longClickable
+     */
+    public void setLongClickable(boolean longClickable) {
+        setFlags(longClickable ? LONG_CLICKABLE : 0, LONG_CLICKABLE);
+    }
+
+    /**
+     * Sets the pressed that for this view.
+     *
+     * @see #isClickable()
+     * @see #setClickable(boolean)
+     *
+     * @param pressed Pass true to set the View's internal state to "pressed", or false to reverts
+     *        the View's internal state from a previously set "pressed" state.
+     */
+    public void setPressed(boolean pressed) {
+        if (pressed) {
+            mPrivateFlags |= PRESSED;
+        } else {
+            mPrivateFlags &= ~PRESSED;
+        }
+        refreshDrawableState();
+        dispatchSetPressed(pressed);
+    }
+
+    /**
+     * Dispatch setPressed to all of this View's children.
+     *
+     * @see #setPressed(boolean)
+     *
+     * @param pressed The new pressed state
+     */
+    protected void dispatchSetPressed(boolean pressed) {
+    }
+
+    /**
+     * Indicates whether the view is currently in pressed state. Unless
+     * {@link #setPressed(boolean)} is explicitly called, only clickable views can enter
+     * the pressed state.
+     *
+     * @see #setPressed
+     * @see #isClickable()
+     * @see #setClickable(boolean)
+     *
+     * @return true if the view is currently pressed, false otherwise
+     */
+    public boolean isPressed() {
+        return (mPrivateFlags & PRESSED) == PRESSED;
+    }
+
+    /**
+     * Indicates whether this view will save its state (that is,
+     * whether its {@link #onSaveInstanceState} method will be called).
+     *
+     * @return Returns true if the view state saving is enabled, else false.
+     *
+     * @see #setSaveEnabled(boolean)
+     * @attr ref android.R.styleable#View_saveEnabled
+     */
+    public boolean isSaveEnabled() {
+        return (mViewFlags & SAVE_DISABLED_MASK) != SAVE_DISABLED;
+    }
+
+    /**
+     * Controls whether the saving of this view's state is
+     * enabled (that is, whether its {@link #onSaveInstanceState} method
+     * will be called).  Note that even if freezing is enabled, the
+     * view still must have an id assigned to it (via {@link #setId setId()})
+     * for its state to be saved.  This flag can only disable the
+     * saving of this view; any child views may still have their state saved.
+     *
+     * @param enabled Set to false to <em>disable</em> state saving, or true
+     * (the default) to allow it.
+     *
+     * @see #isSaveEnabled()
+     * @see #setId(int)
+     * @see #onSaveInstanceState()
+     * @attr ref android.R.styleable#View_saveEnabled
+     */
+    public void setSaveEnabled(boolean enabled) {
+        setFlags(enabled ? 0 : SAVE_DISABLED, SAVE_DISABLED_MASK);
+    }
+
+
+    /**
+     * Returns whether this View is able to take focus.
+     *
+     * @return True if this view can take focus, or false otherwise.
+     * @attr ref android.R.styleable#View_focusable
+     */
+    @ViewDebug.ExportedProperty
+    public final boolean isFocusable() {
+        return FOCUSABLE == (mViewFlags & FOCUSABLE_MASK);
+    }
+
+    /**
+     * When a view is focusable, it may not want to take focus when in touch mode.
+     * For example, a button would like focus when the user is navigating via a D-pad
+     * so that the user can click on it, but once the user starts touching the screen,
+     * the button shouldn't take focus
+     * @return Whether the view is focusable in touch mode.
+     * @attr ref android.R.styleable#View_focusableInTouchMode
+     */
+    @ViewDebug.ExportedProperty
+    public final boolean isFocusableInTouchMode() {
+        return FOCUSABLE_IN_TOUCH_MODE == (mViewFlags & FOCUSABLE_IN_TOUCH_MODE);
+    }
+
+    /**
+     * Find the nearest view in the specified direction that can take focus.
+     * This does not actually give focus to that view.
+     *
+     * @param direction One of FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, and FOCUS_RIGHT
+     *
+     * @return The nearest focusable in the specified direction, or null if none
+     *         can be found.
+     */
+    public View focusSearch(int direction) {
+        if (mParent != null) {
+            return mParent.focusSearch(this, direction);
+        } else {
+            return null;
+        }
+    }
+
+    /**
+     * This method is the last chance for the focused view and its ancestors to
+     * respond to an arrow key. This is called when the focused view did not
+     * consume the key internally, nor could the view system find a new view in
+     * the requested direction to give focus to.
+     *
+     * @param focused The currently focused view.
+     * @param direction The direction focus wants to move. One of FOCUS_UP,
+     *        FOCUS_DOWN, FOCUS_LEFT, and FOCUS_RIGHT.
+     * @return True if the this view consumed this unhandled move.
+     */
+    public boolean dispatchUnhandledMove(View focused, int direction) {
+        return false;
+    }
+
+    /**
+     * If a user manually specified the next view id for a particular direction,
+     * use the root to look up the view.  Once a view is found, it is cached
+     * for future lookups.
+     * @param root The root view of the hierarchy containing this view.
+     * @param direction One of FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, and FOCUS_RIGHT
+     * @return The user specified next view, or null if there is none.
+     */
+    View findUserSetNextFocus(View root, int direction) {
+        switch (direction) {
+            case FOCUS_LEFT:
+                if (mNextFocusLeftId == View.NO_ID) return null;
+                return findViewShouldExist(root, mNextFocusLeftId);
+            case FOCUS_RIGHT:
+                if (mNextFocusRightId == View.NO_ID) return null;
+                return findViewShouldExist(root, mNextFocusRightId);
+            case FOCUS_UP:
+                if (mNextFocusUpId == View.NO_ID) return null;
+                return findViewShouldExist(root, mNextFocusUpId);
+            case FOCUS_DOWN:
+                if (mNextFocusDownId == View.NO_ID) return null;
+                return findViewShouldExist(root, mNextFocusDownId);
+        }
+        return null;
+    }
+
+    private static View findViewShouldExist(View root, int childViewId) {
+        View result = root.findViewById(childViewId);
+        if (result == null) {
+            Log.w(VIEW_LOG_TAG, "couldn't find next focus view specified "
+                    + "by user for id " + childViewId);
+        }
+        return result;
+    }
+
+    /**
+     * Find and return all focusable views that are descendants of this view,
+     * possibly including this view if it is focusable itself.
+     *
+     * @param direction The direction of the focus
+     * @return A list of focusable views
+     */
+    public ArrayList<View> getFocusables(int direction) {
+        ArrayList<View> result = new ArrayList<View>(24);
+        addFocusables(result, direction);
+        return result;
+    }
+
+    /**
+     * Add any focusable views that are descendants of this view (possibly
+     * including this view if it is focusable itself) to views.  If we are in touch mode,
+     * only add views that are also focusable in touch mode.
+     *
+     * @param views Focusable views found so far
+     * @param direction The direction of the focus
+     */
+    public void addFocusables(ArrayList<View> views, int direction) {
+        if (!isFocusable()) return;
+
+        if (isInTouchMode() && !isFocusableInTouchMode()) return;
+
+        views.add(this);
+    }
+
+    /**
+     * Find and return all touchable views that are descendants of this view,
+     * possibly including this view if it is touchable itself.
+     *
+     * @return A list of touchable views
+     */
+    public ArrayList<View> getTouchables() {
+        ArrayList<View> result = new ArrayList<View>();
+        addTouchables(result);
+        return result;
+    }
+
+    /**
+     * Add any touchable views that are descendants of this view (possibly
+     * including this view if it is touchable itself) to views.
+     *
+     * @param views Touchable views found so far
+     */
+    public void addTouchables(ArrayList<View> views) {
+        final int viewFlags = mViewFlags;
+
+        if (((viewFlags & CLICKABLE) == CLICKABLE || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
+                && (viewFlags & ENABLED_MASK) == ENABLED) {
+            views.add(this);
+        }
+    }
+
+    /**
+     * Call this to try to give focus to a specific view or to one of its
+     * descendants.
+     *
+     * A view will not actually take focus if it is not focusable ({@link #isFocusable} returns false),
+     * or if it is focusable and it is not focusable in touch mode ({@link #isFocusableInTouchMode})
+     * while the device is in touch mode.
+     *
+     * See also {@link #focusSearch}, which is what you call to say that you
+     * have focus, and you want your parent to look for the next one.
+     *
+     * This is equivalent to calling {@link #requestFocus(int, Rect)} with arguments
+     * {@link #FOCUS_DOWN} and <code>null</code>.
+     *
+     * @return Whether this view or one of its descendants actually took focus.
+     */
+    public final boolean requestFocus() {
+        return requestFocus(View.FOCUS_DOWN);
+    }
+
+
+    /**
+     * Call this to try to give focus to a specific view or to one of its
+     * descendants and give it a hint about what direction focus is heading.
+     *
+     * A view will not actually take focus if it is not focusable ({@link #isFocusable} returns false),
+     * or if it is focusable and it is not focusable in touch mode ({@link #isFocusableInTouchMode})
+     * while the device is in touch mode.
+     *
+     * See also {@link #focusSearch}, which is what you call to say that you
+     * have focus, and you want your parent to look for the next one.
+     *
+     * This is equivalent to calling {@link #requestFocus(int, Rect)} with
+     * <code>null</code> set for the previously focused rectangle.
+     *
+     * @param direction One of FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, and FOCUS_RIGHT
+     * @return Whether this view or one of its descendants actually took focus.
+     */
+    public final boolean requestFocus(int direction) {
+        return requestFocus(direction, null);
+    }
+
+    /**
+     * Call this to try to give focus to a specific view or to one of its descendants
+     * and give it hints about the direction and a specific rectangle that the focus
+     * is coming from.  The rectangle can help give larger views a finer grained hint
+     * about where focus is coming from, and therefore, where to show selection, or
+     * forward focus change internally.
+     *
+     * A view will not actually take focus if it is not focusable ({@link #isFocusable} returns false),
+     * or if it is focusable and it is not focusable in touch mode ({@link #isFocusableInTouchMode})
+     * while the device is in touch mode.
+     *
+     * A View will not take focus if it is not visible.
+     *
+     * A View will not take focus if one of its parents has {@link android.view.ViewGroup#getDescendantFocusability()}
+     * equal to {@link ViewGroup#FOCUS_BLOCK_DESCENDANTS}.
+     *
+     * See also {@link #focusSearch}, which is what you call to say that you
+     * have focus, and you want your parent to look for the next one.
+     *
+     * You may wish to override this method if your custom {@link View} has an internal
+     * {@link View} that it wishes to forward the request to.
+     *
+     * @param direction One of FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, and FOCUS_RIGHT
+     * @param previouslyFocusedRect The rectangle (in this View's coordinate system)
+     *        to give a finer grained hint about where focus is coming from.  May be null
+     *        if there is no hint.
+     * @return Whether this view or one of its descendants actually took focus.
+     */
+    public boolean requestFocus(int direction, Rect previouslyFocusedRect) {
+        // need to be focusable
+        if ((mViewFlags & FOCUSABLE_MASK) != FOCUSABLE ||
+                (mViewFlags & VISIBILITY_MASK) != VISIBLE) {
+            return false;
+        }
+
+        // need to be focusable in touch mode if in touch mode
+        if (isInTouchMode() &&
+                (FOCUSABLE_IN_TOUCH_MODE != (mViewFlags & FOCUSABLE_IN_TOUCH_MODE))) {
+            return false;
+        }
+
+        // need to not have any parents blocking us
+        if (hasAncestorThatBlocksDescendantFocus()) {
+            return false;
+        }
+
+        handleFocusGainInternal(direction, previouslyFocusedRect);
+        return true;
+    }
+
+    /**
+     * Call this to try to give focus to a specific view or to one of its descendants. This is a
+     * special variant of {@link #requestFocus() } that will allow views that are not focuable in
+     * touch mode to request focus when they are touched.
+     *
+     * @return Whether this view or one of its descendants actually took focus.
+     *
+     * @see #isInTouchMode()
+     *
+     */
+    public final boolean requestFocusFromTouch() {
+        // Leave touch mode if we need to
+        if (isInTouchMode()) {
+            View root = getRootView();
+            if (root != null) {
+               ViewRoot viewRoot = (ViewRoot)root.getParent();
+               if (viewRoot != null) {
+                   viewRoot.ensureTouchMode(false);
+               }
+            }
+        }
+        return requestFocus(View.FOCUS_DOWN);
+    }
+
+    /**
+     * @return Whether any ancestor of this view blocks descendant focus.
+     */
+    private boolean hasAncestorThatBlocksDescendantFocus() {
+        ViewParent ancestor = mParent;
+        while (ancestor instanceof ViewGroup) {
+            final ViewGroup vgAncestor = (ViewGroup) ancestor;
+            if (vgAncestor.getDescendantFocusability() == ViewGroup.FOCUS_BLOCK_DESCENDANTS) {
+                return true;
+            } else {
+                ancestor = vgAncestor.getParent();
+            }
+        }
+        return false;
+    }
+
+    /**
+     * This is called when a container is going to temporarily detach a child
+     * that currently has focus, with
+     * {@link ViewGroup#detachViewFromParent(View) ViewGroup.detachViewFromParent}.
+     * It will either be followed by {@link #onFinishTemporaryDetach()} or
+     * {@link #onDetachedFromWindow()} when the container is done.  Generally
+     * this is currently only done ListView for a view with focus.
+     */
+    public void onStartTemporaryDetach() {
+    }
+    
+    /**
+     * Called after {@link #onStartTemporaryDetach} when the container is done
+     * changing the view.
+     */
+    public void onFinishTemporaryDetach() {
+    }
+    
+    /**
+     * capture information of this view for later analysis: developement only
+     * check dynamic switch to make sure we only dump view
+     * when ViewDebug.SYSTEM_PROPERTY_CAPTURE_VIEW) is set
+     */
+    private static void captureViewInfo(String subTag, View v) {
+        if (v == null ||
+           SystemProperties.getInt(ViewDebug.SYSTEM_PROPERTY_CAPTURE_VIEW, 0) == 0) {
+            return;
+        }
+        ViewDebug.dumpCapturedView(subTag, v);
+    }
+
+    /**
+     * Dispatch a key event before it is processed by any input method
+     * associated with the view hierarchy.  This can be used to intercept
+     * key events in special situations before the IME consumes them; a
+     * typical example would be handling the BACK key to update the application's
+     * UI instead of allowing the IME to see it and close itself.
+     *
+     * @param event The key event to be dispatched.
+     * @return True if the event was handled, false otherwise.
+     */
+    public boolean dispatchKeyEventPreIme(KeyEvent event) {
+        return onKeyPreIme(event.getKeyCode(), event);
+    }
+
+    /**
+     * Dispatch a key event to the next view on the focus path. This path runs
+     * from the top of the view tree down to the currently focused view. If this
+     * view has focus, it will dispatch to itself. Otherwise it will dispatch
+     * the next node down the focus path. This method also fires any key
+     * listeners.
+     *
+     * @param event The key event to be dispatched.
+     * @return True if the event was handled, false otherwise.
+     */
+    public boolean dispatchKeyEvent(KeyEvent event) {
+        // If any attached key listener a first crack at the event.
+        //noinspection SimplifiableIfStatement
+
+        if (android.util.Config.LOGV) {
+            captureViewInfo("captureViewKeyEvent", this);
+        }
+
+        if (mOnKeyListener != null && (mViewFlags & ENABLED_MASK) == ENABLED
+                && mOnKeyListener.onKey(this, event.getKeyCode(), event)) {
+            return true;
+        }
+
+        return event.dispatch(this);
+    }
+
+    /**
+     * Dispatches a key shortcut event.
+     *
+     * @param event The key event to be dispatched.
+     * @return True if the event was handled by the view, false otherwise.
+     */
+    public boolean dispatchKeyShortcutEvent(KeyEvent event) {
+        return onKeyShortcut(event.getKeyCode(), event);
+    }
+
+    /**
+     * Pass the touch screen motion event down to the target view, or this
+     * view if it is the target.
+     *
+     * @param event The motion event to be dispatched.
+     * @return True if the event was handled by the view, false otherwise.
+     */
+    public boolean dispatchTouchEvent(MotionEvent event) {
+        if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED &&
+                mOnTouchListener.onTouch(this, event)) {
+            return true;
+        }
+        return onTouchEvent(event);
+    }
+
+    /**
+     * Pass a trackball motion event down to the focused view.
+     *
+     * @param event The motion event to be dispatched.
+     * @return True if the event was handled by the view, false otherwise.
+     */
+    public boolean dispatchTrackballEvent(MotionEvent event) {
+        //Log.i("view", "view=" + this + ", " + event.toString());
+        return onTrackballEvent(event);
+    }
+
+    /**
+     * Called when the window containing this view gains or loses window focus.
+     * ViewGroups should override to route to their children.
+     *
+     * @param hasFocus True if the window containing this view now has focus,
+     *        false otherwise.
+     */
+    public void dispatchWindowFocusChanged(boolean hasFocus) {
+        onWindowFocusChanged(hasFocus);
+    }
+
+    /**
+     * Called when the window containing this view gains or loses focus.  Note
+     * that this is separate from view focus: to receive key events, both
+     * your view and its window must have focus.  If a window is displayed
+     * on top of yours that takes input focus, then your own window will lose
+     * focus but the view focus will remain unchanged.
+     *
+     * @param hasWindowFocus True if the window containing this view now has
+     *        focus, false otherwise.
+     */
+    public void onWindowFocusChanged(boolean hasWindowFocus) {
+        InputMethodManager imm = InputMethodManager.peekInstance();
+        if (!hasWindowFocus) {
+            if (isPressed()) {
+                setPressed(false);
+            }
+            if (imm != null && (mPrivateFlags & FOCUSED) != 0) {
+                imm.focusOut(this);
+            }
+        } else if (imm != null && (mPrivateFlags & FOCUSED) != 0) {
+            imm.focusIn(this);
+        }
+        refreshDrawableState();
+    }
+
+    /**
+     * Returns true if this view is in a window that currently has window focus.
+     * Note that this is not the same as the view itself having focus.
+     *
+     * @return True if this view is in a window that currently has window focus.
+     */
+    public boolean hasWindowFocus() {
+        return mAttachInfo != null && mAttachInfo.mHasWindowFocus;
+    }
+
+    /**
+     * Dispatch a window visibility change down the view hierarchy.
+     * ViewGroups should override to route to their children.
+     *
+     * @param visibility The new visibility of the window.
+     *
+     * @see #onWindowVisibilityChanged
+     */
+    public void dispatchWindowVisibilityChanged(int visibility) {
+        onWindowVisibilityChanged(visibility);
+    }
+
+    /**
+     * Called when the window containing has change its visibility
+     * (between {@link #GONE}, {@link #INVISIBLE}, and {@link #VISIBLE}).  Note
+     * that this tells you whether or not your window is being made visible
+     * to the window manager; this does <em>not</em> tell you whether or not
+     * your window is obscured by other windows on the screen, even if it
+     * is itself visible.
+     *
+     * @param visibility The new visibility of the window.
+     */
+    protected void onWindowVisibilityChanged(int visibility) {
+    }
+
+    /**
+     * Returns the current visibility of the window this view is attached to
+     * (either {@link #GONE}, {@link #INVISIBLE}, or {@link #VISIBLE}).
+     *
+     * @return Returns the current visibility of the view's window.
+     */
+    public int getWindowVisibility() {
+        return mAttachInfo != null ? mAttachInfo.mWindowVisibility : GONE;
+    }
+
+    /**
+     * Retrieve the overall visible display size in which the window this view is
+     * attached to has been positioned in.  This takes into account screen
+     * decorations above the window, for both cases where the window itself
+     * is being position inside of them or the window is being placed under
+     * then and covered insets are used for the window to position its content
+     * inside.  In effect, this tells you the available area where content can
+     * be placed and remain visible to users.
+     *
+     * <p>This function requires an IPC back to the window manager to retrieve
+     * the requested information, so should not be used in performance critical
+     * code like drawing.
+     *
+     * @param outRect Filled in with the visible display frame.  If the view
+     * is not attached to a window, this is simply the raw display size.
+     */
+    public void getWindowVisibleDisplayFrame(Rect outRect) {
+        if (mAttachInfo != null) {
+            try {
+                mAttachInfo.mSession.getDisplayFrame(mAttachInfo.mWindow, outRect);
+            } catch (RemoteException e) {
+                return;
+            }
+            // XXX This is really broken, and probably all needs to be done
+            // in the window manager, and we need to know more about whether
+            // we want the area behind or in front of the IME.
+            final Rect insets = mAttachInfo.mVisibleInsets;
+            outRect.left += insets.left;
+            outRect.top += insets.top;
+            outRect.right -= insets.right;
+            outRect.bottom -= insets.bottom;
+            return;
+        }
+        Display d = WindowManagerImpl.getDefault().getDefaultDisplay();
+        outRect.set(0, 0, d.getWidth(), d.getHeight());
+    }
+
+    /**
+     * Private function to aggregate all per-view attributes in to the view
+     * root.
+     */
+    void dispatchCollectViewAttributes(int visibility) {
+        performCollectViewAttributes(visibility);
+    }
+
+    void performCollectViewAttributes(int visibility) {
+        //noinspection PointlessBitwiseExpression
+        if (((visibility | mViewFlags) & (VISIBILITY_MASK | KEEP_SCREEN_ON))
+                == (VISIBLE | KEEP_SCREEN_ON)) {
+            mAttachInfo.mKeepScreenOn = true;
+        }
+    }
+
+    void needGlobalAttributesUpdate(boolean force) {
+        AttachInfo ai = mAttachInfo;
+        if (ai != null) {
+            if (ai.mKeepScreenOn || force) {
+                ai.mRecomputeGlobalAttributes = true;
+            }
+        }
+    }
+
+    /**
+     * Returns whether the device is currently in touch mode.  Touch mode is entered
+     * once the user begins interacting with the device by touch, and affects various
+     * things like whether focus is always visible to the user.
+     *
+     * @return Whether the device is in touch mode.
+     */
+    @ViewDebug.ExportedProperty
+    public boolean isInTouchMode() {
+        if (mAttachInfo != null) {
+            return mAttachInfo.mInTouchMode;
+        } else {
+            return ViewRoot.isInTouchMode();
+        }
+    }
+
+    /**
+     * Returns the context the view is running in, through which it can
+     * access the current theme, resources, etc.
+     *
+     * @return The view's Context.
+     */
+    @ViewDebug.CapturedViewProperty
+    public final Context getContext() {
+        return mContext;
+    }
+
+    /**
+     * Handle a key event before it is processed by any input method
+     * associated with the view hierarchy.  This can be used to intercept
+     * key events in special situations before the IME consumes them; a
+     * typical example would be handling the BACK key to update the application's
+     * UI instead of allowing the IME to see it and close itself.
+     *
+     * @param keyCode The value in event.getKeyCode().
+     * @param event Description of the key event.
+     * @return If you handled the event, return true. If you want to allow the
+     *         event to be handled by the next receiver, return false.
+     */
+    public boolean onKeyPreIme(int keyCode, KeyEvent event) {
+        return false;
+    }
+
+    /**
+     * Default implementation of {@link KeyEvent.Callback#onKeyMultiple(int, int, KeyEvent)
+     * KeyEvent.Callback.onKeyMultiple()}: perform press of the view
+     * when {@link KeyEvent#KEYCODE_DPAD_CENTER} or {@link KeyEvent#KEYCODE_ENTER}
+     * is released, if the view is enabled and clickable.
+     *
+     * @param keyCode A key code that represents the button pressed, from
+     *                {@link android.view.KeyEvent}.
+     * @param event   The KeyEvent object that defines the button action.
+     */
+    public boolean onKeyDown(int keyCode, KeyEvent event) {
+        boolean result = false;
+
+        switch (keyCode) {
+            case KeyEvent.KEYCODE_DPAD_CENTER:
+            case KeyEvent.KEYCODE_ENTER: {
+                if ((mViewFlags & ENABLED_MASK) == DISABLED) {
+                    return true;
+                }
+                // Long clickable items don't necessarily have to be clickable
+                if (((mViewFlags & CLICKABLE) == CLICKABLE ||
+                        (mViewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) &&
+                        (event.getRepeatCount() == 0)) {
+                    setPressed(true);
+                    if ((mViewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) {
+                        postCheckForLongClick();
+                    }
+                    return true;
+                }
+                break;
+            }
+        }
+        return result;
+    }
+
+    /**
+     * Default implementation of {@link KeyEvent.Callback#onKeyMultiple(int, int, KeyEvent)
+     * KeyEvent.Callback.onKeyMultiple()}: perform clicking of the view
+     * when {@link KeyEvent#KEYCODE_DPAD_CENTER} or
+     * {@link KeyEvent#KEYCODE_ENTER} is released.
+     *
+     * @param keyCode A key code that represents the button pressed, from
+     *                {@link android.view.KeyEvent}.
+     * @param event   The KeyEvent object that defines the button action.
+     */
+    public boolean onKeyUp(int keyCode, KeyEvent event) {
+        boolean result = false;
+
+        switch (keyCode) {
+            case KeyEvent.KEYCODE_DPAD_CENTER:
+            case KeyEvent.KEYCODE_ENTER: {
+                if ((mViewFlags & ENABLED_MASK) == DISABLED) {
+                    return true;
+                }
+                if ((mViewFlags & CLICKABLE) == CLICKABLE && isPressed()) {
+                    setPressed(false);
+
+                    if (!mHasPerformedLongPress) {
+                        // This is a tap, so remove the longpress check
+                        if (mPendingCheckForLongPress != null) {
+                            removeCallbacks(mPendingCheckForLongPress);
+                        }
+
+                        result = performClick();
+                    }
+                }
+                break;
+            }
+        }
+        return result;
+    }
+
+    /**
+     * Default implementation of {@link KeyEvent.Callback#onKeyMultiple(int, int, KeyEvent)
+     * KeyEvent.Callback.onKeyMultiple()}: always returns false (doesn't handle
+     * the event).
+     *
+     * @param keyCode     A key code that represents the button pressed, from
+     *                    {@link android.view.KeyEvent}.
+     * @param repeatCount The number of times the action was made.
+     * @param event       The KeyEvent object that defines the button action.
+     */
+    public boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event) {
+        return false;
+    }
+
+    /**
+     * Called when an unhandled key shortcut event occurs.
+     *
+     * @param keyCode The value in event.getKeyCode().
+     * @param event Description of the key event.
+     * @return If you handled the event, return true. If you want to allow the
+     *         event to be handled by the next receiver, return false.
+     */
+    public boolean onKeyShortcut(int keyCode, KeyEvent event) {
+        return false;
+    }
+
+    /**
+     * Check whether the called view is a text editor, in which case it
+     * would make sense to automatically display a soft input window for
+     * it.  Subclasses should override this if they implement
+     * {@link #onCreateInputConnection(EditorInfo)} to return true if
+     * a call on that method would return a non-null InputConnection.  The
+     * default implementation always returns false.
+     * 
+     * @return Returns true if this view is a text editor, else false.
+     */
+    public boolean onCheckIsTextEditor() {
+        return false;
+    }
+    
+    /**
+     * Create a new InputConnection for an InputMethod to interact
+     * with the view.  The default implementation returns null, since it doesn't
+     * support input methods.  You can override this to implement such support.
+     * This is only needed for views that take focus and text input.
+     * 
+     * <p>When implementing this, you probably also want to implement
+     * {@link #onCheckIsTextEditor()} to indicate you will return a
+     * non-null InputConnection.
+     *
+     * @param outAttrs Fill in with attribute information about the connection.
+     */
+    public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
+        return null;
+    }
+
+    /**
+     * Show the context menu for this view. It is not safe to hold on to the
+     * menu after returning from this method.
+     *
+     * @param menu The context menu to populate
+     */
+    public void createContextMenu(ContextMenu menu) {
+        ContextMenuInfo menuInfo = getContextMenuInfo();
+
+        // Sets the current menu info so all items added to menu will have
+        // my extra info set.
+        ((MenuBuilder)menu).setCurrentMenuInfo(menuInfo);
+
+        onCreateContextMenu(menu);
+        if (mOnCreateContextMenuListener != null) {
+            mOnCreateContextMenuListener.onCreateContextMenu(menu, this, menuInfo);
+        }
+
+        // Clear the extra information so subsequent items that aren't mine don't
+        // have my extra info.
+        ((MenuBuilder)menu).setCurrentMenuInfo(null);
+
+        if (mParent != null) {
+            mParent.createContextMenu(menu);
+        }
+    }
+
+    /**
+     * Views should implement this if they have extra information to associate
+     * with the context menu. The return result is supplied as a parameter to
+     * the {@link OnCreateContextMenuListener#onCreateContextMenu(ContextMenu, View, ContextMenuInfo)}
+     * callback.
+     *
+     * @return Extra information about the item for which the context menu
+     *         should be shown. This information will vary across different
+     *         subclasses of View.
+     */
+    protected ContextMenuInfo getContextMenuInfo() {
+        return null;
+    }
+
+    /**
+     * Views should implement this if the view itself is going to add items to
+     * the context menu.
+     *
+     * @param menu the context menu to populate
+     */
+    protected void onCreateContextMenu(ContextMenu menu) {
+    }
+
+    /**
+     * Implement this method to handle trackball motion events.  The
+     * <em>relative</em> movement of the trackball since the last event
+     * can be retrieve with {@link MotionEvent#getX MotionEvent.getX()} and
+     * {@link MotionEvent#getY MotionEvent.getY()}.  These are normalized so
+     * that a movement of 1 corresponds to the user pressing one DPAD key (so
+     * they will often be fractional values, representing the more fine-grained
+     * movement information available from a trackball).
+     *
+     * @param event The motion event.
+     * @return True if the event was handled, false otherwise.
+     */
+    public boolean onTrackballEvent(MotionEvent event) {
+        return false;
+    }
+
+    /**
+     * Implement this method to handle touch screen motion events.
+     *
+     * @param event The motion event.
+     * @return True if the event was handled, false otherwise.
+     */
+    public boolean onTouchEvent(MotionEvent event) {
+        final int viewFlags = mViewFlags;
+
+        if ((viewFlags & ENABLED_MASK) == DISABLED) {
+            // A disabled view that is clickable still consumes the touch
+            // events, it just doesn't respond to them.
+            return (((viewFlags & CLICKABLE) == CLICKABLE ||
+                    (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE));
+        }
+
+        if (mTouchDelegate != null) {
+            if (mTouchDelegate.onTouchEvent(event)) {
+                return true;
+            }
+        }
+
+        if (((viewFlags & CLICKABLE) == CLICKABLE ||
+                (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {
+            switch (event.getAction()) {
+                case MotionEvent.ACTION_UP:
+                    if ((mPrivateFlags & PRESSED) != 0) {
+                        // take focus if we don't have it already and we should in
+                        // touch mode.
+                        boolean focusTaken = false;
+                        if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
+                            focusTaken = requestFocus();
+                        }
+
+                        if (!mHasPerformedLongPress) {
+                            // This is a tap, so remove the longpress check
+                            if (mPendingCheckForLongPress != null) {
+                                removeCallbacks(mPendingCheckForLongPress);
+                            }
+
+                            // Only perform take click actions if we were in the pressed state
+                            if (!focusTaken) {
+                                performClick();
+                            }
+                        }
+
+                        if (mUnsetPressedState == null) {
+                            mUnsetPressedState = new UnsetPressedState();
+                        }
+
+                        if (!post(mUnsetPressedState)) {
+                            // If the post failed, unpress right now
+                            mUnsetPressedState.run();
+                        }
+                    }
+                    break;
+
+                case MotionEvent.ACTION_DOWN:
+                    mPrivateFlags |= PRESSED;
+                    refreshDrawableState();
+                    if ((mViewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) {
+                        postCheckForLongClick();
+                    }
+                    break;
+
+                case MotionEvent.ACTION_CANCEL:
+                    mPrivateFlags &= ~PRESSED;
+                    refreshDrawableState();
+                    break;
+
+                case MotionEvent.ACTION_MOVE:
+                    final int x = (int) event.getX();
+                    final int y = (int) event.getY();
+
+                    // Be lenient about moving outside of buttons
+                    int slop = ViewConfiguration.get(mContext).getScaledTouchSlop();
+                    if ((x < 0 - slop) || (x >= getWidth() + slop) ||
+                            (y < 0 - slop) || (y >= getHeight() + slop)) {
+                        // Outside button
+                        if ((mPrivateFlags & PRESSED) != 0) {
+                            // Remove any future long press checks
+                            if (mPendingCheckForLongPress != null) {
+                                removeCallbacks(mPendingCheckForLongPress);
+                            }
+
+                            // Need to switch from pressed to not pressed
+                            mPrivateFlags &= ~PRESSED;
+                            refreshDrawableState();
+                        }
+                    } else {
+                        // Inside button
+                        if ((mPrivateFlags & PRESSED) == 0) {
+                            // Need to switch from not pressed to pressed
+                            mPrivateFlags |= PRESSED;
+                            refreshDrawableState();
+                        }
+                    }
+                    break;
+            }
+            return true;
+        }
+
+        return false;
+    }
+
+    /**
+     * Cancels a pending long press.  Your subclass can use this if you
+     * want the context menu to come up if the user presses and holds
+     * at the same place, but you don't want it to come up if they press
+     * and then move around enough to cause scrolling.
+     */
+    public void cancelLongPress() {
+        if (mPendingCheckForLongPress != null) {
+            removeCallbacks(mPendingCheckForLongPress);
+        }
+    }
+
+    /**
+     * Sets the TouchDelegate for this View.
+     */
+    public void setTouchDelegate(TouchDelegate delegate) {
+        mTouchDelegate = delegate;
+    }
+
+    /**
+     * Gets the TouchDelegate for this View.
+     */
+    public TouchDelegate getTouchDelegate() {
+        return mTouchDelegate;
+    }
+
+    /**
+     * Set flags controlling behavior of this view.
+     *
+     * @param flags Constant indicating the value which should be set
+     * @param mask Constant indicating the bit range that should be changed
+     */
+    void setFlags(int flags, int mask) {
+        int old = mViewFlags;
+        mViewFlags = (mViewFlags & ~mask) | (flags & mask);
+
+        int changed = mViewFlags ^ old;
+        if (changed == 0) {
+            return;
+        }
+        int privateFlags = mPrivateFlags;
+
+        /* Check if the FOCUSABLE bit has changed */
+        if (((changed & FOCUSABLE_MASK) != 0) &&
+                ((privateFlags & HAS_BOUNDS) !=0)) {
+            if (((old & FOCUSABLE_MASK) == FOCUSABLE)
+                    && ((privateFlags & FOCUSED) != 0)) {
+                /* Give up focus if we are no longer focusable */
+                clearFocus();
+            } else if (((old & FOCUSABLE_MASK) == NOT_FOCUSABLE)
+                    && ((privateFlags & FOCUSED) == 0)) {
+                /*
+                 * Tell the view system that we are now available to take focus
+                 * if no one else already has it.
+                 */
+                if (mParent != null) mParent.focusableViewAvailable(this);
+            }
+        }
+
+        if ((flags & VISIBILITY_MASK) == VISIBLE) {
+            if ((changed & VISIBILITY_MASK) != 0) {
+                /*
+                 * If this view is becoming visible, set the DRAWN flag so that
+                 * the next invalidate() will not be skipped.
+                 */
+                mPrivateFlags |= DRAWN;
+
+                needGlobalAttributesUpdate(true);
+
+                // a view becoming visible is worth notifying the parent
+                // about in case nothing has focus.  even if this specific view
+                // isn't focusable, it may contain something that is, so let
+                // the root view try to give this focus if nothing else does.
+                if ((mParent != null) && (mBottom > mTop) && (mRight > mLeft)) {
+                    mParent.focusableViewAvailable(this);
+                }
+            }
+        }
+
+        /* Check if the GONE bit has changed */
+        if ((changed & GONE) != 0) {
+            needGlobalAttributesUpdate(false);
+            requestLayout();
+            invalidate();
+
+            if (((mViewFlags & VISIBILITY_MASK) == GONE) && hasFocus()) {
+                clearFocus();
+            }
+            if (mAttachInfo != null) {
+                mAttachInfo.mViewVisibilityChanged = true;
+            }
+        }
+
+        /* Check if the VISIBLE bit has changed */
+        if ((changed & INVISIBLE) != 0) {
+            needGlobalAttributesUpdate(false);
+            invalidate();
+
+            if (((mViewFlags & VISIBILITY_MASK) == INVISIBLE) && hasFocus()) {
+                // root view becoming invisible shouldn't clear focus
+                if (getRootView() != this) {
+                    clearFocus();
+                }
+            }
+            if (mAttachInfo != null) {
+                mAttachInfo.mViewVisibilityChanged = true;
+            }
+        }
+
+        if ((changed & WILL_NOT_CACHE_DRAWING) != 0) {
+            destroyDrawingCache();
+        }
+
+        if ((changed & DRAWING_CACHE_ENABLED) != 0) {
+            destroyDrawingCache();
+            mPrivateFlags &= ~DRAWING_CACHE_VALID;
+        }
+
+        if ((changed & DRAWING_CACHE_QUALITY_MASK) != 0) {
+            destroyDrawingCache();
+            mPrivateFlags &= ~DRAWING_CACHE_VALID;
+        }
+
+        if ((changed & DRAW_MASK) != 0) {
+            if ((mViewFlags & WILL_NOT_DRAW) != 0) {
+                if (mBGDrawable != null) {
+                    mPrivateFlags &= ~SKIP_DRAW;
+                    mPrivateFlags |= ONLY_DRAWS_BACKGROUND;
+                } else {
+                    mPrivateFlags |= SKIP_DRAW;
+                }
+            } else {
+                mPrivateFlags &= ~SKIP_DRAW;
+            }
+            requestLayout();
+            invalidate();
+        }
+
+        if ((changed & KEEP_SCREEN_ON) != 0) {
+            if (mParent != null) {
+                mParent.recomputeViewAttributes(this);
+            }
+        }
+    }
+
+    /**
+     * Change the view's z order in the tree, so it's on top of other sibling
+     * views
+     */
+    public void bringToFront() {
+        if (mParent != null) {
+            mParent.bringChildToFront(this);
+        }
+    }
+
+    /**
+     * This is called in response to an internal scroll in this view (i.e., the
+     * view scrolled its own contents). This is typically as a result of
+     * {@link #scrollBy(int, int)} or {@link #scrollTo(int, int)} having been
+     * called.
+     *
+     * @param l Current horizontal scroll origin.
+     * @param t Current vertical scroll origin.
+     * @param oldl Previous horizontal scroll origin.
+     * @param oldt Previous vertical scroll origin.
+     */
+    protected void onScrollChanged(int l, int t, int oldl, int oldt) {
+        mBackgroundSizeChanged = true;
+
+        final AttachInfo ai = mAttachInfo;
+        if (ai != null) {
+            ai.mViewScrollChanged = true;
+        }
+    }
+
+    /**
+     * This is called during layout when the size of this view has changed. If
+     * you were just added to the view hierarchy, you're called with the old
+     * values of 0.
+     *
+     * @param w Current width of this view.
+     * @param h Current height of this view.
+     * @param oldw Old width of this view.
+     * @param oldh Old height of this view.
+     */
+    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
+    }
+
+    /**
+     * Called by draw to draw the child views. This may be overridden
+     * by derived classes to gain control just before its children are drawn
+     * (but after its own view has been drawn).
+     * @param canvas the canvas on which to draw the view
+     */
+    protected void dispatchDraw(Canvas canvas) {
+    }
+
+    /**
+     * Gets the parent of this view. Note that the parent is a
+     * ViewParent and not necessarily a View.
+     *
+     * @return Parent of this view.
+     */
+    public final ViewParent getParent() {
+        return mParent;
+    }
+
+    /**
+     * Return the scrolled left position of this view. This is the left edge of
+     * the displayed part of your view. You do not need to draw any pixels
+     * farther left, since those are outside of the frame of your view on
+     * screen.
+     *
+     * @return The left edge of the displayed part of your view, in pixels.
+     */
+    public final int getScrollX() {
+        return mScrollX;
+    }
+
+    /**
+     * Return the scrolled top position of this view. This is the top edge of
+     * the displayed part of your view. You do not need to draw any pixels above
+     * it, since those are outside of the frame of your view on screen.
+     *
+     * @return The top edge of the displayed part of your view, in pixels.
+     */
+    public final int getScrollY() {
+        return mScrollY;
+    }
+
+    /**
+     * Return the width of the your view.
+     *
+     * @return The width of your view, in pixels.
+     */
+    @ViewDebug.ExportedProperty
+    public final int getWidth() {
+        return mRight - mLeft;
+    }
+
+    /**
+     * Return the height of your view.
+     *
+     * @return The height of your view, in pixels.
+     */
+    @ViewDebug.ExportedProperty
+    public final int getHeight() {
+        return mBottom - mTop;
+    }
+
+    /**
+     * Return the visible drawing bounds of your view. Fills in the output
+     * rectangle with the values from getScrollX(), getScrollY(),
+     * getWidth(), and getHeight().
+     *
+     * @param outRect The (scrolled) drawing bounds of the view.
+     */
+    public void getDrawingRect(Rect outRect) {
+        outRect.left = mScrollX;
+        outRect.top = mScrollY;
+        outRect.right = mScrollX + (mRight - mLeft);
+        outRect.bottom = mScrollY + (mBottom - mTop);
+    }
+
+    /**
+     * The width of this view as measured in the most recent call to measure().
+     * This should be used during measurement and layout calculations only. Use
+     * {@link #getWidth()} to see how wide a view is after layout.
+     *
+     * @return The measured width of this view.
+     */
+    public final int getMeasuredWidth() {
+        return mMeasuredWidth;
+    }
+
+    /**
+     * The height of this view as measured in the most recent call to measure().
+     * This should be used during measurement and layout calculations only. Use
+     * {@link #getHeight()} to see how tall a view is after layout.
+     *
+     * @return The measured height of this view.
+     */
+    public final int getMeasuredHeight() {
+        return mMeasuredHeight;
+    }
+
+    /**
+     * Top position of this view relative to its parent.
+     *
+     * @return The top of this view, in pixels.
+     */
+    @ViewDebug.CapturedViewProperty
+    public final int getTop() {
+        return mTop;
+    }
+
+    /**
+     * Bottom position of this view relative to its parent.
+     *
+     * @return The bottom of this view, in pixels.
+     */
+    @ViewDebug.CapturedViewProperty
+    public final int getBottom() {
+        return mBottom;
+    }
+
+    /**
+     * Left position of this view relative to its parent.
+     *
+     * @return The left edge of this view, in pixels.
+     */
+    @ViewDebug.CapturedViewProperty
+    public final int getLeft() {
+        return mLeft;
+    }
+
+    /**
+     * Right position of this view relative to its parent.
+     *
+     * @return The right edge of this view, in pixels.
+     */
+    @ViewDebug.CapturedViewProperty
+    public final int getRight() {
+        return mRight;
+    }
+
+    /**
+     * Hit rectangle in parent's coordinates
+     *
+     * @param outRect The hit rectangle of the view.
+     */
+    public void getHitRect(Rect outRect) {
+        outRect.set(mLeft, mTop, mRight, mBottom);
+    }
+
+    /**
+     * When a view has focus and the user navigates away from it, the next view is searched for
+     * starting from the rectangle filled in by this method.
+     *
+     * By default, the rectange is the {@link #getDrawingRect})of the view.  However, if your
+     * view maintains some idea of internal selection, such as a cursor, or a selected row
+     * or column, you should override this method and fill in a more specific rectangle.
+     *
+     * @param r The rectangle to fill in, in this view's coordinates.
+     */
+    public void getFocusedRect(Rect r) {
+        getDrawingRect(r);
+    }
+
+    /**
+     * If some part of this view is not clipped by any of its parents, then
+     * return that area in r in global (root) coordinates. To convert r to local
+     * coordinates, offset it by -globalOffset (e.g. r.offset(-globalOffset.x,
+     * -globalOffset.y)) If the view is completely clipped or translated out,
+     * return false.
+     *
+     * @param r If true is returned, r holds the global coordinates of the
+     *        visible portion of this view.
+     * @param globalOffset If true is returned, globalOffset holds the dx,dy
+     *        between this view and its root. globalOffet may be null.
+     * @return true if r is non-empty (i.e. part of the view is visible at the
+     *         root level.
+     */
+    public boolean getGlobalVisibleRect(Rect r, Point globalOffset) {
+        int width = mRight - mLeft;
+        int height = mBottom - mTop;
+        if (width > 0 && height > 0) {
+            r.set(0, 0, width, height);
+            if (globalOffset != null) {
+                globalOffset.set(-mScrollX, -mScrollY);
+            }
+            return mParent == null || mParent.getChildVisibleRect(this, r, globalOffset);
+        }
+        return false;
+    }
+
+    public final boolean getGlobalVisibleRect(Rect r) {
+        return getGlobalVisibleRect(r, null);
+    }
+
+    public final boolean getLocalVisibleRect(Rect r) {
+        Point offset = new Point();
+        if (getGlobalVisibleRect(r, offset)) {
+            r.offset(-offset.x, -offset.y); // make r local
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Offset this view's vertical location by the specified number of pixels.
+     *
+     * @param offset the number of pixels to offset the view by
+     */
+    public void offsetTopAndBottom(int offset) {
+        mTop += offset;
+        mBottom += offset;
+    }
+
+    /**
+     * Offset this view's horizontal location by the specified amount of pixels.
+     *
+     * @param offset the numer of pixels to offset the view by
+     */
+    public void offsetLeftAndRight(int offset) {
+        mLeft += offset;
+        mRight += offset;
+    }
+
+    /**
+     * Get the LayoutParams associated with this view. All views should have
+     * layout parameters. These supply parameters to the <i>parent</i> of this
+     * view specifying how it should be arranged. There are many subclasses of
+     * ViewGroup.LayoutParams, and these correspond to the different subclasses
+     * of ViewGroup that are responsible for arranging their children.
+     * @return The LayoutParams associated with this view
+     */
+    @ViewDebug.ExportedProperty(deepExport = true, prefix = "layout_")
+    public ViewGroup.LayoutParams getLayoutParams() {
+        return mLayoutParams;
+    }
+
+    /**
+     * Set the layout parameters associated with this view. These supply
+     * parameters to the <i>parent</i> of this view specifying how it should be
+     * arranged. There are many subclasses of ViewGroup.LayoutParams, and these
+     * correspond to the different subclasses of ViewGroup that are responsible
+     * for arranging their children.
+     *
+     * @param params the layout parameters for this view
+     */
+    public void setLayoutParams(ViewGroup.LayoutParams params) {
+        if (params == null) {
+            throw new NullPointerException("params == null");
+        }
+        mLayoutParams = params;
+        requestLayout();
+    }
+
+    /**
+     * Set the scrolled position of your view. This will cause a call to
+     * {@link #onScrollChanged(int, int, int, int)} and the view will be
+     * invalidated.
+     * @param x the x position to scroll to
+     * @param y the y position to scroll to
+     */
+    public void scrollTo(int x, int y) {
+        if (mScrollX != x || mScrollY != y) {
+            int oldX = mScrollX;
+            int oldY = mScrollY;
+            mScrollX = x;
+            mScrollY = y;
+            onScrollChanged(mScrollX, mScrollY, oldX, oldY);
+            invalidate();
+        }
+    }
+
+    /**
+     * Move the scrolled position of your view. This will cause a call to
+     * {@link #onScrollChanged(int, int, int, int)} and the view will be
+     * invalidated.
+     * @param x the amount of pixels to scroll by horizontally
+     * @param y the amount of pixels to scroll by vertically
+     */
+    public void scrollBy(int x, int y) {
+        scrollTo(mScrollX + x, mScrollY + y);
+    }
+
+    /**
+     * Mark the the area defined by dirty as needing to be drawn. If the view is
+     * visible, {@link #onDraw} will be called at some point in the future.
+     * This must be called from a UI thread. To call from a non-UI thread, call
+     * {@link #postInvalidate()}.
+     *
+     * WARNING: This method is destructive to dirty.
+     * @param dirty the rectangle representing the bounds of the dirty region
+     */
+    public void invalidate(Rect dirty) {
+        if (ViewDebug.TRACE_HIERARCHY) {
+            ViewDebug.trace(this, ViewDebug.HierarchyTraceType.INVALIDATE);
+        }
+
+        if ((mPrivateFlags & (DRAWN | HAS_BOUNDS)) == (DRAWN | HAS_BOUNDS)) {
+            mPrivateFlags &= ~DRAWING_CACHE_VALID;
+            final ViewParent p = mParent;
+            final AttachInfo ai = mAttachInfo;
+            if (p != null && ai != null) {
+                final int scrollX = mScrollX;
+                final int scrollY = mScrollY;
+                final Rect r = ai.mTmpInvalRect;
+                r.set(dirty.left - scrollX, dirty.top - scrollY,
+                        dirty.right - scrollX, dirty.bottom - scrollY);
+                mParent.invalidateChild(this, r);
+            }
+        }
+    }
+
+    /**
+     * Mark the the area defined by the rect (l,t,r,b) as needing to be drawn.
+     * The coordinates of the dirty rect are relative to the view.
+     * If the view is visible, {@link #onDraw} will be called at some point
+     * in the future. This must be called from a UI thread. To call
+     * from a non-UI thread, call {@link #postInvalidate()}.
+     * @param l the left position of the dirty region
+     * @param t the top position of the dirty region
+     * @param r the right position of the dirty region
+     * @param b the bottom position of the dirty region
+     */
+    public void invalidate(int l, int t, int r, int b) {
+        if (ViewDebug.TRACE_HIERARCHY) {
+            ViewDebug.trace(this, ViewDebug.HierarchyTraceType.INVALIDATE);
+        }
+
+        if ((mPrivateFlags & (DRAWN | HAS_BOUNDS)) == (DRAWN | HAS_BOUNDS)) {
+            mPrivateFlags &= ~DRAWING_CACHE_VALID;
+            final ViewParent p = mParent;
+            final AttachInfo ai = mAttachInfo;
+            if (p != null && ai != null && l < r && t < b) {
+                final int scrollX = mScrollX;
+                final int scrollY = mScrollY;
+                final Rect tmpr = ai.mTmpInvalRect;
+                tmpr.set(l - scrollX, t - scrollY, r - scrollX, b - scrollY);
+                p.invalidateChild(this, tmpr);
+            }
+        }
+    }
+
+    /**
+     * Invalidate the whole view. If the view is visible, {@link #onDraw} will
+     * be called at some point in the future. This must be called from a
+     * UI thread. To call from a non-UI thread, call {@link #postInvalidate()}.
+     */
+    public void invalidate() {
+        if (ViewDebug.TRACE_HIERARCHY) {
+            ViewDebug.trace(this, ViewDebug.HierarchyTraceType.INVALIDATE);
+        }
+
+        if ((mPrivateFlags & (DRAWN | HAS_BOUNDS)) == (DRAWN | HAS_BOUNDS)) {
+            mPrivateFlags &= ~DRAWN & ~DRAWING_CACHE_VALID;
+            final ViewParent p = mParent;
+            final AttachInfo ai = mAttachInfo;
+            if (p != null && ai != null) {
+                final Rect r = ai.mTmpInvalRect;
+                r.set(0, 0, mRight - mLeft, mBottom - mTop);
+                // Don't call invalidate -- we don't want to internally scroll
+                // our own bounds
+                p.invalidateChild(this, r);
+            }
+        }
+    }
+
+    /**
+     * @return A handler associated with the thread running the View. This
+     * handler can be used to pump events in the UI events queue.
+     */
+    public Handler getHandler() {
+        if (mAttachInfo != null) {
+            return mAttachInfo.mHandler;
+        }
+        return null;
+    }
+
+    /**
+     * Causes the Runnable to be added to the message queue.
+     * The runnable will be run on the user interface thread.
+     *
+     * @param action The Runnable that will be executed.
+     *
+     * @return Returns true if the Runnable was successfully placed in to the
+     *         message queue.  Returns false on failure, usually because the
+     *         looper processing the message queue is exiting.
+     */
+    public boolean post(Runnable action) {
+        Handler handler;
+        if (mAttachInfo != null) {
+            handler = mAttachInfo.mHandler;
+        } else {
+            // Assume that post will succeed later
+            ViewRoot.getRunQueue().post(action);
+            return true;
+        }
+
+        return handler.post(action);
+    }
+
+    /**
+     * Causes the Runnable to be added to the message queue, to be run
+     * after the specified amount of time elapses.
+     * The runnable will be run on the user interface thread.
+     *
+     * @param action The Runnable that will be executed.
+     * @param delayMillis The delay (in milliseconds) until the Runnable
+     *        will be executed.
+     *
+     * @return true if the Runnable was successfully placed in to the
+     *         message queue.  Returns false on failure, usually because the
+     *         looper processing the message queue is exiting.  Note that a
+     *         result of true does not mean the Runnable will be processed --
+     *         if the looper is quit before the delivery time of the message
+     *         occurs then the message will be dropped.
+     */
+    public boolean postDelayed(Runnable action, long delayMillis) {
+        Handler handler;
+        if (mAttachInfo != null) {
+            handler = mAttachInfo.mHandler;
+        } else {
+            // Assume that post will succeed later
+            ViewRoot.getRunQueue().postDelayed(action, delayMillis);
+            return true;
+        }
+
+        return handler.postDelayed(action, delayMillis);
+    }
+
+    /**
+     * Removes the specified Runnable from the message queue.
+     *
+     * @param action The Runnable to remove from the message handling queue
+     *
+     * @return true if this view could ask the Handler to remove the Runnable,
+     *         false otherwise. When the returned value is true, the Runnable
+     *         may or may not have been actually removed from the message queue
+     *         (for instance, if the Runnable was not in the queue already.)
+     */
+    public boolean removeCallbacks(Runnable action) {
+        Handler handler;
+        if (mAttachInfo != null) {
+            handler = mAttachInfo.mHandler;
+        } else {
+            // Assume that post will succeed later
+            ViewRoot.getRunQueue().removeCallbacks(action);
+            return true;
+        }
+
+        handler.removeCallbacks(action);
+        return true;
+    }
+
+    /**
+     * Cause an invalidate to happen on a subsequent cycle through the event loop.
+     * Use this to invalidate the View from a non-UI thread.
+     *
+     * @see #invalidate()
+     */
+    public void postInvalidate() {
+        postInvalidateDelayed(0);
+    }
+
+    /**
+     * Cause an invalidate of the specified area to happen on a subsequent cycle
+     * through the event loop. Use this to invalidate the View from a non-UI thread.
+     *
+     * @param left The left coordinate of the rectangle to invalidate.
+     * @param top The top coordinate of the rectangle to invalidate.
+     * @param right The right coordinate of the rectangle to invalidate.
+     * @param bottom The bottom coordinate of the rectangle to invalidate.
+     *
+     * @see #invalidate(int, int, int, int)
+     * @see #invalidate(Rect)
+     */
+    public void postInvalidate(int left, int top, int right, int bottom) {
+        postInvalidateDelayed(0, left, top, right, bottom);
+    }
+
+    /**
+     * Cause an invalidate to happen on a subsequent cycle through the event
+     * loop. Waits for the specified amount of time.
+     *
+     * @param delayMilliseconds the duration in milliseconds to delay the
+     *         invalidation by
+     */
+    public void postInvalidateDelayed(long delayMilliseconds) {
+        // We try only with the AttachInfo because there's no point in invalidating
+        // if we are not attached to our window
+        if (mAttachInfo != null) {
+            Message msg = Message.obtain();
+            msg.what = AttachInfo.INVALIDATE_MSG;
+            msg.obj = this;
+            mAttachInfo.mHandler.sendMessageDelayed(msg, delayMilliseconds);
+        }
+    }
+
+    /**
+     * Cause an invalidate of the specified area to happen on a subsequent cycle
+     * through the event loop. Waits for the specified amount of time.
+     *
+     * @param delayMilliseconds the duration in milliseconds to delay the
+     *         invalidation by
+     * @param left The left coordinate of the rectangle to invalidate.
+     * @param top The top coordinate of the rectangle to invalidate.
+     * @param right The right coordinate of the rectangle to invalidate.
+     * @param bottom The bottom coordinate of the rectangle to invalidate.
+     */
+    public void postInvalidateDelayed(long delayMilliseconds, int left, int top,
+            int right, int bottom) {
+
+        // We try only with the AttachInfo because there's no point in invalidating
+        // if we are not attached to our window
+        if (mAttachInfo != null) {
+            final AttachInfo.InvalidateInfo info = AttachInfo.InvalidateInfo.acquire();
+            info.target = this;
+            info.left = left;
+            info.top = top;
+            info.right = right;
+            info.bottom = bottom;
+
+            final Message msg = Message.obtain();
+            msg.what = AttachInfo.INVALIDATE_RECT_MSG;
+            msg.obj = info;
+            mAttachInfo.mHandler.sendMessageDelayed(msg, delayMilliseconds);
+        }
+    }
+
+    /**
+     * Called by a parent to request that a child update its values for mScrollX
+     * and mScrollY if necessary. This will typically be done if the child is
+     * animating a scroll using a {@link android.widget.Scroller Scroller}
+     * object.
+     */
+    public void computeScroll() {
+    }
+
+    /**
+     * <p>Indicate whether the horizontal edges are faded when the view is
+     * scrolled horizontally.</p>
+     *
+     * @return true if the horizontal edges should are faded on scroll, false
+     *         otherwise
+     *
+     * @see #setHorizontalFadingEdgeEnabled(boolean)
+     * @attr ref android.R.styleable#View_fadingEdge
+     */
+    public boolean isHorizontalFadingEdgeEnabled() {
+        return (mViewFlags & FADING_EDGE_HORIZONTAL) == FADING_EDGE_HORIZONTAL;
+    }
+
+    /**
+     * <p>Define whether the horizontal edges should be faded when this view
+     * is scrolled horizontally.</p>
+     *
+     * @param horizontalFadingEdgeEnabled true if the horizontal edges should
+     *                                    be faded when the view is scrolled
+     *                                    horizontally
+     *
+     * @see #isHorizontalFadingEdgeEnabled()
+     * @attr ref android.R.styleable#View_fadingEdge
+     */
+    public void setHorizontalFadingEdgeEnabled(boolean horizontalFadingEdgeEnabled) {
+        if (isHorizontalFadingEdgeEnabled() != horizontalFadingEdgeEnabled) {
+            if (horizontalFadingEdgeEnabled) {
+                initScrollCache();
+            }
+
+            mViewFlags ^= FADING_EDGE_HORIZONTAL;
+        }
+    }
+
+    /**
+     * <p>Indicate whether the vertical edges are faded when the view is
+     * scrolled horizontally.</p>
+     *
+     * @return true if the vertical edges should are faded on scroll, false
+     *         otherwise
+     *
+     * @see #setVerticalFadingEdgeEnabled(boolean)
+     * @attr ref android.R.styleable#View_fadingEdge
+     */
+    public boolean isVerticalFadingEdgeEnabled() {
+        return (mViewFlags & FADING_EDGE_VERTICAL) == FADING_EDGE_VERTICAL;
+    }
+
+    /**
+     * <p>Define whether the vertical edges should be faded when this view
+     * is scrolled vertically.</p>
+     *
+     * @param verticalFadingEdgeEnabled true if the vertical edges should
+     *                                  be faded when the view is scrolled
+     *                                  vertically
+     *
+     * @see #isVerticalFadingEdgeEnabled()
+     * @attr ref android.R.styleable#View_fadingEdge
+     */
+    public void setVerticalFadingEdgeEnabled(boolean verticalFadingEdgeEnabled) {
+        if (isVerticalFadingEdgeEnabled() != verticalFadingEdgeEnabled) {
+            if (verticalFadingEdgeEnabled) {
+                initScrollCache();
+            }
+
+            mViewFlags ^= FADING_EDGE_VERTICAL;
+        }
+    }
+
+    /**
+     * Returns the strength, or intensity, of the top faded edge. The strength is
+     * a value between 0.0 (no fade) and 1.0 (full fade). The default implementation
+     * returns 0.0 or 1.0 but no value in between.
+     *
+     * Subclasses should override this method to provide a smoother fade transition
+     * when scrolling occurs.
+     *
+     * @return the intensity of the top fade as a float between 0.0f and 1.0f
+     */
+    protected float getTopFadingEdgeStrength() {
+        return computeVerticalScrollOffset() > 0 ? 1.0f : 0.0f;
+    }
+
+    /**
+     * Returns the strength, or intensity, of the bottom faded edge. The strength is
+     * a value between 0.0 (no fade) and 1.0 (full fade). The default implementation
+     * returns 0.0 or 1.0 but no value in between.
+     *
+     * Subclasses should override this method to provide a smoother fade transition
+     * when scrolling occurs.
+     *
+     * @return the intensity of the bottom fade as a float between 0.0f and 1.0f
+     */
+    protected float getBottomFadingEdgeStrength() {
+        return computeVerticalScrollOffset() + computeVerticalScrollExtent() <
+                computeVerticalScrollRange() ? 1.0f : 0.0f;
+    }
+
+    /**
+     * Returns the strength, or intensity, of the left faded edge. The strength is
+     * a value between 0.0 (no fade) and 1.0 (full fade). The default implementation
+     * returns 0.0 or 1.0 but no value in between.
+     *
+     * Subclasses should override this method to provide a smoother fade transition
+     * when scrolling occurs.
+     *
+     * @return the intensity of the left fade as a float between 0.0f and 1.0f
+     */
+    protected float getLeftFadingEdgeStrength() {
+        return computeHorizontalScrollOffset() > 0 ? 1.0f : 0.0f;
+    }
+
+    /**
+     * Returns the strength, or intensity, of the right faded edge. The strength is
+     * a value between 0.0 (no fade) and 1.0 (full fade). The default implementation
+     * returns 0.0 or 1.0 but no value in between.
+     *
+     * Subclasses should override this method to provide a smoother fade transition
+     * when scrolling occurs.
+     *
+     * @return the intensity of the right fade as a float between 0.0f and 1.0f
+     */
+    protected float getRightFadingEdgeStrength() {
+        return computeHorizontalScrollOffset() + computeHorizontalScrollExtent() <
+                computeHorizontalScrollRange() ? 1.0f : 0.0f;
+    }
+
+    /**
+     * <p>Indicate whether the horizontal scrollbar should be drawn or not. The
+     * scrollbar is not drawn by default.</p>
+     *
+     * @return true if the horizontal scrollbar should be painted, false
+     *         otherwise
+     *
+     * @see #setHorizontalScrollBarEnabled(boolean)
+     */
+    public boolean isHorizontalScrollBarEnabled() {
+        return (mViewFlags & SCROLLBARS_HORIZONTAL) == SCROLLBARS_HORIZONTAL;
+    }
+
+    /**
+     * <p>Define whether the horizontal scrollbar should be drawn or not. The
+     * scrollbar is not drawn by default.</p>
+     *
+     * @param horizontalScrollBarEnabled true if the horizontal scrollbar should
+     *                                   be painted
+     *
+     * @see #isHorizontalScrollBarEnabled()
+     */
+    public void setHorizontalScrollBarEnabled(boolean horizontalScrollBarEnabled) {
+        if (isHorizontalScrollBarEnabled() != horizontalScrollBarEnabled) {
+            mViewFlags ^= SCROLLBARS_HORIZONTAL;
+            recomputePadding();
+        }
+    }
+
+    /**
+     * <p>Indicate whether the vertical scrollbar should be drawn or not. The
+     * scrollbar is not drawn by default.</p>
+     *
+     * @return true if the vertical scrollbar should be painted, false
+     *         otherwise
+     *
+     * @see #setVerticalScrollBarEnabled(boolean)
+     */
+    public boolean isVerticalScrollBarEnabled() {
+        return (mViewFlags & SCROLLBARS_VERTICAL) == SCROLLBARS_VERTICAL;
+    }
+
+    /**
+     * <p>Define whether the vertical scrollbar should be drawn or not. The
+     * scrollbar is not drawn by default.</p>
+     *
+     * @param verticalScrollBarEnabled true if the vertical scrollbar should
+     *                                 be painted
+     *
+     * @see #isVerticalScrollBarEnabled()
+     */
+    public void setVerticalScrollBarEnabled(boolean verticalScrollBarEnabled) {
+        if (isVerticalScrollBarEnabled() != verticalScrollBarEnabled) {
+            mViewFlags ^= SCROLLBARS_VERTICAL;
+            recomputePadding();
+        }
+    }
+
+    private void recomputePadding() {
+        setPadding(mPaddingLeft, mPaddingTop, mUserPaddingRight, mUserPaddingBottom);
+    }
+
+    /**
+     * <p>Specify the style of the scrollbars. The scrollbars can be overlaid or
+     * inset. When inset, they add to the padding of the view. And the scrollbars
+     * can be drawn inside the padding area or on the edge of the view. For example,
+     * if a view has a background drawable and you want to draw the scrollbars
+     * inside the padding specified by the drawable, you can use
+     * SCROLLBARS_INSIDE_OVERLAY or SCROLLBARS_INSIDE_INSET. If you want them to
+     * appear at the edge of the view, ignoring the padding, then you can use
+     * SCROLLBARS_OUTSIDE_OVERLAY or SCROLLBARS_OUTSIDE_INSET.</p>
+     * @param style the style of the scrollbars. Should be one of
+     * SCROLLBARS_INSIDE_OVERLAY, SCROLLBARS_INSIDE_INSET,
+     * SCROLLBARS_OUTSIDE_OVERLAY or SCROLLBARS_OUTSIDE_INSET.
+     * @see #SCROLLBARS_INSIDE_OVERLAY
+     * @see #SCROLLBARS_INSIDE_INSET
+     * @see #SCROLLBARS_OUTSIDE_OVERLAY
+     * @see #SCROLLBARS_OUTSIDE_INSET
+     */
+    public void setScrollBarStyle(int style) {
+        if (style != (mViewFlags & SCROLLBARS_STYLE_MASK)) {
+            mViewFlags = (mViewFlags & ~SCROLLBARS_STYLE_MASK) | (style & SCROLLBARS_STYLE_MASK);
+            recomputePadding();
+        }
+    }
+
+    /**
+     * <p>Returns the current scrollbar style.</p>
+     * @return the current scrollbar style
+     * @see #SCROLLBARS_INSIDE_OVERLAY
+     * @see #SCROLLBARS_INSIDE_INSET
+     * @see #SCROLLBARS_OUTSIDE_OVERLAY
+     * @see #SCROLLBARS_OUTSIDE_INSET
+     */
+    public int getScrollBarStyle() {
+        return mViewFlags & SCROLLBARS_STYLE_MASK;
+    }
+
+    /**
+     * <p>Compute the horizontal range that the horizontal scrollbar
+     * represents.</p>
+     *
+     * <p>The range is expressed in arbitrary units that must be the same as the
+     * units used by {@link #computeHorizontalScrollExtent()} and
+     * {@link #computeHorizontalScrollOffset()}.</p>
+     *
+     * <p>The default range is the drawing width of this view.</p>
+     *
+     * @return the total horizontal range represented by the horizontal
+     *         scrollbar
+     *
+     * @see #computeHorizontalScrollExtent()
+     * @see #computeHorizontalScrollOffset()
+     * @see android.widget.ScrollBarDrawable
+     */
+    protected int computeHorizontalScrollRange() {
+        return getWidth();
+    }
+
+    /**
+     * <p>Compute the horizontal offset of the horizontal scrollbar's thumb
+     * within the horizontal range. This value is used to compute the position
+     * of the thumb within the scrollbar's track.</p>
+     *
+     * <p>The range is expressed in arbitrary units that must be the same as the
+     * units used by {@link #computeHorizontalScrollRange()} and
+     * {@link #computeHorizontalScrollExtent()}.</p>
+     *
+     * <p>The default offset is the scroll offset of this view.</p>
+     *
+     * @return the horizontal offset of the scrollbar's thumb
+     *
+     * @see #computeHorizontalScrollRange()
+     * @see #computeHorizontalScrollExtent()
+     * @see android.widget.ScrollBarDrawable
+     */
+    protected int computeHorizontalScrollOffset() {
+        return mScrollX;
+    }
+
+    /**
+     * <p>Compute the horizontal extent of the horizontal scrollbar's thumb
+     * within the horizontal range. This value is used to compute the length
+     * of the thumb within the scrollbar's track.</p>
+     *
+     * <p>The range is expressed in arbitrary units that must be the same as the
+     * units used by {@link #computeHorizontalScrollRange()} and
+     * {@link #computeHorizontalScrollOffset()}.</p>
+     *
+     * <p>The default extent is the drawing width of this view.</p>
+     *
+     * @return the horizontal extent of the scrollbar's thumb
+     *
+     * @see #computeHorizontalScrollRange()
+     * @see #computeHorizontalScrollOffset()
+     * @see android.widget.ScrollBarDrawable
+     */
+    protected int computeHorizontalScrollExtent() {
+        return getWidth();
+    }
+
+    /**
+     * <p>Compute the vertical range that the vertical scrollbar represents.</p>
+     *
+     * <p>The range is expressed in arbitrary units that must be the same as the
+     * units used by {@link #computeVerticalScrollExtent()} and
+     * {@link #computeVerticalScrollOffset()}.</p>
+     *
+     * @return the total vertical range represented by the vertical scrollbar
+     *
+     * <p>The default range is the drawing height of this view.</p>
+     *
+     * @see #computeVerticalScrollExtent()
+     * @see #computeVerticalScrollOffset()
+     * @see android.widget.ScrollBarDrawable
+     */
+    protected int computeVerticalScrollRange() {
+        return getHeight();
+    }
+
+    /**
+     * <p>Compute the vertical offset of the vertical scrollbar's thumb
+     * within the horizontal range. This value is used to compute the position
+     * of the thumb within the scrollbar's track.</p>
+     *
+     * <p>The range is expressed in arbitrary units that must be the same as the
+     * units used by {@link #computeVerticalScrollRange()} and
+     * {@link #computeVerticalScrollExtent()}.</p>
+     *
+     * <p>The default offset is the scroll offset of this view.</p>
+     *
+     * @return the vertical offset of the scrollbar's thumb
+     *
+     * @see #computeVerticalScrollRange()
+     * @see #computeVerticalScrollExtent()
+     * @see android.widget.ScrollBarDrawable
+     */
+    protected int computeVerticalScrollOffset() {
+        return mScrollY;
+    }
+
+    /**
+     * <p>Compute the vertical extent of the horizontal scrollbar's thumb
+     * within the vertical range. This value is used to compute the length
+     * of the thumb within the scrollbar's track.</p>
+     *
+     * <p>The range is expressed in arbitrary units that must be the same as the
+     * units used by {@link #computeHorizontalScrollRange()} and
+     * {@link #computeVerticalScrollOffset()}.</p>
+     *
+     * <p>The default extent is the drawing height of this view.</p>
+     *
+     * @return the vertical extent of the scrollbar's thumb
+     *
+     * @see #computeVerticalScrollRange()
+     * @see #computeVerticalScrollOffset()
+     * @see android.widget.ScrollBarDrawable
+     */
+    protected int computeVerticalScrollExtent() {
+        return getHeight();
+    }
+
+    /**
+     * <p>Request the drawing of the horizontal and the vertical scrollbar. The
+     * scrollbars are painted only if they have been awakened first.</p>
+     *
+     * @param canvas the canvas on which to draw the scrollbars
+     */
+    private void onDrawScrollBars(Canvas canvas) {
+        // scrollbars are drawn only when the animation is running
+        final ScrollabilityCache cache = mScrollCache;
+        if (cache != null) {
+            final int viewFlags = mViewFlags;
+
+            final boolean drawHorizontalScrollBar =
+                (viewFlags & SCROLLBARS_HORIZONTAL) == SCROLLBARS_HORIZONTAL;
+            final boolean drawVerticalScrollBar =
+                (viewFlags & SCROLLBARS_VERTICAL) == SCROLLBARS_VERTICAL
+                && !isVerticalScrollBarHidden();
+
+            if (drawVerticalScrollBar || drawHorizontalScrollBar) {
+                final int width = mRight - mLeft;
+                final int height = mBottom - mTop;
+
+                final ScrollBarDrawable scrollBar = cache.scrollBar;
+                int size = scrollBar.getSize(false);
+                if (size <= 0) {
+                    size = cache.scrollBarSize;
+                }
+
+                if (drawHorizontalScrollBar) {
+                    onDrawHorizontalScrollBar(canvas, scrollBar, width, height, size);
+                }
+
+                if (drawVerticalScrollBar) {
+                    onDrawVerticalScrollBar(canvas, scrollBar, width, height, size);
+                }
+            }
+        }
+    }
+    
+    /**
+     * Override this if the vertical scrollbar needs to be hidden in a subclass, like when 
+     * FastScroller is visible.
+     * @return whether to temporarily hide the vertical scrollbar
+     * @hide
+     */
+    protected boolean isVerticalScrollBarHidden() {
+        return false;
+    }
+
+    /**
+     * <p>Draw the horizontal scrollbar if
+     * {@link #isHorizontalScrollBarEnabled()} returns true.</p>
+     *
+     * <p>The length of the scrollbar and its thumb is computed according to the
+     * values returned by {@link #computeHorizontalScrollRange()},
+     * {@link #computeHorizontalScrollExtent()} and
+     * {@link #computeHorizontalScrollOffset()}. Refer to
+     * {@link android.widget.ScrollBarDrawable} for more information about how
+     * these values relate to each other.</p>
+     *
+     * @param canvas the canvas on which to draw the scrollbar
+     * @param scrollBar the scrollbar's drawable
+     * @param width the width of the drawing surface
+     * @param height the height of the drawing surface
+     * @param size the size of the scrollbar
+     *
+     * @see #isHorizontalScrollBarEnabled()
+     * @see #computeHorizontalScrollRange()
+     * @see #computeHorizontalScrollExtent()
+     * @see #computeHorizontalScrollOffset()
+     * @see android.widget.ScrollBarDrawable
+     */
+    private void onDrawHorizontalScrollBar(Canvas canvas, ScrollBarDrawable scrollBar, int width,
+            int height, int size) {
+
+        final int viewFlags = mViewFlags;
+        final int scrollX = mScrollX;
+        final int scrollY = mScrollY;
+        final int inside = (viewFlags & SCROLLBARS_OUTSIDE_MASK) == 0 ? ~0 : 0;
+        final int top = scrollY + height - size - (mUserPaddingBottom & inside);
+
+        final int verticalScrollBarGap =
+            (viewFlags & SCROLLBARS_VERTICAL) == SCROLLBARS_VERTICAL ?
+                    getVerticalScrollbarWidth() : 0;
+
+        scrollBar.setBounds(scrollX + (mPaddingLeft & inside) + getScrollBarPaddingLeft(), top,
+                scrollX + width - (mUserPaddingRight & inside) - verticalScrollBarGap, top + size);
+        scrollBar.setParameters(
+                computeHorizontalScrollRange(),
+                computeHorizontalScrollOffset(),
+                computeHorizontalScrollExtent(), false);
+        scrollBar.draw(canvas);
+    }
+
+    /**
+     * <p>Draw the vertical scrollbar if {@link #isVerticalScrollBarEnabled()}
+     * returns true.</p>
+     *
+     * <p>The length of the scrollbar and its thumb is computed according to the
+     * values returned by {@link #computeVerticalScrollRange()},
+     * {@link #computeVerticalScrollExtent()} and
+     * {@link #computeVerticalScrollOffset()}. Refer to
+     * {@link android.widget.ScrollBarDrawable} for more information about how
+     * these values relate to each other.</p>
+     *
+     * @param canvas the canvas on which to draw the scrollbar
+     * @param scrollBar the scrollbar's drawable
+     * @param width the width of the drawing surface
+     * @param height the height of the drawing surface
+     * @param size the size of the scrollbar
+     *
+     * @see #isVerticalScrollBarEnabled()
+     * @see #computeVerticalScrollRange()
+     * @see #computeVerticalScrollExtent()
+     * @see #computeVerticalScrollOffset()
+     * @see android.widget.ScrollBarDrawable
+     */
+    private void onDrawVerticalScrollBar(Canvas canvas, ScrollBarDrawable scrollBar, int width,
+            int height, int size) {
+
+        final int scrollX = mScrollX;
+        final int scrollY = mScrollY;
+        final int inside = (mViewFlags & SCROLLBARS_OUTSIDE_MASK) == 0 ? ~0 : 0;
+        // TODO: Deal with RTL languages to position scrollbar on left
+        final int left = scrollX + width - size - (mUserPaddingRight & inside);
+
+        scrollBar.setBounds(left, scrollY + (mPaddingTop & inside),
+                left + size, scrollY + height - (mUserPaddingBottom & inside));
+        scrollBar.setParameters(
+                computeVerticalScrollRange(),
+                computeVerticalScrollOffset(),
+                computeVerticalScrollExtent(), true);
+        scrollBar.draw(canvas);
+    }
+
+    /**
+     * Implement this to do your drawing.
+     *
+     * @param canvas the canvas on which the background will be drawn
+     */
+    protected void onDraw(Canvas canvas) {
+    }
+
+    /*
+     * Caller is responsible for calling requestLayout if necessary.
+     * (This allows addViewInLayout to not request a new layout.)
+     */
+    void assignParent(ViewParent parent) {
+        if (mParent == null) {
+            mParent = parent;
+        } else if (parent == null) {
+            mParent = null;
+        } else {
+            throw new RuntimeException("view " + this + " being added, but"
+                    + " it already has a parent");
+        }
+    }
+
+    /**
+     * This is called when the view is attached to a window.  At this point it
+     * has a Surface and will start drawing.  Note that this function is
+     * guaranteed to be called before {@link #onDraw}, however it may be called
+     * any time before the first onDraw -- including before or after
+     * {@link #onMeasure}.
+     *
+     * @see #onDetachedFromWindow()
+     */
+    protected void onAttachedToWindow() {
+        if ((mPrivateFlags & REQUEST_TRANSPARENT_REGIONS) != 0) {
+            mParent.requestTransparentRegion(this);
+        }
+    }
+
+    /**
+     * This is called when the view is detached from a window.  At this point it
+     * no longer has a surface for drawing.
+     *
+     * @see #onAttachedToWindow()
+     */
+    protected void onDetachedFromWindow() {
+        if (mPendingCheckForLongPress != null) {
+            removeCallbacks(mPendingCheckForLongPress);
+        }
+        destroyDrawingCache();
+    }
+
+    /**
+     * @return The number of times this view has been attached to a window
+     */
+    protected int getWindowAttachCount() {
+        return mWindowAttachCount;
+    }
+
+    /**
+     * Retrieve a unique token identifying the window this view is attached to.
+     * @return Return the window's token for use in
+     * {@link WindowManager.LayoutParams#token WindowManager.LayoutParams.token}.
+     */
+    public IBinder getWindowToken() {
+        return mAttachInfo != null ? mAttachInfo.mWindowToken : null;
+    }
+
+    /**
+     * Retrieve a unique token identifying the top-level "real" window of
+     * the window that this view is attached to.  That is, this is like
+     * {@link #getWindowToken}, except if the window this view in is a panel
+     * window (attached to another containing window), then the token of
+     * the containing window is returned instead.
+     *
+     * @return Returns the associated window token, either
+     * {@link #getWindowToken()} or the containing window's token.
+     */
+    public IBinder getApplicationWindowToken() {
+        AttachInfo ai = mAttachInfo;
+        if (ai != null) {
+            IBinder appWindowToken = ai.mPanelParentWindowToken;
+            if (appWindowToken == null) {
+                appWindowToken = ai.mWindowToken;
+            }
+            return appWindowToken;
+        }
+        return null;
+    }
+
+    /**
+     * Retrieve private session object this view hierarchy is using to
+     * communicate with the window manager.
+     * @return the session object to communicate with the window manager
+     */
+    /*package*/ IWindowSession getWindowSession() {
+        return mAttachInfo != null ? mAttachInfo.mSession : null;
+    }
+
+    /**
+     * @param info the {@link android.view.View.AttachInfo} to associated with
+     *        this view
+     */
+    void dispatchAttachedToWindow(AttachInfo info, int visibility) {
+        //System.out.println("Attached! " + this);
+        mAttachInfo = info;
+        mWindowAttachCount++;
+        if (mFloatingTreeObserver != null) {
+            info.mTreeObserver.merge(mFloatingTreeObserver);
+            mFloatingTreeObserver = null;
+        }
+        if ((mPrivateFlags&SCROLL_CONTAINER) != 0) {
+            mAttachInfo.mScrollContainers.add(this);
+            mPrivateFlags |= SCROLL_CONTAINER_ADDED;
+        }
+        performCollectViewAttributes(visibility);
+        onAttachedToWindow();
+        int vis = info.mWindowVisibility;
+        if (vis != GONE) {
+            onWindowVisibilityChanged(vis);
+        }
+    }
+
+    void dispatchDetachedFromWindow() {
+        //System.out.println("Detached! " + this);
+        AttachInfo info = mAttachInfo;
+        if (info != null) {
+            int vis = info.mWindowVisibility;
+            if (vis != GONE) {
+                onWindowVisibilityChanged(GONE);
+            }
+        }
+
+        onDetachedFromWindow();
+        if ((mPrivateFlags&SCROLL_CONTAINER_ADDED) != 0) {
+            mAttachInfo.mScrollContainers.remove(this);
+            mPrivateFlags &= ~SCROLL_CONTAINER_ADDED;
+        }
+        mAttachInfo = null;
+    }
+
+    /**
+     * Store this view hierarchy's frozen state into the given container.
+     *
+     * @param container The SparseArray in which to save the view's state.
+     *
+     * @see #restoreHierarchyState
+     * @see #dispatchSaveInstanceState
+     * @see #onSaveInstanceState
+     */
+    public void saveHierarchyState(SparseArray<Parcelable> container) {
+        dispatchSaveInstanceState(container);
+    }
+
+    /**
+     * Called by {@link #saveHierarchyState} to store the state for this view and its children.
+     * May be overridden to modify how freezing happens to a view's children; for example, some
+     * views may want to not store state for their children.
+     *
+     * @param container The SparseArray in which to save the view's state.
+     *
+     * @see #dispatchRestoreInstanceState
+     * @see #saveHierarchyState
+     * @see #onSaveInstanceState
+     */
+    protected void dispatchSaveInstanceState(SparseArray<Parcelable> container) {
+        if (mID != NO_ID && (mViewFlags & SAVE_DISABLED_MASK) == 0) {
+            mPrivateFlags &= ~SAVE_STATE_CALLED;
+            Parcelable state = onSaveInstanceState();
+            if ((mPrivateFlags & SAVE_STATE_CALLED) == 0) {
+                throw new IllegalStateException(
+                        "Derived class did not call super.onSaveInstanceState()");
+            }
+            if (state != null) {
+                // Log.i("View", "Freezing #" + Integer.toHexString(mID)
+                // + ": " + state);
+                container.put(mID, state);
+            }
+        }
+    }
+
+    /**
+     * Hook allowing a view to generate a representation of its internal state
+     * that can later be used to create a new instance with that same state.
+     * This state should only contain information that is not persistent or can
+     * not be reconstructed later. For example, you will never store your
+     * current position on screen because that will be computed again when a
+     * new instance of the view is placed in its view hierarchy.
+     * <p>
+     * Some examples of things you may store here: the current cursor position
+     * in a text view (but usually not the text itself since that is stored in a
+     * content provider or other persistent storage), the currently selected
+     * item in a list view.
+     *
+     * @return Returns a Parcelable object containing the view's current dynamic
+     *         state, or null if there is nothing interesting to save. The
+     *         default implementation returns null.
+     * @see #onRestoreInstanceState
+     * @see #saveHierarchyState
+     * @see #dispatchSaveInstanceState
+     * @see #setSaveEnabled(boolean)
+     */
+    protected Parcelable onSaveInstanceState() {
+        mPrivateFlags |= SAVE_STATE_CALLED;
+        return BaseSavedState.EMPTY_STATE;
+    }
+
+    /**
+     * Restore this view hierarchy's frozen state from the given container.
+     *
+     * @param container The SparseArray which holds previously frozen states.
+     *
+     * @see #saveHierarchyState
+     * @see #dispatchRestoreInstanceState
+     * @see #onRestoreInstanceState
+     */
+    public void restoreHierarchyState(SparseArray<Parcelable> container) {
+        dispatchRestoreInstanceState(container);
+    }
+
+    /**
+     * Called by {@link #restoreHierarchyState} to retrieve the state for this view and its
+     * children. May be overridden to modify how restoreing happens to a view's children; for
+     * example, some views may want to not store state for their children.
+     *
+     * @param container The SparseArray which holds previously saved state.
+     *
+     * @see #dispatchSaveInstanceState
+     * @see #restoreHierarchyState
+     * @see #onRestoreInstanceState
+     */
+    protected void dispatchRestoreInstanceState(SparseArray<Parcelable> container) {
+        if (mID != NO_ID) {
+            Parcelable state = container.get(mID);
+            if (state != null) {
+                // Log.i("View", "Restoreing #" + Integer.toHexString(mID)
+                // + ": " + state);
+                mPrivateFlags &= ~SAVE_STATE_CALLED;
+                onRestoreInstanceState(state);
+                if ((mPrivateFlags & SAVE_STATE_CALLED) == 0) {
+                    throw new IllegalStateException(
+                            "Derived class did not call super.onRestoreInstanceState()");
+                }
+            }
+        }
+    }
+
+    /**
+     * Hook allowing a view to re-apply a representation of its internal state that had previously
+     * been generated by {@link #onSaveInstanceState}. This function will never be called with a
+     * null state.
+     *
+     * @param state The frozen state that had previously been returned by
+     *        {@link #onSaveInstanceState}.
+     *
+     * @see #onSaveInstanceState
+     * @see #restoreHierarchyState
+     * @see #dispatchRestoreInstanceState
+     */
+    protected void onRestoreInstanceState(Parcelable state) {
+        mPrivateFlags |= SAVE_STATE_CALLED;
+        if (state != BaseSavedState.EMPTY_STATE && state != null) {
+            throw new IllegalArgumentException("Wrong state class -- expecting View State");
+        }
+    }
+
+    /**
+     * <p>Return the time at which the drawing of the view hierarchy started.</p>
+     *
+     * @return the drawing start time in milliseconds
+     */
+    public long getDrawingTime() {
+        return mAttachInfo != null ? mAttachInfo.mDrawingTime : 0;
+    }
+
+    /**
+     * <p>Enables or disables the duplication of the parent's state into this view. When
+     * duplication is enabled, this view gets its drawable state from its parent rather
+     * than from its own internal properties.</p>
+     *
+     * <p>Note: in the current implementation, setting this property to true after the
+     * view was added to a ViewGroup might have no effect at all. This property should
+     * always be used from XML or set to true before adding this view to a ViewGroup.</p>
+     *
+     * <p>Note: if this view's parent addStateFromChildren property is enabled and this
+     * property is enabled, an exception will be thrown.</p>
+     *
+     * @param enabled True to enable duplication of the parent's drawable state, false
+     *                to disable it.
+     *
+     * @see #getDrawableState()
+     * @see #isDuplicateParentStateEnabled()
+     */
+    public void setDuplicateParentStateEnabled(boolean enabled) {
+        setFlags(enabled ? DUPLICATE_PARENT_STATE : 0, DUPLICATE_PARENT_STATE);
+    }
+
+    /**
+     * <p>Indicates whether this duplicates its drawable state from its parent.</p>
+     *
+     * @return True if this view's drawable state is duplicated from the parent,
+     *         false otherwise
+     *
+     * @see #getDrawableState()
+     * @see #setDuplicateParentStateEnabled(boolean)
+     */
+    public boolean isDuplicateParentStateEnabled() {
+        return (mViewFlags & DUPLICATE_PARENT_STATE) == DUPLICATE_PARENT_STATE;
+    }
+
+    /**
+     * <p>Enables or disables the drawing cache. When the drawing cache is enabled, the next call
+     * to {@link #getDrawingCache()} or {@link #buildDrawingCache()} will draw the view in a
+     * bitmap. Calling {@link #draw(android.graphics.Canvas)} will not draw from the cache when
+     * the cache is enabled. To benefit from the cache, you must request the drawing cache by
+     * calling {@link #getDrawingCache()} and draw it on screen if the returned bitmap is not
+     * null.</p>
+     *
+     * @param enabled true to enable the drawing cache, false otherwise
+     *
+     * @see #isDrawingCacheEnabled()
+     * @see #getDrawingCache()
+     * @see #buildDrawingCache()
+     */
+    public void setDrawingCacheEnabled(boolean enabled) {
+        setFlags(enabled ? DRAWING_CACHE_ENABLED : 0, DRAWING_CACHE_ENABLED);
+    }
+
+    /**
+     * <p>Indicates whether the drawing cache is enabled for this view.</p>
+     *
+     * @return true if the drawing cache is enabled
+     *
+     * @see #setDrawingCacheEnabled(boolean)
+     * @see #getDrawingCache()
+     */
+    @ViewDebug.ExportedProperty
+    public boolean isDrawingCacheEnabled() {
+        return (mViewFlags & DRAWING_CACHE_ENABLED) == DRAWING_CACHE_ENABLED;
+    }
+
+    /**
+     * <p>Returns the bitmap in which this view drawing is cached. The returned bitmap
+     * is null when caching is disabled. If caching is enabled and the cache is not ready,
+     * this method will create it. Calling {@link #draw(android.graphics.Canvas)} will not
+     * draw from the cache when the cache is enabled. To benefit from the cache, you must
+     * request the drawing cache by calling this method and draw it on screen if the
+     * returned bitmap is not null.</p>
+     *
+     * @return a bitmap representing this view or null if cache is disabled
+     *
+     * @see #setDrawingCacheEnabled(boolean)
+     * @see #isDrawingCacheEnabled()
+     * @see #buildDrawingCache()
+     * @see #destroyDrawingCache()
+     */
+    public Bitmap getDrawingCache() {
+        if ((mViewFlags & WILL_NOT_CACHE_DRAWING) == WILL_NOT_CACHE_DRAWING) {
+            return null;
+        }
+        if ((mViewFlags & DRAWING_CACHE_ENABLED) == DRAWING_CACHE_ENABLED) {
+            buildDrawingCache();
+        }
+        return mDrawingCache == null ? null : mDrawingCache.get();
+    }
+
+    /**
+     * <p>Frees the resources used by the drawing cache. If you call
+     * {@link #buildDrawingCache()} manually without calling
+     * {@link #setDrawingCacheEnabled(boolean) setDrawingCacheEnabled(true)}, you
+     * should cleanup the cache with this method afterwards.</p>
+     *
+     * @see #setDrawingCacheEnabled(boolean)
+     * @see #buildDrawingCache()
+     * @see #getDrawingCache()
+     */
+    public void destroyDrawingCache() {
+        if (mDrawingCache != null) {
+            final Bitmap bitmap = mDrawingCache.get();
+            if (bitmap != null) bitmap.recycle();
+            mDrawingCache = null;
+        }
+    }
+
+    /**
+     * Setting a solid background color for the drawing cache's bitmaps will improve
+     * perfromance and memory usage. Note, though that this should only be used if this
+     * view will always be drawn on top of a solid color.
+     *
+     * @param color The background color to use for the drawing cache's bitmap
+     *
+     * @see #setDrawingCacheEnabled(boolean)
+     * @see #buildDrawingCache()
+     * @see #getDrawingCache()
+     */
+    public void setDrawingCacheBackgroundColor(int color) {
+        mDrawingCacheBackgroundColor = color;
+    }
+
+    /**
+     * @see #setDrawingCacheBackgroundColor(int)
+     *
+     * @return The background color to used for the drawing cache's bitmap
+     */
+    public int getDrawingCacheBackgroundColor() {
+        return mDrawingCacheBackgroundColor;
+    }
+
+    /**
+     * <p>Forces the drawing cache to be built if the drawing cache is invalid.</p>
+     *
+     * <p>If you call {@link #buildDrawingCache()} manually without calling
+     * {@link #setDrawingCacheEnabled(boolean) setDrawingCacheEnabled(true)}, you
+     * should cleanup the cache by calling {@link #destroyDrawingCache()} afterwards.</p>
+     *
+     * @see #getDrawingCache()
+     * @see #destroyDrawingCache()
+     */
+    public void buildDrawingCache() {
+        if ((mPrivateFlags & DRAWING_CACHE_VALID) == 0 || mDrawingCache == null ||
+                mDrawingCache.get() == null) {
+
+            if (ViewDebug.TRACE_HIERARCHY) {
+                ViewDebug.trace(this, ViewDebug.HierarchyTraceType.BUILD_CACHE);
+            }
+            if (ViewRoot.PROFILE_DRAWING) {
+                EventLog.writeEvent(60002, hashCode());
+            }
+
+            final int width = mRight - mLeft;
+            final int height = mBottom - mTop;
+
+            final int drawingCacheBackgroundColor = mDrawingCacheBackgroundColor;
+            final boolean opaque = drawingCacheBackgroundColor != 0 ||
+                (mBGDrawable != null && mBGDrawable.getOpacity() == PixelFormat.OPAQUE);
+
+            if (width <= 0 || height <= 0 ||
+                    (width * height * (opaque ? 2 : 4) >= // Projected bitmap size in bytes
+                            ViewConfiguration.get(mContext).getScaledMaximumDrawingCacheSize())) {
+                destroyDrawingCache();
+                return;
+            }
+
+            boolean clear = true;
+            Bitmap bitmap = mDrawingCache == null ? null : mDrawingCache.get();
+
+            if (bitmap == null || bitmap.getWidth() != width || bitmap.getHeight() != height) {
+
+                Bitmap.Config quality;
+                if (!opaque) {
+                    switch (mViewFlags & DRAWING_CACHE_QUALITY_MASK) {
+                        case DRAWING_CACHE_QUALITY_AUTO:
+                            quality = Bitmap.Config.ARGB_8888;
+                            break;
+                        case DRAWING_CACHE_QUALITY_LOW:
+                            quality = Bitmap.Config.ARGB_4444;
+                            break;
+                        case DRAWING_CACHE_QUALITY_HIGH:
+                            quality = Bitmap.Config.ARGB_8888;
+                            break;
+                        default:
+                            quality = Bitmap.Config.ARGB_8888;
+                            break;
+                    }
+                } else {
+                    quality = Bitmap.Config.RGB_565;
+                }
+
+                // Try to cleanup memory
+                if (bitmap != null) bitmap.recycle();
+
+                try {
+                    bitmap = Bitmap.createBitmap(width, height, quality);
+                    mDrawingCache = new SoftReference<Bitmap>(bitmap);
+                } catch (OutOfMemoryError e) {
+                    // If there is not enough memory to create the bitmap cache, just
+                    // ignore the issue as bitmap caches are not required to draw the
+                    // view hierarchy
+                    mDrawingCache = null;
+                    return;
+                }
+
+                clear = drawingCacheBackgroundColor != 0;
+            }
+
+            Canvas canvas;
+            final AttachInfo attachInfo = mAttachInfo;
+            if (attachInfo != null) {
+                canvas = attachInfo.mCanvas;
+                if (canvas == null) {
+                    canvas = new Canvas();
+                }
+                canvas.setBitmap(bitmap);
+                // Temporarily clobber the cached Canvas in case one of our children
+                // is also using a drawing cache. Without this, the children would
+                // steal the canvas by attaching their own bitmap to it and bad, bad
+                // thing would happen (invisible views, corrupted drawings, etc.)
+                attachInfo.mCanvas = null;
+            } else {
+                // This case should hopefully never or seldom happen
+                canvas = new Canvas(bitmap);
+            }
+
+            if (clear) {
+                bitmap.eraseColor(drawingCacheBackgroundColor);
+            }
+
+            computeScroll();
+            final int restoreCount = canvas.save();
+            canvas.translate(-mScrollX, -mScrollY);
+
+            mPrivateFlags |= DRAWN;
+
+            // Fast path for layouts with no backgrounds
+            if ((mPrivateFlags & SKIP_DRAW) == SKIP_DRAW) {
+                if (ViewDebug.TRACE_HIERARCHY) {
+                    ViewDebug.trace(this, ViewDebug.HierarchyTraceType.DRAW);
+                }
+                dispatchDraw(canvas);
+            } else {
+                draw(canvas);
+            }
+
+            canvas.restoreToCount(restoreCount);
+
+            if (attachInfo != null) {
+                // Restore the cached Canvas for our siblings
+                attachInfo.mCanvas = canvas;
+            }
+            mPrivateFlags |= DRAWING_CACHE_VALID;
+        }
+    }
+
+    /**
+     * Indicates whether this View is currently in edit mode. A View is usually
+     * in edit mode when displayed within a developer tool. For instance, if
+     * this View is being drawn by a visual user interface builder, this method
+     * should return true.
+     *
+     * Subclasses should check the return value of this method to provide
+     * different behaviors if their normal behavior might interfere with the
+     * host environment. For instance: the class spawns a thread in its
+     * constructor, the drawing code relies on device-specific features, etc.
+     *
+     * This method is usually checked in the drawing code of custom widgets.
+     *
+     * @return True if this View is in edit mode, false otherwise.
+     */
+    public boolean isInEditMode() {
+        return false;
+    }
+
+    /**
+     * If the View draws content inside its padding and enables fading edges,
+     * it needs to support padding offsets. Padding offsets are added to the
+     * fading edges to extend the length of the fade so that it covers pixels
+     * drawn inside the padding.
+     *
+     * Subclasses of this class should override this method if they need
+     * to draw content inside the padding.
+     *
+     * @return True if padding offset must be applied, false otherwise.
+     *
+     * @see #getLeftPaddingOffset()
+     * @see #getRightPaddingOffset()
+     * @see #getTopPaddingOffset()
+     * @see #getBottomPaddingOffset()
+     *
+     * @since CURRENT
+     */
+    protected boolean isPaddingOffsetRequired() {
+        return false;
+    }
+
+    /**
+     * Amount by which to extend the left fading region. Called only when
+     * {@link #isPaddingOffsetRequired()} returns true.
+     *
+     * @return The left padding offset in pixels.
+     *
+     * @see #isPaddingOffsetRequired()
+     *
+     * @since CURRENT
+     */
+    protected int getLeftPaddingOffset() {
+        return 0;
+    }
+
+    /**
+     * Amount by which to extend the right fading region. Called only when
+     * {@link #isPaddingOffsetRequired()} returns true.
+     *
+     * @return The right padding offset in pixels.
+     *
+     * @see #isPaddingOffsetRequired()
+     *
+     * @since CURRENT
+     */
+    protected int getRightPaddingOffset() {
+        return 0;
+    }
+
+    /**
+     * Amount by which to extend the top fading region. Called only when
+     * {@link #isPaddingOffsetRequired()} returns true.
+     *
+     * @return The top padding offset in pixels.
+     *
+     * @see #isPaddingOffsetRequired()
+     *
+     * @since CURRENT
+     */
+    protected int getTopPaddingOffset() {
+        return 0;
+    }
+
+    /**
+     * Amount by which to extend the bottom fading region. Called only when
+     * {@link #isPaddingOffsetRequired()} returns true.
+     *
+     * @return The bottom padding offset in pixels.
+     *
+     * @see #isPaddingOffsetRequired()
+     *
+     * @since CURRENT
+     */
+    protected int getBottomPaddingOffset() {
+        return 0;
+    }
+
+    /**
+     * Manually render this view (and all of its children) to the given Canvas.
+     * The view must have already done a full layout before this function is
+     * called.  When implementing a view, do not override this method; instead,
+     * you should implement {@link #onDraw}.
+     *
+     * @param canvas The Canvas to which the View is rendered.
+     */
+    public void draw(Canvas canvas) {
+        if (ViewDebug.TRACE_HIERARCHY) {
+            ViewDebug.trace(this, ViewDebug.HierarchyTraceType.DRAW);
+        }
+
+        mPrivateFlags |= DRAWN;                    
+
+        /*
+         * Draw traversal performs several drawing steps which must be executed
+         * in the appropriate order:
+         *
+         *      1. Draw the background
+         *      2. If necessary, save the canvas' layers to prepare for fading
+         *      3. Draw view's content
+         *      4. Draw children
+         *      5. If necessary, draw the fading edges and restore layers
+         *      6. Draw decorations (scrollbars for instance)
+         */
+
+        // Step 1, draw the background, if needed
+        int saveCount;
+
+        final Drawable background = mBGDrawable;
+        if (background != null) {
+            final int scrollX = mScrollX;
+            final int scrollY = mScrollY;
+
+            if (mBackgroundSizeChanged) {
+                background.setBounds(0, 0,  mRight - mLeft, mBottom - mTop);
+                mBackgroundSizeChanged = false;
+            }
+
+            if ((scrollX | scrollY) == 0) {
+                background.draw(canvas);
+            } else {
+                canvas.translate(scrollX, scrollY);
+                background.draw(canvas);
+                canvas.translate(-scrollX, -scrollY);
+            }
+        }
+
+        // skip step 2 & 5 if possible (common case)
+        final int viewFlags = mViewFlags;
+        boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0;
+        boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0;
+        if (!verticalEdges && !horizontalEdges) {
+            // Step 3, draw the content
+            onDraw(canvas);
+
+            // Step 4, draw the children
+            dispatchDraw(canvas);
+
+            // Step 6, draw decorations (scrollbars)
+            onDrawScrollBars(canvas);
+
+            // we're done...
+            return;
+        }
+
+        /*
+         * Here we do the full fledged routine...
+         * (this is an uncommon case where speed matters less,
+         * this is why we repeat some of the tests that have been
+         * done above)
+         */
+
+        boolean drawTop = false;
+        boolean drawBottom = false;
+        boolean drawLeft = false;
+        boolean drawRight = false;
+
+        float topFadeStrength = 0.0f;
+        float bottomFadeStrength = 0.0f;
+        float leftFadeStrength = 0.0f;
+        float rightFadeStrength = 0.0f;
+
+        // Step 2, save the canvas' layers
+        int paddingLeft = mPaddingLeft;
+        int paddingTop = mPaddingTop;
+
+        final boolean offsetRequired = isPaddingOffsetRequired();
+        if (offsetRequired) {
+            paddingLeft += getLeftPaddingOffset();
+            paddingTop += getTopPaddingOffset();
+        }
+
+        int left = mScrollX + paddingLeft;
+        int right = left + mRight - mLeft - mPaddingRight - paddingLeft;
+        int top = mScrollY + paddingTop;
+        int bottom = top + mBottom - mTop - mPaddingBottom - paddingTop;
+
+        if (offsetRequired) {
+            right += getRightPaddingOffset();
+            bottom += getBottomPaddingOffset();
+        }
+
+        final ScrollabilityCache scrollabilityCache = mScrollCache;
+        int length = scrollabilityCache.fadingEdgeLength;
+
+        // clip the fade length if top and bottom fades overlap
+        // overlapping fades produce odd-looking artifacts
+        if (verticalEdges && (top + length > bottom - length)) {
+            length = (bottom - top) / 2;
+        }
+
+        // also clip horizontal fades if necessary
+        if (horizontalEdges && (left + length > right - length)) {
+            length = (right - left) / 2;
+        }
+
+        if (verticalEdges) {
+            topFadeStrength = Math.max(0.0f, Math.min(1.0f, getTopFadingEdgeStrength()));
+            drawTop = topFadeStrength >= 0.0f;
+            bottomFadeStrength = Math.max(0.0f, Math.min(1.0f, getBottomFadingEdgeStrength()));
+            drawBottom = bottomFadeStrength >= 0.0f;
+        }
+
+        if (horizontalEdges) {
+            leftFadeStrength = Math.max(0.0f, Math.min(1.0f, getLeftFadingEdgeStrength()));
+            drawLeft = leftFadeStrength >= 0.0f;
+            rightFadeStrength = Math.max(0.0f, Math.min(1.0f, getRightFadingEdgeStrength()));
+            drawRight = rightFadeStrength >= 0.0f;
+        }
+
+        saveCount = canvas.getSaveCount();
+
+        int solidColor = getSolidColor();
+        if (solidColor == 0) {
+            final int flags = Canvas.HAS_ALPHA_LAYER_SAVE_FLAG;
+
+            if (drawTop) {
+                canvas.saveLayer(left, top, right, top + length, null, flags);
+            }
+
+            if (drawBottom) {
+                canvas.saveLayer(left, bottom - length, right, bottom, null, flags);
+            }
+
+            if (drawLeft) {
+                canvas.saveLayer(left, top, left + length, bottom, null, flags);
+            }
+
+            if (drawRight) {
+                canvas.saveLayer(right - length, top, right, bottom, null, flags);
+            }
+        } else {
+            scrollabilityCache.setFadeColor(solidColor);
+        }
+
+        // Step 3, draw the content
+        onDraw(canvas);
+
+        // Step 4, draw the children
+        dispatchDraw(canvas);
+
+        // Step 5, draw the fade effect and restore layers
+        final Paint p = scrollabilityCache.paint;
+        final Matrix matrix = scrollabilityCache.matrix;
+        final Shader fade = scrollabilityCache.shader;
+        final float fadeHeight = scrollabilityCache.fadingEdgeLength;
+
+        if (drawTop) {
+            matrix.setScale(1, fadeHeight * topFadeStrength);
+            matrix.postTranslate(left, top);
+            fade.setLocalMatrix(matrix);
+            canvas.drawRect(left, top, right, top + length, p);
+        }
+
+        if (drawBottom) {
+            matrix.setScale(1, fadeHeight * bottomFadeStrength);
+            matrix.postRotate(180);
+            matrix.postTranslate(left, bottom);
+            fade.setLocalMatrix(matrix);
+            canvas.drawRect(left, bottom - length, right, bottom, p);
+        }
+
+        if (drawLeft) {
+            matrix.setScale(1, fadeHeight * leftFadeStrength);
+            matrix.postRotate(-90);
+            matrix.postTranslate(left, top);
+            fade.setLocalMatrix(matrix);
+            canvas.drawRect(left, top, left + length, bottom, p);
+        }
+
+        if (drawRight) {
+            matrix.setScale(1, fadeHeight * rightFadeStrength);
+            matrix.postRotate(90);
+            matrix.postTranslate(right, top);
+            fade.setLocalMatrix(matrix);
+            canvas.drawRect(right - length, top, right, bottom, p);
+        }
+
+        canvas.restoreToCount(saveCount);
+
+        // Step 6, draw decorations (scrollbars)
+        onDrawScrollBars(canvas);
+    }
+
+    /**
+     * Override this if your view is known to always be drawn on top of a solid color background,
+     * and needs to draw fading edges. Returning a non-zero color enables the view system to
+     * optimize the drawing of the fading edges. If you do return a non-zero color, the alpha
+     * should be set to 0xFF.
+     *
+     * @see #setVerticalFadingEdgeEnabled
+     * @see #setHorizontalFadingEdgeEnabled
+     *
+     * @return The known solid color background for this view, or 0 if the color may vary
+     */
+    public int getSolidColor() {
+        return 0;
+    }
+
+    /**
+     * Build a human readable string representation of the specified view flags.
+     *
+     * @param flags the view flags to convert to a string
+     * @return a String representing the supplied flags
+     */
+    private static String printFlags(int flags) {
+        String output = "";
+        int numFlags = 0;
+        if ((flags & FOCUSABLE_MASK) == FOCUSABLE) {
+            output += "TAKES_FOCUS";
+            numFlags++;
+        }
+
+        switch (flags & VISIBILITY_MASK) {
+        case INVISIBLE:
+            if (numFlags > 0) {
+                output += " ";
+            }
+            output += "INVISIBLE";
+            // USELESS HERE numFlags++;
+            break;
+        case GONE:
+            if (numFlags > 0) {
+                output += " ";
+            }
+            output += "GONE";
+            // USELESS HERE numFlags++;
+            break;
+        default:
+            break;
+        }
+        return output;
+    }
+
+    /**
+     * Build a human readable string representation of the specified private
+     * view flags.
+     *
+     * @param privateFlags the private view flags to convert to a string
+     * @return a String representing the supplied flags
+     */
+    private static String printPrivateFlags(int privateFlags) {
+        String output = "";
+        int numFlags = 0;
+
+        if ((privateFlags & WANTS_FOCUS) == WANTS_FOCUS) {
+            output += "WANTS_FOCUS";
+            numFlags++;
+        }
+
+        if ((privateFlags & FOCUSED) == FOCUSED) {
+            if (numFlags > 0) {
+                output += " ";
+            }
+            output += "FOCUSED";
+            numFlags++;
+        }
+
+        if ((privateFlags & SELECTED) == SELECTED) {
+            if (numFlags > 0) {
+                output += " ";
+            }
+            output += "SELECTED";
+            numFlags++;
+        }
+
+        if ((privateFlags & IS_ROOT_NAMESPACE) == IS_ROOT_NAMESPACE) {
+            if (numFlags > 0) {
+                output += " ";
+            }
+            output += "IS_ROOT_NAMESPACE";
+            numFlags++;
+        }
+
+        if ((privateFlags & HAS_BOUNDS) == HAS_BOUNDS) {
+            if (numFlags > 0) {
+                output += " ";
+            }
+            output += "HAS_BOUNDS";
+            numFlags++;
+        }
+
+        if ((privateFlags & DRAWN) == DRAWN) {
+            if (numFlags > 0) {
+                output += " ";
+            }
+            output += "DRAWN";
+            // USELESS HERE numFlags++;
+        }
+        return output;
+    }
+
+    /**
+     * <p>Indicates whether or not this view's layout will be requested during
+     * the next hierarchy layout pass.</p>
+     *
+     * @return true if the layout will be forced during next layout pass
+     */
+    public boolean isLayoutRequested() {
+        return (mPrivateFlags & FORCE_LAYOUT) == FORCE_LAYOUT;
+    }
+
+    /**
+     * Assign a size and position to a view and all of its
+     * descendants
+     *
+     * <p>This is the second phase of the layout mechanism.
+     * (The first is measuring). In this phase, each parent calls
+     * layout on all of its children to position them.
+     * This is typically done using the child measurements
+     * that were stored in the measure pass().
+     *
+     * Derived classes with children should override
+     * onLayout. In that method, they should
+     * call layout on each of their their children.
+     *
+     * @param l Left position, relative to parent
+     * @param t Top position, relative to parent
+     * @param r Right position, relative to parent
+     * @param b Bottom position, relative to parent
+     */
+    public final void layout(int l, int t, int r, int b) {
+        boolean changed = setFrame(l, t, r, b);
+        if (changed || (mPrivateFlags & LAYOUT_REQUIRED) == LAYOUT_REQUIRED) {
+            if (ViewDebug.TRACE_HIERARCHY) {
+                ViewDebug.trace(this, ViewDebug.HierarchyTraceType.ON_LAYOUT);
+            }
+
+            onLayout(changed, l, t, r, b);
+            mPrivateFlags &= ~LAYOUT_REQUIRED;
+        }
+        mPrivateFlags &= ~FORCE_LAYOUT;
+    }
+
+    /**
+     * Called from layout when this view should
+     * assign a size and position to each of its children.
+     *
+     * Derived classes with children should override
+     * this method and call layout on each of
+     * their their children.
+     * @param changed This is a new size or position for this view
+     * @param left Left position, relative to parent
+     * @param top Top position, relative to parent
+     * @param right Right position, relative to parent
+     * @param bottom Bottom position, relative to parent
+     */
+    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+    }
+
+    /**
+     * Assign a size and position to this view.
+     *
+     * This is called from layout.
+     *
+     * @param left Left position, relative to parent
+     * @param top Top position, relative to parent
+     * @param right Right position, relative to parent
+     * @param bottom Bottom position, relative to parent
+     * @return true if the new size and position are different than the
+     *         previous ones
+     * {@hide}
+     */
+    protected boolean setFrame(int left, int top, int right, int bottom) {
+        boolean changed = false;
+
+        if (DBG) {
+            System.out.println(this + " View.setFrame(" + left + "," + top + ","
+                    + right + "," + bottom + ")");
+        }
+
+        if (mLeft != left || mRight != right || mTop != top || mBottom != bottom) {
+            changed = true;
+
+            // Remember our drawn bit
+            int drawn = mPrivateFlags & DRAWN;
+
+            // Invalidate our old position
+            invalidate();
+
+
+            int oldWidth = mRight - mLeft;
+            int oldHeight = mBottom - mTop;
+
+            mLeft = left;
+            mTop = top;
+            mRight = right;
+            mBottom = bottom;
+
+            mPrivateFlags |= HAS_BOUNDS;
+
+            int newWidth = right - left;
+            int newHeight = bottom - top;
+
+            if (newWidth != oldWidth || newHeight != oldHeight) {
+                onSizeChanged(newWidth, newHeight, oldWidth, oldHeight);
+            }
+
+            if ((mViewFlags & VISIBILITY_MASK) == VISIBLE) {
+                // If we are visible, force the DRAWN bit to on so that
+                // this invalidate will go through (at least to our parent).
+                // This is because someone may have invalidated this view
+                // before this call to setFrame came in, therby clearing
+                // the DRAWN bit.
+                mPrivateFlags |= DRAWN;
+                invalidate();
+            }
+
+            // Reset drawn bit to original value (invalidate turns it off)
+            mPrivateFlags |= drawn;
+
+            mBackgroundSizeChanged = true;
+        }
+        return changed;
+    }
+
+    /**
+     * Finalize inflating a view from XML.  This is called as the last phase
+     * of inflation, after all child views have been added.
+     *
+     * <p>Even if the subclass overrides onFinishInflate, they should always be
+     * sure to call the super method, so that we get called.
+     */
+    protected void onFinishInflate() {
+    }
+
+    /**
+     * Returns the resources associated with this view.
+     *
+     * @return Resources object.
+     */
+    public Resources getResources() {
+        return mResources;
+    }
+
+    /**
+     * Invalidates the specified Drawable.
+     *
+     * @param drawable the drawable to invalidate
+     */
+    public void invalidateDrawable(Drawable drawable) {
+        if (verifyDrawable(drawable)) {
+            final Rect dirty = drawable.getBounds();
+            final int scrollX = mScrollX;
+            final int scrollY = mScrollY;
+
+            invalidate(dirty.left + scrollX, dirty.top + scrollY,
+                    dirty.right + scrollX, dirty.bottom + scrollY);
+        }
+    }
+
+    /**
+     * Schedules an action on a drawable to occur at a specified time.
+     *
+     * @param who the recipient of the action
+     * @param what the action to run on the drawable
+     * @param when the time at which the action must occur. Uses the
+     *        {@link SystemClock#uptimeMillis} timebase.
+     */
+    public void scheduleDrawable(Drawable who, Runnable what, long when) {
+        if (verifyDrawable(who) && what != null && mAttachInfo != null) {
+            mAttachInfo.mHandler.postAtTime(what, who, when);
+        }
+    }
+
+    /**
+     * Cancels a scheduled action on a drawable.
+     *
+     * @param who the recipient of the action
+     * @param what the action to cancel
+     */
+    public void unscheduleDrawable(Drawable who, Runnable what) {
+        if (verifyDrawable(who) && what != null && mAttachInfo != null) {
+            mAttachInfo.mHandler.removeCallbacks(what, who);
+        }
+    }
+
+    /**
+     * Unschedule any events associated with the given Drawable.  This can be
+     * used when selecting a new Drawable into a view, so that the previous
+     * one is completely unscheduled.
+     *
+     * @param who The Drawable to unschedule.
+     *
+     * @see #drawableStateChanged
+     */
+    public void unscheduleDrawable(Drawable who) {
+        if (mAttachInfo != null) {
+            mAttachInfo.mHandler.removeCallbacksAndMessages(who);
+        }
+    }
+
+    /**
+     * If your view subclass is displaying its own Drawable objects, it should
+     * override this function and return true for any Drawable it is
+     * displaying.  This allows animations for those drawables to be
+     * scheduled.
+     *
+     * <p>Be sure to call through to the super class when overriding this
+     * function.
+     *
+     * @param who The Drawable to verify.  Return true if it is one you are
+     *            displaying, else return the result of calling through to the
+     *            super class.
+     *
+     * @return boolean If true than the Drawable is being displayed in the
+     *         view; else false and it is not allowed to animate.
+     *
+     * @see #unscheduleDrawable
+     * @see #drawableStateChanged
+     */
+    protected boolean verifyDrawable(Drawable who) {
+        return who == mBGDrawable;
+    }
+
+    /**
+     * This function is called whenever the state of the view changes in such
+     * a way that it impacts the state of drawables being shown.
+     *
+     * <p>Be sure to call through to the superclass when overriding this
+     * function.
+     *
+     * @see Drawable#setState
+     */
+    protected void drawableStateChanged() {
+        Drawable d = mBGDrawable;
+        if (d != null && d.isStateful()) {
+            d.setState(getDrawableState());
+        }
+    }
+
+    /**
+     * Call this to force a view to update its drawable state. This will cause
+     * drawableStateChanged to be called on this view. Views that are interested
+     * in the new state should call getDrawableState.
+     *
+     * @see #drawableStateChanged
+     * @see #getDrawableState
+     */
+    public void refreshDrawableState() {
+        mPrivateFlags |= DRAWABLE_STATE_DIRTY;
+        drawableStateChanged();
+
+        ViewParent parent = mParent;
+        if (parent != null) {
+            parent.childDrawableStateChanged(this);
+        }
+    }
+
+    /**
+     * Return an array of resource IDs of the drawable states representing the
+     * current state of the view.
+     *
+     * @return The current drawable state
+     *
+     * @see Drawable#setState
+     * @see #drawableStateChanged
+     * @see #onCreateDrawableState
+     */
+    public final int[] getDrawableState() {
+        if ((mDrawableState != null) && ((mPrivateFlags & DRAWABLE_STATE_DIRTY) == 0)) {
+            return mDrawableState;
+        } else {
+            mDrawableState = onCreateDrawableState(0);
+            mPrivateFlags &= ~DRAWABLE_STATE_DIRTY;
+            return mDrawableState;
+        }
+    }
+
+    /**
+     * Generate the new {@link android.graphics.drawable.Drawable} state for
+     * this view. This is called by the view
+     * system when the cached Drawable state is determined to be invalid.  To
+     * retrieve the current state, you should use {@link #getDrawableState}.
+     *
+     * @param extraSpace if non-zero, this is the number of extra entries you
+     * would like in the returned array in which you can place your own
+     * states.
+     *
+     * @return Returns an array holding the current {@link Drawable} state of
+     * the view.
+     *
+     * @see #mergeDrawableStates
+     */
+    protected int[] onCreateDrawableState(int extraSpace) {
+        if ((mViewFlags & DUPLICATE_PARENT_STATE) == DUPLICATE_PARENT_STATE &&
+                mParent instanceof View) {
+            return ((View) mParent).onCreateDrawableState(extraSpace);
+        }
+
+        int[] drawableState;
+
+        int privateFlags = mPrivateFlags;
+
+        int viewStateIndex = (((privateFlags & PRESSED) != 0) ? 1 : 0);
+
+        viewStateIndex = (viewStateIndex << 1)
+                + (((mViewFlags & ENABLED_MASK) == ENABLED) ? 1 : 0);
+
+        viewStateIndex = (viewStateIndex << 1) + (isFocused() ? 1 : 0);
+
+        viewStateIndex = (viewStateIndex << 1)
+                + (((privateFlags & SELECTED) != 0) ? 1 : 0);
+
+        final boolean hasWindowFocus = hasWindowFocus();
+        viewStateIndex = (viewStateIndex << 1) + (hasWindowFocus ? 1 : 0);
+
+        drawableState = VIEW_STATE_SETS[viewStateIndex];
+
+        //noinspection ConstantIfStatement
+        if (false) {
+            Log.i("View", "drawableStateIndex=" + viewStateIndex);
+            Log.i("View", toString()
+                    + " pressed=" + ((privateFlags & PRESSED) != 0)
+                    + " en=" + ((mViewFlags & ENABLED_MASK) == ENABLED)
+                    + " fo=" + hasFocus()
+                    + " sl=" + ((privateFlags & SELECTED) != 0)
+                    + " wf=" + hasWindowFocus
+                    + ": " + Arrays.toString(drawableState));
+        }
+
+        if (extraSpace == 0) {
+            return drawableState;
+        }
+
+        final int[] fullState;
+        if (drawableState != null) {
+            fullState = new int[drawableState.length + extraSpace];
+            System.arraycopy(drawableState, 0, fullState, 0, drawableState.length);
+        } else {
+            fullState = new int[extraSpace];
+        }
+
+        return fullState;
+    }
+
+    /**
+     * Merge your own state values in <var>additionalState</var> into the base
+     * state values <var>baseState</var> that were returned by
+     * {@link #onCreateDrawableState}.
+     *
+     * @param baseState The base state values returned by
+     * {@link #onCreateDrawableState}, which will be modified to also hold your
+     * own additional state values.
+     *
+     * @param additionalState The additional state values you would like
+     * added to <var>baseState</var>; this array is not modified.
+     *
+     * @return As a convenience, the <var>baseState</var> array you originally
+     * passed into the function is returned.
+     *
+     * @see #onCreateDrawableState
+     */
+    protected static int[] mergeDrawableStates(int[] baseState, int[] additionalState) {
+        final int N = baseState.length;
+        int i = N - 1;
+        while (i >= 0 && baseState[i] == 0) {
+            i--;
+        }
+        System.arraycopy(additionalState, 0, baseState, i + 1, additionalState.length);
+        return baseState;
+    }
+
+    /**
+     * Sets the background color for this view.
+     * @param color the color of the background
+     */
+    public void setBackgroundColor(int color) {
+        setBackgroundDrawable(new ColorDrawable(color));
+    }
+
+    /**
+     * Set the background to a given resource. The resource should refer to
+     * a Drawable object.
+     * @param resid The identifier of the resource.
+     * @attr ref android.R.styleable#View_background
+     */
+    public void setBackgroundResource(int resid) {
+        if (resid != 0 && resid == mBackgroundResource) {
+            return;
+        }
+
+        Drawable d= null;
+        if (resid != 0) {
+            d = mResources.getDrawable(resid);
+        }
+        setBackgroundDrawable(d);
+
+        mBackgroundResource = resid;
+    }
+
+    /**
+     * Set the background to a given Drawable, or remove the background. If the
+     * background has padding, this View's padding is set to the background's
+     * padding. However, when a background is removed, this View's padding isn't
+     * touched. If setting the padding is desired, please use
+     * {@link #setPadding(int, int, int, int)}.
+     *
+     * @param d The Drawable to use as the background, or null to remove the
+     *        background
+     */
+    public void setBackgroundDrawable(Drawable d) {
+        boolean requestLayout = false;
+
+        mBackgroundResource = 0;
+
+        /*
+         * Regardless of whether we're setting a new background or not, we want
+         * to clear the previous drawable.
+         */
+        if (mBGDrawable != null) {
+            mBGDrawable.setCallback(null);
+            unscheduleDrawable(mBGDrawable);
+        }
+
+        if (d != null) {
+            Rect padding = sThreadLocal.get();
+            if (padding == null) {
+                padding = new Rect();
+                sThreadLocal.set(padding);
+            }
+            if (d.getPadding(padding)) {
+                setPadding(padding.left, padding.top, padding.right, padding.bottom);
+            }
+
+            // Compare the minimum sizes of the old Drawable and the new.  If there isn't an old or
+            // if it has a different minimum size, we should layout again
+            if (mBGDrawable == null || mBGDrawable.getMinimumHeight() != d.getMinimumHeight() ||
+                    mBGDrawable.getMinimumWidth() != d.getMinimumWidth()) {
+                requestLayout = true;
+            }
+
+            d.setCallback(this);
+            if (d.isStateful()) {
+                d.setState(getDrawableState());
+            }
+            d.setVisible(getVisibility() == VISIBLE, false);
+            mBGDrawable = d;
+
+            if ((mPrivateFlags & SKIP_DRAW) != 0) {
+                mPrivateFlags &= ~SKIP_DRAW;
+                mPrivateFlags |= ONLY_DRAWS_BACKGROUND;
+                requestLayout = true;
+            }
+        } else {
+            /* Remove the background */
+            mBGDrawable = null;
+
+            if ((mPrivateFlags & ONLY_DRAWS_BACKGROUND) != 0) {
+                /*
+                 * This view ONLY drew the background before and we're removing
+                 * the background, so now it won't draw anything
+                 * (hence we SKIP_DRAW)
+                 */
+                mPrivateFlags &= ~ONLY_DRAWS_BACKGROUND;
+                mPrivateFlags |= SKIP_DRAW;
+            }
+
+            /*
+             * When the background is set, we try to apply its padding to this
+             * View. When the background is removed, we don't touch this View's
+             * padding. This is noted in the Javadocs. Hence, we don't need to
+             * requestLayout(), the invalidate() below is sufficient.
+             */
+
+            // The old background's minimum size could have affected this
+            // View's layout, so let's requestLayout
+            requestLayout = true;
+        }
+
+        if (requestLayout) {
+            requestLayout();
+        }
+
+        mBackgroundSizeChanged = true;
+        invalidate();
+    }
+
+    /**
+     * Gets the background drawable
+     * @return The drawable used as the background for this view, if any.
+     */
+    public Drawable getBackground() {
+        return mBGDrawable;
+    }
+
+    private int getScrollBarPaddingLeft() {
+        // TODO: Deal with RTL languages
+        return 0;
+    }
+
+    /*
+     * Returns the pixels occupied by the vertical scrollbar, if not overlaid
+     */
+    private int getScrollBarPaddingRight() {
+        // TODO: Deal with RTL languages
+        if ((mViewFlags & SCROLLBARS_VERTICAL) == 0) {
+            return 0;
+        }
+        return (mViewFlags & SCROLLBARS_INSET_MASK) == 0 ? 0 : getVerticalScrollbarWidth();
+    }
+
+    /*
+     * Returns the pixels occupied by the horizontal scrollbar, if not overlaid
+     */
+    private int getScrollBarPaddingBottom() {
+        if ((mViewFlags & SCROLLBARS_HORIZONTAL) == 0) {
+            return 0;
+        }
+        return (mViewFlags & SCROLLBARS_INSET_MASK) == 0 ? 0 : getHorizontalScrollbarHeight();
+    }
+
+    /**
+     * Sets the padding. The view may add on the space required to display
+     * the scrollbars, depending on the style and visibility of the scrollbars.
+     * So the values returned from {@link #getPaddingLeft}, {@link #getPaddingTop},
+     * {@link #getPaddingRight} and {@link #getPaddingBottom} may be different
+     * from the values set in this call.
+     *
+     * @attr ref android.R.styleable#View_padding
+     * @attr ref android.R.styleable#View_paddingBottom
+     * @attr ref android.R.styleable#View_paddingLeft
+     * @attr ref android.R.styleable#View_paddingRight
+     * @attr ref android.R.styleable#View_paddingTop
+     * @param left the left padding in pixels
+     * @param top the top padding in pixels
+     * @param right the right padding in pixels
+     * @param bottom the bottom padding in pixels
+     */
+    public void setPadding(int left, int top, int right, int bottom) {
+        boolean changed = false;
+
+        mUserPaddingRight = right;
+        mUserPaddingBottom = bottom;
+
+        if (mPaddingLeft != left + getScrollBarPaddingLeft()) {
+            changed = true;
+            mPaddingLeft = left;
+        }
+        if (mPaddingTop != top) {
+            changed = true;
+            mPaddingTop = top;
+        }
+        if (mPaddingRight != right + getScrollBarPaddingRight()) {
+            changed = true;
+            mPaddingRight = right + getScrollBarPaddingRight();
+        }
+        if (mPaddingBottom != bottom + getScrollBarPaddingBottom()) {
+            changed = true;
+            mPaddingBottom = bottom + getScrollBarPaddingBottom();
+        }
+
+        if (changed) {
+            requestLayout();
+        }
+    }
+
+    /**
+     * Returns the top padding of this view.
+     *
+     * @return the top padding in pixels
+     */
+    public int getPaddingTop() {
+        return mPaddingTop;
+    }
+
+    /**
+     * Returns the bottom padding of this view. If there are inset and enabled
+     * scrollbars, this value may include the space required to display the
+     * scrollbars as well.
+     *
+     * @return the bottom padding in pixels
+     */
+    public int getPaddingBottom() {
+        return mPaddingBottom;
+    }
+
+    /**
+     * Returns the left padding of this view. If there are inset and enabled
+     * scrollbars, this value may include the space required to display the
+     * scrollbars as well.
+     *
+     * @return the left padding in pixels
+     */
+    public int getPaddingLeft() {
+        return mPaddingLeft;
+    }
+
+    /**
+     * Returns the right padding of this view. If there are inset and enabled
+     * scrollbars, this value may include the space required to display the
+     * scrollbars as well.
+     *
+     * @return the right padding in pixels
+     */
+    public int getPaddingRight() {
+        return mPaddingRight;
+    }
+
+    /**
+     * Changes the selection state of this view. A view can be selected or not.
+     * Note that selection is not the same as focus. Views are typically
+     * selected in the context of an AdapterView like ListView or GridView;
+     * the selected view is the view that is highlighted.
+     *
+     * @param selected true if the view must be selected, false otherwise
+     */
+    public void setSelected(boolean selected) {
+        if (((mPrivateFlags & SELECTED) != 0) != selected) {
+            mPrivateFlags = (mPrivateFlags & ~SELECTED) | (selected ? SELECTED : 0);
+            invalidate();
+            refreshDrawableState();
+            dispatchSetSelected(selected);
+        }
+    }
+
+    /**
+     * Dispatch setSelected to all of this View's children.
+     *
+     * @see #setSelected(boolean)
+     *
+     * @param selected The new selected state
+     */
+    protected void dispatchSetSelected(boolean selected) {
+    }
+
+    /**
+     * Indicates the selection state of this view.
+     *
+     * @return true if the view is selected, false otherwise
+     */
+    @ViewDebug.ExportedProperty
+    public boolean isSelected() {
+        return (mPrivateFlags & SELECTED) != 0;
+    }
+
+    /**
+     * Returns the ViewTreeObserver for this view's hierarchy. The view tree
+     * observer can be used to get notifications when global events, like
+     * layout, happen.
+     *
+     * The returned ViewTreeObserver observer is not guaranteed to remain
+     * valid for the lifetime of this View. If the caller of this method keeps
+     * a long-lived reference to ViewTreeObserver, it should always check for
+     * the return value of {@link ViewTreeObserver#isAlive()}.
+     *
+     * @return The ViewTreeObserver for this view's hierarchy.
+     */
+    public ViewTreeObserver getViewTreeObserver() {
+        if (mAttachInfo != null) {
+            return mAttachInfo.mTreeObserver;
+        }
+        if (mFloatingTreeObserver == null) {
+            mFloatingTreeObserver = new ViewTreeObserver();
+        }
+        return mFloatingTreeObserver;
+    }
+
+    /**
+     * <p>Finds the topmost view in the current view hierarchy.</p>
+     *
+     * @return the topmost view containing this view
+     */
+    public View getRootView() {
+        if (mAttachInfo != null) {
+            final View v = mAttachInfo.mRootView;
+            if (v != null) {
+                return v;
+            }
+        }
+        
+        View parent = this;
+
+        while (parent.mParent != null && parent.mParent instanceof View) {
+            parent = (View) parent.mParent;
+        }
+
+        return parent;
+    }
+
+    /**
+     * <p>Computes the coordinates of this view on the screen. The argument
+     * must be an array of two integers. After the method returns, the array
+     * contains the x and y location in that order.</p>
+     *
+     * @param location an array of two integers in which to hold the coordinates
+     */
+    public void getLocationOnScreen(int[] location) {
+        getLocationInWindow(location);
+
+        final AttachInfo info = mAttachInfo;
+        location[0] += info.mWindowLeft;
+        location[1] += info.mWindowTop;
+    }
+
+    /**
+     * <p>Computes the coordinates of this view in its window. The argument
+     * must be an array of two integers. After the method returns, the array
+     * contains the x and y location in that order.</p>
+     *
+     * @param location an array of two integers in which to hold the coordinates
+     */
+    public void getLocationInWindow(int[] location) {
+        if (location == null || location.length < 2) {
+            throw new IllegalArgumentException("location must be an array of "
+                    + "two integers");
+        }
+
+        location[0] = mLeft;
+        location[1] = mTop;
+
+        ViewParent viewParent = mParent;
+        while (viewParent instanceof View) {
+            final View view = (View)viewParent;
+            location[0] += view.mLeft - view.mScrollX;
+            location[1] += view.mTop - view.mScrollY;
+            viewParent = view.mParent;
+        }
+        
+        if (viewParent instanceof ViewRoot) {
+            // *cough*
+            final ViewRoot vr = (ViewRoot)viewParent;
+            location[1] -= vr.mCurScrollY;
+        }
+    }
+
+    /**
+     * {@hide}
+     * @param id the id of the view to be found
+     * @return the view of the specified id, null if cannot be found
+     */
+    protected View findViewTraversal(int id) {
+        if (id == mID) {
+            return this;
+        }
+        return null;
+    }
+
+    /**
+     * {@hide}
+     * @param tag the tag of the view to be found
+     * @return the view of specified tag, null if cannot be found
+     */
+    protected View findViewWithTagTraversal(Object tag) {
+        if (tag != null && tag.equals(mTag)) {
+            return this;
+        }
+        return null;
+    }
+
+    /**
+     * Look for a child view with the given id.  If this view has the given
+     * id, return this view.
+     *
+     * @param id The id to search for.
+     * @return The view that has the given id in the hierarchy or null
+     */
+    public final View findViewById(int id) {
+        if (id < 0) {
+            return null;
+        }
+        return findViewTraversal(id);
+    }
+
+    /**
+     * Look for a child view with the given tag.  If this view has the given
+     * tag, return this view.
+     *
+     * @param tag The tag to search for, using "tag.equals(getTag())".
+     * @return The View that has the given tag in the hierarchy or null
+     */
+    public final View findViewWithTag(Object tag) {
+        if (tag == null) {
+            return null;
+        }
+        return findViewWithTagTraversal(tag);
+    }
+
+    /**
+     * Sets the identifier for this view. The identifier does not have to be
+     * unique in this view's hierarchy. The identifier should be a positive
+     * number.
+     *
+     * @see #NO_ID
+     * @see #getId
+     * @see #findViewById
+     *
+     * @param id a number used to identify the view
+     *
+     * @attr ref android.R.styleable#View_id
+     */
+    public void setId(int id) {
+        mID = id;
+    }
+
+    /**
+     * {@hide}
+     *
+     * @param isRoot true if the view belongs to the root namespace, false
+     *        otherwise
+     */
+    public void setIsRootNamespace(boolean isRoot) {
+        if (isRoot) {
+            mPrivateFlags |= IS_ROOT_NAMESPACE;
+        } else {
+            mPrivateFlags &= ~IS_ROOT_NAMESPACE;
+        }
+    }
+
+    /**
+     * {@hide}
+     *
+     * @return true if the view belongs to the root namespace, false otherwise
+     */
+    public boolean isRootNamespace() {
+        return (mPrivateFlags&IS_ROOT_NAMESPACE) != 0;
+    }
+
+    /**
+     * Returns this view's identifier.
+     *
+     * @return a positive integer used to identify the view or {@link #NO_ID}
+     *         if the view has no ID
+     *
+     * @see #setId
+     * @see #findViewById
+     * @attr ref android.R.styleable#View_id
+     */
+    @ViewDebug.CapturedViewProperty
+    public int getId() {
+        return mID;
+    }
+
+    /**
+     * Returns this view's tag.
+     *
+     * @return the Object stored in this view as a tag
+     */
+    @ViewDebug.ExportedProperty
+    public Object getTag() {
+        return mTag;
+    }
+
+    /**
+     * Sets the tag associated with this view. A tag can be used to mark
+     * a view in its hierarchy and does not have to be unique within the
+     * hierarchy. Tags can also be used to store data within a view without
+     * resorting to another data structure.
+     *
+     * @param tag an Object to tag the view with
+     */
+    public void setTag(final Object tag) {
+        mTag = tag;
+    }
+
+    /**
+     * Prints information about this view in the log output, with the tag
+     * {@link #VIEW_LOG_TAG}.
+     *
+     * @hide
+     */
+    public void debug() {
+        debug(0);
+    }
+
+    /**
+     * Prints information about this view in the log output, with the tag
+     * {@link #VIEW_LOG_TAG}. Each line in the output is preceded with an
+     * indentation defined by the <code>depth</code>.
+     *
+     * @param depth the indentation level
+     *
+     * @hide
+     */
+    protected void debug(int depth) {
+        String output = debugIndent(depth - 1);
+
+        output += "+ " + this;
+        int id = getId();
+        if (id != -1) {
+            output += " (id=" + id + ")";
+        }
+        Object tag = getTag();
+        if (tag != null) {
+            output += " (tag=" + tag + ")";
+        }
+        Log.d(VIEW_LOG_TAG, output);
+
+        if ((mPrivateFlags & FOCUSED) != 0) {
+            output = debugIndent(depth) + " FOCUSED";
+            Log.d(VIEW_LOG_TAG, output);
+        }
+
+        output = debugIndent(depth);
+        output += "frame={" + mLeft + ", " + mTop + ", " + mRight
+                + ", " + mBottom + "} scroll={" + mScrollX + ", " + mScrollY
+                + "} ";
+        Log.d(VIEW_LOG_TAG, output);
+
+        if (mPaddingLeft != 0 || mPaddingTop != 0 || mPaddingRight != 0
+                || mPaddingBottom != 0) {
+            output = debugIndent(depth);
+            output += "padding={" + mPaddingLeft + ", " + mPaddingTop
+                    + ", " + mPaddingRight + ", " + mPaddingBottom + "}";
+            Log.d(VIEW_LOG_TAG, output);
+        }
+
+        output = debugIndent(depth);
+        output += "mMeasureWidth=" + mMeasuredWidth +
+                " mMeasureHeight=" + mMeasuredHeight;
+        Log.d(VIEW_LOG_TAG, output);
+
+        output = debugIndent(depth);
+        if (mLayoutParams == null) {
+            output += "BAD! no layout params";
+        } else {
+            output = mLayoutParams.debug(output);
+        }
+        Log.d(VIEW_LOG_TAG, output);
+
+        output = debugIndent(depth);
+        output += "flags={";
+        output += View.printFlags(mViewFlags);
+        output += "}";
+        Log.d(VIEW_LOG_TAG, output);
+
+        output = debugIndent(depth);
+        output += "privateFlags={";
+        output += View.printPrivateFlags(mPrivateFlags);
+        output += "}";
+        Log.d(VIEW_LOG_TAG, output);
+    }
+
+    /**
+     * Creates an string of whitespaces used for indentation.
+     *
+     * @param depth the indentation level
+     * @return a String containing (depth * 2 + 3) * 2 white spaces
+     *
+     * @hide
+     */
+    protected static String debugIndent(int depth) {
+        StringBuilder spaces = new StringBuilder((depth * 2 + 3) * 2);
+        for (int i = 0; i < (depth * 2) + 3; i++) {
+            spaces.append(' ').append(' ');
+        }
+        return spaces.toString();
+    }
+
+    /**
+     * <p>Return the offset of the widget's text baseline from the widget's top
+     * boundary. If this widget does not support baseline alignment, this
+     * method returns -1. </p>
+     *
+     * @return the offset of the baseline within the widget's bounds or -1
+     *         if baseline alignment is not supported
+     */
+    @ViewDebug.ExportedProperty
+    public int getBaseline() {
+        return -1;
+    }
+
+    /**
+     * Call this when something has changed which has invalidated the
+     * layout of this view. This will schedule a layout pass of the view
+     * tree.
+     */
+    public void requestLayout() {
+        if (ViewDebug.TRACE_HIERARCHY) {
+            ViewDebug.trace(this, ViewDebug.HierarchyTraceType.REQUEST_LAYOUT);
+        }
+
+        mPrivateFlags |= FORCE_LAYOUT;
+
+        if (mParent != null && !mParent.isLayoutRequested()) {
+            mParent.requestLayout();
+        }
+    }
+
+    /**
+     * Forces this view to be laid out during the next layout pass.
+     * This method does not call requestLayout() or forceLayout()
+     * on the parent.
+     */
+    public void forceLayout() {
+        mPrivateFlags |= FORCE_LAYOUT;
+    }
+
+    /**
+     * <p>
+     * This is called to find out how big a view should be. The parent
+     * supplies constraint information in the width and height parameters.
+     * </p>
+     *
+     * <p>
+     * The actual mesurement work of a view is performed in
+     * {@link #onMeasure(int, int)}, called by this method. Therefore, only
+     * {@link #onMeasure(int, int)} can and must be overriden by subclasses.
+     * </p>
+     *
+     *
+     * @param widthMeasureSpec Horizontal space requirements as imposed by the
+     *        parent
+     * @param heightMeasureSpec Vertical space requirements as imposed by the
+     *        parent
+     *
+     * @see #onMeasure(int, int)
+     */
+    public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
+        if ((mPrivateFlags & FORCE_LAYOUT) == FORCE_LAYOUT ||
+                widthMeasureSpec != mOldWidthMeasureSpec ||
+                heightMeasureSpec != mOldHeightMeasureSpec) {
+
+            // first clears the measured dimension flag
+            mPrivateFlags &= ~MEASURED_DIMENSION_SET;
+
+            if (ViewDebug.TRACE_HIERARCHY) {
+                ViewDebug.trace(this, ViewDebug.HierarchyTraceType.ON_MEASURE);
+            }
+
+            // measure ourselves, this should set the measured dimension flag back
+            onMeasure(widthMeasureSpec, heightMeasureSpec);
+
+            // flag not set, setMeasuredDimension() was not invoked, we raise
+            // an exception to warn the developer
+            if ((mPrivateFlags & MEASURED_DIMENSION_SET) != MEASURED_DIMENSION_SET) {
+                throw new IllegalStateException("onMeasure() did not set the"
+                        + " measured dimension by calling"
+                        + " setMeasuredDimension()");
+            }
+
+            mPrivateFlags |= LAYOUT_REQUIRED;
+        }
+
+        mOldWidthMeasureSpec = widthMeasureSpec;
+        mOldHeightMeasureSpec = heightMeasureSpec;
+    }
+
+    /**
+     * <p>
+     * Measure the view and its content to determine the measured width and the
+     * measured height. This method is invoked by {@link #measure(int, int)} and
+     * should be overriden by subclasses to provide accurate and efficient
+     * measurement of their contents.
+     * </p>
+     *
+     * <p>
+     * <strong>CONTRACT:</strong> When overriding this method, you
+     * <em>must</em> call {@link #setMeasuredDimension(int, int)} to store the
+     * measured width and height of this view. Failure to do so will trigger an
+     * <code>IllegalStateException</code>, thrown by
+     * {@link #measure(int, int)}. Calling the superclass'
+     * {@link #onMeasure(int, int)} is a valid use.
+     * </p>
+     *
+     * <p>
+     * The base class implementation of measure defaults to the background size,
+     * unless a larger size is allowed by the MeasureSpec. Subclasses should
+     * override {@link #onMeasure(int, int)} to provide better measurements of
+     * their content.
+     * </p>
+     *
+     * <p>
+     * If this method is overridden, it is the subclass's responsibility to make
+     * sure the measured height and width are at least the view's minimum height
+     * and width ({@link #getSuggestedMinimumHeight()} and
+     * {@link #getSuggestedMinimumWidth()}).
+     * </p>
+     *
+     * @param widthMeasureSpec horizontal space requirements as imposed by the parent.
+     *                         The requirements are encoded with
+     *                         {@link android.view.View.MeasureSpec}.
+     * @param heightMeasureSpec vertical space requirements as imposed by the parent.
+     *                         The requirements are encoded with
+     *                         {@link android.view.View.MeasureSpec}.
+     *
+     * @see #getMeasuredWidth()
+     * @see #getMeasuredHeight()
+     * @see #setMeasuredDimension(int, int)
+     * @see #getSuggestedMinimumHeight()
+     * @see #getSuggestedMinimumWidth()
+     * @see android.view.View.MeasureSpec#getMode(int)
+     * @see android.view.View.MeasureSpec#getSize(int)
+     */
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
+                getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
+    }
+
+    /**
+     * <p>This mehod must be called by {@link #onMeasure(int, int)} to store the
+     * measured width and measured height. Failing to do so will trigger an
+     * exception at measurement time.</p>
+     *
+     * @param measuredWidth the measured width of this view
+     * @param measuredHeight the measured height of this view
+     */
+    protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {
+        mMeasuredWidth = measuredWidth;
+        mMeasuredHeight = measuredHeight;
+
+        mPrivateFlags |= MEASURED_DIMENSION_SET;
+    }
+
+    /**
+     * Utility to reconcile a desired size with constraints imposed by a MeasureSpec.
+     * Will take the desired size, unless a different size is imposed by the constraints.
+     *
+     * @param size How big the view wants to be
+     * @param measureSpec Constraints imposed by the parent
+     * @return The size this view should be.
+     */
+    public static int resolveSize(int size, int measureSpec) {
+        int result = size;
+        int specMode = MeasureSpec.getMode(measureSpec);
+        int specSize =  MeasureSpec.getSize(measureSpec);
+        switch (specMode) {
+        case MeasureSpec.UNSPECIFIED:
+            result = size;
+            break;
+        case MeasureSpec.AT_MOST:
+            result = Math.min(size, specSize);
+            break;
+        case MeasureSpec.EXACTLY:
+            result = specSize;
+            break;
+        }
+        return result;
+    }
+
+    /**
+     * Utility to return a default size. Uses the supplied size if the
+     * MeasureSpec imposed no contraints. Will get larger if allowed
+     * by the MeasureSpec.
+     *
+     * @param size Default size for this view
+     * @param measureSpec Constraints imposed by the parent
+     * @return The size this view should be.
+     */
+    public static int getDefaultSize(int size, int measureSpec) {
+        int result = size;
+        int specMode = MeasureSpec.getMode(measureSpec);
+        int specSize =  MeasureSpec.getSize(measureSpec);
+
+        switch (specMode) {
+        case MeasureSpec.UNSPECIFIED:
+            result = size;
+            break;
+        case MeasureSpec.AT_MOST:
+        case MeasureSpec.EXACTLY:
+            result = specSize;
+            break;
+        }
+        return result;
+    }
+
+    /**
+     * Returns the suggested minimum height that the view should use. This
+     * returns the maximum of the view's minimum height
+     * and the background's minimum height
+     * ({@link android.graphics.drawable.Drawable#getMinimumHeight()}).
+     * <p>
+     * When being used in {@link #onMeasure(int, int)}, the caller should still
+     * ensure the returned height is within the requirements of the parent.
+     *
+     * @return The suggested minimum height of the view.
+     */
+    protected int getSuggestedMinimumHeight() {
+        int suggestedMinHeight = mMinHeight;
+
+        if (mBGDrawable != null) {
+            final int bgMinHeight = mBGDrawable.getMinimumHeight();
+            if (suggestedMinHeight < bgMinHeight) {
+                suggestedMinHeight = bgMinHeight;
+            }
+        }
+
+        return suggestedMinHeight;
+    }
+
+    /**
+     * Returns the suggested minimum width that the view should use. This
+     * returns the maximum of the view's minimum width)
+     * and the background's minimum width
+     *  ({@link android.graphics.drawable.Drawable#getMinimumWidth()}).
+     * <p>
+     * When being used in {@link #onMeasure(int, int)}, the caller should still
+     * ensure the returned width is within the requirements of the parent.
+     *
+     * @return The suggested minimum width of the view.
+     */
+    protected int getSuggestedMinimumWidth() {
+        int suggestedMinWidth = mMinWidth;
+
+        if (mBGDrawable != null) {
+            final int bgMinWidth = mBGDrawable.getMinimumWidth();
+            if (suggestedMinWidth < bgMinWidth) {
+                suggestedMinWidth = bgMinWidth;
+            }
+        }
+
+        return suggestedMinWidth;
+    }
+
+    /**
+     * Sets the minimum height of the view. It is not guaranteed the view will
+     * be able to achieve this minimum height (for example, if its parent layout
+     * constrains it with less available height).
+     *
+     * @param minHeight The minimum height the view will try to be.
+     */
+    public void setMinimumHeight(int minHeight) {
+        mMinHeight = minHeight;
+    }
+
+    /**
+     * Sets the minimum width of the view. It is not guaranteed the view will
+     * be able to achieve this minimum width (for example, if its parent layout
+     * constrains it with less available width).
+     *
+     * @param minWidth The minimum width the view will try to be.
+     */
+    public void setMinimumWidth(int minWidth) {
+        mMinWidth = minWidth;
+    }
+
+    /**
+     * Get the animation currently associated with this view.
+     *
+     * @return The animation that is currently playing or
+     *         scheduled to play for this view.
+     */
+    public Animation getAnimation() {
+        return mCurrentAnimation;
+    }
+
+    /**
+     * Start the specified animation now.
+     *
+     * @param animation the animation to start now
+     */
+    public void startAnimation(Animation animation) {
+        animation.setStartTime(Animation.START_ON_FIRST_FRAME);
+        setAnimation(animation);
+        invalidate();
+    }
+
+    /**
+     * Cancels any animations for this view.
+     */
+    public void clearAnimation() {
+        mCurrentAnimation = null;
+    }
+
+    /**
+     * Sets the next animation to play for this view.
+     * If you want the animation to play immediately, use
+     * startAnimation. This method provides allows fine-grained
+     * control over the start time and invalidation, but you
+     * must make sure that 1) the animation has a start time set, and
+     * 2) the view will be invalidated when the animation is supposed to
+     * start.
+     *
+     * @param animation The next animation, or null.
+     */
+    public void setAnimation(Animation animation) {
+        mCurrentAnimation = animation;
+        if (animation != null) {
+            animation.reset();
+        }
+    }
+
+    /**
+     * Invoked by a parent ViewGroup to notify the start of the animation
+     * currently associated with this view. If you override this method,
+     * always call super.onAnimationStart();
+     *
+     * @see #setAnimation(android.view.animation.Animation)
+     * @see #getAnimation()
+     */
+    protected void onAnimationStart() {
+        mPrivateFlags |= ANIMATION_STARTED;
+    }
+
+    /**
+     * Invoked by a parent ViewGroup to notify the end of the animation
+     * currently associated with this view. If you override this method,
+     * always call super.onAnimationEnd();
+     *
+     * @see #setAnimation(android.view.animation.Animation)
+     * @see #getAnimation()
+     */
+    protected void onAnimationEnd() {
+        mPrivateFlags &= ~ANIMATION_STARTED;
+    }
+
+    /**
+     * Invoked if there is a Transform that involves alpha. Subclass that can
+     * draw themselves with the specified alpha should return true, and then
+     * respect that alpha when their onDraw() is called. If this returns false
+     * then the view may be redirected to draw into an offscreen buffer to
+     * fulfill the request, which will look fine, but may be slower than if the
+     * subclass handles it internally. The default implementation returns false.
+     *
+     * @param alpha The alpha (0..255) to apply to the view's drawing
+     * @return true if the view can draw with the specified alpha.
+     */
+    protected boolean onSetAlpha(int alpha) {
+        return false;
+    }
+
+    /**
+     * This is used by the RootView to perform an optimization when
+     * the view hierarchy contains one or several SurfaceView.
+     * SurfaceView is always considered transparent, but its children are not,
+     * therefore all View objects remove themselves from the global transparent
+     * region (passed as a parameter to this function).
+     *
+     * @param region The transparent region for this ViewRoot (window).
+     *
+     * @return Returns true if the effective visibility of the view at this
+     * point is opaque, regardless of the transparent region; returns false
+     * if it is possible for underlying windows to be seen behind the view.
+     *
+     * {@hide}
+     */
+    public boolean gatherTransparentRegion(Region region) {
+        final AttachInfo attachInfo = mAttachInfo;
+        if (region != null && attachInfo != null) {
+            final int pflags = mPrivateFlags;
+            if ((pflags & SKIP_DRAW) == 0) {
+                // The SKIP_DRAW flag IS NOT set, so this view draws. We need to
+                // remove it from the transparent region.
+                final int[] location = attachInfo.mTransparentLocation;
+                getLocationInWindow(location);
+                region.op(location[0], location[1], location[0] + mRight - mLeft,
+                        location[1] + mBottom - mTop, Region.Op.DIFFERENCE);
+            } else if ((pflags & ONLY_DRAWS_BACKGROUND) != 0 && mBGDrawable != null) {
+                // The ONLY_DRAWS_BACKGROUND flag IS set and the background drawable
+                // exists, so we remove the background drawable's non-transparent
+                // parts from this transparent region.
+                applyDrawableToTransparentRegion(mBGDrawable, region);
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Play a sound effect for this view.
+     *
+     * <p>The framework will play sound effects for some built in actions, such as
+     * clicking, but you may wish to play these effects in your widget,
+     * for instance, for internal navigation.
+     *
+     * <p>The sound effect will only be played if sound effects are enabled by the user, and
+     * {@link #isSoundEffectsEnabled()} is true.
+     *
+     * @param soundConstant One of the constants defined in {@link SoundEffectConstants}
+     */
+    public void playSoundEffect(int soundConstant) {
+        if (mAttachInfo == null || mAttachInfo.mRootCallbacks == null || !isSoundEffectsEnabled()) {
+            return;
+        }
+        mAttachInfo.mRootCallbacks.playSoundEffect(soundConstant);
+    }
+
+    /**
+     * Provide haptic feedback to the user for this view.
+     *
+     * <p>The framework will provide haptic feedback for some built in actions,
+     * such as long presses, but you may wish to provide feedback for your
+     * own widget.
+     *
+     * <p>The feedback will only be performed if
+     * {@link #isHapticFeedbackEnabled()} is true.
+     *
+     * @param feedbackConstant One of the constants defined in
+     * {@link HapticFeedbackConstants}
+     */
+    public boolean performHapticFeedback(int feedbackConstant) {
+        return performHapticFeedback(feedbackConstant, 0);
+    }
+
+    /**
+     * Like {@link #performHapticFeedback(int)}, with additional options.
+     *
+     * @param feedbackConstant One of the constants defined in
+     * {@link HapticFeedbackConstants}
+     * @param flags Additional flags as per {@link HapticFeedbackConstants}.
+     */
+    public boolean performHapticFeedback(int feedbackConstant, int flags) {
+        if (mAttachInfo == null) {
+            return false;
+        }
+        if ((flags&HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING) == 0
+                && !isHapticFeedbackEnabled()) {
+            return false;
+        }
+        return mAttachInfo.mRootCallbacks.performHapticFeedback(
+                feedbackConstant,
+                (flags&HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING) != 0);
+    }
+
+    /**
+     * Given a Drawable whose bounds have been set to draw into this view,
+     * update a Region being computed for {@link #gatherTransparentRegion} so
+     * that any non-transparent parts of the Drawable are removed from the
+     * given transparent region.
+     *
+     * @param dr The Drawable whose transparency is to be applied to the region.
+     * @param region A Region holding the current transparency information,
+     * where any parts of the region that are set are considered to be
+     * transparent.  On return, this region will be modified to have the
+     * transparency information reduced by the corresponding parts of the
+     * Drawable that are not transparent.
+     * {@hide}
+     */
+    public void applyDrawableToTransparentRegion(Drawable dr, Region region) {
+        if (DBG) {
+            Log.i("View", "Getting transparent region for: " + this);
+        }
+        final Region r = dr.getTransparentRegion();
+        final Rect db = dr.getBounds();
+        final AttachInfo attachInfo = mAttachInfo;
+        if (r != null && attachInfo != null) {
+            final int w = getRight()-getLeft();
+            final int h = getBottom()-getTop();
+            if (db.left > 0) {
+                //Log.i("VIEW", "Drawable left " + db.left + " > view 0");
+                r.op(0, 0, db.left, h, Region.Op.UNION);
+            }
+            if (db.right < w) {
+                //Log.i("VIEW", "Drawable right " + db.right + " < view " + w);
+                r.op(db.right, 0, w, h, Region.Op.UNION);
+            }
+            if (db.top > 0) {
+                //Log.i("VIEW", "Drawable top " + db.top + " > view 0");
+                r.op(0, 0, w, db.top, Region.Op.UNION);
+            }
+            if (db.bottom < h) {
+                //Log.i("VIEW", "Drawable bottom " + db.bottom + " < view " + h);
+                r.op(0, db.bottom, w, h, Region.Op.UNION);
+            }
+            final int[] location = attachInfo.mTransparentLocation;
+            getLocationInWindow(location);
+            r.translate(location[0], location[1]);
+            region.op(r, Region.Op.INTERSECT);
+        } else {
+            region.op(db, Region.Op.DIFFERENCE);
+        }
+    }
+
+    private void postCheckForLongClick() {
+        mHasPerformedLongPress = false;
+
+        if (mPendingCheckForLongPress == null) {
+            mPendingCheckForLongPress = new CheckForLongPress();
+        }
+        mPendingCheckForLongPress.rememberWindowAttachCount();
+        postDelayed(mPendingCheckForLongPress, ViewConfiguration.getLongPressTimeout());
+    }
+
+    private static int[] stateSetUnion(final int[] stateSet1,
+                                       final int[] stateSet2) {
+        final int stateSet1Length = stateSet1.length;
+        final int stateSet2Length = stateSet2.length;
+        final int[] newSet = new int[stateSet1Length + stateSet2Length];
+        int k = 0;
+        int i = 0;
+        int j = 0;
+        // This is a merge of the two input state sets and assumes that the
+        // input sets are sorted by the order imposed by ViewDrawableStates.
+        for (int viewState : R.styleable.ViewDrawableStates) {
+            if (i < stateSet1Length && stateSet1[i] == viewState) {
+                newSet[k++] = viewState;
+                i++;
+            } else if (j < stateSet2Length && stateSet2[j] == viewState) {
+                newSet[k++] = viewState;
+                j++;
+            }
+            if (k > 1) {
+                assert(newSet[k - 1] > newSet[k - 2]);
+            }
+        }
+        return newSet;
+    }
+
+    /**
+     * Inflate a view from an XML resource.  This convenience method wraps the {@link
+     * LayoutInflater} class, which provides a full range of options for view inflation.
+     *
+     * @param context The Context object for your activity or application.
+     * @param resource The resource ID to inflate
+     * @param root A view group that will be the parent.  Used to properly inflate the
+     * layout_* parameters.
+     * @see LayoutInflater
+     */
+    public static View inflate(Context context, int resource, ViewGroup root) {
+        LayoutInflater factory = LayoutInflater.from(context);
+        return factory.inflate(resource, root);
+    }
+
+    /**
+     * A MeasureSpec encapsulates the layout requirements passed from parent to child.
+     * Each MeasureSpec represents a requirement for either the width or the height.
+     * A MeasureSpec is comprised of a size and a mode. There are three possible
+     * modes:
+     * <dl>
+     * <dt>UNSPECIFIED</dt>
+     * <dd>
+     * The parent has not imposed any constraint on the child. It can be whatever size
+     * it wants.
+     * </dd>
+     *
+     * <dt>EXACTLY</dt>
+     * <dd>
+     * The parent has determined an exact size for the child. The child is going to be
+     * given those bounds regardless of how big it wants to be.
+     * </dd>
+     *
+     * <dt>AT_MOST</dt>
+     * <dd>
+     * The child can be as large as it wants up to the specified size.
+     * </dd>
+     * </dl>
+     *
+     * MeasureSpecs are implemented as ints to reduce object allocation. This class
+     * is provided to pack and unpack the &lt;size, mode&gt; tuple into the int.
+     */
+    public static class MeasureSpec {
+        private static final int MODE_SHIFT = 30;
+        private static final int MODE_MASK  = 0x3 << MODE_SHIFT;
+
+        /**
+         * Measure specification mode: The parent has not imposed any constraint
+         * on the child. It can be whatever size it wants.
+         */
+        public static final int UNSPECIFIED = 0 << MODE_SHIFT;
+
+        /**
+         * Measure specification mode: The parent has determined an exact size
+         * for the child. The child is going to be given those bounds regardless
+         * of how big it wants to be.
+         */
+        public static final int EXACTLY     = 1 << MODE_SHIFT;
+
+        /**
+         * Measure specification mode: The child can be as large as it wants up
+         * to the specified size.
+         */
+        public static final int AT_MOST     = 2 << MODE_SHIFT;
+
+        /**
+         * Creates a measure specification based on the supplied size and mode.
+         *
+         * The mode must always be one of the following:
+         * <ul>
+         *  <li>{@link android.view.View.MeasureSpec#UNSPECIFIED}</li>
+         *  <li>{@link android.view.View.MeasureSpec#EXACTLY}</li>
+         *  <li>{@link android.view.View.MeasureSpec#AT_MOST}</li>
+         * </ul>
+         *
+         * @param size the size of the measure specification
+         * @param mode the mode of the measure specification
+         * @return the measure specification based on size and mode
+         */
+        public static int makeMeasureSpec(int size, int mode) {
+            return size + mode;
+        }
+
+        /**
+         * Extracts the mode from the supplied measure specification.
+         *
+         * @param measureSpec the measure specification to extract the mode from
+         * @return {@link android.view.View.MeasureSpec#UNSPECIFIED},
+         *         {@link android.view.View.MeasureSpec#AT_MOST} or
+         *         {@link android.view.View.MeasureSpec#EXACTLY}
+         */
+        public static int getMode(int measureSpec) {
+            return (measureSpec & MODE_MASK);
+        }
+
+        /**
+         * Extracts the size from the supplied measure specification.
+         *
+         * @param measureSpec the measure specification to extract the size from
+         * @return the size in pixels defined in the supplied measure specification
+         */
+        public static int getSize(int measureSpec) {
+            return (measureSpec & ~MODE_MASK);
+        }
+
+        /**
+         * Returns a String representation of the specified measure
+         * specification.
+         *
+         * @param measureSpec the measure specification to convert to a String
+         * @return a String with the following format: "MeasureSpec: MODE SIZE"
+         */
+        public static String toString(int measureSpec) {
+            int mode = getMode(measureSpec);
+            int size = getSize(measureSpec);
+
+            StringBuilder sb = new StringBuilder("MeasureSpec: ");
+
+            if (mode == UNSPECIFIED)
+                sb.append("UNSPECIFIED ");
+            else if (mode == EXACTLY)
+                sb.append("EXACTLY ");
+            else if (mode == AT_MOST)
+                sb.append("AT_MOST ");
+            else
+                sb.append(mode).append(" ");
+
+            sb.append(size);
+            return sb.toString();
+        }
+    }
+
+    class CheckForLongPress implements Runnable {
+
+        private int mOriginalWindowAttachCount;
+
+        public void run() {
+            if (isPressed() && (mParent != null) && hasWindowFocus()
+                    && mOriginalWindowAttachCount == mWindowAttachCount) {
+                if (performLongClick()) {
+                    mHasPerformedLongPress = true;
+                }
+            }
+        }
+
+        public void rememberWindowAttachCount() {
+            mOriginalWindowAttachCount = mWindowAttachCount;
+        }
+    }
+
+    /**
+     * Interface definition for a callback to be invoked when a key event is
+     * dispatched to this view. The callback will be invoked before the key
+     * event is given to the view.
+     */
+    public interface OnKeyListener {
+        /**
+         * Called when a key is dispatched to a view. This allows listeners to
+         * get a chance to respond before the target view.
+         *
+         * @param v The view the key has been dispatched to.
+         * @param keyCode The code for the physical key that was pressed
+         * @param event The KeyEvent object containing full information about
+         *        the event.
+         * @return True if the listener has consumed the event, false otherwise.
+         */
+        boolean onKey(View v, int keyCode, KeyEvent event);
+    }
+
+    /**
+     * Interface definition for a callback to be invoked when a touch event is
+     * dispatched to this view. The callback will be invoked before the touch
+     * event is given to the view.
+     */
+    public interface OnTouchListener {
+        /**
+         * Called when a touch event is dispatched to a view. This allows listeners to
+         * get a chance to respond before the target view.
+         *
+         * @param v The view the touch event has been dispatched to.
+         * @param event The MotionEvent object containing full information about
+         *        the event.
+         * @return True if the listener has consumed the event, false otherwise.
+         */
+        boolean onTouch(View v, MotionEvent event);
+    }
+
+    /**
+     * Interface definition for a callback to be invoked when a view has been clicked and held.
+     */
+    public interface OnLongClickListener {
+        /**
+         * Called when a view has been clicked and held.
+         *
+         * @param v The view that was clicked and held.
+         *
+         * return True if the callback consumed the long click, false otherwise
+         */
+        boolean onLongClick(View v);
+    }
+
+    /**
+     * Interface definition for a callback to be invoked when the focus state of
+     * a view changed.
+     */
+    public interface OnFocusChangeListener {
+        /**
+         * Called when the focus state of a view has changed.
+         *
+         * @param v The view whose state has changed.
+         * @param hasFocus The new focus state of v.
+         */
+        void onFocusChange(View v, boolean hasFocus);
+    }
+
+    /**
+     * Interface definition for a callback to be invoked when a view is clicked.
+     */
+    public interface OnClickListener {
+        /**
+         * Called when a view has been clicked.
+         *
+         * @param v The view that was clicked.
+         */
+        void onClick(View v);
+    }
+
+    /**
+     * Interface definition for a callback to be invoked when the context menu
+     * for this view is being built.
+     */
+    public interface OnCreateContextMenuListener {
+        /**
+         * Called when the context menu for this view is being built. It is not
+         * safe to hold onto the menu after this method returns.
+         *
+         * @param menu The context menu that is being built
+         * @param v The view for which the context menu is being built
+         * @param menuInfo Extra information about the item for which the
+         *            context menu should be shown. This information will vary
+         *            depending on the class of v.
+         */
+        void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo);
+    }
+
+    private final class UnsetPressedState implements Runnable {
+        public void run() {
+            setPressed(false);
+        }
+    }
+
+    /**
+     * Base class for derived classes that want to save and restore their own
+     * state in {@link android.view.View#onSaveInstanceState()}.
+     */
+    public static class BaseSavedState extends AbsSavedState {
+        /**
+         * Constructor used when reading from a parcel. Reads the state of the superclass.
+         *
+         * @param source
+         */
+        public BaseSavedState(Parcel source) {
+            super(source);
+        }
+
+        /**
+         * Constructor called by derived classes when creating their SavedState objects
+         *
+         * @param superState The state of the superclass of this view
+         */
+        public BaseSavedState(Parcelable superState) {
+            super(superState);
+        }
+
+        public static final Parcelable.Creator<BaseSavedState> CREATOR =
+                new Parcelable.Creator<BaseSavedState>() {
+            public BaseSavedState createFromParcel(Parcel in) {
+                return new BaseSavedState(in);
+            }
+
+            public BaseSavedState[] newArray(int size) {
+                return new BaseSavedState[size];
+            }
+        };
+    }
+
+    /**
+     * A set of information given to a view when it is attached to its parent
+     * window.
+     */
+    static class AttachInfo {
+
+        interface Callbacks {
+            void playSoundEffect(int effectId);
+            boolean performHapticFeedback(int effectId, boolean always);
+        }
+
+        /**
+         * InvalidateInfo is used to post invalidate(int, int, int, int) messages
+         * to a Handler. This class contains the target (View) to invalidate and
+         * the coordinates of the dirty rectangle.
+         *
+         * For performance purposes, this class also implements a pool of up to
+         * POOL_LIMIT objects that get reused. This reduces memory allocations
+         * whenever possible.
+         *
+         * The pool is implemented as a linked list of InvalidateInfo object with
+         * the root pointing to the next available InvalidateInfo. If the root
+         * is null (i.e. when all instances from the pool have been acquired),
+         * then a new InvalidateInfo is created and returned to the caller.
+         *
+         * An InvalidateInfo is sent back to the pool by calling its release()
+         * method. If the pool is full the object is simply discarded.
+         *
+         * This implementation follows the object pool pattern used in the
+         * MotionEvent class.
+         */
+        static class InvalidateInfo {
+            private static final int POOL_LIMIT = 10;
+            private static final Object sLock = new Object();
+
+            private static int sAcquiredCount = 0;
+            private static InvalidateInfo sRoot;
+
+            private InvalidateInfo next;
+
+            View target;
+
+            int left;
+            int top;
+            int right;
+            int bottom;
+
+            static InvalidateInfo acquire() {
+                synchronized (sLock) {
+                    if (sRoot == null) {
+                        return new InvalidateInfo();
+                    }
+
+                    InvalidateInfo info = sRoot;
+                    sRoot = info.next;
+                    sAcquiredCount--;
+
+                    return info;
+                }
+            }
+
+            void release() {
+                synchronized (sLock) {
+                    if (sAcquiredCount < POOL_LIMIT) {
+                        sAcquiredCount++;
+                        next = sRoot;
+                        sRoot = this;
+                    }
+                }
+            }
+        }
+
+        final IWindowSession mSession;
+
+        final IWindow mWindow;
+
+        final IBinder mWindowToken;
+
+        final Callbacks mRootCallbacks;
+
+        /**
+         * The top view of the hierarchy.
+         */
+        View mRootView;
+        
+        IBinder mPanelParentWindowToken;
+        Surface mSurface;
+
+        /**
+         * Left position of this view's window
+         */
+        int mWindowLeft;
+
+        /**
+         * Top position of this view's window
+         */
+        int mWindowTop;
+
+        /**
+         * For windows that are full-screen but using insets to layout inside
+         * of the screen decorations, these are the current insets for the
+         * content of the window.
+         */
+        final Rect mContentInsets = new Rect();
+
+        /**
+         * For windows that are full-screen but using insets to layout inside
+         * of the screen decorations, these are the current insets for the
+         * actual visible parts of the window.
+         */
+        final Rect mVisibleInsets = new Rect();
+
+        /**
+         * The internal insets given by this window.  This value is
+         * supplied by the client (through
+         * {@link ViewTreeObserver.OnComputeInternalInsetsListener}) and will
+         * be given to the window manager when changed to be used in laying
+         * out windows behind it.
+         */
+        final ViewTreeObserver.InternalInsetsInfo mGivenInternalInsets
+                = new ViewTreeObserver.InternalInsetsInfo();
+
+        /**
+         * All views in the window's hierarchy that serve as scroll containers,
+         * used to determine if the window can be resized or must be panned
+         * to adjust for a soft input area.
+         */
+        final ArrayList<View> mScrollContainers = new ArrayList<View>();
+
+        /**
+         * Indicates whether the view's window currently has the focus.
+         */
+        boolean mHasWindowFocus;
+
+        /**
+         * The current visibility of the window.
+         */
+        int mWindowVisibility;
+
+        /**
+         * Indicates the time at which drawing started to occur.
+         */
+        long mDrawingTime;
+
+        /**
+         * Indicates whether the view's window is currently in touch mode.
+         */
+        boolean mInTouchMode;
+
+        /**
+         * Indicates that ViewRoot should trigger a global layout change
+         * the next time it performs a traversal
+         */
+        boolean mRecomputeGlobalAttributes;
+
+        /**
+         * Set to true when attributes (like mKeepScreenOn) need to be
+         * recomputed.
+         */
+        boolean mAttributesChanged;
+
+        /**
+         * Set during a traveral if any views want to keep the screen on.
+         */
+        boolean mKeepScreenOn;
+
+        /**
+         * Set if the visibility of any views has changed.
+         */
+        boolean mViewVisibilityChanged;
+
+        /**
+         * Set to true if a view has been scrolled.
+         */
+        boolean mViewScrollChanged;
+
+        /**
+         * Global to the view hierarchy used as a temporary for dealing with
+         * x/y points in the transparent region computations.
+         */
+        final int[] mTransparentLocation = new int[2];
+
+        /**
+         * Global to the view hierarchy used as a temporary for dealing with
+         * x/y points in the ViewGroup.invalidateChild implementation.
+         */
+        final int[] mInvalidateChildLocation = new int[2];
+
+        /**
+         * The view tree observer used to dispatch global events like
+         * layout, pre-draw, touch mode change, etc.
+         */
+        final ViewTreeObserver mTreeObserver = new ViewTreeObserver();
+
+        /**
+         * A Canvas used by the view hierarchy to perform bitmap caching.
+         */
+        Canvas mCanvas;
+
+        /**
+         * A Handler supplied by a view's {@link android.view.ViewRoot}. This
+         * handler can be used to pump events in the UI events queue.
+         */
+        final Handler mHandler;
+
+        /**
+         * Identifier for messages requesting the view to be invalidated.
+         * Such messages should be sent to {@link #mHandler}.
+         */
+        static final int INVALIDATE_MSG = 0x1;
+
+        /**
+         * Identifier for messages requesting the view to invalidate a region.
+         * Such messages should be sent to {@link #mHandler}.
+         */
+        static final int INVALIDATE_RECT_MSG = 0x2;
+
+        /**
+         * Temporary for use in computing invalidate rectangles while
+         * calling up the hierarchy.
+         */
+        final Rect mTmpInvalRect = new Rect();
+        
+        /**
+         * Creates a new set of attachment information with the specified
+         * events handler and thread.
+         *
+         * @param handler the events handler the view must use
+         */
+        AttachInfo(IWindowSession session, IWindow window,
+                Handler handler, Callbacks effectPlayer) {
+            mSession = session;
+            mWindow = window;
+            mWindowToken = window.asBinder();
+            mHandler = handler;
+            mRootCallbacks = effectPlayer;
+        }
+    }
+
+    /**
+     * <p>ScrollabilityCache holds various fields used by a View when scrolling
+     * is supported. This avoids keeping too many unused fields in most
+     * instances of View.</p>
+     */
+    private static class ScrollabilityCache {
+        public int fadingEdgeLength;
+
+        public int scrollBarSize;
+        public ScrollBarDrawable scrollBar;
+
+        public final Paint paint;
+        public final Matrix matrix;
+        public Shader shader;
+
+        private int mLastColor;
+
+        public ScrollabilityCache(ViewConfiguration configuration) {
+            fadingEdgeLength = configuration.getScaledFadingEdgeLength();
+            scrollBarSize = configuration.getScaledScrollBarSize();
+
+            paint = new Paint();
+            matrix = new Matrix();
+            // use use a height of 1, and then wack the matrix each time we
+            // actually use it.
+            shader = new LinearGradient(0, 0, 0, 1, 0xFF000000, 0, Shader.TileMode.CLAMP);
+            
+            paint.setShader(shader);
+            paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OUT));
+        }
+        
+        public void setFadeColor(int color) {
+            if (color != 0 && color != mLastColor) {
+                mLastColor = color;
+                color |= 0xFF000000;
+                
+                shader = new LinearGradient(0, 0, 0, 1, color, 0, Shader.TileMode.CLAMP);
+                
+                paint.setShader(shader);
+                // Restore the default transfer mode (src_over)
+                paint.setXfermode(null);
+            }
+        }
+    }
+}
diff --git a/core/java/android/view/ViewConfiguration.java b/core/java/android/view/ViewConfiguration.java
new file mode 100644
index 0000000..d3f48c6
--- /dev/null
+++ b/core/java/android/view/ViewConfiguration.java
@@ -0,0 +1,424 @@
+/*
+ * 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 android.view;
+
+import android.content.Context;
+import android.util.DisplayMetrics;
+import android.util.SparseArray;
+
+/**
+ * Contains methods to standard constants used in the UI for timeouts, sizes, and distances.
+ */
+public class ViewConfiguration {
+    /**
+     * Defines the width of the horizontal scrollbar and the height of the vertical scrollbar in
+     * pixels
+     */
+    private static final int SCROLL_BAR_SIZE = 10;
+
+    /**
+     * Defines the length of the fading edges in pixels
+     */
+    private static final int FADING_EDGE_LENGTH = 12;
+
+    /**
+     * Defines the duration in milliseconds of the pressed state in child
+     * components.
+     */
+    private static final int PRESSED_STATE_DURATION = 85;
+    
+    /**
+     * Defines the duration in milliseconds before a press turns into
+     * a long press
+     */
+    private static final int LONG_PRESS_TIMEOUT = 500;
+    
+    /**
+     * Defines the duration in milliseconds a user needs to hold down the
+     * appropriate button to bring up the global actions dialog (power off,
+     * lock screen, etc).
+     */
+    private static final int GLOBAL_ACTIONS_KEY_TIMEOUT = 500;
+    
+    /**
+     * Defines the duration in milliseconds we will wait to see if a touch event 
+     * is a tap or a scroll. If the user does not move within this interval, it is
+     * considered to be a tap. 
+     */
+    private static final int TAP_TIMEOUT = 100;
+    
+    /**
+     * Defines the duration in milliseconds we will wait to see if a touch event 
+     * is a jump tap. If the user does not complete the jump tap within this interval, it is
+     * considered to be a tap. 
+     */
+    private static final int JUMP_TAP_TIMEOUT = 500;
+
+    /**
+     * Defines the duration in milliseconds between the first tap's up event and
+     * the second tap's down event for an interaction to be considered a
+     * double-tap.
+     */
+    private static final int DOUBLE_TAP_TIMEOUT = 300;
+    
+    /**
+     * Defines the duration in milliseconds we want to display zoom controls in response 
+     * to a user panning within an application.
+     */
+    private static final int ZOOM_CONTROLS_TIMEOUT = 3000;
+
+    /**
+     * Inset in pixels to look for touchable content when the user touches the edge of the screen
+     */
+    private static final int EDGE_SLOP = 12;
+    
+    /**
+     * Distance a touch can wander before we think the user is scrolling in pixels
+     */
+    private static final int TOUCH_SLOP = 25;
+    
+    /**
+     * Distance between the first touch and second touch to still be considered a double tap
+     */
+    private static final int DOUBLE_TAP_SLOP = 100;
+    
+    /**
+     * Distance a touch needs to be outside of a window's bounds for it to
+     * count as outside for purposes of dismissing the window.
+     */
+    private static final int WINDOW_TOUCH_SLOP = 16;
+
+    /**
+     * Minimum velocity to initiate a fling, as measured in pixels per second
+     */
+    private static final int MINIMUM_FLING_VELOCITY = 50;
+
+    /**
+     * The maximum size of View's drawing cache, expressed in bytes. This size
+     * should be at least equal to the size of the screen in ARGB888 format.
+     */
+    @Deprecated
+    private static final int MAXIMUM_DRAWING_CACHE_SIZE = 320 * 480 * 4; // HVGA screen, ARGB8888
+
+    /**
+     * The coefficient of friction applied to flings/scrolls.
+     */
+    private static float SCROLL_FRICTION = 0.015f;
+
+    private final int mEdgeSlop;
+    private final int mFadingEdgeLength;
+    private final int mMinimumFlingVelocity;
+    private final int mScrollbarSize;
+    private final int mTouchSlop;
+    private final int mDoubleTapSlop;
+    private final int mWindowTouchSlop;
+    private final int mMaximumDrawingCacheSize;
+
+    private static final SparseArray<ViewConfiguration> sConfigurations =
+            new SparseArray<ViewConfiguration>(2);
+
+    /**
+     * @deprecated Use {@link android.view.ViewConfiguration#get(android.content.Context)} instead.
+     */
+    @Deprecated
+    public ViewConfiguration() {
+        mEdgeSlop = EDGE_SLOP;
+        mFadingEdgeLength = FADING_EDGE_LENGTH;
+        mMinimumFlingVelocity = MINIMUM_FLING_VELOCITY;
+        mScrollbarSize = SCROLL_BAR_SIZE;
+        mTouchSlop = TOUCH_SLOP;
+        mDoubleTapSlop = DOUBLE_TAP_SLOP;
+        mWindowTouchSlop = WINDOW_TOUCH_SLOP;
+        //noinspection deprecation
+        mMaximumDrawingCacheSize = MAXIMUM_DRAWING_CACHE_SIZE;
+    }
+
+    /**
+     * Creates a new configuration for the specified context. The configuration depends on
+     * various parameters of the context, like the dimension of the display or the density
+     * of the display.
+     *
+     * @param context The application context used to initialize this view configuration.
+     *
+     * @see #get(android.content.Context) 
+     * @see android.util.DisplayMetrics
+     */
+    private ViewConfiguration(Context context) {
+        final DisplayMetrics metrics = context.getResources().getDisplayMetrics();
+        final float density = metrics.density;
+
+        mEdgeSlop = (int) (density * EDGE_SLOP + 0.5f);
+        mFadingEdgeLength = (int) (density * FADING_EDGE_LENGTH + 0.5f);
+        mMinimumFlingVelocity = (int) (density * MINIMUM_FLING_VELOCITY + 0.5f);
+        mScrollbarSize = (int) (density * SCROLL_BAR_SIZE + 0.5f);
+        mTouchSlop = (int) (density * TOUCH_SLOP + 0.5f);
+        mDoubleTapSlop = (int) (density * DOUBLE_TAP_SLOP + 0.5f);
+        mWindowTouchSlop = (int) (density * WINDOW_TOUCH_SLOP + 0.5f);
+
+        // Size of the screen in bytes, in ARGB_8888 format
+        mMaximumDrawingCacheSize = 4 * metrics.widthPixels * metrics.heightPixels;
+    }
+
+    /**
+     * Returns a configuration for the specified context. The configuration depends on
+     * various parameters of the context, like the dimension of the display or the
+     * density of the display.
+     *
+     * @param context The application context used to initialize the view configuration.
+     */
+    public static ViewConfiguration get(Context context) {
+        final DisplayMetrics metrics = context.getResources().getDisplayMetrics();
+        final int density = (int) (100.0f * metrics.density);
+
+        ViewConfiguration configuration = sConfigurations.get(density);
+        if (configuration == null) {
+            configuration = new ViewConfiguration(context);
+            sConfigurations.put(density, configuration);
+        }
+
+        return configuration;
+    }
+
+    /**
+     * @return The width of the horizontal scrollbar and the height of the vertical
+     *         scrollbar in pixels
+     *
+     * @deprecated Use {@link #getScaledScrollBarSize()} instead.
+     */
+    @Deprecated
+    public static int getScrollBarSize() {
+        return SCROLL_BAR_SIZE;
+    }
+
+    /**
+     * @return The width of the horizontal scrollbar and the height of the vertical
+     *         scrollbar in pixels
+     */
+    public int getScaledScrollBarSize() {
+        return mScrollbarSize;
+    }
+
+    /**
+     * @return the length of the fading edges in pixels
+     *
+     * @deprecated Use {@link #getScaledFadingEdgeLength()} instead.
+     */
+    @Deprecated
+    public static int getFadingEdgeLength() {
+        return FADING_EDGE_LENGTH;
+    }
+
+    /**
+     * @return the length of the fading edges in pixels
+     */
+    public int getScaledFadingEdgeLength() {
+        return mFadingEdgeLength;
+    }
+
+    /**
+     * @return the duration in milliseconds of the pressed state in child
+     * components.
+     */
+    public static int getPressedStateDuration() {
+        return PRESSED_STATE_DURATION;
+    }
+    
+    /**
+     * @return the duration in milliseconds before a press turns into
+     * a long press
+     */
+    public static int getLongPressTimeout() {
+        return LONG_PRESS_TIMEOUT;
+    }
+    
+    /**
+     * @return the duration in milliseconds we will wait to see if a touch event
+     * is a tap or a scroll. If the user does not move within this interval, it is
+     * considered to be a tap. 
+     */
+    public static int getTapTimeout() {
+        return TAP_TIMEOUT;
+    }
+    
+    /**
+     * @return the duration in milliseconds we will wait to see if a touch event
+     * is a jump tap. If the user does not move within this interval, it is
+     * considered to be a tap. 
+     */
+    public static int getJumpTapTimeout() {
+        return JUMP_TAP_TIMEOUT;
+    }
+    
+    /**
+     * @return the duration in milliseconds between the first tap's up event and
+     * the second tap's down event for an interaction to be considered a
+     * double-tap.
+     * @hide pending API council
+     */
+    public static int getDoubleTapTimeout() {
+        return DOUBLE_TAP_TIMEOUT;
+    }
+    
+    /**
+     * @return Inset in pixels to look for touchable content when the user touches the edge of the
+     *         screen
+     *
+     * @deprecated Use {@link #getScaledEdgeSlop()} instead.
+     */
+    @Deprecated
+    public static int getEdgeSlop() {
+        return EDGE_SLOP;
+    }
+
+    /**
+     * @return Inset in pixels to look for touchable content when the user touches the edge of the
+     *         screen
+     */
+    public int getScaledEdgeSlop() {
+        return mEdgeSlop;
+    }
+
+    /**
+     * @return Distance a touch can wander before we think the user is scrolling in pixels
+     *
+     * @deprecated Use {@link #getScaledTouchSlop()} instead.
+     */
+    @Deprecated
+    public static int getTouchSlop() {
+        return TOUCH_SLOP;
+    }
+
+    /**
+     * @return Distance a touch can wander before we think the user is scrolling in pixels
+     */
+    public int getScaledTouchSlop() {
+        return mTouchSlop;
+    }
+
+    /**
+     * @return Distance between the first touch and second touch to still be
+     *         considered a double tap
+     * @deprecated Use {@link #getScaledDoubleTapSlop()} instead.
+     * @hide The only client of this should be GestureDetector, which needs this
+     *       for clients that still use its deprecated constructor.
+     */
+    @Deprecated
+    public static int getDoubleTapSlop() {
+        return DOUBLE_TAP_SLOP;
+    }
+    
+    /**
+     * @return Distance between the first touch and second touch to still be
+     *         considered a double tap
+     * @hide pending API council
+     */
+    public int getScaledDoubleTapSlop() {
+        return mDoubleTapSlop;
+    }
+
+    /**
+     * @return Distance a touch must be outside the bounds of a window for it
+     * to be counted as outside the window for purposes of dismissing that
+     * window.
+     *
+     * @deprecated Use {@link #getScaledWindowTouchSlop()} instead.
+     */
+    @Deprecated
+    public static int getWindowTouchSlop() {
+        return WINDOW_TOUCH_SLOP;
+    }
+
+    /**
+     * @return Distance a touch must be outside the bounds of a window for it
+     * to be counted as outside the window for purposes of dismissing that
+     * window.
+     */
+    public int getScaledWindowTouchSlop() {
+        return mWindowTouchSlop;
+    }
+    
+    /**
+     * @return Minimum velocity to initiate a fling, as measured in pixels per second.
+     *
+     * @deprecated Use {@link #getScaledMinimumFlingVelocity()} instead.
+     */
+    @Deprecated
+    public static int getMinimumFlingVelocity() {
+        return MINIMUM_FLING_VELOCITY;
+    }
+
+    /**
+     * @return Minimum velocity to initiate a fling, as measured in pixels per second.
+     */
+    public int getScaledMinimumFlingVelocity() {
+        return mMinimumFlingVelocity;
+    }
+
+    /**
+     * The maximum drawing cache size expressed in bytes.
+     *
+     * @return the maximum size of View's drawing cache expressed in bytes
+     *
+     * @deprecated Use {@link #getScaledMaximumDrawingCacheSize()} instead.
+     */
+    @Deprecated
+    public static int getMaximumDrawingCacheSize() {
+        //noinspection deprecation
+        return MAXIMUM_DRAWING_CACHE_SIZE;
+    }
+
+    /**
+     * The maximum drawing cache size expressed in bytes.
+     *
+     * @return the maximum size of View's drawing cache expressed in bytes
+     */
+    public int getScaledMaximumDrawingCacheSize() {
+        return mMaximumDrawingCacheSize;
+    }
+
+    /**
+     * The amount of time that the zoom controls should be
+     * displayed on the screen expressed in milliseconds.
+     * 
+     * @return the time the zoom controls should be visible expressed
+     * in milliseconds.
+     */
+    public static long getZoomControlsTimeout() {
+        return ZOOM_CONTROLS_TIMEOUT;
+    }
+
+    /**
+     * The amount of time a user needs to press the relevant key to bring up
+     * the global actions dialog.
+     *
+     * @return how long a user needs to press the relevant key to bring up
+     *   the global actions dialog.
+     */
+    public static long getGlobalActionKeyTimeout() {
+        return GLOBAL_ACTIONS_KEY_TIMEOUT;
+    }
+
+    /**
+     * The amount of friction applied to scrolls and flings.
+     * 
+     * @return A scalar dimensionless value representing the coefficient of
+     *         friction.
+     */
+    public static float getScrollFriction() {
+        return SCROLL_FRICTION;
+    }
+}
diff --git a/core/java/android/view/ViewDebug.java b/core/java/android/view/ViewDebug.java
new file mode 100644
index 0000000..883c7bd
--- /dev/null
+++ b/core/java/android/view/ViewDebug.java
@@ -0,0 +1,1128 @@
+/*
+ * Copyright (C) 2007 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 android.view;
+
+import android.util.Log;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.os.Environment;
+
+import java.io.File;
+import java.io.BufferedWriter;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.FileOutputStream;
+import java.io.DataOutputStream;
+import java.io.OutputStreamWriter;
+import java.io.BufferedOutputStream;
+import java.io.OutputStream;
+import java.util.List;
+import java.util.LinkedList;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.lang.annotation.Target;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.lang.reflect.InvocationTargetException;
+
+/**
+ * Various debugging/tracing tools related to {@link View} and the view hierarchy.
+ */
+public class ViewDebug {
+    /**
+     * Enables or disables view hierarchy tracing. Any invoker of
+     * {@link #trace(View, android.view.ViewDebug.HierarchyTraceType)} should first
+     * check that this value is set to true as not to affect performance.
+     */
+    public static final boolean TRACE_HIERARCHY = false;
+
+    /**
+     * Enables or disables view recycler tracing. Any invoker of
+     * {@link #trace(View, android.view.ViewDebug.RecyclerTraceType, int[])} should first
+     * check that this value is set to true as not to affect performance.
+     */
+    public static final boolean TRACE_RECYCLER = false;
+    
+    /**
+     * The system property of dynamic switch for capturing view information
+     * when it is set, we dump interested fields and methods for the view on focus
+     */    
+    static final String SYSTEM_PROPERTY_CAPTURE_VIEW = "debug.captureview";
+        
+    /**
+     * The system property of dynamic switch for capturing event information
+     * when it is set, we log key events, touch/motion and trackball events
+     */    
+    static final String SYSTEM_PROPERTY_CAPTURE_EVENT = "debug.captureevent";
+    
+    /**
+     * This annotation can be used to mark fields and methods to be dumped by
+     * the view server. Only non-void methods with no arguments can be annotated
+     * by this annotation.
+     */
+    @Target({ ElementType.FIELD, ElementType.METHOD })
+    @Retention(RetentionPolicy.RUNTIME)
+    public @interface ExportedProperty {
+        /**
+         * When resolveId is true, and if the annotated field/method return value
+         * is an int, the value is converted to an Android's resource name.
+         *
+         * @return true if the property's value must be transformed into an Android
+         *         resource name, false otherwise
+         */
+        boolean resolveId() default false;
+
+        /**
+         * A mapping can be defined to map int values to specific strings. For
+         * instance, View.getVisibility() returns 0, 4 or 8. However, these values
+         * actually mean VISIBLE, INVISIBLE and GONE. A mapping can be used to see
+         * these human readable values:
+         *
+         * <pre>
+         * @ViewDebug.ExportedProperty(mapping = {
+         *     @ViewDebug.IntToString(from = 0, to = "VISIBLE"),
+         *     @ViewDebug.IntToString(from = 4, to = "INVISIBLE"),
+         *     @ViewDebug.IntToString(from = 8, to = "GONE")
+         * })
+         * public int getVisibility() { ...
+         * <pre>
+         *
+         * @return An array of int to String mappings
+         *
+         * @see android.view.ViewDebug.IntToString
+         */
+        IntToString[] mapping() default { };
+
+        /**
+         * When deep export is turned on, this property is not dumped. Instead, the
+         * properties contained in this property are dumped. Each child property
+         * is prefixed with the name of this property.
+         *
+         * @return true if the properties of this property should be dumped
+         *
+         * @see #prefix() 
+         */
+        boolean deepExport() default false;
+
+        /**
+         * The prefix to use on child properties when deep export is enabled
+         *
+         * @return a prefix as a String
+         *
+         * @see #deepExport()
+         */
+        String prefix() default "";
+    }
+
+    /**
+     * Defines a mapping from an int value to a String. Such a mapping can be used
+     * in a @ExportedProperty to provide more meaningful values to the end user.
+     *
+     * @see android.view.ViewDebug.ExportedProperty
+     */
+    @Target({ ElementType.TYPE })
+    @Retention(RetentionPolicy.RUNTIME)
+    public @interface IntToString {
+        /**
+         * The original int value to map to a String.
+         *
+         * @return An arbitrary int value.
+         */
+        int from();
+
+        /**
+         * The String to use in place of the original int value.
+         *
+         * @return An arbitrary non-null String.
+         */
+        String to();
+    }
+    
+    /**
+     * This annotation can be used to mark fields and methods to be dumped when
+     * the view is captured. Methods with this annotation must have no arguments
+     * and must return <some type of data>.
+     * 
+     * @hide pending API Council approval
+     */
+    @Target({ ElementType.FIELD, ElementType.METHOD })
+    @Retention(RetentionPolicy.RUNTIME)
+    public @interface CapturedViewProperty {
+        /**
+         * When retrieveReturn is true, we need to retrieve second level methods 
+         * e.g., we need myView.getFirstLevelMethod().getSecondLevelMethod()
+         * we will set retrieveReturn = true on the annotation of 
+         * myView.getFirstLevelMethod()
+         * @return true if we need the second level methods 
+         */
+        boolean retrieveReturn() default false;        
+    }
+        
+    private static HashMap<Class<?>, Method[]> mCapturedViewMethodsForClasses = null;
+    private static HashMap<Class<?>, Field[]> mCapturedViewFieldsForClasses = null;
+
+    // Maximum delay in ms after which we stop trying to capture a View's drawing
+    private static final int CAPTURE_TIMEOUT = 4000;
+
+    private static final String REMOTE_COMMAND_CAPTURE = "CAPTURE";
+    private static final String REMOTE_COMMAND_DUMP = "DUMP";
+    private static final String REMOTE_COMMAND_INVALIDATE = "INVALIDATE";
+    private static final String REMOTE_COMMAND_REQUEST_LAYOUT = "REQUEST_LAYOUT";
+
+    private static HashMap<Class<?>, Field[]> sFieldsForClasses;
+    private static HashMap<Class<?>, Method[]> sMethodsForClasses;
+        
+    /**
+     * Defines the type of hierarhcy trace to output to the hierarchy traces file.
+     */
+    public enum HierarchyTraceType {
+        INVALIDATE,
+        INVALIDATE_CHILD,
+        INVALIDATE_CHILD_IN_PARENT,
+        REQUEST_LAYOUT,
+        ON_LAYOUT,
+        ON_MEASURE,
+        DRAW,
+        BUILD_CACHE
+    }
+
+    private static BufferedWriter sHierarchyTraces;
+    private static ViewRoot sHierarhcyRoot;
+    private static String sHierarchyTracePrefix;
+
+    /**
+     * Defines the type of recycler trace to output to the recycler traces file.
+     */
+    public enum RecyclerTraceType {
+        NEW_VIEW,
+        BIND_VIEW,
+        RECYCLE_FROM_ACTIVE_HEAP,
+        RECYCLE_FROM_SCRAP_HEAP,
+        MOVE_TO_ACTIVE_HEAP,
+        MOVE_TO_SCRAP_HEAP,
+        MOVE_FROM_ACTIVE_TO_SCRAP_HEAP
+    }
+
+    private static class RecyclerTrace {
+        public int view;
+        public RecyclerTraceType type;
+        public int position;
+        public int indexOnScreen;
+    }
+
+    private static View sRecyclerOwnerView;
+    private static List<View> sRecyclerViews;
+    private static List<RecyclerTrace> sRecyclerTraces;
+    private static String sRecyclerTracePrefix;
+
+    /**
+     * Returns the number of instanciated Views.
+     *
+     * @return The number of Views instanciated in the current process.
+     *
+     * @hide
+     */
+    public static long getViewInstanceCount() {
+        return View.sInstanceCount;
+    }
+
+    /**
+     * Returns the number of instanciated ViewRoots.
+     *
+     * @return The number of ViewRoots instanciated in the current process.
+     *
+     * @hide
+     */
+    public static long getViewRootInstanceCount() {
+        return ViewRoot.getInstanceCount();
+    }    
+
+    /**
+     * Outputs a trace to the currently opened recycler traces. The trace records the type of
+     * recycler action performed on the supplied view as well as a number of parameters.
+     *
+     * @param view the view to trace
+     * @param type the type of the trace
+     * @param parameters parameters depending on the type of the trace
+     */
+    public static void trace(View view, RecyclerTraceType type, int... parameters) {
+        if (sRecyclerOwnerView == null || sRecyclerViews == null) {
+            return;
+        }
+
+        if (!sRecyclerViews.contains(view)) {
+            sRecyclerViews.add(view);
+        }
+
+        final int index = sRecyclerViews.indexOf(view);
+
+        RecyclerTrace trace = new RecyclerTrace();
+        trace.view = index;
+        trace.type = type;
+        trace.position = parameters[0];
+        trace.indexOnScreen = parameters[1];
+
+        sRecyclerTraces.add(trace);
+    }
+
+    /**
+     * Starts tracing the view recycler of the specified view. The trace is identified by a prefix,
+     * used to build the traces files names: <code>/EXTERNAL/view-recycler/PREFIX.traces</code> and
+     * <code>/EXTERNAL/view-recycler/PREFIX.recycler</code>.
+     *
+     * Only one view recycler can be traced at the same time. After calling this method, any
+     * other invocation will result in a <code>IllegalStateException</code> unless
+     * {@link #stopRecyclerTracing()} is invoked before.
+     *
+     * Traces files are created only after {@link #stopRecyclerTracing()} is invoked.
+     *
+     * This method will return immediately if TRACE_RECYCLER is false.
+     *
+     * @param prefix the traces files name prefix
+     * @param view the view whose recycler must be traced
+     *
+     * @see #stopRecyclerTracing()
+     * @see #trace(View, android.view.ViewDebug.RecyclerTraceType, int[])
+     */
+    public static void startRecyclerTracing(String prefix, View view) {
+        //noinspection PointlessBooleanExpression,ConstantConditions
+        if (!TRACE_RECYCLER) {
+            return;
+        }
+
+        if (sRecyclerOwnerView != null) {
+            throw new IllegalStateException("You must call stopRecyclerTracing() before running" +
+                " a new trace!");
+        }
+
+        sRecyclerTracePrefix = prefix;
+        sRecyclerOwnerView = view;
+        sRecyclerViews = new ArrayList<View>();
+        sRecyclerTraces = new LinkedList<RecyclerTrace>();
+    }
+
+    /**
+     * Stops the current view recycer tracing.
+     *
+     * Calling this method creates the file <code>/EXTERNAL/view-recycler/PREFIX.traces</code>
+     * containing all the traces (or method calls) relative to the specified view's recycler.
+     *
+     * Calling this method creates the file <code>/EXTERNAL/view-recycler/PREFIX.recycler</code>
+     * containing all of the views used by the recycler of the view supplied to
+     * {@link #startRecyclerTracing(String, View)}.
+     *
+     * This method will return immediately if TRACE_RECYCLER is false.
+     *
+     * @see #startRecyclerTracing(String, View)
+     * @see #trace(View, android.view.ViewDebug.RecyclerTraceType, int[])
+     */
+    public static void stopRecyclerTracing() {
+        //noinspection PointlessBooleanExpression,ConstantConditions
+        if (!TRACE_RECYCLER) {
+            return;
+        }
+
+        if (sRecyclerOwnerView == null || sRecyclerViews == null) {
+            throw new IllegalStateException("You must call startRecyclerTracing() before" +
+                " stopRecyclerTracing()!");
+        }
+
+        File recyclerDump = new File(Environment.getExternalStorageDirectory(), "view-recycler/");
+        recyclerDump.mkdirs();
+
+        recyclerDump = new File(recyclerDump, sRecyclerTracePrefix + ".recycler");
+        try {
+            final BufferedWriter out = new BufferedWriter(new FileWriter(recyclerDump), 8 * 1024);
+
+            for (View view : sRecyclerViews) {
+                final String name = view.getClass().getName();
+                out.write(name);
+                out.newLine();
+            }
+
+            out.close();
+        } catch (IOException e) {
+            Log.e("View", "Could not dump recycler content");
+            return;
+        }
+
+        recyclerDump = new File(Environment.getExternalStorageDirectory(), "view-recycler/");
+        recyclerDump = new File(recyclerDump, sRecyclerTracePrefix + ".traces");
+        try {
+            final FileOutputStream file = new FileOutputStream(recyclerDump);
+            final DataOutputStream out = new DataOutputStream(file);
+
+            for (RecyclerTrace trace : sRecyclerTraces) {
+                out.writeInt(trace.view);
+                out.writeInt(trace.type.ordinal());
+                out.writeInt(trace.position);
+                out.writeInt(trace.indexOnScreen);
+                out.flush();
+            }
+
+            out.close();
+        } catch (IOException e) {
+            Log.e("View", "Could not dump recycler traces");
+            return;
+        }
+
+        sRecyclerViews.clear();
+        sRecyclerViews = null;
+
+        sRecyclerTraces.clear();
+        sRecyclerTraces = null;
+
+        sRecyclerOwnerView = null;
+    }
+
+    /**
+     * Outputs a trace to the currently opened traces file. The trace contains the class name
+     * and instance's hashcode of the specified view as well as the supplied trace type.
+     *
+     * @param view the view to trace
+     * @param type the type of the trace
+     */
+    public static void trace(View view, HierarchyTraceType type) {
+        if (sHierarchyTraces == null) {
+            return;
+        }
+
+        try {
+            sHierarchyTraces.write(type.name());
+            sHierarchyTraces.write(' ');
+            sHierarchyTraces.write(view.getClass().getName());
+            sHierarchyTraces.write('@');
+            sHierarchyTraces.write(Integer.toHexString(view.hashCode()));
+            sHierarchyTraces.newLine();
+        } catch (IOException e) {
+            Log.w("View", "Error while dumping trace of type " + type + " for view " + view);
+        }
+    }
+
+    /**
+     * Starts tracing the view hierarchy of the specified view. The trace is identified by a prefix,
+     * used to build the traces files names: <code>/EXTERNAL/view-hierarchy/PREFIX.traces</code> and
+     * <code>/EXTERNAL/view-hierarchy/PREFIX.tree</code>.
+     *
+     * Only one view hierarchy can be traced at the same time. After calling this method, any
+     * other invocation will result in a <code>IllegalStateException</code> unless
+     * {@link #stopHierarchyTracing()} is invoked before.
+     *
+     * Calling this method creates the file <code>/EXTERNAL/view-hierarchy/PREFIX.traces</code>
+     * containing all the traces (or method calls) relative to the specified view's hierarchy.
+     *
+     * This method will return immediately if TRACE_HIERARCHY is false.
+     *
+     * @param prefix the traces files name prefix
+     * @param view the view whose hierarchy must be traced
+     *
+     * @see #stopHierarchyTracing()
+     * @see #trace(View, android.view.ViewDebug.HierarchyTraceType)
+     */
+    public static void startHierarchyTracing(String prefix, View view) {
+        //noinspection PointlessBooleanExpression,ConstantConditions
+        if (!TRACE_HIERARCHY) {
+            return;
+        }
+
+        if (sHierarhcyRoot != null) {
+            throw new IllegalStateException("You must call stopHierarchyTracing() before running" +
+                " a new trace!");
+        }
+
+        File hierarchyDump = new File(Environment.getExternalStorageDirectory(), "view-hierarchy/");
+        hierarchyDump.mkdirs();
+
+        hierarchyDump = new File(hierarchyDump, prefix + ".traces");
+        sHierarchyTracePrefix = prefix;
+
+        try {
+            sHierarchyTraces = new BufferedWriter(new FileWriter(hierarchyDump), 8 * 1024);
+        } catch (IOException e) {
+            Log.e("View", "Could not dump view hierarchy");
+            return;
+        }
+
+        sHierarhcyRoot = (ViewRoot) view.getRootView().getParent();
+    }
+
+    /**
+     * Stops the current view hierarchy tracing. This method closes the file
+     * <code>/EXTERNAL/view-hierarchy/PREFIX.traces</code>.
+     *
+     * Calling this method creates the file <code>/EXTERNAL/view-hierarchy/PREFIX.tree</code>
+     * containing the view hierarchy of the view supplied to
+     * {@link #startHierarchyTracing(String, View)}.
+     *
+     * This method will return immediately if TRACE_HIERARCHY is false.
+     *
+     * @see #startHierarchyTracing(String, View) 
+     * @see #trace(View, android.view.ViewDebug.HierarchyTraceType)
+     */
+    public static void stopHierarchyTracing() {
+        //noinspection PointlessBooleanExpression,ConstantConditions
+        if (!TRACE_HIERARCHY) {
+            return;
+        }
+
+        if (sHierarhcyRoot == null || sHierarchyTraces == null) {
+            throw new IllegalStateException("You must call startHierarchyTracing() before" +
+                " stopHierarchyTracing()!");
+        }
+
+        try {
+            sHierarchyTraces.close();
+        } catch (IOException e) {
+            Log.e("View", "Could not write view traces");
+        }
+        sHierarchyTraces = null;
+
+        File hierarchyDump = new File(Environment.getExternalStorageDirectory(), "view-hierarchy/");
+        hierarchyDump.mkdirs();
+        hierarchyDump = new File(hierarchyDump, sHierarchyTracePrefix + ".tree");
+
+        BufferedWriter out;
+        try {
+            out = new BufferedWriter(new FileWriter(hierarchyDump), 8 * 1024);
+        } catch (IOException e) {
+            Log.e("View", "Could not dump view hierarchy");
+            return;
+        }
+
+        View view = sHierarhcyRoot.getView();
+        if (view instanceof ViewGroup) {
+            ViewGroup group = (ViewGroup) view;
+            dumpViewHierarchy(group, out, 0);
+            try {
+                out.close();
+            } catch (IOException e) {
+                Log.e("View", "Could not dump view hierarchy");
+            }
+        }
+
+        sHierarhcyRoot = null;
+    }
+    
+    static void dispatchCommand(View view, String command, String parameters,
+            OutputStream clientStream) throws IOException {
+
+        // Paranoid but safe...
+        view = view.getRootView();
+
+        if (REMOTE_COMMAND_DUMP.equalsIgnoreCase(command)) {
+            dump(view, clientStream);
+        } else {
+            final String[] params = parameters.split(" ");
+            if (REMOTE_COMMAND_CAPTURE.equalsIgnoreCase(command)) {
+                capture(view, clientStream, params[0]);
+            } else if (REMOTE_COMMAND_INVALIDATE.equalsIgnoreCase(command)) {
+                invalidate(view, params[0]);
+            } else if (REMOTE_COMMAND_REQUEST_LAYOUT.equalsIgnoreCase(command)) {
+                requestLayout(view, params[0]);
+            }
+        }
+    }
+
+    private static View findViewByHashCode(View root, String parameter) {
+        final String[] ids = parameter.split("@");
+        final String className = ids[0];
+        final int hashCode = Integer.parseInt(ids[1], 16);
+
+        View view = root.getRootView();
+        if (view instanceof ViewGroup) {
+            return findView((ViewGroup) view, className, hashCode);
+        }
+
+        return null;
+    }
+
+    private static void invalidate(View root, String parameter) {
+        final View view = findViewByHashCode(root, parameter);
+        if (view != null) {
+            view.postInvalidate();
+        }
+    }
+
+    private static void requestLayout(View root, String parameter) {
+        final View view = findViewByHashCode(root, parameter);
+        if (view != null) {
+            root.post(new Runnable() {
+                public void run() {
+                    view.requestLayout();
+                }
+            });
+        }
+    }
+
+    private static void capture(View root, final OutputStream clientStream, String parameter)
+            throws IOException {
+
+        final CountDownLatch latch = new CountDownLatch(1);
+        final View captureView = findViewByHashCode(root, parameter);
+
+        if (captureView != null) {
+            final Bitmap[] cache = new Bitmap[1];
+
+            final boolean hasCache = captureView.isDrawingCacheEnabled();
+            final boolean willNotCache = captureView.willNotCacheDrawing();
+
+            if (willNotCache) {
+                captureView.setWillNotCacheDrawing(false);
+            }
+
+            root.post(new Runnable() {
+                public void run() {
+                    try {
+                        if (!hasCache) {
+                            captureView.buildDrawingCache();
+                        }
+
+                        cache[0] = captureView.getDrawingCache();
+                    } finally {
+                        latch.countDown();
+                    }
+                }
+            });
+
+            try {
+                latch.await(CAPTURE_TIMEOUT, TimeUnit.MILLISECONDS);
+
+                if (cache[0] != null) {
+                    BufferedOutputStream out = null;
+                    try {
+                        out = new BufferedOutputStream(clientStream, 32 * 1024);
+                        cache[0].compress(Bitmap.CompressFormat.PNG, 100, out);
+                        out.flush();
+                    } finally {
+                        if (out != null) {
+                            out.close();
+                        }
+                    }
+                }
+            } catch (InterruptedException e) {
+                Log.w("View", "Could not complete the capture of the view " + captureView);                
+            } finally {
+                if (willNotCache) {
+                    captureView.setWillNotCacheDrawing(true);
+                }
+                if (!hasCache) {
+                    captureView.destroyDrawingCache();
+                }
+            }
+        }
+    }
+
+    private static void dump(View root, OutputStream clientStream) throws IOException {
+        BufferedWriter out = null;
+        try {
+            out = new BufferedWriter(new OutputStreamWriter(clientStream), 32 * 1024);
+            View view = root.getRootView();
+            if (view instanceof ViewGroup) {
+                ViewGroup group = (ViewGroup) view;
+                dumpViewHierarchyWithProperties(group, out, 0);
+            }
+            out.write("DONE.");
+            out.newLine();
+        } finally {
+            if (out != null) {
+                out.close();
+            }
+        }
+    }
+
+    private static View findView(ViewGroup group, String className, int hashCode) {
+        if (isRequestedView(group, className, hashCode)) {
+            return group;
+        }
+
+        final int count = group.getChildCount();
+        for (int i = 0; i < count; i++) {
+            final View view = group.getChildAt(i);
+            if (view instanceof ViewGroup) {
+                final View found = findView((ViewGroup) view, className, hashCode);
+                if (found != null) {
+                    return found;
+                }
+            } else if (isRequestedView(view, className, hashCode)) {
+                return view;
+            }
+        }
+
+        return null;
+    }
+
+    private static boolean isRequestedView(View view, String className, int hashCode) {
+        return view.getClass().getName().equals(className) && view.hashCode() == hashCode;
+    }
+
+    private static void dumpViewHierarchyWithProperties(ViewGroup group,
+            BufferedWriter out, int level) {
+        if (!dumpViewWithProperties(group, out, level)) {
+            return;
+        }
+
+        final int count = group.getChildCount();
+        for (int i = 0; i < count; i++) {
+            final View view = group.getChildAt(i);
+            if (view instanceof ViewGroup) {
+                dumpViewHierarchyWithProperties((ViewGroup) view, out, level + 1);
+            } else {
+                dumpViewWithProperties(view, out, level + 1);
+            }
+        }
+    }
+
+    private static boolean dumpViewWithProperties(View view, BufferedWriter out, int level) {
+        try {
+            for (int i = 0; i < level; i++) {
+                out.write(' ');
+            }
+            out.write(view.getClass().getName());
+            out.write('@');
+            out.write(Integer.toHexString(view.hashCode()));
+            out.write(' ');
+            dumpViewProperties(view, out);
+            out.newLine();
+        } catch (IOException e) {
+            Log.w("View", "Error while dumping hierarchy tree");
+            return false;
+        }
+        return true;
+    }
+
+    private static Field[] getExportedPropertyFields(Class<?> klass) {
+        if (sFieldsForClasses == null) {
+            sFieldsForClasses = new HashMap<Class<?>, Field[]>();
+        }
+        final HashMap<Class<?>, Field[]> map = sFieldsForClasses;
+
+        Field[] fields = map.get(klass);
+        if (fields != null) {
+            return fields;
+        }
+
+        final ArrayList<Field> foundFields = new ArrayList<Field>();
+        fields = klass.getDeclaredFields();
+
+        int count = fields.length;
+        for (int i = 0; i < count; i++) {
+            final Field field = fields[i];
+            if (field.isAnnotationPresent(ExportedProperty.class)) {
+                field.setAccessible(true);
+                foundFields.add(field);
+            }
+        }
+
+        fields = foundFields.toArray(new Field[foundFields.size()]);
+        map.put(klass, fields);
+
+        return fields;
+    }
+
+    private static Method[] getExportedPropertyMethods(Class<?> klass) {
+        if (sMethodsForClasses == null) {
+            sMethodsForClasses = new HashMap<Class<?>, Method[]>();
+        }
+        final HashMap<Class<?>, Method[]> map = sMethodsForClasses;
+
+        Method[] methods = map.get(klass);
+        if (methods != null) {
+            return methods;
+        }
+
+        final ArrayList<Method> foundMethods = new ArrayList<Method>();
+        methods = klass.getDeclaredMethods();
+        
+        int count = methods.length;
+        for (int i = 0; i < count; i++) {
+            final Method method = methods[i];            
+            if (method.getParameterTypes().length == 0 &&
+                        method.isAnnotationPresent(ExportedProperty.class) &&
+                        method.getReturnType() != Void.class) {
+                method.setAccessible(true);
+                foundMethods.add(method);
+            }
+        }
+
+        methods = foundMethods.toArray(new Method[foundMethods.size()]);
+        map.put(klass, methods);
+
+        return methods;
+    }
+
+    private static void dumpViewProperties(Object view, BufferedWriter out) throws IOException {
+        dumpViewProperties(view, out, "");
+    }
+
+    private static void dumpViewProperties(Object view, BufferedWriter out, String prefix)
+            throws IOException {
+        Class<?> klass = view.getClass();
+
+        do {
+            exportFields(view, out, klass, prefix);
+            exportMethods(view, out, klass, prefix);
+            klass = klass.getSuperclass();
+        } while (klass != Object.class);
+    }
+    
+    private static void exportMethods(Object view, BufferedWriter out, Class<?> klass,
+            String prefix) throws IOException {
+
+        final Method[] methods = getExportedPropertyMethods(klass);
+
+        int count = methods.length;
+        for (int i = 0; i < count; i++) {
+            final Method method = methods[i];
+            //noinspection EmptyCatchBlock
+            try {
+                // TODO: This should happen on the UI thread
+                Object methodValue = method.invoke(view, (Object[]) null);
+                final Class<?> returnType = method.getReturnType();
+
+                if (returnType == int.class) {
+                    ExportedProperty property = method.getAnnotation(ExportedProperty.class);
+                    if (property.resolveId() && view instanceof View) {
+                        final Resources resources = ((View) view).getContext().getResources();
+                        final int id = (Integer) methodValue;
+                        if (id >= 0) {
+                            try {
+                                methodValue = resources.getResourceTypeName(id) + '/' +
+                                        resources.getResourceEntryName(id);
+                            } catch (Resources.NotFoundException e) {
+                                methodValue = "UNKNOWN";
+                            }
+                        } else {
+                            methodValue = "NO_ID";
+                        }
+                    } else {
+                        final IntToString[] mapping = property.mapping();
+                        if (mapping.length > 0) {
+                            final int intValue = (Integer) methodValue;
+                            boolean mapped = false;
+                            int mappingCount = mapping.length;
+                            for (int j = 0; j < mappingCount; j++) {
+                                final IntToString mapper = mapping[j];
+                                if (mapper.from() == intValue) {
+                                    methodValue = mapper.to();
+                                    mapped = true;
+                                    break;
+                                }
+                            }
+
+                            if (!mapped) {
+                                methodValue = intValue;
+                            }
+                        }
+                    }
+                } else if (!returnType.isPrimitive()) {
+                    ExportedProperty property = method.getAnnotation(ExportedProperty.class);
+                    if (property.deepExport()) {
+                        dumpViewProperties(methodValue, out, prefix + property.prefix());
+                        continue;
+                    }
+                }
+
+                out.write(prefix);
+                out.write(method.getName());
+                out.write("()=");
+
+                if (methodValue != null) {
+                    final String value = methodValue.toString().replace("\n", "\\n");
+                    out.write(String.valueOf(value.length()));
+                    out.write(",");
+                    out.write(value);
+                } else {
+                    out.write("4,null");
+                }
+
+                out.write(' ');
+            } catch (IllegalAccessException e) {
+            } catch (InvocationTargetException e) {
+            }
+        }
+    }
+
+    private static void exportFields(Object view, BufferedWriter out, Class<?> klass, String prefix)
+            throws IOException {
+        final Field[] fields = getExportedPropertyFields(klass);
+
+        int count = fields.length;
+        for (int i = 0; i < count; i++) {
+            final Field field = fields[i];
+
+            //noinspection EmptyCatchBlock
+            try {
+                Object fieldValue = null;
+                final Class<?> type = field.getType();
+
+                if (type == int.class) {
+                    ExportedProperty property = field.getAnnotation(ExportedProperty.class);
+                    if (property.resolveId() && view instanceof View) {
+                        final Resources resources = ((View) view).getContext().getResources();
+                        final int id = field.getInt(view);
+                        if (id >= 0) {
+                            try {
+                                fieldValue = resources.getResourceTypeName(id) + '/' +
+                                        resources.getResourceEntryName(id);
+                            } catch (Resources.NotFoundException e) {
+                                fieldValue = "UNKNOWN";
+                            }
+                        } else {
+                            fieldValue = "NO_ID";
+                        }
+                    } else {
+                        final IntToString[] mapping = property.mapping();
+                        if (mapping.length > 0) {
+                            final int intValue = field.getInt(view);
+                            int mappingCount = mapping.length;
+                            for (int j = 0; j < mappingCount; j++) {
+                                final IntToString mapped = mapping[j];
+                                if (mapped.from() == intValue) {
+                                    fieldValue = mapped.to();
+                                    break;
+                                }
+                            }
+
+                            if (fieldValue == null) {
+                                fieldValue = intValue;
+                            }
+                        }
+                    }
+                } else if (!type.isPrimitive()) {
+                    ExportedProperty property = field.getAnnotation(ExportedProperty.class);
+                    if (property.deepExport()) {
+                        dumpViewProperties(field.get(view), out, prefix + property.prefix());
+                        continue;
+                    }
+                }
+
+                if (fieldValue == null) {
+                    fieldValue = field.get(view);
+                }                
+
+                out.write(prefix);
+                out.write(field.getName());
+                out.write('=');
+
+                if (fieldValue != null) {
+                    final String value = fieldValue.toString().replace("\n", "\\n");
+                    out.write(String.valueOf(value.length()));
+                    out.write(",");
+                    out.write(value);
+                } else {
+                    out.write("4,null");
+                }
+
+                out.write(' ');
+            } catch (IllegalAccessException e) {
+            }
+        }
+    }
+
+    private static void dumpViewHierarchy(ViewGroup group, BufferedWriter out, int level) {
+        if (!dumpView(group, out, level)) {
+            return;
+        }
+
+        final int count = group.getChildCount();
+        for (int i = 0; i < count; i++) {
+            final View view = group.getChildAt(i);
+            if (view instanceof ViewGroup) {
+                dumpViewHierarchy((ViewGroup) view, out, level + 1);
+            } else {
+                dumpView(view, out, level + 1);
+            }
+        }
+    }
+
+    private static boolean dumpView(Object view, BufferedWriter out, int level) {
+        try {
+            for (int i = 0; i < level; i++) {
+                out.write(' ');
+            }
+            out.write(view.getClass().getName());
+            out.write('@');
+            out.write(Integer.toHexString(view.hashCode()));
+            out.newLine();
+        } catch (IOException e) {
+            Log.w("View", "Error while dumping hierarchy tree");
+            return false;
+        }
+        return true;
+    }
+
+    private static Field[] capturedViewGetPropertyFields(Class<?> klass) {
+        if (mCapturedViewFieldsForClasses == null) {
+            mCapturedViewFieldsForClasses = new HashMap<Class<?>, Field[]>();
+        }
+        final HashMap<Class<?>, Field[]> map = mCapturedViewFieldsForClasses;
+
+        Field[] fields = map.get(klass);
+        if (fields != null) {
+            return fields;
+        }
+
+        final ArrayList<Field> foundFields = new ArrayList<Field>();
+        fields = klass.getFields();
+
+        int count = fields.length;
+        for (int i = 0; i < count; i++) {
+            final Field field = fields[i];
+            if (field.isAnnotationPresent(CapturedViewProperty.class)) {
+                field.setAccessible(true);
+                foundFields.add(field);
+            }
+        }
+
+        fields = foundFields.toArray(new Field[foundFields.size()]);
+        map.put(klass, fields);
+
+        return fields;
+    }
+
+    private static Method[] capturedViewGetPropertyMethods(Class<?> klass) {
+        if (mCapturedViewMethodsForClasses == null) {
+            mCapturedViewMethodsForClasses = new HashMap<Class<?>, Method[]>();
+        }
+        final HashMap<Class<?>, Method[]> map = mCapturedViewMethodsForClasses;
+
+        Method[] methods = map.get(klass);
+        if (methods != null) {
+            return methods;
+        }
+
+        final ArrayList<Method> foundMethods = new ArrayList<Method>();
+        methods = klass.getMethods();
+        
+        int count = methods.length;
+        for (int i = 0; i < count; i++) {
+            final Method method = methods[i];            
+            if (method.getParameterTypes().length == 0 &&
+                    method.isAnnotationPresent(CapturedViewProperty.class) &&
+                    method.getReturnType() != Void.class) {
+                method.setAccessible(true);
+                foundMethods.add(method);
+            }
+        }
+
+        methods = foundMethods.toArray(new Method[foundMethods.size()]);
+        map.put(klass, methods);
+
+        return methods;
+    }
+              
+    private static String capturedViewExportMethods(Object obj, Class<?> klass, 
+            String prefix) {
+
+        if (obj == null) {
+            return "null";
+        }
+        
+        StringBuilder sb = new StringBuilder();
+        final Method[] methods = capturedViewGetPropertyMethods(klass);
+
+        int count = methods.length;
+        for (int i = 0; i < count; i++) {
+            final Method method = methods[i];
+            try {
+                Object methodValue = method.invoke(obj, (Object[]) null);
+                final Class<?> returnType = method.getReturnType();
+                
+                CapturedViewProperty property = method.getAnnotation(CapturedViewProperty.class);
+                if (property.retrieveReturn()) {
+                    //we are interested in the second level data only
+                    sb.append(capturedViewExportMethods(methodValue, returnType, method.getName() + "#"));
+                } else {                    
+                    sb.append(prefix);
+                    sb.append(method.getName());
+                    sb.append("()=");
+                    
+                    if (methodValue != null) {
+                        final String value = methodValue.toString().replace("\n", "\\n");                        
+                        sb.append(value);                        
+                    } else {
+                        sb.append("null");
+                    }
+                    sb.append("; ");
+                }
+              } catch (IllegalAccessException e) {
+                  //Exception IllegalAccess, it is OK here 
+                  //we simply ignore this method
+              } catch (InvocationTargetException e) {
+                  //Exception InvocationTarget, it is OK here 
+                  //we simply ignore this method
+              }              
+        }        
+        return sb.toString();
+    }
+
+    private static String capturedViewExportFields(Object obj, Class<?> klass, String prefix) {
+        
+        if (obj == null) {
+            return "null";
+        }
+        
+        StringBuilder sb = new StringBuilder();
+        final Field[] fields = capturedViewGetPropertyFields(klass);
+
+        int count = fields.length;
+        for (int i = 0; i < count; i++) {
+            final Field field = fields[i];
+            try {
+                Object fieldValue = field.get(obj);
+
+                sb.append(prefix);
+                sb.append(field.getName());
+                sb.append("=");
+
+                if (fieldValue != null) {
+                    final String value = fieldValue.toString().replace("\n", "\\n");
+                    sb.append(value);
+                } else {
+                    sb.append("null");
+                }
+                sb.append(' ');
+            } catch (IllegalAccessException e) {
+                //Exception IllegalAccess, it is OK here 
+                //we simply ignore this field
+            }
+        }
+        return sb.toString();
+    }
+    
+    /**
+     * dump view info for id based instrument test generation 
+     * (and possibly further data analysis). The results are dumped
+     * to the log. 
+     * @param tag for log
+     * @param view for dump
+     * 
+     * @hide pending API Council approval
+     */
+    public static void dumpCapturedView(String tag, Object view) {        
+        Class<?> klass = view.getClass();
+        StringBuilder sb = new StringBuilder(klass.getName() + ": ");
+        sb.append(capturedViewExportFields(view, klass, ""));
+        sb.append(capturedViewExportMethods(view, klass, ""));        
+        Log.d(tag, sb.toString());        
+    }
+}
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
new file mode 100644
index 0000000..70cc2a9
--- /dev/null
+++ b/core/java/android/view/ViewGroup.java
@@ -0,0 +1,3478 @@
+/*
+ * 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 android.view;
+
+import com.android.internal.R;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.graphics.Region;
+import android.graphics.RectF;
+import android.os.Parcelable;
+import android.os.SystemClock;
+import android.util.AttributeSet;
+import android.util.EventLog;
+import android.util.Log;
+import android.util.SparseArray;
+import android.view.animation.Animation;
+import android.view.animation.AnimationUtils;
+import android.view.animation.LayoutAnimationController;
+import android.view.animation.Transformation;
+
+import java.util.ArrayList;
+
+/**
+ * <p>
+ * A <code>ViewGroup</code> is a special view that can contain other views
+ * (called children.) The view group is the base class for layouts and views
+ * containers. This class also defines the
+ * {@link android.view.ViewGroup.LayoutParams} class which serves as the base
+ * class for layouts parameters.
+ * </p>
+ *
+ * <p>
+ * Also see {@link LayoutParams} for layout attributes.
+ * </p>
+ */
+public abstract class ViewGroup extends View implements ViewParent, ViewManager {
+    private static final boolean DBG = false;
+
+    /**
+     * Views which have been hidden or removed which need to be animated on
+     * their way out.
+     * This field should be made private, so it is hidden from the SDK.
+     * {@hide}
+     */
+    protected ArrayList<View> mDisappearingChildren;
+
+    /**
+     * Listener used to propagate events indicating when children are added
+     * and/or removed from a view group.
+     * This field should be made private, so it is hidden from the SDK.
+     * {@hide}
+     */
+    protected OnHierarchyChangeListener mOnHierarchyChangeListener;
+
+    // The view contained within this ViewGroup that has or contains focus.
+    private View mFocused;
+
+    // The current transformation to apply on the child being drawn
+    private Transformation mChildTransformation;
+    private RectF mInvalidateRegion;
+
+    // Target of Motion events
+    private View mMotionTarget;
+    private final Rect mTempRect = new Rect();
+
+    // Layout animation
+    private LayoutAnimationController mLayoutAnimationController;
+    private Animation.AnimationListener mAnimationListener;
+
+    /**
+     * Internal flags.
+     * 
+     * This field should be made private, so it is hidden from the SDK.
+     * {@hide}
+     */
+    protected int mGroupFlags;
+
+    // When set, ViewGroup invalidates only the child's rectangle
+    // Set by default
+    private static final int FLAG_CLIP_CHILDREN = 0x1;
+
+    // When set, ViewGroup excludes the padding area from the invalidate rectangle
+    // Set by default
+    private static final int FLAG_CLIP_TO_PADDING = 0x2;
+
+    // When set, dispatchDraw() will invoke invalidate(); this is set by drawChild() when
+    // a child needs to be invalidated and FLAG_OPTIMIZE_INVALIDATE is set
+    private static final int FLAG_INVALIDATE_REQUIRED  = 0x4;
+
+    // When set, dispatchDraw() will run the layout animation and unset the flag
+    private static final int FLAG_RUN_ANIMATION = 0x8;
+
+    // When set, there is either no layout animation on the ViewGroup or the layout
+    // animation is over
+    // Set by default
+    private static final int FLAG_ANIMATION_DONE = 0x10;
+
+    // If set, this ViewGroup has padding; if unset there is no padding and we don't need
+    // to clip it, even if FLAG_CLIP_TO_PADDING is set
+    private static final int FLAG_PADDING_NOT_NULL = 0x20;
+
+    // When set, this ViewGroup caches its children in a Bitmap before starting a layout animation
+    // Set by default
+    private static final int FLAG_ANIMATION_CACHE = 0x40;
+
+    // When set, this ViewGroup converts calls to invalidate(Rect) to invalidate() during a
+    // layout animation; this avoid clobbering the hierarchy
+    // Automatically set when the layout animation starts, depending on the animation's
+    // characteristics
+    private static final int FLAG_OPTIMIZE_INVALIDATE = 0x80;
+
+    // When set, the next call to drawChild() will clear mChildTransformation's matrix
+    private static final int FLAG_CLEAR_TRANSFORMATION = 0x100;
+
+    // When set, this ViewGroup invokes mAnimationListener.onAnimationEnd() and removes
+    // the children's Bitmap caches if necessary
+    // This flag is set when the layout animation is over (after FLAG_ANIMATION_DONE is set)
+    private static final int FLAG_NOTIFY_ANIMATION_LISTENER = 0x200;
+
+    /**
+     * When set, the drawing method will call {@link #getChildDrawingOrder(int, int)}
+     * to get the index of the child to draw for that iteration.
+     */
+    protected static final int FLAG_USE_CHILD_DRAWING_ORDER = 0x400;
+    
+    /**
+     * When set, this ViewGroup supports static transformations on children; this causes
+     * {@link #getChildStaticTransformation(View, android.view.animation.Transformation)} to be
+     * invoked when a child is drawn.
+     *
+     * Any subclass overriding
+     * {@link #getChildStaticTransformation(View, android.view.animation.Transformation)} should
+     * set this flags in {@link #mGroupFlags}.
+     * 
+     * {@hide}
+     */
+    protected static final int FLAG_SUPPORT_STATIC_TRANSFORMATIONS = 0x800;
+
+    // When the previous drawChild() invocation used an alpha value that was lower than
+    // 1.0 and set it in mCachePaint
+    private static final int FLAG_ALPHA_LOWER_THAN_ONE = 0x1000;
+
+    /**
+     * When set, this ViewGroup's drawable states also include those
+     * of its children.
+     */
+    private static final int FLAG_ADD_STATES_FROM_CHILDREN = 0x2000;
+
+    /**
+     * When set, this ViewGroup tries to always draw its children using their drawing cache.
+     */
+    private static final int FLAG_ALWAYS_DRAWN_WITH_CACHE = 0x4000;
+
+    /**
+     * When set, and if FLAG_ALWAYS_DRAWN_WITH_CACHE is not set, this ViewGroup will try to
+     * draw its children with their drawing cache.
+     */
+    private static final int FLAG_CHILDREN_DRAWN_WITH_CACHE = 0x8000;
+
+    /**
+     * When set, this group will go through its list of children to notify them of
+     * any drawable state change.
+     */
+    private static final int FLAG_NOTIFY_CHILDREN_ON_DRAWABLE_STATE_CHANGE = 0x10000;
+
+    private static final int FLAG_MASK_FOCUSABILITY = 0x60000;
+
+    /**
+     * This view will get focus before any of its descendants.
+     */
+    public static final int FOCUS_BEFORE_DESCENDANTS = 0x20000;
+
+    /**
+     * This view will get focus only if none of its descendants want it.
+     */
+    public static final int FOCUS_AFTER_DESCENDANTS = 0x40000;
+
+    /**
+     * This view will block any of its descendants from getting focus, even
+     * if they are focusable.
+     */
+    public static final int FOCUS_BLOCK_DESCENDANTS = 0x60000;
+
+    /**
+     * Used to map between enum in attrubutes and flag values.
+     */
+    private static final int[] DESCENDANT_FOCUSABILITY_FLAGS =
+            {FOCUS_BEFORE_DESCENDANTS, FOCUS_AFTER_DESCENDANTS,
+                    FOCUS_BLOCK_DESCENDANTS};
+
+    /**
+     * When set, this ViewGroup should not intercept touch events.
+     */
+    private static final int FLAG_DISALLOW_INTERCEPT = 0x80000;
+    
+    /**
+     * Indicates which types of drawing caches are to be kept in memory.
+     * This field should be made private, so it is hidden from the SDK.
+     * {@hide}
+     */
+    protected int mPersistentDrawingCache;
+
+    /**
+     * Used to indicate that no drawing cache should be kept in memory.
+     */
+    public static final int PERSISTENT_NO_CACHE = 0x0;
+
+    /**
+     * Used to indicate that the animation drawing cache should be kept in memory.
+     */
+    public static final int PERSISTENT_ANIMATION_CACHE = 0x1;
+
+    /**
+     * Used to indicate that the scrolling drawing cache should be kept in memory.
+     */
+    public static final int PERSISTENT_SCROLLING_CACHE = 0x2;
+
+    /**
+     * Used to indicate that all drawing caches should be kept in memory.
+     */
+    public static final int PERSISTENT_ALL_CACHES = 0x3;
+
+    /**
+     * We clip to padding when FLAG_CLIP_TO_PADDING and FLAG_PADDING_NOT_NULL
+     * are set at the same time.
+     */
+    protected static final int CLIP_TO_PADDING_MASK = FLAG_CLIP_TO_PADDING | FLAG_PADDING_NOT_NULL;
+
+    // Index of the child's left position in the mLocation array
+    private static final int CHILD_LEFT_INDEX = 0;
+    // Index of the child's top position in the mLocation array
+    private static final int CHILD_TOP_INDEX = 1;
+
+    // Child views of this ViewGroup
+    private View[] mChildren;
+    // Number of valid children in the mChildren array, the rest should be null or not
+    // considered as children
+    private int mChildrenCount;
+
+    private static final int ARRAY_INITIAL_CAPACITY = 12;
+    private static final int ARRAY_CAPACITY_INCREMENT = 12;
+
+    // Used to draw cached views
+    private final Paint mCachePaint = new Paint();
+
+    public ViewGroup(Context context) {
+        super(context);
+        initViewGroup();
+    }
+
+    public ViewGroup(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        initViewGroup();
+        initFromAttributes(context, attrs);
+    }
+
+    public ViewGroup(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+        initViewGroup();
+        initFromAttributes(context, attrs);
+    }
+
+    private void initViewGroup() {
+        // ViewGroup doesn't draw by default
+        setFlags(WILL_NOT_DRAW, DRAW_MASK);
+        mGroupFlags |= FLAG_CLIP_CHILDREN;
+        mGroupFlags |= FLAG_CLIP_TO_PADDING;
+        mGroupFlags |= FLAG_ANIMATION_DONE;
+        mGroupFlags |= FLAG_ANIMATION_CACHE;
+        mGroupFlags |= FLAG_ALWAYS_DRAWN_WITH_CACHE;
+
+        setDescendantFocusability(FOCUS_BEFORE_DESCENDANTS);
+
+        mChildren = new View[ARRAY_INITIAL_CAPACITY];
+        mChildrenCount = 0;
+
+        mCachePaint.setDither(false);
+
+        mPersistentDrawingCache = PERSISTENT_SCROLLING_CACHE;
+    }
+
+    private void initFromAttributes(Context context, AttributeSet attrs) {
+        TypedArray a = context.obtainStyledAttributes(attrs,
+                R.styleable.ViewGroup);
+
+        final int N = a.getIndexCount();
+        for (int i = 0; i < N; i++) {
+            int attr = a.getIndex(i);
+            switch (attr) {
+                case R.styleable.ViewGroup_clipChildren:
+                    setClipChildren(a.getBoolean(attr, true));
+                    break;
+                case R.styleable.ViewGroup_clipToPadding:
+                    setClipToPadding(a.getBoolean(attr, true));
+                    break;
+                case R.styleable.ViewGroup_animationCache:
+                    setAnimationCacheEnabled(a.getBoolean(attr, true));
+                    break;
+                case R.styleable.ViewGroup_persistentDrawingCache:
+                    setPersistentDrawingCache(a.getInt(attr, PERSISTENT_SCROLLING_CACHE));
+                    break;
+                case R.styleable.ViewGroup_addStatesFromChildren:
+                    setAddStatesFromChildren(a.getBoolean(attr, false));
+                    break;
+                case R.styleable.ViewGroup_alwaysDrawnWithCache:
+                    setAlwaysDrawnWithCacheEnabled(a.getBoolean(attr, true));
+                    break;
+                case R.styleable.ViewGroup_layoutAnimation:
+                    int id = a.getResourceId(attr, -1);
+                    if (id > 0) {
+                        setLayoutAnimation(AnimationUtils.loadLayoutAnimation(mContext, id));
+                    }
+                    break;
+                case R.styleable.ViewGroup_descendantFocusability:
+                    setDescendantFocusability(DESCENDANT_FOCUSABILITY_FLAGS[a.getInt(attr, 0)]);
+                    break;
+            }
+        }
+
+        a.recycle();
+    }
+
+    /**
+     * Gets the descendant focusability of this view group.  The descendant
+     * focusability defines the relationship between this view group and its
+     * descendants when looking for a view to take focus in
+     * {@link #requestFocus(int, android.graphics.Rect)}.
+     *
+     * @return one of {@link #FOCUS_BEFORE_DESCENDANTS}, {@link #FOCUS_AFTER_DESCENDANTS},
+     *   {@link #FOCUS_BLOCK_DESCENDANTS}.
+     */
+    @ViewDebug.ExportedProperty(mapping = {
+        @ViewDebug.IntToString(from = FOCUS_BEFORE_DESCENDANTS, to = "FOCUS_BEFORE_DESCENDANTS"),
+        @ViewDebug.IntToString(from = FOCUS_AFTER_DESCENDANTS, to = "FOCUS_AFTER_DESCENDANTS"),
+        @ViewDebug.IntToString(from = FOCUS_BLOCK_DESCENDANTS, to = "FOCUS_BLOCK_DESCENDANTS")
+    })
+    public int getDescendantFocusability() {
+        return mGroupFlags & FLAG_MASK_FOCUSABILITY;
+    }
+
+    /**
+     * Set the descendant focusability of this view group. This defines the relationship
+     * between this view group and its descendants when looking for a view to
+     * take focus in {@link #requestFocus(int, android.graphics.Rect)}.
+     *
+     * @param focusability one of {@link #FOCUS_BEFORE_DESCENDANTS}, {@link #FOCUS_AFTER_DESCENDANTS},
+     *   {@link #FOCUS_BLOCK_DESCENDANTS}.
+     */
+    public void setDescendantFocusability(int focusability) {
+        switch (focusability) {
+            case FOCUS_BEFORE_DESCENDANTS:
+            case FOCUS_AFTER_DESCENDANTS:
+            case FOCUS_BLOCK_DESCENDANTS:
+                break;
+            default:
+                throw new IllegalArgumentException("must be one of FOCUS_BEFORE_DESCENDANTS, "
+                        + "FOCUS_AFTER_DESCENDANTS, FOCUS_BLOCK_DESCENDANTS");
+        }
+        mGroupFlags &= ~FLAG_MASK_FOCUSABILITY;
+        mGroupFlags |= (focusability & FLAG_MASK_FOCUSABILITY);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    void handleFocusGainInternal(int direction, Rect previouslyFocusedRect) {
+        if (mFocused != null) {
+            mFocused.unFocus();
+            mFocused = null;
+        }
+        super.handleFocusGainInternal(direction, previouslyFocusedRect);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void requestChildFocus(View child, View focused) {
+        if (DBG) {
+            System.out.println(this + " requestChildFocus()");
+        }
+        if (getDescendantFocusability() == FOCUS_BLOCK_DESCENDANTS) {
+            return;
+        }
+
+        // Unfocus us, if necessary
+        super.unFocus();
+
+        // We had a previous notion of who had focus. Clear it.
+        if (mFocused != child) {
+            if (mFocused != null) {
+                mFocused.unFocus();
+            }
+
+            mFocused = child;
+        }
+        if (mParent != null) {
+            mParent.requestChildFocus(this, focused);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void focusableViewAvailable(View v) {
+        if (mParent != null
+                // shortcut: don't report a new focusable view if we block our descendants from
+                // getting focus
+                && (getDescendantFocusability() != FOCUS_BLOCK_DESCENDANTS)
+                // shortcut: don't report a new focusable view if we already are focused
+                // (and we don't prefer our descendants)
+                //
+                // note: knowing that mFocused is non-null is not a good enough reason
+                // to break the traversal since in that case we'd actually have to find
+                // the focused view and make sure it wasn't FOCUS_AFTER_DESCENDANTS and
+                // an ancestor of v; this will get checked for at ViewRoot
+                && !(isFocused() && getDescendantFocusability() != FOCUS_AFTER_DESCENDANTS)) {
+            mParent.focusableViewAvailable(v);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public boolean showContextMenuForChild(View originalView) {
+        return mParent != null && mParent.showContextMenuForChild(originalView);
+    }
+
+    /**
+     * Find the nearest view in the specified direction that wants to take
+     * focus.
+     *
+     * @param focused The view that currently has focus
+     * @param direction One of FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, and
+     *        FOCUS_RIGHT, or 0 for not applicable.
+     */
+    public View focusSearch(View focused, int direction) {
+        if (isRootNamespace()) {
+            // root namespace means we should consider ourselves the top of the
+            // tree for focus searching; otherwise we could be focus searching
+            // into other tabs.  see LocalActivityManager and TabHost for more info
+            return FocusFinder.getInstance().findNextFocus(this, focused, direction);
+        } else if (mParent != null) {
+            return mParent.focusSearch(focused, direction);
+        }
+        return null;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public boolean requestChildRectangleOnScreen(View child, Rect rectangle, boolean immediate) {
+        return false;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public boolean dispatchUnhandledMove(View focused, int direction) {
+        return mFocused != null &&
+                mFocused.dispatchUnhandledMove(focused, direction);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void clearChildFocus(View child) {
+        if (DBG) {
+            System.out.println(this + " clearChildFocus()");
+        }
+
+        mFocused = null;
+        if (mParent != null) {
+            mParent.clearChildFocus(this);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void clearFocus() {
+        super.clearFocus();
+
+        // clear any child focus if it exists
+        if (mFocused != null) {
+            mFocused.clearFocus();
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    void unFocus() {
+        if (DBG) {
+            System.out.println(this + " unFocus()");
+        }
+
+        super.unFocus();
+        if (mFocused != null) {
+            mFocused.unFocus();
+        }
+        mFocused = null;
+    }
+
+    /**
+     * Returns the focused child of this view, if any. The child may have focus
+     * or contain focus.
+     *
+     * @return the focused child or null.
+     */
+    public View getFocusedChild() {
+        return mFocused;
+    }
+
+    /**
+     * Returns true if this view has or contains focus
+     *
+     * @return true if this view has or contains focus
+     */
+    @Override
+    public boolean hasFocus() {
+        return (mPrivateFlags & FOCUSED) != 0 || mFocused != null;
+    }
+
+    /*
+     * (non-Javadoc)
+     *
+     * @see android.view.View#findFocus()
+     */
+    @Override
+    public View findFocus() {
+        if (DBG) {
+            System.out.println("Find focus in " + this + ": flags="
+                    + isFocused() + ", child=" + mFocused);
+        }
+
+        if (isFocused()) {
+            return this;
+        }
+
+        if (mFocused != null) {
+            return mFocused.findFocus();
+        }
+        return null;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public boolean hasFocusable() {
+        if ((mViewFlags & VISIBILITY_MASK) != VISIBLE) {
+            return false;
+        }
+
+        if (isFocusable()) {
+            return true;
+        }
+
+        final int descendantFocusability = getDescendantFocusability();
+        if (descendantFocusability != FOCUS_BLOCK_DESCENDANTS) {
+            final int count = mChildrenCount;
+            final View[] children = mChildren;
+
+            for (int i = 0; i < count; i++) {
+                final View child = children[i];
+                if (child.hasFocusable()) {
+                    return true;
+                }
+            }
+        }
+
+        return false;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void addFocusables(ArrayList<View> views, int direction) {
+        final int focusableCount = views.size();
+
+        final int descendantFocusability = getDescendantFocusability();
+
+        if (descendantFocusability != FOCUS_BLOCK_DESCENDANTS) {
+            final int count = mChildrenCount;
+            final View[] children = mChildren;
+
+            for (int i = 0; i < count; i++) {
+                final View child = children[i];
+                if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE) {
+                    child.addFocusables(views, direction);
+                }
+            }
+        }
+
+        // we add ourselves (if focusable) in all cases except for when we are
+        // FOCUS_AFTER_DESCENDANTS and there are some descendants focusable.  this is
+        // to avoid the focus search finding layouts when a more precise search
+        // among the focusable children would be more interesting.
+        if (
+            descendantFocusability != FOCUS_AFTER_DESCENDANTS ||
+                // No focusable descendants
+                (focusableCount == views.size())) {
+            super.addFocusables(views, direction);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void dispatchWindowFocusChanged(boolean hasFocus) {
+        super.dispatchWindowFocusChanged(hasFocus);
+        final int count = mChildrenCount;
+        final View[] children = mChildren;
+        for (int i = 0; i < count; i++) {
+            children[i].dispatchWindowFocusChanged(hasFocus);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void addTouchables(ArrayList<View> views) {
+        super.addTouchables(views);
+
+        final int count = mChildrenCount;
+        final View[] children = mChildren;
+
+        for (int i = 0; i < count; i++) {
+            final View child = children[i];
+            if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE) {
+                child.addTouchables(views);
+            }
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void dispatchWindowVisibilityChanged(int visibility) {
+        super.dispatchWindowVisibilityChanged(visibility);
+        final int count = mChildrenCount;
+        final View[] children = mChildren;
+        for (int i = 0; i < count; i++) {
+            children[i].dispatchWindowVisibilityChanged(visibility);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void recomputeViewAttributes(View child) {
+        ViewParent parent = mParent;
+        if (parent != null) parent.recomputeViewAttributes(this);
+    }
+    
+    @Override
+    void dispatchCollectViewAttributes(int visibility) {
+        visibility |= mViewFlags&VISIBILITY_MASK;
+        super.dispatchCollectViewAttributes(visibility);
+        final int count = mChildrenCount;
+        final View[] children = mChildren;
+        for (int i = 0; i < count; i++) {
+            children[i].dispatchCollectViewAttributes(visibility);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void bringChildToFront(View child) {
+        int index = indexOfChild(child);
+        if (index >= 0) {
+            removeFromArray(index);
+            addInArray(child, mChildrenCount);
+            child.mParent = this;
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public boolean dispatchKeyEventPreIme(KeyEvent event) {
+        if ((mPrivateFlags & (FOCUSED | HAS_BOUNDS)) == (FOCUSED | HAS_BOUNDS)) {
+            return super.dispatchKeyEventPreIme(event);
+        } else if (mFocused != null && (mFocused.mPrivateFlags & HAS_BOUNDS) == HAS_BOUNDS) {
+            return mFocused.dispatchKeyEventPreIme(event);
+        }
+        return false;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public boolean dispatchKeyEvent(KeyEvent event) {
+        if ((mPrivateFlags & (FOCUSED | HAS_BOUNDS)) == (FOCUSED | HAS_BOUNDS)) {
+            return super.dispatchKeyEvent(event);
+        } else if (mFocused != null && (mFocused.mPrivateFlags & HAS_BOUNDS) == HAS_BOUNDS) {
+            return mFocused.dispatchKeyEvent(event);
+        }
+        return false;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public boolean dispatchKeyShortcutEvent(KeyEvent event) {
+        if ((mPrivateFlags & (FOCUSED | HAS_BOUNDS)) == (FOCUSED | HAS_BOUNDS)) {
+            return super.dispatchKeyShortcutEvent(event);
+        } else if (mFocused != null && (mFocused.mPrivateFlags & HAS_BOUNDS) == HAS_BOUNDS) {
+            return mFocused.dispatchKeyShortcutEvent(event);
+        }
+        return false;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public boolean dispatchTrackballEvent(MotionEvent event) {
+        if ((mPrivateFlags & (FOCUSED | HAS_BOUNDS)) == (FOCUSED | HAS_BOUNDS)) {
+            return super.dispatchTrackballEvent(event);
+        } else if (mFocused != null && (mFocused.mPrivateFlags & HAS_BOUNDS) == HAS_BOUNDS) {
+            return mFocused.dispatchTrackballEvent(event);
+        }
+        return false;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public boolean dispatchTouchEvent(MotionEvent ev) {
+        final int action = ev.getAction();
+        final float xf = ev.getX();
+        final float yf = ev.getY();
+        final float scrolledXFloat = xf + mScrollX;
+        final float scrolledYFloat = yf + mScrollY;
+        final Rect frame = mTempRect;
+
+        boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
+
+        if (action == MotionEvent.ACTION_DOWN) {
+            if (mMotionTarget != null) {
+                // this is weird, we got a pen down, but we thought it was
+                // already down!
+                // XXX: We should probably send an ACTION_UP to the current
+                // target.
+                mMotionTarget = null;
+            }
+            // If we're disallowing intercept or if we're allowing and we didn't
+            // intercept
+            if (disallowIntercept || !onInterceptTouchEvent(ev)) {
+                // reset this event's action (just to protect ourselves)
+                ev.setAction(MotionEvent.ACTION_DOWN);
+                // We know we want to dispatch the event down, find a child
+                // who can handle it, start with the front-most child.
+                final int scrolledXInt = (int) scrolledXFloat;
+                final int scrolledYInt = (int) scrolledYFloat;
+                final View[] children = mChildren;
+                final int count = mChildrenCount;
+                for (int i = count - 1; i >= 0; i--) {
+                    final View child = children[i];
+                    if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE
+                            || child.getAnimation() != null) {
+                        child.getHitRect(frame);
+                        if (frame.contains(scrolledXInt, scrolledYInt)) {
+                            // offset the event to the view's coordinate system
+                            final float xc = scrolledXFloat - child.mLeft;
+                            final float yc = scrolledYFloat - child.mTop;
+                            ev.setLocation(xc, yc);
+                            if (child.dispatchTouchEvent(ev))  {
+                                // Event handled, we have a target now.
+                                mMotionTarget = child;
+                                return true;
+                            }
+                            // The event didn't get handled, try the next view.
+                            // Don't reset the event's location, it's not
+                            // necessary here.
+                        }
+                    }
+                }
+            }
+        }
+        
+        boolean isUpOrCancel = (action == MotionEvent.ACTION_UP) ||
+                (action == MotionEvent.ACTION_CANCEL); 
+
+        if (isUpOrCancel) {
+            // Note, we've already copied the previous state to our local
+            // variable, so this takes effect on the next event
+            mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
+        }
+        
+        // The event wasn't an ACTION_DOWN, dispatch it to our target if
+        // we have one.
+        final View target = mMotionTarget;
+        if (target == null) {
+            // We don't have a target, this means we're handling the
+            // event as a regular view.
+            ev.setLocation(xf, yf);
+            return super.dispatchTouchEvent(ev);
+        }
+
+        // if have a target, see if we're allowed to and want to intercept its
+        // events
+        if (!disallowIntercept && onInterceptTouchEvent(ev)) {
+            final float xc = scrolledXFloat - (float) target.mLeft;
+            final float yc = scrolledYFloat - (float) target.mTop;
+            ev.setAction(MotionEvent.ACTION_CANCEL);
+            ev.setLocation(xc, yc);
+            if (!target.dispatchTouchEvent(ev)) {
+                // target didn't handle ACTION_CANCEL. not much we can do
+                // but they should have.
+            }
+            // clear the target
+            mMotionTarget = null;
+            // Don't dispatch this event to our own view, because we already
+            // saw it when intercepting; we just want to give the following
+            // event to the normal onTouchEvent().
+            return true;
+        }
+
+        if (isUpOrCancel) {
+            mMotionTarget = null;
+        }
+
+        // finally offset the event to the target's coordinate system and
+        // dispatch the event.
+        final float xc = scrolledXFloat - (float) target.mLeft;
+        final float yc = scrolledYFloat - (float) target.mTop;
+        ev.setLocation(xc, yc);
+
+        return target.dispatchTouchEvent(ev);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
+        
+        if (disallowIntercept == ((mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0)) {
+            // We're already in this state, assume our ancestors are too
+            return;
+        }
+        
+        if (disallowIntercept) {
+            mGroupFlags |= FLAG_DISALLOW_INTERCEPT;
+        } else {
+            mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
+        }
+        
+        // Pass it up to our parent
+        if (mParent != null) {
+            mParent.requestDisallowInterceptTouchEvent(disallowIntercept);
+        }
+    }
+
+    /**
+     * Implement this method to intercept all touch screen motion events.  This
+     * allows you to watch events as they are dispatched to your children, and
+     * take ownership of the current gesture at any point.
+     *
+     * <p>Using this function takes some care, as it has a fairly complicated
+     * interaction with {@link View#onTouchEvent(MotionEvent)
+     * View.onTouchEvent(MotionEvent)}, and using it requires implementing
+     * that method as well as this one in the correct way.  Events will be
+     * received in the following order:
+     *
+     * <ol>
+     * <li> You will receive the down event here.
+     * <li> The down event will be handled either by a child of this view
+     * group, or given to your own onTouchEvent() method to handle; this means
+     * you should implement onTouchEvent() to return true, so you will
+     * continue to see the rest of the gesture (instead of looking for
+     * a parent view to handle it).  Also, by returning true from
+     * onTouchEvent(), you will not receive any following
+     * events in onInterceptTouchEvent() and all touch processing must
+     * happen in onTouchEvent() like normal.
+     * <li> For as long as you return false from this function, each following
+     * event (up to and including the final up) will be delivered first here
+     * and then to the target's onTouchEvent().
+     * <li> If you return true from here, you will not receive any
+     * following events: the target view will receive the same event but
+     * with the action {@link MotionEvent#ACTION_CANCEL}, and all further
+     * events will be delivered to your onTouchEvent() method and no longer
+     * appear here.
+     * </ol>
+     *
+     * @param ev The motion event being dispatched down the hierarchy.
+     * @return Return true to steal motion events from the children and have
+     * them dispatched to this ViewGroup through onTouchEvent().
+     * The current target will receive an ACTION_CANCEL event, and no further
+     * messages will be delivered here.
+     */
+    public boolean onInterceptTouchEvent(MotionEvent ev) {
+        return false;
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * Looks for a view to give focus to respecting the setting specified by
+     * {@link #getDescendantFocusability()}.
+     *
+     * Uses {@link #onRequestFocusInDescendants(int, android.graphics.Rect)} to
+     * find focus within the children of this group when appropriate.
+     *
+     * @see #FOCUS_BEFORE_DESCENDANTS
+     * @see #FOCUS_AFTER_DESCENDANTS
+     * @see #FOCUS_BLOCK_DESCENDANTS
+     * @see #onRequestFocusInDescendants
+     */
+    @Override
+    public boolean requestFocus(int direction, Rect previouslyFocusedRect) {
+        if (DBG) {
+            System.out.println(this + " ViewGroup.requestFocus direction="
+                    + direction);
+        }
+        int descendantFocusability = getDescendantFocusability();
+
+        switch (descendantFocusability) {
+            case FOCUS_BLOCK_DESCENDANTS:
+                return super.requestFocus(direction, previouslyFocusedRect);
+            case FOCUS_BEFORE_DESCENDANTS: {
+                final boolean took = super.requestFocus(direction, previouslyFocusedRect);
+                return took ? took : onRequestFocusInDescendants(direction, previouslyFocusedRect);
+            }
+            case FOCUS_AFTER_DESCENDANTS: {
+                final boolean took = onRequestFocusInDescendants(direction, previouslyFocusedRect);
+                return took ? took : super.requestFocus(direction, previouslyFocusedRect);
+            }
+            default:
+                throw new IllegalStateException("descendant focusability must be "
+                        + "one of FOCUS_BEFORE_DESCENDANTS, FOCUS_AFTER_DESCENDANTS, FOCUS_BLOCK_DESCENDANTS "
+                        + "but is " + descendantFocusability);
+        }
+    }
+
+    /**
+     * Look for a descendant to call {@link View#requestFocus} on.
+     * Called by {@link ViewGroup#requestFocus(int, android.graphics.Rect)}
+     * when it wants to request focus within its children.  Override this to
+     * customize how your {@link ViewGroup} requests focus within its children.
+     * @param direction One of FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, and FOCUS_RIGHT
+     * @param previouslyFocusedRect The rectangle (in this View's coordinate system)
+     *        to give a finer grained hint about where focus is coming from.  May be null
+     *        if there is no hint.
+     * @return Whether focus was taken.
+     */
+    @SuppressWarnings({"ConstantConditions"})
+    protected boolean onRequestFocusInDescendants(int direction,
+            Rect previouslyFocusedRect) {
+        int index;
+        int increment;
+        int end;
+        int count = mChildrenCount;
+        if ((direction & FOCUS_FORWARD) != 0) {
+            index = 0;
+            increment = 1;
+            end = count;
+        } else {
+            index = count - 1;
+            increment = -1;
+            end = -1;
+        }
+        final View[] children = mChildren;
+        for (int i = index; i != end; i += increment) {
+            View child = children[i];
+            if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE) {
+                if (child.requestFocus(direction, previouslyFocusedRect)) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    void dispatchAttachedToWindow(AttachInfo info, int visibility) {
+        super.dispatchAttachedToWindow(info, visibility);
+        visibility |= mViewFlags & VISIBILITY_MASK;
+        final int count = mChildrenCount;
+        final View[] children = mChildren;
+        for (int i = 0; i < count; i++) {
+            children[i].dispatchAttachedToWindow(info, visibility);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    void dispatchDetachedFromWindow() {
+        // If we still have a motion target, we are still in the process of
+        // dispatching motion events to a child; we need to get rid of that
+        // child to avoid dispatching events to it after the window is torn
+        // down. To make sure we keep the child in a consistent state, we
+        // first send it an ACTION_CANCEL motion event.
+        if (mMotionTarget != null) {
+            final long now = SystemClock.uptimeMillis();
+            final MotionEvent event = MotionEvent.obtain(now, now,
+                    MotionEvent.ACTION_CANCEL, 0.0f, 0.0f, 0);
+            mMotionTarget.dispatchTouchEvent(event);
+            event.recycle();
+            mMotionTarget = null;
+        }
+
+        final int count = mChildrenCount;
+        final View[] children = mChildren;
+        for (int i = 0; i < count; i++) {
+            children[i].dispatchDetachedFromWindow();
+        }
+        super.dispatchDetachedFromWindow();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void setPadding(int left, int top, int right, int bottom) {
+        super.setPadding(left, top, right, bottom);
+
+        if ((mPaddingLeft | mPaddingTop | mPaddingRight | mPaddingRight) != 0) {
+            mGroupFlags |= FLAG_PADDING_NOT_NULL;
+        } else {
+            mGroupFlags &= ~FLAG_PADDING_NOT_NULL;
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    protected void dispatchSaveInstanceState(SparseArray<Parcelable> container) {
+        super.dispatchSaveInstanceState(container);
+        final int count = mChildrenCount;
+        final View[] children = mChildren;
+        for (int i = 0; i < count; i++) {
+            children[i].dispatchSaveInstanceState(container);
+        }
+    }
+
+    /**
+     * Perform dispatching of a {@link #saveHierarchyState freeze()} to only this view,
+     * not to its children.  For use when overriding
+     * {@link #dispatchSaveInstanceState dispatchFreeze()} to allow subclasses to freeze
+     * their own state but not the state of their children.
+     *
+     * @param container the container
+     */
+    protected void dispatchFreezeSelfOnly(SparseArray<Parcelable> container) {
+        super.dispatchSaveInstanceState(container);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    protected void dispatchRestoreInstanceState(SparseArray<Parcelable> container) {
+        super.dispatchRestoreInstanceState(container);
+        final int count = mChildrenCount;
+        final View[] children = mChildren;
+        for (int i = 0; i < count; i++) {
+            children[i].dispatchRestoreInstanceState(container);
+        }
+    }
+
+    /**
+     * Perform dispatching of a {@link #restoreHierarchyState thaw()} to only this view,
+     * not to its children.  For use when overriding
+     * {@link #dispatchRestoreInstanceState dispatchThaw()} to allow subclasses to thaw
+     * their own state but not the state of their children.
+     *
+     * @param container the container
+     */
+    protected void dispatchThawSelfOnly(SparseArray<Parcelable> container) {
+        super.dispatchRestoreInstanceState(container);
+    }
+
+    /**
+     * Enables or disables the drawing cache for each child of this view group.
+     *
+     * @param enabled true to enable the cache, false to dispose of it
+     */
+    protected void setChildrenDrawingCacheEnabled(boolean enabled) {
+        if (enabled || (mPersistentDrawingCache & PERSISTENT_ALL_CACHES) != PERSISTENT_ALL_CACHES) {
+            final View[] children = mChildren;
+            final int count = mChildrenCount;
+            for (int i = 0; i < count; i++) {
+                children[i].setDrawingCacheEnabled(enabled);
+            }
+        }
+    }
+
+    @Override
+    protected void onAnimationStart() {
+        super.onAnimationStart();
+
+        // When this ViewGroup's animation starts, build the cache for the children
+        if ((mGroupFlags & FLAG_ANIMATION_CACHE) == FLAG_ANIMATION_CACHE) {
+            final int count = mChildrenCount;
+            final View[] children = mChildren;
+
+            for (int i = 0; i < count; i++) {
+                final View child = children[i];
+                if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE) {
+                    child.setDrawingCacheEnabled(true);
+                    child.buildDrawingCache();
+                }
+            }
+
+            mGroupFlags |= FLAG_CHILDREN_DRAWN_WITH_CACHE;
+        }
+    }
+
+    @Override
+    protected void onAnimationEnd() {
+        super.onAnimationEnd();
+
+        // When this ViewGroup's animation ends, destroy the cache of the children
+        if ((mGroupFlags & FLAG_ANIMATION_CACHE) == FLAG_ANIMATION_CACHE) {
+            mGroupFlags &= ~FLAG_CHILDREN_DRAWN_WITH_CACHE;
+
+            if ((mPersistentDrawingCache & PERSISTENT_ANIMATION_CACHE) == 0) {
+                setChildrenDrawingCacheEnabled(false);
+            }
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    protected void dispatchDraw(Canvas canvas) {
+        final int count = mChildrenCount;
+        final View[] children = mChildren;
+        int flags = mGroupFlags;
+
+        if ((flags & FLAG_RUN_ANIMATION) != 0 && canAnimate()) {
+            final boolean cache = (mGroupFlags & FLAG_ANIMATION_CACHE) == FLAG_ANIMATION_CACHE;
+
+            for (int i = 0; i < count; i++) {
+                final View child = children[i];
+                if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE) {
+                    final LayoutParams params = child.getLayoutParams();
+                    attachLayoutAnimationParameters(child, params, i, count);
+                    bindLayoutAnimation(child);
+                    if (cache) {
+                        child.setDrawingCacheEnabled(true);
+                        child.buildDrawingCache();
+                    }
+                }
+            }
+
+            final LayoutAnimationController controller = mLayoutAnimationController;
+            if (controller.willOverlap()) {
+                mGroupFlags |= FLAG_OPTIMIZE_INVALIDATE;
+            }
+
+            controller.start();
+
+            mGroupFlags &= ~FLAG_RUN_ANIMATION;
+            mGroupFlags &= ~FLAG_ANIMATION_DONE;
+
+            if (cache) {
+                mGroupFlags |= FLAG_CHILDREN_DRAWN_WITH_CACHE;
+            }
+
+            if (mAnimationListener != null) {
+                mAnimationListener.onAnimationStart(controller.getAnimation());
+            }
+        }
+
+        int saveCount = 0;
+        final boolean clipToPadding = (flags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK;
+        if (clipToPadding) {
+            saveCount = canvas.save();
+            final int scrollX = mScrollX;
+            final int scrollY = mScrollY;
+            canvas.clipRect(scrollX + mPaddingLeft, scrollY + mPaddingTop,
+                    scrollX + mRight - mLeft - mPaddingRight,
+                    scrollY + mBottom - mTop - mPaddingBottom);
+
+        }
+
+        // We will draw our child's animation, let's reset the flag
+        mPrivateFlags &= ~DRAW_ANIMATION;
+        mGroupFlags &= ~FLAG_INVALIDATE_REQUIRED;
+
+        boolean more = false;
+        final long drawingTime = getDrawingTime();
+
+        if ((flags & FLAG_USE_CHILD_DRAWING_ORDER) == 0) {
+            for (int i = 0; i < count; i++) {
+                final View child = children[i];
+                if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {
+                    more |= drawChild(canvas, child, drawingTime);
+                }
+            }
+        } else {
+            for (int i = 0; i < count; i++) {
+                final View child = children[getChildDrawingOrder(count, i)];
+                if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {
+                    more |= drawChild(canvas, child, drawingTime);
+                }
+            }
+        }
+
+        // Draw any disappearing views that have animations
+        if (mDisappearingChildren != null) {
+            final ArrayList<View> disappearingChildren = mDisappearingChildren;
+            final int disappearingCount = disappearingChildren.size() - 1;
+            // Go backwards -- we may delete as animations finish
+            for (int i = disappearingCount; i >= 0; i--) {
+                final View child = disappearingChildren.get(i);
+                more |= drawChild(canvas, child, drawingTime);
+            }
+        }
+
+        if (clipToPadding) {
+            canvas.restoreToCount(saveCount);
+        }
+
+        // mGroupFlags might have been updated by drawChild()
+        flags = mGroupFlags;
+
+        if ((flags & FLAG_INVALIDATE_REQUIRED) == FLAG_INVALIDATE_REQUIRED) {
+            invalidate();
+        }
+
+        if ((flags & FLAG_ANIMATION_DONE) == 0 && (flags & FLAG_NOTIFY_ANIMATION_LISTENER) == 0 &&
+                mLayoutAnimationController.isDone() && !more) {
+            // We want to erase the drawing cache and notify the listener after the
+            // next frame is drawn because one extra invalidate() is caused by
+            // drawChild() after the animation is over
+            mGroupFlags |= FLAG_NOTIFY_ANIMATION_LISTENER;
+            final Runnable end = new Runnable() {
+               public void run() {
+                   notifyAnimationListener();
+               }
+            };
+            post(end);
+        }
+    }
+    
+    /**
+     * Returns the index of the child to draw for this iteration. Override this
+     * if you want to change the drawing order of children. By default, it
+     * returns i.
+     * <p>
+     * NOTE: In order for this method to be called, the
+     * {@link #FLAG_USE_CHILD_DRAWING_ORDER} must be set.
+     * 
+     * @param i The current iteration.
+     * @return The index of the child to draw this iteration.
+     */
+    protected int getChildDrawingOrder(int childCount, int i) {
+        return i;
+    }
+    
+    private void notifyAnimationListener() {
+        mGroupFlags &= ~FLAG_NOTIFY_ANIMATION_LISTENER;
+        mGroupFlags |= FLAG_ANIMATION_DONE;
+
+        if (mAnimationListener != null) {
+           final Runnable end = new Runnable() {
+               public void run() {
+                   mAnimationListener.onAnimationEnd(mLayoutAnimationController.getAnimation());
+               }
+           };
+           post(end);
+        }
+
+        if ((mGroupFlags & FLAG_ANIMATION_CACHE) == FLAG_ANIMATION_CACHE) {
+            mGroupFlags &= ~FLAG_CHILDREN_DRAWN_WITH_CACHE;
+            if ((mPersistentDrawingCache & PERSISTENT_ANIMATION_CACHE) == 0) {
+                setChildrenDrawingCacheEnabled(false);
+            }
+        }
+
+        invalidate();
+    }
+
+    /**
+     * Draw one child of this View Group. This method is responsible for getting
+     * the canvas in the right state. This includes clipping, translating so
+     * that the child's scrolled origin is at 0, 0, and applying any animation
+     * transformations.
+     *
+     * @param canvas The canvas on which to draw the child
+     * @param child Who to draw
+     * @param drawingTime The time at which draw is occuring
+     * @return True if an invalidate() was issued
+     */
+    protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
+        boolean more = false;
+
+        final int cl = child.mLeft;
+        final int ct = child.mTop;
+        final int cr = child.mRight;
+        final int cb = child.mBottom;
+
+        final int flags = mGroupFlags;
+
+        if ((flags & FLAG_CLEAR_TRANSFORMATION) == FLAG_CLEAR_TRANSFORMATION) {
+            if (mChildTransformation != null) {
+                mChildTransformation.clear();
+            }
+            mGroupFlags &= ~FLAG_CLEAR_TRANSFORMATION;
+        }
+
+        Transformation transformToApply = null;
+        final Animation a = child.getAnimation();
+        boolean concatMatrix = false;
+
+        final int childWidth = cr - cl;
+        final int childHeight = cb - ct;
+
+        if (a != null) {
+            if (mInvalidateRegion == null) {
+                mInvalidateRegion = new RectF();
+            }
+            final RectF region = mInvalidateRegion;
+
+            final boolean initialized = a.isInitialized();
+            if (!initialized) {
+                a.initialize(childWidth, childHeight, getWidth(), getHeight());
+                a.initializeInvalidateRegion(0, 0, childWidth, childHeight);
+                child.onAnimationStart();
+            }
+
+            if (mChildTransformation == null) {
+                mChildTransformation = new Transformation();
+            }
+            more = a.getTransformation(drawingTime, mChildTransformation);
+            transformToApply = mChildTransformation;
+
+            concatMatrix = a.willChangeTransformationMatrix();
+
+            if (more) {
+                if (!a.willChangeBounds()) {
+                    if ((flags & (FLAG_OPTIMIZE_INVALIDATE | FLAG_ANIMATION_DONE)) ==
+                            FLAG_OPTIMIZE_INVALIDATE) {
+                        mGroupFlags |= FLAG_INVALIDATE_REQUIRED;
+                    } else if ((flags & FLAG_INVALIDATE_REQUIRED) == 0) {
+                        // The child need to draw an animation, potentially offscreen, so
+                        // make sure we do not cancel invalidate requests
+                        mPrivateFlags |= DRAW_ANIMATION;
+                        invalidate(cl, ct, cr, cb);
+                    }
+                } else {
+                    a.getInvalidateRegion(0, 0, childWidth, childHeight, region, transformToApply);
+
+                    // The child need to draw an animation, potentially offscreen, so
+                    // make sure we do not cancel invalidate requests
+                    mPrivateFlags |= DRAW_ANIMATION;
+                    // Enlarge the invalidate region to account for rounding errors
+                    // in Animation#getInvalidateRegion(); Using 0.5f is unfortunately
+                    // not enough for some types of animations (e.g. scale down.)
+                    final int left = cl + (int) (region.left - 1.0f);
+                    final int top = ct + (int) (region.top - 1.0f);
+                    invalidate(left, top,
+                            left + (int) (region.width() + 1.0f),
+                            top + (int) (region.height() + 1.0f));
+                }
+            }
+        } else if ((flags & FLAG_SUPPORT_STATIC_TRANSFORMATIONS) ==
+                FLAG_SUPPORT_STATIC_TRANSFORMATIONS) {
+            if (mChildTransformation == null) {
+                mChildTransformation = new Transformation();
+            }
+            final boolean hasTransform = getChildStaticTransformation(child, mChildTransformation);
+            if (hasTransform) {
+                final int transformType = mChildTransformation.getTransformationType();
+                transformToApply = transformType != Transformation.TYPE_IDENTITY ?
+                        mChildTransformation : null;
+                concatMatrix = (transformType & Transformation.TYPE_MATRIX) != 0;
+            }
+        }
+
+        if (!concatMatrix && canvas.quickReject(cl, ct, cr, cb, Canvas.EdgeType.BW) &&
+                (child.mPrivateFlags & DRAW_ANIMATION) == 0) {
+            return more;
+        }
+
+        child.computeScroll();
+
+        final int sx = child.mScrollX;
+        final int sy = child.mScrollY;
+
+        Bitmap cache = null;
+        if ((flags & FLAG_CHILDREN_DRAWN_WITH_CACHE) == FLAG_CHILDREN_DRAWN_WITH_CACHE ||
+                (flags & FLAG_ALWAYS_DRAWN_WITH_CACHE) == FLAG_ALWAYS_DRAWN_WITH_CACHE) {
+            cache = child.getDrawingCache();
+        }
+
+        final boolean hasNoCache = cache == null;
+
+        final int restoreTo = canvas.save();
+        if (hasNoCache) {
+            canvas.translate(cl - sx, ct - sy);
+        } else {
+            canvas.translate(cl, ct);
+        }
+
+        float alpha = 1.0f;
+
+        if (transformToApply != null) {
+            if (concatMatrix) {
+                int transX = 0;
+                int transY = 0;
+                if (hasNoCache) {
+                    transX = -sx;
+                    transY = -sy;
+                }
+                // Undo the scroll translation, apply the transformation matrix,
+                // then redo the scroll translate to get the correct result.
+                canvas.translate(-transX, -transY);
+                canvas.concat(transformToApply.getMatrix());
+                canvas.translate(transX, transY);
+                mGroupFlags |= FLAG_CLEAR_TRANSFORMATION;
+            }
+
+            alpha = transformToApply.getAlpha();
+            if (alpha < 1.0f) {
+                mGroupFlags |= FLAG_CLEAR_TRANSFORMATION;
+            }
+
+            if (alpha < 1.0f && hasNoCache) {
+                final int multipliedAlpha = (int) (255 * alpha);
+                if (!child.onSetAlpha(multipliedAlpha)) {
+                    canvas.saveLayerAlpha(sx, sy, sx + cr - cl, sy + cb - ct, multipliedAlpha,
+                            Canvas.HAS_ALPHA_LAYER_SAVE_FLAG | Canvas.CLIP_TO_LAYER_SAVE_FLAG);
+                } else {
+                    child.mPrivateFlags |= ALPHA_SET;
+                }
+            }
+        } else if ((child.mPrivateFlags & ALPHA_SET) == ALPHA_SET) {
+            child.onSetAlpha(255);
+        }
+
+        if ((flags & FLAG_CLIP_CHILDREN) == FLAG_CLIP_CHILDREN) {
+            if (hasNoCache) {
+                canvas.clipRect(sx, sy, sx + childWidth, sy + childHeight);
+            } else {
+                canvas.clipRect(0, 0, childWidth, childHeight);
+            }
+        }
+
+        // Clear the flag as early as possible to allow draw() implementations
+        // to call invalidate() successfully when doing animations
+        child.mPrivateFlags |= DRAWN;
+
+        if (hasNoCache) {
+            // Fast path for layouts with no backgrounds
+            if ((child.mPrivateFlags & SKIP_DRAW) == SKIP_DRAW) {
+                if (ViewDebug.TRACE_HIERARCHY) {
+                    ViewDebug.trace(this, ViewDebug.HierarchyTraceType.DRAW);
+                }
+                child.dispatchDraw(canvas);
+            } else {
+                child.draw(canvas);
+            }
+        } else {
+            final Paint cachePaint = mCachePaint;
+            if (alpha < 1.0f) {
+                cachePaint.setAlpha((int) (alpha * 255));
+                mGroupFlags |= FLAG_ALPHA_LOWER_THAN_ONE;
+            } else if  ((flags & FLAG_ALPHA_LOWER_THAN_ONE) == FLAG_ALPHA_LOWER_THAN_ONE) {
+                cachePaint.setAlpha(255);
+                mGroupFlags &= ~FLAG_ALPHA_LOWER_THAN_ONE;
+            }
+            if (ViewRoot.PROFILE_DRAWING) {
+                EventLog.writeEvent(60003, hashCode());
+            }
+            canvas.drawBitmap(cache, 0.0f, 0.0f, cachePaint);
+        }
+
+        canvas.restoreToCount(restoreTo);
+
+        if (a != null && !more) {
+            child.onSetAlpha(255);
+            finishAnimatingView(child, a);
+        }
+
+        return more;
+    }
+
+    /**
+     * By default, children are clipped to their bounds before drawing. This
+     * allows view groups to override this behavior for animations, etc.
+     *
+     * @param clipChildren true to clip children to their bounds,
+     *        false otherwise
+     * @attr ref android.R.styleable#ViewGroup_clipChildren
+     */
+    public void setClipChildren(boolean clipChildren) {
+        setBooleanFlag(FLAG_CLIP_CHILDREN, clipChildren);
+    }
+
+    /**
+     * By default, children are clipped to the padding of the ViewGroup. This
+     * allows view groups to override this behavior
+     *
+     * @param clipToPadding true to clip children to the padding of the
+     *        group, false otherwise
+     * @attr ref android.R.styleable#ViewGroup_clipToPadding
+     */
+    public void setClipToPadding(boolean clipToPadding) {
+        setBooleanFlag(FLAG_CLIP_TO_PADDING, clipToPadding);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void dispatchSetSelected(boolean selected) {
+        final View[] children = mChildren;
+        final int count = mChildrenCount;
+        for (int i = 0; i < count; i++) {
+            children[i].setSelected(selected);
+        }
+    }
+    
+    @Override
+    protected void dispatchSetPressed(boolean pressed) {
+        final View[] children = mChildren;
+        final int count = mChildrenCount;
+        for (int i = 0; i < count; i++) {
+            children[i].setPressed(pressed);
+        }
+    }
+
+    /**
+     * When this property is set to true, this ViewGroup supports static transformations on
+     * children; this causes
+     * {@link #getChildStaticTransformation(View, android.view.animation.Transformation)} to be
+     * invoked when a child is drawn.
+     *
+     * Any subclass overriding
+     * {@link #getChildStaticTransformation(View, android.view.animation.Transformation)} should
+     * set this property to true.
+     *
+     * @param enabled True to enable static transformations on children, false otherwise.
+     *
+     * @see #FLAG_SUPPORT_STATIC_TRANSFORMATIONS
+     */
+    protected void setStaticTransformationsEnabled(boolean enabled) {
+        setBooleanFlag(FLAG_SUPPORT_STATIC_TRANSFORMATIONS, enabled);
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @see #setStaticTransformationsEnabled(boolean) 
+     */
+    protected boolean getChildStaticTransformation(View child, Transformation t) {
+        return false;
+    }
+
+    /**
+     * {@hide}
+     */
+    @Override
+    protected View findViewTraversal(int id) {
+        if (id == mID) {
+            return this;
+        }
+
+        final View[] where = mChildren;
+        final int len = mChildrenCount;
+
+        for (int i = 0; i < len; i++) {
+            View v = where[i];
+
+            if ((v.mPrivateFlags & IS_ROOT_NAMESPACE) == 0) {
+                v = v.findViewById(id);
+
+                if (v != null) {
+                    return v;
+                }
+            }
+        }
+
+        return null;
+    }
+
+    /**
+     * {@hide}
+     */
+    @Override
+    protected View findViewWithTagTraversal(Object tag) {
+        if (tag != null && tag.equals(mTag)) {
+            return this;
+        }
+
+        final View[] where = mChildren;
+        final int len = mChildrenCount;
+
+        for (int i = 0; i < len; i++) {
+            View v = where[i];
+
+            if ((v.mPrivateFlags & IS_ROOT_NAMESPACE) == 0) {
+                v = v.findViewWithTag(tag);
+
+                if (v != null) {
+                    return v;
+                }
+            }
+        }
+
+        return null;
+    }
+
+    /**
+     * Adds a child view. If no layout parameters are already set on the child, the
+     * default parameters for this ViewGroup are set on the child.
+     *
+     * @param child the child view to add
+     *
+     * @see #generateDefaultLayoutParams()
+     */
+    public void addView(View child) {
+        addView(child, -1);
+    }
+
+    /**
+     * Adds a child view. If no layout parameters are already set on the child, the
+     * default parameters for this ViewGroup are set on the child.
+     *
+     * @param child the child view to add
+     * @param index the position at which to add the child
+     *
+     * @see #generateDefaultLayoutParams()
+     */
+    public void addView(View child, int index) {
+        LayoutParams params = child.getLayoutParams();
+        if (params == null) {
+            params = generateDefaultLayoutParams();
+            if (params == null) {
+                throw new IllegalArgumentException("generateDefaultLayoutParams() cannot return null");
+            }
+        }
+        addView(child, index, params);
+    }
+
+    /**
+     * Adds a child view with this ViewGroup's default layout parameters and the
+     * specified width and height.
+     *
+     * @param child the child view to add
+     */
+    public void addView(View child, int width, int height) {
+        final LayoutParams params = generateDefaultLayoutParams();
+        params.width = width;
+        params.height = height;
+        addView(child, -1, params);
+    }
+
+    /**
+     * Adds a child view with the specified layout parameters.
+     *
+     * @param child the child view to add
+     * @param params the layout parameters to set on the child
+     */
+    public void addView(View child, LayoutParams params) {
+        addView(child, -1, params);
+    }
+
+    /**
+     * Adds a child view with the specified layout parameters.
+     *
+     * @param child the child view to add
+     * @param index the position at which to add the child
+     * @param params the layout parameters to set on the child
+     */
+    public void addView(View child, int index, LayoutParams params) {
+        if (DBG) {
+            System.out.println(this + " addView");
+        }
+
+        // addViewInner() will call child.requestLayout() when setting the new LayoutParams
+        // therefore, we call requestLayout() on ourselves before, so that the child's request
+        // will be blocked at our level
+        requestLayout();
+        invalidate();
+        addViewInner(child, index, params, false);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void updateViewLayout(View view, ViewGroup.LayoutParams params) {
+        if (!checkLayoutParams(params)) {
+            throw new IllegalArgumentException("Invalid LayoutParams supplied to " + this);
+        }
+        if (view.mParent != this) {
+            throw new IllegalArgumentException("Given view not a child of " + this);
+        }
+        view.setLayoutParams(params);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
+        return  p != null;
+    }
+
+    /**
+     * Interface definition for a callback to be invoked when the hierarchy
+     * within this view changed. The hierarchy changes whenever a child is added
+     * to or removed from this view.
+     */
+    public interface OnHierarchyChangeListener {
+        /**
+         * Called when a new child is added to a parent view.
+         *
+         * @param parent the view in which a child was added
+         * @param child the new child view added in the hierarchy
+         */
+        void onChildViewAdded(View parent, View child);
+
+        /**
+         * Called when a child is removed from a parent view.
+         *
+         * @param parent the view from which the child was removed
+         * @param child the child removed from the hierarchy
+         */
+        void onChildViewRemoved(View parent, View child);
+    }
+
+    /**
+     * Register a callback to be invoked when a child is added to or removed
+     * from this view.
+     *
+     * @param listener the callback to invoke on hierarchy change
+     */
+    public void setOnHierarchyChangeListener(OnHierarchyChangeListener listener) {
+        mOnHierarchyChangeListener = listener;
+    }
+
+    /**
+     * Adds a view during layout. This is useful if in your onLayout() method,
+     * you need to add more views (as does the list view for example).
+     *
+     * If index is negative, it means put it at the end of the list.
+     *
+     * @param child the view to add to the group
+     * @param index the index at which the child must be added
+     * @param params the layout parameters to associate with the child
+     * @return true if the child was added, false otherwise
+     */
+    protected boolean addViewInLayout(View child, int index, LayoutParams params) {
+        return addViewInLayout(child, index, params, false);
+    }
+
+    /**
+     * Adds a view during layout. This is useful if in your onLayout() method,
+     * you need to add more views (as does the list view for example).
+     *
+     * If index is negative, it means put it at the end of the list.
+     *
+     * @param child the view to add to the group
+     * @param index the index at which the child must be added
+     * @param params the layout parameters to associate with the child
+     * @param preventRequestLayout if true, calling this method will not trigger a
+     *        layout request on child
+     * @return true if the child was added, false otherwise
+     */
+    protected boolean addViewInLayout(View child, int index, LayoutParams params,
+            boolean preventRequestLayout) {
+        child.mParent = null;
+        addViewInner(child, index, params, preventRequestLayout);
+        child.mPrivateFlags |= DRAWN;
+        return true;
+    }
+
+    /**
+     * Prevents the specified child to be laid out during the next layout pass.
+     *
+     * @param child the child on which to perform the cleanup
+     */
+    protected void cleanupLayoutState(View child) {
+        child.mPrivateFlags &= ~View.FORCE_LAYOUT;
+    }
+
+    private void addViewInner(View child, int index, LayoutParams params,
+            boolean preventRequestLayout) {
+
+        if (child.getParent() != null) {
+            throw new IllegalStateException("The specified child already has a parent. " +
+                    "You must call removeView() on the child's parent first.");
+        }
+
+        if (!checkLayoutParams(params)) {
+            params = generateLayoutParams(params);
+        }
+
+        if (preventRequestLayout) {
+            child.mLayoutParams = params;
+        } else {
+            child.setLayoutParams(params);
+        }
+
+        if (index < 0) {
+            index = mChildrenCount;
+        }
+
+        addInArray(child, index);
+
+        // tell our children
+        if (preventRequestLayout) {
+            child.assignParent(this);
+        } else {
+            child.mParent = this;
+        }
+
+        if (child.hasFocus()) {
+            requestChildFocus(child, child.findFocus());
+        }
+        
+        AttachInfo ai = mAttachInfo;
+        if (ai != null) {
+            boolean lastKeepOn = ai.mKeepScreenOn; 
+            ai.mKeepScreenOn = false;
+            child.dispatchAttachedToWindow(mAttachInfo, (mViewFlags&VISIBILITY_MASK));
+            if (ai.mKeepScreenOn) {
+                needGlobalAttributesUpdate(true);
+            }
+            ai.mKeepScreenOn = lastKeepOn;
+        }
+
+        if (mOnHierarchyChangeListener != null) {
+            mOnHierarchyChangeListener.onChildViewAdded(this, child);
+        }
+
+        if ((child.mViewFlags & DUPLICATE_PARENT_STATE) == DUPLICATE_PARENT_STATE) {
+            mGroupFlags |= FLAG_NOTIFY_CHILDREN_ON_DRAWABLE_STATE_CHANGE;
+        }
+    }
+
+    private void addInArray(View child, int index) {
+        View[] children = mChildren;
+        final int count = mChildrenCount;
+        final int size = children.length;
+        if (index == count) {
+            if (size == count) {
+                mChildren = new View[size + ARRAY_CAPACITY_INCREMENT];
+                System.arraycopy(children, 0, mChildren, 0, size);
+                children = mChildren;
+            }
+            children[mChildrenCount++] = child;
+        } else if (index < count) {
+            if (size == count) {
+                mChildren = new View[size + ARRAY_CAPACITY_INCREMENT];
+                System.arraycopy(children, 0, mChildren, 0, index);
+                System.arraycopy(children, index, mChildren, index + 1, count - index);
+                children = mChildren;
+            } else {
+                System.arraycopy(children, index, children, index + 1, count - index);
+            }
+            children[index] = child;
+            mChildrenCount++;
+        } else {
+            throw new IndexOutOfBoundsException("index=" + index + " count=" + count);
+        }
+    }
+
+    // This method also sets the child's mParent to null
+    private void removeFromArray(int index) {
+        final View[] children = mChildren;
+        children[index].mParent = null;
+        final int count = mChildrenCount;
+        if (index == count - 1) {
+            children[--mChildrenCount] = null;
+        } else if (index >= 0 && index < count) {
+            System.arraycopy(children, index + 1, children, index, count - index - 1);
+            children[--mChildrenCount] = null;
+        } else {
+            throw new IndexOutOfBoundsException();
+        }
+    }
+
+    // This method also sets the children's mParent to null
+    private void removeFromArray(int start, int count) {
+        final View[] children = mChildren;
+        final int childrenCount = mChildrenCount;
+
+        start = Math.max(0, start);
+        final int end = Math.min(childrenCount, start + count);
+
+        if (start == end) {
+            return;
+        }
+
+        if (end == childrenCount) {
+            for (int i = start; i < end; i++) {
+                children[i].mParent = null;
+                children[i] = null;
+            }
+        } else {
+            for (int i = start; i < end; i++) {
+                children[i].mParent = null;
+            }
+
+            // Since we're looping above, we might as well do the copy, but is arraycopy()
+            // faster than the extra 2 bounds checks we would do in the loop?
+            System.arraycopy(children, end, children, start, childrenCount - end);
+
+            for (int i = childrenCount - (end - start); i < childrenCount; i++) {
+                children[i] = null;
+            }
+        }
+
+        mChildrenCount -= (end - start);
+    }
+
+    private void bindLayoutAnimation(View child) {
+        Animation a = mLayoutAnimationController.getAnimationForView(child);
+        child.setAnimation(a);
+    }
+
+    /**
+     * Subclasses should override this method to set layout animation
+     * parameters on the supplied child.
+     *
+     * @param child the child to associate with animation parameters
+     * @param params the child's layout parameters which hold the animation
+     *        parameters
+     * @param index the index of the child in the view group
+     * @param count the number of children in the view group
+     */
+    protected void attachLayoutAnimationParameters(View child,
+            LayoutParams params, int index, int count) {
+        LayoutAnimationController.AnimationParameters animationParams =
+                    params.layoutAnimationParameters;
+        if (animationParams == null) {
+            animationParams = new LayoutAnimationController.AnimationParameters();
+            params.layoutAnimationParameters = animationParams;
+        }
+
+        animationParams.count = count;
+        animationParams.index = index;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void removeView(View view) {
+        removeViewInternal(view);
+        requestLayout();
+        invalidate();
+    }
+
+    /**
+     * Removes a view during layout. This is useful if in your onLayout() method,
+     * you need to remove more views.
+     *
+     * @param view the view to remove from the group
+     */
+    public void removeViewInLayout(View view) {
+        removeViewInternal(view);
+    }
+
+    /**
+     * Removes a range of views during layout. This is useful if in your onLayout() method,
+     * you need to remove more views.
+     *
+     * @param start the index of the first view to remove from the group
+     * @param count the number of views to remove from the group
+     */
+    public void removeViewsInLayout(int start, int count) {
+        removeViewsInternal(start, count);
+    }
+
+    /**
+     * Removes the view at the specified position in the group.
+     *
+     * @param index the position in the group of the view to remove
+     */
+    public void removeViewAt(int index) {
+        removeViewInternal(index, getChildAt(index));
+        requestLayout();
+        invalidate();
+    }
+
+    /**
+     * Removes the specified range of views from the group.
+     *
+     * @param start the first position in the group of the range of views to remove
+     * @param count the number of views to remove
+     */
+    public void removeViews(int start, int count) {
+        removeViewsInternal(start, count);
+        requestLayout();
+        invalidate();
+    }
+
+    private void removeViewInternal(View view) {
+        final int index = indexOfChild(view);
+        if (index >= 0) {
+            removeViewInternal(index, view);
+        }
+    }
+
+    private void removeViewInternal(int index, View view) {
+        boolean clearChildFocus = false;
+        if (view == mFocused) {
+            view.clearFocusForRemoval();
+            clearChildFocus = true;
+        }
+
+        if (view.getAnimation() != null) {
+            addDisappearingView(view);
+        } else if (view.mAttachInfo != null) {
+           view.dispatchDetachedFromWindow();
+        }
+
+        if (mOnHierarchyChangeListener != null) {
+            mOnHierarchyChangeListener.onChildViewRemoved(this, view);
+        }
+
+        needGlobalAttributesUpdate(false);
+        
+        removeFromArray(index);
+
+        if (clearChildFocus) {
+            clearChildFocus(view);
+        }
+    }
+
+    private void removeViewsInternal(int start, int count) {
+        final OnHierarchyChangeListener onHierarchyChangeListener = mOnHierarchyChangeListener;
+        final boolean notifyListener = onHierarchyChangeListener != null;
+        final View focused = mFocused;
+        final boolean detach = mAttachInfo != null;
+        View clearChildFocus = null;
+
+        final View[] children = mChildren;
+        final int end = start + count;
+
+        for (int i = start; i < end; i++) {
+            final View view = children[i];
+
+            if (view == focused) {
+                view.clearFocusForRemoval();
+                clearChildFocus = view;
+            }
+
+            if (view.getAnimation() != null) {
+                addDisappearingView(view);
+            } else if (detach) {
+               view.dispatchDetachedFromWindow();
+            }
+
+            needGlobalAttributesUpdate(false);
+            
+            if (notifyListener) {
+                onHierarchyChangeListener.onChildViewRemoved(this, view);
+            }
+        }
+
+        removeFromArray(start, count);
+
+        if (clearChildFocus != null) {
+            clearChildFocus(clearChildFocus);
+        }
+    }
+
+    /**
+     * Call this method to remove all child views from the
+     * ViewGroup.
+     */
+    public void removeAllViews() {
+        removeAllViewsInLayout();
+        requestLayout();
+        invalidate();
+    }
+
+    /**
+     * Called by a ViewGroup subclass to remove child views from itself,
+     * when it must first know its size on screen before it can calculate how many
+     * child views it will render. An example is a Gallery or a ListView, which
+     * may "have" 50 children, but actually only render the number of children
+     * that can currently fit inside the object on screen. Do not call
+     * this method unless you are extending ViewGroup and understand the
+     * view measuring and layout pipeline.
+     */
+    public void removeAllViewsInLayout() {
+        final int count = mChildrenCount;
+        if (count <= 0) {
+            return;
+        }
+
+        final View[] children = mChildren;
+        mChildrenCount = 0;
+
+        final OnHierarchyChangeListener listener = mOnHierarchyChangeListener;
+        final boolean notify = listener != null;
+        final View focused = mFocused;
+        final boolean detach = mAttachInfo != null;
+        View clearChildFocus = null;
+
+        needGlobalAttributesUpdate(false);
+        
+        for (int i = count - 1; i >= 0; i--) {
+            final View view = children[i];
+
+            if (view == focused) {
+                view.clearFocusForRemoval();
+                clearChildFocus = view;
+            }
+
+            if (view.getAnimation() != null) {
+                addDisappearingView(view);
+            } else if (detach) {
+               view.dispatchDetachedFromWindow();
+            }
+
+            if (notify) {
+                listener.onChildViewRemoved(this, view);
+            }
+
+            view.mParent = null;
+            children[i] = null;
+        }
+
+        if (clearChildFocus != null) {
+            clearChildFocus(clearChildFocus);
+        }
+    }
+
+    /**
+     * Finishes the removal of a detached view. This method will dispatch the detached from
+     * window event and notify the hierarchy change listener.
+     *
+     * @param child the child to be definitely removed from the view hierarchy
+     * @param animate if true and the view has an animation, the view is placed in the
+     *                disappearing views list, otherwise, it is detached from the window
+     *
+     * @see #attachViewToParent(View, int, android.view.ViewGroup.LayoutParams)
+     * @see #detachAllViewsFromParent()
+     * @see #detachViewFromParent(View)
+     * @see #detachViewFromParent(int)
+     */
+    protected void removeDetachedView(View child, boolean animate) {
+        if (child == mFocused) {
+            child.clearFocus();
+        }
+        
+        if (animate && child.getAnimation() != null) {
+            addDisappearingView(child);
+        } else if (child.mAttachInfo != null) {
+            child.dispatchDetachedFromWindow();
+        }
+
+        if (mOnHierarchyChangeListener != null) {
+            mOnHierarchyChangeListener.onChildViewRemoved(this, child);
+        }
+    }
+
+    /**
+     * Attaches a view to this view group. Attaching a view assigns this group as the parent,
+     * sets the layout parameters and puts the view in the list of children so it can be retrieved
+     * by calling {@link #getChildAt(int)}.
+     *
+     * This method should be called only for view which were detached from their parent.
+     *
+     * @param child the child to attach
+     * @param index the index at which the child should be attached
+     * @param params the layout parameters of the child
+     *
+     * @see #removeDetachedView(View, boolean)
+     * @see #detachAllViewsFromParent()
+     * @see #detachViewFromParent(View)
+     * @see #detachViewFromParent(int)
+     */
+    protected void attachViewToParent(View child, int index, LayoutParams params) {
+        child.mLayoutParams = params;
+
+        if (index < 0) {
+            index = mChildrenCount;
+        }
+
+        addInArray(child, index);
+
+        child.mParent = this;
+        child.mPrivateFlags |= DRAWN;
+
+        if (child.hasFocus()) {
+            requestChildFocus(child, child.findFocus());
+        }
+    }
+
+    /**
+     * Detaches a view from its parent. Detaching a view should be temporary and followed
+     * either by a call to {@link #attachViewToParent(View, int, android.view.ViewGroup.LayoutParams)}
+     * or a call to {@link #removeDetachedView(View, boolean)}. When a view is detached,
+     * its parent is null and cannot be retrieved by a call to {@link #getChildAt(int)}.
+     *
+     * @param child the child to detach
+     *
+     * @see #detachViewFromParent(int)
+     * @see #detachViewsFromParent(int, int)
+     * @see #detachAllViewsFromParent()
+     * @see #attachViewToParent(View, int, android.view.ViewGroup.LayoutParams)
+     * @see #removeDetachedView(View, boolean)
+     */
+    protected void detachViewFromParent(View child) {
+        removeFromArray(indexOfChild(child));
+    }
+
+    /**
+     * Detaches a view from its parent. Detaching a view should be temporary and followed
+     * either by a call to {@link #attachViewToParent(View, int, android.view.ViewGroup.LayoutParams)}
+     * or a call to {@link #removeDetachedView(View, boolean)}. When a view is detached,
+     * its parent is null and cannot be retrieved by a call to {@link #getChildAt(int)}.
+     *
+     * @param index the index of the child to detach
+     *
+     * @see #detachViewFromParent(View)
+     * @see #detachAllViewsFromParent()
+     * @see #detachViewsFromParent(int, int)
+     * @see #attachViewToParent(View, int, android.view.ViewGroup.LayoutParams)
+     * @see #removeDetachedView(View, boolean)
+     */
+    protected void detachViewFromParent(int index) {
+        removeFromArray(index);
+    }
+
+    /**
+     * Detaches a range of view from their parent. Detaching a view should be temporary and followed
+     * either by a call to {@link #attachViewToParent(View, int, android.view.ViewGroup.LayoutParams)}
+     * or a call to {@link #removeDetachedView(View, boolean)}. When a view is detached, its
+     * parent is null and cannot be retrieved by a call to {@link #getChildAt(int)}.
+     *
+     * @param start the first index of the childrend range to detach
+     * @param count the number of children to detach
+     *
+     * @see #detachViewFromParent(View)
+     * @see #detachViewFromParent(int)
+     * @see #detachAllViewsFromParent()
+     * @see #attachViewToParent(View, int, android.view.ViewGroup.LayoutParams)
+     * @see #removeDetachedView(View, boolean)
+     */
+    protected void detachViewsFromParent(int start, int count) {
+        removeFromArray(start, count);
+    }
+
+    /**
+     * Detaches all views from the parent. Detaching a view should be temporary and followed
+     * either by a call to {@link #attachViewToParent(View, int, android.view.ViewGroup.LayoutParams)}
+     * or a call to {@link #removeDetachedView(View, boolean)}. When a view is detached,
+     * its parent is null and cannot be retrieved by a call to {@link #getChildAt(int)}.
+     *
+     * @see #detachViewFromParent(View)
+     * @see #detachViewFromParent(int)
+     * @see #detachViewsFromParent(int, int)
+     * @see #attachViewToParent(View, int, android.view.ViewGroup.LayoutParams)
+     * @see #removeDetachedView(View, boolean)
+     */
+    protected void detachAllViewsFromParent() {
+        final int count = mChildrenCount;
+        if (count <= 0) {
+            return;
+        }
+
+        final View[] children = mChildren;
+        mChildrenCount = 0;
+
+        for (int i = count - 1; i >= 0; i--) {
+            children[i].mParent = null;
+            children[i] = null;
+        }
+    }
+
+    /**
+     * Don't call or override this method. It is used for the implementation of
+     * the view hierarchy.
+     */
+    public final void invalidateChild(View child, final Rect dirty) {
+        if (ViewDebug.TRACE_HIERARCHY) {
+            ViewDebug.trace(this, ViewDebug.HierarchyTraceType.INVALIDATE_CHILD);
+        }
+
+        ViewParent parent = this;
+
+        final AttachInfo attachInfo = mAttachInfo;
+        if (attachInfo != null) {
+            final int[] location = attachInfo.mInvalidateChildLocation;
+            location[CHILD_LEFT_INDEX] = child.mLeft;
+            location[CHILD_TOP_INDEX] = child.mTop;
+
+            // If the child is drawing an animation, we want to copy this flag onto
+            // ourselves and the parent to make sure the invalidate request goes
+            // through
+            final boolean drawAnimation = (child.mPrivateFlags & DRAW_ANIMATION) == DRAW_ANIMATION;
+    
+            do {
+                if (drawAnimation && parent instanceof View) {
+                    ((View) parent).mPrivateFlags |= DRAW_ANIMATION;
+                }
+                parent = parent.invalidateChildInParent(location, dirty);
+            } while (parent != null);
+        }
+    }
+
+    /**
+     * Don't call or override this method. It is used for the implementation of
+     * the view hierarchy.
+     *
+     * This implementation returns null if this ViewGroup does not have a parent,
+     * if this ViewGroup is already fully invalidated or if the dirty rectangle
+     * does not intersect with this ViewGroup's bounds.
+     */
+    public ViewParent invalidateChildInParent(final int[] location, final Rect dirty) {
+        if (ViewDebug.TRACE_HIERARCHY) {
+            ViewDebug.trace(this, ViewDebug.HierarchyTraceType.INVALIDATE_CHILD_IN_PARENT);
+        }
+
+        if ((mPrivateFlags & DRAWN) == DRAWN) {
+            if ((mGroupFlags & (FLAG_OPTIMIZE_INVALIDATE | FLAG_ANIMATION_DONE)) !=
+                        FLAG_OPTIMIZE_INVALIDATE) {
+                dirty.offset(location[CHILD_LEFT_INDEX] - mScrollX,
+                        location[CHILD_TOP_INDEX] - mScrollY);
+
+                final int left = mLeft;
+                final int top = mTop;
+
+                if (dirty.intersect(0, 0, mRight - left, mBottom - top) ||
+                        (mPrivateFlags & DRAW_ANIMATION) == DRAW_ANIMATION) {
+                    mPrivateFlags &= ~DRAWING_CACHE_VALID;
+
+                    location[CHILD_LEFT_INDEX] = left;
+                    location[CHILD_TOP_INDEX] = top;
+
+                    return mParent;
+                }
+            } else {
+                mPrivateFlags &= ~DRAWN & ~DRAWING_CACHE_VALID;
+
+                location[CHILD_LEFT_INDEX] = mLeft;
+                location[CHILD_TOP_INDEX] = mTop;
+
+                dirty.set(0, 0, mRight - location[CHILD_LEFT_INDEX],
+                        mBottom - location[CHILD_TOP_INDEX]);
+
+                return mParent;
+            }
+        }
+
+        return null;
+    }
+
+    /**
+     * Offset a rectangle that is in a descendant's coordinate
+     * space into our coordinate space.
+     * @param descendant A descendant of this view
+     * @param rect A rectangle defined in descendant's coordinate space.
+     */
+    public final void offsetDescendantRectToMyCoords(View descendant, Rect rect) {
+        offsetRectBetweenParentAndChild(descendant, rect, true, false);
+    }
+
+    /**
+     * Offset a rectangle that is in our coordinate space into an ancestor's
+     * coordinate space.
+     * @param descendant A descendant of this view
+     * @param rect A rectangle defined in descendant's coordinate space.
+     */
+    public final void offsetRectIntoDescendantCoords(View descendant, Rect rect) {
+        offsetRectBetweenParentAndChild(descendant, rect, false, false);
+    }
+
+    /**
+     * Helper method that offsets a rect either from parent to descendant or
+     * descendant to parent.
+     */
+    void offsetRectBetweenParentAndChild(View descendant, Rect rect,
+            boolean offsetFromChildToParent, boolean clipToBounds) {
+
+        // already in the same coord system :)
+        if (descendant == this) {
+            return;
+        }
+
+        ViewParent theParent = descendant.mParent;
+
+        // search and offset up to the parent
+        while ((theParent != null)
+                && (theParent instanceof View)
+                && (theParent != this)) {
+
+            if (offsetFromChildToParent) {
+                rect.offset(descendant.mLeft - descendant.mScrollX,
+                        descendant.mTop - descendant.mScrollY);
+                if (clipToBounds) {
+                    View p = (View) theParent;
+                    rect.intersect(0, 0, p.mRight - p.mLeft, p.mBottom - p.mTop);
+                }
+            } else {
+                if (clipToBounds) {
+                    View p = (View) theParent;
+                    rect.intersect(0, 0, p.mRight - p.mLeft, p.mBottom - p.mTop);
+                }
+                rect.offset(descendant.mScrollX - descendant.mLeft,
+                        descendant.mScrollY - descendant.mTop);
+            }
+
+            descendant = (View) theParent;
+            theParent = descendant.mParent;
+        }
+
+        // now that we are up to this view, need to offset one more time
+        // to get into our coordinate space
+        if (theParent == this) {
+            if (offsetFromChildToParent) {
+                rect.offset(descendant.mLeft - descendant.mScrollX,
+                        descendant.mTop - descendant.mScrollY);
+            } else {
+                rect.offset(descendant.mScrollX - descendant.mLeft,
+                        descendant.mScrollY - descendant.mTop);
+            }
+        } else {
+            throw new IllegalArgumentException("parameter must be a descendant of this view");
+        }
+    }
+
+    /**
+     * Offset the vertical location of all children of this view by the specified number of pixels.
+     *
+     * @param offset the number of pixels to offset
+     *
+     * @hide
+     */
+    public void offsetChildrenTopAndBottom(int offset) {
+        final int count = mChildrenCount;
+        final View[] children = mChildren;
+
+        for (int i = 0; i < count; i++) {
+            final View v = children[i];
+            v.mTop += offset;
+            v.mBottom += offset;
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public boolean getChildVisibleRect(View child, Rect r, android.graphics.Point offset) {
+        int dx = child.mLeft - mScrollX;
+        int dy = child.mTop - mScrollY;
+        if (offset != null) {
+            offset.x += dx;
+            offset.y += dy;
+        }
+        r.offset(dx, dy);
+        return r.intersect(0, 0, mRight - mLeft, mBottom - mTop) &&
+               (mParent == null || mParent.getChildVisibleRect(this, r, offset));
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    protected abstract void onLayout(boolean changed,
+            int l, int t, int r, int b);
+
+    /**
+     * Indicates whether the view group has the ability to animate its children
+     * after the first layout.
+     *
+     * @return true if the children can be animated, false otherwise
+     */
+    protected boolean canAnimate() {
+        return mLayoutAnimationController != null;
+    }
+
+    /**
+     * Runs the layout animation. Calling this method triggers a relayout of
+     * this view group.
+     */
+    public void startLayoutAnimation() {
+        if (mLayoutAnimationController != null) {
+            mGroupFlags |= FLAG_RUN_ANIMATION;
+            requestLayout();
+        }
+    }
+
+    /**
+     * Schedules the layout animation to be played after the next layout pass
+     * of this view group. This can be used to restart the layout animation
+     * when the content of the view group changes or when the activity is
+     * paused and resumed.
+     */
+    public void scheduleLayoutAnimation() {
+        mGroupFlags |= FLAG_RUN_ANIMATION;
+    }
+
+    /**
+     * Sets the layout animation controller used to animate the group's
+     * children after the first layout.
+     *
+     * @param controller the animation controller
+     */
+    public void setLayoutAnimation(LayoutAnimationController controller) {
+        mLayoutAnimationController = controller;
+        if (mLayoutAnimationController != null) {
+            mGroupFlags |= FLAG_RUN_ANIMATION;
+        }
+    }
+
+    /**
+     * Returns the layout animation controller used to animate the group's
+     * children.
+     *
+     * @return the current animation controller
+     */
+    public LayoutAnimationController getLayoutAnimation() {
+        return mLayoutAnimationController;
+    }
+
+    /**
+     * Indicates whether the children's drawing cache is used during a layout
+     * animation. By default, the drawing cache is enabled but this will prevent
+     * nested layout animations from working. To nest animations, you must disable
+     * the cache.
+     *
+     * @return true if the animation cache is enabled, false otherwise
+     *
+     * @see #setAnimationCacheEnabled(boolean)
+     * @see View#setDrawingCacheEnabled(boolean)
+     */
+    @ViewDebug.ExportedProperty
+    public boolean isAnimationCacheEnabled() {
+        return (mGroupFlags & FLAG_ANIMATION_CACHE) == FLAG_ANIMATION_CACHE;
+    }
+
+    /**
+     * Enables or disables the children's drawing cache during a layout animation.
+     * By default, the drawing cache is enabled but this will prevent nested
+     * layout animations from working. To nest animations, you must disable the
+     * cache.
+     *
+     * @param enabled true to enable the animation cache, false otherwise
+     *
+     * @see #isAnimationCacheEnabled()
+     * @see View#setDrawingCacheEnabled(boolean)
+     */
+    public void setAnimationCacheEnabled(boolean enabled) {
+        setBooleanFlag(FLAG_ANIMATION_CACHE, enabled);
+    }
+
+    /**
+     * Indicates whether this ViewGroup will always try to draw its children using their
+     * drawing cache. By default this property is enabled.
+     *
+     * @return true if the animation cache is enabled, false otherwise
+     *
+     * @see #setAlwaysDrawnWithCacheEnabled(boolean)
+     * @see #setChildrenDrawnWithCacheEnabled(boolean)
+     * @see View#setDrawingCacheEnabled(boolean)
+     */
+    @ViewDebug.ExportedProperty
+    public boolean isAlwaysDrawnWithCacheEnabled() {
+        return (mGroupFlags & FLAG_ALWAYS_DRAWN_WITH_CACHE) == FLAG_ALWAYS_DRAWN_WITH_CACHE;
+    }
+
+    /**
+     * Indicates whether this ViewGroup will always try to draw its children using their
+     * drawing cache. This property can be set to true when the cache rendering is
+     * slightly different from the children's normal rendering. Renderings can be different,
+     * for instance, when the cache's quality is set to low.
+     *
+     * When this property is disabled, the ViewGroup will use the drawing cache of its
+     * children only when asked to. It's usually the task of subclasses to tell ViewGroup
+     * when to start using the drawing cache and when to stop using it.
+     *
+     * @param always true to always draw with the drawing cache, false otherwise
+     *
+     * @see #isAlwaysDrawnWithCacheEnabled()
+     * @see #setChildrenDrawnWithCacheEnabled(boolean)
+     * @see View#setDrawingCacheEnabled(boolean)
+     * @see View#setDrawingCacheQuality(int)
+     */
+    public void setAlwaysDrawnWithCacheEnabled(boolean always) {
+        setBooleanFlag(FLAG_ALWAYS_DRAWN_WITH_CACHE, always);
+    }
+
+    /**
+     * Indicates whether the ViewGroup is currently drawing its children using
+     * their drawing cache.
+     *
+     * @return true if children should be drawn with their cache, false otherwise
+     *
+     * @see #setAlwaysDrawnWithCacheEnabled(boolean)
+     * @see #setChildrenDrawnWithCacheEnabled(boolean)
+     */
+    @ViewDebug.ExportedProperty
+    protected boolean isChildrenDrawnWithCacheEnabled() {
+        return (mGroupFlags & FLAG_CHILDREN_DRAWN_WITH_CACHE) == FLAG_CHILDREN_DRAWN_WITH_CACHE;
+    }
+
+    /**
+     * Tells the ViewGroup to draw its children using their drawing cache. This property
+     * is ignored when {@link #isAlwaysDrawnWithCacheEnabled()} is true. A child's drawing cache
+     * will be used only if it has been enabled.
+     *
+     * Subclasses should call this method to start and stop using the drawing cache when
+     * they perform performance sensitive operations, like scrolling or animating.
+     *
+     * @param enabled true if children should be drawn with their cache, false otherwise
+     *
+     * @see #setAlwaysDrawnWithCacheEnabled(boolean)
+     * @see #isChildrenDrawnWithCacheEnabled()
+     */
+    protected void setChildrenDrawnWithCacheEnabled(boolean enabled) {
+        setBooleanFlag(FLAG_CHILDREN_DRAWN_WITH_CACHE, enabled);
+    }
+
+    private void setBooleanFlag(int flag, boolean value) {
+        if (value) {
+            mGroupFlags |= flag;
+        } else {
+            mGroupFlags &= ~flag;
+        }
+    }
+
+    /**
+     * Returns an integer indicating what types of drawing caches are kept in memory.
+     *
+     * @see #setPersistentDrawingCache(int)
+     * @see #setAnimationCacheEnabled(boolean)
+     *
+     * @return one or a combination of {@link #PERSISTENT_NO_CACHE},
+     *         {@link #PERSISTENT_ANIMATION_CACHE}, {@link #PERSISTENT_SCROLLING_CACHE}
+     *         and {@link #PERSISTENT_ALL_CACHES}
+     */
+    @ViewDebug.ExportedProperty(mapping = {
+        @ViewDebug.IntToString(from = PERSISTENT_NO_CACHE,        to = "NONE"),
+        @ViewDebug.IntToString(from = PERSISTENT_ALL_CACHES,      to = "ANIMATION"),
+        @ViewDebug.IntToString(from = PERSISTENT_SCROLLING_CACHE, to = "SCROLLING"),
+        @ViewDebug.IntToString(from = PERSISTENT_ALL_CACHES,      to = "ALL")
+    })
+    public int getPersistentDrawingCache() {
+        return mPersistentDrawingCache;
+    }
+
+    /**
+     * Indicates what types of drawing caches should be kept in memory after
+     * they have been created.
+     *
+     * @see #getPersistentDrawingCache()
+     * @see #setAnimationCacheEnabled(boolean)
+     *
+     * @param drawingCacheToKeep one or a combination of {@link #PERSISTENT_NO_CACHE},
+     *        {@link #PERSISTENT_ANIMATION_CACHE}, {@link #PERSISTENT_SCROLLING_CACHE}
+     *        and {@link #PERSISTENT_ALL_CACHES}
+     */
+    public void setPersistentDrawingCache(int drawingCacheToKeep) {
+        mPersistentDrawingCache = drawingCacheToKeep & PERSISTENT_ALL_CACHES;
+    }
+
+    /**
+     * Returns a new set of layout parameters based on the supplied attributes set.
+     *
+     * @param attrs the attributes to build the layout parameters from
+     *
+     * @return an instance of {@link android.view.ViewGroup.LayoutParams} or one
+     *         of its descendants
+     */
+    public LayoutParams generateLayoutParams(AttributeSet attrs) {
+        return new LayoutParams(getContext(), attrs);
+    }
+
+    /**
+     * Returns a safe set of layout parameters based on the supplied layout params.
+     * When a ViewGroup is passed a View whose layout params do not pass the test of
+     * {@link #checkLayoutParams(android.view.ViewGroup.LayoutParams)}, this method
+     * is invoked. This method should return a new set of layout params suitable for
+     * this ViewGroup, possibly by copying the appropriate attributes from the
+     * specified set of layout params.
+     *
+     * @param p The layout parameters to convert into a suitable set of layout parameters
+     *          for this ViewGroup.
+     *
+     * @return an instance of {@link android.view.ViewGroup.LayoutParams} or one
+     *         of its descendants
+     */
+    protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
+        return p;
+    }
+
+    /**
+     * Returns a set of default layout parameters. These parameters are requested
+     * when the View passed to {@link #addView(View)} has no layout parameters
+     * already set. If null is returned, an exception is thrown from addView.
+     *
+     * @return a set of default layout parameters or null
+     */
+    protected LayoutParams generateDefaultLayoutParams() {
+        return new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    protected void debug(int depth) {
+        super.debug(depth);
+        String output;
+
+        if (mFocused != null) {
+            output = debugIndent(depth);
+            output += "mFocused";
+            Log.d(VIEW_LOG_TAG, output);
+        }
+        if (mChildrenCount != 0) {
+            output = debugIndent(depth);
+            output += "{";
+            Log.d(VIEW_LOG_TAG, output);
+        }
+        int count = mChildrenCount;
+        for (int i = 0; i < count; i++) {
+            View child = mChildren[i];
+            child.debug(depth + 1);
+        }
+
+        if (mChildrenCount != 0) {
+            output = debugIndent(depth);
+            output += "}";
+            Log.d(VIEW_LOG_TAG, output);
+        }
+    }
+
+    /**
+     * Returns the position in the group of the specified child view.
+     *
+     * @param child the view for which to get the position
+     * @return a positive integer representing the position of the view in the
+     *         group, or -1 if the view does not exist in the group
+     */
+    public int indexOfChild(View child) {
+        final int count = mChildrenCount;
+        final View[] children = mChildren;
+        for (int i = 0; i < count; i++) {
+            if (children[i] == child) {
+                return i;
+            }
+        }
+        return -1;
+    }
+
+    /**
+     * Returns the number of children in the group.
+     *
+     * @return a positive integer representing the number of children in
+     *         the group
+     */
+    public int getChildCount() {
+        return mChildrenCount;
+    }
+
+    /**
+     * Returns the view at the specified position in the group.
+     *
+     * @param index the position at which to get the view from
+     * @return the view at the specified position or null if the position
+     *         does not exist within the group
+     */
+    public View getChildAt(int index) {
+        try {
+            return mChildren[index];
+        } catch (IndexOutOfBoundsException ex) {
+            return null;
+        }
+    }
+
+    /**
+     * Ask all of the children of this view to measure themselves, taking into
+     * account both the MeasureSpec requirements for this view and its padding.
+     * We skip children that are in the GONE state The heavy lifting is done in
+     * getChildMeasureSpec.
+     *
+     * @param widthMeasureSpec The width requirements for this view
+     * @param heightMeasureSpec The height requirements for this view
+     */
+    protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) {
+        final int size = mChildrenCount;
+        final View[] children = mChildren;
+        for (int i = 0; i < size; ++i) {
+            final View child = children[i];
+            if ((child.mViewFlags & VISIBILITY_MASK) != GONE) {
+                measureChild(child, widthMeasureSpec, heightMeasureSpec);
+            }
+        }
+    }
+
+    /**
+     * Ask one of the children of this view to measure itself, taking into
+     * account both the MeasureSpec requirements for this view and its padding.
+     * The heavy lifting is done in getChildMeasureSpec.
+     *
+     * @param child The child to measure
+     * @param parentWidthMeasureSpec The width requirements for this view
+     * @param parentHeightMeasureSpec The height requirements for this view
+     */
+    protected void measureChild(View child, int parentWidthMeasureSpec,
+            int parentHeightMeasureSpec) {
+        final LayoutParams lp = child.getLayoutParams();
+
+        final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
+                mPaddingLeft + mPaddingRight, lp.width);
+        final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
+                mPaddingTop + mPaddingBottom, lp.height);
+
+        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
+    }
+
+    /**
+     * Ask one of the children of this view to measure itself, taking into
+     * account both the MeasureSpec requirements for this view and its padding
+     * and margins. The child must have MarginLayoutParams The heavy lifting is
+     * done in getChildMeasureSpec.
+     *
+     * @param child The child to measure
+     * @param parentWidthMeasureSpec The width requirements for this view
+     * @param widthUsed Extra space that has been used up by the parent
+     *        horizontally (possibly by other children of the parent)
+     * @param parentHeightMeasureSpec The height requirements for this view
+     * @param heightUsed Extra space that has been used up by the parent
+     *        vertically (possibly by other children of the parent)
+     */
+    protected void measureChildWithMargins(View child,
+            int parentWidthMeasureSpec, int widthUsed,
+            int parentHeightMeasureSpec, int heightUsed) {
+        final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
+
+        final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
+                mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin
+                        + widthUsed, lp.width);
+        final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
+                mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin
+                        + heightUsed, lp.height);
+
+        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
+    }
+
+    /**
+     * Does the hard part of measureChildren: figuring out the MeasureSpec to
+     * pass to a particular child. This method figures out the right MeasureSpec
+     * for one dimension (height or width) of one child view.
+     *
+     * The goal is to combine information from our MeasureSpec with the
+     * LayoutParams of the child to get the best possible results. For example,
+     * if the this view knows its size (because its MeasureSpec has a mode of
+     * EXACTLY), and the child has indicated in its LayoutParams that it wants
+     * to be the same size as the parent, the parent should ask the child to
+     * layout given an exact size.
+     *
+     * @param spec The requirements for this view
+     * @param padding The padding of this view for the current dimension and
+     *        margins, if applicable
+     * @param childDimension How big the child wants to be in the current
+     *        dimension
+     * @return a MeasureSpec integer for the child
+     */
+    public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
+        int specMode = MeasureSpec.getMode(spec);
+        int specSize = MeasureSpec.getSize(spec);
+
+        int size = Math.max(0, specSize - padding);
+
+        int resultSize = 0;
+        int resultMode = 0;
+
+        switch (specMode) {
+        // Parent has imposed an exact size on us
+        case MeasureSpec.EXACTLY:
+            if (childDimension >= 0) {
+                resultSize = childDimension;
+                resultMode = MeasureSpec.EXACTLY;
+            } else if (childDimension == LayoutParams.FILL_PARENT) {
+                // Child wants to be our size. So be it.
+                resultSize = size;
+                resultMode = MeasureSpec.EXACTLY;
+            } else if (childDimension == LayoutParams.WRAP_CONTENT) {
+                // Child wants to determine its own size. It can't be
+                // bigger than us.
+                resultSize = size;
+                resultMode = MeasureSpec.AT_MOST;
+            }
+            break;
+
+        // Parent has imposed a maximum size on us
+        case MeasureSpec.AT_MOST:
+            if (childDimension >= 0) {
+                // Child wants a specific size... so be it
+                resultSize = childDimension;
+                resultMode = MeasureSpec.EXACTLY;
+            } else if (childDimension == LayoutParams.FILL_PARENT) {
+                // Child wants to be our size, but our size is not fixed.
+                // Constrain child to not be bigger than us.
+                resultSize = size;
+                resultMode = MeasureSpec.AT_MOST;
+            } else if (childDimension == LayoutParams.WRAP_CONTENT) {
+                // Child wants to determine its own size. It can't be
+                // bigger than us.
+                resultSize = size;
+                resultMode = MeasureSpec.AT_MOST;
+            }
+            break;
+
+        // Parent asked to see how big we want to be
+        case MeasureSpec.UNSPECIFIED:
+            if (childDimension >= 0) {
+                // Child wants a specific size... let him have it
+                resultSize = childDimension;
+                resultMode = MeasureSpec.EXACTLY;
+            } else if (childDimension == LayoutParams.FILL_PARENT) {
+                // Child wants to be our size... find out how big it should
+                // be
+                resultSize = 0;
+                resultMode = MeasureSpec.UNSPECIFIED;
+            } else if (childDimension == LayoutParams.WRAP_CONTENT) {
+                // Child wants to determine its own size.... find out how
+                // big it should be
+                resultSize = 0;
+                resultMode = MeasureSpec.UNSPECIFIED;
+            }
+            break;
+        }
+        return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
+    }
+
+
+    /**
+     * Removes any pending animations for views that have been removed. Call
+     * this if you don't want animations for exiting views to stack up.
+     */
+    public void clearDisappearingChildren() {
+        if (mDisappearingChildren != null) {
+            mDisappearingChildren.clear();
+        }
+    }
+
+    /**
+     * Add a view which is removed from mChildren but still needs animation
+     *
+     * @param v View to add
+     */
+    private void addDisappearingView(View v) {
+        ArrayList<View> disappearingChildren = mDisappearingChildren;
+
+        if (disappearingChildren == null) {
+            disappearingChildren = mDisappearingChildren = new ArrayList<View>();
+        }
+
+        disappearingChildren.add(v);
+    }
+
+    /**
+     * Cleanup a view when its animation is done. This may mean removing it from
+     * the list of disappearing views.
+     *
+     * @param view The view whose animation has finished
+     * @param animation The animation, cannot be null
+     */
+    private void finishAnimatingView(final View view, Animation animation) {
+        final ArrayList<View> disappearingChildren = mDisappearingChildren;
+        if (disappearingChildren != null) {
+            if (disappearingChildren.contains(view)) {
+                disappearingChildren.remove(view);
+
+                if (view.mAttachInfo != null) {
+                    view.dispatchDetachedFromWindow();
+                }
+
+                view.clearAnimation();
+                mGroupFlags |= FLAG_INVALIDATE_REQUIRED;
+            }
+        }
+
+        if (animation != null && !animation.getFillAfter()) {
+            view.clearAnimation();
+        }
+
+        if ((view.mPrivateFlags & ANIMATION_STARTED) == ANIMATION_STARTED) {
+            view.onAnimationEnd();
+            // Should be performed by onAnimationEnd() but this avoid an infinite loop,
+            // so we'd rather be safe than sorry
+            view.mPrivateFlags &= ~ANIMATION_STARTED;
+            // Draw one more frame after the animation is done
+            mGroupFlags |= FLAG_INVALIDATE_REQUIRED;
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public boolean gatherTransparentRegion(Region region) {
+        // If no transparent regions requested, we are always opaque.
+        final boolean meOpaque = (mPrivateFlags & View.REQUEST_TRANSPARENT_REGIONS) == 0;
+        if (meOpaque && region == null) {
+            // The caller doesn't care about the region, so stop now.
+            return true;
+        }
+        super.gatherTransparentRegion(region);
+        final View[] children = mChildren;
+        final int count = mChildrenCount;
+        boolean noneOfTheChildrenAreTransparent = true;
+        for (int i = 0; i < count; i++) {
+            final View child = children[i];
+            if ((child.mViewFlags & VISIBILITY_MASK) != GONE || child.getAnimation() != null) {
+                if (!child.gatherTransparentRegion(region)) {
+                    noneOfTheChildrenAreTransparent = false;
+                }
+            }
+        }
+        return meOpaque || noneOfTheChildrenAreTransparent;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void requestTransparentRegion(View child) {
+        if (child != null) {
+            child.mPrivateFlags |= View.REQUEST_TRANSPARENT_REGIONS;
+            if (mParent != null) {
+                mParent.requestTransparentRegion(this);
+            }
+        }
+    }
+    
+
+    @Override
+    protected boolean fitSystemWindows(Rect insets) {
+        boolean done = super.fitSystemWindows(insets);
+        if (!done) {
+            final int count = mChildrenCount;
+            final View[] children = mChildren;
+            for (int i = 0; i < count; i++) {
+                done = children[i].fitSystemWindows(insets);
+                if (done) {
+                    break;
+                }
+            }
+        }
+        return done;
+    }
+
+    /**
+     * Returns the animation listener to which layout animation events are
+     * sent.
+     *
+     * @return an {@link android.view.animation.Animation.AnimationListener}
+     */
+    public Animation.AnimationListener getLayoutAnimationListener() {
+        return mAnimationListener;
+    }
+
+    @Override
+    protected void drawableStateChanged() {
+        super.drawableStateChanged();
+
+        if ((mGroupFlags & FLAG_NOTIFY_CHILDREN_ON_DRAWABLE_STATE_CHANGE) != 0) {
+            if ((mGroupFlags & FLAG_ADD_STATES_FROM_CHILDREN) != 0) {
+                throw new IllegalStateException("addStateFromChildren cannot be enabled if a"
+                        + " child has duplicateParentState set to true");
+            }
+
+            final View[] children = mChildren;
+            final int count = mChildrenCount;
+
+            for (int i = 0; i < count; i++) {
+                final View child = children[i];
+                if ((child.mViewFlags & DUPLICATE_PARENT_STATE) != 0) {
+                    child.refreshDrawableState();
+                }
+            }
+        }
+    }
+
+    @Override
+    protected int[] onCreateDrawableState(int extraSpace) {
+        if ((mGroupFlags & FLAG_ADD_STATES_FROM_CHILDREN) == 0) {
+            return super.onCreateDrawableState(extraSpace);
+        }
+
+        int need = 0;
+        int n = getChildCount();
+        for (int i = 0; i < n; i++) {
+            int[] childState = getChildAt(i).getDrawableState();
+
+            if (childState != null) {
+                need += childState.length;
+            }
+        }
+
+        int[] state = super.onCreateDrawableState(extraSpace + need);
+
+        for (int i = 0; i < n; i++) {
+            int[] childState = getChildAt(i).getDrawableState();
+
+            if (childState != null) {
+                state = mergeDrawableStates(state, childState);
+            }
+        }
+
+        return state;
+    }
+
+    /**
+     * Sets whether this ViewGroup's drawable states also include
+     * its children's drawable states.  This is used, for example, to
+     * make a group appear to be focused when its child EditText or button
+     * is focused.
+     */
+    public void setAddStatesFromChildren(boolean addsStates) {
+        if (addsStates) {
+            mGroupFlags |= FLAG_ADD_STATES_FROM_CHILDREN;
+        } else {
+            mGroupFlags &= ~FLAG_ADD_STATES_FROM_CHILDREN;
+        }
+
+        refreshDrawableState();
+    }
+
+    /**
+     * Returns whether this ViewGroup's drawable states also include
+     * its children's drawable states.  This is used, for example, to
+     * make a group appear to be focused when its child EditText or button
+     * is focused.
+     */
+    public boolean addStatesFromChildren() {
+        return (mGroupFlags & FLAG_ADD_STATES_FROM_CHILDREN) != 0;
+    }
+
+    /**
+     * If {link #addStatesFromChildren} is true, refreshes this group's
+     * drawable state (to include the states from its children).
+     */
+    public void childDrawableStateChanged(View child) {
+        if ((mGroupFlags & FLAG_ADD_STATES_FROM_CHILDREN) != 0) {
+            refreshDrawableState();
+        }
+    }
+
+    /**
+     * Specifies the animation listener to which layout animation events must
+     * be sent. Only
+     * {@link android.view.animation.Animation.AnimationListener#onAnimationStart(Animation)}
+     * and
+     * {@link android.view.animation.Animation.AnimationListener#onAnimationEnd(Animation)}
+     * are invoked.
+     *
+     * @param animationListener the layout animation listener
+     */
+    public void setLayoutAnimationListener(Animation.AnimationListener animationListener) {
+        mAnimationListener = animationListener;
+    }
+
+    /**
+     * LayoutParams are used by views to tell their parents how they want to be
+     * laid out. See
+     * {@link android.R.styleable#ViewGroup_Layout ViewGroup Layout Attributes}
+     * for a list of all child view attributes that this class supports.
+     * 
+     * <p>
+     * The base LayoutParams class just describes how big the view wants to be
+     * for both width and height. For each dimension, it can specify one of:
+     * <ul>
+     * <li> an exact number
+     * <li>FILL_PARENT, which means the view wants to be as big as its parent
+     * (minus padding)
+     * <li> WRAP_CONTENT, which means that the view wants to be just big enough
+     * to enclose its content (plus padding)
+     * </ul>
+     * There are subclasses of LayoutParams for different subclasses of
+     * ViewGroup. For example, AbsoluteLayout has its own subclass of
+     * LayoutParams which adds an X and Y value.
+     *
+     * @attr ref android.R.styleable#ViewGroup_Layout_layout_height
+     * @attr ref android.R.styleable#ViewGroup_Layout_layout_width
+     */
+    public static class LayoutParams {
+        /**
+         * Special value for the height or width requested by a View.
+         * FILL_PARENT means that the view wants to fill the available space
+         * within the parent, taking the parent's padding into account.
+         */
+        public static final int FILL_PARENT = -1;
+
+        /**
+         * Special value for the height or width requested by a View.
+         * WRAP_CONTENT means that the view wants to be just large enough to fit
+         * its own internal content, taking its own padding into account.
+         */
+        public static final int WRAP_CONTENT = -2;
+
+        /**
+         * Information about how wide the view wants to be. Can be an exact
+         * size, or one of the constants FILL_PARENT or WRAP_CONTENT.
+         */
+        @ViewDebug.ExportedProperty(mapping = {
+            @ViewDebug.IntToString(from = FILL_PARENT, to = "FILL_PARENT"),
+            @ViewDebug.IntToString(from = WRAP_CONTENT, to = "WRAP_CONTENT")
+        })
+        public int width;
+
+        /**
+         * Information about how tall the view wants to be. Can be an exact
+         * size, or one of the constants FILL_PARENT or WRAP_CONTENT.
+         */
+        @ViewDebug.ExportedProperty(mapping = {
+            @ViewDebug.IntToString(from = FILL_PARENT, to = "FILL_PARENT"),
+            @ViewDebug.IntToString(from = WRAP_CONTENT, to = "WRAP_CONTENT")
+        })
+        public int height;
+
+        /**
+         * Used to animate layouts.
+         */
+        public LayoutAnimationController.AnimationParameters layoutAnimationParameters;
+
+        /**
+         * Creates a new set of layout parameters. The values are extracted from
+         * the supplied attributes set and context. The XML attributes mapped
+         * to this set of layout parameters are:
+         *
+         * <ul>
+         *   <li><code>layout_width</code>: the width, either an exact value,
+         *   {@link #WRAP_CONTENT} or {@link #FILL_PARENT}</li>
+         *   <li><code>layout_height</code>: the height, either an exact value,
+         *   {@link #WRAP_CONTENT} or {@link #FILL_PARENT}</li>
+         * </ul>
+         *
+         * @param c the application environment
+         * @param attrs the set of attributes from which to extract the layout
+         *              parameters' values
+         */
+        public LayoutParams(Context c, AttributeSet attrs) {
+            TypedArray a = c.obtainStyledAttributes(attrs, R.styleable.ViewGroup_Layout);
+            setBaseAttributes(a,
+                    R.styleable.ViewGroup_Layout_layout_width,
+                    R.styleable.ViewGroup_Layout_layout_height);
+            a.recycle();
+        }
+
+        /**
+         * Creates a new set of layout parameters with the specified width
+         * and height.
+         *
+         * @param width the width, either {@link #FILL_PARENT},
+         *        {@link #WRAP_CONTENT} or a fixed size in pixels
+         * @param height the height, either {@link #FILL_PARENT},
+         *        {@link #WRAP_CONTENT} or a fixed size in pixels
+         */
+        public LayoutParams(int width, int height) {
+            this.width = width;
+            this.height = height;
+        }
+
+        /**
+         * Copy constructor. Clones the width and height values of the source.
+         *
+         * @param source The layout params to copy from.
+         */
+        public LayoutParams(LayoutParams source) {
+            this.width = source.width;
+            this.height = source.height;
+        }
+
+        /**
+         * Used internally by MarginLayoutParams.
+         * @hide
+         */
+        LayoutParams() {
+        }
+
+        /**
+         * Extracts the layout parameters from the supplied attributes.
+         *
+         * @param a the style attributes to extract the parameters from
+         * @param widthAttr the identifier of the width attribute
+         * @param heightAttr the identifier of the height attribute
+         */
+        protected void setBaseAttributes(TypedArray a, int widthAttr, int heightAttr) {
+            width = a.getLayoutDimension(widthAttr, "layout_width");
+            height = a.getLayoutDimension(heightAttr, "layout_height");
+        }
+
+        /**
+         * Returns a String representation of this set of layout parameters.
+         *
+         * @param output the String to prepend to the internal representation
+         * @return a String with the following format: output +
+         *         "ViewGroup.LayoutParams={ width=WIDTH, height=HEIGHT }"
+         * 
+         * @hide
+         */
+        public String debug(String output) {
+            return output + "ViewGroup.LayoutParams={ width="
+                    + sizeToString(width) + ", height=" + sizeToString(height) + " }";
+        }
+
+        /**
+         * Converts the specified size to a readable String.
+         *
+         * @param size the size to convert
+         * @return a String instance representing the supplied size
+         * 
+         * @hide
+         */
+        protected static String sizeToString(int size) {
+            if (size == WRAP_CONTENT) {
+                return "wrap-content";
+            }
+            if (size == FILL_PARENT) {
+                return "fill-parent";
+            }
+            return String.valueOf(size);
+        }
+    }
+
+    /**
+     * Per-child layout information for layouts that support margins.
+     * See
+     * {@link android.R.styleable#ViewGroup_MarginLayout ViewGroup Margin Layout Attributes}
+     * for a list of all child view attributes that this class supports.
+     */
+    public static class MarginLayoutParams extends ViewGroup.LayoutParams {
+        /**
+         * The left margin in pixels of the child.
+         */
+        @ViewDebug.ExportedProperty
+        public int leftMargin;
+
+        /**
+         * The top margin in pixels of the child.
+         */
+        @ViewDebug.ExportedProperty
+        public int topMargin;
+
+        /**
+         * The right margin in pixels of the child.
+         */
+        @ViewDebug.ExportedProperty
+        public int rightMargin;
+
+        /**
+         * The bottom margin in pixels of the child.
+         */
+        @ViewDebug.ExportedProperty
+        public int bottomMargin;
+
+        /**
+         * Creates a new set of layout parameters. The values are extracted from
+         * the supplied attributes set and context.
+         *
+         * @param c the application environment
+         * @param attrs the set of attributes from which to extract the layout
+         *              parameters' values
+         */
+        public MarginLayoutParams(Context c, AttributeSet attrs) {
+            super();
+
+            TypedArray a = c.obtainStyledAttributes(attrs, R.styleable.ViewGroup_MarginLayout);
+            setBaseAttributes(a,
+                    R.styleable.ViewGroup_MarginLayout_layout_width,
+                    R.styleable.ViewGroup_MarginLayout_layout_height);
+
+            int margin = a.getDimensionPixelSize(
+                    com.android.internal.R.styleable.ViewGroup_MarginLayout_layout_margin, -1);
+            if (margin >= 0) {
+                leftMargin = margin;
+                topMargin = margin;
+                rightMargin= margin;
+                bottomMargin = margin;
+            } else {
+                leftMargin = a.getDimensionPixelSize(
+                        R.styleable.ViewGroup_MarginLayout_layout_marginLeft, 0);
+                topMargin = a.getDimensionPixelSize(
+                        R.styleable.ViewGroup_MarginLayout_layout_marginTop, 0);
+                rightMargin = a.getDimensionPixelSize(
+                        R.styleable.ViewGroup_MarginLayout_layout_marginRight, 0);
+                bottomMargin = a.getDimensionPixelSize(
+                        R.styleable.ViewGroup_MarginLayout_layout_marginBottom, 0);
+            }
+
+            a.recycle();
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        public MarginLayoutParams(int width, int height) {
+            super(width, height);
+        }
+
+        /**
+         * Copy constructor. Clones the width, height and margin values of the source.
+         *
+         * @param source The layout params to copy from.
+         */
+        public MarginLayoutParams(MarginLayoutParams source) {
+            this.width = source.width;
+            this.height = source.height;
+
+            this.leftMargin = source.leftMargin;
+            this.topMargin = source.topMargin;
+            this.rightMargin = source.rightMargin;
+            this.bottomMargin = source.bottomMargin;
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        public MarginLayoutParams(LayoutParams source) {
+            super(source);
+        }
+
+        /**
+         * Sets the margins, in pixels.
+         *
+         * @param left the left margin size
+         * @param top the top margin size
+         * @param right the right margin size
+         * @param bottom the bottom margin size
+         *
+         * @attr ref android.R.styleable#ViewGroup_MarginLayout_layout_marginLeft
+         * @attr ref android.R.styleable#ViewGroup_MarginLayout_layout_marginTop
+         * @attr ref android.R.styleable#ViewGroup_MarginLayout_layout_marginRight
+         * @attr ref android.R.styleable#ViewGroup_MarginLayout_layout_marginBottom
+         */
+        public void setMargins(int left, int top, int right, int bottom) {
+            leftMargin = left;
+            topMargin = top;
+            rightMargin = right;
+            bottomMargin = bottom;
+        }
+    }
+}
diff --git a/core/java/android/view/ViewManager.java b/core/java/android/view/ViewManager.java
new file mode 100644
index 0000000..7f318c1
--- /dev/null
+++ b/core/java/android/view/ViewManager.java
@@ -0,0 +1,27 @@
+/*
+ * 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 android.view;
+
+/** Interface to let you add and remove child views to an Activity. To get an instance
+  * of this class, call {@link android.content.Context#getSystemService(java.lang.String) Context.getSystemService()}.
+  */
+public interface ViewManager
+{
+    public void addView(View view, ViewGroup.LayoutParams params);
+    public void updateViewLayout(View view, ViewGroup.LayoutParams params);
+    public void removeView(View view);
+}
diff --git a/core/java/android/view/ViewParent.java b/core/java/android/view/ViewParent.java
new file mode 100644
index 0000000..b456c5d
--- /dev/null
+++ b/core/java/android/view/ViewParent.java
@@ -0,0 +1,211 @@
+/*
+ * 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 android.view;
+
+import android.graphics.Rect;
+
+/**
+ * Defines the responsibilities for a class that will be a parent of a View.
+ * This is the API that a view sees when it wants to interact with its parent.
+ * 
+ */
+public interface ViewParent {
+    /**
+     * Called when something has changed which has invalidated the layout of a
+     * child of this view parent. This will schedule a layout pass of the view
+     * tree.
+     */
+    public void requestLayout();
+
+    /**
+     * Indicates whether layout was requested on this view parent.
+     *
+     * @return true if layout was requested, false otherwise
+     */
+    public boolean isLayoutRequested();
+
+    /**
+     * Called when a child wants the view hierarchy to gather and report
+     * transparent regions to the window compositor. Views that "punch" holes in
+     * the view hierarchy, such as SurfaceView can use this API to improve
+     * performance of the system. When no such a view is present in the
+     * hierarchy, this optimization in unnecessary and might slightly reduce the
+     * view hierarchy performance.
+     * 
+     * @param child the view requesting the transparent region computation
+     * 
+     */
+    public void requestTransparentRegion(View child);
+
+    /**
+     * All or part of a child is dirty and needs to be redrawn.
+     * 
+     * @param child The child which is dirty
+     * @param r The area within the child that is invalid
+     */
+    public void invalidateChild(View child, Rect r);
+
+    /**
+     * All or part of a child is dirty and needs to be redrawn.
+     *
+     * The location array is an array of two int values which respectively
+     * define the left and the top position of the dirty child.
+     *
+     * This method must return the parent of this ViewParent if the specified
+     * rectangle must be invalidated in the parent. If the specified rectangle
+     * does not require invalidation in the parent or if the parent does not
+     * exist, this method must return null.
+     *
+     * When this method returns a non-null value, the location array must
+     * have been updated with the left and top coordinates of this ViewParent.
+     *
+     * @param location An array of 2 ints containing the left and top
+     *        coordinates of the child to invalidate
+     * @param r The area within the child that is invalid
+     *
+     * @return the parent of this ViewParent or null
+     */
+    public ViewParent invalidateChildInParent(int[] location, Rect r);
+
+    /**
+     * Returns the parent if it exists, or null.
+     *
+     * @return a ViewParent or null if this ViewParent does not have a parent
+     */
+    public ViewParent getParent();
+
+    /**
+     * Called when a child of this parent wants focus
+     * 
+     * @param child The child of this ViewParent that wants focus. This view
+     *        will contain the focused view. It is not necessarily the view that
+     *        actually has focus.
+     * @param focused The view that is a descendant of child that actually has
+     *        focus
+     */
+    public void requestChildFocus(View child, View focused);
+
+    /**
+     * Tell view hierarchy that the global view attributes need to be
+     * re-evaluated.
+     * 
+     * @param child View whose attributes have changed.
+     */
+    public void recomputeViewAttributes(View child);
+    
+    /**
+     * Called when a child of this parent is giving up focus
+     * 
+     * @param child The view that is giving up focus
+     */
+    public void clearChildFocus(View child);
+
+    public boolean getChildVisibleRect(View child, Rect r, android.graphics.Point offset);
+
+    /**
+     * Find the nearest view in the specified direction that wants to take focus
+     * 
+     * @param v The view that currently has focus
+     * @param direction One of FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, and FOCUS_RIGHT
+     */
+    public View focusSearch(View v, int direction);
+
+    /**
+     * Change the z order of the child so it's on top of all other children
+     * 
+     * @param child
+     */
+    public void bringChildToFront(View child);
+
+    /**
+     * Tells the parent that a new focusable view has become available. This is
+     * to handle transitions from the case where there are no focusable views to
+     * the case where the first focusable view appears.
+     * 
+     * @param v The view that has become newly focusable
+     */
+    public void focusableViewAvailable(View v);
+
+    /**
+     * Bring up a context menu for the specified view or its ancestors.
+     * <p>
+     * In most cases, a subclass does not need to override this.  However, if
+     * the subclass is added directly to the window manager (for example,
+     * {@link ViewManager#addView(View, android.view.ViewGroup.LayoutParams)})
+     * then it should override this and show the context menu.
+     * 
+     * @param originalView The source view where the context menu was first invoked
+     * @return true if a context menu was displayed
+     */
+    public boolean showContextMenuForChild(View originalView);
+
+    /**
+     * Have the parent populate the specified context menu if it has anything to
+     * add (and then recurse on its parent).
+     * 
+     * @param menu The menu to populate
+     */
+    public void createContextMenu(ContextMenu menu);
+
+    /**
+     * This method is called on the parent when a child's drawable state
+     * has changed.
+     *
+     * @param child The child whose drawable state has changed.
+     */
+    public void childDrawableStateChanged(View child);
+    
+    /**
+     * Called when a child does not want this parent and its ancestors to
+     * intercept touch events with
+     * {@link ViewGroup#onInterceptTouchEvent(MotionEvent)}.
+     * <p>
+     * This parent should pass this call onto its parents. This parent must obey
+     * this request for the duration of the touch (that is, only clear the flag
+     * after this parent has received an up or a cancel.
+     * 
+     * @param disallowIntercept True if the child does not want the parent to
+     *            intercept touch events.
+     */
+    public void requestDisallowInterceptTouchEvent(boolean disallowIntercept);
+    
+    /**
+     * Called when a child of this group wants a particular rectangle to be
+     * positioned onto the screen.  {@link ViewGroup}s overriding this can trust
+     * that:
+     * <ul>
+     *   <li>child will be a direct child of this group</li>
+     *   <li>rectangle will be in the child's coordinates</li>
+     * </ul>
+     *
+     * <p>{@link ViewGroup}s overriding this should uphold the contract:</p>
+     * <ul>
+     *   <li>nothing will change if the rectangle is already visible</li>
+     *   <li>the view port will be scrolled only just enough to make the
+     *       rectangle visible</li>
+     * <ul>
+     *
+     * @param child The direct child making the request.
+     * @param rectangle The rectangle in the child's coordinates the child
+     *        wishes to be on the screen.
+     * @param immediate True to forbid animated or delayed scrolling,
+     *        false otherwise
+     * @return Whether the group scrolled to handle the operation
+     */
+    public boolean requestChildRectangleOnScreen(View child, Rect rectangle,
+            boolean immediate);
+}
diff --git a/core/java/android/view/ViewRoot.java b/core/java/android/view/ViewRoot.java
new file mode 100644
index 0000000..9b13d38
--- /dev/null
+++ b/core/java/android/view/ViewRoot.java
@@ -0,0 +1,2872 @@
+/*
+ * 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 android.view;
+
+import com.android.internal.view.IInputMethodCallback;
+import com.android.internal.view.IInputMethodSession;
+
+import android.graphics.Canvas;
+import android.graphics.PixelFormat;
+import android.graphics.Point;
+import android.graphics.PorterDuff;
+import android.graphics.Rect;
+import android.graphics.Region;
+import android.os.*;
+import android.os.Process;
+import android.os.SystemProperties;
+import android.util.AndroidRuntimeException;
+import android.util.Config;
+import android.util.Log;
+import android.util.EventLog;
+import android.util.SparseArray;
+import android.util.DisplayMetrics;
+import android.view.View.MeasureSpec;
+import android.view.inputmethod.InputConnection;
+import android.view.inputmethod.InputMethodManager;
+import android.widget.Scroller;
+import android.content.pm.PackageManager;
+import android.content.Context;
+import android.app.ActivityManagerNative;
+import android.Manifest;
+import android.media.AudioManager;
+
+import java.lang.ref.WeakReference;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.ArrayList;
+
+import javax.microedition.khronos.egl.*;
+import javax.microedition.khronos.opengles.*;
+import static javax.microedition.khronos.opengles.GL10.*;
+
+/**
+ * The top of a view hierarchy, implementing the needed protocol between View
+ * and the WindowManager.  This is for the most part an internal implementation
+ * detail of {@link WindowManagerImpl}.
+ *
+ * {@hide}
+ */
+@SuppressWarnings({"EmptyCatchBlock"})
+public final class ViewRoot extends Handler implements ViewParent,
+        View.AttachInfo.Callbacks {
+    private static final String TAG = "ViewRoot";
+    private static final boolean DBG = false;
+    @SuppressWarnings({"ConstantConditionalExpression"})
+    private static final boolean LOCAL_LOGV = false ? Config.LOGD : Config.LOGV;
+    /** @noinspection PointlessBooleanExpression*/
+    private static final boolean DEBUG_DRAW = false || LOCAL_LOGV;
+    private static final boolean DEBUG_LAYOUT = false || LOCAL_LOGV;
+    private static final boolean DEBUG_INPUT_RESIZE = false || LOCAL_LOGV;
+    private static final boolean DEBUG_ORIENTATION = false || LOCAL_LOGV;
+    private static final boolean DEBUG_TRACKBALL = false || LOCAL_LOGV;
+    private static final boolean DEBUG_IMF = false || LOCAL_LOGV;
+    private static final boolean WATCH_POINTER = false;
+
+    static final boolean PROFILE_DRAWING = false;
+    private static final boolean PROFILE_LAYOUT = false;
+    // profiles real fps (times between draws) and displays the result
+    private static final boolean SHOW_FPS = false;
+    // used by SHOW_FPS
+    private static int sDrawTime;
+
+    /**
+     * Maximum time we allow the user to roll the trackball enough to generate
+     * a key event, before resetting the counters.
+     */
+    static final int MAX_TRACKBALL_DELAY = 250;
+
+    static long sInstanceCount = 0;
+
+    static IWindowSession sWindowSession;
+
+    static final Object mStaticInit = new Object();
+    static boolean mInitialized = false;
+
+    static final ThreadLocal<RunQueue> sRunQueues = new ThreadLocal<RunQueue>();
+
+    long mLastTrackballTime = 0;
+    final TrackballAxis mTrackballAxisX = new TrackballAxis();
+    final TrackballAxis mTrackballAxisY = new TrackballAxis();
+
+    final int[] mTmpLocation = new int[2];
+    
+    final InputMethodCallback mInputMethodCallback;
+    final SparseArray<Object> mPendingEvents = new SparseArray<Object>();
+    int mPendingEventSeq = 0;
+    
+    final Thread mThread;
+
+    final WindowLeaked mLocation;
+
+    final WindowManager.LayoutParams mWindowAttributes = new WindowManager.LayoutParams();
+
+    final W mWindow;
+
+    View mView;
+    View mFocusedView;
+    View mRealFocusedView;  // this is not set to null in touch mode
+    int mViewVisibility;
+    boolean mAppVisible = true;
+
+    final Region mTransparentRegion;
+    final Region mPreviousTransparentRegion;
+
+    int mWidth;
+    int mHeight;
+    Rect mDirty; // will be a graphics.Region soon
+
+    final View.AttachInfo mAttachInfo;
+
+    final Rect mTempRect; // used in the transaction to not thrash the heap.
+    final Rect mVisRect; // used to retrieve visible rect of focused view.
+    final Point mVisPoint; // used to retrieve global offset of focused view.
+
+    boolean mTraversalScheduled;
+    boolean mWillDrawSoon;
+    boolean mLayoutRequested;
+    boolean mFirst;
+    boolean mReportNextDraw;
+    boolean mFullRedrawNeeded;
+    boolean mNewSurfaceNeeded;
+    boolean mHasHadWindowFocus;
+    boolean mLastWasImTarget;
+
+    boolean mWindowAttributesChanged = false;
+
+    // These can be accessed by any thread, must be protected with a lock.
+    Surface mSurface;
+
+    boolean mAdded;
+    boolean mAddedTouchMode;
+
+    /*package*/ int mAddNesting;
+
+    // These are accessed by multiple threads.
+    final Rect mWinFrame; // frame given by window manager.
+
+    final Rect mPendingVisibleInsets = new Rect();
+    final Rect mPendingContentInsets = new Rect();
+    final ViewTreeObserver.InternalInsetsInfo mLastGivenInsets
+            = new ViewTreeObserver.InternalInsetsInfo();
+
+    boolean mScrollMayChange;
+    int mSoftInputMode;
+    View mLastScrolledFocus;
+    int mScrollY;
+    int mCurScrollY;
+    Scroller mScroller;
+    
+    EGL10 mEgl;
+    EGLDisplay mEglDisplay;
+    EGLContext mEglContext;
+    EGLSurface mEglSurface;
+    GL11 mGL;
+    Canvas mGlCanvas;
+    boolean mUseGL;
+    boolean mGlWanted;
+
+    final ViewConfiguration mViewConfiguration;    
+
+    /**
+     * see {@link #playSoundEffect(int)}
+     */
+    AudioManager mAudioManager;
+
+    private final float mDensity;
+
+    public ViewRoot(Context context) {
+        super();
+
+        ++sInstanceCount;
+
+        // Initialize the statics when this class is first instantiated. This is
+        // done here instead of in the static block because Zygote does not
+        // allow the spawning of threads.
+        synchronized (mStaticInit) {
+            if (!mInitialized) {
+                try {
+                    InputMethodManager imm = InputMethodManager.getInstance(context);
+                    sWindowSession = IWindowManager.Stub.asInterface(
+                            ServiceManager.getService("window"))
+                            .openSession(imm.getClient(), imm.getInputContext());
+                    mInitialized = true;
+                } catch (RemoteException e) {
+                }
+            }
+        }
+
+        mThread = Thread.currentThread();
+        mLocation = new WindowLeaked(null);
+        mLocation.fillInStackTrace();
+        mWidth = -1;
+        mHeight = -1;
+        mDirty = new Rect();
+        mTempRect = new Rect();
+        mVisRect = new Rect();
+        mVisPoint = new Point();
+        mWinFrame = new Rect();
+        mWindow = new W(this);
+        mInputMethodCallback = new InputMethodCallback(this);
+        mViewVisibility = View.GONE;
+        mTransparentRegion = new Region();
+        mPreviousTransparentRegion = new Region();
+        mFirst = true; // true for the first time the view is added
+        mSurface = new Surface();
+        mAdded = false;
+        mAttachInfo = new View.AttachInfo(sWindowSession, mWindow, this, this);
+        mViewConfiguration = ViewConfiguration.get(context);
+        mDensity = context.getResources().getDisplayMetrics().density;
+    }
+
+    @Override
+    protected void finalize() throws Throwable {
+        super.finalize();
+        --sInstanceCount;
+    }
+
+    public static long getInstanceCount() {
+        return sInstanceCount;
+    }
+
+    // FIXME for perf testing only
+    private boolean mProfile = false;
+
+    /**
+     * Call this to profile the next traversal call.
+     * FIXME for perf testing only. Remove eventually
+     */
+    public void profile() {
+        mProfile = true;
+    }
+
+    /**
+     * Indicates whether we are in touch mode. Calling this method triggers an IPC
+     * call and should be avoided whenever possible.
+     *
+     * @return True, if the device is in touch mode, false otherwise.
+     *
+     * @hide
+     */
+    static boolean isInTouchMode() {
+        if (mInitialized) {
+            try {
+                return sWindowSession.getInTouchMode();
+            } catch (RemoteException e) {
+            }
+        }
+        return false;
+    }
+
+    private void initializeGL() {
+        initializeGLInner();
+        int err = mEgl.eglGetError();
+        if (err != EGL10.EGL_SUCCESS) {
+            // give-up on using GL
+            destroyGL();
+            mGlWanted = false;
+        }
+    }
+
+    private void initializeGLInner() {
+        final EGL10 egl = (EGL10) EGLContext.getEGL();
+        mEgl = egl;
+
+        /*
+         * Get to the default display.
+         */
+        final EGLDisplay eglDisplay = egl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY);
+        mEglDisplay = eglDisplay;
+
+        /*
+         * We can now initialize EGL for that display
+         */
+        int[] version = new int[2];
+        egl.eglInitialize(eglDisplay, version);
+
+        /*
+         * Specify a configuration for our opengl session
+         * and grab the first configuration that matches is
+         */
+        final int[] configSpec = {
+                EGL10.EGL_RED_SIZE,      5,
+                EGL10.EGL_GREEN_SIZE,    6,
+                EGL10.EGL_BLUE_SIZE,     5,
+                EGL10.EGL_DEPTH_SIZE,    0,
+                EGL10.EGL_NONE
+        };
+        final EGLConfig[] configs = new EGLConfig[1];
+        final int[] num_config = new int[1];
+        egl.eglChooseConfig(eglDisplay, configSpec, configs, 1, num_config);
+        final EGLConfig config = configs[0];
+
+        /*
+         * Create an OpenGL ES context. This must be done only once, an
+         * OpenGL context is a somewhat heavy object.
+         */
+        final EGLContext context = egl.eglCreateContext(eglDisplay, config,
+                EGL10.EGL_NO_CONTEXT, null);
+        mEglContext = context;
+
+        /*
+         * Create an EGL surface we can render into.
+         */
+        final EGLSurface surface = egl.eglCreateWindowSurface(eglDisplay, config, mHolder, null);
+        mEglSurface = surface;
+
+        /*
+         * Before we can issue GL commands, we need to make sure
+         * the context is current and bound to a surface.
+         */
+        egl.eglMakeCurrent(eglDisplay, surface, surface, context);
+
+        /*
+         * Get to the appropriate GL interface.
+         * This is simply done by casting the GL context to either
+         * GL10 or GL11.
+         */
+        final GL11 gl = (GL11) context.getGL();
+        mGL = gl;
+        mGlCanvas = new Canvas(gl);
+        mUseGL = true;
+    }
+
+    private void destroyGL() {
+        // inform skia that the context is gone
+        nativeAbandonGlCaches();
+
+        mEgl.eglMakeCurrent(mEglDisplay, EGL10.EGL_NO_SURFACE,
+                EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_CONTEXT);
+        mEgl.eglDestroyContext(mEglDisplay, mEglContext);
+        mEgl.eglDestroySurface(mEglDisplay, mEglSurface);
+        mEgl.eglTerminate(mEglDisplay);
+        mEglContext = null;
+        mEglSurface = null;
+        mEglDisplay = null;
+        mEgl = null;
+        mGlCanvas = null;
+        mGL = null;
+        mUseGL = false;
+    }
+
+    private void checkEglErrors() {
+        if (mUseGL) {
+            int err = mEgl.eglGetError();
+            if (err != EGL10.EGL_SUCCESS) {
+                // something bad has happened revert to
+                // normal rendering.
+                destroyGL();
+                if (err != EGL11.EGL_CONTEXT_LOST) {
+                    // we'll try again if it was context lost
+                    mGlWanted = false;
+                }
+            }
+        }
+    }
+
+    /**
+     * We have one child
+     */
+    public void setView(View view, WindowManager.LayoutParams attrs,
+            View panelParentView) {
+        synchronized (this) {
+            if (mView == null) {
+                mWindowAttributes.copyFrom(attrs);
+                mSoftInputMode = attrs.softInputMode;
+                mWindowAttributesChanged = true;
+                mView = view;
+                mAttachInfo.mRootView = view;
+                if (panelParentView != null) {
+                    mAttachInfo.mPanelParentWindowToken
+                            = panelParentView.getApplicationWindowToken();
+                }
+                mAdded = true;
+                int res; /* = WindowManagerImpl.ADD_OKAY; */
+                
+                // Schedule the first layout -before- adding to the window
+                // manager, to make sure we do the relayout before receiving
+                // any other events from the system.
+                requestLayout();
+                
+                try {
+                    res = sWindowSession.add(mWindow, attrs,
+                            getHostVisibility(), mAttachInfo.mContentInsets);
+                } catch (RemoteException e) {
+                    mAdded = false;
+                    mView = null;
+                    mAttachInfo.mRootView = null;
+                    unscheduleTraversals();
+                    throw new RuntimeException("Adding window failed", e);
+                }
+                mPendingContentInsets.set(mAttachInfo.mContentInsets);
+                mPendingVisibleInsets.set(0, 0, 0, 0);
+                if (Config.LOGV) Log.v("ViewRoot", "Added window " + mWindow);
+                if (res < WindowManagerImpl.ADD_OKAY) {
+                    mView = null;
+                    mAttachInfo.mRootView = null;
+                    mAdded = false;
+                    unscheduleTraversals();
+                    switch (res) {
+                        case WindowManagerImpl.ADD_BAD_APP_TOKEN:
+                        case WindowManagerImpl.ADD_BAD_SUBWINDOW_TOKEN:
+                            throw new WindowManagerImpl.BadTokenException(
+                                "Unable to add window -- token " + attrs.token
+                                + " is not valid; is your activity running?");
+                        case WindowManagerImpl.ADD_NOT_APP_TOKEN:
+                            throw new WindowManagerImpl.BadTokenException(
+                                "Unable to add window -- token " + attrs.token
+                                + " is not for an application");
+                        case WindowManagerImpl.ADD_APP_EXITING:
+                            throw new WindowManagerImpl.BadTokenException(
+                                "Unable to add window -- app for token " + attrs.token
+                                + " is exiting");
+                        case WindowManagerImpl.ADD_DUPLICATE_ADD:
+                            throw new WindowManagerImpl.BadTokenException(
+                                "Unable to add window -- window " + mWindow
+                                + " has already been added");
+                        case WindowManagerImpl.ADD_STARTING_NOT_NEEDED:
+                            // Silently ignore -- we would have just removed it
+                            // right away, anyway.
+                            return;
+                        case WindowManagerImpl.ADD_MULTIPLE_SINGLETON:
+                            throw new WindowManagerImpl.BadTokenException(
+                                "Unable to add window " + mWindow +
+                                " -- another window of this type already exists");
+                        case WindowManagerImpl.ADD_PERMISSION_DENIED:
+                            throw new WindowManagerImpl.BadTokenException(
+                                "Unable to add window " + mWindow +
+                                " -- permission denied for this window type");
+                    }
+                    throw new RuntimeException(
+                        "Unable to add window -- unknown error code " + res);
+                }
+                view.assignParent(this);
+                mAddedTouchMode = (res&WindowManagerImpl.ADD_FLAG_IN_TOUCH_MODE) != 0;
+                mAppVisible = (res&WindowManagerImpl.ADD_FLAG_APP_VISIBLE) != 0;
+            }
+        }
+    }
+
+    public View getView() {
+        return mView;
+    }
+
+    final WindowLeaked getLocation() {
+        return mLocation;
+    }
+
+    void setLayoutParams(WindowManager.LayoutParams attrs, boolean newView) {
+        synchronized (this) {
+            mWindowAttributes.copyFrom(attrs);
+            if (newView) {
+                mSoftInputMode = attrs.softInputMode;
+                requestLayout();
+            }
+            mWindowAttributesChanged = true;
+            scheduleTraversals();
+        }
+    }
+
+    void handleAppVisibility(boolean visible) {
+        if (mAppVisible != visible) {
+            mAppVisible = visible;
+            scheduleTraversals();
+        }
+    }
+
+    void handleGetNewSurface() {
+        mNewSurfaceNeeded = true;
+        mFullRedrawNeeded = true;
+        scheduleTraversals();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void requestLayout() {
+        checkThread();
+        mLayoutRequested = true;
+        scheduleTraversals();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public boolean isLayoutRequested() {
+        return mLayoutRequested;
+    }
+
+    public void invalidateChild(View child, Rect dirty) {
+        checkThread();
+        if (LOCAL_LOGV) Log.v(TAG, "Invalidate child: " + dirty);
+        if (mCurScrollY != 0) {
+            mTempRect.set(dirty);
+            mTempRect.offset(0, -mCurScrollY);
+            dirty = mTempRect;
+        }
+        mDirty.union(dirty);
+        if (!mWillDrawSoon) {
+            scheduleTraversals();
+        }
+    }
+
+    public ViewParent getParent() {
+        return null;
+    }
+
+    public ViewParent invalidateChildInParent(final int[] location, final Rect dirty) {
+        invalidateChild(null, dirty);
+        return null;
+    }
+
+     public boolean getChildVisibleRect(View child, Rect r, android.graphics.Point offset) {
+        if (child != mView) {
+            throw new RuntimeException("child is not mine, honest!");
+        }
+        // Note: don't apply scroll offset, because we want to know its
+        // visibility in the virtual canvas being given to the view hierarchy.
+        return r.intersect(0, 0, mWidth, mHeight);
+    }
+
+    public void bringChildToFront(View child) {
+    }
+
+    public void scheduleTraversals() {
+        if (!mTraversalScheduled) {
+            mTraversalScheduled = true;
+            sendEmptyMessage(DO_TRAVERSAL);
+        }
+    }
+
+    public void unscheduleTraversals() {
+        if (mTraversalScheduled) {
+            mTraversalScheduled = false;
+            removeMessages(DO_TRAVERSAL);
+        }
+    }
+
+    int getHostVisibility() {
+        return mAppVisible ? mView.getVisibility() : View.GONE;
+    }
+    
+    private void performTraversals() {
+        // cache mView since it is used so much below...
+        final View host = mView;
+
+        if (DBG) {
+            System.out.println("======================================");
+            System.out.println("performTraversals");
+            host.debug();
+        }
+
+        if (host == null || !mAdded)
+            return;
+
+        mTraversalScheduled = false;
+        mWillDrawSoon = true;
+        boolean windowResizesToFitContent = false;
+        boolean fullRedrawNeeded = mFullRedrawNeeded;
+        boolean newSurface = false;
+        WindowManager.LayoutParams lp = mWindowAttributes;
+
+        int desiredWindowWidth;
+        int desiredWindowHeight;
+        int childWidthMeasureSpec;
+        int childHeightMeasureSpec;
+
+        final View.AttachInfo attachInfo = mAttachInfo;
+
+        final int viewVisibility = getHostVisibility();
+        boolean viewVisibilityChanged = mViewVisibility != viewVisibility
+                || mNewSurfaceNeeded;
+
+        WindowManager.LayoutParams params = null;
+        if (mWindowAttributesChanged) {
+            mWindowAttributesChanged = false;
+            params = lp;
+        }
+
+        if (mFirst) {
+            fullRedrawNeeded = true;
+            mLayoutRequested = true;
+
+            Display d = new Display(0);
+            desiredWindowWidth = d.getWidth();
+            desiredWindowHeight = d.getHeight();
+
+            // For the very first time, tell the view hierarchy that it
+            // is attached to the window.  Note that at this point the surface
+            // object is not initialized to its backing store, but soon it
+            // will be (assuming the window is visible).
+            attachInfo.mSurface = mSurface;
+            attachInfo.mHasWindowFocus = false;
+            attachInfo.mWindowVisibility = viewVisibility;
+            attachInfo.mRecomputeGlobalAttributes = false;
+            attachInfo.mKeepScreenOn = false;
+            viewVisibilityChanged = false;
+            host.dispatchAttachedToWindow(attachInfo, 0);
+            getRunQueue().executeActions(attachInfo.mHandler);
+            //Log.i(TAG, "Screen on initialized: " + attachInfo.mKeepScreenOn);
+        } else {
+            desiredWindowWidth = mWinFrame.width();
+            desiredWindowHeight = mWinFrame.height();
+            if (desiredWindowWidth != mWidth || desiredWindowHeight != mHeight) {
+                if (DEBUG_ORIENTATION) Log.v("ViewRoot",
+                        "View " + host + " resized to: " + mWinFrame);
+                fullRedrawNeeded = true;
+                mLayoutRequested = true;
+                windowResizesToFitContent = true;
+            }
+        }
+
+        if (viewVisibilityChanged) {
+            attachInfo.mWindowVisibility = viewVisibility;
+            host.dispatchWindowVisibilityChanged(viewVisibility);
+            if (viewVisibility != View.VISIBLE || mNewSurfaceNeeded) {
+                if (mUseGL) {
+                    destroyGL();
+                }
+            }
+            if (viewVisibility == View.GONE) {
+                // After making a window gone, we will count it as being
+                // shown for the first time the next time it gets focus.
+                mHasHadWindowFocus = false;
+            }
+        }
+
+        boolean insetsChanged = false;
+        
+        if (mLayoutRequested) {
+            if (mFirst) {
+                host.fitSystemWindows(mAttachInfo.mContentInsets);
+                // make sure touch mode code executes by setting cached value
+                // to opposite of the added touch mode.
+                mAttachInfo.mInTouchMode = !mAddedTouchMode;
+                ensureTouchModeLocally(mAddedTouchMode);
+            } else {
+                if (!mAttachInfo.mContentInsets.equals(mPendingContentInsets)) {
+                    mAttachInfo.mContentInsets.set(mPendingContentInsets);
+                    host.fitSystemWindows(mAttachInfo.mContentInsets);
+                    insetsChanged = true;
+                    if (DEBUG_LAYOUT) Log.v(TAG, "Content insets changing to: "
+                            + mAttachInfo.mContentInsets);
+                }
+                if (!mAttachInfo.mVisibleInsets.equals(mPendingVisibleInsets)) {
+                    mAttachInfo.mVisibleInsets.set(mPendingVisibleInsets);
+                    if (DEBUG_LAYOUT) Log.v(TAG, "Visible insets changing to: "
+                            + mAttachInfo.mVisibleInsets);
+                }
+                if (lp.width == ViewGroup.LayoutParams.WRAP_CONTENT
+                        || lp.height == ViewGroup.LayoutParams.WRAP_CONTENT) {
+                    windowResizesToFitContent = true;
+
+                    Display d = new Display(0);
+                    desiredWindowWidth = d.getWidth();
+                    desiredWindowHeight = d.getHeight();
+                }
+            }
+
+            childWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth, lp.width);
+            childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);
+
+            // Ask host how big it wants to be
+            if (DEBUG_ORIENTATION || DEBUG_LAYOUT) Log.v("ViewRoot",
+                    "Measuring " + host + " in display " + desiredWindowWidth
+                    + "x" + desiredWindowHeight + "...");
+            host.measure(childWidthMeasureSpec, childHeightMeasureSpec);
+
+            if (DBG) {
+                System.out.println("======================================");
+                System.out.println("performTraversals -- after measure");
+                host.debug();
+            }
+        }
+
+        if (attachInfo.mRecomputeGlobalAttributes) {
+            //Log.i(TAG, "Computing screen on!");
+            attachInfo.mRecomputeGlobalAttributes = false;
+            boolean oldVal = attachInfo.mKeepScreenOn;
+            attachInfo.mKeepScreenOn = false;
+            host.dispatchCollectViewAttributes(0);
+            if (attachInfo.mKeepScreenOn != oldVal) {
+                params = lp;
+                //Log.i(TAG, "Keep screen on changed: " + attachInfo.mKeepScreenOn);
+            }
+        }
+
+        if (mFirst || attachInfo.mViewVisibilityChanged) {
+            attachInfo.mViewVisibilityChanged = false;
+            int resizeMode = mSoftInputMode &
+                    WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST;
+            // If we are in auto resize mode, then we need to determine
+            // what mode to use now.
+            if (resizeMode == WindowManager.LayoutParams.SOFT_INPUT_ADJUST_UNSPECIFIED) {
+                final int N = attachInfo.mScrollContainers.size();
+                for (int i=0; i<N; i++) {
+                    if (attachInfo.mScrollContainers.get(i).isShown()) {
+                        resizeMode = WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
+                    }
+                }
+                if (resizeMode == 0) {
+                    resizeMode = WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN;
+                }
+                if ((lp.softInputMode &
+                        WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST) != resizeMode) {
+                    lp.softInputMode = (lp.softInputMode &
+                            ~WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST) |
+                            resizeMode;
+                    params = lp;
+                }
+            }
+        }
+        
+        if (params != null && (host.mPrivateFlags & View.REQUEST_TRANSPARENT_REGIONS) != 0) {
+            if (!PixelFormat.formatHasAlpha(params.format)) {
+                params.format = PixelFormat.TRANSLUCENT;
+            }
+        }
+
+        boolean windowShouldResize = mLayoutRequested && windowResizesToFitContent
+            && (mWidth != host.mMeasuredWidth || mHeight != host.mMeasuredHeight);
+
+        final boolean computesInternalInsets =
+                attachInfo.mTreeObserver.hasComputeInternalInsetsListeners();
+        boolean insetsPending = false;
+        int relayoutResult = 0;
+        if (mFirst || windowShouldResize || insetsChanged
+                || viewVisibilityChanged || params != null) {
+
+            if (viewVisibility == View.VISIBLE) {
+                // If this window is giving internal insets to the window
+                // manager, and it is being added or changing its visibility,
+                // then we want to first give the window manager "fake"
+                // insets to cause it to effectively ignore the content of
+                // the window during layout.  This avoids it briefly causing
+                // other windows to resize/move based on the raw frame of the
+                // window, waiting until we can finish laying out this window
+                // and get back to the window manager with the ultimately
+                // computed insets.
+                insetsPending = computesInternalInsets
+                        && (mFirst || viewVisibilityChanged);
+                
+                if (mWindowAttributes.memoryType == WindowManager.LayoutParams.MEMORY_TYPE_GPU) {
+                    if (params == null) {
+                        params = mWindowAttributes;
+                    }
+                    mGlWanted = true;
+                }
+            }
+
+            final Rect frame = mWinFrame;
+            boolean initialized = false;
+            boolean contentInsetsChanged = false;
+            boolean visibleInsetsChanged = false;
+            try {
+                boolean hadSurface = mSurface.isValid();
+                int fl = 0;
+                if (params != null) {
+                    fl = params.flags;
+                    if (attachInfo.mKeepScreenOn) {
+                        params.flags |= WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON;
+                    }
+                }
+                relayoutResult = sWindowSession.relayout(
+                    mWindow, params, host.mMeasuredWidth, host.mMeasuredHeight,
+                    viewVisibility, insetsPending, frame,
+                    mPendingContentInsets, mPendingVisibleInsets, mSurface);
+                if (params != null) {
+                    params.flags = fl;
+                }
+
+                if (DEBUG_LAYOUT) Log.v(TAG, "relayout: frame=" + frame.toShortString()
+                        + " content=" + mPendingContentInsets.toShortString()
+                        + " visible=" + mPendingVisibleInsets.toShortString()
+                        + " surface=" + mSurface);
+                
+                contentInsetsChanged = !mPendingContentInsets.equals(
+                        mAttachInfo.mContentInsets);
+                visibleInsetsChanged = !mPendingVisibleInsets.equals(
+                        mAttachInfo.mVisibleInsets);
+                if (contentInsetsChanged) {
+                    mAttachInfo.mContentInsets.set(mPendingContentInsets);
+                    host.fitSystemWindows(mAttachInfo.mContentInsets);
+                    if (DEBUG_LAYOUT) Log.v(TAG, "Content insets changing to: "
+                            + mAttachInfo.mContentInsets);
+                }
+                if (visibleInsetsChanged) {
+                    mAttachInfo.mVisibleInsets.set(mPendingVisibleInsets);
+                    if (DEBUG_LAYOUT) Log.v(TAG, "Visible insets changing to: "
+                            + mAttachInfo.mVisibleInsets);
+                }
+
+                if (!hadSurface) {
+                    if (mSurface.isValid()) {
+                        // If we are creating a new surface, then we need to
+                        // completely redraw it.  Also, when we get to the
+                        // point of drawing it we will hold off and schedule
+                        // a new traversal instead.  This is so we can tell the
+                        // window manager about all of the windows being displayed
+                        // before actually drawing them, so it can display then
+                        // all at once.
+                        newSurface = true;
+                        fullRedrawNeeded = true;
+    
+                        if (mGlWanted && !mUseGL) {
+                            initializeGL();
+                            initialized = mGlCanvas != null;
+                        }
+                    }
+                } else if (!mSurface.isValid()) {
+                    // If the surface has been removed, then reset the scroll
+                    // positions.
+                    mLastScrolledFocus = null;
+                    mScrollY = mCurScrollY = 0;
+                    if (mScroller != null) {
+                        mScroller.abortAnimation();
+                    }
+                }
+            } catch (RemoteException e) {
+            }
+            if (DEBUG_ORIENTATION) Log.v(
+                    "ViewRoot", "Relayout returned: frame=" + mWinFrame + ", surface=" + mSurface);
+
+            attachInfo.mWindowLeft = frame.left;
+            attachInfo.mWindowTop = frame.top;
+
+            // !!FIXME!! This next section handles the case where we did not get the
+            // window size we asked for. We should avoid this by getting a maximum size from
+            // the window session beforehand.
+            mWidth = frame.width();
+            mHeight = frame.height();
+
+            if (initialized) {
+                mGlCanvas.setViewport(mWidth, mHeight);
+            }
+
+            boolean focusChangedDueToTouchMode = ensureTouchModeLocally(
+                    (relayoutResult&WindowManagerImpl.RELAYOUT_IN_TOUCH_MODE) != 0);
+            if (focusChangedDueToTouchMode || mWidth != host.mMeasuredWidth
+                    || mHeight != host.mMeasuredHeight || contentInsetsChanged) {
+                childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
+                childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
+
+                if (DEBUG_LAYOUT) Log.v(TAG, "Ooops, something changed!  mWidth="
+                        + mWidth + " measuredWidth=" + host.mMeasuredWidth
+                        + " mHeight=" + mHeight
+                        + " measuredHeight" + host.mMeasuredHeight
+                        + " coveredInsetsChanged=" + contentInsetsChanged);
+                
+                 // Ask host how big it wants to be
+                host.measure(childWidthMeasureSpec, childHeightMeasureSpec);
+
+                // Implementation of weights from WindowManager.LayoutParams
+                // We just grow the dimensions as needed and re-measure if
+                // needs be
+                int width = host.mMeasuredWidth;
+                int height = host.mMeasuredHeight;
+                boolean measureAgain = false;
+
+                if (lp.horizontalWeight > 0.0f) {
+                    width += (int) ((mWidth - width) * lp.horizontalWeight);
+                    childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(width,
+                            MeasureSpec.EXACTLY);
+                    measureAgain = true;
+                }
+                if (lp.verticalWeight > 0.0f) {
+                    height += (int) ((mHeight - height) * lp.verticalWeight);
+                    childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(height,
+                            MeasureSpec.EXACTLY);
+                    measureAgain = true;
+                }
+
+                if (measureAgain) {
+                    if (DEBUG_LAYOUT) Log.v(TAG,
+                            "And hey let's measure once more: width=" + width
+                            + " height=" + height);
+                    host.measure(childWidthMeasureSpec, childHeightMeasureSpec);
+                }
+
+                mLayoutRequested = true;
+            }
+        }
+
+        final boolean didLayout = mLayoutRequested;
+        boolean triggerGlobalLayoutListener = didLayout
+                || attachInfo.mRecomputeGlobalAttributes;
+        if (didLayout) {
+            mLayoutRequested = false;
+            mScrollMayChange = true;
+            if (DEBUG_ORIENTATION || DEBUG_LAYOUT) Log.v(
+                "ViewRoot", "Laying out " + host + " to (" +
+                host.mMeasuredWidth + ", " + host.mMeasuredHeight + ")");
+            long startTime;
+            if (PROFILE_LAYOUT) {
+                startTime = SystemClock.elapsedRealtime();
+            }
+
+            host.layout(0, 0, host.mMeasuredWidth, host.mMeasuredHeight);
+
+            if (PROFILE_LAYOUT) {
+                EventLog.writeEvent(60001, SystemClock.elapsedRealtime() - startTime);
+            }
+
+            // By this point all views have been sized and positionned
+            // We can compute the transparent area
+
+            if ((host.mPrivateFlags & View.REQUEST_TRANSPARENT_REGIONS) != 0) {
+                // start out transparent
+                // TODO: AVOID THAT CALL BY CACHING THE RESULT?
+                host.getLocationInWindow(mTmpLocation);
+                mTransparentRegion.set(mTmpLocation[0], mTmpLocation[1],
+                        mTmpLocation[0] + host.mRight - host.mLeft,
+                        mTmpLocation[1] + host.mBottom - host.mTop);
+
+                host.gatherTransparentRegion(mTransparentRegion);
+                if (!mTransparentRegion.equals(mPreviousTransparentRegion)) {
+                    mPreviousTransparentRegion.set(mTransparentRegion);
+                    // reconfigure window manager
+                    try {
+                        sWindowSession.setTransparentRegion(mWindow, mTransparentRegion);
+                    } catch (RemoteException e) {
+                    }
+                }
+            }
+
+
+            if (DBG) {
+                System.out.println("======================================");
+                System.out.println("performTraversals -- after setFrame");
+                host.debug();
+            }
+        }
+
+        if (triggerGlobalLayoutListener) {
+            attachInfo.mRecomputeGlobalAttributes = false;
+            attachInfo.mTreeObserver.dispatchOnGlobalLayout();
+        }
+
+        if (computesInternalInsets) {
+            ViewTreeObserver.InternalInsetsInfo insets = attachInfo.mGivenInternalInsets;
+            final Rect givenContent = attachInfo.mGivenInternalInsets.contentInsets;
+            final Rect givenVisible = attachInfo.mGivenInternalInsets.visibleInsets;
+            givenContent.left = givenContent.top = givenContent.right
+                    = givenContent.bottom = givenVisible.left = givenVisible.top
+                    = givenVisible.right = givenVisible.bottom = 0;
+            attachInfo.mTreeObserver.dispatchOnComputeInternalInsets(insets);
+            if (insetsPending || !mLastGivenInsets.equals(insets)) {
+                mLastGivenInsets.set(insets);
+                try {
+                    sWindowSession.setInsets(mWindow, insets.mTouchableInsets,
+                            insets.contentInsets, insets.visibleInsets);
+                } catch (RemoteException e) {
+                }
+            }
+        }
+            
+        if (mFirst) {
+            // handle first focus request
+            if (DEBUG_INPUT_RESIZE) Log.v(TAG, "First: mView.hasFocus()="
+                    + mView.hasFocus());
+            if (mView != null) {
+                if (!mView.hasFocus()) {
+                    mView.requestFocus(View.FOCUS_FORWARD);
+                    mFocusedView = mRealFocusedView = mView.findFocus();
+                    if (DEBUG_INPUT_RESIZE) Log.v(TAG, "First: requested focused view="
+                            + mFocusedView);
+                } else {
+                    mRealFocusedView = mView.findFocus();
+                    if (DEBUG_INPUT_RESIZE) Log.v(TAG, "First: existing focused view="
+                            + mRealFocusedView);
+                }
+            }
+        }
+
+        mFirst = false;
+        mWillDrawSoon = false;
+        mNewSurfaceNeeded = false;
+        mViewVisibility = viewVisibility;
+
+        if (mAttachInfo.mHasWindowFocus) {
+            final boolean imTarget = WindowManager.LayoutParams
+                    .mayUseInputMethod(mWindowAttributes.flags);
+            if (imTarget != mLastWasImTarget) {
+                mLastWasImTarget = imTarget;
+                InputMethodManager imm = InputMethodManager.peekInstance();
+                if (imm != null && imTarget) {
+                    imm.startGettingWindowFocus(mView);
+                    imm.onWindowFocus(mView, mView.findFocus(),
+                            mWindowAttributes.softInputMode,
+                            !mHasHadWindowFocus, mWindowAttributes.flags);
+                }
+            }
+        }
+        
+        boolean cancelDraw = attachInfo.mTreeObserver.dispatchOnPreDraw();
+
+        if (!cancelDraw && !newSurface) {
+            mFullRedrawNeeded = false;
+            draw(fullRedrawNeeded);
+
+            if ((relayoutResult&WindowManagerImpl.RELAYOUT_FIRST_TIME) != 0
+                    || mReportNextDraw) {
+                if (LOCAL_LOGV) {
+                    Log.v("ViewRoot", "FINISHED DRAWING: " + mWindowAttributes.getTitle());
+                }
+                mReportNextDraw = false;
+                try {
+                    sWindowSession.finishDrawing(mWindow);
+                } catch (RemoteException e) {
+                }
+            }
+        } else {
+            // We were supposed to report when we are done drawing. Since we canceled the
+            // draw, remember it here.
+            if ((relayoutResult&WindowManagerImpl.RELAYOUT_FIRST_TIME) != 0) {
+                mReportNextDraw = true;
+            }
+            if (fullRedrawNeeded) {
+                mFullRedrawNeeded = true;
+            }
+            // Try again
+            scheduleTraversals();
+        }
+    }
+
+    public void requestTransparentRegion(View child) {
+        // the test below should not fail unless someone is messing with us
+        checkThread();
+        if (mView == child) {
+            mView.mPrivateFlags |= View.REQUEST_TRANSPARENT_REGIONS;
+            // Need to make sure we re-evaluate the window attributes next
+            // time around, to ensure the window has the correct format.
+            mWindowAttributesChanged = true;
+        }
+    }
+
+    /**
+     * Figures out the measure spec for the root view in a window based on it's
+     * layout params.
+     *
+     * @param windowSize
+     *            The available width or height of the window
+     *
+     * @param rootDimension
+     *            The layout params for one dimension (width or height) of the
+     *            window.
+     *
+     * @return The measure spec to use to measure the root view.
+     */
+    private int getRootMeasureSpec(int windowSize, int rootDimension) {
+        int measureSpec;
+        switch (rootDimension) {
+
+        case ViewGroup.LayoutParams.FILL_PARENT:
+            // Window can't resize. Force root view to be windowSize.
+            measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
+            break;
+        case ViewGroup.LayoutParams.WRAP_CONTENT:
+            // Window can resize. Set max size for root view.
+            measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
+            break;
+        default:
+            // Window wants to be an exact size. Force root view to be that size.
+            measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
+            break;
+        }
+        return measureSpec;
+    }
+
+    private void draw(boolean fullRedrawNeeded) {
+        Surface surface = mSurface;
+        if (surface == null || !surface.isValid()) {
+            return;
+        }
+
+        scrollToRectOrFocus(null, false);
+
+        if (mAttachInfo.mViewScrollChanged) {
+            mAttachInfo.mViewScrollChanged = false;
+            mAttachInfo.mTreeObserver.dispatchOnScrollChanged();
+        }
+        
+        int yoff;
+        final boolean scrolling = mScroller != null
+                && mScroller.computeScrollOffset(); 
+        if (scrolling) {
+            yoff = mScroller.getCurrY();
+        } else {
+            yoff = mScrollY;
+        }
+        if (mCurScrollY != yoff) {
+            mCurScrollY = yoff;
+            fullRedrawNeeded = true;
+        }
+
+        Rect dirty = mDirty;
+        if (mUseGL) {
+            if (!dirty.isEmpty()) {
+                Canvas canvas = mGlCanvas;
+                if (mGL!=null && canvas != null) {
+                    mGL.glDisable(GL_SCISSOR_TEST);
+                    mGL.glClearColor(0, 0, 0, 0);
+                    mGL.glClear(GL_COLOR_BUFFER_BIT);
+                    mGL.glEnable(GL_SCISSOR_TEST);
+
+                    mAttachInfo.mDrawingTime = SystemClock.uptimeMillis();
+                    canvas.translate(0, -yoff);
+                    mView.mPrivateFlags |= View.DRAWN;
+                    mView.draw(canvas);
+                    canvas.translate(0, yoff);
+
+                    mEgl.eglSwapBuffers(mEglDisplay, mEglSurface);
+                    checkEglErrors();
+
+                    if (SHOW_FPS) {
+                        int now = (int)SystemClock.elapsedRealtime();
+                        if (sDrawTime != 0) {
+                            nativeShowFPS(canvas, now - sDrawTime);
+                        }
+                        sDrawTime = now;
+                    }
+                }
+            }
+            if (scrolling) {
+                mFullRedrawNeeded = true;
+                scheduleTraversals();
+            }
+            return;
+        }
+
+        if (fullRedrawNeeded)
+            dirty.union(0, 0, mWidth, mHeight);
+
+        if (DEBUG_ORIENTATION || DEBUG_DRAW) {
+            Log.v("ViewRoot", "Draw " + mView + "/"
+                    + mWindowAttributes.getTitle()
+                    + ": dirty={" + dirty.left + "," + dirty.top
+                    + "," + dirty.right + "," + dirty.bottom + "} surface="
+                    + surface + " surface.isValid()=" + surface.isValid());
+        }
+
+        Canvas canvas;
+        try {
+            canvas = surface.lockCanvas(dirty);
+            // TODO: Do this in native
+            canvas.setDensityScale(mDensity);
+        } catch (Surface.OutOfResourcesException e) {
+            Log.e("ViewRoot", "OutOfResourcesException locking surface", e);
+            // TODO: we should ask the window manager to do something!
+            // for now we just do nothing
+            return;
+        }
+
+        try {
+            if (!dirty.isEmpty()) {
+                long startTime;
+
+                if (DEBUG_ORIENTATION || DEBUG_DRAW) {
+                    Log.v("ViewRoot", "Surface " + surface + " drawing to bitmap w="
+                            + canvas.getWidth() + ", h=" + canvas.getHeight());
+                    //canvas.drawARGB(255, 255, 0, 0);
+                }
+
+                if (PROFILE_DRAWING) {
+                    startTime = SystemClock.elapsedRealtime();
+                }
+
+                // If this bitmap's format includes an alpha channel, we
+                // need to clear it before drawing so that the child will
+                // properly re-composite its drawing on a transparent
+                // background. This automatically respects the clip/dirty region
+                if (!canvas.isOpaque()) {
+                    canvas.drawColor(0x00000000, PorterDuff.Mode.CLEAR);
+                } else if (yoff != 0) {
+                    // If we are applying an offset, we need to clear the area
+                    // where the offset doesn't appear to avoid having garbage
+                    // left in the blank areas.
+                    canvas.drawColor(0, PorterDuff.Mode.CLEAR);
+                }
+
+                dirty.setEmpty();
+                mAttachInfo.mDrawingTime = SystemClock.uptimeMillis();
+                canvas.translate(0, -yoff);
+                mView.mPrivateFlags |= View.DRAWN;                    
+                mView.draw(canvas);
+                canvas.translate(0, yoff);
+
+                if (SHOW_FPS) {
+                    int now = (int)SystemClock.elapsedRealtime();
+                    if (sDrawTime != 0) {
+                        nativeShowFPS(canvas, now - sDrawTime);
+                    }
+                    sDrawTime = now;
+                }
+
+                if (PROFILE_DRAWING) {
+                    EventLog.writeEvent(60000, SystemClock.elapsedRealtime() - startTime);
+                }
+            }
+            
+        } finally {
+            surface.unlockCanvasAndPost(canvas);
+        }
+
+        if (LOCAL_LOGV) {
+            Log.v("ViewRoot", "Surface " + surface + " unlockCanvasAndPost");
+        }
+        
+        if (scrolling) {
+            mFullRedrawNeeded = true;
+            scheduleTraversals();
+        }
+    }
+
+    boolean scrollToRectOrFocus(Rect rectangle, boolean immediate) {
+        final View.AttachInfo attachInfo = mAttachInfo;
+        final Rect ci = attachInfo.mContentInsets;
+        final Rect vi = attachInfo.mVisibleInsets;
+        int scrollY = 0;
+        boolean handled = false;
+        
+        if (vi.left > ci.left || vi.top > ci.top
+                || vi.right > ci.right || vi.bottom > ci.bottom) {
+            // We'll assume that we aren't going to change the scroll
+            // offset, since we want to avoid that unless it is actually
+            // going to make the focus visible...  otherwise we scroll
+            // all over the place.
+            scrollY = mScrollY;
+            // We can be called for two different situations: during a draw,
+            // to update the scroll position if the focus has changed (in which
+            // case 'rectangle' is null), or in response to a
+            // requestChildRectangleOnScreen() call (in which case 'rectangle'
+            // is non-null and we just want to scroll to whatever that
+            // rectangle is).
+            View focus = mRealFocusedView;
+            if (focus != mLastScrolledFocus) {
+                // If the focus has changed, then ignore any requests to scroll
+                // to a rectangle; first we want to make sure the entire focus
+                // view is visible.
+                rectangle = null;
+            }
+            if (DEBUG_INPUT_RESIZE) Log.v(TAG, "Eval scroll: focus=" + focus
+                    + " rectangle=" + rectangle + " ci=" + ci
+                    + " vi=" + vi);
+            if (focus == mLastScrolledFocus && !mScrollMayChange
+                    && rectangle == null) {
+                // Optimization: if the focus hasn't changed since last
+                // time, and no layout has happened, then just leave things
+                // as they are.
+                if (DEBUG_INPUT_RESIZE) Log.v(TAG, "Keeping scroll y="
+                        + mScrollY + " vi=" + vi.toShortString());
+            } else if (focus != null) {
+                // We need to determine if the currently focused view is
+                // within the visible part of the window and, if not, apply
+                // a pan so it can be seen.
+                mLastScrolledFocus = focus;
+                mScrollMayChange = false;
+                if (DEBUG_INPUT_RESIZE) Log.v(TAG, "Need to scroll?");
+                // Try to find the rectangle from the focus view.
+                if (focus.getGlobalVisibleRect(mVisRect, null)) {
+                    if (DEBUG_INPUT_RESIZE) Log.v(TAG, "Root w="
+                            + mView.getWidth() + " h=" + mView.getHeight()
+                            + " ci=" + ci.toShortString()
+                            + " vi=" + vi.toShortString());
+                    if (rectangle == null) {
+                        focus.getFocusedRect(mTempRect);
+                        if (DEBUG_INPUT_RESIZE) Log.v(TAG, "Focus " + focus
+                                + ": focusRect=" + mTempRect.toShortString());
+                        ((ViewGroup) mView).offsetDescendantRectToMyCoords(
+                                focus, mTempRect);
+                        if (DEBUG_INPUT_RESIZE) Log.v(TAG,
+                                "Focus in window: focusRect="
+                                + mTempRect.toShortString()
+                                + " visRect=" + mVisRect.toShortString());
+                    } else {
+                        mTempRect.set(rectangle);
+                        if (DEBUG_INPUT_RESIZE) Log.v(TAG,
+                                "Request scroll to rect: "
+                                + mTempRect.toShortString()
+                                + " visRect=" + mVisRect.toShortString());
+                    }
+                    if (mTempRect.intersect(mVisRect)) {
+                        if (DEBUG_INPUT_RESIZE) Log.v(TAG,
+                                "Focus window visible rect: "
+                                + mTempRect.toShortString());
+                        if (mTempRect.height() >
+                                (mView.getHeight()-vi.top-vi.bottom)) {
+                            // If the focus simply is not going to fit, then
+                            // best is probably just to leave things as-is.
+                            if (DEBUG_INPUT_RESIZE) Log.v(TAG,
+                                    "Too tall; leaving scrollY=" + scrollY);
+                        } else if ((mTempRect.top-scrollY) < vi.top) {
+                            scrollY -= vi.top - (mTempRect.top-scrollY);
+                            if (DEBUG_INPUT_RESIZE) Log.v(TAG,
+                                    "Top covered; scrollY=" + scrollY);
+                        } else if ((mTempRect.bottom-scrollY)
+                                > (mView.getHeight()-vi.bottom)) {
+                            scrollY += (mTempRect.bottom-scrollY)
+                                    - (mView.getHeight()-vi.bottom);
+                            if (DEBUG_INPUT_RESIZE) Log.v(TAG,
+                                    "Bottom covered; scrollY=" + scrollY);
+                        }
+                        handled = true;
+                    }
+                }
+            }
+        }
+        
+        if (scrollY != mScrollY) {
+            if (DEBUG_INPUT_RESIZE) Log.v(TAG, "Pan scroll changed: old="
+                    + mScrollY + " , new=" + scrollY);
+            if (!immediate) {
+                if (mScroller == null) {
+                    mScroller = new Scroller(mView.getContext());
+                }
+                mScroller.startScroll(0, mScrollY, 0, scrollY-mScrollY);
+            } else if (mScroller != null) {
+                mScroller.abortAnimation();
+            }
+            mScrollY = scrollY;
+        }
+        
+        return handled;
+    }
+    
+    public void requestChildFocus(View child, View focused) {
+        checkThread();
+        if (mFocusedView != focused) {
+            mAttachInfo.mTreeObserver.dispatchOnGlobalFocusChange(mFocusedView, focused);
+            scheduleTraversals();
+        }
+        mFocusedView = mRealFocusedView = focused;
+        if (DEBUG_INPUT_RESIZE) Log.v(TAG, "Request child focus: focus now "
+                + mFocusedView);
+    }
+
+    public void clearChildFocus(View child) {
+        checkThread();
+
+        View oldFocus = mFocusedView;
+
+        if (DEBUG_INPUT_RESIZE) Log.v(TAG, "Clearing child focus");
+        mFocusedView = mRealFocusedView = null;
+        if (mView != null && !mView.hasFocus()) {
+            // If a view gets the focus, the listener will be invoked from requestChildFocus()
+            if (!mView.requestFocus(View.FOCUS_FORWARD)) {
+                mAttachInfo.mTreeObserver.dispatchOnGlobalFocusChange(oldFocus, null);
+            }
+        } else if (oldFocus != null) {
+            mAttachInfo.mTreeObserver.dispatchOnGlobalFocusChange(oldFocus, null);
+        }
+    }
+
+
+    public void focusableViewAvailable(View v) {
+        checkThread();
+
+        if (mView != null && !mView.hasFocus()) {
+            v.requestFocus();
+        } else {
+            // the one case where will transfer focus away from the current one
+            // is if the current view is a view group that prefers to give focus
+            // to its children first AND the view is a descendant of it.
+            mFocusedView = mView.findFocus();
+            boolean descendantsHaveDibsOnFocus =
+                    (mFocusedView instanceof ViewGroup) &&
+                        (((ViewGroup) mFocusedView).getDescendantFocusability() ==
+                                ViewGroup.FOCUS_AFTER_DESCENDANTS);
+            if (descendantsHaveDibsOnFocus && isViewDescendantOf(v, mFocusedView)) {
+                // If a view gets the focus, the listener will be invoked from requestChildFocus()
+                v.requestFocus();
+            }
+        }
+    }
+
+    public void recomputeViewAttributes(View child) {
+        checkThread();
+        if (mView == child) {
+            mAttachInfo.mRecomputeGlobalAttributes = true;
+            if (!mWillDrawSoon) {
+                scheduleTraversals();
+            }
+        }
+    }
+
+    void dispatchDetachedFromWindow() {
+        if (Config.LOGV) Log.v("ViewRoot", "Detaching in " + this + " of " + mSurface);
+
+        if (mView != null) {
+            mView.dispatchDetachedFromWindow();
+        }
+
+        mView = null;
+        mAttachInfo.mRootView = null;
+
+        if (mUseGL) {
+            destroyGL();
+        }
+
+        try {
+            sWindowSession.remove(mWindow);
+        } catch (RemoteException e) {
+        }
+    }
+    
+    /**
+     * Return true if child is an ancestor of parent, (or equal to the parent).
+     */
+    private static boolean isViewDescendantOf(View child, View parent) {
+        if (child == parent) {
+            return true;
+        }
+
+        final ViewParent theParent = child.getParent();
+        return (theParent instanceof ViewGroup) && isViewDescendantOf((View) theParent, parent);
+    }
+
+
+    public final static int DO_TRAVERSAL = 1000;
+    public final static int DIE = 1001;
+    public final static int RESIZED = 1002;
+    public final static int RESIZED_REPORT = 1003;
+    public final static int WINDOW_FOCUS_CHANGED = 1004;
+    public final static int DISPATCH_KEY = 1005;
+    public final static int DISPATCH_POINTER = 1006;
+    public final static int DISPATCH_TRACKBALL = 1007;
+    public final static int DISPATCH_APP_VISIBILITY = 1008;
+    public final static int DISPATCH_GET_NEW_SURFACE = 1009;
+    public final static int FINISHED_EVENT = 1010;
+    public final static int DISPATCH_KEY_FROM_IME = 1011;
+    public final static int FINISH_INPUT_CONNECTION = 1012;
+    public final static int CHECK_FOCUS = 1013;
+
+    @Override
+    public void handleMessage(Message msg) {
+        switch (msg.what) {
+        case View.AttachInfo.INVALIDATE_MSG:
+            ((View) msg.obj).invalidate();
+            break;
+        case View.AttachInfo.INVALIDATE_RECT_MSG:
+            final View.AttachInfo.InvalidateInfo info = (View.AttachInfo.InvalidateInfo) msg.obj;
+            info.target.invalidate(info.left, info.top, info.right, info.bottom);
+            info.release();
+            break;
+        case DO_TRAVERSAL:
+            if (mProfile) {
+                Debug.startMethodTracing("ViewRoot");
+            }
+
+            performTraversals();
+
+            if (mProfile) {
+                Debug.stopMethodTracing();
+                mProfile = false;
+            }
+            break;
+        case FINISHED_EVENT:
+            handleFinishedEvent(msg.arg1, msg.arg2 != 0);
+            break;
+        case DISPATCH_KEY:
+            if (LOCAL_LOGV) Log.v(
+                "ViewRoot", "Dispatching key "
+                + msg.obj + " to " + mView);
+            deliverKeyEvent((KeyEvent)msg.obj, true);
+            break;
+        case DISPATCH_POINTER:
+            MotionEvent event = (MotionEvent)msg.obj;
+
+            boolean didFinish;
+            if (event == null) {
+                try {
+                    event = sWindowSession.getPendingPointerMove(mWindow);
+                } catch (RemoteException e) {
+                }
+                didFinish = true;
+            } else {
+                didFinish = event.getAction() == MotionEvent.ACTION_OUTSIDE;
+            }
+
+            try {
+                boolean handled;
+                if (mView != null && mAdded && event != null) {
+
+                    // enter touch mode on the down
+                    boolean isDown = event.getAction() == MotionEvent.ACTION_DOWN;
+                    if (isDown) {
+                        ensureTouchMode(true);
+                    }
+                    if(Config.LOGV) {
+                        captureMotionLog("captureDispatchPointer", event);
+                    }
+                    event.offsetLocation(0, mCurScrollY);
+                    handled = mView.dispatchTouchEvent(event);
+                    if (!handled && isDown) {
+                        int edgeSlop = mViewConfiguration.getScaledEdgeSlop();
+
+                        final int edgeFlags = event.getEdgeFlags();
+                        int direction = View.FOCUS_UP;
+                        int x = (int)event.getX();
+                        int y = (int)event.getY();
+                        final int[] deltas = new int[2];
+
+                        if ((edgeFlags & MotionEvent.EDGE_TOP) != 0) {
+                            direction = View.FOCUS_DOWN;
+                            if ((edgeFlags & MotionEvent.EDGE_LEFT) != 0) {
+                                deltas[0] = edgeSlop;
+                                x += edgeSlop;
+                            } else if ((edgeFlags & MotionEvent.EDGE_RIGHT) != 0) {
+                                deltas[0] = -edgeSlop;
+                                x -= edgeSlop;
+                            }
+                        } else if ((edgeFlags & MotionEvent.EDGE_BOTTOM) != 0) {
+                            direction = View.FOCUS_UP;
+                            if ((edgeFlags & MotionEvent.EDGE_LEFT) != 0) {
+                                deltas[0] = edgeSlop;
+                                x += edgeSlop;
+                            } else if ((edgeFlags & MotionEvent.EDGE_RIGHT) != 0) {
+                                deltas[0] = -edgeSlop;
+                                x -= edgeSlop;
+                            }
+                        } else if ((edgeFlags & MotionEvent.EDGE_LEFT) != 0) {
+                            direction = View.FOCUS_RIGHT;
+                        } else if ((edgeFlags & MotionEvent.EDGE_RIGHT) != 0) {
+                            direction = View.FOCUS_LEFT;
+                        }
+
+                        if (edgeFlags != 0 && mView instanceof ViewGroup) {
+                            View nearest = FocusFinder.getInstance().findNearestTouchable(
+                                    ((ViewGroup) mView), x, y, direction, deltas);
+                            if (nearest != null) {
+                                event.offsetLocation(deltas[0], deltas[1]);
+                                event.setEdgeFlags(0);
+                                mView.dispatchTouchEvent(event);
+                            }
+                        }
+                    }
+                }
+            } finally {
+                if (!didFinish) {
+                    try {
+                        sWindowSession.finishKey(mWindow);
+                    } catch (RemoteException e) {
+                    }
+                }
+                if (event != null) {
+                    event.recycle();
+                }
+                if (LOCAL_LOGV || WATCH_POINTER) Log.i(TAG, "Done dispatching!");
+                // Let the exception fall through -- the looper will catch
+                // it and take care of the bad app for us.
+            }
+            break;
+        case DISPATCH_TRACKBALL:
+            deliverTrackballEvent((MotionEvent)msg.obj);
+            break;
+        case DISPATCH_APP_VISIBILITY:
+            handleAppVisibility(msg.arg1 != 0);
+            break;
+        case DISPATCH_GET_NEW_SURFACE:
+            handleGetNewSurface();
+            break;
+        case RESIZED:
+            Rect coveredInsets = ((Rect[])msg.obj)[0];
+            Rect visibleInsets = ((Rect[])msg.obj)[1];
+            if (mWinFrame.width() == msg.arg1 && mWinFrame.height() == msg.arg2
+                    && mPendingContentInsets.equals(coveredInsets)
+                    && mPendingVisibleInsets.equals(visibleInsets)) {
+                break;
+            }
+            // fall through...
+        case RESIZED_REPORT:
+            if (mAdded) {
+                mWinFrame.left = 0;
+                mWinFrame.right = msg.arg1;
+                mWinFrame.top = 0;
+                mWinFrame.bottom = msg.arg2;
+                mPendingContentInsets.set(((Rect[])msg.obj)[0]);
+                mPendingVisibleInsets.set(((Rect[])msg.obj)[1]);
+                if (msg.what == RESIZED_REPORT) {
+                    mReportNextDraw = true;
+                }
+                requestLayout();
+            }
+            break;
+        case WINDOW_FOCUS_CHANGED: {
+            if (mAdded) {
+                boolean hasWindowFocus = msg.arg1 != 0;
+                mAttachInfo.mHasWindowFocus = hasWindowFocus;
+                if (hasWindowFocus) {
+                    boolean inTouchMode = msg.arg2 != 0;
+                    ensureTouchModeLocally(inTouchMode);
+
+                    if (mGlWanted) {
+                        checkEglErrors();
+                        // we lost the gl context, so recreate it.
+                        if (mGlWanted && !mUseGL) {
+                            initializeGL();
+                            if (mGlCanvas != null) {
+                                mGlCanvas.setViewport(mWidth, mHeight);
+                            }
+                        }
+                    }
+                }
+                
+                mLastWasImTarget = WindowManager.LayoutParams
+                        .mayUseInputMethod(mWindowAttributes.flags);
+                
+                InputMethodManager imm = InputMethodManager.peekInstance();
+                if (mView != null) {
+                    if (hasWindowFocus && imm != null && mLastWasImTarget) {
+                        imm.startGettingWindowFocus(mView);
+                    }
+                    mView.dispatchWindowFocusChanged(hasWindowFocus);
+                }
+                
+                // Note: must be done after the focus change callbacks,
+                // so all of the view state is set up correctly.
+                if (hasWindowFocus) {
+                    if (imm != null && mLastWasImTarget) {
+                        imm.onWindowFocus(mView, mView.findFocus(),
+                                mWindowAttributes.softInputMode,
+                                !mHasHadWindowFocus, mWindowAttributes.flags);
+                    }
+                    // Clear the forward bit.  We can just do this directly, since
+                    // the window manager doesn't care about it.
+                    mWindowAttributes.softInputMode &=
+                            ~WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION;
+                    ((WindowManager.LayoutParams)mView.getLayoutParams())
+                            .softInputMode &=
+                                ~WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION;
+                    mHasHadWindowFocus = true;
+                }
+            }
+        } break;
+        case DIE:
+            dispatchDetachedFromWindow();
+            break;
+        case DISPATCH_KEY_FROM_IME:
+            if (LOCAL_LOGV) Log.v(
+                "ViewRoot", "Dispatching key "
+                + msg.obj + " from IME to " + mView);
+            deliverKeyEventToViewHierarchy((KeyEvent)msg.obj, false);
+            break;
+        case FINISH_INPUT_CONNECTION: {
+            InputMethodManager imm = InputMethodManager.peekInstance();
+            if (imm != null) {
+                imm.reportFinishInputConnection((InputConnection)msg.obj);
+            }
+        } break;
+        case CHECK_FOCUS: {
+            InputMethodManager imm = InputMethodManager.peekInstance();
+            if (imm != null) {
+                imm.checkFocus();
+            }
+        } break;
+        }
+    }
+
+    /**
+     * Something in the current window tells us we need to change the touch mode.  For
+     * example, we are not in touch mode, and the user touches the screen.
+     *
+     * If the touch mode has changed, tell the window manager, and handle it locally.
+     *
+     * @param inTouchMode Whether we want to be in touch mode.
+     * @return True if the touch mode changed and focus changed was changed as a result
+     */
+    boolean ensureTouchMode(boolean inTouchMode) {
+        if (DBG) Log.d("touchmode", "ensureTouchMode(" + inTouchMode + "), current "
+                + "touch mode is " + mAttachInfo.mInTouchMode);
+        if (mAttachInfo.mInTouchMode == inTouchMode) return false;
+
+        // tell the window manager
+        try {
+            sWindowSession.setInTouchMode(inTouchMode);
+        } catch (RemoteException e) {
+            throw new RuntimeException(e);
+        }
+
+        // handle the change
+        return ensureTouchModeLocally(inTouchMode);
+    }
+
+    /**
+     * Ensure that the touch mode for this window is set, and if it is changing,
+     * take the appropriate action.
+     * @param inTouchMode Whether we want to be in touch mode.
+     * @return True if the touch mode changed and focus changed was changed as a result
+     */
+    private boolean ensureTouchModeLocally(boolean inTouchMode) {
+        if (DBG) Log.d("touchmode", "ensureTouchModeLocally(" + inTouchMode + "), current "
+                + "touch mode is " + mAttachInfo.mInTouchMode);
+
+        if (mAttachInfo.mInTouchMode == inTouchMode) return false;
+
+        mAttachInfo.mInTouchMode = inTouchMode;
+        mAttachInfo.mTreeObserver.dispatchOnTouchModeChanged(inTouchMode);
+
+        return (inTouchMode) ? enterTouchMode() : leaveTouchMode();
+    }
+
+    private boolean enterTouchMode() {
+        if (mView != null) {
+            if (mView.hasFocus()) {
+                // note: not relying on mFocusedView here because this could
+                // be when the window is first being added, and mFocused isn't
+                // set yet.
+                final View focused = mView.findFocus();
+                if (focused != null && !focused.isFocusableInTouchMode()) {
+
+                    final ViewGroup ancestorToTakeFocus =
+                            findAncestorToTakeFocusInTouchMode(focused);
+                    if (ancestorToTakeFocus != null) {
+                        // there is an ancestor that wants focus after its descendants that
+                        // is focusable in touch mode.. give it focus
+                        return ancestorToTakeFocus.requestFocus();
+                    } else {
+                        // nothing appropriate to have focus in touch mode, clear it out
+                        mView.unFocus();
+                        mAttachInfo.mTreeObserver.dispatchOnGlobalFocusChange(focused, null);
+                        mFocusedView = null;
+                        return true;
+                    }
+                }
+            }
+        }
+        return false;
+    }
+
+
+    /**
+     * Find an ancestor of focused that wants focus after its descendants and is
+     * focusable in touch mode.
+     * @param focused The currently focused view.
+     * @return An appropriate view, or null if no such view exists.
+     */
+    private ViewGroup findAncestorToTakeFocusInTouchMode(View focused) {
+        ViewParent parent = focused.getParent();
+        while (parent instanceof ViewGroup) {
+            final ViewGroup vgParent = (ViewGroup) parent;
+            if (vgParent.getDescendantFocusability() == ViewGroup.FOCUS_AFTER_DESCENDANTS
+                    && vgParent.isFocusableInTouchMode()) {
+                return vgParent;
+            }
+            if (vgParent.isRootNamespace()) {
+                return null;
+            } else {
+                parent = vgParent.getParent();
+            }
+        }
+        return null;
+    }
+
+    private boolean leaveTouchMode() {
+        if (mView != null) {
+            if (mView.hasFocus()) {
+                // i learned the hard way to not trust mFocusedView :)
+                mFocusedView = mView.findFocus();
+                if (!(mFocusedView instanceof ViewGroup)) {
+                    // some view has focus, let it keep it
+                    return false;
+                } else if (((ViewGroup)mFocusedView).getDescendantFocusability() !=
+                        ViewGroup.FOCUS_AFTER_DESCENDANTS) {
+                    // some view group has focus, and doesn't prefer its children
+                    // over itself for focus, so let them keep it.
+                    return false;
+                }
+            }
+
+            // find the best view to give focus to in this brave new non-touch-mode
+            // world
+            final View focused = focusSearch(null, View.FOCUS_DOWN);
+            if (focused != null) {
+                return focused.requestFocus(View.FOCUS_DOWN);
+            }
+        }
+        return false;
+    }
+
+
+    private void deliverTrackballEvent(MotionEvent event) {
+        boolean didFinish;
+        if (event == null) {
+            try {
+                event = sWindowSession.getPendingTrackballMove(mWindow);
+            } catch (RemoteException e) {
+            }
+            didFinish = true;
+        } else {
+            didFinish = false;
+        }
+
+        if (DEBUG_TRACKBALL) Log.v(TAG, "Motion event:" + event);
+
+        boolean handled = false;
+        try {
+            if (event == null) {
+                handled = true;
+            } else if (mView != null && mAdded) {
+                handled = mView.dispatchTrackballEvent(event);
+                if (!handled) {
+                    // we could do something here, like changing the focus
+                    // or something?
+                }
+            }
+        } finally {
+            if (handled) {
+                if (!didFinish) {
+                    try {
+                        sWindowSession.finishKey(mWindow);
+                    } catch (RemoteException e) {
+                    }
+                }
+                if (event != null) {
+                    event.recycle();
+                }
+                // If we reach this, we delivered a trackball event to mView and
+                // mView consumed it. Because we will not translate the trackball
+                // event into a key event, touch mode will not exit, so we exit
+                // touch mode here.
+                ensureTouchMode(false);
+                //noinspection ReturnInsideFinallyBlock
+                return;
+            }
+            // Let the exception fall through -- the looper will catch
+            // it and take care of the bad app for us.
+        }
+
+        final TrackballAxis x = mTrackballAxisX;
+        final TrackballAxis y = mTrackballAxisY;
+
+        long curTime = SystemClock.uptimeMillis();
+        if ((mLastTrackballTime+MAX_TRACKBALL_DELAY) < curTime) {
+            // It has been too long since the last movement,
+            // so restart at the beginning.
+            x.reset(0);
+            y.reset(0);
+            mLastTrackballTime = curTime;
+        }
+
+        try {
+            final int action = event.getAction();
+            final int metastate = event.getMetaState();
+            switch (action) {
+                case MotionEvent.ACTION_DOWN:
+                    x.reset(2);
+                    y.reset(2);
+                    deliverKeyEvent(new KeyEvent(curTime, curTime,
+                            KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DPAD_CENTER,
+                            0, metastate), false);
+                    break;
+                case MotionEvent.ACTION_UP:
+                    x.reset(2);
+                    y.reset(2);
+                    deliverKeyEvent(new KeyEvent(curTime, curTime,
+                            KeyEvent.ACTION_UP, KeyEvent.KEYCODE_DPAD_CENTER,
+                            0, metastate), false);
+                    break;
+            }
+
+            if (DEBUG_TRACKBALL) Log.v(TAG, "TB X=" + x.position + " step="
+                    + x.step + " dir=" + x.dir + " acc=" + x.acceleration
+                    + " move=" + event.getX()
+                    + " / Y=" + y.position + " step="
+                    + y.step + " dir=" + y.dir + " acc=" + y.acceleration
+                    + " move=" + event.getY());
+            final float xOff = x.collect(event.getX(), event.getEventTime(), "X");
+            final float yOff = y.collect(event.getY(), event.getEventTime(), "Y");
+
+            // Generate DPAD events based on the trackball movement.
+            // We pick the axis that has moved the most as the direction of
+            // the DPAD.  When we generate DPAD events for one axis, then the
+            // other axis is reset -- we don't want to perform DPAD jumps due
+            // to slight movements in the trackball when making major movements
+            // along the other axis.
+            int keycode = 0;
+            int movement = 0;
+            float accel = 1;
+            if (xOff > yOff) {
+                movement = x.generate((2/event.getXPrecision()));
+                if (movement != 0) {
+                    keycode = movement > 0 ? KeyEvent.KEYCODE_DPAD_RIGHT
+                            : KeyEvent.KEYCODE_DPAD_LEFT;
+                    accel = x.acceleration;
+                    y.reset(2);
+                }
+            } else if (yOff > 0) {
+                movement = y.generate((2/event.getYPrecision()));
+                if (movement != 0) {
+                    keycode = movement > 0 ? KeyEvent.KEYCODE_DPAD_DOWN
+                            : KeyEvent.KEYCODE_DPAD_UP;
+                    accel = y.acceleration;
+                    x.reset(2);
+                }
+            }
+
+            if (keycode != 0) {
+                if (movement < 0) movement = -movement;
+                int accelMovement = (int)(movement * accel);
+                if (DEBUG_TRACKBALL) Log.v(TAG, "Move: movement=" + movement
+                        + " accelMovement=" + accelMovement
+                        + " accel=" + accel);
+                if (accelMovement > movement) {
+                    if (DEBUG_TRACKBALL) Log.v("foo", "Delivering fake DPAD: "
+                            + keycode);
+                    movement--;
+                    deliverKeyEvent(new KeyEvent(curTime, curTime,
+                            KeyEvent.ACTION_MULTIPLE, keycode,
+                            accelMovement-movement, metastate), false);
+                }
+                while (movement > 0) {
+                    if (DEBUG_TRACKBALL) Log.v("foo", "Delivering fake DPAD: "
+                            + keycode);
+                    movement--;
+                    curTime = SystemClock.uptimeMillis();
+                    deliverKeyEvent(new KeyEvent(curTime, curTime,
+                            KeyEvent.ACTION_DOWN, keycode, 0, event.getMetaState()), false);
+                    deliverKeyEvent(new KeyEvent(curTime, curTime,
+                            KeyEvent.ACTION_UP, keycode, 0, metastate), false);
+                }
+                mLastTrackballTime = curTime;
+            }
+        } finally {
+            if (!didFinish) {
+                try {
+                    sWindowSession.finishKey(mWindow);
+                } catch (RemoteException e) {
+                }
+                if (event != null) {
+                    event.recycle();
+                }
+            }
+            // Let the exception fall through -- the looper will catch
+            // it and take care of the bad app for us.
+        }
+    }
+
+    /**
+     * @param keyCode The key code
+     * @return True if the key is directional.
+     */
+    static boolean isDirectional(int keyCode) {
+        switch (keyCode) {
+        case KeyEvent.KEYCODE_DPAD_LEFT:
+        case KeyEvent.KEYCODE_DPAD_RIGHT:
+        case KeyEvent.KEYCODE_DPAD_UP:
+        case KeyEvent.KEYCODE_DPAD_DOWN:
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Returns true if this key is a keyboard key.
+     * @param keyEvent The key event.
+     * @return whether this key is a keyboard key.
+     */
+    private static boolean isKeyboardKey(KeyEvent keyEvent) {
+      final int convertedKey = keyEvent.getUnicodeChar();
+        return convertedKey > 0;
+    }
+
+
+
+    /**
+     * See if the key event means we should leave touch mode (and leave touch
+     * mode if so).
+     * @param event The key event.
+     * @return Whether this key event should be consumed (meaning the act of
+     *   leaving touch mode alone is considered the event).
+     */
+    private boolean checkForLeavingTouchModeAndConsume(KeyEvent event) {
+        if (event.getAction() != KeyEvent.ACTION_DOWN) {
+            return false;
+        }
+        if ((event.getFlags()&KeyEvent.FLAG_KEEP_TOUCH_MODE) != 0) {
+            return false;
+        }
+
+        // only relevant if we are in touch mode
+        if (!mAttachInfo.mInTouchMode) {
+            return false;
+        }
+
+        // if something like an edit text has focus and the user is typing,
+        // leave touch mode
+        //
+        // note: the condition of not being a keyboard key is kind of a hacky
+        // approximation of whether we think the focused view will want the
+        // key; if we knew for sure whether the focused view would consume
+        // the event, that would be better.
+        if (isKeyboardKey(event) && mView != null && mView.hasFocus()) {
+            mFocusedView = mView.findFocus();
+            if ((mFocusedView instanceof ViewGroup)
+                    && ((ViewGroup) mFocusedView).getDescendantFocusability() ==
+                    ViewGroup.FOCUS_AFTER_DESCENDANTS) {
+                // something has focus, but is holding it weakly as a container
+                return false;
+            }
+            if (ensureTouchMode(false)) {
+                throw new IllegalStateException("should not have changed focus "
+                        + "when leaving touch mode while a view has focus.");
+            }
+            return false;
+        }
+
+        if (isDirectional(event.getKeyCode())) {
+            // no view has focus, so we leave touch mode (and find something
+            // to give focus to).  the event is consumed if we were able to
+            // find something to give focus to.
+            return ensureTouchMode(false);
+        }
+        return false;
+    }
+
+    /**
+     * log motion events 
+     */
+    private static void captureMotionLog(String subTag, MotionEvent ev) {
+        //check dynamic switch        
+        if (ev == null ||
+                SystemProperties.getInt(ViewDebug.SYSTEM_PROPERTY_CAPTURE_EVENT, 0) == 0) {
+            return;
+        } 
+        
+        StringBuilder sb = new StringBuilder(subTag + ": ");        
+        sb.append(ev.getDownTime()).append(',');        
+        sb.append(ev.getEventTime()).append(',');        
+        sb.append(ev.getAction()).append(',');        
+        sb.append(ev.getX()).append(',');        
+        sb.append(ev.getY()).append(',');        
+        sb.append(ev.getPressure()).append(',');        
+        sb.append(ev.getSize()).append(',');        
+        sb.append(ev.getMetaState()).append(',');        
+        sb.append(ev.getXPrecision()).append(',');        
+        sb.append(ev.getYPrecision()).append(',');        
+        sb.append(ev.getDeviceId()).append(',');        
+        sb.append(ev.getEdgeFlags());
+        Log.d(TAG, sb.toString());        
+    }
+    /**
+     * log motion events 
+     */
+    private static void captureKeyLog(String subTag, KeyEvent ev) {
+        //check dynamic switch                
+        if (ev == null || 
+                SystemProperties.getInt(ViewDebug.SYSTEM_PROPERTY_CAPTURE_EVENT, 0) == 0) {
+            return;
+        }
+        StringBuilder sb = new StringBuilder(subTag + ": ");        
+        sb.append(ev.getDownTime()).append(',');
+        sb.append(ev.getEventTime()).append(',');
+        sb.append(ev.getAction()).append(',');
+        sb.append(ev.getKeyCode()).append(',');        
+        sb.append(ev.getRepeatCount()).append(',');
+        sb.append(ev.getMetaState()).append(',');
+        sb.append(ev.getDeviceId()).append(',');
+        sb.append(ev.getScanCode());
+        Log.d(TAG, sb.toString());        
+    }    
+
+    int enqueuePendingEvent(Object event, boolean sendDone) {
+        int seq = mPendingEventSeq+1;
+        if (seq < 0) seq = 0;
+        mPendingEventSeq = seq;
+        mPendingEvents.put(seq, event);
+        return sendDone ? seq : -seq;
+    }
+
+    Object retrievePendingEvent(int seq) {
+        if (seq < 0) seq = -seq;
+        Object event = mPendingEvents.get(seq);
+        if (event != null) {
+            mPendingEvents.remove(seq);
+        }
+        return event;
+    }
+    
+    private void deliverKeyEvent(KeyEvent event, boolean sendDone) {
+        // If mView is null, we just consume the key event because it doesn't
+        // make sense to do anything else with it.
+        boolean handled = mView != null
+                ? mView.dispatchKeyEventPreIme(event) : true;
+        if (handled) {
+            if (sendDone) {
+                if (LOCAL_LOGV) Log.v(
+                    "ViewRoot", "Telling window manager key is finished");
+                try {
+                    sWindowSession.finishKey(mWindow);
+                } catch (RemoteException e) {
+                }
+            }
+            return;
+        }
+        // If it is possible for this window to interact with the input
+        // method window, then we want to first dispatch our key events
+        // to the input method.
+        if (mLastWasImTarget) {
+            InputMethodManager imm = InputMethodManager.peekInstance();
+            if (imm != null && mView != null) {
+                int seq = enqueuePendingEvent(event, sendDone);
+                if (DEBUG_IMF) Log.v(TAG, "Sending key event to IME: seq="
+                        + seq + " event=" + event);
+                imm.dispatchKeyEvent(mView.getContext(), seq, event,
+                        mInputMethodCallback);
+                return;
+            }
+        }
+        deliverKeyEventToViewHierarchy(event, sendDone);
+    }
+
+    void handleFinishedEvent(int seq, boolean handled) {
+        final KeyEvent event = (KeyEvent)retrievePendingEvent(seq);
+        if (DEBUG_IMF) Log.v(TAG, "IME finished event: seq=" + seq
+                + " handled=" + handled + " event=" + event);
+        if (event != null) {
+            final boolean sendDone = seq >= 0;
+            if (!handled) {
+                deliverKeyEventToViewHierarchy(event, sendDone);
+                return;
+            } else if (sendDone) {
+                if (LOCAL_LOGV) Log.v(
+                        "ViewRoot", "Telling window manager key is finished");
+                try {
+                    sWindowSession.finishKey(mWindow);
+                } catch (RemoteException e) {
+                }
+            } else {
+                Log.w("ViewRoot", "handleFinishedEvent(seq=" + seq
+                        + " handled=" + handled + " ev=" + event
+                        + ") neither delivering nor finishing key");
+            }
+        }
+    }
+    
+    private void deliverKeyEventToViewHierarchy(KeyEvent event, boolean sendDone) {
+        try {
+            if (mView != null && mAdded) {
+                final int action = event.getAction();
+                boolean isDown = (action == KeyEvent.ACTION_DOWN);
+
+                if (checkForLeavingTouchModeAndConsume(event)) {
+                    return;
+                }                
+                
+                if (Config.LOGV) {
+                    captureKeyLog("captureDispatchKeyEvent", event);
+                }
+                boolean keyHandled = mView.dispatchKeyEvent(event);
+
+                if (!keyHandled && isDown) {
+                    int direction = 0;
+                    switch (event.getKeyCode()) {
+                    case KeyEvent.KEYCODE_DPAD_LEFT:
+                        direction = View.FOCUS_LEFT;
+                        break;
+                    case KeyEvent.KEYCODE_DPAD_RIGHT:
+                        direction = View.FOCUS_RIGHT;
+                        break;
+                    case KeyEvent.KEYCODE_DPAD_UP:
+                        direction = View.FOCUS_UP;
+                        break;
+                    case KeyEvent.KEYCODE_DPAD_DOWN:
+                        direction = View.FOCUS_DOWN;
+                        break;
+                    }
+
+                    if (direction != 0) {
+
+                        View focused = mView != null ? mView.findFocus() : null;
+                        if (focused != null) {
+                            View v = focused.focusSearch(direction);
+                            boolean focusPassed = false;
+                            if (v != null && v != focused) {
+                                // do the math the get the interesting rect
+                                // of previous focused into the coord system of
+                                // newly focused view
+                                focused.getFocusedRect(mTempRect);
+                                ((ViewGroup) mView).offsetDescendantRectToMyCoords(focused, mTempRect);
+                                ((ViewGroup) mView).offsetRectIntoDescendantCoords(v, mTempRect);
+                                focusPassed = v.requestFocus(direction, mTempRect);
+                            }
+
+                            if (!focusPassed) {
+                                mView.dispatchUnhandledMove(focused, direction);
+                            } else {
+                                playSoundEffect(SoundEffectConstants.getContantForFocusDirection(direction));
+                            }
+                        }
+                    }
+                }
+            }
+
+        } finally {
+            if (sendDone) {
+                if (LOCAL_LOGV) Log.v(
+                    "ViewRoot", "Telling window manager key is finished");
+                try {
+                    sWindowSession.finishKey(mWindow);
+                } catch (RemoteException e) {
+                }
+            }
+            // Let the exception fall through -- the looper will catch
+            // it and take care of the bad app for us.
+        }
+    }
+
+    private AudioManager getAudioManager() {
+        if (mView == null) {
+            throw new IllegalStateException("getAudioManager called when there is no mView");
+        }
+        if (mAudioManager == null) {
+            mAudioManager = (AudioManager) mView.getContext().getSystemService(Context.AUDIO_SERVICE);
+        }
+        return mAudioManager;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void playSoundEffect(int effectId) {
+        checkThread();
+
+        final AudioManager audioManager = getAudioManager();
+
+        switch (effectId) {
+            case SoundEffectConstants.CLICK:
+                audioManager.playSoundEffect(AudioManager.FX_KEY_CLICK);
+                return;
+            case SoundEffectConstants.NAVIGATION_DOWN:
+                audioManager.playSoundEffect(AudioManager.FX_FOCUS_NAVIGATION_DOWN);
+                return;
+            case SoundEffectConstants.NAVIGATION_LEFT:
+                audioManager.playSoundEffect(AudioManager.FX_FOCUS_NAVIGATION_LEFT);
+                return;
+            case SoundEffectConstants.NAVIGATION_RIGHT:
+                audioManager.playSoundEffect(AudioManager.FX_FOCUS_NAVIGATION_RIGHT);
+                return;
+            case SoundEffectConstants.NAVIGATION_UP:
+                audioManager.playSoundEffect(AudioManager.FX_FOCUS_NAVIGATION_UP);
+                return;
+            default:
+                throw new IllegalArgumentException("unknown effect id " + effectId +
+                        " not defined in " + SoundEffectConstants.class.getCanonicalName());
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public boolean performHapticFeedback(int effectId, boolean always) {
+        try {
+            return sWindowSession.performHapticFeedback(mWindow, effectId, always);
+        } catch (RemoteException e) {
+            return false;
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public View focusSearch(View focused, int direction) {
+        checkThread();
+        if (!(mView instanceof ViewGroup)) {
+            return null;
+        }
+        return FocusFinder.getInstance().findNextFocus((ViewGroup) mView, focused, direction);
+    }
+
+    public void debug() {
+        mView.debug();
+    }
+
+    public void die(boolean immediate) {
+        checkThread();
+        if (Config.LOGV) Log.v("ViewRoot", "DIE in " + this + " of " + mSurface);
+        synchronized (this) {
+            if (mAdded && !mFirst) {
+                int viewVisibility = mView.getVisibility();
+                boolean viewVisibilityChanged = mViewVisibility != viewVisibility;
+                if (mWindowAttributesChanged || viewVisibilityChanged) {
+                    // If layout params have been changed, first give them
+                    // to the window manager to make sure it has the correct
+                    // animation info.
+                    try {
+                        if ((sWindowSession.relayout(
+                                    mWindow, mWindowAttributes,
+                                    mView.mMeasuredWidth, mView.mMeasuredHeight,
+                                    viewVisibility, false, mWinFrame, mPendingContentInsets,
+                                    mPendingVisibleInsets, mSurface)
+                                &WindowManagerImpl.RELAYOUT_FIRST_TIME) != 0) {
+                            sWindowSession.finishDrawing(mWindow);
+                        }
+                    } catch (RemoteException e) {
+                    }
+                }
+
+                mSurface = null;
+            }
+            if (mAdded) {
+                mAdded = false;
+                if (immediate) {
+                    dispatchDetachedFromWindow();
+                } else if (mView != null) {
+                    sendEmptyMessage(DIE);
+                }
+            }
+        }
+    }
+
+    public void dispatchFinishedEvent(int seq, boolean handled) {
+        Message msg = obtainMessage(FINISHED_EVENT);
+        msg.arg1 = seq;
+        msg.arg2 = handled ? 1 : 0;
+        sendMessage(msg);
+    }
+    
+    public void dispatchResized(int w, int h, Rect coveredInsets,
+            Rect visibleInsets, boolean reportDraw) {
+        if (DEBUG_LAYOUT) Log.v(TAG, "Resizing " + this + ": w=" + w
+                + " h=" + h + " coveredInsets=" + coveredInsets.toShortString()
+                + " visibleInsets=" + visibleInsets.toShortString()
+                + " reportDraw=" + reportDraw);
+        Message msg = obtainMessage(reportDraw ? RESIZED_REPORT :RESIZED);
+        msg.arg1 = w;
+        msg.arg2 = h;
+        msg.obj = new Rect[] { new Rect(coveredInsets), new Rect(visibleInsets) };
+        sendMessage(msg);
+    }
+
+    public void dispatchKey(KeyEvent event) {
+        if (event.getAction() == KeyEvent.ACTION_DOWN) {
+            //noinspection ConstantConditions
+            if (false && event.getKeyCode() == KeyEvent.KEYCODE_CAMERA) {
+                if (Config.LOGD) Log.d("keydisp",
+                        "===================================================");
+                if (Config.LOGD) Log.d("keydisp", "Focused view Hierarchy is:");
+                debug();
+
+                if (Config.LOGD) Log.d("keydisp",
+                        "===================================================");
+            }
+        }
+
+        Message msg = obtainMessage(DISPATCH_KEY);
+        msg.obj = event;
+
+        if (LOCAL_LOGV) Log.v(
+            "ViewRoot", "sending key " + event + " to " + mView);
+
+        sendMessageAtTime(msg, event.getEventTime());
+    }
+
+    public void dispatchPointer(MotionEvent event, long eventTime) {
+        Message msg = obtainMessage(DISPATCH_POINTER);
+        msg.obj = event;
+        sendMessageAtTime(msg, eventTime);
+    }
+
+    public void dispatchTrackball(MotionEvent event, long eventTime) {
+        Message msg = obtainMessage(DISPATCH_TRACKBALL);
+        msg.obj = event;
+        sendMessageAtTime(msg, eventTime);
+    }
+
+    public void dispatchAppVisibility(boolean visible) {
+        Message msg = obtainMessage(DISPATCH_APP_VISIBILITY);
+        msg.arg1 = visible ? 1 : 0;
+        sendMessage(msg);
+    }
+
+    public void dispatchGetNewSurface() {
+        Message msg = obtainMessage(DISPATCH_GET_NEW_SURFACE);
+        sendMessage(msg);
+    }
+
+    public void windowFocusChanged(boolean hasFocus, boolean inTouchMode) {
+        Message msg = Message.obtain();
+        msg.what = WINDOW_FOCUS_CHANGED;
+        msg.arg1 = hasFocus ? 1 : 0;
+        msg.arg2 = inTouchMode ? 1 : 0;
+        sendMessage(msg);
+    }
+
+    public boolean showContextMenuForChild(View originalView) {
+        return false;
+    }
+
+    public void createContextMenu(ContextMenu menu) {
+    }
+
+    public void childDrawableStateChanged(View child) {
+    }
+
+    protected Rect getWindowFrame() {
+        return mWinFrame;
+    }
+
+    void checkThread() {
+        if (mThread != Thread.currentThread()) {
+            throw new CalledFromWrongThreadException(
+                    "Only the original thread that created a view hierarchy can touch its views.");
+        }
+    }
+
+    public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
+        // ViewRoot never intercepts touch event, so this can be a no-op
+    }
+
+    public boolean requestChildRectangleOnScreen(View child, Rect rectangle,
+            boolean immediate) {
+        return scrollToRectOrFocus(rectangle, immediate);
+    }
+    
+    static class InputMethodCallback extends IInputMethodCallback.Stub {
+        private WeakReference<ViewRoot> mViewRoot;
+
+        public InputMethodCallback(ViewRoot viewRoot) {
+            mViewRoot = new WeakReference<ViewRoot>(viewRoot);
+        }
+        
+        public void finishedEvent(int seq, boolean handled) {
+            final ViewRoot viewRoot = mViewRoot.get();
+            if (viewRoot != null) {
+                viewRoot.dispatchFinishedEvent(seq, handled);
+            }
+        }
+
+        public void sessionCreated(IInputMethodSession session) throws RemoteException {
+            // Stub -- not for use in the client.
+        }
+    }
+    
+    static class W extends IWindow.Stub {
+        private WeakReference<ViewRoot> mViewRoot;
+
+        public W(ViewRoot viewRoot) {
+            mViewRoot = new WeakReference<ViewRoot>(viewRoot);
+        }
+
+        public void resized(int w, int h, Rect coveredInsets,
+                Rect visibleInsets, boolean reportDraw) {
+            final ViewRoot viewRoot = mViewRoot.get();
+            if (viewRoot != null) {
+                viewRoot.dispatchResized(w, h, coveredInsets,
+                        visibleInsets, reportDraw);
+            }
+        }
+
+        public void dispatchKey(KeyEvent event) {
+            final ViewRoot viewRoot = mViewRoot.get();
+            if (viewRoot != null) {
+                viewRoot.dispatchKey(event);
+            } else {
+                Log.w("ViewRoot.W", "Key event " + event + " but no ViewRoot available!");
+            }
+        }
+
+        public void dispatchPointer(MotionEvent event, long eventTime) {
+            final ViewRoot viewRoot = mViewRoot.get();
+            if (viewRoot != null) {
+                viewRoot.dispatchPointer(event, eventTime);
+            }
+        }
+
+        public void dispatchTrackball(MotionEvent event, long eventTime) {
+            final ViewRoot viewRoot = mViewRoot.get();
+            if (viewRoot != null) {
+                viewRoot.dispatchTrackball(event, eventTime);
+            }
+        }
+
+        public void dispatchAppVisibility(boolean visible) {
+            final ViewRoot viewRoot = mViewRoot.get();
+            if (viewRoot != null) {
+                viewRoot.dispatchAppVisibility(visible);
+            }
+        }
+
+        public void dispatchGetNewSurface() {
+            final ViewRoot viewRoot = mViewRoot.get();
+            if (viewRoot != null) {
+                viewRoot.dispatchGetNewSurface();
+            }
+        }
+
+        public void windowFocusChanged(boolean hasFocus, boolean inTouchMode) {
+            final ViewRoot viewRoot = mViewRoot.get();
+            if (viewRoot != null) {
+                viewRoot.windowFocusChanged(hasFocus, inTouchMode);
+            }
+        }
+
+        private static int checkCallingPermission(String permission) {
+            if (!Process.supportsProcesses()) {
+                return PackageManager.PERMISSION_GRANTED;
+            }
+
+            try {
+                return ActivityManagerNative.getDefault().checkPermission(
+                        permission, Binder.getCallingPid(), Binder.getCallingUid());
+            } catch (RemoteException e) {
+                return PackageManager.PERMISSION_DENIED;
+            }
+        }
+
+        public void executeCommand(String command, String parameters, ParcelFileDescriptor out) {
+            final ViewRoot viewRoot = mViewRoot.get();
+            if (viewRoot != null) {
+                final View view = viewRoot.mView;
+                if (view != null) {
+                    if (checkCallingPermission(Manifest.permission.DUMP) !=
+                            PackageManager.PERMISSION_GRANTED) {
+                        throw new SecurityException("Insufficient permissions to invoke"
+                                + " executeCommand() from pid=" + Binder.getCallingPid()
+                                + ", uid=" + Binder.getCallingUid());
+                    }
+
+                    OutputStream clientStream = null;
+                    try {
+                        clientStream = new ParcelFileDescriptor.AutoCloseOutputStream(out);
+                        ViewDebug.dispatchCommand(view, command, parameters, clientStream);
+                    } catch (IOException e) {
+                        e.printStackTrace();
+                    } finally {
+                        if (clientStream != null) {
+                            try {
+                                clientStream.close();
+                            } catch (IOException e) {
+                                e.printStackTrace();
+                            }
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Maintains state information for a single trackball axis, generating
+     * discrete (DPAD) movements based on raw trackball motion.
+     */
+    static final class TrackballAxis {
+        /**
+         * The maximum amount of acceleration we will apply.
+         */
+        static final float MAX_ACCELERATION = 20;
+        
+        /**
+         * The maximum amount of time (in milliseconds) between events in order
+         * for us to consider the user to be doing fast trackball movements,
+         * and thus apply an acceleration.
+         */
+        static final long FAST_MOVE_TIME = 150;
+        
+        /**
+         * Scaling factor to the time (in milliseconds) between events to how
+         * much to multiple/divide the current acceleration.  When movement
+         * is < FAST_MOVE_TIME this multiplies the acceleration; when >
+         * FAST_MOVE_TIME it divides it.
+         */
+        static final float ACCEL_MOVE_SCALING_FACTOR = (1.0f/40);
+        
+        float position;
+        float absPosition;
+        float acceleration = 1;
+        long lastMoveTime = 0;
+        int step;
+        int dir;
+        int nonAccelMovement;
+
+        void reset(int _step) {
+            position = 0;
+            acceleration = 1;
+            lastMoveTime = 0;
+            step = _step;
+            dir = 0;
+        }
+
+        /**
+         * Add trackball movement into the state.  If the direction of movement
+         * has been reversed, the state is reset before adding the
+         * movement (so that you don't have to compensate for any previously
+         * collected movement before see the result of the movement in the
+         * new direction).
+         *
+         * @return Returns the absolute value of the amount of movement
+         * collected so far.
+         */
+        float collect(float off, long time, String axis) {
+            long normTime;
+            if (off > 0) {
+                normTime = (long)(off * FAST_MOVE_TIME);
+                if (dir < 0) {
+                    if (DEBUG_TRACKBALL) Log.v(TAG, axis + " reversed to positive!");
+                    position = 0;
+                    step = 0;
+                    acceleration = 1;
+                    lastMoveTime = 0;
+                }
+                dir = 1;
+            } else if (off < 0) {
+                normTime = (long)((-off) * FAST_MOVE_TIME);
+                if (dir > 0) {
+                    if (DEBUG_TRACKBALL) Log.v(TAG, axis + " reversed to negative!");
+                    position = 0;
+                    step = 0;
+                    acceleration = 1;
+                    lastMoveTime = 0;
+                }
+                dir = -1;
+            } else {
+                normTime = 0;
+            }
+            
+            // The number of milliseconds between each movement that is
+            // considered "normal" and will not result in any acceleration
+            // or deceleration, scaled by the offset we have here.
+            if (normTime > 0) {
+                long delta = time - lastMoveTime;
+                lastMoveTime = time;
+                float acc = acceleration;
+                if (delta < normTime) {
+                    // The user is scrolling rapidly, so increase acceleration.
+                    float scale = (normTime-delta) * ACCEL_MOVE_SCALING_FACTOR;
+                    if (scale > 1) acc *= scale;
+                    if (DEBUG_TRACKBALL) Log.v(TAG, axis + " accelerate: off="
+                            + off + " normTime=" + normTime + " delta=" + delta
+                            + " scale=" + scale + " acc=" + acc);
+                    acceleration = acc < MAX_ACCELERATION ? acc : MAX_ACCELERATION;
+                } else {
+                    // The user is scrolling slowly, so decrease acceleration.
+                    float scale = (delta-normTime) * ACCEL_MOVE_SCALING_FACTOR;
+                    if (scale > 1) acc /= scale;
+                    if (DEBUG_TRACKBALL) Log.v(TAG, axis + " deccelerate: off="
+                            + off + " normTime=" + normTime + " delta=" + delta
+                            + " scale=" + scale + " acc=" + acc);
+                    acceleration = acc > 1 ? acc : 1;
+                }
+            }
+            position += off;
+            return (absPosition = Math.abs(position));
+        }
+
+        /**
+         * Generate the number of discrete movement events appropriate for
+         * the currently collected trackball movement.
+         *
+         * @param precision The minimum movement required to generate the
+         * first discrete movement.
+         *
+         * @return Returns the number of discrete movements, either positive
+         * or negative, or 0 if there is not enough trackball movement yet
+         * for a discrete movement.
+         */
+        int generate(float precision) {
+            int movement = 0;
+            nonAccelMovement = 0;
+            do {
+                final int dir = position >= 0 ? 1 : -1;
+                switch (step) {
+                    // If we are going to execute the first step, then we want
+                    // to do this as soon as possible instead of waiting for
+                    // a full movement, in order to make things look responsive.
+                    case 0:
+                        if (absPosition < precision) {
+                            return movement;
+                        }
+                        movement += dir;
+                        nonAccelMovement += dir;
+                        step = 1;
+                        break;
+                    // If we have generated the first movement, then we need
+                    // to wait for the second complete trackball motion before
+                    // generating the second discrete movement.
+                    case 1:
+                        if (absPosition < 2) {
+                            return movement;
+                        }
+                        movement += dir;
+                        nonAccelMovement += dir;
+                        position += dir > 0 ? -2 : 2;
+                        absPosition = Math.abs(position);
+                        step = 2;
+                        break;
+                    // After the first two, we generate discrete movements
+                    // consistently with the trackball, applying an acceleration
+                    // if the trackball is moving quickly.  This is a simple
+                    // acceleration on top of what we already compute based
+                    // on how quickly the wheel is being turned, to apply
+                    // a longer increasing acceleration to continuous movement
+                    // in one direction.
+                    default:
+                        if (absPosition < 1) {
+                            return movement;
+                        }
+                        movement += dir;
+                        position += dir >= 0 ? -1 : 1;
+                        absPosition = Math.abs(position);
+                        float acc = acceleration;
+                        acc *= 1.1f;
+                        acceleration = acc < MAX_ACCELERATION ? acc : acceleration;
+                        break;
+                }
+            } while (true);
+        }
+    }
+
+    public static final class CalledFromWrongThreadException extends AndroidRuntimeException {
+        public CalledFromWrongThreadException(String msg) {
+            super(msg);
+        }
+    }
+
+    private SurfaceHolder mHolder = new SurfaceHolder() {
+        // we only need a SurfaceHolder for opengl. it would be nice
+        // to implement everything else though, especially the callback
+        // support (opengl doesn't make use of it right now, but eventually
+        // will).
+        public Surface getSurface() {
+            return mSurface;
+        }
+
+        public boolean isCreating() {
+            return false;
+        }
+
+        public void addCallback(Callback callback) {
+        }
+
+        public void removeCallback(Callback callback) {
+        }
+
+        public void setFixedSize(int width, int height) {
+        }
+
+        public void setSizeFromLayout() {
+        }
+
+        public void setFormat(int format) {
+        }
+
+        public void setType(int type) {
+        }
+
+        public void setKeepScreenOn(boolean screenOn) {
+        }
+
+        public Canvas lockCanvas() {
+            return null;
+        }
+
+        public Canvas lockCanvas(Rect dirty) {
+            return null;
+        }
+
+        public void unlockCanvasAndPost(Canvas canvas) {
+        }
+        public Rect getSurfaceFrame() {
+            return null;
+        }
+    };
+
+    static RunQueue getRunQueue() {
+        RunQueue rq = sRunQueues.get();
+        if (rq != null) {
+            return rq;
+        }
+        rq = new RunQueue();
+        sRunQueues.set(rq);
+        return rq;
+    }
+    
+    /**
+     * @hide
+     */
+    static final class RunQueue {
+        private final ArrayList<HandlerAction> mActions = new ArrayList<HandlerAction>();
+
+        void post(Runnable action) {
+            postDelayed(action, 0);
+        }
+
+        void postDelayed(Runnable action, long delayMillis) {
+            HandlerAction handlerAction = new HandlerAction();
+            handlerAction.action = action;
+            handlerAction.delay = delayMillis;
+
+            synchronized (mActions) {
+                mActions.add(handlerAction);
+            }
+        }
+
+        void removeCallbacks(Runnable action) {
+            final HandlerAction handlerAction = new HandlerAction();
+            handlerAction.action = action;
+
+            synchronized (mActions) {
+                final ArrayList<HandlerAction> actions = mActions;
+
+                while (actions.remove(handlerAction)) {
+                    // Keep going
+                }
+            }
+        }
+
+        void executeActions(Handler handler) {
+            synchronized (mActions) {
+                final ArrayList<HandlerAction> actions = mActions;
+                final int count = actions.size();
+
+                for (int i = 0; i < count; i++) {
+                    final HandlerAction handlerAction = actions.get(i);
+                    handler.postDelayed(handlerAction.action, handlerAction.delay);
+                }
+
+                mActions.clear();
+            }
+        }
+
+        private static class HandlerAction {
+            Runnable action;
+            long delay;
+
+            @Override
+            public boolean equals(Object o) {
+                if (this == o) return true;
+                if (o == null || getClass() != o.getClass()) return false;
+
+                HandlerAction that = (HandlerAction) o;
+
+                return !(action != null ? !action.equals(that.action) : that.action != null);
+
+            }
+
+            @Override
+            public int hashCode() {
+                int result = action != null ? action.hashCode() : 0;
+                result = 31 * result + (int) (delay ^ (delay >>> 32));
+                return result;
+            }
+        }
+    }
+
+    private static native void nativeShowFPS(Canvas canvas, int durationMillis);
+
+    // inform skia to just abandon its texture cache IDs
+    // doesn't call glDeleteTextures
+    private static native void nativeAbandonGlCaches();
+}
diff --git a/core/java/android/view/ViewStub.java b/core/java/android/view/ViewStub.java
new file mode 100644
index 0000000..e159de4
--- /dev/null
+++ b/core/java/android/view/ViewStub.java
@@ -0,0 +1,279 @@
+/*
+ * 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 android.view;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Canvas;
+import android.util.AttributeSet;
+
+import com.android.internal.R;
+
+/**
+ * A ViewStub is an invisible, zero-sized View that can be used to lazily inflate
+ * layout resources at runtime.
+ *
+ * When a ViewStub is made visible, or when {@link #inflate()}  is invoked, the layout resource 
+ * is inflated. The ViewStub then replaces itself in its parent with the inflated View or Views.
+ * Therefore, the ViewStub exists in the view hierarchy until {@link #setVisibility(int)} or
+ * {@link #inflate()} is invoked.
+ *
+ * The inflated View is added to the ViewStub's parent with the ViewStub's layout
+ * parameters. Similarly, you can define/override the inflate View's id by using the
+ * ViewStub's inflatedId property. For instance:
+ *
+ * <pre>
+ *     &lt;ViewStub android:id="@+id/stub"
+ *               android:inflatedId="@+id/subTree"
+ *               android:layout="@layout/mySubTree"
+ *               android:layout_width="120dip"
+ *               android:layout_height="40dip" /&gt;
+ * </pre>
+ *
+ * The ViewStub thus defined can be found using the id "stub." After inflation of
+ * the layout resource "mySubTree," the ViewStub is removed from its parent. The
+ * View created by inflating the layout resource "mySubTree" can be found using the
+ * id "subTree," specified by the inflatedId property. The inflated View is finally
+ * assigned a width of 120dip and a height of 40dip.
+ *
+ * The preferred way to perform the inflation of the layout resource is the following:
+ *
+ * <pre>
+ *     ViewStub stub = (ViewStub) findViewById(R.id.stub);
+ *     View inflated = stub.inflate();
+ * </pre>
+ *
+ * When {@link #inflate()} is invoked, the ViewStub is replaced by the inflated View
+ * and the inflated View is returned. This lets applications get a reference to the
+ * inflated View without executing an extra findViewById().
+ *
+ * @attr ref android.R.styleable#ViewStub_inflatedId
+ * @attr ref android.R.styleable#ViewStub_layout
+ */
+public final class ViewStub extends View {
+    private int mLayoutResource = 0;
+    private int mInflatedId;
+
+    private OnInflateListener mInflateListener;
+
+    public ViewStub(Context context) {
+        initialize(context);
+    }
+
+    /**
+     * Creates a new ViewStub with the specified layout resource.
+     *
+     * @param context The application's environment.
+     * @param layoutResource The reference to a layout resource that will be inflated.
+     */
+    public ViewStub(Context context, int layoutResource) {
+        mLayoutResource = layoutResource;
+        initialize(context);
+    }
+
+    public ViewStub(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    @SuppressWarnings({"UnusedDeclaration"})
+    public ViewStub(Context context, AttributeSet attrs, int defStyle) {
+        TypedArray a = context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.ViewStub,
+                defStyle, 0);
+
+        mInflatedId = a.getResourceId(R.styleable.ViewStub_inflatedId, NO_ID);
+        mLayoutResource = a.getResourceId(R.styleable.ViewStub_layout, 0);
+
+        a.recycle();
+
+        a = context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.View, defStyle, 0);
+        mID = a.getResourceId(R.styleable.View_id, NO_ID);
+        a.recycle();
+
+        initialize(context);
+    }
+
+    private void initialize(Context context) {
+        mContext = context;
+        setVisibility(GONE);
+        setWillNotDraw(true);
+    }
+
+    /**
+     * Returns the id taken by the inflated view. If the inflated id is
+     * {@link View#NO_ID}, the inflated view keeps its original id.
+     *
+     * @return A positive integer used to identify the inflated view or
+     *         {@link #NO_ID} if the inflated view should keep its id.
+     *
+     * @see #setInflatedId(int)
+     * @attr ref android.R.styleable#ViewStub_inflatedId
+     */
+    public int getInflatedId() {
+        return mInflatedId;
+    }
+
+    /**
+     * Defines the id taken by the inflated view. If the inflated id is
+     * {@link View#NO_ID}, the inflated view keeps its original id.
+     *
+     * @param inflatedId A positive integer used to identify the inflated view or
+     *                   {@link #NO_ID} if the inflated view should keep its id.
+     *
+     * @see #getInflatedId()
+     * @attr ref android.R.styleable#ViewStub_inflatedId
+     */
+    public void setInflatedId(int inflatedId) {
+        mInflatedId = inflatedId;
+    }
+
+    /**
+     * Returns the layout resource that will be used by {@link #setVisibility(int)} or
+     * {@link #inflate()} to replace this StubbedView
+     * in its parent by another view.
+     *
+     * @return The layout resource identifier used to inflate the new View.
+     *
+     * @see #setLayoutResource(int)
+     * @see #setVisibility(int)
+     * @see #inflate()
+     * @attr ref android.R.styleable#ViewStub_layout
+     */
+    public int getLayoutResource() {
+        return mLayoutResource;
+    }
+
+    /**
+     * Specifies the layout resource to inflate when this StubbedView becomes visible or invisible
+     * or when {@link #inflate()} is invoked. The View created by inflating the layout resource is
+     * used to replace this StubbedView in its parent.
+     * 
+     * @param layoutResource A valid layout resource identifier (different from 0.)
+     * 
+     * @see #getLayoutResource()
+     * @see #setVisibility(int)
+     * @see #inflate()
+     * @attr ref android.R.styleable#ViewStub_layout
+     */
+    public void setLayoutResource(int layoutResource) {
+        mLayoutResource = layoutResource;
+    }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        setMeasuredDimension(0, 0);
+    }
+
+    @Override
+    public void draw(Canvas canvas) {
+    }
+
+    @Override
+    protected void dispatchDraw(Canvas canvas) {
+    }
+
+    /**
+     * When visibility is set to {@link #VISIBLE} or {@link #INVISIBLE},
+     * {@link #inflate()} is invoked and this StubbedView is replaced in its parent
+     * by the inflated layout resource.
+     *
+     * @param visibility One of {@link #VISIBLE}, {@link #INVISIBLE}, or {@link #GONE}.
+     *
+     * @see #inflate() 
+     */
+    @Override
+    public void setVisibility(int visibility) {
+        super.setVisibility(visibility);
+
+        if (visibility == VISIBLE || visibility == INVISIBLE) {
+            inflate();
+        }
+    }
+
+    /**
+     * Inflates the layout resource identified by {@link #getLayoutResource()}
+     * and replaces this StubbedView in its parent by the inflated layout resource.
+     *
+     * @return The inflated layout resource.
+     *
+     */
+    public View inflate() {
+        final ViewParent viewParent = getParent();
+
+        if (viewParent != null && viewParent instanceof ViewGroup) {
+            if (mLayoutResource != 0) {
+                final ViewGroup parent = (ViewGroup) viewParent;
+                final LayoutInflater factory = LayoutInflater.from(mContext);
+                final View view = factory.inflate(mLayoutResource, parent,
+                        false);
+
+                if (mInflatedId != NO_ID) {
+                    view.setId(mInflatedId);
+                }
+
+                final int index = parent.indexOfChild(this);
+                parent.removeViewInLayout(this);
+
+                final ViewGroup.LayoutParams layoutParams = getLayoutParams();
+                if (layoutParams != null) {
+                    parent.addView(view, index, layoutParams);
+                } else {
+                    parent.addView(view, index);
+                }
+
+                if (mInflateListener != null) {
+                    mInflateListener.onInflate(this, view);
+                }
+
+                return view;
+            } else {
+                throw new IllegalArgumentException("ViewStub must have a valid layoutResource");
+            }
+        } else {
+            throw new IllegalStateException("ViewStub must have a non-null ViewGroup viewParent");
+        }
+    }
+
+    /**
+     * Specifies the inflate listener to be notified after this ViewStub successfully
+     * inflated its layout resource.
+     *
+     * @param inflateListener The OnInflateListener to notify of successful inflation.
+     *
+     * @see android.view.ViewStub.OnInflateListener
+     */
+    public void setOnInflateListener(OnInflateListener inflateListener) {
+        mInflateListener = inflateListener;
+    }
+
+    /**
+     * Listener used to receive a notification after a ViewStub has successfully
+     * inflated its layout resource.
+     *
+     * @see android.view.ViewStub#setOnInflateListener(android.view.ViewStub.OnInflateListener) 
+     */
+    public static interface OnInflateListener {
+        /**
+         * Invoked after a ViewStub successfully inflated its layout resource.
+         * This method is invoked after the inflated view was added to the
+         * hierarchy but before the layout pass.
+         *
+         * @param stub The ViewStub that initiated the inflation.
+         * @param inflated The inflated View.
+         */
+        void onInflate(ViewStub stub, View inflated);
+    }
+}
diff --git a/core/java/android/view/ViewTreeObserver.java b/core/java/android/view/ViewTreeObserver.java
new file mode 100644
index 0000000..47b52e4
--- /dev/null
+++ b/core/java/android/view/ViewTreeObserver.java
@@ -0,0 +1,613 @@
+/*
+ * 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 android.view;
+
+import android.graphics.Rect;
+
+import java.util.ArrayList;
+
+/**
+ * A view tree observer is used to register listeners that can be notified of global
+ * changes in the view tree. Such global events include, but are not limited to,
+ * layout of the whole tree, beginning of the drawing pass, touch mode change....
+ *
+ * A ViewTreeObserver should never be instantiated by applications as it is provided
+ * by the views hierarchy. Refer to {@link android.view.View#getViewTreeObserver()}
+ * for more information.
+ */
+public final class ViewTreeObserver {
+    private ArrayList<OnGlobalFocusChangeListener> mOnGlobalFocusListeners;
+    private ArrayList<OnGlobalLayoutListener> mOnGlobalLayoutListeners;
+    private ArrayList<OnPreDrawListener> mOnPreDrawListeners;
+    private ArrayList<OnTouchModeChangeListener> mOnTouchModeChangeListeners;
+    private ArrayList<OnComputeInternalInsetsListener> mOnComputeInternalInsetsListeners;
+    private ArrayList<OnScrollChangedListener> mOnScrollChangedListeners;
+
+    private boolean mAlive = true;
+
+    /**
+     * Interface definition for a callback to be invoked when the focus state within
+     * the view tree changes.
+     */
+    public interface OnGlobalFocusChangeListener {
+        /**
+         * Callback method to be invoked when the focus changes in the view tree. When
+         * the view tree transitions from touch mode to non-touch mode, oldFocus is null.
+         * When the view tree transitions from non-touch mode to touch mode, newFocus is
+         * null. When focus changes in non-touch mode (without transition from or to
+         * touch mode) either oldFocus or newFocus can be null.
+         *
+         * @param oldFocus The previously focused view, if any.
+         * @param newFocus The newly focused View, if any.
+         */
+        public void onGlobalFocusChanged(View oldFocus, View newFocus);
+    }
+
+    /**
+     * Interface definition for a callback to be invoked when the global layout state
+     * or the visibility of views within the view tree changes.
+     */
+    public interface OnGlobalLayoutListener {
+        /**
+         * Callback method to be invoked when the global layout state or the visibility of views
+         * within the view tree changes
+         */
+        public void onGlobalLayout();
+    }
+
+    /**
+     * Interface definition for a callback to be invoked when the view tree is about to be drawn.
+     */
+    public interface OnPreDrawListener {
+        /**
+         * Callback method to be invoked when the view tree is about to be drawn. At this point, all
+         * views in the tree have been measured and given a frame. Clients can use this to adjust
+         * their scroll bounds or even to request a new layout before drawing occurs.
+         *
+         * @return Return true to proceed with the current drawing pass, or false to cancel.
+         *
+         * @see android.view.View#onMeasure
+         * @see android.view.View#onLayout
+         * @see android.view.View#onDraw
+         */
+        public boolean onPreDraw();
+    }
+
+    /**
+     * Interface definition for a callback to be invoked when the touch mode changes.
+     */
+    public interface OnTouchModeChangeListener {
+        /**
+         * Callback method to be invoked when the touch mode changes.
+         *
+         * @param isInTouchMode True if the view hierarchy is now in touch mode, false  otherwise.
+         */
+        public void onTouchModeChanged(boolean isInTouchMode);
+    }
+
+    /**
+     * Interface definition for a callback to be invoked when
+     * something in the view tree has been scrolled.
+     * 
+     * @hide pending API council approval
+     */
+    public interface OnScrollChangedListener {
+        /**
+         * Callback method to be invoked when something in the view tree
+         * has been scrolled.
+         */
+        public void onScrollChanged();
+    }
+
+    /**
+     * Parameters used with OnComputeInternalInsetsListener.
+     * {@hide pending API Council approval}
+     */
+    public final static class InternalInsetsInfo {
+        /**
+         * Offsets from the frame of the window at which the content of
+         * windows behind it should be placed.
+         */
+        public final Rect contentInsets = new Rect();
+        
+        /**
+         * Offsets from the fram of the window at which windows behind it
+         * are visible.
+         */
+        public final Rect visibleInsets = new Rect();
+        
+        /**
+         * Option for {@link #setTouchableInsets(int)}: the entire window frame
+         * can be touched.
+         */
+        public static final int TOUCHABLE_INSETS_FRAME = 0;
+        
+        /**
+         * Option for {@link #setTouchableInsets(int)}: the area inside of
+         * the content insets can be touched.
+         */
+        public static final int TOUCHABLE_INSETS_CONTENT = 1;
+        
+        /**
+         * Option for {@link #setTouchableInsets(int)}: the area inside of
+         * the visible insets can be touched.
+         */
+        public static final int TOUCHABLE_INSETS_VISIBLE = 2;
+        
+        /**
+         * Set which parts of the window can be touched: either
+         * {@link #TOUCHABLE_INSETS_FRAME}, {@link #TOUCHABLE_INSETS_CONTENT},
+         * or {@link #TOUCHABLE_INSETS_VISIBLE}. 
+         */
+        public void setTouchableInsets(int val) {
+            mTouchableInsets = val;
+        }
+        
+        public int getTouchableInsets() {
+            return mTouchableInsets;
+        }
+        
+        int mTouchableInsets;
+        
+        void reset() {
+            final Rect givenContent = contentInsets;
+            final Rect givenVisible = visibleInsets;
+            givenContent.left = givenContent.top = givenContent.right
+                    = givenContent.bottom = givenVisible.left = givenVisible.top
+                    = givenVisible.right = givenVisible.bottom = 0;
+            mTouchableInsets = TOUCHABLE_INSETS_FRAME;
+        }
+        
+        @Override public boolean equals(Object o) {
+            try {
+                if (o == null) {
+                    return false;
+                }
+                InternalInsetsInfo other = (InternalInsetsInfo)o;
+                if (!contentInsets.equals(other.contentInsets)) {
+                    return false;
+                }
+                if (!visibleInsets.equals(other.visibleInsets)) {
+                    return false;
+                }
+                return mTouchableInsets == other.mTouchableInsets;
+            } catch (ClassCastException e) {
+                return false;
+            }
+        }
+        
+        void set(InternalInsetsInfo other) {
+            contentInsets.set(other.contentInsets);
+            visibleInsets.set(other.visibleInsets);
+            mTouchableInsets = other.mTouchableInsets;
+        }
+    }
+    
+    /**
+     * Interface definition for a callback to be invoked when layout has
+     * completed and the client can compute its interior insets.
+     * {@hide pending API Council approval}
+     */
+    public interface OnComputeInternalInsetsListener {
+        /**
+         * Callback method to be invoked when layout has completed and the
+         * client can compute its interior insets.
+         *
+         * @param inoutInfo Should be filled in by the implementation with
+         * the information about the insets of the window.  This is called
+         * with whatever values the previous OnComputeInternalInsetsListener
+         * returned, if there are multiple such listeners in the window.
+         */
+        public void onComputeInternalInsets(InternalInsetsInfo inoutInfo);
+    }
+
+    /**
+     * Creates a new ViewTreeObserver. This constructor should not be called
+     */
+    ViewTreeObserver() {
+    }
+
+    /**
+     * Merges all the listeners registered on the specified observer with the listeners
+     * registered on this object. After this method is invoked, the specified observer
+     * will return false in {@link #isAlive()} and should not be used anymore.
+     *
+     * @param observer The ViewTreeObserver whose listeners must be added to this observer
+     */
+    void merge(ViewTreeObserver observer) {
+        if (observer.mOnGlobalFocusListeners != null) {
+            if (mOnGlobalFocusListeners != null) {
+                mOnGlobalFocusListeners.addAll(observer.mOnGlobalFocusListeners);
+            } else {
+                mOnGlobalFocusListeners = observer.mOnGlobalFocusListeners;
+            }
+        }
+
+        if (observer.mOnGlobalLayoutListeners != null) {
+            if (mOnGlobalLayoutListeners != null) {
+                mOnGlobalLayoutListeners.addAll(observer.mOnGlobalLayoutListeners);
+            } else {
+                mOnGlobalLayoutListeners = observer.mOnGlobalLayoutListeners;
+            }
+        }
+
+        if (observer.mOnPreDrawListeners != null) {
+            if (mOnPreDrawListeners != null) {
+                mOnPreDrawListeners.addAll(observer.mOnPreDrawListeners);
+            } else {
+                mOnPreDrawListeners = observer.mOnPreDrawListeners;
+            }
+        }
+
+        if (observer.mOnTouchModeChangeListeners != null) {
+            if (mOnTouchModeChangeListeners != null) {
+                mOnTouchModeChangeListeners.addAll(observer.mOnTouchModeChangeListeners);
+            } else {
+                mOnTouchModeChangeListeners = observer.mOnTouchModeChangeListeners;
+            }
+        }
+
+        if (observer.mOnComputeInternalInsetsListeners != null) {
+            if (mOnComputeInternalInsetsListeners != null) {
+                mOnComputeInternalInsetsListeners.addAll(observer.mOnComputeInternalInsetsListeners);
+            } else {
+                mOnComputeInternalInsetsListeners = observer.mOnComputeInternalInsetsListeners;
+            }
+        }
+
+        observer.kill();
+    }
+
+    /**
+     * Register a callback to be invoked when the focus state within the view tree changes.
+     *
+     * @param listener The callback to add
+     *
+     * @throws IllegalStateException If {@link #isAlive()} returns false
+     */
+    public void addOnGlobalFocusChangeListener(OnGlobalFocusChangeListener listener) {
+        checkIsAlive();
+
+        if (mOnGlobalFocusListeners == null) {
+            mOnGlobalFocusListeners = new ArrayList<OnGlobalFocusChangeListener>();
+        }
+
+        mOnGlobalFocusListeners.add(listener);
+    }
+
+    /**
+     * Remove a previously installed focus change callback.
+     *
+     * @param victim The callback to remove
+     *
+     * @throws IllegalStateException If {@link #isAlive()} returns false
+     *
+     * @see #addOnGlobalFocusChangeListener(OnGlobalFocusChangeListener)
+     */
+    public void removeOnGlobalFocusChangeListener(OnGlobalFocusChangeListener victim) {
+        checkIsAlive();
+        if (mOnGlobalFocusListeners == null) {
+            return;
+        }
+        mOnGlobalFocusListeners.remove(victim);
+    }
+
+    /**
+     * Register a callback to be invoked when the global layout state or the visibility of views
+     * within the view tree changes
+     *
+     * @param listener The callback to add
+     *
+     * @throws IllegalStateException If {@link #isAlive()} returns false
+     */
+    public void addOnGlobalLayoutListener(OnGlobalLayoutListener listener) {
+        checkIsAlive();
+
+        if (mOnGlobalLayoutListeners == null) {
+            mOnGlobalLayoutListeners = new ArrayList<OnGlobalLayoutListener>();
+        }
+
+        mOnGlobalLayoutListeners.add(listener);
+    }
+
+    /**
+     * Remove a previously installed global layout callback
+     *
+     * @param victim The callback to remove
+     *
+     * @throws IllegalStateException If {@link #isAlive()} returns false
+     *
+     * @see #addOnGlobalLayoutListener(OnGlobalLayoutListener)
+     */
+    public void removeGlobalOnLayoutListener(OnGlobalLayoutListener victim) {
+        checkIsAlive();
+        if (mOnGlobalLayoutListeners == null) {
+            return;
+        }
+        mOnGlobalLayoutListeners.remove(victim);
+    }
+
+    /**
+     * Register a callback to be invoked when the view tree is about to be drawn
+     *
+     * @param listener The callback to add
+     *
+     * @throws IllegalStateException If {@link #isAlive()} returns false
+     */
+    public void addOnPreDrawListener(OnPreDrawListener listener) {
+        checkIsAlive();
+
+        if (mOnPreDrawListeners == null) {
+            mOnPreDrawListeners = new ArrayList<OnPreDrawListener>();
+        }
+
+        mOnPreDrawListeners.add(listener);
+    }
+
+    /**
+     * Remove a previously installed pre-draw callback
+     *
+     * @param victim The callback to remove
+     *
+     * @throws IllegalStateException If {@link #isAlive()} returns false
+     *
+     * @see #addOnPreDrawListener(OnPreDrawListener)
+     */
+    public void removeOnPreDrawListener(OnPreDrawListener victim) {
+        checkIsAlive();
+        if (mOnPreDrawListeners == null) {
+            return;
+        }
+        mOnPreDrawListeners.remove(victim);
+    }
+
+    /**
+     * Register a callback to be invoked when a view has been scrolled.
+     *
+     * @param listener The callback to add
+     *
+     * @throws IllegalStateException If {@link #isAlive()} returns false
+     *
+     * @hide pending API council approval
+     */
+    public void addOnScrollChangedListener(OnScrollChangedListener listener) {
+        checkIsAlive();
+
+        if (mOnScrollChangedListeners == null) {
+            mOnScrollChangedListeners = new ArrayList<OnScrollChangedListener>();
+        }
+
+        mOnScrollChangedListeners.add(listener);
+    }
+
+    /**
+     * Remove a previously installed scroll-changed callback
+     *
+     * @param victim The callback to remove
+     *
+     * @throws IllegalStateException If {@link #isAlive()} returns false
+     *
+     * @see #addOnScrollChangedListener(OnScrollChangedListener)
+     *
+     * @hide pending API council approval
+     */
+    public void removeOnScrollChangedListener(OnScrollChangedListener victim) {
+        checkIsAlive();
+        if (mOnScrollChangedListeners == null) {
+            return;
+        }
+        mOnScrollChangedListeners.remove(victim);
+    }
+
+    /**
+     * Register a callback to be invoked when the invoked when the touch mode changes.
+     *
+     * @param listener The callback to add
+     *
+     * @throws IllegalStateException If {@link #isAlive()} returns false
+     */
+    public void addOnTouchModeChangeListener(OnTouchModeChangeListener listener) {
+        checkIsAlive();
+
+        if (mOnTouchModeChangeListeners == null) {
+            mOnTouchModeChangeListeners = new ArrayList<OnTouchModeChangeListener>();
+        }
+
+        mOnTouchModeChangeListeners.add(listener);
+    }
+
+    /**
+     * Remove a previously installed touch mode change callback
+     *
+     * @param victim The callback to remove
+     *
+     * @throws IllegalStateException If {@link #isAlive()} returns false
+     *
+     * @see #addOnTouchModeChangeListener(OnTouchModeChangeListener)
+     */
+    public void removeOnTouchModeChangeListener(OnTouchModeChangeListener victim) {
+        checkIsAlive();
+        if (mOnTouchModeChangeListeners == null) {
+            return;
+        }
+        mOnTouchModeChangeListeners.remove(victim);
+    }
+
+    /**
+     * Register a callback to be invoked when the invoked when it is time to
+     * compute the window's internal insets.
+     *
+     * @param listener The callback to add
+     *
+     * @throws IllegalStateException If {@link #isAlive()} returns false
+     * {@hide pending API Council approval}
+     */
+    public void addOnComputeInternalInsetsListener(OnComputeInternalInsetsListener listener) {
+        checkIsAlive();
+
+        if (mOnComputeInternalInsetsListeners == null) {
+            mOnComputeInternalInsetsListeners = new ArrayList<OnComputeInternalInsetsListener>();
+        }
+
+        mOnComputeInternalInsetsListeners.add(listener);
+    }
+
+    /**
+     * Remove a previously installed internal insets computation callback
+     *
+     * @param victim The callback to remove
+     *
+     * @throws IllegalStateException If {@link #isAlive()} returns false
+     *
+     * @see #addOnComputeInternalInsetsListener(OnComputeInternalInsetsListener)
+     * {@hide pending API Council approval}
+     */
+    public void removeOnComputeInternalInsetsListener(OnComputeInternalInsetsListener victim) {
+        checkIsAlive();
+        if (mOnComputeInternalInsetsListeners == null) {
+            return;
+        }
+        mOnComputeInternalInsetsListeners.remove(victim);
+    }
+
+    private void checkIsAlive() {
+        if (!mAlive) {
+            throw new IllegalStateException("This ViewTreeObserver is not alive, call "
+                    + "getViewTreeObserver() again");
+        }
+    }
+
+    /**
+     * Indicates whether this ViewTreeObserver is alive. When an observer is not alive,
+     * any call to a method (except this one) will throw an exception.
+     *
+     * If an application keeps a long-lived reference to this ViewTreeObserver, it should
+     * always check for the result of this method before calling any other method.
+     *
+     * @return True if this object is alive and be used, false otherwise.
+     */
+    public boolean isAlive() {
+        return mAlive;
+    }
+
+    /**
+     * Marks this ViewTreeObserver as not alive. After invoking this method, invoking
+     * any other method but {@link #isAlive()} and {@link #kill()} will throw an Exception.
+     *
+     * @hide
+     */
+    private void kill() {
+        mAlive = false;
+    }
+
+    /**
+     * Notifies registered listeners that focus has changed.
+     */
+    final void dispatchOnGlobalFocusChange(View oldFocus, View newFocus) {
+        final ArrayList<OnGlobalFocusChangeListener> globaFocusListeners = mOnGlobalFocusListeners;
+        if (globaFocusListeners != null) {
+            final int count = globaFocusListeners.size();
+            for (int i = count - 1; i >= 0; i--) {
+                globaFocusListeners.get(i).onGlobalFocusChanged(oldFocus, newFocus);
+            }
+        }
+    }
+
+    /**
+     * Notifies registered listeners that a global layout happened. This can be called
+     * manually if you are forcing a layout on a View or a hierarchy of Views that are
+     * not attached to a Window or in the GONE state.
+     */
+    public final void dispatchOnGlobalLayout() {
+        final ArrayList<OnGlobalLayoutListener> globaLayoutListeners = mOnGlobalLayoutListeners;
+        if (globaLayoutListeners != null) {
+            final int count = globaLayoutListeners.size();
+            for (int i = count - 1; i >= 0; i--) {
+                globaLayoutListeners.get(i).onGlobalLayout();
+            }
+        }
+    }
+
+    /**
+     * Notifies registered listeners that the drawing pass is about to start. If a
+     * listener returns true, then the drawing pass is canceled and rescheduled. This can
+     * be called manually if you are forcing the drawing on a View or a hierarchy of Views
+     * that are not attached to a Window or in the GONE state.
+     *
+     * @return True if the current draw should be canceled and resceduled, false otherwise.
+     */
+    public final boolean dispatchOnPreDraw() {
+        boolean cancelDraw = false;
+        final ArrayList<OnPreDrawListener> preDrawListeners = mOnPreDrawListeners;
+        if (preDrawListeners != null) {
+            final int count = preDrawListeners.size();
+            for (int i = count - 1; i >= 0; i--) {
+                cancelDraw |= !preDrawListeners.get(i).onPreDraw();
+            }
+        }
+        return cancelDraw;
+    }
+
+    /**
+     * Notifies registered listeners that the touch mode has changed.
+     *
+     * @param inTouchMode True if the touch mode is now enabled, false otherwise.
+     */
+    final void dispatchOnTouchModeChanged(boolean inTouchMode) {
+        final ArrayList<OnTouchModeChangeListener> touchModeListeners = mOnTouchModeChangeListeners;
+        if (touchModeListeners != null) {
+            final int count = touchModeListeners.size();
+            for (int i = count - 1; i >= 0; i--) {
+                touchModeListeners.get(i).onTouchModeChanged(inTouchMode);
+            }
+        }
+    }
+
+    /**
+     * Notifies registered listeners that something has scrolled.
+     */
+    final void dispatchOnScrollChanged() {
+        final ArrayList<OnScrollChangedListener> listeners = mOnScrollChangedListeners;
+
+        if (listeners != null) {
+            for (OnScrollChangedListener scl : mOnScrollChangedListeners) {
+                scl.onScrollChanged();
+            }
+        }
+    }
+
+    /**
+     * Returns whether there are listeners for computing internal insets.
+     */
+    final boolean hasComputeInternalInsetsListeners() {
+        final ArrayList<OnComputeInternalInsetsListener> listeners = mOnComputeInternalInsetsListeners;
+        return (listeners != null && listeners.size() > 0);
+    }
+    
+    /**
+     * Calls all listeners to compute the current insets.
+     */
+    final void dispatchOnComputeInternalInsets(InternalInsetsInfo inoutInfo) {
+        final ArrayList<OnComputeInternalInsetsListener> listeners = mOnComputeInternalInsetsListeners;
+        if (listeners != null) {
+            final int count = listeners.size();
+            for (int i = count - 1; i >= 0; i--) {
+                listeners.get(i).onComputeInternalInsets(inoutInfo);
+            }
+        }
+    }
+}
diff --git a/core/java/android/view/VolumePanel.java b/core/java/android/view/VolumePanel.java
new file mode 100644
index 0000000..a573983
--- /dev/null
+++ b/core/java/android/view/VolumePanel.java
@@ -0,0 +1,410 @@
+/*
+ * Copyright (C) 2007 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 android.view;
+
+import android.bluetooth.HeadsetBase;
+import android.content.Context;
+import android.content.Intent;
+import android.content.res.Resources;
+import android.media.AudioManager;
+import android.media.AudioService;
+import android.media.AudioSystem;
+import android.media.ToneGenerator;
+import android.os.Handler;
+import android.os.Message;
+import android.os.Vibrator;
+import android.util.Config;
+import android.util.Log;
+import android.widget.ImageView;
+import android.widget.ProgressBar;
+import android.widget.TextView;
+import android.widget.Toast;
+
+/**
+ * Handle the volume up and down keys.
+ *
+ * This code really should be moved elsewhere.
+ *
+ * @hide
+ */
+public class VolumePanel extends Handler
+{
+    private static final String TAG = "VolumePanel";
+    private static boolean LOGD = false || Config.LOGD;
+
+    /**
+     * The delay before playing a sound. This small period exists so the user
+     * can press another key (non-volume keys, too) to have it NOT be audible.
+     * <p>
+     * PhoneWindow will implement this part.
+     */
+    public static final int PLAY_SOUND_DELAY = 300;
+
+    /**
+     * The delay before vibrating. This small period exists so if the user is
+     * moving to silent mode, it will not emit a short vibrate (it normally
+     * would since vibrate is between normal mode and silent mode using hardware
+     * keys).
+     */
+    public static final int VIBRATE_DELAY = 300;
+
+    private static final int VIBRATE_DURATION = 300;
+    private static final int BEEP_DURATION = 150;
+    private static final int MAX_VOLUME = 100;
+    private static final int FREE_DELAY = 10000;
+
+    private static final int MSG_VOLUME_CHANGED = 0;
+    private static final int MSG_FREE_RESOURCES = 1;
+    private static final int MSG_PLAY_SOUND = 2;
+    private static final int MSG_STOP_SOUNDS = 3;
+    private static final int MSG_VIBRATE = 4;
+
+    private static final int RINGTONE_VOLUME_TEXT = com.android.internal.R.string.volume_ringtone;
+    private static final int MUSIC_VOLUME_TEXT = com.android.internal.R.string.volume_music;
+    private static final int INCALL_VOLUME_TEXT = com.android.internal.R.string.volume_call;
+    private static final int ALARM_VOLUME_TEXT = com.android.internal.R.string.volume_alarm;
+    private static final int UNKNOWN_VOLUME_TEXT = com.android.internal.R.string.volume_unknown;
+    private static final int NOTIFICATION_VOLUME_TEXT =
+            com.android.internal.R.string.volume_notification;
+    private static final int BLUETOOTH_INCALL_VOLUME_TEXT =
+            com.android.internal.R.string.volume_bluetooth_call;
+
+    protected Context mContext;
+    private AudioManager mAudioManager;
+    protected AudioService mAudioService;
+
+    private final Toast mToast;
+    private final View mView;
+    private final TextView mMessage;
+    private final TextView mAdditionalMessage;
+    private final ImageView mSmallStreamIcon;
+    private final ImageView mLargeStreamIcon;
+    private final ProgressBar mLevel;
+
+    // Synchronize when accessing this
+    private ToneGenerator mToneGenerators[];
+    private Vibrator mVibrator;
+
+    public VolumePanel(Context context, AudioService volumeService) {
+        mContext = context;
+        mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
+        mAudioService = volumeService;
+        mToast = new Toast(context);
+
+        LayoutInflater inflater = (LayoutInflater) context
+                .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+        View view = mView = inflater.inflate(com.android.internal.R.layout.volume_adjust, null);
+        mMessage = (TextView) view.findViewById(com.android.internal.R.id.message);
+        mAdditionalMessage =
+                (TextView) view.findViewById(com.android.internal.R.id.additional_message);
+        mSmallStreamIcon = (ImageView) view.findViewById(com.android.internal.R.id.other_stream_icon);
+        mLargeStreamIcon = (ImageView) view.findViewById(com.android.internal.R.id.ringer_stream_icon);
+        mLevel = (ProgressBar) view.findViewById(com.android.internal.R.id.level);
+
+        mToneGenerators = new ToneGenerator[AudioSystem.getNumStreamTypes()];
+        mVibrator = new Vibrator();
+    }
+
+    public void postVolumeChanged(int streamType, int flags) {
+        if (hasMessages(MSG_VOLUME_CHANGED)) return;
+        removeMessages(MSG_FREE_RESOURCES);
+        obtainMessage(MSG_VOLUME_CHANGED, streamType, flags).sendToTarget();
+    }
+
+    /**
+     * Override this if you have other work to do when the volume changes (for
+     * example, vibrating, playing a sound, etc.). Make sure to call through to
+     * the superclass implementation.
+     */
+    protected void onVolumeChanged(int streamType, int flags) {
+
+        if (LOGD) Log.d(TAG, "onVolumeChanged(streamType: " + streamType + ", flags: " + flags + ")");
+
+        if ((flags & AudioManager.FLAG_SHOW_UI) != 0) {
+            onShowVolumeChanged(streamType, flags);
+        }
+
+        if ((flags & AudioManager.FLAG_PLAY_SOUND) != 0) {
+            removeMessages(MSG_PLAY_SOUND);
+            sendMessageDelayed(obtainMessage(MSG_PLAY_SOUND, streamType, flags), PLAY_SOUND_DELAY);
+        }
+
+        if ((flags & AudioManager.FLAG_REMOVE_SOUND_AND_VIBRATE) != 0) {
+            removeMessages(MSG_PLAY_SOUND);
+            removeMessages(MSG_VIBRATE);
+            onStopSounds();
+        }
+
+        removeMessages(MSG_FREE_RESOURCES);
+        sendMessageDelayed(obtainMessage(MSG_FREE_RESOURCES), FREE_DELAY);
+    }
+
+    protected void onShowVolumeChanged(int streamType, int flags) {
+        int index = mAudioService.getStreamVolume(streamType);
+        int message = UNKNOWN_VOLUME_TEXT;
+        int additionalMessage = 0;
+
+        if (LOGD) {
+            Log.d(TAG, "onShowVolumeChanged(streamType: " + streamType
+                    + ", flags: " + flags + "), index: " + index);
+        }
+
+        // get max volume for progress bar
+        int max = mAudioService.getStreamMaxVolume(streamType);
+
+        switch (streamType) {
+
+            case AudioManager.STREAM_RING: {
+                message = RINGTONE_VOLUME_TEXT;
+                setRingerIcon(index);
+                break;
+            }
+
+            case AudioManager.STREAM_MUSIC: {
+                message = MUSIC_VOLUME_TEXT;
+                if (mAudioManager.isBluetoothA2dpOn()) {
+                    additionalMessage =
+                        com.android.internal.R.string.volume_music_hint_playing_through_bluetooth;
+                    setLargeIcon(com.android.internal.R.drawable.ic_volume_bluetooth_ad2p);
+                } else {
+                    setSmallIcon(index);
+                }
+                break;
+            }
+
+            case AudioManager.STREAM_VOICE_CALL: {
+                /*
+                 * For in-call voice call volume, there is no inaudible volume.
+                 * Rescale the UI control so the progress bar doesn't go all
+                 * the way to zero and don't show the mute icon.
+                 */
+                index++;
+                max++;
+                message = INCALL_VOLUME_TEXT;
+                setSmallIcon(index);
+                break;
+            }
+
+            case AudioManager.STREAM_ALARM: {
+                message = ALARM_VOLUME_TEXT;
+                setSmallIcon(index);
+                break;
+            }
+
+            case AudioManager.STREAM_NOTIFICATION: {
+                message = NOTIFICATION_VOLUME_TEXT;
+                setSmallIcon(index);
+                break;
+            }
+
+            case AudioManager.STREAM_BLUETOOTH_SCO: {
+                /*
+                 * For in-call voice call volume, there is no inaudible volume.
+                 * Rescale the UI control so the progress bar doesn't go all
+                 * the way to zero and don't show the mute icon.
+                 */
+                index++;
+                max++;
+                message = BLUETOOTH_INCALL_VOLUME_TEXT;
+                setLargeIcon(com.android.internal.R.drawable.ic_volume_bluetooth_in_call);
+                break;
+            }
+        }
+
+        String messageString = Resources.getSystem().getString(message);
+        if (!mMessage.getText().equals(messageString)) {
+            mMessage.setText(messageString);
+        }
+
+        if (additionalMessage == 0) {
+            mAdditionalMessage.setVisibility(View.GONE);
+        } else {
+            mAdditionalMessage.setVisibility(View.VISIBLE);
+            mAdditionalMessage.setText(Resources.getSystem().getString(additionalMessage));
+        }
+
+        if (max != mLevel.getMax()) {
+            mLevel.setMax(max);
+        }
+        mLevel.setProgress(index);
+
+        mToast.setView(mView);
+        mToast.setDuration(Toast.LENGTH_SHORT);
+        mToast.setGravity(Gravity.TOP, 0, 0);
+        mToast.show();
+
+        // Do a little vibrate if applicable (only when going into vibrate mode)
+        if ((flags & AudioManager.FLAG_VIBRATE) != 0 &&
+                mAudioService.isStreamAffectedByRingerMode(streamType) &&
+                mAudioService.getRingerMode() == AudioManager.RINGER_MODE_VIBRATE &&
+                mAudioService.shouldVibrate(AudioManager.VIBRATE_TYPE_RINGER)) {
+            sendMessageDelayed(obtainMessage(MSG_VIBRATE), VIBRATE_DELAY);
+        }
+
+    }
+
+    protected void onPlaySound(int streamType, int flags) {
+
+        if (hasMessages(MSG_STOP_SOUNDS)) {
+            removeMessages(MSG_STOP_SOUNDS);
+            // Force stop right now
+            onStopSounds();
+        }
+
+        synchronized (this) {
+            ToneGenerator toneGen = getOrCreateToneGenerator(streamType);
+            toneGen.startTone(ToneGenerator.TONE_PROP_BEEP);
+        }
+
+        sendMessageDelayed(obtainMessage(MSG_STOP_SOUNDS), BEEP_DURATION);
+    }
+
+    protected void onStopSounds() {
+
+        synchronized (this) {
+            int numStreamTypes = AudioSystem.getNumStreamTypes();
+            for (int i = numStreamTypes - 1; i >= 0; i--) {
+                ToneGenerator toneGen = mToneGenerators[i];
+                if (toneGen != null) {
+                    toneGen.stopTone();
+                }
+            }
+        }
+    }
+
+    protected void onVibrate() {
+
+        // Make sure we ended up in vibrate ringer mode
+        if (mAudioService.getRingerMode() != AudioManager.RINGER_MODE_VIBRATE) {
+            return;
+        }
+
+        mVibrator.vibrate(VIBRATE_DURATION);
+    }
+
+    /**
+     * Lock on this VolumePanel instance as long as you use the returned ToneGenerator.
+     */
+    private ToneGenerator getOrCreateToneGenerator(int streamType) {
+        synchronized (this) {
+            if (mToneGenerators[streamType] == null) {
+                return mToneGenerators[streamType] = new ToneGenerator(streamType, MAX_VOLUME);
+            } else {
+                return mToneGenerators[streamType];
+            }
+        }
+    }
+
+    /**
+     * Makes the small icon visible, and hides the large icon.
+     *
+     * @param index The volume index, where 0 means muted.
+     */
+    private void setSmallIcon(int index) {
+        mLargeStreamIcon.setVisibility(View.GONE);
+        mSmallStreamIcon.setVisibility(View.VISIBLE);
+
+        mSmallStreamIcon.setImageResource(index == 0
+                ? com.android.internal.R.drawable.ic_volume_off_small
+                : com.android.internal.R.drawable.ic_volume_small);
+    }
+
+    /**
+     * Makes the large image view visible with the given icon.
+     *
+     * @param resId The icon to display.
+     */
+    private void setLargeIcon(int resId) {
+        mSmallStreamIcon.setVisibility(View.GONE);
+        mLargeStreamIcon.setVisibility(View.VISIBLE);
+        mLargeStreamIcon.setImageResource(resId);
+    }
+
+    /**
+     * Makes the ringer icon visible with an icon that is chosen
+     * based on the current ringer mode.
+     *
+     * @param index
+     */
+    private void setRingerIcon(int index) {
+        mSmallStreamIcon.setVisibility(View.GONE);
+        mLargeStreamIcon.setVisibility(View.VISIBLE);
+
+        int ringerMode = mAudioService.getRingerMode();
+        int icon;
+
+        if (LOGD) Log.d(TAG, "setRingerIcon(index: " + index+ "), ringerMode: " + ringerMode);
+
+        if (ringerMode == AudioManager.RINGER_MODE_SILENT) {
+            icon = com.android.internal.R.drawable.ic_volume_off;
+        } else if (ringerMode == AudioManager.RINGER_MODE_VIBRATE) {
+            icon = com.android.internal.R.drawable.ic_vibrate;
+        } else {
+            icon = com.android.internal.R.drawable.ic_volume;
+        }
+        mLargeStreamIcon.setImageResource(icon);
+    }
+
+    protected void onFreeResources() {
+        // We'll keep the views, just ditch the cached drawable and hence
+        // bitmaps
+        mSmallStreamIcon.setImageDrawable(null);
+        mLargeStreamIcon.setImageDrawable(null);
+
+        synchronized (this) {
+            for (int i = mToneGenerators.length - 1; i >= 0; i--) {
+                if (mToneGenerators[i] != null) {
+                    mToneGenerators[i].release();
+                }
+                mToneGenerators[i] = null;
+            }
+        }
+    }
+
+    @Override
+    public void handleMessage(Message msg) {
+        switch (msg.what) {
+
+            case MSG_VOLUME_CHANGED: {
+                onVolumeChanged(msg.arg1, msg.arg2);
+                break;
+            }
+
+            case MSG_FREE_RESOURCES: {
+                onFreeResources();
+                break;
+            }
+
+            case MSG_STOP_SOUNDS: {
+                onStopSounds();
+                break;
+            }
+
+            case MSG_PLAY_SOUND: {
+                onPlaySound(msg.arg1, msg.arg2);
+                break;
+            }
+
+            case MSG_VIBRATE: {
+                onVibrate();
+                break;
+            }
+
+        }
+    }
+
+}
diff --git a/core/java/android/view/Window.java b/core/java/android/view/Window.java
new file mode 100644
index 0000000..428de67
--- /dev/null
+++ b/core/java/android/view/Window.java
@@ -0,0 +1,1005 @@
+/*
+ * 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 android.view;
+
+import android.content.Context;
+import android.content.res.Configuration;
+import android.content.res.TypedArray;
+import android.graphics.PixelFormat;
+import android.graphics.drawable.Drawable;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.util.Log;
+
+/**
+ * Abstract base class for a top-level window look and behavior policy.  An
+ * instance of this class should be used as the top-level view added to the
+ * window manager. It provides standard UI policies such as a background, title
+ * area, default key processing, etc.
+ *
+ * <p>The only existing implementation of this abstract class is
+ * android.policy.PhoneWindow, which you should instantiate when needing a
+ * Window.  Eventually that class will be refactored and a factory method
+ * added for creating Window instances without knowing about a particular
+ * implementation.
+ */
+public abstract class Window {
+    /** Flag for the "options panel" feature.  This is enabled by default. */
+    public static final int FEATURE_OPTIONS_PANEL = 0;
+    /** Flag for the "no title" feature, turning off the title at the top
+     *  of the screen. */
+    public static final int FEATURE_NO_TITLE = 1;
+    /** Flag for the progress indicator feature */
+    public static final int FEATURE_PROGRESS = 2;
+    /** Flag for having an icon on the left side of the title bar */
+    public static final int FEATURE_LEFT_ICON = 3;
+    /** Flag for having an icon on the right side of the title bar */
+    public static final int FEATURE_RIGHT_ICON = 4;
+    /** Flag for indeterminate progress */
+    public static final int FEATURE_INDETERMINATE_PROGRESS = 5;
+    /** Flag for the context menu.  This is enabled by default. */
+    public static final int FEATURE_CONTEXT_MENU = 6;
+    /** Flag for custom title. You cannot combine this feature with other title features. */
+    public static final int FEATURE_CUSTOM_TITLE = 7;
+    /*  Flag for asking for an OpenGL enabled window.
+        All 2D graphics will be handled by OpenGL ES.
+        Private for now, until it is better tested (not shipping in 1.0)
+    */
+    private static final int FEATURE_OPENGL = 8;
+    /** Flag for setting the progress bar's visibility to VISIBLE */
+    public static final int PROGRESS_VISIBILITY_ON = -1;
+    /** Flag for setting the progress bar's visibility to GONE */
+    public static final int PROGRESS_VISIBILITY_OFF = -2;
+    /** Flag for setting the progress bar's indeterminate mode on */
+    public static final int PROGRESS_INDETERMINATE_ON = -3;
+    /** Flag for setting the progress bar's indeterminate mode off */
+    public static final int PROGRESS_INDETERMINATE_OFF = -4;
+    /** Starting value for the (primary) progress */
+    public static final int PROGRESS_START = 0;
+    /** Ending value for the (primary) progress */
+    public static final int PROGRESS_END = 10000;
+    /** Lowest possible value for the secondary progress */
+    public static final int PROGRESS_SECONDARY_START = 20000;
+    /** Highest possible value for the secondary progress */
+    public static final int PROGRESS_SECONDARY_END = 30000;
+    
+    /** The default features enabled */
+    @SuppressWarnings({"PointlessBitwiseExpression"})
+    protected static final int DEFAULT_FEATURES = (1 << FEATURE_OPTIONS_PANEL) |
+            (1 << FEATURE_CONTEXT_MENU);
+
+    /**
+     * The ID that the main layout in the XML layout file should have.
+     */
+    public static final int ID_ANDROID_CONTENT = com.android.internal.R.id.content;
+
+    private final Context mContext;
+    
+    private TypedArray mWindowStyle;
+    private Callback mCallback;
+    private WindowManager mWindowManager;
+    private IBinder mAppToken;
+    private String mAppName;
+    private Window mContainer;
+    private Window mActiveChild;
+    private boolean mIsActive = false;
+    private boolean mHasChildren = false;
+    private int mForcedWindowFlags = 0;
+
+    private int mFeatures = DEFAULT_FEATURES;
+    private int mLocalFeatures = DEFAULT_FEATURES;
+
+    private boolean mHaveWindowFormat = false;
+    private int mDefaultWindowFormat = PixelFormat.OPAQUE;
+
+    private boolean mHasSoftInputMode = false;
+    
+    // The current window attributes.
+    private final WindowManager.LayoutParams mWindowAttributes =
+        new WindowManager.LayoutParams();
+
+    /**
+     * API from a Window back to its caller.  This allows the client to
+     * intercept key dispatching, panels and menus, etc.
+     */
+    public interface Callback {
+        /**
+         * Called to process key events.  At the very least your
+         * implementation must call
+         * {@link android.view.Window#superDispatchKeyEvent} to do the
+         * standard key processing.
+         *
+         * @param event The key event.
+         *
+         * @return boolean Return true if this event was consumed.
+         */
+        public boolean dispatchKeyEvent(KeyEvent event);
+
+        /**
+         * Called to process touch screen events.  At the very least your
+         * implementation must call
+         * {@link android.view.Window#superDispatchTouchEvent} to do the
+         * standard touch screen processing.
+         *
+         * @param event The touch screen event.
+         *
+         * @return boolean Return true if this event was consumed.
+         */
+        public boolean dispatchTouchEvent(MotionEvent event);
+        
+        /**
+         * Called to process trackball events.  At the very least your
+         * implementation must call
+         * {@link android.view.Window#superDispatchTrackballEvent} to do the
+         * standard trackball processing.
+         *
+         * @param event The trackball event.
+         *
+         * @return boolean Return true if this event was consumed.
+         */
+        public boolean dispatchTrackballEvent(MotionEvent event);
+        
+        /**
+         * Instantiate the view to display in the panel for 'featureId'.
+         * You can return null, in which case the default content (typically
+         * a menu) will be created for you.
+         *
+         * @param featureId Which panel is being created.
+         *
+         * @return view The top-level view to place in the panel.
+         *
+         * @see #onPreparePanel
+         */
+        public View onCreatePanelView(int featureId);
+
+        /**
+         * Initialize the contents of the menu for panel 'featureId'.  This is
+         * called if onCreatePanelView() returns null, giving you a standard
+         * menu in which you can place your items.  It is only called once for
+         * the panel, the first time it is shown.
+         *
+         * <p>You can safely hold on to <var>menu</var> (and any items created
+         * from it), making modifications to it as desired, until the next
+         * time onCreatePanelMenu() is called for this feature.
+         *
+         * @param featureId The panel being created.
+         * @param menu The menu inside the panel.
+         *
+         * @return boolean You must return true for the panel to be displayed;
+         *         if you return false it will not be shown.
+         */
+        public boolean onCreatePanelMenu(int featureId, Menu menu);
+
+        /**
+         * Prepare a panel to be displayed.  This is called right before the
+         * panel window is shown, every time it is shown.
+         *
+         * @param featureId The panel that is being displayed.
+         * @param view The View that was returned by onCreatePanelView().
+         * @param menu If onCreatePanelView() returned null, this is the Menu
+         *             being displayed in the panel.
+         *
+         * @return boolean You must return true for the panel to be displayed;
+         *         if you return false it will not be shown.
+         *
+         * @see #onCreatePanelView
+         */
+        public boolean onPreparePanel(int featureId, View view, Menu menu);
+
+        /**
+         * Called when a panel's menu is opened by the user. This may also be
+         * called when the menu is changing from one type to another (for
+         * example, from the icon menu to the expanded menu).
+         * 
+         * @param featureId The panel that the menu is in.
+         * @param menu The menu that is opened.
+         * @return Return true to allow the menu to open, or false to prevent
+         *         the menu from opening.
+         */
+        public boolean onMenuOpened(int featureId, Menu menu);
+        
+        /**
+         * Called when a panel's menu item has been selected by the user.
+         *
+         * @param featureId The panel that the menu is in.
+         * @param item The menu item that was selected.
+         *
+         * @return boolean Return true to finish processing of selection, or
+         *         false to perform the normal menu handling (calling its
+         *         Runnable or sending a Message to its target Handler).
+         */
+        public boolean onMenuItemSelected(int featureId, MenuItem item);
+
+        /**
+         * This is called whenever the current window attributes change.
+         *
+
+         */
+        public void onWindowAttributesChanged(WindowManager.LayoutParams attrs);
+
+        /**
+         * This hook is called whenever the content view of the screen changes
+         * (due to a call to
+         * {@link Window#setContentView(View, android.view.ViewGroup.LayoutParams)
+         * Window.setContentView} or
+         * {@link Window#addContentView(View, android.view.ViewGroup.LayoutParams)
+         * Window.addContentView}).
+         */
+        public void onContentChanged();
+
+        /**
+         * This hook is called whenever the window focus changes.
+         *
+         * @param hasFocus Whether the window now has focus.
+         */
+        public void onWindowFocusChanged(boolean hasFocus);
+
+        /**
+         * Called when a panel is being closed.  If another logical subsequent
+         * panel is being opened (and this panel is being closed to make room for the subsequent
+         * panel), this method will NOT be called.
+         * 
+         * @param featureId The panel that is being displayed.
+         * @param menu If onCreatePanelView() returned null, this is the Menu
+         *            being displayed in the panel.
+         */
+        public void onPanelClosed(int featureId, Menu menu);
+        
+        /**
+         * Called when the user signals the desire to start a search.
+         * 
+         * @return true if search launched, false if activity refuses (blocks)
+         * 
+         * @see android.app.Activity#onSearchRequested() 
+         */
+        public boolean onSearchRequested();
+    }
+
+    public Window(Context context) {
+        mContext = context;
+    }
+
+    /**
+     * Return the Context this window policy is running in, for retrieving
+     * resources and other information.
+     *
+     * @return Context The Context that was supplied to the constructor.
+     */
+    public final Context getContext() {
+        return mContext;
+    }
+
+    /**
+     * Return the {@link android.R.styleable#Window} attributes from this
+     * window's theme.
+     */
+    public final TypedArray getWindowStyle() {
+        synchronized (this) {
+            if (mWindowStyle == null) {
+                mWindowStyle = mContext.obtainStyledAttributes(
+                        com.android.internal.R.styleable.Window);
+            }
+            return mWindowStyle;
+        }
+    }
+    
+    /**
+     * Set the container for this window.  If not set, the DecorWindow
+     * operates as a top-level window; otherwise, it negotiates with the
+     * container to display itself appropriately.
+     *
+     * @param container The desired containing Window.
+     */
+    public void setContainer(Window container) {
+        mContainer = container;
+        if (container != null) {
+            // Embedded screens never have a title.
+            mFeatures |= 1<<FEATURE_NO_TITLE;
+            mLocalFeatures |= 1<<FEATURE_NO_TITLE;
+            container.mHasChildren = true;
+        }
+    }
+
+    /**
+     * Return the container for this Window.
+     *
+     * @return Window The containing window, or null if this is a
+     *         top-level window.
+     */
+    public final Window getContainer() {
+        return mContainer;
+    }
+
+    public final boolean hasChildren() {
+        return mHasChildren;
+    }
+    
+    /**
+     * Set the window manager for use by this Window to, for example,
+     * display panels.  This is <em>not</em> used for displaying the
+     * Window itself -- that must be done by the client.
+     *
+     * @param wm The ViewManager for adding new windows.
+     */
+    public void setWindowManager(WindowManager wm,
+            IBinder appToken, String appName) {
+        mAppToken = appToken;
+        mAppName = appName;
+        if (wm == null) {
+            wm = WindowManagerImpl.getDefault();
+        }
+        mWindowManager = new LocalWindowManager(wm);
+    }
+
+    private class LocalWindowManager implements WindowManager {
+        LocalWindowManager(WindowManager wm) {
+            mWindowManager = wm;
+        }
+
+        public final void addView(View view, ViewGroup.LayoutParams params) {
+            // Let this throw an exception on a bad params.
+            WindowManager.LayoutParams wp = (WindowManager.LayoutParams)params;
+            CharSequence curTitle = wp.getTitle();
+            if (wp.type >= WindowManager.LayoutParams.FIRST_SUB_WINDOW &&
+                wp.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW) {
+                if (wp.token == null) {
+                    View decor = peekDecorView();
+                    if (decor != null) {
+                        wp.token = decor.getWindowToken();
+                    }
+                }
+                if (curTitle == null || curTitle.length() == 0) {
+                    String title;
+                    if (wp.type == WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA) {
+                        title="Media";
+                    } else if (wp.type == WindowManager.LayoutParams.TYPE_APPLICATION_PANEL) {
+                        title="Panel";
+                    } else {
+                        title=Integer.toString(wp.type);
+                    }
+                    if (mAppName != null) {
+                        title += ":" + mAppName;
+                    }
+                    wp.setTitle(title);
+                }
+            } else {
+                if (wp.token == null) {
+                    wp.token = mContainer == null ? mAppToken : mContainer.mAppToken;
+                }
+                if ((curTitle == null || curTitle.length() == 0)
+                        && mAppName != null) {
+                    wp.setTitle(mAppName);
+                }
+           }
+            if (wp.packageName == null) {
+                wp.packageName = mContext.getPackageName();
+            }
+            mWindowManager.addView(view, params);
+        }
+
+        public void updateViewLayout(View view, ViewGroup.LayoutParams params) {
+            mWindowManager.updateViewLayout(view, params);
+        }
+
+        public final void removeView(View view) {
+            mWindowManager.removeView(view);
+        }
+
+        public final void removeViewImmediate(View view) {
+            mWindowManager.removeViewImmediate(view);
+        }
+
+        public Display getDefaultDisplay() {
+            return mWindowManager.getDefaultDisplay();
+        }
+        
+        WindowManager mWindowManager;
+    }
+
+    /**
+     * Return the window manager allowing this Window to display its own
+     * windows.
+     *
+     * @return WindowManager The ViewManager.
+     */
+    public WindowManager getWindowManager() {
+        return mWindowManager;
+    }
+
+    /**
+     * Set the Callback interface for this window, used to intercept key
+     * events and other dynamic operations in the window.
+     *
+     * @param callback The desired Callback interface.
+     */
+    public void setCallback(Callback callback) {
+        mCallback = callback;
+    }
+
+    /**
+     * Return the current Callback interface for this window.
+     */
+    public final Callback getCallback() {
+        return mCallback;
+    }
+
+    /**
+     * Return whether this window is being displayed with a floating style
+     * (based on the {@link android.R.attr#windowIsFloating} attribute in
+     * the style/theme).
+     *
+     * @return Returns true if the window is configured to be displayed floating
+     * on top of whatever is behind it.
+     */
+    public abstract boolean isFloating();
+
+    /**
+     * Set the width and height layout parameters of the window.  The default
+     * for both of these is FILL_PARENT; you can change them to WRAP_CONTENT to
+     * make a window that is not full-screen.
+     *
+     * @param width The desired layout width of the window.
+     * @param height The desired layout height of the window.
+     */
+    public void setLayout(int width, int height)
+    {
+        final WindowManager.LayoutParams attrs = getAttributes();
+        attrs.width = width;
+        attrs.height = height;
+        if (mCallback != null) {
+            mCallback.onWindowAttributesChanged(attrs);
+        }
+    }
+
+    /**
+     * Set the gravity of the window, as per the Gravity constants.  This
+     * controls how the window manager is positioned in the overall window; it
+     * is only useful when using WRAP_CONTENT for the layout width or height.
+     *
+     * @param gravity The desired gravity constant.
+     *
+     * @see Gravity
+     * @see #setLayout
+     */
+    public void setGravity(int gravity)
+    {
+        final WindowManager.LayoutParams attrs = getAttributes();
+        attrs.gravity = gravity;
+        if (mCallback != null) {
+            mCallback.onWindowAttributesChanged(attrs);
+        }
+    }
+
+    /**
+     * Set the type of the window, as per the WindowManager.LayoutParams
+     * types.
+     *
+     * @param type The new window type (see WindowManager.LayoutParams).
+     */
+    public void setType(int type) {
+        final WindowManager.LayoutParams attrs = getAttributes();
+        attrs.type = type;
+        if (mCallback != null) {
+            mCallback.onWindowAttributesChanged(attrs);
+        }
+    }
+
+    /**
+     * Set the format of window, as per the PixelFormat types.  This overrides
+     * the default format that is selected by the Window based on its
+     * window decorations.
+     *
+     * @param format The new window format (see PixelFormat).  Use
+     *               PixelFormat.UNKNOWN to allow the Window to select
+     *               the format.
+     *
+     * @see PixelFormat
+     */
+    public void setFormat(int format) {
+        final WindowManager.LayoutParams attrs = getAttributes();
+        if (format != PixelFormat.UNKNOWN) {
+            attrs.format = format;
+            mHaveWindowFormat = true;
+        } else {
+            attrs.format = mDefaultWindowFormat;
+            mHaveWindowFormat = false;
+        }
+        if (mCallback != null) {
+            mCallback.onWindowAttributesChanged(attrs);
+        }
+    }
+
+    /**
+     * Specify custom animations to use for the window, as per
+     * {@link WindowManager.LayoutParams#windowAnimations
+     * WindowManager.LayoutParams.windowAnimations}.  Providing anything besides
+     * 0 here will override the animations the window would
+     * normally retrieve from its theme.
+     */
+    public void setWindowAnimations(int resId) {
+        final WindowManager.LayoutParams attrs = getAttributes();
+        attrs.windowAnimations = resId;
+        if (mCallback != null) {
+            mCallback.onWindowAttributesChanged(attrs);
+        }
+    }
+
+    /**
+     * Specify an explicit soft input mode to use for the window, as per
+     * {@link WindowManager.LayoutParams#softInputMode
+     * WindowManager.LayoutParams.softInputMode}.  Providing anything besides
+     * "unspecified" here will override the input mode the window would
+     * normally retrieve from its theme.
+     */
+    public void setSoftInputMode(int mode) {
+        final WindowManager.LayoutParams attrs = getAttributes();
+        if (mode != WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED) {
+            attrs.softInputMode = mode;
+            mHasSoftInputMode = true;
+        } else {
+            mHasSoftInputMode = false;
+        }
+        if (mCallback != null) {
+            mCallback.onWindowAttributesChanged(attrs);
+        }
+    }
+    
+    /**
+     * Convenience function to set the flag bits as specified in flags, as
+     * per {@link #setFlags}.
+     * @param flags The flag bits to be set.
+     * @see #setFlags
+     */
+    public void addFlags(int flags) {
+        setFlags(flags, flags);
+    }
+    
+    /**
+     * Convenience function to clear the flag bits as specified in flags, as
+     * per {@link #setFlags}.
+     * @param flags The flag bits to be cleared.
+     * @see #setFlags
+     */
+    public void clearFlags(int flags) {
+        setFlags(0, flags);
+    }
+
+    /**
+     * Set the flags of the window, as per the
+     * {@link WindowManager.LayoutParams WindowManager.LayoutParams}
+     * flags.
+     * 
+     * <p>Note that some flags must be set before the window decoration is
+     * created (by the first call to
+     * {@link #setContentView(View, android.view.ViewGroup.LayoutParams)} or
+     * {@link #getDecorView()}:
+     * {@link WindowManager.LayoutParams#FLAG_LAYOUT_IN_SCREEN} and
+     * {@link WindowManager.LayoutParams#FLAG_LAYOUT_INSET_DECOR}.  These
+     * will be set for you based on the {@link android.R.attr#windowIsFloating}
+     * attribute.
+     *
+     * @param flags The new window flags (see WindowManager.LayoutParams).
+     * @param mask Which of the window flag bits to modify.
+     */
+    public void setFlags(int flags, int mask) {
+        final WindowManager.LayoutParams attrs = getAttributes();
+        attrs.flags = (attrs.flags&~mask) | (flags&mask);
+        mForcedWindowFlags |= mask;
+        if (mCallback != null) {
+            mCallback.onWindowAttributesChanged(attrs);
+        }
+    }
+
+    /**
+     * Specify custom window attributes.  <strong>PLEASE NOTE:</strong> the
+     * layout params you give here should generally be from values previously
+     * retrieved with {@link #getAttributes()}; you probably do not want to
+     * blindly create and apply your own, since this will blow away any values
+     * set by the framework that you are not interested in.
+     *
+     * @param a The new window attributes, which will completely override any
+     *          current values.
+     */
+    public void setAttributes(WindowManager.LayoutParams a) {
+        mWindowAttributes.copyFrom(a);
+        if (mCallback != null) {
+            mCallback.onWindowAttributesChanged(mWindowAttributes);
+        }
+    }
+
+    /**
+     * Retrieve the current window attributes associated with this panel.
+     *
+     * @return WindowManager.LayoutParams Either the existing window
+     *         attributes object, or a freshly created one if there is none.
+     */
+    public final WindowManager.LayoutParams getAttributes() {
+        return mWindowAttributes;
+    }
+
+    /**
+     * Return the window flags that have been explicitly set by the client,
+     * so will not be modified by {@link #getDecorView}.
+     */
+    protected final int getForcedWindowFlags() {
+        return mForcedWindowFlags;
+    }
+    
+    /**
+     * Has the app specified their own soft input mode?
+     */
+    protected final boolean hasSoftInputMode() {
+        return mHasSoftInputMode;
+    }
+    
+    /**
+     * Enable extended screen features.  This must be called before
+     * setContentView().  May be called as many times as desired as long as it
+     * is before setContentView().  If not called, no extended features
+     * will be available.  You can not turn off a feature once it is requested.
+     * You canot use other title features with {@link #FEATURE_CUSTOM_TITLE}.
+     *
+     * @param featureId The desired features, defined as constants by Window.
+     * @return The features that are now set.
+     */
+    public boolean requestFeature(int featureId) {
+        final int flag = 1<<featureId;
+        mFeatures |= flag;
+        mLocalFeatures |= mContainer != null ? (flag&~mContainer.mFeatures) : flag;
+        return (mFeatures&flag) != 0;
+    }
+
+    public final void makeActive() {
+        if (mContainer != null) {
+            if (mContainer.mActiveChild != null) {
+                mContainer.mActiveChild.mIsActive = false;
+            }
+            mContainer.mActiveChild = this;
+        }
+        mIsActive = true;
+        onActive();
+    }
+
+    public final boolean isActive()
+    {
+        return mIsActive;
+    }
+
+    /**
+     * Finds a view that was identified by the id attribute from the XML that
+     * was processed in {@link android.app.Activity#onCreate}.  This will
+     * implicitly call {@link #getDecorView} for you, with all of the
+     * associated side-effects.
+     *
+     * @return The view if found or null otherwise.
+     */
+    public View findViewById(int id) {
+        return getDecorView().findViewById(id);
+    }
+
+    /**
+     * Convenience for
+     * {@link #setContentView(View, android.view.ViewGroup.LayoutParams)}
+     * to set the screen content from a layout resource.  The resource will be
+     * inflated, adding all top-level views to the screen.
+     *
+     * @param layoutResID Resource ID to be inflated.
+     * @see #setContentView(View, android.view.ViewGroup.LayoutParams)
+     */
+    public abstract void setContentView(int layoutResID);
+
+    /**
+     * Convenience for
+     * {@link #setContentView(View, android.view.ViewGroup.LayoutParams)}
+     * set the screen content to an explicit view.  This view is placed
+     * directly into the screen's view hierarchy.  It can itself be a complex
+     * view hierarhcy.
+     *
+     * @param view The desired content to display.
+     * @see #setContentView(View, android.view.ViewGroup.LayoutParams)
+     */
+    public abstract void setContentView(View view);
+
+    /**
+     * Set the screen content to an explicit view.  This view is placed
+     * directly into the screen's view hierarchy.  It can itself be a complex
+     * view hierarchy.
+     *
+     * <p>Note that calling this function "locks in" various characteristics
+     * of the window that can not, from this point forward, be changed: the
+     * features that have been requested with {@link #requestFeature(int)},
+     * and certain window flags as described in {@link #setFlags(int, int)}.
+     * 
+     * @param view The desired content to display.
+     * @param params Layout parameters for the view.
+     */
+    public abstract void setContentView(View view, ViewGroup.LayoutParams params);
+
+    /**
+     * Variation on
+     * {@link #setContentView(View, android.view.ViewGroup.LayoutParams)}
+     * to add an additional content view to the screen.  Added after any existing
+     * ones in the screen -- existing views are NOT removed.
+     *
+     * @param view The desired content to display.
+     * @param params Layout parameters for the view.
+     */
+    public abstract void addContentView(View view, ViewGroup.LayoutParams params);
+
+    /**
+     * Return the view in this Window that currently has focus, or null if
+     * there are none.  Note that this does not look in any containing
+     * Window.
+     *
+     * @return View The current View with focus or null.
+     */
+    public abstract View getCurrentFocus();
+
+    /**
+     * Quick access to the {@link LayoutInflater} instance that this Window
+     * retrieved from its Context.
+     *
+     * @return LayoutInflater The shared LayoutInflater.
+     */
+    public abstract LayoutInflater getLayoutInflater();
+
+    public abstract void setTitle(CharSequence title);
+
+    public abstract void setTitleColor(int textColor);
+
+    public abstract void openPanel(int featureId, KeyEvent event);
+
+    public abstract void closePanel(int featureId);
+
+    public abstract void togglePanel(int featureId, KeyEvent event);
+
+    public abstract boolean performPanelShortcut(int featureId,
+                                                 int keyCode,
+                                                 KeyEvent event,
+                                                 int flags);
+    public abstract boolean performPanelIdentifierAction(int featureId,
+                                                 int id,
+                                                 int flags);
+
+    public abstract void closeAllPanels();
+
+    public abstract boolean performContextMenuIdentifierAction(int id, int flags);
+
+    /**
+     * Should be called when the configuration is changed.
+     * 
+     * @param newConfig The new configuration.
+     */
+    public abstract void onConfigurationChanged(Configuration newConfig);
+    
+    /**
+     * Change the background of this window to a Drawable resource. Setting the
+     * background to null will make the window be opaque. To make the window
+     * transparent, you can use an empty drawable (for instance a ColorDrawable
+     * with the color 0 or the system drawable android:drawable/empty.)
+     * 
+     * @param resid The resource identifier of a drawable resource which will be
+     *              installed as the new background.
+     */
+    public void setBackgroundDrawableResource(int resid)
+    {
+        setBackgroundDrawable(mContext.getResources().getDrawable(resid));
+    }
+
+    /**
+     * Change the background of this window to a custom Drawable. Setting the
+     * background to null will make the window be opaque. To make the window
+     * transparent, you can use an empty drawable (for instance a ColorDrawable
+     * with the color 0 or the system drawable android:drawable/empty.)
+     *
+     * @param drawable The new Drawable to use for this window's background.
+     */
+    public abstract void setBackgroundDrawable(Drawable drawable);
+
+    /**
+     * Set the value for a drawable feature of this window, from a resource
+     * identifier.  You must have called requestFeauture(featureId) before
+     * calling this function.
+     *
+     * @see android.content.res.Resources#getDrawable(int)
+     *
+     * @param featureId The desired drawable feature to change, defined as a
+     * constant by Window.
+     * @param resId Resource identifier of the desired image.
+     */
+    public abstract void setFeatureDrawableResource(int featureId, int resId);
+
+    /**
+     * Set the value for a drawable feature of this window, from a URI. You
+     * must have called requestFeature(featureId) before calling this
+     * function.
+     *
+     * <p>The only URI currently supported is "content:", specifying an image
+     * in a content provider.
+     *
+     * @see android.widget.ImageView#setImageURI
+     *
+     * @param featureId The desired drawable feature to change. Features are
+     * constants defined by Window.
+     * @param uri The desired URI.
+     */
+    public abstract void setFeatureDrawableUri(int featureId, Uri uri);
+
+    /**
+     * Set an explicit Drawable value for feature of this window. You must
+     * have called requestFeature(featureId) before calling this function.
+     *
+     * @param featureId The desired drawable feature to change.
+     * Features are constants defined by Window.
+     * @param drawable A Drawable object to display.
+     */
+    public abstract void setFeatureDrawable(int featureId, Drawable drawable);
+
+    /**
+     * Set a custom alpha value for the given drawale feature, controlling how
+     * much the background is visible through it.
+     *
+     * @param featureId The desired drawable feature to change.
+     * Features are constants defined by Window.
+     * @param alpha The alpha amount, 0 is completely transparent and 255 is
+     *              completely opaque.
+     */
+    public abstract void setFeatureDrawableAlpha(int featureId, int alpha);
+
+    /**
+     * Set the integer value for a feature.  The range of the value depends on
+     * the feature being set.  For FEATURE_PROGRESSS, it should go from 0 to
+     * 10000. At 10000 the progress is complete and the indicator hidden.
+     *
+     * @param featureId The desired feature to change.
+     * Features are constants defined by Window.
+     * @param value The value for the feature.  The interpretation of this
+     *              value is feature-specific.
+     */
+    public abstract void setFeatureInt(int featureId, int value);
+
+    /**
+     * Request that key events come to this activity. Use this if your
+     * activity has no views with focus, but the activity still wants
+     * a chance to process key events.
+     */
+    public abstract void takeKeyEvents(boolean get);
+
+    /**
+     * Used by custom windows, such as Dialog, to pass the key press event
+     * further down the view hierarchy. Application developers should
+     * not need to implement or call this.
+     *
+     */
+    public abstract boolean superDispatchKeyEvent(KeyEvent event);
+
+    /**
+     * Used by custom windows, such as Dialog, to pass the touch screen event
+     * further down the view hierarchy. Application developers should
+     * not need to implement or call this.
+     *
+     */
+    public abstract boolean superDispatchTouchEvent(MotionEvent event);
+    
+    /**
+     * Used by custom windows, such as Dialog, to pass the trackball event
+     * further down the view hierarchy. Application developers should
+     * not need to implement or call this.
+     *
+     */
+    public abstract boolean superDispatchTrackballEvent(MotionEvent event);
+    
+    /**
+     * Retrieve the top-level window decor view (containing the standard
+     * window frame/decorations and the client's content inside of that), which
+     * can be added as a window to the window manager.
+     * 
+     * <p><em>Note that calling this function for the first time "locks in"
+     * various window characteristics as described in
+     * {@link #setContentView(View, android.view.ViewGroup.LayoutParams)}.</em></p>
+     * 
+     * @return Returns the top-level window decor view.
+     */
+    public abstract View getDecorView();
+
+    /**
+     * Retrieve the current decor view, but only if it has already been created;
+     * otherwise returns null.
+     * 
+     * @return Returns the top-level window decor or null.
+     * @see #getDecorView
+     */
+    public abstract View peekDecorView();
+
+    public abstract Bundle saveHierarchyState();
+    
+    public abstract void restoreHierarchyState(Bundle savedInstanceState);
+    
+    protected abstract void onActive();
+
+    /**
+     * Return the feature bits that are enabled.  This is the set of features
+     * that were given to requestFeature(), and are being handled by this
+     * Window itself or its container.  That is, it is the set of
+     * requested features that you can actually use.
+     *
+     * <p>To do: add a public version of this API that allows you to check for
+     * features by their feature ID.
+     *
+     * @return int The feature bits.
+     */
+    protected final int getFeatures()
+    {
+        return mFeatures;
+    }
+
+    /**
+     * Return the feature bits that are being implemented by this Window.
+     * This is the set of features that were given to requestFeature(), and are
+     * being handled by only this Window itself, not by its containers.
+     *
+     * @return int The feature bits.
+     */
+    protected final int getLocalFeatures()
+    {
+        return mLocalFeatures;
+    }
+
+    /**
+     * Set the default format of window, as per the PixelFormat types.  This
+     * is the format that will be used unless the client specifies in explicit
+     * format with setFormat();
+     *
+     * @param format The new window format (see PixelFormat).
+     *
+     * @see #setFormat
+     * @see PixelFormat
+     */
+    protected void setDefaultWindowFormat(int format) {
+        mDefaultWindowFormat = format;
+        if (!mHaveWindowFormat) {
+            final WindowManager.LayoutParams attrs = getAttributes();
+            attrs.format = format;
+            if (mCallback != null) {
+                mCallback.onWindowAttributesChanged(attrs);
+            }
+        }
+    }
+
+    public abstract void setChildDrawable(int featureId, Drawable drawable);
+
+    public abstract void setChildInt(int featureId, int value);
+
+    /**
+     * Is a keypress one of the defined shortcut keys for this window.
+     * @param keyCode the key code from {@link android.view.KeyEvent} to check.
+     * @param event the {@link android.view.KeyEvent} to use to help check.
+     */
+    public abstract boolean isShortcutKey(int keyCode, KeyEvent event);
+    
+    /**
+     * @see android.app.Activity#setVolumeControlStream(int) 
+     */
+    public abstract void setVolumeControlStream(int streamType);
+
+    /**
+     * @see android.app.Activity#getVolumeControlStream()
+     */
+    public abstract int getVolumeControlStream();
+    
+}
diff --git a/core/java/android/view/WindowManager.aidl b/core/java/android/view/WindowManager.aidl
new file mode 100755
index 0000000..556dc72
--- /dev/null
+++ b/core/java/android/view/WindowManager.aidl
@@ -0,0 +1,21 @@
+/* //device/java/android/android/view/WindowManager.aidl
+**
+** Copyright 2007, 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 android.view;
+
+parcelable WindowManager.LayoutParams;
+
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
new file mode 100644
index 0000000..b87cc42
--- /dev/null
+++ b/core/java/android/view/WindowManager.java
@@ -0,0 +1,959 @@
+/*
+ * 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 android.view;
+
+import android.content.pm.ActivityInfo;
+import android.graphics.PixelFormat;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+import android.util.Log;
+
+
+/**
+ * The interface that apps use to talk to the window manager.
+ * <p>
+ * Use <code>Context.getSystemService(Context.WINDOW_SERVICE)</code> to get one of these.
+ *
+ * @see android.content.Context#getSystemService
+ * @see android.content.Context#WINDOW_SERVICE
+ */
+public interface WindowManager extends ViewManager {
+    /**
+     * Exception that is thrown when trying to add view whose
+     * {@link WindowManager.LayoutParams} {@link WindowManager.LayoutParams#token}
+     * is invalid.
+     */
+    public static class BadTokenException extends RuntimeException {
+        public BadTokenException() {
+        }
+
+        public BadTokenException(String name) {
+            super(name);
+        }
+    }
+
+    /**
+     * Use this method to get the default Display object.
+     * 
+     * @return default Display object
+     */
+    public Display getDefaultDisplay();
+    
+    /**
+     * Special variation of {@link #removeView} that immediately invokes
+     * the given view hierarchy's {@link View#onDetachedFromWindow()
+     * View.onDetachedFromWindow()} methods before returning.  This is not
+     * for normal applications; using it correctly requires great care.
+     * 
+     * @param view The view to be removed.
+     */
+    public void removeViewImmediate(View view);
+    
+    public static class LayoutParams extends ViewGroup.LayoutParams
+            implements Parcelable {
+        /**
+         * X position for this window.  With the default gravity it is ignored.
+         * When using {@link Gravity#LEFT} or {@link Gravity#RIGHT} it provides
+         * an offset from the given edge.
+         */
+        public int x;
+        
+        /**
+         * Y position for this window.  With the default gravity it is ignored.
+         * When using {@link Gravity#TOP} or {@link Gravity#BOTTOM} it provides
+         * an offset from the given edge.
+         */
+        public int y;
+
+        /**
+         * Indicates how much of the extra space will be allocated horizontally
+         * to the view associated with these LayoutParams. Specify 0 if the view
+         * should not be stretched. Otherwise the extra pixels will be pro-rated
+         * among all views whose weight is greater than 0.
+         */
+        public float horizontalWeight;
+
+        /**
+         * Indicates how much of the extra space will be allocated vertically
+         * to the view associated with these LayoutParams. Specify 0 if the view
+         * should not be stretched. Otherwise the extra pixels will be pro-rated
+         * among all views whose weight is greater than 0.
+         */
+        public float verticalWeight;
+        
+        /**
+         * The general type of window.  There are three main classes of
+         * window types:
+         * <ul>
+         * <li> <strong>Application windows</strong> (ranging from
+         * {@link #FIRST_APPLICATION_WINDOW} to
+         * {@link #LAST_APPLICATION_WINDOW}) are normal top-level application
+         * windows.  For these types of windows, the {@link #token} must be
+         * set to the token of the activity they are a part of (this will
+         * normally be done for you if {@link #token} is null).
+         * <li> <strong>Sub-windows</strong> (ranging from
+         * {@link #FIRST_SUB_WINDOW} to
+         * {@link #LAST_SUB_WINDOW}) are associated with another top-level
+         * window.  For these types of windows, the {@link #token} must be
+         * the token of the window it is attached to.
+         * <li> <strong>System windows</strong> (ranging from
+         * {@link #FIRST_SYSTEM_WINDOW} to
+         * {@link #LAST_SYSTEM_WINDOW}) are special types of windows for
+         * use by the system for specific purposes.  They should not normally
+         * be used by applications, and a special permission is required
+         * to use them.
+         * </ul>
+         * 
+         * @see #TYPE_BASE_APPLICATION
+         * @see #TYPE_APPLICATION
+         * @see #TYPE_APPLICATION_STARTING
+         * @see #TYPE_APPLICATION_PANEL
+         * @see #TYPE_APPLICATION_MEDIA
+         * @see #TYPE_APPLICATION_SUB_PANEL
+         * @see #TYPE_APPLICATION_ATTACHED_DIALOG
+         * @see #TYPE_STATUS_BAR
+         * @see #TYPE_SEARCH_BAR
+         * @see #TYPE_PHONE
+         * @see #TYPE_SYSTEM_ALERT
+         * @see #TYPE_KEYGUARD
+         * @see #TYPE_TOAST
+         * @see #TYPE_SYSTEM_OVERLAY
+         * @see #TYPE_PRIORITY_PHONE
+         * @see #TYPE_STATUS_BAR_PANEL
+         * @see #TYPE_SYSTEM_DIALOG
+         * @see #TYPE_KEYGUARD_DIALOG
+         * @see #TYPE_SYSTEM_ERROR
+         * @see #TYPE_INPUT_METHOD
+         * @see #TYPE_INPUT_METHOD_DIALOG
+         */
+        public int type;
+    
+        /**
+         * Start of window types that represent normal application windows.
+         */
+        public static final int FIRST_APPLICATION_WINDOW = 1;
+        
+        /**
+         * Window type: an application window that serves as the "base" window
+         * of the overall application; all other application windows will
+         * appear on top of it.
+         */
+        public static final int TYPE_BASE_APPLICATION   = 1;
+        
+        /**
+         * Window type: a normal application window.  The {@link #token} must be
+         * an Activity token identifying who the window belongs to.
+         */
+        public static final int TYPE_APPLICATION        = 2;
+    
+        /**
+         * Window type: special application window that is displayed while the
+         * application is starting.  Not for use by applications themselves;
+         * this is used by the system to display something until the
+         * application can show its own windows.
+         */
+        public static final int TYPE_APPLICATION_STARTING = 3;
+    
+        /**
+         * End of types of application windows.
+         */
+        public static final int LAST_APPLICATION_WINDOW = 99;
+    
+        /**
+         * Start of types of sub-windows.  The {@link #token} of these windows
+         * must be set to the window they are attached to.  These types of
+         * windows are kept next to their attached window in Z-order, and their
+         * coordinate space is relative to their attached window.
+         */
+        public static final int FIRST_SUB_WINDOW        = 1000;
+    
+        /**
+         * Window type: a panel on top of an application window.  These windows
+         * appear on top of their attached window.
+         */
+        public static final int TYPE_APPLICATION_PANEL  = FIRST_SUB_WINDOW;
+    
+        /**
+         * Window type: window for showing media (e.g. video).  These windows
+         * are displayed behind their attached window.
+         */
+        public static final int TYPE_APPLICATION_MEDIA  = FIRST_SUB_WINDOW+1;
+    
+        /**
+         * Window type: a sub-panel on top of an application window.  These
+         * windows are displayed on top their attached window and any
+         * {@link #TYPE_APPLICATION_PANEL} panels.
+         */
+        public static final int TYPE_APPLICATION_SUB_PANEL = FIRST_SUB_WINDOW+2;
+
+        /** Window type: like {@link #TYPE_APPLICATION_PANEL}, but layout
+         * of the window happens as that of a top-level window, <em>not</em>
+         * as a child of its container.
+         */
+        public static final int TYPE_APPLICATION_ATTACHED_DIALOG = FIRST_SUB_WINDOW+3;
+        
+        /**
+         * End of types of sub-windows.
+         */
+        public static final int LAST_SUB_WINDOW         = 1999;
+        
+        /**
+         * Start of system-specific window types.  These are not normally
+         * created by applications.
+         */
+        public static final int FIRST_SYSTEM_WINDOW     = 2000;
+    
+        /**
+         * Window type: the status bar.  There can be only one status bar
+         * window; it is placed at the top of the screen, and all other
+         * windows are shifted down so they are below it.
+         */
+        public static final int TYPE_STATUS_BAR         = FIRST_SYSTEM_WINDOW;
+    
+        /**
+         * Window type: the search bar.  There can be only one search bar
+         * window; it is placed at the top of the screen.
+         */
+        public static final int TYPE_SEARCH_BAR         = FIRST_SYSTEM_WINDOW+1;
+    
+        /**
+         * Window type: phone.  These are non-application windows providing
+         * user interaction with the phone (in particular incoming calls).
+         * These windows are normally placed above all applications, but behind
+         * the status bar.
+         */
+        public static final int TYPE_PHONE              = FIRST_SYSTEM_WINDOW+2;
+    
+        /**
+         * Window type: system window, such as low power alert. These windows
+         * are always on top of application windows.
+         */
+        public static final int TYPE_SYSTEM_ALERT       = FIRST_SYSTEM_WINDOW+3;
+        
+        /**
+         * Window type: keyguard window.
+         */
+        public static final int TYPE_KEYGUARD           = FIRST_SYSTEM_WINDOW+4;
+        
+        /**
+         * Window type: transient notifications.
+         */
+        public static final int TYPE_TOAST              = FIRST_SYSTEM_WINDOW+5;
+        
+        /**
+         * Window type: system overlay windows, which need to be displayed
+         * on top of everything else.  These windows must not take input
+         * focus, or they will interfere with the keyguard.
+         */
+        public static final int TYPE_SYSTEM_OVERLAY     = FIRST_SYSTEM_WINDOW+6;
+        
+        /**
+         * Window type: priority phone UI, which needs to be displayed even if
+         * the keyguard is active.  These windows must not take input
+         * focus, or they will interfere with the keyguard.
+         */
+        public static final int TYPE_PRIORITY_PHONE     = FIRST_SYSTEM_WINDOW+7;
+        
+        /**
+         * Window type: panel that slides out from the status bar
+         */
+        public static final int TYPE_STATUS_BAR_PANEL   = FIRST_SYSTEM_WINDOW+8;
+        
+        /**
+         * Window type: panel that slides out from the status bar
+         */
+        public static final int TYPE_SYSTEM_DIALOG      = FIRST_SYSTEM_WINDOW+8;
+    
+        /**
+         * Window type: dialogs that the keyguard shows
+         */
+        public static final int TYPE_KEYGUARD_DIALOG    = FIRST_SYSTEM_WINDOW+9;
+        
+        /**
+         * Window type: internal system error windows, appear on top of
+         * everything they can.
+         */
+        public static final int TYPE_SYSTEM_ERROR       = FIRST_SYSTEM_WINDOW+10;
+        
+        /**
+         * Window type: internal input methods windows, which appear above
+         * the normal UI.  Application windows may be resized or panned to keep
+         * the input focus visible while this window is displayed.
+         */
+        public static final int TYPE_INPUT_METHOD       = FIRST_SYSTEM_WINDOW+11;
+
+        /**
+         * Window type: internal input methods dialog windows, which appear above
+         * the current input method window.
+         */
+        public static final int TYPE_INPUT_METHOD_DIALOG= FIRST_SYSTEM_WINDOW+12;
+
+        /**
+         * End of types of system windows.
+         */
+        public static final int LAST_SYSTEM_WINDOW      = 2999;
+        
+        /**
+         * Specifies what type of memory buffers should be used by this window.
+         * Default is normal.
+         * 
+         * @see #MEMORY_TYPE_NORMAL
+         * @see #MEMORY_TYPE_HARDWARE
+         * @see #MEMORY_TYPE_GPU
+         * @see #MEMORY_TYPE_PUSH_BUFFERS
+         */
+        public int memoryType;
+
+        /** Memory type: The window's surface is allocated in main memory. */
+        public static final int MEMORY_TYPE_NORMAL = 0;
+        /** Memory type: The window's surface is configured to be accessible
+         * by DMA engines and hardware accelerators. */
+        public static final int MEMORY_TYPE_HARDWARE = 1;
+        /** Memory type: The window's surface is configured to be accessible
+         * by graphics accelerators. */
+        public static final int MEMORY_TYPE_GPU = 2;
+        /** Memory type: The window's surface doesn't own its buffers and
+         * therefore cannot be locked. Instead the buffers are pushed to
+         * it through native binder calls. */
+        public static final int MEMORY_TYPE_PUSH_BUFFERS = 3;
+
+        /**
+         * Various behavioral options/flags.  Default is none.
+         * 
+         * @see #FLAG_BLUR_BEHIND
+         * @see #FLAG_DIM_BEHIND
+         * @see #FLAG_NOT_FOCUSABLE
+         * @see #FLAG_NOT_TOUCHABLE
+         * @see #FLAG_NOT_TOUCH_MODAL
+         * @see #FLAG_LAYOUT_IN_SCREEN
+         * @see #FLAG_DITHER
+         * @see #FLAG_KEEP_SCREEN_ON
+         * @see #FLAG_FULLSCREEN
+         * @see #FLAG_FORCE_NOT_FULLSCREEN
+         * @see #FLAG_IGNORE_CHEEK_PRESSES
+         */
+        public int flags;
+        
+        /** Window flag: everything behind this window will be dimmed.
+         *  Use {@link #dimAmount} to control the amount of dim. */
+        public static final int FLAG_DIM_BEHIND        = 0x00000002;
+        
+        /** Window flag: blur everything behind this window. */
+        public static final int FLAG_BLUR_BEHIND        = 0x00000004;
+        
+        /** Window flag: this window won't ever get key input focus, so the
+         * user can not send key or other button events to it.  Those will
+         * instead go to whatever focusable window is behind it.  This flag
+         * will also enable {@link #FLAG_NOT_TOUCH_MODAL} whether or not that
+         * is explicitly set.
+         * 
+         * <p>Setting this flag also implies that the window will not need to
+         * interact with
+         * a soft input method, so it will be Z-ordered and positioned 
+         * independently of any active input method (typically this means it
+         * gets Z-ordered on top of the input method, so it can use the full
+         * screen for its content and cover the input method if needed.  You
+         * can use {@link #FLAG_ALT_FOCUSABLE_IM} to modify this behavior. */
+        public static final int FLAG_NOT_FOCUSABLE      = 0x00000008;
+        
+        /** Window flag: this window can never receive touch events. */
+        public static final int FLAG_NOT_TOUCHABLE      = 0x00000010;
+        
+        /** Window flag: Even when this window is focusable (its
+         * {@link #FLAG_NOT_FOCUSABLE is not set), allow any pointer events
+         * outside of the window to be sent to the windows behind it.  Otherwise
+         * it will consume all pointer events itself, regardless of whether they
+         * are inside of the window. */
+        public static final int FLAG_NOT_TOUCH_MODAL    = 0x00000020;
+        
+        /** Window flag: When set, if the device is asleep when the touch
+         * screen is pressed, you will receive this first touch event.  Usually
+         * the first touch event is consumed by the system since the user can
+         * not see what they are pressing on.
+         */
+        public static final int FLAG_TOUCHABLE_WHEN_WAKING = 0x00000040;
+        
+        /** Window flag: as long as this window is visible to the user, keep
+         *  the device's screen turned on and bright. */
+        public static final int FLAG_KEEP_SCREEN_ON     = 0x00000080;
+        
+        /** Window flag: place the window within the entire screen, ignoring
+         *  decorations around the border (a.k.a. the status bar).  The
+         *  window must correctly position its contents to take the screen
+         *  decoration into account.  This flag is normally set for you
+         *  by Window as described in {@link Window#setFlags}. */
+        public static final int FLAG_LAYOUT_IN_SCREEN   = 0x00000100;
+        
+        /** Window flag: allow window to extend outside of the screen. */
+        public static final int FLAG_LAYOUT_NO_LIMITS   = 0x00000200;
+        
+        /** Window flag: Hide all screen decorations (e.g. status bar) while
+         * this window is displayed.  This allows the window to use the entire
+         * display space for itself -- the status bar will be hidden when
+         * an app window with this flag set is on the top layer. */
+        public static final int FLAG_FULLSCREEN      = 0x00000400;
+        
+        /** Window flag: Override {@link #FLAG_FULLSCREEN and force the
+         *  screen decorations (such as status bar) to be shown. */
+        public static final int FLAG_FORCE_NOT_FULLSCREEN   = 0x00000800;
+        
+        /** Window flag: turn on dithering when compositing this window to
+         *  the screen. */
+        public static final int FLAG_DITHER             = 0x00001000;
+        
+        /** Window flag: don't allow screen shots while this window is
+         * displayed. */
+        public static final int FLAG_SECURE             = 0x00002000;
+        
+        /** Window flag: a special mode where the layout parameters are used
+         * to perform scaling of the surface when it is composited to the
+         * screen. */
+        public static final int FLAG_SCALED             = 0x00004000;
+        
+        /** Window flag: intended for windows that will often be used when the user is
+         * holding the screen against their face, it will aggressively filter the event
+         * stream to prevent unintended presses in this situation that may not be
+         * desired for a particular window, when such an event stream is detected, the 
+         * application will receive a CANCEL motion event to indicate this so applications
+         * can handle this accordingly by taking no action on the event 
+         * until the finger is released. */
+        public static final int FLAG_IGNORE_CHEEK_PRESSES    = 0x00008000;
+        
+        /** Window flag: a special option only for use in combination with
+         * {@link #FLAG_LAYOUT_IN_SCREEN}.  When requesting layout in the
+         * screen your window may appear on top of or behind screen decorations
+         * such as the status bar.  By also including this flag, the window
+         * manager will report the inset rectangle needed to ensure your
+         * content is not covered by screen decorations.  This flag is normally
+         * set for you by Window as described in {@link Window#setFlags}.*/
+        public static final int FLAG_LAYOUT_INSET_DECOR = 0x00010000;
+        
+        /** Window flag: invert the state of {@link #FLAG_NOT_FOCUSABLE} with
+         * respect to how this window interacts with the current method.  That
+         * is, if FLAG_NOT_FOCUSABLE is set and this flag is set, then the
+         * window will behave as if it needs to interact with the input method
+         * and thus be placed behind/away from it; if FLAG_NOT_FOCUSABLE is
+         * not set and this flag is set, then the window will behave as if it
+         * doesn't need to interact with the input method and can be placed
+         * to use more space and cover the input method.
+         */
+        public static final int FLAG_ALT_FOCUSABLE_IM = 0x00020000;
+        
+        /** Window flag: if you have set {@link #FLAG_NOT_TOUCH_MODAL}, you
+         * can set this flag to receive a single special MotionEvent with
+         * the action
+         * {@link MotionEvent#ACTION_OUTSIDE MotionEvent.ACTION_OUTSIDE} for
+         * touches that occur outside of your window.  Note that you will not
+         * receive the full down/move/up gesture, only the location of the
+         * first down as an ACTION_OUTSIDE.
+         */
+        public static final int FLAG_WATCH_OUTSIDE_TOUCH = 0x00040000;
+        
+        /** Window flag: a special option intended for system dialogs.  When
+         * this flag is set, the window will demand focus unconditionally when
+         * it is created.
+         * {@hide} */
+        public static final int FLAG_SYSTEM_ERROR = 0x40000000;
+
+        /**
+         * Given a particular set of window manager flags, determine whether
+         * such a window may be a target for an input method when it has
+         * focus.  In particular, this checks the
+         * {@link #FLAG_NOT_FOCUSABLE} and {@link #FLAG_ALT_FOCUSABLE_IM}
+         * flags and returns true if the combination of the two corresponds
+         * to a window that needs to be behind the input method so that the
+         * user can type into it.
+         * 
+         * @param flags The current window manager flags.
+         * 
+         * @return Returns true if such a window should be behind/interact
+         * with an input method, false if not.
+         */
+        public static boolean mayUseInputMethod(int flags) {
+            switch (flags&(FLAG_NOT_FOCUSABLE|FLAG_ALT_FOCUSABLE_IM)) {
+                case 0:
+                case FLAG_NOT_FOCUSABLE|FLAG_ALT_FOCUSABLE_IM:
+                    return true;
+            }
+            return false;
+        }
+        
+        /**
+         * Mask for {@link #softInputMode} of the bits that determine the
+         * desired visibility state of the soft input area for this window.
+         */
+        public static final int SOFT_INPUT_MASK_STATE = 0x0f;
+        
+        /**
+         * Visibility state for {@link #softInputMode}: no state has been specified.
+         */
+        public static final int SOFT_INPUT_STATE_UNSPECIFIED = 0;
+        
+        /**
+         * Visibility state for {@link #softInputMode}: please don't change the state of
+         * the soft input area.
+         */
+        public static final int SOFT_INPUT_STATE_UNCHANGED = 1;
+        
+        /**
+         * Visibility state for {@link #softInputMode}: please hide any soft input
+         * area when normally appropriate (when the user is navigating
+         * forward to your window).
+         */
+        public static final int SOFT_INPUT_STATE_HIDDEN = 2;
+        
+        /**
+         * Visibility state for {@link #softInputMode}: please always hide any
+         * soft input area when this window receives focus.
+         */
+        public static final int SOFT_INPUT_STATE_ALWAYS_HIDDEN = 3;
+        
+        /**
+         * Visibility state for {@link #softInputMode}: please show the soft
+         * input area when normally appropriate (when the user is navigating
+         * forward to your window).
+         */
+        public static final int SOFT_INPUT_STATE_VISIBLE = 4;
+        
+        /**
+         * Visibility state for {@link #softInputMode}: please always make the
+         * soft input area visible when this window receives input focus.
+         */
+        public static final int SOFT_INPUT_STATE_ALWAYS_VISIBLE = 5;
+        
+        /**
+         * Mask for {@link #softInputMode} of the bits that determine the
+         * way that the window should be adjusted to accommodate the soft
+         * input window.
+         */
+        public static final int SOFT_INPUT_MASK_ADJUST = 0xf0;
+        
+        /** Adjustment option for {@link #softInputMode}: nothing specified.
+         * The system will try to pick one or
+         * the other depending on the contents of the window.
+         */
+        public static final int SOFT_INPUT_ADJUST_UNSPECIFIED = 0x00;
+        
+        /** Adjustment option for {@link #softInputMode}: set to allow the
+         * window to be resized when an input
+         * method is shown, so that its contents are not covered by the input
+         * method.  This can <em>not<em> be combined with
+         * {@link #SOFT_INPUT_ADJUST_PAN}; if
+         * neither of these are set, then the system will try to pick one or
+         * the other depending on the contents of the window.
+         */
+        public static final int SOFT_INPUT_ADJUST_RESIZE = 0x10;
+        
+        /** Adjustment option for {@link #softInputMode}: set to have a window
+         * pan when an input method is
+         * shown, so it doesn't need to deal with resizing but just panned
+         * by the framework to ensure the current input focus is visible.  This
+         * can <em>not<em> be combined with {@link #SOFT_INPUT_ADJUST_RESIZE}; if
+         * neither of these are set, then the system will try to pick one or
+         * the other depending on the contents of the window.
+         */
+        public static final int SOFT_INPUT_ADJUST_PAN = 0x20;
+        
+        /**
+         * Bit for {@link #softInputMode}: set when the user has navigated
+         * forward to the window.  This is normally set automatically for
+         * you by the system, though you may want to set it in certain cases
+         * when you are displaying a window yourself.  This flag will always
+         * be cleared automatically after the window is displayed.
+         */
+        public static final int SOFT_INPUT_IS_FORWARD_NAVIGATION = 0x100;
+        
+        /**
+         * Desired operating mode for any soft input area.  May any combination
+         * of:
+         * 
+         * <ul>
+         * <li> One of the visibility states
+         * {@link #SOFT_INPUT_STATE_UNSPECIFIED}, {@link #SOFT_INPUT_STATE_UNCHANGED},
+         * {@link #SOFT_INPUT_STATE_HIDDEN}, {@link #SOFT_INPUT_STATE_ALWAYS_VISIBLE}, or
+         * {@link #SOFT_INPUT_STATE_VISIBLE}.
+         * <li> One of the adjustment options
+         * {@link #SOFT_INPUT_ADJUST_UNSPECIFIED},
+         * {@link #SOFT_INPUT_ADJUST_RESIZE}, or
+         * {@link #SOFT_INPUT_ADJUST_PAN}.
+         */
+        public int softInputMode;
+        
+        /**
+         * Placement of window within the screen as per {@link Gravity}
+         *
+         * @see Gravity
+         */
+        public int gravity;
+    
+        /**
+         * The horizontal margin, as a percentage of the container's width,
+         * between the container and the widget.
+         */
+        public float horizontalMargin;
+    
+        /**
+         * The vertical margin, as a percentage of the container's height,
+         * between the container and the widget.
+         */
+        public float verticalMargin;
+    
+        /**
+         * The desired bitmap format.  May be one of the constants in
+         * {@link android.graphics.PixelFormat}.  Default is OPAQUE.
+         */
+        public int format;
+    
+        /**
+         * A style resource defining the animations to use for this window.
+         * This must be a system resource; it can not be an application resource
+         * because the window manager does not have access to applications.
+         */
+        public int windowAnimations;
+    
+        /**
+         * An alpha value to apply to this entire window.
+         * An alpha of 1.0 means fully opaque and 0.0 means fully transparent
+         */
+        public float alpha = 1.0f;
+    
+        /**
+         * When {@link #FLAG_DIM_BEHIND} is set, this is the amount of dimming
+         * to apply.  Range is from 1.0 for completely opaque to 0.0 for no
+         * dim.
+         */
+        public float dimAmount = 1.0f;
+    
+        /**
+         * This can be used to override the user's preferred brightness of
+         * the screen.  A value of less than 0, the default, means to use the
+         * preferred screen brightness.  0 to 1 adjusts the brightness from
+         * dark to full bright.
+         */
+        public float screenBrightness = -1.0f;
+        
+        /**
+         * Identifier for this window.  This will usually be filled in for
+         * you.
+         */
+        public IBinder token = null;
+    
+        /**
+         * Name of the package owning this window.
+         */
+        public String packageName = null;
+        
+        /**
+         * Specific orientation value for a window.
+         * May be any of the same values allowed
+         * for {@link android.content.pm.ActivityInfo#screenOrientation}. 
+         * If not set, a default value of 
+         * {@link android.content.pm.ActivityInfo#SCREEN_ORIENTATION_UNSPECIFIED} 
+         * will be used.
+         */
+        public int screenOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
+        
+        
+        public LayoutParams() {
+            super(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT);
+            type = TYPE_APPLICATION;
+            format = PixelFormat.OPAQUE;
+        }
+        
+        public LayoutParams(int _type) {
+            super(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT);
+            type = _type;
+            format = PixelFormat.OPAQUE;
+        }
+    
+        public LayoutParams(int _type, int _flags) {
+            super(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT);
+            type = _type;
+            flags = _flags;
+            format = PixelFormat.OPAQUE;
+        }
+    
+        public LayoutParams(int _type, int _flags, int _format) {
+            super(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT);
+            type = _type;
+            flags = _flags;
+            format = _format;
+        }
+        
+        public LayoutParams(int w, int h, int _type, int _flags, int _format) {
+            super(w, h);
+            type = _type;
+            flags = _flags;
+            format = _format;
+        }
+        
+        public LayoutParams(int w, int h, int xpos, int ypos, int _type,
+                int _flags, int _format) {
+            super(w, h);
+            x = xpos;
+            y = ypos;
+            type = _type;
+            flags = _flags;
+            format = _format;
+        }
+    
+        public final void setTitle(CharSequence title) {
+            if (null == title)
+                title = "";
+    
+            mTitle = TextUtils.stringOrSpannedString(title);
+        }
+    
+        public final CharSequence getTitle() {
+            return mTitle;
+        }
+    
+        public int describeContents() {
+            return 0;
+        }
+
+        public void writeToParcel(Parcel out, int parcelableFlags) {
+            out.writeInt(width);
+            out.writeInt(height);
+            out.writeInt(x);
+            out.writeInt(y);
+            out.writeInt(type);
+            out.writeInt(memoryType);
+            out.writeInt(flags);
+            out.writeInt(softInputMode);
+            out.writeInt(gravity);
+            out.writeFloat(horizontalMargin);
+            out.writeFloat(verticalMargin);
+            out.writeInt(format);
+            out.writeInt(windowAnimations);
+            out.writeFloat(alpha);
+            out.writeFloat(dimAmount);
+            out.writeFloat(screenBrightness);
+            out.writeStrongBinder(token);
+            out.writeString(packageName);
+            TextUtils.writeToParcel(mTitle, out, parcelableFlags);
+            out.writeInt(screenOrientation);
+        }
+        
+        public static final Parcelable.Creator<LayoutParams> CREATOR
+                    = new Parcelable.Creator<LayoutParams>() {
+            public LayoutParams createFromParcel(Parcel in) {
+                return new LayoutParams(in);
+            }
+    
+            public LayoutParams[] newArray(int size) {
+                return new LayoutParams[size];
+            }
+        };
+    
+    
+        public LayoutParams(Parcel in) {
+            width = in.readInt();
+            height = in.readInt();
+            x = in.readInt();
+            y = in.readInt();
+            type = in.readInt();
+            memoryType = in.readInt();
+            flags = in.readInt();
+            softInputMode = in.readInt();
+            gravity = in.readInt();
+            horizontalMargin = in.readFloat();
+            verticalMargin = in.readFloat();
+            format = in.readInt();
+            windowAnimations = in.readInt();
+            alpha = in.readFloat();
+            dimAmount = in.readFloat();
+            screenBrightness = in.readFloat();
+            token = in.readStrongBinder();
+            packageName = in.readString();
+            mTitle = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
+            screenOrientation = in.readInt();
+        }
+    
+        public static final int LAYOUT_CHANGED = 1<<0;
+        public static final int TYPE_CHANGED = 1<<1;
+        public static final int FLAGS_CHANGED = 1<<2;
+        public static final int FORMAT_CHANGED = 1<<3;
+        public static final int ANIMATION_CHANGED = 1<<4;
+        public static final int DIM_AMOUNT_CHANGED = 1<<5;
+        public static final int TITLE_CHANGED = 1<<6;
+        public static final int ALPHA_CHANGED = 1<<7;
+        public static final int MEMORY_TYPE_CHANGED = 1<<8;
+        public static final int SOFT_INPUT_MODE_CHANGED = 1<<9;
+        public static final int SCREEN_ORIENTATION_CHANGED = 1<<10;
+        public static final int SCREEN_BRIGHTNESS_CHANGED = 1<<11;
+    
+        public final int copyFrom(LayoutParams o) {
+            int changes = 0;
+    
+            if (width != o.width) {
+                width = o.width;
+                changes |= LAYOUT_CHANGED;
+            }
+            if (height != o.height) {
+                height = o.height;
+                changes |= LAYOUT_CHANGED;
+            }
+            if (x != o.x) {
+                x = o.x;
+                changes |= LAYOUT_CHANGED;
+            }
+            if (y != o.y) {
+                y = o.y;
+                changes |= LAYOUT_CHANGED;
+            }
+            if (horizontalWeight != o.horizontalWeight) {
+                horizontalWeight = o.horizontalWeight;
+                changes |= LAYOUT_CHANGED;
+            }
+            if (verticalWeight != o.verticalWeight) {
+                verticalWeight = o.verticalWeight;
+                changes |= LAYOUT_CHANGED;
+            }
+            if (horizontalMargin != o.horizontalMargin) {
+                horizontalMargin = o.horizontalMargin;
+                changes |= LAYOUT_CHANGED;
+            }
+            if (verticalMargin != o.verticalMargin) {
+                verticalMargin = o.verticalMargin;
+                changes |= LAYOUT_CHANGED;
+            }
+            if (type != o.type) {
+                type = o.type;
+                changes |= TYPE_CHANGED;
+            }
+            if (memoryType != o.memoryType) {
+                memoryType = o.memoryType;
+                changes |= MEMORY_TYPE_CHANGED;
+            }
+            if (flags != o.flags) {
+                flags = o.flags;
+                changes |= FLAGS_CHANGED;
+            }
+            if (softInputMode != o.softInputMode) {
+                softInputMode = o.softInputMode;
+                changes |= SOFT_INPUT_MODE_CHANGED;
+            }
+            if (gravity != o.gravity) {
+                gravity = o.gravity;
+                changes |= LAYOUT_CHANGED;
+            }
+            if (horizontalMargin != o.horizontalMargin) {
+                horizontalMargin = o.horizontalMargin;
+                changes |= LAYOUT_CHANGED;
+            }
+            if (verticalMargin != o.verticalMargin) {
+                verticalMargin = o.verticalMargin;
+                changes |= LAYOUT_CHANGED;
+            }
+            if (format != o.format) {
+                format = o.format;
+                changes |= FORMAT_CHANGED;
+            }
+            if (windowAnimations != o.windowAnimations) {
+                windowAnimations = o.windowAnimations;
+                changes |= ANIMATION_CHANGED;
+            }
+            if (token == null) {
+                // NOTE: token only copied if the recipient doesn't
+                // already have one.
+                token = o.token;
+            }
+            if (packageName == null) {
+                // NOTE: packageName only copied if the recipient doesn't
+                // already have one.
+                packageName = o.packageName;
+            }
+            if (!mTitle.equals(o.mTitle)) {
+                mTitle = o.mTitle;
+                changes |= TITLE_CHANGED;
+            }
+            if (alpha != o.alpha) {
+                alpha = o.alpha;
+                changes |= ALPHA_CHANGED;
+            }
+            if (dimAmount != o.dimAmount) {
+                dimAmount = o.dimAmount;
+                changes |= DIM_AMOUNT_CHANGED;
+            }
+            if (screenBrightness != o.screenBrightness) {
+                screenBrightness = o.screenBrightness;
+                changes |= SCREEN_BRIGHTNESS_CHANGED;
+            }
+    
+            if (screenOrientation != o.screenOrientation) {
+                screenOrientation = o.screenOrientation;
+                changes |= SCREEN_ORIENTATION_CHANGED;
+            }
+            return changes;
+        }
+    
+        @Override
+        public String debug(String output) {
+            output += "Contents of " + this + ":";
+            Log.d("Debug", output);
+            output = super.debug("");
+            Log.d("Debug", output);
+            Log.d("Debug", "");
+            Log.d("Debug", "WindowManager.LayoutParams={title=" + mTitle + "}");
+            return "";
+        }
+    
+        @Override
+        public String toString() {
+            StringBuilder sb = new StringBuilder(256);
+            sb.append("WM.LayoutParams{");
+            sb.append("(");
+            sb.append(x);
+            sb.append(',');
+            sb.append(y);
+            sb.append(")(");
+            sb.append((width==FILL_PARENT?"fill":(width==WRAP_CONTENT?"wrap":width)));
+            sb.append('x');
+            sb.append((height==FILL_PARENT?"fill":(height==WRAP_CONTENT?"wrap":height)));
+            sb.append(")");
+            if (softInputMode != 0) {
+                sb.append(" sim=#");
+                sb.append(Integer.toHexString(softInputMode));
+            }
+            if (gravity != 0) {
+                sb.append(" gr=#");
+                sb.append(Integer.toHexString(gravity));
+            }
+            sb.append(" ty=");
+            sb.append(type);
+            sb.append(" fl=#");
+            sb.append(Integer.toHexString(flags));
+            sb.append(" fmt=");
+            sb.append(format);
+            if (windowAnimations != 0) {
+                sb.append(" wanim=0x");
+                sb.append(Integer.toHexString(windowAnimations));
+            }
+            if (screenOrientation != ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED) {
+                sb.append(" or=");
+                sb.append(screenOrientation);
+            }
+            sb.append('}');
+            return sb.toString();
+        }
+    
+        private CharSequence mTitle = "";
+    }
+}
diff --git a/core/java/android/view/WindowManagerImpl.java b/core/java/android/view/WindowManagerImpl.java
new file mode 100644
index 0000000..755d7b8
--- /dev/null
+++ b/core/java/android/view/WindowManagerImpl.java
@@ -0,0 +1,364 @@
+/*
+ * 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 android.view;
+
+import android.graphics.PixelFormat;
+import android.os.IBinder;
+import android.util.AndroidRuntimeException;
+import android.util.Config;
+import android.util.Log;
+import android.view.WindowManager;
+import android.view.inputmethod.InputMethodManager;
+
+final class WindowLeaked extends AndroidRuntimeException {
+    public WindowLeaked(String msg) {
+        super(msg);
+    }
+}
+
+/**
+ * Low-level communication with the global system window manager.  It implements
+ * the ViewManager interface, allowing you to add any View subclass as a
+ * top-level window on the screen. Additional window manager specific layout
+ * parameters are defined for control over how windows are displayed.
+ * It also implemens the WindowManager interface, allowing you to control the
+ * displays attached to the device.
+ * 
+ * <p>Applications will not normally use WindowManager directly, instead relying
+ * on the higher-level facilities in {@link android.app.Activity} and
+ * {@link android.app.Dialog}.
+ * 
+ * <p>Even for low-level window manager access, it is almost never correct to use
+ * this class.  For example, {@link android.app.Activity#getWindowManager}
+ * provides a ViewManager for adding windows that are associated with that
+ * activity -- the window manager will not normally allow you to add arbitrary
+ * windows that are not associated with an activity.
+ * 
+ * @hide
+ */
+public class WindowManagerImpl implements WindowManager {
+    /**
+     * The user is navigating with keys (not the touch screen), so
+     * navigational focus should be shown.
+     */
+    public static final int RELAYOUT_IN_TOUCH_MODE = 0x1;
+    /**
+     * This is the first time the window is being drawn,
+     * so the client must call drawingFinished() when done
+     */
+    public static final int RELAYOUT_FIRST_TIME = 0x2;
+    
+    public static final int ADD_FLAG_APP_VISIBLE = 0x2;
+    public static final int ADD_FLAG_IN_TOUCH_MODE = RELAYOUT_IN_TOUCH_MODE;
+    
+    public static final int ADD_OKAY = 0;
+    public static final int ADD_BAD_APP_TOKEN = -1;
+    public static final int ADD_BAD_SUBWINDOW_TOKEN = -2;
+    public static final int ADD_NOT_APP_TOKEN = -3;
+    public static final int ADD_APP_EXITING = -4;
+    public static final int ADD_DUPLICATE_ADD = -5;
+    public static final int ADD_STARTING_NOT_NEEDED = -6;
+    public static final int ADD_MULTIPLE_SINGLETON = -7;
+    public static final int ADD_PERMISSION_DENIED = -8;
+
+    public static WindowManagerImpl getDefault()
+    {
+        return mWindowManager;
+    }
+    
+    public void addView(View view)
+    {
+        addView(view, new WindowManager.LayoutParams(
+            WindowManager.LayoutParams.TYPE_APPLICATION, 0, PixelFormat.OPAQUE));
+    }
+
+    public void addView(View view, ViewGroup.LayoutParams params)
+    {
+        addView(view, params, false);
+    }
+    
+    public void addViewNesting(View view, ViewGroup.LayoutParams params)
+    {
+        addView(view, params, false);
+    }
+    
+    private void addView(View view, ViewGroup.LayoutParams params, boolean nest)
+    {
+        if (Config.LOGV) Log.v("WindowManager", "addView view=" + view);
+
+        if (!(params instanceof WindowManager.LayoutParams)) {
+            throw new IllegalArgumentException(
+                    "Params must be WindowManager.LayoutParams");
+        }
+
+        final WindowManager.LayoutParams wparams
+                = (WindowManager.LayoutParams)params;
+        
+        ViewRoot root;
+        View panelParentView = null;
+        
+        synchronized (this) {
+            // Here's an odd/questionable case: if someone tries to add a
+            // view multiple times, then we simply bump up a nesting count
+            // and they need to remove the view the corresponding number of
+            // times to have it actually removed from the window manager.
+            // This is useful specifically for the notification manager,
+            // which can continually add/remove the same view as a
+            // notification gets updated.
+            int index = findViewLocked(view, false);
+            if (index >= 0) {
+                if (!nest) {
+                    throw new IllegalStateException("View " + view
+                            + " has already been added to the window manager.");
+                }
+                root = mRoots[index];
+                root.mAddNesting++;
+                // Update layout parameters.
+                view.setLayoutParams(wparams);
+                root.setLayoutParams(wparams, true);
+                return;
+            }
+            
+            // If this is a panel window, then find the window it is being
+            // attached to for future reference.
+            if (wparams.type >= WindowManager.LayoutParams.FIRST_SUB_WINDOW &&
+                    wparams.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW) {
+                final int count = mViews != null ? mViews.length : 0;
+                for (int i=0; i<count; i++) {
+                    if (mRoots[i].mWindow.asBinder() == wparams.token) {
+                        panelParentView = mViews[i];
+                    }
+                }
+            }
+            
+            root = new ViewRoot(view.getContext());
+            root.mAddNesting = 1;
+
+            view.setLayoutParams(wparams);
+            
+            if (mViews == null) {
+                index = 1;
+                mViews = new View[1];
+                mRoots = new ViewRoot[1];
+                mParams = new WindowManager.LayoutParams[1];
+            } else {
+                index = mViews.length + 1;
+                Object[] old = mViews;
+                mViews = new View[index];
+                System.arraycopy(old, 0, mViews, 0, index-1);
+                old = mRoots;
+                mRoots = new ViewRoot[index];
+                System.arraycopy(old, 0, mRoots, 0, index-1);
+                old = mParams;
+                mParams = new WindowManager.LayoutParams[index];
+                System.arraycopy(old, 0, mParams, 0, index-1);
+            }
+            index--;
+
+            mViews[index] = view;
+            mRoots[index] = root;
+            mParams[index] = wparams;
+        }
+
+        // do this last because it fires off messages to start doing things
+        root.setView(view, wparams, panelParentView);
+    }
+
+    public void updateViewLayout(View view, ViewGroup.LayoutParams params) {
+        if (!(params instanceof WindowManager.LayoutParams)) {
+            throw new IllegalArgumentException("Params must be WindowManager.LayoutParams");
+        }
+
+        final WindowManager.LayoutParams wparams
+                = (WindowManager.LayoutParams)params;
+        
+        view.setLayoutParams(wparams);
+
+        synchronized (this) {
+            int index = findViewLocked(view, true);
+            ViewRoot root = mRoots[index];
+            mParams[index] = wparams;
+            root.setLayoutParams(wparams, false);
+        }
+    }
+
+    public void removeView(View view) {
+        synchronized (this) {
+            int index = findViewLocked(view, true);
+            View curView = removeViewLocked(index);
+            if (curView == view) {
+                return;
+            }
+            
+            throw new IllegalStateException("Calling with view " + view
+                    + " but the ViewRoot is attached to " + curView);
+        }
+    }
+
+    public void removeViewImmediate(View view) {
+        synchronized (this) {
+            int index = findViewLocked(view, true);
+            ViewRoot root = mRoots[index];
+            View curView = root.getView();
+            
+            root.mAddNesting = 0;
+            root.die(true);
+            finishRemoveViewLocked(curView, index);
+            if (curView == view) {
+                return;
+            }
+            
+            throw new IllegalStateException("Calling with view " + view
+                    + " but the ViewRoot is attached to " + curView);
+        }
+    }
+    
+    View removeViewLocked(int index) {
+        ViewRoot root = mRoots[index];
+        View view = root.getView();
+        
+        // Don't really remove until we have matched all calls to add().
+        root.mAddNesting--;
+        if (root.mAddNesting > 0) {
+            return view;
+        }
+
+        InputMethodManager imm = InputMethodManager.getInstance(view.getContext());
+        if (imm != null) {
+            imm.windowDismissed(mViews[index].getWindowToken());
+        }
+        root.die(false);
+        finishRemoveViewLocked(view, index);
+        return view;
+    }
+    
+    void finishRemoveViewLocked(View view, int index) {
+        final int count = mViews.length;
+
+        // remove it from the list
+        View[] tmpViews = new View[count-1];
+        removeItem(tmpViews, mViews, index);
+        mViews = tmpViews;
+        
+        ViewRoot[] tmpRoots = new ViewRoot[count-1];
+        removeItem(tmpRoots, mRoots, index);
+        mRoots = tmpRoots;
+        
+        WindowManager.LayoutParams[] tmpParams
+                = new WindowManager.LayoutParams[count-1];
+        removeItem(tmpParams, mParams, index);
+        mParams = tmpParams;
+
+        view.assignParent(null);
+        // func doesn't allow null...  does it matter if we clear them?
+        //view.setLayoutParams(null);
+    }
+
+    public void closeAll(IBinder token, String who, String what) {
+        synchronized (this) {
+            if (mViews == null)
+                return;
+            
+            int count = mViews.length;
+            //Log.i("foo", "Closing all windows of " + token);
+            for (int i=0; i<count; i++) {
+                //Log.i("foo", "@ " + i + " token " + mParams[i].token
+                //        + " view " + mRoots[i].getView());
+                if (token == null || mParams[i].token == token) {
+                    ViewRoot root = mRoots[i];
+                    root.mAddNesting = 1;
+                    
+                    //Log.i("foo", "Force closing " + root);
+                    if (who != null) {
+                        WindowLeaked leak = new WindowLeaked(
+                                what + " " + who + " has leaked window "
+                                + root.getView() + " that was originally added here");
+                        leak.setStackTrace(root.getLocation().getStackTrace());
+                        Log.e("WindowManager", leak.getMessage(), leak);
+                    }
+                    
+                    removeViewLocked(i);
+                    i--;
+                    count--;
+                }
+            }
+        }
+    }
+    
+    public WindowManager.LayoutParams getRootViewLayoutParameter(View view) {
+        ViewParent vp = view.getParent();
+        while (vp != null && !(vp instanceof ViewRoot)) {
+            vp = vp.getParent();
+        }
+        
+        if (vp == null) return null;
+        
+        ViewRoot vr = (ViewRoot)vp;
+        
+        int N = mRoots.length;
+        for (int i = 0; i < N; ++i) {
+            if (mRoots[i] == vr) {
+                return mParams[i];
+            }
+        }
+        
+        return null;
+    }
+    
+    public void closeAll() {
+        closeAll(null, null, null);
+    }
+    
+    public Display getDefaultDisplay() {
+        return new Display(Display.DEFAULT_DISPLAY);
+    }
+
+    private View[] mViews;
+    private ViewRoot[] mRoots;
+    private WindowManager.LayoutParams[] mParams;
+
+    private static void removeItem(Object[] dst, Object[] src, int index)
+    {
+        if (dst.length > 0) {
+            if (index > 0) {
+                System.arraycopy(src, 0, dst, 0, index);
+            }
+            if (index < dst.length) {
+                System.arraycopy(src, index+1, dst, index, src.length-index-1);
+            }
+        }
+    }
+
+    private int findViewLocked(View view, boolean required)
+    {
+        synchronized (this) {
+            final int count = mViews != null ? mViews.length : 0;
+            for (int i=0; i<count; i++) {
+                if (mViews[i] == view) {
+                    return i;
+                }
+            }
+            if (required) {
+                throw new IllegalArgumentException(
+                        "View not attached to window manager");
+            }
+            return -1;
+        }
+    }
+
+    private static WindowManagerImpl mWindowManager = new WindowManagerImpl();
+}
diff --git a/core/java/android/view/WindowManagerPolicy.java b/core/java/android/view/WindowManagerPolicy.java
new file mode 100644
index 0000000..0f15b17
--- /dev/null
+++ b/core/java/android/view/WindowManagerPolicy.java
@@ -0,0 +1,796 @@
+/*
+ * 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 android.view;
+
+import android.content.Context;
+import android.content.res.Configuration;
+import android.graphics.Rect;
+import android.os.IBinder;
+import android.os.LocalPowerManager;
+import android.view.animation.Animation;
+
+/**
+ * This interface supplies all UI-specific behavior of the window manager.  An
+ * instance of it is created by the window manager when it starts up, and allows
+ * customization of window layering, special window types, key dispatching, and
+ * layout.
+ * 
+ * <p>Because this provides deep interaction with the system window manager,
+ * specific methods on this interface can be called from a variety of contexts
+ * with various restrictions on what they can do.  These are encoded through
+ * a suffixes at the end of a method encoding the thread the method is called
+ * from and any locks that are held when it is being called; if no suffix
+ * is attached to a method, then it is not called with any locks and may be
+ * called from the main window manager thread or another thread calling into
+ * the window manager.
+ * 
+ * <p>The current suffixes are:
+ * 
+ * <dl>
+ * <dt> Ti <dd> Called from the input thread.  This is the thread that
+ * collects pending input events and dispatches them to the appropriate window.
+ * It may block waiting for events to be processed, so that the input stream is
+ * properly serialized.
+ * <dt> Tq <dd> Called from the low-level input queue thread.  This is the
+ * thread that reads events out of the raw input devices and places them
+ * into the global input queue that is read by the <var>Ti</var> thread.
+ * This thread should not block for a long period of time on anything but the
+ * key driver.
+ * <dt> Lw <dd> Called with the main window manager lock held.  Because the
+ * window manager is a very low-level system service, there are few other
+ * system services you can call with this lock held.  It is explicitly okay to
+ * make calls into the package manager and power manager; it is explicitly not
+ * okay to make calls into the activity manager.  Note that
+ * {@link android.content.Context#checkPermission(String, int, int)} and
+ * variations require calling into the activity manager.
+ * <dt> Li <dd> Called with the input thread lock held.  This lock can be
+ * acquired by the window manager while it holds the window lock, so this is
+ * even more restrictive than <var>Lw</var>.
+ * </dl>
+ * 
+ * @hide
+ */
+public interface WindowManagerPolicy {
+    public final static int FLAG_WAKE = 0x00000001;
+    public final static int FLAG_WAKE_DROPPED = 0x00000002;
+    public final static int FLAG_SHIFT = 0x00000004;
+    public final static int FLAG_CAPS_LOCK = 0x00000008;
+    public final static int FLAG_ALT = 0x00000010;
+    public final static int FLAG_ALT_GR = 0x00000020;
+    public final static int FLAG_MENU = 0x00000040;
+    public final static int FLAG_LAUNCHER = 0x00000080;
+
+    public final static int FLAG_WOKE_HERE = 0x10000000;
+    public final static int FLAG_BRIGHT_HERE = 0x20000000;
+
+    public final static boolean WATCH_POINTER = false;
+
+    // flags for interceptKeyTq
+    /**
+     * Pass this event to the user / app.  To be returned from {@link #interceptKeyTq}.
+     */
+    public final static int ACTION_PASS_TO_USER = 0x00000001;
+
+    /**
+     * This key event should extend the user activity timeout and turn the lights on.
+     * To be returned from {@link #interceptKeyTq}. Do not return this and
+     * {@link #ACTION_GO_TO_SLEEP} or {@link #ACTION_PASS_TO_USER}.
+     */
+    public final static int ACTION_POKE_USER_ACTIVITY = 0x00000002;
+
+    /**
+     * This key event should put the device to sleep (and engage keyguard if necessary)
+     * To be returned from {@link #interceptKeyTq}.  Do not return this and
+     * {@link #ACTION_POKE_USER_ACTIVITY} or {@link #ACTION_PASS_TO_USER}.
+     */
+    public final static int ACTION_GO_TO_SLEEP = 0x00000004;
+
+    /**
+     * Interface to the Window Manager state associated with a particular
+     * window.  You can hold on to an instance of this interface from the call
+     * to prepareAddWindow() until removeWindow().
+     */
+    public interface WindowState {
+        /**
+         * Perform standard frame computation.  The result can be obtained with
+         * getFrame() if so desired.  Must be called with the window manager
+         * lock held.
+         * 
+         * @param parentFrame The frame of the parent container this window
+         * is in, used for computing its basic position.
+         * @param displayFrame The frame of the overall display in which this
+         * window can appear, used for constraining the overall dimensions
+         * of the window.
+         * @param contentFrame The frame within the display in which we would
+         * like active content to appear.  This will cause windows behind to
+         * be resized to match the given content frame.
+         * @param visibleFrame The frame within the display that the window
+         * is actually visible, used for computing its visible insets to be
+         * given to windows behind.
+         * This can be used as a hint for scrolling (avoiding resizing)
+         * the window to make certain that parts of its content
+         * are visible.
+         */
+        public void computeFrameLw(Rect parentFrame, Rect displayFrame,
+                Rect contentFrame, Rect visibleFrame);
+
+        /**
+         * Retrieve the current frame of the window that has been assigned by
+         * the window manager.  Must be called with the window manager lock held.
+         * 
+         * @return Rect The rectangle holding the window frame.
+         */
+        public Rect getFrameLw();
+
+        /**
+         * Retrieve the current frame of the window that is actually shown.
+         * Must be called with the window manager lock held.
+         * 
+         * @return Rect The rectangle holding the shown window frame.
+         */
+        public Rect getShownFrameLw();
+
+        /**
+         * Retrieve the frame of the display that this window was last
+         * laid out in.  Must be called with the
+         * window manager lock held.
+         * 
+         * @return Rect The rectangle holding the display frame.
+         */
+        public Rect getDisplayFrameLw();
+
+        /**
+         * Retrieve the frame of the content area that this window was last
+         * laid out in.  This is the area in which the content of the window
+         * should be placed.  It will be smaller than the display frame to
+         * account for screen decorations such as a status bar or soft
+         * keyboard.  Must be called with the
+         * window manager lock held.
+         * 
+         * @return Rect The rectangle holding the content frame.
+         */
+        public Rect getContentFrameLw();
+
+        /**
+         * Retrieve the frame of the visible area that this window was last
+         * laid out in.  This is the area of the screen in which the window
+         * will actually be fully visible.  It will be smaller than the
+         * content frame to account for transient UI elements blocking it
+         * such as an input method's candidates UI.  Must be called with the
+         * window manager lock held.
+         * 
+         * @return Rect The rectangle holding the visible frame.
+         */
+        public Rect getVisibleFrameLw();
+
+        /**
+         * Returns true if this window is waiting to receive its given
+         * internal insets from the client app, and so should not impact the
+         * layout of other windows.
+         */
+        public boolean getGivenInsetsPendingLw();
+        
+        /**
+         * Retrieve the insets given by this window's client for the content
+         * area of windows behind it.  Must be called with the
+         * window manager lock held.
+         * 
+         * @return Rect The left, top, right, and bottom insets, relative
+         * to the window's frame, of the actual contents.
+         */
+        public Rect getGivenContentInsetsLw();
+
+        /**
+         * Retrieve the insets given by this window's client for the visible
+         * area of windows behind it.  Must be called with the
+         * window manager lock held.
+         * 
+         * @return Rect The left, top, right, and bottom insets, relative
+         * to the window's frame, of the actual visible area.
+         */
+        public Rect getGivenVisibleInsetsLw();
+
+        /**
+         * Retrieve the current LayoutParams of the window.
+         * 
+         * @return WindowManager.LayoutParams The window's internal LayoutParams
+         *         instance.
+         */
+        public WindowManager.LayoutParams getAttrs();
+
+        /**
+         * Get the layer at which this window's surface will be Z-ordered.
+         */
+        public int getSurfaceLayer();
+        
+        /**
+         * Return the token for the application (actually activity) that owns 
+         * this window.  May return null for system windows. 
+         * 
+         * @return An IApplicationToken identifying the owning activity.
+         */
+        public IApplicationToken getAppToken();
+
+        /**
+         * Return true if, at any point, the application token associated with 
+         * this window has actually displayed any windows.  This is most useful 
+         * with the "starting up" window to determine if any windows were 
+         * displayed when it is closed. 
+         * 
+         * @return Returns true if one or more windows have been displayed, 
+         *         else false.
+         */
+        public boolean hasAppShownWindows();
+
+        /**
+         * Return true if the application token has been asked to display an 
+         * app starting icon as the application is starting up. 
+         * 
+         * @return Returns true if setAppStartingIcon() was called for this 
+         *         window's token.
+         */
+        public boolean hasAppStartingIcon();
+
+        /**
+         * Return the Window that is being displayed as this window's 
+         * application token is being started. 
+         * 
+         * @return Returns the currently displayed starting window, or null if 
+         *         it was not requested, has not yet been displayed, or has
+         *         been removed.
+         */
+        public WindowState getAppStartingWindow();
+
+        /**
+         * Is this window visible?  It is not visible if there is no
+         * surface, or we are in the process of running an exit animation
+         * that will remove the surface.
+         */
+        boolean isVisibleLw();
+        
+        /**
+         * Is this window currently visible to the user on-screen?  It is 
+         * displayed either if it is visible or it is currently running an 
+         * animation before no longer being visible.  Must be called with the
+         * window manager lock held.
+         */
+        boolean isDisplayedLw();
+
+        /**
+         * Returns true if the window is both full screen and opaque.  Must be
+         * called with the window manager lock held.
+         * 
+         * @param width The width of the screen
+         * @param height The height of the screen 
+         * @param shownFrame If true, this is based on the actual shown frame of 
+         *                   the window (taking into account animations); if
+         *                   false, this is based on the currently requested
+         *                   frame, which any current animation will be moving
+         *                   towards.
+         * @param onlyOpaque If true, this will only pass if the window is
+         * also opaque.
+         * @return Returns true if the window is both full screen and opaque
+         */
+        public boolean fillsScreenLw(int width, int height, boolean shownFrame,
+                boolean onlyOpaque);
+
+        /**
+         * Returns true if this window has been shown on screen at some time in 
+         * the past.  Must be called with the window manager lock held.
+         * 
+         * @return boolean
+         */
+        public boolean hasDrawnLw();
+
+        /**
+         * Can be called by the policy to force a window to be hidden,
+         * regardless of whether the client or window manager would like
+         * it shown.  Must be called with the window manager lock held.
+         * Returns true if {@link #showLw} was last called for the window.
+         */
+        public boolean hideLw(boolean doAnimation);
+        
+        /**
+         * Can be called to undo the effect of {@link #hideLw}, allowing a
+         * window to be shown as long as the window manager and client would
+         * also like it to be shown.  Must be called with the window manager
+         * lock held.
+         * Returns true if {@link #hideLw} was last called for the window.
+         */
+        public boolean showLw(boolean doAnimation);
+    }
+
+    /** No transition happening. */
+    public final int TRANSIT_NONE = 0;
+    /** Window has been added to the screen. */
+    public final int TRANSIT_ENTER = 1;
+    /** Window has been removed from the screen. */
+    public final int TRANSIT_EXIT = 2;
+    /** Window has been made visible. */
+    public final int TRANSIT_SHOW = 3;
+    /** Window has been made invisible. */
+    public final int TRANSIT_HIDE = 4;
+    /** The "application starting" preview window is no longer needed, and will
+     * animate away to show the real window. */
+    public final int TRANSIT_PREVIEW_DONE = 5;
+    /** A window in a new activity is being opened on top of an existing one
+     * in the same task. */
+    public final int TRANSIT_ACTIVITY_OPEN = 6;
+    /** The window in the top-most activity is being closed to reveal the
+     * previous activity in the same task. */
+    public final int TRANSIT_ACTIVITY_CLOSE = 7;
+    /** A window in a new task is being opened on top of an existing one
+     * in another activity's task. */
+    public final int TRANSIT_TASK_OPEN = 8;
+    /** A window in the top-most activity is being closed to reveal the
+     * previous activity in a different task. */
+    public final int TRANSIT_TASK_CLOSE = 9;
+    /** A window in an existing task is being displayed on top of an existing one
+     * in another activity's task. */
+    public final int TRANSIT_TASK_TO_FRONT = 10;
+    /** A window in an existing task is being put below all other tasks. */
+    public final int TRANSIT_TASK_TO_BACK = 11;
+    
+    /** Screen turned off because of power button */
+    public final int OFF_BECAUSE_OF_USER = 1;
+    /** Screen turned off because of timeout */
+    public final int OFF_BECAUSE_OF_TIMEOUT = 2;
+
+    /**
+     * Magic constant to {@link IWindowManager#setRotation} to not actually
+     * modify the rotation.
+     */
+    public final int USE_LAST_ROTATION = -1000;
+    
+    /**
+     * Perform initialization of the policy.
+     * 
+     * @param context The system context we are running in.
+     * @param powerManager 
+     */
+    public void init(Context context, IWindowManager windowManager,
+            LocalPowerManager powerManager);
+
+    /**
+     * Check permissions when adding a window.
+     * 
+     * @param attrs The window's LayoutParams. 
+     *  
+     * @return {@link WindowManagerImpl#ADD_OKAY} if the add can proceed;
+     *      else an error code, usually
+     *      {@link WindowManagerImpl#ADD_PERMISSION_DENIED}, to abort the add.
+     */
+    public int checkAddPermission(WindowManager.LayoutParams attrs);
+
+    /**
+     * Sanitize the layout parameters coming from a client.  Allows the policy
+     * to do things like ensure that windows of a specific type can't take
+     * input focus.
+     * 
+     * @param attrs The window layout parameters to be modified.  These values
+     * are modified in-place.
+     */
+    public void adjustWindowParamsLw(WindowManager.LayoutParams attrs);
+    
+    /**
+     * After the window manager has computed the current configuration based
+     * on its knowledge of the display and input devices, it gives the policy
+     * a chance to adjust the information contained in it.  If you want to
+     * leave it as-is, simply do nothing.
+     * 
+     * <p>This method may be called by any thread in the window manager, but
+     * no internal locks in the window manager will be held.
+     * 
+     * @param config The Configuration being computed, for you to change as
+     * desired.
+     */
+    public void adjustConfigurationLw(Configuration config);
+    
+    /**
+     * Assign a window type to a layer.  Allows you to control how different
+     * kinds of windows are ordered on-screen.
+     * 
+     * @param type The type of window being assigned.
+     * 
+     * @return int An arbitrary integer used to order windows, with lower
+     *         numbers below higher ones.
+     */
+    public int windowTypeToLayerLw(int type);
+
+    /**
+     * Return how to Z-order sub-windows in relation to the window they are
+     * attached to.  Return positive to have them ordered in front, negative for
+     * behind.
+     * 
+     * @param type The sub-window type code.
+     * 
+     * @return int Layer in relation to the attached window, where positive is
+     *         above and negative is below.
+     */
+    public int subWindowTypeToLayerLw(int type);
+
+    /**
+     * Called when the system would like to show a UI to indicate that an
+     * application is starting.  You can use this to add a
+     * APPLICATION_STARTING_TYPE window with the given appToken to the window
+     * manager (using the normal window manager APIs) that will be shown until
+     * the application displays its own window.  This is called without the
+     * window manager locked so that you can call back into it.
+     * 
+     * @param appToken Token of the application being started.
+     * @param packageName The name of the application package being started. 
+     * @param theme Resource defining the application's overall visual theme.
+     * @param nonLocalizedLabel The default title label of the application if
+     *        no data is found in the resource.
+     * @param labelRes The resource ID the application would like to use as its name.
+     * @param icon The resource ID the application would like to use as its icon.
+     * 
+     * @return Optionally you can return the View that was used to create the
+     *         window, for easy removal in removeStartingWindow.
+     * 
+     * @see #removeStartingWindow
+     */
+    public View addStartingWindow(IBinder appToken, String packageName,
+            int theme, CharSequence nonLocalizedLabel,
+            int labelRes, int icon);
+
+    /**
+     * Called when the first window of an application has been displayed, while
+     * {@link #addStartingWindow} has created a temporary initial window for
+     * that application.  You should at this point remove the window from the
+     * window manager.  This is called without the window manager locked so
+     * that you can call back into it.
+     * 
+     * <p>Note: due to the nature of these functions not being called with the
+     * window manager locked, you must be prepared for this function to be
+     * called multiple times and/or an initial time with a null View window
+     * even if you previously returned one.
+     * 
+     * @param appToken Token of the application that has started.
+     * @param window Window View that was returned by createStartingWindow.
+     * 
+     * @see #addStartingWindow
+     */
+    public void removeStartingWindow(IBinder appToken, View window);
+
+    /**
+     * Prepare for a window being added to the window manager.  You can throw an
+     * exception here to prevent the window being added, or do whatever setup
+     * you need to keep track of the window.
+     * 
+     * @param win The window being added.
+     * @param attrs The window's LayoutParams. 
+     *  
+     * @return {@link WindowManagerImpl#ADD_OKAY} if the add can proceed, else an 
+     *         error code to abort the add.
+     */
+    public int prepareAddWindowLw(WindowState win,
+            WindowManager.LayoutParams attrs);
+
+    /**
+     * Called when a window is being removed from a window manager.  Must not
+     * throw an exception -- clean up as much as possible.
+     * 
+     * @param win The window being removed.
+     */
+    public void removeWindowLw(WindowState win);
+
+    /**
+     * Control the animation to run when a window's state changes.  Return a
+     * non-0 number to force the animation to a specific resource ID, or 0
+     * to use the default animation.
+     * 
+     * @param win The window that is changing.
+     * @param transit What is happening to the window: {@link #TRANSIT_ENTER},
+     *                {@link #TRANSIT_EXIT}, {@link #TRANSIT_SHOW}, or
+     *                {@link #TRANSIT_HIDE}.
+     * 
+     * @return Resource ID of the actual animation to use, or 0 for none.
+     */
+    public int selectAnimationLw(WindowState win, int transit);
+
+    /**
+     * Called from the key queue thread before a key is dispatched to the
+     * input thread.
+     *
+     * <p>There are some actions that need to be handled here because they
+     * affect the power state of the device, for example, the power keys.
+     * Generally, it's best to keep as little as possible in the queue thread
+     * because it's the most fragile.
+     *
+     * @param event the raw input event as read from the driver
+     * @param screenIsOn true if the screen is already on
+     * @return The bitwise or of the {@link #ACTION_PASS_TO_USER},
+     *          {@link #ACTION_POKE_USER_ACTIVITY} and {@link #ACTION_GO_TO_SLEEP} flags.
+     */
+    public int interceptKeyTq(RawInputEvent event, boolean screenIsOn);
+    
+    /**
+     * Called from the input thread before a key is dispatched to a window.
+     *
+     * <p>Allows you to define
+     * behavior for keys that can not be overridden by applications or redirect
+     * key events to a different window.  This method is called from the
+     * input thread, with no locks held.
+     * 
+     * <p>Note that if you change the window a key is dispatched to, the new
+     * target window will receive the key event without having input focus.
+     * 
+     * @param win The window that currently has focus.  This is where the key
+     *            event will normally go.
+     * @param code Key code.
+     * @param metaKeys TODO
+     * @param down Is this a key press (true) or release (false)?
+     * @param repeatCount Number of times a key down has repeated.
+     * @return Returns true if the policy consumed the event and it should
+     * not be further dispatched.
+     */
+    public boolean interceptKeyTi(WindowState win, int code,
+                               int metaKeys, boolean down, int repeatCount);
+
+    /**
+     * Called when layout of the windows is about to start.
+     * 
+     * @param displayWidth The current full width of the screen.
+     * @param displayHeight The current full height of the screen.
+     */
+    public void beginLayoutLw(int displayWidth, int displayHeight);
+
+    /**
+     * Called for each window attached to the window manager as layout is
+     * proceeding.  The implementation of this function must take care of
+     * setting the window's frame, either here or in finishLayout().
+     * 
+     * @param win The window being positioned.
+     * @param attrs The LayoutParams of the window.
+     * @param attached For sub-windows, the window it is attached to; this
+     *                 window will already have had layoutWindow() called on it
+     *                 so you can use its Rect.  Otherwise null.
+     */
+    public void layoutWindowLw(WindowState win,
+            WindowManager.LayoutParams attrs, WindowState attached);
+
+    
+    /**
+     * Return the insets for the areas covered by system windows. These values
+     * are computed on the most recent layout, so they are not guaranteed to
+     * be correct.
+     * 
+     * @param attrs The LayoutParams of the window.
+     * @param contentInset The areas covered by system windows, expressed as positive insets
+     * 
+     */
+    public void getContentInsetHintLw(WindowManager.LayoutParams attrs, Rect contentInset);
+    
+    /**
+     * Called when layout of the windows is finished.  After this function has
+     * returned, all windows given to layoutWindow() <em>must</em> have had a
+     * frame assigned.
+     */
+    public void finishLayoutLw();
+
+    /**
+     * Called when animation of the windows is about to start.
+     * 
+     * @param displayWidth The current full width of the screen.
+     * @param displayHeight The current full height of the screen.
+     */
+    public void beginAnimationLw(int displayWidth, int displayHeight);
+
+    /**
+     * Called each time a window is animating.
+     * 
+     * @param win The window being positioned.
+     * @param attrs The LayoutParams of the window. 
+     */
+    public void animatingWindowLw(WindowState win,
+            WindowManager.LayoutParams attrs);
+
+    /**
+     * Called when animation of the windows is finished.  If in this function you do 
+     * something that may have modified the animation state of another window, 
+     * be sure to return true in order to perform another animation frame. 
+     *  
+     * @return Return true if animation state may have changed (so that another 
+     *         frame of animation will be run).
+     */
+    public boolean finishAnimationLw();
+
+    /**
+     * Called after the screen turns off.
+     *
+     * @param why {@link #OFF_BECAUSE_OF_USER} or
+     * {@link #OFF_BECAUSE_OF_TIMEOUT}.
+     */
+    public void screenTurnedOff(int why);
+
+    /**
+     * Called after the screen turns on.
+     */
+    public void screenTurnedOn();
+
+    /**
+     * Perform any initial processing of a low-level input event before the
+     * window manager handles special keys and generates a high-level event
+     * that is dispatched to the application.
+     * 
+     * @param event The input event that has occurred.
+     * 
+     * @return Return true if you have consumed the event and do not want
+     * further processing to occur; return false for normal processing.
+     */
+    public boolean preprocessInputEventTq(RawInputEvent event);
+    
+    /**
+     * Determine whether a given key code is used to cause an app switch
+     * to occur (most often the HOME key, also often ENDCALL).  If you return
+     * true, then the system will go into a special key processing state
+     * where it drops any pending events that it cans and adjusts timeouts to
+     * try to get to this key as quickly as possible.
+     * 
+     * <p>Note that this function is called from the low-level input queue
+     * thread, with either/or the window or input lock held; be very careful
+     * about what you do here.  You absolutely should never acquire a lock
+     * that you would ever hold elsewhere while calling out into the window
+     * manager or view hierarchy.
+     * 
+     * @param keycode The key that should be checked for performing an
+     * app switch before delivering to the application.
+     * 
+     * @return Return true if this is an app switch key and special processing
+     * should happen; return false for normal processing.
+     */
+    public boolean isAppSwitchKeyTqTiLwLi(int keycode);
+    
+    /**
+     * Determine whether a given key code is used for movement within a UI,
+     * and does not generally cause actions to be performed (normally the DPAD
+     * movement keys, NOT the DPAD center press key).  This is called
+     * when {@link #isAppSwitchKeyTiLi} returns true to remove any pending events
+     * in the key queue that are not needed to switch applications.
+     * 
+     * <p>Note that this function is called from the low-level input queue
+     * thread; be very careful about what you do here.
+     * 
+     * @param keycode The key that is waiting to be delivered to the
+     * application.
+     * 
+     * @return Return true if this is a purely navigation key and can be
+     * dropped without negative consequences; return false to keep it.
+     */
+    public boolean isMovementKeyTi(int keycode);
+    
+    /**
+     * Given the current state of the world, should this relative movement
+     * wake up the device?
+     * 
+     * @param device The device the movement came from.
+     * @param classes The input classes associated with the device.
+     * @param event The input event that occurred.
+     * @return
+     */
+    public boolean isWakeRelMovementTq(int device, int classes,
+            RawInputEvent event);
+    
+    /**
+     * Given the current state of the world, should this absolute movement
+     * wake up the device?
+     * 
+     * @param device The device the movement came from.
+     * @param classes The input classes associated with the device.
+     * @param event The input event that occurred.
+     * @return
+     */
+    public boolean isWakeAbsMovementTq(int device, int classes,
+            RawInputEvent event);
+    
+    /**
+     * Tell the policy if anyone is requesting that keyguard not come on.
+     *
+     * @param enabled Whether keyguard can be on or not.  does not actually
+     * turn it on, unless it was previously disabled with this function.
+     *
+     * @see android.app.KeyguardManager.KeyguardLock#disableKeyguard()
+     * @see android.app.KeyguardManager.KeyguardLock#reenableKeyguard()
+     */
+    public void enableKeyguard(boolean enabled);
+
+    /**
+     * Callback used by {@link WindowManagerPolicy#exitKeyguardSecurely}
+     */
+    interface OnKeyguardExitResult {
+        void onKeyguardExitResult(boolean success);
+    }
+
+    /**
+     * Tell the policy if anyone is requesting the keyguard to exit securely
+     * (this would be called after the keyguard was disabled)
+     * @param callback Callback to send the result back.
+     * @see android.app.KeyguardManager#exitKeyguardSecurely(android.app.KeyguardManager.OnKeyguardExitResult)
+     */
+    void exitKeyguardSecurely(OnKeyguardExitResult callback);
+
+    /**
+     * Return if keyguard is currently showing.
+     */
+    public boolean keyguardIsShowingTq();
+
+    /**
+     * inKeyguardRestrictedKeyInputMode
+     *
+     * if keyguard screen is showing or in restricted key input mode (i.e. in
+     * keyguard password emergency screen). When in such mode, certain keys,
+     * such as the Home key and the right soft keys, don't work.
+     *
+     * @return true if in keyguard restricted input mode.
+     */
+    public boolean inKeyguardRestrictedKeyInputMode();
+
+    /**
+     * Given an orientation constant
+     * ({@link android.content.pm.ActivityInfo#SCREEN_ORIENTATION_LANDSCAPE
+     * ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE} or
+     * {@link android.content.pm.ActivityInfo#SCREEN_ORIENTATION_PORTRAIT
+     * ActivityInfo.SCREEN_ORIENTATION_PORTRAIT}), return a surface
+     * rotation.
+     */
+    public int rotationForOrientation(int orientation, int lastRotation,
+            boolean displayEnabled);
+    
+    /**
+     * Called when the system is mostly done booting to dentermine whether
+     * the system should go into safe mode.
+     */
+    public boolean detectSafeMode();
+    
+    /**
+     * Called when the system is mostly done booting.
+     */
+    public void systemReady();
+
+    /**
+     * Called when we have finished booting and can now display the home
+     * screen to the user.  This wilWl happen after systemReady(), and at
+     * this point the display is active.
+     */
+    public void enableScreenAfterBoot();
+    
+    /**
+     * Returns true if the user's cheek has been pressed against the phone. This is 
+     * determined by comparing the event's size attribute with a threshold value.
+     * For example for a motion event like down or up or move, if the size exceeds
+     * the threshold, it is considered as cheek press.
+     * @param ev the motion event generated when the cheek is pressed 
+     * against the phone
+     * @return Returns true if the user's cheek has been pressed against the phone
+     * screen resulting in an invalid motion event
+     */
+    public boolean isCheekPressedAgainstScreen(MotionEvent ev);
+    
+    public void setCurrentOrientation(int newOrientation);
+    
+    /**
+     * Call from application to perform haptic feedback on its window.
+     */
+    public boolean performHapticFeedback(WindowState win, int effectId, boolean always);
+    
+    /**
+     * Called when we have stopped keeping the screen on because a window
+     * requesting this is no longer visible.
+     */
+    public void screenOnStopped();
+}
diff --git a/core/java/android/view/WindowOrientationListener.java b/core/java/android/view/WindowOrientationListener.java
new file mode 100755
index 0000000..5877932
--- /dev/null
+++ b/core/java/android/view/WindowOrientationListener.java
@@ -0,0 +1,180 @@
+/*
+ * 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 android.view;
+
+import android.content.Context;
+import android.hardware.Sensor;
+import android.hardware.SensorEvent;
+import android.hardware.SensorEventListener;
+import android.hardware.SensorManager;
+import android.util.Config;
+import android.util.Log;
+
+/**
+ * A special helper class used by the WindowManager
+ *  for receiving notifications from the SensorManager when
+ * the orientation of the device has changed.
+ * @hide
+ */
+public abstract class WindowOrientationListener {
+    private static final String TAG = "WindowOrientationListener";
+    private static final boolean DEBUG = false;
+    private static final boolean localLOGV = DEBUG ? Config.LOGD : Config.LOGV;
+    private int mOrientation = ORIENTATION_UNKNOWN;
+    private SensorManager mSensorManager;
+    private boolean mEnabled = false;
+    private int mRate;
+    private Sensor mSensor;
+    private SensorEventListener mSensorEventListener;
+    
+    /**
+     * Returned from onOrientationChanged when the device orientation cannot be determined
+     * (typically when the device is in a close to flat position).
+     *
+     *  @see #onOrientationChanged
+     */
+    public static final int ORIENTATION_UNKNOWN = -1;
+    /*
+     * Returned when the device is almost lying flat on a surface
+     */
+    public static final int ORIENTATION_FLAT = -2;
+
+    /**
+     * Creates a new WindowOrientationListener.
+     * 
+     * @param context for the WindowOrientationListener.
+     */
+    public WindowOrientationListener(Context context) {
+        this(context, SensorManager.SENSOR_DELAY_NORMAL);
+    }
+    
+    /**
+     * Creates a new WindowOrientationListener.
+     * 
+     * @param context for the WindowOrientationListener.
+     * @param rate at which sensor events are processed (see also
+     * {@link android.hardware.SensorManager SensorManager}). Use the default
+     * value of {@link android.hardware.SensorManager#SENSOR_DELAY_NORMAL 
+     * SENSOR_DELAY_NORMAL} for simple screen orientation change detection.
+     */
+    public WindowOrientationListener(Context context, int rate) {
+        mSensorManager = (SensorManager)context.getSystemService(Context.SENSOR_SERVICE);
+        mRate = rate;
+        mSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
+        if (mSensor != null) {
+            // Create listener only if sensors do exist
+            mSensorEventListener = new SensorEventListenerImpl();
+        }
+    }
+
+    /**
+     * Enables the WindowOrientationListener so it will monitor the sensor and call
+     * {@link #onOrientationChanged} when the device orientation changes.
+     */
+    public void enable() {
+        if (mSensor == null) {
+            Log.w(TAG, "Cannot detect sensors. Not enabled");
+            return;
+        }
+        if (mEnabled == false) {
+            if (localLOGV) Log.d(TAG, "WindowOrientationListener enabled");
+            mSensorManager.registerListener(mSensorEventListener, mSensor, mRate);
+            mEnabled = true;
+        }
+    }
+
+    /**
+     * Disables the WindowOrientationListener.
+     */
+    public void disable() {
+        if (mSensor == null) {
+            Log.w(TAG, "Cannot detect sensors. Invalid disable");
+            return;
+        }
+        if (mEnabled == true) {
+            if (localLOGV) Log.d(TAG, "WindowOrientationListener disabled");
+            mSensorManager.unregisterListener(mSensorEventListener);
+            mEnabled = false;
+        }
+    }
+
+    class SensorEventListenerImpl implements SensorEventListener {
+        private static final int _DATA_X = 0;
+        private static final int _DATA_Y = 1;
+        private static final int _DATA_Z = 2;
+        
+        public void onSensorChanged(SensorEvent event) {
+            float[] values = event.values;
+            int orientation = ORIENTATION_UNKNOWN;
+            float X = values[_DATA_X];
+            float Y = values[_DATA_Y];
+            float Z = values[_DATA_Z];
+            float OneEightyOverPi = 57.29577957855f;
+            float gravity = (float) Math.sqrt(X*X+Y*Y+Z*Z);
+            float zyangle = Math.abs((float)Math.asin(Z/gravity)*OneEightyOverPi);
+            // The device is considered flat if the angle is more than 75
+            // if the angle is less than 40, its considered too flat to switch
+            // orientation. if the angle is between 40 - 75, the orientation is unknown
+            if (zyangle < 40) {
+                // Check orientation only if the phone is flat enough
+                // Don't trust the angle if the magnitude is small compared to the y value
+                float angle = (float)Math.atan2(Y, -X) * OneEightyOverPi;
+                orientation = 90 - (int)Math.round(angle);
+                // normalize to 0 - 359 range
+                while (orientation >= 360) {
+                    orientation -= 360;
+                } 
+                while (orientation < 0) {
+                    orientation += 360;
+                }
+            } else if (zyangle >= 75){
+                orientation = ORIENTATION_FLAT;
+            }
+            
+            if (orientation != mOrientation) {
+                mOrientation = orientation;
+                onOrientationChanged(orientation);
+            }
+        }
+
+        public void onAccuracyChanged(Sensor sensor, int accuracy) {
+
+        }
+    }
+
+    /*
+     * Returns true if sensor is enabled and false otherwise
+     */
+    public boolean canDetectOrientation() {
+        return mSensor != null;
+    }
+    
+    /**
+     * Called when the orientation of the device has changed.
+     * orientation parameter is in degrees, ranging from 0 to 359.
+     * orientation is 0 degrees when the device is oriented in its natural position,
+     * 90 degrees when its left side is at the top, 180 degrees when it is upside down, 
+     * and 270 degrees when its right side is to the top.
+     * {@link #ORIENTATION_UNKNOWN} is returned when the device is close to flat
+     * and the orientation cannot be determined.
+     *
+     * @param orientation The new orientation of the device.
+     *
+     *  @see #ORIENTATION_UNKNOWN
+     */
+    abstract public void onOrientationChanged(int orientation);
+}
diff --git a/core/java/android/view/animation/AccelerateDecelerateInterpolator.java b/core/java/android/view/animation/AccelerateDecelerateInterpolator.java
new file mode 100644
index 0000000..fdb6f9d
--- /dev/null
+++ b/core/java/android/view/animation/AccelerateDecelerateInterpolator.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2007 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 android.view.animation;
+
+import android.content.Context;
+import android.util.AttributeSet;
+
+/**
+ * An interpolator where the rate of change starts and ends slowly but
+ * accelerates through the middle.
+ * 
+ */
+public class AccelerateDecelerateInterpolator implements Interpolator {
+    public AccelerateDecelerateInterpolator() {
+    }
+    
+    public AccelerateDecelerateInterpolator(Context context, AttributeSet attrs) {
+    }
+    
+    public float getInterpolation(float input) {
+        return (float)(Math.cos((input + 1) * Math.PI) / 2.0f) + 0.5f;
+    }
+}
diff --git a/core/java/android/view/animation/AccelerateInterpolator.java b/core/java/android/view/animation/AccelerateInterpolator.java
new file mode 100644
index 0000000..b9e293f0
--- /dev/null
+++ b/core/java/android/view/animation/AccelerateInterpolator.java
@@ -0,0 +1,62 @@
+/*
+ * 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 android.view.animation;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.util.AttributeSet;
+
+/**
+ * An interpolator where the rate of change starts out slowly and 
+ * and then accelerates.
+ *
+ */
+public class AccelerateInterpolator implements Interpolator {
+    public AccelerateInterpolator() {
+    }
+    
+    /**
+     * Constructor
+     * 
+     * @param factor Degree to which the animation should be eased. Seting
+     *        factor to 1.0f produces a y=x^2 parabola. Increasing factor above
+     *        1.0f  exaggerates the ease-in effect (i.e., it starts even
+     *        slower and ends evens faster)
+     */
+    public AccelerateInterpolator(float factor) {
+        mFactor = factor;
+    }
+    
+    public AccelerateInterpolator(Context context, AttributeSet attrs) {
+        TypedArray a =
+            context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.AccelerateInterpolator);
+        
+        mFactor = a.getFloat(com.android.internal.R.styleable.AccelerateInterpolator_factor, 1.0f);
+        
+        a.recycle();
+    }
+    
+    public float getInterpolation(float input) {
+        if (mFactor == 1.0f) {
+            return (float)(input * input);
+        } else {
+            return (float)Math.pow(input, 2 * mFactor);
+        }
+    }
+    
+    private float mFactor = 1.0f;
+}
diff --git a/core/java/android/view/animation/AlphaAnimation.java b/core/java/android/view/animation/AlphaAnimation.java
new file mode 100644
index 0000000..16a10a4
--- /dev/null
+++ b/core/java/android/view/animation/AlphaAnimation.java
@@ -0,0 +1,81 @@
+/*
+ * 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 android.view.animation;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.util.AttributeSet;
+
+/**
+ * An animation that controls the alpha level of an object.
+ * Useful for fading things in and out. This animation ends up
+ * changing the alpha property of a {@link Transformation}
+ *
+ */
+public class AlphaAnimation extends Animation {
+    private float mFromAlpha;
+    private float mToAlpha;
+
+    /**
+     * Constructor used whan an AlphaAnimation is loaded from a resource. 
+     * 
+     * @param context Application context to use
+     * @param attrs Attribute set from which to read values
+     */
+    public AlphaAnimation(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        
+        TypedArray a =
+            context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.AlphaAnimation);
+        
+        mFromAlpha = a.getFloat(com.android.internal.R.styleable.AlphaAnimation_fromAlpha, 1.0f);
+        mToAlpha = a.getFloat(com.android.internal.R.styleable.AlphaAnimation_toAlpha, 1.0f);
+        
+        a.recycle();
+    }
+    
+    /**
+     * Constructor to use when building an AlphaAnimation from code
+     * 
+     * @param fromAlpha Starting alpha value for the animation, where 1.0 means
+     *        fully opaque and 0.0 means fully transparent.
+     * @param toAlpha Ending alpha value for the animation.
+     */
+    public AlphaAnimation(float fromAlpha, float toAlpha) {
+        mFromAlpha = fromAlpha;
+        mToAlpha = toAlpha;
+    }
+    
+    /**
+     * Changes the alpha property of the supplied {@link Transformation}
+     */
+    @Override
+    protected void applyTransformation(float interpolatedTime, Transformation t) {
+        final float alpha = mFromAlpha;
+        t.setAlpha(alpha + ((mToAlpha - alpha) * interpolatedTime));
+    }
+
+    @Override
+    public boolean willChangeTransformationMatrix() {
+        return false;
+    }
+
+    @Override
+    public boolean willChangeBounds() {
+        return false;
+    }
+}
diff --git a/core/java/android/view/animation/Animation.java b/core/java/android/view/animation/Animation.java
new file mode 100644
index 0000000..b9c8ec3
--- /dev/null
+++ b/core/java/android/view/animation/Animation.java
@@ -0,0 +1,925 @@
+/*
+ * 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 android.view.animation;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.util.AttributeSet;
+import android.util.TypedValue;
+import android.graphics.RectF;
+
+/**
+ * Abstraction for an Animation that can be applied to Views, Surfaces, or
+ * other objects. See the {@link android.view.animation animation package
+ * description file}.
+ */
+public abstract class Animation implements Cloneable {
+    /**
+     * Repeat the animation indefinitely.
+     */
+    public static final int INFINITE = -1;
+
+    /**
+     * When the animation reaches the end and the repeat count is INFINTE_REPEAT
+     * or a positive value, the animation restarts from the beginning.
+     */
+    public static final int RESTART = 1;
+
+    /**
+     * When the animation reaches the end and the repeat count is INFINTE_REPEAT
+     * or a positive value, the animation plays backward (and then forward again).
+     */
+    public static final int REVERSE = 2;
+
+    /**
+     * Can be used as the start time to indicate the start time should be the current
+     * time when {@link #getTransformation(long, Transformation)} is invoked for the
+     * first animation frame. This can is useful for short animations.
+     */
+    public static final int START_ON_FIRST_FRAME = -1;
+
+    /**
+     * The specified dimension is an absolute number of pixels.
+     */
+    public static final int ABSOLUTE = 0;
+
+    /**
+     * The specified dimension holds a float and should be multiplied by the
+     * height or width of the object being animated.
+     */
+    public static final int RELATIVE_TO_SELF = 1;
+
+    /**
+     * The specified dimension holds a float and should be multiplied by the
+     * height or width of the parent of the object being animated.
+     */
+    public static final int RELATIVE_TO_PARENT = 2;
+
+    /**
+     * Requests that the content being animated be kept in its current Z
+     * order.
+     */
+    public static final int ZORDER_NORMAL = 0;
+    
+    /**
+     * Requests that the content being animated be forced on top of all other
+     * content for the duration of the animation.
+     */
+    public static final int ZORDER_TOP = 1;
+    
+    /**
+     * Requests that the content being animated be forced under all other
+     * content for the duration of the animation.
+     */
+    public static final int ZORDER_BOTTOM = -1;
+    
+    /**
+     * Set by {@link #getTransformation(long, Transformation)} when the animation ends.
+     */
+    boolean mEnded = false;
+
+    /**
+     * Set by {@link #getTransformation(long, Transformation)} when the animation starts.
+     */
+    boolean mStarted = false;
+
+    /**
+     * Set by {@link #getTransformation(long, Transformation)} when the animation repeats
+     * in REVERSE mode.
+     */
+    boolean mCycleFlip = false;
+
+    /**
+     * This value must be set to true by {@link #initialize(int, int, int, int)}. It
+     * indicates the animation was successfully initialized and can be played.
+     */
+    boolean mInitialized = false;
+
+    /**
+     * Indicates whether the animation transformation should be applied before the
+     * animation starts.
+     */
+    boolean mFillBefore = true;
+
+    /**
+     * Indicates whether the animation transformation should be applied after the
+     * animation ends.
+     */
+    boolean mFillAfter = false;
+
+    /**
+     * Indicates whether fillAfter should be taken into account.
+     */
+    boolean mFillEnabled = false;    
+
+    /**
+     * The time in milliseconds at which the animation must start;
+     */
+    long mStartTime = -1;
+
+    /**
+     * The delay in milliseconds after which the animation must start. When the
+     * start offset is > 0, the start time of the animation is startTime + startOffset.
+     */
+    long mStartOffset;
+
+    /**
+     * The duration of one animation cycle in milliseconds.
+     */
+    long mDuration;
+
+    /**
+     * The number of times the animation must repeat. By default, an animation repeats
+     * indefinitely.
+     */
+    int mRepeatCount = 0;
+
+    /**
+     * Indicates how many times the animation was repeated.
+     */
+    int mRepeated = 0;
+
+    /**
+     * The behavior of the animation when it repeats. The repeat mode is either
+     * {@link #RESTART} or {@link #REVERSE}.
+     *
+     */
+    int mRepeatMode = RESTART;
+
+    /**
+     * The interpolator used by the animation to smooth the movement.
+     */
+    Interpolator mInterpolator;
+
+    /**
+     * The animation listener to be notified when the animation starts, ends or repeats.
+     */
+    AnimationListener mListener;
+
+    /**
+     * Desired Z order mode during animation.
+     */
+    private int mZAdjustment;
+    
+    private boolean mMore = true;
+    private boolean mOneMoreTime = true;
+
+    RectF mPreviousRegion = new RectF();
+    RectF mRegion = new RectF();
+    Transformation mTransformation = new Transformation();
+    Transformation mPreviousTransformation = new Transformation();
+
+    /**
+     * Creates a new animation with a duration of 0ms, the default interpolator, with
+     * fillBefore set to true and fillAfter set to false
+     */
+    public Animation() {
+        ensureInterpolator();
+    }
+
+    /**
+     * Creates a new animation whose parameters come from the specified context and
+     * attributes set.
+     *
+     * @param context the application environment
+     * @param attrs the set of attributes holding the animation parameters
+     */
+    public Animation(Context context, AttributeSet attrs) {
+        TypedArray a = context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.Animation);
+
+        setDuration((long) a.getInt(com.android.internal.R.styleable.Animation_duration, 0));
+        setStartOffset((long) a.getInt(com.android.internal.R.styleable.Animation_startOffset, 0));
+        
+        setFillEnabled(a.getBoolean(com.android.internal.R.styleable.Animation_fillEnabled, mFillEnabled));
+        setFillBefore(a.getBoolean(com.android.internal.R.styleable.Animation_fillBefore, mFillBefore));
+        setFillAfter(a.getBoolean(com.android.internal.R.styleable.Animation_fillAfter, mFillAfter));
+
+        final int resID = a.getResourceId(com.android.internal.R.styleable.Animation_interpolator, 0);
+        if (resID > 0) {
+            setInterpolator(context, resID);
+        }
+
+        setRepeatCount(a.getInt(com.android.internal.R.styleable.Animation_repeatCount, mRepeatCount));
+        setRepeatMode(a.getInt(com.android.internal.R.styleable.Animation_repeatMode, RESTART));
+
+        setZAdjustment(a.getInt(com.android.internal.R.styleable.Animation_zAdjustment, ZORDER_NORMAL));
+        
+        ensureInterpolator();
+
+        a.recycle();
+    }
+
+    @Override
+    protected Animation clone() throws CloneNotSupportedException {
+        final Animation animation = (Animation) super.clone();
+        animation.mPreviousRegion = new RectF();
+        animation.mRegion = new RectF();
+        animation.mTransformation = new Transformation();
+        animation.mPreviousTransformation = new Transformation();
+        return animation;
+    }
+
+    /**
+     * Reset the initialization state of this animation.
+     *
+     * @see #initialize(int, int, int, int)
+     */
+    public void reset() {
+        mPreviousRegion.setEmpty();
+        mPreviousTransformation.clear();
+        mInitialized = false;
+        mCycleFlip = false;
+        mRepeated = 0;
+        mMore = true;
+        mOneMoreTime = true;
+    }
+
+    /**
+     * Whether or not the animation has been initialized.
+     *
+     * @return Has this animation been initialized.
+     * @see #initialize(int, int, int, int)
+     */
+    public boolean isInitialized() {
+        return mInitialized;
+    }
+
+    /**
+     * Initialize this animation with the dimensions of the object being
+     * animated as well as the objects parents. (This is to support animation
+     * sizes being specifed relative to these dimensions.)
+     *
+     * <p>Objects that interpret a Animations should call this method when
+     * the sizes of the object being animated and its parent are known, and
+     * before calling {@link #getTransformation}.
+     *
+     *
+     * @param width Width of the object being animated
+     * @param height Height of the object being animated
+     * @param parentWidth Width of the animated object's parent
+     * @param parentHeight Height of the animated object's parent
+     */
+    public void initialize(int width, int height, int parentWidth, int parentHeight) {
+        reset();
+        mInitialized = true;
+    }
+
+    /**
+     * Sets the acceleration curve for this animation. The interpolator is loaded as
+     * a resource from the specified context.
+     *
+     * @param context The application environment
+     * @param resID The resource identifier of the interpolator to load
+     * @attr ref android.R.styleable#Animation_interpolator
+     */
+    public void setInterpolator(Context context, int resID) {
+        setInterpolator(AnimationUtils.loadInterpolator(context, resID));
+    }
+
+    /**
+     * Sets the acceleration curve for this animation. Defaults to a linear
+     * interpolation.
+     *
+     * @param i The interpolator which defines the acceleration curve
+     * @attr ref android.R.styleable#Animation_interpolator
+     */
+    public void setInterpolator(Interpolator i) {
+        mInterpolator = i;
+    }
+
+    /**
+     * When this animation should start relative to the start time. This is most
+     * useful when composing complex animations using an {@link AnimationSet }
+     * where some of the animations components start at different times.
+     *
+     * @param startOffset When this Animation should start, in milliseconds from
+     *                    the start time of the root AnimationSet.
+     * @attr ref android.R.styleable#Animation_startOffset
+     */
+    public void setStartOffset(long startOffset) {
+        mStartOffset = startOffset;
+    }
+
+    /**
+     * How long this animation should last. The duration cannot be negative.
+     * 
+     * @param durationMillis Duration in milliseconds
+     *
+     * @throw java.lang.IllegalArgumentException if the duration is < 0
+     *
+     * @attr ref android.R.styleable#Animation_duration
+     */
+    public void setDuration(long durationMillis) {
+        if (durationMillis < 0) {
+            throw new IllegalArgumentException("Animation duration cannot be negative");
+        }
+        mDuration = durationMillis;
+    }
+
+    /**
+     * Ensure that the duration that this animation will run is not longer
+     * than <var>durationMillis</var>.  In addition to adjusting the duration
+     * itself, this ensures that the repeat count also will not make it run
+     * longer than the given time.
+     * 
+     * @param durationMillis The maximum duration the animation is allowed
+     * to run.
+     */
+    public void restrictDuration(long durationMillis) {
+        if (mStartOffset > durationMillis) {
+            mStartOffset = durationMillis;
+            mDuration = 0;
+            mRepeatCount = 0;
+            return;
+        }
+        
+        long dur = mDuration + mStartOffset;
+        if (dur > durationMillis) {
+            mDuration = dur = durationMillis-mStartOffset;
+        }
+        if (mRepeatCount < 0 || mRepeatCount > durationMillis
+                || (dur*mRepeatCount) > durationMillis) {
+            mRepeatCount = (int)(durationMillis/dur);
+        }
+    }
+    
+    /**
+     * How much to scale the duration by.
+     *
+     * @param scale The amount to scale the duration.
+     */
+    public void scaleCurrentDuration(float scale) {
+        mDuration = (long) (mDuration * scale);
+    }
+
+    /**
+     * When this animation should start. When the start time is set to
+     * {@link #START_ON_FIRST_FRAME}, the animation will start the first time
+     * {@link #getTransformation(long, Transformation)} is invoked. The time passed
+     * to this method should be obtained by calling
+     * {@link AnimationUtils#currentAnimationTimeMillis()} instead of
+     * {@link System#currentTimeMillis()}.
+     *
+     * @param startTimeMillis the start time in milliseconds
+     */
+    public void setStartTime(long startTimeMillis) {
+        mStartTime = startTimeMillis;
+        mStarted = mEnded = false;
+        mCycleFlip = false;
+        mRepeated = 0;
+        mMore = true;
+    }
+
+    /**
+     * Convenience method to start the animation the first time
+     * {@link #getTransformation(long, Transformation)} is invoked.
+     */
+    public void start() {
+        setStartTime(-1);
+    }
+
+    /**
+     * Convenience method to start the animation at the current time in
+     * milliseconds.
+     */
+    public void startNow() {
+        setStartTime(AnimationUtils.currentAnimationTimeMillis());
+    }
+
+    /**
+     * Defines what this animation should do when it reaches the end. This
+     * setting is applied only when the repeat count is either greater than
+     * 0 or {@link #INFINITE}. Defaults to {@link #RESTART}. 
+     *
+     * @param repeatMode {@link #RESTART} or {@link #REVERSE}
+     * @attr ref android.R.styleable#Animation_repeatMode
+     */
+    public void setRepeatMode(int repeatMode) {
+        mRepeatMode = repeatMode;
+    }
+
+    /**
+     * Sets how many times the animation should be repeated. If the repeat
+     * count is 0, the animation is never repeated. If the repeat count is
+     * greater than 0 or {@link #INFINITE}, the repeat mode will be taken
+     * into account. The repeat count if 0 by default.
+     *
+     * @param repeatCount the number of times the animation should be repeated
+     * @attr ref android.R.styleable#Animation_repeatCount
+     */
+    public void setRepeatCount(int repeatCount) {
+        if (repeatCount < 0) {
+            repeatCount = INFINITE;
+        }
+        mRepeatCount = repeatCount;
+    }
+
+    /**
+     * If fillEnabled is true, this animation will apply fillBefore and fillAfter.
+     *
+     * @return true if the animation will take fillBefore and fillAfter into account
+     * @attr ref android.R.styleable#Animation_fillEnabled
+     */
+    public boolean isFillEnabled() {
+        return mFillEnabled;
+    }
+
+    /**
+     * If fillEnabled is true, the animation will apply the value of fillBefore and
+     * fillAfter. Otherwise, fillBefore and fillAfter are ignored and the animation
+     * transformation is always applied.
+     *
+     * @param fillEnabled true if the animation should take fillBefore and fillAfter into account
+     * @attr ref android.R.styleable#Animation_fillEnabled
+     *
+     * @see #setFillBefore(boolean)
+     * @see #setFillAfter(boolean)
+     */
+    public void setFillEnabled(boolean fillEnabled) {
+        mFillEnabled = fillEnabled;
+    }
+
+    /**
+     * If fillBefore is true, this animation will apply its transformation
+     * before the start time of the animation. Defaults to true if not set.
+     * Note that this applies when using an {@link
+     * android.view.animation.AnimationSet AnimationSet} to chain
+     * animations. The transformation is not applied before the AnimationSet
+     * itself starts.
+     *
+     * @param fillBefore true if the animation should apply its transformation before it starts
+     * @attr ref android.R.styleable#Animation_fillBefore
+     *
+     * @see #setFillEnabled(boolean)
+     */
+    public void setFillBefore(boolean fillBefore) {
+        mFillBefore = fillBefore;
+    }
+
+    /**
+     * If fillAfter is true, the transformation that this animation performed
+     * will persist when it is finished. Defaults to false if not set.
+     * Note that this applies when using an {@link
+     * android.view.animation.AnimationSet AnimationSet} to chain
+     * animations. The transformation is not applied before the AnimationSet
+     * itself starts.
+     *
+     * @param fillAfter true if the animation should apply its transformation after it ends
+     * @attr ref android.R.styleable#Animation_fillAfter
+     *
+     * @see #setFillEnabled(boolean) 
+     */
+    public void setFillAfter(boolean fillAfter) {
+        mFillAfter = fillAfter;
+    }
+
+    /**
+     * Set the Z ordering mode to use while running the animation.
+     * 
+     * @param zAdjustment The desired mode, one of {@link #ZORDER_NORMAL},
+     * {@link #ZORDER_TOP}, or {@link #ZORDER_BOTTOM}.
+     * @attr ref android.R.styleable#Animation_zAdjustment
+     */
+    public void setZAdjustment(int zAdjustment) {
+        mZAdjustment = zAdjustment;
+    }
+    
+    /**
+     * Gets the acceleration curve type for this animation.
+     *
+     * @return the {@link Interpolator} associated to this animation
+     * @attr ref android.R.styleable#Animation_interpolator
+     */
+    public Interpolator getInterpolator() {
+        return mInterpolator;
+    }
+
+    /**
+     * When this animation should start. If the animation has not startet yet,
+     * this method might return {@link #START_ON_FIRST_FRAME}.
+     *
+     * @return the time in milliseconds when the animation should start or
+     *         {@link #START_ON_FIRST_FRAME}
+     */
+    public long getStartTime() {
+        return mStartTime;
+    }
+
+    /**
+     * How long this animation should last
+     *
+     * @return the duration in milliseconds of the animation
+     * @attr ref android.R.styleable#Animation_duration
+     */
+    public long getDuration() {
+        return mDuration;
+    }
+
+    /**
+     * When this animation should start, relative to StartTime
+     *
+     * @return the start offset in milliseconds
+     * @attr ref android.R.styleable#Animation_startOffset
+     */
+    public long getStartOffset() {
+        return mStartOffset;
+    }
+
+    /**
+     * Defines what this animation should do when it reaches the end.
+     *
+     * @return either one of {@link #REVERSE} or {@link #RESTART}
+     * @attr ref android.R.styleable#Animation_repeatMode
+     */
+    public int getRepeatMode() {
+        return mRepeatMode;
+    }
+
+    /**
+     * Defines how many times the animation should repeat. The default value
+     * is 0.
+     *
+     * @return the number of times the animation should repeat, or {@link #INFINITE}
+     * @attr ref android.R.styleable#Animation_repeatCount
+     */
+    public int getRepeatCount() {
+        return mRepeatCount;
+    }
+
+    /**
+     * If fillBefore is true, this animation will apply its transformation
+     * before the start time of the animation.
+     *
+     * @return true if the animation applies its transformation before it starts
+     * @attr ref android.R.styleable#Animation_fillBefore
+     */
+    public boolean getFillBefore() {
+        return mFillBefore;
+    }
+
+    /**
+     * If fillAfter is true, this animation will apply its transformation
+     * after the end time of the animation.
+     *
+     * @return true if the animation applies its transformation after it ends
+     * @attr ref android.R.styleable#Animation_fillAfter
+     */
+    public boolean getFillAfter() {
+        return mFillAfter;
+    }
+
+    /**
+     * Returns the Z ordering mode to use while running the animation as
+     * previously set by {@link #setZAdjustment}.
+     * 
+     * @return Returns one of {@link #ZORDER_NORMAL},
+     * {@link #ZORDER_TOP}, or {@link #ZORDER_BOTTOM}.
+     * @attr ref android.R.styleable#Animation_zAdjustment
+     */
+    public int getZAdjustment() {
+        return mZAdjustment;
+    }
+
+    /**
+     * <p>Indicates whether or not this animation will affect the transformation
+     * matrix. For instance, a fade animation will not affect the matrix whereas
+     * a scale animation will.</p>
+     *
+     * @return true if this animation will change the transformation matrix
+     */
+    public boolean willChangeTransformationMatrix() {
+        // assume we will change the matrix
+        return true;
+    }
+
+    /**
+     * <p>Indicates whether or not this animation will affect the bounds of the
+     * animated view. For instance, a fade animation will not affect the bounds
+     * whereas a 200% scale animation will.</p>
+     *
+     * @return true if this animation will change the view's bounds
+     */
+    public boolean willChangeBounds() {
+        // assume we will change the bounds
+        return true;
+    }
+
+    /**
+     * <p>Binds an animation listener to this animation. The animation listener
+     * is notified of animation events such as the end of the animation or the
+     * repetition of the animation.</p>
+     *
+     * @param listener the animation listener to be notified
+     */
+    public void setAnimationListener(AnimationListener listener) {
+        mListener = listener;
+    }
+
+    /**
+     * Gurantees that this animation has an interpolator. Will use
+     * a AccelerateDecelerateInterpolator is nothing else was specified.
+     */
+    protected void ensureInterpolator() {
+        if (mInterpolator == null) {
+            mInterpolator = new AccelerateDecelerateInterpolator();
+        }
+    }
+
+    /**
+     * Compute a hint at how long the entire animation may last, in milliseconds.
+     * Animations can be written to cause themselves to run for a different
+     * duration than what is computed here, but generally this should be
+     * accurate.
+     */
+    public long computeDurationHint() {
+        return (getStartOffset() + getDuration()) * (getRepeatCount() + 1);
+    }
+    
+    /**
+     * Gets the transformation to apply at a specified point in time. Implementations of this
+     * method should always replace the specified Transformation or document they are doing
+     * otherwise.
+     *
+     * @param currentTime Where we are in the animation. This is wall clock time.
+     * @param outTransformation A tranformation object that is provided by the
+     *        caller and will be filled in by the animation.
+     * @return True if the animation is still running
+     */
+    public boolean getTransformation(long currentTime, Transformation outTransformation) {
+        if (mStartTime == -1) {
+            mStartTime = currentTime;
+        }
+
+        final long startOffset = getStartOffset();
+        final long duration = mDuration;
+        float normalizedTime;
+        if (duration != 0) {
+            normalizedTime = ((float) (currentTime - (mStartTime + startOffset))) /
+                    (float) duration;
+        } else {
+            // time is a step-change with a zero duration
+            normalizedTime = currentTime < mStartTime ? 0.0f : 1.0f;
+        }
+
+        final boolean expired = normalizedTime >= 1.0f;
+        mMore = !expired;
+
+        if (!mFillEnabled) normalizedTime = Math.max(Math.min(normalizedTime, 1.0f), 0.0f);
+
+        if ((normalizedTime >= 0.0f || mFillBefore) && (normalizedTime <= 1.0f || mFillAfter)) {
+            if (!mStarted) {
+                if (mListener != null) {
+                    mListener.onAnimationStart(this);
+                }
+                mStarted = true;
+            }
+
+            if (mFillEnabled) normalizedTime = Math.max(Math.min(normalizedTime, 1.0f), 0.0f);
+
+            if (mCycleFlip) {
+                normalizedTime = 1.0f - normalizedTime;
+            }
+
+            final float interpolatedTime = mInterpolator.getInterpolation(normalizedTime);
+            applyTransformation(interpolatedTime, outTransformation);
+        }
+
+        if (expired) {
+            if (mRepeatCount == mRepeated) {
+                if (!mEnded) {
+                    if (mListener != null) {
+                        mListener.onAnimationEnd(this);
+                    }
+                    mEnded = true;
+                }
+            } else {
+                if (mRepeatCount > 0) {
+                    mRepeated++;
+                }
+
+                if (mRepeatMode == REVERSE) {
+                    mCycleFlip = !mCycleFlip;
+                }
+
+                mStartTime = -1;
+                mMore = true;
+
+                if (mListener != null) {
+                    mListener.onAnimationRepeat(this);
+                }
+            }
+        }
+
+        if (!mMore && mOneMoreTime) {
+            mOneMoreTime = false;
+            return true;
+        }
+
+        return mMore;
+    }
+
+    /**
+     * <p>Indicates whether this animation has started or not.</p>
+     *
+     * @return true if the animation has started, false otherwise
+     */
+    public boolean hasStarted() {
+        return mStarted;
+    }
+
+    /**
+     * <p>Indicates whether this animation has ended or not.</p>
+     *
+     * @return true if the animation has ended, false otherwise
+     */
+    public boolean hasEnded() {
+        return mEnded;
+    }
+
+    /**
+     * Helper for getTransformation. Subclasses should implement this to apply
+     * their transforms given an interpolation value.  Implementations of this
+     * method should always replace the specified Transformation or document
+     * they are doing otherwise.
+     * 
+     * @param interpolatedTime The value of the normalized time (0.0 to 1.0)
+     *        after it has been run through the interpolation function.
+     * @param t The Transofrmation object to fill in with the current
+     *        transforms.
+     */
+    protected void applyTransformation(float interpolatedTime, Transformation t) {
+    }
+
+    /**
+     * Convert the information in the description of a size to an actual
+     * dimension
+     *
+     * @param type One of Animation.ABSOLUTE, Animation.RELATIVE_TO_SELF, or
+     *             Animation.RELATIVE_TO_PARENT.
+     * @param value The dimension associated with the type parameter
+     * @param size The size of the object being animated
+     * @param parentSize The size of the parent of the object being animated
+     * @return The dimension to use for the animation
+     */
+    protected float resolveSize(int type, float value, int size, int parentSize) {
+        switch (type) {
+            case ABSOLUTE:
+                return value;
+            case RELATIVE_TO_SELF:
+                return size * value;
+            case RELATIVE_TO_PARENT:
+                return parentSize * value;
+            default:
+                return value;
+        }
+    }
+
+    /**
+     * @param left
+     * @param top
+     * @param right
+     * @param bottom
+     * @param invalidate
+     * @param transformation
+     * 
+     * @hide
+     */
+    public void getInvalidateRegion(int left, int top, int right, int bottom,
+            RectF invalidate, Transformation transformation) {
+
+        final RectF tempRegion = mRegion;
+        final RectF previousRegion = mPreviousRegion;
+
+        invalidate.set(left, top, right, bottom);
+        transformation.getMatrix().mapRect(invalidate);
+        tempRegion.set(invalidate);
+        invalidate.union(previousRegion);
+
+        previousRegion.set(tempRegion);
+
+        final Transformation tempTransformation = mTransformation;
+        final Transformation previousTransformation = mPreviousTransformation;
+
+        tempTransformation.set(transformation);
+        transformation.set(previousTransformation);
+        previousTransformation.set(tempTransformation);
+    }
+
+    /**
+     * @param left
+     * @param top
+     * @param right
+     * @param bottom
+     *
+     * @hide
+     */
+    public void initializeInvalidateRegion(int left, int top, int right, int bottom) {
+        final RectF region = mPreviousRegion;
+        region.set(left, top, right, bottom);
+        if (mFillBefore) {
+            final Transformation previousTransformation = mPreviousTransformation;
+            applyTransformation(0.0f, previousTransformation);
+        }
+    }
+
+    /**
+     * Utility class to parse a string description of a size.
+     */
+    protected static class Description {
+        /**
+         * One of Animation.ABSOLUTE, Animation.RELATIVE_TO_SELF, or
+         * Animation.RELATIVE_TO_PARENT.
+         */
+        public int type;
+
+        /**
+         * The absolute or relative dimension for this Description.
+         */
+        public float value;
+
+        /**
+         * Size descriptions can appear inthree forms:
+         * <ol>
+         * <li>An absolute size. This is represented by a number.</li>
+         * <li>A size relative to the size of the object being animated. This
+         * is represented by a number followed by "%".</li> *
+         * <li>A size relative to the size of the parent of object being
+         * animated. This is represented by a number followed by "%p".</li>
+         * </ol>
+         * @param value The typed value to parse
+         * @return The parsed version of the description
+         */
+        static Description parseValue(TypedValue value) {
+            Description d = new Description();
+            if (value == null) {
+                d.type = ABSOLUTE;
+                d.value = 0;
+            } else {
+                if (value.type == TypedValue.TYPE_FRACTION) {
+                    d.type = (value.data & TypedValue.COMPLEX_UNIT_MASK) ==
+                            TypedValue.COMPLEX_UNIT_FRACTION_PARENT ?
+                                    RELATIVE_TO_PARENT : RELATIVE_TO_SELF;
+                    d.value = TypedValue.complexToFloat(value.data);
+                    return d;
+                } else if (value.type == TypedValue.TYPE_FLOAT) {
+                    d.type = ABSOLUTE;
+                    d.value = value.getFloat();
+                    return d;
+                } else if (value.type >= TypedValue.TYPE_FIRST_INT &&
+                        value.type <= TypedValue.TYPE_LAST_INT) {
+                    d.type = ABSOLUTE;
+                    d.value = value.data;
+                    return d;
+                }
+            }
+
+            d.type = ABSOLUTE;
+            d.value = 0.0f;
+
+            return d;
+        }
+    }
+
+    /**
+     * <p>An animation listener receives notifications from an animation.
+     * Notifications indicate animation related events, such as the end or the
+     * repetition of the animation.</p>
+     */
+    public static interface AnimationListener {
+        /**
+         * <p>Notifies the start of the animation.</p>
+         *
+         * @param animation The started animation.
+         */
+        void onAnimationStart(Animation animation);
+
+        /**
+         * <p>Notifies the end of the animation. This callback is not invoked
+         * for animations with repeat count set to INFINITE.</p>
+         *
+         * @param animation The animation which reached its end.
+         */
+        void onAnimationEnd(Animation animation);
+
+        /**
+         * <p>Notifies the repetition of the animation.</p>
+         *
+         * @param animation The animation which was repeated.
+         */
+        void onAnimationRepeat(Animation animation);
+    }
+}
diff --git a/core/java/android/view/animation/AnimationSet.java b/core/java/android/view/animation/AnimationSet.java
new file mode 100644
index 0000000..7b56f00
--- /dev/null
+++ b/core/java/android/view/animation/AnimationSet.java
@@ -0,0 +1,472 @@
+/*
+ * 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 android.view.animation;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.util.AttributeSet;
+import android.graphics.RectF;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Represents a group of Animations that should be played together.
+ * The transformation of each individual animation are composed 
+ * together into a single transform. 
+ * If AnimationSet sets any properties that its children also set
+ * (for example, duration or fillBefore), the values of AnimationSet
+ * override the child values.
+ */
+public class AnimationSet extends Animation {
+    private static final int PROPERTY_FILL_AFTER_MASK         = 0x1;
+    private static final int PROPERTY_FILL_BEFORE_MASK        = 0x2;
+    private static final int PROPERTY_REPEAT_MODE_MASK        = 0x4;
+    private static final int PROPERTY_START_OFFSET_MASK       = 0x8;
+    private static final int PROPERTY_SHARE_INTERPOLATOR_MASK = 0x10;
+    private static final int PROPERTY_DURATION_MASK           = 0x20;
+    private static final int PROPERTY_MORPH_MATRIX_MASK       = 0x40;
+    private static final int PROPERTY_CHANGE_BOUNDS_MASK      = 0x80;
+
+    private int mFlags = 0;
+
+    private ArrayList<Animation> mAnimations = new ArrayList<Animation>();
+
+    private Transformation mTempTransformation = new Transformation();
+
+    private long mLastEnd;
+
+    private long[] mStoredOffsets;
+
+    /**
+     * Constructor used whan an AnimationSet is loaded from a resource. 
+     * 
+     * @param context Application context to use
+     * @param attrs Attribute set from which to read values
+     */
+    public AnimationSet(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        
+        TypedArray a =
+            context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.AnimationSet);
+        
+        setFlag(PROPERTY_SHARE_INTERPOLATOR_MASK,
+                a.getBoolean(com.android.internal.R.styleable.AnimationSet_shareInterpolator, true));
+        init();
+        
+        a.recycle();
+    }
+    
+    
+    /**
+     * Constructor to use when building an AnimationSet from code
+     * 
+     * @param shareInterpolator Pass true if all of the animations in this set
+     *        should use the interpolator assocciated with this AnimationSet.
+     *        Pass false if each animation should use its own interpolator.
+     */
+    public AnimationSet(boolean shareInterpolator) {
+        setFlag(PROPERTY_SHARE_INTERPOLATOR_MASK, shareInterpolator);
+        init();
+    }
+
+    @Override
+    protected AnimationSet clone() throws CloneNotSupportedException {
+        final AnimationSet animation = (AnimationSet) super.clone();
+        animation.mTempTransformation = new Transformation();
+        animation.mAnimations = new ArrayList<Animation>();
+
+        final int count = mAnimations.size();
+        final ArrayList<Animation> animations = mAnimations;
+
+        for (int i = 0; i < count; i++) {
+            animation.mAnimations.add(animations.get(i).clone());
+        }
+
+        return animation;
+    }
+
+    private void setFlag(int mask, boolean value) {
+        if (value) {
+            mFlags |= mask;
+        } else {
+            mFlags &= ~mask;
+        }
+    }
+
+    private void init() {
+        mStartTime = 0;
+        mDuration = 0;
+    }
+
+    @Override
+    public void setFillAfter(boolean fillAfter) {
+        mFlags |= PROPERTY_FILL_AFTER_MASK;
+        super.setFillAfter(fillAfter);
+    }
+
+    @Override
+    public void setFillBefore(boolean fillBefore) {
+        mFlags |= PROPERTY_FILL_BEFORE_MASK;
+        super.setFillBefore(fillBefore);
+    }
+
+    @Override
+    public void setRepeatMode(int repeatMode) {
+        mFlags |= PROPERTY_REPEAT_MODE_MASK;
+        super.setRepeatMode(repeatMode);
+    }
+
+    @Override
+    public void setStartOffset(long startOffset) {
+        mFlags |= PROPERTY_START_OFFSET_MASK;
+        super.setStartOffset(startOffset);
+    }
+
+    /**
+     * <p>Sets the duration of every child animation.</p>
+     *
+     * @param durationMillis the duration of the animation, in milliseconds, for
+     *        every child in this set
+     */
+    @Override
+    public void setDuration(long durationMillis) {
+        mFlags |= PROPERTY_DURATION_MASK;
+        super.setDuration(durationMillis);
+    }
+
+    /**
+     * Add a child animation to this animation set.
+     * The transforms of the child animations are applied in the order
+     * that they were added
+     * @param a Animation to add.
+     */
+    public void addAnimation(Animation a) {
+        mAnimations.add(a);
+
+        boolean noMatrix = (mFlags & PROPERTY_MORPH_MATRIX_MASK) == 0;
+        if (noMatrix && a.willChangeTransformationMatrix()) {
+            mFlags |= PROPERTY_MORPH_MATRIX_MASK;
+        }
+
+        boolean changeBounds = (mFlags & PROPERTY_CHANGE_BOUNDS_MASK) == 0;
+        if (changeBounds && a.willChangeTransformationMatrix()) {
+            mFlags |= PROPERTY_CHANGE_BOUNDS_MASK;
+        }
+
+        if (mAnimations.size() == 1) {
+            mDuration = a.getStartOffset() + a.getDuration();
+            mLastEnd = mStartOffset + mDuration;
+        } else {
+            mLastEnd = Math.max(mLastEnd, a.getStartOffset() + a.getDuration());
+            mDuration = mLastEnd - mStartOffset;
+        }
+    }
+    
+    /**
+     * Sets the start time of this animation and all child animations
+     * 
+     * @see android.view.animation.Animation#setStartTime(long)
+     */
+    @Override
+    public void setStartTime(long startTimeMillis) {
+        super.setStartTime(startTimeMillis);
+
+        final int count = mAnimations.size();
+        final ArrayList<Animation> animations = mAnimations;
+
+        for (int i = 0; i < count; i++) {
+            Animation a = animations.get(i);
+            a.setStartTime(startTimeMillis);
+        }
+    }
+
+    @Override
+    public long getStartTime() {
+        long startTime = Long.MAX_VALUE;
+
+        final int count = mAnimations.size();
+        final ArrayList<Animation> animations = mAnimations;
+
+        for (int i = 0; i < count; i++) {
+            Animation a = animations.get(i);
+            startTime = Math.min(startTime, a.getStartTime());
+        }
+
+        return startTime;
+    }
+
+    @Override
+    public void restrictDuration(long durationMillis) {
+        super.restrictDuration(durationMillis);
+
+        final ArrayList<Animation> animations = mAnimations;
+        int count = animations.size();
+
+        for (int i = 0; i < count; i++) {
+            animations.get(i).restrictDuration(durationMillis);
+        }
+    }
+    
+    /**
+     * The duration of an AnimationSet is defined to be the 
+     * duration of the longest child animation.
+     * 
+     * @see android.view.animation.Animation#getDuration()
+     */
+    @Override
+    public long getDuration() {
+        final ArrayList<Animation> animations = mAnimations;
+        final int count = animations.size();
+        long duration = 0;
+
+        boolean durationSet = (mFlags & PROPERTY_DURATION_MASK) == PROPERTY_DURATION_MASK;
+        if (durationSet) {
+            duration = mDuration;
+        } else {
+            for (int i = 0; i < count; i++) {
+                duration = Math.max(duration, animations.get(i).getDuration());
+            }
+        }
+
+        return duration;
+    }
+
+    /**
+     * The duration hint of an animation set is the maximum of the duration
+     * hints of all of its component animations.
+     * 
+     * @see android.view.animation.Animation#computeDurationHint
+     */
+    public long computeDurationHint() {
+        long duration = 0;
+        final int count = mAnimations.size();
+        final ArrayList<Animation> animations = mAnimations;
+        for (int i = count - 1; i >= 0; --i) {
+            final long d = animations.get(i).computeDurationHint();
+            if (d > duration) duration = d;
+        }
+        return duration;
+    }
+
+    /**
+     * @hide
+     */
+    public void getInvalidateRegion(int left, int top, int right, int bottom,
+            RectF invalidate, Transformation transformation) {
+
+        final RectF previousRegion = mPreviousRegion;
+
+        invalidate.set(left, top, right, bottom);
+        transformation.getMatrix().mapRect(invalidate);
+        invalidate.union(previousRegion);
+
+        previousRegion.set(left, top, right, bottom);
+        transformation.getMatrix().mapRect(previousRegion);
+
+        final Transformation tempTransformation = mTransformation;
+        final Transformation previousTransformation = mPreviousTransformation;
+
+        tempTransformation.set(transformation);
+        transformation.set(previousTransformation);
+        previousTransformation.set(tempTransformation);
+    }
+
+    /**
+     * @hide
+     */
+    public void initializeInvalidateRegion(int left, int top, int right, int bottom) {
+        final RectF region = mPreviousRegion;
+        region.set(left, top, right, bottom);
+
+        if (mFillBefore) {
+            final int count = mAnimations.size();
+            final ArrayList<Animation> animations = mAnimations;
+            final Transformation temp = mTempTransformation;
+
+            final Transformation previousTransformation = mPreviousTransformation;
+
+            for (int i = count - 1; i >= 0; --i) {
+                final Animation a = animations.get(i);
+
+                temp.clear();
+                a.applyTransformation(0.0f, temp);
+                previousTransformation.compose(temp);
+            }
+        }
+    }
+
+    /**
+     * The transformation of an animation set is the concatenation of all of its
+     * component animations.
+     * 
+     * @see android.view.animation.Animation#getTransformation
+     */
+    @Override
+    public boolean getTransformation(long currentTime, Transformation t) {
+        final int count = mAnimations.size();
+        final ArrayList<Animation> animations = mAnimations;
+        final Transformation temp = mTempTransformation;
+
+        boolean more = false;
+        boolean started = false;
+        boolean ended = true;
+
+        t.clear();
+
+        for (int i = count - 1; i >= 0; --i) {
+            final Animation a = animations.get(i);
+
+            temp.clear();
+            more = a.getTransformation(currentTime, temp) || more;
+            t.compose(temp);
+
+            started = started || a.hasStarted();
+            ended = a.hasEnded() && ended;
+        }
+
+        if (started && !mStarted) {
+            if (mListener != null) {
+                mListener.onAnimationStart(this);
+            }
+            mStarted = true;
+        }
+
+        if (ended != mEnded) {
+            if (mListener != null) {
+                mListener.onAnimationEnd(this);
+            }
+            mEnded = ended;
+        }
+
+        return more;
+    }
+    
+    /**
+     * @see android.view.animation.Animation#scaleCurrentDuration(float)
+     */
+    @Override
+    public void scaleCurrentDuration(float scale) {
+        final ArrayList<Animation> animations = mAnimations;
+        int count = animations.size();
+        for (int i = 0; i < count; i++) {
+            animations.get(i).scaleCurrentDuration(scale);
+        }
+    }
+
+    /**
+     * @see android.view.animation.Animation#initialize(int, int, int, int)
+     */
+    @Override
+    public void initialize(int width, int height, int parentWidth, int parentHeight) {
+        super.initialize(width, height, parentWidth, parentHeight);
+
+        boolean durationSet = (mFlags & PROPERTY_DURATION_MASK) == PROPERTY_DURATION_MASK;
+        boolean fillAfterSet = (mFlags & PROPERTY_FILL_AFTER_MASK) == PROPERTY_FILL_AFTER_MASK;
+        boolean fillBeforeSet = (mFlags & PROPERTY_FILL_BEFORE_MASK) == PROPERTY_FILL_BEFORE_MASK;
+        boolean repeatModeSet = (mFlags & PROPERTY_REPEAT_MODE_MASK) == PROPERTY_REPEAT_MODE_MASK;
+        boolean shareInterpolator = (mFlags & PROPERTY_SHARE_INTERPOLATOR_MASK)
+                == PROPERTY_SHARE_INTERPOLATOR_MASK;
+        boolean startOffsetSet = (mFlags & PROPERTY_START_OFFSET_MASK)
+                == PROPERTY_START_OFFSET_MASK;
+
+        if (shareInterpolator) {
+            ensureInterpolator();
+        }
+
+        final ArrayList<Animation> children = mAnimations;
+        final int count = children.size();
+
+        final long duration = mDuration;
+        final boolean fillAfter = mFillAfter;
+        final boolean fillBefore = mFillBefore;
+        final int repeatMode = mRepeatMode;
+        final Interpolator interpolator = mInterpolator;
+        final long startOffset = mStartOffset;
+
+
+        long[] storedOffsets = mStoredOffsets;
+        if (storedOffsets == null || storedOffsets.length != count) {
+            storedOffsets = mStoredOffsets = new long[count];
+        }
+
+        for (int i = 0; i < count; i++) {
+            Animation a = children.get(i);
+            if (durationSet) {
+                a.setDuration(duration);
+            }
+            if (fillAfterSet) {
+                a.setFillAfter(fillAfter);
+            }
+            if (fillBeforeSet) {
+                a.setFillBefore(fillBefore);
+            }
+            if (repeatModeSet) {
+                a.setRepeatMode(repeatMode);
+            }
+            if (shareInterpolator) {
+                a.setInterpolator(interpolator);
+            }
+            if (startOffsetSet) {
+                long offset = a.getStartOffset();
+                a.setStartOffset(offset + startOffset);
+                storedOffsets[i] = offset;
+            }
+            a.initialize(width, height, parentWidth, parentHeight);
+        }
+    }
+
+    @Override
+    public void reset() {
+        super.reset();
+        restoreChildrenStartOffset();
+    }
+
+    /**
+     * @hide
+     */
+    void restoreChildrenStartOffset() {
+        final long[] offsets = mStoredOffsets;
+        if (offsets == null) return;
+
+        final ArrayList<Animation> children = mAnimations;
+        final int count = children.size();
+
+
+        for (int i = 0; i < count; i++) {
+            children.get(i).setStartOffset(offsets[i]);
+        }
+    }
+
+    /**
+     * @return All the child animations in this AnimationSet. Note that
+     * this may include other AnimationSets, which are not expanded.
+     */
+    public List<Animation> getAnimations() {
+        return mAnimations;
+    }
+
+    @Override
+    public boolean willChangeTransformationMatrix() {
+        return (mFlags & PROPERTY_MORPH_MATRIX_MASK) == PROPERTY_MORPH_MATRIX_MASK;
+    }
+
+    @Override
+    public boolean willChangeBounds() {
+        return (mFlags & PROPERTY_CHANGE_BOUNDS_MASK) == PROPERTY_CHANGE_BOUNDS_MASK;
+    }
+}
diff --git a/core/java/android/view/animation/AnimationUtils.java b/core/java/android/view/animation/AnimationUtils.java
new file mode 100644
index 0000000..ce3cdc5
--- /dev/null
+++ b/core/java/android/view/animation/AnimationUtils.java
@@ -0,0 +1,329 @@
+/*
+ * Copyright (C) 2007 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 android.view.animation;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import android.content.Context;
+import android.content.res.XmlResourceParser;
+import android.content.res.Resources.NotFoundException;
+import android.util.AttributeSet;
+import android.util.Xml;
+import android.os.SystemClock;
+
+import java.io.IOException;
+
+/**
+ * Defines common utilities for working with animations.
+ *
+ */
+public class AnimationUtils {
+    /**
+     * Returns the current animation time in milliseconds. This time should be used when invoking
+     * {@link Animation#setStartTime(long)}. Refer to {@link android.os.SystemClock} for more
+     * information about the different available clocks. The clock used by this method is
+     * <em>not</em> the "wall" clock (it is not {@link System#currentTimeMillis}).
+     *
+     * @return the current animation time in milliseconds
+     *
+     * @see android.os.SystemClock
+     */
+    public static long currentAnimationTimeMillis() {
+        return SystemClock.uptimeMillis();
+    }
+
+    /**
+     * Loads an {@link Animation} object from a resource
+     * 
+     * @param context Application context used to access resources
+     * @param id The resource id of the animation to load
+     * @return The animation object reference by the specified id
+     * @throws NotFoundException when the animation cannot be loaded
+     */
+    public static Animation loadAnimation(Context context, int id)
+            throws NotFoundException {
+
+        XmlResourceParser parser = null;
+        try {
+            parser = context.getResources().getAnimation(id);
+            return createAnimationFromXml(context, parser);
+        } catch (XmlPullParserException ex) {
+            NotFoundException rnf = new NotFoundException(
+                    "Can't load animation resource ID #0x"
+                            + Integer.toHexString(id));
+            rnf.initCause(ex);
+            throw rnf;
+        } catch (IOException ex) {
+            NotFoundException rnf = new NotFoundException(
+                    "Can't load animation resource ID #0x"
+                            + Integer.toHexString(id));
+            rnf.initCause(ex);
+            throw rnf;
+        } finally {
+            if (parser != null) parser.close();
+        }
+    }
+
+    private static Animation createAnimationFromXml(Context c, XmlPullParser parser)
+            throws XmlPullParserException, IOException {
+        return createAnimationFromXml(c, parser, null, Xml.asAttributeSet(parser));
+    }
+    
+    private static Animation createAnimationFromXml(Context c, XmlPullParser parser, AnimationSet parent, AttributeSet attrs)
+    throws XmlPullParserException, IOException {
+        
+        Animation anim = null;
+ 
+        // Make sure we are on a start tag.
+        int type = parser.getEventType();
+        int depth = parser.getDepth();
+
+        while (((type=parser.next()) != XmlPullParser.END_TAG || parser.getDepth() > depth)
+               && type != XmlPullParser.END_DOCUMENT) {
+
+            if (type != XmlPullParser.START_TAG) {
+                continue;
+            }
+
+            String  name = parser.getName();
+    
+            if (name.equals("set")) {
+                anim = new AnimationSet(c, attrs);
+                createAnimationFromXml(c, parser, (AnimationSet)anim, attrs);
+            } else if (name.equals("alpha")) {
+                anim = new AlphaAnimation(c, attrs);
+            } else if (name.equals("scale")) {
+                anim = new ScaleAnimation(c, attrs);
+            }  else if (name.equals("rotate")) {
+                anim = new RotateAnimation(c, attrs);
+            }  else if (name.equals("translate")) {
+                anim = new TranslateAnimation(c, attrs);
+            } else {
+                throw new RuntimeException("Unknown animation name: " + parser.getName());
+            }
+
+            if (parent != null) {
+                parent.addAnimation(anim);
+            }
+        }
+    
+        return anim;
+
+    }
+
+    public static LayoutAnimationController loadLayoutAnimation(
+            Context context, int id) throws NotFoundException {
+        
+        XmlResourceParser parser = null;
+        try {
+            parser = context.getResources().getAnimation(id);
+            return createLayoutAnimationFromXml(context, parser);
+        } catch (XmlPullParserException ex) {
+            NotFoundException rnf = new NotFoundException(
+                    "Can't load animation resource ID #0x" +
+                            Integer.toHexString(id));
+            rnf.initCause(ex);
+            throw rnf;
+        } catch (IOException ex) {
+            NotFoundException rnf = new NotFoundException(
+                    "Can't load animation resource ID #0x" +
+                            Integer .toHexString(id));
+            rnf.initCause(ex);
+            throw rnf;
+        } finally {
+            if (parser != null) parser.close();
+        }
+    }
+
+    private static LayoutAnimationController createLayoutAnimationFromXml(
+            Context c, XmlPullParser parser)
+            throws XmlPullParserException, IOException {
+        return createLayoutAnimationFromXml(c, parser,
+                Xml.asAttributeSet(parser));
+    }
+
+    private static LayoutAnimationController createLayoutAnimationFromXml(
+            Context c, XmlPullParser parser, AttributeSet attrs)
+            throws XmlPullParserException, IOException {
+
+        LayoutAnimationController controller = null;
+
+        int type;
+        int depth = parser.getDepth();
+
+        while (((type = parser.next()) != XmlPullParser.END_TAG
+                || parser.getDepth() > depth)
+                && type != XmlPullParser.END_DOCUMENT) {
+
+            if (type != XmlPullParser.START_TAG) {
+                continue;
+            }
+
+            String name = parser.getName();
+
+            if ("layoutAnimation".equals(name)) {
+                controller = new LayoutAnimationController(c, attrs);
+            } else if ("gridLayoutAnimation".equals(name)) {
+                controller = new GridLayoutAnimationController(c, attrs);
+            } else {
+                throw new RuntimeException("Unknown layout animation name: " +
+                        name);
+            }
+        }
+
+        return controller;
+    }
+
+    /**
+     * Make an animation for objects becoming visible. Uses a slide and fade
+     * effect.
+     * 
+     * @param c Context for loading resources
+     * @param fromLeft is the object to be animated coming from the left
+     * @return The new animation
+     */
+    public static Animation makeInAnimation(Context c, boolean fromLeft)
+    {
+        
+        Animation a;
+        if (fromLeft) {
+            a = AnimationUtils.loadAnimation(c, com.android.internal.R.anim.slide_in_left);
+        } else {
+            a = AnimationUtils.loadAnimation(c, com.android.internal.R.anim.slide_in_right);
+        }
+
+        a.setInterpolator(new DecelerateInterpolator());
+        a.setStartTime(currentAnimationTimeMillis());
+        return a;
+    }
+    
+    /**
+     * Make an animation for objects becoming invisible. Uses a slide and fade
+     * effect.
+     * 
+     * @param c Context for loading resources
+     * @param toRight is the object to be animated exiting to the right
+     * @return The new animation
+     */
+    public static Animation makeOutAnimation(Context c, boolean toRight)
+    {   
+        
+        Animation a;
+        if (toRight) {
+            a = AnimationUtils.loadAnimation(c, com.android.internal.R.anim.slide_out_right);
+        } else {
+            a = AnimationUtils.loadAnimation(c, com.android.internal.R.anim.slide_out_left);
+        }
+        
+        a.setInterpolator(new AccelerateInterpolator());
+        a.setStartTime(currentAnimationTimeMillis());
+        return a;
+    }
+
+    
+    /**
+     * Make an animation for objects becoming visible. Uses a slide up and fade
+     * effect.
+     * 
+     * @param c Context for loading resources
+     * @return The new animation
+     */
+    public static Animation makeInChildBottomAnimation(Context c)
+    {   
+
+        Animation a;
+        a = AnimationUtils.loadAnimation(c, com.android.internal.R.anim.slide_in_child_bottom);
+        a.setInterpolator(new AccelerateInterpolator());
+        a.setStartTime(currentAnimationTimeMillis());
+        return a;
+    }
+    
+    /**
+     * Loads an {@link Interpolator} object from a resource
+     * 
+     * @param context Application context used to access resources
+     * @param id The resource id of the animation to load
+     * @return The animation object reference by the specified id
+     * @throws NotFoundException
+     */
+    public static Interpolator loadInterpolator(Context context, int id)
+            throws NotFoundException {
+
+        XmlResourceParser parser = null;
+        try {
+            parser = context.getResources().getAnimation(id);
+            return createInterpolatorFromXml(context, parser);
+        } catch (XmlPullParserException ex) {
+            NotFoundException rnf = new NotFoundException(
+                    "Can't load animation resource ID #0x"
+                            + Integer.toHexString(id));
+            rnf.initCause(ex);
+            throw rnf;
+        } catch (IOException ex) {
+            NotFoundException rnf = new NotFoundException(
+                    "Can't load animation resource ID #0x"
+                            + Integer.toHexString(id));
+            rnf.initCause(ex);
+            throw rnf;
+        } finally {
+            if (parser != null) parser.close();
+        }
+
+    }
+    
+    private static Interpolator createInterpolatorFromXml(Context c, XmlPullParser parser)
+    throws XmlPullParserException, IOException {
+        
+        Interpolator interpolator = null;
+ 
+        // Make sure we are on a start tag.
+        int type = parser.getEventType();
+        int depth = parser.getDepth();
+
+        while (((type=parser.next()) != XmlPullParser.END_TAG || parser.getDepth() > depth)
+               && type != XmlPullParser.END_DOCUMENT) {
+
+            if (type != XmlPullParser.START_TAG) {
+                continue;
+            }
+
+            AttributeSet attrs = Xml.asAttributeSet(parser);
+            
+            String  name = parser.getName();
+    
+            
+            if (name.equals("linearInterpolator")) {
+                interpolator = new LinearInterpolator(c, attrs);
+            } else if (name.equals("accelerateInterpolator")) {
+                interpolator = new AccelerateInterpolator(c, attrs);
+            } else if (name.equals("decelerateInterpolator")) {
+                interpolator = new DecelerateInterpolator(c, attrs);
+            }  else if (name.equals("accelerateDecelerateInterpolator")) {
+                interpolator = new AccelerateDecelerateInterpolator(c, attrs);
+            }  else if (name.equals("cycleInterpolator")) {
+                interpolator = new CycleInterpolator(c, attrs);
+            } else {
+                throw new RuntimeException("Unknown interpolator name: " + parser.getName());
+            }
+
+        }
+    
+        return interpolator;
+
+    }
+}
diff --git a/core/java/android/view/animation/CycleInterpolator.java b/core/java/android/view/animation/CycleInterpolator.java
new file mode 100644
index 0000000..d355c23
--- /dev/null
+++ b/core/java/android/view/animation/CycleInterpolator.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2007 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 android.view.animation;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.util.AttributeSet;
+
+/**
+ * Repeats the animation for a specified number of cycles. The
+ * rate of change follows a sinusoidal pattern.
+ *
+ */
+public class CycleInterpolator implements Interpolator {
+    public CycleInterpolator(float cycles) {
+        mCycles = cycles;
+    }
+    
+    public CycleInterpolator(Context context, AttributeSet attrs) {
+        TypedArray a =
+            context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.CycleInterpolator);
+        
+        mCycles = a.getFloat(com.android.internal.R.styleable.CycleInterpolator_cycles, 1.0f);
+        
+        a.recycle();
+    }
+    
+    public float getInterpolation(float input) {
+        return (float)(Math.sin(2 * mCycles * Math.PI * input));
+    }
+    
+    private float mCycles;
+}
diff --git a/core/java/android/view/animation/DecelerateInterpolator.java b/core/java/android/view/animation/DecelerateInterpolator.java
new file mode 100644
index 0000000..176169e
--- /dev/null
+++ b/core/java/android/view/animation/DecelerateInterpolator.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2007 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 android.view.animation;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.util.AttributeSet;
+
+/**
+ * An interpolator where the rate of change starts out quickly and 
+ * and then decelerates.
+ *
+ */
+public class DecelerateInterpolator implements Interpolator {
+    public DecelerateInterpolator() {
+    }
+    
+    /**
+     * Constructor
+     * 
+     * @param factor Degree to which the animation should be eased. Seting factor to 1.0f produces
+     *        an upside-down y=x^2 parabola. Increasing factor above 1.0f makes exaggerates the
+     *        ease-out effect (i.e., it starts even faster and ends evens slower)
+     */
+    public DecelerateInterpolator(float factor) {
+        mFactor = factor;
+    }
+    
+    public DecelerateInterpolator(Context context, AttributeSet attrs) {
+        TypedArray a =
+            context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.DecelerateInterpolator);
+        
+        mFactor = a.getFloat(com.android.internal.R.styleable.DecelerateInterpolator_factor, 1.0f);
+        
+        a.recycle();
+    }
+    
+    public float getInterpolation(float input) {
+        if (mFactor == 1.0f) {
+            return (float)(1.0f - (1.0f - input) * (1.0f - input));
+        } else {
+            return (float)(1.0f - Math.pow((1.0f - input), 2 * mFactor));
+        }
+    }
+    
+    private float mFactor = 1.0f;
+}
diff --git a/core/java/android/view/animation/GridLayoutAnimationController.java b/core/java/android/view/animation/GridLayoutAnimationController.java
new file mode 100644
index 0000000..9161d8b
--- /dev/null
+++ b/core/java/android/view/animation/GridLayoutAnimationController.java
@@ -0,0 +1,424 @@
+/*
+ * Copyright (C) 2007 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 android.view.animation;
+
+import android.view.View;
+import android.view.ViewGroup;
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.util.AttributeSet;
+
+import java.util.Random;
+
+/**
+ * A layout animation controller is used to animated a grid layout's children.
+ *
+ * While {@link LayoutAnimationController} relies only on the index of the child
+ * in the view group to compute the animation delay, this class uses both the
+ * X and Y coordinates of the child within a grid.
+ *
+ * In addition, the animation direction can be controlled. The default direction
+ * is <code>DIRECTION_LEFT_TO_RIGHT | DIRECTION_TOP_TO_BOTTOM</code>. You can
+ * also set the animation priority to columns or rows. The default priority is
+ * none.
+ *
+ * Information used to compute the animation delay of each child are stored
+ * in an instance of
+ * {@link android.view.animation.GridLayoutAnimationController.AnimationParameters},
+ * itself stored in the {@link android.view.ViewGroup.LayoutParams} of the view.
+ *
+ * @see LayoutAnimationController
+ * @see android.widget.GridView
+ * 
+ * @attr ref android.R.styleable#GridLayoutAnimation_columnDelay
+ * @attr ref android.R.styleable#GridLayoutAnimation_rowDelay
+ * @attr ref android.R.styleable#GridLayoutAnimation_direction
+ * @attr ref android.R.styleable#GridLayoutAnimation_directionPriority
+ */
+public class GridLayoutAnimationController extends LayoutAnimationController {
+    /**
+     * Animates the children starting from the left of the grid to the right.
+     */
+    public static final int DIRECTION_LEFT_TO_RIGHT = 0x0;
+
+    /**
+     * Animates the children starting from the right of the grid to the left.
+     */
+    public static final int DIRECTION_RIGHT_TO_LEFT = 0x1;
+
+    /**
+     * Animates the children starting from the top of the grid to the bottom.
+     */
+    public static final int DIRECTION_TOP_TO_BOTTOM = 0x0;
+
+    /**
+     * Animates the children starting from the bottom of the grid to the top.
+     */
+    public static final int DIRECTION_BOTTOM_TO_TOP = 0x2;
+
+    /**
+     * Bitmask used to retrieve the horizontal component of the direction.
+     */
+    public static final int DIRECTION_HORIZONTAL_MASK = 0x1;
+
+    /**
+     * Bitmask used to retrieve the vertical component of the direction.
+     */
+    public static final int DIRECTION_VERTICAL_MASK   = 0x2;
+
+    /**
+     * Rows and columns are animated at the same time.
+     */
+    public static final int PRIORITY_NONE   = 0;
+
+    /**
+     * Columns are animated first.
+     */
+    public static final int PRIORITY_COLUMN = 1;
+
+    /**
+     * Rows are animated first.
+     */
+    public static final int PRIORITY_ROW    = 2;
+
+    private float mColumnDelay;
+    private float mRowDelay;
+
+    private int mDirection;
+    private int mDirectionPriority;
+
+    /**
+     * Creates a new grid layout animation controller from external resources.
+     *
+     * @param context the Context the view  group is running in, through which
+     *        it can access the resources
+     * @param attrs the attributes of the XML tag that is inflating the
+     *        layout animation controller
+     */
+    public GridLayoutAnimationController(Context context, AttributeSet attrs) {
+        super(context, attrs);
+
+        TypedArray a = context.obtainStyledAttributes(attrs,
+                com.android.internal.R.styleable.GridLayoutAnimation);
+
+        Animation.Description d = Animation.Description.parseValue(
+                a.peekValue(com.android.internal.R.styleable.GridLayoutAnimation_columnDelay));
+        mColumnDelay = d.value;
+        d = Animation.Description.parseValue(
+                a.peekValue(com.android.internal.R.styleable.GridLayoutAnimation_rowDelay));
+        mRowDelay = d.value;
+        //noinspection PointlessBitwiseExpression
+        mDirection = a.getInt(com.android.internal.R.styleable.GridLayoutAnimation_direction,
+                DIRECTION_LEFT_TO_RIGHT | DIRECTION_TOP_TO_BOTTOM);
+        mDirectionPriority = a.getInt(com.android.internal.R.styleable.GridLayoutAnimation_directionPriority,
+                PRIORITY_NONE);
+
+        a.recycle();
+    }
+
+    /**
+     * Creates a new layout animation controller with a delay of 50%
+     * for both rows and columns and the specified animation.
+     *
+     * @param animation the animation to use on each child of the view group
+     */
+    public GridLayoutAnimationController(Animation animation) {
+        this(animation, 0.5f, 0.5f);
+    }
+
+    /**
+     * Creates a new layout animation controller with the specified delays
+     * and the specified animation.
+     *
+     * @param animation the animation to use on each child of the view group
+     * @param columnDelay the delay by which each column animation must be offset
+     * @param rowDelay the delay by which each row animation must be offset
+     */
+    public GridLayoutAnimationController(Animation animation, float columnDelay, float rowDelay) {
+        super(animation);
+        mColumnDelay = columnDelay;
+        mRowDelay = rowDelay;
+    }
+
+    /**
+     * Returns the delay by which the children's animation are offset from one
+     * column to the other. The delay is expressed as a fraction of the
+     * animation duration.
+     *
+     * @return a fraction of the animation duration
+     *
+     * @see #setColumnDelay(float)
+     * @see #getRowDelay()
+     * @see #setRowDelay(float)
+     */
+    public float getColumnDelay() {
+        return mColumnDelay;
+    }
+
+    /**
+     * Sets the delay, as a fraction of the animation duration, by which the
+     * children's animations are offset from one column to the other.
+     *
+     * @param columnDelay a fraction of the animation duration
+     *
+     * @see #getColumnDelay()
+     * @see #getRowDelay()
+     * @see #setRowDelay(float)
+     */
+    public void setColumnDelay(float columnDelay) {
+        mColumnDelay = columnDelay;
+    }
+
+    /**
+     * Returns the delay by which the children's animation are offset from one
+     * row to the other. The delay is expressed as a fraction of the
+     * animation duration.
+     *
+     * @return a fraction of the animation duration
+     *
+     * @see #setRowDelay(float)
+     * @see #getColumnDelay()
+     * @see #setColumnDelay(float)
+     */
+    public float getRowDelay() {
+        return mRowDelay;
+    }
+
+    /**
+     * Sets the delay, as a fraction of the animation duration, by which the
+     * children's animations are offset from one row to the other.
+     *
+     * @param rowDelay a fraction of the animation duration
+     *
+     * @see #getRowDelay()
+     * @see #getColumnDelay()
+     * @see #setColumnDelay(float) 
+     */
+    public void setRowDelay(float rowDelay) {
+        mRowDelay = rowDelay;
+    }
+
+    /**
+     * Returns the direction of the animation. {@link #DIRECTION_HORIZONTAL_MASK}
+     * and {@link #DIRECTION_VERTICAL_MASK} can be used to retrieve the
+     * horizontal and vertical components of the direction.
+     *
+     * @return the direction of the animation
+     *
+     * @see #setDirection(int)
+     * @see #DIRECTION_BOTTOM_TO_TOP
+     * @see #DIRECTION_TOP_TO_BOTTOM
+     * @see #DIRECTION_LEFT_TO_RIGHT
+     * @see #DIRECTION_RIGHT_TO_LEFT
+     * @see #DIRECTION_HORIZONTAL_MASK
+     * @see #DIRECTION_VERTICAL_MASK
+     */
+    public int getDirection() {
+        return mDirection;
+    }
+
+    /**
+     * Sets the direction of the animation. The direction is expressed as an
+     * integer containing a horizontal and vertical component. For instance,
+     * <code>DIRECTION_BOTTOM_TO_TOP | DIRECTION_RIGHT_TO_LEFT</code>.
+     *
+     * @param direction the direction of the animation
+     *
+     * @see #getDirection()
+     * @see #DIRECTION_BOTTOM_TO_TOP
+     * @see #DIRECTION_TOP_TO_BOTTOM
+     * @see #DIRECTION_LEFT_TO_RIGHT
+     * @see #DIRECTION_RIGHT_TO_LEFT
+     * @see #DIRECTION_HORIZONTAL_MASK
+     * @see #DIRECTION_VERTICAL_MASK
+     */
+    public void setDirection(int direction) {
+        mDirection = direction;
+    }
+
+    /**
+     * Returns the direction priority for the animation. The priority can
+     * be either {@link #PRIORITY_NONE}, {@link #PRIORITY_COLUMN} or
+     * {@link #PRIORITY_ROW}.
+     *
+     * @return the priority of the animation direction
+     *
+     * @see #setDirectionPriority(int)
+     * @see #PRIORITY_COLUMN
+     * @see #PRIORITY_NONE
+     * @see #PRIORITY_ROW
+     */
+    public int getDirectionPriority() {
+        return mDirectionPriority;
+    }
+
+    /**
+     * Specifies the direction priority of the animation. For instance,
+     * {@link #PRIORITY_COLUMN} will give priority to columns: the animation
+     * will first play on the column, then on the rows.Z
+     *
+     * @param directionPriority the direction priority of the animation
+     *
+     * @see #getDirectionPriority()
+     * @see #PRIORITY_COLUMN
+     * @see #PRIORITY_NONE
+     * @see #PRIORITY_ROW
+     */
+    public void setDirectionPriority(int directionPriority) {
+        mDirectionPriority = directionPriority;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public boolean willOverlap() {
+        return mColumnDelay < 1.0f || mRowDelay < 1.0f;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    protected long getDelayForView(View view) {
+        ViewGroup.LayoutParams lp = view.getLayoutParams();
+        AnimationParameters params = (AnimationParameters) lp.layoutAnimationParameters;
+
+        if (params == null) {
+            return 0;
+        }
+
+        final int column = getTransformedColumnIndex(params);
+        final int row = getTransformedRowIndex(params);
+
+        final int rowsCount = params.rowsCount;
+        final int columnsCount = params.columnsCount;
+
+        final long duration = mAnimation.getDuration();
+        final float columnDelay = mColumnDelay * duration;
+        final float rowDelay = mRowDelay * duration;
+
+        float totalDelay;
+        long viewDelay;
+
+        if (mInterpolator == null) {
+            mInterpolator = new LinearInterpolator();
+        }
+
+        switch (mDirectionPriority) {
+            case PRIORITY_COLUMN:
+                viewDelay = (long) (row * rowDelay + column * rowsCount * rowDelay);
+                totalDelay = rowsCount * rowDelay + columnsCount * rowsCount * rowDelay;
+                break;
+            case PRIORITY_ROW:
+                viewDelay = (long) (column * columnDelay + row * columnsCount * columnDelay);
+                totalDelay = columnsCount * columnDelay + rowsCount * columnsCount * columnDelay;
+                break;
+            case PRIORITY_NONE:
+            default:
+                viewDelay = (long) (column * columnDelay + row * rowDelay);
+                totalDelay = columnsCount * columnDelay + rowsCount * rowDelay;
+                break;
+        }
+
+        float normalizedDelay = viewDelay / totalDelay;
+        normalizedDelay = mInterpolator.getInterpolation(normalizedDelay);
+
+        return (long) (normalizedDelay * totalDelay);
+    }
+
+    private int getTransformedColumnIndex(AnimationParameters params) {
+        int index;
+        switch (getOrder()) {
+            case ORDER_REVERSE:
+                index = params.columnsCount - 1 - params.column;
+                break;
+            case ORDER_RANDOM:
+                if (mRandomizer == null) {
+                    mRandomizer = new Random();
+                }
+                index = (int) (params.columnsCount * mRandomizer.nextFloat());
+                break;
+            case ORDER_NORMAL:
+            default:
+                index = params.column;
+                break;
+        }
+
+        int direction = mDirection & DIRECTION_HORIZONTAL_MASK;
+        if (direction == DIRECTION_RIGHT_TO_LEFT) {
+            index = params.columnsCount - 1 - index;
+        }
+
+        return index;
+    }
+
+    private int getTransformedRowIndex(AnimationParameters params) {
+        int index;
+        switch (getOrder()) {
+            case ORDER_REVERSE:
+                index = params.rowsCount - 1 - params.row;
+                break;
+            case ORDER_RANDOM:
+                if (mRandomizer == null) {
+                    mRandomizer = new Random();
+                }
+                index = (int) (params.rowsCount * mRandomizer.nextFloat());
+                break;
+            case ORDER_NORMAL:
+            default:
+                index = params.row;
+                break;
+        }
+
+        int direction = mDirection & DIRECTION_VERTICAL_MASK;
+        if (direction == DIRECTION_BOTTOM_TO_TOP) {
+            index = params.rowsCount - 1 - index;
+        }
+
+        return index;
+    }
+
+    /**
+     * The set of parameters that has to be attached to each view contained in
+     * the view group animated by the grid layout animation controller. These
+     * parameters are used to compute the start time of each individual view's
+     * animation.
+     */
+    public static class AnimationParameters extends
+            LayoutAnimationController.AnimationParameters {
+        /**
+         * The view group's column to which the view belongs.
+         */
+        public int column;
+
+        /**
+         * The view group's row to which the view belongs.
+         */
+        public int row;
+
+        /**
+         * The number of columns in the view's enclosing grid layout.
+         */
+        public int columnsCount;
+
+        /**
+         * The number of rows in the view's enclosing grid layout.
+         */
+        public int rowsCount;
+    }
+}
diff --git a/core/java/android/view/animation/Interpolator.java b/core/java/android/view/animation/Interpolator.java
new file mode 100644
index 0000000..d14c3e33
--- /dev/null
+++ b/core/java/android/view/animation/Interpolator.java
@@ -0,0 +1,39 @@
+/*
+ * 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 android.view.animation;
+
+/**
+ * An interpolator defines the rate of change of an animation. This allows
+ * the basic animation effects (alpha, scale, translate, rotate) to be 
+ * accelerated, decelerated, repeated, etc.
+ */
+public interface Interpolator {
+    
+    /**
+     * Maps a point on the timeline to a multiplier to be applied to the
+     * transformations of an animation.
+     * 
+     * @param input A value between 0 and 1.0 indicating our current point
+     *        in the animation where 0 represents the start and 1.0 represents
+     *        the end
+     * @return The interpolation value. This value can be more than 1.0 for
+     *         Interpolators which overshoot their targets, or less than 0 for
+     *         Interpolators that undershoot their targets.
+     */
+    float getInterpolation(float input);
+    
+}
diff --git a/core/java/android/view/animation/LayoutAnimationController.java b/core/java/android/view/animation/LayoutAnimationController.java
new file mode 100644
index 0000000..882e738
--- /dev/null
+++ b/core/java/android/view/animation/LayoutAnimationController.java
@@ -0,0 +1,435 @@
+/*
+ * Copyright (C) 2007 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 android.view.animation;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.ViewGroup;
+
+import java.util.Random;
+
+/**
+ * A layout animation controller is used to animated a layout's, or a view
+ * group's, children. Each child uses the same animation but for every one of
+ * them, the animation starts at a different time. A layout animation controller
+ * is used by {@link android.view.ViewGroup} to compute the delay by which each
+ * child's animation start must be offset. The delay is computed by using
+ * characteristics of each child, like its index in the view group.
+ *
+ * This standard implementation computes the delay by multiplying a fixed
+ * amount of miliseconds by the index of the child in its parent view group.
+ * Subclasses are supposed to override
+ * {@link #getDelayForView(android.view.View)} to implement a different way
+ * of computing the delay. For instance, a
+ * {@link android.view.animation.GridLayoutAnimationController} will compute the
+ * delay based on the column and row indices of the child in its parent view
+ * group.
+ *
+ * Information used to compute the animation delay of each child are stored
+ * in an instance of
+ * {@link android.view.animation.LayoutAnimationController.AnimationParameters},
+ * itself stored in the {@link android.view.ViewGroup.LayoutParams} of the view.
+ *
+ * @attr ref android.R.styleable#LayoutAnimation_delay
+ * @attr ref android.R.styleable#LayoutAnimation_animationOrder
+ * @attr ref android.R.styleable#LayoutAnimation_interpolator
+ * @attr ref android.R.styleable#LayoutAnimation_animation
+ */
+public class LayoutAnimationController {
+    /**
+     * Distributes the animation delays in the order in which view were added
+     * to their view group.
+     */
+    public static final int ORDER_NORMAL  = 0;
+
+    /**
+     * Distributes the animation delays in the reverse order in which view were
+     * added to their view group.
+     */
+    public static final int ORDER_REVERSE = 1;
+
+    /**
+     * Randomly distributes the animation delays.
+     */
+    public static final int ORDER_RANDOM  = 2;
+
+    /**
+     * The animation applied on each child of the view group on which this
+     * layout animation controller is set.
+     */
+    protected Animation mAnimation;
+
+    /**
+     * The randomizer used when the order is set to random. Subclasses should
+     * use this object to avoid creating their own.
+     */
+    protected Random mRandomizer;
+
+    /**
+     * The interpolator used to interpolate the delays.
+     */
+    protected Interpolator mInterpolator;
+
+    private float mDelay;
+    private int mOrder;
+
+    private long mDuration;
+    private long mMaxDelay;    
+
+    /**
+     * Creates a new layout animation controller from external resources.
+     *
+     * @param context the Context the view  group is running in, through which
+     *        it can access the resources
+     * @param attrs the attributes of the XML tag that is inflating the
+     *        layout animation controller
+     */
+    public LayoutAnimationController(Context context, AttributeSet attrs) {
+        TypedArray a = context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.LayoutAnimation);
+
+        Animation.Description d = Animation.Description.parseValue(
+                a.peekValue(com.android.internal.R.styleable.LayoutAnimation_delay));
+        mDelay = d.value;
+
+        mOrder = a.getInt(com.android.internal.R.styleable.LayoutAnimation_animationOrder, ORDER_NORMAL);
+
+        int resource = a.getResourceId(com.android.internal.R.styleable.LayoutAnimation_animation, 0);
+        if (resource > 0) {
+            setAnimation(context, resource);
+        }
+
+        resource = a.getResourceId(com.android.internal.R.styleable.LayoutAnimation_interpolator, 0);
+        if (resource > 0) {
+            setInterpolator(context, resource);
+        }
+
+        a.recycle();
+    }
+
+    /**
+     * Creates a new layout animation controller with a delay of 50%
+     * and the specified animation.
+     *
+     * @param animation the animation to use on each child of the view group
+     */
+    public LayoutAnimationController(Animation animation) {
+        this(animation, 0.5f);
+    }
+
+    /**
+     * Creates a new layout animation controller with the specified delay
+     * and the specified animation.
+     *
+     * @param animation the animation to use on each child of the view group
+     * @param delay the delay by which each child's animation must be offset
+     */
+    public LayoutAnimationController(Animation animation, float delay) {
+        mDelay = delay;
+        setAnimation(animation);
+    }
+
+    /**
+     * Returns the order used to compute the delay of each child's animation.
+     *
+     * @return one of {@link #ORDER_NORMAL}, {@link #ORDER_REVERSE} or
+     *         {@link #ORDER_RANDOM)
+     *
+     * @attr ref android.R.styleable#LayoutAnimation_animationOrder
+     */
+    public int getOrder() {
+        return mOrder;
+    }
+
+    /**
+     * Sets the order used to compute the delay of each child's animation.
+     *
+     * @param order one of {@link #ORDER_NORMAL}, {@link #ORDER_REVERSE} or
+     *        {@link #ORDER_RANDOM}
+     *
+     * @attr ref android.R.styleable#LayoutAnimation_animationOrder
+     */
+    public void setOrder(int order) {
+        mOrder = order;
+    }
+
+    /**
+     * Sets the animation to be run on each child of the view group on which
+     * this layout animation controller is .
+     *
+     * @param context the context from which the animation must be inflated
+     * @param resourceID the resource identifier of the animation
+     *
+     * @see #setAnimation(Animation)
+     * @see #getAnimation() 
+     *
+     * @attr ref android.R.styleable#LayoutAnimation_animation
+     */
+    public void setAnimation(Context context, int resourceID) {
+        setAnimation(AnimationUtils.loadAnimation(context, resourceID));
+    }
+
+    /**
+     * Sets the animation to be run on each child of the view group on which
+     * this layout animation controller is .
+     *
+     * @param animation the animation to run on each child of the view group
+
+     * @see #setAnimation(android.content.Context, int)
+     * @see #getAnimation()
+     *
+     * @attr ref android.R.styleable#LayoutAnimation_animation
+     */
+    public void setAnimation(Animation animation) {
+        mAnimation = animation;
+        mAnimation.setFillBefore(true);
+    }
+
+    /**
+     * Returns the animation applied to each child of the view group on which
+     * this controller is set.
+     *
+     * @return an {@link android.view.animation.Animation} instance
+     *
+     * @see #setAnimation(android.content.Context, int)
+     * @see #setAnimation(Animation)
+     */
+    public Animation getAnimation() {
+        return mAnimation;
+    }
+
+    /**
+     * Sets the interpolator used to interpolate the delays between the
+     * children.
+     *
+     * @param context the context from which the interpolator must be inflated
+     * @param resourceID the resource identifier of the interpolator
+     *
+     * @see #getInterpolator()
+     * @see #setInterpolator(Interpolator)
+     *
+     * @attr ref android.R.styleable#LayoutAnimation_interpolator
+     */
+    public void setInterpolator(Context context, int resourceID) {
+        setInterpolator(AnimationUtils.loadInterpolator(context, resourceID));
+    }
+
+    /**
+     * Sets the interpolator used to interpolate the delays between the
+     * children.
+     *
+     * @param interpolator the interpolator
+     *
+     * @see #getInterpolator()
+     * @see #setInterpolator(Interpolator)
+     *
+     * @attr ref android.R.styleable#LayoutAnimation_interpolator
+     */
+    public void setInterpolator(Interpolator interpolator) {
+        mInterpolator = interpolator;
+    }
+
+    /**
+     * Returns the interpolator used to interpolate the delays between the
+     * children.
+     *
+     * @return an {@link android.view.animation.Interpolator}
+     */
+    public Interpolator getInterpolator() {
+        return mInterpolator;
+    }
+
+    /**
+     * Returns the delay by which the children's animation are offset. The
+     * delay is expressed as a fraction of the animation duration.
+     *
+     * @return a fraction of the animation duration
+     *
+     * @see #setDelay(float)
+     */
+    public float getDelay() {
+        return mDelay;
+    }
+
+    /**
+     * Sets the delay, as a fraction of the animation duration, by which the
+     * children's animations are offset. The general formula is:
+     *
+     * <pre>
+     * child animation delay = child index * delay * animation duration
+     * </pre>
+     *
+     * @param delay a fraction of the animation duration
+     *
+     * @see #getDelay()
+     */
+    public void setDelay(float delay) {
+        mDelay = delay;
+    }
+
+    /**
+     * Indicates whether two children's animations will overlap. Animations
+     * overlap when the delay is lower than 100% (or 1.0).
+     *
+     * @return true if animations will overlap, false otherwise
+     */
+    public boolean willOverlap() {
+        return mDelay < 1.0f;
+    }
+
+    /**
+     * Starts the animation.
+     */
+    public void start() {
+        mDuration = mAnimation.getDuration();
+        mMaxDelay = Long.MIN_VALUE;
+        mAnimation.setStartTime(-1);
+    }
+
+    /**
+     * Returns the animation to be applied to the specified view. The returned
+     * animation is delayed by an offset computed according to the information
+     * provided by
+     * {@link android.view.animation.LayoutAnimationController.AnimationParameters}.
+     * This method is called by view groups to obtain the animation to set on
+     * a specific child.
+     *
+     * @param view the view to animate
+     * @return an animation delayed by the number of milliseconds returned by
+     *         {@link #getDelayForView(android.view.View)}
+     *
+     * @see #getDelay()
+     * @see #setDelay(float)
+     * @see #getDelayForView(android.view.View)
+     */
+    public final Animation getAnimationForView(View view) {
+        final long delay = getDelayForView(view) + mAnimation.getStartOffset();
+        mMaxDelay = Math.max(mMaxDelay, delay);
+
+        try {
+            final Animation animation = mAnimation.clone();
+            animation.setStartOffset(delay);
+            return animation;
+        } catch (CloneNotSupportedException e) {
+            return null;
+        }
+    }
+
+    /**
+     * Indicates whether the layout animation is over or not. A layout animation
+     * is considered done when the animation with the longest delay is done.
+     *
+     * @return true if all of the children's animations are over, false otherwise
+     */
+    public boolean isDone() {
+        return AnimationUtils.currentAnimationTimeMillis() >
+                mAnimation.getStartTime() + mMaxDelay + mDuration;
+    }
+
+    /**
+     * Returns the amount of milliseconds by which the specified view's
+     * animation must be delayed or offset. Subclasses should override this
+     * method to return a suitable value.
+     *
+     * This implementation returns <code>child animation delay</code>
+     * milliseconds where:
+     *
+     * <pre>
+     * child animation delay = child index * delay
+     * </pre>
+     *
+     * The index is retrieved from the
+     * {@link android.view.animation.LayoutAnimationController.AnimationParameters}
+     * found in the view's {@link android.view.ViewGroup.LayoutParams}.
+     *
+     * @param view the view for which to obtain the animation's delay
+     * @return a delay in milliseconds
+     *
+     * @see #getAnimationForView(android.view.View)
+     * @see #getDelay()
+     * @see #getTransformedIndex(android.view.animation.LayoutAnimationController.AnimationParameters)
+     * @see android.view.ViewGroup.LayoutParams
+     */
+    protected long getDelayForView(View view) {
+        ViewGroup.LayoutParams lp = view.getLayoutParams();
+        AnimationParameters params = lp.layoutAnimationParameters;
+
+        if (params == null) {
+            return 0;
+        }
+
+        final float delay = mDelay * mAnimation.getDuration();
+        final long viewDelay = (long) (getTransformedIndex(params) * delay);
+        final float totalDelay = delay * params.count;
+
+        if (mInterpolator == null) {
+            mInterpolator = new LinearInterpolator();
+        }
+
+        float normalizedDelay = viewDelay / totalDelay;
+        normalizedDelay = mInterpolator.getInterpolation(normalizedDelay);
+
+        return (long) (normalizedDelay * totalDelay);
+    }
+
+    /**
+     * Transforms the index stored in
+     * {@link android.view.animation.LayoutAnimationController.AnimationParameters}
+     * by the order returned by {@link #getOrder()}. Subclasses should override
+     * this method to provide additional support for other types of ordering.
+     * This method should be invoked by
+     * {@link #getDelayForView(android.view.View)} prior to any computation. 
+     *
+     * @param params the animation parameters containing the index
+     * @return a transformed index
+     */
+    protected int getTransformedIndex(AnimationParameters params) {
+        switch (getOrder()) {
+            case ORDER_REVERSE:
+                return params.count - 1 - params.index;
+            case ORDER_RANDOM:
+                if (mRandomizer == null) {
+                    mRandomizer = new Random();
+                }
+                return (int) (params.count * mRandomizer.nextFloat());
+            case ORDER_NORMAL:
+            default:
+                return params.index;
+        }
+    }
+
+    /**
+     * The set of parameters that has to be attached to each view contained in
+     * the view group animated by the layout animation controller. These
+     * parameters are used to compute the start time of each individual view's
+     * animation.
+     */
+    public static class AnimationParameters {
+        /**
+         * The number of children in the view group containing the view to which
+         * these parameters are attached.
+         */
+        public int count;
+
+        /**
+         * The index of the view to which these parameters are attached in its
+         * containing view group.
+         */
+        public int index;
+    }
+}
diff --git a/core/java/android/view/animation/LinearInterpolator.java b/core/java/android/view/animation/LinearInterpolator.java
new file mode 100644
index 0000000..96a039f
--- /dev/null
+++ b/core/java/android/view/animation/LinearInterpolator.java
@@ -0,0 +1,37 @@
+/*
+ * 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 android.view.animation;
+
+import android.content.Context;
+import android.util.AttributeSet;
+
+/**
+ * An interpolator where the rate of change is constant
+ *
+ */
+public class LinearInterpolator implements Interpolator {
+
+    public LinearInterpolator() {
+    }
+    
+    public LinearInterpolator(Context context, AttributeSet attrs) {
+    }
+    
+    public float getInterpolation(float input) {
+        return input;
+    }
+}
diff --git a/core/java/android/view/animation/RotateAnimation.java b/core/java/android/view/animation/RotateAnimation.java
new file mode 100644
index 0000000..2f51b91
--- /dev/null
+++ b/core/java/android/view/animation/RotateAnimation.java
@@ -0,0 +1,165 @@
+/*
+ * 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 android.view.animation;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.util.AttributeSet;
+
+/**
+ * An animation that controls the rotation of an object. This rotation takes
+ * place int the X-Y plane. You can specify the point to use for the center of
+ * the rotation, where (0,0) is the top left point. If not specified, (0,0) is
+ * the default rotation point.
+ * 
+ */
+public class RotateAnimation extends Animation {
+    private float mFromDegrees;
+    private float mToDegrees;
+
+    private int mPivotXType = ABSOLUTE;
+    private int mPivotYType = ABSOLUTE;
+    private float mPivotXValue = 0.0f;
+    private float mPivotYValue = 0.0f;
+
+    private float mPivotX;
+    private float mPivotY;
+
+    /**
+     * Constructor used whan an RotateAnimation is loaded from a resource.
+     * 
+     * @param context Application context to use
+     * @param attrs Attribute set from which to read values
+     */
+    public RotateAnimation(Context context, AttributeSet attrs) {
+        super(context, attrs);
+
+        TypedArray a = context.obtainStyledAttributes(attrs,
+                com.android.internal.R.styleable.RotateAnimation);
+
+        mFromDegrees = a.getFloat(
+                com.android.internal.R.styleable.RotateAnimation_fromDegrees, 0.0f);
+        mToDegrees = a.getFloat(com.android.internal.R.styleable.RotateAnimation_toDegrees, 0.0f);
+
+        Description d = Description.parseValue(a.peekValue(
+            com.android.internal.R.styleable.RotateAnimation_pivotX));
+        mPivotXType = d.type;
+        mPivotXValue = d.value;
+
+        d = Description.parseValue(a.peekValue(
+            com.android.internal.R.styleable.RotateAnimation_pivotY));
+        mPivotYType = d.type;
+        mPivotYValue = d.value;
+
+        a.recycle();
+    }
+
+    /**
+     * Constructor to use when building a RotateAnimation from code.
+     * Default pivotX/pivotY point is (0,0).
+     * 
+     * @param fromDegrees Rotation offset to apply at the start of the
+     *        animation.
+     * 
+     * @param toDegrees Rotation offset to apply at the end of the animation.
+     */
+    public RotateAnimation(float fromDegrees, float toDegrees) {
+        mFromDegrees = fromDegrees;
+        mToDegrees = toDegrees;
+        mPivotX = 0.0f;
+        mPivotY = 0.0f;
+    }
+
+    /**
+     * Constructor to use when building a RotateAnimation from code
+     * 
+     * @param fromDegrees Rotation offset to apply at the start of the
+     *        animation.
+     * 
+     * @param toDegrees Rotation offset to apply at the end of the animation.
+     * 
+     * @param pivotX The X coordinate of the point about which the object is
+     *        being rotated, specified as an absolute number where 0 is the left
+     *        edge.
+     * @param pivotY The Y coordinate of the point about which the object is
+     *        being rotated, specified as an absolute number where 0 is the top
+     *        edge.
+     */
+    public RotateAnimation(float fromDegrees, float toDegrees, float pivotX, float pivotY) {
+        mFromDegrees = fromDegrees;
+        mToDegrees = toDegrees;
+
+        mPivotXType = ABSOLUTE;
+        mPivotYType = ABSOLUTE;
+        mPivotXValue = pivotX;
+        mPivotYValue = pivotY;
+    }
+
+    /**
+     * Constructor to use when building a RotateAnimation from code
+     * 
+     * @param fromDegrees Rotation offset to apply at the start of the
+     *        animation.
+     * 
+     * @param toDegrees Rotation offset to apply at the end of the animation.
+     * 
+     * @param pivotXType Specifies how pivotXValue should be interpreted. One of
+     *        Animation.ABSOLUTE, Animation.RELATIVE_TO_SELF, or
+     *        Animation.RELATIVE_TO_PARENT.
+     * @param pivotXValue The X coordinate of the point about which the object
+     *        is being rotated, specified as an absolute number where 0 is the
+     *        left edge. This value can either be an absolute number if
+     *        pivotXType is ABSOLUTE, or a percentage (where 1.0 is 100%)
+     *        otherwise.
+     * @param pivotYType Specifies how pivotYValue should be interpreted. One of
+     *        Animation.ABSOLUTE, Animation.RELATIVE_TO_SELF, or
+     *        Animation.RELATIVE_TO_PARENT.
+     * @param pivotYValue The Y coordinate of the point about which the object
+     *        is being rotated, specified as an absolute number where 0 is the
+     *        top edge. This value can either be an absolute number if
+     *        pivotYType is ABSOLUTE, or a percentage (where 1.0 is 100%)
+     *        otherwise.
+     */
+    public RotateAnimation(float fromDegrees, float toDegrees, int pivotXType, float pivotXValue,
+            int pivotYType, float pivotYValue) {
+        mFromDegrees = fromDegrees;
+        mToDegrees = toDegrees;
+
+        mPivotXValue = pivotXValue;
+        mPivotXType = pivotXType;
+        mPivotYValue = pivotYValue;
+        mPivotYType = pivotYType;
+    }
+
+    @Override
+    protected void applyTransformation(float interpolatedTime, Transformation t) {
+        float degrees = mFromDegrees + ((mToDegrees - mFromDegrees) * interpolatedTime);
+
+        if (mPivotX == 0.0f && mPivotY == 0.0f) {
+            t.getMatrix().setRotate(degrees);
+        } else {
+            t.getMatrix().setRotate(degrees, mPivotX, mPivotY);
+        }
+    }
+
+    @Override
+    public void initialize(int width, int height, int parentWidth, int parentHeight) {
+        super.initialize(width, height, parentWidth, parentHeight);
+        mPivotX = resolveSize(mPivotXType, mPivotXValue, width, parentWidth);
+        mPivotY = resolveSize(mPivotYType, mPivotYValue, height, parentHeight);
+    }
+}
diff --git a/core/java/android/view/animation/ScaleAnimation.java b/core/java/android/view/animation/ScaleAnimation.java
new file mode 100644
index 0000000..122ed6d
--- /dev/null
+++ b/core/java/android/view/animation/ScaleAnimation.java
@@ -0,0 +1,186 @@
+/*
+ * 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 android.view.animation;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.util.AttributeSet;
+
+/**
+ * An animation that controls the scale of an object. You can specify the point
+ * to use for the center of scaling.
+ * 
+ */
+public class ScaleAnimation extends Animation {
+    private float mFromX;
+    private float mToX;
+    private float mFromY;
+    private float mToY;
+
+    private int mPivotXType = ABSOLUTE;
+    private int mPivotYType = ABSOLUTE;
+    private float mPivotXValue = 0.0f;
+    private float mPivotYValue = 0.0f;
+
+    private float mPivotX;
+    private float mPivotY;
+
+    /**
+     * Constructor used whan an ScaleAnimation is loaded from a resource.
+     * 
+     * @param context Application context to use
+     * @param attrs Attribute set from which to read values
+     */
+    public ScaleAnimation(Context context, AttributeSet attrs) {
+        super(context, attrs);
+
+        TypedArray a = context.obtainStyledAttributes(attrs,
+                com.android.internal.R.styleable.ScaleAnimation);
+
+        mFromX = a.getFloat(com.android.internal.R.styleable.ScaleAnimation_fromXScale, 0.0f);
+        mToX = a.getFloat(com.android.internal.R.styleable.ScaleAnimation_toXScale, 0.0f);
+
+        mFromY = a.getFloat(com.android.internal.R.styleable.ScaleAnimation_fromYScale, 0.0f);
+        mToY = a.getFloat(com.android.internal.R.styleable.ScaleAnimation_toYScale, 0.0f);
+
+        Description d = Description.parseValue(a.peekValue(
+                com.android.internal.R.styleable.ScaleAnimation_pivotX));
+        mPivotXType = d.type;
+        mPivotXValue = d.value;
+
+        d = Description.parseValue(a.peekValue(
+            com.android.internal.R.styleable.ScaleAnimation_pivotY));
+        mPivotYType = d.type;
+        mPivotYValue = d.value;
+
+        a.recycle();
+    }
+
+    /**
+     * Constructor to use when building a ScaleAnimation from code
+     * 
+     * @param fromX Horizontal scaling factor to apply at the start of the
+     *        animation
+     * @param toX Horizontal scaling factor to apply at the end of the animation
+     * @param fromY Vertical scaling factor to apply at the start of the
+     *        animation
+     * @param toY Vertical scaling factor to apply at the end of the animation
+     */
+    public ScaleAnimation(float fromX, float toX, float fromY, float toY) {
+        mFromX = fromX;
+        mToX = toX;
+        mFromY = fromY;
+        mToY = toY;
+        mPivotX = 0;
+        mPivotY = 0;
+    }
+
+    /**
+     * Constructor to use when building a ScaleAnimation from code
+     * 
+     * @param fromX Horizontal scaling factor to apply at the start of the
+     *        animation
+     * @param toX Horizontal scaling factor to apply at the end of the animation
+     * @param fromY Vertical scaling factor to apply at the start of the
+     *        animation
+     * @param toY Vertical scaling factor to apply at the end of the animation
+     * @param pivotX The X coordinate of the point about which the object is
+     *        being scaled, specified as an absolute number where 0 is the left
+     *        edge. (This point remains fixed while the object changes size.)
+     * @param pivotY The Y coordinate of the point about which the object is
+     *        being scaled, specified as an absolute number where 0 is the top
+     *        edge. (This point remains fixed while the object changes size.)
+     */
+    public ScaleAnimation(float fromX, float toX, float fromY, float toY,
+            float pivotX, float pivotY) {
+        mFromX = fromX;
+        mToX = toX;
+        mFromY = fromY;
+        mToY = toY;
+
+        mPivotXType = ABSOLUTE;
+        mPivotYType = ABSOLUTE;
+        mPivotXValue = pivotX;
+        mPivotYValue = pivotY;
+    }
+
+    /**
+     * Constructor to use when building a ScaleAnimation from code
+     * 
+     * @param fromX Horizontal scaling factor to apply at the start of the
+     *        animation
+     * @param toX Horizontal scaling factor to apply at the end of the animation
+     * @param fromY Vertical scaling factor to apply at the start of the
+     *        animation
+     * @param toY Vertical scaling factor to apply at the end of the animation
+     * @param pivotXType Specifies how pivotXValue should be interpreted. One of
+     *        Animation.ABSOLUTE, Animation.RELATIVE_TO_SELF, or
+     *        Animation.RELATIVE_TO_PARENT.
+     * @param pivotXValue The X coordinate of the point about which the object
+     *        is being scaled, specified as an absolute number where 0 is the
+     *        left edge. (This point remains fixed while the object changes
+     *        size.) This value can either be an absolute number if pivotXType
+     *        is ABSOLUTE, or a percentage (where 1.0 is 100%) otherwise.
+     * @param pivotYType Specifies how pivotYValue should be interpreted. One of
+     *        Animation.ABSOLUTE, Animation.RELATIVE_TO_SELF, or
+     *        Animation.RELATIVE_TO_PARENT.
+     * @param pivotYValue The Y coordinate of the point about which the object
+     *        is being scaled, specified as an absolute number where 0 is the
+     *        top edge. (This point remains fixed while the object changes
+     *        size.) This value can either be an absolute number if pivotYType
+     *        is ABSOLUTE, or a percentage (where 1.0 is 100%) otherwise.
+     */
+    public ScaleAnimation(float fromX, float toX, float fromY, float toY,
+            int pivotXType, float pivotXValue, int pivotYType, float pivotYValue) {
+        mFromX = fromX;
+        mToX = toX;
+        mFromY = fromY;
+        mToY = toY;
+
+        mPivotXValue = pivotXValue;
+        mPivotXType = pivotXType;
+        mPivotYValue = pivotYValue;
+        mPivotYType = pivotYType;
+    }
+
+    @Override
+    protected void applyTransformation(float interpolatedTime, Transformation t) {
+        float sx = 1.0f;
+        float sy = 1.0f;
+
+        if (mFromX != 1.0f || mToX != 1.0f) {
+            sx = mFromX + ((mToX - mFromX) * interpolatedTime);
+        }
+        if (mFromY != 1.0f || mToY != 1.0f) {
+            sy = mFromY + ((mToY - mFromY) * interpolatedTime);
+        }
+
+        if (mPivotX == 0 && mPivotY == 0) {
+            t.getMatrix().setScale(sx, sy);
+        } else {
+            t.getMatrix().setScale(sx, sy, mPivotX, mPivotY);
+        }
+    }
+
+    @Override
+    public void initialize(int width, int height, int parentWidth, int parentHeight) {
+        super.initialize(width, height, parentWidth, parentHeight);
+
+        mPivotX = resolveSize(mPivotXType, mPivotXValue, width, parentWidth);
+        mPivotY = resolveSize(mPivotYType, mPivotYValue, height, parentHeight);
+    }
+}
diff --git a/core/java/android/view/animation/Transformation.java b/core/java/android/view/animation/Transformation.java
new file mode 100644
index 0000000..f9e85bf
--- /dev/null
+++ b/core/java/android/view/animation/Transformation.java
@@ -0,0 +1,147 @@
+/*
+ * 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 android.view.animation;
+
+import android.graphics.Matrix;
+
+/**
+ * Defines the transformation to be applied at
+ * one point in time of an Animation.
+ *
+ */
+public class Transformation {
+    /**
+     * Indicates a transformation that has no effect (alpha = 1 and identity matrix.)
+     */
+    public static int TYPE_IDENTITY = 0x0;
+    /**
+     * Indicates a transformation that applies an alpha only (uses an identity matrix.)
+     */
+    public static int TYPE_ALPHA = 0x1;
+    /**
+     * Indicates a transformation that applies a matrix only (alpha = 1.)
+     */
+    public static int TYPE_MATRIX = 0x2;
+    /**
+     * Indicates a transformation that applies an alpha and a matrix.
+     */
+    public static int TYPE_BOTH = TYPE_ALPHA | TYPE_MATRIX;
+
+    protected Matrix mMatrix;
+    protected float mAlpha;
+    protected int mTransformationType;
+
+    /**
+     * Creates a new transformation with alpha = 1 and the identity matrix.
+     */
+    public Transformation() {
+        clear();
+    }
+
+    /**
+     * Reset the transformation to a state that leaves the object
+     * being animated in an unmodified state. The transformation type is
+     * {@link #TYPE_BOTH} by default.
+     */
+    public void clear() {
+        if (mMatrix == null) {
+            mMatrix = new Matrix();
+        } else {
+            mMatrix.reset();
+        }
+        mAlpha = 1.0f;
+        mTransformationType = TYPE_BOTH;
+    }
+
+    /**
+     * Indicates the nature of this transformation.
+     *
+     * @return {@link #TYPE_ALPHA}, {@link #TYPE_MATRIX},
+     *         {@link #TYPE_BOTH} or {@link #TYPE_IDENTITY}.
+     */
+    public int getTransformationType() {
+        return mTransformationType;
+    }
+
+    /**
+     * Sets the transformation type.
+     *
+     * @param transformationType One of {@link #TYPE_ALPHA},
+     *        {@link #TYPE_MATRIX}, {@link #TYPE_BOTH} or
+     *        {@link #TYPE_IDENTITY}.
+     */
+    public void setTransformationType(int transformationType) {
+        mTransformationType = transformationType;
+    }
+
+    /**
+     * Clones the specified transformation.
+     *
+     * @param t The transformation to clone.
+     */
+    public void set(Transformation t) {
+        mAlpha = t.getAlpha();
+        mMatrix.set(t.getMatrix());
+        mTransformationType = t.getTransformationType();
+    }
+    
+    /**
+     * Apply this Transformation to an existing Transformation, e.g. apply
+     * a scale effect to something that has already been rotated.
+     * @param t
+     */
+    public void compose(Transformation t) {
+        mAlpha *= t.getAlpha();
+        mMatrix.preConcat(t.getMatrix());
+    }
+    
+    /**
+     * @return The 3x3 Matrix representing the trnasformation to apply to the
+     * coordinates of the object being animated
+     */
+    public Matrix getMatrix() {
+        return mMatrix;
+    }
+    
+    /**
+     * Sets the degree of transparency
+     * @param alpha 1.0 means fully opaqe and 0.0 means fully transparent
+     */
+    public void setAlpha(float alpha) {
+        mAlpha = alpha;
+    }
+
+    /**
+     * @return The degree of transparency
+     */
+    public float getAlpha() {
+        return mAlpha;
+    }
+    
+    @Override
+    public String toString() {
+        return "Transformation{alpha=" + mAlpha + " matrix="
+                + mMatrix.toShortString() + "}";
+    }
+    
+    /**
+     * Return a string representation of the transformation in a compact form.
+     */
+    public String toShortString() {
+        return "{alpha=" + mAlpha + " matrix=" + mMatrix.toShortString() + "}";
+    }
+}
diff --git a/core/java/android/view/animation/TranslateAnimation.java b/core/java/android/view/animation/TranslateAnimation.java
new file mode 100644
index 0000000..ca936cb
--- /dev/null
+++ b/core/java/android/view/animation/TranslateAnimation.java
@@ -0,0 +1,171 @@
+/*
+ * 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 android.view.animation;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.util.AttributeSet;
+
+/**
+ * An animation that controls the position of an object. See the
+ * {@link android.view.animation full package} description for details and
+ * sample code.
+ * 
+ */
+public class TranslateAnimation extends Animation {
+    private int mFromXType = ABSOLUTE;
+    private int mToXType = ABSOLUTE;
+
+    private int mFromYType = ABSOLUTE;
+    private int mToYType = ABSOLUTE;
+
+    private float mFromXValue = 0.0f;
+    private float mToXValue = 0.0f;
+
+    private float mFromYValue = 0.0f;
+    private float mToYValue = 0.0f;
+
+    private float mFromXDelta;
+    private float mToXDelta;
+    private float mFromYDelta;
+    private float mToYDelta;
+
+    /**
+     * Constructor used when a TranslateAnimation is loaded from a resource.
+     * 
+     * @param context Application context to use
+     * @param attrs Attribute set from which to read values
+     */
+    public TranslateAnimation(Context context, AttributeSet attrs) {
+        super(context, attrs);
+
+        TypedArray a = context.obtainStyledAttributes(attrs,
+                com.android.internal.R.styleable.TranslateAnimation);
+
+        Description d = Description.parseValue(a.peekValue(
+            com.android.internal.R.styleable.TranslateAnimation_fromXDelta));
+        mFromXType = d.type;
+        mFromXValue = d.value;
+
+        d = Description.parseValue(a.peekValue(
+                com.android.internal.R.styleable.TranslateAnimation_toXDelta));
+        mToXType = d.type;
+        mToXValue = d.value;
+
+        d = Description.parseValue(a.peekValue(
+            com.android.internal.R.styleable.TranslateAnimation_fromYDelta));
+        mFromYType = d.type;
+        mFromYValue = d.value;
+
+        d = Description.parseValue(a.peekValue(
+            com.android.internal.R.styleable.TranslateAnimation_toYDelta));
+        mToYType = d.type;
+        mToYValue = d.value;
+
+        a.recycle();
+    }
+
+    /**
+     * Constructor to use when building a ScaleAnimation from code
+     * 
+     * @param fromXDelta Change in X coordinate to apply at the start of the
+     *        animation
+     * @param toXDelta Change in X coordinate to apply at the end of the
+     *        animation
+     * @param fromYDelta Change in Y coordinate to apply at the start of the
+     *        animation
+     * @param toYDelta Change in Y coordinate to apply at the end of the
+     *        animation
+     */
+    public TranslateAnimation(float fromXDelta, float toXDelta, float fromYDelta, float toYDelta) {
+        mFromXValue = fromXDelta;
+        mToXValue = toXDelta;
+        mFromYValue = fromYDelta;
+        mToYValue = toYDelta;
+
+        mFromXType = ABSOLUTE;
+        mToXType = ABSOLUTE;
+        mFromYType = ABSOLUTE;
+        mToYType = ABSOLUTE;
+    }
+
+    /**
+     * Constructor to use when building a ScaleAnimation from code
+     * 
+     * @param fromXType Specifies how fromXValue should be interpreted. One of
+     *        Animation.ABSOLUTE, Animation.RELATIVE_TO_SELF, or
+     *        Animation.RELATIVE_TO_PARENT.
+     * @param fromXValue Change in X coordinate to apply at the start of the
+     *        animation. This value can either be an absolute number if fromXType
+     *        is ABSOLUTE, or a percentage (where 1.0 is 100%) otherwise.
+     * @param toXType Specifies how toXValue should be interpreted. One of
+     *        Animation.ABSOLUTE, Animation.RELATIVE_TO_SELF, or
+     *        Animation.RELATIVE_TO_PARENT.
+     * @param toXValue Change in X coordinate to apply at the end of the
+     *        animation. This value can either be an absolute number if toXType
+     *        is ABSOLUTE, or a percentage (where 1.0 is 100%) otherwise.
+     * @param fromYType Specifies how fromYValue should be interpreted. One of
+     *        Animation.ABSOLUTE, Animation.RELATIVE_TO_SELF, or
+     *        Animation.RELATIVE_TO_PARENT.
+     * @param fromYValue Change in Y coordinate to apply at the start of the
+     *        animation. This value can either be an absolute number if fromYType
+     *        is ABSOLUTE, or a percentage (where 1.0 is 100%) otherwise.
+     * @param toYType Specifies how toYValue should be interpreted. One of
+     *        Animation.ABSOLUTE, Animation.RELATIVE_TO_SELF, or
+     *        Animation.RELATIVE_TO_PARENT.
+     * @param toYValue Change in Y coordinate to apply at the end of the
+     *        animation. This value can either be an absolute number if toYType
+     *        is ABSOLUTE, or a percentage (where 1.0 is 100%) otherwise.
+     */
+    public TranslateAnimation(int fromXType, float fromXValue, int toXType, float toXValue,
+            int fromYType, float fromYValue, int toYType, float toYValue) {
+
+        mFromXValue = fromXValue;
+        mToXValue = toXValue;
+        mFromYValue = fromYValue;
+        mToYValue = toYValue;
+
+        mFromXType = fromXType;
+        mToXType = toXType;
+        mFromYType = fromYType;
+        mToYType = toYType;
+    }
+
+
+    @Override
+    protected void applyTransformation(float interpolatedTime, Transformation t) {
+        float dx = mFromXDelta;
+        float dy = mFromYDelta;
+        if (mFromXDelta != mToXDelta) {
+            dx = mFromXDelta + ((mToXDelta - mFromXDelta) * interpolatedTime);
+        }
+        if (mFromYDelta != mToYDelta) {
+            dy = mFromYDelta + ((mToYDelta - mFromYDelta) * interpolatedTime);
+        }
+
+        t.getMatrix().setTranslate(dx, dy);
+    }
+
+    @Override
+    public void initialize(int width, int height, int parentWidth, int parentHeight) {
+        super.initialize(width, height, parentWidth, parentHeight);
+        mFromXDelta = resolveSize(mFromXType, mFromXValue, width, parentWidth);
+        mToXDelta = resolveSize(mToXType, mToXValue, width, parentWidth);
+        mFromYDelta = resolveSize(mFromYType, mFromYValue, height, parentHeight);
+        mToYDelta = resolveSize(mToYType, mToYValue, height, parentHeight);
+    }
+}
diff --git a/core/java/android/view/animation/package.html b/core/java/android/view/animation/package.html
new file mode 100755
index 0000000..87c99bb
--- /dev/null
+++ b/core/java/android/view/animation/package.html
@@ -0,0 +1,20 @@
+<html>
+<body>
+<p>Provides classes that handle tweened animations.</p>
+<p>Android provides two mechanisms
+    that you can use to create simple animations: <strong>tweened
+        animation</strong>, in which you tell Android to perform a series of simple
+    transformations (position, size, rotation, and so on) to the content of a
+    View; and <strong>frame-by-frame animation</strong>, which loads a series of Drawable resources
+    one after the other. Both animation types can be used in any View object
+    to provide simple rotating timers, activity icons, and other useful UI elements.
+    Tweened animation is handled by this package (android.view.animation); frame-by-frame animation is
+    handled by the {@link android.graphics.drawable.AnimationDrawable} class.
+    </p>
+
+<p>For more information on creating tweened or frame-by-frame animations, read the discussion in the
+<a href="{@docRoot}guide/topics/graphics/2d-graphics.html#tween-animation">2D Graphics</a>
+Dev Guide.</p>
+
+</body>
+</html>
diff --git a/core/java/android/view/inputmethod/BaseInputConnection.java b/core/java/android/view/inputmethod/BaseInputConnection.java
new file mode 100644
index 0000000..52b4107
--- /dev/null
+++ b/core/java/android/view/inputmethod/BaseInputConnection.java
@@ -0,0 +1,571 @@
+/*
+ * 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 android.view.inputmethod;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.SystemClock;
+import android.text.Editable;
+import android.text.NoCopySpan;
+import android.text.Selection;
+import android.text.Spannable;
+import android.text.SpannableStringBuilder;
+import android.text.Spanned;
+import android.text.TextUtils;
+import android.text.method.MetaKeyKeyListener;
+import android.util.Log;
+import android.util.LogPrinter;
+import android.view.KeyCharacterMap;
+import android.view.KeyEvent;
+import android.view.View;
+import android.view.ViewRoot;
+
+class ComposingText implements NoCopySpan {
+}
+
+/**
+ * Base class for implementors of the InputConnection interface, taking care
+ * of most of the common behavior for providing a connection to an Editable.
+ * Implementors of this class will want to be sure to implement
+ * {@link #getEditable} to provide access to their own editable object.
+ */
+public class BaseInputConnection implements InputConnection {
+    private static final boolean DEBUG = false;
+    private static final String TAG = "BaseInputConnection";
+    static final Object COMPOSING = new ComposingText();
+    
+    final InputMethodManager mIMM;
+    final Handler mH;
+    final View mTargetView;
+    final boolean mDummyMode;
+    
+    private Object[] mDefaultComposingSpans;
+    
+    Editable mEditable;
+    KeyCharacterMap mKeyCharacterMap;
+    
+    BaseInputConnection(InputMethodManager mgr, boolean dummyMode) {
+        mIMM = mgr;
+        mTargetView = null;
+        mH = null;
+        mDummyMode = dummyMode;
+    }
+    
+    public BaseInputConnection(View targetView, boolean dummyMode) {
+        mIMM = (InputMethodManager)targetView.getContext().getSystemService(
+                Context.INPUT_METHOD_SERVICE);
+        mH = targetView.getHandler();
+        mTargetView = targetView;
+        mDummyMode = dummyMode;
+    }
+    
+    public static final void removeComposingSpans(Spannable text) {
+        text.removeSpan(COMPOSING);
+        Object[] sps = text.getSpans(0, text.length(), Object.class);
+        if (sps != null) {
+            for (int i=sps.length-1; i>=0; i--) {
+                Object o = sps[i];
+                if ((text.getSpanFlags(o)&Spanned.SPAN_COMPOSING) != 0) {
+                    text.removeSpan(o);
+                }
+            }
+        }
+    }
+    
+    public static void setComposingSpans(Spannable text) {
+        final Object[] sps = text.getSpans(0, text.length(), Object.class);
+        if (sps != null) {
+            for (int i=sps.length-1; i>=0; i--) {
+                final Object o = sps[i];
+                if (o == COMPOSING) {
+                    text.removeSpan(o);
+                    continue;
+                }
+                final int fl = text.getSpanFlags(o);
+                if ((fl&(Spanned.SPAN_COMPOSING|Spanned.SPAN_POINT_MARK_MASK)) 
+                        != (Spanned.SPAN_COMPOSING|Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)) {
+                    text.setSpan(o, text.getSpanStart(o), text.getSpanEnd(o),
+                            (fl&Spanned.SPAN_POINT_MARK_MASK)
+                                    | Spanned.SPAN_COMPOSING
+                                    | Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+                }
+            }
+        }
+        
+        text.setSpan(COMPOSING, 0, text.length(),
+                Spanned.SPAN_EXCLUSIVE_EXCLUSIVE | Spanned.SPAN_COMPOSING);
+    }
+    
+    public static int getComposingSpanStart(Spannable text) {
+        return text.getSpanStart(COMPOSING);
+    }
+    
+    public static int getComposingSpanEnd(Spannable text) {
+        return text.getSpanEnd(COMPOSING);
+    }
+    
+    /**
+     * Return the target of edit operations.  The default implementation
+     * returns its own fake editable that is just used for composing text;
+     * subclasses that are real text editors should override this and
+     * supply their own.
+     */
+    public Editable getEditable() {
+        if (mEditable == null) {
+            mEditable = Editable.Factory.getInstance().newEditable("");
+            Selection.setSelection(mEditable, 0);
+        }
+        return mEditable;
+    }
+    
+    /**
+     * Default implementation does nothing.
+     */
+    public boolean beginBatchEdit() {
+        return false;
+    }
+
+    /**
+     * Default implementation does nothing.
+     */
+    public boolean endBatchEdit() {
+        return false;
+    }
+
+    /**
+     * Default implementation uses
+     * {@link MetaKeyKeyListener#clearMetaKeyState(long, int)
+     * MetaKeyKeyListener.clearMetaKeyState(long, int)} to clear the state.
+     */
+    public boolean clearMetaKeyStates(int states) {
+        final Editable content = getEditable();
+        if (content == null) return false;
+        MetaKeyKeyListener.clearMetaKeyState(content, states);
+        return true;
+    }
+
+    /**
+     * Default implementation does nothing.
+     */
+    public boolean commitCompletion(CompletionInfo text) {
+        return false;
+    }
+
+    /**
+     * Default implementation replaces any existing composing text with
+     * the given text.  In addition, only if dummy mode, a key event is
+     * sent for the new text and the current editable buffer cleared.
+     */
+    public boolean commitText(CharSequence text, int newCursorPosition) {
+        if (DEBUG) Log.v(TAG, "commitText " + text);
+        replaceText(text, newCursorPosition, false);
+        sendCurrentText();
+        return true;
+    }
+
+    /**
+     * The default implementation performs the deletion around the current
+     * selection position of the editable text.
+     */
+    public boolean deleteSurroundingText(int leftLength, int rightLength) {
+        if (DEBUG) Log.v(TAG, "deleteSurroundingText " + leftLength
+                + " / " + rightLength);
+        final Editable content = getEditable();
+        if (content == null) return false;
+
+        beginBatchEdit();
+        
+        int a = Selection.getSelectionStart(content);
+        int b = Selection.getSelectionEnd(content);
+
+        if (a > b) {
+            int tmp = a;
+            a = b;
+            b = tmp;
+        }
+
+        // ignore the composing text.
+        int ca = getComposingSpanStart(content);
+        int cb = getComposingSpanEnd(content);
+        if (cb < ca) {
+            int tmp = ca;
+            ca = cb;
+            cb = tmp;
+        }
+        if (ca != -1 && cb != -1) {
+            if (ca < a) a = ca;
+            if (cb > b) b = cb;
+        }
+
+        int deleted = 0;
+
+        if (leftLength > 0) {
+            int start = a - leftLength;
+            if (start < 0) start = 0;
+            content.delete(start, a);
+            deleted = a - start;
+        }
+
+        if (rightLength > 0) {
+            b = b - deleted;
+
+            int end = b + rightLength;
+            if (end > content.length()) end = content.length();
+
+            content.delete(b, end);
+        }
+        
+        endBatchEdit();
+        
+        return true;
+    }
+
+    /**
+     * The default implementation removes the composing state from the
+     * current editable text.  In addition, only if dummy mode, a key event is
+     * sent for the new text and the current editable buffer cleared.
+     */
+    public boolean finishComposingText() {
+        if (DEBUG) Log.v(TAG, "finishComposingText");
+        final Editable content = getEditable();
+        if (content != null) {
+            beginBatchEdit();
+            removeComposingSpans(content);
+            endBatchEdit();
+            sendCurrentText();
+        }
+        return true;
+    }
+
+    /**
+     * The default implementation uses TextUtils.getCapsMode to get the
+     * cursor caps mode for the current selection position in the editable
+     * text, unless in dummy mode in which case 0 is always returned.
+     */
+    public int getCursorCapsMode(int reqModes) {
+        if (mDummyMode) return 0;
+        
+        final Editable content = getEditable();
+        if (content == null) return 0;
+        
+        int a = Selection.getSelectionStart(content);
+        int b = Selection.getSelectionEnd(content);
+
+        if (a > b) {
+            int tmp = a;
+            a = b;
+            b = tmp;
+        }
+
+        return TextUtils.getCapsMode(content, a, reqModes);
+    }
+
+    /**
+     * The default implementation always returns null.
+     */
+    public ExtractedText getExtractedText(ExtractedTextRequest request, int flags) {
+        return null;
+    }
+
+    /**
+     * The default implementation returns the given amount of text from the
+     * current cursor position in the buffer.
+     */
+    public CharSequence getTextBeforeCursor(int length, int flags) {
+        final Editable content = getEditable();
+        if (content == null) return null;
+
+        int a = Selection.getSelectionStart(content);
+        int b = Selection.getSelectionEnd(content);
+
+        if (a > b) {
+            int tmp = a;
+            a = b;
+            b = tmp;
+        }
+
+        if (length > a) {
+            length = a;
+        }
+
+        if ((flags&GET_TEXT_WITH_STYLES) != 0) {
+            return content.subSequence(a - length, a);
+        }
+        return TextUtils.substring(content, a - length, a);
+    }
+
+    /**
+     * The default implementation returns the given amount of text from the
+     * current cursor position in the buffer.
+     */
+    public CharSequence getTextAfterCursor(int length, int flags) {
+        final Editable content = getEditable();
+        if (content == null) return null;
+
+        int a = Selection.getSelectionStart(content);
+        int b = Selection.getSelectionEnd(content);
+
+        if (a > b) {
+            int tmp = a;
+            a = b;
+            b = tmp;
+        }
+
+        if (b + length > content.length()) {
+            length = content.length() - b;
+        }
+
+
+        if ((flags&GET_TEXT_WITH_STYLES) != 0) {
+            return content.subSequence(b, b + length);
+        }
+        return TextUtils.substring(content, b, b + length);
+    }
+
+    /**
+     * The default implementation does nothing.
+     */
+    public boolean performEditorAction(int actionCode) {
+        return false;
+    }
+
+    /**
+     * The default implementation does nothing.
+     */
+    public boolean performContextMenuAction(int id) {
+        return false;
+    }
+
+    /**
+     * The default implementation does nothing.
+     */
+    public boolean performPrivateCommand(String action, Bundle data) {
+        return false;
+    }
+
+    /**
+     * The default implementation places the given text into the editable,
+     * replacing any existing composing text.  The new text is marked as
+     * in a composing state with the composing style.
+     */
+    public boolean setComposingText(CharSequence text, int newCursorPosition) {
+        if (DEBUG) Log.v(TAG, "setComposingText " + text);
+        replaceText(text, newCursorPosition, true);
+        return true;
+    }
+
+    /**
+     * The default implementation changes the selection position in the
+     * current editable text.
+     */
+    public boolean setSelection(int start, int end) {
+        if (DEBUG) Log.v(TAG, "setSelection " + start + ", " + end);
+        final Editable content = getEditable();
+        if (content == null) return false;
+        int len = content.length();
+        if (start > len || end > len) {
+            // If the given selection is out of bounds, just ignore it.
+            // Most likely the text was changed out from under the IME,
+            // the the IME is going to have to update all of its state
+            // anyway.
+            return true;
+        }
+        Selection.setSelection(content, start, end);
+        return true;
+    }
+
+    /**
+     * Provides standard implementation for sending a key event to the window
+     * attached to the input connection's view.
+     */
+    public boolean sendKeyEvent(KeyEvent event) {
+        synchronized (mIMM.mH) {
+            Handler h = mH;
+            if (h == null) {
+                if (mIMM.mServedView != null) {
+                    h = mIMM.mServedView.getHandler();
+                }
+            }
+            if (h != null) {
+                h.sendMessage(h.obtainMessage(ViewRoot.DISPATCH_KEY_FROM_IME,
+                        event));
+            }
+        }
+        return false;
+    }
+    
+    /**
+     * Updates InputMethodManager with the current fullscreen mode.
+     */
+    public boolean reportFullscreenMode(boolean enabled) {
+        mIMM.setFullscreenMode(enabled);
+        return true;
+    }
+    
+    private void sendCurrentText() {
+        if (!mDummyMode) {
+            return;
+        }
+        
+        Editable content = getEditable();
+        if (content != null) {
+            final int N = content.length();
+            if (N == 0) {
+                return;
+            }
+            if (N == 1) {
+                // If it's 1 character, we have a chance of being
+                // able to generate normal key events...
+                if (mKeyCharacterMap == null) {
+                    mKeyCharacterMap = KeyCharacterMap.load(
+                            KeyCharacterMap.BUILT_IN_KEYBOARD);
+                }
+                char[] chars = new char[1];
+                content.getChars(0, 1, chars, 0);
+                KeyEvent[] events = mKeyCharacterMap.getEvents(chars);
+                if (events != null) {
+                    for (int i=0; i<events.length; i++) {
+                        if (DEBUG) Log.v(TAG, "Sending: " + events[i]);
+                        sendKeyEvent(events[i]);
+                    }
+                    content.clear();
+                    return;
+                }
+            }
+            
+            // Otherwise, revert to the special key event containing
+            // the actual characters.
+            KeyEvent event = new KeyEvent(SystemClock.uptimeMillis(),
+                    content.toString(), KeyCharacterMap.BUILT_IN_KEYBOARD, 0);
+            sendKeyEvent(event);
+            content.clear();
+        }
+    }
+    
+    private void replaceText(CharSequence text, int newCursorPosition,
+            boolean composing) {
+        final Editable content = getEditable();
+        if (content == null) {
+            return;
+        }
+        
+        beginBatchEdit();
+        
+        // delete composing text set previously.
+        int a = getComposingSpanStart(content);
+        int b = getComposingSpanEnd(content);
+
+        if (DEBUG) Log.v(TAG, "Composing span: " + a + " to " + b);
+        
+        if (b < a) {
+            int tmp = a;
+            a = b;
+            b = tmp;
+        }
+
+        if (a != -1 && b != -1) {
+            removeComposingSpans(content);
+        } else {
+            a = Selection.getSelectionStart(content);
+            b = Selection.getSelectionEnd(content);
+            if (a >=0 && b>= 0 && a != b) {
+                if (b < a) {
+                    int tmp = a;
+                    a = b;
+                    b = tmp;
+                }
+            }
+        }
+
+        if (composing) {
+            Spannable sp = null;
+            if (!(text instanceof Spannable)) {
+                sp = new SpannableStringBuilder(text);
+                text = sp;
+                if (mDefaultComposingSpans == null) {
+                    Context context;
+                    if (mTargetView != null) {
+                        context = mTargetView.getContext();
+                    } else if (mIMM.mServedView != null) {
+                        context = mIMM.mServedView.getContext();
+                    } else {
+                        context = null;
+                    }
+                    if (context != null) {
+                        TypedArray ta = context.getTheme()
+                                .obtainStyledAttributes(new int[] {
+                                        com.android.internal.R.attr.candidatesTextStyleSpans
+                                });
+                        CharSequence style = ta.getText(0);
+                        ta.recycle();
+                        if (style != null && style instanceof Spanned) {
+                            mDefaultComposingSpans = ((Spanned)style).getSpans(
+                                    0, style.length(), Object.class);
+                        }
+                    }
+                }
+                if (mDefaultComposingSpans != null) {
+                    for (int i = 0; i < mDefaultComposingSpans.length; ++i) {
+                        sp.setSpan(mDefaultComposingSpans[i], 0, sp.length(),
+                                Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+                    }
+                }
+            } else {
+                sp = (Spannable)text;
+            }
+            setComposingSpans(sp);
+        }
+        
+        if (DEBUG) Log.v(TAG, "Replacing from " + a + " to " + b + " with \""
+                + text + "\", composing=" + composing
+                + ", type=" + text.getClass().getCanonicalName());
+        
+        if (DEBUG) {
+            LogPrinter lp = new LogPrinter(Log.VERBOSE, TAG);
+            lp.println("Current text:");
+            TextUtils.dumpSpans(content, lp, "  ");
+            lp.println("Composing text:");
+            TextUtils.dumpSpans(text, lp, "  ");
+        }
+        
+        // Position the cursor appropriately, so that after replacing the
+        // desired range of text it will be located in the correct spot.
+        // This allows us to deal with filters performing edits on the text
+        // we are providing here.
+        if (newCursorPosition > 0) {
+            newCursorPosition += b - 1;
+        } else {
+            newCursorPosition += a;
+        }
+        if (newCursorPosition < 0) newCursorPosition = 0;
+        if (newCursorPosition > content.length())
+            newCursorPosition = content.length();
+        Selection.setSelection(content, newCursorPosition);
+
+        content.replace(a, b, text);
+        
+        if (DEBUG) {
+            LogPrinter lp = new LogPrinter(Log.VERBOSE, TAG);
+            lp.println("Final text:");
+            TextUtils.dumpSpans(content, lp, "  ");
+        }
+        
+        endBatchEdit();
+    }
+}
diff --git a/core/java/android/view/inputmethod/CompletionInfo.aidl b/core/java/android/view/inputmethod/CompletionInfo.aidl
new file mode 100644
index 0000000..e601054
--- /dev/null
+++ b/core/java/android/view/inputmethod/CompletionInfo.aidl
@@ -0,0 +1,19 @@
+/*
+ * 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 android.view.inputmethod;
+
+parcelable CompletionInfo;
diff --git a/core/java/android/view/inputmethod/CompletionInfo.java b/core/java/android/view/inputmethod/CompletionInfo.java
new file mode 100644
index 0000000..3a8fe72
--- /dev/null
+++ b/core/java/android/view/inputmethod/CompletionInfo.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright (C) 2007-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 android.view.inputmethod;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+
+/**
+ * Information about a single text completion that an editor has reported to
+ * an input method.
+ */
+public final class CompletionInfo implements Parcelable {
+    static final String TAG = "CompletionInfo";
+    
+    final long mId;
+    final int mPosition;
+    final CharSequence mText;
+    final CharSequence mLabel;
+    
+    /**
+     * Create a simple completion with just text, no label.
+     */
+    public CompletionInfo(long id, int index, CharSequence text) {
+        mId = id;
+        mPosition = index;
+        mText = text;
+        mLabel = null;
+    }
+
+    /**
+     * Create a full completion with both text and label.
+     */
+    public CompletionInfo(long id, int index, CharSequence text, CharSequence label) {
+        mId = id;
+        mPosition = index;
+        mText = text;
+        mLabel = label;
+    }
+
+    CompletionInfo(Parcel source) {
+        mId = source.readLong();
+        mPosition = source.readInt();
+        mText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source);
+        mLabel = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source);
+    }
+    
+    /**
+     * Return the abstract identifier for this completion, typically
+     * corresponding to the id associated with it in the original adapter.
+     */
+    public long getId() {
+        return mId;
+    }
+    
+    /**
+     * Return the original position of this completion, typically
+     * corresponding to its position in the original adapter.
+     */
+    public int getPosition() {
+        return mPosition;
+    }
+    
+    /**
+     * Return the actual text associated with this completion.  This is the
+     * real text that will be inserted into the editor if the user selects it.
+     */
+    public CharSequence getText() {
+        return mText;
+    }
+    
+    /**
+     * Return the user-visible label for the completion, or null if the plain
+     * text should be shown.  If non-null, this will be what the user sees as
+     * the completion option instead of the actual text.
+     */
+    public CharSequence getLabel() {
+        return mLabel;
+    }
+    
+    @Override
+    public String toString() {
+        return "CompletionInfo{#" + mPosition + " \"" + mText
+                + "\" id=" + mId + " label=" + mLabel + "}";
+    }
+
+    /**
+     * Used to package this object into a {@link Parcel}.
+     * 
+     * @param dest The {@link Parcel} to be written.
+     * @param flags The flags used for parceling.
+     */
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeLong(mId);
+        dest.writeInt(mPosition);
+        TextUtils.writeToParcel(mText, dest, flags);
+        TextUtils.writeToParcel(mLabel, dest, flags);
+    }
+
+    /**
+     * Used to make this class parcelable.
+     */
+    public static final Parcelable.Creator<CompletionInfo> CREATOR
+            = new Parcelable.Creator<CompletionInfo>() {
+        public CompletionInfo createFromParcel(Parcel source) {
+            return new CompletionInfo(source);
+        }
+
+        public CompletionInfo[] newArray(int size) {
+            return new CompletionInfo[size];
+        }
+    };
+
+    public int describeContents() {
+        return 0;
+    }
+}
diff --git a/core/java/android/view/inputmethod/EditorInfo.aidl b/core/java/android/view/inputmethod/EditorInfo.aidl
new file mode 100644
index 0000000..48068f0
--- /dev/null
+++ b/core/java/android/view/inputmethod/EditorInfo.aidl
@@ -0,0 +1,19 @@
+/*
+ * 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 android.view.inputmethod;
+
+parcelable EditorInfo;
diff --git a/core/java/android/view/inputmethod/EditorInfo.java b/core/java/android/view/inputmethod/EditorInfo.java
new file mode 100644
index 0000000..0405371
--- /dev/null
+++ b/core/java/android/view/inputmethod/EditorInfo.java
@@ -0,0 +1,263 @@
+package android.view.inputmethod;
+
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.InputType;
+import android.text.TextUtils;
+import android.util.Printer;
+
+/**
+ * An EditorInfo describes several attributes of a text editing object
+ * that an input method is communicating with (typically an EditText), most
+ * importantly the type of text content it contains.
+ */
+public class EditorInfo implements InputType, Parcelable {
+    /**
+     * The content type of the text box, whose bits are defined by
+     * {@link InputType}.
+     * 
+     * @see InputType
+     * @see #TYPE_MASK_CLASS
+     * @see #TYPE_MASK_VARIATION
+     * @see #TYPE_MASK_FLAGS
+     */
+    public int inputType = TYPE_NULL;
+
+    /**
+     * Set of bits in {@link #imeOptions} that provide alternative actions
+     * associated with the "enter" key.  This both helps the IME provide
+     * better feedback about what the enter key will do, and also allows it
+     * to provide alternative mechanisms for providing that command.
+     */
+    public static final int IME_MASK_ACTION = 0x000000ff;
+    
+    /**
+     * Bits of {@link #IME_MASK_ACTION}: there is no special action
+     * associated with this editor.
+     */
+    public static final int IME_ACTION_NONE = 0x00000000;
+    
+    /**
+     * Bits of {@link #IME_MASK_ACTION}: the action key performs a "go"
+     * operation to take the user to the target of the text they typed.
+     * Typically used, for example, when entering a URL.
+     */
+    public static final int IME_ACTION_GO = 0x00000001;
+    
+    /**
+     * Bits of {@link #IME_MASK_ACTION}: the action key performs a "search"
+     * operation, taking the user to the results of searching for the text
+     * the have typed (in whatever context is appropriate).
+     */
+    public static final int IME_ACTION_SEARCH = 0x00000002;
+    
+    /**
+     * Bits of {@link #IME_MASK_ACTION}: the action key performs a "send"
+     * operation, delivering the text to its target.  This is typically used
+     * when composing a message.
+     */
+    public static final int IME_ACTION_SEND = 0x00000003;
+    
+    /**
+     * Bits of {@link #IME_MASK_ACTION}: the action key performs a "next"
+     * operation, taking the user to the next field that will accept text.
+     */
+    public static final int IME_ACTION_NEXT = 0x00000004;
+    
+    /**
+     * Flag of {@link #imeOptions}: used in conjunction with
+     * {@link #IME_MASK_ACTION}, this indicates that the action should not
+     * be available in-line as the same as a "enter" key.  Typically this is
+     * because the action has such a significant impact or is not recoverable
+     * enough that accidentally hitting it should be avoided, such as sending
+     * a message.
+     */
+    public static final int IME_FLAG_NO_ENTER_ACTION = 0x40000000;
+    
+    /**
+     * Generic non-special type for {@link #imeOptions}.
+     */
+    public static final int IME_NORMAL = 0x00000000;
+    
+    /**
+     * Special code for when the ime option has been undefined.  This is not
+     * used with the EditorInfo structure, but can be used elsewhere.
+     */
+    public static final int IME_UNDEFINED = 0x80000000;
+    
+    /**
+     * Extended type information for the editor, to help the IME better
+     * integrate with it.
+     */
+    public int imeOptions = IME_NORMAL;
+    
+    /**
+     * A string supplying additional information options that are
+     * private to a particular IME implementation.  The string must be
+     * scoped to a package owned by the implementation, to ensure there are
+     * no conflicts between implementations, but other than that you can put
+     * whatever you want in it to communicate with the IME.  For example,
+     * you could have a string that supplies an argument like
+     * <code>"com.example.myapp.SpecialMode=3"</code>.  This field is can be
+     * filled in from the {@link android.R.attr#privateImeOptions}
+     * attribute of a TextView.
+     */
+    public String privateImeOptions = null;
+    
+    /**
+     * In some cases an IME may be able to display an arbitrary label for
+     * a command the user can perform, which you can specify here.  You can
+     * not count on this being used.
+     */
+    public CharSequence actionLabel = null;
+    
+    /**
+     * If {@link #actionLabel} has been given, this is the id for that command
+     * when the user presses its button that is delivered back with
+     * {@link InputConnection#performEditorAction(int)
+     * InputConnection.performEditorAction()}.
+     */
+    public int actionId = 0;
+    
+    /**
+     * The text offset of the start of the selection at the time editing
+     * began; -1 if not known.
+     */
+    public int initialSelStart = -1;
+    
+    /**
+     * The text offset of the end of the selection at the time editing
+     * began; -1 if not known.
+     */
+    public int initialSelEnd = -1;
+    
+    /**
+     * The capitalization mode of the first character being edited in the
+     * text.  Values may be any combination of
+     * {@link TextUtils#CAP_MODE_CHARACTERS TextUtils.CAP_MODE_CHARACTERS},
+     * {@link TextUtils#CAP_MODE_WORDS TextUtils.CAP_MODE_WORDS}, and
+     * {@link TextUtils#CAP_MODE_SENTENCES TextUtils.CAP_MODE_SENTENCES}, though
+     * you should generally just take a non-zero value to mean start out in
+     * caps mode.
+     */
+    public int initialCapsMode = 0;
+    
+    /**
+     * The "hint" text of the text view, typically shown in-line when the
+     * text is empty to tell the user what to enter.
+     */
+    public CharSequence hintText;
+    
+    /**
+     * A label to show to the user describing the text they are writing.
+     */
+    public CharSequence label;
+    
+    /**
+     * Name of the package that owns this editor.
+     */
+    public String packageName;
+    
+    /**
+     * Identifier for the editor's field.  This is optional, and may be
+     * 0.  By default it is filled in with the result of
+     * {@link android.view.View#getId() View.getId()} on the View that
+     * is being edited.
+     */
+    public int fieldId;
+    
+    /**
+     * Additional name for the editor's field.  This can supply additional
+     * name information for the field.  By default it is null.  The actual
+     * contents have no meaning.
+     */
+    public String fieldName;
+    
+    /**
+     * Any extra data to supply to the input method.  This is for extended
+     * communication with specific input methods; the name fields in the
+     * bundle should be scoped (such as "com.mydomain.im.SOME_FIELD") so
+     * that they don't conflict with others.  This field is can be
+     * filled in from the {@link android.R.attr#editorExtras}
+     * attribute of a TextView.
+     */
+    public Bundle extras;
+    
+    /**
+     * Write debug output of this object.
+     */
+    public void dump(Printer pw, String prefix) {
+        pw.println(prefix + "inputType=0x" + Integer.toHexString(inputType)
+                + " imeOptions=0x" + Integer.toHexString(imeOptions)
+                + " privateImeOptions=" + privateImeOptions);
+        pw.println(prefix + "actionLabel=" + actionLabel
+                + " actionId=" + actionId);
+        pw.println(prefix + "initialSelStart=" + initialSelStart
+                + " initialSelEnd=" + initialSelEnd
+                + " initialCapsMode=0x"
+                + Integer.toHexString(initialCapsMode));
+        pw.println(prefix + "hintText=" + hintText
+                + " label=" + label);
+        pw.println(prefix + "packageName=" + packageName
+                + " fieldId=" + fieldId
+                + " fieldName=" + fieldName);
+        pw.println(prefix + "extras=" + extras);
+    }
+    
+    /**
+     * Used to package this object into a {@link Parcel}.
+     * 
+     * @param dest The {@link Parcel} to be written.
+     * @param flags The flags used for parceling.
+     */
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeInt(inputType);
+        dest.writeInt(imeOptions);
+        dest.writeString(privateImeOptions);
+        TextUtils.writeToParcel(actionLabel, dest, flags);
+        dest.writeInt(actionId);
+        dest.writeInt(initialSelStart);
+        dest.writeInt(initialSelEnd);
+        dest.writeInt(initialCapsMode);
+        TextUtils.writeToParcel(hintText, dest, flags);
+        TextUtils.writeToParcel(label, dest, flags);
+        dest.writeString(packageName);
+        dest.writeInt(fieldId);
+        dest.writeString(fieldName);
+        dest.writeBundle(extras);
+    }
+
+    /**
+     * Used to make this class parcelable.
+     */
+    public static final Parcelable.Creator<EditorInfo> CREATOR = new Parcelable.Creator<EditorInfo>() {
+        public EditorInfo createFromParcel(Parcel source) {
+            EditorInfo res = new EditorInfo();
+            res.inputType = source.readInt();
+            res.imeOptions = source.readInt();
+            res.privateImeOptions = source.readString();
+            res.actionLabel = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source);
+            res.actionId = source.readInt();
+            res.initialSelStart = source.readInt();
+            res.initialSelEnd = source.readInt();
+            res.initialCapsMode = source.readInt();
+            res.hintText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source);
+            res.label = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source);
+            res.packageName = source.readString();
+            res.fieldId = source.readInt();
+            res.fieldName = source.readString();
+            res.extras = source.readBundle();
+            return res;
+        }
+
+        public EditorInfo[] newArray(int size) {
+            return new EditorInfo[size];
+        }
+    };
+
+    public int describeContents() {
+        return 0;
+    }
+
+}
diff --git a/core/java/android/view/inputmethod/ExtractedText.aidl b/core/java/android/view/inputmethod/ExtractedText.aidl
new file mode 100644
index 0000000..95e56d7
--- /dev/null
+++ b/core/java/android/view/inputmethod/ExtractedText.aidl
@@ -0,0 +1,19 @@
+/*
+ * 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 android.view.inputmethod;
+
+parcelable ExtractedText;
diff --git a/core/java/android/view/inputmethod/ExtractedText.java b/core/java/android/view/inputmethod/ExtractedText.java
new file mode 100644
index 0000000..e5d3cae
--- /dev/null
+++ b/core/java/android/view/inputmethod/ExtractedText.java
@@ -0,0 +1,102 @@
+package android.view.inputmethod;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+
+/**
+ * Information about text that has been extracted for use by an input method.
+ */
+public class ExtractedText implements Parcelable {
+    /**
+     * The text that has been extracted.
+     */
+    public CharSequence text;
+
+    /**
+     * The offset in the overall text at which the extracted text starts.
+     */
+    public int startOffset;
+    
+    /**
+     * If the content is a report of a partial text change, this is the
+     * offset where the change starts and it runs until
+     * {@link #partialEndOffset}.  If the content is the full text, this
+     * field is -1.
+     */
+    public int partialStartOffset;
+    
+    /**
+     * If the content is a report of a partial text change, this is the offset
+     * where the change ends.  Note that the actual text may be larger or
+     * smaller than the difference between this and {@link #partialEndOffset},
+     * meaning a reduction or increase, respectively, in the total text.
+     */
+    public int partialEndOffset;
+    
+    /**
+     * The offset where the selection currently starts within the extracted
+     * text.  The real selection start position is at
+     * <var>startOffset</var>+<var>selectionStart</var>.
+     */
+    public int selectionStart;
+    
+    /**
+     * The offset where the selection currently ends within the extracted
+     * text.  The real selection end position is at
+     * <var>startOffset</var>+<var>selectionEnd</var>.
+     */
+    public int selectionEnd;
+    
+    /**
+     * Bit for {@link #flags}: set if the text being edited can only be on
+     * a single line.
+     */
+    public static final int FLAG_SINGLE_LINE = 0x0001;
+    
+    /**
+     * Additional bit flags of information about the edited text.
+     */
+    public int flags;
+    
+    /**
+     * Used to package this object into a {@link Parcel}.
+     * 
+     * @param dest The {@link Parcel} to be written.
+     * @param flags The flags used for parceling.
+     */
+    public void writeToParcel(Parcel dest, int flags) {
+        TextUtils.writeToParcel(text, dest, flags);
+        dest.writeInt(startOffset);
+        dest.writeInt(partialStartOffset);
+        dest.writeInt(partialEndOffset);
+        dest.writeInt(selectionStart);
+        dest.writeInt(selectionEnd);
+        dest.writeInt(flags);
+    }
+
+    /**
+     * Used to make this class parcelable.
+     */
+    public static final Parcelable.Creator<ExtractedText> CREATOR = new Parcelable.Creator<ExtractedText>() {
+        public ExtractedText createFromParcel(Parcel source) {
+            ExtractedText res = new ExtractedText();
+            res.text = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source);
+            res.startOffset = source.readInt();
+            res.partialStartOffset = source.readInt();
+            res.partialEndOffset = source.readInt();
+            res.selectionStart = source.readInt();
+            res.selectionEnd = source.readInt();
+            res.flags = source.readInt();
+            return res;
+        }
+
+        public ExtractedText[] newArray(int size) {
+            return new ExtractedText[size];
+        }
+    };
+
+    public int describeContents() {
+        return 0;
+    }
+}
diff --git a/core/java/android/view/inputmethod/ExtractedTextRequest.aidl b/core/java/android/view/inputmethod/ExtractedTextRequest.aidl
new file mode 100644
index 0000000..c69acc7
--- /dev/null
+++ b/core/java/android/view/inputmethod/ExtractedTextRequest.aidl
@@ -0,0 +1,19 @@
+/*
+ * 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 android.view.inputmethod;
+
+parcelable ExtractedTextRequest;
diff --git a/core/java/android/view/inputmethod/ExtractedTextRequest.java b/core/java/android/view/inputmethod/ExtractedTextRequest.java
new file mode 100644
index 0000000..e84b094
--- /dev/null
+++ b/core/java/android/view/inputmethod/ExtractedTextRequest.java
@@ -0,0 +1,70 @@
+package android.view.inputmethod;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+
+/**
+ * Description of what an input method would like from an application when
+ * extract text from its input editor.
+ */
+public class ExtractedTextRequest implements Parcelable {
+    /**
+     * Arbitrary integer that can be supplied in the request, which will be
+     * delivered back when reporting updates.
+     */
+    public int token;
+    
+    /**
+     * Additional request flags, having the same possible values as the
+     * flags parameter of {@link InputConnection#getTextBeforeCursor
+     * InputConnection.getTextBeforeCursor()}.
+     */
+    public int flags;
+    
+    /**
+     * Hint for the maximum number of lines to return.
+     */
+    public int hintMaxLines;
+    
+    /**
+     * Hint for the maximum number of characters to return.
+     */
+    public int hintMaxChars;
+    
+    /**
+     * Used to package this object into a {@link Parcel}.
+     * 
+     * @param dest The {@link Parcel} to be written.
+     * @param flags The flags used for parceling.
+     */
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeInt(token);
+        dest.writeInt(this.flags);
+        dest.writeInt(hintMaxLines);
+        dest.writeInt(hintMaxChars);
+    }
+
+    /**
+     * Used to make this class parcelable.
+     */
+    public static final Parcelable.Creator<ExtractedTextRequest> CREATOR
+            = new Parcelable.Creator<ExtractedTextRequest>() {
+        public ExtractedTextRequest createFromParcel(Parcel source) {
+            ExtractedTextRequest res = new ExtractedTextRequest();
+            res.token = source.readInt();
+            res.flags = source.readInt();
+            res.hintMaxLines = source.readInt();
+            res.hintMaxChars = source.readInt();
+            return res;
+        }
+
+        public ExtractedTextRequest[] newArray(int size) {
+            return new ExtractedTextRequest[size];
+        }
+    };
+
+    public int describeContents() {
+        return 0;
+    }
+}
diff --git a/core/java/android/view/inputmethod/InputBinding.aidl b/core/java/android/view/inputmethod/InputBinding.aidl
new file mode 100644
index 0000000..ea09d8b
--- /dev/null
+++ b/core/java/android/view/inputmethod/InputBinding.aidl
@@ -0,0 +1,19 @@
+/*
+ * 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 android.view.inputmethod;
+
+parcelable InputBinding;
diff --git a/core/java/android/view/inputmethod/InputBinding.java b/core/java/android/view/inputmethod/InputBinding.java
new file mode 100644
index 0000000..f4209ef
--- /dev/null
+++ b/core/java/android/view/inputmethod/InputBinding.java
@@ -0,0 +1,152 @@
+/*
+ * Copyright (C) 2007-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 android.view.inputmethod;
+
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+
+/**
+ * Information given to an {@link InputMethod} about a client connecting
+ * to it.
+ */
+public final class InputBinding implements Parcelable {
+    static final String TAG = "InputBinding";
+    
+    /**
+     * The connection back to the client.
+     */
+    final InputConnection mConnection;
+    
+    /**
+     * A remotable token for the connection back to the client.
+     */
+    final IBinder mConnectionToken;
+    
+    /**
+     * The UID where this binding came from.
+     */
+    final int mUid;
+    
+    /**
+     * The PID where this binding came from.
+     */
+    final int mPid;
+    
+    /**
+     * Constructor.
+     * 
+     * @param conn The interface for communicating back with the application.
+     * @param connToken A remoteable token for communicating across processes.
+     * @param uid The user id of the client of this binding.
+     * @param pid The process id of where the binding came from.
+     */
+    public InputBinding(InputConnection conn, IBinder connToken,
+            int uid, int pid) {
+        mConnection = conn;
+        mConnectionToken = connToken;
+        mUid = uid;
+        mPid = pid;
+    }
+
+    /**
+     * Constructor from an existing InputBinding taking a new local input
+     * connection interface.
+     * 
+     * @param conn The new connection interface.
+     * @param binding Existing binding to copy.
+     */
+    public InputBinding(InputConnection conn, InputBinding binding) {
+        mConnection = conn;
+        mConnectionToken = binding.getConnectionToken();
+        mUid = binding.getUid();
+        mPid = binding.getPid();
+    }
+
+    InputBinding(Parcel source) {
+        mConnection = null;
+        mConnectionToken = source.readStrongBinder();
+        mUid = source.readInt();
+        mPid = source.readInt();
+    }
+    
+    /**
+     * Return the connection for interacting back with the application.
+     */
+    public InputConnection getConnection() {
+        return mConnection;
+    }
+    
+    /**
+     * Return the token for the connection back to the application.  You can
+     * not use this directly, it must be converted to a {@link InputConnection}
+     * for you.
+     */
+    public IBinder getConnectionToken() {
+        return mConnectionToken;
+    }
+    
+    /**
+     * Return the user id of the client associated with this binding.
+     */
+    public int getUid() {
+        return mUid;
+    }
+    
+    /**
+     * Return the process id where this binding came from.
+     */
+    public int getPid() {
+        return mPid;
+    }
+    
+    @Override
+    public String toString() {
+        return "InputBinding{" + mConnectionToken
+                + " / uid " + mUid + " / pid " + mPid + "}";
+    }
+
+    /**
+     * Used to package this object into a {@link Parcel}.
+     * 
+     * @param dest The {@link Parcel} to be written.
+     * @param flags The flags used for parceling.
+     */
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeStrongBinder(mConnectionToken);
+        dest.writeInt(mUid);
+        dest.writeInt(mPid);
+    }
+
+    /**
+     * Used to make this class parcelable.
+     */
+    public static final Parcelable.Creator<InputBinding> CREATOR = new Parcelable.Creator<InputBinding>() {
+        public InputBinding createFromParcel(Parcel source) {
+            return new InputBinding(source);
+        }
+
+        public InputBinding[] newArray(int size) {
+            return new InputBinding[size];
+        }
+    };
+
+    public int describeContents() {
+        return 0;
+    }
+}
diff --git a/core/java/android/view/inputmethod/InputConnection.java b/core/java/android/view/inputmethod/InputConnection.java
new file mode 100644
index 0000000..32cce35
--- /dev/null
+++ b/core/java/android/view/inputmethod/InputConnection.java
@@ -0,0 +1,320 @@
+/*
+ * Copyright (C) 2007-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 android.view.inputmethod;
+
+import android.os.Bundle;
+import android.text.Spanned;
+import android.view.KeyCharacterMap;
+import android.view.KeyEvent;
+
+/**
+ * The InputConnection interface is the communication channel from an
+ * {@link InputMethod} back to the application that is receiving its input. It
+ * is used to perform such things as reading text around the cursor,
+ * committing text to the text box, and sending raw key events to the application.
+ * 
+ * <p>Implementations of this interface should generally be done by
+ * subclassing {@link BaseInputConnection}.
+ */
+public interface InputConnection {
+    /**
+     * Flag for use with {@link #getTextAfterCursor} and
+     * {@link #getTextBeforeCursor} to have style information returned along
+     * with the text.  If not set, you will receive only the raw text.  If
+     * set, you may receive a complex CharSequence of both text and style
+     * spans.
+     */
+    static final int GET_TEXT_WITH_STYLES = 0x0001;
+    
+    /**
+     * Flag for use with {@link #getExtractedText} to indicate you would
+     * like to receive updates when the extracted text changes.
+     */
+    public static final int GET_EXTRACTED_TEXT_MONITOR = 0x0001;
+    
+    /**
+     * Get <var>n</var> characters of text before the current cursor position.
+     * 
+     * <p>This method may fail either if the input connection has become invalid
+     * (such as its process crashing) or the client is taking too long to
+     * respond with the text (it is given a couple seconds to return).
+     * In either case, a null is returned.
+     * 
+     * @param n The expected length of the text.
+     * @param flags Supplies additional options controlling how the text is
+     * returned.  May be either 0 or {@link #GET_TEXT_WITH_STYLES}.
+     * 
+     * @return Returns the text before the cursor position; the length of the
+     * returned text might be less than <var>n</var>.
+     */
+    public CharSequence getTextBeforeCursor(int n, int flags);
+
+    /**
+     * Get <var>n</var> characters of text after the current cursor position.
+     * 
+     * <p>This method may fail either if the input connection has become invalid
+     * (such as its process crashing) or the client is taking too long to
+     * respond with the text (it is given a couple seconds to return).
+     * In either case, a null is returned.
+     * 
+     * @param n The expected length of the text.
+     * @param flags Supplies additional options controlling how the text is
+     * returned.  May be either 0 or {@link #GET_TEXT_WITH_STYLES}.
+     * 
+     * @return Returns the text after the cursor position; the length of the
+     * returned text might be less than <var>n</var>.
+     */
+    public CharSequence getTextAfterCursor(int n, int flags);
+
+    /**
+     * Retrieve the current capitalization mode in effect at the current
+     * cursor position in the text.  See
+     * {@link android.text.TextUtils#getCapsMode TextUtils.getCapsMode} for
+     * more information.
+     * 
+     * <p>This method may fail either if the input connection has become invalid
+     * (such as its process crashing) or the client is taking too long to
+     * respond with the text (it is given a couple seconds to return).
+     * In either case, a 0 is returned.
+     * 
+     * @param reqModes The desired modes to retrieve, as defined by
+     * {@link android.text.TextUtils#getCapsMode TextUtils.getCapsMode}.  These
+     * constants are defined so that you can simply pass the current
+     * {@link EditorInfo#inputType TextBoxAttribute.contentType} value
+     * directly in to here.
+     * 
+     * @return Returns the caps mode flags that are in effect.
+     */
+    public int getCursorCapsMode(int reqModes);
+    
+    /**
+     * Retrieve the current text in the input connection's editor, and monitor
+     * for any changes to it.  This function returns with the current text,
+     * and optionally the input connection can send updates to the
+     * input method when its text changes.
+     * 
+     * <p>This method may fail either if the input connection has become invalid
+     * (such as its process crashing) or the client is taking too long to
+     * respond with the text (it is given a couple seconds to return).
+     * In either case, a null is returned.
+     * 
+     * @param request Description of how the text should be returned.
+     * @param flags Additional options to control the client, either 0 or
+     * {@link #GET_EXTRACTED_TEXT_MONITOR}.
+     * 
+     * @return Returns an ExtractedText object describing the state of the
+     * text view and containing the extracted text itself.
+     */
+    public ExtractedText getExtractedText(ExtractedTextRequest request,
+            int flags);
+
+    /**
+     * Delete <var>leftLength</var> characters of text before the current cursor
+     * position, and delete <var>rightLength</var> characters of text after the
+     * current cursor position, excluding composing text.
+     * 
+     * @param leftLength The number of characters to be deleted before the
+     *        current cursor position.
+     * @param rightLength The number of characters to be deleted after the
+     *        current cursor position.
+     *        
+     * @return Returns true on success, false if the input connection is no longer
+     * valid.
+     */
+    boolean deleteSurroundingText(int leftLength, int rightLength);
+
+    /**
+     * Set composing text around the current cursor position with the given text,
+     * and set the new cursor position.  Any composing text set previously will
+     * be removed automatically.
+     * 
+     * @param text The composing text with styles if necessary. If no style
+     *        object attached to the text, the default style for composing text
+     *        is used. See {#link android.text.Spanned} for how to attach style
+     *        object to the text. {#link android.text.SpannableString} and
+     *        {#link android.text.SpannableStringBuilder} are two
+     *        implementations of the interface {#link android.text.Spanned}.
+     * @param newCursorPosition The new cursor position around the text.  If
+     *        > 0, this is relative to the end of the text - 1; if <= 0, this
+     *        is relative to the start of the text.  So a value of 1 will
+     *        always advance you to the position after the full text being
+     *        inserted.  Note that this means you can't position the cursor
+     *        within the text, because the editor can make modifications to
+     *        the text you are providing so it is not possible to correctly
+     *        specify locations there.
+     * 
+     * @return Returns true on success, false if the input connection is no longer
+     * valid.
+     */
+    public boolean setComposingText(CharSequence text, int newCursorPosition);
+
+    /**
+     * Have the text editor finish whatever composing text is currently
+     * active.  This simply leaves the text as-is, removing any special
+     * composing styling or other state that was around it.  The cursor
+     * position remains unchanged.
+     */
+    public boolean finishComposingText();
+    
+    /**
+     * Commit text to the text box and set the new cursor position.
+     * Any composing text set previously will be removed
+     * automatically.
+     * 
+     * @param text The committed text.
+     * @param newCursorPosition The new cursor position around the text.  If
+     *        > 0, this is relative to the end of the text - 1; if <= 0, this
+     *        is relative to the start of the text.  So a value of 1 will
+     *        always advance you to the position after the full text being
+     *        inserted.  Note that this means you can't position the cursor
+     *        within the text, because the editor can make modifications to
+     *        the text you are providing so it is not possible to correctly
+     *        specify locations there.
+     * 
+     *        
+     * @return Returns true on success, false if the input connection is no longer
+     * valid.
+     */
+    public boolean commitText(CharSequence text, int newCursorPosition);
+
+    /**
+     * Commit a completion the user has selected from the possible ones
+     * previously reported to {@link InputMethodSession#displayCompletions
+     * InputMethodSession.displayCompletions()}.  This will result in the
+     * same behavior as if the user had selected the completion from the
+     * actual UI.
+     * 
+     * @param text The committed completion.
+     *        
+     * @return Returns true on success, false if the input connection is no longer
+     * valid.
+     */
+    public boolean commitCompletion(CompletionInfo text);
+
+    /**
+     * Set the selection of the text editor.  To set the cursor position,
+     * start and end should have the same value.
+     * @return Returns true on success, false if the input connection is no longer
+     * valid.
+     */
+    public boolean setSelection(int start, int end);
+    
+    /**
+     * Have the editor perform an action it has said it can do.
+     * 
+     * @param editorAction This must be one of the action constants for
+     * {@link EditorInfo#imeOptions EditorInfo.editorType}, such as
+     * {@link EditorInfo#IME_ACTION_GO EditorInfo.EDITOR_ACTION_GO}.
+     * 
+     * @return Returns true on success, false if the input connection is no longer
+     * valid.
+     */
+    public boolean performEditorAction(int editorAction);
+    
+    /**
+     * Perform a context menu action on the field.  The given id may be one of:
+     * {@link android.R.id#selectAll},
+     * {@link android.R.id#startSelectingText}, {@link android.R.id#stopSelectingText},
+     * {@link android.R.id#cut}, {@link android.R.id#copy},
+     * {@link android.R.id#paste}, {@link android.R.id#copyUrl},
+     * or {@link android.R.id#switchInputMethod}
+     */
+    public boolean performContextMenuAction(int id);
+    
+    /**
+     * Tell the editor that you are starting a batch of editor operations.
+     * The editor will try to avoid sending you updates about its state
+     * until {@link #endBatchEdit} is called.
+     */
+    public boolean beginBatchEdit();
+    
+    /**
+     * Tell the editor that you are done with a batch edit previously
+     * initiated with {@link #endBatchEdit}.
+     */
+    public boolean endBatchEdit();
+    
+    /**
+     * Send a key event to the process that is currently attached through
+     * this input connection.  The event will be dispatched like a normal
+     * key event, to the currently focused; this generally is the view that
+     * is providing this InputConnection, but due to the asynchronous nature
+     * of this protocol that can not be guaranteed and the focus may have
+     * changed by the time the event is received.
+     * 
+     * <p>
+     * This method can be used to send key events to the application. For
+     * example, an on-screen keyboard may use this method to simulate a hardware
+     * keyboard. There are three types of standard keyboards, numeric (12-key),
+     * predictive (20-key) and ALPHA (QWERTY). You can specify the keyboard type
+     * by specify the device id of the key event.
+     * 
+     * <p>
+     * You will usually want to set the flag
+     * {@link KeyEvent#FLAG_SOFT_KEYBOARD KeyEvent.FLAG_SOFT_KEYBOARD} on all
+     * key event objects you give to this API; the flag will not be set
+     * for you.
+     * 
+     * @param event The key event.
+     *        
+     * @return Returns true on success, false if the input connection is no longer
+     * valid.
+     * 
+     * @see KeyEvent
+     * @see KeyCharacterMap#NUMERIC
+     * @see KeyCharacterMap#PREDICTIVE
+     * @see KeyCharacterMap#ALPHA
+     */
+    public boolean sendKeyEvent(KeyEvent event);
+
+    /**
+     * Clear the given meta key pressed states in the given input connection.
+     * 
+     * @param states The states to be cleared, may be one or more bits as
+     * per {@link KeyEvent#getMetaState() KeyEvent.getMetaState()}.
+     * 
+     * @return Returns true on success, false if the input connection is no longer
+     * valid.
+     */
+    public boolean clearMetaKeyStates(int states);
+    
+    /**
+     * Called by the IME to tell the client when it switches between fullscreen
+     * and normal modes.  This will normally be called for you by the standard
+     * implementation of {@link android.inputmethodservice.InputMethodService}.
+     */
+    public boolean reportFullscreenMode(boolean enabled);
+    
+    /**
+     * API to send private commands from an input method to its connected
+     * editor.  This can be used to provide domain-specific features that are
+     * only known between certain input methods and their clients.  Note that
+     * because the InputConnection protocol is asynchronous, you have no way
+     * to get a result back or know if the client understood the command; you
+     * can use the information in {@link EditorInfo} to determine if
+     * a client supports a particular command.
+     * 
+     * @param action Name of the command to be performed.  This <em>must</em>
+     * be a scoped name, i.e. prefixed with a package name you own, so that
+     * different developers will not create conflicting commands.
+     * @param data Any data to include with the command.
+     * @return Returns true if the command was sent (whether or not the
+     * associated editor understood it), false if the input connection is no longer
+     * valid.
+     */
+    public boolean performPrivateCommand(String action, Bundle data);
+}
diff --git a/core/java/android/view/inputmethod/InputMethod.java b/core/java/android/view/inputmethod/InputMethod.java
new file mode 100644
index 0000000..740dca8
--- /dev/null
+++ b/core/java/android/view/inputmethod/InputMethod.java
@@ -0,0 +1,200 @@
+/*
+ * Copyright (C) 2007-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 android.view.inputmethod;
+
+import android.inputmethodservice.InputMethodService;
+import android.os.IBinder;
+
+/**
+ * The InputMethod interface represents an input method which can generate key
+ * events and text, such as digital, email addresses, CJK characters, other
+ * language characters, and etc., while handling various input events, and send
+ * the text back to the application that requests text input.  See
+ * {@link InputMethodManager} for more general information about the
+ * architecture.
+ *
+ * <p>Applications will not normally use this interface themselves, instead
+ * relying on the standard interaction provided by
+ * {@link android.widget.TextView} and {@link android.widget.EditText}.
+ * 
+ * <p>Those implementing input methods should normally do so by deriving from
+ * {@link InputMethodService} or one of its subclasses.  When implementing
+ * an input method, the service component containing it must also supply
+ * a {@link #SERVICE_META_DATA} meta-data field, referencing an XML resource
+ * providing details about the input method.  All input methods also must
+ * require that clients hold the
+ * {@link android.Manifest.permission#BIND_INPUT_METHOD} in order to interact
+ * with the service; if this is not required, the system will not use that
+ * input method, because it can not trust that it is not compromised.
+ * 
+ * <p>The InputMethod interface is actually split into two parts: the interface
+ * here is the top-level interface to the input method, providing all
+ * access to it, which only the system can access (due to the BIND_INPUT_METHOD
+ * permission requirement).  In addition its method
+ * {@link #createSession(android.view.inputmethod.InputMethod.SessionCallback)}
+ * can be called to instantate a secondary {@link InputMethodSession} interface
+ * which is what clients use to communicate with the input method.
+ */
+public interface InputMethod {
+    /**
+     * This is the interface name that a service implementing an input
+     * method should say that it supports -- that is, this is the action it
+     * uses for its intent filter.  (Note: this name is used because this
+     * interface should be moved to the view package.)
+     */
+    public static final String SERVICE_INTERFACE = "android.view.InputMethod";
+    
+    /**
+     * Name under which an InputMethod service component publishes information
+     * about itself.  This meta-data must reference an XML resource containing
+     * an
+     * <code>&lt;{@link android.R.styleable#InputMethod input-method}&gt;</code>
+     * tag.
+     */
+    public static final String SERVICE_META_DATA = "android.view.im";
+    
+    public interface SessionCallback {
+        public void sessionCreated(InputMethodSession session);
+    }
+    
+    /**
+     * Called first thing after an input method is created, this supplies a
+     * unique token for the session it has with the system service.  It is
+     * needed to identify itself with the service to validate its operations.
+     * This token <strong>must not</strong> be passed to applications, since
+     * it grants special priviledges that should not be given to applications.
+     * 
+     * <p>Note: to protect yourself from malicious clients, you should only
+     * accept the first token given to you.  Any after that may come from the
+     * client.
+     */
+    public void attachToken(IBinder token);
+    
+    /**
+     * Bind a new application environment in to the input method, so that it
+     * can later start and stop input processing.
+     * Typically this method is called when this input method is enabled in an
+     * application for the first time.
+     * 
+     * @param binding Information about the application window that is binding
+     * to the input method.
+     * 
+     * @see InputBinding
+     * @see #unbindInput()
+     */
+    public void bindInput(InputBinding binding);
+
+    /**
+     * Unbind an application environment, called when the information previously
+     * set by {@link #bindInput} is no longer valid for this input method.
+     * 
+     * <p>
+     * Typically this method is called when the application changes to be
+     * non-foreground.
+     */
+    public void unbindInput();
+
+    /**
+     * This method is called when the application starts to receive text and it
+     * is ready for this input method to process received events and send result
+     * text back to the application.
+     * 
+     * @param inputConnection Optional specific input connection for
+     * communicating with the text box; if null, you should use the generic
+     * bound input connection.
+     * @param info Information about the text box (typically, an EditText)
+     *        that requests input.
+     * 
+     * @see EditorInfo
+     */
+    public void startInput(InputConnection inputConnection, EditorInfo info);
+
+    /**
+     * This method is called when the state of this input method needs to be
+     * reset.
+     * 
+     * <p>
+     * Typically, this method is called when the input focus is moved from one
+     * text box to another.
+     * 
+     * @param inputConnection Optional specific input connection for
+     * communicating with the text box; if null, you should use the generic
+     * bound input connection.
+     * @param attribute The attribute of the text box (typically, a EditText)
+     *        that requests input.
+     * 
+     * @see EditorInfo
+     */
+    public void restartInput(InputConnection inputConnection, EditorInfo attribute);
+
+    /**
+     * Create a new {@link InputMethodSession} that can be handed to client
+     * applications for interacting with the input method.  You can later
+     * use {@link #revokeSession(InputMethodSession)} to destroy the session
+     * so that it can no longer be used by any clients.
+     * 
+     * @param callback Interface that is called with the newly created session.
+     */
+    public void createSession(SessionCallback callback);
+    
+    /**
+     * Control whether a particular input method session is active.
+     * 
+     * @param session The {@link InputMethodSession} previously provided through
+     * SessionCallback.sessionCreated() that is to be changed.
+     */
+    public void setSessionEnabled(InputMethodSession session, boolean enabled);
+    
+    /**
+     * Disable and destroy a session that was previously created with
+     * {@link #createSession(android.view.inputmethod.InputMethod.SessionCallback)}.
+     * After this call, the given session interface is no longer active and
+     * calls on it will fail.
+     * 
+     * @param session The {@link InputMethodSession} previously provided through
+     * SessionCallback.sessionCreated() that is to be revoked.
+     */
+    public void revokeSession(InputMethodSession session);
+    
+    /**
+     * Flag for {@link #showSoftInput(int)}: this show has been explicitly
+     * requested by the user.  If not set, the system has decided it may be
+     * a good idea to show the input method based on a navigation operation
+     * in the UI.
+     */
+    public static final int SHOW_EXPLICIT = 0x00001;
+    
+    /**
+     * Flag for {@link #showSoftInput(int)}: this show has been forced to
+     * happen by the user.  If set, the input method should remain visible
+     * until deliberated dismissed by the user in its UI.
+     */
+    public static final int SHOW_FORCED = 0x00002;
+    
+    /**
+     * Request that any soft input part of the input method be shown to the user.
+     * 
+     * @param flags Provide additional information about the show request.
+     * Currently may be 0 or have the bit {@link #SHOW_EXPLICIT} set.
+     */
+    public void showSoftInput(int flags);
+    
+    /**
+     * Request that any soft input part of the input method be hidden from the user.
+     */
+    public void hideSoftInput();
+}
diff --git a/core/java/android/view/inputmethod/InputMethodInfo.aidl b/core/java/android/view/inputmethod/InputMethodInfo.aidl
new file mode 100644
index 0000000..5f4d6b6
--- /dev/null
+++ b/core/java/android/view/inputmethod/InputMethodInfo.aidl
@@ -0,0 +1,19 @@
+/*
+ * 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 android.view.inputmethod;
+
+parcelable InputMethodInfo;
diff --git a/core/java/android/view/inputmethod/InputMethodInfo.java b/core/java/android/view/inputmethod/InputMethodInfo.java
new file mode 100644
index 0000000..4e98591
--- /dev/null
+++ b/core/java/android/view/inputmethod/InputMethodInfo.java
@@ -0,0 +1,291 @@
+/*
+ * Copyright (C) 2007-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 android.view.inputmethod;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
+import android.content.res.TypedArray;
+import android.content.res.XmlResourceParser;
+import android.graphics.drawable.Drawable;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.AttributeSet;
+import android.util.Printer;
+import android.util.Xml;
+
+import java.io.IOException;
+
+/**
+ * This class is used to specify meta information of an input method.
+ */
+public final class InputMethodInfo implements Parcelable {
+    static final String TAG = "InputMethodMetaInfo";
+    
+    /**
+     * The Service that implements this input method component.
+     */
+    final ResolveInfo mService;
+    
+    /**
+     * The unique string Id to identify the input method.  This is generated
+     * from the input method component.
+     */
+    final String mId;
+
+    /**
+     * The input method setting activity's name, used by the system settings to
+     * launch the setting activity of this input method.
+     */
+    final String mSettingsActivityName;
+
+    /**
+     * The resource in the input method's .apk that holds a boolean indicating
+     * whether it should be considered the default input method for this
+     * system.  This is a resource ID instead of the final value so that it
+     * can change based on the configuration (in particular locale).
+     */
+    final int mIsDefaultResId;
+    
+    /**
+     * Constructor.
+     * 
+     * @param context The Context in which we are parsing the input method.
+     * @param service The ResolveInfo returned from the package manager about
+     * this input method's component.
+     */
+    public InputMethodInfo(Context context, ResolveInfo service)
+            throws XmlPullParserException, IOException {
+        mService = service;
+        ServiceInfo si = service.serviceInfo;
+        mId = new ComponentName(si.packageName, si.name).flattenToShortString();
+        
+        PackageManager pm = context.getPackageManager();
+        String settingsActivityComponent = null;
+        int isDefaultResId = 0;
+        
+        XmlResourceParser parser = null;
+        try {
+            parser = si.loadXmlMetaData(pm, InputMethod.SERVICE_META_DATA);
+            if (parser == null) {
+                throw new XmlPullParserException("No "
+                        + InputMethod.SERVICE_META_DATA + " meta-data");
+            }
+        
+            AttributeSet attrs = Xml.asAttributeSet(parser);
+            
+            int type;
+            while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
+                    && type != XmlPullParser.START_TAG) {
+            }
+            
+            String nodeName = parser.getName();
+            if (!"input-method".equals(nodeName)) {
+                throw new XmlPullParserException(
+                        "Meta-data does not start with input-method tag");
+            }
+            
+            TypedArray sa = context.getResources().obtainAttributes(attrs,
+                    com.android.internal.R.styleable.InputMethod);
+            settingsActivityComponent = sa.getString(
+                    com.android.internal.R.styleable.InputMethod_settingsActivity);
+            isDefaultResId = sa.getResourceId(
+                    com.android.internal.R.styleable.InputMethod_isDefault, 0);
+            sa.recycle();
+        } finally {
+            if (parser != null) parser.close();
+        }
+        
+        mSettingsActivityName = settingsActivityComponent;
+        mIsDefaultResId = isDefaultResId;
+    }
+
+    InputMethodInfo(Parcel source) {
+        mId = source.readString();
+        mSettingsActivityName = source.readString();
+        mIsDefaultResId = source.readInt();
+        mService = ResolveInfo.CREATOR.createFromParcel(source);
+    }
+    
+    /**
+     * Temporary API for creating a built-in input method.
+     */
+    public InputMethodInfo(String packageName, String className,
+            CharSequence label, String settingsActivity) {
+        ResolveInfo ri = new ResolveInfo();
+        ServiceInfo si = new ServiceInfo();
+        ApplicationInfo ai = new ApplicationInfo();
+        ai.packageName = packageName;
+        ai.enabled = true;
+        si.applicationInfo = ai;
+        si.enabled = true;
+        si.packageName = packageName;
+        si.name = className;
+        si.exported = true;
+        si.nonLocalizedLabel = label;
+        ri.serviceInfo = si;
+        mService = ri;
+        mId = new ComponentName(si.packageName, si.name).flattenToShortString();
+        mSettingsActivityName = settingsActivity;
+        mIsDefaultResId = 0;
+    }
+    
+    /**
+     * Return a unique ID for this input method.  The ID is generated from
+     * the package and class name implementing the method.
+     */
+    public String getId() {
+        return mId;
+    }
+    
+    /**
+     * Return the .apk package that implements this input method.
+     */
+    public String getPackageName() {
+        return mService.serviceInfo.packageName;
+    }
+    
+    /**
+     * Return the class name of the service component that implements
+     * this input method.
+     */
+    public String getServiceName() {
+        return mService.serviceInfo.name;
+    }
+
+    /**
+     * Return the component of the service that implements this input
+     * method.
+     */
+    public ComponentName getComponent() {
+        return new ComponentName(mService.serviceInfo.packageName,
+                mService.serviceInfo.name);
+    }
+    
+    /**
+     * Load the user-displayed label for this input method.
+     * 
+     * @param pm Supply a PackageManager used to load the input method's
+     * resources.
+     */
+    public CharSequence loadLabel(PackageManager pm) {
+        return mService.loadLabel(pm);
+    }
+    
+    /**
+     * Load the user-displayed icon for this input method.
+     * 
+     * @param pm Supply a PackageManager used to load the input method's
+     * resources.
+     */
+    public Drawable loadIcon(PackageManager pm) {
+        return mService.loadIcon(pm);
+    }
+    
+    /**
+     * Return the class name of an activity that provides a settings UI for
+     * the input method.  You can launch this activity be starting it with
+     * an {@link android.content.Intent} whose action is MAIN and with an
+     * explicit {@link android.content.ComponentName}
+     * composed of {@link #getPackageName} and the class name returned here.
+     * 
+     * <p>A null will be returned if there is no settings activity associated
+     * with the input method.
+     */
+    public String getSettingsActivity() {
+        return mSettingsActivityName;
+    }
+    
+    /**
+     * Return the resource identifier of a resource inside of this input
+     * method's .apk that determines whether it should be considered a
+     * default input method for the system.
+     */
+    public int getIsDefaultResourceId() {
+        return mIsDefaultResId;
+    }
+    
+    public void dump(Printer pw, String prefix) {
+        pw.println(prefix + "mId=" + mId
+                + " mSettingsActivityName=" + mSettingsActivityName);
+        pw.println(prefix + "mIsDefaultResId=0x"
+                + Integer.toHexString(mIsDefaultResId));
+        pw.println(prefix + "Service:");
+        mService.dump(pw, prefix + "  ");
+    }
+    
+    @Override
+    public String toString() {
+        return "InputMethodMetaInfo{" + mId
+                + ", settings: "
+                + mSettingsActivityName + "}";
+    }
+
+    /**
+     * Used to test whether the given parameter object is an
+     * {@link InputMethodInfo} and its Id is the same to this one.
+     * 
+     * @return true if the given parameter object is an
+     *         {@link InputMethodInfo} and its Id is the same to this one.
+     */
+    @Override
+    public boolean equals(Object o) {
+        if (o == this) return true;
+        if (o == null) return false;
+
+        if (!(o instanceof InputMethodInfo)) return false;
+
+        InputMethodInfo obj = (InputMethodInfo) o;
+        return mId.equals(obj.mId);
+    }
+    
+    /**
+     * Used to package this object into a {@link Parcel}.
+     * 
+     * @param dest The {@link Parcel} to be written.
+     * @param flags The flags used for parceling.
+     */
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeString(mId);
+        dest.writeString(mSettingsActivityName);
+        dest.writeInt(mIsDefaultResId);
+        mService.writeToParcel(dest, flags);
+    }
+
+    /**
+     * Used to make this class parcelable.
+     */
+    public static final Parcelable.Creator<InputMethodInfo> CREATOR = new Parcelable.Creator<InputMethodInfo>() {
+        public InputMethodInfo createFromParcel(Parcel source) {
+            return new InputMethodInfo(source);
+        }
+
+        public InputMethodInfo[] newArray(int size) {
+            return new InputMethodInfo[size];
+        }
+    };
+
+    public int describeContents() {
+        return 0;
+    }
+}
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
new file mode 100644
index 0000000..0aa1d6c
--- /dev/null
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -0,0 +1,1239 @@
+/*
+ * Copyright (C) 2007-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 android.view.inputmethod;
+
+import android.content.Context;
+import android.graphics.Rect;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.util.Log;
+import android.util.PrintWriterPrinter;
+import android.util.Printer;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewRoot;
+
+import com.android.internal.os.HandlerCaller;
+import com.android.internal.view.IInputConnectionWrapper;
+import com.android.internal.view.IInputContext;
+import com.android.internal.view.IInputMethodCallback;
+import com.android.internal.view.IInputMethodClient;
+import com.android.internal.view.IInputMethodManager;
+import com.android.internal.view.IInputMethodSession;
+import com.android.internal.view.InputBindResult;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Central system API to the overall input method framework (IMF) architecture,
+ * which arbitrates interaction between applications and the current input method.
+ * You can retrieve an instance of this interface with
+ * {@link Context#getSystemService(String) Context.getSystemService()}.
+ * 
+ * <p>Topics covered here:
+ * <ol>
+ * <li><a href="#ArchitectureOverview">Architecture Overview</a>
+ * </ol>
+ * 
+ * <a name="ArchitectureOverview"></a>
+ * <h3>Architecture Overview</h3>
+ * 
+ * <p>There are three primary parties involved in the input method
+ * framework (IMF) architecture:</p>
+ * 
+ * <ul>
+ * <li> The <strong>input method manager</strong> as expressed by this class
+ * is the central point of the system that manages interaction between all
+ * other parts.  It is expressed as the client-side API here which exists
+ * in each application context and communicates with a global system service
+ * that manages the interaction across all processes.
+ * <li> An <strong>input method (IME)</strong> implements a particular
+ * interaction model allowing the user to generate text.  The system binds
+ * to the current input method that is use, causing it to be created and run,
+ * and tells it when to hide and show its UI.  Only one IME is running at a time.
+ * <li> Multiple <strong>client applications</strong> arbitrate with the input
+ * method manager for input focus and control over the state of the IME.  Only
+ * one such client is ever active (working with the IME) at a time.
+ * </ul>
+ * 
+ * 
+ * <a name="Applications"></a>
+ * <h3>Applications</h3>
+ * 
+ * <p>In most cases, applications that are using the standard
+ * {@link android.widget.TextView} or its subclasses will have little they need
+ * to do to work well with soft input methods.  The main things you need to
+ * be aware of are:</p>
+ * 
+ * <ul>
+ * <li> Properly set the {@link android.R.attr#inputType} if your editable
+ * text views, so that the input method will have enough context to help the
+ * user in entering text into them.
+ * <li> Deal well with losing screen space when the input method is
+ * displayed.  Ideally an application should handle its window being resized
+ * smaller, but it can rely on the system performing panning of the window
+ * if needed.  You should set the {@link android.R.attr#windowSoftInputMode}
+ * attribute on your activity or the corresponding values on windows you
+ * create to help the system determine whether to pan or resize (it will
+ * try to determine this automatically but may get it wrong).
+ * <li> You can also control the preferred soft input state (open, closed, etc)
+ * for your window using the same {@link android.R.attr#windowSoftInputMode}
+ * attribute.
+ * </ul>
+ * 
+ * <p>More finer-grained control is available through the APIs here to directly
+ * interact with the IMF and its IME -- either showing or hiding the input
+ * area, letting the user pick an input method, etc.</p>
+ * 
+ * <p>For the rare people amongst us writing their own text editors, you
+ * will need to implement {@link android.view.View#onCreateInputConnection}
+ * to return a new instance of your own {@link InputConnection} interface
+ * allowing the IME to interact with your editor.</p>
+ * 
+ * 
+ * <a name="InputMethods"></a>
+ * <h3>Input Methods</h3>
+ * 
+ * <p>An input method (IME) is implemented
+ * as a {@link android.app.Service}, typically deriving from
+ * {@link android.inputmethodservice.InputMethodService}.  It must provide
+ * the core {@link InputMethod} interface, though this is normally handled by
+ * {@link android.inputmethodservice.InputMethodService} and implementors will
+ * only need to deal with the higher-level API there.</p>
+ * 
+ * See the {@link android.inputmethodservice.InputMethodService} class for
+ * more information on implementing IMEs.
+ * 
+ * 
+ * <a name="Security"></a>
+ * <h3>Security</h3>
+ * 
+ * <p>There are a lot of security issues associated with input methods,
+ * since they essentially have freedom to completely drive the UI and monitor
+ * everything the user enters.  The Android input method framework also allows
+ * arbitrary third party IMEs, so care must be taken to restrict their
+ * selection and interactions.</p>
+ * 
+ * <p>Here are some key points about the security architecture behind the
+ * IMF:</p>
+ * 
+ * <ul>
+ * <li> <p>Only the system is allowed to directly access an IME's
+ * {@link InputMethod} interface, via the
+ * {@link android.Manifest.permission#BIND_INPUT_METHOD} permission.  This is
+ * enforced in the system by not binding to an input method service that does
+ * not require this permission, so the system can guarantee no other untrusted
+ * clients are accessing the current input method outside of its control.</p>
+ * 
+ * <li> <p>There may be many client processes of the IMF, but only one may
+ * be active at a time.  The inactive clients can not interact with key
+ * parts of the IMF through the mechanisms described below.</p>
+ * 
+ * <li> <p>Clients of an input method are only given access to its
+ * {@link InputMethodSession} interface.  One instance of this interface is
+ * created for each client, and only calls from the session associated with
+ * the active client will be processed by the current IME.  This is enforced
+ * by {@link android.inputmethodservice.AbstractInputMethodService} for normal
+ * IMEs, but must be explicitly handled by an IME that is customizing the
+ * raw {@link InputMethodSession} implementation.</p>
+ * 
+ * <li> <p>Only the active client's {@link InputConnection} will accept
+ * operations.  The IMF tells each client process whether it is active, and
+ * the framework enforces that in inactive processes calls on to the current
+ * InputConnection will be ignored.  This ensures that the current IME can
+ * only deliver events and text edits to the UI that the user sees as
+ * being in focus.</p>
+ * 
+ * <li> <p>An IME can never interact with an {@link InputConnection} while
+ * the screen is off.  This is enforced by making all clients inactive while
+ * the screen is off, and prevents bad IMEs from driving the UI when the user
+ * can not be aware of its behavior.</p>
+ * 
+ * <li> <p>A client application can ask that the system let the user pick a
+ * new IME, but can not programmatically switch to one itself.  This avoids
+ * malicious applications from switching the user to their own IME, which
+ * remains running when the user navigates away to another application.  An
+ * IME, on the other hand, <em>is</em> allowed to programmatically switch
+ * the system to another IME, since it already has full control of user
+ * input.</p>
+ * 
+ * <li> <p>The user must explicitly enable a new IME in settings before
+ * they can switch to it, to confirm with the system that they know about it
+ * and want to make it available for use.</p>
+ * </ul>
+ */
+public final class InputMethodManager {
+    static final boolean DEBUG = false;
+    static final String TAG = "InputMethodManager";
+
+    static final Object mInstanceSync = new Object();
+    static InputMethodManager mInstance;
+    
+    final IInputMethodManager mService;
+    final Looper mMainLooper;
+    
+    // For scheduling work on the main thread.  This also serves as our
+    // global lock.
+    final H mH;
+    
+    // Our generic input connection if the current target does not have its own.
+    final IInputContext mIInputContext;
+
+    /**
+     * True if this input method client is active, initially false.
+     */
+    boolean mActive = false;
+    
+    /**
+     * Set whenever this client becomes inactive, to know we need to reset
+     * state with the IME then next time we receive focus.
+     */
+    boolean mHasBeenInactive = true;
+    
+    /**
+     * As reported by IME through InputConnection.
+     */
+    boolean mFullscreenMode;
+    
+    // -----------------------------------------------------------
+    
+    /**
+     * This is the root view of the overall window that currently has input
+     * method focus.
+     */
+    View mCurRootView;
+    /**
+     * This is the view that should currently be served by an input method,
+     * regardless of the state of setting that up.
+     */
+    View mServedView;
+    /**
+     * This is then next view that will be served by the input method, when
+     * we get around to updating things.
+     */
+    View mNextServedView;
+    /**
+     * True if we should restart input in the next served view, even if the
+     * view hasn't actually changed from the current serve view.
+     */
+    boolean mNextServedNeedsStart;
+    /**
+     * This is set when we are in the process of connecting, to determine
+     * when we have actually finished.
+     */
+    boolean mServedConnecting;
+    /**
+     * This is non-null when we have connected the served view; it holds
+     * the attributes that were last retrieved from the served view and given
+     * to the input connection.
+     */
+    EditorInfo mCurrentTextBoxAttribute;
+    /**
+     * The InputConnection that was last retrieved from the served view.
+     */
+    InputConnection mServedInputConnection;
+    /**
+     * The completions that were last provided by the served view.
+     */
+    CompletionInfo[] mCompletions;
+    
+    // Cursor position on the screen.
+    Rect mTmpCursorRect = new Rect();
+    Rect mCursorRect = new Rect();
+    int mCursorSelStart;
+    int mCursorSelEnd;
+    int mCursorCandStart;
+    int mCursorCandEnd;
+
+    // -----------------------------------------------------------
+    
+    /**
+     * Sequence number of this binding, as returned by the server.
+     */
+    int mBindSequence = -1;
+    /**
+     * ID of the method we are bound to.
+     */
+    String mCurId;
+    /**
+     * The actual instance of the method to make calls on it.
+     */
+    IInputMethodSession mCurMethod;
+
+    // -----------------------------------------------------------
+    
+    static final int MSG_DUMP = 1;
+    
+    class H extends Handler {
+        H(Looper looper) {
+            super(looper);
+        }
+        
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case MSG_DUMP: {
+                    HandlerCaller.SomeArgs args = (HandlerCaller.SomeArgs)msg.obj;
+                    try {
+                        doDump((FileDescriptor)args.arg1,
+                                (PrintWriter)args.arg2, (String[])args.arg3);
+                    } catch (RuntimeException e) {
+                        ((PrintWriter)args.arg2).println("Exception: " + e);
+                    }
+                    synchronized (args.arg4) {
+                        ((CountDownLatch)args.arg4).countDown();
+                    }
+                    return;
+                }
+            }
+        }
+    }
+    
+    class ControlledInputConnectionWrapper extends IInputConnectionWrapper {
+        public ControlledInputConnectionWrapper(Looper mainLooper, InputConnection conn) {
+            super(mainLooper, conn);
+        }
+
+        public boolean isActive() {
+            return mActive;
+        }
+    }
+    
+    final IInputMethodClient.Stub mClient = new IInputMethodClient.Stub() {
+        @Override protected void dump(FileDescriptor fd, PrintWriter fout, String[] args) {
+            // No need to check for dump permission, since we only give this
+            // interface to the system.
+            
+            CountDownLatch latch = new CountDownLatch(1);
+            HandlerCaller.SomeArgs sargs = new HandlerCaller.SomeArgs();
+            sargs.arg1 = fd;
+            sargs.arg2 = fout;
+            sargs.arg3 = args;
+            sargs.arg4 = latch;
+            mH.sendMessage(mH.obtainMessage(MSG_DUMP, sargs));
+            try {
+                if (!latch.await(5, TimeUnit.SECONDS)) {
+                    fout.println("Timeout waiting for dump");
+                }
+            } catch (InterruptedException e) {
+                fout.println("Interrupted waiting for dump");
+            }
+        }
+        
+        public void setUsingInputMethod(boolean state) {
+        }
+        
+        public void onBindMethod(InputBindResult res) {
+            synchronized (mH) {
+                if (mBindSequence < 0 || mBindSequence != res.sequence) {
+                    Log.w(TAG, "Ignoring onBind: cur seq=" + mBindSequence
+                            + ", given seq=" + res.sequence);
+                    return;
+                }
+                
+                mCurMethod = res.method;
+                mCurId = res.id;
+                mBindSequence = res.sequence;
+            }
+            startInputInner();
+        }
+        
+        public void onUnbindMethod(int sequence) {
+            synchronized (mH) {
+                if (mBindSequence == sequence) {
+                    if (false) {
+                        // XXX the server has already unbound!
+                        if (mCurMethod != null && mCurrentTextBoxAttribute != null) {
+                            try {
+                                mCurMethod.finishInput();
+                            } catch (RemoteException e) {
+                                Log.w(TAG, "IME died: " + mCurId, e);
+                            }
+                        }
+                    }
+                    clearBindingLocked();
+                    
+                    // If we were actively using the last input method, then
+                    // we would like to re-connect to the next input method.
+                    if (mServedView != null && mServedView.isFocused()) {
+                        mServedConnecting = true;
+                    }
+                }
+                startInputInner();
+            }
+        }
+        
+        public void setActive(boolean active) {
+            synchronized (mH) {
+                mActive = active;
+                mFullscreenMode = false;
+                if (!active) {
+                    // Some other client has starting using the IME, so note
+                    // that this happened and make sure our own editor's
+                    // state is reset.
+                    mHasBeenInactive = true;
+                    try {
+                        // Note that finishComposingText() is allowed to run
+                        // even when we are not active.
+                        mIInputContext.finishComposingText();
+                    } catch (RemoteException e) {
+                    }
+                }
+            }
+        }
+    };    
+    
+    final InputConnection mDummyInputConnection = new BaseInputConnection(this, true);
+    
+    InputMethodManager(IInputMethodManager service, Looper looper) {
+        mService = service;
+        mMainLooper = looper;
+        mH = new H(looper);
+        mIInputContext = new ControlledInputConnectionWrapper(looper,
+                mDummyInputConnection);
+        
+        if (mInstance == null) {
+            mInstance = this;
+        }
+    }
+
+    /**
+     * Retrieve the global InputMethodManager instance, creating it if it
+     * doesn't already exist.
+     * @hide
+     */
+    static public InputMethodManager getInstance(Context context) {
+        synchronized (mInstanceSync) {
+            if (mInstance != null) {
+                return mInstance;
+            }
+            IBinder b = ServiceManager.getService(Context.INPUT_METHOD_SERVICE);
+            IInputMethodManager service = IInputMethodManager.Stub.asInterface(b);
+            mInstance = new InputMethodManager(service, context.getMainLooper());
+        }
+        return mInstance;
+    }
+    
+    /**
+     * Private optimization: retrieve the global InputMethodManager instance,
+     * if it exists.
+     * @hide
+     */
+    static public InputMethodManager peekInstance() {
+        return mInstance;
+    }
+    
+    /** @hide */
+    public IInputMethodClient getClient() {
+        return mClient;
+    }
+    
+    /** @hide */
+    public IInputContext getInputContext() {
+        return mIInputContext;
+    }
+    
+    public List<InputMethodInfo> getInputMethodList() {
+        try {
+            return mService.getInputMethodList();
+        } catch (RemoteException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    public List<InputMethodInfo> getEnabledInputMethodList() {
+        try {
+            return mService.getEnabledInputMethodList();
+        } catch (RemoteException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    public void showStatusIcon(IBinder imeToken, String packageName, int iconId) {
+        try {
+            mService.updateStatusIcon(imeToken, packageName, iconId);
+        } catch (RemoteException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    public void hideStatusIcon(IBinder imeToken) {
+        try {
+            mService.updateStatusIcon(imeToken, null, 0);
+        } catch (RemoteException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /** @hide */
+    public void setFullscreenMode(boolean fullScreen) {
+        mFullscreenMode = fullScreen;
+    }
+    
+    /**
+     * Allows you to discover whether the attached input method is running
+     * in fullscreen mode.  Return true if it is fullscreen, entirely covering
+     * your UI, else returns false.
+     */
+    public boolean isFullscreenMode() {
+        return mFullscreenMode;
+    }
+    
+    /**
+     * Return true if the given view is the currently active view for the
+     * input method.
+     */
+    public boolean isActive(View view) {
+        checkFocus();
+        synchronized (mH) {
+            return mServedView == view && mCurrentTextBoxAttribute != null;
+        }
+    }
+    
+    /**
+     * Return true if any view is currently active in the input method.
+     */
+    public boolean isActive() {
+        checkFocus();
+        synchronized (mH) {
+            return mServedView != null && mCurrentTextBoxAttribute != null;
+        }
+    }
+    
+    /**
+     * Return true if the currently served view is accepting full text edits.
+     * If false, it has no input connection, so can only handle raw key events.
+     */
+    public boolean isAcceptingText() {
+        checkFocus();
+        return mServedInputConnection != null;
+    }
+
+    /**
+     * Reset all of the state associated with being bound to an input method.
+     */
+    void clearBindingLocked() {
+        clearConnectionLocked();
+        mBindSequence = -1;
+        mCurId = null;
+        mCurMethod = null;
+    }
+    
+    /**
+     * Reset all of the state associated with a served view being connected
+     * to an input method
+     */
+    void clearConnectionLocked() {
+        mCurrentTextBoxAttribute = null;
+        mServedInputConnection = null;
+    }
+    
+    /**
+     * Disconnect any existing input connection, clearing the served view.
+     */
+    void finishInputLocked() {
+        mNextServedView = null;
+        if (mServedView != null) {
+            if (DEBUG) Log.v(TAG, "FINISH INPUT: " + mServedView);
+            
+            if (mCurrentTextBoxAttribute != null) {
+                try {
+                    mService.finishInput(mClient);
+                } catch (RemoteException e) {
+                }
+            }
+            
+            if (mServedInputConnection != null) {
+                // We need to tell the previously served view that it is no
+                // longer the input target, so it can reset its state.  Schedule
+                // this call on its window's Handler so it will be on the correct
+                // thread and outside of our lock.
+                Handler vh = mServedView.getHandler();
+                if (vh != null) {
+                    // This will result in a call to reportFinishInputConnection()
+                    // below.
+                    vh.sendMessage(vh.obtainMessage(ViewRoot.FINISH_INPUT_CONNECTION,
+                            mServedInputConnection));
+                }
+            }
+            
+            mServedView = null;
+            mCompletions = null;
+            mServedConnecting = false;
+            clearConnectionLocked();
+        }
+    }
+    
+    /**
+     * Called from the FINISH_INPUT_CONNECTION message above.
+     * @hide
+     */
+    public void reportFinishInputConnection(InputConnection ic) {
+        if (mServedInputConnection != ic) {
+            ic.finishComposingText();
+        }
+    }
+    
+    public void displayCompletions(View view, CompletionInfo[] completions) {
+        checkFocus();
+        synchronized (mH) {
+            if (mServedView != view) {
+                return;
+            }
+            
+            mCompletions = completions;
+            if (mCurMethod != null) {
+                try {
+                    mCurMethod.displayCompletions(mCompletions);
+                } catch (RemoteException e) {
+                }
+            }
+        }
+    }
+    
+    public void updateExtractedText(View view, int token, ExtractedText text) {
+        checkFocus();
+        synchronized (mH) {
+            if (mServedView != view) {
+                return;
+            }
+            
+            if (mCurMethod != null) {
+                try {
+                    mCurMethod.updateExtractedText(token, text);
+                } catch (RemoteException e) {
+                }
+            }
+        }
+    }
+    
+    /**
+     * Flag for {@link #showSoftInput} to indicate that this is an implicit
+     * request to show the input window, not as the result of a direct request
+     * by the user.  The window may not be shown in this case.
+     */
+    public static final int SHOW_IMPLICIT = 0x0001;
+    
+    /**
+     * Flag for {@link #showSoftInput} to indicate that the user has forced
+     * the input method open (such as by long-pressing menu) so it should
+     * not be closed until they explicitly do so.
+     */
+    public static final int SHOW_FORCED = 0x0002;
+    
+    /**
+     * Explicitly request that the current input method's soft input area be
+     * shown to the user, if needed.  Call this if the user interacts with
+     * your view in such a way that they have expressed they would like to
+     * start performing input into it.
+     * 
+     * @param view The currently focused view, which would like to receive
+     * soft keyboard input.
+     * @param flags Provides additional operating flags.  Currently may be
+     * 0 or have the {@link #SHOW_IMPLICIT} bit set.
+     */
+    public void showSoftInput(View view, int flags) {
+        checkFocus();
+        synchronized (mH) {
+            if (mServedView != view) {
+                return;
+            }
+
+            try {
+                mService.showSoftInput(mClient, flags);
+            } catch (RemoteException e) {
+            }
+        }
+    }
+    
+    /** @hide */
+    public void showSoftInputUnchecked(int flags) {
+        try {
+            mService.showSoftInput(mClient, flags);
+        } catch (RemoteException e) {
+        }
+    }
+    
+    /**
+     * Flag for {@link #hideSoftInputFromWindow} to indicate that the soft
+     * input window should only be hidden if it was not explicitly shown
+     * by the user.
+     */
+    public static final int HIDE_IMPLICIT_ONLY = 0x0001;
+    
+    /**
+     * Flag for {@link #hideSoftInputFromWindow} to indicate that the soft
+     * input window should normally be hidden, unless it was originally
+     * shown with {@link #SHOW_FORCED}.
+     */
+    public static final int HIDE_NOT_ALWAYS = 0x0002;
+    
+    /**
+     * Request to hide the soft input window from the context of the window
+     * that is currently accepting input.  This should be called as a result
+     * of the user doing some actually than fairly explicitly requests to
+     * have the input window hidden.
+     * 
+     * @param windowToken The token of the window that is making the request,
+     * as returned by {@link View#getWindowToken() View.getWindowToken()}.
+     * @param flags Provides additional operating flags.  Currently may be
+     * 0 or have the {@link #HIDE_IMPLICIT_ONLY} bit set.
+     */
+    public void hideSoftInputFromWindow(IBinder windowToken, int flags) {
+        checkFocus();
+        synchronized (mH) {
+            if (mServedView == null || mServedView.getWindowToken() != windowToken) {
+                return;
+            }
+
+            try {
+                mService.hideSoftInput(mClient, flags);
+            } catch (RemoteException e) {
+            }
+        }
+    }
+    
+    /**
+     * If the input method is currently connected to the given view,
+     * restart it with its new contents.  You should call this when the text
+     * within your view changes outside of the normal input method or key
+     * input flow, such as when an application calls TextView.setText().
+     * 
+     * @param view The view whose text has changed.
+     */
+    public void restartInput(View view) {
+        checkFocus();
+        synchronized (mH) {
+            if (mServedView != view) {
+                return;
+            }
+            
+            mServedConnecting = true;
+        }
+        
+        startInputInner();
+    }
+    
+    void startInputInner() {
+        final View view;
+        synchronized (mH) {
+            view = mServedView;
+            
+            // Make sure we have a window token for the served view.
+            if (DEBUG) Log.v(TAG, "Starting input: view=" + view);
+            if (view == null) {
+                if (DEBUG) Log.v(TAG, "ABORT input: no served view!");
+                return;
+            }
+        }
+        
+        // Now we need to get an input connection from the served view.
+        // This is complicated in a couple ways: we can't be holding our lock
+        // when calling out to the view, and we need to make sure we call into
+        // the view on the same thread that is driving its view hierarchy.
+        Handler vh = view.getHandler();
+        if (vh == null) {
+            // If the view doesn't have a handler, something has changed out
+            // from under us, so just bail.
+            if (DEBUG) Log.v(TAG, "ABORT input: no handler for view!");
+            return;
+        }
+        if (vh.getLooper() != Looper.myLooper()) {
+            // The view is running on a different thread than our own, so
+            // we need to reschedule our work for over there.
+            if (DEBUG) Log.v(TAG, "Starting input: reschedule to view thread");
+            vh.post(new Runnable() {
+                public void run() {
+                    startInputInner();
+                }
+            });
+        }
+        
+        // Okay we are now ready to call into the served view and have it
+        // do its stuff.
+        // Life is good: let's hook everything up!
+        EditorInfo tba = new EditorInfo();
+        tba.packageName = view.getContext().getPackageName();
+        tba.fieldId = view.getId();
+        InputConnection ic = view.onCreateInputConnection(tba);
+        if (DEBUG) Log.v(TAG, "Starting input: tba=" + tba + " ic=" + ic);
+        
+        synchronized (mH) {
+            // Now that we are locked again, validate that our state hasn't
+            // changed.
+            if (mServedView != view || !mServedConnecting) {
+                // Something else happened, so abort.
+                if (DEBUG) Log.v(TAG, 
+                        "Starting input: finished by someone else (view="
+                        + mServedView + " conn=" + mServedConnecting + ")");
+                return;
+            }
+            
+            // If we already have a text box, then this view is already
+            // connected so we want to restart it.
+            final boolean initial = mCurrentTextBoxAttribute == null;
+            
+            // Hook 'em up and let 'er rip.
+            mCurrentTextBoxAttribute = tba;
+            mServedConnecting = false;
+            mServedInputConnection = ic;
+            IInputContext servedContext;
+            if (ic != null) {
+                mCursorSelStart = tba.initialSelStart;
+                mCursorSelEnd = tba.initialSelEnd;
+                mCursorCandStart = -1;
+                mCursorCandEnd = -1;
+                mCursorRect.setEmpty();
+                servedContext = new ControlledInputConnectionWrapper(vh.getLooper(), ic);
+            } else {
+                servedContext = null;
+            }
+            
+            try {
+                if (DEBUG) Log.v(TAG, "START INPUT: " + view + " ic="
+                        + ic + " tba=" + tba + " initial=" + initial);
+                InputBindResult res = mService.startInput(mClient,
+                        servedContext, tba, initial, mCurMethod == null);
+                if (DEBUG) Log.v(TAG, "Starting input: Bind result=" + res);
+                if (res != null) {
+                    if (res.id != null) {
+                        mBindSequence = res.sequence;
+                        mCurMethod = res.method;
+                    } else {
+                        // This means there is no input method available.
+                        if (DEBUG) Log.v(TAG, "ABORT input: no input method!");
+                        return;
+                    }
+                }
+                if (mCurMethod != null && mCompletions != null) {
+                    try {
+                        mCurMethod.displayCompletions(mCompletions);
+                    } catch (RemoteException e) {
+                    }
+                }
+            } catch (RemoteException e) {
+                Log.w(TAG, "IME died: " + mCurId, e);
+            }
+        }
+    }
+
+    /**
+     * When the focused window is dismissed, this method is called to finish the
+     * input method started before.
+     * @hide
+     */
+    public void windowDismissed(IBinder appWindowToken) {
+        checkFocus();
+        synchronized (mH) {
+            if (mServedView != null &&
+                    mServedView.getWindowToken() == appWindowToken) {
+                finishInputLocked();
+            }
+        }
+    }
+
+    /**
+     * Call this when a view receives focus.
+     * @hide
+     */
+    public void focusIn(View view) {
+        synchronized (mH) {
+            focusInLocked(view);
+        }
+    }
+
+    void focusInLocked(View view) {
+        if (DEBUG) Log.v(TAG, "focusIn: " + view);
+        
+        if (mCurRootView != view.getRootView()) {
+            // This is a request from a window that isn't in the window with
+            // IME focus, so ignore it.
+            if (DEBUG) Log.v(TAG, "Not IME target window, ignoring");
+            return;
+        }
+        
+        mNextServedView = view;
+        scheduleCheckFocusLocked(view);
+    }
+
+    /**
+     * Call this when a view loses focus.
+     * @hide
+     */
+    public void focusOut(View view) {
+        synchronized (mH) {
+            if (DEBUG) Log.v(TAG, "focusOut: " + view
+                    + " mServedView=" + mServedView
+                    + " winFocus=" + view.hasWindowFocus());
+            if (mServedView == view) {
+                // The following code would auto-hide the IME if we end up
+                // with no more views with focus.  This can happen, however,
+                // whenever we go into touch mode, so it ends up hiding
+                // at times when we don't really want it to.  For now it
+                // seems better to just turn it all off.
+                if (false && view.hasWindowFocus()) {
+                    mNextServedView = null;
+                    scheduleCheckFocusLocked(view);
+                }
+            }
+        }
+    }
+
+    void scheduleCheckFocusLocked(View view) {
+        Handler vh = view.getHandler();
+        if (vh != null && !vh.hasMessages(ViewRoot.CHECK_FOCUS)) {
+            // This will result in a call to checkFocus() below.
+            vh.sendMessage(vh.obtainMessage(ViewRoot.CHECK_FOCUS));
+        }
+    }
+    
+    /**
+     * @hide
+     */
+    public void checkFocus() {
+        // This is called a lot, so short-circuit before locking.
+        if (mServedView == mNextServedView && !mNextServedNeedsStart) {
+            return;
+        }
+        
+        InputConnection ic = null;
+        synchronized (mH) {
+            if (mServedView == mNextServedView && !mNextServedNeedsStart) {
+                return;
+            }
+            if (DEBUG) Log.v(TAG, "checkFocus: view=" + mServedView
+                    + " next=" + mNextServedView
+                    + " restart=" + mNextServedNeedsStart);
+            
+            mNextServedNeedsStart = false;
+            if (mNextServedView == null) {
+                finishInputLocked();
+                // In this case, we used to have a focused view on the window,
+                // but no longer do.  We should make sure the input method is
+                // no longer shown, since it serves no purpose.
+                closeCurrentInput();
+                return;
+            }
+            
+            ic = mServedInputConnection;
+            
+            mServedView = mNextServedView;
+            mCurrentTextBoxAttribute = null;
+            mCompletions = null;
+            mServedConnecting = true;
+        }
+        
+        if (ic != null) {
+            ic.finishComposingText();
+        }
+        
+        startInputInner();
+    }
+    
+    void closeCurrentInput() {
+        try {
+            mService.hideSoftInput(mClient, HIDE_NOT_ALWAYS);
+        } catch (RemoteException e) {
+        }
+    }
+    
+    /**
+     * Called by ViewRoot when its window gets input focus.
+     * @hide
+     */
+    public void onWindowFocus(View rootView, View focusedView, int softInputMode,
+            boolean first, int windowFlags) {
+        synchronized (mH) {
+            if (DEBUG) Log.v(TAG, "onWindowFocus: " + focusedView
+                    + " softInputMode=" + softInputMode
+                    + " first=" + first + " flags=#"
+                    + Integer.toHexString(windowFlags));
+            if (mHasBeenInactive) {
+                if (DEBUG) Log.v(TAG, "Has been inactive!  Starting fresh");
+                mHasBeenInactive = false;
+                mNextServedNeedsStart = true;
+            }
+            focusInLocked(focusedView != null ? focusedView : rootView);
+        }
+        
+        checkFocus();
+        
+        synchronized (mH) {
+            try {
+                final boolean isTextEditor = focusedView != null &&
+                        focusedView.onCheckIsTextEditor();
+                mService.windowGainedFocus(mClient, focusedView != null,
+                        isTextEditor, softInputMode, first, windowFlags);
+            } catch (RemoteException e) {
+            }
+        }
+    }
+    
+    /** @hide */
+    public void startGettingWindowFocus(View rootView) {
+        synchronized (mH) {
+            mCurRootView = rootView;
+        }
+    }
+    
+    /**
+     * Report the current selection range.
+     */
+    public void updateSelection(View view, int selStart, int selEnd,
+            int candidatesStart, int candidatesEnd) {
+        checkFocus();
+        synchronized (mH) {
+            if (mServedView != view || mCurrentTextBoxAttribute == null
+                    || mCurMethod == null) {
+                return;
+            }
+            
+            if (mCursorSelStart != selStart || mCursorSelEnd != selEnd
+                    || mCursorCandStart != candidatesStart
+                    || mCursorCandEnd != candidatesEnd) {
+                if (DEBUG) Log.d(TAG, "updateSelection");
+
+                try {
+                    if (DEBUG) Log.v(TAG, "SELECTION CHANGE: " + mCurMethod);
+                    mCurMethod.updateSelection(mCursorSelStart, mCursorSelEnd,
+                            selStart, selEnd, candidatesStart, candidatesEnd);
+                    mCursorSelStart = selStart;
+                    mCursorSelEnd = selEnd;
+                    mCursorCandStart = candidatesStart;
+                    mCursorCandEnd = candidatesEnd;
+                } catch (RemoteException e) {
+                    Log.w(TAG, "IME died: " + mCurId, e);
+                }
+            }
+        }
+    }
+
+    /**
+     * Returns true if the current input method wants to watch the location
+     * of the input editor's cursor in its window.
+     */
+    public boolean isWatchingCursor(View view) {
+        return false;
+    }
+    
+    /**
+     * Report the current cursor location in its window.
+     */
+    public void updateCursor(View view, int left, int top, int right, int bottom) {
+        checkFocus();
+        synchronized (mH) {
+            if (mServedView != view || mCurrentTextBoxAttribute == null
+                    || mCurMethod == null) {
+                return;
+            }
+            
+            mTmpCursorRect.set(left, top, right, bottom);
+            if (!mCursorRect.equals(mTmpCursorRect)) {
+                if (DEBUG) Log.d(TAG, "updateCursor");
+
+                try {
+                    if (DEBUG) Log.v(TAG, "CURSOR CHANGE: " + mCurMethod);
+                    mCurMethod.updateCursor(mTmpCursorRect);
+                    mCursorRect.set(mTmpCursorRect);
+                } catch (RemoteException e) {
+                    Log.w(TAG, "IME died: " + mCurId, e);
+                }
+            }
+        }
+    }
+
+    /**
+     * Call {@link InputMethodSession#appPrivateCommand(String, Bundle)
+     * InputMethodSession.appPrivateCommand()} on the current Input Method.
+     * @param view Optional View that is sending the command, or null if
+     * you want to send the command regardless of the view that is attached
+     * to the input method.
+     * @param action Name of the command to be performed.  This <em>must</em>
+     * be a scoped name, i.e. prefixed with a package name you own, so that
+     * different developers will not create conflicting commands.
+     * @param data Any data to include with the command.
+     */
+    public void sendAppPrivateCommand(View view, String action, Bundle data) {
+        checkFocus();
+        synchronized (mH) {
+            if ((view != null && mServedView != view)
+                    || mCurrentTextBoxAttribute == null || mCurMethod == null) {
+                return;
+            }
+            try {
+                if (DEBUG) Log.v(TAG, "APP PRIVATE COMMAND " + action + ": " + data);
+                mCurMethod.appPrivateCommand(action, data);
+            } catch (RemoteException e) {
+                Log.w(TAG, "IME died: " + mCurId, e);
+            }
+        }
+    }
+    
+    /**
+     * Force switch to a new input method component.  This can only be called
+     * from the currently active input method, as validated by the given token.
+     * @param token Supplies the identifying token given to an input method
+     * when it was started, which allows it to perform this operation on
+     * itself.
+     * @param id The unique identifier for the new input method to be switched to.
+     */
+    public void setInputMethod(IBinder token, String id) {
+        try {
+            mService.setInputMethod(token, id);
+        } catch (RemoteException e) {
+            throw new RuntimeException(e);
+        }
+    }
+    
+    /**
+     * Close/hide the input method's soft input area, so the user no longer
+     * sees it or can interact with it.  This can only be called
+     * from the currently active input method, as validated by the given token.
+     * 
+     * @param token Supplies the identifying token given to an input method
+     * when it was started, which allows it to perform this operation on
+     * itself.
+     * @param flags Provides additional operating flags.  Currently may be
+     * 0 or have the {@link #HIDE_IMPLICIT_ONLY} bit set.
+     */
+    public void hideSoftInputFromInputMethod(IBinder token, int flags) {
+        try {
+            mService.hideMySoftInput(token, flags);
+        } catch (RemoteException e) {
+            throw new RuntimeException(e);
+        }
+    }
+    
+    /**
+     * @hide
+     */
+    public void dispatchKeyEvent(Context context, int seq, KeyEvent key,
+            IInputMethodCallback callback) {
+        synchronized (mH) {
+            if (DEBUG) Log.d(TAG, "dispatchKeyEvent");
+    
+            if (mCurMethod == null) {
+                try {
+                    callback.finishedEvent(seq, false);
+                } catch (RemoteException e) {
+                }
+                return;
+            }
+    
+            if (key.getAction() == KeyEvent.ACTION_DOWN
+                    && key.getKeyCode() == KeyEvent.KEYCODE_SYM) {
+                showInputMethodPicker();
+                try {
+                    callback.finishedEvent(seq, true);
+                } catch (RemoteException e) {
+                }
+                return;
+            }
+            try {
+                if (DEBUG) Log.v(TAG, "DISPATCH KEY: " + mCurMethod);
+                mCurMethod.dispatchKeyEvent(seq, key, callback);
+            } catch (RemoteException e) {
+                Log.w(TAG, "IME died: " + mCurId + " dropping: " + key, e);
+                try {
+                    callback.finishedEvent(seq, false);
+                } catch (RemoteException ex) {
+                }
+            }
+        }
+    }
+
+    /**
+     * @hide
+     */
+    void dispatchTrackballEvent(Context context, int seq, MotionEvent motion,
+            IInputMethodCallback callback) {
+        synchronized (mH) {
+            if (DEBUG) Log.d(TAG, "dispatchTrackballEvent");
+    
+            if (mCurMethod == null || mCurrentTextBoxAttribute == null) {
+                try {
+                    callback.finishedEvent(seq, false);
+                } catch (RemoteException e) {
+                }
+                return;
+            }
+    
+            try {
+                if (DEBUG) Log.v(TAG, "DISPATCH TRACKBALL: " + mCurMethod);
+                mCurMethod.dispatchTrackballEvent(seq, motion, callback);
+            } catch (RemoteException e) {
+                Log.w(TAG, "IME died: " + mCurId + " dropping trackball: " + motion, e);
+                try {
+                    callback.finishedEvent(seq, false);
+                } catch (RemoteException ex) {
+                }
+            }
+        }
+    }
+    
+    public void showInputMethodPicker() {
+        synchronized (mH) {
+            try {
+                mService.showInputMethodPickerFromClient(mClient);
+            } catch (RemoteException e) {
+                Log.w(TAG, "IME died: " + mCurId, e);
+            }
+        }
+    }
+    
+    void doDump(FileDescriptor fd, PrintWriter fout, String[] args) {
+        final Printer p = new PrintWriterPrinter(fout);
+        p.println("Input method client state for " + this + ":");
+        
+        p.println("  mService=" + mService);
+        p.println("  mMainLooper=" + mMainLooper);
+        p.println("  mIInputContext=" + mIInputContext);
+        p.println("  mActive=" + mActive
+                + " mHasBeenInactive=" + mHasBeenInactive
+                + " mBindSequence=" + mBindSequence
+                + " mCurId=" + mCurId);
+        p.println("  mCurMethod=" + mCurMethod);
+        p.println("  mCurRootView=" + mCurRootView);
+        p.println("  mServedView=" + mServedView);
+        p.println("  mNextServedNeedsStart=" + mNextServedNeedsStart
+                + " mNextServedView=" + mNextServedView);
+        p.println("  mServedConnecting=" + mServedConnecting);
+        if (mCurrentTextBoxAttribute != null) {
+            p.println("  mCurrentTextBoxAttribute:");
+            mCurrentTextBoxAttribute.dump(p, "    ");
+        } else {
+            p.println("  mCurrentTextBoxAttribute: null");
+        }
+        p.println("  mServedInputConnection=" + mServedInputConnection);
+        p.println("  mCompletions=" + mCompletions);
+        p.println("  mCursorRect=" + mCursorRect);
+        p.println("  mCursorSelStart=" + mCursorSelStart
+                + " mCursorSelEnd=" + mCursorSelEnd
+                + " mCursorCandStart=" + mCursorCandStart
+                + " mCursorCandEnd=" + mCursorCandEnd);
+    }
+}
diff --git a/core/java/android/view/inputmethod/InputMethodSession.java b/core/java/android/view/inputmethod/InputMethodSession.java
new file mode 100644
index 0000000..b5bbaff
--- /dev/null
+++ b/core/java/android/view/inputmethod/InputMethodSession.java
@@ -0,0 +1,142 @@
+/*
+ * Copyright (C) 2007-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 android.view.inputmethod;
+
+import android.graphics.Rect;
+import android.os.Bundle;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+
+/**
+ * The InputMethodSession interface provides the per-client functionality
+ * of {@link InputMethod} that is safe to expose to applications.
+ * 
+ * <p>Applications will not normally use this interface themselves, instead
+ * relying on the standard interaction provided by
+ * {@link android.widget.TextView} and {@link android.widget.EditText}.
+ */
+public interface InputMethodSession {
+    
+    public interface EventCallback {
+        void finishedEvent(int seq, boolean handled);
+    }
+    
+    /**
+     * This method is called when the application would like to stop
+     * receiving text input.
+     */
+    public void finishInput();
+
+    /**
+     * This method is called when the selection or cursor in the current
+     * target input field has changed.
+     * 
+     * @param oldSelStart The previous text offset of the cursor selection
+     * start position.
+     * @param oldSelEnd The previous text offset of the cursor selection
+     * end position.
+     * @param newSelStart The new text offset of the cursor selection
+     * start position.
+     * @param newSelEnd The new text offset of the cursor selection
+     * end position.
+     * @param candidatesStart The text offset of the current candidate
+     * text start position.
+     * @param candidatesEnd The text offset of the current candidate
+     * text end position.
+     */
+    public void updateSelection(int oldSelStart, int oldSelEnd,
+            int newSelStart, int newSelEnd,
+            int candidatesStart, int candidatesEnd);
+
+    /**
+     * This method is called when cursor location of the target input field
+     * has changed within its window.  This is not normally called, but will
+     * only be reported if requested by the input method.
+     * 
+     * @param newCursor The rectangle of the cursor currently being shown in
+     * the input field's window coordinates.
+     */
+    public void updateCursor(Rect newCursor);
+    
+    /**
+     * Called by a text editor that performs auto completion, to tell the
+     * input method about the completions it has available.  This can be used
+     * by the input method to display them to the user to select the text to
+     * be inserted.
+     * 
+     * @param completions Array of text completions that are available, starting with
+     * the best.  If this array is null, any existing completions will be
+     * removed.
+     */
+    public void displayCompletions(CompletionInfo[] completions);
+    
+    /**
+     * Called by a text editor to report its new extracted text when its
+     * contents change.  This will only be called if the input method
+     * calls {@link InputConnection#getExtractedText(ExtractedTextRequest, int)
+     * InputConnection.getExtractedText()} with the option to report updates.
+     * 
+     * @param token The input method supplied token for identifying its request.
+     * @param text The new extracted text.
+     */
+    public void updateExtractedText(int token, ExtractedText text);
+    
+    /**
+     * This method is called when a key is pressed.  When done with the event,
+     * the implementation must call back on <var>callback</var> with its
+     * result.
+     * 
+     * <p>
+     * If the input method wants to handle this event, return true, otherwise
+     * return false and the caller (i.e. the application) will handle the event.
+     * 
+     * @param event The key event.
+     * 
+     * @return Whether the input method wants to handle this event.
+     * 
+     * @see #dispatchKeyUp
+     * @see android.view.KeyEvent
+     */
+    public void dispatchKeyEvent(int seq, KeyEvent event, EventCallback callback);
+
+    /**
+     * This method is called when there is a track ball event.
+     * 
+     * <p>
+     * If the input method wants to handle this event, return true, otherwise
+     * return false and the caller (i.e. the application) will handle the event.
+     * 
+     * @param event The motion event.
+     * 
+     * @return Whether the input method wants to handle this event.
+     * 
+     * @see android.view.MotionEvent
+     */
+    public void dispatchTrackballEvent(int seq, MotionEvent event, EventCallback callback);
+
+    /**
+     * Process a private command sent from the application to the input method.
+     * This can be used to provide domain-specific features that are
+     * only known between certain input methods and their clients.
+     * 
+     * @param action Name of the command to be performed.  This <em>must</em>
+     * be a scoped name, i.e. prefixed with a package name you own, so that
+     * different developers will not create conflicting commands.
+     * @param data Any data to include with the command.
+     */
+    public void appPrivateCommand(String action, Bundle data);
+}
diff --git a/core/java/android/view/inputmethod/package.html b/core/java/android/view/inputmethod/package.html
new file mode 100644
index 0000000..328c7b3
--- /dev/null
+++ b/core/java/android/view/inputmethod/package.html
@@ -0,0 +1,12 @@
+<html>
+<body>
+Framework classes for interaction between views and input methods (such
+as soft keyboards).  See {@link android.view.inputmethod.InputMethodManager} for
+an overview.  In most cases the main classes here are not needed for
+most applications, since they are dealt with for you by
+{@link android.widget.TextView}.  When implementing a custom text editor,
+however, you will need to implement the
+{@link android.view.inputmethod.InputConnection} class to allow the current
+input method to interact with your view.
+</body>
+</html>
diff --git a/core/java/android/view/package.html b/core/java/android/view/package.html
new file mode 100644
index 0000000..1c58765
--- /dev/null
+++ b/core/java/android/view/package.html
@@ -0,0 +1,6 @@
+<HTML>
+<BODY>
+Provides classes that expose basic user interface classes that handle
+screen layout and interaction with the user.
+</BODY>
+</HTML>
\ No newline at end of file
diff --git a/core/java/android/webkit/BrowserFrame.java b/core/java/android/webkit/BrowserFrame.java
new file mode 100644
index 0000000..451af6d
--- /dev/null
+++ b/core/java/android/webkit/BrowserFrame.java
@@ -0,0 +1,786 @@
+/*
+ * 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 android.webkit;
+
+import android.content.Context;
+import android.content.res.AssetManager;
+import android.graphics.Bitmap;
+import android.net.ParseException;
+import android.net.WebAddress;
+import android.net.http.SslCertificate;
+import android.os.Handler;
+import android.os.Message;
+import android.util.Config;
+import android.util.Log;
+import android.util.TypedValue;
+
+import junit.framework.Assert;
+
+import java.net.URLEncoder;
+import java.util.HashMap;
+import java.util.Iterator;
+
+class BrowserFrame extends Handler {
+
+    private static final String LOGTAG = "webkit";
+
+    /**
+     * Cap the number of LoadListeners that will be instantiated, so
+     * we don't blow the GREF count.  Attempting to queue more than
+     * this many requests will prompt an error() callback on the
+     * request's LoadListener
+     */
+    private final static int MAX_OUTSTANDING_REQUESTS = 300;
+
+    private final CallbackProxy mCallbackProxy;
+    private final WebSettings mSettings;
+    private final Context mContext;
+    private final WebViewDatabase mDatabase;
+    private final WebViewCore mWebViewCore;
+    /* package */ boolean mLoadInitFromJava;
+    private int mLoadType;
+    private boolean mFirstLayoutDone = true;
+    private boolean mCommitted = true;
+
+    // Is this frame the main frame?
+    private boolean mIsMainFrame;
+
+    // Attached Javascript interfaces
+    private HashMap mJSInterfaceMap;
+
+    // message ids
+    // a message posted when a frame loading is completed
+    static final int FRAME_COMPLETED = 1001;
+    // a message posted when the user decides the policy
+    static final int POLICY_FUNCTION = 1003;
+
+    // Note: need to keep these in sync with FrameLoaderTypes.h in native
+    static final int FRAME_LOADTYPE_STANDARD = 0;
+    static final int FRAME_LOADTYPE_BACK = 1;
+    static final int FRAME_LOADTYPE_FORWARD = 2;
+    static final int FRAME_LOADTYPE_INDEXEDBACKFORWARD = 3;
+    static final int FRAME_LOADTYPE_RELOAD = 4;
+    static final int FRAME_LOADTYPE_RELOADALLOWINGSTALEDATA = 5;
+    static final int FRAME_LOADTYPE_SAME = 6;
+    static final int FRAME_LOADTYPE_REDIRECT = 7;
+    static final int FRAME_LOADTYPE_REPLACE = 8;
+
+    // A progress threshold to switch from history Picture to live Picture
+    private static final int TRANSITION_SWITCH_THRESHOLD = 75;
+
+    // This is a field accessed by native code as well as package classes.
+    /*package*/ int mNativeFrame;
+
+    // Static instance of a JWebCoreJavaBridge to handle timer and cookie
+    // requests from WebCore.
+    static JWebCoreJavaBridge sJavaBridge;
+
+    /**
+     * Create a new BrowserFrame to be used in an application.
+     * @param context An application context to use when retrieving assets.
+     * @param w A WebViewCore used as the view for this frame.
+     * @param proxy A CallbackProxy for posting messages to the UI thread and
+     *              querying a client for information.
+     * @param settings A WebSettings object that holds all settings.
+     * XXX: Called by WebCore thread.
+     */
+    public BrowserFrame(Context context, WebViewCore w, CallbackProxy proxy,
+            WebSettings settings) {
+        // Create a global JWebCoreJavaBridge to handle timers and
+        // cookies in the WebCore thread.
+        if (sJavaBridge == null) {
+            sJavaBridge = new JWebCoreJavaBridge();
+            // set WebCore native cache size
+            sJavaBridge.setCacheSize(4 * 1024 * 1024);
+            // initialize CacheManager
+            CacheManager.init(context);
+            // create CookieSyncManager with current Context
+            CookieSyncManager.createInstance(context);
+        }
+        AssetManager am = context.getAssets();
+        nativeCreateFrame(w, am, proxy.getBackForwardList());
+
+        mSettings = settings;
+        mContext = context;
+        mCallbackProxy = proxy;
+        mDatabase = WebViewDatabase.getInstance(context);
+        mWebViewCore = w;
+
+        if (Config.LOGV) {
+            Log.v(LOGTAG, "BrowserFrame constructor: this=" + this);
+        }
+    }
+
+    /**
+     * Load a url from the network or the filesystem into the main frame.
+     * Following the same behaviour as Safari, javascript: URLs are not
+     * passed to the main frame, instead they are evaluated immediately.
+     * @param url The url to load.
+     */
+    public void loadUrl(String url) {
+        mLoadInitFromJava = true;
+        if (URLUtil.isJavaScriptUrl(url)) {
+            // strip off the scheme and evaluate the string
+            stringByEvaluatingJavaScriptFromString(
+                    url.substring("javascript:".length()));
+        } else {
+            nativeLoadUrl(url);
+        }
+        mLoadInitFromJava = false;
+    }
+
+    /**
+     * Load the content as if it was loaded by the provided base URL. The
+     * failUrl is used as the history entry for the load data. If null or
+     * an empty string is passed for the failUrl, then no history entry is
+     * created.
+     * 
+     * @param baseUrl Base URL used to resolve relative paths in the content
+     * @param data Content to render in the browser
+     * @param mimeType Mimetype of the data being passed in
+     * @param encoding Character set encoding of the provided data.
+     * @param failUrl URL to use if the content fails to load or null.
+     */
+    public void loadData(String baseUrl, String data, String mimeType,
+            String encoding, String failUrl) {
+        mLoadInitFromJava = true;
+        if (failUrl == null) {
+            failUrl = "";
+        }
+        if (data == null) {
+            data = "";
+        }
+        
+        // Setup defaults for missing values. These defaults where taken from
+        // WebKit's WebFrame.mm
+        if (baseUrl == null || baseUrl.length() == 0) {
+            baseUrl = "about:blank";
+        }
+        if (mimeType == null || mimeType.length() == 0) {
+            mimeType = "text/html";
+        }
+        nativeLoadData(baseUrl, data, mimeType, encoding, failUrl);
+        mLoadInitFromJava = false;
+    }
+
+    /**
+     * Go back or forward the number of steps given.
+     * @param steps A negative or positive number indicating the direction
+     *              and number of steps to move.
+     */
+    public void goBackOrForward(int steps) {
+        mLoadInitFromJava = true;
+        nativeGoBackOrForward(steps);
+        mLoadInitFromJava = false;
+    }
+
+    /**
+     * native callback
+     * Report an error to an activity.
+     * @param errorCode The HTTP error code.
+     * @param description A String description.
+     * TODO: Report all errors including resource errors but include some kind
+     * of domain identifier. Change errorCode to an enum for a cleaner
+     * interface.
+     */
+    private void reportError(final int errorCode, final String description,
+            final String failingUrl) {
+        // As this is called for the main resource and loading will be stopped
+        // after, reset the state variables.
+        mCommitted = true;
+        mWebViewCore.mEndScaleZoom = mFirstLayoutDone == false;
+        mFirstLayoutDone = true;
+        mCallbackProxy.onReceivedError(errorCode, description, failingUrl);
+    }
+
+    /* package */boolean committed() {
+        return mCommitted;
+    }
+
+    /* package */boolean firstLayoutDone() {
+        return mFirstLayoutDone;
+    }
+
+    /* package */int loadType() {
+        return mLoadType;
+    }
+
+    /* package */void didFirstLayout() {
+        if (!mFirstLayoutDone) {
+            mFirstLayoutDone = true;
+            // ensure {@link WebViewCore#webkitDraw} is called as we were
+            // blocking the update in {@link #loadStarted}
+            mWebViewCore.contentDraw();
+        }
+        mWebViewCore.mEndScaleZoom = true;
+    }
+
+    /**
+     * native callback
+     * Indicates the beginning of a new load.
+     * This method will be called once for the main frame.
+     */
+    private void loadStarted(String url, Bitmap favicon, int loadType,
+            boolean isMainFrame) {
+        mIsMainFrame = isMainFrame;
+
+        if (isMainFrame || loadType == FRAME_LOADTYPE_STANDARD) {
+            mLoadType = loadType;
+
+            if (isMainFrame) {
+                // Call onPageStarted for main frames.
+                mCallbackProxy.onPageStarted(url, favicon);
+                // as didFirstLayout() is only called for the main frame, reset 
+                // mFirstLayoutDone only for the main frames
+                mFirstLayoutDone = false;
+                mCommitted = false;
+                // remove pending draw to block update until mFirstLayoutDone is
+                // set to true in didFirstLayout()
+                mWebViewCore.removeMessages(WebViewCore.EventHub.WEBKIT_DRAW);
+            }
+
+            // Note: only saves committed form data in standard load
+            if (loadType == FRAME_LOADTYPE_STANDARD
+                    && mSettings.getSaveFormData()) {
+                final WebHistoryItem h = mCallbackProxy.getBackForwardList()
+                        .getCurrentItem();
+                if (h != null) {
+                    String currentUrl = h.getUrl();
+                    if (currentUrl != null) {
+                        mDatabase.setFormData(currentUrl, getFormTextData());
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * native callback
+     * Indicates the WebKit has committed to the new load
+     */
+    private void transitionToCommitted(int loadType, boolean isMainFrame) {
+        // loadType is not used yet
+        if (isMainFrame) {
+            mCommitted = true;
+        }
+    }
+
+    /**
+     * native callback
+     * <p>
+     * Indicates the end of a new load.
+     * This method will be called once for the main frame.
+     */
+    private void loadFinished(String url, int loadType, boolean isMainFrame) {
+        // mIsMainFrame and isMainFrame are better be equal!!!
+
+        if (isMainFrame || loadType == FRAME_LOADTYPE_STANDARD) {
+            if (isMainFrame) {
+                mCallbackProxy.switchOutDrawHistory();
+                mCallbackProxy.onPageFinished(url);
+            }
+        }
+    }
+
+    /**
+     * We have received an SSL certificate for the main top-level page.
+     *
+     * !!!Called from the network thread!!!
+     */
+    void certificate(SslCertificate certificate) {
+        if (mIsMainFrame) {
+            // we want to make this call even if the certificate is null
+            // (ie, the site is not secure)
+            mCallbackProxy.onReceivedCertificate(certificate);
+        }
+    }
+
+    /**
+     * Destroy all native components of the BrowserFrame.
+     */
+    public void destroy() {
+        nativeDestroyFrame();
+        removeCallbacksAndMessages(null);
+    }
+
+    /**
+     * Handle messages posted to us.
+     * @param msg The message to handle.
+     */
+    @Override
+    public void handleMessage(Message msg) {
+        switch (msg.what) {
+            case FRAME_COMPLETED: {
+                if (mSettings.getSavePassword() && hasPasswordField()) {
+                    if (Config.DEBUG) {
+                        Assert.assertNotNull(mCallbackProxy.getBackForwardList()
+                                .getCurrentItem());
+                    }
+                    WebAddress uri = new WebAddress(
+                            mCallbackProxy.getBackForwardList().getCurrentItem()
+                            .getUrl());
+                    String schemePlusHost = uri.mScheme + uri.mHost;
+                    String[] up = mDatabase.getUsernamePassword(schemePlusHost);
+                    if (up != null && up[0] != null) {
+                        setUsernamePassword(up[0], up[1]);
+                    }
+                }
+                CacheManager.trimCacheIfNeeded();
+                break;
+            }
+
+            case POLICY_FUNCTION: {
+                nativeCallPolicyFunction(msg.arg1, msg.arg2);
+                break;
+            }
+
+            default:
+                break;
+        }
+    }
+
+    /**
+     * Punch-through for WebCore to set the document
+     * title. Inform the Activity of the new title.
+     * @param title The new title of the document.
+     */
+    private void setTitle(String title) {
+        // FIXME: The activity must call getTitle (a native method) to get the
+        // title. We should try and cache the title if we can also keep it in
+        // sync with the document.
+        mCallbackProxy.onReceivedTitle(title);
+    }
+
+    /**
+     * Retrieves the render tree of this frame and puts it as the object for
+     * the message and sends the message.
+     * @param callback the message to use to send the render tree
+     */
+    public void externalRepresentation(Message callback) {
+        callback.obj = externalRepresentation();;
+        callback.sendToTarget();
+    }
+
+    /**
+     * Return the render tree as a string
+     */
+    private native String externalRepresentation();
+
+    /**
+     * Retrieves the visual text of the current frame, puts it as the object for
+     * the message and sends the message.
+     * @param callback the message to use to send the visual text
+     */
+    public void documentAsText(Message callback) {
+        callback.obj = documentAsText();;
+        callback.sendToTarget();
+    }
+
+    /**
+     * Return the text drawn on the screen as a string
+     */
+    private native String documentAsText();
+
+    /*
+     * This method is called by WebCore to inform the frame that
+     * the Javascript window object has been cleared.
+     * We should re-attach any attached js interfaces.
+     */
+    private void windowObjectCleared(int nativeFramePointer) {
+        if (mJSInterfaceMap != null) {
+            Iterator iter = mJSInterfaceMap.keySet().iterator();
+            while (iter.hasNext())  {
+                String interfaceName = (String) iter.next();
+                nativeAddJavascriptInterface(nativeFramePointer,
+                        mJSInterfaceMap.get(interfaceName), interfaceName);
+            }
+        }
+    }
+
+    /**
+     * This method is called by WebCore to check whether application
+     * wants to hijack url loading
+     */
+    public boolean handleUrl(String url) {
+        if (mLoadInitFromJava == true) {
+            return false;
+        }
+        if (mCallbackProxy.shouldOverrideUrlLoading(url)) {
+            // if the url is hijacked, reset the state of the BrowserFrame
+            didFirstLayout();
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    public void addJavascriptInterface(Object obj, String interfaceName) {
+        if (mJSInterfaceMap == null) {
+            mJSInterfaceMap = new HashMap<String, Object>();
+        }
+        if (mJSInterfaceMap.containsKey(interfaceName)) {
+            mJSInterfaceMap.remove(interfaceName);
+        }
+        mJSInterfaceMap.put(interfaceName, obj);
+    }
+
+    /**
+     * Start loading a resource.
+     * @param loaderHandle The native ResourceLoader that is the target of the
+     *                     data.
+     * @param url The url to load.
+     * @param method The http method.
+     * @param headers The http headers.
+     * @param postData If the method is "POST" postData is sent as the request
+     *                 body. Is null when empty.
+     * @param cacheMode The cache mode to use when loading this resource.
+     * @param isHighPriority True if this resource needs to be put at the front
+     *                       of the network queue.
+     * @param synchronous True if the load is synchronous.
+     * @return A newly created LoadListener object.
+     */
+    private LoadListener startLoadingResource(int loaderHandle,
+                                              String url,
+                                              String method,
+                                              HashMap headers,
+                                              byte[] postData,
+                                              int cacheMode,
+                                              boolean isHighPriority,
+                                              boolean synchronous) {
+        PerfChecker checker = new PerfChecker();
+
+        if (mSettings.getCacheMode() != WebSettings.LOAD_DEFAULT) {
+            cacheMode = mSettings.getCacheMode();
+        }
+
+        if (method.equals("POST")) {
+            // Don't use the cache on POSTs when issuing a normal POST
+            // request.
+            if (cacheMode == WebSettings.LOAD_NORMAL) {
+                cacheMode = WebSettings.LOAD_NO_CACHE;
+            }
+            if (mSettings.getSavePassword() && hasPasswordField()) {
+                try {
+                    if (Config.DEBUG) {
+                        Assert.assertNotNull(mCallbackProxy.getBackForwardList()
+                                .getCurrentItem());
+                    }
+                    WebAddress uri = new WebAddress(mCallbackProxy
+                            .getBackForwardList().getCurrentItem().getUrl());
+                    String schemePlusHost = uri.mScheme + uri.mHost;
+                    String[] ret = getUsernamePassword();
+                    // Has the user entered a username/password pair and is
+                    // there some POST data
+                    if (ret != null && postData != null && 
+                            ret[0].length() > 0 && ret[1].length() > 0) {
+                        // Check to see if the username & password appear in
+                        // the post data (there could be another form on the
+                        // page and that was posted instead.
+                        String postString = new String(postData);
+                        if (postString.contains(URLEncoder.encode(ret[0])) &&
+                                postString.contains(URLEncoder.encode(ret[1]))) {
+                            String[] saved = mDatabase.getUsernamePassword(
+                                    schemePlusHost);
+                            if (saved != null) {
+                                // null username implies that user has chosen not to
+                                // save password
+                                if (saved[0] != null) {
+                                    // non-null username implies that user has
+                                    // chosen to save password, so update the 
+                                    // recorded password
+                                    mDatabase.setUsernamePassword(
+                                            schemePlusHost, ret[0], ret[1]);
+                                }
+                            } else {
+                                // CallbackProxy will handle creating the resume
+                                // message
+                                mCallbackProxy.onSavePassword(schemePlusHost, ret[0], 
+                                        ret[1], null);
+                            }
+                        }
+                    }
+                } catch (ParseException ex) {
+                    // if it is bad uri, don't save its password
+                }
+                
+            }
+        }
+
+        // is this resource the main-frame top-level page?
+        boolean isMainFramePage = mIsMainFrame;
+
+        if (Config.LOGV) {
+            Log.v(LOGTAG, "startLoadingResource: url=" + url + ", method="
+                    + method + ", postData=" + postData + ", isHighPriority="
+                    + isHighPriority + ", isMainFramePage=" + isMainFramePage);
+        }
+
+        // Create a LoadListener
+        LoadListener loadListener = LoadListener.getLoadListener(mContext, this, url,
+                loaderHandle, synchronous, isMainFramePage);
+
+        mCallbackProxy.onLoadResource(url);
+
+        if (LoadListener.getNativeLoaderCount() > MAX_OUTSTANDING_REQUESTS) {
+            loadListener.error(
+                    android.net.http.EventHandler.ERROR, mContext.getString(
+                            com.android.internal.R.string.httpErrorTooManyRequests));
+            loadListener.notifyError();
+            loadListener.tearDown();
+            return null;
+        }
+
+        // during synchronous load, the WebViewCore thread is blocked, so we
+        // need to endCacheTransaction first so that http thread won't be 
+        // blocked in setupFile() when createCacheFile.
+        if (synchronous) {
+            CacheManager.endCacheTransaction();
+        }
+
+        FrameLoader loader = new FrameLoader(loadListener, mSettings,
+                method, isHighPriority);
+        loader.setHeaders(headers);
+        loader.setPostData(postData);
+        loader.setCacheMode(cacheMode); // Set the load mode to the mode used
+                                        // for the current page.
+        // Set referrer to current URL?
+        if (!loader.executeLoad()) {
+            checker.responseAlert("startLoadingResource fail");
+        }
+        checker.responseAlert("startLoadingResource succeed");
+
+        if (synchronous) {
+            CacheManager.startCacheTransaction();
+        }
+
+        return !synchronous ? loadListener : null;
+    }
+
+    /**
+     * Set the progress for the browser activity.  Called by native code.
+     * Uses a delay so it does not happen too often.
+     * @param newProgress An int between zero and one hundred representing
+     *                    the current progress percentage of loading the page.
+     */
+    private void setProgress(int newProgress) {
+        mCallbackProxy.onProgressChanged(newProgress);
+        if (newProgress == 100) {
+            sendMessageDelayed(obtainMessage(FRAME_COMPLETED), 100);
+        }
+        // FIXME: Need to figure out a better way to switch out of the history
+        // drawing mode. Maybe we can somehow compare the history picture with 
+        // the current picture, and switch when it contains more content.
+        if (mFirstLayoutDone && newProgress > TRANSITION_SWITCH_THRESHOLD) {
+            mCallbackProxy.switchOutDrawHistory();
+        }
+    }
+
+    /**
+     * Send the icon to the activity for display.
+     * @param icon A Bitmap representing a page's favicon.
+     */
+    private void didReceiveIcon(Bitmap icon) {
+        mCallbackProxy.onReceivedIcon(icon);
+    }
+
+    /**
+     * Request a new window from the client.
+     * @return The BrowserFrame object stored in the new WebView.
+     */
+    private BrowserFrame createWindow(boolean dialog, boolean userGesture) {
+        WebView w = mCallbackProxy.createWindow(dialog, userGesture);
+        if (w != null) {
+            return w.getWebViewCore().getBrowserFrame();
+        }
+        return null;
+    }
+
+    /**
+     * Try to focus this WebView.
+     */
+    private void requestFocus() {
+        mCallbackProxy.onRequestFocus();
+    }
+
+    /**
+     * Close this frame and window.
+     */
+    private void closeWindow(WebViewCore w) {
+        mCallbackProxy.onCloseWindow(w.getWebView());
+    }
+
+    // XXX: Must match PolicyAction in FrameLoaderTypes.h in webcore
+    static final int POLICY_USE = 0;
+    static final int POLICY_IGNORE = 2;
+
+    private void decidePolicyForFormResubmission(int policyFunction) {
+        Message dontResend = obtainMessage(POLICY_FUNCTION, policyFunction,
+                POLICY_IGNORE);
+        Message resend = obtainMessage(POLICY_FUNCTION, policyFunction,
+                POLICY_USE);
+        mCallbackProxy.onFormResubmission(dontResend, resend);
+    }
+
+    /**
+     * Tell the activity to update its global history.
+     */
+    private void updateVisitedHistory(String url, boolean isReload) {
+        mCallbackProxy.doUpdateVisitedHistory(url, isReload);
+    }
+
+    /**
+     * Get the CallbackProxy for sending messages to the UI thread.
+     */
+    /* package */ CallbackProxy getCallbackProxy() {
+        return mCallbackProxy;
+    }
+
+    /**
+     * Returns the User Agent used by this frame
+     */
+    String getUserAgentString() {
+        return mSettings.getUserAgentString();
+    }
+
+    // these ids need to be in sync with enum RAW_RES_ID in WebFrame
+    private static final int NODOMAIN = 1;
+    private static final int LOADERROR = 2;
+
+    String getRawResFilename(int id) {
+        int resid;
+        switch (id) {
+            case NODOMAIN:
+                resid = com.android.internal.R.raw.nodomain;
+                break;
+
+            case LOADERROR:
+                resid = com.android.internal.R.raw.loaderror;
+                break;
+
+            default:
+                Log.e(LOGTAG, "getRawResFilename got incompatible resource ID");
+                return new String();
+        }
+        TypedValue value = new TypedValue();
+        mContext.getResources().getValue(resid, value, true);
+        return value.string.toString();
+    }
+
+    //==========================================================================
+    // native functions
+    //==========================================================================
+
+    /**
+     * Create a new native frame for a given WebView
+     * @param w     A WebView that the frame draws into.
+     * @param am    AssetManager to use to get assets.
+     * @param list  The native side will add and remove items from this list as
+     *              the native list changes.
+     */
+    private native void nativeCreateFrame(WebViewCore w, AssetManager am,
+            WebBackForwardList list);
+
+    /**
+     * Destroy the native frame.
+     */
+    public native void nativeDestroyFrame();
+
+    private native void nativeCallPolicyFunction(int policyFunction,
+            int decision);
+
+    /**
+     * Reload the current main frame.
+     */
+    public native void reload(boolean allowStale);
+
+    /**
+     * Go back or forward the number of steps given.
+     * @param steps A negative or positive number indicating the direction
+     *              and number of steps to move.
+     */
+    private native void nativeGoBackOrForward(int steps);
+
+    /**
+     * stringByEvaluatingJavaScriptFromString will execute the
+     * JS passed in in the context of this browser frame.
+     * @param script A javascript string to execute
+     * 
+     * @return string result of execution or null
+     */
+    public native String stringByEvaluatingJavaScriptFromString(String script);
+
+    /**
+     * Add a javascript interface to the main frame.
+     */
+    private native void nativeAddJavascriptInterface(int nativeFramePointer,
+            Object obj, String interfaceName);
+
+    /**
+     * Enable or disable the native cache.
+     */
+    /* FIXME: The native cache is always on for now until we have a better
+     * solution for our 2 caches. */
+    private native void setCacheDisabled(boolean disabled);
+
+    public native boolean cacheDisabled();
+
+    public native void clearCache();
+
+    /**
+     * Returns false if the url is bad.
+     */
+    private native void nativeLoadUrl(String url);
+
+    private native void nativeLoadData(String baseUrl, String data,
+            String mimeType, String encoding, String failUrl);
+
+    /**
+     * Stop loading the current page.
+     */
+    public native void stopLoading();
+
+    /**
+     * Return true if the document has images.
+     */
+    public native boolean documentHasImages();
+
+    /**
+     * @return TRUE if there is a password field in the current frame
+     */
+    private native boolean hasPasswordField();
+
+    /**
+     * Get username and password in the current frame. If found, String[0] is
+     * username and String[1] is password. Otherwise return NULL.
+     * @return String[]
+     */
+    private native String[] getUsernamePassword();
+
+    /**
+     * Set username and password to the proper fields in the current frame
+     * @param username
+     * @param password
+     */
+    private native void setUsernamePassword(String username, String password);
+
+    /**
+     * Get form's "text" type data associated with the current frame.
+     * @return HashMap If succeed, returns a list of name/value pair. Otherwise
+     *         returns null.
+     */
+    private native HashMap getFormTextData();
+}
diff --git a/core/java/android/webkit/ByteArrayBuilder.java b/core/java/android/webkit/ByteArrayBuilder.java
new file mode 100644
index 0000000..806b458
--- /dev/null
+++ b/core/java/android/webkit/ByteArrayBuilder.java
@@ -0,0 +1,142 @@
+/*
+ * 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 android.webkit;
+
+import java.util.LinkedList;
+
+/** Utility class optimized for accumulating bytes, and then spitting
+    them back out.  It does not optimize for returning the result in a
+    single array, though this is supported in the API. It is fastest
+    if the retrieval can be done via iterating through chunks.
+
+    Things to add:
+      - consider dynamically increasing our min_capacity,
+        as we see mTotalSize increase
+*/
+class ByteArrayBuilder {
+
+    private static final int DEFAULT_CAPACITY = 8192;
+
+    private LinkedList<Chunk> mChunks;
+
+    /** free pool */
+    private LinkedList<Chunk> mPool;
+
+    private int mMinCapacity;
+
+    public ByteArrayBuilder() {
+        init(0);
+    }
+
+    public ByteArrayBuilder(int minCapacity) {
+        init(minCapacity);
+    }
+
+    private void init(int minCapacity) {
+        mChunks = new LinkedList<Chunk>();
+        mPool = new LinkedList<Chunk>();
+
+        if (minCapacity <= 0) {
+            minCapacity = DEFAULT_CAPACITY;
+        }
+        mMinCapacity = minCapacity;
+    }
+
+    public void append(byte[] array) {
+        append(array, 0, array.length);
+    }
+
+    public synchronized void append(byte[] array, int offset, int length) {
+        while (length > 0) {
+            Chunk c = appendChunk(length);
+            int amount = Math.min(length, c.mArray.length - c.mLength);
+            System.arraycopy(array, offset, c.mArray, c.mLength, amount);
+            c.mLength += amount;
+            length -= amount;
+            offset += amount;
+        }
+    }
+
+    /**
+     * The fastest way to retrieve the data is to iterate through the
+     * chunks.  This returns the first chunk.  Note: this pulls the
+     * chunk out of the queue.  The caller must call releaseChunk() to
+     * dispose of it.
+     */
+    public synchronized Chunk getFirstChunk() {
+        if (mChunks.isEmpty()) return null;
+        return mChunks.removeFirst();
+    }
+
+    /**
+     * recycles chunk
+     */
+    public synchronized void releaseChunk(Chunk c) {
+        c.mLength = 0;
+        mPool.addLast(c);
+    }
+
+    public boolean isEmpty() {
+        return mChunks.isEmpty();
+    }
+
+    public synchronized void clear() {
+        Chunk c = getFirstChunk();
+        while (c != null) {
+            releaseChunk(c);
+            c = getFirstChunk();
+        }
+    }
+
+    private Chunk appendChunk(int length) {
+        if (length < mMinCapacity) {
+            length = mMinCapacity;
+        }
+
+        Chunk c;
+        if (mChunks.isEmpty()) {
+            c = obtainChunk(length);
+        } else {
+            c = mChunks.getLast();
+            if (c.mLength == c.mArray.length) {
+                c = obtainChunk(length);
+            }
+        }
+        return c;
+    }
+
+    private Chunk obtainChunk(int length) {
+        Chunk c;
+        if (mPool.isEmpty()) {
+            c = new Chunk(length);
+        } else {
+            c = mPool.removeFirst();
+        }
+        mChunks.addLast(c);
+        return c;
+    }
+
+    public static class Chunk {
+        public byte[]  mArray;
+        public int     mLength;
+
+        public Chunk(int length) {
+            mArray = new byte[length];
+            mLength = 0;
+        }
+    }
+}
diff --git a/core/java/android/webkit/CacheLoader.java b/core/java/android/webkit/CacheLoader.java
new file mode 100644
index 0000000..3e1b602
--- /dev/null
+++ b/core/java/android/webkit/CacheLoader.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2007 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 android.webkit;
+
+import android.net.http.Headers;
+
+/**
+ * This class is a concrete implementation of StreamLoader that uses a
+ * CacheResult as the source for the stream. The CacheResult stored mimetype
+ * and encoding is added to the HTTP response headers.
+ */
+class CacheLoader extends StreamLoader {
+
+    CacheManager.CacheResult mCacheResult;  // Content source
+
+    /**
+     * Constructs a CacheLoader for use when loading content from the cache.
+     *
+     * @param loadListener LoadListener to pass the content to
+     * @param result CacheResult used as the source for the content.
+     */
+    CacheLoader(LoadListener loadListener, CacheManager.CacheResult result) {
+        super(loadListener);
+        mCacheResult = result;
+    }
+
+    @Override
+    protected boolean setupStreamAndSendStatus() {
+        mDataStream = mCacheResult.inStream;
+        mContentLength = mCacheResult.contentLength;
+        mHandler.status(1, 1, mCacheResult.httpStatusCode, "OK");
+        return true;
+    }
+
+    @Override
+    protected void buildHeaders(Headers headers) {
+        StringBuilder sb = new StringBuilder(mCacheResult.mimeType);
+        if (mCacheResult.encoding != null &&
+                mCacheResult.encoding.length() > 0) {
+            sb.append(';');
+            sb.append(mCacheResult.encoding);
+        }
+        headers.setContentType(sb.toString());
+
+        if (mCacheResult.location != null &&
+                mCacheResult.location.length() > 0) {
+            headers.setLocation(mCacheResult.location);
+        }
+    }
+
+}
diff --git a/core/java/android/webkit/CacheManager.java b/core/java/android/webkit/CacheManager.java
new file mode 100644
index 0000000..d12940d
--- /dev/null
+++ b/core/java/android/webkit/CacheManager.java
@@ -0,0 +1,703 @@
+/*
+ * 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 android.webkit;
+
+import android.content.Context;
+import android.net.http.Headers;
+import android.os.FileUtils;
+import android.util.Config;
+import android.util.Log;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.Map;
+
+import org.bouncycastle.crypto.Digest;
+import org.bouncycastle.crypto.digests.SHA1Digest;
+
+/**
+ * The class CacheManager provides the persistent cache of content that is
+ * received over the network. The component handles parsing of HTTP headers and
+ * utilizes the relevant cache headers to determine if the content should be
+ * stored and if so, how long it is valid for. Network requests are provided to
+ * this component and if they can not be resolved by the cache, the HTTP headers
+ * are attached, as appropriate, to the request for revalidation of content. The
+ * class also manages the cache size.
+ */
+public final class CacheManager {
+
+    private static final String LOGTAG = "cache";
+
+    static final String HEADER_KEY_IFMODIFIEDSINCE = "if-modified-since";
+    static final String HEADER_KEY_IFNONEMATCH = "if-none-match";
+
+    private static final String NO_STORE = "no-store";
+    private static final String NO_CACHE = "no-cache";
+    private static final String MAX_AGE = "max-age";
+
+    private static long CACHE_THRESHOLD = 6 * 1024 * 1024;
+    private static long CACHE_TRIM_AMOUNT = 2 * 1024 * 1024;
+
+    private static boolean mDisabled;
+
+    // Reference count the enable/disable transaction
+    private static int mRefCount;
+
+    // trimCacheIfNeeded() is called when a page is fully loaded. But JavaScript
+    // can load the content, e.g. in a slideshow, continuously, so we need to
+    // trim the cache on a timer base too. endCacheTransaction() is called on a 
+    // timer base. We share the same timer with less frequent update.
+    private static int mTrimCacheCount = 0;
+    private static final int TRIM_CACHE_INTERVAL = 5;
+
+    private static WebViewDatabase mDataBase;
+    private static File mBaseDir;
+    
+    // Flag to clear the cache when the CacheManager is initialized
+    private static boolean mClearCacheOnInit = false;
+
+    public static class CacheResult {
+        // these fields are saved to the database
+        int httpStatusCode;
+        long contentLength;
+        long expires;
+        String localPath;
+        String lastModified;
+        String etag;
+        String mimeType;
+        String location;
+        String encoding;
+
+        // these fields are NOT saved to the database
+        InputStream inStream;
+        OutputStream outStream;
+        File outFile;
+
+        public int getHttpStatusCode() {
+            return httpStatusCode;
+        }
+
+        public long getContentLength() {
+            return contentLength;
+        }
+
+        public String getLocalPath() {
+            return localPath;
+        }
+
+        public long getExpires() {
+            return expires;
+        }
+
+        public String getLastModified() {
+            return lastModified;
+        }
+
+        public String getETag() {
+            return etag;
+        }
+
+        public String getMimeType() {
+            return mimeType;
+        }
+
+        public String getLocation() {
+            return location;
+        }
+
+        public String getEncoding() {
+            return encoding;
+        }
+
+        // For out-of-package access to the underlying streams.
+        public InputStream getInputStream() {
+            return inStream;
+        }
+
+        public OutputStream getOutputStream() {
+            return outStream;
+        }
+
+        // These fields can be set manually.
+        public void setInputStream(InputStream stream) {
+            this.inStream = stream;
+        }
+
+        public void setEncoding(String encoding) {
+            this.encoding = encoding;
+        }
+    }
+
+    /**
+     * initialize the CacheManager. WebView should handle this for each process.
+     * 
+     * @param context The application context.
+     */
+    static void init(Context context) {
+        mDataBase = WebViewDatabase.getInstance(context);
+        mBaseDir = new File(context.getCacheDir(), "webviewCache");
+        if (createCacheDirectory() && mClearCacheOnInit) {
+            removeAllCacheFiles();
+            mClearCacheOnInit = false;
+        }
+    }
+    
+    /**
+     * Create the cache directory if it does not already exist.
+     * 
+     * @return true if the cache directory didn't exist and was created.
+     */
+    static private boolean createCacheDirectory() {
+        if (!mBaseDir.exists()) {
+            if(!mBaseDir.mkdirs()) {
+                Log.w(LOGTAG, "Unable to create webviewCache directory");
+                return false;
+            }
+            FileUtils.setPermissions(
+                    mBaseDir.toString(),
+                    FileUtils.S_IRWXU|FileUtils.S_IRWXG|FileUtils.S_IXOTH,
+                    -1, -1);
+            // If we did create the directory, we need to flush 
+            // the cache database. The directory could be recreated
+            // because the system flushed all the data/cache directories
+            // to free up disk space.
+            WebViewCore.endCacheTransaction();
+            mDataBase.clearCache();
+            WebViewCore.startCacheTransaction();
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * get the base directory of the cache. With localPath of the CacheResult,
+     * it identifies the cache file.
+     * 
+     * @return File The base directory of the cache.
+     */
+    public static File getCacheFileBaseDir() {
+        return mBaseDir;
+    }
+
+    /**
+     * set the flag to control whether cache is enabled or disabled
+     * 
+     * @param disabled true to disable the cache
+     */
+    // only called from WebCore thread
+    static void setCacheDisabled(boolean disabled) {
+        if (disabled == mDisabled) {
+            return;
+        }
+        mDisabled = disabled;
+        if (mDisabled) {
+            removeAllCacheFiles();
+        }
+    }
+
+    /**
+     * get the state of the current cache, enabled or disabled
+     * 
+     * @return return if it is disabled
+     */
+    public static boolean cacheDisabled() {
+        return mDisabled;
+    }
+
+    // only called from WebCore thread
+    // make sure to call enableTransaction/disableTransaction in pair
+    static boolean enableTransaction() {
+        if (++mRefCount == 1) {
+            mDataBase.startCacheTransaction();
+            return true;
+        }
+        return false;
+    }
+
+    // only called from WebCore thread
+    // make sure to call enableTransaction/disableTransaction in pair
+    static boolean disableTransaction() {
+        if (mRefCount == 0) {
+            Log.e(LOGTAG, "disableTransaction is out of sync");
+        }
+        if (--mRefCount == 0) {
+            mDataBase.endCacheTransaction();
+            return true;
+        }
+        return false;
+    }
+
+    // only called from WebCore thread
+    // make sure to call startCacheTransaction/endCacheTransaction in pair
+    public static boolean startCacheTransaction() {
+        return mDataBase.startCacheTransaction();
+    }
+
+    // only called from WebCore thread
+    // make sure to call startCacheTransaction/endCacheTransaction in pair
+    public static boolean endCacheTransaction() {
+        boolean ret = mDataBase.endCacheTransaction();
+        if (++mTrimCacheCount >= TRIM_CACHE_INTERVAL) {
+            mTrimCacheCount = 0;
+            trimCacheIfNeeded();
+        }
+        return ret;
+    }
+
+    /**
+     * Given a url, returns the CacheResult if exists. Otherwise returns null.
+     * If headers are provided and a cache needs validation,
+     * HEADER_KEY_IFNONEMATCH or HEADER_KEY_IFMODIFIEDSINCE will be set in the
+     * cached headers.
+     * 
+     * @return the CacheResult for a given url
+     */
+    // only called from WebCore thread
+    public static CacheResult getCacheFile(String url,
+            Map<String, String> headers) {
+        if (mDisabled) {
+            return null;
+        }
+
+        CacheResult result = mDataBase.getCache(url);
+        if (result != null) {
+            if (result.contentLength == 0) {
+                if (result.httpStatusCode != 301
+                        && result.httpStatusCode != 302
+                        && result.httpStatusCode != 307) {
+                    // this should not happen. If it does, remove it.
+                    mDataBase.removeCache(url);
+                    return null;
+                }
+            } else {
+                File src = new File(mBaseDir, result.localPath);
+                try {
+                    // open here so that even the file is deleted, the content
+                    // is still readable by the caller until close() is called
+                    result.inStream = new FileInputStream(src);
+                } catch (FileNotFoundException e) {
+                    // the files in the cache directory can be removed by the
+                    // system. If it is gone, clean up the database
+                    mDataBase.removeCache(url);
+                    return null;
+                }
+            }
+        } else {
+            return null;
+        }
+
+        // null headers request coming from CACHE_MODE_CACHE_ONLY
+        // which implies that it needs cache even it is expired.
+        // negative expires means time in the far future.
+        if (headers != null && result.expires >= 0
+                && result.expires <= System.currentTimeMillis()) {
+            if (result.lastModified == null && result.etag == null) {
+                return null;
+            }
+            // return HEADER_KEY_IFNONEMATCH or HEADER_KEY_IFMODIFIEDSINCE
+            // for requesting validation
+            if (result.etag != null) {
+                headers.put(HEADER_KEY_IFNONEMATCH, result.etag);
+            }
+            if (result.lastModified != null) {
+                headers.put(HEADER_KEY_IFMODIFIEDSINCE, result.lastModified);
+            }
+        }
+
+        if (Config.LOGV) {
+            Log.v(LOGTAG, "getCacheFile for url " + url);
+        }
+
+        return result;
+    }
+
+    /**
+     * Given a url and its full headers, returns CacheResult if a local cache
+     * can be stored. Otherwise returns null. The mimetype is passed in so that
+     * the function can use the mimetype that will be passed to WebCore which
+     * could be different from the mimetype defined in the headers.
+     * forceCache is for out-of-package callers to force creation of a
+     * CacheResult, and is used to supply surrogate responses for URL
+     * interception.
+     * @return CacheResult for a given url
+     * @hide - hide createCacheFile since it has a parameter of type headers, which is
+     * in a hidden package.
+     */
+    // can be called from any thread
+    public static CacheResult createCacheFile(String url, int statusCode,
+            Headers headers, String mimeType, boolean forceCache) {
+        if (!forceCache && mDisabled) {
+            return null;
+        }
+
+        CacheResult ret = parseHeaders(statusCode, headers, mimeType);
+        if (ret != null) {
+            setupFiles(url, ret);
+            try {
+                ret.outStream = new FileOutputStream(ret.outFile);
+            } catch (FileNotFoundException e) {
+                // This can happen with the system did a purge and our
+                // subdirectory has gone, so lets try to create it again
+                if (createCacheDirectory()) {
+                    try {
+                        ret.outStream = new FileOutputStream(ret.outFile);
+                    } catch  (FileNotFoundException e2) {
+                        // We failed to create the file again, so there
+                        // is something else wrong. Return null.
+                        return null;
+                    }
+                } else {
+                    // Failed to create cache directory
+                    return null;
+                }
+            }
+            ret.mimeType = mimeType;
+        }
+
+        return ret;
+    }
+
+    /**
+     * Save the info of a cache file for a given url to the CacheMap so that it
+     * can be reused later
+     */
+    // only called from WebCore thread
+    public static void saveCacheFile(String url, CacheResult cacheRet) {
+        try {
+            cacheRet.outStream.close();
+        } catch (IOException e) {
+            return;
+        }
+
+        if (!cacheRet.outFile.exists()) {
+            // the file in the cache directory can be removed by the system
+            return;
+        }
+
+        cacheRet.contentLength = cacheRet.outFile.length();
+        if (cacheRet.httpStatusCode == 301
+                || cacheRet.httpStatusCode == 302
+                || cacheRet.httpStatusCode == 307) {
+            // location is in database, no need to keep the file
+            cacheRet.contentLength = 0;
+            cacheRet.localPath = new String();
+            cacheRet.outFile.delete();
+        } else if (cacheRet.contentLength == 0) {
+            cacheRet.outFile.delete();
+            return;
+        }
+
+        mDataBase.addCache(url, cacheRet);
+
+        if (Config.LOGV) {
+            Log.v(LOGTAG, "saveCacheFile for url " + url);
+        }
+    }
+
+    /**
+     * remove all cache files
+     * 
+     * @return true if it succeeds
+     */
+    // only called from WebCore thread
+    static boolean removeAllCacheFiles() {
+        // Note, this is called before init() when the database is
+        // created or upgraded.
+        if (mBaseDir == null) {
+            // Init() has not been called yet, so just flag that
+            // we need to clear the cache when init() is called.
+            mClearCacheOnInit = true;
+            return true;
+        }
+        // delete cache in a separate thread to not block UI.
+        final Runnable clearCache = new Runnable() {
+            public void run() {
+                // delete all cache files
+                try {
+                    String[] files = mBaseDir.list();
+                    // if mBaseDir doesn't exist, files can be null.
+                    if (files != null) {
+                        for (int i = 0; i < files.length; i++) {
+                            new File(mBaseDir, files[i]).delete();
+                        }
+                    }
+                } catch (SecurityException e) {
+                    // Ignore SecurityExceptions.
+                }
+                // delete database
+                mDataBase.clearCache();
+            }
+        };
+        new Thread(clearCache).start();
+        return true;
+    }
+
+    /**
+     * Return true if the cache is empty.
+     */
+    // only called from WebCore thread
+    static boolean cacheEmpty() {
+        return mDataBase.hasCache();
+    }
+
+    // only called from WebCore thread
+    static void trimCacheIfNeeded() {
+        if (mDataBase.getCacheTotalSize() > CACHE_THRESHOLD) {
+            ArrayList<String> pathList = mDataBase.trimCache(CACHE_TRIM_AMOUNT);
+            int size = pathList.size();
+            for (int i = 0; i < size; i++) {
+                new File(mBaseDir, pathList.get(i)).delete();
+            }
+        }
+    }
+
+    @SuppressWarnings("deprecation")
+    private static void setupFiles(String url, CacheResult cacheRet) {
+        if (true) {
+            // Note: SHA1 is much stronger hash. But the cost of setupFiles() is
+            // 3.2% cpu time for a fresh load of nytimes.com. While a simple
+            // String.hashCode() is only 0.6%. If adding the collision resolving
+            // to String.hashCode(), it makes the cpu time to be 1.6% for a 
+            // fresh load, but 5.3% for the worst case where all the files 
+            // already exist in the file system, but database is gone. So it
+            // needs to resolve collision for every file at least once.
+            int hashCode = url.hashCode();
+            StringBuffer ret = new StringBuffer(8);
+            appendAsHex(hashCode, ret);
+            String path = ret.toString();
+            File file = new File(mBaseDir, path);
+            if (true) {
+                boolean checkOldPath = true;
+                // Check hash collision. If the hash file doesn't exist, just
+                // continue. There is a chance that the old cache file is not
+                // same as the hash file. As mDataBase.getCache() is more 
+                // expansive than "leak" a file until clear cache, don't bother.
+                // If the hash file exists, make sure that it is same as the 
+                // cache file. If it is not, resolve the collision.
+                while (file.exists()) {
+                    if (checkOldPath) {
+                        // as this is called from http thread through 
+                        // createCacheFile, we need endCacheTransaction before 
+                        // database access.
+                        WebViewCore.endCacheTransaction();
+                        CacheResult oldResult = mDataBase.getCache(url);
+                        WebViewCore.startCacheTransaction();
+                        if (oldResult != null && oldResult.contentLength > 0) {
+                            if (path.equals(oldResult.localPath)) {
+                                path = oldResult.localPath;
+                            } else {
+                                path = oldResult.localPath;
+                                file = new File(mBaseDir, path);
+                            }
+                            break;
+                        }
+                        checkOldPath = false;
+                    }
+                    ret = new StringBuffer(8);
+                    appendAsHex(++hashCode, ret);
+                    path = ret.toString();
+                    file = new File(mBaseDir, path);
+                }
+            }
+            cacheRet.localPath = path;
+            cacheRet.outFile = file;
+        } else {
+            // get hash in byte[]
+            Digest digest = new SHA1Digest();
+            int digestLen = digest.getDigestSize();
+            byte[] hash = new byte[digestLen];
+            int urlLen = url.length();
+            byte[] data = new byte[urlLen];
+            url.getBytes(0, urlLen, data, 0);
+            digest.update(data, 0, urlLen);
+            digest.doFinal(hash, 0);
+            // convert byte[] to hex String
+            StringBuffer result = new StringBuffer(2 * digestLen);
+            for (int i = 0; i < digestLen; i = i + 4) {
+                int h = (0x00ff & hash[i]) << 24 | (0x00ff & hash[i + 1]) << 16
+                        | (0x00ff & hash[i + 2]) << 8 | (0x00ff & hash[i + 3]);
+                appendAsHex(h, result);
+            }
+            cacheRet.localPath = result.toString();
+            cacheRet.outFile = new File(mBaseDir, cacheRet.localPath);
+        }
+    }
+
+    private static void appendAsHex(int i, StringBuffer ret) {
+        String hex = Integer.toHexString(i);
+        switch (hex.length()) {
+            case 1:
+                ret.append("0000000");
+                break;
+            case 2:
+                ret.append("000000");
+                break;
+            case 3:
+                ret.append("00000");
+                break;
+            case 4:
+                ret.append("0000");
+                break;
+            case 5:
+                ret.append("000");
+                break;
+            case 6:
+                ret.append("00");
+                break;
+            case 7:
+                ret.append("0");
+                break;
+        }
+        ret.append(hex);
+    }
+
+    private static CacheResult parseHeaders(int statusCode, Headers headers,
+            String mimeType) {
+        // TODO: if authenticated or secure, return null
+        CacheResult ret = new CacheResult();
+        ret.httpStatusCode = statusCode;
+
+        String location = headers.getLocation();
+        if (location != null) ret.location = location;
+
+        ret.expires = -1;
+        String expires = headers.getExpires();
+        if (expires != null) {
+            try {
+                ret.expires = HttpDateTime.parse(expires);
+            } catch (IllegalArgumentException ex) {
+                // Take care of the special "-1" and "0" cases
+                if ("-1".equals(expires) || "0".equals(expires)) {
+                    // make it expired, but can be used for history navigation
+                    ret.expires = 0;
+                } else {
+                    Log.e(LOGTAG, "illegal expires: " + expires);
+                }
+            }
+        }
+
+        String lastModified = headers.getLastModified();
+        if (lastModified != null) ret.lastModified = lastModified;
+
+        String etag = headers.getEtag();
+        if (etag != null) ret.etag = etag;
+
+        String cacheControl = headers.getCacheControl();
+        if (cacheControl != null) {
+            String[] controls = cacheControl.toLowerCase().split("[ ,;]");
+            for (int i = 0; i < controls.length; i++) {
+                if (NO_STORE.equals(controls[i])) {
+                    return null;
+                }
+                // According to the spec, 'no-cache' means that the content
+                // must be re-validated on every load. It does not mean that
+                // the content can not be cached. set to expire 0 means it
+                // can only be used in CACHE_MODE_CACHE_ONLY case
+                if (NO_CACHE.equals(controls[i])) {
+                    ret.expires = 0;
+                } else if (controls[i].startsWith(MAX_AGE)) {
+                    int separator = controls[i].indexOf('=');
+                    if (separator < 0) {
+                        separator = controls[i].indexOf(':');
+                    }
+                    if (separator > 0) {
+                        String s = controls[i].substring(separator + 1);
+                        try {
+                            long sec = Long.parseLong(s);
+                            if (sec >= 0) {
+                                ret.expires = System.currentTimeMillis() + 1000
+                                        * sec;
+                            }
+                        } catch (NumberFormatException ex) {
+                            if ("1d".equals(s)) {
+                                // Take care of the special "1d" case
+                                ret.expires = System.currentTimeMillis() + 86400000; // 24*60*60*1000
+                            } else {
+                                Log.e(LOGTAG, "exception in parseHeaders for "
+                                        + "max-age:"
+                                        + controls[i].substring(separator + 1));
+                                ret.expires = 0;
+                            }
+                        }
+                    }
+                }
+            }
+        }
+
+        // According to RFC 2616 section 14.32:
+        // HTTP/1.1 caches SHOULD treat "Pragma: no-cache" as if the
+        // client had sent "Cache-Control: no-cache"
+        if (NO_CACHE.equals(headers.getPragma())) {
+            ret.expires = 0;
+        }
+
+        // According to RFC 2616 section 13.2.4, if an expiration has not been
+        // explicitly defined a heuristic to set an expiration may be used.
+        if (ret.expires == -1) {
+            if (ret.httpStatusCode == 301) {
+                // If it is a permanent redirect, and it did not have an
+                // explicit cache directive, then it never expires
+                ret.expires = Long.MAX_VALUE;
+            } else if (ret.httpStatusCode == 302 || ret.httpStatusCode == 307) {
+                // If it is temporary redirect, expires
+                ret.expires = 0;
+            } else if (ret.lastModified == null) {
+                // When we have no last-modified, then expire the content with
+                // in 24hrs as, according to the RFC, longer time requires a
+                // warning 113 to be added to the response.
+
+                // Only add the default expiration for non-html markup. Some
+                // sites like news.google.com have no cache directives.
+                if (!mimeType.startsWith("text/html")) {
+                    ret.expires = System.currentTimeMillis() + 86400000; // 24*60*60*1000
+                } else {
+                    // Setting a expires as zero will cache the result for
+                    // forward/back nav.
+                    ret.expires = 0;
+                }
+            } else {
+                // If we have a last-modified value, we could use it to set the
+                // expiration. Suggestion from RFC is 10% of time since
+                // last-modified. As we are on mobile, loads are expensive,
+                // increasing this to 20%.
+
+                // 24 * 60 * 60 * 1000
+                long lastmod = System.currentTimeMillis() + 86400000;
+                try {
+                    lastmod = HttpDateTime.parse(ret.lastModified);
+                } catch (IllegalArgumentException ex) {
+                    Log.e(LOGTAG, "illegal lastModified: " + ret.lastModified);
+                }
+                long difference = System.currentTimeMillis() - lastmod;
+                if (difference > 0) {
+                    ret.expires = System.currentTimeMillis() + difference / 5;
+                } else {
+                    // last modified is in the future, expire the content
+                    // on the last modified
+                    ret.expires = lastmod;
+                }
+            }
+        }
+
+        return ret;
+    }
+}
diff --git a/core/java/android/webkit/CallbackProxy.java b/core/java/android/webkit/CallbackProxy.java
new file mode 100644
index 0000000..84aeb83
--- /dev/null
+++ b/core/java/android/webkit/CallbackProxy.java
@@ -0,0 +1,1020 @@
+/*
+ * Copyright (C) 2007 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 android.webkit;
+
+import android.app.AlertDialog;
+import android.content.ActivityNotFoundException;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.graphics.Bitmap;
+import android.net.Uri;
+import android.net.http.SslCertificate;
+import android.net.http.SslError;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
+import android.os.SystemClock;
+import android.util.Config;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.EditText;
+import android.widget.TextView;
+import com.android.internal.R;
+
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.HashMap;
+
+/**
+ * This class is a proxy class for handling WebCore -> UI thread messaging. All
+ * the callback functions are called from the WebCore thread and messages are
+ * posted to the UI thread for the actual client callback.
+ */
+/*
+ * This class is created in the UI thread so its handler and any private classes
+ * that extend Handler will operate in the UI thread.
+ */
+class CallbackProxy extends Handler {
+    // Logging tag
+    private static final String LOGTAG = "CallbackProxy";
+    // Instance of WebViewClient that is the client callback.
+    private volatile WebViewClient mWebViewClient;
+    // Instance of WebChromeClient for handling all chrome functions.
+    private volatile WebChromeClient mWebChromeClient;
+    // Instance of WebView for handling UI requests.
+    private final WebView mWebView;
+    // Client registered callback listener for download events
+    private volatile DownloadListener mDownloadListener;
+    // Keep track of multiple progress updates.
+    private boolean mProgressUpdatePending;
+    // Keep track of the last progress amount.
+    private volatile int mLatestProgress;
+    // Back/Forward list
+    private final WebBackForwardList mBackForwardList;
+    // Used to call startActivity during url override.
+    private final Context mContext;
+
+    // Message Ids
+    private static final int PAGE_STARTED         = 100;
+    private static final int RECEIVED_ICON        = 101;
+    private static final int RECEIVED_TITLE       = 102;
+    private static final int OVERRIDE_URL         = 103;
+    private static final int AUTH_REQUEST         = 104;
+    private static final int SSL_ERROR            = 105;
+    private static final int PROGRESS             = 106;
+    private static final int UPDATE_VISITED       = 107;
+    private static final int LOAD_RESOURCE        = 108;
+    private static final int CREATE_WINDOW        = 109;
+    private static final int CLOSE_WINDOW         = 110;
+    private static final int SAVE_PASSWORD        = 111;
+    private static final int JS_ALERT             = 112;
+    private static final int JS_CONFIRM           = 113;
+    private static final int JS_PROMPT            = 114;
+    private static final int JS_UNLOAD            = 115;
+    private static final int ASYNC_KEYEVENTS      = 116;
+    private static final int TOO_MANY_REDIRECTS   = 117;
+    private static final int DOWNLOAD_FILE        = 118;
+    private static final int REPORT_ERROR         = 119;
+    private static final int RESEND_POST_DATA     = 120;
+    private static final int PAGE_FINISHED        = 121;
+    private static final int REQUEST_FOCUS        = 122;
+    private static final int SCALE_CHANGED        = 123;
+    private static final int RECEIVED_CERTIFICATE = 124;
+    private static final int SWITCH_OUT_HISTORY   = 125;
+
+    // Message triggered by the client to resume execution
+    private static final int NOTIFY               = 200;
+
+    // Result transportation object for returning results across thread
+    // boundaries.
+    private class ResultTransport<E> {
+        // Private result object
+        private E mResult;
+
+        public synchronized void setResult(E result) {
+            mResult = result;
+        }
+
+        public synchronized E getResult() {
+            return mResult;
+        }
+    }
+
+    /**
+     * Construct a new CallbackProxy.
+     */
+    public CallbackProxy(Context context, WebView w) {
+        // Used to start a default activity.
+        mContext = context;
+        mWebView = w;
+        mBackForwardList = new WebBackForwardList();
+    }
+
+    /**
+     * Set the WebViewClient.
+     * @param client An implementation of WebViewClient.
+     */
+    public void setWebViewClient(WebViewClient client) {
+        mWebViewClient = client;
+    }
+
+    /**
+     * Set the WebChromeClient.
+     * @param client An implementation of WebChromeClient.
+     */
+    public void setWebChromeClient(WebChromeClient client) {
+        mWebChromeClient = client;
+    }
+
+    /**
+     * Set the client DownloadListener.
+     * @param client An implementation of DownloadListener.
+     */
+    public void setDownloadListener(DownloadListener client) {
+        mDownloadListener = client;
+    }
+
+    /**
+     * Get the Back/Forward list to return to the user or to update the cached
+     * history list.
+     */
+    public WebBackForwardList getBackForwardList() {
+        return mBackForwardList;
+    }
+
+    /**
+     * Called by the UI side.  Calling overrideUrlLoading from the WebCore
+     * side will post a message to call this method.
+     */
+    public boolean uiOverrideUrlLoading(String overrideUrl) {
+        if (overrideUrl == null || overrideUrl.length() == 0) {
+            return false;
+        }
+        boolean override = false;
+        if (mWebViewClient != null) {
+            override = mWebViewClient.shouldOverrideUrlLoading(mWebView,
+                    overrideUrl);
+        } else {
+            Intent intent = new Intent(Intent.ACTION_VIEW,
+                    Uri.parse(overrideUrl));
+            intent.addCategory(Intent.CATEGORY_BROWSABLE);
+            try {
+                mContext.startActivity(intent);
+                override = true;
+            } catch (ActivityNotFoundException ex) {
+                // If no application can handle the URL, assume that the
+                // browser can handle it.
+            }
+        }
+        return override;
+    }
+
+    /**
+     * Called by UI side.
+     */
+    public boolean uiOverrideKeyEvent(KeyEvent event) {
+        if (mWebViewClient != null) {
+            return mWebViewClient.shouldOverrideKeyEvent(mWebView, event);
+        }
+        return false;
+    }
+
+    @Override
+    public void handleMessage(Message msg) {
+        // We don't have to do synchronization because this function operates
+        // in the UI thread. The WebViewClient and WebChromeClient functions
+        // that check for a non-null callback are ok because java ensures atomic
+        // 32-bit reads and writes.
+        switch (msg.what) {
+            case PAGE_STARTED:
+                if (mWebViewClient != null) {
+                    mWebViewClient.onPageStarted(mWebView,
+                            msg.getData().getString("url"),
+                            (Bitmap) msg.obj);
+                }
+                break;
+
+            case PAGE_FINISHED:
+                if (mWebViewClient != null) {
+                    mWebViewClient.onPageFinished(mWebView, (String) msg.obj);
+                }
+                break;
+                
+            case RECEIVED_ICON:
+                if (mWebChromeClient != null) {
+                    mWebChromeClient.onReceivedIcon(mWebView, (Bitmap) msg.obj);
+                }
+                break;
+
+            case RECEIVED_TITLE:
+                if (mWebChromeClient != null) {
+                    mWebChromeClient.onReceivedTitle(mWebView,
+                            (String) msg.obj);
+                }
+                break;
+
+            case TOO_MANY_REDIRECTS:
+                Message cancelMsg =
+                        (Message) msg.getData().getParcelable("cancelMsg");
+                Message continueMsg =
+                        (Message) msg.getData().getParcelable("continueMsg");
+                if (mWebViewClient != null) {
+                    mWebViewClient.onTooManyRedirects(mWebView, cancelMsg,
+                            continueMsg);
+                } else {
+                    cancelMsg.sendToTarget();
+                }
+                break;
+
+            case REPORT_ERROR:
+                if (mWebViewClient != null) {
+                    int reasonCode = msg.arg1;
+                    final String description  = msg.getData().getString("description");
+                    final String failUrl  = msg.getData().getString("failingUrl");
+                    mWebViewClient.onReceivedError(mWebView, reasonCode,
+                            description, failUrl);
+                }
+                break;
+
+            case RESEND_POST_DATA:
+                Message resend =
+                        (Message) msg.getData().getParcelable("resend");
+                Message dontResend =
+                        (Message) msg.getData().getParcelable("dontResend");
+                if (mWebViewClient != null) {
+                    mWebViewClient.onFormResubmission(mWebView, dontResend,
+                            resend);
+                } else {
+                    dontResend.sendToTarget();
+                }
+                break;
+
+            case OVERRIDE_URL:
+                String overrideUrl = msg.getData().getString("url");
+                boolean override = uiOverrideUrlLoading(overrideUrl);
+                ResultTransport<Boolean> result =
+                        (ResultTransport<Boolean>) msg.obj;
+                synchronized (this) {
+                    result.setResult(override);
+                    notify();
+                }
+                break;
+
+            case AUTH_REQUEST:
+                if (mWebViewClient != null) {
+                    HttpAuthHandler handler = (HttpAuthHandler) msg.obj;
+                    String host = msg.getData().getString("host");
+                    String realm = msg.getData().getString("realm");
+                    mWebViewClient.onReceivedHttpAuthRequest(mWebView, handler,
+                            host, realm);
+                }
+                break;
+
+            case SSL_ERROR:
+                if (mWebViewClient != null) {
+                    HashMap<String, Object> map = 
+                        (HashMap<String, Object>) msg.obj;
+                    mWebViewClient.onReceivedSslError(mWebView,
+                            (SslErrorHandler) map.get("handler"),
+                            (SslError) map.get("error"));
+                }
+                break;
+
+            case PROGRESS:
+                // Synchronize to ensure mLatestProgress is not modified after
+                // setProgress is called and before mProgressUpdatePending is
+                // changed.
+                synchronized (this) {
+                    if (mWebChromeClient != null) {
+                        mWebChromeClient.onProgressChanged(mWebView,
+                                mLatestProgress);
+                    }
+                    mProgressUpdatePending = false;
+                }
+                break;
+
+            case UPDATE_VISITED:
+                if (mWebViewClient != null) {
+                    mWebViewClient.doUpdateVisitedHistory(mWebView,
+                            (String) msg.obj, msg.arg1 != 0);
+                }
+                break;
+
+            case LOAD_RESOURCE:
+                if (mWebViewClient != null) {
+                    mWebViewClient.onLoadResource(mWebView, (String) msg.obj);
+                }
+                break;
+
+            case DOWNLOAD_FILE:
+                if (mDownloadListener != null) {
+                    String url = msg.getData().getString("url");
+                    String userAgent = msg.getData().getString("userAgent");
+                    String contentDisposition =
+                        msg.getData().getString("contentDisposition");
+                    String mimetype = msg.getData().getString("mimetype");
+                    Long contentLength = msg.getData().getLong("contentLength");
+
+                    mDownloadListener.onDownloadStart(url, userAgent,
+                            contentDisposition, mimetype, contentLength);
+                }
+                break;
+
+            case CREATE_WINDOW:
+                if (mWebChromeClient != null) {
+                    if (!mWebChromeClient.onCreateWindow(mWebView,
+                                msg.arg1 == 1, msg.arg2 == 1,
+                                (Message) msg.obj)) {
+                        synchronized (this) {
+                            notify();
+                        }
+                    }
+                }
+                break;
+
+            case REQUEST_FOCUS:
+                if (mWebChromeClient != null) {
+                    mWebChromeClient.onRequestFocus(mWebView);
+                }
+                break;
+
+            case CLOSE_WINDOW:
+                if (mWebChromeClient != null) {
+                    mWebChromeClient.onCloseWindow((WebView) msg.obj);
+                }
+                break;
+
+            case SAVE_PASSWORD:
+                Bundle bundle = msg.getData();
+                String schemePlusHost = bundle.getString("host");
+                String username = bundle.getString("username");
+                String password = bundle.getString("password");
+                // If the client returned false it means that the notify message
+                // will not be sent and we should notify WebCore ourselves.
+                if (!mWebView.onSavePassword(schemePlusHost, username, password,
+                            (Message) msg.obj)) {
+                    synchronized (this) {
+                        notify();
+                    }
+                }
+                break;
+
+            case ASYNC_KEYEVENTS:
+                if (mWebViewClient != null) {
+                    mWebViewClient.onUnhandledKeyEvent(mWebView,
+                            (KeyEvent) msg.obj);
+                }
+                break;
+
+            case JS_ALERT:
+                if (mWebChromeClient != null) {
+                    final JsResult res = (JsResult) msg.obj;
+                    String message = msg.getData().getString("message");
+                    String url = msg.getData().getString("url");
+                    if (!mWebChromeClient.onJsAlert(mWebView, url, message,
+                            res)) {
+                        new AlertDialog.Builder(mContext)
+                                .setTitle(getJsDialogTitle(url))
+                                .setMessage(message)
+                                .setPositiveButton(R.string.ok,
+                                        new AlertDialog.OnClickListener() {
+                                            public void onClick(
+                                                    DialogInterface dialog,
+                                                    int which) {
+                                                res.confirm();
+                                            }
+                                        })
+                                .setCancelable(false)
+                                .show();
+                    }
+                    res.setReady();
+                }
+                break;
+
+            case JS_CONFIRM:
+                if (mWebChromeClient != null) {
+                    final JsResult res = (JsResult) msg.obj;
+                    String message = msg.getData().getString("message");
+                    String url = msg.getData().getString("url");
+                    if (!mWebChromeClient.onJsConfirm(mWebView, url, message,
+                            res)) {
+                        new AlertDialog.Builder(mContext)
+                                .setTitle(getJsDialogTitle(url))
+                                .setMessage(message)
+                                .setPositiveButton(R.string.ok, 
+                                        new DialogInterface.OnClickListener() {
+                                            public void onClick(
+                                                    DialogInterface dialog,
+                                                    int which) {
+                                                res.confirm();
+                                            }})
+                                .setNegativeButton(R.string.cancel, 
+                                        new DialogInterface.OnClickListener() {
+                                            public void onClick(
+                                                    DialogInterface dialog,
+                                                    int which) {
+                                                res.cancel();
+                                            }})
+                                .show();
+                    }
+                    // Tell the JsResult that it is ready for client
+                    // interaction.
+                    res.setReady();
+                }
+                break;
+
+            case JS_PROMPT:
+                if (mWebChromeClient != null) {
+                    final JsPromptResult res = (JsPromptResult) msg.obj;
+                    String message = msg.getData().getString("message");
+                    String defaultVal = msg.getData().getString("default");
+                    String url = msg.getData().getString("url");
+                    if (!mWebChromeClient.onJsPrompt(mWebView, url, message,
+                                defaultVal, res)) {
+                        final LayoutInflater factory = LayoutInflater
+                                .from(mContext);
+                        final View view = factory.inflate(R.layout.js_prompt,
+                                null);
+                        final EditText v = (EditText) view
+                                .findViewById(R.id.value);
+                        v.setText(defaultVal);
+                        ((TextView) view.findViewById(R.id.message))
+                                .setText(message);
+                        new AlertDialog.Builder(mContext)
+                                .setTitle(getJsDialogTitle(url))
+                                .setView(view)
+                                .setPositiveButton(R.string.ok,
+                                        new DialogInterface.OnClickListener() {
+                                            public void onClick(
+                                                    DialogInterface dialog,
+                                                    int whichButton) {
+                                                res.confirm(v.getText()
+                                                        .toString());
+                                            }
+                                        })
+                                .setNegativeButton(R.string.cancel,
+                                        new DialogInterface.OnClickListener() {
+                                            public void onClick(
+                                                    DialogInterface dialog,
+                                                    int whichButton) {
+                                                res.cancel();
+                                            }
+                                        })
+                                .setOnCancelListener(
+                                        new DialogInterface.OnCancelListener() {
+                                            public void onCancel(
+                                                    DialogInterface dialog) {
+                                                res.cancel();
+                                            }
+                                        })
+                                .show();
+                    }
+                    // Tell the JsResult that it is ready for client
+                    // interaction.
+                    res.setReady();
+                }
+                break;
+
+            case JS_UNLOAD:
+                if (mWebChromeClient != null) {
+                    final JsResult res = (JsResult) msg.obj;
+                    String message = msg.getData().getString("message");
+                    String url = msg.getData().getString("url");
+                    if (!mWebChromeClient.onJsBeforeUnload(mWebView, url,
+                            message, res)) {
+                        final String m = mContext.getString(
+                                R.string.js_dialog_before_unload, message);
+                        new AlertDialog.Builder(mContext)
+                                .setMessage(m)
+                                .setPositiveButton(R.string.ok,
+                                        new DialogInterface.OnClickListener() {
+                                            public void onClick(
+                                                    DialogInterface dialog,
+                                                    int which) {
+                                                res.confirm();
+                                            }
+                                        })
+                                .setNegativeButton(R.string.cancel,
+                                        new DialogInterface.OnClickListener() {
+                                            public void onClick(
+                                                    DialogInterface dialog,
+                                                    int which) {
+                                                res.cancel();
+                                            }
+                                        })
+                                .show();
+                    }
+                    res.setReady();
+                }
+                break;
+
+            case RECEIVED_CERTIFICATE:
+                mWebView.setCertificate((SslCertificate) msg.obj);
+                break;
+
+            case NOTIFY:
+                synchronized (this) {
+                    notify();
+                }
+                break;
+
+            case SCALE_CHANGED:
+                if (mWebViewClient != null) {
+                    mWebViewClient.onScaleChanged(mWebView, msg.getData()
+                            .getFloat("old"), msg.getData().getFloat("new"));
+                }
+                break;
+
+            case SWITCH_OUT_HISTORY:
+                mWebView.switchOutDrawHistory();
+                break;
+        }
+    }
+
+    /**
+     * Return the latest progress.
+     */
+    public int getProgress() {
+        return mLatestProgress;
+    }
+
+    /**
+     * Called by WebCore side to switch out of history Picture drawing mode
+     */
+    void switchOutDrawHistory() {
+        sendMessage(obtainMessage(SWITCH_OUT_HISTORY));
+    }
+
+    private String getJsDialogTitle(String url) {
+        String title = url;
+        if (URLUtil.isDataUrl(url)) {
+            // For data: urls, we just display 'JavaScript' similar to Safari.
+            title = mContext.getString(R.string.js_dialog_title_default);
+        } else {
+            try {
+                URL aUrl = new URL(url);
+                // For example: "The page at 'http://www.mit.edu' says:"
+                title = mContext.getString(R.string.js_dialog_title,
+                        aUrl.getProtocol() + "://" + aUrl.getHost());
+            } catch (MalformedURLException ex) {
+                // do nothing. just use the url as the title
+            }
+        }
+        return title;
+    }
+
+    //--------------------------------------------------------------------------
+    // WebViewClient functions.
+    // NOTE: shouldOverrideKeyEvent is never called from the WebCore thread so
+    // it is not necessary to include it here.
+    //--------------------------------------------------------------------------
+
+    // Performance probe
+    private long mWebCoreThreadTime;
+
+    public void onPageStarted(String url, Bitmap favicon) {
+        // Do an unsynchronized quick check to avoid posting if no callback has
+        // been set.
+        if (mWebViewClient == null) {
+            return;
+        }
+        // Performance probe
+        if (false) {
+            mWebCoreThreadTime = SystemClock.currentThreadTimeMillis();
+            Network.getInstance(mContext).startTiming();
+        }
+        Message msg = obtainMessage(PAGE_STARTED);
+        msg.obj = favicon;
+        msg.getData().putString("url", url);
+        sendMessage(msg);
+    }
+
+    public void onPageFinished(String url) {
+        // Do an unsynchronized quick check to avoid posting if no callback has
+        // been set.
+        if (mWebViewClient == null) {
+            return;
+        }
+        // Performance probe
+        if (false) {
+            Log.d("WebCore", "WebCore thread used " + 
+                    (SystemClock.currentThreadTimeMillis() - mWebCoreThreadTime)
+                    + " ms");
+            Network.getInstance(mContext).stopTiming();
+        }
+        Message msg = obtainMessage(PAGE_FINISHED, url);
+        sendMessage(msg);
+    }
+
+    public void onTooManyRedirects(Message cancelMsg, Message continueMsg) {
+        // Do an unsynchronized quick check to avoid posting if no callback has
+        // been set.
+        if (mWebViewClient == null) {
+            cancelMsg.sendToTarget();
+            return;
+        }
+
+        Message msg = obtainMessage(TOO_MANY_REDIRECTS);
+        Bundle bundle = msg.getData();
+        bundle.putParcelable("cancelMsg", cancelMsg);
+        bundle.putParcelable("continueMsg", continueMsg);
+        sendMessage(msg);
+    }
+
+    public void onReceivedError(int errorCode, String description,
+            String failingUrl) {
+        // Do an unsynchronized quick check to avoid posting if no callback has
+        // been set.
+        if (mWebViewClient == null) {
+            return;
+        }
+
+        Message msg = obtainMessage(REPORT_ERROR);
+        msg.arg1 = errorCode;
+        msg.getData().putString("description", description);
+        msg.getData().putString("failingUrl", failingUrl);
+        sendMessage(msg);
+    }
+
+    public void onFormResubmission(Message dontResend, 
+            Message resend) {
+        // Do an unsynchronized quick check to avoid posting if no callback has
+        // been set.
+        if (mWebViewClient == null) {
+            dontResend.sendToTarget();
+            return;
+        }
+
+        Message msg = obtainMessage(RESEND_POST_DATA);
+        Bundle bundle = msg.getData();
+        bundle.putParcelable("resend", resend);
+        bundle.putParcelable("dontResend", dontResend);
+        sendMessage(msg);
+    }
+
+    /**
+     * Called by the WebCore side
+     */
+    public boolean shouldOverrideUrlLoading(String url) {
+        // We have a default behavior if no client exists so always send the
+        // message.
+        ResultTransport<Boolean> res = new ResultTransport<Boolean>();
+        Message msg = obtainMessage(OVERRIDE_URL);
+        msg.getData().putString("url", url);
+        msg.obj = res;
+        synchronized (this) {
+            sendMessage(msg);
+            try {
+                wait();
+            } catch (InterruptedException e) {
+                Log.e(LOGTAG, "Caught exception while waiting for overrideUrl");
+                Log.e(LOGTAG, Log.getStackTraceString(e));
+            }
+        }
+        return res.getResult().booleanValue();
+    }
+
+    public void onReceivedHttpAuthRequest(HttpAuthHandler handler,
+            String hostName, String realmName) {
+        // Do an unsynchronized quick check to avoid posting if no callback has
+        // been set.
+        if (mWebViewClient == null) {
+            handler.cancel();
+            return;
+        }
+        Message msg = obtainMessage(AUTH_REQUEST, handler);
+        msg.getData().putString("host", hostName);
+        msg.getData().putString("realm", realmName);
+        sendMessage(msg);
+    }
+    /**
+     * @hide - hide this because it contains a parameter of type SslError.
+     * SslError is located in a hidden package.
+     */
+    public void onReceivedSslError(SslErrorHandler handler, SslError error) {
+        // Do an unsynchronized quick check to avoid posting if no callback has
+        // been set.
+        if (mWebViewClient == null) {
+            handler.cancel();
+            return;
+        }
+        Message msg = obtainMessage(SSL_ERROR);
+        //, handler);
+        HashMap<String, Object> map = new HashMap();
+        map.put("handler", handler);
+        map.put("error", error);
+        msg.obj = map;
+        sendMessage(msg);
+    }
+    /**
+     * @hide - hide this because it contains a parameter of type SslCertificate,
+     * which is located in a hidden package.
+     */
+
+    public void onReceivedCertificate(SslCertificate certificate) {
+        // Do an unsynchronized quick check to avoid posting if no callback has
+        // been set.
+        if (mWebViewClient == null) {
+            return;
+        }
+        // here, certificate can be null (if the site is not secure)
+        sendMessage(obtainMessage(RECEIVED_CERTIFICATE, certificate));
+    }
+
+    public void doUpdateVisitedHistory(String url, boolean isReload) {
+        // Do an unsynchronized quick check to avoid posting if no callback has
+        // been set.
+        if (mWebViewClient == null) {
+            return;
+        }
+        sendMessage(obtainMessage(UPDATE_VISITED, isReload ? 1 : 0, 0, url));
+    }
+
+    public void onLoadResource(String url) {
+        // Do an unsynchronized quick check to avoid posting if no callback has
+        // been set.
+        if (mWebViewClient == null) {
+            return;
+        }
+        sendMessage(obtainMessage(LOAD_RESOURCE, url));
+    }
+
+    public void onUnhandledKeyEvent(KeyEvent event) {
+        // Do an unsynchronized quick check to avoid posting if no callback has
+        // been set.
+        if (mWebViewClient == null) {
+            return;
+        }
+        sendMessage(obtainMessage(ASYNC_KEYEVENTS, event));
+    }
+
+    public void onScaleChanged(float oldScale, float newScale) {
+        // Do an unsynchronized quick check to avoid posting if no callback has
+        // been set.
+        if (mWebViewClient == null) {
+            return;
+        }
+        Message msg = obtainMessage(SCALE_CHANGED);
+        Bundle bundle = msg.getData();
+        bundle.putFloat("old", oldScale);
+        bundle.putFloat("new", newScale);
+        sendMessage(msg);
+    }
+
+    //--------------------------------------------------------------------------
+    // DownloadListener functions.
+    //--------------------------------------------------------------------------
+
+    /**
+     * Starts a download if a download listener has been registered, otherwise
+     * return false.
+     */
+    public boolean onDownloadStart(String url, String userAgent,
+            String contentDisposition, String mimetype, long contentLength) {
+        // Do an unsynchronized quick check to avoid posting if no callback has
+        // been set.
+        if (mDownloadListener == null) {
+            // Cancel the download if there is no browser client.
+            return false;
+        }
+
+        Message msg = obtainMessage(DOWNLOAD_FILE);
+        Bundle bundle = msg.getData();
+        bundle.putString("url", url);
+        bundle.putString("userAgent", userAgent);
+        bundle.putString("mimetype", mimetype);
+        bundle.putLong("contentLength", contentLength);
+        bundle.putString("contentDisposition", contentDisposition);
+        sendMessage(msg);
+        return true;
+    }
+
+
+    //--------------------------------------------------------------------------
+    // WebView specific functions that do not interact with a client. These
+    // functions just need to operate within the UI thread.
+    //--------------------------------------------------------------------------
+
+    public boolean onSavePassword(String schemePlusHost, String username,
+            String password, Message resumeMsg) {
+        // resumeMsg should be null at this point because we want to create it
+        // within the CallbackProxy.
+        if (Config.DEBUG) {
+            junit.framework.Assert.assertNull(resumeMsg);
+        }
+        resumeMsg = obtainMessage(NOTIFY);
+
+        Message msg = obtainMessage(SAVE_PASSWORD, resumeMsg);
+        Bundle bundle = msg.getData();
+        bundle.putString("host", schemePlusHost);
+        bundle.putString("username", username);
+        bundle.putString("password", password);
+        synchronized (this) {
+            sendMessage(msg);
+            try {
+                wait();
+            } catch (InterruptedException e) {
+                Log.e(LOGTAG,
+                        "Caught exception while waiting for onSavePassword");
+                Log.e(LOGTAG, Log.getStackTraceString(e));
+            }
+        }
+        // Doesn't matter here
+        return false;
+    }
+
+    //--------------------------------------------------------------------------
+    // WebChromeClient methods
+    //--------------------------------------------------------------------------
+
+    public void onProgressChanged(int newProgress) {
+        // Synchronize so that mLatestProgress is up-to-date.
+        synchronized (this) {
+            mLatestProgress = newProgress;
+            if (mWebChromeClient == null) {
+                return;
+            }
+            if (!mProgressUpdatePending) {
+                sendEmptyMessage(PROGRESS);
+                mProgressUpdatePending = true;
+            }
+        }
+    }
+
+    public WebView createWindow(boolean dialog, boolean userGesture) {
+        // Do an unsynchronized quick check to avoid posting if no callback has
+        // been set.
+        if (mWebChromeClient == null) {
+            return null;
+        }
+
+        WebView.WebViewTransport transport = mWebView.new WebViewTransport();
+        final Message msg = obtainMessage(NOTIFY);
+        msg.obj = transport;
+        synchronized (this) {
+            sendMessage(obtainMessage(CREATE_WINDOW, dialog ? 1 : 0,
+                    userGesture ? 1 : 0, msg));
+            try {
+                wait();
+            } catch (InterruptedException e) {
+                Log.e(LOGTAG,
+                        "Caught exception while waiting for createWindow");
+                Log.e(LOGTAG, Log.getStackTraceString(e));
+            }
+        }
+
+        WebView w = transport.getWebView();
+        if (w != null) {
+            w.getWebViewCore().initializeSubwindow();
+        }
+        return w;
+    }
+
+    public void onRequestFocus() {
+        // Do an unsynchronized quick check to avoid posting if no callback has
+        // been set.
+        if (mWebChromeClient == null) {
+            return;
+        }
+
+        sendEmptyMessage(REQUEST_FOCUS);
+    }
+
+    public void onCloseWindow(WebView window) {
+        // Do an unsynchronized quick check to avoid posting if no callback has
+        // been set.
+        if (mWebChromeClient == null) {
+            return;
+        }
+        sendMessage(obtainMessage(CLOSE_WINDOW, window));
+    }
+
+    public void onReceivedIcon(Bitmap icon) {
+        // The current item might be null if the icon was already stored in the
+        // database and this is a new WebView.
+        WebHistoryItem i = mBackForwardList.getCurrentItem();
+        if (i != null) {
+            i.setFavicon(icon);
+        }
+        // Do an unsynchronized quick check to avoid posting if no callback has
+        // been set.
+        if (mWebChromeClient == null) {
+            return;
+        }
+        sendMessage(obtainMessage(RECEIVED_ICON, icon));
+    }
+
+    public void onReceivedTitle(String title) {
+        // Do an unsynchronized quick check to avoid posting if no callback has
+        // been set.
+        if (mWebChromeClient == null) {
+            return;
+        }
+        sendMessage(obtainMessage(RECEIVED_TITLE, title));
+    }
+
+    public void onJsAlert(String url, String message) {
+        // Do an unsynchronized quick check to avoid posting if no callback has
+        // been set.
+        if (mWebChromeClient == null) {
+            return;
+        }
+        JsResult result = new JsResult(this, false);
+        Message alert = obtainMessage(JS_ALERT, result);
+        alert.getData().putString("message", message);
+        alert.getData().putString("url", url);
+        synchronized (this) {
+            sendMessage(alert);
+            try {
+                wait();
+            } catch (InterruptedException e) {
+                Log.e(LOGTAG, "Caught exception while waiting for jsAlert");
+                Log.e(LOGTAG, Log.getStackTraceString(e));
+            }
+        }
+    }
+
+    public boolean onJsConfirm(String url, String message) {
+        // Do an unsynchronized quick check to avoid posting if no callback has
+        // been set.
+        if (mWebChromeClient == null) {
+            return false;
+        }
+        JsResult result = new JsResult(this, false);
+        Message confirm = obtainMessage(JS_CONFIRM, result);
+        confirm.getData().putString("message", message);
+        confirm.getData().putString("url", url);
+        synchronized (this) {
+            sendMessage(confirm);
+            try {
+                wait();
+            } catch (InterruptedException e) {
+                Log.e(LOGTAG, "Caught exception while waiting for jsConfirm");
+                Log.e(LOGTAG, Log.getStackTraceString(e));
+            }
+        }
+        return result.getResult();
+    }
+
+    public String onJsPrompt(String url, String message, String defaultValue) {
+        // Do an unsynchronized quick check to avoid posting if no callback has
+        // been set.
+        if (mWebChromeClient == null) {
+            return null;
+        }
+        JsPromptResult result = new JsPromptResult(this);
+        Message prompt = obtainMessage(JS_PROMPT, result);
+        prompt.getData().putString("message", message);
+        prompt.getData().putString("default", defaultValue);
+        prompt.getData().putString("url", url);
+        synchronized (this) {
+            sendMessage(prompt);
+            try {
+                wait();
+            } catch (InterruptedException e) {
+                Log.e(LOGTAG, "Caught exception while waiting for jsPrompt");
+                Log.e(LOGTAG, Log.getStackTraceString(e));
+            }
+        }
+        return result.getStringResult();
+    }
+
+    public boolean onJsBeforeUnload(String url, String message) {
+        // Do an unsynchronized quick check to avoid posting if no callback has
+        // been set.
+        if (mWebChromeClient == null) {
+            return true;
+        }
+        JsResult result = new JsResult(this, true);
+        Message confirm = obtainMessage(JS_UNLOAD, result);
+        confirm.getData().putString("message", message);
+        confirm.getData().putString("url", url);
+        synchronized (this) {
+            sendMessage(confirm);
+            try {
+                wait();
+            } catch (InterruptedException e) {
+                Log.e(LOGTAG, "Caught exception while waiting for jsUnload");
+                Log.e(LOGTAG, Log.getStackTraceString(e));
+            }
+        }
+        return result.getResult();
+    }
+}
diff --git a/core/java/android/webkit/ContentLoader.java b/core/java/android/webkit/ContentLoader.java
new file mode 100644
index 0000000..fb01c8c
--- /dev/null
+++ b/core/java/android/webkit/ContentLoader.java
@@ -0,0 +1,123 @@
+/*
+ * 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 android.webkit;
+
+import android.content.Context;
+import android.net.http.EventHandler;
+import android.net.http.Headers;
+import android.net.Uri;
+
+import java.io.File;
+import java.io.FileInputStream;
+
+/**
+ * This class is a concrete implementation of StreamLoader that loads
+ * "content:" URIs
+ */
+class ContentLoader extends StreamLoader {
+
+    private String mUrl;
+    private Context mContext;
+    private String mContentType;
+
+    /**
+     * Construct a ContentLoader with the specified content URI
+     *
+     * @param rawUrl "content:" url pointing to content to be loaded. This url
+     *               is the same url passed in to the WebView.
+     * @param loadListener LoadListener to pass the content to
+     * @param context Context to use to access the asset.
+     */
+    ContentLoader(String rawUrl, LoadListener loadListener, Context context) {
+        super(loadListener);
+        mContext = context;
+
+        /* strip off mimetype */
+        int mimeIndex = rawUrl.lastIndexOf('?');
+        if (mimeIndex != -1) {
+            mUrl = rawUrl.substring(0, mimeIndex);
+            mContentType = rawUrl.substring(mimeIndex + 1);
+        } else {
+            mUrl = rawUrl;
+        }
+
+    }
+
+    @Override
+    protected boolean setupStreamAndSendStatus() {
+        Uri uri = Uri.parse(mUrl);
+        if (uri == null) {
+            mHandler.error(
+                    EventHandler.FILE_NOT_FOUND_ERROR,
+                    mContext.getString(
+                            com.android.internal.R.string.httpErrorBadUrl) +
+                    " " + mUrl);
+            return false;
+        }
+
+        try {
+            mDataStream = mContext.getContentResolver().openInputStream(uri);
+            mHandler.status(1, 1, 0, "OK");
+        } catch (java.io.FileNotFoundException ex) {
+            mHandler.error(
+                    EventHandler.FILE_NOT_FOUND_ERROR,
+                    mContext.getString(
+                            com.android.internal.R.string.httpErrorFileNotFound) +
+                    " " + ex.getMessage());
+            return false;
+
+        } catch (java.io.IOException ex) {
+            mHandler.error(
+                    EventHandler.FILE_ERROR,
+                    mContext.getString(
+                            com.android.internal.R.string.httpErrorFileNotFound) +
+                    " " + ex.getMessage());
+            return false;
+        } catch (RuntimeException ex) {
+            // readExceptionWithFileNotFoundExceptionFromParcel in DatabaseUtils
+            // can throw a serial of RuntimeException. Catch them all here.
+            mHandler.error(
+                    EventHandler.FILE_ERROR,
+                    mContext.getString(
+                            com.android.internal.R.string.httpErrorFileNotFound) +
+                    " " + ex.getMessage());
+            return false;
+        }
+        return true;
+    }
+
+    @Override
+    protected void buildHeaders(Headers headers) {
+        if (mContentType != null) {
+            headers.setContentType("text/html");
+        }
+    }
+
+    /**
+     * Construct a ContentLoader and instruct it to start loading.
+     *
+     * @param url "content:" url pointing to content to be loaded
+     * @param loadListener LoadListener to pass the content to
+     * @param context Context to use to access the asset.
+     */
+    public static void requestUrl(String url, LoadListener loadListener,
+            Context context) {
+        ContentLoader loader = new ContentLoader(url, loadListener, context);
+        loader.load();
+    }
+
+}
diff --git a/core/java/android/webkit/CookieManager.java b/core/java/android/webkit/CookieManager.java
new file mode 100644
index 0000000..07c1a5d
--- /dev/null
+++ b/core/java/android/webkit/CookieManager.java
@@ -0,0 +1,934 @@
+/*
+ * 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 android.webkit;
+
+import android.net.ParseException;
+import android.net.WebAddress;
+import android.util.Config;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+/**
+ * CookieManager manages cookies according to RFC2109 spec.
+ */
+public final class CookieManager {
+
+    private static CookieManager sRef;
+
+    private static final String LOGTAG = "webkit";
+
+    private static final String DOMAIN = "domain";
+
+    private static final String PATH = "path";
+
+    private static final String EXPIRES = "expires";
+
+    private static final String SECURE = "secure";
+
+    private static final String MAX_AGE = "max-age";
+
+    private static final String HTTP_ONLY = "httponly";
+
+    private static final String HTTPS = "https";
+
+    private static final char PERIOD = '.';
+
+    private static final char COMMA = ',';
+
+    private static final char SEMICOLON = ';';
+
+    private static final char EQUAL = '=';
+
+    private static final char PATH_DELIM = '/';
+
+    private static final char QUESTION_MARK = '?';
+
+    private static final char WHITE_SPACE = ' ';
+
+    private static final char QUOTATION = '\"';
+
+    private static final int SECURE_LENGTH = SECURE.length();
+
+    private static final int HTTP_ONLY_LENGTH = HTTP_ONLY.length();
+
+    // RFC2109 defines 4k as maximum size of a cookie
+    private static final int MAX_COOKIE_LENGTH = 4 * 1024;
+
+    // RFC2109 defines 20 as max cookie count per domain. As we track with base
+    // domain, we allow 50 per base domain
+    private static final int MAX_COOKIE_COUNT_PER_BASE_DOMAIN = 50;
+
+    // RFC2109 defines 300 as max count of domains. As we track with base
+    // domain, we set 200 as max base domain count
+    private static final int MAX_DOMAIN_COUNT = 200;
+
+    // max cookie count to limit RAM cookie takes less than 100k, it is based on
+    // average cookie entry size is less than 100 bytes
+    private static final int MAX_RAM_COOKIES_COUNT = 1000;
+
+    //  max domain count to limit RAM cookie takes less than 100k,
+    private static final int MAX_RAM_DOMAIN_COUNT = 15;
+
+    private Map<String, ArrayList<Cookie>> mCookieMap = new LinkedHashMap
+            <String, ArrayList<Cookie>>(MAX_DOMAIN_COUNT, 0.75f, true);
+
+    private boolean mAcceptCookie = true;
+
+    /**
+     * This contains a list of 2nd-level domains that aren't allowed to have
+     * wildcards when combined with country-codes. For example: [.co.uk].
+     */
+    private final static String[] BAD_COUNTRY_2LDS =
+          { "ac", "co", "com", "ed", "edu", "go", "gouv", "gov", "info",
+            "lg", "ne", "net", "or", "org" };
+
+    static {
+        Arrays.sort(BAD_COUNTRY_2LDS);
+    }
+
+    /**
+     * Package level class to be accessed by cookie sync manager
+     */
+    static class Cookie {
+        static final byte MODE_NEW = 0;
+
+        static final byte MODE_NORMAL = 1;
+
+        static final byte MODE_DELETED = 2;
+
+        static final byte MODE_REPLACED = 3;
+
+        String domain;
+
+        String path;
+
+        String name;
+
+        String value;
+
+        long expires;
+
+        long lastAcessTime;
+
+        long lastUpdateTime;
+
+        boolean secure;
+
+        byte mode;
+
+        Cookie() {
+        }
+
+        Cookie(String defaultDomain, String defaultPath) {
+            domain = defaultDomain;
+            path = defaultPath;
+            expires = -1;
+        }
+
+        boolean exactMatch(Cookie in) {
+            return domain.equals(in.domain) && path.equals(in.path) &&
+                    name.equals(in.name);
+        }
+
+        boolean domainMatch(String urlHost) {
+            if (domain.startsWith(".")) {
+                if (urlHost.endsWith(domain.substring(1))) {
+                    int len = domain.length();
+                    int urlLen = urlHost.length();
+                    if (urlLen > len - 1) {
+                        // make sure bar.com doesn't match .ar.com
+                        return urlHost.charAt(urlLen - len) == PERIOD;
+                    }
+                    return true;
+                }
+                return false;
+            } else {
+                // exact match if domain is not leading w/ dot
+                return urlHost.equals(domain);
+            }
+        }
+
+        boolean pathMatch(String urlPath) {
+            if (urlPath.startsWith(path)) {
+                int len = path.length();
+                if (len == 0) {
+                    Log.w(LOGTAG, "Empty cookie path");
+                    return false;
+                }
+                int urlLen = urlPath.length();
+                if (path.charAt(len-1) != PATH_DELIM && urlLen > len) {
+                    // make sure /wee doesn't match /we
+                    return urlPath.charAt(len) == PATH_DELIM;
+                }
+                return true;
+            }
+            return false;
+        }
+
+        public String toString() {
+            return "domain: " + domain + "; path: " + path + "; name: " + name
+                    + "; value: " + value;
+        }
+    }
+
+    private CookieManager() {
+    }
+
+    protected Object clone() throws CloneNotSupportedException {
+        throw new CloneNotSupportedException("doesn't implement Cloneable");
+    }
+
+    /**
+     * Get a singleton CookieManager. If this is called before any
+     * {@link WebView} is created or outside of {@link WebView} context, the
+     * caller needs to call {@link CookieSyncManager#createInstance(Context)}
+     * first.
+     * 
+     * @return CookieManager
+=     */
+    public static synchronized CookieManager getInstance() {
+        if (sRef == null) {
+            sRef = new CookieManager();
+        }
+        return sRef;
+    }
+
+    /**
+     * Control whether cookie is enabled or disabled
+     * @param accept TRUE if accept cookie
+     */
+    public synchronized void setAcceptCookie(boolean accept) {
+        mAcceptCookie = accept;
+    }
+
+    /**
+     * Return whether cookie is enabled
+     * @return TRUE if accept cookie
+     */
+    public synchronized boolean acceptCookie() {
+        return mAcceptCookie;
+    }
+
+    /**
+     * Set cookie for a given url. The old cookie with same host/path/name will
+     * be removed. The new cookie will be added if it is not expired or it does
+     * not have expiration which implies it is session cookie.
+     * @param url The url which cookie is set for
+     * @param value The value for set-cookie: in http response header
+     */
+    public void setCookie(String url, String value) {
+        WebAddress uri;
+        try {
+            uri = new WebAddress(url);
+        } catch (ParseException ex) {
+            Log.e(LOGTAG, "Bad address: " + url);
+            return;
+        }
+        setCookie(uri, value);
+    }
+
+    /**
+     * Set cookie for a given uri. The old cookie with same host/path/name will
+     * be removed. The new cookie will be added if it is not expired or it does
+     * not have expiration which implies it is session cookie.
+     * @param uri The uri which cookie is set for
+     * @param value The value for set-cookie: in http response header
+     * @hide - hide this because it takes in a parameter of type WebAddress,
+     * a system private class.
+     */
+    public synchronized void setCookie(WebAddress uri, String value) {
+        if (value != null && value.length() > MAX_COOKIE_LENGTH) {
+            return;
+        }
+        if (!mAcceptCookie || uri == null) {
+            return;
+        }
+        if (Config.LOGV) {
+            Log.v(LOGTAG, "setCookie: uri: " + uri + " value: " + value);
+        }
+
+        String[] hostAndPath = getHostAndPath(uri);
+        if (hostAndPath == null) {
+            return;
+        }
+        
+        // For default path, when setting a cookie, the spec says:
+        //Path:   Defaults to the path of the request URL that generated the
+        // Set-Cookie response, up to, but not including, the
+        // right-most /.
+        if (hostAndPath[1].length() > 1) {
+            int index = hostAndPath[1].lastIndexOf(PATH_DELIM);
+            hostAndPath[1] = hostAndPath[1].substring(0, 
+                    index > 0 ? index : index + 1);
+        }
+
+        ArrayList<Cookie> cookies = null;
+        try {
+            cookies = parseCookie(hostAndPath[0], hostAndPath[1], value);
+        } catch (RuntimeException ex) {
+            Log.e(LOGTAG, "parse cookie failed for: " + value);
+        }
+
+        if (cookies == null || cookies.size() == 0) {
+            return;
+        }
+
+        String baseDomain = getBaseDomain(hostAndPath[0]);
+        ArrayList<Cookie> cookieList = mCookieMap.get(baseDomain);
+        if (cookieList == null) {
+            cookieList = CookieSyncManager.getInstance()
+                    .getCookiesForDomain(baseDomain);
+            mCookieMap.put(baseDomain, cookieList);
+        }
+
+        long now = System.currentTimeMillis();
+        int size = cookies.size();
+        for (int i = 0; i < size; i++) {
+            Cookie cookie = cookies.get(i);
+
+            boolean done = false;
+            Iterator<Cookie> iter = cookieList.iterator();
+            while (iter.hasNext()) {
+                Cookie cookieEntry = iter.next();
+                if (cookie.exactMatch(cookieEntry)) {
+                    // expires == -1 means no expires defined. Otherwise
+                    // negative means far future
+                    if (cookie.expires < 0 || cookie.expires > now) {
+                        // secure cookies can't be overwritten by non-HTTPS url
+                        if (!cookieEntry.secure || HTTPS.equals(uri.mScheme)) {
+                            cookieEntry.value = cookie.value;
+                            cookieEntry.expires = cookie.expires;
+                            cookieEntry.secure = cookie.secure;
+                            cookieEntry.lastAcessTime = now;
+                            cookieEntry.lastUpdateTime = now;
+                            cookieEntry.mode = Cookie.MODE_REPLACED;
+                        }
+                    } else {
+                        cookieEntry.lastUpdateTime = now;
+                        cookieEntry.mode = Cookie.MODE_DELETED;
+                    }
+                    done = true;
+                    break;
+                }
+            }
+
+            // expires == -1 means no expires defined. Otherwise negative means
+            // far future
+            if (!done && (cookie.expires < 0 || cookie.expires > now)) {
+                cookie.lastAcessTime = now;
+                cookie.lastUpdateTime = now;
+                cookie.mode = Cookie.MODE_NEW;
+                if (cookieList.size() > MAX_COOKIE_COUNT_PER_BASE_DOMAIN) {
+                    Cookie toDelete = new Cookie();
+                    toDelete.lastAcessTime = now;
+                    Iterator<Cookie> iter2 = cookieList.iterator();
+                    while (iter2.hasNext()) {
+                        Cookie cookieEntry2 = iter2.next();
+                        if ((cookieEntry2.lastAcessTime < toDelete.lastAcessTime)
+                                && cookieEntry2.mode != Cookie.MODE_DELETED) {
+                            toDelete = cookieEntry2;
+                        }
+                    }
+                    toDelete.mode = Cookie.MODE_DELETED;
+                }
+                cookieList.add(cookie);
+            }
+        }
+    }
+
+    /**
+     * Get cookie(s) for a given url so that it can be set to "cookie:" in http
+     * request header.
+     * @param url The url needs cookie
+     * @return The cookies in the format of NAME=VALUE [; NAME=VALUE]
+     */
+    public String getCookie(String url) {
+        WebAddress uri;
+        try {
+            uri = new WebAddress(url);
+        } catch (ParseException ex) {
+            Log.e(LOGTAG, "Bad address: " + url);
+            return null;
+        }
+        return getCookie(uri);
+    }
+
+    /**
+     * Get cookie(s) for a given uri so that it can be set to "cookie:" in http
+     * request header.
+     * @param uri The uri needs cookie
+     * @return The cookies in the format of NAME=VALUE [; NAME=VALUE]
+     * @hide - hide this because it has a parameter of type WebAddress, which
+     * is a system private class.
+     */
+    public synchronized String getCookie(WebAddress uri) {
+        if (!mAcceptCookie || uri == null) {
+            return null;
+        }
+   
+        String[] hostAndPath = getHostAndPath(uri);
+        if (hostAndPath == null) {
+            return null;
+        }
+
+        String baseDomain = getBaseDomain(hostAndPath[0]);
+        ArrayList<Cookie> cookieList = mCookieMap.get(baseDomain);
+        if (cookieList == null) {
+            cookieList = CookieSyncManager.getInstance()
+                    .getCookiesForDomain(baseDomain);
+            mCookieMap.put(baseDomain, cookieList);
+        }
+
+        long now = System.currentTimeMillis();
+        boolean secure = HTTPS.equals(uri.mScheme);
+        Iterator<Cookie> iter = cookieList.iterator();
+        StringBuilder ret = new StringBuilder(256);
+
+        while (iter.hasNext()) {
+            Cookie cookie = iter.next();
+            if (cookie.domainMatch(hostAndPath[0]) &&
+                    cookie.pathMatch(hostAndPath[1])
+                    // expires == -1 means no expires defined. Otherwise
+                    // negative means far future
+                    && (cookie.expires < 0 || cookie.expires > now)
+                    && (!cookie.secure || secure)
+                    && cookie.mode != Cookie.MODE_DELETED) {
+                cookie.lastAcessTime = now;
+
+                if (ret.length() > 0) {
+                    ret.append(SEMICOLON);
+                    // according to RC2109, SEMICOLON is office separator,
+                    // but when log in yahoo.com, it needs WHITE_SPACE too.
+                    ret.append(WHITE_SPACE);
+                }
+
+                ret.append(cookie.name);
+                ret.append(EQUAL);
+                ret.append(cookie.value);
+            }
+        }
+        if (ret.length() > 0) {
+            if (Config.LOGV) {
+                Log.v(LOGTAG, "getCookie: uri: " + uri + " value: " + ret);
+            }
+            return ret.toString();
+        } else {
+            if (Config.LOGV) {
+                Log.v(LOGTAG, "getCookie: uri: " + uri
+                        + " But can't find cookie.");
+            }
+            return null;
+        }
+    }
+
+    /**
+     * Remove all session cookies, which are cookies without expiration date
+     */
+    public void removeSessionCookie() {
+        final Runnable clearCache = new Runnable() {
+            public void run() {
+                synchronized(CookieManager.this) {
+                    Collection<ArrayList<Cookie>> cookieList = mCookieMap.values();
+                    Iterator<ArrayList<Cookie>> listIter = cookieList.iterator();
+                    while (listIter.hasNext()) {
+                        ArrayList<Cookie> list = listIter.next();
+                        Iterator<Cookie> iter = list.iterator();
+                        while (iter.hasNext()) {
+                            Cookie cookie = iter.next();
+                            if (cookie.expires == -1) {
+                                iter.remove();
+                            }
+                        }
+                    }
+                    CookieSyncManager.getInstance().clearSessionCookies();
+                }
+            }
+        };
+        new Thread(clearCache).start();
+    }
+
+    /**
+     * Remove all cookies
+     */
+    public void removeAllCookie() {
+        final Runnable clearCache = new Runnable() {
+            public void run() {
+                synchronized(CookieManager.this) {
+                    mCookieMap = new LinkedHashMap<String, ArrayList<Cookie>>(
+                            MAX_DOMAIN_COUNT, 0.75f, true);
+                    CookieSyncManager.getInstance().clearAllCookies();
+                }
+            }
+        };
+        new Thread(clearCache).start();
+    }
+
+    /**
+     *  Return true if there are stored cookies.
+     */
+    public synchronized boolean hasCookies() {
+        return CookieSyncManager.getInstance().hasCookies();
+    }
+
+    /**
+     * Remove all expired cookies
+     */
+    public void removeExpiredCookie() {
+        final Runnable clearCache = new Runnable() {
+            public void run() {
+                synchronized(CookieManager.this) {
+                    long now = System.currentTimeMillis();
+                    Collection<ArrayList<Cookie>> cookieList = mCookieMap.values();
+                    Iterator<ArrayList<Cookie>> listIter = cookieList.iterator();
+                    while (listIter.hasNext()) {
+                        ArrayList<Cookie> list = listIter.next();
+                        Iterator<Cookie> iter = list.iterator();
+                        while (iter.hasNext()) {
+                            Cookie cookie = iter.next();
+                            // expires == -1 means no expires defined. Otherwise 
+                            // negative means far future
+                            if (cookie.expires > 0 && cookie.expires < now) {
+                                iter.remove();
+                            }
+                        }
+                    }
+                    CookieSyncManager.getInstance().clearExpiredCookies(now);
+                }
+            }
+        };
+        new Thread(clearCache).start();
+    }
+
+    /**
+     * Package level api, called from CookieSyncManager
+     *
+     * Get a list of cookies which are updated since a given time.
+     * @param last The given time in millisec
+     * @return A list of cookies
+     */
+    synchronized ArrayList<Cookie> getUpdatedCookiesSince(long last) {
+        ArrayList<Cookie> cookies = new ArrayList<Cookie>();
+        Collection<ArrayList<Cookie>> cookieList = mCookieMap.values();
+        Iterator<ArrayList<Cookie>> listIter = cookieList.iterator();
+        while (listIter.hasNext()) {
+            ArrayList<Cookie> list = listIter.next();
+            Iterator<Cookie> iter = list.iterator();
+            while (iter.hasNext()) {
+                Cookie cookie = iter.next();
+                if (cookie.lastUpdateTime > last) {
+                    cookies.add(cookie);
+                }
+            }
+        }
+        return cookies;
+    }
+
+    /**
+     * Package level api, called from CookieSyncManager
+     *
+     * Delete a Cookie in the RAM
+     * @param cookie Cookie to be deleted
+     */
+    synchronized void deleteACookie(Cookie cookie) {
+        if (cookie.mode == Cookie.MODE_DELETED) {
+            String baseDomain = getBaseDomain(cookie.domain);
+            ArrayList<Cookie> cookieList = mCookieMap.get(baseDomain);
+            if (cookieList != null) {
+                cookieList.remove(cookie);
+                if (cookieList.isEmpty()) {
+                    mCookieMap.remove(baseDomain);
+                }
+            }
+        }
+    }
+
+    /**
+     * Package level api, called from CookieSyncManager
+     *
+     * Called after a cookie is synced to FLASH
+     * @param cookie Cookie to be synced
+     */
+    synchronized void syncedACookie(Cookie cookie) {
+        cookie.mode = Cookie.MODE_NORMAL;
+    }
+
+    /**
+     * Package level api, called from CookieSyncManager
+     *
+     * Delete the least recent used domains if the total cookie count in RAM
+     * exceeds the limit
+     * @return A list of cookies which are removed from RAM
+     */
+    synchronized ArrayList<Cookie> deleteLRUDomain() {
+        int count = 0;
+        int byteCount = 0;
+        int mapSize = mCookieMap.size();
+
+        if (mapSize < MAX_RAM_DOMAIN_COUNT) {
+            Collection<ArrayList<Cookie>> cookieLists = mCookieMap.values();
+            Iterator<ArrayList<Cookie>> listIter = cookieLists.iterator();
+            while (listIter.hasNext() && count < MAX_RAM_COOKIES_COUNT) {
+                ArrayList<Cookie> list = listIter.next();
+                if (Config.DEBUG) {
+                    Iterator<Cookie> iter = list.iterator();
+                    while (iter.hasNext() && count < MAX_RAM_COOKIES_COUNT) {
+                        Cookie cookie = iter.next();
+                        // 14 is 3 * sizeof(long) + sizeof(boolean)
+                        // + sizeof(byte)
+                        byteCount += cookie.domain.length()
+                                + cookie.path.length()
+                                + cookie.name.length()
+                                + cookie.value.length() + 14;
+                        count++;
+                    }
+                } else {
+                    count += list.size();
+                }
+            }
+        }
+
+        ArrayList<Cookie> retlist = new ArrayList<Cookie>();
+        if (mapSize >= MAX_RAM_DOMAIN_COUNT || count >= MAX_RAM_COOKIES_COUNT) {
+            if (Config.DEBUG) {
+                Log.v(LOGTAG, count + " cookies used " + byteCount
+                        + " bytes with " + mapSize + " domains");
+            }
+            Object[] domains = mCookieMap.keySet().toArray();
+            int toGo = mapSize / 10 + 1;
+            while (toGo-- > 0){
+                String domain = domains[toGo].toString();
+                if (Config.LOGV) {
+                    Log.v(LOGTAG, "delete domain: " + domain
+                            + " from RAM cache");
+                }
+                retlist.addAll(mCookieMap.get(domain));
+                mCookieMap.remove(domain);
+            }
+        }
+        return retlist;
+    }
+
+    /**
+     * Extract the host and path out of a uri
+     * @param uri The given WebAddress
+     * @return The host and path in the format of String[], String[0] is host
+     *          which has at least two periods, String[1] is path which always
+     *          ended with "/"
+     */
+    private String[] getHostAndPath(WebAddress uri) {
+        if (uri.mHost != null && uri.mPath != null) {
+            String[] ret = new String[2];
+            ret[0] = uri.mHost;
+            ret[1] = uri.mPath;
+
+            int index = ret[0].indexOf(PERIOD);
+            if (index == -1) {
+                if (uri.mScheme.equalsIgnoreCase("file")) {
+                    // There is a potential bug where a local file path matches
+                    // another file in the local web server directory. Still
+                    // "localhost" is the best pseudo domain name.
+                    ret[0] = "localhost";
+                } else if (!ret[0].equals("localhost")) {
+                    return null;
+                }
+            } else if (index == ret[0].lastIndexOf(PERIOD)) {
+                // cookie host must have at least two periods
+                ret[0] = PERIOD + ret[0];
+            }
+
+            if (ret[1].charAt(0) != PATH_DELIM) {
+                return null;
+            }
+
+            /*
+             * find cookie path, e.g. for http://www.google.com, the path is "/"
+             * for http://www.google.com/lab/, the path is "/lab"
+             * for http://www.google.com/lab/foo, the path is "/lab/foo"
+             * for http://www.google.com/lab?hl=en, the path is "/lab"
+             * for http://www.google.com/lab.asp?hl=en, the path is "/lab.asp"
+             * Note: the path from URI has at least one "/"
+             * See:
+             * http://www.unix.com.ua/rfc/rfc2109.html
+             */
+            index = ret[1].indexOf(QUESTION_MARK);
+            if (index != -1) {
+                ret[1] = ret[1].substring(0, index);
+            }
+            return ret;
+        } else
+            return null;
+    }
+
+    /**
+     * Get the base domain for a give host. E.g. mail.google.com will return
+     * google.com
+     * @param host The give host
+     * @return the base domain
+     */
+    private String getBaseDomain(String host) {
+        int startIndex = 0;
+        int nextIndex = host.indexOf(PERIOD);
+        int lastIndex = host.lastIndexOf(PERIOD);
+        while (nextIndex < lastIndex) {
+            startIndex = nextIndex + 1;
+            nextIndex = host.indexOf(PERIOD, startIndex);
+        }
+        if (startIndex > 0) {
+            return host.substring(startIndex);
+        } else {
+            return host;
+        }
+    }
+
+    /**
+     * parseCookie() parses the cookieString which is a comma-separated list of
+     * one or more cookies in the format of "NAME=VALUE; expires=DATE;
+     * path=PATH; domain=DOMAIN_NAME; secure httponly" to a list of Cookies.
+     * Here is a sample: IGDND=1, IGPC=ET=UB8TSNwtDmQ:AF=0; expires=Sun,
+     * 17-Jan-2038 19:14:07 GMT; path=/ig; domain=.google.com, =,
+     * PREF=ID=408909b1b304593d:TM=1156459854:LM=1156459854:S=V-vCAU6Sh-gobCfO;
+     * expires=Sun, 17-Jan-2038 19:14:07 GMT; path=/; domain=.google.com which
+     * contains 3 cookies IGDND, IGPC, PREF and an empty cookie
+     * @param host The default host
+     * @param path The default path
+     * @param cookieString The string coming from "Set-Cookie:"
+     * @return A list of Cookies
+     */
+    private ArrayList<Cookie> parseCookie(String host, String path,
+            String cookieString) {
+        ArrayList<Cookie> ret = new ArrayList<Cookie>();
+
+        int index = 0;
+        int length = cookieString.length();
+        while (true) {
+            Cookie cookie = null;
+
+            // done
+            if (index < 0 || index >= length) {
+                break;
+            }
+
+            // skip white space
+            if (cookieString.charAt(index) == WHITE_SPACE) {
+                index++;
+                continue;
+            }
+
+            /*
+             * get NAME=VALUE; pair. detecting the end of a pair is tricky, it
+             * can be the end of a string, like "foo=bluh", it can be semicolon
+             * like "foo=bluh;path=/"; or it can be enclosed by \", like
+             * "foo=\"bluh bluh\";path=/"
+             *
+             * Note: in the case of "foo=bluh, bar=bluh;path=/", we interpret
+             * it as one cookie instead of two cookies.
+             */
+            int equalIndex = cookieString.indexOf(EQUAL, index);
+            if (equalIndex == -1) {
+                // bad format, force return
+                break;
+            }
+            cookie = new Cookie(host, path);
+            cookie.name = cookieString.substring(index, equalIndex);
+            if (cookieString.charAt(equalIndex + 1) == QUOTATION) {
+                index = cookieString.indexOf(QUOTATION, equalIndex + 2);
+                if (index == -1) {
+                    // bad format, force return
+                    break;
+                }
+            }
+            int semicolonIndex = cookieString.indexOf(SEMICOLON, index);
+            if (semicolonIndex == -1) {
+                semicolonIndex = length;
+            }
+            if (semicolonIndex - equalIndex > MAX_COOKIE_LENGTH) {
+                // cookie is too big, trim it
+                cookie.value = cookieString.substring(equalIndex + 1,
+                        equalIndex + MAX_COOKIE_LENGTH);
+            } else if (equalIndex + 1 == semicolonIndex
+                    || semicolonIndex < equalIndex) {
+                // these are unusual case like foo=; and foo; path=/
+                cookie.value = "";
+            } else {
+                cookie.value = cookieString.substring(equalIndex + 1,
+                        semicolonIndex);
+            }
+            // get attributes
+            index = semicolonIndex;
+            while (true) {
+                // done
+                if (index < 0 || index >= length) {
+                    break;
+                }
+
+                // skip white space and semicolon
+                if (cookieString.charAt(index) == WHITE_SPACE
+                        || cookieString.charAt(index) == SEMICOLON) {
+                    index++;
+                    continue;
+                }
+
+                // comma means next cookie
+                if (cookieString.charAt(index) == COMMA) {
+                    index++;
+                    break;
+                }
+
+                // "secure" is a known attribute doesn't use "=";
+                // while sites like live.com uses "secure="
+                if (length - index > SECURE_LENGTH
+                        && cookieString.substring(index, index + SECURE_LENGTH).
+                        equalsIgnoreCase(SECURE)) {
+                    index += SECURE_LENGTH;
+                    cookie.secure = true;
+                    if (cookieString.charAt(index) == EQUAL) index++;
+                    continue;
+                }
+
+                // "httponly" is a known attribute doesn't use "=";
+                // while sites like live.com uses "httponly="
+                if (length - index > HTTP_ONLY_LENGTH
+                        && cookieString.substring(index,
+                            index + HTTP_ONLY_LENGTH).
+                        equalsIgnoreCase(HTTP_ONLY)) {
+                    index += HTTP_ONLY_LENGTH;
+                    if (cookieString.charAt(index) == EQUAL) index++;
+                    // FIXME: currently only parse the attribute
+                    continue;
+                }
+                equalIndex = cookieString.indexOf(EQUAL, index);
+                if (equalIndex > 0) {
+                    String name = cookieString.substring(index, equalIndex)
+                            .toLowerCase();
+                    if (name.equals(EXPIRES)) {
+                        int comaIndex = cookieString.indexOf(COMMA, equalIndex);
+
+                        // skip ',' in (Wdy, DD-Mon-YYYY HH:MM:SS GMT) or
+                        // (Weekday, DD-Mon-YY HH:MM:SS GMT) if it applies.
+                        // "Wednesday" is the longest Weekday which has length 9
+                        if ((comaIndex != -1) &&
+                                (comaIndex - equalIndex <= 10)) {
+                            index = comaIndex + 1;
+                        }
+                    }
+                    semicolonIndex = cookieString.indexOf(SEMICOLON, index);
+                    int commaIndex = cookieString.indexOf(COMMA, index);
+                    if (semicolonIndex == -1 && commaIndex == -1) {
+                        index = length;
+                    } else if (semicolonIndex == -1) {
+                        index = commaIndex;
+                    } else if (commaIndex == -1) {
+                        index = semicolonIndex;
+                    } else {
+                        index = Math.min(semicolonIndex, commaIndex);
+                    }
+                    String value =
+                            cookieString.substring(equalIndex + 1, index);
+                    
+                    // Strip quotes if they exist
+                    if (value.length() > 2 && value.charAt(0) == QUOTATION) {
+                        int endQuote = value.indexOf(QUOTATION, 1);
+                        if (endQuote > 0) {
+                            value = value.substring(1, endQuote);
+                        }
+                    }
+                    if (name.equals(EXPIRES)) {
+                        try {
+                            cookie.expires = HttpDateTime.parse(value);
+                        } catch (IllegalArgumentException ex) {
+                            Log.e(LOGTAG,
+                                    "illegal format for expires: " + value);
+                        }
+                    } else if (name.equals(MAX_AGE)) {
+                        try {
+                            cookie.expires = System.currentTimeMillis() + 1000
+                                    * Long.parseLong(value);
+                        } catch (NumberFormatException ex) {
+                            Log.e(LOGTAG,
+                                    "illegal format for max-age: " + value);
+                        }
+                    } else if (name.equals(PATH)) {
+                        // only allow non-empty path value
+                        if (value.length() > 0) {
+                            cookie.path = value;
+                        }
+                    } else if (name.equals(DOMAIN)) {
+                        int lastPeriod = value.lastIndexOf(PERIOD);
+                        if (lastPeriod == 0) {
+                            // disallow cookies set for TLDs like [.com]
+                            cookie.domain = null;
+                            continue;
+                        }
+                        try {
+                            Integer.parseInt(value.substring(lastPeriod + 1));
+                            // no wildcard for ip address match
+                            if (!value.equals(host)) {
+                                // no cross-site cookie
+                                cookie.domain = null;
+                            }
+                            continue;
+                        } catch (NumberFormatException ex) {
+                            // ignore the exception, value is a host name
+                        }
+                        value = value.toLowerCase();
+                        if (value.charAt(0) != PERIOD) {
+                            // pre-pended dot to make it as a domain cookie
+                            value = PERIOD + value;
+                            lastPeriod++;
+                        }
+                        if (host.endsWith(value.substring(1))) {
+                            int len = value.length();
+                            int hostLen = host.length();
+                            if (hostLen > (len - 1)
+                                    && host.charAt(hostLen - len) != PERIOD) {
+                                // make sure the bar.com doesn't match .ar.com
+                                cookie.domain = null;
+                                continue;
+                            }
+                            // disallow cookies set on ccTLDs like [.co.uk]
+                            if ((len == lastPeriod + 3)
+                                    && (len >= 6 && len <= 8)) {
+                                String s = value.substring(1, lastPeriod);
+                                if (Arrays.binarySearch(BAD_COUNTRY_2LDS, s) >= 0) {
+                                    cookie.domain = null;
+                                    continue;
+                                }
+                            }
+                            cookie.domain = value;
+                        } else {
+                            // no cross-site or more specific sub-domain cookie
+                            cookie.domain = null;
+                        }
+                    }
+                } else {
+                    // bad format, force return
+                    index = length;
+                }
+            }
+            if (cookie != null && cookie.domain != null) {
+                ret.add(cookie);
+            }
+        }
+        return ret;
+    }
+}
diff --git a/core/java/android/webkit/CookieSyncManager.java b/core/java/android/webkit/CookieSyncManager.java
new file mode 100644
index 0000000..f2511d8
--- /dev/null
+++ b/core/java/android/webkit/CookieSyncManager.java
@@ -0,0 +1,205 @@
+/*
+ * Copyright (C) 2007 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 android.webkit;
+
+import android.content.Context;
+import android.util.Config;
+import android.util.Log;
+import android.webkit.CookieManager.Cookie;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+
+/**
+ * The class CookieSyncManager is used to synchronize the browser cookies
+ * between RAM and FLASH. To get the best performance, browser cookie is saved
+ * in RAM. We use a separate thread to sync the cookies between RAM and FLASH on
+ * a timer base.
+ * <p>
+ * To use the CookieSyncManager, the host application has to call the following
+ * when the application starts.
+ * <p>
+ * CookieSyncManager.createInstance(context)
+ * <p>
+ * To set up for sync, the host application has to call
+ * <p>
+ * CookieSyncManager.getInstance().startSync()
+ * <p>
+ * in its Activity.onResume(), and call
+ * <p>
+ * CookieSyncManager.getInstance().stopSync()
+ * <p>
+ * in its Activity.onStop().
+ * <p>
+ * To get instant sync instead of waiting for the timer to trigger, the host can
+ * call
+ * <p>
+ * CookieSyncManager.getInstance().sync()
+ */
+public final class CookieSyncManager extends WebSyncManager {
+
+    private static CookieSyncManager sRef;
+
+    // time when last update happened
+    private long mLastUpdate;
+
+    private CookieSyncManager(Context context) {
+        super(context, "CookieSyncManager");
+    }
+
+    /**
+     * Singleton access to a {@link CookieSyncManager}. An
+     * IllegalStateException will be thrown if
+     * {@link CookieSyncManager#createInstance(Context)} is not called before.
+     * 
+     * @return CookieSyncManager
+     */
+    public static synchronized CookieSyncManager getInstance() {
+        if (sRef == null) {
+            throw new IllegalStateException(
+                    "CookieSyncManager::createInstance() needs to be called "
+                            + "before CookieSyncManager::getInstance()");
+        }
+        return sRef;
+    }
+
+    /**
+     * Create a singleton CookieSyncManager within a context
+     * @param context
+     * @return CookieSyncManager
+     */
+    public static synchronized CookieSyncManager createInstance(
+            Context context) {
+        if (sRef == null) {
+            sRef = new CookieSyncManager(context);
+        }
+        return sRef;
+    }
+
+    /**
+     * Package level api, called from CookieManager Get all the cookies which
+     * matches a given base domain.
+     * @param domain
+     * @return A list of Cookie
+     */
+    ArrayList<Cookie> getCookiesForDomain(String domain) {
+        // null mDataBase implies that the host application doesn't support
+        // persistent cookie. No sync needed.
+        if (mDataBase == null) {
+            return new ArrayList<Cookie>();
+        }
+
+        return mDataBase.getCookiesForDomain(domain);
+    }
+
+    /**
+     * Package level api, called from CookieManager Clear all cookies in the
+     * database
+     */
+    void clearAllCookies() {
+        // null mDataBase implies that the host application doesn't support
+        // persistent cookie.
+        if (mDataBase == null) {
+            return;
+        }
+
+        mDataBase.clearCookies();
+    }
+
+    /**
+     * Returns true if there are any saved cookies.
+     */
+    boolean hasCookies() {
+        // null mDataBase implies that the host application doesn't support
+        // persistent cookie.
+        if (mDataBase == null) {
+            return false;
+        }
+
+        return mDataBase.hasCookies();
+    }
+
+    /**
+     * Package level api, called from CookieManager Clear all session cookies in
+     * the database
+     */
+    void clearSessionCookies() {
+        // null mDataBase implies that the host application doesn't support
+        // persistent cookie.
+        if (mDataBase == null) {
+            return;
+        }
+
+        mDataBase.clearSessionCookies();
+    }
+
+    /**
+     * Package level api, called from CookieManager Clear all expired cookies in
+     * the database
+     */
+    void clearExpiredCookies(long now) {
+        // null mDataBase implies that the host application doesn't support
+        // persistent cookie.
+        if (mDataBase == null) {
+            return;
+        }
+
+        mDataBase.clearExpiredCookies(now);
+    }
+
+    protected void syncFromRamToFlash() {
+        if (Config.LOGV) {
+            Log.v(LOGTAG, "CookieSyncManager::syncFromRamToFlash STARTS");
+        }
+
+        if (!CookieManager.getInstance().acceptCookie()) {
+            return;
+        }
+
+        ArrayList<Cookie> cookieList = CookieManager.getInstance()
+                .getUpdatedCookiesSince(mLastUpdate);
+        mLastUpdate = System.currentTimeMillis();
+        syncFromRamToFlash(cookieList);
+
+        ArrayList<Cookie> lruList =
+                CookieManager.getInstance().deleteLRUDomain();
+        syncFromRamToFlash(lruList);
+
+        if (Config.LOGV) {
+            Log.v(LOGTAG, "CookieSyncManager::syncFromRamToFlash DONE");
+        }
+    }
+
+    private void syncFromRamToFlash(ArrayList<Cookie> list) {
+        Iterator<Cookie> iter = list.iterator();
+        while (iter.hasNext()) {
+            Cookie cookie = iter.next();
+            if (cookie.mode != Cookie.MODE_NORMAL) {
+                if (cookie.mode != Cookie.MODE_NEW) {
+                    mDataBase.deleteCookies(cookie.domain, cookie.path,
+                            cookie.name);
+                }
+                if (cookie.mode != Cookie.MODE_DELETED) {
+                    mDataBase.addCookie(cookie);
+                    CookieManager.getInstance().syncedACookie(cookie);
+                } else {
+                    CookieManager.getInstance().deleteACookie(cookie);
+                }
+            }
+        }
+    }
+}
diff --git a/core/java/android/webkit/DataLoader.java b/core/java/android/webkit/DataLoader.java
new file mode 100644
index 0000000..dcdc949
--- /dev/null
+++ b/core/java/android/webkit/DataLoader.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2007 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 android.webkit;
+
+import org.apache.http.protocol.HTTP;
+
+import android.net.http.Headers;
+
+import java.io.ByteArrayInputStream;
+
+/**
+ * This class is a concrete implementation of StreamLoader that uses the
+ * content supplied as a URL as the source for the stream. The mimetype
+ * optionally provided in the URL is extracted and inserted into the HTTP
+ * response headers.
+ */
+class DataLoader extends StreamLoader {
+
+    private String mContentType;  // Content mimetype, if supplied in URL
+
+    /**
+     * Constructor uses the dataURL as the source for an InputStream
+     * @param dataUrl data: URL string optionally containing a mimetype
+     * @param loadListener LoadListener to pass the content to
+     */
+    DataLoader(String dataUrl, LoadListener loadListener) {
+        super(loadListener);
+
+        String url = dataUrl.substring("data:".length());
+        String content;
+        int commaIndex = url.indexOf(',');
+        if (commaIndex != -1) {
+            mContentType = url.substring(0, commaIndex);
+            content = url.substring(commaIndex + 1);
+        } else {
+            content = url;
+        }
+        mDataStream = new ByteArrayInputStream(content.getBytes());
+        mContentLength = content.length();
+    }
+
+    @Override
+    protected boolean setupStreamAndSendStatus() {
+        mHandler.status(1, 1, 0, "OK");
+        return true;
+    }
+
+    @Override
+    protected void buildHeaders(Headers headers) {
+        if (mContentType != null) {
+            headers.setContentType(mContentType);
+        }
+    }
+
+    /**
+     * Construct a DataLoader and instruct it to start loading.
+     *
+     * @param url data: URL string optionally containing a mimetype
+     * @param loadListener LoadListener to pass the content to
+     */
+    public static void requestUrl(String url, LoadListener loadListener) {
+        DataLoader loader = new DataLoader(url, loadListener);
+        loader.load();
+    }
+
+}
diff --git a/core/java/android/webkit/DateSorter.java b/core/java/android/webkit/DateSorter.java
new file mode 100644
index 0000000..750403b
--- /dev/null
+++ b/core/java/android/webkit/DateSorter.java
@@ -0,0 +1,124 @@
+/*
+ * 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 android.webkit;
+
+import android.content.Context;
+import android.content.res.Resources;
+
+import java.util.Calendar;
+import java.util.Date;
+
+/**
+ * Sorts dates into the following groups:
+ *   Today
+ *   Yesterday
+ *   five days ago
+ *   one month ago
+ *   older than a month ago
+ */
+
+public class DateSorter {
+
+    private static final String LOGTAG = "webkit";
+
+    /** must be >= 3 */
+    public static final int DAY_COUNT = 5;
+
+    private long [] mBins = new long[DAY_COUNT];
+    private String [] mLabels = new String[DAY_COUNT];
+    
+    private static final int NUM_DAYS_AGO = 5;
+
+    Date mDate = new Date();
+    Calendar mCal = Calendar.getInstance();
+
+    /**
+     * @param context Application context
+     */
+    public DateSorter(Context context) {
+        Resources resources = context.getResources();
+
+        Calendar c = Calendar.getInstance();
+        beginningOfDay(c);
+        
+        // Create the bins
+        mBins[0] = c.getTimeInMillis(); // Today
+        c.roll(Calendar.DAY_OF_YEAR, -1);
+        mBins[1] = c.getTimeInMillis();  // Yesterday
+        c.roll(Calendar.DAY_OF_YEAR, -(NUM_DAYS_AGO - 1));
+        mBins[2] = c.getTimeInMillis();  // Five days ago
+        c.roll(Calendar.DAY_OF_YEAR, NUM_DAYS_AGO); // move back to today
+        c.roll(Calendar.MONTH, -1);
+        mBins[3] = c.getTimeInMillis();  // One month ago
+        c.roll(Calendar.MONTH, -1);
+        mBins[4] = c.getTimeInMillis();  // Over one month ago
+
+        // build labels
+        mLabels[0] = context.getText(com.android.internal.R.string.today).toString();
+        mLabels[1] = context.getText(com.android.internal.R.string.yesterday).toString();
+
+        int resId = com.android.internal.R.plurals.num_days_ago;
+        String format = resources.getQuantityString(resId, NUM_DAYS_AGO);
+        mLabels[2] = String.format(format, NUM_DAYS_AGO);
+
+        mLabels[3] = context.getText(com.android.internal.R.string.oneMonthDurationPast).toString();
+        mLabels[4] = context.getText(com.android.internal.R.string.beforeOneMonthDurationPast)
+                .toString();
+    }
+
+    /**
+     * @param time time since the Epoch in milliseconds, such as that
+     * returned by Calendar.getTimeInMillis()
+     * @return an index from 0 to (DAY_COUNT - 1) that identifies which
+     * date bin this date belongs to
+     */
+    public int getIndex(long time) {
+        // Lame linear search
+        for (int i = 0; i < DAY_COUNT; i++) {
+            if (time > mBins[i]) return i;
+        }
+        return DAY_COUNT - 1;
+    }
+
+    /**
+     * @param index date bin index as returned by getIndex()
+     * @return string label suitable for display to user
+     */
+    public String getLabel(int index) {
+        return mLabels[index];
+    }
+
+
+    /**
+     * @param index date bin index as returned by getIndex()
+     * @return date boundary at given index
+     */
+    public long getBoundary(int index) {
+        return mBins[index];
+    }
+
+    /**
+     * Calcuate 12:00am by zeroing out hour, minute, second, millisecond
+     */
+    private Calendar beginningOfDay(Calendar c) {
+        c.set(Calendar.HOUR_OF_DAY, 0);
+        c.set(Calendar.MINUTE, 0);
+        c.set(Calendar.SECOND, 0);
+        c.set(Calendar.MILLISECOND, 0);
+        return c;
+    }
+}
diff --git a/core/java/android/webkit/DownloadListener.java b/core/java/android/webkit/DownloadListener.java
new file mode 100644
index 0000000..dfaa1b9
--- /dev/null
+++ b/core/java/android/webkit/DownloadListener.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2007 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 android.webkit;
+
+public interface DownloadListener {
+
+    /**
+     * Notify the host application that a file should be downloaded
+     * @param url The full url to the content that should be downloaded
+     * @param userAgent the user agent to be used for the download.
+     * @param contentDisposition Content-disposition http header, if 
+     *                           present.
+     * @param mimetype The mimetype of the content reported by the server
+     * @param contentLength The file size reported by the server
+     */
+    public void onDownloadStart(String url, String userAgent,
+            String contentDisposition, String mimetype, long contentLength);
+
+}
diff --git a/core/java/android/webkit/FileLoader.java b/core/java/android/webkit/FileLoader.java
new file mode 100644
index 0000000..54a4c1d
--- /dev/null
+++ b/core/java/android/webkit/FileLoader.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright (C) 2007 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 android.webkit;
+
+import com.android.internal.R;
+
+import android.content.Context;
+import android.content.res.AssetManager;
+import android.net.http.EventHandler;
+import android.net.http.Headers;
+import android.os.Environment;
+
+import java.io.File;
+import java.io.FileInputStream;
+
+/**
+ * This class is a concrete implementation of StreamLoader that uses a
+ * file or asset as the source for the stream.
+ *
+ */
+class FileLoader extends StreamLoader {
+
+    private String mPath;  // Full path to the file to load
+    private Context mContext;  // Application context, used for asset loads
+    private boolean mIsAsset;  // Indicates if the load is an asset or not
+    private boolean mAllowFileAccess; // Allow/block file system access
+
+    /**
+     * Construct a FileLoader with the file URL specified as the content
+     * source.
+     *
+     * @param url Full file url pointing to content to be loaded
+     * @param loadListener LoadListener to pass the content to
+     * @param context Context to use to access the asset.
+     * @param asset true if url points to an asset.
+     * @param allowFileAccess true if this WebView is allowed to access files
+     *                        on the file system.
+     */
+    FileLoader(String url, LoadListener loadListener, Context context,
+            boolean asset, boolean allowFileAccess) {
+        super(loadListener);
+        mIsAsset = asset;
+        mContext = context;
+        mAllowFileAccess = allowFileAccess;
+
+        // clean the Url
+        int index = url.indexOf('?');
+        if (mIsAsset) {
+            mPath = index > 0 ? URLUtil.stripAnchor(
+                    url.substring(URLUtil.ASSET_BASE.length(), index)) :
+                    URLUtil.stripAnchor(url.substring(
+                            URLUtil.ASSET_BASE.length()));
+        } else {
+            mPath = index > 0 ? URLUtil.stripAnchor(
+                    url.substring(URLUtil.FILE_BASE.length(), index)) :
+                    URLUtil.stripAnchor(url.substring(
+                            URLUtil.FILE_BASE.length()));
+        }
+    }
+
+    @Override
+    protected boolean setupStreamAndSendStatus() {
+        try {
+            if (mIsAsset) {
+                try {
+                    mDataStream = mContext.getAssets().open(mPath);
+                } catch (java.io.FileNotFoundException ex) {
+                    // try the rest files included in the package
+                    mDataStream = mContext.getAssets().openNonAsset(mPath);
+                }
+            } else {
+                if (!mAllowFileAccess) {
+                    mHandler.error(EventHandler.FILE_ERROR,
+                            mContext.getString(R.string.httpErrorFileNotFound));
+                    return false;
+                }
+
+                mDataStream = new FileInputStream(mPath);
+                mContentLength = (new File(mPath)).length();
+            }
+            mHandler.status(1, 1, 0, "OK");
+
+        } catch (java.io.FileNotFoundException ex) {
+            mHandler.error(
+                    EventHandler.FILE_NOT_FOUND_ERROR,
+                    mContext.getString(R.string.httpErrorFileNotFound) +
+                    " " + ex.getMessage());
+            return false;
+
+        } catch (java.io.IOException ex) {
+            mHandler.error(EventHandler.FILE_ERROR,
+                           mContext.getString(R.string.httpErrorFileNotFound) +
+                           " " + ex.getMessage());
+            return false;
+        }
+        return true;
+    }
+
+    @Override
+    protected void buildHeaders(Headers headers) {
+        // do nothing.
+    }
+
+
+    /**
+     * Construct a FileLoader and instruct it to start loading.
+     *
+     * @param url Full file url pointing to content to be loaded
+     * @param loadListener LoadListener to pass the content to
+     * @param context Context to use to access the asset.
+     * @param asset true if url points to an asset.
+     * @param allowFileAccess true if this FileLoader can load files from the
+     *                        file system.
+     */
+    public static void requestUrl(String url, LoadListener loadListener,
+            Context context, boolean asset, boolean allowFileAccess) {
+        FileLoader loader = new FileLoader(url, loadListener, context, asset,
+                allowFileAccess);
+        loader.load();
+    }
+
+}
diff --git a/core/java/android/webkit/FrameLoader.java b/core/java/android/webkit/FrameLoader.java
new file mode 100644
index 0000000..5e323eb
--- /dev/null
+++ b/core/java/android/webkit/FrameLoader.java
@@ -0,0 +1,370 @@
+/*
+ * 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 android.webkit;
+
+import android.net.http.EventHandler;
+import android.net.http.RequestHandle;
+import android.util.Config;
+import android.util.Log;
+import android.webkit.CacheManager.CacheResult;
+import android.webkit.UrlInterceptRegistry;
+
+import java.util.HashMap;
+import java.util.Map;
+
+class FrameLoader {
+
+    private final LoadListener mListener;
+    private final String mMethod;
+    private final boolean mIsHighPriority;
+    private final WebSettings mSettings;
+    private Map<String, String> mHeaders;
+    private byte[] mPostData;
+    private Network mNetwork;
+    private int mCacheMode;
+    private String mReferrer;
+    private String mContentType;
+
+    private static final int URI_PROTOCOL = 0x100;
+
+    private static final String CONTENT_TYPE = "content-type";
+
+    // Contents of an about:blank page
+    private static final String mAboutBlank =
+            "<!DOCTYPE html PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EB\">" +
+            "<html><head><title>about:blank</title></head><body></body></html>";
+
+    static final String HEADER_STR = "text/xml, text/html, " +
+            "application/xhtml+xml, image/png, text/plain, */*;q=0.8";
+
+    private static final String LOGTAG = "webkit";
+    
+    FrameLoader(LoadListener listener, WebSettings settings,
+            String method, boolean highPriority) {
+        mListener = listener;
+        mHeaders = null;
+        mMethod = method;
+        mIsHighPriority = highPriority;
+        mCacheMode = WebSettings.LOAD_NORMAL;
+        mSettings = settings;
+    }
+
+    public void setReferrer(String ref) {
+        // only set referrer for http or https
+        if (URLUtil.isNetworkUrl(ref)) mReferrer = ref;
+    }
+
+    public void setPostData(byte[] postData) {
+        mPostData = postData;
+    }
+
+    public void setContentTypeForPost(String postContentType) {
+        mContentType = postContentType;
+    }
+
+    public void setCacheMode(int cacheMode) {
+        mCacheMode = cacheMode;
+    }
+
+    public void setHeaders(HashMap headers) {
+        mHeaders = headers;
+    }
+
+    public LoadListener getLoadListener() {
+        return mListener;
+    }
+
+    /**
+     * Issues the load request.
+     *
+     * Return value does not indicate if the load was successful or not. It
+     * simply indicates that the load request is reasonable.
+     *
+     * @return true if the load is reasonable.
+     */
+    public boolean executeLoad() {
+        String url = mListener.url();
+
+        // Attempt to decode the percent-encoded url.
+        try {
+            url = new String(URLUtil.decode(url.getBytes()));
+        } catch (IllegalArgumentException e) {
+            // Fail with a bad url error if the decode fails.
+            mListener.error(EventHandler.ERROR_BAD_URL,
+                    mListener.getContext().getString(
+                            com.android.internal.R.string.httpErrorBadUrl));
+            return false;
+        }
+
+        if (URLUtil.isNetworkUrl(url)){
+            if (mSettings.getBlockNetworkLoads()) {
+                mListener.error(EventHandler.ERROR_BAD_URL,
+                        mListener.getContext().getString(
+                                com.android.internal.R.string.httpErrorBadUrl));
+                return false;
+            }
+            mNetwork = Network.getInstance(mListener.getContext());
+            return handleHTTPLoad();
+        } else if (handleLocalFile(url, mListener, mSettings)) {
+            return true;
+        }
+        if (Config.LOGV) {
+            Log.v(LOGTAG, "FrameLoader.executeLoad: url protocol not supported:"
+                    + mListener.url());
+        }
+        mListener.error(EventHandler.ERROR_UNSUPPORTED_SCHEME,
+                mListener.getContext().getText(
+                        com.android.internal.R.string.httpErrorUnsupportedScheme).toString());
+        return false;
+
+    }
+
+    /* package */
+    static boolean handleLocalFile(String url, LoadListener loadListener,
+            WebSettings settings) {
+        if (URLUtil.isAssetUrl(url)) {
+            FileLoader.requestUrl(url, loadListener, loadListener.getContext(),
+                    true, settings.getAllowFileAccess());
+            return true;
+        } else if (URLUtil.isFileUrl(url)) {
+            FileLoader.requestUrl(url, loadListener, loadListener.getContext(),
+                    false, settings.getAllowFileAccess());
+            return true;
+        } else if (URLUtil.isContentUrl(url)) {
+            // Send the raw url to the ContentLoader because it will do a
+            // permission check and the url has to match..
+            ContentLoader.requestUrl(loadListener.url(), loadListener,
+                                     loadListener.getContext());
+            return true;
+        } else if (URLUtil.isDataUrl(url)) {
+            DataLoader.requestUrl(url, loadListener);
+            return true;
+        } else if (URLUtil.isAboutUrl(url)) {
+            loadListener.data(mAboutBlank.getBytes(), mAboutBlank.length());
+            loadListener.endData();
+            return true;
+        }
+        return false;
+    }
+    
+    private boolean handleHTTPLoad() {
+        if (mHeaders == null) {
+            mHeaders = new HashMap<String, String>();
+        }
+        populateStaticHeaders();
+        populateHeaders();
+
+        // response was handled by UrlIntercept, don't issue HTTP request
+        if (handleUrlIntercept()) return true;
+
+        // response was handled by Cache, don't issue HTTP request
+        if (handleCache()) {
+            // push the request data down to the LoadListener
+            // as response from the cache could be a redirect
+            // and we may need to initiate a network request if the cache
+            // can't satisfy redirect URL
+            mListener.setRequestData(mMethod, mHeaders, mPostData, 
+                    mIsHighPriority);
+            return true;
+        }
+
+        if (Config.LOGV) {
+            Log.v(LOGTAG, "FrameLoader: http " + mMethod + " load for: "
+                    + mListener.url());
+        }
+
+        boolean ret = false;
+        int error = EventHandler.ERROR_UNSUPPORTED_SCHEME;
+        
+        try {
+            ret = mNetwork.requestURL(mMethod, mHeaders,
+                    mPostData, mListener, mIsHighPriority);
+        } catch (android.net.ParseException ex) {
+            error = EventHandler.ERROR_BAD_URL;
+        } catch (java.lang.RuntimeException ex) {
+            /* probably an empty header set by javascript.  We want
+               the same result as bad URL  */
+            error = EventHandler.ERROR_BAD_URL;
+        }
+        if (!ret) {
+            mListener.error(error, mListener.getContext().getText(
+                    EventHandler.errorStringResources[Math.abs(error)]).toString());
+            return false;
+        }
+        return true;
+    }
+
+    /*
+     * This function is used by handleUrlInterecpt and handleCache to
+     * setup a load from the byte stream in a CacheResult.
+     */
+    private void startCacheLoad(CacheResult result) {
+        if (Config.LOGV) {
+            Log.v(LOGTAG, "FrameLoader: loading from cache: "
+                  + mListener.url());
+        }
+        // Tell the Listener respond with the cache file
+        CacheLoader cacheLoader =
+                new CacheLoader(mListener, result);
+        mListener.setCacheLoader(cacheLoader);
+        cacheLoader.load();
+    }
+
+    /*
+     * This function is used by handleHTTPLoad to allow URL
+     * interception. This can be used to provide alternative load
+     * methods such as locally stored versions or for debugging.
+     *
+     * Returns true if the response was handled by UrlIntercept.
+     */
+    private boolean handleUrlIntercept() {
+        // Check if the URL can be served from UrlIntercept. If
+        // successful, return the data just like a cache hit.
+        CacheResult result = UrlInterceptRegistry.getSurrogate(
+                mListener.url(), mHeaders);
+        if(result != null) {
+            // Intercepted. The data is stored in result.stream. Setup
+            // a load from the CacheResult.
+            startCacheLoad(result);
+            return true;
+        }
+        // Not intercepted. Carry on as normal.
+        return false;
+    }
+
+    /*
+     * This function is used by the handleHTTPLoad to setup the cache headers
+     * correctly.
+     * Returns true if the response was handled from the cache
+     */
+    private boolean handleCache() {
+        switch (mCacheMode) {
+            // This mode is normally used for a reload, it instructs the http
+            // loader to not use the cached content.
+            case WebSettings.LOAD_NO_CACHE:
+                break;
+                
+                
+            // This mode is used when the content should only be loaded from
+            // the cache. If it is not there, then fail the load. This is used
+            // to load POST content in a history navigation.
+            case WebSettings.LOAD_CACHE_ONLY: {
+                CacheResult result = CacheManager.getCacheFile(mListener.url(),
+                        null);
+                if (result != null) {
+                    startCacheLoad(result);
+                } else {
+                    // This happens if WebCore was first told that the POST
+                    // response was in the cache, then when we try to use it
+                    // it has gone.
+                    // Generate a file not found error
+                    int err = EventHandler.FILE_NOT_FOUND_ERROR;
+                    mListener.error(err, mListener.getContext().getText(
+                            EventHandler.errorStringResources[Math.abs(err)])
+                            .toString());
+                }
+                return true;
+            }
+
+            // This mode is for when the user is doing a history navigation
+            // in the browser and should returned cached content regardless
+            // of it's state. If it is not in the cache, then go to the 
+            // network.
+            case WebSettings.LOAD_CACHE_ELSE_NETWORK: {
+                if (Config.LOGV) {
+                    Log.v(LOGTAG, "FrameLoader: checking cache: "
+                            + mListener.url());
+                }
+                // Get the cache file name for the current URL, passing null for
+                // the validation headers causes no validation to occur
+                CacheResult result = CacheManager.getCacheFile(mListener.url(),
+                        null);
+                if (result != null) {
+                    startCacheLoad(result);
+                    return true;
+                }
+                break;
+            }
+
+            // This is the default case, which is to check to see if the
+            // content in the cache can be used. If it can be used, then
+            // use it. If it needs revalidation then the relevant headers
+            // are added to the request.
+            default:
+            case WebSettings.LOAD_NORMAL:
+                return mListener.checkCache(mHeaders);
+        }// end of switch
+
+        return false;
+    }
+    
+    /**
+     * Add the static headers that don't change with each request.
+     */
+    private void populateStaticHeaders() {
+        // Accept header should already be there as they are built by WebCore,
+        // but in the case they are missing, add some.
+        String accept = mHeaders.get("Accept");
+        if (accept == null || accept.length() == 0) {
+            mHeaders.put("Accept", HEADER_STR);
+        }
+        mHeaders.put("Accept-Charset", "utf-8, iso-8859-1, utf-16, *;q=0.7");
+
+        String acceptLanguage = mSettings.getAcceptLanguage();
+        if (acceptLanguage.length() > 0) {
+            mHeaders.put("Accept-Language", acceptLanguage);
+        }
+        
+        mHeaders.put("User-Agent", mSettings.getUserAgentString());
+    }
+
+    /**
+     * Add the content related headers. These headers contain user private data
+     * and is not used when we are proxying an untrusted request.
+     */
+    private void populateHeaders() {
+        
+        if (mReferrer != null) mHeaders.put("Referer", mReferrer);
+        if (mContentType != null) mHeaders.put(CONTENT_TYPE, mContentType);
+
+        // if we have an active proxy and have proxy credentials, do pre-emptive
+        // authentication to avoid an extra round-trip:
+        if (mNetwork.isValidProxySet()) {
+            String username;
+            String password;
+            /* The proxy credentials can be set in the Network thread */
+            synchronized (mNetwork) {
+                username = mNetwork.getProxyUsername();
+                password = mNetwork.getProxyPassword();
+            }
+            if (username != null && password != null) {
+                // we collect credentials ONLY if the proxy scheme is BASIC!!!
+                String proxyHeader = RequestHandle.authorizationHeader(true);
+                mHeaders.put(proxyHeader,
+                        "Basic " + RequestHandle.computeBasicAuthResponse(
+                                username, password));
+            }
+        }
+
+        // Set cookie header
+        String cookie = CookieManager.getInstance().getCookie(
+                mListener.getWebAddress());
+        if (cookie != null && cookie.length() > 0) {
+            mHeaders.put("cookie", cookie);
+        }
+    }
+}
diff --git a/core/java/android/webkit/HttpAuthHandler.java b/core/java/android/webkit/HttpAuthHandler.java
new file mode 100644
index 0000000..48b9eec
--- /dev/null
+++ b/core/java/android/webkit/HttpAuthHandler.java
@@ -0,0 +1,186 @@
+/*
+ * 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 android.webkit;
+
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
+
+import java.util.ListIterator;
+import java.util.LinkedList;
+
+/**
+ * HTTP authentication handler: local handler that takes care
+ * of HTTP authentication requests. This class is passed as a
+ * parameter to BrowserCallback.displayHttpAuthDialog and is
+ * meant to receive the user's response.
+ */
+public class HttpAuthHandler extends Handler {
+    /* It is important that the handler is in Network, because
+     * we want to share it accross multiple loaders and windows
+     * (like our subwindow and the main window).
+     */
+
+    private static final String LOGTAG = "network";
+
+    /**
+     * Network.
+     */
+    private Network mNetwork;
+
+    /**
+     * Loader queue.
+     */
+    private LinkedList<LoadListener> mLoaderQueue;
+
+
+    // Message id for handling the user response
+    private final int AUTH_PROCEED = 100;
+    private final int AUTH_CANCEL = 200;
+
+    /**
+     * Creates a new HTTP authentication handler with an empty
+     * loader queue
+     *
+     * @param network The parent network object
+     */
+    /* package */ HttpAuthHandler(Network network) {
+        mNetwork = network;
+        mLoaderQueue = new LinkedList<LoadListener>();
+    }
+
+
+    @Override
+    public void handleMessage(Message msg) {
+        LoadListener loader = null;
+        synchronized (mLoaderQueue) {
+            loader = mLoaderQueue.poll();
+        }
+
+        switch (msg.what) {
+            case AUTH_PROCEED:
+                String username = msg.getData().getString("username");
+                String password = msg.getData().getString("password");
+
+                loader.handleAuthResponse(username, password);
+                break;
+
+            case AUTH_CANCEL:
+
+                mNetwork.resetHandlersAndStopLoading(loader.getFrame());
+                break;
+        }
+
+        processNextLoader();
+    }
+
+
+    /**
+     * Proceed with the authorization with the given credentials
+     *
+     * @param username The username to use for authentication
+     * @param password The password to use for authentication
+     */
+    public void proceed(String username, String password) {
+        Message msg = obtainMessage(AUTH_PROCEED);
+        msg.getData().putString("username", username);
+        msg.getData().putString("password", password);
+        sendMessage(msg);
+    }
+
+    /**
+     * Cancel the authorization request
+     */
+    public void cancel() {
+        sendMessage(obtainMessage(AUTH_CANCEL));
+    }
+
+    /**
+     * @return True if we can use user credentials on record
+     * (ie, if we did not fail trying to use them last time)
+     */
+    public boolean useHttpAuthUsernamePassword() {
+        LoadListener loader = null;
+        synchronized (mLoaderQueue) {
+            loader = mLoaderQueue.peek();
+        }
+        if (loader != null) {
+            return !loader.authCredentialsInvalid();
+        }
+
+        return false;
+    }
+
+    /**
+     * Resets the HTTP-authentication request handler, removes
+     * all loaders that share the same BrowserFrame
+     *
+     * @param frame The browser frame
+     */
+    /* package */ void reset(BrowserFrame frame) {
+        synchronized (mLoaderQueue) {
+            ListIterator<LoadListener> i = mLoaderQueue.listIterator(0);
+            while (i.hasNext()) {
+                LoadListener loader = i.next();
+                if (frame == loader.getFrame()) {
+                    i.remove();
+                }
+            }
+        }
+    }
+
+    /**
+     * Enqueues the loader, if the loader is the only element
+     * in the queue, starts processing the loader
+     *
+     * @param loader The loader that resulted in this http
+     * authentication request
+     */
+    /* package */ void handleAuthRequest(LoadListener loader) {
+        boolean processNext = false;
+
+        synchronized (mLoaderQueue) {
+            mLoaderQueue.offer(loader);
+            processNext =
+                (mLoaderQueue.size() == 1);
+        }
+
+        if (processNext) {
+            processNextLoader();
+        }
+    }
+
+    /**
+     * Process the next loader in the queue (helper method)
+     */
+    private void processNextLoader() {
+        LoadListener loader = null;
+        synchronized (mLoaderQueue) {
+            loader = mLoaderQueue.peek();
+        }
+        if (loader != null) {
+            CallbackProxy proxy = loader.getFrame().getCallbackProxy();
+
+            String hostname = loader.proxyAuthenticate() ?
+                mNetwork.getProxyHostname() : loader.host();
+
+            String realm = loader.realm();
+
+            proxy.onReceivedHttpAuthRequest(this, hostname, realm);
+        }
+    }
+}
diff --git a/core/java/android/webkit/HttpDateTime.java b/core/java/android/webkit/HttpDateTime.java
new file mode 100644
index 0000000..c6ec2d2
--- /dev/null
+++ b/core/java/android/webkit/HttpDateTime.java
@@ -0,0 +1,195 @@
+/*
+ * Copyright (C) 2007 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 android.webkit;
+
+import android.text.format.Time;
+
+import java.util.Calendar;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+
+class HttpDateTime {
+
+    /*
+     * Regular expression for parsing HTTP-date.
+     *
+     * Wdy, DD Mon YYYY HH:MM:SS GMT
+     * RFC 822, updated by RFC 1123
+     *
+     * Weekday, DD-Mon-YY HH:MM:SS GMT
+     * RFC 850, obsoleted by RFC 1036
+     *
+     * Wdy Mon DD HH:MM:SS YYYY
+     * ANSI C's asctime() format
+     *
+     * with following variations
+     *
+     * Wdy, DD-Mon-YYYY HH:MM:SS GMT
+     * Wdy, (SP)D Mon YYYY HH:MM:SS GMT
+     * Wdy,DD Mon YYYY HH:MM:SS GMT
+     * Wdy, DD-Mon-YY HH:MM:SS GMT
+     * Wdy, DD Mon YYYY HH:MM:SS -HHMM
+     * Wdy, DD Mon YYYY HH:MM:SS
+     * Wdy Mon (SP)D HH:MM:SS YYYY
+     * Wdy Mon DD HH:MM:SS YYYY GMT
+     */
+    private static final String HTTP_DATE_RFC_REGEXP =
+            "([0-9]{1,2})[- ]([A-Za-z]{3,3})[- ]([0-9]{2,4})[ ]"
+            + "([0-9][0-9]:[0-9][0-9]:[0-9][0-9])";
+
+    private static final String HTTP_DATE_ANSIC_REGEXP =
+            "[ ]([A-Za-z]{3,3})[ ]+([0-9]{1,2})[ ]"
+            + "([0-9][0-9]:[0-9][0-9]:[0-9][0-9])[ ]([0-9]{2,4})";
+
+    /**
+     * The compiled version of the HTTP-date regular expressions.
+     */
+    private static final Pattern HTTP_DATE_RFC_PATTERN =
+            Pattern.compile(HTTP_DATE_RFC_REGEXP);
+    private static final Pattern HTTP_DATE_ANSIC_PATTERN =
+            Pattern.compile(HTTP_DATE_ANSIC_REGEXP);
+
+    private static class TimeOfDay {
+        int hour;
+        int minute;
+        int second;
+    }
+
+    public static Long parse(String timeString)
+            throws IllegalArgumentException {
+
+        int date = 1;
+        int month = Calendar.JANUARY;
+        int year = 1970;
+        TimeOfDay timeOfDay = new TimeOfDay();
+
+        Matcher rfcMatcher = HTTP_DATE_RFC_PATTERN.matcher(timeString);
+        if (rfcMatcher.find()) {
+            date = getDate(rfcMatcher.group(1));
+            month = getMonth(rfcMatcher.group(2));
+            year = getYear(rfcMatcher.group(3));
+            timeOfDay = getTime(rfcMatcher.group(4));
+        } else {
+            Matcher ansicMatcher = HTTP_DATE_ANSIC_PATTERN.matcher(timeString);
+            if (ansicMatcher.find()) {
+                month = getMonth(ansicMatcher.group(1));
+                date = getDate(ansicMatcher.group(2));
+                timeOfDay = getTime(ansicMatcher.group(3));
+                year = getYear(ansicMatcher.group(4));
+            } else {
+                throw new IllegalArgumentException();
+            }
+        }
+
+        // FIXME: Y2038 BUG!
+        if (year >= 2038) {
+            year = 2038;
+            month = Calendar.JANUARY;
+            date = 1;
+        }
+
+        Time time = new Time(Time.TIMEZONE_UTC);
+        time.set(timeOfDay.second, timeOfDay.minute, timeOfDay.hour, date,
+                month, year);
+        return time.toMillis(false /* use isDst */);
+    }
+
+    private static int getDate(String dateString) {
+        if (dateString.length() == 2) {
+            return (dateString.charAt(0) - '0') * 10
+                    + (dateString.charAt(1) - '0');
+        } else {
+            return (dateString.charAt(0) - '0');
+        }
+    }
+
+    /*
+     * jan = 9 + 0 + 13 = 22
+     * feb = 5 + 4 + 1 = 10
+     * mar = 12 + 0 + 17 = 29
+     * apr = 0 + 15 + 17 = 32
+     * may = 12 + 0 + 24 = 36
+     * jun = 9 + 20 + 13 = 42
+     * jul = 9 + 20 + 11 = 40
+     * aug = 0 + 20 + 6 = 26
+     * sep = 18 + 4 + 15 = 37
+     * oct = 14 + 2 + 19 = 35
+     * nov = 13 + 14 + 21 = 48
+     * dec = 3 + 4 + 2 = 9
+     */
+    private static int getMonth(String monthString) {
+        int hash = Character.toLowerCase(monthString.charAt(0)) +
+                Character.toLowerCase(monthString.charAt(1)) +
+                Character.toLowerCase(monthString.charAt(2)) - 3 * 'a';
+        switch (hash) {
+            case 22:
+                return Calendar.JANUARY;
+            case 10:
+                return Calendar.FEBRUARY;
+            case 29:
+                return Calendar.MARCH;
+            case 32:
+                return Calendar.APRIL;
+            case 36:
+                return Calendar.MAY;
+            case 42:
+                return Calendar.JUNE;
+            case 40:
+                return Calendar.JULY;
+            case 26:
+                return Calendar.AUGUST;
+            case 37:
+                return Calendar.SEPTEMBER;
+            case 35:
+                return Calendar.OCTOBER;
+            case 48:
+                return Calendar.NOVEMBER;
+            case 9:
+                return Calendar.DECEMBER;
+            default:
+                throw new IllegalArgumentException();
+        }
+    }
+
+    private static int getYear(String yearString) {
+        if (yearString.length() == 2) {
+            int year = (yearString.charAt(0) - '0') * 10
+                    + (yearString.charAt(1) - '0');
+            if (year >= 70) {
+                return year + 1900;
+            } else {
+                return year + 2000;
+            }
+        } else
+            return (yearString.charAt(0) - '0') * 1000
+                    + (yearString.charAt(1) - '0') * 100
+                    + (yearString.charAt(2) - '0') * 10
+                    + (yearString.charAt(3) - '0');
+    }
+
+    private static TimeOfDay getTime(String timeString) {
+        TimeOfDay time = new TimeOfDay();
+        time.hour = (timeString.charAt(0) - '0') * 10
+                + (timeString.charAt(1) - '0');
+        time.minute = (timeString.charAt(3) - '0') * 10
+                + (timeString.charAt(4) - '0');
+        time.second = (timeString.charAt(6) - '0') * 10
+                + (timeString.charAt(7) - '0');
+        return time;
+    }
+}
diff --git a/core/java/android/webkit/JWebCoreJavaBridge.java b/core/java/android/webkit/JWebCoreJavaBridge.java
new file mode 100644
index 0000000..a0049ac
--- /dev/null
+++ b/core/java/android/webkit/JWebCoreJavaBridge.java
@@ -0,0 +1,195 @@
+/*
+ * 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 android.webkit;
+
+import android.os.Handler;
+import android.os.Message;
+import android.util.Config;
+import android.util.Log;
+
+final class JWebCoreJavaBridge extends Handler {
+    // Identifier for the timer message.
+    private static final int TIMER_MESSAGE = 1;
+    // ID for servicing functionptr queue
+    private static final int FUNCPTR_MESSAGE = 2;
+    // Log system identifier.
+    private static final String LOGTAG = "webkit-timers";
+
+    // Native object pointer for interacting in native code.
+    private int mNativeBridge;
+    // Instant timer is used to implement a timer that needs to fire almost
+    // immediately.
+    private boolean mHasInstantTimer;
+    // Reference count the pause/resume of timers
+    private int mPauseTimerRefCount;
+
+    /**
+     * Construct a new JWebCoreJavaBridge to interface with
+     * WebCore timers and cookies.
+     */
+    public JWebCoreJavaBridge() {
+        nativeConstructor();
+    }
+
+    @Override
+    protected void finalize() {
+        nativeFinalize();
+    }
+
+    /**
+     * handleMessage
+     * @param msg The dispatched message.
+     *
+     * The only accepted message currently is TIMER_MESSAGE
+     */
+    @Override
+    public void handleMessage(Message msg) {
+        switch (msg.what) {
+            case TIMER_MESSAGE: {
+                PerfChecker checker = new PerfChecker();
+                // clear the flag so that sharedTimerFired() can set a new timer
+                mHasInstantTimer = false;
+                sharedTimerFired();
+                checker.responseAlert("sharedTimer");
+                break;
+            }
+            case FUNCPTR_MESSAGE:
+                nativeServiceFuncPtrQueue();
+                break;
+        }
+    }
+    
+    // called from JNI side
+    private void signalServiceFuncPtrQueue() {
+        Message msg = obtainMessage(FUNCPTR_MESSAGE);
+        sendMessage(msg);
+    }
+    
+    private native void nativeServiceFuncPtrQueue();
+
+    /**
+     * Pause all timers.
+     */
+    public void pause() {
+        if (--mPauseTimerRefCount == 0) {
+            setDeferringTimers(true);
+        }
+    }
+
+    /**
+     * Resume all timers.
+     */
+    public void resume() {
+        if (++mPauseTimerRefCount == 1) {
+            setDeferringTimers(false);
+        }
+    }
+
+    /**
+     * Set WebCore cache size.
+     * @param bytes The cache size in bytes.
+     */
+    public native void setCacheSize(int bytes);
+
+    /**
+     * Store a cookie string associated with a url.
+     * @param url The url to be used as a key for the cookie.
+     * @param docUrl The policy base url used by WebCore.
+     * @param value The cookie string to be stored.
+     */
+    private void setCookies(String url, String docUrl, String value) {
+        if (value.contains("\r") || value.contains("\n")) {
+            // for security reason, filter out '\r' and '\n' from the cookie
+            int size = value.length();
+            StringBuilder buffer = new StringBuilder(size);
+            int i = 0;
+            while (i != -1 && i < size) {
+                int ir = value.indexOf('\r', i);
+                int in = value.indexOf('\n', i);
+                int newi = (ir == -1) ? in : (in == -1 ? ir : (ir < in ? ir
+                        : in));
+                if (newi > i) {
+                    buffer.append(value.subSequence(i, newi));
+                } else if (newi == -1) {
+                    buffer.append(value.subSequence(i, size));
+                    break;
+                }
+                i = newi + 1;
+            }
+            value = buffer.toString();
+        }
+        CookieManager.getInstance().setCookie(url, value);
+    }
+
+    /**
+     * Retrieve the cookie string for the given url.
+     * @param url The resource's url.
+     * @return A String representing the cookies for the given resource url.
+     */
+    private String cookies(String url) {
+        return CookieManager.getInstance().getCookie(url);
+    }
+
+    /**
+     * Returns whether cookies are enabled or not.
+     */
+    private boolean cookiesEnabled() {
+        return CookieManager.getInstance().acceptCookie();
+    }
+
+    /**
+     * setSharedTimer
+     * @param timemillis The relative time when the timer should fire
+     */
+    private void setSharedTimer(long timemillis) {
+        if (Config.LOGV) Log.v(LOGTAG, "setSharedTimer " + timemillis);
+
+        if (timemillis <= 0) {
+            // we don't accumulate the sharedTimer unless it is a delayed
+            // request. This way we won't flood the message queue with
+            // WebKit messages. This should improve the browser's
+            // responsiveness to key events.
+            if (mHasInstantTimer) {
+                return;
+            } else {
+                mHasInstantTimer = true;
+                Message msg = obtainMessage(TIMER_MESSAGE);
+                sendMessageDelayed(msg, timemillis);
+            }
+        } else {
+            Message msg = obtainMessage(TIMER_MESSAGE);
+            sendMessageDelayed(msg, timemillis);
+        }
+    }
+
+    /**
+     * Stop the shared timer.
+     */
+    private void stopSharedTimer() {
+        if (Config.LOGV) {
+            Log.v(LOGTAG, "stopSharedTimer removing all timers");
+        }
+        removeMessages(TIMER_MESSAGE);
+        mHasInstantTimer = false;
+    }
+
+    private native void nativeConstructor();
+    private native void nativeFinalize();
+    private native void sharedTimerFired();
+    private native void setDeferringTimers(boolean defer);
+    public native void setNetworkOnLine(boolean online);
+}
diff --git a/core/java/android/webkit/JsPromptResult.java b/core/java/android/webkit/JsPromptResult.java
new file mode 100644
index 0000000..9fcd1bc
--- /dev/null
+++ b/core/java/android/webkit/JsPromptResult.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2007 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 android.webkit;
+
+
+/**
+ * Public class for handling javascript prompt requests. A
+ * JsDialogHandlerInterface implentation will receive a jsPrompt call with a
+ * JsPromptResult parameter. This parameter is used to return a result to
+ * WebView. The client can call cancel() to cancel the dialog or confirm() with
+ * the user's input to confirm the dialog.
+ */
+public class JsPromptResult extends JsResult {
+    // String result of the prompt
+    private String mStringResult;
+
+    /**
+     * Handle a confirmation response from the user.
+     */
+    public void confirm(String result) {
+        mStringResult = result;
+        confirm();
+    }
+
+    /*package*/ JsPromptResult(CallbackProxy proxy) {
+        super(proxy, /* unused */ false);
+    }
+
+    /*package*/ String getStringResult() {
+        return mStringResult;
+    }
+
+    @Override
+    /*package*/ void handleDefault() {
+        mStringResult = null;
+        super.handleDefault();
+    }
+}
diff --git a/core/java/android/webkit/JsResult.java b/core/java/android/webkit/JsResult.java
new file mode 100644
index 0000000..0c86e0a
--- /dev/null
+++ b/core/java/android/webkit/JsResult.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2007 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 android.webkit;
+
+
+public class JsResult {
+    // This prevents a user from interacting with the result before WebCore is
+    // ready to handle it.
+    private boolean mReady;
+    // Tells us if the user tried to confirm or cancel the result before WebCore
+    // is ready.
+    private boolean mTriedToNotifyBeforeReady;
+    // This is a basic result of a confirm or prompt dialog.
+    protected boolean mResult;
+    // This is the caller of the prompt and is the object that is waiting.
+    protected final CallbackProxy mProxy;
+    // This is the default value of the result.
+    private final boolean mDefaultValue;
+
+    /**
+     * Handle the result if the user cancelled the dialog.
+     */
+    public final void cancel() {
+        mResult = false;
+        wakeUp();
+    }
+
+    /**
+     * Handle a confirmation response from the user.
+     */
+    public final void confirm() {
+        mResult = true;
+        wakeUp();
+    }
+
+    /*package*/ JsResult(CallbackProxy proxy, boolean defaultVal) {
+        mProxy = proxy;
+        mDefaultValue = defaultVal;
+    }
+
+    /*package*/ final boolean getResult() {
+        return mResult;
+    }
+
+    /*package*/ final void setReady() {
+        mReady = true;
+        if (mTriedToNotifyBeforeReady) {
+            wakeUp();
+        }
+    }
+
+    /*package*/ void handleDefault() {
+        setReady();
+        mResult = mDefaultValue;
+        wakeUp();
+    }
+
+    /* Wake up the WebCore thread. */
+    protected final void wakeUp() {
+        if (mReady) {
+            synchronized (mProxy) {
+                mProxy.notify();
+            }
+        } else {
+            mTriedToNotifyBeforeReady = true;
+        }
+    }
+}
diff --git a/core/java/android/webkit/LoadListener.java b/core/java/android/webkit/LoadListener.java
new file mode 100644
index 0000000..dfae17d
--- /dev/null
+++ b/core/java/android/webkit/LoadListener.java
@@ -0,0 +1,1499 @@
+/*
+ * 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 android.webkit;
+
+import android.content.Context;
+import android.net.WebAddress;
+import android.net.ParseException;
+import android.net.http.EventHandler;
+import android.net.http.Headers;
+import android.net.http.HttpAuthHeader;
+import android.net.http.RequestHandle;
+import android.net.http.SslCertificate;
+import android.net.http.SslError;
+import android.net.http.SslCertificate;
+
+import android.os.Handler;
+import android.os.Message;
+import android.util.Config;
+import android.util.Log;
+import android.webkit.CacheManager.CacheResult;
+
+import com.android.internal.R;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Vector;
+import java.util.regex.Pattern;
+import java.util.regex.Matcher;
+
+import org.apache.commons.codec.binary.Base64;
+
+class LoadListener extends Handler implements EventHandler {
+
+    private static final String LOGTAG = "webkit";
+
+    // Messages used internally to communicate state between the
+    // Network thread and the WebCore thread.
+    private static final int MSG_CONTENT_HEADERS = 100;
+    private static final int MSG_CONTENT_DATA = 110;
+    private static final int MSG_CONTENT_FINISHED = 120;
+    private static final int MSG_CONTENT_ERROR = 130;
+    private static final int MSG_LOCATION_CHANGED = 140;
+    private static final int MSG_LOCATION_CHANGED_REQUEST = 150;
+    private static final int MSG_STATUS = 160;
+    private static final int MSG_SSL_CERTIFICATE = 170;
+    private static final int MSG_SSL_ERROR = 180;
+
+    // Standard HTTP status codes in a more representative format
+    private static final int HTTP_OK = 200;
+    private static final int HTTP_MOVED_PERMANENTLY = 301;
+    private static final int HTTP_FOUND = 302;
+    private static final int HTTP_SEE_OTHER = 303;
+    private static final int HTTP_NOT_MODIFIED = 304;
+    private static final int HTTP_TEMPORARY_REDIRECT = 307;
+    private static final int HTTP_AUTH = 401;
+    private static final int HTTP_NOT_FOUND = 404;
+    private static final int HTTP_PROXY_AUTH = 407;
+
+    private static int sNativeLoaderCount;
+
+    private final ByteArrayBuilder mDataBuilder = new ByteArrayBuilder(8192);
+
+    private String   mUrl;
+    private WebAddress mUri;
+    private boolean  mPermanent;
+    private String   mOriginalUrl;
+    private Context  mContext;
+    private BrowserFrame mBrowserFrame;
+    private int      mNativeLoader;
+    private String   mMimeType;
+    private String   mEncoding;
+    private String   mTransferEncoding;
+    private int      mStatusCode;
+    private String   mStatusText;
+    public long mContentLength; // Content length of the incoming data
+    private boolean  mCancelled;  // The request has been cancelled.
+    private boolean  mAuthFailed;  // indicates that the prev. auth failed
+    private CacheLoader mCacheLoader;
+    private CacheManager.CacheResult mCacheResult;
+    private HttpAuthHeader mAuthHeader;
+    private int      mErrorID = OK;
+    private String   mErrorDescription;
+    private SslError mSslError;
+    private RequestHandle mRequestHandle;
+
+    // Request data. It is only valid when we are doing a load from the
+    // cache. It is needed if the cache returns a redirect
+    private String mMethod;
+    private Map<String, String> mRequestHeaders;
+    private byte[] mPostData;
+    private boolean mIsHighPriority;
+    // Flag to indicate that this load is synchronous.
+    private boolean mSynchronous;
+    private Vector<Message> mMessageQueue;
+
+    // Does this loader correspond to the main-frame top-level page?
+    private boolean mIsMainPageLoader;
+
+    private Headers mHeaders;
+
+    // =========================================================================
+    // Public functions
+    // =========================================================================
+
+    public static LoadListener getLoadListener(
+            Context context, BrowserFrame frame, String url,
+            int nativeLoader, boolean synchronous, boolean isMainPageLoader) {
+
+        sNativeLoaderCount += 1;
+        return new LoadListener(
+            context, frame, url, nativeLoader, synchronous, isMainPageLoader);
+    }
+
+    public static int getNativeLoaderCount() {
+        return sNativeLoaderCount;
+    }
+
+    LoadListener(Context context, BrowserFrame frame, String url,
+            int nativeLoader, boolean synchronous, boolean isMainPageLoader) {
+        if (Config.LOGV) {
+            Log.v(LOGTAG, "LoadListener constructor url=" + url);
+        }
+        mContext = context;
+        mBrowserFrame = frame;
+        setUrl(url);
+        mNativeLoader = nativeLoader;
+        mMimeType = "";
+        mEncoding = "";
+        mSynchronous = synchronous;
+        if (synchronous) {
+            mMessageQueue = new Vector<Message>();
+        }
+        mIsMainPageLoader = isMainPageLoader;
+    }
+
+    /**
+     * We keep a count of refs to the nativeLoader so we do not create
+     * so many LoadListeners that the GREFs blow up
+     */
+    private void clearNativeLoader() {
+        sNativeLoaderCount -= 1;
+        mNativeLoader = 0;
+    }
+
+    /*
+     * This message handler is to facilitate communication between the network
+     * thread and the browser thread.
+     */
+    public void handleMessage(Message msg) {
+        switch (msg.what) {
+            case MSG_CONTENT_HEADERS:
+                /*
+                 * This message is sent when the LoadListener has headers
+                 * available. The headers are sent onto WebCore to see what we
+                 * should do with them.
+                 */
+                handleHeaders((Headers) msg.obj);
+                break;
+
+            case MSG_CONTENT_DATA:
+                /*
+                 * This message is sent when the LoadListener has data available
+                 * in it's data buffer. This data buffer could be filled from a
+                 * file (this thread) or from http (Network thread).
+                 */
+                if (mNativeLoader != 0 && !ignoreCallbacks()) {
+                    commitLoad();
+                }
+                break;
+
+            case MSG_CONTENT_FINISHED:
+                /*
+                 * This message is sent when the LoadListener knows that the
+                 * load is finished. This message is not sent in the case of an
+                 * error.
+                 *
+                 */
+                handleEndData();
+                break;
+
+            case MSG_CONTENT_ERROR:
+                /*
+                 * This message is sent when a load error has occured. The
+                 * LoadListener will clean itself up.
+                 */
+                handleError(msg.arg1, (String) msg.obj);
+                break;
+
+            case MSG_LOCATION_CHANGED:
+                /*
+                 * This message is sent from LoadListener.endData to inform the
+                 * browser activity that the location of the top level page
+                 * changed.
+                 */
+                doRedirect();
+                break;
+
+            case MSG_LOCATION_CHANGED_REQUEST:
+                /*
+                 * This message is sent from endData on receipt of a 307
+                 * Temporary Redirect in response to a POST -- the user must
+                 * confirm whether to continue loading. If the user says Yes,
+                 * we simply call MSG_LOCATION_CHANGED. If the user says No,
+                 * we call MSG_CONTENT_FINISHED.
+                 */
+                Message contMsg = obtainMessage(MSG_LOCATION_CHANGED);
+                Message stopMsg = obtainMessage(MSG_CONTENT_FINISHED);
+                mBrowserFrame.getCallbackProxy().onFormResubmission(
+                        stopMsg, contMsg);
+                break;
+
+            case MSG_STATUS:
+                /*
+                 * This message is sent from the network thread when the http
+                 * stack has received the status response from the server.
+                 */
+                HashMap status = (HashMap) msg.obj;
+                handleStatus(((Integer) status.get("major")).intValue(),
+                        ((Integer) status.get("minor")).intValue(),
+                        ((Integer) status.get("code")).intValue(),
+                        (String) status.get("reason"));
+                break;
+
+            case MSG_SSL_CERTIFICATE:
+                /*
+                 * This message is sent when the network thread receives a ssl
+                 * certificate.
+                 */
+                handleCertificate((SslCertificate) msg.obj);
+                break;
+
+            case MSG_SSL_ERROR:
+                /*
+                 * This message is sent when the network thread encounters a
+                 * ssl error.
+                 */
+                handleSslError((SslError) msg.obj);
+                break;
+        }
+    }
+
+    /**
+     * @return The loader's BrowserFrame.
+     */
+    BrowserFrame getFrame() {
+        return mBrowserFrame;
+    }
+
+    Context getContext() {
+        return mContext;
+    }
+
+    /* package */ boolean isSynchronous() {
+        return mSynchronous;
+    }
+
+    /**
+     * @return True iff the load has been cancelled
+     */
+    public boolean cancelled() {
+        return mCancelled;
+    }
+
+    /**
+     * Parse the headers sent from the server.
+     * @param headers gives up the HeaderGroup
+     * IMPORTANT: as this is called from network thread, can't call native
+     * directly
+     */
+    public void headers(Headers headers) {
+        if (Config.LOGV) Log.v(LOGTAG, "LoadListener.headers");
+        sendMessageInternal(obtainMessage(MSG_CONTENT_HEADERS, headers));
+    }
+
+    // Does the header parsing work on the WebCore thread.
+    private void handleHeaders(Headers headers) {
+        if (mCancelled) return;
+        mHeaders = headers;
+        mMimeType = "";
+        mEncoding = "";
+
+        ArrayList<String> cookies = headers.getSetCookie();
+        for (int i = 0; i < cookies.size(); ++i) {
+            CookieManager.getInstance().setCookie(mUri, cookies.get(i));
+        }
+
+        long contentLength = headers.getContentLength();
+        if (contentLength != Headers.NO_CONTENT_LENGTH) {
+            mContentLength = contentLength;
+        } else {
+            mContentLength = 0;
+        }
+
+        String contentType = headers.getContentType();
+        if (contentType != null) {
+            parseContentTypeHeader(contentType);
+
+            // If we have one of "generic" MIME types, try to deduce
+            // the right MIME type from the file extension (if any):
+            if (mMimeType.equalsIgnoreCase("text/plain") ||
+                    mMimeType.equalsIgnoreCase("application/octet-stream")) {
+
+                String newMimeType = guessMimeTypeFromExtension();
+                if (newMimeType != null) {
+                    mMimeType = newMimeType;
+                }
+            } else if (mMimeType.equalsIgnoreCase("text/vnd.wap.wml")) {
+                // As we don't support wml, render it as plain text
+                mMimeType = "text/plain";
+            } else {
+                // XXX: Until the servers send us either correct xhtml or
+                // text/html, treat application/xhtml+xml as text/html.
+                // It seems that xhtml+xml and vnd.wap.xhtml+xml mime
+                // subtypes are used interchangeably. So treat them the same.
+                if (mMimeType.equalsIgnoreCase("application/xhtml+xml") ||
+                        mMimeType.equals("application/vnd.wap.xhtml+xml")) {
+                    mMimeType = "text/html";
+                }
+            }
+        } else {
+            /* Often when servers respond with 304 Not Modified or a
+               Redirect, then they don't specify a MIMEType. When this
+               occurs, the function below is called.  In the case of
+               304 Not Modified, the cached headers are used rather
+               than the headers that are returned from the server. */
+            guessMimeType();
+        }
+
+        // is it an authentication request?
+        boolean mustAuthenticate = (mStatusCode == HTTP_AUTH ||
+                mStatusCode == HTTP_PROXY_AUTH);
+        // is it a proxy authentication request?
+        boolean isProxyAuthRequest = (mStatusCode == HTTP_PROXY_AUTH);
+        // is this authentication request due to a failed attempt to
+        // authenticate ealier?
+        mAuthFailed = false;
+
+        // if we tried to authenticate ourselves last time
+        if (mAuthHeader != null) {
+            // we failed, if we must to authenticate again now and
+            // we have a proxy-ness match
+            mAuthFailed = (mustAuthenticate &&
+                    isProxyAuthRequest == mAuthHeader.isProxy());
+
+            // if we did NOT fail and last authentication request was a
+            // proxy-authentication request
+            if (!mAuthFailed && mAuthHeader.isProxy()) {
+                Network network = Network.getInstance(mContext);
+                // if we have a valid proxy set
+                if (network.isValidProxySet()) {
+                    /* The proxy credentials can be read in the WebCore thread
+                    */
+                    synchronized (network) {
+                        // save authentication credentials for pre-emptive proxy
+                        // authentication
+                        network.setProxyUsername(mAuthHeader.getUsername());
+                        network.setProxyPassword(mAuthHeader.getPassword());
+                    }
+                }
+            }
+        }
+        // it is only here that we can reset the last mAuthHeader object
+        // (if existed) and start a new one!!!
+        mAuthHeader = null;
+        if (mustAuthenticate) {
+            if (mStatusCode == HTTP_AUTH) {
+                mAuthHeader = parseAuthHeader(
+                        headers.getWwwAuthenticate());
+            } else {
+                mAuthHeader = parseAuthHeader(
+                        headers.getProxyAuthenticate());
+                // if successfully parsed the header
+                if (mAuthHeader != null) {
+                    // mark the auth-header object as a proxy
+                    mAuthHeader.setProxy();
+                }
+            }
+        }
+
+        // Only create a cache file if the server has responded positively.
+        if ((mStatusCode == HTTP_OK ||
+                mStatusCode == HTTP_FOUND ||
+                mStatusCode == HTTP_MOVED_PERMANENTLY ||
+                mStatusCode == HTTP_TEMPORARY_REDIRECT) && 
+                mNativeLoader != 0) {
+            // Content arriving from a StreamLoader (eg File, Cache or Data)
+            // will not be cached as they have the header:
+            // cache-control: no-store
+            mCacheResult = CacheManager.createCacheFile(mUrl, mStatusCode,
+                    headers, mMimeType, false);
+            if (mCacheResult != null) {
+                mCacheResult.encoding = mEncoding;
+            }
+        }
+        commitHeadersCheckRedirect();
+    }
+
+    /**
+     * @return True iff this loader is in the proxy-authenticate state.
+     */
+    boolean proxyAuthenticate() {
+        if (mAuthHeader != null) {
+            return mAuthHeader.isProxy();
+        }
+
+        return false;
+    }
+
+    /**
+     * Report the status of the response.
+     * TODO: Comments about each parameter.
+     * IMPORTANT: as this is called from network thread, can't call native
+     * directly
+     */
+    public void status(int majorVersion, int minorVersion,
+            int code, /* Status-Code value */ String reasonPhrase) {
+        if (Config.LOGV) {
+            Log.v(LOGTAG, "LoadListener: from: " + mUrl
+                    + " major: " + majorVersion
+                    + " minor: " + minorVersion
+                    + " code: " + code
+                    + " reason: " + reasonPhrase);
+        }
+        HashMap status = new HashMap();
+        status.put("major", majorVersion);
+        status.put("minor", minorVersion);
+        status.put("code", code);
+        status.put("reason", reasonPhrase);
+        sendMessageInternal(obtainMessage(MSG_STATUS, status));
+    }
+
+    // Handle the status callback on the WebCore thread.
+    private void handleStatus(int major, int minor, int code, String reason) {
+        if (mCancelled) return;
+
+        mStatusCode = code;
+        mStatusText = reason;
+        mPermanent = false;
+    }
+
+    /**
+     * Implementation of certificate handler for EventHandler.
+     * Called every time a resource is loaded via a secure
+     * connection. In this context, can be called multiple
+     * times if we have redirects
+     * @param certificate The SSL certifcate
+     * IMPORTANT: as this is called from network thread, can't call native
+     * directly
+     */
+    public void certificate(SslCertificate certificate) {
+        sendMessageInternal(obtainMessage(MSG_SSL_CERTIFICATE, certificate));
+    }
+
+    // Handle the certificate on the WebCore thread.
+    private void handleCertificate(SslCertificate certificate) {
+        // if this is the top-most main-frame page loader
+        if (mIsMainPageLoader) {
+            // update the browser frame (ie, the main frame)
+            mBrowserFrame.certificate(certificate);
+        }
+    }
+
+    /**
+     * Implementation of error handler for EventHandler.
+     * Subclasses should call this method to have error fields set.
+     * @param id The error id described by EventHandler.
+     * @param description A string description of the error.
+     * IMPORTANT: as this is called from network thread, can't call native
+     * directly
+     */
+    public void error(int id, String description) {
+        if (Config.LOGV) {
+            Log.v(LOGTAG, "LoadListener.error url:" +
+                    url() + " id:" + id + " description:" + description);
+        }
+        sendMessageInternal(obtainMessage(MSG_CONTENT_ERROR, id, 0, description));
+    }
+
+    // Handle the error on the WebCore thread.
+    private void handleError(int id, String description) {
+        mErrorID = id;
+        mErrorDescription = description;
+        detachRequestHandle();
+        notifyError();
+        tearDown();
+    }
+
+    /**
+     * Add data to the internal collection of data. This function is used by
+     * the data: scheme, about: scheme and http/https schemes.
+     * @param data A byte array containing the content.
+     * @param length The length of data.
+     * IMPORTANT: as this is called from network thread, can't call native
+     * directly
+     * XXX: Unlike the other network thread methods, this method can do the
+     * work of decoding the data and appending it to the data builder because
+     * mDataBuilder is a thread-safe structure.
+     */
+    public void data(byte[] data, int length) {
+        if (Config.LOGV) {
+            Log.v(LOGTAG, "LoadListener.data(): url: " + url());
+        }
+
+        // Decode base64 data
+        // Note: It's fine that we only decode base64 here and not in the other
+        // data call because the only caller of the stream version is not
+        // base64 encoded.
+        if ("base64".equalsIgnoreCase(mTransferEncoding)) {
+            if (length < data.length) {
+                byte[] trimmedData = new byte[length];
+                System.arraycopy(data, 0, trimmedData, 0, length);
+                data = trimmedData;
+            }
+            data = Base64.decodeBase64(data);
+            length = data.length;
+        }
+        // Synchronize on mData because commitLoad may write mData to WebCore
+        // and we don't want to replace mData or mDataLength at the same time
+        // as a write.
+        boolean sendMessage = false;
+        synchronized (mDataBuilder) {
+            sendMessage = mDataBuilder.isEmpty();
+            mDataBuilder.append(data, 0, length);
+        }
+        if (sendMessage) {
+            // Send a message whenever data comes in after a write to WebCore
+            sendMessageInternal(obtainMessage(MSG_CONTENT_DATA));
+        }
+    }
+
+    /**
+     * Event handler's endData call. Send a message to the handler notifying
+     * them that the data has finished.
+     * IMPORTANT: as this is called from network thread, can't call native
+     * directly
+     */
+    public void endData() {
+        if (Config.LOGV) {
+            Log.v(LOGTAG, "LoadListener.endData(): url: " + url());
+        }
+        sendMessageInternal(obtainMessage(MSG_CONTENT_FINISHED));
+    }
+
+    // Handle the end of data.
+    private void handleEndData() {
+        if (mCancelled) return;
+
+        switch (mStatusCode) {
+            case HTTP_MOVED_PERMANENTLY:
+                // 301 - permanent redirect
+                mPermanent = true;
+            case HTTP_FOUND:
+            case HTTP_SEE_OTHER:
+            case HTTP_TEMPORARY_REDIRECT:
+                // 301, 302, 303, and 307 - redirect
+                if (mStatusCode == HTTP_TEMPORARY_REDIRECT) {
+                    if (mRequestHandle != null && 
+                                mRequestHandle.getMethod().equals("POST")) {
+                        sendMessageInternal(obtainMessage(
+                                MSG_LOCATION_CHANGED_REQUEST));  
+                    } else if (mMethod != null && mMethod.equals("POST")) {
+                        sendMessageInternal(obtainMessage(
+                                MSG_LOCATION_CHANGED_REQUEST));
+                    } else {
+                        sendMessageInternal(obtainMessage(MSG_LOCATION_CHANGED));
+                    }
+                } else {
+                    sendMessageInternal(obtainMessage(MSG_LOCATION_CHANGED));
+                }
+                return;
+
+            case HTTP_AUTH:
+            case HTTP_PROXY_AUTH:
+                // According to rfc2616, the response for HTTP_AUTH must include
+                // WWW-Authenticate header field and the response for 
+                // HTTP_PROXY_AUTH must include Proxy-Authenticate header field.
+                if (mAuthHeader != null &&
+                        (Network.getInstance(mContext).isValidProxySet() ||
+                         !mAuthHeader.isProxy())) {
+                    Network.getInstance(mContext).handleAuthRequest(this);
+                    return;
+                }
+                break;  // use default
+
+            case HTTP_NOT_MODIFIED:
+                // Server could send back NOT_MODIFIED even if we didn't
+                // ask for it, so make sure we have a valid CacheLoader
+                // before calling it.
+                if (mCacheLoader != null) {
+                    detachRequestHandle();
+                    mCacheLoader.load();
+                    if (Config.LOGV) {
+                        Log.v(LOGTAG, "LoadListener cache load url=" + url());
+                    }
+                    return;
+                }
+                break;  // use default
+
+            case HTTP_NOT_FOUND:
+                // Not an error, the server can send back content.
+            default:
+                break;
+        }
+        detachRequestHandle();
+        tearDown();
+    }
+
+    /* This method is called from CacheLoader when the initial request is
+     * serviced by the Cache. */
+    /* package */ void setCacheLoader(CacheLoader c) {
+        mCacheLoader = c;
+    }
+
+    /**
+     * Check the cache for the current URL, and load it if it is valid.
+     *
+     * @param headers for the request
+     * @return true if cached response is used.
+     */
+    boolean checkCache(Map<String, String> headers) {
+        // Get the cache file name for the current URL
+        CacheResult result = CacheManager.getCacheFile(url(),
+                headers);
+
+        // Go ahead and set the cache loader to null in case the result is
+        // null.
+        mCacheLoader = null;
+
+        if (result != null) {
+            // The contents of the cache may need to be revalidated so just
+            // remember the cache loader in the case that the server responds
+            // positively to the cached content. This is also used to detect if
+            // a redirect came from the cache.
+            mCacheLoader = new CacheLoader(this, result);
+
+            // If I got a cachedUrl and the revalidation header was not
+            // added, then the cached content valid, we should use it.
+            if (!headers.containsKey(
+                    CacheManager.HEADER_KEY_IFNONEMATCH) &&
+                    !headers.containsKey(
+                            CacheManager.HEADER_KEY_IFMODIFIEDSINCE)) {
+                if (Config.LOGV) {
+                    Log.v(LOGTAG, "FrameLoader: HTTP URL in cache " +
+                            "and usable: " + url());
+                }
+                // Load the cached file
+                mCacheLoader.load();
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * SSL certificate error callback. Handles SSL error(s) on the way up
+     * to the user.
+     * IMPORTANT: as this is called from network thread, can't call native
+     * directly
+     */
+    public void handleSslErrorRequest(SslError error) {
+        if (Config.LOGV) {
+            Log.v(LOGTAG,
+                    "LoadListener.handleSslErrorRequest(): url:" + url() +
+                    " primary error: " + error.getPrimaryError() +
+                    " certificate: " + error.getCertificate());
+        }
+        sendMessageInternal(obtainMessage(MSG_SSL_ERROR, error));
+    }
+
+    // Handle the ssl error on the WebCore thread.
+    private void handleSslError(SslError error) {
+        if (!mCancelled) {
+            mSslError = error;
+            Network.getInstance(mContext).handleSslErrorRequest(this);
+        }
+    }
+
+    /**
+     * @return HTTP authentication realm or null if none.
+     */
+    String realm() {
+        if (mAuthHeader == null) {
+            return null;
+        } else {
+            return mAuthHeader.getRealm();
+        }
+    }
+
+    /**
+     * Returns true iff an HTTP authentication problem has
+     * occured (credentials invalid).
+     */
+    boolean authCredentialsInvalid() {
+        // if it is digest and the nonce is stale, we just
+        // resubmit with a new nonce
+        return (mAuthFailed &&
+                !(mAuthHeader.isDigest() && mAuthHeader.getStale()));
+    }
+
+    /**
+     * @return The last SSL error or null if there is none
+     */
+    SslError sslError() {
+        return mSslError;
+    }
+
+    /**
+     * Handles SSL error(s) on the way down from the user
+     * (the user has already provided their feedback).
+     */
+    void handleSslErrorResponse(boolean proceed) {
+        if (mRequestHandle != null) {
+            mRequestHandle.handleSslErrorResponse(proceed);
+        }
+    }
+
+    /**
+     * Uses user-supplied credentials to restar a request.
+     */
+    void handleAuthResponse(String username, String password) {
+        if (Config.LOGV) {
+            Log.v(LOGTAG, "LoadListener.handleAuthResponse: url: " + mUrl
+                    + " username: " + username
+                    + " password: " + password);
+        }
+
+        // create and queue an authentication-response
+        if (username != null && password != null) {
+            if (mAuthHeader != null && mRequestHandle != null) {
+                mAuthHeader.setUsername(username);
+                mAuthHeader.setPassword(password);
+
+                int scheme = mAuthHeader.getScheme();
+                if (scheme == HttpAuthHeader.BASIC) {
+                    // create a basic response
+                    boolean isProxy = mAuthHeader.isProxy();
+
+                    mRequestHandle.setupBasicAuthResponse(isProxy,
+                            username, password);
+                } else {
+                    if (scheme == HttpAuthHeader.DIGEST) {
+                        // create a digest response
+                        boolean isProxy = mAuthHeader.isProxy();
+
+                        String realm     = mAuthHeader.getRealm();
+                        String nonce     = mAuthHeader.getNonce();
+                        String qop       = mAuthHeader.getQop();
+                        String algorithm = mAuthHeader.getAlgorithm();
+                        String opaque    = mAuthHeader.getOpaque();
+
+                        mRequestHandle.setupDigestAuthResponse
+                                (isProxy, username, password, realm,
+                                 nonce, qop, algorithm, opaque);
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * This is called when a request can be satisfied by the cache, however,
+     * the cache result could be a redirect. In this case we need to issue
+     * the network request.
+     * @param method
+     * @param headers
+     * @param postData
+     * @param isHighPriority
+     */
+    void setRequestData(String method, Map<String, String> headers, 
+            byte[] postData, boolean isHighPriority) {
+        mMethod = method;
+        mRequestHeaders = headers;
+        mPostData = postData;
+        mIsHighPriority = isHighPriority;
+    }
+
+    /**
+     * @return The current URL associated with this load.
+     */
+    String url() {
+        return mUrl;
+    }
+
+    /**
+     * @return The current WebAddress associated with this load.
+     */
+    WebAddress getWebAddress() {
+        return mUri;
+    }
+
+    /**
+     * @return URL hostname (current URL).
+     */
+    String host() {
+        if (mUri != null) {
+            return mUri.mHost;
+        }
+
+        return null;
+    }
+
+    /**
+     * @return The original URL associated with this load.
+     */
+    String originalUrl() {
+        if (mOriginalUrl != null) {
+            return mOriginalUrl;
+        } else {
+            return mUrl;
+        }
+    }
+
+    void attachRequestHandle(RequestHandle requestHandle) {
+        if (Config.LOGV) {
+            Log.v(LOGTAG, "LoadListener.attachRequestHandle(): " +
+                    "requestHandle: " +  requestHandle);
+        }
+        mRequestHandle = requestHandle;
+    }
+
+    void detachRequestHandle() {
+        if (Config.LOGV) {
+            Log.v(LOGTAG, "LoadListener.detachRequestHandle(): " +
+                    "requestHandle: " + mRequestHandle);
+        }
+        mRequestHandle = null;
+    }
+
+    /*
+     * This function is called from native WebCore code to
+     * notify this LoadListener that the content it is currently
+     * downloading should be saved to a file and not sent to
+     * WebCore.
+     */
+    void downloadFile() {
+        // Setting the Cache Result to null ensures that this
+        // content is not added to the cache
+        mCacheResult = null;
+        
+        // Inform the client that they should download a file
+        mBrowserFrame.getCallbackProxy().onDownloadStart(url(), 
+                mBrowserFrame.getUserAgentString(),
+                mHeaders.getContentDisposition(), 
+                mMimeType, mContentLength);
+
+        // Cancel the download. We need to stop the http load.
+        // The native loader object will get cleared by the call to
+        // cancel() but will also be cleared on the WebCore side
+        // when this function returns.
+        cancel();
+    }
+    
+    /*
+     * This function is called from native WebCore code to
+     * find out if the given URL is in the cache, and if it can
+     * be used. This is just for forward/back navigation to a POST
+     * URL.
+     */
+    static boolean willLoadFromCache(String url) {
+        boolean inCache = CacheManager.getCacheFile(url, null) != null;
+        if (Config.LOGV) {
+            Log.v(LOGTAG, "willLoadFromCache: " + url + " in cache: " + 
+                    inCache);
+        }
+        return inCache;
+    }
+
+    /*
+     * Reset the cancel flag. This is used when we are resuming a stopped
+     * download. To suspend a download, we cancel it. It can also be cancelled
+     * when it has run out of disk space. In this situation, the download
+     * can be resumed.
+     */
+    void resetCancel() {
+        mCancelled = false;
+    }
+
+    String mimeType() {
+        return mMimeType;
+    }
+
+    /*
+     * Return the size of the content being downloaded. This represents the
+     * full content size, even under the situation where the download has been
+     * resumed after interruption.
+     *
+     * @ return full content size
+     */
+    long contentLength() {
+        return mContentLength;
+    }
+
+    // Commit the headers if the status code is not a redirect.
+    private void commitHeadersCheckRedirect() {
+        if (mCancelled) return;
+
+        // do not call webcore if it is redirect. According to the code in
+        // InspectorController::willSendRequest(), the response is only updated
+        // when it is not redirect.
+        if ((mStatusCode >= 301 && mStatusCode <= 303) || mStatusCode == 307) {
+            return;
+        }
+
+        commitHeaders();
+    }
+
+    // This commits the headers without checking the response status code.
+    private void commitHeaders() {
+        // Commit the headers to WebCore
+        int nativeResponse = createNativeResponse();
+        // The native code deletes the native response object.
+        nativeReceivedResponse(nativeResponse);
+    }
+
+    /**
+     * Create a WebCore response object so that it can be used by
+     * nativeReceivedResponse or nativeRedirectedToUrl
+     * @return native response pointer
+     */
+    private int createNativeResponse() {
+        // The reason we change HTTP_NOT_MODIFIED to HTTP_OK is because we know
+        // that WebCore never sends the if-modified-since header. Our
+        // CacheManager does it for us. If the server responds with a 304, then
+        // we treat it like it was a 200 code and proceed with loading the file
+        // from the cache.
+        int statusCode = mStatusCode == HTTP_NOT_MODIFIED
+                ? HTTP_OK : mStatusCode;
+        // pass content-type content-length and content-encoding
+        final int nativeResponse = nativeCreateResponse(
+                mUrl, statusCode, mStatusText,
+                mMimeType, mContentLength, mEncoding,
+                mCacheResult == null ? 0 : mCacheResult.expires / 1000);
+        if (mHeaders != null) {
+            mHeaders.getHeaders(new Headers.HeaderCallback() {
+                    public void header(String name, String value) {
+                        nativeSetResponseHeader(nativeResponse, name, value);
+                    }
+                });
+        }
+        return nativeResponse;
+    }
+
+    /**
+     * Commit the load.  It should be ok to call repeatedly but only before
+     * tearDown is called.
+     */
+    private void commitLoad() {
+        if (mCancelled) return;
+
+        // Give the data to WebKit now
+        PerfChecker checker = new PerfChecker();
+        ByteArrayBuilder.Chunk c;
+        while (true) {
+            c = mDataBuilder.getFirstChunk();
+            if (c == null) break;
+
+            if (c.mLength != 0) {
+                if (mCacheResult != null) {
+                    try {
+                        mCacheResult.outStream.write(c.mArray, 0, c.mLength);
+                    } catch (IOException e) {
+                        mCacheResult = null;
+                    }
+                }
+                nativeAddData(c.mArray, c.mLength);
+            }
+            mDataBuilder.releaseChunk(c);
+            checker.responseAlert("res nativeAddData");
+        }
+    }
+
+    /**
+     * Tear down the load. Subclasses should clean up any mess because of
+     * cancellation or errors during the load.
+     */
+    void tearDown() {
+        if (mCacheResult != null) {
+            if (getErrorID() == OK) {
+                CacheManager.saveCacheFile(mUrl, mCacheResult);
+            }
+
+            // we need to reset mCacheResult to be null
+            // resource loader's tearDown will call into WebCore's
+            // nativeFinish, which in turn calls loader.cancel().
+            // If we don't reset mCacheFile, the file will be deleted.
+            mCacheResult = null;
+        }
+        if (mNativeLoader != 0) {
+            PerfChecker checker = new PerfChecker();
+            nativeFinished();
+            checker.responseAlert("res nativeFinished");
+            clearNativeLoader();
+        }
+    }
+
+    /**
+     * Helper for getting the error ID.
+     * @return errorID.
+     */
+    private int getErrorID() {
+        return mErrorID;
+    }
+
+    /**
+     * Return the error description.
+     * @return errorDescription.
+     */
+    private String getErrorDescription() {
+        return mErrorDescription;
+    }
+
+    /**
+     * Notify the loader we encountered an error.
+     */
+    void notifyError() {
+        if (mNativeLoader != 0) {
+            String description = getErrorDescription();
+            if (description == null) description = "";
+            nativeError(getErrorID(), description, url());
+            clearNativeLoader();
+        }
+    }
+
+    /**
+     * Cancel a request.
+     * FIXME: This will only work if the request has yet to be handled. This
+     * is in no way guarenteed if requests are served in a separate thread.
+     * It also causes major problems if cancel is called during an
+     * EventHandler's method call.
+     */
+    public void cancel() {
+        if (Config.LOGV) {
+            if (mRequestHandle == null) {
+                Log.v(LOGTAG, "LoadListener.cancel(): no requestHandle");
+            } else {
+                Log.v(LOGTAG, "LoadListener.cancel()");
+            }
+        }
+        if (mRequestHandle != null) {
+            mRequestHandle.cancel();
+            mRequestHandle = null;
+        }
+
+        mCacheResult = null;
+        mCancelled = true;
+
+        clearNativeLoader();
+    }
+
+    // This count is transferred from RequestHandle to LoadListener when
+    // loading from the cache so that we can detect redirect loops that switch
+    // between the network and the cache.
+    private int mCacheRedirectCount;
+
+    /*
+     * Perform the actual redirection. This involves setting up the new URL,
+     * informing WebCore and then telling the Network to start loading again.
+     */
+    private void doRedirect() {
+        // as cancel() can cancel the load before doRedirect() is
+        // called through handleMessage, needs to check to see if we
+        // are canceled before proceed
+        if (mCancelled) {
+            return;
+        }
+
+        // Do the same check for a redirect loop that
+        // RequestHandle.setupRedirect does.
+        if (mCacheRedirectCount >= RequestHandle.MAX_REDIRECT_COUNT) {
+            handleError(EventHandler.ERROR_REDIRECT_LOOP, mContext.getString(
+                    R.string.httpErrorRedirectLoop));
+            return;
+        }
+
+        String redirectTo = mHeaders.getLocation();
+        if (redirectTo != null) {
+            int nativeResponse = createNativeResponse();
+            redirectTo =
+                    nativeRedirectedToUrl(mUrl, redirectTo, nativeResponse);
+            // nativeRedirectedToUrl() may call cancel(), e.g. when redirect
+            // from a https site to a http site, check mCancelled again
+            if (mCancelled) {
+                return;
+            }
+            if (redirectTo == null) {
+                Log.d(LOGTAG, "Redirection failed for "
+                        + mHeaders.getLocation());
+                cancel();
+                return;
+            } else if (!URLUtil.isNetworkUrl(redirectTo)) {
+                final String text = mContext
+                        .getString(R.string.open_permission_deny)
+                        + "\n" + redirectTo;
+                nativeAddData(text.getBytes(), text.length());
+                nativeFinished();
+                clearNativeLoader();
+                return;
+            }
+
+            if (mOriginalUrl == null) {
+                mOriginalUrl = mUrl;
+            }
+
+            // Cache the redirect response
+            if (mCacheResult != null) {
+                if (getErrorID() == OK) {
+                    CacheManager.saveCacheFile(mUrl, mCacheResult);
+                }
+                mCacheResult = null;
+            }
+
+            setUrl(redirectTo);
+
+            // Redirect may be in the cache
+            if (mRequestHeaders == null) {
+                mRequestHeaders = new HashMap<String, String>();
+            }
+            boolean fromCache = false;
+            if (mCacheLoader != null) {
+                // This is a redirect from the cache loader. Increment the
+                // redirect count to avoid redirect loops.
+                mCacheRedirectCount++;
+                fromCache = true;
+            }
+            if (!checkCache(mRequestHeaders)) {
+                // mRequestHandle can be null when the request was satisfied
+                // by the cache, and the cache returned a redirect
+                if (mRequestHandle != null) {
+                    mRequestHandle.setupRedirect(redirectTo, mStatusCode,
+                            mRequestHeaders);
+                } else {
+                    // If the original request came from the cache, there is no
+                    // RequestHandle, we have to create a new one through
+                    // Network.requestURL.
+                    Network network = Network.getInstance(getContext());
+                    if (!network.requestURL(mMethod, mRequestHeaders,
+                            mPostData, this, mIsHighPriority)) {
+                        // Signal a bad url error if we could not load the
+                        // redirection.
+                        handleError(EventHandler.ERROR_BAD_URL,
+                                mContext.getString(R.string.httpErrorBadUrl));
+                        return;
+                    }
+                }
+                if (fromCache) {
+                    // If we are coming from a cache load, we need to transfer
+                    // the redirect count to the new (or old) RequestHandle to
+                    // keep the redirect count in sync.
+                    mRequestHandle.setRedirectCount(mCacheRedirectCount);
+                }
+            } else if (!fromCache) {
+                // Switching from network to cache means we need to grab the
+                // redirect count from the RequestHandle to keep the count in
+                // sync. Add 1 to account for the current redirect.
+                mCacheRedirectCount = mRequestHandle.getRedirectCount() + 1;
+            }
+            // Clear the buffered data since the redirect is valid.
+            mDataBuilder.clear();
+        } else {
+            commitHeaders();
+            commitLoad();
+            tearDown();
+        }
+
+        if (Config.LOGV) {
+            Log.v(LOGTAG, "LoadListener.onRedirect(): redirect to: " +
+                    redirectTo);
+        }
+    }
+
+    /**
+     * Parses the content-type header.
+     */
+    private static final Pattern CONTENT_TYPE_PATTERN =
+            Pattern.compile("^([a-zA-Z\\*]+/[\\w\\+\\*-]+[\\.[\\w\\+-]+]*)$");
+
+    private void parseContentTypeHeader(String contentType) {
+        if (Config.LOGV) {
+            Log.v(LOGTAG, "LoadListener.parseContentTypeHeader: " +
+                    "contentType: " + contentType);
+        }
+
+        if (contentType != null) {
+            int i = contentType.indexOf(';');
+            if (i >= 0) {
+                mMimeType = contentType.substring(0, i);
+
+                int j = contentType.indexOf('=', i);
+                if (j > 0) {
+                    i = contentType.indexOf(';', j);
+                    if (i < j) {
+                        i = contentType.length();
+                    }
+                    mEncoding = contentType.substring(j + 1, i);
+                } else {
+                    mEncoding = contentType.substring(i + 1);
+                }
+                // Trim excess whitespace.
+                mEncoding = mEncoding.trim();
+
+                if (i < contentType.length() - 1) {
+                    // for data: uri the mimeType and encoding have
+                    // the form image/jpeg;base64 or text/plain;charset=utf-8
+                    // or text/html;charset=utf-8;base64
+                    mTransferEncoding = contentType.substring(i + 1).trim();
+                }
+            } else {
+                mMimeType = contentType;
+            }
+
+            // Trim leading and trailing whitespace
+            mMimeType = mMimeType.trim();
+
+            try {
+                Matcher m = CONTENT_TYPE_PATTERN.matcher(mMimeType);
+                if (m.find()) {
+                    mMimeType = m.group(1);
+                } else {
+                    guessMimeType();
+                }
+            } catch (IllegalStateException ex) {
+                guessMimeType();
+            }
+        }
+    }
+
+    /**
+     * @return The HTTP-authentication object or null if there
+     * is no supported scheme in the header.
+     * If there are several valid schemes present, we pick the
+     * strongest one. If there are several schemes of the same
+     * strength, we pick the one that comes first.
+     */
+    private HttpAuthHeader parseAuthHeader(String header) {
+        if (header != null) {
+            int posMax = 256;
+            int posLen = 0;
+            int[] pos = new int [posMax];
+
+            int headerLen = header.length();
+            if (headerLen > 0) {
+                // first, we find all unquoted instances of 'Basic' and 'Digest'
+                boolean quoted = false;
+                for (int i = 0; i < headerLen && posLen < posMax; ++i) {
+                    if (header.charAt(i) == '\"') {
+                        quoted = !quoted;
+                    } else {
+                        if (!quoted) {
+                            if (header.regionMatches(true, i,
+                                    HttpAuthHeader.BASIC_TOKEN, 0,
+                                    HttpAuthHeader.BASIC_TOKEN.length())) {
+                                pos[posLen++] = i;
+                                continue;
+                            }
+
+                            if (header.regionMatches(true, i,
+                                    HttpAuthHeader.DIGEST_TOKEN, 0,
+                                    HttpAuthHeader.DIGEST_TOKEN.length())) {
+                                pos[posLen++] = i;
+                                continue;
+                            }
+                        }
+                    }
+                }
+            }
+
+            if (posLen > 0) {
+                // consider all digest schemes first (if any)
+                for (int i = 0; i < posLen; i++) {
+                    if (header.regionMatches(true, pos[i],
+                                HttpAuthHeader.DIGEST_TOKEN, 0,
+                                HttpAuthHeader.DIGEST_TOKEN.length())) {
+                        String sub = header.substring(pos[i],
+                                (i + 1 < posLen ? pos[i + 1] : headerLen));
+
+                        HttpAuthHeader rval = new HttpAuthHeader(sub);
+                        if (rval.isSupportedScheme()) {
+                            // take the first match
+                            return rval;
+                        }
+                    }
+                }
+
+                // ...then consider all basic schemes (if any)
+                for (int i = 0; i < posLen; i++) {
+                    if (header.regionMatches(true, pos[i],
+                                HttpAuthHeader.BASIC_TOKEN, 0,
+                                HttpAuthHeader.BASIC_TOKEN.length())) {
+                        String sub = header.substring(pos[i],
+                                (i + 1 < posLen ? pos[i + 1] : headerLen));
+
+                        HttpAuthHeader rval = new HttpAuthHeader(sub);
+                        if (rval.isSupportedScheme()) {
+                            // take the first match
+                            return rval;
+                        }
+                    }
+                }
+            }
+        }
+
+        return null;
+    }
+
+    /**
+     * If the content is a redirect or not modified we should not send
+     * any data into WebCore as that will cause it create a document with
+     * the data, then when we try to provide the real content, it will assert.
+     *
+     * @return True iff the callback should be ignored.
+     */
+    private boolean ignoreCallbacks() {
+        return (mCancelled || mAuthHeader != null ||
+                (mStatusCode > 300 && mStatusCode < 400));
+    }
+
+    /**
+     * Sets the current URL associated with this load.
+     */
+    void setUrl(String url) {
+        if (url != null) {
+            if (URLUtil.isDataUrl(url)) {
+             // Don't strip anchor as that is a valid part of the URL
+                mUrl = url;
+            } else {
+                mUrl = URLUtil.stripAnchor(url);
+            }
+            mUri = null;
+            if (URLUtil.isNetworkUrl(mUrl)) {
+                try {
+                    mUri = new WebAddress(mUrl);
+                } catch (ParseException e) {
+                    e.printStackTrace();
+                }
+            }
+        }
+    }
+
+    /**
+     * Guesses MIME type if one was not specified. Defaults to 'text/html'. In
+     * addition, tries to guess the MIME type based on the extension.
+     *
+     */
+    private void guessMimeType() {
+        // Data urls must have a valid mime type or a blank string for the mime
+        // type (implying text/plain).
+        if (URLUtil.isDataUrl(mUrl) && mMimeType.length() != 0) {
+            cancel();
+            final String text = mContext.getString(R.string.httpErrorBadUrl);
+            handleError(EventHandler.ERROR_BAD_URL, text);
+        } else {
+            // Note: This is ok because this is used only for the main content
+            // of frames. If no content-type was specified, it is fine to
+            // default to text/html.
+            mMimeType = "text/html";
+            String newMimeType = guessMimeTypeFromExtension();
+            if (newMimeType != null) {
+                mMimeType =  newMimeType;
+            }
+        }
+    }
+
+    /**
+     * guess MIME type based on the file extension.
+     */
+    private String guessMimeTypeFromExtension() {
+        // PENDING: need to normalize url
+        if (Config.LOGV) {
+            Log.v(LOGTAG, "guessMimeTypeFromExtension: mURL = " + mUrl);
+        }
+
+        String mimeType =
+                MimeTypeMap.getSingleton().getMimeTypeFromExtension(
+                        MimeTypeMap.getFileExtensionFromUrl(mUrl));
+
+        if (mimeType != null) {
+            // XXX: Until the servers send us either correct xhtml or
+            // text/html, treat application/xhtml+xml as text/html.
+            if (mimeType.equals("application/xhtml+xml")) {
+                mimeType = "text/html";
+            }
+        }
+
+        return mimeType;
+    }
+
+    /**
+     * Either send a message to ourselves or queue the message if this is a
+     * synchronous load.
+     */
+    private void sendMessageInternal(Message msg) {
+        if (mSynchronous) {
+            mMessageQueue.add(msg);
+        } else {
+            sendMessage(msg);
+        }
+    }
+
+    /**
+     * Cycle through our messages for synchronous loads.
+     */
+    /* package */ void loadSynchronousMessages() {
+        if (Config.DEBUG && !mSynchronous) {
+            throw new AssertionError();
+        }
+        // Note: this can be called twice if it is a synchronous network load,
+        // and there is a cache, but it needs to go to network to validate. If 
+        // validation succeed, the CacheLoader is used so this is first called 
+        // from http thread. Then it is called again from WebViewCore thread 
+        // after the load is completed. So make sure the queue is cleared but
+        // don't set it to null.
+        for (int size = mMessageQueue.size(); size > 0; size--) {
+            handleMessage(mMessageQueue.remove(0));
+        }
+    }
+
+    //=========================================================================
+    // native functions
+    //=========================================================================
+
+    /**
+     * Create a new native response object.
+     * @param url The url of the resource.
+     * @param statusCode The HTTP status code.
+     * @param statusText The HTTP status text.
+     * @param mimeType HTTP content-type.
+     * @param expectedLength An estimate of the content length or the length
+     *                       given by the server.
+     * @param encoding HTTP encoding.
+     * @param expireTime HTTP expires converted to seconds since the epoch.
+     * @return The native response pointer.
+     */
+    private native int nativeCreateResponse(String url, int statusCode,
+            String statusText, String mimeType, long expectedLength,
+            String encoding, long expireTime);
+
+    /**
+     * Add a response header to the native object.
+     * @param nativeResponse The native pointer.
+     * @param key String key.
+     * @param val String value.
+     */
+    private native void nativeSetResponseHeader(int nativeResponse, String key,
+            String val);
+
+    /**
+     * Dispatch the response.
+     * @param nativeResponse The native pointer.
+     */
+    private native void nativeReceivedResponse(int nativeResponse);
+
+    /**
+     * Add data to the loader.
+     * @param data Byte array of data.
+     * @param length Number of objects in data.
+     */
+    private native void nativeAddData(byte[] data, int length);
+
+    /**
+     * Tell the loader it has finished.
+     */
+    private native void nativeFinished();
+
+    /**
+     * tell the loader to redirect
+     * @param baseUrl The base url.
+     * @param redirectTo The url to redirect to.
+     * @param nativeResponse The native pointer.
+     * @return The new url that the resource redirected to.
+     */
+    private native String nativeRedirectedToUrl(String baseUrl,
+            String redirectTo, int nativeResponse);
+
+    /**
+     * Tell the loader there is error
+     * @param id
+     * @param desc
+     * @param failingUrl The url that failed.
+     */
+    private native void nativeError(int id, String desc, String failingUrl);
+
+}
diff --git a/core/java/android/webkit/MimeTypeMap.java b/core/java/android/webkit/MimeTypeMap.java
new file mode 100644
index 0000000..c9cc208
--- /dev/null
+++ b/core/java/android/webkit/MimeTypeMap.java
@@ -0,0 +1,503 @@
+/*
+ * Copyright (C) 2007 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 android.webkit;
+
+import java.util.HashMap;
+import java.util.regex.Pattern;
+
+/**
+ * Two-way map that maps MIME-types to file extensions and vice versa.
+ */
+public /* package */ class MimeTypeMap {
+
+    /**
+     * Singleton MIME-type map instance:
+     */
+    private static MimeTypeMap sMimeTypeMap;
+
+    /**
+     * MIME-type to file extension mapping:
+     */
+    private HashMap<String, String> mMimeTypeToExtensionMap;
+
+    /**
+     * File extension to MIME type mapping:
+     */
+    private HashMap<String, String> mExtensionToMimeTypeMap;
+
+
+    /**
+     * Creates a new MIME-type map.
+     */
+    private MimeTypeMap() {
+        mMimeTypeToExtensionMap = new HashMap<String, String>();
+        mExtensionToMimeTypeMap = new HashMap<String, String>();
+    }
+
+    /**
+     * Returns the file extension or an empty string iff there is no
+     * extension.
+     */
+    public static String getFileExtensionFromUrl(String url) {
+        if (url != null && url.length() > 0) {
+            int query = url.lastIndexOf('?');
+            if (query > 0) {
+                url = url.substring(0, query);
+            }
+            int filenamePos = url.lastIndexOf('/');
+            String filename =
+                0 <= filenamePos ? url.substring(filenamePos + 1) : url;
+
+            // if the filename contains special characters, we don't
+            // consider it valid for our matching purposes:
+            if (filename.length() > 0 &&
+                Pattern.matches("[a-zA-Z_0-9\\.\\-\\(\\)]+", filename)) {
+                int dotPos = filename.lastIndexOf('.');
+                if (0 <= dotPos) {
+                    return filename.substring(dotPos + 1);
+                }
+            }
+        }
+
+        return "";
+    }
+
+    /**
+     * Load an entry into the map. This does not check if the item already
+     * exists, it trusts the caller!
+     */
+    private void loadEntry(String mimeType, String extension, 
+            boolean textType) {
+        //
+        // if we have an existing x --> y mapping, we do not want to
+        // override it with another mapping x --> ?
+        // this is mostly because of the way the mime-type map below
+        // is constructed (if a mime type maps to several extensions
+        // the first extension is considered the most popular and is
+        // added first; we do not want to overwrite it later).
+        //
+        if (!mMimeTypeToExtensionMap.containsKey(mimeType)) {
+            mMimeTypeToExtensionMap.put(mimeType, extension);
+        }
+
+        //
+        // here, we don't want to map extensions to text MIME types;
+        // otherwise, we will start replacing generic text/plain and
+        // text/html with text MIME types that our platform does not
+        // understand.
+        //
+        if (!textType) {
+            mExtensionToMimeTypeMap.put(extension, mimeType);
+        }
+    }
+
+    /**
+     * @return True iff there is a mimeType entry in the map.
+     */
+    public boolean hasMimeType(String mimeType) {
+        if (mimeType != null && mimeType.length() > 0) {
+            return mMimeTypeToExtensionMap.containsKey(mimeType);
+        }
+
+        return false;
+    }
+
+    /**
+     * @return The extension for the MIME type or null iff there is none.
+     */
+    public String getMimeTypeFromExtension(String extension) {
+        if (extension != null && extension.length() > 0) {
+            return mExtensionToMimeTypeMap.get(extension);
+        }
+
+        return null;
+    }
+
+    /**
+     * @return True iff there is an extension entry in the map.
+     */
+    public boolean hasExtension(String extension) {
+        if (extension != null && extension.length() > 0) {
+            return mExtensionToMimeTypeMap.containsKey(extension);
+        }
+
+        return false;
+    }
+
+    /**
+     * @return The MIME type for the extension or null iff there is none.
+     */
+    public String getExtensionFromMimeType(String mimeType) {
+        if (mimeType != null && mimeType.length() > 0) {
+            return mMimeTypeToExtensionMap.get(mimeType);
+        }
+
+        return null;
+    }
+
+    /**
+     * @return The singleton instance of the MIME-type map.
+     */
+    public static MimeTypeMap getSingleton() {
+        if (sMimeTypeMap == null) {
+            sMimeTypeMap = new MimeTypeMap();
+
+            // The following table is based on /etc/mime.types data minus
+            // chemical/* MIME types and MIME types that don't map to any
+            // file extensions. We also exclude top-level domain names to
+            // deal with cases like:
+            //
+            // mail.google.com/a/google.com
+            //
+            // and "active" MIME types (due to potential security issues).
+            //
+            // Also, notice that not all data from this table is actually
+            // added (see loadEntry method for more details).
+
+            sMimeTypeMap.loadEntry("application/andrew-inset", "ez", false);
+            sMimeTypeMap.loadEntry("application/dsptype", "tsp", false);
+            sMimeTypeMap.loadEntry("application/futuresplash", "spl", false);
+            sMimeTypeMap.loadEntry("application/hta", "hta", false);
+            sMimeTypeMap.loadEntry("application/mac-binhex40", "hqx", false);
+            sMimeTypeMap.loadEntry("application/mac-compactpro", "cpt", false);
+            sMimeTypeMap.loadEntry("application/mathematica", "nb", false);
+            sMimeTypeMap.loadEntry("application/msaccess", "mdb", false);
+            sMimeTypeMap.loadEntry("application/oda", "oda", false);
+            sMimeTypeMap.loadEntry("application/ogg", "ogg", false);
+            sMimeTypeMap.loadEntry("application/pdf", "pdf", false);
+            sMimeTypeMap.loadEntry("application/pgp-keys", "key", false);
+            sMimeTypeMap.loadEntry("application/pgp-signature", "pgp", false);
+            sMimeTypeMap.loadEntry("application/pics-rules", "prf", false);
+            sMimeTypeMap.loadEntry("application/rar", "rar", false);
+            sMimeTypeMap.loadEntry("application/rdf+xml", "rdf", false);
+            sMimeTypeMap.loadEntry("application/rss+xml", "rss", false);
+            sMimeTypeMap.loadEntry("application/zip", "zip", false);
+            sMimeTypeMap.loadEntry("application/vnd.android.package-archive", 
+                    "apk", false);
+            sMimeTypeMap.loadEntry("application/vnd.cinderella", "cdy", false);
+            sMimeTypeMap.loadEntry("application/vnd.ms-pki.stl", "stl", false);
+            sMimeTypeMap.loadEntry(
+                    "application/vnd.oasis.opendocument.database", "odb", 
+                    false);
+            sMimeTypeMap.loadEntry(
+                    "application/vnd.oasis.opendocument.formula", "odf", 
+                    false);
+            sMimeTypeMap.loadEntry(
+                    "application/vnd.oasis.opendocument.graphics", "odg", 
+                    false);
+            sMimeTypeMap.loadEntry(
+                    "application/vnd.oasis.opendocument.graphics-template",
+                    "otg", false);
+            sMimeTypeMap.loadEntry(
+                    "application/vnd.oasis.opendocument.image", "odi", false);
+            sMimeTypeMap.loadEntry(
+                    "application/vnd.oasis.opendocument.spreadsheet", "ods", 
+                    false);
+            sMimeTypeMap.loadEntry(
+                    "application/vnd.oasis.opendocument.spreadsheet-template",
+                    "ots", false);
+            sMimeTypeMap.loadEntry(
+                    "application/vnd.oasis.opendocument.text", "odt", false);
+            sMimeTypeMap.loadEntry(
+                    "application/vnd.oasis.opendocument.text-master", "odm", 
+                    false);
+            sMimeTypeMap.loadEntry(
+                    "application/vnd.oasis.opendocument.text-template", "ott", 
+                    false);
+            sMimeTypeMap.loadEntry(
+                    "application/vnd.oasis.opendocument.text-web", "oth", 
+                    false);
+            sMimeTypeMap.loadEntry("application/vnd.rim.cod", "cod", false);
+            sMimeTypeMap.loadEntry("application/vnd.smaf", "mmf", false);
+            sMimeTypeMap.loadEntry("application/vnd.stardivision.calc", "sdc", 
+                    false);
+            sMimeTypeMap.loadEntry("application/vnd.stardivision.draw", "sda", 
+                    false);
+            sMimeTypeMap.loadEntry(
+                    "application/vnd.stardivision.impress", "sdd", false);
+            sMimeTypeMap.loadEntry(
+                    "application/vnd.stardivision.impress", "sdp", false);
+            sMimeTypeMap.loadEntry("application/vnd.stardivision.math", "smf", 
+                    false);
+            sMimeTypeMap.loadEntry("application/vnd.stardivision.writer", "sdw", 
+                    false);
+            sMimeTypeMap.loadEntry("application/vnd.stardivision.writer", "vor", 
+                    false);
+            sMimeTypeMap.loadEntry(
+                    "application/vnd.stardivision.writer-global", "sgl", false);
+            sMimeTypeMap.loadEntry("application/vnd.sun.xml.calc", "sxc", 
+                    false);
+            sMimeTypeMap.loadEntry(
+                    "application/vnd.sun.xml.calc.template", "stc", false);
+            sMimeTypeMap.loadEntry("application/vnd.sun.xml.draw", "sxd", 
+                    false);
+            sMimeTypeMap.loadEntry(
+                    "application/vnd.sun.xml.draw.template", "std", false);
+            sMimeTypeMap.loadEntry("application/vnd.sun.xml.impress", "sxi", 
+                    false);
+            sMimeTypeMap.loadEntry(
+                    "application/vnd.sun.xml.impress.template", "sti", false);
+            sMimeTypeMap.loadEntry("application/vnd.sun.xml.math", "sxm", 
+                    false);
+            sMimeTypeMap.loadEntry("application/vnd.sun.xml.writer", "sxw", 
+                    false);
+            sMimeTypeMap.loadEntry(
+                    "application/vnd.sun.xml.writer.global", "sxg", false);
+            sMimeTypeMap.loadEntry(
+                    "application/vnd.sun.xml.writer.template", "stw", false);
+            sMimeTypeMap.loadEntry("application/vnd.visio", "vsd", false);
+            sMimeTypeMap.loadEntry("application/x-abiword", "abw", false);
+            sMimeTypeMap.loadEntry("application/x-apple-diskimage", "dmg", 
+                    false);
+            sMimeTypeMap.loadEntry("application/x-bcpio", "bcpio", false);
+            sMimeTypeMap.loadEntry("application/x-bittorrent", "torrent", 
+                    false);
+            sMimeTypeMap.loadEntry("application/x-cdf", "cdf", false);
+            sMimeTypeMap.loadEntry("application/x-cdlink", "vcd", false);
+            sMimeTypeMap.loadEntry("application/x-chess-pgn", "pgn", false);
+            sMimeTypeMap.loadEntry("application/x-cpio", "cpio", false);
+            sMimeTypeMap.loadEntry("application/x-debian-package", "deb", 
+                    false);
+            sMimeTypeMap.loadEntry("application/x-debian-package", "udeb", 
+                    false);
+            sMimeTypeMap.loadEntry("application/x-director", "dcr", false);
+            sMimeTypeMap.loadEntry("application/x-director", "dir", false);
+            sMimeTypeMap.loadEntry("application/x-director", "dxr", false);
+            sMimeTypeMap.loadEntry("application/x-dms", "dms", false);
+            sMimeTypeMap.loadEntry("application/x-doom", "wad", false);
+            sMimeTypeMap.loadEntry("application/x-dvi", "dvi", false);
+            sMimeTypeMap.loadEntry("application/x-flac", "flac", false);
+            sMimeTypeMap.loadEntry("application/x-font", "pfa", false);
+            sMimeTypeMap.loadEntry("application/x-font", "pfb", false);
+            sMimeTypeMap.loadEntry("application/x-font", "gsf", false);
+            sMimeTypeMap.loadEntry("application/x-font", "pcf", false);
+            sMimeTypeMap.loadEntry("application/x-font", "pcf.Z", false);
+            sMimeTypeMap.loadEntry("application/x-freemind", "mm", false);
+            sMimeTypeMap.loadEntry("application/x-futuresplash", "spl", false);
+            sMimeTypeMap.loadEntry("application/x-gnumeric", "gnumeric", false);
+            sMimeTypeMap.loadEntry("application/x-go-sgf", "sgf", false);
+            sMimeTypeMap.loadEntry("application/x-graphing-calculator", "gcf", 
+                    false);
+            sMimeTypeMap.loadEntry("application/x-gtar", "gtar", false);
+            sMimeTypeMap.loadEntry("application/x-gtar", "tgz", false);
+            sMimeTypeMap.loadEntry("application/x-gtar", "taz", false);
+            sMimeTypeMap.loadEntry("application/x-hdf", "hdf", false);
+            sMimeTypeMap.loadEntry("application/x-ica", "ica", false);
+            sMimeTypeMap.loadEntry("application/x-internet-signup", "ins", 
+                    false);
+            sMimeTypeMap.loadEntry("application/x-internet-signup", "isp", 
+                    false);
+            sMimeTypeMap.loadEntry("application/x-iphone", "iii", false);
+            sMimeTypeMap.loadEntry("application/x-iso9660-image", "iso", false);
+            sMimeTypeMap.loadEntry("application/x-jmol", "jmz", false);
+            sMimeTypeMap.loadEntry("application/x-kchart", "chrt", false);
+            sMimeTypeMap.loadEntry("application/x-killustrator", "kil", false);
+            sMimeTypeMap.loadEntry("application/x-koan", "skp", false);
+            sMimeTypeMap.loadEntry("application/x-koan", "skd", false);
+            sMimeTypeMap.loadEntry("application/x-koan", "skt", false);
+            sMimeTypeMap.loadEntry("application/x-koan", "skm", false);
+            sMimeTypeMap.loadEntry("application/x-kpresenter", "kpr", false);
+            sMimeTypeMap.loadEntry("application/x-kpresenter", "kpt", false);
+            sMimeTypeMap.loadEntry("application/x-kspread", "ksp", false);
+            sMimeTypeMap.loadEntry("application/x-kword", "kwd", false);
+            sMimeTypeMap.loadEntry("application/x-kword", "kwt", false);
+            sMimeTypeMap.loadEntry("application/x-latex", "latex", false);
+            sMimeTypeMap.loadEntry("application/x-lha", "lha", false);
+            sMimeTypeMap.loadEntry("application/x-lzh", "lzh", false);
+            sMimeTypeMap.loadEntry("application/x-lzx", "lzx", false);
+            sMimeTypeMap.loadEntry("application/x-maker", "frm", false);
+            sMimeTypeMap.loadEntry("application/x-maker", "maker", false);
+            sMimeTypeMap.loadEntry("application/x-maker", "frame", false);
+            sMimeTypeMap.loadEntry("application/x-maker", "fb", false);
+            sMimeTypeMap.loadEntry("application/x-maker", "book", false);
+            sMimeTypeMap.loadEntry("application/x-maker", "fbdoc", false);
+            sMimeTypeMap.loadEntry("application/x-mif", "mif", false);
+            sMimeTypeMap.loadEntry("application/x-ms-wmd", "wmd", false);
+            sMimeTypeMap.loadEntry("application/x-ms-wmz", "wmz", false);
+            sMimeTypeMap.loadEntry("application/x-msi", "msi", false);
+            sMimeTypeMap.loadEntry("application/x-ns-proxy-autoconfig", "pac", 
+                    false);
+            sMimeTypeMap.loadEntry("application/x-nwc", "nwc", false);
+            sMimeTypeMap.loadEntry("application/x-object", "o", false);
+            sMimeTypeMap.loadEntry("application/x-oz-application", "oza", 
+                    false);
+            sMimeTypeMap.loadEntry("application/x-pkcs7-certreqresp", "p7r", 
+                    false);
+            sMimeTypeMap.loadEntry("application/x-pkcs7-crl", "crl", false);
+            sMimeTypeMap.loadEntry("application/x-quicktimeplayer", "qtl", 
+                    false);
+            sMimeTypeMap.loadEntry("application/x-shar", "shar", false);
+            sMimeTypeMap.loadEntry("application/x-stuffit", "sit", false);
+            sMimeTypeMap.loadEntry("application/x-sv4cpio", "sv4cpio", false);
+            sMimeTypeMap.loadEntry("application/x-sv4crc", "sv4crc", false);
+            sMimeTypeMap.loadEntry("application/x-tar", "tar", false);
+            sMimeTypeMap.loadEntry("application/x-texinfo", "texinfo", false);
+            sMimeTypeMap.loadEntry("application/x-texinfo", "texi", false);
+            sMimeTypeMap.loadEntry("application/x-troff", "t", false);
+            sMimeTypeMap.loadEntry("application/x-troff", "roff", false);
+            sMimeTypeMap.loadEntry("application/x-troff-man", "man", false);
+            sMimeTypeMap.loadEntry("application/x-ustar", "ustar", false);
+            sMimeTypeMap.loadEntry("application/x-wais-source", "src", false);
+            sMimeTypeMap.loadEntry("application/x-wingz", "wz", false);
+            sMimeTypeMap.loadEntry(
+                    "application/x-webarchive", "webarchive", false); // added
+            sMimeTypeMap.loadEntry("application/x-x509-ca-cert", "crt", false);
+            sMimeTypeMap.loadEntry("application/x-xcf", "xcf", false);
+            sMimeTypeMap.loadEntry("application/x-xfig", "fig", false);
+            sMimeTypeMap.loadEntry("audio/basic", "snd", false);
+            sMimeTypeMap.loadEntry("audio/midi", "mid", false);
+            sMimeTypeMap.loadEntry("audio/midi", "midi", false);
+            sMimeTypeMap.loadEntry("audio/midi", "kar", false);
+            sMimeTypeMap.loadEntry("audio/mpeg", "mpga", false);
+            sMimeTypeMap.loadEntry("audio/mpeg", "mpega", false);
+            sMimeTypeMap.loadEntry("audio/mpeg", "mp2", false);
+            sMimeTypeMap.loadEntry("audio/mpeg", "mp3", false);
+            sMimeTypeMap.loadEntry("audio/mpeg", "m4a", false);
+            sMimeTypeMap.loadEntry("audio/mpegurl", "m3u", false);
+            sMimeTypeMap.loadEntry("audio/prs.sid", "sid", false);
+            sMimeTypeMap.loadEntry("audio/x-aiff", "aif", false);
+            sMimeTypeMap.loadEntry("audio/x-aiff", "aiff", false);
+            sMimeTypeMap.loadEntry("audio/x-aiff", "aifc", false);
+            sMimeTypeMap.loadEntry("audio/x-gsm", "gsm", false);
+            sMimeTypeMap.loadEntry("audio/x-mpegurl", "m3u", false);
+            sMimeTypeMap.loadEntry("audio/x-ms-wma", "wma", false);
+            sMimeTypeMap.loadEntry("audio/x-ms-wax", "wax", false);
+            sMimeTypeMap.loadEntry("audio/x-pn-realaudio", "ra", false);
+            sMimeTypeMap.loadEntry("audio/x-pn-realaudio", "rm", false);
+            sMimeTypeMap.loadEntry("audio/x-pn-realaudio", "ram", false);
+            sMimeTypeMap.loadEntry("audio/x-realaudio", "ra", false);
+            sMimeTypeMap.loadEntry("audio/x-scpls", "pls", false);
+            sMimeTypeMap.loadEntry("audio/x-sd2", "sd2", false);
+            sMimeTypeMap.loadEntry("audio/x-wav", "wav", false);
+            sMimeTypeMap.loadEntry("image/bmp", "bmp", false); // added
+            sMimeTypeMap.loadEntry("image/gif", "gif", false);
+            sMimeTypeMap.loadEntry("image/ico", "cur", false); // added
+            sMimeTypeMap.loadEntry("image/ico", "ico", false); // added
+            sMimeTypeMap.loadEntry("image/ief", "ief", false);
+            sMimeTypeMap.loadEntry("image/jpeg", "jpeg", false);
+            sMimeTypeMap.loadEntry("image/jpeg", "jpg", false);
+            sMimeTypeMap.loadEntry("image/jpeg", "jpe", false);
+            sMimeTypeMap.loadEntry("image/pcx", "pcx", false);
+            sMimeTypeMap.loadEntry("image/png", "png", false);
+            sMimeTypeMap.loadEntry("image/svg+xml", "svg", false);
+            sMimeTypeMap.loadEntry("image/svg+xml", "svgz", false);
+            sMimeTypeMap.loadEntry("image/tiff", "tiff", false);
+            sMimeTypeMap.loadEntry("image/tiff", "tif", false);
+            sMimeTypeMap.loadEntry("image/vnd.djvu", "djvu", false);
+            sMimeTypeMap.loadEntry("image/vnd.djvu", "djv", false);
+            sMimeTypeMap.loadEntry("image/vnd.wap.wbmp", "wbmp", false);
+            sMimeTypeMap.loadEntry("image/x-cmu-raster", "ras", false);
+            sMimeTypeMap.loadEntry("image/x-coreldraw", "cdr", false);
+            sMimeTypeMap.loadEntry("image/x-coreldrawpattern", "pat", false);
+            sMimeTypeMap.loadEntry("image/x-coreldrawtemplate", "cdt", false);
+            sMimeTypeMap.loadEntry("image/x-corelphotopaint", "cpt", false);
+            sMimeTypeMap.loadEntry("image/x-icon", "ico", false);
+            sMimeTypeMap.loadEntry("image/x-jg", "art", false);
+            sMimeTypeMap.loadEntry("image/x-jng", "jng", false);
+            sMimeTypeMap.loadEntry("image/x-ms-bmp", "bmp", false);
+            sMimeTypeMap.loadEntry("image/x-photoshop", "psd", false);
+            sMimeTypeMap.loadEntry("image/x-portable-anymap", "pnm", false);
+            sMimeTypeMap.loadEntry("image/x-portable-bitmap", "pbm", false);
+            sMimeTypeMap.loadEntry("image/x-portable-graymap", "pgm", false);
+            sMimeTypeMap.loadEntry("image/x-portable-pixmap", "ppm", false);
+            sMimeTypeMap.loadEntry("image/x-rgb", "rgb", false);
+            sMimeTypeMap.loadEntry("image/x-xbitmap", "xbm", false);
+            sMimeTypeMap.loadEntry("image/x-xpixmap", "xpm", false);
+            sMimeTypeMap.loadEntry("image/x-xwindowdump", "xwd", false);
+            sMimeTypeMap.loadEntry("model/iges", "igs", false);
+            sMimeTypeMap.loadEntry("model/iges", "iges", false);
+            sMimeTypeMap.loadEntry("model/mesh", "msh", false);
+            sMimeTypeMap.loadEntry("model/mesh", "mesh", false);
+            sMimeTypeMap.loadEntry("model/mesh", "silo", false);
+            sMimeTypeMap.loadEntry("text/calendar", "ics", true);
+            sMimeTypeMap.loadEntry("text/calendar", "icz", true);
+            sMimeTypeMap.loadEntry("text/comma-separated-values", "csv", true);
+            sMimeTypeMap.loadEntry("text/css", "css", true);
+            sMimeTypeMap.loadEntry("text/h323", "323", true);
+            sMimeTypeMap.loadEntry("text/iuls", "uls", true);
+            sMimeTypeMap.loadEntry("text/mathml", "mml", true);
+            // add it first so it will be the default for ExtensionFromMimeType
+            sMimeTypeMap.loadEntry("text/plain", "txt", true);
+            sMimeTypeMap.loadEntry("text/plain", "asc", true);
+            sMimeTypeMap.loadEntry("text/plain", "text", true);
+            sMimeTypeMap.loadEntry("text/plain", "diff", true);
+            sMimeTypeMap.loadEntry("text/plain", "pot", true);
+            sMimeTypeMap.loadEntry("text/richtext", "rtx", true);
+            sMimeTypeMap.loadEntry("text/rtf", "rtf", true);
+            sMimeTypeMap.loadEntry("text/texmacs", "ts", true);
+            sMimeTypeMap.loadEntry("text/text", "phps", true);
+            sMimeTypeMap.loadEntry("text/tab-separated-values", "tsv", true);
+            sMimeTypeMap.loadEntry("text/x-bibtex", "bib", true);
+            sMimeTypeMap.loadEntry("text/x-boo", "boo", true);
+            sMimeTypeMap.loadEntry("text/x-c++hdr", "h++", true);
+            sMimeTypeMap.loadEntry("text/x-c++hdr", "hpp", true);
+            sMimeTypeMap.loadEntry("text/x-c++hdr", "hxx", true);
+            sMimeTypeMap.loadEntry("text/x-c++hdr", "hh", true);
+            sMimeTypeMap.loadEntry("text/x-c++src", "c++", true);
+            sMimeTypeMap.loadEntry("text/x-c++src", "cpp", true);
+            sMimeTypeMap.loadEntry("text/x-c++src", "cxx", true);
+            sMimeTypeMap.loadEntry("text/x-chdr", "h", true);
+            sMimeTypeMap.loadEntry("text/x-component", "htc", true);
+            sMimeTypeMap.loadEntry("text/x-csh", "csh", true);
+            sMimeTypeMap.loadEntry("text/x-csrc", "c", true);
+            sMimeTypeMap.loadEntry("text/x-dsrc", "d", true);
+            sMimeTypeMap.loadEntry("text/x-haskell", "hs", true);
+            sMimeTypeMap.loadEntry("text/x-java", "java", true);
+            sMimeTypeMap.loadEntry("text/x-literate-haskell", "lhs", true);
+            sMimeTypeMap.loadEntry("text/x-moc", "moc", true);
+            sMimeTypeMap.loadEntry("text/x-pascal", "p", true);
+            sMimeTypeMap.loadEntry("text/x-pascal", "pas", true);
+            sMimeTypeMap.loadEntry("text/x-pcs-gcd", "gcd", true);
+            sMimeTypeMap.loadEntry("text/x-setext", "etx", true);
+            sMimeTypeMap.loadEntry("text/x-tcl", "tcl", true);
+            sMimeTypeMap.loadEntry("text/x-tex", "tex", true);
+            sMimeTypeMap.loadEntry("text/x-tex", "ltx", true);
+            sMimeTypeMap.loadEntry("text/x-tex", "sty", true);
+            sMimeTypeMap.loadEntry("text/x-tex", "cls", true);
+            sMimeTypeMap.loadEntry("text/x-vcalendar", "vcs", true);
+            sMimeTypeMap.loadEntry("text/x-vcard", "vcf", true);
+            sMimeTypeMap.loadEntry("video/3gpp", "3gp", false);
+            sMimeTypeMap.loadEntry("video/3gpp", "3g2", false);
+            sMimeTypeMap.loadEntry("video/dl", "dl", false);
+            sMimeTypeMap.loadEntry("video/dv", "dif", false);
+            sMimeTypeMap.loadEntry("video/dv", "dv", false);
+            sMimeTypeMap.loadEntry("video/fli", "fli", false);
+            sMimeTypeMap.loadEntry("video/mpeg", "mpeg", false);
+            sMimeTypeMap.loadEntry("video/mpeg", "mpg", false);
+            sMimeTypeMap.loadEntry("video/mpeg", "mpe", false);
+            sMimeTypeMap.loadEntry("video/mp4", "mp4", false);
+            sMimeTypeMap.loadEntry("video/mpeg", "VOB", false);
+            sMimeTypeMap.loadEntry("video/quicktime", "qt", false);
+            sMimeTypeMap.loadEntry("video/quicktime", "mov", false);
+            sMimeTypeMap.loadEntry("video/vnd.mpegurl", "mxu", false);
+            sMimeTypeMap.loadEntry("video/x-la-asf", "lsf", false);
+            sMimeTypeMap.loadEntry("video/x-la-asf", "lsx", false);
+            sMimeTypeMap.loadEntry("video/x-mng", "mng", false);
+            sMimeTypeMap.loadEntry("video/x-ms-asf", "asf", false);
+            sMimeTypeMap.loadEntry("video/x-ms-asf", "asx", false);
+            sMimeTypeMap.loadEntry("video/x-ms-wm", "wm", false);
+            sMimeTypeMap.loadEntry("video/x-ms-wmv", "wmv", false);
+            sMimeTypeMap.loadEntry("video/x-ms-wmx", "wmx", false);
+            sMimeTypeMap.loadEntry("video/x-ms-wvx", "wvx", false);
+            sMimeTypeMap.loadEntry("video/x-msvideo", "avi", false);
+            sMimeTypeMap.loadEntry("video/x-sgi-movie", "movie", false);
+            sMimeTypeMap.loadEntry("x-conference/x-cooltalk", "ice", false);
+        }
+
+        return sMimeTypeMap;
+    }
+}
diff --git a/core/java/android/webkit/Network.java b/core/java/android/webkit/Network.java
new file mode 100644
index 0000000..74622b3
--- /dev/null
+++ b/core/java/android/webkit/Network.java
@@ -0,0 +1,349 @@
+/*
+ * 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 android.webkit;
+
+import android.content.Context;
+import android.net.http.*;
+import android.os.*;
+import android.util.Log;
+import android.util.Config;
+
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+import java.util.Map;
+
+import junit.framework.Assert;
+
+class Network {
+
+    private static final String LOGTAG = "network";
+
+    /**
+     * Static instance of a Network object.
+     */
+    private static Network sNetwork;
+    
+    /**
+     * Flag to store the state of platform notifications, for the case
+     * when the Network object has not been constructed yet
+     */
+    private static boolean sPlatformNotifications;
+    
+    /**
+     * Reference count for platform notifications as the network class is a 
+     * static and can exist over multiple activities, thus over multiple 
+     * onPause/onResume pairs. 
+     */
+    private static int sPlatformNotificationEnableRefCount;
+
+    /**
+     * Proxy username if known (used for pre-emptive proxy authentication).
+     */
+    private String mProxyUsername;
+
+    /**
+     * Proxy password if known (used for pre-emptive proxy authentication).
+     */
+    private String mProxyPassword;
+
+    /**
+     * Network request queue (requests are added from the browser thread).
+     */
+    private RequestQueue mRequestQueue;
+
+    /**
+     * SSL error handler: takes care of synchronization of multiple async
+     * loaders with SSL-related problems.
+     */
+    private SslErrorHandler mSslErrorHandler;
+
+    /**
+     * HTTP authentication handler: takes care of synchronization of HTTP
+     * authentication requests.
+     */
+    private HttpAuthHandler mHttpAuthHandler;
+
+    /**
+     * @return The singleton instance of the network.
+     */
+    public static synchronized Network getInstance(Context context) {
+        if (sNetwork == null) {
+            // Note Context of the Application is used here, rather than
+            // the what is passed in (usually a Context derived from an 
+            // Activity) so the intent receivers belong to the application
+            // rather than an activity - this fixes the issue where 
+            // Activities are created and destroyed during the lifetime of
+            // an Application
+            sNetwork = new Network(context.getApplicationContext());
+            if (sPlatformNotifications) {
+                // Adjust the ref count before calling enable as it is already
+                // taken into account when the static function was called 
+                // directly
+                --sPlatformNotificationEnableRefCount;
+                enablePlatformNotifications();
+            }
+        }
+        return sNetwork;
+    }
+
+
+    /**
+     * Enables data state and proxy tracking
+     */
+    public static void enablePlatformNotifications() {
+        if (++sPlatformNotificationEnableRefCount == 1) {
+            if (sNetwork != null) {
+                sNetwork.mRequestQueue.enablePlatformNotifications();
+            } else {
+                sPlatformNotifications = true;
+            }
+        }
+    }
+
+    /**
+     * If platform notifications are enabled, this should be called
+     * from onPause() or onStop()
+     */
+    public static void disablePlatformNotifications() {
+        if (--sPlatformNotificationEnableRefCount == 0) {
+            if (sNetwork != null) {
+                sNetwork.mRequestQueue.disablePlatformNotifications();
+            } else {
+                sPlatformNotifications = false;
+            }
+        }
+    }
+
+    /**
+     * Creates a new Network object.
+     * XXX: Must be created in the same thread as WebCore!!!!!
+     */
+    private Network(Context context) {
+        if (Config.DEBUG) {
+            Assert.assertTrue(Thread.currentThread().
+                    getName().equals(WebViewCore.THREAD_NAME));
+        }
+        mSslErrorHandler = new SslErrorHandler(this);
+        mHttpAuthHandler = new HttpAuthHandler(this);
+
+        mRequestQueue = new RequestQueue(context);
+    }
+
+    /**
+     * Request a url from either the network or the file system.
+     * @param url The url to load.
+     * @param method The http method.
+     * @param headers The http headers.
+     * @param postData The body of the request.
+     * @param loader A LoadListener for receiving the results of the request.
+     * @param isHighPriority True if this is high priority request.
+     * @return True if the request was successfully queued.
+     */
+    public boolean requestURL(String method,
+                              Map<String, String> headers,
+                              byte [] postData,
+                              LoadListener loader,
+                              boolean isHighPriority) {
+
+        String url = loader.url();
+
+        // Not a valid url, return false because we won't service the request!
+        if (!URLUtil.isValidUrl(url)) {
+            return false;
+        }
+
+        // asset, file system or data stream are handled in the other code path.
+        // This only handles network request.
+        if (URLUtil.isAssetUrl(url) || URLUtil.isFileUrl(url) ||
+                URLUtil.isDataUrl(url)) {
+            return false;
+        }
+
+        /* FIXME: this is lame.  Pass an InputStream in, rather than
+           making this lame one here */
+        InputStream bodyProvider = null;
+        int bodyLength = 0;
+        if (postData != null) {
+            bodyLength = postData.length;
+            bodyProvider = new ByteArrayInputStream(postData);
+        }
+
+        RequestQueue q = mRequestQueue;
+        if (loader.isSynchronous()) {
+            q = new RequestQueue(loader.getContext(), 1);
+        }
+
+        RequestHandle handle = q.queueRequest(
+                url, loader.getWebAddress(), method, headers, loader,
+                bodyProvider, bodyLength, isHighPriority);
+        loader.attachRequestHandle(handle);
+
+        if (loader.isSynchronous()) {
+            handle.waitUntilComplete();
+            loader.loadSynchronousMessages();
+            q.shutdown();
+        }
+        return true;
+    }
+
+    /**
+     * @return True iff there is a valid proxy set.
+     */
+    public boolean isValidProxySet() {
+        // The proxy host and port can be set within a different thread during
+        // an Intent broadcast.
+        synchronized (mRequestQueue) {
+            return mRequestQueue.getProxyHost() != null;
+        }
+    }
+
+    /**
+     * Get the proxy hostname.
+     * @return The proxy hostname obtained from the network queue and proxy
+     *         settings.
+     */
+    public String getProxyHostname() {
+        return mRequestQueue.getProxyHost().getHostName();
+    }
+
+    /**
+     * @return The proxy username or null if none.
+     */
+    public synchronized String getProxyUsername() {
+        return mProxyUsername;
+    }
+
+    /**
+     * Sets the proxy username.
+     * @param proxyUsername Username to use when
+     * connecting through the proxy.
+     */
+    public synchronized void setProxyUsername(String proxyUsername) {
+        if (Config.DEBUG) {
+            Assert.assertTrue(isValidProxySet());
+        }
+
+        mProxyUsername = proxyUsername;
+    }
+
+    /**
+     * @return The proxy password or null if none.
+     */
+    public synchronized String getProxyPassword() {
+        return mProxyPassword;
+    }
+
+    /**
+     * Sets the proxy password.
+     * @param proxyPassword Password to use when
+     * connecting through the proxy.
+     */
+    public synchronized void setProxyPassword(String proxyPassword) {
+        if (Config.DEBUG) {
+            Assert.assertTrue(isValidProxySet());
+        }
+
+        mProxyPassword = proxyPassword;
+    }
+
+    /**
+     * If we need to stop loading done in a handler (here, browser frame), we
+     * send a message to the handler to stop loading, and remove all loaders
+     * that share the same CallbackProxy in question from all local
+     * handlers (such as ssl-error and http-authentication handler).
+     * @param proxy The CallbackProxy responsible for cancelling the current
+     *              load.
+     */
+    public void resetHandlersAndStopLoading(BrowserFrame frame) {
+        if (Config.LOGV) {
+            Log.v(LOGTAG, "Network.resetHandlersAndStopLoading()");
+        }
+
+        frame.stopLoading();
+        mSslErrorHandler.reset(frame);
+        mHttpAuthHandler.reset(frame);
+    }
+
+    /**
+     * Saves the state of network handlers (user SSL and HTTP-authentication
+     * preferences).
+     * @param outState The out-state to save (write) to.
+     * @return True iff succeeds.
+     */
+    public boolean saveState(Bundle outState) {
+        if (Config.LOGV) {
+            Log.v(LOGTAG, "Network.saveState()");
+        }
+
+        return mSslErrorHandler.saveState(outState);
+    }
+
+    /**
+     * Restores the state of network handlers (user SSL and HTTP-authentication
+     * preferences).
+     * @param inState The in-state to load (read) from.
+     * @return True iff succeeds.
+     */
+    public boolean restoreState(Bundle inState) {
+        if (Config.LOGV) {
+            Log.v(LOGTAG, "Network.restoreState()");
+        }
+
+        return mSslErrorHandler.restoreState(inState);
+    }
+
+    /**
+     * Clears user SSL-error preference table.
+     */
+    public void clearUserSslPrefTable() {
+        mSslErrorHandler.clear();
+    }
+
+    /**
+     * Handles SSL error(s) on the way up to the user: the user must decide
+     * whether errors should be ignored or not.
+     * @param loader The loader that resulted in SSL errors.
+     */
+    public void handleSslErrorRequest(LoadListener loader) {
+        if (Config.DEBUG) Assert.assertNotNull(loader);
+        if (loader != null) {
+            mSslErrorHandler.handleSslErrorRequest(loader);
+        }
+    }
+
+     /**
+     * Handles authentication requests on their way up to the user (the user
+     * must provide credentials).
+     * @param loader The loader that resulted in an HTTP
+     * authentication request.
+     */
+    public void handleAuthRequest(LoadListener loader) {
+        if (Config.DEBUG) Assert.assertNotNull(loader);
+        if (loader != null) {
+            mHttpAuthHandler.handleAuthRequest(loader);
+        }
+    }
+
+    // Performance probe
+    public void startTiming() {
+        mRequestQueue.startTiming();
+    }
+
+    public void stopTiming() {
+        mRequestQueue.stopTiming();
+    }
+}
diff --git a/core/java/android/webkit/PerfChecker.java b/core/java/android/webkit/PerfChecker.java
new file mode 100644
index 0000000..8c5f86e
--- /dev/null
+++ b/core/java/android/webkit/PerfChecker.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2007 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 android.webkit;
+
+import android.os.SystemClock;
+import android.util.Log;
+
+class PerfChecker {
+
+    private long mTime;
+    private static final long mResponseThreshold = 2000;    // 2s
+
+    public PerfChecker() {
+        if (false) {
+            mTime = SystemClock.uptimeMillis();
+        }
+    }
+
+    /**
+     * @param what log string
+     * Logs given string if mResponseThreshold time passed between either
+     * instantiation or previous responseAlert call
+     */
+    public void responseAlert(String what) {
+        if (false) {
+            long upTime = SystemClock.uptimeMillis();
+            long time =  upTime - mTime;
+            if (time > mResponseThreshold) {
+                Log.w("webkit", what + " used " + time + " ms");
+            }
+            // Reset mTime, to permit reuse
+            mTime = upTime;
+        }
+    }
+}
diff --git a/core/java/android/webkit/Plugin.java b/core/java/android/webkit/Plugin.java
new file mode 100644
index 0000000..f83da99
--- /dev/null
+++ b/core/java/android/webkit/Plugin.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2007 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 android.webkit;
+
+import com.android.internal.R;
+
+import android.app.AlertDialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.webkit.WebView;
+
+/**
+ * Represents a plugin (Java equivalent of the PluginPackageAndroid
+ * C++ class in libs/WebKitLib/WebKit/WebCore/plugins/android/)
+ */
+public class Plugin {
+    public interface PreferencesClickHandler {
+        public void handleClickEvent(Context context);
+    }
+
+    private String mName;
+    private String mPath;
+    private String mFileName;
+    private String mDescription;
+    private PreferencesClickHandler mHandler;
+
+    public Plugin(String name,
+                  String path,
+                  String fileName,
+                  String description) {
+        mName = name;
+        mPath = path;
+        mFileName = fileName;
+        mDescription = description;
+        mHandler = new DefaultClickHandler();
+    }
+
+    public String toString() {
+        return mName;
+    }
+
+    public String getName() {
+        return mName;
+    }
+
+    public String getPath() {
+        return mPath;
+    }
+
+    public String getFileName() {
+        return mFileName;
+    }
+
+    public String getDescription() {
+        return mDescription;
+    }
+
+    public void setName(String name) {
+        mName = name;
+    }
+
+    public void setPath(String path) {
+        mPath = path;
+    }
+
+    public void setFileName(String fileName) {
+        mFileName = fileName;
+    }
+
+    public void setDescription(String description) {
+        mDescription = description;
+    }
+
+    public void setClickHandler(PreferencesClickHandler handler) {
+        mHandler = handler;
+    }
+
+   /**
+    * Invokes the click handler for this plugin.
+    */
+    public void dispatchClickEvent(Context context) {
+        if (mHandler != null) {
+            mHandler.handleClickEvent(context);
+        }
+    }
+
+   /**
+    * Default click handler. The plugins should implement their own.
+    */
+    private class DefaultClickHandler implements PreferencesClickHandler,
+                                                 DialogInterface.OnClickListener {
+        private AlertDialog mDialog;
+
+        public void handleClickEvent(Context context) {
+            // Show a simple popup dialog containing the description
+            // string of the plugin.
+            if (mDialog == null) {
+                mDialog = new AlertDialog.Builder(context)
+                        .setTitle(mName)
+                        .setMessage(mDescription)
+                        .setPositiveButton(R.string.ok, this)
+                        .setCancelable(false)
+                        .show();
+            }
+        }
+
+        public void onClick(DialogInterface dialog, int which) {
+            mDialog.dismiss();
+            mDialog = null;
+        }
+    }
+}
diff --git a/core/java/android/webkit/PluginList.java b/core/java/android/webkit/PluginList.java
new file mode 100644
index 0000000..a9d3d8c
--- /dev/null
+++ b/core/java/android/webkit/PluginList.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2007 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 android.webkit;
+
+import android.content.Context;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A simple list of initialized plugins. This list gets
+ * populated when the plugins are initialized (at
+ * browser startup, at the moment).
+ */
+public class PluginList {
+    private ArrayList<Plugin> mPlugins;
+
+   /**
+    * Public constructor. Initializes the list of plugins.
+    */
+    public PluginList() {
+        mPlugins = new ArrayList<Plugin>();
+    }
+
+   /**
+    * Returns the list of plugins as a java.util.List.
+    */
+    public synchronized List getList() {
+        return mPlugins;
+    }
+
+   /**
+    * Adds a plugin to the list.
+    */
+    public synchronized void addPlugin(Plugin plugin) {
+        if (!mPlugins.contains(plugin)) {
+            mPlugins.add(plugin);
+        }
+    }
+
+   /**
+    * Removes a plugin from the list.
+    */
+    public synchronized void removePlugin(Plugin plugin) {
+        int location = mPlugins.indexOf(plugin);
+        if (location != -1) {
+            mPlugins.remove(location);
+        }
+    }
+
+   /**
+    * Clears the plugin list.
+    */
+    public synchronized void clear() {
+        mPlugins.clear();
+    }
+
+   /**
+    * Dispatches the click event to the appropriate plugin.
+    */
+    public synchronized void pluginClicked(Context context, int position) {
+        try {
+            Plugin plugin = mPlugins.get(position);
+            plugin.dispatchClickEvent(context);
+        } catch (IndexOutOfBoundsException e) {
+            // This can happen if the list of plugins
+            // gets changed while the pref menu is up.
+        }
+    }
+}
diff --git a/core/java/android/webkit/SslErrorHandler.java b/core/java/android/webkit/SslErrorHandler.java
new file mode 100644
index 0000000..115434a
--- /dev/null
+++ b/core/java/android/webkit/SslErrorHandler.java
@@ -0,0 +1,255 @@
+/*
+ * Copyright (C) 2007 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 android.webkit;
+
+import junit.framework.Assert;
+
+import android.net.http.SslError;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
+import android.util.Config;
+import android.util.Log;
+
+import java.util.LinkedList;
+import java.util.ListIterator;
+
+/**
+ * SslErrorHandler: class responsible for handling SSL errors. This class is
+ * passed as a parameter to BrowserCallback.displaySslErrorDialog and is meant
+ * to receive the user's response.
+ */
+public class SslErrorHandler extends Handler {
+    /* One problem here is that there may potentially be multiple SSL errors
+     * coming from mutiple loaders. Therefore, we keep a queue of loaders
+     * that have SSL-related problems and process errors one by one in the
+     * order they were received.
+     */
+
+    private static final String LOGTAG = "network";
+
+    /**
+     * Network.
+     */
+    private Network mNetwork;
+
+    /**
+     * Queue of loaders that experience SSL-related problems.
+     */
+    private LinkedList<LoadListener> mLoaderQueue;
+
+    /**
+     * SSL error preference table.
+     */
+    private Bundle mSslPrefTable;
+
+    // Message id for handling the response
+    private final int HANDLE_RESPONSE = 100;
+
+    @Override
+    public void handleMessage(Message msg) {
+        switch (msg.what) {
+            case HANDLE_RESPONSE:
+                handleSslErrorResponse(msg.arg1 == 1);
+                fastProcessQueuedSslErrors();
+                break;
+        }
+    }
+
+    /**
+     * Creates a new error handler with an empty loader queue.
+     */
+    /* package */ SslErrorHandler(Network network) {
+        mNetwork = network;
+
+        mLoaderQueue = new LinkedList<LoadListener>();
+        mSslPrefTable = new Bundle();
+    }
+
+    /**
+     * Saves this handler's state into a map.
+     * @return True iff succeeds.
+     */
+    /* package */ boolean saveState(Bundle outState) {
+        boolean success = (outState != null);
+        if (success) {
+            // TODO?
+            outState.putBundle("ssl-error-handler", mSslPrefTable);
+        }
+
+        return success;
+    }
+
+    /**
+     * Restores this handler's state from a map.
+     * @return True iff succeeds.
+     */
+    /* package */ boolean restoreState(Bundle inState) {
+        boolean success = (inState != null);
+        if (success) {
+            success = inState.containsKey("ssl-error-handler");
+            if (success) {
+                mSslPrefTable = inState.getBundle("ssl-error-handler");
+            }
+        }
+
+        return success;
+    }
+
+    /**
+     * Clears SSL error preference table.
+     */
+    /* package */ synchronized void clear() {
+        mSslPrefTable.clear();
+    }
+
+    /**
+     * Resets the SSL error handler, removes all loaders that
+     * share the same BrowserFrame.
+     */
+    /* package */ synchronized void reset(BrowserFrame frame) {
+        ListIterator<LoadListener> i = mLoaderQueue.listIterator(0);
+        while (i.hasNext()) {
+            LoadListener loader = i.next();
+            if (frame == loader.getFrame()) {
+                i.remove();
+            }
+        }
+    }
+
+    /**
+     * Handles SSL error(s) on the way up to the user.
+     */
+    /* package */ synchronized void handleSslErrorRequest(LoadListener loader) {
+        if (Config.LOGV) {
+            Log.v(LOGTAG, "SslErrorHandler.handleSslErrorRequest(): " +
+                  "url=" + loader.url());
+        }
+
+        if (!loader.cancelled()) {
+            mLoaderQueue.offer(loader);
+            if (loader == mLoaderQueue.peek()) {
+                fastProcessQueuedSslErrors();
+            }
+        }
+    }
+
+    /**
+     * Processes queued SSL-error confirmation requests in
+     * a tight loop while there is no need to ask the user.
+     */
+    /* package */void fastProcessQueuedSslErrors() {
+        while (processNextLoader());
+    }
+
+    /**
+     * Processes the next loader in the queue.
+     * @return True iff should proceed to processing the
+     * following loader in the queue
+     */
+    private synchronized boolean processNextLoader() {
+        LoadListener loader = mLoaderQueue.peek();
+        if (loader != null) {
+            // if this loader has been cancelled
+            if (loader.cancelled()) {
+                // go to the following loader in the queue
+                return true;
+            }
+
+            SslError error = loader.sslError();
+
+            if (Config.DEBUG) {
+                Assert.assertNotNull(error);
+            }
+
+            int primary = error.getPrimaryError();
+            String host = loader.host();
+
+            if (Config.DEBUG) {
+                Assert.assertTrue(host != null && primary != 0);
+            }
+
+            if (mSslPrefTable.containsKey(host)) {
+                if (primary <= mSslPrefTable.getInt(host)) {
+                    handleSslErrorResponse(true);
+                    return true;
+                }
+            }
+
+            // if we do not have information on record, ask
+            // the user (display a dialog)
+            CallbackProxy proxy = loader.getFrame().getCallbackProxy();
+            proxy.onReceivedSslError(this, error);
+        }
+
+        // the queue must be empty, stop
+        return false;
+    }
+
+    /**
+     * Proceed with the SSL certificate.
+     */
+    public void proceed() {
+        sendMessage(obtainMessage(HANDLE_RESPONSE, 1, 0));
+    }
+
+    /**
+     * Cancel this request and all pending requests for the WebView that had
+     * the error.
+     */
+    public void cancel() {
+        sendMessage(obtainMessage(HANDLE_RESPONSE, 0, 0));
+    }
+
+    /**
+     * Handles SSL error(s) on the way down from the user.
+     */
+    /* package */ synchronized void handleSslErrorResponse(boolean proceed) {
+        LoadListener loader = mLoaderQueue.poll();
+        if (Config.DEBUG) {
+            Assert.assertNotNull(loader);
+        }
+
+        if (Config.LOGV) {
+            Log.v(LOGTAG, "SslErrorHandler.handleSslErrorResponse():"
+                  + " proceed: " + proceed
+                  + " url:" + loader.url());
+        }
+
+        if (!loader.cancelled()) {
+            if (proceed) {
+                // update the user's SSL error preference table
+                int primary = loader.sslError().getPrimaryError();
+                String host = loader.host();
+
+                if (Config.DEBUG) {
+                    Assert.assertTrue(host != null && primary != 0);
+                }
+                boolean hasKey = mSslPrefTable.containsKey(host);
+                if (!hasKey ||
+                    primary > mSslPrefTable.getInt(host)) {
+                    mSslPrefTable.putInt(host, new Integer(primary));
+                }
+
+                loader.handleSslErrorResponse(proceed);
+            } else {
+                loader.handleSslErrorResponse(proceed);
+                mNetwork.resetHandlersAndStopLoading(loader.getFrame());
+            }
+        }
+    }
+}
diff --git a/core/java/android/webkit/StreamLoader.java b/core/java/android/webkit/StreamLoader.java
new file mode 100644
index 0000000..9098307
--- /dev/null
+++ b/core/java/android/webkit/StreamLoader.java
@@ -0,0 +1,199 @@
+/*
+ * Copyright (C) 2007 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 android.webkit;
+
+import android.net.http.EventHandler;
+import android.net.http.Headers;
+import android.os.Handler;
+import android.os.Message;
+import android.util.Config;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+
+/**
+ * This abstract class is used for all content loaders that rely on streaming
+ * content into the rendering engine loading framework.
+ *
+ * The class implements a state machine to load the content into the frame in
+ * a similar manor to the way content arrives from the network. The class uses
+ * messages to move from one state to the next, which enables async. loading of
+ * the streamed content.
+ *
+ * Classes that inherit from this class must implement two methods, the first
+ * method is used to setup the InputStream and notify the loading framework if
+ * it can load it's content. The other method allows the derived class to add
+ * additional HTTP headers to the response.
+ *
+ * By default, content loaded with a StreamLoader is marked with a HTTP header
+ * that indicates the content should not be cached.
+ *
+ */
+abstract class StreamLoader extends Handler {
+
+    public static final String NO_STORE       = "no-store";
+
+    private static final int MSG_STATUS = 100;  // Send status to loader
+    private static final int MSG_HEADERS = 101; // Send headers to loader
+    private static final int MSG_DATA = 102;  // Send data to loader
+    private static final int MSG_END = 103;  // Send endData to loader
+
+    protected LoadListener mHandler; // loader class
+    protected InputStream mDataStream; // stream to read data from
+    protected long mContentLength; // content length of data
+    private byte [] mData; // buffer to pass data to loader with.
+
+    /**
+     * Constructor. Although this class calls the LoadListener, it only calls
+     * the EventHandler Interface methods. LoadListener concrete class is used
+     * to avoid the penality of calling an interface.
+     *
+     * @param loadlistener The LoadListener to call with the data.
+     */
+    StreamLoader(LoadListener loadlistener) {
+        mHandler = loadlistener;
+    }
+
+    /**
+     * This method is called when the derived class should setup mDataStream,
+     * and call mHandler.status() to indicate that the load can occur. If it
+     * fails to setup, it should still call status() with the error code.
+     *
+     * @return true if stream was successfully setup
+     */
+    protected abstract boolean setupStreamAndSendStatus();
+
+    /**
+     * This method is called when the headers are about to be sent to the
+     * load framework. The derived class has the opportunity to add addition
+     * headers.
+     *
+     * @param headers Map of HTTP headers that will be sent to the loader.
+     */
+    abstract protected void buildHeaders(Headers headers);
+
+
+    /**
+     * Calling this method starts the load of the content for this StreamLoader.
+     * This method simply posts a message to send the status and returns
+     * immediately.
+     */
+    public void load() {
+        if (!mHandler.isSynchronous()) {
+            sendMessage(obtainMessage(MSG_STATUS));
+        } else {
+            // Load the stream synchronously.
+            if (setupStreamAndSendStatus()) {
+                // We were able to open the stream, create the array
+                // to pass data to the loader
+                mData = new byte[8192];
+                sendHeaders();
+                while (!sendData());
+                closeStreamAndSendEndData();
+                mHandler.loadSynchronousMessages();
+            }
+        }
+    }
+
+    /* (non-Javadoc)
+     * @see android.os.Handler#handleMessage(android.os.Message)
+     */
+    public void handleMessage(Message msg) {
+        if (Config.DEBUG && mHandler.isSynchronous()) {
+            throw new AssertionError();
+        }
+        switch(msg.what) {
+            case MSG_STATUS:
+                if (setupStreamAndSendStatus()) {
+                    // We were able to open the stream, create the array
+                    // to pass data to the loader
+                    mData = new byte[8192];
+                    sendMessage(obtainMessage(MSG_HEADERS));
+                }
+                break;
+            case MSG_HEADERS:
+                sendHeaders();
+                sendMessage(obtainMessage(MSG_DATA));
+                break;
+            case MSG_DATA:
+                if (sendData()) {
+                    sendMessage(obtainMessage(MSG_END));
+                } else {
+                    sendMessage(obtainMessage(MSG_DATA));
+                }
+                break;
+            case MSG_END:
+                closeStreamAndSendEndData();
+                break;
+            default:
+                super.handleMessage(msg);
+                break;
+        }
+    }
+
+    /**
+     * Construct the headers and pass them to the EventHandler.
+     */
+    private void sendHeaders() {
+        Headers headers = new Headers();
+        if (mContentLength > 0) {
+            headers.setContentLength(mContentLength);
+        }
+        headers.setCacheControl(NO_STORE);
+        buildHeaders(headers);
+        mHandler.headers(headers);
+    }
+
+    /**
+     * Read data from the stream and pass it to the EventHandler.
+     * If an error occurs reading the stream, then an error is sent to the
+     * EventHandler, and moves onto the next state - end of data.
+     * @return True if all the data has been read. False if sendData should be
+     *         called again.
+     */
+    private boolean sendData() {
+        if (mDataStream != null) {
+            try {
+                int amount = mDataStream.read(mData);
+                if (amount > 0) {
+                    mHandler.data(mData, amount);
+                    return false;
+                }
+            } catch (IOException ex) {
+                mHandler.error(EventHandler.FILE_ERROR,
+                               ex.getMessage());
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Close the stream and inform the EventHandler that load is complete.
+     */
+    private void closeStreamAndSendEndData() {
+        if (mDataStream != null) {
+            try {
+                mDataStream.close();
+            } catch (IOException ex) {
+                // ignore.
+            }
+        }
+        mHandler.endData();
+    }
+
+}
diff --git a/core/java/android/webkit/TextDialog.java b/core/java/android/webkit/TextDialog.java
new file mode 100644
index 0000000..8a82411
--- /dev/null
+++ b/core/java/android/webkit/TextDialog.java
@@ -0,0 +1,610 @@
+/*
+ * Copyright (C) 2007 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 android.webkit;
+
+import android.content.Context;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.LayerDrawable;
+import android.graphics.drawable.ShapeDrawable;
+import android.graphics.drawable.shapes.RectShape;
+import android.text.Editable;
+import android.text.InputFilter;
+import android.text.Selection;
+import android.text.Spannable;
+import android.text.TextPaint;
+import android.text.TextUtils;
+import android.text.method.MovementMethod;
+import android.text.method.PasswordTransformationMethod;
+import android.text.method.TextKeyListener;
+import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.InputMethodManager;
+import android.view.KeyCharacterMap;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AbsoluteLayout.LayoutParams;
+import android.widget.ArrayAdapter;
+import android.widget.AutoCompleteTextView;
+import android.widget.TextView;
+
+import java.util.ArrayList;
+
+/**
+ * TextDialog is a specialized version of EditText used by WebView
+ * to overlay html textfields (and textareas) to use our standard
+ * text editing.
+ */
+/* package */ class TextDialog extends AutoCompleteTextView {
+
+    private WebView         mWebView;
+    private boolean         mSingle;
+    private int             mWidthSpec;
+    private int             mHeightSpec;
+    private int             mNodePointer;
+    // FIXME: This is a hack for blocking unmatched key ups, in particular
+    // on the enter key.  The method for blocking unmatched key ups prevents
+    // the shift key from working properly.
+    private boolean         mGotEnterDown;
+    // mScrollToAccommodateCursor being set to false prevents us from scrolling
+    // the cursor on screen when using the trackball to select a textfield.
+    private boolean         mScrollToAccommodateCursor;
+    private int             mMaxLength;
+    // Keep track of the text before the change so we know whether we actually
+    // need to send down the DOM events.
+    private String          mPreChange;
+    // Array to store the final character added in onTextChanged, so that its
+    // KeyEvents may be determined.
+    private char[]          mCharacter = new char[1];
+    // This is used to reset the length filter when on a textfield
+    // with no max length.
+    // FIXME: This can be replaced with TextView.NO_FILTERS if that
+    // is made public/protected.
+    private static final InputFilter[] NO_FILTERS = new InputFilter[0];
+
+    /**
+     * Create a new TextDialog.
+     * @param   context The Context for this TextDialog.
+     * @param   webView The WebView that created this.
+     */
+    /* package */ TextDialog(Context context, WebView webView) {
+        super(context);
+        mWebView = webView;
+        ShapeDrawable background = new ShapeDrawable(new RectShape());
+        Paint shapePaint = background.getPaint();
+        shapePaint.setStyle(Paint.Style.STROKE);
+        ColorDrawable color = new ColorDrawable(Color.WHITE);
+        Drawable[] array = new Drawable[2];
+        array[0] = color;
+        array[1] = background;
+        LayerDrawable layers = new LayerDrawable(array);
+        // Hide WebCore's text behind this and allow the WebView
+        // to draw its own focusring.
+        setBackgroundDrawable(layers);
+        // Align the text better with the text behind it, so moving
+        // off of the textfield will not appear to move the text.
+        setPadding(3, 2, 0, 0);
+        mMaxLength = -1;
+        // Turn on subpixel text, and turn off kerning, so it better matches
+        // the text in webkit.
+        TextPaint paint = getPaint();
+        int flags = paint.getFlags() | Paint.SUBPIXEL_TEXT_FLAG |
+                Paint.ANTI_ALIAS_FLAG & ~Paint.DEV_KERN_TEXT_FLAG;
+        paint.setFlags(flags);
+        // Set the text color to black, regardless of the theme.  This ensures
+        // that other applications that use embedded WebViews will properly
+        // display the text in textfields.
+        setTextColor(Color.BLACK);
+    }
+
+    @Override
+    protected boolean shouldAdvanceFocusOnEnter() {
+        // In the browser, single line textfields use enter as a form submit,
+        // so we never want to advance the focus on enter.
+        return false;
+    }
+
+    @Override
+    public boolean dispatchKeyEvent(KeyEvent event) {
+        if (event.isSystem()) {
+            return super.dispatchKeyEvent(event);
+        }
+        // Treat ACTION_DOWN and ACTION MULTIPLE the same
+        boolean down = event.getAction() != KeyEvent.ACTION_UP;
+        int keyCode = event.getKeyCode();
+        Spannable text = (Spannable) getText();
+        int oldLength = text.length();
+        // Normally the delete key's dom events are sent via onTextChanged.
+        // However, if the length is zero, the text did not change, so we 
+        // go ahead and pass the key down immediately.
+        if (KeyEvent.KEYCODE_DEL == keyCode && 0 == oldLength) {
+            sendDomEvent(event);
+            return true;
+        }
+
+        if ((mSingle && KeyEvent.KEYCODE_ENTER == keyCode)) {
+            if (isPopupShowing()) {
+                return super.dispatchKeyEvent(event);
+            }
+            if (!down) {
+                // Hide the keyboard, since the user has just submitted this
+                // form.  The submission happens thanks to the two calls
+                // to sendDomEvent.
+                InputMethodManager.getInstance(mContext)
+                        .hideSoftInputFromWindow(getWindowToken(), 0);
+                sendDomEvent(new KeyEvent(KeyEvent.ACTION_DOWN, keyCode));
+                sendDomEvent(event);
+            }
+            return super.dispatchKeyEvent(event);
+        } else if (KeyEvent.KEYCODE_DPAD_CENTER == keyCode) {
+            // Note that this handles center key and trackball.
+            if (isPopupShowing()) {
+                return super.dispatchKeyEvent(event);
+            }
+            // Center key should be passed to a potential onClick
+            if (!down) {
+                mWebView.shortPressOnTextField();
+            }
+            // Pass to super to handle longpress.
+            return super.dispatchKeyEvent(event);
+        }
+
+        // Ensure there is a layout so arrow keys are handled properly.
+        if (getLayout() == null) {
+            measure(mWidthSpec, mHeightSpec);
+        }
+        int oldStart = Selection.getSelectionStart(text);
+        int oldEnd = Selection.getSelectionEnd(text);
+
+        boolean maxedOut = mMaxLength != -1 && oldLength == mMaxLength;
+        // If we are at max length, and there is a selection rather than a
+        // cursor, we need to store the text to compare later, since the key
+        // may have changed the string.
+        String oldText;
+        if (maxedOut && oldEnd != oldStart) {
+            oldText = text.toString();
+        } else {
+            oldText = "";
+        }
+        if (super.dispatchKeyEvent(event)) {
+            // If the TextDialog handled the key it was either an alphanumeric
+            // key, a delete, or a movement within the text. All of those are
+            // ok to pass to javascript.
+
+            // UNLESS there is a max length determined by the html.  In that
+            // case, if the string was already at the max length, an
+            // alphanumeric key will be erased by the LengthFilter,
+            // so do not pass down to javascript, and instead
+            // return true.  If it is an arrow key or a delete key, we can go
+            // ahead and pass it down.
+            boolean isArrowKey;
+            switch(keyCode) {
+                case KeyEvent.KEYCODE_DPAD_LEFT:
+                case KeyEvent.KEYCODE_DPAD_RIGHT:
+                case KeyEvent.KEYCODE_DPAD_UP:
+                case KeyEvent.KEYCODE_DPAD_DOWN:
+                    isArrowKey = true;
+                    break;
+                case KeyEvent.KEYCODE_ENTER:
+                    // For multi-line text boxes, newlines will
+                    // trigger onTextChanged for key down (which will send both
+                    // key up and key down) but not key up.
+                    mGotEnterDown = true;
+                default:
+                    isArrowKey = false;
+                    break;
+            }
+            if (maxedOut && !isArrowKey && keyCode != KeyEvent.KEYCODE_DEL) {
+                if (oldEnd == oldStart) {
+                    // Return true so the key gets dropped.
+                    mScrollToAccommodateCursor = true;
+                    return true;
+                } else if (!oldText.equals(getText().toString())) {
+                    // FIXME: This makes the text work properly, but it
+                    // does not pass down the key event, so it may not
+                    // work for a textfield that has the type of
+                    // behavior of GoogleSuggest.  That said, it is
+                    // unlikely that a site would combine the two in
+                    // one textfield.
+                    Spannable span = (Spannable) getText();
+                    int newStart = Selection.getSelectionStart(span);
+                    int newEnd = Selection.getSelectionEnd(span);
+                    mWebView.replaceTextfieldText(0, oldLength, span.toString(),
+                            newStart, newEnd);
+                    mScrollToAccommodateCursor = true;
+                    return true;
+                }
+            }
+            if (isArrowKey) {
+                // Arrow key does not change the text, but we still want to send
+                // the DOM events.
+                sendDomEvent(event);
+            }
+            mScrollToAccommodateCursor = true;
+            return true;
+        }
+        // FIXME: TextViews return false for up and down key events even though
+        // they change the selection. Since we don't want the get out of sync
+        // with WebCore's notion of the current selection, reset the selection
+        // to what it was before the key event.
+        Selection.setSelection(text, oldStart, oldEnd);
+        // Ignore the key up event for newlines. This prevents
+        // multiple newlines in the native textarea.
+        if (mGotEnterDown && !down) {
+            return true;
+        }
+        // if it is a navigation key, pass it to WebView
+        if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT
+                || keyCode == KeyEvent.KEYCODE_DPAD_RIGHT
+                || keyCode == KeyEvent.KEYCODE_DPAD_UP
+                || keyCode == KeyEvent.KEYCODE_DPAD_DOWN) {
+            // WebView check the trackballtime in onKeyDown to avoid calling
+            // native from both trackball and key handling. As this is called 
+            // from TextDialog, we always want WebView to check with native. 
+            // Reset trackballtime to ensure it.
+            mWebView.resetTrackballTime();
+            return down ? mWebView.onKeyDown(keyCode, event) : mWebView
+                    .onKeyUp(keyCode, event);
+        }
+        return false;
+    }
+
+    /**
+     *  Create a fake touch up event at (x,y) with respect to this TextDialog.
+     *  This is used by WebView to act as though a touch event which happened
+     *  before we placed the TextDialog actually hit it, so that it can place
+     *  the cursor accordingly.
+     */
+    /* package */ void fakeTouchEvent(float x, float y) {
+        // We need to ensure that there is a Layout, since the Layout is used
+        // in determining where to place the cursor.
+        if (getLayout() == null) {
+            measure(mWidthSpec, mHeightSpec);
+        }
+        // Create a fake touch up, which is used to place the cursor.
+        MotionEvent ev = MotionEvent.obtain(0, 0, MotionEvent.ACTION_UP,
+                x, y, 0);
+        onTouchEvent(ev);
+        ev.recycle();
+    }
+
+    /**
+     *  Determine whether this TextDialog currently represents the node
+     *  represented by ptr.
+     *  @param  ptr Pointer to a node to compare to.
+     *  @return boolean Whether this TextDialog already represents the node
+     *          pointed to by ptr.
+     */
+    /* package */ boolean isSameTextField(int ptr) {
+        return ptr == mNodePointer;
+    }
+
+    @Override
+    public boolean onPreDraw() {
+        if (getLayout() == null) {
+            measure(mWidthSpec, mHeightSpec);
+        }
+        return super.onPreDraw();
+    }
+    
+    @Override
+    protected void onTextChanged(CharSequence s,int start,int before,int count){
+        super.onTextChanged(s, start, before, count);
+        String postChange = s.toString();
+        // Prevent calls to setText from invoking onTextChanged (since this will
+        // mean we are on a different textfield).  Also prevent the change when
+        // going from a textfield with a string of text to one with a smaller 
+        // limit on text length from registering the onTextChanged event.
+        if (mPreChange == null || mPreChange.equals(postChange) ||
+                (mMaxLength > -1 && mPreChange.length() > mMaxLength &&
+                mPreChange.substring(0, mMaxLength).equals(postChange))) {
+            return;
+        }
+        mPreChange = postChange;
+        // This was simply a delete or a cut, so just delete the 
+        // selection.
+        if (before > 0 && 0 == count) {
+            mWebView.deleteSelection(start, start + before);
+            // For this and all changes to the text, update our cache
+            updateCachedTextfield();
+            return;
+        }
+        // Find the last character being replaced.  If it can be represented by
+        // events, we will pass them to native (after replacing the beginning
+        // of the changed text), so we can see javascript events.
+        // Otherwise, replace the text being changed (including the last
+        // character) in the textfield.
+        TextUtils.getChars(s, start + count - 1, start + count, mCharacter, 0);
+        KeyCharacterMap kmap =
+                KeyCharacterMap.load(KeyCharacterMap.BUILT_IN_KEYBOARD);
+        KeyEvent[] events = kmap.getEvents(mCharacter);
+        boolean cannotUseKeyEvents = null == events;
+        int charactersFromKeyEvents = cannotUseKeyEvents ? 0 : 1;
+        if (count > 1 || cannotUseKeyEvents) {
+            String replace = s.subSequence(start,
+                    start + count - charactersFromKeyEvents).toString();
+            mWebView.replaceTextfieldText(start, start + before, replace,
+                    start + count - charactersFromKeyEvents,
+                    start + count - charactersFromKeyEvents);
+        } else {
+            // This corrects the selection which may have been affected by the 
+            // trackball or auto-correct.
+            mWebView.setSelection(start, start + before);
+        }
+        updateCachedTextfield();
+        if (cannotUseKeyEvents) {
+            return;
+        }
+        int length = events.length;
+        for (int i = 0; i < length; i++) {
+            // We never send modifier keys to native code so don't send them
+            // here either.
+            if (!KeyEvent.isModifierKey(events[i].getKeyCode())) {
+                sendDomEvent(events[i]);
+            }
+        }
+    }
+    
+    @Override
+    public boolean onTrackballEvent(MotionEvent event) {
+        if (isPopupShowing()) {
+            return super.onTrackballEvent(event);
+        }
+        if (event.getAction() != MotionEvent.ACTION_MOVE) {
+            return false;
+        }
+        Spannable text = (Spannable) getText();
+        MovementMethod move = getMovementMethod();
+        if (move != null && getLayout() != null &&
+            move.onTrackballEvent(this, text, event)) {
+            // Need to pass down the selection, which has changed.
+            // FIXME: This should work, but does not, so we set the selection
+            // in onTextChanged.
+            //int start = Selection.getSelectionStart(text);
+            //int end = Selection.getSelectionEnd(text);
+            //mWebView.setSelection(start, end);
+            return true;
+        }
+        // If the user is in a textfield, and the movement method is not
+        // handling the trackball events, it means they are at the end of the
+        // field and continuing to move the trackball.  In this case, we should
+        // not scroll the cursor on screen bc the user may be attempting to
+        // scroll the page, possibly in the opposite direction of the cursor.
+        mScrollToAccommodateCursor = false;
+        return false;
+    }
+
+    /**
+     * Remove this TextDialog from its host WebView, and return
+     * focus to the host.
+     */
+    /* package */ void remove() {
+        // hide the soft keyboard when the edit text is out of focus
+        InputMethodManager.getInstance(mContext).hideSoftInputFromWindow(
+                getWindowToken(), 0);
+        mWebView.removeView(this);
+        mWebView.requestFocus();
+        mScrollToAccommodateCursor = false;
+    }
+
+    /* package */ void enableScrollOnScreen(boolean enable) {
+        mScrollToAccommodateCursor = enable;
+    }
+
+    /* package */ void bringIntoView() {
+        if (getLayout() != null) {
+            bringPointIntoView(Selection.getSelectionEnd(getText()));
+        }
+    }
+
+    @Override
+    public boolean requestRectangleOnScreen(Rect rectangle) {
+        if (mScrollToAccommodateCursor) {
+            return super.requestRectangleOnScreen(rectangle);
+        }
+        return false;
+    }
+    
+    /**
+     *  Send the DOM events for the specified event.
+     *  @param event    KeyEvent to be translated into a DOM event.
+     */
+    private void sendDomEvent(KeyEvent event) {
+        mWebView.passToJavaScript(getText().toString(), event);
+    }
+
+    /**
+     *  Always use this instead of setAdapter, as this has features specific to
+     *  the TextDialog.
+     */
+    public void setAdapterCustom(AutoCompleteAdapter adapter) {
+        if (adapter != null) {
+            setInputType(EditorInfo.TYPE_TEXT_FLAG_AUTO_COMPLETE);
+            adapter.setTextView(this);
+        }
+        super.setAdapter(adapter);
+    }
+
+    /**
+     *  This is a special version of ArrayAdapter which changes its text size
+     *  to match the text size of its host TextView.
+     */
+    public static class AutoCompleteAdapter extends ArrayAdapter<String> {
+        private TextView mTextView;
+
+        public AutoCompleteAdapter(Context context, ArrayList<String> entries) {
+            super(context, com.android.internal.R.layout
+                    .search_dropdown_item_1line, entries);
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        public View getView(int position, View convertView, ViewGroup parent) {
+            TextView tv =
+                    (TextView) super.getView(position, convertView, parent);
+            if (tv != null && mTextView != null) {
+                tv.setTextSize(mTextView.getTextSize());
+            }
+            return tv;
+        }
+
+        /**
+         * Set the TextView so we can match its text size.
+         */
+        private void setTextView(TextView tv) {
+            mTextView = tv;
+        }
+    }
+
+    /**
+     * Determine whether to use the system-wide password disguising method,
+     * or to use none.
+     * @param   inPassword  True if the textfield is a password field.
+     */
+    /* package */ void setInPassword(boolean inPassword) {
+        PasswordTransformationMethod method;
+        if (inPassword) {
+            method = PasswordTransformationMethod.getInstance();
+            setInputType(EditorInfo.TYPE_CLASS_TEXT|EditorInfo.
+                    TYPE_TEXT_VARIATION_PASSWORD);
+        } else {
+            method = null;
+        }
+        setTransformationMethod(method);
+    }
+
+    /* package */ void setMaxLength(int maxLength) {
+        mMaxLength = maxLength;
+        if (-1 == maxLength) {
+            setFilters(NO_FILTERS);
+        } else {
+            setFilters(new InputFilter[] {
+                new InputFilter.LengthFilter(maxLength) });
+        }
+    }
+
+    /**
+     *  Set the pointer for this node so it can be determined which node this
+     *  TextDialog represents.
+     *  @param  ptr Integer representing the pointer to the node which this
+     *          TextDialog represents.
+     */
+    /* package */ void setNodePointer(int ptr) {
+        mNodePointer = ptr;
+    }
+
+    /**
+     * Determine the position and size of TextDialog, and add it to the
+     * WebView's view heirarchy.  All parameters are presumed to be in
+     * view coordinates.  Also requests Focus and sets the cursor to not
+     * request to be in view.
+     * @param x         x-position of the textfield.
+     * @param y         y-position of the textfield.
+     * @param width     width of the textfield.
+     * @param height    height of the textfield.
+     */
+    /* package */ void setRect(int x, int y, int width, int height) {
+        LayoutParams lp = (LayoutParams) getLayoutParams();
+        if (null == lp) {
+            lp = new LayoutParams(width, height, x, y);
+        } else {
+            lp.x = x;
+            lp.y = y;
+            lp.width = width;
+            lp.height = height;
+        }
+        if (getParent() == null) {
+            mWebView.addView(this, lp);
+        } else {
+            setLayoutParams(lp);
+        }
+        // Set up a measure spec so a layout can always be recreated.
+        mWidthSpec = MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY);
+        mHeightSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);
+        requestFocus();
+    }
+
+    /**
+     * Set whether this is a single-line textfield or a multi-line textarea.
+     * Textfields scroll horizontally, and do not handle the enter key.
+     * Textareas behave oppositely.
+     * Do NOT call this after calling setInPassword(true).  This will result in
+     * removing the password input type.
+     */
+    public void setSingleLine(boolean single) {
+        if (mSingle != single) {
+            TextKeyListener.Capitalize cap;
+            int inputType = EditorInfo.TYPE_CLASS_TEXT;
+            if (single) {
+                cap = TextKeyListener.Capitalize.NONE;
+            } else {
+                cap = TextKeyListener.Capitalize.SENTENCES;
+                inputType |= EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE;
+            }
+            setKeyListener(TextKeyListener.getInstance(!single, cap));
+            mSingle = single;
+            setHorizontallyScrolling(single);
+            setInputType(inputType);
+        }
+    }
+
+    /**
+     * Set the text for this TextDialog, and set the selection to (start, end)
+     * @param   text    Text to go into this TextDialog.
+     * @param   start   Beginning of the selection.
+     * @param   end     End of the selection.
+     */
+    /* package */ void setText(CharSequence text, int start, int end) {
+        mPreChange = text.toString();
+        setText(text);
+        Spannable span = (Spannable) getText();
+        int length = span.length();
+        if (end > length) {
+            end = length;
+        }
+        if (start < 0) {
+            start = 0;
+        } else if (start > length) {
+            start = length;
+        }
+        Selection.setSelection(span, start, end);
+    }
+
+    /**
+     * Set the text to the new string, but use the old selection, making sure
+     * to keep it within the new string.
+     * @param   text    The new text to place in the textfield.
+     */
+    /* package */ void setTextAndKeepSelection(String text) {
+        mPreChange = text.toString();
+        Editable edit = (Editable) getText();
+        edit.replace(0, edit.length(), text);
+        updateCachedTextfield();
+    }
+    
+    /**
+     *  Update the cache to reflect the current text.
+     */
+    /* package */ void updateCachedTextfield() {
+        mWebView.updateCachedTextfield(getText().toString());
+    }
+}
diff --git a/core/java/android/webkit/URLUtil.java b/core/java/android/webkit/URLUtil.java
new file mode 100644
index 0000000..0e8144e
--- /dev/null
+++ b/core/java/android/webkit/URLUtil.java
@@ -0,0 +1,363 @@
+/*
+ * 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 android.webkit;
+
+import java.io.UnsupportedEncodingException;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import android.net.Uri;
+import android.net.ParseException;
+import android.net.WebAddress;
+import android.util.Config;
+import android.util.Log;
+
+public final class URLUtil {
+
+    private static final String LOGTAG = "webkit";
+    
+    static final String ASSET_BASE = "file:///android_asset/";
+    static final String FILE_BASE = "file://";
+    static final String PROXY_BASE = "file:///cookieless_proxy/";
+
+    /**
+     * Cleans up (if possible) user-entered web addresses
+     */
+    public static String guessUrl(String inUrl) {
+
+        String retVal = inUrl;
+        WebAddress webAddress;
+
+        Log.v(LOGTAG, "guessURL before queueRequest: " + inUrl);
+
+        if (inUrl.length() == 0) return inUrl;
+        if (inUrl.startsWith("about:")) return inUrl;
+        // Do not try to interpret data scheme URLs
+        if (inUrl.startsWith("data:")) return inUrl;
+        // Do not try to interpret file scheme URLs
+        if (inUrl.startsWith("file:")) return inUrl;
+        // Do not try to interpret javascript scheme URLs
+        if (inUrl.startsWith("javascript:")) return inUrl;
+
+        // bug 762454: strip period off end of url
+        if (inUrl.endsWith(".") == true) {
+            inUrl = inUrl.substring(0, inUrl.length() - 1);
+        }
+
+        try {
+            webAddress = new WebAddress(inUrl);
+        } catch (ParseException ex) {
+
+            if (Config.LOGV) {
+                Log.v(LOGTAG, "smartUrlFilter: failed to parse url = " + inUrl);
+            }
+            return retVal;
+        }
+
+        // Check host
+        if (webAddress.mHost.indexOf('.') == -1) {
+            // no dot: user probably entered a bare domain.  try .com
+            webAddress.mHost = "www." + webAddress.mHost + ".com";
+        }
+        return webAddress.toString();
+    }
+
+    public static String composeSearchUrl(String inQuery, String template,
+                                          String queryPlaceHolder) {
+        int placeHolderIndex = template.indexOf(queryPlaceHolder);
+        if (placeHolderIndex < 0) {
+            return null;
+        }
+
+        String query;
+        StringBuilder buffer = new StringBuilder();
+        buffer.append(template.substring(0, placeHolderIndex));
+
+        try {
+            query = java.net.URLEncoder.encode(inQuery, "utf-8");
+            buffer.append(query);
+        } catch (UnsupportedEncodingException ex) {
+            return null;
+        }
+
+        buffer.append(template.substring(
+                placeHolderIndex + queryPlaceHolder.length()));
+
+        return buffer.toString();
+    }
+
+    public static byte[] decode(byte[] url) throws IllegalArgumentException {
+        if (url.length == 0) {
+            return new byte[0];
+        }
+
+        // Create a new byte array with the same length to ensure capacity
+        byte[] tempData = new byte[url.length];
+
+        int tempCount = 0;
+        for (int i = 0; i < url.length; i++) {
+            byte b = url[i];
+            if (b == '%') {
+                if (url.length - i > 2) {
+                    b = (byte) (parseHex(url[i + 1]) * 16
+                            + parseHex(url[i + 2]));
+                    i += 2;
+                } else {
+                    throw new IllegalArgumentException("Invalid format");
+                }
+            }
+            tempData[tempCount++] = b;
+        }
+        byte[] retData = new byte[tempCount];
+        System.arraycopy(tempData, 0, retData, 0, tempCount);
+        return retData;
+    }
+
+    private static int parseHex(byte b) {
+        if (b >= '0' && b <= '9') return (b - '0');
+        if (b >= 'A' && b <= 'F') return (b - 'A' + 10);
+        if (b >= 'a' && b <= 'f') return (b - 'a' + 10);
+
+        throw new IllegalArgumentException("Invalid hex char '" + b + "'");
+    }
+
+    /**
+     * @return True iff the url is an asset file.
+     */
+    public static boolean isAssetUrl(String url) {
+        return (null != url) && url.startsWith(ASSET_BASE);
+    }
+    
+    /**
+     * @return True iff the url is an proxy url to allow cookieless network 
+     * requests from a file url.
+     * @deprecated Cookieless proxy is no longer supported.
+     */
+    public static boolean isCookielessProxyUrl(String url) {
+        return (null != url) && url.startsWith(PROXY_BASE);
+    }
+
+    /**
+     * @return True iff the url is a local file.
+     */
+    public static boolean isFileUrl(String url) {
+        return (null != url) && (url.startsWith(FILE_BASE) &&
+                                 !url.startsWith(ASSET_BASE) &&
+                                 !url.startsWith(PROXY_BASE));
+    }
+
+    /**
+     * @return True iff the url is an about: url.
+     */
+    public static boolean isAboutUrl(String url) {
+        return (null != url) && url.startsWith("about:");
+    }
+
+    /**
+     * @return True iff the url is a data: url.
+     */
+    public static boolean isDataUrl(String url) {
+        return (null != url) && url.startsWith("data:");
+    }
+
+    /**
+     * @return True iff the url is a javascript: url.
+     */
+    public static boolean isJavaScriptUrl(String url) {
+        return (null != url) && url.startsWith("javascript:");
+    }
+
+    /**
+     * @return True iff the url is an http: url.
+     */
+    public static boolean isHttpUrl(String url) {
+        return (null != url) &&
+               (url.length() > 6) &&
+               url.substring(0, 7).equalsIgnoreCase("http://");
+    }
+
+    /**
+     * @return True iff the url is an https: url.
+     */
+    public static boolean isHttpsUrl(String url) {
+        return (null != url) &&
+               (url.length() > 7) &&
+               url.substring(0, 8).equalsIgnoreCase("https://");
+    }
+
+    /**
+     * @return True iff the url is a network url.
+     */
+    public static boolean isNetworkUrl(String url) {
+        if (url == null || url.length() == 0) {
+            return false;
+        }
+        return isHttpUrl(url) || isHttpsUrl(url);
+    }
+
+    /**
+     * @return True iff the url is a content: url.
+     */
+    public static boolean isContentUrl(String url) {
+        return (null != url) && url.startsWith("content:");
+    }
+
+    /**
+     * @return True iff the url is valid.
+     */
+    public static boolean isValidUrl(String url) {
+        if (url == null || url.length() == 0) {
+            return false;
+        }
+
+        return (isAssetUrl(url) ||
+                isFileUrl(url) ||
+                isAboutUrl(url) ||
+                isHttpUrl(url) ||
+                isHttpsUrl(url) ||
+                isJavaScriptUrl(url) ||
+                isContentUrl(url));
+    }
+
+    /**
+     * Strips the url of the anchor.
+     */
+    public static String stripAnchor(String url) {
+        int anchorIndex = url.indexOf('#');
+        if (anchorIndex != -1) {
+            return url.substring(0, anchorIndex);
+        }
+        return url;
+    }
+    
+    /**
+     * Guesses canonical filename that a download would have, using
+     * the URL and contentDisposition. File extension, if not defined,
+     * is added based on the mimetype
+     * @param url Url to the content
+     * @param contentDisposition Content-Disposition HTTP header or null
+     * @param mimeType Mime-type of the content or null
+     * 
+     * @return suggested filename
+     */
+    public static final String guessFileName(
+            String url,
+            String contentDisposition,
+            String mimeType) {
+        String filename = null;
+        String extension = null;
+
+        // If we couldn't do anything with the hint, move toward the content disposition
+        if (filename == null && contentDisposition != null) {
+            filename = parseContentDisposition(contentDisposition);
+            if (filename != null) {
+                int index = filename.lastIndexOf('/') + 1;
+                if (index > 0) {
+                    filename = filename.substring(index);
+                }
+            }
+        }
+
+        // If all the other http-related approaches failed, use the plain uri
+        if (filename == null) {
+            String decodedUrl = Uri.decode(url);
+            if (decodedUrl != null) {
+                int queryIndex = decodedUrl.indexOf('?');
+                // If there is a query string strip it, same as desktop browsers
+                if (queryIndex > 0) {
+                    decodedUrl = decodedUrl.substring(0, queryIndex);
+                }
+                if (!decodedUrl.endsWith("/")) {
+                    int index = decodedUrl.lastIndexOf('/') + 1;
+                    if (index > 0) {
+                        filename = decodedUrl.substring(index);
+                    }
+                }
+            }
+        }
+
+        // Finally, if couldn't get filename from URI, get a generic filename
+        if (filename == null) {
+            filename = "downloadfile";
+        }
+
+        // Split filename between base and extension
+        // Add an extension if filename does not have one
+        int dotIndex = filename.indexOf('.');
+        if (dotIndex < 0) {
+            if (mimeType != null) {
+                extension = MimeTypeMap.getSingleton().getExtensionFromMimeType(mimeType);
+                if (extension != null) {
+                    extension = "." + extension;
+                }
+            }
+            if (extension == null) {
+                if (mimeType != null && mimeType.toLowerCase().startsWith("text/")) {
+                    if (mimeType.equalsIgnoreCase("text/html")) {
+                        extension = ".html";
+                    } else {
+                        extension = ".txt";
+                    }
+                } else {
+                    extension = ".bin";
+                }
+            }
+        } else {
+            if (mimeType != null) {
+                // Compare the last segment of the extension against the mime type.
+                // If there's a mismatch, discard the entire extension.
+                int lastDotIndex = filename.lastIndexOf('.');
+                String typeFromExt = MimeTypeMap.getSingleton().getMimeTypeFromExtension(
+                        filename.substring(lastDotIndex + 1));
+                if (typeFromExt != null && !typeFromExt.equalsIgnoreCase(mimeType)) {
+                    extension = MimeTypeMap.getSingleton().getExtensionFromMimeType(mimeType);
+                    if (extension != null) {
+                        extension = "." + extension;
+                    }
+                }
+            }
+            if (extension == null) {
+                extension = filename.substring(dotIndex);
+            }
+            filename = filename.substring(0, dotIndex);
+        }
+
+        return filename + extension;
+    }
+
+    /** Regex used to parse content-disposition headers */
+    private static final Pattern CONTENT_DISPOSITION_PATTERN =
+            Pattern.compile("attachment;\\s*filename\\s*=\\s*\"([^\"]*)\"");
+
+    /*
+     * Parse the Content-Disposition HTTP Header. The format of the header
+     * is defined here: http://www.w3.org/Protocols/rfc2616/rfc2616-sec19.html
+     * This header provides a filename for content that is going to be
+     * downloaded to the file system. We only support the attachment type.
+     */
+    private static String parseContentDisposition(String contentDisposition) {
+        try {
+            Matcher m = CONTENT_DISPOSITION_PATTERN.matcher(contentDisposition);
+            if (m.find()) {
+                return m.group(1);
+            }
+        } catch (IllegalStateException ex) {
+             // This function is defined as returning null when it can't parse the header
+        }
+        return null;
+    }
+}
diff --git a/core/java/android/webkit/UrlInterceptHandler.java b/core/java/android/webkit/UrlInterceptHandler.java
new file mode 100644
index 0000000..e1c9d61
--- /dev/null
+++ b/core/java/android/webkit/UrlInterceptHandler.java
@@ -0,0 +1,34 @@
+/*
+ * 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 android.webkit;
+
+import android.webkit.CacheManager.CacheResult;
+import java.util.Map;
+
+public interface UrlInterceptHandler {
+
+    /**
+     * Given an URL, returns the CacheResult which contains the
+     * surrogate response for the request, or null if the handler is
+     * not interested.
+     *
+     * @param url URL string.
+     * @param headers The headers associated with the request. May be null.
+     * @return The CacheResult containing the surrogate response.
+     */
+    public CacheResult service(String url, Map<String, String> headers);
+}
diff --git a/core/java/android/webkit/UrlInterceptRegistry.java b/core/java/android/webkit/UrlInterceptRegistry.java
new file mode 100644
index 0000000..a218191
--- /dev/null
+++ b/core/java/android/webkit/UrlInterceptRegistry.java
@@ -0,0 +1,106 @@
+/*
+ * 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 android.webkit;
+
+import android.webkit.CacheManager.CacheResult;
+import android.webkit.UrlInterceptHandler;
+
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.Map;
+
+public final class UrlInterceptRegistry {
+
+    private final static String LOGTAG = "intercept";
+
+    private static boolean mDisabled = false;
+
+    private static LinkedList mHandlerList;
+
+    private static synchronized LinkedList getHandlers() {
+        if(mHandlerList == null)
+            mHandlerList = new LinkedList<UrlInterceptHandler>();
+        return mHandlerList;
+    }
+
+    /**
+     * set the flag to control whether url intercept is enabled or disabled
+     * 
+     * @param disabled true to disable the cache
+     */
+    public static synchronized void setUrlInterceptDisabled(boolean disabled) {
+        mDisabled = disabled;
+    }
+
+    /**
+     * get the state of the url intercept, enabled or disabled
+     * 
+     * @return return if it is disabled
+     */
+    public static synchronized boolean urlInterceptDisabled() {
+        return mDisabled;
+    }
+
+    /**
+     * Register a new UrlInterceptHandler. This handler will be called
+     * before any that were previously registered.
+     *
+     * @param handler The new UrlInterceptHandler object
+     * @return true if the handler was not previously registered.
+     */
+    public static synchronized boolean registerHandler(
+            UrlInterceptHandler handler) {
+        if (!getHandlers().contains(handler)) {
+            getHandlers().addFirst(handler);
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    /**
+     * Unregister a previously registered UrlInterceptHandler.
+     *
+     * @param handler A previously registered UrlInterceptHandler.
+     * @return true if the handler was found and removed from the list.
+     */
+    public static synchronized boolean unregisterHandler(
+            UrlInterceptHandler handler) {
+        return getHandlers().remove(handler);
+    }
+    
+    /**
+     * Given an url, returns the CacheResult of the first
+     * UrlInterceptHandler interested, or null if none are.
+     * 
+     * @return A CacheResult containing surrogate content.
+     */
+    public static synchronized CacheResult getSurrogate(
+            String url, Map<String, String> headers) {
+        if (urlInterceptDisabled())
+            return null;
+        Iterator iter = getHandlers().listIterator();
+        while (iter.hasNext()) {
+            UrlInterceptHandler handler = (UrlInterceptHandler) iter.next();
+            CacheResult result = handler.service(url, headers);
+            if (result != null) {
+                return result;
+            }
+        }
+        return null;
+    }
+}
diff --git a/core/java/android/webkit/WebBackForwardList.java b/core/java/android/webkit/WebBackForwardList.java
new file mode 100644
index 0000000..9dea5ec
--- /dev/null
+++ b/core/java/android/webkit/WebBackForwardList.java
@@ -0,0 +1,188 @@
+/*
+ * 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 android.webkit;
+
+import android.util.Config;
+import java.io.Serializable;
+import java.util.ArrayList;
+
+/**
+ * This class contains the back/forward list for a WebView.
+ * WebView.copyBackForwardList() will return a copy of this class used to
+ * inspect the entries in the list.
+ */
+public class WebBackForwardList implements Cloneable, Serializable {
+    // Current position in the list.
+    private int mCurrentIndex;
+    // ArrayList of WebHistoryItems for maintaining our copy.
+    private ArrayList<WebHistoryItem> mArray;
+    // Flag to indicate that the list is invalid
+    private boolean mClearPending;
+
+    /**
+     * Construct a back/forward list used by clients of WebView.
+     */
+    /*package*/ WebBackForwardList() {
+        mCurrentIndex = -1;
+        mArray = new ArrayList<WebHistoryItem>();
+    }
+
+    /**
+     * Return the current history item. This method returns null if the list is
+     * empty.
+     * @return The current history item.
+     */
+    public synchronized WebHistoryItem getCurrentItem() {
+        return getItemAtIndex(mCurrentIndex);
+    }
+
+    /**
+     * Get the index of the current history item. This index can be used to
+     * directly index into the array list.
+     * @return The current index from 0...n or -1 if the list is empty.
+     */
+    public synchronized int getCurrentIndex() {
+        return mCurrentIndex;
+    }
+
+    /**
+     * Get the history item at the given index. The index range is from 0...n
+     * where 0 is the first item and n is the last item.
+     * @param index The index to retrieve.
+     */
+    public synchronized WebHistoryItem getItemAtIndex(int index) {
+        if (index < 0 || index >= getSize()) {
+            return null;
+        }
+        return mArray.get(index);
+    }
+
+    /**
+     * Get the total size of the back/forward list.
+     * @return The size of the list.
+     */
+    public synchronized int getSize() {
+        return mArray.size();
+    }
+
+    /**
+     * Mark the back/forward list as having a pending clear. This is used on the
+     * UI side to mark the list as being invalid during the clearHistory method.
+     */
+    /*package*/ synchronized void setClearPending() {
+        mClearPending = true;
+    }
+
+    /**
+     * Return the status of the clear flag. This is used on the UI side to
+     * determine if the list is valid for checking things like canGoBack.
+     */
+    /*package*/ synchronized boolean getClearPending() {
+        return mClearPending;
+    }
+
+    /**
+     * Add a new history item to the list. This will remove all items after the
+     * current item and append the new item to the end of the list. Called from
+     * the WebCore thread only. Synchronized because the UI thread may be
+     * reading the array or the current index.
+     * @param item A new history item.
+     */
+    /*package*/ synchronized void addHistoryItem(WebHistoryItem item) {
+        // Update the current position because we are going to add the new item
+        // in that slot.
+        ++mCurrentIndex;
+        // If the current position is not at the end, remove all history items
+        // after the current item.
+        final int size = mArray.size();
+        final int newPos = mCurrentIndex;
+        if (newPos != size) {
+            for (int i = size - 1; i >= newPos; i--) {
+                final WebHistoryItem h = mArray.remove(i);
+            }
+        }
+        // Add the item to the list.
+        mArray.add(item);
+    }
+
+    /**
+     * Clear the back/forward list. Called from the WebCore thread.
+     */
+    /*package*/ synchronized void close(int nativeFrame) {
+        // Clear the array first because nativeClose will call addHistoryItem
+        // with the current item.
+        mArray.clear();
+        mCurrentIndex = -1;
+        nativeClose(nativeFrame);
+        // Reset the clear flag
+        mClearPending = false;
+    }
+
+    /* Remove the item at the given index. Called by JNI only. */
+    private synchronized void removeHistoryItem(int index) {
+        // XXX: This is a special case. Since the callback is only triggered
+        // when removing the first item, we can assert that the index is 0.
+        // This lets us change the current index without having to query the
+        // native BackForwardList.
+        if (Config.DEBUG && (index != 0)) {
+            throw new AssertionError();
+        }
+        final WebHistoryItem h = mArray.remove(index);
+        // XXX: If we ever add another callback for removing history items at
+        // any index, this will no longer be valid.
+        mCurrentIndex--;
+    }
+
+    /**
+     * Clone the entire object to be used in the UI thread by clients of
+     * WebView. This creates a copy that should never be modified by any of the
+     * webkit package classes.
+     */
+    protected synchronized WebBackForwardList clone() {
+        WebBackForwardList l = new WebBackForwardList();
+        if (mClearPending) {
+            // If a clear is pending, return a copy with only the current item.
+            l.addHistoryItem(getCurrentItem());
+            return l;
+        }
+        l.mCurrentIndex = mCurrentIndex;
+        int size = getSize();
+        l.mArray = new ArrayList<WebHistoryItem>(size);
+        for (int i = 0; i < size; i++) {
+            // Add a copy of each WebHistoryItem
+            l.mArray.add(mArray.get(i).clone());
+        }
+        return l;
+    }
+
+    /**
+     * Set the new history index.
+     * @param newIndex The new history index.
+     */
+    /*package*/ synchronized void setCurrentIndex(int newIndex) {
+        mCurrentIndex = newIndex;
+    }
+
+    /**
+     * Restore the history index.
+     */
+    /*package*/ static native synchronized void restoreIndex(int nativeFrame,
+            int index);
+
+    /* Close the native list. */
+    private static native void nativeClose(int nativeFrame);
+}
diff --git a/core/java/android/webkit/WebChromeClient.java b/core/java/android/webkit/WebChromeClient.java
new file mode 100644
index 0000000..f9400061
--- /dev/null
+++ b/core/java/android/webkit/WebChromeClient.java
@@ -0,0 +1,160 @@
+/*
+ * 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 android.webkit;
+
+import android.graphics.Bitmap;
+import android.os.Message;
+
+public class WebChromeClient {
+
+    /**
+     * Tell the host application the current progress of loading a page.
+     * @param view The WebView that initiated the callback.
+     * @param newProgress Current page loading progress, represented by
+     *                    an integer between 0 and 100.
+     */
+    public void onProgressChanged(WebView view, int newProgress) {}
+
+    /**
+     * Notify the host application of a change in the document title.
+     * @param view The WebView that initiated the callback.
+     * @param title A String containing the new title of the document.
+     */
+    public void onReceivedTitle(WebView view, String title) {}
+
+    /**
+     * Notify the host application of a new favicon for the current page.
+     * @param view The WebView that initiated the callback.
+     * @param icon A Bitmap containing the favicon for the current page.
+     */
+    public void onReceivedIcon(WebView view, Bitmap icon) {}
+
+    /**
+     * Request the host application to create a new Webview. The host
+     * application should handle placement of the new WebView in the view
+     * system. The default behavior returns null.
+     * @param view The WebView that initiated the callback.
+     * @param dialog True if the new window is meant to be a small dialog
+     *               window.
+     * @param userGesture True if the request was initiated by a user gesture
+     *                    such as clicking a link.
+     * @param resultMsg The message to send when done creating a new WebView.
+     *                  Set the new WebView through resultMsg.obj which is 
+     *                  WebView.WebViewTransport() and then call
+     *                  resultMsg.sendToTarget();
+     * @return Similar to javscript dialogs, this method should return true if
+     *         the client is going to handle creating a new WebView. Note that
+     *         the WebView will halt processing if this method returns true so
+     *         make sure to call resultMsg.sendToTarget(). It is undefined
+     *         behavior to call resultMsg.sendToTarget() after returning false
+     *         from this method.
+     */
+    public boolean onCreateWindow(WebView view, boolean dialog,
+            boolean userGesture, Message resultMsg) {
+        return false;
+    }
+
+    /**
+     * Request display and focus for this WebView. This may happen due to
+     * another WebView opening a link in this WebView and requesting that this
+     * WebView be displayed.
+     * @param view The WebView that needs to be focused.
+     */
+    public void onRequestFocus(WebView view) {}
+
+    /**
+     * Notify the host application to close the given WebView and remove it
+     * from the view system if necessary. At this point, WebCore has stopped
+     * any loading in this window and has removed any cross-scripting ability
+     * in javascript.
+     * @param window The WebView that needs to be closed.
+     */
+    public void onCloseWindow(WebView window) {}
+
+    /**
+     * Tell the client to display a javascript alert dialog.  If the client
+     * returns true, WebView will assume that the client will handle the
+     * dialog.  If the client returns false, it will continue execution.
+     * @param view The WebView that initiated the callback.
+     * @param url The url of the page requesting the dialog.
+     * @param message Message to be displayed in the window.
+     * @param result A JsResult to confirm that the user hit enter.
+     * @return boolean Whether the client will handle the alert dialog.
+     */
+    public boolean onJsAlert(WebView view, String url, String message,
+            JsResult result) {
+        return false;
+    }
+
+    /**
+     * Tell the client to display a confirm dialog to the user. If the client
+     * returns true, WebView will assume that the client will handle the
+     * confirm dialog and call the appropriate JsResult method. If the
+     * client returns false, a default value of false will be returned to
+     * javascript. The default behavior is to return false.
+     * @param view The WebView that initiated the callback.
+     * @param url The url of the page requesting the dialog.
+     * @param message Message to be displayed in the window.
+     * @param result A JsResult used to send the user's response to
+     *               javascript.
+     * @return boolean Whether the client will handle the confirm dialog.
+     */
+    public boolean onJsConfirm(WebView view, String url, String message,
+            JsResult result) {
+        return false;
+    }
+
+    /**
+     * Tell the client to display a prompt dialog to the user. If the client
+     * returns true, WebView will assume that the client will handle the
+     * prompt dialog and call the appropriate JsPromptResult method. If the
+     * client returns false, a default value of false will be returned to to
+     * javascript. The default behavior is to return false.
+     * @param view The WebView that initiated the callback.
+     * @param url The url of the page requesting the dialog.
+     * @param message Message to be displayed in the window.
+     * @param defaultValue The default value displayed in the prompt dialog.
+     * @param result A JsPromptResult used to send the user's reponse to
+     *               javascript.
+     * @return boolean Whether the client will handle the prompt dialog.
+     */
+    public boolean onJsPrompt(WebView view, String url, String message,
+            String defaultValue, JsPromptResult result) {
+        return false;
+    }
+
+    /**
+     * Tell the client to display a dialog to confirm navigation away from the
+     * current page. This is the result of the onbeforeunload javascript event.
+     * If the client returns true, WebView will assume that the client will
+     * handle the confirm dialog and call the appropriate JsResult method. If
+     * the client returns false, a default value of true will be returned to
+     * javascript to accept navigation away from the current page. The default
+     * behavior is to return false. Setting the JsResult to true will navigate
+     * away from the current page, false will cancel the navigation.
+     * @param view The WebView that initiated the callback.
+     * @param url The url of the page requesting the dialog.
+     * @param message Message to be displayed in the window.
+     * @param result A JsResult used to send the user's response to
+     *               javascript.
+     * @return boolean Whether the client will handle the confirm dialog.
+     */
+    public boolean onJsBeforeUnload(WebView view, String url, String message,
+            JsResult result) {
+        return false;
+    }
+}
diff --git a/core/java/android/webkit/WebHistoryItem.java b/core/java/android/webkit/WebHistoryItem.java
new file mode 100644
index 0000000..a408e06
--- /dev/null
+++ b/core/java/android/webkit/WebHistoryItem.java
@@ -0,0 +1,179 @@
+/*
+ * 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 android.webkit;
+
+import android.graphics.Bitmap;
+
+/**
+ * A convenience class for accessing fields in an entry in the back/forward list
+ * of a WebView. Each WebHistoryItem is a snapshot of the requested history
+ * item. Each history item may be updated during the load of a page.
+ * @see WebBackForwardList
+ */
+public class WebHistoryItem implements Cloneable {
+    // Global identifier count.
+    private static int sNextId = 0;
+    // Unique identifier.
+    private final int mId;
+    // The title of this item's document.
+    private String mTitle;
+    // The base url of this item.
+    private String mUrl;
+    // The original requested url of this item.
+    private String mOriginalUrl;
+    // The favicon for this item.
+    private Bitmap mFavicon;
+    // The pre-flattened data used for saving the state.
+    private byte[] mFlattenedData;
+
+    /**
+     * Basic constructor that assigns a unique id to the item. Called by JNI
+     * only.
+     */
+    private WebHistoryItem() {
+        synchronized (WebHistoryItem.class) {
+            mId = sNextId++;
+        }
+    }
+
+    /**
+     * Construct a new WebHistoryItem with initial flattened data.
+     * @param data The pre-flattened data coming from restoreState.
+     */
+    /*package*/ WebHistoryItem(byte[] data) {
+        mUrl = null; // This will be updated natively
+        mFlattenedData = data;
+        synchronized (WebHistoryItem.class) {
+            mId = sNextId++;
+        }
+    }
+
+    /**
+     * Construct a clone of a WebHistoryItem from the given item.
+     * @param item The history item to clone.
+     */
+    private WebHistoryItem(WebHistoryItem item) {
+        mUrl = item.mUrl;
+        mTitle = item.mTitle;
+        mFlattenedData = item.mFlattenedData;
+        mFavicon = item.mFavicon;
+        mId = item.mId;
+}
+
+    /**
+     * Return an identifier for this history item. If an item is a copy of
+     * another item, the identifiers will be the same even if they are not the
+     * same object.
+     * @return The id for this item.
+     */
+    public int getId() {
+        return mId;
+    }
+
+    /**
+     * Return the url of this history item. The url is the base url of this
+     * history item. See getTargetUrl() for the url that is the actual target of
+     * this history item.
+     * @return The base url of this history item.
+     * Note: The VM ensures 32-bit atomic read/write operations so we don't have
+     * to synchronize this method.
+     */
+    public String getUrl() {
+        return mUrl;
+    }
+
+    /**
+     * Return the original url of this history item. This was the requested
+     * url, the final url may be different as there might have been 
+     * redirects while loading the site.
+     * @return The original url of this history item.
+     * 
+     * @hide pending API Council approval
+     */
+    public String getOriginalUrl() {
+        return mOriginalUrl;
+    }
+    
+    /**
+     * Return the document title of this history item.
+     * @return The document title of this history item.
+     * Note: The VM ensures 32-bit atomic read/write operations so we don't have
+     * to synchronize this method.
+     */
+    public String getTitle() {
+        return mTitle;
+    }
+
+    /**
+     * Return the favicon of this history item or null if no favicon was found.
+     * @return A Bitmap containing the favicon for this history item or null.
+     * Note: The VM ensures 32-bit atomic read/write operations so we don't have
+     * to synchronize this method.
+     */
+    public Bitmap getFavicon() {
+        return mFavicon;
+    }
+
+    /**
+     * Set the favicon.
+     * @param icon A Bitmap containing the favicon for this history item.
+     * Note: The VM ensures 32-bit atomic read/write operations so we don't have
+     * to synchronize this method.
+     */
+    /*package*/ void setFavicon(Bitmap icon) {
+        mFavicon = icon;
+    }
+
+    /**
+     * Get the pre-flattened data.
+     * Note: The VM ensures 32-bit atomic read/write operations so we don't have
+     * to synchronize this method.
+     */
+    /*package*/ byte[] getFlattenedData() {
+        return mFlattenedData;
+    }
+
+    /**
+     * Inflate this item.
+     * Note: The VM ensures 32-bit atomic read/write operations so we don't have
+     * to synchronize this method.
+     */
+    /*package*/ void inflate(int nativeFrame) {
+        inflate(nativeFrame, mFlattenedData);
+    }
+
+    /**
+     * Clone the history item for use by clients of WebView.
+     */
+    protected synchronized WebHistoryItem clone() {
+        return new WebHistoryItem(this);
+    }
+
+    /* Natively inflate this item, this method is called in the WebCore thread.
+     */
+    private native void inflate(int nativeFrame, byte[] data);
+
+    /* Called by jni when the item is updated */
+    private void update(String url, String originalUrl, String title, 
+            Bitmap favicon, byte[] data) {
+        mUrl = url;
+        mOriginalUrl = originalUrl;
+        mTitle = title;
+        mFavicon = favicon;
+        mFlattenedData = data;
+    }
+}
diff --git a/core/java/android/webkit/WebIconDatabase.java b/core/java/android/webkit/WebIconDatabase.java
new file mode 100644
index 0000000..d284f5e
--- /dev/null
+++ b/core/java/android/webkit/WebIconDatabase.java
@@ -0,0 +1,251 @@
+/*
+ * 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 android.webkit;
+
+import android.os.Handler;
+import android.os.Message;
+import android.graphics.Bitmap;
+
+import java.util.Vector;
+
+/**
+ * Functions for manipulating the icon database used by WebView.
+ * These functions require that a WebView be constructed before being invoked
+ * and WebView.getIconDatabase() will return a WebIconDatabase object. This
+ * WebIconDatabase object is a single instance and all methods operate on that
+ * single object.
+ */
+public final class WebIconDatabase {
+    // Global instance of a WebIconDatabase
+    private static WebIconDatabase sIconDatabase;
+    // EventHandler for handling messages before and after the WebCore thread is
+    // ready.
+    private final EventHandler mEventHandler = new EventHandler();
+
+    // Class to handle messages before WebCore is ready
+    private class EventHandler extends Handler {
+        // Message ids
+        static final int OPEN         = 0;
+        static final int CLOSE        = 1;
+        static final int REMOVE_ALL   = 2;
+        static final int REQUEST_ICON = 3;
+        static final int RETAIN_ICON  = 4;
+        static final int RELEASE_ICON = 5;
+        // Message for dispatching icon request results
+        private static final int ICON_RESULT = 10;
+        // Actual handler that runs in WebCore thread
+        private Handler mHandler;
+        // Vector of messages before the WebCore thread is ready
+        private Vector<Message> mMessages = new Vector<Message>();
+        // Class to handle a result dispatch
+        private class IconResult {
+            private final String mUrl;
+            private final Bitmap mIcon;
+            private final IconListener mListener;
+            IconResult(String url, Bitmap icon, IconListener l) {
+                mUrl = url;
+                mIcon = icon;
+                mListener = l;
+            }
+            void dispatch() {
+                mListener.onReceivedIcon(mUrl, mIcon);
+            }
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            // Note: This is the message handler for the UI thread.
+            switch (msg.what) {
+                case ICON_RESULT:
+                    ((IconResult) msg.obj).dispatch();
+                    break;
+            }
+        }
+
+        // Called by WebCore thread to create the actual handler
+        private synchronized void createHandler() {
+            if (mHandler == null) {
+                mHandler = new Handler() {
+                    @Override
+                    public void handleMessage(Message msg) {
+                        // Note: This is the message handler for the WebCore
+                        // thread.
+                        switch (msg.what) {
+                            case OPEN:
+                                nativeOpen((String) msg.obj);
+                                break;
+
+                            case CLOSE:
+                                nativeClose();
+                                break;
+
+                            case REMOVE_ALL:
+                                nativeRemoveAllIcons();
+                                break;
+
+                            case REQUEST_ICON:
+                                IconListener l = (IconListener) msg.obj;
+                                String url = msg.getData().getString("url");
+                                Bitmap icon = nativeIconForPageUrl(url);
+                                if (icon != null) {
+                                    EventHandler.this.sendMessage(
+                                            Message.obtain(null, ICON_RESULT,
+                                                new IconResult(url, icon, l)));
+                                }
+                                break;
+
+                            case RETAIN_ICON:
+                                nativeRetainIconForPageUrl((String) msg.obj);
+                                break;
+
+                            case RELEASE_ICON:
+                                nativeReleaseIconForPageUrl((String) msg.obj);
+                                break;
+                        }
+                    }
+                };
+                // Transfer all pending messages
+                for (int size = mMessages.size(); size > 0; size--) {
+                    mHandler.sendMessage(mMessages.remove(0));
+                }
+                mMessages = null;
+            }
+        }
+
+        private synchronized void postMessage(Message msg) {
+            if (mMessages != null) {
+                mMessages.add(msg);
+            } else {
+                mHandler.sendMessage(msg);
+            }
+        }
+    }
+
+    /**
+     * Interface for receiving icons from the database.
+     */
+    public interface IconListener {
+        /**
+         * Called when the icon has been retrieved from the database and the
+         * result is non-null.
+         * @param url The url passed in the request.
+         * @param icon The favicon for the given url.
+         */
+        public void onReceivedIcon(String url, Bitmap icon);
+    }
+
+    /**
+     * Open a the icon database and store the icons in the given path.
+     * @param path The directory path where the icon database will be stored.
+     * @return True if the database was successfully opened or created in
+     *         the given path.
+     */
+    public void open(String path) {
+        if (path != null) {
+            mEventHandler.postMessage(
+                    Message.obtain(null, EventHandler.OPEN, path));
+        }
+    }
+
+    /**
+     * Close the shared instance of the icon database.
+     */
+    public void close() {
+        mEventHandler.postMessage(
+                Message.obtain(null, EventHandler.CLOSE));
+    }
+
+    /**
+     * Removes all the icons in the database.
+     */
+    public void removeAllIcons() {
+        mEventHandler.postMessage(
+                Message.obtain(null, EventHandler.REMOVE_ALL));
+    }
+
+    /**
+     * Request the Bitmap representing the icon for the given page
+     * url. If the icon exists, the listener will be called with the result.
+     * @param url The page's url.
+     * @param listener An implementation on IconListener to receive the result.
+     */
+    public void requestIconForPageUrl(String url, IconListener listener) {
+        if (listener == null || url == null) {
+            return;
+        }
+        Message msg = Message.obtain(null, EventHandler.REQUEST_ICON, listener);
+        msg.getData().putString("url", url);
+        mEventHandler.postMessage(msg);
+    }
+
+    /**
+     * Retain the icon for the given page url.
+     * @param url The page's url.
+     */
+    public void retainIconForPageUrl(String url) {
+        if (url != null) {
+            mEventHandler.postMessage(
+                    Message.obtain(null, EventHandler.RETAIN_ICON, url));
+        }
+    }
+
+    /**
+     * Release the icon for the given page url.
+     * @param url The page's url.
+     */
+    public void releaseIconForPageUrl(String url) {
+        if (url != null) {
+            mEventHandler.postMessage(
+                    Message.obtain(null, EventHandler.RELEASE_ICON, url));
+        }
+    }
+
+    /**
+     * Get the global instance of WebIconDatabase.
+     * @return A single instance of WebIconDatabase. It will be the same
+     *         instance for the current process each time this method is
+     *         called.
+     */
+    public static WebIconDatabase getInstance() {
+        // XXX: Must be created in the UI thread.
+        if (sIconDatabase == null) {
+            sIconDatabase = new WebIconDatabase();
+        }
+        return sIconDatabase;
+    }
+
+    /**
+     * Create the internal handler and transfer all pending messages.
+     * XXX: Called by WebCore thread only!
+     */
+    /*package*/ void createHandler() {
+        mEventHandler.createHandler();
+    }
+
+    /**
+     * Private constructor to avoid anyone else creating an instance.
+     */
+    private WebIconDatabase() {}
+
+    // Native functions
+    private static native void nativeOpen(String path);
+    private static native void nativeClose();
+    private static native void nativeRemoveAllIcons();
+    private static native Bitmap nativeIconForPageUrl(String url);
+    private static native void nativeRetainIconForPageUrl(String url);
+    private static native void nativeReleaseIconForPageUrl(String url);
+}
diff --git a/core/java/android/webkit/WebSettings.java b/core/java/android/webkit/WebSettings.java
new file mode 100644
index 0000000..4e2b2ab
--- /dev/null
+++ b/core/java/android/webkit/WebSettings.java
@@ -0,0 +1,1112 @@
+/*
+ * Copyright (C) 2007 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 android.webkit;
+
+import android.content.Context;
+import android.os.Build;
+import android.os.Handler;
+import android.os.Message;
+import android.provider.Checkin;
+
+import java.lang.SecurityException;
+import android.content.pm.PackageManager;
+
+import java.util.Locale;
+
+/**
+ * Manages settings state for a WebView. When a WebView is first created, it
+ * obtains a set of default settings. These default settings will be returned
+ * from any getter call. A WebSettings object obtained from
+ * WebView.getSettings() is tied to the life of the WebView. If a WebView has
+ * been destroyed, any method call on WebSettings will throw an
+ * IllegalStateException.
+ */
+public class WebSettings {
+    /**
+     * Enum for controlling the layout of html.
+     * NORMAL means no rendering changes.
+     * SINGLE_COLUMN moves all content into one column that is the width of the
+     * view.
+     * NARROW_COLUMNS makes all columns no wider than the screen if possible.
+     */
+    // XXX: These must match LayoutAlgorithm in Settings.h in WebCore.
+    public enum LayoutAlgorithm {
+        NORMAL,
+        SINGLE_COLUMN,
+        NARROW_COLUMNS
+    }
+
+    /**
+     * Enum for specifying the text size.
+     * SMALLEST is 50%
+     * SMALLER is 75%
+     * NORMAL is 100%
+     * LARGER is 150%
+     * LARGEST is 200%
+     */
+    public enum TextSize {
+        SMALLEST(50),
+        SMALLER(75),
+        NORMAL(100),
+        LARGER(150),
+        LARGEST(200);
+        TextSize(int size) {
+            value = size;
+        }
+        int value;
+    }
+    
+    /**
+     * Default cache usage pattern  Use with {@link #setCacheMode}.
+     */
+    public static final int LOAD_DEFAULT = -1;
+
+    /**
+     * Normal cache usage pattern  Use with {@link #setCacheMode}.
+     */
+    public static final int LOAD_NORMAL = 0;
+
+    /**
+     * Use cache if content is there, even if expired (eg, history nav)
+     * If it is not in the cache, load from network.
+     * Use with {@link #setCacheMode}.
+     */
+    public static final int LOAD_CACHE_ELSE_NETWORK = 1;
+
+    /**
+     * Don't use the cache, load from network
+     * Use with {@link #setCacheMode}.
+     */
+    public static final int LOAD_NO_CACHE = 2;
+    
+    /**
+     * Don't use the network, load from cache only.
+     * Use with {@link #setCacheMode}.
+     */
+    public static final int LOAD_CACHE_ONLY = 3;
+
+    public enum RenderPriority {
+        NORMAL,
+        HIGH,
+        LOW
+    }
+
+    // BrowserFrame used to access the native frame pointer.
+    private BrowserFrame mBrowserFrame;
+    // Flag to prevent multiple SYNC messages at one time.
+    private boolean mSyncPending = false;
+    // Custom handler that queues messages until the WebCore thread is active.
+    private final EventHandler mEventHandler;
+    // Private settings so we don't have to go into native code to
+    // retrieve the values. After setXXX, postSync() needs to be called.
+    // XXX: The default values need to match those in WebSettings.cpp
+    private LayoutAlgorithm mLayoutAlgorithm = LayoutAlgorithm.NARROW_COLUMNS;
+    private Context         mContext;
+    private TextSize        mTextSize = TextSize.NORMAL;
+    private String          mStandardFontFamily = "sans-serif";
+    private String          mFixedFontFamily = "monospace";
+    private String          mSansSerifFontFamily = "sans-serif";
+    private String          mSerifFontFamily = "serif";
+    private String          mCursiveFontFamily = "cursive";
+    private String          mFantasyFontFamily = "fantasy";
+    private String          mDefaultTextEncoding = "Latin-1";
+    private String          mUserAgent;
+    private boolean         mUseDefaultUserAgent;
+    private String          mAcceptLanguage;
+    private String          mPluginsPath = "";
+    private int             mMinimumFontSize = 8;
+    private int             mMinimumLogicalFontSize = 8;
+    private int             mDefaultFontSize = 16;
+    private int             mDefaultFixedFontSize = 13;
+    private boolean         mLoadsImagesAutomatically = true;
+    private boolean         mBlockNetworkImage = false;
+    private boolean         mBlockNetworkLoads = false;
+    private boolean         mJavaScriptEnabled = false;
+    private boolean         mPluginsEnabled = false;
+    private boolean         mJavaScriptCanOpenWindowsAutomatically = false;
+    private boolean         mUseDoubleTree = false;
+    private boolean         mUseWideViewport = false;
+    private boolean         mSupportMultipleWindows = false;
+    private boolean         mShrinksStandaloneImagesToFit = false;
+    // Don't need to synchronize the get/set methods as they
+    // are basic types, also none of these values are used in
+    // native WebCore code.
+    private RenderPriority  mRenderPriority = RenderPriority.NORMAL;
+    private int             mOverrideCacheMode = LOAD_DEFAULT;
+    private boolean         mSaveFormData = true;
+    private boolean         mSavePassword = true;
+    private boolean         mLightTouchEnabled = false;
+    private boolean         mNeedInitialFocus = true;
+    private boolean         mNavDump = false;
+    private boolean         mSupportZoom = true;
+    private boolean         mAllowFileAccess = true;
+
+    // Class to handle messages before WebCore is ready.
+    private class EventHandler {
+        // Message id for syncing
+        static final int SYNC = 0;
+        // Message id for setting priority
+        static final int PRIORITY = 1;
+        // Actual WebCore thread handler
+        private Handler mHandler;
+
+        private synchronized void createHandler() {
+            // as mRenderPriority can be set before thread is running, sync up
+            setRenderPriority();
+
+            // create a new handler
+            mHandler = new Handler() {
+                @Override
+                public void handleMessage(Message msg) {
+                    switch (msg.what) {
+                        case SYNC:
+                            synchronized (WebSettings.this) {
+                                if (mBrowserFrame.mNativeFrame != 0) {
+                                    nativeSync(mBrowserFrame.mNativeFrame);
+                                }
+                                mSyncPending = false;
+                            }
+                            break;
+
+                        case PRIORITY: {
+                            setRenderPriority();
+                            break;
+                        }
+                    }
+                }
+            };
+        }
+
+        private void setRenderPriority() {
+            synchronized (WebSettings.this) {
+                if (mRenderPriority == RenderPriority.NORMAL) {
+                    android.os.Process.setThreadPriority(
+                            android.os.Process.THREAD_PRIORITY_DEFAULT);
+                } else if (mRenderPriority == RenderPriority.HIGH) {
+                    android.os.Process.setThreadPriority(
+                            android.os.Process.THREAD_PRIORITY_FOREGROUND +
+                            android.os.Process.THREAD_PRIORITY_LESS_FAVORABLE);
+                } else if (mRenderPriority == RenderPriority.LOW) {
+                    android.os.Process.setThreadPriority(
+                            android.os.Process.THREAD_PRIORITY_BACKGROUND);
+                }
+            }
+        }
+
+        /**
+         * Send a message to the private queue or handler.
+         */
+        private synchronized boolean sendMessage(Message msg) {
+            if (mHandler != null) {
+                mHandler.sendMessage(msg);
+                return true;
+            } else {
+                return false;
+            }
+        }
+    }
+
+    // User agent strings.
+    private static final String DESKTOP_USERAGENT =
+            "Mozilla/5.0 (Macintosh; U; Intel Mac OS X; en)"
+            + " AppleWebKit/528.5+ (KHTML, like Gecko) Version/3.1.2"
+            + " Safari/525.20.1";
+    private static final String IPHONE_USERAGENT = 
+            "Mozilla/5.0 (iPhone; U; CPU iPhone 2_1 like Mac OS X; en)"
+            + " AppleWebKit/528.5+ (KHTML, like Gecko) Version/3.1.2"
+            + " Mobile/5F136 Safari/525.20.1";
+    private static Locale sLocale;
+    private static Object sLockForLocaleSettings;
+    
+    /**
+     * Package constructor to prevent clients from creating a new settings
+     * instance.
+     */
+    WebSettings(Context context) {   
+        mEventHandler = new EventHandler();
+        mContext = context;
+
+        if (sLockForLocaleSettings == null) {
+            sLockForLocaleSettings = new Object();
+            sLocale = Locale.getDefault();
+        }
+        mAcceptLanguage = getCurrentAcceptLanguage();
+        mUserAgent = getCurrentUserAgent();
+        mUseDefaultUserAgent = true;
+
+        verifyNetworkAccess();
+    }
+
+    /**
+     * Looks at sLocale and returns current AcceptLanguage String.
+     * @return Current AcceptLanguage String.
+     */
+    private String getCurrentAcceptLanguage() {
+        Locale locale;
+        synchronized(sLockForLocaleSettings) {
+            locale = sLocale;
+        }
+        StringBuffer buffer = new StringBuffer();
+        final String language = locale.getLanguage();
+        if (language != null) {
+            buffer.append(language);
+            final String country = locale.getCountry();
+            if (country != null) {
+                buffer.append("-");
+                buffer.append(country);
+            }
+        }
+        if (!locale.equals(Locale.US)) {
+            buffer.append(", ");
+            java.util.Locale us = Locale.US;
+            if (us.getLanguage() != null) {
+                buffer.append(us.getLanguage());
+                final String country = us.getCountry();
+                if (country != null) {
+                    buffer.append("-");
+                    buffer.append(country);
+                }
+            }
+        }
+
+        return buffer.toString();
+    }
+    
+    /**
+     * Looks at sLocale and mContext and returns current UserAgent String.
+     * @return Current UserAgent String.
+     */
+    private synchronized String getCurrentUserAgent() {
+        Locale locale;
+        synchronized(sLockForLocaleSettings) {
+            locale = sLocale;
+        }
+        StringBuffer buffer = new StringBuffer();
+        // Add version
+        final String version = Build.VERSION.RELEASE;
+        if (version.length() > 0) {
+            buffer.append(version);
+        } else {
+            // default to "1.0"
+            buffer.append("1.0");
+        }  
+        buffer.append("; ");
+        final String language = locale.getLanguage();
+        if (language != null) {
+            buffer.append(language.toLowerCase());
+            final String country = locale.getCountry();
+            if (country != null) {
+                buffer.append("-");
+                buffer.append(country.toLowerCase());
+            }
+        } else {
+            // default to "en"
+            buffer.append("en");
+        }
+        
+        final String model = Build.MODEL;
+        if (model.length() > 0) {
+            buffer.append("; ");
+            buffer.append(model);
+        }
+        final String id = Build.ID;
+        if (id.length() > 0) {
+            buffer.append(" Build/");
+            buffer.append(id);
+        }
+        final String base = mContext.getResources().getText(
+                com.android.internal.R.string.web_user_agent).toString();
+        return String.format(base, buffer);
+    }
+    
+    /**
+     * Enables dumping the pages navigation cache to a text file.
+     */
+    public void setNavDump(boolean enabled) {
+        mNavDump = enabled;
+    }
+
+    /**
+     * Returns true if dumping the navigation cache is enabled.
+     */
+    public boolean getNavDump() {
+        return mNavDump;
+    }
+
+    /**
+     * Set whether the WebView supports zoom
+     */
+    public void setSupportZoom(boolean support) {
+        mSupportZoom = support;
+    }
+
+    /**
+     * Returns whether the WebView supports zoom
+     */
+    public boolean supportZoom() {
+        return mSupportZoom;
+    }
+
+    /**
+     * Enable or disable file access within WebView. File access is enabled by
+     * default.
+     */
+    public void setAllowFileAccess(boolean allow) {
+        mAllowFileAccess = allow;
+    }
+
+    /**
+     * Returns true if this WebView supports file access.
+     */
+    public boolean getAllowFileAccess() {
+        return mAllowFileAccess;
+    }
+
+    /**
+     * Store whether the WebView is saving form data.
+     */
+    public void setSaveFormData(boolean save) {
+        mSaveFormData = save;
+    }
+
+    /**
+     *  Return whether the WebView is saving form data.
+     */
+    public boolean getSaveFormData() {
+        return mSaveFormData;
+    }
+
+    /**
+     *  Store whether the WebView is saving password.
+     */
+    public void setSavePassword(boolean save) {
+        mSavePassword = save;
+    }
+
+    /**
+     *  Return whether the WebView is saving password.
+     */
+    public boolean getSavePassword() {
+        return mSavePassword;
+    }
+
+    /**
+     * Set the text size of the page.
+     * @param t A TextSize value for increasing or decreasing the text.
+     * @see WebSettings.TextSize
+     */
+    public synchronized void setTextSize(TextSize t) {
+        if (WebView.mLogEvent && mTextSize != t ) {
+            Checkin.updateStats(mContext.getContentResolver(),
+                    Checkin.Stats.Tag.BROWSER_TEXT_SIZE_CHANGE, 1, 0.0);
+        }
+        mTextSize = t;
+        postSync();
+    }
+
+    /**
+     * Get the text size of the page.
+     * @return A TextSize enum value describing the text size.
+     * @see WebSettings.TextSize
+     */
+    public synchronized TextSize getTextSize() {
+        return mTextSize;
+    }
+
+    /**
+     * Enables using light touches to make a selection and activate mouseovers.
+     */
+    public void setLightTouchEnabled(boolean enabled) {
+        mLightTouchEnabled = enabled;
+    }
+
+    /**
+     * Returns true if light touches are enabled.
+     */
+    public boolean getLightTouchEnabled() {
+        return mLightTouchEnabled;
+    }
+
+    /**
+     * Tell the WebView to use the double tree rendering algorithm.
+     * @param use True if the WebView is to use double tree rendering, false
+     *            otherwise.
+     */
+    public synchronized void setUseDoubleTree(boolean use) {
+        if (mUseDoubleTree != use) {
+            mUseDoubleTree = use;
+            postSync();
+        }
+    }
+
+    /**
+     * Return true if the WebView is using the double tree rendering algorithm.
+     * @return True if the WebView is using the double tree rendering
+     *         algorithm.
+     */
+    public synchronized boolean getUseDoubleTree() {
+        return mUseDoubleTree;
+    }
+
+    /**
+     * Tell the WebView about user-agent string.
+     * @param ua 0 if the WebView should use an Android user-agent string,
+     *           1 if the WebView should use a desktop user-agent string.
+     *
+     * @deprecated Please use setUserAgentString instead.
+     */
+    @Deprecated
+    public synchronized void setUserAgent(int ua) {
+        String uaString = null;
+        if (ua == 1) {
+            if (DESKTOP_USERAGENT.equals(mUserAgent)) {
+                return; // do nothing
+            } else {
+                uaString = DESKTOP_USERAGENT;
+            }
+        } else if (ua == 2) {
+            if (IPHONE_USERAGENT.equals(mUserAgent)) {
+                return; // do nothing
+            } else {
+                uaString = IPHONE_USERAGENT;
+            }
+        } else if (ua != 0) {
+            return; // do nothing
+        }
+        setUserAgentString(uaString);
+    }
+
+    /**
+     * Return user-agent as int
+     * @return int  0 if the WebView is using an Android user-agent string.
+     *              1 if the WebView is using a desktop user-agent string.
+     *             -1 if the WebView is using user defined user-agent string.
+     *
+     * @deprecated Please use getUserAgentString instead.
+     */
+    @Deprecated
+    public synchronized int getUserAgent() {
+        if (DESKTOP_USERAGENT.equals(mUserAgent)) {
+            return 1;
+        } else if (IPHONE_USERAGENT.equals(mUserAgent)) {
+            return 2;
+        } else if (mUseDefaultUserAgent) {
+            return 0;
+        }
+        return -1;
+    }
+
+    /**
+     * Tell the WebView to use the wide viewport
+     */
+    public synchronized void setUseWideViewPort(boolean use) {
+        if (mUseWideViewport != use) {
+            mUseWideViewport = use;
+            postSync();
+        }
+    }
+
+    /**
+     * @return True if the WebView is using a wide viewport
+     */
+    public synchronized boolean getUseWideViewPort() {
+        return mUseWideViewport;
+    }
+
+    /**
+     * Tell the WebView whether it supports multiple windows. TRUE means
+     *         that {@link WebChromeClient#onCreateWindow(WebView, boolean,
+     *         boolean, Message)} is implemented by the host application.
+     */
+    public synchronized void setSupportMultipleWindows(boolean support) {
+        if (mSupportMultipleWindows != support) {
+            mSupportMultipleWindows = support;
+            postSync();
+        }
+    }
+
+    /**
+     * @return True if the WebView is supporting multiple windows. This means
+     *         that {@link WebChromeClient#onCreateWindow(WebView, boolean,
+     *         boolean, Message)} is implemented by the host application.
+     */
+    public synchronized boolean supportMultipleWindows() {
+        return mSupportMultipleWindows;
+    }
+
+    /**
+     * Set the underlying layout algorithm. This will cause a relayout of the
+     * WebView.
+     * @param l A LayoutAlgorithm enum specifying the algorithm to use.
+     * @see WebSettings.LayoutAlgorithm
+     */
+    public synchronized void setLayoutAlgorithm(LayoutAlgorithm l) {
+        // XXX: This will only be affective if libwebcore was built with
+        // ANDROID_LAYOUT defined.
+        if (mLayoutAlgorithm != l) {
+            mLayoutAlgorithm = l;
+            postSync();
+        }
+    }
+
+    /**
+     * Return the current layout algorithm.
+     * @return LayoutAlgorithm enum value describing the layout algorithm
+     *         being used.
+     * @see WebSettings.LayoutAlgorithm
+     */
+    public synchronized LayoutAlgorithm getLayoutAlgorithm() {
+        return mLayoutAlgorithm;
+    }
+
+    /**
+     * Set the standard font family name.
+     * @param font A font family name.
+     */
+    public synchronized void setStandardFontFamily(String font) {
+        if (font != null && !font.equals(mStandardFontFamily)) {
+            mStandardFontFamily = font;
+            postSync();
+        }
+    }
+
+    /**
+     * Get the standard font family name.
+     * @return The standard font family name as a string.
+     */
+    public synchronized String getStandardFontFamily() {
+        return mStandardFontFamily;
+    }
+
+    /**
+     * Set the fixed font family name.
+     * @param font A font family name.
+     */
+    public synchronized void setFixedFontFamily(String font) {
+        if (font != null && !font.equals(mFixedFontFamily)) {
+            mFixedFontFamily = font;
+            postSync();
+        }
+    }
+
+    /**
+     * Get the fixed font family name.
+     * @return The fixed font family name as a string.
+     */
+    public synchronized String getFixedFontFamily() {
+        return mFixedFontFamily;
+    }
+
+    /**
+     * Set the sans-serif font family name.
+     * @param font A font family name.
+     */
+    public synchronized void setSansSerifFontFamily(String font) {
+        if (font != null && !font.equals(mSansSerifFontFamily)) {
+            mSansSerifFontFamily = font;
+            postSync();
+        }
+    }
+
+    /**
+     * Get the sans-serif font family name.
+     * @return The sans-serif font family name as a string.
+     */
+    public synchronized String getSansSerifFontFamily() {
+        return mSansSerifFontFamily;
+    }
+
+    /**
+     * Set the serif font family name.
+     * @param font A font family name.
+     */
+    public synchronized void setSerifFontFamily(String font) {
+        if (font != null && !font.equals(mSerifFontFamily)) {
+            mSerifFontFamily = font;
+            postSync();
+        }
+    }
+
+    /**
+     * Get the serif font family name.
+     * @return The serif font family name as a string.
+     */
+    public synchronized String getSerifFontFamily() {
+        return mSerifFontFamily;
+    }
+
+    /**
+     * Set the cursive font family name.
+     * @param font A font family name.
+     */
+    public synchronized void setCursiveFontFamily(String font) {
+        if (font != null && !font.equals(mCursiveFontFamily)) {
+            mCursiveFontFamily = font;
+            postSync();
+        }
+    }
+
+    /**
+     * Get the cursive font family name.
+     * @return The cursive font family name as a string.
+     */
+    public synchronized String getCursiveFontFamily() {
+        return mCursiveFontFamily;
+    }
+
+    /**
+     * Set the fantasy font family name.
+     * @param font A font family name.
+     */
+    public synchronized void setFantasyFontFamily(String font) {
+        if (font != null && !font.equals(mFantasyFontFamily)) {
+            mFantasyFontFamily = font;
+            postSync();
+        }
+    }
+
+    /**
+     * Get the fantasy font family name.
+     * @return The fantasy font family name as a string.
+     */
+    public synchronized String getFantasyFontFamily() {
+        return mFantasyFontFamily;
+    }
+
+    /**
+     * Set the minimum font size.
+     * @param size A non-negative integer between 1 and 72.
+     * Any number outside the specified range will be pinned.
+     */
+    public synchronized void setMinimumFontSize(int size) {
+        size = pin(size);
+        if (mMinimumFontSize != size) {
+            mMinimumFontSize = size;
+            postSync();
+        }
+    }
+
+    /**
+     * Get the minimum font size.
+     * @return A non-negative integer between 1 and 72.
+     */
+    public synchronized int getMinimumFontSize() {
+        return mMinimumFontSize;
+    }
+
+    /**
+     * Set the minimum logical font size.
+     * @param size A non-negative integer between 1 and 72.
+     * Any number outside the specified range will be pinned.
+     */
+    public synchronized void setMinimumLogicalFontSize(int size) {
+        size = pin(size);
+        if (mMinimumLogicalFontSize != size) {
+            mMinimumLogicalFontSize = size;
+            postSync();
+        }
+    }
+
+    /**
+     * Get the minimum logical font size.
+     * @return A non-negative integer between 1 and 72.
+     */
+    public synchronized int getMinimumLogicalFontSize() {
+        return mMinimumLogicalFontSize;
+    }
+
+    /**
+     * Set the default font size.
+     * @param size A non-negative integer between 1 and 72.
+     * Any number outside the specified range will be pinned.
+     */
+    public synchronized void setDefaultFontSize(int size) {
+        size = pin(size);
+        if (mDefaultFontSize != size) {
+            mDefaultFontSize = size;
+            postSync();
+        }
+    }
+
+    /**
+     * Get the default font size.
+     * @return A non-negative integer between 1 and 72.
+     */
+    public synchronized int getDefaultFontSize() {
+        return mDefaultFontSize;
+    }
+
+    /**
+     * Set the default fixed font size.
+     * @param size A non-negative integer between 1 and 72.
+     * Any number outside the specified range will be pinned.
+     */
+    public synchronized void setDefaultFixedFontSize(int size) {
+        size = pin(size);
+        if (mDefaultFixedFontSize != size) {
+            mDefaultFixedFontSize = size;
+            postSync();
+        }
+    }
+
+    /**
+     * Get the default fixed font size.
+     * @return A non-negative integer between 1 and 72.
+     */
+    public synchronized int getDefaultFixedFontSize() {
+        return mDefaultFixedFontSize;
+    }
+
+    /**
+     * Tell the WebView to load image resources automatically.
+     * @param flag True if the WebView should load images automatically.
+     */
+    public synchronized void setLoadsImagesAutomatically(boolean flag) {
+        if (mLoadsImagesAutomatically != flag) {
+            mLoadsImagesAutomatically = flag;
+            postSync();
+        }
+    }
+
+    /**
+     * Return true if the WebView will load image resources automatically.
+     * @return True if the WebView loads images automatically.
+     */
+    public synchronized boolean getLoadsImagesAutomatically() {
+        return mLoadsImagesAutomatically;
+    }
+
+    /**
+     * Tell the WebView to block network image. This is only checked when
+     * getLoadsImagesAutomatically() is true.
+     * @param flag True if the WebView should block network image
+     */
+    public synchronized void setBlockNetworkImage(boolean flag) {
+        if (mBlockNetworkImage != flag) {
+            mBlockNetworkImage = flag;
+            postSync();
+        }
+    }
+
+    /**
+     * Return true if the WebView will block network image.
+     * @return True if the WebView blocks network image.
+     */
+    public synchronized boolean getBlockNetworkImage() {
+        return mBlockNetworkImage;
+    }
+    
+    /**
+     * @hide
+     * Tell the WebView to block all network load requests. 
+     * @param flag True if the WebView should block all network loads
+     */
+    public synchronized void setBlockNetworkLoads(boolean flag) {
+        if (mBlockNetworkLoads != flag) {
+            mBlockNetworkLoads = flag;
+            verifyNetworkAccess();
+        }
+    }
+
+    /**
+     * @hide
+     * Return true if the WebView will block all network loads.
+     * @return True if the WebView blocks all network loads.
+     */
+    public synchronized boolean getBlockNetworkLoads() {
+        return mBlockNetworkLoads;
+    }
+    
+    
+    private void verifyNetworkAccess() {
+        if (!mBlockNetworkLoads) {
+            if (mContext.checkPermission("android.permission.INTERNET", 
+                    android.os.Process.myPid(), 0) != 
+                        PackageManager.PERMISSION_GRANTED) {
+                throw new SecurityException
+                        ("Permission denied - " +
+                                "application missing INTERNET permission");
+            }
+        }
+    }
+
+    /**
+     * Tell the WebView to enable javascript execution.
+     * @param flag True if the WebView should execute javascript.
+     */
+    public synchronized void setJavaScriptEnabled(boolean flag) {
+        if (mJavaScriptEnabled != flag) {
+            mJavaScriptEnabled = flag;
+            postSync();
+        }
+    }
+
+    /**
+     * Tell the WebView to enable plugins.
+     * @param flag True if the WebView should load plugins.
+     */
+    public synchronized void setPluginsEnabled(boolean flag) {
+        if (mPluginsEnabled != flag) {
+            mPluginsEnabled = flag;
+            postSync();
+        }
+    }
+
+    /**
+     * Set a custom path to plugins used by the WebView. The client
+     * must ensure it exists before this call.
+     * @param pluginsPath String path to the directory containing plugins.
+     */
+    public synchronized void setPluginsPath(String pluginsPath) {
+        if (pluginsPath != null && !pluginsPath.equals(mPluginsPath)) {
+            mPluginsPath = pluginsPath;
+            postSync();
+        }
+    }
+
+    /**
+     * Return true if javascript is enabled.
+     * @return True if javascript is enabled.
+     */
+    public synchronized boolean getJavaScriptEnabled() {
+        return mJavaScriptEnabled;
+    }
+
+    /**
+     * Return true if plugins are enabled.
+     * @return True if plugins are enabled.
+     */
+    public synchronized boolean getPluginsEnabled() {
+        return mPluginsEnabled;
+    }
+
+    /**
+     * Return the current path used for plugins in the WebView.
+     * @return The string path to the WebView plugins.
+     */
+    public synchronized String getPluginsPath() {
+        return mPluginsPath;
+    }
+
+    /**
+     * Tell javascript to open windows automatically. This applies to the
+     * javascript function window.open().
+     * @param flag True if javascript can open windows automatically.
+     */
+    public synchronized void setJavaScriptCanOpenWindowsAutomatically(
+            boolean flag) {
+        if (mJavaScriptCanOpenWindowsAutomatically != flag) {
+            mJavaScriptCanOpenWindowsAutomatically = flag;
+            postSync();
+        }
+    }
+
+    /**
+     * Return true if javascript can open windows automatically.
+     * @return True if javascript can open windows automatically during
+     *         window.open().
+     */
+    public synchronized boolean getJavaScriptCanOpenWindowsAutomatically() {
+        return mJavaScriptCanOpenWindowsAutomatically;
+    }
+
+    /**
+     * Set the default text encoding name to use when decoding html pages.
+     * @param encoding The text encoding name.
+     */
+    public synchronized void setDefaultTextEncodingName(String encoding) {
+        if (encoding != null && !encoding.equals(mDefaultTextEncoding)) {
+            mDefaultTextEncoding = encoding;
+            postSync();
+        }
+    }
+
+    /**
+     * Get the default text encoding name.
+     * @return The default text encoding name as a string.
+     */
+    public synchronized String getDefaultTextEncodingName() {
+        return mDefaultTextEncoding;
+    }
+
+    /**
+     * Set the WebView's user-agent string. If the string "ua" is null or empty,
+     * it will use the system default user-agent string.
+     */
+    public synchronized void setUserAgentString(String ua) {
+        if (ua == null || ua.length() == 0) {
+            synchronized(sLockForLocaleSettings) {
+                Locale currentLocale = Locale.getDefault(); 
+                if (!sLocale.equals(currentLocale)) {
+                    sLocale = currentLocale;
+                    mAcceptLanguage = getCurrentAcceptLanguage();
+                }
+            }
+            ua = getCurrentUserAgent();
+            mUseDefaultUserAgent = true;
+        } else  {
+            mUseDefaultUserAgent = false;
+        }
+
+        if (!ua.equals(mUserAgent)) {
+            mUserAgent = ua;
+            postSync();
+        }
+    }
+
+    /**
+     * Return the WebView's user-agent string.
+     */
+    public synchronized String getUserAgentString() {
+        if (DESKTOP_USERAGENT.equals(mUserAgent) ||
+                IPHONE_USERAGENT.equals(mUserAgent) ||
+                !mUseDefaultUserAgent) {
+            return mUserAgent;
+        }
+
+        boolean doPostSync = false;
+        synchronized(sLockForLocaleSettings) {
+            Locale currentLocale = Locale.getDefault();
+            if (!sLocale.equals(currentLocale)) {
+                sLocale = currentLocale;
+                mUserAgent = getCurrentUserAgent();
+                mAcceptLanguage = getCurrentAcceptLanguage();
+                doPostSync = true;
+            }
+        }
+        if (doPostSync) {
+            postSync();
+        }
+        return mUserAgent;
+    }
+
+    /* package api to grab the Accept Language string. */
+    /*package*/ synchronized String getAcceptLanguage() {
+        synchronized(sLockForLocaleSettings) {
+            Locale currentLocale = Locale.getDefault();
+            if (!sLocale.equals(currentLocale)) {
+                sLocale = currentLocale;
+                mAcceptLanguage = getCurrentAcceptLanguage();
+            }
+        }
+        return mAcceptLanguage;
+    }
+    
+    /**
+     * Tell the WebView whether it needs to set a node to have focus when
+     * {@link WebView#requestFocus(int, android.graphics.Rect)} is called.
+     * 
+     * @param flag
+     */
+    public void setNeedInitialFocus(boolean flag) {
+        if (mNeedInitialFocus != flag) {
+            mNeedInitialFocus = flag;
+        }
+    }
+
+    /* Package api to get the choice whether it needs to set initial focus. */
+    /* package */ boolean getNeedInitialFocus() {
+        return mNeedInitialFocus;
+    }
+
+    /**
+     * Set the priority of the Render thread. Unlike the other settings, this
+     * one only needs to be called once per process.
+     * 
+     * @param priority RenderPriority, can be normal, high or low.
+     */
+    public synchronized void setRenderPriority(RenderPriority priority) {
+        if (mRenderPriority != priority) {
+            mRenderPriority = priority;
+            mEventHandler.sendMessage(Message.obtain(null,
+                    EventHandler.PRIORITY));
+        }
+    }
+    
+    /**
+     * Override the way the cache is used. The way the cache is used is based
+     * on the navigation option. For a normal page load, the cache is checked
+     * and content is re-validated as needed. When navigating back, content is
+     * not revalidated, instead the content is just pulled from the cache.
+     * This function allows the client to override this behavior.
+     * @param mode One of the LOAD_ values.
+     */
+    public void setCacheMode(int mode) {
+        if (mode != mOverrideCacheMode) {
+            mOverrideCacheMode = mode;
+        }
+    }
+    
+    /**
+     * Return the current setting for overriding the cache mode. For a full
+     * description, see the {@link #setCacheMode(int)} function.
+     */
+    public int getCacheMode() {
+        return mOverrideCacheMode;
+    }
+    
+    /**
+     * If set, webkit alternately shrinks and expands images viewed outside
+     * of an HTML page to fit the screen. This conflicts with attempts by
+     * the UI to zoom in and out of an image, so it is set false by default.
+     * @param shrink Set true to let webkit shrink the standalone image to fit.
+     * {@hide}
+     */
+    public void setShrinksStandaloneImagesToFit(boolean shrink) {
+        if (mShrinksStandaloneImagesToFit != shrink) {
+            mShrinksStandaloneImagesToFit = shrink;
+            postSync();
+        }
+     }
+
+    /**
+     * Transfer messages from the queue to the new WebCoreThread. Called from
+     * WebCore thread.
+     */
+    /*package*/
+    synchronized void syncSettingsAndCreateHandler(BrowserFrame frame) {
+        mBrowserFrame = frame;
+        if (android.util.Config.DEBUG) {
+            junit.framework.Assert.assertTrue(frame.mNativeFrame != 0);
+        }
+        nativeSync(frame.mNativeFrame);
+        mSyncPending = false;
+        mEventHandler.createHandler();
+    }
+
+    private int pin(int size) {
+        // FIXME: 72 is just an arbitrary max text size value.
+        if (size < 1) {
+            return 1;
+        } else if (size > 72) {
+            return 72;
+        }
+        return size;
+    }
+
+    /* Post a SYNC message to handle syncing the native settings. */
+    private synchronized void postSync() {
+        // Only post if a sync is not pending
+        if (!mSyncPending) {
+            mSyncPending = mEventHandler.sendMessage(
+                    Message.obtain(null, EventHandler.SYNC));
+        }
+    }
+
+    // Synchronize the native and java settings.
+    private native void nativeSync(int nativeFrame);
+}
diff --git a/core/java/android/webkit/WebSyncManager.java b/core/java/android/webkit/WebSyncManager.java
new file mode 100644
index 0000000..e6e9994
--- /dev/null
+++ b/core/java/android/webkit/WebSyncManager.java
@@ -0,0 +1,162 @@
+/*
+ * Copyright (C) 2007 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 android.webkit;
+
+import android.content.Context;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.Process;
+import android.util.Config;
+import android.util.Log;
+
+abstract class WebSyncManager implements Runnable {
+    // message code for sync message
+    private static final int SYNC_MESSAGE = 101;
+    // time delay in millisec for a sync (now) message
+    private static int SYNC_NOW_INTERVAL = 100; // 100 millisec
+    // time delay in millisec for a sync (later) message
+    private static int SYNC_LATER_INTERVAL = 5 * 60 * 1000; // 5 minutes
+    // thread for syncing
+    private Thread mSyncThread;
+    // Name of thread
+    private String mThreadName;
+    // handler of the sync thread
+    protected Handler mHandler;
+    // database for the persistent storage
+    protected WebViewDatabase mDataBase;
+    // Ref count for calls to start/stop sync
+    private int mStartSyncRefCount;
+    // log tag
+    protected static final String LOGTAG = "websync";
+
+    private class SyncHandler extends Handler {
+        @Override
+        public void handleMessage(Message msg) {
+            if (msg.what == SYNC_MESSAGE) {
+                if (Config.LOGV) {
+                    Log.v(LOGTAG, "*** WebSyncManager sync ***");
+                }
+                syncFromRamToFlash();
+
+                // send a delayed message to request sync later
+                Message newmsg = obtainMessage(SYNC_MESSAGE);
+                sendMessageDelayed(newmsg, SYNC_LATER_INTERVAL);
+            }
+        }
+    }
+
+    protected WebSyncManager(Context context, String name) {
+        mThreadName = name;
+        if (context != null) {
+            mDataBase = WebViewDatabase.getInstance(context);
+            mSyncThread = new Thread(this);
+            mSyncThread.setName(mThreadName);
+            mSyncThread.start();
+        } else {
+            throw new IllegalStateException(
+                    "WebSyncManager can't be created without context");
+        }
+    }
+
+    protected Object clone() throws CloneNotSupportedException {
+        throw new CloneNotSupportedException("doesn't implement Cloneable");
+    }
+
+    public void run() {
+        // prepare Looper for sync handler
+        Looper.prepare();
+        mHandler = new SyncHandler();
+        onSyncInit();
+        // lower the priority after onSyncInit() is done
+       Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
+
+        Message msg = mHandler.obtainMessage(SYNC_MESSAGE);
+        mHandler.sendMessageDelayed(msg, SYNC_LATER_INTERVAL);
+
+        Looper.loop();
+    }
+
+    /**
+     * sync() forces sync manager to sync now
+     */
+    public void sync() {
+        if (Config.LOGV) {
+            Log.v(LOGTAG, "*** WebSyncManager sync ***");
+        }
+        if (mHandler == null) {
+            return;
+        }
+        mHandler.removeMessages(SYNC_MESSAGE);
+        Message msg = mHandler.obtainMessage(SYNC_MESSAGE);
+        mHandler.sendMessageDelayed(msg, SYNC_NOW_INTERVAL);
+    }
+
+    /**
+     * resetSync() resets sync manager's timer
+     */
+    public void resetSync() {
+        if (Config.LOGV) {
+            Log.v(LOGTAG, "*** WebSyncManager resetSync ***");
+        }
+        if (mHandler == null) {
+            return;
+        }
+        mHandler.removeMessages(SYNC_MESSAGE);
+        Message msg = mHandler.obtainMessage(SYNC_MESSAGE);
+        mHandler.sendMessageDelayed(msg, SYNC_LATER_INTERVAL);
+    }
+
+    /**
+     * startSync() requests sync manager to start sync
+     */
+    public void startSync() {
+        if (Config.LOGV) {
+            Log.v(LOGTAG, "***  WebSyncManager startSync ***, Ref count:" + 
+                    mStartSyncRefCount);
+        }
+        if (mHandler == null) {
+            return;
+        }
+        if (++mStartSyncRefCount == 1) {
+            Message msg = mHandler.obtainMessage(SYNC_MESSAGE);
+            mHandler.sendMessageDelayed(msg, SYNC_LATER_INTERVAL);
+        }
+    }
+
+    /**
+     * stopSync() requests sync manager to stop sync. remove any SYNC_MESSAGE in
+     * the queue to break the sync loop
+     */
+    public void stopSync() {
+        if (Config.LOGV) {
+            Log.v(LOGTAG, "*** WebSyncManager stopSync ***, Ref count:" + 
+                    mStartSyncRefCount);
+        }
+        if (mHandler == null) {
+            return;
+        }
+        if (--mStartSyncRefCount == 0) {
+            mHandler.removeMessages(SYNC_MESSAGE);
+        }
+    }
+
+    protected void onSyncInit() {
+    }
+
+    abstract void syncFromRamToFlash();
+}
diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java
new file mode 100644
index 0000000..5126ef0
--- /dev/null
+++ b/core/java/android/webkit/WebView.java
@@ -0,0 +1,5406 @@
+/*
+ * 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 android.webkit;
+
+import android.app.AlertDialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.DialogInterface.OnCancelListener;
+import android.content.res.TypedArray;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.Path;
+import android.graphics.Picture;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.graphics.Region;
+import android.net.http.SslCertificate;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
+import android.os.ServiceManager;
+import android.os.SystemClock;
+import android.provider.Checkin;
+import android.text.IClipboard;
+import android.text.Selection;
+import android.text.Spannable;
+import android.util.AttributeSet;
+import android.util.Config;
+import android.util.EventLog;
+import android.util.Log;
+import android.view.animation.AlphaAnimation;
+import android.view.animation.Animation;
+import android.view.animation.AnimationUtils;
+import android.view.Gravity;
+import android.view.KeyEvent;
+import android.view.LayoutInflater;
+import android.view.MotionEvent;
+import android.view.SoundEffectConstants;
+import android.view.VelocityTracker;
+import android.view.View;
+import android.view.ViewConfiguration;
+import android.view.ViewGroup;
+import android.view.ViewParent;
+import android.view.ViewTreeObserver;
+import android.view.inputmethod.InputMethodManager;
+import android.webkit.TextDialog.AutoCompleteAdapter;
+import android.webkit.WebViewCore.EventHub;
+import android.widget.AbsoluteLayout;
+import android.widget.AdapterView;
+import android.widget.ArrayAdapter;
+import android.widget.ImageView;
+import android.widget.ListView;
+import android.widget.RelativeLayout;
+import android.widget.Scroller;
+import android.widget.Toast;
+import android.widget.ZoomButtonsController;
+import android.widget.ZoomControls;
+import android.widget.ZoomRingController;
+import android.widget.FrameLayout;
+import android.widget.AdapterView.OnItemClickListener;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.net.URLDecoder;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+
+/**
+ * <p>A View that displays web pages. This class is the basis upon which you 
+ * can roll your own web browser or simply display some online content within your Activity.
+ * It uses the WebKit rendering engine to display
+ * web pages and includes methods to navigate forward and backward
+ * through a history, zoom in and out, perform text searches and more.</p>
+ * <p>Note that, in order for your Activity to access the Internet and load web pages
+ * in a WebView, you must add the <var>INTERNET</var> permissions to your 
+ * Android Manifest file:</p>
+ * <pre>&lt;uses-permission android:name="android.permission.INTERNET" /></pre>
+ * <p>This must be a child of the <code>&lt;manifest></code> element.</p>
+ */
+public class WebView extends AbsoluteLayout 
+        implements ViewTreeObserver.OnGlobalFocusChangeListener,
+        ViewGroup.OnHierarchyChangeListener {
+
+    // if AUTO_REDRAW_HACK is true, then the CALL key will toggle redrawing
+    // the screen all-the-time. Good for profiling our drawing code
+    static private final boolean AUTO_REDRAW_HACK = false;
+    // true means redraw the screen all-the-time. Only with AUTO_REDRAW_HACK
+    private boolean mAutoRedraw;
+
+    // keep debugging parameters near the top of the file
+    static final String LOGTAG = "webview";
+    static final boolean DEBUG = false;
+    static final boolean LOGV_ENABLED = DEBUG ? Config.LOGD : Config.LOGV;
+
+    private class ExtendedZoomControls extends FrameLayout {
+        public ExtendedZoomControls(Context context, AttributeSet attrs) {
+            super(context, attrs);
+            LayoutInflater inflater = (LayoutInflater)
+                    context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+            inflater.inflate(com.android.internal.R.layout.zoom_magnify, this, true);
+            mZoomControls = (ZoomControls) findViewById(com.android.internal.R.id.zoomControls);
+            mZoomMagnify = (ImageView) findViewById(com.android.internal.R.id.zoomMagnify);
+        }
+        
+        public void show(boolean showZoom, boolean canZoomOut) {
+            mZoomControls.setVisibility(showZoom ? View.VISIBLE : View.GONE);
+            mZoomMagnify.setVisibility(canZoomOut ? View.VISIBLE : View.GONE);
+            fade(View.VISIBLE, 0.0f, 1.0f);
+        }
+        
+        public void hide() {
+            fade(View.GONE, 1.0f, 0.0f);
+        }
+        
+        private void fade(int visibility, float startAlpha, float endAlpha) {
+            AlphaAnimation anim = new AlphaAnimation(startAlpha, endAlpha);
+            anim.setDuration(500);
+            startAnimation(anim);
+            setVisibility(visibility);
+        }
+        
+        public void setIsZoomMagnifyEnabled(boolean isEnabled) {
+            mZoomMagnify.setEnabled(isEnabled);
+        }
+        
+        public boolean hasFocus() {
+            return mZoomControls.hasFocus() || mZoomMagnify.hasFocus();
+        }
+        
+        public void setOnZoomInClickListener(OnClickListener listener) {
+            mZoomControls.setOnZoomInClickListener(listener);
+        }
+            
+        public void setOnZoomOutClickListener(OnClickListener listener) {
+            mZoomControls.setOnZoomOutClickListener(listener);
+        }
+            
+        public void setOnZoomMagnifyClickListener(OnClickListener listener) {
+            mZoomMagnify.setOnClickListener(listener);
+        }
+
+        ZoomControls mZoomControls;
+        ImageView mZoomMagnify;
+    }
+    
+    /**
+     *  Transportation object for returning WebView across thread boundaries.
+     */
+    public class WebViewTransport {
+        private WebView mWebview;
+
+        /**
+         * Set the WebView to the transportation object.
+         * @param webview The WebView to transport.
+         */
+        public synchronized void setWebView(WebView webview) {
+            mWebview = webview;
+        }
+
+        /**
+         * Return the WebView object.
+         * @return WebView The transported WebView object.
+         */
+        public synchronized WebView getWebView() {
+            return mWebview;
+        }
+    }
+
+    // A final CallbackProxy shared by WebViewCore and BrowserFrame.
+    private final CallbackProxy mCallbackProxy;
+
+    private final WebViewDatabase mDatabase;
+
+    // SSL certificate for the main top-level page (if secure)
+    private SslCertificate mCertificate;
+
+    // Native WebView pointer that is 0 until the native object has been
+    // created.
+    private int mNativeClass;
+    // This would be final but it needs to be set to null when the WebView is
+    // destroyed.
+    private WebViewCore mWebViewCore;
+    // Handler for dispatching UI messages.
+    /* package */ final Handler mPrivateHandler = new PrivateHandler();
+    private TextDialog mTextEntry;
+    // Used to ignore changes to webkit text that arrives to the UI side after
+    // more key events.
+    private int mTextGeneration;
+
+    // The list of loaded plugins.
+    private static PluginList sPluginList;
+
+    /**
+     * Position of the last touch event.
+     */
+    private float mLastTouchX;
+    private float mLastTouchY;
+
+    /**
+     * Time of the last touch event.
+     */
+    private long mLastTouchTime;
+
+    /**
+     * Time of the last time sending touch event to WebViewCore
+     */
+    private long mLastSentTouchTime;
+
+    /**
+     * The minimum elapsed time before sending another ACTION_MOVE event to
+     * WebViewCore
+     */
+    private static final int TOUCH_SENT_INTERVAL = 100;
+
+    /**
+     * Helper class to get velocity for fling
+     */
+    VelocityTracker mVelocityTracker;
+
+    private static boolean mShowZoomRingTutorial = true;
+    private static final int ZOOM_RING_TUTORIAL_DURATION = 3000;
+
+    /**
+     * Touch mode
+     */
+    private int mTouchMode = TOUCH_DONE_MODE;
+    private static final int TOUCH_INIT_MODE = 1;
+    private static final int TOUCH_DRAG_START_MODE = 2;
+    private static final int TOUCH_DRAG_MODE = 3;
+    private static final int TOUCH_SHORTPRESS_START_MODE = 4;
+    private static final int TOUCH_SHORTPRESS_MODE = 5;
+    private static final int TOUCH_DOUBLECLICK_MODE = 6;
+    private static final int TOUCH_DONE_MODE = 7;
+    private static final int TOUCH_SELECT_MODE = 8;
+    // touch mode values specific to scale+scroll
+    private static final int FIRST_SCROLL_ZOOM = 9;
+    private static final int SCROLL_ZOOM_ANIMATION_IN = 9;
+    private static final int SCROLL_ZOOM_ANIMATION_OUT = 10;
+    private static final int SCROLL_ZOOM_OUT = 11;
+    private static final int LAST_SCROLL_ZOOM = 11;
+    // end of touch mode values specific to scale+scroll
+
+    // Whether to forward the touch events to WebCore
+    private boolean mForwardTouchEvents = false;
+
+    // Whether we are in the drag tap mode, which exists starting at the second
+    // tap's down, through its move, and includes its up. These events should be
+    // given to the method on the zoom controller.
+    private boolean mInZoomTapDragMode = false;
+
+    // Whether to prevent drag during touch. The initial value depends on
+    // mForwardTouchEvents. If WebCore wants touch events, we assume it will
+    // take control of touch events unless it says no for touch down event.
+    private boolean mPreventDrag;
+
+    // If updateTextEntry gets called while we are out of focus, use this 
+    // variable to remember to do it next time we gain focus.
+    private boolean mNeedsUpdateTextEntry = false;
+    
+    // Whether or not to draw the focus ring.
+    private boolean mDrawFocusRing = true;
+
+    /**
+     * Customizable constant
+     */
+    // pre-computed square of ViewConfiguration.getScaledTouchSlop()
+    private int mTouchSlopSquare;
+    // pre-computed square of ViewConfiguration.getScaledDoubleTapSlop()
+    private int mDoubleTapSlopSquare;
+    // This should be ViewConfiguration.getTapTimeout()
+    // But system time out is 100ms, which is too short for the browser.
+    // In the browser, if it switches out of tap too soon, jump tap won't work.
+    private static final int TAP_TIMEOUT = 200;
+    // The duration in milliseconds we will wait to see if it is a double tap.
+    private static final int DOUBLE_TAP_TIMEOUT = 250;
+    // This should be ViewConfiguration.getLongPressTimeout()
+    // But system time out is 500ms, which is too short for the browser.
+    // With a short timeout, it's difficult to treat trigger a short press.
+    private static final int LONG_PRESS_TIMEOUT = 1000;
+    // needed to avoid flinging after a pause of no movement
+    private static final int MIN_FLING_TIME = 250;
+    // The time that the Zoom Controls are visible before fading away
+    private static final long ZOOM_CONTROLS_TIMEOUT = 
+            ViewConfiguration.getZoomControlsTimeout();
+    // The amount of content to overlap between two screens when going through
+    // pages with the space bar, in pixels.
+    private static final int PAGE_SCROLL_OVERLAP = 24;
+
+    /**
+     * These prevent calling requestLayout if either dimension is fixed. This
+     * depends on the layout parameters and the measure specs.
+     */
+    boolean mWidthCanMeasure;
+    boolean mHeightCanMeasure;
+
+    // Remember the last dimensions we sent to the native side so we can avoid
+    // sending the same dimensions more than once.
+    int mLastWidthSent;
+    int mLastHeightSent;
+
+    private int mContentWidth;   // cache of value from WebViewCore
+    private int mContentHeight;  // cache of value from WebViewCore
+
+    static int MAX_FLOAT_CONTENT_WIDTH = 480;
+    // the calculated minimum content width for calculating the minimum scale.
+    // If it is 0, it means don't use it.
+    private int mMinContentWidth;
+
+    // Need to have the separate control for horizontal and vertical scrollbar 
+    // style than the View's single scrollbar style
+    private boolean mOverlayHorizontalScrollbar = true;
+    private boolean mOverlayVerticalScrollbar = false;
+
+    // our standard speed. this way small distances will be traversed in less
+    // time than large distances, but we cap the duration, so that very large
+    // distances won't take too long to get there.
+    private static final int STD_SPEED = 480;  // pixels per second
+    // time for the longest scroll animation
+    private static final int MAX_DURATION = 750;   // milliseconds
+    private Scroller mScroller;
+
+    private boolean mWrapContent;
+
+    // The View containing the zoom controls
+    private ExtendedZoomControls mZoomControls;
+    private Runnable mZoomControlRunnable;
+
+    // true if we should call webcore to draw the content, false means we have
+    // requested something but it isn't ready to draw yet.
+    private WebViewCore.FocusData mFocusData;
+    /**
+     * Private message ids
+     */
+    private static final int REMEMBER_PASSWORD = 1;
+    private static final int NEVER_REMEMBER_PASSWORD = 2;
+    private static final int SWITCH_TO_SHORTPRESS = 3;
+    private static final int SWITCH_TO_LONGPRESS = 4;
+    private static final int RELEASE_SINGLE_TAP = 5;
+    private static final int UPDATE_TEXT_ENTRY_ADAPTER = 6;
+    private static final int SWITCH_TO_ENTER = 7;
+    private static final int RESUME_WEBCORE_UPDATE = 8;
+    private static final int DISMISS_ZOOM_RING_TUTORIAL = 9;
+
+    //! arg1=x, arg2=y
+    static final int SCROLL_TO_MSG_ID               = 10;
+    static final int SCROLL_BY_MSG_ID               = 11;
+    //! arg1=x, arg2=y
+    static final int SPAWN_SCROLL_TO_MSG_ID         = 12;
+    //! arg1=x, arg2=y
+    static final int SYNC_SCROLL_TO_MSG_ID          = 13;
+    static final int NEW_PICTURE_MSG_ID             = 14;
+    static final int UPDATE_TEXT_ENTRY_MSG_ID       = 15;
+    static final int WEBCORE_INITIALIZED_MSG_ID     = 16;
+    static final int UPDATE_TEXTFIELD_TEXT_MSG_ID   = 17;
+    static final int DID_FIRST_LAYOUT_MSG_ID        = 18;
+    static final int RECOMPUTE_FOCUS_MSG_ID         = 19;
+    static final int NOTIFY_FOCUS_SET_MSG_ID        = 20;
+    static final int MARK_NODE_INVALID_ID           = 21;
+    static final int UPDATE_CLIPBOARD               = 22;
+    static final int LONG_PRESS_ENTER               = 23;
+    static final int PREVENT_TOUCH_ID               = 24;
+    static final int WEBCORE_NEED_TOUCH_EVENTS      = 25;
+    // obj=Rect in doc coordinates
+    static final int INVAL_RECT_MSG_ID              = 26;
+    
+    static final String[] HandlerDebugString = {
+        "REMEMBER_PASSWORD", // = 1;
+        "NEVER_REMEMBER_PASSWORD", // = 2;
+        "SWITCH_TO_SHORTPRESS", // = 3;
+        "SWITCH_TO_LONGPRESS", // = 4;
+        "RELEASE_SINGLE_TAP", // = 5;
+        "UPDATE_TEXT_ENTRY_ADAPTER", // = 6;
+        "SWITCH_TO_ENTER", // = 7;
+        "RESUME_WEBCORE_UPDATE", // = 8;
+        "9",
+        "SCROLL_TO_MSG_ID", //               = 10;
+        "SCROLL_BY_MSG_ID", //               = 11;
+        "SPAWN_SCROLL_TO_MSG_ID", //         = 12;
+        "SYNC_SCROLL_TO_MSG_ID", //          = 13;
+        "NEW_PICTURE_MSG_ID", //             = 14;
+        "UPDATE_TEXT_ENTRY_MSG_ID", //       = 15;
+        "WEBCORE_INITIALIZED_MSG_ID", //     = 16;
+        "UPDATE_TEXTFIELD_TEXT_MSG_ID", //   = 17;
+        "DID_FIRST_LAYOUT_MSG_ID", //        = 18;
+        "RECOMPUTE_FOCUS_MSG_ID", //         = 19;
+        "NOTIFY_FOCUS_SET_MSG_ID", //        = 20;
+        "MARK_NODE_INVALID_ID", //           = 21;
+        "UPDATE_CLIPBOARD", //               = 22;
+        "LONG_PRESS_ENTER", //               = 23;
+        "PREVENT_TOUCH_ID", //               = 24;
+        "WEBCORE_NEED_TOUCH_EVENTS", //      = 25;
+        "INVAL_RECT_MSG_ID" //               = 26;
+    };
+
+    // width which view is considered to be fully zoomed out
+    static final int ZOOM_OUT_WIDTH = 1024;
+
+    private static final float MAX_ZOOM_RING_ANGLE = (float) (Math.PI * 2 / 3);
+    private static final int ZOOM_RING_STEPS = 4;
+    private static final float ZOOM_RING_ANGLE_UNIT = MAX_ZOOM_RING_ANGLE
+            / ZOOM_RING_STEPS;
+
+    private static final float DEFAULT_MAX_ZOOM_SCALE = 2;
+    private static final float DEFAULT_MIN_ZOOM_SCALE = (float) 1/3;
+    // scale limit, which can be set through viewport meta tag in the web page
+    private float mMaxZoomScale = DEFAULT_MAX_ZOOM_SCALE;
+    private float mMinZoomScale = DEFAULT_MIN_ZOOM_SCALE;
+
+    // initial scale in percent. 0 means using default.
+    private int mInitialScale = 0;
+
+    // set to true temporarily while the zoom control is being dragged
+    private boolean mPreviewZoomOnly = false;
+
+    // computed scale and inverse, from mZoomWidth.
+    private float mActualScale = 1;
+    private float mInvActualScale = 1;
+    // if this is non-zero, it is used on drawing rather than mActualScale
+    private float mZoomScale;
+    private float mInvInitialZoomScale;
+    private float mInvFinalZoomScale;
+    private long mZoomStart;
+    private static final int ZOOM_ANIMATION_LENGTH = 500;
+
+    private boolean mUserScroll = false;
+
+    private int mSnapScrollMode = SNAP_NONE;
+    private static final int SNAP_NONE = 1;
+    private static final int SNAP_X = 2;
+    private static final int SNAP_Y = 3;
+    private static final int SNAP_X_LOCK = 4;
+    private static final int SNAP_Y_LOCK = 5;
+    private boolean mSnapPositive;
+    
+    // Used to match key downs and key ups
+    private boolean mGotKeyDown;
+
+    /* package */ static boolean mLogEvent = true;
+    private static final int EVENT_LOG_ZOOM_LEVEL_CHANGE = 70101;
+    private static final int EVENT_LOG_DOUBLE_TAP_DURATION = 70102;
+
+    // for event log
+    private long mLastTouchUpTime = 0;
+
+    /**
+     * URI scheme for telephone number
+     */
+    public static final String SCHEME_TEL = "tel:";
+    /**
+     * URI scheme for email address
+     */
+    public static final String SCHEME_MAILTO = "mailto:";
+    /**
+     * URI scheme for map address
+     */
+    public static final String SCHEME_GEO = "geo:0,0?q=";
+    
+    private int mBackgroundColor = Color.WHITE;
+
+    // Used to notify listeners of a new picture.
+    private PictureListener mPictureListener;
+    /**
+     * Interface to listen for new pictures as they change.
+     */
+    public interface PictureListener {
+        /**
+         * Notify the listener that the picture has changed.
+         * @param view The WebView that owns the picture.
+         * @param picture The new picture.
+         */
+        public void onNewPicture(WebView view, Picture picture);
+    }
+
+    public class HitTestResult {
+        /**
+         * Default HitTestResult, where the target is unknown
+         */
+        public static final int UNKNOWN_TYPE = 0;
+        /**
+         * HitTestResult for hitting a HTML::a tag
+         */
+        public static final int ANCHOR_TYPE = 1;
+        /**
+         * HitTestResult for hitting a phone number
+         */
+        public static final int PHONE_TYPE = 2;
+        /**
+         * HitTestResult for hitting a map address
+         */
+        public static final int GEO_TYPE = 3;
+        /**
+         * HitTestResult for hitting an email address
+         */
+        public static final int EMAIL_TYPE = 4;
+        /**
+         * HitTestResult for hitting an HTML::img tag
+         */
+        public static final int IMAGE_TYPE = 5;
+        /**
+         * HitTestResult for hitting a HTML::a tag which contains HTML::img
+         */
+        public static final int IMAGE_ANCHOR_TYPE = 6;
+        /**
+         * HitTestResult for hitting a HTML::a tag with src=http
+         */
+        public static final int SRC_ANCHOR_TYPE = 7;
+        /**
+         * HitTestResult for hitting a HTML::a tag with src=http + HTML::img
+         */
+        public static final int SRC_IMAGE_ANCHOR_TYPE = 8;
+        /**
+         * HitTestResult for hitting an edit text area
+         */
+        public static final int EDIT_TEXT_TYPE = 9;
+
+        private int mType;
+        private String mExtra;
+
+        HitTestResult() {
+            mType = UNKNOWN_TYPE;
+        }
+
+        private void setType(int type) {
+            mType = type;
+        }
+
+        private void setExtra(String extra) {
+            mExtra = extra;
+        }
+
+        public int getType() {
+            return mType;
+        }
+
+        public String getExtra() {
+            return mExtra;
+        }
+    }
+
+    private ZoomButtonsController mZoomButtonsController; 
+
+    private ZoomRingController mZoomRingController;
+    private ImageView mZoomRingOverview;
+    private Animation mZoomRingOverviewExitAnimation;
+
+    // These keep track of the center point of the zoom ring.  They are used to
+    // determine the point around which we should zoom.
+    private float mZoomCenterX;
+    private float mZoomCenterY;
+
+    private ZoomRingController.OnZoomListener mZoomListener =
+            new ZoomRingController.OnZoomListener() {
+
+        private float mClockwiseBound;
+        private float mCounterClockwiseBound;
+        private float mStartScale;
+
+        public void onCenter(int x, int y) {
+            // Don't translate when the control is invoked, hence we do nothing
+            // in this callback
+        }
+
+        public void onBeginPan() {
+            setZoomOverviewVisible(false);
+            if (mLogEvent) {
+                Checkin.updateStats(mContext.getContentResolver(),
+                        Checkin.Stats.Tag.BROWSER_ZOOM_RING_DRAG, 1, 0.0);
+            }
+        }
+
+        public boolean onPan(int deltaX, int deltaY) {
+            return pinScrollBy(deltaX, deltaY, false, 0);
+        }
+
+        public void onEndPan() {
+        }
+
+        public void onVisibilityChanged(boolean visible) {
+            if (visible) {
+                switchOutDrawHistory();
+                if (mMaxZoomScale - 1 > ZOOM_RING_STEPS * 0.01f) {
+                    mClockwiseBound = (float) (2 * Math.PI - MAX_ZOOM_RING_ANGLE);
+                } else {
+                    mClockwiseBound = (float) (2 * Math.PI);
+                }
+                mZoomRingController.setThumbClockwiseBound(mClockwiseBound);
+                if (1 - mMinZoomScale > ZOOM_RING_STEPS * 0.01f) {
+                    mCounterClockwiseBound = MAX_ZOOM_RING_ANGLE;
+                } else {
+                    mCounterClockwiseBound = 0;
+                }
+                mZoomRingController
+                        .setThumbCounterclockwiseBound(mCounterClockwiseBound);
+                float angle = 0f;
+                if (mActualScale > 1 && mClockwiseBound < (float) (2 * Math.PI)) {
+                    angle = -(float) Math.round(ZOOM_RING_STEPS
+                            * (mActualScale - 1) / (mMaxZoomScale - 1))
+                            / ZOOM_RING_STEPS;
+                } else if (mActualScale < 1 && mCounterClockwiseBound > 0) {
+                    angle = (float) Math.round(ZOOM_RING_STEPS
+                            * (1 - mActualScale) / (1 - mMinZoomScale))
+                            / ZOOM_RING_STEPS;
+                }
+                mZoomRingController.setThumbAngle(angle * MAX_ZOOM_RING_ANGLE);
+
+                // Don't show a thumb if the user cannot zoom
+                mZoomRingController.setThumbVisible(mMinZoomScale != mMaxZoomScale);
+                
+                // Show the zoom overview tab on the ring
+                setZoomOverviewVisible(true);
+                if (mLogEvent) {
+                    Checkin.updateStats(mContext.getContentResolver(),
+                            Checkin.Stats.Tag.BROWSER_ZOOM_RING, 1, 0.0);
+                }
+            }
+        }
+
+        public void onBeginDrag() {
+            mPreviewZoomOnly = true;
+            mStartScale = mActualScale;
+            setZoomOverviewVisible(false);
+        }
+        
+        public void onEndDrag() {
+            mPreviewZoomOnly = false;
+            if (mLogEvent) {
+                EventLog.writeEvent(EVENT_LOG_ZOOM_LEVEL_CHANGE,
+                        (int) mStartScale * 100, (int) mActualScale * 100,
+                        System.currentTimeMillis());
+            }
+            setNewZoomScale(mActualScale, true);
+        }
+
+        public boolean onDragZoom(int deltaZoomLevel, int centerX,
+                int centerY, float startAngle, float curAngle) {
+            if (deltaZoomLevel < 0
+                    && Math.abs(mActualScale - mMinZoomScale) < 0.01f
+                    || deltaZoomLevel > 0
+                    &&  Math.abs(mActualScale - mMaxZoomScale) < 0.01f
+                    || deltaZoomLevel == 0) {
+                return false;
+            }
+            mZoomCenterX = (float) centerX;
+            mZoomCenterY = (float) centerY;
+
+            float scale = 1.0f;
+            // curAngle is [0, 2 * Math.PI)
+            if (curAngle < (float) Math.PI) {
+                if (curAngle >= mCounterClockwiseBound) {
+                    scale = mMinZoomScale;
+                } else {
+                    scale = 1 - (float) Math.round(curAngle
+                            / ZOOM_RING_ANGLE_UNIT) / ZOOM_RING_STEPS
+                            * (1 - mMinZoomScale);
+                }
+            } else {
+                if (curAngle <= mClockwiseBound) {
+                    scale = mMaxZoomScale;
+                } else {
+                    scale = 1 + (float) Math.round(
+                            ((float) 2 * Math.PI - curAngle)
+                            / ZOOM_RING_ANGLE_UNIT) / ZOOM_RING_STEPS
+                            * (mMaxZoomScale - 1);
+                }
+            }
+            zoomWithPreview(scale);
+            return true;
+        }
+
+        public void onSimpleZoom(boolean zoomIn) {
+            if (zoomIn) {
+                zoomIn();
+            } else {
+                zoomOut();
+            }
+        }
+        
+    };
+    
+    /**
+     * Construct a new WebView with a Context object.
+     * @param context A Context object used to access application assets.
+     */
+    public WebView(Context context) {
+        this(context, null);
+    }
+
+    /**
+     * Construct a new WebView with layout parameters.
+     * @param context A Context object used to access application assets.
+     * @param attrs An AttributeSet passed to our parent.
+     */
+    public WebView(Context context, AttributeSet attrs) {
+        this(context, attrs, com.android.internal.R.attr.webViewStyle);
+    }
+
+    /**
+     * Construct a new WebView with layout parameters and a default style.
+     * @param context A Context object used to access application assets.
+     * @param attrs An AttributeSet passed to our parent.
+     * @param defStyle The default style resource ID.
+     */
+    public WebView(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+        init();
+
+        mCallbackProxy = new CallbackProxy(context, this);
+        mWebViewCore = new WebViewCore(context, this, mCallbackProxy);
+        mDatabase = WebViewDatabase.getInstance(context);
+        mFocusData = new WebViewCore.FocusData();
+        mFocusData.mFrame = 0;
+        mFocusData.mNode = 0;
+        mFocusData.mX = 0;
+        mFocusData.mY = 0;
+        mScroller = new Scroller(context);
+        mZoomRingController = new ZoomRingController(context, this);
+        mZoomRingController.setResetThumbAutomatically(false);
+        mZoomRingController.setCallback(mZoomListener);
+        mZoomRingController.setZoomRingTrack(
+                com.android.internal.R.drawable.zoom_ring_track_absolute);
+        mZoomRingController.setPannerAcceleration(160);
+        mZoomRingController.setPannerStartAcceleratingDuration(700);
+        createZoomRingOverviewTab();
+        mZoomButtonsController = new ZoomButtonsController(context, this);
+        mZoomButtonsController.setOverviewVisible(true);
+        mZoomButtonsController.setCallback(new ZoomButtonsController.OnZoomListener() {
+            public void onCenter(int x, int y) {
+                mZoomListener.onCenter(x, y);
+            }
+
+            public void onOverview() {
+                mZoomButtonsController.setVisible(false);
+                zoomScrollOut();
+            }
+
+            public void onVisibilityChanged(boolean visible) {
+                mZoomListener.onVisibilityChanged(visible);
+            }
+
+            public void onZoom(boolean zoomIn) {
+                mZoomListener.onSimpleZoom(zoomIn);
+            }
+        });
+    }
+
+    private void init() {
+        setWillNotDraw(false);
+        setFocusable(true);
+        setFocusableInTouchMode(true);
+        setClickable(true);
+        setLongClickable(true);
+
+        final int slop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
+        mTouchSlopSquare = slop * slop;
+        mMinLockSnapReverseDistance = slop;
+        final int doubleTapslop = ViewConfiguration.get(getContext())
+                .getScaledDoubleTapSlop();
+        mDoubleTapSlopSquare = doubleTapslop * doubleTapslop;
+    }
+
+    private void createZoomRingOverviewTab() {
+        Context context = getContext();
+        
+        mZoomRingOverviewExitAnimation = AnimationUtils.loadAnimation(context,
+                com.android.internal.R.anim.fade_out);
+        
+        mZoomRingOverview = new ImageView(context);
+        mZoomRingOverview.setBackgroundResource(
+                com.android.internal.R.drawable.zoom_ring_overview_tab);
+        mZoomRingOverview.setImageResource(com.android.internal.R.drawable.btn_zoom_page);
+        
+        FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(
+                FrameLayout.LayoutParams.WRAP_CONTENT,
+                FrameLayout.LayoutParams.WRAP_CONTENT,
+                Gravity.CENTER);
+        // TODO: magic constant that's based on the zoom ring radius + some offset
+        lp.topMargin = 200;
+        mZoomRingOverview.setLayoutParams(lp);
+        mZoomRingOverview.setOnClickListener(new View.OnClickListener() {
+            public void onClick(View v) {
+                // Hide the zoom ring
+                mZoomRingController.setVisible(false);
+                if (mLogEvent) {
+                    Checkin.updateStats(mContext.getContentResolver(),
+                            Checkin.Stats.Tag.BROWSER_ZOOM_OVERVIEW, 1, 0.0);
+                }
+                zoomScrollOut();
+            }});
+        
+        // Measure the overview View to figure out its height
+        mZoomRingOverview.forceLayout();
+        mZoomRingOverview.measure(MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED),
+                MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
+        
+        ViewGroup container = mZoomRingController.getContainer();
+        // Find the index of the zoom ring in the container
+        View zoomRing = container.findViewById(mZoomRingController.getZoomRingId());
+        int zoomRingIndex;
+        for (zoomRingIndex = container.getChildCount() - 1; zoomRingIndex >= 0; zoomRingIndex--) {
+            if (container.getChildAt(zoomRingIndex) == zoomRing) break;
+        }
+        // Add the overview tab below the zoom ring (so we don't steal its events)
+        container.addView(mZoomRingOverview, zoomRingIndex);
+        // Since we use margins to adjust the vertical placement of the tab, the widget
+        // ends up getting clipped off. Ensure the container is big enough for
+        // us.
+        int myHeight = mZoomRingOverview.getMeasuredHeight() + lp.topMargin / 2;
+        // Multiplied by 2 b/c the zoom ring needs to be centered on the screen
+        container.setMinimumHeight(myHeight * 2);
+    }
+    
+    private void setZoomOverviewVisible(boolean visible) {
+        int newVisibility = visible ? View.VISIBLE : View.INVISIBLE;
+        if (mZoomRingOverview.getVisibility() == newVisibility) return;
+            
+        if (!visible) {
+            mZoomRingOverview.startAnimation(mZoomRingOverviewExitAnimation);
+        }
+        mZoomRingOverview.setVisibility(newVisibility);
+    }
+    
+    /* package */ boolean onSavePassword(String schemePlusHost, String username,
+            String password, final Message resumeMsg) {
+       boolean rVal = false;
+       if (resumeMsg == null) {
+           // null resumeMsg implies saving password silently
+           mDatabase.setUsernamePassword(schemePlusHost, username, password);
+       } else {
+            final Message remember = mPrivateHandler.obtainMessage(
+                    REMEMBER_PASSWORD);
+            remember.getData().putString("host", schemePlusHost);
+            remember.getData().putString("username", username);
+            remember.getData().putString("password", password);
+            remember.obj = resumeMsg;
+
+            final Message neverRemember = mPrivateHandler.obtainMessage(
+                    NEVER_REMEMBER_PASSWORD);
+            neverRemember.getData().putString("host", schemePlusHost);
+            neverRemember.getData().putString("username", username);
+            neverRemember.getData().putString("password", password);
+            neverRemember.obj = resumeMsg;
+
+            new AlertDialog.Builder(getContext())
+                    .setTitle(com.android.internal.R.string.save_password_label)
+                    .setMessage(com.android.internal.R.string.save_password_message)
+                    .setPositiveButton(com.android.internal.R.string.save_password_notnow,
+                    new DialogInterface.OnClickListener() {
+                        public void onClick(DialogInterface dialog, int which) {
+                            resumeMsg.sendToTarget();
+                        }
+                    })
+                    .setNeutralButton(com.android.internal.R.string.save_password_remember,
+                    new DialogInterface.OnClickListener() {
+                        public void onClick(DialogInterface dialog, int which) {
+                            remember.sendToTarget();
+                        }
+                    })
+                    .setNegativeButton(com.android.internal.R.string.save_password_never,
+                    new DialogInterface.OnClickListener() {
+                        public void onClick(DialogInterface dialog, int which) {
+                            neverRemember.sendToTarget();
+                        }
+                    })
+                    .setOnCancelListener(new OnCancelListener() {
+                        public void onCancel(DialogInterface dialog) {
+                            resumeMsg.sendToTarget();
+                        }
+                    }).show();
+            // Return true so that WebViewCore will pause while the dialog is
+            // up.
+            rVal = true;
+        }
+       return rVal;
+    }
+
+    @Override
+    public void setScrollBarStyle(int style) {
+        if (style == View.SCROLLBARS_INSIDE_INSET
+                || style == View.SCROLLBARS_OUTSIDE_INSET) {
+            mOverlayHorizontalScrollbar = mOverlayVerticalScrollbar = false;
+        } else {
+            mOverlayHorizontalScrollbar = mOverlayVerticalScrollbar = true;
+        }
+        super.setScrollBarStyle(style);
+    }
+
+    /**
+     * Specify whether the horizontal scrollbar has overlay style.
+     * @param overlay TRUE if horizontal scrollbar should have overlay style.
+     */
+    public void setHorizontalScrollbarOverlay(boolean overlay) {
+        mOverlayHorizontalScrollbar = overlay;
+    }
+
+    /**
+     * Specify whether the vertical scrollbar has overlay style.
+     * @param overlay TRUE if vertical scrollbar should have overlay style.
+     */
+    public void setVerticalScrollbarOverlay(boolean overlay) {
+        mOverlayVerticalScrollbar = overlay;
+    }
+
+    /**
+     * Return whether horizontal scrollbar has overlay style
+     * @return TRUE if horizontal scrollbar has overlay style.
+     */
+    public boolean overlayHorizontalScrollbar() {
+        return mOverlayHorizontalScrollbar;
+    }
+
+    /**
+     * Return whether vertical scrollbar has overlay style
+     * @return TRUE if vertical scrollbar has overlay style.
+     */
+    public boolean overlayVerticalScrollbar() {
+        return mOverlayVerticalScrollbar;
+    }
+
+    /*
+     * Return the width of the view where the content of WebView should render
+     * to.
+     */
+    private int getViewWidth() {
+        if (!isVerticalScrollBarEnabled() || mOverlayVerticalScrollbar) {
+            return getWidth();
+        } else {
+            return getWidth() - getVerticalScrollbarWidth();
+        }
+    }
+
+    /*
+     * Return the height of the view where the content of WebView should render
+     * to.
+     */
+    private int getViewHeight() {
+        if (!isHorizontalScrollBarEnabled() || mOverlayHorizontalScrollbar) {
+            return getHeight();
+        } else {
+            return getHeight() - getHorizontalScrollbarHeight();
+        }
+    }
+
+    /**
+     * @return The SSL certificate for the main top-level page or null if
+     * there is no certificate (the site is not secure).
+     */
+    public SslCertificate getCertificate() {
+        return mCertificate;
+    }
+
+    /**
+     * Sets the SSL certificate for the main top-level page.
+     */
+    public void setCertificate(SslCertificate certificate) {
+        // here, the certificate can be null (if the site is not secure)
+        mCertificate = certificate;
+    }
+
+    //-------------------------------------------------------------------------
+    // Methods called by activity
+    //-------------------------------------------------------------------------
+
+    /**
+     * Save the username and password for a particular host in the WebView's
+     * internal database.
+     * @param host The host that required the credentials.
+     * @param username The username for the given host.
+     * @param password The password for the given host.
+     */
+    public void savePassword(String host, String username, String password) {
+        mDatabase.setUsernamePassword(host, username, password);
+    }
+
+    /**
+     * Set the HTTP authentication credentials for a given host and realm.
+     *
+     * @param host The host for the credentials.
+     * @param realm The realm for the credentials.
+     * @param username The username for the password. If it is null, it means
+     *                 password can't be saved.
+     * @param password The password
+     */
+    public void setHttpAuthUsernamePassword(String host, String realm,
+            String username, String password) {
+        mDatabase.setHttpAuthUsernamePassword(host, realm, username, password);
+    }
+
+    /**
+     * Retrieve the HTTP authentication username and password for a given
+     * host & realm pair
+     *
+     * @param host The host for which the credentials apply.
+     * @param realm The realm for which the credentials apply.
+     * @return String[] if found, String[0] is username, which can be null and
+     *         String[1] is password. Return null if it can't find anything.
+     */
+    public String[] getHttpAuthUsernamePassword(String host, String realm) {
+        return mDatabase.getHttpAuthUsernamePassword(host, realm);
+    }
+
+    /**
+     * Destroy the internal state of the WebView. This method should be called
+     * after the WebView has been removed from the view system. No other
+     * methods may be called on a WebView after destroy.
+     */
+    public void destroy() {
+        clearTextEntry();
+        if (mWebViewCore != null) {
+            // Set the handlers to null before destroying WebViewCore so no
+            // more messages will be posted. 
+            mCallbackProxy.setWebViewClient(null);
+            mCallbackProxy.setWebChromeClient(null);
+            // Tell WebViewCore to destroy itself
+            WebViewCore webViewCore = mWebViewCore;
+            mWebViewCore = null; // prevent using partial webViewCore
+            webViewCore.destroy();
+            // Remove any pending messages that might not be serviced yet.
+            mPrivateHandler.removeCallbacksAndMessages(null);
+            mCallbackProxy.removeCallbacksAndMessages(null);
+            // Wake up the WebCore thread just in case it is waiting for a
+            // javascript dialog.
+            synchronized (mCallbackProxy) {
+                mCallbackProxy.notify();
+            }
+        }
+        if (mNativeClass != 0) {
+            nativeDestroy();
+            mNativeClass = 0;
+        }
+    }
+
+    /**
+     * Enables platform notifications of data state and proxy changes.
+     */
+    public static void enablePlatformNotifications() {
+        Network.enablePlatformNotifications();
+    }
+
+    /**
+     * If platform notifications are enabled, this should be called
+     * from onPause() or onStop().
+     */
+    public static void disablePlatformNotifications() {
+        Network.disablePlatformNotifications();
+    }
+    
+    /**
+     * Inform WebView of the network state. This is used to set
+     * the javascript property window.navigator.isOnline and
+     * generates the online/offline event as specified in HTML5, sec. 5.7.7
+     * @param networkUp boolean indicating if network is available
+     * 
+     * @hide pending API Council approval
+     */
+    public void setNetworkAvailable(boolean networkUp) {
+        BrowserFrame.sJavaBridge.setNetworkOnLine(networkUp);
+    }
+
+    /**
+     * Save the state of this WebView used in 
+     * {@link android.app.Activity#onSaveInstanceState}. Please note that this
+     * method no longer stores the display data for this WebView. The previous
+     * behavior could potentially leak files if {@link #restoreState} was never
+     * called. See {@link #savePicture} and {@link #restorePicture} for saving
+     * and restoring the display data.
+     * @param outState The Bundle to store the WebView state.
+     * @return The same copy of the back/forward list used to save the state. If
+     *         saveState fails, the returned list will be null.
+     * @see #savePicture
+     * @see #restorePicture
+     */
+    public WebBackForwardList saveState(Bundle outState) {
+        if (outState == null) {
+            return null;
+        }
+        // We grab a copy of the back/forward list because a client of WebView
+        // may have invalidated the history list by calling clearHistory.
+        WebBackForwardList list = copyBackForwardList();
+        final int currentIndex = list.getCurrentIndex();
+        final int size = list.getSize();
+        // We should fail saving the state if the list is empty or the index is
+        // not in a valid range.
+        if (currentIndex < 0 || currentIndex >= size || size == 0) {
+            return null;
+        }
+        outState.putInt("index", currentIndex);
+        // FIXME: This should just be a byte[][] instead of ArrayList but
+        // Parcel.java does not have the code to handle multi-dimensional
+        // arrays.
+        ArrayList<byte[]> history = new ArrayList<byte[]>(size);
+        for (int i = 0; i < size; i++) {
+            WebHistoryItem item = list.getItemAtIndex(i);
+            byte[] data = item.getFlattenedData();
+            if (data == null) {
+                // It would be very odd to not have any data for a given history
+                // item. And we will fail to rebuild the history list without
+                // flattened data.
+                return null;
+            }
+            history.add(data);
+        }
+        outState.putSerializable("history", history);
+        if (mCertificate != null) {
+            outState.putBundle("certificate",
+                               SslCertificate.saveState(mCertificate));
+        }
+        return list;
+    }
+
+    /**
+     * Save the current display data to the Bundle given. Used in conjunction
+     * with {@link #saveState}.
+     * @param b A Bundle to store the display data.
+     * @param dest The file to store the serialized picture data. Will be
+     *             overwritten with this WebView's picture data.
+     * @return True if the picture was successfully saved.
+     */
+    public boolean savePicture(Bundle b, File dest) {
+        if (dest == null || b == null) {
+            return false;
+        }
+        final Picture p = capturePicture();
+        try {
+            final FileOutputStream out = new FileOutputStream(dest);
+            p.writeToStream(out);
+            out.close();
+        } catch (FileNotFoundException e){
+            e.printStackTrace();
+        } catch (IOException e) {
+            e.printStackTrace();
+        } catch (RuntimeException e) {
+            e.printStackTrace();
+        }
+        if (dest.length() > 0) {
+            b.putInt("scrollX", mScrollX);
+            b.putInt("scrollY", mScrollY);
+            b.putFloat("scale", mActualScale);
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Restore the display data that was save in {@link #savePicture}. Used in
+     * conjunction with {@link #restoreState}.
+     * @param b A Bundle containing the saved display data.
+     * @param src The file where the picture data was stored.
+     * @return True if the picture was successfully restored.
+     */
+    public boolean restorePicture(Bundle b, File src) {
+        if (src == null || b == null) {
+            return false;
+        }
+        if (src.exists()) {
+            Picture p = null;
+            try {
+                final FileInputStream in = new FileInputStream(src);
+                p = Picture.createFromStream(in);
+                in.close();
+            } catch (FileNotFoundException e){
+                e.printStackTrace();
+            } catch (RuntimeException e) {
+                e.printStackTrace();
+            } catch (IOException e) {
+                e.printStackTrace();
+            }
+            if (p != null) {
+                int sx = b.getInt("scrollX", 0);
+                int sy = b.getInt("scrollY", 0);
+                float scale = b.getFloat("scale", 1.0f);
+                mDrawHistory = true;
+                mHistoryPicture = p;
+                mScrollX = sx;
+                mScrollY = sy;
+                mHistoryWidth = Math.round(p.getWidth() * scale);
+                mHistoryHeight = Math.round(p.getHeight() * scale);
+                // as getWidth() / getHeight() of the view are not
+                // available yet, set up mActualScale, so that when
+                // onSizeChanged() is called, the rest will be set
+                // correctly
+                mActualScale = scale;
+                invalidate();
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Restore the state of this WebView from the given map used in
+     * {@link android.app.Activity#onRestoreInstanceState}. This method should 
+     * be called to restore the state of the WebView before using the object. If 
+     * it is called after the WebView has had a chance to build state (load 
+     * pages, create a back/forward list, etc.) there may be undesirable 
+     * side-effects. Please note that this method no longer restores the
+     * display data for this WebView. See {@link #savePicture} and {@link
+     * #restorePicture} for saving and restoring the display data.
+     * @param inState The incoming Bundle of state.
+     * @return The restored back/forward list or null if restoreState failed.
+     * @see #savePicture
+     * @see #restorePicture
+     */
+    public WebBackForwardList restoreState(Bundle inState) {
+        WebBackForwardList returnList = null;
+        if (inState == null) {
+            return returnList;
+        }
+        if (inState.containsKey("index") && inState.containsKey("history")) {
+            mCertificate = SslCertificate.restoreState(
+                inState.getBundle("certificate"));
+
+            final WebBackForwardList list = mCallbackProxy.getBackForwardList();
+            final int index = inState.getInt("index");
+            // We can't use a clone of the list because we need to modify the
+            // shared copy, so synchronize instead to prevent concurrent
+            // modifications.
+            synchronized (list) {
+                final List<byte[]> history =
+                        (List<byte[]>) inState.getSerializable("history");
+                final int size = history.size();
+                // Check the index bounds so we don't crash in native code while
+                // restoring the history index.
+                if (index < 0 || index >= size) {
+                    return null;
+                }
+                for (int i = 0; i < size; i++) {
+                    byte[] data = history.remove(0);
+                    if (data == null) {
+                        // If we somehow have null data, we cannot reconstruct
+                        // the item and thus our history list cannot be rebuilt.
+                        return null;
+                    }
+                    WebHistoryItem item = new WebHistoryItem(data);
+                    list.addHistoryItem(item);
+                }
+                // Grab the most recent copy to return to the caller.
+                returnList = copyBackForwardList();
+                // Update the copy to have the correct index.
+                returnList.setCurrentIndex(index);
+            }
+            // Remove all pending messages because we are restoring previous
+            // state.
+            mWebViewCore.removeMessages();
+            // Send a restore state message.
+            mWebViewCore.sendMessage(EventHub.RESTORE_STATE, index);
+        }
+        return returnList;
+    }
+
+    /**
+     * Load the given url.
+     * @param url The url of the resource to load.
+     */
+    public void loadUrl(String url) {
+        switchOutDrawHistory();
+        mWebViewCore.sendMessage(EventHub.LOAD_URL, url);
+        clearTextEntry();
+    }
+
+    /**
+     * Load the given data into the WebView. This will load the data into
+     * WebView using the data: scheme. Content loaded through this mechanism
+     * does not have the ability to load content from the network.
+     * @param data A String of data in the given encoding.
+     * @param mimeType The MIMEType of the data. i.e. text/html, image/jpeg
+     * @param encoding The encoding of the data. i.e. utf-8, base64
+     */
+    public void loadData(String data, String mimeType, String encoding) {
+        loadUrl("data:" + mimeType + ";" + encoding + "," + data);
+    }
+
+    /**
+     * Load the given data into the WebView, use the provided URL as the base
+     * URL for the content. The base URL is the URL that represents the page
+     * that is loaded through this interface. As such, it is used for the
+     * history entry and to resolve any relative URLs. The failUrl is used if
+     * browser fails to load the data provided. If it is empty or null, and the
+     * load fails, then no history entry is created.
+     * <p>
+     * Note for post 1.0. Due to the change in the WebKit, the access to asset
+     * files through "file:///android_asset/" for the sub resources is more
+     * restricted. If you provide null or empty string as baseUrl, you won't be
+     * able to access asset files. If the baseUrl is anything other than
+     * http(s)/ftp(s)/about/javascript as scheme, you can access asset files for
+     * sub resources.
+     * 
+     * @param baseUrl Url to resolve relative paths with, if null defaults to
+     *            "about:blank"
+     * @param data A String of data in the given encoding.
+     * @param mimeType The MIMEType of the data. i.e. text/html. If null,
+     *            defaults to "text/html"
+     * @param encoding The encoding of the data. i.e. utf-8, us-ascii
+     * @param failUrl URL to use if the content fails to load or null.
+     */
+    public void loadDataWithBaseURL(String baseUrl, String data,
+            String mimeType, String encoding, String failUrl) {
+        
+        if (baseUrl != null && baseUrl.toLowerCase().startsWith("data:")) {
+            loadData(data, mimeType, encoding);
+            return;
+        }
+        switchOutDrawHistory();
+        HashMap arg = new HashMap();
+        arg.put("baseUrl", baseUrl);
+        arg.put("data", data);
+        arg.put("mimeType", mimeType);
+        arg.put("encoding", encoding);
+        arg.put("failUrl", failUrl);
+        mWebViewCore.sendMessage(EventHub.LOAD_DATA, arg);
+        clearTextEntry();
+    }
+
+    /**
+     * Stop the current load.
+     */
+    public void stopLoading() {
+        // TODO: should we clear all the messages in the queue before sending
+        // STOP_LOADING?
+        switchOutDrawHistory();
+        mWebViewCore.sendMessage(EventHub.STOP_LOADING);
+    }
+
+    /**
+     * Reload the current url.
+     */
+    public void reload() {
+        switchOutDrawHistory();
+        mWebViewCore.sendMessage(EventHub.RELOAD);
+    }
+
+    /**
+     * Return true if this WebView has a back history item.
+     * @return True iff this WebView has a back history item.
+     */
+    public boolean canGoBack() {
+        WebBackForwardList l = mCallbackProxy.getBackForwardList();
+        synchronized (l) {
+            if (l.getClearPending()) {
+                return false;
+            } else {
+                return l.getCurrentIndex() > 0;
+            }
+        }
+    }
+
+    /**
+     * Go back in the history of this WebView.
+     */
+    public void goBack() {
+        goBackOrForward(-1);
+    }
+
+    /**
+     * Return true if this WebView has a forward history item.
+     * @return True iff this Webview has a forward history item.
+     */
+    public boolean canGoForward() {
+        WebBackForwardList l = mCallbackProxy.getBackForwardList();
+        synchronized (l) {
+            if (l.getClearPending()) {
+                return false;
+            } else {
+                return l.getCurrentIndex() < l.getSize() - 1;
+            }
+        }
+    }
+
+    /**
+     * Go forward in the history of this WebView.
+     */
+    public void goForward() {
+        goBackOrForward(1);
+    }
+
+    /**
+     * Return true if the page can go back or forward the given
+     * number of steps.
+     * @param steps The negative or positive number of steps to move the
+     *              history.
+     */
+    public boolean canGoBackOrForward(int steps) {
+        WebBackForwardList l = mCallbackProxy.getBackForwardList();
+        synchronized (l) {
+            if (l.getClearPending()) {
+                return false;
+            } else {
+                int newIndex = l.getCurrentIndex() + steps;
+                return newIndex >= 0 && newIndex < l.getSize();
+            }
+        }
+    }
+
+    /**
+     * Go to the history item that is the number of steps away from
+     * the current item. Steps is negative if backward and positive
+     * if forward.
+     * @param steps The number of steps to take back or forward in the back
+     *              forward list.
+     */
+    public void goBackOrForward(int steps) {
+        goBackOrForward(steps, false);
+    }
+
+    private void goBackOrForward(int steps, boolean ignoreSnapshot) {
+        // every time we go back or forward, we want to reset the
+        // WebView certificate:
+        // if the new site is secure, we will reload it and get a
+        // new certificate set;
+        // if the new site is not secure, the certificate must be
+        // null, and that will be the case
+        mCertificate = null;
+        if (steps != 0) {
+            clearTextEntry();
+            mWebViewCore.sendMessage(EventHub.GO_BACK_FORWARD, steps,
+                    ignoreSnapshot ? 1 : 0);
+        }
+    }
+    
+    private boolean extendScroll(int y) {
+        int finalY = mScroller.getFinalY();
+        int newY = pinLocY(finalY + y);
+        if (newY == finalY) return false;
+        mScroller.setFinalY(newY);
+        mScroller.extendDuration(computeDuration(0, y));
+        return true;
+    }
+    
+    /**
+     * Scroll the contents of the view up by half the view size
+     * @param top true to jump to the top of the page
+     * @return true if the page was scrolled
+     */
+    public boolean pageUp(boolean top) {
+        if (mNativeClass == 0) {
+            return false;
+        }
+        nativeClearFocus(-1, -1);
+        if (top) {
+            // go to the top of the document
+            return pinScrollTo(mScrollX, 0, true, 0);
+        }
+        // Page up
+        int h = getHeight();
+        int y;
+        if (h > 2 * PAGE_SCROLL_OVERLAP) {
+            y = -h + PAGE_SCROLL_OVERLAP;
+        } else {
+            y = -h / 2;
+        }
+        mUserScroll = true;
+        return mScroller.isFinished() ? pinScrollBy(0, y, true, 0) 
+                : extendScroll(y);
+    }
+    
+    /**
+     * Scroll the contents of the view down by half the page size
+     * @param bottom true to jump to bottom of page
+     * @return true if the page was scrolled
+     */
+    public boolean pageDown(boolean bottom) {
+        if (mNativeClass == 0) {
+            return false;
+        }
+        nativeClearFocus(-1, -1);
+        if (bottom) {
+            return pinScrollTo(mScrollX, mContentHeight, true, 0);
+        }
+        // Page down.
+        int h = getHeight();
+        int y;
+        if (h > 2 * PAGE_SCROLL_OVERLAP) {
+            y = h - PAGE_SCROLL_OVERLAP;
+        } else {
+            y = h / 2;
+        }
+        mUserScroll = true;
+        return mScroller.isFinished() ? pinScrollBy(0, y, true, 0) 
+                : extendScroll(y);
+    }
+
+    /**
+     * Clear the view so that onDraw() will draw nothing but white background,
+     * and onMeasure() will return 0 if MeasureSpec is not MeasureSpec.EXACTLY
+     */
+    public void clearView() {
+        mContentWidth = 0;
+        mContentHeight = 0;
+        mWebViewCore.sendMessage(EventHub.CLEAR_CONTENT);
+    }
+    
+    /**
+     * Return a new picture that captures the current display of the webview.
+     * This is a copy of the display, and will be unaffected if the webview
+     * later loads a different URL.
+     *
+     * @return a picture containing the current contents of the view. Note this
+     *         picture is of the entire document, and is not restricted to the
+     *         bounds of the view.
+     */
+    public Picture capturePicture() {
+        if (null == mWebViewCore) return null; // check for out of memory tab 
+        return mWebViewCore.copyContentPicture();
+    }
+
+    /**
+     *  Return true if the browser is displaying a TextView for text input.
+     */
+    private boolean inEditingMode() {
+        return mTextEntry != null && mTextEntry.getParent() != null
+                && mTextEntry.hasFocus();
+    }
+
+    private void clearTextEntry() {
+        if (inEditingMode()) {
+            mTextEntry.remove();
+        }
+    }
+
+    /** 
+     * Return the current scale of the WebView
+     * @return The current scale.
+     */
+    public float getScale() {
+        return mActualScale;
+    }
+
+    /**
+     * Set the initial scale for the WebView. 0 means default. If
+     * {@link WebSettings#getUseWideViewPort()} is true, it zooms out all the
+     * way. Otherwise it starts with 100%. If initial scale is greater than 0,
+     * WebView starts will this value as initial scale.
+     *
+     * @param scaleInPercent The initial scale in percent.
+     */
+    public void setInitialScale(int scaleInPercent) {
+        mInitialScale = scaleInPercent;
+    }
+
+    /**
+     * Invoke the graphical zoom picker widget for this WebView. This will
+     * result in the zoom widget appearing on the screen to control the zoom
+     * level of this WebView.
+     */
+    public void invokeZoomPicker() {
+        if (!getSettings().supportZoom()) {
+            Log.w(LOGTAG, "This WebView doesn't support zoom.");
+            return;
+        }
+        clearTextEntry();
+        ExtendedZoomControls zoomControls = (ExtendedZoomControls) 
+                getZoomControls();
+        zoomControls.show(true, canZoomScrollOut());
+        zoomControls.requestFocus();
+        mPrivateHandler.removeCallbacks(mZoomControlRunnable);
+        mPrivateHandler.postDelayed(mZoomControlRunnable,
+                ZOOM_CONTROLS_TIMEOUT);
+    }
+
+    /**
+     * Return a HitTestResult based on the current focus node. If a HTML::a tag
+     * is found and the anchor has a non-javascript url, the HitTestResult type
+     * is set to SRC_ANCHOR_TYPE and the url is set in the "extra" field. If the
+     * anchor does not have a url or if it is a javascript url, the type will
+     * be UNKNOWN_TYPE and the url has to be retrieved through
+     * {@link #requestFocusNodeHref} asynchronously. If a HTML::img tag is
+     * found, the HitTestResult type is set to IMAGE_TYPE and the url is set in
+     * the "extra" field. A type of
+     * SRC_IMAGE_ANCHOR_TYPE indicates an anchor with a url that has an image as
+     * a child node. If a phone number is found, the HitTestResult type is set
+     * to PHONE_TYPE and the phone number is set in the "extra" field of
+     * HitTestResult. If a map address is found, the HitTestResult type is set
+     * to GEO_TYPE and the address is set in the "extra" field of HitTestResult.
+     * If an email address is found, the HitTestResult type is set to EMAIL_TYPE
+     * and the email is set in the "extra" field of HitTestResult. Otherwise,
+     * HitTestResult type is set to UNKNOWN_TYPE.
+     */
+    public HitTestResult getHitTestResult() {
+        if (mNativeClass == 0) {
+            return null;
+        }
+
+        HitTestResult result = new HitTestResult();
+
+        if (nativeUpdateFocusNode()) {
+            FocusNode node = mFocusNode;
+            if (node.mIsTextField || node.mIsTextArea) {
+                result.setType(HitTestResult.EDIT_TEXT_TYPE);
+            } else if (node.mText != null) {
+                String text = node.mText;
+                if (text.startsWith(SCHEME_TEL)) {
+                    result.setType(HitTestResult.PHONE_TYPE);
+                    result.setExtra(text.substring(SCHEME_TEL.length()));
+                } else if (text.startsWith(SCHEME_MAILTO)) {
+                    result.setType(HitTestResult.EMAIL_TYPE);
+                    result.setExtra(text.substring(SCHEME_MAILTO.length()));
+                } else if (text.startsWith(SCHEME_GEO)) {
+                    result.setType(HitTestResult.GEO_TYPE);
+                    result.setExtra(URLDecoder.decode(text
+                            .substring(SCHEME_GEO.length())));
+                } else if (node.mIsAnchor) {
+                    result.setType(HitTestResult.SRC_ANCHOR_TYPE);
+                    result.setExtra(text);
+                }
+            }
+        }
+        int type = result.getType();
+        if (type == HitTestResult.UNKNOWN_TYPE
+                || type == HitTestResult.SRC_ANCHOR_TYPE) {
+            // Now check to see if it is an image.
+            int contentX = viewToContent((int) mLastTouchX + mScrollX);
+            int contentY = viewToContent((int) mLastTouchY + mScrollY);
+            String text = nativeImageURI(contentX, contentY);
+            if (text != null) {
+                result.setType(type == HitTestResult.UNKNOWN_TYPE ? 
+                        HitTestResult.IMAGE_TYPE : 
+                        HitTestResult.SRC_IMAGE_ANCHOR_TYPE);
+                result.setExtra(text);
+            }
+        }
+        return result;
+    }
+
+    /**
+     * Request the href of an anchor element due to getFocusNodePath returning
+     * "href." If hrefMsg is null, this method returns immediately and does not
+     * dispatch hrefMsg to its target.
+     * 
+     * @param hrefMsg This message will be dispatched with the result of the
+     *            request as the data member with "url" as key. The result can
+     *            be null.
+     */
+    public void requestFocusNodeHref(Message hrefMsg) {
+        if (hrefMsg == null || mNativeClass == 0) {
+            return;
+        }
+        if (nativeUpdateFocusNode()) {
+            FocusNode node = mFocusNode;
+            if (node.mIsAnchor) {
+                // NOTE: We may already have the url of the anchor stored in
+                // node.mText but it may be out of date or the caller may want
+                // to know about javascript urls.
+                mWebViewCore.sendMessage(EventHub.REQUEST_FOCUS_HREF,
+                        node.mFramePointer, node.mNodePointer, hrefMsg);
+            }
+        }
+    }
+    
+    /**
+     * Request the url of the image last touched by the user. msg will be sent
+     * to its target with a String representing the url as its object.
+     * 
+     * @param msg This message will be dispatched with the result of the request
+     *            as the data member with "url" as key. The result can be null.
+     */
+    public void requestImageRef(Message msg) {
+        int contentX = viewToContent((int) mLastTouchX + mScrollX);
+        int contentY = viewToContent((int) mLastTouchY + mScrollY);
+        String ref = nativeImageURI(contentX, contentY);
+        Bundle data = msg.getData();
+        data.putString("url", ref);
+        msg.setData(data);
+        msg.sendToTarget();
+    }
+
+    private static int pinLoc(int x, int viewMax, int docMax) {
+//        Log.d(LOGTAG, "-- pinLoc " + x + " " + viewMax + " " + docMax);
+        if (docMax < viewMax) {   // the doc has room on the sides for "blank"
+            x = -(viewMax - docMax) >> 1;
+//            Log.d(LOGTAG, "--- center " + x);
+        } else if (x < 0) {
+            x = 0;
+//            Log.d(LOGTAG, "--- zero");
+        } else if (x + viewMax > docMax) {
+            x = docMax - viewMax;
+//            Log.d(LOGTAG, "--- pin " + x);
+        }
+        return x;
+    }
+
+    // Expects x in view coordinates
+    private int pinLocX(int x) {
+        return pinLoc(x, getViewWidth(), computeHorizontalScrollRange());
+    }
+
+    // Expects y in view coordinates
+    private int pinLocY(int y) {
+        return pinLoc(y, getViewHeight(), computeVerticalScrollRange());
+    }
+
+    /*package*/ int viewToContent(int x) {
+        return Math.round(x * mInvActualScale);
+    }
+
+    private int contentToView(int x) {
+        return Math.round(x * mActualScale);
+    }
+
+    // Called by JNI to invalidate the View, given rectangle coordinates in
+    // content space
+    private void viewInvalidate(int l, int t, int r, int b) {
+        invalidate(contentToView(l), contentToView(t), contentToView(r),
+                contentToView(b));
+    }
+
+    // Called by JNI to invalidate the View after a delay, given rectangle
+    // coordinates in content space
+    private void viewInvalidateDelayed(long delay, int l, int t, int r, int b) {
+        postInvalidateDelayed(delay, contentToView(l), contentToView(t),
+                contentToView(r), contentToView(b));
+    }
+
+    private Rect contentToView(Rect x) {
+        return new Rect(contentToView(x.left), contentToView(x.top)
+                , contentToView(x.right), contentToView(x.bottom));
+    }
+
+    /* call from webcoreview.draw(), so we're still executing in the UI thread
+    */
+    private void recordNewContentSize(int w, int h, boolean updateLayout) {
+
+        // premature data from webkit, ignore
+        if ((w | h) == 0) {
+            return;
+        }
+        
+        // don't abort a scroll animation if we didn't change anything
+        if (mContentWidth != w || mContentHeight != h) {
+            // record new dimensions
+            mContentWidth = w;
+            mContentHeight = h;
+            // If history Picture is drawn, don't update scroll. They will be
+            // updated when we get out of that mode.
+            if (!mDrawHistory) {
+                // repin our scroll, taking into account the new content size
+                int oldX = mScrollX;
+                int oldY = mScrollY;
+                mScrollX = pinLocX(mScrollX);
+                mScrollY = pinLocY(mScrollY);
+                // android.util.Log.d("skia", "recordNewContentSize -
+                // abortAnimation");
+                mScroller.abortAnimation(); // just in case
+                if (oldX != mScrollX || oldY != mScrollY) {
+                    sendOurVisibleRect();
+                }
+            }
+        }
+        contentSizeChanged(updateLayout);
+    }
+
+    private void setNewZoomScale(float scale, boolean force) {
+        if (scale < mMinZoomScale) {
+            scale = mMinZoomScale;
+        } else if (scale > mMaxZoomScale) {
+            scale = mMaxZoomScale;
+        }
+        if (scale != mActualScale || force) {
+            if (mDrawHistory) {
+                // If history Picture is drawn, don't update scroll. They will
+                // be updated when we get out of that mode.
+                if (scale != mActualScale && !mPreviewZoomOnly) {
+                    mCallbackProxy.onScaleChanged(mActualScale, scale);
+                }
+                mActualScale = scale;
+                mInvActualScale = 1 / scale;
+                if (!mPreviewZoomOnly) {
+                    sendViewSizeZoom();
+                }
+            } else {
+                // update our scroll so we don't appear to jump
+                // i.e. keep the center of the doc in the center of the view
+
+                int oldX = mScrollX;
+                int oldY = mScrollY;
+                float ratio = scale * mInvActualScale;   // old inverse
+                float sx = ratio * oldX + (ratio - 1) * mZoomCenterX;
+                float sy = ratio * oldY + (ratio - 1) * mZoomCenterY;
+
+                // now update our new scale and inverse
+                if (scale != mActualScale && !mPreviewZoomOnly) {
+                    mCallbackProxy.onScaleChanged(mActualScale, scale);
+                }
+                mActualScale = scale;
+                mInvActualScale = 1 / scale;
+
+                // as we don't have animation for scaling, don't do animation 
+                // for scrolling, as it causes weird intermediate state
+                //        pinScrollTo(Math.round(sx), Math.round(sy));
+                mScrollX = pinLocX(Math.round(sx));
+                mScrollY = pinLocY(Math.round(sy));
+
+                if (!mPreviewZoomOnly) {
+                    sendViewSizeZoom();
+                    sendOurVisibleRect();
+                }
+            }
+        }
+    }
+
+    // Used to avoid sending many visible rect messages.
+    private Rect mLastVisibleRectSent;
+    private Rect mLastGlobalRect;
+
+    private Rect sendOurVisibleRect() {
+        Rect rect = new Rect();
+        calcOurContentVisibleRect(rect);
+        if (mFindIsUp) {
+            rect.bottom -= viewToContent(FIND_HEIGHT);
+        }
+        // Rect.equals() checks for null input.
+        if (!rect.equals(mLastVisibleRectSent)) {
+            mWebViewCore.sendMessage(EventHub.SET_SCROLL_OFFSET,
+                                     rect.left, rect.top);
+            mLastVisibleRectSent = rect;
+        }
+        Rect globalRect = new Rect();
+        if (getGlobalVisibleRect(globalRect)
+                && !globalRect.equals(mLastGlobalRect)) {
+            // TODO: the global offset is only used by windowRect()
+            // in ChromeClientAndroid ; other clients such as touch
+            // and mouse events could return view + screen relative points.
+            mWebViewCore.sendMessage(EventHub.SET_GLOBAL_BOUNDS, globalRect);
+            mLastGlobalRect = globalRect;
+        }
+        return rect;
+    }
+
+    // Sets r to be the visible rectangle of our webview in view coordinates
+    private void calcOurVisibleRect(Rect r) {
+        Point p = new Point();
+        getGlobalVisibleRect(r, p);
+        r.offset(-p.x, -p.y);
+    }
+
+    // Sets r to be our visible rectangle in content coordinates
+    private void calcOurContentVisibleRect(Rect r) {
+        calcOurVisibleRect(r);
+        r.left = viewToContent(r.left);
+        r.top = viewToContent(r.top);
+        r.right = viewToContent(r.right);
+        r.bottom = viewToContent(r.bottom);
+    }
+
+    /**
+     * Compute unzoomed width and height, and if they differ from the last
+     * values we sent, send them to webkit (to be used has new viewport)
+     *
+     * @return true if new values were sent
+     */
+    private boolean sendViewSizeZoom() {
+        int viewWidth = getViewWidth();
+        int newWidth = Math.round(viewWidth * mInvActualScale);
+        int newHeight = Math.round(getViewHeight() * mInvActualScale);
+        /*
+         * Because the native side may have already done a layout before the
+         * View system was able to measure us, we have to send a height of 0 to
+         * remove excess whitespace when we grow our width. This will trigger a
+         * layout and a change in content size. This content size change will
+         * mean that contentSizeChanged will either call this method directly or
+         * indirectly from onSizeChanged.
+         */
+        if (newWidth > mLastWidthSent && mWrapContent) {
+            newHeight = 0;
+        }
+        // Avoid sending another message if the dimensions have not changed.
+        if (newWidth != mLastWidthSent || newHeight != mLastHeightSent) {
+            mWebViewCore.sendMessage(EventHub.VIEW_SIZE_CHANGED,
+                    newWidth, newHeight, new Integer(viewWidth));
+            mLastWidthSent = newWidth;
+            mLastHeightSent = newHeight;
+            return true;
+        }
+        return false;
+    }
+
+    @Override
+    protected int computeHorizontalScrollRange() {
+        if (mDrawHistory) {
+            return mHistoryWidth;
+        } else {
+            return contentToView(mContentWidth);
+        }
+    }
+
+    // Make sure this stays in sync with the actual height of the FindDialog.
+    private static final int FIND_HEIGHT = 79;
+
+    @Override
+    protected int computeVerticalScrollRange() {
+        if (mDrawHistory) {
+            return mHistoryHeight;
+        } else {
+            int height = contentToView(mContentHeight);
+            if (mFindIsUp) {
+                height += FIND_HEIGHT;
+            }
+            return height;
+        }
+    }
+
+    /**
+     * Get the url for the current page. This is not always the same as the url
+     * passed to WebViewClient.onPageStarted because although the load for
+     * that url has begun, the current page may not have changed.
+     * @return The url for the current page.
+     */
+    public String getUrl() {
+        WebHistoryItem h = mCallbackProxy.getBackForwardList().getCurrentItem();
+        return h != null ? h.getUrl() : null;
+    }
+    
+    /**
+     * Get the original url for the current page. This is not always the same 
+     * as the url passed to WebViewClient.onPageStarted because although the 
+     * load for that url has begun, the current page may not have changed.
+     * Also, there may have been redirects resulting in a different url to that
+     * originally requested.
+     * @return The url that was originally requested for the current page.
+     * 
+     * @hide pending API Council approval
+     */
+    public String getOriginalUrl() {
+        WebHistoryItem h = mCallbackProxy.getBackForwardList().getCurrentItem();
+        return h != null ? h.getOriginalUrl() : null;
+    }
+
+    /**
+     * Get the title for the current page. This is the title of the current page
+     * until WebViewClient.onReceivedTitle is called.
+     * @return The title for the current page.
+     */
+    public String getTitle() {
+        WebHistoryItem h = mCallbackProxy.getBackForwardList().getCurrentItem();
+        return h != null ? h.getTitle() : null;
+    }
+
+    /**
+     * Get the favicon for the current page. This is the favicon of the current
+     * page until WebViewClient.onReceivedIcon is called.
+     * @return The favicon for the current page.
+     */
+    public Bitmap getFavicon() {
+        WebHistoryItem h = mCallbackProxy.getBackForwardList().getCurrentItem();
+        return h != null ? h.getFavicon() : null;
+    }
+
+    /**
+     * Get the progress for the current page.
+     * @return The progress for the current page between 0 and 100.
+     */
+    public int getProgress() {
+        return mCallbackProxy.getProgress();
+    }
+    
+    /**
+     * @return the height of the HTML content.
+     */
+    public int getContentHeight() {
+        return mContentHeight;
+    }
+
+    /**
+     * Pause all layout, parsing, and javascript timers. This can be useful if
+     * the WebView is not visible or the application has been paused.
+     */
+    public void pauseTimers() {
+        mWebViewCore.sendMessage(EventHub.PAUSE_TIMERS);
+    }
+
+    /**
+     * Resume all layout, parsing, and javascript timers. This will resume
+     * dispatching all timers.
+     */
+    public void resumeTimers() {
+        mWebViewCore.sendMessage(EventHub.RESUME_TIMERS);
+    }
+
+    /**
+     * Clear the resource cache. This will cause resources to be re-downloaded
+     * if accessed again.
+     * <p>
+     * Note: this really needs to be a static method as it clears cache for all
+     * WebView. But we need mWebViewCore to send message to WebCore thread, so
+     * we can't make this static.
+     */
+    public void clearCache(boolean includeDiskFiles) {
+        mWebViewCore.sendMessage(EventHub.CLEAR_CACHE,
+                includeDiskFiles ? 1 : 0, 0);
+    }
+
+    /**
+     * Make sure that clearing the form data removes the adapter from the
+     * currently focused textfield if there is one.
+     */
+    public void clearFormData() {
+        if (inEditingMode()) {
+            AutoCompleteAdapter adapter = null;
+            mTextEntry.setAdapterCustom(adapter);
+        }
+    }
+
+    /**
+     * Tell the WebView to clear its internal back/forward list.
+     */
+    public void clearHistory() {
+        mCallbackProxy.getBackForwardList().setClearPending();
+        mWebViewCore.sendMessage(EventHub.CLEAR_HISTORY);
+    }
+
+    /**
+     * Clear the SSL preferences table stored in response to proceeding with SSL
+     * certificate errors.
+     */
+    public void clearSslPreferences() {
+        mWebViewCore.sendMessage(EventHub.CLEAR_SSL_PREF_TABLE);
+    }
+
+    /**
+     * Return the WebBackForwardList for this WebView. This contains the
+     * back/forward list for use in querying each item in the history stack.
+     * This is a copy of the private WebBackForwardList so it contains only a
+     * snapshot of the current state. Multiple calls to this method may return
+     * different objects. The object returned from this method will not be
+     * updated to reflect any new state.
+     */
+    public WebBackForwardList copyBackForwardList() {
+        return mCallbackProxy.getBackForwardList().clone();
+    }
+
+    /*
+     * Highlight and scroll to the next occurance of String in findAll.
+     * Wraps the page infinitely, and scrolls.  Must be called after 
+     * calling findAll.
+     *
+     * @param forward Direction to search.
+     */
+    public void findNext(boolean forward) {
+        nativeFindNext(forward);
+    }
+
+    /*
+     * Find all instances of find on the page and highlight them.
+     * @param find  String to find.
+     * @return int  The number of occurances of the String "find"
+     *              that were found.
+     */
+    public int findAll(String find) {
+        mFindIsUp = true;
+        int result = nativeFindAll(find.toLowerCase(), find.toUpperCase());
+        invalidate();
+        return result;
+    }
+
+    // Used to know whether the find dialog is open.  Affects whether
+    // or not we draw the highlights for matches.
+    private boolean mFindIsUp;
+
+    private native int nativeFindAll(String findLower, String findUpper);
+    private native void nativeFindNext(boolean forward);
+    
+    /**
+     * Return the first substring consisting of the address of a physical 
+     * location. Currently, only addresses in the United States are detected,
+     * and consist of:
+     * - a house number
+     * - a street name
+     * - a street type (Road, Circle, etc), either spelled out or abbreviated
+     * - a city name
+     * - a state or territory, either spelled out or two-letter abbr.
+     * - an optional 5 digit or 9 digit zip code.
+     *
+     * All names must be correctly capitalized, and the zip code, if present,
+     * must be valid for the state. The street type must be a standard USPS
+     * spelling or abbreviation. The state or territory must also be spelled
+     * or abbreviated using USPS standards. The house number may not exceed 
+     * five digits.
+     * @param addr The string to search for addresses.
+     *
+     * @return the address, or if no address is found, return null.
+     */
+    public static String findAddress(String addr) {
+        return WebViewCore.nativeFindAddress(addr);
+    }
+
+    /*
+     * Clear the highlighting surrounding text matches created by findAll.
+     */
+    public void clearMatches() {
+        mFindIsUp = false;
+        nativeSetFindIsDown();
+        // Now that the dialog has been removed, ensure that we scroll to a
+        // location that is not beyond the end of the page.
+        pinScrollTo(mScrollX, mScrollY, false, 0);
+        invalidate();
+    }
+
+    /**
+     * Query the document to see if it contains any image references. The
+     * message object will be dispatched with arg1 being set to 1 if images
+     * were found and 0 if the document does not reference any images.
+     * @param response The message that will be dispatched with the result.
+     */
+    public void documentHasImages(Message response) {
+        if (response == null) {
+            return;
+        }
+        mWebViewCore.sendMessage(EventHub.DOC_HAS_IMAGES, response);
+    }
+
+    @Override
+    public void computeScroll() {
+        if (mScroller.computeScrollOffset()) {
+            int oldX = mScrollX;
+            int oldY = mScrollY;
+            mScrollX = mScroller.getCurrX();
+            mScrollY = mScroller.getCurrY();
+            postInvalidate();  // So we draw again
+            if (oldX != mScrollX || oldY != mScrollY) {
+                // as onScrollChanged() is not called, sendOurVisibleRect()
+                // needs to be call explicitly
+                sendOurVisibleRect();
+            }
+        } else {
+            super.computeScroll();
+        }
+    }
+
+    private static int computeDuration(int dx, int dy) {
+        int distance = Math.max(Math.abs(dx), Math.abs(dy));
+        int duration = distance * 1000 / STD_SPEED;
+        return Math.min(duration, MAX_DURATION);
+    }
+
+    // helper to pin the scrollBy parameters (already in view coordinates)
+    // returns true if the scroll was changed
+    private boolean pinScrollBy(int dx, int dy, boolean animate, int animationDuration) {
+        return pinScrollTo(mScrollX + dx, mScrollY + dy, animate, animationDuration);
+    }
+
+    // helper to pin the scrollTo parameters (already in view coordinates)
+    // returns true if the scroll was changed
+    private boolean pinScrollTo(int x, int y, boolean animate, int animationDuration) {
+        x = pinLocX(x);
+        y = pinLocY(y);
+        int dx = x - mScrollX;
+        int dy = y - mScrollY;
+
+        if ((dx | dy) == 0) {
+            return false;
+        }
+
+        if (true && animate) {
+            //        Log.d(LOGTAG, "startScroll: " + dx + " " + dy);
+
+            mScroller.startScroll(mScrollX, mScrollY, dx, dy,
+                    animationDuration > 0 ? animationDuration : computeDuration(dx, dy));
+            invalidate();
+        } else {
+            mScroller.abortAnimation(); // just in case
+            scrollTo(x, y);
+        }
+        return true;
+    }
+
+    // Scale from content to view coordinates, and pin.
+    // Also called by jni webview.cpp
+    private void setContentScrollBy(int cx, int cy, boolean animate) {
+        if (mDrawHistory) {
+            // disallow WebView to change the scroll position as History Picture
+            // is used in the view system.
+            // TODO: as we switchOutDrawHistory when trackball or navigation
+            // keys are hit, this should be safe. Right?
+            return;
+        }
+        cx = contentToView(cx);
+        cy = contentToView(cy);
+        if (mHeightCanMeasure) {
+            // move our visible rect according to scroll request
+            if (cy != 0) {
+                Rect tempRect = new Rect();
+                calcOurVisibleRect(tempRect);
+                tempRect.offset(cx, cy);
+                requestRectangleOnScreen(tempRect);
+            }
+            // FIXME: We scroll horizontally no matter what because currently
+            // ScrollView and ListView will not scroll horizontally.
+            // FIXME: Why do we only scroll horizontally if there is no
+            // vertical scroll?
+//                Log.d(LOGTAG, "setContentScrollBy cy=" + cy);
+            if (cy == 0 && cx != 0) {
+                pinScrollBy(cx, 0, animate, 0);
+            }
+        } else {
+            pinScrollBy(cx, cy, animate, 0);
+        }
+    }
+
+    // scale from content to view coordinates, and pin
+    // return true if pin caused the final x/y different than the request cx/cy;
+    // return false if the view scroll to the exact position as it is requested.
+    private boolean setContentScrollTo(int cx, int cy) {
+        if (mDrawHistory) {
+            // disallow WebView to change the scroll position as History Picture
+            // is used in the view system.
+            // One known case where this is called is that WebCore tries to
+            // restore the scroll position. As history Picture already uses the
+            // saved scroll position, it is ok to skip this.
+            return false;
+        }
+        int vx = contentToView(cx);
+        int vy = contentToView(cy);
+//        Log.d(LOGTAG, "content scrollTo [" + cx + " " + cy + "] view=[" +
+//                      vx + " " + vy + "]");
+        pinScrollTo(vx, vy, false, 0);
+        if (mScrollX != vx || mScrollY != vy) {
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    // scale from content to view coordinates, and pin
+    private void spawnContentScrollTo(int cx, int cy) {
+        if (mDrawHistory) {
+            // disallow WebView to change the scroll position as History Picture
+            // is used in the view system.
+            return;
+        }
+        int vx = contentToView(cx);
+        int vy = contentToView(cy);
+        pinScrollTo(vx, vy, true, 0);
+    }
+
+    /**
+     * These are from webkit, and are in content coordinate system (unzoomed)
+     */
+    private void contentSizeChanged(boolean updateLayout) {
+        // suppress 0,0 since we usually see real dimensions soon after
+        // this avoids drawing the prev content in a funny place. If we find a
+        // way to consolidate these notifications, this check may become
+        // obsolete
+        if ((mContentWidth | mContentHeight) == 0) {
+            return;
+        }
+
+        if (mHeightCanMeasure) {
+            if (getMeasuredHeight() != contentToView(mContentHeight)
+                    && updateLayout) {
+                requestLayout();
+            }
+        } else if (mWidthCanMeasure) {
+            if (getMeasuredWidth() != contentToView(mContentWidth)
+                    && updateLayout) {
+                requestLayout();
+            }
+        } else {
+            // If we don't request a layout, try to send our view size to the
+            // native side to ensure that WebCore has the correct dimensions.
+            sendViewSizeZoom();
+        }
+    }
+
+    /**
+     * Set the WebViewClient that will receive various notifications and
+     * requests. This will replace the current handler.
+     * @param client An implementation of WebViewClient.
+     */
+    public void setWebViewClient(WebViewClient client) {
+        mCallbackProxy.setWebViewClient(client);
+    }
+
+    /**
+     * Register the interface to be used when content can not be handled by
+     * the rendering engine, and should be downloaded instead. This will replace
+     * the current handler.
+     * @param listener An implementation of DownloadListener.
+     */
+    public void setDownloadListener(DownloadListener listener) {
+        mCallbackProxy.setDownloadListener(listener);
+    }
+
+    /**
+     * Set the chrome handler. This is an implementation of WebChromeClient for
+     * use in handling Javascript dialogs, favicons, titles, and the progress.
+     * This will replace the current handler.
+     * @param client An implementation of WebChromeClient.
+     */
+    public void setWebChromeClient(WebChromeClient client) {
+        mCallbackProxy.setWebChromeClient(client);
+    }
+
+    /**
+     * Set the Picture listener. This is an interface used to receive
+     * notifications of a new Picture.
+     * @param listener An implementation of WebView.PictureListener.
+     */
+    public void setPictureListener(PictureListener listener) {
+        mPictureListener = listener;
+    }
+
+    /**
+     * {@hide}
+     */
+    /* FIXME: Debug only! Remove for SDK! */
+    public void externalRepresentation(Message callback) {
+        mWebViewCore.sendMessage(EventHub.REQUEST_EXT_REPRESENTATION, callback);
+    }
+
+    /**
+     * {@hide}
+     */
+    /* FIXME: Debug only! Remove for SDK! */
+    public void documentAsText(Message callback) {
+        mWebViewCore.sendMessage(EventHub.REQUEST_DOC_AS_TEXT, callback);
+    }
+
+    /**
+     * Use this function to bind an object to Javascript so that the
+     * methods can be accessed from Javascript.
+     * <p><strong>IMPORTANT:</strong>
+     * <ul>
+     * <li> Using addJavascriptInterface() allows JavaScript to control your
+     * application. This can be a very useful feature or a dangerous security
+     * issue. When the HTML in the WebView is untrustworthy (for example, part
+     * or all of the HTML is provided by some person or process), then an
+     * attacker could inject HTML that will execute your code and possibly any
+     * code of the attacker's choosing.<br>
+     * Do not use addJavascriptInterface() unless all of the HTML in this
+     * WebView was written by you.</li>
+     * <li> The Java object that is bound runs in another thread and not in
+     * the thread that it was constructed in.</li>
+     * </ul></p>
+     * @param obj The class instance to bind to Javascript
+     * @param interfaceName The name to used to expose the class in Javascript
+     */
+    public void addJavascriptInterface(Object obj, String interfaceName) {
+        // Use Hashmap rather than Bundle as Bundles can't cope with Objects
+        HashMap arg = new HashMap();
+        arg.put("object", obj);
+        arg.put("interfaceName", interfaceName);
+        mWebViewCore.sendMessage(EventHub.ADD_JS_INTERFACE, arg);
+    }
+
+    /**
+     * Return the WebSettings object used to control the settings for this
+     * WebView.
+     * @return A WebSettings object that can be used to control this WebView's
+     *         settings.
+     */
+    public WebSettings getSettings() {
+        return mWebViewCore.getSettings();
+    }
+
+   /**
+    * Return the list of currently loaded plugins.
+    * @return The list of currently loaded plugins.
+    */
+    public static synchronized PluginList getPluginList() {
+        if (sPluginList == null) {
+            sPluginList = new PluginList();
+        }
+        return sPluginList;
+    }
+
+   /**
+    * Signal the WebCore thread to refresh its list of plugins. Use
+    * this if the directory contents of one of the plugin directories
+    * has been modified and needs its changes reflecting. May cause
+    * plugin load and/or unload.
+    * @param reloadOpenPages Set to true to reload all open pages.
+    */
+    public void refreshPlugins(boolean reloadOpenPages) {
+        if (mWebViewCore != null) {
+            mWebViewCore.sendMessage(EventHub.REFRESH_PLUGINS, reloadOpenPages);
+        }
+    }
+
+    //-------------------------------------------------------------------------
+    // Override View methods
+    //-------------------------------------------------------------------------
+
+    @Override
+    protected void finalize() throws Throwable {
+        destroy();
+    }
+    
+    @Override
+    protected void onDraw(Canvas canvas) {
+        // if mNativeClass is 0, the WebView has been destroyed. Do nothing.
+        if (mNativeClass == 0) {
+            return;
+        }
+        if (mWebViewCore.mEndScaleZoom) {
+            mWebViewCore.mEndScaleZoom = false;
+            if (mTouchMode >= FIRST_SCROLL_ZOOM 
+                    && mTouchMode <= LAST_SCROLL_ZOOM) {
+                setHorizontalScrollBarEnabled(true);
+                setVerticalScrollBarEnabled(true);
+                mTouchMode = TOUCH_DONE_MODE;
+            }
+        }
+        int sc = canvas.save();
+        if (mTouchMode >= FIRST_SCROLL_ZOOM && mTouchMode <= LAST_SCROLL_ZOOM) {
+            scrollZoomDraw(canvas);
+        } else {
+            nativeRecomputeFocus();
+            // Update the buttons in the picture, so when we draw the picture
+            // to the screen, they are in the correct state.
+            // Tell the native side if user is a) touching the screen,
+            // b) pressing the trackball down, or c) pressing the enter key
+            // If the focus is a button, we need to draw it in the pressed
+            // state.
+            // If mNativeClass is 0, we should not reach here, so we do not
+            // need to check it again.
+            nativeRecordButtons(hasFocus() && hasWindowFocus(),
+                    mTouchMode == TOUCH_SHORTPRESS_START_MODE
+                    || mTrackballDown || mGotEnterDown, false);
+            drawCoreAndFocusRing(canvas, mBackgroundColor, mDrawFocusRing);
+        }
+        canvas.restoreToCount(sc);
+        
+        if (AUTO_REDRAW_HACK && mAutoRedraw) {
+            invalidate();
+        }
+    }
+
+    @Override
+    public void setLayoutParams(ViewGroup.LayoutParams params) {
+        if (params.height == LayoutParams.WRAP_CONTENT) {
+            mWrapContent = true;
+        }
+        super.setLayoutParams(params);
+    }
+
+    @Override
+    public boolean performLongClick() {
+        if (inEditingMode()) {
+            return mTextEntry.performLongClick();
+        } else {
+            return super.performLongClick();
+        }
+    }
+
+    private void drawCoreAndFocusRing(Canvas canvas, int color,
+        boolean drawFocus) {
+        if (mDrawHistory) {
+            canvas.scale(mActualScale, mActualScale);
+            canvas.drawPicture(mHistoryPicture);
+            return;
+        }
+
+        boolean animateZoom = mZoomScale != 0;
+        boolean animateScroll = !mScroller.isFinished() 
+                || mVelocityTracker != null;
+        if (animateZoom) {
+            float zoomScale;
+            int interval = (int) (SystemClock.uptimeMillis() - mZoomStart);
+            if (interval < ZOOM_ANIMATION_LENGTH) {
+                float ratio = (float) interval / ZOOM_ANIMATION_LENGTH;
+                zoomScale = 1.0f / (mInvInitialZoomScale 
+                        + (mInvFinalZoomScale - mInvInitialZoomScale) * ratio);
+                invalidate();
+            } else {
+                zoomScale = mZoomScale;
+                // set mZoomScale to be 0 as we have done animation
+                mZoomScale = 0;
+            }
+            float scale = (mActualScale - zoomScale) * mInvActualScale;
+            float tx = scale * (mZoomCenterX + mScrollX);
+            float ty = scale * (mZoomCenterY + mScrollY);
+
+            // this block pins the translate to "legal" bounds. This makes the
+            // animation a bit non-obvious, but it means we won't pop when the
+            // "real" zoom takes effect
+            if (true) {
+               // canvas.translate(mScrollX, mScrollY);
+                tx -= mScrollX;
+                ty -= mScrollY;
+                tx = -pinLoc(-Math.round(tx), getViewWidth(), Math
+                        .round(mContentWidth * zoomScale));
+                ty = -pinLoc(-Math.round(ty), getViewHeight(), Math
+                        .round(mContentHeight * zoomScale));
+                tx += mScrollX;
+                ty += mScrollY;
+            }
+            canvas.translate(tx, ty);
+            canvas.scale(zoomScale, zoomScale);
+        } else {
+            canvas.scale(mActualScale, mActualScale);
+        }
+
+        mWebViewCore.drawContentPicture(canvas, color, animateZoom,
+                animateScroll);
+
+        if (mNativeClass == 0) return;
+        if (mShiftIsPressed) {
+            if (mTouchSelection) {
+                nativeDrawSelectionRegion(canvas);
+            } else {
+                nativeDrawSelection(canvas, mSelectX, mSelectY, 
+                        mExtendSelection);
+            }
+        } else if (drawFocus) {
+            if (mTouchMode == TOUCH_SHORTPRESS_START_MODE) {
+                mTouchMode = TOUCH_SHORTPRESS_MODE;
+                HitTestResult hitTest = getHitTestResult();
+                if (hitTest != null &&
+                        hitTest.mType != HitTestResult.UNKNOWN_TYPE) {
+                    mPrivateHandler.sendMessageDelayed(mPrivateHandler
+                            .obtainMessage(SWITCH_TO_LONGPRESS),
+                            LONG_PRESS_TIMEOUT);
+                }
+            }
+            nativeDrawFocusRing(canvas);
+        }
+        // When the FindDialog is up, only draw the matches if we are not in
+        // the process of scrolling them into view.
+        if (mFindIsUp && !animateScroll) {
+            nativeDrawMatches(canvas);
+        }
+    }
+
+    private native void nativeDrawMatches(Canvas canvas);
+    
+    private float scrollZoomGridScale(float invScale) {
+        float griddedInvScale = (int) (invScale * SCROLL_ZOOM_GRID) 
+            / (float) SCROLL_ZOOM_GRID;
+        return 1.0f / griddedInvScale;
+    }
+    
+    private float scrollZoomX(float scale) {
+        int width = getViewWidth();
+        float maxScrollZoomX = mContentWidth * scale - width;
+        int maxX = mContentWidth - width;
+        return -(maxScrollZoomX > 0 ? mZoomScrollX * maxScrollZoomX / maxX
+                : maxScrollZoomX / 2);
+    }
+
+    private float scrollZoomY(float scale) {
+        int height = getViewHeight();
+        float maxScrollZoomY = mContentHeight * scale - height;
+        int maxY = mContentHeight - height;
+        return -(maxScrollZoomY > 0 ? mZoomScrollY * maxScrollZoomY / maxY
+                : maxScrollZoomY / 2);
+    }
+    
+    private void drawMagnifyFrame(Canvas canvas, Rect frame, Paint paint) {
+        final float ADORNMENT_LEN = 16.0f;
+        float width = frame.width();
+        float height = frame.height();
+        Path path = new Path();
+        path.moveTo(-ADORNMENT_LEN, -ADORNMENT_LEN);
+        path.lineTo(0, 0);
+        path.lineTo(width, 0);
+        path.lineTo(width + ADORNMENT_LEN, -ADORNMENT_LEN);
+        path.moveTo(-ADORNMENT_LEN, height + ADORNMENT_LEN);
+        path.lineTo(0, height);
+        path.lineTo(width, height);
+        path.lineTo(width + ADORNMENT_LEN, height + ADORNMENT_LEN);
+        path.moveTo(0, 0);
+        path.lineTo(0, height);
+        path.moveTo(width, 0);
+        path.lineTo(width, height);
+        path.offset(frame.left, frame.top);
+        canvas.drawPath(path, paint);
+    }
+    
+    // Returns frame surrounding magified portion of screen while 
+    // scroll-zoom is enabled. The frame is also used to center the
+    // zoom-in zoom-out points at the start and end of the animation.
+    private Rect scrollZoomFrame(int width, int height, float halfScale) {
+        Rect scrollFrame = new Rect();
+        scrollFrame.set(mZoomScrollX, mZoomScrollY, 
+                mZoomScrollX + width, mZoomScrollY + height);
+        if (mContentWidth * mZoomScrollLimit < width) {
+            float scale = zoomFrameScaleX(width, halfScale, 1.0f);
+            float offsetX = (width * scale - width) * 0.5f;
+            scrollFrame.left -= offsetX;
+            scrollFrame.right += offsetX;
+        }
+        if (mContentHeight * mZoomScrollLimit < height) {
+            float scale = zoomFrameScaleY(height, halfScale, 1.0f);
+            float offsetY = (height * scale - height) * 0.5f;
+            scrollFrame.top -= offsetY;
+            scrollFrame.bottom += offsetY;
+        }
+        return scrollFrame;
+    }
+    
+    private float zoomFrameScaleX(int width, float halfScale, float noScale) {
+        // mContentWidth > width > mContentWidth * mZoomScrollLimit
+        if (mContentWidth <= width) {
+            return halfScale;
+        }
+        float part = (width - mContentWidth * mZoomScrollLimit)  
+                / (width * (1 - mZoomScrollLimit));
+        return halfScale * part + noScale * (1.0f - part);
+    }
+    
+    private float zoomFrameScaleY(int height, float halfScale, float noScale) {
+        if (mContentHeight <= height) {
+            return halfScale;
+        }
+        float part = (height - mContentHeight * mZoomScrollLimit)  
+                / (height * (1 - mZoomScrollLimit));
+        return halfScale * part + noScale * (1.0f - part);
+    }
+    
+    private float scrollZoomMagScale(float invScale) {
+        return (invScale * 2 + mInvActualScale) / 3;
+    }
+    
+    private void scrollZoomDraw(Canvas canvas) {
+        float invScale = mZoomScrollInvLimit; 
+        int elapsed = 0;
+        if (mTouchMode != SCROLL_ZOOM_OUT) {
+            elapsed = (int) Math.min(System.currentTimeMillis() 
+                - mZoomScrollStart, SCROLL_ZOOM_DURATION);
+            float transitionScale = (mZoomScrollInvLimit - mInvActualScale) 
+                    * elapsed / SCROLL_ZOOM_DURATION;
+            if (mTouchMode == SCROLL_ZOOM_ANIMATION_OUT) {
+                invScale = mInvActualScale + transitionScale;
+            } else { /* if (mTouchMode == SCROLL_ZOOM_ANIMATION_IN) */
+                invScale = mZoomScrollInvLimit - transitionScale;
+            }
+        }
+        float scale = scrollZoomGridScale(invScale);
+        invScale = 1.0f / scale;
+        int width = getViewWidth();
+        int height = getViewHeight();
+        float halfScale = scrollZoomMagScale(invScale);
+        Rect scrollFrame = scrollZoomFrame(width, height, halfScale);
+        if (elapsed == SCROLL_ZOOM_DURATION) {
+            if (mTouchMode == SCROLL_ZOOM_ANIMATION_IN) {
+                setHorizontalScrollBarEnabled(true);
+                setVerticalScrollBarEnabled(true);
+                updateTextEntry();
+                scrollTo((int) (scrollFrame.centerX() * mActualScale) 
+                        - (width >> 1), (int) (scrollFrame.centerY() 
+                        * mActualScale) - (height >> 1));
+                mTouchMode = TOUCH_DONE_MODE;
+            } else {
+                mTouchMode = SCROLL_ZOOM_OUT;
+            }
+        }
+        float newX = scrollZoomX(scale);
+        float newY = scrollZoomY(scale);
+        if (LOGV_ENABLED) {
+            Log.v(LOGTAG, "scrollZoomDraw scale=" + scale + " + (" + newX
+                    + ", " + newY + ") mZoomScroll=(" + mZoomScrollX + ", "
+                    + mZoomScrollY + ")" + " invScale=" + invScale + " scale=" 
+                    + scale);
+        }
+        canvas.translate(newX, newY);
+        canvas.scale(scale, scale);
+        boolean animating = mTouchMode != SCROLL_ZOOM_OUT;
+        if (mDrawHistory) {
+            int sc = canvas.save(Canvas.CLIP_SAVE_FLAG);
+            Rect clip = new Rect(0, 0, mHistoryPicture.getWidth(),
+                    mHistoryPicture.getHeight());
+            canvas.clipRect(clip, Region.Op.DIFFERENCE);
+            canvas.drawColor(mBackgroundColor);
+            canvas.restoreToCount(sc);
+            canvas.drawPicture(mHistoryPicture);
+        } else {
+            mWebViewCore.drawContentPicture(canvas, mBackgroundColor,
+                    animating, true);
+        }
+        if (mTouchMode == TOUCH_DONE_MODE) {
+            return;
+        }
+        Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
+        paint.setStyle(Paint.Style.STROKE);
+        paint.setStrokeWidth(30.0f);
+        paint.setARGB(0x50, 0, 0, 0);
+        int maxX = mContentWidth - width;
+        int maxY = mContentHeight - height;
+        if (true) { // experiment: draw hint to place finger off magnify area
+            drawMagnifyFrame(canvas, scrollFrame, paint);
+        } else {
+            canvas.drawRect(scrollFrame, paint);
+        }
+        int sc = canvas.save();
+        canvas.clipRect(scrollFrame);
+        float halfX = (float) mZoomScrollX / maxX;
+        if (mContentWidth * mZoomScrollLimit < width) {
+            halfX = zoomFrameScaleX(width, 0.5f, halfX);
+        }
+        float halfY = (float) mZoomScrollY / maxY;
+        if (mContentHeight * mZoomScrollLimit < height) {
+            halfY = zoomFrameScaleY(height, 0.5f, halfY);
+        }
+        canvas.scale(halfScale, halfScale, mZoomScrollX + width * halfX
+                , mZoomScrollY + height * halfY);
+        if (LOGV_ENABLED) {
+            Log.v(LOGTAG, "scrollZoomDraw halfScale=" + halfScale + " w/h=(" 
+                    + width + ", " + height + ") half=(" + halfX + ", "
+                    + halfY + ")");
+        }
+        if (mDrawHistory) {
+            canvas.drawPicture(mHistoryPicture);
+        } else {
+            mWebViewCore.drawContentPicture(canvas, mBackgroundColor,
+                    animating, false);
+        }
+        canvas.restoreToCount(sc);
+        if (mTouchMode != SCROLL_ZOOM_OUT) {
+            invalidate();
+        }
+    }
+
+    private void zoomScrollTap(float x, float y) {
+        float scale = scrollZoomGridScale(mZoomScrollInvLimit);
+        float left = scrollZoomX(scale);
+        float top = scrollZoomY(scale);
+        int width = getViewWidth();
+        int height = getViewHeight();
+        x -= width * scale / 2;
+        y -= height * scale / 2;
+        mZoomScrollX = Math.min(mContentWidth - width
+                , Math.max(0, (int) ((x - left) / scale)));
+        mZoomScrollY = Math.min(mContentHeight - height
+                , Math.max(0, (int) ((y - top) / scale)));
+        if (LOGV_ENABLED) {
+            Log.v(LOGTAG, "zoomScrollTap scale=" + scale + " + (" + left
+                    + ", " + top + ") mZoomScroll=(" + mZoomScrollX + ", "
+                    + mZoomScrollY + ")" + " x=" + x + " y=" + y);
+        }
+    }
+
+    private boolean canZoomScrollOut() {
+        if (mContentWidth == 0 || mContentHeight == 0) {
+            return false;
+        }
+        int width = getViewWidth();
+        int height = getViewHeight();
+        float x = (float) width / (float) mContentWidth;
+        float y = (float) height / (float) mContentHeight;
+        mZoomScrollLimit = Math.max(DEFAULT_MIN_ZOOM_SCALE, Math.min(x, y));
+        mZoomScrollInvLimit = 1.0f / mZoomScrollLimit;
+        if (LOGV_ENABLED) {
+            Log.v(LOGTAG, "canZoomScrollOut"
+                    + " mInvActualScale=" + mInvActualScale
+                    + " mZoomScrollLimit=" + mZoomScrollLimit
+                    + " mZoomScrollInvLimit=" + mZoomScrollInvLimit
+                    + " mContentWidth=" + mContentWidth
+                    + " mContentHeight=" + mContentHeight
+                    );
+        }
+        // don't zoom out unless magnify area is at least half as wide
+        // or tall as content
+        float limit = mZoomScrollLimit * 2;
+        return mContentWidth >= width * limit
+                || mContentHeight >= height * limit;
+    }
+        
+    private void startZoomScrollOut() {
+        setHorizontalScrollBarEnabled(false);
+        setVerticalScrollBarEnabled(false);
+        if (mZoomControlRunnable != null) {
+            mPrivateHandler.removeCallbacks(mZoomControlRunnable);
+        }
+        if (mZoomControls != null) {
+            mZoomControls.hide();
+        }
+        int width = getViewWidth();
+        int height = getViewHeight();
+        int halfW = width >> 1;
+        mLastTouchX = halfW;
+        int halfH = height >> 1;
+        mLastTouchY = halfH;
+        mScroller.abortAnimation();
+        mZoomScrollStart = System.currentTimeMillis();
+        Rect zoomFrame = scrollZoomFrame(width, height
+                , scrollZoomMagScale(mZoomScrollInvLimit));
+        mZoomScrollX = Math.max(0, (int) ((mScrollX + halfW) * mInvActualScale) 
+                - (zoomFrame.width() >> 1));
+        mZoomScrollY = Math.max(0, (int) ((mScrollY + halfH) * mInvActualScale) 
+                - (zoomFrame.height() >> 1));
+        scrollTo(0, 0); // triggers inval, starts animation
+        clearTextEntry();
+        if (LOGV_ENABLED) {
+            Log.v(LOGTAG, "startZoomScrollOut mZoomScroll=(" 
+                    + mZoomScrollX + ", " + mZoomScrollY +")");
+        }
+    }
+    
+    private void zoomScrollOut() {
+        if (canZoomScrollOut() == false) {
+            mTouchMode = TOUCH_DONE_MODE;
+            return;
+        }
+        startZoomScrollOut();
+        mTouchMode = SCROLL_ZOOM_ANIMATION_OUT;
+        invalidate();
+    }
+
+    private void moveZoomScrollWindow(float x, float y) {
+        if (Math.abs(x - mLastZoomScrollRawX) < 1.5f 
+                && Math.abs(y - mLastZoomScrollRawY) < 1.5f) {
+            return;
+        }
+        mLastZoomScrollRawX = x;
+        mLastZoomScrollRawY = y;
+        int oldX = mZoomScrollX;
+        int oldY = mZoomScrollY;
+        int width = getViewWidth();
+        int height = getViewHeight();
+        int maxZoomX = mContentWidth - width;
+        if (maxZoomX > 0) {
+            int maxScreenX = width - (int) Math.ceil(width 
+                    * mZoomScrollLimit) - SCROLL_ZOOM_FINGER_BUFFER;
+            if (LOGV_ENABLED) {
+                Log.v(LOGTAG, "moveZoomScrollWindow-X" 
+                        + " maxScreenX=" + maxScreenX + " width=" + width
+                        + " mZoomScrollLimit=" + mZoomScrollLimit + " x=" + x); 
+            }
+            x += maxScreenX * mLastScrollX / maxZoomX - mLastTouchX;
+            x *= Math.max(maxZoomX / maxScreenX, mZoomScrollInvLimit);
+            mZoomScrollX = Math.max(0, Math.min(maxZoomX, (int) x));
+        }
+        int maxZoomY = mContentHeight - height;
+        if (maxZoomY > 0) {
+            int maxScreenY = height - (int) Math.ceil(height 
+                    * mZoomScrollLimit) - SCROLL_ZOOM_FINGER_BUFFER;
+            if (LOGV_ENABLED) {
+                Log.v(LOGTAG, "moveZoomScrollWindow-Y" 
+                        + " maxScreenY=" + maxScreenY + " height=" + height
+                        + " mZoomScrollLimit=" + mZoomScrollLimit + " y=" + y); 
+            }
+            y += maxScreenY * mLastScrollY / maxZoomY - mLastTouchY;
+            y *= Math.max(maxZoomY / maxScreenY, mZoomScrollInvLimit);
+            mZoomScrollY = Math.max(0, Math.min(maxZoomY, (int) y));
+        }
+        if (oldX != mZoomScrollX || oldY != mZoomScrollY) {
+            invalidate();
+        }
+        if (LOGV_ENABLED) {
+            Log.v(LOGTAG, "moveZoomScrollWindow" 
+                    + " scrollTo=(" + mZoomScrollX + ", " + mZoomScrollY + ")" 
+                    + " mLastTouch=(" + mLastTouchX + ", " + mLastTouchY + ")" 
+                    + " maxZoom=(" + maxZoomX + ", " + maxZoomY + ")" 
+                    + " last=("+mLastScrollX+", "+mLastScrollY+")" 
+                    + " x=" + x + " y=" + y);
+        }
+    }
+
+    private void setZoomScrollIn() {
+        mZoomScrollStart = System.currentTimeMillis();
+    }
+
+    private float mZoomScrollLimit;
+    private float mZoomScrollInvLimit;
+    private int mLastScrollX;
+    private int mLastScrollY;
+    private long mZoomScrollStart;
+    private int mZoomScrollX;
+    private int mZoomScrollY;
+    private float mLastZoomScrollRawX = -1000.0f;
+    private float mLastZoomScrollRawY = -1000.0f;
+    // The zoomed scale varies from 1.0 to DEFAULT_MIN_ZOOM_SCALE == 0.25.
+    // The zoom animation duration SCROLL_ZOOM_DURATION == 0.5.
+    // Two pressures compete for gridding; a high frame rate (e.g. 20 fps)
+    // and minimizing font cache allocations (fewer frames is better).
+    // A SCROLL_ZOOM_GRID of 6 permits about 20 zoom levels over 0.5 seconds:
+    // the inverse of: 1.0, 1.16, 1.33, 1.5, 1.67, 1.84, 2.0, etc. to 4.0
+    private static final int SCROLL_ZOOM_GRID = 6;
+    private static final int SCROLL_ZOOM_DURATION = 500;
+    // Make it easier to get to the bottom of a document by reserving a 32
+    // pixel buffer, for when the starting drag is a bit below the bottom of
+    // the magnify frame.
+    private static final int SCROLL_ZOOM_FINGER_BUFFER = 32;
+
+    // draw history
+    private boolean mDrawHistory = false;
+    private Picture mHistoryPicture = null;
+    private int mHistoryWidth = 0;
+    private int mHistoryHeight = 0;
+
+    // Only check the flag, can be called from WebCore thread
+    boolean drawHistory() {
+        return mDrawHistory;
+    }
+
+    // Should only be called in UI thread
+    void switchOutDrawHistory() {
+        if (null == mWebViewCore) return; // CallbackProxy may trigger this
+        if (mDrawHistory) {
+            mDrawHistory = false;
+            invalidate();
+            int oldScrollX = mScrollX;
+            int oldScrollY = mScrollY;
+            mScrollX = pinLocX(mScrollX);
+            mScrollY = pinLocY(mScrollY);
+            if (oldScrollX != mScrollX || oldScrollY != mScrollY) {
+                mUserScroll = false;
+                mWebViewCore.sendMessage(EventHub.SYNC_SCROLL, oldScrollX,
+                        oldScrollY);
+            }
+            sendOurVisibleRect();
+        }
+    }
+
+    /**
+     *  Class representing the node which is focused.
+     */
+    private class FocusNode {
+        public FocusNode() {
+            mBounds = new Rect();
+        }
+        // Only to be called by JNI
+        private void setAll(boolean isTextField, boolean isTextArea, boolean 
+                isPassword, boolean isAnchor, boolean isRtlText, int maxLength, 
+                int textSize, int boundsX, int boundsY, int boundsRight, int 
+                boundsBottom, int nodePointer, int framePointer, String text, 
+                String name, int rootTextGeneration) {
+            mIsTextField        = isTextField;
+            mIsTextArea         = isTextArea;
+            mIsPassword         = isPassword;
+            mIsAnchor           = isAnchor;
+            mIsRtlText          = isRtlText;
+
+            mMaxLength          = maxLength;
+            mTextSize           = textSize;
+            
+            mBounds.set(boundsX, boundsY, boundsRight, boundsBottom);
+            
+            
+            mNodePointer        = nodePointer;
+            mFramePointer       = framePointer;
+            mText               = text;
+            mName               = name;
+            mRootTextGeneration = rootTextGeneration;
+        }
+        public boolean  mIsTextField;
+        public boolean  mIsTextArea;
+        public boolean  mIsPassword;
+        public boolean  mIsAnchor;
+        public boolean  mIsRtlText;
+
+        public int      mSelectionStart;
+        public int      mSelectionEnd;
+        public int      mMaxLength;
+        public int      mTextSize;
+        
+        public Rect     mBounds;
+        
+        public int      mNodePointer;
+        public int      mFramePointer;
+        public String   mText;
+        public String   mName;
+        public int      mRootTextGeneration;
+    }
+    
+    // Warning: ONLY use mFocusNode AFTER calling nativeUpdateFocusNode(),
+    // and ONLY if it returns true;
+    private FocusNode mFocusNode = new FocusNode();
+    
+    /**
+     *  Delete text from start to end in the focused textfield. If there is no
+     *  focus, or if start == end, silently fail.  If start and end are out of 
+     *  order, swap them.
+     *  @param  start   Beginning of selection to delete.
+     *  @param  end     End of selection to delete.
+     */
+    /* package */ void deleteSelection(int start, int end) {
+        mWebViewCore.sendMessage(EventHub.DELETE_SELECTION, start, end,
+                new WebViewCore.FocusData(mFocusData));
+    }
+
+    /**
+     *  Set the selection to (start, end) in the focused textfield. If start and
+     *  end are out of order, swap them.
+     *  @param  start   Beginning of selection.
+     *  @param  end     End of selection.
+     */
+    /* package */ void setSelection(int start, int end) {
+        mWebViewCore.sendMessage(EventHub.SET_SELECTION, start, end,
+                new WebViewCore.FocusData(mFocusData));
+    }
+
+    // Called by JNI when a touch event puts a textfield into focus.
+    private void displaySoftKeyboard() {
+        InputMethodManager imm = (InputMethodManager)
+                getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
+        imm.showSoftInput(mTextEntry, 0);
+        mTextEntry.enableScrollOnScreen(true);
+        // Now we need to fake a touch event to place the cursor where the
+        // user touched.
+        AbsoluteLayout.LayoutParams lp = (AbsoluteLayout.LayoutParams)
+                mTextEntry.getLayoutParams();
+        if (lp != null) {
+            // Take the last touch and adjust for the location of the
+            // TextDialog.
+            float x = mLastTouchX + (float) (mScrollX - lp.x);
+            float y = mLastTouchY + (float) (mScrollY - lp.y);
+            mTextEntry.fakeTouchEvent(x, y);
+        }
+    }
+
+    private void updateTextEntry() {
+        if (mTextEntry == null) {
+            mTextEntry = new TextDialog(mContext, WebView.this);
+            // Initialize our generation number.
+            mTextGeneration = 0;
+        }
+        // If we do not have focus, do nothing until we gain focus.
+        if (!hasFocus() && !mTextEntry.hasFocus()
+                || (mTouchMode >= FIRST_SCROLL_ZOOM 
+                && mTouchMode <= LAST_SCROLL_ZOOM)) {
+            mNeedsUpdateTextEntry = true;
+            return;
+        }
+        boolean alreadyThere = inEditingMode();
+        if (0 == mNativeClass || !nativeUpdateFocusNode()) {
+            if (alreadyThere) {
+                mTextEntry.remove();
+            }
+            return;
+        }
+        FocusNode node = mFocusNode;
+        if (!node.mIsTextField && !node.mIsTextArea) {
+            if (alreadyThere) {
+                mTextEntry.remove();
+            }
+            return;
+        }
+        mTextEntry.setTextSize(contentToView(node.mTextSize));
+        Rect visibleRect = sendOurVisibleRect();
+        // Note that sendOurVisibleRect calls viewToContent, so the coordinates
+        // should be in content coordinates.
+        if (!Rect.intersects(node.mBounds, visibleRect)) {
+            // Node is not on screen, so do not bother.
+            return;
+        }
+        int x = node.mBounds.left;
+        int y = node.mBounds.top;
+        int width = node.mBounds.width();
+        int height = node.mBounds.height();
+        if (alreadyThere && mTextEntry.isSameTextField(node.mNodePointer)) {
+            // It is possible that we have the same textfield, but it has moved,
+            // i.e. In the case of opening/closing the screen.
+            // In that case, we need to set the dimensions, but not the other
+            // aspects.
+            // We also need to restore the selection, which gets wrecked by
+            // calling setTextEntryRect.
+            Spannable spannable = (Spannable) mTextEntry.getText();
+            int start = Selection.getSelectionStart(spannable);
+            int end = Selection.getSelectionEnd(spannable);
+            setTextEntryRect(x, y, width, height);
+            // If the text has been changed by webkit, update it.  However, if
+            // there has been more UI text input, ignore it.  We will receive
+            // another update when that text is recognized.
+            if (node.mText != null && !node.mText.equals(spannable.toString())
+                    && node.mRootTextGeneration == mTextGeneration) {
+                mTextEntry.setTextAndKeepSelection(node.mText);
+            } else {
+                Selection.setSelection(spannable, start, end);
+            }
+        } else {
+            String text = node.mText;
+            setTextEntryRect(x, y, width, height);
+            mTextEntry.setGravity(node.mIsRtlText ? Gravity.RIGHT : 
+                    Gravity.NO_GRAVITY);
+            // this needs to be called before update adapter thread starts to
+            // ensure the mTextEntry has the same node pointer
+            mTextEntry.setNodePointer(node.mNodePointer);
+            int maxLength = -1;
+            if (node.mIsTextField) {
+                maxLength = node.mMaxLength;
+                if (mWebViewCore.getSettings().getSaveFormData()
+                        && node.mName != null) {
+                    HashMap data = new HashMap();
+                    data.put("text", node.mText);
+                    Message update = mPrivateHandler.obtainMessage(
+                            UPDATE_TEXT_ENTRY_ADAPTER, node.mNodePointer, 0,
+                            data);
+                    UpdateTextEntryAdapter updater = new UpdateTextEntryAdapter(
+                            node.mName, getUrl(), update);
+                    Thread t = new Thread(updater);
+                    t.start();
+                }
+            }
+            mTextEntry.setMaxLength(maxLength);
+            AutoCompleteAdapter adapter = null;
+            mTextEntry.setAdapterCustom(adapter);
+            mTextEntry.setSingleLine(node.mIsTextField);
+            mTextEntry.setInPassword(node.mIsPassword);
+            if (null == text) {
+                mTextEntry.setText("", 0, 0);
+            } else {
+                // Change to true to enable the old style behavior, where
+                // entering a textfield/textarea always set the selection to the
+                // whole field.  This was desirable for the case where the user
+                // intends to scroll past the field using the trackball.
+                // However, it causes a problem when replying to emails - the
+                // user expects the cursor to be at the beginning of the
+                // textarea.  Testing out a new behavior, where textfields set
+                // selection at the end, and textareas at the beginning.
+                if (false) {
+                    mTextEntry.setText(text, 0, text.length());
+                } else if (node.mIsTextField) {
+                    int length = text.length();
+                    mTextEntry.setText(text, length, length);
+                } else {
+                    mTextEntry.setText(text, 0, 0);
+                }
+            }
+            mTextEntry.requestFocus();
+        }
+    }
+
+    private class UpdateTextEntryAdapter implements Runnable {
+        private String mName;
+        private String mUrl;
+        private Message mUpdateMessage;
+
+        public UpdateTextEntryAdapter(String name, String url, Message msg) {
+            mName = name;
+            mUrl = url;
+            mUpdateMessage = msg;
+        }
+
+        public void run() {
+            ArrayList<String> pastEntries = mDatabase.getFormData(mUrl, mName);
+            if (pastEntries.size() > 0) {
+                AutoCompleteAdapter adapter = new
+                        AutoCompleteAdapter(mContext, pastEntries);
+                ((HashMap) mUpdateMessage.obj).put("adapter", adapter);
+                mUpdateMessage.sendToTarget();
+            }
+        }
+    }
+
+    private void setTextEntryRect(int x, int y, int width, int height) {
+        x = contentToView(x);
+        y = contentToView(y);
+        width = contentToView(width);
+        height = contentToView(height);
+        mTextEntry.setRect(x, y, width, height);
+    }
+
+    // This is used to determine long press with the enter key, or
+    // a center key.  Does not affect long press with the trackball/touch.
+    private boolean mGotEnterDown = false;
+
+    @Override
+    public boolean onKeyDown(int keyCode, KeyEvent event) {
+        if (LOGV_ENABLED) {
+            Log.v(LOGTAG, "keyDown at " + System.currentTimeMillis()
+                    + ", " + event);
+        }
+
+        if (mNativeClass == 0) {
+            return false;
+        }
+
+        // do this hack up front, so it always works, regardless of touch-mode
+        if (AUTO_REDRAW_HACK && (keyCode == KeyEvent.KEYCODE_CALL)) {
+            mAutoRedraw = !mAutoRedraw;
+            if (mAutoRedraw) {
+                invalidate();
+            }
+            return true;
+        }
+
+        // Bubble up the key event if
+        // 1. it is a system key; or
+        // 2. the host application wants to handle it; or
+        // 3. webview is in scroll-zoom state;
+        if (event.isSystem()
+                || mCallbackProxy.uiOverrideKeyEvent(event)
+                || (mTouchMode >= FIRST_SCROLL_ZOOM && mTouchMode <= LAST_SCROLL_ZOOM)) {
+            return false;
+        }
+
+        if (mShiftIsPressed == false && nativeFocusNodeWantsKeyEvents() == false
+                && (keyCode == KeyEvent.KEYCODE_SHIFT_LEFT 
+                || keyCode == KeyEvent.KEYCODE_SHIFT_RIGHT)) {
+            mExtendSelection = false;
+            mShiftIsPressed = true;
+            if (nativeUpdateFocusNode()) {
+                FocusNode node = mFocusNode;
+                mSelectX = contentToView(node.mBounds.left);
+                mSelectY = contentToView(node.mBounds.top);
+            } else {
+                mSelectX = mScrollX + (int) mLastTouchX;
+                mSelectY = mScrollY + (int) mLastTouchY;
+            }
+            int contentX = viewToContent((int) mLastTouchX + mScrollX);
+            int contentY = viewToContent((int) mLastTouchY + mScrollY);
+            nativeClearFocus(contentX, contentY);
+       }
+
+        if (keyCode >= KeyEvent.KEYCODE_DPAD_UP
+                && keyCode <= KeyEvent.KEYCODE_DPAD_RIGHT) {
+            // always handle the navigation keys in the UI thread
+            switchOutDrawHistory();
+            if (navHandledKey(keyCode, 1, false, event.getEventTime())) {
+                playSoundEffect(keyCodeToSoundsEffect(keyCode));
+                return true;
+            }
+            // Bubble up the key event as WebView doesn't handle it
+            return false;
+        }
+
+        if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER
+                || keyCode == KeyEvent.KEYCODE_ENTER) {
+            switchOutDrawHistory();
+            if (event.getRepeatCount() == 0) {
+                mGotEnterDown = true;
+                mPrivateHandler.sendMessageDelayed(mPrivateHandler
+                        .obtainMessage(LONG_PRESS_ENTER), LONG_PRESS_TIMEOUT);
+                // Already checked mNativeClass, so we do not need to check it
+                // again.
+                nativeRecordButtons(hasFocus() && hasWindowFocus(), true, true);
+                return true;
+            }
+            // Bubble up the key event as WebView doesn't handle it
+            return false;
+        }
+
+        if (getSettings().getNavDump()) {
+            switch (keyCode) {
+                case KeyEvent.KEYCODE_4:
+                    // "/data/data/com.android.browser/displayTree.txt"
+                    nativeDumpDisplayTree(getUrl());
+                    break;
+                case KeyEvent.KEYCODE_5:
+                case KeyEvent.KEYCODE_6:
+                    // 5: dump the dom tree to the file
+                    // "/data/data/com.android.browser/domTree.txt"
+                    // 6: dump the dom tree to the adb log
+                    mWebViewCore.sendMessage(EventHub.DUMP_DOMTREE,
+                            (keyCode == KeyEvent.KEYCODE_5) ? 1 : 0, 0);
+                    break;
+                case KeyEvent.KEYCODE_7:
+                case KeyEvent.KEYCODE_8:
+                    // 7: dump the render tree to the file
+                    // "/data/data/com.android.browser/renderTree.txt"
+                    // 8: dump the render tree to the adb log
+                    mWebViewCore.sendMessage(EventHub.DUMP_RENDERTREE,
+                            (keyCode == KeyEvent.KEYCODE_7) ? 1 : 0, 0);
+                    break;
+                case KeyEvent.KEYCODE_9:
+                    nativeInstrumentReport();
+                    return true;
+            }
+        }
+
+        // TODO: should we pass all the keys to DOM or check the meta tag
+        if (nativeFocusNodeWantsKeyEvents() || true) {
+            // pass the key to DOM
+            mWebViewCore.sendMessage(EventHub.KEY_DOWN, event);
+            // return true as DOM handles the key
+            return true;
+        }
+
+        // Bubble up the key event as WebView doesn't handle it
+        return false;
+    }
+
+    @Override
+    public boolean onKeyUp(int keyCode, KeyEvent event) {
+        if (LOGV_ENABLED) {
+            Log.v(LOGTAG, "keyUp at " + System.currentTimeMillis()
+                    + ", " + event);
+        }
+
+        if (mNativeClass == 0) {
+            return false;
+        }
+
+        // special CALL handling when focus node's href is "tel:XXX"
+        if (keyCode == KeyEvent.KEYCODE_CALL && nativeUpdateFocusNode()) {
+            FocusNode node = mFocusNode;
+            String text = node.mText;
+            if (!node.mIsTextField && !node.mIsTextArea && text != null
+                    && text.startsWith(SCHEME_TEL)) {
+                Intent intent = new Intent(Intent.ACTION_DIAL, Uri.parse(text));
+                getContext().startActivity(intent);
+                return true;
+            }
+        }
+
+        // Bubble up the key event if
+        // 1. it is a system key; or
+        // 2. the host application wants to handle it;
+        if (event.isSystem() || mCallbackProxy.uiOverrideKeyEvent(event)) {
+            return false;
+        }
+
+        // special handling in scroll_zoom state
+        if (mTouchMode >= FIRST_SCROLL_ZOOM && mTouchMode <= LAST_SCROLL_ZOOM) {
+            if (KeyEvent.KEYCODE_DPAD_CENTER == keyCode
+                    && mTouchMode != SCROLL_ZOOM_ANIMATION_IN) {
+                setZoomScrollIn();
+                mTouchMode = SCROLL_ZOOM_ANIMATION_IN;
+                invalidate();
+                return true;
+            }
+            return false;
+        }
+
+        if (keyCode == KeyEvent.KEYCODE_SHIFT_LEFT 
+                || keyCode == KeyEvent.KEYCODE_SHIFT_RIGHT) {
+            if (commitCopy()) {
+                return true;
+            }
+        }
+
+        if (keyCode >= KeyEvent.KEYCODE_DPAD_UP
+                && keyCode <= KeyEvent.KEYCODE_DPAD_RIGHT) {
+            // always handle the navigation keys in the UI thread
+            // Bubble up the key event as WebView doesn't handle it
+            return false;
+        }
+
+        if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER
+                || keyCode == KeyEvent.KEYCODE_ENTER) {
+            // remove the long press message first
+            mPrivateHandler.removeMessages(LONG_PRESS_ENTER);
+            mGotEnterDown = false;
+
+            if (KeyEvent.KEYCODE_DPAD_CENTER == keyCode) {
+                if (mShiftIsPressed) {
+                    return false;
+                }
+                if (getSettings().supportZoom()) {
+                    if (mTouchMode == TOUCH_DOUBLECLICK_MODE) {
+                        zoomScrollOut();
+                    } else {
+                        if (LOGV_ENABLED) {
+                            Log.v(LOGTAG, "TOUCH_DOUBLECLICK_MODE");
+                        }
+                        mPrivateHandler.sendMessageDelayed(mPrivateHandler
+                                .obtainMessage(SWITCH_TO_ENTER), TAP_TIMEOUT);
+                        mTouchMode = TOUCH_DOUBLECLICK_MODE;
+                    }
+                    return true;
+                }
+            }
+
+            Rect visibleRect = sendOurVisibleRect();
+            // Note that sendOurVisibleRect calls viewToContent, so the
+            // coordinates should be in content coordinates.
+            if (nativeUpdateFocusNode()) {
+                if (Rect.intersects(mFocusNode.mBounds, visibleRect)) {
+                    nativeSetFollowedLink(true);
+                    mWebViewCore.sendMessage(EventHub.SET_FINAL_FOCUS,
+                            EventHub.BLOCK_FOCUS_CHANGE_UNTIL_KEY_UP, 0,
+                            new WebViewCore.FocusData(mFocusData));
+                    playSoundEffect(SoundEffectConstants.CLICK);
+                    if (!mCallbackProxy.uiOverrideUrlLoading(mFocusNode.mText)) {
+                        // use CLICK instead of KEY_DOWN/KEY_UP so that we can
+                        // trigger mouse click events
+                        mWebViewCore.sendMessage(EventHub.CLICK);
+                    }
+                }
+                return true;
+            }
+            // Bubble up the key event as WebView doesn't handle it
+            return false;
+        }
+
+        // TODO: should we pass all the keys to DOM or check the meta tag
+        if (nativeFocusNodeWantsKeyEvents() || true) {
+            // pass the key to DOM
+            mWebViewCore.sendMessage(EventHub.KEY_UP, event);
+            // return true as DOM handles the key
+            return true;
+        }
+
+        // Bubble up the key event as WebView doesn't handle it
+        return false;
+    }
+    
+    /**
+     * @hide
+     */
+    public void emulateShiftHeld() {
+        mExtendSelection = false;
+        mShiftIsPressed = true;
+        int contentX = viewToContent((int) mLastTouchX + mScrollX);
+        int contentY = viewToContent((int) mLastTouchY + mScrollY);
+        nativeClearFocus(contentX, contentY);
+    }
+
+    private boolean commitCopy() {
+        boolean copiedSomething = false;
+        if (mExtendSelection) {
+            // copy region so core operates on copy without touching orig.
+            Region selection = new Region(nativeGetSelection());
+            if (selection.isEmpty() == false) {
+                Toast.makeText(mContext
+                        , com.android.internal.R.string.text_copied
+                        , Toast.LENGTH_SHORT).show();
+                mWebViewCore.sendMessage(EventHub.GET_SELECTION, selection);
+                copiedSomething = true;
+            }
+            mExtendSelection = false;
+        }
+        mShiftIsPressed = false;
+        if (mTouchMode == TOUCH_SELECT_MODE) {
+            mTouchMode = TOUCH_INIT_MODE;
+        }
+        return copiedSomething;
+    }
+
+    // Set this as a hierarchy change listener so we can know when this view
+    // is removed and still have access to our parent.
+    @Override
+    protected void onAttachedToWindow() {
+        super.onAttachedToWindow();
+        ViewParent parent = getParent();
+        if (parent instanceof ViewGroup) {
+            ViewGroup p = (ViewGroup) parent;
+            p.setOnHierarchyChangeListener(this);
+        }
+    }
+
+    @Override
+    protected void onDetachedFromWindow() {
+        super.onDetachedFromWindow();
+        ViewParent parent = getParent();
+        if (parent instanceof ViewGroup) {
+            ViewGroup p = (ViewGroup) parent;
+            p.setOnHierarchyChangeListener(null);
+        }
+
+        // Clean up the zoom ring
+        mZoomRingController.setVisible(false);
+        mZoomButtonsController.setVisible(false);
+    }
+    
+    // Implementation for OnHierarchyChangeListener
+    public void onChildViewAdded(View parent, View child) {}
+    
+    public void onChildViewRemoved(View p, View child) {
+        if (child == this) {
+            if (inEditingMode()) {
+                clearTextEntry();
+                mNeedsUpdateTextEntry = true;
+            }
+        }
+    }
+
+    /**
+     * @deprecated WebView should not have implemented
+     * ViewTreeObserver.OnGlobalFocusChangeListener.  This method
+     * does nothing now.
+     */
+    @Deprecated
+    public void onGlobalFocusChanged(View oldFocus, View newFocus) {
+    }
+
+    // To avoid drawing the focus ring, and remove the TextView when our window
+    // loses focus.
+    @Override
+    public void onWindowFocusChanged(boolean hasWindowFocus) {
+        if (hasWindowFocus) {
+            if (hasFocus()) {
+                // If our window regained focus, and we have focus, then begin
+                // drawing the focus ring, and restore the TextView if
+                // necessary.
+                mDrawFocusRing = true;
+                if (mNeedsUpdateTextEntry) {
+                    updateTextEntry();
+                }
+                if (mNativeClass != 0) {
+                    nativeRecordButtons(true, false, true);
+                }
+            } else {
+                // If our window gained focus, but we do not have it, do not
+                // draw the focus ring.
+                mDrawFocusRing = false;
+                // We do not call nativeRecordButtons here because we assume
+                // that when we lost focus, or window focus, it got called with
+                // false for the first parameter
+            }
+        } else {
+            if (!mZoomButtonsController.isVisible()) {
+                /*
+                 * The zoom controls come in their own window, so our window
+                 * loses focus. Our policy is to not draw the focus ring if
+                 * our window is not focused, but this is an exception since
+                 * the user can still navigate the web page with the zoom
+                 * controls showing.
+                 */
+                // If our window has lost focus, stop drawing the focus ring
+                mDrawFocusRing = false;
+            }
+            mGotKeyDown = false;
+            mShiftIsPressed = false;
+            if (mNativeClass != 0) {
+                nativeRecordButtons(false, false, true);
+            }
+        }
+        invalidate();
+        super.onWindowFocusChanged(hasWindowFocus);
+    }
+
+    @Override
+    protected void onFocusChanged(boolean focused, int direction,
+            Rect previouslyFocusedRect) {
+        if (LOGV_ENABLED) {
+            Log.v(LOGTAG, "MT focusChanged " + focused + ", " + direction);
+        }
+        if (focused) {
+            // When we regain focus, if we have window focus, resume drawing
+            // the focus ring, and add the TextView if necessary.
+            if (hasWindowFocus()) {
+                mDrawFocusRing = true;
+                if (mNeedsUpdateTextEntry) {
+                    updateTextEntry();
+                    mNeedsUpdateTextEntry = false;
+                }
+                if (mNativeClass != 0) {
+                    nativeRecordButtons(true, false, true);
+                }
+            //} else {
+                // The WebView has gained focus while we do not have
+                // windowfocus.  When our window lost focus, we should have
+                // called nativeRecordButtons(false...)
+            }
+        } else {
+            // When we lost focus, unless focus went to the TextView (which is
+            // true if we are in editing mode), stop drawing the focus ring.
+            if (!inEditingMode()) {
+                mDrawFocusRing = false;
+                if (mNativeClass != 0) {
+                    nativeRecordButtons(false, false, true);
+                }
+            }
+            mGotKeyDown = false;
+        }
+
+        super.onFocusChanged(focused, direction, previouslyFocusedRect);
+    }
+
+    @Override
+    protected void onSizeChanged(int w, int h, int ow, int oh) {
+        super.onSizeChanged(w, h, ow, oh);
+        // Center zooming to the center of the screen.  This is appropriate for
+        // this case of zooming, and it also sets us up properly if we remove
+        // the new zoom ring controller
+        mZoomCenterX = getViewWidth() * .5f;
+        mZoomCenterY = getViewHeight() * .5f;
+
+        // update mMinZoomScale
+        if (mMinContentWidth > MAX_FLOAT_CONTENT_WIDTH) {
+            boolean atMin = Math.abs(mActualScale - mMinZoomScale) < 0.01f;
+            mMinZoomScale = (float) getViewWidth() / mContentWidth;
+            if (atMin) {
+                // if the WebView was at the minimum zoom scale, keep it. e,g.,
+                // the WebView was at the minimum zoom scale at the portrait
+                // mode, rotate it to the landscape modifying the scale to the
+                // new minimum zoom scale, when rotating back, we would like to
+                // keep the minimum zoom scale instead of keeping the same scale
+                // as normally we do.
+                mActualScale = mMinZoomScale;
+            }
+        }
+
+        // we always force, in case our height changed, in which case we still
+        // want to send the notification over to webkit
+        setNewZoomScale(mActualScale, true);
+    }
+
+    @Override
+    protected void onScrollChanged(int l, int t, int oldl, int oldt) {
+        super.onScrollChanged(l, t, oldl, oldt);
+        sendOurVisibleRect();
+    }
+    
+    
+    @Override
+    public boolean dispatchKeyEvent(KeyEvent event) {
+        boolean dispatch = true;
+
+        if (!inEditingMode()) {
+            if (event.getAction() == KeyEvent.ACTION_DOWN) {
+                mGotKeyDown = true;
+            } else {
+                if (!mGotKeyDown) {
+                    /*
+                     * We got a key up for which we were not the recipient of
+                     * the original key down. Don't give it to the view.
+                     */
+                    dispatch = false;
+                }
+                mGotKeyDown = false;
+            }
+        }
+
+        if (dispatch) {
+            return super.dispatchKeyEvent(event);
+        } else {
+            // We didn't dispatch, so let something else handle the key
+            return false;
+        }
+    }
+
+    // Here are the snap align logic:
+    // 1. If it starts nearly horizontally or vertically, snap align;
+    // 2. If there is a dramitic direction change, let it go;
+    // 3. If there is a same direction back and forth, lock it.
+
+    // adjustable parameters
+    private int mMinLockSnapReverseDistance;
+    private static final float MAX_SLOPE_FOR_DIAG = 1.5f;
+    private static final int MIN_BREAK_SNAP_CROSS_DISTANCE = 80;
+
+    @Override
+    public boolean onTouchEvent(MotionEvent ev) {
+        if (mNativeClass == 0 || !isClickable() || !isLongClickable()) {
+            return false;
+        }
+
+        if (mShowZoomRingTutorial && getSettings().supportZoom()
+                && (mMaxZoomScale - mMinZoomScale) > ZOOM_RING_STEPS * 0.01f) {
+            ZoomRingController.showZoomTutorialOnce(mContext);
+            mShowZoomRingTutorial = false;
+            mPrivateHandler.sendMessageDelayed(mPrivateHandler
+                    .obtainMessage(DISMISS_ZOOM_RING_TUTORIAL),
+                    ZOOM_RING_TUTORIAL_DURATION);
+        }
+
+        if (LOGV_ENABLED) {
+            Log.v(LOGTAG, ev + " at " + ev.getEventTime() + " mTouchMode="
+                    + mTouchMode);
+        }
+
+        if ((mZoomRingController.isVisible() || mZoomButtonsController.isVisible())
+                && mInZoomTapDragMode) {
+            if (ev.getAction() == MotionEvent.ACTION_UP) {
+                // Just released the second tap, no longer in tap-drag mode
+                mInZoomTapDragMode = false;
+            }
+            return mZoomRingController.handleDoubleTapEvent(ev);
+        }
+
+        int action = ev.getAction();
+        float x = ev.getX();
+        float y = ev.getY();
+        long eventTime = ev.getEventTime();
+
+        // Due to the touch screen edge effect, a touch closer to the edge
+        // always snapped to the edge. As getViewWidth() can be different from
+        // getWidth() due to the scrollbar, adjusting the point to match
+        // getViewWidth(). Same applied to the height.
+        if (x > getViewWidth() - 1) {
+            x = getViewWidth() - 1;
+        }
+        if (y > getViewHeight() - 1) {
+            y = getViewHeight() - 1;
+        }
+
+        // pass the touch events from UI thread to WebCore thread
+        if (mForwardTouchEvents && mTouchMode != SCROLL_ZOOM_OUT
+                && mTouchMode != SCROLL_ZOOM_ANIMATION_IN
+                && mTouchMode != SCROLL_ZOOM_ANIMATION_OUT
+                && (action != MotionEvent.ACTION_MOVE || 
+                        eventTime - mLastSentTouchTime > TOUCH_SENT_INTERVAL)) {
+            WebViewCore.TouchEventData ted = new WebViewCore.TouchEventData();
+            ted.mAction = action;
+            ted.mX = viewToContent((int) x + mScrollX);
+            ted.mY = viewToContent((int) y + mScrollY);;
+            mWebViewCore.sendMessage(EventHub.TOUCH_EVENT, ted);
+            mLastSentTouchTime = eventTime;
+        }
+
+        int deltaX = (int) (mLastTouchX - x);
+        int deltaY = (int) (mLastTouchY - y);
+
+        switch (action) {
+            case MotionEvent.ACTION_DOWN: {
+                if (mTouchMode == SCROLL_ZOOM_ANIMATION_IN
+                        || mTouchMode == SCROLL_ZOOM_ANIMATION_OUT) {
+                    // no interaction while animation is in progress
+                    break;
+                } else if (mTouchMode == SCROLL_ZOOM_OUT) {
+                    mLastScrollX = mZoomScrollX;
+                    mLastScrollY = mZoomScrollY;
+                    // If two taps are close, ignore the first tap
+                } else if (!mScroller.isFinished()) {
+                    mScroller.abortAnimation();
+                    mTouchMode = TOUCH_DRAG_START_MODE;
+                    mPrivateHandler.removeMessages(RESUME_WEBCORE_UPDATE);
+                } else if (mShiftIsPressed) {
+                    mSelectX = mScrollX + (int) x;
+                    mSelectY = mScrollY + (int) y;
+                    mTouchMode = TOUCH_SELECT_MODE;
+                    if (LOGV_ENABLED) {
+                        Log.v(LOGTAG, "select=" + mSelectX + "," + mSelectY);
+                    }
+                    nativeMoveSelection(viewToContent(mSelectX)
+                            , viewToContent(mSelectY), false);
+                    mTouchSelection = mExtendSelection = true;
+                } else if (!ZoomRingController.useOldZoom(mContext) &&
+                        mPrivateHandler.hasMessages(RELEASE_SINGLE_TAP) &&
+                        (deltaX * deltaX + deltaY * deltaY < mDoubleTapSlopSquare)) {
+                    // Found doubletap, invoke the zoom controller
+                    mPrivateHandler.removeMessages(RELEASE_SINGLE_TAP);
+                    int contentX = viewToContent((int) mLastTouchX + mScrollX);
+                    int contentY = viewToContent((int) mLastTouchY + mScrollY);
+                    if (inEditingMode()) {
+                        mTextEntry.updateCachedTextfield();
+                    }
+                    nativeClearFocus(contentX, contentY);
+                    mInZoomTapDragMode = true;
+                    if (mLogEvent) {
+                        EventLog.writeEvent(EVENT_LOG_DOUBLE_TAP_DURATION,
+                                (eventTime - mLastTouchUpTime), eventTime);
+                    }
+                    return mZoomRingController.handleDoubleTapEvent(ev) ||
+                            mZoomButtonsController.handleDoubleTapEvent(ev);
+                } else {
+                    mTouchMode = TOUCH_INIT_MODE;
+                    mPreventDrag = mForwardTouchEvents;
+                    if (mLogEvent && eventTime - mLastTouchUpTime < 1000) {
+                        EventLog.writeEvent(EVENT_LOG_DOUBLE_TAP_DURATION,
+                                (eventTime - mLastTouchUpTime), eventTime);
+                    }
+                }
+                // don't trigger the link if zoom ring is visible
+                if (mTouchMode == TOUCH_INIT_MODE
+                        && !mZoomRingController.isVisible()) {
+                    mPrivateHandler.sendMessageDelayed(mPrivateHandler
+                            .obtainMessage(SWITCH_TO_SHORTPRESS), TAP_TIMEOUT);
+                }
+                // Remember where the motion event started
+                mLastTouchX = x;
+                mLastTouchY = y;
+                mLastTouchTime = eventTime;
+                mVelocityTracker = VelocityTracker.obtain();
+                mSnapScrollMode = SNAP_NONE;
+                break;
+            }
+            case MotionEvent.ACTION_MOVE: {
+                if (mTouchMode == TOUCH_DONE_MODE 
+                        || mTouchMode == SCROLL_ZOOM_ANIMATION_IN
+                        || mTouchMode == SCROLL_ZOOM_ANIMATION_OUT) {
+                    // no dragging during scroll zoom animation
+                    break;
+                }
+                if (mTouchMode == SCROLL_ZOOM_OUT) {
+                    // while fully zoomed out, move the virtual window
+                    moveZoomScrollWindow(x, y);
+                    break;
+                }
+                mVelocityTracker.addMovement(ev);
+
+                if (mTouchMode != TOUCH_DRAG_MODE) {
+                    if (mTouchMode == TOUCH_SELECT_MODE) {
+                        mSelectX = mScrollX + (int) x;
+                        mSelectY = mScrollY + (int) y;
+                        if (LOGV_ENABLED) {
+                            Log.v(LOGTAG, "xtend=" + mSelectX + "," + mSelectY);
+                        }
+                        nativeMoveSelection(viewToContent(mSelectX)
+                                , viewToContent(mSelectY), true);
+                        invalidate();
+                        break;
+                    }
+                    if (mPreventDrag || (deltaX * deltaX + deltaY * deltaY)
+                            < mTouchSlopSquare) {
+                        break;
+                    }
+
+                    if (mTouchMode == TOUCH_SHORTPRESS_MODE
+                            || mTouchMode == TOUCH_SHORTPRESS_START_MODE) {
+                        mPrivateHandler.removeMessages(SWITCH_TO_LONGPRESS);
+                    } else if (mTouchMode == TOUCH_INIT_MODE) {
+                        mPrivateHandler.removeMessages(SWITCH_TO_SHORTPRESS);
+                    }
+
+                    // if it starts nearly horizontal or vertical, enforce it
+                    int ax = Math.abs(deltaX);
+                    int ay = Math.abs(deltaY);
+                    if (ax > MAX_SLOPE_FOR_DIAG * ay) {
+                        mSnapScrollMode = SNAP_X;
+                        mSnapPositive = deltaX > 0;
+                    } else if (ay > MAX_SLOPE_FOR_DIAG * ax) {
+                        mSnapScrollMode = SNAP_Y;
+                        mSnapPositive = deltaY > 0;
+                    }
+
+                    mTouchMode = TOUCH_DRAG_MODE;
+                    WebViewCore.pauseUpdate(mWebViewCore);
+                    int contentX = viewToContent((int) x + mScrollX);
+                    int contentY = viewToContent((int) y + mScrollY);
+                    if (inEditingMode()) {
+                        mTextEntry.updateCachedTextfield();
+                    }
+                    nativeClearFocus(contentX, contentY);
+                    // remove the zoom anchor if there is any
+                    if (mZoomScale != 0) {
+                        mWebViewCore
+                                .sendMessage(EventHub.SET_SNAP_ANCHOR, 0, 0);
+                    }
+                }
+
+                // do pan
+                int newScrollX = pinLocX(mScrollX + deltaX);
+                deltaX = newScrollX - mScrollX;
+                int newScrollY = pinLocY(mScrollY + deltaY);
+                deltaY = newScrollY - mScrollY;
+                boolean done = false;
+                if (deltaX == 0 && deltaY == 0) {
+                    done = true;
+                } else {
+                    if (mSnapScrollMode == SNAP_X || mSnapScrollMode == SNAP_Y) {
+                        int ax = Math.abs(deltaX);
+                        int ay = Math.abs(deltaY);
+                        if (mSnapScrollMode == SNAP_X) {
+                            // radical change means getting out of snap mode
+                            if (ay > MAX_SLOPE_FOR_DIAG * ax
+                                    && ay > MIN_BREAK_SNAP_CROSS_DISTANCE) {
+                                mSnapScrollMode = SNAP_NONE;
+                            }
+                            // reverse direction means lock in the snap mode
+                            if ((ax > MAX_SLOPE_FOR_DIAG * ay) &&
+                                    ((mSnapPositive && 
+                                    deltaX < -mMinLockSnapReverseDistance)
+                                    || (!mSnapPositive &&
+                                    deltaX > mMinLockSnapReverseDistance))) {
+                                mSnapScrollMode = SNAP_X_LOCK;
+                            }
+                        } else {
+                            // radical change means getting out of snap mode
+                            if ((ax > MAX_SLOPE_FOR_DIAG * ay)
+                                    && ax > MIN_BREAK_SNAP_CROSS_DISTANCE) {
+                                mSnapScrollMode = SNAP_NONE;
+                            }
+                            // reverse direction means lock in the snap mode
+                            if ((ay > MAX_SLOPE_FOR_DIAG * ax) &&
+                                    ((mSnapPositive && 
+                                    deltaY < -mMinLockSnapReverseDistance)
+                                    || (!mSnapPositive && 
+                                    deltaY > mMinLockSnapReverseDistance))) {
+                                mSnapScrollMode = SNAP_Y_LOCK;
+                            }
+                        }
+                    }
+
+                    if (mSnapScrollMode == SNAP_X
+                            || mSnapScrollMode == SNAP_X_LOCK) {
+                        scrollBy(deltaX, 0);
+                        mLastTouchX = x;
+                    } else if (mSnapScrollMode == SNAP_Y
+                            || mSnapScrollMode == SNAP_Y_LOCK) {
+                        scrollBy(0, deltaY);
+                        mLastTouchY = y;
+                    } else {
+                        scrollBy(deltaX, deltaY);
+                        mLastTouchX = x;
+                        mLastTouchY = y;
+                    }
+                    mLastTouchTime = eventTime;
+                    mUserScroll = true;
+                }
+
+                if (ZoomRingController.useOldZoom(mContext)) {
+                    boolean showPlusMinus = mMinZoomScale < mMaxZoomScale;
+                    boolean showMagnify = canZoomScrollOut();
+                    if (mZoomControls != null && (showPlusMinus || showMagnify)) {
+                        if (mZoomControls.getVisibility() == View.VISIBLE) {
+                            mPrivateHandler.removeCallbacks(mZoomControlRunnable);
+                        } else {
+                            mZoomControls.show(showPlusMinus, showMagnify);
+                        }
+                        mPrivateHandler.postDelayed(mZoomControlRunnable,
+                                ZOOM_CONTROLS_TIMEOUT);
+                    }
+                }
+                if (done) {
+                    // return false to indicate that we can't pan out of the
+                    // view space
+                    return false;
+                }
+                break;
+            }
+            case MotionEvent.ACTION_UP: {
+                mLastTouchUpTime = eventTime;
+                switch (mTouchMode) {
+                    case TOUCH_INIT_MODE: // tap
+                        if (mZoomRingController.isVisible()) {
+                            // don't trigger the link if zoom ring is visible,
+                            // but still allow the double tap
+                            mPrivateHandler.sendMessageDelayed(mPrivateHandler
+                                    .obtainMessage(RELEASE_SINGLE_TAP,
+                                            new Boolean(false)),
+                                    DOUBLE_TAP_TIMEOUT);
+                            break;
+                        }
+                        mPrivateHandler.removeMessages(SWITCH_TO_SHORTPRESS);
+                        if (getSettings().supportZoom()) {
+                            mPrivateHandler.sendMessageDelayed(mPrivateHandler
+                                    .obtainMessage(RELEASE_SINGLE_TAP,
+                                            new Boolean(true)),
+                                    DOUBLE_TAP_TIMEOUT);
+                        } else {
+                            // do short press now
+                            mTouchMode = TOUCH_DONE_MODE;
+                            doShortPress();
+                        }
+                        break;
+                    case TOUCH_SELECT_MODE:
+                        commitCopy();
+                        mTouchSelection = false;
+                        break;
+                    case SCROLL_ZOOM_ANIMATION_IN:
+                    case SCROLL_ZOOM_ANIMATION_OUT:
+                        // no action during scroll animation
+                        break;
+                    case SCROLL_ZOOM_OUT:
+                        if (LOGV_ENABLED) {
+                            Log.v(LOGTAG, "ACTION_UP SCROLL_ZOOM_OUT"
+                                    + " eventTime - mLastTouchTime="
+                                    + (eventTime - mLastTouchTime));
+                        }
+                        // for now, always zoom back when the drag completes
+                        if (true || eventTime - mLastTouchTime < TAP_TIMEOUT) {
+                            // but if we tap, zoom in where we tap
+                            if (eventTime - mLastTouchTime < TAP_TIMEOUT) {
+                                zoomScrollTap(x, y);
+                            }
+                            // start zooming in back to the original view
+                            setZoomScrollIn();
+                            mTouchMode = SCROLL_ZOOM_ANIMATION_IN;
+                            invalidate();
+                        }
+                        break;
+                    case TOUCH_SHORTPRESS_START_MODE:
+                    case TOUCH_SHORTPRESS_MODE: {
+                        mPrivateHandler.removeMessages(SWITCH_TO_LONGPRESS);
+                        if (eventTime - mLastTouchTime < TAP_TIMEOUT
+                                && getSettings().supportZoom()) {
+                            // Note: window manager will not release ACTION_UP
+                            // until all the previous action events are
+                            // returned. If GC happens, it can cause
+                            // SWITCH_TO_SHORTPRESS message fired before
+                            // ACTION_UP sent even time stamp of ACTION_UP is
+                            // less than the tap time out. We need to treat this
+                            // as tap instead of short press.
+                            mTouchMode = TOUCH_INIT_MODE;
+                            mPrivateHandler.sendMessageDelayed(mPrivateHandler
+                                    .obtainMessage(RELEASE_SINGLE_TAP,
+                                            new Boolean(true)),
+                                    DOUBLE_TAP_TIMEOUT);
+                        } else {
+                            mTouchMode = TOUCH_DONE_MODE;
+                            doShortPress();
+                        }
+                        break;
+                    }
+                    case TOUCH_DRAG_MODE:
+                        // if the user waits a while w/o moving before the
+                        // up, we don't want to do a fling
+                        if (eventTime - mLastTouchTime <= MIN_FLING_TIME) {
+                            mVelocityTracker.addMovement(ev);
+                            doFling();
+                            break;
+                        }
+                        WebViewCore.resumeUpdate(mWebViewCore);
+                        break;
+                    case TOUCH_DRAG_START_MODE:
+                    case TOUCH_DONE_MODE:
+                        // do nothing
+                        break;
+                }
+                // we also use mVelocityTracker == null to tell us that we are
+                // not "moving around", so we can take the slower/prettier
+                // mode in the drawing code
+                if (mVelocityTracker != null) {
+                    mVelocityTracker.recycle();
+                    mVelocityTracker = null;
+                }
+                break;
+            }
+            case MotionEvent.ACTION_CANCEL: {
+                // we also use mVelocityTracker == null to tell us that we are
+                // not "moving around", so we can take the slower/prettier
+                // mode in the drawing code
+                if (mVelocityTracker != null) {
+                    mVelocityTracker.recycle();
+                    mVelocityTracker = null;
+                }
+                if (mTouchMode == SCROLL_ZOOM_OUT ||
+                        mTouchMode == SCROLL_ZOOM_ANIMATION_IN) {
+                    scrollTo(mZoomScrollX, mZoomScrollY);
+                } else if (mTouchMode == TOUCH_DRAG_MODE) {
+                    WebViewCore.resumeUpdate(mWebViewCore);
+                }
+                mPrivateHandler.removeMessages(SWITCH_TO_SHORTPRESS);
+                mPrivateHandler.removeMessages(SWITCH_TO_LONGPRESS);
+                mPrivateHandler.removeMessages(RELEASE_SINGLE_TAP);
+                mTouchMode = TOUCH_DONE_MODE;
+                int contentX = viewToContent((int) mLastTouchX + mScrollX);
+                int contentY = viewToContent((int) mLastTouchY + mScrollY);
+                if (inEditingMode()) {
+                    mTextEntry.updateCachedTextfield();
+                }
+                nativeClearFocus(contentX, contentY);
+                break;
+            }
+        }
+        return true;
+    }
+    
+    private long mTrackballFirstTime = 0;
+    private long mTrackballLastTime = 0;
+    private float mTrackballRemainsX = 0.0f;
+    private float mTrackballRemainsY = 0.0f;
+    private int mTrackballXMove = 0;
+    private int mTrackballYMove = 0;
+    private boolean mExtendSelection = false;
+    private boolean mTouchSelection = false;
+    private static final int TRACKBALL_KEY_TIMEOUT = 1000;
+    private static final int TRACKBALL_TIMEOUT = 200;
+    private static final int TRACKBALL_WAIT = 100;
+    private static final int TRACKBALL_SCALE = 400;
+    private static final int TRACKBALL_SCROLL_COUNT = 5;
+    private static final int TRACKBALL_MOVE_COUNT = 10;
+    private static final int TRACKBALL_MULTIPLIER = 3;
+    private static final int SELECT_CURSOR_OFFSET = 16;
+    private int mSelectX = 0;
+    private int mSelectY = 0;
+    private boolean mShiftIsPressed = false;
+    private boolean mTrackballDown = false;
+    private long mTrackballUpTime = 0;
+    private long mLastFocusTime = 0;
+    private Rect mLastFocusBounds;
+
+    // Set by default; BrowserActivity clears to interpret trackball data
+    // directly for movement. Currently, the framework only passes 
+    // arrow key events, not trackball events, from one child to the next
+    private boolean mMapTrackballToArrowKeys = true;
+    
+    public void setMapTrackballToArrowKeys(boolean setMap) {
+        mMapTrackballToArrowKeys = setMap;
+    }
+
+    void resetTrackballTime() {
+        mTrackballLastTime = 0;
+    }
+
+    @Override
+    public boolean onTrackballEvent(MotionEvent ev) {
+        long time = ev.getEventTime();
+        if ((ev.getMetaState() & KeyEvent.META_ALT_ON) != 0) {
+            if (ev.getY() > 0) pageDown(true);
+            if (ev.getY() < 0) pageUp(true);
+            return true;
+        }
+        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
+            mPrivateHandler.removeMessages(SWITCH_TO_ENTER);
+            mTrackballDown = true;
+            if (mNativeClass != 0) {
+                nativeRecordButtons(hasFocus() && hasWindowFocus(), true, true);
+            }
+            if (time - mLastFocusTime <= TRACKBALL_TIMEOUT
+                    && !mLastFocusBounds.equals(nativeGetFocusRingBounds())) {
+                nativeSelectBestAt(mLastFocusBounds);
+            }
+            if (LOGV_ENABLED) {
+                Log.v(LOGTAG, "onTrackballEvent down ev=" + ev
+                        + " time=" + time 
+                        + " mLastFocusTime=" + mLastFocusTime);
+            }
+            if (isInTouchMode()) requestFocusFromTouch();
+            return false; // let common code in onKeyDown at it
+        } 
+        if (ev.getAction() == MotionEvent.ACTION_UP) {
+            // LONG_PRESS_ENTER is set in common onKeyDown
+            mPrivateHandler.removeMessages(LONG_PRESS_ENTER);
+            mTrackballDown = false;
+            mTrackballUpTime = time;
+            if (mShiftIsPressed) {
+                if (mExtendSelection) {
+                    commitCopy();
+                } else {
+                    mExtendSelection = true;
+                }
+            }
+            if (LOGV_ENABLED) {
+                Log.v(LOGTAG, "onTrackballEvent up ev=" + ev
+                        + " time=" + time 
+                );
+            }
+            return false; // let common code in onKeyUp at it
+        }
+        if (mMapTrackballToArrowKeys && mShiftIsPressed == false) {
+            if (LOGV_ENABLED) Log.v(LOGTAG, "onTrackballEvent gmail quit");
+            return false;
+        }
+        // no move if we're still waiting on SWITCH_TO_ENTER timeout
+        if (mTouchMode == TOUCH_DOUBLECLICK_MODE) {
+            if (LOGV_ENABLED) Log.v(LOGTAG, "onTrackballEvent 2 click quit");
+            return true;
+        }
+        if (mTrackballDown) {
+            if (LOGV_ENABLED) Log.v(LOGTAG, "onTrackballEvent down quit");
+            return true; // discard move if trackball is down
+        }
+        if (time - mTrackballUpTime < TRACKBALL_TIMEOUT) {
+            if (LOGV_ENABLED) Log.v(LOGTAG, "onTrackballEvent up timeout quit");
+            return true;
+        }
+        // TODO: alternatively we can do panning as touch does
+        switchOutDrawHistory();
+        if (time - mTrackballLastTime > TRACKBALL_TIMEOUT) {
+            if (LOGV_ENABLED) {
+                Log.v(LOGTAG, "onTrackballEvent time=" 
+                        + time + " last=" + mTrackballLastTime);
+            }
+            mTrackballFirstTime = time;
+            mTrackballXMove = mTrackballYMove = 0;
+        }
+        mTrackballLastTime = time;
+        if (LOGV_ENABLED) {
+            Log.v(LOGTAG, "onTrackballEvent ev=" + ev + " time=" + time);
+        }
+        mTrackballRemainsX += ev.getX();
+        mTrackballRemainsY += ev.getY();
+        doTrackball(time);
+        return true;
+    }
+    
+    void moveSelection(float xRate, float yRate) {
+        if (mNativeClass == 0)
+            return;
+        int width = getViewWidth();
+        int height = getViewHeight();
+        mSelectX += scaleTrackballX(xRate, width);
+        mSelectY += scaleTrackballY(yRate, height);
+        int maxX = width + mScrollX;
+        int maxY = height + mScrollY;
+        mSelectX = Math.min(maxX, Math.max(mScrollX - SELECT_CURSOR_OFFSET
+                , mSelectX));
+        mSelectY = Math.min(maxY, Math.max(mScrollY - SELECT_CURSOR_OFFSET
+                , mSelectY));
+        if (LOGV_ENABLED) {
+            Log.v(LOGTAG, "moveSelection" 
+                    + " mSelectX=" + mSelectX
+                    + " mSelectY=" + mSelectY
+                    + " mScrollX=" + mScrollX
+                    + " mScrollY=" + mScrollY
+                    + " xRate=" + xRate
+                    + " yRate=" + yRate
+                    );
+        }
+        nativeMoveSelection(viewToContent(mSelectX)
+                , viewToContent(mSelectY), mExtendSelection);
+        int scrollX = mSelectX < mScrollX ? -SELECT_CURSOR_OFFSET
+                : mSelectX > maxX - SELECT_CURSOR_OFFSET ? SELECT_CURSOR_OFFSET 
+                : 0;
+        int scrollY = mSelectY < mScrollY ? -SELECT_CURSOR_OFFSET
+                : mSelectY > maxY - SELECT_CURSOR_OFFSET ? SELECT_CURSOR_OFFSET 
+                : 0;
+        pinScrollBy(scrollX, scrollY, true, 0);
+        Rect select = new Rect(mSelectX, mSelectY, mSelectX + 1, mSelectY + 1);
+        requestRectangleOnScreen(select);
+        invalidate();
+   }
+
+    private int scaleTrackballX(float xRate, int width) {
+        int xMove = (int) (xRate / TRACKBALL_SCALE * width);
+        int nextXMove = xMove;
+        if (xMove > 0) {
+            if (xMove > mTrackballXMove) {
+                xMove -= mTrackballXMove;
+            }
+        } else if (xMove < mTrackballXMove) {
+            xMove -= mTrackballXMove;
+        }
+        mTrackballXMove = nextXMove;
+        return xMove;
+    }
+
+    private int scaleTrackballY(float yRate, int height) {
+        int yMove = (int) (yRate / TRACKBALL_SCALE * height);
+        int nextYMove = yMove;
+        if (yMove > 0) {
+            if (yMove > mTrackballYMove) {
+                yMove -= mTrackballYMove;
+            }
+        } else if (yMove < mTrackballYMove) {
+            yMove -= mTrackballYMove;
+        }
+        mTrackballYMove = nextYMove;
+        return yMove;
+    }
+
+    private int keyCodeToSoundsEffect(int keyCode) {
+        switch(keyCode) {
+            case KeyEvent.KEYCODE_DPAD_UP:
+                return SoundEffectConstants.NAVIGATION_UP;
+            case KeyEvent.KEYCODE_DPAD_RIGHT:
+                return SoundEffectConstants.NAVIGATION_RIGHT;
+            case KeyEvent.KEYCODE_DPAD_DOWN:
+                return SoundEffectConstants.NAVIGATION_DOWN;
+            case KeyEvent.KEYCODE_DPAD_LEFT:
+                return SoundEffectConstants.NAVIGATION_LEFT;
+        }
+        throw new IllegalArgumentException("keyCode must be one of " +
+                "{KEYCODE_DPAD_UP, KEYCODE_DPAD_RIGHT, KEYCODE_DPAD_DOWN, " +
+                "KEYCODE_DPAD_LEFT}.");
+    }
+
+    private void doTrackball(long time) {
+        int elapsed = (int) (mTrackballLastTime - mTrackballFirstTime);
+        if (elapsed == 0) {
+            elapsed = TRACKBALL_TIMEOUT;
+        }
+        float xRate = mTrackballRemainsX * 1000 / elapsed; 
+        float yRate = mTrackballRemainsY * 1000 / elapsed;
+        if (mShiftIsPressed) {
+            moveSelection(xRate, yRate);
+            mTrackballRemainsX = mTrackballRemainsY = 0;
+            return;
+        }
+        float ax = Math.abs(xRate);
+        float ay = Math.abs(yRate);
+        float maxA = Math.max(ax, ay);
+        if (LOGV_ENABLED) {
+            Log.v(LOGTAG, "doTrackball elapsed=" + elapsed
+                    + " xRate=" + xRate
+                    + " yRate=" + yRate
+                    + " mTrackballRemainsX=" + mTrackballRemainsX
+                    + " mTrackballRemainsY=" + mTrackballRemainsY);
+        }
+        int width = mContentWidth - getViewWidth();
+        int height = mContentHeight - getViewHeight();
+        if (width < 0) width = 0;
+        if (height < 0) height = 0;
+        if (mTouchMode == SCROLL_ZOOM_OUT) {
+            int oldX = mZoomScrollX;
+            int oldY = mZoomScrollY;
+            int maxWH = Math.max(width, height);
+            mZoomScrollX += scaleTrackballX(xRate, maxWH);
+            mZoomScrollY += scaleTrackballY(yRate, maxWH);
+            if (LOGV_ENABLED) {
+                Log.v(LOGTAG, "doTrackball SCROLL_ZOOM_OUT"
+                        + " mZoomScrollX=" + mZoomScrollX 
+                        + " mZoomScrollY=" + mZoomScrollY);
+            }
+            mZoomScrollX = Math.min(width, Math.max(0, mZoomScrollX));
+            mZoomScrollY = Math.min(height, Math.max(0, mZoomScrollY));
+            if (oldX != mZoomScrollX || oldY != mZoomScrollY) {
+                invalidate();
+            }
+            mTrackballRemainsX = mTrackballRemainsY = 0;
+            return;
+        }
+        ax = Math.abs(mTrackballRemainsX * TRACKBALL_MULTIPLIER);
+        ay = Math.abs(mTrackballRemainsY * TRACKBALL_MULTIPLIER);
+        maxA = Math.max(ax, ay);
+        int count = Math.max(0, (int) maxA);
+        int oldScrollX = mScrollX;
+        int oldScrollY = mScrollY;
+        if (count > 0) {
+            int selectKeyCode = ax < ay ? mTrackballRemainsY < 0 ? 
+                    KeyEvent.KEYCODE_DPAD_UP : KeyEvent.KEYCODE_DPAD_DOWN : 
+                    mTrackballRemainsX < 0 ? KeyEvent.KEYCODE_DPAD_LEFT :
+                    KeyEvent.KEYCODE_DPAD_RIGHT;
+            count = Math.min(count, TRACKBALL_MOVE_COUNT);
+            if (LOGV_ENABLED) {
+                Log.v(LOGTAG, "doTrackball keyCode=" + selectKeyCode 
+                        + " count=" + count
+                        + " mTrackballRemainsX=" + mTrackballRemainsX
+                        + " mTrackballRemainsY=" + mTrackballRemainsY);
+            }
+            if (navHandledKey(selectKeyCode, count, false, time)) {
+                playSoundEffect(keyCodeToSoundsEffect(selectKeyCode));
+            }
+            mTrackballRemainsX = mTrackballRemainsY = 0;
+        }
+        if (count >= TRACKBALL_SCROLL_COUNT) {
+            int xMove = scaleTrackballX(xRate, width);
+            int yMove = scaleTrackballY(yRate, height);
+            if (LOGV_ENABLED) {
+                Log.v(LOGTAG, "doTrackball pinScrollBy"
+                        + " count=" + count
+                        + " xMove=" + xMove + " yMove=" + yMove
+                        + " mScrollX-oldScrollX=" + (mScrollX-oldScrollX) 
+                        + " mScrollY-oldScrollY=" + (mScrollY-oldScrollY) 
+                        );
+            }
+            if (Math.abs(mScrollX - oldScrollX) > Math.abs(xMove)) {
+                xMove = 0;
+            }
+            if (Math.abs(mScrollY - oldScrollY) > Math.abs(yMove)) {
+                yMove = 0;
+            }
+            if (xMove != 0 || yMove != 0) {
+                pinScrollBy(xMove, yMove, true, 0);
+            }
+            mUserScroll = true;
+        } 
+        mWebViewCore.sendMessage(EventHub.UNBLOCK_FOCUS);        
+    }
+
+    public void flingScroll(int vx, int vy) {
+        int maxX = Math.max(computeHorizontalScrollRange() - getViewWidth(), 0);
+        int maxY = Math.max(computeVerticalScrollRange() - getViewHeight(), 0);
+        
+        mScroller.fling(mScrollX, mScrollY, vx, vy, 0, maxX, 0, maxY);
+        invalidate();
+    }
+    
+    private void doFling() {
+        if (mVelocityTracker == null) {
+            return;
+        }
+        int maxX = Math.max(computeHorizontalScrollRange() - getViewWidth(), 0);
+        int maxY = Math.max(computeVerticalScrollRange() - getViewHeight(), 0);
+
+        mVelocityTracker.computeCurrentVelocity(1000);
+        int vx = (int) mVelocityTracker.getXVelocity();
+        int vy = (int) mVelocityTracker.getYVelocity();
+
+        if (mSnapScrollMode != SNAP_NONE) {
+            if (mSnapScrollMode == SNAP_X || mSnapScrollMode == SNAP_X_LOCK) {
+                vy = 0;
+            } else {
+                vx = 0;
+            }
+        }
+        
+        if (true /* EMG release: make our fling more like Maps' */) {
+            // maps cuts their velocity in half
+            vx = vx * 3 / 4;
+            vy = vy * 3 / 4;
+        }
+
+        mScroller.fling(mScrollX, mScrollY, -vx, -vy, 0, maxX, 0, maxY);
+        // TODO: duration is calculated based on velocity, if the range is
+        // small, the animation will stop before duration is up. We may
+        // want to calculate how long the animation is going to run to precisely
+        // resume the webcore update.
+        final int time = mScroller.getDuration();
+        mPrivateHandler.sendEmptyMessageDelayed(RESUME_WEBCORE_UPDATE, time);
+        invalidate();
+    }
+
+    private boolean zoomWithPreview(float scale) {
+        float oldScale = mActualScale;
+
+        // snap to 100% if it is close
+        if (scale > 0.95f && scale < 1.05f) {
+            scale = 1.0f;
+        }
+
+        setNewZoomScale(scale, false);
+
+        if (oldScale != mActualScale) {
+            // use mZoomPickerScale to see zoom preview first
+            mZoomStart = SystemClock.uptimeMillis();
+            mInvInitialZoomScale = 1.0f / oldScale;
+            mInvFinalZoomScale = 1.0f / mActualScale;
+            mZoomScale = mActualScale;
+            invalidate();
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    /**
+     * Returns a view containing zoom controls i.e. +/- buttons. The caller is
+     * in charge of installing this view to the view hierarchy. This view will
+     * become visible when the user starts scrolling via touch and fade away if
+     * the user does not interact with it.
+     */
+    public View getZoomControls() {
+        if (!getSettings().supportZoom()) {
+            Log.w(LOGTAG, "This WebView doesn't support zoom.");
+            return null;
+        }
+        if (mZoomControls == null) {
+            mZoomControls = createZoomControls();
+            
+            /*
+             * need to be set to VISIBLE first so that getMeasuredHeight() in
+             * {@link #onSizeChanged()} can return the measured value for proper
+             * layout.
+             */
+            mZoomControls.setVisibility(View.VISIBLE);
+            mZoomControlRunnable = new Runnable() {
+                public void run() {
+                    
+                    /* Don't dismiss the controls if the user has
+                     * focus on them. Wait and check again later.
+                     */
+                    if (!mZoomControls.hasFocus()) {
+                        mZoomControls.hide();
+                    } else {
+                        mPrivateHandler.removeCallbacks(mZoomControlRunnable);
+                        mPrivateHandler.postDelayed(mZoomControlRunnable,
+                                ZOOM_CONTROLS_TIMEOUT);
+                    }
+                }
+            };
+        }
+        return mZoomControls;
+    }
+
+    /**
+     * @hide pending API council? Assuming we make ZoomRingController itself
+     *       public, which I think we will.
+     */
+    public ZoomRingController getZoomRingController() {
+        return mZoomRingController;    
+    }
+    
+    /**
+     * Perform zoom in in the webview
+     * @return TRUE if zoom in succeeds. FALSE if no zoom changes.
+     */
+    public boolean zoomIn() {
+        // TODO: alternatively we can disallow this during draw history mode
+        switchOutDrawHistory();
+        return zoomWithPreview(mActualScale * 1.25f);
+    }
+
+    /**
+     * Perform zoom out in the webview
+     * @return TRUE if zoom out succeeds. FALSE if no zoom changes.
+     */
+    public boolean zoomOut() {
+        // TODO: alternatively we can disallow this during draw history mode
+        switchOutDrawHistory();
+        return zoomWithPreview(mActualScale * 0.8f);
+    }
+
+    private ExtendedZoomControls createZoomControls() {
+        ExtendedZoomControls zoomControls = new ExtendedZoomControls(mContext
+            , null);
+        zoomControls.setOnZoomInClickListener(new OnClickListener() {
+            public void onClick(View v) {
+                // reset time out
+                mPrivateHandler.removeCallbacks(mZoomControlRunnable);
+                mPrivateHandler.postDelayed(mZoomControlRunnable,
+                        ZOOM_CONTROLS_TIMEOUT);
+                zoomIn();
+            }
+        });
+        zoomControls.setOnZoomOutClickListener(new OnClickListener() {
+            public void onClick(View v) {
+                // reset time out
+                mPrivateHandler.removeCallbacks(mZoomControlRunnable);
+                mPrivateHandler.postDelayed(mZoomControlRunnable,
+                        ZOOM_CONTROLS_TIMEOUT);
+                zoomOut();
+            }
+        });
+        zoomControls.setOnZoomMagnifyClickListener(new OnClickListener() {
+            public void onClick(View v) {
+                // Hide the zoom ring
+                mZoomRingController.setVisible(false);
+                
+                mPrivateHandler.removeCallbacks(mZoomControlRunnable);
+                mPrivateHandler.postDelayed(mZoomControlRunnable,
+                        ZOOM_CONTROLS_TIMEOUT);
+                zoomScrollOut();
+            }
+        });
+        return zoomControls;
+    }
+
+    private void updateSelection() {
+        if (mNativeClass == 0) {
+            return;
+        }
+        // mLastTouchX and mLastTouchY are the point in the current viewport
+        int contentX = viewToContent((int) mLastTouchX + mScrollX);
+        int contentY = viewToContent((int) mLastTouchY + mScrollY);
+        int contentSize = ViewConfiguration.getTouchSlop();
+        Rect rect = new Rect(contentX - contentSize, contentY - contentSize,
+                contentX + contentSize, contentY + contentSize);
+        // If we were already focused on a textfield, update its cache.
+        if (inEditingMode()) {
+            mTextEntry.updateCachedTextfield();
+        }
+        nativeSelectBestAt(rect);
+    }
+
+    /*package*/ void shortPressOnTextField() {
+        if (inEditingMode()) {
+            View v = mTextEntry;
+            int x = viewToContent((v.getLeft() + v.getRight()) >> 1);
+            int y = viewToContent((v.getTop() + v.getBottom()) >> 1);
+            int contentSize = ViewConfiguration.get(getContext()).getScaledTouchSlop();
+            nativeMotionUp(x, y, contentSize, true);
+        }
+    }
+
+    private void doShortPress() {
+        if (mNativeClass == 0) {
+            return;
+        }
+        switchOutDrawHistory();
+        // mLastTouchX and mLastTouchY are the point in the current viewport
+        int contentX = viewToContent((int) mLastTouchX + mScrollX);
+        int contentY = viewToContent((int) mLastTouchY + mScrollY);
+        int contentSize = ViewConfiguration.get(getContext()).getScaledTouchSlop();
+        if (nativeMotionUp(contentX, contentY, contentSize, true)) {
+            if (mLogEvent) {
+                Checkin.updateStats(mContext.getContentResolver(),
+                        Checkin.Stats.Tag.BROWSER_SNAP_CENTER, 1, 0.0);
+            }
+        }
+        if (nativeUpdateFocusNode() && !mFocusNode.mIsTextField
+                && !mFocusNode.mIsTextArea) {
+            playSoundEffect(SoundEffectConstants.CLICK);
+        }
+    }
+
+    // Called by JNI to handle a touch on a node representing an email address,
+    // address, or phone number
+    private void overrideLoading(String url) {
+        mCallbackProxy.uiOverrideUrlLoading(url);
+    }
+
+    @Override
+    public boolean requestFocus(int direction, Rect previouslyFocusedRect) {
+        boolean result = false;
+        if (inEditingMode()) {
+            result = mTextEntry.requestFocus(direction, previouslyFocusedRect);
+        } else {
+            result = super.requestFocus(direction, previouslyFocusedRect);
+            if (mWebViewCore.getSettings().getNeedInitialFocus()) {
+                // For cases such as GMail, where we gain focus from a direction,
+                // we want to move to the first available link.
+                // FIXME: If there are no visible links, we may not want to
+                int fakeKeyDirection = 0;
+                switch(direction) {
+                    case View.FOCUS_UP:
+                        fakeKeyDirection = KeyEvent.KEYCODE_DPAD_UP;
+                        break;
+                    case View.FOCUS_DOWN:
+                        fakeKeyDirection = KeyEvent.KEYCODE_DPAD_DOWN;
+                        break;
+                    case View.FOCUS_LEFT:
+                        fakeKeyDirection = KeyEvent.KEYCODE_DPAD_LEFT;
+                        break;
+                    case View.FOCUS_RIGHT:
+                        fakeKeyDirection = KeyEvent.KEYCODE_DPAD_RIGHT;
+                        break;
+                    default:
+                        return result;
+                }
+                if (mNativeClass != 0 && !nativeUpdateFocusNode()) {
+                    navHandledKey(fakeKeyDirection, 1, true, 0);
+                }
+            }
+        }
+        return result;
+    }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+
+        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
+        int heightSize = MeasureSpec.getSize(heightMeasureSpec);
+        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
+        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
+
+        int measuredHeight = heightSize;
+        int measuredWidth = widthSize;
+
+        // Grab the content size from WebViewCore.
+        int contentHeight = mContentHeight;
+        int contentWidth = mContentWidth;
+
+//        Log.d(LOGTAG, "------- measure " + heightMode);
+
+        if (heightMode != MeasureSpec.EXACTLY) {
+            mHeightCanMeasure = true;
+            measuredHeight = contentHeight;
+            if (heightMode == MeasureSpec.AT_MOST) {
+                // If we are larger than the AT_MOST height, then our height can
+                // no longer be measured and we should scroll internally.
+                if (measuredHeight > heightSize) {
+                    measuredHeight = heightSize;
+                    mHeightCanMeasure = false;
+                }
+            }
+        } else {
+            mHeightCanMeasure = false;
+        }
+        if (mNativeClass != 0) {
+            nativeSetHeightCanMeasure(mHeightCanMeasure);
+        }
+        // For the width, always use the given size unless unspecified.
+        if (widthMode == MeasureSpec.UNSPECIFIED) {
+            mWidthCanMeasure = true;
+            measuredWidth = contentWidth;
+        } else {
+            mWidthCanMeasure = false;
+        }
+
+        synchronized (this) {
+            setMeasuredDimension(measuredWidth, measuredHeight);
+        }
+    }
+
+    @Override
+    public boolean requestChildRectangleOnScreen(View child,
+                                                 Rect rect,
+                                                 boolean immediate) {
+        rect.offset(child.getLeft() - child.getScrollX(),
+                child.getTop() - child.getScrollY());
+
+        int height = getHeight() - getHorizontalScrollbarHeight();
+        int screenTop = mScrollY;
+        int screenBottom = screenTop + height;
+
+        int scrollYDelta = 0;
+
+        if (rect.bottom > screenBottom && rect.top > screenTop) {
+            if (rect.height() > height) {
+                scrollYDelta += (rect.top - screenTop);
+            } else {
+                scrollYDelta += (rect.bottom - screenBottom);
+            }
+        } else if (rect.top < screenTop) {
+            scrollYDelta -= (screenTop - rect.top);
+        }
+
+        int width = getWidth() - getVerticalScrollbarWidth();
+        int screenLeft = mScrollX;
+        int screenRight = screenLeft + width;
+
+        int scrollXDelta = 0;
+
+        if (rect.right > screenRight && rect.left > screenLeft) {
+            if (rect.width() > width) {
+                scrollXDelta += (rect.left - screenLeft);
+            } else {
+                scrollXDelta += (rect.right - screenRight);
+            }
+        } else if (rect.left < screenLeft) {
+            scrollXDelta -= (screenLeft - rect.left);
+        }
+
+        if ((scrollYDelta | scrollXDelta) != 0) {
+            return pinScrollBy(scrollXDelta, scrollYDelta, !immediate, 0);
+        }
+
+        return false;
+    }
+    
+    /* package */ void replaceTextfieldText(int oldStart, int oldEnd,
+            String replace, int newStart, int newEnd) {
+        HashMap arg = new HashMap();
+        arg.put("focusData", new WebViewCore.FocusData(mFocusData));
+        arg.put("replace", replace);
+        arg.put("start", new Integer(newStart));
+        arg.put("end", new Integer(newEnd));
+        mWebViewCore.sendMessage(EventHub.REPLACE_TEXT, oldStart, oldEnd, arg);
+    }
+
+    /* package */ void passToJavaScript(String currentText, KeyEvent event) {
+        HashMap arg = new HashMap();
+        arg.put("focusData", new WebViewCore.FocusData(mFocusData));
+        arg.put("event", event);
+        arg.put("currentText", currentText);
+        // Increase our text generation number, and pass it to webcore thread
+        mTextGeneration++;
+        mWebViewCore.sendMessage(EventHub.PASS_TO_JS, mTextGeneration, 0, arg);
+        // WebKit's document state is not saved until about to leave the page.
+        // To make sure the host application, like Browser, has the up to date 
+        // document state when it goes to background, we force to save the 
+        // document state.
+        mWebViewCore.removeMessages(EventHub.SAVE_DOCUMENT_STATE);
+        mWebViewCore.sendMessageDelayed(EventHub.SAVE_DOCUMENT_STATE,
+                new WebViewCore.FocusData(mFocusData), 1000);
+    }
+
+    /* package */ WebViewCore getWebViewCore() {
+        return mWebViewCore;
+    }
+
+    //-------------------------------------------------------------------------
+    // Methods can be called from a separate thread, like WebViewCore
+    // If it needs to call the View system, it has to send message.
+    //-------------------------------------------------------------------------
+
+    /**
+     * General handler to receive message coming from webkit thread
+     */
+    class PrivateHandler extends Handler {
+        @Override
+        public void handleMessage(Message msg) {
+            if (LOGV_ENABLED) {
+                Log.v(LOGTAG, msg.what < REMEMBER_PASSWORD || msg.what 
+                        > INVAL_RECT_MSG_ID ? Integer.toString(msg.what) 
+                        : HandlerDebugString[msg.what - REMEMBER_PASSWORD]);
+            }
+            switch (msg.what) {
+                case REMEMBER_PASSWORD: {
+                    mDatabase.setUsernamePassword(
+                            msg.getData().getString("host"),
+                            msg.getData().getString("username"),
+                            msg.getData().getString("password"));
+                    ((Message) msg.obj).sendToTarget();
+                    break;
+                }
+                case NEVER_REMEMBER_PASSWORD: {
+                    mDatabase.setUsernamePassword(
+                            msg.getData().getString("host"), null, null);
+                    ((Message) msg.obj).sendToTarget();
+                    break;
+                }
+                case SWITCH_TO_SHORTPRESS: {
+                    if (mTouchMode == TOUCH_INIT_MODE) {
+                        mTouchMode = TOUCH_SHORTPRESS_START_MODE;
+                        updateSelection();
+                    }
+                    break;
+                }
+                case SWITCH_TO_LONGPRESS: {
+                    mTouchMode = TOUCH_DONE_MODE;
+                    performLongClick();
+                    updateTextEntry();
+                    break;
+                }
+                case RELEASE_SINGLE_TAP: {
+                    mTouchMode = TOUCH_DONE_MODE;
+                    if ((Boolean)msg.obj) {
+                        doShortPress();
+                    }
+                    break;
+                }
+                case SWITCH_TO_ENTER:
+                    if (LOGV_ENABLED) Log.v(LOGTAG, "SWITCH_TO_ENTER");
+                    mTouchMode = TOUCH_DONE_MODE;
+                    onKeyUp(KeyEvent.KEYCODE_ENTER
+                            , new KeyEvent(KeyEvent.ACTION_UP
+                            , KeyEvent.KEYCODE_ENTER));
+                    break;
+                case SCROLL_BY_MSG_ID:
+                    setContentScrollBy(msg.arg1, msg.arg2, (Boolean) msg.obj);
+                    break;
+                case SYNC_SCROLL_TO_MSG_ID:
+                    if (mUserScroll) {
+                        // if user has scrolled explicitly, don't sync the 
+                        // scroll position any more
+                        mUserScroll = false;
+                        break;
+                    }
+                    // fall through
+                case SCROLL_TO_MSG_ID:
+                    if (setContentScrollTo(msg.arg1, msg.arg2)) {
+                        // if we can't scroll to the exact position due to pin,
+                        // send a message to WebCore to re-scroll when we get a 
+                        // new picture
+                        mUserScroll = false;
+                        mWebViewCore.sendMessage(EventHub.SYNC_SCROLL,
+                                msg.arg1, msg.arg2);
+                    }
+                    break;
+                case SPAWN_SCROLL_TO_MSG_ID:
+                    spawnContentScrollTo(msg.arg1, msg.arg2);
+                    break;
+                case NEW_PICTURE_MSG_ID:
+                    // called for new content
+                    final WebViewCore.DrawData draw = 
+                            (WebViewCore.DrawData) msg.obj;
+                    final Point viewSize = draw.mViewPoint;
+                    if (mZoomScale > 0) {
+                        // use the same logic in sendViewSizeZoom() to make sure
+                        // the mZoomScale has matched the viewSize so that we
+                        // can clear mZoomScale
+                        if (Math.round(getViewWidth() / mZoomScale) == viewSize.x) {
+                            mZoomScale = 0;
+                            mWebViewCore.sendMessage(EventHub.SET_SNAP_ANCHOR,
+                                    0, 0);
+                        }
+                    }
+                    mMinContentWidth = msg.arg1;
+                    if (mMinContentWidth > MAX_FLOAT_CONTENT_WIDTH) {
+                        mMinZoomScale = (float) getViewWidth()
+                                / draw.mWidthHeight.x;
+                    }
+                    // We update the layout (i.e. request a layout from the
+                    // view system) if the last view size that we sent to
+                    // WebCore matches the view size of the picture we just
+                    // received in the fixed dimension.
+                    final boolean updateLayout = viewSize.x == mLastWidthSent
+                            && viewSize.y == mLastHeightSent;
+                    recordNewContentSize(draw.mWidthHeight.x, 
+                            draw.mWidthHeight.y, updateLayout);
+                    if (LOGV_ENABLED) {
+                        Rect b = draw.mInvalRegion.getBounds();
+                        Log.v(LOGTAG, "NEW_PICTURE_MSG_ID {" +
+                                b.left+","+b.top+","+b.right+","+b.bottom+"}");
+                    }
+                    invalidate(contentToView(draw.mInvalRegion.getBounds()));
+                    if (mPictureListener != null) {
+                        mPictureListener.onNewPicture(WebView.this, capturePicture());
+                    }
+                    break;
+                case WEBCORE_INITIALIZED_MSG_ID:
+                    // nativeCreate sets mNativeClass to a non-zero value
+                    nativeCreate(msg.arg1);
+                    break;
+                case UPDATE_TEXTFIELD_TEXT_MSG_ID:
+                    // Make sure that the textfield is currently focused
+                    // and representing the same node as the pointer.  
+                    if (inEditingMode() && 
+                            mTextEntry.isSameTextField(msg.arg1)) {
+                        if (msg.getData().getBoolean("password")) {
+                            Spannable text = (Spannable) mTextEntry.getText();
+                            int start = Selection.getSelectionStart(text);
+                            int end = Selection.getSelectionEnd(text);
+                            mTextEntry.setInPassword(true);
+                            // Restore the selection, which may have been
+                            // ruined by setInPassword.
+                            Spannable pword = (Spannable) mTextEntry.getText();
+                            Selection.setSelection(pword, start, end);
+                        // If the text entry has created more events, ignore
+                        // this one.
+                        } else if (msg.arg2 == mTextGeneration) {
+                            mTextEntry.setTextAndKeepSelection(
+                                    (String) msg.obj);
+                        }
+                    }
+                    break;
+                case DID_FIRST_LAYOUT_MSG_ID:
+                    if (mNativeClass == 0) {
+                        break;
+                    }
+// Do not reset the focus or clear the text; the user may have already
+// navigated or entered text at this point. The focus should have gotten 
+// reset, if need be, when the focus cache was built. Similarly, the text
+// view should already be torn down and rebuilt if needed.
+//                    nativeResetFocus();
+//                    clearTextEntry();
+                    HashMap scaleLimit = (HashMap) msg.obj;
+                    int minScale = (Integer) scaleLimit.get("minScale");
+                    if (minScale == 0) {
+                        mMinZoomScale = DEFAULT_MIN_ZOOM_SCALE;
+                    } else {
+                        mMinZoomScale = (float) (minScale / 100.0);
+                    }
+                    int maxScale = (Integer) scaleLimit.get("maxScale");
+                    if (maxScale == 0) {
+                        mMaxZoomScale = DEFAULT_MAX_ZOOM_SCALE;
+                    } else {
+                        mMaxZoomScale = (float) (maxScale / 100.0);
+                    }
+                    // If history Picture is drawn, don't update zoomWidth
+                    if (mDrawHistory) {
+                        break;
+                    }
+                    int width = getViewWidth();
+                    if (width == 0) {
+                        break;
+                    }
+                    int initialScale = msg.arg1;
+                    int viewportWidth = msg.arg2;
+                    // by default starting a new page with 100% zoom scale.
+                    float scale = 1.0f;
+                    if (mInitialScale > 0) {
+                        scale = mInitialScale / 100.0f;
+                    } else  {
+                        if (mWebViewCore.getSettings().getUseWideViewPort()) {
+                            // force viewSizeChanged by setting mLastWidthSent
+                            // to 0
+                            mLastWidthSent = 0;
+                        }
+                        if (initialScale == 0) {
+                            // if viewportWidth is defined and it is smaller
+                            // than the view width, zoom in to fill the view
+                            if (viewportWidth > 0 && viewportWidth < width) {
+                                scale = (float) width / viewportWidth;
+                            }
+                        } else {
+                            scale = initialScale / 100.0f;
+                        }
+                    }
+                    setNewZoomScale(scale, false);
+                    break;
+                case MARK_NODE_INVALID_ID:
+                    nativeMarkNodeInvalid(msg.arg1);
+                    break;
+                case NOTIFY_FOCUS_SET_MSG_ID:
+                    if (mNativeClass != 0) {
+                        nativeNotifyFocusSet(inEditingMode());
+                    }
+                    break;
+                case UPDATE_TEXT_ENTRY_MSG_ID:
+                    // this is sent after finishing resize in WebViewCore. Make 
+                    // sure the text edit box is still on the  screen.
+                    boolean alreadyThere = inEditingMode();
+                    if (alreadyThere && nativeUpdateFocusNode()) {
+                        FocusNode node = mFocusNode;
+                        if (node.mIsTextField || node.mIsTextArea) {
+                            mTextEntry.bringIntoView();
+                        }
+                    }
+                    updateTextEntry();
+                    break;
+                case RECOMPUTE_FOCUS_MSG_ID:
+                    if (mNativeClass != 0) {
+                        nativeRecomputeFocus();
+                    }
+                    break;
+                case INVAL_RECT_MSG_ID: {
+                    Rect r = (Rect)msg.obj;
+                    if (r == null) {
+                        invalidate();
+                    } else {
+                        // we need to scale r from content into view coords,
+                        // which viewInvalidate() does for us
+                        viewInvalidate(r.left, r.top, r.right, r.bottom);
+                    }
+                    break;
+                }
+                case UPDATE_TEXT_ENTRY_ADAPTER:
+                    HashMap data = (HashMap) msg.obj;
+                    if (mTextEntry.isSameTextField(msg.arg1)) {
+                        AutoCompleteAdapter adapter =
+                                (AutoCompleteAdapter) data.get("adapter");
+                        mTextEntry.setAdapterCustom(adapter);
+                    }
+                    break;
+                case UPDATE_CLIPBOARD:
+                    String str = (String) msg.obj;
+                    if (LOGV_ENABLED) {
+                        Log.v(LOGTAG, "UPDATE_CLIPBOARD " + str);
+                    }
+                    try {
+                        IClipboard clip = IClipboard.Stub.asInterface(
+                                ServiceManager.getService("clipboard"));
+                                clip.setClipboardText(str);
+                    } catch (android.os.RemoteException e) {
+                        Log.e(LOGTAG, "Clipboard failed", e);
+                    }
+                    break;
+                case RESUME_WEBCORE_UPDATE:
+                    WebViewCore.resumeUpdate(mWebViewCore);
+                    break;
+
+                case LONG_PRESS_ENTER:
+                    // as this is shared by keydown and trackballdown, reset all
+                    // the states
+                    mGotEnterDown = false;
+                    mTrackballDown = false;
+                    // LONG_PRESS_ENTER is sent as a delayed message. If we
+                    // switch to windows overview, the WebView will be
+                    // temporarily removed from the view system. In that case,
+                    // do nothing.
+                    if (getParent() != null) {
+                        performLongClick();
+                    }
+                    break;
+
+                case WEBCORE_NEED_TOUCH_EVENTS:
+                    mForwardTouchEvents = (msg.arg1 != 0);
+                    break;
+
+                case PREVENT_TOUCH_ID:
+                    if (msg.arg1 == MotionEvent.ACTION_DOWN) {
+                        mPreventDrag = msg.arg2 == 1;
+                        if (mPreventDrag) {
+                            mTouchMode = TOUCH_DONE_MODE;
+                        }
+                    }
+                    break;
+
+                case DISMISS_ZOOM_RING_TUTORIAL:
+                    mZoomRingController.finishZoomTutorial();
+                    break;
+
+                default:
+                    super.handleMessage(msg);
+                    break;
+            }
+        }
+    }
+
+    // Class used to use a dropdown for a <select> element
+    private class InvokeListBox implements Runnable {
+        // Strings for the labels in the listbox.
+        private String[]    mArray;
+        // Array representing whether each item is enabled.
+        private boolean[]   mEnableArray;
+        // Whether the listbox allows multiple selection.
+        private boolean     mMultiple;
+        // Passed in to a list with multiple selection to tell
+        // which items are selected.
+        private int[]       mSelectedArray;
+        // Passed in to a list with single selection to tell 
+        // where the initial selection is.
+        private int         mSelection;
+
+        private Container[] mContainers;
+
+        // Need these to provide stable ids to my ArrayAdapter,
+        // which normally does not have stable ids. (Bug 1250098)
+        private class Container extends Object {
+            String  mString;
+            boolean mEnabled;
+            int     mId;
+
+            public String toString() {
+                return mString;
+            }
+        }
+
+        /**
+         *  Subclass ArrayAdapter so we can disable OptionGroupLabels, 
+         *  and allow filtering.
+         */
+        private class MyArrayListAdapter extends ArrayAdapter<Container> {
+            public MyArrayListAdapter(Context context, Container[] objects, boolean multiple) {
+                super(context, 
+                            multiple ? com.android.internal.R.layout.select_dialog_multichoice :
+                            com.android.internal.R.layout.select_dialog_singlechoice, 
+                            objects);
+            }
+
+            @Override
+            public boolean hasStableIds() {
+                return true;
+            }
+
+            private Container item(int position) {
+                if (position < 0 || position >= getCount()) {
+                    return null;
+                }
+                return (Container) getItem(position);
+            }
+
+            @Override
+            public long getItemId(int position) {
+                Container item = item(position);
+                if (item == null) {
+                    return -1;
+                }
+                return item.mId;
+            }
+
+            @Override
+            public boolean areAllItemsEnabled() {
+                return false;
+            }
+
+            @Override
+            public boolean isEnabled(int position) {
+                Container item = item(position);
+                if (item == null) {
+                    return false;
+                }
+                return item.mEnabled;
+            }
+        }
+
+        private InvokeListBox(String[] array,
+                boolean[] enabled, int[] selected) {
+            mMultiple = true;
+            mSelectedArray = selected;
+
+            int length = array.length;
+            mContainers = new Container[length];
+            for (int i = 0; i < length; i++) {
+                mContainers[i] = new Container();
+                mContainers[i].mString = array[i];
+                mContainers[i].mEnabled = enabled[i];
+                mContainers[i].mId = i;
+            }
+        }
+
+        private InvokeListBox(String[] array, boolean[] enabled, int 
+                selection) {
+            mSelection = selection;
+            mMultiple = false;
+
+            int length = array.length;
+            mContainers = new Container[length];
+            for (int i = 0; i < length; i++) {
+                mContainers[i] = new Container();
+                mContainers[i].mString = array[i];
+                mContainers[i].mEnabled = enabled[i];
+                mContainers[i].mId = i;
+            }
+        }
+
+        public void run() {
+            final ListView listView = (ListView) LayoutInflater.from(mContext)
+                    .inflate(com.android.internal.R.layout.select_dialog, null);
+            final MyArrayListAdapter adapter = new 
+                    MyArrayListAdapter(mContext, mContainers, mMultiple);
+            AlertDialog.Builder b = new AlertDialog.Builder(mContext)
+                    .setView(listView).setCancelable(true)
+                    .setInverseBackgroundForced(true);
+                    
+            if (mMultiple) {
+                b.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
+                    public void onClick(DialogInterface dialog, int which) {
+                        mWebViewCore.sendMessage(
+                                EventHub.LISTBOX_CHOICES, 
+                                adapter.getCount(), 0,
+                                listView.getCheckedItemPositions());
+                    }});
+                b.setNegativeButton(android.R.string.cancel, null);
+            }
+            final AlertDialog dialog = b.create();
+            listView.setAdapter(adapter);
+            listView.setFocusableInTouchMode(true);
+            // There is a bug (1250103) where the checks in a ListView with
+            // multiple items selected are associated with the positions, not
+            // the ids, so the items do not properly retain their checks when 
+            // filtered.  Do not allow filtering on multiple lists until
+            // that bug is fixed.
+            
+            // Disable filter altogether
+            // listView.setTextFilterEnabled(!mMultiple);
+            if (mMultiple) {
+                listView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE);
+                int length = mSelectedArray.length;
+                for (int i = 0; i < length; i++) {
+                    listView.setItemChecked(mSelectedArray[i], true);
+                }
+            } else {
+                listView.setOnItemClickListener(new OnItemClickListener() {
+                    public void onItemClick(AdapterView parent, View v,
+                            int position, long id) {
+                        mWebViewCore.sendMessage(
+                                EventHub.SINGLE_LISTBOX_CHOICE, (int)id, 0);
+                        dialog.dismiss();
+                    }
+                });
+                if (mSelection != -1) {
+                    listView.setSelection(mSelection);
+                    listView.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
+                    listView.setItemChecked(mSelection, true);
+                }
+            }
+            dialog.setOnCancelListener(new DialogInterface.OnCancelListener() {
+                public void onCancel(DialogInterface dialog) {
+                    mWebViewCore.sendMessage(
+                                EventHub.SINGLE_LISTBOX_CHOICE, -2, 0);
+                }
+            });
+            dialog.show();
+        }
+    }
+
+    /*
+     * Request a dropdown menu for a listbox with multiple selection.
+     *
+     * @param array Labels for the listbox.
+     * @param enabledArray  Which positions are enabled.
+     * @param selectedArray Which positions are initally selected.
+     */
+    void requestListBox(String[] array, boolean[]enabledArray, int[]
+            selectedArray) {
+        mPrivateHandler.post(
+                new InvokeListBox(array, enabledArray, selectedArray));
+    }
+
+    /*
+     * Request a dropdown menu for a listbox with single selection or a single
+     * <select> element.
+     *
+     * @param array Labels for the listbox.
+     * @param enabledArray  Which positions are enabled.
+     * @param selection Which position is initally selected.
+     */
+    void requestListBox(String[] array, boolean[]enabledArray, int selection) {
+        mPrivateHandler.post(
+                new InvokeListBox(array, enabledArray, selection));
+    }
+
+    // called by JNI
+    private void sendFinalFocus(int frame, int node, int x, int y) {
+        WebViewCore.FocusData focusData = new WebViewCore.FocusData();
+        focusData.mFrame = frame;
+        focusData.mNode = node;
+        focusData.mX = x;
+        focusData.mY = y;
+        mWebViewCore.sendMessage(EventHub.SET_FINAL_FOCUS, 
+                EventHub.NO_FOCUS_CHANGE_BLOCK, 0, focusData);
+    }
+
+    // called by JNI
+    private void setFocusData(int moveGeneration, int buildGeneration,
+            int frame, int node, int x, int y, boolean ignoreNullFocus) {
+        mFocusData.mMoveGeneration = moveGeneration;
+        mFocusData.mBuildGeneration = buildGeneration;
+        mFocusData.mFrame = frame;
+        mFocusData.mNode = node;
+        mFocusData.mX = x;
+        mFocusData.mY = y;
+        mFocusData.mIgnoreNullFocus = ignoreNullFocus;
+    }
+    
+    // called by JNI
+    private void sendKitFocus() {
+        WebViewCore.FocusData focusData = new WebViewCore.FocusData(mFocusData);
+        mWebViewCore.sendMessage(EventHub.SET_KIT_FOCUS, focusData);
+    }
+
+    // called by JNI
+    private void sendMotionUp(int touchGeneration, int buildGeneration,
+            int frame, int node, int x, int y, int size, boolean isClick,
+            boolean retry) {
+        WebViewCore.TouchUpData touchUpData = new WebViewCore.TouchUpData();
+        touchUpData.mMoveGeneration = touchGeneration;
+        touchUpData.mBuildGeneration = buildGeneration;
+        touchUpData.mSize = size;
+        touchUpData.mIsClick = isClick;
+        touchUpData.mRetry = retry;
+        mFocusData.mFrame = touchUpData.mFrame = frame;
+        mFocusData.mNode = touchUpData.mNode = node;
+        mFocusData.mX = touchUpData.mX = x;
+        mFocusData.mY = touchUpData.mY = y;
+        mWebViewCore.sendMessage(EventHub.TOUCH_UP, touchUpData);
+    }
+
+
+    private int getScaledMaxXScroll() {
+        int width;
+        if (mHeightCanMeasure == false) {
+            width = getViewWidth() / 4;
+        } else {
+            Rect visRect = new Rect();
+            calcOurVisibleRect(visRect);
+            width = visRect.width() / 2;
+        }
+        // FIXME the divisor should be retrieved from somewhere
+        return viewToContent(width);
+    }
+
+    private int getScaledMaxYScroll() {
+        int height;
+        if (mHeightCanMeasure == false) {
+            height = getViewHeight() / 4;
+        } else {
+            Rect visRect = new Rect();
+            calcOurVisibleRect(visRect);
+            height = visRect.height() / 2;
+        }
+        // FIXME the divisor should be retrieved from somewhere
+        // the closest thing today is hard-coded into ScrollView.java
+        // (from ScrollView.java, line 363)   int maxJump = height/2;
+        return viewToContent(height);
+    }
+
+    /**
+     * Called by JNI to invalidate view
+     */
+    private void viewInvalidate() {
+        invalidate();
+    }
+    
+    // return true if the key was handled
+    private boolean navHandledKey(int keyCode, int count, boolean noScroll
+            , long time) {
+        if (mNativeClass == 0) {
+            return false;
+        }
+        mLastFocusTime = time;
+        mLastFocusBounds = nativeGetFocusRingBounds();
+        boolean keyHandled = nativeMoveFocus(keyCode, count, noScroll) == false;
+        if (LOGV_ENABLED) {
+            Log.v(LOGTAG, "navHandledKey mLastFocusBounds=" + mLastFocusBounds
+                    + " mLastFocusTime=" + mLastFocusTime
+                    + " handled=" + keyHandled);
+        }
+        if (keyHandled == false || mHeightCanMeasure == false) {
+            return keyHandled;
+        }
+        Rect contentFocus = nativeGetFocusRingBounds();
+        if (contentFocus.isEmpty()) return keyHandled;
+        Rect viewFocus = contentToView(contentFocus);
+        Rect visRect = new Rect();
+        calcOurVisibleRect(visRect);
+        Rect outset = new Rect(visRect);
+        int maxXScroll = visRect.width() / 2;
+        int maxYScroll = visRect.height() / 2;
+        outset.inset(-maxXScroll, -maxYScroll);
+        if (Rect.intersects(outset, viewFocus) == false) {
+            return keyHandled;
+        }
+        // FIXME: Necessary because ScrollView/ListView do not scroll left/right
+        int maxH = Math.min(viewFocus.right - visRect.right, maxXScroll);
+        if (maxH > 0) {
+            pinScrollBy(maxH, 0, true, 0);
+        } else {
+            maxH = Math.max(viewFocus.left - visRect.left, -maxXScroll);
+            if (maxH < 0) {
+                pinScrollBy(maxH, 0, true, 0);
+            }
+        }
+        if (mLastFocusBounds.isEmpty()) return keyHandled;
+        if (mLastFocusBounds.equals(contentFocus)) return keyHandled;
+        if (LOGV_ENABLED) {
+            Log.v(LOGTAG, "navHandledKey contentFocus=" + contentFocus);
+        }
+        requestRectangleOnScreen(viewFocus);
+        mUserScroll = true;
+        return keyHandled;
+    }
+    
+    /**
+     * Set the background color. It's white by default. Pass
+     * zero to make the view transparent.
+     * @param color   the ARGB color described by Color.java
+     */
+    public void setBackgroundColor(int color) {
+        mBackgroundColor = color;
+        mWebViewCore.sendMessage(EventHub.SET_BACKGROUND_COLOR, color);
+    }
+
+    public void debugDump() {
+        nativeDebugDump();
+        mWebViewCore.sendMessage(EventHub.DUMP_NAVTREE);
+    }
+    
+    /**
+     *  Update our cache with updatedText.
+     *  @param updatedText  The new text to put in our cache.
+     */
+    /* package */ void updateCachedTextfield(String updatedText) {
+        // Also place our generation number so that when we look at the cache
+        // we recognize that it is up to date.
+        nativeUpdateCachedTextfield(updatedText, mTextGeneration);
+    }
+    
+    // Never call this version except by updateCachedTextfield(String) -
+    // we always want to pass in our generation number.
+    private native void     nativeUpdateCachedTextfield(String updatedText, 
+            int generation);
+    private native void     nativeClearFocus(int x, int y);
+    private native void     nativeCreate(int ptr);
+    private native void     nativeDebugDump();
+    private native void     nativeDestroy();
+    private native void     nativeDrawFocusRing(Canvas content);
+    private native void     nativeDrawSelection(Canvas content
+            , int x, int y, boolean extendSelection);
+    private native void     nativeDrawSelectionRegion(Canvas content);
+    private native boolean  nativeUpdateFocusNode();
+    private native Rect     nativeGetFocusRingBounds();
+    private native Rect     nativeGetNavBounds();
+    private native void     nativeInstrumentReport();
+    private native void     nativeMarkNodeInvalid(int node);
+    // return true if the page has been scrolled
+    private native boolean  nativeMotionUp(int x, int y, int slop, boolean isClick);
+    // returns false if it handled the key
+    private native boolean  nativeMoveFocus(int keyCode, int count, 
+            boolean noScroll);
+    private native void     nativeNotifyFocusSet(boolean inEditingMode);
+    private native void     nativeRecomputeFocus();
+    // Like many other of our native methods, you must make sure that
+    // mNativeClass is not null before calling this method.
+    private native void     nativeRecordButtons(boolean focused,
+            boolean pressed, boolean invalidate);
+    private native void     nativeResetFocus();
+    private native void     nativeResetNavClipBounds();
+    private native void     nativeSelectBestAt(Rect rect);
+    private native void     nativeSetFindIsDown();
+    private native void     nativeSetFollowedLink(boolean followed);
+    private native void     nativeSetHeightCanMeasure(boolean measure);
+    private native void     nativeSetNavBounds(Rect rect);
+    private native void     nativeSetNavClipBounds(Rect rect);
+    private native String   nativeImageURI(int x, int y);
+    /**
+     * Returns true if the native focus nodes says it wants to handle key events
+     * (ala plugins). This can only be called if mNativeClass is non-zero!
+     */
+    private native boolean  nativeFocusNodeWantsKeyEvents();
+    private native void     nativeMoveSelection(int x, int y
+            , boolean extendSelection);
+    private native Region   nativeGetSelection();
+
+    private native void nativeDumpDisplayTree(String urlOrNull);
+}
diff --git a/core/java/android/webkit/WebViewClient.java b/core/java/android/webkit/WebViewClient.java
new file mode 100644
index 0000000..a185779
--- /dev/null
+++ b/core/java/android/webkit/WebViewClient.java
@@ -0,0 +1,206 @@
+/*
+ * 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 android.webkit;
+
+import android.graphics.Bitmap;
+import android.net.http.SslError;
+import android.os.Message;
+import android.view.KeyEvent;
+
+public class WebViewClient {
+
+    /**
+     * Give the host application a chance to take over the control when a new
+     * url is about to be loaded in the current WebView. If WebViewClient is not
+     * provided, by default WebView will ask Activity Manager to choose the
+     * proper handler for the url. If WebViewClient is provided, return true
+     * means the host application handles the url, while return false means the
+     * current WebView handles the url.
+     * 
+     * @param view The WebView that is initiating the callback.
+     * @param url The url to be loaded.
+     * @return True if the host application wants to leave the current WebView
+     *         and handle the url itself, otherwise return false.
+     */
+    public boolean shouldOverrideUrlLoading(WebView view, String url) {
+        return false;
+    }
+
+    /**
+     * Notify the host application that a page has started loading. This method
+     * is called once for each main frame load so a page with iframes or
+     * framesets will call onPageStarted one time for the main frame. This also
+     * means that onPageStarted will not be called when the contents of an
+     * embedded frame changes, i.e. clicking a link whose target is an iframe.
+     * 
+     * @param view The WebView that is initiating the callback.
+     * @param url The url to be loaded.
+     * @param favicon The favicon for this page if it already exists in the
+     *            database.
+     */
+    public void onPageStarted(WebView view, String url, Bitmap favicon) {
+    }
+
+    /**
+     * Notify the host application that a page has finished loading. This method
+     * is called only for main frame. When onPageFinished() is called, the
+     * rendering picture may not be updated yet. To get the notification for the
+     * new Picture, use {@link WebView.PictureListener#onNewPicture}.
+     * 
+     * @param view The WebView that is initiating the callback.
+     * @param url The url of the page.
+     */
+    public void onPageFinished(WebView view, String url) {
+    }
+
+    /**
+     * Notify the host application that the WebView will load the resource
+     * specified by the given url.
+     * 
+     * @param view The WebView that is initiating the callback.
+     * @param url The url of the resource the WebView will load.
+     */
+    public void onLoadResource(WebView view, String url) {
+    }
+
+    /**
+     * Notify the host application that there have been an excessive number of
+     * HTTP redirects. As the host application if it would like to continue
+     * trying to load the resource. The default behavior is to send the cancel
+     * message.
+     * 
+     * @param view The WebView that is initiating the callback.
+     * @param cancelMsg The message to send if the host wants to cancel
+     * @param continueMsg The message to send if the host wants to continue
+     */
+    public void onTooManyRedirects(WebView view, Message cancelMsg,
+            Message continueMsg) {
+        cancelMsg.sendToTarget();
+    }
+
+    /**
+     * Report an error to an activity. These errors come up from WebCore, and
+     * are network errors.
+     * 
+     * @param view The WebView that is initiating the callback.
+     * @param errorCode The HTTP error code.
+     * @param description A String description.
+     * @param failingUrl The url that failed.
+     */
+    public void onReceivedError(WebView view, int errorCode,
+            String description, String failingUrl) {
+    }
+
+    /**
+     * As the host application if the browser should resend data as the
+     * requested page was a result of a POST. The default is to not resend the
+     * data.
+     * 
+     * @param view The WebView that is initiating the callback.
+     * @param dontResend The message to send if the browser should not resend
+     * @param resend The message to send if the browser should resend data
+     */
+    public void onFormResubmission(WebView view, Message dontResend,
+            Message resend) {
+        dontResend.sendToTarget();
+    }
+
+    /**
+     * Notify the host application to update its visited links database.
+     * 
+     * @param view The WebView that is initiating the callback.
+     * @param url The url being visited.
+     * @param isReload True if this url is being reloaded.
+     */
+    public void doUpdateVisitedHistory(WebView view, String url,
+            boolean isReload) {
+    }
+
+    /**
+     * Notify the host application to handle a ssl certificate error request
+     * (display the error to the user and ask whether to proceed or not). The
+     * host application has to call either handler.cancel() or handler.proceed()
+     * as the connection is suspended and waiting for the response. The default
+     * behavior is to cancel the load.
+     * 
+     * @param view The WebView that is initiating the callback.
+     * @param handler An SslErrorHandler object that will handle the user's
+     *            response.
+     * @param error The SSL error object.
+     * @hide - hide this because it contains a parameter of type SslError,
+     * which is located in a hidden package.
+     */
+    public void onReceivedSslError(WebView view, SslErrorHandler handler,
+            SslError error) {
+        handler.cancel();
+    }
+
+    /**
+     * Notify the host application to handle an authentication request. The
+     * default behavior is to cancel the request.
+     * 
+     * @param view The WebView that is initiating the callback.
+     * @param handler The HttpAuthHandler that will handle the user's response.
+     * @param host The host requiring authentication.
+     * @param realm A description to help store user credentials for future
+     *            visits.
+     */
+    public void onReceivedHttpAuthRequest(WebView view,
+            HttpAuthHandler handler, String host, String realm) {
+        handler.cancel();
+    }
+
+    /**
+     * Give the host application a chance to handle the key event synchronously.
+     * e.g. menu shortcut key events need to be filtered this way. If return
+     * true, WebView will not handle the key event. If return false, WebView
+     * will always handle the key event, so none of the super in the view chain
+     * will see the key event. The default behavior returns false.
+     * 
+     * @param view The WebView that is initiating the callback.
+     * @param event The key event.
+     * @return True if the host application wants to handle the key event
+     *         itself, otherwise return false
+     */
+    public boolean shouldOverrideKeyEvent(WebView view, KeyEvent event) {
+        return false;
+    }
+
+    /**
+     * Notify the host application that a key was not handled by the WebView.
+     * Except system keys, WebView always consumes the keys in the normal flow
+     * or if shouldOverrideKeyEvent returns true. This is called asynchronously
+     * from where the key is dispatched. It gives the host application an chance
+     * to handle the unhandled key events.
+     * 
+     * @param view The WebView that is initiating the callback.
+     * @param event The key event.
+     */
+    public void onUnhandledKeyEvent(WebView view, KeyEvent event) {
+    }
+
+    /**
+     * Notify the host application that the scale applied to the WebView has
+     * changed.
+     * 
+     * @param view he WebView that is initiating the callback.
+     * @param oldScale The old scale factor
+     * @param newScale The new scale factor
+     */
+    public void onScaleChanged(WebView view, float oldScale, float newScale) {
+    }
+}
diff --git a/core/java/android/webkit/WebViewCore.java b/core/java/android/webkit/WebViewCore.java
new file mode 100644
index 0000000..6ab088d
--- /dev/null
+++ b/core/java/android/webkit/WebViewCore.java
@@ -0,0 +1,1674 @@
+/*
+ * Copyright (C) 2007 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 android.webkit;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.DrawFilter;
+import android.graphics.Paint;
+import android.graphics.PaintFlagsDrawFilter;
+import android.graphics.Picture;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.graphics.Region;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.Process;
+import android.util.Config;
+import android.util.Log;
+import android.util.SparseBooleanArray;
+import android.view.KeyEvent;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+
+import junit.framework.Assert;
+
+final class WebViewCore {
+
+    private static final String LOGTAG = "webcore";
+    static final boolean DEBUG = false;
+    static final boolean LOGV_ENABLED = DEBUG ? Config.LOGD : Config.LOGV;
+
+    static {
+        // Load libwebcore during static initialization. This happens in the
+        // zygote process so it will be shared read-only across all app
+        // processes.
+        System.loadLibrary("webcore");
+    }
+
+    /*
+     * WebViewCore always executes in the same thread as the native webkit.
+     */
+
+    // The WebView that corresponds to this WebViewCore.
+    private WebView mWebView;
+    // Proxy for handling callbacks from native code
+    private final CallbackProxy mCallbackProxy;
+    // Settings object for maintaining all settings
+    private final WebSettings mSettings;
+    // Context for initializing the BrowserFrame with the proper assets.
+    private final Context mContext;
+    // The pointer to a native view object.
+    private int mNativeClass;
+    // The BrowserFrame is an interface to the native Frame component.
+    private BrowserFrame mBrowserFrame;
+
+    /*
+     * range is from 200 to 10,000. 0 is a special value means device-width. -1
+     * means undefined.
+     */
+    private int mViewportWidth = -1;
+
+    /*
+     * range is from 200 to 10,000. 0 is a special value means device-height. -1
+     * means undefined.
+     */
+    private int mViewportHeight = -1;
+
+    /*
+     * scale in percent, range is from 1 to 1000. 0 means undefined.
+     */
+    private int mViewportInitialScale = 0;
+
+    /*
+     * scale in percent, range is from 1 to 1000. 0 means undefined.
+     */
+    private int mViewportMinimumScale = 0;
+
+    /*
+     * scale in percent, range is from 1 to 1000. 0 means undefined.
+     */
+    private int mViewportMaximumScale = 0;
+
+    private boolean mViewportUserScalable = true;
+    
+    private int mRestoredScale = 100;
+    private int mRestoredX = 0;
+    private int mRestoredY = 0;
+
+    private int mWebkitScrollX = 0;
+    private int mWebkitScrollY = 0;
+
+    // The thread name used to identify the WebCore thread and for use in
+    // debugging other classes that require operation within the WebCore thread.
+    /* package */ static final String THREAD_NAME = "WebViewCoreThread";
+
+    public WebViewCore(Context context, WebView w, CallbackProxy proxy) {
+        // No need to assign this in the WebCore thread.
+        mCallbackProxy = proxy;
+        mWebView = w;
+        // This context object is used to initialize the WebViewCore during
+        // subwindow creation.
+        mContext = context;
+
+        // We need to wait for the initial thread creation before sending
+        // a message to the WebCore thread.
+        // XXX: This is the only time the UI thread will wait for the WebCore
+        // thread!
+        synchronized (WebViewCore.class) {
+            if (sWebCoreHandler == null) {
+                // Create a global thread and start it.
+                Thread t = new Thread(new WebCoreThread());
+                t.setName(THREAD_NAME);
+                t.start();
+                try {
+                    WebViewCore.class.wait();
+                } catch (InterruptedException e) {
+                    Log.e(LOGTAG, "Caught exception while waiting for thread " +
+                           "creation.");
+                    Log.e(LOGTAG, Log.getStackTraceString(e));
+                }
+            }
+        }
+        // Create an EventHub to handle messages before and after the thread is
+        // ready.
+        mEventHub = new EventHub();
+        // Create a WebSettings object for maintaining all settings
+        mSettings = new WebSettings(mContext);
+        // The WebIconDatabase needs to be initialized within the UI thread so
+        // just request the instance here.
+        WebIconDatabase.getInstance();
+        // Send a message to initialize the WebViewCore.
+        Message init = sWebCoreHandler.obtainMessage(
+                WebCoreThread.INITIALIZE, this);
+        sWebCoreHandler.sendMessage(init);
+    }
+
+    /* Initialize private data within the WebCore thread.
+     */
+    private void initialize() {
+        /* Initialize our private BrowserFrame class to handle all
+         * frame-related functions. We need to create a new view which
+         * in turn creates a C level FrameView and attaches it to the frame.
+         */
+        mBrowserFrame = new BrowserFrame(mContext, this, mCallbackProxy,
+                mSettings);
+        // Sync the native settings and also create the WebCore thread handler.
+        mSettings.syncSettingsAndCreateHandler(mBrowserFrame);
+        // Create the handler and transfer messages for the IconDatabase
+        WebIconDatabase.getInstance().createHandler();
+        // The transferMessages call will transfer all pending messages to the
+        // WebCore thread handler.
+        mEventHub.transferMessages();
+
+        // Send a message back to WebView to tell it that we have set up the
+        // WebCore thread.
+        if (mWebView != null) {
+            Message.obtain(mWebView.mPrivateHandler,
+                    WebView.WEBCORE_INITIALIZED_MSG_ID,
+                    mNativeClass, 0).sendToTarget();
+        }
+
+    }
+
+    /* Handle the initialization of WebViewCore during subwindow creation. This
+     * method is called from the WebCore thread but it is called before the
+     * INITIALIZE message can be handled.
+     */
+    /* package */ void initializeSubwindow() {
+        // Go ahead and initialize the core components.
+        initialize();
+        // Remove the INITIALIZE method so we don't try to initialize twice.
+        sWebCoreHandler.removeMessages(WebCoreThread.INITIALIZE, this);
+    }
+
+    /* Get the BrowserFrame component. This is used for subwindow creation and
+     * is called only from BrowserFrame in the WebCore thread. */
+    /* package */ BrowserFrame getBrowserFrame() {
+        return mBrowserFrame;
+    }
+
+    //-------------------------------------------------------------------------
+    // Common methods
+    //-------------------------------------------------------------------------
+
+    /**
+     * Causes all timers to pause. This applies to all WebViews in the current
+     * app process.
+     */
+    public static void pauseTimers() {
+        if (BrowserFrame.sJavaBridge == null) {
+            throw new IllegalStateException(
+                    "No WebView has been created in this process!");
+        }
+        BrowserFrame.sJavaBridge.pause();
+    }
+
+    /**
+     * Resume all timers. This applies to all WebViews in the current process.
+     */
+    public static void resumeTimers() {
+        if (BrowserFrame.sJavaBridge == null) {
+            throw new IllegalStateException(
+                    "No WebView has been created in this process!");
+        }
+        BrowserFrame.sJavaBridge.resume();
+    }
+
+    public WebSettings getSettings() {
+        return mSettings;
+    }
+
+    /**
+     * Invoke a javascript alert.
+     * @param message The message displayed in the alert.
+     */
+    protected void jsAlert(String url, String message) {
+        mCallbackProxy.onJsAlert(url, message);
+    }
+
+    /**
+     * Invoke a javascript confirm dialog.
+     * @param message The message displayed in the dialog.
+     * @return True if the user confirmed or false if the user cancelled.
+     */
+    protected boolean jsConfirm(String url, String message) {
+        return mCallbackProxy.onJsConfirm(url, message);
+    }
+
+    /**
+     * Invoke a javascript prompt dialog.
+     * @param message The message to be displayed in the dialog.
+     * @param defaultValue The default value in the prompt input.
+     * @return The input from the user or null to indicate the user cancelled
+     *         the dialog.
+     */
+    protected String jsPrompt(String url, String message, String defaultValue) {
+        return mCallbackProxy.onJsPrompt(url, message, defaultValue);
+    }
+
+    /**
+     * Invoke a javascript before unload dialog.
+     * @param url The url that is requesting the dialog.
+     * @param message The message displayed in the dialog.
+     * @return True if the user confirmed or false if the user cancelled. False
+     *         will cancel the navigation.
+     */
+    protected boolean jsUnload(String url, String message) {
+        return mCallbackProxy.onJsBeforeUnload(url, message);
+    }
+
+    //-------------------------------------------------------------------------
+    // JNI methods
+    //-------------------------------------------------------------------------
+
+    static native String nativeFindAddress(String addr);
+
+    /**
+     * Rebuild the nav cache if the dom changed.
+     */
+    private native void nativeCheckNavCache();
+
+    /**
+     * Empty the picture set.
+     */
+    private native void nativeClearContent();
+    
+    /**
+     * Create a flat picture from the set of pictures.
+     */
+    private native void nativeCopyContentToPicture(Picture picture);
+   
+    /**
+     * Draw the picture set with a background color. Returns true
+     * if some individual picture took too long to draw and can be 
+     * split into parts. Called from the UI thread.
+     */
+    private native boolean nativeDrawContent(Canvas canvas, int color);
+    
+    /**
+     * Redraw a portion of the picture set. The Point wh returns the
+     * width and height of the overall picture.
+     */
+    private native boolean nativeRecordContent(Region invalRegion, Point wh);
+    
+    /**
+     * Splits slow parts of the picture set. Called from the webkit
+     * thread after nativeDrawContent returns true.
+     */
+    private native void nativeSplitContent();
+
+    private native boolean nativeKey(int keyCode, int unichar,
+            int repeatCount, boolean isShift, boolean isAlt, boolean isDown);
+
+    private native boolean nativeClick();
+
+    private native void nativeSendListBoxChoices(boolean[] choices, int size);
+
+    private native void nativeSendListBoxChoice(int choice);
+
+    /*  Tell webkit what its width and height are, for the purposes
+        of layout/line-breaking. These coordinates are in document space,
+        which is the same as View coords unless we have zoomed the document
+        (see nativeSetZoom).
+        screenWidth is used by layout to wrap column around. If viewport uses
+        fixed size, screenWidth can be different from width with zooming.
+        should this be called nativeSetViewPortSize?
+    */
+    private native void nativeSetSize(int width, int height, int screenWidth,
+            float scale, int realScreenWidth, int screenHeight);
+
+    private native int nativeGetContentMinPrefWidth();
+    
+    // Start: functions that deal with text editing
+    private native void nativeReplaceTextfieldText(int frame, int node, int x, 
+            int y, int oldStart, int oldEnd, String replace, int newStart, 
+            int newEnd);
+
+    private native void passToJs(int frame, int node, int x, int y, int gen,
+            String currentText, int keyCode, int keyValue, boolean down,
+            boolean cap, boolean fn, boolean sym);
+
+    private native void nativeSaveDocumentState(int frame);
+
+    private native void nativeSetFinalFocus(int framePtr, int nodePtr, int x,
+            int y, boolean block);
+
+    private native void nativeSetKitFocus(int moveGeneration,
+            int buildGeneration, int framePtr, int nodePtr, int x, int y,
+            boolean ignoreNullFocus);
+
+    private native String nativeRetrieveHref(int framePtr, int nodePtr);
+    
+    private native void nativeTouchUp(int touchGeneration, 
+            int buildGeneration, int framePtr, int nodePtr, int x, int y, 
+            int size, boolean isClick, boolean retry);
+
+    private native boolean nativeHandleTouchEvent(int action, int x, int y);
+
+    private native void nativeUnblockFocus();
+    
+    private native void nativeUpdateFrameCache();
+    
+    private native void nativeSetSnapAnchor(int x, int y);
+    
+    private native void nativeSnapToAnchor();
+    
+    private native void nativeSetBackgroundColor(int color);
+    
+    private native void nativeDumpDomTree(boolean useFile);
+
+    private native void nativeDumpRenderTree(boolean useFile);
+
+    private native void nativeDumpNavTree();
+
+    private native void nativeRefreshPlugins(boolean reloadOpenPages);
+    
+    /**
+     *  Delete text from start to end in the focused textfield. If there is no
+     *  focus, or if start == end, silently fail.  If start and end are out of 
+     *  order, swap them.
+     *  @param  start   Beginning of selection to delete.
+     *  @param  end     End of selection to delete.
+     */
+    private native void nativeDeleteSelection(int frame, int node, int x, int y,
+        int start, int end);
+
+    /**
+     *  Set the selection to (start, end) in the focused textfield. If start and
+     *  end are out of order, swap them.
+     *  @param  start   Beginning of selection.
+     *  @param  end     End of selection.
+     */
+    private native void nativeSetSelection(int frame, int node, int x, int y,
+        int start, int end);
+
+    private native String nativeGetSelection(Region sel);
+    
+    // Register a scheme to be treated as local scheme so that it can access
+    // local asset files for resources
+    private native void nativeRegisterURLSchemeAsLocal(String scheme);
+
+    // EventHub for processing messages
+    private final EventHub mEventHub;
+    // WebCore thread handler
+    private static Handler sWebCoreHandler;
+    // Class for providing Handler creation inside the WebCore thread.
+    private static class WebCoreThread implements Runnable {
+        // Message id for initializing a new WebViewCore.
+        private static final int INITIALIZE = 0;
+        private static final int REDUCE_PRIORITY = 1;
+        private static final int RESUME_PRIORITY = 2;
+        private static final int CACHE_TICKER = 3;
+        private static final int BLOCK_CACHE_TICKER = 4;
+        private static final int RESUME_CACHE_TICKER = 5;
+
+        private static final int CACHE_TICKER_INTERVAL = 60 * 1000; // 1 minute
+
+        private static boolean mCacheTickersBlocked = true;
+
+        public void run() {
+            Looper.prepare();
+            Assert.assertNull(sWebCoreHandler);
+            synchronized (WebViewCore.class) {
+                sWebCoreHandler = new Handler() {
+                    @Override
+                    public void handleMessage(Message msg) {
+                        switch (msg.what) {
+                            case INITIALIZE:
+                                WebViewCore core = (WebViewCore) msg.obj;
+                                core.initialize();
+                                break;
+
+                            case REDUCE_PRIORITY:
+                                // 3 is an adjustable number.
+                                Process.setThreadPriority(
+                                        Process.THREAD_PRIORITY_DEFAULT + 3 *
+                                        Process.THREAD_PRIORITY_LESS_FAVORABLE);
+                                break;
+
+                            case RESUME_PRIORITY:
+                                Process.setThreadPriority(
+                                        Process.THREAD_PRIORITY_DEFAULT);
+                                break;
+
+                            case CACHE_TICKER:
+                                if (!mCacheTickersBlocked) {
+                                    CacheManager.endCacheTransaction();
+                                    CacheManager.startCacheTransaction();
+                                    sendMessageDelayed(
+                                            obtainMessage(CACHE_TICKER), 
+                                            CACHE_TICKER_INTERVAL);
+                                }
+                                break;
+
+                            case BLOCK_CACHE_TICKER:
+                                if (CacheManager.endCacheTransaction()) {
+                                    mCacheTickersBlocked = true;
+                                }
+                                break;
+
+                            case RESUME_CACHE_TICKER:
+                                if (CacheManager.startCacheTransaction()) {
+                                    mCacheTickersBlocked = false;
+                                }
+                                break;
+                        }
+                    }
+                };
+                WebViewCore.class.notify();
+            }
+            Looper.loop();
+        }
+    }
+
+    static class FocusData {
+        FocusData() {}
+        FocusData(FocusData d) {
+            mMoveGeneration = d.mMoveGeneration;
+            mBuildGeneration = d.mBuildGeneration;
+            mFrame = d.mFrame;
+            mNode = d.mNode;
+            mX = d.mX;
+            mY = d.mY;
+            mIgnoreNullFocus = d.mIgnoreNullFocus;
+        }
+        int mMoveGeneration;
+        int mBuildGeneration;
+        int mFrame;
+        int mNode;
+        int mX;
+        int mY;
+        boolean mIgnoreNullFocus;
+    }
+
+    static class TouchUpData {
+        int mMoveGeneration;
+        int mBuildGeneration;
+        int mFrame;
+        int mNode;
+        int mX;
+        int mY;
+        int mSize;
+        boolean mIsClick;
+        boolean mRetry;
+    }
+
+    static class TouchEventData {
+        int mAction;    // MotionEvent.getAction()
+        int mX;
+        int mY;
+    }
+
+        static final String[] HandlerDebugString = {
+            "LOAD_URL", // = 100;
+            "STOP_LOADING", // = 101;
+            "RELOAD", // = 102;
+            "KEY_DOWN", // = 103;
+            "KEY_UP", // = 104;
+            "VIEW_SIZE_CHANGED", // = 105;
+            "GO_BACK_FORWARD", // = 106;
+            "SET_SCROLL_OFFSET", // = 107;
+            "RESTORE_STATE", // = 108;
+            "PAUSE_TIMERS", // = 109;
+            "RESUME_TIMERS", // = 110;
+            "CLEAR_CACHE", // = 111;
+            "CLEAR_HISTORY", // = 112;
+            "SET_SELECTION", // = 113;
+            "REPLACE_TEXT", // = 114;
+            "PASS_TO_JS", // = 115;
+            "SET_GLOBAL_BOUNDS", // = 116;
+            "UPDATE_CACHE_AND_TEXT_ENTRY", // = 117;
+            "CLICK", // = 118;
+            "119",
+            "DOC_HAS_IMAGES", // = 120;
+            "SET_SNAP_ANCHOR", // = 121;
+            "DELETE_SELECTION", // = 122;
+            "LISTBOX_CHOICES", // = 123;
+            "SINGLE_LISTBOX_CHOICE", // = 124;
+            "125",
+            "SET_BACKGROUND_COLOR", // = 126;
+            "UNBLOCK_FOCUS", // = 127;
+            "SAVE_DOCUMENT_STATE", // = 128;
+            "GET_SELECTION", // = 129;
+            "WEBKIT_DRAW", // = 130;
+            "SYNC_SCROLL", // = 131;
+            "REFRESH_PLUGINS", // = 132;
+            "SPLIT_PICTURE_SET", // = 133;
+            "CLEAR_CONTENT", // = 134;
+            "SET_FINAL_FOCUS", // = 135;
+            "SET_KIT_FOCUS", // = 136;
+            "REQUEST_FOCUS_HREF", // = 137;
+            "ADD_JS_INTERFACE", // = 138;
+            "LOAD_DATA", // = 139;
+            "TOUCH_UP", // = 140;
+            "TOUCH_EVENT", // = 141;
+        };
+
+    class EventHub {
+        // Message Ids
+        static final int LOAD_URL = 100;
+        static final int STOP_LOADING = 101;
+        static final int RELOAD = 102;
+        static final int KEY_DOWN = 103;
+        static final int KEY_UP = 104;
+        static final int VIEW_SIZE_CHANGED = 105;
+        static final int GO_BACK_FORWARD = 106;
+        static final int SET_SCROLL_OFFSET = 107;
+        static final int RESTORE_STATE = 108;
+        static final int PAUSE_TIMERS = 109;
+        static final int RESUME_TIMERS = 110;
+        static final int CLEAR_CACHE = 111;
+        static final int CLEAR_HISTORY = 112;
+        static final int SET_SELECTION = 113;
+        static final int REPLACE_TEXT = 114;
+        static final int PASS_TO_JS = 115;
+        static final int SET_GLOBAL_BOUNDS = 116;
+        static final int UPDATE_CACHE_AND_TEXT_ENTRY = 117;
+        static final int CLICK = 118;
+        static final int DOC_HAS_IMAGES = 120;
+        static final int SET_SNAP_ANCHOR = 121;
+        static final int DELETE_SELECTION = 122;
+        static final int LISTBOX_CHOICES = 123;
+        static final int SINGLE_LISTBOX_CHOICE = 124;
+        static final int SET_BACKGROUND_COLOR = 126;
+        static final int UNBLOCK_FOCUS = 127;
+        static final int SAVE_DOCUMENT_STATE = 128;
+        static final int GET_SELECTION = 129;
+        static final int WEBKIT_DRAW = 130;
+        static final int SYNC_SCROLL = 131;
+        static final int REFRESH_PLUGINS = 132;
+        static final int SPLIT_PICTURE_SET = 133;
+        static final int CLEAR_CONTENT = 134;
+        
+        // UI nav messages
+        static final int SET_FINAL_FOCUS = 135;
+        static final int SET_KIT_FOCUS = 136;
+        static final int REQUEST_FOCUS_HREF = 137;
+        static final int ADD_JS_INTERFACE = 138;
+        static final int LOAD_DATA = 139;
+
+        // motion
+        static final int TOUCH_UP = 140;
+        // message used to pass UI touch events to WebCore
+        static final int TOUCH_EVENT = 141;
+
+        // Network-based messaging
+        static final int CLEAR_SSL_PREF_TABLE = 150;
+
+        // Test harness messages
+        static final int REQUEST_EXT_REPRESENTATION = 160;
+        static final int REQUEST_DOC_AS_TEXT = 161;
+
+        // debugging
+        static final int DUMP_DOMTREE = 170;
+        static final int DUMP_RENDERTREE = 171;
+        static final int DUMP_NAVTREE = 172;
+
+        // private message ids
+        private static final int DESTROY =     200;
+        
+        // flag values passed to message SET_FINAL_FOCUS
+        static final int NO_FOCUS_CHANGE_BLOCK = 0;
+        static final int BLOCK_FOCUS_CHANGE_UNTIL_KEY_UP = 1;
+
+        // Private handler for WebCore messages.
+        private Handler mHandler;
+        // Message queue for containing messages before the WebCore thread is
+        // ready.
+        private ArrayList<Message> mMessages = new ArrayList<Message>();
+        // Flag for blocking messages. This is used during DESTROY to avoid
+        // posting more messages to the EventHub or to WebView's event handler.
+        private boolean mBlockMessages;
+
+        private int mTid;
+        private int mSavedPriority;
+
+        /**
+         * Prevent other classes from creating an EventHub.
+         */
+        private EventHub() {}
+
+        /**
+         * Transfer all messages to the newly created webcore thread handler.
+         */
+        private void transferMessages() {
+            mTid = Process.myTid();
+            mSavedPriority = Process.getThreadPriority(mTid);
+
+            mHandler = new Handler() {
+                @Override
+                public void handleMessage(Message msg) {
+                    if (LOGV_ENABLED) {
+                        Log.v(LOGTAG, msg.what < LOAD_URL || msg.what 
+                                > TOUCH_EVENT ? Integer.toString(msg.what)
+                                : HandlerDebugString[msg.what - LOAD_URL]);
+                    }
+                    switch (msg.what) {
+                        case WEBKIT_DRAW:
+                            webkitDraw();
+                            break;
+
+                        case DESTROY:
+                            // Time to take down the world. Cancel all pending
+                            // loads and destroy the native view and frame.
+                            mBrowserFrame.destroy();
+                            mBrowserFrame = null;
+                            mNativeClass = 0;
+                            break;
+
+                        case LOAD_URL:
+                            loadUrl((String) msg.obj);
+                            break;
+
+                        case LOAD_DATA:
+                            HashMap loadParams = (HashMap) msg.obj;
+                            String baseUrl = (String) loadParams.get("baseUrl");
+                            if (baseUrl != null) {
+                                int i = baseUrl.indexOf(':');
+                                if (i > 0) {
+                                    /*
+                                     * In 1.0, {@link
+                                     * WebView#loadDataWithBaseURL} can access
+                                     * local asset files as long as the data is
+                                     * valid. In the new WebKit, the restriction
+                                     * is tightened. To be compatible with 1.0,
+                                     * we automatically add the scheme of the
+                                     * baseUrl for local access as long as it is
+                                     * not http(s)/ftp(s)/about/javascript
+                                     */ 
+                                    String scheme = baseUrl.substring(0, i);
+                                    if (!scheme.startsWith("http") &&
+                                            !scheme.startsWith("ftp") &&
+                                            !scheme.startsWith("about") &&
+                                            !scheme.startsWith("javascript")) {
+                                        nativeRegisterURLSchemeAsLocal(scheme);
+                                    }
+                                }
+                            }
+                            mBrowserFrame.loadData(baseUrl,
+                                    (String) loadParams.get("data"),
+                                    (String) loadParams.get("mimeType"),
+                                    (String) loadParams.get("encoding"),
+                                    (String) loadParams.get("failUrl"));
+                            break;
+
+                        case STOP_LOADING:
+                            // If the WebCore has committed the load, but not 
+                            // finished the first layout yet, we need to set 
+                            // first layout done to trigger the interpreted side sync 
+                            // up with native side
+                            if (mBrowserFrame.committed()
+                                    && !mBrowserFrame.firstLayoutDone()) {
+                                mBrowserFrame.didFirstLayout();
+                            }
+                            // Do this after syncing up the layout state.
+                            stopLoading();
+                            break;
+
+                        case RELOAD:
+                            mBrowserFrame.reload(false);
+                            break;
+
+                        case KEY_DOWN:
+                            key((KeyEvent) msg.obj, true);
+                            break;
+
+                        case KEY_UP:
+                            key((KeyEvent) msg.obj, false);
+                            break;
+
+                        case CLICK:
+                            nativeClick();
+                            break;
+
+                        case VIEW_SIZE_CHANGED:
+                            viewSizeChanged(msg.arg1, msg.arg2,
+                                    ((Integer) msg.obj).intValue());
+                            break;
+
+                        case SET_SCROLL_OFFSET:
+                            // note: these are in document coordinates
+                            // (inv-zoom)
+                            nativeSetScrollOffset(msg.arg1, msg.arg2);
+                            break;
+                            
+                        case SET_GLOBAL_BOUNDS:
+                            Rect r = (Rect) msg.obj;
+                            nativeSetGlobalBounds(r.left, r.top, r.width(),
+                                r.height());
+                            break;
+
+                        case GO_BACK_FORWARD:
+                            // If it is a standard load and the load is not
+                            // committed yet, we interpret BACK as RELOAD
+                            if (!mBrowserFrame.committed() && msg.arg1 == -1 &&
+                                    (mBrowserFrame.loadType() == 
+                                    BrowserFrame.FRAME_LOADTYPE_STANDARD)) {
+                                mBrowserFrame.reload(true);
+                            } else {
+                                mBrowserFrame.goBackOrForward(msg.arg1);
+                            }
+                            break;
+
+                        case RESTORE_STATE:
+                            stopLoading();
+                            restoreState(msg.arg1);
+                            break;
+
+                        case PAUSE_TIMERS:
+                            mSavedPriority = Process.getThreadPriority(mTid);
+                            Process.setThreadPriority(mTid,
+                                    Process.THREAD_PRIORITY_BACKGROUND);
+                            pauseTimers();
+                            if (CacheManager.disableTransaction()) {
+                                WebCoreThread.mCacheTickersBlocked = true;
+                                sWebCoreHandler.removeMessages(
+                                        WebCoreThread.CACHE_TICKER);
+                            }
+                            break;
+
+                        case RESUME_TIMERS:
+                            Process.setThreadPriority(mTid, mSavedPriority);
+                            resumeTimers();
+                            if (CacheManager.enableTransaction()) {
+                                WebCoreThread.mCacheTickersBlocked = false;
+                                sWebCoreHandler.sendMessageDelayed(
+                                        sWebCoreHandler.obtainMessage(
+                                        WebCoreThread.CACHE_TICKER),
+                                        WebCoreThread.CACHE_TICKER_INTERVAL);
+                            }
+                            break;
+
+                        case CLEAR_CACHE:
+                            mBrowserFrame.clearCache();
+                            if (msg.arg1 == 1) {
+                                CacheManager.removeAllCacheFiles();
+                            }
+                            break;
+
+                        case CLEAR_HISTORY:
+                            mCallbackProxy.getBackForwardList().
+                                    close(mBrowserFrame.mNativeFrame);
+                            break;
+
+                        case REPLACE_TEXT: 
+                            HashMap jMap = (HashMap) msg.obj;
+                            FocusData fData = (FocusData) jMap.get("focusData");
+                            String replace = (String) jMap.get("replace");
+                            int newStart = 
+                                    ((Integer) jMap.get("start")).intValue();
+                            int newEnd = 
+                                    ((Integer) jMap.get("end")).intValue();
+                            nativeReplaceTextfieldText(fData.mFrame,
+                                    fData.mNode, fData.mX, fData.mY, msg.arg1,
+                                    msg.arg2, replace, newStart, newEnd);
+                            break;
+
+                        case PASS_TO_JS: {
+                            HashMap jsMap = (HashMap) msg.obj;
+                            FocusData fDat = (FocusData) jsMap.get("focusData");
+                            KeyEvent evt = (KeyEvent) jsMap.get("event");
+                            int keyCode = evt.getKeyCode();
+                            int keyValue = evt.getUnicodeChar();
+                            int generation = msg.arg1;
+                            passToJs(fDat.mFrame, fDat.mNode, fDat.mX, fDat.mY,
+                                    generation,
+                                    (String) jsMap.get("currentText"),
+                                    keyCode,
+                                    keyValue,
+                                    evt.isDown(),
+                                    evt.isShiftPressed(), evt.isAltPressed(),
+                                    evt.isSymPressed());
+                            break;
+                        }
+
+                        case SAVE_DOCUMENT_STATE: {
+                            FocusData fDat = (FocusData) msg.obj;
+                            nativeSaveDocumentState(fDat.mFrame);
+                            break;
+                        }
+
+                        case CLEAR_SSL_PREF_TABLE:
+                            Network.getInstance(mContext)
+                                    .clearUserSslPrefTable();
+                            break;
+
+                        case TOUCH_UP:
+                            TouchUpData touchUpData = (TouchUpData) msg.obj;
+                            nativeTouchUp(touchUpData.mMoveGeneration,
+                                    touchUpData.mBuildGeneration,
+                                    touchUpData.mFrame, touchUpData.mNode,
+                                    touchUpData.mX, touchUpData.mY, 
+                                    touchUpData.mSize, touchUpData.mIsClick,
+                                    touchUpData.mRetry);
+                            break;
+
+                        case TOUCH_EVENT: {
+                            TouchEventData ted = (TouchEventData) msg.obj;
+                            Message.obtain(
+                                    mWebView.mPrivateHandler,
+                                    WebView.PREVENT_TOUCH_ID, ted.mAction,
+                                    nativeHandleTouchEvent(ted.mAction, ted.mX,
+                                            ted.mY) ? 1 : 0).sendToTarget();
+                            break;
+                        }
+
+                        case ADD_JS_INTERFACE:
+                            HashMap map = (HashMap) msg.obj;
+                            Object obj = map.get("object");
+                            String interfaceName = (String)
+                                    map.get("interfaceName");
+                            mBrowserFrame.addJavascriptInterface(obj,
+                                    interfaceName);
+                            break;
+
+                        case REQUEST_EXT_REPRESENTATION:
+                            mBrowserFrame.externalRepresentation(
+                                    (Message) msg.obj);
+                            break;
+
+                        case REQUEST_DOC_AS_TEXT:
+                            mBrowserFrame.documentAsText((Message) msg.obj);
+                            break;
+
+                        case SET_FINAL_FOCUS:
+                            FocusData finalData = (FocusData) msg.obj;
+                            nativeSetFinalFocus(finalData.mFrame,
+                                     finalData.mNode, finalData.mX, 
+                                     finalData.mY, msg.arg1 
+                                     != EventHub.NO_FOCUS_CHANGE_BLOCK);
+                            break;
+
+                        case UNBLOCK_FOCUS:
+                            nativeUnblockFocus();
+                            break;
+
+                        case SET_KIT_FOCUS:
+                            FocusData focusData = (FocusData) msg.obj;
+                            nativeSetKitFocus(focusData.mMoveGeneration,
+                                    focusData.mBuildGeneration,
+                                    focusData.mFrame, focusData.mNode,
+                                    focusData.mX, focusData.mY,
+                                    focusData.mIgnoreNullFocus);
+                            break;
+
+                        case REQUEST_FOCUS_HREF: {
+                            Message hrefMsg = (Message) msg.obj;
+                            String res = nativeRetrieveHref(msg.arg1, msg.arg2);
+                            hrefMsg.getData().putString("url", res);
+                            hrefMsg.sendToTarget();
+                            break;
+                        }
+                            
+                        case UPDATE_CACHE_AND_TEXT_ENTRY:
+                            nativeUpdateFrameCache();
+                            // FIXME: this should provide a minimal rectangle
+                            if (mWebView != null) {
+                                mWebView.postInvalidate();
+                            }
+                            sendUpdateTextEntry();
+                            break;
+
+                        case DOC_HAS_IMAGES:
+                            Message imageResult = (Message) msg.obj;
+                            imageResult.arg1 =
+                                    mBrowserFrame.documentHasImages() ? 1 : 0;
+                            imageResult.sendToTarget();
+                            break;
+
+                        case SET_SNAP_ANCHOR:
+                            nativeSetSnapAnchor(msg.arg1, msg.arg2);
+                            break;
+                            
+                        case DELETE_SELECTION:
+                            FocusData delData = (FocusData) msg.obj;
+                            nativeDeleteSelection(delData.mFrame,
+                                     delData.mNode, delData.mX, 
+                                     delData.mY, msg.arg1, msg.arg2);
+                            break;
+
+                        case SET_SELECTION:
+                            FocusData selData = (FocusData) msg.obj;
+                            nativeSetSelection(selData.mFrame,
+                                     selData.mNode, selData.mX, 
+                                     selData.mY, msg.arg1, msg.arg2);
+                            break;
+                            
+                        case LISTBOX_CHOICES:
+                            SparseBooleanArray choices = (SparseBooleanArray)
+                                    msg.obj;
+                            int choicesSize = msg.arg1;
+                            boolean[] choicesArray = new boolean[choicesSize];
+                            for (int c = 0; c < choicesSize; c++) {
+                                choicesArray[c] = choices.get(c);
+                            }
+                            nativeSendListBoxChoices(choicesArray, 
+                                    choicesSize);
+                            break;
+
+                        case SINGLE_LISTBOX_CHOICE:
+                            nativeSendListBoxChoice(msg.arg1);
+                            break;
+                            
+                        case SET_BACKGROUND_COLOR:
+                            nativeSetBackgroundColor(msg.arg1);
+                            break;
+                            
+                        case GET_SELECTION:
+                            String str = nativeGetSelection((Region) msg.obj);
+                            Message.obtain(mWebView.mPrivateHandler
+                                    , WebView.UPDATE_CLIPBOARD, str)
+                                    .sendToTarget();
+                            break;
+
+                        case DUMP_DOMTREE:
+                            nativeDumpDomTree(msg.arg1 == 1);
+                            break;
+
+                        case DUMP_RENDERTREE:
+                            nativeDumpRenderTree(msg.arg1 == 1);
+                            break;
+
+                        case DUMP_NAVTREE:
+                            nativeDumpNavTree();
+                            break;
+
+                        case SYNC_SCROLL:
+                            mWebkitScrollX = msg.arg1;
+                            mWebkitScrollY = msg.arg2;
+                            break;
+
+                        case REFRESH_PLUGINS:
+                            nativeRefreshPlugins(msg.arg1 != 0);
+                            break;
+                            
+                        case SPLIT_PICTURE_SET:
+                            nativeSplitContent();
+                            mSplitPictureIsScheduled = false;
+                            break;
+                            
+                        case CLEAR_CONTENT:
+                            // Clear the view so that onDraw() will draw nothing
+                            // but white background
+                            // (See public method WebView.clearView)
+                            nativeClearContent();
+                            break;
+                    }
+                }
+            };
+            // Take all queued messages and resend them to the new handler.
+            synchronized (this) {
+                int size = mMessages.size();
+                for (int i = 0; i < size; i++) {
+                    mHandler.sendMessage(mMessages.get(i));
+                }
+                mMessages = null;
+            }
+        }
+
+        /**
+         * Send a message internally to the queue or to the handler
+         */
+        private synchronized void sendMessage(Message msg) {
+            if (mBlockMessages) {
+                return;
+            }
+            if (mMessages != null) {
+                mMessages.add(msg);
+            } else {
+                mHandler.sendMessage(msg);
+            }
+        }
+
+        private synchronized void removeMessages(int what) {
+            if (mBlockMessages) {
+                return;
+            }
+            if (what == EventHub.WEBKIT_DRAW) {
+                mDrawIsScheduled = false;
+            }
+            if (mMessages != null) {
+                Log.w(LOGTAG, "Not supported in this case.");
+            } else {
+                mHandler.removeMessages(what);
+            }
+        }
+
+        private synchronized void sendMessageDelayed(Message msg, long delay) {
+            if (mBlockMessages) {
+                return;
+            }
+            mHandler.sendMessageDelayed(msg, delay);
+        }
+
+        /**
+         * Send a message internally to the front of the queue.
+         */
+        private synchronized void sendMessageAtFrontOfQueue(Message msg) {
+            if (mBlockMessages) {
+                return;
+            }
+            if (mMessages != null) {
+                mMessages.add(0, msg);
+            } else {
+                mHandler.sendMessageAtFrontOfQueue(msg);
+            }
+        }
+
+        /**
+         * Remove all the messages.
+         */
+        private synchronized void removeMessages() {
+            // reset mDrawIsScheduled flag as WEBKIT_DRAW may be removed
+            mDrawIsScheduled = false;
+            mSplitPictureIsScheduled = false;
+            if (mMessages != null) {
+                mMessages.clear();
+            } else {
+                mHandler.removeCallbacksAndMessages(null);
+            }
+        }
+
+        /**
+         * Block sending messages to the EventHub.
+         */
+        private synchronized void blockMessages() {
+            mBlockMessages = true;
+        }
+    }
+
+    //-------------------------------------------------------------------------
+    // Methods called by host activity (in the same thread)
+    //-------------------------------------------------------------------------
+
+    void stopLoading() {
+        if (LOGV_ENABLED) Log.v(LOGTAG, "CORE stopLoading");
+        if (mBrowserFrame != null) {
+            mBrowserFrame.stopLoading();
+        }
+    }
+
+    //-------------------------------------------------------------------------
+    // Methods called by WebView
+    // If it refers to local variable, it needs synchronized().
+    // If it needs WebCore, it has to send message.
+    //-------------------------------------------------------------------------
+
+    void sendMessage(Message msg) {
+        mEventHub.sendMessage(msg);
+    }
+
+    void sendMessage(int what) {
+        mEventHub.sendMessage(Message.obtain(null, what));
+    }
+
+    void sendMessage(int what, Object obj) {
+        mEventHub.sendMessage(Message.obtain(null, what, obj));
+    }
+
+    void sendMessage(int what, int arg1) {
+        // just ignore the second argument (make it 0)
+        mEventHub.sendMessage(Message.obtain(null, what, arg1, 0));
+    }
+
+    void sendMessage(int what, int arg1, int arg2) {
+        mEventHub.sendMessage(Message.obtain(null, what, arg1, arg2));
+    }
+
+    void sendMessage(int what, int arg1, Object obj) {
+        // just ignore the second argument (make it 0)
+        mEventHub.sendMessage(Message.obtain(null, what, arg1, 0, obj));
+    }
+
+    void sendMessage(int what, int arg1, int arg2, Object obj) {
+        mEventHub.sendMessage(Message.obtain(null, what, arg1, arg2, obj));
+    }
+
+    void sendMessageDelayed(int what, Object obj, long delay) {
+        mEventHub.sendMessageDelayed(Message.obtain(null, what, obj), delay);
+    }
+
+    void removeMessages(int what) {
+        mEventHub.removeMessages(what);
+    }
+
+    void removeMessages() {
+        mEventHub.removeMessages();
+    }
+
+    /**
+     * Removes pending messages and trigger a DESTROY message to send to
+     * WebCore.
+     * Called from UI thread.
+     */
+    void destroy() {
+        // We don't want anyone to post a message between removing pending
+        // messages and sending the destroy message.
+        synchronized (mEventHub) {
+            mEventHub.removeMessages();
+            mEventHub.sendMessageAtFrontOfQueue(
+                    Message.obtain(null, EventHub.DESTROY));
+            mEventHub.blockMessages();
+            mWebView = null;
+        }
+    }
+
+    //-------------------------------------------------------------------------
+    // WebViewCore private methods
+    //-------------------------------------------------------------------------
+
+    private void loadUrl(String url) {
+        if (LOGV_ENABLED) Log.v(LOGTAG, " CORE loadUrl " + url);
+        mBrowserFrame.loadUrl(url);
+    }
+
+    private void key(KeyEvent evt, boolean isDown) {
+        if (LOGV_ENABLED) {
+            Log.v(LOGTAG, "CORE key at " + System.currentTimeMillis() + ", "
+                    + evt);
+        }
+        if (!nativeKey(evt.getKeyCode(), evt.getUnicodeChar(),
+                evt.getRepeatCount(), evt.isShiftPressed(), evt.isAltPressed(),
+                isDown)) {
+            // bubble up the event handling
+            mCallbackProxy.onUnhandledKeyEvent(evt);
+        }
+    }
+
+    // These values are used to avoid requesting a layout based on old values
+    private int mCurrentViewWidth = 0;
+    private int mCurrentViewHeight = 0;
+
+    // Define a minimum screen width so that we won't wrap the paragraph to one
+    // word per line during zoom-in.
+    private static final int MIN_SCREEN_WIDTH = 160;
+
+    // notify webkit that our virtual view size changed size (after inv-zoom)
+    private void viewSizeChanged(int w, int h, int viewWidth) {
+        if (LOGV_ENABLED) Log.v(LOGTAG, "CORE onSizeChanged");
+        if (w == 0) {
+            Log.w(LOGTAG, "skip viewSizeChanged as w is 0");
+            return;
+        }
+        // negative scale indicate that WebCore should reuse the current scale
+        float scale = (float) viewWidth / w;
+        if (mSettings.getUseWideViewPort()
+                && (w < mViewportWidth || mViewportWidth == -1)) {
+            int width = mViewportWidth;
+            int screenWidth = Math.max(w, MIN_SCREEN_WIDTH);
+            if (mViewportWidth == -1) {
+                if (mSettings.getLayoutAlgorithm() == 
+                        WebSettings.LayoutAlgorithm.NORMAL) {
+                    width = WebView.ZOOM_OUT_WIDTH;
+                } else {
+                    /*
+                     * if a page's minimum preferred width is wider than the
+                     * given "w", use it instead to get better layout result. If
+                     * we start a page with MAX_ZOOM_WIDTH, "w" will be always
+                     * wider. If we start a page with screen width, due to the
+                     * delay between {@link #didFirstLayout} and
+                     * {@link #viewSizeChanged},
+                     * {@link #nativeGetContentMinPrefWidth} will return a more
+                     * accurate value than initial 0 to result a better layout.
+                     * In the worse case, the native width will be adjusted when
+                     * next zoom or screen orientation change happens.
+                     */
+                    int minContentWidth = nativeGetContentMinPrefWidth();
+                    if (minContentWidth > WebView.MAX_FLOAT_CONTENT_WIDTH) {
+                        // keep the same width and screen width so that there is 
+                        // no reflow when zoom-out
+                        width = minContentWidth;
+                        screenWidth = Math.min(screenWidth, Math.abs(viewWidth));
+                    } else {
+                        width = Math.max(w, minContentWidth);
+                    }
+                }
+            }
+            nativeSetSize(width, Math.round((float) width * h / w),
+                    screenWidth, scale, w, h);
+        } else {
+            nativeSetSize(w, h, w, scale, w, h);
+        }
+        // Remember the current width and height
+        boolean needInvalidate = (mCurrentViewWidth == 0);
+        mCurrentViewWidth = w;
+        mCurrentViewHeight = h;
+        if (needInvalidate) {
+            // ensure {@link #webkitDraw} is called as we were blocking in
+            // {@link #contentDraw} when mCurrentViewWidth is 0
+            if (LOGV_ENABLED) Log.v(LOGTAG, "viewSizeChanged");
+            contentDraw();
+        }
+        mEventHub.sendMessage(Message.obtain(null,
+                EventHub.UPDATE_CACHE_AND_TEXT_ENTRY));
+    }
+
+    private void sendUpdateTextEntry() {
+        if (mWebView != null) {
+            Message.obtain(mWebView.mPrivateHandler,
+                    WebView.UPDATE_TEXT_ENTRY_MSG_ID).sendToTarget();
+        }
+    }
+
+    // Used to avoid posting more than one draw message.
+    private boolean mDrawIsScheduled;
+    
+    // Used to avoid posting more than one split picture message.
+    private boolean mSplitPictureIsScheduled;
+
+    // Used to suspend drawing.
+    private boolean mDrawIsPaused;
+
+    // Used to end scale+scroll mode, accessed by both threads
+    boolean mEndScaleZoom = false;
+    
+    public class DrawData {
+        public DrawData() {
+            mInvalRegion = new Region();
+            mWidthHeight = new Point();
+        }
+        public Region mInvalRegion;
+        public Point mViewPoint;
+        public Point mWidthHeight;
+    }
+    
+    private void webkitDraw() {
+        mDrawIsScheduled = false;
+        DrawData draw = new DrawData();
+        if (LOGV_ENABLED) Log.v(LOGTAG, "webkitDraw start");
+        if (nativeRecordContent(draw.mInvalRegion, draw.mWidthHeight) 
+                == false) {
+            if (LOGV_ENABLED) Log.v(LOGTAG, "webkitDraw abort");
+            return;
+        }
+        if (mWebView != null) {
+            // Send the native view size that was used during the most recent
+            // layout.
+            draw.mViewPoint = new Point(mCurrentViewWidth, mCurrentViewHeight);
+            if (LOGV_ENABLED) Log.v(LOGTAG, "webkitDraw NEW_PICTURE_MSG_ID");
+            Message.obtain(mWebView.mPrivateHandler,
+                    WebView.NEW_PICTURE_MSG_ID,
+                    mViewportMinimumScale == 0 ? nativeGetContentMinPrefWidth()
+                            : 0,
+                    0, draw).sendToTarget();
+            nativeCheckNavCache();
+            if (mWebkitScrollX != 0 || mWebkitScrollY != 0) {
+                // as we have the new picture, try to sync the scroll position
+                Message.obtain(mWebView.mPrivateHandler,
+                        WebView.SYNC_SCROLL_TO_MSG_ID, mWebkitScrollX,
+                        mWebkitScrollY).sendToTarget();
+                mWebkitScrollX = mWebkitScrollY = 0;
+            }
+            // nativeSnapToAnchor() needs to be called after NEW_PICTURE_MSG_ID
+            // is sent, so that scroll will be based on the new content size.
+            nativeSnapToAnchor();
+        }
+    }
+
+    ///////////////////////////////////////////////////////////////////////////
+    // These are called from the UI thread, not our thread
+
+    static final int ZOOM_BITS = Paint.FILTER_BITMAP_FLAG |
+                                         Paint.DITHER_FLAG |
+                                         Paint.SUBPIXEL_TEXT_FLAG;
+    static final int SCROLL_BITS = Paint.FILTER_BITMAP_FLAG |
+                                           Paint.DITHER_FLAG;
+
+    final DrawFilter mZoomFilter =
+                    new PaintFlagsDrawFilter(ZOOM_BITS, Paint.LINEAR_TEXT_FLAG);
+    final DrawFilter mScrollFilter =
+                    new PaintFlagsDrawFilter(SCROLL_BITS, 0);
+
+    /* package */ void drawContentPicture(Canvas canvas, int color,
+                                          boolean animatingZoom,
+                                          boolean animatingScroll) {
+        DrawFilter df = null;
+        if (animatingZoom) {
+            df = mZoomFilter;
+        } else if (animatingScroll) {
+            df = mScrollFilter;
+        }
+        canvas.setDrawFilter(df);
+        boolean tookTooLong = nativeDrawContent(canvas, color);
+        canvas.setDrawFilter(null);
+        if (tookTooLong && mSplitPictureIsScheduled == false) {
+            mSplitPictureIsScheduled = true;
+            sendMessage(EventHub.SPLIT_PICTURE_SET);
+        }
+    }
+
+    /*package*/ Picture copyContentPicture() {
+        Picture result = new Picture();
+        nativeCopyContentToPicture(result);
+        return result;
+    }
+
+    static void pauseUpdate(WebViewCore core) {
+        // remove the pending REDUCE_PRIORITY and RESUME_PRIORITY messages
+        sWebCoreHandler.removeMessages(WebCoreThread.REDUCE_PRIORITY);
+        sWebCoreHandler.removeMessages(WebCoreThread.RESUME_PRIORITY);
+        sWebCoreHandler.sendMessageAtFrontOfQueue(sWebCoreHandler
+                .obtainMessage(WebCoreThread.REDUCE_PRIORITY));
+        // Note: there is one possible failure mode. If pauseUpdate() is called
+        // from UI thread while in webcore thread WEBKIT_DRAW is just pulled out 
+        // of the queue and about to be executed. mDrawIsScheduled may be set to 
+        // false in webkitDraw(). So update won't be blocked. But at least the 
+        // webcore thread priority is still lowered.
+        if (core != null) {
+            synchronized (core) {
+                core.mDrawIsPaused = true;
+                core.mEventHub.removeMessages(EventHub.WEBKIT_DRAW);
+            }
+        }
+    }
+
+    static void resumeUpdate(WebViewCore core) {
+        // remove the pending REDUCE_PRIORITY and RESUME_PRIORITY messages
+        sWebCoreHandler.removeMessages(WebCoreThread.REDUCE_PRIORITY);
+        sWebCoreHandler.removeMessages(WebCoreThread.RESUME_PRIORITY);
+        sWebCoreHandler.sendMessageAtFrontOfQueue(sWebCoreHandler
+                .obtainMessage(WebCoreThread.RESUME_PRIORITY));
+        if (core != null) {
+            synchronized (core) {
+                core.mDrawIsScheduled = false;
+                core.mDrawIsPaused = false;
+                if (LOGV_ENABLED) Log.v(LOGTAG, "resumeUpdate");
+                core.contentDraw();
+            }
+        }
+    }
+
+    static void startCacheTransaction() {
+        sWebCoreHandler.sendMessage(sWebCoreHandler
+                .obtainMessage(WebCoreThread.RESUME_CACHE_TICKER));
+    }
+
+    static void endCacheTransaction() {
+        sWebCoreHandler.sendMessage(sWebCoreHandler
+                .obtainMessage(WebCoreThread.BLOCK_CACHE_TICKER));
+    }
+
+    //////////////////////////////////////////////////////////////////////////
+
+    private void restoreState(int index) {
+        WebBackForwardList list = mCallbackProxy.getBackForwardList();
+        int size = list.getSize();
+        for (int i = 0; i < size; i++) {
+            list.getItemAtIndex(i).inflate(mBrowserFrame.mNativeFrame);
+        }
+        mBrowserFrame.mLoadInitFromJava = true;
+        list.restoreIndex(mBrowserFrame.mNativeFrame, index);
+        mBrowserFrame.mLoadInitFromJava = false;
+    }
+
+    //-------------------------------------------------------------------------
+    // Implement abstract methods in WebViewCore, native WebKit callback part
+    //-------------------------------------------------------------------------
+
+    // called from JNI or WebView thread
+    /* package */ void contentDraw() {
+        // don't update the Picture until we have an initial width and finish
+        // the first layout
+        if (mCurrentViewWidth == 0 || !mBrowserFrame.firstLayoutDone()) {
+            return;
+        }
+        // only fire an event if this is our first request
+        synchronized (this) {
+            if (mDrawIsPaused || mDrawIsScheduled) {
+                return;
+            }
+            mDrawIsScheduled = true;
+            mEventHub.sendMessage(Message.obtain(null, EventHub.WEBKIT_DRAW));
+        }
+    }
+    
+    // called by JNI
+    private void contentScrollBy(int dx, int dy, boolean animate) {
+        if (!mBrowserFrame.firstLayoutDone()) {
+            // Will this happen? If yes, we need to do something here.
+            return;
+        }
+        if (mWebView != null) {
+            Message.obtain(mWebView.mPrivateHandler,
+                    WebView.SCROLL_BY_MSG_ID, dx, dy, 
+                    new Boolean(animate)).sendToTarget();
+        }
+    }
+
+    // called by JNI
+    private void contentScrollTo(int x, int y) {
+        if (!mBrowserFrame.firstLayoutDone()) {
+            /*
+             * WebKit restore state will be called before didFirstLayout(),
+             * remember the position as it has to be applied after restoring
+             * zoom factor which is controlled by screenWidth.
+             */
+            mRestoredX = x;
+            mRestoredY = y;
+            return;
+        }
+        if (mWebView != null) {
+            Message.obtain(mWebView.mPrivateHandler,
+                    WebView.SCROLL_TO_MSG_ID, x, y).sendToTarget();
+        }
+    }
+
+    // called by JNI
+    private void contentSpawnScrollTo(int x, int y) {
+        if (!mBrowserFrame.firstLayoutDone()) {
+            /*
+             * WebKit restore state will be called before didFirstLayout(),
+             * remember the position as it has to be applied after restoring
+             * zoom factor which is controlled by screenWidth.
+             */
+            mRestoredX = x;
+            mRestoredY = y;
+            return;
+        }
+        if (mWebView != null) {
+            Message.obtain(mWebView.mPrivateHandler,
+                    WebView.SPAWN_SCROLL_TO_MSG_ID, x, y).sendToTarget();
+        }
+    }
+
+    // called by JNI
+    private void sendMarkNodeInvalid(int node) {
+        if (mWebView != null) {
+            Message.obtain(mWebView.mPrivateHandler,
+                    WebView.MARK_NODE_INVALID_ID, node, 0).sendToTarget();
+        }
+    }
+
+    // called by JNI
+    private void sendNotifyFocusSet() {
+        if (mWebView != null) {
+            Message.obtain(mWebView.mPrivateHandler,
+                    WebView.NOTIFY_FOCUS_SET_MSG_ID).sendToTarget();
+        }
+    }
+
+    // called by JNI
+    private void sendNotifyProgressFinished() {
+        sendUpdateTextEntry();
+        // as CacheManager can behave based on database transaction, we need to
+        // call tick() to trigger endTransaction
+        sWebCoreHandler.removeMessages(WebCoreThread.CACHE_TICKER);
+        sWebCoreHandler.sendMessage(sWebCoreHandler
+                .obtainMessage(WebCoreThread.CACHE_TICKER));
+        contentDraw();
+    }
+
+    // called by JNI
+    private void sendRecomputeFocus() {
+        if (mWebView != null) {
+            Message.obtain(mWebView.mPrivateHandler,
+                    WebView.RECOMPUTE_FOCUS_MSG_ID).sendToTarget();
+        }
+    }
+
+    /*  Called by JNI. The coordinates are in doc coordinates, so they need to
+        be scaled before they can be used by the view system, which happens
+        in WebView since it (and its thread) know the current scale factor.
+     */
+    private void sendViewInvalidate(int left, int top, int right, int bottom) {
+        if (mWebView != null) {
+            Message.obtain(mWebView.mPrivateHandler,
+                           WebView.INVAL_RECT_MSG_ID,
+                           new Rect(left, top, right, bottom)).sendToTarget();
+        }
+    }
+
+    /* package */ WebView getWebView() {
+        return mWebView;
+    }
+
+    private native void setViewportSettingsFromNative();
+    
+    // called by JNI
+    private void didFirstLayout() {
+        // Trick to ensure that the Picture has the exact height for the content
+        // by forcing to layout with 0 height after the page is ready, which is
+        // indicated by didFirstLayout. This is essential to get rid of the 
+        // white space in the GMail which uses WebView for message view.
+        if (mWebView != null && mWebView.mHeightCanMeasure) {
+            mWebView.mLastHeightSent = 0;
+            // Send a negative screen width to indicate that WebCore should 
+            // reuse the current scale
+            mEventHub.sendMessage(Message.obtain(null,
+                    EventHub.VIEW_SIZE_CHANGED, mWebView.mLastWidthSent,
+                    mWebView.mLastHeightSent, -mWebView.mLastWidthSent));
+        }
+
+        mBrowserFrame.didFirstLayout();
+
+        // reset the scroll position as it is a new page now
+        mWebkitScrollX = mWebkitScrollY = 0;
+
+        // set the viewport settings from WebKit
+        setViewportSettingsFromNative();
+
+        // infer the values if they are not defined.
+        if (mViewportWidth == 0) {
+            if (mViewportInitialScale == 0) {
+                mViewportInitialScale = 100;
+            }
+            if (mViewportMinimumScale == 0) {
+                mViewportMinimumScale = 100;
+            }
+        }
+        if (mViewportUserScalable == false) {
+            mViewportInitialScale = 100;
+            mViewportMinimumScale = 100;
+            mViewportMaximumScale = 100;
+        }
+        if (mViewportMinimumScale > mViewportInitialScale) {
+            if (mViewportInitialScale == 0) {
+                mViewportInitialScale = mViewportMinimumScale;
+            } else {
+                mViewportMinimumScale = mViewportInitialScale;
+            }
+        }
+        if (mViewportMaximumScale > 0) {
+            if (mViewportMaximumScale < mViewportInitialScale) {
+                mViewportMaximumScale = mViewportInitialScale;
+            } else if (mViewportInitialScale == 0) {
+                mViewportInitialScale = mViewportMaximumScale;
+            }            
+        }
+        if (mViewportWidth < 0 && mViewportInitialScale == 100) {
+            mViewportWidth = 0;
+        }
+
+        // now notify webview
+        if (mWebView != null) {
+            HashMap scaleLimit = new HashMap();
+            scaleLimit.put("minScale", mViewportMinimumScale);
+            scaleLimit.put("maxScale", mViewportMaximumScale);
+
+            if (mRestoredScale > 0) {
+                Message.obtain(mWebView.mPrivateHandler,
+                        WebView.DID_FIRST_LAYOUT_MSG_ID, mRestoredScale, 0,
+                        scaleLimit).sendToTarget();
+                mRestoredScale = 0;
+            } else {
+                Message.obtain(mWebView.mPrivateHandler,
+                        WebView.DID_FIRST_LAYOUT_MSG_ID, mViewportInitialScale,
+                        mViewportWidth, scaleLimit).sendToTarget();
+            }
+
+            // if no restored offset, move the new page to (0, 0)
+            Message.obtain(mWebView.mPrivateHandler, WebView.SCROLL_TO_MSG_ID,
+                    mRestoredX, mRestoredY).sendToTarget();
+            mRestoredX = mRestoredY = 0;
+
+            // force an early draw for quick feedback after the first layout
+            if (mCurrentViewWidth != 0) {
+                synchronized (this) {
+                    if (mDrawIsScheduled) {
+                        mEventHub.removeMessages(EventHub.WEBKIT_DRAW);
+                    }
+                    mDrawIsScheduled = true;
+                    mEventHub.sendMessageAtFrontOfQueue(Message.obtain(null,
+                            EventHub.WEBKIT_DRAW));
+                }
+            }
+        }
+    }
+
+    // called by JNI
+    private void restoreScale(int scale) {
+        if (mBrowserFrame.firstLayoutDone() == false) {
+            mRestoredScale = scale;
+        }
+    }
+
+    // called by JNI
+    private void needTouchEvents(boolean need) {
+        if (mWebView != null) {
+            Message.obtain(mWebView.mPrivateHandler,
+                    WebView.WEBCORE_NEED_TOUCH_EVENTS, need ? 1 : 0, 0)
+                    .sendToTarget();
+        }
+    }
+
+    // called by JNI
+    private void updateTextfield(int ptr, boolean changeToPassword,
+            String text, int textGeneration) {
+        if (mWebView != null) {
+            Message msg = Message.obtain(mWebView.mPrivateHandler,
+                    WebView.UPDATE_TEXTFIELD_TEXT_MSG_ID, ptr, 
+                    textGeneration, text);
+            msg.getData().putBoolean("password", changeToPassword);
+            msg.sendToTarget();
+        }
+    }
+
+    // these must be in document space (i.e. not scaled/zoomed).
+    private native void nativeSetScrollOffset(int dx, int dy);
+
+    private native void nativeSetGlobalBounds(int x, int y, int w, int h);
+
+    // called by JNI
+    private void requestListBox(String[] array, boolean[] enabledArray,
+            int[] selectedArray) {
+        if (mWebView != null) {
+            mWebView.requestListBox(array, enabledArray, selectedArray);
+        }
+    }
+
+    // called by JNI
+    private void requestListBox(String[] array, boolean[] enabledArray,
+            int selection) {
+        if (mWebView != null) {
+            mWebView.requestListBox(array, enabledArray, selection);
+        }
+        
+    }
+}
diff --git a/core/java/android/webkit/WebViewDatabase.java b/core/java/android/webkit/WebViewDatabase.java
new file mode 100644
index 0000000..1004e30
--- /dev/null
+++ b/core/java/android/webkit/WebViewDatabase.java
@@ -0,0 +1,967 @@
+/*
+ * Copyright (C) 2007 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 android.webkit;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Set;
+import java.util.Map.Entry;
+
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.Cursor;
+import android.database.DatabaseUtils;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteStatement;
+import android.util.Log;
+import android.webkit.CookieManager.Cookie;
+import android.webkit.CacheManager.CacheResult;
+
+public class WebViewDatabase {
+    private static final String DATABASE_FILE = "webview.db";
+    private static final String CACHE_DATABASE_FILE = "webviewCache.db";
+
+    // log tag
+    protected static final String LOGTAG = "webviewdatabase";
+
+    private static final int DATABASE_VERSION = 9;
+    // 2 -> 3 Modified Cache table to allow cache of redirects
+    // 3 -> 4 Added Oma-Downloads table
+    // 4 -> 5 Modified Cache table to support persistent contentLength
+    // 5 -> 4 Removed Oma-Downoads table
+    // 5 -> 6 Add INDEX for cache table
+    // 6 -> 7 Change cache localPath from int to String
+    // 7 -> 8 Move cache to its own db
+    // 8 -> 9 Store both scheme and host when storing passwords
+    private static final int CACHE_DATABASE_VERSION = 1;
+
+    private static WebViewDatabase mInstance = null;
+
+    private static SQLiteDatabase mDatabase = null;
+    private static SQLiteDatabase mCacheDatabase = null;
+
+    // synchronize locks
+    private final Object mCookieLock = new Object();
+    private final Object mPasswordLock = new Object();
+    private final Object mFormLock = new Object();
+    private final Object mHttpAuthLock = new Object();
+
+    private static final String mTableNames[] = {
+        "cookies", "password", "formurl", "formdata", "httpauth"
+    };
+
+    // Table ids (they are index to mTableNames)
+    private static final int TABLE_COOKIES_ID = 0;
+
+    private static final int TABLE_PASSWORD_ID = 1;
+
+    private static final int TABLE_FORMURL_ID = 2;
+
+    private static final int TABLE_FORMDATA_ID = 3;
+
+    private static final int TABLE_HTTPAUTH_ID = 4;
+
+    // column id strings for "_id" which can be used by any table
+    private static final String ID_COL = "_id";
+
+    private static final String[] ID_PROJECTION = new String[] {
+        "_id"
+    };
+
+    // column id strings for "cookies" table
+    private static final String COOKIES_NAME_COL = "name";
+
+    private static final String COOKIES_VALUE_COL = "value";
+
+    private static final String COOKIES_DOMAIN_COL = "domain";
+
+    private static final String COOKIES_PATH_COL = "path";
+
+    private static final String COOKIES_EXPIRES_COL = "expires";
+
+    private static final String COOKIES_SECURE_COL = "secure";
+
+    // column id strings for "cache" table
+    private static final String CACHE_URL_COL = "url";
+
+    private static final String CACHE_FILE_PATH_COL = "filepath";
+
+    private static final String CACHE_LAST_MODIFY_COL = "lastmodify";
+
+    private static final String CACHE_ETAG_COL = "etag";
+
+    private static final String CACHE_EXPIRES_COL = "expires";
+
+    private static final String CACHE_MIMETYPE_COL = "mimetype";
+
+    private static final String CACHE_ENCODING_COL = "encoding";
+
+    private static final String CACHE_HTTP_STATUS_COL = "httpstatus";
+
+    private static final String CACHE_LOCATION_COL = "location";
+
+    private static final String CACHE_CONTENTLENGTH_COL = "contentlength";
+
+    // column id strings for "password" table
+    private static final String PASSWORD_HOST_COL = "host";
+
+    private static final String PASSWORD_USERNAME_COL = "username";
+
+    private static final String PASSWORD_PASSWORD_COL = "password";
+
+    // column id strings for "formurl" table
+    private static final String FORMURL_URL_COL = "url";
+
+    // column id strings for "formdata" table
+    private static final String FORMDATA_URLID_COL = "urlid";
+
+    private static final String FORMDATA_NAME_COL = "name";
+
+    private static final String FORMDATA_VALUE_COL = "value";
+
+    // column id strings for "httpauth" table
+    private static final String HTTPAUTH_HOST_COL = "host";
+
+    private static final String HTTPAUTH_REALM_COL = "realm";
+
+    private static final String HTTPAUTH_USERNAME_COL = "username";
+
+    private static final String HTTPAUTH_PASSWORD_COL = "password";
+
+    // use InsertHelper to improve insert performance by 40%
+    private static DatabaseUtils.InsertHelper mCacheInserter;
+    private static int mCacheUrlColIndex;
+    private static int mCacheFilePathColIndex;
+    private static int mCacheLastModifyColIndex;
+    private static int mCacheETagColIndex;
+    private static int mCacheExpiresColIndex;
+    private static int mCacheMimeTypeColIndex;
+    private static int mCacheEncodingColIndex;
+    private static int mCacheHttpStatusColIndex;
+    private static int mCacheLocationColIndex;
+    private static int mCacheContentLengthColIndex;
+
+    private static int mCacheTransactionRefcount;
+
+    private WebViewDatabase() {
+        // Singleton only, use getInstance()
+    }
+
+    public static synchronized WebViewDatabase getInstance(Context context) {
+        if (mInstance == null) {
+            mInstance = new WebViewDatabase();
+            mDatabase = context.openOrCreateDatabase(DATABASE_FILE, 0, null);
+
+            // mDatabase should not be null, 
+            // the only case is RequestAPI test has problem to create db 
+            if (mDatabase != null && mDatabase.getVersion() != DATABASE_VERSION) {
+                mDatabase.beginTransaction();
+                try {
+                    upgradeDatabase();
+                    mDatabase.setTransactionSuccessful();
+                } finally {
+                    mDatabase.endTransaction();
+                }
+            }
+
+            if (mDatabase != null) {
+                // use per table Mutex lock, turn off database lock, this
+                // improves performance as database's ReentrantLock is expansive
+                mDatabase.setLockingEnabled(false);
+            }
+
+            mCacheDatabase = context.openOrCreateDatabase(CACHE_DATABASE_FILE,
+                    0, null);
+
+            // mCacheDatabase should not be null, 
+            // the only case is RequestAPI test has problem to create db 
+            if (mCacheDatabase != null
+                    && mCacheDatabase.getVersion() != CACHE_DATABASE_VERSION) {
+                mCacheDatabase.beginTransaction();
+                try {
+                    upgradeCacheDatabase();
+                    bootstrapCacheDatabase();
+                    mCacheDatabase.setTransactionSuccessful();
+                } finally {
+                    mCacheDatabase.endTransaction();
+                }
+                // Erase the files from the file system in the 
+                // case that the database was updated and the 
+                // there were existing cache content
+                CacheManager.removeAllCacheFiles();
+            }
+
+            if (mCacheDatabase != null) {
+                // use InsertHelper for faster insertion
+                mCacheInserter = new DatabaseUtils.InsertHelper(mCacheDatabase,
+                        "cache");
+                mCacheUrlColIndex = mCacheInserter
+                        .getColumnIndex(CACHE_URL_COL);
+                mCacheFilePathColIndex = mCacheInserter
+                        .getColumnIndex(CACHE_FILE_PATH_COL);
+                mCacheLastModifyColIndex = mCacheInserter
+                        .getColumnIndex(CACHE_LAST_MODIFY_COL);
+                mCacheETagColIndex = mCacheInserter
+                        .getColumnIndex(CACHE_ETAG_COL);
+                mCacheExpiresColIndex = mCacheInserter
+                        .getColumnIndex(CACHE_EXPIRES_COL);
+                mCacheMimeTypeColIndex = mCacheInserter
+                        .getColumnIndex(CACHE_MIMETYPE_COL);
+                mCacheEncodingColIndex = mCacheInserter
+                        .getColumnIndex(CACHE_ENCODING_COL);
+                mCacheHttpStatusColIndex = mCacheInserter
+                        .getColumnIndex(CACHE_HTTP_STATUS_COL);
+                mCacheLocationColIndex = mCacheInserter
+                        .getColumnIndex(CACHE_LOCATION_COL);
+                mCacheContentLengthColIndex = mCacheInserter
+                        .getColumnIndex(CACHE_CONTENTLENGTH_COL);
+            }
+        }
+
+        return mInstance;
+    }
+
+    private static void upgradeDatabase() {
+        int oldVersion = mDatabase.getVersion();
+        if (oldVersion != 0) {
+            Log.i(LOGTAG, "Upgrading database from version "
+                    + oldVersion + " to "
+                    + DATABASE_VERSION + ", which will destroy old data");
+        }
+        boolean justPasswords = 8 == oldVersion && 9 == DATABASE_VERSION;
+        if (!justPasswords) {
+            mDatabase.execSQL("DROP TABLE IF EXISTS "
+                    + mTableNames[TABLE_COOKIES_ID]);
+            mDatabase.execSQL("DROP TABLE IF EXISTS cache");
+            mDatabase.execSQL("DROP TABLE IF EXISTS "
+                    + mTableNames[TABLE_FORMURL_ID]);
+            mDatabase.execSQL("DROP TABLE IF EXISTS "
+                    + mTableNames[TABLE_FORMDATA_ID]);
+            mDatabase.execSQL("DROP TABLE IF EXISTS "
+                    + mTableNames[TABLE_HTTPAUTH_ID]);
+        }
+        mDatabase.execSQL("DROP TABLE IF EXISTS "
+                + mTableNames[TABLE_PASSWORD_ID]);
+
+        mDatabase.setVersion(DATABASE_VERSION);
+
+        if (!justPasswords) {
+            // cookies
+            mDatabase.execSQL("CREATE TABLE " + mTableNames[TABLE_COOKIES_ID]
+                    + " (" + ID_COL + " INTEGER PRIMARY KEY, "
+                    + COOKIES_NAME_COL + " TEXT, " + COOKIES_VALUE_COL
+                    + " TEXT, " + COOKIES_DOMAIN_COL + " TEXT, "
+                    + COOKIES_PATH_COL + " TEXT, " + COOKIES_EXPIRES_COL
+                    + " INTEGER, " + COOKIES_SECURE_COL + " INTEGER" + ");");
+            mDatabase.execSQL("CREATE INDEX cookiesIndex ON "
+                    + mTableNames[TABLE_COOKIES_ID] + " (path)");
+
+            // formurl
+            mDatabase.execSQL("CREATE TABLE " + mTableNames[TABLE_FORMURL_ID]
+                    + " (" + ID_COL + " INTEGER PRIMARY KEY, " + FORMURL_URL_COL
+                    + " TEXT" + ");");
+
+            // formdata
+            mDatabase.execSQL("CREATE TABLE " + mTableNames[TABLE_FORMDATA_ID]
+                    + " (" + ID_COL + " INTEGER PRIMARY KEY, "
+                    + FORMDATA_URLID_COL + " INTEGER, " + FORMDATA_NAME_COL
+                    + " TEXT, " + FORMDATA_VALUE_COL + " TEXT," + " UNIQUE ("
+                    + FORMDATA_URLID_COL + ", " + FORMDATA_NAME_COL + ", "
+                    + FORMDATA_VALUE_COL + ") ON CONFLICT IGNORE);");
+
+            // httpauth
+            mDatabase.execSQL("CREATE TABLE " + mTableNames[TABLE_HTTPAUTH_ID]
+                    + " (" + ID_COL + " INTEGER PRIMARY KEY, "
+                    + HTTPAUTH_HOST_COL + " TEXT, " + HTTPAUTH_REALM_COL
+                    + " TEXT, " + HTTPAUTH_USERNAME_COL + " TEXT, "
+                    + HTTPAUTH_PASSWORD_COL + " TEXT," + " UNIQUE ("
+                    + HTTPAUTH_HOST_COL + ", " + HTTPAUTH_REALM_COL + ", "
+                    + HTTPAUTH_USERNAME_COL + ") ON CONFLICT REPLACE);");
+        }
+        // passwords
+        mDatabase.execSQL("CREATE TABLE " + mTableNames[TABLE_PASSWORD_ID]
+                + " (" + ID_COL + " INTEGER PRIMARY KEY, "
+                + PASSWORD_HOST_COL + " TEXT, " + PASSWORD_USERNAME_COL
+                + " TEXT, " + PASSWORD_PASSWORD_COL + " TEXT," + " UNIQUE ("
+                + PASSWORD_HOST_COL + ", " + PASSWORD_USERNAME_COL
+                + ") ON CONFLICT REPLACE);");
+    }
+
+    private static void upgradeCacheDatabase() {
+        int oldVersion = mCacheDatabase.getVersion();
+        if (oldVersion != 0) {
+            Log.i(LOGTAG, "Upgrading cache database from version "
+                    + oldVersion + " to "
+                    + DATABASE_VERSION + ", which will destroy all old data");
+        }
+        mCacheDatabase.execSQL("DROP TABLE IF EXISTS cache");
+        mCacheDatabase.setVersion(CACHE_DATABASE_VERSION);
+    }
+
+    private static void bootstrapCacheDatabase() {
+        if (mCacheDatabase != null) {
+            mCacheDatabase.execSQL("CREATE TABLE cache"
+                    + " (" + ID_COL + " INTEGER PRIMARY KEY, " + CACHE_URL_COL
+                    + " TEXT, " + CACHE_FILE_PATH_COL + " TEXT, "
+                    + CACHE_LAST_MODIFY_COL + " TEXT, " + CACHE_ETAG_COL
+                    + " TEXT, " + CACHE_EXPIRES_COL + " INTEGER, "
+                    + CACHE_MIMETYPE_COL + " TEXT, " + CACHE_ENCODING_COL
+                    + " TEXT," + CACHE_HTTP_STATUS_COL + " INTEGER, "
+                    + CACHE_LOCATION_COL + " TEXT, " + CACHE_CONTENTLENGTH_COL
+                    + " INTEGER, " + " UNIQUE (" + CACHE_URL_COL
+                    + ") ON CONFLICT REPLACE);");
+            mCacheDatabase.execSQL("CREATE INDEX cacheUrlIndex ON cache ("
+                    + CACHE_URL_COL + ")");
+        }
+    }
+
+    private boolean hasEntries(int tableId) {
+        if (mDatabase == null) {
+            return false;
+        }
+
+        Cursor cursor = mDatabase.query(mTableNames[tableId], ID_PROJECTION,
+                null, null, null, null, null);
+        boolean ret = cursor.moveToFirst() == true;
+        cursor.close();
+        return ret;
+    }
+
+    //
+    // cookies functions
+    //
+
+    /**
+     * Get cookies in the format of CookieManager.Cookie inside an ArrayList for
+     * a given domain
+     *
+     * @return ArrayList<Cookie> If nothing is found, return an empty list.
+     */
+    ArrayList<Cookie> getCookiesForDomain(String domain) {
+        ArrayList<Cookie> list = new ArrayList<Cookie>();
+        if (domain == null || mDatabase == null) {
+            return list;
+        }
+
+        synchronized (mCookieLock) {
+            final String[] columns = new String[] {
+                    ID_COL, COOKIES_DOMAIN_COL, COOKIES_PATH_COL,
+                    COOKIES_NAME_COL, COOKIES_VALUE_COL, COOKIES_EXPIRES_COL,
+                    COOKIES_SECURE_COL
+            };
+            final String selection = "(" + COOKIES_DOMAIN_COL
+                    + " GLOB '*' || ?)";
+            Cursor cursor = mDatabase.query(mTableNames[TABLE_COOKIES_ID],
+                    columns, selection, new String[] { domain }, null, null,
+                    null);
+            if (cursor.moveToFirst()) {
+                int domainCol = cursor.getColumnIndex(COOKIES_DOMAIN_COL);
+                int pathCol = cursor.getColumnIndex(COOKIES_PATH_COL);
+                int nameCol = cursor.getColumnIndex(COOKIES_NAME_COL);
+                int valueCol = cursor.getColumnIndex(COOKIES_VALUE_COL);
+                int expiresCol = cursor.getColumnIndex(COOKIES_EXPIRES_COL);
+                int secureCol = cursor.getColumnIndex(COOKIES_SECURE_COL);
+                do {
+                    Cookie cookie = new Cookie();
+                    cookie.domain = cursor.getString(domainCol);
+                    cookie.path = cursor.getString(pathCol);
+                    cookie.name = cursor.getString(nameCol);
+                    cookie.value = cursor.getString(valueCol);
+                    if (cursor.isNull(expiresCol)) {
+                        cookie.expires = -1;
+                    } else {
+                        cookie.expires = cursor.getLong(expiresCol);
+                    }
+                    cookie.secure = cursor.getShort(secureCol) != 0;
+                    cookie.mode = Cookie.MODE_NORMAL;
+                    list.add(cookie);
+                } while (cursor.moveToNext());
+            }
+            cursor.close();
+            return list;
+        }
+    }
+
+    /**
+     * Delete cookies which matches (domain, path, name).
+     *
+     * @param domain If it is null, nothing happens.
+     * @param path If it is null, all the cookies match (domain) will be
+     *            deleted.
+     * @param name If it is null, all the cookies match (domain, path) will be
+     *            deleted.
+     */
+    void deleteCookies(String domain, String path, String name) {
+        if (domain == null || mDatabase == null) {
+            return;
+        }
+
+        synchronized (mCookieLock) {
+            final String where = "(" + COOKIES_DOMAIN_COL + " == ?) AND ("
+                    + COOKIES_PATH_COL + " == ?) AND (" + COOKIES_NAME_COL
+                    + " == ?)";
+            mDatabase.delete(mTableNames[TABLE_COOKIES_ID], where,
+                    new String[] { domain, path, name });
+        }
+    }
+
+    /**
+     * Add a cookie to the database
+     *
+     * @param cookie
+     */
+    void addCookie(Cookie cookie) {
+        if (cookie.domain == null || cookie.path == null || cookie.name == null
+                || mDatabase == null) {
+            return;
+        }
+
+        synchronized (mCookieLock) {
+            ContentValues cookieVal = new ContentValues();
+            cookieVal.put(COOKIES_DOMAIN_COL, cookie.domain);
+            cookieVal.put(COOKIES_PATH_COL, cookie.path);
+            cookieVal.put(COOKIES_NAME_COL, cookie.name);
+            cookieVal.put(COOKIES_VALUE_COL, cookie.value);
+            if (cookie.expires != -1) {
+                cookieVal.put(COOKIES_EXPIRES_COL, cookie.expires);
+            }
+            cookieVal.put(COOKIES_SECURE_COL, cookie.secure);
+            mDatabase.insert(mTableNames[TABLE_COOKIES_ID], null, cookieVal);
+        }
+    }
+
+    /**
+     * Whether there is any cookies in the database
+     *
+     * @return TRUE if there is cookie.
+     */
+    boolean hasCookies() {
+        synchronized (mCookieLock) {
+            return hasEntries(TABLE_COOKIES_ID);
+        }
+    }
+
+    /**
+     * Clear cookie database
+     */
+    void clearCookies() {
+        if (mDatabase == null) {
+            return;
+        }
+
+        synchronized (mCookieLock) {
+            mDatabase.delete(mTableNames[TABLE_COOKIES_ID], null, null);
+        }
+    }
+
+    /**
+     * Clear session cookies, which means cookie doesn't have EXPIRES.
+     */
+    void clearSessionCookies() {
+        if (mDatabase == null) {
+            return;
+        }
+
+        final String sessionExpired = COOKIES_EXPIRES_COL + " ISNULL";
+        synchronized (mCookieLock) {
+            mDatabase.delete(mTableNames[TABLE_COOKIES_ID], sessionExpired,
+                    null);
+        }
+    }
+
+    /**
+     * Clear expired cookies
+     *
+     * @param now Time for now
+     */
+    void clearExpiredCookies(long now) {
+        if (mDatabase == null) {
+            return;
+        }
+
+        final String expires = COOKIES_EXPIRES_COL + " <= ?";
+        synchronized (mCookieLock) {
+            mDatabase.delete(mTableNames[TABLE_COOKIES_ID], expires,
+                    new String[] { Long.toString(now) });
+        }
+    }
+
+    //
+    // cache functions, can only be called from WebCoreThread
+    //
+
+    boolean startCacheTransaction() {
+        if (++mCacheTransactionRefcount == 1) {
+            mCacheDatabase.beginTransaction();
+            return true;
+        }
+        return false;
+    }
+
+    boolean endCacheTransaction() {
+        if (--mCacheTransactionRefcount == 0) {
+            try {
+                mCacheDatabase.setTransactionSuccessful();
+            } finally {
+                mCacheDatabase.endTransaction();
+            }
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Get a cache item.
+     * 
+     * @param url The url
+     * @return CacheResult The CacheManager.CacheResult
+     */
+    CacheResult getCache(String url) {
+        if (url == null || mCacheDatabase == null) {
+            return null;
+        }
+
+        Cursor cursor = mCacheDatabase.rawQuery("SELECT filepath, lastmodify, etag, expires, "
+                    + "mimetype, encoding, httpstatus, location, contentlength "
+                    + "FROM cache WHERE url = ?",
+                new String[] { url });
+
+        try {
+            if (cursor.moveToFirst()) {
+                CacheResult ret = new CacheResult();
+                ret.localPath = cursor.getString(0);
+                ret.lastModified = cursor.getString(1);
+                ret.etag = cursor.getString(2);
+                ret.expires = cursor.getLong(3);
+                ret.mimeType = cursor.getString(4);
+                ret.encoding = cursor.getString(5);
+                ret.httpStatusCode = cursor.getInt(6);
+                ret.location = cursor.getString(7);
+                ret.contentLength = cursor.getLong(8);
+                return ret;
+            }
+        } finally {
+            if (cursor != null) cursor.close();
+        }
+        return null;
+    }
+
+    /**
+     * Remove a cache item.
+     * 
+     * @param url The url
+     */
+    void removeCache(String url) {
+        if (url == null || mCacheDatabase == null) {
+            return;
+        }
+
+        mCacheDatabase.execSQL("DELETE FROM cache WHERE url = ?", new String[] { url });
+    }
+
+    /**
+     * Add or update a cache. CACHE_URL_COL is unique in the table.
+     *
+     * @param url The url
+     * @param c The CacheManager.CacheResult
+     */
+    void addCache(String url, CacheResult c) {
+        if (url == null || mCacheDatabase == null) {
+            return;
+        }
+
+        mCacheInserter.prepareForInsert();
+        mCacheInserter.bind(mCacheUrlColIndex, url);
+        mCacheInserter.bind(mCacheFilePathColIndex, c.localPath);
+        mCacheInserter.bind(mCacheLastModifyColIndex, c.lastModified);
+        mCacheInserter.bind(mCacheETagColIndex, c.etag);
+        mCacheInserter.bind(mCacheExpiresColIndex, c.expires);
+        mCacheInserter.bind(mCacheMimeTypeColIndex, c.mimeType);
+        mCacheInserter.bind(mCacheEncodingColIndex, c.encoding);
+        mCacheInserter.bind(mCacheHttpStatusColIndex, c.httpStatusCode);
+        mCacheInserter.bind(mCacheLocationColIndex, c.location);
+        mCacheInserter.bind(mCacheContentLengthColIndex, c.contentLength);
+        mCacheInserter.execute();
+    }
+
+    /**
+     * Clear cache database
+     */
+    void clearCache() {
+        if (mCacheDatabase == null) {
+            return;
+        }
+
+        mCacheDatabase.delete("cache", null, null);
+    }
+
+    boolean hasCache() {
+        if (mCacheDatabase == null) {
+            return false;
+        }
+
+        Cursor cursor = mCacheDatabase.query("cache", ID_PROJECTION,
+                null, null, null, null, null);
+        boolean ret = cursor.moveToFirst() == true;
+        cursor.close();
+        return ret;
+    }
+
+    long getCacheTotalSize() {
+        long size = 0;
+        Cursor cursor = mCacheDatabase.rawQuery(
+                "SELECT SUM(contentlength) as sum FROM cache", null);
+        if (cursor.moveToFirst()) {
+            size = cursor.getLong(0);
+        }
+        cursor.close();
+        return size;
+    }
+
+    ArrayList<String> trimCache(long amount) {
+        ArrayList<String> pathList = new ArrayList<String>(100);
+        Cursor cursor = mCacheDatabase.rawQuery(
+                "SELECT contentlength, filepath FROM cache ORDER BY expires ASC",
+                null);
+        if (cursor.moveToFirst()) {
+            int batchSize = 100;
+            StringBuilder pathStr = new StringBuilder(20 + 16 * batchSize);
+            pathStr.append("DELETE FROM cache WHERE filepath IN (?");
+            for (int i = 1; i < batchSize; i++) {
+                pathStr.append(", ?");
+            }
+            pathStr.append(")");
+            SQLiteStatement statement = mCacheDatabase.compileStatement(pathStr
+                    .toString());
+            // as bindString() uses 1-based index, initialize index to 1
+            int index = 1;
+            do {
+                long length = cursor.getLong(0);
+                if (length == 0) {
+                    continue;
+                }
+                amount -= length;
+                String filePath = cursor.getString(1);
+                statement.bindString(index, filePath);
+                pathList.add(filePath);
+                if (index++ == batchSize) {
+                    statement.execute();
+                    statement.clearBindings();
+                    index = 1;
+                }
+            } while (cursor.moveToNext() && amount > 0);
+            if (index > 1) {
+                // there may be old bindings from the previous statement if
+                // index is less than batchSize, which is Ok.
+                statement.execute();
+            }
+            statement.close();
+        }
+        cursor.close();
+        return pathList;
+    }
+
+    //
+    // password functions
+    //
+
+    /**
+     * Set password. Tuple (PASSWORD_HOST_COL, PASSWORD_USERNAME_COL) is unique.
+     *
+     * @param schemePlusHost The scheme and host for the password
+     * @param username The username for the password. If it is null, it means
+     *            password can't be saved.
+     * @param password The password
+     */
+    void setUsernamePassword(String schemePlusHost, String username,
+                String password) {
+        if (schemePlusHost == null || mDatabase == null) {
+            return;
+        }
+
+        synchronized (mPasswordLock) {
+            final ContentValues c = new ContentValues();
+            c.put(PASSWORD_HOST_COL, schemePlusHost);
+            c.put(PASSWORD_USERNAME_COL, username);
+            c.put(PASSWORD_PASSWORD_COL, password);
+            mDatabase.insert(mTableNames[TABLE_PASSWORD_ID], PASSWORD_HOST_COL,
+                    c);
+        }
+    }
+
+    /**
+     * Retrieve the username and password for a given host
+     *
+     * @param schemePlusHost The scheme and host which passwords applies to
+     * @return String[] if found, String[0] is username, which can be null and
+     *         String[1] is password. Return null if it can't find anything.
+     */
+    String[] getUsernamePassword(String schemePlusHost) {
+        if (schemePlusHost == null || mDatabase == null) {
+            return null;
+        }
+
+        final String[] columns = new String[] {
+                PASSWORD_USERNAME_COL, PASSWORD_PASSWORD_COL
+        };
+        final String selection = "(" + PASSWORD_HOST_COL + " == ?)";
+        synchronized (mPasswordLock) {
+            String[] ret = null;
+            Cursor cursor = mDatabase.query(mTableNames[TABLE_PASSWORD_ID],
+                    columns, selection, new String[] { schemePlusHost }, null,
+                    null, null);
+            if (cursor.moveToFirst()) {
+                ret = new String[2];
+                ret[0] = cursor.getString(
+                        cursor.getColumnIndex(PASSWORD_USERNAME_COL));
+                ret[1] = cursor.getString(
+                        cursor.getColumnIndex(PASSWORD_PASSWORD_COL));
+            }
+            cursor.close();
+            return ret;
+        }
+    }
+
+    /**
+     * Find out if there are any passwords saved. 
+     *
+     * @return TRUE if there is passwords saved
+     */
+    public boolean hasUsernamePassword() {
+        synchronized (mPasswordLock) {
+            return hasEntries(TABLE_PASSWORD_ID);
+        }
+    }
+
+    /**
+     * Clear password database
+     */
+    public void clearUsernamePassword() {
+        if (mDatabase == null) {
+            return;
+        }
+
+        synchronized (mPasswordLock) {
+            mDatabase.delete(mTableNames[TABLE_PASSWORD_ID], null, null);
+        }
+    }
+
+    //
+    // http authentication password functions
+    //
+
+    /**
+     * Set HTTP authentication password. Tuple (HTTPAUTH_HOST_COL,
+     * HTTPAUTH_REALM_COL, HTTPAUTH_USERNAME_COL) is unique.
+     *
+     * @param host The host for the password
+     * @param realm The realm for the password
+     * @param username The username for the password. If it is null, it means
+     *            password can't be saved.
+     * @param password The password
+     */
+    void setHttpAuthUsernamePassword(String host, String realm, String username,
+            String password) {
+        if (host == null || realm == null || mDatabase == null) {
+            return;
+        }
+
+        synchronized (mHttpAuthLock) {
+            final ContentValues c = new ContentValues();
+            c.put(HTTPAUTH_HOST_COL, host);
+            c.put(HTTPAUTH_REALM_COL, realm);
+            c.put(HTTPAUTH_USERNAME_COL, username);
+            c.put(HTTPAUTH_PASSWORD_COL, password);
+            mDatabase.insert(mTableNames[TABLE_HTTPAUTH_ID], HTTPAUTH_HOST_COL,
+                    c);
+        }
+    }
+
+    /**
+     * Retrieve the HTTP authentication username and password for a given
+     * host+realm pair
+     *
+     * @param host The host the password applies to
+     * @param realm The realm the password applies to
+     * @return String[] if found, String[0] is username, which can be null and
+     *         String[1] is password. Return null if it can't find anything.
+     */
+    String[] getHttpAuthUsernamePassword(String host, String realm) {
+        if (host == null || realm == null || mDatabase == null){
+            return null;
+        }
+
+        final String[] columns = new String[] {
+                HTTPAUTH_USERNAME_COL, HTTPAUTH_PASSWORD_COL
+        };
+        final String selection = "(" + HTTPAUTH_HOST_COL + " == ?) AND ("
+                + HTTPAUTH_REALM_COL + " == ?)";
+        synchronized (mHttpAuthLock) {
+            String[] ret = null;
+            Cursor cursor = mDatabase.query(mTableNames[TABLE_HTTPAUTH_ID],
+                    columns, selection, new String[] { host, realm }, null,
+                    null, null);
+            if (cursor.moveToFirst()) {
+                ret = new String[2];
+                ret[0] = cursor.getString(
+                        cursor.getColumnIndex(HTTPAUTH_USERNAME_COL));
+                ret[1] = cursor.getString(
+                        cursor.getColumnIndex(HTTPAUTH_PASSWORD_COL));
+            }
+            cursor.close();
+            return ret;
+        }
+    }
+
+    /**
+     *  Find out if there are any HTTP authentication passwords saved.   
+     *
+     * @return TRUE if there are passwords saved
+     */
+    public boolean hasHttpAuthUsernamePassword() {
+        synchronized (mHttpAuthLock) {
+            return hasEntries(TABLE_HTTPAUTH_ID);
+        }
+    }
+
+    /**
+     * Clear HTTP authentication password database
+     */
+    public void clearHttpAuthUsernamePassword() {
+        if (mDatabase == null) {
+            return;
+        }
+
+        synchronized (mHttpAuthLock) {
+            mDatabase.delete(mTableNames[TABLE_HTTPAUTH_ID], null, null);
+        }
+    }
+
+    //
+    // form data functions
+    //
+
+    /**
+     * Set form data for a site. Tuple (FORMDATA_URLID_COL, FORMDATA_NAME_COL,
+     * FORMDATA_VALUE_COL) is unique
+     *
+     * @param url The url of the site
+     * @param formdata The form data in HashMap
+     */
+    void setFormData(String url, HashMap<String, String> formdata) {
+        if (url == null || formdata == null || mDatabase == null) {
+            return;
+        }
+
+        final String selection = "(" + FORMURL_URL_COL + " == ?)";
+        synchronized (mFormLock) {
+            long urlid = -1;
+            Cursor cursor = mDatabase.query(mTableNames[TABLE_FORMURL_ID],
+                    ID_PROJECTION, selection, new String[] { url }, null, null,
+                    null);
+            if (cursor.moveToFirst()) {
+                urlid = cursor.getLong(cursor.getColumnIndex(ID_COL));
+            } else {
+                ContentValues c = new ContentValues();
+                c.put(FORMURL_URL_COL, url);
+                urlid = mDatabase.insert(
+                        mTableNames[TABLE_FORMURL_ID], null, c);
+            }
+            cursor.close();
+            if (urlid >= 0) {
+                Set<Entry<String, String>> set = formdata.entrySet();
+                Iterator<Entry<String, String>> iter = set.iterator();
+                ContentValues map = new ContentValues();
+                map.put(FORMDATA_URLID_COL, urlid);
+                while (iter.hasNext()) {
+                    Entry<String, String> entry = iter.next();
+                    map.put(FORMDATA_NAME_COL, entry.getKey());
+                    map.put(FORMDATA_VALUE_COL, entry.getValue());
+                    mDatabase.insert(mTableNames[TABLE_FORMDATA_ID], null, map);
+                }
+            }
+        }
+    }
+
+    /**
+     * Get all the values for a form entry with "name" in a given site
+     *
+     * @param url The url of the site
+     * @param name The name of the form entry
+     * @return A list of values. Return empty list if nothing is found.
+     */
+    ArrayList<String> getFormData(String url, String name) {
+        ArrayList<String> values = new ArrayList<String>();
+        if (url == null || name == null || mDatabase == null) {
+            return values;
+        }
+
+        final String urlSelection = "(" + FORMURL_URL_COL + " == ?)";
+        final String dataSelection = "(" + FORMDATA_URLID_COL + " == ?) AND ("
+                + FORMDATA_NAME_COL + " == ?)";
+        synchronized (mFormLock) {
+            Cursor cursor = mDatabase.query(mTableNames[TABLE_FORMURL_ID],
+                    ID_PROJECTION, urlSelection, new String[] { url }, null,
+                    null, null);
+            if (cursor.moveToFirst()) {
+                long urlid = cursor.getLong(cursor.getColumnIndex(ID_COL));
+                Cursor dataCursor = mDatabase.query(
+                        mTableNames[TABLE_FORMDATA_ID],
+                        new String[] { ID_COL, FORMDATA_VALUE_COL },
+                        dataSelection,
+                        new String[] { Long.toString(urlid), name }, null,
+                        null, null);
+                if (dataCursor.moveToFirst()) {
+                    int valueCol =
+                            dataCursor.getColumnIndex(FORMDATA_VALUE_COL);
+                    do {
+                        values.add(dataCursor.getString(valueCol));
+                    } while (dataCursor.moveToNext());
+                }
+                dataCursor.close();
+            }
+            cursor.close();
+            return values;
+        }
+    }
+
+    /**
+     * Find out if there is form data saved.
+     *
+     * @return TRUE if there is form data in the database
+     */
+    public boolean hasFormData() {
+        synchronized (mFormLock) {
+            return hasEntries(TABLE_FORMURL_ID);
+        }
+    }
+
+    /**
+     * Clear form database
+     */
+    public void clearFormData() {
+        if (mDatabase == null) {
+            return;
+        }
+
+        synchronized (mFormLock) {
+            mDatabase.delete(mTableNames[TABLE_FORMURL_ID], null, null);
+            mDatabase.delete(mTableNames[TABLE_FORMDATA_ID], null, null);
+        }
+    }
+}
diff --git a/core/java/android/webkit/gears/AndroidGpsLocationProvider.java b/core/java/android/webkit/gears/AndroidGpsLocationProvider.java
new file mode 100644
index 0000000..3646042
--- /dev/null
+++ b/core/java/android/webkit/gears/AndroidGpsLocationProvider.java
@@ -0,0 +1,156 @@
+// Copyright 2008, The Android Open Source Project
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+//  1. Redistributions of source code must retain the above copyright notice,
+//     this list of conditions and the following disclaimer.
+//  2. Redistributions in binary form must reproduce the above copyright notice,
+//     this list of conditions and the following disclaimer in the documentation
+//     and/or other materials provided with the distribution.
+//  3. Neither the name of Google Inc. nor the names of its contributors may be
+//     used to endorse or promote products derived from this software without
+//     specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+// WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+// EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+// OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+// ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+package android.webkit.gears;
+
+import android.content.Context;
+import android.location.Location;
+import android.location.LocationListener;
+import android.location.LocationManager;
+import android.location.LocationProvider;
+import android.os.Bundle;
+import android.util.Log;
+import android.webkit.WebView;
+
+/**
+ * GPS provider implementation for Android.
+ */
+public final class AndroidGpsLocationProvider implements LocationListener {
+  /**
+   * Logging tag
+   */
+  private static final String TAG = "Gears-J-GpsProvider";
+  /**
+   * Our location manager instance.
+   */
+  private LocationManager locationManager;
+  /**
+   * The native object ID.
+   */
+  private long nativeObject;
+
+  public AndroidGpsLocationProvider(WebView webview, long object) {
+    nativeObject = object;
+    locationManager = (LocationManager) webview.getContext().getSystemService(
+        Context.LOCATION_SERVICE);
+    if (locationManager == null) {
+      Log.e(TAG,
+          "AndroidGpsLocationProvider: could not get location manager.");
+      throw new NullPointerException(
+          "AndroidGpsLocationProvider: locationManager is null.");
+    }
+    // Register for location updates.
+    try {
+      locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0, 0,
+          this);
+    } catch (IllegalArgumentException ex) {
+      Log.e(TAG,
+          "AndroidLocationGpsProvider: could not register for updates: " + ex);
+      throw ex;
+    } catch (SecurityException ex) {
+      Log.e(TAG,
+          "AndroidGpsLocationProvider: not allowed to register for update: "
+          + ex);
+      throw ex;
+    }
+  }
+
+  /**
+   * Called when the provider is no longer needed.
+   */
+  public void shutdown() {
+    locationManager.removeUpdates(this);
+    Log.i(TAG, "GPS provider closed.");
+  }
+
+ /**
+  * Called when the location has changed.
+  * @param location The new location, as a Location object.
+  */
+  public void onLocationChanged(Location location) {
+    Log.i(TAG, "Location changed: " + location);
+    nativeLocationChanged(location, nativeObject);
+  }
+
+  /**
+   * Called when the provider status changes.
+   *
+   * @param provider the name of the location provider associated with this
+   * update.
+   * @param status {@link LocationProvider#OUT_OF_SERVICE} if the
+   * provider is out of service, and this is not expected to change in the
+   * near future; {@link LocationProvider#TEMPORARILY_UNAVAILABLE} if
+   * the provider is temporarily unavailable but is expected to be available
+   * shortly; and {@link LocationProvider#AVAILABLE} if the
+   * provider is currently available.
+   * @param extras an optional Bundle which will contain provider specific
+   * status variables (such as number of satellites).
+   */
+  public void onStatusChanged(String provider, int status, Bundle extras) {
+    Log.i(TAG, "Provider " + provider + " status changed to " + status);
+    if (status == LocationProvider.OUT_OF_SERVICE ||
+        status == LocationProvider.TEMPORARILY_UNAVAILABLE) {
+      nativeProviderError(false, nativeObject);
+    }
+  }
+
+  /**
+   * Called when the provider is enabled.
+   *
+   * @param provider the name of the location provider that is now enabled.
+   */
+  public void onProviderEnabled(String provider) {
+    Log.i(TAG, "Provider " + provider + " enabled.");
+    // No need to notify the native side. It's enough to start sending
+    // valid position fixes again.
+  }
+
+  /**
+   * Called when the provider is disabled.
+   *
+   * @param provider the name of the location provider that is now disabled.
+   */
+  public void onProviderDisabled(String provider) {
+    Log.i(TAG, "Provider " + provider + " disabled.");
+    nativeProviderError(true, nativeObject);
+  }
+
+  /**
+   * The native method called when a new location is available.
+   * @param location is the new Location instance to pass to the native side.
+   * @param nativeObject is a pointer to the corresponding
+   * AndroidGpsLocationProvider C++ instance.
+   */
+  private native void nativeLocationChanged(Location location, long object);
+
+  /**
+   * The native method called when there is a GPS provder error.
+   * @param isDisabled is true when the error signifies the fact that the GPS
+   * HW is disabled. For other errors, this param is always false.
+   * @param nativeObject is a pointer to the corresponding
+   * AndroidGpsLocationProvider C++ instance.
+   */
+  private native void nativeProviderError(boolean isDisabled, long object);
+}
diff --git a/core/java/android/webkit/gears/AndroidRadioDataProvider.java b/core/java/android/webkit/gears/AndroidRadioDataProvider.java
new file mode 100644
index 0000000..c920d45
--- /dev/null
+++ b/core/java/android/webkit/gears/AndroidRadioDataProvider.java
@@ -0,0 +1,244 @@
+// Copyright 2008, The Android Open Source Project
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+//  1. Redistributions of source code must retain the above copyright notice,
+//     this list of conditions and the following disclaimer.
+//  2. Redistributions in binary form must reproduce the above copyright notice,
+//     this list of conditions and the following disclaimer in the documentation
+//     and/or other materials provided with the distribution.
+//  3. Neither the name of Google Inc. nor the names of its contributors may be
+//     used to endorse or promote products derived from this software without
+//     specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+// WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+// EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+// OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+// ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+package android.webkit.gears;
+
+import android.content.Context;
+import android.telephony.CellLocation;
+import android.telephony.ServiceState;
+import android.telephony.gsm.GsmCellLocation;
+import android.telephony.PhoneStateListener;
+import android.telephony.TelephonyManager;
+import android.util.Log;
+import android.webkit.WebView;
+
+/**
+ * Radio data provider implementation for Android.
+ */
+public final class AndroidRadioDataProvider extends PhoneStateListener {
+
+  /** Logging tag */
+  private static final String TAG = "Gears-J-RadioProvider";
+
+  /** Network types */
+  private static final int RADIO_TYPE_UNKNOWN = 0;
+  private static final int RADIO_TYPE_GSM = 1;
+  private static final int RADIO_TYPE_WCDMA = 2;
+
+  /** Simple container for radio data */
+  public static final class RadioData {
+    public int cellId = -1;
+    public int locationAreaCode = -1;
+    public int signalStrength = -1;
+    public int mobileCountryCode = -1;
+    public int mobileNetworkCode = -1;
+    public int homeMobileCountryCode = -1;
+    public int homeMobileNetworkCode = -1;
+    public int radioType = RADIO_TYPE_UNKNOWN;
+    public String carrierName;
+
+    /**
+     * Constructs radioData object from the given telephony data.
+     * @param telephonyManager contains the TelephonyManager instance.
+     * @param cellLocation contains information about the current GSM cell.
+     * @param signalStrength is the strength of the network signal.
+     * @param serviceState contains information about the network service.
+     * @return a new RadioData object populated with the currently
+     *         available network information or null if there isn't
+     *         enough information.
+     */
+    public static RadioData getInstance(TelephonyManager telephonyManager,
+        CellLocation cellLocation, int signalStrength,
+        ServiceState serviceState) {
+
+      if (!(cellLocation instanceof GsmCellLocation)) {
+        // This also covers the case when cellLocation is null.
+        // When that happens, we do not bother creating a
+        // RadioData instance.
+        return null;
+      }
+
+      RadioData radioData = new RadioData();
+      GsmCellLocation gsmCellLocation = (GsmCellLocation) cellLocation;
+
+      // Extract the cell id, LAC, and signal strength.
+      radioData.cellId = gsmCellLocation.getCid();
+      radioData.locationAreaCode = gsmCellLocation.getLac();
+      radioData.signalStrength = signalStrength;
+
+      // Extract the home MCC and home MNC.
+      String operator = telephonyManager.getSimOperator();
+      radioData.setMobileCodes(operator, true);
+
+      if (serviceState != null) {
+        // Extract the carrier name.
+        radioData.carrierName = serviceState.getOperatorAlphaLong();
+
+        // Extract the MCC and MNC.
+        operator = serviceState.getOperatorNumeric();
+        radioData.setMobileCodes(operator, false);
+      }
+
+      // Finally get the radio type.
+      int type = telephonyManager.getNetworkType();
+      if (type == TelephonyManager.NETWORK_TYPE_UMTS) {
+        radioData.radioType = RADIO_TYPE_WCDMA;
+      } else if (type == TelephonyManager.NETWORK_TYPE_GPRS
+                 || type == TelephonyManager.NETWORK_TYPE_EDGE) {
+        radioData.radioType = RADIO_TYPE_GSM;
+      }
+
+      // Print out what we got.
+      Log.i(TAG, "Got the following data:");
+      Log.i(TAG, "CellId: " + radioData.cellId);
+      Log.i(TAG, "LAC: " + radioData.locationAreaCode);
+      Log.i(TAG, "MNC: " + radioData.mobileNetworkCode);
+      Log.i(TAG, "MCC: " + radioData.mobileCountryCode);
+      Log.i(TAG, "home MNC: " + radioData.homeMobileNetworkCode);
+      Log.i(TAG, "home MCC: " + radioData.homeMobileCountryCode);
+      Log.i(TAG, "Signal strength: " + radioData.signalStrength);
+      Log.i(TAG, "Carrier: " + radioData.carrierName);
+      Log.i(TAG, "Network type: " + radioData.radioType);
+
+      return radioData;
+    }
+
+    private RadioData() {}
+
+    /**
+     * Parses a string containing a mobile country code and a mobile
+     * network code and sets the corresponding member variables.
+     * @param codes is the string to parse.
+     * @param homeValues flags whether the codes are for the home operator.
+     */
+    private void setMobileCodes(String codes, boolean homeValues) {
+      if (codes != null) {
+        try {
+          // The operator numeric format is 3 digit country code plus 2 or
+          // 3 digit network code.
+          int mcc = Integer.parseInt(codes.substring(0, 3));
+          int mnc = Integer.parseInt(codes.substring(3));
+          if (homeValues) {
+            homeMobileCountryCode = mcc;
+            homeMobileNetworkCode = mnc;
+          } else {
+            mobileCountryCode = mcc;
+            mobileNetworkCode = mnc;
+          }
+        } catch (IndexOutOfBoundsException ex) {
+          Log.e(
+              TAG,
+              "AndroidRadioDataProvider: Invalid operator numeric data: " + ex);
+        } catch (NumberFormatException ex) {
+          Log.e(
+              TAG,
+              "AndroidRadioDataProvider: Operator numeric format error: " + ex);
+        }
+      }
+    }
+  };
+
+  /** The native object ID */
+  private long nativeObject;
+
+  /** The last known cellLocation */
+  private CellLocation cellLocation = null;
+
+  /** The last known signal strength */
+  private int signalStrength = -1;
+
+  /** The last known serviceState */
+  private ServiceState serviceState = null;
+
+  /**
+   * Our TelephonyManager instance.
+   */
+  private TelephonyManager telephonyManager;
+
+  /**
+   * Public constructor. Uses the webview to get the Context object.
+   */
+  public AndroidRadioDataProvider(WebView webview, long object) {
+    super();
+    nativeObject = object;
+    telephonyManager = (TelephonyManager) webview.getContext().getSystemService(
+        Context.TELEPHONY_SERVICE);
+    if (telephonyManager == null) {
+      Log.e(TAG,
+          "AndroidRadioDataProvider: could not get tepephony manager.");
+      throw new NullPointerException(
+          "AndroidRadioDataProvider: telephonyManager is null.");
+    }
+
+    // Register for cell id, signal strength and service state changed
+    // notifications.
+    telephonyManager.listen(this, PhoneStateListener.LISTEN_CELL_LOCATION
+        | PhoneStateListener.LISTEN_SIGNAL_STRENGTH
+        | PhoneStateListener.LISTEN_SERVICE_STATE);
+  }
+
+  /**
+   * Should be called when the provider is no longer needed.
+   */
+  public void shutdown() {
+    telephonyManager.listen(this, PhoneStateListener.LISTEN_NONE);
+    Log.i(TAG, "AndroidRadioDataProvider shutdown.");
+  }
+
+  @Override
+  public void onServiceStateChanged(ServiceState state) {
+    serviceState = state;
+    notifyListeners();
+  }
+
+  @Override
+  public void onSignalStrengthChanged(int asu) {
+    signalStrength = asu;
+    notifyListeners();
+  }
+
+  @Override
+  public void onCellLocationChanged(CellLocation location) {
+    cellLocation = location;
+    notifyListeners();
+  }
+
+  private void notifyListeners() {
+    RadioData radioData = RadioData.getInstance(telephonyManager, cellLocation,
+        signalStrength, serviceState);
+    if (radioData != null) {
+      onUpdateAvailable(radioData, nativeObject);
+    }
+  }
+
+  /**
+   * The native method called when new radio data is available.
+   * @param radioData is the RadioData instance to pass to the native side.
+   * @param nativeObject is a pointer to the corresponding
+   * AndroidRadioDataProvider C++ instance.
+   */
+  private static native void onUpdateAvailable(
+      RadioData radioData, long nativeObject);
+}
diff --git a/core/java/android/webkit/gears/AndroidWifiDataProvider.java b/core/java/android/webkit/gears/AndroidWifiDataProvider.java
new file mode 100644
index 0000000..7379f59
--- /dev/null
+++ b/core/java/android/webkit/gears/AndroidWifiDataProvider.java
@@ -0,0 +1,136 @@
+// Copyright 2008, Google Inc.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+//  1. Redistributions of source code must retain the above copyright notice,
+//     this list of conditions and the following disclaimer.
+//  2. Redistributions in binary form must reproduce the above copyright notice,
+//     this list of conditions and the following disclaimer in the documentation
+//     and/or other materials provided with the distribution.
+//  3. Neither the name of Google Inc. nor the names of its contributors may be
+//     used to endorse or promote products derived from this software without
+//     specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+// WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+// EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+// OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+// ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+package android.webkit.gears;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.net.wifi.ScanResult;
+import android.net.wifi.WifiManager;
+import android.os.Handler;
+import android.os.Looper;
+import android.util.Config;
+import android.util.Log;
+import android.webkit.WebView;
+import java.util.List;
+
+/**
+ * WiFi data provider implementation for Android.
+ * {@hide}
+ */
+public final class AndroidWifiDataProvider extends BroadcastReceiver {
+  /**
+   * Logging tag
+   */
+  private static final String TAG = "Gears-J-WifiProvider";
+  /**
+   * Our Wifi manager instance.
+   */
+  private WifiManager mWifiManager;
+  /**
+   * The native object ID.
+   */
+  private long mNativeObject;
+  /**
+   * The Context instance.
+   */
+  private Context mContext;
+
+  /**
+   * Constructs a instance of this class and registers for wifi scan
+   * updates. Note that this constructor must be called on a Looper
+   * thread. Suitable threads can be created on the native side using
+   * the AndroidLooperThread C++ class.
+   */
+  public AndroidWifiDataProvider(WebView webview, long object) {
+    mNativeObject = object;
+    mContext = webview.getContext();
+    mWifiManager =
+        (WifiManager) mContext.getSystemService(Context.WIFI_SERVICE);
+    if (mWifiManager == null) {
+      Log.e(TAG,
+          "AndroidWifiDataProvider: could not get location manager.");
+      throw new NullPointerException(
+          "AndroidWifiDataProvider: locationManager is null.");
+    }
+
+    // Create a Handler that identifies the message loop associated
+    // with the current thread. Note that it is not necessary to
+    // override handleMessage() at all since the Intent
+    // ReceiverDispatcher (see the ActivityThread class) only uses
+    // this handler to post a Runnable to this thread's loop.
+    Handler handler = new Handler(Looper.myLooper());
+
+    IntentFilter filter = new IntentFilter();
+    filter.addAction(mWifiManager.SCAN_RESULTS_AVAILABLE_ACTION);
+    mContext.registerReceiver(this, filter, null, handler);
+
+    // Get the last scan results and pass them to the native side.
+    // We can't just invoke the callback here, so we queue a message
+    // to this thread's loop.
+    handler.post(new Runnable() {
+        public void run() {
+          onUpdateAvailable(mWifiManager.getScanResults(), mNativeObject);
+        }
+      });
+  }
+
+  /**
+   * Called when the provider is no longer needed.
+   */
+  public void shutdown() {
+    mContext.unregisterReceiver(this);
+    if (Config.LOGV) {
+      Log.v(TAG, "Wifi provider closed.");
+    }
+  }
+
+  /**
+   * This method is called when the AndroidWifiDataProvider is receiving an
+   * Intent broadcast.
+   * @param context The Context in which the receiver is running.
+   * @param intent The Intent being received.
+   */
+  public void onReceive(Context context, Intent intent) {
+    if (intent.getAction().equals(
+            mWifiManager.SCAN_RESULTS_AVAILABLE_ACTION)) {
+      if (Config.LOGV) {
+        Log.v(TAG, "Wifi scan resulst available");
+      }
+      onUpdateAvailable(mWifiManager.getScanResults(), mNativeObject);
+    }
+  }
+
+ /**
+   * The native method called when new wifi data is available.
+   * @param scanResults is a list of ScanResults  to pass to the native side.
+   * @param nativeObject is a pointer to the corresponding
+   * AndroidWifiDataProvider C++ instance.
+   */
+  private static native void onUpdateAvailable(
+      List<ScanResult> scanResults, long nativeObject);
+}
diff --git a/core/java/android/webkit/gears/ApacheHttpRequestAndroid.java b/core/java/android/webkit/gears/ApacheHttpRequestAndroid.java
new file mode 100644
index 0000000..0569255
--- /dev/null
+++ b/core/java/android/webkit/gears/ApacheHttpRequestAndroid.java
@@ -0,0 +1,1122 @@
+// Copyright 2008, The Android Open Source Project
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+//  1. Redistributions of source code must retain the above copyright notice,
+//     this list of conditions and the following disclaimer.
+//  2. Redistributions in binary form must reproduce the above copyright notice,
+//     this list of conditions and the following disclaimer in the documentation
+//     and/or other materials provided with the distribution.
+//  3. Neither the name of Google Inc. nor the names of its contributors may be
+//     used to endorse or promote products derived from this software without
+//     specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+// WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+// EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+// OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+// ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+package android.webkit.gears;
+
+import android.net.http.Headers;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.util.Config;
+import android.util.Log;
+import android.webkit.CacheManager;
+import android.webkit.CacheManager.CacheResult;
+import android.webkit.CookieManager;
+
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.IOException;
+import java.lang.StringBuilder;
+import java.util.Date;
+import java.util.Map;
+import java.util.HashMap;
+import java.util.Iterator;
+
+import org.apache.http.Header;
+import org.apache.http.HttpEntity;
+import org.apache.http.client.params.HttpClientParams;
+import org.apache.http.params.HttpParams;
+import org.apache.http.params.HttpConnectionParams;
+import org.apache.http.params.HttpProtocolParams;
+import org.apache.http.HttpResponse;
+import org.apache.http.entity.AbstractHttpEntity;
+import org.apache.http.client.*;
+import org.apache.http.client.methods.*;
+import org.apache.http.impl.client.AbstractHttpClient;
+import org.apache.http.impl.client.DefaultHttpClient;
+import org.apache.http.impl.client.DefaultHttpRequestRetryHandler;
+import org.apache.http.conn.ssl.StrictHostnameVerifier;
+import org.apache.http.impl.cookie.DateUtils;
+import org.apache.http.util.CharArrayBuffer;
+
+import java.util.concurrent.locks.Condition;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+
+/**
+ * Performs the underlying HTTP/HTTPS GET, POST, HEAD, PUT, DELETE requests.
+ * <p> These are performed synchronously (blocking). The caller should
+ * ensure that it is in a background thread if asynchronous behavior
+ * is required. All data is pushed, so there is no need for JNI native
+ * callbacks.
+ * <p> This uses Apache's HttpClient framework to perform most
+ * of the underlying network activity. The Android brower's cache,
+ * android.webkit.CacheManager, is also used when caching is enabled,
+ * and updated with new data. The android.webkit.CookieManager is also
+ * queried and updated as necessary.
+ * <p> The public interface is designed to be called by native code
+ * through JNI, and to simplify coding none of the public methods will
+ * surface a checked exception. Unchecked exceptions may still be
+ * raised but only if the system is in an ill state, such as out of
+ * memory.
+ * <p> TODO: This isn't plumbed into LocalServer yet. Mutually
+ * dependent on LocalServer - will attach the two together once both
+ * are submitted.
+ */
+public final class ApacheHttpRequestAndroid {
+    /** Debug logging tag. */
+    private static final String LOG_TAG = "Gears-J";
+    /** HTTP response header line endings are CR-LF style. */
+    private static final String HTTP_LINE_ENDING = "\r\n";
+    /** Safe MIME type to use whenever it isn't specified. */
+    private static final String DEFAULT_MIME_TYPE = "text/plain";
+    /** Case-sensitive header keys */
+    public static final String KEY_CONTENT_LENGTH = "Content-Length";
+    public static final String KEY_EXPIRES = "Expires";
+    public static final String KEY_LAST_MODIFIED = "Last-Modified";
+    public static final String KEY_ETAG = "ETag";
+    public static final String KEY_LOCATION = "Location";
+    public static final String KEY_CONTENT_TYPE = "Content-Type";
+    /** Number of bytes to send and receive on the HTTP connection in
+     * one go. */
+    private static final int BUFFER_SIZE = 4096;
+
+    /** The first element of the String[] value in a headers map is the
+     * unmodified (case-sensitive) key. */
+    public static final int HEADERS_MAP_INDEX_KEY = 0;
+    /** The second element of the String[] value in a headers map is the
+     * associated value. */
+    public static final int HEADERS_MAP_INDEX_VALUE = 1;
+
+    /** Request headers, as key -> value map. */
+    // TODO: replace this design by a simpler one (the C++ side has to
+    // be modified too), where we do not store both the original header
+    // and the lowercase one.
+    private Map<String, String[]> mRequestHeaders =
+        new HashMap<String, String[]>();
+    /** Response headers, as a lowercase key -> value map. */
+    private Map<String, String[]> mResponseHeaders =
+        new HashMap<String, String[]>();
+    /** The URL used for createCacheResult() */
+    private String mCacheResultUrl;
+    /** CacheResult being saved into, if inserting a new cache entry. */
+    private CacheResult mCacheResult;
+    /** Initialized by initChildThread(). Used to target abort(). */
+    private Thread mBridgeThread;
+
+    /** Our HttpClient */
+    private AbstractHttpClient mClient;
+    /** The HttpMethod associated with this request */
+    private HttpRequestBase mMethod;
+    /** The complete response line e.g "HTTP/1.0 200 OK" */
+    private String mResponseLine;
+    /** HTTP body stream, setup after connection. */
+    private InputStream mBodyInputStream;
+
+    /** HTTP Response Entity */
+    private HttpResponse mResponse;
+
+    /** Post Entity, used to stream the request to the server */
+    private StreamEntity mPostEntity = null;
+    /** Content lenght, mandatory when using POST */
+    private long mContentLength;
+
+    /** The request executes in a parallel thread */
+    private Thread mHttpThread = null;
+    /** protect mHttpThread, if interrupt() is called concurrently */
+    private Lock mHttpThreadLock = new ReentrantLock();
+    /** Flag set to true when the request thread is joined */
+    private boolean mConnectionFinished = false;
+    /** Flag set to true by interrupt() and/or connection errors */
+    private boolean mConnectionFailed = false;
+    /** Lock protecting the access to mConnectionFailed */
+    private Lock mConnectionFailedLock = new ReentrantLock();
+
+    /** Lock on the loop in StreamEntity */
+    private Lock mStreamingReadyLock = new ReentrantLock();
+    /** Condition variable used to signal the loop is ready... */
+    private Condition mStreamingReady = mStreamingReadyLock.newCondition();
+
+    /** Used to pass around the block of data POSTed */
+    private Buffer mBuffer = new Buffer();
+    /** Used to signal that the block of data has been written */
+    private SignalConsumed mSignal = new SignalConsumed();
+
+    // inner classes
+
+    /**
+     * Implements the http request
+     */
+    class Connection implements Runnable {
+        public void run() {
+            boolean problem = false;
+            try {
+                if (Config.LOGV) {
+                    Log.i(LOG_TAG, "REQUEST : " + mMethod.getRequestLine());
+                }
+                mResponse = mClient.execute(mMethod);
+                if (mResponse != null) {
+                    if (Config.LOGV) {
+                        Log.i(LOG_TAG, "response (status line): "
+                              + mResponse.getStatusLine());
+                    }
+                    mResponseLine = "" + mResponse.getStatusLine();
+                } else {
+                    if (Config.LOGV) {
+                        Log.i(LOG_TAG, "problem, response == null");
+                    }
+                    problem = true;
+                }
+            } catch (IOException e) {
+                Log.e(LOG_TAG, "Connection IO exception ", e);
+                problem = true;
+            } catch (RuntimeException e) {
+                Log.e(LOG_TAG, "Connection runtime exception ", e);
+                problem = true;
+            }
+
+            if (!problem) {
+                if (Config.LOGV) {
+                    Log.i(LOG_TAG, "Request complete ("
+                          + mMethod.getRequestLine() + ")");
+                }
+            } else {
+                mConnectionFailedLock.lock();
+                mConnectionFailed = true;
+                mConnectionFailedLock.unlock();
+                if (Config.LOGV) {
+                    Log.i(LOG_TAG, "Request FAILED ("
+                          + mMethod.getRequestLine() + ")");
+                }
+                // We abort the execution in order to shutdown and release
+                // the underlying connection
+                mMethod.abort();
+                if (mPostEntity != null) {
+                    // If there is a post entity, we need to wake it up from
+                    // a potential deadlock
+                    mPostEntity.signalOutputStream();
+                }
+            }
+        }
+    }
+
+    /**
+     * simple buffer class implementing a producer/consumer model
+     */
+    class Buffer {
+        private DataPacket mPacket;
+        private boolean mEmpty = true;
+        public synchronized void put(DataPacket packet) {
+            while (!mEmpty) {
+                try {
+                    wait();
+                } catch (InterruptedException e) {
+                    if (Config.LOGV) {
+                        Log.i(LOG_TAG, "InterruptedException while putting " +
+                            "a DataPacket in the Buffer: " + e);
+                    }
+                }
+            }
+            mPacket = packet;
+            mEmpty = false;
+            notify();
+        }
+        public synchronized DataPacket get() {
+            while (mEmpty) {
+                try {
+                    wait();
+                } catch (InterruptedException e) {
+                    if (Config.LOGV) {
+                      Log.i(LOG_TAG, "InterruptedException while getting " +
+                          "a DataPacket in the Buffer: " + e);
+                    }
+                }
+            }
+            mEmpty = true;
+            notify();
+            return mPacket;
+        }
+    }
+
+    /**
+     * utility class used to block until the packet is signaled as being
+     * consumed
+     */
+    class SignalConsumed {
+        private boolean mConsumed = false;
+        public synchronized void waitUntilPacketConsumed() {
+            while (!mConsumed) {
+                try {
+                    wait();
+                } catch (InterruptedException e) {
+                    if (Config.LOGV) {
+                        Log.i(LOG_TAG, "InterruptedException while waiting " +
+                            "until a DataPacket is consumed: " + e);
+                    }
+                }
+            }
+            mConsumed = false;
+            notify();
+        }
+        public synchronized void packetConsumed() {
+            while (mConsumed) {
+                try {
+                    wait();
+                } catch (InterruptedException e) {
+                    if (Config.LOGV) {
+                        Log.i(LOG_TAG, "InterruptedException while indicating "
+                              + "that the DataPacket has been consumed: " + e);
+                    }
+                }
+            }
+            mConsumed = true;
+            notify();
+        }
+    }
+
+    /**
+     * Utility class encapsulating a packet of data
+     */
+    class DataPacket {
+        private byte[] mContent;
+        private int mLength;
+        public DataPacket(byte[] content, int length) {
+            mContent = content;
+            mLength = length;
+        }
+        public byte[] getBytes() {
+            return mContent;
+        }
+        public int getLength() {
+            return mLength;
+        }
+    }
+
+    /**
+     * HttpEntity class to write the bytes received by the C++ thread
+     * on the connection outputstream, in a streaming way.
+     * This entity is executed in the request thread.
+     * The writeTo() method is automatically called by the
+     * HttpPost execution; upon reception, we loop while receiving
+     * the data packets from the main thread, until completion
+     * or error. When done, we flush the outputstream.
+     * The main thread (sendPostData()) also blocks until the
+     * outputstream is made available (or an error happens)
+     */
+    class StreamEntity implements HttpEntity {
+        private OutputStream mOutputStream;
+
+        // HttpEntity interface methods
+
+        public boolean isRepeatable() {
+            return false;
+        }
+
+        public boolean isChunked() {
+            return false;
+        }
+
+        public long getContentLength() {
+            return mContentLength;
+        }
+
+        public Header getContentType() {
+            return null;
+        }
+
+        public Header getContentEncoding() {
+            return null;
+        }
+
+        public InputStream getContent() throws IOException {
+            return null;
+        }
+
+        public void writeTo(final OutputStream out) throws IOException {
+            // We signal that the outputstream is available
+            mStreamingReadyLock.lock();
+            mOutputStream = out;
+            mStreamingReady.signal();
+            mStreamingReadyLock.unlock();
+
+            // We then loop waiting on messages to process.
+            boolean finished = false;
+            while (!finished) {
+                DataPacket packet = mBuffer.get();
+                if (packet == null) {
+                    finished = true;
+                } else {
+                    write(packet);
+                }
+                mSignal.packetConsumed();
+                mConnectionFailedLock.lock();
+                if (mConnectionFailed) {
+                    if (Config.LOGV) {
+                        Log.i(LOG_TAG, "stopping loop on error");
+                    }
+                    finished = true;
+                }
+                mConnectionFailedLock.unlock();
+            }
+            if (Config.LOGV) {
+                Log.i(LOG_TAG, "flushing the outputstream...");
+            }
+            mOutputStream.flush();
+        }
+
+        public boolean isStreaming() {
+            return true;
+        }
+
+        public void consumeContent() throws IOException {
+            // Nothing to release
+        }
+
+        // local methods
+
+        private void write(DataPacket packet) {
+            try {
+                if (mOutputStream == null) {
+                    if (Config.LOGV) {
+                        Log.i(LOG_TAG, "NO OUTPUT STREAM !!!");
+                    }
+                    return;
+                }
+                mOutputStream.write(packet.getBytes(), 0, packet.getLength());
+                mOutputStream.flush();
+            } catch (IOException e) {
+                if (Config.LOGV) {
+                    Log.i(LOG_TAG, "exc: " + e);
+                }
+                mConnectionFailedLock.lock();
+                mConnectionFailed = true;
+                mConnectionFailedLock.unlock();
+            }
+        }
+
+        public boolean isReady() {
+            mStreamingReadyLock.lock();
+            try {
+                if (mOutputStream == null) {
+                    mStreamingReady.await();
+                }
+            } catch (InterruptedException e) {
+                if (Config.LOGV) {
+                    Log.i(LOG_TAG, "InterruptedException in "
+                          + "StreamEntity::isReady() : ", e);
+                }
+            } finally {
+                mStreamingReadyLock.unlock();
+            }
+            if (mOutputStream == null) {
+                return false;
+            }
+            return true;
+        }
+
+        public void signalOutputStream() {
+            mStreamingReadyLock.lock();
+            mStreamingReady.signal();
+            mStreamingReadyLock.unlock();
+        }
+    }
+
+    /**
+     * Initialize mBridgeThread using the TLS value of
+     * Thread.currentThread(). Called on start up of the native child
+     * thread.
+     */
+    public synchronized void initChildThread() {
+        mBridgeThread = Thread.currentThread();
+    }
+
+    public void setContentLength(long length) {
+        mContentLength = length;
+    }
+
+    /**
+     * Analagous to the native-side HttpRequest::open() function. This
+     * initializes an underlying HttpClient method, but does
+     * not go to the wire. On success, this enables a call to send() to
+     * initiate the transaction.
+     *
+     * @param method    The HTTP method, e.g GET or POST.
+     * @param url       The URL to open.
+     * @return          True on success with a complete HTTP response.
+     *                  False on failure.
+     */
+    public synchronized boolean open(String method, String url) {
+        if (Config.LOGV) {
+            Log.i(LOG_TAG, "open " + method + " " + url);
+        }
+        // Create the client
+        if (mConnectionFailed) {
+            // interrupt() could have been called even before open()
+            return false;
+        }
+        mClient = new DefaultHttpClient();
+        mClient.setHttpRequestRetryHandler(
+            new DefaultHttpRequestRetryHandler(0, false));
+        mBodyInputStream = null;
+        mResponseLine = null;
+        mResponseHeaders = null;
+        mPostEntity = null;
+        mHttpThread = null;
+        mConnectionFailed = false;
+        mConnectionFinished = false;
+
+        // Create the method. We support everything that
+        // Apache HttpClient supports, apart from TRACE.
+        if ("GET".equalsIgnoreCase(method)) {
+            mMethod = new HttpGet(url);
+        } else if ("POST".equalsIgnoreCase(method)) {
+            mMethod = new HttpPost(url);
+            mPostEntity = new StreamEntity();
+            ((HttpPost)mMethod).setEntity(mPostEntity);
+        } else if ("HEAD".equalsIgnoreCase(method)) {
+            mMethod = new HttpHead(url);
+        } else if ("PUT".equalsIgnoreCase(method)) {
+            mMethod = new HttpPut(url);
+        } else if ("DELETE".equalsIgnoreCase(method)) {
+            mMethod = new HttpDelete(url);
+        } else {
+            if (Config.LOGV) {
+                Log.i(LOG_TAG, "Method " + method + " not supported");
+            }
+            return false;
+        }
+        HttpParams params = mClient.getParams();
+        // We handle the redirections C++-side
+        HttpClientParams.setRedirecting(params, false);
+        HttpProtocolParams.setUseExpectContinue(params, false);
+        return true;
+    }
+
+    /**
+     * We use this to start the connection thread (doing the method execute).
+     * We usually always return true here, as the connection will run its
+     * course in the thread.
+     * We only return false if interrupted beforehand -- if a connection
+     * problem happens, we will thus fail in either sendPostData() or
+     * parseHeaders().
+     */
+    public synchronized boolean connectToRemote() {
+        boolean ret = false;
+        applyRequestHeaders();
+        mConnectionFailedLock.lock();
+        if (!mConnectionFailed) {
+            mHttpThread = new Thread(new Connection());
+            mHttpThread.start();
+        }
+        ret = mConnectionFailed;
+        mConnectionFailedLock.unlock();
+        return !ret;
+    }
+
+    /**
+     * Get the complete response line of the HTTP request. Only valid on
+     * completion of the transaction.
+     * @return The complete HTTP response line, e.g "HTTP/1.0 200 OK".
+     */
+    public synchronized String getResponseLine() {
+        return mResponseLine;
+    }
+
+    /**
+     * Wait for the request thread completion
+     * (unless already finished)
+     */
+    private void waitUntilConnectionFinished() {
+        if (Config.LOGV) {
+            Log.i(LOG_TAG, "waitUntilConnectionFinished("
+                  + mConnectionFinished + ")");
+        }
+        if (!mConnectionFinished) {
+            if (mHttpThread != null) {
+                try {
+                    mHttpThread.join();
+                    mConnectionFinished = true;
+                    if (Config.LOGV) {
+                        Log.i(LOG_TAG, "http thread joined");
+                    }
+                } catch (InterruptedException e) {
+                    if (Config.LOGV) {
+                        Log.i(LOG_TAG, "interrupted: " + e);
+                    }
+                }
+            } else {
+                Log.e(LOG_TAG, ">>> Trying to join on mHttpThread " +
+                      "when it does not exist!");
+            }
+        }
+    }
+
+    // Headers handling
+
+    /**
+     * Receive all headers from the server and populate
+     * mResponseHeaders.
+     * @return True if headers are successfully received, False on
+     *         connection error.
+     */
+    public synchronized boolean parseHeaders() {
+        mConnectionFailedLock.lock();
+        if (mConnectionFailed) {
+            mConnectionFailedLock.unlock();
+            return false;
+        }
+        mConnectionFailedLock.unlock();
+        waitUntilConnectionFinished();
+        mResponseHeaders = new HashMap<String, String[]>();
+        if (mResponse == null)
+            return false;
+
+        Header[] headers = mResponse.getAllHeaders();
+        for (int i = 0; i < headers.length; i++) {
+            Header header = headers[i];
+            if (Config.LOGV) {
+                Log.i(LOG_TAG, "header " + header.getName()
+                      + " -> " + header.getValue());
+            }
+            setResponseHeader(header.getName(), header.getValue());
+        }
+
+        return true;
+    }
+
+    /**
+     * Set a header to send with the HTTP request. Will not take effect
+     * on a transaction already in progress. The key is associated
+     * case-insensitive, but stored case-sensitive.
+     * @param name  The name of the header, e.g "Set-Cookie".
+     * @param value The value for this header, e.g "text/html".
+     */
+    public synchronized void setRequestHeader(String name, String value) {
+        String[] mapValue = { name, value };
+        if (Config.LOGV) {
+            Log.i(LOG_TAG, "setRequestHeader: " + name + " => " + value);
+        }
+        if (name.equalsIgnoreCase(KEY_CONTENT_LENGTH)) {
+            setContentLength(Long.parseLong(value));
+        } else {
+            mRequestHeaders.put(name.toLowerCase(), mapValue);
+        }
+    }
+
+    /**
+     * Returns the value associated with the given request header.
+     * @param name The name of the request header, non-null, case-insensitive.
+     * @return The value associated with the request header, or null if
+     *         not set, or error.
+     */
+    public synchronized String getRequestHeader(String name) {
+        String[] value = mRequestHeaders.get(name.toLowerCase());
+        if (value != null) {
+            return value[HEADERS_MAP_INDEX_VALUE];
+        } else {
+            return null;
+        }
+    }
+
+    private void applyRequestHeaders() {
+        if (mMethod == null)
+            return;
+        Iterator<String[]> it = mRequestHeaders.values().iterator();
+        while (it.hasNext()) {
+            // Set the key case-sensitive.
+            String[] entry = it.next();
+            if (Config.LOGV) {
+                Log.i(LOG_TAG, "apply header " + entry[HEADERS_MAP_INDEX_KEY] +
+                    " => " + entry[HEADERS_MAP_INDEX_VALUE]);
+            }
+            mMethod.setHeader(entry[HEADERS_MAP_INDEX_KEY],
+                                     entry[HEADERS_MAP_INDEX_VALUE]);
+        }
+    }
+
+    /**
+     * Returns the value associated with the given response header.
+     * @param name The name of the response header, non-null, case-insensitive.
+     * @return The value associated with the response header, or null if
+     *         not set or error.
+     */
+    public synchronized String getResponseHeader(String name) {
+        if (mResponseHeaders != null) {
+            String[] value = mResponseHeaders.get(name.toLowerCase());
+            if (value != null) {
+                return value[HEADERS_MAP_INDEX_VALUE];
+            } else {
+                return null;
+            }
+        } else {
+            if (Config.LOGV) {
+                Log.i(LOG_TAG, "getResponseHeader() called but "
+                      + "response not received");
+            }
+            return null;
+        }
+    }
+
+    /**
+     * Return all response headers, separated by CR-LF line endings, and
+     * ending with a trailing blank line. This mimics the format of the
+     * raw response header up to but not including the body.
+     * @return A string containing the entire response header.
+     */
+    public synchronized String getAllResponseHeaders() {
+        if (mResponseHeaders == null) {
+            if (Config.LOGV) {
+                Log.i(LOG_TAG, "getAllResponseHeaders() called but "
+                      + "response not received");
+            }
+            return null;
+        }
+        StringBuilder result = new StringBuilder();
+        Iterator<String[]> it = mResponseHeaders.values().iterator();
+        while (it.hasNext()) {
+            String[] entry = it.next();
+            // Output the "key: value" lines.
+            result.append(entry[HEADERS_MAP_INDEX_KEY]);
+            result.append(": ");
+            result.append(entry[HEADERS_MAP_INDEX_VALUE]);
+            result.append(HTTP_LINE_ENDING);
+        }
+        result.append(HTTP_LINE_ENDING);
+        return result.toString();
+    }
+
+
+    /**
+     * Set a response header and associated value. The key is associated
+     * case-insensitively, but stored case-sensitively.
+     * @param name  Case sensitive request header key.
+     * @param value The associated value.
+     */
+    private void setResponseHeader(String name, String value) {
+        if (Config.LOGV) {
+            Log.i(LOG_TAG, "Set response header " + name + ": " + value);
+        }
+        String mapValue[] = { name, value };
+        mResponseHeaders.put(name.toLowerCase(), mapValue);
+    }
+
+    // Cookie handling
+
+    /**
+     * Get the cookie for the given URL.
+     * @param url The fully qualified URL.
+     * @return A string containing the cookie for the URL if it exists,
+     *         or null if not.
+     */
+    public static String getCookieForUrl(String url) {
+        // Get the cookie for this URL, set as a header
+        return CookieManager.getInstance().getCookie(url);
+    }
+
+    /**
+     * Set the cookie for the given URL.
+     * @param url    The fully qualified URL.
+     * @param cookie The new cookie value.
+     * @return A string containing the cookie for the URL if it exists,
+     *         or null if not.
+     */
+    public static void setCookieForUrl(String url, String cookie) {
+        // Get the cookie for this URL, set as a header
+        CookieManager.getInstance().setCookie(url, cookie);
+    }
+
+    // Cache handling
+
+    /**
+     * Perform a request using LocalServer if possible. Initializes
+     * class members so that receive() will obtain data from the stream
+     * provided by the response.
+     * @param url The fully qualified URL to try in LocalServer.
+     * @return True if the url was found and is now setup to receive.
+     *         False if not found, with no side-effect.
+     */
+    public synchronized boolean useLocalServerResult(String url) {
+        UrlInterceptHandlerGears handler =
+            UrlInterceptHandlerGears.getInstance();
+        if (handler == null) {
+            return false;
+        }
+        UrlInterceptHandlerGears.ServiceResponse serviceResponse =
+            handler.getServiceResponse(url, mRequestHeaders);
+        if (serviceResponse == null) {
+            if (Config.LOGV) {
+                Log.i(LOG_TAG, "No response in LocalServer");
+            }
+            return false;
+        }
+        // LocalServer will handle this URL. Initialize stream and
+        // response.
+        mBodyInputStream = serviceResponse.getInputStream();
+        mResponseLine = serviceResponse.getStatusLine();
+        mResponseHeaders = serviceResponse.getResponseHeaders();
+        if (Config.LOGV) {
+            Log.i(LOG_TAG, "Got response from LocalServer: " + mResponseLine);
+        }
+        return true;
+    }
+
+    /**
+     * Perform a request using the cache result if present. Initializes
+     * class members so that receive() will obtain data from the cache.
+     * @param url The fully qualified URL to try in the cache.
+     * @return True is the url was found and is now setup to receive
+     *         from cache. False if not found, with no side-effect.
+     */
+    public synchronized boolean useCacheResult(String url) {
+        // Try the browser's cache. CacheManager wants a Map<String, String>.
+        Map<String, String> cacheRequestHeaders = new HashMap<String, String>();
+        Iterator<Map.Entry<String, String[]>> it =
+            mRequestHeaders.entrySet().iterator();
+        while (it.hasNext()) {
+            Map.Entry<String, String[]> entry = it.next();
+            cacheRequestHeaders.put(
+                entry.getKey(),
+                entry.getValue()[HEADERS_MAP_INDEX_VALUE]);
+        }
+        CacheResult mCacheResult =
+            CacheManager.getCacheFile(url, cacheRequestHeaders);
+        if (mCacheResult == null) {
+            if (Config.LOGV) {
+                Log.i(LOG_TAG, "No CacheResult for " + url);
+            }
+            return false;
+        }
+        if (Config.LOGV) {
+            Log.i(LOG_TAG, "Got CacheResult from browser cache");
+        }
+        // Check for expiry. -1 is "never", otherwise milliseconds since 1970.
+        // Can be compared to System.currentTimeMillis().
+        long expires = mCacheResult.getExpires();
+        if (expires >= 0 && System.currentTimeMillis() >= expires) {
+            if (Config.LOGV) {
+                Log.i(LOG_TAG, "CacheResult expired "
+                    + (System.currentTimeMillis() - expires)
+                    + " milliseconds ago");
+            }
+            // Cache hit has expired. Do not return it.
+            return false;
+        }
+        // Setup the mBodyInputStream to come from the cache.
+        mBodyInputStream = mCacheResult.getInputStream();
+        if (mBodyInputStream == null) {
+            // Cache result may have gone away.
+            if (Config.LOGV) {
+                Log.i(LOG_TAG, "No mBodyInputStream for CacheResult " + url);
+            }
+            return false;
+        }
+        // Cache hit. Parse headers.
+        synthesizeHeadersFromCacheResult(mCacheResult);
+        return true;
+    }
+
+    /**
+     * Take the limited set of headers in a CacheResult and synthesize
+     * response headers.
+     * @param cacheResult A CacheResult to populate mResponseHeaders with.
+     */
+    private void synthesizeHeadersFromCacheResult(CacheResult cacheResult) {
+        int statusCode = cacheResult.getHttpStatusCode();
+        // The status message is informal, so we can greatly simplify it.
+        String statusMessage;
+        if (statusCode >= 200 && statusCode < 300) {
+            statusMessage = "OK";
+        } else if (statusCode >= 300 && statusCode < 400) {
+            statusMessage = "MOVED";
+        } else {
+            statusMessage = "UNAVAILABLE";
+        }
+        // Synthesize the response line.
+        mResponseLine = "HTTP/1.1 " + statusCode + " " + statusMessage;
+        if (Config.LOGV) {
+            Log.i(LOG_TAG, "Synthesized " + mResponseLine);
+        }
+        // Synthesize the returned headers from cache.
+        mResponseHeaders = new HashMap<String, String[]>();
+        String contentLength = Long.toString(cacheResult.getContentLength());
+        setResponseHeader(KEY_CONTENT_LENGTH, contentLength);
+        long expires = cacheResult.getExpires();
+        if (expires >= 0) {
+            // "Expires" header is valid and finite. Milliseconds since 1970
+            // epoch, formatted as RFC-1123.
+            String expiresString = DateUtils.formatDate(new Date(expires));
+            setResponseHeader(KEY_EXPIRES, expiresString);
+        }
+        String lastModified = cacheResult.getLastModified();
+        if (lastModified != null) {
+            // Last modification time of the page. Passed end-to-end, but
+            // not used by us.
+            setResponseHeader(KEY_LAST_MODIFIED, lastModified);
+        }
+        String eTag = cacheResult.getETag();
+        if (eTag != null) {
+            // Entity tag. A kind of GUID to identify identical resources.
+            setResponseHeader(KEY_ETAG, eTag);
+        }
+        String location = cacheResult.getLocation();
+        if (location != null) {
+            // If valid, refers to the location of a redirect.
+            setResponseHeader(KEY_LOCATION, location);
+        }
+        String mimeType = cacheResult.getMimeType();
+        if (mimeType == null) {
+            // Use a safe default MIME type when none is
+            // specified. "text/plain" is safe to render in the browser
+            // window (even if large) and won't be intepreted as anything
+            // that would cause execution.
+            mimeType = DEFAULT_MIME_TYPE;
+        }
+        String encoding = cacheResult.getEncoding();
+        // Encoding may not be specified. No default.
+        String contentType = mimeType;
+        if (encoding != null) {
+            if (encoding.length() > 0) {
+                contentType += "; charset=" + encoding;
+            }
+        }
+        setResponseHeader(KEY_CONTENT_TYPE, contentType);
+    }
+
+    /**
+     * Create a CacheResult for this URL. This enables the repsonse body
+     * to be sent in calls to appendCacheResult().
+     * @param url          The fully qualified URL to add to the cache.
+     * @param responseCode The response code returned for the request, e.g 200.
+     * @param mimeType     The MIME type of the body, e.g "text/plain".
+     * @param encoding     The encoding, e.g "utf-8". Use "" for unknown.
+     */
+    public synchronized boolean createCacheResult(
+        String url, int responseCode, String mimeType, String encoding) {
+        if (Config.LOGV) {
+            Log.i(LOG_TAG, "Making cache entry for " + url);
+        }
+        // Take the headers and parse them into a format needed by
+        // CacheManager.
+        Headers cacheHeaders = new Headers();
+        Iterator<Map.Entry<String, String[]>> it =
+            mResponseHeaders.entrySet().iterator();
+        while (it.hasNext()) {
+            Map.Entry<String, String[]> entry = it.next();
+            // Headers.parseHeader() expects lowercase keys.
+            String keyValue = entry.getKey() + ": "
+                + entry.getValue()[HEADERS_MAP_INDEX_VALUE];
+            CharArrayBuffer buffer = new CharArrayBuffer(keyValue.length());
+            buffer.append(keyValue);
+            // Parse it into the header container.
+            cacheHeaders.parseHeader(buffer);
+        }
+        mCacheResult = CacheManager.createCacheFile(
+            url, responseCode, cacheHeaders, mimeType, true);
+        if (mCacheResult != null) {
+            if (Config.LOGV) {
+                Log.i(LOG_TAG, "Saving into cache");
+            }
+            mCacheResult.setEncoding(encoding);
+            mCacheResultUrl = url;
+            return true;
+        } else {
+            if (Config.LOGV) {
+                Log.i(LOG_TAG, "Couldn't create mCacheResult");
+            }
+            return false;
+        }
+    }
+
+    /**
+     * Add data from the response body to the CacheResult created with
+     * createCacheResult().
+     * @param data  A byte array of the next sequential bytes in the
+     *              response body.
+     * @param bytes The number of bytes to write from the start of
+     *              the array.
+     * @return True if all bytes successfully written, false on failure.
+     */
+    public synchronized boolean appendCacheResult(byte[] data, int bytes) {
+        if (mCacheResult == null) {
+            if (Config.LOGV) {
+                Log.i(LOG_TAG, "appendCacheResult() called without a "
+                      + "CacheResult initialized");
+            }
+            return false;
+        }
+        try {
+            mCacheResult.getOutputStream().write(data, 0, bytes);
+        } catch (IOException ex) {
+            if (Config.LOGV) {
+                Log.i(LOG_TAG, "Got IOException writing cache data: " + ex);
+            }
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * Save the completed CacheResult into the CacheManager. This must
+     * have been created first with createCacheResult().
+     * @return Returns true if the entry has been successfully saved.
+     */
+    public synchronized boolean saveCacheResult() {
+        if (mCacheResult == null || mCacheResultUrl == null) {
+            if (Config.LOGV) {
+                Log.i(LOG_TAG, "Tried to save cache result but "
+                      + "createCacheResult not called");
+            }
+            return false;
+        }
+
+        if (Config.LOGV) {
+            Log.i(LOG_TAG, "Saving cache result");
+        }
+        CacheManager.saveCacheFile(mCacheResultUrl, mCacheResult);
+        mCacheResult = null;
+        mCacheResultUrl = null;
+        return true;
+    }
+
+
+   /**
+     * Interrupt a blocking IO operation. This will cause the child
+     * thread to expediently return from an operation if it was stuck at
+     * the time. Note that this inherently races, and unfortunately
+     * requires the caller to loop.
+     */
+    public synchronized void interrupt() {
+        if (Config.LOGV) {
+            Log.i(LOG_TAG, "INTERRUPT CALLED");
+        }
+        mConnectionFailedLock.lock();
+        mConnectionFailed = true;
+        mConnectionFailedLock.unlock();
+        if (mMethod != null) {
+            mMethod.abort();
+        }
+        if (mHttpThread != null) {
+            waitUntilConnectionFinished();
+        }
+    }
+
+    /**
+     * Receive the next sequential bytes of the response body after
+     * successful connection. This will receive up to the size of the
+     * provided byte array. If there is no body, this will return 0
+     * bytes on the first call after connection.
+     * @param  buf A pre-allocated byte array to receive data into.
+     * @return The number of bytes from the start of the array which
+     *         have been filled, 0 on EOF, or negative on error.
+     */
+    public synchronized int receive(byte[] buf) {
+        if (mBodyInputStream == null) {
+            // If this is the first call, setup the InputStream. This may
+            // fail if there were headers, but no body returned by the
+            // server.
+            try {
+                if (mResponse != null) {
+                    HttpEntity entity = mResponse.getEntity();
+                    mBodyInputStream = entity.getContent();
+                }
+            } catch (IOException inputException) {
+                if (Config.LOGV) {
+                    Log.i(LOG_TAG, "Failed to connect InputStream: "
+                          + inputException);
+                }
+                // Not unexpected. For example, 404 response return headers,
+                // and sometimes a body with a detailed error.
+            }
+            if (mBodyInputStream == null) {
+                // No error stream either. Treat as a 0 byte response.
+                if (Config.LOGV) {
+                    Log.i(LOG_TAG, "No InputStream");
+                }
+                return 0; // EOF.
+            }
+        }
+        int ret;
+        try {
+            int got = mBodyInputStream.read(buf);
+            if (got > 0) {
+                // Got some bytes, not EOF.
+                ret = got;
+            } else {
+                // EOF.
+                mBodyInputStream.close();
+                ret = 0;
+            }
+        } catch (IOException e) {
+            // An abort() interrupts us by calling close() on our stream.
+            if (Config.LOGV) {
+                Log.i(LOG_TAG, "Got IOException in mBodyInputStream.read(): ", e);
+            }
+            ret = -1;
+        }
+        return ret;
+    }
+
+    /**
+     * For POST method requests, send a stream of data provided by the
+     * native side in repeated callbacks.
+     * We put the data in mBuffer, and wait until it is consumed
+     * by the StreamEntity in the request thread.
+     * @param data  A byte array containing the data to sent, or null
+     *              if indicating EOF.
+     * @param bytes The number of bytes from the start of the array to
+     *              send, or 0 if indicating EOF.
+     * @return True if all bytes were successfully sent, false on error.
+     */
+    public boolean sendPostData(byte[] data, int bytes) {
+        mConnectionFailedLock.lock();
+        if (mConnectionFailed) {
+            mConnectionFailedLock.unlock();
+            return false;
+        }
+        mConnectionFailedLock.unlock();
+        if (mPostEntity == null) return false;
+
+        // We block until the outputstream is available
+        // (or in case of connection error)
+        if (!mPostEntity.isReady()) return false;
+
+        if (data == null && bytes == 0) {
+            mBuffer.put(null);
+        } else {
+            mBuffer.put(new DataPacket(data, bytes));
+        }
+        mSignal.waitUntilPacketConsumed();
+
+        mConnectionFailedLock.lock();
+        if (mConnectionFailed) {
+            Log.e(LOG_TAG, "failure");
+            mConnectionFailedLock.unlock();
+            return false;
+        }
+        mConnectionFailedLock.unlock();
+        return true;
+    }
+
+}
diff --git a/core/java/android/webkit/gears/DesktopAndroid.java b/core/java/android/webkit/gears/DesktopAndroid.java
new file mode 100644
index 0000000..ee8ca49
--- /dev/null
+++ b/core/java/android/webkit/gears/DesktopAndroid.java
@@ -0,0 +1,109 @@
+// Copyright 2008 The Android Open Source Project
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+//  1. Redistributions of source code must retain the above copyright notice,
+//     this list of conditions and the following disclaimer.
+//  2. Redistributions in binary form must reproduce the above copyright notice,
+//     this list of conditions and the following disclaimer in the documentation
+//     and/or other materials provided with the distribution.
+//  3. Neither the name of Google Inc. nor the names of its contributors may be
+//     used to endorse or promote products derived from this software without
+//     specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+// WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+// EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+// OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+// ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+package android.webkit.gears;
+
+import android.content.Context;
+import android.content.ComponentName;
+import android.content.Intent;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.net.Uri;
+import android.util.Log;
+import android.webkit.WebView;
+
+/**
+ * Utility class to create a shortcut on Android
+ */
+public class DesktopAndroid {
+
+  private static final String TAG = "Gears-J-Desktop";
+  private static final String EXTRA_SHORTCUT_DUPLICATE = "duplicate";
+  private static final String ACTION_INSTALL_SHORTCUT =
+      "com.android.launcher.action.INSTALL_SHORTCUT";
+
+  // Android now enforces a 64x64 limit for the icon
+  private static int MAX_WIDTH = 64;
+  private static int MAX_HEIGHT = 64;
+
+  /**
+   * Small utility function returning a Bitmap object.
+   *
+   * @param path the icon path
+   */
+  private static Bitmap getBitmap(String path) {
+    return BitmapFactory.decodeFile(path);
+  }
+
+  /**
+   * Create a shortcut for a webpage.
+   *
+   * <p>To set a shortcut on Android, we use the ACTION_INSTALL_SHORTCUT
+   * from the default Home application. We only have to create an Intent
+   * containing extra parameters specifying the shortcut.
+   * <p>Note: the shortcut mechanism is not system wide and depends on the
+   * Home application; if phone carriers decide to rewrite a Home application
+   * that does not accept this Intent, no shortcut will be added.
+   *
+   * @param webview the webview we are called from
+   * @param title the shortcut's title
+   * @param url the shortcut's url
+   * @param imagePath the local path of the shortcut's icon
+   */
+  public static void setShortcut(WebView webview, String title,
+        String url, String imagePath) {
+    Context context = webview.getContext();
+
+    Intent viewWebPage = new Intent(Intent.ACTION_VIEW);
+    viewWebPage.setData(Uri.parse(url));
+    viewWebPage.addCategory(Intent.CATEGORY_BROWSABLE);
+
+    Intent intent = new Intent(ACTION_INSTALL_SHORTCUT);
+    intent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, viewWebPage);
+    intent.putExtra(Intent.EXTRA_SHORTCUT_NAME, title);
+
+    // We disallow the creation of duplicate shortcuts (i.e. same
+    // url, same title, but different screen position).
+    intent.putExtra(EXTRA_SHORTCUT_DUPLICATE, false);
+
+    Bitmap bmp = getBitmap(imagePath);
+    if (bmp != null) {
+      if ((bmp.getWidth() > MAX_WIDTH) ||
+          (bmp.getHeight() > MAX_HEIGHT)) {
+        Bitmap scaledBitmap = Bitmap.createScaledBitmap(bmp,
+            MAX_WIDTH, MAX_HEIGHT, true);
+        intent.putExtra(Intent.EXTRA_SHORTCUT_ICON, scaledBitmap);
+      } else {
+        intent.putExtra(Intent.EXTRA_SHORTCUT_ICON, bmp);
+      }
+    } else {
+      // This should not happen as we just downloaded the icon
+      Log.e(TAG, "icon file <" + imagePath + "> not found");
+    }
+
+    context.sendBroadcast(intent);
+  }
+
+}
diff --git a/core/java/android/webkit/gears/NativeDialog.java b/core/java/android/webkit/gears/NativeDialog.java
new file mode 100644
index 0000000..9e2b375
--- /dev/null
+++ b/core/java/android/webkit/gears/NativeDialog.java
@@ -0,0 +1,142 @@
+// Copyright 2008 The Android Open Source Project
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+//  1. Redistributions of source code must retain the above copyright notice,
+//     this list of conditions and the following disclaimer.
+//  2. Redistributions in binary form must reproduce the above copyright notice,
+//     this list of conditions and the following disclaimer in the documentation
+//     and/or other materials provided with the distribution.
+//  3. Neither the name of Google Inc. nor the names of its contributors may be
+//     used to endorse or promote products derived from this software without
+//     specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+// WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+// EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+// OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+// ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+package android.webkit.gears;
+
+import android.content.ActivityNotFoundException;
+import android.content.Context;
+import android.content.Intent;
+import android.util.Log;
+
+import java.io.File;
+import java.lang.InterruptedException;
+import java.util.concurrent.locks.Condition;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+
+/**
+ * Utility class to call a modal native dialog on Android
+ * The dialog itself is an Activity defined in the Browser.
+ * @hide
+ */
+public class NativeDialog {
+
+  private static final String TAG = "Gears-J-NativeDialog";
+
+  private final String DIALOG_PACKAGE = "com.android.browser";
+  private final String DIALOG_CLASS = DIALOG_PACKAGE + ".GearsNativeDialog";
+
+  private static Lock mLock = new ReentrantLock();
+  private static Condition mDialogFinished = mLock.newCondition();
+  private static String mResults = null;
+
+  private static boolean mAsynchronousDialog;
+
+  /**
+   * Utility function to build the intent calling the
+   * dialog activity
+   */
+  private Intent createIntent(String type, String arguments) {
+    Intent intent = new Intent();
+    intent.setClassName(DIALOG_PACKAGE, DIALOG_CLASS);
+    intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+    intent.putExtra("dialogArguments", arguments);
+    intent.putExtra("dialogType", type);
+    return intent;
+  }
+
+  /**
+   * Opens a native dialog synchronously and waits for its completion.
+   *
+   * The dialog is an activity (GearsNativeDialog) provided by the Browser
+   * that we call via startActivity(). Contrary to a normal activity though,
+   * we need to block until it returns. To do so, we define a static lock
+   * object in this class, which GearsNativeDialog can unlock once done
+   */
+  public String showDialog(Context context, String file,
+      String arguments) {
+
+    try {
+      mAsynchronousDialog = false;
+      mLock.lock();
+      File path = new File(file);
+      String fileName = path.getName();
+      String type = fileName.substring(0, fileName.indexOf(".html"));
+      Intent intent = createIntent(type, arguments);
+
+      mResults = null;
+      context.startActivity(intent);
+      mDialogFinished.await();
+    } catch (InterruptedException e) {
+      Log.e(TAG, "exception e: " + e);
+    } catch (ActivityNotFoundException e) {
+      Log.e(TAG, "exception e: " + e);
+    } finally {
+      mLock.unlock();
+    }
+
+    return mResults;
+  }
+
+  /**
+   * Opens a native dialog asynchronously
+   *
+   * The dialog is an activity (GearsNativeDialog) provided by the
+   * Browser.
+   */
+  public void showAsyncDialog(Context context, String type,
+                           String arguments) {
+    mAsynchronousDialog = true;
+    Intent intent = createIntent(type, arguments);
+    context.startActivity(intent);
+  }
+
+  /**
+   * Static method that GearsNativeDialog calls to unlock us
+   */
+  public static void signalFinishedDialog() {
+    if (!mAsynchronousDialog) {
+      mLock.lock();
+      mDialogFinished.signal();
+      mLock.unlock();
+    } else {
+      // we call the native callback
+      closeAsynchronousDialog(mResults);
+    }
+  }
+
+  /**
+   * Static method that GearsNativeDialog calls to set the
+   * dialog's result
+   */
+  public static void closeDialog(String res) {
+    mResults = res;
+  }
+
+  /**
+   * Native callback method
+   */
+  private native static void closeAsynchronousDialog(String res);
+}
diff --git a/core/java/android/webkit/gears/PluginSettings.java b/core/java/android/webkit/gears/PluginSettings.java
new file mode 100644
index 0000000..2d0cc13
--- /dev/null
+++ b/core/java/android/webkit/gears/PluginSettings.java
@@ -0,0 +1,79 @@
+// Copyright 2008 The Android Open Source Project
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+//  1. Redistributions of source code must retain the above copyright notice,
+//     this list of conditions and the following disclaimer.
+//  2. Redistributions in binary form must reproduce the above copyright notice,
+//     this list of conditions and the following disclaimer in the documentation
+//     and/or other materials provided with the distribution.
+//  3. Neither the name of Google Inc. nor the names of its contributors may be
+//     used to endorse or promote products derived from this software without
+//     specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+// WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+// EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+// OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+// ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+package android.webkit.gears;
+
+import android.content.Context;
+import android.util.Log;
+import android.webkit.Plugin;
+import android.webkit.Plugin.PreferencesClickHandler;
+
+/**
+ * Simple bridge class intercepting the click in the
+ * browser plugin list and calling the Gears settings
+ * dialog.
+ */
+public class PluginSettings {
+
+  private static final String TAG = "Gears-J-PluginSettings";
+  private Context mContext;
+
+  public PluginSettings(Plugin plugin) {
+    plugin.setClickHandler(new ClickHandler());
+  }
+
+  /**
+   * We do not call the dialog synchronously here as the main
+   * message loop would be blocked, so we call it via a secondary thread.
+   */
+  private class ClickHandler implements PreferencesClickHandler {
+    public void handleClickEvent(Context context) {
+      mContext = context.getApplicationContext();
+      Thread startDialog = new Thread(new StartDialog(context));
+      startDialog.start();
+    }
+  }
+
+  /**
+   * Simple wrapper class to call the gears native method in
+   * a separate thread (the native code will then instanciate a NativeDialog
+   * object which will start the GearsNativeDialog activity defined in
+   * the Browser).
+   */
+  private class StartDialog implements Runnable {
+    Context mContext;
+
+    public StartDialog(Context context) {
+      mContext = context;
+    }
+
+    public void run() {
+      runSettingsDialog(mContext);
+    }
+  }
+
+  private static native void runSettingsDialog(Context c);
+
+}
diff --git a/core/java/android/webkit/gears/UrlInterceptHandlerGears.java b/core/java/android/webkit/gears/UrlInterceptHandlerGears.java
new file mode 100644
index 0000000..2a5cbe9
--- /dev/null
+++ b/core/java/android/webkit/gears/UrlInterceptHandlerGears.java
@@ -0,0 +1,501 @@
+// Copyright 2008, The Android Open Source Project
+//
+// Redistribution and use in source and binary forms, with or without 
+// modification, are permitted provided that the following conditions are met:
+//
+//  1. Redistributions of source code must retain the above copyright notice, 
+//     this list of conditions and the following disclaimer.
+//  2. Redistributions in binary form must reproduce the above copyright notice,
+//     this list of conditions and the following disclaimer in the documentation
+//     and/or other materials provided with the distribution.
+//  3. Neither the name of Google Inc. nor the names of its contributors may be
+//     used to endorse or promote products derived from this software without
+//     specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+// WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 
+// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+// EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+// OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 
+// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 
+// ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+package android.webkit.gears;
+
+import android.net.http.Headers;
+import android.util.Log;
+import android.webkit.CacheManager;
+import android.webkit.CacheManager.CacheResult;
+import android.webkit.Plugin;
+import android.webkit.UrlInterceptRegistry;
+import android.webkit.UrlInterceptHandler;
+import android.webkit.WebView;
+
+import org.apache.http.impl.cookie.DateUtils;
+import org.apache.http.util.CharArrayBuffer;
+
+import java.io.*;
+import java.util.*;
+
+/**
+ * Services requests to handle URLs coming from the browser or
+ * HttpRequestAndroid. This registers itself with the
+ * UrlInterceptRegister in Android so we get a chance to service all
+ * URLs passing through the browser before anything else.
+ */
+public class UrlInterceptHandlerGears implements UrlInterceptHandler {
+  /** Singleton instance. */
+  private static UrlInterceptHandlerGears instance;
+  /** Debug logging tag. */
+  private static final String LOG_TAG = "Gears-J";
+  /** Buffer size for reading/writing streams. */
+  private static final int BUFFER_SIZE = 4096;
+  /**
+   * Number of milliseconds to expire LocalServer temporary entries in
+   * the browser's cache. Somewhat arbitrarily chosen as a compromise
+   * between being a) long enough not to expire during page load and
+   * b) short enough to evict entries during a session. */
+  private static final int CACHE_EXPIRY_MS = 60000; // 1 minute.
+  /** Enable/disable all logging in this class. */
+  private static boolean logEnabled = false;
+  /** The unmodified (case-sensitive) key in the headers map is the
+   * same index as used by HttpRequestAndroid. */
+  public static final int HEADERS_MAP_INDEX_KEY =
+      ApacheHttpRequestAndroid.HEADERS_MAP_INDEX_KEY;
+  /** The associated value in the headers map is the same index as
+   * used by HttpRequestAndroid. */
+  public static final int HEADERS_MAP_INDEX_VALUE =
+      ApacheHttpRequestAndroid.HEADERS_MAP_INDEX_VALUE;
+
+  /**
+   * Object passed to the native side, containing information about
+   * the URL to service.
+   */
+  public static class ServiceRequest {
+    // The URL being requested.
+    private String url;
+    // Request headers. Map of lowercase key to [ unmodified key, value ].
+    private Map<String, String[]> requestHeaders;
+
+    /**
+     * Initialize members on construction.
+     * @param url The URL being requested.
+     * @param requestHeaders Headers associated with the request,
+     *                       or null if none.
+     *                       Map of lowercase key to [ unmodified key, value ].
+     */
+    public ServiceRequest(String url, Map<String, String[]> requestHeaders) {
+      this.url = url;
+      this.requestHeaders = requestHeaders;
+    }
+
+    /**
+     * Returns the URL being requested.
+     * @return The URL being requested.
+     */
+    public String getUrl() {
+      return url;
+    }
+
+    /**
+     * Get the value associated with a request header key, if any.
+     * @param header The key to find, case insensitive.
+     * @return The value associated with this header, or null if not found.
+     */
+    public String getRequestHeader(String header) {
+      if (requestHeaders != null) {
+        String[] value = requestHeaders.get(header.toLowerCase());
+        if (value != null) {
+          return value[HEADERS_MAP_INDEX_VALUE];
+        } else {
+          return null;
+        }
+      } else {
+        return null;
+      }
+    }
+  }
+
+  /**
+   * Object returned by the native side, containing information needed
+   * to pass the entire response back to the browser or
+   * HttpRequestAndroid. Works from either an in-memory array or a
+   * file on disk.
+   */
+  public class ServiceResponse {
+    // The response status code, e.g 200.
+    private int statusCode;
+    // The full status line, e.g "HTTP/1.1 200 OK".
+    private String statusLine;
+    // All headers associated with the response. Map of lowercase key
+    // to [ unmodified key, value ].
+    private Map<String, String[]> responseHeaders =
+        new HashMap<String, String[]>();
+    // The MIME type, e.g "text/html".
+    private String mimeType;
+    // The encoding, e.g "utf-8", or null if none.
+    private String encoding;
+    // The stream which contains the body when read().
+    private InputStream inputStream;
+
+    /**
+     * Initialize members using an in-memory array to return the body.
+     * @param statusCode The response status code, e.g 200.
+     * @param statusLine The full status line, e.g "HTTP/1.1 200 OK".
+     * @param mimeType The MIME type, e.g "text/html".
+     * @param encoding Encoding, e.g "utf-8" or null if none.
+     * @param body The response body as a byte array, non-empty.
+     */
+    void setResultArray(
+        int statusCode,
+        String statusLine,
+        String mimeType,
+        String encoding,
+        byte[] body) {
+      this.statusCode = statusCode;
+      this.statusLine = statusLine;
+      this.mimeType = mimeType;
+      this.encoding = encoding;
+      // Setup a stream to read out of the byte array.
+      this.inputStream = new ByteArrayInputStream(body);
+    }
+    
+    /**
+     * Initialize members using a file on disk to return the body.
+     * @param statusCode The response status code, e.g 200.
+     * @param statusLine The full status line, e.g "HTTP/1.1 200 OK".
+     * @param mimeType The MIME type, e.g "text/html".
+     * @param encoding Encoding, e.g "utf-8" or null if none.
+     * @param path Full path to the file containing the body.
+     * @return True if the file is successfully setup to stream,
+     *         false on error such as file not found.
+     */
+    boolean setResultFile(
+        int statusCode,
+        String statusLine,
+        String mimeType,
+        String encoding,
+        String path) {
+      this.statusCode = statusCode;
+      this.statusLine = statusLine;
+      this.mimeType = mimeType;
+      this.encoding = encoding;
+      try {
+        // Setup a stream to read out of a file on disk.
+        this.inputStream = new FileInputStream(new File(path));
+        return true;
+      } catch (java.io.FileNotFoundException ex) {
+        log("File not found: " + path);
+        return false;
+      }
+    }
+    
+    /**
+     * Set a response header, adding its settings to the header members.
+     * @param key   The case sensitive key for the response header,
+     *              e.g "Set-Cookie".
+     * @param value The value associated with this key, e.g "cookie1234".
+     */
+    public void setResponseHeader(String key, String value) {
+      // The map value contains the unmodified key (not lowercase).
+      String[] mapValue = { key, value };
+      responseHeaders.put(key.toLowerCase(), mapValue);
+    }
+
+    /**
+     * Return the "Content-Type" header possibly supplied by a
+     * previous setResponseHeader().
+     * @return The "Content-Type" value, or null if not present.
+     */
+    public String getContentType() {
+      // The map keys are lowercase.
+      String[] value = responseHeaders.get("content-type");
+      if (value != null) {
+        return value[HEADERS_MAP_INDEX_VALUE];
+      } else {
+        return null;
+      }
+    }
+
+    /**
+     * Returns the HTTP status code for the response, supplied in
+     * setResultArray() or setResultFile().
+     * @return The HTTP statue code, e.g 200.
+     */
+    public int getStatusCode() {
+      return statusCode;
+    }
+    
+    /**
+     * Returns the full HTTP status line for the response, supplied in
+     * setResultArray() or setResultFile().
+     * @return The HTTP statue line, e.g "HTTP/1.1 200 OK".
+     */
+    public String getStatusLine() {
+      return statusLine;
+    }
+    
+    /**
+     * Get all response headers supplied in calls in
+     * setResponseHeader().
+     * @return A Map<String, String[]> containing all headers.
+     */
+    public Map<String, String[]> getResponseHeaders() {
+      return responseHeaders;
+    }
+
+    /**
+     * Returns the MIME type for the response, supplied in
+     * setResultArray() or setResultFile().
+     * @return The MIME type, e.g "text/html".
+     */
+    public String getMimeType() {
+      return mimeType;
+    }
+    
+    /**
+     * Returns the encoding for the response, supplied in
+     * setResultArray() or setResultFile(), or null if none.
+     * @return The encoding, e.g "utf-8", or null if none.
+     */
+    public String getEncoding() {
+      return encoding;
+    }
+
+    /**
+     * Returns the InputStream setup by setResultArray() or
+     * setResultFile() to allow reading data either from memory or
+     * disk.
+     * @return The InputStream containing the response body.
+     */
+    public InputStream getInputStream() {
+      return inputStream;
+    }
+  }
+
+  /**
+   * Construct and initialize the singleton instance.
+   */
+  public UrlInterceptHandlerGears() {
+    if (instance != null) {
+      Log.e(LOG_TAG, "UrlInterceptHandlerGears singleton already constructed");
+      throw new RuntimeException();
+    }
+    instance = this;
+  }
+
+  /**
+   * Turn on/off logging in this class.
+   * @param on Logging enable state.
+   */
+  public static void enableLogging(boolean on) {
+    logEnabled = on;
+  }
+
+  /**
+   * Get the singleton instance.
+   * @return The singleton instance.
+   */
+  public static UrlInterceptHandlerGears getInstance() {
+    return instance;
+  }
+
+  /**
+   * Register the singleton instance with the browser's interception
+   * mechanism.
+   */
+  public synchronized void register() {
+    UrlInterceptRegistry.registerHandler(this);
+  }
+
+  /**
+   * Unregister the singleton instance from the browser's interception
+   * mechanism.
+   */
+  public synchronized void unregister() {
+    UrlInterceptRegistry.unregisterHandler(this);
+  }
+
+  /**
+   * Copy the entire InputStream to OutputStream.
+   * @param inputStream The stream to read from.
+   * @param outputStream The stream to write to.
+   * @return True if the entire stream copied successfully, false on error.
+   */
+  private boolean copyStream(InputStream inputStream,
+      OutputStream outputStream) {
+    try {
+      // Temporary buffer to copy stream through.
+      byte[] buf = new byte[BUFFER_SIZE];
+      for (;;) {
+        // Read up to BUFFER_SIZE bytes.
+        int bytes = inputStream.read(buf);
+        if (bytes < 0) {
+          break;
+        }
+        // Write the number of bytes we just read.
+        outputStream.write(buf, 0, bytes);
+      }
+    } catch (IOException ex) {
+      log("Got IOException copying stream: " + ex);
+      return false;
+    }
+    return true;
+  }
+
+  /**
+   * Given an URL, returns a CacheResult which contains the response
+   * for the request. This implements the UrlInterceptHandler interface.
+   *
+   * @param url            The fully qualified URL being requested.
+   * @param requestHeaders The request headers for this URL.
+   * @return If a response can be crafted, a CacheResult initialized
+   *         to return the surrogate response. If this URL cannot
+   *         be serviced, returns null.
+   */
+  public CacheResult service(String url, Map<String, String> requestHeaders) {
+    // Thankfully the browser does call us with case-sensitive
+    // headers. We just need to map it case-insensitive.
+    Map<String, String[]> lowercaseRequestHeaders =
+        new HashMap<String, String[]>();
+    Iterator<Map.Entry<String, String>> requestHeadersIt =
+        requestHeaders.entrySet().iterator();
+    while (requestHeadersIt.hasNext()) {
+      Map.Entry<String, String> entry = requestHeadersIt.next();
+      String key = entry.getKey();
+      String mapValue[] = { key, entry.getValue() };
+      lowercaseRequestHeaders.put(key.toLowerCase(), mapValue);
+    }
+    ServiceResponse response = getServiceResponse(url, lowercaseRequestHeaders);
+    if (response == null) {
+      // No result for this URL.
+      return null;
+    }
+    // Translate the ServiceResponse to a CacheResult.
+    // Translate http -> gears, https -> gearss, so we don't overwrite
+    // existing entries.
+    String gearsUrl = "gears" + url.substring("http".length());
+    // Set the result to expire, so that entries don't pollute the
+    // browser's cache for too long.
+    long now_ms = System.currentTimeMillis();
+    String expires = DateUtils.formatDate(new Date(now_ms + CACHE_EXPIRY_MS));
+    response.setResponseHeader(ApacheHttpRequestAndroid.KEY_EXPIRES, expires);
+    // The browser is only interested in a small subset of headers,
+    // contained in a Headers object. Iterate the map of all headers
+    // and add them to Headers.
+    Headers headers = new Headers();
+    Iterator<Map.Entry<String, String[]>> responseHeadersIt =
+        response.getResponseHeaders().entrySet().iterator();
+    while (responseHeadersIt.hasNext()) {
+      Map.Entry<String, String[]> entry = responseHeadersIt.next();
+      // Headers.parseHeader() expects lowercase keys.
+      String keyValue = entry.getKey() + ": "
+          + entry.getValue()[HEADERS_MAP_INDEX_VALUE];
+      CharArrayBuffer buffer = new CharArrayBuffer(keyValue.length());
+      buffer.append(keyValue);
+      // Parse it into the header container.
+      headers.parseHeader(buffer);
+    }
+    CacheResult cacheResult = CacheManager.createCacheFile(
+        gearsUrl,
+        response.getStatusCode(),
+        headers,
+        response.getMimeType(),
+        true); // forceCache
+
+    if (cacheResult == null) {
+      // With the no-cache policy we could end up
+      // with a null result
+      return null;
+    }
+
+    // Set encoding if specified.
+    String encoding = response.getEncoding();
+    if (encoding != null) {
+      cacheResult.setEncoding(encoding);
+    }
+    // Copy the response body to the CacheResult. This handles all
+    // combinations of memory vs on-disk on both sides.
+    InputStream inputStream = response.getInputStream();
+    OutputStream outputStream = cacheResult.getOutputStream();
+    boolean copied = copyStream(inputStream, outputStream);
+    // Close the input and output streams to relinquish their
+    // resources earlier.
+    try {
+      inputStream.close();
+    } catch (IOException ex) {
+      log("IOException closing InputStream: " + ex);
+      copied = false;
+    }
+    try {
+      outputStream.close();
+    } catch (IOException ex) {
+      log("IOException closing OutputStream: " + ex);
+      copied = false;
+    }
+    if (!copied) {
+      log("copyStream of local result failed");
+      return null;
+    }
+    // Save the entry into the browser's cache.
+    CacheManager.saveCacheFile(gearsUrl, cacheResult);
+    // Get it back from the cache, this time properly initialized to
+    // be used for input.
+    cacheResult = CacheManager.getCacheFile(gearsUrl, null);
+    if (cacheResult != null) {
+      log("Returning surrogate result");
+      return cacheResult;
+    } else {
+      // Not an expected condition, but handle gracefully. Perhaps out
+      // of memory or disk?
+      Log.e(LOG_TAG, "Lost CacheResult between save and get. Can't serve.\n");
+      return null;
+    }
+  }
+
+  /**
+   * Given an URL, returns a CacheResult and headers which contain the
+   * response for the request.
+   *
+   * @param url             The fully qualified URL being requested.
+   * @param requestHeaders  The request headers for this URL.
+   * @return If a response can be crafted, a ServiceResponse is
+   *         created which contains all response headers and an InputStream
+   *         attached to the body. If there is no response, null is returned.
+   */
+  public ServiceResponse getServiceResponse(String url,
+      Map<String, String[]> requestHeaders) {
+    if (!url.startsWith("http://") && !url.startsWith("https://")) {
+      // Don't know how to service non-HTTP URLs
+      return null;
+    }
+    // Call the native handler to craft a response for this URL.
+    return nativeService(new ServiceRequest(url, requestHeaders));
+  }
+
+  /**
+   * Convenience debug function. Calls the Android logging
+   * mechanism. logEnabled is not a constant, so if the string
+   * evaluation is potentially expensive, the caller also needs to
+   * check it.
+   * @param str String to log to the Android console.
+   */
+  private void log(String str) {
+    if (logEnabled) {
+      Log.i(LOG_TAG, str);
+    }
+  }
+
+  /**
+   * Native method which handles the bulk of the request in LocalServer.
+   * @param request A ServiceRequest object containing information about
+   *                the request.
+   * @return If serviced, a ServiceResponse object containing all the
+   *         information to provide a response for the URL, or null
+   *         if no response available for this URL.
+   */
+  private native static ServiceResponse nativeService(ServiceRequest request);
+}
diff --git a/core/java/android/webkit/gears/VersionExtractor.java b/core/java/android/webkit/gears/VersionExtractor.java
new file mode 100644
index 0000000..172dacb
--- /dev/null
+++ b/core/java/android/webkit/gears/VersionExtractor.java
@@ -0,0 +1,147 @@
+// Copyright 2008, The Android Open Source Project
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+//  1. Redistributions of source code must retain the above copyright notice,
+//     this list of conditions and the following disclaimer.
+//  2. Redistributions in binary form must reproduce the above copyright notice,
+//     this list of conditions and the following disclaimer in the documentation
+//     and/or other materials provided with the distribution.
+//  3. Neither the name of Google Inc. nor the names of its contributors may be
+//     used to endorse or promote products derived from this software without
+//     specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+// WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+// EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+// OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+// ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+package android.webkit.gears;
+
+import android.util.Log;
+import java.io.IOException;
+import java.io.StringReader;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.FactoryConfigurationError;
+import javax.xml.parsers.ParserConfigurationException;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+import org.w3c.dom.Document;
+import org.w3c.dom.DOMException;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+/**
+ * A class that can extract the Gears version and upgrade URL from an
+ * xml document.
+ */
+public final class VersionExtractor {
+
+  /**
+   * Logging tag
+   */
+  private static final String TAG = "Gears-J-VersionExtractor";
+  /**
+   * XML element names.
+   */
+  private static final String VERSION = "em:version";
+  private static final String URL = "em:updateLink";
+
+  /**
+   * Parses the input xml string and invokes the native
+   * setVersionAndUrl method.
+   * @param xml is the XML string to parse.
+   * @return true if the extraction is successful and false otherwise.
+   */
+  public static boolean extract(String xml, long nativeObject) {
+    try {
+      // Build the builders.
+      DocumentBuilderFactory factory =  DocumentBuilderFactory.newInstance();
+      factory.setNamespaceAware(false);
+      DocumentBuilder builder = factory.newDocumentBuilder();
+
+      // Create the document.
+      Document doc = builder.parse(new InputSource(new StringReader(xml)));
+
+      // Look for the version and url elements and get their text
+      // contents.
+      String version = extractText(doc, VERSION);
+      String url = extractText(doc, URL);
+
+      // If we have both, let the native side know.
+      if (version != null && url != null) {
+        setVersionAndUrl(version, url, nativeObject);
+        return true;
+      }
+
+      return false;
+
+    } catch (FactoryConfigurationError ex) {
+      Log.e(TAG, "Could not create the DocumentBuilderFactory " + ex);
+    } catch (ParserConfigurationException ex) {
+      Log.e(TAG, "Could not create the DocumentBuilder " + ex);
+    } catch (SAXException ex) {
+      Log.e(TAG, "Could not parse the xml " + ex);
+    } catch (IOException ex) {
+      Log.e(TAG, "Could not read the xml " + ex);
+    }
+
+    return false;
+  }
+
+ /**
+   * Extracts the text content of the first element with the given name.
+   * @param doc is the Document where the element is searched for.
+   * @param elementName is name of the element to searched for.
+   * @return the text content of the element or null if no such
+   *         element is found.
+   */
+  private static String extractText(Document doc, String elementName) {
+    String text = null;
+    NodeList node_list = doc.getElementsByTagName(elementName);
+
+    if (node_list.getLength() > 0) {
+      // We are only interested in the first node. Normally there
+      // should not be more than one anyway.
+      Node  node = node_list.item(0);
+
+      // Iterate through the text children.
+      NodeList child_list = node.getChildNodes();
+
+      try {
+        for (int i = 0; i < child_list.getLength(); ++i) {
+          Node child = child_list.item(i);
+          if (child.getNodeType() == Node.TEXT_NODE) {
+            if (text == null) {
+              text = new String();
+            }
+            text += child.getNodeValue();
+          }
+        }
+      } catch (DOMException ex) {
+        Log.e(TAG, "getNodeValue() failed " + ex);
+      }
+    }
+
+    if (text != null) {
+      text = text.trim();
+    }
+
+    return text;
+  }
+
+  /**
+   * Native method used to send the version and url back to the C++
+   * side.
+   */
+  private static native void setVersionAndUrl(
+      String version, String url, long nativeObject);
+}
diff --git a/core/java/android/webkit/gears/ZipInflater.java b/core/java/android/webkit/gears/ZipInflater.java
new file mode 100644
index 0000000..f6b6be5
--- /dev/null
+++ b/core/java/android/webkit/gears/ZipInflater.java
@@ -0,0 +1,200 @@
+// Copyright 2008, The Android Open Source Project
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+//  1. Redistributions of source code must retain the above copyright notice,
+//     this list of conditions and the following disclaimer.
+//  2. Redistributions in binary form must reproduce the above copyright notice,
+//     this list of conditions and the following disclaimer in the documentation
+//     and/or other materials provided with the distribution.
+//  3. Neither the name of Google Inc. nor the names of its contributors may be
+//     used to endorse or promote products derived from this software without
+//     specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+// WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+// EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+// OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+// ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+package android.webkit.gears;
+
+import android.os.StatFs;
+import android.util.Log;
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.Enumeration;
+import java.util.zip.CRC32;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
+import java.util.zip.ZipInputStream;
+
+
+/**
+ * A class that can inflate a zip archive.
+ */
+public final class ZipInflater {
+  /**
+   * Logging tag
+   */
+  private static final String TAG = "Gears-J-ZipInflater";
+
+  /**
+   * The size of the buffer used to read from the archive.
+   */
+  private static final int BUFFER_SIZE_BYTES = 32 * 1024;  // 32 KB.
+  /**
+   * The path navigation component (i.e. "../").
+   */
+  private static final String PATH_NAVIGATION_COMPONENT = ".." + File.separator;
+  /**
+   * The root of the data partition.
+   */
+  private static final String DATA_PARTITION_ROOT = "/data";
+
+  /**
+   * We need two be able to store two versions of gears in parallel:
+   * - the zipped version
+   * - the unzipped version, which will be loaded next time the browser is started.
+   * We are conservative and do not attempt to unpack unless there enough free
+   * space on the device to store 4 times the new Gears size.
+   */
+  private static final long SIZE_MULTIPLIER = 4;
+
+  /**
+   * Unzips the archive with the given name.
+   * @param filename is the name of the zip to inflate.
+   * @param path is the path where the zip should be unpacked. It must contain
+   *             a trailing separator, or the extraction will fail.
+   * @return true if the extraction is successful and false otherwise.
+   */
+  public static boolean inflate(String filename, String path) {
+    Log.i(TAG, "Extracting " + filename + " to " + path);
+
+    // Check that the path ends with a separator.
+    if (!path.endsWith(File.separator)) {
+      Log.e(TAG, "Path missing trailing separator: " + path);
+      return false;
+    }
+
+    boolean result = false;
+
+    // Use a ZipFile to get an enumeration of the entries and
+    // calculate the overall uncompressed size of the archive. Also
+    // check for existing files or directories that have the same
+    // name as the entries in the archive. Also check for invalid
+    // entry names (e.g names that attempt to navigate to the
+    // parent directory).
+    ZipInputStream zipStream = null;
+    long uncompressedSize = 0;
+    try {
+      ZipFile zipFile = new ZipFile(filename);
+      try {
+        Enumeration entries = zipFile.entries();
+        while (entries.hasMoreElements()) {
+          ZipEntry entry = (ZipEntry) entries.nextElement();
+          uncompressedSize += entry.getSize();
+          // Check against entry names that may attempt to navigate
+          // out of the destination directory.
+          if (entry.getName().indexOf(PATH_NAVIGATION_COMPONENT) >= 0) {
+            throw new IOException("Illegal entry name: " + entry.getName());
+          }
+
+          // Check against entries with the same name as pre-existing files or
+          // directories.
+          File file = new File(path + entry.getName());
+          if (file.exists()) {
+            // A file or directory with the same name already exist.
+            // This must not happen, so we treat this as an error.
+            throw new IOException(
+                "A file or directory with the same name already exists.");
+          }
+        }
+      } finally {
+        zipFile.close();
+      }
+
+      Log.i(TAG, "Determined uncompressed size: " + uncompressedSize);
+      // Check we have enough space to unpack this archive.
+      if (freeSpace() <= uncompressedSize * SIZE_MULTIPLIER) {
+        throw new IOException("Not enough space to unpack this archive.");
+      }
+
+      zipStream = new ZipInputStream(
+          new BufferedInputStream(new FileInputStream(filename)));
+      ZipEntry entry;
+      int counter;
+      byte buffer[] = new byte[BUFFER_SIZE_BYTES];
+
+      // Iterate through the entries and write each of them to a file.
+      while ((entry = zipStream.getNextEntry()) != null) {
+        File file = new File(path + entry.getName());
+        if (entry.isDirectory()) {
+          // If the entry denotes a directory, we need to create a
+          // directory with the same name.
+          file.mkdirs();
+        } else {
+          CRC32 checksum = new CRC32();
+          BufferedOutputStream output = new BufferedOutputStream(
+              new FileOutputStream(file),
+              BUFFER_SIZE_BYTES);
+          try {
+            // Read the entry and write it to the file.
+            while ((counter = zipStream.read(buffer, 0, BUFFER_SIZE_BYTES)) !=
+                -1) {
+              output.write(buffer, 0, counter);
+              checksum.update(buffer, 0, counter);
+            }
+            output.flush();
+          } finally {
+            output.close();
+          }
+
+          if (checksum.getValue() != entry.getCrc()) {
+            throw new IOException(
+                "Integrity check failed for: " + entry.getName());
+          }
+        }
+        zipStream.closeEntry();
+      }
+
+      result = true;
+
+    } catch (FileNotFoundException ex) {
+      Log.e(TAG, "The zip file could not be found. " + ex);
+    } catch (IOException ex) {
+      Log.e(TAG, "Could not read or write an entry. " + ex);
+    } catch(IllegalArgumentException ex) {
+      Log.e(TAG, "Could not create the BufferedOutputStream. " + ex);
+    } finally {
+      if (zipStream != null) {
+        try {
+          zipStream.close();
+        } catch (IOException ex) {
+          // Ignored.
+        }
+      }
+      // Discard any exceptions and return the result to the native side.
+      return result;
+    }
+  }
+
+  private static final long freeSpace() {
+    StatFs data_partition =  new StatFs(DATA_PARTITION_ROOT);
+    long freeSpace = data_partition.getAvailableBlocks() *
+        data_partition.getBlockSize();
+    Log.i(TAG, "Free space on the data partition: " + freeSpace);
+    return freeSpace;
+  }
+}
diff --git a/core/java/android/webkit/gears/package.html b/core/java/android/webkit/gears/package.html
new file mode 100644
index 0000000..db6f78b
--- /dev/null
+++ b/core/java/android/webkit/gears/package.html
@@ -0,0 +1,3 @@
+<body>
+{@hide}
+</body>
\ No newline at end of file
diff --git a/core/java/android/webkit/package.html b/core/java/android/webkit/package.html
new file mode 100644
index 0000000..4ed08da
--- /dev/null
+++ b/core/java/android/webkit/package.html
@@ -0,0 +1,7 @@
+<html>
+<body>
+Provides tools for browsing the web.
+<p>The only classes or interfaces in this package intended for use by SDK
+developers are WebView, BroswerCallbackAdapter, BrowserCallback, and CookieManager.
+</body>
+</html>
\ No newline at end of file
diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java
new file mode 100644
index 0000000..9da78d0
--- /dev/null
+++ b/core/java/android/widget/AbsListView.java
@@ -0,0 +1,3428 @@
+/*
+ * 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 android.widget;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Canvas;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.TransitionDrawable;
+import android.os.Debug;
+import android.os.Handler;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.Editable;
+import android.text.TextUtils;
+import android.text.TextWatcher;
+import android.util.AttributeSet;
+import android.view.Gravity;
+import android.view.HapticFeedbackConstants;
+import android.view.KeyEvent;
+import android.view.LayoutInflater;
+import android.view.MotionEvent;
+import android.view.VelocityTracker;
+import android.view.View;
+import android.view.ViewConfiguration;
+import android.view.ViewDebug;
+import android.view.ViewGroup;
+import android.view.ViewTreeObserver;
+import android.view.inputmethod.InputMethodManager;
+import android.view.ContextMenu.ContextMenuInfo;
+
+import com.android.internal.R;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Common code shared between ListView and GridView
+ *
+ * @attr ref android.R.styleable#AbsListView_listSelector
+ * @attr ref android.R.styleable#AbsListView_drawSelectorOnTop
+ * @attr ref android.R.styleable#AbsListView_stackFromBottom
+ * @attr ref android.R.styleable#AbsListView_scrollingCache
+ * @attr ref android.R.styleable#AbsListView_textFilterEnabled
+ * @attr ref android.R.styleable#AbsListView_transcriptMode
+ * @attr ref android.R.styleable#AbsListView_cacheColorHint
+ * @attr ref android.R.styleable#AbsListView_fastScrollEnabled
+ * @attr ref android.R.styleable#AbsListView_smoothScrollbar
+ */
+public abstract class AbsListView extends AdapterView<ListAdapter> implements TextWatcher,
+        ViewTreeObserver.OnGlobalLayoutListener, Filter.FilterListener,
+        ViewTreeObserver.OnTouchModeChangeListener {
+
+    /**
+     * Disables the transcript mode.
+     *
+     * @see #setTranscriptMode(int)
+     */
+    public static final int TRANSCRIPT_MODE_DISABLED = 0;
+    /**
+     * The list will automatically scroll to the bottom when a data set change
+     * notification is received and only if the last item is already visible
+     * on screen.
+     *
+     * @see #setTranscriptMode(int)
+     */
+    public static final int TRANSCRIPT_MODE_NORMAL = 1;
+    /**
+     * The list will automatically scroll to the bottom, no matter what items
+     * are currently visible. 
+     *
+     * @see #setTranscriptMode(int)
+     */
+    public static final int TRANSCRIPT_MODE_ALWAYS_SCROLL = 2;
+
+    /**
+     * Indicates that we are not in the middle of a touch gesture
+     */
+    static final int TOUCH_MODE_REST = -1;
+
+    /**
+     * Indicates we just received the touch event and we are waiting to see if the it is a tap or a
+     * scroll gesture.
+     */
+    static final int TOUCH_MODE_DOWN = 0;
+
+    /**
+     * Indicates the touch has been recognized as a tap and we are now waiting to see if the touch
+     * is a longpress
+     */
+    static final int TOUCH_MODE_TAP = 1;
+
+    /**
+     * Indicates we have waited for everything we can wait for, but the user's finger is still down
+     */
+    static final int TOUCH_MODE_DONE_WAITING = 2;
+
+    /**
+     * Indicates the touch gesture is a scroll
+     */
+    static final int TOUCH_MODE_SCROLL = 3;
+
+    /**
+     * Indicates the view is in the process of being flung
+     */
+    static final int TOUCH_MODE_FLING = 4;
+    
+    /**
+     * Indicates that the user is currently dragging the fast scroll thumb
+     */
+    static final int TOUCH_MODE_FAST_SCROLL = 5;
+
+    /**
+     * Regular layout - usually an unsolicited layout from the view system
+     */
+    static final int LAYOUT_NORMAL = 0;
+
+    /**
+     * Show the first item
+     */
+    static final int LAYOUT_FORCE_TOP = 1;
+
+    /**
+     * Force the selected item to be on somewhere on the screen
+     */
+    static final int LAYOUT_SET_SELECTION = 2;
+
+    /**
+     * Show the last item
+     */
+    static final int LAYOUT_FORCE_BOTTOM = 3;
+
+    /**
+     * Make a mSelectedItem appear in a specific location and build the rest of
+     * the views from there. The top is specified by mSpecificTop.
+     */
+    static final int LAYOUT_SPECIFIC = 4;
+
+    /**
+     * Layout to sync as a result of a data change. Restore mSyncPosition to have its top
+     * at mSpecificTop
+     */
+    static final int LAYOUT_SYNC = 5;
+
+    /**
+     * Layout as a result of using the navigation keys
+     */
+    static final int LAYOUT_MOVE_SELECTION = 6;
+
+    /**
+     * Controls how the next layout will happen
+     */
+    int mLayoutMode = LAYOUT_NORMAL;
+
+    /**
+     * Should be used by subclasses to listen to changes in the dataset
+     */
+    AdapterDataSetObserver mDataSetObserver;
+
+    /**
+     * The adapter containing the data to be displayed by this view
+     */
+    ListAdapter mAdapter;
+
+    /**
+     * Indicates whether the list selector should be drawn on top of the children or behind
+     */
+    boolean mDrawSelectorOnTop = false;
+
+    /**
+     * The drawable used to draw the selector
+     */
+    Drawable mSelector;
+
+    /**
+     * Defines the selector's location and dimension at drawing time
+     */
+    Rect mSelectorRect = new Rect();
+
+    /**
+     * The data set used to store unused views that should be reused during the next layout
+     * to avoid creating new ones
+     */
+    final RecycleBin mRecycler = new RecycleBin();
+
+    /**
+     * The selection's left padding
+     */
+    int mSelectionLeftPadding = 0;
+
+    /**
+     * The selection's top padding
+     */
+    int mSelectionTopPadding = 0;
+
+    /**
+     * The selection's right padding
+     */
+    int mSelectionRightPadding = 0;
+
+    /**
+     * The selection's bottom padding
+     */
+    int mSelectionBottomPadding = 0;
+
+    /**
+     * This view's padding
+     */
+    Rect mListPadding = new Rect();
+
+    /**
+     * Subclasses must retain their measure spec from onMeasure() into this member
+     */
+    int mWidthMeasureSpec = 0;
+
+    /**
+     * The top scroll indicator
+     */
+    View mScrollUp;
+
+    /**
+     * The down scroll indicator
+     */
+    View mScrollDown;
+
+    /**
+     * When the view is scrolling, this flag is set to true to indicate subclasses that
+     * the drawing cache was enabled on the children
+     */
+    boolean mCachingStarted;
+
+    /**
+     * The position of the view that received the down motion event
+     */
+    int mMotionPosition;
+
+    /**
+     * The offset to the top of the mMotionPosition view when the down motion event was received
+     */
+    int mMotionViewOriginalTop;
+
+    /**
+     * The desired offset to the top of the mMotionPosition view after a scroll
+     */
+    int mMotionViewNewTop;
+
+    /**
+     * The X value associated with the the down motion event
+     */
+    int mMotionX;
+
+    /**
+     * The Y value associated with the the down motion event
+     */
+    int mMotionY;
+
+    /**
+     * One of TOUCH_MODE_REST, TOUCH_MODE_DOWN, TOUCH_MODE_TAP, TOUCH_MODE_SCROLL, or
+     * TOUCH_MODE_DONE_WAITING
+     */
+    int mTouchMode = TOUCH_MODE_REST;
+
+    /**
+     * Y value from on the previous motion event (if any)
+     */
+    int mLastY;
+
+    /**
+     * How far the finger moved before we started scrolling
+     */
+    int mMotionCorrection;
+
+    /**
+     * Determines speed during touch scrolling
+     */
+    private VelocityTracker mVelocityTracker;
+
+    /**
+     * Handles one frame of a fling
+     */
+    private FlingRunnable mFlingRunnable;
+
+    /**
+     * The offset in pixels form the top of the AdapterView to the top
+     * of the currently selected view. Used to save and restore state.
+     */
+    int mSelectedTop = 0;
+
+    /**
+     * Indicates whether the list is stacked from the bottom edge or
+     * the top edge.
+     */
+    boolean mStackFromBottom;
+
+    /**
+     * When set to true, the list automatically discards the children's
+     * bitmap cache after scrolling.
+     */
+    boolean mScrollingCacheEnabled;
+    
+    /**
+     * Whether or not to enable the fast scroll feature on this list
+     */
+    boolean mFastScrollEnabled;
+
+    /**
+     * Optional callback to notify client when scroll position has changed
+     */
+    private OnScrollListener mOnScrollListener;
+
+    /**
+     * Keeps track of our accessory window
+     */
+    PopupWindow mPopup;
+
+    /**
+     * Used with type filter window
+     */
+    EditText mTextFilter;
+
+    /**
+     * Indicates whether to use pixels-based or position-based scrollbar
+     * properties.
+     */
+    private boolean mSmoothScrollbarEnabled = true;
+
+    /**
+     * Indicates that this view supports filtering
+     */
+    private boolean mTextFilterEnabled;
+
+    /**
+     * Indicates that this view is currently displaying a filtered view of the data
+     */
+    private boolean mFiltered;
+
+    /**
+     * Rectangle used for hit testing children
+     */
+    private Rect mTouchFrame;
+
+    /**
+     * The position to resurrect the selected position to.
+     */
+    int mResurrectToPosition = INVALID_POSITION;
+
+    private ContextMenuInfo mContextMenuInfo = null;
+
+    /**
+     * Used to request a layout when we changed touch mode
+     */
+    private static final int TOUCH_MODE_UNKNOWN = -1;
+    private static final int TOUCH_MODE_ON = 0;
+    private static final int TOUCH_MODE_OFF = 1;
+
+    private int mLastTouchMode = TOUCH_MODE_UNKNOWN;
+
+    private static final boolean PROFILE_SCROLLING = false;
+    private boolean mScrollProfilingStarted = false;
+
+    private static final boolean PROFILE_FLINGING = false;
+    private boolean mFlingProfilingStarted = false;
+
+    /**
+     * The last CheckForLongPress runnable we posted, if any
+     */
+    private CheckForLongPress mPendingCheckForLongPress;
+
+    /**
+     * The last CheckForTap runnable we posted, if any
+     */
+    private Runnable mPendingCheckForTap;
+    
+    /**
+     * The last CheckForKeyLongPress runnable we posted, if any
+     */
+    private CheckForKeyLongPress mPendingCheckForKeyLongPress;
+
+    /**
+     * Acts upon click
+     */
+    private AbsListView.PerformClick mPerformClick;
+
+    /**
+     * This view is in transcript mode -- it shows the bottom of the list when the data
+     * changes
+     */
+    private int mTranscriptMode;
+
+    /**
+     * Indicates that this list is always drawn on top of a solid, single-color, opaque
+     * background
+     */
+    private int mCacheColorHint;
+
+    /**
+     * The select child's view (from the adapter's getView) is enabled.
+     */
+    private boolean mIsChildViewEnabled;
+
+    /**
+     * The last scroll state reported to clients through {@link OnScrollListener}.
+     */
+    private int mLastScrollState = OnScrollListener.SCROLL_STATE_IDLE;
+
+    /**
+     * Helper object that renders and controls the fast scroll thumb.
+     */
+    private FastScroller mFastScroller;
+
+    private int mTouchSlop;
+
+    private float mDensityScale;
+
+    /**
+     * Interface definition for a callback to be invoked when the list or grid
+     * has been scrolled.
+     */
+    public interface OnScrollListener {
+
+        /**
+         * The view is not scrolling. Note navigating the list using the trackball counts as
+         * being in the idle state since these transitions are not animated.
+         */
+        public static int SCROLL_STATE_IDLE = 0;
+
+        /**
+         * The user is scrolling using touch, and their finger is still on the screen
+         */
+        public static int SCROLL_STATE_TOUCH_SCROLL = 1;
+
+        /**
+         * The user had previously been scrolling using touch and had performed a fling. The
+         * animation is now coasting to a stop
+         */
+        public static int SCROLL_STATE_FLING = 2;
+
+        /**
+         * Callback method to be invoked while the list view or grid view is being scrolled. If the
+         * view is being scrolled, this method will be called before the next frame of the scroll is
+         * rendered. In particular, it will be called before any calls to
+         * {@link Adapter#getView(int, View, ViewGroup)}.
+         *
+         * @param view The view whose scroll state is being reported
+         *
+         * @param scrollState The current scroll state. One of {@link #SCROLL_STATE_IDLE},
+         * {@link #SCROLL_STATE_TOUCH_SCROLL} or {@link #SCROLL_STATE_IDLE}.
+         */
+        public void onScrollStateChanged(AbsListView view, int scrollState);
+
+        /**
+         * Callback method to be invoked when the list or grid has been scrolled. This will be
+         * called after the scroll has completed
+         * @param view The view whose scroll state is being reported
+         * @param firstVisibleItem the index of the first visible cell (ignore if
+         *        visibleItemCount == 0)
+         * @param visibleItemCount the number of visible cells
+         * @param totalItemCount the number of items in the list adaptor
+         */
+        public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount,
+                int totalItemCount);
+    }
+
+    public AbsListView(Context context) {
+        super(context);
+        initAbsListView();
+
+        setVerticalScrollBarEnabled(true);
+        TypedArray a = context.obtainStyledAttributes(R.styleable.View);
+        initializeScrollbars(a);
+        a.recycle();
+    }
+
+    public AbsListView(Context context, AttributeSet attrs) {
+        this(context, attrs, com.android.internal.R.attr.absListViewStyle);
+    }
+
+    public AbsListView(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+        initAbsListView();
+
+        TypedArray a = context.obtainStyledAttributes(attrs,
+                com.android.internal.R.styleable.AbsListView, defStyle, 0);
+
+        Drawable d = a.getDrawable(com.android.internal.R.styleable.AbsListView_listSelector);
+        if (d != null) {
+            setSelector(d);
+        }
+
+        mDrawSelectorOnTop = a.getBoolean(
+                com.android.internal.R.styleable.AbsListView_drawSelectorOnTop, false);
+
+        boolean stackFromBottom = a.getBoolean(R.styleable.AbsListView_stackFromBottom, false);
+        setStackFromBottom(stackFromBottom);
+
+        boolean scrollingCacheEnabled = a.getBoolean(R.styleable.AbsListView_scrollingCache, true);
+        setScrollingCacheEnabled(scrollingCacheEnabled);
+
+        boolean useTextFilter = a.getBoolean(R.styleable.AbsListView_textFilterEnabled, false);
+        setTextFilterEnabled(useTextFilter);
+
+        int transcriptMode = a.getInt(R.styleable.AbsListView_transcriptMode,
+                TRANSCRIPT_MODE_DISABLED);
+        setTranscriptMode(transcriptMode);
+
+        int color = a.getColor(R.styleable.AbsListView_cacheColorHint, 0);
+        setCacheColorHint(color);
+        
+        boolean enableFastScroll = a.getBoolean(R.styleable.AbsListView_fastScrollEnabled, false);
+        setFastScrollEnabled(enableFastScroll);
+
+        boolean smoothScrollbar = a.getBoolean(R.styleable.AbsListView_smoothScrollbar, true);
+        setSmoothScrollbarEnabled(smoothScrollbar);
+        
+        a.recycle();
+    }
+
+    /**
+     * Enables fast scrolling by letting the user quickly scroll through lists by 
+     * dragging the fast scroll thumb. The adapter attached to the list may want 
+     * to implement {@link SectionIndexer} if it wishes to display alphabet preview and
+     * jump between sections of the list. 
+     * @see SectionIndexer
+     * @see #isFastScrollEnabled()
+     * @param enabled whether or not to enable fast scrolling
+     */
+    public void setFastScrollEnabled(boolean enabled) {
+        mFastScrollEnabled = enabled;
+        if (enabled) {
+            if (mFastScroller == null) {
+                mFastScroller = new FastScroller(getContext(), this);
+            }
+        } else {
+            if (mFastScroller != null) {
+                mFastScroller.stop();
+                mFastScroller = null;
+            }
+        }
+    }
+    
+    /**
+     * Returns the current state of the fast scroll feature.
+     * @see #setFastScrollEnabled(boolean)
+     * @return true if fast scroll is enabled, false otherwise
+     */
+    @ViewDebug.ExportedProperty
+    public boolean isFastScrollEnabled() {
+        return mFastScrollEnabled;
+    }
+    
+    /**
+     * If fast scroll is visible, then don't draw the vertical scrollbar.
+     * @hide 
+     */
+    @Override
+    protected boolean isVerticalScrollBarHidden() {
+        return mFastScroller != null && mFastScroller.isVisible();
+    }
+
+    /**
+     * When smooth scrollbar is enabled, the position and size of the scrollbar thumb
+     * is computed based on the number of visible pixels in the visible items. This
+     * however assumes that all list items have the same height. If you use a list in
+     * which items have different heights, the scrollbar will change appearance as the
+     * user scrolls through the list. To avoid this issue, you need to disable this
+     * property.
+     *
+     * When smooth scrollbar is disabled, the position and size of the scrollbar thumb
+     * is based solely on the number of items in the adapter and the position of the
+     * visible items inside the adapter. This provides a stable scrollbar as the user
+     * navigates through a list of items with varying heights. 
+     *
+     * @param enabled Whether or not to enable smooth scrollbar.
+     *
+     * @see #setSmoothScrollbarEnabled(boolean) 
+     * @attr ref android.R.styleable#AbsListView_smoothScrollbar
+     */
+    public void setSmoothScrollbarEnabled(boolean enabled) {
+        mSmoothScrollbarEnabled = enabled;
+    }
+
+    /**
+     * Returns the current state of the fast scroll feature.
+     *
+     * @return True if smooth scrollbar is enabled is enabled, false otherwise.
+     *
+     * @see #setSmoothScrollbarEnabled(boolean)
+     */
+    @ViewDebug.ExportedProperty
+    public boolean isSmoothScrollbarEnabled() {
+        return mSmoothScrollbarEnabled;
+    }
+
+    /**
+     * Set the listener that will receive notifications every time the list scrolls.
+     *
+     * @param l the scroll listener
+     */
+    public void setOnScrollListener(OnScrollListener l) {
+        mOnScrollListener = l;
+        invokeOnItemScrollListener();
+    }
+
+    /**
+     * Notify our scroll listener (if there is one) of a change in scroll state
+     */
+    void invokeOnItemScrollListener() {
+        if (mFastScroller != null) {
+            mFastScroller.onScroll(this, mFirstPosition, getChildCount(), mItemCount);
+        }
+        if (mOnScrollListener != null) {
+            mOnScrollListener.onScroll(this, mFirstPosition, getChildCount(), mItemCount);
+        }
+    }
+
+    /**
+     * Indicates whether the children's drawing cache is used during a scroll.
+     * By default, the drawing cache is enabled but this will consume more memory.
+     *
+     * @return true if the scrolling cache is enabled, false otherwise
+     *
+     * @see #setScrollingCacheEnabled(boolean)
+     * @see View#setDrawingCacheEnabled(boolean)
+     */
+    @ViewDebug.ExportedProperty
+    public boolean isScrollingCacheEnabled() {
+        return mScrollingCacheEnabled;
+    }
+
+    /**
+     * Enables or disables the children's drawing cache during a scroll.
+     * By default, the drawing cache is enabled but this will use more memory.
+     *
+     * When the scrolling cache is enabled, the caches are kept after the
+     * first scrolling. You can manually clear the cache by calling
+     * {@link android.view.ViewGroup#setChildrenDrawingCacheEnabled(boolean)}.
+     *
+     * @param enabled true to enable the scroll cache, false otherwise
+     *
+     * @see #isScrollingCacheEnabled()
+     * @see View#setDrawingCacheEnabled(boolean)
+     */
+    public void setScrollingCacheEnabled(boolean enabled) {
+        if (mScrollingCacheEnabled && !enabled) {
+            clearScrollingCache();
+        }
+        mScrollingCacheEnabled = enabled;
+    }
+
+    /**
+     * Enables or disables the type filter window. If enabled, typing when
+     * this view has focus will filter the children to match the users input.
+     * Note that the {@link Adapter} used by this view must implement the
+     * {@link Filterable} interface.
+     *
+     * @param textFilterEnabled true to enable type filtering, false otherwise
+     *
+     * @see Filterable
+     */
+    public void setTextFilterEnabled(boolean textFilterEnabled) {
+        mTextFilterEnabled = textFilterEnabled;
+    }
+
+    /**
+     * Indicates whether type filtering is enabled for this view
+     *
+     * @return true if type filtering is enabled, false otherwise
+     *
+     * @see #setTextFilterEnabled(boolean)
+     * @see Filterable
+     */
+    @ViewDebug.ExportedProperty
+    public boolean isTextFilterEnabled() {
+        return mTextFilterEnabled;
+    }
+
+    @Override
+    public void getFocusedRect(Rect r) {
+        View view = getSelectedView();
+        if (view != null) {
+            // the focused rectangle of the selected view offset into the
+            // coordinate space of this view.
+            view.getFocusedRect(r);
+            offsetDescendantRectToMyCoords(view, r);
+        } else {
+            // otherwise, just the norm
+            super.getFocusedRect(r);
+        }
+    }
+
+    private void initAbsListView() {
+        // Setting focusable in touch mode will set the focusable property to true
+        setFocusableInTouchMode(true);
+        setWillNotDraw(false);
+        setAlwaysDrawnWithCacheEnabled(false);
+        setScrollingCacheEnabled(true);
+
+        mTouchSlop = ViewConfiguration.get(mContext).getScaledTouchSlop();
+        mDensityScale = getContext().getResources().getDisplayMetrics().density;
+    }
+
+    private void useDefaultSelector() {
+        setSelector(getResources().getDrawable(
+                com.android.internal.R.drawable.list_selector_background));
+    }
+
+    /**
+     * Indicates whether the content of this view is pinned to, or stacked from,
+     * the bottom edge.
+     *
+     * @return true if the content is stacked from the bottom edge, false otherwise
+     */
+    @ViewDebug.ExportedProperty
+    public boolean isStackFromBottom() {
+        return mStackFromBottom;
+    }
+
+    /**
+     * When stack from bottom is set to true, the list fills its content starting from
+     * the bottom of the view.
+     *
+     * @param stackFromBottom true to pin the view's content to the bottom edge,
+     *        false to pin the view's content to the top edge
+     */
+    public void setStackFromBottom(boolean stackFromBottom) {
+        if (mStackFromBottom != stackFromBottom) {
+            mStackFromBottom = stackFromBottom;
+            requestLayoutIfNecessary();
+        }
+    }
+
+    void requestLayoutIfNecessary() {
+        if (getChildCount() > 0) {
+            resetList();
+            requestLayout();
+            invalidate();
+        }
+    }
+
+    static class SavedState extends BaseSavedState {
+        long selectedId;
+        long firstId;
+        int viewTop;
+        int position;
+        int height;
+        String filter;
+
+        /**
+         * Constructor called from {@link AbsListView#onSaveInstanceState()}
+         */
+        SavedState(Parcelable superState) {
+            super(superState);
+        }
+
+        /**
+         * Constructor called from {@link #CREATOR}
+         */
+        private SavedState(Parcel in) {
+            super(in);
+            selectedId = in.readLong();
+            firstId = in.readLong();
+            viewTop = in.readInt();
+            position = in.readInt();
+            height = in.readInt();
+            filter = in.readString();
+        }
+
+        @Override
+        public void writeToParcel(Parcel out, int flags) {
+            super.writeToParcel(out, flags);
+            out.writeLong(selectedId);
+            out.writeLong(firstId);
+            out.writeInt(viewTop);
+            out.writeInt(position);
+            out.writeInt(height);
+            out.writeString(filter);
+        }
+
+        @Override
+        public String toString() {
+            return "AbsListView.SavedState{"
+                    + Integer.toHexString(System.identityHashCode(this))
+                    + " selectedId=" + selectedId
+                    + " firstId=" + firstId
+                    + " viewTop=" + viewTop
+                    + " position=" + position
+                    + " height=" + height
+                    + " filter=" + filter + "}";
+        }
+
+        public static final Parcelable.Creator<SavedState> CREATOR
+                = new Parcelable.Creator<SavedState>() {
+            public SavedState createFromParcel(Parcel in) {
+                return new SavedState(in);
+            }
+
+            public SavedState[] newArray(int size) {
+                return new SavedState[size];
+            }
+        };
+    }
+
+    @Override
+    public Parcelable onSaveInstanceState() {
+        /*
+         * This doesn't really make sense as the place to dismiss the
+         * popup, but there don't seem to be any other useful hooks
+         * that happen early enough to keep from getting complaints
+         * about having leaked the window.
+         */
+        dismissPopup();
+
+        Parcelable superState = super.onSaveInstanceState();
+
+        SavedState ss = new SavedState(superState);
+
+        boolean haveChildren = getChildCount() > 0;
+        long selectedId = getSelectedItemId();
+        ss.selectedId = selectedId;
+        ss.height = getHeight();
+
+        if (selectedId >= 0) {
+            // Remember the selection
+            ss.viewTop = mSelectedTop;
+            ss.position = getSelectedItemPosition();
+            ss.firstId = INVALID_POSITION;
+        } else {
+            if (haveChildren) {
+                // Remember the position of the first child
+                View v = getChildAt(0);
+                ss.viewTop = v.getTop();
+                ss.position = mFirstPosition;
+                ss.firstId = mAdapter.getItemId(mFirstPosition);
+            } else {
+                ss.viewTop = 0;
+                ss.firstId = INVALID_POSITION;
+                ss.position = 0;
+            }
+        }
+
+        ss.filter = null;
+        if (mFiltered) {
+            final EditText textFilter = mTextFilter;
+            if (textFilter != null) {
+                Editable filterText = textFilter.getText();
+                if (filterText != null) {
+                    ss.filter = filterText.toString();
+                }
+            }
+        }
+
+        return ss;
+    }
+
+    @Override
+    public void onRestoreInstanceState(Parcelable state) {
+        SavedState ss = (SavedState) state;
+
+        super.onRestoreInstanceState(ss.getSuperState());
+        mDataChanged = true;
+
+        mSyncHeight = ss.height;
+
+        if (ss.selectedId >= 0) {
+            mNeedSync = true;
+            mSyncRowId = ss.selectedId;
+            mSyncPosition = ss.position;
+            mSpecificTop = ss.viewTop;
+            mSyncMode = SYNC_SELECTED_POSITION;
+        } else if (ss.firstId >= 0) {
+            setSelectedPositionInt(INVALID_POSITION);
+            // Do this before setting mNeedSync since setNextSelectedPosition looks at mNeedSync
+            setNextSelectedPositionInt(INVALID_POSITION);
+            mNeedSync = true;
+            mSyncRowId = ss.firstId;
+            mSyncPosition = ss.position;
+            mSpecificTop = ss.viewTop;
+            mSyncMode = SYNC_FIRST_POSITION;
+        }
+
+        setFilterText(ss.filter);
+
+        requestLayout();
+    }
+
+    private boolean acceptFilter() {
+        final Context context = mContext;
+        final InputMethodManager inputManager = (InputMethodManager)
+                context.getSystemService(Context.INPUT_METHOD_SERVICE);
+        return !inputManager.isFullscreenMode();
+    }
+
+    /**
+     * Sets the initial value for the text filter.
+     * @param filterText The text to use for the filter.
+     * 
+     * @see #setTextFilterEnabled
+     */
+    public void setFilterText(String filterText) {
+        // TODO: Should we check for acceptFilter()?
+        if (mTextFilterEnabled && !TextUtils.isEmpty(filterText)) {
+            createTextFilter(false);
+            // This is going to call our listener onTextChanged, but we might not
+            // be ready to bring up a window yet
+            mTextFilter.setText(filterText);
+            mTextFilter.setSelection(filterText.length());
+            if (mAdapter instanceof Filterable) {
+                // if mPopup is non-null, then onTextChanged will do the filtering
+                if (mPopup == null) {
+                    Filter f = ((Filterable) mAdapter).getFilter();
+                    f.filter(filterText);
+                }
+                // Set filtered to true so we will display the filter window when our main
+                // window is ready
+                mFiltered = true;
+                mDataSetObserver.clearSavedState();
+            }
+        }
+    }
+
+    /**
+     * Returns the list's text filter, if available. 
+     * @return the list's text filter or null if filtering isn't enabled
+     * @hide pending API Council approval
+     */
+    public CharSequence getTextFilter() {
+        if (mTextFilterEnabled && mTextFilter != null) {
+            return mTextFilter.getText();
+        }
+        return null;
+    }
+    
+    @Override
+    protected void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) {
+        super.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
+        if (gainFocus && mSelectedPosition < 0 && !isInTouchMode()) {
+            resurrectSelection();
+        }
+    }
+
+    @Override
+    public void requestLayout() {
+        if (!mBlockLayoutRequests && !mInLayout) {
+            super.requestLayout();
+        }
+    }
+
+    /**
+     * The list is empty. Clear everything out.
+     */
+    void resetList() {
+        removeAllViewsInLayout();
+        mFirstPosition = 0;
+        mDataChanged = false;
+        mNeedSync = false;
+        mOldSelectedPosition = INVALID_POSITION;
+        mOldSelectedRowId = INVALID_ROW_ID;
+        setSelectedPositionInt(INVALID_POSITION);
+        setNextSelectedPositionInt(INVALID_POSITION);
+        mSelectedTop = 0;
+        mSelectorRect.setEmpty();
+        invalidate();
+    }
+
+    @Override
+    protected int computeVerticalScrollExtent() {
+        final int count = getChildCount();
+        if (count > 0) {
+            if (mSmoothScrollbarEnabled) {
+                int extent = count * 100;
+
+                View view = getChildAt(0);
+                final int top = view.getTop();
+                int height = view.getHeight();
+                if (height > 0) {
+                    extent += (top * 100) / height;
+                }
+
+                view = getChildAt(count - 1);
+                final int bottom = view.getBottom();
+                height = view.getHeight();
+                if (height > 0) {
+                    extent -= ((bottom - getHeight()) * 100) / height;
+                }
+
+                return extent;
+            } else {
+                return 1;
+            }
+        }
+        return 0;
+    }
+
+    @Override
+    protected int computeVerticalScrollOffset() {
+        final int firstPosition = mFirstPosition;
+        final int childCount = getChildCount();
+        if (firstPosition >= 0 && childCount > 0) {
+            if (mSmoothScrollbarEnabled) {
+                final View view = getChildAt(0);
+                final int top = view.getTop();
+                int height = view.getHeight();
+                if (height > 0) {
+                    return Math.max(firstPosition * 100 - (top * 100) / height, 0);
+                }
+            } else {
+                int index;
+                final int count = mItemCount;
+                if (firstPosition == 0) {
+                    index = 0;
+                } else if (firstPosition + childCount == count) {
+                    index = count;
+                } else {
+                    index = firstPosition + childCount / 2;
+                }
+                return (int) (firstPosition + childCount * (index / (float) count));
+            }
+        }
+        return 0;
+    }
+
+    @Override
+    protected int computeVerticalScrollRange() {
+        return mSmoothScrollbarEnabled ? Math.max(mItemCount * 100, 0) : mItemCount;
+    }
+
+    @Override
+    protected float getTopFadingEdgeStrength() {
+        final int count = getChildCount();
+        final float fadeEdge = super.getTopFadingEdgeStrength();
+        if (count == 0) {
+            return fadeEdge;
+        } else {
+            if (mFirstPosition > 0) {
+                return 1.0f;
+            }
+
+            final int top = getChildAt(0).getTop();
+            final float fadeLength = (float) getVerticalFadingEdgeLength();
+            return top < mPaddingTop ? (float) -(top - mPaddingTop) / fadeLength : fadeEdge;
+        }
+    }
+
+    @Override
+    protected float getBottomFadingEdgeStrength() {
+        final int count = getChildCount();
+        final float fadeEdge = super.getBottomFadingEdgeStrength();
+        if (count == 0) {
+            return fadeEdge;
+        } else {
+            if (mFirstPosition + count - 1 < mItemCount - 1) {
+                return 1.0f;
+            }
+
+            final int bottom = getChildAt(count - 1).getBottom();
+            final int height = getHeight();
+            final float fadeLength = (float) getVerticalFadingEdgeLength();
+            return bottom > height - mPaddingBottom ?
+                    (float) (bottom - height + mPaddingBottom) / fadeLength : fadeEdge;
+        }
+    }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        if (mSelector == null) {
+            useDefaultSelector();
+        }
+        final Rect listPadding = mListPadding;
+        listPadding.left = mSelectionLeftPadding + mPaddingLeft;
+        listPadding.top = mSelectionTopPadding + mPaddingTop;
+        listPadding.right = mSelectionRightPadding + mPaddingRight;
+        listPadding.bottom = mSelectionBottomPadding + mPaddingBottom;
+    }
+
+    @Override
+    protected void onLayout(boolean changed, int l, int t, int r, int b) {
+        super.onLayout(changed, l, t, r, b);
+        mInLayout = true;
+        layoutChildren();
+        mInLayout = false;
+    }
+
+    /**
+     * @hide
+     */
+    @Override
+    protected boolean setFrame(int left, int top, int right, int bottom) {
+        final boolean changed = super.setFrame(left, top, right, bottom);
+
+        // Reposition the popup when the frame has changed. This includes
+        // translating the widget, not just changing its dimension. The
+        // filter popup needs to follow the widget.
+        if (mFiltered && changed && getWindowVisibility() == View.VISIBLE && mPopup != null &&
+                mPopup.isShowing()) {
+            positionPopup(true);
+        }
+
+        return changed;
+    }
+
+    protected void layoutChildren() {
+    }
+
+    void updateScrollIndicators() {
+        if (mScrollUp != null) {
+            boolean canScrollUp;
+            // 0th element is not visible
+            canScrollUp = mFirstPosition > 0;
+
+            // ... Or top of 0th element is not visible
+            if (!canScrollUp) {
+                if (getChildCount() > 0) {
+                    View child = getChildAt(0);
+                    canScrollUp = child.getTop() < mListPadding.top;
+                }
+            }
+
+            mScrollUp.setVisibility(canScrollUp ? View.VISIBLE : View.INVISIBLE);
+        }
+
+        if (mScrollDown != null) {
+            boolean canScrollDown;
+            int count = getChildCount();
+
+            // Last item is not visible
+            canScrollDown = (mFirstPosition + count) < mItemCount;
+
+            // ... Or bottom of the last element is not visible
+            if (!canScrollDown && count > 0) {
+                View child = getChildAt(count - 1);
+                canScrollDown = child.getBottom() > mBottom - mListPadding.bottom;
+            }
+
+            mScrollDown.setVisibility(canScrollDown ? View.VISIBLE : View.INVISIBLE);
+        }
+    }
+
+    @Override
+    @ViewDebug.ExportedProperty
+    public View getSelectedView() {
+        if (mItemCount > 0 && mSelectedPosition >= 0) {
+            return getChildAt(mSelectedPosition - mFirstPosition);
+        } else {
+            return null;
+        }
+    }
+
+    /**
+     * List padding is the maximum of the normal view's padding and the padding of the selector.
+     *
+     * @see android.view.View#getPaddingTop()
+     * @see #getSelector()
+     *
+     * @return The top list padding.
+     */
+    public int getListPaddingTop() {
+        return mListPadding.top;
+    }
+
+    /**
+     * List padding is the maximum of the normal view's padding and the padding of the selector.
+     *
+     * @see android.view.View#getPaddingBottom()
+     * @see #getSelector()
+     *
+     * @return The bottom list padding.
+     */
+    public int getListPaddingBottom() {
+        return mListPadding.bottom;
+    }
+
+    /**
+     * List padding is the maximum of the normal view's padding and the padding of the selector.
+     *
+     * @see android.view.View#getPaddingLeft()
+     * @see #getSelector()
+     *
+     * @return The left list padding.
+     */
+    public int getListPaddingLeft() {
+        return mListPadding.left;
+    }
+
+    /**
+     * List padding is the maximum of the normal view's padding and the padding of the selector.
+     *
+     * @see android.view.View#getPaddingRight()
+     * @see #getSelector()
+     *
+     * @return The right list padding.
+     */
+    public int getListPaddingRight() {
+        return mListPadding.right;
+    }
+
+    /**
+     * Get a view and have it show the data associated with the specified
+     * position. This is called when we have already discovered that the view is
+     * not available for reuse in the recycle bin. The only choices left are
+     * converting an old view or making a new one.
+     *
+     * @param position The position to display
+     * @return A view displaying the data associated with the specified position
+     */
+    View obtainView(int position) {
+        View scrapView;
+
+        scrapView = mRecycler.getScrapView(position);
+
+        View child;
+        if (scrapView != null) {
+            if (ViewDebug.TRACE_RECYCLER) {
+                ViewDebug.trace(scrapView, ViewDebug.RecyclerTraceType.RECYCLE_FROM_SCRAP_HEAP,
+                        position, -1);
+            }
+
+            child = mAdapter.getView(position, scrapView, this);
+
+            if (ViewDebug.TRACE_RECYCLER) {
+                ViewDebug.trace(child, ViewDebug.RecyclerTraceType.BIND_VIEW,
+                        position, getChildCount());
+            }
+
+            if (child != scrapView) {
+                mRecycler.addScrapView(scrapView);
+                if (mCacheColorHint != 0) {
+                    child.setDrawingCacheBackgroundColor(mCacheColorHint);
+                }
+                if (ViewDebug.TRACE_RECYCLER) {
+                    ViewDebug.trace(scrapView, ViewDebug.RecyclerTraceType.MOVE_TO_SCRAP_HEAP,
+                            position, -1);
+                }
+            }
+        } else {
+            child = mAdapter.getView(position, null, this);
+            if (mCacheColorHint != 0) {
+                child.setDrawingCacheBackgroundColor(mCacheColorHint);
+            }
+            if (ViewDebug.TRACE_RECYCLER) {
+                ViewDebug.trace(child, ViewDebug.RecyclerTraceType.NEW_VIEW,
+                        position, getChildCount());
+            }
+        }
+
+        return child;
+    }
+
+    void positionSelector(View sel) {
+        final Rect selectorRect = mSelectorRect;
+        selectorRect.set(sel.getLeft(), sel.getTop(), sel.getRight(), sel.getBottom());
+        positionSelector(selectorRect.left, selectorRect.top, selectorRect.right,
+                selectorRect.bottom);
+
+        final boolean isChildViewEnabled = mIsChildViewEnabled;
+        if (sel.isEnabled() != isChildViewEnabled) {
+            mIsChildViewEnabled = !isChildViewEnabled;
+            refreshDrawableState();
+        }
+    }
+
+    private void positionSelector(int l, int t, int r, int b) {
+        mSelectorRect.set(l - mSelectionLeftPadding, t - mSelectionTopPadding, r
+                + mSelectionRightPadding, b + mSelectionBottomPadding);
+    }
+
+    @Override
+    protected void dispatchDraw(Canvas canvas) {
+        int saveCount = 0;
+        final boolean clipToPadding = (mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK;
+        if (clipToPadding) {
+            saveCount = canvas.save();
+            final int scrollX = mScrollX;
+            final int scrollY = mScrollY;
+            canvas.clipRect(scrollX + mPaddingLeft, scrollY + mPaddingTop,
+                    scrollX + mRight - mLeft - mPaddingRight,
+                    scrollY + mBottom - mTop - mPaddingBottom);
+            mGroupFlags &= ~CLIP_TO_PADDING_MASK;
+        }
+
+        final boolean drawSelectorOnTop = mDrawSelectorOnTop;
+        if (!drawSelectorOnTop) {
+            drawSelector(canvas);
+        }
+
+        super.dispatchDraw(canvas);
+
+        if (drawSelectorOnTop) {
+            drawSelector(canvas);
+        }
+
+        if (clipToPadding) {
+            canvas.restoreToCount(saveCount);
+            mGroupFlags |= CLIP_TO_PADDING_MASK;
+        }
+    }
+
+    @Override
+    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
+        if (getChildCount() > 0) {
+            mDataChanged = true;
+            rememberSyncState();
+        }
+        if (mFastScroller != null) {
+            mFastScroller.onSizeChanged(w, h, oldw, oldh);
+        }
+    }
+
+    /**
+     * @return True if the current touch mode requires that we draw the selector in the pressed
+     *         state.
+     */
+    boolean touchModeDrawsInPressedState() {
+        // FIXME use isPressed for this
+        switch (mTouchMode) {
+        case TOUCH_MODE_TAP:
+        case TOUCH_MODE_DONE_WAITING:
+            return true;
+        default:
+            return false;
+        }
+    }
+
+    /**
+     * Indicates whether this view is in a state where the selector should be drawn. This will
+     * happen if we have focus but are not in touch mode, or we are in the middle of displaying
+     * the pressed state for an item.
+     *
+     * @return True if the selector should be shown
+     */
+    boolean shouldShowSelector() {
+        return (hasFocus() && !isInTouchMode()) || touchModeDrawsInPressedState();
+    }
+
+    private void drawSelector(Canvas canvas) {
+        if (shouldShowSelector() && mSelectorRect != null && !mSelectorRect.isEmpty()) {
+            final Drawable selector = mSelector;
+            selector.setBounds(mSelectorRect);
+            selector.draw(canvas);
+        }
+    }
+
+    /**
+     * Controls whether the selection highlight drawable should be drawn on top of the item or
+     * behind it.
+     *
+     * @param onTop If true, the selector will be drawn on the item it is highlighting. The default
+     *        is false.
+     *
+     * @attr ref android.R.styleable#AbsListView_drawSelectorOnTop
+     */
+    public void setDrawSelectorOnTop(boolean onTop) {
+        mDrawSelectorOnTop = onTop;
+    }
+
+    /**
+     * Set a Drawable that should be used to highlight the currently selected item.
+     *
+     * @param resID A Drawable resource to use as the selection highlight.
+     *
+     * @attr ref android.R.styleable#AbsListView_listSelector
+     */
+    public void setSelector(int resID) {
+        setSelector(getResources().getDrawable(resID));
+    }
+
+    public void setSelector(Drawable sel) {
+        if (mSelector != null) {
+            mSelector.setCallback(null);
+            unscheduleDrawable(mSelector);
+        }
+        mSelector = sel;
+        Rect padding = new Rect();
+        sel.getPadding(padding);
+        mSelectionLeftPadding = padding.left;
+        mSelectionTopPadding = padding.top;
+        mSelectionRightPadding = padding.right;
+        mSelectionBottomPadding = padding.bottom;
+        sel.setCallback(this);
+        sel.setState(getDrawableState());
+    }
+
+    /**
+     * Returns the selector {@link android.graphics.drawable.Drawable} that is used to draw the
+     * selection in the list.
+     *
+     * @return the drawable used to display the selector
+     */
+    public Drawable getSelector() {
+        return mSelector;
+    }
+
+    /**
+     * Sets the selector state to "pressed" and posts a CheckForKeyLongPress to see if
+     * this is a long press.
+     */
+    void keyPressed() {
+        Drawable selector = mSelector;
+        Rect selectorRect = mSelectorRect;
+        if (selector != null && (isFocused() || touchModeDrawsInPressedState())
+                && selectorRect != null && !selectorRect.isEmpty()) {
+
+            final View v = getChildAt(mSelectedPosition - mFirstPosition);
+
+            if (v != null) v.setPressed(true);
+            setPressed(true);
+
+            final boolean longClickable = isLongClickable();
+            Drawable d = selector.getCurrent();
+            if (d != null && d instanceof TransitionDrawable) {
+                if (longClickable) {
+                    ((TransitionDrawable) d).startTransition(ViewConfiguration
+                            .getLongPressTimeout());
+                } else {
+                    ((TransitionDrawable) d).resetTransition();
+                }
+            }
+            if (longClickable && !mDataChanged) {
+                if (mPendingCheckForKeyLongPress == null) {
+                    mPendingCheckForKeyLongPress = new CheckForKeyLongPress();
+                }
+                mPendingCheckForKeyLongPress.rememberWindowAttachCount();
+                postDelayed(mPendingCheckForKeyLongPress, ViewConfiguration.getLongPressTimeout());
+            }
+        }
+    }
+
+    public void setScrollIndicators(View up, View down) {
+        mScrollUp = up;
+        mScrollDown = down;
+    }
+
+    @Override
+    protected void drawableStateChanged() {
+        super.drawableStateChanged();
+        if (mSelector != null) {
+            mSelector.setState(getDrawableState());
+        }
+    }
+
+    @Override
+    protected int[] onCreateDrawableState(int extraSpace) {
+        // If the child view is enabled then do the default behavior.
+        if (mIsChildViewEnabled) {
+            // Common case
+            return super.onCreateDrawableState(extraSpace);
+        }
+
+        // The selector uses this View's drawable state. The selected child view
+        // is disabled, so we need to remove the enabled state from the drawable
+        // states.
+        final int enabledState = ENABLED_STATE_SET[0];
+
+        // If we don't have any extra space, it will return one of the static state arrays,
+        // and clearing the enabled state on those arrays is a bad thing!  If we specify
+        // we need extra space, it will create+copy into a new array that safely mutable.
+        int[] state = super.onCreateDrawableState(extraSpace + 1);
+        int enabledPos = -1;
+        for (int i = state.length - 1; i >= 0; i--) {
+            if (state[i] == enabledState) {
+                enabledPos = i;
+                break;
+            }
+        }
+
+        // Remove the enabled state
+        if (enabledPos >= 0) {
+            System.arraycopy(state, enabledPos + 1, state, enabledPos,
+                    state.length - enabledPos - 1);
+        }
+        
+        return state;
+    }
+
+    @Override
+    public boolean verifyDrawable(Drawable dr) {
+        return mSelector == dr || super.verifyDrawable(dr);
+    }
+
+    @Override
+    protected void onAttachedToWindow() {
+        super.onAttachedToWindow();
+
+        final ViewTreeObserver treeObserver = getViewTreeObserver();
+        if (treeObserver != null) {
+            treeObserver.addOnTouchModeChangeListener(this);
+        }
+    }
+
+    @Override
+    protected void onDetachedFromWindow() {
+        super.onDetachedFromWindow();
+
+        final ViewTreeObserver treeObserver = getViewTreeObserver();
+        if (treeObserver != null) {
+            treeObserver.removeOnTouchModeChangeListener(this);
+        }
+    }
+
+    @Override
+    public void onWindowFocusChanged(boolean hasWindowFocus) {
+        super.onWindowFocusChanged(hasWindowFocus);
+
+        final int touchMode = isInTouchMode() ? TOUCH_MODE_ON : TOUCH_MODE_OFF;
+
+        if (!hasWindowFocus) {
+            setChildrenDrawingCacheEnabled(false);
+            removeCallbacks(mFlingRunnable);
+            // Always hide the type filter
+            dismissPopup();
+
+            if (touchMode == TOUCH_MODE_OFF) {
+                // Remember the last selected element
+                mResurrectToPosition = mSelectedPosition;
+            }
+        } else {
+            if (mFiltered) {
+                // Show the type filter only if a filter is in effect
+                showPopup();
+            }
+
+            // If we changed touch mode since the last time we had focus
+            if (touchMode != mLastTouchMode && mLastTouchMode != TOUCH_MODE_UNKNOWN) {
+                // If we come back in trackball mode, we bring the selection back
+                if (touchMode == TOUCH_MODE_OFF) {
+                    // This will trigger a layout
+                    resurrectSelection();
+
+                // If we come back in touch mode, then we want to hide the selector
+                } else {
+                    hideSelector();
+                    mLayoutMode = LAYOUT_NORMAL;
+                    layoutChildren();
+                }
+            }
+        }
+
+        mLastTouchMode = touchMode;
+    }
+
+    /**
+     * Creates the ContextMenuInfo returned from {@link #getContextMenuInfo()}. This
+     * methods knows the view, position and ID of the item that received the
+     * long press.
+     *
+     * @param view The view that received the long press.
+     * @param position The position of the item that received the long press.
+     * @param id The ID of the item that received the long press.
+     * @return The extra information that should be returned by
+     *         {@link #getContextMenuInfo()}.
+     */
+    ContextMenuInfo createContextMenuInfo(View view, int position, long id) {
+        return new AdapterContextMenuInfo(view, position, id);
+    }
+
+    /**
+     * A base class for Runnables that will check that their view is still attached to
+     * the original window as when the Runnable was created.
+     *
+     */
+    private class WindowRunnnable {
+        private int mOriginalAttachCount;
+        
+        public void rememberWindowAttachCount() {
+            mOriginalAttachCount = getWindowAttachCount();
+        }
+        
+        public boolean sameWindow() {
+            return hasWindowFocus() && getWindowAttachCount() == mOriginalAttachCount;
+        }
+    }
+    
+    private class PerformClick extends WindowRunnnable implements Runnable {
+        View mChild;
+        int mClickMotionPosition;
+
+        public void run() {
+            // The data has changed since we posted this action in the event queue,
+            // bail out before bad things happen
+            if (mDataChanged) return;
+
+            if (mAdapter != null && mItemCount > 0 &&
+                    mClickMotionPosition < mAdapter.getCount() && sameWindow()) {
+                performItemClick(mChild, mClickMotionPosition, getAdapter().getItemId(
+                        mClickMotionPosition));
+            }
+        }
+    }
+
+    private class CheckForLongPress extends WindowRunnnable implements Runnable {
+        public void run() {
+            final int motionPosition = mMotionPosition;
+            final View child = getChildAt(motionPosition - mFirstPosition);
+            if (child != null) {
+                final int longPressPosition = mMotionPosition;
+                final long longPressId = mAdapter.getItemId(mMotionPosition);
+
+                boolean handled = false;
+                if (sameWindow() && !mDataChanged) { 
+                    handled = performLongPress(child, longPressPosition, longPressId);
+                }
+                if (handled) {
+                    mTouchMode = TOUCH_MODE_REST;
+                    setPressed(false);
+                    child.setPressed(false);
+                } else {
+                    mTouchMode = TOUCH_MODE_DONE_WAITING;
+                }
+
+            }
+        }
+    }
+    
+    private class CheckForKeyLongPress extends WindowRunnnable implements Runnable {
+        public void run() {
+            if (isPressed() && mSelectedPosition >= 0) {
+                int index = mSelectedPosition - mFirstPosition;
+                View v = getChildAt(index);
+
+                if (!mDataChanged) {
+                    boolean handled = false;
+                    if (sameWindow()) {
+                        handled = performLongPress(v, mSelectedPosition, mSelectedRowId);
+                    }
+                    if (handled) {
+                        setPressed(false);
+                        v.setPressed(false);
+                    }
+                } else {
+                    setPressed(false);
+                    if (v != null) v.setPressed(false);
+                }
+            }
+        }
+    }
+
+    private boolean performLongPress(final View child,
+            final int longPressPosition, final long longPressId) {
+        boolean handled = false;
+
+        if (mOnItemLongClickListener != null) {
+            handled = mOnItemLongClickListener.onItemLongClick(AbsListView.this, child,
+                    longPressPosition, longPressId);
+        }
+        if (!handled) {
+            mContextMenuInfo = createContextMenuInfo(child, longPressPosition, longPressId);
+            handled = super.showContextMenuForChild(AbsListView.this);
+        }
+        if (handled) {
+            performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
+        }
+        return handled;
+    }
+
+    @Override
+    protected ContextMenuInfo getContextMenuInfo() {
+        return mContextMenuInfo;
+    }
+
+    @Override
+    public boolean showContextMenuForChild(View originalView) {
+        final int longPressPosition = getPositionForView(originalView);
+        if (longPressPosition >= 0) {
+            final long longPressId = mAdapter.getItemId(longPressPosition);
+            boolean handled = false;
+
+            if (mOnItemLongClickListener != null) {
+                handled = mOnItemLongClickListener.onItemLongClick(AbsListView.this, originalView,
+                        longPressPosition, longPressId);
+            }
+            if (!handled) {
+                mContextMenuInfo = createContextMenuInfo(
+                        getChildAt(longPressPosition - mFirstPosition),
+                        longPressPosition, longPressId);
+                handled = super.showContextMenuForChild(originalView);
+            }
+
+            return handled;
+        }
+        return false;
+    }
+
+    @Override
+    public boolean onKeyUp(int keyCode, KeyEvent event) {
+        switch (keyCode) {
+        case KeyEvent.KEYCODE_DPAD_CENTER:
+        case KeyEvent.KEYCODE_ENTER:
+            if (isPressed() && mSelectedPosition >= 0 && mAdapter != null &&
+                    mSelectedPosition < mAdapter.getCount()) {
+                final View view = getChildAt(mSelectedPosition - mFirstPosition);
+                performItemClick(view, mSelectedPosition, mSelectedRowId);
+                setPressed(false);
+                if (view != null) view.setPressed(false);
+                return true;
+            }
+        }
+        return super.onKeyUp(keyCode, event);
+    }
+
+    @Override
+    protected void dispatchSetPressed(boolean pressed) {
+        // Don't dispatch setPressed to our children. We call setPressed on ourselves to
+        // get the selector in the right state, but we don't want to press each child.
+    }
+
+    /**
+     * Maps a point to a position in the list.
+     *
+     * @param x X in local coordinate
+     * @param y Y in local coordinate
+     * @return The position of the item which contains the specified point, or
+     *         {@link #INVALID_POSITION} if the point does not intersect an item.
+     */
+    public int pointToPosition(int x, int y) {
+        Rect frame = mTouchFrame;
+        if (frame == null) {
+            mTouchFrame = new Rect();
+            frame = mTouchFrame;
+        }
+
+        final int count = getChildCount();
+        for (int i = count - 1; i >= 0; i--) {
+            final View child = getChildAt(i);
+            if (child.getVisibility() == View.VISIBLE) {
+                child.getHitRect(frame);
+                if (frame.contains(x, y)) {
+                    return mFirstPosition + i;
+                }
+            }
+        }
+        return INVALID_POSITION;
+    }
+
+
+    /**
+     * Maps a point to a the rowId of the item which intersects that point.
+     *
+     * @param x X in local coordinate
+     * @param y Y in local coordinate
+     * @return The rowId of the item which contains the specified point, or {@link #INVALID_ROW_ID}
+     *         if the point does not intersect an item.
+     */
+    public long pointToRowId(int x, int y) {
+        int position = pointToPosition(x, y);
+        if (position >= 0) {
+            return mAdapter.getItemId(position);
+        }
+        return INVALID_ROW_ID;
+    }
+
+    final class CheckForTap implements Runnable {
+        public void run() {
+            if (mTouchMode == TOUCH_MODE_DOWN) {
+                mTouchMode = TOUCH_MODE_TAP;
+                final View child = getChildAt(mMotionPosition - mFirstPosition);
+                if (child != null && !child.hasFocusable()) {
+                    mLayoutMode = LAYOUT_NORMAL;
+
+                    if (!mDataChanged) {
+                        layoutChildren();
+                        child.setPressed(true);
+                        positionSelector(child);
+                        setPressed(true);
+
+                        final int longPressTimeout = ViewConfiguration.getLongPressTimeout();
+                        final boolean longClickable = isLongClickable();
+
+                        if (mSelector != null) {
+                            Drawable d = mSelector.getCurrent();
+                            if (d != null && d instanceof TransitionDrawable) {
+                                if (longClickable) {
+                                    ((TransitionDrawable) d).startTransition(longPressTimeout);
+                                } else {
+                                    ((TransitionDrawable) d).resetTransition();
+                                }
+                            }
+                        }
+
+                        if (longClickable) {
+                            if (mPendingCheckForLongPress == null) {
+                                mPendingCheckForLongPress = new CheckForLongPress();
+                            }
+                            mPendingCheckForLongPress.rememberWindowAttachCount();
+                            postDelayed(mPendingCheckForLongPress, longPressTimeout);
+                        } else {
+                            mTouchMode = TOUCH_MODE_DONE_WAITING;
+                        }
+                    } else {
+                        mTouchMode = TOUCH_MODE_DONE_WAITING;                        
+                    }
+                }
+            }
+        }
+    }
+
+    private boolean startScrollIfNeeded(int deltaY) {
+        // Check if we have moved far enough that it looks more like a
+        // scroll than a tap
+        final int distance = Math.abs(deltaY);
+        if (distance > mTouchSlop) {
+            createScrollingCache();
+            mTouchMode = TOUCH_MODE_SCROLL;
+            mMotionCorrection = deltaY;
+            final Handler handler = getHandler();
+            // Handler should not be null unless the AbsListView is not attached to a
+            // window, which would make it very hard to scroll it... but the monkeys
+            // say it's possible.
+            if (handler != null) {
+                handler.removeCallbacks(mPendingCheckForLongPress);
+            }
+            setPressed(false);
+            View motionView = getChildAt(mMotionPosition - mFirstPosition);
+            if (motionView != null) {
+                motionView.setPressed(false);
+            }
+            reportScrollStateChange(OnScrollListener.SCROLL_STATE_TOUCH_SCROLL);
+            // Time to start stealing events! Once we've stolen them, don't let anyone
+            // steal from us
+            requestDisallowInterceptTouchEvent(true);
+            return true;
+        }
+
+        return false;
+    }
+
+    public void onTouchModeChanged(boolean isInTouchMode) {
+        if (isInTouchMode) {
+            // Get rid of the selection when we enter touch mode
+            hideSelector();
+            // Layout, but only if we already have done so previously.
+            // (Otherwise may clobber a LAYOUT_SYNC layout that was requested to restore
+            // state.)
+            if (getHeight() > 0 && getChildCount() > 0) {
+                // We do not lose focus initiating a touch (since AbsListView is focusable in
+                // touch mode). Force an initial layout to get rid of the selection.
+                mLayoutMode = LAYOUT_NORMAL;
+                layoutChildren();
+            }
+        }
+    }
+
+    @Override
+    public boolean onTouchEvent(MotionEvent ev) {
+        
+        if (mFastScroller != null) {
+            boolean intercepted = mFastScroller.onTouchEvent(ev);
+            if (intercepted) {
+                return true;
+            }            
+        }
+        final int action = ev.getAction();
+        final int x = (int) ev.getX();
+        final int y = (int) ev.getY();
+
+        View v;
+        int deltaY;
+
+        if (mVelocityTracker == null) {
+            mVelocityTracker = VelocityTracker.obtain();
+        }
+        mVelocityTracker.addMovement(ev);
+
+        switch (action) {
+        case MotionEvent.ACTION_DOWN: {
+            int motionPosition = pointToPosition(x, y);
+            if (!mDataChanged) {
+                if ((mTouchMode != TOUCH_MODE_FLING) && (motionPosition >= 0)
+                        && (getAdapter().isEnabled(motionPosition))) {
+                    // User clicked on an actual view (and was not stopping a fling). It might be a
+                    // click or a scroll. Assume it is a click until proven otherwise
+                    mTouchMode = TOUCH_MODE_DOWN;
+                    // FIXME Debounce
+                    if (mPendingCheckForTap == null) {
+                        mPendingCheckForTap = new CheckForTap();
+                    }
+                    postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
+                } else {
+                    if (ev.getEdgeFlags() != 0 && motionPosition < 0) {
+                        // If we couldn't find a view to click on, but the down event was touching
+                        // the edge, we will bail out and try again. This allows the edge correcting
+                        // code in ViewRoot to try to find a nearby view to select
+                        return false;
+                    }
+                    // User clicked on whitespace, or stopped a fling. It is a scroll.
+                    createScrollingCache();
+                    mTouchMode = TOUCH_MODE_SCROLL;
+                    motionPosition = findMotionRow(y);
+                    reportScrollStateChange(OnScrollListener.SCROLL_STATE_TOUCH_SCROLL);
+                }
+            }
+
+            if (motionPosition >= 0) {
+                // Remember where the motion event started
+                v = getChildAt(motionPosition - mFirstPosition);
+                mMotionViewOriginalTop = v.getTop();
+                mMotionX = x;
+                mMotionY = y;
+                mMotionPosition = motionPosition;
+            }
+            mLastY = Integer.MIN_VALUE;
+            break;
+        }
+
+        case MotionEvent.ACTION_MOVE: {
+            deltaY = y - mMotionY;
+            switch (mTouchMode) {
+            case TOUCH_MODE_DOWN:
+            case TOUCH_MODE_TAP:
+            case TOUCH_MODE_DONE_WAITING:
+                // Check if we have moved far enough that it looks more like a
+                // scroll than a tap
+                startScrollIfNeeded(deltaY);
+                break;
+            case TOUCH_MODE_SCROLL:
+                if (PROFILE_SCROLLING) {
+                    if (!mScrollProfilingStarted) {
+                        Debug.startMethodTracing("AbsListViewScroll");
+                        mScrollProfilingStarted = true;
+                    }
+                }
+
+                if (y != mLastY) {
+                    deltaY -= mMotionCorrection;
+                    int incrementalDeltaY = mLastY != Integer.MIN_VALUE ? y - mLastY : deltaY;
+                    trackMotionScroll(deltaY, incrementalDeltaY);
+
+                    // Check to see if we have bumped into the scroll limit
+                    View motionView = this.getChildAt(mMotionPosition - mFirstPosition);
+                    if (motionView != null) {
+                        // Check if the top of the motion view is where it is
+                        // supposed to be
+                        if (motionView.getTop() != mMotionViewNewTop) {
+                            // We did not scroll the full amount. Treat this essentially like the
+                            // start of a new touch scroll
+                            final int motionPosition = findMotionRow(y);
+
+                            mMotionCorrection = 0;
+                            motionView = getChildAt(motionPosition - mFirstPosition);
+                            mMotionViewOriginalTop = motionView.getTop();
+                            mMotionY = y;
+                            mMotionPosition = motionPosition;
+                        }
+                    }
+                    mLastY = y;
+                }
+                break;
+            }
+
+            break;
+        }
+
+        case MotionEvent.ACTION_UP: {
+            switch (mTouchMode) {
+            case TOUCH_MODE_DOWN:
+            case TOUCH_MODE_TAP:
+            case TOUCH_MODE_DONE_WAITING:
+                final int motionPosition = mMotionPosition;
+                final View child = getChildAt(motionPosition - mFirstPosition);
+                if (child != null && !child.hasFocusable()) {
+                    if (mTouchMode != TOUCH_MODE_DOWN) {
+                        child.setPressed(false);
+                    }
+
+                    if (mPerformClick == null) {
+                        mPerformClick = new PerformClick();
+                    }
+
+                    final AbsListView.PerformClick performClick = mPerformClick;
+                    performClick.mChild = child;
+                    performClick.mClickMotionPosition = motionPosition;
+                    performClick.rememberWindowAttachCount();
+
+                    mResurrectToPosition = motionPosition;
+
+                    if (mTouchMode == TOUCH_MODE_DOWN || mTouchMode == TOUCH_MODE_TAP) {
+                        final Handler handler = getHandler();
+                        if (handler != null) {
+                            handler.removeCallbacks(mTouchMode == TOUCH_MODE_DOWN ?
+                                    mPendingCheckForTap : mPendingCheckForLongPress);
+                        }
+                        mLayoutMode = LAYOUT_NORMAL;
+                        mTouchMode = TOUCH_MODE_TAP;
+                        if (!mDataChanged) {
+                            setSelectedPositionInt(mMotionPosition);
+                            layoutChildren();
+                            child.setPressed(true);
+                            positionSelector(child);
+                            setPressed(true);
+                            if (mSelector != null) {
+                                Drawable d = mSelector.getCurrent();
+                                if (d != null && d instanceof TransitionDrawable) {
+                                    ((TransitionDrawable)d).resetTransition();
+                                }
+                            }
+                            postDelayed(new Runnable() {
+                                public void run() {
+                                    child.setPressed(false);
+                                    setPressed(false);
+                                    if (!mDataChanged) {
+                                        post(performClick);
+                                    }
+                                    mTouchMode = TOUCH_MODE_REST;
+                                }
+                            }, ViewConfiguration.getPressedStateDuration());
+                        }
+                        return true;
+                    } else {
+                        if (!mDataChanged) {
+                            post(performClick);
+                        }
+                    }
+                }
+                mTouchMode = TOUCH_MODE_REST;
+                break;
+            case TOUCH_MODE_SCROLL:
+                final VelocityTracker velocityTracker = mVelocityTracker;
+                velocityTracker.computeCurrentVelocity(1000);
+                int initialVelocity = (int)velocityTracker.getYVelocity();
+
+                if ((Math.abs(initialVelocity) >
+                        ViewConfiguration.get(mContext).getScaledMinimumFlingVelocity()) &&
+                        (getChildCount() > 0)) {
+                    if (mFlingRunnable == null) {
+                        mFlingRunnable = new FlingRunnable();
+                    }
+                    reportScrollStateChange(OnScrollListener.SCROLL_STATE_FLING);
+                    mFlingRunnable.start(-initialVelocity);
+                } else {
+                    mTouchMode = TOUCH_MODE_REST;
+                    reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
+                }
+            }
+
+            setPressed(false);
+            
+            // Need to redraw since we probably aren't drawing the selector anymore
+            invalidate();
+            
+            final Handler handler = getHandler();
+            if (handler != null) {
+                handler.removeCallbacks(mPendingCheckForLongPress);
+            }
+
+            if (mVelocityTracker != null) {
+                mVelocityTracker.recycle();
+                mVelocityTracker = null;
+            }
+
+            if (PROFILE_SCROLLING) {
+                if (mScrollProfilingStarted) {
+                    Debug.stopMethodTracing();
+                    mScrollProfilingStarted = false;
+                }
+            }
+            break;
+        }
+
+        case MotionEvent.ACTION_CANCEL: {
+            mTouchMode = TOUCH_MODE_REST;
+            setPressed(false);
+            View motionView = this.getChildAt(mMotionPosition - mFirstPosition);
+            if (motionView != null) {
+                motionView.setPressed(false);
+            }
+            clearScrollingCache();
+
+            final Handler handler = getHandler();
+            if (handler != null) {
+                handler.removeCallbacks(mPendingCheckForLongPress);
+            }
+
+            if (mVelocityTracker != null) {
+                mVelocityTracker.recycle();
+                mVelocityTracker = null;
+            }
+        }
+
+        }
+
+        return true;
+    }
+    
+    @Override
+    public void draw(Canvas canvas) {
+        super.draw(canvas);
+        if (mFastScroller != null) {
+            mFastScroller.draw(canvas);
+        }
+    }
+
+    @Override
+    public boolean onInterceptTouchEvent(MotionEvent ev) {
+        int action = ev.getAction();
+        int x = (int) ev.getX();
+        int y = (int) ev.getY();
+        View v;
+        
+        if (mFastScroller != null) {
+            boolean intercepted = mFastScroller.onInterceptTouchEvent(ev);
+            if (intercepted) {
+                return true;
+            }
+        }
+        
+        switch (action) {
+        case MotionEvent.ACTION_DOWN: {
+            int motionPosition = findMotionRow(y);
+            if (mTouchMode != TOUCH_MODE_FLING && motionPosition >= 0) {
+                // User clicked on an actual view (and was not stopping a fling).
+                // Remember where the motion event started
+                v = getChildAt(motionPosition - mFirstPosition);
+                mMotionViewOriginalTop = v.getTop();
+                mMotionX = x;
+                mMotionY = y;
+                mMotionPosition = motionPosition;
+                mTouchMode = TOUCH_MODE_DOWN;
+                clearScrollingCache();
+            }
+            mLastY = Integer.MIN_VALUE;
+            break;
+        }
+
+        case MotionEvent.ACTION_MOVE: {
+            switch (mTouchMode) {
+            case TOUCH_MODE_DOWN:
+                if (startScrollIfNeeded(y - mMotionY)) {
+                    return true;
+                }
+                break;
+            }
+            break;
+        }
+
+        case MotionEvent.ACTION_UP: {
+            mTouchMode = TOUCH_MODE_REST;
+            reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
+            break;
+        }
+        }
+
+        return false;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void addTouchables(ArrayList<View> views) {
+        final int count = getChildCount();
+        final int firstPosition = mFirstPosition;
+        final ListAdapter adapter = mAdapter;
+
+        if (adapter == null) {
+            return;
+        }
+
+        for (int i = 0; i < count; i++) {
+            final View child = getChildAt(i);
+            if (adapter.isEnabled(firstPosition + i)) {
+                views.add(child);
+            }
+            child.addTouchables(views);
+        }
+    }
+
+    /**
+     * Fires an "on scroll state changed" event to the registered
+     * {@link android.widget.AbsListView.OnScrollListener}, if any. The state change
+     * is fired only if the specified state is different from the previously known state.
+     *
+     * @param newState The new scroll state.
+     */
+    void reportScrollStateChange(int newState) {
+        if (newState != mLastScrollState) {
+            if (mOnScrollListener != null) {
+                mOnScrollListener.onScrollStateChanged(this, newState);
+                mLastScrollState = newState;
+            }
+        }
+    }
+
+    /**
+     * Responsible for fling behavior. Use {@link #start(int)} to
+     * initiate a fling. Each frame of the fling is handled in {@link #run()}.
+     * A FlingRunnable will keep re-posting itself until the fling is done.
+     *
+     */
+    private class FlingRunnable implements Runnable {
+        /**
+         * Tracks the decay of a fling scroll
+         */
+        private Scroller mScroller;
+
+        /**
+         * Y value reported by mScroller on the previous fling
+         */
+        private int mLastFlingY;
+
+        public FlingRunnable() {
+            mScroller = new Scroller(getContext());
+        }
+
+        public void start(int initialVelocity) {
+            int initialY = initialVelocity < 0 ? Integer.MAX_VALUE : 0;
+            mLastFlingY = initialY;
+            mScroller.fling(0, initialY, 0, initialVelocity,
+                    0, Integer.MAX_VALUE, 0, Integer.MAX_VALUE);
+            mTouchMode = TOUCH_MODE_FLING;
+            post(this);
+
+            if (PROFILE_FLINGING) {
+                if (!mFlingProfilingStarted) {
+                    Debug.startMethodTracing("AbsListViewFling");
+                    mFlingProfilingStarted = true;
+                }
+            }
+        }
+
+        private void endFling() {
+            mTouchMode = TOUCH_MODE_REST;
+            reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
+            clearScrollingCache();
+        }
+
+        public void run() {
+            if (mTouchMode != TOUCH_MODE_FLING) {
+                return;
+            }
+
+            if (mItemCount == 0 || getChildCount() == 0) {
+                endFling();
+                return;
+            }
+
+            final Scroller scroller = mScroller;
+            boolean more = scroller.computeScrollOffset();
+            final int y = scroller.getCurrY();
+
+            // Flip sign to convert finger direction to list items direction
+            // (e.g. finger moving down means list is moving towards the top)
+            int delta = mLastFlingY - y;
+
+            // Pretend that each frame of a fling scroll is a touch scroll
+            if (delta > 0) {
+                // List is moving towards the top. Use first view as mMotionPosition
+                mMotionPosition = mFirstPosition;
+                final View firstView = getChildAt(0);
+                mMotionViewOriginalTop = firstView.getTop();
+
+                // Don't fling more than 1 screen
+                delta = Math.min(getHeight() - mPaddingBottom - mPaddingTop - 1, delta);
+            } else {
+                // List is moving towards the bottom. Use last view as mMotionPosition
+                int offsetToLast = getChildCount() - 1;
+                mMotionPosition = mFirstPosition + offsetToLast;
+
+                final View lastView = getChildAt(offsetToLast);
+                mMotionViewOriginalTop = lastView.getTop();
+
+                // Don't fling more than 1 screen
+                delta = Math.max(-(getHeight() - mPaddingBottom - mPaddingTop - 1), delta);
+            }
+
+            trackMotionScroll(delta, delta);
+
+            // Check to see if we have bumped into the scroll limit
+            View motionView = getChildAt(mMotionPosition - mFirstPosition);
+            if (motionView != null) {
+                // Check if the top of the motion view is where it is
+                // supposed to be
+                if (motionView.getTop() != mMotionViewNewTop) {
+                   more = false;
+                }
+            }
+
+            if (more) {
+                mLastFlingY = y;
+                post(this);
+            } else {
+                endFling();
+                if (PROFILE_FLINGING) {
+                    if (mFlingProfilingStarted) {
+                        Debug.stopMethodTracing();
+                        mFlingProfilingStarted = false;
+                    }
+                }
+            }
+        }
+    }
+
+    private void createScrollingCache() {
+        if (mScrollingCacheEnabled && !mCachingStarted) {
+            setChildrenDrawnWithCacheEnabled(true);
+            setChildrenDrawingCacheEnabled(true);
+            mCachingStarted = true;
+        }
+    }
+
+    private void clearScrollingCache() {
+        if (mCachingStarted) {
+            setChildrenDrawnWithCacheEnabled(false);
+            if ((mPersistentDrawingCache & PERSISTENT_SCROLLING_CACHE) == 0) {
+                setChildrenDrawingCacheEnabled(false);
+            }
+            if (!isAlwaysDrawnWithCacheEnabled()) {
+                invalidate();
+            }
+            mCachingStarted = false;
+        }
+    }
+
+    /**
+     * Track a motion scroll
+     *
+     * @param deltaY Amount to offset mMotionView. This is the accumulated delta since the motion
+     *        began. Positive numbers mean the user's finger is moving down the screen.
+     * @param incrementalDeltaY Change in deltaY from the previous event.
+     */
+    void trackMotionScroll(int deltaY, int incrementalDeltaY) {
+        final int childCount = getChildCount();
+        if (childCount == 0) {
+            return;
+        }
+
+        final int firstTop = getChildAt(0).getTop();
+        final int lastBottom = getChildAt(childCount - 1).getBottom();
+
+        final Rect listPadding = mListPadding;
+
+         // FIXME account for grid vertical spacing too?
+        final int spaceAbove = listPadding.top - firstTop;
+        final int end = getHeight() - listPadding.bottom;
+        final int spaceBelow = lastBottom - end;
+
+        final int height = getHeight() - mPaddingBottom - mPaddingTop;
+        if (deltaY < 0) {
+            deltaY = Math.max(-(height - 1), deltaY);
+        } else {
+            deltaY = Math.min(height - 1, deltaY);
+        }
+
+        if (incrementalDeltaY < 0) {
+            incrementalDeltaY = Math.max(-(height - 1), incrementalDeltaY);
+        } else {
+            incrementalDeltaY = Math.min(height - 1, incrementalDeltaY);
+        }
+
+        final int absIncrementalDeltaY = Math.abs(incrementalDeltaY);
+
+        if (spaceAbove >= absIncrementalDeltaY && spaceBelow >= absIncrementalDeltaY) {
+            hideSelector();
+            offsetChildrenTopAndBottom(incrementalDeltaY);
+            invalidate();
+            mMotionViewNewTop = mMotionViewOriginalTop + deltaY;
+        } else {
+            final int firstPosition = mFirstPosition;
+
+            if (firstPosition == 0 && firstTop >= listPadding.top && deltaY > 0) {
+                // Don't need to move views down if the top of the first position is already visible
+                return;
+            }
+
+            if (firstPosition + childCount == mItemCount && lastBottom <= end && deltaY < 0) {
+                // Don't need to move views up if the bottom of the last position is already visible
+                return;
+            }
+
+            final boolean down = incrementalDeltaY < 0;
+
+            hideSelector();
+
+            final int headerViewsCount = getHeaderViewsCount();
+            final int footerViewsStart = mItemCount - getFooterViewsCount();
+
+            int start = 0;
+            int count = 0;
+
+            if (down) {
+                final int top = listPadding.top - incrementalDeltaY;
+                for (int i = 0; i < childCount; i++) {
+                    final View child = getChildAt(i);
+                    if (child.getBottom() >= top) {
+                        break;
+                    } else {
+                        count++;
+                        int position = firstPosition + i;
+                        if (position >= headerViewsCount && position < footerViewsStart) {
+                            mRecycler.addScrapView(child);
+
+                            if (ViewDebug.TRACE_RECYCLER) {
+                                ViewDebug.trace(child,
+                                        ViewDebug.RecyclerTraceType.MOVE_TO_SCRAP_HEAP,
+                                        firstPosition + i, -1);
+                            }
+                        }
+                    }
+                }
+            } else {
+                final int bottom = getHeight() - listPadding.bottom - incrementalDeltaY;
+                for (int i = childCount - 1; i >= 0; i--) {
+                    final View child = getChildAt(i);
+                    if (child.getTop() <= bottom) {
+                        break;
+                    } else {
+                        start = i;
+                        count++;
+                        int position = firstPosition + i;
+                        if (position >= headerViewsCount && position < footerViewsStart) {
+                            mRecycler.addScrapView(child);
+
+                            if (ViewDebug.TRACE_RECYCLER) {
+                                ViewDebug.trace(child,
+                                        ViewDebug.RecyclerTraceType.MOVE_TO_SCRAP_HEAP,
+                                        firstPosition + i, -1);
+                            }
+                        }
+                    }
+                }
+            }
+
+            mMotionViewNewTop = mMotionViewOriginalTop + deltaY;
+
+            mBlockLayoutRequests = true;
+            detachViewsFromParent(start, count);
+            offsetChildrenTopAndBottom(incrementalDeltaY);
+
+            if (down) {
+                mFirstPosition += count;
+            }
+
+            invalidate();
+            fillGap(down);
+            mBlockLayoutRequests = false;
+
+            invokeOnItemScrollListener();
+        }
+    }
+
+    /**
+     * Returns the number of header views in the list. Header views are special views
+     * at the top of the list that should not be recycled during a layout.
+     *
+     * @return The number of header views, 0 in the default implementation.
+     */
+    int getHeaderViewsCount() {
+        return 0;
+    }
+
+    /**
+     * Returns the number of footer views in the list. Footer views are special views
+     * at the bottom of the list that should not be recycled during a layout.
+     *
+     * @return The number of footer views, 0 in the default implementation.
+     */
+    int getFooterViewsCount() {
+        return 0;
+    }
+
+    /**
+     * Fills the gap left open by a touch-scroll. During a touch scroll, children that
+     * remain on screen are shifted and the other ones are discarded. The role of this
+     * method is to fill the gap thus created by performing a partial layout in the
+     * empty space.
+     *
+     * @param down true if the scroll is going down, false if it is going up
+     */
+    abstract void fillGap(boolean down);
+
+    void hideSelector() {
+        if (mSelectedPosition != INVALID_POSITION) {
+            mResurrectToPosition = mSelectedPosition;
+            if (mNextSelectedPosition >= 0 && mNextSelectedPosition != mSelectedPosition) {
+                mResurrectToPosition = mNextSelectedPosition;
+            }
+            setSelectedPositionInt(INVALID_POSITION);
+            setNextSelectedPositionInt(INVALID_POSITION);
+            mSelectedTop = 0;
+            mSelectorRect.setEmpty();
+        }
+    }
+
+    /**
+     * @return A position to select. First we try mSelectedPosition. If that has been clobbered by
+     * entering touch mode, we then try mResurrectToPosition. Values are pinned to the range
+     * of items available in the adapter
+     */
+    int reconcileSelectedPosition() {
+        int position = mSelectedPosition;
+        if (position < 0) {
+            position = mResurrectToPosition;
+        }
+        position = Math.max(0, position);
+        position = Math.min(position, mItemCount - 1);
+        return position;
+    }
+
+    /**
+     * Find the row closest to y. This row will be used as the motion row when scrolling
+     *
+     * @param y Where the user touched
+     * @return The position of the first (or only) item in the row closest to y
+     */
+    abstract int findMotionRow(int y);
+
+    /**
+     * Causes all the views to be rebuilt and redrawn.
+     */
+    public void invalidateViews() {
+        mDataChanged = true;
+        rememberSyncState();
+        requestLayout();
+        invalidate();
+    }
+
+    /**
+     * Makes the item at the supplied position selected.
+     *
+     * @param position the position of the new selection
+     */
+    abstract void setSelectionInt(int position);
+
+    /**
+     * Attempt to bring the selection back if the user is switching from touch
+     * to trackball mode
+     * @return Whether selection was set to something.
+     */
+    boolean resurrectSelection() {
+        final int childCount = getChildCount();
+
+        if (childCount <= 0) {
+            return false;
+        }
+
+        int selectedTop = 0;
+        int selectedPos;
+        int childrenTop = mListPadding.top;
+        int childrenBottom = mBottom - mTop - mListPadding.bottom;
+        final int firstPosition = mFirstPosition;
+        final int toPosition = mResurrectToPosition;
+        boolean down = true;
+
+        if (toPosition >= firstPosition && toPosition < firstPosition + childCount) {
+            selectedPos = toPosition;
+
+            final View selected = getChildAt(selectedPos - mFirstPosition);
+            selectedTop = selected.getTop();
+            int selectedBottom = selected.getBottom();
+
+            // We are scrolled, don't get in the fade
+            if (selectedTop < childrenTop) {
+                selectedTop = childrenTop + getVerticalFadingEdgeLength();
+            } else if (selectedBottom > childrenBottom) {
+                selectedTop = childrenBottom - selected.getMeasuredHeight()
+                        - getVerticalFadingEdgeLength();
+            }
+        } else {
+            if (toPosition < firstPosition) {
+                // Default to selecting whatever is first
+                selectedPos = firstPosition;
+                for (int i = 0; i < childCount; i++) {
+                    final View v = getChildAt(i);
+                    final int top = v.getTop();
+
+                    if (i == 0) {
+                        // Remember the position of the first item
+                        selectedTop = top;
+                        // See if we are scrolled at all
+                        if (firstPosition > 0 || top < childrenTop) {
+                            // If we are scrolled, don't select anything that is
+                            // in the fade region
+                            childrenTop += getVerticalFadingEdgeLength();
+                        }
+                    }
+                    if (top >= childrenTop) {
+                        // Found a view whose top is fully visisble
+                        selectedPos = firstPosition + i;
+                        selectedTop = top;
+                        break;
+                    }
+                }
+            } else {
+                final int itemCount = mItemCount;
+                down = false;
+                selectedPos = firstPosition + childCount - 1;
+
+                for (int i = childCount - 1; i >= 0; i--) {
+                    final View v = getChildAt(i);
+                    final int top = v.getTop();
+                    final int bottom = v.getBottom();
+
+                    if (i == childCount - 1) {
+                        selectedTop = top;
+                        if (firstPosition + childCount < itemCount || bottom > childrenBottom) {
+                            childrenBottom -= getVerticalFadingEdgeLength();
+                        }
+                    }
+
+                    if (bottom <= childrenBottom) {
+                        selectedPos = firstPosition + i;
+                        selectedTop = top;
+                        break;
+                    }
+                }
+            }
+        }
+
+        mResurrectToPosition = INVALID_POSITION;
+        removeCallbacks(mFlingRunnable);
+        mTouchMode = TOUCH_MODE_REST;
+        clearScrollingCache();
+        mSpecificTop = selectedTop;
+        selectedPos = lookForSelectablePosition(selectedPos, down);
+        if (selectedPos >= firstPosition && selectedPos <= getLastVisiblePosition()) {
+            mLayoutMode = LAYOUT_SPECIFIC;
+            setSelectionInt(selectedPos);
+            invokeOnItemScrollListener();
+        } else {
+            selectedPos = INVALID_POSITION;
+        }
+        reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
+
+        return selectedPos >= 0;
+    }
+
+    @Override
+    protected void handleDataChanged() {
+        int count = mItemCount;
+        if (count > 0) {
+
+            int newPos;
+
+            int selectablePos;
+
+            // Find the row we are supposed to sync to
+            if (mNeedSync) {
+                // Update this first, since setNextSelectedPositionInt inspects it
+                mNeedSync = false;
+
+                if (mTranscriptMode == TRANSCRIPT_MODE_ALWAYS_SCROLL ||
+                        (mTranscriptMode == TRANSCRIPT_MODE_NORMAL &&
+                                mFirstPosition + getChildCount() >= mOldItemCount)) {
+                    mLayoutMode = LAYOUT_FORCE_BOTTOM;
+                    return;
+                }
+
+                switch (mSyncMode) {
+                case SYNC_SELECTED_POSITION:
+                    if (isInTouchMode()) {
+                        // We saved our state when not in touch mode. (We know this because
+                        // mSyncMode is SYNC_SELECTED_POSITION.) Now we are trying to
+                        // restore in touch mode. Just leave mSyncPosition as it is (possibly
+                        // adjusting if the available range changed) and return.
+                        mLayoutMode = LAYOUT_SYNC;
+                        mSyncPosition = Math.min(Math.max(0, mSyncPosition), count - 1);
+
+                        return;
+                    } else {
+                        // See if we can find a position in the new data with the same
+                        // id as the old selection. This will change mSyncPosition.
+                        newPos = findSyncPosition();
+                        if (newPos >= 0) {
+                            // Found it. Now verify that new selection is still selectable
+                            selectablePos = lookForSelectablePosition(newPos, true);
+                            if (selectablePos == newPos) {
+                                // Same row id is selected
+                                mSyncPosition = newPos;
+
+                                if (mSyncHeight == getHeight()) {
+                                    // If we are at the same height as when we saved state, try
+                                    // to restore the scroll position too.
+                                    mLayoutMode = LAYOUT_SYNC;
+                                } else {
+                                    // We are not the same height as when the selection was saved, so
+                                    // don't try to restore the exact position
+                                    mLayoutMode = LAYOUT_SET_SELECTION;
+                                }
+
+                                // Restore selection
+                                setNextSelectedPositionInt(newPos);
+                                return;
+                            }
+                        }
+                    }
+                    break;
+                case SYNC_FIRST_POSITION:
+                    // Leave mSyncPosition as it is -- just pin to available range
+                    mLayoutMode = LAYOUT_SYNC;
+                    mSyncPosition = Math.min(Math.max(0, mSyncPosition), count - 1);
+
+                    return;
+                }
+            }
+
+            if (!isInTouchMode()) {
+                // We couldn't find matching data -- try to use the same position
+                newPos = getSelectedItemPosition();
+
+                // Pin position to the available range
+                if (newPos >= count) {
+                    newPos = count - 1;
+                }
+                if (newPos < 0) {
+                    newPos = 0;
+                }
+
+                // Make sure we select something selectable -- first look down
+                selectablePos = lookForSelectablePosition(newPos, true);
+
+                if (selectablePos >= 0) {
+                    setNextSelectedPositionInt(selectablePos);
+                    return;
+                } else {
+                    // Looking down didn't work -- try looking up
+                    selectablePos = lookForSelectablePosition(newPos, false);
+                    if (selectablePos >= 0) {
+                        setNextSelectedPositionInt(selectablePos);
+                        return;
+                    }
+                }
+            } else {
+
+                // We already know where we want to resurrect the selection
+                if (mResurrectToPosition >= 0) {
+                    return;
+                }
+            }
+
+        }
+
+        // Nothing is selected. Give up and reset everything.
+        mLayoutMode = mStackFromBottom ? LAYOUT_FORCE_BOTTOM : LAYOUT_FORCE_TOP;
+        mSelectedPosition = INVALID_POSITION;
+        mSelectedRowId = INVALID_ROW_ID;
+        mNextSelectedPosition = INVALID_POSITION;
+        mNextSelectedRowId = INVALID_ROW_ID;
+        mNeedSync = false;
+        checkSelectionChanged();
+    }
+
+    /**
+     * Removes the filter window
+     */
+    void dismissPopup() {
+        if (mPopup != null) {
+            mPopup.dismiss();
+        }
+    }
+
+    /**
+     * Shows the filter window
+     */
+    private void showPopup() {
+        // Make sure we have a window before showing the popup
+        if (getWindowVisibility() == View.VISIBLE) {
+            positionPopup(false);
+            // Make sure we get focus if we are showing the popup
+            checkFocus();
+        }
+    }
+
+    private void positionPopup(boolean update) {
+        int screenHeight = getResources().getDisplayMetrics().heightPixels;
+        final int[] xy = new int[2];
+        getLocationOnScreen(xy);
+        // TODO: The 20 below should come from the theme and be expressed in dip
+        // TODO: And the gravity should be defined in the theme as well
+        final int bottomGap = screenHeight - xy[1] - getHeight() + (int) (mDensityScale * 20);
+        if (!update) {
+            mPopup.showAtLocation(this, Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL,
+                    xy[0], bottomGap);
+        } else {
+            mPopup.update(xy[0], bottomGap, -1, -1);
+        }
+    }
+
+    /**
+     * What is the distance between the source and destination rectangles given the direction of
+     * focus navigation between them? The direction basically helps figure out more quickly what is
+     * self evident by the relationship between the rects...
+     *
+     * @param source the source rectangle
+     * @param dest the destination rectangle
+     * @param direction the direction
+     * @return the distance between the rectangles
+     */
+    static int getDistance(Rect source, Rect dest, int direction) {
+        int sX, sY; // source x, y
+        int dX, dY; // dest x, y
+        switch (direction) {
+        case View.FOCUS_RIGHT:
+            sX = source.right;
+            sY = source.top + source.height() / 2;
+            dX = dest.left;
+            dY = dest.top + dest.height() / 2;
+            break;
+        case View.FOCUS_DOWN:
+            sX = source.left + source.width() / 2;
+            sY = source.bottom;
+            dX = dest.left + dest.width() / 2;
+            dY = dest.top;
+            break;
+        case View.FOCUS_LEFT:
+            sX = source.left;
+            sY = source.top + source.height() / 2;
+            dX = dest.right;
+            dY = dest.top + dest.height() / 2;
+            break;
+        case View.FOCUS_UP:
+            sX = source.left + source.width() / 2;
+            sY = source.top;
+            dX = dest.left + dest.width() / 2;
+            dY = dest.bottom;
+            break;
+        default:
+            throw new IllegalArgumentException("direction must be one of "
+                    + "{FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, FOCUS_RIGHT}.");
+        }
+        int deltaX = dX - sX;
+        int deltaY = dY - sY;
+        return deltaY * deltaY + deltaX * deltaX;
+    }
+
+    @Override
+    protected boolean isInFilterMode() {
+        return mFiltered;
+    }
+
+    /**
+     * Sends a key to the text filter window
+     *
+     * @param keyCode The keycode for the event
+     * @param event The actual key event
+     *
+     * @return True if the text filter handled the event, false otherwise.
+     */
+    boolean sendToTextFilter(int keyCode, int count, KeyEvent event) {
+        if (!mTextFilterEnabled || !(getAdapter() instanceof Filterable) ||
+                ((Filterable) getAdapter()).getFilter() == null) {
+            return false;
+        }
+
+        boolean handled = false;
+        boolean okToSend = true;
+        switch (keyCode) {
+        case KeyEvent.KEYCODE_DPAD_UP:
+        case KeyEvent.KEYCODE_DPAD_DOWN:
+        case KeyEvent.KEYCODE_DPAD_LEFT:
+        case KeyEvent.KEYCODE_DPAD_RIGHT:
+        case KeyEvent.KEYCODE_DPAD_CENTER:
+        case KeyEvent.KEYCODE_ENTER:
+            okToSend = false;
+            break;
+        case KeyEvent.KEYCODE_BACK:
+            if (mFiltered && mPopup != null && mPopup.isShowing() &&
+                    event.getAction() == KeyEvent.ACTION_DOWN) {
+                handled = true;
+                mTextFilter.setText("");
+            }
+            okToSend = false;
+            break;
+        case KeyEvent.KEYCODE_SPACE:
+            // Only send spaces once we are filtered
+            okToSend = mFiltered = true;
+            break;
+        }
+
+        if (okToSend && acceptFilter()) {
+            createTextFilter(true);
+
+            KeyEvent forwardEvent = event;
+            if (forwardEvent.getRepeatCount() > 0) {
+                forwardEvent = new KeyEvent(event, event.getEventTime(), 0);
+            }
+
+            int action = event.getAction();
+            switch (action) {
+                case KeyEvent.ACTION_DOWN:
+                    handled = mTextFilter.onKeyDown(keyCode, forwardEvent);
+                    break;
+
+                case KeyEvent.ACTION_UP:
+                    handled = mTextFilter.onKeyUp(keyCode, forwardEvent);
+                    break;
+
+                case KeyEvent.ACTION_MULTIPLE:
+                    handled = mTextFilter.onKeyMultiple(keyCode, count, event);
+                    break;
+            }
+        }
+        return handled;
+    }
+
+    /**
+     * Creates the window for the text filter and populates it with an EditText field;
+     *
+     * @param animateEntrance true if the window should appear with an animation
+     */
+    private void createTextFilter(boolean animateEntrance) {
+        if (mPopup == null) {
+            Context c = getContext();
+            PopupWindow p = new PopupWindow(c);
+            LayoutInflater layoutInflater = (LayoutInflater) c
+                    .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+            mTextFilter = (EditText) layoutInflater.inflate(
+                    com.android.internal.R.layout.typing_filter, null);
+            mTextFilter.addTextChangedListener(this);
+            p.setFocusable(false);
+            p.setTouchable(false);
+            p.setInputMethodMode(PopupWindow.INPUT_METHOD_NOT_NEEDED);
+            p.setContentView(mTextFilter);
+            p.setWidth(LayoutParams.WRAP_CONTENT);
+            p.setHeight(LayoutParams.WRAP_CONTENT);
+            p.setBackgroundDrawable(null);
+            mPopup = p;
+            getViewTreeObserver().addOnGlobalLayoutListener(this);
+        }
+        if (animateEntrance) {
+            mPopup.setAnimationStyle(com.android.internal.R.style.Animation_TypingFilter);
+        } else {
+            mPopup.setAnimationStyle(com.android.internal.R.style.Animation_TypingFilterRestore);
+        }
+    }
+
+    /**
+     * Clear the text filter.
+     */
+    public void clearTextFilter() {
+        if (mFiltered) {
+            mTextFilter.setText("");
+            mFiltered = false;
+            if (mPopup != null && mPopup.isShowing()) {
+                dismissPopup();
+            }
+        }
+    }
+
+    /**
+     * Returns if the ListView currently has a text filter.
+     */
+    public boolean hasTextFilter() {
+        return mFiltered;
+    }
+
+    public void onGlobalLayout() {
+        if (isShown()) {
+            // Show the popup if we are filtered
+            if (mFiltered && mPopup != null && !mPopup.isShowing()) {
+                showPopup();
+            }
+        } else {
+            // Hide the popup when we are no longer visible
+            if (mPopup.isShowing()) {
+                dismissPopup();
+            }
+        }
+
+    }
+
+    /**
+     * For our text watcher that associated with the text filter
+     */
+    public void beforeTextChanged(CharSequence s, int start, int count, int after) {
+    }
+
+    /**
+     * For our text watcher that associated with the text filter. Performs the actual
+     * filtering as the text changes.
+     */
+    public void onTextChanged(CharSequence s, int start, int before, int count) {
+        if (mPopup != null) {
+            int length = s.length();
+            boolean showing = mPopup.isShowing();
+            if (!showing && length > 0) {
+                // Show the filter popup if necessary
+                showPopup();
+                mFiltered = true;
+            } else if (showing && length == 0) {
+                // Remove the filter popup if the user has cleared all text
+                mPopup.dismiss();
+                mFiltered = false;
+            }
+            if (mAdapter instanceof Filterable) {
+                Filter f = ((Filterable) mAdapter).getFilter();
+                // Filter should not be null when we reach this part
+                if (f != null) {
+                    f.filter(s, this);
+                } else {
+                    throw new IllegalStateException("You cannot call onTextChanged with a non "
+                            + "filterable adapter");
+                }
+            }
+        }
+    }
+
+    /**
+     * For our text watcher that associated with the text filter
+     */
+    public void afterTextChanged(Editable s) {
+    }
+
+    public void onFilterComplete(int count) {
+        if (mSelectedPosition < 0 && count > 0) {
+            mResurrectToPosition = INVALID_POSITION;
+            resurrectSelection();
+        }
+    }
+
+    @Override
+    protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
+        return new LayoutParams(p);
+    }
+
+    @Override
+    public LayoutParams generateLayoutParams(AttributeSet attrs) {
+        return new AbsListView.LayoutParams(getContext(), attrs);
+    }
+
+    @Override
+    protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
+        return p instanceof AbsListView.LayoutParams;
+    }
+
+    /**
+     * Puts the list or grid into transcript mode. In this mode the list or grid will always scroll
+     * to the bottom to show new items.
+     *
+     * @param mode the transcript mode to set
+     *
+     * @see #TRANSCRIPT_MODE_DISABLED
+     * @see #TRANSCRIPT_MODE_NORMAL
+     * @see #TRANSCRIPT_MODE_ALWAYS_SCROLL
+     */
+    public void setTranscriptMode(int mode) {
+        mTranscriptMode = mode;
+    }
+
+    /**
+     * Returns the current transcript mode.
+     *
+     * @return {@link #TRANSCRIPT_MODE_DISABLED}, {@link #TRANSCRIPT_MODE_NORMAL} or
+     *         {@link #TRANSCRIPT_MODE_ALWAYS_SCROLL}
+     */
+    public int getTranscriptMode() {
+        return mTranscriptMode;
+    }
+
+    @Override
+    public int getSolidColor() {
+        return mCacheColorHint;
+    }
+
+    /**
+     * When set to a non-zero value, the cache color hint indicates that this list is always drawn
+     * on top of a solid, single-color, opaque background
+     *
+     * @param color The background color
+     */
+    public void setCacheColorHint(int color) {
+        mCacheColorHint = color;
+    }
+
+    /**
+     * When set to a non-zero value, the cache color hint indicates that this list is always drawn
+     * on top of a solid, single-color, opaque background
+     *
+     * @return The cache color hint
+     */
+    public int getCacheColorHint() {
+        return mCacheColorHint;
+    }
+
+    /**
+     * Move all views (excluding headers and footers) held by this AbsListView into the supplied
+     * List. This includes views displayed on the screen as well as views stored in AbsListView's
+     * internal view recycler.
+     *
+     * @param views A list into which to put the reclaimed views
+     */
+    public void reclaimViews(List<View> views) {
+        int childCount = getChildCount();
+        RecyclerListener listener = mRecycler.mRecyclerListener;
+
+        // Reclaim views on screen
+        for (int i = 0; i < childCount; i++) {
+            View child = getChildAt(i);
+            AbsListView.LayoutParams lp = (AbsListView.LayoutParams)child.getLayoutParams();
+            // Don't reclaim header or footer views, or views that should be ignored
+            if (lp != null && mRecycler.shouldRecycleViewType(lp.viewType)) {
+                views.add(child);
+                if (listener != null) {
+                    // Pretend they went through the scrap heap
+                    listener.onMovedToScrapHeap(child);
+                }
+            }
+        }
+        mRecycler.reclaimScrapViews(views);
+        removeAllViewsInLayout();
+    }
+
+    /**
+     * Sets the recycler listener to be notified whenever a View is set aside in
+     * the recycler for later reuse. This listener can be used to free resources
+     * associated to the View.
+     *
+     * @param listener The recycler listener to be notified of views set aside
+     *        in the recycler.
+     *
+     * @see android.widget.AbsListView.RecycleBin
+     * @see android.widget.AbsListView.RecyclerListener
+     */
+    public void setRecyclerListener(RecyclerListener listener) {
+        mRecycler.mRecyclerListener = listener;
+    }
+
+    /**
+     * AbsListView extends LayoutParams to provide a place to hold the view type.
+     */
+    public static class LayoutParams extends ViewGroup.LayoutParams {
+        /**
+         * View type for this view, as returned by
+         * {@link android.widget.Adapter#getItemViewType(int) }
+         */
+        int viewType;
+
+        public LayoutParams(Context c, AttributeSet attrs) {
+            super(c, attrs);
+        }
+
+        public LayoutParams(int w, int h) {
+            super(w, h);
+        }
+
+        public LayoutParams(int w, int h, int viewType) {
+            super(w, h);
+            this.viewType = viewType;
+        }
+
+        public LayoutParams(ViewGroup.LayoutParams source) {
+            super(source);
+        }
+    }
+
+    /**
+     * A RecyclerListener is used to receive a notification whenever a View is placed
+     * inside the RecycleBin's scrap heap. This listener is used to free resources
+     * associated to Views placed in the RecycleBin.
+     *
+     * @see android.widget.AbsListView.RecycleBin
+     * @see android.widget.AbsListView#setRecyclerListener(android.widget.AbsListView.RecyclerListener)
+     */
+    public static interface RecyclerListener {
+        /**
+         * Indicates that the specified View was moved into the recycler's scrap heap.
+         * The view is not displayed on screen any more and any expensive resource
+         * associated with the view should be discarded.
+         *
+         * @param view
+         */
+        void onMovedToScrapHeap(View view);
+    }
+
+    /**
+     * The RecycleBin facilitates reuse of views across layouts. The RecycleBin has two levels of
+     * storage: ActiveViews and ScrapViews. ActiveViews are those views which were onscreen at the
+     * start of a layout. By construction, they are displaying current information. At the end of
+     * layout, all views in ActiveViews are demoted to ScrapViews. ScrapViews are old views that
+     * could potentially be used by the adapter to avoid allocating views unnecessarily.
+     *
+     * @see android.widget.AbsListView#setRecyclerListener(android.widget.AbsListView.RecyclerListener)
+     * @see android.widget.AbsListView.RecyclerListener
+     */
+    class RecycleBin {
+        private RecyclerListener mRecyclerListener;
+
+        /**
+         * The position of the first view stored in mActiveViews.
+         */
+        private int mFirstActivePosition;
+
+        /**
+         * Views that were on screen at the start of layout. This array is populated at the start of
+         * layout, and at the end of layout all view in mActiveViews are moved to mScrapViews.
+         * Views in mActiveViews represent a contiguous range of Views, with position of the first
+         * view store in mFirstActivePosition.
+         */
+        private View[] mActiveViews = new View[0];
+
+        /**
+         * Unsorted views that can be used by the adapter as a convert view.
+         */
+        private ArrayList<View>[] mScrapViews;
+
+        private int mViewTypeCount;
+
+        private ArrayList<View> mCurrentScrap;
+
+        public void setViewTypeCount(int viewTypeCount) {
+            if (viewTypeCount < 1) {
+                throw new IllegalArgumentException("Can't have a viewTypeCount < 1");
+            }
+            //noinspection unchecked
+            ArrayList<View>[] scrapViews = new ArrayList[viewTypeCount];
+            for (int i = 0; i < viewTypeCount; i++) {
+                scrapViews[i] = new ArrayList<View>();
+            }
+            mViewTypeCount = viewTypeCount;
+            mCurrentScrap = scrapViews[0];
+            mScrapViews = scrapViews;
+        }
+        
+        public boolean shouldRecycleViewType(int viewType) {
+            return viewType >= 0;
+        }
+
+        /**
+         * Clears the scrap heap.
+         */
+        void clear() {
+            if (mViewTypeCount == 1) {
+                final ArrayList<View> scrap = mCurrentScrap;
+                final int scrapCount = scrap.size();
+                for (int i = 0; i < scrapCount; i++) {
+                    removeDetachedView(scrap.remove(scrapCount - 1 - i), false);
+                }
+            } else {
+                final int typeCount = mViewTypeCount;
+                for (int i = 0; i < typeCount; i++) {
+                    final ArrayList<View> scrap = mScrapViews[i];
+                    final int scrapCount = scrap.size();
+                    for (int j = 0; j < scrapCount; j++) {
+                        removeDetachedView(scrap.remove(scrapCount - 1 - j), false);
+                    }
+                }
+            }
+        }
+
+        /**
+         * Fill ActiveViews with all of the children of the AbsListView.
+         *
+         * @param childCount The minimum number of views mActiveViews should hold
+         * @param firstActivePosition The position of the first view that will be stored in
+         *        mActiveViews
+         */
+        void fillActiveViews(int childCount, int firstActivePosition) {
+            if (mActiveViews.length < childCount) {
+                mActiveViews = new View[childCount];
+            }
+            mFirstActivePosition = firstActivePosition;
+
+            final View[] activeViews = mActiveViews;
+            for (int i = 0; i < childCount; i++) {
+                View child = getChildAt(i);
+                AbsListView.LayoutParams lp = (AbsListView.LayoutParams)child.getLayoutParams();
+                // Don't put header or footer views into the scrap heap
+                if (lp != null && lp.viewType != AdapterView.ITEM_VIEW_TYPE_HEADER_OR_FOOTER) {
+                    // Note:  We do place AdapterView.ITEM_VIEW_TYPE_IGNORE in active views.
+                    //        However, we will NOT place them into scrap views.
+                    activeViews[i] = getChildAt(i);
+                }
+            }
+        }
+
+        /**
+         * Get the view corresponding to the specified position. The view will be removed from
+         * mActiveViews if it is found.
+         *
+         * @param position The position to look up in mActiveViews
+         * @return The view if it is found, null otherwise
+         */
+        View getActiveView(int position) {
+            int index = position - mFirstActivePosition;
+            final View[] activeViews = mActiveViews;
+            if (index >=0 && index < activeViews.length) {
+                final View match = activeViews[index];
+                activeViews[index] = null;
+                return match;
+            }
+            return null;
+        }
+
+        /**
+         * @return A view from the ScrapViews collection. These are unordered.
+         */
+        View getScrapView(int position) {
+            ArrayList<View> scrapViews;
+            if (mViewTypeCount == 1) {
+                scrapViews = mCurrentScrap;
+                int size = scrapViews.size();
+                if (size > 0) {
+                    return scrapViews.remove(size - 1);
+                } else {
+                    return null;
+                }
+            } else {
+                int whichScrap = mAdapter.getItemViewType(position);
+                if (whichScrap >= 0 && whichScrap < mScrapViews.length) {
+                    scrapViews = mScrapViews[whichScrap];
+                    int size = scrapViews.size();
+                    if (size > 0) {
+                        return scrapViews.remove(size - 1);
+                    }
+                }
+            }
+            return null;
+        }
+
+        /**
+         * Put a view into the ScapViews list. These views are unordered.
+         *
+         * @param scrap The view to add
+         */
+        void addScrapView(View scrap) {
+            AbsListView.LayoutParams lp = (AbsListView.LayoutParams) scrap.getLayoutParams();
+            if (lp == null) {
+                return;
+            }
+
+            // Don't put header or footer views or views that should be ignored
+            // into the scrap heap
+            int viewType = lp.viewType;
+            if (!shouldRecycleViewType(viewType)) {
+                return;
+            }
+
+            if (mViewTypeCount == 1) {
+                mCurrentScrap.add(scrap);
+            } else {
+                mScrapViews[viewType].add(scrap);
+            }
+
+            if (mRecyclerListener != null) {
+                mRecyclerListener.onMovedToScrapHeap(scrap);
+            }
+        }
+
+        /**
+         * Move all views remaining in mActiveViews to mScrapViews.
+         */
+        void scrapActiveViews() {
+            final View[] activeViews = mActiveViews;
+            final boolean hasListener = mRecyclerListener != null;
+            final boolean multipleScraps = mViewTypeCount > 1;
+
+            ArrayList<View> scrapViews = mCurrentScrap;
+            final int count = activeViews.length;
+            for (int i = 0; i < count; ++i) {
+                final View victim = activeViews[i];
+                if (victim != null) {
+                    int whichScrap = ((AbsListView.LayoutParams)
+                            victim.getLayoutParams()).viewType;
+
+                    activeViews[i] = null;
+
+                    if (whichScrap == AdapterView.ITEM_VIEW_TYPE_IGNORE) {
+                        // Do not move views that should be ignored
+                        continue;
+                    }
+
+                    if (multipleScraps) {
+                        scrapViews = mScrapViews[whichScrap];
+                    }
+                    scrapViews.add(victim);
+
+                    if (hasListener) {
+                        mRecyclerListener.onMovedToScrapHeap(victim);
+                    }
+
+                    if (ViewDebug.TRACE_RECYCLER) {
+                        ViewDebug.trace(victim,
+                                ViewDebug.RecyclerTraceType.MOVE_FROM_ACTIVE_TO_SCRAP_HEAP,
+                                mFirstActivePosition + i, -1);
+                    }
+                }
+            }
+
+            pruneScrapViews();
+        }
+
+        /**
+         * Makes sure that the size of mScrapViews does not exceed the size of mActiveViews.
+         * (This can happen if an adapter does not recycle its views).
+         */
+        private void pruneScrapViews() {
+            final int maxViews = mActiveViews.length;
+            final int viewTypeCount = mViewTypeCount;
+            final ArrayList<View>[] scrapViews = mScrapViews;
+            for (int i = 0; i < viewTypeCount; ++i) {
+                final ArrayList<View> scrapPile = scrapViews[i];
+                int size = scrapPile.size();
+                final int extras = size - maxViews;
+                size--;
+                for (int j = 0; j < extras; j++) {
+                    removeDetachedView(scrapPile.remove(size--), false);
+                }
+            }
+        }
+
+        /**
+         * Puts all views in the scrap heap into the supplied list.
+         */
+        void reclaimScrapViews(List<View> views) {
+            if (mViewTypeCount == 1) {
+                views.addAll(mCurrentScrap);
+            } else {
+                final int viewTypeCount = mViewTypeCount;
+                final ArrayList<View>[] scrapViews = mScrapViews;
+                for (int i = 0; i < viewTypeCount; ++i) {
+                    final ArrayList<View> scrapPile = scrapViews[i];
+                    views.addAll(scrapPile);
+                }
+            }
+        }
+    }
+}
diff --git a/core/java/android/widget/AbsSeekBar.java b/core/java/android/widget/AbsSeekBar.java
new file mode 100644
index 0000000..1d553f1
--- /dev/null
+++ b/core/java/android/widget/AbsSeekBar.java
@@ -0,0 +1,375 @@
+/*
+ * Copyright (C) 2007 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 android.widget;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Canvas;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.util.AttributeSet;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+
+public abstract class AbsSeekBar extends ProgressBar {
+
+    private Drawable mThumb;
+    private int mThumbOffset;
+    
+    /**
+     * On touch, this offset plus the scaled value from the position of the
+     * touch will form the progress value. Usually 0.
+     */
+    float mTouchProgressOffset;
+
+    /**
+     * Whether this is user seekable.
+     */
+    boolean mIsUserSeekable = true;
+
+    /**
+     * On key presses (right or left), the amount to increment/decrement the
+     * progress.
+     */
+    private int mKeyProgressIncrement = 1;
+    
+    private static final int NO_ALPHA = 0xFF;
+    private float mDisabledAlpha;
+    
+    public AbsSeekBar(Context context) {
+        super(context);
+    }
+
+    public AbsSeekBar(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    public AbsSeekBar(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+
+        TypedArray a = context.obtainStyledAttributes(attrs,
+                com.android.internal.R.styleable.SeekBar, defStyle, 0);
+        Drawable thumb = a.getDrawable(com.android.internal.R.styleable.SeekBar_thumb);
+        setThumb(thumb);
+        int thumbOffset =
+                a.getDimensionPixelOffset(com.android.internal.R.styleable.SeekBar_thumbOffset, 0);
+        setThumbOffset(thumbOffset);
+        a.recycle();
+
+        a = context.obtainStyledAttributes(attrs,
+                com.android.internal.R.styleable.Theme, 0, 0);
+        mDisabledAlpha = a.getFloat(com.android.internal.R.styleable.Theme_disabledAlpha, 0.5f);
+        a.recycle();
+    }
+
+    /**
+     * Sets the thumb that will be drawn at the end of the progress meter within the SeekBar
+     * 
+     * @param thumb Drawable representing the thumb
+     */
+    public void setThumb(Drawable thumb) {
+        if (thumb != null) {
+            thumb.setCallback(this);
+        }
+        mThumb = thumb;
+        invalidate();
+    }
+
+    /**
+     * @see #setThumbOffset(int)
+     */
+    public int getThumbOffset() {
+        return mThumbOffset;
+    }
+
+    /**
+     * Sets the thumb offset that allows the thumb to extend out of the range of
+     * the track.
+     * 
+     * @param thumbOffset The offset amount in pixels.
+     */
+    public void setThumbOffset(int thumbOffset) {
+        mThumbOffset = thumbOffset;
+        invalidate();
+    }
+
+    /**
+     * Sets the amount of progress changed via the arrow keys.
+     * 
+     * @param increment The amount to increment or decrement when the user
+     *            presses the arrow keys.
+     */
+    public void setKeyProgressIncrement(int increment) {
+        mKeyProgressIncrement = increment < 0 ? -increment : increment;
+    }
+
+    /**
+     * Returns the amount of progress changed via the arrow keys.
+     * <p>
+     * By default, this will be a value that is derived from the max progress.
+     * 
+     * @return The amount to increment or decrement when the user presses the
+     *         arrow keys. This will be positive.
+     */
+    public int getKeyProgressIncrement() {
+        return mKeyProgressIncrement;
+    }
+    
+    @Override
+    public synchronized void setMax(int max) {
+        super.setMax(max);
+
+        if ((mKeyProgressIncrement == 0) || (getMax() / mKeyProgressIncrement > 20)) {
+            // It will take the user too long to change this via keys, change it
+            // to something more reasonable
+            setKeyProgressIncrement(Math.max(1, Math.round((float) getMax() / 20)));
+        }
+    }
+
+    @Override
+    protected boolean verifyDrawable(Drawable who) {
+        return who == mThumb || super.verifyDrawable(who);
+    }
+
+    @Override
+    protected void drawableStateChanged() {
+        super.drawableStateChanged();
+        
+        Drawable progressDrawable = getProgressDrawable();
+        if (progressDrawable != null) {
+            progressDrawable.setAlpha(isEnabled() ? NO_ALPHA : (int) (NO_ALPHA * mDisabledAlpha));
+        }
+        
+        if (mThumb != null && mThumb.isStateful()) {
+            int[] state = getDrawableState();
+            mThumb.setState(state);
+        }
+    }
+    
+    @Override
+    void onProgressRefresh(float scale, boolean fromUser) { 
+        Drawable thumb = mThumb;
+        if (thumb != null) {
+            setThumbPos(getWidth(), getHeight(), thumb, scale, Integer.MIN_VALUE);
+            /*
+             * Since we draw translated, the drawable's bounds that it signals
+             * for invalidation won't be the actual bounds we want invalidated,
+             * so just invalidate this whole view.
+             */
+            invalidate();
+        }
+    }
+    
+    
+    @Override
+    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
+        Drawable d = getCurrentDrawable();
+        Drawable thumb = mThumb;
+        int thumbHeight = thumb == null ? 0 : thumb.getIntrinsicHeight();
+        // The max height does not incorporate padding, whereas the height
+        // parameter does
+        int trackHeight = Math.min(mMaxHeight, h - mPaddingTop - mPaddingBottom);
+        
+        int max = getMax();
+        float scale = max > 0 ? (float) getProgress() / (float) max : 0;
+        
+        if (thumbHeight > trackHeight) {
+            if (thumb != null) {
+                setThumbPos(w, h, thumb, scale, 0);
+            }
+            int gapForCenteringTrack = (thumbHeight - trackHeight) / 2;
+            if (d != null) {
+                // Canvas will be translated by the padding, so 0,0 is where we start drawing
+                d.setBounds(0, gapForCenteringTrack, 
+                        w - mPaddingRight - mPaddingLeft, h - mPaddingBottom - gapForCenteringTrack
+                        - mPaddingTop);
+            }
+        } else {
+            if (d != null) {
+                // Canvas will be translated by the padding, so 0,0 is where we start drawing
+                d.setBounds(0, 0, w - mPaddingRight - mPaddingLeft, h - mPaddingBottom
+                        - mPaddingTop);
+            }
+            int gap = (trackHeight - thumbHeight) / 2;
+            if (thumb != null) {
+                setThumbPos(w, h, thumb, scale, gap);
+            }
+        }
+    }
+
+    /**
+     * @param gap If set to {@link Integer#MIN_VALUE}, this will be ignored and
+     *            the old vertical bounds will be used.
+     */
+    private void setThumbPos(int w, int h, Drawable thumb, float scale, int gap) {
+        int available = w - mPaddingLeft - mPaddingRight;
+        int thumbWidth = thumb.getIntrinsicWidth();
+        int thumbHeight = thumb.getIntrinsicHeight();
+        available -= thumbWidth;
+
+        // The extra space for the thumb to move on the track
+        available += mThumbOffset * 2;
+
+        int thumbPos = (int) (scale * available);
+
+        int topBound, bottomBound;
+        if (gap == Integer.MIN_VALUE) {
+            Rect oldBounds = thumb.getBounds();
+            topBound = oldBounds.top;
+            bottomBound = oldBounds.bottom;
+        } else {
+            topBound = gap;
+            bottomBound = gap + thumbHeight;
+        }
+        
+        // Canvas will be translated, so 0,0 is where we start drawing
+        thumb.setBounds(thumbPos, topBound, thumbPos + thumbWidth, bottomBound);
+    }
+    
+    @Override
+    protected synchronized void onDraw(Canvas canvas) {
+        super.onDraw(canvas);
+        if (mThumb != null) {
+            canvas.save();
+            // Translate the padding. For the x, we need to allow the thumb to
+            // draw in its extra space
+            canvas.translate(mPaddingLeft - mThumbOffset, mPaddingTop);
+            mThumb.draw(canvas);
+            canvas.restore();
+        }
+    }
+
+    @Override
+    protected synchronized void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        Drawable d = getCurrentDrawable();
+
+        int thumbHeight = mThumb == null ? 0 : mThumb.getIntrinsicHeight();
+        int dw = 0;
+        int dh = 0;
+        if (d != null) {
+            dw = Math.max(mMinWidth, Math.min(mMaxWidth, d.getIntrinsicWidth()));
+            dh = Math.max(mMinHeight, Math.min(mMaxHeight, d.getIntrinsicHeight()));
+            dh = Math.max(thumbHeight, dh);
+        }
+        dw += mPaddingLeft + mPaddingRight;
+        dh += mPaddingTop + mPaddingBottom;
+        
+        setMeasuredDimension(resolveSize(dw, widthMeasureSpec),
+                resolveSize(dh, heightMeasureSpec));
+    }
+    
+    @Override
+    public boolean onTouchEvent(MotionEvent event) {
+        if (!mIsUserSeekable || !isEnabled()) {
+            return false;
+        }
+        
+        switch (event.getAction()) {
+            case MotionEvent.ACTION_DOWN:
+                setPressed(true);
+                onStartTrackingTouch();
+                trackTouchEvent(event);
+                break;
+                
+            case MotionEvent.ACTION_MOVE:
+                trackTouchEvent(event);
+                attemptClaimDrag();
+                break;
+                
+            case MotionEvent.ACTION_UP:
+                trackTouchEvent(event);
+                onStopTrackingTouch();
+                setPressed(false);
+                break;
+                
+            case MotionEvent.ACTION_CANCEL:
+                onStopTrackingTouch();
+                setPressed(false);
+                break;
+        }
+        return true;
+    }
+
+    private void trackTouchEvent(MotionEvent event) {
+        final int width = getWidth();
+        final int available = width - mPaddingLeft - mPaddingRight;
+        int x = (int)event.getX();
+        float scale;
+        float progress = 0;
+        if (x < mPaddingLeft) {
+            scale = 0.0f;
+        } else if (x > width - mPaddingRight) {
+            scale = 1.0f;
+        } else {
+            scale = (float)(x - mPaddingLeft) / (float)available;
+            progress = mTouchProgressOffset;
+        }
+        
+        final int max = getMax();
+        progress += scale * max;
+        if (progress < 0) {
+            progress = 0;
+        } else if (progress > max) {
+            progress = max;
+        }
+        
+        setProgress((int) progress, true);
+    }
+
+    /**
+     * Tries to claim the user's drag motion, and requests disallowing any
+     * ancestors from stealing events in the drag.
+     */
+    private void attemptClaimDrag() {
+        if (mParent != null) {
+            mParent.requestDisallowInterceptTouchEvent(true);
+        }
+    }
+    
+    /**
+     * This is called when the user has started touching this widget.
+     */
+    void onStartTrackingTouch() {
+    }
+
+    /**
+     * This is called when the user either releases his touch or the touch is
+     * canceled.
+     */
+    void onStopTrackingTouch() {
+    }
+
+    @Override
+    public boolean onKeyDown(int keyCode, KeyEvent event) {
+        int progress = getProgress();
+        
+        switch (keyCode) {
+            case KeyEvent.KEYCODE_DPAD_LEFT:
+                if (progress <= 0) break;
+                setProgress(progress - mKeyProgressIncrement, true);
+                return true;
+        
+            case KeyEvent.KEYCODE_DPAD_RIGHT:
+                if (progress >= getMax()) break;
+                setProgress(progress + mKeyProgressIncrement, true);
+                return true;
+        }
+
+        return super.onKeyDown(keyCode, event);
+    }
+
+}
diff --git a/core/java/android/widget/AbsSpinner.java b/core/java/android/widget/AbsSpinner.java
new file mode 100644
index 0000000..424a936
--- /dev/null
+++ b/core/java/android/widget/AbsSpinner.java
@@ -0,0 +1,490 @@
+/*
+ * 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 android.widget;
+
+import com.android.internal.R;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.database.DataSetObserver;
+import android.graphics.Rect;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.AttributeSet;
+import android.util.SparseArray;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.animation.Interpolator;
+
+
+/**
+ * An abstract base class for spinner widgets. SDK users will probably not
+ * need to use this class.
+ * 
+ * @attr ref android.R.styleable#AbsSpinner_entries
+ */
+public abstract class AbsSpinner extends AdapterView<SpinnerAdapter> {
+
+    SpinnerAdapter mAdapter;
+
+    int mHeightMeasureSpec;
+    int mWidthMeasureSpec;
+    boolean mBlockLayoutRequests;
+    int mSelectionLeftPadding = 0;
+    int mSelectionTopPadding = 0;
+    int mSelectionRightPadding = 0;
+    int mSelectionBottomPadding = 0;
+    Rect mSpinnerPadding = new Rect();
+    View mSelectedView = null;
+    Interpolator mInterpolator;
+
+    RecycleBin mRecycler = new RecycleBin();
+    private DataSetObserver mDataSetObserver;
+
+
+    /** Temporary frame to hold a child View's frame rectangle */
+    private Rect mTouchFrame;
+
+    public AbsSpinner(Context context) {
+        super(context);
+        initAbsSpinner();
+    }
+
+    public AbsSpinner(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public AbsSpinner(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+        initAbsSpinner();
+
+        TypedArray a = context.obtainStyledAttributes(attrs,
+                com.android.internal.R.styleable.AbsSpinner, defStyle, 0);
+
+        CharSequence[] entries = a.getTextArray(R.styleable.AbsSpinner_entries);
+        if (entries != null) {
+            ArrayAdapter<CharSequence> adapter =
+                    new ArrayAdapter<CharSequence>(context,
+                            R.layout.simple_spinner_item, entries);
+            adapter.setDropDownViewResource(R.layout.simple_spinner_dropdown_item);
+            setAdapter(adapter);
+        }
+
+        a.recycle();
+    }
+
+    /**
+     * Common code for different constructor flavors
+     */
+    private void initAbsSpinner() {
+        setFocusable(true);
+        setWillNotDraw(false);
+    }
+
+
+    /**
+     * The Adapter is used to provide the data which backs this Spinner.
+     * It also provides methods to transform spinner items based on their position
+     * relative to the selected item.
+     * @param adapter The SpinnerAdapter to use for this Spinner
+     */
+    @Override
+    public void setAdapter(SpinnerAdapter adapter) {
+        if (null != mAdapter) {
+            mAdapter.unregisterDataSetObserver(mDataSetObserver);
+            resetList();
+        }
+        
+        mAdapter = adapter;
+        
+        mOldSelectedPosition = INVALID_POSITION;
+        mOldSelectedRowId = INVALID_ROW_ID;
+        
+        if (mAdapter != null) {
+            mOldItemCount = mItemCount;
+            mItemCount = mAdapter.getCount();
+            checkFocus();
+
+            mDataSetObserver = new AdapterDataSetObserver();
+            mAdapter.registerDataSetObserver(mDataSetObserver);
+
+            int position = mItemCount > 0 ? 0 : INVALID_POSITION;
+
+            setSelectedPositionInt(position);
+            setNextSelectedPositionInt(position);
+            
+            if (mItemCount == 0) {
+                // Nothing selected
+                checkSelectionChanged();
+            }
+            
+        } else {
+            checkFocus();            
+            resetList();
+            // Nothing selected
+            checkSelectionChanged();
+        }
+
+        requestLayout();
+    }
+
+    /**
+     * Clear out all children from the list
+     */
+    void resetList() {
+        mDataChanged = false;
+        mNeedSync = false;
+        
+        removeAllViewsInLayout();
+        mOldSelectedPosition = INVALID_POSITION;
+        mOldSelectedRowId = INVALID_ROW_ID;
+        
+        setSelectedPositionInt(INVALID_POSITION);
+        setNextSelectedPositionInt(INVALID_POSITION);
+        invalidate();
+    }
+
+    /** 
+     * @see android.view.View#measure(int, int)
+     * 
+     * Figure out the dimensions of this Spinner. The width comes from
+     * the widthMeasureSpec as Spinnners can't have their width set to
+     * UNSPECIFIED. The height is based on the height of the selected item
+     * plus padding. 
+     */
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
+        int widthSize;
+        int heightSize;
+
+        mSpinnerPadding.left = mPaddingLeft > mSelectionLeftPadding ? mPaddingLeft
+                : mSelectionLeftPadding;
+        mSpinnerPadding.top = mPaddingTop > mSelectionTopPadding ? mPaddingTop
+                : mSelectionTopPadding;
+        mSpinnerPadding.right = mPaddingRight > mSelectionRightPadding ? mPaddingRight
+                : mSelectionRightPadding;
+        mSpinnerPadding.bottom = mPaddingBottom > mSelectionBottomPadding ? mPaddingBottom
+                : mSelectionBottomPadding;
+
+        if (mDataChanged) {
+            handleDataChanged();
+        }
+        
+        int preferredHeight = 0;
+        int preferredWidth = 0;
+        boolean needsMeasuring = true;
+        
+        int selectedPosition = getSelectedItemPosition();
+        if (selectedPosition >= 0 && mAdapter != null) {
+            // Try looking in the recycler. (Maybe we were measured once already)
+            View view = mRecycler.get(selectedPosition);
+            if (view == null) {
+                // Make a new one
+                view = mAdapter.getView(selectedPosition, null, this);
+            }
+
+            if (view != null) {
+                // Put in recycler for re-measuring and/or layout
+                mRecycler.put(selectedPosition, view);
+            }
+
+            if (view != null) {
+                if (view.getLayoutParams() == null) {
+                    mBlockLayoutRequests = true;
+                    view.setLayoutParams(generateDefaultLayoutParams());
+                    mBlockLayoutRequests = false;
+                }
+                measureChild(view, widthMeasureSpec, heightMeasureSpec);
+                
+                preferredHeight = getChildHeight(view) + mSpinnerPadding.top + mSpinnerPadding.bottom;
+                preferredWidth = getChildWidth(view) + mSpinnerPadding.left + mSpinnerPadding.right;
+                
+                needsMeasuring = false;
+            }
+        }
+        
+        if (needsMeasuring) {
+            // No views -- just use padding
+            preferredHeight = mSpinnerPadding.top + mSpinnerPadding.bottom;
+            if (widthMode == MeasureSpec.UNSPECIFIED) {
+                preferredWidth = mSpinnerPadding.left + mSpinnerPadding.right;
+            }
+        }
+
+        preferredHeight = Math.max(preferredHeight, getSuggestedMinimumHeight());
+        preferredWidth = Math.max(preferredWidth, getSuggestedMinimumWidth());
+
+        heightSize = resolveSize(preferredHeight, heightMeasureSpec);
+        widthSize = resolveSize(preferredWidth, widthMeasureSpec);
+
+        setMeasuredDimension(widthSize, heightSize);
+        mHeightMeasureSpec = heightMeasureSpec;
+        mWidthMeasureSpec = widthMeasureSpec;
+    }
+
+    
+    int getChildHeight(View child) {
+        return child.getMeasuredHeight();
+    }
+    
+    int getChildWidth(View child) {
+        return child.getMeasuredWidth();
+    }
+    
+    @Override
+    protected ViewGroup.LayoutParams generateDefaultLayoutParams() {
+        return new ViewGroup.LayoutParams(
+                ViewGroup.LayoutParams.FILL_PARENT,
+                ViewGroup.LayoutParams.WRAP_CONTENT);
+    }
+    
+    void recycleAllViews() {
+        int childCount = getChildCount();
+        final AbsSpinner.RecycleBin recycleBin = mRecycler;
+
+        // All views go in recycler
+        for (int i=0; i<childCount; i++) {
+            View v = getChildAt(i);
+            int index = mFirstPosition + i;
+            recycleBin.put(index, v);
+        }  
+    }
+    
+    @Override
+    void handleDataChanged() {
+        // FIXME -- this is called from both measure and layout.
+        // This is harmless right now, but we don't want to do redundant work if
+        // this gets more complicated
+       super.handleDataChanged();
+    }
+    
+  
+
+    /**
+     * Jump directly to a specific item in the adapter data.
+     */
+    public void setSelection(int position, boolean animate) {
+        // Animate only if requested position is already on screen somewhere
+        boolean shouldAnimate = animate && mFirstPosition <= position &&
+                position <= mFirstPosition + getChildCount() - 1;
+        setSelectionInt(position, shouldAnimate);
+    }
+    
+
+    @Override
+    public void setSelection(int position) {
+        setNextSelectedPositionInt(position);
+        requestLayout();
+        invalidate();
+    }
+    
+
+    /**
+     * Makes the item at the supplied position selected.
+     * 
+     * @param position Position to select
+     * @param animate Should the transition be animated
+     * 
+     */
+    void setSelectionInt(int position, boolean animate) {
+        if (position != mOldSelectedPosition) {
+            mBlockLayoutRequests = true;
+            int delta  = position - mSelectedPosition;
+            setNextSelectedPositionInt(position);
+            layout(delta, animate);
+            mBlockLayoutRequests = false;
+        }
+    }
+
+    abstract void layout(int delta, boolean animate);
+
+    @Override
+    public View getSelectedView() {
+        if (mItemCount > 0 && mSelectedPosition >= 0) {
+            return getChildAt(mSelectedPosition - mFirstPosition);
+        } else {
+            return null;
+        }
+    }
+   
+    /**
+     * Override to prevent spamming ourselves with layout requests
+     * as we place views
+     * 
+     * @see android.view.View#requestLayout()
+     */
+    @Override
+    public void requestLayout() {
+        if (!mBlockLayoutRequests) {
+            super.requestLayout();
+        }
+    }
+
+ 
+
+    @Override
+    public SpinnerAdapter getAdapter() {
+        return mAdapter;
+    }
+
+    @Override
+    public int getCount() {
+        return mItemCount;
+    }
+
+    /**
+     * Maps a point to a position in the list.
+     * 
+     * @param x X in local coordinate
+     * @param y Y in local coordinate
+     * @return The position of the item which contains the specified point, or
+     *         {@link #INVALID_POSITION} if the point does not intersect an item.
+     */
+    public int pointToPosition(int x, int y) {
+        Rect frame = mTouchFrame;
+        if (frame == null) {
+            mTouchFrame = new Rect();
+            frame = mTouchFrame;
+        }
+
+        final int count = getChildCount();
+        for (int i = count - 1; i >= 0; i--) {
+            View child = getChildAt(i);
+            if (child.getVisibility() == View.VISIBLE) {
+                child.getHitRect(frame);
+                if (frame.contains(x, y)) {
+                    return mFirstPosition + i;
+                }
+            }
+        } 
+        return INVALID_POSITION;
+    }
+    
+    static class SavedState extends BaseSavedState {
+        long selectedId;
+        int position;
+
+        /**
+         * Constructor called from {@link AbsSpinner#onSaveInstanceState()}
+         */
+        SavedState(Parcelable superState) {
+            super(superState);
+        }
+        
+        /**
+         * Constructor called from {@link #CREATOR}
+         */
+        private SavedState(Parcel in) {
+            super(in);
+            selectedId = in.readLong();
+            position = in.readInt();
+        }
+
+        @Override
+        public void writeToParcel(Parcel out, int flags) {
+            super.writeToParcel(out, flags);
+            out.writeLong(selectedId);
+            out.writeInt(position);
+        }
+
+        @Override
+        public String toString() {
+            return "AbsSpinner.SavedState{"
+                    + Integer.toHexString(System.identityHashCode(this))
+                    + " selectedId=" + selectedId
+                    + " position=" + position + "}";
+        }
+
+        public static final Parcelable.Creator<SavedState> CREATOR
+                = new Parcelable.Creator<SavedState>() {
+            public SavedState createFromParcel(Parcel in) {
+                return new SavedState(in);
+            }
+
+            public SavedState[] newArray(int size) {
+                return new SavedState[size];
+            }
+        };
+    }
+
+    @Override
+    public Parcelable onSaveInstanceState() {
+        Parcelable superState = super.onSaveInstanceState();
+        SavedState ss = new SavedState(superState);
+        ss.selectedId = getSelectedItemId();
+        if (ss.selectedId >= 0) {
+            ss.position = getSelectedItemPosition();
+        } else {
+            ss.position = INVALID_POSITION;
+        }
+        return ss;
+    }
+
+    @Override
+    public void onRestoreInstanceState(Parcelable state) {
+        SavedState ss = (SavedState) state;
+  
+        super.onRestoreInstanceState(ss.getSuperState());
+
+        if (ss.selectedId >= 0) {
+            mDataChanged = true;
+            mNeedSync = true;
+            mSyncRowId = ss.selectedId;
+            mSyncPosition = ss.position;
+            mSyncMode = SYNC_SELECTED_POSITION;
+            requestLayout();
+        }
+    }
+
+    class RecycleBin {
+        private SparseArray<View> mScrapHeap = new SparseArray<View>();
+
+        public void put(int position, View v) {
+            mScrapHeap.put(position, v);
+        }
+        
+        View get(int position) {
+            // System.out.print("Looking for " + position);
+            View result = mScrapHeap.get(position);
+            if (result != null) {
+                // System.out.println(" HIT");
+                mScrapHeap.delete(position);
+            } else {
+                // System.out.println(" MISS");
+            }
+            return result;
+        }
+        
+        View peek(int position) {
+            // System.out.print("Looking for " + position);
+            return mScrapHeap.get(position);
+        }
+        
+        void clear() {
+            final SparseArray<View> scrapHeap = mScrapHeap;
+            final int count = scrapHeap.size();
+            for (int i = 0; i < count; i++) {
+                final View view = scrapHeap.valueAt(i);
+                if (view != null) {
+                    removeDetachedView(view, true);
+                }
+            }
+            scrapHeap.clear();
+        }
+    }
+}
diff --git a/core/java/android/widget/AbsoluteLayout.java b/core/java/android/widget/AbsoluteLayout.java
new file mode 100644
index 0000000..c77f7ae
--- /dev/null
+++ b/core/java/android/widget/AbsoluteLayout.java
@@ -0,0 +1,220 @@
+/*
+ * 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 android.widget;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.RemoteViews.RemoteView;
+
+
+/**
+ * A layout that lets you specify exact locations (x/y coordinates) of its
+ * children. Absolute layouts are less flexible and harder to maintain than
+ * other types of layouts without absolute positioning.
+ *
+ * <p><strong>XML attributes</strong></p> <p> See {@link
+ * android.R.styleable#ViewGroup ViewGroup Attributes}, {@link
+ * android.R.styleable#View View Attributes}</p>
+ * 
+ * @deprecated Use {@link android.widget.FrameLayout}, {@link android.widget.RelativeLayout}
+ *             or a custom layout instead.
+ */
+@Deprecated
+@RemoteView
+public class AbsoluteLayout extends ViewGroup {
+    public AbsoluteLayout(Context context) {
+        super(context);
+    }
+
+    public AbsoluteLayout(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    public AbsoluteLayout(Context context, AttributeSet attrs,
+            int defStyle) {
+        super(context, attrs, defStyle);
+    }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        int count = getChildCount();
+
+        int maxHeight = 0;
+        int maxWidth = 0;
+
+        // Find out how big everyone wants to be
+        measureChildren(widthMeasureSpec, heightMeasureSpec);
+
+        // Find rightmost and bottom-most child
+        for (int i = 0; i < count; i++) {
+            View child = getChildAt(i);
+            if (child.getVisibility() != GONE) {
+                int childRight;
+                int childBottom;
+
+                AbsoluteLayout.LayoutParams lp
+                        = (AbsoluteLayout.LayoutParams) child.getLayoutParams();
+
+                childRight = lp.x + child.getMeasuredWidth();
+                childBottom = lp.y + child.getMeasuredHeight();
+
+                maxWidth = Math.max(maxWidth, childRight);
+                maxHeight = Math.max(maxHeight, childBottom);
+            }
+        }
+
+        // Account for padding too
+        maxWidth += mPaddingLeft + mPaddingRight;
+        maxHeight += mPaddingTop + mPaddingBottom;
+
+        // Check against minimum height and width
+        maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight());
+        maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());
+        
+        setMeasuredDimension(resolveSize(maxWidth, widthMeasureSpec),
+                resolveSize(maxHeight, heightMeasureSpec));
+    }
+
+    /**
+     * Returns a set of layout parameters with a width of
+     * {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT},
+     * a height of {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT}
+     * and with the coordinates (0, 0).
+     */
+    @Override
+    protected ViewGroup.LayoutParams generateDefaultLayoutParams() {
+        return new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT, 0, 0);
+    }
+
+    @Override
+    protected void onLayout(boolean changed, int l, int t,
+            int r, int b) {
+        int count = getChildCount();
+
+        for (int i = 0; i < count; i++) {
+            View child = getChildAt(i);
+            if (child.getVisibility() != GONE) {
+
+                AbsoluteLayout.LayoutParams lp =
+                        (AbsoluteLayout.LayoutParams) child.getLayoutParams();
+
+                int childLeft = mPaddingLeft + lp.x;
+                int childTop = mPaddingTop + lp.y;
+                child.layout(childLeft, childTop,
+                        childLeft + child.getMeasuredWidth(),
+                        childTop + child.getMeasuredHeight());
+
+            }
+        }
+    }
+
+    @Override
+    public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {
+        return new AbsoluteLayout.LayoutParams(getContext(), attrs);
+    }
+
+    // Override to allow type-checking of LayoutParams. 
+    @Override
+    protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
+        return p instanceof AbsoluteLayout.LayoutParams;
+    }
+
+    @Override
+    protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
+        return new LayoutParams(p);
+    }
+
+    /**
+     * Per-child layout information associated with AbsoluteLayout.
+     * See
+     * {@link android.R.styleable#AbsoluteLayout_Layout Absolute Layout Attributes}
+     * for a list of all child view attributes that this class supports.
+     */
+    public static class LayoutParams extends ViewGroup.LayoutParams {
+        /**
+         * The horizontal, or X, location of the child within the view group.
+         */
+        public int x;
+        /**
+         * The vertical, or Y, location of the child within the view group.
+         */
+        public int y;
+
+        /**
+         * Creates a new set of layout parameters with the specified width,
+         * height and location.
+         *
+         * @param width the width, either {@link #FILL_PARENT},
+                  {@link #WRAP_CONTENT} or a fixed size in pixels
+         * @param height the height, either {@link #FILL_PARENT},
+                  {@link #WRAP_CONTENT} or a fixed size in pixels
+         * @param x the X location of the child
+         * @param y the Y location of the child
+         */
+        public LayoutParams(int width, int height, int x, int y) {
+            super(width, height);
+            this.x = x;
+            this.y = y;
+        }
+
+        /**
+         * Creates a new set of layout parameters. The values are extracted from
+         * the supplied attributes set and context. The XML attributes mapped
+         * to this set of layout parameters are:
+         *
+         * <ul>
+         *   <li><code>layout_x</code>: the X location of the child</li>
+         *   <li><code>layout_y</code>: the Y location of the child</li>
+         *   <li>All the XML attributes from
+         *   {@link android.view.ViewGroup.LayoutParams}</li>
+         * </ul>
+         *
+         * @param c the application environment
+         * @param attrs the set of attributes fom which to extract the layout
+         *              parameters values
+         */
+        public LayoutParams(Context c, AttributeSet attrs) {
+            super(c, attrs);
+            TypedArray a = c.obtainStyledAttributes(attrs,
+                    com.android.internal.R.styleable.AbsoluteLayout_Layout);
+            x = a.getDimensionPixelOffset(
+                    com.android.internal.R.styleable.AbsoluteLayout_Layout_layout_x, 0);
+            y = a.getDimensionPixelOffset(
+                    com.android.internal.R.styleable.AbsoluteLayout_Layout_layout_y, 0);
+            a.recycle();
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        public LayoutParams(ViewGroup.LayoutParams source) {
+            super(source);
+        }
+
+        @Override
+        public String debug(String output) {
+            return output + "Absolute.LayoutParams={width="
+                    + sizeToString(width) + ", height=" + sizeToString(height)
+                    + " x=" + x + " y=" + y + "}";
+        }
+    }
+}
+
+
diff --git a/core/java/android/widget/Adapter.java b/core/java/android/widget/Adapter.java
new file mode 100644
index 0000000..f2b3e2a
--- /dev/null
+++ b/core/java/android/widget/Adapter.java
@@ -0,0 +1,149 @@
+/*
+ * 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 android.widget;
+
+import android.database.DataSetObserver;
+import android.view.KeyEvent;
+import android.view.View;
+import android.view.ViewGroup;
+
+/**
+ * An Adapter object acts as a bridge between an {@link AdapterView} and the
+ * underlying data for that view. The Adapter provides access to the data items.
+ * The Adapter is also responsible for making a {@link android.view.View} for
+ * each item in the data set.
+ * 
+ * @see android.widget.ArrayAdapter
+ * @see android.widget.CursorAdapter
+ * @see android.widget.SimpleCursorAdapter
+ */
+public interface Adapter {
+    /**
+     * Register an observer that is called when changes happen to the data used by this adapter.
+     *
+     * @param observer the object that gets notified when the data set changes.
+     */
+    void registerDataSetObserver(DataSetObserver observer);
+
+    /**
+     * Unregister an observer that has previously been registered with this
+     * adapter via {@link #registerDataSetObserver}.
+     *
+     * @param observer the object to unregister.
+     */
+    void unregisterDataSetObserver(DataSetObserver observer);
+
+    /**
+     * How many items are in the data set represented by this Adapter.
+     * 
+     * @return Count of items.
+     */
+    int getCount();   
+    
+    /**
+     * Get the data item associated with the specified position in the data set.
+     * 
+     * @param position Position of the item whose data we want within the adapter's 
+     * data set.
+     * @return The data at the specified position.
+     */
+    Object getItem(int position);
+    
+    /**
+     * Get the row id associated with the specified position in the list.
+     * 
+     * @param position The position of the item within the adapter's data set whose row id we want.
+     * @return The id of the item at the specified position.
+     */
+    long getItemId(int position);
+    
+    /**
+     * Indicated whether the item ids are stable across changes to the
+     * underlying data.
+     * 
+     * @return True if the same id always refers to the same object.
+     */
+    boolean hasStableIds();
+    
+    /**
+     * Get a View that displays the data at the specified position in the data set. You can either
+     * create a View manually or inflate it from an XML layout file. When the View is inflated, the
+     * parent View (GridView, ListView...) will apply default layout parameters unless you use
+     * {@link android.view.LayoutInflater#inflate(int, android.view.ViewGroup, boolean)}
+     * to specify a root view and to prevent attachment to the root.
+     * 
+     * @param position The position of the item within the adapter's data set of the item whose view
+     *        we want.
+     * @param convertView The old view to reuse, if possible. Note: You should check that this view
+     *        is non-null and of an appropriate type before using. If it is not possible to convert
+     *        this view to display the correct data, this method can create a new view.
+     * @param parent The parent that this view will eventually be attached to
+     * @return A View corresponding to the data at the specified position.
+     */
+    View getView(int position, View convertView, ViewGroup parent);
+
+    /**
+     * An item view type that causes the {@link AdapterView} to ignore the item
+     * view. For example, this can be used if the client does not want a
+     * particular view to be given for conversion in
+     * {@link #getView(int, View, ViewGroup)}.
+     * 
+     * @see #getItemViewType(int)
+     * @see #getViewTypeCount()
+     */
+    static final int IGNORE_ITEM_VIEW_TYPE = AdapterView.ITEM_VIEW_TYPE_IGNORE;
+    
+    /**
+     * Get the type of View that will be created by {@link #getView} for the specified item.
+     * 
+     * @param position The position of the item within the adapter's data set whose view type we
+     *        want.
+     * @return An integer representing the type of View. Two views should share the same type if one
+     *         can be converted to the other in {@link #getView}. Note: Integers must be in the
+     *         range 0 to {@link #getViewTypeCount} - 1. {@link #IGNORE_ITEM_VIEW_TYPE} can
+     *         also be returned.
+     * @see #IGNORE_ITEM_VIEW_TYPE
+     */
+    int getItemViewType(int position);
+    
+    /**
+     * <p>
+     * Returns the number of types of Views that will be created by
+     * {@link #getView}. Each type represents a set of views that can be
+     * converted in {@link #getView}. If the adapter always returns the same
+     * type of View for all items, this method should return 1.
+     * </p>
+     * <p>
+     * This method will only be called when when the adapter is set on the
+     * the {@link AdapterView}.
+     * </p>
+     * 
+     * @return The number of types of Views that will be created by this adapter
+     */
+    int getViewTypeCount();
+    
+    static final int NO_SELECTION = Integer.MIN_VALUE;
+ 
+     /**
+      * @return true if this adapter doesn't contain any data.  This is used to determine
+      * whether the empty view should be displayed.  A typical implementation will return
+      * getCount() == 0 but since getCount() includes the headers and footers, specialized
+      * adapters might want a different behavior.
+      */
+     boolean isEmpty();
+}
+
diff --git a/core/java/android/widget/AdapterView.java b/core/java/android/widget/AdapterView.java
new file mode 100644
index 0000000..173e80f
--- /dev/null
+++ b/core/java/android/widget/AdapterView.java
@@ -0,0 +1,1097 @@
+/*
+ * 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 android.widget;
+
+import android.content.Context;
+import android.database.DataSetObserver;
+import android.os.Handler;
+import android.os.Parcelable;
+import android.os.SystemClock;
+import android.util.AttributeSet;
+import android.util.SparseArray;
+import android.view.ContextMenu;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewDebug;
+import android.view.SoundEffectConstants;
+import android.view.ContextMenu.ContextMenuInfo;
+
+
+/**
+ * An AdapterView is a view whose children are determined by an {@link Adapter}.
+ *
+ * <p>
+ * See {@link ListView}, {@link GridView}, {@link Spinner} and
+ *      {@link Gallery} for commonly used subclasses of AdapterView.
+ */
+public abstract class AdapterView<T extends Adapter> extends ViewGroup {
+
+    /**
+     * The item view type returned by {@link Adapter#getItemViewType(int)} when
+     * the adapter does not want the item's view recycled.
+     */
+    public static final int ITEM_VIEW_TYPE_IGNORE = -1;
+
+    /**
+     * The item view type returned by {@link Adapter#getItemViewType(int)} when
+     * the item is a header or footer.
+     */
+    public static final int ITEM_VIEW_TYPE_HEADER_OR_FOOTER = -2;
+
+    /**
+     * The position of the first child displayed
+     */
+    @ViewDebug.ExportedProperty
+    int mFirstPosition = 0;
+
+    /**
+     * The offset in pixels from the top of the AdapterView to the top
+     * of the view to select during the next layout.
+     */
+    int mSpecificTop;
+
+    /**
+     * Position from which to start looking for mSyncRowId
+     */
+    int mSyncPosition;
+
+    /**
+     * Row id to look for when data has changed
+     */
+    long mSyncRowId = INVALID_ROW_ID;
+
+    /**
+     * Height of the view when mSyncPosition and mSyncRowId where set
+     */
+    long mSyncHeight;
+
+    /**
+     * True if we need to sync to mSyncRowId
+     */
+    boolean mNeedSync = false;
+
+    /**
+     * Indicates whether to sync based on the selection or position. Possible
+     * values are {@link #SYNC_SELECTED_POSITION} or
+     * {@link #SYNC_FIRST_POSITION}.
+     */
+    int mSyncMode;
+
+    /**
+     * Our height after the last layout
+     */
+    private int mLayoutHeight;
+
+    /**
+     * Sync based on the selected child
+     */
+    static final int SYNC_SELECTED_POSITION = 0;
+
+    /**
+     * Sync based on the first child displayed
+     */
+    static final int SYNC_FIRST_POSITION = 1;
+
+    /**
+     * Maximum amount of time to spend in {@link #findSyncPosition()}
+     */
+    static final int SYNC_MAX_DURATION_MILLIS = 100;
+
+    /**
+     * Indicates that this view is currently being laid out.
+     */
+    boolean mInLayout = false;
+
+    /**
+     * The listener that receives notifications when an item is selected.
+     */
+    OnItemSelectedListener mOnItemSelectedListener;
+
+    /**
+     * The listener that receives notifications when an item is clicked.
+     */
+    OnItemClickListener mOnItemClickListener;
+
+    /**
+     * The listener that receives notifications when an item is long clicked.
+     */
+    OnItemLongClickListener mOnItemLongClickListener;
+
+    /**
+     * True if the data has changed since the last layout
+     */
+    boolean mDataChanged;
+
+    /**
+     * The position within the adapter's data set of the item to select
+     * during the next layout.
+     */
+    @ViewDebug.ExportedProperty    
+    int mNextSelectedPosition = INVALID_POSITION;
+
+    /**
+     * The item id of the item to select during the next layout.
+     */
+    long mNextSelectedRowId = INVALID_ROW_ID;
+
+    /**
+     * The position within the adapter's data set of the currently selected item.
+     */
+    @ViewDebug.ExportedProperty    
+    int mSelectedPosition = INVALID_POSITION;
+
+    /**
+     * The item id of the currently selected item.
+     */
+    long mSelectedRowId = INVALID_ROW_ID;
+
+    /**
+     * View to show if there are no items to show.
+     */
+    View mEmptyView;
+
+    /**
+     * The number of items in the current adapter.
+     */
+    @ViewDebug.ExportedProperty
+    int mItemCount;
+
+    /**
+     * The number of items in the adapter before a data changed event occured.
+     */
+    int mOldItemCount;
+
+    /**
+     * Represents an invalid position. All valid positions are in the range 0 to 1 less than the
+     * number of items in the current adapter.
+     */
+    public static final int INVALID_POSITION = -1;
+
+    /**
+     * Represents an empty or invalid row id
+     */
+    public static final long INVALID_ROW_ID = Long.MIN_VALUE;
+
+    /**
+     * The last selected position we used when notifying
+     */
+    int mOldSelectedPosition = INVALID_POSITION;
+    
+    /**
+     * The id of the last selected position we used when notifying
+     */
+    long mOldSelectedRowId = INVALID_ROW_ID;
+
+    /**
+     * Indicates what focusable state is requested when calling setFocusable().
+     * In addition to this, this view has other criteria for actually
+     * determining the focusable state (such as whether its empty or the text
+     * filter is shown).
+     *
+     * @see #setFocusable(boolean)
+     * @see #checkFocus()
+     */
+    private boolean mDesiredFocusableState;
+    private boolean mDesiredFocusableInTouchModeState;
+
+    private SelectionNotifier mSelectionNotifier;
+    /**
+     * When set to true, calls to requestLayout() will not propagate up the parent hierarchy.
+     * This is used to layout the children during a layout pass.
+     */
+    boolean mBlockLayoutRequests = false;
+
+    public AdapterView(Context context) {
+        super(context);
+    }
+
+    public AdapterView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    public AdapterView(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+    }
+
+
+    /**
+     * Interface definition for a callback to be invoked when an item in this
+     * AdapterView has been clicked.
+     */
+    public interface OnItemClickListener {
+
+        /**
+         * Callback method to be invoked when an item in this AdapterView has
+         * been clicked.
+         * <p>
+         * Implementers can call getItemAtPosition(position) if they need
+         * to access the data associated with the selected item.
+         *
+         * @param parent The AdapterView where the click happened.
+         * @param view The view within the AdapterView that was clicked (this
+         *            will be a view provided by the adapter)
+         * @param position The position of the view in the adapter.
+         * @param id The row id of the item that was clicked.
+         */
+        void onItemClick(AdapterView<?> parent, View view, int position, long id);
+    }
+
+    /**
+     * Register a callback to be invoked when an item in this AdapterView has
+     * been clicked.
+     *
+     * @param listener The callback that will be invoked.
+     */
+    public void setOnItemClickListener(OnItemClickListener listener) {
+        mOnItemClickListener = listener;
+    }
+
+    /**
+     * @return The callback to be invoked with an item in this AdapterView has
+     *         been clicked, or null id no callback has been set.
+     */
+    public final OnItemClickListener getOnItemClickListener() {
+        return mOnItemClickListener;
+    }
+
+    /**
+     * Call the OnItemClickListener, if it is defined.
+     *
+     * @param view The view within the AdapterView that was clicked.
+     * @param position The position of the view in the adapter.
+     * @param id The row id of the item that was clicked.
+     * @return True if there was an assigned OnItemClickListener that was
+     *         called, false otherwise is returned.
+     */
+    public boolean performItemClick(View view, int position, long id) {
+        if (mOnItemClickListener != null) {
+            playSoundEffect(SoundEffectConstants.CLICK);
+            mOnItemClickListener.onItemClick(this, view, position, id);
+            return true;
+        }
+
+        return false;
+    }
+
+    /**
+     * Interface definition for a callback to be invoked when an item in this
+     * view has been clicked and held.
+     */
+    public interface OnItemLongClickListener {
+        /**
+         * Callback method to be invoked when an item in this view has been
+         * clicked and held.
+         *
+         * Implementers can call getItemAtPosition(position) if they need to access
+         * the data associated with the selected item.
+         *
+         * @param parent The AbsListView where the click happened
+         * @param view The view within the AbsListView that was clicked
+         * @param position The position of the view in the list
+         * @param id The row id of the item that was clicked
+         *
+         * @return true if the callback consumed the long click, false otherwise
+         */
+        boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id);
+    }
+
+
+    /**
+     * Register a callback to be invoked when an item in this AdapterView has
+     * been clicked and held
+     *
+     * @param listener The callback that will run
+     */
+    public void setOnItemLongClickListener(OnItemLongClickListener listener) {
+        if (!isLongClickable()) {
+            setLongClickable(true);
+        }
+        mOnItemLongClickListener = listener;
+    }
+
+    /**
+     * @return The callback to be invoked with an item in this AdapterView has
+     *         been clicked and held, or null id no callback as been set.
+     */
+    public final OnItemLongClickListener getOnItemLongClickListener() {
+        return mOnItemLongClickListener;
+    }
+
+    /**
+     * Interface definition for a callback to be invoked when
+     * an item in this view has been selected.
+     */
+    public interface OnItemSelectedListener {
+        /**
+         * Callback method to be invoked when an item in this view has been
+         * selected.
+         *
+         * Impelmenters can call getItemAtPosition(position) if they need to access the
+         * data associated with the selected item.
+         *
+         * @param parent The AdapterView where the selection happened
+         * @param view The view within the AdapterView that was clicked
+         * @param position The position of the view in the adapter
+         * @param id The row id of the item that is selected
+         */
+        void onItemSelected(AdapterView<?> parent, View view, int position, long id);
+
+        /**
+         * Callback method to be invoked when the selection disappears from this
+         * view. The selection can disappear for instance when touch is activated
+         * or when the adapter becomes empty.
+         *
+         * @param parent The AdapterView that now contains no selected item.
+         */
+        void onNothingSelected(AdapterView<?> parent);
+    }
+
+
+    /**
+     * Register a callback to be invoked when an item in this AdapterView has
+     * been selected.
+     *
+     * @param listener The callback that will run
+     */
+    public void setOnItemSelectedListener(OnItemSelectedListener listener) {
+        mOnItemSelectedListener = listener;
+    }
+
+    public final OnItemSelectedListener getOnItemSelectedListener() {
+        return mOnItemSelectedListener;
+    }
+
+    /**
+     * Extra menu information provided to the
+     * {@link android.view.View.OnCreateContextMenuListener#onCreateContextMenu(ContextMenu, View, ContextMenuInfo) }
+     * callback when a context menu is brought up for this AdapterView.
+     *
+     */
+    public static class AdapterContextMenuInfo implements ContextMenu.ContextMenuInfo {
+
+        public AdapterContextMenuInfo(View targetView, int position, long id) {
+            this.targetView = targetView;
+            this.position = position;
+            this.id = id;
+        }
+
+        /**
+         * The child view for which the context menu is being displayed. This
+         * will be one of the children of this AdapterView.
+         */
+        public View targetView;
+
+        /**
+         * The position in the adapter for which the context menu is being
+         * displayed.
+         */
+        public int position;
+
+        /**
+         * The row id of the item for which the context menu is being displayed.
+         */
+        public long id;
+    }
+
+    /**
+     * Returns the adapter currently associated with this widget.
+     *
+     * @return The adapter used to provide this view's content.
+     */
+    public abstract T getAdapter();
+
+    /**
+     * Sets the adapter that provides the data and the views to represent the data
+     * in this widget.
+     *
+     * @param adapter The adapter to use to create this view's content.
+     */
+    public abstract void setAdapter(T adapter);
+
+    /**
+     * This method is not supported and throws an UnsupportedOperationException when called.
+     *
+     * @param child Ignored.
+     *
+     * @throws UnsupportedOperationException Every time this method is invoked.
+     */
+    @Override
+    public void addView(View child) {
+        throw new UnsupportedOperationException("addView(View) is not supported in AdapterView");
+    }
+
+    /**
+     * This method is not supported and throws an UnsupportedOperationException when called.
+     *
+     * @param child Ignored.
+     * @param index Ignored.
+     *
+     * @throws UnsupportedOperationException Every time this method is invoked.
+     */
+    @Override
+    public void addView(View child, int index) {
+        throw new UnsupportedOperationException("addView(View, int) is not supported in AdapterView");
+    }
+
+    /**
+     * This method is not supported and throws an UnsupportedOperationException when called.
+     *
+     * @param child Ignored.
+     * @param params Ignored.
+     *
+     * @throws UnsupportedOperationException Every time this method is invoked.
+     */
+    @Override
+    public void addView(View child, LayoutParams params) {
+        throw new UnsupportedOperationException("addView(View, LayoutParams) "
+                + "is not supported in AdapterView");
+    }
+
+    /**
+     * This method is not supported and throws an UnsupportedOperationException when called.
+     *
+     * @param child Ignored.
+     * @param index Ignored.
+     * @param params Ignored.
+     *
+     * @throws UnsupportedOperationException Every time this method is invoked.
+     */
+    @Override
+    public void addView(View child, int index, LayoutParams params) {
+        throw new UnsupportedOperationException("addView(View, int, LayoutParams) "
+                + "is not supported in AdapterView");
+    }
+
+    /**
+     * This method is not supported and throws an UnsupportedOperationException when called.
+     *
+     * @param child Ignored.
+     *
+     * @throws UnsupportedOperationException Every time this method is invoked.
+     */
+    @Override
+    public void removeView(View child) {
+        throw new UnsupportedOperationException("removeView(View) is not supported in AdapterView");
+    }
+
+    /**
+     * This method is not supported and throws an UnsupportedOperationException when called.
+     *
+     * @param index Ignored.
+     *
+     * @throws UnsupportedOperationException Every time this method is invoked.
+     */
+    @Override
+    public void removeViewAt(int index) {
+        throw new UnsupportedOperationException("removeViewAt(int) is not supported in AdapterView");
+    }
+
+    /**
+     * This method is not supported and throws an UnsupportedOperationException when called.
+     *
+     * @throws UnsupportedOperationException Every time this method is invoked.
+     */
+    @Override
+    public void removeAllViews() {
+        throw new UnsupportedOperationException("removeAllViews() is not supported in AdapterView");
+    }
+
+    @Override
+    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+        mLayoutHeight = getHeight();
+    }
+
+    /**
+     * Return the position of the currently selected item within the adapter's data set
+     *
+     * @return int Position (starting at 0), or {@link #INVALID_POSITION} if there is nothing selected.
+     */
+    @ViewDebug.CapturedViewProperty
+    public int getSelectedItemPosition() {
+        return mNextSelectedPosition;
+    }
+
+    /**
+     * @return The id corresponding to the currently selected item, or {@link #INVALID_ROW_ID}
+     * if nothing is selected.
+     */
+    @ViewDebug.CapturedViewProperty
+    public long getSelectedItemId() {
+        return mNextSelectedRowId;
+    }
+
+    /**
+     * @return The view corresponding to the currently selected item, or null
+     * if nothing is selected
+     */
+    public abstract View getSelectedView();
+
+    /**
+     * @return The data corresponding to the currently selected item, or
+     * null if there is nothing selected.
+     */
+    public Object getSelectedItem() {
+        T adapter = getAdapter();
+        int selection = getSelectedItemPosition();
+        if (adapter != null && adapter.getCount() > 0 && selection >= 0) {
+            return adapter.getItem(selection);
+        } else {
+            return null;
+        }
+    }
+
+    /**
+     * @return The number of items owned by the Adapter associated with this
+     *         AdapterView. (This is the number of data items, which may be
+     *         larger than the number of visible view.)
+     */
+    @ViewDebug.CapturedViewProperty
+    public int getCount() {
+        return mItemCount;
+    }
+
+    /**
+     * Get the position within the adapter's data set for the view, where view is a an adapter item
+     * or a descendant of an adapter item.
+     *
+     * @param view an adapter item, or a descendant of an adapter item. This must be visible in this
+     *        AdapterView at the time of the call.
+     * @return the position within the adapter's data set of the view, or {@link #INVALID_POSITION}
+     *         if the view does not correspond to a list item (or it is not currently visible).
+     */
+    public int getPositionForView(View view) {
+        View listItem = view;
+        try {
+            View v;
+            while (!(v = (View) listItem.getParent()).equals(this)) {
+                listItem = v;
+            }
+        } catch (ClassCastException e) {
+            // We made it up to the window without find this list view
+            return INVALID_POSITION;
+        }
+
+        // Search the children for the list item
+        final int childCount = getChildCount();
+        for (int i = 0; i < childCount; i++) {
+            if (getChildAt(i).equals(listItem)) {
+                return mFirstPosition + i;
+            }
+        }
+
+        // Child not found!
+        return INVALID_POSITION;
+    }
+
+    /**
+     * Returns the position within the adapter's data set for the first item
+     * displayed on screen.
+     *
+     * @return The position within the adapter's data set
+     */
+    public int getFirstVisiblePosition() {
+        return mFirstPosition;
+    }
+
+    /**
+     * Returns the position within the adapter's data set for the last item
+     * displayed on screen.
+     *
+     * @return The position within the adapter's data set
+     */
+    public int getLastVisiblePosition() {
+        return mFirstPosition + getChildCount() - 1;
+    }
+
+    /**
+     * Sets the currently selected item
+     * @param position Index (starting at 0) of the data item to be selected.
+     */
+    public abstract void setSelection(int position);
+
+    /**
+     * Sets the view to show if the adapter is empty
+     */
+    public void setEmptyView(View emptyView) {
+        mEmptyView = emptyView;
+
+        final T adapter = getAdapter();
+        final boolean empty = ((adapter == null) || adapter.isEmpty());
+        updateEmptyStatus(empty);
+    }
+
+    /**
+     * When the current adapter is empty, the AdapterView can display a special view
+     * call the empty view. The empty view is used to provide feedback to the user
+     * that no data is available in this AdapterView.
+     *
+     * @return The view to show if the adapter is empty.
+     */
+    public View getEmptyView() {
+        return mEmptyView;
+    }
+
+    /**
+     * Indicates whether this view is in filter mode. Filter mode can for instance
+     * be enabled by a user when typing on the keyboard.
+     *
+     * @return True if the view is in filter mode, false otherwise.
+     */
+    boolean isInFilterMode() {
+        return false;
+    }
+
+    @Override
+    public void setFocusable(boolean focusable) {
+        final T adapter = getAdapter();
+        final boolean empty = adapter == null || adapter.getCount() == 0;
+
+        mDesiredFocusableState = focusable;
+        if (!focusable) {
+            mDesiredFocusableInTouchModeState = false;
+        }
+
+        super.setFocusable(focusable && (!empty || isInFilterMode()));
+    }
+
+    @Override
+    public void setFocusableInTouchMode(boolean focusable) {
+        final T adapter = getAdapter();
+        final boolean empty = adapter == null || adapter.getCount() == 0;
+
+        mDesiredFocusableInTouchModeState = focusable;
+        if (focusable) {
+            mDesiredFocusableState = true;
+        }
+
+        super.setFocusableInTouchMode(focusable && (!empty || isInFilterMode()));
+    }
+
+    void checkFocus() {
+        final T adapter = getAdapter();
+        final boolean empty = adapter == null || adapter.getCount() == 0;
+        final boolean focusable = !empty || isInFilterMode();
+        // The order in which we set focusable in touch mode/focusable may matter
+        // for the client, see View.setFocusableInTouchMode() comments for more
+        // details
+        super.setFocusableInTouchMode(focusable && mDesiredFocusableInTouchModeState);
+        super.setFocusable(focusable && mDesiredFocusableState);
+        if (mEmptyView != null) {
+            updateEmptyStatus((adapter == null) || adapter.isEmpty());
+        }
+    }
+
+    /**
+     * Update the status of the list based on the empty parameter.  If empty is true and
+     * we have an empty view, display it.  In all the other cases, make sure that the listview
+     * is VISIBLE and that the empty view is GONE (if it's not null).
+     */
+    private void updateEmptyStatus(boolean empty) {
+        if (isInFilterMode()) {
+            empty = false;
+        }
+
+        if (empty) {
+            if (mEmptyView != null) {
+                mEmptyView.setVisibility(View.VISIBLE);
+                setVisibility(View.GONE);
+            } else {
+                // If the caller just removed our empty view, make sure the list view is visible
+                setVisibility(View.VISIBLE);
+            }
+
+            // We are now GONE, so pending layouts will not be dispatched.
+            // Force one here to make sure that the state of the list matches
+            // the state of the adapter.
+            if (mDataChanged) {           
+                this.onLayout(false, mLeft, mTop, mRight, mBottom); 
+            }
+        } else {
+            if (mEmptyView != null) mEmptyView.setVisibility(View.GONE);
+            setVisibility(View.VISIBLE);
+        }
+    }
+
+    /**
+     * Gets the data associated with the specified position in the list.
+     *
+     * @param position Which data to get
+     * @return The data associated with the specified position in the list
+     */
+    public Object getItemAtPosition(int position) {
+        T adapter = getAdapter();
+        return (adapter == null || position < 0) ? null : adapter.getItem(position);
+    }
+
+    public long getItemIdAtPosition(int position) {
+        T adapter = getAdapter();
+        return (adapter == null || position < 0) ? INVALID_ROW_ID : adapter.getItemId(position);
+    }
+
+    @Override
+    public void setOnClickListener(OnClickListener l) {
+        throw new RuntimeException("Don't call setOnClickListener for an AdapterView. "
+                + "You probably want setOnItemClickListener instead");
+    }
+
+    /**
+     * Override to prevent freezing of any views created by the adapter.
+     */
+    @Override
+    protected void dispatchSaveInstanceState(SparseArray<Parcelable> container) {
+        dispatchFreezeSelfOnly(container);
+    }
+
+    /**
+     * Override to prevent thawing of any views created by the adapter.
+     */
+    @Override
+    protected void dispatchRestoreInstanceState(SparseArray<Parcelable> container) {
+        dispatchThawSelfOnly(container);
+    }
+
+    class AdapterDataSetObserver extends DataSetObserver {
+
+        private Parcelable mInstanceState = null;
+
+        @Override
+        public void onChanged() {
+            mDataChanged = true;
+            mOldItemCount = mItemCount;
+            mItemCount = getAdapter().getCount();
+
+            // Detect the case where a cursor that was previously invalidated has
+            // been repopulated with new data.
+            if (AdapterView.this.getAdapter().hasStableIds() && mInstanceState != null
+                    && mOldItemCount == 0 && mItemCount > 0) {
+                AdapterView.this.onRestoreInstanceState(mInstanceState);
+                mInstanceState = null;
+            } else {
+                rememberSyncState();
+            }
+            checkFocus();
+            requestLayout();
+        }
+
+        @Override
+        public void onInvalidated() {
+            mDataChanged = true;
+
+            if (AdapterView.this.getAdapter().hasStableIds()) {
+                // Remember the current state for the case where our hosting activity is being
+                // stopped and later restarted
+                mInstanceState = AdapterView.this.onSaveInstanceState();
+            }
+
+            // Data is invalid so we should reset our state
+            mOldItemCount = mItemCount;
+            mItemCount = 0;
+            mSelectedPosition = INVALID_POSITION;
+            mSelectedRowId = INVALID_ROW_ID;
+            mNextSelectedPosition = INVALID_POSITION;
+            mNextSelectedRowId = INVALID_ROW_ID;
+            mNeedSync = false;
+            checkSelectionChanged();
+
+            checkFocus();
+            requestLayout();
+        }
+
+        public void clearSavedState() {
+            mInstanceState = null;
+        }
+    }
+
+    private class SelectionNotifier extends Handler implements Runnable {
+        public void run() {
+            if (mDataChanged) {
+                // Data has changed between when this SelectionNotifier
+                // was posted and now. We need to wait until the AdapterView
+                // has been synched to the new data.
+                post(this);
+            } else {
+                fireOnSelected();
+            }
+        }
+    }
+
+    void selectionChanged() {
+        if (mOnItemSelectedListener != null) {
+            if (mInLayout || mBlockLayoutRequests) {
+                // If we are in a layout traversal, defer notification
+                // by posting. This ensures that the view tree is
+                // in a consistent state and is able to accomodate
+                // new layout or invalidate requests.
+                if (mSelectionNotifier == null) {
+                    mSelectionNotifier = new SelectionNotifier();
+                }
+                mSelectionNotifier.post(mSelectionNotifier);
+            } else {
+                fireOnSelected();
+            }
+        }
+    }
+
+    private void fireOnSelected() {
+        if (mOnItemSelectedListener == null)
+            return;
+
+        int selection = this.getSelectedItemPosition();
+        if (selection >= 0) {
+            View v = getSelectedView();
+            mOnItemSelectedListener.onItemSelected(this, v, selection,
+                    getAdapter().getItemId(selection));
+        } else {
+            mOnItemSelectedListener.onNothingSelected(this);
+        }
+    }
+
+    @Override
+    protected boolean canAnimate() {
+        return super.canAnimate() && mItemCount > 0;
+    }
+
+    void handleDataChanged() {
+        final int count = mItemCount;
+        boolean found = false;
+
+        if (count > 0) {
+
+            int newPos;
+
+            // Find the row we are supposed to sync to
+            if (mNeedSync) {
+                // Update this first, since setNextSelectedPositionInt inspects
+                // it
+                mNeedSync = false;
+
+                // See if we can find a position in the new data with the same
+                // id as the old selection
+                newPos = findSyncPosition();
+                if (newPos >= 0) {
+                    // Verify that new selection is selectable
+                    int selectablePos = lookForSelectablePosition(newPos, true);
+                    if (selectablePos == newPos) {
+                        // Same row id is selected
+                        setNextSelectedPositionInt(newPos);
+                        found = true;
+                    }
+                }
+            }
+            if (!found) {
+                // Try to use the same position if we can't find matching data
+                newPos = getSelectedItemPosition();
+
+                // Pin position to the available range
+                if (newPos >= count) {
+                    newPos = count - 1;
+                }
+                if (newPos < 0) {
+                    newPos = 0;
+                }
+
+                // Make sure we select something selectable -- first look down
+                int selectablePos = lookForSelectablePosition(newPos, true);
+                if (selectablePos < 0) {
+                    // Looking down didn't work -- try looking up
+                    selectablePos = lookForSelectablePosition(newPos, false);
+                }
+                if (selectablePos >= 0) {
+                    setNextSelectedPositionInt(selectablePos);
+                    checkSelectionChanged();
+                    found = true;
+                }
+            }
+        }
+        if (!found) {
+            // Nothing is selected
+            mSelectedPosition = INVALID_POSITION;
+            mSelectedRowId = INVALID_ROW_ID;
+            mNextSelectedPosition = INVALID_POSITION;
+            mNextSelectedRowId = INVALID_ROW_ID;
+            mNeedSync = false;
+            checkSelectionChanged();
+        }
+    }
+
+    void checkSelectionChanged() {
+        if ((mSelectedPosition != mOldSelectedPosition) || (mSelectedRowId != mOldSelectedRowId)) {
+            selectionChanged();
+            mOldSelectedPosition = mSelectedPosition;
+            mOldSelectedRowId = mSelectedRowId;
+        }
+    }
+
+    /**
+     * Searches the adapter for a position matching mSyncRowId. The search starts at mSyncPosition
+     * and then alternates between moving up and moving down until 1) we find the right position, or
+     * 2) we run out of time, or 3) we have looked at every position
+     *
+     * @return Position of the row that matches mSyncRowId, or {@link #INVALID_POSITION} if it can't
+     *         be found
+     */
+    int findSyncPosition() {
+        int count = mItemCount;
+
+        if (count == 0) {
+            return INVALID_POSITION;
+        }
+
+        long idToMatch = mSyncRowId;
+        int seed = mSyncPosition;
+
+        // If there isn't a selection don't hunt for it
+        if (idToMatch == INVALID_ROW_ID) {
+            return INVALID_POSITION;
+        }
+
+        // Pin seed to reasonable values
+        seed = Math.max(0, seed);
+        seed = Math.min(count - 1, seed);
+
+        long endTime = SystemClock.uptimeMillis() + SYNC_MAX_DURATION_MILLIS;
+
+        long rowId;
+
+        // first position scanned so far
+        int first = seed;
+
+        // last position scanned so far
+        int last = seed;
+
+        // True if we should move down on the next iteration
+        boolean next = false;
+
+        // True when we have looked at the first item in the data
+        boolean hitFirst;
+
+        // True when we have looked at the last item in the data
+        boolean hitLast;
+
+        // Get the item ID locally (instead of getItemIdAtPosition), so
+        // we need the adapter
+        T adapter = getAdapter();
+        if (adapter == null) {
+            return INVALID_POSITION;
+        }
+
+        while (SystemClock.uptimeMillis() <= endTime) {
+            rowId = adapter.getItemId(seed);
+            if (rowId == idToMatch) {
+                // Found it!
+                return seed;
+            }
+
+            hitLast = last == count - 1;
+            hitFirst = first == 0;
+
+            if (hitLast && hitFirst) {
+                // Looked at everything
+                break;
+            }
+
+            if (hitFirst || (next && !hitLast)) {
+                // Either we hit the top, or we are trying to move down
+                last++;
+                seed = last;
+                // Try going up next time
+                next = false;
+            } else if (hitLast || (!next && !hitFirst)) {
+                // Either we hit the bottom, or we are trying to move up
+                first--;
+                seed = first;
+                // Try going down next time
+                next = true;
+            }
+
+        }
+
+        return INVALID_POSITION;
+    }
+
+    /**
+     * Find a position that can be selected (i.e., is not a separator).
+     *
+     * @param position The starting position to look at.
+     * @param lookDown Whether to look down for other positions.
+     * @return The next selectable position starting at position and then searching either up or
+     *         down. Returns {@link #INVALID_POSITION} if nothing can be found.
+     */
+    int lookForSelectablePosition(int position, boolean lookDown) {
+        return position;
+    }
+
+    /**
+     * Utility to keep mSelectedPosition and mSelectedRowId in sync
+     * @param position Our current position
+     */
+    void setSelectedPositionInt(int position) {
+        mSelectedPosition = position;
+        mSelectedRowId = getItemIdAtPosition(position);
+    }
+
+    /**
+     * Utility to keep mNextSelectedPosition and mNextSelectedRowId in sync
+     * @param position Intended value for mSelectedPosition the next time we go
+     * through layout
+     */
+    void setNextSelectedPositionInt(int position) {
+        mNextSelectedPosition = position;
+        mNextSelectedRowId = getItemIdAtPosition(position);
+        // If we are trying to sync to the selection, update that too
+        if (mNeedSync && mSyncMode == SYNC_SELECTED_POSITION && position >= 0) {
+            mSyncPosition = position;
+            mSyncRowId = mNextSelectedRowId;
+        }
+    }
+
+    /**
+     * Remember enough information to restore the screen state when the data has
+     * changed.
+     *
+     */
+    void rememberSyncState() {
+        if (getChildCount() > 0) {
+            mNeedSync = true;
+            mSyncHeight = mLayoutHeight;
+            if (mSelectedPosition >= 0) {
+                // Sync the selection state
+                View v = getChildAt(mSelectedPosition - mFirstPosition);
+                mSyncRowId = mNextSelectedRowId;
+                mSyncPosition = mNextSelectedPosition;
+                if (v != null) {
+                    mSpecificTop = v.getTop();
+                }
+                mSyncMode = SYNC_SELECTED_POSITION;
+            } else {
+                // Sync the based on the offset of the first view
+                View v = getChildAt(0);
+                T adapter = getAdapter();
+                if (mFirstPosition >= 0 && mFirstPosition < adapter.getCount()) {
+                    mSyncRowId = adapter.getItemId(mFirstPosition);
+                } else {
+                    mSyncRowId = NO_ID;
+                }
+                mSyncPosition = mFirstPosition;
+                if (v != null) {
+                    mSpecificTop = v.getTop();
+                }
+                mSyncMode = SYNC_FIRST_POSITION;
+            }
+        }
+    }
+}
diff --git a/core/java/android/widget/AlphabetIndexer.java b/core/java/android/widget/AlphabetIndexer.java
new file mode 100644
index 0000000..bbabaaa
--- /dev/null
+++ b/core/java/android/widget/AlphabetIndexer.java
@@ -0,0 +1,283 @@
+/*
+ * 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 android.widget;
+
+import android.database.Cursor;
+import android.database.DataSetObserver;
+import android.util.SparseIntArray;
+
+/**
+ * A helper class for adapters that implement the SectionIndexer interface.
+ * If the items in the adapter are sorted by simple alphabet-based sorting, then
+ * this class provides a way to do fast indexing of large lists using binary search.
+ * It caches the indices that have been determined through the binary search and also
+ * invalidates the cache if changes occur in the cursor.
+ * <p/>
+ * Your adapter is responsible for updating the cursor by calling {@link #setCursor} if the
+ * cursor changes. {@link #getPositionForSection} method does the binary search for the starting 
+ * index of a given section (alphabet).
+ * @hide pending API council approval
+ */
+public class AlphabetIndexer extends DataSetObserver implements SectionIndexer {
+
+    /**
+     * Cursor that is used by the adapter of the list view.
+     */
+    protected Cursor mDataCursor;
+    
+    /**
+     * The index of the cursor column that this list is sorted on.
+     */
+    protected int mColumnIndex;
+    
+    /**
+     * The string of characters that make up the indexing sections.
+     */
+    protected CharSequence mAlphabet;
+    
+    /**
+     * Cached length of the alphabet array.
+     */
+    private int mAlphabetLength;
+    
+    /**
+     * This contains a cache of the computed indices so far. It will get reset whenever
+     * the dataset changes or the cursor changes.
+     */
+    private SparseIntArray mAlphaMap;
+    
+    /**
+     * Use a collator to compare strings in a localized manner.
+     */
+    private java.text.Collator mCollator;
+    
+    /**
+     * The section array converted from the alphabet string.
+     */
+    private String[] mAlphabetArray;
+
+    /**
+     * Constructs the indexer.
+     * @param cursor the cursor containing the data set
+     * @param sortedColumnIndex the column number in the cursor that is sorted 
+     *        alphabetically
+     * @param alphabet string containing the alphabet, with space as the first character. 
+     *        For example, use the string " ABCDEFGHIJKLMNOPQRSTUVWXYZ" for English indexing.
+     *        The characters must be uppercase and be sorted in ascii/unicode order. Basically
+     *        characters in the alphabet will show up as preview letters.
+     */
+    public AlphabetIndexer(Cursor cursor, int sortedColumnIndex, CharSequence alphabet) {
+        mDataCursor = cursor;
+        mColumnIndex = sortedColumnIndex;
+        mAlphabet = alphabet;
+        mAlphabetLength = alphabet.length();
+        mAlphabetArray = new String[mAlphabetLength];
+        for (int i = 0; i < mAlphabetLength; i++) {
+            mAlphabetArray[i] = Character.toString(mAlphabet.charAt(i));
+        }
+        mAlphaMap = new SparseIntArray(mAlphabetLength);
+        if (cursor != null) {
+            cursor.registerDataSetObserver(this);
+        }
+        // Get a Collator for the current locale for string comparisons.
+        mCollator = java.text.Collator.getInstance();
+        mCollator.setStrength(java.text.Collator.PRIMARY);
+    }
+
+    /**
+     * Returns the section array constructed from the alphabet provided in the constructor.
+     * @return the section array
+     */
+    public Object[] getSections() {
+        return mAlphabetArray;
+    }
+    
+    /**
+     * Sets a new cursor as the data set and resets the cache of indices.
+     * @param cursor the new cursor to use as the data set
+     */
+    public void setCursor(Cursor cursor) {
+        if (mDataCursor != null) {
+            mDataCursor.unregisterDataSetObserver(this);
+        }
+        mDataCursor = cursor;
+        if (cursor != null) {
+            mDataCursor.registerDataSetObserver(this);
+        }
+        mAlphaMap.clear();
+    }
+
+    /**
+     * Default implementation compares the first character of word with letter.
+     */
+    protected int compare(String word, String letter) {
+        return mCollator.compare(word.substring(0, 1), letter);
+    }
+    
+    /**
+     * Performs a binary search or cache lookup to find the first row that
+     * matches a given section's starting letter.
+     * @param sectionIndex the section to search for
+     * @return the row index of the first occurrence, or the nearest next letter.
+     * For instance, if searching for "T" and no "T" is found, then the first
+     * row starting with "U" or any higher letter is returned. If there is no
+     * data following "T" at all, then the list size is returned.
+     */
+    public int getPositionForSection(int sectionIndex) {
+        final SparseIntArray alphaMap = mAlphaMap;
+        final Cursor cursor = mDataCursor;
+
+        if (cursor == null || mAlphabet == null) {
+            return 0;
+        }
+        
+        // Check bounds
+        if (sectionIndex <= 0) {
+            return 0;
+        }
+        if (sectionIndex >= mAlphabetLength) {
+            sectionIndex = mAlphabetLength - 1;
+        }
+
+        int savedCursorPos = cursor.getPosition();
+
+        int count = cursor.getCount();
+        int start = 0;
+        int end = count;
+        int pos;
+
+        char letter = mAlphabet.charAt(sectionIndex);
+        String targetLetter = Character.toString(letter);
+        int key = letter;
+        // Check map
+        if (Integer.MIN_VALUE != (pos = alphaMap.get(key, Integer.MIN_VALUE))) {
+            // Is it approximate? Using negative value to indicate that it's 
+            // an approximation and positive value when it is the accurate
+            // position.
+            if (pos < 0) {
+                pos = -pos;
+                end = pos;
+            } else {
+                // Not approximate, this is the confirmed start of section, return it
+                return pos;
+            }
+        }
+
+        // Do we have the position of the previous section?
+        if (sectionIndex > 0) {
+            int prevLetter =
+                    mAlphabet.charAt(sectionIndex - 1);
+            int prevLetterPos = alphaMap.get(prevLetter, Integer.MIN_VALUE);
+            if (prevLetterPos != Integer.MIN_VALUE) {
+                start = Math.abs(prevLetterPos);
+            }
+        }
+
+        // Now that we have a possibly optimized start and end, let's binary search
+
+        pos = (end + start) / 2;
+
+        while (pos < end) {
+            // Get letter at pos
+            cursor.moveToPosition(pos);
+            String curName = cursor.getString(mColumnIndex);
+            if (curName == null) {
+                if (pos == 0) {
+                    break;
+                } else {
+                    pos--;
+                    continue;
+                }
+            }
+            int diff = compare(curName, targetLetter);
+            if (diff != 0) {
+                // Commenting out approximation code because it doesn't work for certain 
+                // lists with custom comparators
+                // Enter approximation in hash if a better solution doesn't exist
+                // String startingLetter = Character.toString(getFirstLetter(curName));
+                // int startingLetterKey = startingLetter.charAt(0);
+                // int curPos = alphaMap.get(startingLetterKey, Integer.MIN_VALUE);
+                // if (curPos == Integer.MIN_VALUE || Math.abs(curPos) > pos) {
+                //     Negative pos indicates that it is an approximation
+                //     alphaMap.put(startingLetterKey, -pos);
+                // }
+                // if (mCollator.compare(startingLetter, targetLetter) < 0) {
+                if (diff < 0) {
+                    start = pos + 1;
+                    if (start >= count) {
+                        pos = count;
+                        break;
+                    }
+                } else {
+                    end = pos;
+                }
+            } else {
+                // They're the same, but that doesn't mean it's the start
+                if (start == pos) {
+                    // This is it
+                    break;
+                } else {
+                    // Need to go further lower to find the starting row
+                    end = pos;
+                }
+            }
+            pos = (start + end) / 2;
+        }
+        alphaMap.put(key, pos);
+        cursor.moveToPosition(savedCursorPos);
+        return pos;
+    }
+
+    /**
+     * Returns the section index for a given position in the list by querying the item
+     * and comparing it with all items in the section array.
+     */
+    public int getSectionForPosition(int position) {
+        int savedCursorPos = mDataCursor.getPosition();
+        mDataCursor.moveToPosition(position);
+        mDataCursor.moveToPosition(savedCursorPos);
+        String curName = mDataCursor.getString(mColumnIndex);
+        // Linear search, as there are only a few items in the section index
+        // Could speed this up later if it actually gets used.
+        for (int i = 0; i < mAlphabetLength; i++) {
+            char letter = mAlphabet.charAt(i);
+            String targetLetter = Character.toString(letter);
+            if (compare(curName, targetLetter) == 0) {
+                return i;
+            }
+        }
+        return 0; // Don't recognize the letter - falls under zero'th section    
+    }
+    
+    /*
+     * @hide
+     */
+    @Override
+    public void onChanged() {
+        super.onChanged();
+        mAlphaMap.clear();
+    }
+
+    /*
+     * @hide
+     */
+    @Override
+    public void onInvalidated() {
+        super.onInvalidated();
+        mAlphaMap.clear();
+    }
+}
diff --git a/core/java/android/widget/AnalogClock.java b/core/java/android/widget/AnalogClock.java
new file mode 100644
index 0000000..f847bc3
--- /dev/null
+++ b/core/java/android/widget/AnalogClock.java
@@ -0,0 +1,246 @@
+/*
+ * 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 android.widget;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.BroadcastReceiver;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.graphics.Canvas;
+import android.graphics.drawable.Drawable;
+import android.os.Handler;
+import android.text.format.Time;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.RemoteViews.RemoteView;
+
+import java.util.TimeZone;
+
+/**
+ * This widget display an analogic clock with two hands for hours and
+ * minutes.
+ */
+@RemoteView
+public class AnalogClock extends View {
+    private Time mCalendar;
+
+    private Drawable mHourHand;
+    private Drawable mMinuteHand;
+    private Drawable mDial;
+
+    private int mDialWidth;
+    private int mDialHeight;
+
+    private boolean mAttached;
+
+    private final Handler mHandler = new Handler();
+    private float mMinutes;
+    private float mHour;
+    private boolean mChanged;
+
+    public AnalogClock(Context context) {
+        this(context, null);
+    }
+
+    public AnalogClock(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public AnalogClock(Context context, AttributeSet attrs,
+                       int defStyle) {
+        super(context, attrs, defStyle);
+        Resources r = mContext.getResources();
+        TypedArray a =
+                context.obtainStyledAttributes(
+                        attrs, com.android.internal.R.styleable.AnalogClock, defStyle, 0);
+
+        mDial = a.getDrawable(com.android.internal.R.styleable.AnalogClock_dial);
+        if (mDial == null) {
+            mDial = r.getDrawable(com.android.internal.R.drawable.clock_dial);
+        }
+
+        mHourHand = a.getDrawable(com.android.internal.R.styleable.AnalogClock_hand_hour);
+        if (mHourHand == null) {
+            mHourHand = r.getDrawable(com.android.internal.R.drawable.clock_hand_hour);
+        }
+
+        mMinuteHand = a.getDrawable(com.android.internal.R.styleable.AnalogClock_hand_minute);
+        if (mMinuteHand == null) {
+            mMinuteHand = r.getDrawable(com.android.internal.R.drawable.clock_hand_minute);
+        }
+
+        mCalendar = new Time();
+
+        mDialWidth = mDial.getIntrinsicWidth();
+        mDialHeight = mDial.getIntrinsicHeight();
+    }
+
+    @Override
+    protected void onAttachedToWindow() {
+        super.onAttachedToWindow();
+
+        if (!mAttached) {
+            mAttached = true;
+            IntentFilter filter = new IntentFilter();
+
+            filter.addAction(Intent.ACTION_TIME_TICK);
+            filter.addAction(Intent.ACTION_TIME_CHANGED);
+            filter.addAction(Intent.ACTION_TIMEZONE_CHANGED);
+
+            getContext().registerReceiver(mIntentReceiver, filter, null, mHandler);
+        }
+
+        // NOTE: It's safe to do these after registering the receiver since the receiver always runs
+        // in the main thread, therefore the receiver can't run before this method returns.
+
+        // The time zone may have changed while the receiver wasn't registered, so update the Time
+        mCalendar = new Time();
+
+        // Make sure we update to the current time
+        onTimeChanged();
+    }
+
+    @Override
+    protected void onDetachedFromWindow() {
+        super.onDetachedFromWindow();
+        if (mAttached) {
+            getContext().unregisterReceiver(mIntentReceiver);
+            mAttached = false;
+        }
+    }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+
+        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
+        int widthSize =  MeasureSpec.getSize(widthMeasureSpec);
+        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
+        int heightSize =  MeasureSpec.getSize(heightMeasureSpec);
+
+        float hScale = 1.0f;
+        float vScale = 1.0f;
+
+        if (widthMode != MeasureSpec.UNSPECIFIED && widthSize < mDialWidth) {
+            hScale = (float) widthSize / (float) mDialWidth;
+        }
+
+        if (heightMode != MeasureSpec.UNSPECIFIED && heightSize < mDialHeight) {
+            vScale = (float )heightSize / (float) mDialHeight;
+        }
+
+        float scale = Math.min(hScale, vScale);
+
+        setMeasuredDimension(resolveSize((int) (mDialWidth * scale), widthMeasureSpec),
+                resolveSize((int) (mDialHeight * scale), heightMeasureSpec));
+    }
+
+    @Override
+    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
+        super.onSizeChanged(w, h, oldw, oldh);
+        mChanged = true;
+    }
+
+    @Override
+    protected void onDraw(Canvas canvas) {
+        super.onDraw(canvas);
+
+        boolean changed = mChanged;
+        if (changed) {
+            mChanged = false;
+        }
+
+        int availableWidth = mRight - mLeft;
+        int availableHeight = mBottom - mTop;
+
+        int x = availableWidth / 2;
+        int y = availableHeight / 2;
+
+        final Drawable dial = mDial;
+        int w = dial.getIntrinsicWidth();
+        int h = dial.getIntrinsicHeight();
+
+        boolean scaled = false;
+
+        if (availableWidth < w || availableHeight < h) {
+            scaled = true;
+            float scale = Math.min((float) availableWidth / (float) w,
+                                   (float) availableHeight / (float) h);
+            canvas.save();
+            canvas.scale(scale, scale, x, y);
+        }
+
+        if (changed) {
+            dial.setBounds(x - (w / 2), y - (h / 2), x + (w / 2), y + (h / 2));
+        }
+        dial.draw(canvas);
+
+        canvas.save();
+        canvas.rotate(mHour / 12.0f * 360.0f, x, y);
+        final Drawable hourHand = mHourHand;
+        if (changed) {
+            w = hourHand.getIntrinsicWidth();
+            h = hourHand.getIntrinsicHeight();
+            hourHand.setBounds(x - (w / 2), y - (h / 2), x + (w / 2), y + (h / 2));
+        }
+        hourHand.draw(canvas);
+        canvas.restore();
+
+        canvas.save();
+        canvas.rotate(mMinutes / 60.0f * 360.0f, x, y);
+
+        final Drawable minuteHand = mMinuteHand;
+        if (changed) {
+            w = minuteHand.getIntrinsicWidth();
+            h = minuteHand.getIntrinsicHeight();
+            minuteHand.setBounds(x - (w / 2), y - (h / 2), x + (w / 2), y + (h / 2));
+        }
+        minuteHand.draw(canvas);
+        canvas.restore();
+
+        if (scaled) {
+            canvas.restore();
+        }
+    }
+
+    private void onTimeChanged() {
+        mCalendar.setToNow();
+
+        int hour = mCalendar.hour;
+        int minute = mCalendar.minute;
+        int second = mCalendar.second;
+
+        mMinutes = minute + second / 60.0f;
+        mHour = hour + mMinutes / 60.0f;
+        mChanged = true;
+    }
+
+    private final BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            if (intent.getAction().equals(Intent.ACTION_TIMEZONE_CHANGED)) {
+                String tz = intent.getStringExtra("time-zone");
+                mCalendar = new Time(TimeZone.getTimeZone(tz).getID());
+            }
+
+            onTimeChanged();
+            
+            invalidate();
+        }
+    };
+}
diff --git a/core/java/android/widget/AppSecurityPermissions.java b/core/java/android/widget/AppSecurityPermissions.java
new file mode 100755
index 0000000..5fa00e7
--- /dev/null
+++ b/core/java/android/widget/AppSecurityPermissions.java
@@ -0,0 +1,504 @@
+/*
+**
+** Copyright 2007, 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 android.widget;
+
+import com.android.internal.R;
+import android.content.Context;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageParser;
+import android.content.pm.PermissionGroupInfo;
+import android.content.pm.PermissionInfo;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.graphics.drawable.Drawable;
+import android.net.Uri;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+
+import java.io.File;
+import java.text.Collator;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * This class contains the SecurityPermissions view implementation.
+ * Initially the package's advanced or dangerous security permissions
+ * are displayed under categorized
+ * groups. Clicking on the additional permissions presents
+ * extended information consisting of all groups and permissions.
+ * To use this view define a LinearLayout or any ViewGroup and add this
+ * view by instantiating AppSecurityPermissions and invoking getPermissionsView.
+ * 
+ * {@hide}
+ */
+public class AppSecurityPermissions  implements View.OnClickListener {
+
+    private enum State {
+        NO_PERMS,
+        DANGEROUS_ONLY,
+        NORMAL_ONLY,
+        BOTH
+    }
+
+    private final static String TAG = "AppSecurityPermissions";
+    private boolean localLOGV = false;
+    private Context mContext;
+    private LayoutInflater mInflater;
+    private PackageManager mPm;
+    private LinearLayout mPermsView;
+    private Map<String, String> mDangerousMap;
+    private Map<String, String> mNormalMap;
+    private List<PermissionInfo> mPermsList;
+    private String mDefaultGrpLabel;
+    private String mDefaultGrpName="DefaultGrp";
+    private String mPermFormat;
+    private Drawable mNormalIcon;
+    private Drawable mDangerousIcon;
+    private boolean mExpanded;
+    private Drawable mShowMaxIcon;
+    private Drawable mShowMinIcon;
+    private View mShowMore;
+    private TextView mShowMoreText;
+    private ImageView mShowMoreIcon;
+    private State mCurrentState;
+    private LinearLayout mNonDangerousList;
+    private LinearLayout mDangerousList;
+    private HashMap<String, CharSequence> mGroupLabelCache;
+    private View mNoPermsView;
+    
+    public AppSecurityPermissions(Context context, List<PermissionInfo> permList) {
+        mContext = context;
+        mPm = mContext.getPackageManager();
+        mPermsList = permList;
+    }
+    
+    public AppSecurityPermissions(Context context, String packageName) {
+        mContext = context;
+        mPm = mContext.getPackageManager();
+        mPermsList = new ArrayList<PermissionInfo>();
+        Set<PermissionInfo> permSet = new HashSet<PermissionInfo>();
+        PackageInfo pkgInfo;
+        try {
+            pkgInfo = mPm.getPackageInfo(packageName, PackageManager.GET_PERMISSIONS);
+        } catch (NameNotFoundException e) {
+            Log.w(TAG, "Could'nt retrieve permissions for package:"+packageName);
+            return;
+        }
+        // Extract all user permissions
+        if((pkgInfo.applicationInfo != null) && (pkgInfo.applicationInfo.uid != -1)) {
+            getAllUsedPermissions(pkgInfo.applicationInfo.uid, permSet);
+        }
+        for(PermissionInfo tmpInfo : permSet) {
+            mPermsList.add(tmpInfo);
+        }
+    }
+    
+    public AppSecurityPermissions(Context context, PackageParser.Package pkg) {
+        mContext = context;
+        mPm = mContext.getPackageManager();
+        mPermsList = new ArrayList<PermissionInfo>();
+        Set<PermissionInfo> permSet = new HashSet<PermissionInfo>();
+        if(pkg == null) {
+            return;
+        }
+        // Extract shared user permissions if any
+        if(pkg.mSharedUserId != null) {
+            int sharedUid;
+            try {
+                sharedUid = mPm.getUidForSharedUser(pkg.mSharedUserId);
+            } catch (NameNotFoundException e) {
+                Log.w(TAG, "Could'nt retrieve shared user id for:"+pkg.packageName);
+                return;
+            }
+            getAllUsedPermissions(sharedUid, permSet);
+        } else {
+            ArrayList<String> strList = pkg.requestedPermissions;
+            int size;
+            if((strList == null) || ((size = strList.size()) == 0)) {
+                return;
+            }
+            // Extract permissions defined in current package
+            extractPerms(strList.toArray(new String[size]), permSet);
+        }
+        for(PermissionInfo tmpInfo : permSet) {
+            mPermsList.add(tmpInfo);
+        }
+    }
+    
+    public PackageParser.Package getPackageInfo(Uri packageURI) {
+        final String archiveFilePath = packageURI.getPath();
+        PackageParser packageParser = new PackageParser(archiveFilePath);
+        File sourceFile = new File(archiveFilePath);
+        DisplayMetrics metrics = new DisplayMetrics();
+        metrics.setToDefaults();
+        return packageParser.parsePackage(sourceFile, archiveFilePath, metrics, 0);
+    }
+    
+    private void getAllUsedPermissions(int sharedUid, Set<PermissionInfo> permSet) {
+        String sharedPkgList[] = mPm.getPackagesForUid(sharedUid);
+        if(sharedPkgList == null || (sharedPkgList.length == 0)) {
+            return;
+        }
+        for(String sharedPkg : sharedPkgList) {
+            getPermissionsForPackage(sharedPkg, permSet);
+        }
+    }
+    
+    private void getPermissionsForPackage(String packageName, 
+            Set<PermissionInfo> permSet) {
+        PackageInfo pkgInfo;
+        try {
+            pkgInfo = mPm.getPackageInfo(packageName, PackageManager.GET_PERMISSIONS);
+        } catch (NameNotFoundException e) {
+            Log.w(TAG, "Could'nt retrieve permissions for package:"+packageName);
+            return;
+        }
+        if(pkgInfo == null) {
+            return;
+        }
+        String strList[] = pkgInfo.requestedPermissions;
+        if(strList == null) {
+            return;
+        }
+        extractPerms(strList, permSet);
+    }
+    
+    private void extractPerms(String strList[], Set<PermissionInfo> permSet) {
+        if((strList == null) || (strList.length == 0)) {
+            return;
+        }
+        for(String permName:strList) {
+            try {
+                PermissionInfo tmpPermInfo = mPm.getPermissionInfo(permName, 0);
+                if(tmpPermInfo != null) {
+                    permSet.add(tmpPermInfo);
+                }
+            } catch (NameNotFoundException e) {
+                Log.i(TAG, "Ignoring unknown permission:"+permName);
+            }
+        }
+    }
+    
+    public int getPermissionCount() {
+        return mPermsList.size();
+    }
+
+    public View getPermissionsView() {
+        
+        mInflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+        mPermsView = (LinearLayout) mInflater.inflate(R.layout.app_perms_summary, null);
+        mShowMore = mPermsView.findViewById(R.id.show_more);
+        mShowMoreIcon = (ImageView) mShowMore.findViewById(R.id.show_more_icon);
+        mShowMoreText = (TextView) mShowMore.findViewById(R.id.show_more_text);
+        mDangerousList = (LinearLayout) mPermsView.findViewById(R.id.dangerous_perms_list);
+        mNonDangerousList = (LinearLayout) mPermsView.findViewById(R.id.non_dangerous_perms_list);
+        mNoPermsView = mPermsView.findViewById(R.id.no_permissions);
+
+        // Set up the LinearLayout that acts like a list item.
+        mShowMore.setClickable(true);
+        mShowMore.setOnClickListener(this);
+        mShowMore.setFocusable(true);
+        mShowMore.setBackgroundResource(android.R.drawable.list_selector_background);
+
+        // Pick up from framework resources instead.
+        mDefaultGrpLabel = mContext.getString(R.string.default_permission_group);
+        mPermFormat = mContext.getString(R.string.permissions_format);
+        mNormalIcon = mContext.getResources().getDrawable(R.drawable.ic_text_dot);
+        mDangerousIcon = mContext.getResources().getDrawable(R.drawable.ic_bullet_key_permission);
+        mShowMaxIcon = mContext.getResources().getDrawable(R.drawable.expander_ic_maximized);
+        mShowMinIcon = mContext.getResources().getDrawable(R.drawable.expander_ic_minimized);
+        
+        // Set permissions view
+        setPermissions(mPermsList);
+        return mPermsView;
+    }
+
+    /**
+     * Canonicalizes the group description before it is displayed to the user.
+     *
+     * TODO check for internationalization issues remove trailing '.' in str1
+     */
+    private String canonicalizeGroupDesc(String groupDesc) {
+        if ((groupDesc == null) || (groupDesc.length() == 0)) {
+            return null;
+        }
+        // Both str1 and str2 are non-null and are non-zero in size.
+        int len = groupDesc.length();
+        if(groupDesc.charAt(len-1) == '.') {
+            groupDesc = groupDesc.substring(0, len-1);
+        }
+        return groupDesc;
+    }
+
+    /**
+     * Utility method that concatenates two strings defined by mPermFormat.
+     * a null value is returned if both str1 and str2 are null, if one of the strings
+     * is null the other non null value is returned without formatting
+     * this is to placate initial error checks
+     */
+    private String formatPermissions(String groupDesc, CharSequence permDesc) {
+        if(groupDesc == null) {
+            if(permDesc == null) {
+                return null;
+            }
+            return permDesc.toString();
+        }
+        groupDesc = canonicalizeGroupDesc(groupDesc);
+        if(permDesc == null) {
+            return groupDesc;
+        }
+        // groupDesc and permDesc are non null
+        return String.format(mPermFormat, groupDesc, permDesc.toString());
+    }
+
+    private CharSequence getGroupLabel(String grpName) {
+        if (grpName == null) {
+            //return default label
+            return mDefaultGrpLabel;
+        }
+        CharSequence cachedLabel = mGroupLabelCache.get(grpName);
+        if (cachedLabel != null) {
+            return cachedLabel;
+        }
+        PermissionGroupInfo pgi;
+        try {
+            pgi = mPm.getPermissionGroupInfo(grpName, 0);
+        } catch (NameNotFoundException e) {
+            Log.i(TAG, "Invalid group name:" + grpName);
+            return null;
+        }
+        CharSequence label = pgi.loadLabel(mPm).toString();
+        mGroupLabelCache.put(grpName, label);
+        return label;
+    }
+
+    /**
+     * Utility method that displays permissions from a map containing group name and
+     * list of permission descriptions.
+     */
+    private void displayPermissions(boolean dangerous) {
+        Map<String, String> permInfoMap = dangerous ? mDangerousMap : mNormalMap;
+        LinearLayout permListView = dangerous ? mDangerousList : mNonDangerousList;
+        permListView.removeAllViews();
+
+        Set<String> permInfoStrSet = permInfoMap.keySet();
+        for (String loopPermGrpInfoStr : permInfoStrSet) {
+            CharSequence grpLabel = getGroupLabel(loopPermGrpInfoStr);
+            //guaranteed that grpLabel wont be null since permissions without groups
+            //will belong to the default group
+            if(localLOGV) Log.i(TAG, "Adding view group:" + grpLabel + ", desc:"
+                    + permInfoMap.get(loopPermGrpInfoStr));
+            permListView.addView(getPermissionItemView(grpLabel,
+                    permInfoMap.get(loopPermGrpInfoStr), dangerous));
+        }
+    }
+
+    private void displayNoPermissions() {
+        mNoPermsView.setVisibility(View.VISIBLE);
+    }
+
+    private View getPermissionItemView(CharSequence grpName, String permList,
+            boolean dangerous) {
+        View permView = mInflater.inflate(R.layout.app_permission_item, null);
+        Drawable icon = dangerous ? mDangerousIcon : mNormalIcon;
+        int grpColor = dangerous ? R.color.perms_dangerous_grp_color :
+            R.color.perms_normal_grp_color;
+        int permColor = dangerous ? R.color.perms_dangerous_perm_color :
+            R.color.perms_normal_perm_color;
+
+        TextView permGrpView = (TextView) permView.findViewById(R.id.permission_group);
+        TextView permDescView = (TextView) permView.findViewById(R.id.permission_list);
+        permGrpView.setTextColor(mContext.getResources().getColor(grpColor));
+        permDescView.setTextColor(mContext.getResources().getColor(permColor));
+
+        ImageView imgView = (ImageView)permView.findViewById(R.id.perm_icon);
+        imgView.setImageDrawable(icon);
+        if(grpName != null) {
+            permGrpView.setText(grpName);
+            permDescView.setText(permList);
+        } else {
+            permGrpView.setText(permList);
+            permDescView.setVisibility(View.GONE);
+        }
+        return permView;
+    }
+
+    private void showPermissions() {
+
+        switch(mCurrentState) {
+        case NO_PERMS:
+            displayNoPermissions();
+            break;
+
+        case DANGEROUS_ONLY:
+            displayPermissions(true);
+            break;
+
+        case NORMAL_ONLY:
+            displayPermissions(false);
+            break;
+
+        case BOTH:
+            displayPermissions(true);
+            if (mExpanded) {
+                displayPermissions(false);
+                mShowMoreIcon.setImageDrawable(mShowMaxIcon);
+                mShowMoreText.setText(R.string.perms_hide);
+                mNonDangerousList.setVisibility(View.VISIBLE);
+            } else {
+                mShowMoreIcon.setImageDrawable(mShowMinIcon);
+                mShowMoreText.setText(R.string.perms_show_all);
+                mNonDangerousList.setVisibility(View.GONE);
+            }
+            mShowMore.setVisibility(View.VISIBLE);
+            break;
+        }
+    }
+    
+    private boolean isDisplayablePermission(PermissionInfo pInfo) {
+        if(pInfo.protectionLevel == PermissionInfo.PROTECTION_DANGEROUS ||
+                pInfo.protectionLevel == PermissionInfo.PROTECTION_NORMAL) {
+            return true;
+        }
+        return false;
+    }
+    
+    /*
+     * Utility method that aggregates all permission descriptions categorized by group
+     * Say group1 has perm11, perm12, perm13, the group description will be
+     * perm11_Desc, perm12_Desc, perm13_Desc
+     */
+    private void aggregateGroupDescs(
+            Map<String, List<PermissionInfo> > map, Map<String, String> retMap) {
+        if(map == null) {
+            return;
+        }
+        if(retMap == null) {
+           return;
+        }
+        Set<String> grpNames = map.keySet();
+        Iterator<String> grpNamesIter = grpNames.iterator();
+        while(grpNamesIter.hasNext()) {
+            String grpDesc = null;
+            String grpNameKey = grpNamesIter.next();
+            List<PermissionInfo> grpPermsList = map.get(grpNameKey);
+            if(grpPermsList == null) {
+                continue;
+            }
+            for(PermissionInfo permInfo: grpPermsList) {
+                CharSequence permDesc = permInfo.loadLabel(mPm);
+                grpDesc = formatPermissions(grpDesc, permDesc);
+            }
+            // Insert grpDesc into map
+            if(grpDesc != null) {
+                if(localLOGV) Log.i(TAG, "Group:"+grpNameKey+" description:"+grpDesc.toString());
+                retMap.put(grpNameKey, grpDesc.toString());
+            }
+        }
+    }
+    
+    private static class PermissionInfoComparator implements Comparator<PermissionInfo> {
+        private PackageManager mPm;
+        private final Collator sCollator = Collator.getInstance();
+        PermissionInfoComparator(PackageManager pm) {
+            mPm = pm;
+        }
+        public final int compare(PermissionInfo a, PermissionInfo b) {
+            CharSequence sa = a.loadLabel(mPm);
+            CharSequence sb = b.loadLabel(mPm);
+            return sCollator.compare(sa, sb);
+        }
+    }
+    
+    private void setPermissions(List<PermissionInfo> permList) {
+        mGroupLabelCache = new HashMap<String, CharSequence>();
+        //add the default label so that uncategorized permissions can go here
+        mGroupLabelCache.put(mDefaultGrpName, mDefaultGrpLabel);
+        
+        // Map containing group names and a list of permissions under that group
+        // categorized as dangerous
+        mDangerousMap = new HashMap<String, String>();
+        // Map containing group names and a list of permissions under that group
+        // categorized as normal
+        mNormalMap = new HashMap<String, String>();
+        
+        // Additional structures needed to ensure that permissions are unique under 
+        // each group
+        Map<String, List<PermissionInfo>> dangerousMap = 
+            new HashMap<String,  List<PermissionInfo>>();
+        Map<String, List<PermissionInfo> > normalMap = 
+            new HashMap<String,  List<PermissionInfo>>();
+        PermissionInfoComparator permComparator = new PermissionInfoComparator(mPm);
+        
+        if (permList != null) {
+            // First pass to group permissions
+            for (PermissionInfo pInfo : permList) {
+                if(localLOGV) Log.i(TAG, "Processing permission:"+pInfo.name);
+                if(!isDisplayablePermission(pInfo)) {
+                    if(localLOGV) Log.i(TAG, "Permission:"+pInfo.name+" is not displayable");
+                    continue;
+                }
+                Map<String, List<PermissionInfo> > permInfoMap =
+                    (pInfo.protectionLevel == PermissionInfo.PROTECTION_DANGEROUS) ?
+                            dangerousMap : normalMap;
+                String grpName = (pInfo.group == null) ? mDefaultGrpName : pInfo.group;
+                if(localLOGV) Log.i(TAG, "Permission:"+pInfo.name+" belongs to group:"+grpName);
+                List<PermissionInfo> grpPermsList = permInfoMap.get(grpName);
+                if(grpPermsList == null) {
+                    grpPermsList = new ArrayList<PermissionInfo>();
+                    permInfoMap.put(grpName, grpPermsList);
+                    grpPermsList.add(pInfo);
+                } else {
+                    int idx = Collections.binarySearch(grpPermsList, pInfo, permComparator);
+                    if(localLOGV) Log.i(TAG, "idx="+idx+", list.size="+grpPermsList.size());
+                    if (idx < 0) {
+                        idx = -idx-1;
+                        grpPermsList.add(idx, pInfo);
+                    }
+                }
+            }
+            // Second pass to actually form the descriptions
+            // Look at dangerous permissions first
+            aggregateGroupDescs(dangerousMap, mDangerousMap);
+            aggregateGroupDescs(normalMap, mNormalMap);
+        }
+
+        mCurrentState = State.NO_PERMS;
+        if(mDangerousMap.size() > 0) {
+            mCurrentState = (mNormalMap.size() > 0) ? State.BOTH : State.DANGEROUS_ONLY;
+        } else if(mNormalMap.size() > 0) {
+            mCurrentState = State.NORMAL_ONLY;
+        }
+        if(localLOGV) Log.i(TAG, "mCurrentState=" + mCurrentState);
+        showPermissions();
+    }
+
+    public void onClick(View v) {
+        if(localLOGV) Log.i(TAG, "mExpanded="+mExpanded);
+        mExpanded = !mExpanded;
+        showPermissions();
+    }
+}
diff --git a/core/java/android/widget/ArrayAdapter.java b/core/java/android/widget/ArrayAdapter.java
new file mode 100644
index 0000000..c65a3ce
--- /dev/null
+++ b/core/java/android/widget/ArrayAdapter.java
@@ -0,0 +1,455 @@
+/*
+ * 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 android.widget;
+
+import android.content.Context;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * A ListAdapter that manages a ListView backed by an array of arbitrary
+ * objects.  By default this class expects that the provided resource id references
+ * a single TextView.  If you want to use a more complex layout, use the constructors that
+ * also takes a field id.  That field id should reference a TextView in the larger layout
+ * resource.
+ *
+ * However the TextView is referenced, it will be filled with the toString() of each object in
+ * the array. You can add lists or arrays of custom objects. Override the toString() method
+ * of your objects to determine what text will be displayed for the item in the list.
+ *
+ * To use something other than TextViews for the array display, for instance, ImageViews,
+ * or to have some of data besides toString() results fill the views,
+ * override {@link #getView(int, View, ViewGroup)} to return the type of view you want.
+ */
+public class ArrayAdapter<T> extends BaseAdapter implements Filterable {
+    /**
+     * Contains the list of objects that represent the data of this ArrayAdapter.
+     * The content of this list is referred to as "the array" in the documentation.
+     */
+    private List<T> mObjects;
+
+    /**
+     * Lock used to modify the content of {@link #mObjects}. Any write operation
+     * performed on the array should be synchronized on this lock. This lock is also
+     * used by the filter (see {@link #getFilter()} to make a synchronized copy of
+     * the original array of data.
+     */
+    private final Object mLock = new Object();
+
+    /**
+     * The resource indicating what views to inflate to display the content of this
+     * array adapter.
+     */
+    private int mResource;
+
+    /**
+     * The resource indicating what views to inflate to display the content of this
+     * array adapter in a drop down widget.
+     */
+    private int mDropDownResource;
+
+    /**
+     * If the inflated resource is not a TextView, {@link #mFieldId} is used to find
+     * a TextView inside the inflated views hierarchy. This field must contain the
+     * identifier that matches the one defined in the resource file.
+     */
+    private int mFieldId = 0;
+
+    /**
+     * Indicates whether or not {@link #notifyDataSetChanged()} must be called whenever
+     * {@link #mObjects} is modified.
+     */
+    private boolean mNotifyOnChange = true;
+
+    private Context mContext;    
+
+    private ArrayList<T> mOriginalValues;
+    private ArrayFilter mFilter;
+
+    private LayoutInflater mInflater;
+
+    /**
+     * Constructor
+     *
+     * @param context The current context.
+     * @param textViewResourceId The resource ID for a layout file containing a TextView to use when
+     *                 instantiating views.
+     */
+    public ArrayAdapter(Context context, int textViewResourceId) {
+        init(context, textViewResourceId, 0, new ArrayList<T>());
+    }
+
+    /**
+     * Constructor
+     *
+     * @param context The current context.
+     * @param resource The resource ID for a layout file containing a layout to use when
+     *                 instantiating views.
+     * @param textViewResourceId The id of the TextView within the layout resource to be populated
+     */
+    public ArrayAdapter(Context context, int resource, int textViewResourceId) {
+        init(context, resource, textViewResourceId, new ArrayList<T>());
+    }
+
+    /**
+     * Constructor
+     *
+     * @param context The current context.
+     * @param textViewResourceId The resource ID for a layout file containing a TextView to use when
+     *                 instantiating views.
+     * @param objects The objects to represent in the ListView.
+     */
+    public ArrayAdapter(Context context, int textViewResourceId, T[] objects) {
+        init(context, textViewResourceId, 0, Arrays.asList(objects));
+    }
+
+    /**
+     * Constructor
+     *
+     * @param context The current context.
+     * @param resource The resource ID for a layout file containing a layout to use when
+     *                 instantiating views.
+     * @param textViewResourceId The id of the TextView within the layout resource to be populated
+     * @param objects The objects to represent in the ListView.
+     */
+    public ArrayAdapter(Context context, int resource, int textViewResourceId, T[] objects) {
+        init(context, resource, textViewResourceId, Arrays.asList(objects));
+    }
+
+    /**
+     * Constructor
+     *
+     * @param context The current context.
+     * @param textViewResourceId The resource ID for a layout file containing a TextView to use when
+     *                 instantiating views.
+     * @param objects The objects to represent in the ListView.
+     */
+    public ArrayAdapter(Context context, int textViewResourceId, List<T> objects) {
+        init(context, textViewResourceId, 0, objects);
+    }
+
+    /**
+     * Constructor
+     *
+     * @param context The current context.
+     * @param resource The resource ID for a layout file containing a layout to use when
+     *                 instantiating views.
+     * @param textViewResourceId The id of the TextView within the layout resource to be populated
+     * @param objects The objects to represent in the ListView.
+     */
+    public ArrayAdapter(Context context, int resource, int textViewResourceId, List<T> objects) {
+        init(context, resource, textViewResourceId, objects);
+    }
+
+    /**
+     * Adds the specified object at the end of the array.
+     *
+     * @param object The object to add at the end of the array.
+     */
+    public void add(T object) {
+        if (mOriginalValues != null) {
+            synchronized (mLock) {
+                mOriginalValues.add(object);
+                if (mNotifyOnChange) notifyDataSetChanged();
+            }
+        } else {
+            mObjects.add(object);
+            if (mNotifyOnChange) notifyDataSetChanged();
+        }
+    }
+
+    /**
+     * Inserts the specified object at the specified index in the array.
+     *
+     * @param object The object to insert into the array.
+     * @param index The index at which the object must be inserted.
+     */
+    public void insert(T object, int index) {
+        if (mOriginalValues != null) {
+            synchronized (mLock) {
+                mOriginalValues.add(index, object);
+                if (mNotifyOnChange) notifyDataSetChanged();
+            }
+        } else {
+            mObjects.add(index, object);
+            if (mNotifyOnChange) notifyDataSetChanged();
+        }
+    }
+
+    /**
+     * Removes the specified object from the array.
+     *
+     * @param object The object to remove.
+     */
+    public void remove(T object) {
+        if (mOriginalValues != null) {
+            synchronized (mLock) {
+                mOriginalValues.remove(object);
+            }
+        } else {
+            mObjects.remove(object);
+        }
+        if (mNotifyOnChange) notifyDataSetChanged();
+    }
+
+    /**
+     * Remove all elements from the list.
+     */
+    public void clear() {
+        if (mOriginalValues != null) {
+            synchronized (mLock) {
+                mOriginalValues.clear();
+            }
+        } else {
+            mObjects.clear();
+        }
+        if (mNotifyOnChange) notifyDataSetChanged();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void notifyDataSetChanged() {
+        super.notifyDataSetChanged();
+        mNotifyOnChange = true;
+    }
+
+    /**
+     * Control whether methods that change the list ({@link #add},
+     * {@link #insert}, {@link #remove}, {@link #clear}) automatically call
+     * {@link #notifyDataSetChanged}.  If set to false, caller must
+     * manually call notifyDataSetChanged() to have the changes
+     * reflected in the attached view.
+     *
+     * The default is true, and calling notifyDataSetChanged()
+     * resets the flag to true.
+     *
+     * @param notifyOnChange if true, modifications to the list will
+     *                       automatically call {@link
+     *                       #notifyDataSetChanged}
+     */
+    public void setNotifyOnChange(boolean notifyOnChange) {
+        mNotifyOnChange = notifyOnChange;
+    }
+
+    private void init(Context context, int resource, int textViewResourceId, List<T> objects) {
+        mContext = context;
+        mInflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+        mResource = mDropDownResource = resource;
+        mObjects = objects;
+        mFieldId = textViewResourceId;
+    }
+
+    /**
+     * Returns the context associated with this array adapter. The context is used
+     * to create views from the resource passed to the constructor.
+     *
+     * @return The Context associated with this adapter.
+     */
+    public Context getContext() {
+        return mContext;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public int getCount() {
+        return mObjects.size();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public T getItem(int position) {
+        return mObjects.get(position);
+    }
+
+    /**
+     * Returns the position of the specified item in the array.
+     *
+     * @param item The item to retrieve the position of.
+     *
+     * @return The position of the specified item.
+     */
+    public int getPosition(T item) {
+        return mObjects.indexOf(item);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public long getItemId(int position) {
+        return position;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public View getView(int position, View convertView, ViewGroup parent) {
+        return createViewFromResource(position, convertView, parent, mResource);
+    }
+
+    private View createViewFromResource(int position, View convertView, ViewGroup parent,
+            int resource) {
+        View view;
+        TextView text;
+
+        if (convertView == null) {
+            view = mInflater.inflate(resource, parent, false);
+        } else {
+            view = convertView;
+        }
+
+        try {
+            if (mFieldId == 0) {
+                //  If no custom field is assigned, assume the whole resource is a TextView
+                text = (TextView) view;
+            } else {
+                //  Otherwise, find the TextView field within the layout
+                text = (TextView) view.findViewById(mFieldId);
+            }
+        } catch (ClassCastException e) {
+            Log.e("ArrayAdapter", "You must supply a resource ID for a TextView");
+            throw new IllegalStateException(
+                    "ArrayAdapter requires the resource ID to be a TextView", e);
+        }
+
+        text.setText(getItem(position).toString());
+
+        return view;
+    }
+
+    /**
+     * <p>Sets the layout resource to create the drop down views.</p>
+     *
+     * @param resource the layout resource defining the drop down views
+     * @see #getDropDownView(int, android.view.View, android.view.ViewGroup)
+     */
+    public void setDropDownViewResource(int resource) {
+        this.mDropDownResource = resource;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public View getDropDownView(int position, View convertView, ViewGroup parent) {
+        return createViewFromResource(position, convertView, parent, mDropDownResource);
+    }
+
+    /**
+     * Creates a new ArrayAdapter from external resources. The content of the array is
+     * obtained through {@link android.content.res.Resources#getTextArray(int)}.
+     *
+     * @param context The application's environment.
+     * @param textArrayResId The identifier of the array to use as the data source.
+     * @param textViewResId The identifier of the layout used to create views.
+     *
+     * @return An ArrayAdapter<CharSequence>.
+     */
+    public static ArrayAdapter<CharSequence> createFromResource(Context context,
+            int textArrayResId, int textViewResId) {
+        CharSequence[] strings = context.getResources().getTextArray(textArrayResId);
+        return new ArrayAdapter<CharSequence>(context, textViewResId, strings);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public Filter getFilter() {
+        if (mFilter == null) {
+            mFilter = new ArrayFilter();
+        }
+        return mFilter;
+    }
+
+    /**
+     * <p>An array filter constrains the content of the array adapter with
+     * a prefix. Each item that does not start with the supplied prefix
+     * is removed from the list.</p>
+     */
+    private class ArrayFilter extends Filter {
+        @Override
+        protected FilterResults performFiltering(CharSequence prefix) {
+            FilterResults results = new FilterResults();
+
+            if (mOriginalValues == null) {
+                synchronized (mLock) {
+                    mOriginalValues = new ArrayList<T>(mObjects);
+                }
+            }
+
+            if (prefix == null || prefix.length() == 0) {
+                synchronized (mLock) {
+                    ArrayList<T> list = new ArrayList<T>(mOriginalValues);
+                    results.values = list;
+                    results.count = list.size();
+                }
+            } else {
+                String prefixString = prefix.toString().toLowerCase();
+
+                final ArrayList<T> values = mOriginalValues;
+                final int count = values.size();
+
+                final ArrayList<T> newValues = new ArrayList<T>(count);
+
+                for (int i = 0; i < count; i++) {
+                    final T value = values.get(i);
+                    final String valueText = value.toString().toLowerCase();
+
+                    // First match against the whole, non-splitted value
+                    if (valueText.startsWith(prefixString)) {
+                        newValues.add(value);
+                    } else {
+                        final String[] words = valueText.split(" ");
+                        final int wordCount = words.length;
+
+                        for (int k = 0; k < wordCount; k++) {
+                            if (words[k].startsWith(prefixString)) {
+                                newValues.add(value);
+                                break;
+                            }
+                        }
+                    }
+                }
+
+                results.values = newValues;
+                results.count = newValues.size();
+            }
+
+            return results;
+        }
+
+        @Override
+        protected void publishResults(CharSequence constraint, FilterResults results) {
+            //noinspection unchecked
+            mObjects = (List<T>) results.values;
+            if (results.count > 0) {
+                notifyDataSetChanged();
+            } else {
+                notifyDataSetInvalidated();
+            }
+        }
+    }
+}
diff --git a/core/java/android/widget/AutoCompleteTextView.java b/core/java/android/widget/AutoCompleteTextView.java
new file mode 100644
index 0000000..0c1c72a
--- /dev/null
+++ b/core/java/android/widget/AutoCompleteTextView.java
@@ -0,0 +1,1164 @@
+/*
+ * Copyright (C) 2007 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 android.widget;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.text.Editable;
+import android.text.Selection;
+import android.text.TextUtils;
+import android.text.TextWatcher;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.LayoutInflater;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.inputmethod.CompletionInfo;
+import android.view.inputmethod.InputMethodManager;
+import android.view.inputmethod.EditorInfo;
+
+import com.android.internal.R;
+
+
+/**
+ * <p>An editable text view that shows completion suggestions automatically
+ * while the user is typing. The list of suggestions is displayed in a drop
+ * down menu from which the user can choose an item to replace the content
+ * of the edit box with.</p>
+ *
+ * <p>The drop down can be dismissed at any time by pressing the back key or,
+ * if no item is selected in the drop down, by pressing the enter/dpad center
+ * key.</p>
+ *
+ * <p>The list of suggestions is obtained from a data adapter and appears
+ * only after a given number of characters defined by
+ * {@link #getThreshold() the threshold}.</p>
+ *
+ * <p>The following code snippet shows how to create a text view which suggests
+ * various countries names while the user is typing:</p>
+ *
+ * <pre class="prettyprint">
+ * public class CountriesActivity extends Activity {
+ *     protected void onCreate(Bundle icicle) {
+ *         super.onCreate(icicle);
+ *         setContentView(R.layout.countries);
+ *
+ *         ArrayAdapter&lt;String&gt; adapter = new ArrayAdapter&lt;String&gt;(this,
+ *                 android.R.layout.simple_dropdown_item_1line, COUNTRIES);
+ *         AutoCompleteTextView textView = (AutoCompleteTextView)
+ *                 findViewById(R.id.countries_list);
+ *         textView.setAdapter(adapter);
+ *     }
+ *
+ *     private static final String[] COUNTRIES = new String[] {
+ *         "Belgium", "France", "Italy", "Germany", "Spain"
+ *     };
+ * }
+ * </pre>
+ *
+ * @attr ref android.R.styleable#AutoCompleteTextView_completionHint
+ * @attr ref android.R.styleable#AutoCompleteTextView_completionThreshold
+ * @attr ref android.R.styleable#AutoCompleteTextView_completionHintView
+ * @attr ref android.R.styleable#AutoCompleteTextView_dropDownSelector
+ * @attr ref android.R.styleable#AutoCompleteTextView_dropDownAnchor
+ * @attr ref android.R.styleable#AutoCompleteTextView_dropDownWidth
+ */
+public class AutoCompleteTextView extends EditText implements Filter.FilterListener {
+    static final boolean DEBUG = false;
+    static final String TAG = "AutoCompleteTextView";
+
+    private static final int HINT_VIEW_ID = 0x17;
+
+    private CharSequence mHintText;
+    private int mHintResource;
+
+    private ListAdapter mAdapter;
+    private Filter mFilter;
+    private int mThreshold;
+
+    private PopupWindow mPopup;
+    private DropDownListView mDropDownList;
+    private int mDropDownVerticalOffset;
+    private int mDropDownHorizontalOffset;
+    private int mDropDownAnchorId;
+    private View mDropDownAnchorView;  // view is retrieved lazily from id once needed
+    private int mDropDownWidth;
+
+    private Drawable mDropDownListHighlight;
+
+    private AdapterView.OnItemClickListener mItemClickListener;
+    private AdapterView.OnItemSelectedListener mItemSelectedListener;
+
+    private final DropDownItemClickListener mDropDownItemClickListener =
+            new DropDownItemClickListener();
+
+    private int mLastKeyCode = KeyEvent.KEYCODE_UNKNOWN;
+    private boolean mOpenBefore;
+
+    private Validator mValidator = null;
+
+    private boolean mBlockCompletion;
+
+    private AutoCompleteTextView.ListSelectorHider mHideSelector;
+
+    // Indicates whether this AutoCompleteTextView is attached to a window or not
+    // The widget is attached to a window when mAttachCount > 0
+    private int mAttachCount;
+
+    public AutoCompleteTextView(Context context) {
+        this(context, null);
+    }
+
+    public AutoCompleteTextView(Context context, AttributeSet attrs) {
+        this(context, attrs, com.android.internal.R.attr.autoCompleteTextViewStyle);
+    }
+
+    public AutoCompleteTextView(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+
+        mPopup = new PopupWindow(context, attrs,
+                com.android.internal.R.attr.autoCompleteTextViewStyle);
+
+        TypedArray a =
+            context.obtainStyledAttributes(
+                attrs, com.android.internal.R.styleable.AutoCompleteTextView, defStyle, 0);
+
+        mThreshold = a.getInt(
+                R.styleable.AutoCompleteTextView_completionThreshold, 2);
+
+        mHintText = a.getText(R.styleable.AutoCompleteTextView_completionHint);
+
+        mDropDownListHighlight = a.getDrawable(
+                R.styleable.AutoCompleteTextView_dropDownSelector);
+        mDropDownVerticalOffset = (int)
+                a.getDimension(R.styleable.AutoCompleteTextView_dropDownVerticalOffset, 0.0f);
+        mDropDownHorizontalOffset = (int)
+                a.getDimension(R.styleable.AutoCompleteTextView_dropDownHorizontalOffset, 0.0f);
+        
+        // Get the anchor's id now, but the view won't be ready, so wait to actually get the
+        // view and store it in mDropDownAnchorView lazily in getDropDownAnchorView later.
+        // Defaults to NO_ID, in which case the getDropDownAnchorView method will simply return
+        // this TextView, as a default anchoring point.
+        mDropDownAnchorId = a.getResourceId(R.styleable.AutoCompleteTextView_dropDownAnchor,
+                View.NO_ID);
+        
+        // For dropdown width, the developer can specify a specific width, or FILL_PARENT
+        // (for full screen width) or WRAP_CONTENT (to match the width of the anchored view).
+        mDropDownWidth = a.getLayoutDimension(R.styleable.AutoCompleteTextView_dropDownWidth,
+                ViewGroup.LayoutParams.WRAP_CONTENT);
+
+        mHintResource = a.getResourceId(R.styleable.AutoCompleteTextView_completionHintView,
+                R.layout.simple_dropdown_hint);
+
+        // Always turn on the auto complete input type flag, since it
+        // makes no sense to use this widget without it.
+        int inputType = getInputType();
+        if ((inputType&EditorInfo.TYPE_MASK_CLASS)
+                == EditorInfo.TYPE_CLASS_TEXT) {
+            inputType |= EditorInfo.TYPE_TEXT_FLAG_AUTO_COMPLETE;
+            setRawInputType(inputType);
+        }
+
+        a.recycle();
+
+        setFocusable(true);
+
+        addTextChangedListener(new MyWatcher());
+    }
+
+    /**
+     * Sets this to be single line; a separate method so
+     * MultiAutoCompleteTextView can skip this.
+     */
+    /* package */ void finishInit() {
+        setSingleLine();
+    }
+
+    /**
+     * <p>Sets the optional hint text that is displayed at the bottom of the
+     * the matching list.  This can be used as a cue to the user on how to
+     * best use the list, or to provide extra information.</p>
+     *
+     * @param hint the text to be displayed to the user
+     *
+     * @attr ref android.R.styleable#AutoCompleteTextView_completionHint
+     */
+    public void setCompletionHint(CharSequence hint) {
+        mHintText = hint;
+    }
+    
+    /**
+     * <p>Returns the current width for the auto-complete drop down list. This can
+     * be a fixed width, or {@link ViewGroup.LayoutParams#FILL_PARENT} to fill the screen, or
+     * {@link ViewGroup.LayoutParams#WRAP_CONTENT} to fit the width of its anchor view.</p>
+     * 
+     * @return the width for the drop down list
+     */
+    public int getDropDownWidth() {
+        return mDropDownWidth;
+    }
+    
+    /**
+     * <p>Sets the current width for the auto-complete drop down list. This can
+     * be a fixed width, or {@link ViewGroup.LayoutParams#FILL_PARENT} to fill the screen, or
+     * {@link ViewGroup.LayoutParams#WRAP_CONTENT} to fit the width of its anchor view.</p>
+     * 
+     * @param width the width to use
+     */
+    public void setDropDownWidth(int width) {
+        mDropDownWidth = width;
+    }
+    
+    /**
+     * <p>Returns the id for the view that the auto-complete drop down list is anchored to.</p>
+     *  
+     * @return the view's id, or {@link View#NO_ID} if none specified
+     */
+    public int getDropDownAnchor() {
+        return mDropDownAnchorId;
+    }
+    
+    /**
+     * <p>Sets the view to which the auto-complete drop down list should anchor. The view
+     * corresponding to this id will not be loaded until the next time it is needed to avoid
+     * loading a view which is not yet instantiated.</p>
+     * 
+     * @param id the id to anchor the drop down list view to
+     */
+    public void setDropDownAnchor(int id) {
+        mDropDownAnchorId = id;
+        mDropDownAnchorView = null;
+    }
+
+    /**
+     * <p>Returns the number of characters the user must type before the drop
+     * down list is shown.</p>
+     *
+     * @return the minimum number of characters to type to show the drop down
+     *
+     * @see #setThreshold(int)
+     */
+    public int getThreshold() {
+        return mThreshold;
+    }
+
+    /**
+     * <p>Specifies the minimum number of characters the user has to type in the
+     * edit box before the drop down list is shown.</p>
+     *
+     * <p>When <code>threshold</code> is less than or equals 0, a threshold of
+     * 1 is applied.</p>
+     *
+     * @param threshold the number of characters to type before the drop down
+     *                  is shown
+     *
+     * @see #getThreshold()
+     *
+     * @attr ref android.R.styleable#AutoCompleteTextView_completionThreshold
+     */
+    public void setThreshold(int threshold) {
+        if (threshold <= 0) {
+            threshold = 1;
+        }
+
+        mThreshold = threshold;
+    }
+
+    /**
+     * <p>Sets the listener that will be notified when the user clicks an item
+     * in the drop down list.</p>
+     *
+     * @param l the item click listener
+     */
+    public void setOnItemClickListener(AdapterView.OnItemClickListener l) {
+        mItemClickListener = l;
+    }
+
+    /**
+     * <p>Sets the listener that will be notified when the user selects an item
+     * in the drop down list.</p>
+     *
+     * @param l the item selected listener
+     */
+    public void setOnItemSelectedListener(AdapterView.OnItemSelectedListener l) {
+        mItemSelectedListener = l;
+    }
+
+    /**
+     * <p>Returns the listener that is notified whenever the user clicks an item
+     * in the drop down list.</p>
+     *
+     * @return the item click listener
+     *
+     * @deprecated Use {@link #getOnItemClickListener()} intead
+     */
+    @Deprecated
+    public AdapterView.OnItemClickListener getItemClickListener() {
+        return mItemClickListener;
+    }
+
+    /**
+     * <p>Returns the listener that is notified whenever the user selects an
+     * item in the drop down list.</p>
+     *
+     * @return the item selected listener
+     *
+     * @deprecated Use {@link #getOnItemSelectedListener()} intead
+     */
+    @Deprecated
+    public AdapterView.OnItemSelectedListener getItemSelectedListener() {
+        return mItemSelectedListener;
+    }
+
+    /**
+     * <p>Returns the listener that is notified whenever the user clicks an item
+     * in the drop down list.</p>
+     *
+     * @return the item click listener
+     */
+    public AdapterView.OnItemClickListener getOnItemClickListener() {
+        return mItemClickListener;
+    }
+
+    /**
+     * <p>Returns the listener that is notified whenever the user selects an
+     * item in the drop down list.</p>
+     *
+     * @return the item selected listener
+     */
+    public AdapterView.OnItemSelectedListener getOnItemSelectedListener() {
+        return mItemSelectedListener;
+    }
+
+    /**
+     * <p>Returns a filterable list adapter used for auto completion.</p>
+     *
+     * @return a data adapter used for auto completion
+     */
+    public ListAdapter getAdapter() {
+        return mAdapter;
+    }
+
+    /**
+     * <p>Changes the list of data used for auto completion. The provided list
+     * must be a filterable list adapter.</p>
+     * 
+     * <p>The caller is still responsible for managing any resources used by the adapter.
+     * Notably, when the AutoCompleteTextView is closed or released, the adapter is not notified.
+     * A common case is the use of {@link android.widget.CursorAdapter}, which
+     * contains a {@link android.database.Cursor} that must be closed.  This can be done
+     * automatically (see 
+     * {@link android.app.Activity#startManagingCursor(android.database.Cursor) 
+     * startManagingCursor()}),
+     * or by manually closing the cursor when the AutoCompleteTextView is dismissed.</p>
+     *
+     * @param adapter the adapter holding the auto completion data
+     *
+     * @see #getAdapter()
+     * @see android.widget.Filterable
+     * @see android.widget.ListAdapter
+     */
+    public <T extends ListAdapter & Filterable> void setAdapter(T adapter) {
+        mAdapter = adapter;
+        if (mAdapter != null) {
+            //noinspection unchecked
+            mFilter = ((Filterable) mAdapter).getFilter();
+        } else {
+            mFilter = null;
+        }
+
+        if (mDropDownList != null) {
+            mDropDownList.setAdapter(mAdapter);
+        }
+    }
+
+    @Override
+    public boolean onKeyPreIme(int keyCode, KeyEvent event) {
+        if (isPopupShowing()) {
+            // special case for the back key, we do not even try to send it
+            // to the drop down list but instead, consume it immediately
+            if (keyCode == KeyEvent.KEYCODE_BACK) {
+                dismissDropDown();
+                return true;
+            }
+        }
+        return super.onKeyPreIme(keyCode, event);
+    }
+
+    @Override
+    public boolean onKeyUp(int keyCode, KeyEvent event) {
+        if (isPopupShowing() && mDropDownList.getSelectedItemPosition() >= 0) {
+            boolean consumed = mDropDownList.onKeyUp(keyCode, event);
+            if (consumed) {
+                switch (keyCode) {
+                    // if the list accepts the key events and the key event
+                    // was a click, the text view gets the selected item
+                    // from the drop down as its content
+                    case KeyEvent.KEYCODE_ENTER:
+                    case KeyEvent.KEYCODE_DPAD_CENTER:
+                        performCompletion();
+                        return true;
+                }
+            }
+        }
+        return super.onKeyUp(keyCode, event);
+    }
+
+    @Override
+    public boolean onKeyDown(int keyCode, KeyEvent event) {
+        // when the drop down is shown, we drive it directly
+        if (isPopupShowing()) {
+            // the key events are forwarded to the list in the drop down view
+            // note that ListView handles space but we don't want that to happen
+            // also if selection is not currently in the drop down, then don't
+            // let center or enter presses go there since that would cause it
+            // to select one of its items
+            if (keyCode != KeyEvent.KEYCODE_SPACE
+                    && (mDropDownList.getSelectedItemPosition() >= 0
+                            || (keyCode != KeyEvent.KEYCODE_ENTER
+                                    && keyCode != KeyEvent.KEYCODE_DPAD_CENTER))) {
+                int curIndex = mDropDownList.getSelectedItemPosition();
+                boolean consumed;
+                final boolean below = !mPopup.isAboveAnchor();
+                if ((below && keyCode == KeyEvent.KEYCODE_DPAD_UP && curIndex <= 0) ||
+                        (!below && keyCode == KeyEvent.KEYCODE_DPAD_DOWN && curIndex >=
+                        mDropDownList.getAdapter().getCount() - 1)) {
+                    // When the selection is at the top, we block the key
+                    // event to prevent focus from moving.
+                    mDropDownList.hideSelector();
+                    mDropDownList.requestLayout();
+                    mPopup.setInputMethodMode(PopupWindow.INPUT_METHOD_NEEDED);
+                    mPopup.update();
+                    return true;
+                }
+                consumed = mDropDownList.onKeyDown(keyCode, event);
+                if (DEBUG) Log.v(TAG, "Key down: code=" + keyCode + " list consumed="
+                        + consumed);
+                if (consumed) {
+                    // If it handled the key event, then the user is
+                    // navigating in the list, so we should put it in front.
+                    mPopup.setInputMethodMode(PopupWindow.INPUT_METHOD_NOT_NEEDED);
+                    // Here's a little trick we need to do to make sure that
+                    // the list view is actually showing its focus indicator,
+                    // by ensuring it has focus and getting its window out
+                    // of touch mode.
+                    mDropDownList.requestFocusFromTouch();
+                    if (false) {
+                        // Update whether the pop-up is in front of or behind
+                        // the input method, depending on whether the user has
+                        // moved down in it.
+                        mPopup.setInputMethodMode(curIndex > 0
+                                ? PopupWindow.INPUT_METHOD_NOT_NEEDED
+                                : PopupWindow.INPUT_METHOD_NEEDED);
+                    }
+                    mPopup.update();
+
+                    switch (keyCode) {
+                        // avoid passing the focus from the text view to the
+                        // next component
+                        case KeyEvent.KEYCODE_ENTER:
+                        case KeyEvent.KEYCODE_DPAD_CENTER:
+                        case KeyEvent.KEYCODE_DPAD_DOWN:
+                        case KeyEvent.KEYCODE_DPAD_UP:
+                            return true;
+                    }
+                } else {
+                    if (below && keyCode == KeyEvent.KEYCODE_DPAD_DOWN) {
+                        // when the selection is at the bottom, we block the
+                        // event to avoid going to the next focusable widget
+                        Adapter adapter = mDropDownList.getAdapter();
+                        if (adapter != null && curIndex == adapter.getCount() - 1) {
+                            return true;
+                        }
+                    } else if (!below && keyCode == KeyEvent.KEYCODE_DPAD_UP && curIndex == 0) {
+                        return true;
+                    }
+                }
+            }
+        } else {
+            switch(keyCode) {
+            case KeyEvent.KEYCODE_DPAD_DOWN:
+                performValidation();
+            }
+        }
+
+        mLastKeyCode = keyCode;
+        boolean handled = super.onKeyDown(keyCode, event);
+        mLastKeyCode = KeyEvent.KEYCODE_UNKNOWN;
+
+        return handled;
+    }
+
+    /**
+     * Returns <code>true</code> if the amount of text in the field meets
+     * or exceeds the {@link #getThreshold} requirement.  You can override
+     * this to impose a different standard for when filtering will be
+     * triggered.
+     */
+    public boolean enoughToFilter() {
+        if (DEBUG) Log.v(TAG, "Enough to filter: len=" + getText().length()
+                + " threshold=" + mThreshold);
+        return getText().length() >= mThreshold;
+    }
+
+    /**
+     * This is used to watch for edits to the text view.  Note that we call
+     * to methods on the auto complete text view class so that we can access
+     * private vars without going through thunks.
+     */
+    private class MyWatcher implements TextWatcher {
+        public void afterTextChanged(Editable s) {
+            doAfterTextChanged();
+        }
+        public void beforeTextChanged(CharSequence s, int start, int count, int after) {
+            doBeforeTextChanged();
+        }
+        public void onTextChanged(CharSequence s, int start, int before, int count) {
+        }
+    }
+
+    void doBeforeTextChanged() {
+        if (mBlockCompletion) return;
+
+        // when text is changed, inserted or deleted, we attempt to show
+        // the drop down
+        mOpenBefore = isPopupShowing();
+        if (DEBUG) Log.v(TAG, "before text changed: open=" + mOpenBefore);
+    }
+
+    void doAfterTextChanged() {
+        if (mBlockCompletion) return;
+
+        // if the list was open before the keystroke, but closed afterwards,
+        // then something in the keystroke processing (an input filter perhaps)
+        // called performCompletion() and we shouldn't do any more processing.
+        if (DEBUG) Log.v(TAG, "after text changed: openBefore=" + mOpenBefore
+                + " open=" + isPopupShowing());
+        if (mOpenBefore && !isPopupShowing()) {
+            return;
+        }
+
+        // the drop down is shown only when a minimum number of characters
+        // was typed in the text view
+        if (enoughToFilter()) {
+            if (mFilter != null) {
+                performFiltering(getText(), mLastKeyCode);
+            }
+        } else {
+            // drop down is automatically dismissed when enough characters
+            // are deleted from the text view
+            dismissDropDown();
+            if (mFilter != null) {
+                mFilter.filter(null);
+            }
+        }
+    }
+
+    /**
+     * <p>Indicates whether the popup menu is showing.</p>
+     *
+     * @return true if the popup menu is showing, false otherwise
+     */
+    public boolean isPopupShowing() {
+        return mPopup.isShowing();
+    }
+
+    /**
+     * <p>Converts the selected item from the drop down list into a sequence
+     * of character that can be used in the edit box.</p>
+     *
+     * @param selectedItem the item selected by the user for completion
+     *
+     * @return a sequence of characters representing the selected suggestion
+     */
+    protected CharSequence convertSelectionToString(Object selectedItem) {
+        return mFilter.convertResultToString(selectedItem);
+    }
+    
+    /**
+     * <p>Clear the list selection.  This may only be temporary, as user input will often bring 
+     * it back.
+     */
+    public void clearListSelection() {
+        if (mDropDownList != null) {
+            mDropDownList.hideSelector();
+            mDropDownList.requestLayout();
+        }
+    }
+    
+    /**
+     * Set the position of the dropdown view selection.
+     * 
+     * @param position The position to move the selector to.
+     */
+    public void setListSelection(int position) {
+        if (mPopup.isShowing() && (mDropDownList != null)) {
+            mDropDownList.setSelection(position);
+            // ListView.setSelection() will call requestLayout()
+        }
+    }
+
+    /**
+     * Get the position of the dropdown view selection, if there is one.  Returns 
+     * {@link ListView#INVALID_POSITION ListView.INVALID_POSITION} if there is no dropdown or if
+     * there is no selection.
+     * 
+     * @return the position of the current selection, if there is one, or 
+     * {@link ListView#INVALID_POSITION ListView.INVALID_POSITION} if not.
+     * 
+     * @see ListView#getSelectedItemPosition()
+     */
+    public int getListSelection() {
+        if (mPopup.isShowing() && (mDropDownList != null)) {
+            return mDropDownList.getSelectedItemPosition();
+        }
+        return ListView.INVALID_POSITION;
+    }
+    
+    /**
+     * <p>Starts filtering the content of the drop down list. The filtering
+     * pattern is the content of the edit box. Subclasses should override this
+     * method to filter with a different pattern, for instance a substring of
+     * <code>text</code>.</p>
+     *
+     * @param text the filtering pattern
+     * @param keyCode the last character inserted in the edit box; beware that
+     * this will be null when text is being added through a soft input method.
+     */
+    @SuppressWarnings({ "UnusedDeclaration" })
+    protected void performFiltering(CharSequence text, int keyCode) {
+        mFilter.filter(text, this);
+    }
+
+    /**
+     * <p>Performs the text completion by converting the selected item from
+     * the drop down list into a string, replacing the text box's content with
+     * this string and finally dismissing the drop down menu.</p>
+     */
+    public void performCompletion() {
+        performCompletion(null, -1, -1);
+    }
+
+    @Override
+    public void onCommitCompletion(CompletionInfo completion) {
+        if (isPopupShowing()) {
+            mBlockCompletion = true;
+            replaceText(completion.getText());
+            mBlockCompletion = false;
+
+            if (mItemClickListener != null) {
+                final DropDownListView list = mDropDownList;
+                // Note that we don't have a View here, so we will need to
+                // supply null.  Hopefully no existing apps crash...
+                mItemClickListener.onItemClick(list, null, completion.getPosition(),
+                        completion.getId());
+            }
+        }
+    }
+
+    private void performCompletion(View selectedView, int position, long id) {
+        if (isPopupShowing()) {
+            Object selectedItem;
+            if (position < 0) {
+                selectedItem = mDropDownList.getSelectedItem();
+            } else {
+                selectedItem = mAdapter.getItem(position);
+            }
+            if (selectedItem == null) {
+                Log.w(TAG, "performCompletion: no selected item");
+                return;
+            }
+
+            mBlockCompletion = true;
+            replaceText(convertSelectionToString(selectedItem));
+            mBlockCompletion = false;            
+
+            if (mItemClickListener != null) {
+                final DropDownListView list = mDropDownList;
+
+                if (selectedView == null || position < 0) {
+                    selectedView = list.getSelectedView();
+                    position = list.getSelectedItemPosition();
+                    id = list.getSelectedItemId();
+                }
+                mItemClickListener.onItemClick(list, selectedView, position, id);
+            }
+        }
+
+        dismissDropDown();
+    }
+    
+    /**
+     * Identifies whether the view is currently performing a text completion, so subclasses
+     * can decide whether to respond to text changed events.
+     */
+    public boolean isPerformingCompletion() {
+        return mBlockCompletion;
+    }
+
+    /**
+     * <p>Performs the text completion by replacing the current text by the
+     * selected item. Subclasses should override this method to avoid replacing
+     * the whole content of the edit box.</p>
+     *
+     * @param text the selected suggestion in the drop down list
+     */
+    protected void replaceText(CharSequence text) {
+        setText(text);
+        // make sure we keep the caret at the end of the text view
+        Editable spannable = getText();
+        Selection.setSelection(spannable, spannable.length());
+    }
+
+    public void onFilterComplete(int count) {
+        if (mAttachCount <= 0) return;
+
+        /*
+         * This checks enoughToFilter() again because filtering requests
+         * are asynchronous, so the result may come back after enough text
+         * has since been deleted to make it no longer appropriate
+         * to filter.
+         */
+
+        if (count > 0 && enoughToFilter()) {
+            if (hasFocus() && hasWindowFocus()) {
+                showDropDown();
+            }
+        } else {
+            dismissDropDown();
+        }
+    }
+
+    @Override
+    public void onWindowFocusChanged(boolean hasWindowFocus) {
+        super.onWindowFocusChanged(hasWindowFocus);
+        performValidation();
+        if (!hasWindowFocus) {
+            dismissDropDown();
+        }
+    }
+
+    @Override
+    protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) {
+        super.onFocusChanged(focused, direction, previouslyFocusedRect);
+        performValidation();
+        if (!focused) {
+            dismissDropDown();
+        }
+    }
+
+    @Override
+    protected void onAttachedToWindow() {
+        super.onAttachedToWindow();
+        mAttachCount++;
+    }
+
+    @Override
+    protected void onDetachedFromWindow() {
+        dismissDropDown();
+        mAttachCount--;
+        super.onDetachedFromWindow();
+    }
+
+    /**
+     * <p>Closes the drop down if present on screen.</p>
+     */
+    public void dismissDropDown() {
+        InputMethodManager imm = InputMethodManager.peekInstance();
+        if (imm != null) {
+            imm.displayCompletions(this, null);
+        }
+        mPopup.dismiss();
+        mPopup.setContentView(null);
+        mDropDownList = null;
+    }
+
+    @Override
+    protected boolean setFrame(int l, int t, int r, int b) {
+        boolean result = super.setFrame(l, t, r, b);
+
+        if (mPopup.isShowing()) {
+            mPopup.update(this, r - l, -1);
+        }
+
+        return result;
+    }
+    
+    /**
+     * <p>Used for lazy instantiation of the anchor view from the id we have. If the value of
+     * the id is NO_ID or we can't find a view for the given id, we return this TextView as
+     * the default anchoring point.</p>
+     */
+    private View getDropDownAnchorView() {
+        if (mDropDownAnchorView == null && mDropDownAnchorId != View.NO_ID) {
+            mDropDownAnchorView = getRootView().findViewById(mDropDownAnchorId);
+        }
+        return mDropDownAnchorView == null ? this : mDropDownAnchorView;
+    }
+
+    /**
+     * <p>Displays the drop down on screen.</p>
+     */
+    public void showDropDown() {
+        int height = buildDropDown();
+        if (mPopup.isShowing()) {
+            int widthSpec;
+            if (mDropDownWidth == ViewGroup.LayoutParams.FILL_PARENT) {
+                // The call to PopupWindow's update method below can accept -1 for any
+                // value you do not want to update.
+                widthSpec = -1;
+            } else if (mDropDownWidth == ViewGroup.LayoutParams.WRAP_CONTENT) {
+                widthSpec = getDropDownAnchorView().getWidth();
+            } else {
+                widthSpec = mDropDownWidth;
+            }
+            mPopup.update(getDropDownAnchorView(), mDropDownHorizontalOffset,
+                    mDropDownVerticalOffset, widthSpec, height);
+        } else {
+            int widthSpec;
+            if (mDropDownWidth == ViewGroup.LayoutParams.FILL_PARENT) {
+                mPopup.setWindowLayoutMode(ViewGroup.LayoutParams.FILL_PARENT,
+                        ViewGroup.LayoutParams.WRAP_CONTENT);
+            } else {
+                mPopup.setWindowLayoutMode(0, ViewGroup.LayoutParams.WRAP_CONTENT);
+                if (mDropDownWidth == ViewGroup.LayoutParams.WRAP_CONTENT) {
+                    mPopup.setWidth(getDropDownAnchorView().getWidth());
+                } else {
+                    mPopup.setWidth(mDropDownWidth);
+                }
+            }
+            mPopup.setHeight(height);
+            mPopup.setInputMethodMode(PopupWindow.INPUT_METHOD_NEEDED);
+            mPopup.setOutsideTouchable(true);
+            mPopup.setTouchInterceptor(new PopupTouchIntercepter());
+            mPopup.showAsDropDown(getDropDownAnchorView(),
+                    mDropDownHorizontalOffset, mDropDownVerticalOffset);
+            mDropDownList.setSelection(ListView.INVALID_POSITION);
+            mDropDownList.hideSelector();
+            mDropDownList.requestFocus();
+            post(mHideSelector);
+        }
+    }
+
+    /**
+     * <p>Builds the popup window's content and returns the height the popup
+     * should have. Returns -1 when the content already exists.</p>
+     *
+     * @return the content's height or -1 if content already exists
+     */
+    private int buildDropDown() {
+        ViewGroup dropDownView;
+        int otherHeights = 0;
+
+        if (mAdapter != null) {
+            InputMethodManager imm = InputMethodManager.peekInstance();
+            if (imm != null) {
+                int N = mAdapter.getCount();
+                if (N > 20) N = 20;
+                CompletionInfo[] completions = new CompletionInfo[N];
+                for (int i = 0; i < N; i++) {
+                    Object item = mAdapter.getItem(i);
+                    long id = mAdapter.getItemId(i);
+                    completions[i] = new CompletionInfo(id, i,
+                            convertSelectionToString(item));
+                }
+                imm.displayCompletions(this, completions);
+            }
+        }
+
+        if (mDropDownList == null) {
+            Context context = getContext();
+
+            mHideSelector = new ListSelectorHider();
+
+            mDropDownList = new DropDownListView(context);
+            mDropDownList.setSelector(mDropDownListHighlight);
+            mDropDownList.setAdapter(mAdapter);
+            mDropDownList.setVerticalFadingEdgeEnabled(true);
+            mDropDownList.setOnItemClickListener(mDropDownItemClickListener);
+            mDropDownList.setFocusable(true);
+            mDropDownList.setFocusableInTouchMode(true);
+
+            if (mItemSelectedListener != null) {
+                mDropDownList.setOnItemSelectedListener(mItemSelectedListener);
+            }
+
+            dropDownView = mDropDownList;
+
+            View hintView = getHintView(context);
+            if (hintView != null) {
+                // if an hint has been specified, we accomodate more space for it and
+                // add a text view in the drop down menu, at the bottom of the list
+                LinearLayout hintContainer = new LinearLayout(context);
+                hintContainer.setOrientation(LinearLayout.VERTICAL);
+
+                LinearLayout.LayoutParams hintParams = new LinearLayout.LayoutParams(
+                        ViewGroup.LayoutParams.FILL_PARENT, 0, 1.0f
+                );
+                hintContainer.addView(dropDownView, hintParams);
+                hintContainer.addView(hintView);
+
+                // measure the hint's height to find how much more vertical space
+                // we need to add to the drop down's height
+                int widthSpec = MeasureSpec.makeMeasureSpec(getWidth(), MeasureSpec.AT_MOST);
+                int heightSpec = MeasureSpec.UNSPECIFIED;
+                hintView.measure(widthSpec, heightSpec);
+
+                hintParams = (LinearLayout.LayoutParams) hintView.getLayoutParams();
+                otherHeights = hintView.getMeasuredHeight() + hintParams.topMargin
+                        + hintParams.bottomMargin;
+
+                dropDownView = hintContainer;
+            }
+
+            mPopup.setContentView(dropDownView);
+        } else {
+            dropDownView = (ViewGroup) mPopup.getContentView();
+            final View view = dropDownView.findViewById(HINT_VIEW_ID);
+            if (view != null) {
+                LinearLayout.LayoutParams hintParams =
+                        (LinearLayout.LayoutParams) view.getLayoutParams();
+                otherHeights = view.getMeasuredHeight() + hintParams.topMargin
+                        + hintParams.bottomMargin;
+            }
+        }
+
+        // Max height available on the screen for a popup anchored to us
+        final int maxHeight = mPopup.getMaxAvailableHeight(this, mDropDownVerticalOffset);
+        //otherHeights += dropDownView.getPaddingTop() + dropDownView.getPaddingBottom();
+
+        return mDropDownList.measureHeightOfChildren(MeasureSpec.UNSPECIFIED,
+                0, ListView.NO_POSITION, maxHeight - otherHeights, 2) + otherHeights;
+    }
+
+    private View getHintView(Context context) {
+        if (mHintText != null && mHintText.length() > 0) {
+            final TextView hintView = (TextView) LayoutInflater.from(context).inflate(
+                    mHintResource, null).findViewById(com.android.internal.R.id.text1);
+            hintView.setText(mHintText);
+            hintView.setId(HINT_VIEW_ID);
+            return hintView;
+        } else {
+            return null;
+        }
+    }
+
+    /**
+     * Sets the validator used to perform text validation.
+     *
+     * @param validator The validator used to validate the text entered in this widget.
+     *
+     * @see #getValidator()
+     * @see #performValidation()
+     */
+    public void setValidator(Validator validator) {
+        mValidator = validator;
+    }
+
+    /**
+     * Returns the Validator set with {@link #setValidator},
+     * or <code>null</code> if it was not set.
+     *
+     * @see #setValidator(android.widget.AutoCompleteTextView.Validator)
+     * @see #performValidation()
+     */
+    public Validator getValidator() {
+        return mValidator;
+    }
+
+    /**
+     * If a validator was set on this view and the current string is not valid,
+     * ask the validator to fix it.
+     *
+     * @see #getValidator()
+     * @see #setValidator(android.widget.AutoCompleteTextView.Validator)
+     */
+    public void performValidation() {
+        if (mValidator == null) return;
+
+        CharSequence text = getText();
+
+        if (!TextUtils.isEmpty(text) && !mValidator.isValid(text)) {
+            setText(mValidator.fixText(text));
+        }
+    }
+
+    /**
+     * Returns the Filter obtained from {@link Filterable#getFilter},
+     * or <code>null</code> if {@link #setAdapter} was not called with
+     * a Filterable.
+     */
+    protected Filter getFilter() {
+        return mFilter;
+    }
+
+    private class ListSelectorHider implements Runnable {
+        public void run() {
+            if (mDropDownList != null) {
+                mDropDownList.hideSelector();
+                mDropDownList.requestLayout();
+            }
+        }
+    }
+
+    private class PopupTouchIntercepter implements OnTouchListener {
+        public boolean onTouch(View v, MotionEvent event) {
+            if (event.getAction() == MotionEvent.ACTION_DOWN) {
+                mPopup.setInputMethodMode(PopupWindow.INPUT_METHOD_NOT_NEEDED);
+                mPopup.update();
+            }
+            return false;
+        }
+    }
+    
+    private class DropDownItemClickListener implements AdapterView.OnItemClickListener {
+        public void onItemClick(AdapterView parent, View v, int position, long id) {
+            performCompletion(v, position, id);
+        }
+    }
+
+    /**
+     * <p>Wrapper class for a ListView. This wrapper hijacks the focus to
+     * make sure the list uses the appropriate drawables and states when
+     * displayed on screen within a drop down. The focus is never actually
+     * passed to the drop down; the list only looks focused.</p>
+     */
+    private static class DropDownListView extends ListView {
+        /**
+         * <p>Creates a new list view wrapper.</p>
+         *
+         * @param context this view's context
+         */
+        public DropDownListView(Context context) {
+            super(context, null, com.android.internal.R.attr.dropDownListViewStyle);
+        }
+
+        /**
+         * <p>Avoids jarring scrolling effect by ensuring that list elements
+         * made of a text view fit on a single line.</p>
+         *
+         * @param position the item index in the list to get a view for
+         * @return the view for the specified item
+         */
+        @Override
+        protected View obtainView(int position) {
+            View view = super.obtainView(position);
+
+            if (view instanceof TextView) {
+                ((TextView) view).setHorizontallyScrolling(true);
+            }
+
+            return view;
+        }
+
+        /**
+         * <p>Returns the top padding of the currently selected view.</p>
+         *
+         * @return the height of the top padding for the selection
+         */
+        public int getSelectionPaddingTop() {
+            return mSelectionTopPadding;
+        }
+
+        /**
+         * <p>Returns the bottom padding of the currently selected view.</p>
+         *
+         * @return the height of the bottom padding for the selection
+         */
+        public int getSelectionPaddingBottom() {
+            return mSelectionBottomPadding;
+        }
+
+        /**
+         * <p>Returns the focus state in the drop down.</p>
+         *
+         * @return true always
+         */
+        @Override
+        public boolean hasWindowFocus() {
+            return true;
+        }
+
+        /**
+         * <p>Returns the focus state in the drop down.</p>
+         *
+         * @return true always
+         */
+        @Override
+        public boolean isFocused() {
+            return true;
+        }
+
+        /**
+         * <p>Returns the focus state in the drop down.</p>
+         *
+         * @return true always
+         */
+        @Override
+        public boolean hasFocus() {
+            return true;
+        }
+        
+        protected int[] onCreateDrawableState(int extraSpace) {
+            int[] res = super.onCreateDrawableState(extraSpace);
+            if (false) {
+                StringBuilder sb = new StringBuilder("Created drawable state: [");
+                for (int i=0; i<res.length; i++) {
+                    if (i > 0) sb.append(", ");
+                    sb.append("0x");
+                    sb.append(Integer.toHexString(res[i]));
+                }
+                sb.append("]");
+                Log.i(TAG, sb.toString());
+            }
+            return res;
+        }
+    }
+
+    /**
+     * This interface is used to make sure that the text entered in this TextView complies to
+     * a certain format.  Since there is no foolproof way to prevent the user from leaving
+     * this View with an incorrect value in it, all we can do is try to fix it ourselves
+     * when this happens.
+     */
+    public interface Validator {
+        /**
+         * Validates the specified text.
+         *
+         * @return true If the text currently in the text editor is valid.
+         *
+         * @see #fixText(CharSequence)
+         */
+        boolean isValid(CharSequence text);
+
+        /**
+         * Corrects the specified text to make it valid.
+         *
+         * @param invalidText A string that doesn't pass validation: isValid(invalidText)
+         *        returns false
+         *
+         * @return A string based on invalidText such as invoking isValid() on it returns true.
+         *
+         * @see #isValid(CharSequence)
+         */
+        CharSequence fixText(CharSequence invalidText);
+    }
+}
diff --git a/core/java/android/widget/BaseAdapter.java b/core/java/android/widget/BaseAdapter.java
new file mode 100644
index 0000000..532fd76
--- /dev/null
+++ b/core/java/android/widget/BaseAdapter.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2007 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 android.widget;
+
+import android.database.DataSetObservable;
+import android.database.DataSetObserver;
+import android.view.View;
+import android.view.ViewGroup;
+
+/**
+ * Common base class of common implementation for an {@link Adapter} that can be
+ * used in both {@link ListView} (by implementing the specialized
+ * {@link ListAdapter} interface} and {@link Spinner} (by implementing the
+ * specialized {@link SpinnerAdapter} interface.
+ */
+public abstract class BaseAdapter implements ListAdapter, SpinnerAdapter {
+    private final DataSetObservable mDataSetObservable = new DataSetObservable();
+
+    public boolean hasStableIds() {
+        return false;
+    }
+    
+    public void registerDataSetObserver(DataSetObserver observer) {
+        mDataSetObservable.registerObserver(observer);
+    }
+
+    public void unregisterDataSetObserver(DataSetObserver observer) {
+        mDataSetObservable.unregisterObserver(observer);
+    }
+    
+    /**
+     * Notifies the attached View that the underlying data has been changed
+     * and it should refresh itself.
+     */
+    public void notifyDataSetChanged() {
+        mDataSetObservable.notifyChanged();
+    }
+    
+    public void notifyDataSetInvalidated() {
+        mDataSetObservable.notifyInvalidated();
+    }
+
+    public boolean areAllItemsEnabled() {
+        return true;
+    }
+
+    public boolean isEnabled(int position) {
+        return true;
+    }
+
+    public View getDropDownView(int position, View convertView, ViewGroup parent) {
+        return getView(position, convertView, parent);
+    }
+
+    public int getItemViewType(int position) {
+        return 0;
+    }
+
+    public int getViewTypeCount() {
+        return 1;
+    }
+    
+    public boolean isEmpty() {
+        return getCount() == 0;
+    }
+}
diff --git a/core/java/android/widget/BaseExpandableListAdapter.java b/core/java/android/widget/BaseExpandableListAdapter.java
new file mode 100644
index 0000000..1bba7f0
--- /dev/null
+++ b/core/java/android/widget/BaseExpandableListAdapter.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2007 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 android.widget;
+
+import android.database.DataSetObservable;
+import android.database.DataSetObserver;
+import android.view.KeyEvent;
+
+/**
+ * Base class for a {@link ExpandableListAdapter} used to provide data and Views
+ * from some data to an expandable list view.
+ * <p>
+ * Adapters inheriting this class should verify that the base implementations of
+ * {@link #getCombinedChildId(long, long)} and {@link #getCombinedGroupId(long)}
+ * are correct in generating unique IDs from the group/children IDs.
+ * <p>
+ * @see SimpleExpandableListAdapter
+ * @see SimpleCursorTreeAdapter
+ */
+public abstract class BaseExpandableListAdapter implements ExpandableListAdapter {
+    private final DataSetObservable mDataSetObservable = new DataSetObservable();
+    
+    public void registerDataSetObserver(DataSetObserver observer) {
+        mDataSetObservable.registerObserver(observer);
+    }
+
+    public void unregisterDataSetObserver(DataSetObserver observer) {
+        mDataSetObservable.unregisterObserver(observer);
+    }
+    
+    /**
+     * {@see DataSetObservable#notifyInvalidated()}
+     */
+    public void notifyDataSetInvalidated() {
+        mDataSetObservable.notifyInvalidated();
+    }
+    
+    /**
+     * {@see DataSetObservable#notifyChanged()}
+     */
+    public void notifyDataSetChanged() {
+        mDataSetObservable.notifyChanged();
+    }
+
+    public boolean areAllItemsEnabled() {
+        return true;
+    }
+
+    public void onGroupCollapsed(int groupPosition) {
+    }
+
+    public void onGroupExpanded(int groupPosition) {
+    }
+
+    /**
+     * Override this method if you foresee a clash in IDs based on this scheme:
+     * <p>
+     * Base implementation returns a long:
+     * <li> bit 0: Whether this ID points to a child (unset) or group (set), so for this method
+     *             this bit will be 1.
+     * <li> bit 1-31: Lower 31 bits of the groupId
+     * <li> bit 32-63: Lower 32 bits of the childId.
+     * <p> 
+     * {@inheritDoc}
+     */
+    public long getCombinedChildId(long groupId, long childId) {
+        return 0x8000000000000000L | ((groupId & 0x7FFFFFFF) << 32) | (childId & 0xFFFFFFFF);
+    }
+
+    /**
+     * Override this method if you foresee a clash in IDs based on this scheme:
+     * <p>
+     * Base implementation returns a long:
+     * <li> bit 0: Whether this ID points to a child (unset) or group (set), so for this method
+     *             this bit will be 0.
+     * <li> bit 1-31: Lower 31 bits of the groupId
+     * <li> bit 32-63: Lower 32 bits of the childId.
+     * <p> 
+     * {@inheritDoc}
+     */
+    public long getCombinedGroupId(long groupId) {
+        return (groupId & 0x7FFFFFFF) << 32;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public boolean isEmpty() {
+        return getGroupCount() == 0;
+    }
+    
+}
diff --git a/core/java/android/widget/Button.java b/core/java/android/widget/Button.java
new file mode 100644
index 0000000..5e692d4
--- /dev/null
+++ b/core/java/android/widget/Button.java
@@ -0,0 +1,71 @@
+/*
+ * 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 android.widget;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.MotionEvent;
+import android.view.KeyEvent;
+import android.widget.RemoteViews.RemoteView;
+
+
+/**
+ * <p>
+ * <code>Button</code> represents a push-button widget. Push-buttons can be
+ * pressed, or clicked, by the user to perform an action. A typical use of a
+ * push-button in an activity would be the following:
+ * </p>
+ *
+ * <pre class="prettyprint">
+ * public class MyActivity extends Activity {
+ *     protected void onCreate(Bundle icicle) {
+ *         super.onCreate(icicle);
+ *
+ *         setContentView(R.layout.content_layout_id);
+ *
+ *         final Button button = (Button) findViewById(R.id.button_id);
+ *         button.setOnClickListener(new View.OnClickListener() {
+ *             public void onClick(View v) {
+ *                 // Perform action on click
+ *             }
+ *         });
+ *     }
+ * }
+ * </pre>
+ *
+ * <p><strong>XML attributes</strong></p>
+ * <p> 
+ * See {@link android.R.styleable#Button Button Attributes}, 
+ * {@link android.R.styleable#TextView TextView Attributes},  
+ * {@link android.R.styleable#View View Attributes}
+ * </p>
+ */
+@RemoteView
+public class Button extends TextView {
+    public Button(Context context) {
+        this(context, null);
+    }
+
+    public Button(Context context, AttributeSet attrs) {
+        this(context, attrs, com.android.internal.R.attr.buttonStyle);
+    }
+
+    public Button(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+    }
+}
diff --git a/core/java/android/widget/CheckBox.java b/core/java/android/widget/CheckBox.java
new file mode 100644
index 0000000..ff63a24
--- /dev/null
+++ b/core/java/android/widget/CheckBox.java
@@ -0,0 +1,65 @@
+/*
+ * 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 android.widget;
+
+import android.content.Context;
+import android.util.AttributeSet;
+
+
+/**
+ * <p>
+ * A checkbox is a specific type of two-states button that can be either
+ * checked or unchecked. A example usage of a checkbox inside your activity
+ * would be the following:
+ * </p>
+ *
+ * <pre class="prettyprint">
+ * public class MyActivity extends Activity {
+ *     protected void onCreate(Bundle icicle) {
+ *         super.onCreate(icicle);
+ *
+ *         setContentView(R.layout.content_layout_id);
+ *
+ *         final CheckBox checkBox = (CheckBox) findViewById(R.id.checkbox_id);
+ *         if (checkBox.isChecked()) {
+ *             checkBox.setChecked(false);
+ *         }
+ *     }
+ * }
+ * </pre>
+ *  
+ * <p><strong>XML attributes</strong></p> 
+ * <p>
+ * See {@link android.R.styleable#CompoundButton CompoundButton Attributes}, 
+ * {@link android.R.styleable#Button Button Attributes}, 
+ * {@link android.R.styleable#TextView TextView Attributes}, 
+ * {@link android.R.styleable#View View Attributes}
+ * </p>
+ */
+public class CheckBox extends CompoundButton {
+    public CheckBox(Context context) {
+        this(context, null);
+    }
+    
+    public CheckBox(Context context, AttributeSet attrs) {
+        this(context, attrs, com.android.internal.R.attr.checkboxStyle);
+    }
+
+    public CheckBox(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+    }
+}
diff --git a/core/java/android/widget/Checkable.java b/core/java/android/widget/Checkable.java
new file mode 100644
index 0000000..eb97b4a
--- /dev/null
+++ b/core/java/android/widget/Checkable.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2007 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 android.widget;
+
+/**
+ * Defines an extension for views that make them checkable.
+ *
+ */
+public interface Checkable {
+    
+    /**
+     * Change the checked state of the view
+     * 
+     * @param checked The new checked state
+     */
+    void setChecked(boolean checked);
+        
+    /**
+     * @return The current checked state of the view
+     */
+    boolean isChecked();
+    
+    /**
+     * Change the checked state of the view to the inverse of its current state
+     *
+     */
+    void toggle();
+}
diff --git a/core/java/android/widget/CheckedTextView.java b/core/java/android/widget/CheckedTextView.java
new file mode 100644
index 0000000..f5a0b1c
--- /dev/null
+++ b/core/java/android/widget/CheckedTextView.java
@@ -0,0 +1,198 @@
+/*
+ * Copyright (C) 2007 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 android.widget;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Canvas;
+import android.graphics.drawable.Drawable;
+import android.util.AttributeSet;
+import android.view.Gravity;
+
+import com.android.internal.R;
+
+
+/**
+ * An extension to TextView that supports the {@link android.widget.Checkable} interface.
+ * This is useful when used in a {@link android.widget.ListView ListView} where the it's 
+ * {@link android.widget.ListView#setChoiceMode(int) setChoiceMode} has been set to
+ * something other than {@link android.widget.ListView#CHOICE_MODE_NONE CHOICE_MODE_NONE}.
+ *
+ */
+public abstract class CheckedTextView extends TextView implements Checkable {
+    private boolean mChecked;
+    private int mCheckMarkResource;
+    private Drawable mCheckMarkDrawable;
+    private int mBasePaddingRight;
+    private int mCheckMarkWidth;
+
+    private static final int[] CHECKED_STATE_SET = {
+        R.attr.state_checked
+    };
+
+    public CheckedTextView(Context context) {
+        this(context, null);
+    }
+
+    public CheckedTextView(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public CheckedTextView(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+
+        TypedArray a = context.obtainStyledAttributes(attrs,
+                R.styleable.CheckedTextView, defStyle, 0);
+
+        Drawable d = a.getDrawable(R.styleable.CheckedTextView_checkMark);
+        if (d != null) {
+            setCheckMarkDrawable(d);
+        }
+
+        boolean checked = a.getBoolean(R.styleable.CheckedTextView_checked, false);
+        setChecked(checked);
+
+        a.recycle();
+    }
+
+    public void toggle() {
+        setChecked(!mChecked);
+    }
+    
+    public boolean isChecked() {
+        return mChecked;
+    }
+
+    /**
+     * <p>Changes the checked state of this text view.</p>
+     *
+     * @param checked true to check the text, false to uncheck it
+     */
+    public void setChecked(boolean checked) {
+        if (mChecked != checked) {
+            mChecked = checked;
+            refreshDrawableState();
+        }
+    }
+
+
+    /**
+     * Set the checkmark to a given Drawable, identified by its resourece id. This will be drawn
+     * when {@link #isChecked()} is true.
+     * 
+     * @param resid The Drawable to use for the checkmark.
+     */
+    public void setCheckMarkDrawable(int resid) {
+        if (resid != 0 && resid == mCheckMarkResource) {
+            return;
+        }
+
+        mCheckMarkResource = resid;
+
+        Drawable d = null;
+        if (mCheckMarkResource != 0) {
+            d = getResources().getDrawable(mCheckMarkResource);
+        }
+        setCheckMarkDrawable(d);
+    }
+
+    /**
+     * Set the checkmark to a given Drawable. This will be drawn when {@link #isChecked()} is true.
+     *
+     * @param d The Drawable to use for the checkmark.
+     */
+    public void setCheckMarkDrawable(Drawable d) {
+        if (d != null) {
+            if (mCheckMarkDrawable != null) {
+                mCheckMarkDrawable.setCallback(null);
+                unscheduleDrawable(mCheckMarkDrawable);
+            }
+            d.setCallback(this);
+            d.setVisible(getVisibility() == VISIBLE, false);
+            d.setState(CHECKED_STATE_SET);
+            setMinHeight(d.getIntrinsicHeight());
+            
+            mCheckMarkWidth = d.getIntrinsicWidth();
+            mPaddingRight = mCheckMarkWidth + mBasePaddingRight;
+            d.setState(getDrawableState());
+            mCheckMarkDrawable = d;
+        } else {
+            mPaddingRight = mBasePaddingRight;
+        }
+        requestLayout();
+    }
+    
+    @Override
+    public void setPadding(int left, int top, int right, int bottom) {
+        super.setPadding(left, top, right, bottom);
+        mBasePaddingRight = mPaddingRight;
+    }
+
+    @Override
+    protected void onDraw(Canvas canvas) {
+        super.onDraw(canvas);
+
+        final Drawable checkMarkDrawable = mCheckMarkDrawable;
+        if (checkMarkDrawable != null) {
+            final int verticalGravity = getGravity() & Gravity.VERTICAL_GRAVITY_MASK;
+            final int height = checkMarkDrawable.getIntrinsicHeight();
+
+            int y = 0;
+
+            switch (verticalGravity) {
+                case Gravity.BOTTOM:
+                    y = getHeight() - height;
+                    break;
+                case Gravity.CENTER_VERTICAL:
+                    y = (getHeight() - height) / 2;
+                    break;
+            }
+            
+            int right = getWidth();
+            checkMarkDrawable.setBounds(
+                    right - mCheckMarkWidth - mBasePaddingRight, 
+                    y, 
+                    right - mBasePaddingRight, 
+                    y + height);
+            checkMarkDrawable.draw(canvas);
+        }
+    }
+    
+    @Override
+    protected int[] onCreateDrawableState(int extraSpace) {
+        final int[] drawableState = super.onCreateDrawableState(extraSpace + 1);
+        if (isChecked()) {
+            mergeDrawableStates(drawableState, CHECKED_STATE_SET);
+        }
+        return drawableState;
+    }
+
+    @Override
+    protected void drawableStateChanged() {
+        super.drawableStateChanged();
+        
+        if (mCheckMarkDrawable != null) {
+            int[] myDrawableState = getDrawableState();
+            
+            // Set the state of the Drawable
+            mCheckMarkDrawable.setState(myDrawableState);
+            
+            invalidate();
+        }
+    }
+    
+}
diff --git a/core/java/android/widget/Chronometer.java b/core/java/android/widget/Chronometer.java
new file mode 100644
index 0000000..91add58
--- /dev/null
+++ b/core/java/android/widget/Chronometer.java
@@ -0,0 +1,278 @@
+/*
+ * 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 android.widget;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Canvas;
+import android.os.Handler;
+import android.os.Message;
+import android.os.SystemClock;
+import android.text.format.DateUtils;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.widget.RemoteViews.RemoteView;
+
+import java.util.Formatter;
+import java.util.IllegalFormatException;
+import java.util.Locale;
+
+/**
+ * Class that implements a simple timer.
+ * <p>
+ * You can give it a start time in the {@link SystemClock#elapsedRealtime} timebase,
+ * and it counts up from that, or if you don't give it a base time, it will use the
+ * time at which you call {@link #start}.  By default it will display the current
+ * timer value in the form "MM:SS" or "H:MM:SS", or you can use {@link #setFormat}
+ * to format the timer value into an arbitrary string.
+ *
+ * @attr ref android.R.styleable#Chronometer_format
+ */
+@RemoteView
+public class Chronometer extends TextView {
+    private static final String TAG = "Chronometer";
+
+    /**
+     * A callback that notifies when the chronometer has incremented on its own.
+     */
+    public interface OnChronometerTickListener {
+
+        /**
+         * Notification that the chronometer has changed.
+         */
+        void onChronometerTick(Chronometer chronometer);
+
+    }
+
+    private long mBase;
+    private boolean mVisible;
+    private boolean mStarted;
+    private boolean mRunning;
+    private boolean mLogged;
+    private String mFormat;
+    private Formatter mFormatter;
+    private Locale mFormatterLocale;
+    private Object[] mFormatterArgs = new Object[1];
+    private StringBuilder mFormatBuilder;
+    private OnChronometerTickListener mOnChronometerTickListener;
+    private StringBuilder mRecycle = new StringBuilder(8);
+    
+    private static final int TICK_WHAT = 2;
+    
+    /**
+     * Initialize this Chronometer object.
+     * Sets the base to the current time.
+     */
+    public Chronometer(Context context) {
+        this(context, null, 0);
+    }
+
+    /**
+     * Initialize with standard view layout information.
+     * Sets the base to the current time.
+     */
+    public Chronometer(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    /**
+     * Initialize with standard view layout information and style.
+     * Sets the base to the current time.
+     */
+    public Chronometer(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+
+        TypedArray a = context.obtainStyledAttributes(
+                attrs,
+                com.android.internal.R.styleable.Chronometer, defStyle, 0);
+        setFormat(a.getString(com.android.internal.R.styleable.Chronometer_format));
+        a.recycle();
+
+        init();
+    }
+
+    private void init() {
+        mBase = SystemClock.elapsedRealtime();
+        updateText(mBase);
+    }
+
+    /**
+     * Set the time that the count-up timer is in reference to.
+     *
+     * @param base Use the {@link SystemClock#elapsedRealtime} time base.
+     */
+    @android.view.RemotableViewMethod
+    public void setBase(long base) {
+        mBase = base;
+        dispatchChronometerTick();
+        updateText(SystemClock.elapsedRealtime());
+    }
+
+    /**
+     * Return the base time as set through {@link #setBase}.
+     */
+    public long getBase() {
+        return mBase;
+    }
+
+    /**
+     * Sets the format string used for display.  The Chronometer will display
+     * this string, with the first "%s" replaced by the current timer value in
+     * "MM:SS" or "H:MM:SS" form.
+     *
+     * If the format string is null, or if you never call setFormat(), the
+     * Chronometer will simply display the timer value in "MM:SS" or "H:MM:SS"
+     * form.
+     *
+     * @param format the format string.
+     */
+    @android.view.RemotableViewMethod
+    public void setFormat(String format) {
+        mFormat = format;
+        if (format != null && mFormatBuilder == null) {
+            mFormatBuilder = new StringBuilder(format.length() * 2);
+        }
+    }
+
+    /**
+     * Returns the current format string as set through {@link #setFormat}.
+     */
+    public String getFormat() {
+        return mFormat;
+    }
+
+    /**
+     * Sets the listener to be called when the chronometer changes.
+     * 
+     * @param listener The listener.
+     */
+    public void setOnChronometerTickListener(OnChronometerTickListener listener) {
+        mOnChronometerTickListener = listener;
+    }
+
+    /**
+     * @return The listener (may be null) that is listening for chronometer change
+     *         events.
+     */
+    public OnChronometerTickListener getOnChronometerTickListener() {
+        return mOnChronometerTickListener;
+    }
+
+    /**
+     * Start counting up.  This does not affect the base as set from {@link #setBase}, just
+     * the view display.
+     * 
+     * Chronometer works by regularly scheduling messages to the handler, even when the 
+     * Widget is not visible.  To make sure resource leaks do not occur, the user should 
+     * make sure that each start() call has a reciprocal call to {@link #stop}. 
+     */
+    public void start() {
+        mStarted = true;
+        updateRunning();
+    }
+
+    /**
+     * Stop counting up.  This does not affect the base as set from {@link #setBase}, just
+     * the view display.
+     * 
+     * This stops the messages to the handler, effectively releasing resources that would
+     * be held as the chronometer is running, via {@link #start}. 
+     */
+    public void stop() {
+        mStarted = false;
+        updateRunning();
+    }
+
+    /**
+     * The same as calling {@link #start} or {@link #stop}.
+     */
+    @android.view.RemotableViewMethod
+    public void setStarted(boolean started) {
+        mStarted = started;
+        updateRunning();
+    }
+
+    @Override
+    protected void onDetachedFromWindow() {
+        super.onDetachedFromWindow();
+        mVisible = false;
+        updateRunning();
+    }
+
+    @Override
+    protected void onWindowVisibilityChanged(int visibility) {
+        super.onWindowVisibilityChanged(visibility);
+        mVisible = visibility == VISIBLE;
+        updateRunning();
+    }
+
+    private synchronized void updateText(long now) {
+        long seconds = now - mBase;
+        seconds /= 1000;
+        String text = DateUtils.formatElapsedTime(mRecycle, seconds);
+
+        if (mFormat != null) {
+            Locale loc = Locale.getDefault();
+            if (mFormatter == null || !loc.equals(mFormatterLocale)) {
+                mFormatterLocale = loc;
+                mFormatter = new Formatter(mFormatBuilder, loc);
+            }
+            mFormatBuilder.setLength(0);
+            mFormatterArgs[0] = text;
+            try {
+                mFormatter.format(mFormat, mFormatterArgs);
+                text = mFormatBuilder.toString();
+            } catch (IllegalFormatException ex) {
+                if (!mLogged) {
+                    Log.w(TAG, "Illegal format string: " + mFormat);
+                    mLogged = true;
+                }
+            }
+        }
+        setText(text);
+    }
+
+    private void updateRunning() {
+        boolean running = mVisible && mStarted;
+        if (running != mRunning) {
+            if (running) {
+                updateText(SystemClock.elapsedRealtime());
+                dispatchChronometerTick();
+                mHandler.sendMessageDelayed(Message.obtain(mHandler, TICK_WHAT), 1000);
+            } else {
+                mHandler.removeMessages(TICK_WHAT);
+            }
+            mRunning = running;
+        }
+    }
+    
+    private Handler mHandler = new Handler() {
+        public void handleMessage(Message m) {
+            if (mRunning) {
+                updateText(SystemClock.elapsedRealtime());
+                dispatchChronometerTick();
+                sendMessageDelayed(Message.obtain(this, TICK_WHAT), 1000);
+            }
+        }
+    };
+
+    void dispatchChronometerTick() {
+        if (mOnChronometerTickListener != null) {
+            mOnChronometerTickListener.onChronometerTick(this);
+        }
+    }
+}
diff --git a/core/java/android/widget/CompoundButton.java b/core/java/android/widget/CompoundButton.java
new file mode 100644
index 0000000..d4482dc
--- /dev/null
+++ b/core/java/android/widget/CompoundButton.java
@@ -0,0 +1,323 @@
+/*
+ * Copyright (C) 2007 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 android.widget;
+
+import com.android.internal.R;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Canvas;
+import android.graphics.drawable.Drawable;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.AttributeSet;
+import android.view.Gravity;
+
+
+/**
+ * <p>
+ * A button with two states, checked and unchecked. When the button is pressed
+ * or clicked, the state changes automatically.
+ * </p>
+ *
+ * <p><strong>XML attributes</strong></p>
+ * <p>
+ * See {@link android.R.styleable#CompoundButton
+ * CompoundButton Attributes}, {@link android.R.styleable#Button Button
+ * Attributes}, {@link android.R.styleable#TextView TextView Attributes}, {@link
+ * android.R.styleable#View View Attributes}
+ * </p>
+ */
+public abstract class CompoundButton extends Button implements Checkable {
+    private boolean mChecked;
+    private int mButtonResource;
+    private boolean mBroadcasting;
+    private Drawable mButtonDrawable;
+    private OnCheckedChangeListener mOnCheckedChangeListener;
+    private OnCheckedChangeListener mOnCheckedChangeWidgetListener;
+
+    private static final int[] CHECKED_STATE_SET = {
+        R.attr.state_checked
+    };
+
+    public CompoundButton(Context context) {
+        this(context, null);
+    }
+
+    public CompoundButton(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public CompoundButton(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+
+        TypedArray a =
+                context.obtainStyledAttributes(
+                        attrs, com.android.internal.R.styleable.CompoundButton, defStyle, 0);
+
+        Drawable d = a.getDrawable(com.android.internal.R.styleable.CompoundButton_button);
+        if (d != null) {
+            setButtonDrawable(d);
+        }
+
+        boolean checked = a
+                .getBoolean(com.android.internal.R.styleable.CompoundButton_checked, false);
+        setChecked(checked);
+
+        a.recycle();
+    }
+
+    public void toggle() {
+        setChecked(!mChecked);
+    }
+
+    @Override
+    public boolean performClick() {
+        /*
+         * XXX: These are tiny, need some surrounding 'expanded touch area',
+         * which will need to be implemented in Button if we only override
+         * performClick()
+         */
+
+        /* When clicked, toggle the state */
+        toggle();
+        return super.performClick();
+    }
+
+    public boolean isChecked() {
+        return mChecked;
+    }
+
+    /**
+     * <p>Changes the checked state of this button.</p>
+     *
+     * @param checked true to check the button, false to uncheck it
+     */
+    public void setChecked(boolean checked) {
+        if (mChecked != checked) {
+            mChecked = checked;
+            refreshDrawableState();
+
+            // Avoid infinite recursions if setChecked() is called from a listener
+            if (mBroadcasting) {
+                return;
+            }
+
+            mBroadcasting = true;
+            if (mOnCheckedChangeListener != null) {
+                mOnCheckedChangeListener.onCheckedChanged(this, mChecked);
+            }
+            if (mOnCheckedChangeWidgetListener != null) {
+                mOnCheckedChangeWidgetListener.onCheckedChanged(this, mChecked);
+            }
+            mBroadcasting = false;            
+        }
+    }
+
+    /**
+     * Register a callback to be invoked when the checked state of this button
+     * changes.
+     *
+     * @param listener the callback to call on checked state change
+     */
+    public void setOnCheckedChangeListener(OnCheckedChangeListener listener) {
+        mOnCheckedChangeListener = listener;
+    }
+
+    /**
+     * Register a callback to be invoked when the checked state of this button
+     * changes. This callback is used for internal purpose only.
+     *
+     * @param listener the callback to call on checked state change
+     * @hide
+     */
+    void setOnCheckedChangeWidgetListener(OnCheckedChangeListener listener) {
+        mOnCheckedChangeWidgetListener = listener;
+    }
+
+    /**
+     * Interface definition for a callback to be invoked when the checked state
+     * of a compound button changed.
+     */
+    public static interface OnCheckedChangeListener {
+        /**
+         * Called when the checked state of a compound button has changed.
+         *
+         * @param buttonView The compound button view whose state has changed.
+         * @param isChecked  The new checked state of buttonView.
+         */
+        void onCheckedChanged(CompoundButton buttonView, boolean isChecked);
+    }
+
+    /**
+     * Set the background to a given Drawable, identified by its resource id.
+     *
+     * @param resid the resource id of the drawable to use as the background 
+     */
+    public void setButtonDrawable(int resid) {
+        if (resid != 0 && resid == mButtonResource) {
+            return;
+        }
+
+        mButtonResource = resid;
+
+        Drawable d = null;
+        if (mButtonResource != 0) {
+            d = getResources().getDrawable(mButtonResource);
+        }
+        setButtonDrawable(d);
+    }
+
+    /**
+     * Set the background to a given Drawable
+     *
+     * @param d The Drawable to use as the background
+     */
+    public void setButtonDrawable(Drawable d) {
+        if (d != null) {
+            if (mButtonDrawable != null) {
+                mButtonDrawable.setCallback(null);
+                unscheduleDrawable(mButtonDrawable);
+            }
+            d.setCallback(this);
+            d.setState(getDrawableState());
+            d.setVisible(getVisibility() == VISIBLE, false);
+            mButtonDrawable = d;
+            mButtonDrawable.setState(null);
+            setMinHeight(mButtonDrawable.getIntrinsicHeight());
+        }
+
+        refreshDrawableState();
+    }
+
+    @Override
+    protected void onDraw(Canvas canvas) {
+        super.onDraw(canvas);
+
+        final Drawable buttonDrawable = mButtonDrawable;
+        if (buttonDrawable != null) {
+            final int verticalGravity = getGravity() & Gravity.VERTICAL_GRAVITY_MASK;
+            final int height = buttonDrawable.getIntrinsicHeight();
+
+            int y = 0;
+
+            switch (verticalGravity) {
+                case Gravity.BOTTOM:
+                    y = getHeight() - height;
+                    break;
+                case Gravity.CENTER_VERTICAL:
+                    y = (getHeight() - height) / 2;
+                    break;
+            }
+
+            buttonDrawable.setBounds(0, y, buttonDrawable.getIntrinsicWidth(), y + height);
+            buttonDrawable.draw(canvas);
+        }
+    }
+
+    @Override
+    protected int[] onCreateDrawableState(int extraSpace) {
+        final int[] drawableState = super.onCreateDrawableState(extraSpace + 1);
+        if (isChecked()) {
+            mergeDrawableStates(drawableState, CHECKED_STATE_SET);
+        }
+        return drawableState;
+    }
+
+    @Override
+    protected void drawableStateChanged() {
+        super.drawableStateChanged();
+        
+        if (mButtonDrawable != null) {
+            int[] myDrawableState = getDrawableState();
+            
+            // Set the state of the Drawable
+            mButtonDrawable.setState(myDrawableState);
+            
+            invalidate();
+        }
+    }
+
+    @Override
+    protected boolean verifyDrawable(Drawable who) {
+        return super.verifyDrawable(who) || who == mButtonDrawable;
+    }
+
+    static class SavedState extends BaseSavedState {
+        boolean checked;
+
+        /**
+         * Constructor called from {@link CompoundButton#onSaveInstanceState()}
+         */
+        SavedState(Parcelable superState) {
+            super(superState);
+        }
+        
+        /**
+         * Constructor called from {@link #CREATOR}
+         */
+        private SavedState(Parcel in) {
+            super(in);
+            checked = (Boolean)in.readValue(null);
+        }
+
+        @Override
+        public void writeToParcel(Parcel out, int flags) {
+            super.writeToParcel(out, flags);
+            out.writeValue(checked);
+        }
+
+        @Override
+        public String toString() {
+            return "CompoundButton.SavedState{"
+                    + Integer.toHexString(System.identityHashCode(this))
+                    + " checked=" + checked + "}";
+        }
+
+        public static final Parcelable.Creator<SavedState> CREATOR
+                = new Parcelable.Creator<SavedState>() {
+            public SavedState createFromParcel(Parcel in) {
+                return new SavedState(in);
+            }
+
+            public SavedState[] newArray(int size) {
+                return new SavedState[size];
+            }
+        };
+    }
+
+    @Override
+    public Parcelable onSaveInstanceState() {
+        // Force our ancestor class to save its state
+        setFreezesText(true);
+        Parcelable superState = super.onSaveInstanceState();
+
+        SavedState ss = new SavedState(superState);
+
+        ss.checked = isChecked();
+        return ss;
+    }
+
+    @Override
+    public void onRestoreInstanceState(Parcelable state) {
+        SavedState ss = (SavedState) state;
+  
+        super.onRestoreInstanceState(ss.getSuperState());
+        setChecked(ss.checked);
+        requestLayout();
+    }
+}
diff --git a/core/java/android/widget/CursorAdapter.java b/core/java/android/widget/CursorAdapter.java
new file mode 100644
index 0000000..898e501
--- /dev/null
+++ b/core/java/android/widget/CursorAdapter.java
@@ -0,0 +1,396 @@
+/*
+ * 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 android.widget;
+
+import android.content.Context;
+import android.database.ContentObserver;
+import android.database.Cursor;
+import android.database.DataSetObserver;
+import android.os.Handler;
+import android.util.Config;
+import android.util.Log;
+import android.view.View;
+import android.view.ViewGroup;
+
+/**
+ * Adapter that exposes data from a {@link android.database.Cursor Cursor} to a 
+ * {@link android.widget.ListView ListView} widget. The Cursor must include 
+ * a column named "_id" or this class will not work.
+ */
+public abstract class CursorAdapter extends BaseAdapter implements Filterable,
+        CursorFilter.CursorFilterClient {
+    /**
+     * This field should be made private, so it is hidden from the SDK.
+     * {@hide}
+     */
+    protected boolean mDataValid;
+    /**
+     * This field should be made private, so it is hidden from the SDK.
+     * {@hide}
+     */
+    protected boolean mAutoRequery;
+    /**
+     * This field should be made private, so it is hidden from the SDK.
+     * {@hide}
+     */
+    protected Cursor mCursor;
+    /**
+     * This field should be made private, so it is hidden from the SDK.
+     * {@hide}
+     */
+    protected Context mContext;
+    /**
+     * This field should be made private, so it is hidden from the SDK.
+     * {@hide}
+     */
+    protected int mRowIDColumn;
+    /**
+     * This field should be made private, so it is hidden from the SDK.
+     * {@hide}
+     */
+    protected ChangeObserver mChangeObserver;
+    /**
+     * This field should be made private, so it is hidden from the SDK.
+     * {@hide}
+     */
+    protected DataSetObserver mDataSetObserver = new MyDataSetObserver();
+    /**
+     * This field should be made private, so it is hidden from the SDK.
+     * {@hide}
+     */
+    protected CursorFilter mCursorFilter;
+    /**
+     * This field should be made private, so it is hidden from the SDK.
+     * {@hide}
+     */
+    protected FilterQueryProvider mFilterQueryProvider;
+
+    /**
+     * Constructor. The adapter will call requery() on the cursor whenever
+     * it changes so that the most recent data is always displayed.
+     *
+     * @param c The cursor from which to get the data.
+     * @param context The context
+     */
+    public CursorAdapter(Context context, Cursor c) {
+        init(context, c, true);
+    }
+
+    /**
+     * Constructor
+     * @param c The cursor from which to get the data.
+     * @param context The context
+     * @param autoRequery If true the adapter will call requery() on the
+     *                    cursor whenever it changes so the most recent
+     *                    data is always displayed.
+     */
+    public CursorAdapter(Context context, Cursor c, boolean autoRequery) {
+        init(context, c, autoRequery);
+    }
+
+    protected void init(Context context, Cursor c, boolean autoRequery) {
+        boolean cursorPresent = c != null;
+        mAutoRequery = autoRequery;
+        mCursor = c;
+        mDataValid = cursorPresent;
+        mContext = context;
+        mRowIDColumn = cursorPresent ? c.getColumnIndexOrThrow("_id") : -1;
+        mChangeObserver = new ChangeObserver();
+        if (cursorPresent) {
+            c.registerContentObserver(mChangeObserver);
+            c.registerDataSetObserver(mDataSetObserver);
+        }
+    }
+
+    /**
+     * Returns the cursor.
+     * @return the cursor.
+     */
+    public Cursor getCursor() {
+        return mCursor;
+    }
+
+    /**
+     * @see android.widget.ListAdapter#getCount()
+     */
+    public final int getCount() {
+        if (mDataValid && mCursor != null) {
+            return mCursor.getCount();
+        } else {
+            return 0;
+        }
+    }
+    
+    /**
+     * @see android.widget.ListAdapter#getItem(int)
+     */
+    public final Object getItem(int position) {
+        if (mDataValid && mCursor != null) {
+            mCursor.moveToPosition(position);
+            return mCursor;
+        } else {
+            return null;
+        }
+    }
+
+    /**
+     * @see android.widget.ListAdapter#getItemId(int)
+     */
+    public final long getItemId(int position) {
+        if (mDataValid && mCursor != null) {
+            if (mCursor.moveToPosition(position)) {
+                return mCursor.getLong(mRowIDColumn);
+            } else {
+                return 0;
+            }
+        } else {
+            return 0;
+        }
+    }
+    
+    @Override
+    public boolean hasStableIds() {
+        return true;
+    }
+
+    /**
+     * @see android.widget.ListAdapter#getView(int, View, ViewGroup)
+     */
+    public View getView(int position, View convertView, ViewGroup parent) {
+        if (!mDataValid) {
+            throw new IllegalStateException("this should only be called when the cursor is valid");
+        }
+        if (!mCursor.moveToPosition(position)) {
+            throw new IllegalStateException("couldn't move cursor to position " + position);
+        }
+        View v;
+        if (convertView == null) {
+            v = newView(mContext, mCursor, parent);
+        } else {
+            v = convertView;
+        }
+        bindView(v, mContext, mCursor);
+        return v;
+    }
+
+    @Override
+    public View getDropDownView(int position, View convertView, ViewGroup parent) {
+        if (mDataValid) {
+            mCursor.moveToPosition(position);
+            View v;
+            if (convertView == null) {
+                v = newDropDownView(mContext, mCursor, parent);
+            } else {
+                v = convertView;
+            }
+            bindView(v, mContext, mCursor);
+            return v;
+        } else {
+            return null;
+        }
+    }
+    
+    /**
+     * Makes a new view to hold the data pointed to by cursor.
+     * @param context Interface to application's global information
+     * @param cursor The cursor from which to get the data. The cursor is already
+     * moved to the correct position.
+     * @param parent The parent to which the new view is attached to
+     * @return the newly created view.
+     */
+    public abstract View newView(Context context, Cursor cursor, ViewGroup parent);
+
+    /**
+     * Makes a new drop down view to hold the data pointed to by cursor.
+     * @param context Interface to application's global information
+     * @param cursor The cursor from which to get the data. The cursor is already
+     * moved to the correct position.
+     * @param parent The parent to which the new view is attached to
+     * @return the newly created view.
+     */
+    public View newDropDownView(Context context, Cursor cursor, ViewGroup parent) {
+        return newView(context, cursor, parent);
+    }
+
+    /**
+     * Bind an existing view to the data pointed to by cursor
+     * @param view Existing view, returned earlier by newView
+     * @param context Interface to application's global information
+     * @param cursor The cursor from which to get the data. The cursor is already
+     * moved to the correct position.
+     */
+    public abstract void bindView(View view, Context context, Cursor cursor);
+    
+    /**
+     * Change the underlying cursor to a new cursor. If there is an existing cursor it will be
+     * closed.
+     * 
+     * @param cursor the new cursor to be used
+     */
+    public void changeCursor(Cursor cursor) {
+        if (cursor == mCursor) {
+            return;
+        }
+        if (mCursor != null) {
+            mCursor.unregisterContentObserver(mChangeObserver);
+            mCursor.unregisterDataSetObserver(mDataSetObserver);
+            mCursor.close();
+        }
+        mCursor = cursor;
+        if (cursor != null) {
+            cursor.registerContentObserver(mChangeObserver);
+            cursor.registerDataSetObserver(mDataSetObserver);
+            mRowIDColumn = cursor.getColumnIndexOrThrow("_id");
+            mDataValid = true;
+            // notify the observers about the new cursor
+            notifyDataSetChanged();
+        } else {
+            mRowIDColumn = -1;
+            mDataValid = false;
+            // notify the observers about the lack of a data set
+            notifyDataSetInvalidated();
+        }
+    }
+
+    /**
+     * <p>Converts the cursor into a CharSequence. Subclasses should override this
+     * method to convert their results. The default implementation returns an
+     * empty String for null values or the default String representation of
+     * the value.</p>
+     *
+     * @param cursor the cursor to convert to a CharSequence
+     * @return a CharSequence representing the value
+     */
+    public CharSequence convertToString(Cursor cursor) {
+        return cursor == null ? "" : cursor.toString();
+    }
+
+    /**
+     * Runs a query with the specified constraint. This query is requested
+     * by the filter attached to this adapter.
+     *
+     * The query is provided by a
+     * {@link android.widget.FilterQueryProvider}.
+     * If no provider is specified, the current cursor is not filtered and returned.
+     *
+     * After this method returns the resulting cursor is passed to {@link #changeCursor(Cursor)}
+     * and the previous cursor is closed.
+     *
+     * This method is always executed on a background thread, not on the
+     * application's main thread (or UI thread.)
+     * 
+     * Contract: when constraint is null or empty, the original results,
+     * prior to any filtering, must be returned.
+     *
+     * @param constraint the constraint with which the query must be filtered
+     *
+     * @return a Cursor representing the results of the new query
+     *
+     * @see #getFilter()
+     * @see #getFilterQueryProvider()
+     * @see #setFilterQueryProvider(android.widget.FilterQueryProvider)
+     */
+    public Cursor runQueryOnBackgroundThread(CharSequence constraint) {
+        if (mFilterQueryProvider != null) {
+            return mFilterQueryProvider.runQuery(constraint);
+        }
+
+        return mCursor;
+    }
+
+    public Filter getFilter() {
+        if (mCursorFilter == null) {
+            mCursorFilter = new CursorFilter(this);
+        }
+        return mCursorFilter;
+    }
+
+    /**
+     * Returns the query filter provider used for filtering. When the
+     * provider is null, no filtering occurs.
+     *
+     * @return the current filter query provider or null if it does not exist
+     *
+     * @see #setFilterQueryProvider(android.widget.FilterQueryProvider)
+     * @see #runQueryOnBackgroundThread(CharSequence)
+     */
+    public FilterQueryProvider getFilterQueryProvider() {
+        return mFilterQueryProvider;
+    }
+
+    /**
+     * Sets the query filter provider used to filter the current Cursor.
+     * The provider's
+     * {@link android.widget.FilterQueryProvider#runQuery(CharSequence)}
+     * method is invoked when filtering is requested by a client of
+     * this adapter.
+     *
+     * @param filterQueryProvider the filter query provider or null to remove it
+     *
+     * @see #getFilterQueryProvider()
+     * @see #runQueryOnBackgroundThread(CharSequence)
+     */
+    public void setFilterQueryProvider(FilterQueryProvider filterQueryProvider) {
+        mFilterQueryProvider = filterQueryProvider;
+    }
+
+    /**
+     * Called when the {@link ContentObserver} on the cursor receives a change notification.
+     * The default implementation provides the auto-requery logic, but may be overridden by
+     * sub classes.
+     * 
+     * @see ContentObserver#onChange(boolean)
+     * @hide pending API Council approval
+     */
+    protected void onContentChanged() {
+        if (mAutoRequery && mCursor != null && !mCursor.isClosed()) {
+            if (Config.LOGV) Log.v("Cursor", "Auto requerying " + mCursor + " due to update");
+            mDataValid = mCursor.requery();
+        }
+    }
+
+    private class ChangeObserver extends ContentObserver {
+        public ChangeObserver() {
+            super(new Handler());
+        }
+
+        @Override
+        public boolean deliverSelfNotifications() {
+            return true;
+        }
+
+        @Override
+        public void onChange(boolean selfChange) {
+            onContentChanged();
+        }
+    }
+
+    private class MyDataSetObserver extends DataSetObserver {
+        @Override
+        public void onChanged() {
+            mDataValid = true;
+            notifyDataSetChanged();
+        }
+
+        @Override
+        public void onInvalidated() {
+            mDataValid = false;
+            notifyDataSetInvalidated();
+        }
+    }
+
+}
diff --git a/core/java/android/widget/CursorFilter.java b/core/java/android/widget/CursorFilter.java
new file mode 100644
index 0000000..dbded69
--- /dev/null
+++ b/core/java/android/widget/CursorFilter.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2007 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 android.widget;
+
+import android.database.Cursor;
+
+/**
+ * <p>The CursorFilter delegates most of the work to the CursorAdapter.
+ * Subclasses should override these delegate methods to run the queries
+ * and convert the results into String that can be used by auto-completion
+ * widgets.</p>
+ */
+class CursorFilter extends Filter {
+    
+    CursorFilterClient mClient;
+    
+    interface CursorFilterClient {
+        CharSequence convertToString(Cursor cursor);
+        Cursor runQueryOnBackgroundThread(CharSequence constraint);
+        Cursor getCursor();
+        void changeCursor(Cursor cursor);
+    }
+
+    CursorFilter(CursorFilterClient client) {
+        mClient = client;
+    }
+    
+    @Override
+    public CharSequence convertResultToString(Object resultValue) {
+        return mClient.convertToString((Cursor) resultValue);
+    }
+
+    @Override
+    protected FilterResults performFiltering(CharSequence constraint) {
+        Cursor cursor = mClient.runQueryOnBackgroundThread(constraint);
+
+        FilterResults results = new FilterResults();
+        if (cursor != null) {
+            results.count = cursor.getCount();
+            results.values = cursor;
+        } else {
+            results.count = 0;
+            results.values = null;
+        }
+        return results;
+    }
+
+    @Override
+    protected void publishResults(CharSequence constraint, FilterResults results) {
+        Cursor oldCursor = mClient.getCursor();
+        
+        if (results.values != null && results.values != oldCursor) {
+            mClient.changeCursor((Cursor) results.values);
+        }
+    }
+}
diff --git a/core/java/android/widget/CursorTreeAdapter.java b/core/java/android/widget/CursorTreeAdapter.java
new file mode 100644
index 0000000..7b9b7bd
--- /dev/null
+++ b/core/java/android/widget/CursorTreeAdapter.java
@@ -0,0 +1,521 @@
+/*
+ * Copyright (C) 2007 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 android.widget;
+
+import android.app.Activity;
+import android.content.Context;
+import android.database.ContentObserver;
+import android.database.Cursor;
+import android.database.DataSetObserver;
+import android.os.Handler;
+import android.util.Config;
+import android.util.Log;
+import android.util.SparseArray;
+import android.view.View;
+import android.view.ViewGroup;
+
+/**
+ * An adapter that exposes data from a series of {@link Cursor}s to an
+ * {@link ExpandableListView} widget. The top-level {@link Cursor} (that is
+ * given in the constructor) exposes the groups, while subsequent {@link Cursor}s
+ * returned from {@link #getChildrenCursor(Cursor)} expose children within a
+ * particular group. The Cursors must include a column named "_id" or this class
+ * will not work.
+ */
+public abstract class CursorTreeAdapter extends BaseExpandableListAdapter implements Filterable,
+        CursorFilter.CursorFilterClient {
+    private Context mContext;
+    private Handler mHandler;
+    private boolean mAutoRequery;
+
+    /** The cursor helper that is used to get the groups */
+    MyCursorHelper mGroupCursorHelper;
+    
+    /**
+     * The map of a group position to the group's children cursor helper (the
+     * cursor helper that is used to get the children for that group)
+     */
+    SparseArray<MyCursorHelper> mChildrenCursorHelpers;
+
+    // Filter related
+    CursorFilter mCursorFilter;
+    FilterQueryProvider mFilterQueryProvider;
+    
+    /**
+     * Constructor. The adapter will call {@link Cursor#requery()} on the cursor whenever
+     * it changes so that the most recent data is always displayed.
+     *
+     * @param cursor The cursor from which to get the data for the groups.
+     */
+    public CursorTreeAdapter(Cursor cursor, Context context) {
+        init(cursor, context, true);
+    }
+
+    /**
+     * Constructor.
+     * 
+     * @param cursor The cursor from which to get the data for the groups.
+     * @param context The context
+     * @param autoRequery If true the adapter will call {@link Cursor#requery()}
+     *        on the cursor whenever it changes so the most recent data is
+     *        always displayed.
+     */
+    public CursorTreeAdapter(Cursor cursor, Context context, boolean autoRequery) {
+        init(cursor, context, autoRequery);
+    }
+    
+    private void init(Cursor cursor, Context context, boolean autoRequery) {
+        mContext = context;
+        mHandler = new Handler();
+        mAutoRequery = autoRequery;
+        
+        mGroupCursorHelper = new MyCursorHelper(cursor);
+        mChildrenCursorHelpers = new SparseArray<MyCursorHelper>();
+    }
+
+    /**
+     * Gets the cursor helper for the children in the given group.
+     * 
+     * @param groupPosition The group whose children will be returned
+     * @param requestCursor Whether to request a Cursor via
+     *            {@link #getChildrenCursor(Cursor)} (true), or to assume a call
+     *            to {@link #setChildrenCursor(int, Cursor)} will happen shortly
+     *            (false).
+     * @return The cursor helper for the children of the given group
+     */
+    synchronized MyCursorHelper getChildrenCursorHelper(int groupPosition, boolean requestCursor) {
+        MyCursorHelper cursorHelper = mChildrenCursorHelpers.get(groupPosition);
+        
+        if (cursorHelper == null) {
+            if (mGroupCursorHelper.moveTo(groupPosition) == null) return null;
+            
+            final Cursor cursor = getChildrenCursor(mGroupCursorHelper.getCursor());
+            cursorHelper = new MyCursorHelper(cursor);
+            mChildrenCursorHelpers.put(groupPosition, cursorHelper);
+        }
+        
+        return cursorHelper;
+    }
+
+    /**
+     * Gets the Cursor for the children at the given group. Subclasses must
+     * implement this method to return the children data for a particular group.
+     * <p>
+     * If you want to asynchronously query a provider to prevent blocking the
+     * UI, it is possible to return null and at a later time call
+     * {@link #setChildrenCursor(int, Cursor)}.
+     * <p>
+     * It is your responsibility to manage this Cursor through the Activity
+     * lifecycle. It is a good idea to use {@link Activity#managedQuery} which
+     * will handle this for you. In some situations, the adapter will deactivate
+     * the Cursor on its own, but this will not always be the case, so please
+     * ensure the Cursor is properly managed.
+     * 
+     * @param groupCursor The cursor pointing to the group whose children cursor
+     *            should be returned
+     * @return The cursor for the children of a particular group, or null.
+     */
+    abstract protected Cursor getChildrenCursor(Cursor groupCursor);
+    
+    /**
+     * Sets the group Cursor.
+     * 
+     * @param cursor The Cursor to set for the group.
+     */
+    public void setGroupCursor(Cursor cursor) {
+        mGroupCursorHelper.changeCursor(cursor, false);
+    }
+    
+    /**
+     * Sets the children Cursor for a particular group.
+     * <p>
+     * This is useful when asynchronously querying to prevent blocking the UI.
+     * 
+     * @param groupPosition The group whose children are being set via this Cursor.
+     * @param childrenCursor The Cursor that contains the children of the group.
+     */
+    public void setChildrenCursor(int groupPosition, Cursor childrenCursor) {
+        
+        /*
+         * Don't request a cursor from the subclass, instead we will be setting
+         * the cursor ourselves.
+         */
+        MyCursorHelper childrenCursorHelper = getChildrenCursorHelper(groupPosition, false);
+
+        /*
+         * Don't release any cursor since we know exactly what data is changing
+         * (this cursor, which is still valid).
+         */
+        childrenCursorHelper.changeCursor(childrenCursor, false);
+    }
+    
+    public Cursor getChild(int groupPosition, int childPosition) {
+        // Return this group's children Cursor pointing to the particular child
+        return getChildrenCursorHelper(groupPosition, true).moveTo(childPosition);
+    }
+
+    public long getChildId(int groupPosition, int childPosition) {
+        return getChildrenCursorHelper(groupPosition, true).getId(childPosition);
+    }
+
+    public int getChildrenCount(int groupPosition) {
+        MyCursorHelper helper = getChildrenCursorHelper(groupPosition, true);
+        return (mGroupCursorHelper.isValid() && helper != null) ? helper.getCount() : 0;
+    }
+
+    public Cursor getGroup(int groupPosition) {
+        // Return the group Cursor pointing to the given group
+        return mGroupCursorHelper.moveTo(groupPosition);
+    }
+
+    public int getGroupCount() {
+        return mGroupCursorHelper.getCount();
+    }
+
+    public long getGroupId(int groupPosition) {
+        return mGroupCursorHelper.getId(groupPosition);
+    }
+
+    public View getGroupView(int groupPosition, boolean isExpanded, View convertView,
+            ViewGroup parent) {
+        Cursor cursor = mGroupCursorHelper.moveTo(groupPosition);
+        if (cursor == null) {
+            throw new IllegalStateException("this should only be called when the cursor is valid");
+        }
+        
+        View v;
+        if (convertView == null) {
+            v = newGroupView(mContext, cursor, isExpanded, parent);
+        } else {
+            v = convertView;
+        }
+        bindGroupView(v, mContext, cursor, isExpanded);
+        return v;
+    }
+
+    /**
+     * Makes a new group view to hold the group data pointed to by cursor.
+     * 
+     * @param context Interface to application's global information
+     * @param cursor The group cursor from which to get the data. The cursor is
+     *            already moved to the correct position.
+     * @param isExpanded Whether the group is expanded.
+     * @param parent The parent to which the new view is attached to
+     * @return The newly created view.
+     */
+    protected abstract View newGroupView(Context context, Cursor cursor, boolean isExpanded,
+            ViewGroup parent);
+
+    /**
+     * Bind an existing view to the group data pointed to by cursor.
+     * 
+     * @param view Existing view, returned earlier by newGroupView.
+     * @param context Interface to application's global information
+     * @param cursor The cursor from which to get the data. The cursor is
+     *            already moved to the correct position.
+     * @param isExpanded Whether the group is expanded.
+     */
+    protected abstract void bindGroupView(View view, Context context, Cursor cursor,
+            boolean isExpanded);
+
+    public View getChildView(int groupPosition, int childPosition, boolean isLastChild,
+            View convertView, ViewGroup parent) {
+        MyCursorHelper cursorHelper = getChildrenCursorHelper(groupPosition, true);
+        
+        Cursor cursor = cursorHelper.moveTo(childPosition);
+        if (cursor == null) {
+            throw new IllegalStateException("this should only be called when the cursor is valid");
+        }
+        
+        View v;
+        if (convertView == null) {
+            v = newChildView(mContext, cursor, isLastChild, parent);
+        } else {
+            v = convertView;
+        }
+        bindChildView(v, mContext, cursor, isLastChild);
+        return v;
+    }
+
+    /**
+     * Makes a new child view to hold the data pointed to by cursor.
+     * 
+     * @param context Interface to application's global information
+     * @param cursor The cursor from which to get the data. The cursor is
+     *            already moved to the correct position.
+     * @param isLastChild Whether the child is the last child within its group.
+     * @param parent The parent to which the new view is attached to
+     * @return the newly created view.
+     */
+    protected abstract View newChildView(Context context, Cursor cursor, boolean isLastChild,
+            ViewGroup parent);
+
+    /**
+     * Bind an existing view to the child data pointed to by cursor
+     * 
+     * @param view Existing view, returned earlier by newChildView
+     * @param context Interface to application's global information
+     * @param cursor The cursor from which to get the data. The cursor is
+     *            already moved to the correct position.
+     * @param isLastChild Whether the child is the last child within its group.
+     */
+    protected abstract void bindChildView(View view, Context context, Cursor cursor,
+            boolean isLastChild);
+    
+    public boolean isChildSelectable(int groupPosition, int childPosition) {
+        return true;
+    }
+
+    public boolean hasStableIds() {
+        return true;
+    }
+
+    private synchronized void releaseCursorHelpers() {
+        for (int pos = mChildrenCursorHelpers.size() - 1; pos >= 0; pos--) {
+            mChildrenCursorHelpers.valueAt(pos).deactivate();
+        }
+        
+        mChildrenCursorHelpers.clear();
+    }
+    
+    @Override
+    public void notifyDataSetChanged() {
+        notifyDataSetChanged(true);
+    }
+
+    /**
+     * Notifies a data set change, but with the option of not releasing any
+     * cached cursors.
+     * 
+     * @param releaseCursors Whether to release and deactivate any cached
+     *            cursors.
+     */
+    public void notifyDataSetChanged(boolean releaseCursors) {
+        
+        if (releaseCursors) {
+            releaseCursorHelpers();
+        }
+        
+        super.notifyDataSetChanged();
+    }
+    
+    @Override
+    public void notifyDataSetInvalidated() {
+        releaseCursorHelpers();
+        super.notifyDataSetInvalidated();
+    }
+
+    @Override
+    public void onGroupCollapsed(int groupPosition) {
+        deactivateChildrenCursorHelper(groupPosition);
+    }
+
+    /**
+     * Deactivates the Cursor and removes the helper from cache.
+     * 
+     * @param groupPosition The group whose children Cursor and helper should be
+     *            deactivated.
+     */
+    synchronized void deactivateChildrenCursorHelper(int groupPosition) {
+        MyCursorHelper cursorHelper = getChildrenCursorHelper(groupPosition, true);
+        mChildrenCursorHelpers.remove(groupPosition);
+        cursorHelper.deactivate();
+    }
+
+    /**
+     * @see CursorAdapter#convertToString(Cursor)
+     */
+    public String convertToString(Cursor cursor) {
+        return cursor == null ? "" : cursor.toString();
+    }
+
+    /**
+     * @see CursorAdapter#runQueryOnBackgroundThread(CharSequence)
+     */
+    public Cursor runQueryOnBackgroundThread(CharSequence constraint) {
+        if (mFilterQueryProvider != null) {
+            return mFilterQueryProvider.runQuery(constraint);
+        }
+
+        return mGroupCursorHelper.getCursor();
+    }
+    
+    public Filter getFilter() {
+        if (mCursorFilter == null) {
+            mCursorFilter = new CursorFilter(this);
+        }
+        return mCursorFilter;
+    }
+
+    /**
+     * @see CursorAdapter#getFilterQueryProvider()
+     */
+    public FilterQueryProvider getFilterQueryProvider() {
+        return mFilterQueryProvider;
+    }
+
+    /**
+     * @see CursorAdapter#setFilterQueryProvider(FilterQueryProvider)
+     */
+    public void setFilterQueryProvider(FilterQueryProvider filterQueryProvider) {
+        mFilterQueryProvider = filterQueryProvider;
+    }
+    
+    /**
+     * @see CursorAdapter#changeCursor(Cursor)
+     */
+    public void changeCursor(Cursor cursor) {
+        mGroupCursorHelper.changeCursor(cursor, true);
+    }
+
+    /**
+     * @see CursorAdapter#getCursor()
+     */
+    public Cursor getCursor() {
+        return mGroupCursorHelper.getCursor();
+    }
+
+    /**
+     * Helper class for Cursor management:
+     * <li> Data validity
+     * <li> Funneling the content and data set observers from a Cursor to a
+     *      single data set observer for widgets
+     * <li> ID from the Cursor for use in adapter IDs
+     * <li> Swapping cursors but maintaining other metadata
+     */
+    class MyCursorHelper {
+        private Cursor mCursor;
+        private boolean mDataValid;
+        private int mRowIDColumn;
+        private MyContentObserver mContentObserver;
+        private MyDataSetObserver mDataSetObserver;
+        
+        MyCursorHelper(Cursor cursor) {
+            final boolean cursorPresent = cursor != null;
+            mCursor = cursor;
+            mDataValid = cursorPresent;
+            mRowIDColumn = cursorPresent ? cursor.getColumnIndex("_id") : -1;
+            mContentObserver = new MyContentObserver();
+            mDataSetObserver = new MyDataSetObserver();
+            if (cursorPresent) {
+                cursor.registerContentObserver(mContentObserver);
+                cursor.registerDataSetObserver(mDataSetObserver);
+            }
+        }
+        
+        Cursor getCursor() {
+            return mCursor;
+        }
+
+        int getCount() {
+            if (mDataValid && mCursor != null) {
+                return mCursor.getCount();
+            } else {
+                return 0;
+            }
+        }
+        
+        long getId(int position) {
+            if (mDataValid && mCursor != null) {
+                if (mCursor.moveToPosition(position)) {
+                    return mCursor.getLong(mRowIDColumn);
+                } else {
+                    return 0;
+                }
+            } else {
+                return 0;
+            }
+        }
+        
+        Cursor moveTo(int position) {
+            if (mDataValid && (mCursor != null) && mCursor.moveToPosition(position)) {
+                return mCursor;
+            } else {
+                return null;
+            }
+        }
+        
+        void changeCursor(Cursor cursor, boolean releaseCursors) {
+            if (cursor == mCursor) return;
+
+            deactivate();
+            mCursor = cursor;
+            if (cursor != null) {
+                cursor.registerContentObserver(mContentObserver);
+                cursor.registerDataSetObserver(mDataSetObserver);
+                mRowIDColumn = cursor.getColumnIndex("_id");
+                mDataValid = true;
+                // notify the observers about the new cursor
+                notifyDataSetChanged(releaseCursors);
+            } else {
+                mRowIDColumn = -1;
+                mDataValid = false;
+                // notify the observers about the lack of a data set
+                notifyDataSetInvalidated();
+            }
+        }
+
+        void deactivate() {
+            if (mCursor == null) {
+                return;
+            }
+            
+            mCursor.unregisterContentObserver(mContentObserver);
+            mCursor.unregisterDataSetObserver(mDataSetObserver);
+            mCursor.deactivate();
+            mCursor = null;
+        }
+        
+        boolean isValid() {
+            return mDataValid && mCursor != null;
+        }
+        
+        private class MyContentObserver extends ContentObserver {
+            public MyContentObserver() {
+                super(mHandler);
+            }
+
+            @Override
+            public boolean deliverSelfNotifications() {
+                return true;
+            }
+
+            @Override
+            public void onChange(boolean selfChange) {
+                if (mAutoRequery && mCursor != null) {
+                    if (Config.LOGV) Log.v("Cursor", "Auto requerying " + mCursor +
+                            " due to update");
+                    mDataValid = mCursor.requery();
+                }
+            }
+        }
+
+        private class MyDataSetObserver extends DataSetObserver {
+            @Override
+            public void onChanged() {
+                mDataValid = true;
+                notifyDataSetChanged();
+            }
+
+            @Override
+            public void onInvalidated() {
+                mDataValid = false;
+                notifyDataSetInvalidated();
+            }
+        }
+    }
+}
diff --git a/core/java/android/widget/DatePicker.java b/core/java/android/widget/DatePicker.java
new file mode 100644
index 0000000..54f2707
--- /dev/null
+++ b/core/java/android/widget/DatePicker.java
@@ -0,0 +1,321 @@
+/*
+ * Copyright (C) 2007 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 android.widget;
+
+import android.annotation.Widget;
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.format.DateFormat;
+import android.util.AttributeSet;
+import android.util.SparseArray;
+import android.view.LayoutInflater;
+
+import com.android.internal.R;
+import com.android.internal.widget.NumberPicker;
+import com.android.internal.widget.NumberPicker.OnChangedListener;
+
+import java.text.DateFormatSymbols;
+import java.util.Calendar;
+
+/**
+ * A view for selecting a month / year / day based on a calendar like layout.
+ *
+ * For a dialog using this view, see {@link android.app.DatePickerDialog}.
+ */
+@Widget
+public class DatePicker extends FrameLayout {
+
+    private static final int DEFAULT_START_YEAR = 1900;
+    private static final int DEFAULT_END_YEAR = 2100;
+    
+    /* UI Components */
+    private final NumberPicker mDayPicker;
+    private final NumberPicker mMonthPicker;
+    private final NumberPicker mYearPicker;
+
+    /**
+     * How we notify users the date has changed.
+     */
+    private OnDateChangedListener mOnDateChangedListener;
+    
+    private int mDay;
+    private int mMonth;
+    private int mYear;
+
+    /**
+     * The callback used to indicate the user changes the date.
+     */
+    public interface OnDateChangedListener {
+
+        /**
+         * @param view The view associated with this listener.
+         * @param year The year that was set.
+         * @param monthOfYear The month that was set (0-11) for compatibility
+         *  with {@link java.util.Calendar}.
+         * @param dayOfMonth The day of the month that was set.
+         */
+        void onDateChanged(DatePicker view, int year, int monthOfYear, int dayOfMonth);
+    }
+
+    public DatePicker(Context context) {
+        this(context, null);
+    }
+    
+    public DatePicker(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public DatePicker(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+
+        LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+        inflater.inflate(R.layout.date_picker, this, true);
+
+        mDayPicker = (NumberPicker) findViewById(R.id.day);
+        mDayPicker.setFormatter(NumberPicker.TWO_DIGIT_FORMATTER);
+        mDayPicker.setSpeed(100);
+        mDayPicker.setOnChangeListener(new OnChangedListener() {
+            public void onChanged(NumberPicker picker, int oldVal, int newVal) {
+                mDay = newVal;
+                if (mOnDateChangedListener != null) {
+                    mOnDateChangedListener.onDateChanged(DatePicker.this, mYear, mMonth, mDay);
+                }
+            }
+        });
+        mMonthPicker = (NumberPicker) findViewById(R.id.month);
+        mMonthPicker.setFormatter(NumberPicker.TWO_DIGIT_FORMATTER);
+        DateFormatSymbols dfs = new DateFormatSymbols();
+        mMonthPicker.setRange(1, 12, dfs.getShortMonths());
+        mMonthPicker.setSpeed(200);
+        mMonthPicker.setOnChangeListener(new OnChangedListener() {
+            public void onChanged(NumberPicker picker, int oldVal, int newVal) {
+                
+                /* We display the month 1-12 but store it 0-11 so always
+                 * subtract by one to ensure our internal state is always 0-11
+                 */
+                mMonth = newVal - 1;
+                if (mOnDateChangedListener != null) {
+                    mOnDateChangedListener.onDateChanged(DatePicker.this, mYear, mMonth, mDay);
+                }
+                updateDaySpinner();
+            }
+        });
+        mYearPicker = (NumberPicker) findViewById(R.id.year);
+        mYearPicker.setSpeed(100);
+        mYearPicker.setOnChangeListener(new OnChangedListener() {
+            public void onChanged(NumberPicker picker, int oldVal, int newVal) {
+                mYear = newVal;
+                if (mOnDateChangedListener != null) {
+                    mOnDateChangedListener.onDateChanged(DatePicker.this, mYear, mMonth, mDay);
+                }
+            }
+        });
+        
+        // attributes
+        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.DatePicker);
+
+        int mStartYear = a.getInt(R.styleable.DatePicker_startYear, DEFAULT_START_YEAR);
+        int mEndYear = a.getInt(R.styleable.DatePicker_endYear, DEFAULT_END_YEAR);
+        mYearPicker.setRange(mStartYear, mEndYear);
+        
+        a.recycle();
+        
+        // initialize to current date
+        Calendar cal = Calendar.getInstance();
+        init(cal.get(Calendar.YEAR), cal.get(Calendar.MONTH), cal.get(Calendar.DAY_OF_MONTH), null);
+        
+        // re-order the number pickers to match the current date format
+        reorderPickers();
+        
+        if (!isEnabled()) {
+            setEnabled(false);
+        }
+    }
+    
+    @Override
+    public void setEnabled(boolean enabled) {
+        super.setEnabled(enabled);
+        mDayPicker.setEnabled(enabled);
+        mMonthPicker.setEnabled(enabled);
+        mYearPicker.setEnabled(enabled);
+    }
+
+    private void reorderPickers() {
+        char[] order = DateFormat.getDateFormatOrder(mContext);
+        
+        /* Default order is month, date, year so if that's the order then
+         * do nothing.
+         */
+        if ((order[0] == DateFormat.MONTH) && (order[1] == DateFormat.DATE)) {
+            return;
+        }
+        
+        /* Remove the 3 pickers from their parent and then add them back in the
+         * required order.
+         */
+        LinearLayout parent = (LinearLayout) findViewById(R.id.parent);
+        parent.removeAllViews();
+        for (char c : order) {
+            if (c == DateFormat.DATE) {
+                parent.addView(mDayPicker);
+            } else if (c == DateFormat.MONTH) {
+                parent.addView(mMonthPicker);
+            } else {
+                parent.addView (mYearPicker);
+            }
+        }
+    }
+
+    public void updateDate(int year, int monthOfYear, int dayOfMonth) {
+        mYear = year;
+        mMonth = monthOfYear;
+        mDay = dayOfMonth;
+        updateSpinners();
+    }
+
+    private static class SavedState extends BaseSavedState {
+
+        private final int mYear;
+        private final int mMonth;
+        private final int mDay;
+
+        /**
+         * Constructor called from {@link DatePicker#onSaveInstanceState()}
+         */
+        private SavedState(Parcelable superState, int year, int month, int day) {
+            super(superState);
+            mYear = year;
+            mMonth = month;
+            mDay = day;
+        }
+        
+        /**
+         * Constructor called from {@link #CREATOR}
+         */
+        private SavedState(Parcel in) {
+            super(in);
+            mYear = in.readInt();
+            mMonth = in.readInt();
+            mDay = in.readInt();
+        }
+
+        public int getYear() {
+            return mYear;
+        }
+
+        public int getMonth() {
+            return mMonth;
+        }
+
+        public int getDay() {
+            return mDay;
+        }
+
+        @Override
+        public void writeToParcel(Parcel dest, int flags) {
+            super.writeToParcel(dest, flags);
+            dest.writeInt(mYear);
+            dest.writeInt(mMonth);
+            dest.writeInt(mDay);
+        }
+
+        public static final Parcelable.Creator<SavedState> CREATOR =
+                new Creator<SavedState>() {
+
+                    public SavedState createFromParcel(Parcel in) {
+                        return new SavedState(in);
+                    }
+
+                    public SavedState[] newArray(int size) {
+                        return new SavedState[size];
+                    }
+                };
+    }
+
+
+    /**
+     * Override so we are in complete control of save / restore for this widget.
+     */
+    @Override
+    protected void dispatchRestoreInstanceState(SparseArray<Parcelable> container) {
+        dispatchThawSelfOnly(container);
+    }
+
+    @Override
+    protected Parcelable onSaveInstanceState() {
+        Parcelable superState = super.onSaveInstanceState();
+        
+        return new SavedState(superState, mYear, mMonth, mDay);
+    }
+
+    @Override
+    protected void onRestoreInstanceState(Parcelable state) {
+        SavedState ss = (SavedState) state;
+        super.onRestoreInstanceState(ss.getSuperState());
+        mYear = ss.getYear();
+        mMonth = ss.getMonth();
+        mDay = ss.getDay();
+    }
+
+    /**
+     * Initialize the state.
+     * @param year The initial year.
+     * @param monthOfYear The initial month.
+     * @param dayOfMonth The initial day of the month.
+     * @param onDateChangedListener How user is notified date is changed by user, can be null.
+     */
+    public void init(int year, int monthOfYear, int dayOfMonth,
+            OnDateChangedListener onDateChangedListener) {
+        mYear = year;
+        mMonth = monthOfYear;
+        mDay = dayOfMonth;
+        mOnDateChangedListener = onDateChangedListener;
+        updateSpinners();
+    }
+
+    private void updateSpinners() {
+        updateDaySpinner();
+        mYearPicker.setCurrent(mYear);
+        
+        /* The month display uses 1-12 but our internal state stores it
+         * 0-11 so add one when setting the display.
+         */
+        mMonthPicker.setCurrent(mMonth + 1);
+    }
+
+    private void updateDaySpinner() {
+        Calendar cal = Calendar.getInstance();
+        cal.set(mYear, mMonth, mDay);
+        int max = cal.getActualMaximum(Calendar.DAY_OF_MONTH);
+        mDayPicker.setRange(1, max);
+        mDayPicker.setCurrent(mDay);
+    }
+
+    public int getYear() {
+        return mYear;
+    }
+
+    public int getMonth() {
+        return mMonth;
+    }
+
+    public int getDayOfMonth() {
+        return mDay;
+    }
+}
diff --git a/core/java/android/widget/DialerFilter.java b/core/java/android/widget/DialerFilter.java
new file mode 100644
index 0000000..a23887f
--- /dev/null
+++ b/core/java/android/widget/DialerFilter.java
@@ -0,0 +1,431 @@
+/*
+ * 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 android.widget;
+
+import android.content.Context;
+import android.view.KeyEvent;
+import android.text.Editable;
+import android.text.InputFilter;
+import android.text.Selection;
+import android.text.Spannable;
+import android.text.Spanned;
+import android.text.TextWatcher;
+import android.text.method.DialerKeyListener;
+import android.text.method.KeyListener;
+import android.text.method.TextKeyListener;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.KeyCharacterMap;
+import android.view.View;
+import android.graphics.Rect;
+
+
+
+public class DialerFilter extends RelativeLayout
+{
+    public DialerFilter(Context context) {
+        super(context);
+    }
+
+    public DialerFilter(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    @Override
+    protected void onFinishInflate() {
+        super.onFinishInflate();
+
+        // Setup the filter view
+        mInputFilters = new InputFilter[] { new InputFilter.AllCaps() };
+
+        mHint = (EditText) findViewById(com.android.internal.R.id.hint);
+        if (mHint == null) {
+            throw new IllegalStateException("DialerFilter must have a child EditText named hint");
+        }
+        mHint.setFilters(mInputFilters);
+
+        mLetters = mHint;
+        mLetters.setKeyListener(TextKeyListener.getInstance());
+        mLetters.setMovementMethod(null);
+        mLetters.setFocusable(false);
+
+        // Setup the digits view
+        mPrimary = (EditText) findViewById(com.android.internal.R.id.primary);
+        if (mPrimary == null) {
+            throw new IllegalStateException("DialerFilter must have a child EditText named primary");
+        }
+        mPrimary.setFilters(mInputFilters);
+
+        mDigits = mPrimary;
+        mDigits.setKeyListener(DialerKeyListener.getInstance());
+        mDigits.setMovementMethod(null);
+        mDigits.setFocusable(false);
+
+        // Look for an icon
+        mIcon = (ImageView) findViewById(com.android.internal.R.id.icon);
+
+        // Setup focus & highlight for this view
+        setFocusable(true);
+
+        // Default the mode based on the keyboard
+        KeyCharacterMap kmap
+                = KeyCharacterMap.load(KeyCharacterMap.BUILT_IN_KEYBOARD);
+        mIsQwerty = kmap.getKeyboardType() != KeyCharacterMap.NUMERIC;
+        if (mIsQwerty) {
+            Log.i("DialerFilter", "This device looks to be QWERTY");
+//            setMode(DIGITS_AND_LETTERS);
+        } else {
+            Log.i("DialerFilter", "This device looks to be 12-KEY");
+//            setMode(DIGITS_ONLY);
+        }
+
+        // XXX Force the mode to QWERTY for now, since 12-key isn't supported
+        mIsQwerty = true;
+        setMode(DIGITS_AND_LETTERS);
+    }
+
+    /**
+     * Only show the icon view when focused, if there is one.
+     */
+    @Override
+    protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) {
+        super.onFocusChanged(focused, direction, previouslyFocusedRect);
+
+        if (mIcon != null) {
+            mIcon.setVisibility(focused ? View.VISIBLE : View.GONE);
+        }
+    }
+
+
+    public boolean isQwertyKeyboard() {
+        return mIsQwerty;
+    }
+
+    @Override
+    public boolean onKeyDown(int keyCode, KeyEvent event) {
+        boolean handled = false;
+
+        switch (keyCode) {
+            case KeyEvent.KEYCODE_DPAD_UP:
+            case KeyEvent.KEYCODE_DPAD_DOWN:
+            case KeyEvent.KEYCODE_DPAD_LEFT:
+            case KeyEvent.KEYCODE_DPAD_RIGHT:
+            case KeyEvent.KEYCODE_ENTER:
+            case KeyEvent.KEYCODE_DPAD_CENTER:
+                break;
+
+            case KeyEvent.KEYCODE_DEL:
+                switch (mMode) {
+                    case DIGITS_AND_LETTERS:
+                        handled = mDigits.onKeyDown(keyCode, event);
+                        handled &= mLetters.onKeyDown(keyCode, event);
+                        break;
+
+                    case DIGITS_AND_LETTERS_NO_DIGITS:
+                        handled = mLetters.onKeyDown(keyCode, event);
+                        if (mLetters.getText().length() == mDigits.getText().length()) {
+                            setMode(DIGITS_AND_LETTERS);
+                        }
+                        break;
+
+                    case DIGITS_AND_LETTERS_NO_LETTERS:
+                        if (mDigits.getText().length() == mLetters.getText().length()) {
+                            mLetters.onKeyDown(keyCode, event);
+                            setMode(DIGITS_AND_LETTERS);
+                        }
+                        handled = mDigits.onKeyDown(keyCode, event);
+                        break;
+
+                    case DIGITS_ONLY:
+                        handled = mDigits.onKeyDown(keyCode, event);
+                        break;
+
+                    case LETTERS_ONLY:
+                        handled = mLetters.onKeyDown(keyCode, event);
+                        break;
+                }
+                break;
+
+            default:
+                //mIsQwerty = msg.getKeyIsQwertyKeyboard();
+
+                switch (mMode) {
+                    case DIGITS_AND_LETTERS:
+                        handled = mLetters.onKeyDown(keyCode, event);
+
+                        // pass this throw so the shift state is correct (for example,
+                        // on a standard QWERTY keyboard, * and 8 are on the same key)
+                        if (KeyEvent.isModifierKey(keyCode)) {
+                            mDigits.onKeyDown(keyCode, event);
+                            handled = true;
+                            break;
+                        }
+
+                        // Only check to see if the digit is valid if the key is a printing key
+                        // in the TextKeyListener. This prevents us from hiding the digits
+                        // line when keys like UP and DOWN are hit.
+                        // XXX note that KEYCODE_TAB is special-cased here for 
+                        // devices that share tab and 0 on a single key.
+                        boolean isPrint = event.isPrintingKey();
+                        if (isPrint || keyCode == KeyEvent.KEYCODE_SPACE
+                                || keyCode == KeyEvent.KEYCODE_TAB) {
+                            char c = event.getMatch(DialerKeyListener.CHARACTERS);
+                            if (c != 0) {
+                                handled &= mDigits.onKeyDown(keyCode, event);
+                            } else {
+                                setMode(DIGITS_AND_LETTERS_NO_DIGITS);
+                            }
+                        }
+                        break;
+
+                    case DIGITS_AND_LETTERS_NO_LETTERS:
+                    case DIGITS_ONLY:
+                        handled = mDigits.onKeyDown(keyCode, event);
+                        break;
+
+                    case DIGITS_AND_LETTERS_NO_DIGITS:
+                    case LETTERS_ONLY:
+                        handled = mLetters.onKeyDown(keyCode, event);
+                        break;
+                }
+        }
+
+        if (!handled) {
+            return super.onKeyDown(keyCode, event);
+        } else {
+            return true;
+        }
+    }
+
+    @Override
+    public boolean onKeyUp(int keyCode, KeyEvent event) {
+        boolean a = mLetters.onKeyUp(keyCode, event);
+        boolean b = mDigits.onKeyUp(keyCode, event);
+        return a || b;
+    }
+
+    public int getMode() {
+        return mMode;
+    }
+
+    /**
+     * Change the mode of the widget.
+     *
+     * @param newMode The mode to switch to.
+     */
+    public void setMode(int newMode) {
+        switch (newMode) {
+            case DIGITS_AND_LETTERS:
+                makeDigitsPrimary();
+                mLetters.setVisibility(View.VISIBLE);
+                mDigits.setVisibility(View.VISIBLE);
+                break;
+
+            case DIGITS_ONLY:
+                makeDigitsPrimary();
+                mLetters.setVisibility(View.GONE);
+                mDigits.setVisibility(View.VISIBLE);
+                break;
+
+            case LETTERS_ONLY:
+                makeLettersPrimary();
+                mLetters.setVisibility(View.VISIBLE);
+                mDigits.setVisibility(View.GONE);
+                break;
+
+            case DIGITS_AND_LETTERS_NO_LETTERS:
+                makeDigitsPrimary();
+                mLetters.setVisibility(View.INVISIBLE);
+                mDigits.setVisibility(View.VISIBLE);
+                break;
+
+            case DIGITS_AND_LETTERS_NO_DIGITS:
+                makeLettersPrimary();
+                mLetters.setVisibility(View.VISIBLE);
+                mDigits.setVisibility(View.INVISIBLE);
+                break;
+
+        }
+        int oldMode = mMode;
+        mMode = newMode;
+        onModeChange(oldMode, newMode);
+    }
+
+    private void makeLettersPrimary() {
+        if (mPrimary == mDigits) {
+            swapPrimaryAndHint(true);
+        }
+    }
+
+    private void makeDigitsPrimary() {
+        if (mPrimary == mLetters) {
+            swapPrimaryAndHint(false);
+        }
+    }
+
+    private void swapPrimaryAndHint(boolean makeLettersPrimary) {
+        Editable lettersText = mLetters.getText();
+        Editable digitsText = mDigits.getText();
+        KeyListener lettersInput = mLetters.getKeyListener();
+        KeyListener digitsInput = mDigits.getKeyListener();
+
+        if (makeLettersPrimary) {
+            mLetters = mPrimary;
+            mDigits = mHint;
+        } else {
+            mLetters = mHint;
+            mDigits = mPrimary;
+        }
+
+        mLetters.setKeyListener(lettersInput);
+        mLetters.setText(lettersText);
+        lettersText = mLetters.getText();
+        Selection.setSelection(lettersText, lettersText.length());
+
+        mDigits.setKeyListener(digitsInput);
+        mDigits.setText(digitsText);
+        digitsText = mDigits.getText();
+        Selection.setSelection(digitsText, digitsText.length());
+
+        // Reset the filters
+        mPrimary.setFilters(mInputFilters);
+        mHint.setFilters(mInputFilters);
+    }
+
+
+    public CharSequence getLetters() {
+        if (mLetters.getVisibility() == View.VISIBLE) {
+            return mLetters.getText();
+        } else {
+            return "";
+        }
+    }
+
+    public CharSequence getDigits() {
+        if (mDigits.getVisibility() == View.VISIBLE) {
+            return mDigits.getText();
+        } else {
+            return "";
+        }
+    }
+
+    public CharSequence getFilterText() {
+        if (mMode != DIGITS_ONLY) {
+            return getLetters();
+        } else {
+            return getDigits();
+        }
+    }
+
+    public void append(String text) {
+        switch (mMode) {
+            case DIGITS_AND_LETTERS:
+                mDigits.getText().append(text);
+                mLetters.getText().append(text);
+                break;
+
+            case DIGITS_AND_LETTERS_NO_LETTERS:
+            case DIGITS_ONLY:
+                mDigits.getText().append(text);
+                break;
+
+            case DIGITS_AND_LETTERS_NO_DIGITS:
+            case LETTERS_ONLY:
+                mLetters.getText().append(text);
+                break;
+        }
+    }
+
+    /**
+     * Clears both the digits and the filter text.
+     */
+    public void clearText() {
+        Editable text;
+
+        text = mLetters.getText();
+        text.clear();
+
+        text = mDigits.getText();
+        text.clear();
+
+        // Reset the mode based on the hardware type
+        if (mIsQwerty) {
+            setMode(DIGITS_AND_LETTERS);
+        } else {
+            setMode(DIGITS_ONLY);
+        }
+    }
+
+    public void setLettersWatcher(TextWatcher watcher) {
+        CharSequence text = mLetters.getText();
+        Spannable span = (Spannable)text;
+        span.setSpan(watcher, 0, text.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
+    }
+
+    public void setDigitsWatcher(TextWatcher watcher) {
+        CharSequence text = mDigits.getText();
+        Spannable span = (Spannable)text;
+        span.setSpan(watcher, 0, text.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
+    }
+
+    public void setFilterWatcher(TextWatcher watcher) {
+        if (mMode != DIGITS_ONLY) {
+            setLettersWatcher(watcher);
+        } else {
+            setDigitsWatcher(watcher);
+        }
+    }
+
+    public void removeFilterWatcher(TextWatcher watcher) {
+        Spannable text;
+        if (mMode != DIGITS_ONLY) {
+            text = mLetters.getText();
+        } else {
+            text = mDigits.getText();
+        }
+        text.removeSpan(watcher);
+    }
+
+    /**
+     * Called right after the mode changes to give subclasses the option to
+     * restyle, etc.
+     */
+    protected void onModeChange(int oldMode, int newMode) {
+    }
+
+    /** This mode has both lines */
+    public static final int DIGITS_AND_LETTERS = 1;
+    /** This mode is when after starting in {@link #DIGITS_AND_LETTERS} mode the filter
+     *  has removed all possibility of the digits matching, leaving only the letters line */
+    public static final int DIGITS_AND_LETTERS_NO_DIGITS = 2;
+    /** This mode is when after starting in {@link #DIGITS_AND_LETTERS} mode the filter
+     *  has removed all possibility of the letters matching, leaving only the digits line */
+    public static final int DIGITS_AND_LETTERS_NO_LETTERS = 3;
+    /** This mode has only the digits line */
+    public static final int DIGITS_ONLY = 4;
+    /** This mode has only the letters line */
+    public static final int LETTERS_ONLY = 5;
+
+    EditText mLetters;
+    EditText mDigits;
+    EditText mPrimary;
+    EditText mHint;
+    InputFilter mInputFilters[];
+    ImageView mIcon;
+    int mMode;
+    private boolean mIsQwerty;
+}
diff --git a/core/java/android/widget/DigitalClock.java b/core/java/android/widget/DigitalClock.java
new file mode 100644
index 0000000..379883a
--- /dev/null
+++ b/core/java/android/widget/DigitalClock.java
@@ -0,0 +1,129 @@
+/*
+ * 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 android.widget;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.database.ContentObserver;
+import android.os.Handler;
+import android.os.SystemClock;
+import android.provider.Settings;
+import android.text.format.DateFormat;
+import android.util.AttributeSet;
+
+import java.util.Calendar;
+
+/**
+ * Like AnalogClock, but digital.  Shows seconds.
+ *
+ * FIXME: implement separate views for hours/minutes/seconds, so
+ * proportional fonts don't shake rendering
+ */
+
+public class DigitalClock extends TextView {
+
+    Calendar mCalendar;
+    private final static String m12 = "h:mm:ss aa";
+    private final static String m24 = "k:mm:ss";
+    private FormatChangeObserver mFormatChangeObserver;
+
+    private Runnable mTicker;
+    private Handler mHandler;
+
+    private boolean mTickerStopped = false;
+
+    String mFormat;
+
+    public DigitalClock(Context context) {
+        super(context);
+        initClock(context);
+    }
+
+    public DigitalClock(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        initClock(context);
+    }
+
+    private void initClock(Context context) {
+        Resources r = mContext.getResources();
+
+        if (mCalendar == null) {
+            mCalendar = Calendar.getInstance();
+        }
+
+        mFormatChangeObserver = new FormatChangeObserver();
+        getContext().getContentResolver().registerContentObserver(
+                Settings.System.CONTENT_URI, true, mFormatChangeObserver);
+
+        setFormat();
+    }
+
+    @Override
+    protected void onAttachedToWindow() {
+        mTickerStopped = false;
+        super.onAttachedToWindow();
+        mHandler = new Handler();
+
+        /**
+         * requests a tick on the next hard-second boundary
+         */
+        mTicker = new Runnable() {
+                public void run() {
+                    if (mTickerStopped) return;
+                    mCalendar.setTimeInMillis(System.currentTimeMillis());
+                    setText(DateFormat.format(mFormat, mCalendar));
+                    invalidate();
+                    long now = SystemClock.uptimeMillis();
+                    long next = now + (1000 - now % 1000);
+                    mHandler.postAtTime(mTicker, next);
+                }
+            };
+        mTicker.run();
+    }
+
+    @Override
+    protected void onDetachedFromWindow() {
+        super.onDetachedFromWindow();
+        mTickerStopped = true;
+    }
+
+    /**
+     * Pulls 12/24 mode from system settings
+     */
+    private boolean get24HourMode() {
+        return android.text.format.DateFormat.is24HourFormat(getContext());
+    }
+
+    private void setFormat() {
+        if (get24HourMode()) {
+            mFormat = m24;
+        } else {
+            mFormat = m12;
+        }
+    }
+
+    private class FormatChangeObserver extends ContentObserver {
+        public FormatChangeObserver() {
+            super(new Handler());
+        }
+
+        @Override
+        public void onChange(boolean selfChange) {
+            setFormat();
+        }
+    }
+}
diff --git a/core/java/android/widget/DoubleDigitManager.java b/core/java/android/widget/DoubleDigitManager.java
new file mode 100644
index 0000000..1eea1fb
--- /dev/null
+++ b/core/java/android/widget/DoubleDigitManager.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2007 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 android.widget;
+
+import android.os.Handler;
+
+/**
+ * Provides callbacks indicating the steps in two digit pressing within a
+ * timeout.
+ *
+ * Package private: only relevant in helping {@link TimeSpinnerHelper}.
+ */
+class DoubleDigitManager {
+
+    private final long timeoutInMillis;
+    private final CallBack mCallBack;
+
+    private Integer intermediateDigit;
+
+    /**
+     * @param timeoutInMillis How long after the first digit is pressed does
+     *   the user have to press the second digit?
+     * @param callBack The callback to indicate what's going on with the user.
+     */
+    public DoubleDigitManager(long timeoutInMillis, CallBack callBack) {
+        this.timeoutInMillis = timeoutInMillis;
+        mCallBack = callBack;
+    }
+
+    /**
+     * Report to this manager that a digit was pressed.
+     * @param digit
+     */
+    public void reportDigit(int digit) {
+        if (intermediateDigit == null) {
+            intermediateDigit = digit;
+
+            new Handler().postDelayed(new Runnable() {
+                public void run() {
+                    if (intermediateDigit != null) {
+                        mCallBack.singleDigitFinal(intermediateDigit);
+                        intermediateDigit = null;
+                    }
+                }
+            }, timeoutInMillis);
+
+            if (!mCallBack.singleDigitIntermediate(digit)) {
+
+                // this wasn't a good candidate for the intermediate digit,
+                // make it the final digit (since there is no opportunity to
+                // reject the final digit).
+                intermediateDigit = null;
+                mCallBack.singleDigitFinal(digit);
+            }
+        } else if (mCallBack.twoDigitsFinal(intermediateDigit, digit)) {
+             intermediateDigit = null;
+        }
+    }
+
+    /**
+     * The callback to indicate what is going on with the digits pressed.
+     */
+    static interface CallBack {
+
+        /**
+         * A digit was pressed, and there are no intermediate digits.
+         * @param digit The digit pressed.
+         * @return Whether the digit was accepted; how the user of this manager
+         *   tells us that the intermediate digit is acceptable as an
+         *   intermediate digit.
+         */
+        boolean singleDigitIntermediate(int digit);
+
+        /**
+         * A single digit was pressed, and it is 'the final answer'.
+         * - a single digit pressed, and the timeout expires.
+         * - a single digit pressed, and {@link #singleDigitIntermediate}
+         *   returned false.
+         * @param digit The digit.
+         */
+        void singleDigitFinal(int digit);
+
+        /**
+         * The user pressed digit1, then digit2 within the timeout.
+         * @param digit1
+         * @param digit2
+         */
+        boolean twoDigitsFinal(int digit1, int digit2);
+    }
+
+}
diff --git a/core/java/android/widget/EditText.java b/core/java/android/widget/EditText.java
new file mode 100644
index 0000000..57aca24
--- /dev/null
+++ b/core/java/android/widget/EditText.java
@@ -0,0 +1,110 @@
+/*
+ * 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 android.widget;
+
+import android.text.*;
+import android.text.method.*;
+import android.content.Context;
+import android.util.AttributeSet;
+
+
+/*
+ * This is supposed to be a *very* thin veneer over TextView.
+ * Do not make any changes here that do anything that a TextView
+ * with a key listener and a movement method wouldn't do!
+ */
+
+/**
+ * EditText is a thin veneer over TextView that configures itself
+ * to be editable.
+ * <p>
+ * <b>XML attributes</b>
+ * <p>
+ * See {@link android.R.styleable#EditText EditText Attributes},
+ * {@link android.R.styleable#TextView TextView Attributes},
+ * {@link android.R.styleable#View View Attributes}
+ */
+public class EditText extends TextView {
+    public EditText(Context context) {
+        this(context, null);
+    }
+
+    public EditText(Context context, AttributeSet attrs) {
+        this(context, attrs, com.android.internal.R.attr.editTextStyle);
+    }
+
+    public EditText(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+    }
+
+    @Override
+    protected boolean getDefaultEditable() {
+        return true;
+    }
+
+    @Override
+    protected MovementMethod getDefaultMovementMethod() {
+        return ArrowKeyMovementMethod.getInstance();
+    }
+
+    @Override
+    public Editable getText() {
+        return (Editable) super.getText();
+    }
+
+    @Override
+    public void setText(CharSequence text, BufferType type) {
+        super.setText(text, BufferType.EDITABLE);
+    }
+
+    /**
+     * Convenience for {@link Selection#setSelection(Spannable, int, int)}.
+     */
+    public void setSelection(int start, int stop) {
+        Selection.setSelection(getText(), start, stop);
+    }
+
+    /**
+     * Convenience for {@link Selection#setSelection(Spannable, int)}.
+     */
+    public void setSelection(int index) {
+        Selection.setSelection(getText(), index);
+    }
+
+    /**
+     * Convenience for {@link Selection#selectAll}.
+     */
+    public void selectAll() {
+        Selection.selectAll(getText());
+    }
+
+    /**
+     * Convenience for {@link Selection#extendSelection}.
+     */
+    public void extendSelection(int index) {
+        Selection.extendSelection(getText(), index);
+    }
+
+    @Override
+    public void setEllipsize(TextUtils.TruncateAt ellipsis) {
+        if (ellipsis == TextUtils.TruncateAt.MARQUEE) {
+            throw new IllegalArgumentException("EditText cannot use the ellipsize mode "
+                    + "TextUtils.TruncateAt.MARQUEE");
+        }
+        super.setEllipsize(ellipsis);
+    }
+}
diff --git a/core/java/android/widget/ExpandableListAdapter.java b/core/java/android/widget/ExpandableListAdapter.java
new file mode 100644
index 0000000..b75983c
--- /dev/null
+++ b/core/java/android/widget/ExpandableListAdapter.java
@@ -0,0 +1,210 @@
+/*
+ * Copyright (C) 2007 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 android.widget;
+
+import android.database.DataSetObserver;
+import android.view.KeyEvent;
+import android.view.View;
+import android.view.ViewGroup;
+
+/**
+ * An adapter that links a {@link ExpandableListView} with the underlying
+ * data. The implementation of this interface will provide access
+ * to the data of the children (categorized by groups), and also instantiate
+ * {@link View}s for children and groups.
+ */
+public interface ExpandableListAdapter {
+    /**
+     * @see Adapter#registerDataSetObserver(DataSetObserver)
+     */
+    void registerDataSetObserver(DataSetObserver observer);
+
+    /**
+     * @see Adapter#unregisterDataSetObserver(DataSetObserver)
+     */
+    void unregisterDataSetObserver(DataSetObserver observer);
+
+    /**
+     * Gets the number of groups.
+     * 
+     * @return the number of groups
+     */
+    int getGroupCount();
+
+    /**
+     * Gets the number of children in a specified group.
+     * 
+     * @param groupPosition the position of the group for which the children
+     *            count should be returned
+     * @return the children count in the specified group
+     */
+    int getChildrenCount(int groupPosition);
+
+    /**
+     * Gets the data associated with the given group.
+     * 
+     * @param groupPosition the position of the group
+     * @return the data child for the specified group
+     */
+    Object getGroup(int groupPosition);
+    
+    /**
+     * Gets the data associated with the given child within the given group.
+     * 
+     * @param groupPosition the position of the group that the child resides in
+     * @param childPosition the position of the child with respect to other
+     *            children in the group
+     * @return the data of the child
+     */
+    Object getChild(int groupPosition, int childPosition);
+
+    /**
+     * Gets the ID for the group at the given position. This group ID must be
+     * unique across groups. The combined ID (see
+     * {@link #getCombinedGroupId(long)}) must be unique across ALL items
+     * (groups and all children).
+     * 
+     * @param groupPosition the position of the group for which the ID is wanted
+     * @return the ID associated with the group
+     */
+    long getGroupId(int groupPosition);
+
+    /**
+     * Gets the ID for the given child within the given group. This ID must be
+     * unique across all children within the group. The combined ID (see
+     * {@link #getCombinedChildId(long, long)}) must be unique across ALL items
+     * (groups and all children).
+     * 
+     * @param groupPosition the position of the group that contains the child
+     * @param childPosition the position of the child within the group for which
+     *            the ID is wanted
+     * @return the ID associated with the child
+     */
+    long getChildId(int groupPosition, int childPosition);
+
+    /**
+     * Indicates whether the child and group IDs are stable across changes to the
+     * underlying data.
+     * 
+     * @return whether or not the same ID always refers to the same object
+     * @see Adapter#hasStableIds()
+     */
+    boolean hasStableIds();
+
+    /**
+     * Gets a View that displays the given group. This View is only for the
+     * group--the Views for the group's children will be fetched using
+     * getChildrenView.
+     * 
+     * @param groupPosition the position of the group for which the View is
+     *            returned
+     * @param isExpanded whether the group is expanded or collapsed
+     * @param convertView the old view to reuse, if possible. You should check
+     *            that this view is non-null and of an appropriate type before
+     *            using. If it is not possible to convert this view to display
+     *            the correct data, this method can create a new view. It is not
+     *            guaranteed that the convertView will have been previously
+     *            created by
+     *            {@link #getGroupView(int, boolean, View, ViewGroup)}.
+     * @param parent the parent that this view will eventually be attached to
+     * @return the View corresponding to the group at the specified position
+     */
+    View getGroupView(int groupPosition, boolean isExpanded, View convertView, ViewGroup parent);
+
+    /**
+     * Gets a View that displays the data for the given child within the given
+     * group.
+     * 
+     * @param groupPosition the position of the group that contains the child
+     * @param childPosition the position of the child (for which the View is
+     *            returned) within the group
+     * @param isLastChild Whether the child is the last child within the group
+     * @param convertView the old view to reuse, if possible. You should check
+     *            that this view is non-null and of an appropriate type before
+     *            using. If it is not possible to convert this view to display
+     *            the correct data, this method can create a new view. It is not
+     *            guaranteed that the convertView will have been previously
+     *            created by
+     *            {@link #getChildView(int, int, boolean, View, ViewGroup)}.
+     * @param parent the parent that this view will eventually be attached to
+     * @return the View corresponding to the child at the specified position
+     */
+    View getChildView(int groupPosition, int childPosition, boolean isLastChild,
+            View convertView, ViewGroup parent);
+
+    /**
+     * Whether the child at the specified position is selectable.
+     * 
+     * @param groupPosition the position of the group that contains the child
+     * @param childPosition the position of the child within the group
+     * @return whether the child is selectable.
+     */
+    boolean isChildSelectable(int groupPosition, int childPosition);
+
+    /**
+     * @see ListAdapter#areAllItemsEnabled()
+     */
+    boolean areAllItemsEnabled();
+    
+    /**
+     * @see ListAdapter#isEmpty()
+     */
+    boolean isEmpty();
+
+    /**
+     * Called when a group is expanded.
+     * 
+     * @param groupPosition The group being expanded.
+     */
+    void onGroupExpanded(int groupPosition);
+    
+    /**
+     * Called when a group is collapsed.
+     * 
+     * @param groupPosition The group being collapsed.
+     */
+    void onGroupCollapsed(int groupPosition);
+    
+    /**
+     * Gets an ID for a child that is unique across any item (either group or
+     * child) that is in this list. Expandable lists require each item (group or
+     * child) to have a unique ID among all children and groups in the list.
+     * This method is responsible for returning that unique ID given a child's
+     * ID and its group's ID. Furthermore, if {@link #hasStableIds()} is true, the
+     * returned ID must be stable as well.
+     * 
+     * @param groupId The ID of the group that contains this child.
+     * @param childId The ID of the child.
+     * @return The unique (and possibly stable) ID of the child across all
+     *         groups and children in this list.
+     */
+    long getCombinedChildId(long groupId, long childId);
+
+    /**
+     * Gets an ID for a group that is unique across any item (either group or
+     * child) that is in this list. Expandable lists require each item (group or
+     * child) to have a unique ID among all children and groups in the list.
+     * This method is responsible for returning that unique ID given a group's
+     * ID. Furthermore, if {@link #hasStableIds()} is true, the returned ID must be
+     * stable as well.
+     * 
+     * @param groupId The ID of the group
+     * @return The unique (and possibly stable) ID of the group across all
+     *         groups and children in this list.
+     */
+    long getCombinedGroupId(long groupId);
+}
diff --git a/core/java/android/widget/ExpandableListConnector.java b/core/java/android/widget/ExpandableListConnector.java
new file mode 100644
index 0000000..ccce7c1
--- /dev/null
+++ b/core/java/android/widget/ExpandableListConnector.java
@@ -0,0 +1,1009 @@
+/*
+ * Copyright (C) 2007 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 android.widget;
+
+import android.database.DataSetObserver;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.SystemClock;
+import android.view.View;
+import android.view.ViewGroup;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/*
+ * Implementation notes:
+ * 
+ * <p>
+ * Terminology:
+ * <li> flPos - Flat list position, the position used by ListView
+ * <li> gPos - Group position, the position of a group among all the groups
+ * <li> cPos - Child position, the position of a child among all the children
+ * in a group
+ */
+
+/**
+ * A {@link BaseAdapter} that provides data/Views in an expandable list (offers
+ * features such as collapsing/expanding groups containing children). By
+ * itself, this adapter has no data and is a connector to a
+ * {@link ExpandableListAdapter} which provides the data.
+ * <p>
+ * Internally, this connector translates the flat list position that the
+ * ListAdapter expects to/from group and child positions that the ExpandableListAdapter
+ * expects.
+ */
+class ExpandableListConnector extends BaseAdapter implements Filterable {
+    /**
+     * The ExpandableListAdapter to fetch the data/Views for this expandable list
+     */
+    private ExpandableListAdapter mExpandableListAdapter;
+
+    /**
+     * List of metadata for the currently expanded groups. The metadata consists
+     * of data essential for efficiently translating between flat list positions
+     * and group/child positions. See {@link GroupMetadata}.
+     */
+    private ArrayList<GroupMetadata> mExpGroupMetadataList;
+
+    /** The number of children from all currently expanded groups */
+    private int mTotalExpChildrenCount;
+    
+    /** The maximum number of allowable expanded groups. Defaults to 'no limit' */
+    private int mMaxExpGroupCount = Integer.MAX_VALUE;
+
+    /** Change observer used to have ExpandableListAdapter changes pushed to us */
+    private DataSetObserver mDataSetObserver = new MyDataSetObserver();
+
+    /**
+     * Constructs the connector
+     */
+    public ExpandableListConnector(ExpandableListAdapter expandableListAdapter) {
+        mExpGroupMetadataList = new ArrayList<GroupMetadata>();
+
+        setExpandableListAdapter(expandableListAdapter);
+    }
+
+    /**
+     * Point to the {@link ExpandableListAdapter} that will give us data/Views
+     * 
+     * @param expandableListAdapter the adapter that supplies us with data/Views
+     */
+    public void setExpandableListAdapter(ExpandableListAdapter expandableListAdapter) {
+        if (mExpandableListAdapter != null) {
+            mExpandableListAdapter.unregisterDataSetObserver(mDataSetObserver);
+        }
+        
+        mExpandableListAdapter = expandableListAdapter;
+        expandableListAdapter.registerDataSetObserver(mDataSetObserver);
+    }
+
+    /**
+     * Translates a flat list position to either a) group pos if the specified
+     * flat list position corresponds to a group, or b) child pos if it
+     * corresponds to a child.  Performs a binary search on the expanded
+     * groups list to find the flat list pos if it is an exp group, otherwise
+     * finds where the flat list pos fits in between the exp groups.
+     * 
+     * @param flPos the flat list position to be translated
+     * @return the group position or child position of the specified flat list
+     *         position encompassed in a {@link PositionMetadata} object
+     *         that contains additional useful info for insertion, etc.
+     */
+    PositionMetadata getUnflattenedPos(final int flPos) {
+        /* Keep locally since frequent use */
+        final ArrayList<GroupMetadata> egml = mExpGroupMetadataList;
+        final int numExpGroups = egml.size();
+        
+        /* Binary search variables */
+        int leftExpGroupIndex = 0;
+        int rightExpGroupIndex = numExpGroups - 1;
+        int midExpGroupIndex = 0;
+        GroupMetadata midExpGm; 
+        
+        if (numExpGroups == 0) {
+            /*
+             * There aren't any expanded groups (hence no visible children
+             * either), so flPos must be a group and its group pos will be the
+             * same as its flPos
+             */
+            return PositionMetadata.obtain(flPos, ExpandableListPosition.GROUP, flPos,
+                    -1, null, 0);
+        }
+
+        /*
+         * Binary search over the expanded groups to find either the exact
+         * expanded group (if we're looking for a group) or the group that
+         * contains the child we're looking for. If we are looking for a
+         * collapsed group, we will not have a direct match here, but we will
+         * find the expanded group just before the group we're searching for (so
+         * then we can calculate the group position of the group we're searching
+         * for). If there isn't an expanded group prior to the group being
+         * searched for, then the group being searched for's group position is
+         * the same as the flat list position (since there are no children before
+         * it, and all groups before it are collapsed).
+         */
+        while (leftExpGroupIndex <= rightExpGroupIndex) {
+            midExpGroupIndex =
+                    (rightExpGroupIndex - leftExpGroupIndex) / 2
+                            + leftExpGroupIndex;
+            midExpGm = egml.get(midExpGroupIndex);
+            
+            if (flPos > midExpGm.lastChildFlPos) {
+                /*
+                 * The flat list position is after the current middle group's
+                 * last child's flat list position, so search right
+                 */
+                leftExpGroupIndex = midExpGroupIndex + 1;
+            } else if (flPos < midExpGm.flPos) {
+                /*
+                 * The flat list position is before the current middle group's
+                 * flat list position, so search left
+                 */
+                rightExpGroupIndex = midExpGroupIndex - 1;
+            } else if (flPos == midExpGm.flPos) {
+                /*
+                 * The flat list position is this middle group's flat list
+                 * position, so we've found an exact hit
+                 */
+                return PositionMetadata.obtain(flPos, ExpandableListPosition.GROUP,
+                        midExpGm.gPos, -1, midExpGm, midExpGroupIndex);
+            } else if (flPos <= midExpGm.lastChildFlPos
+                    /* && flPos > midGm.flPos as deduced from previous
+                     * conditions */) {
+                /* The flat list position is a child of the middle group */
+                
+                /* 
+                 * Subtract the first child's flat list position from the
+                 * specified flat list pos to get the child's position within
+                 * the group
+                 */
+                final int childPos = flPos - (midExpGm.flPos + 1);
+                return PositionMetadata.obtain(flPos, ExpandableListPosition.CHILD,
+                        midExpGm.gPos, childPos, midExpGm, midExpGroupIndex);
+            } 
+        }
+
+        /* 
+         * If we've reached here, it means the flat list position must be a
+         * group that is not expanded, since otherwise we would have hit it
+         * in the above search.
+         */
+
+
+        /**
+         * If we are to expand this group later, where would it go in the
+         * mExpGroupMetadataList ?
+         */
+        int insertPosition = 0;
+        
+        /** What is its group position in the list of all groups? */
+        int groupPos = 0;
+        
+        /*
+         * To figure out exact insertion and prior group positions, we need to
+         * determine how we broke out of the binary search.  We backtrack
+         * to see this.
+         */ 
+        if (leftExpGroupIndex > midExpGroupIndex) {
+            
+            /*
+             * This would occur in the first conditional, so the flat list
+             * insertion position is after the left group. Also, the
+             * leftGroupPos is one more than it should be (since that broke out
+             * of our binary search), so we decrement it.
+             */  
+            final GroupMetadata leftExpGm = egml.get(leftExpGroupIndex-1);            
+
+            insertPosition = leftExpGroupIndex;
+
+            /*
+             * Sums the number of groups between the prior exp group and this
+             * one, and then adds it to the prior group's group pos
+             */
+            groupPos =
+                (flPos - leftExpGm.lastChildFlPos) + leftExpGm.gPos;            
+        } else if (rightExpGroupIndex < midExpGroupIndex) {
+
+            /*
+             * This would occur in the second conditional, so the flat list
+             * insertion position is before the right group. Also, the
+             * rightGroupPos is one less than it should be, so increment it.
+             */
+            final GroupMetadata rightExpGm = egml.get(++rightExpGroupIndex);            
+
+            insertPosition = rightExpGroupIndex;
+            
+            /*
+             * Subtracts this group's flat list pos from the group after's flat
+             * list position to find out how many groups are in between the two
+             * groups. Then, subtracts that number from the group after's group
+             * pos to get this group's pos.
+             */
+            groupPos = rightExpGm.gPos - (rightExpGm.flPos - flPos);
+        } else {
+            // TODO: clean exit
+            throw new RuntimeException("Unknown state");
+        }
+        
+        return PositionMetadata.obtain(flPos, ExpandableListPosition.GROUP, groupPos, -1,
+                null, insertPosition);
+    }
+
+    /**
+     * Translates either a group pos or a child pos (+ group it belongs to) to a
+     * flat list position.  If searching for a child and its group is not expanded, this will
+     * return null since the child isn't being shown in the ListView, and hence it has no
+     * position.
+     * 
+     * @param pos a {@link ExpandableListPosition} representing either a group position
+     *        or child position
+     * @return the flat list position encompassed in a {@link PositionMetadata}
+     *         object that contains additional useful info for insertion, etc., or null.
+     */
+    PositionMetadata getFlattenedPos(final ExpandableListPosition pos) {
+        final ArrayList<GroupMetadata> egml = mExpGroupMetadataList;
+        final int numExpGroups = egml.size();
+
+        /* Binary search variables */
+        int leftExpGroupIndex = 0;
+        int rightExpGroupIndex = numExpGroups - 1;
+        int midExpGroupIndex = 0;
+        GroupMetadata midExpGm; 
+        
+        if (numExpGroups == 0) {
+            /*
+             * There aren't any expanded groups, so flPos must be a group and
+             * its flPos will be the same as its group pos.  The
+             * insert position is 0 (since the list is empty).
+             */
+            return PositionMetadata.obtain(pos.groupPos, pos.type,
+                    pos.groupPos, pos.childPos, null, 0);
+        }
+
+        /*
+         * Binary search over the expanded groups to find either the exact
+         * expanded group (if we're looking for a group) or the group that
+         * contains the child we're looking for.
+         */
+        while (leftExpGroupIndex <= rightExpGroupIndex) {
+            midExpGroupIndex = (rightExpGroupIndex - leftExpGroupIndex)/2 + leftExpGroupIndex;
+            midExpGm = egml.get(midExpGroupIndex);
+            
+            if (pos.groupPos > midExpGm.gPos) {
+                /*
+                 * It's after the current middle group, so search right
+                 */
+                leftExpGroupIndex = midExpGroupIndex + 1;
+            } else if (pos.groupPos < midExpGm.gPos) {
+                /*
+                 * It's before the current middle group, so search left
+                 */
+                rightExpGroupIndex = midExpGroupIndex - 1;
+            } else if (pos.groupPos == midExpGm.gPos) {
+                /*
+                 * It's this middle group, exact hit
+                 */
+                
+                if (pos.type == ExpandableListPosition.GROUP) {
+                    /* If it's a group, give them this matched group's flPos */
+                    return PositionMetadata.obtain(midExpGm.flPos, pos.type,
+                            pos.groupPos, pos.childPos, midExpGm, midExpGroupIndex);
+                } else if (pos.type == ExpandableListPosition.CHILD) {
+                    /* If it's a child, calculate the flat list pos */
+                    return PositionMetadata.obtain(midExpGm.flPos + pos.childPos
+                            + 1, pos.type, pos.groupPos, pos.childPos,
+                            midExpGm, midExpGroupIndex);
+                } else {
+                    return null;
+                }
+            } 
+        }
+
+        /* 
+         * If we've reached here, it means there was no match in the expanded
+         * groups, so it must be a collapsed group that they're search for
+         */
+        if (pos.type != ExpandableListPosition.GROUP) {
+            /* If it isn't a group, return null */
+            return null;
+        }
+        
+        /*
+         * To figure out exact insertion and prior group positions, we need to
+         * determine how we broke out of the binary search. We backtrack to see
+         * this.
+         */ 
+        if (leftExpGroupIndex > midExpGroupIndex) {
+            
+            /*
+             * This would occur in the first conditional, so the flat list
+             * insertion position is after the left group.
+             * 
+             * The leftGroupPos is one more than it should be (from the binary
+             * search loop) so we subtract 1 to get the actual left group.  Since
+             * the insertion point is AFTER the left group, we keep this +1
+             * value as the insertion point
+             */  
+            final GroupMetadata leftExpGm = egml.get(leftExpGroupIndex-1);            
+            final int flPos =
+                    leftExpGm.lastChildFlPos
+                            + (pos.groupPos - leftExpGm.gPos);
+
+            return PositionMetadata.obtain(flPos, pos.type, pos.groupPos,
+                    pos.childPos, null, leftExpGroupIndex);
+        } else if (rightExpGroupIndex < midExpGroupIndex) {
+
+            /*
+             * This would occur in the second conditional, so the flat list
+             * insertion position is before the right group. Also, the
+             * rightGroupPos is one less than it should be (from binary search
+             * loop), so we increment to it.
+             */
+            final GroupMetadata rightExpGm = egml.get(++rightExpGroupIndex);            
+            final int flPos =
+                    rightExpGm.flPos
+                            - (rightExpGm.gPos - pos.groupPos);
+            return PositionMetadata.obtain(flPos, pos.type, pos.groupPos,
+                    pos.childPos, null, rightExpGroupIndex);
+        } else {
+            return null;
+        }
+    }
+
+    @Override
+    public boolean areAllItemsEnabled() {
+        return mExpandableListAdapter.areAllItemsEnabled();
+    }
+
+    @Override
+    public boolean isEnabled(int flatListPos) {
+        final ExpandableListPosition pos = getUnflattenedPos(flatListPos).position;
+        
+        boolean retValue;
+        if (pos.type == ExpandableListPosition.CHILD) {
+            retValue = mExpandableListAdapter.isChildSelectable(pos.groupPos, pos.childPos);
+        } else {
+            // Groups are always selectable
+            retValue = true;
+        }
+        
+        pos.recycle();
+        
+        return retValue;
+    }
+
+    public int getCount() {
+        /*
+         * Total count for the list view is the number groups plus the 
+         * number of children from currently expanded groups (a value we keep
+         * cached in this class)
+         */ 
+        return mExpandableListAdapter.getGroupCount() + mTotalExpChildrenCount;
+    }
+
+    public Object getItem(int flatListPos) {
+        final PositionMetadata posMetadata = getUnflattenedPos(flatListPos);
+
+        Object retValue;
+        if (posMetadata.position.type == ExpandableListPosition.GROUP) {
+            retValue = mExpandableListAdapter
+                    .getGroup(posMetadata.position.groupPos);
+        } else if (posMetadata.position.type == ExpandableListPosition.CHILD) {
+            retValue = mExpandableListAdapter.getChild(posMetadata.position.groupPos,
+                    posMetadata.position.childPos);
+        } else {
+            // TODO: clean exit
+            throw new RuntimeException("Flat list position is of unknown type");
+        }
+        
+        posMetadata.recycle();
+        
+        return retValue;
+    }
+
+    public long getItemId(int flatListPos) {
+        final PositionMetadata posMetadata = getUnflattenedPos(flatListPos);
+        final long groupId = mExpandableListAdapter.getGroupId(posMetadata.position.groupPos);
+        
+        long retValue;
+        if (posMetadata.position.type == ExpandableListPosition.GROUP) {
+            retValue = mExpandableListAdapter.getCombinedGroupId(groupId);
+        } else if (posMetadata.position.type == ExpandableListPosition.CHILD) {
+            final long childId = mExpandableListAdapter.getChildId(posMetadata.position.groupPos,
+                    posMetadata.position.childPos);
+            retValue = mExpandableListAdapter.getCombinedChildId(groupId, childId);
+        } else {
+            // TODO: clean exit
+            throw new RuntimeException("Flat list position is of unknown type");
+        }
+        
+        posMetadata.recycle();
+        
+        return retValue;
+    }
+
+    public View getView(int flatListPos, View convertView, ViewGroup parent) {
+        final PositionMetadata posMetadata = getUnflattenedPos(flatListPos);
+
+        View retValue;
+        if (posMetadata.position.type == ExpandableListPosition.GROUP) {
+            retValue = mExpandableListAdapter.getGroupView(posMetadata.position.groupPos, posMetadata
+                    .isExpanded(), convertView, parent);
+        } else if (posMetadata.position.type == ExpandableListPosition.CHILD) {
+            final boolean isLastChild = posMetadata.groupMetadata.lastChildFlPos == flatListPos;
+            
+            retValue = mExpandableListAdapter.getChildView(posMetadata.position.groupPos,
+                    posMetadata.position.childPos, isLastChild, convertView, parent);
+        } else {
+            // TODO: clean exit
+            throw new RuntimeException("Flat list position is of unknown type");
+        }
+        
+        posMetadata.recycle();
+        
+        return retValue;
+    }
+
+    @Override
+    public int getItemViewType(int flatListPos) {
+        final ExpandableListPosition pos = getUnflattenedPos(flatListPos).position;
+
+        int retValue;
+        if (pos.type == ExpandableListPosition.GROUP) {
+            retValue = 0;
+        } else {
+            retValue = 1;
+        }
+        
+        pos.recycle();
+        
+        return retValue;
+    }
+
+    @Override
+    public int getViewTypeCount() {
+        return 2;
+    }
+    
+    @Override
+    public boolean hasStableIds() {
+        return mExpandableListAdapter.hasStableIds();
+    }
+
+    /**
+     * Traverses the expanded group metadata list and fills in the flat list
+     * positions.
+     * 
+     * @param forceChildrenCountRefresh Forces refreshing of the children count
+     *        for all expanded groups.
+     * @param syncGroupPositions Whether to search for the group positions
+     *         based on the group IDs. This should only be needed when calling
+     *         this from an onChanged callback.
+     */
+    @SuppressWarnings("unchecked")
+    private void refreshExpGroupMetadataList(boolean forceChildrenCountRefresh,
+            boolean syncGroupPositions) {
+        final ArrayList<GroupMetadata> egml = mExpGroupMetadataList;
+        int egmlSize = egml.size();
+        int curFlPos = 0;
+        
+        /* Update child count as we go through */
+        mTotalExpChildrenCount = 0;
+        
+        if (syncGroupPositions) {
+            // We need to check whether any groups have moved positions
+            boolean positionsChanged = false;
+            
+            for (int i = egmlSize - 1; i >= 0; i--) {
+                GroupMetadata curGm = egml.get(i);
+                int newGPos = findGroupPosition(curGm.gId, curGm.gPos);
+                if (newGPos != curGm.gPos) {
+                    if (newGPos == AdapterView.INVALID_POSITION) {
+                        // Doh, just remove it from the list of expanded groups
+                        egml.remove(i);
+                        egmlSize--;
+                    }
+                    
+                    curGm.gPos = newGPos;
+                    if (!positionsChanged) positionsChanged = true;
+                }
+            }
+            
+            if (positionsChanged) {
+                // At least one group changed positions, so re-sort
+                Collections.sort(egml);
+            }
+        }
+        
+        int gChildrenCount;
+        int lastGPos = 0;
+        for (int i = 0; i < egmlSize; i++) {
+            /* Store in local variable since we'll access freq */
+            GroupMetadata curGm = egml.get(i);
+            
+            /*
+             * Get the number of children, try to refrain from calling
+             * another class's method unless we have to (so do a subtraction)
+             */
+            if ((curGm.lastChildFlPos == GroupMetadata.REFRESH) || forceChildrenCountRefresh) {
+                gChildrenCount = mExpandableListAdapter.getChildrenCount(curGm.gPos);
+            } else {
+                /* Num children for this group is its last child's fl pos minus
+                 * the group's fl pos
+                 */
+                gChildrenCount = curGm.lastChildFlPos - curGm.flPos;
+            }
+            
+            /* Update */
+            mTotalExpChildrenCount += gChildrenCount;
+            
+            /*
+             * This skips the collapsed groups and increments the flat list
+             * position (for subsequent exp groups) by accounting for the collapsed
+             * groups
+             */
+            curFlPos += (curGm.gPos - lastGPos);
+            lastGPos = curGm.gPos;
+            
+            /* Update the flat list positions, and the current flat list pos */
+            curGm.flPos = curFlPos;
+            curFlPos += gChildrenCount; 
+            curGm.lastChildFlPos = curFlPos; 
+        }
+    }
+    
+    /**
+     * Collapse a group in the grouped list view
+     * 
+     * @param groupPos position of the group to collapse
+     */
+    boolean collapseGroup(int groupPos) {
+        PositionMetadata pm = getFlattenedPos(ExpandableListPosition.obtain(
+                ExpandableListPosition.GROUP, groupPos, -1, -1)); 
+        if (pm == null) return false;
+        
+        boolean retValue = collapseGroup(pm);
+        pm.recycle();
+        return retValue;
+    }
+    
+    boolean collapseGroup(PositionMetadata posMetadata) {
+        /*
+         * Collapsing requires removal from mExpGroupMetadataList 
+         */
+        
+        /*
+         * If it is null, it must be already collapsed. This group metadata
+         * object should have been set from the search that returned the
+         * position metadata object.
+         */
+        if (posMetadata.groupMetadata == null) return false;
+        
+        // Remove the group from the list of expanded groups 
+        mExpGroupMetadataList.remove(posMetadata.groupMetadata);
+
+        // Refresh the metadata
+        refreshExpGroupMetadataList(false, false);
+        
+        // Notify of change
+        notifyDataSetChanged();
+        
+        // Give the callback
+        mExpandableListAdapter.onGroupCollapsed(posMetadata.groupMetadata.gPos);
+        
+        return true;
+    }
+
+    /**
+     * Expand a group in the grouped list view
+     * @param groupPos the group to be expanded
+     */
+    boolean expandGroup(int groupPos) {
+        PositionMetadata pm = getFlattenedPos(ExpandableListPosition.obtain(
+                ExpandableListPosition.GROUP, groupPos, -1, -1));
+        boolean retValue = expandGroup(pm);
+        pm.recycle();
+        return retValue;
+    }
+
+    boolean expandGroup(PositionMetadata posMetadata) {
+        /*
+         * Expanding requires insertion into the mExpGroupMetadataList 
+         */
+
+        if (posMetadata.position.groupPos < 0) {
+            // TODO clean exit
+            throw new RuntimeException("Need group");
+        }
+
+        if (mMaxExpGroupCount == 0) return false;
+        
+        // Check to see if it's already expanded
+        if (posMetadata.groupMetadata != null) return false;
+        
+        /* Restrict number of exp groups to mMaxExpGroupCount */
+        if (mExpGroupMetadataList.size() >= mMaxExpGroupCount) {
+            /* Collapse a group */
+            // TODO: Collapse something not on the screen instead of the first one?
+            // TODO: Could write overloaded function to take GroupMetadata to collapse
+            GroupMetadata collapsedGm = mExpGroupMetadataList.get(0);
+            
+            int collapsedIndex = mExpGroupMetadataList.indexOf(collapsedGm);
+            
+            collapseGroup(collapsedGm.gPos);
+
+            /* Decrement index if it is after the group we removed */
+            if (posMetadata.groupInsertIndex > collapsedIndex) {
+                posMetadata.groupInsertIndex--;
+            }
+        }
+        
+        GroupMetadata expandedGm = GroupMetadata.obtain(
+                GroupMetadata.REFRESH,
+                GroupMetadata.REFRESH,
+                posMetadata.position.groupPos,
+                mExpandableListAdapter.getGroupId(posMetadata.position.groupPos));
+        
+        mExpGroupMetadataList.add(posMetadata.groupInsertIndex, expandedGm);
+
+        // Refresh the metadata
+        refreshExpGroupMetadataList(false, false);
+        
+        // Notify of change
+        notifyDataSetChanged();
+        
+        // Give the callback
+        mExpandableListAdapter.onGroupExpanded(expandedGm.gPos);
+
+        return true;
+    }
+    
+    /**
+     * Whether the given group is currently expanded.
+     * @param groupPosition The group to check.
+     * @return Whether the group is currently expanded.
+     */
+    public boolean isGroupExpanded(int groupPosition) {
+        GroupMetadata groupMetadata;
+        for (int i = mExpGroupMetadataList.size() - 1; i >= 0; i--) {
+            groupMetadata = mExpGroupMetadataList.get(i);
+            
+            if (groupMetadata.gPos == groupPosition) {
+                return true;
+            }
+        }
+        
+        return false;
+    }
+    
+    /**
+     * Set the maximum number of groups that can be expanded at any given time
+     */
+    public void setMaxExpGroupCount(int maxExpGroupCount) {
+        mMaxExpGroupCount = maxExpGroupCount;
+    }    
+
+    ExpandableListAdapter getAdapter() {
+        return mExpandableListAdapter;
+    }
+    
+    public Filter getFilter() {
+        ExpandableListAdapter adapter = getAdapter();
+        if (adapter instanceof Filterable) {
+            return ((Filterable) adapter).getFilter();
+        } else {
+            return null;
+        }
+    }
+
+    ArrayList<GroupMetadata> getExpandedGroupMetadataList() {
+        return mExpGroupMetadataList;
+    }
+    
+    void setExpandedGroupMetadataList(ArrayList<GroupMetadata> expandedGroupMetadataList) {
+        
+        if ((expandedGroupMetadataList == null) || (mExpandableListAdapter == null)) {
+            return;
+        }
+        
+        // Make sure our current data set is big enough for the previously
+        // expanded groups, if not, ignore this request
+        int numGroups = mExpandableListAdapter.getGroupCount();
+        for (int i = expandedGroupMetadataList.size() - 1; i >= 0; i--) {
+            if (expandedGroupMetadataList.get(i).gPos >= numGroups) {
+                // Doh, for some reason the client doesn't have some of the groups
+                return;
+            }
+        }
+        
+        mExpGroupMetadataList = expandedGroupMetadataList;
+        refreshExpGroupMetadataList(true, false);
+    }
+    
+    @Override
+    public boolean isEmpty() {
+        ExpandableListAdapter adapter = getAdapter();
+        return adapter != null ? adapter.isEmpty() : true;
+    }
+
+    /**
+     * Searches the expandable list adapter for a group position matching the
+     * given group ID. The search starts at the given seed position and then
+     * alternates between moving up and moving down until 1) we find the right
+     * position, or 2) we run out of time, or 3) we have looked at every
+     * position
+     * 
+     * @return Position of the row that matches the given row ID, or
+     *         {@link AdapterView#INVALID_POSITION} if it can't be found
+     * @see AdapterView#findSyncPosition()
+     */
+    int findGroupPosition(long groupIdToMatch, int seedGroupPosition) {
+        int count = mExpandableListAdapter.getGroupCount();
+
+        if (count == 0) {
+            return AdapterView.INVALID_POSITION;
+        }
+
+        // If there isn't a selection don't hunt for it
+        if (groupIdToMatch == AdapterView.INVALID_ROW_ID) {
+            return AdapterView.INVALID_POSITION;
+        }
+
+        // Pin seed to reasonable values
+        seedGroupPosition = Math.max(0, seedGroupPosition);
+        seedGroupPosition = Math.min(count - 1, seedGroupPosition);
+
+        long endTime = SystemClock.uptimeMillis() + AdapterView.SYNC_MAX_DURATION_MILLIS;
+
+        long rowId;
+
+        // first position scanned so far
+        int first = seedGroupPosition;
+
+        // last position scanned so far
+        int last = seedGroupPosition;
+
+        // True if we should move down on the next iteration
+        boolean next = false;
+
+        // True when we have looked at the first item in the data
+        boolean hitFirst;
+
+        // True when we have looked at the last item in the data
+        boolean hitLast;
+
+        // Get the item ID locally (instead of getItemIdAtPosition), so
+        // we need the adapter
+        ExpandableListAdapter adapter = getAdapter();
+        if (adapter == null) {
+            return AdapterView.INVALID_POSITION;
+        }
+
+        while (SystemClock.uptimeMillis() <= endTime) {
+            rowId = adapter.getGroupId(seedGroupPosition);
+            if (rowId == groupIdToMatch) {
+                // Found it!
+                return seedGroupPosition;
+            }
+
+            hitLast = last == count - 1;
+            hitFirst = first == 0;
+
+            if (hitLast && hitFirst) {
+                // Looked at everything
+                break;
+            }
+
+            if (hitFirst || (next && !hitLast)) {
+                // Either we hit the top, or we are trying to move down
+                last++;
+                seedGroupPosition = last;
+                // Try going up next time
+                next = false;
+            } else if (hitLast || (!next && !hitFirst)) {
+                // Either we hit the bottom, or we are trying to move up
+                first--;
+                seedGroupPosition = first;
+                // Try going down next time
+                next = true;
+            }
+
+        }
+
+        return AdapterView.INVALID_POSITION;
+    }
+
+    protected class MyDataSetObserver extends DataSetObserver {
+        @Override
+        public void onChanged() {
+            refreshExpGroupMetadataList(true, true);
+            
+            notifyDataSetChanged();
+        }
+
+        @Override
+        public void onInvalidated() {
+            refreshExpGroupMetadataList(true, true);
+            
+            notifyDataSetInvalidated();
+        }
+    }
+    
+    /**
+     * Metadata about an expanded group to help convert from a flat list
+     * position to either a) group position for groups, or b) child position for
+     * children
+     */
+    static class GroupMetadata implements Parcelable, Comparable {
+        final static int REFRESH = -1;
+        
+        /** This group's flat list position */
+        int flPos;
+        
+        /* firstChildFlPos isn't needed since it's (flPos + 1) */
+        
+        /**
+         * This group's last child's flat list position, so basically
+         * the range of this group in the flat list
+         */
+        int lastChildFlPos;
+        
+        /**
+         * This group's group position
+         */
+        int gPos;
+        
+        /**
+         * This group's id
+         */
+        long gId;
+        
+        private GroupMetadata() {
+        }
+
+        static GroupMetadata obtain(int flPos, int lastChildFlPos, int gPos, long gId) {
+            GroupMetadata gm = new GroupMetadata();
+            gm.flPos = flPos;
+            gm.lastChildFlPos = lastChildFlPos;
+            gm.gPos = gPos;
+            gm.gId = gId;
+            return gm;
+        }
+        
+        public int compareTo(Object another) {
+            if (another == null || !(another instanceof GroupMetadata)) {
+                throw new ClassCastException();
+            }
+            
+            return gPos - ((GroupMetadata) another).gPos;
+        }
+
+        public int describeContents() {
+            return 0;
+        }
+
+        public void writeToParcel(Parcel dest, int flags) {
+            dest.writeInt(flPos);
+            dest.writeInt(lastChildFlPos);
+            dest.writeInt(gPos);
+            dest.writeLong(gId);
+        }
+        
+        public static final Parcelable.Creator<GroupMetadata> CREATOR =
+                new Parcelable.Creator<GroupMetadata>() {
+            
+            public GroupMetadata createFromParcel(Parcel in) {
+                GroupMetadata gm = GroupMetadata.obtain(
+                        in.readInt(),
+                        in.readInt(),
+                        in.readInt(),
+                        in.readLong());
+                return gm;
+            }
+    
+            public GroupMetadata[] newArray(int size) {
+                return new GroupMetadata[size];
+            }
+        };
+        
+    }
+
+    /**
+     * Data type that contains an expandable list position (can refer to either a group
+     * or child) and some extra information regarding referred item (such as
+     * where to insert into the flat list, etc.)
+     */
+    static public class PositionMetadata {
+        
+        private static final int MAX_POOL_SIZE = 5;
+        private static ArrayList<PositionMetadata> sPool =
+                new ArrayList<PositionMetadata>(MAX_POOL_SIZE);
+        
+        /** Data type to hold the position and its type (child/group) */
+        public ExpandableListPosition position;
+        
+        /**
+         * Link back to the expanded GroupMetadata for this group. Useful for
+         * removing the group from the list of expanded groups inside the
+         * connector when we collapse the group, and also as a check to see if
+         * the group was expanded or collapsed (this will be null if the group
+         * is collapsed since we don't keep that group's metadata)
+         */
+        public GroupMetadata groupMetadata;
+
+        /**
+         * For groups that are collapsed, we use this as the index (in
+         * mExpGroupMetadataList) to insert this group when we are expanding
+         * this group.
+         */
+        public int groupInsertIndex;
+        
+        private void resetState() {
+            position = null;
+            groupMetadata = null;
+            groupInsertIndex = 0;
+        }
+        
+        /**
+         * Use {@link #obtain(int, int, int, int, GroupMetadata, int)}
+         */
+        private PositionMetadata() {
+        }
+        
+        static PositionMetadata obtain(int flatListPos, int type, int groupPos,
+                int childPos, GroupMetadata groupMetadata, int groupInsertIndex) {
+            PositionMetadata pm = getRecycledOrCreate();
+            pm.position = ExpandableListPosition.obtain(type, groupPos, childPos, flatListPos);
+            pm.groupMetadata = groupMetadata;
+            pm.groupInsertIndex = groupInsertIndex;
+            return pm;
+        }
+        
+        private static PositionMetadata getRecycledOrCreate() {
+            PositionMetadata pm;
+            synchronized (sPool) {
+                if (sPool.size() > 0) {
+                    pm = sPool.remove(0);
+                } else {
+                    return new PositionMetadata();
+                }
+            }
+            pm.resetState();
+            return pm;
+        }
+        
+        public void recycle() {
+            synchronized (sPool) {
+                if (sPool.size() < MAX_POOL_SIZE) {
+                    sPool.add(this);
+                }
+            }
+        }
+        
+        /**
+         * Checks whether the group referred to in this object is expanded,
+         * or not (at the time this object was created)
+         * 
+         * @return whether the group at groupPos is expanded or not
+         */
+        public boolean isExpanded() {
+            return groupMetadata != null;
+        }
+    }
+}
diff --git a/core/java/android/widget/ExpandableListPosition.java b/core/java/android/widget/ExpandableListPosition.java
new file mode 100644
index 0000000..e8d6113
--- /dev/null
+++ b/core/java/android/widget/ExpandableListPosition.java
@@ -0,0 +1,135 @@
+/*
+ * Copyright (C) 2007 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 android.widget;
+
+import java.util.ArrayList;
+
+/**
+ * ExpandableListPosition can refer to either a group's position or a child's
+ * position. Referring to a child's position requires both a group position (the
+ * group containing the child) and a child position (the child's position within
+ * that group). To create objects, use {@link #obtainChildPosition(int, int)} or
+ * {@link #obtainGroupPosition(int)}.
+ */
+class ExpandableListPosition {
+    
+    private static final int MAX_POOL_SIZE = 5;
+    private static ArrayList<ExpandableListPosition> sPool =
+        new ArrayList<ExpandableListPosition>(MAX_POOL_SIZE);
+    
+    /**
+     * This data type represents a child position
+     */
+    public final static int CHILD = 1;
+
+    /**
+     * This data type represents a group position
+     */
+    public final static int GROUP = 2;
+
+    /**
+     * The position of either the group being referred to, or the parent
+     * group of the child being referred to
+     */
+    public int groupPos;
+
+    /**
+     * The position of the child within its parent group 
+     */
+    public int childPos;
+
+    /**
+     * The position of the item in the flat list (optional, used internally when
+     * the corresponding flat list position for the group or child is known)
+     */
+    int flatListPos;
+    
+    /**
+     * What type of position this ExpandableListPosition represents
+     */
+    public int type;
+    
+    private void resetState() {
+        groupPos = 0;
+        childPos = 0;
+        flatListPos = 0;
+        type = 0;
+    }
+    
+    private ExpandableListPosition() {
+    }
+    
+    long getPackedPosition() {
+        if (type == CHILD) return ExpandableListView.getPackedPositionForChild(groupPos, childPos);
+        else return ExpandableListView.getPackedPositionForGroup(groupPos);
+    }
+
+    static ExpandableListPosition obtainGroupPosition(int groupPosition) {
+        return obtain(GROUP, groupPosition, 0, 0);
+    }
+    
+    static ExpandableListPosition obtainChildPosition(int groupPosition, int childPosition) {
+        return obtain(CHILD, groupPosition, childPosition, 0);
+    }
+
+    static ExpandableListPosition obtainPosition(long packedPosition) {
+        if (packedPosition == ExpandableListView.PACKED_POSITION_VALUE_NULL) {
+            return null;
+        }
+        
+        ExpandableListPosition elp = getRecycledOrCreate(); 
+        elp.groupPos = ExpandableListView.getPackedPositionGroup(packedPosition);
+        if (ExpandableListView.getPackedPositionType(packedPosition) ==
+                ExpandableListView.PACKED_POSITION_TYPE_CHILD) {
+            elp.type = CHILD;
+            elp.childPos = ExpandableListView.getPackedPositionChild(packedPosition);
+        } else {
+            elp.type = GROUP;
+        }
+        return elp;
+    }
+    
+    static ExpandableListPosition obtain(int type, int groupPos, int childPos, int flatListPos) {
+        ExpandableListPosition elp = getRecycledOrCreate(); 
+        elp.type = type;
+        elp.groupPos = groupPos;
+        elp.childPos = childPos;
+        elp.flatListPos = flatListPos;
+        return elp;
+    }
+    
+    private static ExpandableListPosition getRecycledOrCreate() {
+        ExpandableListPosition elp;
+        synchronized (sPool) {
+            if (sPool.size() > 0) {
+                elp = sPool.remove(0);
+            } else {
+                return new ExpandableListPosition();
+            }
+        }
+        elp.resetState();
+        return elp;
+    }
+    
+    public void recycle() {
+        synchronized (sPool) {
+            if (sPool.size() < MAX_POOL_SIZE) {
+                sPool.add(this);
+            }
+        }
+    }
+}
diff --git a/core/java/android/widget/ExpandableListView.java b/core/java/android/widget/ExpandableListView.java
new file mode 100644
index 0000000..0fc8f49
--- /dev/null
+++ b/core/java/android/widget/ExpandableListView.java
@@ -0,0 +1,1094 @@
+/*
+ * 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 android.widget;
+
+import com.android.internal.R;
+
+import java.util.ArrayList;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Canvas;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.ColorDrawable;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.AttributeSet;
+import android.view.ContextMenu;
+import android.view.SoundEffectConstants;
+import android.view.View;
+import android.view.ContextMenu.ContextMenuInfo;
+import android.widget.ExpandableListConnector.PositionMetadata;
+
+/**
+ * A view that shows items in a vertically scrolling two-level list. This
+ * differs from the {@link ListView} by allowing two levels: groups which can
+ * individually be expanded to show its children. The items come from the
+ * {@link ExpandableListAdapter} associated with this view.
+ * <p>
+ * Expandable lists are able to show an indicator beside each item to display
+ * the item's current state (the states are usually one of expanded group,
+ * collapsed group, child, or last child). Use
+ * {@link #setChildIndicator(Drawable)} or {@link #setGroupIndicator(Drawable)}
+ * (or the corresponding XML attributes) to set these indicators (see the docs
+ * for each method to see additional state that each Drawable can have). The
+ * default style for an {@link ExpandableListView} provides indicators which
+ * will be shown next to Views given to the {@link ExpandableListView}. The
+ * layouts android.R.layout.simple_expandable_list_item_1 and
+ * android.R.layout.simple_expandable_list_item_2 (which should be used with
+ * {@link SimpleCursorTreeAdapter}) contain the preferred position information
+ * for indicators.
+ * <p>
+ * The context menu information set by an {@link ExpandableListView} will be a
+ * {@link ExpandableListContextMenuInfo} object with
+ * {@link ExpandableListContextMenuInfo#packedPosition} being a packed position
+ * that can be used with {@link #getPackedPositionType(long)} and the other
+ * similar methods.
+ * <p>
+ * <em><b>Note:</b></em> You cannot use the value <code>wrap_content</code>
+ * for the <code>android:layout_height</code> attribute of a
+ * ExpandableListView in XML if the parent's size is also not strictly specified
+ * (for example, if the parent were ScrollView you could not specify
+ * wrap_content since it also can be any length. However, you can use
+ * wrap_content if the ExpandableListView parent has a specific size, such as
+ * 100 pixels.
+ * 
+ * @attr ref android.R.styleable#ExpandableListView_groupIndicator
+ * @attr ref android.R.styleable#ExpandableListView_indicatorLeft
+ * @attr ref android.R.styleable#ExpandableListView_indicatorRight
+ * @attr ref android.R.styleable#ExpandableListView_childIndicator
+ * @attr ref android.R.styleable#ExpandableListView_childIndicatorLeft
+ * @attr ref android.R.styleable#ExpandableListView_childIndicatorRight
+ * @attr ref android.R.styleable#ExpandableListView_childDivider
+ */
+public class ExpandableListView extends ListView {
+
+    /**
+     * The packed position represents a group.
+     */
+    public static final int PACKED_POSITION_TYPE_GROUP = 0;
+    
+    /**
+     * The packed position represents a child.
+     */
+    public static final int PACKED_POSITION_TYPE_CHILD = 1;
+
+    /**
+     * The packed position represents a neither/null/no preference.
+     */
+    public static final int PACKED_POSITION_TYPE_NULL = 2;
+    
+    /**
+     * The value for a packed position that represents neither/null/no
+     * preference. This value is not otherwise possible since a group type
+     * (first bit 0) should not have a child position filled.
+     */
+    public static final long PACKED_POSITION_VALUE_NULL = 0x00000000FFFFFFFFL;
+    
+    /** The mask (in packed position representation) for the child */
+    private static final long PACKED_POSITION_MASK_CHILD = 0x00000000FFFFFFFFL;
+
+    /** The mask (in packed position representation) for the group */
+    private static final long PACKED_POSITION_MASK_GROUP = 0x7FFFFFFF00000000L;
+
+    /** The mask (in packed position representation) for the type */
+    private static final long PACKED_POSITION_MASK_TYPE  = 0x8000000000000000L;
+
+    /** The shift amount (in packed position representation) for the group */
+    private static final long PACKED_POSITION_SHIFT_GROUP = 32;
+
+    /** The shift amount (in packed position representation) for the type */
+    private static final long PACKED_POSITION_SHIFT_TYPE  = 63;
+
+    /** The mask (in integer child position representation) for the child */
+    private static final long PACKED_POSITION_INT_MASK_CHILD = 0xFFFFFFFF;
+
+    /** The mask (in integer group position representation) for the group */
+    private static final long PACKED_POSITION_INT_MASK_GROUP = 0x7FFFFFFF;
+    
+    /** Serves as the glue/translator between a ListView and an ExpandableListView */
+    private ExpandableListConnector mConnector;
+    
+    /** Gives us Views through group+child positions */ 
+    private ExpandableListAdapter mAdapter;
+    
+    /** Left bound for drawing the indicator. */
+    private int mIndicatorLeft;
+
+    /** Right bound for drawing the indicator. */
+    private int mIndicatorRight;
+
+    /**
+     * Left bound for drawing the indicator of a child. Value of
+     * {@link #CHILD_INDICATOR_INHERIT} means use mIndicatorLeft.
+     */
+    private int mChildIndicatorLeft;
+
+    /**
+     * Right bound for drawing the indicator of a child. Value of
+     * {@link #CHILD_INDICATOR_INHERIT} means use mIndicatorRight.
+     */
+    private int mChildIndicatorRight;
+
+    /**
+     * Denotes when a child indicator should inherit this bound from the generic
+     * indicator bounds
+     */
+    public static final int CHILD_INDICATOR_INHERIT = -1;
+    
+    /** The indicator drawn next to a group. */
+    private Drawable mGroupIndicator;
+
+    /** The indicator drawn next to a child. */
+    private Drawable mChildIndicator;
+
+    private static final int[] EMPTY_STATE_SET = {};
+
+    /** State indicating the group is expanded. */
+    private static final int[] GROUP_EXPANDED_STATE_SET =
+            {R.attr.state_expanded};
+
+    /** State indicating the group is empty (has no children). */
+    private static final int[] GROUP_EMPTY_STATE_SET =
+            {R.attr.state_empty};
+
+    /** State indicating the group is expanded and empty (has no children). */
+    private static final int[] GROUP_EXPANDED_EMPTY_STATE_SET =
+            {R.attr.state_expanded, R.attr.state_empty};
+
+    /** States for the group where the 0th bit is expanded and 1st bit is empty. */
+    private static final int[][] GROUP_STATE_SETS = {
+         EMPTY_STATE_SET, // 00
+         GROUP_EXPANDED_STATE_SET, // 01
+         GROUP_EMPTY_STATE_SET, // 10
+         GROUP_EXPANDED_EMPTY_STATE_SET // 11
+    };
+
+    /** State indicating the child is the last within its group. */
+    private static final int[] CHILD_LAST_STATE_SET =
+            {R.attr.state_last};
+    
+    /** Drawable to be used as a divider when it is adjacent to any children */
+    private Drawable mChildDivider;
+    private boolean mClipChildDivider;
+
+    // Bounds of the indicator to be drawn
+    private final Rect mIndicatorRect = new Rect();
+
+    public ExpandableListView(Context context) {
+        this(context, null);
+    }
+
+    public ExpandableListView(Context context, AttributeSet attrs) {
+        this(context, attrs, com.android.internal.R.attr.expandableListViewStyle);
+    }
+
+    public ExpandableListView(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+
+        TypedArray a =
+            context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.ExpandableListView, defStyle,
+                    0);
+
+        mGroupIndicator = a
+                .getDrawable(com.android.internal.R.styleable.ExpandableListView_groupIndicator);
+        mChildIndicator = a
+                .getDrawable(com.android.internal.R.styleable.ExpandableListView_childIndicator);
+        mIndicatorLeft = a
+                .getDimensionPixelSize(com.android.internal.R.styleable.ExpandableListView_indicatorLeft, 0);
+        mIndicatorRight = a
+                .getDimensionPixelSize(com.android.internal.R.styleable.ExpandableListView_indicatorRight, 0);
+        mChildIndicatorLeft = a.getDimensionPixelSize(
+                com.android.internal.R.styleable.ExpandableListView_childIndicatorLeft, CHILD_INDICATOR_INHERIT);
+        mChildIndicatorRight = a.getDimensionPixelSize(
+                com.android.internal.R.styleable.ExpandableListView_childIndicatorRight, CHILD_INDICATOR_INHERIT);
+        mChildDivider = a.getDrawable(com.android.internal.R.styleable.ExpandableListView_childDivider);
+        
+        a.recycle();
+    }
+    
+    
+    @Override
+    protected void dispatchDraw(Canvas canvas) {
+        // Draw children, etc.
+        super.dispatchDraw(canvas);
+
+        // If we have any indicators to draw, we do it here
+        if ((mChildIndicator == null) && (mGroupIndicator == null)) {
+            return;
+        }
+
+        int saveCount = 0;
+        final boolean clipToPadding = (mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK;
+        if (clipToPadding) {
+            saveCount = canvas.save();
+            final int scrollX = mScrollX;
+            final int scrollY = mScrollY;
+            canvas.clipRect(scrollX + mPaddingLeft, scrollY + mPaddingTop,
+                    scrollX + mRight - mLeft - mPaddingRight,
+                    scrollY + mBottom - mTop - mPaddingBottom);
+        }
+
+        final int headerViewsCount = getHeaderViewsCount();
+        
+        final int lastChildFlPos = mItemCount - getFooterViewsCount() - headerViewsCount - 1;
+
+        final int myB = mBottom; 
+        
+        PositionMetadata pos;
+        View item;
+        Drawable indicator; 
+        int t, b;
+        
+        // Start at a value that is neither child nor group
+        int lastItemType = ~(ExpandableListPosition.CHILD | ExpandableListPosition.GROUP);
+
+        final Rect indicatorRect = mIndicatorRect;
+
+        // The "child" mentioned in the following two lines is this
+        // View's child, not referring to an expandable list's
+        // notion of a child (as opposed to a group)
+        final int childCount = getChildCount(); 
+        for (int i = 0, childFlPos = mFirstPosition - headerViewsCount; i < childCount;
+             i++, childFlPos++) {
+
+            if (childFlPos < 0) {
+                // This child is header
+                continue;
+            } else if (childFlPos > lastChildFlPos) {
+                // This child is footer, so are all subsequent children
+                break;
+            }
+            
+            item = getChildAt(i);
+            t = item.getTop();
+            b = item.getBottom();
+            
+            // This item isn't on the screen
+            if ((b < 0) || (t > myB)) continue;
+
+            // Get more expandable list-related info for this item
+            pos = mConnector.getUnflattenedPos(childFlPos);
+
+            // If this item type and the previous item type are different, then we need to change
+            // the left & right bounds
+            if (pos.position.type != lastItemType) {
+                if (pos.position.type == ExpandableListPosition.CHILD) {
+                    indicatorRect.left = (mChildIndicatorLeft == CHILD_INDICATOR_INHERIT) ?
+                            mIndicatorLeft : mChildIndicatorLeft;
+                    indicatorRect.right = (mChildIndicatorRight == CHILD_INDICATOR_INHERIT) ?
+                            mIndicatorRight : mChildIndicatorRight;
+                } else {
+                    indicatorRect.left = mIndicatorLeft;
+                    indicatorRect.right = mIndicatorRight;
+                }
+                
+                lastItemType = pos.position.type; 
+            }
+
+            if (indicatorRect.left != indicatorRect.right) {
+                // Use item's full height + the divider height
+                if (mStackFromBottom) {
+                    // See ListView#dispatchDraw
+                    indicatorRect.top = t;// - mDividerHeight;
+                    indicatorRect.bottom = b;
+                } else {
+                    indicatorRect.top = t;
+                    indicatorRect.bottom = b;// + mDividerHeight;
+                }
+                
+                // Get the indicator (with its state set to the item's state)
+                indicator = getIndicator(pos);
+                if (indicator != null) {
+                    // Draw the indicator
+                    indicator.setBounds(indicatorRect);
+                    indicator.draw(canvas);
+                }
+            }
+            
+            pos.recycle();
+        }
+
+        if (clipToPadding) {
+            canvas.restoreToCount(saveCount);
+        }
+    }
+
+    /**
+     * Gets the indicator for the item at the given position. If the indicator
+     * is stateful, the state will be given to the indicator.
+     * 
+     * @param pos The flat list position of the item whose indicator
+     *            should be returned.
+     * @return The indicator in the proper state.
+     */
+    private Drawable getIndicator(PositionMetadata pos) {
+        Drawable indicator;
+        
+        if (pos.position.type == ExpandableListPosition.GROUP) {
+            indicator = mGroupIndicator;
+            
+            if (indicator != null && indicator.isStateful()) {
+                // Empty check based on availability of data.  If the groupMetadata isn't null,
+                // we do a check on it. Otherwise, the group is collapsed so we consider it
+                // empty for performance reasons.
+                boolean isEmpty = (pos.groupMetadata == null) ||
+                        (pos.groupMetadata.lastChildFlPos == pos.groupMetadata.flPos);
+                
+                final int stateSetIndex =
+                    (pos.isExpanded() ? 1 : 0) | // Expanded?
+                    (isEmpty ? 2 : 0); // Empty?
+                indicator.setState(GROUP_STATE_SETS[stateSetIndex]);
+            }
+        } else {
+            indicator = mChildIndicator;
+            
+            if (indicator != null && indicator.isStateful()) {
+                // No need for a state sets array for the child since it only has two states
+                final int stateSet[] = pos.position.flatListPos == pos.groupMetadata.lastChildFlPos
+                        ? CHILD_LAST_STATE_SET
+                        : EMPTY_STATE_SET;
+                indicator.setState(stateSet);
+            }
+        }
+        
+        return indicator;
+    }
+    
+    /**
+     * Sets the drawable that will be drawn adjacent to every child in the list. This will
+     * be drawn using the same height as the normal divider ({@link #setDivider(Drawable)}) or
+     * if it does not have an intrinsic height, the height set by {@link #setDividerHeight(int)}.
+     * 
+     * @param childDivider The drawable to use.
+     */
+    public void setChildDivider(Drawable childDivider) {
+        mChildDivider = childDivider;
+        mClipChildDivider = childDivider != null && childDivider instanceof ColorDrawable;
+    }
+
+    @Override
+    void drawDivider(Canvas canvas, Rect bounds, int childIndex) {
+        int flatListPosition = childIndex + mFirstPosition;
+        
+        // Only proceed as possible child if the divider isn't above all items (if it is above
+        // all items, then the item below it has to be a group)
+        if (flatListPosition >= 0) {
+            PositionMetadata pos = mConnector.getUnflattenedPos(flatListPosition);
+            // If this item is a child, or it is a non-empty group that is expanded
+            if ((pos.position.type == ExpandableListPosition.CHILD) || (pos.isExpanded() &&
+                    pos.groupMetadata.lastChildFlPos != pos.groupMetadata.flPos)) {
+                // These are the cases where we draw the child divider
+                final Drawable divider = mChildDivider;
+                final boolean clip = mClipChildDivider;
+                if (!clip) {
+                    divider.setBounds(bounds);
+                } else {
+                    canvas.save();
+                    canvas.clipRect(bounds);
+                }
+                divider.draw(canvas);
+                if (clip) {
+                    canvas.restore();
+                }
+                pos.recycle();
+                return;
+            }
+            pos.recycle();
+        }
+        
+        // Otherwise draw the default divider
+        super.drawDivider(canvas, bounds, flatListPosition);
+    }
+
+    /**
+     * This overloaded method should not be used, instead use
+     * {@link #setAdapter(ExpandableListAdapter)}.
+     * <p>
+     * {@inheritDoc}
+     */
+    @Override
+    public void setAdapter(ListAdapter adapter) {
+        throw new RuntimeException(
+                "For ExpandableListView, use setAdapter(ExpandableListAdapter) instead of " +
+                "setAdapter(ListAdapter)");
+    }
+
+    /**
+     * This method should not be used, use {@link #getExpandableListAdapter()}.
+     */
+    @Override
+    public ListAdapter getAdapter() {
+        /*
+         * The developer should never really call this method on an
+         * ExpandableListView, so it would be nice to throw a RuntimeException,
+         * but AdapterView calls this
+         */
+        return super.getAdapter();
+    }
+
+    /**
+     * Register a callback to be invoked when an item has been clicked and the
+     * caller prefers to receive a ListView-style position instead of a group
+     * and/or child position. In most cases, the caller should use
+     * {@link #setOnGroupClickListener} and/or {@link #setOnChildClickListener}.
+     * <p />
+     * {@inheritDoc}
+     */
+    @Override
+    public void setOnItemClickListener(OnItemClickListener l) {
+        super.setOnItemClickListener(l);
+    }
+
+    /**
+     * Sets the adapter that provides data to this view.
+     * @param adapter The adapter that provides data to this view.
+     */
+    public void setAdapter(ExpandableListAdapter adapter) {
+        // Set member variable
+        mAdapter = adapter;
+        
+        if (adapter != null) {
+            // Create the connector
+            mConnector = new ExpandableListConnector(adapter);
+        } else {
+            mConnector = null;
+        }
+        
+        // Link the ListView (superclass) to the expandable list data through the connector
+        super.setAdapter(mConnector);
+    }
+    
+    /**
+     * Gets the adapter that provides data to this view.
+     * @return The adapter that provides data to this view.
+     */
+    public ExpandableListAdapter getExpandableListAdapter() {
+        return mAdapter;
+    }
+    
+    @Override
+    public boolean performItemClick(View v, int position, long id) {
+        // Ignore clicks in header/footers
+        final int headerViewsCount = getHeaderViewsCount();
+        final int footerViewsStart = mItemCount - getFooterViewsCount();
+
+        if (position < headerViewsCount || position >= footerViewsStart) {
+            // Clicked on a header/footer, so ignore pass it on to super
+            return super.performItemClick(v, position, id);
+        }
+        
+        // Internally handle the item click
+        return handleItemClick(v, position - headerViewsCount, id);
+    }
+    
+    /**
+     * This will either expand/collapse groups (if a group was clicked) or pass
+     * on the click to the proper child (if a child was clicked)
+     * 
+     * @param position The flat list position. This has already been factored to
+     *            remove the header/footer.
+     * @param id The ListAdapter ID, not the group or child ID.
+     */
+    boolean handleItemClick(View v, int position, long id) {
+        final PositionMetadata posMetadata = mConnector.getUnflattenedPos(position);
+        
+        id = getChildOrGroupId(posMetadata.position);
+        
+        boolean returnValue;
+        if (posMetadata.position.type == ExpandableListPosition.GROUP) {
+            /* It's a group, so handle collapsing/expanding */
+            
+            if (posMetadata.isExpanded()) {
+                /* Collapse it */
+                mConnector.collapseGroup(posMetadata);
+
+                playSoundEffect(SoundEffectConstants.CLICK);
+                
+                if (mOnGroupCollapseListener != null) {
+                    mOnGroupCollapseListener.onGroupCollapse(posMetadata.position.groupPos);
+                }
+                
+            } else {
+                /* It's a group click, so pass on event */
+                if (mOnGroupClickListener != null) {
+                    if (mOnGroupClickListener.onGroupClick(this, v,
+                            posMetadata.position.groupPos, id)) {
+                        posMetadata.recycle();
+                        return true;
+                    }
+                }
+
+                /* Expand it */
+                mConnector.expandGroup(posMetadata);
+
+                playSoundEffect(SoundEffectConstants.CLICK);
+                
+                if (mOnGroupExpandListener != null) {
+                    mOnGroupExpandListener.onGroupExpand(posMetadata.position.groupPos);
+                }
+            }
+            
+            returnValue = true;
+        } else {
+            /* It's a child, so pass on event */
+            if (mOnChildClickListener != null) {
+                playSoundEffect(SoundEffectConstants.CLICK);
+                return mOnChildClickListener.onChildClick(this, v, posMetadata.position.groupPos,
+                        posMetadata.position.childPos, id);
+            }
+            
+            returnValue = false;
+        }
+        
+        posMetadata.recycle();
+        
+        return returnValue;
+    }
+
+    /**
+     * Expand a group in the grouped list view
+     *
+     * @param groupPos the group to be expanded
+     * @return True if the group was expanded, false otherwise (if the group
+     *         was already expanded, this will return false)
+     */
+    public boolean expandGroup(int groupPos) {
+        boolean retValue = mConnector.expandGroup(groupPos);
+
+        if (mOnGroupExpandListener != null) {
+            mOnGroupExpandListener.onGroupExpand(groupPos);
+        }
+        
+        return retValue;
+    }
+    
+    /**
+     * Collapse a group in the grouped list view
+     * 
+     * @param groupPos position of the group to collapse
+     * @return True if the group was collapsed, false otherwise (if the group
+     *         was already collapsed, this will return false)
+     */
+    public boolean collapseGroup(int groupPos) {
+        boolean retValue = mConnector.collapseGroup(groupPos);
+        
+        if (mOnGroupCollapseListener != null) {
+            mOnGroupCollapseListener.onGroupCollapse(groupPos);
+        }
+
+        return retValue;
+    }
+
+    /** Used for being notified when a group is collapsed */
+    public interface OnGroupCollapseListener {
+        /**
+         * Callback method to be invoked when a group in this expandable list has
+         * been collapsed.
+         * 
+         * @param groupPosition The group position that was collapsed
+         */
+        void onGroupCollapse(int groupPosition);
+    }
+    
+    private OnGroupCollapseListener mOnGroupCollapseListener;
+    
+    public void setOnGroupCollapseListener(
+            OnGroupCollapseListener onGroupCollapseListener) {
+        mOnGroupCollapseListener = onGroupCollapseListener;
+    }
+
+    /** Used for being notified when a group is expanded */
+    public interface OnGroupExpandListener {
+        /**
+         * Callback method to be invoked when a group in this expandable list has
+         * been expanded.
+         * 
+         * @param groupPosition The group position that was expanded
+         */
+        void onGroupExpand(int groupPosition);
+    }
+    
+    private OnGroupExpandListener mOnGroupExpandListener;
+    
+    public void setOnGroupExpandListener(
+            OnGroupExpandListener onGroupExpandListener) {
+        mOnGroupExpandListener = onGroupExpandListener;
+    }
+
+    /**
+     * Interface definition for a callback to be invoked when a group in this
+     * expandable list has been clicked.
+     */
+    public interface OnGroupClickListener {
+        /**
+         * Callback method to be invoked when a group in this expandable list has
+         * been clicked.
+         * 
+         * @param parent The ExpandableListConnector where the click happened
+         * @param v The view within the expandable list/ListView that was clicked
+         * @param groupPosition The group position that was clicked
+         * @param id The row id of the group that was clicked
+         * @return True if the click was handled
+         */
+        boolean onGroupClick(ExpandableListView parent, View v, int groupPosition,
+                long id);
+    }
+    
+    private OnGroupClickListener mOnGroupClickListener;
+
+    public void setOnGroupClickListener(OnGroupClickListener onGroupClickListener) {
+        mOnGroupClickListener = onGroupClickListener;
+    }
+    
+    /**
+     * Interface definition for a callback to be invoked when a child in this
+     * expandable list has been clicked.
+     */
+    public interface OnChildClickListener {
+        /**
+         * Callback method to be invoked when a child in this expandable list has
+         * been clicked.
+         * 
+         * @param parent The ExpandableListView where the click happened
+         * @param v The view within the expandable list/ListView that was clicked
+         * @param groupPosition The group position that contains the child that
+         *        was clicked
+         * @param childPosition The child position within the group
+         * @param id The row id of the child that was clicked
+         * @return True if the click was handled
+         */
+        boolean onChildClick(ExpandableListView parent, View v, int groupPosition,
+                int childPosition, long id);
+    }
+    
+    private OnChildClickListener mOnChildClickListener;
+
+    public void setOnChildClickListener(OnChildClickListener onChildClickListener) {
+        mOnChildClickListener = onChildClickListener;
+    }
+    
+    /**
+     * Converts a flat list position (the raw position of an item (child or
+     * group) in the list) to an group and/or child position (represented in a
+     * packed position). This is useful in situations where the caller needs to
+     * use the underlying {@link ListView}'s methods. Use
+     * {@link ExpandableListView#getPackedPositionType} ,
+     * {@link ExpandableListView#getPackedPositionChild},
+     * {@link ExpandableListView#getPackedPositionGroup} to unpack.
+     * 
+     * @param flatListPosition The flat list position to be converted.
+     * @return The group and/or child position for the given flat list position
+     *         in packed position representation.
+     */
+    public long getExpandableListPosition(int flatListPosition) {
+        PositionMetadata pm = mConnector.getUnflattenedPos(flatListPosition);
+        long packedPos = pm.position.getPackedPosition();
+        pm.recycle();
+        return packedPos;
+    }
+    
+    /**
+     * Converts a group and/or child position to a flat list position. This is
+     * useful in situations where the caller needs to use the underlying
+     * {@link ListView}'s methods.
+     * 
+     * @param packedPosition The group and/or child positions to be converted in
+     *            packed position representation. Use
+     *            {@link #getPackedPositionForChild(int, int)} or
+     *            {@link #getPackedPositionForGroup(int)}.
+     * @return The flat list position for the given child or group.
+     */
+    public int getFlatListPosition(long packedPosition) {
+        PositionMetadata pm = mConnector.getFlattenedPos(ExpandableListPosition
+                .obtainPosition(packedPosition));
+        int retValue = pm.position.flatListPos;
+        pm.recycle();
+        return retValue;
+    }
+
+    /**
+     * Gets the position of the currently selected group or child (along with
+     * its type). Can return {@link #PACKED_POSITION_VALUE_NULL} if no selection.
+     * 
+     * @return A packed position containing the currently selected group or
+     *         child's position and type. #PACKED_POSITION_VALUE_NULL if no selection.
+     */
+    public long getSelectedPosition() {
+        final int selectedPos = getSelectedItemPosition();
+        if (selectedPos == -1) return PACKED_POSITION_VALUE_NULL;
+        
+        return getExpandableListPosition(selectedPos);
+    }
+    
+    /**
+     * Gets the ID of the currently selected group or child. Can return -1 if no
+     * selection.
+     * 
+     * @return The ID of the currently selected group or child. -1 if no
+     *         selection.
+     */
+    public long getSelectedId() {
+        long packedPos = getSelectedPosition();
+        if (packedPos == PACKED_POSITION_VALUE_NULL) return -1;
+
+        int groupPos = getPackedPositionGroup(packedPos);
+        
+        if (getPackedPositionType(packedPos) == PACKED_POSITION_TYPE_GROUP) {
+            // It's a group
+            return mAdapter.getGroupId(groupPos);
+        } else {
+            // It's a child
+            return mAdapter.getChildId(groupPos, getPackedPositionChild(packedPos));
+        }
+    }
+    
+    /**
+     * Sets the selection to the specified group.
+     * @param groupPosition The position of the group that should be selected.
+     */
+    public void setSelectedGroup(int groupPosition) {
+        ExpandableListPosition elGroupPos = ExpandableListPosition
+                .obtainGroupPosition(groupPosition);
+        PositionMetadata pm = mConnector.getFlattenedPos(elGroupPos);
+        elGroupPos.recycle();
+        super.setSelection(pm.position.flatListPos);
+        pm.recycle();
+    }
+    
+    /**
+     * Sets the selection to the specified child. If the child is in a collapsed
+     * group, the group will only be expanded and child subsequently selected if
+     * shouldExpandGroup is set to true, otherwise the method will return false.
+     * 
+     * @param groupPosition The position of the group that contains the child.
+     * @param childPosition The position of the child within the group.
+     * @param shouldExpandGroup Whether the child's group should be expanded if
+     *            it is collapsed.
+     * @return Whether the selection was successfully set on the child.
+     */
+    public boolean setSelectedChild(int groupPosition, int childPosition, boolean shouldExpandGroup) {
+        ExpandableListPosition elChildPos = ExpandableListPosition.obtainChildPosition(
+                groupPosition, childPosition); 
+        PositionMetadata flatChildPos = mConnector.getFlattenedPos(elChildPos);
+        
+        if (flatChildPos == null) {
+            // The child's group isn't expanded
+            
+            // Shouldn't expand the group, so return false for we didn't set the selection
+            if (!shouldExpandGroup) return false; 
+
+            expandGroup(groupPosition);
+            
+            flatChildPos = mConnector.getFlattenedPos(elChildPos);
+            
+            // Sanity check
+            if (flatChildPos == null) {
+                throw new IllegalStateException("Could not find child");
+            }
+        }
+        
+        super.setSelection(flatChildPos.position.flatListPos);
+        
+        elChildPos.recycle();
+        flatChildPos.recycle();
+        
+        return true;
+    }
+
+    /**
+     * Whether the given group is currently expanded.
+     * 
+     * @param groupPosition The group to check.
+     * @return Whether the group is currently expanded.
+     */
+    public boolean isGroupExpanded(int groupPosition) {
+        return mConnector.isGroupExpanded(groupPosition);
+    }
+    
+    /**
+     * Gets the type of a packed position. See
+     * {@link #getPackedPositionForChild(int, int)}.
+     * 
+     * @param packedPosition The packed position for which to return the type.
+     * @return The type of the position contained within the packed position,
+     *         either {@link #PACKED_POSITION_TYPE_CHILD}, {@link #PACKED_POSITION_TYPE_GROUP}, or
+     *         {@link #PACKED_POSITION_TYPE_NULL}.
+     */
+    public static int getPackedPositionType(long packedPosition) {
+        if (packedPosition == PACKED_POSITION_VALUE_NULL) {
+            return PACKED_POSITION_TYPE_NULL;
+        }
+        
+        return (packedPosition & PACKED_POSITION_MASK_TYPE) == PACKED_POSITION_MASK_TYPE
+                ? PACKED_POSITION_TYPE_CHILD
+                : PACKED_POSITION_TYPE_GROUP;
+    }
+
+    /**
+     * Gets the group position from a packed position. See
+     * {@link #getPackedPositionForChild(int, int)}.
+     * 
+     * @param packedPosition The packed position from which the group position
+     *            will be returned.
+     * @return The group position portion of the packed position. If this does
+     *         not contain a group, returns -1.
+     */
+    public static int getPackedPositionGroup(long packedPosition) {
+        // Null
+        if (packedPosition == PACKED_POSITION_VALUE_NULL) return -1;
+        
+        return (int) ((packedPosition & PACKED_POSITION_MASK_GROUP) >> PACKED_POSITION_SHIFT_GROUP);
+    }
+
+    /**
+     * Gets the child position from a packed position that is of
+     * {@link #PACKED_POSITION_TYPE_CHILD} type (use {@link #getPackedPositionType(long)}).
+     * To get the group that this child belongs to, use
+     * {@link #getPackedPositionGroup(long)}. See
+     * {@link #getPackedPositionForChild(int, int)}.
+     * 
+     * @param packedPosition The packed position from which the child position
+     *            will be returned.
+     * @return The child position portion of the packed position. If this does
+     *         not contain a child, returns -1.
+     */
+    public static int getPackedPositionChild(long packedPosition) {
+        // Null
+        if (packedPosition == PACKED_POSITION_VALUE_NULL) return -1;
+        
+        // Group since a group type clears this bit
+        if ((packedPosition & PACKED_POSITION_MASK_TYPE) != PACKED_POSITION_MASK_TYPE) return -1;
+
+        return (int) (packedPosition & PACKED_POSITION_MASK_CHILD);
+    }
+
+    /**
+     * Returns the packed position representation of a child's position.
+     * <p>
+     * In general, a packed position should be used in
+     * situations where the position given to/returned from an
+     * {@link ExpandableListAdapter} or {@link ExpandableListView} method can
+     * either be a child or group. The two positions are packed into a single
+     * long which can be unpacked using
+     * {@link #getPackedPositionChild(long)},
+     * {@link #getPackedPositionGroup(long)}, and
+     * {@link #getPackedPositionType(long)}.
+     * 
+     * @param groupPosition The child's parent group's position.
+     * @param childPosition The child position within the group.
+     * @return The packed position representation of the child (and parent group).
+     */
+    public static long getPackedPositionForChild(int groupPosition, int childPosition) {
+        return (((long)PACKED_POSITION_TYPE_CHILD) << PACKED_POSITION_SHIFT_TYPE)
+                | ((((long)groupPosition) & PACKED_POSITION_INT_MASK_GROUP)
+                        << PACKED_POSITION_SHIFT_GROUP)
+                | (childPosition & PACKED_POSITION_INT_MASK_CHILD);  
+    }
+
+    /**
+     * Returns the packed position representation of a group's position. See
+     * {@link #getPackedPositionForChild(int, int)}.
+     * 
+     * @param groupPosition The child's parent group's position.
+     * @return The packed position representation of the group.
+     */
+    public static long getPackedPositionForGroup(int groupPosition) {
+        // No need to OR a type in because PACKED_POSITION_GROUP == 0
+        return ((((long)groupPosition) & PACKED_POSITION_INT_MASK_GROUP)
+                        << PACKED_POSITION_SHIFT_GROUP); 
+    }
+
+    @Override
+    ContextMenuInfo createContextMenuInfo(View view, int flatListPosition, long id) {
+        PositionMetadata pm = mConnector.getUnflattenedPos(flatListPosition);
+        ExpandableListPosition pos = pm.position;
+        pm.recycle();
+        
+        id = getChildOrGroupId(pos);
+        long packedPosition = pos.getPackedPosition();
+        pos.recycle();
+        
+        return new ExpandableListContextMenuInfo(view, packedPosition, id);
+    }
+
+    /**
+     * Gets the ID of the group or child at the given <code>position</code>.
+     * This is useful since there is no ListAdapter ID -> ExpandableListAdapter
+     * ID conversion mechanism (in some cases, it isn't possible).
+     * 
+     * @param position The position of the child or group whose ID should be
+     *            returned.
+     */
+    private long getChildOrGroupId(ExpandableListPosition position) {
+        if (position.type == ExpandableListPosition.CHILD) {
+            return mAdapter.getChildId(position.groupPos, position.childPos);
+        } else {
+            return mAdapter.getGroupId(position.groupPos);
+        }
+    }
+    
+    /**
+     * Sets the indicator to be drawn next to a child.
+     * 
+     * @param childIndicator The drawable to be used as an indicator. If the
+     *            child is the last child for a group, the state
+     *            {@link android.R.attr#state_last} will be set.
+     */
+    public void setChildIndicator(Drawable childIndicator) {
+        mChildIndicator = childIndicator;
+    }
+    
+    /**
+     * Sets the drawing bounds for the child indicator. For either, you can
+     * specify {@link #CHILD_INDICATOR_INHERIT} to use inherit from the general
+     * indicator's bounds.
+     *
+     * @see #setIndicatorBounds(int, int)
+     * @param left The left position (relative to the left bounds of this View)
+     *            to start drawing the indicator.
+     * @param right The right position (relative to the left bounds of this
+     *            View) to end the drawing of the indicator.
+     */
+    public void setChildIndicatorBounds(int left, int right) {
+        mChildIndicatorLeft = left;
+        mChildIndicatorRight = right;
+    }
+    
+    /**
+     * Sets the indicator to be drawn next to a group.
+     * 
+     * @param groupIndicator The drawable to be used as an indicator. If the
+     *            group is empty, the state {@link android.R.attr#state_empty} will be
+     *            set. If the group is expanded, the state
+     *            {@link android.R.attr#state_expanded} will be set.
+     */
+    public void setGroupIndicator(Drawable groupIndicator) {
+        mGroupIndicator = groupIndicator;
+    }
+    
+    /**
+     * Sets the drawing bounds for the indicators (at minimum, the group indicator
+     * is affected by this; the child indicator is affected by this if the
+     * child indicator bounds are set to inherit).
+     * 
+     * @see #setChildIndicatorBounds(int, int) 
+     * @param left The left position (relative to the left bounds of this View)
+     *            to start drawing the indicator.
+     * @param right The right position (relative to the left bounds of this
+     *            View) to end the drawing of the indicator.
+     */
+    public void setIndicatorBounds(int left, int right) {
+        mIndicatorLeft = left;
+        mIndicatorRight = right;
+    }
+    
+    /**
+     * Extra menu information specific to an {@link ExpandableListView} provided
+     * to the
+     * {@link android.view.View.OnCreateContextMenuListener#onCreateContextMenu(ContextMenu, View, ContextMenuInfo) }
+     * callback when a context menu is brought up for this AdapterView.
+     */
+    public static class ExpandableListContextMenuInfo implements ContextMenu.ContextMenuInfo {
+        
+        public ExpandableListContextMenuInfo(View targetView, long packedPosition, long id) {
+            this.targetView = targetView;
+            this.packedPosition = packedPosition;
+            this.id = id;
+        }
+        
+        /**
+         * The view for which the context menu is being displayed. This
+         * will be one of the children Views of this {@link ExpandableListView}.
+         */
+        public View targetView;
+
+        /**
+         * The packed position in the list represented by the adapter for which
+         * the context menu is being displayed. Use the methods
+         * {@link ExpandableListView#getPackedPositionType},
+         * {@link ExpandableListView#getPackedPositionChild}, and
+         * {@link ExpandableListView#getPackedPositionGroup} to unpack this.
+         */
+        public long packedPosition;
+
+        /**
+         * The ID of the item (group or child) for which the context menu is
+         * being displayed.
+         */
+        public long id;
+    }
+    
+    static class SavedState extends BaseSavedState {
+        ArrayList<ExpandableListConnector.GroupMetadata> expandedGroupMetadataList;
+        
+        /**
+         * Constructor called from {@link ExpandableListView#onSaveInstanceState()}
+         */
+        SavedState(
+                Parcelable superState,
+                ArrayList<ExpandableListConnector.GroupMetadata> expandedGroupMetadataList) {
+            super(superState);
+            this.expandedGroupMetadataList = expandedGroupMetadataList;
+        }
+
+        /**
+         * Constructor called from {@link #CREATOR}
+         */
+        private SavedState(Parcel in) {
+            super(in);
+            expandedGroupMetadataList = new ArrayList<ExpandableListConnector.GroupMetadata>();
+            in.readList(expandedGroupMetadataList, ExpandableListConnector.class.getClassLoader());
+        }
+
+        @Override
+        public void writeToParcel(Parcel out, int flags) {
+            super.writeToParcel(out, flags);
+            out.writeList(expandedGroupMetadataList);
+        }
+
+        public static final Parcelable.Creator<SavedState> CREATOR
+                = new Parcelable.Creator<SavedState>() {
+            public SavedState createFromParcel(Parcel in) {
+                return new SavedState(in);
+            }
+
+            public SavedState[] newArray(int size) {
+                return new SavedState[size];
+            }
+        };
+    }
+
+    @Override
+    public Parcelable onSaveInstanceState() {
+        Parcelable superState = super.onSaveInstanceState();
+        return new SavedState(superState,
+                mConnector != null ? mConnector.getExpandedGroupMetadataList() : null);
+    }
+
+    @Override
+    public void onRestoreInstanceState(Parcelable state) {
+        SavedState ss = (SavedState) state;
+        super.onRestoreInstanceState(ss.getSuperState());
+        
+        if (mConnector != null && ss.expandedGroupMetadataList != null) {
+            mConnector.setExpandedGroupMetadataList(ss.expandedGroupMetadataList);
+        }
+    }
+
+}
diff --git a/core/java/android/widget/FastScroller.java b/core/java/android/widget/FastScroller.java
new file mode 100644
index 0000000..57e21e4
--- /dev/null
+++ b/core/java/android/widget/FastScroller.java
@@ -0,0 +1,482 @@
+/*
+ * 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 android.widget;
+
+import android.content.Context;
+import android.content.res.ColorStateList;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.RectF;
+import android.graphics.drawable.Drawable;
+import android.os.Handler;
+import android.os.SystemClock;
+import android.util.TypedValue;
+import android.view.MotionEvent;
+
+/**
+ * Helper class for AbsListView to draw and control the Fast Scroll thumb
+ */
+class FastScroller {
+   
+    // Minimum number of pages to justify showing a fast scroll thumb
+    private static int MIN_PAGES = 4;
+    // Scroll thumb not showing
+    private static final int STATE_NONE = 0;
+    // Not implemented yet - fade-in transition
+    private static final int STATE_ENTER = 1;
+    // Scroll thumb visible and moving along with the scrollbar
+    private static final int STATE_VISIBLE = 2;
+    // Scroll thumb being dragged by user
+    private static final int STATE_DRAGGING = 3;
+    // Scroll thumb fading out due to inactivity timeout
+    private static final int STATE_EXIT = 4;
+    
+    private Drawable mThumbDrawable;
+    private Drawable mOverlayDrawable;
+
+    private int mThumbH;
+    private int mThumbW;
+    private int mThumbY;
+
+    private RectF mOverlayPos;
+    private int mOverlaySize = 104;
+
+    private AbsListView mList;
+    private boolean mScrollCompleted;
+    private int mVisibleItem;
+    private Paint mPaint;
+    private int mListOffset;
+    
+    private Object [] mSections;
+    private String mSectionText;
+    private boolean mDrawOverlay;
+    private ScrollFade mScrollFade;
+    
+    private int mState;
+    
+    private Handler mHandler = new Handler();
+    
+    private BaseAdapter mListAdapter;
+    private SectionIndexer mSectionIndexer;
+
+    private boolean mChangedBounds;
+    
+    public FastScroller(Context context, AbsListView listView) {
+        mList = listView;
+        init(context);
+    }
+
+    public void setState(int state) {
+        switch (state) {
+            case STATE_NONE:
+                mHandler.removeCallbacks(mScrollFade);
+                mList.invalidate();
+                break;
+            case STATE_VISIBLE:
+                if (mState != STATE_VISIBLE) { // Optimization
+                    resetThumbPos();
+                }
+                // Fall through
+            case STATE_DRAGGING:
+                mHandler.removeCallbacks(mScrollFade);
+                break;
+            case STATE_EXIT:
+                int viewWidth = mList.getWidth();
+                mList.invalidate(viewWidth - mThumbW, mThumbY, viewWidth, mThumbY + mThumbH);
+                break;
+        }
+        mState = state;
+    }
+    
+    public int getState() {
+        return mState;
+    }
+    
+    private void resetThumbPos() {
+        final int viewWidth = mList.getWidth();
+        // Bounds are always top right. Y coordinate get's translated during draw
+        mThumbDrawable.setBounds(viewWidth - mThumbW, 0, viewWidth, mThumbH);
+        mThumbDrawable.setAlpha(ScrollFade.ALPHA_MAX);
+    }
+    
+    private void useThumbDrawable(Drawable drawable) {
+        mThumbDrawable = drawable;
+        mThumbW = 64; //mCurrentThumb.getIntrinsicWidth();
+        mThumbH = 52; //mCurrentThumb.getIntrinsicHeight();
+        mChangedBounds = true;
+    }
+
+    private void init(Context context) {
+        // Get both the scrollbar states drawables
+        final Resources res = context.getResources();
+        useThumbDrawable(res.getDrawable(
+                com.android.internal.R.drawable.scrollbar_handle_accelerated_anim2));
+        
+        mOverlayDrawable = res.getDrawable(
+                com.android.internal.R.drawable.menu_submenu_background);
+        
+        mScrollCompleted = true;
+
+        getSections();
+        
+        mOverlayPos = new RectF();
+        mScrollFade = new ScrollFade();
+        mPaint = new Paint();
+        mPaint.setAntiAlias(true);
+        mPaint.setTextAlign(Paint.Align.CENTER);
+        mPaint.setTextSize(mOverlaySize / 2);
+        TypedArray ta = context.getTheme().obtainStyledAttributes(new int[] { 
+                android.R.attr.textColorPrimary });
+        ColorStateList textColor = ta.getColorStateList(ta.getIndex(0));
+        int textColorNormal = textColor.getDefaultColor();
+        mPaint.setColor(textColorNormal);
+        mPaint.setStyle(Paint.Style.FILL_AND_STROKE);
+        
+        mState = STATE_NONE;
+    }
+    
+    void stop() {
+        setState(STATE_NONE);
+    }
+    
+    boolean isVisible() {
+        return !(mState == STATE_NONE);
+    }
+    
+    public void draw(Canvas canvas) {
+        
+        if (mState == STATE_NONE) {
+            // No need to draw anything
+            return;
+        }
+
+        final int y = mThumbY;
+        final int viewWidth = mList.getWidth();
+        final FastScroller.ScrollFade scrollFade = mScrollFade;
+
+        int alpha = -1;
+        if (mState == STATE_EXIT) {
+            alpha = scrollFade.getAlpha();
+            if (alpha < ScrollFade.ALPHA_MAX / 2) {
+                mThumbDrawable.setAlpha(alpha * 2);
+            }
+            int left = viewWidth - (mThumbW * alpha) / ScrollFade.ALPHA_MAX;
+            mThumbDrawable.setBounds(left, 0, viewWidth, mThumbH);
+            mChangedBounds = true;
+        }
+
+        canvas.translate(0, y);
+        mThumbDrawable.draw(canvas);
+        canvas.translate(0, -y);
+
+        // If user is dragging the scroll bar, draw the alphabet overlay
+        if (mState == STATE_DRAGGING && mDrawOverlay) {
+            mOverlayDrawable.draw(canvas);
+            final Paint paint = mPaint;
+            float descent = paint.descent();
+            final RectF rectF = mOverlayPos;
+            canvas.drawText(mSectionText, (int) (rectF.left + rectF.right) / 2,
+                    (int) (rectF.bottom + rectF.top) / 2 + mOverlaySize / 4 - descent, paint);
+        } else if (mState == STATE_EXIT) {
+            if (alpha == 0) { // Done with exit
+                setState(STATE_NONE);
+            } else {
+                mList.invalidate(viewWidth - mThumbW, y, viewWidth, y + mThumbH);            
+            }
+        }
+    }
+
+    void onSizeChanged(int w, int h, int oldw, int oldh) {
+        if (mThumbDrawable != null) {
+            mThumbDrawable.setBounds(w - mThumbW, 0, w, mThumbH);
+        }
+        final RectF pos = mOverlayPos;
+        pos.left = (w - mOverlaySize) / 2;
+        pos.right = pos.left + mOverlaySize;
+        pos.top = h / 10; // 10% from top
+        pos.bottom = pos.top + mOverlaySize;
+        if (mOverlayDrawable != null) {
+            mOverlayDrawable.setBounds((int) pos.left, (int) pos.top,
+                (int) pos.right, (int) pos.bottom);
+        }
+    }
+    
+    void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, 
+            int totalItemCount) {
+        // Are there enough pages to require fast scroll?
+        if (visibleItemCount > 0 && totalItemCount / visibleItemCount < MIN_PAGES) {
+            if (mState != STATE_NONE) {
+                setState(STATE_NONE);
+            }
+            return;
+        }
+        if (totalItemCount - visibleItemCount > 0 && mState != STATE_DRAGGING ) {
+            mThumbY = ((mList.getHeight() - mThumbH) * firstVisibleItem) 
+                    / (totalItemCount - visibleItemCount);
+            if (mChangedBounds) {
+                resetThumbPos();
+                mChangedBounds = false;
+            }
+        }
+        mScrollCompleted = true;
+        if (firstVisibleItem == mVisibleItem) {
+            return;
+        }
+        mVisibleItem = firstVisibleItem;
+        if (mState != STATE_DRAGGING) {
+            setState(STATE_VISIBLE);
+            mHandler.postDelayed(mScrollFade, 1500);
+        }
+    }
+
+    private void getSections() {
+        Adapter adapter = mList.getAdapter();
+        mSectionIndexer = null;
+        if (adapter instanceof HeaderViewListAdapter) {
+            mListOffset = ((HeaderViewListAdapter)adapter).getHeadersCount();
+            adapter = ((HeaderViewListAdapter)adapter).getWrappedAdapter();
+        }
+        if (adapter instanceof ExpandableListConnector) {
+            ExpandableListAdapter expAdapter = ((ExpandableListConnector)adapter).getAdapter();
+            if (expAdapter instanceof SectionIndexer) {
+                mSectionIndexer = (SectionIndexer) expAdapter;
+                mListAdapter = (BaseAdapter) adapter;
+                mSections = mSectionIndexer.getSections();
+            }
+        } else {
+            if (adapter instanceof SectionIndexer) {
+                mListAdapter = (BaseAdapter) adapter;
+                mSectionIndexer = (SectionIndexer) adapter;
+                mSections = mSectionIndexer.getSections();
+                
+            } else {
+                mListAdapter = (BaseAdapter) adapter;
+                mSections = new String[] { " " };
+            }
+        }
+    }
+
+    private void scrollTo(float position) {
+        int count = mList.getCount();
+        mScrollCompleted = false;
+        float fThreshold = (1.0f / count) / 8;
+        final Object[] sections = mSections;
+        int sectionIndex;
+        if (sections != null && sections.length > 1) {
+            final int nSections = sections.length;
+            int section = (int) (position * nSections);
+            if (section >= nSections) {
+                section = nSections - 1;
+            }
+            int exactSection = section;
+            sectionIndex = section;
+            int index = mSectionIndexer.getPositionForSection(section);
+            // Given the expected section and index, the following code will
+            // try to account for missing sections (no names starting with..)
+            // It will compute the scroll space of surrounding empty sections
+            // and interpolate the currently visible letter's range across the
+            // available space, so that there is always some list movement while
+            // the user moves the thumb.
+            int nextIndex = count;
+            int prevIndex = index;
+            int prevSection = section;
+            int nextSection = section + 1;
+            // Assume the next section is unique
+            if (section < nSections - 1) {
+                nextIndex = mSectionIndexer.getPositionForSection(section + 1);
+            }
+            
+            // Find the previous index if we're slicing the previous section
+            if (nextIndex == index) {
+                // Non-existent letter
+                while (section > 0) {
+                    section--;
+                     prevIndex = mSectionIndexer.getPositionForSection(section);
+                     if (prevIndex != index) {
+                         prevSection = section;
+                         sectionIndex = section;
+                         break;
+                     }
+                }
+            }
+            // Find the next index, in case the assumed next index is not
+            // unique. For instance, if there is no P, then request for P's 
+            // position actually returns Q's. So we need to look ahead to make
+            // sure that there is really a Q at Q's position. If not, move 
+            // further down...
+            int nextNextSection = nextSection + 1;
+            while (nextNextSection < nSections &&
+                    mSectionIndexer.getPositionForSection(nextNextSection) == nextIndex) {
+                nextNextSection++;
+                nextSection++;
+            }
+            // Compute the beginning and ending scroll range percentage of the
+            // currently visible letter. This could be equal to or greater than
+            // (1 / nSections). 
+            float fPrev = (float) prevSection / nSections;
+            float fNext = (float) nextSection / nSections;
+            if (prevSection == exactSection && position - fPrev < fThreshold) {
+                index = prevIndex;
+            } else {
+                index = prevIndex + (int) ((nextIndex - prevIndex) * (position - fPrev) 
+                    / (fNext - fPrev));
+            }
+            // Don't overflow
+            if (index > count - 1) index = count - 1;
+            
+            if (mList instanceof ExpandableListView) {
+                ExpandableListView expList = (ExpandableListView) mList;
+                expList.setSelectionFromTop(expList.getFlatListPosition(
+                        ExpandableListView.getPackedPositionForGroup(index + mListOffset)), 0);
+            } else if (mList instanceof ListView) {
+                ((ListView)mList).setSelectionFromTop(index + mListOffset, 0);
+            } else {
+                mList.setSelection(index + mListOffset);
+            }
+        } else {
+            int index = (int) (position * count);
+            if (mList instanceof ExpandableListView) {
+                ExpandableListView expList = (ExpandableListView) mList;
+                expList.setSelectionFromTop(expList.getFlatListPosition(
+                        ExpandableListView.getPackedPositionForGroup(index + mListOffset)), 0);
+            } else if (mList instanceof ListView) {
+                ((ListView)mList).setSelectionFromTop(index + mListOffset, 0);
+            } else {
+                mList.setSelection(index + mListOffset);
+            }
+            sectionIndex = -1;
+        }
+
+        if (sectionIndex >= 0) {
+            String text = mSectionText = sections[sectionIndex].toString();
+            mDrawOverlay = (text.length() != 1 || text.charAt(0) != ' ') &&
+                    sectionIndex < sections.length;
+        } else {
+            mDrawOverlay = false;
+        }
+    }
+
+    private void cancelFling() {
+        // Cancel the list fling
+        MotionEvent cancelFling = MotionEvent.obtain(0, 0, MotionEvent.ACTION_CANCEL, 0, 0, 0);
+        mList.onTouchEvent(cancelFling);
+        cancelFling.recycle();
+    }
+    
+    boolean onInterceptTouchEvent(MotionEvent ev) {
+        if (mState > STATE_NONE && ev.getAction() == MotionEvent.ACTION_DOWN) {
+            if (ev.getX() > mList.getWidth() - mThumbW && ev.getY() >= mThumbY &&
+                    ev.getY() <= mThumbY + mThumbH) {
+                setState(STATE_DRAGGING);
+                return true;
+            }
+        }
+        return false;
+    }
+
+    boolean onTouchEvent(MotionEvent me) {
+        if (mState == STATE_NONE) {
+            return false;
+        }
+        if (me.getAction() == MotionEvent.ACTION_DOWN) {
+            if (me.getX() > mList.getWidth() - mThumbW
+                    && me.getY() >= mThumbY 
+                    && me.getY() <= mThumbY + mThumbH) {
+                
+                setState(STATE_DRAGGING);
+                if (mListAdapter == null && mList != null) {
+                    getSections();
+                }
+
+                cancelFling();
+                return true;
+            }
+        } else if (me.getAction() == MotionEvent.ACTION_UP) {
+            if (mState == STATE_DRAGGING) {
+                setState(STATE_VISIBLE);
+                final Handler handler = mHandler;
+                handler.removeCallbacks(mScrollFade);
+                handler.postDelayed(mScrollFade, 1000);
+                return true;
+            }
+        } else if (me.getAction() == MotionEvent.ACTION_MOVE) {
+            if (mState == STATE_DRAGGING) {
+                final int viewHeight = mList.getHeight();
+                // Jitter
+                int newThumbY = (int) me.getY() - mThumbH + 10;
+                if (newThumbY < 0) {
+                    newThumbY = 0;
+                } else if (newThumbY + mThumbH > viewHeight) {
+                    newThumbY = viewHeight - mThumbH;
+                }
+                if (Math.abs(mThumbY - newThumbY) < 2) {
+                    return true;
+                }
+                mThumbY = newThumbY;
+                // If the previous scrollTo is still pending
+                if (mScrollCompleted) {
+                    scrollTo((float) mThumbY / (viewHeight - mThumbH));
+                }
+                return true;
+            }
+        }
+        return false;
+    }
+    
+    public class ScrollFade implements Runnable {
+        
+        long mStartTime;
+        long mFadeDuration;
+        static final int ALPHA_MAX = 208;
+        static final long FADE_DURATION = 200;
+        
+        void startFade() {
+            mFadeDuration = FADE_DURATION;
+            mStartTime = SystemClock.uptimeMillis();
+            setState(STATE_EXIT);
+        }
+        
+        int getAlpha() {
+            if (getState() != STATE_EXIT) {
+                return ALPHA_MAX;
+            }
+            int alpha;
+            long now = SystemClock.uptimeMillis();
+            if (now > mStartTime + mFadeDuration) {
+                alpha = 0;
+            } else {
+                alpha = (int) (ALPHA_MAX - ((now - mStartTime) * ALPHA_MAX) / mFadeDuration); 
+            }
+            return alpha;
+        }
+        
+        public void run() {
+            if (getState() != STATE_EXIT) {
+                startFade();
+                return;
+            }
+            
+            if (getAlpha() > 0) {
+                mList.invalidate();
+            } else {
+                setState(STATE_NONE);
+            }
+        }
+    }
+}
diff --git a/core/java/android/widget/Filter.java b/core/java/android/widget/Filter.java
new file mode 100644
index 0000000..1d0fd5e
--- /dev/null
+++ b/core/java/android/widget/Filter.java
@@ -0,0 +1,290 @@
+/*
+ * Copyright (C) 2007 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 android.widget;
+
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.os.Message;
+import android.util.Log;
+
+/**
+ * <p>A filter constrains data with a filtering pattern.</p>
+ *
+ * <p>Filters are usually created by {@link android.widget.Filterable}
+ * classes.</p>
+ *
+ * <p>Filtering operations performed by calling {@link #filter(CharSequence)} or
+ * {@link #filter(CharSequence, android.widget.Filter.FilterListener)} are
+ * performed asynchronously. When these methods are called, a filtering request
+ * is posted in a request queue and processed later. Any call to one of these
+ * methods will cancel any previous non-executed filtering request.</p>
+ *
+ * @see android.widget.Filterable
+ */
+public abstract class Filter {
+    private static final String LOG_TAG = "Filter";
+    
+    private static final String THREAD_NAME = "Filter";
+    private static final int FILTER_TOKEN = 0xD0D0F00D;
+    private static final int FINISH_TOKEN = 0xDEADBEEF;
+    
+    private Handler mThreadHandler;
+    private Handler mResultHandler;
+
+    /**
+     * <p>Creates a new asynchronous filter.</p>
+     */
+    public Filter() {
+        mResultHandler = new ResultsHandler();
+    }
+
+    /**
+     * <p>Starts an asynchronous filtering operation. Calling this method
+     * cancels all previous non-executed filtering requests and posts a new
+     * filtering request that will be executed later.</p>
+     *
+     * @param constraint the constraint used to filter the data
+     *
+     * @see #filter(CharSequence, android.widget.Filter.FilterListener)
+     */
+    public final void filter(CharSequence constraint) {
+        filter(constraint, null);
+    }
+
+    /**
+     * <p>Starts an asynchronous filtering operation. Calling this method
+     * cancels all previous non-executed filtering requests and posts a new
+     * filtering request that will be executed later.</p>
+     *
+     * <p>Upon completion, the listener is notified.</p>
+     *
+     * @param constraint the constraint used to filter the data
+     * @param listener a listener notified upon completion of the operation
+     *
+     * @see #filter(CharSequence)
+     * @see #performFiltering(CharSequence)
+     * @see #publishResults(CharSequence, android.widget.Filter.FilterResults)
+     */
+    public final void filter(CharSequence constraint, FilterListener listener) {
+        synchronized (this) {
+
+            if (mThreadHandler == null) {
+                HandlerThread thread = new HandlerThread(THREAD_NAME);
+                thread.start();
+                mThreadHandler = new RequestHandler(thread.getLooper());
+            }
+            
+            Message message = mThreadHandler.obtainMessage(FILTER_TOKEN);
+    
+            RequestArguments args = new RequestArguments();
+            // make sure we use an immutable copy of the constraint, so that
+            // it doesn't change while the filter operation is in progress
+            args.constraint = constraint != null ? constraint.toString() : null;
+            args.listener = listener;
+            message.obj = args;
+    
+            mThreadHandler.removeMessages(FILTER_TOKEN);
+            mThreadHandler.removeMessages(FINISH_TOKEN);
+            mThreadHandler.sendMessage(message);
+        }
+    }
+
+    /**
+     * <p>Invoked in a worker thread to filter the data according to the
+     * constraint. Subclasses must implement this method to perform the
+     * filtering operation. Results computed by the filtering operation
+     * must be returned as a {@link android.widget.Filter.FilterResults} that
+     * will then be published in the UI thread through
+     * {@link #publishResults(CharSequence,
+     * android.widget.Filter.FilterResults)}.</p>
+     *
+     * <p><strong>Contract:</strong> When the constraint is null, the original
+     * data must be restored.</p>
+     *
+     * @param constraint the constraint used to filter the data
+     * @return the results of the filtering operation
+     *
+     * @see #filter(CharSequence, android.widget.Filter.FilterListener)
+     * @see #publishResults(CharSequence, android.widget.Filter.FilterResults)
+     * @see android.widget.Filter.FilterResults
+     */
+    protected abstract FilterResults performFiltering(CharSequence constraint);
+
+    /**
+     * <p>Invoked in the UI thread to publish the filtering results in the
+     * user interface. Subclasses must implement this method to display the
+     * results computed in {@link #performFiltering}.</p>
+     *
+     * @param constraint the constraint used to filter the data
+     * @param results the results of the filtering operation
+     *
+     * @see #filter(CharSequence, android.widget.Filter.FilterListener)
+     * @see #performFiltering(CharSequence)
+     * @see android.widget.Filter.FilterResults
+     */
+    protected abstract void publishResults(CharSequence constraint,
+            FilterResults results);
+
+    /**
+     * <p>Converts a value from the filtered set into a CharSequence. Subclasses
+     * should override this method to convert their results. The default
+     * implementation returns an empty String for null values or the default
+     * String representation of the value.</p>
+     *
+     * @param resultValue the value to convert to a CharSequence
+     * @return a CharSequence representing the value
+     */
+    public CharSequence convertResultToString(Object resultValue) {
+        return resultValue == null ? "" : resultValue.toString();
+    }
+
+    /**
+     * <p>Holds the results of a filtering operation. The results are the values
+     * computed by the filtering operation and the number of these values.</p>
+     */
+    protected static class FilterResults {
+        public FilterResults() {
+            // nothing to see here
+        }
+
+        /**
+         * <p>Contains all the values computed by the filtering operation.</p>
+         */
+        public Object values;
+
+        /**
+         * <p>Contains the number of values computed by the filtering
+         * operation.</p>
+         */
+        public int count;
+    }
+
+    /**
+     * <p>Listener used to receive a notification upon completion of a filtering
+     * operation.</p>
+     */
+    public static interface FilterListener {
+        /**
+         * <p>Notifies the end of a filtering operation.</p>
+         *
+         * @param count the number of values computed by the filter
+         */
+        public void onFilterComplete(int count);
+    }
+
+    /**
+     * <p>Worker thread handler. When a new filtering request is posted from
+     * {@link android.widget.Filter#filter(CharSequence, android.widget.Filter.FilterListener)},
+     * it is sent to this handler.</p>
+     */
+    private class RequestHandler extends Handler {
+        public RequestHandler(Looper looper) {
+            super(looper);
+        }
+        
+        /**
+         * <p>Handles filtering requests by calling
+         * {@link Filter#performFiltering} and then sending a message
+         * with the results to the results handler.</p>
+         *
+         * @param msg the filtering request
+         */
+        public void handleMessage(Message msg) {
+            int what = msg.what;
+            Message message;
+            switch (what) {
+                case FILTER_TOKEN:
+                    RequestArguments args = (RequestArguments) msg.obj;
+                    try {
+                        args.results = performFiltering(args.constraint);
+                    } catch (Exception e) {
+                        args.results = new FilterResults();
+                        Log.w(LOG_TAG, "An exception occured during performFiltering()!", e);
+                    } finally {
+                        message = mResultHandler.obtainMessage(what);
+                        message.obj = args;
+                        message.sendToTarget();
+                    }
+
+                    synchronized (this) {
+                        if (mThreadHandler != null) {
+                            Message finishMessage = mThreadHandler.obtainMessage(FINISH_TOKEN);
+                            mThreadHandler.sendMessageDelayed(finishMessage, 3000);
+                        }
+                    }
+                    break;
+                case FINISH_TOKEN:
+                    synchronized (this) {
+                        if (mThreadHandler != null) {
+                            mThreadHandler.getLooper().quit();
+                            mThreadHandler = null;
+                        }
+                    }
+                    break;
+            }
+        }
+    }
+
+    /**
+     * <p>Handles the results of a filtering operation. The results are
+     * handled in the UI thread.</p>
+     */
+    private class ResultsHandler extends Handler {
+        /**
+         * <p>Messages received from the request handler are processed in the
+         * UI thread. The processing involves calling
+         * {@link Filter#publishResults(CharSequence,
+         * android.widget.Filter.FilterResults)}
+         * to post the results back in the UI and then notifying the listener,
+         * if any.</p> 
+         *
+         * @param msg the filtering results
+         */
+        @Override
+        public void handleMessage(Message msg) {
+            RequestArguments args = (RequestArguments) msg.obj;
+
+            publishResults(args.constraint, args.results);
+            if (args.listener != null) {
+                int count = args.results != null ? args.results.count : -1;
+                args.listener.onFilterComplete(count);
+            }
+        }
+    }
+
+    /**
+     * <p>Holds the arguments of a filtering request as well as the results
+     * of the request.</p>
+     */
+    private static class RequestArguments {
+        /**
+         * <p>The constraint used to filter the data.</p>
+         */
+        CharSequence constraint;
+
+        /**
+         * <p>The listener to notify upon completion. Can be null.</p>
+         */
+        FilterListener listener;
+
+        /**
+         * <p>The results of the filtering operation.</p>
+         */
+        FilterResults results;
+    }
+}
diff --git a/core/java/android/widget/FilterQueryProvider.java b/core/java/android/widget/FilterQueryProvider.java
new file mode 100644
index 0000000..740d2f0
--- /dev/null
+++ b/core/java/android/widget/FilterQueryProvider.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2007 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 android.widget;
+
+import android.database.Cursor;
+
+/**
+ * This class can be used by external clients of CursorAdapter and
+ * CursorTreeAdapter to define how the content of the adapter should be
+ * filtered.
+ * 
+ * @see #runQuery(CharSequence)
+ */
+public interface FilterQueryProvider {
+    /**
+     * Runs a query with the specified constraint. This query is requested
+     * by the filter attached to this adapter.
+     *
+     * Contract: when constraint is null or empty, the original results,
+     * prior to any filtering, must be returned.
+     *
+     * @param constraint the constraint with which the query must
+     *        be filtered
+     *
+     * @return a Cursor representing the results of the new query
+     */
+    Cursor runQuery(CharSequence constraint);
+}
diff --git a/core/java/android/widget/Filterable.java b/core/java/android/widget/Filterable.java
new file mode 100644
index 0000000..f7c8d59
--- /dev/null
+++ b/core/java/android/widget/Filterable.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2007 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 android.widget;
+
+/**
+ * <p>Defines a filterable behavior. A filterable class can have its data
+ * constrained by a filter. Filterable classes are usually
+ * {@link android.widget.Adapter} implementations.</p>
+ *
+ * @see android.widget.Filter
+ */
+public interface Filterable {
+    /**
+     * <p>Returns a filter that can be used to constrain data with a filtering
+     * pattern.</p>
+     *
+     * <p>This method is usually implemented by {@link android.widget.Adapter}
+     * classes.</p>
+     *
+     * @return a filter used to constrain data
+     */
+    Filter getFilter();
+}
diff --git a/core/java/android/widget/FrameLayout.java b/core/java/android/widget/FrameLayout.java
new file mode 100644
index 0000000..8aafee2
--- /dev/null
+++ b/core/java/android/widget/FrameLayout.java
@@ -0,0 +1,450 @@
+/*
+ * 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 android.widget;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Canvas;
+import android.graphics.Rect;
+import android.graphics.Region;
+import android.graphics.drawable.Drawable;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.Gravity;
+import android.widget.RemoteViews.RemoteView;
+
+
+/**
+ * FrameLayout is designed to block out an area on the screen to display
+ * a single item. You can add multiple children to a FrameLayout, but all
+ * children are pegged to the top left of the screen.
+ * Children are drawn in a stack, with the most recently added child on top.
+ * The size of the frame layout is the size of its largest child (plus padding), visible
+ * or not (if the FrameLayout's parent permits). Views that are GONE are used for sizing
+ * only if {@link #setMeasureAllChildren(boolean) setConsiderGoneChildrenWhenMeasuring()}
+ * is set to true.
+ *
+ * @attr ref android.R.styleable#FrameLayout_foreground
+ * @attr ref android.R.styleable#FrameLayout_foregroundGravity
+ * @attr ref android.R.styleable#FrameLayout_measureAllChildren
+ */
+@RemoteView
+public class FrameLayout extends ViewGroup {
+    boolean mMeasureAllChildren = false;
+
+    private Drawable mForeground;
+    private int mForegroundPaddingLeft = 0;
+    private int mForegroundPaddingTop = 0;
+    private int mForegroundPaddingRight = 0;
+    private int mForegroundPaddingBottom = 0;
+
+    private final Rect mSelfBounds = new Rect();
+    private final Rect mOverlayBounds = new Rect();
+    private int mForegroundGravity = Gravity.FILL;
+
+    public FrameLayout(Context context) {
+        super(context);
+    }
+    
+    public FrameLayout(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public FrameLayout(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+
+        TypedArray a = context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.FrameLayout,
+                    defStyle, 0);
+
+        final Drawable d = a.getDrawable(com.android.internal.R.styleable.FrameLayout_foreground);
+        if (d != null) {
+            setForeground(d);
+        }
+        
+        if (a.getBoolean(com.android.internal.R.styleable.FrameLayout_measureAllChildren, false)) {
+            setMeasureAllChildren(true);
+        }
+
+        mForegroundGravity = a.getInt(com.android.internal.R.styleable.FrameLayout_foregroundGravity,
+                mForegroundGravity);
+
+        a.recycle();
+    }
+
+    /**
+     * Describes how the foreground is positioned. Defaults to FILL.
+     *
+     * @param foregroundGravity See {@link android.view.Gravity}
+     *
+     * @attr ref android.R.styleable#FrameLayout_foregroundGravity
+     */
+    @android.view.RemotableViewMethod
+    public void setForegroundGravity(int foregroundGravity) {
+        if (mForegroundGravity != foregroundGravity) {
+            if ((foregroundGravity & Gravity.HORIZONTAL_GRAVITY_MASK) == 0) {
+                foregroundGravity |= Gravity.LEFT;
+            }
+
+            if ((foregroundGravity & Gravity.VERTICAL_GRAVITY_MASK) == 0) {
+                foregroundGravity |= Gravity.TOP;
+            }
+
+            mForegroundGravity = foregroundGravity;
+            requestLayout();
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    protected boolean verifyDrawable(Drawable who) {
+        return super.verifyDrawable(who) || (who == mForeground);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    protected void drawableStateChanged() {
+        super.drawableStateChanged();
+        if (mForeground != null && mForeground.isStateful()) {
+            mForeground.setState(getDrawableState());
+        }
+    }
+
+    /**
+     * Returns a set of layout parameters with a width of
+     * {@link android.view.ViewGroup.LayoutParams#FILL_PARENT},
+     * and a height of {@link android.view.ViewGroup.LayoutParams#FILL_PARENT}.
+     */
+    @Override
+    protected LayoutParams generateDefaultLayoutParams() {
+        return new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT);
+    }
+
+    /**
+     * Supply a Drawable that is to be rendered on top of all of the child
+     * views in the frame layout.  Any padding in the Drawable will be taken
+     * into account by ensuring that the children are inset to be placed
+     * inside of the padding area.
+     * 
+     * @param drawable The Drawable to be drawn on top of the children.
+     * 
+     * @attr ref android.R.styleable#FrameLayout_foreground
+     */
+    public void setForeground(Drawable drawable) {
+        if (mForeground != drawable) {
+            if (mForeground != null) {
+                mForeground.setCallback(null);
+                unscheduleDrawable(mForeground);
+            }
+
+            mForeground = drawable;
+            mForegroundPaddingLeft = 0;
+            mForegroundPaddingTop = 0;
+            mForegroundPaddingRight = 0;
+            mForegroundPaddingBottom = 0;
+
+            if (drawable != null) {
+                setWillNotDraw(false);
+                drawable.setCallback(this);
+                if (drawable.isStateful()) {
+                    drawable.setState(getDrawableState());
+                }
+                Rect padding = new Rect();
+                if (drawable.getPadding(padding)) {
+                    mForegroundPaddingLeft = padding.left;
+                    mForegroundPaddingTop = padding.top;
+                    mForegroundPaddingRight = padding.right;
+                    mForegroundPaddingBottom = padding.bottom;
+                }
+            }  else {
+                setWillNotDraw(true);
+            }
+            requestLayout();
+            invalidate();
+        }
+    }
+
+    /**
+     * Returns the drawable used as the foreground of this FrameLayout. The
+     * foreground drawable, if non-null, is always drawn on top of the children.
+     *
+     * @return A Drawable or null if no foreground was set.
+     */
+    public Drawable getForeground() {
+        return mForeground;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        final int count = getChildCount();
+
+        int maxHeight = 0;
+        int maxWidth = 0;
+
+        // Find rightmost and bottommost child
+        for (int i = 0; i < count; i++) {
+            final View child = getChildAt(i);
+            if (mMeasureAllChildren || child.getVisibility() != GONE) {
+                measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
+                maxWidth = Math.max(maxWidth, child.getMeasuredWidth());
+                maxHeight = Math.max(maxHeight, child.getMeasuredHeight());
+            }
+        }
+
+        // Account for padding too
+        maxWidth += mPaddingLeft + mPaddingRight + mForegroundPaddingLeft + mForegroundPaddingRight;
+        maxHeight += mPaddingTop + mPaddingBottom + mForegroundPaddingTop + mForegroundPaddingBottom;
+
+        // Check against our minimum height and width
+        maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight());
+        maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());
+
+        // Check against our foreground's minimum height and width
+        final Drawable drawable = getForeground();
+        if (drawable != null) {
+            maxHeight = Math.max(maxHeight, drawable.getMinimumHeight());
+            maxWidth = Math.max(maxWidth, drawable.getMinimumWidth());
+        }
+
+        setMeasuredDimension(resolveSize(maxWidth, widthMeasureSpec),
+                resolveSize(maxHeight, heightMeasureSpec));
+    }
+ 
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+        final int count = getChildCount();
+
+        final int parentLeft = mPaddingLeft + mForegroundPaddingLeft;
+        final int parentRight = right - left - mPaddingRight - mForegroundPaddingRight;
+
+        final int parentTop = mPaddingTop + mForegroundPaddingTop;
+        final int parentBottom = bottom - top - mPaddingBottom - mForegroundPaddingBottom;
+
+        for (int i = 0; i < count; i++) {
+            final View child = getChildAt(i);
+            if (child.getVisibility() != GONE) {
+                final LayoutParams lp = (LayoutParams) child.getLayoutParams();
+
+                final int width = child.getMeasuredWidth();
+                final int height = child.getMeasuredHeight();
+
+                int childLeft = parentLeft;
+                int childTop = parentTop;
+
+                final int gravity = lp.gravity;
+
+                if (gravity != -1) {
+                    final int horizontalGravity = gravity & Gravity.HORIZONTAL_GRAVITY_MASK;
+                    final int verticalGravity = gravity & Gravity.VERTICAL_GRAVITY_MASK;
+
+                    switch (horizontalGravity) {
+                        case Gravity.LEFT:
+                            childLeft = parentLeft + lp.leftMargin;
+                            break;
+                        case Gravity.CENTER_HORIZONTAL:
+                            childLeft = parentLeft + (parentRight - parentLeft + lp.leftMargin +
+                                    lp.rightMargin - width) / 2;
+                            break;
+                        case Gravity.RIGHT:
+                            childLeft = parentRight - width - lp.rightMargin;
+                            break;
+                        default:
+                            childLeft = parentLeft + lp.leftMargin;
+                    }
+
+                    switch (verticalGravity) {
+                        case Gravity.TOP:
+                            childTop = parentTop + lp.topMargin;
+                            break;
+                        case Gravity.CENTER_VERTICAL:
+                            childTop = parentTop + (parentBottom - parentTop + lp.topMargin +
+                                    lp.bottomMargin - height) / 2;
+                            break;
+                        case Gravity.BOTTOM:
+                            childTop = parentBottom - height - lp.bottomMargin;
+                            break;
+                        default:
+                            childTop = parentTop + lp.topMargin;
+                    }
+                }
+
+                child.layout(childLeft, childTop, childLeft + width, childTop + height);
+            }
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
+        super.onSizeChanged(w, h, oldw, oldh);
+
+        final Drawable foreground = mForeground;
+        if (foreground != null) {
+            final Rect selfBounds = mSelfBounds;
+            final Rect overlayBounds = mOverlayBounds;
+
+            selfBounds.set(0, 0, w, h);
+            Gravity.apply(mForegroundGravity, foreground.getIntrinsicWidth(),
+                    foreground.getIntrinsicHeight(), selfBounds, overlayBounds);
+
+            foreground.setBounds(overlayBounds);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void draw(Canvas canvas) {
+        super.draw(canvas);
+
+        if (mForeground != null) {
+            mForeground.draw(canvas);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public boolean gatherTransparentRegion(Region region) {
+        boolean opaque = super.gatherTransparentRegion(region);
+        if (region != null && mForeground != null) {
+            applyDrawableToTransparentRegion(mForeground, region);
+        }
+        return opaque;
+    }
+
+    /**
+     * Determines whether to measure all children or just those in 
+     * the VISIBLE or INVISIBLE state when measuring. Defaults to false.
+     * @param measureAll true to consider children marked GONE, false otherwise.
+     * Default value is false.
+     * 
+     * @attr ref android.R.styleable#FrameLayout_measureAllChildren
+     */
+    @android.view.RemotableViewMethod
+    public void setMeasureAllChildren(boolean measureAll) {
+        mMeasureAllChildren = measureAll;
+    }
+    
+    /**
+     * Determines whether to measure all children or just those in 
+     * the VISIBLE or INVISIBLE state when measuring. 
+     */
+    public boolean getConsiderGoneChildrenWhenMeasuring() {
+        return mMeasureAllChildren;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public LayoutParams generateLayoutParams(AttributeSet attrs) {
+        return new FrameLayout.LayoutParams(getContext(), attrs);        
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
+        return p instanceof LayoutParams;
+    }
+
+    @Override
+    protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
+        return new LayoutParams(p);
+    }
+
+    /**
+     * Per-child layout information for layouts that support margins.
+     * See {@link android.R.styleable#FrameLayout_Layout FrameLayout Layout Attributes}
+     * for a list of all child view attributes that this class supports.
+     */
+    public static class LayoutParams extends MarginLayoutParams {
+        /**
+         * The gravity to apply with the View to which these layout parameters
+         * are associated.
+         *
+         * @see android.view.Gravity
+         */
+        public int gravity = -1;
+
+        /**
+         * {@inheritDoc}
+         */
+        public LayoutParams(Context c, AttributeSet attrs) {
+            super(c, attrs);
+
+            TypedArray a = c.obtainStyledAttributes(attrs, com.android.internal.R.styleable.FrameLayout_Layout);
+            gravity = a.getInt(com.android.internal.R.styleable.FrameLayout_Layout_layout_gravity, -1);
+            a.recycle();
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        public LayoutParams(int width, int height) {
+            super(width, height);
+        }
+
+        /**
+         * Creates a new set of layout parameters with the specified width, height
+         * and weight.
+         *
+         * @param width the width, either {@link #FILL_PARENT},
+         *        {@link #WRAP_CONTENT} or a fixed size in pixels
+         * @param height the height, either {@link #FILL_PARENT},
+         *        {@link #WRAP_CONTENT} or a fixed size in pixels
+         * @param gravity the gravity
+         *
+         * @see android.view.Gravity
+         */
+        public LayoutParams(int width, int height, int gravity) {
+            super(width, height);
+            this.gravity = gravity;
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        public LayoutParams(ViewGroup.LayoutParams source) {
+            super(source);
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        public LayoutParams(ViewGroup.MarginLayoutParams source) {
+            super(source);
+        }
+    }
+}
+
diff --git a/core/java/android/widget/Gallery.java b/core/java/android/widget/Gallery.java
new file mode 100644
index 0000000..e7b303a
--- /dev/null
+++ b/core/java/android/widget/Gallery.java
@@ -0,0 +1,1408 @@
+/*
+ * Copyright (C) 2007 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 android.widget;
+
+import com.android.internal.R;
+
+import android.annotation.Widget;
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Rect;
+import android.util.AttributeSet;
+import android.util.Config;
+import android.util.Log;
+import android.view.GestureDetector;
+import android.view.Gravity;
+import android.view.HapticFeedbackConstants;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewConfiguration;
+import android.view.ViewGroup;
+import android.view.SoundEffectConstants;
+import android.view.ContextMenu.ContextMenuInfo;
+import android.view.animation.Transformation;
+import android.widget.AbsSpinner;
+import android.widget.Scroller;
+
+/**
+ * A view that shows items in a center-locked, horizontally scrolling list.
+ * <p>
+ * The default values for the Gallery assume you will be using
+ * {@link android.R.styleable#Theme_galleryItemBackground} as the background for
+ * each View given to the Gallery from the Adapter. If you are not doing this,
+ * you may need to adjust some Gallery properties, such as the spacing.
+ * <p>
+ * Views given to the Gallery should use {@link Gallery.LayoutParams} as their
+ * layout parameters type.
+ * 
+ * @attr ref android.R.styleable#Gallery_animationDuration
+ * @attr ref android.R.styleable#Gallery_spacing
+ * @attr ref android.R.styleable#Gallery_gravity
+ */
+@Widget
+public class Gallery extends AbsSpinner implements GestureDetector.OnGestureListener {
+
+    private static final String TAG = "Gallery";
+
+    private static final boolean localLOGV = Config.LOGV;
+
+    /**
+     * Duration in milliseconds from the start of a scroll during which we're
+     * unsure whether the user is scrolling or flinging.
+     */
+    private static final int SCROLL_TO_FLING_UNCERTAINTY_TIMEOUT = 250;
+
+    /**
+     * Horizontal spacing between items.
+     */
+    private int mSpacing = 0;
+
+    /**
+     * How long the transition animation should run when a child view changes
+     * position, measured in milliseconds.
+     */
+    private int mAnimationDuration = 400;
+
+    /**
+     * The alpha of items that are not selected.
+     */
+    private float mUnselectedAlpha;
+    
+    /**
+     * Left most edge of a child seen so far during layout.
+     */
+    private int mLeftMost;
+
+    /**
+     * Right most edge of a child seen so far during layout.
+     */
+    private int mRightMost;
+
+    private int mGravity;
+
+    /**
+     * Helper for detecting touch gestures.
+     */
+    private GestureDetector mGestureDetector;
+
+    /**
+     * The position of the item that received the user's down touch.
+     */
+    private int mDownTouchPosition;
+
+    /**
+     * The view of the item that received the user's down touch.
+     */
+    private View mDownTouchView;
+    
+    /**
+     * Executes the delta scrolls from a fling or scroll movement. 
+     */
+    private FlingRunnable mFlingRunnable = new FlingRunnable();
+
+    /**
+     * Sets mSuppressSelectionChanged = false. This is used to set it to false
+     * in the future. It will also trigger a selection changed.
+     */
+    private Runnable mDisableSuppressSelectionChangedRunnable = new Runnable() {
+        public void run() {
+            mSuppressSelectionChanged = false;
+            selectionChanged();
+        }
+    };
+    
+    /**
+     * When fling runnable runs, it resets this to false. Any method along the
+     * path until the end of its run() can set this to true to abort any
+     * remaining fling. For example, if we've reached either the leftmost or
+     * rightmost item, we will set this to true.
+     */
+    private boolean mShouldStopFling;
+    
+    /**
+     * The currently selected item's child.
+     */
+    private View mSelectedChild;
+    
+    /**
+     * Whether to continuously callback on the item selected listener during a
+     * fling.
+     */
+    private boolean mShouldCallbackDuringFling = true;
+
+    /**
+     * Whether to callback when an item that is not selected is clicked.
+     */
+    private boolean mShouldCallbackOnUnselectedItemClick = true;
+
+    /**
+     * If true, do not callback to item selected listener. 
+     */
+    private boolean mSuppressSelectionChanged;
+
+    /**
+     * If true, we have received the "invoke" (center or enter buttons) key
+     * down. This is checked before we action on the "invoke" key up, and is
+     * subsequently cleared.
+     */
+    private boolean mReceivedInvokeKeyDown;
+    
+    private AdapterContextMenuInfo mContextMenuInfo;
+
+    /**
+     * If true, this onScroll is the first for this user's drag (remember, a
+     * drag sends many onScrolls).
+     */
+    private boolean mIsFirstScroll;
+    
+    public Gallery(Context context) {
+        this(context, null);
+    }
+
+    public Gallery(Context context, AttributeSet attrs) {
+        this(context, attrs, R.attr.galleryStyle);
+    }
+
+    public Gallery(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+        
+        mGestureDetector = new GestureDetector(context, this);
+        mGestureDetector.setIsLongpressEnabled(true);
+        
+        TypedArray a = context.obtainStyledAttributes(
+                attrs, com.android.internal.R.styleable.Gallery, defStyle, 0);
+
+        int index = a.getInt(com.android.internal.R.styleable.Gallery_gravity, -1);
+        if (index >= 0) {
+            setGravity(index);
+        }
+
+        int animationDuration =
+                a.getInt(com.android.internal.R.styleable.Gallery_animationDuration, -1);
+        if (animationDuration > 0) {
+            setAnimationDuration(animationDuration);
+        }
+
+        int spacing =
+                a.getDimensionPixelOffset(com.android.internal.R.styleable.Gallery_spacing, 0);
+        setSpacing(spacing);
+
+        float unselectedAlpha = a.getFloat(
+                com.android.internal.R.styleable.Gallery_unselectedAlpha, 0.5f);
+        setUnselectedAlpha(unselectedAlpha);
+        
+        a.recycle();
+
+        // We draw the selected item last (because otherwise the item to the
+        // right overlaps it)
+        mGroupFlags |= FLAG_USE_CHILD_DRAWING_ORDER;
+        
+        mGroupFlags |= FLAG_SUPPORT_STATIC_TRANSFORMATIONS;
+    }
+
+    /**
+     * Whether or not to callback on any {@link #getOnItemSelectedListener()}
+     * while the items are being flinged. If false, only the final selected item
+     * will cause the callback. If true, all items between the first and the
+     * final will cause callbacks.
+     * 
+     * @param shouldCallback Whether or not to callback on the listener while
+     *            the items are being flinged.
+     */
+    public void setCallbackDuringFling(boolean shouldCallback) {
+        mShouldCallbackDuringFling = shouldCallback;
+    }
+
+    /**
+     * Whether or not to callback when an item that is not selected is clicked.
+     * If false, the item will become selected (and re-centered). If true, the
+     * {@link #getOnItemClickListener()} will get the callback.
+     * 
+     * @param shouldCallback Whether or not to callback on the listener when a
+     *            item that is not selected is clicked.
+     * @hide
+     */
+    public void setCallbackOnUnselectedItemClick(boolean shouldCallback) {
+        mShouldCallbackOnUnselectedItemClick = shouldCallback;
+    }
+    
+    /**
+     * Sets how long the transition animation should run when a child view
+     * changes position. Only relevant if animation is turned on.
+     * 
+     * @param animationDurationMillis The duration of the transition, in
+     *        milliseconds.
+     * 
+     * @attr ref android.R.styleable#Gallery_animationDuration
+     */
+    public void setAnimationDuration(int animationDurationMillis) {
+        mAnimationDuration = animationDurationMillis;
+    }
+
+    /**
+     * Sets the spacing between items in a Gallery
+     * 
+     * @param spacing The spacing in pixels between items in the Gallery
+     * 
+     * @attr ref android.R.styleable#Gallery_spacing
+     */
+    public void setSpacing(int spacing) {
+        mSpacing = spacing;
+    }
+
+    /**
+     * Sets the alpha of items that are not selected in the Gallery.
+     * 
+     * @param unselectedAlpha the alpha for the items that are not selected.
+     * 
+     * @attr ref android.R.styleable#Gallery_unselectedAlpha
+     */
+    public void setUnselectedAlpha(float unselectedAlpha) {
+        mUnselectedAlpha = unselectedAlpha;
+    }
+
+    @Override
+    protected boolean getChildStaticTransformation(View child, Transformation t) {
+        
+        t.clear();
+        t.setAlpha(child == mSelectedChild ? 1.0f : mUnselectedAlpha);
+        
+        return true;
+    }
+
+    @Override
+    protected int computeHorizontalScrollExtent() {
+        // Only 1 item is considered to be selected
+        return 1;
+    }
+
+    @Override
+    protected int computeHorizontalScrollOffset() {
+        // Current scroll position is the same as the selected position
+        return mSelectedPosition;
+    }
+
+    @Override
+    protected int computeHorizontalScrollRange() {
+        // Scroll range is the same as the item count
+        return mItemCount;
+    }
+
+    @Override
+    protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
+        return p instanceof LayoutParams;
+    }
+
+    @Override
+    protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
+        return new LayoutParams(p);
+    }
+
+    @Override
+    public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {
+        return new LayoutParams(getContext(), attrs);
+    }
+
+    @Override
+    protected ViewGroup.LayoutParams generateDefaultLayoutParams() {
+        /*
+         * Gallery expects Gallery.LayoutParams.
+         */
+        return new Gallery.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
+                ViewGroup.LayoutParams.WRAP_CONTENT);
+    }
+
+    @Override
+    protected void onLayout(boolean changed, int l, int t, int r, int b) {
+        super.onLayout(changed, l, t, r, b);
+        
+        /*
+         * Remember that we are in layout to prevent more layout request from
+         * being generated.
+         */
+        mInLayout = true;
+        layout(0, false);
+        mInLayout = false;
+    }
+
+    @Override
+    int getChildHeight(View child) {
+        return child.getMeasuredHeight();
+    }
+    
+    /**
+     * Tracks a motion scroll. In reality, this is used to do just about any
+     * movement to items (touch scroll, arrow-key scroll, set an item as selected).
+     * 
+     * @param deltaX Change in X from the previous event.
+     */
+    void trackMotionScroll(int deltaX) {
+
+        if (getChildCount() == 0) {
+            return;
+        }
+        
+        boolean toLeft = deltaX < 0; 
+        
+        int limitedDeltaX = getLimitedMotionScrollAmount(toLeft, deltaX);
+        if (limitedDeltaX != deltaX) {
+            // The above call returned a limited amount, so stop any scrolls/flings
+            mFlingRunnable.endFling(false);
+            onFinishedMovement();
+        }
+        
+        offsetChildrenLeftAndRight(limitedDeltaX);
+        
+        detachOffScreenChildren(toLeft);
+        
+        if (toLeft) {
+            // If moved left, there will be empty space on the right
+            fillToGalleryRight();
+        } else {
+            // Similarly, empty space on the left
+            fillToGalleryLeft();
+        }
+        
+        // Clear unused views
+        mRecycler.clear();
+        
+        setSelectionToCenterChild();
+        
+        invalidate();
+    }
+
+    int getLimitedMotionScrollAmount(boolean motionToLeft, int deltaX) {
+        int extremeItemPosition = motionToLeft ? mItemCount - 1 : 0;
+        View extremeChild = getChildAt(extremeItemPosition - mFirstPosition);
+        
+        if (extremeChild == null) {
+            return deltaX;
+        }
+        
+        int extremeChildCenter = getCenterOfView(extremeChild);
+        int galleryCenter = getCenterOfGallery();
+        
+        if (motionToLeft) {
+            if (extremeChildCenter <= galleryCenter) {
+                
+                // The extreme child is past his boundary point!
+                return 0;
+            }
+        } else {
+            if (extremeChildCenter >= galleryCenter) {
+
+                // The extreme child is past his boundary point!
+                return 0;
+            }
+        }
+        
+        int centerDifference = galleryCenter - extremeChildCenter;
+
+        return motionToLeft
+                ? Math.max(centerDifference, deltaX)
+                : Math.min(centerDifference, deltaX); 
+    }
+
+    /**
+     * Offset the horizontal location of all children of this view by the
+     * specified number of pixels.
+     * 
+     * @param offset the number of pixels to offset
+     */
+    private void offsetChildrenLeftAndRight(int offset) {
+        for (int i = getChildCount() - 1; i >= 0; i--) {
+            getChildAt(i).offsetLeftAndRight(offset);
+        }
+    }
+    
+    /**
+     * @return The center of this Gallery.
+     */
+    private int getCenterOfGallery() {
+        return (getWidth() - mPaddingLeft - mPaddingRight) / 2 + mPaddingLeft;
+    }
+    
+    /**
+     * @return The center of the given view.
+     */
+    private static int getCenterOfView(View view) {
+        return view.getLeft() + view.getWidth() / 2;
+    }
+    
+    /**
+     * Detaches children that are off the screen (i.e.: Gallery bounds).
+     * 
+     * @param toLeft Whether to detach children to the left of the Gallery, or
+     *            to the right.
+     */
+    private void detachOffScreenChildren(boolean toLeft) {
+        int numChildren = getChildCount();
+        int firstPosition = mFirstPosition;
+        int start = 0;
+        int count = 0;
+
+        if (toLeft) {
+            final int galleryLeft = mPaddingLeft;
+            for (int i = 0; i < numChildren; i++) {
+                final View child = getChildAt(i);
+                if (child.getRight() >= galleryLeft) {
+                    break;
+                } else {
+                    count++;
+                    mRecycler.put(firstPosition + i, child);
+                }
+            }
+        } else {
+            final int galleryRight = getWidth() - mPaddingRight;
+            for (int i = numChildren - 1; i >= 0; i--) {
+                final View child = getChildAt(i);
+                if (child.getLeft() <= galleryRight) {
+                    break;
+                } else {
+                    start = i;
+                    count++;
+                    mRecycler.put(firstPosition + i, child);
+                }
+            }
+        }
+
+        detachViewsFromParent(start, count);
+        
+        if (toLeft) {
+            mFirstPosition += count;
+        }
+    }
+    
+    /**
+     * Scrolls the items so that the selected item is in its 'slot' (its center
+     * is the gallery's center).
+     */
+    private void scrollIntoSlots() {
+        
+        if (getChildCount() == 0 || mSelectedChild == null) return;
+        
+        int selectedCenter = getCenterOfView(mSelectedChild);
+        int targetCenter = getCenterOfGallery();
+        
+        int scrollAmount = targetCenter - selectedCenter;
+        if (scrollAmount != 0) {
+            mFlingRunnable.startUsingDistance(scrollAmount);
+        } else {
+            onFinishedMovement();
+        }
+    }
+
+    private void onFinishedMovement() {
+        if (mSuppressSelectionChanged) {
+            mSuppressSelectionChanged = false;
+            
+            // We haven't been callbacking during the fling, so do it now
+            super.selectionChanged();
+        }
+    }
+    
+    @Override
+    void selectionChanged() {
+        if (!mSuppressSelectionChanged) {
+            super.selectionChanged();
+        }
+    }
+
+    /**
+     * Looks for the child that is closest to the center and sets it as the
+     * selected child.
+     */
+    private void setSelectionToCenterChild() {
+        
+        View selView = mSelectedChild;
+        if (mSelectedChild == null) return;
+        
+        int galleryCenter = getCenterOfGallery();
+        
+        if (selView != null) {
+
+            // Common case where the current selected position is correct
+            if (selView.getLeft() <= galleryCenter && selView.getRight() >= galleryCenter) {
+                return;
+            }
+        }
+        
+        // TODO better search
+        int closestEdgeDistance = Integer.MAX_VALUE;
+        int newSelectedChildIndex = 0;
+        for (int i = getChildCount() - 1; i >= 0; i--) {
+            
+            View child = getChildAt(i);
+            
+            if (child.getLeft() <= galleryCenter && child.getRight() >=  galleryCenter) {
+                // This child is in the center
+                newSelectedChildIndex = i;
+                break;
+            }
+            
+            int childClosestEdgeDistance = Math.min(Math.abs(child.getLeft() - galleryCenter),
+                    Math.abs(child.getRight() - galleryCenter));
+            if (childClosestEdgeDistance < closestEdgeDistance) {
+                closestEdgeDistance = childClosestEdgeDistance;
+                newSelectedChildIndex = i;
+            }
+        }
+        
+        int newPos = mFirstPosition + newSelectedChildIndex;
+        
+        if (newPos != mSelectedPosition) {
+            setSelectedPositionInt(newPos);
+            setNextSelectedPositionInt(newPos);
+            checkSelectionChanged();
+        }
+    }
+
+    /**
+     * Creates and positions all views for this Gallery.
+     * <p>
+     * We layout rarely, most of the time {@link #trackMotionScroll(int)} takes
+     * care of repositioning, adding, and removing children.
+     * 
+     * @param delta Change in the selected position. +1 means the selection is
+     *            moving to the right, so views are scrolling to the left. -1
+     *            means the selection is moving to the left.
+     */
+    @Override
+    void layout(int delta, boolean animate) {
+
+        int childrenLeft = mSpinnerPadding.left;
+        int childrenWidth = mRight - mLeft - mSpinnerPadding.left - mSpinnerPadding.right;
+
+        if (mDataChanged) {
+            handleDataChanged();
+        }
+
+        // Handle an empty gallery by removing all views.
+        if (mItemCount == 0) {
+            resetList();
+            return;
+        }
+
+        // Update to the new selected position.
+        if (mNextSelectedPosition >= 0) {
+            setSelectedPositionInt(mNextSelectedPosition);
+        }
+
+        // All views go in recycler while we are in layout
+        recycleAllViews();
+
+        // Clear out old views
+        //removeAllViewsInLayout();
+        detachAllViewsFromParent();
+
+        /*
+         * These will be used to give initial positions to views entering the
+         * gallery as we scroll
+         */
+        mRightMost = 0;
+        mLeftMost = 0;
+
+        // Make selected view and center it
+        
+        /*
+         * mFirstPosition will be decreased as we add views to the left later
+         * on. The 0 for x will be offset in a couple lines down.
+         */  
+        mFirstPosition = mSelectedPosition;
+        View sel = makeAndAddView(mSelectedPosition, 0, 0, true);
+        
+        // Put the selected child in the center
+        Gallery.LayoutParams lp = (Gallery.LayoutParams) sel.getLayoutParams();
+        int selectedOffset = childrenLeft + (childrenWidth / 2) - (sel.getWidth() / 2);
+        sel.offsetLeftAndRight(selectedOffset);
+
+        fillToGalleryRight();
+        fillToGalleryLeft();
+        
+        // Flush any cached views that did not get reused above
+        mRecycler.clear();
+
+        invalidate();
+        checkSelectionChanged();
+
+        mDataChanged = false;
+        mNeedSync = false;
+        setNextSelectedPositionInt(mSelectedPosition);
+        
+        updateSelectedItemMetadata();
+    }
+
+    private void fillToGalleryLeft() {
+        int itemSpacing = mSpacing;
+        int galleryLeft = mPaddingLeft;
+        
+        // Set state for initial iteration
+        View prevIterationView = getChildAt(0);
+        int curPosition;
+        int curRightEdge;
+        
+        if (prevIterationView != null) {
+            curPosition = mFirstPosition - 1;
+            curRightEdge = prevIterationView.getLeft() - itemSpacing;
+        } else {
+            // No children available!
+            curPosition = 0; 
+            curRightEdge = mRight - mLeft - mPaddingRight;
+            mShouldStopFling = true;
+        }
+                
+        while (curRightEdge > galleryLeft && curPosition >= 0) {
+            prevIterationView = makeAndAddView(curPosition, curPosition - mSelectedPosition,
+                    curRightEdge, false);
+
+            // Remember some state
+            mFirstPosition = curPosition;
+            
+            // Set state for next iteration
+            curRightEdge = prevIterationView.getLeft() - itemSpacing;
+            curPosition--;
+        }
+    }
+    
+    private void fillToGalleryRight() {
+        int itemSpacing = mSpacing;
+        int galleryRight = mRight - mLeft - mPaddingRight;
+        int numChildren = getChildCount();
+        int numItems = mItemCount;
+        
+        // Set state for initial iteration
+        View prevIterationView = getChildAt(numChildren - 1);
+        int curPosition;
+        int curLeftEdge;
+        
+        if (prevIterationView != null) {
+            curPosition = mFirstPosition + numChildren;
+            curLeftEdge = prevIterationView.getRight() + itemSpacing;
+        } else {
+            mFirstPosition = curPosition = mItemCount - 1;
+            curLeftEdge = mPaddingLeft;
+            mShouldStopFling = true;
+        }
+                
+        while (curLeftEdge < galleryRight && curPosition < numItems) {
+            prevIterationView = makeAndAddView(curPosition, curPosition - mSelectedPosition,
+                    curLeftEdge, true);
+
+            // Set state for next iteration
+            curLeftEdge = prevIterationView.getRight() + itemSpacing;
+            curPosition++;
+        }
+    }
+
+    /**
+     * Obtain a view, either by pulling an existing view from the recycler or by
+     * getting a new one from the adapter. If we are animating, make sure there
+     * is enough information in the view's layout parameters to animate from the
+     * old to new positions.
+     * 
+     * @param position Position in the gallery for the view to obtain
+     * @param offset Offset from the selected position
+     * @param x X-coordintate indicating where this view should be placed. This
+     *        will either be the left or right edge of the view, depending on
+     *        the fromLeft paramter
+     * @param fromLeft Are we posiitoning views based on the left edge? (i.e.,
+     *        building from left to right)?
+     * @return A view that has been added to the gallery
+     */
+    private View makeAndAddView(int position, int offset, int x,
+            boolean fromLeft) {
+
+        View child;
+
+        if (!mDataChanged) {
+            child = mRecycler.get(position);
+            if (child != null) {
+                // Can reuse an existing view
+                Gallery.LayoutParams lp = (Gallery.LayoutParams) 
+                    child.getLayoutParams();
+
+                int childLeft = child.getLeft();
+                
+                // Remember left and right edges of where views have been placed
+                mRightMost = Math.max(mRightMost, childLeft 
+                        + child.getMeasuredWidth());
+                mLeftMost = Math.min(mLeftMost, childLeft);
+
+                // Position the view
+                setUpChild(child, offset, x, fromLeft);
+
+                return child;
+            }
+        }
+
+        // Nothing found in the recycler -- ask the adapter for a view
+        child = mAdapter.getView(position, null, this);
+
+        // Position the view
+        setUpChild(child, offset, x, fromLeft);
+
+        return child;
+    }
+
+    /**
+     * Helper for makeAndAddView to set the position of a view and fill out its
+     * layout paramters.
+     * 
+     * @param child The view to position
+     * @param offset Offset from the selected position
+     * @param x X-coordintate indicating where this view should be placed. This
+     *        will either be the left or right edge of the view, depending on
+     *        the fromLeft paramter
+     * @param fromLeft Are we posiitoning views based on the left edge? (i.e.,
+     *        building from left to right)?
+     */
+    private void setUpChild(View child, int offset, int x, boolean fromLeft) {
+
+        // Respect layout params that are already in the view. Otherwise
+        // make some up...
+        Gallery.LayoutParams lp = (Gallery.LayoutParams) 
+            child.getLayoutParams();
+        if (lp == null) {
+            lp = (Gallery.LayoutParams) generateDefaultLayoutParams();
+        }
+
+        addViewInLayout(child, fromLeft ? -1 : 0, lp);
+
+        child.setSelected(offset == 0);
+
+        // Get measure specs
+        int childHeightSpec = ViewGroup.getChildMeasureSpec(mHeightMeasureSpec,
+                mSpinnerPadding.top + mSpinnerPadding.bottom, lp.height);
+        int childWidthSpec = ViewGroup.getChildMeasureSpec(mWidthMeasureSpec,
+                mSpinnerPadding.left + mSpinnerPadding.right, lp.width);
+
+        // Measure child
+        child.measure(childWidthSpec, childHeightSpec);
+
+        int childLeft;
+        int childRight;
+
+        // Position vertically based on gravity setting
+        int childTop = calculateTop(child, lp, true);
+        int childBottom = childTop + child.getMeasuredHeight();
+
+        int width = child.getMeasuredWidth();
+        if (fromLeft) {
+            childLeft = x;
+            childRight = childLeft + width;
+        } else {
+            childLeft = x - width;
+            childRight = x;
+        }
+
+        child.layout(childLeft, childTop, childRight, childBottom);
+    }
+
+    /**
+     * Figure out vertical placement based on mGravity
+     * 
+     * @param child Child to place
+     * @param lp LayoutParams for this view (just so we don't keep looking them
+     *        up)
+     * @return Where the top of the child should be
+     */
+    private int calculateTop(View child, Gallery.LayoutParams lp, boolean duringLayout) {
+        int myHeight = duringLayout ? mMeasuredHeight : getHeight();
+        int childHeight = duringLayout ? child.getMeasuredHeight() : child.getHeight(); 
+        
+        int childTop = 0;
+
+        switch (mGravity) {
+        case Gravity.TOP:
+            childTop = mSpinnerPadding.top;
+            break;
+        case Gravity.CENTER_VERTICAL:
+            int availableSpace = myHeight - mSpinnerPadding.bottom
+                    - mSpinnerPadding.top - childHeight;
+            childTop = mSpinnerPadding.top + (availableSpace / 2);
+            break;
+        case Gravity.BOTTOM:
+            childTop = myHeight - mSpinnerPadding.bottom - childHeight;
+            break;
+        }
+        return childTop;
+    }
+
+    @Override
+    public boolean onTouchEvent(MotionEvent event) {
+
+        // Give everything to the gesture detector
+        boolean retValue = mGestureDetector.onTouchEvent(event);
+
+        int action = event.getAction();
+        if (action == MotionEvent.ACTION_UP) {
+            // Helper method for lifted finger
+            onUp();
+        } else if (action == MotionEvent.ACTION_CANCEL) {
+            onCancel();
+        }
+        
+        return retValue;
+    }
+    
+    /**
+     * {@inheritDoc}
+     */
+    public boolean onSingleTapUp(MotionEvent e) {
+
+        if (mDownTouchPosition >= 0) {
+            
+            // An item tap should make it selected, so scroll to this child.
+            scrollToChild(mDownTouchPosition - mFirstPosition);
+
+            // Also pass the click so the client knows, if it wants to.
+            if (mShouldCallbackOnUnselectedItemClick || mDownTouchPosition == mSelectedPosition) {
+                performItemClick(mDownTouchView, mDownTouchPosition, mAdapter
+                        .getItemId(mDownTouchPosition));
+            }
+            
+            return true;
+        }
+        
+        return false;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
+        
+        if (!mShouldCallbackDuringFling) {
+            // We want to suppress selection changes
+            
+            // Remove any future code to set mSuppressSelectionChanged = false
+            removeCallbacks(mDisableSuppressSelectionChangedRunnable);
+
+            // This will get reset once we scroll into slots
+            if (!mSuppressSelectionChanged) mSuppressSelectionChanged = true;
+        }
+        
+        // Fling the gallery!
+        mFlingRunnable.startUsingVelocity((int) -velocityX);
+        
+        return true;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
+
+        if (localLOGV) Log.v(TAG, String.valueOf(e2.getX() - e1.getX()));
+        
+        /*
+         * Now's a good time to tell our parent to stop intercepting our events!
+         * The user has moved more than the slop amount, since GestureDetector
+         * ensures this before calling this method. Also, if a parent is more
+         * interested in this touch's events than we are, it would have
+         * intercepted them by now (for example, we can assume when a Gallery is
+         * in the ListView, a vertical scroll would not end up in this method
+         * since a ListView would have intercepted it by now).
+         */
+        mParent.requestDisallowInterceptTouchEvent(true);
+        
+        // As the user scrolls, we want to callback selection changes so related-
+        // info on the screen is up-to-date with the gallery's selection
+        if (!mShouldCallbackDuringFling) {
+            if (mIsFirstScroll) {
+                /*
+                 * We're not notifying the client of selection changes during
+                 * the fling, and this scroll could possibly be a fling. Don't
+                 * do selection changes until we're sure it is not a fling.
+                 */
+                if (!mSuppressSelectionChanged) mSuppressSelectionChanged = true;
+                postDelayed(mDisableSuppressSelectionChangedRunnable, SCROLL_TO_FLING_UNCERTAINTY_TIMEOUT);
+            }
+        } else {
+            if (mSuppressSelectionChanged) mSuppressSelectionChanged = false;
+        }
+        
+        // Track the motion
+        trackMotionScroll(-1 * (int) distanceX);
+       
+        mIsFirstScroll = false;
+        return true;
+    }
+    
+    /**
+     * {@inheritDoc}
+     */
+    public boolean onDown(MotionEvent e) {
+
+        // Kill any existing fling/scroll
+        mFlingRunnable.stop(false);
+
+        // Get the item's view that was touched
+        mDownTouchPosition = pointToPosition((int) e.getX(), (int) e.getY());
+        
+        if (mDownTouchPosition >= 0) {
+            mDownTouchView = getChildAt(mDownTouchPosition - mFirstPosition);
+            mDownTouchView.setPressed(true);
+        }
+        
+        // Reset the multiple-scroll tracking state
+        mIsFirstScroll = true;
+        
+        // Must return true to get matching events for this down event.
+        return true;
+    }
+
+    /**
+     * Called when a touch event's action is MotionEvent.ACTION_UP.
+     */
+    void onUp() {
+        
+        if (mFlingRunnable.mScroller.isFinished()) {
+            scrollIntoSlots();
+        }
+        
+        dispatchUnpress();
+    }
+    
+    /**
+     * Called when a touch event's action is MotionEvent.ACTION_CANCEL.
+     */
+    void onCancel() {
+        onUp();
+    }
+    
+    /**
+     * {@inheritDoc}
+     */
+    public void onLongPress(MotionEvent e) {
+        
+        if (mDownTouchPosition < 0) {
+            return;
+        }
+        
+        performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
+        long id = getItemIdAtPosition(mDownTouchPosition);
+        dispatchLongPress(mDownTouchView, mDownTouchPosition, id);
+    }
+
+    // Unused methods from GestureDetector.OnGestureListener below
+    
+    /**
+     * {@inheritDoc}
+     */
+    public void onShowPress(MotionEvent e) {
+    }
+
+    // Unused methods from GestureDetector.OnGestureListener above
+    
+    private void dispatchPress(View child) {
+        
+        if (child != null) {
+            child.setPressed(true);
+        }
+        
+        setPressed(true);
+    }
+    
+    private void dispatchUnpress() {
+        
+        for (int i = getChildCount() - 1; i >= 0; i--) {
+            getChildAt(i).setPressed(false);
+        }
+        
+        setPressed(false);
+    }
+    
+    @Override
+    public void dispatchSetSelected(boolean selected) {
+        /*
+         * We don't want to pass the selected state given from its parent to its
+         * children since this widget itself has a selected state to give to its
+         * children.
+         */
+    }
+
+    @Override
+    protected void dispatchSetPressed(boolean pressed) {
+        
+        // Show the pressed state on the selected child
+        if (mSelectedChild != null) {
+            mSelectedChild.setPressed(pressed);
+        }
+    }
+
+    @Override
+    protected ContextMenuInfo getContextMenuInfo() {
+        return mContextMenuInfo;
+    }
+
+    @Override
+    public boolean showContextMenuForChild(View originalView) {
+
+        final int longPressPosition = getPositionForView(originalView);
+        if (longPressPosition < 0) {
+            return false;
+        }
+        
+        final long longPressId = mAdapter.getItemId(longPressPosition);
+        return dispatchLongPress(originalView, longPressPosition, longPressId);
+    }
+
+    @Override
+    public boolean showContextMenu() {
+        
+        if (isPressed() && mSelectedPosition >= 0) {
+            int index = mSelectedPosition - mFirstPosition;
+            View v = getChildAt(index);
+            return dispatchLongPress(v, mSelectedPosition, mSelectedRowId);
+        }        
+        
+        return false;
+    }
+
+    private boolean dispatchLongPress(View view, int position, long id) {
+        boolean handled = false;
+        
+        if (mOnItemLongClickListener != null) {
+            handled = mOnItemLongClickListener.onItemLongClick(this, mDownTouchView,
+                    mDownTouchPosition, id);
+        }
+
+        if (!handled) {
+            mContextMenuInfo = new AdapterContextMenuInfo(view, position, id);
+            handled = super.showContextMenuForChild(this);
+        }
+
+        if (handled) {
+            performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
+        }
+        
+        return handled;
+    }
+    
+    @Override
+    public boolean dispatchKeyEvent(KeyEvent event) {
+        // Gallery steals all key events
+        return event.dispatch(this);
+    }
+
+    /**
+     * Handles left, right, and clicking
+     * @see android.view.View#onKeyDown
+     */
+    @Override
+    public boolean onKeyDown(int keyCode, KeyEvent event) {
+        switch (keyCode) {
+            
+        case KeyEvent.KEYCODE_DPAD_LEFT:
+            if (movePrevious()) {
+                playSoundEffect(SoundEffectConstants.NAVIGATION_LEFT);
+            }
+            return true;
+
+        case KeyEvent.KEYCODE_DPAD_RIGHT:
+            if (moveNext()) {
+                playSoundEffect(SoundEffectConstants.NAVIGATION_RIGHT);
+            }
+            return true;
+
+        case KeyEvent.KEYCODE_DPAD_CENTER:
+        case KeyEvent.KEYCODE_ENTER:
+            mReceivedInvokeKeyDown = true;
+            // fallthrough to default handling
+        }
+        
+        return super.onKeyDown(keyCode, event);
+    }
+
+    @Override
+    public boolean onKeyUp(int keyCode, KeyEvent event) {
+        switch (keyCode) {
+        case KeyEvent.KEYCODE_DPAD_CENTER:
+        case KeyEvent.KEYCODE_ENTER: {
+            
+            if (mReceivedInvokeKeyDown) {
+                if (mItemCount > 0) {
+    
+                    dispatchPress(mSelectedChild);
+                    postDelayed(new Runnable() {
+                        public void run() {
+                            dispatchUnpress();
+                        }
+                    }, ViewConfiguration.getPressedStateDuration());
+    
+                    int selectedIndex = mSelectedPosition - mFirstPosition;
+                    performItemClick(getChildAt(selectedIndex), mSelectedPosition, mAdapter
+                            .getItemId(mSelectedPosition));
+                }
+            }
+            
+            // Clear the flag
+            mReceivedInvokeKeyDown = false;
+            
+            return true;
+        }
+        }
+
+        return super.onKeyUp(keyCode, event);
+    }
+    
+    boolean movePrevious() {
+        if (mItemCount > 0 && mSelectedPosition > 0) {
+            scrollToChild(mSelectedPosition - mFirstPosition - 1);
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    boolean moveNext() {
+        if (mItemCount > 0 && mSelectedPosition < mItemCount - 1) {
+            scrollToChild(mSelectedPosition - mFirstPosition + 1);
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    private boolean scrollToChild(int childPosition) {
+        View child = getChildAt(childPosition);
+        
+        if (child != null) {
+            int distance = getCenterOfGallery() - getCenterOfView(child);
+            mFlingRunnable.startUsingDistance(distance);
+            return true;
+        }
+        
+        return false;
+    }
+    
+    @Override
+    void setSelectedPositionInt(int position) {
+        super.setSelectedPositionInt(position);
+
+        // Updates any metadata we keep about the selected item.
+        updateSelectedItemMetadata();
+    }
+
+    private void updateSelectedItemMetadata() {
+        
+        View oldSelectedChild = mSelectedChild;
+
+        View child = mSelectedChild = getChildAt(mSelectedPosition - mFirstPosition);
+        if (child == null) {
+            return;
+        }
+
+        child.setSelected(true);
+        child.setFocusable(true);
+
+        if (hasFocus()) {
+            child.requestFocus();
+        }
+
+        // We unfocus the old child down here so the above hasFocus check
+        // returns true
+        if (oldSelectedChild != null) {
+
+            // Make sure its drawable state doesn't contain 'selected'
+            oldSelectedChild.setSelected(false);
+            
+            // Make sure it is not focusable anymore, since otherwise arrow keys
+            // can make this one be focused
+            oldSelectedChild.setFocusable(false);
+        }
+        
+    }
+    
+    /**
+     * Describes how the child views are aligned.
+     * @param gravity
+     * 
+     * @attr ref android.R.styleable#Gallery_gravity
+     */
+    public void setGravity(int gravity)
+    {
+        if (mGravity != gravity) {
+            mGravity = gravity;
+            requestLayout();
+        }
+    }
+
+    @Override
+    protected int getChildDrawingOrder(int childCount, int i) {
+        int selectedIndex = mSelectedPosition - mFirstPosition;
+        
+        // Just to be safe
+        if (selectedIndex < 0) return i;
+        
+        if (i == childCount - 1) {
+            // Draw the selected child last
+            return selectedIndex;
+        } else if (i >= selectedIndex) {
+            // Move the children to the right of the selected child earlier one
+            return i + 1;
+        } else {
+            // Keep the children to the left of the selected child the same
+            return i;
+        }
+    }
+
+    @Override
+    protected void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) {
+        super.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
+        
+        /*
+         * The gallery shows focus by focusing the selected item. So, give
+         * focus to our selected item instead. We steal keys from our
+         * selected item elsewhere.
+         */
+        if (gainFocus && mSelectedChild != null) {
+            mSelectedChild.requestFocus(direction);
+        }
+
+    }
+
+    /**
+     * Responsible for fling behavior. Use {@link #startUsingVelocity(int)} to
+     * initiate a fling. Each frame of the fling is handled in {@link #run()}.
+     * A FlingRunnable will keep re-posting itself until the fling is done.
+     *
+     */
+    private class FlingRunnable implements Runnable {
+        /**
+         * Tracks the decay of a fling scroll
+         */
+        private Scroller mScroller;
+
+        /**
+         * X value reported by mScroller on the previous fling
+         */
+        private int mLastFlingX;
+
+        public FlingRunnable() {
+            mScroller = new Scroller(getContext());
+        }
+
+        private void startCommon() {
+            // Remove any pending flings
+            removeCallbacks(this);
+        }
+        
+        public void startUsingVelocity(int initialVelocity) {
+            if (initialVelocity == 0) return;
+            
+            startCommon();
+            
+            int initialX = initialVelocity < 0 ? Integer.MAX_VALUE : 0;
+            mLastFlingX = initialX;
+            mScroller.fling(initialX, 0, initialVelocity, 0,
+                    0, Integer.MAX_VALUE, 0, Integer.MAX_VALUE);
+            post(this);
+        }
+
+        public void startUsingDistance(int distance) {
+            if (distance == 0) return;
+            
+            startCommon();
+            
+            mLastFlingX = 0;
+            mScroller.startScroll(0, 0, -distance, 0, mAnimationDuration);
+            post(this);
+        }
+        
+        public void stop(boolean scrollIntoSlots) {
+            removeCallbacks(this);
+            endFling(scrollIntoSlots);
+        }
+        
+        private void endFling(boolean scrollIntoSlots) {
+            /*
+             * Force the scroller's status to finished (without setting its
+             * position to the end)
+             */
+            mScroller.forceFinished(true);
+            
+            if (scrollIntoSlots) scrollIntoSlots();
+        }
+
+        public void run() {
+
+            if (mItemCount == 0) {
+                endFling(true);
+                return;
+            }
+
+            mShouldStopFling = false;
+            
+            final Scroller scroller = mScroller;
+            boolean more = scroller.computeScrollOffset();
+            final int x = scroller.getCurrX();
+
+            // Flip sign to convert finger direction to list items direction
+            // (e.g. finger moving down means list is moving towards the top)
+            int delta = mLastFlingX - x;
+
+            // Pretend that each frame of a fling scroll is a touch scroll
+            if (delta > 0) {
+                // Moving towards the left. Use first view as mDownTouchPosition
+                mDownTouchPosition = mFirstPosition;
+
+                // Don't fling more than 1 screen
+                delta = Math.min(getWidth() - mPaddingLeft - mPaddingRight - 1, delta);
+            } else {
+                // Moving towards the right. Use last view as mDownTouchPosition
+                int offsetToLast = getChildCount() - 1;
+                mDownTouchPosition = mFirstPosition + offsetToLast;
+
+                // Don't fling more than 1 screen
+                delta = Math.max(-(getWidth() - mPaddingRight - mPaddingLeft - 1), delta);
+            }
+
+            trackMotionScroll(delta);
+
+            if (more && !mShouldStopFling) {
+                mLastFlingX = x;
+                post(this);
+            } else {
+               endFling(true);
+            }
+        }
+        
+    }
+    
+    /**
+     * Gallery extends LayoutParams to provide a place to hold current
+     * Transformation information along with previous position/transformation
+     * info.
+     * 
+     */
+    public static class LayoutParams extends ViewGroup.LayoutParams {
+        public LayoutParams(Context c, AttributeSet attrs) {
+            super(c, attrs);
+        }
+
+        public LayoutParams(int w, int h) {
+            super(w, h);
+        }
+
+        public LayoutParams(ViewGroup.LayoutParams source) {
+            super(source);
+        }
+    }
+}
diff --git a/core/java/android/widget/GridView.java b/core/java/android/widget/GridView.java
new file mode 100644
index 0000000..6bbf062
--- /dev/null
+++ b/core/java/android/widget/GridView.java
@@ -0,0 +1,1833 @@
+/*
+ * Copyright (C) 2007 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 android.widget;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Rect;
+import android.util.AttributeSet;
+import android.view.Gravity;
+import android.view.KeyEvent;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.SoundEffectConstants;
+import android.view.animation.GridLayoutAnimationController;
+
+
+/**
+ * A view that shows items in two-dimensional scrolling grid. The items in the
+ * grid come from the {@link ListAdapter} associated with this view.
+ */
+public class GridView extends AbsListView {
+    public static final int NO_STRETCH = 0;
+    public static final int STRETCH_SPACING = 1;
+    public static final int STRETCH_COLUMN_WIDTH = 2;
+    public static final int STRETCH_SPACING_UNIFORM = 3;
+    
+    public static final int AUTO_FIT = -1;
+
+    private int mNumColumns = AUTO_FIT;
+
+    private int mHorizontalSpacing = 0;
+    private int mRequestedHorizontalSpacing;
+    private int mVerticalSpacing = 0;
+    private int mStretchMode = STRETCH_COLUMN_WIDTH;
+    private int mColumnWidth;
+    private int mRequestedColumnWidth;
+    private int mRequestedNumColumns;
+
+    private View mReferenceView = null;
+    private View mReferenceViewInSelectedRow = null;
+
+    private int mGravity = Gravity.LEFT;
+
+    private final Rect mTempRect = new Rect();
+
+    public GridView(Context context) {
+        super(context);
+    }
+
+    public GridView(Context context, AttributeSet attrs) {
+        this(context, attrs, com.android.internal.R.attr.gridViewStyle);
+    }
+
+    public GridView(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+
+        TypedArray a = context.obtainStyledAttributes(attrs,
+                com.android.internal.R.styleable.GridView, defStyle, 0);
+
+        int hSpacing = a.getDimensionPixelOffset(
+                com.android.internal.R.styleable.GridView_horizontalSpacing, 0);
+        setHorizontalSpacing(hSpacing);
+
+        int vSpacing = a.getDimensionPixelOffset(
+                com.android.internal.R.styleable.GridView_verticalSpacing, 0);
+        setVerticalSpacing(vSpacing);
+
+        int index = a.getInt(com.android.internal.R.styleable.GridView_stretchMode, STRETCH_COLUMN_WIDTH);
+        if (index >= 0) {
+            setStretchMode(index);
+        }
+
+        int columnWidth = a.getDimensionPixelOffset(com.android.internal.R.styleable.GridView_columnWidth, -1);
+        if (columnWidth > 0) {
+            setColumnWidth(columnWidth);
+        }
+
+        int numColumns = a.getInt(com.android.internal.R.styleable.GridView_numColumns, 1);
+        setNumColumns(numColumns);
+
+        index = a.getInt(com.android.internal.R.styleable.GridView_gravity, -1);
+        if (index >= 0) {
+            setGravity(index);
+        }
+        
+        a.recycle();
+    }
+
+    @Override
+    public ListAdapter getAdapter() {
+        return mAdapter;
+    }
+
+    /**
+     * Sets the data behind this GridView.
+     *
+     * @param adapter the adapter providing the grid's data
+     */
+    @Override
+    public void setAdapter(ListAdapter adapter) {
+        if (null != mAdapter) {
+            mAdapter.unregisterDataSetObserver(mDataSetObserver);
+        }
+
+        resetList();
+        mRecycler.clear();        
+        mAdapter = adapter;
+
+        mOldSelectedPosition = INVALID_POSITION;
+        mOldSelectedRowId = INVALID_ROW_ID;
+        
+        if (mAdapter != null) {
+            mOldItemCount = mItemCount;
+            mItemCount = mAdapter.getCount();
+            mDataChanged = true;
+            checkFocus();
+
+            mDataSetObserver = new AdapterDataSetObserver();
+            mAdapter.registerDataSetObserver(mDataSetObserver);
+
+            mRecycler.setViewTypeCount(mAdapter.getViewTypeCount());
+
+            int position;
+            if (mStackFromBottom) {
+                position = lookForSelectablePosition(mItemCount - 1, false);
+            } else {
+                position = lookForSelectablePosition(0, true);
+            }
+            setSelectedPositionInt(position);
+            setNextSelectedPositionInt(position);
+            checkSelectionChanged();
+        } else {
+            checkFocus();            
+            // Nothing selected
+            checkSelectionChanged();
+        }
+
+        requestLayout();
+    }
+
+    @Override
+    int lookForSelectablePosition(int position, boolean lookDown) {
+        final ListAdapter adapter = mAdapter;
+        if (adapter == null || isInTouchMode()) {
+            return INVALID_POSITION;
+        }
+
+        if (position < 0 || position >= mItemCount) {
+            return INVALID_POSITION;
+        }
+        return position;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    void fillGap(boolean down) {
+        final int numColumns = mNumColumns;
+        final int verticalSpacing = mVerticalSpacing;
+
+        final int count = getChildCount();
+
+        if (down) {
+            final int startOffset = count > 0 ?
+                    getChildAt(count - 1).getBottom() + verticalSpacing : getListPaddingTop();
+            int position = mFirstPosition + count;
+            if (mStackFromBottom) {
+                position += numColumns - 1;
+            }
+            fillDown(position, startOffset);
+            correctTooHigh(numColumns, verticalSpacing, getChildCount());
+        } else {
+            final int startOffset = count > 0 ?
+                    getChildAt(0).getTop() - verticalSpacing : getHeight() - getListPaddingBottom();
+            int position = mFirstPosition;
+            if (!mStackFromBottom) {
+                position -= numColumns;
+            } else {
+                position--;
+            }
+            fillUp(position, startOffset);
+            correctTooLow(numColumns, verticalSpacing, getChildCount());
+        }
+    }
+
+    /**
+     * Fills the list from pos down to the end of the list view.
+     *
+     * @param pos The first position to put in the list
+     *
+     * @param nextTop The location where the top of the item associated with pos
+     *        should be drawn
+     *
+     * @return The view that is currently selected, if it happens to be in the
+     *         range that we draw.
+     */
+    private View fillDown(int pos, int nextTop) {
+        View selectedView = null;
+
+        final int end = (mBottom - mTop) - mListPadding.bottom;
+
+        while (nextTop < end && pos < mItemCount) {
+            View temp = makeRow(pos, nextTop, true);
+            if (temp != null) {
+                selectedView = temp;
+            }
+
+            nextTop = mReferenceView.getBottom() + mVerticalSpacing;
+
+            pos += mNumColumns;
+        }
+
+        return selectedView;
+    }
+
+    private View makeRow(int startPos, int y, boolean flow) {
+        final int columnWidth = mColumnWidth;
+        final int horizontalSpacing = mHorizontalSpacing;
+
+        int last;
+        int nextLeft = mListPadding.left + ((mStretchMode == STRETCH_SPACING_UNIFORM) ? horizontalSpacing : 0);
+
+        if (!mStackFromBottom) {
+            last = Math.min(startPos + mNumColumns, mItemCount);
+        } else {
+            last = startPos + 1;
+            startPos = Math.max(0, startPos - mNumColumns + 1);
+
+            if (last - startPos < mNumColumns) {
+                nextLeft += (mNumColumns - (last - startPos)) * (columnWidth + horizontalSpacing);
+            }
+        }
+
+        View selectedView = null;
+
+        final boolean hasFocus = shouldShowSelector();
+        final boolean inClick = touchModeDrawsInPressedState();
+        final int selectedPosition = mSelectedPosition;
+
+        mReferenceView = null;
+
+        for (int pos = startPos; pos < last; pos++) {
+            // is this the selected item?
+            boolean selected = pos == selectedPosition;
+            // does the list view have focus or contain focus
+
+            final int where = flow ? -1 : pos - startPos;
+            final View child = makeAndAddView(pos, y, flow, nextLeft, selected, where);
+            mReferenceView = child;
+
+            nextLeft += columnWidth;
+            if (pos < last - 1) {
+                nextLeft += horizontalSpacing;
+            }
+
+            if (selected && (hasFocus || inClick)) {
+                selectedView = child;
+            }
+        }
+
+        if (selectedView != null) {
+            mReferenceViewInSelectedRow = mReferenceView;
+        }
+
+        return selectedView;
+    }
+
+    /**
+     * Fills the list from pos up to the top of the list view.
+     *
+     * @param pos The first position to put in the list
+     *
+     * @param nextBottom The location where the bottom of the item associated
+     *        with pos should be drawn
+     *
+     * @return The view that is currently selected
+     */
+    private View fillUp(int pos, int nextBottom) {
+        View selectedView = null;
+
+        final int end = mListPadding.top;
+
+        while (nextBottom > end && pos >= 0) {
+
+            View temp = makeRow(pos, nextBottom, false);
+            if (temp != null) {
+                selectedView = temp;
+            }
+
+            nextBottom = mReferenceView.getTop() - mVerticalSpacing;
+
+            mFirstPosition = pos;
+
+            pos -= mNumColumns;
+        }
+
+        if (mStackFromBottom) {
+            mFirstPosition = Math.max(0, pos + 1);
+        }
+
+        return selectedView;
+    }
+
+    /**
+     * Fills the list from top to bottom, starting with mFirstPosition
+     *
+     * @param nextTop The location where the top of the first item should be
+     *        drawn
+     *
+     * @return The view that is currently selected
+     */
+    private View fillFromTop(int nextTop) {
+        mFirstPosition = Math.min(mFirstPosition, mSelectedPosition);
+        mFirstPosition = Math.min(mFirstPosition, mItemCount - 1);
+        if (mFirstPosition < 0) {
+            mFirstPosition = 0;
+        }
+        mFirstPosition -= mFirstPosition % mNumColumns;
+        return fillDown(mFirstPosition, nextTop);
+    }
+
+    private View fillFromBottom(int lastPosition, int nextBottom) {
+        lastPosition = Math.max(lastPosition, mSelectedPosition);
+        lastPosition = Math.min(lastPosition, mItemCount - 1);
+
+        final int invertedPosition = mItemCount - 1 - lastPosition;
+        lastPosition = mItemCount - 1 - (invertedPosition - (invertedPosition % mNumColumns));
+
+        return fillUp(lastPosition, nextBottom);
+    }
+
+    private View fillSelection(int childrenTop, int childrenBottom) {
+        final int selectedPosition = reconcileSelectedPosition();
+        final int numColumns = mNumColumns;
+        final int verticalSpacing = mVerticalSpacing;
+
+        int rowStart;
+        int rowEnd = -1;
+
+        if (!mStackFromBottom) {
+            rowStart = selectedPosition - (selectedPosition % numColumns);
+        } else {
+            final int invertedSelection = mItemCount - 1 - selectedPosition;
+
+            rowEnd = mItemCount - 1 - (invertedSelection - (invertedSelection % numColumns));
+            rowStart = Math.max(0, rowEnd - numColumns + 1);
+        }
+
+        final int fadingEdgeLength = getVerticalFadingEdgeLength();
+        final int topSelectionPixel = getTopSelectionPixel(childrenTop, fadingEdgeLength, rowStart);
+
+        final View sel = makeRow(mStackFromBottom ? rowEnd : rowStart, topSelectionPixel, true);
+        mFirstPosition = rowStart;
+
+        final View referenceView = mReferenceView;
+
+        if (!mStackFromBottom) {
+            fillDown(rowStart + numColumns, referenceView.getBottom() + verticalSpacing);
+            pinToBottom(childrenBottom);
+            fillUp(rowStart - numColumns, referenceView.getTop() - verticalSpacing);
+            adjustViewsUpOrDown();
+        } else {
+            final int bottomSelectionPixel = getBottomSelectionPixel(childrenBottom,
+                    fadingEdgeLength, numColumns, rowStart);
+            final int offset = bottomSelectionPixel - referenceView.getBottom();
+            offsetChildrenTopAndBottom(offset);
+            fillUp(rowStart - 1, referenceView.getTop() - verticalSpacing);
+            pinToTop(childrenTop);
+            fillDown(rowEnd + numColumns, referenceView.getBottom() + verticalSpacing);
+            adjustViewsUpOrDown();
+        }
+
+        return sel;
+    }
+
+    private void pinToTop(int childrenTop) {
+        if (mFirstPosition == 0) {
+            final int top = getChildAt(0).getTop();
+            final int offset = childrenTop - top;
+            if (offset < 0) {
+                offsetChildrenTopAndBottom(offset);
+            }
+        }
+    }
+
+    private void pinToBottom(int childrenBottom) {
+        final int count = getChildCount();
+        if (mFirstPosition + count == mItemCount) {
+            final int bottom = getChildAt(count - 1).getBottom();
+            final int offset = childrenBottom - bottom;
+            if (offset > 0) {
+                offsetChildrenTopAndBottom(offset);
+            }
+        }
+    }    
+
+    @Override
+    int findMotionRow(int y) {
+        final int childCount = getChildCount();
+        if (childCount > 0) {
+
+            final int numColumns = mNumColumns;
+            if (!mStackFromBottom) {
+                for (int i = 0; i < childCount; i += numColumns) {
+                    if (y <= getChildAt(i).getBottom()) {
+                        return mFirstPosition + i;
+                    }
+                }
+            } else {
+                for (int i = childCount - 1; i >= 0; i -= numColumns) {
+                    if (y >= getChildAt(i).getTop()) {
+                        return mFirstPosition + i;
+                    }
+                }
+            }
+
+            return mFirstPosition + childCount - 1;
+        }
+        return INVALID_POSITION;
+    }
+
+    /**
+     * Layout during a scroll that results from tracking motion events. Places
+     * the mMotionPosition view at the offset specified by mMotionViewTop, and
+     * then build surrounding views from there.
+     *
+     * @param position the position at which to start filling
+     * @param top the top of the view at that position
+     * @return The selected view, or null if the selected view is outside the
+     *         visible area.
+     */
+    private View fillSpecific(int position, int top) {
+        final int numColumns = mNumColumns;
+
+        int motionRowStart;
+        int motionRowEnd = -1;
+
+        if (!mStackFromBottom) {
+            motionRowStart = position - (position % numColumns);
+        } else {
+            final int invertedSelection = mItemCount - 1 - position;
+
+            motionRowEnd = mItemCount - 1 - (invertedSelection - (invertedSelection % numColumns));
+            motionRowStart = Math.max(0, motionRowEnd - numColumns + 1);
+        }
+
+        final View temp = makeRow(mStackFromBottom ? motionRowEnd : motionRowStart, top, true);
+
+        // Possibly changed again in fillUp if we add rows above this one.
+        mFirstPosition = motionRowStart;
+
+        final View referenceView = mReferenceView;
+        final int verticalSpacing = mVerticalSpacing;
+
+        View above;
+        View below;
+
+        if (!mStackFromBottom) {
+            above = fillUp(motionRowStart - numColumns, referenceView.getTop() - verticalSpacing);
+            adjustViewsUpOrDown();
+            below = fillDown(motionRowStart + numColumns, referenceView.getBottom() + verticalSpacing);
+            // Check if we have dragged the bottom of the grid too high
+            final int childCount = getChildCount();
+            if (childCount > 0) {
+                correctTooHigh(numColumns, verticalSpacing, childCount);
+            }
+        } else {
+            below = fillDown(motionRowEnd + numColumns, referenceView.getBottom() + verticalSpacing);
+            adjustViewsUpOrDown();
+            above = fillUp(motionRowStart - 1, referenceView.getTop() - verticalSpacing);
+            // Check if we have dragged the bottom of the grid too high
+            final int childCount = getChildCount();
+            if (childCount > 0) {
+                correctTooLow(numColumns, verticalSpacing, childCount);
+            }
+        }
+
+        if (temp != null) {
+            return temp;
+        } else if (above != null) {
+            return above;
+        } else {
+            return below;
+        }
+    }
+
+    private void correctTooHigh(int numColumns, int verticalSpacing, int childCount) {
+        // First see if the last item is visible
+        final int lastPosition = mFirstPosition + childCount - 1;
+        if (lastPosition == mItemCount - 1 && childCount > 0) {
+            // Get the last child ...
+            final View lastChild = getChildAt(childCount - 1);
+
+            // ... and its bottom edge
+            final int lastBottom = lastChild.getBottom();
+            // This is bottom of our drawable area
+            final int end = (mBottom - mTop) - mListPadding.bottom;
+
+            // This is how far the bottom edge of the last view is from the bottom of the
+            // drawable area
+            int bottomOffset = end - lastBottom;        
+
+            final View firstChild = getChildAt(0);
+            final int firstTop = firstChild.getTop();
+
+            // Make sure we are 1) Too high, and 2) Either there are more rows above the
+            // first row or the first row is scrolled off the top of the drawable area
+            if (bottomOffset > 0 && (mFirstPosition > 0 || firstTop < mListPadding.top))  {
+                if (mFirstPosition == 0) {
+                    // Don't pull the top too far down
+                    bottomOffset = Math.min(bottomOffset, mListPadding.top - firstTop);
+                }
+                
+                // Move everything down
+                offsetChildrenTopAndBottom(bottomOffset);
+                if (mFirstPosition > 0) {
+                    // Fill the gap that was opened above mFirstPosition with more rows, if
+                    // possible
+                    fillUp(mFirstPosition - (mStackFromBottom ? 1 : numColumns),
+                            firstChild.getTop() - verticalSpacing);
+                    // Close up the remaining gap
+                    adjustViewsUpOrDown();
+                }
+            }
+        }
+    }
+
+    private void correctTooLow(int numColumns, int verticalSpacing, int childCount) {
+        if (mFirstPosition == 0 && childCount > 0) {
+            // Get the first child ...
+            final View firstChild = getChildAt(0);
+
+            // ... and its top edge
+            final int firstTop = firstChild.getTop();
+
+            // This is top of our drawable area
+            final int start = mListPadding.top;
+
+            // This is bottom of our drawable area
+            final int end = (mBottom - mTop) - mListPadding.bottom;
+
+            // This is how far the top edge of the first view is from the top of the
+            // drawable area
+            int topOffset = firstTop - start;
+            final View lastChild = getChildAt(childCount - 1);
+            final int lastBottom = lastChild.getBottom();
+            final int lastPosition = mFirstPosition + childCount - 1;
+
+            // Make sure we are 1) Too low, and 2) Either there are more rows below the
+            // last row or the last row is scrolled off the bottom of the drawable area
+            if (topOffset > 0 && (lastPosition < mItemCount - 1 || lastBottom > end))  {
+                if (lastPosition == mItemCount - 1 ) {
+                    // Don't pull the bottom too far up
+                    topOffset = Math.min(topOffset, lastBottom - end);
+                }
+                
+                // Move everything up
+                offsetChildrenTopAndBottom(-topOffset);
+                if (lastPosition < mItemCount - 1) {
+                    // Fill the gap that was opened below the last position with more rows, if
+                    // possible
+                    fillDown(lastPosition + (!mStackFromBottom ? 1 : numColumns),
+                            lastChild.getBottom() + verticalSpacing);
+                    // Close up the remaining gap
+                    adjustViewsUpOrDown();
+                }
+            }
+        }
+    }
+
+    /**
+     * Fills the grid based on positioning the new selection at a specific
+     * location. The selection may be moved so that it does not intersect the
+     * faded edges. The grid is then filled upwards and downwards from there.
+     *
+     * @param selectedTop Where the selected item should be
+     * @param childrenTop Where to start drawing children
+     * @param childrenBottom Last pixel where children can be drawn
+     * @return The view that currently has selection
+     */
+    private View fillFromSelection(int selectedTop, int childrenTop, int childrenBottom) {
+        final int fadingEdgeLength = getVerticalFadingEdgeLength();
+        final int selectedPosition = mSelectedPosition;
+        final int numColumns = mNumColumns;
+        final int verticalSpacing = mVerticalSpacing;
+
+        int rowStart;
+        int rowEnd = -1;
+
+        if (!mStackFromBottom) {
+            rowStart = selectedPosition - (selectedPosition % numColumns);
+        } else {
+            int invertedSelection = mItemCount - 1 - selectedPosition;
+
+            rowEnd = mItemCount - 1 - (invertedSelection - (invertedSelection % numColumns));
+            rowStart = Math.max(0, rowEnd - numColumns + 1);
+        }
+
+        View sel;
+        View referenceView;
+
+        int topSelectionPixel = getTopSelectionPixel(childrenTop, fadingEdgeLength, rowStart);
+        int bottomSelectionPixel = getBottomSelectionPixel(childrenBottom, fadingEdgeLength,
+                numColumns, rowStart);
+
+        sel = makeRow(mStackFromBottom ? rowEnd : rowStart, selectedTop, true);
+        // Possibly changed again in fillUp if we add rows above this one.
+        mFirstPosition = rowStart;
+
+        referenceView = mReferenceView;
+        adjustForTopFadingEdge(referenceView, topSelectionPixel, bottomSelectionPixel);
+        adjustForBottomFadingEdge(referenceView, topSelectionPixel, bottomSelectionPixel);
+
+        if (!mStackFromBottom) {
+            fillUp(rowStart - numColumns, referenceView.getTop() - verticalSpacing);
+            adjustViewsUpOrDown();
+            fillDown(rowStart + numColumns, referenceView.getBottom() + verticalSpacing);
+        } else {
+            fillDown(rowEnd + numColumns, referenceView.getBottom() + verticalSpacing);
+            adjustViewsUpOrDown();
+            fillUp(rowStart - 1, referenceView.getTop() - verticalSpacing);
+        }
+
+
+        return sel;
+    }
+
+    /**
+     * Calculate the bottom-most pixel we can draw the selection into
+     *
+     * @param childrenBottom Bottom pixel were children can be drawn
+     * @param fadingEdgeLength Length of the fading edge in pixels, if present
+     * @param numColumns Number of columns in the grid
+     * @param rowStart The start of the row that will contain the selection
+     * @return The bottom-most pixel we can draw the selection into
+     */
+    private int getBottomSelectionPixel(int childrenBottom, int fadingEdgeLength,
+            int numColumns, int rowStart) {
+        // Last pixel we can draw the selection into
+        int bottomSelectionPixel = childrenBottom;
+        if (rowStart + numColumns - 1 < mItemCount - 1) {
+            bottomSelectionPixel -= fadingEdgeLength;
+        }
+        return bottomSelectionPixel;
+    }
+
+    /**
+     * Calculate the top-most pixel we can draw the selection into
+     *
+     * @param childrenTop Top pixel were children can be drawn
+     * @param fadingEdgeLength Length of the fading edge in pixels, if present
+     * @param rowStart The start of the row that will contain the selection
+     * @return The top-most pixel we can draw the selection into
+     */
+    private int getTopSelectionPixel(int childrenTop, int fadingEdgeLength, int rowStart) {
+        // first pixel we can draw the selection into
+        int topSelectionPixel = childrenTop;
+        if (rowStart > 0) {
+            topSelectionPixel += fadingEdgeLength;
+        }
+        return topSelectionPixel;
+    }
+
+    /**
+     * Move all views upwards so the selected row does not interesect the bottom
+     * fading edge (if necessary).
+     *
+     * @param childInSelectedRow A child in the row that contains the selection
+     * @param topSelectionPixel The topmost pixel we can draw the selection into
+     * @param bottomSelectionPixel The bottommost pixel we can draw the
+     *        selection into
+     */
+    private void adjustForBottomFadingEdge(View childInSelectedRow,
+            int topSelectionPixel, int bottomSelectionPixel) {
+        // Some of the newly selected item extends below the bottom of the
+        // list
+        if (childInSelectedRow.getBottom() > bottomSelectionPixel) {
+
+            // Find space available above the selection into which we can
+            // scroll upwards
+            int spaceAbove = childInSelectedRow.getTop() - topSelectionPixel;
+
+            // Find space required to bring the bottom of the selected item
+            // fully into view
+            int spaceBelow = childInSelectedRow.getBottom() - bottomSelectionPixel;
+            int offset = Math.min(spaceAbove, spaceBelow);
+
+            // Now offset the selected item to get it into view
+            offsetChildrenTopAndBottom(-offset);
+        }
+    }
+
+    /**
+     * Move all views upwards so the selected row does not interesect the top
+     * fading edge (if necessary).
+     *
+     * @param childInSelectedRow A child in the row that contains the selection
+     * @param topSelectionPixel The topmost pixel we can draw the selection into
+     * @param bottomSelectionPixel The bottommost pixel we can draw the
+     *        selection into
+     */
+    private void adjustForTopFadingEdge(View childInSelectedRow,
+            int topSelectionPixel, int bottomSelectionPixel) {
+        // Some of the newly selected item extends above the top of the list
+        if (childInSelectedRow.getTop() < topSelectionPixel) {
+            // Find space required to bring the top of the selected item
+            // fully into view
+            int spaceAbove = topSelectionPixel - childInSelectedRow.getTop();
+
+            // Find space available below the selection into which we can
+            // scroll downwards
+            int spaceBelow = bottomSelectionPixel - childInSelectedRow.getBottom();
+            int offset = Math.min(spaceAbove, spaceBelow);
+
+            // Now offset the selected item to get it into view
+            offsetChildrenTopAndBottom(offset);
+        }
+    }
+
+    /**
+     * Fills the grid based on positioning the new selection relative to the old
+     * selection. The new selection will be placed at, above, or below the
+     * location of the new selection depending on how the selection is moving.
+     * The selection will then be pinned to the visible part of the screen,
+     * excluding the edges that are faded. The grid is then filled upwards and
+     * downwards from there.
+     *
+     * @param delta Which way we are moving
+     * @param childrenTop Where to start drawing children
+     * @param childrenBottom Last pixel where children can be drawn
+     * @return The view that currently has selection
+     */
+    private View moveSelection(int delta, int childrenTop, int childrenBottom) {
+        final int fadingEdgeLength = getVerticalFadingEdgeLength();
+        final int selectedPosition = mSelectedPosition;
+        final int numColumns = mNumColumns;
+        final int verticalSpacing = mVerticalSpacing;
+
+        int oldRowStart;
+        int rowStart;
+        int rowEnd = -1;
+
+        if (!mStackFromBottom) {
+            oldRowStart = (selectedPosition - delta) - ((selectedPosition - delta) % numColumns);
+
+            rowStart = selectedPosition - (selectedPosition % numColumns);
+        } else {
+            int invertedSelection = mItemCount - 1 - selectedPosition;
+
+            rowEnd = mItemCount - 1 - (invertedSelection - (invertedSelection % numColumns));
+            rowStart = Math.max(0, rowEnd - numColumns + 1);
+
+            invertedSelection = mItemCount - 1 - (selectedPosition - delta);
+            oldRowStart = mItemCount - 1 - (invertedSelection - (invertedSelection % numColumns));
+            oldRowStart = Math.max(0, oldRowStart - numColumns + 1);
+        }
+
+        final int rowDelta = rowStart - oldRowStart;
+
+        final int topSelectionPixel = getTopSelectionPixel(childrenTop, fadingEdgeLength, rowStart);
+        final int bottomSelectionPixel = getBottomSelectionPixel(childrenBottom, fadingEdgeLength,
+                numColumns, rowStart);
+
+        // Possibly changed again in fillUp if we add rows above this one.
+        mFirstPosition = rowStart;
+
+        View sel;
+        View referenceView;
+
+        if (rowDelta > 0) {
+            /*
+             * Case 1: Scrolling down.
+             */
+
+            final int oldBottom = mReferenceViewInSelectedRow == null ? 0 :
+                    mReferenceViewInSelectedRow.getBottom();
+
+            sel = makeRow(mStackFromBottom ? rowEnd : rowStart, oldBottom + verticalSpacing, true);
+            referenceView = mReferenceView;
+
+            adjustForBottomFadingEdge(referenceView, topSelectionPixel, bottomSelectionPixel);
+        } else if (rowDelta < 0) {
+            /*
+             * Case 2: Scrolling up.
+             */
+            final int oldTop = mReferenceViewInSelectedRow == null ?
+                    0 : mReferenceViewInSelectedRow .getTop();
+
+            sel = makeRow(mStackFromBottom ? rowEnd : rowStart, oldTop - verticalSpacing, false);
+            referenceView = mReferenceView;
+
+            adjustForTopFadingEdge(referenceView, topSelectionPixel, bottomSelectionPixel);
+        } else {
+            /*
+             * Keep selection where it was
+             */
+            final int oldTop = mReferenceViewInSelectedRow == null ?
+                    0 : mReferenceViewInSelectedRow .getTop();
+
+            sel = makeRow(mStackFromBottom ? rowEnd : rowStart, oldTop, true);
+            referenceView = mReferenceView;
+        }
+
+        if (!mStackFromBottom) {
+            fillUp(rowStart - numColumns, referenceView.getTop() - verticalSpacing);
+            adjustViewsUpOrDown();
+            fillDown(rowStart + numColumns, referenceView.getBottom() + verticalSpacing);
+        } else {
+            fillDown(rowEnd + numColumns, referenceView.getBottom() + verticalSpacing);
+            adjustViewsUpOrDown();
+            fillUp(rowStart - 1, referenceView.getTop() - verticalSpacing);
+        }
+
+        return sel;
+    }
+
+    private void determineColumns(int availableSpace) {
+        final int requestedHorizontalSpacing = mRequestedHorizontalSpacing;
+        final int stretchMode = mStretchMode;
+        final int requestedColumnWidth = mRequestedColumnWidth;
+        
+        if (mRequestedNumColumns == AUTO_FIT) {
+            if (requestedColumnWidth > 0) {
+                // Client told us to pick the number of columns
+                mNumColumns = (availableSpace + requestedHorizontalSpacing) /
+                        (requestedColumnWidth + requestedHorizontalSpacing);
+            } else {
+                // Just make up a number if we don't have enough info
+                mNumColumns = 2;
+            }
+        } else {
+            // We picked the columns
+            mNumColumns = mRequestedNumColumns;
+        }
+        
+        if (mNumColumns <= 0) {
+            mNumColumns = 1;
+        }
+
+        switch (stretchMode) {
+        case NO_STRETCH:
+            // Nobody stretches
+            mColumnWidth = requestedColumnWidth;
+            mHorizontalSpacing = requestedHorizontalSpacing;
+            break;
+
+        default:
+            int spaceLeftOver = availableSpace - (mNumColumns * requestedColumnWidth) -
+                    ((mNumColumns - 1) * requestedHorizontalSpacing);
+            switch (stretchMode) {
+            case STRETCH_COLUMN_WIDTH:
+                // Stretch the columns
+                mColumnWidth = requestedColumnWidth + spaceLeftOver / mNumColumns;
+                mHorizontalSpacing = requestedHorizontalSpacing;
+                break;
+
+            case STRETCH_SPACING:
+                // Stretch the spacing between columns
+                mColumnWidth = requestedColumnWidth;
+                if (mNumColumns > 1) {
+                    mHorizontalSpacing = requestedHorizontalSpacing + 
+                        spaceLeftOver / (mNumColumns - 1);
+                } else {
+                    mHorizontalSpacing = requestedHorizontalSpacing + spaceLeftOver;
+                }
+                break;
+
+            case STRETCH_SPACING_UNIFORM:
+                // Stretch the spacing between columns
+                mColumnWidth = requestedColumnWidth;
+                if (mNumColumns > 1) {
+                    mHorizontalSpacing = requestedHorizontalSpacing + 
+                        spaceLeftOver / (mNumColumns + 1);
+                } else {
+                    mHorizontalSpacing = requestedHorizontalSpacing + spaceLeftOver;
+                }
+                break;
+            }
+
+            break;
+        }
+    }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        // Sets up mListPadding
+        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+
+        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
+        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
+        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
+        int heightSize = MeasureSpec.getSize(heightMeasureSpec);
+
+        if (widthMode == MeasureSpec.UNSPECIFIED) {
+            if (mColumnWidth > 0) {
+                widthSize = mColumnWidth + mListPadding.left + mListPadding.right;
+            } else {
+                widthSize = mListPadding.left + mListPadding.right;
+            }
+            widthSize += getVerticalScrollbarWidth();
+        }
+        
+        int childWidth = widthSize - mListPadding.left - mListPadding.right;
+        determineColumns(childWidth);
+
+        int childHeight = 0;
+
+        mItemCount = mAdapter == null ? 0 : mAdapter.getCount();
+        final int count = mItemCount;
+        if (count > 0) {
+            final View child = obtainView(0);
+
+            AbsListView.LayoutParams p = (AbsListView.LayoutParams)child.getLayoutParams();
+            if (p == null) {
+                p = new AbsListView.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT,
+                        ViewGroup.LayoutParams.WRAP_CONTENT, 0);
+            }
+            p.viewType = mAdapter.getItemViewType(0);
+
+            int childHeightSpec = getChildMeasureSpec(
+                    MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED), 0, p.height);
+            int childWidthSpec = getChildMeasureSpec(
+                    MeasureSpec.makeMeasureSpec(mColumnWidth, MeasureSpec.EXACTLY), 0, p.width);
+            child.measure(childWidthSpec, childHeightSpec);
+
+            childHeight = child.getMeasuredHeight();
+
+            if (mRecycler.shouldRecycleViewType(p.viewType)) {
+                mRecycler.addScrapView(child);
+            }
+        }
+        
+        if (heightMode == MeasureSpec.UNSPECIFIED) {
+            heightSize = mListPadding.top + mListPadding.bottom + childHeight +
+                    getVerticalFadingEdgeLength() * 2;
+        }
+
+        if (heightMode == MeasureSpec.AT_MOST) {
+            int ourSize =  mListPadding.top + mListPadding.bottom;
+           
+            final int numColumns = mNumColumns;
+            for (int i = 0; i < count; i += numColumns) {
+                ourSize += childHeight;
+                if (i + numColumns < count) {
+                    ourSize += mVerticalSpacing;
+                }
+                if (ourSize >= heightSize) {
+                    ourSize = heightSize;
+                    break;
+                }
+            }
+            heightSize = ourSize;
+        }
+
+        setMeasuredDimension(widthSize, heightSize);
+        mWidthMeasureSpec = widthMeasureSpec;
+    }
+
+    @Override
+    protected void attachLayoutAnimationParameters(View child,
+            ViewGroup.LayoutParams params, int index, int count) {
+
+        GridLayoutAnimationController.AnimationParameters animationParams =
+                (GridLayoutAnimationController.AnimationParameters) params.layoutAnimationParameters;
+
+        if (animationParams == null) {
+            animationParams = new GridLayoutAnimationController.AnimationParameters();
+            params.layoutAnimationParameters = animationParams;
+        }
+
+        animationParams.count = count;
+        animationParams.index = index;
+        animationParams.columnsCount = mNumColumns;
+        animationParams.rowsCount = count / mNumColumns;
+
+        if (!mStackFromBottom) {
+            animationParams.column = index % mNumColumns;
+            animationParams.row = index / mNumColumns;
+        } else {
+            final int invertedIndex = count - 1 - index;
+
+            animationParams.column = mNumColumns - 1 - (invertedIndex % mNumColumns);
+            animationParams.row = animationParams.rowsCount - 1 - invertedIndex / mNumColumns;
+        }
+    }
+
+    @Override
+    protected void layoutChildren() {
+        final boolean blockLayoutRequests = mBlockLayoutRequests;
+        if (!blockLayoutRequests) {
+            mBlockLayoutRequests = true;
+        }
+
+        try {
+            super.layoutChildren();
+
+            invalidate();
+
+            if (mAdapter == null) {
+                resetList();
+                invokeOnItemScrollListener();
+                return;
+            }
+
+            final int childrenTop = mListPadding.top;
+            final int childrenBottom = mBottom - mTop - mListPadding.bottom;
+
+            int childCount = getChildCount();
+            int index;
+            int delta = 0;
+
+            View sel;
+            View oldSel = null;
+            View oldFirst = null;
+            View newSel = null;
+
+            // Remember stuff we will need down below
+            switch (mLayoutMode) {
+            case LAYOUT_SET_SELECTION:
+                index = mNextSelectedPosition - mFirstPosition;
+                if (index >= 0 && index < childCount) {
+                    newSel = getChildAt(index);
+                }
+                break;
+            case LAYOUT_FORCE_TOP:
+            case LAYOUT_FORCE_BOTTOM:
+            case LAYOUT_SPECIFIC:
+            case LAYOUT_SYNC:
+                break;
+            case LAYOUT_MOVE_SELECTION:
+                if (mNextSelectedPosition >= 0) {
+                    delta = mNextSelectedPosition - mSelectedPosition;
+                }
+                break;
+            default:
+                // Remember the previously selected view
+                index = mSelectedPosition - mFirstPosition;
+                if (index >= 0 && index < childCount) {
+                    oldSel = getChildAt(index);
+                }
+
+                // Remember the previous first child
+                oldFirst = getChildAt(0);
+            }
+
+            boolean dataChanged = mDataChanged;
+            if (dataChanged) {
+                handleDataChanged();
+            }
+
+            // Handle the empty set by removing all views that are visible
+            // and calling it a day
+            if (mItemCount == 0) {
+                resetList();
+                invokeOnItemScrollListener();
+                return;
+            }
+
+            setSelectedPositionInt(mNextSelectedPosition);
+
+            // Pull all children into the RecycleBin.
+            // These views will be reused if possible
+            final int firstPosition = mFirstPosition;
+            final RecycleBin recycleBin = mRecycler;
+
+            if (dataChanged) {
+                for (int i = 0; i < childCount; i++) {
+                    recycleBin.addScrapView(getChildAt(i));
+                }
+            } else {
+                recycleBin.fillActiveViews(childCount, firstPosition);
+            }
+
+            // Clear out old views
+            //removeAllViewsInLayout();
+            detachAllViewsFromParent();
+
+            switch (mLayoutMode) {
+            case LAYOUT_SET_SELECTION:
+                if (newSel != null) {
+                    sel = fillFromSelection(newSel.getTop(), childrenTop, childrenBottom);
+                } else {
+                    sel = fillSelection(childrenTop, childrenBottom);
+                }
+                break;
+            case LAYOUT_FORCE_TOP:
+                mFirstPosition = 0;
+                sel = fillFromTop(childrenTop);
+                adjustViewsUpOrDown();
+                break;
+            case LAYOUT_FORCE_BOTTOM:
+                sel = fillUp(mItemCount - 1, childrenBottom);
+                adjustViewsUpOrDown();
+                break;
+            case LAYOUT_SPECIFIC:
+                sel = fillSpecific(mSelectedPosition, mSpecificTop);
+                break;
+            case LAYOUT_SYNC:
+                sel = fillSpecific(mSyncPosition, mSpecificTop);
+                break;
+            case LAYOUT_MOVE_SELECTION:
+                // Move the selection relative to its old position
+                sel = moveSelection(delta, childrenTop, childrenBottom);
+                break;
+            default:
+                if (childCount == 0) {
+                    if (!mStackFromBottom) {
+                        setSelectedPositionInt(0);
+                        sel = fillFromTop(childrenTop);
+                    } else {
+                        final int last = mItemCount - 1;
+                        setSelectedPositionInt(last);
+                        sel = fillFromBottom(last, childrenBottom);
+                    }
+                } else {
+                    if (mSelectedPosition >= 0 && mSelectedPosition < mItemCount) {
+                        sel = fillSpecific(mSelectedPosition, oldSel == null ?
+                                childrenTop : oldSel.getTop());
+                    } else if (mFirstPosition < mItemCount)  {
+                        sel = fillSpecific(mFirstPosition, oldFirst == null ?
+                                childrenTop : oldFirst.getTop());
+                    } else {
+                        sel = fillSpecific(0, childrenTop);
+                    }
+                }
+                break;
+            }
+
+            // Flush any cached views that did not get reused above
+            recycleBin.scrapActiveViews();
+
+            if (sel != null) {
+               positionSelector(sel);
+               mSelectedTop = sel.getTop();
+            } else {
+               mSelectedTop = 0;
+               mSelectorRect.setEmpty();
+            }
+
+            mLayoutMode = LAYOUT_NORMAL;
+            mDataChanged = false;
+            mNeedSync = false;
+            setNextSelectedPositionInt(mSelectedPosition);
+
+            updateScrollIndicators();
+
+            if (mItemCount > 0) {
+                checkSelectionChanged();
+            }
+
+            invokeOnItemScrollListener();
+        } finally {
+            if (!blockLayoutRequests) {
+                mBlockLayoutRequests = false;
+            }
+        }
+    }
+
+
+    /**
+     * Obtain the view and add it to our list of children. The view can be made
+     * fresh, converted from an unused view, or used as is if it was in the
+     * recycle bin.
+     *
+     * @param position Logical position in the list
+     * @param y Top or bottom edge of the view to add
+     * @param flow if true, align top edge to y. If false, align bottom edge to
+     *        y.
+     * @param childrenLeft Left edge where children should be positioned
+     * @param selected Is this position selected?
+     * @param where to add new item in the list
+     * @return View that was added
+     */
+    private View makeAndAddView(int position, int y, boolean flow, int childrenLeft,
+            boolean selected, int where) {
+        View child;
+
+        if (!mDataChanged) {
+            // Try to use an exsiting view for this position
+            child = mRecycler.getActiveView(position);
+            if (child != null) {
+                // Found it -- we're using an existing child
+                // This just needs to be positioned
+                setupChild(child, position, y, flow, childrenLeft, selected, true, where);
+                return child;
+            }
+        }
+
+        // Make a new view for this position, or convert an unused view if
+        // possible
+        child = obtainView(position);
+
+        // This needs to be positioned and measured
+        setupChild(child, position, y, flow, childrenLeft, selected, false, where);
+
+        return child;
+    }
+
+    /**
+     * Add a view as a child and make sure it is measured (if necessary) and
+     * positioned properly.
+     *
+     * @param child The view to add
+     * @param position The position of the view
+     * @param y The y position relative to which this view will be positioned
+     * @param flow if true, align top edge to y. If false, align bottom edge
+     *        to y.
+     * @param childrenLeft Left edge where children should be positioned
+     * @param selected Is this position selected?
+     * @param recycled Has this view been pulled from the recycle bin? If so it
+     *        does not need to be remeasured.
+     * @param where Where to add the item in the list
+     *
+     */
+    private void setupChild(View child, int position, int y, boolean flow, int childrenLeft,
+            boolean selected, boolean recycled, int where) {
+        boolean isSelected = selected && shouldShowSelector();
+
+        final boolean updateChildSelected = isSelected != child.isSelected();
+        boolean needToMeasure = !recycled || updateChildSelected || child.isLayoutRequested();
+
+        // Respect layout params that are already in the view. Otherwise make
+        // some up...
+        AbsListView.LayoutParams p = (AbsListView.LayoutParams)child.getLayoutParams();
+        if (p == null) {
+            p = new AbsListView.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT,
+                    ViewGroup.LayoutParams.WRAP_CONTENT, 0);
+        }
+        p.viewType = mAdapter.getItemViewType(position);
+
+        if (recycled) {
+            attachViewToParent(child, where, p);
+        } else {
+            addViewInLayout(child, where, p, true);
+        }
+
+        if (updateChildSelected) {
+            child.setSelected(isSelected);
+            if (isSelected) {
+                requestFocus();
+            }
+        }
+
+        if (needToMeasure) {
+            int childHeightSpec = ViewGroup.getChildMeasureSpec(
+                    MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED), 0, p.height);
+
+            int childWidthSpec = ViewGroup.getChildMeasureSpec(
+                    MeasureSpec.makeMeasureSpec(mColumnWidth, MeasureSpec.EXACTLY), 0, p.width);
+            child.measure(childWidthSpec, childHeightSpec);
+        } else {
+            cleanupLayoutState(child);
+        }
+
+        final int w = child.getMeasuredWidth();
+        final int h = child.getMeasuredHeight();
+
+        int childLeft;
+        final int childTop = flow ? y : y - h;
+
+        switch (mGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
+        case Gravity.LEFT:
+            childLeft = childrenLeft;
+            break;
+        case Gravity.CENTER_HORIZONTAL:
+            childLeft = childrenLeft + ((mColumnWidth - w) / 2);
+            break;
+        case Gravity.RIGHT:
+            childLeft = childrenLeft + mColumnWidth - w;
+            break;
+        default:
+            childLeft = childrenLeft;
+            break;
+        }
+
+        if (needToMeasure) {
+            final int childRight = childLeft + w;
+            final int childBottom = childTop + h;
+            child.layout(childLeft, childTop, childRight, childBottom);
+        } else {
+            child.offsetLeftAndRight(childLeft - child.getLeft());
+            child.offsetTopAndBottom(childTop - child.getTop());
+        }
+
+        if (mCachingStarted) {
+            child.setDrawingCacheEnabled(true);
+        }
+    }
+
+    /**
+     * Sets the currently selected item
+     * 
+     * @param position Index (starting at 0) of the data item to be selected.
+     * 
+     * If in touch mode, the item will not be selected but it will still be positioned
+     * appropriately.
+     */
+    @Override
+    public void setSelection(int position) {
+        if (!isInTouchMode()) {
+            setNextSelectedPositionInt(position);
+        } else {
+            mResurrectToPosition = position;
+        }
+        mLayoutMode = LAYOUT_SET_SELECTION;
+        requestLayout();
+    }
+
+    /**
+     * Makes the item at the supplied position selected.
+     *
+     * @param position the position of the new selection
+     */
+    @Override
+    void setSelectionInt(int position) {
+        mBlockLayoutRequests = true;
+        setNextSelectedPositionInt(position);
+        layoutChildren();
+
+        mBlockLayoutRequests = false;
+    }
+
+    @Override
+    public boolean onKeyDown(int keyCode, KeyEvent event) {
+        return commonKey(keyCode, 1, event);
+    }
+
+    @Override
+    public boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event) {
+        return commonKey(keyCode, repeatCount, event);
+    }
+
+    @Override
+    public boolean onKeyUp(int keyCode, KeyEvent event) {
+        return commonKey(keyCode, 1, event);
+    }
+
+    private boolean commonKey(int keyCode, int count, KeyEvent event) {
+        if (mAdapter == null) {
+            return false;
+        }
+
+        if (mDataChanged) {
+            layoutChildren();
+        }
+
+        boolean handled = false;
+        int action = event.getAction();
+
+        if (action != KeyEvent.ACTION_UP) {
+            if (mSelectedPosition < 0) {
+                switch (keyCode) {
+                    case KeyEvent.KEYCODE_DPAD_UP:
+                    case KeyEvent.KEYCODE_DPAD_DOWN:
+                    case KeyEvent.KEYCODE_DPAD_LEFT:
+                    case KeyEvent.KEYCODE_DPAD_RIGHT:
+                    case KeyEvent.KEYCODE_DPAD_CENTER:
+                    case KeyEvent.KEYCODE_SPACE:
+                    case KeyEvent.KEYCODE_ENTER:
+                        resurrectSelection();
+                        return true;
+                }
+            }
+
+            switch (keyCode) {
+                case KeyEvent.KEYCODE_DPAD_LEFT:
+                    handled = arrowScroll(FOCUS_LEFT);
+                    break;
+
+                case KeyEvent.KEYCODE_DPAD_RIGHT:
+                    handled = arrowScroll(FOCUS_RIGHT);
+                    break;
+
+                case KeyEvent.KEYCODE_DPAD_UP:
+                    if (!event.isAltPressed()) {
+                        handled = arrowScroll(FOCUS_UP);
+
+                    } else {
+                        handled = fullScroll(FOCUS_UP);
+                    }
+                    break;
+
+                case KeyEvent.KEYCODE_DPAD_DOWN:
+                    if (!event.isAltPressed()) {
+                        handled = arrowScroll(FOCUS_DOWN);
+                    } else {
+                        handled = fullScroll(FOCUS_DOWN);
+                    }
+                    break;
+
+                case KeyEvent.KEYCODE_DPAD_CENTER:
+                case KeyEvent.KEYCODE_ENTER: {
+                    if (getChildCount() > 0 && event.getRepeatCount() == 0) {
+                        keyPressed();
+                    }
+
+                    return true;
+                }
+
+                case KeyEvent.KEYCODE_SPACE:
+                    if (mPopup == null || !mPopup.isShowing()) {
+                        if (!event.isShiftPressed()) {
+                            handled = pageScroll(FOCUS_DOWN);
+                        } else {
+                            handled = pageScroll(FOCUS_UP);
+                        }
+                    }
+                    break;
+            }
+        }
+
+        if (!handled) {
+            handled = sendToTextFilter(keyCode, count, event);
+        }
+
+        if (handled) {
+            return true;
+        } else {
+            switch (action) {
+                case KeyEvent.ACTION_DOWN:
+                    return super.onKeyDown(keyCode, event);
+                case KeyEvent.ACTION_UP:
+                    return super.onKeyUp(keyCode, event);
+                case KeyEvent.ACTION_MULTIPLE:
+                    return super.onKeyMultiple(keyCode, count, event);
+                default:
+                    return false;
+            }
+        }
+    }
+
+    /**
+     * Scrolls up or down by the number of items currently present on screen.
+     *
+     * @param direction either {@link View#FOCUS_UP} or {@link View#FOCUS_DOWN}
+     * @return whether selection was moved
+     */
+    boolean pageScroll(int direction) {
+        int nextPage = -1;
+
+        if (direction == FOCUS_UP) {
+            nextPage = Math.max(0, mSelectedPosition - getChildCount() - 1);
+        } else if (direction == FOCUS_DOWN) {
+            nextPage = Math.min(mItemCount - 1, mSelectedPosition + getChildCount() - 1);
+        }
+
+        if (nextPage >= 0) {
+            setSelectionInt(nextPage);
+            invokeOnItemScrollListener();
+            return true;
+        }
+
+        return false;
+    }
+
+    /**
+     * Go to the last or first item if possible.
+     *
+     * @param direction either {@link View#FOCUS_UP} or {@link View#FOCUS_DOWN}.
+     *
+     * @return Whether selection was moved.
+     */
+    boolean fullScroll(int direction) {
+        boolean moved = false;
+        if (direction == FOCUS_UP) {
+            mLayoutMode = LAYOUT_SET_SELECTION;
+            setSelectionInt(0);
+            invokeOnItemScrollListener();
+            moved = true;
+        } else if (direction == FOCUS_DOWN) {
+            mLayoutMode = LAYOUT_SET_SELECTION;
+            setSelectionInt(mItemCount - 1);
+            invokeOnItemScrollListener();
+            moved = true;
+        }
+
+        return moved;
+    }
+
+    /**
+     * Scrolls to the next or previous item, horizontally or vertically.
+     *
+     * @param direction either {@link View#FOCUS_LEFT}, {@link View#FOCUS_RIGHT},
+     *        {@link View#FOCUS_UP} or {@link View#FOCUS_DOWN}
+     *
+     * @return whether selection was moved
+     */
+    boolean arrowScroll(int direction) {
+        final int selectedPosition = mSelectedPosition;
+        final int numColumns = mNumColumns;
+
+        int startOfRowPos;
+        int endOfRowPos;
+
+        boolean moved = false;
+
+        if (!mStackFromBottom) {
+            startOfRowPos = (selectedPosition / numColumns) * numColumns;
+            endOfRowPos = Math.min(startOfRowPos + numColumns - 1, mItemCount - 1);
+        } else {
+            final int invertedSelection = mItemCount - 1 - selectedPosition;
+            endOfRowPos = mItemCount - 1 - (invertedSelection / numColumns) * numColumns;
+            startOfRowPos = Math.max(0, endOfRowPos - numColumns + 1);
+        }
+
+        switch (direction) {
+            case FOCUS_UP:
+                if (startOfRowPos > 0) {
+                    mLayoutMode = LAYOUT_MOVE_SELECTION;
+                    setSelectionInt(Math.max(0, selectedPosition - numColumns));
+                    moved = true;
+                }
+                break;
+            case FOCUS_DOWN:
+                if (endOfRowPos < mItemCount - 1) {
+                    mLayoutMode = LAYOUT_MOVE_SELECTION;
+                    setSelectionInt(Math.min(selectedPosition + numColumns, mItemCount - 1));
+                    moved = true;
+                }
+                break;
+            case FOCUS_LEFT:
+                if (selectedPosition > startOfRowPos) {
+                    mLayoutMode = LAYOUT_MOVE_SELECTION;
+                    setSelectionInt(selectedPosition - 1);
+                    moved = true;
+                }
+                break;
+            case FOCUS_RIGHT:
+                if (selectedPosition < endOfRowPos) {
+                    mLayoutMode = LAYOUT_MOVE_SELECTION;
+                    setSelectionInt(selectedPosition + 1);
+                    moved = true;
+                }
+                break;
+        }
+
+        if (moved) {
+            playSoundEffect(SoundEffectConstants.getContantForFocusDirection(direction));
+            invokeOnItemScrollListener();
+        }
+
+        return moved;
+    }
+
+    @Override
+    protected void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) {
+        super.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
+
+        int closestChildIndex = -1;
+        if (gainFocus && previouslyFocusedRect != null) {
+            previouslyFocusedRect.offset(mScrollX, mScrollY);
+
+            // figure out which item should be selected based on previously
+            // focused rect
+            Rect otherRect = mTempRect;
+            int minDistance = Integer.MAX_VALUE;
+            final int childCount = getChildCount();
+            for (int i = 0; i < childCount; i++) {
+                // only consider view's on appropriate edge of grid
+                if (!isCandidateSelection(i, direction)) {
+                    continue;
+                }
+
+                final View other = getChildAt(i);
+                other.getDrawingRect(otherRect);
+                offsetDescendantRectToMyCoords(other, otherRect);
+                int distance = getDistance(previouslyFocusedRect, otherRect, direction);
+
+                if (distance < minDistance) {
+                    minDistance = distance;
+                    closestChildIndex = i;
+                }
+            }
+        }
+
+        if (closestChildIndex >= 0) {
+            setSelection(closestChildIndex + mFirstPosition);
+        } else {
+            requestLayout();
+        }
+    }
+
+    /**
+     * Is childIndex a candidate for next focus given the direction the focus
+     * change is coming from?
+     * @param childIndex The index to check.
+     * @param direction The direction, one of
+     *        {FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, FOCUS_RIGHT}
+     * @return Whether childIndex is a candidate.
+     */
+    private boolean isCandidateSelection(int childIndex, int direction) {
+        final int count = getChildCount();
+        final int invertedIndex = count - 1 - childIndex;
+
+        int rowStart;
+        int rowEnd;
+
+        if (!mStackFromBottom) {
+            rowStart = childIndex - (childIndex % mNumColumns);
+            rowEnd = Math.max(rowStart + mNumColumns - 1, count);
+        } else {
+            rowEnd = count - 1 - (invertedIndex - (invertedIndex % mNumColumns));
+            rowStart = Math.max(0, rowEnd - mNumColumns + 1);
+        }
+
+        switch (direction) {
+            case View.FOCUS_RIGHT:
+                // coming from left, selection is only valid if it is on left
+                // edge
+                return childIndex == rowStart;
+            case View.FOCUS_DOWN:
+                // coming from top; only valid if in top row
+                return rowStart == 0;
+            case View.FOCUS_LEFT:
+                // coming from right, must be on right edge
+                return childIndex == rowEnd;
+            case View.FOCUS_UP:
+                // coming from bottom, need to be in last row
+                return rowEnd == count - 1;
+            default:
+                throw new IllegalArgumentException("direction must be one of "
+                        + "{FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, FOCUS_RIGHT}.");
+        }
+    }
+
+    /**
+     * Describes how the child views are horizontally aligned. Defaults to Gravity.LEFT
+     *
+     * @param gravity the gravity to apply to this grid's children
+     *
+     * @attr ref android.R.styleable#GridView_gravity
+     */
+    public void setGravity(int gravity) {
+        if (mGravity != gravity) {
+            mGravity = gravity;
+            requestLayoutIfNecessary();
+        }
+    }
+
+    /**
+     * Set the amount of horizontal (x) spacing to place between each item
+     * in the grid.
+     *
+     * @param horizontalSpacing The amount of horizontal space between items,
+     * in pixels.
+     *
+     * @attr ref android.R.styleable#GridView_horizontalSpacing
+     */
+    public void setHorizontalSpacing(int horizontalSpacing) {
+        if (horizontalSpacing != mRequestedHorizontalSpacing) {
+            mRequestedHorizontalSpacing = horizontalSpacing;
+            requestLayoutIfNecessary();
+        }
+    }
+
+
+    /**
+     * Set the amount of vertical (y) spacing to place between each item
+     * in the grid.
+     *
+     * @param verticalSpacing The amount of vertical space between items,
+     * in pixels.
+     *
+     * @attr ref android.R.styleable#GridView_verticalSpacing
+     */
+    public void setVerticalSpacing(int verticalSpacing) {
+        if (verticalSpacing != mVerticalSpacing) {
+            mVerticalSpacing = verticalSpacing;
+            requestLayoutIfNecessary();
+        }
+    }
+
+    /**
+     * Control how items are stretched to fill their space.
+     *
+     * @param stretchMode Either {@link #NO_STRETCH},
+     * {@link #STRETCH_SPACING}, {@link #STRETCH_SPACING_UNIFORM}, or {@link #STRETCH_COLUMN_WIDTH}.
+     *
+     * @attr ref android.R.styleable#GridView_stretchMode
+     */
+    public void setStretchMode(int stretchMode) {
+        if (stretchMode != mStretchMode) {
+            mStretchMode = stretchMode;
+            requestLayoutIfNecessary();
+        }
+    }
+
+    public int getStretchMode() {
+        return mStretchMode;
+    }
+
+    /**
+     * Set the width of columns in the grid.
+     *
+     * @param columnWidth The column width, in pixels.
+     *
+     * @attr ref android.R.styleable#GridView_columnWidth
+     */
+    public void setColumnWidth(int columnWidth) {
+        if (columnWidth != mRequestedColumnWidth) {
+            mRequestedColumnWidth = columnWidth;
+            requestLayoutIfNecessary();
+        }
+    }
+
+    /**
+     * Set the number of columns in the grid
+     *
+     * @param numColumns The desired number of columns.
+     *
+     * @attr ref android.R.styleable#GridView_numColumns
+     */
+    public void setNumColumns(int numColumns) {
+        if (numColumns != mRequestedNumColumns) {
+            mRequestedNumColumns = numColumns;
+            requestLayoutIfNecessary();
+        }
+    }
+
+    /**
+     * Make sure views are touching the top or bottom edge, as appropriate for
+     * our gravity
+     */
+    private void adjustViewsUpOrDown() {
+        final int childCount = getChildCount();
+
+        if (childCount > 0) {
+            int delta;
+            View child;
+
+            if (!mStackFromBottom) {
+                // Uh-oh -- we came up short. Slide all views up to make them
+                // align with the top
+                child = getChildAt(0);
+                delta = child.getTop() - mListPadding.top;
+                if (mFirstPosition != 0) {
+                    // It's OK to have some space above the first item if it is
+                    // part of the vertical spacing
+                    delta -= mVerticalSpacing;
+                }
+                if (delta < 0) {
+                    // We only are looking to see if we are too low, not too high
+                    delta = 0;
+                }
+            } else {
+                // we are too high, slide all views down to align with bottom
+                child = getChildAt(childCount - 1);
+                delta = child.getBottom() - (getHeight() - mListPadding.bottom);
+                
+                if (mFirstPosition + childCount < mItemCount) {
+                    // It's OK to have some space below the last item if it is
+                    // part of the vertical spacing
+                    delta += mVerticalSpacing;
+                }
+                
+                if (delta > 0) {
+                    // We only are looking to see if we are too high, not too low
+                    delta = 0;
+                }
+            }
+
+            if (delta != 0) {
+                offsetChildrenTopAndBottom(-delta);
+            }
+        }
+    }
+    
+    @Override
+    protected int computeVerticalScrollExtent() {
+        final int count = getChildCount();
+        if (count > 0) {
+            final int numColumns = mNumColumns;
+            final int rowCount = (count + numColumns - 1) / numColumns;
+            
+            int extent = rowCount * 100;
+
+            View view = getChildAt(0);
+            final int top = view.getTop();
+            int height = view.getHeight();
+            if (height > 0) {
+                extent += (top * 100) / height;
+            }
+
+            view = getChildAt(count - 1);
+            final int bottom = view.getBottom();
+            height = view.getHeight();
+            if (height > 0) {
+                extent -= ((bottom - getHeight()) * 100) / height;
+            }
+
+            return extent;
+        }
+        return 0;
+    }
+
+    @Override
+    protected int computeVerticalScrollOffset() {
+        if (mFirstPosition >= 0 && getChildCount() > 0) {
+            final View view = getChildAt(0);
+            final int top = view.getTop();
+            int height = view.getHeight();
+            if (height > 0) {
+                final int whichRow = mFirstPosition / mNumColumns;
+                return Math.max(whichRow * 100 - (top * 100) / height, 0);
+            }
+        }
+        return 0;
+    }
+
+    @Override
+    protected int computeVerticalScrollRange() {
+        // TODO: Account for vertical spacing too
+        final int numColumns = mNumColumns;
+        final int rowCount = (mItemCount + numColumns - 1) / numColumns;
+        return Math.max(rowCount * 100, 0);
+    }
+}
+
diff --git a/core/java/android/widget/HeaderViewListAdapter.java b/core/java/android/widget/HeaderViewListAdapter.java
new file mode 100644
index 0000000..b0e5f7e
--- /dev/null
+++ b/core/java/android/widget/HeaderViewListAdapter.java
@@ -0,0 +1,241 @@
+/*
+ * 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 android.widget;
+
+import android.database.DataSetObserver;
+import android.view.View;
+import android.view.ViewGroup;
+
+import java.util.ArrayList;
+
+/**
+ * ListAdapter used when a ListView has header views. This ListAdapter
+ * wraps another one and also keeps track of the header views and their
+ * associated data objects.
+ *<p>This is intended as a base class; you will probably not need to
+ * use this class directly in your own code.
+ *
+ */
+public class HeaderViewListAdapter implements WrapperListAdapter, Filterable {
+
+    private ListAdapter mAdapter;
+
+    ArrayList<ListView.FixedViewInfo> mHeaderViewInfos;
+    ArrayList<ListView.FixedViewInfo> mFooterViewInfos;
+    boolean mAreAllFixedViewsSelectable;
+
+    private boolean mIsFilterable;
+
+    public HeaderViewListAdapter(ArrayList<ListView.FixedViewInfo> headerViewInfos,
+                                 ArrayList<ListView.FixedViewInfo> footerViewInfos,
+                                 ListAdapter adapter) {
+        mAdapter = adapter;
+        mIsFilterable = adapter instanceof Filterable;
+
+        mHeaderViewInfos = headerViewInfos;
+        mFooterViewInfos = footerViewInfos;
+
+        mAreAllFixedViewsSelectable =
+                areAllListInfosSelectable(mHeaderViewInfos)
+                && areAllListInfosSelectable(mFooterViewInfos);
+    }
+
+    public int getHeadersCount() {
+        return mHeaderViewInfos == null ? 0 : mHeaderViewInfos.size();
+    }
+
+    public int getFootersCount() {
+        return mFooterViewInfos == null ? 0 : mFooterViewInfos.size();
+    }
+
+    public boolean isEmpty() {
+        return mAdapter == null || mAdapter.isEmpty();
+    }
+
+    private boolean areAllListInfosSelectable(ArrayList<ListView.FixedViewInfo> infos) {
+        if (infos != null) {
+            for (ListView.FixedViewInfo info : infos) {
+                if (!info.isSelectable) {
+                    return false;
+                }
+            }
+        }
+        return true;
+    }
+
+    public boolean removeHeader(View v) {
+        for (int i = 0; i < mHeaderViewInfos.size(); i++) {
+            ListView.FixedViewInfo info = mHeaderViewInfos.get(i);
+            if (info.view == v) {
+                mHeaderViewInfos.remove(i);
+
+                mAreAllFixedViewsSelectable =
+                        areAllListInfosSelectable(mHeaderViewInfos)
+                        && areAllListInfosSelectable(mFooterViewInfos);
+
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    public boolean removeFooter(View v) {
+        for (int i = 0; i < mFooterViewInfos.size(); i++) {
+            ListView.FixedViewInfo info = mFooterViewInfos.get(i);
+            if (info.view == v) {
+                mFooterViewInfos.remove(i);
+
+                mAreAllFixedViewsSelectable =
+                        areAllListInfosSelectable(mHeaderViewInfos)
+                        && areAllListInfosSelectable(mFooterViewInfos);
+
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    public int getCount() {
+        if (mAdapter != null) {
+            return getFootersCount() + getHeadersCount() + mAdapter.getCount();
+        } else {
+            return getFootersCount() + getHeadersCount();
+        }
+    }
+
+    public boolean areAllItemsEnabled() {
+        if (mAdapter != null) {
+            return mAreAllFixedViewsSelectable && mAdapter.areAllItemsEnabled();
+        } else {
+            return true;
+        }
+    }
+
+    public boolean isEnabled(int position) {
+        int numHeaders = getHeadersCount();
+        if (mAdapter != null && position >= numHeaders) {
+            int adjPosition = position - numHeaders;
+            int adapterCount = mAdapter.getCount();
+            if (adjPosition >= adapterCount && mFooterViewInfos != null) {
+                return mFooterViewInfos.get(adjPosition - adapterCount).isSelectable;
+            } else {
+                return mAdapter.isEnabled(adjPosition);
+            }
+        } else if (position < numHeaders && mHeaderViewInfos != null) {
+            return mHeaderViewInfos.get(position).isSelectable;
+        }
+        return true;
+    }
+
+    public Object getItem(int position) {
+        int numHeaders = getHeadersCount();
+        if (mAdapter != null && position >= numHeaders) {
+            int adjPosition = position - numHeaders;
+            int adapterCount = mAdapter.getCount();
+            if (adjPosition >= adapterCount && mFooterViewInfos != null) {
+                return mFooterViewInfos.get(adjPosition - adapterCount).data;
+            } else {
+                return mAdapter.getItem(adjPosition);
+            }
+        } else if (position < numHeaders && mHeaderViewInfos != null) {
+            return mHeaderViewInfos.get(position).data;
+        }
+        return null;
+    }
+
+    public long getItemId(int position) {
+        int numHeaders = getHeadersCount();
+        if (mAdapter != null && position >= numHeaders) {
+            int adjPosition = position - numHeaders;
+            int adapterCnt = mAdapter.getCount();
+            if (adjPosition < adapterCnt) {
+                return mAdapter.getItemId(adjPosition);
+            }
+        }
+        return -1;
+    }
+
+    public boolean hasStableIds() {
+        if (mAdapter != null) {
+            return mAdapter.hasStableIds();
+        }
+        return false;
+    }
+
+    public View getView(int position, View convertView, ViewGroup parent) {
+        int numHeaders = getHeadersCount();
+        if (mAdapter != null && position >= numHeaders) {
+            int adjPosition = position - numHeaders;
+            int adapterCount = mAdapter.getCount();
+            if (adjPosition >= adapterCount) {
+                if (mFooterViewInfos != null) {
+                    return mFooterViewInfos.get(adjPosition - adapterCount).view;
+                }
+            } else {
+                return mAdapter.getView(adjPosition, convertView, parent);
+            }
+        } else if (position < numHeaders) {
+            return mHeaderViewInfos.get(position).view;
+        }
+        return null;
+    }
+
+    public int getItemViewType(int position) {
+        int numHeaders = getHeadersCount();
+        if (mAdapter != null && position >= numHeaders) {
+            int adjPosition = position - numHeaders;
+            int adapterCount = mAdapter.getCount();
+            if (adjPosition < adapterCount) {
+                return mAdapter.getItemViewType(adjPosition);
+            }
+        }
+
+        return AdapterView.ITEM_VIEW_TYPE_HEADER_OR_FOOTER;
+    }
+
+    public int getViewTypeCount() {
+        if (mAdapter != null) {
+            return mAdapter.getViewTypeCount();
+        }
+        return 1;
+    }
+
+    public void registerDataSetObserver(DataSetObserver observer) {
+        if (mAdapter != null) {
+            mAdapter.registerDataSetObserver(observer);
+        }
+    }
+
+    public void unregisterDataSetObserver(DataSetObserver observer) {
+        if (mAdapter != null) {
+            mAdapter.unregisterDataSetObserver(observer);
+        }
+    }
+
+    public Filter getFilter() {
+        if (mIsFilterable) {
+            return ((Filterable) mAdapter).getFilter();
+        }
+        return null;
+    }
+    
+    public ListAdapter getWrappedAdapter() {
+        return mAdapter;
+    }
+}
diff --git a/core/java/android/widget/HorizontalScrollView.java b/core/java/android/widget/HorizontalScrollView.java
new file mode 100644
index 0000000..652e30c
--- /dev/null
+++ b/core/java/android/widget/HorizontalScrollView.java
@@ -0,0 +1,1197 @@
+/*
+ * 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 android.widget;
+
+import android.util.AttributeSet;
+import android.graphics.Rect;
+import android.view.View;
+import android.view.VelocityTracker;
+import android.view.ViewConfiguration;
+import android.view.ViewGroup;
+import android.view.KeyEvent;
+import android.view.FocusFinder;
+import android.view.MotionEvent;
+import android.view.ViewParent;
+import android.view.animation.AnimationUtils;
+import android.content.Context;
+import android.content.res.TypedArray;
+
+import java.util.List;
+
+/**
+ * Layout container for a view hierarchy that can be scrolled by the user,
+ * allowing it to be larger than the physical display.  A HorizontalScrollView
+ * is a {@link FrameLayout}, meaning you should place one child in it
+ * containing the entire contents to scroll; this child may itself be a layout
+ * manager with a complex hierarchy of objects.  A child that is often used
+ * is a {@link LinearLayout} in a horizontal orientation, presenting a horizontal
+ * array of top-level items that the user can scroll through.
+ *
+ * <p>You should never use a HorizontalScrollView with a {@link ListView}, since
+ * ListView takes care of its own scrolling.  Most importantly, doing this
+ * defeats all of the important optimizations in ListView for dealing with
+ * large lists, since it effectively forces the ListView to display its entire
+ * list of items to fill up the infinite container supplied by HorizontalScrollView.
+ *
+ * <p>The {@link TextView} class also
+ * takes care of its own scrolling, so does not require a ScrollView, but
+ * using the two together is possible to achieve the effect of a text view
+ * within a larger container.
+ *
+ * <p>HorizontalScrollView only supports horizontal scrolling.
+ */
+public class HorizontalScrollView extends FrameLayout {
+    private static final int ANIMATED_SCROLL_GAP = ScrollView.ANIMATED_SCROLL_GAP;
+
+    private static final float MAX_SCROLL_FACTOR = ScrollView.MAX_SCROLL_FACTOR;
+
+
+    private long mLastScroll;
+
+    private final Rect mTempRect = new Rect();
+    private Scroller mScroller;
+
+    /**
+     * Flag to indicate that we are moving focus ourselves. This is so the
+     * code that watches for focus changes initiated outside this ScrollView
+     * knows that it does not have to do anything.
+     */
+    private boolean mScrollViewMovedFocus;
+
+    /**
+     * Position of the last motion event.
+     */
+    private float mLastMotionX;
+
+    /**
+     * True when the layout has changed but the traversal has not come through yet.
+     * Ideally the view hierarchy would keep track of this for us.
+     */
+    private boolean mIsLayoutDirty = true;
+
+    /**
+     * The child to give focus to in the event that a child has requested focus while the
+     * layout is dirty. This prevents the scroll from being wrong if the child has not been
+     * laid out before requesting focus.
+     */
+    private View mChildToScrollTo = null;
+
+    /**
+     * True if the user is currently dragging this ScrollView around. This is
+     * not the same as 'is being flinged', which can be checked by
+     * mScroller.isFinished() (flinging begins when the user lifts his finger).
+     */
+    private boolean mIsBeingDragged = false;
+
+    /**
+     * Determines speed during touch scrolling
+     */
+    private VelocityTracker mVelocityTracker;
+
+    /**
+     * When set to true, the scroll view measure its child to make it fill the currently
+     * visible area.
+     */
+    private boolean mFillViewport;
+
+    /**
+     * Whether arrow scrolling is animated.
+     */
+    private boolean mSmoothScrollingEnabled = true;
+
+    private int mTouchSlop;
+
+    public HorizontalScrollView(Context context) {
+        this(context, null);
+    }
+
+    public HorizontalScrollView(Context context, AttributeSet attrs) {
+        this(context, attrs, com.android.internal.R.attr.horizontalScrollViewStyle);
+    }
+
+    public HorizontalScrollView(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+        initScrollView();
+
+        TypedArray a = context.obtainStyledAttributes(attrs,
+                android.R.styleable.HorizontalScrollView, defStyle, 0);
+
+        setFillViewport(a.getBoolean(android.R.styleable.HorizontalScrollView_fillViewport, false));
+
+        a.recycle();
+    }
+
+    @Override
+    protected float getLeftFadingEdgeStrength() {
+        if (getChildCount() == 0) {
+            return 0.0f;
+        }
+
+        final int length = getHorizontalFadingEdgeLength();
+        if (mScrollX < length) {
+            return mScrollX / (float) length;
+        }
+
+        return 1.0f;
+    }
+
+    @Override
+    protected float getRightFadingEdgeStrength() {
+        if (getChildCount() == 0) {
+            return 0.0f;
+        }
+
+        final int length = getHorizontalFadingEdgeLength();
+        final int rightEdge = getWidth() - mPaddingRight;
+        final int span = getChildAt(0).getRight() - mScrollX - rightEdge;
+        if (span < length) {
+            return span / (float) length;
+        }
+
+        return 1.0f;
+    }
+
+    /**
+     * @return The maximum amount this scroll view will scroll in response to
+     *   an arrow event.
+     */
+    public int getMaxScrollAmount() {
+        return (int) (MAX_SCROLL_FACTOR * (mRight - mLeft));
+    }
+
+
+    private void initScrollView() {
+        mScroller = new Scroller(getContext());
+        setFocusable(true);
+        setDescendantFocusability(FOCUS_AFTER_DESCENDANTS);
+        setWillNotDraw(false);
+        mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
+    }
+
+    @Override
+    public void addView(View child) {
+        if (getChildCount() > 0) {
+            throw new IllegalStateException("HorizontalScrollView can host only one direct child");
+        }
+
+        super.addView(child);
+    }
+
+    @Override
+    public void addView(View child, int index) {
+        if (getChildCount() > 0) {
+            throw new IllegalStateException("HorizontalScrollView can host only one direct child");
+        }
+
+        super.addView(child, index);
+    }
+
+    @Override
+    public void addView(View child, ViewGroup.LayoutParams params) {
+        if (getChildCount() > 0) {
+            throw new IllegalStateException("HorizontalScrollView can host only one direct child");
+        }
+
+        super.addView(child, params);
+    }
+
+    @Override
+    public void addView(View child, int index, ViewGroup.LayoutParams params) {
+        if (getChildCount() > 0) {
+            throw new IllegalStateException("HorizontalScrollView can host only one direct child");
+        }
+
+        super.addView(child, index, params);
+    }
+
+    /**
+     * @return Returns true this HorizontalScrollView can be scrolled
+     */
+    private boolean canScroll() {
+        View child = getChildAt(0);
+        if (child != null) {
+            int childWidth = child.getWidth();
+            return getWidth() < childWidth + mPaddingLeft + mPaddingRight ;
+        }
+        return false;
+    }
+
+    /**
+     * Indicates whether this ScrollView's content is stretched to fill the viewport.
+     *
+     * @return True if the content fills the viewport, false otherwise.
+     */
+    public boolean isFillViewport() {
+        return mFillViewport;
+    }
+
+    /**
+     * Indicates this ScrollView whether it should stretch its content width to fill
+     * the viewport or not.
+     *
+     * @param fillViewport True to stretch the content's width to the viewport's
+     *        boundaries, false otherwise.
+     */
+    public void setFillViewport(boolean fillViewport) {
+        if (fillViewport != mFillViewport) {
+            mFillViewport = fillViewport;
+            requestLayout();
+        }
+    }
+
+    /**
+     * @return Whether arrow scrolling will animate its transition.
+     */
+    public boolean isSmoothScrollingEnabled() {
+        return mSmoothScrollingEnabled;
+    }
+
+    /**
+     * Set whether arrow scrolling will animate its transition.
+     * @param smoothScrollingEnabled whether arrow scrolling will animate its transition
+     */
+    public void setSmoothScrollingEnabled(boolean smoothScrollingEnabled) {
+        mSmoothScrollingEnabled = smoothScrollingEnabled;
+    }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+
+        if (!mFillViewport) {
+            return;
+        }
+
+        final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
+        if (widthMode == MeasureSpec.UNSPECIFIED) {
+            return;
+        }
+
+        final View child = getChildAt(0);
+        int width = getMeasuredWidth();
+        if (child.getMeasuredHeight() < width) {
+            final FrameLayout.LayoutParams lp = (LayoutParams) child.getLayoutParams();
+
+            int childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec, mPaddingTop
+                    + mPaddingBottom, lp.height);
+            width -= mPaddingLeft;
+            width -= mPaddingRight;
+            int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY);
+
+            child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
+        }
+    }
+
+    @Override
+    public boolean dispatchKeyEvent(KeyEvent event) {
+        // Let the focused view and/or our descendants get the key first
+        boolean handled = super.dispatchKeyEvent(event);
+        if (handled) {
+            return true;
+        }
+        return executeKeyEvent(event);
+    }
+
+    /**
+     * You can call this function yourself to have the scroll view perform
+     * scrolling from a key event, just as if the event had been dispatched to
+     * it by the view hierarchy.
+     *
+     * @param event The key event to execute.
+     * @return Return true if the event was handled, else false.
+     */
+    public boolean executeKeyEvent(KeyEvent event) {
+        mTempRect.setEmpty();
+
+        if (!canScroll()) {
+            if (isFocused()) {
+                View currentFocused = findFocus();
+                if (currentFocused == this) currentFocused = null;
+                View nextFocused = FocusFinder.getInstance().findNextFocus(this,
+                        currentFocused, View.FOCUS_RIGHT);
+                return nextFocused != null && nextFocused != this &&
+                        nextFocused.requestFocus(View.FOCUS_RIGHT);
+            }
+            return false;
+        }
+
+        boolean handled = false;
+        if (event.getAction() == KeyEvent.ACTION_DOWN) {
+            switch (event.getKeyCode()) {
+                case KeyEvent.KEYCODE_DPAD_LEFT:
+                    if (!event.isAltPressed()) {
+                        handled = arrowScroll(View.FOCUS_LEFT);
+                    } else {
+                        handled = fullScroll(View.FOCUS_LEFT);
+                    }
+                    break;
+                case KeyEvent.KEYCODE_DPAD_RIGHT:
+                    if (!event.isAltPressed()) {
+                        handled = arrowScroll(View.FOCUS_RIGHT);
+                    } else {
+                        handled = fullScroll(View.FOCUS_RIGHT);
+                    }
+                    break;
+                case KeyEvent.KEYCODE_SPACE:
+                    pageScroll(event.isShiftPressed() ? View.FOCUS_LEFT : View.FOCUS_RIGHT);
+                    break;
+            }
+        }
+
+        return handled;
+    }
+
+    @Override
+    public boolean onInterceptTouchEvent(MotionEvent ev) {
+        /*
+         * This method JUST determines whether we want to intercept the motion.
+         * If we return true, onMotionEvent will be called and we do the actual
+         * scrolling there.
+         */
+
+        /*
+        * Shortcut the most recurring case: the user is in the dragging
+        * state and he is moving his finger.  We want to intercept this
+        * motion.
+        */
+        final int action = ev.getAction();
+        if ((action == MotionEvent.ACTION_MOVE) && (mIsBeingDragged)) {
+            return true;
+        }
+
+        if (!canScroll()) {
+            mIsBeingDragged = false;
+            return false;
+        }
+
+        final float x = ev.getX();
+
+        switch (action) {
+            case MotionEvent.ACTION_MOVE:
+                /*
+                 * mIsBeingDragged == false, otherwise the shortcut would have caught it. Check
+                 * whether the user has moved far enough from his original down touch.
+                 */
+
+                /*
+                * Locally do absolute value. mLastMotionX is set to the x value
+                * of the down event.
+                */
+                final int xDiff = (int) Math.abs(x - mLastMotionX);
+                if (xDiff > mTouchSlop) {
+                    mIsBeingDragged = true;
+                    if (mParent != null) mParent.requestDisallowInterceptTouchEvent(true);
+                }
+                break;
+
+            case MotionEvent.ACTION_DOWN:
+                /* Remember location of down touch */
+                mLastMotionX = x;
+
+                /*
+                * If being flinged and user touches the screen, initiate drag;
+                * otherwise don't.  mScroller.isFinished should be false when
+                * being flinged.
+                */
+                mIsBeingDragged = !mScroller.isFinished();
+                break;
+
+            case MotionEvent.ACTION_CANCEL:
+            case MotionEvent.ACTION_UP:
+                /* Release the drag */
+                mIsBeingDragged = false;
+                break;
+        }
+
+        /*
+        * The only time we want to intercept motion events is if we are in the
+        * drag mode.
+        */
+        return mIsBeingDragged;
+    }
+
+    @Override
+    public boolean onTouchEvent(MotionEvent ev) {
+
+        if (ev.getAction() == MotionEvent.ACTION_DOWN && ev.getEdgeFlags() != 0) {
+            // Don't handle edge touches immediately -- they may actually belong to one of our
+            // descendants.
+            return false;
+        }
+
+        if (!canScroll()) {
+            return false;
+        }
+
+        if (mVelocityTracker == null) {
+            mVelocityTracker = VelocityTracker.obtain();
+        }
+        mVelocityTracker.addMovement(ev);
+
+        final int action = ev.getAction();
+        final float x = ev.getX();
+
+        switch (action) {
+            case MotionEvent.ACTION_DOWN:
+                /*
+                * If being flinged and user touches, stop the fling. isFinished
+                * will be false if being flinged.
+                */
+                if (!mScroller.isFinished()) {
+                    mScroller.abortAnimation();
+                }
+
+                // Remember where the motion event started
+                mLastMotionX = x;
+                break;
+            case MotionEvent.ACTION_MOVE:
+                // Scroll to follow the motion event
+                final int deltaX = (int) (mLastMotionX - x);
+                mLastMotionX = x;
+
+                if (deltaX < 0) {
+                    if (mScrollX > 0) {
+                        scrollBy(deltaX, 0);
+                    }
+                } else if (deltaX > 0) {
+                    final int rightEdge = getWidth() - mPaddingRight;
+                    final int availableToScroll = getChildAt(0).getRight() - mScrollX - rightEdge;
+                    if (availableToScroll > 0) {
+                        scrollBy(Math.min(availableToScroll, deltaX), 0);
+                    }
+                }
+                break;
+            case MotionEvent.ACTION_UP:
+                final VelocityTracker velocityTracker = mVelocityTracker;
+                velocityTracker.computeCurrentVelocity(1000);
+                int initialVelocity = (int) velocityTracker.getXVelocity();
+
+                if ((Math.abs(initialVelocity) >
+                        ViewConfiguration.get(mContext).getScaledMinimumFlingVelocity()) &&
+                        getChildCount() > 0) {
+                    fling(-initialVelocity);
+                }
+
+                if (mVelocityTracker != null) {
+                    mVelocityTracker.recycle();
+                    mVelocityTracker = null;
+                }
+        }
+        return true;
+    }
+
+    /**
+     * <p>
+     * Finds the next focusable component that fits in this View's bounds
+     * (excluding fading edges) pretending that this View's left is located at
+     * the parameter left.
+     * </p>
+     *
+     * @param leftFocus          look for a candidate is the one at the left of the bounds
+     *                           if leftFocus is true, or at the right of the bounds if leftFocus
+     *                           is false
+     * @param left               the left offset of the bounds in which a focusable must be
+     *                           found (the fading edge is assumed to start at this position)
+     * @param preferredFocusable the View that has highest priority and will be
+     *                           returned if it is within my bounds (null is valid)
+     * @return the next focusable component in the bounds or null if none can be found
+     */
+    private View findFocusableViewInMyBounds(final boolean leftFocus,
+            final int left, View preferredFocusable) {
+        /*
+         * The fading edge's transparent side should be considered for focus
+         * since it's mostly visible, so we divide the actual fading edge length
+         * by 2.
+         */
+        final int fadingEdgeLength = getHorizontalFadingEdgeLength() / 2;
+        final int leftWithoutFadingEdge = left + fadingEdgeLength;
+        final int rightWithoutFadingEdge = left + getWidth() - fadingEdgeLength;
+
+        if ((preferredFocusable != null)
+                && (preferredFocusable.getLeft() < rightWithoutFadingEdge)
+                && (preferredFocusable.getRight() > leftWithoutFadingEdge)) {
+            return preferredFocusable;
+        }
+
+        return findFocusableViewInBounds(leftFocus, leftWithoutFadingEdge,
+                rightWithoutFadingEdge);
+    }
+
+    /**
+     * <p>
+     * Finds the next focusable component that fits in the specified bounds.
+     * </p>
+     *
+     * @param leftFocus look for a candidate is the one at the left of the bounds
+     *                  if leftFocus is true, or at the right of the bounds if
+     *                  leftFocus is false
+     * @param left      the left offset of the bounds in which a focusable must be
+     *                  found
+     * @param right     the right offset of the bounds in which a focusable must
+     *                  be found
+     * @return the next focusable component in the bounds or null if none can
+     *         be found
+     */
+    private View findFocusableViewInBounds(boolean leftFocus, int left, int right) {
+
+        List<View> focusables = getFocusables(View.FOCUS_FORWARD);
+        View focusCandidate = null;
+
+        /*
+         * A fully contained focusable is one where its left is below the bound's
+         * left, and its right is above the bound's right. A partially
+         * contained focusable is one where some part of it is within the
+         * bounds, but it also has some part that is not within bounds.  A fully contained
+         * focusable is preferred to a partially contained focusable.
+         */
+        boolean foundFullyContainedFocusable = false;
+
+        int count = focusables.size();
+        for (int i = 0; i < count; i++) {
+            View view = focusables.get(i);
+            int viewLeft = view.getLeft();
+            int viewRight = view.getRight();
+
+            if (left < viewRight && viewLeft < right) {
+                /*
+                 * the focusable is in the target area, it is a candidate for
+                 * focusing
+                 */
+
+                final boolean viewIsFullyContained = (left < viewLeft) &&
+                        (viewRight < right);
+
+                if (focusCandidate == null) {
+                    /* No candidate, take this one */
+                    focusCandidate = view;
+                    foundFullyContainedFocusable = viewIsFullyContained;
+                } else {
+                    final boolean viewIsCloserToBoundary =
+                            (leftFocus && viewLeft < focusCandidate.getLeft()) ||
+                                    (!leftFocus && viewRight > focusCandidate.getRight());
+
+                    if (foundFullyContainedFocusable) {
+                        if (viewIsFullyContained && viewIsCloserToBoundary) {
+                            /*
+                             * We're dealing with only fully contained views, so
+                             * it has to be closer to the boundary to beat our
+                             * candidate
+                             */
+                            focusCandidate = view;
+                        }
+                    } else {
+                        if (viewIsFullyContained) {
+                            /* Any fully contained view beats a partially contained view */
+                            focusCandidate = view;
+                            foundFullyContainedFocusable = true;
+                        } else if (viewIsCloserToBoundary) {
+                            /*
+                             * Partially contained view beats another partially
+                             * contained view if it's closer
+                             */
+                            focusCandidate = view;
+                        }
+                    }
+                }
+            }
+        }
+
+        return focusCandidate;
+    }
+
+    /**
+     * <p>Handles scrolling in response to a "page up/down" shortcut press. This
+     * method will scroll the view by one page left or right and give the focus
+     * to the leftmost/rightmost component in the new visible area. If no
+     * component is a good candidate for focus, this scrollview reclaims the
+     * focus.</p>
+     *
+     * @param direction the scroll direction: {@link android.view.View#FOCUS_LEFT}
+     *                  to go one page left or {@link android.view.View#FOCUS_RIGHT}
+     *                  to go one page right
+     * @return true if the key event is consumed by this method, false otherwise
+     */
+    public boolean pageScroll(int direction) {
+        boolean right = direction == View.FOCUS_RIGHT;
+        int width = getWidth();
+
+        if (right) {
+            mTempRect.left = getScrollX() + width;
+            int count = getChildCount();
+            if (count > 0) {
+                View view = getChildAt(count - 1);
+                if (mTempRect.left + width > view.getRight()) {
+                    mTempRect.left = view.getRight() - width;
+                }
+            }
+        } else {
+            mTempRect.left = getScrollX() - width;
+            if (mTempRect.left < 0) {
+                mTempRect.left = 0;
+            }
+        }
+        mTempRect.right = mTempRect.left + width;
+
+        return scrollAndFocus(direction, mTempRect.left, mTempRect.right);
+    }
+
+    /**
+     * <p>Handles scrolling in response to a "home/end" shortcut press. This
+     * method will scroll the view to the left or right and give the focus
+     * to the leftmost/rightmost component in the new visible area. If no
+     * component is a good candidate for focus, this scrollview reclaims the
+     * focus.</p>
+     *
+     * @param direction the scroll direction: {@link android.view.View#FOCUS_LEFT}
+     *                  to go the left of the view or {@link android.view.View#FOCUS_RIGHT}
+     *                  to go the right
+     * @return true if the key event is consumed by this method, false otherwise
+     */
+    public boolean fullScroll(int direction) {
+        boolean right = direction == View.FOCUS_RIGHT;
+        int width = getWidth();
+
+        mTempRect.left = 0;
+        mTempRect.right = width;
+
+        if (right) {
+            int count = getChildCount();
+            if (count > 0) {
+                View view = getChildAt(count - 1);
+                mTempRect.right = view.getRight();
+                mTempRect.left = mTempRect.right - width;
+            }
+        }
+
+        return scrollAndFocus(direction, mTempRect.left, mTempRect.right);
+    }
+
+    /**
+     * <p>Scrolls the view to make the area defined by <code>left</code> and
+     * <code>right</code> visible. This method attempts to give the focus
+     * to a component visible in this area. If no component can be focused in
+     * the new visible area, the focus is reclaimed by this scrollview.</p>
+     *
+     * @param direction the scroll direction: {@link android.view.View#FOCUS_LEFT}
+     *                  to go left {@link android.view.View#FOCUS_RIGHT} to right
+     * @param left     the left offset of the new area to be made visible
+     * @param right    the right offset of the new area to be made visible
+     * @return true if the key event is consumed by this method, false otherwise
+     */
+    private boolean scrollAndFocus(int direction, int left, int right) {
+        boolean handled = true;
+
+        int width = getWidth();
+        int containerLeft = getScrollX();
+        int containerRight = containerLeft + width;
+        boolean goLeft = direction == View.FOCUS_LEFT;
+
+        View newFocused = findFocusableViewInBounds(goLeft, left, right);
+        if (newFocused == null) {
+            newFocused = this;
+        }
+
+        if (left >= containerLeft && right <= containerRight) {
+            handled = false;
+        } else {
+            int delta = goLeft ? (left - containerLeft) : (right - containerRight);
+            doScrollX(delta);
+        }
+
+        if (newFocused != findFocus() && newFocused.requestFocus(direction)) {
+            mScrollViewMovedFocus = true;
+            mScrollViewMovedFocus = false;
+        }
+
+        return handled;
+    }
+
+    /**
+     * Handle scrolling in response to a left or right arrow click.
+     *
+     * @param direction The direction corresponding to the arrow key that was
+     *                  pressed
+     * @return True if we consumed the event, false otherwise
+     */
+    public boolean arrowScroll(int direction) {
+
+        View currentFocused = findFocus();
+        if (currentFocused == this) currentFocused = null;
+
+        View nextFocused = FocusFinder.getInstance().findNextFocus(this, currentFocused, direction);
+
+        final int maxJump = getMaxScrollAmount();
+
+        if (nextFocused != null && isWithinDeltaOfScreen(nextFocused, maxJump)) {
+            nextFocused.getDrawingRect(mTempRect);
+            offsetDescendantRectToMyCoords(nextFocused, mTempRect);
+            int scrollDelta = computeScrollDeltaToGetChildRectOnScreen(mTempRect);
+            doScrollX(scrollDelta);
+            nextFocused.requestFocus(direction);
+        } else {
+            // no new focus
+            int scrollDelta = maxJump;
+
+            if (direction == View.FOCUS_LEFT && getScrollX() < scrollDelta) {
+                scrollDelta = getScrollX();
+            } else if (direction == View.FOCUS_RIGHT) {
+
+                int daRight = getChildAt(getChildCount() - 1).getRight();
+
+                int screenRight = getScrollX() + getWidth();
+
+                if (daRight - screenRight < maxJump) {
+                    scrollDelta = daRight - screenRight;
+                }
+            }
+            if (scrollDelta == 0) {
+                return false;
+            }
+            doScrollX(direction == View.FOCUS_RIGHT ? scrollDelta : -scrollDelta);
+        }
+
+        if (currentFocused != null && currentFocused.isFocused()
+                && isOffScreen(currentFocused)) {
+            // previously focused item still has focus and is off screen, give
+            // it up (take it back to ourselves)
+            // (also, need to temporarily force FOCUS_BEFORE_DESCENDANTS so we are
+            // sure to
+            // get it)
+            final int descendantFocusability = getDescendantFocusability();  // save
+            setDescendantFocusability(ViewGroup.FOCUS_BEFORE_DESCENDANTS);
+            requestFocus();
+            setDescendantFocusability(descendantFocusability);  // restore
+        }
+        return true;
+    }
+
+    /**
+     * @return whether the descendant of this scroll view is scrolled off
+     *  screen.
+     */
+    private boolean isOffScreen(View descendant) {
+        return !isWithinDeltaOfScreen(descendant, 0);
+    }
+
+    /**
+     * @return whether the descendant of this scroll view is within delta
+     *  pixels of being on the screen.
+     */
+    private boolean isWithinDeltaOfScreen(View descendant, int delta) {
+        descendant.getDrawingRect(mTempRect);
+        offsetDescendantRectToMyCoords(descendant, mTempRect);
+
+        return (mTempRect.right + delta) >= getScrollX()
+                && (mTempRect.left - delta) <= (getScrollX() + getWidth());
+    }
+
+    /**
+     * Smooth scroll by a X delta
+     *
+     * @param delta the number of pixels to scroll by on the X axis
+     */
+    private void doScrollX(int delta) {
+        if (delta != 0) {
+            if (mSmoothScrollingEnabled) {
+                smoothScrollBy(delta, 0);
+            } else {
+                scrollBy(delta, 0);
+            }
+        }
+    }
+
+    /**
+     * Like {@link View#scrollBy}, but scroll smoothly instead of immediately.
+     *
+     * @param dx the number of pixels to scroll by on the X axis
+     * @param dy the number of pixels to scroll by on the Y axis
+     */
+    public final void smoothScrollBy(int dx, int dy) {
+        long duration = AnimationUtils.currentAnimationTimeMillis() - mLastScroll;
+        if (duration > ANIMATED_SCROLL_GAP) {
+            mScroller.startScroll(mScrollX, mScrollY, dx, dy);
+            invalidate();
+        } else {
+            if (!mScroller.isFinished()) {
+                mScroller.abortAnimation();
+            }
+            scrollBy(dx, dy);
+        }
+        mLastScroll = AnimationUtils.currentAnimationTimeMillis();
+    }
+
+    /**
+     * Like {@link #scrollTo}, but scroll smoothly instead of immediately.
+     *
+     * @param x the position where to scroll on the X axis
+     * @param y the position where to scroll on the Y axis
+     */
+    public final void smoothScrollTo(int x, int y) {
+        smoothScrollBy(x - mScrollX, y - mScrollY);
+    }
+
+    /**
+     * <p>The scroll range of a scroll view is the overall width of all of its
+     * children.</p>
+     */
+    @Override
+    protected int computeHorizontalScrollRange() {
+        int count = getChildCount();
+        return count == 0 ? getWidth() : getChildAt(0).getRight();
+    }
+
+
+    @Override
+    protected void measureChild(View child, int parentWidthMeasureSpec, int parentHeightMeasureSpec) {
+        ViewGroup.LayoutParams lp = child.getLayoutParams();
+
+        int childWidthMeasureSpec;
+        int childHeightMeasureSpec;
+
+        childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec, mPaddingTop
+                + mPaddingBottom, lp.height);
+
+        childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
+
+        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
+    }
+
+    @Override
+    protected void measureChildWithMargins(View child, int parentWidthMeasureSpec, int widthUsed,
+            int parentHeightMeasureSpec, int heightUsed) {
+        final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
+
+        final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
+                mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin
+                        + heightUsed, lp.height);
+        final int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(
+                lp.leftMargin + lp.rightMargin, MeasureSpec.UNSPECIFIED);
+
+        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
+    }
+
+    @Override
+    public void computeScroll() {
+        if (mScroller.computeScrollOffset()) {
+            // This is called at drawing time by ViewGroup.  We don't want to
+            // re-show the scrollbars at this point, which scrollTo will do,
+            // so we replicate most of scrollTo here.
+            //
+            //         It's a little odd to call onScrollChanged from inside the drawing.
+            //
+            //         It is, except when you remember that computeScroll() is used to
+            //         animate scrolling. So unless we want to defer the onScrollChanged()
+            //         until the end of the animated scrolling, we don't really have a
+            //         choice here.
+            //
+            //         I agree.  The alternative, which I think would be worse, is to post
+            //         something and tell the subclasses later.  This is bad because there
+            //         will be a window where mScrollX/Y is different from what the app
+            //         thinks it is.
+            //
+            int oldX = mScrollX;
+            int oldY = mScrollY;
+            int x = mScroller.getCurrX();
+            int y = mScroller.getCurrY();
+            if (getChildCount() > 0) {
+                View child = getChildAt(0);
+                mScrollX = clamp(x, getWidth() - mPaddingRight - mPaddingLeft, child.getWidth());
+                mScrollY = clamp(y, getHeight() - mPaddingBottom - mPaddingTop, child.getHeight());
+            } else {
+                mScrollX = x;
+                mScrollY = y;
+            }
+            if (oldX != mScrollX || oldY != mScrollY) {
+                onScrollChanged(mScrollX, mScrollY, oldX, oldY);
+            }
+
+            // Keep on drawing until the animation has finished.
+            postInvalidate();
+        }
+    }
+
+    /**
+     * Scrolls the view to the given child.
+     *
+     * @param child the View to scroll to
+     */
+    private void scrollToChild(View child) {
+        child.getDrawingRect(mTempRect);
+
+        /* Offset from child's local coordinates to ScrollView coordinates */
+        offsetDescendantRectToMyCoords(child, mTempRect);
+
+        int scrollDelta = computeScrollDeltaToGetChildRectOnScreen(mTempRect);
+
+        if (scrollDelta != 0) {
+            scrollBy(scrollDelta, 0);
+        }
+    }
+
+    /**
+     * If rect is off screen, scroll just enough to get it (or at least the
+     * first screen size chunk of it) on screen.
+     *
+     * @param rect      The rectangle.
+     * @param immediate True to scroll immediately without animation
+     * @return true if scrolling was performed
+     */
+    private boolean scrollToChildRect(Rect rect, boolean immediate) {
+        final int delta = computeScrollDeltaToGetChildRectOnScreen(rect);
+        final boolean scroll = delta != 0;
+        if (scroll) {
+            if (immediate) {
+                scrollBy(delta, 0);
+            } else {
+                smoothScrollBy(delta, 0);
+            }
+        }
+        return scroll;
+    }
+
+    /**
+     * Compute the amount to scroll in the X direction in order to get
+     * a rectangle completely on the screen (or, if taller than the screen,
+     * at least the first screen size chunk of it).
+     *
+     * @param rect The rect.
+     * @return The scroll delta.
+     */
+    protected int computeScrollDeltaToGetChildRectOnScreen(Rect rect) {
+
+        int width = getWidth();
+        int screenLeft = getScrollX();
+        int screenRight = screenLeft + width;
+
+        int fadingEdge = getHorizontalFadingEdgeLength();
+
+        // leave room for left fading edge as long as rect isn't at very left
+        if (rect.left > 0) {
+            screenLeft += fadingEdge;
+        }
+
+        // leave room for right fading edge as long as rect isn't at very right
+        if (rect.right < getChildAt(0).getWidth()) {
+            screenRight -= fadingEdge;
+        }
+
+        int scrollXDelta = 0;
+
+        if (rect.right > screenRight && rect.left > screenLeft) {
+            // need to move right to get it in view: move right just enough so
+            // that the entire rectangle is in view (or at least the first
+            // screen size chunk).
+
+            if (rect.width() > width) {
+                // just enough to get screen size chunk on
+                scrollXDelta += (rect.left - screenLeft);
+            } else {
+                // get entire rect at right of screen
+                scrollXDelta += (rect.right - screenRight);
+            }
+
+            // make sure we aren't scrolling beyond the end of our content
+            int right = getChildAt(getChildCount() - 1).getRight();
+            int distanceToRight = right - screenRight;
+            scrollXDelta = Math.min(scrollXDelta, distanceToRight);
+
+        } else if (rect.left < screenLeft && rect.right < screenRight) {
+            // need to move right to get it in view: move right just enough so that
+            // entire rectangle is in view (or at least the first screen
+            // size chunk of it).
+
+            if (rect.width() > width) {
+                // screen size chunk
+                scrollXDelta -= (screenRight - rect.right);
+            } else {
+                // entire rect at left
+                scrollXDelta -= (screenLeft - rect.left);
+            }
+
+            // make sure we aren't scrolling any further than the left our content
+            scrollXDelta = Math.max(scrollXDelta, -getScrollX());
+        }
+        return scrollXDelta;
+    }
+
+    @Override
+    public void requestChildFocus(View child, View focused) {
+        if (!mScrollViewMovedFocus) {
+            if (!mIsLayoutDirty) {
+                scrollToChild(focused);
+            } else {
+                // The child may not be laid out yet, we can't compute the scroll yet
+                mChildToScrollTo = focused;
+            }
+        }
+        super.requestChildFocus(child, focused);
+    }
+
+
+    /**
+     * When looking for focus in children of a scroll view, need to be a little
+     * more careful not to give focus to something that is scrolled off screen.
+     *
+     * This is more expensive than the default {@link android.view.ViewGroup}
+     * implementation, otherwise this behavior might have been made the default.
+     */
+    @Override
+    protected boolean onRequestFocusInDescendants(int direction,
+            Rect previouslyFocusedRect) {
+
+        // convert from forward / backward notation to up / down / left / right
+        // (ugh).
+        if (direction == View.FOCUS_FORWARD) {
+            direction = View.FOCUS_RIGHT;
+        } else if (direction == View.FOCUS_BACKWARD) {
+            direction = View.FOCUS_LEFT;
+        }
+
+        final View nextFocus = previouslyFocusedRect == null ?
+                FocusFinder.getInstance().findNextFocus(this, null, direction) :
+                FocusFinder.getInstance().findNextFocusFromRect(this,
+                        previouslyFocusedRect, direction);
+
+        if (nextFocus == null) {
+            return false;
+        }
+
+        if (isOffScreen(nextFocus)) {
+            return false;
+        }
+
+        return nextFocus.requestFocus(direction, previouslyFocusedRect);
+    }
+
+    @Override
+    public boolean requestChildRectangleOnScreen(View child, Rect rectangle,
+            boolean immediate) {
+        // offset into coordinate space of this scroll view
+        rectangle.offset(child.getLeft() - child.getScrollX(),
+                child.getTop() - child.getScrollY());
+
+        return scrollToChildRect(rectangle, immediate);
+    }
+
+    @Override
+    public void requestLayout() {
+        mIsLayoutDirty = true;
+        super.requestLayout();
+    }
+
+    @Override
+    protected void onLayout(boolean changed, int l, int t, int r, int b) {
+        super.onLayout(changed, l, t, r, b);
+        mIsLayoutDirty = false;
+        // Give a child focus if it needs it
+        if (mChildToScrollTo != null && isViewDescendantOf(mChildToScrollTo, this)) {
+                scrollToChild(mChildToScrollTo);
+        }
+        mChildToScrollTo = null;
+
+        // Calling this with the present values causes it to re-clam them
+        scrollTo(mScrollX, mScrollY);
+    }
+
+    @Override
+    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
+        super.onSizeChanged(w, h, oldw, oldh);
+
+        View currentFocused = findFocus();
+        if (null == currentFocused || this == currentFocused)
+            return;
+
+        final int maxJump = mRight - mLeft;
+
+        if (isWithinDeltaOfScreen(currentFocused, maxJump)) {
+            currentFocused.getDrawingRect(mTempRect);
+            offsetDescendantRectToMyCoords(currentFocused, mTempRect);
+            int scrollDelta = computeScrollDeltaToGetChildRectOnScreen(mTempRect);
+            doScrollX(scrollDelta);
+        }
+    }
+
+    /**
+     * Return true if child is an descendant of parent, (or equal to the parent).
+     */
+    private boolean isViewDescendantOf(View child, View parent) {
+        if (child == parent) {
+            return true;
+        }
+
+        final ViewParent theParent = child.getParent();
+        return (theParent instanceof ViewGroup) && isViewDescendantOf((View) theParent, parent);
+    }
+
+    /**
+     * Fling the scroll view
+     *
+     * @param velocityX The initial velocity in the X direction. Positive
+     *                  numbers mean that the finger/curor is moving down the screen,
+     *                  which means we want to scroll towards the left.
+     */
+    public void fling(int velocityX) {
+        int width = getWidth() - mPaddingRight - mPaddingLeft;
+        int right = getChildAt(0).getWidth();
+
+        mScroller.fling(mScrollX, mScrollY, velocityX, 0, 0, right - width, 0, 0);
+
+        final boolean movingRight = velocityX > 0;
+
+        View newFocused = findFocusableViewInMyBounds(movingRight,
+                mScroller.getFinalX(), findFocus());
+
+        if (newFocused == null) {
+            newFocused = this;
+        }
+
+        if (newFocused != findFocus()
+                && newFocused.requestFocus(movingRight ? View.FOCUS_RIGHT : View.FOCUS_LEFT)) {
+            mScrollViewMovedFocus = true;
+            mScrollViewMovedFocus = false;
+        }
+
+        invalidate();
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p>This version also clamps the scrolling to the bounds of our child.
+     */
+    public void scrollTo(int x, int y) {
+        // we rely on the fact the View.scrollBy calls scrollTo.
+        if (getChildCount() > 0) {
+            View child = getChildAt(0);
+            x = clamp(x, getWidth() - mPaddingRight - mPaddingLeft, child.getWidth());
+            y = clamp(y, getHeight() - mPaddingBottom - mPaddingTop, child.getHeight());
+            if (x != mScrollX || y != mScrollY) {
+                super.scrollTo(x, y);
+            }
+        }
+    }
+
+    private int clamp(int n, int my, int child) {
+        if (my >= child || n < 0) {
+            return 0;
+        }
+        if ((my + n) > child) {
+            return child - my;
+        }
+        return n;
+    }
+}
diff --git a/core/java/android/widget/ImageButton.java b/core/java/android/widget/ImageButton.java
new file mode 100644
index 0000000..4c1cbf6
--- /dev/null
+++ b/core/java/android/widget/ImageButton.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2007 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 android.widget;
+
+import android.content.Context;
+import android.os.Handler;
+import android.os.Message;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+import android.widget.RemoteViews.RemoteView;
+
+import java.util.Map;
+
+/**
+ * <p>
+ * An image button displays an image that can be pressed, or clicked, by the
+ * user.
+ * </p>
+ *
+ * <p><strong>XML attributes</strong></p>
+ * <p>
+ * See {@link android.R.styleable#ImageView Button Attributes},
+ * {@link android.R.styleable#View View Attributes}
+ * </p>
+ */
+@RemoteView
+public class ImageButton extends ImageView {
+    public ImageButton(Context context) {
+        this(context, null);
+    }
+
+    public ImageButton(Context context, AttributeSet attrs) {
+        this(context, attrs, com.android.internal.R.attr.imageButtonStyle);
+    }
+
+    public ImageButton(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+        setFocusable(true);
+    }
+
+    @Override
+    protected boolean onSetAlpha(int alpha) {
+        return false;
+    }
+}
diff --git a/core/java/android/widget/ImageSwitcher.java b/core/java/android/widget/ImageSwitcher.java
new file mode 100644
index 0000000..bcb750a
--- /dev/null
+++ b/core/java/android/widget/ImageSwitcher.java
@@ -0,0 +1,59 @@
+/*
+ * 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 android.widget;
+
+import java.util.Map;
+
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.net.Uri;
+import android.util.AttributeSet;
+
+
+public class ImageSwitcher extends ViewSwitcher
+{
+    public ImageSwitcher(Context context)
+    {
+        super(context);
+    }
+    
+    public ImageSwitcher(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    public void setImageResource(int resid)
+    {
+        ImageView image = (ImageView)this.getNextView();
+        image.setImageResource(resid);
+        showNext();
+    }
+
+    public void setImageURI(Uri uri)
+    {
+        ImageView image = (ImageView)this.getNextView();
+        image.setImageURI(uri);
+        showNext();
+    }
+
+    public void setImageDrawable(Drawable drawable)
+    {
+        ImageView image = (ImageView)this.getNextView();
+        image.setImageDrawable(drawable);
+        showNext();
+    }
+}
+
diff --git a/core/java/android/widget/ImageView.java b/core/java/android/widget/ImageView.java
new file mode 100644
index 0000000..a4523b92
--- /dev/null
+++ b/core/java/android/widget/ImageView.java
@@ -0,0 +1,891 @@
+/*
+ * 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 android.widget;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.ColorFilter;
+import android.graphics.Matrix;
+import android.graphics.PorterDuff;
+import android.graphics.PorterDuffColorFilter;
+import android.graphics.RectF;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.net.Uri;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.View;
+import android.widget.RemoteViews.RemoteView;
+
+
+/**
+ * Displays an arbitrary image, such as an icon.  The ImageView class
+ * can load images from various sources (such as resources or content
+ * providers), takes care of computing its measurement from the image so that
+ * it can be used in any layout manager, and provides various display options
+ * such as scaling and tinting.
+ *
+ * @attr ref android.R.styleable#ImageView_adjustViewBounds
+ * @attr ref android.R.styleable#ImageView_src
+ * @attr ref android.R.styleable#ImageView_maxWidth
+ * @attr ref android.R.styleable#ImageView_maxHeight
+ * @attr ref android.R.styleable#ImageView_tint
+ * @attr ref android.R.styleable#ImageView_scaleType
+ * @attr ref android.R.styleable#ImageView_cropToPadding
+ */
+@RemoteView
+public class ImageView extends View {
+    // settable by the client
+    private Uri mUri;
+    private int mResource = 0;
+    private Matrix mMatrix;
+    private ScaleType mScaleType;
+    private boolean mHaveFrame = false;
+    private boolean mAdjustViewBounds = false;
+    private int mMaxWidth = Integer.MAX_VALUE;
+    private int mMaxHeight = Integer.MAX_VALUE;
+
+    // these are applied to the drawable
+    private ColorFilter mColorFilter;
+    private int mAlpha = 255;
+    private int mViewAlphaScale = 256;
+
+    private Drawable mDrawable = null;
+    private int[] mState = null;
+    private boolean mMergeState = false;
+    private int mLevel = 0;
+    private int mDrawableWidth;
+    private int mDrawableHeight;
+    private Matrix mDrawMatrix = null;
+
+    // Avoid allocations...
+    private RectF mTempSrc = new RectF();
+    private RectF mTempDst = new RectF();
+
+    private boolean mCropToPadding;
+
+    private boolean mBaselineAligned = false;
+
+    private static final ScaleType[] sScaleTypeArray = {
+        ScaleType.MATRIX,
+        ScaleType.FIT_XY,
+        ScaleType.FIT_START,
+        ScaleType.FIT_CENTER,
+        ScaleType.FIT_END,
+        ScaleType.CENTER,
+        ScaleType.CENTER_CROP,
+        ScaleType.CENTER_INSIDE
+    };
+
+    public ImageView(Context context) {
+        super(context);
+        initImageView();
+    }
+    
+    public ImageView(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+    
+    public ImageView(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+        initImageView();
+
+        TypedArray a = context.obtainStyledAttributes(attrs,
+                com.android.internal.R.styleable.ImageView, defStyle, 0);
+
+        Drawable d = a.getDrawable(com.android.internal.R.styleable.ImageView_src);
+        if (d != null) {
+            setImageDrawable(d);
+        }
+
+        mBaselineAligned = a.getBoolean(
+                com.android.internal.R.styleable.ImageView_baselineAlignBottom, false);
+        
+        setAdjustViewBounds(
+            a.getBoolean(com.android.internal.R.styleable.ImageView_adjustViewBounds,
+            false));
+
+        setMaxWidth(a.getDimensionPixelSize(
+                com.android.internal.R.styleable.ImageView_maxWidth, Integer.MAX_VALUE));
+        
+        setMaxHeight(a.getDimensionPixelSize(
+                com.android.internal.R.styleable.ImageView_maxHeight, Integer.MAX_VALUE));
+        
+        int index = a.getInt(com.android.internal.R.styleable.ImageView_scaleType, -1);
+        if (index >= 0) {
+            setScaleType(sScaleTypeArray[index]);
+        }
+
+        int tint = a.getInt(com.android.internal.R.styleable.ImageView_tint, 0);
+        if (tint != 0) {
+            setColorFilter(tint, PorterDuff.Mode.SRC_ATOP);
+        }
+        
+        mCropToPadding = a.getBoolean(
+                com.android.internal.R.styleable.ImageView_cropToPadding, false);
+        
+        a.recycle();
+
+        //need inflate syntax/reader for matrix
+    }
+
+    private void initImageView() {
+        mMatrix     = new Matrix();
+        mScaleType  = ScaleType.FIT_CENTER;
+    }
+
+    @Override
+    protected boolean verifyDrawable(Drawable dr) {
+        return mDrawable == dr || super.verifyDrawable(dr);
+    }
+    
+    @Override
+    public void invalidateDrawable(Drawable dr) {
+        if (dr == mDrawable) {
+            /* we invalidate the whole view in this case because it's very
+             * hard to know where the drawable actually is. This is made
+             * complicated because of the offsets and transformations that
+             * can be applied. In theory we could get the drawable's bounds
+             * and run them through the transformation and offsets, but this
+             * is probably not worth the effort.
+             */
+            invalidate();
+        } else {
+            super.invalidateDrawable(dr);
+        }
+    }
+    
+    @Override
+    protected boolean onSetAlpha(int alpha) {
+        if (getBackground() == null) {
+            int scale = alpha + (alpha >> 7);
+            if (mViewAlphaScale != scale) {
+                mViewAlphaScale = scale;
+                applyColorMod();
+            }
+            return true;
+        }
+        return false;
+    }
+    
+    /**
+     * Set this to true if you want the ImageView to adjust its bounds
+     * to preserve the aspect ratio of its drawable.
+     * @param adjustViewBounds Whether to adjust the bounds of this view
+     * to presrve the original aspect ratio of the drawable
+     * 
+     * @attr ref android.R.styleable#ImageView_adjustViewBounds
+     */
+    @android.view.RemotableViewMethod
+    public void setAdjustViewBounds(boolean adjustViewBounds) {
+        mAdjustViewBounds = adjustViewBounds;
+        if (adjustViewBounds) {
+            setScaleType(ScaleType.FIT_CENTER);
+        }
+    }
+    
+    /**
+     * An optional argument to supply a maximum width for this view. Only valid if
+     * {@link #setAdjustViewBounds} has been set to true. To set an image to be a maximum of 100 x
+     * 100 while preserving the original aspect ratio, do the following: 1) set adjustViewBounds to
+     * true 2) set maxWidth and maxHeight to 100 3) set the height and width layout params to
+     * WRAP_CONTENT.
+     * 
+     * <p>
+     * Note that this view could be still smaller than 100 x 100 using this approach if the original
+     * image is small. To set an image to a fixed size, specify that size in the layout params and
+     * then use {@link #setScaleType} to determine how to fit the image within the bounds.
+     * </p>
+     * 
+     * @param maxWidth maximum width for this view
+     * 
+     * @attr ref android.R.styleable#ImageView_maxWidth
+     */
+    @android.view.RemotableViewMethod
+    public void setMaxWidth(int maxWidth) {
+        mMaxWidth = maxWidth;
+    }
+    
+    /**
+     * An optional argument to supply a maximum height for this view. Only valid if
+     * {@link #setAdjustViewBounds} has been set to true. To set an image to be a maximum of 100 x
+     * 100 while preserving the original aspect ratio, do the following: 1) set adjustViewBounds to
+     * true 2) set maxWidth and maxHeight to 100 3) set the height and width layout params to
+     * WRAP_CONTENT.
+     * 
+     * <p>
+     * Note that this view could be still smaller than 100 x 100 using this approach if the original
+     * image is small. To set an image to a fixed size, specify that size in the layout params and
+     * then use {@link #setScaleType} to determine how to fit the image within the bounds.
+     * </p>
+     * 
+     * @param maxHeight maximum height for this view
+     * 
+     * @attr ref android.R.styleable#ImageView_maxHeight
+     */
+    @android.view.RemotableViewMethod
+    public void setMaxHeight(int maxHeight) {
+        mMaxHeight = maxHeight;
+    }
+
+    /** Return the view's drawable, or null if no drawable has been
+        assigned.
+    */
+    public Drawable getDrawable() {
+        return mDrawable;
+    }
+
+    /**
+     * Sets a drawable as the content of this ImageView.
+     * 
+     * @param resId the resource identifier of the the drawable
+     * 
+     * @attr ref android.R.styleable#ImageView_src
+     */
+    @android.view.RemotableViewMethod
+    public void setImageResource(int resId) {
+        if (mUri != null || mResource != resId) {
+            updateDrawable(null);
+            mResource = resId;
+            mUri = null;
+            resolveUri();
+            requestLayout();
+            invalidate();
+        }
+    }
+
+    /**
+     * Sets the content of this ImageView to the specified Uri.
+     * 
+     * @param uri The Uri of an image
+     */
+    @android.view.RemotableViewMethod
+    public void setImageURI(Uri uri) {
+        if (mResource != 0 ||
+                (mUri != uri &&
+                 (uri == null || mUri == null || !uri.equals(mUri)))) {
+            updateDrawable(null);
+            mResource = 0;
+            mUri = uri;
+            resolveUri();
+            requestLayout();
+            invalidate();
+        }
+    }
+
+    
+    /**
+     * Sets a drawable as the content of this ImageView.
+     * 
+     * @param drawable The drawable to set
+     */
+    public void setImageDrawable(Drawable drawable) {
+        if (mDrawable != drawable) {
+            mResource = 0;
+            mUri = null;
+            updateDrawable(drawable);
+            requestLayout();
+            invalidate();
+        }
+    }
+
+    /**
+     * Sets a Bitmap as the content of this ImageView.
+     * 
+     * @param bm The bitmap to set
+     */
+    @android.view.RemotableViewMethod
+    public void setImageBitmap(Bitmap bm) {
+        // if this is used frequently, may handle bitmaps explicitly
+        // to reduce the intermediate drawable object
+        setImageDrawable(new BitmapDrawable(bm));
+    }
+
+    public void setImageState(int[] state, boolean merge) {
+        mState = state;
+        mMergeState = merge;
+        if (mDrawable != null) {
+            refreshDrawableState();
+            resizeFromDrawable();
+        }
+    }
+
+    @Override
+    public void setSelected(boolean selected) {
+        super.setSelected(selected);
+        resizeFromDrawable();
+    }
+
+    @android.view.RemotableViewMethod
+    public void setImageLevel(int level) {
+        mLevel = level;
+        if (mDrawable != null) {
+            mDrawable.setLevel(level);
+            resizeFromDrawable();
+        }
+    }
+
+    /**
+     * Options for scaling the bounds of an image to the bounds of this view.
+     */
+    public enum ScaleType {
+        /**
+         * Scale using the image matrix when drawing. The image matrix can be set using
+         * {@link ImageView#setImageMatrix(Matrix)}. From XML, use this syntax:
+         * <code>android:scaleType="matrix"</code>.
+         */
+        MATRIX      (0),
+        /**
+         * Scale the image using {@link Matrix.ScaleToFit#FILL}.
+         * From XML, use this syntax: <code>android:scaleType="fitXY"</code>.
+         */
+        FIT_XY      (1),
+        /**
+         * Scale the image using {@link Matrix.ScaleToFit#START}.
+         * From XML, use this syntax: <code>android:scaleType="fitStart"</code>.
+         */
+        FIT_START   (2),
+        /**
+         * Scale the image using {@link Matrix.ScaleToFit#CENTER}.
+         * From XML, use this syntax:
+         * <code>android:scaleType="fitCenter"</code>.
+         */
+        FIT_CENTER  (3),
+        /**
+         * Scale the image using {@link Matrix.ScaleToFit#END}.
+         * From XML, use this syntax: <code>android:scaleType="fitEnd"</code>.
+         */
+        FIT_END     (4),
+        /**
+         * Center the image in the view, but perform no scaling.
+         * From XML, use this syntax: <code>android:scaleType="center"</code>.
+         */
+        CENTER      (5),
+        /**
+         * Scale the image uniformly (maintain the image's aspect ratio) so
+         * that both dimensions (width and height) of the image will be equal
+         * to or larger than the corresponding dimension of the view
+         * (minus padding). The image is then centered in the view.
+         * From XML, use this syntax: <code>android:scaleType="centerCrop"</code>.
+         */
+        CENTER_CROP (6),
+        /**
+         * Scale the image uniformly (maintain the image's aspect ratio) so
+         * that both dimensions (width and height) of the image will be equal
+         * to or less than the corresponding dimension of the view
+         * (minus padding). The image is then centered in the view.
+         * From XML, use this syntax: <code>android:scaleType="centerInside"</code>.
+         */
+        CENTER_INSIDE (7);
+        
+        ScaleType(int ni) {
+            nativeInt = ni;
+        }
+        final int nativeInt;
+    }
+
+    /**
+     * Controls how the image should be resized or moved to match the size
+     * of this ImageView.
+     * 
+     * @param scaleType The desired scaling mode.
+     * 
+     * @attr ref android.R.styleable#ImageView_scaleType
+     */
+    public void setScaleType(ScaleType scaleType) {
+        if (scaleType == null) {
+            throw new NullPointerException();
+        }
+
+        if (mScaleType != scaleType) {
+            mScaleType = scaleType;
+
+            setWillNotCacheDrawing(mScaleType == ScaleType.CENTER);            
+
+            requestLayout();
+            invalidate();
+        }
+    }
+    
+    /**
+     * Return the current scale type in use by this ImageView.
+     *
+     * @see ImageView.ScaleType
+     *
+     * @attr ref android.R.styleable#ImageView_scaleType
+     */
+    public ScaleType getScaleType() {
+        return mScaleType;
+    }
+
+    /** Return the view's optional matrix. This is applied to the
+        view's drawable when it is drawn. If there is not matrix,
+        this method will return null.
+        Do not change this matrix in place. If you want a different matrix
+        applied to the drawable, be sure to call setImageMatrix().
+    */
+    public Matrix getImageMatrix() {
+        return mMatrix;
+    }
+
+    public void setImageMatrix(Matrix matrix) {
+        // collaps null and identity to just null
+        if (matrix != null && matrix.isIdentity()) {
+            matrix = null;
+        }
+        
+        // don't invalidate unless we're actually changing our matrix
+        if (matrix == null && !mMatrix.isIdentity() ||
+                matrix != null && !mMatrix.equals(matrix)) {
+            mMatrix.set(matrix);
+            invalidate();
+        }
+    }
+    
+    private void resolveUri() {
+        if (mDrawable != null) {
+            return;
+        }
+
+        Resources rsrc = getResources();
+        if (rsrc == null) {
+            return;
+        }
+
+        Drawable d = null;
+
+        if (mResource != 0) {
+            try {
+                d = rsrc.getDrawable(mResource);
+            } catch (Exception e) {
+                Log.w("ImageView", "Unable to find resource: " + mResource, e);
+                // Don't try again.
+                mUri = null;
+            }
+        } else if (mUri != null) {
+            if ("content".equals(mUri.getScheme())) {
+                try {
+                    d = Drawable.createFromStream(
+                        mContext.getContentResolver().openInputStream(mUri),
+                        null);
+                } catch (Exception e) {
+                    Log.w("ImageView", "Unable to open content: " + mUri, e);
+                }
+            } else {
+                d = Drawable.createFromPath(mUri.toString());
+            }
+    
+            if (d == null) {
+                System.out.println("resolveUri failed on bad bitmap uri: "
+                                   + mUri);
+                // Don't try again.
+                mUri = null;
+            }
+        } else {
+            return;
+        }
+
+        updateDrawable(d);
+    }
+
+    @Override
+    public int[] onCreateDrawableState(int extraSpace) {
+        if (mState == null) {
+            return super.onCreateDrawableState(extraSpace);
+        } else if (!mMergeState) {
+            return mState;
+        } else {
+            return mergeDrawableStates(
+                    super.onCreateDrawableState(extraSpace + mState.length), mState);
+        }
+    }
+
+    private void updateDrawable(Drawable d) {
+        if (mDrawable != null) {
+            mDrawable.setCallback(null);
+            unscheduleDrawable(mDrawable);
+        }
+        mDrawable = d;
+        if (d != null) {
+            d.setCallback(this);
+            if (d.isStateful()) {
+                d.setState(getDrawableState());
+            }
+            d.setLevel(mLevel);
+            mDrawableWidth = d.getIntrinsicWidth();
+            mDrawableHeight = d.getIntrinsicHeight();
+            applyColorMod();
+            configureBounds();
+        }
+    }
+
+    private void resizeFromDrawable() {
+        Drawable d = mDrawable;
+        if (d != null) {
+            int w = d.getIntrinsicWidth();
+            if (w < 0) w = mDrawableWidth;
+            int h = d.getIntrinsicHeight();
+            if (h < 0) h = mDrawableHeight;
+            if (w != mDrawableWidth || h != mDrawableHeight) {
+                mDrawableWidth = w;
+                mDrawableHeight = h;
+                requestLayout();
+            }
+        }
+    }
+
+    private static final Matrix.ScaleToFit[] sS2FArray = {
+        Matrix.ScaleToFit.FILL,
+        Matrix.ScaleToFit.START,
+        Matrix.ScaleToFit.CENTER,
+        Matrix.ScaleToFit.END
+    };
+
+    private static Matrix.ScaleToFit scaleTypeToScaleToFit(ScaleType st)  {
+        // ScaleToFit enum to their corresponding Matrix.ScaleToFit values
+        return sS2FArray[st.nativeInt - 1];
+    }    
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        resolveUri();
+        int w;
+        int h;
+        
+        // Desired aspect ratio of the view's contents (not including padding)
+        float desiredAspect = 0.0f;
+        
+        // We are allowed to change the view's width
+        boolean resizeWidth = false;
+        
+        // We are allowed to change the view's height
+        boolean resizeHeight = false;
+        
+        if (mDrawable == null) {
+            // If no drawable, its intrinsic size is 0.
+            mDrawableWidth = -1;
+            mDrawableHeight = -1;
+            w = h = 0;
+        } else {
+            w = mDrawableWidth;
+            h = mDrawableHeight;
+            if (w <= 0) w = 1;
+            if (h <= 0) h = 1;
+            
+            // We are supposed to adjust view bounds to match the aspect
+            // ratio of our drawable. See if that is possible.
+            if (mAdjustViewBounds) {
+                
+                int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
+                int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
+                
+                resizeWidth = widthSpecMode != MeasureSpec.EXACTLY;
+                resizeHeight = heightSpecMode != MeasureSpec.EXACTLY;
+                
+                desiredAspect = (float)w/(float)h;
+            }
+        }
+        
+        int pleft = mPaddingLeft;
+        int pright = mPaddingRight;
+        int ptop = mPaddingTop;
+        int pbottom = mPaddingBottom;
+
+        int widthSize;
+        int heightSize;
+
+        if (resizeWidth || resizeHeight) {
+            /* If we get here, it means we want to resize to match the
+                drawables aspect ratio, and we have the freedom to change at
+                least one dimension. 
+            */
+
+            // Get the max possible width given our constraints
+            widthSize = resolveAdjustedSize(w + pleft + pright,
+                                                 mMaxWidth, widthMeasureSpec);
+            
+            // Get the max possible height given our constraints
+            heightSize = resolveAdjustedSize(h + ptop + pbottom,
+                                                mMaxHeight, heightMeasureSpec);
+            
+            if (desiredAspect != 0.0f) {
+                // See what our actual aspect ratio is
+                float actualAspect = (float)(widthSize - pleft - pright) /
+                                        (heightSize - ptop - pbottom);
+                
+                if (Math.abs(actualAspect - desiredAspect) > 0.0000001) {
+                    
+                    boolean done = false;
+                    
+                    // Try adjusting width to be proportional to height
+                    if (resizeWidth) {
+                        int newWidth = (int)(desiredAspect *
+                                            (heightSize - ptop - pbottom))
+                                            + pleft + pright;
+                        if (newWidth <= widthSize) {
+                            widthSize = newWidth;
+                            done = true;
+                        } 
+                    }
+                    
+                    // Try adjusting height to be proportional to width
+                    if (!done && resizeHeight) {
+                        int newHeight = (int)((widthSize - pleft - pright)
+                                            / desiredAspect) + ptop + pbottom;
+                        if (newHeight <= heightSize) {
+                            heightSize = newHeight;
+                        } 
+                    }
+                }
+            }
+        } else {
+            /* We are either don't want to preserve the drawables aspect ratio,
+               or we are not allowed to change view dimensions. Just measure in
+               the normal way.
+            */
+            w += pleft + pright;
+            h += ptop + pbottom;
+                
+            w = Math.max(w, getSuggestedMinimumWidth());
+            h = Math.max(h, getSuggestedMinimumHeight());
+
+            widthSize = resolveSize(w, widthMeasureSpec);
+            heightSize = resolveSize(h, heightMeasureSpec);
+        }
+
+        setMeasuredDimension(widthSize, heightSize);
+    }
+
+    private int resolveAdjustedSize(int desiredSize, int maxSize,
+                                   int measureSpec) {
+        int result = desiredSize;
+        int specMode = MeasureSpec.getMode(measureSpec);
+        int specSize =  MeasureSpec.getSize(measureSpec);
+        switch (specMode) {
+            case MeasureSpec.UNSPECIFIED:
+                /* Parent says we can be as big as we want. Just don't be larger
+                   than max size imposed on ourselves.
+                */
+                result = Math.min(desiredSize, maxSize);
+                break;
+            case MeasureSpec.AT_MOST:
+                // Parent says we can be as big as we want, up to specSize. 
+                // Don't be larger than specSize, and don't be larger than 
+                // the max size imposed on ourselves.
+                result = Math.min(Math.min(desiredSize, specSize), maxSize);
+                break;
+            case MeasureSpec.EXACTLY:
+                // No choice. Do what we are told.
+                result = specSize;
+                break;
+        }
+        return result;
+    }
+
+    @Override
+    protected boolean setFrame(int l, int t, int r, int b) {
+        boolean changed = super.setFrame(l, t, r, b);
+        mHaveFrame = true;
+        configureBounds();
+        return changed;
+    }
+
+    private void configureBounds() {
+        if (mDrawable == null || !mHaveFrame) {
+            return;
+        }
+
+        int dwidth = mDrawableWidth;
+        int dheight = mDrawableHeight;
+
+        int vwidth = getWidth() - mPaddingLeft - mPaddingRight;
+        int vheight = getHeight() - mPaddingTop - mPaddingBottom;
+
+        boolean fits = (dwidth < 0 || vwidth == dwidth) &&
+                       (dheight < 0 || vheight == dheight);
+
+        if (dwidth <= 0 || dheight <= 0 || ScaleType.FIT_XY == mScaleType) {
+            /* If the drawable has no intrinsic size, or we're told to
+                scaletofit, then we just fill our entire view.
+            */
+            mDrawable.setBounds(0, 0, vwidth, vheight);
+            mDrawMatrix = null;
+        } else {
+            // We need to do the scaling ourself, so have the drawable
+            // use its native size.
+            mDrawable.setBounds(0, 0, dwidth, dheight);
+
+            if (ScaleType.MATRIX == mScaleType) {
+                // Use the specified matrix as-is.
+                if (mMatrix.isIdentity()) {
+                    mDrawMatrix = null;
+                } else {
+                    mDrawMatrix = mMatrix;
+                }
+            } else if (fits) {
+                // The bitmap fits exactly, no transform needed.
+                mDrawMatrix = null;
+            } else if (ScaleType.CENTER == mScaleType) {
+                // Center bitmap in view, no scaling.
+                mDrawMatrix = mMatrix;
+                mDrawMatrix.setTranslate((vwidth - dwidth) * 0.5f,
+                                         (vheight - dheight) * 0.5f);
+            } else if (ScaleType.CENTER_CROP == mScaleType) {
+                mDrawMatrix = mMatrix;
+
+                float scale;
+                float dx = 0, dy = 0;
+
+                if (dwidth * vheight > vwidth * dheight) {
+                    scale = (float) vheight / (float) dheight; 
+                    dx = (vwidth - dwidth * scale) * 0.5f;
+                } else {
+                    scale = (float) vwidth / (float) dwidth;
+                    dy = (vheight - dheight * scale) * 0.5f;
+                }
+
+                mDrawMatrix.setScale(scale, scale);
+                mDrawMatrix.postTranslate(dx, dy);
+            } else if (ScaleType.CENTER_INSIDE == mScaleType) {
+                mDrawMatrix = mMatrix;
+                float scale;
+                float dx;
+                float dy;
+                
+                if (dwidth <= vwidth && dheight <= vheight) {
+                    scale = 1.0f;
+                } else {
+                    scale = Math.min((float) vwidth / (float) dwidth, 
+                            (float) vheight / (float) dheight);
+                }
+                
+                dx = (vwidth - dwidth * scale) * 0.5f;
+                dy = (vheight - dheight * scale) * 0.5f;
+
+                mDrawMatrix.setScale(scale, scale);
+                mDrawMatrix.postTranslate(dx, dy);
+            } else {
+                // Generate the required transform.
+                mTempSrc.set(0, 0, dwidth, dheight);
+                mTempDst.set(0, 0, vwidth, vheight);
+                
+                mDrawMatrix = mMatrix;
+                mDrawMatrix.setRectToRect(mTempSrc, mTempDst,
+                                          scaleTypeToScaleToFit(mScaleType));
+            }
+        }
+    }
+
+    @Override
+    protected void drawableStateChanged() {
+        super.drawableStateChanged();
+        Drawable d = mDrawable;
+        if (d != null && d.isStateful()) {
+            d.setState(getDrawableState());
+        }
+    }
+
+    @Override 
+    protected void onDraw(Canvas canvas) {
+        super.onDraw(canvas);
+
+        if (mDrawable == null) {
+            return; // couldn't resolve the URI
+        }
+
+        if (mDrawableWidth == 0 || mDrawableHeight == 0) {
+            return;     // nothing to draw (empty bounds)
+        }
+
+        if (mDrawMatrix == null && mPaddingTop == 0 && mPaddingLeft == 0) {
+            mDrawable.draw(canvas);
+        } else {
+            int saveCount = canvas.getSaveCount();
+            canvas.save();
+            
+            if (mCropToPadding) {
+                final int scrollX = mScrollX;
+                final int scrollY = mScrollY;
+                canvas.clipRect(scrollX + mPaddingLeft, scrollY + mPaddingTop,
+                        scrollX + mRight - mLeft - mPaddingRight,
+                        scrollY + mBottom - mTop - mPaddingBottom);
+            }
+            
+            canvas.translate(mPaddingLeft, mPaddingTop);
+
+            if (mDrawMatrix != null) {
+                canvas.concat(mDrawMatrix);
+            }
+            mDrawable.draw(canvas);
+            canvas.restoreToCount(saveCount);
+        }
+    }
+
+    @Override
+    public int getBaseline() {
+        return mBaselineAligned ? getMeasuredHeight() : -1;
+    }
+    
+    /**
+     * Set a tinting option for the image.
+     * 
+     * @param color Color tint to apply.
+     * @param mode How to apply the color.  The standard mode is
+     * {@link PorterDuff.Mode#SRC_ATOP}
+     * 
+     * @attr ref android.R.styleable#ImageView_tint
+     */
+    public final void setColorFilter(int color, PorterDuff.Mode mode) {
+        setColorFilter(new PorterDuffColorFilter(color, mode));
+    }
+
+    public final void clearColorFilter() {
+        setColorFilter(null);
+    }
+    
+    /**
+     * Apply an arbitrary colorfilter to the image.
+     *
+     * @param cf the colorfilter to apply (may be null)
+     */
+    public void setColorFilter(ColorFilter cf) {
+        if (mColorFilter != cf) {
+            mColorFilter = cf;
+            applyColorMod();
+            invalidate();
+        }
+    }
+    
+    public void setAlpha(int alpha) {
+        alpha &= 0xFF;          // keep it legal
+        if (mAlpha != alpha) {
+            mAlpha = alpha;
+            applyColorMod();
+            invalidate();
+        }
+    }
+
+    private void applyColorMod() {
+        if (mDrawable != null) {
+            mDrawable.setColorFilter(mColorFilter);
+            mDrawable.setAlpha(mAlpha * mViewAlphaScale >> 8);
+        }
+    }
+}
diff --git a/core/java/android/widget/LinearLayout.java b/core/java/android/widget/LinearLayout.java
new file mode 100644
index 0000000..a9822f8
--- /dev/null
+++ b/core/java/android/widget/LinearLayout.java
@@ -0,0 +1,1318 @@
+/*
+ * 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 android.widget;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.util.AttributeSet;
+import android.view.Gravity;
+import android.view.View;
+import android.view.ViewDebug;
+import android.view.ViewGroup;
+import android.widget.RemoteViews.RemoteView;
+
+import com.android.internal.R;
+
+
+/**
+ * A Layout that arranges its children in a single column or a single row. The direction of 
+ * the row can be set by calling {@link #setOrientation(int) setOrientation()}. 
+ * You can also specify gravity, which specifies the alignment of all the child elements by
+ * calling {@link #setGravity(int) setGravity()} or specify that specific children 
+ * grow to fill up any remaining space in the layout by setting the <em>weight</em> member of
+ * {@link android.widget.LinearLayout.LayoutParams LinearLayout.LayoutParams}.
+ * The default orientation is horizontal.
+ *
+ * <p>
+ * Also see {@link LinearLayout.LayoutParams android.widget.LinearLayout.LayoutParams}
+ * for layout attributes </p>
+ */
+@RemoteView
+public class LinearLayout extends ViewGroup {
+    public static final int HORIZONTAL = 0;
+    public static final int VERTICAL = 1;
+
+    /**
+     * Whether the children of this layout are baseline aligned.  Only applicable
+     * if {@link #mOrientation} is horizontal.
+     */
+    private boolean mBaselineAligned = true;
+
+    /**
+     * If this layout is part of another layout that is baseline aligned,
+     * use the child at this index as the baseline.
+     *
+     * Note: this is orthogonal to {@link #mBaselineAligned}, which is concerned
+     * with whether the children of this layout are baseline aligned.
+     */
+    private int mBaselineAlignedChildIndex = 0;
+
+    /**
+     * The additional offset to the child's baseline.
+     * We'll calculate the baseline of this layout as we measure vertically; for
+     * horizontal linear layouts, the offset of 0 is appropriate.
+     */
+    private int mBaselineChildTop = 0;
+
+    private int mOrientation;
+    private int mGravity = Gravity.LEFT | Gravity.TOP;
+    private int mTotalLength;
+
+    private float mWeightSum;
+
+    private int[] mMaxAscent;
+    private int[] mMaxDescent;
+
+    private static final int VERTICAL_GRAVITY_COUNT = 4;
+
+    private static final int INDEX_CENTER_VERTICAL = 0;
+    private static final int INDEX_TOP = 1;
+    private static final int INDEX_BOTTOM = 2;
+    private static final int INDEX_FILL = 3;    
+
+    public LinearLayout(Context context) {
+        super(context);
+    }
+
+    public LinearLayout(Context context, AttributeSet attrs) {
+        super(context, attrs);
+
+        TypedArray a = 
+            context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.LinearLayout);
+
+        int index = a.getInt(com.android.internal.R.styleable.LinearLayout_orientation, -1);
+        if (index >= 0) {
+            setOrientation(index);
+        }
+
+        index = a.getInt(com.android.internal.R.styleable.LinearLayout_gravity, -1);
+        if (index >= 0) {
+            setGravity(index);
+        }
+
+        boolean baselineAligned = a.getBoolean(R.styleable.LinearLayout_baselineAligned, true);
+        if (!baselineAligned) {
+            setBaselineAligned(baselineAligned);
+        }
+
+        mWeightSum = a.getFloat(R.styleable.LinearLayout_weightSum, -1.0f);
+
+        mBaselineAlignedChildIndex =
+                a.getInt(com.android.internal.R.styleable.LinearLayout_baselineAlignedChildIndex, -1);
+
+        a.recycle();
+    }
+
+    /**
+     * <p>Indicates whether widgets contained within this layout are aligned
+     * on their baseline or not.</p>
+     *
+     * @return true when widgets are baseline-aligned, false otherwise
+     */
+    public boolean isBaselineAligned() {
+        return mBaselineAligned;
+    }
+
+    /**
+     * <p>Defines whether widgets contained in this layout are
+     * baseline-aligned or not.</p>
+     *
+     * @param baselineAligned true to align widgets on their baseline,
+     *         false otherwise
+     *
+     * @attr ref android.R.styleable#LinearLayout_baselineAligned
+     */
+    @android.view.RemotableViewMethod
+    public void setBaselineAligned(boolean baselineAligned) {
+        mBaselineAligned = baselineAligned;
+    }
+
+    @Override
+    public int getBaseline() {
+        if (mBaselineAlignedChildIndex < 0) {
+            return super.getBaseline();
+        }
+
+        if (getChildCount() <= mBaselineAlignedChildIndex) {
+            throw new RuntimeException("mBaselineAlignedChildIndex of LinearLayout "
+                    + "set to an index that is out of bounds.");
+        }
+
+        final View child = getChildAt(mBaselineAlignedChildIndex);
+        final int childBaseline = child.getBaseline();
+
+        if (childBaseline == -1) {
+            if (mBaselineAlignedChildIndex == 0) {
+                // this is just the default case, safe to return -1
+                return -1;
+            }
+            // the user picked an index that points to something that doesn't
+            // know how to calculate its baseline.
+            throw new RuntimeException("mBaselineAlignedChildIndex of LinearLayout "
+                    + "points to a View that doesn't know how to get its baseline.");
+        }
+
+        // TODO: This should try to take into account the virtual offsets
+        // (See getNextLocationOffset and getLocationOffset)
+        // We should add to childTop:
+        // sum([getNextLocationOffset(getChildAt(i)) / i < mBaselineAlignedChildIndex])
+        // and also add:
+        // getLocationOffset(child)
+        int childTop = mBaselineChildTop;
+
+        if (mOrientation == VERTICAL) {
+            final int majorGravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
+            if (majorGravity != Gravity.TOP) {
+               switch (majorGravity) {
+                   case Gravity.BOTTOM:
+                       childTop = mBottom - mTop - mPaddingBottom - mTotalLength;
+                       break;
+
+                   case Gravity.CENTER_VERTICAL:
+                       childTop += ((mBottom - mTop - mPaddingTop - mPaddingBottom) -
+                               mTotalLength) / 2;
+                       break;
+               }
+            }
+        }
+
+        LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) child.getLayoutParams();
+        return childTop + lp.topMargin + childBaseline;
+    }
+
+    /**
+     * @return The index of the child that will be used if this layout is
+     *   part of a larger layout that is baseline aligned, or -1 if none has
+     *   been set.
+     */
+    public int getBaselineAlignedChildIndex() {
+        return mBaselineAlignedChildIndex;
+    }
+
+    /**
+     * @param i The index of the child that will be used if this layout is
+     *          part of a larger layout that is baseline aligned.
+     * 
+     * @attr ref android.R.styleable#LinearLayout_baselineAlignedChildIndex
+     */
+    @android.view.RemotableViewMethod
+    public void setBaselineAlignedChildIndex(int i) {
+        if ((i < 0) || (i >= getChildCount())) {
+            throw new IllegalArgumentException("base aligned child index out "
+                    + "of range (0, " + getChildCount() + ")");
+        }
+        mBaselineAlignedChildIndex = i;
+    }
+
+    /**
+     * <p>Returns the view at the specified index. This method can be overriden
+     * to take into account virtual children. Refer to
+     * {@link android.widget.TableLayout} and {@link android.widget.TableRow}
+     * for an example.</p>
+     *
+     * @param index the child's index
+     * @return the child at the specified index
+     */
+    View getVirtualChildAt(int index) {
+        return getChildAt(index);
+    }
+
+    /**
+     * <p>Returns the virtual number of children. This number might be different
+     * than the actual number of children if the layout can hold virtual
+     * children. Refer to
+     * {@link android.widget.TableLayout} and {@link android.widget.TableRow}
+     * for an example.</p>
+     *
+     * @return the virtual number of children
+     */
+    int getVirtualChildCount() {
+        return getChildCount();
+    }
+
+    /**
+     * Returns the desired weights sum.
+     *
+     * @return A number greater than 0.0f if the weight sum is defined, or
+     *         a number lower than or equals to 0.0f if not weight sum is
+     *         to be used.
+     */
+    public float getWeightSum() {
+        return mWeightSum;
+    }
+
+    /**
+     * Defines the desired weights sum. If unspecified the weights sum is computed
+     * at layout time by adding the layout_weight of each child.
+     *
+     * This can be used for instance to give a single child 50% of the total
+     * available space by giving it a layout_weight of 0.5 and setting the
+     * weightSum to 1.0.
+     *
+     * @param weightSum a number greater than 0.0f, or a number lower than or equals
+     *        to 0.0f if the weight sum should be computed from the children's
+     *        layout_weight
+     */
+    @android.view.RemotableViewMethod
+    public void setWeightSum(float weightSum) {
+        mWeightSum = Math.max(0.0f, weightSum);
+    }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        if (mOrientation == VERTICAL) {
+            measureVertical(widthMeasureSpec, heightMeasureSpec);
+        } else {
+            measureHorizontal(widthMeasureSpec, heightMeasureSpec);
+        }
+    }
+
+    /**
+     * Measures the children when the orientation of this LinearLayout is set
+     * to {@link #VERTICAL}.
+     *
+     * @param widthMeasureSpec Horizontal space requirements as imposed by the parent.
+     * @param heightMeasureSpec Vertical space requirements as imposed by the parent.
+     *
+     * @see #getOrientation()
+     * @see #setOrientation(int)
+     * @see #onMeasure(int, int)
+     */
+    void measureVertical(int widthMeasureSpec, int heightMeasureSpec) {
+        mTotalLength = 0;
+        int maxWidth = 0;
+        int alternativeMaxWidth = 0;
+        int weightedMaxWidth = 0;
+        boolean allFillParent = true;
+        float totalWeight = 0;
+
+        final int count = getVirtualChildCount();
+        
+        final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
+        final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
+
+        boolean matchWidth = false;
+
+        final int baselineChildIndex = mBaselineAlignedChildIndex;        
+
+        // See how tall everyone is. Also remember max width.
+        for (int i = 0; i < count; ++i) {
+            final View child = getVirtualChildAt(i);
+
+            if (child == null) {
+                mTotalLength += measureNullChild(i);
+                continue;
+            }
+
+            if (child.getVisibility() == View.GONE) {
+               i += getChildrenSkipCount(child, i);
+               continue;
+            }
+
+            LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) child.getLayoutParams();
+
+            totalWeight += lp.weight;
+            
+            if (heightMode == MeasureSpec.EXACTLY && lp.height == 0 && lp.weight > 0) {
+                // Optimization: don't bother measuring children who are going to use
+                // leftover space. These views will get measured again down below if
+                // there is any leftover space.
+                mTotalLength += lp.topMargin + lp.bottomMargin;
+            } else {
+               int oldHeight = Integer.MIN_VALUE;
+
+               if (lp.height == 0 && lp.weight > 0) {
+                   // heightMode is either UNSPECIFIED OR AT_MOST, and this child
+                   // wanted to stretch to fill available space. Translate that to
+                   // WRAP_CONTENT so that it does not end up with a height of 0
+                   oldHeight = 0;
+                   lp.height = LayoutParams.WRAP_CONTENT;
+               }
+
+               // Determine how big this child would like to.  If this or
+               // previous children have given a weight, then we allow it to
+               // use all available space (and we will shrink things later
+               // if needed).
+               measureChildBeforeLayout(
+                       child, i, widthMeasureSpec, 0, heightMeasureSpec,
+                       totalWeight == 0 ? mTotalLength : 0);
+
+               if (oldHeight != Integer.MIN_VALUE) {
+                   lp.height = oldHeight;
+               }
+
+               mTotalLength += child.getMeasuredHeight() + lp.topMargin +
+                       lp.bottomMargin + getNextLocationOffset(child);
+            }
+
+            /**
+             * If applicable, compute the additional offset to the child's baseline
+             * we'll need later when asked {@link #getBaseline}.
+             */
+            if ((baselineChildIndex >= 0) && (baselineChildIndex == i + 1)) {
+               mBaselineChildTop = mTotalLength;
+            }
+
+            // if we are trying to use a child index for our baseline, the above
+            // book keeping only works if there are no children above it with
+            // weight.  fail fast to aid the developer.
+            if (i < baselineChildIndex && lp.weight > 0) {
+                throw new RuntimeException("A child of LinearLayout with index "
+                        + "less than mBaselineAlignedChildIndex has weight > 0, which "
+                        + "won't work.  Either remove the weight, or don't set "
+                        + "mBaselineAlignedChildIndex.");
+            }
+
+            boolean matchWidthLocally = false;
+            if (widthMode != MeasureSpec.EXACTLY && lp.width == LayoutParams.FILL_PARENT) {
+                // The width of the linear layout will scale, and at least one
+                // child said it wanted to match our width. Set a flag
+                // indicating that we need to remeasure at least that view when
+                // we know our width.
+                matchWidth = true;
+                matchWidthLocally = true;
+            }
+
+            final int margin = lp.leftMargin + lp.rightMargin;
+            final int measuredWidth = child.getMeasuredWidth() + margin;
+            maxWidth = Math.max(maxWidth, measuredWidth);
+
+            allFillParent = allFillParent && lp.width == LayoutParams.FILL_PARENT;
+            if (lp.weight > 0) {
+                /*
+                 * Widths of weighted Views are bogus if we end up
+                 * remeasuring, so keep them separate.
+                 */
+                weightedMaxWidth = Math.max(weightedMaxWidth,
+                        matchWidthLocally ? margin : measuredWidth);
+            } else {
+                alternativeMaxWidth = Math.max(alternativeMaxWidth,
+                        matchWidthLocally ? margin : measuredWidth);
+            }
+
+            i += getChildrenSkipCount(child, i);
+        }
+        
+        // Add in our padding
+        mTotalLength += mPaddingTop + mPaddingBottom;
+
+        int heightSize = mTotalLength;
+
+        // Check against our minimum height
+        heightSize = Math.max(heightSize, getSuggestedMinimumHeight());
+        
+        // Reconcile our calculated size with the heightMeasureSpec
+        heightSize = resolveSize(heightSize, heightMeasureSpec);
+        
+        // Either expand children with weight to take up available space or
+        // shrink them if they extend beyond our current bounds
+        int delta = heightSize - mTotalLength;
+        if (delta != 0 && totalWeight > 0.0f) {
+            float weightSum = mWeightSum > 0.0f ? mWeightSum : totalWeight;
+
+            mTotalLength = 0;
+
+            for (int i = 0; i < count; ++i) {
+                final View child = getVirtualChildAt(i);
+                
+                if (child.getVisibility() == View.GONE) {
+                    continue;
+                }
+                
+                LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) child.getLayoutParams();
+                
+                float childExtra = lp.weight;
+                if (childExtra > 0) {
+                    // Child said it could absorb extra space -- give him his share
+                    int share = (int) (childExtra * delta / weightSum);
+                    weightSum -= childExtra;
+                    delta -= share;
+
+                    final int childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec,
+                            mPaddingLeft + mPaddingRight +
+                                    lp.leftMargin + lp.rightMargin, lp.width);
+
+                    // TODO: Use a field like lp.isMeasured to figure out if this
+                    // child has been previously measured
+                    if ((lp.height != 0) || (heightMode != MeasureSpec.EXACTLY)) {
+                        // child was measured once already above...
+                        // base new measurement on stored values
+                        int childHeight = child.getMeasuredHeight() + share;
+                        if (childHeight < 0) {
+                            childHeight = 0;
+                        }
+                        
+                        child.measure(childWidthMeasureSpec,
+                                MeasureSpec.makeMeasureSpec(childHeight, MeasureSpec.EXACTLY));
+                    } else {
+                        // child was skipped in the loop above.
+                        // Measure for this first time here      
+                        child.measure(childWidthMeasureSpec,
+                                MeasureSpec.makeMeasureSpec(share > 0 ? share : 0,
+                                        MeasureSpec.EXACTLY));
+                    }
+                }
+
+                final int margin =  lp.leftMargin + lp.rightMargin;
+                final int measuredWidth = child.getMeasuredWidth() + margin;
+                maxWidth = Math.max(maxWidth, measuredWidth);
+
+                boolean matchWidthLocally = widthMode != MeasureSpec.EXACTLY &&
+                        lp.width == LayoutParams.FILL_PARENT;
+
+                alternativeMaxWidth = Math.max(alternativeMaxWidth,
+                        matchWidthLocally ? margin : measuredWidth);
+
+                allFillParent = allFillParent && lp.width == LayoutParams.FILL_PARENT;
+
+                mTotalLength += child.getMeasuredHeight() + lp.topMargin +
+                        lp.bottomMargin + getNextLocationOffset(child);
+            }
+
+            // Add in our padding
+            mTotalLength += mPaddingTop + mPaddingBottom;            
+        } else {
+            alternativeMaxWidth = Math.max(alternativeMaxWidth,
+                                           weightedMaxWidth);
+        }
+
+        if (!allFillParent && widthMode != MeasureSpec.EXACTLY) {
+            maxWidth = alternativeMaxWidth;
+        }
+        
+        maxWidth += mPaddingLeft + mPaddingRight;
+
+        // Check against our minimum width
+        maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());
+        
+        setMeasuredDimension(resolveSize(maxWidth, widthMeasureSpec), heightSize);
+
+        if (matchWidth) {
+            forceUniformWidth(count, heightMeasureSpec);
+        }
+    }
+
+    private void forceUniformWidth(int count, int heightMeasureSpec) {
+        // Pretend that the linear layout has an exact size.
+        int uniformMeasureSpec = MeasureSpec.makeMeasureSpec(getMeasuredWidth(),
+                MeasureSpec.EXACTLY);
+        for (int i = 0; i< count; ++i) {
+           final View child = getVirtualChildAt(i);
+           if (child.getVisibility() != GONE) { 
+               LinearLayout.LayoutParams lp = ((LinearLayout.LayoutParams)child.getLayoutParams());
+               
+               if (lp.width == LayoutParams.FILL_PARENT) {
+                   // Temporarily force children to reuse their old measured height
+                   // FIXME: this may not be right for something like wrapping text?
+                   int oldHeight = lp.height;
+                   lp.height = child.getMeasuredHeight();
+                   
+                   // Remeasue with new dimensions
+                   measureChildWithMargins(child, uniformMeasureSpec, 0, heightMeasureSpec, 0);
+                   lp.height = oldHeight;
+               }
+           }
+        }
+    }
+
+    /**
+     * Measures the children when the orientation of this LinearLayout is set
+     * to {@link #HORIZONTAL}.
+     *
+     * @param widthMeasureSpec Horizontal space requirements as imposed by the parent.
+     * @param heightMeasureSpec Vertical space requirements as imposed by the parent.
+     *
+     * @see #getOrientation()
+     * @see #setOrientation(int)
+     * @see #onMeasure(int, int) 
+     */
+    void measureHorizontal(int widthMeasureSpec, int heightMeasureSpec) {
+        mTotalLength = 0;
+        int maxHeight = 0;
+        int alternativeMaxHeight = 0;
+        int weightedMaxHeight = 0;
+        boolean allFillParent = true;
+        float totalWeight = 0;
+
+        final int count = getVirtualChildCount();
+        
+        final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
+        final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
+
+        boolean matchHeight = false;
+
+        if (mMaxAscent == null || mMaxDescent == null) {
+            mMaxAscent = new int[VERTICAL_GRAVITY_COUNT];
+            mMaxDescent = new int[VERTICAL_GRAVITY_COUNT];
+        }
+
+        final int[] maxAscent = mMaxAscent;
+        final int[] maxDescent = mMaxDescent;
+
+        maxAscent[0] = maxAscent[1] = maxAscent[2] = maxAscent[3] = -1;
+        maxDescent[0] = maxDescent[1] = maxDescent[2] = maxDescent[3] = -1;
+
+        final boolean baselineAligned = mBaselineAligned;
+
+        // See how wide everyone is. Also remember max height.
+        for (int i = 0; i < count; ++i) {
+            final View child = getVirtualChildAt(i);
+
+            if (child == null) {
+                mTotalLength += measureNullChild(i);
+                continue;
+            }
+           
+            if (child.getVisibility() == GONE) {
+                i += getChildrenSkipCount(child, i);
+                continue;
+            }
+
+            final LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) child.getLayoutParams();
+
+            totalWeight += lp.weight;
+            
+            if (widthMode == MeasureSpec.EXACTLY && lp.width == 0 && lp.weight > 0) {
+                // Optimization: don't bother measuring children who are going to use
+                // leftover space. These views will get measured again down below if
+                // there is any leftover space.
+                mTotalLength += lp.leftMargin + lp.rightMargin;
+
+                // Baseline alignment requires to measure widgets to obtain the
+                // baseline offset (in particular for TextViews).
+                // The following defeats the optimization mentioned above.
+                // Allow the child to use as much space as it wants because we
+                // can shrink things later (and re-measure).
+                if (baselineAligned) {
+                    final int freeSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
+                    child.measure(freeSpec, freeSpec);
+                }
+            } else {
+                int oldWidth = Integer.MIN_VALUE;
+
+                if (lp.width == 0 && lp.weight > 0) {
+                    // widthMode is either UNSPECIFIED OR AT_MOST, and this child
+                    // wanted to stretch to fill available space. Translate that to
+                    // WRAP_CONTENT so that it does not end up with a width of 0
+                    oldWidth = 0;
+                    lp.width = LayoutParams.WRAP_CONTENT;
+                }
+
+                // Determine how big this child would like to be. If this or
+                // previous children have given a weight, then we allow it to
+                // use all available space (and we will shrink things later
+                // if needed).
+                measureChildBeforeLayout(child, i, widthMeasureSpec,
+                        totalWeight == 0 ? mTotalLength : 0,
+                        heightMeasureSpec, 0);
+
+                if (oldWidth != Integer.MIN_VALUE) {
+                    lp.width = oldWidth;
+                }
+               
+                mTotalLength += child.getMeasuredWidth() + lp.leftMargin +
+                        lp.rightMargin + getNextLocationOffset(child);
+            }
+
+            boolean matchHeightLocally = false;
+            if (heightMode != MeasureSpec.EXACTLY && lp.height == LayoutParams.FILL_PARENT) {
+                // The height of the linear layout will scale, and at least one
+                // child said it wanted to match our height. Set a flag indicating that
+                // we need to remeasure at least that view when we know our height.
+                matchHeight = true;
+                matchHeightLocally = true;
+            }
+
+            final int margin = lp.topMargin + lp.bottomMargin;
+            final int childHeight = child.getMeasuredHeight() + margin;
+
+            if (baselineAligned) {
+                final int childBaseline = child.getBaseline();
+                if (childBaseline != -1) {
+                    // Translates the child's vertical gravity into an index
+                    // in the range 0..VERTICAL_GRAVITY_COUNT
+                    final int gravity = (lp.gravity < 0 ? mGravity : lp.gravity)
+                            & Gravity.VERTICAL_GRAVITY_MASK;
+                    final int index = ((gravity >> Gravity.AXIS_Y_SHIFT)
+                            & ~Gravity.AXIS_SPECIFIED) >> 1;
+
+                    maxAscent[index] = Math.max(maxAscent[index], childBaseline);
+                    maxDescent[index] = Math.max(maxDescent[index], childHeight - childBaseline);
+                }
+            }
+
+            maxHeight = Math.max(maxHeight, childHeight);
+
+            allFillParent = allFillParent && lp.height == LayoutParams.FILL_PARENT;
+            if (lp.weight > 0) {
+                /*
+                 * Heights of weighted Views are bogus if we end up
+                 * remeasuring, so keep them separate.
+                 */
+                weightedMaxHeight = Math.max(weightedMaxHeight,
+                        matchHeightLocally ? margin : childHeight);
+            } else {
+                alternativeMaxHeight = Math.max(alternativeMaxHeight,
+                        matchHeightLocally ? margin : childHeight);
+            }
+
+            i += getChildrenSkipCount(child, i);
+        }
+
+        // Check mMaxAscent[INDEX_TOP] first because it maps to Gravity.TOP,
+        // the most common case
+        if (maxAscent[INDEX_TOP] != -1 ||
+                maxAscent[INDEX_CENTER_VERTICAL] != -1 ||
+                maxAscent[INDEX_BOTTOM] != -1 ||
+                maxAscent[INDEX_FILL] != -1) {
+            final int ascent = Math.max(maxAscent[INDEX_FILL],
+                    Math.max(maxAscent[INDEX_CENTER_VERTICAL],
+                    Math.max(maxAscent[INDEX_TOP], maxAscent[INDEX_BOTTOM])));
+            final int descent = Math.max(maxDescent[INDEX_FILL],
+                    Math.max(maxDescent[INDEX_CENTER_VERTICAL],
+                    Math.max(maxDescent[INDEX_TOP], maxDescent[INDEX_BOTTOM])));
+            maxHeight = Math.max(maxHeight, ascent + descent);
+        }
+
+        // Add in our padding
+        mTotalLength += mPaddingLeft + mPaddingRight;
+        
+        int widthSize = mTotalLength;
+        
+        // Check against our minimum width
+        widthSize = Math.max(widthSize, getSuggestedMinimumWidth());
+        
+        // Reconcile our calculated size with the widthMeasureSpec
+        widthSize = resolveSize(widthSize, widthMeasureSpec);
+        
+        // Either expand children with weight to take up available space or
+        // shrink them if they extend beyond our current bounds
+        int delta = widthSize - mTotalLength;
+        if (delta != 0 && totalWeight > 0.0f) {
+            float weightSum = mWeightSum > 0.0f ? mWeightSum : totalWeight;
+
+            maxAscent[0] = maxAscent[1] = maxAscent[2] = maxAscent[3] = -1;
+            maxDescent[0] = maxDescent[1] = maxDescent[2] = maxDescent[3] = -1;
+            maxHeight = -1;
+
+            mTotalLength = 0;
+
+            for (int i = 0; i < count; ++i) {
+                final View child = getVirtualChildAt(i);
+
+                if (child == null || child.getVisibility() == View.GONE) {
+                    continue;
+                }
+                
+                final LinearLayout.LayoutParams lp =
+                        (LinearLayout.LayoutParams) child.getLayoutParams();
+
+                float childExtra = lp.weight;
+                if (childExtra > 0) {
+                    // Child said it could absorb extra space -- give him his share
+                    int share = (int) (childExtra * delta / weightSum);
+                    weightSum -= childExtra;
+                    delta -= share;
+
+                    final int childHeightMeasureSpec = getChildMeasureSpec(
+                            heightMeasureSpec,
+                            mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin,
+                            lp.height);
+
+                    // TODO: Use a field like lp.isMeasured to figure out if this
+                    // child has been previously measured
+                    if ((lp.width != 0) || (widthMode != MeasureSpec.EXACTLY)) {
+                        // child was measured once already above ... base new measurement
+                        // on stored values
+                        int childWidth = child.getMeasuredWidth() + share;
+                        if (childWidth < 0) {
+                            childWidth = 0;
+                        }
+
+                        child.measure(
+                            MeasureSpec.makeMeasureSpec(childWidth, MeasureSpec.EXACTLY),
+                            childHeightMeasureSpec);
+                    } else {
+                        // child was skipped in the loop above. Measure for this first time here
+                        child.measure(MeasureSpec.makeMeasureSpec(
+                                share > 0 ? share : 0, MeasureSpec.EXACTLY),
+                                childHeightMeasureSpec);
+                    }
+                }
+
+                mTotalLength += child.getMeasuredWidth() + lp.leftMargin +
+                        lp.rightMargin + getNextLocationOffset(child);
+
+                boolean matchHeightLocally = heightMode != MeasureSpec.EXACTLY &&
+                        lp.height == LayoutParams.FILL_PARENT;
+
+                final int margin = lp.topMargin + lp .bottomMargin;
+                int childHeight = child.getMeasuredHeight() + margin;
+                maxHeight = Math.max(maxHeight, childHeight);
+                alternativeMaxHeight = Math.max(alternativeMaxHeight,
+                        matchHeightLocally ? margin : childHeight);
+
+                allFillParent = allFillParent && lp.height == LayoutParams.FILL_PARENT;
+
+                if (baselineAligned) {
+                    final int childBaseline = child.getBaseline();
+                    if (childBaseline != -1) {
+                        // Translates the child's vertical gravity into an index in the range 0..2
+                        final int gravity = (lp.gravity < 0 ? mGravity : lp.gravity)
+                                & Gravity.VERTICAL_GRAVITY_MASK;
+                        final int index = ((gravity >> Gravity.AXIS_Y_SHIFT)
+                                & ~Gravity.AXIS_SPECIFIED) >> 1;
+
+                        maxAscent[index] = Math.max(maxAscent[index], childBaseline);
+                        maxDescent[index] = Math.max(maxDescent[index],
+                                childHeight - childBaseline);
+                    }
+                }
+            }
+
+            // Add in our padding
+            mTotalLength += mPaddingLeft + mPaddingRight;
+
+            // Check mMaxAscent[INDEX_TOP] first because it maps to Gravity.TOP,
+            // the most common case
+            if (maxAscent[INDEX_TOP] != -1 ||
+                    maxAscent[INDEX_CENTER_VERTICAL] != -1 ||
+                    maxAscent[INDEX_BOTTOM] != -1 ||
+                    maxAscent[INDEX_FILL] != -1) {
+                final int ascent = Math.max(maxAscent[INDEX_FILL],
+                        Math.max(maxAscent[INDEX_CENTER_VERTICAL],
+                        Math.max(maxAscent[INDEX_TOP], maxAscent[INDEX_BOTTOM])));
+                final int descent = Math.max(maxDescent[INDEX_FILL],
+                        Math.max(maxDescent[INDEX_CENTER_VERTICAL],
+                        Math.max(maxDescent[INDEX_TOP], maxDescent[INDEX_BOTTOM])));
+                maxHeight = Math.max(maxHeight, ascent + descent);
+            }
+        } else {
+            alternativeMaxHeight = Math.max(alternativeMaxHeight, weightedMaxHeight);
+        }
+
+        if (!allFillParent && heightMode != MeasureSpec.EXACTLY) {
+            maxHeight = alternativeMaxHeight;
+        }
+        
+        maxHeight += mPaddingTop + mPaddingBottom;
+
+        // Check against our minimum height
+        maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight());
+        
+        setMeasuredDimension(widthSize, resolveSize(maxHeight, heightMeasureSpec));
+
+        if (matchHeight) {
+            forceUniformHeight(count, widthMeasureSpec);
+        }
+    }
+
+    private void forceUniformHeight(int count, int widthMeasureSpec) {
+        // Pretend that the linear layout has an exact size. This is the measured height of
+        // ourselves. The measured height should be the max height of the children, changed
+        // to accomodate the heightMesureSpec from the parent
+        int uniformMeasureSpec = MeasureSpec.makeMeasureSpec(getMeasuredHeight(),
+                MeasureSpec.EXACTLY);
+        for (int i = 0; i < count; ++i) {
+           final View child = getVirtualChildAt(i);
+           if (child.getVisibility() != GONE) { 
+               LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) child.getLayoutParams();
+               
+               if (lp.height == LayoutParams.FILL_PARENT) {
+                   // Temporarily force children to reuse their old measured width
+                   // FIXME: this may not be right for something like wrapping text?
+                   int oldWidth = lp.width;
+                   lp.width = child.getMeasuredWidth();
+                   
+                   // Remeasure with new dimensions
+                   measureChildWithMargins(child, widthMeasureSpec, 0, uniformMeasureSpec, 0);
+                   lp.width = oldWidth;
+               }
+           }
+        }
+    }
+
+    /**
+     * <p>Returns the number of children to skip after measuring/laying out
+     * the specified child.</p>
+     *
+     * @param child the child after which we want to skip children
+     * @param index the index of the child after which we want to skip children
+     * @return the number of children to skip, 0 by default
+     */
+    int getChildrenSkipCount(View child, int index) {
+        return 0;
+    }
+
+    /**
+     * <p>Returns the size (width or height) that should be occupied by a null
+     * child.</p>
+     *
+     * @param childIndex the index of the null child
+     * @return the width or height of the child depending on the orientation
+     */
+    int measureNullChild(int childIndex) {
+        return 0;
+    }
+
+    /**
+     * <p>Measure the child according to the parent's measure specs. This
+     * method should be overriden by subclasses to force the sizing of
+     * children. This method is called by {@link #measureVertical(int, int)} and
+     * {@link #measureHorizontal(int, int)}.</p>
+     *
+     * @param child the child to measure
+     * @param childIndex the index of the child in this view
+     * @param widthMeasureSpec horizontal space requirements as imposed by the parent
+     * @param totalWidth extra space that has been used up by the parent horizontally
+     * @param heightMeasureSpec vertical space requirements as imposed by the parent
+     * @param totalHeight extra space that has been used up by the parent vertically
+     */
+    void measureChildBeforeLayout(View child, int childIndex,
+            int widthMeasureSpec, int totalWidth, int heightMeasureSpec,
+            int totalHeight) {
+        measureChildWithMargins(child, widthMeasureSpec, totalWidth,
+                heightMeasureSpec, totalHeight);
+    }
+
+    /**
+     * <p>Return the location offset of the specified child. This can be used
+     * by subclasses to change the location of a given widget.</p>
+     *
+     * @param child the child for which to obtain the location offset
+     * @return the location offset in pixels
+     */
+    int getLocationOffset(View child) {
+        return 0;
+    }
+
+    /**
+     * <p>Return the size offset of the next sibling of the specified child.
+     * This can be used by subclasses to change the location of the widget
+     * following <code>child</code>.</p>
+     *
+     * @param child the child whose next sibling will be moved
+     * @return the location offset of the next child in pixels
+     */
+    int getNextLocationOffset(View child) {
+        return 0;
+    }
+
+    @Override
+    protected void onLayout(boolean changed, int l, int t, int r, int b) {
+        if (mOrientation == VERTICAL) {
+            layoutVertical();
+        } else {
+            layoutHorizontal();
+        }
+    }
+
+    /**
+     * Position the children during a layout pass if the orientation of this
+     * LinearLayout is set to {@link #VERTICAL}.
+     *
+     * @see #getOrientation()
+     * @see #setOrientation(int)
+     * @see #onLayout(boolean, int, int, int, int)
+     */
+    void layoutVertical() {
+        final int paddingLeft = mPaddingLeft;
+
+        int childTop = mPaddingTop;
+        int childLeft = paddingLeft;
+        
+        // Where right end of child should go
+        final int width = mRight - mLeft;
+        int childRight = width - mPaddingRight;
+        
+        // Space available for child
+        int childSpace = width - paddingLeft - mPaddingRight;
+        
+        final int count = getVirtualChildCount();
+
+        final int majorGravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
+        final int minorGravity = mGravity & Gravity.HORIZONTAL_GRAVITY_MASK;
+
+        if (majorGravity != Gravity.TOP) {
+           switch (majorGravity) {
+               case Gravity.BOTTOM:
+                   // mTotalLength contains the padding already, we add the top
+                   // padding to compensate
+                   childTop = mBottom - mTop + mPaddingTop - mTotalLength;
+                   break;
+
+               case Gravity.CENTER_VERTICAL:
+                   childTop += ((mBottom - mTop)  - mTotalLength) / 2;
+                   break;
+           }
+           
+        }
+        
+        for (int i = 0; i < count; i++) {
+            final View child = getVirtualChildAt(i);
+            if (child == null) {
+                childTop += measureNullChild(i);
+            } else if (child.getVisibility() != GONE) {
+                final int childWidth = child.getMeasuredWidth();
+                final int childHeight = child.getMeasuredHeight();
+                
+                final LinearLayout.LayoutParams lp =
+                        (LinearLayout.LayoutParams) child.getLayoutParams();
+                
+                int gravity = lp.gravity;
+                if (gravity < 0) {
+                    gravity = minorGravity;
+                }
+                
+                switch (gravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
+                    case Gravity.LEFT:
+                        childLeft = paddingLeft + lp.leftMargin;
+                        break;
+
+                    case Gravity.CENTER_HORIZONTAL:
+                        childLeft = paddingLeft + ((childSpace - childWidth) / 2)
+                                + lp.leftMargin - lp.rightMargin;
+                        break;
+
+                    case Gravity.RIGHT:
+                        childLeft = childRight - childWidth - lp.rightMargin;
+                        break;
+                }
+                
+                
+                childTop += lp.topMargin;
+                setChildFrame(child, childLeft, childTop + getLocationOffset(child),
+                        childWidth, childHeight);
+                childTop += childHeight + lp.bottomMargin + getNextLocationOffset(child);
+
+                i += getChildrenSkipCount(child, i);
+            }
+        }
+    }
+
+    /**
+     * Position the children during a layout pass if the orientation of this
+     * LinearLayout is set to {@link #HORIZONTAL}.
+     *
+     * @see #getOrientation()
+     * @see #setOrientation(int)
+     * @see #onLayout(boolean, int, int, int, int)
+     */
+    void layoutHorizontal() {
+        final int paddingTop = mPaddingTop;
+
+        int childTop = paddingTop;
+        int childLeft = mPaddingLeft;
+        
+        // Where bottom of child should go
+        final int height = mBottom - mTop;
+        int childBottom = height - mPaddingBottom; 
+        
+        // Space available for child
+        int childSpace = height - paddingTop - mPaddingBottom;
+
+        final int count = getVirtualChildCount();
+
+        final int majorGravity = mGravity & Gravity.HORIZONTAL_GRAVITY_MASK;
+        final int minorGravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
+
+        final boolean baselineAligned = mBaselineAligned;
+
+        final int[] maxAscent = mMaxAscent;
+        final int[] maxDescent = mMaxDescent;
+
+        if (majorGravity != Gravity.LEFT) {
+            switch (majorGravity) {
+                case Gravity.RIGHT:
+                    // mTotalLength contains the padding already, we add the left
+                    // padding to compensate
+                    childLeft = mRight - mLeft + mPaddingLeft - mTotalLength;
+                    break;
+
+                case Gravity.CENTER_HORIZONTAL:
+                    childLeft += ((mRight - mLeft) - mTotalLength) / 2;
+                    break;
+            }
+       }
+
+        for (int i = 0; i < count; i++) {
+            final View child = getVirtualChildAt(i);
+
+            if (child == null) {
+                childLeft += measureNullChild(i);
+            } else if (child.getVisibility() != GONE) {
+                final int childWidth = child.getMeasuredWidth();
+                final int childHeight = child.getMeasuredHeight();
+                int childBaseline = -1;
+
+                final LinearLayout.LayoutParams lp =
+                        (LinearLayout.LayoutParams) child.getLayoutParams();
+
+                if (baselineAligned && lp.height != LayoutParams.FILL_PARENT) {
+                    childBaseline = child.getBaseline();
+                }
+                
+                int gravity = lp.gravity;
+                if (gravity < 0) {
+                    gravity = minorGravity;
+                }
+                
+                switch (gravity & Gravity.VERTICAL_GRAVITY_MASK) {
+                    case Gravity.TOP:
+                        childTop = paddingTop + lp.topMargin;
+                        if (childBaseline != -1) {
+                            childTop += maxAscent[INDEX_TOP] - childBaseline;
+                        }
+                        break;
+
+                    case Gravity.CENTER_VERTICAL:
+                        // Removed support for baselign alignment when layout_gravity or
+                        // gravity == center_vertical. See bug #1038483.
+                        // Keep the code around if we need to re-enable this feature
+                        // if (childBaseline != -1) {
+                        //     // Align baselines vertically only if the child is smaller than us
+                        //     if (childSpace - childHeight > 0) {
+                        //         childTop = paddingTop + (childSpace / 2) - childBaseline;
+                        //     } else {
+                        //         childTop = paddingTop + (childSpace - childHeight) / 2;
+                        //     }
+                        // } else {
+                        childTop = paddingTop + ((childSpace - childHeight) / 2)
+                                + lp.topMargin - lp.bottomMargin;
+                        break;
+
+                    case Gravity.BOTTOM:
+                        childTop = childBottom - childHeight - lp.bottomMargin;
+                        if (childBaseline != -1) {
+                            int descent = child.getMeasuredHeight() - childBaseline;
+                            childTop -= (maxDescent[INDEX_BOTTOM] - descent);
+                        }
+                        break;
+                }
+
+                childLeft += lp.leftMargin;
+                setChildFrame(child, childLeft + getLocationOffset(child), childTop,
+                        childWidth, childHeight);
+                childLeft += childWidth + lp.rightMargin +
+                        getNextLocationOffset(child);
+
+                i += getChildrenSkipCount(child, i);
+            }
+        }
+    }
+
+    private void setChildFrame(View child, int left, int top, int width, int height) {        
+        child.layout(left, top, left + width, top + height);
+    }
+    
+    /**
+     * Should the layout be a column or a row.
+     * @param orientation Pass HORIZONTAL or VERTICAL. Default
+     * value is HORIZONTAL.
+     * 
+     * @attr ref android.R.styleable#LinearLayout_orientation
+     */
+    public void setOrientation(int orientation) {
+        if (mOrientation != orientation) {
+            mOrientation = orientation;
+            requestLayout();
+        }
+    }
+
+    /**
+     * Returns the current orientation.
+     * 
+     * @return either {@link #HORIZONTAL} or {@link #VERTICAL}
+     */
+    public int getOrientation() {
+        return mOrientation;
+    }
+
+    /**
+     * Describes how the child views are positioned. Defaults to GRAVITY_TOP. If
+     * this layout has a VERTICAL orientation, this controls where all the child
+     * views are placed if there is extra vertical space. If this layout has a
+     * HORIZONTAL orientation, this controls the alignment of the children.
+     * 
+     * @param gravity See {@link android.view.Gravity}
+     * 
+     * @attr ref android.R.styleable#LinearLayout_gravity
+     */
+    @android.view.RemotableViewMethod
+    public void setGravity(int gravity) {
+        if (mGravity != gravity) {
+            if ((gravity & Gravity.HORIZONTAL_GRAVITY_MASK) == 0) {
+                gravity |= Gravity.LEFT;
+            }
+
+            if ((gravity & Gravity.VERTICAL_GRAVITY_MASK) == 0) {
+                gravity |= Gravity.TOP;
+            }
+
+            mGravity = gravity;
+            requestLayout();
+        }
+    }
+
+    @android.view.RemotableViewMethod
+    public void setHorizontalGravity(int horizontalGravity) {
+        final int gravity = horizontalGravity & Gravity.HORIZONTAL_GRAVITY_MASK;
+        if ((mGravity & Gravity.HORIZONTAL_GRAVITY_MASK) != gravity) {
+            mGravity = (mGravity & ~Gravity.HORIZONTAL_GRAVITY_MASK) | gravity;
+            requestLayout();
+        }
+    }
+
+    @android.view.RemotableViewMethod
+    public void setVerticalGravity(int verticalGravity) {
+        final int gravity = verticalGravity & Gravity.VERTICAL_GRAVITY_MASK;
+        if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != gravity) {
+            mGravity = (mGravity & ~Gravity.VERTICAL_GRAVITY_MASK) | gravity;
+            requestLayout();
+        }
+    }
+    
+    @Override
+    public LayoutParams generateLayoutParams(AttributeSet attrs) {
+        return new LinearLayout.LayoutParams(getContext(), attrs);
+    }
+
+    /**
+     * Returns a set of layout parameters with a width of
+     * {@link android.view.ViewGroup.LayoutParams#FILL_PARENT}
+     * and a height of {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT}
+     * when the layout's orientation is {@link #VERTICAL}. When the orientation is
+     * {@link #HORIZONTAL}, the width is set to {@link LayoutParams#WRAP_CONTENT}
+     * and the height to {@link LayoutParams#WRAP_CONTENT}.
+     */
+    @Override
+    protected LayoutParams generateDefaultLayoutParams() {
+        if (mOrientation == HORIZONTAL) {
+            return new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
+        } else if (mOrientation == VERTICAL) {
+            return new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.WRAP_CONTENT);
+        }
+        return null;
+    }
+
+    @Override
+    protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
+        return new LayoutParams(p);
+    }
+
+
+    // Override to allow type-checking of LayoutParams.
+    @Override
+    protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
+        return p instanceof LinearLayout.LayoutParams;
+    }
+    
+    /**
+     * Per-child layout information associated with ViewLinearLayout.
+     * 
+     * @attr ref android.R.styleable#LinearLayout_Layout_layout_weight
+     * @attr ref android.R.styleable#LinearLayout_Layout_layout_gravity
+     */
+    public static class LayoutParams extends ViewGroup.MarginLayoutParams {
+        /**
+         * Indicates how much of the extra space in the LinearLayout will be
+         * allocated to the view associated with these LayoutParams. Specify
+         * 0 if the view should not be stretched. Otherwise the extra pixels
+         * will be pro-rated among all views whose weight is greater than 0.
+         */
+        @ViewDebug.ExportedProperty
+        public float weight;
+
+        /**
+         * Gravity for the view associated with these LayoutParams.
+         *
+         * @see android.view.Gravity
+         */
+        @ViewDebug.ExportedProperty(mapping = {
+            @ViewDebug.IntToString(from =  -1,                       to = "NONE"),
+            @ViewDebug.IntToString(from = Gravity.NO_GRAVITY,        to = "NONE"),
+            @ViewDebug.IntToString(from = Gravity.TOP,               to = "TOP"),
+            @ViewDebug.IntToString(from = Gravity.BOTTOM,            to = "BOTTOM"),
+            @ViewDebug.IntToString(from = Gravity.LEFT,              to = "LEFT"),
+            @ViewDebug.IntToString(from = Gravity.RIGHT,             to = "RIGHT"),
+            @ViewDebug.IntToString(from = Gravity.CENTER_VERTICAL,   to = "CENTER_VERTICAL"),
+            @ViewDebug.IntToString(from = Gravity.FILL_VERTICAL,     to = "FILL_VERTICAL"),
+            @ViewDebug.IntToString(from = Gravity.CENTER_HORIZONTAL, to = "CENTER_HORIZONTAL"),
+            @ViewDebug.IntToString(from = Gravity.FILL_HORIZONTAL,   to = "FILL_HORIZONTAL"),
+            @ViewDebug.IntToString(from = Gravity.CENTER,            to = "CENTER"),
+            @ViewDebug.IntToString(from = Gravity.FILL,              to = "FILL")
+        })
+        public int gravity = -1;
+
+        /**
+         * {@inheritDoc}
+         */
+        public LayoutParams(Context c, AttributeSet attrs) {
+            super(c, attrs);
+            TypedArray a =
+                    c.obtainStyledAttributes(attrs, com.android.internal.R.styleable.LinearLayout_Layout);
+
+            weight = a.getFloat(com.android.internal.R.styleable.LinearLayout_Layout_layout_weight, 0);
+            gravity = a.getInt(com.android.internal.R.styleable.LinearLayout_Layout_layout_gravity, -1);
+
+            a.recycle();
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        public LayoutParams(int width, int height) {
+            super(width, height);
+            weight = 0;
+        }
+
+        /**
+         * Creates a new set of layout parameters with the specified width, height
+         * and weight.
+         *
+         * @param width the width, either {@link #FILL_PARENT},
+         *        {@link #WRAP_CONTENT} or a fixed size in pixels
+         * @param height the height, either {@link #FILL_PARENT},
+         *        {@link #WRAP_CONTENT} or a fixed size in pixels
+         * @param weight the weight
+         */
+        public LayoutParams(int width, int height, float weight) {
+            super(width, height);
+            this.weight = weight;
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        public LayoutParams(ViewGroup.LayoutParams p) {
+            super(p);
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        public LayoutParams(MarginLayoutParams source) {
+            super(source);
+        }
+
+        @Override
+        public String debug(String output) {
+            return output + "LinearLayout.LayoutParams={width=" + sizeToString(width) +
+                    ", height=" + sizeToString(height) + " weight=" + weight +  "}";
+        }
+    }
+}
diff --git a/core/java/android/widget/ListAdapter.java b/core/java/android/widget/ListAdapter.java
new file mode 100644
index 0000000..a035145
--- /dev/null
+++ b/core/java/android/widget/ListAdapter.java
@@ -0,0 +1,44 @@
+/*
+ * 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 android.widget;
+
+/**
+ * Extended {@link Adapter} that is the bridge between a {@link ListView}
+ * and the data that backs the list. Frequently that data comes from a Cursor,
+ * but that is not
+ * required. The ListView can display any data provided that it is wrapped in a
+ * ListAdapter.
+ */
+public interface ListAdapter extends Adapter {
+
+    /**
+     * Are all items in this ListAdapter enabled?
+     * If yes it means all items are selectable and clickable.
+     * 
+     * @return True if all items are enabled
+     */
+    public boolean areAllItemsEnabled();
+
+    /**
+     * Returns true if the item at the specified position is not a separator.
+     * (A separator is a non-selectable, non-clickable item).
+     *
+     * @param position Index of the item
+     * @return True if the item is not a separator
+     */
+    boolean isEnabled(int position);
+}
diff --git a/core/java/android/widget/ListView.java b/core/java/android/widget/ListView.java
new file mode 100644
index 0000000..404a4ee
--- /dev/null
+++ b/core/java/android/widget/ListView.java
@@ -0,0 +1,3268 @@
+/*
+ * 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 android.widget;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Canvas;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.ColorDrawable;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.AttributeSet;
+import android.util.SparseBooleanArray;
+import android.util.SparseArray;
+import android.view.FocusFinder;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewDebug;
+import android.view.ViewGroup;
+import android.view.ViewParent;
+import android.view.SoundEffectConstants;
+
+import com.google.android.collect.Lists;
+import com.android.internal.R;
+
+import java.util.ArrayList;
+
+/*
+ * Implementation Notes:
+ *
+ * Some terminology:
+ *
+ *     index    - index of the items that are currently visible
+ *     position - index of the items in the cursor
+ */
+
+
+/**
+ * A view that shows items in a vertically scrolling list. The items
+ * come from the {@link ListAdapter} associated with this view.
+ *
+ * @attr ref android.R.styleable#ListView_entries
+ * @attr ref android.R.styleable#ListView_divider
+ * @attr ref android.R.styleable#ListView_dividerHeight
+ * @attr ref android.R.styleable#ListView_choiceMode
+ * @attr ref android.R.styleable#ListView_headerDividersEnabled
+ * @attr ref android.R.styleable#ListView_footerDividersEnabled
+ */
+public class ListView extends AbsListView {
+    /**
+     * Used to indicate a no preference for a position type.
+     */
+    static final int NO_POSITION = -1;
+
+    /**
+     * Normal list that does not indicate choices
+     */
+    public static final int CHOICE_MODE_NONE = 0;
+
+    /**
+     * The list allows up to one choice
+     */
+    public static final int CHOICE_MODE_SINGLE = 1;
+
+    /**
+     * The list allows multiple choices
+     */
+    public static final int CHOICE_MODE_MULTIPLE = 2;
+
+    /**
+     * When arrow scrolling, ListView will never scroll more than this factor
+     * times the height of the list.
+     */
+    private static final float MAX_SCROLL_FACTOR = 0.33f;
+
+    /**
+     * When arrow scrolling, need a certain amount of pixels to preview next
+     * items.  This is usually the fading edge, but if that is small enough,
+     * we want to make sure we preview at least this many pixels.
+     */
+    private static final int MIN_SCROLL_PREVIEW_PIXELS = 2;
+
+    /**
+     * A class that represents a fixed view in a list, for example a header at the top
+     * or a footer at the bottom.
+     */
+    public class FixedViewInfo {
+        /** The view to add to the list */
+        public View view;
+        /** The data backing the view. This is returned from {@link ListAdapter#getItem(int)}. */
+        public Object data;
+        /** <code>true</code> if the fixed view should be selectable in the list */
+        public boolean isSelectable;
+    }
+
+    private ArrayList<FixedViewInfo> mHeaderViewInfos = Lists.newArrayList();
+    private ArrayList<FixedViewInfo> mFooterViewInfos = Lists.newArrayList();
+
+    Drawable mDivider;
+    int mDividerHeight;
+    private boolean mClipDivider;
+    private boolean mHeaderDividersEnabled;
+    private boolean mFooterDividersEnabled;
+
+    private boolean mAreAllItemsSelectable = true;
+
+    private boolean mItemsCanFocus = false;
+
+    private int mChoiceMode = CHOICE_MODE_NONE;
+
+    private SparseBooleanArray mCheckStates;
+
+    // used for temporary calculations.
+    private Rect mTempRect = new Rect();
+
+    // the single allocated result per list view; kinda cheesey but avoids
+    // allocating these thingies too often.
+    private ArrowScrollFocusResult mArrowScrollFocusResult = new ArrowScrollFocusResult();
+
+    public ListView(Context context) {
+        this(context, null);
+    }
+
+    public ListView(Context context, AttributeSet attrs) {
+        this(context, attrs, com.android.internal.R.attr.listViewStyle);
+    }
+
+    public ListView(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+
+        TypedArray a = context.obtainStyledAttributes(attrs,
+                com.android.internal.R.styleable.ListView, defStyle, 0);
+
+        CharSequence[] entries = a.getTextArray(
+                com.android.internal.R.styleable.ListView_entries);
+        if (entries != null) {
+            setAdapter(new ArrayAdapter<CharSequence>(context,
+                    com.android.internal.R.layout.simple_list_item_1, entries));
+        }
+
+        final Drawable d = a.getDrawable(com.android.internal.R.styleable.ListView_divider);
+        if (d != null) {
+            // If a divider is specified use its intrinsic height for divider height
+            setDivider(d);
+        }
+
+        // Use the height specified, zero being the default
+        final int dividerHeight = a.getDimensionPixelSize(
+                com.android.internal.R.styleable.ListView_dividerHeight, 0);
+        if (dividerHeight != 0) {
+            setDividerHeight(dividerHeight);
+        }
+
+        mHeaderDividersEnabled = a.getBoolean(R.styleable.ListView_headerDividersEnabled, true);
+        mFooterDividersEnabled = a.getBoolean(R.styleable.ListView_footerDividersEnabled, true);
+
+        a.recycle();
+    }
+
+    /**
+     * @return The maximum amount a list view will scroll in response to
+     *   an arrow event.
+     */
+    public int getMaxScrollAmount() {
+        return (int) (MAX_SCROLL_FACTOR * (mBottom - mTop));
+    }
+
+    /**
+     * Make sure views are touching the top or bottom edge, as appropriate for
+     * our gravity
+     */
+    private void adjustViewsUpOrDown() {
+        final int childCount = getChildCount();
+        int delta;
+
+        if (childCount > 0) {
+            View child;
+
+            if (!mStackFromBottom) {
+                // Uh-oh -- we came up short. Slide all views up to make them
+                // align with the top
+                child = getChildAt(0);
+                delta = child.getTop() - mListPadding.top;
+                if (mFirstPosition != 0) {
+                    // It's OK to have some space above the first item if it is
+                    // part of the vertical spacing
+                    delta -= mDividerHeight;
+                }
+                if (delta < 0) {
+                    // We only are looking to see if we are too low, not too high
+                    delta = 0;
+                }
+            } else {
+                // we are too high, slide all views down to align with bottom
+                child = getChildAt(childCount - 1);
+                delta = child.getBottom() - (getHeight() - mListPadding.bottom);
+
+                if (mFirstPosition + childCount < mItemCount) {
+                    // It's OK to have some space below the last item if it is
+                    // part of the vertical spacing
+                    delta += mDividerHeight;
+                }
+
+                if (delta > 0) {
+                    delta = 0;
+                }
+            }
+
+            if (delta != 0) {
+                offsetChildrenTopAndBottom(-delta);
+            }
+        }
+    }
+
+    /**
+     * Add a fixed view to appear at the top of the list. If addHeaderView is
+     * called more than once, the views will appear in the order they were
+     * added. Views added using this call can take focus if they want.
+     * <p>
+     * NOTE: Call this before calling setAdapter. This is so ListView can wrap
+     * the supplied cursor with one that that will also account for header
+     * views.
+     *
+     * @param v The view to add.
+     * @param data Data to associate with this view
+     * @param isSelectable whether the item is selectable
+     */
+    public void addHeaderView(View v, Object data, boolean isSelectable) {
+
+        if (mAdapter != null) {
+            throw new IllegalStateException(
+                    "Cannot add header view to list -- setAdapter has already been called.");
+        }
+
+        FixedViewInfo info = new FixedViewInfo();
+        info.view = v;
+        info.data = data;
+        info.isSelectable = isSelectable;
+        mHeaderViewInfos.add(info);
+    }
+
+    /**
+     * Add a fixed view to appear at the top of the list. If addHeaderView is
+     * called more than once, the views will appear in the order they were
+     * added. Views added using this call can take focus if they want.
+     * <p>
+     * NOTE: Call this before calling setAdapter. This is so ListView can wrap
+     * the supplied cursor with one that that will also account for header
+     * views.
+     *
+     * @param v The view to add.
+     */
+    public void addHeaderView(View v) {
+        addHeaderView(v, null, true);
+    }
+
+    @Override
+    public int getHeaderViewsCount() {
+        return mHeaderViewInfos.size();
+    }
+
+    /**
+     * Removes a previously-added header view.
+     *
+     * @param v The view to remove
+     * @return true if the view was removed, false if the view was not a header
+     *         view
+     */
+    public boolean removeHeaderView(View v) {
+        if (mHeaderViewInfos.size() > 0) {
+            boolean result = false;
+            if (((HeaderViewListAdapter) mAdapter).removeHeader(v)) {
+                mDataSetObserver.onChanged();
+                result = true;
+            }
+            removeFixedViewInfo(v, mHeaderViewInfos);
+            return result;
+        }
+        return false;
+    }
+
+    private void removeFixedViewInfo(View v, ArrayList<FixedViewInfo> where) {
+        int len = where.size();
+        for (int i = 0; i < len; ++i) {
+            FixedViewInfo info = where.get(i);
+            if (info.view == v) {
+                where.remove(i);
+                break;
+            }
+        }
+    }
+
+    /**
+     * Add a fixed view to appear at the bottom of the list. If addFooterView is
+     * called more than once, the views will appear in the order they were
+     * added. Views added using this call can take focus if they want.
+     * <p>
+     * NOTE: Call this before calling setAdapter. This is so ListView can wrap
+     * the supplied cursor with one that that will also account for header
+     * views.
+     *
+     * @param v The view to add.
+     * @param data Data to associate with this view
+     * @param isSelectable true if the footer view can be selected
+     */
+    public void addFooterView(View v, Object data, boolean isSelectable) {
+        FixedViewInfo info = new FixedViewInfo();
+        info.view = v;
+        info.data = data;
+        info.isSelectable = isSelectable;
+        mFooterViewInfos.add(info);
+
+        // in the case of re-adding a footer view, or adding one later on,
+        // we need to notify the observer
+        if (mDataSetObserver != null) {
+            mDataSetObserver.onChanged();
+        }
+    }
+
+    /**
+     * Add a fixed view to appear at the bottom of the list. If addFooterView is called more
+     * than once, the views will appear in the order they were added. Views added using
+     * this call can take focus if they want.
+     * <p>NOTE: Call this before calling setAdapter. This is so ListView can wrap the supplied
+     * cursor with one that that will also account for header views.
+     *
+     *
+     * @param v The view to add.
+     */
+    public void addFooterView(View v) {
+        addFooterView(v, null, true);
+    }
+
+    @Override
+    public int getFooterViewsCount() {
+        return mFooterViewInfos.size();
+    }
+
+    /**
+     * Removes a previously-added footer view.
+     *
+     * @param v The view to remove
+     * @return
+     * true if the view was removed, false if the view was not a footer view
+     */
+    public boolean removeFooterView(View v) {
+        if (mFooterViewInfos.size() > 0) {
+            boolean result = false;
+            if (((HeaderViewListAdapter) mAdapter).removeFooter(v)) {
+                mDataSetObserver.onChanged();
+                result = true;
+            }
+            removeFixedViewInfo(v, mFooterViewInfos);
+            return result;
+        }
+        return false;
+    }
+
+    /**
+     * Returns the adapter currently in use in this ListView. The returned adapter
+     * might not be the same adapter passed to {@link #setAdapter(ListAdapter)} but
+     * might be a {@link WrapperListAdapter}.
+     *
+     * @return The adapter currently used to display data in this ListView.
+     *
+     * @see #setAdapter(ListAdapter)
+     */
+    @Override
+    public ListAdapter getAdapter() {
+        return mAdapter;
+    }
+
+    /**
+     * Sets the data behind this ListView.
+     *
+     * The adapter passed to this method may be wrapped by a {@link WrapperListAdapter},
+     * depending on the ListView features currently in use. For instance, adding
+     * headers and/or footers will cause the adapter to be wrapped.
+     *
+     * @param adapter The ListAdapter which is responsible for maintaining the
+     *        data backing this list and for producing a view to represent an
+     *        item in that data set.
+     *
+     * @see #getAdapter() 
+     */
+    @Override
+    public void setAdapter(ListAdapter adapter) {
+        if (null != mAdapter) {
+            mAdapter.unregisterDataSetObserver(mDataSetObserver);
+        }
+
+        resetList();
+        mRecycler.clear();
+
+        if (mHeaderViewInfos.size() > 0|| mFooterViewInfos.size() > 0) {
+            mAdapter = new HeaderViewListAdapter(mHeaderViewInfos, mFooterViewInfos, adapter);
+        } else {
+            mAdapter = adapter;
+        }
+
+        mOldSelectedPosition = INVALID_POSITION;
+        mOldSelectedRowId = INVALID_ROW_ID;
+        if (mAdapter != null) {
+            mAreAllItemsSelectable = mAdapter.areAllItemsEnabled();
+            mOldItemCount = mItemCount;
+            mItemCount = mAdapter.getCount();
+            checkFocus();
+
+            mDataSetObserver = new AdapterDataSetObserver();
+            mAdapter.registerDataSetObserver(mDataSetObserver);
+
+            mRecycler.setViewTypeCount(mAdapter.getViewTypeCount());
+
+            int position;
+            if (mStackFromBottom) {
+                position = lookForSelectablePosition(mItemCount - 1, false);
+            } else {
+                position = lookForSelectablePosition(0, true);
+            }
+            setSelectedPositionInt(position);
+            setNextSelectedPositionInt(position);
+
+            if (mItemCount == 0) {
+                // Nothing selected
+                checkSelectionChanged();
+            }
+
+        } else {
+            mAreAllItemsSelectable = true;
+            checkFocus();
+            // Nothing selected
+            checkSelectionChanged();
+        }
+
+        if (mCheckStates != null) {
+            mCheckStates.clear();
+        }
+
+        requestLayout();
+    }
+
+
+    /**
+     * The list is empty. Clear everything out.
+     */
+    @Override
+    void resetList() {
+        super.resetList();
+        mLayoutMode = LAYOUT_NORMAL;
+    }
+
+    /**
+     * @return Whether the list needs to show the top fading edge
+     */
+    private boolean showingTopFadingEdge() {
+        final int listTop = mScrollY + mListPadding.top;
+        return (mFirstPosition > 0) || (getChildAt(0).getTop() > listTop);
+    }
+
+    /**
+     * @return Whether the list needs to show the bottom fading edge
+     */
+    private boolean showingBottomFadingEdge() {
+        final int childCount = getChildCount();
+        final int bottomOfBottomChild = getChildAt(childCount - 1).getBottom();
+        final int lastVisiblePosition = mFirstPosition + childCount - 1;
+
+        final int listBottom = mScrollY + getHeight() - mListPadding.bottom;
+
+        return (lastVisiblePosition < mItemCount - 1)
+                         || (bottomOfBottomChild < listBottom);
+    }
+
+
+    @Override
+    public boolean requestChildRectangleOnScreen(View child, Rect rect, boolean immediate) {
+
+        int rectTopWithinChild = rect.top;
+
+        // offset so rect is in coordinates of the this view
+        rect.offset(child.getLeft(), child.getTop());
+        rect.offset(-child.getScrollX(), -child.getScrollY());
+
+        final int height = getHeight();
+        int listUnfadedTop = getScrollY();
+        int listUnfadedBottom = listUnfadedTop + height;
+        final int fadingEdge = getVerticalFadingEdgeLength();
+
+        if (showingTopFadingEdge()) {
+            // leave room for top fading edge as long as rect isn't at very top
+            if ((mSelectedPosition > 0) || (rectTopWithinChild > fadingEdge)) {
+                listUnfadedTop += fadingEdge;
+            }
+        }
+
+        int childCount = getChildCount();
+        int bottomOfBottomChild = getChildAt(childCount - 1).getBottom();
+
+        if (showingBottomFadingEdge()) {
+            // leave room for bottom fading edge as long as rect isn't at very bottom
+            if ((mSelectedPosition < mItemCount - 1)
+                    || (rect.bottom < (bottomOfBottomChild - fadingEdge))) {
+                listUnfadedBottom -= fadingEdge;
+            }
+        }
+
+        int scrollYDelta = 0;
+
+        if (rect.bottom > listUnfadedBottom && rect.top > listUnfadedTop) {
+            // need to MOVE DOWN to get it in view: move down just enough so
+            // that the entire rectangle is in view (or at least the first
+            // screen size chunk).
+
+            if (rect.height() > height) {
+                // just enough to get screen size chunk on
+                scrollYDelta += (rect.top - listUnfadedTop);
+            } else {
+                // get entire rect at bottom of screen
+                scrollYDelta += (rect.bottom - listUnfadedBottom);
+            }
+
+            // make sure we aren't scrolling beyond the end of our children
+            int distanceToBottom = bottomOfBottomChild - listUnfadedBottom;
+            scrollYDelta = Math.min(scrollYDelta, distanceToBottom);
+        } else if (rect.top < listUnfadedTop && rect.bottom < listUnfadedBottom) {
+            // need to MOVE UP to get it in view: move up just enough so that
+            // entire rectangle is in view (or at least the first screen
+            // size chunk of it).
+
+            if (rect.height() > height) {
+                // screen size chunk
+                scrollYDelta -= (listUnfadedBottom - rect.bottom);
+            } else {
+                // entire rect at top
+                scrollYDelta -= (listUnfadedTop - rect.top);
+            }
+
+            // make sure we aren't scrolling any further than the top our children
+            int top = getChildAt(0).getTop();
+            int deltaToTop = top - listUnfadedTop;
+            scrollYDelta = Math.max(scrollYDelta, deltaToTop);
+        }
+
+        final boolean scroll = scrollYDelta != 0;
+        if (scroll) {
+            scrollListItemsBy(-scrollYDelta);
+            positionSelector(child);
+            mSelectedTop = child.getTop();
+            invalidate();
+        }
+        return scroll;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    void fillGap(boolean down) {
+        final int count = getChildCount();
+        if (down) {
+            final int startOffset = count > 0 ? getChildAt(count - 1).getBottom() + mDividerHeight :
+                    getListPaddingTop();
+            fillDown(mFirstPosition + count, startOffset);
+            correctTooHigh(getChildCount());
+        } else {
+            final int startOffset = count > 0 ? getChildAt(0).getTop() - mDividerHeight :
+                    getHeight() - getListPaddingBottom();
+            fillUp(mFirstPosition - 1, startOffset);
+            correctTooLow(getChildCount());
+        }
+    }
+
+    /**
+     * Fills the list from pos down to the end of the list view.
+     *
+     * @param pos The first position to put in the list
+     *
+     * @param nextTop The location where the top of the item associated with pos
+     *        should be drawn
+     *
+     * @return The view that is currently selected, if it happens to be in the
+     *         range that we draw.
+     */
+    private View fillDown(int pos, int nextTop) {
+        View selectedView = null;
+
+        int end = (mBottom - mTop) - mListPadding.bottom;
+
+        while (nextTop < end && pos < mItemCount) {
+            // is this the selected item?
+            boolean selected = pos == mSelectedPosition;
+            View child = makeAndAddView(pos, nextTop, true, mListPadding.left, selected);
+
+            nextTop = child.getBottom() + mDividerHeight;
+            if (selected) {
+                selectedView = child;
+            }
+            pos++;
+        }
+
+        return selectedView;
+    }
+
+    /**
+     * Fills the list from pos up to the top of the list view.
+     *
+     * @param pos The first position to put in the list
+     *
+     * @param nextBottom The location where the bottom of the item associated
+     *        with pos should be drawn
+     *
+     * @return The view that is currently selected
+     */
+    private View fillUp(int pos, int nextBottom) {
+        View selectedView = null;
+
+        int end = mListPadding.top;
+
+        while (nextBottom > end && pos >= 0) {
+            // is this the selected item?
+            boolean selected = pos == mSelectedPosition;
+            View child = makeAndAddView(pos, nextBottom, false, mListPadding.left, selected);
+            nextBottom = child.getTop() - mDividerHeight;
+            if (selected) {
+                selectedView = child;
+            }
+            pos--;
+        }
+
+        mFirstPosition = pos + 1;
+
+        return selectedView;
+    }
+
+    /**
+     * Fills the list from top to bottom, starting with mFirstPosition
+     *
+     * @param nextTop The location where the top of the first item should be
+     *        drawn
+     *
+     * @return The view that is currently selected
+     */
+    private View fillFromTop(int nextTop) {
+        mFirstPosition = Math.min(mFirstPosition, mSelectedPosition);
+        mFirstPosition = Math.min(mFirstPosition, mItemCount - 1);
+        if (mFirstPosition < 0) {
+            mFirstPosition = 0;
+        }
+        return fillDown(mFirstPosition, nextTop);
+    }
+
+
+    /**
+     * Put mSelectedPosition in the middle of the screen and then build up and
+     * down from there. This method forces mSelectedPosition to the center.
+     *
+     * @param childrenTop Top of the area in which children can be drawn, as
+     *        measured in pixels
+     * @param childrenBottom Bottom of the area in which children can be drawn,
+     *        as measured in pixels
+     * @return Currently selected view
+     */
+    private View fillFromMiddle(int childrenTop, int childrenBottom) {
+        int height = childrenBottom - childrenTop;
+
+        int position = reconcileSelectedPosition();
+
+        View sel = makeAndAddView(position, childrenTop, true,
+                mListPadding.left, true);
+        mFirstPosition = position;
+
+        int selHeight = sel.getMeasuredHeight();
+        if (selHeight <= height) {
+            sel.offsetTopAndBottom((height - selHeight) / 2);
+        }
+
+        fillAboveAndBelow(sel, position);
+
+        if (!mStackFromBottom) {
+            correctTooHigh(getChildCount());
+        } else {
+            correctTooLow(getChildCount());
+        }
+
+        return sel;
+    }
+
+    /**
+     * Once the selected view as been placed, fill up the visible area above and
+     * below it.
+     *
+     * @param sel The selected view
+     * @param position The position corresponding to sel
+     */
+    private void fillAboveAndBelow(View sel, int position) {
+        final int dividerHeight = mDividerHeight;
+        if (!mStackFromBottom) {
+            fillUp(position - 1, sel.getTop() - dividerHeight);
+            adjustViewsUpOrDown();
+            fillDown(position + 1, sel.getBottom() + dividerHeight);
+        } else {
+            fillDown(position + 1, sel.getBottom() + dividerHeight);
+            adjustViewsUpOrDown();
+            fillUp(position - 1, sel.getTop() - dividerHeight);
+        }
+    }
+
+
+    /**
+     * Fills the grid based on positioning the new selection at a specific
+     * location. The selection may be moved so that it does not intersect the
+     * faded edges. The grid is then filled upwards and downwards from there.
+     *
+     * @param selectedTop Where the selected item should be
+     * @param childrenTop Where to start drawing children
+     * @param childrenBottom Last pixel where children can be drawn
+     * @return The view that currently has selection
+     */
+    private View fillFromSelection(int selectedTop, int childrenTop, int childrenBottom) {
+        int fadingEdgeLength = getVerticalFadingEdgeLength();
+        final int selectedPosition = mSelectedPosition;
+
+        View sel;
+
+        final int topSelectionPixel = getTopSelectionPixel(childrenTop, fadingEdgeLength,
+                selectedPosition);
+        final int bottomSelectionPixel = getBottomSelectionPixel(childrenBottom, fadingEdgeLength,
+                selectedPosition);
+
+        sel = makeAndAddView(selectedPosition, selectedTop, true, mListPadding.left, true);
+
+
+        // Some of the newly selected item extends below the bottom of the list
+        if (sel.getBottom() > bottomSelectionPixel) {
+            // Find space available above the selection into which we can scroll
+            // upwards
+            final int spaceAbove = sel.getTop() - topSelectionPixel;
+
+            // Find space required to bring the bottom of the selected item
+            // fully into view
+            final int spaceBelow = sel.getBottom() - bottomSelectionPixel;
+            final int offset = Math.min(spaceAbove, spaceBelow);
+
+            // Now offset the selected item to get it into view
+            sel.offsetTopAndBottom(-offset);
+        } else if (sel.getTop() < topSelectionPixel) {
+            // Find space required to bring the top of the selected item fully
+            // into view
+            final int spaceAbove = topSelectionPixel - sel.getTop();
+
+            // Find space available below the selection into which we can scroll
+            // downwards
+            final int spaceBelow = bottomSelectionPixel - sel.getBottom();
+            final int offset = Math.min(spaceAbove, spaceBelow);
+
+            // Offset the selected item to get it into view
+            sel.offsetTopAndBottom(offset);
+        }
+
+        // Fill in views above and below
+        fillAboveAndBelow(sel, selectedPosition);
+
+        if (!mStackFromBottom) {
+            correctTooHigh(getChildCount());
+        } else {
+            correctTooLow(getChildCount());
+        }
+
+        return sel;
+    }
+
+    /**
+     * Calculate the bottom-most pixel we can draw the selection into
+     *
+     * @param childrenBottom Bottom pixel were children can be drawn
+     * @param fadingEdgeLength Length of the fading edge in pixels, if present
+     * @param selectedPosition The position that will be selected
+     * @return The bottom-most pixel we can draw the selection into
+     */
+    private int getBottomSelectionPixel(int childrenBottom, int fadingEdgeLength,
+            int selectedPosition) {
+        int bottomSelectionPixel = childrenBottom;
+        if (selectedPosition != mItemCount - 1) {
+            bottomSelectionPixel -= fadingEdgeLength;
+        }
+        return bottomSelectionPixel;
+    }
+
+    /**
+     * Calculate the top-most pixel we can draw the selection into
+     *
+     * @param childrenTop Top pixel were children can be drawn
+     * @param fadingEdgeLength Length of the fading edge in pixels, if present
+     * @param selectedPosition The position that will be selected
+     * @return The top-most pixel we can draw the selection into
+     */
+    private int getTopSelectionPixel(int childrenTop, int fadingEdgeLength, int selectedPosition) {
+        // first pixel we can draw the selection into
+        int topSelectionPixel = childrenTop;
+        if (selectedPosition > 0) {
+            topSelectionPixel += fadingEdgeLength;
+        }
+        return topSelectionPixel;
+    }
+
+
+    /**
+     * Fills the list based on positioning the new selection relative to the old
+     * selection. The new selection will be placed at, above, or below the
+     * location of the new selection depending on how the selection is moving.
+     * The selection will then be pinned to the visible part of the screen,
+     * excluding the edges that are faded. The list is then filled upwards and
+     * downwards from there.
+     *
+     * @param oldSel The old selected view. Useful for trying to put the new
+     *        selection in the same place
+     * @param newSel The view that is to become selected. Useful for trying to
+     *        put the new selection in the same place
+     * @param delta Which way we are moving
+     * @param childrenTop Where to start drawing children
+     * @param childrenBottom Last pixel where children can be drawn
+     * @return The view that currently has selection
+     */
+    private View moveSelection(View oldSel, View newSel, int delta, int childrenTop,
+            int childrenBottom) {
+        int fadingEdgeLength = getVerticalFadingEdgeLength();
+        final int selectedPosition = mSelectedPosition;
+
+        View sel;
+
+        final int topSelectionPixel = getTopSelectionPixel(childrenTop, fadingEdgeLength,
+                selectedPosition);
+        final int bottomSelectionPixel = getBottomSelectionPixel(childrenTop, fadingEdgeLength,
+                selectedPosition);
+
+        if (delta > 0) {
+            /*
+             * Case 1: Scrolling down.
+             */
+
+            /*
+             *     Before           After
+             *    |       |        |       |
+             *    +-------+        +-------+
+             *    |   A   |        |   A   |
+             *    |   1   |   =>   +-------+
+             *    +-------+        |   B   |
+             *    |   B   |        |   2   |
+             *    +-------+        +-------+
+             *    |       |        |       |
+             *
+             *    Try to keep the top of the previously selected item where it was.
+             *    oldSel = A
+             *    sel = B
+             */
+
+            // Put oldSel (A) where it belongs
+            oldSel = makeAndAddView(selectedPosition - 1, oldSel.getTop(), true,
+                    mListPadding.left, false);
+
+            final int dividerHeight = mDividerHeight;
+
+            // Now put the new selection (B) below that
+            sel = makeAndAddView(selectedPosition, oldSel.getBottom() + dividerHeight, true,
+                    mListPadding.left, true);
+
+            // Some of the newly selected item extends below the bottom of the list
+            if (sel.getBottom() > bottomSelectionPixel) {
+
+                // Find space available above the selection into which we can scroll upwards
+                int spaceAbove = sel.getTop() - topSelectionPixel;
+
+                // Find space required to bring the bottom of the selected item fully into view
+                int spaceBelow = sel.getBottom() - bottomSelectionPixel;
+
+                // Don't scroll more than half the height of the list
+                int halfVerticalSpace = (childrenBottom - childrenTop) / 2;
+                int offset = Math.min(spaceAbove, spaceBelow);
+                offset = Math.min(offset, halfVerticalSpace);
+
+                // We placed oldSel, so offset that item
+                oldSel.offsetTopAndBottom(-offset);
+                // Now offset the selected item to get it into view
+                sel.offsetTopAndBottom(-offset);
+            }
+
+            // Fill in views above and below
+            if (!mStackFromBottom) {
+                fillUp(mSelectedPosition - 2, sel.getTop() - dividerHeight);
+                adjustViewsUpOrDown();
+                fillDown(mSelectedPosition + 1, sel.getBottom() + dividerHeight);
+            } else {
+                fillDown(mSelectedPosition + 1, sel.getBottom() + dividerHeight);
+                adjustViewsUpOrDown();
+                fillUp(mSelectedPosition - 2, sel.getTop() - dividerHeight);
+            }
+        } else if (delta < 0) {
+            /*
+             * Case 2: Scrolling up.
+             */
+
+            /*
+             *     Before           After
+             *    |       |        |       |
+             *    +-------+        +-------+
+             *    |   A   |        |   A   |
+             *    +-------+   =>   |   1   |
+             *    |   B   |        +-------+
+             *    |   2   |        |   B   |
+             *    +-------+        +-------+
+             *    |       |        |       |
+             *
+             *    Try to keep the top of the item about to become selected where it was.
+             *    newSel = A
+             *    olSel = B
+             */
+
+            if (newSel != null) {
+                // Try to position the top of newSel (A) where it was before it was selected
+                sel = makeAndAddView(selectedPosition, newSel.getTop(), true, mListPadding.left,
+                        true);
+            } else {
+                // If (A) was not on screen and so did not have a view, position
+                // it above the oldSel (B)
+                sel = makeAndAddView(selectedPosition, oldSel.getTop(), false, mListPadding.left,
+                        true);
+            }
+
+            // Some of the newly selected item extends above the top of the list
+            if (sel.getTop() < topSelectionPixel) {
+                // Find space required to bring the top of the selected item fully into view
+                int spaceAbove = topSelectionPixel - sel.getTop();
+
+               // Find space available below the selection into which we can scroll downwards
+                int spaceBelow = bottomSelectionPixel - sel.getBottom();
+
+                // Don't scroll more than half the height of the list
+                int halfVerticalSpace = (childrenBottom - childrenTop) / 2;
+                int offset = Math.min(spaceAbove, spaceBelow);
+                offset = Math.min(offset, halfVerticalSpace);
+
+                // Offset the selected item to get it into view
+                sel.offsetTopAndBottom(offset);
+            }
+
+            // Fill in views above and below
+            fillAboveAndBelow(sel, selectedPosition);
+        } else {
+
+            int oldTop = oldSel.getTop();
+
+            /*
+             * Case 3: Staying still
+             */
+            sel = makeAndAddView(selectedPosition, oldTop, true, mListPadding.left, true);
+
+            // We're staying still...
+            if (oldTop < childrenTop) {
+                // ... but the top of the old selection was off screen.
+                // (This can happen if the data changes size out from under us)
+                int newBottom = sel.getBottom();
+                if (newBottom < childrenTop + 20) {
+                    // Not enough visible -- bring it onscreen
+                    sel.offsetTopAndBottom(childrenTop - sel.getTop());
+                }
+            }
+
+            // Fill in views above and below
+            fillAboveAndBelow(sel, selectedPosition);
+        }
+
+        return sel;
+    }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        // Sets up mListPadding
+        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+
+        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
+        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
+        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
+        int heightSize = MeasureSpec.getSize(heightMeasureSpec);
+
+        int childWidth = 0;
+        int childHeight = 0;
+
+        mItemCount = mAdapter == null ? 0 : mAdapter.getCount();
+        if (mItemCount > 0 && (widthMode == MeasureSpec.UNSPECIFIED ||
+                heightMode == MeasureSpec.UNSPECIFIED)) {
+            final View child = obtainView(0);
+
+            measureScrapChild(child, 0, widthMeasureSpec);
+
+            childWidth = child.getMeasuredWidth();
+            childHeight = child.getMeasuredHeight();
+
+            if (recycleOnMeasure()) {
+                mRecycler.addScrapView(child);
+            }
+        }
+
+        if (widthMode == MeasureSpec.UNSPECIFIED) {
+            widthSize = mListPadding.left + mListPadding.right + childWidth +
+                    getVerticalScrollbarWidth();
+        }
+
+        if (heightMode == MeasureSpec.UNSPECIFIED) {
+            heightSize = mListPadding.top + mListPadding.bottom + childHeight +
+                    getVerticalFadingEdgeLength() * 2;
+        }
+
+        if (heightMode == MeasureSpec.AT_MOST) {
+            // TODO: after first layout we should maybe start at the first visible position, not 0
+            heightSize = measureHeightOfChildren(widthMeasureSpec, 0, NO_POSITION, heightSize, -1);
+        }
+
+        setMeasuredDimension(widthSize, heightSize);
+        mWidthMeasureSpec = widthMeasureSpec;        
+    }
+
+    private void measureScrapChild(View child, int position, int widthMeasureSpec) {
+        LayoutParams p = (LayoutParams) child.getLayoutParams();
+        if (p == null) {
+            p = new LayoutParams(ViewGroup.LayoutParams.FILL_PARENT,
+                    ViewGroup.LayoutParams.WRAP_CONTENT, 0);
+        }
+        p.viewType = mAdapter.getItemViewType(position);
+
+        int childWidthSpec = ViewGroup.getChildMeasureSpec(widthMeasureSpec,
+                mListPadding.left + mListPadding.right, p.width);
+        int lpHeight = p.height;
+        int childHeightSpec;
+        if (lpHeight > 0) {
+            childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight, MeasureSpec.EXACTLY);
+        } else {
+            childHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
+        }
+        child.measure(childWidthSpec, childHeightSpec);
+    }
+
+    /**
+     * @return True to recycle the views used to measure this ListView in
+     *         UNSPECIFIED/AT_MOST modes, false otherwise.
+     * @hide
+     */
+    protected boolean recycleOnMeasure() {
+        return true;
+    }
+
+    /**
+     * Measures the height of the given range of children (inclusive) and
+     * returns the height with this ListView's padding and divider heights
+     * included. If maxHeight is provided, the measuring will stop when the
+     * current height reaches maxHeight.
+     *
+     * @param widthMeasureSpec The width measure spec to be given to a child's
+     *            {@link View#measure(int, int)}.
+     * @param startPosition The position of the first child to be shown.
+     * @param endPosition The (inclusive) position of the last child to be
+     *            shown. Specify {@link #NO_POSITION} if the last child should be
+     *            the last available child from the adapter.
+     * @param maxHeight The maximum height that will be returned (if all the
+     *            children don't fit in this value, this value will be
+     *            returned).
+     * @param disallowPartialChildPosition In general, whether the returned
+     *            height should only contain entire children. This is more
+     *            powerful--it is the first inclusive position at which partial
+     *            children will not be allowed. Example: it looks nice to have
+     *            at least 3 completely visible children, and in portrait this
+     *            will most likely fit; but in landscape there could be times
+     *            when even 2 children can not be completely shown, so a value
+     *            of 2 (remember, inclusive) would be good (assuming
+     *            startPosition is 0).
+     * @return The height of this ListView with the given children.
+     */
+    final int measureHeightOfChildren(int widthMeasureSpec, int startPosition, int endPosition,
+            final int maxHeight, int disallowPartialChildPosition) {
+
+        final ListAdapter adapter = mAdapter;
+        if (adapter == null) {
+            return mListPadding.top + mListPadding.bottom;
+        }
+
+        // Include the padding of the list
+        int returnedHeight = mListPadding.top + mListPadding.bottom;
+        final int dividerHeight = ((mDividerHeight > 0) && mDivider != null) ? mDividerHeight : 0;
+        // The previous height value that was less than maxHeight and contained
+        // no partial children
+        int prevHeightWithoutPartialChild = 0;
+        int i;
+        View child;
+
+        // mItemCount - 1 since endPosition parameter is inclusive
+        endPosition = (endPosition == NO_POSITION) ? adapter.getCount() - 1 : endPosition;
+        final AbsListView.RecycleBin recycleBin = mRecycler;
+        final boolean recyle = recycleOnMeasure();
+
+        for (i = startPosition; i <= endPosition; ++i) {
+            child = obtainView(i);
+
+            measureScrapChild(child, i, widthMeasureSpec);
+
+            if (i > 0) {
+                // Count the divider for all but one child
+                returnedHeight += dividerHeight;
+            }
+
+            // Recycle the view before we possibly return from the method
+            if (recyle) {
+                recycleBin.addScrapView(child);
+            }
+
+            returnedHeight += child.getMeasuredHeight();
+
+            if (returnedHeight >= maxHeight) {
+                // We went over, figure out which height to return.  If returnedHeight > maxHeight,
+                // then the i'th position did not fit completely.
+                return (disallowPartialChildPosition >= 0) // Disallowing is enabled (> -1)
+                            && (i > disallowPartialChildPosition) // We've past the min pos
+                            && (prevHeightWithoutPartialChild > 0) // We have a prev height
+                            && (returnedHeight != maxHeight) // i'th child did not fit completely
+                        ? prevHeightWithoutPartialChild
+                        : maxHeight;
+            }
+
+            if ((disallowPartialChildPosition >= 0) && (i >= disallowPartialChildPosition)) {
+                prevHeightWithoutPartialChild = returnedHeight;
+            }
+        }
+
+        // At this point, we went through the range of children, and they each
+        // completely fit, so return the returnedHeight
+        return returnedHeight;
+    }
+
+    @Override
+    int findMotionRow(int y) {
+        int childCount = getChildCount();
+        if (childCount > 0) {
+            for (int i = 0; i < childCount; i++) {
+                View v = getChildAt(i);
+                if (y <= v.getBottom()) {
+                    return mFirstPosition + i;
+                }
+            }
+            return mFirstPosition + childCount - 1;
+        }
+        return INVALID_POSITION;
+    }
+
+    /**
+     * Put a specific item at a specific location on the screen and then build
+     * up and down from there.
+     *
+     * @param position The reference view to use as the starting point
+     * @param top Pixel offset from the top of this view to the top of the
+     *        reference view.
+     *
+     * @return The selected view, or null if the selected view is outside the
+     *         visible area.
+     */
+    private View fillSpecific(int position, int top) {
+        boolean tempIsSelected = position == mSelectedPosition;
+        View temp = makeAndAddView(position, top, true, mListPadding.left, tempIsSelected);
+        // Possibly changed again in fillUp if we add rows above this one.
+        mFirstPosition = position;
+
+        View above;
+        View below;
+
+        final int dividerHeight = mDividerHeight;
+        if (!mStackFromBottom) {
+            above = fillUp(position - 1, temp.getTop() - dividerHeight);
+            // This will correct for the top of the first view not touching the top of the list
+            adjustViewsUpOrDown();
+            below = fillDown(position + 1, temp.getBottom() + dividerHeight);
+            int childCount = getChildCount();
+            if (childCount > 0) {
+                correctTooHigh(childCount);
+            }
+        } else {
+            below = fillDown(position + 1, temp.getBottom() + dividerHeight);
+            // This will correct for the bottom of the last view not touching the bottom of the list
+            adjustViewsUpOrDown();
+            above = fillUp(position - 1, temp.getTop() - dividerHeight);
+            int childCount = getChildCount();
+            if (childCount > 0) {
+                 correctTooLow(childCount);
+            }
+        }
+
+        if (tempIsSelected) {
+            return temp;
+        } else if (above != null) {
+            return above;
+        } else {
+            return below;
+        }
+    }
+
+    /**
+     * Check if we have dragged the bottom of the list too high (we have pushed the
+     * top element off the top of the screen when we did not need to). Correct by sliding
+     * everything back down.
+     *
+     * @param childCount Number of children
+     */
+    private void correctTooHigh(int childCount) {
+        // First see if the last item is visible. If it is not, it is OK for the
+        // top of the list to be pushed up.
+        int lastPosition = mFirstPosition + childCount - 1;
+        if (lastPosition == mItemCount - 1 && childCount > 0) {
+
+            // Get the last child ...
+            final View lastChild = getChildAt(childCount - 1);
+
+            // ... and its bottom edge
+            final int lastBottom = lastChild.getBottom();
+
+            // This is bottom of our drawable area
+            final int end = (mBottom - mTop) - mListPadding.bottom;
+
+            // This is how far the bottom edge of the last view is from the bottom of the
+            // drawable area
+            int bottomOffset = end - lastBottom;
+            View firstChild = getChildAt(0);
+            final int firstTop = firstChild.getTop();
+
+            // Make sure we are 1) Too high, and 2) Either there are more rows above the
+            // first row or the first row is scrolled off the top of the drawable area
+            if (bottomOffset > 0 && (mFirstPosition > 0 || firstTop < mListPadding.top))  {
+                if (mFirstPosition == 0) {
+                    // Don't pull the top too far down
+                    bottomOffset = Math.min(bottomOffset, mListPadding.top - firstTop);
+                }
+                // Move everything down
+                offsetChildrenTopAndBottom(bottomOffset);
+                if (mFirstPosition > 0) {
+                    // Fill the gap that was opened above mFirstPosition with more rows, if
+                    // possible
+                    fillUp(mFirstPosition - 1, firstChild.getTop() - mDividerHeight);
+                    // Close up the remaining gap
+                    adjustViewsUpOrDown();
+                }
+
+            }
+        }
+    }
+
+    /**
+     * Check if we have dragged the bottom of the list too low (we have pushed the
+     * bottom element off the bottom of the screen when we did not need to). Correct by sliding
+     * everything back up.
+     *
+     * @param childCount Number of children
+     */
+    private void correctTooLow(int childCount) {
+        // First see if the first item is visible. If it is not, it is OK for the
+        // bottom of the list to be pushed down.
+        if (mFirstPosition == 0 && childCount > 0) {
+
+            // Get the first child ...
+            final View firstChild = getChildAt(0);
+
+            // ... and its top edge
+            final int firstTop = firstChild.getTop();
+
+            // This is top of our drawable area
+            final int start = mListPadding.top;
+
+            // This is bottom of our drawable area
+            final int end = (mBottom - mTop) - mListPadding.bottom;
+
+            // This is how far the top edge of the first view is from the top of the
+            // drawable area
+            int topOffset = firstTop - start;
+            View lastChild = getChildAt(childCount - 1);
+            final int lastBottom = lastChild.getBottom();
+            int lastPosition = mFirstPosition + childCount - 1;
+
+            // Make sure we are 1) Too low, and 2) Either there are more rows below the
+            // last row or the last row is scrolled off the bottom of the drawable area
+            if (topOffset > 0 && (lastPosition < mItemCount - 1 || lastBottom > end))  {
+                if (lastPosition == mItemCount - 1 ) {
+                    // Don't pull the bottom too far up
+                    topOffset = Math.min(topOffset, lastBottom - end);
+                }
+                // Move everything up
+                offsetChildrenTopAndBottom(-topOffset);
+                if (lastPosition < mItemCount - 1) {
+                    // Fill the gap that was opened below the last position with more rows, if
+                    // possible
+                    fillDown(lastPosition + 1, lastChild.getBottom() + mDividerHeight);
+                    // Close up the remaining gap
+                    adjustViewsUpOrDown();
+                }
+            }
+        }
+    }
+
+    @Override
+    protected void layoutChildren() {
+        final boolean blockLayoutRequests = mBlockLayoutRequests;
+        if (!blockLayoutRequests) {
+            mBlockLayoutRequests = true;
+        }
+
+        try {
+            super.layoutChildren();
+
+            invalidate();
+
+            if (mAdapter == null) {
+                resetList();
+                invokeOnItemScrollListener();
+                return;
+            }
+
+            int childrenTop = mListPadding.top;
+            int childrenBottom = mBottom - mTop - mListPadding.bottom;
+
+            int childCount = getChildCount();
+            int index;
+            int delta = 0;
+
+            View sel;
+            View oldSel = null;
+            View oldFirst = null;
+            View newSel = null;
+
+            View focusLayoutRestoreView = null;
+
+            // Remember stuff we will need down below
+            switch (mLayoutMode) {
+            case LAYOUT_SET_SELECTION:
+                index = mNextSelectedPosition - mFirstPosition;
+                if (index >= 0 && index < childCount) {
+                    newSel = getChildAt(index);
+                }
+                break;
+            case LAYOUT_FORCE_TOP:
+            case LAYOUT_FORCE_BOTTOM:
+            case LAYOUT_SPECIFIC:
+            case LAYOUT_SYNC:
+                break;
+            case LAYOUT_MOVE_SELECTION:
+            default:
+                // Remember the previously selected view
+                index = mSelectedPosition - mFirstPosition;
+                if (index >= 0 && index < childCount) {
+                    oldSel = getChildAt(index);
+                }
+
+                // Remember the previous first child
+                oldFirst = getChildAt(0);
+
+                if (mNextSelectedPosition >= 0) {
+                    delta = mNextSelectedPosition - mSelectedPosition;
+                }
+
+                // Caution: newSel might be null
+                newSel = getChildAt(index + delta);
+            }
+
+
+            boolean dataChanged = mDataChanged;
+            if (dataChanged) {
+                handleDataChanged();
+            }
+
+            // Handle the empty set by removing all views that are visible
+            // and calling it a day
+            if (mItemCount == 0) {
+                resetList();
+                invokeOnItemScrollListener();
+                return;
+            }
+
+            setSelectedPositionInt(mNextSelectedPosition);
+
+            // Pull all children into the RecycleBin.
+            // These views will be reused if possible
+            final int firstPosition = mFirstPosition;
+            final RecycleBin recycleBin = mRecycler;
+
+            // reset the focus restoration
+            View focusLayoutRestoreDirectChild = null;
+
+
+            // Don't put header or footer views into the Recycler. Those are
+            // already cached in mHeaderViews;
+            if (dataChanged) {
+                for (int i = 0; i < childCount; i++) {
+                    recycleBin.addScrapView(getChildAt(i));
+                    if (ViewDebug.TRACE_RECYCLER) {
+                        ViewDebug.trace(getChildAt(i),
+                                ViewDebug.RecyclerTraceType.MOVE_TO_SCRAP_HEAP, index, i);
+                    }
+                }
+            } else {
+                recycleBin.fillActiveViews(childCount, firstPosition);
+            }
+
+            // take focus back to us temporarily to avoid the eventual
+            // call to clear focus when removing the focused child below
+            // from messing things up when ViewRoot assigns focus back
+            // to someone else
+            final View focusedChild = getFocusedChild();
+            if (focusedChild != null) {
+                // TODO: in some cases focusedChild.getParent() == null
+
+                // we can remember the focused view to restore after relayout if the
+                // data hasn't changed, or if the focused position is a header or footer
+                if (!dataChanged || isDirectChildHeaderOrFooter(focusedChild)) {
+                    focusLayoutRestoreDirectChild = getFocusedChild();
+                    if (focusLayoutRestoreDirectChild != null) {
+
+                        // remember the specific view that had focus
+                        focusLayoutRestoreView = findFocus();
+                        if (focusLayoutRestoreView != null) {
+                            // tell it we are going to mess with it
+                            focusLayoutRestoreView.onStartTemporaryDetach();
+                        }
+                    }
+                }
+                requestFocus();
+            }
+
+            // Clear out old views
+            //removeAllViewsInLayout();
+            detachAllViewsFromParent();
+
+            switch (mLayoutMode) {
+            case LAYOUT_SET_SELECTION:
+                if (newSel != null) {
+                    sel = fillFromSelection(newSel.getTop(), childrenTop, childrenBottom);
+                } else {
+                    sel = fillFromMiddle(childrenTop, childrenBottom);
+                }
+                break;
+            case LAYOUT_SYNC:
+                sel = fillSpecific(mSyncPosition, mSpecificTop);
+                break;
+            case LAYOUT_FORCE_BOTTOM:
+                sel = fillUp(mItemCount - 1, childrenBottom);
+                adjustViewsUpOrDown();
+                break;
+            case LAYOUT_FORCE_TOP:
+                mFirstPosition = 0;
+                sel = fillFromTop(childrenTop);
+                adjustViewsUpOrDown();
+                break;
+            case LAYOUT_SPECIFIC:
+                sel = fillSpecific(reconcileSelectedPosition(), mSpecificTop);
+                break;
+            case LAYOUT_MOVE_SELECTION:
+                sel = moveSelection(oldSel, newSel, delta, childrenTop, childrenBottom);
+                break;
+            default:
+                if (childCount == 0) {
+                    if (!mStackFromBottom) {
+                        final int position = lookForSelectablePosition(0, true);
+                        setSelectedPositionInt(position);
+                        sel = fillFromTop(childrenTop);
+                    } else {
+                        final int position = lookForSelectablePosition(mItemCount - 1, false);
+                        setSelectedPositionInt(position);
+                        sel = fillUp(mItemCount - 1, childrenBottom);
+                    }
+                } else {
+                    if (mSelectedPosition >= 0 && mSelectedPosition < mItemCount) {
+                        sel = fillSpecific(mSelectedPosition,
+                                oldSel == null ? childrenTop : oldSel.getTop());
+                    } else if (mFirstPosition < mItemCount) {
+                        sel = fillSpecific(mFirstPosition,
+                                oldFirst == null ? childrenTop : oldFirst.getTop());
+                    } else {
+                        sel = fillSpecific(0, childrenTop);
+                    }
+                }
+                break;
+            }
+
+            // Flush any cached views that did not get reused above
+            recycleBin.scrapActiveViews();
+
+            if (sel != null) {
+               // the current selected item should get focus if items
+               // are focusable
+               if (mItemsCanFocus && hasFocus() && !sel.hasFocus()) {
+                   final boolean focusWasTaken = (sel == focusLayoutRestoreDirectChild &&
+                           focusLayoutRestoreView.requestFocus()) || sel.requestFocus();
+                   if (!focusWasTaken) {
+                       // selected item didn't take focus, fine, but still want
+                       // to make sure something else outside of the selected view
+                       // has focus
+                       final View focused = getFocusedChild();
+                       if (focused != null) {
+                           focused.clearFocus();
+                       }
+                       positionSelector(sel);
+                   } else {
+                       sel.setSelected(false);
+                       mSelectorRect.setEmpty();
+                   }
+               } else {
+                   positionSelector(sel);
+               }
+               mSelectedTop = sel.getTop();
+            } else {
+               mSelectedTop = 0;
+               mSelectorRect.setEmpty();
+
+               // even if there is not selected position, we may need to restore
+               // focus (i.e. something focusable in touch mode)
+               if (hasFocus() && focusLayoutRestoreView != null) {
+                   focusLayoutRestoreView.requestFocus();
+               }
+            }
+
+            // tell focus view we are done mucking with it, if it is still in
+            // our view hierarchy.
+            if (focusLayoutRestoreView != null
+                    && focusLayoutRestoreView.getWindowToken() != null) {
+                focusLayoutRestoreView.onFinishTemporaryDetach();
+            }
+            
+            mLayoutMode = LAYOUT_NORMAL;
+            mDataChanged = false;
+            mNeedSync = false;
+            setNextSelectedPositionInt(mSelectedPosition);
+
+            updateScrollIndicators();
+
+            if (mItemCount > 0) {
+                checkSelectionChanged();
+            }
+
+            invokeOnItemScrollListener();
+        } finally {
+            if (!blockLayoutRequests) {
+                mBlockLayoutRequests = false;
+            }
+        }
+    }
+
+    /**
+     * @param child a direct child of this list.
+     * @return Whether child is a header or footer view.
+     */
+    private boolean isDirectChildHeaderOrFooter(View child) {
+
+        final ArrayList<FixedViewInfo> headers = mHeaderViewInfos;
+        final int numHeaders = headers.size();
+        for (int i = 0; i < numHeaders; i++) {
+            if (child == headers.get(i).view) {
+                return true;
+            }
+        }
+        final ArrayList<FixedViewInfo> footers = mFooterViewInfos;
+        final int numFooters = footers.size();
+        for (int i = 0; i < numFooters; i++) {
+            if (child == footers.get(i).view) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Obtain the view and add it to our list of children. The view can be made
+     * fresh, converted from an unused view, or used as is if it was in the
+     * recycle bin.
+     *
+     * @param position Logical position in the list
+     * @param y Top or bottom edge of the view to add
+     * @param flow If flow is true, align top edge to y. If false, align bottom
+     *        edge to y.
+     * @param childrenLeft Left edge where children should be positioned
+     * @param selected Is this position selected?
+     * @return View that was added
+     */
+    private View makeAndAddView(int position, int y, boolean flow, int childrenLeft,
+            boolean selected) {
+        View child;
+
+
+        if (!mDataChanged) {
+            // Try to use an exsiting view for this position
+            child = mRecycler.getActiveView(position);
+            if (child != null) {
+                if (ViewDebug.TRACE_RECYCLER) {
+                    ViewDebug.trace(child, ViewDebug.RecyclerTraceType.RECYCLE_FROM_ACTIVE_HEAP,
+                            position, getChildCount());
+                }
+
+                // Found it -- we're using an existing child
+                // This just needs to be positioned
+                setupChild(child, position, y, flow, childrenLeft, selected, true);
+
+                return child;
+            }
+        }
+
+        // Make a new view for this position, or convert an unused view if possible
+        child = obtainView(position);
+
+        // This needs to be positioned and measured
+        setupChild(child, position, y, flow, childrenLeft, selected, false);
+
+        return child;
+    }
+
+    /**
+     * Add a view as a child and make sure it is measured (if necessary) and
+     * positioned properly.
+     *
+     * @param child The view to add
+     * @param position The position of this child
+     * @param y The y position relative to which this view will be positioned
+     * @param flowDown If true, align top edge to y. If false, align bottom
+     *        edge to y.
+     * @param childrenLeft Left edge where children should be positioned
+     * @param selected Is this position selected?
+     * @param recycled Has this view been pulled from the recycle bin? If so it
+     *        does not need to be remeasured.
+     */
+    private void setupChild(View child, int position, int y, boolean flowDown, int childrenLeft,
+            boolean selected, boolean recycled) {
+        final boolean isSelected = selected && shouldShowSelector();
+        final boolean updateChildSelected = isSelected != child.isSelected();
+        final boolean needToMeasure = !recycled || updateChildSelected || child.isLayoutRequested();
+
+        // Respect layout params that are already in the view. Otherwise make some up...
+        // noinspection unchecked
+        AbsListView.LayoutParams p = (AbsListView.LayoutParams) child.getLayoutParams();
+        if (p == null) {
+            p = new AbsListView.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT,
+                    ViewGroup.LayoutParams.WRAP_CONTENT, 0);
+        }
+        p.viewType = mAdapter.getItemViewType(position);
+
+        if (recycled) {
+            attachViewToParent(child, flowDown ? -1 : 0, p);
+        } else {
+            addViewInLayout(child, flowDown ? -1 : 0, p, true);
+        }
+
+        if (updateChildSelected) {
+            child.setSelected(isSelected);
+        }
+
+        if (mChoiceMode != CHOICE_MODE_NONE && mCheckStates != null) {
+            if (child instanceof Checkable) {
+                ((Checkable) child).setChecked(mCheckStates.get(position));
+            }
+        }
+
+        if (needToMeasure) {
+            int childWidthSpec = ViewGroup.getChildMeasureSpec(mWidthMeasureSpec,
+                    mListPadding.left + mListPadding.right, p.width);
+            int lpHeight = p.height;
+            int childHeightSpec;
+            if (lpHeight > 0) {
+                childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight, MeasureSpec.EXACTLY);
+            } else {
+                childHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
+            }
+            child.measure(childWidthSpec, childHeightSpec);
+        } else {
+            cleanupLayoutState(child);
+        }
+
+        final int w = child.getMeasuredWidth();
+        final int h = child.getMeasuredHeight();
+        final int childTop = flowDown ? y : y - h;
+
+        if (needToMeasure) {
+            final int childRight = childrenLeft + w;
+            final int childBottom = childTop + h;
+            child.layout(childrenLeft, childTop, childRight, childBottom);
+        } else {
+            child.offsetLeftAndRight(childrenLeft - child.getLeft());
+            child.offsetTopAndBottom(childTop - child.getTop());
+        }
+
+        if (mCachingStarted && !child.isDrawingCacheEnabled()) {
+            child.setDrawingCacheEnabled(true);
+        }
+    }
+
+    @Override
+    protected boolean canAnimate() {
+        return super.canAnimate() && mItemCount > 0;
+    }
+
+    /**
+     * Sets the currently selected item. If in touch mode, the item will not be selected
+     * but it will still be positioned appropriately. If the specified selection position
+     * is less than 0, then the item at position 0 will be selected.
+     *
+     * @param position Index (starting at 0) of the data item to be selected.
+     */
+    @Override
+    public void setSelection(int position) {
+        setSelectionFromTop(position, 0);
+    }
+
+    /**
+     * Sets the selected item and positions the selection y pixels from the top edge
+     * of the ListView. (If in touch mode, the item will not be selected but it will
+     * still be positioned appropriately.)
+     *
+     * @param position Index (starting at 0) of the data item to be selected.
+     * @param y The distance from the top edge of the ListView (plus padding) that the
+     *        item will be positioned.
+     */
+    public void setSelectionFromTop(int position, int y) {
+        if (mAdapter == null) {
+            return;
+        }
+
+        if (!isInTouchMode()) {
+            position = lookForSelectablePosition(position, true);
+            if (position >= 0) {
+                setNextSelectedPositionInt(position);
+            }
+        } else {
+            mResurrectToPosition = position;
+        }
+
+        if (position >= 0) {
+            mLayoutMode = LAYOUT_SPECIFIC;
+            mSpecificTop = mListPadding.top + y;
+
+            if (mNeedSync) {
+                mSyncPosition = position;
+                mSyncRowId = mAdapter.getItemId(position);
+            }
+
+            requestLayout();
+        }
+    }
+
+    /**
+     * Makes the item at the supplied position selected.
+     *
+     * @param position the position of the item to select
+     */
+    @Override
+    void setSelectionInt(int position) {
+        mBlockLayoutRequests = true;
+        setNextSelectedPositionInt(position);
+        layoutChildren();
+        mBlockLayoutRequests = false;
+    }
+
+    /**
+     * Find a position that can be selected (i.e., is not a separator).
+     *
+     * @param position The starting position to look at.
+     * @param lookDown Whether to look down for other positions.
+     * @return The next selectable position starting at position and then searching either up or
+     *         down. Returns {@link #INVALID_POSITION} if nothing can be found.
+     */
+    @Override
+    int lookForSelectablePosition(int position, boolean lookDown) {
+        final ListAdapter adapter = mAdapter;
+        if (adapter == null || isInTouchMode()) {
+            return INVALID_POSITION;
+        }
+
+        final int count = adapter.getCount();
+        if (!mAreAllItemsSelectable) {
+            if (lookDown) {
+                position = Math.max(0, position);
+                while (position < count && !adapter.isEnabled(position)) {
+                    position++;
+                }
+            } else {
+                position = Math.min(position, count - 1);
+                while (position >= 0 && !adapter.isEnabled(position)) {
+                    position--;
+                }
+            }
+
+            if (position < 0 || position >= count) {
+                return INVALID_POSITION;
+            }
+            return position;
+        } else {
+            if (position < 0 || position >= count) {
+                return INVALID_POSITION;
+            }
+            return position;
+        }
+    }
+
+    /**
+     * setSelectionAfterHeaderView set the selection to be the first list item
+     * after the header views.
+     */
+    public void setSelectionAfterHeaderView() {
+        final int count = mHeaderViewInfos.size();
+        if (count > 0) {
+            mNextSelectedPosition = 0;
+            return;
+        }
+
+        if (mAdapter != null) {
+            setSelection(count);
+        } else {
+            mNextSelectedPosition = count;
+            mLayoutMode = LAYOUT_SET_SELECTION;
+        }
+
+    }
+
+    @Override
+    public boolean dispatchKeyEvent(KeyEvent event) {
+        // Dispatch in the normal way
+        boolean handled = super.dispatchKeyEvent(event);
+        if (!handled) {
+            // If we didn't handle it...
+            View focused = getFocusedChild();
+            if (focused != null && event.getAction() == KeyEvent.ACTION_DOWN) {
+                // ... and our focused child didn't handle it
+                // ... give it to ourselves so we can scroll if necessary
+                handled = onKeyDown(event.getKeyCode(), event);
+            }
+        }
+        return handled;
+    }
+
+    @Override
+    public boolean onKeyDown(int keyCode, KeyEvent event) {
+        return commonKey(keyCode, 1, event);
+    }
+
+    @Override
+    public boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event) {
+        return commonKey(keyCode, repeatCount, event);
+    }
+
+    @Override
+    public boolean onKeyUp(int keyCode, KeyEvent event) {
+        return commonKey(keyCode, 1, event);
+    }
+
+    private boolean commonKey(int keyCode, int count, KeyEvent event) {
+        if (mAdapter == null) {
+            return false;
+        }
+
+        if (mDataChanged) {
+            layoutChildren();
+        }
+
+        boolean handled = false;
+        int action = event.getAction();
+
+        if (action != KeyEvent.ACTION_UP) {
+            if (mSelectedPosition < 0) {
+                switch (keyCode) {
+                case KeyEvent.KEYCODE_DPAD_UP:
+                case KeyEvent.KEYCODE_DPAD_DOWN:
+                case KeyEvent.KEYCODE_DPAD_CENTER:
+                case KeyEvent.KEYCODE_ENTER:
+                case KeyEvent.KEYCODE_SPACE:
+                    if (resurrectSelection()) {
+                        return true;
+                    }
+                }
+            }
+            switch (keyCode) {
+            case KeyEvent.KEYCODE_DPAD_UP:
+                if (!event.isAltPressed()) {
+                    while (count > 0) {
+                        handled = arrowScroll(FOCUS_UP);
+                        count--;
+                    }
+                } else {
+                    handled = fullScroll(FOCUS_UP);
+                }
+                break;
+
+            case KeyEvent.KEYCODE_DPAD_DOWN:
+                if (!event.isAltPressed()) {
+                    while (count > 0) {
+                        handled = arrowScroll(FOCUS_DOWN);
+                        count--;
+                    }
+                } else {
+                    handled = fullScroll(FOCUS_DOWN);
+                }
+                break;
+
+            case KeyEvent.KEYCODE_DPAD_LEFT:
+                handled = handleHorizontalFocusWithinListItem(View.FOCUS_LEFT);
+                break;
+            case KeyEvent.KEYCODE_DPAD_RIGHT:
+                handled = handleHorizontalFocusWithinListItem(View.FOCUS_RIGHT);
+                break;
+
+            case KeyEvent.KEYCODE_DPAD_CENTER:
+            case KeyEvent.KEYCODE_ENTER:
+                if (mItemCount > 0 && event.getRepeatCount() == 0) {
+                    keyPressed();
+                }
+                handled = true;
+                break;
+
+            case KeyEvent.KEYCODE_SPACE:
+                if (mPopup == null || !mPopup.isShowing()) {
+                    if (!event.isShiftPressed()) {
+                        pageScroll(FOCUS_DOWN);
+                    } else {
+                        pageScroll(FOCUS_UP);
+                    }
+                    handled = true;
+                }
+                break;
+            }
+        }
+
+        if (!handled) {
+            handled = sendToTextFilter(keyCode, count, event);
+        }
+
+        if (handled) {
+            return true;
+        } else {
+            switch (action) {
+                case KeyEvent.ACTION_DOWN:
+                    return super.onKeyDown(keyCode, event);
+
+                case KeyEvent.ACTION_UP:
+                    return super.onKeyUp(keyCode, event);
+
+                case KeyEvent.ACTION_MULTIPLE:
+                    return super.onKeyMultiple(keyCode, count, event);
+
+                default: // shouldn't happen
+                    return false;
+            }
+        }
+    }
+
+    /**
+     * Scrolls up or down by the number of items currently present on screen.
+     *
+     * @param direction either {@link View#FOCUS_UP} or {@link View#FOCUS_DOWN}
+     * @return whether selection was moved
+     */
+    boolean pageScroll(int direction) {
+        int nextPage = -1;
+        boolean down = false;
+
+        if (direction == FOCUS_UP) {
+            nextPage = Math.max(0, mSelectedPosition - getChildCount() - 1);
+        } else if (direction == FOCUS_DOWN) {
+            nextPage = Math.min(mItemCount - 1, mSelectedPosition + getChildCount() - 1);
+            down = true;
+        }
+
+        if (nextPage >= 0) {
+            int position = lookForSelectablePosition(nextPage, down);
+            if (position >= 0) {
+                mLayoutMode = LAYOUT_SPECIFIC;
+                mSpecificTop = mPaddingTop + getVerticalFadingEdgeLength();
+
+                if (down && position > mItemCount - getChildCount()) {
+                    mLayoutMode = LAYOUT_FORCE_BOTTOM;
+                }
+
+                if (!down && position < getChildCount()) {
+                    mLayoutMode = LAYOUT_FORCE_TOP;
+                }
+
+                setSelectionInt(position);
+                invokeOnItemScrollListener();
+                invalidate();
+
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    /**
+     * Go to the last or first item if possible (not worrying about panning across or navigating
+     * within the internal focus of the currently selected item.)
+     *
+     * @param direction either {@link View#FOCUS_UP} or {@link View#FOCUS_DOWN}
+     *
+     * @return whether selection was moved
+     */
+    boolean fullScroll(int direction) {
+        boolean moved = false;
+        if (direction == FOCUS_UP) {
+            if (mSelectedPosition != 0) {
+                int position = lookForSelectablePosition(0, true);
+                if (position >= 0) {
+                    mLayoutMode = LAYOUT_FORCE_TOP;
+                    setSelectionInt(position);
+                    invokeOnItemScrollListener();
+                }
+                moved = true;
+            }
+        } else if (direction == FOCUS_DOWN) {
+            if (mSelectedPosition < mItemCount - 1) {
+                int position = lookForSelectablePosition(mItemCount - 1, true);
+                if (position >= 0) {
+                    mLayoutMode = LAYOUT_FORCE_BOTTOM;
+                    setSelectionInt(position);
+                    invokeOnItemScrollListener();
+                }
+                moved = true;
+            }
+        }
+
+        if (moved) {
+            invalidate();
+        }
+
+        return moved;
+    }
+
+    /**
+     * To avoid horizontal focus searches changing the selected item, we
+     * manually focus search within the selected item (as applicable), and
+     * prevent focus from jumping to something within another item.
+     * @param direction one of {View.FOCUS_LEFT, View.FOCUS_RIGHT}
+     * @return Whether this consumes the key event.
+     */
+    private boolean handleHorizontalFocusWithinListItem(int direction) {
+        if (direction != View.FOCUS_LEFT && direction != View.FOCUS_RIGHT)  {
+            throw new IllegalArgumentException("direction must be one of {View.FOCUS_LEFT, View.FOCUS_RIGHT}");
+        }
+
+        final int numChildren = getChildCount();
+        if (mItemsCanFocus && numChildren > 0 && mSelectedPosition != INVALID_POSITION) {
+            final View selectedView = getSelectedView();
+            if (selectedView.hasFocus() && selectedView instanceof ViewGroup) {
+                final View currentFocus = selectedView.findFocus();
+                final View nextFocus = FocusFinder.getInstance().findNextFocus(
+                        (ViewGroup) selectedView,
+                        currentFocus,
+                        direction);
+                if (nextFocus != null) {
+                    // do the math to get interesting rect in next focus' coordinates
+                    currentFocus.getFocusedRect(mTempRect);
+                    offsetDescendantRectToMyCoords(currentFocus, mTempRect);
+                    offsetRectIntoDescendantCoords(nextFocus, mTempRect);
+                    if (nextFocus.requestFocus(direction, mTempRect)) {
+                        return true;
+                    }
+                }
+                // we are blocking the key from being handled (by returning true)
+                // if the global result is going to be some other view within this
+                // list.  this is to acheive the overall goal of having
+                // horizontal d-pad navigation remain in the current item.
+                final View globalNextFocus = FocusFinder.getInstance()
+                        .findNextFocus(
+                                (ViewGroup) getRootView(),
+                                currentFocus,
+                                direction);
+                if (globalNextFocus != null) {
+                    return isViewAncestorOf(globalNextFocus, this);
+                }
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Scrolls to the next or previous item if possible.
+     *
+     * @param direction either {@link View#FOCUS_UP} or {@link View#FOCUS_DOWN}
+     *
+     * @return whether selection was moved
+     */
+    boolean arrowScroll(int direction) {
+        try {
+            mInLayout = true;
+            final boolean handled = arrowScrollImpl(direction);
+            if (handled) {
+                playSoundEffect(SoundEffectConstants.getContantForFocusDirection(direction));
+            }
+            return handled;
+        } finally {
+            mInLayout = false;
+        }
+    }
+
+    /**
+     * Handle an arrow scroll going up or down.  Take into account whether items are selectable,
+     * whether there are focusable items etc.
+     *
+     * @param direction Either {@link android.view.View#FOCUS_UP} or {@link android.view.View#FOCUS_DOWN}.
+     * @return Whether any scrolling, selection or focus change occured.
+     */
+    private boolean arrowScrollImpl(int direction) {
+        if (getChildCount() <= 0) {
+            return false;
+        }
+
+        View selectedView = getSelectedView();
+
+        int nextSelectedPosition = lookForSelectablePositionOnScreen(direction);
+        int amountToScroll = amountToScroll(direction, nextSelectedPosition);
+
+        // if we are moving focus, we may OVERRIDE the default behavior
+        final ArrowScrollFocusResult focusResult = mItemsCanFocus ? arrowScrollFocused(direction) : null;
+        if (focusResult != null) {
+            nextSelectedPosition = focusResult.getSelectedPosition();
+            amountToScroll = focusResult.getAmountToScroll();
+        }
+
+        boolean needToRedraw = focusResult != null;
+        if (nextSelectedPosition != INVALID_POSITION) {
+            handleNewSelectionChange(selectedView, direction, nextSelectedPosition, focusResult != null);
+            setSelectedPositionInt(nextSelectedPosition);
+            setNextSelectedPositionInt(nextSelectedPosition);
+            selectedView = getSelectedView();
+            if (mItemsCanFocus && focusResult == null) {
+                // there was no new view found to take focus, make sure we
+                // don't leave focus with the old selection
+                final View focused = getFocusedChild();
+                if (focused != null) {
+                    focused.clearFocus();
+                }
+            }
+            needToRedraw = true;
+            checkSelectionChanged();
+        }
+
+        if (amountToScroll > 0) {
+            scrollListItemsBy((direction == View.FOCUS_UP) ? amountToScroll : -amountToScroll);
+            needToRedraw = true;
+        }
+
+        // if we didn't find a new focusable, make sure any existing focused
+        // item that was panned off screen gives up focus.
+        if (mItemsCanFocus && (focusResult == null)
+                && selectedView != null && selectedView.hasFocus()) {
+            final View focused = selectedView.findFocus();
+            if (distanceToView(focused) > 0) {
+                focused.clearFocus();
+            }
+        }
+
+        // if  the current selection is panned off, we need to remove the selection
+        if (nextSelectedPosition == INVALID_POSITION && selectedView != null
+                && !isViewAncestorOf(selectedView, this)) {
+            selectedView = null;
+            hideSelector();
+
+            // but we don't want to set the ressurect position (that would make subsequent
+            // unhandled key events bring back the item we just scrolled off!)
+            mResurrectToPosition = INVALID_POSITION;
+        }
+
+        if (needToRedraw) {
+            if (selectedView != null) {
+                positionSelector(selectedView);
+                mSelectedTop = selectedView.getTop();
+            }
+            invalidate();
+            invokeOnItemScrollListener();
+            return true;
+        }
+
+        return false;
+    }
+
+    /**
+     * When selection changes, it is possible that the previously selected or the
+     * next selected item will change its size.  If so, we need to offset some folks,
+     * and re-layout the items as appropriate.
+     *
+     * @param selectedView The currently selected view (before changing selection).
+     *   should be <code>null</code> if there was no previous selection.
+     * @param direction Either {@link android.view.View#FOCUS_UP} or
+     *        {@link android.view.View#FOCUS_DOWN}.
+     * @param newSelectedPosition The position of the next selection.
+     * @param newFocusAssigned whether new focus was assigned.  This matters because
+     *        when something has focus, we don't want to show selection (ugh).
+     */
+    private void handleNewSelectionChange(View selectedView, int direction, int newSelectedPosition,
+            boolean newFocusAssigned) {
+        if (newSelectedPosition == INVALID_POSITION) {
+            throw new IllegalArgumentException("newSelectedPosition needs to be valid");
+        }
+
+        // whether or not we are moving down or up, we want to preserve the
+        // top of whatever view is on top:
+        // - moving down: the view that had selection
+        // - moving up: the view that is getting selection
+        View topView;
+        View bottomView;
+        int topViewIndex, bottomViewIndex;
+        boolean topSelected = false;
+        final int selectedIndex = mSelectedPosition - mFirstPosition;
+        final int nextSelectedIndex = newSelectedPosition - mFirstPosition;
+        if (direction == View.FOCUS_UP) {
+            topViewIndex = nextSelectedIndex;
+            bottomViewIndex = selectedIndex;
+            topView = getChildAt(topViewIndex);
+            bottomView = selectedView;
+            topSelected = true;
+        } else {
+            topViewIndex = selectedIndex;
+            bottomViewIndex = nextSelectedIndex;
+            topView = selectedView;
+            bottomView = getChildAt(bottomViewIndex);
+        }
+
+        final int numChildren = getChildCount();
+
+        // start with top view: is it changing size?
+        if (topView != null) {
+            topView.setSelected(!newFocusAssigned && topSelected);
+            measureAndAdjustDown(topView, topViewIndex, numChildren);
+        }
+
+        // is the bottom view changing size?
+        if (bottomView != null) {
+            bottomView.setSelected(!newFocusAssigned && !topSelected);
+            measureAndAdjustDown(bottomView, bottomViewIndex, numChildren);
+        }
+    }
+
+    /**
+     * Re-measure a child, and if its height changes, lay it out preserving its
+     * top, and adjust the children below it appropriately.
+     * @param child The child
+     * @param childIndex The view group index of the child.
+     * @param numChildren The number of children in the view group.
+     */
+    private void measureAndAdjustDown(View child, int childIndex, int numChildren) {
+        int oldHeight = child.getHeight();
+        measureItem(child);
+        if (child.getMeasuredHeight() != oldHeight) {
+            // lay out the view, preserving its top
+            relayoutMeasuredItem(child);
+
+            // adjust views below appropriately
+            final int heightDelta = child.getMeasuredHeight() - oldHeight;
+            for (int i = childIndex + 1; i < numChildren; i++) {
+                getChildAt(i).offsetTopAndBottom(heightDelta);
+            }
+        }
+    }
+
+    /**
+     * Measure a particular list child.
+     * TODO: unify with setUpChild.
+     * @param child The child.
+     */
+    private void measureItem(View child) {
+        ViewGroup.LayoutParams p = child.getLayoutParams();
+        if (p == null) {
+            p = new ViewGroup.LayoutParams(
+                    ViewGroup.LayoutParams.FILL_PARENT,
+                    ViewGroup.LayoutParams.WRAP_CONTENT);
+        }
+
+        int childWidthSpec = ViewGroup.getChildMeasureSpec(mWidthMeasureSpec,
+                mListPadding.left + mListPadding.right, p.width);
+        int lpHeight = p.height;
+        int childHeightSpec;
+        if (lpHeight > 0) {
+            childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight, MeasureSpec.EXACTLY);
+        } else {
+            childHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
+        }
+        child.measure(childWidthSpec, childHeightSpec);
+    }
+
+    /**
+     * Layout a child that has been measured, preserving its top position.
+     * TODO: unify with setUpChild.
+     * @param child The child.
+     */
+    private void relayoutMeasuredItem(View child) {
+        final int w = child.getMeasuredWidth();
+        final int h = child.getMeasuredHeight();
+        final int childLeft = mListPadding.left;
+        final int childRight = childLeft + w;
+        final int childTop = child.getTop();
+        final int childBottom = childTop + h;
+        child.layout(childLeft, childTop, childRight, childBottom);
+    }
+
+    /**
+     * @return The amount to preview next items when arrow srolling.
+     */
+    private int getArrowScrollPreviewLength() {
+        return Math.max(MIN_SCROLL_PREVIEW_PIXELS, getVerticalFadingEdgeLength());
+    }
+
+    /**
+     * Determine how much we need to scroll in order to get the next selected view
+     * visible, with a fading edge showing below as applicable.  The amount is
+     * capped at {@link #getMaxScrollAmount()} .
+     *
+     * @param direction either {@link android.view.View#FOCUS_UP} or
+     *        {@link android.view.View#FOCUS_DOWN}.
+     * @param nextSelectedPosition The position of the next selection, or
+     *        {@link #INVALID_POSITION} if there is no next selectable position
+     * @return The amount to scroll. Note: this is always positive!  Direction
+     *         needs to be taken into account when actually scrolling.
+     */
+    private int amountToScroll(int direction, int nextSelectedPosition) {
+        final int listBottom = getHeight() - mListPadding.bottom;
+        final int listTop = mListPadding.top;
+
+        final int numChildren = getChildCount();
+
+        if (direction == View.FOCUS_DOWN) {
+            int indexToMakeVisible = numChildren - 1;
+            if (nextSelectedPosition != INVALID_POSITION) {
+                indexToMakeVisible = nextSelectedPosition - mFirstPosition;
+            }
+
+            final int positionToMakeVisible = mFirstPosition + indexToMakeVisible;
+            final View viewToMakeVisible = getChildAt(indexToMakeVisible);
+
+            int goalBottom = listBottom;
+            if (positionToMakeVisible < mItemCount - 1) {
+                goalBottom -= getArrowScrollPreviewLength();
+            }
+
+            if (viewToMakeVisible.getBottom() <= goalBottom) {
+                // item is fully visible.
+                return 0;
+            }
+
+            if (nextSelectedPosition != INVALID_POSITION
+                    && (goalBottom - viewToMakeVisible.getTop()) >= getMaxScrollAmount()) {
+                // item already has enough of it visible, changing selection is good enough
+                return 0;
+            }
+
+            int amountToScroll = (viewToMakeVisible.getBottom() - goalBottom);
+
+            if ((mFirstPosition + numChildren) == mItemCount) {
+                // last is last in list -> make sure we don't scroll past it
+                final int max = getChildAt(numChildren - 1).getBottom() - listBottom;
+                amountToScroll = Math.min(amountToScroll, max);
+            }
+
+            return Math.min(amountToScroll, getMaxScrollAmount());
+        } else {
+            int indexToMakeVisible = 0;
+            if (nextSelectedPosition != INVALID_POSITION) {
+                indexToMakeVisible = nextSelectedPosition - mFirstPosition;
+            }
+            final int positionToMakeVisible = mFirstPosition + indexToMakeVisible;
+            final View viewToMakeVisible = getChildAt(indexToMakeVisible);
+            int goalTop = listTop;
+            if (positionToMakeVisible > 0) {
+                goalTop += getArrowScrollPreviewLength();
+            }
+            if (viewToMakeVisible.getTop() >= goalTop) {
+                // item is fully visible.
+                return 0;
+            }
+
+            if (nextSelectedPosition != INVALID_POSITION &&
+                    (viewToMakeVisible.getBottom() - goalTop) >= getMaxScrollAmount()) {
+                // item already has enough of it visible, changing selection is good enough
+                return 0;
+            }
+
+            int amountToScroll = (goalTop - viewToMakeVisible.getTop());
+            if (mFirstPosition == 0) {
+                // first is first in list -> make sure we don't scroll past it
+                final int max = listTop - getChildAt(0).getTop();
+                amountToScroll = Math.min(amountToScroll,  max);
+            }
+            return Math.min(amountToScroll, getMaxScrollAmount());
+        }
+    }
+
+    /**
+     * Holds results of focus aware arrow scrolling.
+     */
+    static private class ArrowScrollFocusResult {
+        private int mSelectedPosition;
+        private int mAmountToScroll;
+
+        /**
+         * How {@link android.widget.ListView#arrowScrollFocused} returns its values.
+         */
+        void populate(int selectedPosition, int amountToScroll) {
+            mSelectedPosition = selectedPosition;
+            mAmountToScroll = amountToScroll;
+        }
+
+        public int getSelectedPosition() {
+            return mSelectedPosition;
+        }
+
+        public int getAmountToScroll() {
+            return mAmountToScroll;
+        }
+    }
+
+    /**
+     * @param direction either {@link android.view.View#FOCUS_UP} or
+     *        {@link android.view.View#FOCUS_DOWN}.
+     * @return The position of the next selectable position of the views that
+     *         are currently visible, taking into account the fact that there might
+     *         be no selection.  Returns {@link #INVALID_POSITION} if there is no
+     *         selectable view on screen in the given direction.
+     */
+    private int lookForSelectablePositionOnScreen(int direction) {
+        final int firstPosition = mFirstPosition;
+        if (direction == View.FOCUS_DOWN) {
+            int startPos = (mSelectedPosition != INVALID_POSITION) ?
+                    mSelectedPosition + 1 :
+                    firstPosition;
+            if (startPos >= mAdapter.getCount()) {
+                return INVALID_POSITION;
+            }
+            if (startPos < firstPosition) {
+                startPos = firstPosition;
+            }
+            
+            final int lastVisiblePos = getLastVisiblePosition();
+            final ListAdapter adapter = getAdapter();
+            for (int pos = startPos; pos <= lastVisiblePos; pos++) {
+                if (adapter.isEnabled(pos)
+                        && getChildAt(pos - firstPosition).getVisibility() == View.VISIBLE) {
+                    return pos;
+                }
+            }
+        } else {
+            int last = firstPosition + getChildCount() - 1;
+            int startPos = (mSelectedPosition != INVALID_POSITION) ?
+                    mSelectedPosition - 1 :
+                    firstPosition + getChildCount() - 1;
+            if (startPos < 0) {
+                return INVALID_POSITION;
+            }
+            if (startPos > last) {
+                startPos = last;
+            }
+
+            final ListAdapter adapter = getAdapter();
+            for (int pos = startPos; pos >= firstPosition; pos--) {
+                if (adapter.isEnabled(pos)
+                        && getChildAt(pos - firstPosition).getVisibility() == View.VISIBLE) {
+                    return pos;
+                }
+            }
+        }
+        return INVALID_POSITION;
+    }
+
+    /**
+     * Do an arrow scroll based on focus searching.  If a new view is
+     * given focus, return the selection delta and amount to scroll via
+     * an {@link ArrowScrollFocusResult}, otherwise, return null.
+     *
+     * @param direction either {@link android.view.View#FOCUS_UP} or
+     *        {@link android.view.View#FOCUS_DOWN}.
+     * @return The result if focus has changed, or <code>null</code>.
+     */
+    private ArrowScrollFocusResult arrowScrollFocused(final int direction) {
+        final View selectedView = getSelectedView();
+        View newFocus;
+        if (selectedView != null && selectedView.hasFocus()) {
+            View oldFocus = selectedView.findFocus();
+            newFocus = FocusFinder.getInstance().findNextFocus(this, oldFocus, direction);
+        } else {
+            if (direction == View.FOCUS_DOWN) {
+                final boolean topFadingEdgeShowing = (mFirstPosition > 0);
+                final int listTop = mListPadding.top +
+                        (topFadingEdgeShowing ? getArrowScrollPreviewLength() : 0);
+                final int ySearchPoint =
+                        (selectedView != null && selectedView.getTop() > listTop) ?
+                                selectedView.getTop() :
+                                listTop;
+                mTempRect.set(0, ySearchPoint, 0, ySearchPoint);
+            } else {
+                final boolean bottomFadingEdgeShowing =
+                        (mFirstPosition + getChildCount() - 1) < mItemCount;
+                final int listBottom = getHeight() - mListPadding.bottom -
+                        (bottomFadingEdgeShowing ? getArrowScrollPreviewLength() : 0);
+                final int ySearchPoint =
+                        (selectedView != null && selectedView.getBottom() < listBottom) ?
+                                selectedView.getBottom() :
+                                listBottom;
+                mTempRect.set(0, ySearchPoint, 0, ySearchPoint);
+            }
+            newFocus = FocusFinder.getInstance().findNextFocusFromRect(this, mTempRect, direction);
+        }
+
+        if (newFocus != null) {
+            final int positionOfNewFocus = positionOfNewFocus(newFocus);
+
+            // if the focus change is in a different new position, make sure
+            // we aren't jumping over another selectable position
+            if (mSelectedPosition != INVALID_POSITION && positionOfNewFocus != mSelectedPosition) {
+                final int selectablePosition = lookForSelectablePositionOnScreen(direction);
+                if (selectablePosition != INVALID_POSITION &&
+                        ((direction == View.FOCUS_DOWN && selectablePosition < positionOfNewFocus) ||
+                        (direction == View.FOCUS_UP && selectablePosition > positionOfNewFocus))) {
+                    return null;
+                }
+            }
+
+            int focusScroll = amountToScrollToNewFocus(direction, newFocus, positionOfNewFocus);
+
+            final int maxScrollAmount = getMaxScrollAmount();
+            if (focusScroll < maxScrollAmount) {
+                // not moving too far, safe to give next view focus
+                newFocus.requestFocus(direction);
+                mArrowScrollFocusResult.populate(positionOfNewFocus, focusScroll);
+                return mArrowScrollFocusResult;
+            } else if (distanceToView(newFocus) < maxScrollAmount){
+                // Case to consider:
+                // too far to get entire next focusable on screen, but by going
+                // max scroll amount, we are getting it at least partially in view,
+                // so give it focus and scroll the max ammount.
+                newFocus.requestFocus(direction);
+                mArrowScrollFocusResult.populate(positionOfNewFocus, maxScrollAmount);
+                return mArrowScrollFocusResult;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * @param newFocus The view that would have focus.
+     * @return the position that contains newFocus
+     */
+    private int positionOfNewFocus(View newFocus) {
+        final int numChildren = getChildCount();
+        for (int i = 0; i < numChildren; i++) {
+            final View child = getChildAt(i);
+            if (isViewAncestorOf(newFocus, child)) {
+                return mFirstPosition + i;
+            }
+        }
+        throw new IllegalArgumentException("newFocus is not a child of any of the"
+                + " children of the list!");
+    }
+
+    /**
+     * Return true if child is an ancestor of parent, (or equal to the parent).
+     */
+    private boolean isViewAncestorOf(View child, View parent) {
+        if (child == parent) {
+            return true;
+        }
+
+        final ViewParent theParent = child.getParent();
+        return (theParent instanceof ViewGroup) && isViewAncestorOf((View) theParent, parent);
+    }
+
+    /**
+     * Determine how much we need to scroll in order to get newFocus in view.
+     * @param direction either {@link android.view.View#FOCUS_UP} or
+     *        {@link android.view.View#FOCUS_DOWN}.
+     * @param newFocus The view that would take focus.
+     * @param positionOfNewFocus The position of the list item containing newFocus
+     * @return The amount to scroll.  Note: this is always positive!  Direction
+     *   needs to be taken into account when actually scrolling.
+     */
+    private int amountToScrollToNewFocus(int direction, View newFocus, int positionOfNewFocus) {
+        int amountToScroll = 0;
+        newFocus.getDrawingRect(mTempRect);
+        offsetDescendantRectToMyCoords(newFocus, mTempRect);
+        if (direction == View.FOCUS_UP) {
+            if (mTempRect.top < mListPadding.top) {
+                amountToScroll = mListPadding.top - mTempRect.top;
+                if (positionOfNewFocus > 0) {
+                    amountToScroll += getArrowScrollPreviewLength();
+                }
+            }
+        } else {
+            final int listBottom = getHeight() - mListPadding.bottom;
+            if (mTempRect.bottom > listBottom) {
+                amountToScroll = mTempRect.bottom - listBottom;
+                if (positionOfNewFocus < mItemCount - 1) {
+                    amountToScroll += getArrowScrollPreviewLength();
+                }
+            }
+        }
+        return amountToScroll;
+    }
+
+    /**
+     * Determine the distance to the nearest edge of a view in a particular
+     * direciton.
+     * @param descendant A descendant of this list.
+     * @return The distance, or 0 if the nearest edge is already on screen.
+     */
+    private int distanceToView(View descendant) {
+        int distance = 0;
+        descendant.getDrawingRect(mTempRect);
+        offsetDescendantRectToMyCoords(descendant, mTempRect);
+        final int listBottom = mBottom - mTop - mListPadding.bottom;
+        if (mTempRect.bottom < mListPadding.top) {
+            distance = mListPadding.top - mTempRect.bottom;
+        } else if (mTempRect.top > listBottom) {
+            distance = mTempRect.top - listBottom;
+        }
+        return distance;
+    }
+
+
+    /**
+     * Scroll the children by amount, adding a view at the end and removing
+     * views that fall off as necessary.
+     *
+     * @param amount The amount (positive or negative) to scroll.
+     */
+    private void scrollListItemsBy(int amount) {
+        offsetChildrenTopAndBottom(amount);
+
+        final int listBottom = getHeight() - mListPadding.bottom;
+        final int listTop = mListPadding.top;
+
+        if (amount < 0) {
+            // shifted items up
+
+            // may need to pan views into the bottom space
+            int numChildren = getChildCount();
+            View last = getChildAt(numChildren - 1);
+            while (last.getBottom() < listBottom) {
+                final int lastVisiblePosition = mFirstPosition + numChildren - 1;
+                if (lastVisiblePosition < mItemCount - 1) {
+                    last = addViewBelow(last, lastVisiblePosition);
+                    numChildren++;
+                } else {
+                    break;
+                }
+            }
+
+            // may have brought in the last child of the list that is skinnier
+            // than the fading edge, thereby leaving space at the end.  need
+            // to shift back
+            if (last.getBottom() < listBottom) {
+                offsetChildrenTopAndBottom(listBottom - last.getBottom());
+            }
+
+            // top views may be panned off screen
+            View first = getChildAt(0);
+            while (first.getBottom() < listTop) {
+                removeViewInLayout(first);
+                mRecycler.addScrapView(first);
+                first = getChildAt(0);
+                mFirstPosition++;
+            }
+        } else {
+            // shifted items down
+            View first = getChildAt(0);
+
+            // may need to pan views into top
+            while ((first.getTop() > listTop) && (mFirstPosition > 0)) {
+                first = addViewAbove(first, mFirstPosition);
+                mFirstPosition--;
+            }
+
+            // may have brought the very first child of the list in too far and
+            // need to shift it back
+            if (first.getTop() > listTop) {
+                offsetChildrenTopAndBottom(listTop - first.getTop());
+            }
+
+            int lastIndex = getChildCount() - 1;
+            View last = getChildAt(lastIndex);
+
+            // bottom view may be panned off screen
+            while (last.getTop() > listBottom) {
+                removeViewInLayout(last);
+                mRecycler.addScrapView(last);
+                last = getChildAt(--lastIndex);
+            }
+        }
+    }
+
+    private View addViewAbove(View theView, int position) {
+        int abovePosition = position - 1;
+        View view = obtainView(abovePosition);
+        int edgeOfNewChild = theView.getTop() - mDividerHeight;
+        setupChild(view, abovePosition, edgeOfNewChild, false, mListPadding.left, false, false);
+        return view;
+    }
+
+    private View addViewBelow(View theView, int position) {
+        int belowPosition = position + 1;
+        View view = obtainView(belowPosition);
+        int edgeOfNewChild = theView.getBottom() + mDividerHeight;
+        setupChild(view, belowPosition, edgeOfNewChild, true, mListPadding.left, false, false);
+        return view;
+    }
+
+    /**
+     * Indicates that the views created by the ListAdapter can contain focusable
+     * items.
+     *
+     * @param itemsCanFocus true if items can get focus, false otherwise
+     */
+    public void setItemsCanFocus(boolean itemsCanFocus) {
+        mItemsCanFocus = itemsCanFocus;
+        if (!itemsCanFocus) {
+            setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS);
+        }
+    }
+
+    /**
+     * @return Whether the views created by the ListAdapter can contain focusable
+     * items.
+     */
+    public boolean getItemsCanFocus() {
+        return mItemsCanFocus;
+    }
+
+    @Override
+    protected void dispatchDraw(Canvas canvas) {
+        // Draw the dividers
+        final int dividerHeight = mDividerHeight;
+
+        if (dividerHeight > 0 && mDivider != null) {
+            // Only modify the top and bottom in the loop, we set the left and right here
+            final Rect bounds = mTempRect;
+            bounds.left = mPaddingLeft;
+            bounds.right = mRight - mLeft - mPaddingRight;
+
+            final int count = getChildCount();
+            final int headerCount = mHeaderViewInfos.size();
+            final int footerLimit = mItemCount - mFooterViewInfos.size() - 1;
+            final boolean headerDividers = mHeaderDividersEnabled;
+            final boolean footerDividers = mFooterDividersEnabled;
+            final int first = mFirstPosition;
+
+            if (!mStackFromBottom) {
+                int bottom;
+                int listBottom = mBottom - mTop - mListPadding.bottom;
+
+                for (int i = 0; i < count; i++) {
+                    if ((headerDividers || first + i >= headerCount) &&
+                            (footerDividers || first + i < footerLimit)) {
+                        View child = getChildAt(i);
+                        bottom = child.getBottom();
+                        if (bottom < listBottom) {
+                            bounds.top = bottom;
+                            bounds.bottom = bottom + dividerHeight;
+                            drawDivider(canvas, bounds, i);
+                        }
+                    }
+                }
+            } else {
+                int top;
+                int listTop = mListPadding.top;
+
+                for (int i = 0; i < count; i++) {
+                    if ((headerDividers || first + i >= headerCount) &&
+                            (footerDividers || first + i < footerLimit)) {
+                        View child = getChildAt(i);
+                        top = child.getTop();
+                        if (top > listTop) {
+                            bounds.top = top - dividerHeight;
+                            bounds.bottom = top;
+                            // Give the method the child ABOVE the divider, so we
+                            // subtract one from our child
+                            // position. Give -1 when there is no child above the
+                            // divider.
+                            drawDivider(canvas, bounds, i - 1);
+                        }
+                    }
+                }
+            }
+        }
+
+        // Draw the indicators (these should be drawn above the dividers) and children
+        super.dispatchDraw(canvas);
+    }
+
+    /**
+     * Draws a divider for the given child in the given bounds.
+     *
+     * @param canvas The canvas to draw to.
+     * @param bounds The bounds of the divider.
+     * @param childIndex The index of child (of the View) above the divider.
+     *            This will be -1 if there is no child above the divider to be
+     *            drawn.
+     */
+    void drawDivider(Canvas canvas, Rect bounds, int childIndex) {
+        // This widget draws the same divider for all children
+        final Drawable divider = mDivider;
+        final boolean clipDivider = mClipDivider;
+
+        if (!clipDivider) {
+            divider.setBounds(bounds);
+        } else {
+            canvas.save();
+            canvas.clipRect(bounds);
+        }
+
+        divider.draw(canvas);
+
+        if (clipDivider) {
+            canvas.restore();
+        }
+    }
+
+    /**
+     * Returns the drawable that will be drawn between each item in the list.
+     *
+     * @return the current drawable drawn between list elements
+     */
+    public Drawable getDivider() {
+        return mDivider;
+    }
+
+    /**
+     * Sets the drawable that will be drawn between each item in the list. If the drawable does
+     * not have an intrinsic height, you should also call {@link #setDividerHeight(int)}
+     *
+     * @param divider The drawable to use.
+     */
+    public void setDivider(Drawable divider) {
+        if (divider != null) {
+            mDividerHeight = divider.getIntrinsicHeight();
+            mClipDivider = divider instanceof ColorDrawable;
+        } else {
+            mDividerHeight = 0;
+            mClipDivider = false;
+        }
+        mDivider = divider;
+        requestLayoutIfNecessary();
+    }
+
+    /**
+     * @return Returns the height of the divider that will be drawn between each item in the list.
+     */
+    public int getDividerHeight() {
+        return mDividerHeight;
+    }
+    
+    /**
+     * Sets the height of the divider that will be drawn between each item in the list. Calling
+     * this will override the intrinsic height as set by {@link #setDivider(Drawable)}
+     *
+     * @param height The new height of the divider in pixels.
+     */
+    public void setDividerHeight(int height) {
+        mDividerHeight = height;
+        requestLayoutIfNecessary();
+    }
+
+    /**
+     * Enables or disables the drawing of the divider for header views.
+     *
+     * @param headerDividersEnabled True to draw the headers, false otherwise.
+     *
+     * @see #setFooterDividersEnabled(boolean)
+     * @see #addHeaderView(android.view.View)
+     */
+    public void setHeaderDividersEnabled(boolean headerDividersEnabled) {
+        mHeaderDividersEnabled = headerDividersEnabled;
+        invalidate();
+    }
+
+    /**
+     * Enables or disables the drawing of the divider for footer views.
+     *
+     * @param footerDividersEnabled True to draw the footers, false otherwise.
+     *
+     * @see #setHeaderDividersEnabled(boolean)
+     * @see #addFooterView(android.view.View)
+     */
+    public void setFooterDividersEnabled(boolean footerDividersEnabled) {
+        mFooterDividersEnabled = footerDividersEnabled;
+        invalidate();
+    }
+
+    @Override
+    protected void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) {
+        super.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
+
+        int closetChildIndex = -1;
+        if (gainFocus && previouslyFocusedRect != null) {
+            previouslyFocusedRect.offset(mScrollX, mScrollY);
+
+            // figure out which item should be selected based on previously
+            // focused rect
+            Rect otherRect = mTempRect;
+            int minDistance = Integer.MAX_VALUE;
+            final int childCount = getChildCount();
+            final int firstPosition = mFirstPosition;
+            final ListAdapter adapter = mAdapter;
+
+            for (int i = 0; i < childCount; i++) {
+                // only consider selectable views
+                if (!adapter.isEnabled(firstPosition + i)) {
+                    continue;
+                }
+
+                View other = getChildAt(i);
+                other.getDrawingRect(otherRect);
+                offsetDescendantRectToMyCoords(other, otherRect);
+                int distance = getDistance(previouslyFocusedRect, otherRect, direction);
+
+                if (distance < minDistance) {
+                    minDistance = distance;
+                    closetChildIndex = i;
+                }
+            }
+        }
+
+        if (closetChildIndex >= 0) {
+            setSelection(closetChildIndex + mFirstPosition);
+        } else {
+            requestLayout();
+        }
+    }
+
+
+    /*
+     * (non-Javadoc)
+     *
+     * Children specified in XML are assumed to be header views. After we have
+     * parsed them move them out of the children list and into mHeaderViews.
+     */
+    @Override
+    protected void onFinishInflate() {
+        super.onFinishInflate();
+
+        int count = getChildCount();
+        if (count > 0) {
+            for (int i = 0; i < count; ++i) {
+                addHeaderView(getChildAt(i));
+            }
+            removeAllViews();
+        }
+    }
+
+    /* (non-Javadoc)
+     * @see android.view.View#findViewById(int)
+     * First look in our children, then in any header and footer views that may be scrolled off.
+     */
+    @Override
+    protected View findViewTraversal(int id) {
+        View v;
+        v = super.findViewTraversal(id);
+        if (v == null) {
+            v = findViewInHeadersOrFooters(mHeaderViewInfos, id);
+            if (v != null) {
+                return v;
+            }
+            v = findViewInHeadersOrFooters(mFooterViewInfos, id);
+            if (v != null) {
+                return v;
+            }
+        }
+        return v;
+    }
+
+    /* (non-Javadoc)
+     *
+     * Look in the passed in list of headers or footers for the view.
+     */
+    View findViewInHeadersOrFooters(ArrayList<FixedViewInfo> where, int id) {
+        if (where != null) {
+            int len = where.size();
+            View v;
+
+            for (int i = 0; i < len; i++) {
+                v = where.get(i).view;
+
+                if (!v.isRootNamespace()) {
+                    v = v.findViewById(id);
+
+                    if (v != null) {
+                        return v;
+                    }
+                }
+            }
+        }
+        return null;
+    }
+
+    /* (non-Javadoc)
+     * @see android.view.View#findViewWithTag(String)
+     * First look in our children, then in any header and footer views that may be scrolled off.
+     */
+    @Override
+    protected View findViewWithTagTraversal(Object tag) {
+        View v;
+        v = super.findViewWithTagTraversal(tag);
+        if (v == null) {
+            v = findViewTagInHeadersOrFooters(mHeaderViewInfos, tag);
+            if (v != null) {
+                return v;
+            }
+
+            v = findViewTagInHeadersOrFooters(mFooterViewInfos, tag);
+            if (v != null) {
+                return v;
+            }
+        }
+        return v;
+    }
+
+    /* (non-Javadoc)
+     *
+     * Look in the passed in list of headers or footers for the view with the tag.
+     */
+    View findViewTagInHeadersOrFooters(ArrayList<FixedViewInfo> where, Object tag) {
+        if (where != null) {
+            int len = where.size();
+            View v;
+
+            for (int i = 0; i < len; i++) {
+                v = where.get(i).view;
+
+                if (!v.isRootNamespace()) {
+                    v = v.findViewWithTag(tag);
+
+                    if (v != null) {
+                        return v;
+                    }
+                }
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public boolean onTouchEvent(MotionEvent ev) {
+        if (mItemsCanFocus && ev.getAction() == MotionEvent.ACTION_DOWN && ev.getEdgeFlags() != 0) {
+            // Don't handle edge touches immediately -- they may actually belong to one of our
+            // descendants.
+            return false;
+        }
+        return super.onTouchEvent(ev);
+    }
+
+    /**
+     * @see #setChoiceMode(int)
+     *
+     * @return The current choice mode
+     */
+    public int getChoiceMode() {
+        return mChoiceMode;
+    }
+
+    /**
+     * Defines the choice behavior for the List. By default, Lists do not have any choice behavior
+     * ({@link #CHOICE_MODE_NONE}). By setting the choiceMode to {@link #CHOICE_MODE_SINGLE}, the
+     * List allows up to one item to  be in a chosen state. By setting the choiceMode to
+     * {@link #CHOICE_MODE_MULTIPLE}, the list allows any number of items to be chosen.
+     *
+     * @param choiceMode One of {@link #CHOICE_MODE_NONE}, {@link #CHOICE_MODE_SINGLE}, or
+     * {@link #CHOICE_MODE_MULTIPLE}
+     */
+    public void setChoiceMode(int choiceMode) {
+        mChoiceMode = choiceMode;
+        if (mChoiceMode != CHOICE_MODE_NONE && mCheckStates == null) {
+            mCheckStates = new SparseBooleanArray();
+        }
+    }
+
+    @Override
+    public boolean performItemClick(View view, int position, long id) {
+        boolean handled = false;
+
+        if (mChoiceMode != CHOICE_MODE_NONE) {
+            handled = true;
+
+            if (mChoiceMode == CHOICE_MODE_MULTIPLE) {
+                boolean oldValue = mCheckStates.get(position, false);
+                mCheckStates.put(position, !oldValue);
+            } else {
+                boolean oldValue = mCheckStates.get(position, false);
+                if (!oldValue) {
+                    mCheckStates.clear();
+                    mCheckStates.put(position, true);
+                }
+            }
+
+            mDataChanged = true;
+            rememberSyncState();
+            requestLayout();
+        }
+
+        handled |= super.performItemClick(view, position, id);
+
+        return handled;
+    }
+
+    /**
+     * Sets the checked state of the specified position. The is only valid if
+     * the choice mode has been set to {@link #CHOICE_MODE_SINGLE} or
+     * {@link #CHOICE_MODE_MULTIPLE}.
+     *
+     * @param position The item whose checked state is to be checked
+     * @param value The new checked sate for the item
+     */
+    public void setItemChecked(int position, boolean value) {
+        if (mChoiceMode == CHOICE_MODE_NONE) {
+            return;
+        }
+
+        if (mChoiceMode == CHOICE_MODE_MULTIPLE) {
+            mCheckStates.put(position, value);
+        } else {
+            boolean oldValue = mCheckStates.get(position, false);
+            mCheckStates.clear();
+            if (!oldValue) {
+                mCheckStates.put(position, true);
+            }
+        }
+
+        // Do not generate a data change while we are in the layout phase
+        if (!mInLayout && !mBlockLayoutRequests) {
+            mDataChanged = true;
+            rememberSyncState();
+            requestLayout();
+        }
+    }
+
+    /**
+     * Returns the checked state of the specified position. The result is only
+     * valid if the choice mode has not been set to {@link #CHOICE_MODE_SINGLE}
+     * or {@link #CHOICE_MODE_MULTIPLE}.
+     *
+     * @param position The item whose checked state to return
+     * @return The item's checked state
+     *
+     * @see #setChoiceMode(int)
+     */
+    public boolean isItemChecked(int position) {
+        if (mChoiceMode != CHOICE_MODE_NONE && mCheckStates != null) {
+            return mCheckStates.get(position);
+        }
+
+        return false;
+    }
+
+    /**
+     * Returns the currently checked item. The result is only valid if the choice
+     * mode has not been set to {@link #CHOICE_MODE_SINGLE}.
+     *
+     * @return The position of the currently checked item or
+     *         {@link #INVALID_POSITION} if nothing is selected
+     *
+     * @see #setChoiceMode(int)
+     */
+    public int getCheckedItemPosition() {
+        if (mChoiceMode == CHOICE_MODE_SINGLE && mCheckStates != null && mCheckStates.size() == 1) {
+            return mCheckStates.keyAt(0);
+        }
+
+        return INVALID_POSITION;
+    }
+
+    /**
+     * Returns the set of checked items in the list. The result is only valid if
+     * the choice mode has not been set to {@link #CHOICE_MODE_SINGLE}.
+     *
+     * @return  A SparseBooleanArray which will return true for each call to
+     *          get(int position) where position is a position in the list.
+     */
+    public SparseBooleanArray getCheckedItemPositions() {
+        if (mChoiceMode != CHOICE_MODE_NONE) {
+            return mCheckStates;
+        }
+        return null;
+    }
+
+    /**
+     * Clear any choices previously set
+     */
+    public void clearChoices() {
+        if (mCheckStates != null) {
+            mCheckStates.clear();
+        }
+    }
+
+    static class SavedState extends BaseSavedState {
+        SparseBooleanArray checkState;
+
+        /**
+         * Constructor called from {@link ListView#onSaveInstanceState()}
+         */
+        SavedState(Parcelable superState, SparseBooleanArray checkState) {
+            super(superState);
+            this.checkState = checkState;
+        }
+
+        /**
+         * Constructor called from {@link #CREATOR}
+         */
+        private SavedState(Parcel in) {
+            super(in);
+            checkState = in.readSparseBooleanArray();
+        }
+
+        @Override
+        public void writeToParcel(Parcel out, int flags) {
+            super.writeToParcel(out, flags);
+            out.writeSparseBooleanArray(checkState);
+        }
+
+        @Override
+        public String toString() {
+            return "ListView.SavedState{"
+                    + Integer.toHexString(System.identityHashCode(this))
+                    + " checkState=" + checkState + "}";
+        }
+
+        public static final Parcelable.Creator<SavedState> CREATOR
+                = new Parcelable.Creator<SavedState>() {
+            public SavedState createFromParcel(Parcel in) {
+                return new SavedState(in);
+            }
+
+            public SavedState[] newArray(int size) {
+                return new SavedState[size];
+            }
+        };
+    }
+
+    @Override
+    public Parcelable onSaveInstanceState() {
+        Parcelable superState = super.onSaveInstanceState();
+        return new SavedState(superState, mCheckStates);
+    }
+
+    @Override
+    public void onRestoreInstanceState(Parcelable state) {
+        SavedState ss = (SavedState) state;
+
+        super.onRestoreInstanceState(ss.getSuperState());
+
+        if (ss.checkState != null) {
+           mCheckStates = ss.checkState;
+        }
+
+    }
+}
diff --git a/core/java/android/widget/MediaController.java b/core/java/android/widget/MediaController.java
new file mode 100644
index 0000000..f2cec92
--- /dev/null
+++ b/core/java/android/widget/MediaController.java
@@ -0,0 +1,552 @@
+/*
+ * 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 android.widget;
+
+import android.content.Context;
+import android.graphics.PixelFormat;
+import android.media.AudioManager;
+import android.os.Handler;
+import android.os.Message;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.Gravity;
+import android.view.KeyEvent;
+import android.view.LayoutInflater;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.Window;
+import android.view.WindowManager;
+import android.widget.SeekBar.OnSeekBarChangeListener;
+
+import com.android.internal.policy.PolicyManager;
+
+import java.util.Formatter;
+import java.util.Locale;
+
+/**
+ * A view containing controls for a MediaPlayer. Typically contains the
+ * buttons like "Play/Pause", "Rewind", "Fast Forward" and a progress
+ * slider. It takes care of synchronizing the controls with the state
+ * of the MediaPlayer.
+ * <p>
+ * The way to use this class is to instantiate it programatically.
+ * The MediaController will create a default set of controls
+ * and put them in a window floating above your application. Specifically,
+ * the controls will float above the view specified with setAnchorView().
+ * The window will disappear if left idle for three seconds and reappear
+ * when the user touches the anchor view.
+ * <p>
+ * Functions like show() and hide() have no effect when MediaController
+ * is created in an xml layout.
+ * 
+ * MediaController will hide and
+ * show the buttons according to these rules:
+ * <ul>
+ * <li> The "previous" and "next" buttons are hidden until setPrevNextListeners()
+ *   has been called
+ * <li> The "previous" and "next" buttons are visible but disabled if
+ *   setPrevNextListeners() was called with null listeners
+ * <li> The "rewind" and "fastforward" buttons are shown unless requested
+ *   otherwise by using the MediaController(Context, boolean) constructor
+ *   with the boolean set to false
+ * </ul>
+ */
+public class MediaController extends FrameLayout {
+
+    private MediaPlayerControl  mPlayer;
+    private Context             mContext;
+    private View                mAnchor;
+    private View                mRoot;
+    private WindowManager       mWindowManager;
+    private Window              mWindow;
+    private View                mDecor;
+    private ProgressBar         mProgress;
+    private TextView            mEndTime, mCurrentTime;
+    private boolean             mShowing;
+    private boolean             mDragging;
+    private static final int    sDefaultTimeout = 3000;
+    private static final int    FADE_OUT = 1;
+    private static final int    SHOW_PROGRESS = 2;
+    private boolean             mUseFastForward;
+    private boolean             mFromXml;
+    private boolean             mListenersSet;
+    private View.OnClickListener mNextListener, mPrevListener;
+    StringBuilder               mFormatBuilder;
+    Formatter                   mFormatter;
+    private ImageButton         mPauseButton;
+    private ImageButton         mFfwdButton;
+    private ImageButton         mRewButton;
+    private ImageButton         mNextButton;
+    private ImageButton         mPrevButton;
+
+    public MediaController(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        mRoot = this;
+        mContext = context;
+        mUseFastForward = true;
+        mFromXml = true;
+    }
+
+    @Override
+    public void onFinishInflate() {
+        if (mRoot != null)
+            initControllerView(mRoot);
+    }
+
+    public MediaController(Context context, boolean useFastForward) {
+        super(context);
+        mContext = context;
+        mUseFastForward = useFastForward;
+        initFloatingWindow();
+    }
+
+    public MediaController(Context context) {
+        super(context);
+        mContext = context;
+        mUseFastForward = true;
+        initFloatingWindow();
+    }
+
+    private void initFloatingWindow() {
+        mWindowManager = (WindowManager)mContext.getSystemService("window");
+        mWindow = PolicyManager.makeNewWindow(mContext);
+        mWindow.setWindowManager(mWindowManager, null, null);
+        mWindow.requestFeature(Window.FEATURE_NO_TITLE);
+        mDecor = mWindow.getDecorView();
+        mDecor.setOnTouchListener(mTouchListener);
+        mWindow.setContentView(this);
+        mWindow.setBackgroundDrawableResource(android.R.color.transparent);
+        
+        // While the media controller is up, the volume control keys should
+        // affect the media stream type
+        mWindow.setVolumeControlStream(AudioManager.STREAM_MUSIC);
+
+        setFocusable(true);
+        setFocusableInTouchMode(true);
+        setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
+        requestFocus();
+    }
+
+    private OnTouchListener mTouchListener = new OnTouchListener() {
+        public boolean onTouch(View v, MotionEvent event) {
+            if (event.getAction() == MotionEvent.ACTION_DOWN) {
+                if (mShowing) {
+                    hide();
+                }
+            }
+            return false;
+        }
+    };
+    
+    public void setMediaPlayer(MediaPlayerControl player) {
+        mPlayer = player;
+        updatePausePlay();
+    }
+
+    /**
+     * Set the view that acts as the anchor for the control view.
+     * This can for example be a VideoView, or your Activity's main view.
+     * @param view The view to which to anchor the controller when it is visible.
+     */
+    public void setAnchorView(View view) {
+        mAnchor = view;
+
+        FrameLayout.LayoutParams frameParams = new FrameLayout.LayoutParams(
+                ViewGroup.LayoutParams.FILL_PARENT,
+                ViewGroup.LayoutParams.FILL_PARENT
+        );
+
+        removeAllViews();
+        View v = makeControllerView();
+        addView(v, frameParams);
+    }
+
+    /**
+     * Create the view that holds the widgets that control playback.
+     * Derived classes can override this to create their own.
+     * @return The controller view.
+     * @hide This doesn't work as advertised
+     */
+    protected View makeControllerView() {
+        LayoutInflater inflate = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+        mRoot = inflate.inflate(com.android.internal.R.layout.media_controller, null);
+
+        initControllerView(mRoot);
+
+        return mRoot;
+    }
+
+    private void initControllerView(View v) {
+        mPauseButton = (ImageButton) v.findViewById(com.android.internal.R.id.pause);
+        if (mPauseButton != null) {
+            mPauseButton.requestFocus();
+            mPauseButton.setOnClickListener(mPauseListener);
+        }
+
+        mFfwdButton = (ImageButton) v.findViewById(com.android.internal.R.id.ffwd);
+        if (mFfwdButton != null) {
+            mFfwdButton.setOnClickListener(mFfwdListener);
+            if (!mFromXml) {
+                mFfwdButton.setVisibility(mUseFastForward ? View.VISIBLE : View.GONE);
+            }
+        }
+
+        mRewButton = (ImageButton) v.findViewById(com.android.internal.R.id.rew);
+        if (mRewButton != null) {
+            mRewButton.setOnClickListener(mRewListener);
+            if (!mFromXml) {
+                mRewButton.setVisibility(mUseFastForward ? View.VISIBLE : View.GONE);
+            }
+        }
+
+        // By default these are hidden. They will be enabled when setPrevNextListeners() is called 
+        mNextButton = (ImageButton) v.findViewById(com.android.internal.R.id.next);
+        if (mNextButton != null && !mFromXml && !mListenersSet) {
+            mNextButton.setVisibility(View.GONE);
+        }
+        mPrevButton = (ImageButton) v.findViewById(com.android.internal.R.id.prev);
+        if (mPrevButton != null && !mFromXml && !mListenersSet) {
+            mPrevButton.setVisibility(View.GONE);
+        }
+
+        mProgress = (ProgressBar) v.findViewById(com.android.internal.R.id.mediacontroller_progress);
+        if (mProgress != null) {
+            if (mProgress instanceof SeekBar) {
+                SeekBar seeker = (SeekBar) mProgress;
+                seeker.setOnSeekBarChangeListener(mSeekListener);
+            }
+            mProgress.setMax(1000);
+        }
+
+        mEndTime = (TextView) v.findViewById(com.android.internal.R.id.time);
+        mCurrentTime = (TextView) v.findViewById(com.android.internal.R.id.time_current);
+        mFormatBuilder = new StringBuilder();
+        mFormatter = new Formatter(mFormatBuilder, Locale.getDefault());
+
+        installPrevNextListeners();
+    }
+
+    /**
+     * Show the controller on screen. It will go away
+     * automatically after 3 seconds of inactivity.
+     */
+    public void show() {
+        show(sDefaultTimeout);
+    }
+
+    /**
+     * Show the controller on screen. It will go away
+     * automatically after 'timeout' milliseconds of inactivity.
+     * @param timeout The timeout in milliseconds. Use 0 to show
+     * the controller until hide() is called.
+     */
+    public void show(int timeout) {
+
+        if (!mShowing && mAnchor != null) {
+            setProgress();
+
+            int [] anchorpos = new int[2];
+            mAnchor.getLocationOnScreen(anchorpos);
+
+            WindowManager.LayoutParams p = new WindowManager.LayoutParams();
+            p.gravity = Gravity.TOP;
+            p.width = mAnchor.getWidth();
+            p.height = LayoutParams.WRAP_CONTENT;
+            p.x = 0;
+            p.y = anchorpos[1] + mAnchor.getHeight() - p.height;
+            p.format = PixelFormat.TRANSLUCENT;
+            p.type = WindowManager.LayoutParams.TYPE_APPLICATION_PANEL;
+            p.flags |= WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
+            p.token = null;
+            p.windowAnimations = 0; // android.R.style.DropDownAnimationDown;
+            mWindowManager.addView(mDecor, p);
+            mShowing = true;
+        }
+        updatePausePlay();
+        
+        // cause the progress bar to be updated even if mShowing
+        // was already true.  This happens, for example, if we're
+        // paused with the progress bar showing the user hits play.
+        mHandler.sendEmptyMessage(SHOW_PROGRESS);
+
+        Message msg = mHandler.obtainMessage(FADE_OUT);
+        if (timeout != 0) {
+            mHandler.removeMessages(FADE_OUT);
+            mHandler.sendMessageDelayed(msg, timeout);
+        }
+    }
+    
+    public boolean isShowing() {
+        return mShowing;
+    }
+
+    /**
+     * Remove the controller from the screen.
+     */
+    public void hide() {
+        if (mAnchor == null)
+            return;
+
+        if (mShowing) {
+            try {
+                mHandler.removeMessages(SHOW_PROGRESS);
+                mWindowManager.removeView(mDecor);
+            } catch (IllegalArgumentException ex) {
+                Log.w("MediaController", "already removed");
+            }
+            mShowing = false;
+        }
+    }
+
+    private Handler mHandler = new Handler() {
+        @Override
+        public void handleMessage(Message msg) {
+            int pos;
+            switch (msg.what) {
+                case FADE_OUT:
+                    hide();
+                    break;
+                case SHOW_PROGRESS:
+                    pos = setProgress();
+                    if (!mDragging && mShowing && mPlayer.isPlaying()) {
+                        msg = obtainMessage(SHOW_PROGRESS);
+                        sendMessageDelayed(msg, 1000 - (pos % 1000));
+                    }
+                    break;
+            }
+        }
+    };
+
+    private String stringForTime(int timeMs) {
+        int totalSeconds = timeMs / 1000;
+
+        int seconds = totalSeconds % 60;
+        int minutes = (totalSeconds / 60) % 60;
+        int hours   = totalSeconds / 3600;
+
+        mFormatBuilder.setLength(0);
+        if (hours > 0) {
+            return mFormatter.format("%d:%02d:%02d", hours, minutes, seconds).toString();
+        } else {
+            return mFormatter.format("%02d:%02d", minutes, seconds).toString();
+        }
+    }
+
+    private int setProgress() {
+        if (mPlayer == null || mDragging) {
+            return 0;
+        }
+        int position = mPlayer.getCurrentPosition();
+        int duration = mPlayer.getDuration();
+        if (mProgress != null) {
+            if (duration > 0) {
+                // use long to avoid overflow
+                long pos = 1000L * position / duration;
+                mProgress.setProgress( (int) pos);
+            }
+            int percent = mPlayer.getBufferPercentage();
+            mProgress.setSecondaryProgress(percent * 10);
+        }
+
+        if (mEndTime != null)
+            mEndTime.setText(stringForTime(duration));
+        if (mCurrentTime != null)
+            mCurrentTime.setText(stringForTime(position));
+
+        return position;
+    }
+
+    @Override
+    public boolean onTouchEvent(MotionEvent event) {
+        show(sDefaultTimeout);
+        return true;
+    }
+
+    @Override
+    public boolean onTrackballEvent(MotionEvent ev) {
+        show(sDefaultTimeout);
+        return false;
+    }
+
+    @Override
+    public boolean dispatchKeyEvent(KeyEvent event) {
+        int keyCode = event.getKeyCode();
+        if (event.getRepeatCount() == 0 && event.isDown() && (
+                keyCode ==  KeyEvent.KEYCODE_HEADSETHOOK ||
+                keyCode ==  KeyEvent.KEYCODE_PLAYPAUSE ||
+                keyCode ==  KeyEvent.KEYCODE_SPACE)) {
+            doPauseResume();
+            show(sDefaultTimeout);
+            return true;
+        } else if (keyCode ==  KeyEvent.KEYCODE_STOP) {
+            if (mPlayer.isPlaying()) {
+                mPlayer.pause();
+                updatePausePlay();
+            }
+            return true;
+        } else if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN ||
+                keyCode == KeyEvent.KEYCODE_VOLUME_UP) {
+            // don't show the controls for volume adjustment
+            return super.dispatchKeyEvent(event);
+        } else if (keyCode == KeyEvent.KEYCODE_BACK || keyCode == KeyEvent.KEYCODE_MENU) {
+            hide();
+        } else {
+            show(sDefaultTimeout);
+        }
+        return super.dispatchKeyEvent(event);
+    }
+
+    private View.OnClickListener mPauseListener = new View.OnClickListener() {
+        public void onClick(View v) {
+            doPauseResume();
+            show(sDefaultTimeout);
+        }
+    };
+
+    private void updatePausePlay() {
+        if (mRoot == null)
+            return;
+
+        ImageButton button = (ImageButton) mRoot.findViewById(com.android.internal.R.id.pause);
+        if (button == null)
+            return;
+
+        if (mPlayer.isPlaying()) {
+            button.setImageResource(com.android.internal.R.drawable.ic_media_pause);
+        } else {
+            button.setImageResource(com.android.internal.R.drawable.ic_media_play);
+        }
+    }
+
+    private void doPauseResume() {
+        if (mPlayer.isPlaying()) {
+            mPlayer.pause();
+        } else {
+            mPlayer.start();
+        }
+        updatePausePlay();
+    }
+
+    private OnSeekBarChangeListener mSeekListener = new OnSeekBarChangeListener() {
+        long duration;
+        public void onStartTrackingTouch(SeekBar bar) {
+            show(3600000);
+            duration = mPlayer.getDuration();
+        }
+        public void onProgressChanged(SeekBar bar, int progress, boolean fromtouch) {
+            if (fromtouch) {
+                mDragging = true;
+                long newposition = (duration * progress) / 1000L;
+                mPlayer.seekTo( (int) newposition);
+                if (mCurrentTime != null)
+                    mCurrentTime.setText(stringForTime( (int) newposition));
+            }
+        }
+        public void onStopTrackingTouch(SeekBar bar) {
+            mDragging = false;
+            setProgress();
+            updatePausePlay();
+            show(sDefaultTimeout);
+        }
+    };
+
+    @Override
+    public void setEnabled(boolean enabled) {
+        if (mPauseButton != null) {
+            mPauseButton.setEnabled(enabled);
+        }
+        if (mFfwdButton != null) {
+            mFfwdButton.setEnabled(enabled);
+        }
+        if (mRewButton != null) {
+            mRewButton.setEnabled(enabled);
+        }
+        if (mNextButton != null) {
+            mNextButton.setEnabled(enabled && mNextListener != null);
+        }
+        if (mPrevButton != null) {
+            mPrevButton.setEnabled(enabled && mPrevListener != null);
+        }
+        if (mProgress != null) {
+            mProgress.setEnabled(enabled);
+        }
+
+        super.setEnabled(enabled);
+    }
+
+    private View.OnClickListener mRewListener = new View.OnClickListener() {
+        public void onClick(View v) {
+            int pos = mPlayer.getCurrentPosition();
+            pos -= 5000; // milliseconds
+            mPlayer.seekTo(pos);
+            setProgress();
+
+            show(sDefaultTimeout);
+        }
+    };
+
+    private View.OnClickListener mFfwdListener = new View.OnClickListener() {
+        public void onClick(View v) {
+            int pos = mPlayer.getCurrentPosition();
+            pos += 15000; // milliseconds
+            mPlayer.seekTo(pos);
+            setProgress();
+
+            show(sDefaultTimeout);
+        }
+    };
+
+    private void installPrevNextListeners() {
+        if (mNextButton != null) {
+            mNextButton.setOnClickListener(mNextListener);
+            mNextButton.setEnabled(mNextListener != null);
+        }
+
+        if (mPrevButton != null) {
+            mPrevButton.setOnClickListener(mPrevListener);
+            mPrevButton.setEnabled(mPrevListener != null);
+        }
+    }
+
+    public void setPrevNextListeners(View.OnClickListener next, View.OnClickListener prev) {
+        mNextListener = next;
+        mPrevListener = prev;
+        mListenersSet = true;
+
+        if (mRoot != null) {
+            installPrevNextListeners();
+            
+            if (mNextButton != null && !mFromXml) {
+                mNextButton.setVisibility(View.VISIBLE);
+            }
+            if (mPrevButton != null && !mFromXml) {
+                mPrevButton.setVisibility(View.VISIBLE);
+            }
+        }
+    }
+
+    public interface MediaPlayerControl {
+        void    start();
+        void    pause();
+        int     getDuration();
+        int     getCurrentPosition();
+        void    seekTo(int pos);
+        boolean isPlaying();
+        int     getBufferPercentage();
+    };
+}
diff --git a/core/java/android/widget/MultiAutoCompleteTextView.java b/core/java/android/widget/MultiAutoCompleteTextView.java
new file mode 100644
index 0000000..05abc26
--- /dev/null
+++ b/core/java/android/widget/MultiAutoCompleteTextView.java
@@ -0,0 +1,282 @@
+/*
+ * Copyright (C) 2007 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 android.widget;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.text.Editable;
+import android.text.Selection;
+import android.text.Spanned;
+import android.text.Spannable;
+import android.text.SpannableString;
+import android.text.TextUtils;
+import android.text.method.QwertyKeyListener;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import com.android.internal.R;
+
+/**
+ * An editable text view, extending {@link AutoCompleteTextView}, that
+ * can show completion suggestions for the substring of the text where
+ * the user is typing instead of necessarily for the entire thing.
+ * <p>
+ * You must must provide a {@link Tokenizer} to distinguish the
+ * various substrings.
+ *
+ * <p>The following code snippet shows how to create a text view which suggests
+ * various countries names while the user is typing:</p>
+ *
+ * <pre class="prettyprint">
+ * public class CountriesActivity extends Activity {
+ *     protected void onCreate(Bundle savedInstanceState) {
+ *         super.onCreate(savedInstanceState);
+ *         setContentView(R.layout.autocomplete_7);
+ * 
+ *         ArrayAdapter&lt;String&gt; adapter = new ArrayAdapter&lt;String&gt;(this,
+ *                 android.R.layout.simple_dropdown_item_1line, COUNTRIES);
+ *         MultiAutoCompleteTextView textView = (MultiAutoCompleteTextView) findViewById(R.id.edit);
+ *         textView.setAdapter(adapter);
+ *         textView.setTokenizer(new MultiAutoCompleteTextView.CommaTokenizer());
+ *     }
+ *
+ *     private static final String[] COUNTRIES = new String[] {
+ *         "Belgium", "France", "Italy", "Germany", "Spain"
+ *     };
+ * }</pre>
+ */
+
+public class MultiAutoCompleteTextView extends AutoCompleteTextView {
+    private Tokenizer mTokenizer;
+
+    public MultiAutoCompleteTextView(Context context) {
+        this(context, null);
+    }
+
+    public MultiAutoCompleteTextView(Context context, AttributeSet attrs) {
+        this(context, attrs, com.android.internal.R.attr.autoCompleteTextViewStyle);
+    }
+
+    public MultiAutoCompleteTextView(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+    }
+
+    /* package */ void finishInit() { }
+
+    /**
+     * Sets the Tokenizer that will be used to determine the relevant
+     * range of the text where the user is typing.
+     */
+    public void setTokenizer(Tokenizer t) {
+        mTokenizer = t;
+    }
+
+    /**
+     * Instead of filtering on the entire contents of the edit box,
+     * this subclass method filters on the range from
+     * {@link Tokenizer#findTokenStart} to {@link #getSelectionEnd}
+     * if the length of that range meets or exceeds {@link #getThreshold}.
+     */
+    @Override
+    protected void performFiltering(CharSequence text, int keyCode) {
+        if (enoughToFilter()) {
+            int end = getSelectionEnd();
+            int start = mTokenizer.findTokenStart(text, end);
+
+            performFiltering(text, start, end, keyCode);
+        } else {
+            dismissDropDown();
+
+            Filter f = getFilter();
+            if (f != null) {
+                f.filter(null);
+            }
+        }
+    }
+
+    /**
+     * Instead of filtering whenever the total length of the text
+     * exceeds the threshhold, this subclass filters only when the
+     * length of the range from
+     * {@link Tokenizer#findTokenStart} to {@link #getSelectionEnd}
+     * meets or exceeds {@link #getThreshold}.
+     */
+    @Override
+    public boolean enoughToFilter() {
+        Editable text = getText();
+
+        int end = getSelectionEnd();
+        if (end < 0 || mTokenizer == null) {
+            return false;
+        }
+
+        int start = mTokenizer.findTokenStart(text, end);
+
+        if (end - start >= getThreshold()) {
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    /**
+     * Instead of validating the entire text, this subclass method validates
+     * each token of the text individually.  Empty tokens are removed.
+     */
+    @Override 
+    public void performValidation() {
+        Validator v = getValidator();
+
+        if (v == null || mTokenizer == null) {
+            return;
+        }
+
+        Editable e = getText();
+        int i = getText().length();
+        while (i > 0) {
+            int start = mTokenizer.findTokenStart(e, i);
+            int end = mTokenizer.findTokenEnd(e, start);
+
+            CharSequence sub = e.subSequence(start, end);
+            if (TextUtils.isEmpty(sub)) {
+                e.replace(start, i, "");
+            } else if (!v.isValid(sub)) {
+                e.replace(start, i,
+                          mTokenizer.terminateToken(v.fixText(sub)));
+            }
+
+            i = start;
+        }
+    }
+
+    /**
+     * <p>Starts filtering the content of the drop down list. The filtering
+     * pattern is the specified range of text from the edit box. Subclasses may
+     * override this method to filter with a different pattern, for
+     * instance a smaller substring of <code>text</code>.</p>
+     */
+    protected void performFiltering(CharSequence text, int start, int end,
+                                    int keyCode) {
+        getFilter().filter(text.subSequence(start, end), this);
+    }
+
+    /**
+     * <p>Performs the text completion by replacing the range from
+     * {@link Tokenizer#findTokenStart} to {@link #getSelectionEnd} by the
+     * the result of passing <code>text</code> through
+     * {@link Tokenizer#terminateToken}.
+     * In addition, the replaced region will be marked as an AutoText
+     * substition so that if the user immediately presses DEL, the
+     * completion will be undone.
+     * Subclasses may override this method to do some different
+     * insertion of the content into the edit box.</p>
+     *
+     * @param text the selected suggestion in the drop down list
+     */
+    @Override
+    protected void replaceText(CharSequence text) {
+        int end = getSelectionEnd();
+        int start = mTokenizer.findTokenStart(getText(), end);
+
+        Editable editable = getText();
+        String original = TextUtils.substring(editable, start, end);
+
+        QwertyKeyListener.markAsReplaced(editable, start, end, original);
+        editable.replace(start, end, mTokenizer.terminateToken(text));
+    }
+
+    public static interface Tokenizer {
+        /**
+         * Returns the start of the token that ends at offset
+         * <code>cursor</code> within <code>text</code>.
+         */
+        public int findTokenStart(CharSequence text, int cursor);
+
+        /**
+         * Returns the end of the token (minus trailing punctuation)
+         * that begins at offset <code>cursor</code> within <code>text</code>.
+         */
+        public int findTokenEnd(CharSequence text, int cursor);
+
+        /**
+         * Returns <code>text</code>, modified, if necessary, to ensure that
+         * it ends with a token terminator (for example a space or comma).
+         */
+        public CharSequence terminateToken(CharSequence text);
+    }
+
+    /**
+     * This simple Tokenizer can be used for lists where the items are
+     * separated by a comma and one or more spaces.
+     */
+    public static class CommaTokenizer implements Tokenizer {
+        public int findTokenStart(CharSequence text, int cursor) {
+            int i = cursor;
+
+            while (i > 0 && text.charAt(i - 1) != ',') {
+                i--;
+            }
+            while (i < cursor && text.charAt(i) == ' ') {
+                i++;
+            }
+
+            return i;
+        }
+
+        public int findTokenEnd(CharSequence text, int cursor) {
+            int i = cursor;
+            int len = text.length();
+
+            while (i < len) {
+                if (text.charAt(i) == ',') {
+                    return i;
+                } else {
+                    i++;
+                }
+            }
+
+            return len;
+        }
+
+        public CharSequence terminateToken(CharSequence text) {
+            int i = text.length();
+
+            while (i > 0 && text.charAt(i - 1) == ' ') {
+                i--;
+            }
+
+            if (i > 0 && text.charAt(i - 1) == ',') {
+                return text;
+            } else {
+                if (text instanceof Spanned) {
+                    SpannableString sp = new SpannableString(text + ", ");
+                    TextUtils.copySpansFrom((Spanned) text, 0, text.length(),
+                                            Object.class, sp, 0);
+                    return sp;
+                } else {
+                    return text + ", ";
+                }
+            }
+        }
+    }
+}
diff --git a/core/java/android/widget/PopupWindow.java b/core/java/android/widget/PopupWindow.java
new file mode 100644
index 0000000..53db77e
--- /dev/null
+++ b/core/java/android/widget/PopupWindow.java
@@ -0,0 +1,1275 @@
+/*
+ * Copyright (C) 2007 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 android.widget;
+
+import com.android.internal.R;
+
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.WindowManager;
+import android.view.Gravity;
+import android.view.ViewGroup;
+import android.view.ViewTreeObserver;
+import android.view.ViewTreeObserver.OnScrollChangedListener;
+import android.view.View.OnTouchListener;
+import android.graphics.PixelFormat;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.StateListDrawable;
+import android.os.IBinder;
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.util.AttributeSet;
+
+import java.lang.ref.WeakReference;
+
+/**
+ * <p>A popup window that can be used to display an arbitrary view. The popup
+ * windows is a floating container that appears on top of the current
+ * activity.</p>
+ * 
+ * @see android.widget.AutoCompleteTextView
+ * @see android.widget.Spinner
+ */
+public class PopupWindow {
+    /**
+     * Mode for {@link #setInputMethodMode(int): the requirements for the
+     * input method should be based on the focusability of the popup.  That is
+     * if it is focusable than it needs to work with the input method, else
+     * it doesn't.
+     */
+    public static final int INPUT_METHOD_FROM_FOCUSABLE = 0;
+    
+    /**
+     * Mode for {@link #setInputMethodMode(int): this popup always needs to
+     * work with an input method, regardless of whether it is focusable.  This
+     * means that it will always be displayed so that the user can also operate
+     * the input method while it is shown.
+     */
+    
+    public static final int INPUT_METHOD_NEEDED = 1;
+    
+    /**
+     * Mode for {@link #setInputMethodMode(int): this popup never needs to
+     * work with an input method, regardless of whether it is focusable.  This
+     * means that it will always be displayed to use as much space on the
+     * screen as needed, regardless of whether this covers the input method.
+     */
+    public static final int INPUT_METHOD_NOT_NEEDED = 2;
+    
+    private final Context mContext;
+    private final WindowManager mWindowManager;
+    
+    private boolean mIsShowing;
+    private boolean mIsDropdown;
+
+    private View mContentView;
+    private View mPopupView;
+    private boolean mFocusable;
+    private int mInputMethodMode = INPUT_METHOD_FROM_FOCUSABLE;
+    private boolean mTouchable = true;
+    private boolean mOutsideTouchable = false;
+    private boolean mClippingEnabled = true;
+
+    private OnTouchListener mTouchInterceptor;
+    
+    private int mWidthMode;
+    private int mWidth;
+    private int mLastWidth;
+    private int mHeightMode;
+    private int mHeight;
+    private int mLastHeight;
+
+    private int mPopupWidth;
+    private int mPopupHeight;
+    
+    private int[] mDrawingLocation = new int[2];
+    private int[] mScreenLocation = new int[2];
+    private Rect mTempRect = new Rect();
+    
+    private Drawable mBackground;
+    private Drawable mAboveAnchorBackgroundDrawable;
+    private Drawable mBelowAnchorBackgroundDrawable;
+
+    private boolean mAboveAnchor;
+    
+    private OnDismissListener mOnDismissListener;
+    private boolean mIgnoreCheekPress = false;
+
+    private int mAnimationStyle = -1;
+    
+    private static final int[] ABOVE_ANCHOR_STATE_SET = new int[] {
+        com.android.internal.R.attr.state_above_anchor
+    };
+
+    private WeakReference<View> mAnchor;
+    private OnScrollChangedListener mOnScrollChangedListener =
+        new OnScrollChangedListener() {
+            public void onScrollChanged() {
+                View anchor = mAnchor.get();
+                if (anchor != null && mPopupView != null) {
+                    WindowManager.LayoutParams p = (WindowManager.LayoutParams)
+                            mPopupView.getLayoutParams();
+
+                    mAboveAnchor = findDropDownPosition(anchor, p, mAnchorXoff, mAnchorYoff);
+                    update(p.x, p.y, -1, -1, true);
+                }
+            }
+        };
+    private int mAnchorXoff, mAnchorYoff;
+
+    /**
+     * <p>Create a new empty, non focusable popup window of dimension (0,0).</p>
+     *
+     * <p>The popup does provide a background.</p>
+     */
+    public PopupWindow(Context context) {
+        this(context, null);
+    }
+
+    /**
+     * <p>Create a new empty, non focusable popup window of dimension (0,0).</p>
+     *
+     * <p>The popup does provide a background.</p>
+     */
+    public PopupWindow(Context context, AttributeSet attrs) {
+        this(context, attrs, com.android.internal.R.attr.popupWindowStyle);
+    }
+
+    /**
+     * <p>Create a new empty, non focusable popup window of dimension (0,0).</p>
+     *
+     * <p>The popup does provide a background.</p>
+     */
+    public PopupWindow(Context context, AttributeSet attrs, int defStyle) {
+        mContext = context;
+        mWindowManager = (WindowManager)context.getSystemService(
+                Context.WINDOW_SERVICE);
+
+        TypedArray a =
+            context.obtainStyledAttributes(
+                attrs, com.android.internal.R.styleable.PopupWindow, defStyle, 0);
+
+        mBackground = a.getDrawable(R.styleable.PopupWindow_popupBackground);
+
+        // If this is a StateListDrawable, try to find and store the drawable to be
+        // used when the drop-down is placed above its anchor view, and the one to be
+        // used when the drop-down is placed below its anchor view. We extract
+        // the drawables ourselves to work around a problem with using refreshDrawableState
+        // that it will take into account the padding of all drawables specified in a
+        // StateListDrawable, thus adding superfluous padding to drop-down views.
+        //
+        // We assume a StateListDrawable will have a drawable for ABOVE_ANCHOR_STATE_SET and
+        // at least one other drawable, intended for the 'below-anchor state'.
+        if (mBackground instanceof StateListDrawable) {
+            StateListDrawable background = (StateListDrawable) mBackground;
+
+            // Find the above-anchor view - this one's easy, it should be labeled as such.
+            int aboveAnchorStateIndex = background.getStateDrawableIndex(ABOVE_ANCHOR_STATE_SET);
+            
+            // Now, for the below-anchor view, look for any other drawable specified in the
+            // StateListDrawable which is not for the above-anchor state and use that.
+            int count = background.getStateCount();
+            int belowAnchorStateIndex = -1;
+            for (int i = 0; i < count; i++) {
+                if (i != aboveAnchorStateIndex) {
+                    belowAnchorStateIndex = i;
+                    break;
+                }
+            }
+            
+            // Store the drawables we found, if we found them. Otherwise, set them both
+            // to null so that we'll just use refreshDrawableState.
+            if (aboveAnchorStateIndex != -1 && belowAnchorStateIndex != -1) {
+                mAboveAnchorBackgroundDrawable = background.getStateDrawable(aboveAnchorStateIndex);
+                mBelowAnchorBackgroundDrawable = background.getStateDrawable(belowAnchorStateIndex);
+            } else {
+                mBelowAnchorBackgroundDrawable = null;
+                mAboveAnchorBackgroundDrawable = null;
+            }
+        }
+        
+        a.recycle();
+    }
+
+    /**
+     * <p>Create a new empty, non focusable popup window of dimension (0,0).</p>
+     *
+     * <p>The popup does not provide any background. This should be handled
+     * by the content view.</p>
+     */
+    public PopupWindow() {
+        this(null, 0, 0);
+    }
+
+    /**
+     * <p>Create a new non focusable popup window which can display the
+     * <tt>contentView</tt>. The dimension of the window are (0,0).</p>
+     *
+     * <p>The popup does not provide any background. This should be handled
+     * by the content view.</p>
+     *
+     * @param contentView the popup's content
+     */
+    public PopupWindow(View contentView) {
+        this(contentView, 0, 0);
+    }
+
+    /**
+     * <p>Create a new empty, non focusable popup window. The dimension of the
+     * window must be passed to this constructor.</p>
+     *
+     * <p>The popup does not provide any background. This should be handled
+     * by the content view.</p>
+     *
+     * @param width the popup's width
+     * @param height the popup's height
+     */
+    public PopupWindow(int width, int height) {
+        this(null, width, height);
+    }
+
+    /**
+     * <p>Create a new non focusable popup window which can display the
+     * <tt>contentView</tt>. The dimension of the window must be passed to
+     * this constructor.</p>
+     *
+     * <p>The popup does not provide any background. This should be handled
+     * by the content view.</p>
+     *
+     * @param contentView the popup's content
+     * @param width the popup's width
+     * @param height the popup's height
+     */
+    public PopupWindow(View contentView, int width, int height) {
+        this(contentView, width, height, false);
+    }
+
+    /**
+     * <p>Create a new popup window which can display the <tt>contentView</tt>.
+     * The dimension of the window must be passed to this constructor.</p>
+     *
+     * <p>The popup does not provide any background. This should be handled
+     * by the content view.</p>
+     *
+     * @param contentView the popup's content
+     * @param width the popup's width
+     * @param height the popup's height
+     * @param focusable true if the popup can be focused, false otherwise
+     */
+    public PopupWindow(View contentView, int width, int height,
+            boolean focusable) {
+        mContext = contentView.getContext();
+        mWindowManager = (WindowManager)mContext.getSystemService(
+                Context.WINDOW_SERVICE);
+        setContentView(contentView);
+        setWidth(width);
+        setHeight(height);
+        setFocusable(focusable);
+    }
+
+    /**
+     * <p>Return the drawable used as the popup window's background.</p>
+     *
+     * @return the background drawable or null
+     */
+    public Drawable getBackground() {
+        return mBackground;
+    }
+
+    /**
+     * <p>Change the background drawable for this popup window. The background
+     * can be set to null.</p>
+     *
+     * @param background the popup's background
+     */
+    public void setBackgroundDrawable(Drawable background) {
+        mBackground = background;
+    }
+
+    /**
+     * <p>Return the animation style to use the popup appears and disappears</p>
+     *
+     * @return the animation style to use the popup appears and disappears
+     */
+    public int getAnimationStyle() {
+        return mAnimationStyle;
+    }
+    
+    /**
+     * Set the flag on popup to ignore cheek press eventt; by default this flag
+     * is set to false
+     * which means the pop wont ignore cheek press dispatch events.
+     * 
+     * <p>If the popup is showing, calling this method will take effect only
+     * the next time the popup is shown or through a manual call to one of
+     * the {@link #update()} methods.</p>
+     * 
+     * @see #update()
+     */
+    public void setIgnoreCheekPress() {
+        mIgnoreCheekPress = true;
+    }
+    
+
+    /**
+     * <p>Change the animation style resource for this popup.</p>
+     *
+     * <p>If the popup is showing, calling this method will take effect only
+     * the next time the popup is shown or through a manual call to one of
+     * the {@link #update()} methods.</p>
+     *
+     * @param animationStyle animation style to use when the popup appears
+     *      and disappears.  Set to -1 for the default animation, 0 for no
+     *      animation, or a resource identifier for an explicit animation.
+     *      
+     * @see #update()
+     */
+    public void setAnimationStyle(int animationStyle) {
+        mAnimationStyle = animationStyle;
+    }
+    
+    /**
+     * <p>Return the view used as the content of the popup window.</p>
+     *
+     * @return a {@link android.view.View} representing the popup's content
+     *
+     * @see #setContentView(android.view.View)
+     */
+    public View getContentView() {
+        return mContentView;
+    }
+
+    /**
+     * <p>Change the popup's content. The content is represented by an instance
+     * of {@link android.view.View}.</p>
+     *
+     * <p>This method has no effect if called when the popup is showing.  To
+     * apply it while a popup is showing, call </p>
+     *
+     * @param contentView the new content for the popup
+     *
+     * @see #getContentView()
+     * @see #isShowing()
+     */
+    public void setContentView(View contentView) {
+        if (isShowing()) {
+            return;
+        }
+
+        mContentView = contentView;
+    }
+
+    /**
+     * Set a callback for all touch events being dispatched to the popup
+     * window.
+     */
+    public void setTouchInterceptor(OnTouchListener l) {
+        mTouchInterceptor = l;
+    }
+    
+    /**
+     * <p>Indicate whether the popup window can grab the focus.</p>
+     *
+     * @return true if the popup is focusable, false otherwise
+     *
+     * @see #setFocusable(boolean)
+     */
+    public boolean isFocusable() {
+        return mFocusable;
+    }
+
+    /**
+     * <p>Changes the focusability of the popup window. When focusable, the
+     * window will grab the focus from the current focused widget if the popup
+     * contains a focusable {@link android.view.View}.  By default a popup
+     * window is not focusable.</p>
+     *
+     * <p>If the popup is showing, calling this method will take effect only
+     * the next time the popup is shown or through a manual call to one of
+     * the {@link #update()} methods.</p>
+     *
+     * @param focusable true if the popup should grab focus, false otherwise.
+     *
+     * @see #isFocusable()
+     * @see #isShowing() 
+     * @see #update()
+     */
+    public void setFocusable(boolean focusable) {
+        mFocusable = focusable;
+    }
+
+    /**
+     * Return the current value in {@link #setInputMethodMode(int)}.
+     * 
+     * @see #setInputMethodMode(int)
+     */
+    public int getInputMethodMode() {
+        return mInputMethodMode;
+        
+    }
+    
+    /**
+     * Control how the popup operates with an input method: one of
+     * {@link #INPUT_METHOD_FROM_FOCUSABLE}, {@link #INPUT_METHOD_NEEDED},
+     * or {@link #INPUT_METHOD_NOT_NEEDED}.
+     * 
+     * <p>If the popup is showing, calling this method will take effect only
+     * the next time the popup is shown or through a manual call to one of
+     * the {@link #update()} methods.</p>
+     * 
+     * @see #getInputMethodMode()
+     * @see #update()
+     */
+    public void setInputMethodMode(int mode) {
+        mInputMethodMode = mode;
+    }
+    
+    /**
+     * <p>Indicates whether the popup window receives touch events.</p>
+     * 
+     * @return true if the popup is touchable, false otherwise
+     * 
+     * @see #setTouchable(boolean)
+     */
+    public boolean isTouchable() {
+        return mTouchable;
+    }
+
+    /**
+     * <p>Changes the touchability of the popup window. When touchable, the
+     * window will receive touch events, otherwise touch events will go to the
+     * window below it. By default the window is touchable.</p>
+     *
+     * <p>If the popup is showing, calling this method will take effect only
+     * the next time the popup is shown or through a manual call to one of
+     * the {@link #update()} methods.</p>
+     *
+     * @param touchable true if the popup should receive touch events, false otherwise
+     *
+     * @see #isTouchable()
+     * @see #isShowing() 
+     * @see #update()
+     */
+    public void setTouchable(boolean touchable) {
+        mTouchable = touchable;
+    }
+
+    /**
+     * <p>Indicates whether the popup window will be informed of touch events
+     * outside of its window.</p>
+     * 
+     * @return true if the popup is outside touchable, false otherwise
+     * 
+     * @see #setOutsideTouchable(boolean)
+     */
+    public boolean isOutsideTouchable() {
+        return mOutsideTouchable;
+    }
+
+    /**
+     * <p>Controls whether the pop-up will be informed of touch events outside
+     * of its window.  This only makes sense for pop-ups that are touchable
+     * but not focusable, which means touches outside of the window will
+     * be delivered to the window behind.  The default is false.</p>
+     *
+     * <p>If the popup is showing, calling this method will take effect only
+     * the next time the popup is shown or through a manual call to one of
+     * the {@link #update()} methods.</p>
+     *
+     * @param touchable true if the popup should receive outside
+     * touch events, false otherwise
+     *
+     * @see #isOutsideTouchable()
+     * @see #isShowing() 
+     * @see #update()
+     */
+    public void setOutsideTouchable(boolean touchable) {
+        mOutsideTouchable = touchable;
+    }
+
+    /**
+     * <p>Indicates whether clipping of the popup window is enabled.</p>
+     * 
+     * @return true if the clipping is enabled, false otherwise
+     * 
+     * @see #setClippingEnabled(boolean)
+     */
+    public boolean isClippingEnabled() {
+        return mClippingEnabled;
+    }
+
+    /**
+     * <p>Allows the popup window to extend beyond the bounds of the screen. By default the
+     * window is clipped to the screen boundaries. Setting this to false will allow windows to be
+     * accurately positioned.</p>
+     * 
+     * <p>If the popup is showing, calling this method will take effect only
+     * the next time the popup is shown or through a manual call to one of
+     * the {@link #update()} methods.</p>
+     *
+     * @param enabled false if the window should be allowed to extend outside of the screen
+     * @see #isShowing() 
+     * @see #isClippingEnabled()
+     * @see #update()
+     */
+    public void setClippingEnabled(boolean enabled) {
+        mClippingEnabled = enabled;
+    }
+
+    /**
+     * <p>Change the width and height measure specs that are given to the
+     * window manager by the popup.  By default these are 0, meaning that
+     * the current width or height is requested as an explicit size from
+     * the window manager.  You can supply
+     * {@link ViewGroup.LayoutParams#WRAP_CONTENT} or 
+     * {@link ViewGroup.LayoutParams#FILL_PARENT} to have that measure
+     * spec supplied instead, replacing the absolute width and height that
+     * has been set in the popup.</p>
+     *
+     * <p>If the popup is showing, calling this method will take effect only
+     * the next time the popup is shown.</p>
+     *
+     * @param widthSpec an explicit width measure spec mode, either
+     * {@link ViewGroup.LayoutParams#WRAP_CONTENT},
+     * {@link ViewGroup.LayoutParams#FILL_PARENT}, or 0 to use the absolute
+     * width.
+     * @param heightSpec an explicit height measure spec mode, either
+     * {@link ViewGroup.LayoutParams#WRAP_CONTENT},
+     * {@link ViewGroup.LayoutParams#FILL_PARENT}, or 0 to use the absolute
+     * height.
+     */
+    public void setWindowLayoutMode(int widthSpec, int heightSpec) {
+        mWidthMode = widthSpec;
+        mHeightMode = heightSpec;
+    }
+    
+    /**
+     * <p>Return this popup's height MeasureSpec</p>
+     *
+     * @return the height MeasureSpec of the popup
+     *
+     * @see #setHeight(int)
+     */
+    public int getHeight() {
+        return mHeight;
+    }
+
+    /**
+     * <p>Change the popup's height MeasureSpec</p>
+     *
+     * <p>If the popup is showing, calling this method will take effect only
+     * the next time the popup is shown.</p>
+     *
+     * @param height the height MeasureSpec of the popup
+     *
+     * @see #getHeight()
+     * @see #isShowing() 
+     */
+    public void setHeight(int height) {
+        mHeight = height;
+    }
+
+    /**
+     * <p>Return this popup's width MeasureSpec</p>
+     *
+     * @return the width MeasureSpec of the popup
+     *
+     * @see #setWidth(int) 
+     */
+    public int getWidth() {
+        return mWidth;
+    }
+
+    /**
+     * <p>Change the popup's width MeasureSpec</p>
+     *
+     * <p>If the popup is showing, calling this method will take effect only
+     * the next time the popup is shown.</p>
+     *
+     * @param width the width MeasureSpec of the popup
+     *
+     * @see #getWidth()
+     * @see #isShowing()
+     */
+    public void setWidth(int width) {
+        mWidth = width;
+    }
+
+    /**
+     * <p>Indicate whether this popup window is showing on screen.</p>
+     *
+     * @return true if the popup is showing, false otherwise
+     */
+    public boolean isShowing() {
+        return mIsShowing;
+    }
+
+    /**
+     * <p>
+     * Display the content view in a popup window at the specified location. If the popup window
+     * cannot fit on screen, it will be clipped. See {@link android.view.WindowManager.LayoutParams}
+     * for more information on how gravity and the x and y parameters are related. Specifying
+     * a gravity of {@link android.view.Gravity#NO_GRAVITY} is similar to specifying
+     * <code>Gravity.LEFT | Gravity.TOP</code>.
+     * </p>
+     * 
+     * @param parent a parent view to get the {@link android.view.View#getWindowToken()} token from
+     * @param gravity the gravity which controls the placement of the popup window
+     * @param x the popup's x location offset
+     * @param y the popup's y location offset
+     */
+    public void showAtLocation(View parent, int gravity, int x, int y) {
+        if (isShowing() || mContentView == null) {
+            return;
+        }
+
+        unregisterForScrollChanged();
+
+        mIsShowing = true;
+        mIsDropdown = false;
+
+        WindowManager.LayoutParams p = createPopupLayout(parent.getWindowToken());
+        p.windowAnimations = computeAnimationResource();
+       
+        preparePopup(p);
+        if (gravity == Gravity.NO_GRAVITY) {
+            gravity = Gravity.TOP | Gravity.LEFT;
+        }
+        p.gravity = gravity;
+        p.x = x;
+        p.y = y;
+        invokePopup(p);
+    }
+
+    /**
+     * <p>Display the content view in a popup window anchored to the bottom-left
+     * corner of the anchor view. If there is not enough room on screen to show
+     * the popup in its entirety, this method tries to find a parent scroll
+     * view to scroll. If no parent scroll view can be scrolled, the bottom-left
+     * corner of the popup is pinned at the top left corner of the anchor view.</p>
+     *
+     * @param anchor the view on which to pin the popup window
+     *
+     * @see #dismiss()
+     */
+    public void showAsDropDown(View anchor) {
+        showAsDropDown(anchor, 0, 0);
+    }
+
+    /**
+     * <p>Display the content view in a popup window anchored to the bottom-left
+     * corner of the anchor view offset by the specified x and y coordinates.
+     * If there is not enough room on screen to show
+     * the popup in its entirety, this method tries to find a parent scroll
+     * view to scroll. If no parent scroll view can be scrolled, the bottom-left
+     * corner of the popup is pinned at the top left corner of the anchor view.</p>
+     * <p>If the view later scrolls to move <code>anchor</code> to a different
+     * location, the popup will be moved correspondingly.</p>
+     *
+     * @param anchor the view on which to pin the popup window
+     *
+     * @see #dismiss()
+     */
+    public void showAsDropDown(View anchor, int xoff, int yoff) {
+        if (isShowing() || mContentView == null) {
+            return;
+        }
+
+        registerForScrollChanged(anchor, xoff, yoff);
+
+        mIsShowing = true;
+        mIsDropdown = true;
+
+        WindowManager.LayoutParams p = createPopupLayout(anchor.getWindowToken());
+        preparePopup(p);
+        mAboveAnchor = findDropDownPosition(anchor, p, xoff, yoff);
+
+        if (mBackground != null) {
+            // If the background drawable provided was a StateListDrawable with above-anchor
+            // and below-anchor states, use those. Otherwise rely on refreshDrawableState to
+            // do the job.
+            if (mAboveAnchorBackgroundDrawable != null) {
+                if (mAboveAnchor) {
+                    mPopupView.setBackgroundDrawable(mAboveAnchorBackgroundDrawable);
+                } else {
+                    mPopupView.setBackgroundDrawable(mBelowAnchorBackgroundDrawable);
+                }
+            } else {
+                mPopupView.refreshDrawableState();
+            }
+        }
+
+        if (mHeightMode < 0) p.height = mLastHeight = mHeightMode;
+        if (mWidthMode < 0) p.width = mLastWidth = mWidthMode;
+
+        p.windowAnimations = computeAnimationResource();
+
+        invokePopup(p);
+    }
+
+    /**
+     * Indicates whether the popup is showing above (the y coordinate of the popup's bottom
+     * is less than the y coordinate of the anchor) or below the anchor view (the y coordinate
+     * of the popup is greater than y coordinate of the anchor's bottom).
+     *
+     * The value returned
+     * by this method is meaningful only after {@link #showAsDropDown(android.view.View)}
+     * or {@link #showAsDropDown(android.view.View, int, int)} was invoked.
+     *
+     * @return True if this popup is showing above the anchor view, false otherwise.
+     */
+    public boolean isAboveAnchor() {
+        return mAboveAnchor;
+    }
+
+    /**
+     * <p>Prepare the popup by embedding in into a new ViewGroup if the
+     * background drawable is not null. If embedding is required, the layout
+     * parameters' height is mnodified to take into account the background's
+     * padding.</p>
+     *
+     * @param p the layout parameters of the popup's content view
+     */
+    private void preparePopup(WindowManager.LayoutParams p) {
+        if (mBackground != null) {
+            final ViewGroup.LayoutParams layoutParams = mContentView.getLayoutParams();
+            int height = ViewGroup.LayoutParams.FILL_PARENT;
+            if (layoutParams != null &&
+                    layoutParams.height == ViewGroup.LayoutParams.WRAP_CONTENT) {
+                height = ViewGroup.LayoutParams.WRAP_CONTENT;
+            }
+
+            // when a background is available, we embed the content view
+            // within another view that owns the background drawable
+            PopupViewContainer popupViewContainer = new PopupViewContainer(mContext);
+            PopupViewContainer.LayoutParams listParams = new PopupViewContainer.LayoutParams(
+                    ViewGroup.LayoutParams.FILL_PARENT, height
+            );
+            popupViewContainer.setBackgroundDrawable(mBackground);
+            popupViewContainer.addView(mContentView, listParams);
+
+            mPopupView = popupViewContainer;
+        } else {
+            mPopupView = mContentView;
+        }
+        mPopupWidth = p.width;
+        mPopupHeight = p.height;
+    }
+
+    /**
+     * <p>Invoke the popup window by adding the content view to the window
+     * manager.</p>
+     *
+     * <p>The content view must be non-null when this method is invoked.</p>
+     *
+     * @param p the layout parameters of the popup's content view
+     */
+    private void invokePopup(WindowManager.LayoutParams p) {
+        mWindowManager.addView(mPopupView, p);
+    }
+
+    /**
+     * <p>Generate the layout parameters for the popup window.</p>
+     *
+     * @param token the window token used to bind the popup's window
+     *
+     * @return the layout parameters to pass to the window manager
+     */
+    private WindowManager.LayoutParams createPopupLayout(IBinder token) {
+        // generates the layout parameters for the drop down
+        // we want a fixed size view located at the bottom left of the anchor
+        WindowManager.LayoutParams p = new WindowManager.LayoutParams();
+        // these gravity settings put the view at the top left corner of the
+        // screen. The view is then positioned to the appropriate location
+        // by setting the x and y offsets to match the anchor's bottom
+        // left corner
+        p.gravity = Gravity.LEFT | Gravity.TOP;
+        p.width = mLastWidth = mWidth;
+        p.height = mLastHeight = mHeight;
+        if (mBackground != null) {
+            p.format = mBackground.getOpacity();
+        } else {
+            p.format = PixelFormat.TRANSLUCENT;
+        }
+        p.flags = computeFlags(p.flags);
+        p.type = WindowManager.LayoutParams.TYPE_APPLICATION_PANEL;
+        p.token = token;
+        p.setTitle("PopupWindow:" + Integer.toHexString(hashCode()));
+
+        return p;
+    }
+
+    private int computeFlags(int curFlags) {
+        curFlags &= ~(
+                WindowManager.LayoutParams.FLAG_IGNORE_CHEEK_PRESSES |
+                WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE |
+                WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE |
+                WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH |
+                WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS |
+                WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM);
+        if(mIgnoreCheekPress) {
+            curFlags |= WindowManager.LayoutParams.FLAG_IGNORE_CHEEK_PRESSES;
+        }
+        if (!mFocusable) {
+            curFlags |= WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
+            if (mInputMethodMode == INPUT_METHOD_NEEDED) {
+                curFlags |= WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
+            }
+        } else if (mInputMethodMode == INPUT_METHOD_NOT_NEEDED) {
+            curFlags |= WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
+        }
+        if (!mTouchable) {
+            curFlags |= WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
+        }
+        if (mTouchable) {
+            curFlags |= WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH;
+        }
+        if (!mClippingEnabled) {
+            curFlags |= WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS;
+        }
+        return curFlags;
+    }
+    
+    private int computeAnimationResource() {
+        if (mAnimationStyle == -1) {
+            if (mIsDropdown) {
+                return mAboveAnchor
+                        ? com.android.internal.R.style.Animation_DropDownUp
+                        : com.android.internal.R.style.Animation_DropDownDown;
+            }
+            return 0;
+        }
+        return mAnimationStyle;
+    }
+    
+    /**
+     * <p>Positions the popup window on screen. When the popup window is too
+     * tall to fit under the anchor, a parent scroll view is seeked and scrolled
+     * up to reclaim space. If scrolling is not possible or not enough, the
+     * popup window gets moved on top of the anchor.</p>
+     *
+     * <p>The height must have been set on the layout parameters prior to
+     * calling this method.</p>
+     *
+     * @param anchor the view on which the popup window must be anchored
+     * @param p the layout parameters used to display the drop down
+     *
+     * @return true if the popup is translated upwards to fit on screen
+     */
+    private boolean findDropDownPosition(View anchor, WindowManager.LayoutParams p,
+            int xoff, int yoff) {
+
+        anchor.getLocationInWindow(mDrawingLocation);
+        p.x = mDrawingLocation[0] + xoff;
+        p.y = mDrawingLocation[1] + anchor.getMeasuredHeight() + yoff;
+        
+        boolean onTop = false;
+
+        p.gravity = Gravity.LEFT | Gravity.TOP;
+        
+        anchor.getLocationOnScreen(mScreenLocation);
+        final Rect displayFrame = new Rect();
+        anchor.getWindowVisibleDisplayFrame(displayFrame);
+        
+        final View root = anchor.getRootView();
+        if (p.y + mPopupHeight > displayFrame.bottom || p.x + mPopupWidth - root.getWidth() > 0) {
+            // if the drop down disappears at the bottom of the screen. we try to
+            // scroll a parent scrollview or move the drop down back up on top of
+            // the edit box
+            int scrollX = anchor.getScrollX();
+            int scrollY = anchor.getScrollY();
+            Rect r = new Rect(scrollX, scrollY,  scrollX + mPopupWidth,
+                    scrollY + mPopupHeight + anchor.getMeasuredHeight());
+            anchor.requestRectangleOnScreen(r, true);
+            
+            // now we re-evaluate the space available, and decide from that
+            // whether the pop-up will go above or below the anchor.
+            anchor.getLocationInWindow(mDrawingLocation);
+            p.x = mDrawingLocation[0] + xoff;
+            p.y = mDrawingLocation[1] + anchor.getMeasuredHeight() + yoff;
+            
+            // determine whether there is more space above or below the anchor
+            anchor.getLocationOnScreen(mScreenLocation);
+            
+            onTop = (displayFrame.bottom - mScreenLocation[1] - anchor.getMeasuredHeight() - yoff) <
+                    (mScreenLocation[1] - yoff - displayFrame.top);
+            if (onTop) {
+                p.gravity = Gravity.LEFT | Gravity.BOTTOM;
+                p.y = root.getHeight() - mDrawingLocation[1] + yoff;
+            } else {
+                p.y = mDrawingLocation[1] + anchor.getMeasuredHeight() + yoff;
+            }
+        }
+
+        p.gravity |= Gravity.DISPLAY_CLIP_VERTICAL;
+        
+        return onTop;
+    }
+    
+    /**
+     * Returns the maximum height that is available for the popup to be
+     * completely shown. It is recommended that this height be the maximum for
+     * the popup's height, otherwise it is possible that the popup will be
+     * clipped.
+     * 
+     * @param anchor The view on which the popup window must be anchored.
+     * @return The maximum available height for the popup to be completely
+     *         shown.
+     */
+    public int getMaxAvailableHeight(View anchor) {
+        return getMaxAvailableHeight(anchor, 0);
+    }
+
+    /**
+     * Returns the maximum height that is available for the popup to be
+     * completely shown. It is recommended that this height be the maximum for
+     * the popup's height, otherwise it is possible that the popup will be
+     * clipped.
+     *
+     * @param anchor The view on which the popup window must be anchored.
+     * @param yOffset y offset from the view's bottom edge
+     * @return The maximum available height for the popup to be completely
+     *         shown.
+     */
+    public int getMaxAvailableHeight(View anchor, int yOffset) {
+        final Rect displayFrame = new Rect();
+        anchor.getWindowVisibleDisplayFrame(displayFrame);
+
+        final int[] anchorPos = mDrawingLocation;
+        anchor.getLocationOnScreen(anchorPos);
+        
+        final int distanceToBottom = displayFrame.bottom -
+                (anchorPos[1] + anchor.getHeight()) - yOffset;
+        final int distanceToTop = anchorPos[1] - displayFrame.top + yOffset;
+
+        // anchorPos[1] is distance from anchor to top of screen
+        int returnedHeight = Math.max(distanceToBottom, distanceToTop);
+        if (mBackground != null) {
+            mBackground.getPadding(mTempRect);
+            returnedHeight -= mTempRect.top + mTempRect.bottom; 
+        }
+        
+        return returnedHeight;
+    }
+    
+    /**
+     * <p>Dispose of the popup window. This method can be invoked only after
+     * {@link #showAsDropDown(android.view.View)} has been executed. Failing that, calling
+     * this method will have no effect.</p>
+     *
+     * @see #showAsDropDown(android.view.View) 
+     */
+    public void dismiss() {
+        if (isShowing() && mPopupView != null) {
+            unregisterForScrollChanged();
+
+            mWindowManager.removeView(mPopupView);
+            if (mPopupView != mContentView && mPopupView instanceof ViewGroup) {
+                ((ViewGroup) mPopupView).removeView(mContentView);
+            }
+            mPopupView = null;
+            mIsShowing = false;
+
+            if (mOnDismissListener != null) {
+                mOnDismissListener.onDismiss();
+            }
+        }
+    }
+
+    /**
+     * Sets the listener to be called when the window is dismissed.
+     * 
+     * @param onDismissListener The listener.
+     */
+    public void setOnDismissListener(OnDismissListener onDismissListener) {
+        mOnDismissListener = onDismissListener;
+    }
+    
+    /**
+     * Updates the state of the popup window, if it is currently being displayed,
+     * from the currently set state.  This include:
+     * {@link #setClippingEnabled(boolean)}, {@link #setFocusable(boolean)},
+     * {@link #setIgnoreCheekPress()}, {@link #setInputMethodMode(int)},
+     * {@link #setTouchable(boolean)}, and {@link #setAnimationStyle(int)}.
+     */
+    public void update() {
+        if (!isShowing() || mContentView == null) {
+            return;
+        }
+        
+        WindowManager.LayoutParams p = (WindowManager.LayoutParams)
+                mPopupView.getLayoutParams();
+        
+        boolean update = false;
+        
+        final int newAnim = computeAnimationResource();
+        if (newAnim != p.windowAnimations) {
+            p.windowAnimations = newAnim;
+            update = true;
+        }
+
+        final int newFlags = computeFlags(p.flags);
+        if (newFlags != p.flags) {
+            p.flags = newFlags;
+            update = true;
+        }
+        
+        if (update) {
+            mWindowManager.updateViewLayout(mPopupView, p);
+        }
+    }
+    
+    /**
+     * <p>Updates the position and the dimension of the popup window. Width and
+     * height can be set to -1 to update location only.  Calling this function
+     * also updates the window with the current popup state as
+     * described for {@link #update()}.</p>
+     *
+     * @param x the new x location
+     * @param y the new y location
+     * @param width the new width, can be -1 to ignore
+     * @param height the new height, can be -1 to ignore
+     */
+    public void update(int x, int y, int width, int height) {
+        update(x, y, width, height, false);
+    }
+
+    /**
+     * <p>Updates the position and the dimension of the popup window. Width and
+     * height can be set to -1 to update location only.  Calling this function
+     * also updates the window with the current popup state as
+     * described for {@link #update()}.</p>
+     *
+     * @param x the new x location
+     * @param y the new y location
+     * @param width the new width, can be -1 to ignore
+     * @param height the new height, can be -1 to ignore
+     * @param force reposition the window even if the specified position
+     *              already seems to correspond to the LayoutParams
+     * 
+     * @hide pending API council approval
+     */
+    public void update(int x, int y, int width, int height, boolean force) {
+        if (width != -1) {
+            mLastWidth = width;
+            setWidth(width);
+        }
+
+        if (height != -1) {
+            mLastHeight = height;
+            setHeight(height);
+        }
+
+        if (!isShowing() || mContentView == null) {
+            return;
+        }
+
+        WindowManager.LayoutParams p = (WindowManager.LayoutParams)
+                mPopupView.getLayoutParams();
+
+        boolean update = force;
+
+        final int finalWidth = mWidthMode < 0 ? mWidthMode : mLastWidth;
+        if (width != -1 && p.width != finalWidth) {
+            p.width = mLastWidth = finalWidth;
+            update = true;
+        }
+
+        final int finalHeight = mHeightMode < 0 ? mHeightMode : mLastHeight;
+        if (height != -1 && p.height != finalHeight) {
+            p.height = mLastHeight = finalHeight;
+            update = true;
+        }
+
+        if (p.x != x) {
+            p.x = x;
+            update = true;
+        }
+
+        if (p.y != y) {
+            p.y = y;
+            update = true;
+        }
+
+        final int newAnim = computeAnimationResource();
+        if (newAnim != p.windowAnimations) {
+            p.windowAnimations = newAnim;
+            update = true;
+        }
+
+        final int newFlags = computeFlags(p.flags);
+        if (newFlags != p.flags) {
+            p.flags = newFlags;
+            update = true;
+        }
+        
+        if (update) {
+            mWindowManager.updateViewLayout(mPopupView, p);
+        }
+    }
+
+    /**
+     * <p>Updates the position and the dimension of the popup window. Width and
+     * height can be set to -1 to update location only.  Calling this function
+     * also updates the window with the current popup state as
+     * described for {@link #update()}.</p>
+     *
+     * @param anchor the popup's anchor view
+     * @param width the new width, can be -1 to ignore
+     * @param height the new height, can be -1 to ignore
+     */
+    public void update(View anchor, int width, int height) {
+        update(anchor, 0, 0, width, height);
+    }
+
+    /**
+     * <p>Updates the position and the dimension of the popup window. Width and
+     * height can be set to -1 to update location only.  Calling this function
+     * also updates the window with the current popup state as
+     * described for {@link #update()}.</p>
+     * <p>If the view later scrolls to move <code>anchor</code> to a different
+     * location, the popup will be moved correspondingly.</p>
+     *
+     * @param anchor the popup's anchor view
+     * @param xoff x offset from the view's left edge
+     * @param yoff y offset from the view's bottom edge
+     * @param width the new width, can be -1 to ignore
+     * @param height the new height, can be -1 to ignore
+     */
+    public void update(View anchor, int xoff, int yoff, int width, int height) {
+        if (!isShowing() || mContentView == null) {
+            return;
+        }
+
+        WeakReference<View> oldAnchor = mAnchor;
+        if (oldAnchor == null || oldAnchor.get() != anchor ||
+            mAnchorXoff != xoff || mAnchorYoff != yoff) {
+            registerForScrollChanged(anchor, xoff, yoff);
+        }
+
+        WindowManager.LayoutParams p = (WindowManager.LayoutParams)
+                mPopupView.getLayoutParams();
+
+        if (width == -1) {
+            width = mPopupWidth;
+        } else {
+            mPopupWidth = width;
+        }
+        if (height == -1) {
+            height = mPopupHeight;
+        } else {
+            mPopupHeight = height;
+        }
+        
+        mAboveAnchor = findDropDownPosition(anchor, p, xoff, yoff);
+        update(p.x, p.y, width, height);
+    }
+
+    /**
+     * Listener that is called when this popup window is dismissed.
+     */
+    public interface OnDismissListener {
+        /**
+         * Called when this popup window is dismissed.
+         */
+        public void onDismiss();
+    }
+
+    private void unregisterForScrollChanged() {
+        WeakReference<View> anchorRef = mAnchor;
+        View anchor = null;
+        if (anchorRef != null) {
+            anchor = anchorRef.get();
+        }
+        if (anchor != null) {
+            ViewTreeObserver vto = anchor.getViewTreeObserver();
+            vto.removeOnScrollChangedListener(mOnScrollChangedListener);
+        }
+        mAnchor = null;
+    }
+
+    private void registerForScrollChanged(View anchor, int xoff, int yoff) {
+        unregisterForScrollChanged();
+
+        mAnchor = new WeakReference<View>(anchor);
+        ViewTreeObserver vto = anchor.getViewTreeObserver();
+        if (vto != null) {
+            vto.addOnScrollChangedListener(mOnScrollChangedListener);
+        }
+
+        mAnchorXoff = xoff;
+        mAnchorYoff = yoff;
+    }
+
+    private class PopupViewContainer extends FrameLayout {
+
+        public PopupViewContainer(Context context) {
+            super(context);
+        }
+
+        @Override
+        protected int[] onCreateDrawableState(int extraSpace) {
+            if (mAboveAnchor) {
+                // 1 more needed for the above anchor state
+                final int[] drawableState = super.onCreateDrawableState(extraSpace + 1);
+                View.mergeDrawableStates(drawableState, ABOVE_ANCHOR_STATE_SET);
+                return drawableState;
+            } else {
+                return super.onCreateDrawableState(extraSpace);
+            }
+        }
+
+        @Override
+        public boolean dispatchKeyEvent(KeyEvent event) {
+            if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) {
+                dismiss();
+                return true;
+            } else {
+                return super.dispatchKeyEvent(event);
+            }
+        }
+
+        @Override
+        public boolean dispatchTouchEvent(MotionEvent ev) {
+            if (mTouchInterceptor != null && mTouchInterceptor.onTouch(this, ev)) {
+                return true;
+            }
+            return super.dispatchTouchEvent(ev);
+        }
+
+        @Override
+        public boolean onTouchEvent(MotionEvent event) {
+            final int x = (int) event.getX();
+            final int y = (int) event.getY();
+            
+            if ((event.getAction() == MotionEvent.ACTION_DOWN)
+                    && ((x < 0) || (x >= getWidth()) || (y < 0) || (y >= getHeight()))) {
+                dismiss();
+                return true;
+            } else if (event.getAction() == MotionEvent.ACTION_OUTSIDE) {
+                dismiss();
+                return true;
+            } else {
+                return super.onTouchEvent(event);
+            }
+        }
+        
+    }
+    
+}
diff --git a/core/java/android/widget/ProgressBar.java b/core/java/android/widget/ProgressBar.java
new file mode 100644
index 0000000..f646ab5
--- /dev/null
+++ b/core/java/android/widget/ProgressBar.java
@@ -0,0 +1,919 @@
+/*
+ * 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 android.widget;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Bitmap;
+import android.graphics.BitmapShader;
+import android.graphics.Canvas;
+import android.graphics.Shader;
+import android.graphics.Rect;
+import android.graphics.drawable.AnimationDrawable;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.ClipDrawable;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.LayerDrawable;
+import android.graphics.drawable.ShapeDrawable;
+import android.graphics.drawable.StateListDrawable;
+import android.graphics.drawable.shapes.RoundRectShape;
+import android.graphics.drawable.shapes.Shape;
+import android.util.AttributeSet;
+import android.view.Gravity;
+import android.view.View;
+import android.view.animation.AlphaAnimation;
+import android.view.animation.Animation;
+import android.view.animation.AnimationUtils;
+import android.view.animation.Interpolator;
+import android.view.animation.LinearInterpolator;
+import android.view.animation.Transformation;
+import android.widget.RemoteViews.RemoteView;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.SystemClock;
+
+import com.android.internal.R;
+
+
+/**
+ * <p>
+ * Visual indicator of progress in some operation.  Displays a bar to the user
+ * representing how far the operation has progressed; the application can 
+ * change the amount of progress (modifying the length of the bar) as it moves 
+ * forward.  There is also a secondary progress displayable on a progress bar
+ * which is useful for displaying intermediate progress, such as the buffer
+ * level during a streaming playback progress bar.
+ * </p>
+ *
+ * <p>
+ * A progress bar can also be made indeterminate. In indeterminate mode, the
+ * progress bar shows a cyclic animation. This mode is used by applications
+ * when the length of the task is unknown. 
+ * </p>
+ *
+ * <p>The following code example shows how a progress bar can be used from
+ * a worker thread to update the user interface to notify the user of progress:
+ * </p>
+ * 
+ * <pre class="prettyprint">
+ * public class MyActivity extends Activity {
+ *     private static final int PROGRESS = 0x1;
+ *
+ *     private ProgressBar mProgress;
+ *     private int mProgressStatus = 0;
+ *
+ *     private Handler mHandler = new Handler();
+ *
+ *     protected void onCreate(Bundle icicle) {
+ *         super.onCreate(icicle);
+ *
+ *         setContentView(R.layout.progressbar_activity);
+ *
+ *         mProgress = (ProgressBar) findViewById(R.id.progress_bar);
+ *
+ *         // Start lengthy operation in a background thread
+ *         new Thread(new Runnable() {
+ *             public void run() {
+ *                 while (mProgressStatus < 100) {
+ *                     mProgressStatus = doWork();
+ *
+ *                     // Update the progress bar
+ *                     mHandler.post(new Runnable() {
+ *                         public void run() {
+ *                             mProgress.setProgress(mProgressStatus);
+ *                         }
+ *                     });
+ *                 }
+ *             }
+ *         }).start();
+ *     }
+ * }
+ * </pre>
+ *  
+ * <p><strong>XML attributes</b></strong> 
+ * <p> 
+ * See {@link android.R.styleable#ProgressBar ProgressBar Attributes}, 
+ * {@link android.R.styleable#View View Attributes}
+ * </p>
+ * 
+ * <p><strong>Styles</b></strong> 
+ * <p> 
+ * @attr ref android.R.styleable#Theme_progressBarStyle
+ * @attr ref android.R.styleable#Theme_progressBarStyleSmall
+ * @attr ref android.R.styleable#Theme_progressBarStyleLarge
+ * @attr ref android.R.styleable#Theme_progressBarStyleHorizontal
+ * </p>
+ */
+@RemoteView
+public class ProgressBar extends View {
+    private static final int MAX_LEVEL = 10000;
+    private static final int ANIMATION_RESOLUTION = 200;
+
+    int mMinWidth;
+    int mMaxWidth;
+    int mMinHeight;
+    int mMaxHeight;
+
+    private int mProgress;
+    private int mSecondaryProgress;
+    private int mMax;
+
+    private int mBehavior;
+    private int mDuration;
+    private boolean mIndeterminate;
+    private boolean mOnlyIndeterminate;
+    private Transformation mTransformation;
+    private AlphaAnimation mAnimation;
+    private Drawable mIndeterminateDrawable;
+    private Drawable mProgressDrawable;
+    private Drawable mCurrentDrawable;
+    Bitmap mSampleTile;
+    private boolean mNoInvalidate;
+    private Interpolator mInterpolator;
+    private RefreshProgressRunnable mRefreshProgressRunnable;
+    private long mUiThreadId;
+    private boolean mShouldStartAnimationDrawable;
+    private long mLastDrawTime;
+
+    private boolean mInDrawing;
+
+    /**
+     * Create a new progress bar with range 0...100 and initial progress of 0.
+     * @param context the application environment
+     */
+    public ProgressBar(Context context) {
+        this(context, null);
+    }
+    
+    public ProgressBar(Context context, AttributeSet attrs) {
+        this(context, attrs, com.android.internal.R.attr.progressBarStyle);
+    }
+
+    public ProgressBar(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+        mUiThreadId = Thread.currentThread().getId();
+        initProgressBar();
+
+        TypedArray a =
+            context.obtainStyledAttributes(attrs, R.styleable.ProgressBar, defStyle, 0);
+        
+        mNoInvalidate = true;
+        
+        Drawable drawable = a.getDrawable(R.styleable.ProgressBar_progressDrawable);
+        if (drawable != null) {
+            drawable = tileify(drawable, false);
+            setProgressDrawable(drawable);
+        }
+
+
+        mDuration = a.getInt(R.styleable.ProgressBar_indeterminateDuration, mDuration);
+
+        mMinWidth = a.getDimensionPixelSize(R.styleable.ProgressBar_minWidth, mMinWidth);
+        mMaxWidth = a.getDimensionPixelSize(R.styleable.ProgressBar_maxWidth, mMaxWidth);
+        mMinHeight = a.getDimensionPixelSize(R.styleable.ProgressBar_minHeight, mMinHeight);
+        mMaxHeight = a.getDimensionPixelSize(R.styleable.ProgressBar_maxHeight, mMaxHeight);
+
+        mBehavior = a.getInt(R.styleable.ProgressBar_indeterminateBehavior, mBehavior);
+
+        final int resID = a.getResourceId(com.android.internal.R.styleable.ProgressBar_interpolator, -1);
+        if (resID > 0) {
+            setInterpolator(context, resID);
+        }
+
+        setMax(a.getInt(R.styleable.ProgressBar_max, mMax));
+
+        setProgress(a.getInt(R.styleable.ProgressBar_progress, mProgress));
+
+        setSecondaryProgress(
+                a.getInt(R.styleable.ProgressBar_secondaryProgress, mSecondaryProgress));
+
+        drawable = a.getDrawable(R.styleable.ProgressBar_indeterminateDrawable);
+        if (drawable != null) {
+            drawable = tileifyIndeterminate(drawable);
+            setIndeterminateDrawable(drawable);
+        }
+
+        mOnlyIndeterminate = a.getBoolean(
+                R.styleable.ProgressBar_indeterminateOnly, mOnlyIndeterminate);
+
+        mNoInvalidate = false;
+
+        setIndeterminate(mOnlyIndeterminate || a.getBoolean(
+                R.styleable.ProgressBar_indeterminate, mIndeterminate));
+
+        a.recycle();
+    }
+
+    /**
+     * Converts a drawable to a tiled version of itself. It will recursively
+     * traverse layer and state list drawables.
+     */
+    private Drawable tileify(Drawable drawable, boolean clip) {
+        
+        if (drawable instanceof LayerDrawable) {
+            LayerDrawable background = (LayerDrawable) drawable;
+            final int N = background.getNumberOfLayers();
+            Drawable[] outDrawables = new Drawable[N];
+            
+            for (int i = 0; i < N; i++) {
+                int id = background.getId(i);
+                outDrawables[i] = tileify(background.getDrawable(i),
+                        (id == R.id.progress || id == R.id.secondaryProgress));
+            }
+
+            LayerDrawable newBg = new LayerDrawable(outDrawables);
+            
+            for (int i = 0; i < N; i++) {
+                newBg.setId(i, background.getId(i));
+            }
+            
+            return newBg;
+            
+        } else if (drawable instanceof StateListDrawable) {
+            StateListDrawable in = (StateListDrawable) drawable;
+            StateListDrawable out = new StateListDrawable();
+            int numStates = in.getStateCount();
+            for (int i = 0; i < numStates; i++) {
+                out.addState(in.getStateSet(i), tileify(in.getStateDrawable(i), clip));
+            }
+            return out;
+            
+        } else if (drawable instanceof BitmapDrawable) {
+            final Bitmap tileBitmap = ((BitmapDrawable) drawable).getBitmap();
+            if (mSampleTile == null) {
+                mSampleTile = tileBitmap;
+            }
+            
+            final ShapeDrawable shapeDrawable = new ShapeDrawable(getDrawableShape());
+
+            final BitmapShader bitmapShader = new BitmapShader(tileBitmap,
+                    Shader.TileMode.REPEAT, Shader.TileMode.CLAMP);
+            shapeDrawable.getPaint().setShader(bitmapShader);
+
+            return (clip) ? new ClipDrawable(shapeDrawable, Gravity.LEFT,
+                    ClipDrawable.HORIZONTAL) : shapeDrawable;
+        }
+        
+        return drawable;
+    }
+
+    Shape getDrawableShape() {
+        final float[] roundedCorners = new float[] { 5, 5, 5, 5, 5, 5, 5, 5 };
+        return new RoundRectShape(roundedCorners, null, null);
+    }
+    
+    /**
+     * Convert a AnimationDrawable for use as a barberpole animation.
+     * Each frame of the animation is wrapped in a ClipDrawable and
+     * given a tiling BitmapShader.
+     */
+    private Drawable tileifyIndeterminate(Drawable drawable) {
+        if (drawable instanceof AnimationDrawable) {
+            AnimationDrawable background = (AnimationDrawable) drawable;
+            final int N = background.getNumberOfFrames();
+            AnimationDrawable newBg = new AnimationDrawable();
+            newBg.setOneShot(background.isOneShot());
+            
+            for (int i = 0; i < N; i++) {
+                Drawable frame = tileify(background.getFrame(i), true);
+                frame.setLevel(10000);
+                newBg.addFrame(frame, background.getDuration(i));
+            }
+            newBg.setLevel(10000);
+            drawable = newBg;
+        }
+        return drawable;
+    }
+    
+    /**
+     * <p>
+     * Initialize the progress bar's default values:
+     * </p>
+     * <ul>
+     * <li>progress = 0</li>
+     * <li>max = 100</li>
+     * <li>animation duration = 4000 ms</li>
+     * <li>indeterminate = false</li>
+     * <li>behavior = repeat</li>
+     * </ul>
+     */
+    private void initProgressBar() {
+        mMax = 100;
+        mProgress = 0;
+        mSecondaryProgress = 0;
+        mIndeterminate = false;
+        mOnlyIndeterminate = false;
+        mDuration = 4000;
+        mBehavior = AlphaAnimation.RESTART;
+        mMinWidth = 24;
+        mMaxWidth = 48;
+        mMinHeight = 24;
+        mMaxHeight = 48;
+    }
+
+    /**
+     * <p>Indicate whether this progress bar is in indeterminate mode.</p>
+     *
+     * @return true if the progress bar is in indeterminate mode
+     */
+    public synchronized boolean isIndeterminate() {
+        return mIndeterminate;
+    }
+
+    /**
+     * <p>Change the indeterminate mode for this progress bar. In indeterminate
+     * mode, the progress is ignored and the progress bar shows an infinite
+     * animation instead.</p>
+     * 
+     * If this progress bar's style only supports indeterminate mode (such as the circular
+     * progress bars), then this will be ignored.
+     *
+     * @param indeterminate true to enable the indeterminate mode
+     */
+    @android.view.RemotableViewMethod
+    public synchronized void setIndeterminate(boolean indeterminate) {
+        if ((!mOnlyIndeterminate || !mIndeterminate) && indeterminate != mIndeterminate) {
+            mIndeterminate = indeterminate;
+
+            if (indeterminate) {
+                // swap between indeterminate and regular backgrounds
+                mCurrentDrawable = mIndeterminateDrawable;
+                startAnimation();
+            } else {
+                mCurrentDrawable = mProgressDrawable;
+                stopAnimation();
+            }
+        }
+    }
+
+    /**
+     * <p>Get the drawable used to draw the progress bar in
+     * indeterminate mode.</p>
+     *
+     * @return a {@link android.graphics.drawable.Drawable} instance
+     *
+     * @see #setIndeterminateDrawable(android.graphics.drawable.Drawable)
+     * @see #setIndeterminate(boolean)
+     */
+    public Drawable getIndeterminateDrawable() {
+        return mIndeterminateDrawable;
+    }
+
+    /**
+     * <p>Define the drawable used to draw the progress bar in
+     * indeterminate mode.</p>
+     *
+     * @param d the new drawable
+     *
+     * @see #getIndeterminateDrawable()
+     * @see #setIndeterminate(boolean)
+     */
+    public void setIndeterminateDrawable(Drawable d) {
+        if (d != null) {
+            d.setCallback(this);
+        }
+        mIndeterminateDrawable = d;
+        if (mIndeterminate) {
+            mCurrentDrawable = d;
+            postInvalidate();
+        }
+    }
+    
+    /**
+     * <p>Get the drawable used to draw the progress bar in
+     * progress mode.</p>
+     *
+     * @return a {@link android.graphics.drawable.Drawable} instance
+     *
+     * @see #setProgressDrawable(android.graphics.drawable.Drawable)
+     * @see #setIndeterminate(boolean)
+     */
+    public Drawable getProgressDrawable() {
+        return mProgressDrawable;
+    }
+
+    /**
+     * <p>Define the drawable used to draw the progress bar in
+     * progress mode.</p>
+     *
+     * @param d the new drawable
+     *
+     * @see #getProgressDrawable()
+     * @see #setIndeterminate(boolean)
+     */
+    public void setProgressDrawable(Drawable d) {
+        if (d != null) {
+            d.setCallback(this);
+        }
+        mProgressDrawable = d;
+        if (!mIndeterminate) {
+            mCurrentDrawable = d;
+            postInvalidate();
+        }
+    }
+    
+    /**
+     * @return The drawable currently used to draw the progress bar
+     */
+    Drawable getCurrentDrawable() {
+        return mCurrentDrawable;
+    }
+
+    @Override
+    protected boolean verifyDrawable(Drawable who) {
+        return who == mProgressDrawable || who == mIndeterminateDrawable
+                || super.verifyDrawable(who);
+    }
+
+    @Override
+    public void postInvalidate() {
+        if (!mNoInvalidate) {
+            super.postInvalidate();
+        }
+    }
+
+    private class RefreshProgressRunnable implements Runnable {
+
+        private int mId;
+        private int mProgress;
+        private boolean mFromUser;
+        
+        RefreshProgressRunnable(int id, int progress, boolean fromUser) {
+            mId = id;
+            mProgress = progress;
+            mFromUser = fromUser;
+        }
+        
+        public void run() {
+            doRefreshProgress(mId, mProgress, mFromUser);
+            // Put ourselves back in the cache when we are done
+            mRefreshProgressRunnable = this;
+        }
+        
+        public void setup(int id, int progress, boolean fromUser) {
+            mId = id;
+            mProgress = progress;
+            mFromUser = fromUser;
+        }
+        
+    }
+    
+    private synchronized void doRefreshProgress(int id, int progress, boolean fromUser) {
+        float scale = mMax > 0 ? (float) progress / (float) mMax : 0;
+        final Drawable d = mCurrentDrawable;
+        if (d != null) {
+            Drawable progressDrawable = null;
+
+            if (d instanceof LayerDrawable) {
+                progressDrawable = ((LayerDrawable) d).findDrawableByLayerId(id);
+            }
+
+            final int level = (int) (scale * MAX_LEVEL);
+            (progressDrawable != null ? progressDrawable : d).setLevel(level);
+        } else {
+            invalidate();
+        }
+        
+        if (id == R.id.progress) {
+            onProgressRefresh(scale, fromUser);
+        }
+    }
+    
+    void onProgressRefresh(float scale, boolean fromUser) {        
+    }
+
+    private synchronized void refreshProgress(int id, int progress, boolean fromUser) {
+        if (mUiThreadId == Thread.currentThread().getId()) {
+            doRefreshProgress(id, progress, fromUser);
+        } else {
+            RefreshProgressRunnable r;
+            if (mRefreshProgressRunnable != null) {
+                // Use cached RefreshProgressRunnable if available
+                r = mRefreshProgressRunnable;
+                // Uncache it
+                mRefreshProgressRunnable = null;
+                r.setup(id, progress, fromUser);
+            } else {
+                // Make a new one
+                r = new RefreshProgressRunnable(id, progress, fromUser);
+            }
+            post(r);
+        }
+    }
+    
+    /**
+     * <p>Set the current progress to the specified value. Does not do anything
+     * if the progress bar is in indeterminate mode.</p>
+     *
+     * @param progress the new progress, between 0 and {@link #getMax()}
+     *
+     * @see #setIndeterminate(boolean)
+     * @see #isIndeterminate()
+     * @see #getProgress()
+     * @see #incrementProgressBy(int) 
+     */
+    @android.view.RemotableViewMethod
+    public synchronized void setProgress(int progress) {
+        setProgress(progress, false);
+    }
+    
+    @android.view.RemotableViewMethod
+    synchronized void setProgress(int progress, boolean fromUser) {
+        if (mIndeterminate) {
+            return;
+        }
+
+        if (progress < 0) {
+            progress = 0;
+        }
+
+        if (progress > mMax) {
+            progress = mMax;
+        }
+
+        if (progress != mProgress) {
+            mProgress = progress;
+            refreshProgress(R.id.progress, mProgress, fromUser);
+        }
+    }
+
+    /**
+     * <p>
+     * Set the current secondary progress to the specified value. Does not do
+     * anything if the progress bar is in indeterminate mode.
+     * </p>
+     * 
+     * @param secondaryProgress the new secondary progress, between 0 and {@link #getMax()}
+     * @see #setIndeterminate(boolean)
+     * @see #isIndeterminate()
+     * @see #getSecondaryProgress()
+     * @see #incrementSecondaryProgressBy(int)
+     */
+    @android.view.RemotableViewMethod
+    public synchronized void setSecondaryProgress(int secondaryProgress) {
+        if (mIndeterminate) {
+            return;
+        }
+
+        if (secondaryProgress < 0) {
+            secondaryProgress = 0;
+        }
+
+        if (secondaryProgress > mMax) {
+            secondaryProgress = mMax;
+        }
+
+        if (secondaryProgress != mSecondaryProgress) {
+            mSecondaryProgress = secondaryProgress;
+            refreshProgress(R.id.secondaryProgress, mSecondaryProgress, false);
+        }
+    }
+
+    /**
+     * <p>Get the progress bar's current level of progress. Return 0 when the
+     * progress bar is in indeterminate mode.</p>
+     *
+     * @return the current progress, between 0 and {@link #getMax()}
+     *
+     * @see #setIndeterminate(boolean)
+     * @see #isIndeterminate()
+     * @see #setProgress(int)
+     * @see #setMax(int)
+     * @see #getMax()
+     */
+    public synchronized int getProgress() {
+        return mIndeterminate ? 0 : mProgress;
+    }
+
+    /**
+     * <p>Get the progress bar's current level of secondary progress. Return 0 when the
+     * progress bar is in indeterminate mode.</p>
+     *
+     * @return the current secondary progress, between 0 and {@link #getMax()}
+     *
+     * @see #setIndeterminate(boolean)
+     * @see #isIndeterminate()
+     * @see #setSecondaryProgress(int)
+     * @see #setMax(int)
+     * @see #getMax()
+     */
+    public synchronized int getSecondaryProgress() {
+        return mIndeterminate ? 0 : mSecondaryProgress;
+    }
+
+    /**
+     * <p>Return the upper limit of this progress bar's range.</p>
+     *
+     * @return a positive integer
+     *
+     * @see #setMax(int)
+     * @see #getProgress()
+     * @see #getSecondaryProgress()
+     */
+    public synchronized int getMax() {
+        return mMax;
+    }
+
+    /**
+     * <p>Set the range of the progress bar to 0...<tt>max</tt>.</p>
+     *
+     * @param max the upper range of this progress bar
+     *
+     * @see #getMax()
+     * @see #setProgress(int) 
+     * @see #setSecondaryProgress(int) 
+     */
+    @android.view.RemotableViewMethod
+    public synchronized void setMax(int max) {
+        if (max < 0) {
+            max = 0;
+        }
+        if (max != mMax) {
+            mMax = max;
+            postInvalidate();
+
+            if (mProgress > max) {
+                mProgress = max;
+            }
+        }
+    }
+    
+    /**
+     * <p>Increase the progress bar's progress by the specified amount.</p>
+     *
+     * @param diff the amount by which the progress must be increased
+     *
+     * @see #setProgress(int) 
+     */
+    public synchronized final void incrementProgressBy(int diff) {
+        setProgress(mProgress + diff);
+    }
+
+    /**
+     * <p>Increase the progress bar's secondary progress by the specified amount.</p>
+     *
+     * @param diff the amount by which the secondary progress must be increased
+     *
+     * @see #setSecondaryProgress(int) 
+     */
+    public synchronized final void incrementSecondaryProgressBy(int diff) {
+        setSecondaryProgress(mSecondaryProgress + diff);
+    }
+
+    /**
+     * <p>Start the indeterminate progress animation.</p>
+     */
+    void startAnimation() {
+        int visibility = getVisibility();
+        if (visibility != VISIBLE) {
+            return;
+        }
+
+        if (mIndeterminateDrawable instanceof AnimationDrawable) {
+            mShouldStartAnimationDrawable = true;
+            mAnimation = null;
+        } else {
+            if (mInterpolator == null) {
+                mInterpolator = new LinearInterpolator();
+            }
+    
+            mTransformation = new Transformation();
+            mAnimation = new AlphaAnimation(0.0f, 1.0f);
+            mAnimation.setRepeatMode(mBehavior);
+            mAnimation.setRepeatCount(Animation.INFINITE);
+            mAnimation.setDuration(mDuration);
+            mAnimation.setInterpolator(mInterpolator);
+            mAnimation.setStartTime(Animation.START_ON_FIRST_FRAME);
+            postInvalidate();
+        }
+    }
+
+    /**
+     * <p>Stop the indeterminate progress animation.</p>
+     */
+    void stopAnimation() {
+        mAnimation = null;
+        mTransformation = null;
+        if (mIndeterminateDrawable instanceof AnimationDrawable) {
+            ((AnimationDrawable) mIndeterminateDrawable).stop();
+            mShouldStartAnimationDrawable = false;
+        }
+    }
+
+    /**
+     * Sets the acceleration curve for the indeterminate animation.
+     * The interpolator is loaded as a resource from the specified context.
+     *
+     * @param context The application environment
+     * @param resID The resource identifier of the interpolator to load
+     */
+    public void setInterpolator(Context context, int resID) {
+        setInterpolator(AnimationUtils.loadInterpolator(context, resID));
+    }
+
+    /**
+     * Sets the acceleration curve for the indeterminate animation.
+     * Defaults to a linear interpolation.
+     *
+     * @param interpolator The interpolator which defines the acceleration curve
+     */
+    public void setInterpolator(Interpolator interpolator) {
+        mInterpolator = interpolator;
+    }
+
+    /**
+     * Gets the acceleration curve type for the indeterminate animation.
+     *
+     * @return the {@link Interpolator} associated to this animation
+     */
+    public Interpolator getInterpolator() {
+        return mInterpolator;
+    }
+
+    @Override
+    public void setVisibility(int v) {
+        if (getVisibility() != v) {
+            super.setVisibility(v);
+
+            if (mIndeterminate) {
+                // let's be nice with the UI thread
+                if (v == GONE || v == INVISIBLE) {
+                    stopAnimation();
+                } else if (v == VISIBLE) {
+                    startAnimation();
+                }
+            }
+        }
+    }
+
+    @Override
+    public void invalidateDrawable(Drawable dr) {
+        if (!mInDrawing) {
+            if (dr == mProgressDrawable || dr == mIndeterminateDrawable) {
+                final Rect dirty = dr.getBounds();
+                final int scrollX = mScrollX + mPaddingLeft;
+                final int scrollY = mScrollY + mPaddingTop;
+
+                invalidate(dirty.left + scrollX, dirty.top + scrollY,
+                        dirty.right + scrollX, dirty.bottom + scrollY);
+            } else {
+                super.invalidateDrawable(dr);
+            }
+        }
+    }
+
+    @Override
+    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
+        // onDraw will translate the canvas so we draw starting at 0,0
+        int right = w - mPaddingRight - mPaddingLeft;
+        int bottom = h - mPaddingBottom - mPaddingTop;
+
+        if (mIndeterminateDrawable != null) {
+            mIndeterminateDrawable.setBounds(0, 0, right, bottom);
+        }
+        
+        if (mProgressDrawable != null) {
+            mProgressDrawable.setBounds(0, 0, right, bottom);
+        }
+    }
+
+    @Override
+    protected synchronized void onDraw(Canvas canvas) {
+        super.onDraw(canvas);
+
+        Drawable d = mCurrentDrawable;
+        if (d != null) {
+            // Translate canvas so a indeterminate circular progress bar with padding
+            // rotates properly in its animation
+            canvas.save();
+            canvas.translate(mPaddingLeft, mPaddingTop);
+            long time = getDrawingTime();
+            if (mAnimation != null) {
+                mAnimation.getTransformation(time, mTransformation);
+                float scale = mTransformation.getAlpha();
+                try {
+                    mInDrawing = true;
+                    d.setLevel((int) (scale * MAX_LEVEL));
+                } finally {
+                    mInDrawing = false;
+                }
+                if (SystemClock.uptimeMillis() - mLastDrawTime >= ANIMATION_RESOLUTION) {
+                    mLastDrawTime = SystemClock.uptimeMillis();
+                    postInvalidateDelayed(ANIMATION_RESOLUTION);
+                }
+            }
+            d.draw(canvas);
+            canvas.restore();
+            if (mShouldStartAnimationDrawable && d instanceof AnimationDrawable) {
+                ((AnimationDrawable) d).start();
+                mShouldStartAnimationDrawable = false;
+            }
+        }
+    }
+
+    @Override
+    protected synchronized void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        Drawable d = mCurrentDrawable;
+
+        int dw = 0;
+        int dh = 0;
+        if (d != null) {
+            dw = Math.max(mMinWidth, Math.min(mMaxWidth, d.getIntrinsicWidth()));
+            dh = Math.max(mMinHeight, Math.min(mMaxHeight, d.getIntrinsicHeight()));
+        }
+        dw += mPaddingLeft + mPaddingRight;
+        dh += mPaddingTop + mPaddingBottom;
+
+        setMeasuredDimension(resolveSize(dw, widthMeasureSpec),
+                resolveSize(dh, heightMeasureSpec));
+    }
+    
+    @Override
+    protected void drawableStateChanged() {
+        super.drawableStateChanged();
+        
+        int[] state = getDrawableState();
+        
+        if (mProgressDrawable != null && mProgressDrawable.isStateful()) {
+            mProgressDrawable.setState(state);
+        }
+        
+        if (mIndeterminateDrawable != null && mIndeterminateDrawable.isStateful()) {
+            mIndeterminateDrawable.setState(state);
+        }
+    }
+
+    static class SavedState extends BaseSavedState {
+        int progress;
+        int secondaryProgress;
+        
+        /**
+         * Constructor called from {@link ProgressBar#onSaveInstanceState()}
+         */
+        SavedState(Parcelable superState) {
+            super(superState);
+        }
+        
+        /**
+         * Constructor called from {@link #CREATOR}
+         */
+        private SavedState(Parcel in) {
+            super(in);
+            progress = in.readInt();
+            secondaryProgress = in.readInt();
+        }
+
+        @Override
+        public void writeToParcel(Parcel out, int flags) {
+            super.writeToParcel(out, flags);
+            out.writeInt(progress);
+            out.writeInt(secondaryProgress);
+        }
+
+        public static final Parcelable.Creator<SavedState> CREATOR
+                = new Parcelable.Creator<SavedState>() {
+            public SavedState createFromParcel(Parcel in) {
+                return new SavedState(in);
+            }
+
+            public SavedState[] newArray(int size) {
+                return new SavedState[size];
+            }
+        };
+    }
+
+    @Override
+    public Parcelable onSaveInstanceState() {
+        // Force our ancestor class to save its state
+        Parcelable superState = super.onSaveInstanceState();
+        SavedState ss = new SavedState(superState);
+        
+        ss.progress = mProgress;
+        ss.secondaryProgress = mSecondaryProgress;
+        
+        return ss;
+    }
+
+    @Override
+    public void onRestoreInstanceState(Parcelable state) {
+        SavedState ss = (SavedState) state;
+        super.onRestoreInstanceState(ss.getSuperState());
+        
+        setProgress(ss.progress);
+        setSecondaryProgress(ss.secondaryProgress);
+    }
+}
diff --git a/core/java/android/widget/RadioButton.java b/core/java/android/widget/RadioButton.java
new file mode 100644
index 0000000..14ec8c6
--- /dev/null
+++ b/core/java/android/widget/RadioButton.java
@@ -0,0 +1,72 @@
+/*
+ * 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 android.widget;
+
+import android.content.Context;
+import android.util.AttributeSet;
+
+
+/**
+ * <p>
+ * A radio button is a two-states button that can be either checked or
+ * unchecked. When the radio button is unchecked, the user can press or click it
+ * to check it. However, contrary to a {@link android.widget.CheckBox}, a radio
+ * button cannot be unchecked by the user once checked.
+ * </p>
+ *
+ * <p>
+ * Radio buttons are normally used together in a
+ * {@link android.widget.RadioGroup}. When several radio buttons live inside
+ * a radio group, checking one radio button unchecks all the others.</p>
+ * </p>
+ *
+ * <p><strong>XML attributes</strong></p>
+ * <p> 
+ * See {@link android.R.styleable#CompoundButton CompoundButton Attributes}, 
+ * {@link android.R.styleable#Button Button Attributes}, 
+ * {@link android.R.styleable#TextView TextView Attributes}, 
+ * {@link android.R.styleable#View View Attributes}
+ * </p>
+ */
+public class RadioButton extends CompoundButton {
+    
+    public RadioButton(Context context) {
+        this(context, null);
+    }
+    
+    public RadioButton(Context context, AttributeSet attrs) {
+        this(context, attrs, com.android.internal.R.attr.radioButtonStyle);
+    }
+
+    public RadioButton(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+    }
+
+    /**
+     * {@inheritDoc}
+     * <p>
+     * If the radio button is already checked, this method will not toggle the radio button.
+     */
+    @Override
+    public void toggle() {
+        // we override to prevent toggle when the radio is already
+        // checked (as opposed to check boxes widgets)
+        if (!isChecked()) {
+            super.toggle();
+        }
+    }
+}
diff --git a/core/java/android/widget/RadioGroup.java b/core/java/android/widget/RadioGroup.java
new file mode 100644
index 0000000..393346a
--- /dev/null
+++ b/core/java/android/widget/RadioGroup.java
@@ -0,0 +1,388 @@
+/*
+ * 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 android.widget;
+
+import com.android.internal.R;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.ViewGroup;
+
+
+/**
+ * <p>This class is used to create a multiple-exclusion scope for a set of radio
+ * buttons. Checking one radio button that belongs to a radio group unchecks
+ * any previously checked radio button within the same group.</p>
+ *
+ * <p>Intially, all of the radio buttons are unchecked. While it is not possible
+ * to uncheck a particular radio button, the radio group can be cleared to
+ * remove the checked state.</p>
+ *
+ * <p>The selection is identified by the unique id of the radio button as defined
+ * in the XML layout file.</p>
+ *
+ * <p><strong>XML Attributes</strong></p>
+ * <p>See {@link android.R.styleable#RadioGroup RadioGroup Attributes}, 
+ * {@link android.R.styleable#LinearLayout LinearLayout Attributes},
+ * {@link android.R.styleable#ViewGroup ViewGroup Attributes},
+ * {@link android.R.styleable#View View Attributes}</p>
+ * <p>Also see
+ * {@link android.widget.LinearLayout.LayoutParams LinearLayout.LayoutParams}
+ * for layout attributes.</p>
+ * 
+ * @see RadioButton
+ *
+ */
+public class RadioGroup extends LinearLayout {
+    // holds the checked id; the selection is empty by default
+    private int mCheckedId = -1;
+    // tracks children radio buttons checked state
+    private CompoundButton.OnCheckedChangeListener mChildOnCheckedChangeListener;
+    // when true, mOnCheckedChangeListener discards events
+    private boolean mProtectFromCheckedChange = false;
+    private OnCheckedChangeListener mOnCheckedChangeListener;
+    private PassThroughHierarchyChangeListener mPassThroughListener;
+
+    /**
+     * {@inheritDoc}
+     */
+    public RadioGroup(Context context) {
+        super(context);
+        setOrientation(VERTICAL);
+        init();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public RadioGroup(Context context, AttributeSet attrs) {
+        super(context, attrs);
+
+        // retrieve selected radio button as requested by the user in the
+        // XML layout file
+        TypedArray attributes = context.obtainStyledAttributes(
+                attrs, com.android.internal.R.styleable.RadioGroup, com.android.internal.R.attr.radioButtonStyle, 0);
+
+        int value = attributes.getResourceId(R.styleable.RadioGroup_checkedButton, View.NO_ID);
+        if (value != View.NO_ID) {
+            mCheckedId = value;
+        }
+
+        final int index = attributes.getInt(com.android.internal.R.styleable.RadioGroup_orientation, VERTICAL);
+        setOrientation(index);
+
+        attributes.recycle();
+        init();
+    }
+
+    private void init() {
+        mChildOnCheckedChangeListener = new CheckedStateTracker();
+        mPassThroughListener = new PassThroughHierarchyChangeListener();
+        super.setOnHierarchyChangeListener(mPassThroughListener);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void setOnHierarchyChangeListener(OnHierarchyChangeListener listener) {
+        // the user listener is delegated to our pass-through listener
+        mPassThroughListener.mOnHierarchyChangeListener = listener;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    protected void onFinishInflate() {
+        super.onFinishInflate();
+
+        // checks the appropriate radio button as requested in the XML file
+        if (mCheckedId != -1) {
+            mProtectFromCheckedChange = true;
+            setCheckedStateForView(mCheckedId, true);
+            mProtectFromCheckedChange = false;
+            setCheckedId(mCheckedId);
+        }
+    }
+
+    @Override
+    public void addView(View child, int index, ViewGroup.LayoutParams params) {
+        if (child instanceof RadioButton) {
+            final RadioButton button = (RadioButton) child;
+            if (button.isChecked()) {
+                mProtectFromCheckedChange = true;
+                if (mCheckedId != -1) {
+                    setCheckedStateForView(mCheckedId, false);
+                }
+                mProtectFromCheckedChange = false;
+                setCheckedId(button.getId());
+            }
+        }
+
+        super.addView(child, index, params);
+    }
+
+    /**
+     * <p>Sets the selection to the radio button whose identifier is passed in
+     * parameter. Using -1 as the selection identifier clears the selection;
+     * such an operation is equivalent to invoking {@link #clearCheck()}.</p>
+     *
+     * @param id the unique id of the radio button to select in this group
+     *
+     * @see #getCheckedRadioButtonId()
+     * @see #clearCheck()
+     */
+    public void check(int id) {
+        // don't even bother
+        if (id != -1 && (id == mCheckedId)) {
+            return;
+        }
+
+        if (mCheckedId != -1) {
+            setCheckedStateForView(mCheckedId, false);
+        }
+
+        if (id != -1) {
+            setCheckedStateForView(id, true);
+        }
+
+        setCheckedId(id);
+    }
+
+    private void setCheckedId(int id) {
+        mCheckedId = id;
+        if (mOnCheckedChangeListener != null) {
+            mOnCheckedChangeListener.onCheckedChanged(this, mCheckedId);
+        }
+    }
+
+    private void setCheckedStateForView(int viewId, boolean checked) {
+        View checkedView = findViewById(viewId);
+        if (checkedView != null && checkedView instanceof RadioButton) {
+            ((RadioButton) checkedView).setChecked(checked);
+        }
+    }
+
+    /**
+     * <p>Returns the identifier of the selected radio button in this group.
+     * Upon empty selection, the returned value is -1.</p>
+     *
+     * @return the unique id of the selected radio button in this group
+     *
+     * @see #check(int)
+     * @see #clearCheck()
+     */
+    public int getCheckedRadioButtonId() {
+        return mCheckedId;
+    }
+
+    /**
+     * <p>Clears the selection. When the selection is cleared, no radio button
+     * in this group is selected and {@link #getCheckedRadioButtonId()} returns
+     * null.</p>
+     *
+     * @see #check(int)
+     * @see #getCheckedRadioButtonId()
+     */
+    public void clearCheck() {
+        check(-1);
+    }
+
+    /**
+     * <p>Register a callback to be invoked when the checked radio button
+     * changes in this group.</p>
+     *
+     * @param listener the callback to call on checked state change
+     */
+    public void setOnCheckedChangeListener(OnCheckedChangeListener listener) {
+        mOnCheckedChangeListener = listener;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public LayoutParams generateLayoutParams(AttributeSet attrs) {
+        return new RadioGroup.LayoutParams(getContext(), attrs);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
+        return p instanceof RadioGroup.LayoutParams;
+    }
+
+    @Override
+    protected LinearLayout.LayoutParams generateDefaultLayoutParams() {
+        return new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
+    }
+
+    /**
+     * <p>This set of layout parameters defaults the width and the height of
+     * the children to {@link #WRAP_CONTENT} when they are not specified in the
+     * XML file. Otherwise, this class ussed the value read from the XML file.</p>
+     *
+     * <p>See
+     * {@link android.R.styleable#LinearLayout_Layout LinearLayout Attributes}
+     * for a list of all child view attributes that this class supports.</p>
+     *
+     */
+    public static class LayoutParams extends LinearLayout.LayoutParams {
+        /**
+         * {@inheritDoc}
+         */
+        public LayoutParams(Context c, AttributeSet attrs) {
+            super(c, attrs);
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        public LayoutParams(int w, int h) {
+            super(w, h);
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        public LayoutParams(int w, int h, float initWeight) {
+            super(w, h, initWeight);
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        public LayoutParams(ViewGroup.LayoutParams p) {
+            super(p);
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        public LayoutParams(MarginLayoutParams source) {
+            super(source);
+        }
+
+        /**
+         * <p>Fixes the child's width to
+         * {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT} and the child's
+         * height to  {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT}
+         * when not specified in the XML file.</p>
+         *
+         * @param a the styled attributes set
+         * @param widthAttr the width attribute to fetch
+         * @param heightAttr the height attribute to fetch
+         */
+        @Override
+        protected void setBaseAttributes(TypedArray a,
+                int widthAttr, int heightAttr) {
+
+            if (a.hasValue(widthAttr)) {
+                width = a.getLayoutDimension(widthAttr, "layout_width");
+            } else {
+                width = WRAP_CONTENT;
+            }
+            
+            if (a.hasValue(heightAttr)) {
+                height = a.getLayoutDimension(heightAttr, "layout_height");
+            } else {
+                height = WRAP_CONTENT;
+            }
+        }
+    }
+
+    /**
+     * <p>Interface definition for a callback to be invoked when the checked
+     * radio button changed in this group.</p>
+     */
+    public interface OnCheckedChangeListener {
+        /**
+         * <p>Called when the checked radio button has changed. When the
+         * selection is cleared, checkedId is -1.</p>
+         *
+         * @param group the group in which the checked radio button has changed
+         * @param checkedId the unique identifier of the newly checked radio button
+         */
+        public void onCheckedChanged(RadioGroup group, int checkedId);
+    }
+
+    private class CheckedStateTracker implements CompoundButton.OnCheckedChangeListener {
+        public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
+            // prevents from infinite recursion
+            if (mProtectFromCheckedChange) {
+                return;
+            }
+
+            mProtectFromCheckedChange = true;
+            if (mCheckedId != -1) {
+                setCheckedStateForView(mCheckedId, false);
+            }
+            mProtectFromCheckedChange = false;
+
+            int id = buttonView.getId();
+            setCheckedId(id);
+        }
+    }
+
+    /**
+     * <p>A pass-through listener acts upon the events and dispatches them
+     * to another listener. This allows the table layout to set its own internal
+     * hierarchy change listener without preventing the user to setup his.</p>
+     */
+    private class PassThroughHierarchyChangeListener implements
+            ViewGroup.OnHierarchyChangeListener {
+        private ViewGroup.OnHierarchyChangeListener mOnHierarchyChangeListener;
+
+        /**
+         * {@inheritDoc}
+         */
+        public void onChildViewAdded(View parent, View child) {
+            if (parent == RadioGroup.this && child instanceof RadioButton) {
+                int id = child.getId();
+                // generates an id if it's missing
+                if (id == View.NO_ID) {
+                    id = child.hashCode();
+                    child.setId(id);
+                }
+                ((RadioButton) child).setOnCheckedChangeWidgetListener(
+                        mChildOnCheckedChangeListener);
+            }
+
+            if (mOnHierarchyChangeListener != null) {
+                mOnHierarchyChangeListener.onChildViewAdded(parent, child);
+            }
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        public void onChildViewRemoved(View parent, View child) {
+            if (parent == RadioGroup.this && child instanceof RadioButton) {
+                ((RadioButton) child).setOnCheckedChangeWidgetListener(null);
+            }
+
+            if (mOnHierarchyChangeListener != null) {
+                mOnHierarchyChangeListener.onChildViewRemoved(parent, child);
+            }
+        }
+    }
+}
diff --git a/core/java/android/widget/RatingBar.java b/core/java/android/widget/RatingBar.java
new file mode 100644
index 0000000..ad5ca07
--- /dev/null
+++ b/core/java/android/widget/RatingBar.java
@@ -0,0 +1,317 @@
+/*
+ * Copyright (C) 2007 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 android.widget;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.drawable.shapes.RectShape;
+import android.graphics.drawable.shapes.Shape;
+import android.util.AttributeSet;
+
+import com.android.internal.R;
+
+/**
+ * A RatingBar is an extension of SeekBar and ProgressBar that shows a rating in
+ * stars. The user can touch/drag or use arrow keys to set the rating when using
+ * the default size RatingBar. The smaller RatingBar style (
+ * {@link android.R.attr#ratingBarStyleSmall}) and the larger indicator-only
+ * style ({@link android.R.attr#ratingBarStyleIndicator}) do not support user
+ * interaction and should only be used as indicators.
+ * <p>
+ * When using a RatingBar that supports user interaction, placing widgets to the
+ * left or right of the RatingBar is discouraged.
+ * <p>
+ * The number of stars set (via {@link #setNumStars(int)} or in an XML layout)
+ * will be shown when the layout width is set to wrap content (if another layout
+ * width is set, the results may be unpredictable).
+ * <p>
+ * The secondary progress should not be modified by the client as it is used
+ * internally as the background for a fractionally filled star.
+ * 
+ * @attr ref android.R.styleable#RatingBar_numStars
+ * @attr ref android.R.styleable#RatingBar_rating
+ * @attr ref android.R.styleable#RatingBar_stepSize
+ * @attr ref android.R.styleable#RatingBar_isIndicator
+ */
+public class RatingBar extends AbsSeekBar {
+
+    /**
+     * A callback that notifies clients when the rating has been changed. This
+     * includes changes that were initiated by the user through a touch gesture
+     * or arrow key/trackball as well as changes that were initiated
+     * programmatically.
+     */
+    public interface OnRatingBarChangeListener {
+        
+        /**
+         * Notification that the rating has changed. Clients can use the
+         * fromUser parameter to distinguish user-initiated changes from those
+         * that occurred programmatically. This will not be called continuously
+         * while the user is dragging, only when the user finalizes a rating by
+         * lifting the touch.
+         * 
+         * @param ratingBar The RatingBar whose rating has changed.
+         * @param rating The current rating. This will be in the range
+         *            0..numStars.
+         * @param fromUser True if the rating change was initiated by a user's
+         *            touch gesture or arrow key/horizontal trackbell movement.
+         */
+        void onRatingChanged(RatingBar ratingBar, float rating, boolean fromUser);
+
+    }
+
+    private int mNumStars = 5;
+
+    private int mProgressOnStartTracking;
+    
+    private OnRatingBarChangeListener mOnRatingBarChangeListener;
+    
+    public RatingBar(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+        
+        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.RatingBar,
+                defStyle, 0);
+        final int numStars = a.getInt(R.styleable.RatingBar_numStars, mNumStars);
+        setIsIndicator(a.getBoolean(R.styleable.RatingBar_isIndicator, !mIsUserSeekable));
+        final float rating = a.getFloat(R.styleable.RatingBar_rating, -1);
+        final float stepSize = a.getFloat(R.styleable.RatingBar_stepSize, -1);
+        a.recycle();
+
+        if (numStars > 0 && numStars != mNumStars) {
+            setNumStars(numStars);            
+        }
+        
+        if (stepSize >= 0) {
+            setStepSize(stepSize);
+        } else {
+            setStepSize(0.5f);
+        }
+        
+        if (rating >= 0) {
+            setRating(rating);
+        }
+        
+        // A touch inside a star fill up to that fractional area (slightly more
+        // than 1 so boundaries round up).
+        mTouchProgressOffset = 1.1f;
+    }
+
+    public RatingBar(Context context, AttributeSet attrs) {
+        this(context, attrs, com.android.internal.R.attr.ratingBarStyle);
+    }
+
+    public RatingBar(Context context) {
+        this(context, null);
+    }
+    
+    /**
+     * Sets the listener to be called when the rating changes.
+     * 
+     * @param listener The listener.
+     */
+    public void setOnRatingBarChangeListener(OnRatingBarChangeListener listener) {
+        mOnRatingBarChangeListener = listener;
+    }
+    
+    /**
+     * @return The listener (may be null) that is listening for rating change
+     *         events.
+     */
+    public OnRatingBarChangeListener getOnRatingBarChangeListener() {
+        return mOnRatingBarChangeListener;
+    }
+
+    /**
+     * Whether this rating bar should only be an indicator (thus non-changeable
+     * by the user).
+     * 
+     * @param isIndicator Whether it should be an indicator.
+     */
+    public void setIsIndicator(boolean isIndicator) {
+        mIsUserSeekable = !isIndicator;
+        setFocusable(!isIndicator);
+    }
+    
+    /**
+     * @return Whether this rating bar is only an indicator.
+     */
+    public boolean isIndicator() {
+        return !mIsUserSeekable;
+    }
+    
+    /**
+     * Sets the number of stars to show. In order for these to be shown
+     * properly, it is recommended the layout width of this widget be wrap
+     * content.
+     * 
+     * @param numStars The number of stars.
+     */
+    public void setNumStars(final int numStars) {
+        if (numStars <= 0) {
+            return;
+        }
+        
+        mNumStars = numStars;
+        
+        // This causes the width to change, so re-layout
+        requestLayout();
+    }
+
+    /**
+     * Returns the number of stars shown.
+     * @return The number of stars shown.
+     */
+    public int getNumStars() {
+        return mNumStars;
+    }
+    
+    /**
+     * Sets the rating (the number of stars filled).
+     * 
+     * @param rating The rating to set.
+     */
+    public void setRating(float rating) {
+        setProgress(Math.round(rating * getProgressPerStar()));
+    }
+
+    /**
+     * Gets the current rating (number of stars filled).
+     * 
+     * @return The current rating.
+     */
+    public float getRating() {
+        return getProgress() / getProgressPerStar();        
+    }
+
+    /**
+     * Sets the step size (granularity) of this rating bar.
+     * 
+     * @param stepSize The step size of this rating bar. For example, if
+     *            half-star granularity is wanted, this would be 0.5.
+     */
+    public void setStepSize(float stepSize) {
+        if (stepSize <= 0) {
+            return;
+        }
+        
+        final float newMax = mNumStars / stepSize;
+        final int newProgress = (int) (newMax / getMax() * getProgress());
+        setMax((int) newMax);
+        setProgress(newProgress);
+    }
+
+    /**
+     * Gets the step size of this rating bar.
+     * 
+     * @return The step size.
+     */
+    public float getStepSize() {
+        return (float) getNumStars() / getMax();
+    }
+    
+    /**
+     * @return The amount of progress that fits into a star
+     */
+    private float getProgressPerStar() {
+        if (mNumStars > 0) {
+            return 1f * getMax() / mNumStars;
+        } else {
+            return 1;
+        }
+    }
+
+    @Override
+    Shape getDrawableShape() {
+        // TODO: Once ProgressBar's TODOs are fixed, this won't be needed
+        return new RectShape();
+    }
+
+    @Override
+    void onProgressRefresh(float scale, boolean fromUser) {
+        super.onProgressRefresh(scale, fromUser);
+
+        // Keep secondary progress in sync with primary
+        updateSecondaryProgress(getProgress());
+        
+        if (!fromUser) {
+            // Callback for non-user rating changes
+            dispatchRatingChange(false);
+        }
+    }
+
+    /**
+     * The secondary progress is used to differentiate the background of a
+     * partially filled star. This method keeps the secondary progress in sync
+     * with the progress.
+     * 
+     * @param progress The primary progress level.
+     */
+    private void updateSecondaryProgress(int progress) {
+        final float ratio = getProgressPerStar();
+        if (ratio > 0) {
+            final float progressInStars = progress / ratio;
+            final int secondaryProgress = (int) (Math.ceil(progressInStars) * ratio);
+            setSecondaryProgress(secondaryProgress);
+        }
+    }
+    
+    @Override
+    protected synchronized void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+        
+        if (mSampleTile != null) {
+            // TODO: Once ProgressBar's TODOs are gone, this can be done more
+            // cleanly than mSampleTile
+            final int width = mSampleTile.getWidth() * mNumStars;
+            setMeasuredDimension(resolveSize(width, widthMeasureSpec), mMeasuredHeight);
+        }
+    }
+
+    @Override
+    void onStartTrackingTouch() {
+        mProgressOnStartTracking = getProgress();
+        
+        super.onStartTrackingTouch();
+    }
+
+    @Override
+    void onStopTrackingTouch() {
+        super.onStopTrackingTouch();
+
+        if (getProgress() != mProgressOnStartTracking) {
+            dispatchRatingChange(true);
+        }
+    }
+    
+    void dispatchRatingChange(boolean fromUser) {
+        if (mOnRatingBarChangeListener != null) {
+            mOnRatingBarChangeListener.onRatingChanged(this, getRating(),
+                    fromUser);
+        }
+    }
+
+    @Override
+    public synchronized void setMax(int max) {
+        // Disallow max progress = 0
+        if (max <= 0) {
+            return;
+        }
+        
+        super.setMax(max);
+    }
+    
+}
diff --git a/core/java/android/widget/RelativeLayout.java b/core/java/android/widget/RelativeLayout.java
new file mode 100644
index 0000000..ba63ec3
--- /dev/null
+++ b/core/java/android/widget/RelativeLayout.java
@@ -0,0 +1,954 @@
+/*
+ * 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 android.widget;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.Gravity;
+import android.widget.RemoteViews.RemoteView;
+import android.graphics.Rect;
+import com.android.internal.R;
+
+
+/**
+ * A Layout where the positions of the children can be described in relation to each other or to the
+ * parent. For the sake of efficiency, the relations between views are evaluated in one pass, so if
+ * view Y is dependent on the position of view X, make sure the view X comes first in the layout.
+ * 
+ * <p>
+ * Note that you cannot have a circular dependency between the size of the RelativeLayout and the
+ * position of its children. For example, you cannot have a RelativeLayout whose height is set to
+ * {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT WRAP_CONTENT} and a child set to
+ * {@link #ALIGN_PARENT_BOTTOM}.
+ * </p>
+ * 
+ * <p>
+ * Also see {@link android.widget.RelativeLayout.LayoutParams RelativeLayout.LayoutParams} for
+ * layout attributes
+ * </p>
+ * 
+ * @attr ref android.R.styleable#RelativeLayout_gravity
+ * @attr ref android.R.styleable#RelativeLayout_ignoreGravity
+ */
+@RemoteView
+public class RelativeLayout extends ViewGroup {
+    public static final int TRUE = -1;
+
+    /**
+     * Rule that aligns a child's right edge with another child's left edge.
+     */
+    public static final int LEFT_OF                  = 0;
+    /**
+     * Rule that aligns a child's left edge with another child's right edge.
+     */
+    public static final int RIGHT_OF                 = 1;
+    /**
+     * Rule that aligns a child's bottom edge with another child's top edge.
+     */
+    public static final int ABOVE                    = 2;
+    /**
+     * Rule that aligns a child's top edge with another child's bottom edge.
+     */
+    public static final int BELOW                    = 3;
+
+    /**
+     * Rule that aligns a child's baseline with another child's baseline.
+     */
+    public static final int ALIGN_BASELINE           = 4;
+    /**
+     * Rule that aligns a child's left edge with another child's left edge.
+     */
+    public static final int ALIGN_LEFT               = 5;
+    /**
+     * Rule that aligns a child's top edge with another child's top edge.
+     */
+    public static final int ALIGN_TOP                = 6;
+    /**
+     * Rule that aligns a child's right edge with another child's right edge.
+     */
+    public static final int ALIGN_RIGHT              = 7;
+    /**
+     * Rule that aligns a child's bottom edge with another child's bottom edge.
+     */
+    public static final int ALIGN_BOTTOM             = 8;
+
+    /**
+     * Rule that aligns the child's left edge with its RelativeLayout
+     * parent's left edge.
+     */
+    public static final int ALIGN_PARENT_LEFT        = 9;
+    /**
+     * Rule that aligns the child's top edge with its RelativeLayout
+     * parent's top edge.
+     */
+    public static final int ALIGN_PARENT_TOP         = 10;
+    /**
+     * Rule that aligns the child's right edge with its RelativeLayout
+     * parent's right edge.
+     */
+    public static final int ALIGN_PARENT_RIGHT       = 11;
+    /**
+     * Rule that aligns the child's bottom edge with its RelativeLayout
+     * parent's bottom edge.
+     */
+    public static final int ALIGN_PARENT_BOTTOM      = 12;
+
+    /**
+     * Rule that centers the child with respect to the bounds of its
+     * RelativeLayout parent.
+     */
+    public static final int CENTER_IN_PARENT         = 13;
+    /**
+     * Rule that centers the child horizontally with respect to the
+     * bounds of its RelativeLayout parent.
+     */
+    public static final int CENTER_HORIZONTAL        = 14;
+    /**
+     * Rule that centers the child vertically with respect to the
+     * bounds of its RelativeLayout parent.
+     */
+    public static final int CENTER_VERTICAL          = 15;
+
+    private static final int VERB_COUNT              = 16;
+
+    private View mBaselineView = null;
+    private boolean mHasBaselineAlignedChild;
+
+    private int mGravity = Gravity.LEFT | Gravity.TOP;
+    private final Rect mContentBounds = new Rect();
+    private final Rect mSelfBounds = new Rect();
+    private int mIgnoreGravity;
+
+    public RelativeLayout(Context context) {
+        super(context);
+    }
+
+    public RelativeLayout(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        initFromAttributes(context, attrs);
+    }
+
+    public RelativeLayout(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+        initFromAttributes(context, attrs);
+    }
+
+    private void initFromAttributes(Context context, AttributeSet attrs) {
+        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.RelativeLayout);
+        mIgnoreGravity = a.getResourceId(R.styleable.RelativeLayout_ignoreGravity, View.NO_ID);
+        mGravity = a.getInt(R.styleable.RelativeLayout_gravity, mGravity);
+        a.recycle();
+    }
+
+    /**
+     * Defines which View is ignored when the gravity is applied. This setting has no
+     * effect if the gravity is <code>Gravity.LEFT | Gravity.TOP</code>.
+     *
+     * @param viewId The id of the View to be ignored by gravity, or 0 if no View
+     *        should be ignored.
+     *
+     * @see #setGravity(int)
+     *
+     * @attr ref android.R.styleable#RelativeLayout_ignoreGravity
+     */
+    @android.view.RemotableViewMethod
+    public void setIgnoreGravity(int viewId) {
+        mIgnoreGravity = viewId;
+    }
+
+    /**
+     * Describes how the child views are positioned. Defaults to
+     * <code>Gravity.LEFT | Gravity.TOP</code>.
+     *
+     * @param gravity See {@link android.view.Gravity}
+     *
+     * @see #setHorizontalGravity(int)
+     * @see #setVerticalGravity(int)
+     *
+     * @attr ref android.R.styleable#RelativeLayout_gravity
+     */
+    @android.view.RemotableViewMethod
+    public void setGravity(int gravity) {
+        if (mGravity != gravity) {
+            if ((gravity & Gravity.HORIZONTAL_GRAVITY_MASK) == 0) {
+                gravity |= Gravity.LEFT;
+            }
+
+            if ((gravity & Gravity.VERTICAL_GRAVITY_MASK) == 0) {
+                gravity |= Gravity.TOP;
+            }
+
+            mGravity = gravity;
+            requestLayout();
+        }
+    }
+
+    @android.view.RemotableViewMethod
+    public void setHorizontalGravity(int horizontalGravity) {
+        final int gravity = horizontalGravity & Gravity.HORIZONTAL_GRAVITY_MASK;
+        if ((mGravity & Gravity.HORIZONTAL_GRAVITY_MASK) != gravity) {
+            mGravity = (mGravity & ~Gravity.HORIZONTAL_GRAVITY_MASK) | gravity;
+            requestLayout();
+        }
+    }
+
+    @android.view.RemotableViewMethod
+    public void setVerticalGravity(int verticalGravity) {
+        final int gravity = verticalGravity & Gravity.VERTICAL_GRAVITY_MASK;
+        if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != gravity) {
+            mGravity = (mGravity & ~Gravity.VERTICAL_GRAVITY_MASK) | gravity;
+            requestLayout();
+        }
+    }
+
+    @Override
+    public int getBaseline() {
+        return mBaselineView != null ? mBaselineView.getBaseline() : super.getBaseline();
+    }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        int myWidth = -1;
+        int myHeight = -1;
+
+        int width = 0;
+        int height = 0;
+
+        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
+        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
+        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
+        int heightSize = MeasureSpec.getSize(heightMeasureSpec);
+
+        // Record our dimensions if they are known;
+        if (widthMode != MeasureSpec.UNSPECIFIED) {
+            myWidth = widthSize;
+        }
+
+        if (heightMode != MeasureSpec.UNSPECIFIED) {
+            myHeight = heightSize;
+        }
+
+        if (widthMode == MeasureSpec.EXACTLY) {
+            width = myWidth;
+        }
+
+        if (heightMode == MeasureSpec.EXACTLY) {
+            height = myHeight;
+        }
+
+        int len = this.getChildCount();
+        mHasBaselineAlignedChild = false;
+
+        View ignore = null;
+        int gravity = mGravity & Gravity.HORIZONTAL_GRAVITY_MASK;
+        final boolean horizontalGravity = gravity != Gravity.LEFT && gravity != 0;
+        gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
+        final boolean verticalGravity = gravity != Gravity.TOP && gravity != 0;
+
+        int left = Integer.MAX_VALUE;
+        int top = Integer.MAX_VALUE;
+        int right = Integer.MIN_VALUE;
+        int bottom = Integer.MIN_VALUE;
+
+        if ((horizontalGravity || verticalGravity) && mIgnoreGravity != View.NO_ID) {
+            ignore = findViewById(mIgnoreGravity);
+        }
+
+        for (int i = 0; i < len; i++) {
+            View child = getChildAt(i);
+            if (child.getVisibility() != GONE) {
+                LayoutParams params = (LayoutParams) child.getLayoutParams();
+                applySizeRules(params, myWidth, myHeight);
+                measureChild(child, params, myWidth, myHeight);
+                positionChild(child, params, myWidth, myHeight);
+
+                if (widthMode != MeasureSpec.EXACTLY) {
+                    width = Math.max(width, params.mRight);
+                }
+                if (heightMode != MeasureSpec.EXACTLY) {
+                    height = Math.max(height, params.mBottom);
+                }
+
+                if (child != ignore || verticalGravity) {
+                    left = Math.min(left, params.mLeft - params.leftMargin);
+                    top = Math.min(top, params.mTop - params.topMargin);
+                }
+
+                if (child != ignore || horizontalGravity) {
+                    right = Math.max(right, params.mRight + params.rightMargin);
+                    bottom = Math.max(bottom, params.mBottom + params.bottomMargin);
+                }
+            }
+        }
+
+        if (mHasBaselineAlignedChild) {
+            for (int i = 0; i < len; i++) {
+                View child = getChildAt(i);
+                if (child.getVisibility() != GONE) {
+                    LayoutParams params = (LayoutParams) child.getLayoutParams();
+                    alignBaseline(child, params);
+
+                    if (child != ignore || verticalGravity) {
+                    left = Math.min(left, params.mLeft - params.leftMargin);
+                    top = Math.min(top, params.mTop - params.topMargin);
+                    }
+
+                    if (child != ignore || horizontalGravity) {
+                        right = Math.max(right, params.mRight + params.rightMargin);
+                        bottom = Math.max(bottom, params.mBottom + params.bottomMargin);
+                    }
+                }
+            }
+        }
+
+        if (widthMode != MeasureSpec.EXACTLY) {
+            // Width already has left padding in it since it was calculated by looking at 
+            // the right of each child view
+            width += mPaddingRight;
+
+            if (mLayoutParams.width >= 0) {
+                width = Math.max(width, mLayoutParams.width);
+            }
+
+            width = Math.max(width, getSuggestedMinimumWidth());
+            width = resolveSize(width, widthMeasureSpec);
+        }
+        if (heightMode != MeasureSpec.EXACTLY) {
+            // Height already has top padding in it since it was calculated by looking at 
+            // the bottom of each child view
+            height += mPaddingBottom;
+
+            if (mLayoutParams.height >= 0) {
+                height = Math.max(height, mLayoutParams.height);
+            }
+
+            height = Math.max(height, getSuggestedMinimumHeight());
+            height = resolveSize(height, heightMeasureSpec);
+        }
+
+        if (horizontalGravity || verticalGravity) {
+            final Rect selfBounds = mSelfBounds;
+            selfBounds.set(mPaddingLeft, mPaddingTop, width - mPaddingRight,
+                    height - mPaddingBottom);
+
+            final Rect contentBounds = mContentBounds;
+            Gravity.apply(mGravity, right - left, bottom - top, selfBounds, contentBounds);
+
+            final int horizontalOffset = contentBounds.left - left;
+            final int verticalOffset = contentBounds.top - top;
+            if (horizontalOffset != 0 || verticalOffset != 0) {
+                for (int i = 0; i < len; i++) {
+                    View child = getChildAt(i);
+                    if (child.getVisibility() != GONE && child != ignore) {
+                        LayoutParams params = (LayoutParams) child.getLayoutParams();
+                        params.mLeft += horizontalOffset;
+                        params.mRight += horizontalOffset;
+                        params.mTop += verticalOffset;
+                        params.mBottom += verticalOffset;
+                    }
+                }
+            }
+        }
+
+        setMeasuredDimension(width, height);
+    }
+
+    private void alignBaseline(View child, LayoutParams params) {
+        int[] rules = params.getRules();
+        int anchorBaseline = getRelatedViewBaseline(rules, ALIGN_BASELINE);
+
+        if (anchorBaseline != -1) {
+            LayoutParams anchorParams = getRelatedViewParams(rules, ALIGN_BASELINE);
+            if (anchorParams != null) {
+                int offset = anchorParams.mTop + anchorBaseline;
+                int baseline = child.getBaseline();
+                if (baseline != -1) {
+                    offset -= baseline;
+                }
+                int height = params.mBottom - params.mTop;
+                params.mTop = offset;
+                params.mBottom = params.mTop + height;
+            }
+        }
+
+        if (mBaselineView == null) {
+            mBaselineView = child;
+        } else {
+            LayoutParams lp = (LayoutParams) mBaselineView.getLayoutParams();
+            if (params.mTop < lp.mTop || (params.mTop == lp.mTop && params.mLeft < lp.mLeft)) {
+                mBaselineView = child;
+            }
+        }
+    }
+
+    /**
+     * Measure a child. The child should have left, top, right and bottom information
+     * stored in its LayoutParams. If any of these values is -1 it means that the view
+     * can extend up to the corresponding edge.
+     *
+     * @param child Child to measure
+     * @param params LayoutParams associated with child
+     * @param myWidth Width of the the RelativeLayout
+     * @param myHeight Height of the RelativeLayout
+     */
+    private void measureChild(View child, LayoutParams params, int myWidth,
+            int myHeight) {
+
+        int childWidthMeasureSpec = getChildMeasureSpec(params.mLeft,
+                params.mRight, params.width,
+                params.leftMargin, params.rightMargin,
+                mPaddingLeft, mPaddingRight,
+                myWidth);
+        int childHeightMeasureSpec = getChildMeasureSpec(params.mTop,
+                params.mBottom, params.height,
+                params.topMargin, params.bottomMargin,
+                mPaddingTop, mPaddingBottom,
+                myHeight);
+        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
+    }
+
+    /**
+     * Get a measure spec that accounts for all of the constraints on this view.
+     * This includes size contstraints imposed by the RelativeLayout as well as
+     * the View's desired dimension.
+     *
+     * @param childStart The left or top field of the child's layout params
+     * @param childEnd The right or bottom field of the child's layout params
+     * @param childSize The child's desired size (the width or height field of
+     *        the child's layout params)
+     * @param startMargin The left or top margin
+     * @param endMargin The right or bottom margin
+     * @param startPadding mPaddingLeft or mPaddingTop
+     * @param endPadding mPaddingRight or mPaddingBottom
+     * @param mySize The width or height of this view (the RelativeLayout)
+     * @return MeasureSpec for the child
+     */
+    private int getChildMeasureSpec(int childStart, int childEnd,
+            int childSize, int startMargin, int endMargin, int startPadding,
+            int endPadding, int mySize) {
+        int childSpecMode = 0;
+        int childSpecSize = 0;
+
+        // Figure out start and end bounds.
+        int tempStart = childStart;
+        int tempEnd = childEnd;
+
+        // If the view did not express a layout constraint for an edge, use
+        // view's margins and our padding
+        if (tempStart < 0) {
+            tempStart = startPadding + startMargin;
+        }
+        if (tempEnd < 0) {
+            tempEnd = mySize - endPadding - endMargin;
+        }
+
+        // Figure out maximum size available to this view
+        int maxAvailable = tempEnd - tempStart;
+
+        if (childStart >= 0 && childEnd >= 0) {
+            // Constraints fixed both edges, so child must be an exact size
+            childSpecMode = MeasureSpec.EXACTLY;
+            childSpecSize = maxAvailable;
+        } else {
+            if (childSize >= 0) {
+                // Child wanted an exact size. Give as much as possible
+                childSpecMode = MeasureSpec.EXACTLY;
+
+                if (maxAvailable >= 0) {
+                    // We have a maxmum size in this dimension.
+                    childSpecSize = Math.min(maxAvailable, childSize);
+                } else {
+                    // We can grow in this dimension.
+                    childSpecSize = childSize;
+                }
+            } else if (childSize == LayoutParams.FILL_PARENT) {
+                // Child wanted to be as big as possible. Give all availble
+                // space
+                childSpecMode = MeasureSpec.EXACTLY;
+                childSpecSize = maxAvailable;
+            } else if (childSize == LayoutParams.WRAP_CONTENT) {
+                // Child wants to wrap content. Use AT_MOST
+                // to communicate available space if we know
+                // our max size
+                if (maxAvailable >= 0) {
+                    // We have a maxmum size in this dimension.
+                    childSpecMode = MeasureSpec.AT_MOST;
+                    childSpecSize = maxAvailable;
+                } else {
+                    // We can grow in this dimension. Child can be as big as it
+                    // wants
+                    childSpecMode = MeasureSpec.UNSPECIFIED;
+                    childSpecSize = 0;
+                }
+            }
+        }
+
+        return MeasureSpec.makeMeasureSpec(childSpecSize, childSpecMode);
+    }
+
+    /**
+     * After the child has been measured, assign it a position. Some views may
+     * already have final values for l,t,r,b. Others may have one or both edges
+     * unfixed (i.e. set to -1) in each dimension. These will get positioned
+     * based on which edge is fixed, the view's desired dimension, and whether
+     * or not it is centered.
+     *
+     * @param child Child to position
+     * @param params LayoutParams associated with child
+     * @param myWidth Width of the the RelativeLayout
+     * @param myHeight Height of the RelativeLayout
+     */
+    private void positionChild(View child, LayoutParams params, int myWidth, int myHeight) {
+        int[] rules = params.getRules();
+
+        if (params.mLeft < 0 && params.mRight >= 0) {
+            // Right is fixed, but left varies
+            params.mLeft = params.mRight - child.getMeasuredWidth();
+        } else if (params.mLeft >= 0 && params.mRight < 0) {
+            // Left is fixed, but right varies
+            params.mRight = params.mLeft + child.getMeasuredWidth();
+        } else if (params.mLeft < 0 && params.mRight < 0) {
+            // Both left and right vary
+            if (0 != rules[CENTER_IN_PARENT] || 0 != rules[CENTER_HORIZONTAL]) {
+                centerHorizontal(child, params, myWidth);
+            } else {
+                params.mLeft = mPaddingLeft + params.leftMargin;
+                params.mRight = params.mLeft + child.getMeasuredWidth();
+            }
+        }
+
+        if (params.mTop < 0 && params.mBottom >= 0) {
+            // Bottom is fixed, but top varies
+            params.mTop = params.mBottom - child.getMeasuredHeight();
+        } else if (params.mTop >= 0 && params.mBottom < 0) {
+            // Top is fixed, but bottom varies
+            params.mBottom = params.mTop + child.getMeasuredHeight();
+        } else if (params.mTop < 0 && params.mBottom < 0) {
+            // Both top and bottom vary
+            if (0 != rules[CENTER_IN_PARENT] || 0 != rules[CENTER_VERTICAL]) {
+                centerVertical(child, params, myHeight);
+            } else {
+                params.mTop = mPaddingTop + params.topMargin;
+                params.mBottom = params.mTop + child.getMeasuredHeight();
+            }
+        }
+    }
+
+    /**
+     * Set l,t,r,b values in the LayoutParams for one view based on its layout rules.
+     * Big assumption #1: All antecedents of this view have been sized & positioned
+     * Big assumption #2: The dimensions of the parent view (the RelativeLayout)
+     * are already known if they are needed.
+     *
+     * @param childParams LayoutParams for the view being positioned
+     * @param myWidth Width of the the RelativeLayout
+     * @param myHeight Height of the RelativeLayout
+     */
+    private void applySizeRules(LayoutParams childParams, int myWidth, int myHeight) {
+        int[] rules = childParams.getRules();
+        RelativeLayout.LayoutParams anchorParams;
+
+        // -1 indicated a "soft requirement" in that direction. For example:
+        // left=10, right=-1 means the view must start at 10, but can go as far as it wants to the right
+        // left =-1, right=10 means the view must end at 10, but can go as far as it wants to the left
+        // left=10, right=20 means the left and right ends are both fixed
+        childParams.mLeft = -1;
+        childParams.mRight = -1;
+
+        anchorParams = getRelatedViewParams(rules, LEFT_OF);
+        if (anchorParams != null) {
+            childParams.mRight = anchorParams.mLeft - (anchorParams.leftMargin +
+                    childParams.rightMargin);
+        } else if (childParams.alignWithParent && rules[LEFT_OF] != 0) {
+            if (myWidth >= 0) {
+                childParams.mRight = myWidth - mPaddingRight - childParams.rightMargin;
+            } else {
+                // FIXME uh oh...
+            }
+        }
+
+        anchorParams = getRelatedViewParams(rules, RIGHT_OF);
+        if (anchorParams != null) {
+            childParams.mLeft = anchorParams.mRight + (anchorParams.rightMargin +
+                    childParams.leftMargin);
+        } else if (childParams.alignWithParent && rules[RIGHT_OF] != 0) {
+            childParams.mLeft = mPaddingLeft + childParams.leftMargin;
+        }
+
+        anchorParams = getRelatedViewParams(rules, ALIGN_LEFT);
+        if (anchorParams != null) {
+            childParams.mLeft = anchorParams.mLeft + childParams.leftMargin;
+        } else if (childParams.alignWithParent && rules[ALIGN_LEFT] != 0) {
+            childParams.mLeft = mPaddingLeft + childParams.leftMargin;
+        }
+
+        anchorParams = getRelatedViewParams(rules, ALIGN_RIGHT);
+        if (anchorParams != null) {
+            childParams.mRight = anchorParams.mRight - childParams.rightMargin;
+        } else if (childParams.alignWithParent && rules[ALIGN_RIGHT] != 0) {
+            if (myWidth >= 0) {
+                childParams.mRight = myWidth - mPaddingRight - childParams.rightMargin;
+            } else {
+                // FIXME uh oh...
+            }
+        }
+
+        if (0 != rules[ALIGN_PARENT_LEFT]) {
+            childParams.mLeft = mPaddingLeft + childParams.leftMargin;
+        }
+
+        if (0 != rules[ALIGN_PARENT_RIGHT]) {
+            if (myWidth >= 0) {
+                childParams.mRight = myWidth - mPaddingRight - childParams.rightMargin;
+            } else {
+                // FIXME uh oh...
+            }
+        }
+
+        childParams.mTop = -1;
+        childParams.mBottom = -1;
+
+        anchorParams = getRelatedViewParams(rules, ABOVE);
+        if (anchorParams != null) {
+            childParams.mBottom = anchorParams.mTop - (anchorParams.topMargin +
+                    childParams.bottomMargin);
+        } else if (childParams.alignWithParent && rules[ABOVE] != 0) {
+            if (myHeight >= 0) {
+                childParams.mBottom = myHeight - mPaddingBottom - childParams.bottomMargin;
+            } else {
+                // FIXME uh oh...
+            }
+        }
+
+        anchorParams = getRelatedViewParams(rules, BELOW);
+        if (anchorParams != null) {
+            childParams.mTop = anchorParams.mBottom + (anchorParams.bottomMargin +
+                    childParams.topMargin);
+        } else if (childParams.alignWithParent && rules[BELOW] != 0) {
+            childParams.mTop = mPaddingTop + childParams.topMargin;
+        }
+
+        anchorParams = getRelatedViewParams(rules, ALIGN_TOP);
+        if (anchorParams != null) {
+            childParams.mTop = anchorParams.mTop + childParams.topMargin;
+        } else if (childParams.alignWithParent && rules[ALIGN_TOP] != 0) {
+            childParams.mTop = mPaddingTop + childParams.topMargin;
+        }
+
+        anchorParams = getRelatedViewParams(rules, ALIGN_BOTTOM);
+        if (anchorParams != null) {
+            childParams.mBottom = anchorParams.mBottom - childParams.bottomMargin;
+        } else if (childParams.alignWithParent && rules[ALIGN_BOTTOM] != 0) {
+            if (myHeight >= 0) {
+                childParams.mBottom = myHeight - mPaddingBottom - childParams.bottomMargin;
+            } else {
+                // FIXME uh oh...
+            }
+        }
+
+        if (0 != rules[ALIGN_PARENT_TOP]) {
+            childParams.mTop = mPaddingTop + childParams.topMargin;
+        }
+
+        if (0 != rules[ALIGN_PARENT_BOTTOM]) {
+            if (myHeight >= 0) {
+                childParams.mBottom = myHeight - mPaddingBottom - childParams.bottomMargin;
+            } else {
+                // FIXME uh oh...
+            }
+        }
+
+        if (rules[ALIGN_BASELINE] != 0) {
+            mHasBaselineAlignedChild = true;
+        }
+    }
+
+    private View getRelatedView(int[] rules, int relation) {
+        int id = rules[relation];
+        if (id != 0) {
+            View v = findViewById(id);
+            if (v == null) {
+                return null;
+            }
+
+            // Find the first non-GONE view up the chain
+            while (v.getVisibility() == View.GONE) {
+                rules = ((LayoutParams) v.getLayoutParams()).getRules();
+                v = v.findViewById(rules[relation]);
+                if (v == null) {
+                    return null;
+                }
+            }
+
+            return v;
+        }
+
+        return null;
+    }
+
+    private LayoutParams getRelatedViewParams(int[] rules, int relation) {
+        View v = getRelatedView(rules, relation);
+        if (v != null) {
+            ViewGroup.LayoutParams params = v.getLayoutParams();
+            if (params instanceof LayoutParams) {
+                return (LayoutParams) v.getLayoutParams();
+            }
+        }
+        return null;
+    }
+
+    private int getRelatedViewBaseline(int[] rules, int relation) {
+        View v = getRelatedView(rules, relation);
+        if (v != null) {
+            return v.getBaseline();
+        }
+        return -1;
+    }
+
+    private void centerHorizontal(View child, LayoutParams params, int myWidth) {
+        int childWidth = child.getMeasuredWidth();
+        int left = (myWidth - childWidth) / 2;
+
+        params.mLeft = left;
+        params.mRight = left + childWidth;
+    }
+
+    private void centerVertical(View child, LayoutParams params, int myHeight) {
+        int childHeight = child.getMeasuredHeight();
+        int top = (myHeight - childHeight) / 2;
+
+        params.mTop = top;
+        params.mBottom = top + childHeight;
+    }
+
+    @Override
+    protected void onLayout(boolean changed, int l, int t, int r, int b) {
+        //  The layout has actually already been performed and the positions
+        //  cached.  Apply the cached values to the children.
+        int count = getChildCount();
+
+        for (int i = 0; i < count; i++) {
+            View child = getChildAt(i);
+            if (child.getVisibility() != GONE) {
+                RelativeLayout.LayoutParams st =
+                        (RelativeLayout.LayoutParams) child.getLayoutParams();
+                child.layout(st.mLeft, st.mTop, st.mRight, st.mBottom);
+
+            }
+        }
+    }
+
+    @Override
+    public LayoutParams generateLayoutParams(AttributeSet attrs) {
+        return new RelativeLayout.LayoutParams(getContext(), attrs);
+    }
+
+    /**
+     * Returns a set of layout parameters with a width of
+     * {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT},
+     * a height of {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT} and no spanning.
+     */
+    @Override
+    protected ViewGroup.LayoutParams generateDefaultLayoutParams() {
+        return new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
+    }
+
+    // Override to allow type-checking of LayoutParams.
+    @Override
+    protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
+        return p instanceof RelativeLayout.LayoutParams;
+    }
+
+    @Override
+    protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
+        return new LayoutParams(p);
+    }
+
+    /**
+     * Per-child layout information associated with RelativeLayout.
+     *
+     * @attr ref android.R.styleable#RelativeLayout_Layout_layout_alignWithParentIfMissing
+     * @attr ref android.R.styleable#RelativeLayout_Layout_layout_toLeftOf
+     * @attr ref android.R.styleable#RelativeLayout_Layout_layout_toRightOf
+     * @attr ref android.R.styleable#RelativeLayout_Layout_layout_above
+     * @attr ref android.R.styleable#RelativeLayout_Layout_layout_below
+     * @attr ref android.R.styleable#RelativeLayout_Layout_layout_alignBaseline
+     * @attr ref android.R.styleable#RelativeLayout_Layout_layout_alignLeft
+     * @attr ref android.R.styleable#RelativeLayout_Layout_layout_alignTop
+     * @attr ref android.R.styleable#RelativeLayout_Layout_layout_alignRight
+     * @attr ref android.R.styleable#RelativeLayout_Layout_layout_alignBottom
+     * @attr ref android.R.styleable#RelativeLayout_Layout_layout_alignParentLeft
+     * @attr ref android.R.styleable#RelativeLayout_Layout_layout_alignParentTop
+     * @attr ref android.R.styleable#RelativeLayout_Layout_layout_alignParentRight
+     * @attr ref android.R.styleable#RelativeLayout_Layout_layout_alignParentBottom
+     * @attr ref android.R.styleable#RelativeLayout_Layout_layout_centerInParent
+     * @attr ref android.R.styleable#RelativeLayout_Layout_layout_centerHorizontal
+     * @attr ref android.R.styleable#RelativeLayout_Layout_layout_centerVertical
+     */
+    public static class LayoutParams extends ViewGroup.MarginLayoutParams {
+        private int[] mRules = new int[VERB_COUNT];
+        private int mLeft, mTop, mRight, mBottom;
+
+        /**
+         * When true, uses the parent as the anchor if the anchor doesn't exist or if
+         * the anchor's visibility is GONE.
+         */
+        public boolean alignWithParent;
+
+        public LayoutParams(Context c, AttributeSet attrs) {
+            super(c, attrs);
+
+            TypedArray a = c.obtainStyledAttributes(attrs,
+                    com.android.internal.R.styleable.RelativeLayout_Layout);
+
+            final int[] rules = mRules;
+
+            final int N = a.getIndexCount();
+            for (int i = 0; i < N; i++) {
+                int attr = a.getIndex(i);
+                switch (attr) {
+                    case com.android.internal.R.styleable.RelativeLayout_Layout_layout_alignWithParentIfMissing:
+                        alignWithParent = a.getBoolean(attr, false);
+                        break;
+                    case com.android.internal.R.styleable.RelativeLayout_Layout_layout_toLeftOf:
+                        rules[LEFT_OF] = a.getResourceId(attr, 0);
+                        break;
+                    case com.android.internal.R.styleable.RelativeLayout_Layout_layout_toRightOf:
+                        rules[RIGHT_OF] = a.getResourceId(attr, 0);
+                        break;
+                    case com.android.internal.R.styleable.RelativeLayout_Layout_layout_above:
+                        rules[ABOVE] = a.getResourceId(attr, 0);
+                        break;
+                    case com.android.internal.R.styleable.RelativeLayout_Layout_layout_below:
+                        rules[BELOW] = a.getResourceId(attr, 0);
+                        break;
+                    case com.android.internal.R.styleable.RelativeLayout_Layout_layout_alignBaseline:
+                        rules[ALIGN_BASELINE] = a.getResourceId(attr, 0);
+                        break;
+                    case com.android.internal.R.styleable.RelativeLayout_Layout_layout_alignLeft:
+                        rules[ALIGN_LEFT] = a.getResourceId(attr, 0);
+                        break;
+                    case com.android.internal.R.styleable.RelativeLayout_Layout_layout_alignTop:
+                        rules[ALIGN_TOP] = a.getResourceId(attr, 0);
+                        break;
+                    case com.android.internal.R.styleable.RelativeLayout_Layout_layout_alignRight:
+                        rules[ALIGN_RIGHT] = a.getResourceId(attr, 0);
+                        break;
+                    case com.android.internal.R.styleable.RelativeLayout_Layout_layout_alignBottom:
+                        rules[ALIGN_BOTTOM] = a.getResourceId(attr, 0);
+                        break;
+                    case com.android.internal.R.styleable.RelativeLayout_Layout_layout_alignParentLeft:
+                        rules[ALIGN_PARENT_LEFT] = a.getBoolean(attr, false) ? TRUE : 0;
+                        break;
+                    case com.android.internal.R.styleable.RelativeLayout_Layout_layout_alignParentTop:
+                        rules[ALIGN_PARENT_TOP] = a.getBoolean(attr, false) ? TRUE : 0;
+                        break;
+                    case com.android.internal.R.styleable.RelativeLayout_Layout_layout_alignParentRight:
+                        rules[ALIGN_PARENT_RIGHT] = a.getBoolean(attr, false) ? TRUE : 0;
+                        break;
+                    case com.android.internal.R.styleable.RelativeLayout_Layout_layout_alignParentBottom:
+                        rules[ALIGN_PARENT_BOTTOM] = a.getBoolean(attr, false) ? TRUE : 0;
+                        break;
+                    case com.android.internal.R.styleable.RelativeLayout_Layout_layout_centerInParent:
+                        rules[CENTER_IN_PARENT] = a.getBoolean(attr, false) ? TRUE : 0;
+                        break;
+                    case com.android.internal.R.styleable.RelativeLayout_Layout_layout_centerHorizontal:
+                        rules[CENTER_HORIZONTAL] = a.getBoolean(attr, false) ? TRUE : 0;
+                        break;
+                    case com.android.internal.R.styleable.RelativeLayout_Layout_layout_centerVertical:
+                        rules[CENTER_VERTICAL] = a.getBoolean(attr, false) ? TRUE : 0;
+                       break;
+                }
+            }
+
+            a.recycle();
+        }
+
+        public LayoutParams(int w, int h) {
+            super(w, h);
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        public LayoutParams(ViewGroup.LayoutParams source) {
+            super(source);
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        public LayoutParams(ViewGroup.MarginLayoutParams source) {
+            super(source);
+        }
+
+        @Override
+        public String debug(String output) {
+            return output + "ViewGroup.LayoutParams={ width=" + sizeToString(width) +
+                    ", height=" + sizeToString(height) + " }";
+        }
+
+        /**
+         * Adds a layout rule to be interpreted by the RelativeLayout. This
+         * method should only be used for constraints that don't refer to another sibling
+         * (e.g., CENTER_IN_PARENT) or take a boolean value ({@link RelativeLayout#TRUE}
+         * for true or - for false). To specify a verb that takes a subject, use
+         * {@link #addRule(int, int)} instead.
+         *
+         * @param verb One of the verbs defined by
+         *        {@link android.widget.RelativeLayout RelativeLayout}, such as
+         *        ALIGN_WITH_PARENT_LEFT.
+         * @see #addRule(int, int)
+         */
+        public void addRule(int verb) {
+            mRules[verb] = TRUE;
+        }
+
+        /**
+         * Adds a layout rule to be interpreted by the RelativeLayout. Use this for
+         * verbs that take a target, such as a sibling (ALIGN_RIGHT) or a boolean
+         * value (VISIBLE).
+         *
+         * @param verb One of the verbs defined by
+         *        {@link android.widget.RelativeLayout RelativeLayout}, such as
+         *         ALIGN_WITH_PARENT_LEFT.
+         * @param anchor The id of another view to use as an anchor,
+         *        or a boolean value(represented as {@link RelativeLayout#TRUE})
+         *        for true or 0 for false).  For verbs that don't refer to another sibling
+         *        (for example, ALIGN_WITH_PARENT_BOTTOM) just use -1.
+         * @see #addRule(int)
+         */
+        public void addRule(int verb, int anchor) {
+            mRules[verb] = anchor;
+        }
+
+        /**
+         * Retrieves a complete list of all supported rules, where the index is the rule
+         * verb, and the element value is the value specified, or "false" if it was never
+         * set.
+         *
+         * @return the supported rules
+         * @see #addRule(int, int)
+         */
+        public int[] getRules() {
+            return mRules;
+        }
+    }
+}
diff --git a/core/java/android/widget/RemoteViews.aidl b/core/java/android/widget/RemoteViews.aidl
new file mode 100644
index 0000000..ec86410
--- /dev/null
+++ b/core/java/android/widget/RemoteViews.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (c) 2007, 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 android.widget;
+
+parcelable RemoteViews;
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
new file mode 100644
index 0000000..e000d2e
--- /dev/null
+++ b/core/java/android/widget/RemoteViews.java
@@ -0,0 +1,834 @@
+/*
+ * Copyright (C) 2007 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 android.widget;
+
+import android.app.PendingIntent;
+import android.app.PendingIntent.CanceledException;
+import android.content.Context;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.PorterDuff;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.net.Uri;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.RemotableViewMethod;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.LayoutInflater.Filter;
+import android.view.View.OnClickListener;
+import android.view.animation.Animation;
+import android.view.animation.AnimationUtils;
+
+import java.lang.Class;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+
+
+/**
+ * A class that describes a view hierarchy that can be displayed in
+ * another process. The hierarchy is inflated from a layout resource
+ * file, and this class provides some basic operations for modifying
+ * the content of the inflated hierarchy.
+ */
+public class RemoteViews implements Parcelable, Filter {
+    
+    private static final String LOG_TAG = "RemoteViews";
+    
+    /**
+     * The package name of the package containing the layout 
+     * resource. (Added to the parcel)
+     */
+    private String mPackage;
+    
+    /**
+     * The resource ID of the layout file. (Added to the parcel)
+     */
+    private int mLayoutId;
+    
+    /**
+     * The Context object used to inflate the layout file. Also may
+     * be used by actions if they need access to the senders resources.
+     */
+    private Context mContext;
+    
+    /**
+     * An array of actions to perform on the view tree once it has been
+     * inflated
+     */
+    private ArrayList<Action> mActions;
+    
+    
+    /**
+     * This annotation indicates that a subclass of View is alllowed to be used
+     * with the {@link android.widget.RemoteViews} mechanism.
+     */
+    @Target({ ElementType.TYPE })
+    @Retention(RetentionPolicy.RUNTIME)
+    public @interface RemoteView {
+    }
+
+    /**
+     * Exception to send when something goes wrong executing an action
+     *
+     */
+    public static class ActionException extends RuntimeException {
+        public ActionException(Exception ex) {
+            super(ex);
+        }
+        public ActionException(String message) {
+            super(message);
+        }
+    }
+    
+    /**
+     * Base class for all actions that can be performed on an 
+     * inflated view.
+     *
+     */
+    private abstract static class Action implements Parcelable {
+        public abstract void apply(View root) throws ActionException;
+
+        public int describeContents() {
+            return 0;
+        }
+    };
+
+    /**
+     * Equivalent to calling
+     * {@link android.view.View#setOnClickListener(android.view.View.OnClickListener)}
+     * to launch the provided {@link PendingIntent}.
+     */
+    private class SetOnClickPendingIntent extends Action {
+        public SetOnClickPendingIntent(int id, PendingIntent pendingIntent) {
+            this.viewId = id;
+            this.pendingIntent = pendingIntent;
+        }
+        
+        public SetOnClickPendingIntent(Parcel parcel) {
+            viewId = parcel.readInt();
+            pendingIntent = PendingIntent.readPendingIntentOrNullFromParcel(parcel);
+        }
+        
+        public void writeToParcel(Parcel dest, int flags) {
+            dest.writeInt(TAG);
+            dest.writeInt(viewId);
+            pendingIntent.writeToParcel(dest, 0 /* no flags */);
+        }
+        
+        @Override
+        public void apply(View root) {
+            final View target = root.findViewById(viewId);
+            if (target != null && pendingIntent != null) {
+                OnClickListener listener = new OnClickListener() {
+                    public void onClick(View v) {
+                        try {
+                            // TODO: Unregister this handler if PendingIntent.FLAG_ONE_SHOT?
+                            pendingIntent.send();
+                        } catch (CanceledException e) {
+                            throw new ActionException(e.toString());
+                        }
+                    }
+                };
+                target.setOnClickListener(listener);
+            }
+        }
+        
+        int viewId;
+        PendingIntent pendingIntent;
+
+        public final static int TAG = 1;
+    }
+
+    /**
+     * Equivalent to calling a combination of {@link Drawable#setAlpha(int)},
+     * {@link Drawable#setColorFilter(int, android.graphics.PorterDuff.Mode)},
+     * and/or {@link Drawable#setLevel(int)} on the {@link Drawable} of a given view.
+     * <p>
+     * These operations will be performed on the {@link Drawable} returned by the
+     * target {@link View#getBackground()} by default.  If targetBackground is false,
+     * we assume the target is an {@link ImageView} and try applying the operations
+     * to {@link ImageView#getDrawable()}.
+     * <p>
+     * You can omit specific calls by marking their values with null or -1.
+     */
+    private class SetDrawableParameters extends Action {
+        public SetDrawableParameters(int id, boolean targetBackground, int alpha,
+                int colorFilter, PorterDuff.Mode mode, int level) {
+            this.viewId = id;
+            this.targetBackground = targetBackground;
+            this.alpha = alpha;
+            this.colorFilter = colorFilter;
+            this.filterMode = mode;
+            this.level = level;
+        }
+        
+        public SetDrawableParameters(Parcel parcel) {
+            viewId = parcel.readInt();
+            targetBackground = parcel.readInt() != 0;
+            alpha = parcel.readInt();
+            colorFilter = parcel.readInt();
+            boolean hasMode = parcel.readInt() != 0;
+            if (hasMode) {
+                filterMode = PorterDuff.Mode.valueOf(parcel.readString());
+            } else {
+                filterMode = null;
+            }
+            level = parcel.readInt();
+        }
+        
+        public void writeToParcel(Parcel dest, int flags) {
+            dest.writeInt(TAG);
+            dest.writeInt(viewId);
+            dest.writeInt(targetBackground ? 1 : 0);
+            dest.writeInt(alpha);
+            dest.writeInt(colorFilter);
+            if (filterMode != null) {
+                dest.writeInt(1);
+                dest.writeString(filterMode.toString());
+            } else {
+                dest.writeInt(0);
+            }
+            dest.writeInt(level);
+        }
+        
+        @Override
+        public void apply(View root) {
+            final View target = root.findViewById(viewId);
+            if (target == null) {
+                return;
+            }
+            
+            // Pick the correct drawable to modify for this view
+            Drawable targetDrawable = null;
+            if (targetBackground) {
+                targetDrawable = target.getBackground();
+            } else if (target instanceof ImageView) {
+                ImageView imageView = (ImageView) target;
+                targetDrawable = imageView.getDrawable();
+            }
+            
+            // Perform modifications only if values are set correctly
+            if (alpha != -1) {
+                targetDrawable.setAlpha(alpha);
+            }
+            if (colorFilter != -1 && filterMode != null) {
+                targetDrawable.setColorFilter(colorFilter, filterMode);
+            }
+            if (level != -1) {
+                targetDrawable.setLevel(level);
+            }
+        }
+        
+        int viewId;
+        boolean targetBackground;
+        int alpha;
+        int colorFilter;
+        PorterDuff.Mode filterMode;
+        int level;
+
+        public final static int TAG = 3;
+    }
+    
+    /**
+     * Base class for the reflection actions.
+     */
+    private class ReflectionAction extends Action {
+        static final int TAG = 2;
+
+        static final int BOOLEAN = 1;
+        static final int BYTE = 2;
+        static final int SHORT = 3;
+        static final int INT = 4;
+        static final int LONG = 5;
+        static final int FLOAT = 6;
+        static final int DOUBLE = 7;
+        static final int CHAR = 8;
+        static final int STRING = 9;
+        static final int CHAR_SEQUENCE = 10;
+        static final int URI = 11;
+        static final int BITMAP = 12;
+
+        int viewId;
+        String methodName;
+        int type;
+        Object value;
+
+        ReflectionAction(int viewId, String methodName, int type, Object value) {
+            this.viewId = viewId;
+            this.methodName = methodName;
+            this.type = type;
+            this.value = value;
+        }
+
+        ReflectionAction(Parcel in) {
+            this.viewId = in.readInt();
+            this.methodName = in.readString();
+            this.type = in.readInt();
+            if (false) {
+                Log.d("RemoteViews", "read viewId=0x" + Integer.toHexString(this.viewId)
+                        + " methodName=" + this.methodName + " type=" + this.type);
+            }
+            switch (this.type) {
+                case BOOLEAN:
+                    this.value = in.readInt() != 0;
+                    break;
+                case BYTE:
+                    this.value = in.readByte();
+                    break;
+                case SHORT:
+                    this.value = (short)in.readInt();
+                    break;
+                case INT:
+                    this.value = in.readInt();
+                    break;
+                case LONG:
+                    this.value = in.readLong();
+                    break;
+                case FLOAT:
+                    this.value = in.readFloat();
+                    break;
+                case DOUBLE:
+                    this.value = in.readDouble();
+                    break;
+                case CHAR:
+                    this.value = (char)in.readInt();
+                    break;
+                case STRING:
+                    this.value = in.readString();
+                    break;
+                case CHAR_SEQUENCE:
+                    this.value = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
+                    break;
+                case URI:
+                    this.value = Uri.CREATOR.createFromParcel(in);
+                    break;
+                case BITMAP:
+                    this.value = Bitmap.CREATOR.createFromParcel(in);
+                    break;
+                default:
+                    break;
+            }
+        }
+
+        public void writeToParcel(Parcel out, int flags) {
+            out.writeInt(TAG);
+            out.writeInt(this.viewId);
+            out.writeString(this.methodName);
+            out.writeInt(this.type);
+            if (false) {
+                Log.d("RemoteViews", "write viewId=0x" + Integer.toHexString(this.viewId)
+                        + " methodName=" + this.methodName + " type=" + this.type);
+            }
+            switch (this.type) {
+                case BOOLEAN:
+                    out.writeInt(((Boolean)this.value).booleanValue() ? 1 : 0);
+                    break;
+                case BYTE:
+                    out.writeByte(((Byte)this.value).byteValue());
+                    break;
+                case SHORT:
+                    out.writeInt(((Short)this.value).shortValue());
+                    break;
+                case INT:
+                    out.writeInt(((Integer)this.value).intValue());
+                    break;
+                case LONG:
+                    out.writeLong(((Long)this.value).longValue());
+                    break;
+                case FLOAT:
+                    out.writeFloat(((Float)this.value).floatValue());
+                    break;
+                case DOUBLE:
+                    out.writeDouble(((Double)this.value).doubleValue());
+                    break;
+                case CHAR:
+                    out.writeInt((int)((Character)this.value).charValue());
+                    break;
+                case STRING:
+                    out.writeString((String)this.value);
+                    break;
+                case CHAR_SEQUENCE:
+                    TextUtils.writeToParcel((CharSequence)this.value, out, flags);   
+                    break;
+                case URI:
+                    ((Uri)this.value).writeToParcel(out, flags);
+                    break;
+                case BITMAP:
+                    ((Bitmap)this.value).writeToParcel(out, flags);
+                    break;
+                default:
+                    break;
+            }
+        }
+
+        private Class getParameterType() {
+            switch (this.type) {
+                case BOOLEAN:
+                    return boolean.class;
+                case BYTE:
+                    return byte.class;
+                case SHORT:
+                    return short.class;
+                case INT:
+                    return int.class;
+                case LONG:
+                    return long.class;
+                case FLOAT:
+                    return float.class;
+                case DOUBLE:
+                    return double.class;
+                case CHAR:
+                    return char.class;
+                case STRING:
+                    return String.class;
+                case CHAR_SEQUENCE:
+                    return CharSequence.class;
+                case URI:
+                    return Uri.class;
+                case BITMAP:
+                    return Bitmap.class;
+                default:
+                    return null;
+            }
+        }
+
+        @Override
+        public void apply(View root) {
+            final View view = root.findViewById(viewId);
+            if (view == null) {
+                throw new ActionException("can't find view: 0x" + Integer.toHexString(viewId));
+            }
+
+            Class param = getParameterType();
+            if (param == null) {
+                throw new ActionException("bad type: " + this.type);
+            }
+
+            Class klass = view.getClass();
+            Method method = null;
+            try {
+                method = klass.getMethod(this.methodName, getParameterType());
+            }
+            catch (NoSuchMethodException ex) {
+                throw new ActionException("view: " + klass.getName() + " doesn't have method: "
+                        + this.methodName + "(" + param.getName() + ")");
+            }
+
+            if (!method.isAnnotationPresent(RemotableViewMethod.class)) {
+                throw new ActionException("view: " + klass.getName()
+                        + " can't use method with RemoteViews: "
+                        + this.methodName + "(" + param.getName() + ")");
+            }
+
+            try {
+                if (false) {
+                    Log.d("RemoteViews", "view: " + klass.getName() + " calling method: "
+                        + this.methodName + "(" + param.getName() + ") with "
+                        + (this.value == null ? "null" : this.value.getClass().getName()));
+                }
+                method.invoke(view, this.value);
+            }
+            catch (Exception ex) {
+                throw new ActionException(ex);
+            }
+        }
+    }
+
+
+    /**
+     * Create a new RemoteViews object that will display the views contained
+     * in the specified layout file.
+     * 
+     * @param packageName Name of the package that contains the layout resource
+     * @param layoutId The id of the layout resource
+     */
+    public RemoteViews(String packageName, int layoutId) {
+        mPackage = packageName;
+        mLayoutId = layoutId;
+    }
+
+    /**
+     * Reads a RemoteViews object from a parcel.
+     * 
+     * @param parcel
+     */
+    public RemoteViews(Parcel parcel) {
+        mPackage = parcel.readString();
+        mLayoutId = parcel.readInt();
+        int count = parcel.readInt();
+        if (count > 0) {
+            mActions = new ArrayList<Action>(count);
+            for (int i=0; i<count; i++) {
+                int tag = parcel.readInt();
+                switch (tag) {
+                case SetOnClickPendingIntent.TAG:
+                    mActions.add(new SetOnClickPendingIntent(parcel));
+                    break;
+                case SetDrawableParameters.TAG:
+                    mActions.add(new SetDrawableParameters(parcel));
+                    break;
+                case ReflectionAction.TAG:
+                    mActions.add(new ReflectionAction(parcel));
+                    break;
+                default:
+                    throw new ActionException("Tag " + tag + " not found");
+                }
+            }
+        }
+    }
+
+    public String getPackage() {
+        return mPackage;
+    }
+
+    public int getLayoutId() {
+        return mLayoutId;
+    }
+
+    /**
+     * Add an action to be executed on the remote side when apply is called.
+     * 
+     * @param a The action to add
+     */
+    private void addAction(Action a) {
+        if (mActions == null) {
+            mActions = new ArrayList<Action>();
+        }
+        mActions.add(a);
+    }
+    
+    /**
+     * Equivalent to calling View.setVisibility
+     * 
+     * @param viewId The id of the view whose visibility should change
+     * @param visibility The new visibility for the view
+     */
+    public void setViewVisibility(int viewId, int visibility) {
+        setInt(viewId, "setVisibility", visibility);
+    }
+    
+    /**
+     * Equivalent to calling TextView.setText
+     * 
+     * @param viewId The id of the view whose text should change
+     * @param text The new text for the view
+     */
+    public void setTextViewText(int viewId, CharSequence text) {
+        setCharSequence(viewId, "setText", text);
+    }
+    
+    /**
+     * Equivalent to calling ImageView.setImageResource
+     * 
+     * @param viewId The id of the view whose drawable should change
+     * @param srcId The new resource id for the drawable
+     */
+    public void setImageViewResource(int viewId, int srcId) {   
+        setInt(viewId, "setImageResource", srcId);
+    }
+
+    /**
+     * Equivalent to calling ImageView.setImageURI
+     * 
+     * @param viewId The id of the view whose drawable should change
+     * @param uri The Uri for the image
+     */
+    public void setImageViewUri(int viewId, Uri uri) {
+        setUri(viewId, "setImageURI", uri);
+    }
+
+    /**
+     * Equivalent to calling ImageView.setImageBitmap
+     * 
+     * @param viewId The id of the view whose drawable should change
+     * @param bitmap The new Bitmap for the drawable
+     */
+    public void setImageViewBitmap(int viewId, Bitmap bitmap) {
+        setBitmap(viewId, "setImageBitmap", bitmap);
+    }
+
+    /**
+     * Equivalent to calling {@link Chronometer#setBase Chronometer.setBase},
+     * {@link Chronometer#setFormat Chronometer.setFormat},
+     * and {@link Chronometer#start Chronometer.start()} or
+     * {@link Chronometer#stop Chronometer.stop()}.
+     * 
+     * @param viewId The id of the view whose text should change
+     * @param base The time at which the timer would have read 0:00.  This
+     *             time should be based off of
+     *             {@link android.os.SystemClock#elapsedRealtime SystemClock.elapsedRealtime()}.
+     * @param format The Chronometer format string, or null to
+     *               simply display the timer value.
+     * @param started True if you want the clock to be started, false if not.
+     */
+    public void setChronometer(int viewId, long base, String format, boolean started) {
+        setLong(viewId, "setBase", base);
+        setString(viewId, "setFormat", format);
+        setBoolean(viewId, "setStarted", started);
+    }
+    
+    /**
+     * Equivalent to calling {@link ProgressBar#setMax ProgressBar.setMax},
+     * {@link ProgressBar#setProgress ProgressBar.setProgress}, and
+     * {@link ProgressBar#setIndeterminate ProgressBar.setIndeterminate}
+     *
+     * If indeterminate is true, then the values for max and progress are ignored.
+     * 
+     * @param viewId The id of the view whose text should change
+     * @param max The 100% value for the progress bar
+     * @param progress The current value of the progress bar.
+     * @param indeterminate True if the progress bar is indeterminate, 
+     *                false if not.
+     */
+    public void setProgressBar(int viewId, int max, int progress, 
+            boolean indeterminate) {
+        setBoolean(viewId, "setIndeterminate", indeterminate);
+        if (!indeterminate) {
+            setInt(viewId, "setMax", max);
+            setInt(viewId, "setProgress", progress);
+        }
+    }
+    
+    /**
+     * Equivalent to calling
+     * {@link android.view.View#setOnClickListener(android.view.View.OnClickListener)}
+     * to launch the provided {@link PendingIntent}.
+     * 
+     * @param viewId The id of the view that will trigger the {@link PendingIntent} when clicked
+     * @param pendingIntent The {@link PendingIntent} to send when user clicks
+     */
+    public void setOnClickPendingIntent(int viewId, PendingIntent pendingIntent) {
+        addAction(new SetOnClickPendingIntent(viewId, pendingIntent));
+    }
+
+    /**
+     * @hide
+     * Equivalent to calling a combination of {@link Drawable#setAlpha(int)},
+     * {@link Drawable#setColorFilter(int, android.graphics.PorterDuff.Mode)},
+     * and/or {@link Drawable#setLevel(int)} on the {@link Drawable} of a given
+     * view.
+     * <p>
+     * You can omit specific calls by marking their values with null or -1.
+     * 
+     * @param viewId The id of the view that contains the target
+     *            {@link Drawable}
+     * @param targetBackground If true, apply these parameters to the
+     *            {@link Drawable} returned by
+     *            {@link android.view.View#getBackground()}. Otherwise, assume
+     *            the target view is an {@link ImageView} and apply them to
+     *            {@link ImageView#getDrawable()}.
+     * @param alpha Specify an alpha value for the drawable, or -1 to leave
+     *            unchanged.
+     * @param colorFilter Specify a color for a
+     *            {@link android.graphics.ColorFilter} for this drawable, or -1
+     *            to leave unchanged.
+     * @param mode Specify a PorterDuff mode for this drawable, or null to leave
+     *            unchanged.
+     * @param level Specify the level for the drawable, or -1 to leave
+     *            unchanged.
+     */
+    public void setDrawableParameters(int viewId, boolean targetBackground, int alpha,
+            int colorFilter, PorterDuff.Mode mode, int level) {
+        addAction(new SetDrawableParameters(viewId, targetBackground, alpha,
+                colorFilter, mode, level));
+    }
+
+    /**
+     * Equivalent to calling {@link android.widget.TextView#setTextColor(int)}.
+     * 
+     * @param viewId The id of the view whose text should change
+     * @param color Sets the text color for all the states (normal, selected,
+     *            focused) to be this color.
+     */
+    public void setTextColor(int viewId, int color) {
+        setInt(viewId, "setTextColor", color);
+    }
+
+    public void setBoolean(int viewId, String methodName, boolean value) {
+        addAction(new ReflectionAction(viewId, methodName, ReflectionAction.BOOLEAN, value));
+    }
+
+    public void setByte(int viewId, String methodName, byte value) {
+        addAction(new ReflectionAction(viewId, methodName, ReflectionAction.BYTE, value));
+    }
+
+    public void setShort(int viewId, String methodName, short value) {
+        addAction(new ReflectionAction(viewId, methodName, ReflectionAction.SHORT, value));
+    }
+
+    public void setInt(int viewId, String methodName, int value) {
+        addAction(new ReflectionAction(viewId, methodName, ReflectionAction.INT, value));
+    }
+
+    public void setLong(int viewId, String methodName, long value) {
+        addAction(new ReflectionAction(viewId, methodName, ReflectionAction.LONG, value));
+    }
+
+    public void setFloat(int viewId, String methodName, float value) {
+        addAction(new ReflectionAction(viewId, methodName, ReflectionAction.FLOAT, value));
+    }
+
+    public void setDouble(int viewId, String methodName, double value) {
+        addAction(new ReflectionAction(viewId, methodName, ReflectionAction.DOUBLE, value));
+    }
+
+    public void setChar(int viewId, String methodName, char value) {
+        addAction(new ReflectionAction(viewId, methodName, ReflectionAction.CHAR, value));
+    }
+
+    public void setString(int viewId, String methodName, String value) {
+        addAction(new ReflectionAction(viewId, methodName, ReflectionAction.STRING, value));
+    }
+
+    public void setCharSequence(int viewId, String methodName, CharSequence value) {
+        addAction(new ReflectionAction(viewId, methodName, ReflectionAction.CHAR_SEQUENCE, value));
+    }
+
+    public void setUri(int viewId, String methodName, Uri value) {
+        addAction(new ReflectionAction(viewId, methodName, ReflectionAction.URI, value));
+    }
+
+    public void setBitmap(int viewId, String methodName, Bitmap value) {
+        addAction(new ReflectionAction(viewId, methodName, ReflectionAction.BITMAP, value));
+    }
+
+    /**
+     * Inflates the view hierarchy represented by this object and applies
+     * all of the actions.
+     * 
+     * <p><strong>Caller beware: this may throw</strong>
+     * 
+     * @param context Default context to use
+     * @param parent Parent that the resulting view hierarchy will be attached to. This method
+     * does <strong>not</strong> attach the hierarchy. The caller should do so when appropriate.
+     * @return The inflated view hierarchy
+     */
+    public View apply(Context context, ViewGroup parent) {
+        View result = null;
+
+        Context c = prepareContext(context);
+
+        Resources r = c.getResources();
+        LayoutInflater inflater = (LayoutInflater) c
+                .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+
+        inflater = inflater.cloneInContext(c);
+        inflater.setFilter(this);
+
+        result = inflater.inflate(mLayoutId, parent, false);
+
+        performApply(result);
+
+        return result;
+    }
+    
+    /**
+     * Applies all of the actions to the provided view.
+     *
+     * <p><strong>Caller beware: this may throw</strong>
+     * 
+     * @param v The view to apply the actions to.  This should be the result of
+     * the {@link #apply(Context,ViewGroup)} call.
+     */
+    public void reapply(Context context, View v) {
+        prepareContext(context);
+        performApply(v);
+    }
+
+    private void performApply(View v) {
+        if (mActions != null) {
+            final int count = mActions.size();
+            for (int i = 0; i < count; i++) {
+                Action a = mActions.get(i);
+                a.apply(v);
+            }
+        }
+    }
+
+    private Context prepareContext(Context context) {
+        Context c = null;
+        String packageName = mPackage;
+
+        if (packageName != null) {
+            try {
+                c = context.createPackageContext(packageName, 0);
+            } catch (NameNotFoundException e) {
+                Log.e(LOG_TAG, "Package name " + packageName + " not found");
+                c = context;
+            }
+        } else {
+            c = context;
+        }
+
+        mContext = c;
+
+        return c;
+    }
+
+    /* (non-Javadoc)
+     * Used to restrict the views which can be inflated
+     * 
+     * @see android.view.LayoutInflater.Filter#onLoadClass(java.lang.Class)
+     */
+    public boolean onLoadClass(Class clazz) {
+        return clazz.isAnnotationPresent(RemoteView.class);
+    }
+    
+    public int describeContents() {
+        return 0;
+    }
+
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeString(mPackage);
+        dest.writeInt(mLayoutId);
+        int count;
+        if (mActions != null) {
+            count = mActions.size();
+        } else {
+            count = 0;
+        }
+        dest.writeInt(count);
+        for (int i=0; i<count; i++) {
+            Action a = mActions.get(i);
+            a.writeToParcel(dest, 0);
+        }
+    }
+
+    /**
+     * Parcelable.Creator that instantiates RemoteViews objects
+     */
+    public static final Parcelable.Creator<RemoteViews> CREATOR = new Parcelable.Creator<RemoteViews>() {
+        public RemoteViews createFromParcel(Parcel parcel) {
+            return new RemoteViews(parcel);
+        }
+
+        public RemoteViews[] newArray(int size) {
+            return new RemoteViews[size];
+        }
+    };
+}
diff --git a/core/java/android/widget/ResourceCursorAdapter.java b/core/java/android/widget/ResourceCursorAdapter.java
new file mode 100644
index 0000000..a5dbd98
--- /dev/null
+++ b/core/java/android/widget/ResourceCursorAdapter.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2007 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 android.widget;
+
+import android.content.Context;
+import android.database.Cursor;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.LayoutInflater;
+
+
+/**
+ * An easy adapter that creates views defined in an XML file. You can specify
+ * the XML file that defines the appearance of the views.
+ */
+public abstract class ResourceCursorAdapter extends CursorAdapter {
+    private int mLayout;
+
+    private int mDropDownLayout;
+    
+    private LayoutInflater mInflater;
+    
+    /**
+     * Constructor.
+     * 
+     * @param context The context where the ListView associated with this
+     *            SimpleListItemFactory is running
+     * @param layout resource identifier of a layout file that defines the views
+     *            for this list item.  Unless you override them later, this will
+     *            define both the item views and the drop down views.
+     */
+    public ResourceCursorAdapter(Context context, int layout, Cursor c) {
+        super(context, c);
+        mLayout = mDropDownLayout = layout;
+        mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+    }
+    
+    /**
+     * Constructor.
+     * 
+     * @param context The context where the ListView associated with this
+     *            SimpleListItemFactory is running
+     * @param layout resource identifier of a layout file that defines the views
+     *            for this list item.  Unless you override them later, this will
+     *            define both the item views and the drop down views.
+     * @param c The cursor from which to get the data.
+     * @param autoRequery If true the adapter will call requery() on the
+     *                    cursor whenever it changes so the most recent
+     *                    data is always displayed.
+     * @hide Pending API Council approval
+     */
+    public ResourceCursorAdapter(Context context, int layout, Cursor c, boolean autoRequery) {
+        super(context, c, autoRequery);
+        mLayout = mDropDownLayout = layout;
+        mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+    }
+
+    /**
+     * Inflates view(s) from the specified XML file.
+     * 
+     * @see android.widget.CursorAdapter#newView(android.content.Context,
+     *      android.database.Cursor, ViewGroup)
+     */
+    @Override
+    public View newView(Context context, Cursor cursor, ViewGroup parent) {
+        return mInflater.inflate(mLayout, parent, false);
+    }
+
+    @Override
+    public View newDropDownView(Context context, Cursor cursor, ViewGroup parent) {
+        return mInflater.inflate(mDropDownLayout, parent, false);
+    }
+
+    /**
+     * <p>Sets the layout resource of the item views.</p>
+     *
+     * @param layout the layout resources used to create item views
+     */
+    public void setViewResource(int layout) {
+        mLayout = layout;
+    }
+    
+    /**
+     * <p>Sets the layout resource of the drop down views.</p>
+     *
+     * @param dropDownLayout the layout resources used to create drop down views
+     */
+    public void setDropDownViewResource(int dropDownLayout) {
+        mDropDownLayout = dropDownLayout;
+    }
+}
diff --git a/core/java/android/widget/ResourceCursorTreeAdapter.java b/core/java/android/widget/ResourceCursorTreeAdapter.java
new file mode 100644
index 0000000..ddce515
--- /dev/null
+++ b/core/java/android/widget/ResourceCursorTreeAdapter.java
@@ -0,0 +1,109 @@
+/*
+ * 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 android.widget;
+
+import android.content.Context;
+import android.database.Cursor;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.LayoutInflater;
+
+/**
+ * A fairly simple ExpandableListAdapter that creates views defined in an XML
+ * file. You can specify the XML file that defines the appearance of the views.
+ */
+public abstract class ResourceCursorTreeAdapter extends CursorTreeAdapter {
+    private int mCollapsedGroupLayout;
+    private int mExpandedGroupLayout;
+    private int mChildLayout;
+    private int mLastChildLayout;
+    private LayoutInflater mInflater;
+    
+    /**
+     * Constructor.
+     * 
+     * @param context The context where the ListView associated with this
+     *            SimpleListItemFactory is running
+     * @param cursor The database cursor
+     * @param collapsedGroupLayout resource identifier of a layout file that
+     *            defines the views for collapsed groups.
+     * @param expandedGroupLayout resource identifier of a layout file that
+     *            defines the views for expanded groups.
+     * @param childLayout resource identifier of a layout file that defines the
+     *            views for all children but the last..
+     * @param lastChildLayout resource identifier of a layout file that defines
+     *            the views for the last child of a group.
+     */
+    public ResourceCursorTreeAdapter(Context context, Cursor cursor, int collapsedGroupLayout,
+            int expandedGroupLayout, int childLayout, int lastChildLayout) {
+        super(cursor, context);
+        
+        mCollapsedGroupLayout = collapsedGroupLayout;
+        mExpandedGroupLayout = expandedGroupLayout;
+        mChildLayout = childLayout;
+        mLastChildLayout = lastChildLayout;
+        
+        mInflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+    }
+
+    /**
+     * Constructor.
+     * 
+     * @param context The context where the ListView associated with this
+     *            SimpleListItemFactory is running
+     * @param cursor The database cursor
+     * @param collapsedGroupLayout resource identifier of a layout file that
+     *            defines the views for collapsed groups.
+     * @param expandedGroupLayout resource identifier of a layout file that
+     *            defines the views for expanded groups.
+     * @param childLayout resource identifier of a layout file that defines the
+     *            views for all children.
+     */
+    public ResourceCursorTreeAdapter(Context context, Cursor cursor, int collapsedGroupLayout,
+            int expandedGroupLayout, int childLayout) {
+        this(context, cursor, collapsedGroupLayout, expandedGroupLayout, childLayout, childLayout);
+    }
+
+    /**
+     * Constructor.
+     * 
+     * @param context The context where the ListView associated with this
+     *            SimpleListItemFactory is running
+     * @param cursor The database cursor
+     * @param groupLayout resource identifier of a layout file that defines the
+     *            views for all groups.
+     * @param childLayout resource identifier of a layout file that defines the
+     *            views for all children.
+     */
+    public ResourceCursorTreeAdapter(Context context, Cursor cursor, int groupLayout,
+            int childLayout) {
+        this(context, cursor, groupLayout, groupLayout, childLayout, childLayout);
+    }
+    
+    @Override
+    public View newChildView(Context context, Cursor cursor, boolean isLastChild,
+            ViewGroup parent) {
+        return mInflater.inflate((isLastChild) ? mLastChildLayout : mChildLayout, parent, false);
+    }
+
+    @Override
+    public View newGroupView(Context context, Cursor cursor, boolean isExpanded, ViewGroup parent) {
+        return mInflater.inflate((isExpanded) ? mExpandedGroupLayout : mCollapsedGroupLayout,
+                parent, false);
+    }
+
+}
diff --git a/core/java/android/widget/ScrollBarDrawable.java b/core/java/android/widget/ScrollBarDrawable.java
new file mode 100644
index 0000000..3b113ae
--- /dev/null
+++ b/core/java/android/widget/ScrollBarDrawable.java
@@ -0,0 +1,253 @@
+/*
+ * 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 android.widget;
+
+import android.graphics.Canvas;
+import android.graphics.ColorFilter;
+import android.graphics.PixelFormat;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+
+/**
+ * This is only used by View for displaying its scroll bars.  It should probably
+ * be moved in to the view package since it is used in that lower-level layer.
+ * For now, we'll hide it so it can be cleaned up later.
+ * {@hide}
+ */
+public class ScrollBarDrawable extends Drawable {
+    private Drawable mVerticalTrack;
+    private Drawable mHorizontalTrack;
+    private Drawable mVerticalThumb;
+    private Drawable mHorizontalThumb;
+    private int mRange;
+    private int mOffset;
+    private int mExtent;
+    private boolean mVertical;
+    private boolean mChanged;
+    private boolean mRangeChanged;
+    private final Rect mTempBounds = new Rect();
+    private boolean mAlwaysDrawHorizontalTrack;
+    private boolean mAlwaysDrawVerticalTrack;
+
+    public ScrollBarDrawable() {
+    }
+
+    /**
+     * Indicate whether the horizontal scrollbar track should always be drawn regardless of the
+     * extent. Defaults to false.
+     *
+     * @param alwaysDrawTrack Set to true if the track should always be drawn
+     */
+    public void setAlwaysDrawHorizontalTrack(boolean alwaysDrawTrack) {
+        mAlwaysDrawHorizontalTrack = alwaysDrawTrack;
+    }
+
+    /**
+     * Indicate whether the vertical scrollbar track should always be drawn regardless of the
+     * extent. Defaults to false.
+     *
+     * @param alwaysDrawTrack Set to true if the track should always be drawn
+     */
+    public void setAlwaysDrawVerticalTrack(boolean alwaysDrawTrack) {
+        mAlwaysDrawVerticalTrack = alwaysDrawTrack;
+    }
+
+    /**
+     * Indicates whether the vertical scrollbar track should always be drawn regardless of the
+     * extent.
+     */
+    public boolean getAlwaysDrawVerticalTrack() {
+        return mAlwaysDrawVerticalTrack;
+    }
+
+    /**
+     * Indicates whether the horizontal scrollbar track should always be drawn regardless of the
+     * extent.
+     */
+    public boolean getAlwaysDrawHorizontalTrack() {
+        return mAlwaysDrawHorizontalTrack;
+    }
+
+    public void setParameters(int range, int offset, int extent, boolean vertical) {
+        if (mVertical != vertical) {
+            mChanged = true;
+        }
+
+        if (mRange != range || mOffset != offset || mExtent != extent) {
+            mRangeChanged = true;
+        }
+
+        mRange = range;
+        mOffset = offset;
+        mExtent = extent;
+        mVertical = vertical;
+    }
+
+    @Override
+    public void draw(Canvas canvas) {
+        final boolean vertical = mVertical;
+        final int extent = mExtent;
+        final int range = mRange;
+
+        boolean drawTrack = true;
+        boolean drawThumb = true;
+        if (extent <= 0 || range <= extent) {
+            drawTrack = vertical ? mAlwaysDrawVerticalTrack : mAlwaysDrawHorizontalTrack;
+            drawThumb = false;
+        }
+
+        Rect r = getBounds();
+        if (canvas.quickReject(r.left, r.top, r.right, r.bottom, 
+                Canvas.EdgeType.AA)) {
+            return;
+        }
+        if (drawTrack) {
+            drawTrack(canvas, r, vertical);
+        }
+
+        if (drawThumb) {
+            int size = vertical ? r.height() : r.width();
+            int thickness = vertical ? r.width() : r.height();
+            int length = Math.round((float) size * extent / range);
+            int offset = Math.round((float) (size - length) * mOffset / (range - extent));
+
+            // avoid the tiny thumb
+            int minLength = thickness * 2;
+            if (length < minLength) {
+                length = minLength;
+            }
+            // avoid the too-big thumb
+            if (offset + length > size) {
+                offset = size - length;
+            }
+
+            drawThumb(canvas, r, offset, length, vertical);
+        }
+    }
+
+    @Override
+    protected void onBoundsChange(Rect bounds) {
+        super.onBoundsChange(bounds);
+        mChanged = true;
+    }
+
+    protected void drawTrack(Canvas canvas, Rect bounds, boolean vertical) {
+        Drawable track;
+        if (vertical) {
+            track = mVerticalTrack;
+        } else {
+            track = mHorizontalTrack;
+        }
+        if (track != null) {
+            if (mChanged) {
+                track.setBounds(bounds);
+            }
+            track.draw(canvas);
+        }
+    }
+
+    protected void drawThumb(Canvas canvas, Rect bounds, int offset, int length, boolean vertical) {
+        final Rect thumbRect = mTempBounds;
+        final boolean changed = mRangeChanged || mChanged;
+        if (changed) {
+            if (vertical) {
+                thumbRect.set(bounds.left,  bounds.top + offset,
+                        bounds.right, bounds.top + offset + length);
+            } else {
+                thumbRect.set(bounds.left + offset, bounds.top,
+                        bounds.left + offset + length, bounds.bottom);
+            }
+        }
+
+        if (vertical) {
+            final Drawable thumb = mVerticalThumb;
+            if (changed) thumb.setBounds(thumbRect);
+            thumb.draw(canvas);
+        } else {
+            final Drawable thumb = mHorizontalThumb;
+            if (changed) thumb.setBounds(thumbRect);
+            thumb.draw(canvas);
+        }
+    }
+
+    public void setVerticalThumbDrawable(Drawable thumb) {
+        if (thumb != null) {
+            mVerticalThumb = thumb;
+        }
+    }
+
+    public void setVerticalTrackDrawable(Drawable track) {
+        mVerticalTrack = track;
+    }
+
+    public void setHorizontalThumbDrawable(Drawable thumb) {
+        if (thumb != null) {
+            mHorizontalThumb = thumb;
+        }
+    }
+
+    public void setHorizontalTrackDrawable(Drawable track) {
+        mHorizontalTrack = track;
+    }
+
+    public int getSize(boolean vertical) {
+        if (vertical) {
+            return (mVerticalTrack != null ?
+                    mVerticalTrack : mVerticalThumb).getIntrinsicWidth();
+        } else {
+            return (mHorizontalTrack != null ?
+                    mHorizontalTrack : mHorizontalThumb).getIntrinsicHeight();
+        }
+    }
+
+    @Override
+    public void setAlpha(int alpha) {
+        if (mVerticalTrack != null) {
+            mVerticalTrack.setAlpha(alpha);
+        }
+        mVerticalThumb.setAlpha(alpha);
+        if (mHorizontalTrack != null) {
+            mHorizontalTrack.setAlpha(alpha);
+        }
+        mHorizontalThumb.setAlpha(alpha);
+    }
+
+    @Override
+    public void setColorFilter(ColorFilter cf) {
+        if (mVerticalTrack != null) {
+            mVerticalTrack.setColorFilter(cf);
+        }
+        mVerticalThumb.setColorFilter(cf);
+        if (mHorizontalTrack != null) {
+            mHorizontalTrack.setColorFilter(cf);
+        }
+        mHorizontalThumb.setColorFilter(cf);
+    }
+
+    @Override
+    public int getOpacity() {
+        return PixelFormat.TRANSLUCENT;
+    }
+
+    @Override
+    public String toString() {
+        return "ScrollBarDrawable: range=" + mRange + " offset=" + mOffset +
+               " extent=" + mExtent + (mVertical ? " V" : " H");
+    }
+}
+
+
diff --git a/core/java/android/widget/ScrollView.java b/core/java/android/widget/ScrollView.java
new file mode 100644
index 0000000..c9b3751
--- /dev/null
+++ b/core/java/android/widget/ScrollView.java
@@ -0,0 +1,1232 @@
+/*
+ * 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 android.widget;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Rect;
+import android.util.AttributeSet;
+import android.util.Config;
+import android.util.Log;
+import android.view.FocusFinder;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+import android.view.VelocityTracker;
+import android.view.View;
+import android.view.ViewConfiguration;
+import android.view.ViewGroup;
+import android.view.ViewParent;
+import android.view.animation.AnimationUtils;
+
+import com.android.internal.R;
+
+import java.util.List;
+
+/**
+ * Layout container for a view hierarchy that can be scrolled by the user,
+ * allowing it to be larger than the physical display.  A ScrollView
+ * is a {@link FrameLayout}, meaning you should place one child in it
+ * containing the entire contents to scroll; this child may itself be a layout
+ * manager with a complex hierarchy of objects.  A child that is often used
+ * is a {@link LinearLayout} in a vertical orientation, presenting a vertical
+ * array of top-level items that the user can scroll through.
+ *
+ * <p>The {@link TextView} class also
+ * takes care of its own scrolling, so does not require a ScrollView, but
+ * using the two together is possible to achieve the effect of a text view
+ * within a larger container.
+ * 
+ * <p>ScrollView only supports vertical scrolling.
+ */
+public class ScrollView extends FrameLayout {
+    static final String TAG = "ScrollView";
+    static final boolean localLOGV = false || Config.LOGV;
+    
+    static final int ANIMATED_SCROLL_GAP = 250;
+
+    static final float MAX_SCROLL_FACTOR = 0.5f;
+
+
+    private long mLastScroll;
+
+    private final Rect mTempRect = new Rect();
+    private Scroller mScroller;
+
+    /**
+     * Flag to indicate that we are moving focus ourselves. This is so the
+     * code that watches for focus changes initiated outside this ScrollView
+     * knows that it does not have to do anything.
+     */
+    private boolean mScrollViewMovedFocus;
+
+    /**
+     * Position of the last motion event.
+     */
+    private float mLastMotionY;
+
+    /**
+     * True when the layout has changed but the traversal has not come through yet.
+     * Ideally the view hierarchy would keep track of this for us.
+     */
+    private boolean mIsLayoutDirty = true;
+
+    /**
+     * The child to give focus to in the event that a child has requested focus while the
+     * layout is dirty. This prevents the scroll from being wrong if the child has not been
+     * laid out before requesting focus.
+     */
+    private View mChildToScrollTo = null;
+
+    /**
+     * True if the user is currently dragging this ScrollView around. This is
+     * not the same as 'is being flinged', which can be checked by
+     * mScroller.isFinished() (flinging begins when the user lifts his finger).
+     */
+    private boolean mIsBeingDragged = false;
+
+    /**
+     * Determines speed during touch scrolling
+     */
+    private VelocityTracker mVelocityTracker;
+
+    /**
+     * When set to true, the scroll view measure its child to make it fill the currently
+     * visible area.
+     */
+    private boolean mFillViewport;
+
+    /**
+     * Whether arrow scrolling is animated.
+     */
+    private boolean mSmoothScrollingEnabled = true;
+
+    private int mTouchSlop;
+
+    public ScrollView(Context context) {
+        this(context, null);
+    }
+
+    public ScrollView(Context context, AttributeSet attrs) {
+        this(context, attrs, com.android.internal.R.attr.scrollViewStyle);
+    }
+
+    public ScrollView(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+        initScrollView();
+
+        TypedArray a =
+            context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.ScrollView, defStyle, 0);
+
+        setFillViewport(a.getBoolean(R.styleable.ScrollView_fillViewport, false));
+
+        a.recycle();
+    }
+
+    @Override
+    protected float getTopFadingEdgeStrength() {
+        if (getChildCount() == 0) {
+            return 0.0f;
+        }
+
+        final int length = getVerticalFadingEdgeLength();
+        if (mScrollY < length) {
+            return mScrollY / (float) length;
+        }
+
+        return 1.0f;
+    }
+
+    @Override
+    protected float getBottomFadingEdgeStrength() {
+        if (getChildCount() == 0) {
+            return 0.0f;
+        }
+
+        final int length = getVerticalFadingEdgeLength();
+        final int bottomEdge = getHeight() - mPaddingBottom;
+        final int span = getChildAt(0).getBottom() - mScrollY - bottomEdge;
+        if (span < length) {
+            return span / (float) length;
+        }
+
+        return 1.0f;
+    }
+
+    /**
+     * @return The maximum amount this scroll view will scroll in response to
+     *   an arrow event.
+     */
+    public int getMaxScrollAmount() {
+        return (int) (MAX_SCROLL_FACTOR * (mBottom - mTop));
+    }
+
+
+    private void initScrollView() {
+        mScroller = new Scroller(getContext());
+        setFocusable(true);
+        setDescendantFocusability(FOCUS_AFTER_DESCENDANTS);
+        setWillNotDraw(false);
+        mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
+    }
+
+    @Override
+    public void addView(View child) {
+        if (getChildCount() > 0) {
+            throw new IllegalStateException("ScrollView can host only one direct child");
+        }
+
+        super.addView(child);
+    }
+
+    @Override
+    public void addView(View child, int index) {
+        if (getChildCount() > 0) {
+            throw new IllegalStateException("ScrollView can host only one direct child");
+        }
+
+        super.addView(child, index);
+    }
+
+    @Override
+    public void addView(View child, ViewGroup.LayoutParams params) {
+        if (getChildCount() > 0) {
+            throw new IllegalStateException("ScrollView can host only one direct child");
+        }
+
+        super.addView(child, params);
+    }
+
+    @Override
+    public void addView(View child, int index, ViewGroup.LayoutParams params) {
+        if (getChildCount() > 0) {
+            throw new IllegalStateException("ScrollView can host only one direct child");
+        }
+
+        super.addView(child, index, params);
+    }
+
+    /**
+     * @return Returns true this ScrollView can be scrolled
+     */
+    private boolean canScroll() {
+        View child = getChildAt(0);
+        if (child != null) {
+            int childHeight = child.getHeight();
+            return getHeight() < childHeight + mPaddingTop + mPaddingBottom;
+        }
+        return false;
+    }
+
+    /**
+     * Indicates whether this ScrollView's content is stretched to fill the viewport.
+     *
+     * @return True if the content fills the viewport, false otherwise.
+     */
+    public boolean isFillViewport() {
+        return mFillViewport;
+    }
+
+    /**
+     * Indicates this ScrollView whether it should stretch its content height to fill
+     * the viewport or not.
+     *
+     * @param fillViewport True to stretch the content's height to the viewport's
+     *        boundaries, false otherwise.
+     */
+    public void setFillViewport(boolean fillViewport) {
+        if (fillViewport != mFillViewport) {
+            mFillViewport = fillViewport;
+            requestLayout();
+        }
+    }
+
+    /**
+     * @return Whether arrow scrolling will animate its transition.
+     */
+    public boolean isSmoothScrollingEnabled() {
+        return mSmoothScrollingEnabled;
+    }
+
+    /**
+     * Set whether arrow scrolling will animate its transition.
+     * @param smoothScrollingEnabled whether arrow scrolling will animate its transition
+     */
+    public void setSmoothScrollingEnabled(boolean smoothScrollingEnabled) {
+        mSmoothScrollingEnabled = smoothScrollingEnabled;
+    }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+
+        if (!mFillViewport) {
+            return;
+        }
+
+        final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
+        if (heightMode == MeasureSpec.UNSPECIFIED) {
+            return;
+        }
+
+        final View child = getChildAt(0);
+        int height = getMeasuredHeight();
+        if (child.getMeasuredHeight() < height) {
+            final FrameLayout.LayoutParams lp = (LayoutParams) child.getLayoutParams();
+
+            int childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec, mPaddingLeft
+                    + mPaddingRight, lp.width);
+            height -= mPaddingTop;
+            height -= mPaddingBottom;
+            int childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);
+
+            child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
+        }
+    }
+
+    @Override
+    public boolean dispatchKeyEvent(KeyEvent event) {
+        // Let the focused view and/or our descendants get the key first
+        boolean handled = super.dispatchKeyEvent(event);
+        if (handled) {
+            return true;
+        }
+        return executeKeyEvent(event);
+    }
+
+    /**
+     * You can call this function yourself to have the scroll view perform
+     * scrolling from a key event, just as if the event had been dispatched to
+     * it by the view hierarchy.
+     *
+     * @param event The key event to execute.
+     * @return Return true if the event was handled, else false.
+     */
+    public boolean executeKeyEvent(KeyEvent event) {
+        mTempRect.setEmpty();
+
+        if (!canScroll()) {
+            if (isFocused()) {
+                View currentFocused = findFocus();
+                if (currentFocused == this) currentFocused = null;
+                View nextFocused = FocusFinder.getInstance().findNextFocus(this,
+                        currentFocused, View.FOCUS_DOWN);
+                return nextFocused != null
+                        && nextFocused != this
+                        && nextFocused.requestFocus(View.FOCUS_DOWN);
+            }
+            return false;
+        }
+
+        boolean handled = false;
+        if (event.getAction() == KeyEvent.ACTION_DOWN) {
+            switch (event.getKeyCode()) {
+                case KeyEvent.KEYCODE_DPAD_UP:
+                    if (!event.isAltPressed()) {
+                        handled = arrowScroll(View.FOCUS_UP);
+                    } else {
+                        handled = fullScroll(View.FOCUS_UP);
+                    }
+                    break;
+                case KeyEvent.KEYCODE_DPAD_DOWN:
+                    if (!event.isAltPressed()) {
+                        handled = arrowScroll(View.FOCUS_DOWN);
+                    } else {
+                        handled = fullScroll(View.FOCUS_DOWN);
+                    }
+                    break;
+                case KeyEvent.KEYCODE_SPACE:
+                    pageScroll(event.isShiftPressed() ? View.FOCUS_UP : View.FOCUS_DOWN);
+                    break;
+            }
+        }
+
+        return handled;
+    }
+
+    @Override
+    public boolean onInterceptTouchEvent(MotionEvent ev) {
+        /*
+         * This method JUST determines whether we want to intercept the motion.
+         * If we return true, onMotionEvent will be called and we do the actual
+         * scrolling there.
+         */
+
+        /*
+        * Shortcut the most recurring case: the user is in the dragging
+        * state and he is moving his finger.  We want to intercept this
+        * motion.
+        */
+        final int action = ev.getAction();
+        if ((action == MotionEvent.ACTION_MOVE) && (mIsBeingDragged)) {
+            return true;
+        }
+
+        if (!canScroll()) {
+            mIsBeingDragged = false;
+            return false;
+        }
+
+        final float y = ev.getY();
+
+        switch (action) {
+            case MotionEvent.ACTION_MOVE:
+                /*
+                 * mIsBeingDragged == false, otherwise the shortcut would have caught it. Check
+                 * whether the user has moved far enough from his original down touch.
+                 */
+
+                /*
+                * Locally do absolute value. mLastMotionY is set to the y value
+                * of the down event.
+                */
+                final int yDiff = (int) Math.abs(y - mLastMotionY);
+                if (yDiff > mTouchSlop) {
+                    mIsBeingDragged = true;
+                }
+                break;
+
+            case MotionEvent.ACTION_DOWN:
+                /* Remember location of down touch */
+                mLastMotionY = y;
+
+                /*
+                * If being flinged and user touches the screen, initiate drag;
+                * otherwise don't.  mScroller.isFinished should be false when
+                * being flinged.
+                */
+                mIsBeingDragged = !mScroller.isFinished();
+                break;
+
+            case MotionEvent.ACTION_CANCEL:
+            case MotionEvent.ACTION_UP:
+                /* Release the drag */
+                mIsBeingDragged = false;
+                break;
+        }
+
+        /*
+        * The only time we want to intercept motion events is if we are in the
+        * drag mode.
+        */
+        return mIsBeingDragged;
+    }
+
+    @Override
+    public boolean onTouchEvent(MotionEvent ev) {
+
+        if (ev.getAction() == MotionEvent.ACTION_DOWN && ev.getEdgeFlags() != 0) {
+            // Don't handle edge touches immediately -- they may actually belong to one of our
+            // descendants.
+            return false;
+        }
+        
+        if (!canScroll()) {
+            return false;
+        }
+
+        if (mVelocityTracker == null) {
+            mVelocityTracker = VelocityTracker.obtain();
+        }
+        mVelocityTracker.addMovement(ev);
+
+        final int action = ev.getAction();
+        final float y = ev.getY();
+
+        switch (action) {
+            case MotionEvent.ACTION_DOWN:
+                /*
+                * If being flinged and user touches, stop the fling. isFinished
+                * will be false if being flinged.
+                */
+                if (!mScroller.isFinished()) {
+                    mScroller.abortAnimation();
+                }
+
+                // Remember where the motion event started
+                mLastMotionY = y;
+                break;
+            case MotionEvent.ACTION_MOVE:
+                // Scroll to follow the motion event
+                final int deltaY = (int) (mLastMotionY - y);
+                mLastMotionY = y;
+
+                if (deltaY < 0) {
+                    if (mScrollY > 0) {
+                        scrollBy(0, deltaY);
+                    }
+                } else if (deltaY > 0) {
+                    final int bottomEdge = getHeight() - mPaddingBottom;
+                    final int availableToScroll = getChildAt(0).getBottom() - mScrollY - bottomEdge;
+                    if (availableToScroll > 0) {
+                        scrollBy(0, Math.min(availableToScroll, deltaY));
+                    }
+                }
+                break;
+            case MotionEvent.ACTION_UP:
+                final VelocityTracker velocityTracker = mVelocityTracker;
+                velocityTracker.computeCurrentVelocity(1000);
+                int initialVelocity = (int) velocityTracker.getYVelocity();
+
+                if ((Math.abs(initialVelocity) >
+                        ViewConfiguration.get(mContext).getScaledMinimumFlingVelocity()) &&
+                        getChildCount() > 0) {
+                    fling(-initialVelocity);
+                }
+
+                if (mVelocityTracker != null) {
+                    mVelocityTracker.recycle();
+                    mVelocityTracker = null;
+                }
+        }
+        return true;
+    }
+
+    /**
+     * <p>
+     * Finds the next focusable component that fits in this View's bounds
+     * (excluding fading edges) pretending that this View's top is located at
+     * the parameter top.
+     * </p>
+     *
+     * @param topFocus           look for a candidate is the one at the top of the bounds
+     *                           if topFocus is true, or at the bottom of the bounds if topFocus is
+     *                           false
+     * @param top                the top offset of the bounds in which a focusable must be
+     *                           found (the fading edge is assumed to start at this position)
+     * @param preferredFocusable the View that has highest priority and will be
+     *                           returned if it is within my bounds (null is valid)
+     * @return the next focusable component in the bounds or null if none can be
+     *         found
+     */
+    private View findFocusableViewInMyBounds(final boolean topFocus,
+            final int top, View preferredFocusable) {
+        /*
+         * The fading edge's transparent side should be considered for focus
+         * since it's mostly visible, so we divide the actual fading edge length
+         * by 2.
+         */
+        final int fadingEdgeLength = getVerticalFadingEdgeLength() / 2;
+        final int topWithoutFadingEdge = top + fadingEdgeLength;
+        final int bottomWithoutFadingEdge = top + getHeight() - fadingEdgeLength;
+
+        if ((preferredFocusable != null)
+                && (preferredFocusable.getTop() < bottomWithoutFadingEdge)
+                && (preferredFocusable.getBottom() > topWithoutFadingEdge)) {
+            return preferredFocusable;
+        }
+
+        return findFocusableViewInBounds(topFocus, topWithoutFadingEdge,
+                bottomWithoutFadingEdge);
+    }
+
+    /**
+     * <p>
+     * Finds the next focusable component that fits in the specified bounds.
+     * </p>
+     *
+     * @param topFocus look for a candidate is the one at the top of the bounds
+     *                 if topFocus is true, or at the bottom of the bounds if topFocus is
+     *                 false
+     * @param top      the top offset of the bounds in which a focusable must be
+     *                 found
+     * @param bottom   the bottom offset of the bounds in which a focusable must
+     *                 be found
+     * @return the next focusable component in the bounds or null if none can
+     *         be found
+     */
+    private View findFocusableViewInBounds(boolean topFocus, int top, int bottom) {
+
+        List<View> focusables = getFocusables(View.FOCUS_FORWARD);
+        View focusCandidate = null;
+
+        /*
+         * A fully contained focusable is one where its top is below the bound's
+         * top, and its bottom is above the bound's bottom. A partially
+         * contained focusable is one where some part of it is within the
+         * bounds, but it also has some part that is not within bounds.  A fully contained
+         * focusable is preferred to a partially contained focusable.
+         */
+        boolean foundFullyContainedFocusable = false;
+
+        int count = focusables.size();
+        for (int i = 0; i < count; i++) {
+            View view = focusables.get(i);
+            int viewTop = view.getTop();
+            int viewBottom = view.getBottom();
+
+            if (top < viewBottom && viewTop < bottom) {
+                /*
+                 * the focusable is in the target area, it is a candidate for
+                 * focusing
+                 */
+
+                final boolean viewIsFullyContained = (top < viewTop) &&
+                        (viewBottom < bottom);
+
+                if (focusCandidate == null) {
+                    /* No candidate, take this one */
+                    focusCandidate = view;
+                    foundFullyContainedFocusable = viewIsFullyContained;
+                } else {
+                    final boolean viewIsCloserToBoundary =
+                            (topFocus && viewTop < focusCandidate.getTop()) ||
+                                    (!topFocus && viewBottom > focusCandidate
+                                            .getBottom());
+
+                    if (foundFullyContainedFocusable) {
+                        if (viewIsFullyContained && viewIsCloserToBoundary) {
+                            /*
+                             * We're dealing with only fully contained views, so
+                             * it has to be closer to the boundary to beat our
+                             * candidate
+                             */
+                            focusCandidate = view;
+                        }
+                    } else {
+                        if (viewIsFullyContained) {
+                            /* Any fully contained view beats a partially contained view */
+                            focusCandidate = view;
+                            foundFullyContainedFocusable = true;
+                        } else if (viewIsCloserToBoundary) {
+                            /*
+                             * Partially contained view beats another partially
+                             * contained view if it's closer
+                             */
+                            focusCandidate = view;
+                        }
+                    }
+                }
+            }
+        }
+
+        return focusCandidate;
+    }
+
+    /**
+     * <p>Handles scrolling in response to a "page up/down" shortcut press. This
+     * method will scroll the view by one page up or down and give the focus
+     * to the topmost/bottommost component in the new visible area. If no
+     * component is a good candidate for focus, this scrollview reclaims the
+     * focus.</p>
+     *
+     * @param direction the scroll direction: {@link android.view.View#FOCUS_UP}
+     *                  to go one page up or
+     *                  {@link android.view.View#FOCUS_DOWN} to go one page down
+     * @return true if the key event is consumed by this method, false otherwise
+     */
+    public boolean pageScroll(int direction) {
+        boolean down = direction == View.FOCUS_DOWN;
+        int height = getHeight();
+
+        if (down) {
+            mTempRect.top = getScrollY() + height;
+            int count = getChildCount();
+            if (count > 0) {
+                View view = getChildAt(count - 1);
+                if (mTempRect.top + height > view.getBottom()) {
+                    mTempRect.top = view.getBottom() - height;
+                }
+            }
+        } else {
+            mTempRect.top = getScrollY() - height;
+            if (mTempRect.top < 0) {
+                mTempRect.top = 0;
+            }
+        }
+        mTempRect.bottom = mTempRect.top + height;
+
+        return scrollAndFocus(direction, mTempRect.top, mTempRect.bottom);
+    }
+
+    /**
+     * <p>Handles scrolling in response to a "home/end" shortcut press. This
+     * method will scroll the view to the top or bottom and give the focus
+     * to the topmost/bottommost component in the new visible area. If no
+     * component is a good candidate for focus, this scrollview reclaims the
+     * focus.</p>
+     *
+     * @param direction the scroll direction: {@link android.view.View#FOCUS_UP}
+     *                  to go the top of the view or
+     *                  {@link android.view.View#FOCUS_DOWN} to go the bottom
+     * @return true if the key event is consumed by this method, false otherwise
+     */
+    public boolean fullScroll(int direction) {
+        boolean down = direction == View.FOCUS_DOWN;
+        int height = getHeight();
+
+        mTempRect.top = 0;
+        mTempRect.bottom = height;
+
+        if (down) {
+            int count = getChildCount();
+            if (count > 0) {
+                View view = getChildAt(count - 1);
+                mTempRect.bottom = view.getBottom();
+                mTempRect.top = mTempRect.bottom - height;
+            }
+        }
+
+        return scrollAndFocus(direction, mTempRect.top, mTempRect.bottom);
+    }
+
+    /**
+     * <p>Scrolls the view to make the area defined by <code>top</code> and
+     * <code>bottom</code> visible. This method attempts to give the focus
+     * to a component visible in this area. If no component can be focused in
+     * the new visible area, the focus is reclaimed by this scrollview.</p>
+     *
+     * @param direction the scroll direction: {@link android.view.View#FOCUS_UP}
+     *                  to go upward
+     *                  {@link android.view.View#FOCUS_DOWN} to downward
+     * @param top       the top offset of the new area to be made visible
+     * @param bottom    the bottom offset of the new area to be made visible
+     * @return true if the key event is consumed by this method, false otherwise
+     */
+    private boolean scrollAndFocus(int direction, int top, int bottom) {
+        boolean handled = true;
+
+        int height = getHeight();
+        int containerTop = getScrollY();
+        int containerBottom = containerTop + height;
+        boolean up = direction == View.FOCUS_UP;
+
+        View newFocused = findFocusableViewInBounds(up, top, bottom);
+        if (newFocused == null) {
+            newFocused = this;
+        }
+
+        if (top >= containerTop && bottom <= containerBottom) {
+            handled = false;
+        } else {
+            int delta = up ? (top - containerTop) : (bottom - containerBottom);
+            doScrollY(delta);
+        }
+
+        if (newFocused != findFocus() && newFocused.requestFocus(direction)) {
+            mScrollViewMovedFocus = true;
+            mScrollViewMovedFocus = false;
+        }
+
+        return handled;
+    }
+
+    /**
+     * Handle scrolling in response to an up or down arrow click.
+     *
+     * @param direction The direction corresponding to the arrow key that was
+     *                  pressed
+     * @return True if we consumed the event, false otherwise
+     */
+    public boolean arrowScroll(int direction) {
+
+        View currentFocused = findFocus();
+        if (currentFocused == this) currentFocused = null;
+
+        View nextFocused = FocusFinder.getInstance().findNextFocus(this, currentFocused, direction);
+
+        final int maxJump = getMaxScrollAmount();
+
+        if (nextFocused != null && isWithinDeltaOfScreen(nextFocused, maxJump)) {
+            nextFocused.getDrawingRect(mTempRect);
+            offsetDescendantRectToMyCoords(nextFocused, mTempRect);
+            int scrollDelta = computeScrollDeltaToGetChildRectOnScreen(mTempRect);
+            doScrollY(scrollDelta);
+            nextFocused.requestFocus(direction);
+        } else {
+            // no new focus
+            int scrollDelta = maxJump;
+
+            if (direction == View.FOCUS_UP && getScrollY() < scrollDelta) {
+                scrollDelta = getScrollY();
+            } else if (direction == View.FOCUS_DOWN) {
+
+                int daBottom = getChildAt(getChildCount() - 1).getBottom();
+
+                int screenBottom = getScrollY() + getHeight();
+
+                if (daBottom - screenBottom < maxJump) {
+                    scrollDelta = daBottom - screenBottom;
+                }
+            }
+            if (scrollDelta == 0) {
+                return false;
+            }
+            doScrollY(direction == View.FOCUS_DOWN ? scrollDelta : -scrollDelta);
+        }
+
+        if (currentFocused != null && currentFocused.isFocused()
+                && isOffScreen(currentFocused)) {
+            // previously focused item still has focus and is off screen, give
+            // it up (take it back to ourselves)
+            // (also, need to temporarily force FOCUS_BEFORE_DESCENDANTS so we are
+            // sure to
+            // get it)
+            final int descendantFocusability = getDescendantFocusability();  // save
+            setDescendantFocusability(ViewGroup.FOCUS_BEFORE_DESCENDANTS);
+            requestFocus();
+            setDescendantFocusability(descendantFocusability);  // restore
+        }
+        return true;
+    }
+
+    /**
+     * @return whether the descendant of this scroll view is scrolled off
+     *  screen.
+     */
+    private boolean isOffScreen(View descendant) {
+        return !isWithinDeltaOfScreen(descendant, 0);
+    }
+
+    /**
+     * @return whether the descendant of this scroll view is within delta
+     *  pixels of being on the screen.
+     */
+    private boolean isWithinDeltaOfScreen(View descendant, int delta) {
+        descendant.getDrawingRect(mTempRect);
+        offsetDescendantRectToMyCoords(descendant, mTempRect);
+
+        return (mTempRect.bottom + delta) >= getScrollY()
+                && (mTempRect.top - delta) <= (getScrollY() + getHeight());
+    }
+
+    /**
+     * Smooth scroll by a Y delta
+     *
+     * @param delta the number of pixels to scroll by on the Y axis
+     */
+    private void doScrollY(int delta) {
+        if (delta != 0) {
+            if (mSmoothScrollingEnabled) {
+                smoothScrollBy(0, delta);
+            } else {
+                scrollBy(0, delta);
+            }
+        }
+    }
+
+    /**
+     * Like {@link View#scrollBy}, but scroll smoothly instead of immediately.
+     *
+     * @param dx the number of pixels to scroll by on the X axis
+     * @param dy the number of pixels to scroll by on the Y axis
+     */
+    public final void smoothScrollBy(int dx, int dy) {
+        long duration = AnimationUtils.currentAnimationTimeMillis() - mLastScroll;
+        if (duration > ANIMATED_SCROLL_GAP) {
+            if (localLOGV) Log.v(TAG, "Smooth scroll: mScrollY=" + mScrollY
+                    + " dy=" + dy);
+            mScroller.startScroll(mScrollX, mScrollY, dx, dy);
+            invalidate();
+        } else {
+            if (!mScroller.isFinished()) {
+                mScroller.abortAnimation();
+            }
+            if (localLOGV) Log.v(TAG, "Immediate scroll: mScrollY=" + mScrollY
+                    + " dy=" + dy);
+            scrollBy(dx, dy);
+        }
+        mLastScroll = AnimationUtils.currentAnimationTimeMillis();
+    }
+
+    /**
+     * Like {@link #scrollTo}, but scroll smoothly instead of immediately.
+     *
+     * @param x the position where to scroll on the X axis
+     * @param y the position where to scroll on the Y axis
+     */
+    public final void smoothScrollTo(int x, int y) {
+        smoothScrollBy(x - mScrollX, y - mScrollY);
+    }
+
+    /**
+     * <p>The scroll range of a scroll view is the overall height of all of its
+     * children.</p>
+     */
+    @Override
+    protected int computeVerticalScrollRange() {
+        int count = getChildCount();
+        return count == 0 ? getHeight() : (getChildAt(0)).getBottom();
+    }
+
+
+    @Override
+    protected void measureChild(View child, int parentWidthMeasureSpec, int parentHeightMeasureSpec) {
+        ViewGroup.LayoutParams lp = child.getLayoutParams();
+
+        int childWidthMeasureSpec;
+        int childHeightMeasureSpec;
+
+        childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec, mPaddingLeft
+                + mPaddingRight, lp.width);
+
+        childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
+
+        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
+    }
+
+    @Override
+    protected void measureChildWithMargins(View child, int parentWidthMeasureSpec, int widthUsed,
+            int parentHeightMeasureSpec, int heightUsed) {
+        final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
+
+        final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
+                mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin
+                        + widthUsed, lp.width);
+        final int childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(
+                lp.topMargin + lp.bottomMargin, MeasureSpec.UNSPECIFIED);
+
+        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
+    }
+
+    @Override
+    public void computeScroll() {
+        if (mScroller.computeScrollOffset()) {
+            // This is called at drawing time by ViewGroup.  We don't want to
+            // re-show the scrollbars at this point, which scrollTo will do,
+            // so we replicate most of scrollTo here.
+            //
+            //         It's a little odd to call onScrollChanged from inside the drawing.
+            //
+            //         It is, except when you remember that computeScroll() is used to
+            //         animate scrolling. So unless we want to defer the onScrollChanged()
+            //         until the end of the animated scrolling, we don't really have a
+            //         choice here.
+            //
+            //         I agree.  The alternative, which I think would be worse, is to post
+            //         something and tell the subclasses later.  This is bad because there
+            //         will be a window where mScrollX/Y is different from what the app
+            //         thinks it is.
+            //
+            int oldX = mScrollX;
+            int oldY = mScrollY;
+            int x = mScroller.getCurrX();
+            int y = mScroller.getCurrY();
+            if (getChildCount() > 0) {
+                View child = getChildAt(0);
+                mScrollX = clamp(x, getWidth() - mPaddingRight - mPaddingLeft, child.getWidth());
+                mScrollY = clamp(y, getHeight() - mPaddingBottom - mPaddingTop, child.getHeight());
+                if (localLOGV) Log.v(TAG, "mScrollY=" + mScrollY + " y=" + y
+                        + " height=" + this.getHeight()
+                        + " child height=" + child.getHeight());
+            } else {
+                mScrollX = x;
+                mScrollY = y;
+            }            
+            if (oldX != mScrollX || oldY != mScrollY) {
+                onScrollChanged(mScrollX, mScrollY, oldX, oldY);
+            }
+            
+            // Keep on drawing until the animation has finished.
+            postInvalidate();
+        }
+    }
+
+    /**
+     * Scrolls the view to the given child.
+     *
+     * @param child the View to scroll to
+     */
+    private void scrollToChild(View child) {
+        child.getDrawingRect(mTempRect);
+
+        /* Offset from child's local coordinates to ScrollView coordinates */
+        offsetDescendantRectToMyCoords(child, mTempRect);
+
+        int scrollDelta = computeScrollDeltaToGetChildRectOnScreen(mTempRect);
+
+        if (scrollDelta != 0) {
+            scrollBy(0, scrollDelta);
+        }
+    }
+
+    /**
+     * If rect is off screen, scroll just enough to get it (or at least the
+     * first screen size chunk of it) on screen.
+     *
+     * @param rect      The rectangle.
+     * @param immediate True to scroll immediately without animation
+     * @return true if scrolling was performed
+     */
+    private boolean scrollToChildRect(Rect rect, boolean immediate) {
+        final int delta = computeScrollDeltaToGetChildRectOnScreen(rect);
+        final boolean scroll = delta != 0;
+        if (scroll) {
+            if (immediate) {
+                scrollBy(0, delta);
+            } else {
+                smoothScrollBy(0, delta);
+            }
+        }
+        return scroll;
+    }
+
+    /**
+     * Compute the amount to scroll in the Y direction in order to get
+     * a rectangle completely on the screen (or, if taller than the screen,
+     * at least the first screen size chunk of it).
+     *
+     * @param rect The rect.
+     * @return The scroll delta.
+     */
+    protected int computeScrollDeltaToGetChildRectOnScreen(Rect rect) {
+
+        int height = getHeight();
+        int screenTop = getScrollY();
+        int screenBottom = screenTop + height;
+
+        int fadingEdge = getVerticalFadingEdgeLength();
+
+        // leave room for top fading edge as long as rect isn't at very top
+        if (rect.top > 0) {
+            screenTop += fadingEdge;
+        }
+
+        // leave room for bottom fading edge as long as rect isn't at very bottom
+        if (rect.bottom < getChildAt(0).getHeight()) {
+            screenBottom -= fadingEdge;
+        }
+
+        int scrollYDelta = 0;
+
+        if (localLOGV) Log.v(TAG, "child=" + rect.toShortString()
+                + " screenTop=" + screenTop + " screenBottom=" + screenBottom
+                + " height=" + height);
+        if (rect.bottom > screenBottom && rect.top > screenTop) {
+            // need to move down to get it in view: move down just enough so
+            // that the entire rectangle is in view (or at least the first
+            // screen size chunk).
+
+            if (rect.height() > height) {
+                // just enough to get screen size chunk on
+                scrollYDelta += (rect.top - screenTop);
+            } else {
+                // get entire rect at bottom of screen
+                scrollYDelta += (rect.bottom - screenBottom);
+            }
+
+            // make sure we aren't scrolling beyond the end of our content
+            int bottom = getChildAt(getChildCount() - 1).getBottom();
+            int distanceToBottom = bottom - screenBottom;
+            if (localLOGV) Log.v(TAG, "scrollYDelta=" + scrollYDelta
+                    + " distanceToBottom=" + distanceToBottom);
+            scrollYDelta = Math.min(scrollYDelta, distanceToBottom);
+
+        } else if (rect.top < screenTop && rect.bottom < screenBottom) {
+            // need to move up to get it in view: move up just enough so that
+            // entire rectangle is in view (or at least the first screen
+            // size chunk of it).
+
+            if (rect.height() > height) {
+                // screen size chunk
+                scrollYDelta -= (screenBottom - rect.bottom);
+            } else {
+                // entire rect at top
+                scrollYDelta -= (screenTop - rect.top);
+            }
+
+            // make sure we aren't scrolling any further than the top our content
+            scrollYDelta = Math.max(scrollYDelta, -getScrollY());
+        }
+        return scrollYDelta;
+    }
+
+    @Override
+    public void requestChildFocus(View child, View focused) {
+        if (!mScrollViewMovedFocus) {
+            if (!mIsLayoutDirty) {
+                scrollToChild(focused);
+            } else {
+                // The child may not be laid out yet, we can't compute the scroll yet
+                mChildToScrollTo = focused;
+            }
+        }
+        super.requestChildFocus(child, focused);
+    }
+
+
+    /**
+     * When looking for focus in children of a scroll view, need to be a little
+     * more careful not to give focus to something that is scrolled off screen.
+     *
+     * This is more expensive than the default {@link android.view.ViewGroup}
+     * implementation, otherwise this behavior might have been made the default.
+     */
+    @Override
+    protected boolean onRequestFocusInDescendants(int direction,
+            Rect previouslyFocusedRect) {
+
+        // convert from forward / backward notation to up / down / left / right
+        // (ugh).
+        if (direction == View.FOCUS_FORWARD) {
+            direction = View.FOCUS_DOWN;
+        } else if (direction == View.FOCUS_BACKWARD) {
+            direction = View.FOCUS_UP;
+        }
+
+        final View nextFocus = previouslyFocusedRect == null ?
+                FocusFinder.getInstance().findNextFocus(this, null, direction) :
+                FocusFinder.getInstance().findNextFocusFromRect(this,
+                        previouslyFocusedRect, direction);
+
+        if (nextFocus == null) {
+            return false;
+        }
+
+        if (isOffScreen(nextFocus)) {
+            return false;
+        }
+
+        return nextFocus.requestFocus(direction, previouslyFocusedRect);
+    }    
+
+    @Override
+    public boolean requestChildRectangleOnScreen(View child, Rect rectangle,
+            boolean immediate) {
+        // offset into coordinate space of this scroll view
+        rectangle.offset(child.getLeft() - child.getScrollX(),
+                child.getTop() - child.getScrollY());
+
+        return scrollToChildRect(rectangle, immediate);
+    }
+
+    @Override
+    public void requestLayout() {
+        mIsLayoutDirty = true;
+        super.requestLayout();
+    }
+
+    @Override
+    protected void onLayout(boolean changed, int l, int t, int r, int b) {
+        super.onLayout(changed, l, t, r, b);
+        mIsLayoutDirty = false;
+        // Give a child focus if it needs it 
+        if (mChildToScrollTo != null && isViewDescendantOf(mChildToScrollTo, this)) {
+                scrollToChild(mChildToScrollTo);
+        }
+        mChildToScrollTo = null;
+
+        // Calling this with the present values causes it to re-clam them
+        scrollTo(mScrollX, mScrollY);
+    }
+
+    @Override
+    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
+        super.onSizeChanged(w, h, oldw, oldh);
+
+        View currentFocused = findFocus();
+        if (null == currentFocused || this == currentFocused)
+            return;
+
+        final int maxJump = mBottom - mTop;
+
+        if (isWithinDeltaOfScreen(currentFocused, maxJump)) {
+            currentFocused.getDrawingRect(mTempRect);
+            offsetDescendantRectToMyCoords(currentFocused, mTempRect);
+            int scrollDelta = computeScrollDeltaToGetChildRectOnScreen(mTempRect);
+            doScrollY(scrollDelta);
+        }
+    }    
+
+    /**
+     * Return true if child is an descendant of parent, (or equal to the parent).
+     */
+    private boolean isViewDescendantOf(View child, View parent) {
+        if (child == parent) {
+            return true;
+        }
+
+        final ViewParent theParent = child.getParent();
+        return (theParent instanceof ViewGroup) && isViewDescendantOf((View) theParent, parent);
+    }    
+
+    /**
+     * Fling the scroll view
+     *
+     * @param velocityY The initial velocity in the Y direction. Positive
+     *                  numbers mean that the finger/curor is moving down the screen,
+     *                  which means we want to scroll towards the top.
+     */
+    public void fling(int velocityY) {
+        int height = getHeight() - mPaddingBottom - mPaddingTop;
+        int bottom = getChildAt(0).getHeight();
+
+        mScroller.fling(mScrollX, mScrollY, 0, velocityY, 0, 0, 0, bottom - height);
+
+        final boolean movingDown = velocityY > 0;
+
+        View newFocused =
+                findFocusableViewInMyBounds(movingDown, mScroller.getFinalY(), findFocus());
+        if (newFocused == null) {
+            newFocused = this;
+        }
+
+        if (newFocused != findFocus()
+                && newFocused.requestFocus(movingDown ? View.FOCUS_DOWN : View.FOCUS_UP)) {
+            mScrollViewMovedFocus = true;
+            mScrollViewMovedFocus = false;
+        }
+
+        invalidate();
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p>This version also clamps the scrolling to the bounds of our child.
+     */
+    public void scrollTo(int x, int y) {
+        // we rely on the fact the View.scrollBy calls scrollTo.
+        if (getChildCount() > 0) {
+            View child = getChildAt(0);
+            x = clamp(x, getWidth() - mPaddingRight - mPaddingLeft, child.getWidth());
+            y = clamp(y, getHeight() - mPaddingBottom - mPaddingTop, child.getHeight());
+            if (x != mScrollX || y != mScrollY) {
+                super.scrollTo(x, y);
+            }
+        }
+    }
+
+    private int clamp(int n, int my, int child) {
+        if (my >= child || n < 0) {
+            /* my >= child is this case:
+             *                    |--------------- me ---------------|
+             *     |------ child ------|
+             * or
+             *     |--------------- me ---------------|
+             *            |------ child ------|
+             * or
+             *     |--------------- me ---------------|
+             *                                  |------ child ------|
+             *
+             * n < 0 is this case:
+             *     |------ me ------|
+             *                    |-------- child --------|
+             *     |-- mScrollX --|
+             */
+            return 0;
+        }
+        if ((my+n) > child) {
+            /* this case:
+             *                    |------ me ------|
+             *     |------ child ------|
+             *     |-- mScrollX --|
+             */
+            return child-my;
+        }
+        return n;
+    }
+}
diff --git a/core/java/android/widget/Scroller.java b/core/java/android/widget/Scroller.java
new file mode 100644
index 0000000..febc956
--- /dev/null
+++ b/core/java/android/widget/Scroller.java
@@ -0,0 +1,388 @@
+/*
+ * 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 android.widget;
+
+import android.content.Context;
+import android.view.ViewConfiguration;
+import android.view.animation.AnimationUtils;
+import android.view.animation.Interpolator;
+
+
+/**
+ * This class encapsulates scrolling.  The duration of the scroll
+ * can be passed in the constructor and specifies the maximum time that
+ * the scrolling animation should take.  Past this time, the scrolling is 
+ * automatically moved to its final stage and computeScrollOffset()
+ * will always return false to indicate that scrolling is over.
+ */
+public class Scroller  {
+    private int mMode;
+
+    private int mStartX;
+    private int mStartY;
+    private int mFinalX;
+    private int mFinalY;
+
+    private int mMinX;
+    private int mMaxX;
+    private int mMinY;
+    private int mMaxY;
+
+    private int mCurrX;
+    private int mCurrY;
+    private long mStartTime;
+    private int mDuration;
+    private float mDurationReciprocal;
+    private float mDeltaX;
+    private float mDeltaY;
+    private float mViscousFluidScale;
+    private float mViscousFluidNormalize;
+    private boolean mFinished;
+    private Interpolator mInterpolator;
+
+    private float mCoeffX = 0.0f;
+    private float mCoeffY = 1.0f;
+    private float mVelocity;
+
+    private static final int DEFAULT_DURATION = 250;
+    private static final int SCROLL_MODE = 0;
+    private static final int FLING_MODE = 1;
+
+    private final float mDeceleration;
+
+    /**
+     * Create a Scroller with the default duration and interpolator.
+     */
+    public Scroller(Context context) {
+        this(context, null);
+    }
+
+    /**
+     * Create a Scroller with the specified interpolator. If the interpolator is
+     * null, the default (viscous) interpolator will be used.
+     */
+    public Scroller(Context context, Interpolator interpolator) {
+        mFinished = true;
+        mInterpolator = interpolator;
+        float ppi = context.getResources().getDisplayMetrics().density * 160.0f;
+        mDeceleration = 9.8f   // g (m/s^2)
+                      * 39.37f // inch/meter
+                      * ppi    // pixels per inch
+                      * ViewConfiguration.getScrollFriction();
+    }
+    
+    /**
+     * 
+     * Returns whether the scroller has finished scrolling.
+     * 
+     * @return True if the scroller has finished scrolling, false otherwise.
+     */
+    public final boolean isFinished() {
+        return mFinished;
+    }
+    
+    /**
+     * Force the finished field to a particular value.
+     *  
+     * @param finished The new finished value.
+     */
+    public final void forceFinished(boolean finished) {
+        mFinished = finished;
+    }
+    
+    /**
+     * Returns how long the scroll event will take, in milliseconds.
+     * 
+     * @return The duration of the scroll in milliseconds.
+     */
+    public final int getDuration() {
+        return mDuration;
+    }
+    
+    /**
+     * Returns the current X offset in the scroll. 
+     * 
+     * @return The new X offset as an absolute distance from the origin.
+     */
+    public final int getCurrX() {
+        return mCurrX;
+    }
+    
+    /**
+     * Returns the current Y offset in the scroll. 
+     * 
+     * @return The new Y offset as an absolute distance from the origin.
+     */
+    public final int getCurrY() {
+        return mCurrY;
+    }
+    
+    /**
+     * Returns the start X offset in the scroll. 
+     * 
+     * @return The start X offset as an absolute distance from the origin.
+     * @hide pending API council
+     */
+    public final int getStartX() {
+        return mStartX;
+    }
+    
+    /**
+     * Returns the start Y offset in the scroll. 
+     * 
+     * @return The start Y offset as an absolute distance from the origin.
+     * @hide pending API council
+     */
+    public final int getStartY() {
+        return mStartY;
+    }
+    
+    /**
+     * Returns where the scroll will end. Valid only for "fling" scrolls.
+     * 
+     * @return The final X offset as an absolute distance from the origin.
+     */
+    public final int getFinalX() {
+        return mFinalX;
+    }
+    
+    /**
+     * Returns where the scroll will end. Valid only for "fling" scrolls.
+     * 
+     * @return The final Y offset as an absolute distance from the origin.
+     */
+    public final int getFinalY() {
+        return mFinalY;
+    }
+
+    /**
+     * Call this when you want to know the new location.  If it returns true,
+     * the animation is not yet finished.  loc will be altered to provide the
+     * new location.
+     */ 
+    public boolean computeScrollOffset() {
+        if (mFinished) {
+            return false;
+        }
+
+        int timePassed = (int)(AnimationUtils.currentAnimationTimeMillis() - mStartTime);
+    
+        if (timePassed < mDuration) {
+            switch (mMode) {
+            case SCROLL_MODE:
+                float x = (float)timePassed * mDurationReciprocal;
+    
+                if (mInterpolator == null)
+                    x = viscousFluid(x); 
+                else
+                    x = mInterpolator.getInterpolation(x);
+    
+                mCurrX = mStartX + Math.round(x * mDeltaX);
+                mCurrY = mStartY + Math.round(x * mDeltaY);
+                if ((mCurrX == mFinalX) && (mCurrY == mFinalY)) {
+                    mFinished = true;
+                }
+                break;
+            case FLING_MODE:
+                float timePassedSeconds = timePassed / 1000.0f;
+                float distance = (mVelocity * timePassedSeconds)
+                        - (mDeceleration * timePassedSeconds * timePassedSeconds / 2.0f);
+                
+                mCurrX = mStartX + Math.round(distance * mCoeffX);
+                // Pin to mMinX <= mCurrX <= mMaxX
+                mCurrX = Math.min(mCurrX, mMaxX);
+                mCurrX = Math.max(mCurrX, mMinX);
+                
+                mCurrY = mStartY + Math.round(distance * mCoeffY);
+                // Pin to mMinY <= mCurrY <= mMaxY
+                mCurrY = Math.min(mCurrY, mMaxY);
+                mCurrY = Math.max(mCurrY, mMinY);
+
+                if (mCurrX == mFinalX && mCurrY == mFinalY) {
+                    mFinished = true;
+                }
+                
+                break;
+            }
+        }
+        else {
+            mCurrX = mFinalX;
+            mCurrY = mFinalY;
+            mFinished = true;
+        }
+        return true;
+    }
+    
+    /**
+     * Start scrolling by providing a starting point and the distance to travel.
+     * The scroll will use the default value of 250 milliseconds for the
+     * duration.
+     * 
+     * @param startX Starting horizontal scroll offset in pixels. Positive
+     *        numbers will scroll the content to the left.
+     * @param startY Starting vertical scroll offset in pixels. Positive numbers
+     *        will scroll the content up.
+     * @param dx Horizontal distance to travel. Positive numbers will scroll the
+     *        content to the left.
+     * @param dy Vertical distance to travel. Positive numbers will scroll the
+     *        content up.
+     */
+    public void startScroll(int startX, int startY, int dx, int dy) {
+        startScroll(startX, startY, dx, dy, DEFAULT_DURATION);
+    }
+
+    /**
+     * Start scrolling by providing a starting point and the distance to travel.
+     * 
+     * @param startX Starting horizontal scroll offset in pixels. Positive
+     *        numbers will scroll the content to the left.
+     * @param startY Starting vertical scroll offset in pixels. Positive numbers
+     *        will scroll the content up.
+     * @param dx Horizontal distance to travel. Positive numbers will scroll the
+     *        content to the left.
+     * @param dy Vertical distance to travel. Positive numbers will scroll the
+     *        content up.
+     * @param duration Duration of the scroll in milliseconds.
+     */
+    public void startScroll(int startX, int startY, int dx, int dy, int duration) {
+        mMode = SCROLL_MODE;
+        mFinished = false;
+        mDuration = duration;
+        mStartTime = AnimationUtils.currentAnimationTimeMillis();
+        mStartX = startX;
+        mStartY = startY;
+        mFinalX = startX + dx;
+        mFinalY = startY + dy;
+        mDeltaX = dx;
+        mDeltaY = dy;
+        mDurationReciprocal = 1.0f / (float) mDuration;
+        // This controls the viscous fluid effect (how much of it)
+        mViscousFluidScale = 8.0f;
+        // must be set to 1.0 (used in viscousFluid())
+        mViscousFluidNormalize = 1.0f;
+        mViscousFluidNormalize = 1.0f / viscousFluid(1.0f);
+    }
+
+    /**
+     * Start scrolling based on a fling gesture. The distance travelled will
+     * depend on the initial velocity of the fling.
+     * 
+     * @param startX Starting point of the scroll (X)
+     * @param startY Starting point of the scroll (Y)
+     * @param velocityX Initial velocity of the fling (X) measured in pixels per
+     *        second.
+     * @param velocityY Initial velocity of the fling (Y) measured in pixels per
+     *        second
+     * @param minX Minimum X value. The scroller will not scroll past this
+     *        point.
+     * @param maxX Maximum X value. The scroller will not scroll past this
+     *        point.
+     * @param minY Minimum Y value. The scroller will not scroll past this
+     *        point.
+     * @param maxY Maximum Y value. The scroller will not scroll past this
+     *        point.
+     */
+    public void fling(int startX, int startY, int velocityX, int velocityY,
+            int minX, int maxX, int minY, int maxY) {
+        mMode = FLING_MODE;
+        mFinished = false;
+
+        float velocity = (float)Math.hypot(velocityX, velocityY);
+     
+        mVelocity = velocity;
+        mDuration = (int) (1000 * velocity / mDeceleration); // Duration is in
+                                                            // milliseconds
+        mStartTime = AnimationUtils.currentAnimationTimeMillis();
+        mStartX = startX;
+        mStartY = startY;
+
+        mCoeffX = velocity == 0 ? 1.0f : velocityX / velocity; 
+        mCoeffY = velocity == 0 ? 1.0f : velocityY / velocity;
+
+        int totalDistance = (int) ((velocity * velocity) / (2 * mDeceleration));
+        
+        mMinX = minX;
+        mMaxX = maxX;
+        mMinY = minY;
+        mMaxY = maxY;
+        
+        
+        mFinalX = startX + Math.round(totalDistance * mCoeffX);
+        // Pin to mMinX <= mFinalX <= mMaxX
+        mFinalX = Math.min(mFinalX, mMaxX);
+        mFinalX = Math.max(mFinalX, mMinX);
+        
+        mFinalY = startY + Math.round(totalDistance * mCoeffY);
+        // Pin to mMinY <= mFinalY <= mMaxY
+        mFinalY = Math.min(mFinalY, mMaxY);
+        mFinalY = Math.max(mFinalY, mMinY);
+    }
+    
+    
+    
+    private float viscousFluid(float x)
+    {
+        x *= mViscousFluidScale;
+        if (x < 1.0f) {
+            x -= (1.0f - (float)Math.exp(-x));
+        } else {
+            float start = 0.36787944117f;   // 1/e == exp(-1)
+            x = 1.0f - (float)Math.exp(1.0f - x);
+            x = start + x * (1.0f - start);
+        }
+        x *= mViscousFluidNormalize;
+        return x;
+    }
+    
+    /**
+     * 
+     */
+    public void abortAnimation() {
+        mCurrX = mFinalX;
+        mCurrY = mFinalY;
+        mFinished = true;
+    }
+    
+    /**
+     * Extend the scroll animation. This allows a running animation to 
+     * scroll further and longer, when used with setFinalX() or setFinalY().
+     *
+     * @param extend Additional time to scroll in milliseconds.
+     */
+    public void extendDuration(int extend) {
+        int passed = timePassed();
+        mDuration = passed + extend;
+        mDurationReciprocal = 1.0f / (float)mDuration;
+        mFinished = false;
+    }
+    
+    public int timePassed() {
+        return (int)(AnimationUtils.currentAnimationTimeMillis() - mStartTime);
+    }
+    
+    public void setFinalX(int newX) {
+        mFinalX = newX;
+        mDeltaX = mFinalX - mStartX;
+        mFinished = false;
+    }
+
+   public void setFinalY(int newY) {
+        mFinalY = newY;
+        mDeltaY = mFinalY - mStartY;
+        mFinished = false;
+    }
+}
diff --git a/core/java/android/widget/SectionIndexer.java b/core/java/android/widget/SectionIndexer.java
new file mode 100644
index 0000000..24f894c
--- /dev/null
+++ b/core/java/android/widget/SectionIndexer.java
@@ -0,0 +1,52 @@
+/*
+ * 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 android.widget;
+
+/**
+ * Interface that should be implemented on Adapters to enable fast scrolling 
+ * in an {@link AbsListView} between sections of the list. A section is a group of list items
+ * to jump to that have something in common. For example, they may begin with the
+ * same letter or they may be songs from the same artist. 
+ */
+public interface SectionIndexer {
+    /**
+     * This provides the list view with an array of section objects. In the simplest
+     * case these are Strings, each containing one letter of the alphabet.
+     * They could be more complex objects that indicate the grouping for the adapter's
+     * consumption. The list view will call toString() on the objects to get the
+     * preview letter to display while scrolling.
+     * @return the array of objects that indicate the different sections of the list.
+     */
+    Object[] getSections();
+    
+    /**
+     * Provides the starting index in the list for a given section.
+     * @param section the index of the section to jump to.
+     * @return the starting position of that section. If the section is out of bounds, the
+     * position must be clipped to fall within the size of the list.
+     */
+    int getPositionForSection(int section);
+    
+    /**
+     * This is a reverse mapping to fetch the section index for a given position
+     * in the list.
+     * @param position the position for which to return the section
+     * @return the section index. If the position is out of bounds, the section index
+     * must be clipped to fall within the size of the section array.
+     */
+    int getSectionForPosition(int position);    
+}
diff --git a/core/java/android/widget/SeekBar.java b/core/java/android/widget/SeekBar.java
new file mode 100644
index 0000000..dfee29b
--- /dev/null
+++ b/core/java/android/widget/SeekBar.java
@@ -0,0 +1,119 @@
+/*
+ * 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 android.widget;
+
+import android.content.Context;
+import android.util.AttributeSet;
+
+
+
+/**
+ * A SeekBar is an extension of ProgressBar that adds a draggable thumb. The user can touch
+ * the thumb and drag left or right to set the current progress level or use the arrow keys.
+ * Placing focusable widgets to the left or right of a SeekBar is discouraged. 
+ * <p>
+ * Clients of the SeekBar can attach a {@link SeekBar.OnSeekBarChangeListener} to
+ * be notified of the user's actions.
+ *
+ * @attr ref android.R.styleable#SeekBar_thumb
+ */
+public class SeekBar extends AbsSeekBar {
+
+    /**
+     * A callback that notifies clients when the progress level has been
+     * changed. This includes changes that were initiated by the user through a
+     * touch gesture or arrow key/trackball as well as changes that were initiated
+     * programmatically.
+     */
+    public interface OnSeekBarChangeListener {
+        
+        /**
+         * Notification that the progress level has changed. Clients can use the fromUser parameter
+         * to distinguish user-initiated changes from those that occurred programmatically.
+         * 
+         * @param seekBar The SeekBar whose progress has changed
+         * @param progress The current progress level. This will be in the range 0..max where max
+         *        was set by {@link ProgressBar#setMax(int)}. (The default value for max is 100.)
+         * @param fromUser True if the progress change was initiated by the user.
+         */
+        void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser);
+    
+        /**
+         * Notification that the user has started a touch gesture. Clients may want to use this
+         * to disable advancing the seekbar. 
+         * @param seekBar The SeekBar in which the touch gesture began
+         */
+        void onStartTrackingTouch(SeekBar seekBar);
+        
+        /**
+         * Notification that the user has finished a touch gesture. Clients may want to use this
+         * to re-enable advancing the seekbar. 
+         * @param seekBar The SeekBar in which the touch gesture began
+         */
+        void onStopTrackingTouch(SeekBar seekBar);
+    }
+
+    private OnSeekBarChangeListener mOnSeekBarChangeListener;
+    
+    public SeekBar(Context context) {
+        this(context, null);
+    }
+    
+    public SeekBar(Context context, AttributeSet attrs) {
+        this(context, attrs, com.android.internal.R.attr.seekBarStyle);
+    }
+
+    public SeekBar(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+    }
+
+    @Override
+    void onProgressRefresh(float scale, boolean fromUser) {
+        super.onProgressRefresh(scale, fromUser);
+
+        if (mOnSeekBarChangeListener != null) {
+            mOnSeekBarChangeListener.onProgressChanged(this, getProgress(), fromUser);
+        }
+    }
+
+    /**
+     * Sets a listener to receive notifications of changes to the SeekBar's progress level. Also
+     * provides notifications of when the user starts and stops a touch gesture within the SeekBar.
+     * 
+     * @param l The seek bar notification listener
+     * 
+     * @see SeekBar.OnSeekBarChangeListener
+     */
+    public void setOnSeekBarChangeListener(OnSeekBarChangeListener l) {
+        mOnSeekBarChangeListener = l;
+    }
+    
+    @Override
+    void onStartTrackingTouch() {
+        if (mOnSeekBarChangeListener != null) {
+            mOnSeekBarChangeListener.onStartTrackingTouch(this);
+        }
+    }
+    
+    @Override
+    void onStopTrackingTouch() {
+        if (mOnSeekBarChangeListener != null) {
+            mOnSeekBarChangeListener.onStopTrackingTouch(this);
+        }
+    }
+    
+}
diff --git a/core/java/android/widget/SimpleAdapter.java b/core/java/android/widget/SimpleAdapter.java
new file mode 100644
index 0000000..093c24e
--- /dev/null
+++ b/core/java/android/widget/SimpleAdapter.java
@@ -0,0 +1,393 @@
+/*
+ * 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 android.widget;
+
+import android.content.Context;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.LayoutInflater;
+import android.net.Uri;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * An easy adapter to map static data to views defined in an XML file. You can specify the data
+ * backing the list as an ArrayList of Maps. Each entry in the ArrayList corresponds to one row
+ * in the list. The Maps contain the data for each row. You also specify an XML file that
+ * defines the views used to display the row, and a mapping from keys in the Map to specific
+ * views.
+ *
+ * Binding data to views occurs in two phases. First, if a
+ * {@link android.widget.SimpleAdapter.ViewBinder} is available,
+ * {@link ViewBinder#setViewValue(android.view.View, Object, String)}
+ * is invoked. If the returned value is true, binding has occurred. 
+ * If the returned value is false, the following views are then tried in order:
+ * <ul>
+ * <li> A view that implements Checkable (e.g. CheckBox).  The expected bind value is a boolean.
+ * <li> TextView.  The expected bind value is a string and {@link #setViewText(TextView, String)} 
+ * is invoked.
+ * <li> ImageView. The expected bind value is a resource id or a string and 
+ * {@link #setViewImage(ImageView, int)} or {@link #setViewImage(ImageView, String)} is invoked. 
+ * </ul>
+ * If no appropriate binding can be found, an {@link IllegalStateException} is thrown.
+ */
+public class SimpleAdapter extends BaseAdapter implements Filterable {
+    private int[] mTo;
+    private String[] mFrom;
+    private ViewBinder mViewBinder;
+
+    private List<? extends Map<String, ?>> mData;
+
+    private int mResource;
+    private int mDropDownResource;
+    private LayoutInflater mInflater;
+
+    private SimpleFilter mFilter;
+    private ArrayList<Map<String, ?>> mUnfilteredData;
+
+    /**
+     * Constructor
+     * 
+     * @param context The context where the View associated with this SimpleAdapter is running
+     * @param data A List of Maps. Each entry in the List corresponds to one row in the list. The
+     *        Maps contain the data for each row, and should include all the entries specified in
+     *        "from"
+     * @param resource Resource identifier of a view layout that defines the views for this list
+     *        item. The layout file should include at least those named views defined in "to"
+     * @param from A list of column names that will be added to the Map associated with each
+     *        item.
+     * @param to The views that should display column in the "from" parameter. These should all be
+     *        TextViews. The first N views in this list are given the values of the first N columns
+     *        in the from parameter.
+     */
+    public SimpleAdapter(Context context, List<? extends Map<String, ?>> data,
+            int resource, String[] from, int[] to) {
+        mData = data;
+        mResource = mDropDownResource = resource;
+        mFrom = from;
+        mTo = to;
+        mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+    }
+
+    
+    /**
+     * @see android.widget.Adapter#getCount()
+     */
+    public int getCount() {
+        return mData.size();
+    }
+
+    /**
+     * @see android.widget.Adapter#getItem(int)
+     */
+    public Object getItem(int position) {
+        return mData.get(position);
+    }
+
+    /**
+     * @see android.widget.Adapter#getItemId(int)
+     */
+    public long getItemId(int position) {
+        return position;
+    }
+
+    /**
+     * @see android.widget.Adapter#getView(int, View, ViewGroup)
+     */
+    public View getView(int position, View convertView, ViewGroup parent) {
+        return createViewFromResource(position, convertView, parent, mResource);
+    }
+
+    private View createViewFromResource(int position, View convertView,
+            ViewGroup parent, int resource) {
+        View v;
+        if (convertView == null) {
+            v = mInflater.inflate(resource, parent, false);
+
+            final int[] to = mTo;
+            final int count = to.length;
+            final View[] holder = new View[count];
+
+            for (int i = 0; i < count; i++) {
+                holder[i] = v.findViewById(to[i]);
+            }
+
+            v.setTag(holder);
+        } else {
+            v = convertView;
+        }
+
+        bindView(position, v);
+
+        return v;
+    }
+
+    /**
+     * <p>Sets the layout resource to create the drop down views.</p>
+     *
+     * @param resource the layout resource defining the drop down views
+     * @see #getDropDownView(int, android.view.View, android.view.ViewGroup)
+     */
+    public void setDropDownViewResource(int resource) {
+        this.mDropDownResource = resource;
+    }
+
+    @Override
+    public View getDropDownView(int position, View convertView, ViewGroup parent) {
+        return createViewFromResource(position, convertView, parent, mDropDownResource);
+    }
+
+    private void bindView(int position, View view) {
+        final Map dataSet = mData.get(position);
+        if (dataSet == null) {
+            return;
+        }
+
+        final ViewBinder binder = mViewBinder;
+        final View[] holder = (View[]) view.getTag();
+        final String[] from = mFrom;
+        final int[] to = mTo;
+        final int count = to.length;
+
+        for (int i = 0; i < count; i++) {
+            final View v = holder[i];
+            if (v != null) {
+                final Object data = dataSet.get(from[i]);
+                String text = data == null ? "" : data.toString();
+                if (text == null) {
+                    text = "";
+                }
+
+                boolean bound = false;
+                if (binder != null) {
+                    bound = binder.setViewValue(v, data, text);
+                }
+
+                if (!bound) {
+                    if (v instanceof Checkable) {
+                        if (data instanceof Boolean) {
+                            ((Checkable) v).setChecked((Boolean) data);
+                        } else {
+                            throw new IllegalStateException(v.getClass().getName() +
+                                    " should be bound to a Boolean, not a " + data.getClass());
+                        }
+                    } else if (v instanceof TextView) {
+                        // Note: keep the instanceof TextView check at the bottom of these
+                        // ifs since a lot of views are TextViews (e.g. CheckBoxes).
+                        setViewText((TextView) v, text);
+                    } else if (v instanceof ImageView) {
+                        if (data instanceof Integer) {
+                            setViewImage((ImageView) v, (Integer) data);                            
+                        } else {
+                            setViewImage((ImageView) v, text);
+                        }
+                    } else {
+                        throw new IllegalStateException(v.getClass().getName() + " is not a " +
+                                " view that can be bounds by this SimpleAdapter");
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Returns the {@link ViewBinder} used to bind data to views.
+     *
+     * @return a ViewBinder or null if the binder does not exist
+     *
+     * @see #setViewBinder(android.widget.SimpleAdapter.ViewBinder)
+     */
+    public ViewBinder getViewBinder() {
+        return mViewBinder;
+    }
+
+    /**
+     * Sets the binder used to bind data to views.
+     *
+     * @param viewBinder the binder used to bind data to views, can be null to
+     *        remove the existing binder
+     *
+     * @see #getViewBinder()
+     */
+    public void setViewBinder(ViewBinder viewBinder) {
+        mViewBinder = viewBinder;
+    }
+
+    /**
+     * Called by bindView() to set the image for an ImageView but only if
+     * there is no existing ViewBinder or if the existing ViewBinder cannot
+     * handle binding to an ImageView.
+     *
+     * This method is called instead of {@link #setViewImage(ImageView, String)}
+     * if the supplied data is an int or Integer.
+     *
+     * @param v ImageView to receive an image
+     * @param value the value retrieved from the data set
+     *
+     * @see #setViewImage(ImageView, String)
+     */
+    public void setViewImage(ImageView v, int value) {
+        v.setImageResource(value);
+    }
+
+    /**
+     * Called by bindView() to set the image for an ImageView but only if
+     * there is no existing ViewBinder or if the existing ViewBinder cannot
+     * handle binding to an ImageView.
+     *
+     * By default, the value will be treated as an image resource. If the
+     * value cannot be used as an image resource, the value is used as an
+     * image Uri.
+     *
+     * This method is called instead of {@link #setViewImage(ImageView, int)}
+     * if the supplied data is not an int or Integer.
+     *
+     * @param v ImageView to receive an image
+     * @param value the value retrieved from the data set
+     *
+     * @see #setViewImage(ImageView, int) 
+     */
+    public void setViewImage(ImageView v, String value) {
+        try {
+            v.setImageResource(Integer.parseInt(value));
+        } catch (NumberFormatException nfe) {
+            v.setImageURI(Uri.parse(value));
+        }
+    }
+
+    /**
+     * Called by bindView() to set the text for a TextView but only if
+     * there is no existing ViewBinder or if the existing ViewBinder cannot
+     * handle binding to an TextView.
+     *
+     * @param v TextView to receive text
+     * @param text the text to be set for the TextView
+     */
+    public void setViewText(TextView v, String text) {
+        v.setText(text);
+    }
+
+    public Filter getFilter() {
+        if (mFilter == null) {
+            mFilter = new SimpleFilter();
+        }
+        return mFilter;
+    }
+
+    /**
+     * This class can be used by external clients of SimpleAdapter to bind
+     * values to views.
+     *
+     * You should use this class to bind values to views that are not
+     * directly supported by SimpleAdapter or to change the way binding
+     * occurs for views supported by SimpleAdapter.
+     *
+     * @see SimpleAdapter#setViewImage(ImageView, int)
+     * @see SimpleAdapter#setViewImage(ImageView, String)
+     * @see SimpleAdapter#setViewText(TextView, String)
+     */
+    public static interface ViewBinder {
+        /**
+         * Binds the specified data to the specified view.
+         *
+         * When binding is handled by this ViewBinder, this method must return true.
+         * If this method returns false, SimpleAdapter will attempts to handle
+         * the binding on its own.
+         *
+         * @param view the view to bind the data to
+         * @param data the data to bind to the view
+         * @param textRepresentation a safe String representation of the supplied data:
+         *        it is either the result of data.toString() or an empty String but it
+         *        is never null
+         *
+         * @return true if the data was bound to the view, false otherwise
+         */
+        boolean setViewValue(View view, Object data, String textRepresentation);
+    }
+
+    /**
+     * <p>An array filters constrains the content of the array adapter with
+     * a prefix. Each item that does not start with the supplied prefix
+     * is removed from the list.</p>
+     */
+    private class SimpleFilter extends Filter {
+
+        @Override
+        protected FilterResults performFiltering(CharSequence prefix) {
+            FilterResults results = new FilterResults();
+
+            if (mUnfilteredData == null) {
+                mUnfilteredData = new ArrayList<Map<String, ?>>(mData);
+            }
+
+            if (prefix == null || prefix.length() == 0) {
+                ArrayList<Map<String, ?>> list = mUnfilteredData;
+                results.values = list;
+                results.count = list.size();
+            } else {
+                String prefixString = prefix.toString().toLowerCase();
+
+                ArrayList<Map<String, ?>> unfilteredValues = mUnfilteredData;
+                int count = unfilteredValues.size();
+
+                ArrayList<Map<String, ?>> newValues = new ArrayList<Map<String, ?>>(count);
+
+                for (int i = 0; i < count; i++) {
+                    Map<String, ?> h = unfilteredValues.get(i);
+                    if (h != null) {
+                        
+                        int len = mTo.length;
+
+                        for (int j=0; j<len; j++) {
+                            String str =  (String)h.get(mFrom[j]);
+                            
+                            String[] words = str.split(" ");
+                            int wordCount = words.length;
+                            
+                            for (int k = 0; k < wordCount; k++) {
+                                String word = words[k];
+                                
+                                if (word.toLowerCase().startsWith(prefixString)) {
+                                    newValues.add(h);
+                                    break;
+                                }
+                            }
+                        }
+                    }
+                }
+
+                results.values = newValues;
+                results.count = newValues.size();
+            }
+
+            return results;
+        }
+
+        @Override
+        protected void publishResults(CharSequence constraint, FilterResults results) {
+            //noinspection unchecked
+            mData = (List<Map<String, ?>>) results.values;
+            if (results.count > 0) {
+                notifyDataSetChanged();
+            } else {
+                notifyDataSetInvalidated();
+            }
+        }
+    }
+}
diff --git a/core/java/android/widget/SimpleCursorAdapter.java b/core/java/android/widget/SimpleCursorAdapter.java
new file mode 100644
index 0000000..c1595ea
--- /dev/null
+++ b/core/java/android/widget/SimpleCursorAdapter.java
@@ -0,0 +1,416 @@
+/*
+ * 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 android.widget;
+
+import android.content.Context;
+import android.database.Cursor;
+import android.net.Uri;
+import android.view.View;
+import android.view.ViewGroup;
+
+/**
+ * An easy adapter to map columns from a cursor to TextViews or ImageViews
+ * defined in an XML file. You can specify which columns you want, which
+ * views you want to display the columns, and the XML file that defines
+ * the appearance of these views.
+ *
+ * Binding occurs in two phases. First, if a
+ * {@link android.widget.SimpleCursorAdapter.ViewBinder} is available,
+ * {@link ViewBinder#setViewValue(android.view.View, android.database.Cursor, int)}
+ * is invoked. If the returned value is true, binding has occured. If the
+ * returned value is false and the view to bind is a TextView,
+ * {@link #setViewText(TextView, String)} is invoked. If the returned value
+ * is false and the view to bind is an ImageView,
+ * {@link #setViewImage(ImageView, String)} is invoked. If no appropriate
+ * binding can be found, an {@link IllegalStateException} is thrown.
+ *
+ * If this adapter is used with filtering, for instance in an
+ * {@link android.widget.AutoCompleteTextView}, you can use the
+ * {@link android.widget.SimpleCursorAdapter.CursorToStringConverter} and the
+ * {@link android.widget.FilterQueryProvider} interfaces
+ * to get control over the filtering process. You can refer to
+ * {@link #convertToString(android.database.Cursor)} and
+ * {@link #runQueryOnBackgroundThread(CharSequence)} for more information.
+ */
+public class SimpleCursorAdapter extends ResourceCursorAdapter {
+    /**
+     * A list of columns containing the data to bind to the UI.
+     * This field should be made private, so it is hidden from the SDK.
+     * {@hide}
+     */
+    protected int[] mFrom;
+    /**
+     * A list of View ids representing the views to which the data must be bound.
+     * This field should be made private, so it is hidden from the SDK.
+     * {@hide}
+     */
+    protected int[] mTo;
+
+    private int mStringConversionColumn = -1;
+    private CursorToStringConverter mCursorToStringConverter;
+    private ViewBinder mViewBinder;
+    private String[] mOriginalFrom;
+
+    /**
+     * Constructor.
+     * 
+     * @param context The context where the ListView associated with this
+     *            SimpleListItemFactory is running
+     * @param layout resource identifier of a layout file that defines the views
+     *            for this list item. Thelayout file should include at least
+     *            those named views defined in "to"
+     * @param c The database cursor.  Can be null if the cursor is not available yet.
+     * @param from A list of column names representing the data to bind to the UI.  Can be null 
+     *            if the cursor is not available yet.
+     * @param to The views that should display column in the "from" parameter.
+     *            These should all be TextViews. The first N views in this list
+     *            are given the values of the first N columns in the from
+     *            parameter.  Can be null if the cursor is not available yet.
+     */
+    public SimpleCursorAdapter(Context context, int layout, Cursor c, String[] from, int[] to) {
+        super(context, layout, c);
+        mTo = to;
+        mOriginalFrom = from;
+        findColumns(from);
+    }
+
+    @Override
+    public View newView(Context context, Cursor cursor, ViewGroup parent) {
+        return generateViewHolder(super.newView(context, cursor, parent));
+    }
+
+    @Override
+    public View newDropDownView(Context context, Cursor cursor, ViewGroup parent) {
+        return generateViewHolder(super.newDropDownView(context, cursor, parent));
+    }
+
+    private View generateViewHolder(View v) {
+        final int[] to = mTo;
+        final int count = to.length;
+        final View[] holder = new View[count];
+
+        for (int i = 0; i < count; i++) {
+            holder[i] = v.findViewById(to[i]);
+        }
+        v.setTag(holder);
+
+        return v;
+    }
+
+    /**
+     * Binds all of the field names passed into the "to" parameter of the
+     * constructor with their corresponding cursor columns as specified in the
+     * "from" parameter.
+     *
+     * Binding occurs in two phases. First, if a
+     * {@link android.widget.SimpleCursorAdapter.ViewBinder} is available,
+     * {@link ViewBinder#setViewValue(android.view.View, android.database.Cursor, int)}
+     * is invoked. If the returned value is true, binding has occured. If the
+     * returned value is false and the view to bind is a TextView,
+     * {@link #setViewText(TextView, String)} is invoked. If the returned value is
+     * false and the view to bind is an ImageView,
+     * {@link #setViewImage(ImageView, String)} is invoked. If no appropriate
+     * binding can be found, an {@link IllegalStateException} is thrown.
+     *
+     * @throws IllegalStateException if binding cannot occur
+     * 
+     * @see android.widget.CursorAdapter#bindView(android.view.View,
+     *      android.content.Context, android.database.Cursor)
+     * @see #getViewBinder()
+     * @see #setViewBinder(android.widget.SimpleCursorAdapter.ViewBinder)
+     * @see #setViewImage(ImageView, String)
+     * @see #setViewText(TextView, String)
+     */
+    @Override
+    public void bindView(View view, Context context, Cursor cursor) {
+        final View[] holder = (View[]) view.getTag();
+        final ViewBinder binder = mViewBinder;
+        final int count = mTo.length;
+        final int[] from = mFrom;
+
+        for (int i = 0; i < count; i++) {
+            final View v = holder[i];
+            if (v != null) {
+                String text = cursor.getString(from[i]);
+                if (text == null) {
+                    text = "";
+                }
+
+                boolean bound = false;
+                if (binder != null) {
+                    bound = binder.setViewValue(v, cursor, from[i]);
+                }
+
+                if (!bound) {
+                    if (v instanceof TextView) {
+                        setViewText((TextView) v, text);
+                    } else if (v instanceof ImageView) {
+                        setViewImage((ImageView) v, text);
+                    } else {
+                        throw new IllegalStateException(v.getClass().getName() + " is not a " +
+                                " view that can be bounds by this SimpleCursorAdapter");
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Returns the {@link ViewBinder} used to bind data to views.
+     *
+     * @return a ViewBinder or null if the binder does not exist
+     *
+     * @see #bindView(android.view.View, android.content.Context, android.database.Cursor)
+     * @see #setViewBinder(android.widget.SimpleCursorAdapter.ViewBinder)
+     */
+    public ViewBinder getViewBinder() {
+        return mViewBinder;
+    }
+
+    /**
+     * Sets the binder used to bind data to views.
+     *
+     * @param viewBinder the binder used to bind data to views, can be null to
+     *        remove the existing binder
+     *
+     * @see #bindView(android.view.View, android.content.Context, android.database.Cursor)
+     * @see #getViewBinder()
+     */
+    public void setViewBinder(ViewBinder viewBinder) {
+        mViewBinder = viewBinder;
+    }
+
+    /**
+     * Called by bindView() to set the image for an ImageView but only if
+     * there is no existing ViewBinder or if the existing ViewBinder cannot
+     * handle binding to an ImageView.
+     *
+     * By default, the value will be treated as an image resource. If the
+     * value cannot be used as an image resource, the value is used as an
+     * image Uri.
+     *
+     * Intended to be overridden by Adapters that need to filter strings
+     * retrieved from the database.
+     *
+     * @param v ImageView to receive an image
+     * @param value the value retrieved from the cursor
+     */
+    public void setViewImage(ImageView v, String value) {
+        try {
+            v.setImageResource(Integer.parseInt(value));
+        } catch (NumberFormatException nfe) {
+            v.setImageURI(Uri.parse(value));
+        }
+    }
+
+    /**
+     * Called by bindView() to set the text for a TextView but only if
+     * there is no existing ViewBinder or if the existing ViewBinder cannot
+     * handle binding to an TextView.
+     *
+     * Intended to be overridden by Adapters that need to filter strings
+     * retrieved from the database.
+     * 
+     * @param v TextView to receive text
+     * @param text the text to be set for the TextView
+     */    
+    public void setViewText(TextView v, String text) {
+        v.setText(text);
+    }
+
+    /**
+     * Return the index of the column used to get a String representation
+     * of the Cursor.
+     *
+     * @return a valid index in the current Cursor or -1
+     *
+     * @see android.widget.CursorAdapter#convertToString(android.database.Cursor)
+     * @see #setStringConversionColumn(int) 
+     * @see #setCursorToStringConverter(android.widget.SimpleCursorAdapter.CursorToStringConverter)
+     * @see #getCursorToStringConverter()
+     */
+    public int getStringConversionColumn() {
+        return mStringConversionColumn;
+    }
+
+    /**
+     * Defines the index of the column in the Cursor used to get a String
+     * representation of that Cursor. The column is used to convert the
+     * Cursor to a String only when the current CursorToStringConverter
+     * is null.
+     *
+     * @param stringConversionColumn a valid index in the current Cursor or -1 to use the default
+     *        conversion mechanism
+     *
+     * @see android.widget.CursorAdapter#convertToString(android.database.Cursor)
+     * @see #getStringConversionColumn()
+     * @see #setCursorToStringConverter(android.widget.SimpleCursorAdapter.CursorToStringConverter)
+     * @see #getCursorToStringConverter()
+     */
+    public void setStringConversionColumn(int stringConversionColumn) {
+        mStringConversionColumn = stringConversionColumn;
+    }
+
+    /**
+     * Returns the converter used to convert the filtering Cursor
+     * into a String.
+     *
+     * @return null if the converter does not exist or an instance of
+     *         {@link android.widget.SimpleCursorAdapter.CursorToStringConverter}
+     *
+     * @see #setCursorToStringConverter(android.widget.SimpleCursorAdapter.CursorToStringConverter)
+     * @see #getStringConversionColumn()
+     * @see #setStringConversionColumn(int)
+     * @see android.widget.CursorAdapter#convertToString(android.database.Cursor)
+     */
+    public CursorToStringConverter getCursorToStringConverter() {
+        return mCursorToStringConverter;
+    }
+
+    /**
+     * Sets the converter  used to convert the filtering Cursor
+     * into a String.
+     *
+     * @param cursorToStringConverter the Cursor to String converter, or
+     *        null to remove the converter
+     *
+     * @see #setCursorToStringConverter(android.widget.SimpleCursorAdapter.CursorToStringConverter) 
+     * @see #getStringConversionColumn()
+     * @see #setStringConversionColumn(int)
+     * @see android.widget.CursorAdapter#convertToString(android.database.Cursor)
+     */
+    public void setCursorToStringConverter(CursorToStringConverter cursorToStringConverter) {
+        mCursorToStringConverter = cursorToStringConverter;
+    }
+
+    /**
+     * Returns a CharSequence representation of the specified Cursor as defined
+     * by the current CursorToStringConverter. If no CursorToStringConverter
+     * has been set, the String conversion column is used instead. If the
+     * conversion column is -1, the returned String is empty if the cursor
+     * is null or Cursor.toString().
+     *
+     * @param cursor the Cursor to convert to a CharSequence
+     *
+     * @return a non-null CharSequence representing the cursor
+     */
+    @Override
+    public CharSequence convertToString(Cursor cursor) {
+        if (mCursorToStringConverter != null) {
+            return mCursorToStringConverter.convertToString(cursor);
+        } else if (mStringConversionColumn > -1) {
+            return cursor.getString(mStringConversionColumn);
+        }
+
+        return super.convertToString(cursor);
+    }
+
+    /**
+     * Create a map from an array of strings to an array of column-id integers in mCursor.
+     * If mCursor is null, the array will be discarded.
+     * 
+     * @param from the Strings naming the columns of interest
+     */
+    private void findColumns(String[] from) {
+        if (mCursor != null) {
+            int i;
+            int count = from.length;
+            if (mFrom == null || mFrom.length != count) {
+                mFrom = new int[count];
+            }
+            for (i = 0; i < count; i++) {
+                mFrom[i] = mCursor.getColumnIndexOrThrow(from[i]);
+            }
+        } else {
+            mFrom = null;
+        }
+    }
+
+    @Override
+    public void changeCursor(Cursor c) {
+        super.changeCursor(c);
+        // rescan columns in case cursor layout is different
+        findColumns(mOriginalFrom);
+    }
+    
+    /**
+     * Change the cursor and change the column-to-view mappings at the same time.
+     *  
+     * @param c The database cursor.  Can be null if the cursor is not available yet.
+     * @param from A list of column names representing the data to bind to the UI.  Can be null 
+     *            if the cursor is not available yet.
+     * @param to The views that should display column in the "from" parameter.
+     *            These should all be TextViews. The first N views in this list
+     *            are given the values of the first N columns in the from
+     *            parameter.  Can be null if the cursor is not available yet.
+     */
+    public void changeCursorAndColumns(Cursor c, String[] from, int[] to) {
+        mOriginalFrom = from;
+        mTo = to;
+        super.changeCursor(c);        
+        findColumns(mOriginalFrom);
+    }
+
+    /**
+     * This class can be used by external clients of SimpleCursorAdapter
+     * to bind values fom the Cursor to views.
+     *
+     * You should use this class to bind values from the Cursor to views
+     * that are not directly supported by SimpleCursorAdapter or to
+     * change the way binding occurs for views supported by
+     * SimpleCursorAdapter.
+     *
+     * @see SimpleCursorAdapter#bindView(android.view.View, android.content.Context, android.database.Cursor)
+     * @see SimpleCursorAdapter#setViewImage(ImageView, String) 
+     * @see SimpleCursorAdapter#setViewText(TextView, String)
+     */
+    public static interface ViewBinder {
+        /**
+         * Binds the Cursor column defined by the specified index to the specified view.
+         *
+         * When binding is handled by this ViewBinder, this method must return true.
+         * If this method returns false, SimpleCursorAdapter will attempts to handle
+         * the binding on its own.
+         *
+         * @param view the view to bind the data to
+         * @param cursor the cursor to get the data from
+         * @param columnIndex the column at which the data can be found in the cursor
+         *
+         * @return true if the data was bound to the view, false otherwise
+         */
+        boolean setViewValue(View view, Cursor cursor, int columnIndex);
+    }
+
+    /**
+     * This class can be used by external clients of SimpleCursorAdapter
+     * to define how the Cursor should be converted to a String.
+     *
+     * @see android.widget.CursorAdapter#convertToString(android.database.Cursor)
+     */
+    public static interface CursorToStringConverter {
+        /**
+         * Returns a CharSequence representing the specified Cursor.
+         *
+         * @param cursor the cursor for which a CharSequence representation
+         *        is requested
+         *
+         * @return a non-null CharSequence representing the cursor
+         */
+        CharSequence convertToString(Cursor cursor);
+    }
+
+}
diff --git a/core/java/android/widget/SimpleCursorTreeAdapter.java b/core/java/android/widget/SimpleCursorTreeAdapter.java
new file mode 100644
index 0000000..c456f56
--- /dev/null
+++ b/core/java/android/widget/SimpleCursorTreeAdapter.java
@@ -0,0 +1,241 @@
+/*
+ * 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 android.widget;
+
+import android.content.Context;
+import android.database.Cursor;
+import android.net.Uri;
+import android.view.View;
+
+/**
+ * An easy adapter to map columns from a cursor to TextViews or ImageViews
+ * defined in an XML file. You can specify which columns you want, which views
+ * you want to display the columns, and the XML file that defines the appearance
+ * of these views. Separate XML files for child and groups are possible.
+ * TextViews bind the values to their text property (see
+ * {@link TextView#setText(CharSequence)}). ImageViews bind the values to their
+ * image's Uri property (see {@link ImageView#setImageURI(android.net.Uri)}).
+ */
+public abstract class SimpleCursorTreeAdapter extends ResourceCursorTreeAdapter {
+    /** The indices of columns that contain data to display for a group. */
+    private int[] mGroupFrom;
+    /**
+     * The View IDs that will display a group's data fetched from the
+     * corresponding column.
+     */
+    private int[] mGroupTo;
+
+    /** The indices of columns that contain data to display for a child. */
+    private int[] mChildFrom;
+    /**
+     * The View IDs that will display a child's data fetched from the
+     * corresponding column.
+     */
+    private int[] mChildTo;
+    
+    /**
+     * Constructor.
+     * 
+     * @param context The context where the {@link ExpandableListView}
+     *            associated with this {@link SimpleCursorTreeAdapter} is
+     *            running
+     * @param cursor The database cursor
+     * @param collapsedGroupLayout The resource identifier of a layout file that
+     *            defines the views for a collapsed group. The layout file
+     *            should include at least those named views defined in groupTo.
+     * @param expandedGroupLayout The resource identifier of a layout file that
+     *            defines the views for an expanded group. The layout file
+     *            should include at least those named views defined in groupTo.
+     * @param groupFrom A list of column names that will be used to display the
+     *            data for a group.
+     * @param groupTo The group views (from the group layouts) that should
+     *            display column in the "from" parameter. These should all be
+     *            TextViews or ImageViews. The first N views in this list are
+     *            given the values of the first N columns in the from parameter.
+     * @param childLayout The resource identifier of a layout file that defines
+     *            the views for a child (except the last). The layout file
+     *            should include at least those named views defined in childTo.
+     * @param lastChildLayout The resource identifier of a layout file that
+     *            defines the views for the last child within a group. The
+     *            layout file should include at least those named views defined
+     *            in childTo.
+     * @param childFrom A list of column names that will be used to display the
+     *            data for a child.
+     * @param childTo The child views (from the child layouts) that should
+     *            display column in the "from" parameter. These should all be
+     *            TextViews or ImageViews. The first N views in this list are
+     *            given the values of the first N columns in the from parameter.
+     */
+    public SimpleCursorTreeAdapter(Context context, Cursor cursor, int collapsedGroupLayout,
+            int expandedGroupLayout, String[] groupFrom, int[] groupTo, int childLayout,
+            int lastChildLayout, String[] childFrom, int[] childTo) {
+        super(context, cursor, collapsedGroupLayout, expandedGroupLayout, childLayout,
+                lastChildLayout);
+        init(groupFrom, groupTo, childFrom, childTo);
+    }
+
+    /**
+     * Constructor.
+     * 
+     * @param context The context where the {@link ExpandableListView}
+     *            associated with this {@link SimpleCursorTreeAdapter} is
+     *            running
+     * @param cursor The database cursor
+     * @param collapsedGroupLayout The resource identifier of a layout file that
+     *            defines the views for a collapsed group. The layout file
+     *            should include at least those named views defined in groupTo.
+     * @param expandedGroupLayout The resource identifier of a layout file that
+     *            defines the views for an expanded group. The layout file
+     *            should include at least those named views defined in groupTo.
+     * @param groupFrom A list of column names that will be used to display the
+     *            data for a group.
+     * @param groupTo The group views (from the group layouts) that should
+     *            display column in the "from" parameter. These should all be
+     *            TextViews or ImageViews. The first N views in this list are
+     *            given the values of the first N columns in the from parameter.
+     * @param childLayout The resource identifier of a layout file that defines
+     *            the views for a child. The layout file
+     *            should include at least those named views defined in childTo.
+     * @param childFrom A list of column names that will be used to display the
+     *            data for a child.
+     * @param childTo The child views (from the child layouts) that should
+     *            display column in the "from" parameter. These should all be
+     *            TextViews or ImageViews. The first N views in this list are
+     *            given the values of the first N columns in the from parameter.
+     */
+    public SimpleCursorTreeAdapter(Context context, Cursor cursor, int collapsedGroupLayout,
+            int expandedGroupLayout, String[] groupFrom, int[] groupTo,
+            int childLayout, String[] childFrom, int[] childTo) {
+        super(context, cursor, collapsedGroupLayout, expandedGroupLayout, childLayout);
+        init(groupFrom, groupTo, childFrom, childTo);
+    }
+
+    /**
+     * Constructor.
+     * 
+     * @param context The context where the {@link ExpandableListView}
+     *            associated with this {@link SimpleCursorTreeAdapter} is
+     *            running
+     * @param cursor The database cursor
+     * @param groupLayout The resource identifier of a layout file that defines
+     *            the views for a group. The layout file should include at least
+     *            those named views defined in groupTo.
+     * @param groupFrom A list of column names that will be used to display the
+     *            data for a group.
+     * @param groupTo The group views (from the group layouts) that should
+     *            display column in the "from" parameter. These should all be
+     *            TextViews or ImageViews. The first N views in this list are
+     *            given the values of the first N columns in the from parameter.
+     * @param childLayout The resource identifier of a layout file that defines
+     *            the views for a child. The layout file should include at least
+     *            those named views defined in childTo.
+     * @param childFrom A list of column names that will be used to display the
+     *            data for a child.
+     * @param childTo The child views (from the child layouts) that should
+     *            display column in the "from" parameter. These should all be
+     *            TextViews or ImageViews. The first N views in this list are
+     *            given the values of the first N columns in the from parameter.
+     */
+    public SimpleCursorTreeAdapter(Context context, Cursor cursor, int groupLayout,
+            String[] groupFrom, int[] groupTo, int childLayout, String[] childFrom,
+            int[] childTo) {
+        super(context, cursor, groupLayout, childLayout);
+        init(groupFrom, groupTo, childFrom, childTo);
+    }
+
+    private void init(String[] groupFromNames, int[] groupTo, String[] childFromNames,
+            int[] childTo) {
+        mGroupTo = groupTo;
+        
+        mChildTo = childTo;
+        
+        // Get the group cursor column indices, the child cursor column indices will come
+        // when needed
+        initGroupFromColumns(groupFromNames);
+        
+        // Get a temporary child cursor to init the column indices
+        if (getGroupCount() > 0) {
+            MyCursorHelper tmpCursorHelper = getChildrenCursorHelper(0, true);
+            if (tmpCursorHelper != null) {
+                initChildrenFromColumns(childFromNames, tmpCursorHelper.getCursor());
+                deactivateChildrenCursorHelper(0);
+            }
+        }
+    }
+    
+    private void initFromColumns(Cursor cursor, String[] fromColumnNames, int[] fromColumns) {
+        for (int i = fromColumnNames.length - 1; i >= 0; i--) {
+            fromColumns[i] = cursor.getColumnIndexOrThrow(fromColumnNames[i]);
+        }
+    }
+    
+    private void initGroupFromColumns(String[] groupFromNames) {
+        mGroupFrom = new int[groupFromNames.length];
+        initFromColumns(mGroupCursorHelper.getCursor(), groupFromNames, mGroupFrom);
+    }
+
+    private void initChildrenFromColumns(String[] childFromNames, Cursor childCursor) {
+        mChildFrom = new int[childFromNames.length];
+        initFromColumns(childCursor, childFromNames, mChildFrom);
+    }
+    
+    private void bindView(View view, Context context, Cursor cursor, int[] from, int[] to) {
+        for (int i = 0; i < to.length; i++) {
+            View v = view.findViewById(to[i]);
+            if (v != null) {
+                String text = cursor.getString(from[i]);
+                if (text == null) {
+                    text = "";
+                }
+                if (v instanceof TextView) {
+                    ((TextView) v).setText(text);
+                } else if (v instanceof ImageView) {
+                    setViewImage((ImageView) v, text);
+                } else {
+                    throw new IllegalStateException("SimpleCursorAdapter can bind values only to" +
+                            " TextView and ImageView!");
+                }
+            }
+        }
+    }
+    
+    @Override
+    protected void bindChildView(View view, Context context, Cursor cursor, boolean isLastChild) {
+        bindView(view, context, cursor, mChildFrom, mChildTo);
+    }
+
+    @Override
+    protected void bindGroupView(View view, Context context, Cursor cursor, boolean isExpanded) {
+        bindView(view, context, cursor, mGroupFrom, mGroupTo);
+    }
+
+    /**
+     * Called by bindView() to set the image for an ImageView. By default, the
+     * value will be treated as a Uri. Intended to be overridden by Adapters
+     * that need to filter strings retrieved from the database.
+     * 
+     * @param v ImageView to receive an image
+     * @param value the value retrieved from the cursor
+     */
+    protected void setViewImage(ImageView v, String value) {
+        try {
+            v.setImageResource(Integer.parseInt(value));
+        } catch (NumberFormatException nfe) {
+            v.setImageURI(Uri.parse(value));
+        }
+    }
+}
diff --git a/core/java/android/widget/SimpleExpandableListAdapter.java b/core/java/android/widget/SimpleExpandableListAdapter.java
new file mode 100644
index 0000000..015c169
--- /dev/null
+++ b/core/java/android/widget/SimpleExpandableListAdapter.java
@@ -0,0 +1,301 @@
+/*
+ * 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 android.widget;
+
+import android.content.Context;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.LayoutInflater;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * An easy adapter to map static data to group and child views defined in an XML
+ * file. You can separately specify the data backing the group as a List of
+ * Maps. Each entry in the ArrayList corresponds to one group in the expandable
+ * list. The Maps contain the data for each row. You also specify an XML file
+ * that defines the views used to display a group, and a mapping from keys in
+ * the Map to specific views. This process is similar for a child, except it is
+ * one-level deeper so the data backing is specified as a List<List<Map>>,
+ * where the first List corresponds to the group of the child, the second List
+ * corresponds to the position of the child within the group, and finally the
+ * Map holds the data for that particular child.
+ */
+public class SimpleExpandableListAdapter extends BaseExpandableListAdapter {
+    private List<? extends Map<String, ?>> mGroupData;
+    private int mExpandedGroupLayout;
+    private int mCollapsedGroupLayout;
+    private String[] mGroupFrom;
+    private int[] mGroupTo;
+    
+    private List<? extends List<? extends Map<String, ?>>> mChildData;
+    private int mChildLayout;
+    private int mLastChildLayout;
+    private String[] mChildFrom;
+    private int[] mChildTo;
+    
+    private LayoutInflater mInflater;
+    
+    /**
+     * Constructor
+     * 
+     * @param context The context where the {@link ExpandableListView}
+     *            associated with this {@link SimpleExpandableListAdapter} is
+     *            running
+     * @param groupData A List of Maps. Each entry in the List corresponds to
+     *            one group in the list. The Maps contain the data for each
+     *            group, and should include all the entries specified in
+     *            "groupFrom"
+     * @param groupFrom A list of keys that will be fetched from the Map
+     *            associated with each group.
+     * @param groupTo The group views that should display column in the
+     *            "groupFrom" parameter. These should all be TextViews. The
+     *            first N views in this list are given the values of the first N
+     *            columns in the groupFrom parameter.
+     * @param groupLayout resource identifier of a view layout that defines the
+     *            views for a group. The layout file should include at least
+     *            those named views defined in "groupTo"
+     * @param childData A List of List of Maps. Each entry in the outer List
+     *            corresponds to a group (index by group position), each entry
+     *            in the inner List corresponds to a child within the group
+     *            (index by child position), and the Map corresponds to the data
+     *            for a child (index by values in the childFrom array). The Map
+     *            contains the data for each child, and should include all the
+     *            entries specified in "childFrom"
+     * @param childFrom A list of keys that will be fetched from the Map
+     *            associated with each child.
+     * @param childTo The child views that should display column in the
+     *            "childFrom" parameter. These should all be TextViews. The
+     *            first N views in this list are given the values of the first N
+     *            columns in the childFrom parameter.
+     * @param childLayout resource identifier of a view layout that defines the
+     *            views for a child. The layout file should include at least
+     *            those named views defined in "childTo"
+     */
+    public SimpleExpandableListAdapter(Context context,
+            List<? extends Map<String, ?>> groupData, int groupLayout,
+            String[] groupFrom, int[] groupTo,
+            List<? extends List<? extends Map<String, ?>>> childData,
+            int childLayout, String[] childFrom, int[] childTo) {
+        this(context, groupData, groupLayout, groupLayout, groupFrom, groupTo, childData,
+                childLayout, childLayout, childFrom, childTo);
+    }
+
+    /**
+     * Constructor
+     * 
+     * @param context The context where the {@link ExpandableListView}
+     *            associated with this {@link SimpleExpandableListAdapter} is
+     *            running
+     * @param groupData A List of Maps. Each entry in the List corresponds to
+     *            one group in the list. The Maps contain the data for each
+     *            group, and should include all the entries specified in
+     *            "groupFrom"
+     * @param groupFrom A list of keys that will be fetched from the Map
+     *            associated with each group.
+     * @param groupTo The group views that should display column in the
+     *            "groupFrom" parameter. These should all be TextViews. The
+     *            first N views in this list are given the values of the first N
+     *            columns in the groupFrom parameter.
+     * @param expandedGroupLayout resource identifier of a view layout that
+     *            defines the views for an expanded group. The layout file
+     *            should include at least those named views defined in "groupTo"
+     * @param collapsedGroupLayout resource identifier of a view layout that
+     *            defines the views for a collapsed group. The layout file
+     *            should include at least those named views defined in "groupTo"
+     * @param childData A List of List of Maps. Each entry in the outer List
+     *            corresponds to a group (index by group position), each entry
+     *            in the inner List corresponds to a child within the group
+     *            (index by child position), and the Map corresponds to the data
+     *            for a child (index by values in the childFrom array). The Map
+     *            contains the data for each child, and should include all the
+     *            entries specified in "childFrom"
+     * @param childFrom A list of keys that will be fetched from the Map
+     *            associated with each child.
+     * @param childTo The child views that should display column in the
+     *            "childFrom" parameter. These should all be TextViews. The
+     *            first N views in this list are given the values of the first N
+     *            columns in the childFrom parameter.
+     * @param childLayout resource identifier of a view layout that defines the
+     *            views for a child. The layout file should include at least
+     *            those named views defined in "childTo"
+     */
+    public SimpleExpandableListAdapter(Context context,
+            List<? extends Map<String, ?>> groupData, int expandedGroupLayout,
+            int collapsedGroupLayout, String[] groupFrom, int[] groupTo,
+            List<? extends List<? extends Map<String, ?>>> childData,
+            int childLayout, String[] childFrom, int[] childTo) {
+        this(context, groupData, expandedGroupLayout, collapsedGroupLayout,
+                groupFrom, groupTo, childData, childLayout, childLayout,
+                childFrom, childTo);
+    }
+
+    /**
+     * Constructor
+     * 
+     * @param context The context where the {@link ExpandableListView}
+     *            associated with this {@link SimpleExpandableListAdapter} is
+     *            running
+     * @param groupData A List of Maps. Each entry in the List corresponds to
+     *            one group in the list. The Maps contain the data for each
+     *            group, and should include all the entries specified in
+     *            "groupFrom"
+     * @param groupFrom A list of keys that will be fetched from the Map
+     *            associated with each group.
+     * @param groupTo The group views that should display column in the
+     *            "groupFrom" parameter. These should all be TextViews. The
+     *            first N views in this list are given the values of the first N
+     *            columns in the groupFrom parameter.
+     * @param expandedGroupLayout resource identifier of a view layout that
+     *            defines the views for an expanded group. The layout file
+     *            should include at least those named views defined in "groupTo"
+     * @param collapsedGroupLayout resource identifier of a view layout that
+     *            defines the views for a collapsed group. The layout file
+     *            should include at least those named views defined in "groupTo"
+     * @param childData A List of List of Maps. Each entry in the outer List
+     *            corresponds to a group (index by group position), each entry
+     *            in the inner List corresponds to a child within the group
+     *            (index by child position), and the Map corresponds to the data
+     *            for a child (index by values in the childFrom array). The Map
+     *            contains the data for each child, and should include all the
+     *            entries specified in "childFrom"
+     * @param childFrom A list of keys that will be fetched from the Map
+     *            associated with each child.
+     * @param childTo The child views that should display column in the
+     *            "childFrom" parameter. These should all be TextViews. The
+     *            first N views in this list are given the values of the first N
+     *            columns in the childFrom parameter.
+     * @param childLayout resource identifier of a view layout that defines the
+     *            views for a child (unless it is the last child within a group,
+     *            in which case the lastChildLayout is used). The layout file
+     *            should include at least those named views defined in "childTo"
+     * @param lastChildLayout resource identifier of a view layout that defines
+     *            the views for the last child within each group. The layout
+     *            file should include at least those named views defined in
+     *            "childTo"
+     */
+    public SimpleExpandableListAdapter(Context context,
+            List<? extends Map<String, ?>> groupData, int expandedGroupLayout,
+            int collapsedGroupLayout, String[] groupFrom, int[] groupTo,
+            List<? extends List<? extends Map<String, ?>>> childData,
+            int childLayout, int lastChildLayout, String[] childFrom,
+            int[] childTo) {
+        mGroupData = groupData;
+        mExpandedGroupLayout = expandedGroupLayout;
+        mCollapsedGroupLayout = collapsedGroupLayout;
+        mGroupFrom = groupFrom;
+        mGroupTo = groupTo;
+        
+        mChildData = childData;
+        mChildLayout = childLayout;
+        mLastChildLayout = lastChildLayout;
+        mChildFrom = childFrom;
+        mChildTo = childTo;
+        
+        mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+    }
+    
+    public Object getChild(int groupPosition, int childPosition) {
+        return mChildData.get(groupPosition).get(childPosition);
+    }
+
+    public long getChildId(int groupPosition, int childPosition) {
+        return childPosition;
+    }
+
+    public View getChildView(int groupPosition, int childPosition, boolean isLastChild,
+            View convertView, ViewGroup parent) {
+        View v;
+        if (convertView == null) {
+            v = newChildView(isLastChild, parent);
+        } else {
+            v = convertView;
+        }
+        bindView(v, mChildData.get(groupPosition).get(childPosition), mChildFrom, mChildTo);
+        return v;
+    }
+
+    /**
+     * Instantiates a new View for a child.
+     * @param isLastChild Whether the child is the last child within its group.
+     * @param parent The eventual parent of this new View.
+     * @return A new child View
+     */
+    public View newChildView(boolean isLastChild, ViewGroup parent) {
+        return mInflater.inflate((isLastChild) ? mLastChildLayout : mChildLayout, parent, false);
+    }
+    
+    private void bindView(View view, Map<String, ?> data, String[] from, int[] to) {
+        int len = to.length;
+
+        for (int i = 0; i < len; i++) {
+            TextView v = (TextView)view.findViewById(to[i]);
+            if (v != null) {
+                v.setText((String)data.get(from[i]));
+            }
+        }
+    }
+
+    public int getChildrenCount(int groupPosition) {
+        return mChildData.get(groupPosition).size();
+    }
+
+    public Object getGroup(int groupPosition) {
+        return mGroupData.get(groupPosition);
+    }
+
+    public int getGroupCount() {
+        return mGroupData.size();
+    }
+
+    public long getGroupId(int groupPosition) {
+        return groupPosition;
+    }
+
+    public View getGroupView(int groupPosition, boolean isExpanded, View convertView,
+            ViewGroup parent) {
+        View v;
+        if (convertView == null) {
+            v = newGroupView(isExpanded, parent);
+        } else {
+            v = convertView;
+        }
+        bindView(v, mGroupData.get(groupPosition), mGroupFrom, mGroupTo);
+        return v;
+    }
+
+    /**
+     * Instantiates a new View for a group.
+     * @param isExpanded Whether the group is currently expanded.
+     * @param parent The eventual parent of this new View.
+     * @return A new group View
+     */
+    public View newGroupView(boolean isExpanded, ViewGroup parent) {
+        return mInflater.inflate((isExpanded) ? mExpandedGroupLayout : mCollapsedGroupLayout,
+                parent, false);
+    }
+
+    public boolean isChildSelectable(int groupPosition, int childPosition) {
+        return true;
+    }
+
+    public boolean hasStableIds() {
+        return true;
+    }
+
+}
diff --git a/core/java/android/widget/SlidingDrawer.java b/core/java/android/widget/SlidingDrawer.java
new file mode 100644
index 0000000..e77c4ca
--- /dev/null
+++ b/core/java/android/widget/SlidingDrawer.java
@@ -0,0 +1,938 @@
+/*
+ * 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 android.widget;
+
+import android.view.ViewGroup;
+import android.view.View;
+import android.view.MotionEvent;
+import android.view.VelocityTracker;
+import android.view.SoundEffectConstants;
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.util.AttributeSet;
+import android.graphics.Canvas;
+import android.graphics.Rect;
+import android.graphics.Bitmap;
+import android.os.SystemClock;
+import android.os.Handler;
+import android.os.Message;
+import android.R;
+
+/**
+ * SlidingDrawer hides content out of the screen and allows the user to drag a handle
+ * to bring the content on screen. SlidingDrawer can be used vertically or horizontally.
+ *
+ * A special widget composed of two children views: the handle, that the users drags,
+ * and the content, attached to the handle and dragged with it.
+ *
+ * SlidingDrawer should be used as an overlay inside layouts. This means SlidingDrawer
+ * should only be used inside of a FrameLayout or a RelativeLayout for instance. The
+ * size of the SlidingDrawer defines how much space the content will occupy once slid
+ * out so SlidingDrawer should usually use fill_parent for both its dimensions.
+ *
+ * Inside an XML layout, SlidingDrawer must define the id of the handle and of the
+ * content:
+ *
+ * <pre class="prettyprint">
+ * &lt;SlidingDrawer
+ *     android:id="@+id/drawer"
+ *     android:layout_width="fill_parent"
+ *     android:layout_height="fill_parent"
+ *
+ *     android:handle="@+id/handle"
+ *     android:content="@+id/content"&gt;
+ *
+ *     &lt;ImageView
+ *         android:id="@id/handle"
+ *         android:layout_width="88dip"
+ *         android:layout_height="44dip" /&gt;
+ *
+ *     &lt;GridView
+ *         android:id="@id/content"
+ *         android:layout_width="fill_parent"
+ *         android:layout_height="fill_parent" /&gt;
+ *
+ * &lt;/SlidingDrawer&gt;
+ * </pre>
+ *
+ * @attr ref android.R.styleable#SlidingDrawer_content
+ * @attr ref android.R.styleable#SlidingDrawer_handle
+ * @attr ref android.R.styleable#SlidingDrawer_topOffset
+ * @attr ref android.R.styleable#SlidingDrawer_bottomOffset
+ * @attr ref android.R.styleable#SlidingDrawer_orientation
+ * @attr ref android.R.styleable#SlidingDrawer_allowSingleTap
+ * @attr ref android.R.styleable#SlidingDrawer_animateOnClick
+ */
+public class SlidingDrawer extends ViewGroup {
+    public static final int ORIENTATION_HORIZONTAL = 0;
+    public static final int ORIENTATION_VERTICAL = 1;
+
+    private static final int TAP_THRESHOLD = 6;
+    private static final float MAXIMUM_TAP_VELOCITY = 100.0f;
+    private static final float MAXIMUM_MINOR_VELOCITY = 150.0f;
+    private static final float MAXIMUM_MAJOR_VELOCITY = 200.0f;
+    private static final float MAXIMUM_ACCELERATION = 2000.0f;
+    private static final int VELOCITY_UNITS = 1000;
+    private static final int MSG_ANIMATE = 1000;
+    private static final int ANIMATION_FRAME_DURATION = 1000 / 60;
+
+    private static final int EXPANDED_FULL_OPEN = -10001;
+    private static final int COLLAPSED_FULL_CLOSED = -10002;
+
+    private final int mHandleId;
+    private final int mContentId;
+
+    private View mHandle;
+    private View mContent;
+
+    private final Rect mFrame = new Rect();
+    private final Rect mInvalidate = new Rect();
+    private boolean mTracking;
+    private boolean mLocked;
+
+    private VelocityTracker mVelocityTracker;
+
+    private boolean mVertical;
+    private boolean mExpanded;
+    private int mBottomOffset;
+    private int mTopOffset;
+    private int mHandleHeight;
+    private int mHandleWidth;
+
+    private OnDrawerOpenListener mOnDrawerOpenListener;
+    private OnDrawerCloseListener mOnDrawerCloseListener;
+    private OnDrawerScrollListener mOnDrawerScrollListener;
+
+    private final Handler mHandler = new SlidingHandler();
+    private float mAnimatedAcceleration;
+    private float mAnimatedVelocity;
+    private float mAnimationPosition;
+    private long mAnimationLastTime;
+    private long mCurrentAnimationTime;
+    private int mTouchDelta;
+    private boolean mAnimating;
+    private boolean mAllowSingleTap;
+    private boolean mAnimateOnClick;
+
+    private final int mTapThreshold;
+    private final int mMaximumTapVelocity;
+    private final int mMaximumMinorVelocity;
+    private final int mMaximumMajorVelocity;
+    private final int mMaximumAcceleration;
+    private final int mVelocityUnits;
+
+    /**
+     * Callback invoked when the drawer is opened.
+     */
+    public static interface OnDrawerOpenListener {
+        /**
+         * Invoked when the drawer becomes fully open.
+         */
+        public void onDrawerOpened();
+    }
+
+    /**
+     * Callback invoked when the drawer is closed.
+     */
+    public static interface OnDrawerCloseListener {
+        /**
+         * Invoked when the drawer becomes fully closed.
+         */
+        public void onDrawerClosed();
+    }
+
+    /**
+     * Callback invoked when the drawer is scrolled.
+     */
+    public static interface OnDrawerScrollListener {
+        /**
+         * Invoked when the user starts dragging/flinging the drawer's handle.
+         */
+        public void onScrollStarted();
+
+        /**
+         * Invoked when the user stops dragging/flinging the drawer's handle.
+         */
+        public void onScrollEnded();
+    }
+
+    /**
+     * Creates a new SlidingDrawer from a specified set of attributes defined in XML.
+     *
+     * @param context The application's environment.
+     * @param attrs The attributes defined in XML.
+     */
+    public SlidingDrawer(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    /**
+     * Creates a new SlidingDrawer from a specified set of attributes defined in XML.
+     *
+     * @param context The application's environment.
+     * @param attrs The attributes defined in XML.
+     * @param defStyle The style to apply to this widget.
+     */
+    public SlidingDrawer(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.SlidingDrawer, defStyle, 0);
+
+        int orientation = a.getInt(R.styleable.SlidingDrawer_orientation, ORIENTATION_VERTICAL);
+        mVertical = orientation == ORIENTATION_VERTICAL;
+        mBottomOffset = (int) a.getDimension(R.styleable.SlidingDrawer_bottomOffset, 0.0f);
+        mTopOffset = (int) a.getDimension(R.styleable.SlidingDrawer_topOffset, 0.0f);
+        mAllowSingleTap = a.getBoolean(R.styleable.SlidingDrawer_allowSingleTap, true);
+        mAnimateOnClick = a.getBoolean(R.styleable.SlidingDrawer_animateOnClick, true);
+
+        int handleId = a.getResourceId(R.styleable.SlidingDrawer_handle, 0);
+        if (handleId == 0) {
+            throw new IllegalArgumentException("The handle attribute is required and must refer "
+                    + "to a valid child.");
+        }
+
+        int contentId = a.getResourceId(R.styleable.SlidingDrawer_content, 0);
+        if (contentId == 0) {
+            throw new IllegalArgumentException("The handle attribute is required and must refer "
+                    + "to a valid child.");
+        }
+
+        mHandleId = handleId;
+        mContentId = contentId;
+
+        final float density = getResources().getDisplayMetrics().density;
+        mTapThreshold = (int) (TAP_THRESHOLD * density + 0.5f);
+        mMaximumTapVelocity = (int) (MAXIMUM_TAP_VELOCITY * density + 0.5f);
+        mMaximumMinorVelocity = (int) (MAXIMUM_MINOR_VELOCITY * density + 0.5f);
+        mMaximumMajorVelocity = (int) (MAXIMUM_MAJOR_VELOCITY * density + 0.5f);
+        mMaximumAcceleration = (int) (MAXIMUM_ACCELERATION * density + 0.5f);
+        mVelocityUnits = (int) (VELOCITY_UNITS * density + 0.5f);
+
+        a.recycle();
+
+        setAlwaysDrawnWithCacheEnabled(false);
+    }
+
+    @Override
+    protected void onFinishInflate() {
+        mHandle = findViewById(mHandleId);
+        if (mHandle == null) {
+            throw new IllegalArgumentException("The handle attribute is must refer to an"
+                    + " existing child.");
+        }
+        mHandle.setOnClickListener(new DrawerToggler());
+
+        mContent = findViewById(mContentId);
+        if (mContent == null) {
+            throw new IllegalArgumentException("The content attribute is must refer to an" 
+                    + " existing child.");
+        }
+        mContent.setVisibility(View.GONE);
+    }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
+        int widthSpecSize =  MeasureSpec.getSize(widthMeasureSpec);
+
+        int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
+        int heightSpecSize =  MeasureSpec.getSize(heightMeasureSpec);
+
+        if (widthSpecMode == MeasureSpec.UNSPECIFIED || heightSpecMode == MeasureSpec.UNSPECIFIED) {
+            throw new RuntimeException("SlidingDrawer cannot have UNSPECIFIED dimensions");
+        }
+
+        final View handle = mHandle;
+        measureChild(handle, widthMeasureSpec, heightMeasureSpec);
+
+        if (mVertical) {
+            int height = heightSpecSize - handle.getMeasuredHeight() - mTopOffset;
+            mContent.measure(MeasureSpec.makeMeasureSpec(widthSpecSize, MeasureSpec.EXACTLY),
+                    MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY));
+        } else {
+            int width = widthSpecSize - handle.getMeasuredWidth() - mTopOffset;
+            mContent.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
+                    MeasureSpec.makeMeasureSpec(heightSpecSize, MeasureSpec.EXACTLY));
+        }
+
+        setMeasuredDimension(widthSpecSize, heightSpecSize);
+    }
+
+    @Override
+    protected void dispatchDraw(Canvas canvas) {
+        final long drawingTime = getDrawingTime();
+        final View handle = mHandle;
+        final boolean isVertical = mVertical;
+
+        drawChild(canvas, handle, drawingTime);
+
+        if (mTracking || mAnimating) {
+            final Bitmap cache = mContent.getDrawingCache();
+            if (cache != null) {
+                if (isVertical) {
+                    canvas.drawBitmap(cache, 0, handle.getBottom(), null);
+                } else {
+                    canvas.drawBitmap(cache, handle.getRight(), 0, null);                    
+                }
+            } else {
+                canvas.save();
+                canvas.translate(isVertical ? 0 : handle.getLeft() - mTopOffset,
+                        isVertical ? handle.getTop() - mTopOffset : 0);
+                drawChild(canvas, mContent, drawingTime);
+                canvas.restore();
+            }
+        } else if (mExpanded) {
+            drawChild(canvas, mContent, drawingTime);
+        }
+    }
+
+    @Override
+    protected void onLayout(boolean changed, int l, int t, int r, int b) {
+        if (mTracking) {
+            return;
+        }
+
+        final int width = r - l;
+        final int height = b - t;
+
+        final View handle = mHandle;
+
+        int childWidth = handle.getMeasuredWidth();
+        int childHeight = handle.getMeasuredHeight();
+
+        int childLeft;
+        int childTop;
+
+        final View content = mContent;
+
+        if (mVertical) {
+            childLeft = (width - childWidth) / 2;
+            childTop = mExpanded ? mTopOffset : height - childHeight + mBottomOffset;
+
+            content.layout(0, mTopOffset + childHeight, content.getMeasuredWidth(),
+                    mTopOffset + childHeight + content.getMeasuredHeight());
+        } else {
+            childLeft = mExpanded ? mTopOffset : width - childWidth + mBottomOffset;
+            childTop = (height - childHeight) / 2;
+
+            content.layout(mTopOffset + childWidth, 0,
+                    mTopOffset + childWidth + content.getMeasuredWidth(),
+                    content.getMeasuredHeight());            
+        }
+
+        handle.layout(childLeft, childTop, childLeft + childWidth, childTop + childHeight);
+        mHandleHeight = handle.getHeight();
+        mHandleWidth = handle.getWidth();
+    }
+
+    @Override
+    public boolean onInterceptTouchEvent(MotionEvent event) {
+        if (mLocked) {
+            return false;
+        }
+
+        final int action = event.getAction();
+
+        float x = event.getX();
+        float y = event.getY();
+
+        final Rect frame = mFrame;
+        final View handle = mHandle;
+
+        handle.getHitRect(frame);
+        if (!mTracking && !frame.contains((int) x, (int) y)) {
+            return false;
+        }
+
+        if (action == MotionEvent.ACTION_DOWN) {
+            if (mOnDrawerScrollListener != null) {
+                mOnDrawerScrollListener.onScrollStarted();
+            }
+            mTracking = true;
+
+            handle.setPressed(true);
+            // Must be called before prepareTracking()
+            prepareContent();
+
+            if (mVertical) {
+                final int top = mHandle.getTop();
+                mTouchDelta = (int) y - top;
+                prepareTracking(top);
+            } else {
+                final int left = mHandle.getLeft();
+                mTouchDelta = (int) x - left;
+                prepareTracking(left);
+            }
+            mVelocityTracker.addMovement(event);
+        }
+
+        return true;
+    }
+
+    @Override
+    public boolean onTouchEvent(MotionEvent event) {
+        if (mLocked) {
+            return true;
+        }
+
+        if (mTracking) {
+            mVelocityTracker.addMovement(event);
+            final int action = event.getAction();
+            switch (action) {
+                case MotionEvent.ACTION_MOVE:
+                    moveHandle((int) (mVertical ? event.getY() : event.getX()) - mTouchDelta);
+                    break;
+                case MotionEvent.ACTION_UP:
+                case MotionEvent.ACTION_CANCEL: {
+                    final VelocityTracker velocityTracker = mVelocityTracker;
+                    velocityTracker.computeCurrentVelocity(mVelocityUnits);
+
+                    float yVelocity = velocityTracker.getYVelocity();
+                    float xVelocity = velocityTracker.getXVelocity();
+                    boolean negative;
+
+                    final boolean vertical = mVertical;
+                    if (vertical) {
+                        negative = yVelocity < 0;
+                        if (xVelocity < 0) {
+                            xVelocity = -xVelocity;
+                        }
+                        if (xVelocity > mMaximumMinorVelocity) {
+                            xVelocity = mMaximumMinorVelocity;
+                        }
+                    } else {
+                        negative = xVelocity < 0;
+                        if (yVelocity < 0) {
+                            yVelocity = -yVelocity;
+                        }
+                        if (yVelocity > mMaximumMinorVelocity) {
+                            yVelocity = mMaximumMinorVelocity;
+                        }
+                    }
+
+                    float velocity = (float) Math.hypot(xVelocity, yVelocity);
+                    if (negative) {
+                        velocity = -velocity;
+                    }
+
+                    final int top = mHandle.getTop();
+                    final int left = mHandle.getLeft();
+
+                    if (Math.abs(velocity) < mMaximumTapVelocity) {
+                        if (vertical ? (mExpanded && top < mTapThreshold + mTopOffset) ||
+                                (!mExpanded && top > mBottomOffset + mBottom - mTop -
+                                        mHandleHeight - mTapThreshold) :
+                                (mExpanded && left < mTapThreshold + mTopOffset) ||
+                                (!mExpanded && left > mBottomOffset + mRight - mLeft -
+                                        mHandleWidth - mTapThreshold)) {
+
+                            if (mAllowSingleTap) {
+                                playSoundEffect(SoundEffectConstants.CLICK);
+
+                                if (mExpanded) {
+                                    animateClose(vertical ? top : left);
+                                } else {
+                                    animateOpen(vertical ? top : left);
+                                }
+                            }
+
+                        } else {
+                            performFling(vertical ? top : left, velocity, false);
+                        }
+                    } else {
+                        performFling(vertical ? top : left, velocity, false);
+                    }
+                }
+                break;
+            }
+        }
+
+        return mTracking || mAnimating || super.onTouchEvent(event);
+    }
+
+    private void animateClose(int position) {
+        prepareTracking(position);
+        performFling(position, mMaximumAcceleration, true);
+    }
+
+    private void animateOpen(int position) {
+        prepareTracking(position);
+        performFling(position, -mMaximumAcceleration, true);
+    }
+
+    private void performFling(int position, float velocity, boolean always) {
+        mAnimationPosition = position;
+        mAnimatedVelocity = velocity;
+
+        if (mExpanded) {
+            if (always || (velocity > mMaximumMajorVelocity ||
+                    (position > mTopOffset + (mVertical ? mHandleHeight : mHandleWidth) &&
+                            velocity > -mMaximumMajorVelocity))) {
+                // We are expanded, but they didn't move sufficiently to cause
+                // us to retract.  Animate back to the expanded position.
+                mAnimatedAcceleration = mMaximumAcceleration;
+                if (velocity < 0) {
+                    mAnimatedVelocity = 0;
+                }
+            } else {
+                // We are expanded and are now going to animate away.
+                mAnimatedAcceleration = -mMaximumAcceleration;
+                if (velocity > 0) {
+                    mAnimatedVelocity = 0;
+                }
+            }
+        } else {
+            if (!always && (velocity > mMaximumMajorVelocity ||
+                    (position > (mVertical ? getHeight() : getWidth()) / 2 &&
+                            velocity > -mMaximumMajorVelocity))) {
+                // We are collapsed, and they moved enough to allow us to expand.
+                mAnimatedAcceleration = mMaximumAcceleration;
+                if (velocity < 0) {
+                    mAnimatedVelocity = 0;
+                }
+            } else {
+                // We are collapsed, but they didn't move sufficiently to cause
+                // us to retract.  Animate back to the collapsed position.
+                mAnimatedAcceleration = -mMaximumAcceleration;
+                if (velocity > 0) {
+                    mAnimatedVelocity = 0;
+                }
+            }
+        }
+
+        long now = SystemClock.uptimeMillis();
+        mAnimationLastTime = now;
+        mCurrentAnimationTime = now + ANIMATION_FRAME_DURATION;
+        mAnimating = true;
+        mHandler.removeMessages(MSG_ANIMATE);
+        mHandler.sendMessageAtTime(mHandler.obtainMessage(MSG_ANIMATE), mCurrentAnimationTime);
+        stopTracking();
+    }
+
+    private void prepareTracking(int position) {
+        mTracking = true;
+        mVelocityTracker = VelocityTracker.obtain();
+        boolean opening = !mExpanded;
+        if (opening) {
+            mAnimatedAcceleration = mMaximumAcceleration;
+            mAnimatedVelocity = mMaximumMajorVelocity;
+            mAnimationPosition = mBottomOffset +
+                    (mVertical ? getHeight() - mHandleHeight : getWidth() - mHandleWidth);
+            moveHandle((int) mAnimationPosition);
+            mAnimating = true;
+            mHandler.removeMessages(MSG_ANIMATE);
+            long now = SystemClock.uptimeMillis();
+            mAnimationLastTime = now;
+            mCurrentAnimationTime = now + ANIMATION_FRAME_DURATION;
+            mAnimating = true;
+        } else {
+            if (mAnimating) {
+                mAnimating = false;
+                mHandler.removeMessages(MSG_ANIMATE);
+            }
+            moveHandle(position);
+        }
+    }
+
+    private void moveHandle(int position) {
+        final View handle = mHandle;
+
+        if (mVertical) {
+            if (position == EXPANDED_FULL_OPEN) {
+                handle.offsetTopAndBottom(mTopOffset - handle.getTop());
+                invalidate();
+            } else if (position == COLLAPSED_FULL_CLOSED) {
+                handle.offsetTopAndBottom(mBottomOffset + mBottom - mTop -
+                        mHandleHeight - handle.getTop());
+                invalidate();
+            } else {
+                final int top = handle.getTop();
+                int deltaY = position - top;
+                if (position < mTopOffset) {
+                    deltaY = mTopOffset - top;
+                } else if (deltaY > mBottomOffset + mBottom - mTop - mHandleHeight - top) {
+                    deltaY = mBottomOffset + mBottom - mTop - mHandleHeight - top;
+                }
+                handle.offsetTopAndBottom(deltaY);
+
+                final Rect frame = mFrame;
+                final Rect region = mInvalidate;
+
+                handle.getHitRect(frame);
+                region.set(frame);
+
+                region.union(frame.left, frame.top - deltaY, frame.right, frame.bottom - deltaY);
+                region.union(0, frame.bottom - deltaY, getWidth(),
+                        frame.bottom - deltaY + mContent.getHeight());
+
+                invalidate(region);
+            }
+        } else {
+            if (position == EXPANDED_FULL_OPEN) {
+                handle.offsetLeftAndRight(mTopOffset - handle.getLeft());
+                invalidate();
+            } else if (position == COLLAPSED_FULL_CLOSED) {
+                handle.offsetLeftAndRight(mBottomOffset + mRight - mLeft -
+                        mHandleWidth - handle.getLeft());
+                invalidate();
+            } else {
+                final int left = handle.getLeft();
+                int deltaX = position - left;
+                if (position < mTopOffset) {
+                    deltaX = mTopOffset - left;
+                } else if (deltaX > mBottomOffset + mRight - mLeft - mHandleWidth - left) {
+                    deltaX = mBottomOffset + mRight - mLeft - mHandleWidth - left;
+                }
+                handle.offsetLeftAndRight(deltaX);
+
+                final Rect frame = mFrame;
+                final Rect region = mInvalidate;
+
+                handle.getHitRect(frame);
+                region.set(frame);
+
+                region.union(frame.left - deltaX, frame.top, frame.right - deltaX, frame.bottom);
+                region.union(frame.right - deltaX, 0,
+                        frame.right - deltaX + mContent.getWidth(), getHeight());
+
+                invalidate(region);
+            }
+        }
+    }
+
+    private void prepareContent() {
+        if (mAnimating) {
+            return;
+        }
+
+        // Something changed in the content, we need to honor the layout request
+        // before creating the cached bitmap
+        final View content = mContent;
+        if (content.isLayoutRequested()) {
+            if (mVertical) {
+                final int childHeight = mHandleHeight;
+                int height = mBottom - mTop - childHeight - mTopOffset;
+                content.measure(MeasureSpec.makeMeasureSpec(mRight - mLeft, MeasureSpec.EXACTLY),
+                        MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY));
+                content.layout(0, mTopOffset + childHeight, content.getMeasuredWidth(),
+                        mTopOffset + childHeight + content.getMeasuredHeight());
+            } else {
+                final int childWidth = mHandle.getWidth();
+                int width = mRight - mLeft - childWidth - mTopOffset;
+                content.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
+                        MeasureSpec.makeMeasureSpec(mBottom - mTop, MeasureSpec.EXACTLY));
+                content.layout(childWidth + mTopOffset, 0,
+                        mTopOffset + childWidth + content.getMeasuredWidth(),
+                        content.getMeasuredHeight());
+            }
+        }
+        // Try only once... we should really loop but it's not a big deal
+        // if the draw was cancelled, it will only be temporary anyway
+        content.getViewTreeObserver().dispatchOnPreDraw();
+        content.buildDrawingCache();
+
+        content.setVisibility(View.GONE);        
+    }
+
+    private void stopTracking() {
+        mHandle.setPressed(false);
+        mTracking = false;
+
+        if (mOnDrawerScrollListener != null) {
+            mOnDrawerScrollListener.onScrollEnded();
+        }
+
+        if (mVelocityTracker != null) {
+            mVelocityTracker.recycle();
+            mVelocityTracker = null;
+        }
+    }
+
+    private void doAnimation() {
+        if (mAnimating) {
+            incrementAnimation();
+            if (mAnimationPosition >= mBottomOffset + (mVertical ? getHeight() : getWidth()) - 1) {
+                mAnimating = false;
+                closeDrawer();
+            } else if (mAnimationPosition < mTopOffset) {
+                mAnimating = false;
+                openDrawer();
+            } else {
+                moveHandle((int) mAnimationPosition);
+                mCurrentAnimationTime += ANIMATION_FRAME_DURATION;
+                mHandler.sendMessageAtTime(mHandler.obtainMessage(MSG_ANIMATE),
+                        mCurrentAnimationTime);
+            }
+        }
+    }
+
+    private void incrementAnimation() {
+        long now = SystemClock.uptimeMillis();
+        float t = (now - mAnimationLastTime) / 1000.0f;                   // ms -> s
+        final float position = mAnimationPosition;
+        final float v = mAnimatedVelocity;                                // px/s
+        final float a = mAnimatedAcceleration;                            // px/s/s
+        mAnimationPosition = position + (v * t) + (0.5f * a * t * t);     // px
+        mAnimatedVelocity = v + (a * t);                                  // px/s
+        mAnimationLastTime = now;                                         // ms
+    }
+
+    /**
+     * Toggles the drawer open and close. Takes effect immediately.
+     *
+     * @see #open()
+     * @see #close()
+     * @see #animateClose()
+     * @see #animateOpen()
+     * @see #animateToggle()
+     */
+    public void toggle() {
+        if (!mExpanded) {
+            openDrawer();
+        } else {
+            closeDrawer();
+        }
+        invalidate();
+        requestLayout();
+    }
+
+    /**
+     * Toggles the drawer open and close with an animation.
+     *
+     * @see #open()
+     * @see #close()
+     * @see #animateClose()
+     * @see #animateOpen()
+     * @see #toggle()
+     */
+    public void animateToggle() {
+        if (!mExpanded) {
+            animateOpen();
+        } else {
+            animateClose();
+        }
+    }
+
+    /**
+     * Opens the drawer immediately.
+     *
+     * @see #toggle()
+     * @see #close()
+     * @see #animateOpen()
+     */
+    public void open() {
+        openDrawer();
+        invalidate();
+        requestLayout();
+    }
+
+    /**
+     * Closes the drawer immediately.
+     *
+     * @see #toggle()
+     * @see #open()
+     * @see #animateClose()
+     */
+    public void close() {
+        closeDrawer();
+        invalidate();
+        requestLayout();
+    }
+
+    /**
+     * Closes the drawer with an animation.
+     *
+     * @see #close()
+     * @see #open()
+     * @see #animateOpen()
+     * @see #animateToggle()
+     * @see #toggle()
+     */
+    public void animateClose() {
+        final OnDrawerScrollListener scrollListener = mOnDrawerScrollListener;
+        if (scrollListener != null) {
+            scrollListener.onScrollStarted();
+        }
+        prepareContent();
+        animateClose(mVertical ? mHandle.getTop() : mHandle.getLeft());
+        if (scrollListener != null) {
+            scrollListener.onScrollEnded();
+        }
+    }
+
+    /**
+     * Opens the drawer with an animation.
+     *
+     * @see #close()
+     * @see #open()
+     * @see #animateClose()
+     * @see #animateToggle()
+     * @see #toggle()
+     */
+    public void animateOpen() {
+        final OnDrawerScrollListener scrollListener = mOnDrawerScrollListener;
+        if (scrollListener != null) {
+            scrollListener.onScrollStarted();
+        }
+        prepareContent();
+        animateOpen(mVertical ? mHandle.getTop() : mHandle.getLeft());
+        if (scrollListener != null) {
+            scrollListener.onScrollEnded();
+        }
+    }
+
+    private void closeDrawer() {
+        moveHandle(COLLAPSED_FULL_CLOSED);
+        mContent.setVisibility(View.GONE);
+        mContent.destroyDrawingCache();
+
+        if (!mExpanded) {
+            return;
+        }
+
+        mExpanded = false;
+        if (mOnDrawerCloseListener != null) {
+            mOnDrawerCloseListener.onDrawerClosed();
+        }
+    }
+
+    private void openDrawer() {
+        moveHandle(EXPANDED_FULL_OPEN);
+        mContent.setVisibility(View.VISIBLE);
+
+        if (mExpanded) {
+            return;
+        }
+
+        mExpanded = true;
+        if (mOnDrawerOpenListener != null) {
+            mOnDrawerOpenListener.onDrawerOpened();
+        }
+    }
+
+    /**
+     * Sets the listener that receives a notification when the drawer becomes open.
+     *
+     * @param onDrawerOpenListener The listener to be notified when the drawer is opened.
+     */
+    public void setOnDrawerOpenListener(OnDrawerOpenListener onDrawerOpenListener) {
+        mOnDrawerOpenListener = onDrawerOpenListener;
+    }
+
+    /**
+     * Sets the listener that receives a notification when the drawer becomes close.
+     *
+     * @param onDrawerCloseListener The listener to be notified when the drawer is closed.
+     */
+    public void setOnDrawerCloseListener(OnDrawerCloseListener onDrawerCloseListener) {
+        mOnDrawerCloseListener = onDrawerCloseListener;
+    }
+
+    /**
+     * Sets the listener that receives a notification when the drawer starts or ends
+     * a scroll. A fling is considered as a scroll. A fling will also trigger a
+     * drawer opened or drawer closed event.
+     *
+     * @param onDrawerScrollListener The listener to be notified when scrolling
+     *        starts or stops.
+     */
+    public void setOnDrawerScrollListener(OnDrawerScrollListener onDrawerScrollListener) {
+        mOnDrawerScrollListener = onDrawerScrollListener;
+    }
+
+    /**
+     * Returns the handle of the drawer.
+     *
+     * @return The View reprenseting the handle of the drawer, identified by
+     *         the "handle" id in XML.
+     */
+    public View getHandle() {
+        return mHandle;
+    }
+
+    /**
+     * Returns the content of the drawer.
+     *
+     * @return The View reprenseting the content of the drawer, identified by
+     *         the "content" id in XML.
+     */
+    public View getContent() {
+        return mContent;
+    }
+
+    /**
+     * Unlocks the SlidingDrawer so that touch events are processed.
+     *
+     * @see #lock() 
+     */
+    public void unlock() {
+        mLocked = false;
+    }
+
+    /**
+     * Locks the SlidingDrawer so that touch events are ignores.
+     *
+     * @see #unlock()
+     */
+    public void lock() {
+        mLocked = true;
+    }
+
+    /**
+     * Indicates whether the drawer is currently fully opened.
+     *
+     * @return True if the drawer is opened, false otherwise.
+     */
+    public boolean isOpened() {
+        return mExpanded;
+    }
+
+    /**
+     * Indicates whether the drawer is scrolling or flinging.
+     *
+     * @return True if the drawer is scroller or flinging, false otherwise.
+     */
+    public boolean isMoving() {
+        return mTracking || mAnimating;
+    }
+
+    private class DrawerToggler implements OnClickListener {
+        public void onClick(View v) {
+            if (mLocked) {
+                return;
+            }
+            // mAllowSingleTap isn't relevant here; you're *always*
+            // allowed to open/close the drawer by clicking with the
+            // trackball.
+
+            if (mAnimateOnClick) {
+                animateToggle();
+            } else {
+                toggle();
+            }
+        }
+    }
+
+    private class SlidingHandler extends Handler {
+        public void handleMessage(Message m) {
+            switch (m.what) {
+                case MSG_ANIMATE:
+                    doAnimation();
+                    break;
+            }
+        }
+    }
+}
diff --git a/core/java/android/widget/Spinner.java b/core/java/android/widget/Spinner.java
new file mode 100644
index 0000000..80d688e
--- /dev/null
+++ b/core/java/android/widget/Spinner.java
@@ -0,0 +1,364 @@
+/*
+ * Copyright (C) 2007 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 android.widget;
+
+import android.annotation.Widget;
+import android.app.AlertDialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.DialogInterface.OnClickListener;
+import android.content.res.TypedArray;
+import android.database.DataSetObserver;
+import android.util.AttributeSet;
+import android.view.KeyEvent;
+import android.view.View;
+import android.view.ViewGroup;
+
+
+/**
+ * A view that displays one child at a time and lets the user pick among them.
+ * The items in the Spinner come from the {@link Adapter} associated with
+ * this view.
+ * 
+ * @attr ref android.R.styleable#Spinner_prompt
+ */
+@Widget
+public class Spinner extends AbsSpinner implements OnClickListener {
+    
+    private CharSequence mPrompt;
+
+    public Spinner(Context context) {
+        this(context, null);
+    }
+
+    public Spinner(Context context, AttributeSet attrs) {
+        this(context, attrs, com.android.internal.R.attr.spinnerStyle);
+    }
+
+    public Spinner(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+
+        TypedArray a = context.obtainStyledAttributes(attrs,
+                com.android.internal.R.styleable.Spinner, defStyle, 0);
+        
+        mPrompt = a.getString(com.android.internal.R.styleable.Spinner_prompt);
+
+        a.recycle();
+    }
+
+    @Override
+    public int getBaseline() {
+        View child = null;
+
+        if (getChildCount() > 0) {
+            child = getChildAt(0);
+        } else if (mAdapter != null && mAdapter.getCount() > 0) {
+            child = makeAndAddView(0);
+            // TODO: We should probably put the child in the recycler
+        }
+
+        if (child != null) {
+            return child.getTop() + child.getBaseline();
+        } else {
+            return -1;
+        }
+    }
+
+    /**
+     * <p>A spinner does not support item click events. Calling this method
+     * will raise an exception.</p>
+     *
+     * @param l this listener will be ignored
+     */
+    @Override
+    public void setOnItemClickListener(OnItemClickListener l) {
+        throw new RuntimeException("setOnItemClickListener cannot be used with a spinner.");
+    }
+
+    /**
+     * @see android.view.View#onLayout(boolean,int,int,int,int)
+     *
+     * Creates and positions all views
+     *
+     */
+    @Override
+    protected void onLayout(boolean changed, int l, int t, int r, int b) {
+        super.onLayout(changed, l, t, r, b);
+        mInLayout = true;
+        layout(0, false);
+        mInLayout = false;
+    }
+
+    /**
+     * Creates and positions all views for this Spinner.
+     *
+     * @param delta Change in the selected position. +1 moves selection is moving to the right,
+     * so views are scrolling to the left. -1 means selection is moving to the left.
+     */
+    @Override
+    void layout(int delta, boolean animate) {
+        int childrenLeft = mSpinnerPadding.left;
+        int childrenWidth = mRight - mLeft - mSpinnerPadding.left - mSpinnerPadding.right;
+
+        if (mDataChanged) {
+            handleDataChanged();
+        }
+
+        // Handle the empty set by removing all views
+        if (mItemCount == 0) {
+            resetList();
+            return;
+        }
+
+        if (mNextSelectedPosition >= 0) {
+            setSelectedPositionInt(mNextSelectedPosition);
+        }
+
+        recycleAllViews();
+
+        // Clear out old views
+        removeAllViewsInLayout();
+
+        // Make selected view and center it
+        mFirstPosition = mSelectedPosition;
+        View sel = makeAndAddView(mSelectedPosition);
+        int width = sel.getMeasuredWidth();
+        int selectedOffset = childrenLeft + (childrenWidth / 2) - (width / 2);
+        sel.offsetLeftAndRight(selectedOffset);
+
+        // Flush any cached views that did not get reused above
+        mRecycler.clear();
+
+        invalidate();
+
+        checkSelectionChanged();
+
+        mDataChanged = false;
+        mNeedSync = false;
+        setNextSelectedPositionInt(mSelectedPosition);
+    }
+
+    /**
+     * Obtain a view, either by pulling an existing view from the recycler or
+     * by getting a new one from the adapter. If we are animating, make sure
+     * there is enough information in the view's layout parameters to animate
+     * from the old to new positions.
+     *
+     * @param position Position in the spinner for the view to obtain
+     * @return A view that has been added to the spinner
+     */
+    private View makeAndAddView(int position) {
+
+        View child;
+
+        if (!mDataChanged) {
+            child = mRecycler.get(position);
+            if (child != null) {
+                // Position the view
+                setUpChild(child);
+
+                return child;
+            }
+        }
+
+        // Nothing found in the recycler -- ask the adapter for a view
+        child = mAdapter.getView(position, null, this);
+
+        // Position the view
+        setUpChild(child);
+
+        return child;
+    }
+
+
+
+    /**
+     * Helper for makeAndAddView to set the position of a view
+     * and fill out its layout paramters.
+     *
+     * @param child The view to position
+     */
+    private void setUpChild(View child) {
+
+        // Respect layout params that are already in the view. Otherwise
+        // make some up...
+        ViewGroup.LayoutParams lp = child.getLayoutParams();
+        if (lp == null) {
+            lp = generateDefaultLayoutParams();
+        }
+
+        addViewInLayout(child, 0, lp);
+
+        child.setSelected(hasFocus());
+
+        // Get measure specs
+        int childHeightSpec = ViewGroup.getChildMeasureSpec(mHeightMeasureSpec,
+                mSpinnerPadding.top + mSpinnerPadding.bottom, lp.height);
+        int childWidthSpec = ViewGroup.getChildMeasureSpec(mWidthMeasureSpec,
+                mSpinnerPadding.left + mSpinnerPadding.right, lp.width);
+
+        // Measure child
+        child.measure(childWidthSpec, childHeightSpec);
+
+        int childLeft;
+        int childRight;
+
+        // Position vertically based on gravity setting
+        int childTop = mSpinnerPadding.top
+                + ((mMeasuredHeight - mSpinnerPadding.bottom - 
+                        mSpinnerPadding.top - child.getMeasuredHeight()) / 2);
+        int childBottom = childTop + child.getMeasuredHeight();
+
+        int width = child.getMeasuredWidth();
+        childLeft = 0;
+        childRight = childLeft + width;
+
+        child.layout(childLeft, childTop, childRight, childBottom);
+    }
+
+    @Override
+    public boolean performClick() {
+        boolean handled = super.performClick();
+        
+        if (!handled) {
+            handled = true;
+            Context context = getContext();
+            
+            final DropDownAdapter adapter = new DropDownAdapter(getAdapter());
+
+            AlertDialog.Builder builder = new AlertDialog.Builder(context);
+            if (mPrompt != null) {
+                builder.setTitle(mPrompt);
+            }
+            builder.setSingleChoiceItems(adapter, getSelectedItemPosition(), this).show();
+        }
+
+        return handled;
+    }
+    
+    public void onClick(DialogInterface dialog, int which) {
+        setSelection(which);
+        dialog.dismiss();
+    }
+
+    /**
+     * Sets the prompt to display when the dialog is shown.
+     * @param prompt the prompt to set
+     */
+    public void setPrompt(CharSequence prompt) {
+        mPrompt = prompt;
+    }
+
+    /**
+     * Sets the prompt to display when the dialog is shown.
+     * @param promptId the resource ID of the prompt to display when the dialog is shown
+     */
+    public void setPromptId(int promptId) {
+        mPrompt = getContext().getText(promptId);
+    }
+
+    /**
+     * @return The prompt to display when the dialog is shown
+     */
+    public CharSequence getPrompt() {
+        return mPrompt;
+    }
+    
+    /**
+     * <p>Wrapper class for an Adapter. Transforms the embedded Adapter instance
+     * into a ListAdapter.</p>
+     */
+    private static class DropDownAdapter implements ListAdapter, SpinnerAdapter {
+        private SpinnerAdapter mAdapter;
+
+        /**
+         * <p>Creates a new ListAddapter wrapper for the specified adapter.</p>
+         *
+         * @param adapter the Adapter to transform into a ListAdapter
+         */
+        public DropDownAdapter(SpinnerAdapter adapter) {
+            this.mAdapter = adapter;
+        }
+
+        public int getCount() {
+            return mAdapter == null ? 0 : mAdapter.getCount();
+        }
+
+        public Object getItem(int position) {
+            return mAdapter == null ? null : mAdapter.getItem(position);
+        }
+
+        public long getItemId(int position) {
+            return mAdapter == null ? -1 : mAdapter.getItemId(position);
+        }
+
+        public View getView(int position, View convertView, ViewGroup parent) {
+            return getDropDownView(position, convertView, parent);
+        }
+
+        public View getDropDownView(int position, View convertView, ViewGroup parent) {
+            return mAdapter == null ? null :
+                    mAdapter.getDropDownView(position, convertView, parent);
+        }
+
+        public boolean hasStableIds() {
+            return mAdapter != null && mAdapter.hasStableIds();
+        }
+
+        public void registerDataSetObserver(DataSetObserver observer) {
+            if (mAdapter != null) {
+                mAdapter.registerDataSetObserver(observer);
+            }
+        }
+
+        public void unregisterDataSetObserver(DataSetObserver observer) {
+            if (mAdapter != null) {
+                mAdapter.unregisterDataSetObserver(observer);
+            }
+        }
+
+        /**
+         * <p>Always returns false.</p>
+         *
+         * @return false
+         */
+        public boolean areAllItemsEnabled() {
+            return true;
+        }
+
+        /**
+         * <p>Always returns false.</p>
+         *
+         * @return false
+         */
+        public boolean isEnabled(int position) {
+            return true;
+        }
+
+        public int getItemViewType(int position) {
+            return 0;
+        }
+
+        public int getViewTypeCount() {
+            return 1;
+        }
+        
+        public boolean isEmpty() {
+            return getCount() == 0;
+        }
+    }
+}
diff --git a/core/java/android/widget/SpinnerAdapter.java b/core/java/android/widget/SpinnerAdapter.java
new file mode 100644
index 0000000..91504cf
--- /dev/null
+++ b/core/java/android/widget/SpinnerAdapter.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2007 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 android.widget;
+
+import android.view.View;
+import android.view.ViewGroup;
+
+/**
+ * Extended {@link Adapter} that is the bridge between a
+ * {@link android.widget.Spinner} and its data. A spinner adapter allows to
+ * define two different views: one that shows the data in the spinner itself and
+ * one that shows the data in the drop down list when the spinner is pressed.</p>
+ */
+public interface SpinnerAdapter extends Adapter {
+    /**
+     * <p>Get a {@link android.view.View} that displays in the drop down popup
+     * the data at the specified position in the data set.</p>
+     *
+     * @param position      index of the item whose view we want.
+     * @param convertView   the old view to reuse, if possible. Note: You should
+     *        check that this view is non-null and of an appropriate type before
+     *        using. If it is not possible to convert this view to display the
+     *        correct data, this method can create a new view.
+     * @param parent the parent that this view will eventually be attached to
+     * @return a {@link android.view.View} corresponding to the data at the
+     *         specified position.
+     */
+    public View getDropDownView(int position, View convertView, ViewGroup parent);
+}
diff --git a/core/java/android/widget/TabHost.java b/core/java/android/widget/TabHost.java
new file mode 100644
index 0000000..dc2c70d
--- /dev/null
+++ b/core/java/android/widget/TabHost.java
@@ -0,0 +1,632 @@
+/*
+ * 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 android.widget;
+
+import android.app.LocalActivityManager;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.drawable.Drawable;
+import android.util.AttributeSet;
+import android.view.KeyEvent;
+import android.view.LayoutInflater;
+import android.view.SoundEffectConstants;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewTreeObserver;
+import android.view.Window;
+import com.android.internal.R;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Container for a tabbed window view. This object holds two children: a set of tab labels that the
+ * user clicks to select a specific tab, and a FrameLayout object that displays the contents of that
+ * page. The individual elements are typically controlled using this container object, rather than
+ * setting values on the child elements themselves.
+ */
+public class TabHost extends FrameLayout implements ViewTreeObserver.OnTouchModeChangeListener {
+
+    private TabWidget mTabWidget;
+    private FrameLayout mTabContent;
+    private List<TabSpec> mTabSpecs = new ArrayList<TabSpec>(2);
+    /**
+     * This field should be made private, so it is hidden from the SDK.
+     * {@hide}
+     */
+    protected int mCurrentTab = -1;
+    private View mCurrentView = null;
+    /**
+     * This field should be made private, so it is hidden from the SDK.
+     * {@hide}
+     */
+    protected LocalActivityManager mLocalActivityManager = null;
+    private OnTabChangeListener mOnTabChangeListener;
+    private OnKeyListener mTabKeyListener;
+
+    public TabHost(Context context) {
+        super(context);
+        initTabHost();
+    }
+
+    public TabHost(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        initTabHost();
+    }
+
+    private final void initTabHost() {
+        setFocusableInTouchMode(true);
+        setDescendantFocusability(FOCUS_AFTER_DESCENDANTS);
+
+        mCurrentTab = -1;
+        mCurrentView = null;
+    }
+
+    /**
+     * Get a new {@link TabSpec} associated with this tab host.
+     * @param tag required tag of tab.
+     */
+    public TabSpec newTabSpec(String tag) {
+        return new TabSpec(tag);
+    }
+
+
+
+    /**
+      * <p>Call setup() before adding tabs if loading TabHost using findViewById(). <i><b>However</i></b>: You do
+      * not need to call setup() after getTabHost() in {@link android.app.TabActivity TabActivity}.
+      * Example:</p>
+<pre>mTabHost = (TabHost)findViewById(R.id.tabhost);
+mTabHost.setup();
+mTabHost.addTab(TAB_TAG_1, "Hello, world!", "Tab 1");
+      */
+    public void setup() {
+        mTabWidget = (TabWidget) findViewById(com.android.internal.R.id.tabs);
+        if (mTabWidget == null) {
+            throw new RuntimeException(
+                    "Your TabHost must have a TabWidget whose id attribute is 'android.R.id.tabs'");
+        }
+        
+        // KeyListener to attach to all tabs. Detects non-navigation keys 
+        // and relays them to the tab content.
+        mTabKeyListener = new OnKeyListener() {
+            public boolean onKey(View v, int keyCode, KeyEvent event) {
+                switch (keyCode) {
+                    case KeyEvent.KEYCODE_DPAD_CENTER:
+                    case KeyEvent.KEYCODE_DPAD_LEFT:
+                    case KeyEvent.KEYCODE_DPAD_RIGHT:
+                    case KeyEvent.KEYCODE_DPAD_UP:
+                    case KeyEvent.KEYCODE_DPAD_DOWN:
+                    case KeyEvent.KEYCODE_ENTER:
+                        return false;
+                    
+                }
+                mTabContent.requestFocus(View.FOCUS_FORWARD);
+                return mTabContent.dispatchKeyEvent(event);
+            }
+            
+        };
+        
+        mTabWidget.setTabSelectionListener(new TabWidget.OnTabSelectionChanged() {
+            public void onTabSelectionChanged(int tabIndex, boolean clicked) {
+                setCurrentTab(tabIndex);
+                if (clicked) {
+                    mTabContent.requestFocus(View.FOCUS_FORWARD);
+                }
+            }
+        });
+
+        mTabContent = (FrameLayout) findViewById(com.android.internal.R.id.tabcontent);
+        if (mTabContent == null) {
+            throw new RuntimeException(
+                    "Your TabHost must have a FrameLayout whose id attribute is 'android.R.id.tabcontent'");
+        }
+    }
+
+    /**
+     * If you are using {@link TabSpec#setContent(android.content.Intent)}, this
+     * must be called since the activityGroup is needed to launch the local activity.
+     *
+     * This is done for you if you extend {@link android.app.TabActivity}.
+     * @param activityGroup Used to launch activities for tab content.
+     */
+    public void setup(LocalActivityManager activityGroup) {
+        setup();
+        mLocalActivityManager = activityGroup;
+    }
+
+
+    @Override
+    protected void onAttachedToWindow() {
+        super.onAttachedToWindow();
+        final ViewTreeObserver treeObserver = getViewTreeObserver();
+        if (treeObserver != null) {
+            treeObserver.addOnTouchModeChangeListener(this);
+        }
+    }
+
+    @Override
+    protected void onDetachedFromWindow() {
+        super.onDetachedFromWindow();
+        final ViewTreeObserver treeObserver = getViewTreeObserver();
+        if (treeObserver != null) {
+            treeObserver.removeOnTouchModeChangeListener(this);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void onTouchModeChanged(boolean isInTouchMode) {
+        if (!isInTouchMode) {
+            // leaving touch mode.. if nothing has focus, let's give it to
+            // the indicator of the current tab
+            if (!mCurrentView.hasFocus() || mCurrentView.isFocused()) {
+                mTabWidget.getChildAt(mCurrentTab).requestFocus();
+            }
+        }
+    }
+
+    /**
+     * Add a tab.
+     * @param tabSpec Specifies how to create the indicator and content.
+     */
+    public void addTab(TabSpec tabSpec) {
+
+        if (tabSpec.mIndicatorStrategy == null) {
+            throw new IllegalArgumentException("you must specify a way to create the tab indicator.");
+        }
+
+        if (tabSpec.mContentStrategy == null) {
+            throw new IllegalArgumentException("you must specify a way to create the tab content");
+        }
+        View tabIndicator = tabSpec.mIndicatorStrategy.createIndicatorView();
+        tabIndicator.setOnKeyListener(mTabKeyListener);
+        mTabWidget.addView(tabIndicator);
+        mTabSpecs.add(tabSpec);
+
+        if (mCurrentTab == -1) {
+            setCurrentTab(0);
+        }
+    }
+
+
+    /**
+     * Removes all tabs from the tab widget associated with this tab host.
+     */
+    public void clearAllTabs() {
+        mTabWidget.removeAllViews();
+        initTabHost();
+        mTabContent.removeAllViews();
+        mTabSpecs.clear();
+        requestLayout();
+        invalidate();
+    }
+
+    public TabWidget getTabWidget() {
+        return mTabWidget;
+    }
+
+    public int getCurrentTab() {
+        return mCurrentTab;
+    }
+
+    public String getCurrentTabTag() {
+        if (mCurrentTab >= 0 && mCurrentTab < mTabSpecs.size()) {
+            return mTabSpecs.get(mCurrentTab).getTag();
+        }
+        return null;
+    }
+
+    public View getCurrentTabView() {
+        if (mCurrentTab >= 0 && mCurrentTab < mTabSpecs.size()) {
+            return mTabWidget.getChildAt(mCurrentTab);
+        }
+        return null;
+    }
+
+    public View getCurrentView() {
+        return mCurrentView;
+    }
+
+    public void setCurrentTabByTag(String tag) {
+        int i;
+        for (i = 0; i < mTabSpecs.size(); i++) {
+            if (mTabSpecs.get(i).getTag().equals(tag)) {
+                setCurrentTab(i);
+                break;
+            }
+        }
+    }
+
+    /**
+     * Get the FrameLayout which holds tab content
+     */
+    public FrameLayout getTabContentView() {
+        return mTabContent;
+    }
+
+    @Override
+    public boolean dispatchKeyEvent(KeyEvent event) {
+        final boolean handled = super.dispatchKeyEvent(event);
+
+        // unhandled key ups change focus to tab indicator for embedded activities
+        // when there is nothing that will take focus from default focus searching
+        if (!handled
+                && (event.getAction() == KeyEvent.ACTION_DOWN)
+                && (event.getKeyCode() == KeyEvent.KEYCODE_DPAD_UP)
+                && (mCurrentView.isRootNamespace())
+                && (mCurrentView.hasFocus())
+                && (mCurrentView.findFocus().focusSearch(View.FOCUS_UP) == null)) {
+            mTabWidget.getChildAt(mCurrentTab).requestFocus();
+            playSoundEffect(SoundEffectConstants.NAVIGATION_UP);
+            return true;
+        }
+        return handled;        
+    }
+
+
+    @Override
+    public void dispatchWindowFocusChanged(boolean hasFocus) {
+        mCurrentView.dispatchWindowFocusChanged(hasFocus);
+    }
+
+    public void setCurrentTab(int index) {
+        if (index < 0 || index >= mTabSpecs.size()) {
+            return;
+        }
+
+        if (index == mCurrentTab) {
+            return;
+        }
+
+        // notify old tab content
+        if (mCurrentTab != -1) {
+            mTabSpecs.get(mCurrentTab).mContentStrategy.tabClosed();
+        }
+
+        mCurrentTab = index;
+        final TabHost.TabSpec spec = mTabSpecs.get(index);
+
+        // Call the tab widget's focusCurrentTab(), instead of just
+        // selecting the tab.
+        mTabWidget.focusCurrentTab(mCurrentTab);
+        
+        // tab content
+        mCurrentView = spec.mContentStrategy.getContentView();
+
+        if (mCurrentView.getParent() == null) {
+            mTabContent
+                    .addView(
+                            mCurrentView,
+                            new ViewGroup.LayoutParams(
+                                    ViewGroup.LayoutParams.FILL_PARENT,
+                                    ViewGroup.LayoutParams.FILL_PARENT));
+        }
+
+        if (!mTabWidget.hasFocus()) {
+            // if the tab widget didn't take focus (likely because we're in touch mode)
+            // give the current tab content view a shot
+            mCurrentView.requestFocus();
+        }
+
+        //mTabContent.requestFocus(View.FOCUS_FORWARD);
+        invokeOnTabChangeListener();
+    }
+
+    /**
+     * Register a callback to be invoked when the selected state of any of the items
+     * in this list changes
+     * @param l
+     * The callback that will run
+     */
+    public void setOnTabChangedListener(OnTabChangeListener l) {
+        mOnTabChangeListener = l;
+    }
+
+    private void invokeOnTabChangeListener() {
+        if (mOnTabChangeListener != null) {
+            mOnTabChangeListener.onTabChanged(getCurrentTabTag());
+        }
+    }
+
+    /**
+     * Interface definition for a callback to be invoked when tab changed
+     */
+    public interface OnTabChangeListener {
+        void onTabChanged(String tabId);
+    }
+
+
+    /**
+     * Makes the content of a tab when it is selected. Use this if your tab
+     * content needs to be created on demand, i.e. you are not showing an
+     * existing view or starting an activity.
+     */
+    public interface TabContentFactory {
+        /**
+         * Callback to make the tab contents
+         * 
+         * @param tag
+         *            Which tab was selected.
+         * @return The view to distplay the contents of the selected tab.
+         */
+        View createTabContent(String tag);
+    }
+
+
+    /**
+     * A tab has a tab indictor, content, and a tag that is used to keep
+     * track of it.  This builder helps choose among these options.
+     *
+     * For the tab indicator, your choices are:
+     * 1) set a label
+     * 2) set a label and an icon
+     *
+     * For the tab content, your choices are:
+     * 1) the id of a {@link View}
+     * 2) a {@link TabContentFactory} that creates the {@link View} content.
+     * 3) an {@link Intent} that launches an {@link android.app.Activity}.
+     */
+    public class TabSpec {
+
+        private String mTag;
+
+        private IndicatorStrategy mIndicatorStrategy;
+        private ContentStrategy mContentStrategy;
+
+        private TabSpec(String tag) {
+            mTag = tag;
+        }
+
+        /**
+         * Specify a label as the tab indicator.
+         */
+        public TabSpec setIndicator(CharSequence label) {
+            mIndicatorStrategy = new LabelIndicatorStrategy(label);
+            return this;
+        }
+
+        /**
+         * Specify a label and icon as the tab indicator.
+         */
+        public TabSpec setIndicator(CharSequence label, Drawable icon) {
+            mIndicatorStrategy = new LabelAndIconIndicatorStrategy(label, icon);
+            return this;
+        }
+
+        /**
+         * Specify the id of the view that should be used as the content
+         * of the tab.
+         */
+        public TabSpec setContent(int viewId) {
+            mContentStrategy = new ViewIdContentStrategy(viewId);
+            return this;
+        }
+
+        /**
+         * Specify a {@link android.widget.TabHost.TabContentFactory} to use to
+         * create the content of the tab.
+         */
+        public TabSpec setContent(TabContentFactory contentFactory) {
+            mContentStrategy = new FactoryContentStrategy(mTag, contentFactory);
+            return this;
+        }
+
+        /**
+         * Specify an intent to use to launch an activity as the tab content.
+         */
+        public TabSpec setContent(Intent intent) {
+            mContentStrategy = new IntentContentStrategy(mTag, intent);
+            return this;
+        }
+
+
+        String getTag() {
+            return mTag;
+        }
+    }
+
+    /**
+     * Specifies what you do to create a tab indicator.
+     */
+    private static interface IndicatorStrategy {
+
+        /**
+         * Return the view for the indicator.
+         */
+        View createIndicatorView();
+    }
+
+    /**
+     * Specifies what you do to manage the tab content.
+     */
+    private static interface ContentStrategy {
+
+        /**
+         * Return the content view.  The view should may be cached locally.
+         */
+        View getContentView();
+
+        /**
+         * Perhaps do something when the tab associated with this content has
+         * been closed (i.e make it invisible, or remove it).
+         */
+        void tabClosed();
+    }
+
+    /**
+     * How to create a tab indicator that just has a label.
+     */
+    private class LabelIndicatorStrategy implements IndicatorStrategy {
+
+        private final CharSequence mLabel;
+
+        private LabelIndicatorStrategy(CharSequence label) {
+            mLabel = label;
+        }
+
+        public View createIndicatorView() {
+            LayoutInflater inflater =
+                    (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+            View tabIndicator = inflater.inflate(R.layout.tab_indicator,
+                    mTabWidget, // tab widget is the parent
+                    false); // no inflate params
+
+            final TextView tv = (TextView) tabIndicator.findViewById(R.id.title);
+            tv.setText(mLabel);
+
+            return tabIndicator;
+        }
+    }
+
+    /**
+     * How we create a tab indicator that has a label and an icon
+     */
+    private class LabelAndIconIndicatorStrategy implements IndicatorStrategy {
+
+        private final CharSequence mLabel;
+        private final Drawable mIcon;
+
+        private LabelAndIconIndicatorStrategy(CharSequence label, Drawable icon) {
+            mLabel = label;
+            mIcon = icon;
+        }
+
+        public View createIndicatorView() {
+            LayoutInflater inflater =
+                    (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+            View tabIndicator = inflater.inflate(R.layout.tab_indicator,
+                    mTabWidget, // tab widget is the parent
+                    false); // no inflate params
+
+            final TextView tv = (TextView) tabIndicator.findViewById(R.id.title);
+            tv.setText(mLabel);
+
+            final ImageView iconView = (ImageView) tabIndicator.findViewById(R.id.icon);
+            iconView.setImageDrawable(mIcon);
+
+            return tabIndicator;
+        }
+    }
+
+    /**
+     * How to create the tab content via a view id.
+     */
+    private class ViewIdContentStrategy implements ContentStrategy {
+
+        private final View mView;
+
+        private ViewIdContentStrategy(int viewId) {
+            mView = mTabContent.findViewById(viewId);
+            if (mView != null) {
+                mView.setVisibility(View.GONE);
+            } else {
+                throw new RuntimeException("Could not create tab content because " +
+                        "could not find view with id " + viewId);
+            }
+        }
+
+        public View getContentView() {
+            mView.setVisibility(View.VISIBLE);
+            return mView;
+        }
+
+        public void tabClosed() {
+            mView.setVisibility(View.GONE);
+        }
+    }
+
+    /**
+     * How tab content is managed using {@link TabContentFactory}.
+     */
+    private class FactoryContentStrategy implements ContentStrategy {
+        private View mTabContent;
+        private final CharSequence mTag;
+        private TabContentFactory mFactory;
+
+        public FactoryContentStrategy(CharSequence tag, TabContentFactory factory) {
+            mTag = tag;
+            mFactory = factory;
+        }
+
+        public View getContentView() {
+            if (mTabContent == null) {
+                mTabContent = mFactory.createTabContent(mTag.toString());
+            }
+            mTabContent.setVisibility(View.VISIBLE);
+            return mTabContent;
+        }
+
+        public void tabClosed() {
+            mTabContent.setVisibility(View.INVISIBLE);
+        }
+    }
+
+    /**
+     * How tab content is managed via an {@link Intent}: the content view is the
+     * decorview of the launched activity.
+     */
+    private class IntentContentStrategy implements ContentStrategy {
+
+        private final String mTag;
+        private final Intent mIntent;
+
+        private View mLaunchedView;
+
+        private IntentContentStrategy(String tag, Intent intent) {
+            mTag = tag;
+            mIntent = intent;
+        }
+
+        public View getContentView() {
+            if (mLocalActivityManager == null) {
+                throw new IllegalStateException("Did you forget to call 'public void setup(LocalActivityManager activityGroup)'?");
+            }
+            final Window w = mLocalActivityManager.startActivity(
+                    mTag, mIntent);
+            final View wd = w != null ? w.getDecorView() : null;
+            if (mLaunchedView != wd && mLaunchedView != null) {
+                if (mLaunchedView.getParent() != null) {
+                    mTabContent.removeView(mLaunchedView);
+                }
+            }
+            mLaunchedView = wd;
+            
+            // XXX Set FOCUS_AFTER_DESCENDANTS on embedded activies for now so they can get
+            // focus if none of their children have it. They need focus to be able to
+            // display menu items.
+            //
+            // Replace this with something better when Bug 628886 is fixed...
+            //
+            if (mLaunchedView != null) {
+                mLaunchedView.setVisibility(View.VISIBLE);
+                mLaunchedView.setFocusableInTouchMode(true);
+                ((ViewGroup) mLaunchedView).setDescendantFocusability(
+                        FOCUS_AFTER_DESCENDANTS);
+            }
+            return mLaunchedView;
+        }
+
+        public void tabClosed() {
+            if (mLaunchedView != null) {
+                mLaunchedView.setVisibility(View.GONE);
+            }
+        }
+    }
+
+}
diff --git a/core/java/android/widget/TabWidget.java b/core/java/android/widget/TabWidget.java
new file mode 100644
index 0000000..20cddcb
--- /dev/null
+++ b/core/java/android/widget/TabWidget.java
@@ -0,0 +1,289 @@
+/*
+ * 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 android.widget;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Canvas;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.View.OnFocusChangeListener;
+
+
+
+/**
+ * 
+ * Displays a list of tab labels representing each page in the parent's tab
+ * collection. The container object for this widget is
+ * {@link android.widget.TabHost TabHost}. When the user selects a tab, this
+ * object sends a message to the parent container, TabHost, to tell it to switch
+ * the displayed page. You typically won't use many methods directly on this
+ * object. The container TabHost is used to add labels, add the callback
+ * handler, and manage callbacks. You might call this object to iterate the list
+ * of tabs, or to tweak the layout of the tab list, but most methods should be
+ * called on the containing TabHost object.
+ */
+public class TabWidget extends LinearLayout implements OnFocusChangeListener {
+
+
+    private OnTabSelectionChanged mSelectionChangedListener;
+    private int mSelectedTab = 0;
+    private Drawable mBottomLeftStrip;
+    private Drawable mBottomRightStrip;
+    private boolean mStripMoved;
+
+    public TabWidget(Context context) {
+        this(context, null);
+    }
+
+    public TabWidget(Context context, AttributeSet attrs) {
+        this(context, attrs, com.android.internal.R.attr.tabWidgetStyle);
+    }
+
+    public TabWidget(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs);
+        initTabWidget();
+
+        TypedArray a = 
+            context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.TabWidget,
+                    defStyle, 0);
+
+        a.recycle();
+    }
+    
+    @Override
+    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
+        mStripMoved = true;
+        super.onSizeChanged(w, h, oldw, oldh);
+    }
+
+    private void initTabWidget() {
+        setOrientation(LinearLayout.HORIZONTAL);
+        mBottomLeftStrip = mContext.getResources().getDrawable(
+                com.android.internal.R.drawable.tab_bottom_left);
+        mBottomRightStrip = mContext.getResources().getDrawable(
+                com.android.internal.R.drawable.tab_bottom_right);
+        // Deal with focus, as we don't want the focus to go by default
+        // to a tab other than the current tab
+        setFocusable(true);
+        setOnFocusChangeListener(this);
+    }
+
+    @Override
+    public void childDrawableStateChanged(View child) {
+        if (child == getChildAt(mSelectedTab)) {
+            // To make sure that the bottom strip is redrawn
+            invalidate();
+        }
+        super.childDrawableStateChanged(child);
+    }
+    
+    @Override
+    public void dispatchDraw(Canvas canvas) {
+        super.dispatchDraw(canvas);
+
+        View selectedChild = getChildAt(mSelectedTab);
+        
+        mBottomLeftStrip.setState(selectedChild.getDrawableState());
+        mBottomRightStrip.setState(selectedChild.getDrawableState());
+        
+        if (mStripMoved) {
+            Rect selBounds = new Rect(); // Bounds of the selected tab indicator
+            selBounds.left = selectedChild.getLeft();
+            selBounds.right = selectedChild.getRight();
+            final int myHeight = getHeight();
+            mBottomLeftStrip.setBounds(
+                    Math.min(0, selBounds.left 
+                                 - mBottomLeftStrip.getIntrinsicWidth()),
+                    myHeight - mBottomLeftStrip.getIntrinsicHeight(),
+                    selBounds.left,
+                    getHeight());
+            mBottomRightStrip.setBounds(
+                    selBounds.right,
+                    myHeight - mBottomRightStrip.getIntrinsicHeight(),
+                    Math.max(getWidth(), 
+                            selBounds.right + mBottomRightStrip.getIntrinsicWidth()),
+                    myHeight);
+            mStripMoved = false;
+        }
+        
+        mBottomLeftStrip.draw(canvas);
+        mBottomRightStrip.draw(canvas);
+    }
+
+    /**
+     * Sets the current tab.
+     * This method is used to bring a tab to the front of the Widget,
+     * and is used to post to the rest of the UI that a different tab
+     * has been brought to the foreground.
+     * 
+     * Note, this is separate from the traditional "focus" that is 
+     * employed from the view logic.  
+     * 
+     * For instance, if we have a list in a tabbed view, a user may be 
+     * navigating up and down the list, moving the UI focus (orange 
+     * highlighting) through the list items.  The cursor movement does 
+     * not effect the "selected" tab though, because what is being 
+     * scrolled through is all on the same tab.  The selected tab only 
+     * changes when we navigate between tabs (moving from the list view 
+     * to the next tabbed view, in this example).
+     * 
+     * To move both the focus AND the selected tab at once, please use
+     * {@link #setCurrentTab}. Normally, the view logic takes care of 
+     * adjusting the focus, so unless you're circumventing the UI, 
+     * you'll probably just focus your interest here.
+     * 
+     *  @param index The tab that you want to indicate as the selected
+     *  tab (tab brought to the front of the widget)
+     *  
+     *  @see #focusCurrentTab
+     */
+    public void setCurrentTab(int index) {
+        if (index < 0 || index >= getChildCount()) {
+            return;
+        }
+
+        getChildAt(mSelectedTab).setSelected(false);
+        mSelectedTab = index;
+        getChildAt(mSelectedTab).setSelected(true);
+        mStripMoved = true;
+    }
+    
+    /**
+     * Sets the current tab and focuses the UI on it.
+     * This method makes sure that the focused tab matches the selected 
+     * tab, normally at {@link #setCurrentTab}.  Normally this would not 
+     * be an issue if we go through the UI, since the UI is responsible 
+     * for calling TabWidget.onFocusChanged(), but in the case where we 
+     * are selecting the tab programmatically, we'll need to make sure 
+     * focus keeps up.
+     * 
+     *  @param index The tab that you want focused (highlighted in orange) 
+     *  and selected (tab brought to the front of the widget)
+     *  
+     *  @see #setCurrentTab
+     */
+    public void focusCurrentTab(int index) {
+        final int oldTab = mSelectedTab;
+
+        // set the tab
+        setCurrentTab(index);
+        
+        // change the focus if applicable.
+        if (oldTab != index) {
+            getChildAt(index).requestFocus();
+        }
+    }
+    
+    @Override
+    public void setEnabled(boolean enabled) {
+        super.setEnabled(enabled);
+        int count = getChildCount();
+        
+        for (int i=0; i<count; i++) {
+            View child = getChildAt(i);
+            child.setEnabled(enabled);
+        }
+    }
+
+    @Override
+    public void addView(View child) {
+        if (child.getLayoutParams() == null) {
+            final LinearLayout.LayoutParams lp = new LayoutParams(
+                    0,
+                    ViewGroup.LayoutParams.WRAP_CONTENT, 1);
+            lp.setMargins(0, 0, 0, 0);
+            child.setLayoutParams(lp);
+        }
+
+        // Ensure you can navigate to the tab with the keyboard, and you can touch it
+        child.setFocusable(true);
+        child.setClickable(true);
+
+        super.addView(child);
+
+        // TODO: detect this via geometry with a tabwidget listener rather
+        // than potentially interfere with the view's listener
+        child.setOnClickListener(new TabClickListener(getChildCount() - 1));
+        child.setOnFocusChangeListener(this);
+    }
+
+
+
+
+    /**
+     * Provides a way for {@link TabHost} to be notified that the user clicked on a tab indicator.
+     */
+    void setTabSelectionListener(OnTabSelectionChanged listener) {
+        mSelectionChangedListener = listener;
+    }
+
+    public void onFocusChange(View v, boolean hasFocus) {
+        if (v == this && hasFocus) {
+            getChildAt(mSelectedTab).requestFocus();
+            return;
+        }
+        
+        if (hasFocus) {
+            int i = 0;
+            while (i < getChildCount()) {
+                if (getChildAt(i) == v) {
+                    setCurrentTab(i);
+                    mSelectionChangedListener.onTabSelectionChanged(i, false);
+                    break;
+                }
+                i++;
+            }
+        }
+    }
+
+    // registered with each tab indicator so we can notify tab host
+    private class TabClickListener implements OnClickListener {
+
+        private final int mTabIndex;
+
+        private TabClickListener(int tabIndex) {
+            mTabIndex = tabIndex;
+        }
+
+        public void onClick(View v) {
+            mSelectionChangedListener.onTabSelectionChanged(mTabIndex, true);
+        }
+    }
+
+    /**
+     * Let {@link TabHost} know that the user clicked on a tab indicator.
+     */
+    static interface OnTabSelectionChanged {
+        /**
+         * Informs the TabHost which tab was selected. It also indicates
+         * if the tab was clicked/pressed or just focused into.
+         *  
+         * @param tabIndex index of the tab that was selected
+         * @param clicked whether the selection changed due to a touch/click
+         * or due to focus entering the tab through navigation. Pass true
+         * if it was due to a press/click and false otherwise.
+         */
+        void onTabSelectionChanged(int tabIndex, boolean clicked);
+    }
+
+}
+
diff --git a/core/java/android/widget/TableLayout.java b/core/java/android/widget/TableLayout.java
new file mode 100644
index 0000000..afa2f3b
--- /dev/null
+++ b/core/java/android/widget/TableLayout.java
@@ -0,0 +1,751 @@
+/*
+ * Copyright (C) 2007 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 android.widget;
+
+import com.android.internal.R;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.util.AttributeSet;
+import android.util.SparseBooleanArray;
+import android.view.View;
+import android.view.ViewGroup;
+
+import java.util.regex.Pattern;
+
+/**
+ * <p>A layout that arranges its children into rows and columns.
+ * A TableLayout consists of a number of {@link android.widget.TableRow} objects,
+ * each defining a row (actually, you can have other children, which will be
+ * explained below). TableLayout containers do not display border lines for
+ * their rows, columns, or cells. Each row has zero or more cells; each cell can
+ * hold one {@link android.view.View View} object. The table has as many columns
+ * as the row with the most cells. A table can leave cells empty. Cells can span
+ * columns, as they can in HTML.</p>
+ *
+ * <p>The width of a column is defined by the row with the widest cell in that
+ * column. However, a TableLayout can specify certain columns as shrinkable or
+ * stretchable by calling
+ * {@link #setColumnShrinkable(int, boolean) setColumnShrinkable()}
+ * or {@link #setColumnStretchable(int, boolean) setColumnStretchable()}. If
+ * marked as shrinkable, the column width can be shrunk to fit the table into
+ * its parent object. If marked as stretchable, it can expand in width to fit
+ * any extra space. The total width of the table is defined by its parent
+ * container. It is important to remember that a column can be both shrinkable
+ * and stretchable. In such a situation, the column will change its size to
+ * always use up the available space, but never more. Finally, you can hide a
+ * column by calling
+ * {@link #setColumnCollapsed(int,boolean) setColumnCollapsed()}.</p>
+ *
+ * <p>The children of a TableLayout cannot specify the <code>layout_width</code>
+ * attribute. Width is always <code>FILL_PARENT</code>. However, the
+ * <code>layout_height</code> attribute can be defined by a child; default value
+ * is {@link android.widget.TableLayout.LayoutParams#WRAP_CONTENT}. If the child
+ * is a {@link android.widget.TableRow}, then the height is always
+ * {@link android.widget.TableLayout.LayoutParams#WRAP_CONTENT}.</p>
+ *
+ * <p> Cells must be added to a row in increasing column order, both in code and
+ * XML. Column numbers are zero-based. If you don't specify a column number for
+ * a child cell, it will autoincrement to the next available column. If you skip
+ * a column number, it will be considered an empty cell in that row. See the
+ * TableLayout examples in ApiDemos for examples of creating tables in XML.</p>
+ *
+ * <p>Although the typical child of a TableLayout is a TableRow, you can
+ * actually use any View subclass as a direct child of TableLayout. The View
+ * will be displayed as a single row that spans all the table columns.</p>
+ */
+public class TableLayout extends LinearLayout {
+    private int[] mMaxWidths;
+    private SparseBooleanArray mStretchableColumns;
+    private SparseBooleanArray mShrinkableColumns;
+    private SparseBooleanArray mCollapsedColumns;
+
+    private boolean mShrinkAllColumns;
+    private boolean mStretchAllColumns;
+
+    private TableLayout.PassThroughHierarchyChangeListener mPassThroughListener;
+
+    private boolean mInitialized;
+
+    /**
+     * <p>Creates a new TableLayout for the given context.</p>
+     *
+     * @param context the application environment
+     */
+    public TableLayout(Context context) {
+        super(context);
+        initTableLayout();
+    }
+
+    /**
+     * <p>Creates a new TableLayout for the given context and with the
+     * specified set attributes.</p>
+     *
+     * @param context the application environment
+     * @param attrs a collection of attributes
+     */
+    public TableLayout(Context context, AttributeSet attrs) {
+        super(context, attrs);
+
+        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.TableLayout);
+
+        String stretchedColumns = a.getString(R.styleable.TableLayout_stretchColumns);
+        if (stretchedColumns != null) {
+            if (stretchedColumns.charAt(0) == '*') {
+                mStretchAllColumns = true;
+            } else {
+                mStretchableColumns = parseColumns(stretchedColumns);
+            }
+        }
+
+        String shrinkedColumns = a.getString(R.styleable.TableLayout_shrinkColumns);
+        if (shrinkedColumns != null) {
+            if (shrinkedColumns.charAt(0) == '*') {
+                mShrinkAllColumns = true;
+            } else {
+                mShrinkableColumns = parseColumns(shrinkedColumns);
+            }
+        }
+
+        String collapsedColumns = a.getString(R.styleable.TableLayout_collapseColumns);
+        if (collapsedColumns != null) {
+            mCollapsedColumns = parseColumns(collapsedColumns);
+        }
+
+        a.recycle();
+        initTableLayout();
+    }
+
+    /**
+     * <p>Parses a sequence of columns ids defined in a CharSequence with the
+     * following pattern (regex): \d+(\s*,\s*\d+)*</p>
+     *
+     * <p>Examples: "1" or "13, 7, 6" or "".</p>
+     *
+     * <p>The result of the parsing is stored in a sparse boolean array. The
+     * parsed column ids are used as the keys of the sparse array. The values
+     * are always true.</p>
+     *
+     * @param sequence a sequence of column ids, can be empty but not null
+     * @return a sparse array of boolean mapping column indexes to the columns
+     *         collapse state
+     */
+    private static SparseBooleanArray parseColumns(String sequence) {
+        SparseBooleanArray columns = new SparseBooleanArray();
+        Pattern pattern = Pattern.compile("\\s*,\\s*");
+        String[] columnDefs = pattern.split(sequence);
+
+        for (String columnIdentifier : columnDefs) {
+            try {
+                int columnIndex = Integer.parseInt(columnIdentifier);
+                // only valid, i.e. positive, columns indexes are handled
+                if (columnIndex >= 0) {
+                    // putting true in this sparse array indicates that the
+                    // column index was defined in the XML file
+                    columns.put(columnIndex, true);
+                }
+            } catch (NumberFormatException e) {
+                // we just ignore columns that don't exist
+            }
+        }
+
+        return columns;
+    }
+
+    /**
+     * <p>Performs initialization common to prorgrammatic use and XML use of
+     * this widget.</p>
+     */
+    private void initTableLayout() {
+        if (mCollapsedColumns == null) {
+            mCollapsedColumns = new SparseBooleanArray();
+        }
+        if (mStretchableColumns == null) {
+            mStretchableColumns = new SparseBooleanArray();
+        }
+        if (mShrinkableColumns == null) {
+            mShrinkableColumns = new SparseBooleanArray();
+        }
+
+        mPassThroughListener = new PassThroughHierarchyChangeListener();
+        // make sure to call the parent class method to avoid potential
+        // infinite loops
+        super.setOnHierarchyChangeListener(mPassThroughListener);
+
+        mInitialized = true;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void setOnHierarchyChangeListener(
+            OnHierarchyChangeListener listener) {
+        // the user listener is delegated to our pass-through listener
+        mPassThroughListener.mOnHierarchyChangeListener = listener;
+    }
+
+    private void requestRowsLayout() {
+        if (mInitialized) {
+            final int count = getChildCount();
+            for (int i = 0; i < count; i++) {
+                getChildAt(i).requestLayout();
+            }
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void requestLayout() {
+        if (mInitialized) {
+            int count = getChildCount();
+            for (int i = 0; i < count; i++) {
+                getChildAt(i).forceLayout();
+            }
+        }
+
+        super.requestLayout();
+    }
+
+    /**
+     * <p>Indicates whether all columns are shrinkable or not.</p>
+     *
+     * @return true if all columns are shrinkable, false otherwise
+     */
+    public boolean isShrinkAllColumns() {
+        return mShrinkAllColumns;
+    }
+
+    /**
+     * <p>Convenience method to mark all columns as shrinkable.</p>
+     *
+     * @param shrinkAllColumns true to mark all columns shrinkable
+     *
+     * @attr ref android.R.styleable#TableLayout_shrinkColumns
+     */
+    public void setShrinkAllColumns(boolean shrinkAllColumns) {
+        mShrinkAllColumns = shrinkAllColumns;
+    }
+
+    /**
+     * <p>Indicates whether all columns are stretchable or not.</p>
+     *
+     * @return true if all columns are stretchable, false otherwise
+     */
+    public boolean isStretchAllColumns() {
+        return mStretchAllColumns;
+    }
+
+    /**
+     * <p>Convenience method to mark all columns as stretchable.</p>
+     *
+     * @param stretchAllColumns true to mark all columns stretchable
+     *
+     * @attr ref android.R.styleable#TableLayout_stretchColumns
+     */
+    public void setStretchAllColumns(boolean stretchAllColumns) {
+        mStretchAllColumns = stretchAllColumns;
+    }
+
+    /**
+     * <p>Collapses or restores a given column. When collapsed, a column
+     * does not appear on screen and the extra space is reclaimed by the
+     * other columns. A column is collapsed/restored only when it belongs to
+     * a {@link android.widget.TableRow}.</p>
+     *
+     * <p>Calling this method requests a layout operation.</p>
+     *
+     * @param columnIndex the index of the column
+     * @param isCollapsed true if the column must be collapsed, false otherwise
+     *
+     * @attr ref android.R.styleable#TableLayout_collapseColumns
+     */
+    public void setColumnCollapsed(int columnIndex, boolean isCollapsed) {
+        // update the collapse status of the column
+        mCollapsedColumns.put(columnIndex, isCollapsed);
+
+        int count = getChildCount();
+        for (int i = 0; i < count; i++) {
+            final View view = getChildAt(i);
+            if (view instanceof TableRow) {
+                ((TableRow) view).setColumnCollapsed(columnIndex, isCollapsed);
+            }
+        }
+
+        requestRowsLayout();
+    }
+
+    /**
+     * <p>Returns the collapsed state of the specified column.</p>
+     *
+     * @param columnIndex the index of the column
+     * @return true if the column is collapsed, false otherwise
+     */
+    public boolean isColumnCollapsed(int columnIndex) {
+        return mCollapsedColumns.get(columnIndex);
+    }
+
+    /**
+     * <p>Makes the given column stretchable or not. When stretchable, a column
+     * takes up as much as available space as possible in its row.</p>
+     *
+     * <p>Calling this method requests a layout operation.</p>
+     *
+     * @param columnIndex the index of the column
+     * @param isStretchable true if the column must be stretchable,
+     *                      false otherwise. Default is false.
+     *
+     * @attr ref android.R.styleable#TableLayout_stretchColumns
+     */
+    public void setColumnStretchable(int columnIndex, boolean isStretchable) {
+        mStretchableColumns.put(columnIndex, isStretchable);
+        requestRowsLayout();
+    }
+
+    /**
+     * <p>Returns whether the specified column is stretchable or not.</p>
+     *
+     * @param columnIndex the index of the column
+     * @return true if the column is stretchable, false otherwise
+     */
+    public boolean isColumnStretchable(int columnIndex) {
+        return mStretchAllColumns || mStretchableColumns.get(columnIndex);
+    }
+
+    /**
+     * <p>Makes the given column shrinkable or not. When a row is too wide, the
+     * table can reclaim extra space from shrinkable columns.</p>
+     *
+     * <p>Calling this method requests a layout operation.</p>
+     *
+     * @param columnIndex the index of the column
+     * @param isShrinkable true if the column must be shrinkable,
+     *                     false otherwise. Default is false.
+     *
+     * @attr ref android.R.styleable#TableLayout_shrinkColumns
+     */
+    public void setColumnShrinkable(int columnIndex, boolean isShrinkable) {
+        mShrinkableColumns.put(columnIndex, isShrinkable);
+        requestRowsLayout();
+    }
+
+    /**
+     * <p>Returns whether the specified column is shrinkable or not.</p>
+     *
+     * @param columnIndex the index of the column
+     * @return true if the column is shrinkable, false otherwise. Default is false.
+     */
+    public boolean isColumnShrinkable(int columnIndex) {
+        return mShrinkAllColumns || mShrinkableColumns.get(columnIndex);
+    }
+
+    /**
+     * <p>Applies the columns collapse status to a new row added to this
+     * table. This method is invoked by PassThroughHierarchyChangeListener
+     * upon child insertion.</p>
+     *
+     * <p>This method only applies to {@link android.widget.TableRow}
+     * instances.</p>
+     *
+     * @param child the newly added child
+     */
+    private void trackCollapsedColumns(View child) {
+        if (child instanceof TableRow) {
+            final TableRow row = (TableRow) child;
+            final SparseBooleanArray collapsedColumns = mCollapsedColumns;
+            final int count = collapsedColumns.size();
+            for (int i = 0; i < count; i++) {
+                int columnIndex = collapsedColumns.keyAt(i);
+                boolean isCollapsed = collapsedColumns.valueAt(i);
+                // the collapse status is set only when the column should be
+                // collapsed; otherwise, this might affect the default
+                // visibility of the row's children
+                if (isCollapsed) {
+                    row.setColumnCollapsed(columnIndex, isCollapsed);
+                }
+            }
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void addView(View child) {
+        super.addView(child);
+        requestRowsLayout();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void addView(View child, int index) {
+        super.addView(child, index);
+        requestRowsLayout();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void addView(View child, ViewGroup.LayoutParams params) {
+        super.addView(child, params);
+        requestRowsLayout();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void addView(View child, int index, ViewGroup.LayoutParams params) {
+        super.addView(child, index, params);
+        requestRowsLayout();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        // enforce vertical layout
+        measureVertical(widthMeasureSpec, heightMeasureSpec);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    protected void onLayout(boolean changed, int l, int t, int r, int b) {
+        // enforce vertical layout
+        layoutVertical();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    void measureChildBeforeLayout(View child, int childIndex,
+            int widthMeasureSpec, int totalWidth,
+            int heightMeasureSpec, int totalHeight) {
+        // when the measured child is a table row, we force the width of its
+        // children with the widths computed in findLargestCells()
+        if (child instanceof TableRow) {
+            ((TableRow) child).setColumnsWidthConstraints(mMaxWidths);
+        }
+
+        super.measureChildBeforeLayout(child, childIndex,
+                widthMeasureSpec, totalWidth, heightMeasureSpec, totalHeight);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    void measureVertical(int widthMeasureSpec, int heightMeasureSpec) {
+        findLargestCells(widthMeasureSpec);
+        shrinkAndStretchColumns(widthMeasureSpec);
+
+        super.measureVertical(widthMeasureSpec, heightMeasureSpec);
+    }
+
+    /**
+     * <p>Finds the largest cell in each column. For each column, the width of
+     * the largest cell is applied to all the other cells.</p>
+     *
+     * @param widthMeasureSpec the measure constraint imposed by our parent
+     */
+    private void findLargestCells(int widthMeasureSpec) {
+        boolean firstRow = true;
+
+        // find the maximum width for each column
+        // the total number of columns is dynamically changed if we find
+        // wider rows as we go through the children
+        // the array is reused for each layout operation; the array can grow
+        // but never shrinks. Unused extra cells in the array are just ignored
+        // this behavior avoids to unnecessary grow the array after the first
+        // layout operation
+        final int count = getChildCount();
+        for (int i = 0; i < count; i++) {
+            final View child = getChildAt(i);
+            if (child.getVisibility() == GONE) {
+                continue;
+            }
+
+            if (child instanceof TableRow) {
+                final TableRow row = (TableRow) child;
+                // forces the row's height
+                final ViewGroup.LayoutParams layoutParams = row.getLayoutParams();
+                layoutParams.height = LayoutParams.WRAP_CONTENT;
+
+                final int[] widths = row.getColumnsWidths(widthMeasureSpec);
+                final int newLength = widths.length;
+                // this is the first row, we just need to copy the values
+                if (firstRow) {
+                    if (mMaxWidths == null || mMaxWidths.length != newLength) {
+                        mMaxWidths = new int[newLength];
+                    }
+                    System.arraycopy(widths, 0, mMaxWidths, 0, newLength);
+                    firstRow = false;
+                } else {
+                    int length = mMaxWidths.length;
+                    final int difference = newLength - length;
+                    // the current row is wider than the previous rows, so
+                    // we just grow the array and copy the values
+                    if (difference > 0) {
+                        final int[] oldMaxWidths = mMaxWidths;
+                        mMaxWidths = new int[newLength];
+                        System.arraycopy(oldMaxWidths, 0, mMaxWidths, 0,
+                                oldMaxWidths.length);
+                        System.arraycopy(widths, oldMaxWidths.length,
+                                mMaxWidths, oldMaxWidths.length, difference);
+                    }
+
+                    // the row is narrower or of the same width as the previous
+                    // rows, so we find the maximum width for each column
+                    // if the row is narrower than the previous ones,
+                    // difference will be negative
+                    final int[] maxWidths = mMaxWidths;
+                    length = Math.min(length, newLength);
+                    for (int j = 0; j < length; j++) {
+                        maxWidths[j] = Math.max(maxWidths[j], widths[j]);
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * <p>Shrinks the columns if their total width is greater than the
+     * width allocated by widthMeasureSpec. When the total width is less
+     * than the allocated width, this method attempts to stretch columns
+     * to fill the remaining space.</p>
+     *
+     * @param widthMeasureSpec the width measure specification as indicated
+     *                         by this widget's parent
+     */
+    private void shrinkAndStretchColumns(int widthMeasureSpec) {
+        // when we have no row, mMaxWidths is not initialized and the loop
+        // below could cause a NPE
+        if (mMaxWidths == null) {
+            return;
+        }
+
+        // should we honor AT_MOST, EXACTLY and UNSPECIFIED?
+        int totalWidth = 0;
+        for (int width : mMaxWidths) {
+            totalWidth += width;
+        }
+
+        int size = MeasureSpec.getSize(widthMeasureSpec) - mPaddingLeft - mPaddingRight;
+
+        if ((totalWidth > size) && (mShrinkAllColumns || mShrinkableColumns.size() > 0)) {
+            // oops, the largest columns are wider than the row itself
+            // fairly redistribute the row's widh among the columns
+            mutateColumnsWidth(mShrinkableColumns, mShrinkAllColumns, size, totalWidth);
+        } else if ((totalWidth < size) && (mStretchAllColumns || mStretchableColumns.size() > 0)) {
+            // if we have some space left, we distribute it among the
+            // expandable columns
+            mutateColumnsWidth(mStretchableColumns, mStretchAllColumns, size, totalWidth);
+        }
+    }
+
+    private void mutateColumnsWidth(SparseBooleanArray columns,
+            boolean allColumns, int size, int totalWidth) {
+        int skipped = 0;
+        final int[] maxWidths = mMaxWidths;
+        final int length = maxWidths.length;
+        final int count = allColumns ? length : columns.size();
+        final int totalExtraSpace = size - totalWidth;
+        int extraSpace = totalExtraSpace / count;
+
+        if (!allColumns) {
+            for (int i = 0; i < count; i++) {
+                int column = columns.keyAt(i);
+                if (columns.valueAt(i)) {
+                    if (column < length) {
+                        maxWidths[column] += extraSpace;
+                    } else {
+                        skipped++;
+                    }
+                }
+            }
+        } else {
+            for (int i = 0; i < count; i++) {
+                maxWidths[i] += extraSpace;
+            }
+
+            // we don't skip any column so we can return right away
+            return;
+        }
+
+        if (skipped > 0 && skipped < count) {
+            // reclaim any extra space we left to columns that don't exist
+            extraSpace = skipped * extraSpace / (count - skipped);
+            for (int i = 0; i < count; i++) {
+                int column = columns.keyAt(i);
+                if (columns.valueAt(i) && column < length) {
+                    if (extraSpace > maxWidths[column]) {
+                        maxWidths[column] = 0;
+                    } else {
+                        maxWidths[column] += extraSpace;
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public LayoutParams generateLayoutParams(AttributeSet attrs) {
+        return new TableLayout.LayoutParams(getContext(), attrs);
+    }
+
+    /**
+     * Returns a set of layout parameters with a width of
+     * {@link android.view.ViewGroup.LayoutParams#FILL_PARENT},
+     * and a height of {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT}.
+     */
+    @Override
+    protected LinearLayout.LayoutParams generateDefaultLayoutParams() {
+        return new LayoutParams();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
+        return p instanceof TableLayout.LayoutParams;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    protected LinearLayout.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
+        return new LayoutParams(p);
+    }
+
+    /**
+     * <p>This set of layout parameters enforces the width of each child to be
+     * {@link #FILL_PARENT} and the height of each child to be
+     * {@link #WRAP_CONTENT}, but only if the height is not specified.</p>
+     */
+    @SuppressWarnings({"UnusedDeclaration"})
+    public static class LayoutParams extends LinearLayout.LayoutParams {
+        /**
+         * {@inheritDoc}
+         */
+        public LayoutParams(Context c, AttributeSet attrs) {
+            super(c, attrs);
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        public LayoutParams(int w, int h) {
+            super(FILL_PARENT, h);
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        public LayoutParams(int w, int h, float initWeight) {
+            super(FILL_PARENT, h, initWeight);
+        }
+
+        /**
+         * <p>Sets the child width to
+         * {@link android.view.ViewGroup.LayoutParams} and the child height to
+         * {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT}.</p>
+         */
+        public LayoutParams() {
+            super(FILL_PARENT, WRAP_CONTENT);
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        public LayoutParams(ViewGroup.LayoutParams p) {
+            super(p);
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        public LayoutParams(MarginLayoutParams source) {
+            super(source);
+        }
+
+        /**
+         * <p>Fixes the row's width to
+         * {@link android.view.ViewGroup.LayoutParams#FILL_PARENT}; the row's
+         * height is fixed to
+         * {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT} if no layout
+         * height is specified.</p>
+         *
+         * @param a the styled attributes set
+         * @param widthAttr the width attribute to fetch
+         * @param heightAttr the height attribute to fetch
+         */
+        @Override
+        protected void setBaseAttributes(TypedArray a,
+                int widthAttr, int heightAttr) {
+            this.width = FILL_PARENT;
+            if (a.hasValue(heightAttr)) {
+                this.height = a.getLayoutDimension(heightAttr, "layout_height");
+            } else {
+                this.height = WRAP_CONTENT;
+            }
+        }
+    }
+
+    /**
+     * <p>A pass-through listener acts upon the events and dispatches them
+     * to another listener. This allows the table layout to set its own internal
+     * hierarchy change listener without preventing the user to setup his.</p>
+     */
+    private class PassThroughHierarchyChangeListener implements
+            OnHierarchyChangeListener {
+        private OnHierarchyChangeListener mOnHierarchyChangeListener;
+
+        /**
+         * {@inheritDoc}
+         */
+        public void onChildViewAdded(View parent, View child) {
+            trackCollapsedColumns(child);
+
+            if (mOnHierarchyChangeListener != null) {
+                mOnHierarchyChangeListener.onChildViewAdded(parent, child);
+            }
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        public void onChildViewRemoved(View parent, View child) {
+            if (mOnHierarchyChangeListener != null) {
+                mOnHierarchyChangeListener.onChildViewRemoved(parent, child);
+            }
+        }
+    }
+}
diff --git a/core/java/android/widget/TableRow.java b/core/java/android/widget/TableRow.java
new file mode 100644
index 0000000..5628cab
--- /dev/null
+++ b/core/java/android/widget/TableRow.java
@@ -0,0 +1,531 @@
+/*
+ * Copyright (C) 2007 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 android.widget;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.util.AttributeSet;
+import android.util.SparseIntArray;
+import android.view.Gravity;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewDebug;
+
+
+/**
+ * <p>A layout that arranges its children horizontally. A TableRow should
+ * always be used as a child of a {@link android.widget.TableLayout}. If a
+ * TableRow's parent is not a TableLayout, the TableRow will behave as
+ * an horizontal {@link android.widget.LinearLayout}.</p>
+ *
+ * <p>The children of a TableRow do not need to specify the
+ * <code>layout_width</code> and <code>layout_height</code> attributes in the
+ * XML file. TableRow always enforces those values to be respectively
+ * {@link android.widget.TableLayout.LayoutParams#FILL_PARENT} and
+ * {@link android.widget.TableLayout.LayoutParams#WRAP_CONTENT}.</p>
+ *
+ * <p>
+ * Also see {@link TableRow.LayoutParams android.widget.TableRow.LayoutParams}
+ * for layout attributes </p>
+ */
+public class TableRow extends LinearLayout {
+    private int mNumColumns = 0;
+    private int[] mColumnWidths;
+    private int[] mConstrainedColumnWidths;
+    private SparseIntArray mColumnToChildIndex;
+
+    private ChildrenTracker mChildrenTracker;
+
+    /**
+     * <p>Creates a new TableRow for the given context.</p>
+     *
+     * @param context the application environment
+     */
+    public TableRow(Context context) {
+        super(context);
+        initTableRow();
+    }
+
+    /**
+     * <p>Creates a new TableRow for the given context and with the
+     * specified set attributes.</p>
+     *
+     * @param context the application environment
+     * @param attrs a collection of attributes
+     */
+    public TableRow(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        initTableRow();
+    }
+
+    private void initTableRow() {
+        OnHierarchyChangeListener oldListener = mOnHierarchyChangeListener;
+        mChildrenTracker = new ChildrenTracker();
+        if (oldListener != null) {
+            mChildrenTracker.setOnHierarchyChangeListener(oldListener);
+        }
+        super.setOnHierarchyChangeListener(mChildrenTracker);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void setOnHierarchyChangeListener(OnHierarchyChangeListener listener) {
+        mChildrenTracker.setOnHierarchyChangeListener(listener);
+    }
+
+    /**
+     * <p>Collapses or restores a given column.</p>
+     *
+     * @param columnIndex the index of the column
+     * @param collapsed true if the column must be collapsed, false otherwise
+     * {@hide}
+     */
+    void setColumnCollapsed(int columnIndex, boolean collapsed) {
+        View child = getVirtualChildAt(columnIndex);
+        if (child != null) {
+            child.setVisibility(collapsed ? GONE : VISIBLE);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        // enforce horizontal layout
+        measureHorizontal(widthMeasureSpec, heightMeasureSpec);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    protected void onLayout(boolean changed, int l, int t, int r, int b) {
+        // enforce horizontal layout
+        layoutHorizontal();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public View getVirtualChildAt(int i) {
+        if (mColumnToChildIndex == null) {
+            mapIndexAndColumns();
+        }
+
+        final int deflectedIndex = mColumnToChildIndex.get(i, -1);
+        if (deflectedIndex != -1) {
+            return getChildAt(deflectedIndex);
+        }
+
+        return null;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public int getVirtualChildCount() {
+        if (mColumnToChildIndex == null) {
+            mapIndexAndColumns();
+        }
+        return mNumColumns;
+    }
+
+    private void mapIndexAndColumns() {
+        if (mColumnToChildIndex == null) {
+            int virtualCount = 0;
+            final int count = getChildCount();
+
+            mColumnToChildIndex = new SparseIntArray();
+            final SparseIntArray columnToChild = mColumnToChildIndex;
+
+            for (int i = 0; i < count; i++) {
+                final View child = getChildAt(i);
+                final LayoutParams layoutParams = (LayoutParams) child.getLayoutParams();
+
+                if (layoutParams.column >= virtualCount) {
+                    virtualCount = layoutParams.column;
+                }
+
+                for (int j = 0; j < layoutParams.span; j++) {
+                    columnToChild.put(virtualCount++, i);
+                }
+            }
+
+            mNumColumns = virtualCount;
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    int measureNullChild(int childIndex) {
+        return mConstrainedColumnWidths[childIndex];
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    void measureChildBeforeLayout(View child, int childIndex,
+            int widthMeasureSpec, int totalWidth,
+            int heightMeasureSpec, int totalHeight) {
+        if (mConstrainedColumnWidths != null) {
+            final LayoutParams lp = (LayoutParams) child.getLayoutParams();
+
+            int measureMode = MeasureSpec.EXACTLY;
+            int columnWidth = 0;
+
+            final int span = lp.span;
+            final int[] constrainedColumnWidths = mConstrainedColumnWidths;
+            for (int i = 0; i < span; i++) {
+                columnWidth += constrainedColumnWidths[childIndex + i];
+            }
+
+            final int gravity = lp.gravity;
+            final boolean isHorizontalGravity = Gravity.isHorizontal(gravity);
+
+            if (isHorizontalGravity) {
+                measureMode = MeasureSpec.AT_MOST;
+            }
+
+            // no need to care about padding here,
+            // ViewGroup.getChildMeasureSpec() would get rid of it anyway
+            // because of the EXACTLY measure spec we use
+            int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(
+                    Math.max(0, columnWidth - lp.leftMargin - lp.rightMargin), measureMode
+            );
+            int childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec,
+                    mPaddingTop + mPaddingBottom + lp.topMargin +
+                    lp .bottomMargin + totalHeight, lp.height);
+
+            child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
+
+            if (isHorizontalGravity) {
+                final int childWidth = child.getMeasuredWidth();
+                lp.mOffset[LayoutParams.LOCATION_NEXT] = columnWidth - childWidth;
+
+                switch (gravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
+                    case Gravity.LEFT:
+                        // don't offset on X axis
+                        break;
+                    case Gravity.RIGHT:
+                        lp.mOffset[LayoutParams.LOCATION] = lp.mOffset[LayoutParams.LOCATION_NEXT];
+                        break;
+                    case Gravity.CENTER_HORIZONTAL:
+                        lp.mOffset[LayoutParams.LOCATION] = lp.mOffset[LayoutParams.LOCATION_NEXT] / 2;
+                        break;
+                }
+            } else {
+                lp.mOffset[LayoutParams.LOCATION] = lp.mOffset[LayoutParams.LOCATION_NEXT] = 0;
+            }
+        } else {
+            // fail silently when column widths are not available
+            super.measureChildBeforeLayout(child, childIndex, widthMeasureSpec,
+                    totalWidth, heightMeasureSpec, totalHeight);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    int getChildrenSkipCount(View child, int index) {
+        LayoutParams layoutParams = (LayoutParams) child.getLayoutParams();
+
+        // when the span is 1 (default), we need to skip 0 child
+        return layoutParams.span - 1;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    int getLocationOffset(View child) {
+        return ((TableRow.LayoutParams) child.getLayoutParams()).mOffset[LayoutParams.LOCATION];
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    int getNextLocationOffset(View child) {
+        return ((TableRow.LayoutParams) child.getLayoutParams()).mOffset[LayoutParams.LOCATION_NEXT];
+    }
+
+    /**
+     * <p>Measures the preferred width of each child, including its margins.</p>
+     *
+     * @param widthMeasureSpec the width constraint imposed by our parent
+     *
+     * @return an array of integers corresponding to the width of each cell, or
+     *         column, in this row
+     * {@hide}
+     */
+    int[] getColumnsWidths(int widthMeasureSpec) {
+        final int numColumns = getVirtualChildCount();
+        if (mColumnWidths == null || numColumns != mColumnWidths.length) {
+            mColumnWidths = new int[numColumns];
+        }
+
+        final int[] columnWidths = mColumnWidths;
+
+        for (int i = 0; i < numColumns; i++) {
+            final View child = getVirtualChildAt(i);
+            if (child != null && child.getVisibility() != GONE) {
+                final LayoutParams layoutParams = (LayoutParams) child.getLayoutParams();
+                if (layoutParams.span == 1) {
+                    int spec;
+                    switch (layoutParams.width) {
+                        case LayoutParams.WRAP_CONTENT:
+                            spec = getChildMeasureSpec(widthMeasureSpec, 0, LayoutParams.WRAP_CONTENT);
+                            break;
+                        case LayoutParams.FILL_PARENT:
+                            spec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
+                            break;
+                        default:
+                            spec = MeasureSpec.makeMeasureSpec(layoutParams.width, MeasureSpec.EXACTLY);
+                    }
+                    child.measure(spec, spec);
+
+                    final int width = child.getMeasuredWidth() + layoutParams.leftMargin +
+                            layoutParams.rightMargin;
+                    columnWidths[i] = width;
+                } else {
+                    columnWidths[i] = 0;
+                }
+            } else {
+                columnWidths[i] = 0;
+            }
+        }
+
+        return columnWidths;
+    }
+
+    /**
+     * <p>Sets the width of all of the columns in this row. At layout time,
+     * this row sets a fixed width, as defined by <code>columnWidths</code>,
+     * on each child (or cell, or column.)</p>
+     *
+     * @param columnWidths the fixed width of each column that this row must
+     *                     honor
+     * @throws IllegalArgumentException when columnWidths' length is smaller
+     *         than the number of children in this row
+     * {@hide}
+     */
+    void setColumnsWidthConstraints(int[] columnWidths) {
+        if (columnWidths == null || columnWidths.length < getVirtualChildCount()) {
+            throw new IllegalArgumentException(
+                    "columnWidths should be >= getVirtualChildCount()");
+        }
+
+        mConstrainedColumnWidths = columnWidths;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public LayoutParams generateLayoutParams(AttributeSet attrs) {
+        return new TableRow.LayoutParams(getContext(), attrs);
+    }
+
+    /**
+     * Returns a set of layout parameters with a width of
+     * {@link android.view.ViewGroup.LayoutParams#FILL_PARENT},
+     * a height of {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT} and no spanning.
+     */
+    @Override
+    protected LinearLayout.LayoutParams generateDefaultLayoutParams() {
+        return new LayoutParams();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
+        return p instanceof TableRow.LayoutParams;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    protected LinearLayout.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
+        return new LayoutParams(p);
+    }
+
+    /**
+     * <p>Set of layout parameters used in table rows.</p>
+     *
+     * @see android.widget.TableLayout.LayoutParams
+     * 
+     * @attr ref android.R.styleable#TableRow_Cell_layout_column
+     * @attr ref android.R.styleable#TableRow_Cell_layout_span
+     */
+    public static class LayoutParams extends LinearLayout.LayoutParams {
+        /**
+         * <p>The column index of the cell represented by the widget.</p>
+         */
+        @ViewDebug.ExportedProperty
+        public int column;
+
+        /**
+         * <p>The number of columns the widgets spans over.</p>
+         */
+        @ViewDebug.ExportedProperty
+        public int span;
+
+        private static final int LOCATION = 0;
+        private static final int LOCATION_NEXT = 1;
+
+        private int[] mOffset = new int[2];
+
+        /**
+         * {@inheritDoc}
+         */
+        public LayoutParams(Context c, AttributeSet attrs) {
+            super(c, attrs);
+
+            TypedArray a =
+                    c.obtainStyledAttributes(attrs,
+                            com.android.internal.R.styleable.TableRow_Cell);
+
+            column = a.getInt(com.android.internal.R.styleable.TableRow_Cell_layout_column, -1);
+            span = a.getInt(com.android.internal.R.styleable.TableRow_Cell_layout_span, 1);
+            if (span <= 1) {
+                span = 1;
+            }
+
+            a.recycle();
+        }
+
+        /**
+         * <p>Sets the child width and the child height.</p>
+         *
+         * @param w the desired width
+         * @param h the desired height
+         */
+        public LayoutParams(int w, int h) {
+            super(w, h);
+            column = -1;
+            span = 1;
+        }
+
+        /**
+         * <p>Sets the child width, height and weight.</p>
+         *
+         * @param w the desired width
+         * @param h the desired height
+         * @param initWeight the desired weight
+         */
+        public LayoutParams(int w, int h, float initWeight) {
+            super(w, h, initWeight);
+            column = -1;
+            span = 1;
+        }
+
+        /**
+         * <p>Sets the child width to {@link android.view.ViewGroup.LayoutParams}
+         * and the child height to
+         * {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT}.</p>
+         */
+        public LayoutParams() {
+            super(FILL_PARENT, WRAP_CONTENT);
+            column = -1;
+            span = 1;
+        }
+
+        /**
+         * <p>Puts the view in the specified column.</p>
+         *
+         * <p>Sets the child width to {@link android.view.ViewGroup.LayoutParams#FILL_PARENT}
+         * and the child height to
+         * {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT}.</p>
+         *
+         * @param column the column index for the view
+         */
+        public LayoutParams(int column) {
+            this();
+            this.column = column;
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        public LayoutParams(ViewGroup.LayoutParams p) {
+            super(p);
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        public LayoutParams(MarginLayoutParams source) {
+            super(source);
+        }
+
+        @Override
+        protected void setBaseAttributes(TypedArray a, int widthAttr, int heightAttr) {
+            // We don't want to force users to specify a layout_width
+            if (a.hasValue(widthAttr)) {
+                width = a.getLayoutDimension(widthAttr, "layout_width");
+            } else {
+                width = FILL_PARENT;
+            }
+
+            // We don't want to force users to specify a layout_height
+            if (a.hasValue(heightAttr)) {
+                height = a.getLayoutDimension(heightAttr, "layout_height");
+            } else {
+                height = WRAP_CONTENT;
+            }
+        }
+    }
+
+    // special transparent hierarchy change listener
+    private class ChildrenTracker implements OnHierarchyChangeListener {
+        private OnHierarchyChangeListener listener;
+
+        private void setOnHierarchyChangeListener(OnHierarchyChangeListener listener) {
+            this.listener = listener;
+        }
+
+        public void onChildViewAdded(View parent, View child) {
+            // dirties the index to column map
+            mColumnToChildIndex = null;
+
+            if (this.listener != null) {
+                this.listener.onChildViewAdded(parent, child);
+            }
+        }
+
+        public void onChildViewRemoved(View parent, View child) {
+            // dirties the index to column map
+            mColumnToChildIndex = null;
+
+            if (this.listener != null) {
+                this.listener.onChildViewRemoved(parent, child);
+            }
+        }
+    }
+}
diff --git a/core/java/android/widget/TextSwitcher.java b/core/java/android/widget/TextSwitcher.java
new file mode 100644
index 0000000..a8794a3
--- /dev/null
+++ b/core/java/android/widget/TextSwitcher.java
@@ -0,0 +1,91 @@
+/*
+ * 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 android.widget;
+
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.ViewGroup;
+
+/**
+ * Specialized {@link android.widget.ViewSwitcher} that contains
+ * only children of type {@link android.widget.TextView}.
+ *
+ * A TextSwitcher is useful to animate a label on screen. Whenever
+ * {@link #setText(CharSequence)} is called, TextSwitcher animates the current text
+ * out and animates the new text in. 
+ */
+public class TextSwitcher extends ViewSwitcher {
+    /**
+     * Creates a new empty TextSwitcher.
+     *
+     * @param context the application's environment
+     */
+    public TextSwitcher(Context context) {
+        super(context);
+    }
+
+    /**
+     * Creates a new empty TextSwitcher for the given context and with the
+     * specified set attributes.
+     *
+     * @param context the application environment
+     * @param attrs a collection of attributes
+     */
+    public TextSwitcher(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @throws IllegalArgumentException if child is not an instance of
+     *         {@link android.widget.TextView}
+     */
+    @Override
+    public void addView(View child, int index, ViewGroup.LayoutParams params) {
+        if (!(child instanceof TextView)) {
+            throw new IllegalArgumentException(
+                    "TextSwitcher children must be instances of TextView");
+        }
+
+        super.addView(child, index, params);
+    }
+
+    /**
+     * Sets the text of the next view and switches to the next view. This can
+     * be used to animate the old text out and animate the next text in.
+     *
+     * @param text the new text to display
+     */
+    public void setText(CharSequence text) {
+        final TextView t = (TextView) getNextView();
+        t.setText(text);
+        showNext();
+    }
+
+    /**
+     * Sets the text of the text view that is currently showing.  This does
+     * not perform the animations.
+     *
+     * @param text the new text to display
+     */
+    public void setCurrentText(CharSequence text) {
+        ((TextView)getCurrentView()).setText(text);
+    }
+}
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
new file mode 100644
index 0000000..080f3de
--- /dev/null
+++ b/core/java/android/widget/TextView.java
@@ -0,0 +1,6787 @@
+/*
+ * 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 android.widget;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.res.ColorStateList;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.content.res.XmlResourceParser;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Path;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.graphics.Typeface;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.SystemClock;
+import android.os.Message;
+import android.text.BoringLayout;
+import android.text.DynamicLayout;
+import android.text.Editable;
+import android.text.GetChars;
+import android.text.GraphicsOperations;
+import android.text.ClipboardManager;
+import android.text.InputFilter;
+import android.text.Layout;
+import android.text.ParcelableSpan;
+import android.text.Selection;
+import android.text.SpanWatcher;
+import android.text.Spannable;
+import android.text.Spanned;
+import android.text.SpannedString;
+import android.text.SpannableString;
+import android.text.StaticLayout;
+import android.text.TextPaint;
+import android.text.TextUtils;
+import android.text.TextWatcher;
+import android.text.method.DateKeyListener;
+import android.text.method.DateTimeKeyListener;
+import android.text.method.DialerKeyListener;
+import android.text.method.DigitsKeyListener;
+import android.text.method.KeyListener;
+import android.text.method.LinkMovementMethod;
+import android.text.method.MetaKeyKeyListener;
+import android.text.method.MovementMethod;
+import android.text.method.TimeKeyListener;
+
+import android.text.method.PasswordTransformationMethod;
+import android.text.method.SingleLineTransformationMethod;
+import android.text.method.TextKeyListener;
+import android.text.method.TransformationMethod;
+import android.text.style.ParagraphStyle;
+import android.text.style.URLSpan;
+import android.text.style.UpdateAppearance;
+import android.text.util.Linkify;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.util.FloatMath;
+import android.util.TypedValue;
+import android.view.ContextMenu;
+import android.view.Gravity;
+import android.view.KeyEvent;
+import android.view.LayoutInflater;
+import android.view.MenuItem;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewDebug;
+import android.view.ViewRoot;
+import android.view.ViewTreeObserver;
+import android.view.ViewGroup.LayoutParams;
+import android.view.animation.AnimationUtils;
+import android.view.inputmethod.BaseInputConnection;
+import android.view.inputmethod.CompletionInfo;
+import android.view.inputmethod.ExtractedText;
+import android.view.inputmethod.ExtractedTextRequest;
+import android.view.inputmethod.InputConnection;
+import android.view.inputmethod.InputMethodManager;
+import android.view.inputmethod.EditorInfo;
+import android.widget.RemoteViews.RemoteView;
+
+import java.io.IOException;
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+
+import com.android.internal.util.FastMath;
+import com.android.internal.widget.EditableInputConnection;
+
+import org.xmlpull.v1.XmlPullParserException;
+
+/**
+ * Displays text to the user and optionally allows them to edit it.  A TextView
+ * is a complete text editor, however the basic class is configured to not
+ * allow editing; see {@link EditText} for a subclass that configures the text
+ * view for editing.
+ *
+ * <p>
+ * <b>XML attributes</b>
+ * <p>
+ * See {@link android.R.styleable#TextView TextView Attributes},
+ * {@link android.R.styleable#View View Attributes}
+ *
+ * @attr ref android.R.styleable#TextView_text
+ * @attr ref android.R.styleable#TextView_bufferType
+ * @attr ref android.R.styleable#TextView_hint
+ * @attr ref android.R.styleable#TextView_textColor
+ * @attr ref android.R.styleable#TextView_textColorHighlight
+ * @attr ref android.R.styleable#TextView_textColorHint
+ * @attr ref android.R.styleable#TextView_textSize
+ * @attr ref android.R.styleable#TextView_textScaleX
+ * @attr ref android.R.styleable#TextView_typeface
+ * @attr ref android.R.styleable#TextView_textStyle
+ * @attr ref android.R.styleable#TextView_cursorVisible
+ * @attr ref android.R.styleable#TextView_maxLines
+ * @attr ref android.R.styleable#TextView_maxHeight
+ * @attr ref android.R.styleable#TextView_lines
+ * @attr ref android.R.styleable#TextView_height
+ * @attr ref android.R.styleable#TextView_minLines
+ * @attr ref android.R.styleable#TextView_minHeight
+ * @attr ref android.R.styleable#TextView_maxEms
+ * @attr ref android.R.styleable#TextView_maxWidth
+ * @attr ref android.R.styleable#TextView_ems
+ * @attr ref android.R.styleable#TextView_width
+ * @attr ref android.R.styleable#TextView_minEms
+ * @attr ref android.R.styleable#TextView_minWidth
+ * @attr ref android.R.styleable#TextView_gravity
+ * @attr ref android.R.styleable#TextView_scrollHorizontally
+ * @attr ref android.R.styleable#TextView_password
+ * @attr ref android.R.styleable#TextView_singleLine
+ * @attr ref android.R.styleable#TextView_selectAllOnFocus
+ * @attr ref android.R.styleable#TextView_includeFontPadding
+ * @attr ref android.R.styleable#TextView_maxLength
+ * @attr ref android.R.styleable#TextView_shadowColor
+ * @attr ref android.R.styleable#TextView_shadowDx
+ * @attr ref android.R.styleable#TextView_shadowDy
+ * @attr ref android.R.styleable#TextView_shadowRadius
+ * @attr ref android.R.styleable#TextView_autoLink
+ * @attr ref android.R.styleable#TextView_linksClickable
+ * @attr ref android.R.styleable#TextView_numeric
+ * @attr ref android.R.styleable#TextView_digits
+ * @attr ref android.R.styleable#TextView_phoneNumber
+ * @attr ref android.R.styleable#TextView_inputMethod
+ * @attr ref android.R.styleable#TextView_capitalize
+ * @attr ref android.R.styleable#TextView_autoText
+ * @attr ref android.R.styleable#TextView_editable
+ * @attr ref android.R.styleable#TextView_drawableTop
+ * @attr ref android.R.styleable#TextView_drawableBottom
+ * @attr ref android.R.styleable#TextView_drawableRight
+ * @attr ref android.R.styleable#TextView_drawableLeft
+ * @attr ref android.R.styleable#TextView_lineSpacingExtra
+ * @attr ref android.R.styleable#TextView_lineSpacingMultiplier
+ * @attr ref android.R.styleable#TextView_marqueeRepeatLimit
+ */
+@RemoteView
+public class TextView extends View implements ViewTreeObserver.OnPreDrawListener {
+    static final String TAG = "TextView";
+    static final boolean DEBUG_EXTRACT = false;
+    
+    private static int PRIORITY = 100;
+
+    private ColorStateList mTextColor;
+    private int mCurTextColor;
+    private ColorStateList mHintTextColor;
+    private ColorStateList mLinkTextColor;
+    private int mCurHintTextColor;
+    private boolean mFreezesText;
+    private boolean mFrozenWithFocus;
+    private boolean mTemporaryDetach;
+
+    private boolean mEatTouchRelease = false;
+    private boolean mScrolled = false;
+
+    private Editable.Factory mEditableFactory = Editable.Factory.getInstance();
+    private Spannable.Factory mSpannableFactory = Spannable.Factory.getInstance();
+
+    private float mShadowRadius, mShadowDx, mShadowDy;
+
+    private static final int PREDRAW_NOT_REGISTERED = 0;
+    private static final int PREDRAW_PENDING = 1;
+    private static final int PREDRAW_DONE = 2;
+    private int mPreDrawState = PREDRAW_NOT_REGISTERED;
+
+    private TextUtils.TruncateAt mEllipsize = null;
+
+    // Enum for the "typeface" XML parameter.
+    // TODO: How can we get this from the XML instead of hardcoding it here?
+    private static final int SANS = 1;
+    private static final int SERIF = 2;
+    private static final int MONOSPACE = 3;
+
+    // Bitfield for the "numeric" XML parameter.
+    // TODO: How can we get this from the XML instead of hardcoding it here?
+    private static final int SIGNED = 2;
+    private static final int DECIMAL = 4;
+
+    class Drawables {
+        final Rect mCompoundRect = new Rect();
+        Drawable mDrawableTop, mDrawableBottom, mDrawableLeft, mDrawableRight;
+        int mDrawableSizeTop, mDrawableSizeBottom, mDrawableSizeLeft, mDrawableSizeRight;
+        int mDrawableWidthTop, mDrawableWidthBottom, mDrawableHeightLeft, mDrawableHeightRight;
+        int mDrawablePadding;
+    }
+    private Drawables mDrawables;
+
+    private CharSequence mError;
+    private boolean mErrorWasChanged;
+    private PopupWindow mPopup;
+    /**
+     * This flag is set if the TextView tries to display an error before it
+     * is attached to the window (so its position is still unknown).
+     * It causes the error to be shown later, when onAttachedToWindow()
+     * is called.
+     */
+    private boolean mShowErrorAfterAttach;
+
+    private CharWrapper mCharWrapper = null;
+
+    private boolean mSelectionMoved = false;
+
+    private Marquee mMarquee;
+    private boolean mRestartMarquee;
+
+    private int mMarqueeRepeatLimit = 3;
+
+    class InputContentType {
+        int imeOptions = EditorInfo.IME_UNDEFINED;
+        String privateImeOptions;
+        CharSequence imeActionLabel;
+        int imeActionId;
+        Bundle extras;
+        OnEditorActionListener onEditorActionListener;
+        boolean enterDown;
+    }
+    InputContentType mInputContentType;
+
+    class InputMethodState {
+        Rect mCursorRectInWindow = new Rect();
+        RectF mTmpRectF = new RectF();
+        float[] mTmpOffset = new float[2];
+        ExtractedTextRequest mExtracting;
+        final ExtractedText mTmpExtracted = new ExtractedText();
+        int mBatchEditNesting;
+        boolean mCursorChanged;
+        boolean mContentChanged;
+        int mChangedStart, mChangedEnd, mChangedDelta;
+    }
+    InputMethodState mInputMethodState;
+
+    /*
+     * Kick-start the font cache for the zygote process (to pay the cost of
+     * initializing freetype for our default font only once).
+     */
+    static {
+        Paint p = new Paint();
+        p.setAntiAlias(true);
+        // We don't care about the result, just the side-effect of measuring.
+        p.measureText("H");
+    }
+
+    /**
+     * Interface definition for a callback to be invoked when an action is
+     * performed on the editor.
+     */
+    public interface OnEditorActionListener {
+        /**
+         * Called when an action is being performed.
+         *
+         * @param v The view that was clicked.
+         * @param actionId Identifier of the action.  This will be either the
+         * identifier you supplied, or {@link EditorInfo#IME_UNDEFINED
+         * EditorInfo.IME_UNDEFINED} if being called due to the enter key
+         * being pressed.
+         * @param event If triggered by an enter key, this is the event;
+         * otherwise, this is null.
+         * @return Return true if you have consumed the action, else false.
+         */
+        boolean onEditorAction(TextView v, int actionId, KeyEvent event);
+    }
+    
+    public TextView(Context context) {
+        this(context, null);
+    }
+
+    public TextView(Context context,
+                    AttributeSet attrs) {
+        this(context, attrs, com.android.internal.R.attr.textViewStyle);
+    }
+
+    public TextView(Context context,
+                    AttributeSet attrs,
+                    int defStyle) {
+        super(context, attrs, defStyle);
+        mText = "";
+
+        mTextPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG);
+        // If we get the paint from the skin, we should set it to left, since
+        // the layout always wants it to be left.
+        // mTextPaint.setTextAlign(Paint.Align.LEFT);
+
+        mHighlightPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+
+        mMovement = getDefaultMovementMethod();
+        mTransformation = null;
+
+        TypedArray a =
+            context.obtainStyledAttributes(
+                attrs, com.android.internal.R.styleable.TextView, defStyle, 0);
+
+        int textColorHighlight = 0;
+        ColorStateList textColor = null;
+        ColorStateList textColorHint = null;
+        ColorStateList textColorLink = null;
+        int textSize = 15;
+        int typefaceIndex = -1;
+        int styleIndex = -1;
+
+        /*
+         * Look the appearance up without checking first if it exists because
+         * almost every TextView has one and it greatly simplifies the logic
+         * to be able to parse the appearance first and then let specific tags
+         * for this View override it.
+         */
+        TypedArray appearance = null;
+        int ap = a.getResourceId(com.android.internal.R.styleable.TextView_textAppearance, -1);
+        if (ap != -1) {
+            appearance = context.obtainStyledAttributes(ap,
+                                com.android.internal.R.styleable.
+                                TextAppearance);
+        }
+        if (appearance != null) {
+            int n = appearance.getIndexCount();
+            for (int i = 0; i < n; i++) {
+                int attr = appearance.getIndex(i);
+
+                switch (attr) {
+                case com.android.internal.R.styleable.TextAppearance_textColorHighlight:
+                    textColorHighlight = appearance.getColor(attr, textColorHighlight);
+                    break;
+
+                case com.android.internal.R.styleable.TextAppearance_textColor:
+                    textColor = appearance.getColorStateList(attr);
+                    break;
+
+                case com.android.internal.R.styleable.TextAppearance_textColorHint:
+                    textColorHint = appearance.getColorStateList(attr);
+                    break;
+
+                case com.android.internal.R.styleable.TextAppearance_textColorLink:
+                    textColorLink = appearance.getColorStateList(attr);
+                    break;
+
+                case com.android.internal.R.styleable.TextAppearance_textSize:
+                    textSize = appearance.getDimensionPixelSize(attr, textSize);
+                    break;
+
+                case com.android.internal.R.styleable.TextAppearance_typeface:
+                    typefaceIndex = appearance.getInt(attr, -1);
+                    break;
+
+                case com.android.internal.R.styleable.TextAppearance_textStyle:
+                    styleIndex = appearance.getInt(attr, -1);
+                    break;
+                }
+            }
+
+            appearance.recycle();
+        }
+
+        boolean editable = getDefaultEditable();
+        CharSequence inputMethod = null;
+        int numeric = 0;
+        CharSequence digits = null;
+        boolean phone = false;
+        boolean autotext = false;
+        int autocap = -1;
+        int buffertype = 0;
+        boolean selectallonfocus = false;
+        Drawable drawableLeft = null, drawableTop = null, drawableRight = null,
+            drawableBottom = null;
+        int drawablePadding = 0;
+        int ellipsize = -1;
+        boolean singleLine = false;
+        int maxlength = -1;
+        CharSequence text = "";
+        int shadowcolor = 0;
+        float dx = 0, dy = 0, r = 0;
+        boolean password = false;
+        int inputType = EditorInfo.TYPE_NULL;
+
+        int n = a.getIndexCount();
+        for (int i = 0; i < n; i++) {
+            int attr = a.getIndex(i);
+
+            switch (attr) {
+            case com.android.internal.R.styleable.TextView_editable:
+                editable = a.getBoolean(attr, editable);
+                break;
+
+            case com.android.internal.R.styleable.TextView_inputMethod:
+                inputMethod = a.getText(attr);
+                break;
+
+            case com.android.internal.R.styleable.TextView_numeric:
+                numeric = a.getInt(attr, numeric);
+                break;
+
+            case com.android.internal.R.styleable.TextView_digits:
+                digits = a.getText(attr);
+                break;
+
+            case com.android.internal.R.styleable.TextView_phoneNumber:
+                phone = a.getBoolean(attr, phone);
+                break;
+
+            case com.android.internal.R.styleable.TextView_autoText:
+                autotext = a.getBoolean(attr, autotext);
+                break;
+
+            case com.android.internal.R.styleable.TextView_capitalize:
+                autocap = a.getInt(attr, autocap);
+                break;
+
+            case com.android.internal.R.styleable.TextView_bufferType:
+                buffertype = a.getInt(attr, buffertype);
+                break;
+
+            case com.android.internal.R.styleable.TextView_selectAllOnFocus:
+                selectallonfocus = a.getBoolean(attr, selectallonfocus);
+                break;
+
+            case com.android.internal.R.styleable.TextView_autoLink:
+                mAutoLinkMask = a.getInt(attr, 0);
+                break;
+
+            case com.android.internal.R.styleable.TextView_linksClickable:
+                mLinksClickable = a.getBoolean(attr, true);
+                break;
+
+            case com.android.internal.R.styleable.TextView_drawableLeft:
+                drawableLeft = a.getDrawable(attr);
+                break;
+
+            case com.android.internal.R.styleable.TextView_drawableTop:
+                drawableTop = a.getDrawable(attr);
+                break;
+
+            case com.android.internal.R.styleable.TextView_drawableRight:
+                drawableRight = a.getDrawable(attr);
+                break;
+
+            case com.android.internal.R.styleable.TextView_drawableBottom:
+                drawableBottom = a.getDrawable(attr);
+                break;
+
+            case com.android.internal.R.styleable.TextView_drawablePadding:
+                drawablePadding = a.getDimensionPixelSize(attr, drawablePadding);
+                break;
+
+            case com.android.internal.R.styleable.TextView_maxLines:
+                setMaxLines(a.getInt(attr, -1));
+                break;
+
+            case com.android.internal.R.styleable.TextView_maxHeight:
+                setMaxHeight(a.getDimensionPixelSize(attr, -1));
+                break;
+
+            case com.android.internal.R.styleable.TextView_lines:
+                setLines(a.getInt(attr, -1));
+                break;
+
+            case com.android.internal.R.styleable.TextView_height:
+                setHeight(a.getDimensionPixelSize(attr, -1));
+                break;
+
+            case com.android.internal.R.styleable.TextView_minLines:
+                setMinLines(a.getInt(attr, -1));
+                break;
+
+            case com.android.internal.R.styleable.TextView_minHeight:
+                setMinHeight(a.getDimensionPixelSize(attr, -1));
+                break;
+
+            case com.android.internal.R.styleable.TextView_maxEms:
+                setMaxEms(a.getInt(attr, -1));
+                break;
+
+            case com.android.internal.R.styleable.TextView_maxWidth:
+                setMaxWidth(a.getDimensionPixelSize(attr, -1));
+                break;
+
+            case com.android.internal.R.styleable.TextView_ems:
+                setEms(a.getInt(attr, -1));
+                break;
+
+            case com.android.internal.R.styleable.TextView_width:
+                setWidth(a.getDimensionPixelSize(attr, -1));
+                break;
+
+            case com.android.internal.R.styleable.TextView_minEms:
+                setMinEms(a.getInt(attr, -1));
+                break;
+
+            case com.android.internal.R.styleable.TextView_minWidth:
+                setMinWidth(a.getDimensionPixelSize(attr, -1));
+                break;
+
+            case com.android.internal.R.styleable.TextView_gravity:
+                setGravity(a.getInt(attr, -1));
+                break;
+
+            case com.android.internal.R.styleable.TextView_hint:
+                setHint(a.getText(attr));
+                break;
+
+            case com.android.internal.R.styleable.TextView_text:
+                text = a.getText(attr);
+                break;
+
+            case com.android.internal.R.styleable.TextView_scrollHorizontally:
+                if (a.getBoolean(attr, false)) {
+                    setHorizontallyScrolling(true);
+                }
+                break;
+
+            case com.android.internal.R.styleable.TextView_singleLine:
+                singleLine = a.getBoolean(attr, singleLine);
+                break;
+
+            case com.android.internal.R.styleable.TextView_ellipsize:
+                ellipsize = a.getInt(attr, ellipsize);
+                break;
+
+            case com.android.internal.R.styleable.TextView_marqueeRepeatLimit:
+                setMarqueeRepeatLimit(a.getInt(attr, mMarqueeRepeatLimit));
+                break;
+
+            case com.android.internal.R.styleable.TextView_includeFontPadding:
+                if (!a.getBoolean(attr, true)) {
+                    setIncludeFontPadding(false);
+                }
+                break;
+
+            case com.android.internal.R.styleable.TextView_cursorVisible:
+                if (!a.getBoolean(attr, true)) {
+                    setCursorVisible(false);
+                }
+                break;
+
+            case com.android.internal.R.styleable.TextView_maxLength:
+                maxlength = a.getInt(attr, -1);
+                break;
+
+            case com.android.internal.R.styleable.TextView_textScaleX:
+                setTextScaleX(a.getFloat(attr, 1.0f));
+                break;
+
+            case com.android.internal.R.styleable.TextView_freezesText:
+                mFreezesText = a.getBoolean(attr, false);
+                break;
+
+            case com.android.internal.R.styleable.TextView_shadowColor:
+                shadowcolor = a.getInt(attr, 0);
+                break;
+
+            case com.android.internal.R.styleable.TextView_shadowDx:
+                dx = a.getFloat(attr, 0);
+                break;
+
+            case com.android.internal.R.styleable.TextView_shadowDy:
+                dy = a.getFloat(attr, 0);
+                break;
+
+            case com.android.internal.R.styleable.TextView_shadowRadius:
+                r = a.getFloat(attr, 0);
+                break;
+
+            case com.android.internal.R.styleable.TextView_enabled:
+                setEnabled(a.getBoolean(attr, isEnabled()));
+                break;
+
+            case com.android.internal.R.styleable.TextView_textColorHighlight:
+                textColorHighlight = a.getColor(attr, textColorHighlight);
+                break;
+
+            case com.android.internal.R.styleable.TextView_textColor:
+                textColor = a.getColorStateList(attr);
+                break;
+
+            case com.android.internal.R.styleable.TextView_textColorHint:
+                textColorHint = a.getColorStateList(attr);
+                break;
+
+            case com.android.internal.R.styleable.TextView_textColorLink:
+                textColorLink = a.getColorStateList(attr);
+                break;
+
+            case com.android.internal.R.styleable.TextView_textSize:
+                textSize = a.getDimensionPixelSize(attr, textSize);
+                break;
+
+            case com.android.internal.R.styleable.TextView_typeface:
+                typefaceIndex = a.getInt(attr, typefaceIndex);
+                break;
+
+            case com.android.internal.R.styleable.TextView_textStyle:
+                styleIndex = a.getInt(attr, styleIndex);
+                break;
+
+            case com.android.internal.R.styleable.TextView_password:
+                password = a.getBoolean(attr, password);
+                break;
+
+            case com.android.internal.R.styleable.TextView_lineSpacingExtra:
+                mSpacingAdd = a.getDimensionPixelSize(attr, (int) mSpacingAdd);
+                break;
+
+            case com.android.internal.R.styleable.TextView_lineSpacingMultiplier:
+                mSpacingMult = a.getFloat(attr, mSpacingMult);
+                break;
+
+            case com.android.internal.R.styleable.TextView_inputType:
+                inputType = a.getInt(attr, mInputType);
+                break;
+
+            case com.android.internal.R.styleable.TextView_imeOptions:
+                if (mInputContentType == null) {
+                    mInputContentType = new InputContentType();
+                }
+                mInputContentType.imeOptions = a.getInt(attr,
+                        mInputContentType.imeOptions);
+                break;
+
+            case com.android.internal.R.styleable.TextView_imeActionLabel:
+                if (mInputContentType == null) {
+                    mInputContentType = new InputContentType();
+                }
+                mInputContentType.imeActionLabel = a.getText(attr);
+                break;
+
+            case com.android.internal.R.styleable.TextView_imeActionId:
+                if (mInputContentType == null) {
+                    mInputContentType = new InputContentType();
+                }
+                mInputContentType.imeActionId = a.getInt(attr,
+                        mInputContentType.imeActionId);
+                break;
+
+            case com.android.internal.R.styleable.TextView_privateImeOptions:
+                setPrivateImeOptions(a.getString(attr));
+                break;
+
+            case com.android.internal.R.styleable.TextView_editorExtras:
+                try {
+                    setInputExtras(a.getResourceId(attr, 0));
+                } catch (XmlPullParserException e) {
+                    Log.w("TextView", "Failure reading input extras", e);
+                } catch (IOException e) {
+                    Log.w("TextView", "Failure reading input extras", e);
+                }
+                break;
+            }
+        }
+        a.recycle();
+
+        BufferType bufferType = BufferType.EDITABLE;
+
+        if ((inputType&(EditorInfo.TYPE_MASK_CLASS
+                |EditorInfo.TYPE_MASK_VARIATION))
+                == (EditorInfo.TYPE_CLASS_TEXT
+                        |EditorInfo.TYPE_TEXT_VARIATION_PASSWORD)) {
+            password = true;
+        }
+
+        if (inputMethod != null) {
+            Class c;
+
+            try {
+                c = Class.forName(inputMethod.toString());
+            } catch (ClassNotFoundException ex) {
+                throw new RuntimeException(ex);
+            }
+
+            try {
+                mInput = (KeyListener) c.newInstance();
+            } catch (InstantiationException ex) {
+                throw new RuntimeException(ex);
+            } catch (IllegalAccessException ex) {
+                throw new RuntimeException(ex);
+            }
+            try {
+                mInputType = inputType != EditorInfo.TYPE_NULL
+                        ? inputType
+                        : mInput.getInputType();
+            } catch (IncompatibleClassChangeError e) {
+                mInputType = EditorInfo.TYPE_CLASS_TEXT;
+            }
+        } else if (digits != null) {
+            mInput = DigitsKeyListener.getInstance(digits.toString());
+            mInputType = inputType;
+        } else if (inputType != EditorInfo.TYPE_NULL) {
+            setInputType(inputType, true);
+            singleLine = (inputType&(EditorInfo.TYPE_MASK_CLASS
+                            | EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE)) !=
+                    (EditorInfo.TYPE_CLASS_TEXT
+                            | EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE);
+        } else if (phone) {
+            mInput = DialerKeyListener.getInstance();
+            inputType = EditorInfo.TYPE_CLASS_PHONE;
+        } else if (numeric != 0) {
+            mInput = DigitsKeyListener.getInstance((numeric & SIGNED) != 0,
+                                                   (numeric & DECIMAL) != 0);
+            inputType = EditorInfo.TYPE_CLASS_NUMBER;
+            if ((numeric & SIGNED) != 0) {
+                inputType |= EditorInfo.TYPE_NUMBER_FLAG_SIGNED;
+            }
+            if ((numeric & DECIMAL) != 0) {
+                inputType |= EditorInfo.TYPE_NUMBER_FLAG_DECIMAL;
+            }
+            mInputType = inputType;
+        } else if (autotext || autocap != -1) {
+            TextKeyListener.Capitalize cap;
+
+            inputType = EditorInfo.TYPE_CLASS_TEXT;
+            if (!singleLine) {
+                inputType |= EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE;
+            }
+
+            switch (autocap) {
+            case 1:
+                cap = TextKeyListener.Capitalize.SENTENCES;
+                inputType |= EditorInfo.TYPE_TEXT_FLAG_CAP_SENTENCES;
+                break;
+
+            case 2:
+                cap = TextKeyListener.Capitalize.WORDS;
+                inputType |= EditorInfo.TYPE_TEXT_FLAG_CAP_WORDS;
+                break;
+
+            case 3:
+                cap = TextKeyListener.Capitalize.CHARACTERS;
+                inputType |= EditorInfo.TYPE_TEXT_FLAG_CAP_CHARACTERS;
+                break;
+
+            default:
+                cap = TextKeyListener.Capitalize.NONE;
+                break;
+            }
+
+            mInput = TextKeyListener.getInstance(autotext, cap);
+            mInputType = inputType;
+        } else if (editable) {
+            mInput = TextKeyListener.getInstance();
+            mInputType = EditorInfo.TYPE_CLASS_TEXT;
+        } else {
+            mInput = null;
+
+            switch (buffertype) {
+                case 0:
+                    bufferType = BufferType.NORMAL;
+                    break;
+                case 1:
+                    bufferType = BufferType.SPANNABLE;
+                    break;
+                case 2:
+                    bufferType = BufferType.EDITABLE;
+                    break;
+            }
+        }
+
+        if (password && (mInputType&EditorInfo.TYPE_MASK_CLASS)
+                == EditorInfo.TYPE_CLASS_TEXT) {
+            mInputType = (mInputType & ~(EditorInfo.TYPE_MASK_VARIATION))
+                | EditorInfo.TYPE_TEXT_VARIATION_PASSWORD;
+        }
+
+        if (selectallonfocus) {
+            mSelectAllOnFocus = true;
+
+            if (bufferType == BufferType.NORMAL)
+                bufferType = BufferType.SPANNABLE;
+        }
+
+        setCompoundDrawablesWithIntrinsicBounds(
+            drawableLeft, drawableTop, drawableRight, drawableBottom);
+        setCompoundDrawablePadding(drawablePadding);
+
+        if (singleLine) {
+            setSingleLine();
+
+            if (mInput == null && ellipsize < 0) {
+                ellipsize = 3; // END
+            }
+        }
+
+        switch (ellipsize) {
+            case 1:
+                setEllipsize(TextUtils.TruncateAt.START);
+                break;
+            case 2:
+                setEllipsize(TextUtils.TruncateAt.MIDDLE);
+                break;
+            case 3:
+                setEllipsize(TextUtils.TruncateAt.END);
+                break;
+            case 4:
+                setHorizontalFadingEdgeEnabled(true);
+                setEllipsize(TextUtils.TruncateAt.MARQUEE);
+                break;
+        }
+
+        setTextColor(textColor != null ? textColor : ColorStateList.valueOf(0xFF000000));
+        setHintTextColor(textColorHint);
+        setLinkTextColor(textColorLink);
+        if (textColorHighlight != 0) {
+            setHighlightColor(textColorHighlight);
+        }
+        setRawTextSize(textSize);
+
+        if (password) {
+            setTransformationMethod(PasswordTransformationMethod.getInstance());
+            typefaceIndex = MONOSPACE;
+        }
+
+        setTypefaceByIndex(typefaceIndex, styleIndex);
+
+        if (shadowcolor != 0) {
+            setShadowLayer(r, dx, dy, shadowcolor);
+        }
+
+        if (maxlength >= 0) {
+            setFilters(new InputFilter[] { new InputFilter.LengthFilter(maxlength) });
+        } else {
+            setFilters(NO_FILTERS);
+        }
+
+        setText(text, bufferType);
+
+        /*
+         * Views are not normally focusable unless specified to be.
+         * However, TextViews that have input or movement methods *are*
+         * focusable by default.
+         */
+        a = context.obtainStyledAttributes(attrs,
+                                           com.android.internal.R.styleable.View,
+                                           defStyle, 0);
+
+        boolean focusable = mMovement != null || mInput != null;
+        boolean clickable = focusable;
+        boolean longClickable = focusable;
+
+        n = a.getIndexCount();
+        for (int i = 0; i < n; i++) {
+            int attr = a.getIndex(i);
+
+            switch (attr) {
+            case com.android.internal.R.styleable.View_focusable:
+                focusable = a.getBoolean(attr, focusable);
+                break;
+
+            case com.android.internal.R.styleable.View_clickable:
+                clickable = a.getBoolean(attr, clickable);
+                break;
+
+            case com.android.internal.R.styleable.View_longClickable:
+                longClickable = a.getBoolean(attr, longClickable);
+                break;
+            }
+        }
+        a.recycle();
+
+        setFocusable(focusable);
+        setClickable(clickable);
+        setLongClickable(longClickable);
+    }
+
+    private void setTypefaceByIndex(int typefaceIndex, int styleIndex) {
+        Typeface tf = null;
+        switch (typefaceIndex) {
+            case SANS:
+                tf = Typeface.SANS_SERIF;
+                break;
+
+            case SERIF:
+                tf = Typeface.SERIF;
+                break;
+
+            case MONOSPACE:
+                tf = Typeface.MONOSPACE;
+                break;
+        }
+
+        setTypeface(tf, styleIndex);
+    }
+
+    /**
+     * Sets the typeface and style in which the text should be displayed,
+     * and turns on the fake bold and italic bits in the Paint if the
+     * Typeface that you provided does not have all the bits in the
+     * style that you specified.
+     *
+     * @attr ref android.R.styleable#TextView_typeface
+     * @attr ref android.R.styleable#TextView_textStyle
+     */
+    public void setTypeface(Typeface tf, int style) {
+        if (style > 0) {
+            if (tf == null) {
+                tf = Typeface.defaultFromStyle(style);
+            } else {
+                tf = Typeface.create(tf, style);
+            }
+
+            setTypeface(tf);
+            // now compute what (if any) algorithmic styling is needed
+            int typefaceStyle = tf != null ? tf.getStyle() : 0;
+            int need = style & ~typefaceStyle;
+            mTextPaint.setFakeBoldText((need & Typeface.BOLD) != 0);
+            mTextPaint.setTextSkewX((need & Typeface.ITALIC) != 0 ? -0.25f : 0);
+        } else {
+            mTextPaint.setFakeBoldText(false);
+            mTextPaint.setTextSkewX(0);
+            setTypeface(tf);
+        }
+    }
+
+    /**
+     * Subclasses override this to specify that they have a KeyListener
+     * by default even if not specifically called for in the XML options.
+     */
+    protected boolean getDefaultEditable() {
+        return false;
+    }
+
+    /**
+     * Subclasses override this to specify a default movement method.
+     */
+    protected MovementMethod getDefaultMovementMethod() {
+        return null;
+    }
+
+    /**
+     * Return the text the TextView is displaying. If setText() was called with
+     * an argument of BufferType.SPANNABLE or BufferType.EDITABLE, you can cast
+     * the return value from this method to Spannable or Editable, respectively.
+     *
+     * Note: The content of the return value should not be modified. If you want
+     * a modifiable one, you should make your own copy first.
+     */
+    @ViewDebug.CapturedViewProperty
+    public CharSequence getText() {
+        return mText;
+    }
+
+    /**
+     * Returns the length, in characters, of the text managed by this TextView
+     */
+    public int length() {
+        return mText.length();
+    }
+
+    /**
+     * Return the text the TextView is displaying as an Editable object.  If
+     * the text is not editable, null is returned.
+     *
+     * @see #getText
+     */
+    public Editable getEditableText() {
+        return (mText instanceof Editable) ? (Editable)mText : null;
+    }
+
+    /**
+     * @return the height of one standard line in pixels.  Note that markup
+     * within the text can cause individual lines to be taller or shorter
+     * than this height, and the layout may contain additional first-
+     * or last-line padding.
+     */
+    public int getLineHeight() {
+        return FastMath.round(mTextPaint.getFontMetricsInt(null) * mSpacingMult
+                          + mSpacingAdd);
+    }
+
+    /**
+     * @return the Layout that is currently being used to display the text.
+     * This can be null if the text or width has recently changes.
+     */
+    public final Layout getLayout() {
+        return mLayout;
+    }
+
+    /**
+     * @return the current key listener for this TextView.
+     * This will frequently be null for non-EditText TextViews.
+     */
+    public final KeyListener getKeyListener() {
+        return mInput;
+    }
+
+    /**
+     * Sets the key listener to be used with this TextView.  This can be null
+     * to disallow user input.  Note that this method has significant and
+     * subtle interactions with soft keyboards and other input method:
+     * see {@link KeyListener#getInputType() KeyListener.getContentType()}
+     * for important details.  Calling this method will replace the current
+     * content type of the text view with the content type returned by the
+     * key listener.
+     * <p>
+     * Be warned that if you want a TextView with a key listener or movement
+     * method not to be focusable, or if you want a TextView without a
+     * key listener or movement method to be focusable, you must call
+     * {@link #setFocusable} again after calling this to get the focusability
+     * back the way you want it.
+     *
+     * @attr ref android.R.styleable#TextView_numeric
+     * @attr ref android.R.styleable#TextView_digits
+     * @attr ref android.R.styleable#TextView_phoneNumber
+     * @attr ref android.R.styleable#TextView_inputMethod
+     * @attr ref android.R.styleable#TextView_capitalize
+     * @attr ref android.R.styleable#TextView_autoText
+     */
+    public void setKeyListener(KeyListener input) {
+        setKeyListenerOnly(input);
+        fixFocusableAndClickableSettings();
+
+        if (input != null) {
+            try {
+                mInputType = mInput.getInputType();
+            } catch (IncompatibleClassChangeError e) {
+                mInputType = EditorInfo.TYPE_CLASS_TEXT;
+            }
+            if ((mInputType&EditorInfo.TYPE_MASK_CLASS)
+                    == EditorInfo.TYPE_CLASS_TEXT) {
+                if (mSingleLine) {
+                    mInputType &= ~EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE;
+                } else {
+                    mInputType |= EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE;
+                }
+            }
+        } else {
+            mInputType = EditorInfo.TYPE_NULL;
+        }
+
+        InputMethodManager imm = InputMethodManager.peekInstance();
+        if (imm != null) imm.restartInput(this);
+    }
+
+    private void setKeyListenerOnly(KeyListener input) {
+        mInput = input;
+        if (mInput != null && !(mText instanceof Editable))
+            setText(mText);
+
+        setFilters((Editable) mText, mFilters);
+    }
+
+    /**
+     * @return the movement method being used for this TextView.
+     * This will frequently be null for non-EditText TextViews.
+     */
+    public final MovementMethod getMovementMethod() {
+        return mMovement;
+    }
+
+    /**
+     * Sets the movement method (arrow key handler) to be used for
+     * this TextView.  This can be null to disallow using the arrow keys
+     * to move the cursor or scroll the view.
+     * <p>
+     * Be warned that if you want a TextView with a key listener or movement
+     * method not to be focusable, or if you want a TextView without a
+     * key listener or movement method to be focusable, you must call
+     * {@link #setFocusable} again after calling this to get the focusability
+     * back the way you want it.
+     */
+    public final void setMovementMethod(MovementMethod movement) {
+        mMovement = movement;
+
+        if (mMovement != null && !(mText instanceof Spannable))
+            setText(mText);
+
+        fixFocusableAndClickableSettings();
+    }
+
+    private void fixFocusableAndClickableSettings() {
+        if ((mMovement != null) || mInput != null) {
+            setFocusable(true);
+            setClickable(true);
+            setLongClickable(true);
+        } else {
+            setFocusable(false);
+            setClickable(false);
+            setLongClickable(false);
+        }
+    }
+
+    /**
+     * @return the current transformation method for this TextView.
+     * This will frequently be null except for single-line and password
+     * fields.
+     */
+    public final TransformationMethod getTransformationMethod() {
+        return mTransformation;
+    }
+
+    /**
+     * Sets the transformation that is applied to the text that this
+     * TextView is displaying.
+     *
+     * @attr ref android.R.styleable#TextView_password
+     * @attr ref android.R.styleable#TextView_singleLine
+     */
+    public final void setTransformationMethod(TransformationMethod method) {
+        if (method == mTransformation) {
+            // Avoid the setText() below if the transformation is
+            // the same.
+            return;
+        }
+        if (mTransformation != null) {
+            if (mText instanceof Spannable) {
+                ((Spannable) mText).removeSpan(mTransformation);
+            }
+        }
+
+        mTransformation = method;
+
+        setText(mText);
+    }
+
+    /**
+     * Returns the top padding of the view, plus space for the top
+     * Drawable if any.
+     */
+    public int getCompoundPaddingTop() {
+        final Drawables dr = mDrawables;
+        if (dr == null || dr.mDrawableTop == null) {
+            return mPaddingTop;
+        } else {
+            return mPaddingTop + dr.mDrawablePadding + dr.mDrawableSizeTop;
+        }
+    }
+
+    /**
+     * Returns the bottom padding of the view, plus space for the bottom
+     * Drawable if any.
+     */
+    public int getCompoundPaddingBottom() {
+        final Drawables dr = mDrawables;
+        if (dr == null || dr.mDrawableBottom == null) {
+            return mPaddingBottom;
+        } else {
+            return mPaddingBottom + dr.mDrawablePadding + dr.mDrawableSizeBottom;
+        }
+    }
+
+    /**
+     * Returns the left padding of the view, plus space for the left
+     * Drawable if any.
+     */
+    public int getCompoundPaddingLeft() {
+        final Drawables dr = mDrawables;
+        if (dr == null || dr.mDrawableLeft == null) {
+            return mPaddingLeft;
+        } else {
+            return mPaddingLeft + dr.mDrawablePadding + dr.mDrawableSizeLeft;
+        }
+    }
+
+    /**
+     * Returns the right padding of the view, plus space for the right
+     * Drawable if any.
+     */
+    public int getCompoundPaddingRight() {
+        final Drawables dr = mDrawables;
+        if (dr == null || dr.mDrawableRight == null) {
+            return mPaddingRight;
+        } else {
+            return mPaddingRight + dr.mDrawablePadding + dr.mDrawableSizeRight;
+        }
+    }
+
+    /**
+     * Returns the extended top padding of the view, including both the
+     * top Drawable if any and any extra space to keep more than maxLines
+     * of text from showing.  It is only valid to call this after measuring.
+     */
+    public int getExtendedPaddingTop() {
+        if (mMaxMode != LINES) {
+            return getCompoundPaddingTop();
+        }
+
+        if (mLayout.getLineCount() <= mMaximum) {
+            return getCompoundPaddingTop();
+        }
+
+        int top = getCompoundPaddingTop();
+        int bottom = getCompoundPaddingBottom();
+        int viewht = getHeight() - top - bottom;
+        int layoutht = mLayout.getLineTop(mMaximum);
+
+        if (layoutht >= viewht) {
+            return top;
+        }
+
+        final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
+        if (gravity == Gravity.TOP) {
+            return top;
+        } else if (gravity == Gravity.BOTTOM) {
+            return top + viewht - layoutht;
+        } else { // (gravity == Gravity.CENTER_VERTICAL)
+            return top + (viewht - layoutht) / 2;
+        }
+    }
+
+    /**
+     * Returns the extended bottom padding of the view, including both the
+     * bottom Drawable if any and any extra space to keep more than maxLines
+     * of text from showing.  It is only valid to call this after measuring.
+     */
+    public int getExtendedPaddingBottom() {
+        if (mMaxMode != LINES) {
+            return getCompoundPaddingBottom();
+        }
+
+        if (mLayout.getLineCount() <= mMaximum) {
+            return getCompoundPaddingBottom();
+        }
+
+        int top = getCompoundPaddingTop();
+        int bottom = getCompoundPaddingBottom();
+        int viewht = getHeight() - top - bottom;
+        int layoutht = mLayout.getLineTop(mMaximum);
+
+        if (layoutht >= viewht) {
+            return bottom;
+        }
+
+        final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
+        if (gravity == Gravity.TOP) {
+            return bottom + viewht - layoutht;
+        } else if (gravity == Gravity.BOTTOM) {
+            return bottom;
+        } else { // (gravity == Gravity.CENTER_VERTICAL)
+            return bottom + (viewht - layoutht) / 2;
+        }
+    }
+
+    /**
+     * Returns the total left padding of the view, including the left
+     * Drawable if any.
+     */
+    public int getTotalPaddingLeft() {
+        return getCompoundPaddingLeft();
+    }
+
+    /**
+     * Returns the total right padding of the view, including the right
+     * Drawable if any.
+     */
+    public int getTotalPaddingRight() {
+        return getCompoundPaddingRight();
+    }
+
+    /**
+     * Returns the total top padding of the view, including the top
+     * Drawable if any, the extra space to keep more than maxLines
+     * from showing, and the vertical offset for gravity, if any.
+     */
+    public int getTotalPaddingTop() {
+        return getExtendedPaddingTop() + getVerticalOffset(true);
+    }
+
+    /**
+     * Returns the total bottom padding of the view, including the bottom
+     * Drawable if any, the extra space to keep more than maxLines
+     * from showing, and the vertical offset for gravity, if any.
+     */
+    public int getTotalPaddingBottom() {
+        return getExtendedPaddingBottom() + getBottomVerticalOffset(true);
+    }
+
+    /**
+     * Sets the Drawables (if any) to appear to the left of, above,
+     * to the right of, and below the text.  Use null if you do not
+     * want a Drawable there.  The Drawables must already have had
+     * {@link Drawable#setBounds} called.
+     *
+     * @attr ref android.R.styleable#TextView_drawableLeft
+     * @attr ref android.R.styleable#TextView_drawableTop
+     * @attr ref android.R.styleable#TextView_drawableRight
+     * @attr ref android.R.styleable#TextView_drawableBottom
+     */
+    public void setCompoundDrawables(Drawable left, Drawable top,
+                                     Drawable right, Drawable bottom) {
+        Drawables dr = mDrawables;
+
+        final boolean drawables = left != null || top != null
+                || right != null || bottom != null;
+
+        if (!drawables) {
+            // Clearing drawables...  can we free the data structure?
+            if (dr != null) {
+                if (dr.mDrawablePadding == 0) {
+                    mDrawables = null;
+                } else {
+                    // We need to retain the last set padding, so just clear
+                    // out all of the fields in the existing structure.
+                    dr.mDrawableLeft = null;
+                    dr.mDrawableTop = null;
+                    dr.mDrawableRight = null;
+                    dr.mDrawableBottom = null;
+                    dr.mDrawableSizeLeft = dr.mDrawableHeightLeft = 0;
+                    dr.mDrawableSizeRight = dr.mDrawableHeightRight = 0;
+                    dr.mDrawableSizeTop = dr.mDrawableWidthTop = 0;
+                    dr.mDrawableSizeBottom = dr.mDrawableWidthBottom = 0;
+                }
+            }
+        } else {
+            if (dr == null) {
+                mDrawables = dr = new Drawables();
+            }
+
+            dr.mDrawableLeft = left;
+            dr.mDrawableTop = top;
+            dr.mDrawableRight = right;
+            dr.mDrawableBottom = bottom;
+
+            final Rect compoundRect = dr.mCompoundRect;
+            int[] state = null;
+
+            state = getDrawableState();
+
+            if (left != null) {
+                left.setState(state);
+                left.copyBounds(compoundRect);
+                dr.mDrawableSizeLeft = compoundRect.width();
+                dr.mDrawableHeightLeft = compoundRect.height();
+            } else {
+                dr.mDrawableSizeLeft = dr.mDrawableHeightLeft = 0;
+            }
+
+            if (right != null) {
+                right.setState(state);
+                right.copyBounds(compoundRect);
+                dr.mDrawableSizeRight = compoundRect.width();
+                dr.mDrawableHeightRight = compoundRect.height();
+            } else {
+                dr.mDrawableSizeRight = dr.mDrawableHeightRight = 0;
+            }
+
+            if (top != null) {
+                top.setState(state);
+                top.copyBounds(compoundRect);
+                dr.mDrawableSizeTop = compoundRect.height();
+                dr.mDrawableWidthTop = compoundRect.width();
+            } else {
+                dr.mDrawableSizeTop = dr.mDrawableWidthTop = 0;
+            }
+
+            if (bottom != null) {
+                bottom.setState(state);
+                bottom.copyBounds(compoundRect);
+                dr.mDrawableSizeBottom = compoundRect.height();
+                dr.mDrawableWidthBottom = compoundRect.width();
+            } else {
+                dr.mDrawableSizeBottom = dr.mDrawableWidthBottom = 0;
+            }
+        }
+
+        invalidate();
+        requestLayout();
+    }
+
+    /**
+     * Sets the Drawables (if any) to appear to the left of, above,
+     * to the right of, and below the text.  Use 0 if you do not
+     * want a Drawable there. The Drawables' bounds will be set to
+     * their intrinsic bounds.
+     *
+     * @param left Resource identifier of the left Drawable.
+     * @param top Resource identifier of the top Drawable.
+     * @param right Resource identifier of the right Drawable.
+     * @param bottom Resource identifier of the bottom Drawable.
+     *
+     * @attr ref android.R.styleable#TextView_drawableLeft
+     * @attr ref android.R.styleable#TextView_drawableTop
+     * @attr ref android.R.styleable#TextView_drawableRight
+     * @attr ref android.R.styleable#TextView_drawableBottom
+     */
+    public void setCompoundDrawablesWithIntrinsicBounds(int left, int top, int right, int bottom) {
+        final Resources resources = getContext().getResources();
+        setCompoundDrawablesWithIntrinsicBounds(left != 0 ? resources.getDrawable(left) : null,
+                top != 0 ? resources.getDrawable(top) : null,
+                right != 0 ? resources.getDrawable(right) : null,
+                bottom != 0 ? resources.getDrawable(bottom) : null);
+    }
+
+    /**
+     * Sets the Drawables (if any) to appear to the left of, above,
+     * to the right of, and below the text.  Use null if you do not
+     * want a Drawable there. The Drawables' bounds will be set to
+     * their intrinsic bounds.
+     *
+     * @attr ref android.R.styleable#TextView_drawableLeft
+     * @attr ref android.R.styleable#TextView_drawableTop
+     * @attr ref android.R.styleable#TextView_drawableRight
+     * @attr ref android.R.styleable#TextView_drawableBottom
+     */
+    public void setCompoundDrawablesWithIntrinsicBounds(Drawable left, Drawable top,
+            Drawable right, Drawable bottom) {
+
+        if (left != null) {
+            left.setBounds(0, 0, left.getIntrinsicWidth(), left.getIntrinsicHeight());
+        }
+        if (right != null) {
+            right.setBounds(0, 0, right.getIntrinsicWidth(), right.getIntrinsicHeight());
+        }
+        if (top != null) {
+            top.setBounds(0, 0, top.getIntrinsicWidth(), top.getIntrinsicHeight());
+        }
+        if (bottom != null) {
+            bottom.setBounds(0, 0, bottom.getIntrinsicWidth(), bottom.getIntrinsicHeight());
+        }
+        setCompoundDrawables(left, top, right, bottom);
+    }
+
+    /**
+     * Returns drawables for the left, top, right, and bottom borders.
+     */
+    public Drawable[] getCompoundDrawables() {
+        final Drawables dr = mDrawables;
+        if (dr != null) {
+            return new Drawable[] {
+                dr.mDrawableLeft, dr.mDrawableTop, dr.mDrawableRight, dr.mDrawableBottom
+            };
+        } else {
+            return new Drawable[] { null, null, null, null };
+        }
+    }
+
+    /**
+     * Sets the size of the padding between the compound drawables and
+     * the text.
+     *
+     * @attr ref android.R.styleable#TextView_drawablePadding
+     */
+    public void setCompoundDrawablePadding(int pad) {
+        Drawables dr = mDrawables;
+        if (pad == 0) {
+            if (dr != null) {
+                dr.mDrawablePadding = pad;
+            }
+        } else {
+            if (dr == null) {
+                mDrawables = dr = new Drawables();
+            }
+            dr.mDrawablePadding = pad;
+        }
+
+        invalidate();
+        requestLayout();
+    }
+
+    /**
+     * Returns the padding between the compound drawables and the text.
+     */
+    public int getCompoundDrawablePadding() {
+        final Drawables dr = mDrawables;
+        return dr != null ? dr.mDrawablePadding : 0;
+    }
+
+    @Override
+    public void setPadding(int left, int top, int right, int bottom) {
+        if (left != getPaddingLeft() ||
+            right != getPaddingRight() ||
+            top != getPaddingTop() ||
+            bottom != getPaddingBottom()) {
+            nullLayouts();
+        }
+
+        // the super call will requestLayout()
+        super.setPadding(left, top, right, bottom);
+        invalidate();
+    }
+
+    /**
+     * Gets the autolink mask of the text.  See {@link
+     * android.text.util.Linkify#ALL Linkify.ALL} and peers for
+     * possible values.
+     *
+     * @attr ref android.R.styleable#TextView_autoLink
+     */
+    public final int getAutoLinkMask() {
+        return mAutoLinkMask;
+    }
+
+    /**
+     * Sets the text color, size, style, hint color, and highlight color
+     * from the specified TextAppearance resource.
+     */
+    public void setTextAppearance(Context context, int resid) {
+        TypedArray appearance =
+            context.obtainStyledAttributes(resid,
+                                           com.android.internal.R.styleable.TextAppearance);
+
+        int color;
+        ColorStateList colors;
+        int ts;
+
+        color = appearance.getColor(com.android.internal.R.styleable.TextAppearance_textColorHighlight, 0);
+        if (color != 0) {
+            setHighlightColor(color);
+        }
+
+        colors = appearance.getColorStateList(com.android.internal.R.styleable.
+                                              TextAppearance_textColor);
+        if (colors != null) {
+            setTextColor(colors);
+        }
+
+        ts = appearance.getDimensionPixelSize(com.android.internal.R.styleable.
+                                              TextAppearance_textSize, 0);
+        if (ts != 0) {
+            setRawTextSize(ts);
+        }
+
+        colors = appearance.getColorStateList(com.android.internal.R.styleable.
+                                              TextAppearance_textColorHint);
+        if (colors != null) {
+            setHintTextColor(colors);
+        }
+
+        colors = appearance.getColorStateList(com.android.internal.R.styleable.
+                                              TextAppearance_textColorLink);
+        if (colors != null) {
+            setLinkTextColor(colors);
+        }
+
+        int typefaceIndex, styleIndex;
+
+        typefaceIndex = appearance.getInt(com.android.internal.R.styleable.
+                                          TextAppearance_typeface, -1);
+        styleIndex = appearance.getInt(com.android.internal.R.styleable.
+                                       TextAppearance_textStyle, -1);
+
+        setTypefaceByIndex(typefaceIndex, styleIndex);
+        appearance.recycle();
+    }
+
+    /**
+     * @return the size (in pixels) of the default text size in this TextView.
+     */
+    public float getTextSize() {
+        return mTextPaint.getTextSize();
+    }
+
+    /**
+     * Set the default text size to the given value, interpreted as "scaled
+     * pixel" units.  This size is adjusted based on the current density and
+     * user font size preference.
+     *
+     * @param size The scaled pixel size.
+     *
+     * @attr ref android.R.styleable#TextView_textSize
+     */
+    @android.view.RemotableViewMethod
+    public void setTextSize(float size) {
+        setTextSize(TypedValue.COMPLEX_UNIT_SP, size);
+    }
+
+    /**
+     * Set the default text size to a given unit and value.  See {@link
+     * TypedValue} for the possible dimension units.
+     *
+     * @param unit The desired dimension unit.
+     * @param size The desired size in the given units.
+     *
+     * @attr ref android.R.styleable#TextView_textSize
+     */
+    public void setTextSize(int unit, float size) {
+        Context c = getContext();
+        Resources r;
+
+        if (c == null)
+            r = Resources.getSystem();
+        else
+            r = c.getResources();
+
+        setRawTextSize(TypedValue.applyDimension(
+            unit, size, r.getDisplayMetrics()));
+    }
+
+    private void setRawTextSize(float size) {
+        if (size != mTextPaint.getTextSize()) {
+            mTextPaint.setTextSize(size);
+
+            if (mLayout != null) {
+                nullLayouts();
+                requestLayout();
+                invalidate();
+            }
+        }
+    }
+
+    /**
+     * @return the extent by which text is currently being stretched
+     * horizontally.  This will usually be 1.
+     */
+    public float getTextScaleX() {
+        return mTextPaint.getTextScaleX();
+    }
+
+    /**
+     * Sets the extent by which text should be stretched horizontally.
+     *
+     * @attr ref android.R.styleable#TextView_textScaleX
+     */
+    @android.view.RemotableViewMethod
+    public void setTextScaleX(float size) {
+        if (size != mTextPaint.getTextScaleX()) {
+            mTextPaint.setTextScaleX(size);
+
+            if (mLayout != null) {
+                nullLayouts();
+                requestLayout();
+                invalidate();
+            }
+        }
+    }
+
+    /**
+     * Sets the typeface and style in which the text should be displayed.
+     * Note that not all Typeface families actually have bold and italic
+     * variants, so you may need to use
+     * {@link #setTypeface(Typeface, int)} to get the appearance
+     * that you actually want.
+     *
+     * @attr ref android.R.styleable#TextView_typeface
+     * @attr ref android.R.styleable#TextView_textStyle
+     */
+    public void setTypeface(Typeface tf) {
+        if (mTextPaint.getTypeface() != tf) {
+            mTextPaint.setTypeface(tf);
+
+            if (mLayout != null) {
+                nullLayouts();
+                requestLayout();
+                invalidate();
+            }
+        }
+    }
+
+    /**
+     * @return the current typeface and style in which the text is being
+     * displayed.
+     */
+    public Typeface getTypeface() {
+        return mTextPaint.getTypeface();
+    }
+
+    /**
+     * Sets the text color for all the states (normal, selected,
+     * focused) to be this color.
+     *
+     * @attr ref android.R.styleable#TextView_textColor
+     */
+    @android.view.RemotableViewMethod
+    public void setTextColor(int color) {
+        mTextColor = ColorStateList.valueOf(color);
+        updateTextColors();
+    }
+
+    /**
+     * Sets the text color.
+     *
+     * @attr ref android.R.styleable#TextView_textColor
+     */
+    public void setTextColor(ColorStateList colors) {
+        if (colors == null) {
+            throw new NullPointerException();
+        }
+
+        mTextColor = colors;
+        updateTextColors();
+    }
+
+    /**
+     * Return the set of text colors.
+     *
+     * @return Returns the set of text colors.
+     */
+    public final ColorStateList getTextColors() {
+        return mTextColor;
+    }
+
+    /**
+     * <p>Return the current color selected for normal text.</p>
+     *
+     * @return Returns the current text color.
+     */
+    public final int getCurrentTextColor() {
+        return mCurTextColor;
+    }
+
+    /**
+     * Sets the color used to display the selection highlight.
+     *
+     * @attr ref android.R.styleable#TextView_textColorHighlight
+     */
+    @android.view.RemotableViewMethod
+    public void setHighlightColor(int color) {
+        if (mHighlightColor != color) {
+            mHighlightColor = color;
+            invalidate();
+        }
+    }
+
+    /**
+     * Gives the text a shadow of the specified radius and color, the specified
+     * distance from its normal position.
+     *
+     * @attr ref android.R.styleable#TextView_shadowColor
+     * @attr ref android.R.styleable#TextView_shadowDx
+     * @attr ref android.R.styleable#TextView_shadowDy
+     * @attr ref android.R.styleable#TextView_shadowRadius
+     */
+    public void setShadowLayer(float radius, float dx, float dy, int color) {
+        mTextPaint.setShadowLayer(radius, dx, dy, color);
+
+        mShadowRadius = radius;
+        mShadowDx = dx;
+        mShadowDy = dy;
+
+        invalidate();
+    }
+
+    /**
+     * @return the base paint used for the text.  Please use this only to
+     * consult the Paint's properties and not to change them.
+     */
+    public TextPaint getPaint() {
+        return mTextPaint;
+    }
+
+    /**
+     * Sets the autolink mask of the text.  See {@link
+     * android.text.util.Linkify#ALL Linkify.ALL} and peers for
+     * possible values.
+     *
+     * @attr ref android.R.styleable#TextView_autoLink
+     */
+    @android.view.RemotableViewMethod
+    public final void setAutoLinkMask(int mask) {
+        mAutoLinkMask = mask;
+    }
+
+    /**
+     * Sets whether the movement method will automatically be set to
+     * {@link LinkMovementMethod} if {@link #setAutoLinkMask} has been
+     * set to nonzero and links are detected in {@link #setText}.
+     * The default is true.
+     *
+     * @attr ref android.R.styleable#TextView_linksClickable
+     */
+    @android.view.RemotableViewMethod
+    public final void setLinksClickable(boolean whether) {
+        mLinksClickable = whether;
+    }
+
+    /**
+     * Returns whether the movement method will automatically be set to
+     * {@link LinkMovementMethod} if {@link #setAutoLinkMask} has been
+     * set to nonzero and links are detected in {@link #setText}.
+     * The default is true.
+     *
+     * @attr ref android.R.styleable#TextView_linksClickable
+     */
+    public final boolean getLinksClickable() {
+        return mLinksClickable;
+    }
+
+    /**
+     * Returns the list of URLSpans attached to the text
+     * (by {@link Linkify} or otherwise) if any.  You can call
+     * {@link URLSpan#getURL} on them to find where they link to
+     * or use {@link Spanned#getSpanStart} and {@link Spanned#getSpanEnd}
+     * to find the region of the text they are attached to.
+     */
+    public URLSpan[] getUrls() {
+        if (mText instanceof Spanned) {
+            return ((Spanned) mText).getSpans(0, mText.length(), URLSpan.class);
+        } else {
+            return new URLSpan[0];
+        }
+    }
+
+    /**
+     * Sets the color of the hint text.
+     *
+     * @attr ref android.R.styleable#TextView_textColorHint
+     */
+    @android.view.RemotableViewMethod
+    public final void setHintTextColor(int color) {
+        mHintTextColor = ColorStateList.valueOf(color);
+        updateTextColors();
+    }
+
+    /**
+     * Sets the color of the hint text.
+     *
+     * @attr ref android.R.styleable#TextView_textColorHint
+     */
+    public final void setHintTextColor(ColorStateList colors) {
+        mHintTextColor = colors;
+        updateTextColors();
+    }
+
+    /**
+     * <p>Return the color used to paint the hint text.</p>
+     *
+     * @return Returns the list of hint text colors.
+     */
+    public final ColorStateList getHintTextColors() {
+        return mHintTextColor;
+    }
+
+    /**
+     * <p>Return the current color selected to paint the hint text.</p>
+     *
+     * @return Returns the current hint text color.
+     */
+    public final int getCurrentHintTextColor() {
+        return mHintTextColor != null ? mCurHintTextColor : mCurTextColor;
+    }
+
+    /**
+     * Sets the color of links in the text.
+     *
+     * @attr ref android.R.styleable#TextView_textColorLink
+     */
+    @android.view.RemotableViewMethod
+    public final void setLinkTextColor(int color) {
+        mLinkTextColor = ColorStateList.valueOf(color);
+        updateTextColors();
+    }
+
+    /**
+     * Sets the color of links in the text.
+     *
+     * @attr ref android.R.styleable#TextView_textColorLink
+     */
+    public final void setLinkTextColor(ColorStateList colors) {
+        mLinkTextColor = colors;
+        updateTextColors();
+    }
+
+    /**
+     * <p>Returns the color used to paint links in the text.</p>
+     *
+     * @return Returns the list of link text colors.
+     */
+    public final ColorStateList getLinkTextColors() {
+        return mLinkTextColor;
+    }
+
+    /**
+     * Sets the horizontal alignment of the text and the
+     * vertical gravity that will be used when there is extra space
+     * in the TextView beyond what is required for the text itself.
+     *
+     * @see android.view.Gravity
+     * @attr ref android.R.styleable#TextView_gravity
+     */
+    public void setGravity(int gravity) {
+        if ((gravity & Gravity.HORIZONTAL_GRAVITY_MASK) == 0) {
+            gravity |= Gravity.LEFT;
+        }
+        if ((gravity & Gravity.VERTICAL_GRAVITY_MASK) == 0) {
+            gravity |= Gravity.TOP;
+        }
+
+        boolean newLayout = false;
+
+        if ((gravity & Gravity.HORIZONTAL_GRAVITY_MASK) !=
+            (mGravity & Gravity.HORIZONTAL_GRAVITY_MASK)) {
+            newLayout = true;
+        }
+
+        if (gravity != mGravity) {
+            invalidate();
+        }
+
+        mGravity = gravity;
+
+        if (mLayout != null && newLayout) {
+            // XXX this is heavy-handed because no actual content changes.
+            int want = mLayout.getWidth();
+            int hintWant = mHintLayout == null ? 0 : mHintLayout.getWidth();
+
+            makeNewLayout(want, hintWant, UNKNOWN_BORING, UNKNOWN_BORING,
+                          mRight - mLeft - getCompoundPaddingLeft() -
+                          getCompoundPaddingRight(), true);
+        }
+    }
+
+    /**
+     * Returns the horizontal and vertical alignment of this TextView.
+     *
+     * @see android.view.Gravity
+     * @attr ref android.R.styleable#TextView_gravity
+     */
+    public int getGravity() {
+        return mGravity;
+    }
+
+    /**
+     * @return the flags on the Paint being used to display the text.
+     * @see Paint#getFlags
+     */
+    public int getPaintFlags() {
+        return mTextPaint.getFlags();
+    }
+
+    /**
+     * Sets flags on the Paint being used to display the text and
+     * reflows the text if they are different from the old flags.
+     * @see Paint#setFlags
+     */
+    @android.view.RemotableViewMethod
+    public void setPaintFlags(int flags) {
+        if (mTextPaint.getFlags() != flags) {
+            mTextPaint.setFlags(flags);
+
+            if (mLayout != null) {
+                nullLayouts();
+                requestLayout();
+                invalidate();
+            }
+        }
+    }
+
+    /**
+     * Sets whether the text should be allowed to be wider than the
+     * View is.  If false, it will be wrapped to the width of the View.
+     *
+     * @attr ref android.R.styleable#TextView_scrollHorizontally
+     */
+    public void setHorizontallyScrolling(boolean whether) {
+        mHorizontallyScrolling = whether;
+
+        if (mLayout != null) {
+            nullLayouts();
+            requestLayout();
+            invalidate();
+        }
+    }
+
+    /**
+     * Makes the TextView at least this many lines tall
+     *
+     * @attr ref android.R.styleable#TextView_minLines
+     */
+    @android.view.RemotableViewMethod
+    public void setMinLines(int minlines) {
+        mMinimum = minlines;
+        mMinMode = LINES;
+
+        requestLayout();
+        invalidate();
+    }
+
+    /**
+     * Makes the TextView at least this many pixels tall
+     *
+     * @attr ref android.R.styleable#TextView_minHeight
+     */
+    @android.view.RemotableViewMethod
+    public void setMinHeight(int minHeight) {
+        mMinimum = minHeight;
+        mMinMode = PIXELS;
+
+        requestLayout();
+        invalidate();
+    }
+
+    /**
+     * Makes the TextView at most this many lines tall
+     *
+     * @attr ref android.R.styleable#TextView_maxLines
+     */
+    @android.view.RemotableViewMethod
+    public void setMaxLines(int maxlines) {
+        mMaximum = maxlines;
+        mMaxMode = LINES;
+
+        requestLayout();
+        invalidate();
+    }
+
+    /**
+     * Makes the TextView at most this many pixels tall
+     *
+     * @attr ref android.R.styleable#TextView_maxHeight
+     */
+    @android.view.RemotableViewMethod
+    public void setMaxHeight(int maxHeight) {
+        mMaximum = maxHeight;
+        mMaxMode = PIXELS;
+
+        requestLayout();
+        invalidate();
+    }
+
+    /**
+     * Makes the TextView exactly this many lines tall
+     *
+     * @attr ref android.R.styleable#TextView_lines
+     */
+    @android.view.RemotableViewMethod
+    public void setLines(int lines) {
+        mMaximum = mMinimum = lines;
+        mMaxMode = mMinMode = LINES;
+
+        requestLayout();
+        invalidate();
+    }
+
+    /**
+     * Makes the TextView exactly this many pixels tall.
+     * You could do the same thing by specifying this number in the
+     * LayoutParams.
+     *
+     * @attr ref android.R.styleable#TextView_height
+     */
+    @android.view.RemotableViewMethod
+    public void setHeight(int pixels) {
+        mMaximum = mMinimum = pixels;
+        mMaxMode = mMinMode = PIXELS;
+
+        requestLayout();
+        invalidate();
+    }
+
+    /**
+     * Makes the TextView at least this many ems wide
+     *
+     * @attr ref android.R.styleable#TextView_minEms
+     */
+    @android.view.RemotableViewMethod
+    public void setMinEms(int minems) {
+        mMinWidth = minems;
+        mMinWidthMode = EMS;
+
+        requestLayout();
+        invalidate();
+    }
+
+    /**
+     * Makes the TextView at least this many pixels wide
+     *
+     * @attr ref android.R.styleable#TextView_minWidth
+     */
+    @android.view.RemotableViewMethod
+    public void setMinWidth(int minpixels) {
+        mMinWidth = minpixels;
+        mMinWidthMode = PIXELS;
+
+        requestLayout();
+        invalidate();
+    }
+
+    /**
+     * Makes the TextView at most this many ems wide
+     *
+     * @attr ref android.R.styleable#TextView_maxEms
+     */
+    @android.view.RemotableViewMethod
+    public void setMaxEms(int maxems) {
+        mMaxWidth = maxems;
+        mMaxWidthMode = EMS;
+
+        requestLayout();
+        invalidate();
+    }
+
+    /**
+     * Makes the TextView at most this many pixels wide
+     *
+     * @attr ref android.R.styleable#TextView_maxWidth
+     */
+    @android.view.RemotableViewMethod
+    public void setMaxWidth(int maxpixels) {
+        mMaxWidth = maxpixels;
+        mMaxWidthMode = PIXELS;
+
+        requestLayout();
+        invalidate();
+    }
+
+    /**
+     * Makes the TextView exactly this many ems wide
+     *
+     * @attr ref android.R.styleable#TextView_ems
+     */
+    @android.view.RemotableViewMethod
+    public void setEms(int ems) {
+        mMaxWidth = mMinWidth = ems;
+        mMaxWidthMode = mMinWidthMode = EMS;
+
+        requestLayout();
+        invalidate();
+    }
+
+    /**
+     * Makes the TextView exactly this many pixels wide.
+     * You could do the same thing by specifying this number in the
+     * LayoutParams.
+     *
+     * @attr ref android.R.styleable#TextView_width
+     */
+    @android.view.RemotableViewMethod
+    public void setWidth(int pixels) {
+        mMaxWidth = mMinWidth = pixels;
+        mMaxWidthMode = mMinWidthMode = PIXELS;
+
+        requestLayout();
+        invalidate();
+    }
+
+
+    /**
+     * Sets line spacing for this TextView.  Each line will have its height
+     * multiplied by <code>mult</code> and have <code>add</code> added to it.
+     *
+     * @attr ref android.R.styleable#TextView_lineSpacingExtra
+     * @attr ref android.R.styleable#TextView_lineSpacingMultiplier
+     */
+    public void setLineSpacing(float add, float mult) {
+        mSpacingMult = mult;
+        mSpacingAdd = add;
+
+        if (mLayout != null) {
+            nullLayouts();
+            requestLayout();
+            invalidate();
+        }
+    }
+
+    /**
+     * Convenience method: Append the specified text to the TextView's
+     * display buffer, upgrading it to BufferType.EDITABLE if it was
+     * not already editable.
+     */
+    public final void append(CharSequence text) {
+        append(text, 0, text.length());
+    }
+
+    /**
+     * Convenience method: Append the specified text slice to the TextView's
+     * display buffer, upgrading it to BufferType.EDITABLE if it was
+     * not already editable.
+     */
+    public void append(CharSequence text, int start, int end) {
+        if (!(mText instanceof Editable)) {
+            setText(mText, BufferType.EDITABLE);
+        }
+
+        ((Editable) mText).append(text, start, end);
+    }
+
+    private void updateTextColors() {
+        boolean inval = false;
+        int color = mTextColor.getColorForState(getDrawableState(), 0);
+        if (color != mCurTextColor) {
+            mCurTextColor = color;
+            inval = true;
+        }
+        if (mLinkTextColor != null) {
+            color = mLinkTextColor.getColorForState(getDrawableState(), 0);
+            if (color != mTextPaint.linkColor) {
+                mTextPaint.linkColor = color;
+                inval = true;
+            }
+        }
+        if (mHintTextColor != null) {
+            color = mHintTextColor.getColorForState(getDrawableState(), 0);
+            if (color != mCurHintTextColor && mText.length() == 0) {
+                mCurHintTextColor = color;
+                inval = true;
+            }
+        }
+        if (inval) {
+            invalidate();
+        }
+    }
+
+    @Override
+    protected void drawableStateChanged() {
+        super.drawableStateChanged();
+        if (mTextColor != null && mTextColor.isStateful()
+                || (mHintTextColor != null && mHintTextColor.isStateful())
+                || (mLinkTextColor != null && mLinkTextColor.isStateful())) {
+            updateTextColors();
+        }
+
+        final Drawables dr = mDrawables;
+        if (dr != null) {
+            int[] state = getDrawableState();
+            if (dr.mDrawableTop != null && dr.mDrawableTop.isStateful()) {
+                dr.mDrawableTop.setState(state);
+            }
+            if (dr.mDrawableBottom != null && dr.mDrawableBottom.isStateful()) {
+                dr.mDrawableBottom.setState(state);
+            }
+            if (dr.mDrawableLeft != null && dr.mDrawableLeft.isStateful()) {
+                dr.mDrawableLeft.setState(state);
+            }
+            if (dr.mDrawableRight != null && dr.mDrawableRight.isStateful()) {
+                dr.mDrawableRight.setState(state);
+            }
+        }
+    }
+
+    /**
+     * User interface state that is stored by TextView for implementing
+     * {@link View#onSaveInstanceState}.
+     */
+    public static class SavedState extends BaseSavedState {
+        int selStart;
+        int selEnd;
+        CharSequence text;
+        boolean frozenWithFocus;
+
+        SavedState(Parcelable superState) {
+            super(superState);
+        }
+
+        @Override
+        public void writeToParcel(Parcel out, int flags) {
+            super.writeToParcel(out, flags);
+            out.writeInt(selStart);
+            out.writeInt(selEnd);
+            out.writeInt(frozenWithFocus ? 1 : 0);
+            TextUtils.writeToParcel(text, out, flags);
+        }
+
+        @Override
+        public String toString() {
+            String str = "TextView.SavedState{"
+                    + Integer.toHexString(System.identityHashCode(this))
+                    + " start=" + selStart + " end=" + selEnd;
+            if (text != null) {
+                str += " text=" + text;
+            }
+            return str + "}";
+        }
+
+        public static final Parcelable.Creator<SavedState> CREATOR
+                = new Parcelable.Creator<SavedState>() {
+            public SavedState createFromParcel(Parcel in) {
+                return new SavedState(in);
+            }
+
+            public SavedState[] newArray(int size) {
+                return new SavedState[size];
+            }
+        };
+
+        private SavedState(Parcel in) {
+            super(in);
+            selStart = in.readInt();
+            selEnd = in.readInt();
+            frozenWithFocus = (in.readInt() != 0);
+            text = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
+        }
+    }
+
+    @Override
+    public Parcelable onSaveInstanceState() {
+        Parcelable superState = super.onSaveInstanceState();
+
+        // Save state if we are forced to
+        boolean save = mFreezesText;
+        int start = 0;
+        int end = 0;
+
+        if (mText != null) {
+            start = Selection.getSelectionStart(mText);
+            end = Selection.getSelectionEnd(mText);
+            if (start >= 0 || end >= 0) {
+                // Or save state if there is a selection
+                save = true;
+            }
+        }
+
+        if (save) {
+            SavedState ss = new SavedState(superState);
+            // XXX Should also save the current scroll position!
+            ss.selStart = start;
+            ss.selEnd = end;
+
+            if (mText instanceof Spanned) {
+                /*
+                 * Calling setText() strips off any ChangeWatchers;
+                 * strip them now to avoid leaking references.
+                 * But do it to a copy so that if there are any
+                 * further changes to the text of this view, it
+                 * won't get into an inconsistent state.
+                 */
+
+                Spannable sp = new SpannableString(mText);
+
+                for (ChangeWatcher cw :
+                     sp.getSpans(0, sp.length(), ChangeWatcher.class)) {
+                    sp.removeSpan(cw);
+                }
+
+                ss.text = sp;
+            } else {
+                ss.text = mText.toString();
+            }
+
+            if (isFocused() && start >= 0 && end >= 0) {
+                ss.frozenWithFocus = true;
+            }
+
+            return ss;
+        }
+
+        return superState;
+    }
+
+    @Override
+    public void onRestoreInstanceState(Parcelable state) {
+        if (!(state instanceof SavedState)) {
+            super.onRestoreInstanceState(state);
+            return;
+        }
+
+        SavedState ss = (SavedState)state;
+        super.onRestoreInstanceState(ss.getSuperState());
+
+        // XXX restore buffer type too, as well as lots of other stuff
+        if (ss.text != null) {
+            setText(ss.text);
+        }
+
+        if (ss.selStart >= 0 && ss.selEnd >= 0) {
+            if (mText instanceof Spannable) {
+                int len = mText.length();
+
+                if (ss.selStart > len || ss.selEnd > len) {
+                    String restored = "";
+
+                    if (ss.text != null) {
+                        restored = "(restored) ";
+                    }
+
+                    Log.e("TextView", "Saved cursor position " + ss.selStart +
+                          "/" + ss.selEnd + " out of range for " + restored +
+                          "text " + mText);
+                } else {
+                    Selection.setSelection((Spannable) mText, ss.selStart,
+                                           ss.selEnd);
+
+                    if (ss.frozenWithFocus) {
+                        mFrozenWithFocus = true;
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Control whether this text view saves its entire text contents when
+     * freezing to an icicle, in addition to dynamic state such as cursor
+     * position.  By default this is false, not saving the text.  Set to true
+     * if the text in the text view is not being saved somewhere else in
+     * persistent storage (such as in a content provider) so that if the
+     * view is later thawed the user will not lose their data.
+     *
+     * @param freezesText Controls whether a frozen icicle should include the
+     * entire text data: true to include it, false to not.
+     *
+     * @attr ref android.R.styleable#TextView_freezesText
+     */
+    @android.view.RemotableViewMethod
+    public void setFreezesText(boolean freezesText) {
+        mFreezesText = freezesText;
+    }
+
+    /**
+     * Return whether this text view is including its entire text contents
+     * in frozen icicles.
+     *
+     * @return Returns true if text is included, false if it isn't.
+     *
+     * @see #setFreezesText
+     */
+    public boolean getFreezesText() {
+        return mFreezesText;
+    }
+
+    ///////////////////////////////////////////////////////////////////////////
+
+    /**
+     * Sets the Factory used to create new Editables.
+     */
+    public final void setEditableFactory(Editable.Factory factory) {
+        mEditableFactory = factory;
+        setText(mText);
+    }
+
+    /**
+     * Sets the Factory used to create new Spannables.
+     */
+    public final void setSpannableFactory(Spannable.Factory factory) {
+        mSpannableFactory = factory;
+        setText(mText);
+    }
+
+    /**
+     * Sets the string value of the TextView. TextView <em>does not</em> accept
+     * HTML-like formatting, which you can do with text strings in XML resource files.
+     * To style your strings, attach android.text.style.* objects to a
+     * {@link android.text.SpannableString SpannableString}, or see the
+     * <a href="{@docRoot}guide/topics/resources/available-resources.html#stringresources">
+     * Available Resource Types</a> documentation for an example of setting 
+     * formatted text in the XML resource file.
+     *
+     * @attr ref android.R.styleable#TextView_text
+     */
+    @android.view.RemotableViewMethod
+    public final void setText(CharSequence text) {
+        setText(text, mBufferType);
+    }
+
+    /**
+     * Like {@link #setText(CharSequence)},
+     * except that the cursor position (if any) is retained in the new text.
+     *
+     * @param text The new text to place in the text view.
+     *
+     * @see #setText(CharSequence)
+     */
+    @android.view.RemotableViewMethod
+    public final void setTextKeepState(CharSequence text) {
+        setTextKeepState(text, mBufferType);
+    }
+
+    /**
+     * Sets the text that this TextView is to display (see
+     * {@link #setText(CharSequence)}) and also sets whether it is stored
+     * in a styleable/spannable buffer and whether it is editable.
+     *
+     * @attr ref android.R.styleable#TextView_text
+     * @attr ref android.R.styleable#TextView_bufferType
+     */
+    public void setText(CharSequence text, BufferType type) {
+        setText(text, type, true, 0);
+
+        if (mCharWrapper != null) {
+            mCharWrapper.mChars = null;
+        }
+    }
+
+    private void setText(CharSequence text, BufferType type,
+                         boolean notifyBefore, int oldlen) {
+        if (text == null) {
+            text = "";
+        }
+
+        if (text instanceof Spanned &&
+            ((Spanned) text).getSpanStart(TextUtils.TruncateAt.MARQUEE) >= 0) {
+            setHorizontalFadingEdgeEnabled(true);
+            setEllipsize(TextUtils.TruncateAt.MARQUEE);
+        }
+
+        int n = mFilters.length;
+        for (int i = 0; i < n; i++) {
+            CharSequence out = mFilters[i].filter(text, 0, text.length(),
+                                                  EMPTY_SPANNED, 0, 0);
+            if (out != null) {
+                text = out;
+            }
+        }
+
+        if (notifyBefore) {
+            if (mText != null) {
+                oldlen = mText.length();
+                sendBeforeTextChanged(mText, 0, oldlen, text.length());
+            } else {
+                sendBeforeTextChanged("", 0, 0, text.length());
+            }
+        }
+
+        boolean needEditableForNotification = false;
+
+        if (mListeners != null && mListeners.size() != 0) {
+            needEditableForNotification = true;
+        }
+
+        if (type == BufferType.EDITABLE || mInput != null ||
+            needEditableForNotification) {
+            Editable t = mEditableFactory.newEditable(text);
+            text = t;
+            setFilters(t, mFilters);
+            InputMethodManager imm = InputMethodManager.peekInstance();
+            if (imm != null) imm.restartInput(this);
+        } else if (type == BufferType.SPANNABLE || mMovement != null) {
+            text = mSpannableFactory.newSpannable(text);
+        } else if (!(text instanceof CharWrapper)) {
+            text = TextUtils.stringOrSpannedString(text);
+        }
+
+        if (mAutoLinkMask != 0) {
+            Spannable s2;
+
+            if (type == BufferType.EDITABLE || text instanceof Spannable) {
+                s2 = (Spannable) text;
+            } else {
+                s2 = mSpannableFactory.newSpannable(text);
+            }
+
+            if (Linkify.addLinks(s2, mAutoLinkMask)) {
+                text = s2;
+                type = (type == BufferType.EDITABLE) ? BufferType.EDITABLE : BufferType.SPANNABLE;
+
+                /*
+                 * We must go ahead and set the text before changing the
+                 * movement method, because setMovementMethod() may call
+                 * setText() again to try to upgrade the buffer type.
+                 */
+                mText = text;
+
+                if (mLinksClickable) {
+                    setMovementMethod(LinkMovementMethod.getInstance());
+                }
+            }
+        }
+
+        mBufferType = type;
+        mText = text;
+
+        if (mTransformation == null)
+            mTransformed = text;
+        else
+            mTransformed = mTransformation.getTransformation(text, this);
+
+        final int textLength = text.length();
+
+        if (text instanceof Spannable) {
+            Spannable sp = (Spannable) text;
+
+            // Remove any ChangeWatchers that might have come
+            // from other TextViews.
+            final ChangeWatcher[] watchers = sp.getSpans(0, sp.length(), ChangeWatcher.class);
+            final int count = watchers.length;
+            for (int i = 0; i < count; i++)
+                sp.removeSpan(watchers[i]);
+
+            if (mChangeWatcher == null)
+                mChangeWatcher = new ChangeWatcher();
+
+            sp.setSpan(mChangeWatcher, 0, textLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE |
+                       (PRIORITY << Spanned.SPAN_PRIORITY_SHIFT));
+
+            if (mInput != null) {
+                sp.setSpan(mInput, 0, textLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
+            }
+
+            if (mTransformation != null) {
+                sp.setSpan(mTransformation, 0, textLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
+
+            }
+
+            if (mMovement != null) {
+                mMovement.initialize(this, (Spannable) text);
+
+                /*
+                 * Initializing the movement method will have set the
+                 * selection, so reset mSelectionMoved to keep that from
+                 * interfering with the normal on-focus selection-setting.
+                 */
+                mSelectionMoved = false;
+            }
+        }
+
+        if (mLayout != null) {
+            checkForRelayout();
+        }
+
+        sendOnTextChanged(text, 0, oldlen, textLength);
+        onTextChanged(text, 0, oldlen, textLength);
+
+        if (needEditableForNotification) {
+            sendAfterTextChanged((Editable) text);
+        }
+    }
+
+    /**
+     * Sets the TextView to display the specified slice of the specified
+     * char array.  You must promise that you will not change the contents
+     * of the array except for right before another call to setText(),
+     * since the TextView has no way to know that the text
+     * has changed and that it needs to invalidate and re-layout.
+     */
+    public final void setText(char[] text, int start, int len) {
+        int oldlen = 0;
+
+        if (start < 0 || len < 0 || start + len > text.length) {
+            throw new IndexOutOfBoundsException(start + ", " + len);
+        }
+
+        /*
+         * We must do the before-notification here ourselves because if
+         * the old text is a CharWrapper we destroy it before calling
+         * into the normal path.
+         */
+        if (mText != null) {
+            oldlen = mText.length();
+            sendBeforeTextChanged(mText, 0, oldlen, len);
+        } else {
+            sendBeforeTextChanged("", 0, 0, len);
+        }
+
+        if (mCharWrapper == null) {
+            mCharWrapper = new CharWrapper(text, start, len);
+        } else {
+            mCharWrapper.set(text, start, len);
+        }
+
+        setText(mCharWrapper, mBufferType, false, oldlen);
+    }
+
+    private static class CharWrapper
+            implements CharSequence, GetChars, GraphicsOperations {
+        private char[] mChars;
+        private int mStart, mLength;
+
+        public CharWrapper(char[] chars, int start, int len) {
+            mChars = chars;
+            mStart = start;
+            mLength = len;
+        }
+
+        /* package */ void set(char[] chars, int start, int len) {
+            mChars = chars;
+            mStart = start;
+            mLength = len;
+        }
+
+        public int length() {
+            return mLength;
+        }
+
+        public char charAt(int off) {
+            return mChars[off + mStart];
+        }
+
+        public String toString() {
+            return new String(mChars, mStart, mLength);
+        }
+
+        public CharSequence subSequence(int start, int end) {
+            if (start < 0 || end < 0 || start > mLength || end > mLength) {
+                throw new IndexOutOfBoundsException(start + ", " + end);
+            }
+
+            return new String(mChars, start + mStart, end - start);
+        }
+
+        public void getChars(int start, int end, char[] buf, int off) {
+            if (start < 0 || end < 0 || start > mLength || end > mLength) {
+                throw new IndexOutOfBoundsException(start + ", " + end);
+            }
+
+            System.arraycopy(mChars, start + mStart, buf, off, end - start);
+        }
+
+        public void drawText(Canvas c, int start, int end,
+                             float x, float y, Paint p) {
+            c.drawText(mChars, start + mStart, end - start, x, y, p);
+        }
+
+        public float measureText(int start, int end, Paint p) {
+            return p.measureText(mChars, start + mStart, end - start);
+        }
+
+        public int getTextWidths(int start, int end, float[] widths, Paint p) {
+            return p.getTextWidths(mChars, start + mStart, end - start, widths);
+        }
+    }
+
+    /**
+     * Like {@link #setText(CharSequence, android.widget.TextView.BufferType)},
+     * except that the cursor position (if any) is retained in the new text.
+     *
+     * @see #setText(CharSequence, android.widget.TextView.BufferType)
+     */
+    public final void setTextKeepState(CharSequence text, BufferType type) {
+        int start = getSelectionStart();
+        int end = getSelectionEnd();
+        int len = text.length();
+
+        setText(text, type);
+
+        if (start >= 0 || end >= 0) {
+            if (mText instanceof Spannable) {
+                Selection.setSelection((Spannable) mText,
+                                       Math.max(0, Math.min(start, len)),
+                                       Math.max(0, Math.min(end, len)));
+            }
+        }
+    }
+
+    @android.view.RemotableViewMethod
+    public final void setText(int resid) {
+        setText(getContext().getResources().getText(resid));
+    }
+
+    public final void setText(int resid, BufferType type) {
+        setText(getContext().getResources().getText(resid), type);
+    }
+
+    /**
+     * Sets the text to be displayed when the text of the TextView is empty.
+     * Null means to use the normal empty text. The hint does not currently
+     * participate in determining the size of the view.
+     *
+     * This method is deprecated. Use {link #setHint(int, String)} or
+     * {link #setHint(CharSequence, String)} instead.
+     *
+     * @attr ref android.R.styleable#TextView_hint
+     */
+    @android.view.RemotableViewMethod
+    public final void setHint(CharSequence hint) {
+        mHint = TextUtils.stringOrSpannedString(hint);
+
+        if (mLayout != null) {
+            checkForRelayout();
+        }
+
+        if (mText.length() == 0)
+            invalidate();
+    }
+
+    /**
+     * Sets the text to be displayed when the text of the TextView is empty,
+     * from a resource.
+     *
+     * This method is deprecated. Use {link #setHint(int, String)} or
+     * {link #setHint(CharSequence, String)} instead.
+     *
+     * @attr ref android.R.styleable#TextView_hint
+     */
+    @android.view.RemotableViewMethod
+    public final void setHint(int resid) {
+        setHint(getContext().getResources().getText(resid));
+    }
+
+    /**
+     * Returns the hint that is displayed when the text of the TextView
+     * is empty.
+     *
+     * @attr ref android.R.styleable#TextView_hint
+     */
+    @ViewDebug.CapturedViewProperty
+    public CharSequence getHint() {
+        return mHint;
+    }
+
+    /**
+     * Set the type of the content with a constant as defined for
+     * {@link EditorInfo#inputType}.  This will take care of changing
+     * the key listener, by calling {@link #setKeyListener(KeyListener)}, to
+     * match the given content type.  If the given content type is
+     * {@link EditorInfo#TYPE_NULL} then a soft keyboard will
+     * not be displayed for this text view.
+     *
+     * @see #getInputType()
+     * @see #setRawInputType(int)
+     * @see android.text.InputType
+     * @attr ref android.R.styleable#TextView_inputType
+     */
+    public void setInputType(int type) {
+        setInputType(type, false);
+        final boolean isPassword = (type&(EditorInfo.TYPE_MASK_CLASS
+                |EditorInfo.TYPE_MASK_VARIATION))
+                == (EditorInfo.TYPE_CLASS_TEXT
+                        |EditorInfo.TYPE_TEXT_VARIATION_PASSWORD);
+        boolean forceUpdate = false;
+        if (isPassword) {
+            setTransformationMethod(PasswordTransformationMethod.getInstance());
+            setTypefaceByIndex(MONOSPACE, 0);
+        } else if (mTransformation == PasswordTransformationMethod.getInstance()) {
+            // We need to clean up if we were previously in password mode.
+            setTypefaceByIndex(-1, -1);
+            forceUpdate = true;
+        }
+        
+        boolean multiLine = (type&(EditorInfo.TYPE_MASK_CLASS
+                        | EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE)) ==
+                (EditorInfo.TYPE_CLASS_TEXT
+                        | EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE);
+        
+        // We need to update the single line mode if it has changed or we
+        // were previously in password mode.
+        if (mSingleLine == multiLine || forceUpdate) {
+            // Change single line mode, but only change the transformation if
+            // we are not in password mode.
+            applySingleLine(!multiLine, !isPassword);
+        }
+        
+        InputMethodManager imm = InputMethodManager.peekInstance();
+        if (imm != null) imm.restartInput(this);
+    }
+
+    /**
+     * Directly change the content type integer of the text view, without
+     * modifying any other state.
+     * @see #setInputType(int)
+     * @see android.text.InputType
+     * @attr ref android.R.styleable#TextView_inputType
+     */
+    public void setRawInputType(int type) {
+        mInputType = type;
+    }
+
+    private void setInputType(int type, boolean direct) {
+        final int cls = type & EditorInfo.TYPE_MASK_CLASS;
+        KeyListener input;
+        if (cls == EditorInfo.TYPE_CLASS_TEXT) {
+            boolean autotext = (type & EditorInfo.TYPE_TEXT_FLAG_AUTO_CORRECT)
+                    != 0;
+            TextKeyListener.Capitalize cap;
+            if ((type & EditorInfo.TYPE_TEXT_FLAG_CAP_CHARACTERS) != 0) {
+                cap = TextKeyListener.Capitalize.CHARACTERS;
+            } else if ((type & EditorInfo.TYPE_TEXT_FLAG_CAP_WORDS) != 0) {
+                cap = TextKeyListener.Capitalize.WORDS;
+            } else if ((type & EditorInfo.TYPE_TEXT_FLAG_CAP_SENTENCES) != 0) {
+                cap = TextKeyListener.Capitalize.SENTENCES;
+            } else {
+                cap = TextKeyListener.Capitalize.NONE;
+            }
+            input = TextKeyListener.getInstance(autotext, cap);
+        } else if (cls == EditorInfo.TYPE_CLASS_NUMBER) {
+            input = DigitsKeyListener.getInstance(
+                    (type & EditorInfo.TYPE_NUMBER_FLAG_SIGNED) != 0,
+                    (type & EditorInfo.TYPE_NUMBER_FLAG_DECIMAL) != 0);
+        } else if (cls == EditorInfo.TYPE_CLASS_DATETIME) {
+            switch (type & EditorInfo.TYPE_MASK_VARIATION) {
+                case EditorInfo.TYPE_DATETIME_VARIATION_DATE:
+                    input = DateKeyListener.getInstance();
+                    break;
+                case EditorInfo.TYPE_DATETIME_VARIATION_TIME:
+                    input = TimeKeyListener.getInstance();
+                    break;
+                default:
+                    input = DateTimeKeyListener.getInstance();
+                    break;
+            }
+        } else if (cls == EditorInfo.TYPE_CLASS_PHONE) {
+            input = DialerKeyListener.getInstance();
+        } else {
+            input = TextKeyListener.getInstance();
+        }
+        mInputType = type;
+        if (direct) mInput = input;
+        else {
+            setKeyListenerOnly(input);
+        }
+    }
+
+    /**
+     * Get the type of the content.
+     *
+     * @see #setInputType(int)
+     * @see android.text.InputType
+     */
+    public int getInputType() {
+        return mInputType;
+    }
+
+    /**
+     * Change the editor type integer associated with the text view, which
+     * will be reported to an IME with {@link EditorInfo#imeOptions} when it
+     * has focus.
+     * @see #getImeOptions
+     * @see android.view.inputmethod.EditorInfo
+     * @attr ref android.R.styleable#TextView_imeOptions
+     */
+    public void setImeOptions(int imeOptions) {
+        if (mInputContentType == null) {
+            mInputContentType = new InputContentType();
+        }
+        mInputContentType.imeOptions = imeOptions;
+    }
+
+    /**
+     * Get the type of the IME editor.
+     *
+     * @see #setImeOptions(int)
+     * @see android.view.inputmethod.EditorInfo
+     */
+    public int getImeOptions() {
+        return mInputContentType != null
+                ? mInputContentType.imeOptions : EditorInfo.IME_UNDEFINED;
+    }
+
+    /**
+     * Change the custom IME action associated with the text view, which
+     * will be reported to an IME with {@link EditorInfo#actionLabel}
+     * and {@link EditorInfo#actionId} when it has focus.
+     * @see #getImeActionLabel
+     * @see #getImeActionId
+     * @see android.view.inputmethod.EditorInfo
+     * @attr ref android.R.styleable#TextView_imeActionLabel
+     * @attr ref android.R.styleable#TextView_imeActionId
+     */
+    public void setImeActionLabel(CharSequence label, int actionId) {
+        if (mInputContentType == null) {
+            mInputContentType = new InputContentType();
+        }
+        mInputContentType.imeActionLabel = label;
+        mInputContentType.imeActionId = actionId;
+    }
+
+    /**
+     * Get the IME action label previous set with {@link #setImeActionLabel}.
+     *
+     * @see #setImeActionLabel
+     * @see android.view.inputmethod.EditorInfo
+     */
+    public CharSequence getImeActionLabel() {
+        return mInputContentType != null
+                ? mInputContentType.imeActionLabel : null;
+    }
+
+    /**
+     * Get the IME action ID previous set with {@link #setImeActionLabel}.
+     *
+     * @see #setImeActionLabel
+     * @see android.view.inputmethod.EditorInfo
+     */
+    public int getImeActionId() {
+        return mInputContentType != null
+                ? mInputContentType.imeActionId : 0;
+    }
+
+    /**
+     * Set a special listener to be called when an action is performed
+     * on the text view.  This will be called when the enter key is pressed,
+     * or when an action supplied to the IME is selected by the user.  Setting
+     * this means that the normal hard key event will not insert a newline
+     * into the text view, even if it is multi-line; holding down the ALT
+     * modifier will, however, allow the user to insert a newline character.
+     */
+    public void setOnEditorActionListener(OnEditorActionListener l) {
+        if (mInputContentType == null) {
+            mInputContentType = new InputContentType();
+        }
+        mInputContentType.onEditorActionListener = l;
+    }
+    
+    /**
+     * Called when an attached input method calls
+     * {@link InputConnection#performEditorAction(int)
+     * InputConnection.performEditorAction()}
+     * for this text view.  The default implementation will call your click
+     * listener supplied to {@link #setOnEditorActionListener},
+     * or generate an enter key down/up pair to invoke the action if not.
+     * 
+     * @param actionCode The code of the action being performed.
+     * 
+     * @see #setOnEditorActionListener
+     */
+    public void onEditorAction(int actionCode) {
+        final InputContentType ict = mInputContentType;
+        if (ict != null) {
+            if (ict.onEditorActionListener != null) {
+                if (ict.onEditorActionListener.onEditorAction(this,
+                        actionCode, null)) {
+                    return;
+                }
+            }
+        }
+        
+        if (actionCode == EditorInfo.IME_ACTION_NEXT &&
+                (ict != null || !shouldAdvanceFocusOnEnter())) {
+            // This is the default handling for the NEXT action, to advance
+            // focus.  Note that for backwards compatibility we don't do this
+            // default handling if explicit ime options have not been given,
+            // and we do not advance by default on an enter key -- in that
+            // case, we want to turn this into the normal enter key codes that
+            // an app may be expecting.
+            View v = focusSearch(FOCUS_DOWN);
+            if (v != null) {
+                if (!v.requestFocus(FOCUS_DOWN)) {
+                    throw new IllegalStateException("focus search returned a view " +
+                            "that wasn't able to take focus!");
+                }
+            }
+            return;
+        }
+        
+        Handler h = getHandler();
+        long eventTime = SystemClock.uptimeMillis();
+        h.sendMessage(h.obtainMessage(ViewRoot.DISPATCH_KEY_FROM_IME,
+                new KeyEvent(eventTime, eventTime,
+                KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_ENTER, 0, 0, 0, 0,
+                KeyEvent.FLAG_SOFT_KEYBOARD|KeyEvent.FLAG_KEEP_TOUCH_MODE)));
+        h.sendMessage(h.obtainMessage(ViewRoot.DISPATCH_KEY_FROM_IME,
+                new KeyEvent(SystemClock.uptimeMillis(), eventTime,
+                KeyEvent.ACTION_UP, KeyEvent.KEYCODE_ENTER, 0, 0, 0, 0,
+                KeyEvent.FLAG_SOFT_KEYBOARD|KeyEvent.FLAG_KEEP_TOUCH_MODE)));
+    }
+    
+    /**
+     * Set the private content type of the text, which is the
+     * {@link EditorInfo#privateImeOptions EditorInfo.privateImeOptions}
+     * field that will be filled in when creating an input connection.
+     *
+     * @see #getPrivateImeOptions()
+     * @see EditorInfo#privateImeOptions
+     * @attr ref android.R.styleable#TextView_privateImeOptions
+     */
+    public void setPrivateImeOptions(String type) {
+        if (mInputContentType == null) mInputContentType = new InputContentType();
+        mInputContentType.privateImeOptions = type;
+    }
+
+    /**
+     * Get the private type of the content.
+     *
+     * @see #setPrivateImeOptions(String)
+     * @see EditorInfo#privateImeOptions
+     */
+    public String getPrivateImeOptions() {
+        return mInputContentType != null
+                ? mInputContentType.privateImeOptions : null;
+    }
+
+    /**
+     * Set the extra input data of the text, which is the
+     * {@link EditorInfo#extras TextBoxAttribute.extras}
+     * Bundle that will be filled in when creating an input connection.  The
+     * given integer is the resource ID of an XML resource holding an
+     * {@link android.R.styleable#InputExtras &lt;input-extras&gt;} XML tree.
+     *
+     * @see #getInputExtras(boolean) 
+     * @see EditorInfo#extras
+     * @attr ref android.R.styleable#TextView_editorExtras
+     */
+    public void setInputExtras(int xmlResId)
+            throws XmlPullParserException, IOException {
+        XmlResourceParser parser = getResources().getXml(xmlResId);
+        if (mInputContentType == null) mInputContentType = new InputContentType();
+        mInputContentType.extras = new Bundle();
+        getResources().parseBundleExtras(parser, mInputContentType.extras);
+    }
+
+    /**
+     * Retrieve the input extras currently associated with the text view, which
+     * can be viewed as well as modified.
+     *
+     * @param create If true, the extras will be created if they don't already
+     * exist.  Otherwise, null will be returned if none have been created.
+     * @see #setInputExtras(int)View
+     * @see EditorInfo#extras
+     * @attr ref android.R.styleable#TextView_editorExtras
+     */
+    public Bundle getInputExtras(boolean create) {
+        if (mInputContentType == null) {
+            if (!create) return null;
+            mInputContentType = new InputContentType();
+        }
+        if (mInputContentType.extras == null) {
+            if (!create) return null;
+            mInputContentType.extras = new Bundle();
+        }
+        return mInputContentType.extras;
+    }
+
+    /**
+     * Returns the error message that was set to be displayed with
+     * {@link #setError}, or <code>null</code> if no error was set
+     * or if it the error was cleared by the widget after user input.
+     */
+    public CharSequence getError() {
+        return mError;
+    }
+
+    /**
+     * Sets the right-hand compound drawable of the TextView to the "error"
+     * icon and sets an error message that will be displayed in a popup when
+     * the TextView has focus.  The icon and error message will be reset to
+     * null when any key events cause changes to the TextView's text.  If the
+     * <code>error</code> is <code>null</code>, the error message and icon
+     * will be cleared.
+     */
+    @android.view.RemotableViewMethod
+    public void setError(CharSequence error) {
+        if (error == null) {
+            setError(null, null);
+        } else {
+            Drawable dr = getContext().getResources().
+                getDrawable(com.android.internal.R.drawable.
+                            indicator_input_error);
+
+            dr.setBounds(0, 0, dr.getIntrinsicWidth(), dr.getIntrinsicHeight());
+            setError(error, dr);
+        }
+    }
+
+    /**
+     * Sets the right-hand compound drawable of the TextView to the specified
+     * icon and sets an error message that will be displayed in a popup when
+     * the TextView has focus.  The icon and error message will be reset to
+     * null when any key events cause changes to the TextView's text.  The
+     * drawable must already have had {@link Drawable#setBounds} set on it.
+     * If the <code>error</code> is <code>null</code>, the error message will
+     * be cleared (and you should provide a <code>null</code> icon as well).
+     */
+    public void setError(CharSequence error, Drawable icon) {
+        error = TextUtils.stringOrSpannedString(error);
+
+        mError = error;
+        mErrorWasChanged = true;
+        final Drawables dr = mDrawables;
+        if (dr != null) {
+            setCompoundDrawables(dr.mDrawableLeft, dr.mDrawableTop,
+                                 icon, dr.mDrawableBottom);
+        } else {
+            setCompoundDrawables(null, null, icon, null);
+        }
+
+        if (error == null) {
+            if (mPopup != null) {
+                if (mPopup.isShowing()) {
+                    mPopup.dismiss();
+                }
+
+                mPopup = null;
+            }
+        } else {
+            if (isFocused()) {
+                showError();
+            }
+        }
+    }
+
+    private void showError() {
+        if (getWindowToken() == null) {
+            mShowErrorAfterAttach = true;
+            return;
+        }
+
+        if (mPopup == null) {
+            LayoutInflater inflater = LayoutInflater.from(getContext());
+            final TextView err = (TextView) inflater.inflate(com.android.internal.R.layout.textview_hint,
+                    null);
+
+            mPopup = new PopupWindow(err, 200, 50) {
+                private boolean mAbove = false;
+
+                @Override
+                public void update(int x, int y, int w, int h, boolean force) {
+                    super.update(x, y, w, h, force);
+
+                    boolean above = isAboveAnchor();
+                    if (above != mAbove) {
+                        mAbove = above;
+
+                        if (above) {
+                            err.setBackgroundResource(com.android.internal.R.drawable.popup_inline_error_above);
+                        } else {
+                            err.setBackgroundResource(com.android.internal.R.drawable.popup_inline_error);
+                        }
+                    }
+                }
+            };
+            mPopup.setFocusable(false);
+        }
+
+        TextView tv = (TextView) mPopup.getContentView();
+        chooseSize(mPopup, mError, tv);
+        tv.setText(mError);
+
+        mPopup.showAsDropDown(this, getErrorX(), getErrorY());
+    }
+
+    /**
+     * Returns the Y offset to make the pointy top of the error point
+     * at the middle of the error icon.
+     */
+    private int getErrorX() {
+        /*
+         * The "25" is the distance between the point and the right edge
+         * of the background
+         */
+
+        final Drawables dr = mDrawables;
+        return getWidth() - mPopup.getWidth()
+                - getPaddingRight()
+                - (dr != null ? dr.mDrawableSizeRight : 0) / 2 + 25;
+    }
+
+    /**
+     * Returns the Y offset to make the pointy top of the error point
+     * at the bottom of the error icon.
+     */
+    private int getErrorY() {
+        /*
+         * Compound, not extended, because the icon is not clipped
+         * if the text height is smaller.
+         */
+        int vspace = mBottom - mTop -
+                     getCompoundPaddingBottom() - getCompoundPaddingTop();
+
+        final Drawables dr = mDrawables;
+        int icontop = getCompoundPaddingTop()
+                + (vspace - (dr != null ? dr.mDrawableHeightRight : 0)) / 2;
+
+        /*
+         * The "2" is the distance between the point and the top edge
+         * of the background.
+         */
+
+        return icontop + (dr != null ? dr.mDrawableHeightRight : 0)
+                - getHeight() - 2;
+    }
+
+    private void hideError() {
+        if (mPopup != null) {
+            if (mPopup.isShowing()) {
+                mPopup.dismiss();
+            }
+        }
+
+        mShowErrorAfterAttach = false;
+    }
+
+    private void chooseSize(PopupWindow pop, CharSequence text, TextView tv) {
+        int wid = tv.getPaddingLeft() + tv.getPaddingRight();
+        int ht = tv.getPaddingTop() + tv.getPaddingBottom();
+
+        /*
+         * Figure out how big the text would be if we laid it out to the
+         * full width of this view minus the border.
+         */
+        int cap = getWidth() - wid;
+        if (cap < 0) {
+            cap = 200; // We must not be measured yet -- setFrame() will fix it.
+        }
+
+        Layout l = new StaticLayout(text, tv.getPaint(), cap,
+                                    Layout.Alignment.ALIGN_NORMAL, 1, 0, true);
+        float max = 0;
+        for (int i = 0; i < l.getLineCount(); i++) {
+            max = Math.max(max, l.getLineWidth(i));
+        }
+
+        /*
+         * Now set the popup size to be big enough for the text plus the border.
+         */
+        pop.setWidth(wid + (int) Math.ceil(max));
+        pop.setHeight(ht + l.getHeight());
+    }
+
+
+    @Override
+    protected boolean setFrame(int l, int t, int r, int b) {
+        boolean result = super.setFrame(l, t, r, b);
+
+        if (mPopup != null) {
+            TextView tv = (TextView) mPopup.getContentView();
+            chooseSize(mPopup, mError, tv);
+            mPopup.update(this, getErrorX(), getErrorY(), -1, -1);
+        }
+
+        if (mRestartMarquee && mEllipsize == TextUtils.TruncateAt.MARQUEE) {
+            mRestartMarquee = false;
+            startMarquee();
+        }
+
+        return result;
+    }
+
+    /**
+     * Sets the list of input filters that will be used if the buffer is
+     * Editable.  Has no effect otherwise.
+     *
+     * @attr ref android.R.styleable#TextView_maxLength
+     */
+    public void setFilters(InputFilter[] filters) {
+        if (filters == null) {
+            throw new IllegalArgumentException();
+        }
+
+        mFilters = filters;
+
+        if (mText instanceof Editable) {
+            setFilters((Editable) mText, filters);
+        }
+    }
+
+    /**
+     * Sets the list of input filters on the specified Editable,
+     * and includes mInput in the list if it is an InputFilter.
+     */
+    private void setFilters(Editable e, InputFilter[] filters) {
+        if (mInput instanceof InputFilter) {
+            InputFilter[] nf = new InputFilter[filters.length + 1];
+
+            System.arraycopy(filters, 0, nf, 0, filters.length);
+            nf[filters.length] = (InputFilter) mInput;
+
+            e.setFilters(nf);
+        } else {
+            e.setFilters(filters);
+        }
+    }
+
+    /**
+     * Returns the current list of input filters.
+     */
+    public InputFilter[] getFilters() {
+        return mFilters;
+    }
+
+    /////////////////////////////////////////////////////////////////////////
+
+    private int getVerticalOffset(boolean forceNormal) {
+        int voffset = 0;
+        final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
+
+        Layout l = mLayout;
+        if (!forceNormal && mText.length() == 0 && mHintLayout != null) {
+            l = mHintLayout;
+        }
+
+        if (gravity != Gravity.TOP) {
+            int boxht;
+
+            if (l == mHintLayout) {
+                boxht = getMeasuredHeight() - getCompoundPaddingTop() -
+                        getCompoundPaddingBottom();
+            } else {
+                boxht = getMeasuredHeight() - getExtendedPaddingTop() -
+                        getExtendedPaddingBottom();
+            }
+            int textht = l.getHeight();
+
+            if (textht < boxht) {
+                if (gravity == Gravity.BOTTOM)
+                    voffset = boxht - textht;
+                else // (gravity == Gravity.CENTER_VERTICAL)
+                    voffset = (boxht - textht) >> 1;
+            }
+        }
+        return voffset;
+    }
+
+    private int getBottomVerticalOffset(boolean forceNormal) {
+        int voffset = 0;
+        final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
+
+        Layout l = mLayout;
+        if (!forceNormal && mText.length() == 0 && mHintLayout != null) {
+            l = mHintLayout;
+        }
+
+        if (gravity != Gravity.BOTTOM) {
+            int boxht;
+
+            if (l == mHintLayout) {
+                boxht = getMeasuredHeight() - getCompoundPaddingTop() -
+                        getCompoundPaddingBottom();
+            } else {
+                boxht = getMeasuredHeight() - getExtendedPaddingTop() -
+                        getExtendedPaddingBottom();
+            }
+            int textht = l.getHeight();
+
+            if (textht < boxht) {
+                if (gravity == Gravity.TOP)
+                    voffset = boxht - textht;
+                else // (gravity == Gravity.CENTER_VERTICAL)
+                    voffset = (boxht - textht) >> 1;
+            }
+        }
+        return voffset;
+    }
+
+    private void invalidateCursorPath() {
+        if (mHighlightPathBogus) {
+            invalidateCursor();
+        } else {
+            synchronized (sTempRect) {
+                /*
+                 * The reason for this concern about the thickness of the
+                 * cursor and doing the floor/ceil on the coordinates is that
+                 * some EditTexts (notably textfields in the Browser) have
+                 * anti-aliased text where not all the characters are
+                 * necessarily at integer-multiple locations.  This should
+                 * make sure the entire cursor gets invalidated instead of
+                 * sometimes missing half a pixel.
+                 */
+
+                float thick = FloatMath.ceil(mTextPaint.getStrokeWidth());
+                if (thick < 1.0f) {
+                    thick = 1.0f;
+                }
+
+                thick /= 2;
+
+                mHighlightPath.computeBounds(sTempRect, false);
+
+                int left = getCompoundPaddingLeft();
+                int top = getExtendedPaddingTop() + getVerticalOffset(true);
+
+                invalidate((int) FloatMath.floor(left + sTempRect.left - thick),
+                           (int) FloatMath.floor(top + sTempRect.top - thick),
+                           (int) FloatMath.ceil(left + sTempRect.right + thick),
+                           (int) FloatMath.ceil(top + sTempRect.bottom + thick));
+            }
+        }
+    }
+
+    private void invalidateCursor() {
+        int where = Selection.getSelectionEnd(mText);
+
+        invalidateCursor(where, where, where);
+    }
+
+    private void invalidateCursor(int a, int b, int c) {
+        if (mLayout == null) {
+            invalidate();
+        } else {
+            if (a >= 0 || b >= 0 || c >= 0) {
+                int first = Math.min(Math.min(a, b), c);
+                int last = Math.max(Math.max(a, b), c);
+
+                int line = mLayout.getLineForOffset(first);
+                int top = mLayout.getLineTop(line);
+
+                // This is ridiculous, but the descent from the line above
+                // can hang down into the line we really want to redraw,
+                // so we have to invalidate part of the line above to make
+                // sure everything that needs to be redrawn really is.
+                // (But not the whole line above, because that would cause
+                // the same problem with the descenders on the line above it!)
+                if (line > 0) {
+                    top -= mLayout.getLineDescent(line - 1);
+                }
+
+                int line2;
+
+                if (first == last)
+                    line2 = line;
+                else
+                    line2 = mLayout.getLineForOffset(last);
+
+                int bottom = mLayout.getLineTop(line2 + 1);
+                int voffset = getVerticalOffset(true);
+
+                int left = getCompoundPaddingLeft() + mScrollX;
+                invalidate(left, top + voffset + getExtendedPaddingTop(),
+                           left + getWidth() - getCompoundPaddingLeft() -
+                           getCompoundPaddingRight(),
+                           bottom + voffset + getExtendedPaddingTop());
+            }
+        }
+    }
+
+    private void registerForPreDraw() {
+        final ViewTreeObserver observer = getViewTreeObserver();
+        if (observer == null) {
+            return;
+        }
+
+        if (mPreDrawState == PREDRAW_NOT_REGISTERED) {
+            observer.addOnPreDrawListener(this);
+            mPreDrawState = PREDRAW_PENDING;
+        } else if (mPreDrawState == PREDRAW_DONE) {
+            mPreDrawState = PREDRAW_PENDING;
+        }
+
+        // else state is PREDRAW_PENDING, so keep waiting.
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public boolean onPreDraw() {
+        if (mPreDrawState != PREDRAW_PENDING) {
+            return true;
+        }
+
+        if (mLayout == null) {
+            assumeLayout();
+        }
+
+        boolean changed = false;
+
+        if (mMovement != null) {
+            int curs = Selection.getSelectionEnd(mText);
+
+            /*
+             * TODO: This should really only keep the end in view if
+             * it already was before the text changed.  I'm not sure
+             * of a good way to tell from here if it was.
+             */
+            if (curs < 0 &&
+                  (mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) {
+                curs = mText.length();
+            }
+
+            if (curs >= 0) {
+                changed = bringPointIntoView(curs);
+            }
+        } else {
+            changed = bringTextIntoView();
+        }
+
+        mPreDrawState = PREDRAW_DONE;
+        return !changed;
+    }
+
+    @Override
+    protected void onAttachedToWindow() {
+        super.onAttachedToWindow();
+
+        mTemporaryDetach = false;
+        
+        if (mShowErrorAfterAttach) {
+            showError();
+            mShowErrorAfterAttach = false;
+        }
+    }
+
+    @Override
+    protected void onDetachedFromWindow() {
+        super.onDetachedFromWindow();
+
+        if (mPreDrawState != PREDRAW_NOT_REGISTERED) {
+            final ViewTreeObserver observer = getViewTreeObserver();
+            if (observer != null) {
+                observer.removeOnPreDrawListener(this);
+                mPreDrawState = PREDRAW_NOT_REGISTERED;
+            }
+        }
+
+        if (mError != null) {
+            hideError();
+        }
+    }
+
+    @Override
+    protected boolean isPaddingOffsetRequired() {
+        return mShadowRadius != 0;
+    }
+
+    @Override
+    protected int getLeftPaddingOffset() {
+        return (int) Math.min(0, mShadowDx - mShadowRadius);
+    }
+
+    @Override
+    protected int getTopPaddingOffset() {
+        return (int) Math.min(0, mShadowDy - mShadowRadius);
+    }
+
+    @Override
+    protected int getBottomPaddingOffset() {
+        return (int) Math.max(0, mShadowDy + mShadowRadius);
+    }
+
+    @Override
+    protected int getRightPaddingOffset() {
+        return (int) Math.max(0, mShadowDx + mShadowRadius);
+    }
+
+    @Override
+    protected boolean verifyDrawable(Drawable who) {
+        final boolean verified = super.verifyDrawable(who);
+        if (!verified && mDrawables != null) {
+            return who == mDrawables.mDrawableLeft || who == mDrawables.mDrawableTop ||
+                    who == mDrawables.mDrawableRight || who == mDrawables.mDrawableBottom;
+        }
+        return verified;
+    }
+
+    @Override
+    protected void onDraw(Canvas canvas) {
+        // Draw the background for this view
+        super.onDraw(canvas);
+
+        final int compoundPaddingLeft = getCompoundPaddingLeft();
+        final int compoundPaddingTop = getCompoundPaddingTop();
+        final int compoundPaddingRight = getCompoundPaddingRight();
+        final int compoundPaddingBottom = getCompoundPaddingBottom();
+        final int scrollX = mScrollX;
+        final int scrollY = mScrollY;
+        final int right = mRight;
+        final int left = mLeft;
+        final int bottom = mBottom;
+        final int top = mTop;
+
+        final Drawables dr = mDrawables;
+        if (dr != null) {
+            /*
+             * Compound, not extended, because the icon is not clipped
+             * if the text height is smaller.
+             */
+
+            int vspace = bottom - top - compoundPaddingBottom - compoundPaddingTop;
+            int hspace = right - left - compoundPaddingRight - compoundPaddingLeft;
+
+            if (dr.mDrawableLeft != null) {
+                canvas.save();
+                canvas.translate(scrollX + mPaddingLeft,
+                                 scrollY + compoundPaddingTop +
+                                 (vspace - dr.mDrawableHeightLeft) / 2);
+                dr.mDrawableLeft.draw(canvas);
+                canvas.restore();
+            }
+
+            if (dr.mDrawableRight != null) {
+                canvas.save();
+                canvas.translate(scrollX + right - left - mPaddingRight - dr.mDrawableSizeRight,
+                         scrollY + compoundPaddingTop + (vspace - dr.mDrawableHeightRight) / 2);
+                dr.mDrawableRight.draw(canvas);
+                canvas.restore();
+            }
+
+            if (dr.mDrawableTop != null) {
+                canvas.save();
+                canvas.translate(scrollX + compoundPaddingLeft + (hspace - dr.mDrawableWidthTop) / 2,
+                        scrollY + mPaddingTop);
+                dr.mDrawableTop.draw(canvas);
+                canvas.restore();
+            }
+
+            if (dr.mDrawableBottom != null) {
+                canvas.save();
+                canvas.translate(scrollX + compoundPaddingLeft +
+                        (hspace - dr.mDrawableWidthBottom) / 2,
+                         scrollY + bottom - top - mPaddingBottom - dr.mDrawableSizeBottom);
+                dr.mDrawableBottom.draw(canvas);
+                canvas.restore();
+            }
+        }
+
+        if (mPreDrawState == PREDRAW_DONE) {
+            final ViewTreeObserver observer = getViewTreeObserver();
+            if (observer != null) {
+                observer.removeOnPreDrawListener(this);
+                mPreDrawState = PREDRAW_NOT_REGISTERED;
+            }
+        }
+
+        int color = mCurTextColor;
+
+        if (mLayout == null) {
+            assumeLayout();
+        }
+
+        Layout layout = mLayout;
+        int cursorcolor = color;
+
+        if (mHint != null && mText.length() == 0) {
+            if (mHintTextColor != null) {
+                color = mCurHintTextColor;
+            }
+
+            layout = mHintLayout;
+        }
+
+        mTextPaint.setColor(color);
+        mTextPaint.drawableState = getDrawableState();
+
+        canvas.save();
+        /*  Would be faster if we didn't have to do this. Can we chop the
+            (displayable) text so that we don't need to do this ever?
+        */
+
+        int extendedPaddingTop = getExtendedPaddingTop();
+        int extendedPaddingBottom = getExtendedPaddingBottom();
+
+        float clipLeft = compoundPaddingLeft + scrollX;
+        float clipTop = extendedPaddingTop + scrollY;
+        float clipRight = right - left - compoundPaddingRight + scrollX;
+        float clipBottom = bottom - top - extendedPaddingBottom + scrollY;
+
+        if (mShadowRadius != 0) {
+            clipLeft += Math.min(0, mShadowDx - mShadowRadius);
+            clipRight += Math.max(0, mShadowDx + mShadowRadius);
+
+            clipTop += Math.min(0, mShadowDy - mShadowRadius);
+            clipBottom += Math.max(0, mShadowDy + mShadowRadius);
+        }
+
+        canvas.clipRect(clipLeft, clipTop, clipRight, clipBottom);
+
+        int voffsetText = 0;
+        int voffsetCursor = 0;
+
+        // translate in by our padding
+        {
+            /* shortcircuit calling getVerticaOffset() */
+            if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
+                voffsetText = getVerticalOffset(false);
+                voffsetCursor = getVerticalOffset(true);
+            }
+            canvas.translate(compoundPaddingLeft, extendedPaddingTop + voffsetText);
+        }
+
+        if (mEllipsize == TextUtils.TruncateAt.MARQUEE) {
+            if (!mSingleLine && getLineCount() == 1 && canMarquee() &&
+                    (mGravity & Gravity.HORIZONTAL_GRAVITY_MASK) != Gravity.LEFT) {
+                canvas.translate(mLayout.getLineRight(0) - (mRight - mLeft -
+                        getCompoundPaddingLeft() - getCompoundPaddingRight()), 0.0f);
+            }
+
+            if (mMarquee != null && mMarquee.isRunning()) {
+                canvas.translate(-mMarquee.mScroll, 0.0f);
+            }
+        }
+
+        Path highlight = null;
+        int selStart = -1, selEnd = -1;
+
+        //  If there is no movement method, then there can be no selection.
+        //  Check that first and attempt to skip everything having to do with
+        //  the cursor.
+        //  XXX This is not strictly true -- a program could set the
+        //  selection manually if it really wanted to.
+        if (mMovement != null && (isFocused() || isPressed())) {
+            selStart = Selection.getSelectionStart(mText);
+            selEnd = Selection.getSelectionEnd(mText);
+
+            if (mCursorVisible && selStart >= 0 && isEnabled()) {
+                if (mHighlightPath == null)
+                    mHighlightPath = new Path();
+
+                if (selStart == selEnd) {
+                    if ((SystemClock.uptimeMillis() - mShowCursor) % (2 * BLINK)
+                        < BLINK) {
+                        if (mHighlightPathBogus) {
+                            mHighlightPath.reset();
+                            mLayout.getCursorPath(selStart, mHighlightPath, mText);
+                            mHighlightPathBogus = false;
+                        }
+
+                        // XXX should pass to skin instead of drawing directly
+                        mHighlightPaint.setColor(cursorcolor);
+                        mHighlightPaint.setStyle(Paint.Style.STROKE);
+
+                        highlight = mHighlightPath;
+                    }
+                } else {
+                    if (mHighlightPathBogus) {
+                        mHighlightPath.reset();
+                        mLayout.getSelectionPath(selStart, selEnd, mHighlightPath);
+                        mHighlightPathBogus = false;
+                    }
+
+                    // XXX should pass to skin instead of drawing directly
+                    mHighlightPaint.setColor(mHighlightColor);
+                    mHighlightPaint.setStyle(Paint.Style.FILL);
+
+                    highlight = mHighlightPath;
+                }
+            }
+        }
+
+        /*  Comment out until we decide what to do about animations
+        boolean isLinearTextOn = false;
+        if (currentTransformation != null) {
+            isLinearTextOn = mTextPaint.isLinearTextOn();
+            Matrix m = currentTransformation.getMatrix();
+            if (!m.isIdentity()) {
+                // mTextPaint.setLinearTextOn(true);
+            }
+        }
+        */
+
+        final InputMethodState ims = mInputMethodState;
+        if (ims != null && ims.mBatchEditNesting == 0) {
+            InputMethodManager imm = InputMethodManager.peekInstance();
+            if (imm != null) {
+                if (imm.isActive(this)) {
+                    boolean reported = false;
+                    if (ims.mContentChanged) {
+                        // We are in extract mode and the content has changed
+                        // in some way... just report complete new text to the
+                        // input method.
+                        reported = reportExtractedText();
+                    }
+                    if (!reported && highlight != null) {
+                        int candStart = -1;
+                        int candEnd = -1;
+                        if (mText instanceof Spannable) {
+                            Spannable sp = (Spannable)mText;
+                            candStart = EditableInputConnection.getComposingSpanStart(sp);
+                            candEnd = EditableInputConnection.getComposingSpanEnd(sp);
+                        }
+                        imm.updateSelection(this, selStart, selEnd, candStart, candEnd);
+                    }
+                }
+                
+                if (imm.isWatchingCursor(this) && highlight != null) {
+                    highlight.computeBounds(ims.mTmpRectF, true);
+                    ims.mTmpOffset[0] = ims.mTmpOffset[1] = 0;
+    
+                    canvas.getMatrix().mapPoints(ims.mTmpOffset);
+                    ims.mTmpRectF.offset(ims.mTmpOffset[0], ims.mTmpOffset[1]);
+    
+                    ims.mTmpRectF.offset(0, voffsetCursor - voffsetText);
+    
+                    ims.mCursorRectInWindow.set((int)(ims.mTmpRectF.left + 0.5),
+                            (int)(ims.mTmpRectF.top + 0.5),
+                            (int)(ims.mTmpRectF.right + 0.5),
+                            (int)(ims.mTmpRectF.bottom + 0.5));
+    
+                    imm.updateCursor(this,
+                            ims.mCursorRectInWindow.left, ims.mCursorRectInWindow.top,
+                            ims.mCursorRectInWindow.right, ims.mCursorRectInWindow.bottom);
+                }
+            }
+        }
+
+        layout.draw(canvas, highlight, mHighlightPaint, voffsetCursor - voffsetText);
+
+        /*  Comment out until we decide what to do about animations
+        if (currentTransformation != null) {
+            mTextPaint.setLinearTextOn(isLinearTextOn);
+        }
+        */
+
+        canvas.restore();
+    }
+
+    @Override
+    public void getFocusedRect(Rect r) {
+        if (mLayout == null) {
+            super.getFocusedRect(r);
+            return;
+        }
+
+        int sel = getSelectionEnd();
+        if (sel < 0) {
+            super.getFocusedRect(r);
+            return;
+        }
+
+        int line = mLayout.getLineForOffset(sel);
+        r.top = mLayout.getLineTop(line);
+        r.bottom = mLayout.getLineBottom(line);
+
+        r.left = (int) mLayout.getPrimaryHorizontal(sel);
+        r.right = r.left + 1;
+
+        // Adjust for padding and gravity.
+        int paddingLeft = getCompoundPaddingLeft();
+        int paddingTop = getExtendedPaddingTop();
+        if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
+            paddingTop += getVerticalOffset(false);
+        }
+        r.offset(paddingLeft, paddingTop);
+    }
+
+    /**
+     * Return the number of lines of text, or 0 if the internal Layout has not
+     * been built.
+     */
+    public int getLineCount() {
+        return mLayout != null ? mLayout.getLineCount() : 0;
+    }
+
+    /**
+     * Return the baseline for the specified line (0...getLineCount() - 1)
+     * If bounds is not null, return the top, left, right, bottom extents
+     * of the specified line in it. If the internal Layout has not been built,
+     * return 0 and set bounds to (0, 0, 0, 0)
+     * @param line which line to examine (0..getLineCount() - 1)
+     * @param bounds Optional. If not null, it returns the extent of the line
+     * @return the Y-coordinate of the baseline
+     */
+    public int getLineBounds(int line, Rect bounds) {
+        if (mLayout == null) {
+            if (bounds != null) {
+                bounds.set(0, 0, 0, 0);
+            }
+            return 0;
+        }
+        else {
+            int baseline = mLayout.getLineBounds(line, bounds);
+
+            int voffset = getExtendedPaddingTop();
+            if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
+                voffset += getVerticalOffset(true);
+            }
+            if (bounds != null) {
+                bounds.offset(getCompoundPaddingLeft(), voffset);
+            }
+            return baseline + voffset;
+        }
+    }
+
+    @Override
+    public int getBaseline() {
+        if (mLayout == null) {
+            return super.getBaseline();
+        }
+
+        int voffset = 0;
+        if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
+            voffset = getVerticalOffset(true);
+        }
+
+        return getExtendedPaddingTop() + voffset + mLayout.getLineBaseline(0);
+    }
+
+    @Override
+    public boolean onKeyDown(int keyCode, KeyEvent event) {
+        int which = doKeyDown(keyCode, event, null);
+        if (which == 0) {
+            // Go through default dispatching.
+            return super.onKeyDown(keyCode, event);
+        }
+
+        return true;
+    }
+
+    @Override
+    public boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event) {
+        KeyEvent down = new KeyEvent(event, KeyEvent.ACTION_DOWN);
+
+        int which = doKeyDown(keyCode, down, event);
+        if (which == 0) {
+            // Go through default dispatching.
+            return super.onKeyMultiple(keyCode, repeatCount, event);
+        }
+        if (which == -1) {
+            // Consumed the whole thing.
+            return true;
+        }
+
+        repeatCount--;
+        
+        // We are going to dispatch the remaining events to either the input
+        // or movement method.  To do this, we will just send a repeated stream
+        // of down and up events until we have done the complete repeatCount.
+        // It would be nice if those interfaces had an onKeyMultiple() method,
+        // but adding that is a more complicated change.
+        KeyEvent up = new KeyEvent(event, KeyEvent.ACTION_UP);
+        if (which == 1) {
+            mInput.onKeyUp(this, (Editable)mText, keyCode, up);
+            while (--repeatCount > 0) {
+                mInput.onKeyDown(this, (Editable)mText, keyCode, down);
+                mInput.onKeyUp(this, (Editable)mText, keyCode, up);
+            }
+            if (mError != null && !mErrorWasChanged) {
+                setError(null, null);
+            }
+
+        } else if (which == 2) {
+            mMovement.onKeyUp(this, (Spannable)mText, keyCode, up);
+            while (--repeatCount > 0) {
+                mMovement.onKeyDown(this, (Spannable)mText, keyCode, down);
+                mMovement.onKeyUp(this, (Spannable)mText, keyCode, up);
+            }
+        }
+
+        return true;
+    }
+
+    /**
+     * Returns true if pressing ENTER in this field advances focus instead
+     * of inserting the character.  This is true mostly in single-line fields,
+     * but also in mail addresses and subjects which will display on multiple
+     * lines but where it doesn't make sense to insert newlines.
+     */
+    protected boolean shouldAdvanceFocusOnEnter() {
+        if (mInput == null) {
+            return false;
+        }
+
+        if (mSingleLine) {
+            return true;
+        }
+
+        if ((mInputType & EditorInfo.TYPE_MASK_CLASS) == EditorInfo.TYPE_CLASS_TEXT) {
+            int variation = mInputType & EditorInfo.TYPE_MASK_VARIATION;
+
+            if (variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_ADDRESS ||
+                variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_SUBJECT) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    private boolean isInterestingEnter(KeyEvent event) {
+        if ((event.getFlags() & KeyEvent.FLAG_SOFT_KEYBOARD) != 0 &&
+                mInputContentType != null &&
+                (mInputContentType.imeOptions &
+                        EditorInfo.IME_FLAG_NO_ENTER_ACTION) != 0) {
+            // If this enter key came from a soft keyboard, and the
+            // text editor has been configured to not do a default
+            // action for software enter keys, then we aren't interested.
+            return false;
+        }
+        return true;
+    }
+    
+    private int doKeyDown(int keyCode, KeyEvent event, KeyEvent otherEvent) {
+        if (!isEnabled()) {
+            return 0;
+        }
+
+        switch (keyCode) {
+            case KeyEvent.KEYCODE_ENTER:
+                if (!isInterestingEnter(event)) {
+                    // Ignore enter key we aren't interested in.
+                    return -1;
+                }
+                if ((event.getMetaState()&KeyEvent.META_ALT_ON) == 0
+                        && mInputContentType != null
+                        && mInputContentType.onEditorActionListener != null) {
+                    mInputContentType.enterDown = true;
+                    // We are consuming the enter key for them.
+                    return -1;
+                }
+                // fall through...
+            case KeyEvent.KEYCODE_DPAD_CENTER:
+                if (shouldAdvanceFocusOnEnter()) {
+                    return 0;
+                }
+        }
+
+        if (mInput != null) {
+            /*
+             * Keep track of what the error was before doing the input
+             * so that if an input filter changed the error, we leave
+             * that error showing.  Otherwise, we take down whatever
+             * error was showing when the user types something.
+             */
+            mErrorWasChanged = false;
+
+            boolean doDown = true;
+            if (otherEvent != null) {
+                try {
+                    beginBatchEdit();
+                    boolean handled = mInput.onKeyOther(this, (Editable) mText,
+                            otherEvent);
+                    if (mError != null && !mErrorWasChanged) {
+                        setError(null, null);
+                    }
+                    doDown = false;
+                    if (handled) {
+                        return -1;
+                    }
+                } catch (AbstractMethodError e) {
+                    // onKeyOther was added after 1.0, so if it isn't
+                    // implemented we need to try to dispatch as a regular down.
+                } finally {
+                    endBatchEdit();
+                }
+            }
+            
+            if (doDown) {
+                beginBatchEdit();
+                if (mInput.onKeyDown(this, (Editable) mText, keyCode, event)) {
+                    endBatchEdit();
+                    if (mError != null && !mErrorWasChanged) {
+                        setError(null, null);
+                    }
+                    return 1;
+                }
+                endBatchEdit();
+            }
+        }
+
+        // bug 650865: sometimes we get a key event before a layout.
+        // don't try to move around if we don't know the layout.
+
+        if (mMovement != null && mLayout != null) {
+            boolean doDown = true;
+            if (otherEvent != null) {
+                try {
+                    boolean handled = mMovement.onKeyOther(this, (Spannable) mText,
+                            otherEvent);
+                    doDown = false;
+                    if (handled) {
+                        return -1;
+                    }
+                } catch (AbstractMethodError e) {
+                    // onKeyOther was added after 1.0, so if it isn't
+                    // implemented we need to try to dispatch as a regular down.
+                }
+            }
+            if (doDown) {
+                if (mMovement.onKeyDown(this, (Spannable)mText, keyCode, event))
+                    return 2;
+            }
+        }
+
+        return 0;
+    }
+
+    @Override
+    public boolean onKeyUp(int keyCode, KeyEvent event) {
+        if (!isEnabled()) {
+            return super.onKeyUp(keyCode, event);
+        }
+
+        switch (keyCode) {
+            case KeyEvent.KEYCODE_DPAD_CENTER:
+                /*
+                 * If there is a click listener, just call through to
+                 * super, which will invoke it.
+                 *
+                 * If there isn't a click listener, try to show the soft
+                 * input method.  (It will also
+                 * call performClick(), but that won't do anything in
+                 * this case.)
+                 */
+                if (mOnClickListener == null) {
+                    if (mMovement != null && mText instanceof Editable
+                            && mLayout != null && onCheckIsTextEditor()) {
+                        InputMethodManager imm = (InputMethodManager)
+                                getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
+                        imm.showSoftInput(this, 0);
+                    }
+                }
+                return super.onKeyUp(keyCode, event);
+                
+            case KeyEvent.KEYCODE_ENTER:
+                if (mInputContentType != null
+                        && mInputContentType.onEditorActionListener != null
+                        && mInputContentType.enterDown) {
+                    mInputContentType.enterDown = false;
+                    if (mInputContentType.onEditorActionListener.onEditorAction(
+                            this, EditorInfo.IME_UNDEFINED, event)) {
+                        return true;
+                    }
+                }
+                
+                if (shouldAdvanceFocusOnEnter()) {
+                    /*
+                     * If there is a click listener, just call through to
+                     * super, which will invoke it.
+                     *
+                     * If there isn't a click listener, try to advance focus,
+                     * but still call through to super, which will reset the
+                     * pressed state and longpress state.  (It will also
+                     * call performClick(), but that won't do anything in
+                     * this case.)
+                     */
+                    if (mOnClickListener == null) {
+                        View v = focusSearch(FOCUS_DOWN);
+
+                        if (v != null) {
+                            if (!v.requestFocus(FOCUS_DOWN)) {
+                                throw new IllegalStateException("focus search returned a view " +
+                                        "that wasn't able to take focus!");
+                            }
+
+                            /*
+                             * Return true because we handled the key; super
+                             * will return false because there was no click
+                             * listener.
+                             */
+                            super.onKeyUp(keyCode, event);
+                            return true;
+                        }
+                    }
+
+                    return super.onKeyUp(keyCode, event);
+                }
+        }
+
+        if (mInput != null)
+            if (mInput.onKeyUp(this, (Editable) mText, keyCode, event))
+                return true;
+
+        if (mMovement != null && mLayout != null)
+            if (mMovement.onKeyUp(this, (Spannable) mText, keyCode, event))
+                return true;
+
+        return super.onKeyUp(keyCode, event);
+    }
+
+    @Override public boolean onCheckIsTextEditor() {
+        return mInputType != EditorInfo.TYPE_NULL;
+    }
+    
+    @Override public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
+        if (onCheckIsTextEditor()) {
+            if (mInputMethodState == null) {
+                mInputMethodState = new InputMethodState();
+            }
+            outAttrs.inputType = mInputType;
+            if (mInputContentType != null) {
+                outAttrs.imeOptions = mInputContentType.imeOptions;
+                outAttrs.privateImeOptions = mInputContentType.privateImeOptions;
+                outAttrs.actionLabel = mInputContentType.imeActionLabel;
+                outAttrs.actionId = mInputContentType.imeActionId;
+                outAttrs.extras = mInputContentType.extras;
+            } else {
+                outAttrs.imeOptions = EditorInfo.IME_UNDEFINED;
+            }
+            if (outAttrs.imeOptions == EditorInfo.IME_UNDEFINED) {
+                if (focusSearch(FOCUS_DOWN) != null) {
+                    // An action has not been set, but the enter key will move to
+                    // the next focus, so set the action to that.
+                    outAttrs.imeOptions = EditorInfo.IME_ACTION_NEXT;
+                    if (!shouldAdvanceFocusOnEnter()) {
+                        outAttrs.imeOptions |= EditorInfo.IME_FLAG_NO_ENTER_ACTION;
+                    }
+                }
+            }
+            outAttrs.hintText = mHint;
+            if (mText instanceof Editable) {
+                InputConnection ic = new EditableInputConnection(this);
+                outAttrs.initialSelStart = Selection.getSelectionStart(mText);
+                outAttrs.initialSelEnd = Selection.getSelectionEnd(mText);
+                outAttrs.initialCapsMode = ic.getCursorCapsMode(mInputType);
+                return ic;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * If this TextView contains editable content, extract a portion of it
+     * based on the information in <var>request</var> in to <var>outText</var>.
+     * @return Returns true if the text was successfully extracted, else false.
+     */
+    public boolean extractText(ExtractedTextRequest request,
+            ExtractedText outText) {
+        return extractTextInternal(request, -1, -1, -1, outText);
+    }
+    
+    boolean extractTextInternal(ExtractedTextRequest request,
+            int partialStartOffset, int partialEndOffset, int delta,
+            ExtractedText outText) {
+        final CharSequence content = mText;
+        if (content != null) {
+            final int N = content.length();
+            if (partialStartOffset < 0) {
+                outText.partialStartOffset = outText.partialEndOffset = -1;
+                partialStartOffset = 0;
+                partialEndOffset = N;
+            } else {
+                // Adjust offsets to ensure we contain full spans.
+                if (content instanceof Spanned) {
+                    Spanned spanned = (Spanned)content;
+                    Object[] spans = spanned.getSpans(partialStartOffset,
+                            partialEndOffset, ParcelableSpan.class);
+                    int i = spans.length;
+                    while (i > 0) {
+                        i--;
+                        int j = spanned.getSpanStart(spans[i]);
+                        if (j < partialStartOffset) partialStartOffset = j;
+                        j = spanned.getSpanEnd(spans[i]);
+                        if (j > partialEndOffset) partialEndOffset = j;
+                    }
+                }
+                outText.partialStartOffset = partialStartOffset;
+                outText.partialEndOffset = partialEndOffset;
+                // Now use the delta to determine the actual amount of text
+                // we need.
+                partialEndOffset += delta;
+                if (partialEndOffset > N) {
+                    partialEndOffset = N;
+                } else if (partialEndOffset < 0) {
+                    partialEndOffset = 0;
+                }
+            }
+            if ((request.flags&InputConnection.GET_TEXT_WITH_STYLES) != 0) {
+                outText.text = content.subSequence(partialStartOffset,
+                        partialEndOffset);
+            } else {
+                outText.text = TextUtils.substring(content, partialStartOffset,
+                        partialEndOffset);
+            }
+            outText.startOffset = 0;
+            outText.selectionStart = Selection.getSelectionStart(content);
+            outText.selectionEnd = Selection.getSelectionEnd(content);
+            return true;
+        }
+        return false;
+    }
+    
+    boolean reportExtractedText() {
+        final InputMethodState ims = mInputMethodState;
+        if (ims != null && ims.mContentChanged) {
+            ims.mContentChanged = false;
+            final ExtractedTextRequest req = mInputMethodState.mExtracting;
+            if (req != null) {
+                InputMethodManager imm = InputMethodManager.peekInstance();
+                if (imm != null) {
+                    if (DEBUG_EXTRACT) Log.v(TAG, "Retrieving extracted start="
+                            + ims.mChangedStart + " end=" + ims.mChangedEnd
+                            + " delta=" + ims.mChangedDelta);
+                    if (extractTextInternal(req, ims.mChangedStart, ims.mChangedEnd,
+                            ims.mChangedDelta, ims.mTmpExtracted)) {
+                        if (DEBUG_EXTRACT) Log.v(TAG, "Reporting extracted start="
+                                + ims.mTmpExtracted.partialStartOffset
+                                + " end=" + ims.mTmpExtracted.partialEndOffset
+                                + ": " + ims.mTmpExtracted.text);
+                        imm.updateExtractedText(this, req.token,
+                                mInputMethodState.mTmpExtracted);
+                        return true;
+                    }
+                }
+            }
+        }
+        return false;
+    }
+    
+    /**
+     * This is used to remove all style-impacting spans from text before new
+     * extracted text is being replaced into it, so that we don't have any
+     * lingering spans applied during the replace.
+     */
+    static void removeParcelableSpans(Spannable spannable, int start, int end) {
+        Object[] spans = spannable.getSpans(start, end, ParcelableSpan.class);
+        int i = spans.length;
+        while (i > 0) {
+            i--;
+            spannable.removeSpan(spans[i]);
+        }
+    }
+    
+    /**
+     * Apply to this text view the given extracted text, as previously
+     * returned by {@link #extractText(ExtractedTextRequest, ExtractedText)}.
+     */
+    public void setExtractedText(ExtractedText text) {
+        Editable content = getEditableText();
+        if (content == null) {
+            setText(text.text, TextView.BufferType.EDITABLE);
+        } else if (text.partialStartOffset < 0) {
+            removeParcelableSpans(content, 0, content.length());
+            content.replace(0, content.length(), text.text);
+        } else {
+            final int N = content.length();
+            int start = text.partialStartOffset;
+            if (start > N) start = N;
+            int end = text.partialEndOffset;
+            if (end > N) end = N;
+            removeParcelableSpans(content, start, end);
+            content.replace(start, end, text.text);
+        }
+        
+        // Now set the selection position...  make sure it is in range, to
+        // avoid crashes.  If this is a partial update, it is possible that
+        // the underlying text may have changed, causing us problems here.
+        // Also we just don't want to trust clients to do the right thing.
+        Spannable sp = (Spannable)getText();
+        final int N = sp.length();
+        int start = text.selectionStart;
+        if (start < 0) start = 0;
+        else if (start > N) start = N;
+        int end = text.selectionEnd;
+        if (end < 0) end = 0;
+        else if (end > N) end = N;
+        Selection.setSelection(sp, start, end);
+    }
+    
+    /**
+     * @hide
+     */
+    public void setExtracting(ExtractedTextRequest req) {
+        if (mInputMethodState != null) {
+            mInputMethodState.mExtracting = req;
+        }
+    }
+    
+    /**
+     * Called by the framework in response to a text completion from
+     * the current input method, provided by it calling
+     * {@link InputConnection#commitCompletion
+     * InputConnection.commitCompletion()}.  The default implementation does
+     * nothing; text views that are supporting auto-completion should override
+     * this to do their desired behavior.
+     *
+     * @param text The auto complete text the user has selected.
+     */
+    public void onCommitCompletion(CompletionInfo text) {
+    }
+
+    public void beginBatchEdit() {
+        final InputMethodState ims = mInputMethodState;
+        if (ims != null) {
+            int nesting = ++ims.mBatchEditNesting;
+            if (nesting == 1) {
+                ims.mCursorChanged = false;
+                ims.mChangedDelta = 0;
+                if (ims.mContentChanged) {
+                    // We already have a pending change from somewhere else,
+                    // so turn this into a full update.
+                    ims.mChangedStart = 0;
+                    ims.mChangedEnd = mText.length();
+                } else {
+                    ims.mChangedStart = -1;
+                    ims.mChangedEnd = -1;
+                    ims.mContentChanged = false;
+                }
+                onBeginBatchEdit();
+            }
+        }
+    }
+    
+    public void endBatchEdit() {
+        final InputMethodState ims = mInputMethodState;
+        if (ims != null) {
+            int nesting = --ims.mBatchEditNesting;
+            if (nesting == 0) {
+                finishBatchEdit(ims);
+            }
+        }
+    }
+    
+    void ensureEndedBatchEdit() {
+        final InputMethodState ims = mInputMethodState;
+        if (ims != null && ims.mBatchEditNesting != 0) {
+            ims.mBatchEditNesting = 0;
+            finishBatchEdit(ims);
+        }
+    }
+    
+    void finishBatchEdit(final InputMethodState ims) {
+        onEndBatchEdit();
+        
+        if (ims.mContentChanged) {
+            updateAfterEdit();
+            reportExtractedText();
+        } else if (ims.mCursorChanged) {
+            // Cheezy way to get us to report the current cursor location.
+            invalidateCursor();
+        }
+    }
+    
+    void updateAfterEdit() {
+        invalidate();
+        int curs = Selection.getSelectionStart(mText);
+
+        if (curs >= 0 || (mGravity & Gravity.VERTICAL_GRAVITY_MASK) ==
+                             Gravity.BOTTOM) {
+            registerForPreDraw();
+        }
+
+        if (curs >= 0) {
+            mHighlightPathBogus = true;
+
+            if (isFocused()) {
+                mShowCursor = SystemClock.uptimeMillis();
+                makeBlink();
+            }
+        }
+        
+        checkForResize();
+    }
+    
+    /**
+     * Called by the framework in response to a request to begin a batch
+     * of edit operations through a call to link {@link #beginBatchEdit()}.
+     */
+    public void onBeginBatchEdit() {
+    }
+    
+    /**
+     * Called by the framework in response to a request to end a batch
+     * of edit operations through a call to link {@link #endBatchEdit}.
+     */
+    public void onEndBatchEdit() {
+    }
+    
+    /**
+     * Called by the framework in response to a private command from the
+     * current method, provided by it calling
+     * {@link InputConnection#performPrivateCommand
+     * InputConnection.performPrivateCommand()}.
+     *
+     * @param action The action name of the command.
+     * @param data Any additional data for the command.  This may be null.
+     * @return Return true if you handled the command, else false.
+     */
+    public boolean onPrivateIMECommand(String action, Bundle data) {
+        return false;
+    }
+
+    private void nullLayouts() {
+        if (mLayout instanceof BoringLayout && mSavedLayout == null) {
+            mSavedLayout = (BoringLayout) mLayout;
+        }
+        if (mHintLayout instanceof BoringLayout && mSavedHintLayout == null) {
+            mSavedHintLayout = (BoringLayout) mHintLayout;
+        }
+
+        mLayout = mHintLayout = null;
+    }
+
+    /**
+     * Make a new Layout based on the already-measured size of the view,
+     * on the assumption that it was measured correctly at some point.
+     */
+    private void assumeLayout() {
+        int width = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
+
+        if (width < 1) {
+            width = 0;
+        }
+
+        int physicalWidth = width;
+
+        if (mHorizontallyScrolling) {
+            width = VERY_WIDE;
+        }
+
+        makeNewLayout(width, physicalWidth, UNKNOWN_BORING, UNKNOWN_BORING,
+                      physicalWidth, false);
+    }
+
+    /**
+     * The width passed in is now the desired layout width,
+     * not the full view width with padding.
+     * {@hide}
+     */
+    protected void makeNewLayout(int w, int hintWidth,
+                                 BoringLayout.Metrics boring,
+                                 BoringLayout.Metrics hintBoring,
+                                 int ellipsisWidth, boolean bringIntoView) {
+        stopMarquee();
+
+        mHighlightPathBogus = true;
+
+        if (w < 0) {
+            w = 0;
+        }
+        if (hintWidth < 0) {
+            hintWidth = 0;
+        }
+
+        Layout.Alignment alignment;
+        switch (mGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
+            case Gravity.CENTER_HORIZONTAL:
+                alignment = Layout.Alignment.ALIGN_CENTER;
+                break;
+
+            case Gravity.RIGHT:
+                alignment = Layout.Alignment.ALIGN_OPPOSITE;
+                break;
+
+            default:
+                alignment = Layout.Alignment.ALIGN_NORMAL;
+        }
+
+        if (mText instanceof Spannable) {
+            mLayout = new DynamicLayout(mText, mTransformed, mTextPaint, w,
+                    alignment, mSpacingMult,
+                    mSpacingAdd, mIncludePad, mEllipsize,
+                    ellipsisWidth);
+        } else {
+            if (boring == UNKNOWN_BORING) {
+                boring = BoringLayout.isBoring(mTransformed, mTextPaint,
+                                               mBoring);
+                if (boring != null) {
+                    mBoring = boring;
+                }
+            }
+
+            if (boring != null) {
+                if (boring.width <= w &&
+                    (mEllipsize == null || boring.width <= ellipsisWidth)) {
+                    if (mSavedLayout != null) {
+                        mLayout = mSavedLayout.
+                                replaceOrMake(mTransformed, mTextPaint,
+                                w, alignment, mSpacingMult, mSpacingAdd,
+                                boring, mIncludePad);
+                    } else {
+                        mLayout = BoringLayout.make(mTransformed, mTextPaint,
+                                w, alignment, mSpacingMult, mSpacingAdd,
+                                boring, mIncludePad);
+                    }
+                    // Log.e("aaa", "Boring: " + mTransformed);
+
+                    mSavedLayout = (BoringLayout) mLayout;
+                } else if (mEllipsize != null && boring.width <= w) {
+                    if (mSavedLayout != null) {
+                        mLayout = mSavedLayout.
+                                replaceOrMake(mTransformed, mTextPaint,
+                                w, alignment, mSpacingMult, mSpacingAdd,
+                                boring, mIncludePad, mEllipsize,
+                                ellipsisWidth);
+                    } else {
+                        mLayout = BoringLayout.make(mTransformed, mTextPaint,
+                                w, alignment, mSpacingMult, mSpacingAdd,
+                                boring, mIncludePad, mEllipsize,
+                                ellipsisWidth);
+                    }
+                } else if (mEllipsize != null) {
+                    mLayout = new StaticLayout(mTransformed,
+                                0, mTransformed.length(),
+                                mTextPaint, w, alignment, mSpacingMult,
+                                mSpacingAdd, mIncludePad, mEllipsize,
+                                ellipsisWidth);
+                } else {
+                    mLayout = new StaticLayout(mTransformed, mTextPaint,
+                            w, alignment, mSpacingMult, mSpacingAdd,
+                            mIncludePad);
+                    // Log.e("aaa", "Boring but wide: " + mTransformed);
+                }
+            } else if (mEllipsize != null) {
+                mLayout = new StaticLayout(mTransformed,
+                            0, mTransformed.length(),
+                            mTextPaint, w, alignment, mSpacingMult,
+                            mSpacingAdd, mIncludePad, mEllipsize,
+                            ellipsisWidth);
+            } else {
+                mLayout = new StaticLayout(mTransformed, mTextPaint,
+                        w, alignment, mSpacingMult, mSpacingAdd,
+                        mIncludePad);
+            }
+        }
+
+        mHintLayout = null;
+
+        if (mHint != null) {
+            if (hintBoring == UNKNOWN_BORING) {
+                hintBoring = BoringLayout.isBoring(mHint, mTextPaint,
+                                                   mHintBoring);
+                if (hintBoring != null) {
+                    mHintBoring = hintBoring;
+                }
+            }
+
+            if (hintBoring != null) {
+                if (hintBoring.width <= hintWidth) {
+                    if (mSavedHintLayout != null) {
+                        mHintLayout = mSavedHintLayout.
+                                replaceOrMake(mHint, mTextPaint,
+                                hintWidth, alignment, mSpacingMult,
+                                mSpacingAdd, hintBoring, mIncludePad);
+                    } else {
+                        mHintLayout = BoringLayout.make(mHint, mTextPaint,
+                                hintWidth, alignment, mSpacingMult,
+                                mSpacingAdd, hintBoring, mIncludePad);
+                    }
+
+                    mSavedHintLayout = (BoringLayout) mHintLayout;
+                } else {
+                    mHintLayout = new StaticLayout(mHint, mTextPaint,
+                            hintWidth, alignment, mSpacingMult, mSpacingAdd,
+                            mIncludePad);
+                }
+            } else {
+                mHintLayout = new StaticLayout(mHint, mTextPaint,
+                        hintWidth, alignment, mSpacingMult, mSpacingAdd,
+                        mIncludePad);
+            }
+        }
+
+        if (bringIntoView) {
+            registerForPreDraw();
+        }
+
+        if (mEllipsize == TextUtils.TruncateAt.MARQUEE) {
+            final int height = mLayoutParams.height;
+            // If the size of the view does not depend on the size of the text, try to
+            // start the marquee immediately
+            if (height != LayoutParams.WRAP_CONTENT && height != LayoutParams.FILL_PARENT) {
+                startMarquee();
+            } else {
+                // Defer the start of the marquee until we know our width (see setFrame())
+                mRestartMarquee = true;
+            }
+        }
+    }
+
+    private static int desired(Layout layout) {
+        int n = layout.getLineCount();
+        CharSequence text = layout.getText();
+        float max = 0;
+
+        // if any line was wrapped, we can't use it.
+        // but it's ok for the last line not to have a newline
+
+        for (int i = 0; i < n - 1; i++) {
+            if (text.charAt(layout.getLineEnd(i) - 1) != '\n')
+                return -1;
+        }
+
+        for (int i = 0; i < n; i++) {
+            max = Math.max(max, layout.getLineWidth(i));
+        }
+
+        return (int) FloatMath.ceil(max);
+    }
+
+    /**
+     * Set whether the TextView includes extra top and bottom padding to make
+     * room for accents that go above the normal ascent and descent.
+     * The default is true.
+     *
+     * @attr ref android.R.styleable#TextView_includeFontPadding
+     */
+    public void setIncludeFontPadding(boolean includepad) {
+        mIncludePad = includepad;
+
+        if (mLayout != null) {
+            nullLayouts();
+            requestLayout();
+            invalidate();
+        }
+    }
+
+    private static final BoringLayout.Metrics UNKNOWN_BORING =
+                                                new BoringLayout.Metrics();
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
+        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
+        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
+        int heightSize = MeasureSpec.getSize(heightMeasureSpec);
+
+        int width;
+        int height;
+
+        BoringLayout.Metrics boring = UNKNOWN_BORING;
+        BoringLayout.Metrics hintBoring = UNKNOWN_BORING;
+
+        int des = -1;
+        boolean fromexisting = false;
+
+        if (widthMode == MeasureSpec.EXACTLY) {
+            // Parent has told us how big to be. So be it.
+            width = widthSize;
+        } else {
+            if (mLayout != null && mEllipsize == null) {
+                des = desired(mLayout);
+            }
+
+            if (des < 0) {
+                boring = BoringLayout.isBoring(mTransformed, mTextPaint,
+                                               mBoring);
+                if (boring != null) {
+                    mBoring = boring;
+                }
+            } else {
+                fromexisting = true;
+            }
+
+            if (boring == null || boring == UNKNOWN_BORING) {
+                if (des < 0) {
+                    des = (int) FloatMath.ceil(Layout.
+                                    getDesiredWidth(mTransformed, mTextPaint));
+                }
+
+                width = des;
+            } else {
+                width = boring.width;
+            }
+
+            final Drawables dr = mDrawables;
+            if (dr != null) {
+                width = Math.max(width, dr.mDrawableWidthTop);
+                width = Math.max(width, dr.mDrawableWidthBottom);
+            }
+
+            if (mHint != null) {
+                int hintDes = -1;
+                int hintWidth;
+
+                if (mHintLayout != null) {
+                    hintDes = desired(mHintLayout);
+                }
+
+                if (hintDes < 0) {
+                    hintBoring = BoringLayout.isBoring(mHint, mTextPaint,
+                                                       mHintBoring);
+                    if (hintBoring != null) {
+                        mHintBoring = hintBoring;
+                    }
+                }
+
+                if (hintBoring == null || hintBoring == UNKNOWN_BORING) {
+                    if (hintDes < 0) {
+                        hintDes = (int) FloatMath.ceil(Layout.
+                                        getDesiredWidth(mHint, mTextPaint));
+                    }
+
+                    hintWidth = hintDes;
+                } else {
+                    hintWidth = hintBoring.width;
+                }
+
+                if (hintWidth > width) {
+                    width = hintWidth;
+                }
+            }
+
+            width += getCompoundPaddingLeft() + getCompoundPaddingRight();
+
+            if (mMaxWidthMode == EMS) {
+                width = Math.min(width, mMaxWidth * getLineHeight());
+            } else {
+                width = Math.min(width, mMaxWidth);
+            }
+
+            if (mMinWidthMode == EMS) {
+                width = Math.max(width, mMinWidth * getLineHeight());
+            } else {
+                width = Math.max(width, mMinWidth);
+            }
+
+            // Check against our minimum width
+            width = Math.max(width, getSuggestedMinimumWidth());
+
+            if (widthMode == MeasureSpec.AT_MOST) {
+                width = Math.min(widthSize, width);
+            }
+        }
+
+        int want = width - getCompoundPaddingLeft() - getCompoundPaddingRight();
+        int unpaddedWidth = want;
+        int hintWant = want;
+
+        if (mHorizontallyScrolling)
+            want = VERY_WIDE;
+
+        int hintWidth = mHintLayout == null ? hintWant : mHintLayout.getWidth();
+
+        if (mLayout == null) {
+            makeNewLayout(want, hintWant, boring, hintBoring,
+                          width - getCompoundPaddingLeft() - getCompoundPaddingRight(),
+                          false);
+        } else if ((mLayout.getWidth() != want) || (hintWidth != hintWant) ||
+                   (mLayout.getEllipsizedWidth() !=
+                        width - getCompoundPaddingLeft() - getCompoundPaddingRight())) {
+            if (mHint == null && mEllipsize == null &&
+                    want > mLayout.getWidth() &&
+                    (mLayout instanceof BoringLayout ||
+                        (fromexisting && des >= 0 && des <= want))) {
+                mLayout.increaseWidthTo(want);
+            } else {
+                makeNewLayout(want, hintWant, boring, hintBoring,
+                              width - getCompoundPaddingLeft() - getCompoundPaddingRight(),
+                              false);
+            }
+        } else {
+            // Width has not changed.
+        }
+
+        if (heightMode == MeasureSpec.EXACTLY) {
+            // Parent has told us how big to be. So be it.
+            height = heightSize;
+            mDesiredHeightAtMeasure = -1;
+        } else {
+            int desired = getDesiredHeight();
+
+            height = desired;
+            mDesiredHeightAtMeasure = desired;
+
+            if (heightMode == MeasureSpec.AT_MOST) {
+                height = Math.min(desired, height);
+            }
+        }
+
+        int unpaddedHeight = height - getCompoundPaddingTop() -
+                                getCompoundPaddingBottom();
+        if (mMaxMode == LINES && mLayout.getLineCount() > mMaximum) {
+            unpaddedHeight = Math.min(unpaddedHeight,
+                                      mLayout.getLineTop(mMaximum));
+        }
+
+        /*
+         * We didn't let makeNewLayout() register to bring the cursor into view,
+         * so do it here if there is any possibility that it is needed.
+         */
+        if (mMovement != null ||
+            mLayout.getWidth() > unpaddedWidth ||
+            mLayout.getHeight() > unpaddedHeight) {
+            registerForPreDraw();
+        } else {
+            scrollTo(0, 0);
+        }
+
+        setMeasuredDimension(width, height);
+    }
+
+    private int getDesiredHeight() {
+        return Math.max(getDesiredHeight(mLayout, true),
+                        getDesiredHeight(mHintLayout, false));
+    }
+
+    private int getDesiredHeight(Layout layout, boolean cap) {
+        if (layout == null) {
+            return 0;
+        }
+
+        int linecount = layout.getLineCount();
+        int pad = getCompoundPaddingTop() + getCompoundPaddingBottom();
+        int desired = layout.getLineTop(linecount);
+
+        final Drawables dr = mDrawables;
+        if (dr != null) {
+            desired = Math.max(desired, dr.mDrawableHeightLeft);
+            desired = Math.max(desired, dr.mDrawableHeightRight);
+        }
+
+        desired += pad;
+
+        if (mMaxMode == LINES) {
+            /*
+             * Don't cap the hint to a certain number of lines.
+             * (Do cap it, though, if we have a maximum pixel height.)
+             */
+            if (cap) {
+                if (linecount > mMaximum) {
+                    desired = layout.getLineTop(mMaximum) +
+                              layout.getBottomPadding();
+
+                    if (dr != null) {
+                        desired = Math.max(desired, dr.mDrawableHeightLeft);
+                        desired = Math.max(desired, dr.mDrawableHeightRight);
+                    }
+
+                    desired += pad;
+                    linecount = mMaximum;
+                }
+            }
+        } else {
+            desired = Math.min(desired, mMaximum);
+        }
+
+        if (mMinMode == LINES) {
+            if (linecount < mMinimum) {
+                desired += getLineHeight() * (mMinimum - linecount);
+            }
+        } else {
+            desired = Math.max(desired, mMinimum);
+        }
+
+        // Check against our minimum height
+        desired = Math.max(desired, getSuggestedMinimumHeight());
+
+        return desired;
+    }
+
+    /**
+     * Check whether a change to the existing text layout requires a
+     * new view layout.
+     */
+    private void checkForResize() {
+        boolean sizeChanged = false;
+
+        if (mLayout != null) {
+            // Check if our width changed
+            if (mLayoutParams.width == LayoutParams.WRAP_CONTENT) {
+                sizeChanged = true;
+                invalidate();
+            }
+
+            // Check if our height changed
+            if (mLayoutParams.height == LayoutParams.WRAP_CONTENT) {
+                int desiredHeight = getDesiredHeight();
+
+                if (desiredHeight != this.getHeight()) {
+                    sizeChanged = true;
+                }
+            } else if (mLayoutParams.height == LayoutParams.FILL_PARENT) {
+                if (mDesiredHeightAtMeasure >= 0) {
+                    int desiredHeight = getDesiredHeight();
+
+                    if (desiredHeight != mDesiredHeightAtMeasure) {
+                        sizeChanged = true;
+                    }
+                }
+            }
+        }
+
+        if (sizeChanged) {
+            requestLayout();
+            // caller will have already invalidated
+        }
+    }
+
+    /**
+     * Check whether entirely new text requires a new view layout
+     * or merely a new text layout.
+     */
+    private void checkForRelayout() {
+        // If we have a fixed width, we can just swap in a new text layout
+        // if the text height stays the same or if the view height is fixed.
+
+        if ((mLayoutParams.width != LayoutParams.WRAP_CONTENT ||
+                (mMaxWidthMode == mMinWidthMode && mMaxWidth == mMinWidth)) &&
+                (mHint == null || mHintLayout != null) &&
+                (mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight() > 0)) {
+            // Static width, so try making a new text layout.
+
+            int oldht = mLayout.getHeight();
+            int want = mLayout.getWidth();
+            int hintWant = mHintLayout == null ? 0 : mHintLayout.getWidth();
+
+            /*
+             * No need to bring the text into view, since the size is not
+             * changing (unless we do the requestLayout(), in which case it
+             * will happen at measure).
+             */
+            makeNewLayout(want, hintWant, UNKNOWN_BORING, UNKNOWN_BORING,
+                          mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(), false);
+
+            // In a fixed-height view, so use our new text layout.
+            if (mLayoutParams.height != LayoutParams.WRAP_CONTENT &&
+                mLayoutParams.height != LayoutParams.FILL_PARENT) {
+                invalidate();
+                return;
+            }
+
+            // Dynamic height, but height has stayed the same,
+            // so use our new text layout.
+            if (mLayout.getHeight() == oldht &&
+                (mHintLayout == null || mHintLayout.getHeight() == oldht)) {
+                invalidate();
+                return;
+            }
+
+            // We lose: the height has changed and we have a dynamic height.
+            // Request a new view layout using our new text layout.
+            requestLayout();
+            invalidate();
+        } else {
+            // Dynamic width, so we have no choice but to request a new
+            // view layout with a new text layout.
+
+            nullLayouts();
+            requestLayout();
+            invalidate();
+        }
+    }
+
+    /**
+     * Returns true if anything changed.
+     */
+    private boolean bringTextIntoView() {
+        int line = 0;
+        if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) {
+            line = mLayout.getLineCount() - 1;
+        }
+
+        Layout.Alignment a = mLayout.getParagraphAlignment(line);
+        int dir = mLayout.getParagraphDirection(line);
+        int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
+        int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom();
+        int ht = mLayout.getHeight();
+
+        int scrollx, scrolly;
+
+        if (a == Layout.Alignment.ALIGN_CENTER) {
+            /*
+             * Keep centered if possible, or, if it is too wide to fit,
+             * keep leading edge in view.
+             */
+
+            int left = (int) FloatMath.floor(mLayout.getLineLeft(line));
+            int right = (int) FloatMath.ceil(mLayout.getLineRight(line));
+
+            if (right - left < hspace) {
+                scrollx = (right + left) / 2 - hspace / 2;
+            } else {
+                if (dir < 0) {
+                    scrollx = right - hspace;
+                } else {
+                    scrollx = left;
+                }
+            }
+        } else if (a == Layout.Alignment.ALIGN_NORMAL) {
+            /*
+             * Keep leading edge in view.
+             */
+
+            if (dir < 0) {
+                int right = (int) FloatMath.ceil(mLayout.getLineRight(line));
+                scrollx = right - hspace;
+            } else {
+                scrollx = (int) FloatMath.floor(mLayout.getLineLeft(line));
+            }
+        } else /* a == Layout.Alignment.ALIGN_OPPOSITE */ {
+            /*
+             * Keep trailing edge in view.
+             */
+
+            if (dir < 0) {
+                scrollx = (int) FloatMath.floor(mLayout.getLineLeft(line));
+            } else {
+                int right = (int) FloatMath.ceil(mLayout.getLineRight(line));
+                scrollx = right - hspace;
+            }
+        }
+
+        if (ht < vspace) {
+            scrolly = 0;
+        } else {
+            if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) {
+                scrolly = ht - vspace;
+            } else {
+                scrolly = 0;
+            }
+        }
+
+        if (scrollx != mScrollX || scrolly != mScrollY) {
+            scrollTo(scrollx, scrolly);
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    /**
+     * Move the point, specified by the offset, into the view if it is needed.
+     * This has to be called after layout. Returns true if anything changed.
+     */
+    public boolean bringPointIntoView(int offset) {
+        boolean changed = false;
+
+        int line = mLayout.getLineForOffset(offset);
+
+        // FIXME: Is it okay to truncate this, or should we round?
+        final int x = (int)mLayout.getPrimaryHorizontal(offset);
+        final int top = mLayout.getLineTop(line);
+        final int bottom = mLayout.getLineTop(line+1);
+
+        int left = (int) FloatMath.floor(mLayout.getLineLeft(line));
+        int right = (int) FloatMath.ceil(mLayout.getLineRight(line));
+        int ht = mLayout.getHeight();
+
+        int grav;
+
+        switch (mLayout.getParagraphAlignment(line)) {
+            case ALIGN_NORMAL:
+                grav = 1;
+                break;
+
+            case ALIGN_OPPOSITE:
+                grav = -1;
+                break;
+
+            default:
+                grav = 0;
+        }
+
+        grav *= mLayout.getParagraphDirection(line);
+
+        int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
+        int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom();
+
+        int hslack = (bottom - top) / 2;
+        int vslack = hslack;
+
+        if (vslack > vspace / 4)
+            vslack = vspace / 4;
+        if (hslack > hspace / 4)
+            hslack = hspace / 4;
+
+        int hs = mScrollX;
+        int vs = mScrollY;
+
+        if (top - vs < vslack)
+            vs = top - vslack;
+        if (bottom - vs > vspace - vslack)
+            vs = bottom - (vspace - vslack);
+        if (ht - vs < vspace)
+            vs = ht - vspace;
+        if (0 - vs > 0)
+            vs = 0;
+
+        if (grav != 0) {
+            if (x - hs < hslack) {
+                hs = x - hslack;
+            }
+            if (x - hs > hspace - hslack) {
+                hs = x - (hspace - hslack);
+            }
+        }
+
+        if (grav < 0) {
+            if (left - hs > 0)
+                hs = left;
+            if (right - hs < hspace)
+                hs = right - hspace;
+        } else if (grav > 0) {
+            if (right - hs < hspace)
+                hs = right - hspace;
+            if (left - hs > 0)
+                hs = left;
+        } else /* grav == 0 */ {
+            if (right - left <= hspace) {
+                /*
+                 * If the entire text fits, center it exactly.
+                 */
+                hs = left - (hspace - (right - left)) / 2;
+            } else if (x > right - hslack) {
+                /*
+                 * If we are near the right edge, keep the right edge
+                 * at the edge of the view.
+                 */
+                hs = right - hspace;
+            } else if (x < left + hslack) {
+                /*
+                 * If we are near the left edge, keep the left edge
+                 * at the edge of the view.
+                 */
+                hs = left;
+            } else if (left > hs) {
+                /*
+                 * Is there whitespace visible at the left?  Fix it if so.
+                 */
+                hs = left;
+            } else if (right < hs + hspace) {
+                /*
+                 * Is there whitespace visible at the right?  Fix it if so.
+                 */
+                hs = right - hspace;
+            } else {
+                /*
+                 * Otherwise, float as needed.
+                 */
+                if (x - hs < hslack) {
+                    hs = x - hslack;
+                }
+                if (x - hs > hspace - hslack) {
+                    hs = x - (hspace - hslack);
+                }
+            }
+        }
+
+        if (hs != mScrollX || vs != mScrollY) {
+            if (mScroller == null) {
+                scrollTo(hs, vs);
+            } else {
+                long duration = AnimationUtils.currentAnimationTimeMillis() - mLastScroll;
+                int dx = hs - mScrollX;
+                int dy = vs - mScrollY;
+
+                if (duration > ANIMATED_SCROLL_GAP) {
+                    mScroller.startScroll(mScrollX, mScrollY, dx, dy);
+                    invalidate();
+                } else {
+                    if (!mScroller.isFinished()) {
+                        mScroller.abortAnimation();
+                    }
+
+                    scrollBy(dx, dy);
+                }
+
+                mLastScroll = AnimationUtils.currentAnimationTimeMillis();
+            }
+
+            changed = true;
+        }
+
+        if (isFocused()) {
+            // This offsets because getInterestingRect() is in terms of
+            // viewport coordinates, but requestRectangleOnScreen()
+            // is in terms of content coordinates.
+
+            Rect r = new Rect();
+            getInterestingRect(r, x, top, bottom, line);
+            r.offset(mScrollX, mScrollY);
+
+            if (requestRectangleOnScreen(r)) {
+                changed = true;
+            }
+        }
+
+        return changed;
+    }
+
+    @Override
+    public void computeScroll() {
+        if (mScroller != null) {
+            if (mScroller.computeScrollOffset()) {
+                mScrollX = mScroller.getCurrX();
+                mScrollY = mScroller.getCurrY();
+                postInvalidate();  // So we draw again
+            }
+        }
+    }
+
+    private void getInterestingRect(Rect r, int h, int top, int bottom,
+                                    int line) {
+        int paddingTop = getExtendedPaddingTop();
+        if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
+            paddingTop += getVerticalOffset(false);
+        }
+        top += paddingTop;
+        bottom += paddingTop;
+        h += getCompoundPaddingLeft();
+
+        if (line == 0)
+            top -= getExtendedPaddingTop();
+        if (line == mLayout.getLineCount() - 1)
+            bottom += getExtendedPaddingBottom();
+
+        r.set(h, top, h+1, bottom);
+        r.offset(-mScrollX, -mScrollY);
+    }
+
+    @Override
+    public void debug(int depth) {
+        super.debug(depth);
+
+        String output = debugIndent(depth);
+        output += "frame={" + mLeft + ", " + mTop + ", " + mRight
+                + ", " + mBottom + "} scroll={" + mScrollX + ", " + mScrollY
+                + "} ";
+
+        if (mText != null) {
+
+            output += "mText=\"" + mText + "\" ";
+            if (mLayout != null) {
+                output += "mLayout width=" + mLayout.getWidth()
+                        + " height=" + mLayout.getHeight();
+            }
+        } else {
+            output += "mText=NULL";
+        }
+        Log.d(VIEW_LOG_TAG, output);
+    }
+
+    /**
+     * Convenience for {@link Selection#getSelectionStart}.
+     */
+    public int getSelectionStart() {
+        return Selection.getSelectionStart(getText());
+    }
+
+    /**
+     * Convenience for {@link Selection#getSelectionEnd}.
+     */
+    public int getSelectionEnd() {
+        return Selection.getSelectionEnd(getText());
+    }
+
+    /**
+     * Return true iff there is a selection inside this text view.
+     */
+    public boolean hasSelection() {
+        return getSelectionStart() != getSelectionEnd();
+    }
+
+    /**
+     * Sets the properties of this field (lines, horizontally scrolling,
+     * transformation method) to be for a single-line input.
+     *
+     * @attr ref android.R.styleable#TextView_singleLine
+     */
+    public void setSingleLine() {
+        setSingleLine(true);
+    }
+
+    /**
+     * If true, sets the properties of this field (lines, horizontally
+     * scrolling, transformation method) to be for a single-line input;
+     * if false, restores these to the default conditions.
+     * Note that calling this with false restores default conditions,
+     * not necessarily those that were in effect prior to calling
+     * it with true.
+     *
+     * @attr ref android.R.styleable#TextView_singleLine
+     */
+    @android.view.RemotableViewMethod
+    public void setSingleLine(boolean singleLine) {
+        if ((mInputType&EditorInfo.TYPE_MASK_CLASS)
+                == EditorInfo.TYPE_CLASS_TEXT) {
+            if (singleLine) {
+                mInputType &= ~EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE;
+            } else {
+                mInputType |= EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE;
+            }
+        }
+        applySingleLine(singleLine, true);
+    }
+
+    private void applySingleLine(boolean singleLine, boolean applyTransformation) {
+        mSingleLine = singleLine;
+        if (singleLine) {
+            setLines(1);
+            setHorizontallyScrolling(true);
+            if (applyTransformation) {
+                setTransformationMethod(SingleLineTransformationMethod.
+                                        getInstance());
+            }
+        } else {
+            setMaxLines(Integer.MAX_VALUE);
+            setHorizontallyScrolling(false);
+            if (applyTransformation) {
+                setTransformationMethod(null);
+            }
+        }
+    }
+    
+    /**
+     * Causes words in the text that are longer than the view is wide
+     * to be ellipsized instead of broken in the middle.  You may also
+     * want to {@link #setSingleLine} or {@link #setHorizontallyScrolling}
+     * to constrain the text toa single line.  Use <code>null</code>
+     * to turn off ellipsizing.
+     *
+     * @attr ref android.R.styleable#TextView_ellipsize
+     */
+    public void setEllipsize(TextUtils.TruncateAt where) {
+        mEllipsize = where;
+
+        if (mLayout != null) {
+            nullLayouts();
+            requestLayout();
+            invalidate();
+        }
+    }
+
+    /**
+     * Sets how many times to repeat the marquee animation. Only applied if the
+     * TextView has marquee enabled. Set to -1 to repeat indefinitely.
+     *
+     * @attr ref android.R.styleable#TextView_marqueeRepeatLimit
+     */
+    public void setMarqueeRepeatLimit(int marqueeLimit) {
+        mMarqueeRepeatLimit = marqueeLimit;
+    }
+
+    /**
+     * Returns where, if anywhere, words that are longer than the view
+     * is wide should be ellipsized.
+     */
+    @ViewDebug.ExportedProperty
+    public TextUtils.TruncateAt getEllipsize() {
+        return mEllipsize;
+    }
+
+    /**
+     * Set the TextView so that when it takes focus, all the text is
+     * selected.
+     *
+     * @attr ref android.R.styleable#TextView_selectAllOnFocus
+     */
+    @android.view.RemotableViewMethod
+    public void setSelectAllOnFocus(boolean selectAllOnFocus) {
+        mSelectAllOnFocus = selectAllOnFocus;
+
+        if (selectAllOnFocus && !(mText instanceof Spannable)) {
+            setText(mText, BufferType.SPANNABLE);
+        }
+    }
+
+    /**
+     * Set whether the cursor is visible.  The default is true.
+     *
+     * @attr ref android.R.styleable#TextView_cursorVisible
+     */
+    @android.view.RemotableViewMethod
+    public void setCursorVisible(boolean visible) {
+        mCursorVisible = visible;
+        invalidate();
+
+        if (visible) {
+            makeBlink();
+        } else if (mBlink != null) {
+            mBlink.removeCallbacks(mBlink);
+        }
+    }
+
+    private boolean canMarquee() {
+        int width = (mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight());
+        return width > 0 && mLayout.getLineWidth(0) > width;
+    }
+
+    private void startMarquee() {
+        if ((mMarquee == null || mMarquee.isStopped()) && (isFocused() || isSelected()) &&
+                getLineCount() == 1 && canMarquee()) {
+            if (mMarquee == null) mMarquee = new Marquee(this);
+            mMarquee.start(mMarqueeRepeatLimit);
+        }
+    }
+
+    private void stopMarquee() {
+        if (mMarquee != null && !mMarquee.isStopped()) {
+            mMarquee.stop();
+        }
+    }
+
+    private void startStopMarquee(boolean start) {
+        if (mEllipsize == TextUtils.TruncateAt.MARQUEE) {
+            if (start) {
+                startMarquee();
+            } else {
+                stopMarquee();
+            }
+        }
+    }
+
+    private static final class Marquee extends Handler {
+        // TODO: Add an option to configure this
+        private static final int MARQUEE_DELAY = 1200;
+        private static final int MARQUEE_RESTART_DELAY = 1200;
+        private static final int MARQUEE_RESOLUTION = 1000 / 30;
+        private static final int MARQUEE_PIXELS_PER_SECOND = 30;
+
+        private static final byte MARQUEE_STOPPED = 0x0;
+        private static final byte MARQUEE_STARTING = 0x1;
+        private static final byte MARQUEE_RUNNING = 0x2;
+
+        private static final int MESSAGE_START = 0x1;
+        private static final int MESSAGE_TICK = 0x2;
+        private static final int MESSAGE_RESTART = 0x3;
+
+        private final WeakReference<TextView> mView;
+
+        private byte mStatus = MARQUEE_STOPPED;
+        private float mScrollUnit;
+        private float mMaxScroll;
+        private int mRepeatLimit;
+
+        float mScroll;
+
+        Marquee(TextView v) {
+            final float density = v.getContext().getResources().getDisplayMetrics().density;
+            mScrollUnit = (MARQUEE_PIXELS_PER_SECOND * density) / (float) MARQUEE_RESOLUTION;
+            mView = new WeakReference<TextView>(v);
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case MESSAGE_START:
+                    mStatus = MARQUEE_RUNNING;
+                    tick();
+                    break;
+                case MESSAGE_TICK:
+                    tick();
+                    break;
+                case MESSAGE_RESTART:
+                    if (mStatus == MARQUEE_RUNNING) {
+                        if (mRepeatLimit >= 0) {
+                            mRepeatLimit--;
+                        }
+                        start(mRepeatLimit);
+                    }
+                    break;
+            }
+        }
+
+        void tick() {
+            if (mStatus != MARQUEE_RUNNING) {
+                return;
+            }
+
+            removeMessages(MESSAGE_TICK);
+
+            final TextView textView = mView.get();
+            if (textView != null && (textView.isFocused() || textView.isSelected())) {
+                mScroll += mScrollUnit;
+                if (mScroll > mMaxScroll) {
+                    mScroll = mMaxScroll;
+                    sendEmptyMessageDelayed(MESSAGE_RESTART, MARQUEE_RESTART_DELAY);
+                } else {
+                    sendEmptyMessageDelayed(MESSAGE_TICK, MARQUEE_RESOLUTION);
+                }
+                textView.invalidate();
+            }
+        }
+
+        void stop() {
+            mStatus = MARQUEE_STOPPED;
+            removeMessages(MESSAGE_START);
+            removeMessages(MESSAGE_RESTART);
+            removeMessages(MESSAGE_TICK);
+            resetScroll();
+        }
+
+        private void resetScroll() {
+            mScroll = 0.0f;
+            final TextView textView = mView.get();
+            if (textView != null) textView.invalidate();
+        }
+
+        void start(int repeatLimit) {
+            if (repeatLimit == 0) {
+                stop();
+                return;
+            }
+            mRepeatLimit = repeatLimit;
+            final TextView textView = mView.get();
+            if (textView != null && textView.mLayout != null) {
+                mStatus = MARQUEE_STARTING;
+                mScroll = 0.0f;
+                mMaxScroll = textView.mLayout.getLineWidth(0) - (textView.getWidth() -
+                        textView.getCompoundPaddingLeft() - textView.getCompoundPaddingRight());
+                textView.invalidate();
+                sendEmptyMessageDelayed(MESSAGE_START, MARQUEE_DELAY);
+            }
+        }
+
+        boolean isRunning() {
+            return mStatus == MARQUEE_RUNNING;
+        }
+
+        boolean isStopped() {
+            return mStatus == MARQUEE_STOPPED;
+        }
+    }
+
+    /**
+     * This method is called when the text is changed, in case any
+     * subclasses would like to know.
+     *
+     * @param text The text the TextView is displaying.
+     * @param start The offset of the start of the range of the text
+     *              that was modified.
+     * @param before The offset of the former end of the range of the
+     *               text that was modified.  If text was simply inserted,
+     *               this will be the same as <code>start</code>.
+     *               If text was replaced with new text or deleted, the
+     *               length of the old text was <code>before-start</code>.
+     * @param after The offset of the end of the range of the text
+     *              that was modified.  If text was simply deleted,
+     *              this will be the same as <code>start</code>.
+     *              If text was replaced with new text or inserted,
+     *              the length of the new text is <code>after-start</code>.
+     */
+    protected void onTextChanged(CharSequence text,
+                                 int start, int before, int after) {
+    }
+
+    /**
+     * This method is called when the selection has changed, in case any
+     * subclasses would like to know.
+     * 
+     * @param selStart The new selection start location.
+     * @param selEnd The new selection end location.
+     */
+    protected void onSelectionChanged(int selStart, int selEnd) {
+    }
+    
+    /**
+     * Adds a TextWatcher to the list of those whose methods are called
+     * whenever this TextView's text changes.
+     * <p>
+     * In 1.0, the {@link TextWatcher#afterTextChanged} method was erroneously
+     * not called after {@link #setText} calls.  Now, doing {@link #setText}
+     * if there are any text changed listeners forces the buffer type to
+     * Editable if it would not otherwise be and does call this method.
+     */
+    public void addTextChangedListener(TextWatcher watcher) {
+        if (mListeners == null) {
+            mListeners = new ArrayList<TextWatcher>();
+        }
+
+        mListeners.add(watcher);
+    }
+
+    /**
+     * Removes the specified TextWatcher from the list of those whose
+     * methods are called
+     * whenever this TextView's text changes.
+     */
+    public void removeTextChangedListener(TextWatcher watcher) {
+        if (mListeners != null) {
+            int i = mListeners.indexOf(watcher);
+
+            if (i >= 0) {
+                mListeners.remove(i);
+            }
+        }
+    }
+
+    private void sendBeforeTextChanged(CharSequence text, int start, int before,
+                                   int after) {
+        if (mListeners != null) {
+            final ArrayList<TextWatcher> list = mListeners;
+            final int count = list.size();
+            for (int i = 0; i < count; i++) {
+                list.get(i).beforeTextChanged(text, start, before, after);
+            }
+        }
+    }
+
+    /**
+     * Not private so it can be called from an inner class without going
+     * through a thunk.
+     */
+    void sendOnTextChanged(CharSequence text, int start, int before,
+                                   int after) {
+        if (mListeners != null) {
+            final ArrayList<TextWatcher> list = mListeners;
+            final int count = list.size();
+            for (int i = 0; i < count; i++) {
+                list.get(i).onTextChanged(text, start, before, after);
+            }
+        }
+    }
+
+    /**
+     * Not private so it can be called from an inner class without going
+     * through a thunk.
+     */
+    void sendAfterTextChanged(Editable text) {
+        if (mListeners != null) {
+            final ArrayList<TextWatcher> list = mListeners;
+            final int count = list.size();
+            for (int i = 0; i < count; i++) {
+                list.get(i).afterTextChanged(text);
+            }
+        }
+    }
+
+    /**
+     * Not private so it can be called from an inner class without going
+     * through a thunk.
+     */
+    void handleTextChanged(CharSequence buffer, int start,
+            int before, int after) {
+        final InputMethodState ims = mInputMethodState;
+        if (ims == null || ims.mBatchEditNesting == 0) {
+            updateAfterEdit();
+        }
+        if (ims != null) {
+            ims.mContentChanged = true;
+            if (ims.mChangedStart < 0) {
+                ims.mChangedStart = start;
+                ims.mChangedEnd = start+before;
+            } else {
+                if (ims.mChangedStart > start) ims.mChangedStart = start;
+                if (ims.mChangedEnd < (start+before)) ims.mChangedEnd = start+before;
+            }
+            ims.mChangedDelta += after-before;
+        }
+        
+        sendOnTextChanged(buffer, start, before, after);
+        onTextChanged(buffer, start, before, after);
+    }
+    
+    /**
+     * Not private so it can be called from an inner class without going
+     * through a thunk.
+     */
+    void spanChange(Spanned buf, Object what, int oldStart, int newStart,
+            int oldEnd, int newEnd) {
+        // XXX Make the start and end move together if this ends up
+        // spending too much time invalidating.
+
+        boolean selChanged = false;
+        int newSelStart=-1, newSelEnd=-1;
+        
+        final InputMethodState ims = mInputMethodState;
+        
+        if (what == Selection.SELECTION_END) {
+            mHighlightPathBogus = true;
+            selChanged = true;
+            newSelEnd = newStart;
+
+            if (!isFocused()) {
+                mSelectionMoved = true;
+            }
+
+            if (oldStart >= 0 || newStart >= 0) {
+                invalidateCursor(Selection.getSelectionStart(buf), oldStart, newStart);
+                registerForPreDraw();
+
+                if (isFocused()) {
+                    mShowCursor = SystemClock.uptimeMillis();
+                    makeBlink();
+                }
+            }
+        }
+
+        if (what == Selection.SELECTION_START) {
+            mHighlightPathBogus = true;
+            selChanged = true;
+            newSelStart = newStart;
+
+            if (!isFocused()) {
+                mSelectionMoved = true;
+            }
+
+            if (oldStart >= 0 || newStart >= 0) {
+                int end = Selection.getSelectionEnd(buf);
+                invalidateCursor(end, oldStart, newStart);
+            }
+        }
+
+        if (selChanged) {
+            if ((buf.getSpanFlags(what)&Spanned.SPAN_INTERMEDIATE) == 0) {
+                if (newSelStart < 0) {
+                    newSelStart = Selection.getSelectionStart(buf);
+                }
+                if (newSelEnd < 0) {
+                    newSelEnd = Selection.getSelectionEnd(buf);
+                }
+                onSelectionChanged(newSelStart, newSelEnd);
+            }
+        }
+        
+        if (what instanceof UpdateAppearance ||
+            what instanceof ParagraphStyle) {
+            if (ims == null || ims.mBatchEditNesting == 0) {
+                invalidate();
+                mHighlightPathBogus = true;
+                checkForResize();
+            } else {
+                ims.mContentChanged = true;
+            }
+        }
+
+        if (MetaKeyKeyListener.isMetaTracker(buf, what)) {
+            mHighlightPathBogus = true;
+
+            if (Selection.getSelectionStart(buf) >= 0) {
+                if (ims == null || ims.mBatchEditNesting == 0) {
+                    invalidateCursor();
+                } else {
+                    ims.mCursorChanged = true;
+                }
+            }
+        }
+        
+        if (what instanceof ParcelableSpan) {
+            // If this is a span that can be sent to a remote process,
+            // the current extract editor would be interested in it.
+            if (ims != null && ims.mExtracting != null) {
+                if (ims.mBatchEditNesting != 0) {
+                    if (oldStart >= 0) {
+                        if (ims.mChangedStart > oldStart) {
+                            ims.mChangedStart = oldStart;
+                        }
+                        if (ims.mChangedStart > oldEnd) {
+                            ims.mChangedStart = oldEnd;
+                        }
+                    }
+                    if (newStart >= 0) {
+                        if (ims.mChangedStart > newStart) {
+                            ims.mChangedStart = newStart;
+                        }
+                        if (ims.mChangedStart > newEnd) {
+                            ims.mChangedStart = newEnd;
+                        }
+                    }
+                } else {
+                    if (DEBUG_EXTRACT) Log.v(TAG, "Span change outside of batch: "
+                            + oldStart + "-" + oldEnd + ","
+                            + newStart + "-" + newEnd + what);
+                    ims.mContentChanged = true;
+                }
+            }
+        }
+    }
+
+    private class ChangeWatcher
+    implements TextWatcher, SpanWatcher {
+        public void beforeTextChanged(CharSequence buffer, int start,
+                                      int before, int after) {
+            if (DEBUG_EXTRACT) Log.v(TAG, "beforeTextChanged start=" + start
+                    + " before=" + before + " after=" + after + ": " + buffer);
+            TextView.this.sendBeforeTextChanged(buffer, start, before, after);
+        }
+
+        public void onTextChanged(CharSequence buffer, int start,
+                                  int before, int after) {
+            if (DEBUG_EXTRACT) Log.v(TAG, "onTextChanged start=" + start
+                    + " before=" + before + " after=" + after + ": " + buffer);
+            TextView.this.handleTextChanged(buffer, start, before, after);
+        }
+
+        public void afterTextChanged(Editable buffer) {
+            if (DEBUG_EXTRACT) Log.v(TAG, "afterTextChanged: " + buffer);
+            TextView.this.sendAfterTextChanged(buffer);
+
+            if (MetaKeyKeyListener.getMetaState(buffer,
+                                 MetaKeyKeyListener.META_SELECTING) != 0) {
+                MetaKeyKeyListener.stopSelecting(TextView.this, buffer);
+            }
+        }
+
+        public void onSpanChanged(Spannable buf,
+                                  Object what, int s, int e, int st, int en) {
+            if (DEBUG_EXTRACT) Log.v(TAG, "onSpanChanged s=" + s + " e=" + e
+                    + " st=" + st + " en=" + en + " what=" + what + ": " + buf);
+            TextView.this.spanChange(buf, what, s, st, e, en);
+        }
+
+        public void onSpanAdded(Spannable buf, Object what, int s, int e) {
+            if (DEBUG_EXTRACT) Log.v(TAG, "onSpanAdded s=" + s + " e=" + e
+                    + " what=" + what + ": " + buf);
+            TextView.this.spanChange(buf, what, -1, s, -1, e);
+        }
+
+        public void onSpanRemoved(Spannable buf, Object what, int s, int e) {
+            if (DEBUG_EXTRACT) Log.v(TAG, "onSpanRemoved s=" + s + " e=" + e
+                    + " what=" + what + ": " + buf);
+            TextView.this.spanChange(buf, what, s, -1, e, -1);
+        }
+    }
+
+    private void makeBlink() {
+        if (!mCursorVisible) {
+            if (mBlink != null) {
+                mBlink.removeCallbacks(mBlink);
+            }
+
+            return;
+        }
+
+        if (mBlink == null)
+            mBlink = new Blink(this);
+
+        mBlink.removeCallbacks(mBlink);
+        mBlink.postAtTime(mBlink, mShowCursor + BLINK);
+    }
+
+    @Override
+    public void onStartTemporaryDetach() {
+        mTemporaryDetach = true;
+    }
+    
+    @Override
+    public void onFinishTemporaryDetach() {
+        mTemporaryDetach = false;
+    }
+    
+    @Override
+    protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) {
+        if (mTemporaryDetach) {
+            // If we are temporarily in the detach state, then do nothing.
+            super.onFocusChanged(focused, direction, previouslyFocusedRect);
+            return;
+        }
+        
+        mShowCursor = SystemClock.uptimeMillis();
+
+        ensureEndedBatchEdit();
+        
+        if (focused) {
+            int selStart = getSelectionStart();
+            int selEnd = getSelectionEnd();
+
+            if (!mFrozenWithFocus || (selStart < 0 || selEnd < 0)) {
+                boolean selMoved = mSelectionMoved;
+
+                if (mMovement != null) {
+                    mMovement.onTakeFocus(this, (Spannable) mText, direction);
+                }
+
+                if (mSelectAllOnFocus) {
+                    Selection.setSelection((Spannable) mText, 0, mText.length());
+                }
+
+                if (selMoved && selStart >= 0 && selEnd >= 0) {
+                    /*
+                     * Someone intentionally set the selection, so let them
+                     * do whatever it is that they wanted to do instead of
+                     * the default on-focus behavior.  We reset the selection
+                     * here instead of just skipping the onTakeFocus() call
+                     * because some movement methods do something other than
+                     * just setting the selection in theirs and we still
+                     * need to go through that path.
+                     */
+
+                    Selection.setSelection((Spannable) mText, selStart, selEnd);
+                }
+            }
+
+            mFrozenWithFocus = false;
+            mSelectionMoved = false;
+
+            if (mText instanceof Spannable) {
+                Spannable sp = (Spannable) mText;
+                MetaKeyKeyListener.resetMetaState(sp);
+            }
+
+            makeBlink();
+
+            if (mError != null) {
+                showError();
+            }
+        } else {
+            if (mError != null) {
+                hideError();
+            }
+            // Don't leave us in the middle of a batch edit.
+            onEndBatchEdit();
+        }
+
+        startStopMarquee(focused);
+
+        if (mTransformation != null) {
+            mTransformation.onFocusChanged(this, mText, focused, direction,
+                                           previouslyFocusedRect);
+        }
+
+        super.onFocusChanged(focused, direction, previouslyFocusedRect);
+    }
+
+    @Override
+    public void onWindowFocusChanged(boolean hasWindowFocus) {
+        super.onWindowFocusChanged(hasWindowFocus);
+
+        if (hasWindowFocus) {
+            if (mBlink != null) {
+                mBlink.uncancel();
+
+                if (isFocused()) {
+                    mShowCursor = SystemClock.uptimeMillis();
+                    makeBlink();
+                }
+            }
+        } else {
+            if (mBlink != null) {
+                mBlink.cancel();
+            }
+            // Don't leave us in the middle of a batch edit.
+            onEndBatchEdit();
+            if (mInputContentType != null) {
+                mInputContentType.enterDown = false;
+            }
+        }
+
+        startStopMarquee(hasWindowFocus);
+    }
+
+    /**
+     * Use {@link BaseInputConnection#removeComposingSpans
+     * BaseInputConnection.removeComposingSpans()} to remove any IME composing
+     * state from this text view.
+     */
+    public void clearComposingText() {
+        if (mText instanceof Spannable) {
+            BaseInputConnection.removeComposingSpans((Spannable)mText);
+        }
+    }
+    
+    @Override
+    public void setSelected(boolean selected) {
+        boolean wasSelected = isSelected();
+
+        super.setSelected(selected);
+
+        if (selected != wasSelected && mEllipsize == TextUtils.TruncateAt.MARQUEE) {
+            if (selected) {
+                startMarquee();
+            } else {
+                stopMarquee();
+            }
+        }
+    }
+
+    @Override
+    public boolean onTouchEvent(MotionEvent event) {
+        final boolean superResult = super.onTouchEvent(event);
+
+        final int action = event.getAction();
+        
+        /*
+         * Don't handle the release after a long press, because it will
+         * move the selection away from whatever the menu action was
+         * trying to affect.
+         */
+        if (mEatTouchRelease && action == MotionEvent.ACTION_UP) {
+            mEatTouchRelease = false;
+            return superResult;
+        }
+
+        if (mMovement != null && mText instanceof Spannable && mLayout != null) {
+            
+            if (action == MotionEvent.ACTION_DOWN) {
+                mScrolled = false;
+            }
+            
+            boolean moved = mMovement.onTouchEvent(this, (Spannable) mText, event);
+
+            if (mText instanceof Editable && onCheckIsTextEditor()) {
+                if (action == MotionEvent.ACTION_UP && isFocused() && !mScrolled) {
+                    InputMethodManager imm = (InputMethodManager)
+                            getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
+                    imm.showSoftInput(this, 0);
+                }
+            }
+
+            if (moved) {
+                return true;
+            }
+        }
+
+        return superResult;
+    }
+
+    @Override
+    public void cancelLongPress() {
+        super.cancelLongPress();
+        mScrolled = true;
+    }
+    
+    @Override
+    public boolean onTrackballEvent(MotionEvent event) {
+        if (mMovement != null && mText instanceof Spannable &&
+            mLayout != null) {
+            if (mMovement.onTrackballEvent(this, (Spannable) mText, event)) {
+                return true;
+            }
+        }
+
+        return super.onTrackballEvent(event);
+    }
+
+    public void setScroller(Scroller s) {
+        mScroller = s;
+    }
+
+    private static class Blink extends Handler implements Runnable {
+        private WeakReference<TextView> mView;
+        private boolean mCancelled;
+
+        public Blink(TextView v) {
+            mView = new WeakReference<TextView>(v);
+        }
+
+        public void run() {
+            if (mCancelled) {
+                return;
+            }
+
+            removeCallbacks(Blink.this);
+
+            TextView tv = mView.get();
+
+            if (tv != null && tv.isFocused()) {
+                int st = Selection.getSelectionStart(tv.mText);
+                int en = Selection.getSelectionEnd(tv.mText);
+
+                if (st == en && st >= 0 && en >= 0) {
+                    if (tv.mLayout != null) {
+                        tv.invalidateCursorPath();
+                    }
+
+                    postAtTime(this, SystemClock.uptimeMillis() + BLINK);
+                }
+            }
+        }
+
+        void cancel() {
+            if (!mCancelled) {
+                removeCallbacks(Blink.this);
+                mCancelled = true;
+            }
+        }
+
+        void uncancel() {
+            mCancelled = false;
+        }
+    }
+
+    @Override
+    protected float getLeftFadingEdgeStrength() {
+        if (mEllipsize == TextUtils.TruncateAt.MARQUEE) {
+            if (mMarquee != null && !mMarquee.isStopped()) {
+                final Marquee marquee = mMarquee;
+                return marquee.mScroll / getHorizontalFadingEdgeLength();
+            } else if (getLineCount() == 1) {
+                switch (mGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
+                    case Gravity.LEFT:
+                        return 0.0f;
+                    case Gravity.RIGHT:
+                        return (mLayout.getLineRight(0) - (mRight - mLeft) -
+                                getCompoundPaddingLeft() - getCompoundPaddingRight() -
+                                mLayout.getLineLeft(0)) / getHorizontalFadingEdgeLength();
+                    case Gravity.CENTER_HORIZONTAL:
+                        return 0.0f;
+                }
+            }
+        }
+        return super.getLeftFadingEdgeStrength();
+    }
+
+    @Override
+    protected float getRightFadingEdgeStrength() {
+        if (mEllipsize == TextUtils.TruncateAt.MARQUEE) {
+            if (mMarquee != null && !mMarquee.isStopped()) {
+                final Marquee marquee = mMarquee;
+                return (marquee.mMaxScroll - marquee.mScroll) / getHorizontalFadingEdgeLength();
+            } else if (getLineCount() == 1) {
+                switch (mGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
+                    case Gravity.LEFT:
+                        return (mLayout.getLineRight(0) - mScrollX - (mRight - mLeft) -
+                                getCompoundPaddingLeft() - getCompoundPaddingRight()) /
+                                getHorizontalFadingEdgeLength();
+                    case Gravity.RIGHT:
+                        return 0.0f;
+                    case Gravity.CENTER_HORIZONTAL:
+                        return (mLayout.getLineWidth(0) - ((mRight - mLeft) -
+                                getCompoundPaddingLeft() - getCompoundPaddingRight())) /
+                                getHorizontalFadingEdgeLength();
+                }
+            }
+        }
+        return super.getRightFadingEdgeStrength();
+    }
+
+    @Override
+    protected int computeHorizontalScrollRange() {
+        if (mLayout != null)
+            return mLayout.getWidth();
+
+        return super.computeHorizontalScrollRange();
+    }
+
+    @Override
+    protected int computeVerticalScrollRange() {
+        if (mLayout != null)
+            return mLayout.getHeight();
+
+        return super.computeVerticalScrollRange();
+    }
+
+    public enum BufferType {
+        NORMAL, SPANNABLE, EDITABLE,
+    }
+
+    /**
+     * Returns the TextView_textColor attribute from the
+     * Resources.StyledAttributes, if set, or the TextAppearance_textColor
+     * from the TextView_textAppearance attribute, if TextView_textColor
+     * was not set directly.
+     */
+    public static ColorStateList getTextColors(Context context, TypedArray attrs) {
+        ColorStateList colors;
+        colors = attrs.getColorStateList(com.android.internal.R.styleable.
+                                         TextView_textColor);
+
+        if (colors == null) {
+            int ap = attrs.getResourceId(com.android.internal.R.styleable.
+                                         TextView_textAppearance, -1);
+            if (ap != -1) {
+                TypedArray appearance;
+                appearance = context.obtainStyledAttributes(ap,
+                                            com.android.internal.R.styleable.TextAppearance);
+                colors = appearance.getColorStateList(com.android.internal.R.styleable.
+                                                  TextAppearance_textColor);
+                appearance.recycle();
+            }
+        }
+
+        return colors;
+    }
+
+    /**
+     * Returns the default color from the TextView_textColor attribute
+     * from the AttributeSet, if set, or the default color from the
+     * TextAppearance_textColor from the TextView_textAppearance attribute,
+     * if TextView_textColor was not set directly.
+     */
+    public static int getTextColor(Context context,
+                                   TypedArray attrs,
+                                   int def) {
+        ColorStateList colors = getTextColors(context, attrs);
+
+        if (colors == null) {
+            return def;
+        } else {
+            return colors.getDefaultColor();
+        }
+    }
+
+    @Override
+    public boolean onKeyShortcut(int keyCode, KeyEvent event) {
+        switch (keyCode) {
+        case KeyEvent.KEYCODE_A:
+            if (canSelectAll()) {
+                return onTextContextMenuItem(ID_SELECT_ALL);
+            }
+
+            break;
+
+        case KeyEvent.KEYCODE_X:
+            if (canCut()) {
+                return onTextContextMenuItem(ID_CUT);
+            }
+
+            break;
+
+        case KeyEvent.KEYCODE_C:
+            if (canCopy()) {
+                return onTextContextMenuItem(ID_COPY);
+            }
+
+            break;
+
+        case KeyEvent.KEYCODE_V:
+            if (canPaste()) {
+                return onTextContextMenuItem(ID_PASTE);
+            }
+
+            break;
+        }
+
+        return super.onKeyShortcut(keyCode, event);
+    }
+
+    private boolean canSelectAll() {
+        if (mText instanceof Spannable && mText.length() != 0 &&
+            mMovement != null && mMovement.canSelectArbitrarily()) {
+            return true;
+        }
+
+        return false;
+    }
+
+    private boolean canSelectText() {
+        if (mText instanceof Spannable && mText.length() != 0 &&
+            mMovement != null && mMovement.canSelectArbitrarily()) {
+            return true;
+        }
+
+        return false;
+    }
+
+    private boolean canCut() {
+        if (mTransformation instanceof PasswordTransformationMethod) {
+            return false;
+        }
+
+        if (mText.length() > 0 && getSelectionStart() >= 0) {
+            if (mText instanceof Editable && mInput != null) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    private boolean canCopy() {
+        if (mTransformation instanceof PasswordTransformationMethod) {
+            return false;
+        }
+
+        if (mText.length() > 0 && getSelectionStart() >= 0) {
+            return true;
+        }
+
+        return false;
+    }
+
+    private boolean canPaste() {
+        if (mText instanceof Editable && mInput != null &&
+            getSelectionStart() >= 0 && getSelectionEnd() >= 0) {
+            ClipboardManager clip = (ClipboardManager)getContext()
+                    .getSystemService(Context.CLIPBOARD_SERVICE);
+            if (clip.hasText()) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    /**
+     * Returns a word to add to the dictionary from the context menu,
+     * or null if there is no cursor or no word at the cursor.
+     */
+    private String getWordForDictionary() {
+        int end = getSelectionEnd();
+
+        if (end < 0) {
+            return null;
+        }
+
+        int start = end;
+        int len = mText.length();
+
+        for (; start > 0; start--) {
+            char c = mTransformed.charAt(start - 1);
+            int type = Character.getType(c);
+
+            if (c != '\'' &&
+                type != Character.UPPERCASE_LETTER &&
+                type != Character.LOWERCASE_LETTER &&
+                type != Character.TITLECASE_LETTER &&
+                type != Character.MODIFIER_LETTER &&
+                type != Character.DECIMAL_DIGIT_NUMBER) {
+                break;
+            }
+        }
+
+        for (; end < len; end++) {
+            char c = mTransformed.charAt(end);
+            int type = Character.getType(c);
+
+            if (c != '\'' &&
+                type != Character.UPPERCASE_LETTER &&
+                type != Character.LOWERCASE_LETTER &&
+                type != Character.TITLECASE_LETTER &&
+                type != Character.MODIFIER_LETTER &&
+                type != Character.DECIMAL_DIGIT_NUMBER) {
+                break;
+            }
+        }
+
+        if (start == end) {
+            return null;
+        }
+
+        if (end - start > 48) {
+            return null;
+        }
+
+        return TextUtils.substring(mTransformed, start, end);
+    }
+
+    @Override
+    protected void onCreateContextMenu(ContextMenu menu) {
+        super.onCreateContextMenu(menu);
+        boolean added = false;
+
+        if (!isFocused()) {
+            if (isFocusable() && mInput != null) {
+                if (canCopy()) {
+                    MenuHandler handler = new MenuHandler();
+                    int name = com.android.internal.R.string.copyAll;
+
+                    menu.add(0, ID_COPY, 0, name).
+                        setOnMenuItemClickListener(handler).
+                        setAlphabeticShortcut('c');
+                    menu.setHeaderTitle(com.android.internal.R.string.
+                        editTextMenuTitle);
+                }
+            }
+
+            return;
+        }
+
+        MenuHandler handler = new MenuHandler();
+
+        if (canSelectAll()) {
+            menu.add(0, ID_SELECT_ALL, 0,
+                    com.android.internal.R.string.selectAll).
+                setOnMenuItemClickListener(handler).
+                setAlphabeticShortcut('a');
+            added = true;
+        }
+
+        boolean selection = getSelectionStart() != getSelectionEnd();
+
+        if (canSelectText()) {
+            if (MetaKeyKeyListener.getMetaState(mText, MetaKeyKeyListener.META_SELECTING) != 0) {
+                menu.add(0, ID_STOP_SELECTING_TEXT, 0,
+                        com.android.internal.R.string.stopSelectingText).
+                    setOnMenuItemClickListener(handler);
+                added = true;
+            } else {
+                menu.add(0, ID_START_SELECTING_TEXT, 0,
+                        com.android.internal.R.string.selectText).
+                    setOnMenuItemClickListener(handler);
+                added = true;
+            }
+        }
+
+        if (canCut()) {
+            int name;
+            if (selection) {
+                name = com.android.internal.R.string.cut;
+            } else {
+                name = com.android.internal.R.string.cutAll;
+            }
+
+            menu.add(0, ID_CUT, 0, name).
+                setOnMenuItemClickListener(handler).
+                setAlphabeticShortcut('x');
+            added = true;
+        }
+
+        if (canCopy()) {
+            int name;
+            if (selection) {
+                name = com.android.internal.R.string.copy;
+            } else {
+                name = com.android.internal.R.string.copyAll;
+            }
+
+            menu.add(0, ID_COPY, 0, name).
+                setOnMenuItemClickListener(handler).
+                setAlphabeticShortcut('c');
+            added = true;
+        }
+
+        if (canPaste()) {
+            menu.add(0, ID_PASTE, 0, com.android.internal.R.string.paste).
+                    setOnMenuItemClickListener(handler).
+                    setAlphabeticShortcut('v');
+            added = true;
+        }
+
+        if (mText instanceof Spanned) {
+            int selStart = getSelectionStart();
+            int selEnd = getSelectionEnd();
+
+            int min = Math.min(selStart, selEnd);
+            int max = Math.max(selStart, selEnd);
+
+            URLSpan[] urls = ((Spanned) mText).getSpans(min, max,
+                                                        URLSpan.class);
+            if (urls.length == 1) {
+                menu.add(0, ID_COPY_URL, 0,
+                         com.android.internal.R.string.copyUrl).
+                            setOnMenuItemClickListener(handler);
+                added = true;
+            }
+        }
+
+        if (isInputMethodTarget()) {
+            menu.add(1, ID_SWITCH_INPUT_METHOD, 0, com.android.internal.R.string.inputMethod).
+                    setOnMenuItemClickListener(handler);
+            added = true;
+        }
+
+        String word = getWordForDictionary();
+        if (word != null) {
+            menu.add(1, ID_ADD_TO_DICTIONARY, 0,
+                     getContext().getString(com.android.internal.R.string.addToDictionary, word)).
+                    setOnMenuItemClickListener(handler);
+            added = true;
+
+        }
+
+        if (added) {
+            menu.setHeaderTitle(com.android.internal.R.string.editTextMenuTitle);
+        }
+    }
+
+    /**
+     * Returns whether this text view is a current input method target.  The
+     * default implementation just checks with {@link InputMethodManager}.
+     */
+    public boolean isInputMethodTarget() {
+        InputMethodManager imm = InputMethodManager.peekInstance();
+        return imm != null && imm.isActive(this);
+    }
+    
+    private static final int ID_SELECT_ALL = android.R.id.selectAll;
+    private static final int ID_START_SELECTING_TEXT = android.R.id.startSelectingText;
+    private static final int ID_STOP_SELECTING_TEXT = android.R.id.stopSelectingText;
+    private static final int ID_CUT = android.R.id.cut;
+    private static final int ID_COPY = android.R.id.copy;
+    private static final int ID_PASTE = android.R.id.paste;
+    private static final int ID_COPY_URL = android.R.id.copyUrl;
+    private static final int ID_SWITCH_INPUT_METHOD = android.R.id.switchInputMethod;
+    private static final int ID_ADD_TO_DICTIONARY = android.R.id.addToDictionary;
+
+    private class MenuHandler implements MenuItem.OnMenuItemClickListener {
+        public boolean onMenuItemClick(MenuItem item) {
+            return onTextContextMenuItem(item.getItemId());
+        }
+    }
+
+    /**
+     * Called when a context menu option for the text view is selected.  Currently
+     * this will be one of: {@link android.R.id#selectAll},
+     * {@link android.R.id#startSelectingText}, {@link android.R.id#stopSelectingText},
+     * {@link android.R.id#cut}, {@link android.R.id#copy},
+     * {@link android.R.id#paste}, {@link android.R.id#copyUrl},
+     * or {@link android.R.id#switchInputMethod}.
+     */
+    public boolean onTextContextMenuItem(int id) {
+        int selStart = getSelectionStart();
+        int selEnd = getSelectionEnd();
+
+        if (!isFocused()) {
+            selStart = 0;
+            selEnd = mText.length();
+        }
+
+        int min = Math.min(selStart, selEnd);
+        int max = Math.max(selStart, selEnd);
+
+        if (min < 0) {
+            min = 0;
+        }
+        if (max < 0) {
+            max = 0;
+        }
+
+        ClipboardManager clip = (ClipboardManager)getContext()
+                .getSystemService(Context.CLIPBOARD_SERVICE);
+
+        switch (id) {
+            case ID_SELECT_ALL:
+                Selection.setSelection((Spannable) mText, 0,
+                        mText.length());
+                return true;
+
+            case ID_START_SELECTING_TEXT:
+                MetaKeyKeyListener.startSelecting(this, (Spannable) mText);
+                return true;
+
+            case ID_STOP_SELECTING_TEXT:
+                MetaKeyKeyListener.stopSelecting(this, (Spannable) mText);
+                Selection.setSelection((Spannable) mText, getSelectionEnd());
+                return true;
+
+            case ID_CUT:
+                MetaKeyKeyListener.stopSelecting(this, (Spannable) mText);
+
+                if (min == max) {
+                    min = 0;
+                    max = mText.length();
+                }
+
+                clip.setText(mTransformed.subSequence(min, max));
+                ((Editable) mText).delete(min, max);
+                return true;
+
+            case ID_COPY:
+                MetaKeyKeyListener.stopSelecting(this, (Spannable) mText);
+
+                if (min == max) {
+                    min = 0;
+                    max = mText.length();
+                }
+
+                clip.setText(mTransformed.subSequence(min, max));
+                return true;
+
+            case ID_PASTE:
+                MetaKeyKeyListener.stopSelecting(this, (Spannable) mText);
+
+                CharSequence paste = clip.getText();
+
+                if (paste != null) {
+                    Selection.setSelection((Spannable) mText, max);
+                    ((Editable) mText).replace(min, max, paste);
+                }
+
+                return true;
+
+            case ID_COPY_URL:
+                MetaKeyKeyListener.stopSelecting(this, (Spannable) mText);
+
+                URLSpan[] urls = ((Spanned) mText).getSpans(min, max,
+                                                       URLSpan.class);
+                if (urls.length == 1) {
+                    clip.setText(urls[0].getURL());
+                }
+
+                return true;
+
+            case ID_SWITCH_INPUT_METHOD:
+                InputMethodManager imm = InputMethodManager.peekInstance();
+                if (imm != null) {
+                    imm.showInputMethodPicker();
+                }
+                return true;
+
+            case ID_ADD_TO_DICTIONARY:
+                String word = getWordForDictionary();
+
+                if (word != null) {
+                    Intent i = new Intent("com.android.settings.USER_DICTIONARY_INSERT");
+                    i.putExtra("word", word);
+                    getContext().startActivity(i);
+                }
+
+                return true;
+            }
+
+        return false;
+    }
+
+    public boolean performLongClick() {
+        if (super.performLongClick()) {
+            mEatTouchRelease = true;
+            return true;
+        }
+
+        return false;
+    }
+
+    @ViewDebug.ExportedProperty
+    private CharSequence            mText;
+    private CharSequence            mTransformed;
+    private BufferType              mBufferType = BufferType.NORMAL;
+
+    private int                     mInputType = EditorInfo.TYPE_NULL;
+    private CharSequence            mHint;
+    private Layout                  mHintLayout;
+
+    private KeyListener             mInput;
+
+    private MovementMethod          mMovement;
+    private TransformationMethod    mTransformation;
+    private ChangeWatcher           mChangeWatcher;
+
+    private ArrayList<TextWatcher>  mListeners = null;
+
+    // display attributes
+    private TextPaint mTextPaint;
+    private Paint                   mHighlightPaint;
+    private int                     mHighlightColor = 0xFFBBDDFF;
+    private Layout                  mLayout;
+
+    private long                    mShowCursor;
+    private Blink                   mBlink;
+    private boolean                 mCursorVisible = true;
+
+    private boolean                 mSelectAllOnFocus = false;
+
+    private int                     mGravity = Gravity.TOP | Gravity.LEFT;
+    private boolean                 mHorizontallyScrolling;
+
+    private int                     mAutoLinkMask;
+    private boolean                 mLinksClickable = true;
+
+    private float                   mSpacingMult = 1;
+    private float                   mSpacingAdd = 0;
+
+    private static final int        LINES = 1;
+    private static final int        EMS = LINES;
+    private static final int        PIXELS = 2;
+
+    private int                     mMaximum = Integer.MAX_VALUE;
+    private int                     mMaxMode = LINES;
+    private int                     mMinimum = 0;
+    private int                     mMinMode = LINES;
+
+    private int                     mMaxWidth = Integer.MAX_VALUE;
+    private int                     mMaxWidthMode = PIXELS;
+    private int                     mMinWidth = 0;
+    private int                     mMinWidthMode = PIXELS;
+
+    private boolean                 mSingleLine;
+    private int                     mDesiredHeightAtMeasure = -1;
+    private boolean                 mIncludePad = true;
+
+    // tmp primitives, so we don't alloc them on each draw
+    private Path                    mHighlightPath;
+    private boolean                 mHighlightPathBogus = true;
+    private static final RectF      sTempRect = new RectF();
+
+    // XXX should be much larger
+    private static final int        VERY_WIDE = 16384;
+
+    private static final int        BLINK = 500;
+
+    private static final int ANIMATED_SCROLL_GAP = 250;
+    private long mLastScroll;
+    private Scroller mScroller = null;
+
+    private BoringLayout.Metrics mBoring;
+    private BoringLayout.Metrics mHintBoring;
+
+    private BoringLayout mSavedLayout, mSavedHintLayout;
+
+    private static final InputFilter[] NO_FILTERS = new InputFilter[0];
+    private InputFilter[] mFilters = NO_FILTERS;
+    private static final Spanned EMPTY_SPANNED = new SpannedString("");
+}
diff --git a/core/java/android/widget/TimePicker.java b/core/java/android/widget/TimePicker.java
new file mode 100644
index 0000000..ab4edc5
--- /dev/null
+++ b/core/java/android/widget/TimePicker.java
@@ -0,0 +1,360 @@
+/*
+ * Copyright (C) 2007 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 android.widget;
+
+import android.annotation.Widget;
+import android.content.Context;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.AttributeSet;
+import android.view.LayoutInflater;
+import android.view.View;
+
+import com.android.internal.R;
+import com.android.internal.widget.NumberPicker;
+
+import java.text.DateFormatSymbols;
+import java.util.Calendar;
+
+/**
+ * A view for selecting the time of day, in either 24 hour or AM/PM mode.
+ *
+ * The hour, each minute digit, and AM/PM (if applicable) can be conrolled by
+ * vertical spinners.
+ *
+ * The hour can be entered by keyboard input.  Entering in two digit hours
+ * can be accomplished by hitting two digits within a timeout of about a
+ * second (e.g. '1' then '2' to select 12).
+ *
+ * The minutes can be entered by entering single digits.
+ *
+ * Under AM/PM mode, the user can hit 'a', 'A", 'p' or 'P' to pick.
+ *
+ * For a dialog using this view, see {@link android.app.TimePickerDialog}.
+ */
+@Widget
+public class TimePicker extends FrameLayout {
+    
+    /**
+     * A no-op callback used in the constructor to avoid null checks
+     * later in the code.
+     */
+    private static final OnTimeChangedListener NO_OP_CHANGE_LISTENER = new OnTimeChangedListener() {
+        public void onTimeChanged(TimePicker view, int hourOfDay, int minute) {
+        }
+    };
+    
+    // state
+    private int mCurrentHour = 0; // 0-23
+    private int mCurrentMinute = 0; // 0-59
+    private Boolean mIs24HourView = false;
+    private boolean mIsAm;
+
+    // ui components
+    private final NumberPicker mHourPicker;
+    private final NumberPicker mMinutePicker;
+    private final Button mAmPmButton;
+    private final String mAmText;
+    private final String mPmText;
+    
+    // callbacks
+    private OnTimeChangedListener mOnTimeChangedListener;
+
+    /**
+     * The callback interface used to indicate the time has been adjusted.
+     */
+    public interface OnTimeChangedListener {
+
+        /**
+         * @param view The view associated with this listener.
+         * @param hourOfDay The current hour.
+         * @param minute The current minute.
+         */
+        void onTimeChanged(TimePicker view, int hourOfDay, int minute);
+    }
+
+    public TimePicker(Context context) {
+        this(context, null);
+    }
+    
+    public TimePicker(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public TimePicker(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+
+        LayoutInflater inflater =
+                (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+        inflater.inflate(R.layout.time_picker,
+            this, // we are the parent
+            true);
+
+        // hour
+        mHourPicker = (NumberPicker) findViewById(R.id.hour);
+        mHourPicker.setOnChangeListener(new NumberPicker.OnChangedListener() {
+            public void onChanged(NumberPicker spinner, int oldVal, int newVal) {
+                mCurrentHour = newVal;
+                if (!mIs24HourView) {
+                    // adjust from [1-12] to [0-11] internally, with the times
+                    // written "12:xx" being the start of the half-day
+                    if (mCurrentHour == 12) {
+                        mCurrentHour = 0;
+                    }
+                    if (!mIsAm) {
+                        // PM means 12 hours later than nominal
+                        mCurrentHour += 12;
+                    }
+                }
+                onTimeChanged();
+            }
+        });
+
+        // digits of minute
+        mMinutePicker = (NumberPicker) findViewById(R.id.minute);
+        mMinutePicker.setRange(0, 59);
+        mMinutePicker.setSpeed(100);
+        mMinutePicker.setFormatter(NumberPicker.TWO_DIGIT_FORMATTER);
+        mMinutePicker.setOnChangeListener(new NumberPicker.OnChangedListener() {
+            public void onChanged(NumberPicker spinner, int oldVal, int newVal) {
+                mCurrentMinute = newVal;
+                onTimeChanged();
+            }
+        });
+
+        // am/pm
+        mAmPmButton = (Button) findViewById(R.id.amPm);
+
+        // now that the hour/minute picker objects have been initialized, set
+        // the hour range properly based on the 12/24 hour display mode.
+        configurePickerRanges();
+
+        // initialize to current time
+        Calendar cal = Calendar.getInstance();
+        setOnTimeChangedListener(NO_OP_CHANGE_LISTENER);
+        
+        // by default we're not in 24 hour mode
+        setCurrentHour(cal.get(Calendar.HOUR_OF_DAY));
+        setCurrentMinute(cal.get(Calendar.MINUTE));
+        
+        mIsAm = (mCurrentHour < 12);
+        
+        /* Get the localized am/pm strings and use them in the spinner */
+        DateFormatSymbols dfs = new DateFormatSymbols();
+        String[] dfsAmPm = dfs.getAmPmStrings();
+        mAmText = dfsAmPm[Calendar.AM];
+        mPmText = dfsAmPm[Calendar.PM];
+        mAmPmButton.setText(mIsAm ? mAmText : mPmText);
+        mAmPmButton.setOnClickListener(new OnClickListener() {
+            public void onClick(View v) {
+                requestFocus();
+                if (mIsAm) {
+                    
+                    // Currently AM switching to PM
+                    if (mCurrentHour < 12) {
+                        mCurrentHour += 12;
+                    }                
+                } else {
+                    
+                    // Currently PM switching to AM
+                    if (mCurrentHour >= 12) {
+                        mCurrentHour -= 12;
+                    }
+                }
+                mIsAm = !mIsAm;
+                mAmPmButton.setText(mIsAm ? mAmText : mPmText);
+                onTimeChanged();
+            }
+        });
+        
+        if (!isEnabled()) {
+            setEnabled(false);
+        }
+    }
+    
+    @Override
+    public void setEnabled(boolean enabled) {
+        super.setEnabled(enabled);
+        mMinutePicker.setEnabled(enabled);
+        mHourPicker.setEnabled(enabled);
+        mAmPmButton.setEnabled(enabled);
+    }
+
+    /**
+     * Used to save / restore state of time picker
+     */
+    private static class SavedState extends BaseSavedState {
+
+        private final int mHour;
+        private final int mMinute;
+
+        private SavedState(Parcelable superState, int hour, int minute) {
+            super(superState);
+            mHour = hour;
+            mMinute = minute;
+        }
+        
+        private SavedState(Parcel in) {
+            super(in);
+            mHour = in.readInt();
+            mMinute = in.readInt();
+        }
+
+        public int getHour() {
+            return mHour;
+        }
+
+        public int getMinute() {
+            return mMinute;
+        }
+
+        @Override
+        public void writeToParcel(Parcel dest, int flags) {
+            super.writeToParcel(dest, flags);
+            dest.writeInt(mHour);
+            dest.writeInt(mMinute);
+        }
+
+        public static final Parcelable.Creator<SavedState> CREATOR
+                = new Creator<SavedState>() {
+            public SavedState createFromParcel(Parcel in) {
+                return new SavedState(in);
+            }
+
+            public SavedState[] newArray(int size) {
+                return new SavedState[size];
+            }
+        };
+    }
+
+    @Override
+    protected Parcelable onSaveInstanceState() {
+        Parcelable superState = super.onSaveInstanceState();
+        return new SavedState(superState, mCurrentHour, mCurrentMinute);
+    }
+
+    @Override
+    protected void onRestoreInstanceState(Parcelable state) {
+        SavedState ss = (SavedState) state;
+        super.onRestoreInstanceState(ss.getSuperState());
+        setCurrentHour(ss.getHour());
+        setCurrentMinute(ss.getMinute());
+    }
+
+    /**
+     * Set the callback that indicates the time has been adjusted by the user.
+     * @param onTimeChangedListener the callback, should not be null.
+     */
+    public void setOnTimeChangedListener(OnTimeChangedListener onTimeChangedListener) {
+        mOnTimeChangedListener = onTimeChangedListener;
+    }
+
+    /**
+     * @return The current hour (0-23).
+     */
+    public Integer getCurrentHour() {
+        return mCurrentHour;
+    }
+
+    /**
+     * Set the current hour.
+     */
+    public void setCurrentHour(Integer currentHour) {
+        this.mCurrentHour = currentHour;
+        updateHourDisplay();
+    }
+
+    /**
+     * Set whether in 24 hour or AM/PM mode.
+     * @param is24HourView True = 24 hour mode. False = AM/PM.
+     */
+    public void setIs24HourView(Boolean is24HourView) {
+        if (mIs24HourView != is24HourView) {
+            mIs24HourView = is24HourView;
+            configurePickerRanges();
+            updateHourDisplay();
+        }
+    }
+
+    /**
+     * @return true if this is in 24 hour view else false.
+     */
+    public boolean is24HourView() {
+        return mIs24HourView;
+    }
+    
+    /**
+     * @return The current minute.
+     */
+    public Integer getCurrentMinute() {
+        return mCurrentMinute;
+    }
+
+    /**
+     * Set the current minute (0-59).
+     */
+    public void setCurrentMinute(Integer currentMinute) {
+        this.mCurrentMinute = currentMinute;
+        updateMinuteDisplay();
+    }
+
+    @Override
+    public int getBaseline() {
+        return mHourPicker.getBaseline(); 
+    }
+
+    /**
+     * Set the state of the spinners appropriate to the current hour.
+     */
+    private void updateHourDisplay() {
+        int currentHour = mCurrentHour;
+        if (!mIs24HourView) {
+            // convert [0,23] ordinal to wall clock display
+            if (currentHour > 12) currentHour -= 12;
+            else if (currentHour == 0) currentHour = 12;
+        }
+        mHourPicker.setCurrent(currentHour);
+        mIsAm = mCurrentHour < 12;
+        mAmPmButton.setText(mIsAm ? mAmText : mPmText);
+        onTimeChanged();
+    }
+
+    private void configurePickerRanges() {
+        if (mIs24HourView) {
+            mHourPicker.setRange(0, 23);
+            mHourPicker.setFormatter(NumberPicker.TWO_DIGIT_FORMATTER);
+            mAmPmButton.setVisibility(View.GONE);
+        } else {
+            mHourPicker.setRange(1, 12);
+            mHourPicker.setFormatter(null);
+            mAmPmButton.setVisibility(View.VISIBLE);
+        }
+    }
+
+    private void onTimeChanged() {
+        mOnTimeChangedListener.onTimeChanged(this, getCurrentHour(), getCurrentMinute());
+    }
+
+    /**
+     * Set the state of the spinners appropriate to the current minute.
+     */
+    private void updateMinuteDisplay() {
+        mMinutePicker.setCurrent(mCurrentMinute);
+        mOnTimeChangedListener.onTimeChanged(this, getCurrentHour(), getCurrentMinute());
+    }
+}
+
diff --git a/core/java/android/widget/Toast.java b/core/java/android/widget/Toast.java
new file mode 100644
index 0000000..ff74787
--- /dev/null
+++ b/core/java/android/widget/Toast.java
@@ -0,0 +1,399 @@
+/*
+ * Copyright (C) 2007 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 android.widget;
+
+import android.app.INotificationManager;
+import android.app.ITransientNotification;
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.PixelFormat;
+import android.os.RemoteException;
+import android.os.Handler;
+import android.os.ServiceManager;
+import android.util.Log;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.WindowManager;
+import android.view.WindowManagerImpl;
+
+/**
+ * A toast is a view containing a quick little message for the user.  The toast class
+ * helps you create and show those.
+ * {@more}
+ *
+ * <p>
+ * When the view is shown to the user, appears as a floating view over the
+ * application.  It will never receive focus.  The user will probably be in the
+ * middle of typing something else.  The idea is to be as unobtrusive as
+ * possible, while still showing the user the information you want them to see.
+ * Two examples are the volume control, and the brief message saying that your
+ * settings have been saved.
+ * <p>
+ * The easiest way to use this class is to call one of the static methods that constructs
+ * everything you need and returns a new Toast object.
+ */ 
+public class Toast {
+    static final String TAG = "Toast";
+    static final boolean localLOGV = false;
+
+    /**
+     * Show the view or text notification for a short period of time.  This time
+     * could be user-definable.  This is the default.
+     * @see #setDuration
+     */
+    public static final int LENGTH_SHORT = 0;
+
+    /**
+     * Show the view or text notification for a long period of time.  This time
+     * could be user-definable.
+     * @see #setDuration
+     */
+    public static final int LENGTH_LONG = 1;
+
+    final Context mContext;
+    final TN mTN;
+    int mDuration;
+    int mGravity = Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM;
+    int mX, mY;
+    float mHorizontalMargin;
+    float mVerticalMargin;
+    View mView;
+    View mNextView;
+
+    /**
+     * Construct an empty Toast object.  You must call {@link #setView} before you
+     * can call {@link #show}.
+     *
+     * @param context  The context to use.  Usually your {@link android.app.Application}
+     *                 or {@link android.app.Activity} object.
+     */
+    public Toast(Context context) {
+        mContext = context;
+        mTN = new TN(context);
+        mY = context.getResources().getDimensionPixelSize(
+                com.android.internal.R.dimen.toast_y_offset);
+    }
+    
+    /**
+     * Show the view for the specified duration.
+     */
+    public void show() {
+        if (mNextView == null) {
+            throw new RuntimeException("setView must have been called");
+        }
+
+        INotificationManager service = getService();
+
+        String pkg = mContext.getPackageName();
+
+        TN tn = mTN;
+
+        try {
+            service.enqueueToast(pkg, tn, mDuration);
+        } catch (RemoteException e) {
+            // Empty
+        }
+    }
+
+    /**
+     * Close the view if it's showing, or don't show it if it isn't showing yet.
+     * You do not normally have to call this.  Normally view will disappear on its own
+     * after the appropriate duration.
+     */
+    public void cancel() {
+        mTN.hide();
+        // TODO this still needs to cancel the inflight notification if any
+    }
+    
+    /**
+     * Set the view to show.
+     * @see #getView
+     */
+    public void setView(View view) {
+        mNextView = view;
+    }
+
+    /**
+     * Return the view.
+     * @see #setView
+     */
+    public View getView() {
+        return mNextView;
+    }
+
+    /**
+     * Set how long to show the view for.
+     * @see #LENGTH_SHORT
+     * @see #LENGTH_LONG
+     */
+    public void setDuration(int duration) {
+        mDuration = duration;
+    }
+
+    /**
+     * Return the duration.
+     * @see #setDuration
+     */
+    public int getDuration() {
+        return mDuration;
+    }
+    
+    /**
+     * Set the margins of the view.
+     *
+     * @param horizontalMargin The horizontal margin, in percentage of the
+     *        container width, between the container's edges and the
+     *        notification
+     * @param verticalMargin The vertical margin, in percentage of the
+     *        container height, between the container's edges and the
+     *        notification
+     */
+    public void setMargin(float horizontalMargin, float verticalMargin) {
+        mHorizontalMargin = horizontalMargin;
+        mVerticalMargin = verticalMargin;
+    }
+
+    /**
+     * Return the horizontal margin.
+     */
+    public float getHorizontalMargin() {
+        return mHorizontalMargin;
+    }
+
+    /**
+     * Return the vertical margin.
+     */
+    public float getVerticalMargin() {
+        return mVerticalMargin;
+    }
+
+    /**
+     * Set the location at which the notification should appear on the screen.
+     * @see android.view.Gravity
+     * @see #getGravity
+     */
+    public void setGravity(int gravity, int xOffset, int yOffset) {
+        mGravity = gravity;
+        mX = xOffset;
+        mY = yOffset;
+    }
+
+     /**
+     * Get the location at which the notification should appear on the screen.
+     * @see android.view.Gravity
+     * @see #getGravity
+     */
+    public int getGravity() {
+        return mGravity;
+    }
+
+    /**
+     * Return the X offset in pixels to apply to the gravity's location.
+     */
+    public int getXOffset() {
+        return mX;
+    }
+    
+    /**
+     * Return the Y offset in pixels to apply to the gravity's location.
+     */
+    public int getYOffset() {
+        return mY;
+    }
+    
+    /**
+     * Make a standard toast that just contains a text view.
+     *
+     * @param context  The context to use.  Usually your {@link android.app.Application}
+     *                 or {@link android.app.Activity} object.
+     * @param text     The text to show.  Can be formatted text.
+     * @param duration How long to display the message.  Either {@link #LENGTH_SHORT} or
+     *                 {@link #LENGTH_LONG}
+     *
+     */
+    public static Toast makeText(Context context, CharSequence text, int duration) {
+        Toast result = new Toast(context);
+
+        LayoutInflater inflate = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+        View v = inflate.inflate(com.android.internal.R.layout.transient_notification, null);
+        TextView tv = (TextView)v.findViewById(com.android.internal.R.id.message);
+        tv.setText(text);
+        
+        result.mNextView = v;
+        result.mDuration = duration;
+
+        return result;
+    }
+
+    /**
+     * Make a standard toast that just contains a text view with the text from a resource.
+     *
+     * @param context  The context to use.  Usually your {@link android.app.Application}
+     *                 or {@link android.app.Activity} object.
+     * @param resId    The resource id of the string resource to use.  Can be formatted text.
+     * @param duration How long to display the message.  Either {@link #LENGTH_SHORT} or
+     *                 {@link #LENGTH_LONG}
+     *
+     * @throws Resources.NotFoundException if the resource can't be found.
+     */
+    public static Toast makeText(Context context, int resId, int duration)
+                                throws Resources.NotFoundException {
+        return makeText(context, context.getResources().getText(resId), duration);
+    }
+
+    /**
+     * Update the text in a Toast that was previously created using one of the makeText() methods.
+     * @param resId The new text for the Toast.
+     */
+    public void setText(int resId) {
+        setText(mContext.getText(resId));
+    }
+    
+    /**
+     * Update the text in a Toast that was previously created using one of the makeText() methods.
+     * @param s The new text for the Toast.
+     */
+    public void setText(CharSequence s) {
+        if (mNextView == null) {
+            throw new RuntimeException("This Toast was not created with Toast.makeText()");
+        }
+        TextView tv = (TextView) mNextView.findViewById(com.android.internal.R.id.message);
+        if (tv == null) {
+            throw new RuntimeException("This Toast was not created with Toast.makeText()");
+        }
+        tv.setText(s);
+    }
+    
+    // =======================================================================================
+    // All the gunk below is the interaction with the Notification Service, which handles
+    // the proper ordering of these system-wide.
+    // =======================================================================================
+
+    private static INotificationManager sService;
+
+    static private INotificationManager getService()
+    {
+        if (sService != null) {
+            return sService;
+        }
+        sService = INotificationManager.Stub.asInterface(ServiceManager.getService("notification"));
+        return sService;
+    }
+
+    private class TN extends ITransientNotification.Stub
+    {
+        TN(Context context)
+        {
+            // XXX This should be changed to use a Dialog, with a Theme.Toast
+            // defined that sets up the layout params appropriately.
+            mParams.height = WindowManager.LayoutParams.WRAP_CONTENT;
+            mParams.width = WindowManager.LayoutParams.WRAP_CONTENT;
+            mParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
+                    | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
+                    | WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON;
+            mParams.format = PixelFormat.TRANSLUCENT;
+            mParams.windowAnimations = com.android.internal.R.style.Animation_Toast;
+            mParams.type = WindowManager.LayoutParams.TYPE_TOAST;
+            mParams.setTitle("Toast");
+        }
+
+        /**
+         * schedule handleShow into the right thread
+         */
+        public void show()
+        {
+            if (localLOGV) Log.v(TAG, "SHOW: " + this);
+            mHandler.post(mShow);
+        }
+
+        /**
+         * schedule handleHide into the right thread
+         */
+        public void hide()
+        {
+            if (localLOGV) Log.v(TAG, "HIDE: " + this);
+            mHandler.post(mHide);
+        }
+
+        public void handleShow()
+        {
+            if (localLOGV) Log.v(TAG, "HANDLE SHOW: " + this + " mView=" + mView
+                    + " mNextView=" + mNextView);
+            if (mView != mNextView) {
+                // remove the old view if necessary
+                handleHide();
+                mView = mNextView;
+                mWM = WindowManagerImpl.getDefault();
+                final int gravity = mGravity;
+                mParams.gravity = gravity;
+                if ((gravity & Gravity.HORIZONTAL_GRAVITY_MASK) == Gravity.FILL_HORIZONTAL) {
+                    mParams.horizontalWeight = 1.0f;
+                }
+                if ((gravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.FILL_VERTICAL) {
+                    mParams.verticalWeight = 1.0f;
+                }
+                mParams.x = mX;
+                mParams.y = mY;
+                mParams.verticalMargin = mVerticalMargin;
+                mParams.horizontalMargin = mHorizontalMargin;
+                if (mView.getParent() != null) {
+                    if (localLOGV) Log.v(
+                            TAG, "REMOVE! " + mView + " in " + this);
+                    mWM.removeView(mView);
+                }
+                if (localLOGV) Log.v(TAG, "ADD! " + mView + " in " + this);
+                mWM.addView(mView, mParams);
+            }
+        }
+
+        public void handleHide()
+        {
+            if (localLOGV) Log.v(TAG, "HANDLE HIDE: " + this + " mView=" + mView);
+            if (mView != null) {
+                // note: checking parent() just to make sure the view has
+                // been added...  i have seen cases where we get here when
+                // the view isn't yet added, so let's try not to crash.
+                if (mView.getParent() != null) {
+                    if (localLOGV) Log.v(
+                            TAG, "REMOVE! " + mView + " in " + this);
+                    mWM.removeView(mView);
+                }
+                mView = null;
+            }
+        }
+
+        Runnable mShow = new Runnable() {
+            public void run() {
+                handleShow();
+            }
+        };
+
+        Runnable mHide = new Runnable() {
+            public void run() {
+                handleHide();
+            }
+        };
+
+        private final WindowManager.LayoutParams mParams = new WindowManager.LayoutParams();
+        
+        WindowManagerImpl mWM;
+    }
+
+    final Handler mHandler = new Handler();
+}
+
diff --git a/core/java/android/widget/ToggleButton.java b/core/java/android/widget/ToggleButton.java
new file mode 100644
index 0000000..dc791e3
--- /dev/null
+++ b/core/java/android/widget/ToggleButton.java
@@ -0,0 +1,147 @@
+/*
+ * Copyright (C) 2007 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 android.widget;
+
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.LayerDrawable;
+import android.util.AttributeSet;
+
+/**
+ * Displays checked/unchecked states as a button
+ * with a "light" indicator and by default accompanied with the text "ON" or "OFF".
+ * 
+ * @attr ref android.R.styleable#ToggleButton_textOn
+ * @attr ref android.R.styleable#ToggleButton_textOff
+ * @attr ref android.R.styleable#ToggleButton_disabledAlpha
+ */
+public class ToggleButton extends CompoundButton {
+    private CharSequence mTextOn;
+    private CharSequence mTextOff;
+    
+    private Drawable mIndicatorDrawable;
+
+    private static final int NO_ALPHA = 0xFF;
+    private float mDisabledAlpha;
+    
+    public ToggleButton(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+        
+        TypedArray a =
+            context.obtainStyledAttributes(
+                    attrs, com.android.internal.R.styleable.ToggleButton, defStyle, 0);
+        mTextOn = a.getText(com.android.internal.R.styleable.ToggleButton_textOn);
+        mTextOff = a.getText(com.android.internal.R.styleable.ToggleButton_textOff);
+        mDisabledAlpha = a.getFloat(com.android.internal.R.styleable.ToggleButton_disabledAlpha, 0.5f);
+        syncTextState();
+        a.recycle();
+    }
+
+    public ToggleButton(Context context, AttributeSet attrs) {
+        this(context, attrs, com.android.internal.R.attr.buttonStyleToggle);
+    }
+
+    public ToggleButton(Context context) {
+        this(context, null);
+    }
+
+    @Override
+    public void setChecked(boolean checked) {
+        super.setChecked(checked);
+        
+        syncTextState();
+    }
+
+    private void syncTextState() {
+        boolean checked = isChecked();
+        if (checked && mTextOn != null) {
+            setText(mTextOn);
+        } else if (!checked && mTextOff != null) {
+            setText(mTextOff);
+        }
+    }
+
+    /**
+     * Returns the text for when the button is in the checked state.
+     * 
+     * @return The text.
+     */
+    public CharSequence getTextOn() {
+        return mTextOn;
+    }
+
+    /**
+     * Sets the text for when the button is in the checked state.
+     *  
+     * @param textOn The text.
+     */
+    public void setTextOn(CharSequence textOn) {
+        mTextOn = textOn;
+    }
+
+    /**
+     * Returns the text for when the button is not in the checked state.
+     * 
+     * @return The text.
+     */
+    public CharSequence getTextOff() {
+        return mTextOff;
+    }
+
+    /**
+     * Sets the text for when the button is not in the checked state.
+     * 
+     * @param textOff The text.
+     */
+    public void setTextOff(CharSequence textOff) {
+        mTextOff = textOff;
+    }
+
+    @Override
+    protected void onFinishInflate() {
+        super.onFinishInflate();
+        
+        updateReferenceToIndicatorDrawable(getBackground());
+    }
+
+    @Override
+    public void setBackgroundDrawable(Drawable d) {
+        super.setBackgroundDrawable(d);
+        
+        updateReferenceToIndicatorDrawable(d);
+    }
+
+    private void updateReferenceToIndicatorDrawable(Drawable backgroundDrawable) {
+        if (backgroundDrawable instanceof LayerDrawable) {
+            LayerDrawable layerDrawable = (LayerDrawable) backgroundDrawable;
+            mIndicatorDrawable =
+                    layerDrawable.findDrawableByLayerId(com.android.internal.R.id.toggle);
+        }
+    }
+    
+    @Override
+    protected void drawableStateChanged() {
+        super.drawableStateChanged();
+        
+        if (mIndicatorDrawable != null) {
+            mIndicatorDrawable.setAlpha(isEnabled() ? NO_ALPHA : (int) (NO_ALPHA * mDisabledAlpha));
+        }
+    }
+    
+}
diff --git a/core/java/android/widget/TwoLineListItem.java b/core/java/android/widget/TwoLineListItem.java
new file mode 100644
index 0000000..77ea645
--- /dev/null
+++ b/core/java/android/widget/TwoLineListItem.java
@@ -0,0 +1,90 @@
+/*
+ * 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 android.widget;
+
+import com.android.internal.R;
+
+
+import android.annotation.Widget;
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.RelativeLayout;
+
+/**
+ * <p>A view group with two children, intended for use in ListViews. This item has two 
+ * {@link android.widget.TextView TextViews} elements (or subclasses) with the ID values 
+ * {@link android.R.id#text1 text1}
+ * and {@link android.R.id#text2 text2}. There is an optional third View element with the 
+ * ID {@link android.R.id#selectedIcon selectedIcon}, which can be any View subclass 
+ * (though it is typically a graphic View, such as {@link android.widget.ImageView ImageView})
+ * that can be displayed when a TwoLineListItem has focus. Android supplies a 
+ * {@link android.R.layout#two_line_list_item standard layout resource for TwoLineListView} 
+ * (which does not include a selected item icon), but you can design your own custom XML
+ * layout for this object as shown here:</p>
+ * {@sample packages/apps/Phone/res/layout/dialer_list_item.xml} 
+ * 
+ * @attr ref android.R.styleable#TwoLineListItem_mode
+ */
+@Widget
+public class TwoLineListItem extends RelativeLayout {
+
+    private TextView mText1;
+    private TextView mText2;
+
+    public TwoLineListItem(Context context) {
+        this(context, null, 0);
+    }
+
+    public TwoLineListItem(Context context, AttributeSet attrs) {
+        this(context, attrs, 0); 
+    }
+
+    public TwoLineListItem(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+
+        TypedArray a = context.obtainStyledAttributes(attrs,
+                com.android.internal.R.styleable.TwoLineListItem, defStyle, 0);
+
+        a.recycle();
+    }
+
+    @Override
+    protected void onFinishInflate() {
+        super.onFinishInflate();
+        
+        mText1 = (TextView) findViewById(com.android.internal.R.id.text1);
+        mText2 = (TextView) findViewById(com.android.internal.R.id.text2);
+    }
+    
+    /**
+     * Returns a handle to the item with ID text1.
+     * @return A handle to the item with ID text1.
+     */
+    public TextView getText1() {
+        return mText1;
+    }
+    
+    /**
+     * Returns a handle to the item with ID text2.
+     * @return A handle to the item with ID text2.
+     */
+    public TextView getText2() {
+        return mText2;
+    }
+}
diff --git a/core/java/android/widget/VideoView.java b/core/java/android/widget/VideoView.java
new file mode 100644
index 0000000..1227afd
--- /dev/null
+++ b/core/java/android/widget/VideoView.java
@@ -0,0 +1,533 @@
+/*
+ * 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 android.widget;
+
+import android.app.AlertDialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.res.Resources;
+import android.media.AudioManager;
+import android.media.MediaPlayer;
+import android.media.MediaPlayer.OnCompletionListener;
+import android.media.MediaPlayer.OnErrorListener;
+import android.net.Uri;
+import android.os.PowerManager;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+import android.view.SurfaceHolder;
+import android.view.SurfaceView;
+import android.view.View;
+import android.widget.MediaController.MediaPlayerControl;
+
+import java.io.IOException;
+
+/**
+ * Displays a video file.  The VideoView class
+ * can load images from various sources (such as resources or content
+ * providers), takes care of computing its measurement from the video so that
+ * it can be used in any layout manager, and provides various display options
+ * such as scaling and tinting.
+ */
+public class VideoView extends SurfaceView implements MediaPlayerControl {
+    // settable by the client
+    private Uri         mUri;
+
+    // All the stuff we need for playing and showing a video
+    private SurfaceHolder mSurfaceHolder = null;
+    private MediaPlayer mMediaPlayer = null;
+    private boolean     mIsPrepared;
+    private int         mVideoWidth;
+    private int         mVideoHeight;
+    private int         mSurfaceWidth;
+    private int         mSurfaceHeight;
+    private MediaController mMediaController;
+    private OnCompletionListener mOnCompletionListener;
+    private MediaPlayer.OnPreparedListener mOnPreparedListener;
+    private int         mCurrentBufferPercentage;
+    private OnErrorListener mOnErrorListener;
+    private boolean     mStartWhenPrepared;
+    private int         mSeekWhenPrepared;
+
+    public VideoView(Context context) {
+        super(context);
+        initVideoView();
+    }
+    
+    public VideoView(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+        initVideoView();
+    }
+    
+    public VideoView(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+
+        initVideoView();
+    }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        //Log.i("@@@@", "onMeasure");
+        int width = getDefaultSize(mVideoWidth, widthMeasureSpec);
+        int height = getDefaultSize(mVideoHeight, heightMeasureSpec);
+        if (mVideoWidth > 0 && mVideoHeight > 0) {
+            if ( mVideoWidth * height  > width * mVideoHeight ) {
+                //Log.i("@@@", "image too tall, correcting");
+                height = width * mVideoHeight / mVideoWidth;
+            } else if ( mVideoWidth * height  < width * mVideoHeight ) {
+                //Log.i("@@@", "image too wide, correcting");
+                width = height * mVideoWidth / mVideoHeight;
+            } else {
+                //Log.i("@@@", "aspect ratio is correct: " +
+                        //width+"/"+height+"="+
+                        //mVideoWidth+"/"+mVideoHeight);
+            }
+        }
+        //Log.i("@@@@@@@@@@", "setting size: " + width + 'x' + height);
+        setMeasuredDimension(width, height);
+    }
+    
+    public int resolveAdjustedSize(int desiredSize, int measureSpec) {
+        int result = desiredSize;
+        int specMode = MeasureSpec.getMode(measureSpec);
+        int specSize =  MeasureSpec.getSize(measureSpec);
+
+        switch (specMode) {
+            case MeasureSpec.UNSPECIFIED:
+                /* Parent says we can be as big as we want. Just don't be larger
+                 * than max size imposed on ourselves.
+                 */
+                result = desiredSize;
+                break;
+
+            case MeasureSpec.AT_MOST:
+                /* Parent says we can be as big as we want, up to specSize. 
+                 * Don't be larger than specSize, and don't be larger than 
+                 * the max size imposed on ourselves.
+                 */
+                result = Math.min(desiredSize, specSize);
+                break;
+                
+            case MeasureSpec.EXACTLY:
+                // No choice. Do what we are told.
+                result = specSize;
+                break;
+        }
+        return result;
+}
+    
+    private void initVideoView() {
+        mVideoWidth = 0;
+        mVideoHeight = 0;
+        getHolder().addCallback(mSHCallback);
+        getHolder().setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
+        setFocusable(true);
+        setFocusableInTouchMode(true);
+        requestFocus();
+    }
+
+    public void setVideoPath(String path) {
+        setVideoURI(Uri.parse(path));
+    }
+
+    public void setVideoURI(Uri uri) {
+        mUri = uri;
+        mStartWhenPrepared = false;
+        mSeekWhenPrepared = 0;
+        openVideo();
+        requestLayout();
+        invalidate();
+    }
+    
+    public void stopPlayback() {
+        if (mMediaPlayer != null) {
+            mMediaPlayer.stop();
+            mMediaPlayer.release();
+            mMediaPlayer = null;
+        }
+    }
+
+    private void openVideo() {
+        if (mUri == null || mSurfaceHolder == null) {
+            // not ready for playback just yet, will try again later
+            return;
+        }
+        // Tell the music playback service to pause 
+        // TODO: these constants need to be published somewhere in the framework.
+        Intent i = new Intent("com.android.music.musicservicecommand");
+        i.putExtra("command", "pause");
+        mContext.sendBroadcast(i);
+        
+        if (mMediaPlayer != null) {
+            mMediaPlayer.reset();
+            mMediaPlayer.release();
+            mMediaPlayer = null;
+        }
+        try {
+            mMediaPlayer = new MediaPlayer();
+            mMediaPlayer.setOnPreparedListener(mPreparedListener);
+            mMediaPlayer.setOnVideoSizeChangedListener(mSizeChangedListener);
+            mIsPrepared = false;
+            mMediaPlayer.setOnCompletionListener(mCompletionListener);
+            mMediaPlayer.setOnErrorListener(mErrorListener);
+            mMediaPlayer.setOnBufferingUpdateListener(mBufferingUpdateListener);
+            mCurrentBufferPercentage = 0;
+            mMediaPlayer.setDataSource(mContext, mUri);
+            mMediaPlayer.setDisplay(mSurfaceHolder);
+            mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
+            mMediaPlayer.setScreenOnWhilePlaying(true);
+            mMediaPlayer.prepareAsync();
+            attachMediaController();
+        } catch (IOException ex) {
+            Log.w("VideoView", "Unable to open content: " + mUri, ex);
+            return;
+        } catch (IllegalArgumentException ex) {
+            Log.w("VideoView", "Unable to open content: " + mUri, ex);
+            return;
+        }
+    }
+    
+    public void setMediaController(MediaController controller) {
+        if (mMediaController != null) {
+            mMediaController.hide();
+        }
+        mMediaController = controller;
+        attachMediaController();
+    }
+
+    private void attachMediaController() {
+        if (mMediaPlayer != null && mMediaController != null) {
+            mMediaController.setMediaPlayer(this);
+            View anchorView = this.getParent() instanceof View ?
+                    (View)this.getParent() : this;
+            mMediaController.setAnchorView(anchorView);
+            mMediaController.setEnabled(mIsPrepared);
+        }
+    }
+    
+    MediaPlayer.OnVideoSizeChangedListener mSizeChangedListener =
+        new MediaPlayer.OnVideoSizeChangedListener() {
+            public void onVideoSizeChanged(MediaPlayer mp, int width, int height) {
+                mVideoWidth = mp.getVideoWidth();
+                mVideoHeight = mp.getVideoHeight();
+                if (mVideoWidth != 0 && mVideoHeight != 0) {
+                    getHolder().setFixedSize(mVideoWidth, mVideoHeight);
+                }
+            }
+    };
+    
+    MediaPlayer.OnPreparedListener mPreparedListener = new MediaPlayer.OnPreparedListener() {
+        public void onPrepared(MediaPlayer mp) {
+            // briefly show the mediacontroller
+            mIsPrepared = true;
+            if (mOnPreparedListener != null) {
+                mOnPreparedListener.onPrepared(mMediaPlayer);
+            }
+            if (mMediaController != null) {
+                mMediaController.setEnabled(true);
+            }
+            mVideoWidth = mp.getVideoWidth();
+            mVideoHeight = mp.getVideoHeight();
+            if (mVideoWidth != 0 && mVideoHeight != 0) {
+                //Log.i("@@@@", "video size: " + mVideoWidth +"/"+ mVideoHeight);
+                getHolder().setFixedSize(mVideoWidth, mVideoHeight);
+                if (mSurfaceWidth == mVideoWidth && mSurfaceHeight == mVideoHeight) {
+                    // We didn't actually change the size (it was already at the size
+                    // we need), so we won't get a "surface changed" callback, so
+                    // start the video here instead of in the callback.
+                    if (mSeekWhenPrepared != 0) {
+                        mMediaPlayer.seekTo(mSeekWhenPrepared);
+                        mSeekWhenPrepared = 0;
+                    }
+                    if (mStartWhenPrepared) {
+                        mMediaPlayer.start();
+                        mStartWhenPrepared = false;
+                        if (mMediaController != null) {
+                            mMediaController.show();
+                        }
+                    } else if (!isPlaying() &&
+                            (mSeekWhenPrepared != 0 || getCurrentPosition() > 0)) {
+                       if (mMediaController != null) {
+                           // Show the media controls when we're paused into a video and make 'em stick.
+                           mMediaController.show(0);
+                       }
+                   }
+                }
+            } else {
+                // We don't know the video size yet, but should start anyway.
+                // The video size might be reported to us later.
+                if (mSeekWhenPrepared != 0) {
+                    mMediaPlayer.seekTo(mSeekWhenPrepared);
+                    mSeekWhenPrepared = 0;
+                }
+                if (mStartWhenPrepared) {
+                    mMediaPlayer.start();
+                    mStartWhenPrepared = false;
+                }
+            }
+        }
+    };
+
+    private MediaPlayer.OnCompletionListener mCompletionListener =
+        new MediaPlayer.OnCompletionListener() {
+        public void onCompletion(MediaPlayer mp) {
+            if (mMediaController != null) {
+                mMediaController.hide();
+            }
+            if (mOnCompletionListener != null) {
+                mOnCompletionListener.onCompletion(mMediaPlayer);
+            }
+        }
+    };
+
+    private MediaPlayer.OnErrorListener mErrorListener =
+        new MediaPlayer.OnErrorListener() {
+        public boolean onError(MediaPlayer mp, int a, int b) {
+            Log.d("VideoView", "Error: " + a + "," + b);
+            if (mMediaController != null) {
+                mMediaController.hide();
+            }
+
+            /* If an error handler has been supplied, use it and finish. */
+            if (mOnErrorListener != null) {
+                if (mOnErrorListener.onError(mMediaPlayer, a, b)) {
+                    return true;
+                }
+            }
+
+            /* Otherwise, pop up an error dialog so the user knows that
+             * something bad has happened. Only try and pop up the dialog
+             * if we're attached to a window. When we're going away and no
+             * longer have a window, don't bother showing the user an error.
+             */
+            if (getWindowToken() != null) {
+                Resources r = mContext.getResources();
+                new AlertDialog.Builder(mContext)
+                        .setTitle(com.android.internal.R.string.VideoView_error_title)
+                        .setMessage(com.android.internal.R.string.VideoView_error_text_unknown)
+                        .setPositiveButton(com.android.internal.R.string.VideoView_error_button,
+                                new DialogInterface.OnClickListener() {
+                                    public void onClick(DialogInterface dialog, int whichButton) {
+                                        /* If we get here, there is no onError listener, so
+                                         * at least inform them that the video is over.
+                                         */
+                                        if (mOnCompletionListener != null) {
+                                            mOnCompletionListener.onCompletion(mMediaPlayer);
+                                        }
+                                    }
+                                })
+                        .setCancelable(false)
+                        .show();
+            }
+            return true;
+        }
+    };
+
+    private MediaPlayer.OnBufferingUpdateListener mBufferingUpdateListener =
+        new MediaPlayer.OnBufferingUpdateListener() {
+        public void onBufferingUpdate(MediaPlayer mp, int percent) {
+            mCurrentBufferPercentage = percent;
+        }
+    };
+
+    /**
+     * Register a callback to be invoked when the media file
+     * is loaded and ready to go.
+     *
+     * @param l The callback that will be run
+     */
+    public void setOnPreparedListener(MediaPlayer.OnPreparedListener l)
+    {
+        mOnPreparedListener = l;
+    }
+
+    /**
+     * Register a callback to be invoked when the end of a media file
+     * has been reached during playback.
+     *
+     * @param l The callback that will be run
+     */
+    public void setOnCompletionListener(OnCompletionListener l)
+    {
+        mOnCompletionListener = l;
+    }
+
+    /**
+     * Register a callback to be invoked when an error occurs
+     * during playback or setup.  If no listener is specified,
+     * or if the listener returned false, VideoView will inform
+     * the user of any errors.
+     *
+     * @param l The callback that will be run
+     */
+    public void setOnErrorListener(OnErrorListener l)
+    {
+        mOnErrorListener = l;
+    }
+
+    SurfaceHolder.Callback mSHCallback = new SurfaceHolder.Callback()
+    {
+        public void surfaceChanged(SurfaceHolder holder, int format,
+                                    int w, int h)
+        {
+            mSurfaceWidth = w;
+            mSurfaceHeight = h;
+            if (mMediaPlayer != null && mIsPrepared && mVideoWidth == w && mVideoHeight == h) {
+                if (mSeekWhenPrepared != 0) {
+                    mMediaPlayer.seekTo(mSeekWhenPrepared);
+                    mSeekWhenPrepared = 0;
+                }
+                mMediaPlayer.start();
+                if (mMediaController != null) {
+                    mMediaController.show();
+                }
+            }
+        }
+
+        public void surfaceCreated(SurfaceHolder holder)
+        {
+            mSurfaceHolder = holder;
+            openVideo();
+        }
+
+        public void surfaceDestroyed(SurfaceHolder holder)
+        {
+            // after we return from this we can't use the surface any more
+            mSurfaceHolder = null;
+            if (mMediaController != null) mMediaController.hide();
+            if (mMediaPlayer != null) {
+                mMediaPlayer.reset();
+                mMediaPlayer.release();
+                mMediaPlayer = null;
+            }
+        }
+    };
+
+    @Override
+    public boolean onTouchEvent(MotionEvent ev) {
+        if (mIsPrepared && mMediaPlayer != null && mMediaController != null) {
+            toggleMediaControlsVisiblity();
+        }
+        return false;
+    }
+    
+    @Override
+    public boolean onTrackballEvent(MotionEvent ev) {
+        if (mIsPrepared && mMediaPlayer != null && mMediaController != null) {
+            toggleMediaControlsVisiblity();
+        }
+        return false;
+    }
+    
+    @Override
+    public boolean onKeyDown(int keyCode, KeyEvent event)
+    {
+        if (mIsPrepared &&
+                keyCode != KeyEvent.KEYCODE_BACK &&
+                keyCode != KeyEvent.KEYCODE_VOLUME_UP &&
+                keyCode != KeyEvent.KEYCODE_VOLUME_DOWN &&
+                keyCode != KeyEvent.KEYCODE_MENU &&
+                keyCode != KeyEvent.KEYCODE_CALL &&
+                keyCode != KeyEvent.KEYCODE_ENDCALL &&
+                mMediaPlayer != null &&
+                mMediaController != null) {
+            if (keyCode == KeyEvent.KEYCODE_HEADSETHOOK ||
+                    keyCode == KeyEvent.KEYCODE_PLAYPAUSE) {
+                if (mMediaPlayer.isPlaying()) {
+                    pause();
+                    mMediaController.show();
+                } else {
+                    start();
+                    mMediaController.hide();
+                }
+                return true;
+            } else if (keyCode == KeyEvent.KEYCODE_STOP 
+                    && mMediaPlayer.isPlaying()) {
+                pause();
+                mMediaController.show();
+            } else {
+                toggleMediaControlsVisiblity();
+            }
+        }
+
+        return super.onKeyDown(keyCode, event);
+    }
+
+    private void toggleMediaControlsVisiblity() {
+        if (mMediaController.isShowing()) { 
+            mMediaController.hide();
+        } else {
+            mMediaController.show();
+        }
+    }
+    
+    public void start() {
+        if (mMediaPlayer != null && mIsPrepared) {
+                mMediaPlayer.start();
+                mStartWhenPrepared = false;
+        } else {
+            mStartWhenPrepared = true;
+        }
+    }
+    
+    public void pause() {
+        if (mMediaPlayer != null && mIsPrepared) {
+            if (mMediaPlayer.isPlaying()) {
+                mMediaPlayer.pause();
+            }
+        }
+        mStartWhenPrepared = false;
+    }
+    
+    public int getDuration() {
+        if (mMediaPlayer != null && mIsPrepared) {
+            return mMediaPlayer.getDuration();
+        }
+        return -1;
+    }
+    
+    public int getCurrentPosition() {
+        if (mMediaPlayer != null && mIsPrepared) {
+            return mMediaPlayer.getCurrentPosition();
+        }
+        return 0;
+    }
+    
+    public void seekTo(int msec) {
+        if (mMediaPlayer != null && mIsPrepared) {
+            mMediaPlayer.seekTo(msec);
+        } else {
+            mSeekWhenPrepared = msec;
+        }
+    }    
+            
+    public boolean isPlaying() {
+        if (mMediaPlayer != null && mIsPrepared) {
+            return mMediaPlayer.isPlaying();
+        }
+        return false;
+    }
+    
+    public int getBufferPercentage() {
+        if (mMediaPlayer != null) {
+            return mCurrentBufferPercentage;
+        }
+        return 0;
+    }
+}
diff --git a/core/java/android/widget/ViewAnimator.java b/core/java/android/widget/ViewAnimator.java
new file mode 100644
index 0000000..fa8935e3
--- /dev/null
+++ b/core/java/android/widget/ViewAnimator.java
@@ -0,0 +1,300 @@
+/*
+ * 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 android.widget;
+
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.animation.Animation;
+import android.view.animation.AnimationUtils;
+
+/**
+ * Base class for a {@link FrameLayout} container that will perform animations
+ * when switching between its views.
+ *
+ * @attr ref android.R.styleable#ViewAnimator_inAnimation
+ * @attr ref android.R.styleable#ViewAnimator_outAnimation
+ */
+public class ViewAnimator extends FrameLayout {
+
+    int mWhichChild = 0;
+    boolean mFirstTime = true;
+    boolean mAnimateFirstTime = true;
+
+    Animation mInAnimation;
+    Animation mOutAnimation;
+
+    public ViewAnimator(Context context) {
+        super(context);
+        initViewAnimator();
+    }
+
+    public ViewAnimator(Context context, AttributeSet attrs) {
+        super(context, attrs);
+
+        TypedArray a = context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.ViewAnimator);
+        int resource = a.getResourceId(com.android.internal.R.styleable.ViewAnimator_inAnimation, 0);
+        if (resource > 0) {
+            setInAnimation(context, resource);
+        }
+
+        resource = a.getResourceId(com.android.internal.R.styleable.ViewAnimator_outAnimation, 0);
+        if (resource > 0) {
+            setOutAnimation(context, resource);
+        }
+        a.recycle();
+
+        initViewAnimator();
+    }
+
+    private void initViewAnimator() {
+        mMeasureAllChildren = true;
+    }
+    
+    /**
+     * Sets which child view will be displayed.
+     * 
+     * @param whichChild the index of the child view to display
+     */
+    public void setDisplayedChild(int whichChild) {
+        mWhichChild = whichChild;
+        if (whichChild >= getChildCount()) {
+            mWhichChild = 0;
+        } else if (whichChild < 0) {
+            mWhichChild = getChildCount() - 1;
+        }
+        boolean hasFocus = getFocusedChild() != null;
+        // This will clear old focus if we had it
+        showOnly(mWhichChild);
+        if (hasFocus) {
+            // Try to retake focus if we had it
+            requestFocus(FOCUS_FORWARD);
+        }
+    }
+    
+    /**
+     * Returns the index of the currently displayed child view.
+     */
+    public int getDisplayedChild() {
+        return mWhichChild;
+    }
+    
+    /**
+     * Manually shows the next child.
+     */
+    public void showNext() {
+        setDisplayedChild(mWhichChild + 1);
+    }
+
+    /**
+     * Manually shows the previous child.
+     */
+    public void showPrevious() {
+        setDisplayedChild(mWhichChild - 1);
+    }
+
+    /**
+     * Shows only the specified child. The other displays Views exit the screen
+     * with the {@link #getOutAnimation() out animation} and the specified child
+     * enters the screen with the {@link #getInAnimation() in animation}.
+     *
+     * @param childIndex The index of the child to be shown.
+     */
+    void showOnly(int childIndex) {
+        final int count = getChildCount();
+        for (int i = 0; i < count; i++) {
+            final View child = getChildAt(i);
+            if (i == childIndex) {
+                if ((!mFirstTime || mAnimateFirstTime) && mInAnimation != null) {
+                    child.startAnimation(mInAnimation);
+                }
+                child.setVisibility(View.VISIBLE);
+                mFirstTime = false;
+            } else {
+                if (mOutAnimation != null && child.getVisibility() == View.VISIBLE) {
+                    child.startAnimation(mOutAnimation);
+                } else if (child.getAnimation() == mInAnimation)
+                    child.clearAnimation();
+                child.setVisibility(View.GONE);
+            }
+        }
+    }
+
+    @Override
+    public void addView(View child, int index, ViewGroup.LayoutParams params) {
+        super.addView(child, index, params);
+        if (getChildCount() == 1) {
+            child.setVisibility(View.VISIBLE);
+        } else {
+            child.setVisibility(View.GONE);
+        }
+    }
+
+    @Override
+    public void removeAllViews() {
+        super.removeAllViews();
+        mWhichChild = 0;
+        mFirstTime = true;
+    }
+
+    @Override
+    public void removeView(View view) {
+        final int index = indexOfChild(view);
+        if (index >= 0) {
+            removeViewAt(index);
+        }
+    }
+
+    @Override
+    public void removeViewAt(int index) {
+        super.removeViewAt(index);
+        final int childCount = getChildCount();
+        if (childCount == 0) {
+            mWhichChild = 0;
+            mFirstTime = true;
+        } else if (mWhichChild >= childCount) {
+            // Displayed is above child count, so float down to top of stack
+            setDisplayedChild(childCount - 1);
+        } else if (mWhichChild == index) {
+            // Displayed was removed, so show the new child living in its place
+            setDisplayedChild(mWhichChild);
+        }
+    }
+
+    public void removeViewInLayout(View view) {
+        removeView(view);
+    }
+
+    public void removeViews(int start, int count) {
+        super.removeViews(start, count);
+        if (getChildCount() == 0) {
+            mWhichChild = 0;
+            mFirstTime = true;
+        } else if (mWhichChild >= start && mWhichChild < start + count) {
+            // Try showing new displayed child, wrapping if needed
+            setDisplayedChild(mWhichChild);
+        }
+    }
+
+    public void removeViewsInLayout(int start, int count) {
+        removeViews(start, count);
+    }
+
+    /**
+     * Returns the View corresponding to the currently displayed child.
+     *
+     * @return The View currently displayed.
+     *
+     * @see #getDisplayedChild()
+     */
+    public View getCurrentView() {
+        return getChildAt(mWhichChild);
+    }
+
+    /**
+     * Returns the current animation used to animate a View that enters the screen.
+     *
+     * @return An Animation or null if none is set.
+     *
+     * @see #setInAnimation(android.view.animation.Animation)
+     * @see #setInAnimation(android.content.Context, int)
+     */
+    public Animation getInAnimation() {
+        return mInAnimation;
+    }
+
+    /**
+     * Specifies the animation used to animate a View that enters the screen.
+     *
+     * @param inAnimation The animation started when a View enters the screen.
+     *
+     * @see #getInAnimation()
+     * @see #setInAnimation(android.content.Context, int)
+     */
+    public void setInAnimation(Animation inAnimation) {
+        mInAnimation = inAnimation;
+    }
+
+    /**
+     * Returns the current animation used to animate a View that exits the screen.
+     *
+     * @return An Animation or null if none is set.
+     *
+     * @see #setOutAnimation(android.view.animation.Animation)
+     * @see #setOutAnimation(android.content.Context, int)
+     */
+    public Animation getOutAnimation() {
+        return mOutAnimation;
+    }
+
+    /**
+     * Specifies the animation used to animate a View that exit the screen.
+     *
+     * @param outAnimation The animation started when a View exit the screen.
+     *
+     * @see #getOutAnimation()
+     * @see #setOutAnimation(android.content.Context, int)
+     */
+    public void setOutAnimation(Animation outAnimation) {
+        mOutAnimation = outAnimation;
+    }
+
+    /**
+     * Specifies the animation used to animate a View that enters the screen.
+     *
+     * @param context The application's environment.
+     * @param resourceID The resource id of the animation.
+     *
+     * @see #getInAnimation()
+     * @see #setInAnimation(android.view.animation.Animation)
+     */
+    public void setInAnimation(Context context, int resourceID) {
+        setInAnimation(AnimationUtils.loadAnimation(context, resourceID));
+    }
+
+    /**
+     * Specifies the animation used to animate a View that exit the screen.
+     *
+     * @param context The application's environment.
+     * @param resourceID The resource id of the animation.
+     *
+     * @see #getOutAnimation()
+     * @see #setOutAnimation(android.view.animation.Animation)
+     */
+    public void setOutAnimation(Context context, int resourceID) {
+        setOutAnimation(AnimationUtils.loadAnimation(context, resourceID));
+    }
+
+    /**
+     * Indicates whether the current View should be animated the first time
+     * the ViewAnimation is displayed.
+     *
+     * @param animate True to animate the current View the first time it is displayed,
+     *                false otherwise.
+     */
+    public void setAnimateFirstView(boolean animate) {
+        mAnimateFirstTime = animate;
+    }
+
+    @Override
+    public int getBaseline() {
+        return (getCurrentView() != null) ? getCurrentView().getBaseline() : super.getBaseline();
+    }
+}
diff --git a/core/java/android/widget/ViewFlipper.java b/core/java/android/widget/ViewFlipper.java
new file mode 100644
index 0000000..8a7946b
--- /dev/null
+++ b/core/java/android/widget/ViewFlipper.java
@@ -0,0 +1,103 @@
+/*
+ * 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 android.widget;
+
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.os.Handler;
+import android.os.Message;
+import android.util.AttributeSet;
+import android.widget.RemoteViews.RemoteView;
+
+/**
+ * Simple {@link ViewAnimator} that will animate between two or more views
+ * that have been added to it.  Only one child is shown at a time.  If
+ * requested, can automatically flip between each child at a regular interval.
+ *
+ * @attr ref android.R.styleable#ViewFlipper_flipInterval
+ */
+public class ViewFlipper extends ViewAnimator {
+    private int mFlipInterval = 3000;
+    private boolean mKeepFlipping = false;
+
+    public ViewFlipper(Context context) {
+        super(context);
+    }
+
+    public ViewFlipper(Context context, AttributeSet attrs) {
+        super(context, attrs);
+
+        TypedArray a = context.obtainStyledAttributes(attrs,
+                com.android.internal.R.styleable.ViewFlipper);
+        mFlipInterval = a.getInt(com.android.internal.R.styleable.ViewFlipper_flipInterval,
+                3000);
+        a.recycle();
+    }
+
+    /**
+     * How long to wait before flipping to the next view
+     * 
+     * @param milliseconds
+     *            time in milliseconds
+     */
+    @android.view.RemotableViewMethod
+    public void setFlipInterval(int milliseconds) {
+        mFlipInterval = milliseconds;
+    }
+
+    /**
+     * Start a timer to cycle through child views
+     */
+    public void startFlipping() {
+        if (!mKeepFlipping) {
+            mKeepFlipping = true;
+            showOnly(mWhichChild);
+            Message msg = mHandler.obtainMessage(FLIP_MSG);
+            mHandler.sendMessageDelayed(msg, mFlipInterval);
+        }
+    }
+
+    /**
+     * No more flips
+     */
+    public void stopFlipping() {
+        mKeepFlipping = false;
+    }
+
+    /**
+     * Returns true if the child views are flipping.
+     */
+    public boolean isFlipping() {
+        return mKeepFlipping;
+    }
+
+    private final int FLIP_MSG = 1;
+
+    private final Handler mHandler = new Handler() {
+        @Override
+        public void handleMessage(Message msg) {
+            if (msg.what == FLIP_MSG) {
+                if (mKeepFlipping) {
+                    showNext();
+                    msg = obtainMessage(FLIP_MSG);
+                    sendMessageDelayed(msg, mFlipInterval);
+                }
+            }
+        }
+    };
+}
diff --git a/core/java/android/widget/ViewSwitcher.java b/core/java/android/widget/ViewSwitcher.java
new file mode 100644
index 0000000..f4f23a8
--- /dev/null
+++ b/core/java/android/widget/ViewSwitcher.java
@@ -0,0 +1,135 @@
+/*
+ * 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 android.widget;
+
+import java.util.Map;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.ViewGroup;
+
+/**
+ * {@link ViewAnimator} that switches between two views, and has a factory
+ * from which these views are created.  You can either use the factory to
+ * create the views, or add them yourself.  A ViewSwitcher can only have two
+ * child views, of which only one is shown at a time.
+ */
+public class ViewSwitcher extends ViewAnimator {
+    /**
+     * The factory used to create the two children.
+     */
+    ViewFactory mFactory;
+
+    /**
+     * Creates a new empty ViewSwitcher.
+     *
+     * @param context the application's environment
+     */
+    public ViewSwitcher(Context context) {
+        super(context);
+    }
+
+    /**
+     * Creates a new empty ViewSwitcher for the given context and with the
+     * specified set attributes.
+     *
+     * @param context the application environment
+     * @param attrs a collection of attributes
+     */
+    public ViewSwitcher(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @throws IllegalStateException if this switcher already contains two children
+     */
+    @Override
+    public void addView(View child, int index, ViewGroup.LayoutParams params) {
+        if (getChildCount() >= 2) {
+            throw new IllegalStateException("Can't add more than 2 views to a ViewSwitcher");
+        }
+        super.addView(child, index, params);
+    }
+
+    /**
+     * Returns the next view to be displayed.
+     *
+     * @return the view that will be displayed after the next views flip.
+     */
+    public View getNextView() {
+        int which = mWhichChild == 0 ? 1 : 0;
+        return getChildAt(which);
+    }
+
+    private View obtainView() {
+        View child = mFactory.makeView();
+        LayoutParams lp = (LayoutParams) child.getLayoutParams();
+        if (lp == null) {
+            lp = new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.WRAP_CONTENT);
+        }
+        addView(child, lp);
+        return child;
+    }
+
+    /**
+     * Sets the factory used to create the two views between which the
+     * ViewSwitcher will flip. Instead of using a factory, you can call
+     * {@link #addView(android.view.View, int, android.view.ViewGroup.LayoutParams)}
+     * twice.
+     *
+     * @param factory the view factory used to generate the switcher's content
+     */
+    public void setFactory(ViewFactory factory) {
+        mFactory = factory;
+        obtainView();
+        obtainView();
+    }
+
+    /**
+     * Reset the ViewSwitcher to hide all of the existing views and to make it
+     * think that the first time animation has not yet played.
+     */
+    public void reset() {
+        mFirstTime = true;
+        View v;
+        v = getChildAt(0);
+        if (v != null) {
+            v.setVisibility(View.GONE);
+        }
+        v = getChildAt(1);
+        if (v != null) {
+            v.setVisibility(View.GONE);
+        }
+    }
+
+    /**
+     * Creates views in a ViewSwitcher.
+     */
+    public interface ViewFactory {
+        /**
+         * Creates a new {@link android.view.View} to be added in a
+         * {@link android.widget.ViewSwitcher}.
+         *
+         * @return a {@link android.view.View}
+         */
+        View makeView();
+    }
+}
+
diff --git a/core/java/android/widget/WrapperListAdapter.java b/core/java/android/widget/WrapperListAdapter.java
new file mode 100644
index 0000000..7fe12ae
--- /dev/null
+++ b/core/java/android/widget/WrapperListAdapter.java
@@ -0,0 +1,32 @@
+/*
+ * 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 android.widget;
+
+/**
+ * List adapter that wraps another list adapter. The wrapped adapter can be retrieved
+ * by calling {@link #getWrappedAdapter()}.
+ *
+ * @see ListView
+ */
+public interface WrapperListAdapter extends ListAdapter {
+    /**
+     * Returns the adapter wrapped by this list adapter.
+     *
+     * @return The {@link android.widget.ListAdapter} wrapped by this adapter.
+     */
+    public ListAdapter getWrappedAdapter();
+}
diff --git a/core/java/android/widget/ZoomButton.java b/core/java/android/widget/ZoomButton.java
new file mode 100644
index 0000000..0df919d
--- /dev/null
+++ b/core/java/android/widget/ZoomButton.java
@@ -0,0 +1,112 @@
+/*
+ * 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 android.widget;
+
+import android.content.Context;
+import android.os.Handler;
+import android.util.AttributeSet;
+import android.view.GestureDetector;
+import android.view.HapticFeedbackConstants;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.GestureDetector.SimpleOnGestureListener;
+import android.view.View.OnLongClickListener;
+
+
+public class ZoomButton extends ImageButton implements OnLongClickListener {
+
+    private final Handler mHandler;
+    private final Runnable mRunnable = new Runnable() {
+        public void run() {
+            if ((mOnClickListener != null) && mIsInLongpress && isEnabled()) {
+                mOnClickListener.onClick(ZoomButton.this);
+                mHandler.postDelayed(this, mZoomSpeed);
+            }
+        }
+    };
+    private final GestureDetector mGestureDetector;
+    
+    private long mZoomSpeed = 1000;
+    private boolean mIsInLongpress;
+    
+    public ZoomButton(Context context) {
+        this(context, null);
+    }
+
+    public ZoomButton(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+    
+    public ZoomButton(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+        mHandler = new Handler();
+        mGestureDetector = new GestureDetector(context, new SimpleOnGestureListener() {
+            @Override
+            public void onLongPress(MotionEvent e) {
+                performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
+                onLongClick(ZoomButton.this);
+            }
+        });
+        setOnLongClickListener(this);
+    }
+
+    @Override
+    public boolean onTouchEvent(MotionEvent event) {
+        mGestureDetector.onTouchEvent(event);
+        if ((event.getAction() == MotionEvent.ACTION_CANCEL)
+                || (event.getAction() == MotionEvent.ACTION_UP)) {
+            mIsInLongpress = false;
+        }
+        return super.onTouchEvent(event);
+    }
+        
+    public void setZoomSpeed(long speed) {
+        mZoomSpeed = speed;
+    }
+
+    public boolean onLongClick(View v) {
+        mIsInLongpress = true;
+        mHandler.post(mRunnable);
+        return true;
+    }
+        
+    @Override
+    public boolean onKeyUp(int keyCode, KeyEvent event) {
+        mIsInLongpress = false;
+        return super.onKeyUp(keyCode, event);
+    }
+    
+    @Override
+    public void setEnabled(boolean enabled) {
+        if (!enabled) {
+            
+            /* If we're being disabled reset the state back to unpressed
+             * as disabled views don't get events and therefore we won't
+             * get the up event to reset the state.
+             */
+            setPressed(false);
+        }
+        super.setEnabled(enabled);
+    }
+    
+    @Override
+    public boolean dispatchUnhandledMove(View focused, int direction) {
+        clearFocus();
+        return super.dispatchUnhandledMove(focused, direction);
+    }
+}
diff --git a/core/java/android/widget/ZoomButtonsController.java b/core/java/android/widget/ZoomButtonsController.java
new file mode 100644
index 0000000..ec45e23
--- /dev/null
+++ b/core/java/android/widget/ZoomButtonsController.java
@@ -0,0 +1,478 @@
+/*
+ * 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 android.widget;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.graphics.PixelFormat;
+import android.graphics.Rect;
+import android.os.Handler;
+import android.os.Message;
+import android.provider.Settings;
+import android.view.Gravity;
+import android.view.KeyEvent;
+import android.view.LayoutInflater;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewConfiguration;
+import android.view.WindowManager;
+import android.view.View.OnClickListener;
+import android.view.WindowManager.LayoutParams;
+
+// TODO: make sure no px values exist, only dip (scale if necessary from Viewconfiguration)
+
+/**
+ * TODO: Docs
+ *
+ * If you are using this with a custom View, please call
+ * {@link #setVisible(boolean) setVisible(false)} from the
+ * {@link View#onDetachedFromWindow}.
+ *
+ * @hide
+ */
+public class ZoomButtonsController implements View.OnTouchListener {
+
+    private static final String TAG = "ZoomButtonsController";
+
+    private static final int ZOOM_CONTROLS_TIMEOUT =
+            (int) ViewConfiguration.getZoomControlsTimeout();
+
+    // TODO: scaled to density
+    private static final int ZOOM_CONTROLS_TOUCH_PADDING = 20;
+    
+    private Context mContext;
+    private WindowManager mWindowManager;
+
+    /**
+     * The view that is being zoomed by this zoom ring.
+     */
+    private View mOwnerView;
+
+    /**
+     * The bounds of the owner view in global coordinates. This is recalculated
+     * each time the zoom ring is shown.
+     */
+    private Rect mOwnerViewBounds = new Rect();
+
+    /**
+     * The container that is added as a window.
+     */
+    private FrameLayout mContainer;
+    private LayoutParams mContainerLayoutParams;
+    private int[] mContainerLocation = new int[2];
+
+    private ZoomControls mControls;
+    
+    /**
+     * The view (or null) that should receive touch events. This will get set if
+     * the touch down hits the container. It will be reset on the touch up.
+     */
+    private View mTouchTargetView;
+    /**
+     * The {@link #mTouchTargetView}'s location in window, set on touch down.
+     */
+    private int[] mTouchTargetLocationInWindow = new int[2];
+    /**
+     * If the zoom ring is dismissed but the user is still in a touch
+     * interaction, we set this to true. This will ignore all touch events until
+     * up/cancel, and then set the owner's touch listener to null.
+     */
+    private boolean mReleaseTouchListenerOnUp;
+
+    private boolean mIsVisible;
+
+    private Rect mTempRect = new Rect();
+
+    private OnZoomListener mCallback;
+
+    /**
+     * When showing the zoom, we add the view as a new window. However, there is
+     * logic that needs to know the size of the zoom which is determined after
+     * it's laid out. Therefore, we must post this logic onto the UI thread so
+     * it will be exceuted AFTER the layout. This is the logic.
+     */
+    private Runnable mPostedVisibleInitializer;
+
+    private IntentFilter mConfigurationChangedFilter =
+            new IntentFilter(Intent.ACTION_CONFIGURATION_CHANGED);
+
+    private BroadcastReceiver mConfigurationChangedReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            if (!mIsVisible) return;
+
+            mHandler.removeMessages(MSG_POST_CONFIGURATION_CHANGED);
+            mHandler.sendEmptyMessage(MSG_POST_CONFIGURATION_CHANGED);
+        }
+    };
+
+    /** When configuration changes, this is called after the UI thread is idle. */
+    private static final int MSG_POST_CONFIGURATION_CHANGED = 2;
+    /** Used to delay the zoom ring dismissal. */
+    private static final int MSG_DISMISS_ZOOM_RING = 3;
+    /**
+     * If setVisible(true) is called and the owner view's window token is null,
+     * we delay the setVisible(true) call until it is not null.
+     */
+    private static final int MSG_POST_SET_VISIBLE = 4;
+    
+    private Handler mHandler = new Handler() {
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case MSG_POST_CONFIGURATION_CHANGED:
+                    onPostConfigurationChanged();
+                    break;
+
+                case MSG_DISMISS_ZOOM_RING:
+                    setVisible(false);
+                    break;
+                    
+                case MSG_POST_SET_VISIBLE:
+                    if (mOwnerView.getWindowToken() == null) {
+                        // Doh, it is still null, throw an exception
+                        throw new IllegalArgumentException(
+                                "Cannot make the zoom ring visible if the owner view is " +
+                                "not attached to a window.");
+                    }
+                    setVisible(true);
+                    break;
+            }
+
+        }
+    };
+
+    public ZoomButtonsController(Context context, View ownerView) {
+        mContext = context;
+        mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
+        mOwnerView = ownerView;
+
+        mContainer = createContainer();
+    }
+
+    private FrameLayout createContainer() {
+        LayoutParams lp = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
+        lp.gravity = Gravity.BOTTOM | Gravity.CENTER;
+        lp.flags = LayoutParams.FLAG_NOT_TOUCHABLE |
+                LayoutParams.FLAG_LAYOUT_NO_LIMITS;
+        lp.height = LayoutParams.WRAP_CONTENT;
+        lp.width = LayoutParams.FILL_PARENT;
+        lp.type = LayoutParams.TYPE_APPLICATION_PANEL;
+        lp.format = PixelFormat.TRANSPARENT;
+        // TODO: make a new animation for this
+        lp.windowAnimations = com.android.internal.R.style.Animation_InputMethodFancy;
+        mContainerLayoutParams = lp;
+        
+        FrameLayout container = new FrameLayout(mContext);
+        container.setLayoutParams(lp);
+        container.setMeasureAllChildren(true);
+        
+        LayoutInflater inflater = (LayoutInflater) mContext
+                .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+        inflater.inflate(com.android.internal.R.layout.zoom_magnify, container);
+        
+        mControls = (ZoomControls) container.findViewById(com.android.internal.R.id.zoomControls);
+        mControls.setOnZoomInClickListener(new OnClickListener() {
+            public void onClick(View v) {
+                dismissControlsDelayed(ZOOM_CONTROLS_TIMEOUT);
+                if (mCallback != null) mCallback.onZoom(true);
+            }
+        });
+        mControls.setOnZoomOutClickListener(new OnClickListener() {
+            public void onClick(View v) {
+                dismissControlsDelayed(ZOOM_CONTROLS_TIMEOUT);
+                if (mCallback != null) mCallback.onZoom(false);
+            }
+        });
+
+        View overview = container.findViewById(com.android.internal.R.id.zoomMagnify);
+        overview.setVisibility(View.GONE);
+        overview.setOnClickListener(new OnClickListener() {
+            public void onClick(View v) {
+                dismissControlsDelayed(ZOOM_CONTROLS_TIMEOUT);
+                if (mCallback != null) mCallback.onOverview();
+            }
+        });
+        
+        return container;
+    }
+    
+    public void setCallback(OnZoomListener callback) {
+        mCallback = callback;
+    }
+
+    public void setFocusable(boolean focusable) {
+        if (focusable) {
+            mContainerLayoutParams.flags &= ~LayoutParams.FLAG_NOT_FOCUSABLE; 
+        } else {
+            mContainerLayoutParams.flags |= LayoutParams.FLAG_NOT_FOCUSABLE;
+        }
+        
+        if (mIsVisible) {
+            mWindowManager.updateViewLayout(mContainer, mContainerLayoutParams);
+        }
+    }
+    
+    public void setOverviewVisible(boolean visible) {
+        mContainer.findViewById(com.android.internal.R.id.zoomMagnify)
+                .setVisibility(visible ? View.VISIBLE : View.GONE);
+    }
+
+    public boolean isVisible() {
+        return mIsVisible;
+    }
+
+    public void setVisible(boolean visible) {
+
+        if (!useThisZoom(mContext)) return;
+        
+        if (visible) {
+            if (mOwnerView.getWindowToken() == null) {
+                /*
+                 * We need a window token to show ourselves, maybe the owner's
+                 * window hasn't been created yet but it will have been by the
+                 * time the looper is idle, so post the setVisible(true) call.
+                 */
+                if (!mHandler.hasMessages(MSG_POST_SET_VISIBLE)) {
+                    mHandler.sendEmptyMessage(MSG_POST_SET_VISIBLE);
+                }
+                return;
+            }
+            
+            dismissControlsDelayed(ZOOM_CONTROLS_TIMEOUT);
+        }
+
+        if (mIsVisible == visible) {
+            return;
+        }
+        mIsVisible = visible;
+
+        if (visible) {
+            if (mContainerLayoutParams.token == null) {
+                mContainerLayoutParams.token = mOwnerView.getWindowToken();
+            }
+
+            mWindowManager.addView(mContainer, mContainerLayoutParams);
+
+            if (mPostedVisibleInitializer == null) {
+                mPostedVisibleInitializer = new Runnable() {
+                    public void run() {
+                        refreshPositioningVariables();
+
+                        if (mCallback != null) {
+                            mCallback.onVisibilityChanged(true);
+                        }
+                    }
+                };
+            }
+
+            mHandler.post(mPostedVisibleInitializer);
+
+            // Handle configuration changes when visible
+            mContext.registerReceiver(mConfigurationChangedReceiver, mConfigurationChangedFilter);
+
+            // Steal touches events from the owner
+            mOwnerView.setOnTouchListener(this);
+            mReleaseTouchListenerOnUp = false;
+
+        } else {
+            // Don't want to steal any more touches
+            if (mTouchTargetView != null) {
+                // We are still stealing the touch events for this touch
+                // sequence, so release the touch listener later
+                mReleaseTouchListenerOnUp = true;
+            } else {
+                mOwnerView.setOnTouchListener(null);
+            }
+
+            // No longer care about configuration changes
+            mContext.unregisterReceiver(mConfigurationChangedReceiver);
+
+            mWindowManager.removeView(mContainer);
+            mHandler.removeCallbacks(mPostedVisibleInitializer);
+
+            if (mCallback != null) {
+                mCallback.onVisibilityChanged(false);
+            }
+        }
+
+    }
+
+    /**
+     * TODO: docs
+     *
+     * Notes:
+     * - Please ensure you set your View to INVISIBLE not GONE when hiding it.
+     *
+     * @return TODO
+     */
+    public FrameLayout getContainer() {
+        return mContainer;
+    }
+
+    public int getZoomRingId() {
+        return mControls.getId();
+    }
+
+    private void dismissControlsDelayed(int delay) {
+        mHandler.removeMessages(MSG_DISMISS_ZOOM_RING);
+        mHandler.sendEmptyMessageDelayed(MSG_DISMISS_ZOOM_RING, delay);
+    }
+
+    /**
+     * Should be called by the client for each event belonging to the second tap
+     * (the down, move, up, and cancel events).
+     *
+     * @param event The event belonging to the second tap.
+     * @return Whether the event was consumed.
+     */
+    public boolean handleDoubleTapEvent(MotionEvent event) {
+        if (!useThisZoom(mContext)) return false;
+        
+        int action = event.getAction();
+
+        if (action == MotionEvent.ACTION_DOWN) {
+            int x = (int) event.getX();
+            int y = (int) event.getY();
+
+            setVisible(true);
+            centerPoint(x, y);
+        }
+
+        return true;
+    }
+
+    private void refreshPositioningVariables() {
+        // Calculate the owner view's bounds
+        mOwnerView.getGlobalVisibleRect(mOwnerViewBounds);
+        mContainer.getLocationOnScreen(mContainerLocation);
+    }
+
+    /**
+     * Centers the point (in owner view's coordinates).
+     */
+    private void centerPoint(int x, int y) {
+        if (mCallback != null) {
+            mCallback.onCenter(x, y);
+        }
+    }
+
+    public boolean onTouch(View v, MotionEvent event) {
+        int action = event.getAction();
+
+        if (mReleaseTouchListenerOnUp) {
+            // The ring was dismissed but we need to throw away all events until the up
+            if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
+                mOwnerView.setOnTouchListener(null);
+                setTouchTargetView(null);
+                mReleaseTouchListenerOnUp = false;
+            }
+
+            // Eat this event
+            return true;
+        }
+
+        // TODO: optimize this (it ends up removing message and queuing another)
+        dismissControlsDelayed(ZOOM_CONTROLS_TIMEOUT);
+        
+        View targetView = mTouchTargetView;
+
+        switch (action) {
+            case MotionEvent.ACTION_DOWN:
+                targetView = getViewForTouch((int) event.getRawX(), (int) event.getRawY());
+                setTouchTargetView(targetView);
+                break;
+
+            case MotionEvent.ACTION_UP:
+            case MotionEvent.ACTION_CANCEL:
+                setTouchTargetView(null);
+                break;
+        }
+
+        if (targetView != null) {
+            // The upperleft corner of the target view in raw coordinates
+            int targetViewRawX = mContainerLocation[0] + mTouchTargetLocationInWindow[0];
+            int targetViewRawY = mContainerLocation[1] + mTouchTargetLocationInWindow[1];
+
+            MotionEvent containerEvent = MotionEvent.obtain(event);
+            // Convert the motion event into the target view's coordinates (from
+            // owner view's coordinates)
+            containerEvent.offsetLocation(mOwnerViewBounds.left - targetViewRawX,
+                    mOwnerViewBounds.top - targetViewRawY);
+            boolean retValue = targetView.dispatchTouchEvent(containerEvent);
+            containerEvent.recycle();
+            return retValue;
+
+        } else {
+            return false;
+        }
+    }
+
+    private void setTouchTargetView(View view) {
+        mTouchTargetView = view;
+        if (view != null) {
+            view.getLocationInWindow(mTouchTargetLocationInWindow);
+        }
+    }
+
+    /**
+     * Returns the View that should receive a touch at the given coordinates.
+     *
+     * @param rawX The raw X.
+     * @param rawY The raw Y.
+     * @return The view that should receive the touches, or null if there is not one.
+     */
+    private View getViewForTouch(int rawX, int rawY) {
+        // Reverse order so the child drawn on top gets first dibs.
+        int containerCoordsX = rawX - mContainerLocation[0];
+        int containerCoordsY = rawY - mContainerLocation[1];
+        Rect frame = mTempRect;
+        for (int i = mContainer.getChildCount() - 1; i >= 0; i--) {
+            View child = mContainer.getChildAt(i);
+            if (child.getVisibility() != View.VISIBLE) {
+                continue;
+            }
+
+            child.getHitRect(frame);
+            // Expand the touch region
+            frame.top -= ZOOM_CONTROLS_TOUCH_PADDING;
+            if (frame.contains(containerCoordsX, containerCoordsY)) {
+                return child;
+            }
+        }
+
+        return null;
+    }
+
+    private void onPostConfigurationChanged() {
+        dismissControlsDelayed(ZOOM_CONTROLS_TIMEOUT);
+        refreshPositioningVariables();
+    }
+
+    public static boolean useThisZoom(Context context) {
+        return ZoomRingController.getZoomType(context) == 2;
+    }
+    
+    public interface OnZoomListener {
+        void onCenter(int x, int y);
+        void onVisibilityChanged(boolean visible);
+        void onZoom(boolean zoomIn);
+        void onOverview();
+    }
+}
diff --git a/core/java/android/widget/ZoomControls.java b/core/java/android/widget/ZoomControls.java
new file mode 100644
index 0000000..fdc05b6
--- /dev/null
+++ b/core/java/android/widget/ZoomControls.java
@@ -0,0 +1,112 @@
+/*
+ * 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 android.widget;
+
+import android.annotation.Widget;
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.LayoutInflater;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.animation.AlphaAnimation;
+
+import com.android.internal.R;
+
+
+/**
+ * The {@code ZoomControls} class displays a simple set of controls used for zooming and
+ * provides callbacks to register for events.
+ */
+@Widget
+public class ZoomControls extends LinearLayout {
+
+    private final ZoomButton mZoomIn;
+    private final ZoomButton mZoomOut;
+        
+    public ZoomControls(Context context) {
+        this(context, null);
+    }
+
+    public ZoomControls(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        setFocusable(false);
+        
+        LayoutInflater inflater = (LayoutInflater) context
+                .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+        inflater.inflate(R.layout.zoom_controls, this, // we are the parent
+                true);
+        
+        mZoomIn = (ZoomButton) findViewById(R.id.zoomIn);
+        mZoomOut = (ZoomButton) findViewById(R.id.zoomOut);
+    }
+
+    public void setOnZoomInClickListener(OnClickListener listener) {
+        mZoomIn.setOnClickListener(listener);
+    }
+    
+    public void setOnZoomOutClickListener(OnClickListener listener) {
+        mZoomOut.setOnClickListener(listener);
+    }
+    
+    /*
+     * Sets how fast you get zoom events when the user holds down the
+     * zoom in/out buttons.
+     */
+    public void setZoomSpeed(long speed) {
+        mZoomIn.setZoomSpeed(speed);
+        mZoomOut.setZoomSpeed(speed);
+    }
+    
+    @Override
+    public boolean onTouchEvent(MotionEvent event) {
+        
+        /* Consume all touch events so they don't get dispatched to the view
+         * beneath this view.
+         */
+        return true;
+    }
+    
+    public void show() {
+        if (ZoomRingController.useOldZoom(mContext)) {
+            fade(View.VISIBLE, 0.0f, 1.0f);
+        }
+    }
+    
+    public void hide() {
+        fade(View.GONE, 1.0f, 0.0f);
+    }
+    
+    private void fade(int visibility, float startAlpha, float endAlpha) {
+        AlphaAnimation anim = new AlphaAnimation(startAlpha, endAlpha);
+        anim.setDuration(500);
+        startAnimation(anim);
+        setVisibility(visibility);
+    }
+    
+    public void setIsZoomInEnabled(boolean isEnabled) {
+        mZoomIn.setEnabled(isEnabled);
+    }
+    
+    public void setIsZoomOutEnabled(boolean isEnabled) {
+        mZoomOut.setEnabled(isEnabled);
+    }
+    
+    @Override
+    public boolean hasFocus() {
+        return mZoomIn.hasFocus() || mZoomOut.hasFocus();
+    }
+}
diff --git a/core/java/android/widget/ZoomRing.java b/core/java/android/widget/ZoomRing.java
new file mode 100644
index 0000000..a5a867b
--- /dev/null
+++ b/core/java/android/widget/ZoomRing.java
@@ -0,0 +1,1180 @@
+package android.widget;
+
+import com.android.internal.R;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Canvas;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.RotateDrawable;
+import android.os.Handler;
+import android.os.Message;
+import android.os.SystemClock;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.HapticFeedbackConstants;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewConfiguration;
+
+/**
+ * @hide
+ */
+public class ZoomRing extends View {
+
+    // TODO: move to ViewConfiguration?
+    static final int DOUBLE_TAP_DISMISS_TIMEOUT = ViewConfiguration.getDoubleTapTimeout();
+    // TODO: get from theme
+    private static final String TAG = "ZoomRing";
+
+    // TODO: Temporary until the trail is done
+    private static final boolean DRAW_TRAIL = false;
+
+    // TODO: xml
+    private static final int THUMB_DISTANCE = 63;
+
+    /** To avoid floating point calculations, we multiply radians by this value. */
+    public static final int RADIAN_INT_MULTIPLIER = 10000;
+    public static final int RADIAN_INT_ERROR = 100;
+    /** PI using our multiplier. */
+    public static final int PI_INT_MULTIPLIED = (int) (Math.PI * RADIAN_INT_MULTIPLIER);
+    public static final int TWO_PI_INT_MULTIPLIED = PI_INT_MULTIPLIED * 2;
+    /** PI/2 using our multiplier. */
+    private static final int HALF_PI_INT_MULTIPLIED = PI_INT_MULTIPLIED / 2;
+
+    private int mZeroAngle = HALF_PI_INT_MULTIPLIED * 3;
+    
+    private static final int THUMB_GRAB_SLOP = PI_INT_MULTIPLIED / 8;
+    private static final int THUMB_DRAG_SLOP = PI_INT_MULTIPLIED / 12;
+
+    /**
+     * Includes error because we compare this to the result of
+     * getDelta(getClosestTickeAngle(..), oldAngle) which ends up having some
+     * rounding error.
+     */ 
+    private static final int MAX_ABS_JUMP_DELTA_ANGLE = (2 * PI_INT_MULTIPLIED / 3) +
+            RADIAN_INT_ERROR; 
+
+    /** The cached X of our center. */
+    private int mCenterX;
+    /** The cached Y of our center. */
+    private int mCenterY;
+
+    /** The angle of the thumb (in int radians) */
+    private int mThumbAngle;
+    private int mThumbHalfWidth;
+    private int mThumbHalfHeight;
+    
+    private int mThumbCwBound = Integer.MIN_VALUE;
+    private int mThumbCcwBound = Integer.MIN_VALUE;
+    private boolean mEnforceMaxAbsJump = true;
+    
+    /** The inner radius of the track. */
+    private int mBoundInnerRadiusSquared = 0;
+    /** The outer radius of the track. */
+    private int mBoundOuterRadiusSquared = Integer.MAX_VALUE;
+
+    private int mPreviousWidgetDragX;
+    private int mPreviousWidgetDragY;
+
+    private boolean mThumbVisible = true;
+    private Drawable mThumbDrawable;
+    
+    /** Shown beneath the thumb if we can still zoom in. */
+    private Drawable mThumbPlusArrowDrawable;
+    /** Shown beneath the thumb if we can still zoom out. */
+    private Drawable mThumbMinusArrowDrawable;
+    private static final int THUMB_ARROW_PLUS = 1 << 0;
+    private static final int THUMB_ARROW_MINUS = 1 << 1;
+    /** Bitwise-OR of {@link #THUMB_ARROW_MINUS} and {@link #THUMB_ARROW_PLUS} */
+    private int mThumbArrowsToDraw;
+    private static final int THUMB_ARROWS_FADE_DURATION = 300;
+    private long mThumbArrowsFadeStartTime;
+    private int mThumbArrowsAlpha = 255;
+
+    private static final int THUMB_PLUS_MINUS_DISTANCE = 69;
+    private static final int THUMB_PLUS_MINUS_OFFSET_ANGLE = TWO_PI_INT_MULTIPLIED / 11;
+    /** Drawn (without rotation) on top of the arrow. */
+    private Drawable mThumbPlusDrawable;
+    /** Drawn (without rotation) on top of the arrow. */
+    private Drawable mThumbMinusDrawable;
+    
+    private static final int MODE_IDLE = 0;
+
+    /**
+     * User has his finger down somewhere on the ring (besides the thumb) and we
+     * are waiting for him to move the slop amount before considering him in the
+     * drag thumb state.
+     */
+    private static final int MODE_WAITING_FOR_DRAG_THUMB_AFTER_JUMP = 5;
+    private static final int MODE_DRAG_THUMB = 1;
+    /**
+     * User has his finger down, but we are waiting for him to pass the touch
+     * slop before going into the #MODE_MOVE_ZOOM_RING. This is a good time to
+     * show the movable hint.
+     */
+    private static final int MODE_WAITING_FOR_MOVE_ZOOM_RING = 4;
+    private static final int MODE_MOVE_ZOOM_RING = 2;
+    private static final int MODE_TAP_DRAG = 3;
+    /** Ignore the touch interaction until the user touches the thumb again. */
+    private static final int MODE_IGNORE_UNTIL_TOUCHES_THUMB = 6;
+    private int mMode;
+    
+    /** Records the last mode the user was in. */
+    private int mPreviousMode;
+    
+    private long mPreviousCenterUpTime;
+    private int mPreviousDownX;
+    private int mPreviousDownY;
+
+    private int mWaitingForDragThumbDownAngle;
+    
+    private OnZoomRingCallback mCallback;
+    private int mPreviousCallbackAngle;
+    private int mCallbackThreshold = Integer.MAX_VALUE;
+    /** If the user drags to within __% of a tick, snap to that tick. */
+    private int mFuzzyCallbackThreshold = Integer.MAX_VALUE;
+    
+    private boolean mResetThumbAutomatically = true;
+    private int mThumbDragStartAngle;
+
+    private final int mTouchSlop;
+
+    private Drawable mTrail;
+    private double mAcculumalatedTrailAngle;
+    
+    private Scroller mThumbScroller;
+
+    private boolean mVibration = true;
+    
+    private static final int MSG_THUMB_SCROLLER_TICK = 1;
+    private static final int MSG_THUMB_ARROWS_FADE_TICK = 2;
+    private Handler mHandler = new Handler() {
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case MSG_THUMB_SCROLLER_TICK:
+                    onThumbScrollerTick();
+                    break;
+                    
+                case MSG_THUMB_ARROWS_FADE_TICK:
+                    onThumbArrowsFadeTick();
+                    break;
+            }
+        }
+    };
+    
+    public ZoomRing(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+
+        ViewConfiguration viewConfiguration = ViewConfiguration.get(context);
+        mTouchSlop = viewConfiguration.getScaledTouchSlop();
+
+        // TODO get drawables from style instead
+        Resources res = context.getResources();
+        mThumbDrawable = res.getDrawable(R.drawable.zoom_ring_thumb);
+        mThumbPlusArrowDrawable = res.getDrawable(R.drawable.zoom_ring_thumb_plus_arrow_rotatable).
+                mutate();
+        mThumbMinusArrowDrawable = res.getDrawable(R.drawable.zoom_ring_thumb_minus_arrow_rotatable).
+                mutate();
+        mThumbPlusDrawable = res.getDrawable(R.drawable.zoom_ring_thumb_plus);
+        mThumbMinusDrawable = res.getDrawable(R.drawable.zoom_ring_thumb_minus);
+        if (DRAW_TRAIL) {
+            mTrail = res.getDrawable(R.drawable.zoom_ring_trail).mutate();
+        }
+
+        // TODO: add padding to drawable
+        setBackgroundResource(R.drawable.zoom_ring_track);
+        // TODO get from style
+        setRingBounds(43, Integer.MAX_VALUE);
+
+        mThumbHalfHeight = mThumbDrawable.getIntrinsicHeight() / 2;
+        mThumbHalfWidth = mThumbDrawable.getIntrinsicWidth() / 2;
+
+        setCallbackThreshold(PI_INT_MULTIPLIED / 6);
+    }
+
+    public ZoomRing(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public ZoomRing(Context context) {
+        this(context, null);
+    }
+
+    public void setCallback(OnZoomRingCallback callback) {
+        mCallback = callback;
+    }
+
+    // TODO: rename
+    public void setCallbackThreshold(int callbackThreshold) {
+        mCallbackThreshold = callbackThreshold;
+        mFuzzyCallbackThreshold = (int) (callbackThreshold * 0.65f);
+    }
+
+    public void setVibration(boolean vibrate) {
+        mVibration = vibrate;
+    }
+    
+    public void setThumbVisible(boolean thumbVisible) {
+        if (mThumbVisible != thumbVisible) {
+            mThumbVisible = thumbVisible;
+            invalidate();
+        }
+    }
+    
+    // TODO: from XML too
+    public void setRingBounds(int innerRadius, int outerRadius) {
+        mBoundInnerRadiusSquared = innerRadius * innerRadius;
+        if (mBoundInnerRadiusSquared < innerRadius) {
+            // Prevent overflow
+            mBoundInnerRadiusSquared = Integer.MAX_VALUE;
+        }
+
+        mBoundOuterRadiusSquared = outerRadius * outerRadius;
+        if (mBoundOuterRadiusSquared < outerRadius) {
+            // Prevent overflow
+            mBoundOuterRadiusSquared = Integer.MAX_VALUE;
+        }
+    }
+
+    public void setThumbClockwiseBound(int angle) {
+        if (angle < 0) {
+            mThumbCwBound = Integer.MIN_VALUE;
+        } else {
+            mThumbCwBound = getClosestTickAngle(angle);
+        }
+        setEnforceMaxAbsJump();
+    }
+    
+    public void setThumbCounterclockwiseBound(int angle) {
+        if (angle < 0) {
+            mThumbCcwBound = Integer.MIN_VALUE;
+        } else {
+            mThumbCcwBound = getClosestTickAngle(angle);
+        }
+        setEnforceMaxAbsJump();
+    }
+    
+    private void setEnforceMaxAbsJump() {
+        // If there are bounds in both direction, there is no reason to restrict
+        // the amount that a user can absolute jump to
+        mEnforceMaxAbsJump =
+            mThumbCcwBound == Integer.MIN_VALUE || mThumbCwBound == Integer.MIN_VALUE;
+    }
+    
+    public int getThumbAngle() {
+        return mThumbAngle;
+    }
+    
+    public void setThumbAngle(int angle) {
+        angle = getValidAngle(angle);
+        mPreviousCallbackAngle = getClosestTickAngle(angle);
+        setThumbAngleAuto(angle, false, false);
+    }
+    
+    /**
+     * Sets the thumb angle. If already animating, will continue the animation,
+     * otherwise it will do a direct jump.
+     * 
+     * @param angle
+     * @param useDirection Whether to use the ccw parameter
+     * @param ccw Whether going counterclockwise (only used if useDirection is true)
+     */
+    private void setThumbAngleAuto(int angle, boolean useDirection, boolean ccw) {
+        if (mThumbScroller == null
+                || mThumbScroller.isFinished()
+                || Math.abs(getDelta(angle, getThumbScrollerAngle())) < THUMB_GRAB_SLOP) {
+            setThumbAngleInt(angle);
+        } else {
+            if (useDirection) {
+                setThumbAngleAnimated(angle, 0, ccw);
+            } else {
+                setThumbAngleAnimated(angle, 0);
+            }
+        }
+    }
+    
+    private void setThumbAngleInt(int angle) {
+        mThumbAngle = angle;
+        int unoffsetAngle = angle + mZeroAngle;
+        int thumbCenterX = (int) (Math.cos(1f * unoffsetAngle / RADIAN_INT_MULTIPLIER) *
+                THUMB_DISTANCE) + mCenterX;
+        int thumbCenterY = (int) (Math.sin(1f * unoffsetAngle / RADIAN_INT_MULTIPLIER) *
+                THUMB_DISTANCE) * -1 + mCenterY;
+
+        mThumbDrawable.setBounds(thumbCenterX - mThumbHalfWidth,
+                thumbCenterY - mThumbHalfHeight,
+                thumbCenterX + mThumbHalfWidth,
+                thumbCenterY + mThumbHalfHeight);
+
+        if (mThumbArrowsToDraw > 0) {
+            setThumbArrowsAngle(angle);
+        }
+        
+        if (DRAW_TRAIL) {
+            double degrees;
+            degrees = Math.min(359.0, Math.abs(mAcculumalatedTrailAngle));
+            int level = (int) (10000.0 * degrees / 360.0);
+
+            mTrail.setLevel((int) (10000.0 *
+                    (-Math.toDegrees(angle / (double) RADIAN_INT_MULTIPLIER) -
+                            degrees + 90) / 360.0));
+            ((RotateDrawable) mTrail).getDrawable().setLevel(level);
+        }
+
+        invalidate();
+    }
+    
+    /**
+     * 
+     * @param angle
+     * @param duration The animation duration, or 0 for the default duration.
+     */
+    public void setThumbAngleAnimated(int angle, int duration) {
+        // The angle when going from the current angle to the new angle
+        int deltaAngle = getDelta(mThumbAngle, angle);
+        setThumbAngleAnimated(angle, duration, deltaAngle > 0);
+    }
+    
+    public void setThumbAngleAnimated(int angle, int duration, boolean counterClockwise) {
+        if (mThumbScroller == null) {
+            mThumbScroller = new Scroller(mContext);
+        }
+        
+        int startAngle = mThumbAngle;
+        int endAngle = getValidAngle(angle);
+        int deltaAngle = getDelta(startAngle, endAngle, counterClockwise);
+        if (startAngle + deltaAngle < 0) {
+            // Keep our angles positive
+            startAngle += TWO_PI_INT_MULTIPLIED;
+        }
+        
+        if (!mThumbScroller.isFinished()) {
+            duration = mThumbScroller.getDuration() - mThumbScroller.timePassed();
+        } else if (duration == 0) {
+            duration = getAnimationDuration(deltaAngle);
+        }
+        mThumbScroller.startScroll(startAngle, 0, deltaAngle, 0, duration);
+        onThumbScrollerTick();
+    }
+    
+    private int getAnimationDuration(int deltaAngle) {
+        if (deltaAngle < 0) deltaAngle *= -1;
+        return 300 + deltaAngle * 300 / RADIAN_INT_MULTIPLIER; 
+    }
+    
+    private void onThumbScrollerTick() {
+        if (!mThumbScroller.computeScrollOffset()) return;
+        setThumbAngleInt(getThumbScrollerAngle());        
+        mHandler.sendEmptyMessage(MSG_THUMB_SCROLLER_TICK);
+    }
+
+    private int getThumbScrollerAngle() {
+        return mThumbScroller.getCurrX() % TWO_PI_INT_MULTIPLIED;
+    }
+    
+    public void resetThumbAngle() {
+        if (mResetThumbAutomatically) {
+            mPreviousCallbackAngle = 0;
+            setThumbAngleInt(0);
+        }
+    }
+    
+    public void setResetThumbAutomatically(boolean resetThumbAutomatically) {
+        mResetThumbAutomatically = resetThumbAutomatically;
+    }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        setMeasuredDimension(resolveSize(getSuggestedMinimumWidth(), widthMeasureSpec),
+                resolveSize(getSuggestedMinimumHeight(), heightMeasureSpec));
+    }
+
+    @Override
+    protected void onLayout(boolean changed, int left, int top, int right,
+            int bottom) {
+        super.onLayout(changed, left, top, right, bottom);
+
+        // Cache the center point
+        mCenterX = (right - left) / 2;
+        mCenterY = (bottom - top) / 2;
+
+        // Done here since we now have center, which is needed to calculate some
+        // aux info for thumb angle
+        if (mThumbAngle == Integer.MIN_VALUE) {
+            resetThumbAngle();
+        }
+
+        if (DRAW_TRAIL) {
+            mTrail.setBounds(0, 0, right - left, bottom - top);
+        }
+        
+        // These drawables are the same size as the track
+        mThumbPlusArrowDrawable.setBounds(0, 0, right - left, bottom - top);
+        mThumbMinusArrowDrawable.setBounds(0, 0, right - left, bottom - top);
+    }
+
+    @Override
+    public boolean onTouchEvent(MotionEvent event) {
+//        Log.d(TAG, "History size: " + event.getHistorySize());
+        
+        return handleTouch(event.getAction(), event.getEventTime(),
+                (int) event.getX(), (int) event.getY(), (int) event.getRawX(),
+                (int) event.getRawY());
+    }
+
+    private void resetToIdle() {
+        setMode(MODE_IDLE);
+        mPreviousWidgetDragX = mPreviousWidgetDragY = Integer.MIN_VALUE;
+        mAcculumalatedTrailAngle = 0.0;
+    }
+
+    public void setTapDragMode(boolean tapDragMode, int x, int y) {
+        resetToIdle();
+        if (tapDragMode) {
+            setMode(MODE_TAP_DRAG);
+            mCallback.onUserInteractionStarted();
+            onThumbDragStarted(getAngle(x - mCenterX, y - mCenterY));
+        } else {
+            onTouchUp(SystemClock.elapsedRealtime(), true);
+        }
+    }
+
+    public boolean handleTouch(int action, long time, int x, int y, int rawX, int rawY) {
+        // local{X,Y} will be where the center of the widget is (0,0)
+        int localX = x - mCenterX;
+        int localY = y - mCenterY;
+
+        /*
+         * If we are not drawing the thumb, there is no way for the user to be
+         * touching the thumb. Also, if this is the case, assume they are not
+         * touching the ring (so the user cannot absolute set the thumb, and
+         * there will be a larger touch region for going into the move-ring
+         * mode).
+         */
+        boolean isTouchingThumb = mThumbVisible;
+        boolean isTouchingRing = mThumbVisible;
+        
+        int touchAngle = getAngle(localX, localY);
+//        printAngle("touchAngle", touchAngle);
+//        printAngle("mThumbAngle", mThumbAngle);
+//        printAngle("mPreviousCallbackAngle", mPreviousCallbackAngle);
+//        Log.d(TAG, "");
+        		
+        
+        int radiusSquared = localX * localX + localY * localY;
+        if (radiusSquared < mBoundInnerRadiusSquared ||
+                radiusSquared > mBoundOuterRadiusSquared) {
+            // Out-of-bounds
+            isTouchingThumb = false;
+            isTouchingRing = false;
+        }
+
+        if (isTouchingThumb) {
+            int deltaThumbAndTouch = getDelta(mThumbAngle, touchAngle);
+            int absoluteDeltaThumbAndTouch = deltaThumbAndTouch >= 0 ?
+                    deltaThumbAndTouch : -deltaThumbAndTouch;
+            if (absoluteDeltaThumbAndTouch > THUMB_GRAB_SLOP) {
+                // Didn't grab close enough to the thumb
+                isTouchingThumb = false;
+            }
+        }
+
+        switch (action) {
+            case MotionEvent.ACTION_DOWN:
+                if (!isTouchingRing &&
+                        (time - mPreviousCenterUpTime <= DOUBLE_TAP_DISMISS_TIMEOUT)) {
+                    // Make sure the double-tap is in the center of the widget (and not on the ring)
+                    mCallback.onZoomRingDismissed(true);
+                    onTouchUp(time, isTouchingRing);
+                    
+                    // Dismissing, so halt here
+                    return true;
+                }
+
+                resetToIdle();
+                mCallback.onUserInteractionStarted();
+                mPreviousDownX = x;
+                mPreviousDownY = y;
+                // Fall through to code below switch (since the down is used for
+                // jumping to the touched tick)
+                break;
+
+            case MotionEvent.ACTION_MOVE:
+                // Fall through to code below switch
+                break;
+
+            case MotionEvent.ACTION_CANCEL:
+            case MotionEvent.ACTION_UP:
+                onTouchUp(time, isTouchingRing);
+                return true;
+
+            default:
+                return false;
+        }
+
+        if (mMode == MODE_IDLE) {
+            if (isTouchingThumb) {
+                // They grabbed the thumb
+                setMode(MODE_DRAG_THUMB);
+                onThumbDragStarted(touchAngle);
+                
+            } else if (isTouchingRing) {
+                // They tapped somewhere else on the ring
+                int tickAngle = getClosestTickAngle(touchAngle);
+                int deltaThumbAndTick = getDelta(mThumbAngle, tickAngle);
+                int boundAngle = getBoundIfExceeds(mThumbAngle, deltaThumbAndTick);
+
+                if (mEnforceMaxAbsJump) {
+                    // Enforcing the max jump
+                    if (deltaThumbAndTick > MAX_ABS_JUMP_DELTA_ANGLE ||
+                            deltaThumbAndTick < -MAX_ABS_JUMP_DELTA_ANGLE) {
+                        // Trying to jump too far, ignore this touch interaction                    
+                        setMode(MODE_IGNORE_UNTIL_TOUCHES_THUMB);
+                        return true;
+                    }
+
+                    if (boundAngle != Integer.MIN_VALUE) {
+                        // Cap the user's jump to the bound
+                        tickAngle = boundAngle;
+                    }
+                } else {
+                    // Not enforcing the max jump, but we have to make sure
+                    // we're getting to the tapped angle by going through the
+                    // in-bounds region
+                    if (boundAngle != Integer.MIN_VALUE) {
+                        // Going this direction hits a bound, let's go the opposite direction
+                        boolean oldDirectionIsCcw = deltaThumbAndTick > 0;
+                        deltaThumbAndTick = getDelta(mThumbAngle, tickAngle, !oldDirectionIsCcw);
+                        boundAngle = getBoundIfExceeds(mThumbAngle, deltaThumbAndTick);
+                        if (boundAngle != Integer.MIN_VALUE) {
+                            // Cannot get to the tapped location because it is out-of-bounds
+                            setMode(MODE_IGNORE_UNTIL_TOUCHES_THUMB);
+                            return true;
+                        }
+                    }
+                }
+
+                setMode(MODE_WAITING_FOR_DRAG_THUMB_AFTER_JUMP);
+                mWaitingForDragThumbDownAngle = touchAngle;
+                boolean ccw = deltaThumbAndTick > 0;
+                setThumbAngleAnimated(tickAngle, 0, ccw);
+                
+                /*
+                 * Our thumb scrolling animation takes us from mThumbAngle to
+                 * tickAngle, so manifest that as the user dragging the thumb
+                 * there.
+                 */
+                onThumbDragStarted(mThumbAngle);
+                // We know which direction we want to go
+                onThumbDragged(tickAngle, true, ccw);
+                
+            } else {
+                // They tapped somewhere else on the widget
+                setMode(MODE_WAITING_FOR_MOVE_ZOOM_RING);
+                mCallback.onZoomRingSetMovableHintVisible(true);
+            }
+
+        } else if (mMode == MODE_WAITING_FOR_DRAG_THUMB_AFTER_JUMP) {
+            int deltaDownAngle = getDelta(mWaitingForDragThumbDownAngle, touchAngle);
+            if ((deltaDownAngle < -THUMB_DRAG_SLOP || deltaDownAngle > THUMB_DRAG_SLOP) &&
+                    isDeltaInBounds(mWaitingForDragThumbDownAngle, deltaDownAngle)) {
+                setMode(MODE_DRAG_THUMB);
+                
+                // No need to call onThumbDragStarted, since that was done when they tapped-to-jump
+            }
+
+        } else if (mMode == MODE_WAITING_FOR_MOVE_ZOOM_RING) {
+            if (Math.abs(x - mPreviousDownX) > mTouchSlop ||
+                    Math.abs(y - mPreviousDownY) > mTouchSlop) {
+                /* Make sure the user has moved the slop amount before going into that mode. */
+                setMode(MODE_MOVE_ZOOM_RING);
+                mCallback.onZoomRingMovingStarted();
+            }
+        } else if (mMode == MODE_IGNORE_UNTIL_TOUCHES_THUMB) {
+            if (isTouchingThumb) {
+                // The user is back on the thumb, let's go back to the previous mode
+                setMode(mPreviousMode);
+            }
+        }
+
+        // Purposefully not an "else if"
+        if (mMode == MODE_DRAG_THUMB || mMode == MODE_TAP_DRAG) {
+            if (isTouchingRing) {
+                onThumbDragged(touchAngle, false, false);
+            }
+        } else if (mMode == MODE_MOVE_ZOOM_RING) {
+            onZoomRingMoved(rawX, rawY);
+        }
+
+        return true;
+    }
+    
+    private void onTouchUp(long time, boolean isTouchingRing) {
+        int mode = mMode;
+        if (mode == MODE_IGNORE_UNTIL_TOUCHES_THUMB) {
+            // For cleaning up, pretend like the user was still in the previous mode
+            mode = mPreviousMode;
+        }
+        
+        if (mode == MODE_MOVE_ZOOM_RING || mode == MODE_WAITING_FOR_MOVE_ZOOM_RING) {
+            mCallback.onZoomRingSetMovableHintVisible(false);
+            if (mode == MODE_MOVE_ZOOM_RING) {
+                mCallback.onZoomRingMovingStopped();
+            }
+        } else if (mode == MODE_DRAG_THUMB || mode == MODE_TAP_DRAG ||
+                mode == MODE_WAITING_FOR_DRAG_THUMB_AFTER_JUMP) {
+            onThumbDragStopped();
+            
+            if (mode == MODE_DRAG_THUMB || mode == MODE_TAP_DRAG) {
+                // Animate back to a tick
+                setThumbAngleAnimated(mPreviousCallbackAngle, 0);
+            }
+        }
+        mCallback.onUserInteractionStopped();
+        
+        if (!isTouchingRing) {
+            mPreviousCenterUpTime = time;
+        }
+    }
+    
+    private void setMode(int mode) {
+        if (mode != mMode) {
+            mPreviousMode = mMode;
+            mMode = mode;
+        }
+    }
+
+    private boolean isDeltaInBounds(int startAngle, int deltaAngle) {
+        return getBoundIfExceeds(startAngle, deltaAngle) == Integer.MIN_VALUE;
+    }
+    
+    private int getBoundIfExceeds(int startAngle, int deltaAngle) {
+        if (deltaAngle > 0) {
+            // Counterclockwise movement
+            if (mThumbCcwBound != Integer.MIN_VALUE &&
+                    getDelta(startAngle, mThumbCcwBound, true) < deltaAngle) {
+                return mThumbCcwBound;
+            }
+        } else if (deltaAngle < 0) {
+            // Clockwise movement, both of these will be negative
+            int deltaThumbAndBound = getDelta(startAngle, mThumbCwBound, false);
+            if (mThumbCwBound != Integer.MIN_VALUE &&
+                    deltaThumbAndBound > deltaAngle) {
+                // Tapped outside of the bound in that direction                    
+                return mThumbCwBound;
+            }
+        }
+        
+        return Integer.MIN_VALUE;
+    }
+
+    private int getDelta(int startAngle, int endAngle, boolean useDirection, boolean ccw) {
+        return useDirection ? getDelta(startAngle, endAngle, ccw) : getDelta(startAngle, endAngle);
+    }
+    
+    /**
+     * Gets the smallest delta between two angles, and infers the direction
+     * based on the shortest path between the two angles. If going from
+     * startAngle to endAngle is counterclockwise, the result will be positive.
+     * If it is clockwise, the result will be negative.
+     * 
+     * @param startAngle The start angle.
+     * @param endAngle The end angle.
+     * @return The difference in angles.
+     */
+    private int getDelta(int startAngle, int endAngle) {
+        int largerAngle, smallerAngle;
+        if (endAngle > startAngle) {
+            largerAngle = endAngle;
+            smallerAngle = startAngle;
+        } else {
+            largerAngle = startAngle;
+            smallerAngle = endAngle;
+        }
+
+        int delta = largerAngle - smallerAngle;
+        if (delta <= PI_INT_MULTIPLIED) {
+            // If going clockwise, negate the delta
+            return startAngle == largerAngle ? -delta : delta; 
+        } else {
+            // The other direction is the delta we want (it includes the
+            // discontinuous 0-2PI angle)
+            delta = TWO_PI_INT_MULTIPLIED - delta;
+            // If going clockwise, negate the delta
+            return startAngle == smallerAngle ? -delta : delta; 
+        }
+    }
+
+    /**
+     * Gets the delta between two angles in the direction specified.
+     * 
+     * @param startAngle The start angle.
+     * @param endAngle The end angle.
+     * @param counterClockwise The direction to take when computing the delta.
+     * @return The difference in angles in the given direction.
+     */
+    private int getDelta(int startAngle, int endAngle, boolean counterClockwise) {
+        int delta = endAngle - startAngle;
+        
+        if (!counterClockwise && delta > 0) {
+            // Crossed the discontinuous 0/2PI angle, take the leftover slice of
+            // the pie and negate it
+            return -TWO_PI_INT_MULTIPLIED + delta;
+        } else if (counterClockwise && delta < 0) {
+            // Crossed the discontinuous 0/2PI angle, take the leftover slice of
+            // the pie (and ensure it is positive)
+            return TWO_PI_INT_MULTIPLIED + delta;
+        } else {
+            return delta;
+        }
+    }
+    
+    private void onThumbDragStarted(int startAngle) {
+        setThumbArrowsVisible(false);
+        mThumbDragStartAngle = startAngle;
+        mCallback.onZoomRingThumbDraggingStarted();
+    }
+    
+    private void onThumbDragged(int touchAngle, boolean useDirection, boolean ccw) {
+        boolean animateThumbToNewAngle = false;
+        
+        int totalDeltaAngle;
+        totalDeltaAngle = getDelta(mPreviousCallbackAngle, touchAngle, useDirection, ccw);
+        if (totalDeltaAngle >= mFuzzyCallbackThreshold
+                || totalDeltaAngle <= -mFuzzyCallbackThreshold) {
+
+            if (!useDirection) {
+                // Set ccw to match the direction found by getDelta
+                ccw = totalDeltaAngle > 0;
+            }
+            
+            /*
+             * When the user slides the thumb through the tick that corresponds
+             * to a zoom bound, we don't want to abruptly stop there. Instead,
+             * let the user slide it to the next tick, and then animate it back
+             * to the original zoom bound tick. Because of this, we make sure
+             * the delta from the bound is more than halfway to the next tick.
+             * We make sure the bound is between the touch and the previous
+             * callback to ensure we just passed the bound.
+             */ 
+            int oldTouchAngle = touchAngle;
+            if (ccw && mThumbCcwBound != Integer.MIN_VALUE) {
+                int deltaCcwBoundAndTouch =
+                        getDelta(mThumbCcwBound, touchAngle, useDirection, true);
+                if (deltaCcwBoundAndTouch >= mCallbackThreshold / 2) {
+                    // The touch has past a bound
+                    int deltaPreviousCbAndTouch = getDelta(mPreviousCallbackAngle,
+                            touchAngle, useDirection, true);
+                    if (deltaPreviousCbAndTouch >= deltaCcwBoundAndTouch) {
+                        // The bound is between the previous callback angle and the touch
+                        touchAngle = mThumbCcwBound;
+                        // We're moving the touch BACK to the bound, so opposite direction
+                        ccw = false;
+                    }
+                }
+            } else if (!ccw && mThumbCwBound != Integer.MIN_VALUE) {
+                // See block above for general comments
+                int deltaCwBoundAndTouch =
+                        getDelta(mThumbCwBound, touchAngle, useDirection, false);
+                if (deltaCwBoundAndTouch <= -mCallbackThreshold / 2) {
+                    int deltaPreviousCbAndTouch = getDelta(mPreviousCallbackAngle,
+                            touchAngle, useDirection, false);
+                    /*
+                     * Both of these will be negative since we got delta in
+                     * clockwise direction, and we want the magnitude of
+                     * deltaPreviousCbAndTouch to be greater than the magnitude
+                     * of deltaCwBoundAndTouch
+                     */
+                    if (deltaPreviousCbAndTouch <= deltaCwBoundAndTouch) {
+                        touchAngle = mThumbCwBound;
+                        ccw = true;
+                    }
+                }
+            }
+            if (touchAngle != oldTouchAngle) {
+                // We bounded the touch angle
+                totalDeltaAngle = getDelta(mPreviousCallbackAngle, touchAngle, useDirection, ccw);
+                animateThumbToNewAngle = true;
+                setMode(MODE_IGNORE_UNTIL_TOUCHES_THUMB);
+            }
+            
+            
+            // Prevent it from jumping too far
+            if (mEnforceMaxAbsJump) {
+                if (totalDeltaAngle <= -MAX_ABS_JUMP_DELTA_ANGLE) {
+                    totalDeltaAngle = -MAX_ABS_JUMP_DELTA_ANGLE;
+                    animateThumbToNewAngle = true;
+                } else if (totalDeltaAngle >= MAX_ABS_JUMP_DELTA_ANGLE) {
+                    totalDeltaAngle = MAX_ABS_JUMP_DELTA_ANGLE;
+                    animateThumbToNewAngle = true;
+                }
+            }
+
+            /*
+             * We need to cover the edge case of a user grabbing the thumb,
+             * going into the center of the widget, and then coming out from the
+             * center to an angle that's slightly below the angle he's trying to
+             * hit. If we do int division, we'll end up with one level lower
+             * than the one he was going for.
+             */
+            int deltaLevels = Math.round((float) totalDeltaAngle / mCallbackThreshold); 
+            if (deltaLevels != 0) {
+                boolean canStillZoom = mCallback.onZoomRingThumbDragged(
+                        deltaLevels, mThumbDragStartAngle, touchAngle);
+                
+                if (mVibration) {
+                    // TODO: we're trying the haptics to see how it goes with
+                    // users, so we're ignoring the settings (for now)
+                    performHapticFeedback(HapticFeedbackConstants.ZOOM_RING_TICK,
+                            HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING |
+                            HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING);
+                }
+                
+                // Set the callback angle to the actual angle based on how many delta levels we gave
+                mPreviousCallbackAngle = getValidAngle(
+                        mPreviousCallbackAngle + (deltaLevels * mCallbackThreshold));
+            }
+        }
+
+        if (DRAW_TRAIL) {
+            int deltaAngle = getDelta(mThumbAngle, touchAngle, useDirection, ccw);
+            mAcculumalatedTrailAngle += Math.toDegrees(deltaAngle / (double) RADIAN_INT_MULTIPLIER);
+        }
+            
+        if (animateThumbToNewAngle) {
+            if (useDirection) {
+                setThumbAngleAnimated(touchAngle, 0, ccw);
+            } else {
+                setThumbAngleAnimated(touchAngle, 0);                
+            }
+        } else {
+            setThumbAngleAuto(touchAngle, useDirection, ccw);
+        }
+    }
+//    private void onThumbDragged(int touchAngle, boolean useDirection, boolean ccw) {
+//        int deltaPrevCbAndTouch = getDelta(mPreviousCallbackAngle, touchAngle, useDirection, ccw);
+//        
+//        if (!useDirection) {
+//            // Set ccw to match the direction found by getDelta
+//            ccw = deltaPrevCbAndTouch > 0;
+//            useDirection = true;            
+//        }
+//
+//        boolean animateThumbToNewAngle = false;
+//        boolean animationCcw = ccw;
+//
+//        if (deltaPrevCbAndTouch >= mFuzzyCallbackThreshold
+//                || deltaPrevCbAndTouch <= -mFuzzyCallbackThreshold) {
+//
+//            /*
+//             * When the user slides the thumb through the tick that corresponds
+//             * to a zoom bound, we don't want to abruptly stop there. Instead,
+//             * let the user slide it to the next tick, and then animate it back
+//             * to the original zoom bound tick. Because of this, we make sure
+//             * the delta from the bound is more than halfway to the next tick.
+//             * We make sure the bound is between the touch and the previous
+//             * callback to ensure we JUST passed the bound.
+//             */ 
+//            int oldTouchAngle = touchAngle;
+//            if (ccw && mThumbCcwBound != Integer.MIN_VALUE) {
+//                int deltaCcwBoundAndTouch =
+//                        getDelta(mThumbCcwBound, touchAngle, true, ccw);
+//                if (deltaCcwBoundAndTouch >= mCallbackThreshold / 2) {
+//                    // The touch has past far enough from the bound
+//                    int deltaPreviousCbAndTouch = getDelta(mPreviousCallbackAngle,
+//                            touchAngle, true, ccw);
+//                    if (deltaPreviousCbAndTouch >= deltaCcwBoundAndTouch) {
+//                        // The bound is between the previous callback angle and the touch
+//                        // Cap to the bound
+//                        touchAngle = mThumbCcwBound;
+//                        /*
+//                         * We're moving the touch BACK to the bound, so animate
+//                         * back in the opposite direction that passed the bound.
+//                         */
+//                        animationCcw = false;
+//                    }
+//                }
+//            } else if (!ccw && mThumbCwBound != Integer.MIN_VALUE) {
+//                // See block above for general comments
+//                int deltaCwBoundAndTouch =
+//                        getDelta(mThumbCwBound, touchAngle, true, ccw);
+//                if (deltaCwBoundAndTouch <= -mCallbackThreshold / 2) {
+//                    int deltaPreviousCbAndTouch = getDelta(mPreviousCallbackAngle,
+//                            touchAngle, true, ccw);
+//                    /*
+//                     * Both of these will be negative since we got delta in
+//                     * clockwise direction, and we want the magnitude of
+//                     * deltaPreviousCbAndTouch to be greater than the magnitude
+//                     * of deltaCwBoundAndTouch
+//                     */
+//                    if (deltaPreviousCbAndTouch <= deltaCwBoundAndTouch) {
+//                        touchAngle = mThumbCwBound;
+//                        animationCcw = true;
+//                    }
+//                }
+//            }
+//            if (touchAngle != oldTouchAngle) {
+//                // We bounded the touch angle
+//                deltaPrevCbAndTouch = getDelta(mPreviousCallbackAngle, touchAngle, true, ccw);
+//                // Animate back to the bound
+//                animateThumbToNewAngle = true;
+//                // Disallow movement now
+//                setMode(MODE_IGNORE_UNTIL_UP);
+//            }
+//            
+//            
+//            /*
+//             * Prevent it from jumping too far (this could happen if the user
+//             * goes through the center)
+//             */
+//
+//            if (mEnforceMaxAbsJump) {
+//                if (deltaPrevCbAndTouch <= -MAX_ABS_JUMP_DELTA_ANGLE) {
+//                    deltaPrevCbAndTouch = -MAX_ABS_JUMP_DELTA_ANGLE;
+//                    animateThumbToNewAngle = true;
+//                } else if (deltaPrevCbAndTouch >= MAX_ABS_JUMP_DELTA_ANGLE) {
+//                    deltaPrevCbAndTouch = MAX_ABS_JUMP_DELTA_ANGLE;
+//                    animateThumbToNewAngle = true;
+//                }
+//            }
+//
+//            /*
+//             * We need to cover the edge case of a user grabbing the thumb,
+//             * going into the center of the widget, and then coming out from the
+//             * center to an angle that's slightly below the angle he's trying to
+//             * hit. If we do int division, we'll end up with one level lower
+//             * than the one he was going for.
+//             */
+//            int deltaLevels = Math.round((float) deltaPrevCbAndTouch / mCallbackThreshold); 
+//            if (deltaLevels != 0) {
+//                boolean canStillZoom = mCallback.onZoomRingThumbDragged(
+//                        deltaLevels, mThumbDragStartAngle, touchAngle);
+//                
+//                if (mVibration) {
+//                    // TODO: we're trying the haptics to see how it goes with
+//                    // users, so we're ignoring the settings (for now)
+//                    performHapticFeedback(HapticFeedbackConstants.ZOOM_RING_TICK,
+//                            HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING |
+//                            HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING);
+//                
+//                }
+//                // Set the callback angle to the actual angle based on how many delta levels we gave
+//                mPreviousCallbackAngle = getValidAngle(
+//                        mPreviousCallbackAngle + (deltaLevels * mCallbackThreshold));
+//            }
+//        }
+//
+//        if (DRAW_TRAIL) {
+//            int deltaAngle = getDelta(mThumbAngle, touchAngle, true, ccw);
+//            mAcculumalatedTrailAngle += Math.toDegrees(deltaAngle / (double) RADIAN_INT_MULTIPLIER);
+//        }
+//            
+//        if (animateThumbToNewAngle) {
+//            setThumbAngleAnimated(touchAngle, 0, animationCcw);
+//        } else {
+//            /*
+//             * Use regular ccw here because animationCcw will never have been
+//             * changed if animateThumbToNewAngle is false
+//             */
+//            setThumbAngleAuto(touchAngle, true, ccw);
+//        }
+//    }
+    
+    private int getValidAngle(int invalidAngle) {
+        if (invalidAngle < 0) {
+            return (invalidAngle % TWO_PI_INT_MULTIPLIED) + TWO_PI_INT_MULTIPLIED;
+        } else if (invalidAngle >= TWO_PI_INT_MULTIPLIED) {
+            return invalidAngle % TWO_PI_INT_MULTIPLIED;
+        } else {
+            return invalidAngle;
+        }
+    }
+
+    private int getClosestTickAngle(int angle) {
+        int smallerAngleDistance = angle % mCallbackThreshold;
+        int smallerAngle = angle - smallerAngleDistance;
+        if (smallerAngleDistance < mCallbackThreshold / 2) {
+            // Closer to the smaller angle
+            return smallerAngle;
+        } else {
+            // Closer to the bigger angle (premodding)
+            return (smallerAngle + mCallbackThreshold) % TWO_PI_INT_MULTIPLIED; 
+        }
+    }
+
+    private void onThumbDragStopped() {
+        mCallback.onZoomRingThumbDraggingStopped();
+    }
+
+    private void onZoomRingMoved(int rawX, int rawY) {
+        if (mPreviousWidgetDragX != Integer.MIN_VALUE) {
+            int deltaX = rawX - mPreviousWidgetDragX;
+            int deltaY = rawY - mPreviousWidgetDragY;
+
+            mCallback.onZoomRingMoved(deltaX, deltaY, rawX, rawY);
+        }
+
+        mPreviousWidgetDragX = rawX;
+        mPreviousWidgetDragY = rawY;
+    }
+
+    @Override
+    public void onWindowFocusChanged(boolean hasWindowFocus) {
+        super.onWindowFocusChanged(hasWindowFocus);
+
+        if (!hasWindowFocus) {
+            mCallback.onZoomRingDismissed(true);
+        }
+    }
+    
+    private int getAngle(int localX, int localY) {
+        int radians = (int) (Math.atan2(localY, localX) * RADIAN_INT_MULTIPLIER);
+
+        // Convert from [-pi,pi] to {0,2pi]
+        if (radians < 0) {
+            radians = -radians;
+        } else if (radians > 0) {
+            radians = 2 * PI_INT_MULTIPLIED - radians;
+        } else {
+            radians = 0;
+        }
+
+        radians = radians - mZeroAngle;
+        return radians >= 0 ? radians : radians + 2 * PI_INT_MULTIPLIED;
+    }
+
+    @Override
+    protected void onDraw(Canvas canvas) {
+        super.onDraw(canvas);
+
+        if (mThumbVisible) {
+            if (DRAW_TRAIL) {
+                mTrail.draw(canvas);
+            }
+            if ((mThumbArrowsToDraw & THUMB_ARROW_PLUS) != 0) {
+                mThumbPlusArrowDrawable.draw(canvas);
+                mThumbPlusDrawable.draw(canvas);
+            }
+            if ((mThumbArrowsToDraw & THUMB_ARROW_MINUS) != 0) {
+                mThumbMinusArrowDrawable.draw(canvas);
+                mThumbMinusDrawable.draw(canvas);
+            }
+            mThumbDrawable.draw(canvas);
+        }
+    }
+    
+    private void setThumbArrowsAngle(int angle) {
+        int level = -angle * 10000 / ZoomRing.TWO_PI_INT_MULTIPLIED;
+        mThumbPlusArrowDrawable.setLevel(level);
+        mThumbMinusArrowDrawable.setLevel(level);
+        
+        // Assume it is a square
+        int halfSideLength = mThumbPlusDrawable.getIntrinsicHeight() / 2;
+        int unoffsetAngle = angle + mZeroAngle;
+        
+        int plusCenterX = (int) (Math.cos(1f * (unoffsetAngle - THUMB_PLUS_MINUS_OFFSET_ANGLE)
+                / RADIAN_INT_MULTIPLIER) * THUMB_PLUS_MINUS_DISTANCE) + mCenterX;
+        int plusCenterY = (int) (Math.sin(1f * (unoffsetAngle - THUMB_PLUS_MINUS_OFFSET_ANGLE)
+                / RADIAN_INT_MULTIPLIER) * THUMB_PLUS_MINUS_DISTANCE) * -1 + mCenterY;
+        mThumbPlusDrawable.setBounds(plusCenterX - halfSideLength,
+                plusCenterY - halfSideLength,
+                plusCenterX + halfSideLength,
+                plusCenterY + halfSideLength);
+        
+        int minusCenterX = (int) (Math.cos(1f * (unoffsetAngle + THUMB_PLUS_MINUS_OFFSET_ANGLE)
+                / RADIAN_INT_MULTIPLIER) * THUMB_PLUS_MINUS_DISTANCE) + mCenterX;
+        int minusCenterY = (int) (Math.sin(1f * (unoffsetAngle + THUMB_PLUS_MINUS_OFFSET_ANGLE)
+                / RADIAN_INT_MULTIPLIER) * THUMB_PLUS_MINUS_DISTANCE) * -1 + mCenterY;
+        mThumbMinusDrawable.setBounds(minusCenterX - halfSideLength,
+                minusCenterY - halfSideLength,
+                minusCenterX + halfSideLength,
+                minusCenterY + halfSideLength);
+    }
+    
+    public void setThumbArrowsVisible(boolean visible) {
+        if (visible) {
+            mThumbArrowsAlpha = 255;
+            int callbackAngle = mPreviousCallbackAngle;
+            if (callbackAngle < mThumbCwBound - RADIAN_INT_ERROR ||
+                    callbackAngle > mThumbCwBound + RADIAN_INT_ERROR) {
+                mThumbPlusArrowDrawable.setAlpha(255);
+                mThumbPlusDrawable.setAlpha(255);
+                mThumbArrowsToDraw |= THUMB_ARROW_PLUS;                
+            } else {
+                mThumbArrowsToDraw &= ~THUMB_ARROW_PLUS;
+            }
+            if (callbackAngle < mThumbCcwBound - RADIAN_INT_ERROR ||
+                    callbackAngle > mThumbCcwBound + RADIAN_INT_ERROR) {
+                mThumbMinusArrowDrawable.setAlpha(255);
+                mThumbMinusDrawable.setAlpha(255);
+                mThumbArrowsToDraw |= THUMB_ARROW_MINUS;
+            } else {
+                mThumbArrowsToDraw &= ~THUMB_ARROW_MINUS;
+            }
+            invalidate();
+        } else if (mThumbArrowsAlpha == 255) {
+            // Only start fade if we're fully visible (otherwise another fade is happening already)
+            mThumbArrowsFadeStartTime = SystemClock.elapsedRealtime();
+            onThumbArrowsFadeTick();
+        }
+    }
+    
+    private void onThumbArrowsFadeTick() {
+        if (mThumbArrowsAlpha <= 0) {
+            mThumbArrowsToDraw = 0;
+            return;
+        }
+        
+        mThumbArrowsAlpha = (int)
+                (255 - (255 * (SystemClock.elapsedRealtime() - mThumbArrowsFadeStartTime)
+                        / THUMB_ARROWS_FADE_DURATION));
+        if (mThumbArrowsAlpha < 0) mThumbArrowsAlpha = 0;
+        if ((mThumbArrowsToDraw & THUMB_ARROW_PLUS) != 0) {
+            mThumbPlusArrowDrawable.setAlpha(mThumbArrowsAlpha);
+            mThumbPlusDrawable.setAlpha(mThumbArrowsAlpha);
+            invalidateDrawable(mThumbPlusDrawable);
+            invalidateDrawable(mThumbPlusArrowDrawable);
+        }
+        if ((mThumbArrowsToDraw & THUMB_ARROW_MINUS) != 0) {
+            mThumbMinusArrowDrawable.setAlpha(mThumbArrowsAlpha);
+            mThumbMinusDrawable.setAlpha(mThumbArrowsAlpha);
+            invalidateDrawable(mThumbMinusDrawable);
+            invalidateDrawable(mThumbMinusArrowDrawable);
+        }
+            
+        if (!mHandler.hasMessages(MSG_THUMB_ARROWS_FADE_TICK)) {
+            mHandler.sendEmptyMessage(MSG_THUMB_ARROWS_FADE_TICK);
+        }
+    }
+    
+    @Override
+    protected void onAttachedToWindow() {
+        super.onAttachedToWindow();
+        
+        setThumbArrowsAngle(mThumbAngle);
+        setThumbArrowsVisible(true);
+    }
+
+    public interface OnZoomRingCallback {
+        void onZoomRingSetMovableHintVisible(boolean visible);
+        
+        void onZoomRingMovingStarted();
+        boolean onZoomRingMoved(int deltaX, int deltaY, int rawX, int rawY);
+        void onZoomRingMovingStopped();
+        
+        void onZoomRingThumbDraggingStarted();
+        boolean onZoomRingThumbDragged(int numLevels, int startAngle, int curAngle);
+        void onZoomRingThumbDraggingStopped();
+        
+        void onZoomRingDismissed(boolean dismissImmediately);
+        
+        void onUserInteractionStarted();
+        void onUserInteractionStopped();
+    }
+
+    private static void printAngle(String angleName, int angle) {
+        Log.d(TAG, angleName + ": " + (long) angle * 180 / PI_INT_MULTIPLIED);
+    }
+}
diff --git a/core/java/android/widget/ZoomRingController.java b/core/java/android/widget/ZoomRingController.java
new file mode 100644
index 0000000..19f66a0
--- /dev/null
+++ b/core/java/android/widget/ZoomRingController.java
@@ -0,0 +1,1216 @@
+/*
+ * 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 android.widget;
+
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.content.BroadcastReceiver;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.SharedPreferences;
+import android.graphics.PixelFormat;
+import android.graphics.Rect;
+import android.os.Handler;
+import android.os.Message;
+import android.os.SystemClock;
+import android.provider.Settings;
+import android.util.Log;
+import android.view.Gravity;
+import android.view.KeyEvent;
+import android.view.LayoutInflater;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewConfiguration;
+import android.view.Window;
+import android.view.WindowManager;
+import android.view.WindowManager.LayoutParams;
+import android.view.animation.Animation;
+import android.view.animation.AnimationUtils;
+import android.view.animation.DecelerateInterpolator;
+
+// TODO: make sure no px values exist, only dip (scale if necessary from Viewconfiguration)
+
+/**
+ * TODO: Docs
+ *
+ * If you are using this with a custom View, please call
+ * {@link #setVisible(boolean) setVisible(false)} from the
+ * {@link View#onDetachedFromWindow}.
+ *
+ * @hide
+ */
+public class ZoomRingController implements ZoomRing.OnZoomRingCallback,
+        View.OnTouchListener, View.OnKeyListener {
+
+    private static final int ZOOM_RING_RADIUS_INSET = 24;
+
+    private static final int ZOOM_RING_RECENTERING_DURATION = 500;
+
+    private static final String TAG = "ZoomRing";
+
+    public static final boolean USE_OLD_ZOOM = false;
+    static int getZoomType(Context context) {
+        return Settings.System.getInt(context.getContentResolver(), "zoom", 1);
+    }
+    public static boolean useOldZoom(Context context) {
+        return getZoomType(context) == 0;
+    }
+    private static boolean useThisZoom(Context context) {
+        return getZoomType(context) == 1;
+    }
+
+    private static final int ZOOM_CONTROLS_TIMEOUT =
+            (int) ViewConfiguration.getZoomControlsTimeout();
+
+    // TODO: move these to ViewConfiguration or re-use existing ones
+    // TODO: scale px values based on latest from ViewConfiguration
+    private static final int SECOND_TAP_TIMEOUT = 500;
+    private static final int ZOOM_RING_DISMISS_DELAY = SECOND_TAP_TIMEOUT / 2;
+    // TODO: view config?  at least scaled
+    private static final int MAX_PAN_GAP = 20;
+    private static final int MAX_INITIATE_PAN_GAP = 10;
+    // TODO view config
+    private static final int INITIATE_PAN_DELAY = 300;
+
+    private static final String SETTING_NAME_SHOWN_TOAST = "shown_zoom_ring_toast";
+
+    private Context mContext;
+    private WindowManager mWindowManager;
+
+    /**
+     * The view that is being zoomed by this zoom ring.
+     */
+    private View mOwnerView;
+
+    /**
+     * The bounds of the owner view in global coordinates. This is recalculated
+     * each time the zoom ring is shown.
+     */
+    private Rect mOwnerViewBounds = new Rect();
+
+    /**
+     * The container that is added as a window.
+     */
+    private FrameLayout mContainer;
+    private LayoutParams mContainerLayoutParams;
+
+    /**
+     * The view (or null) that should receive touch events. This will get set if
+     * the touch down hits the container. It will be reset on the touch up.
+     */
+    private View mTouchTargetView;
+    /**
+     * The {@link #mTouchTargetView}'s location in window, set on touch down.
+     */
+    private int[] mTouchTargetLocationInWindow = new int[2];
+    /**
+     * If the zoom ring is dismissed but the user is still in a touch
+     * interaction, we set this to true. This will ignore all touch events until
+     * up/cancel, and then set the owner's touch listener to null.
+     */
+    private boolean mReleaseTouchListenerOnUp;
+
+
+    /*
+     * Tap-drag is an interaction where the user first taps and then (quickly)
+     * does the clockwise or counter-clockwise drag. In reality, this is: (down,
+     * up, down, move in circles, up). This differs from the usual events of:
+     * (down, up, down, up, down, move in circles, up). While the only
+     * difference is the omission of an (up, down), for power-users this is a
+     * pretty big improvement as it now only requires them to focus on the
+     * screen once (for the first tap down) instead of twice (for the first tap
+     * down and then to grab the thumb).
+     */
+    private int mTapDragStartX;
+    private int mTapDragStartY;
+
+    private static final int TOUCH_MODE_IDLE = 0;
+    private static final int TOUCH_MODE_WAITING_FOR_SECOND_TAP = 1;
+    private static final int TOUCH_MODE_WAITING_FOR_TAP_DRAG_MOVEMENT = 2;
+    private static final int TOUCH_MODE_FORWARDING_FOR_TAP_DRAG = 3;
+    private int mTouchMode;
+
+    private boolean mIsZoomRingVisible;
+
+    private ZoomRing mZoomRing;
+    private int mZoomRingWidth;
+    private int mZoomRingHeight;
+
+    /** Invokes panning of owner view if the zoom ring is touching an edge. */
+    private Panner mPanner;
+    private long mTouchingEdgeStartTime;
+    private boolean mPanningEnabledForThisInteraction;
+
+    /**
+     * When the finger moves the zoom ring to an edge, this is the horizontal
+     * accumulator for how much the finger has moved off of its original touch
+     * point on the zoom ring (OOB = out-of-bounds). If < 0, the finger has
+     * moved that many px to the left of its original touch point on the ring.
+     */
+    private int mMovingZoomRingOobX;
+    /** Vertical accumulator, see {@link #mMovingZoomRingOobX} */
+    private int mMovingZoomRingOobY;
+
+    private ImageView mPanningArrows;
+    private Animation mPanningArrowsEnterAnimation;
+    private Animation mPanningArrowsExitAnimation;
+
+    private Rect mTempRect = new Rect();
+
+    private OnZoomListener mCallback;
+
+    private ViewConfiguration mViewConfig;
+
+    /**
+     * When the zoom ring is centered on screen, this will be the x value used
+     * for the container's layout params.
+     */
+    private int mCenteredContainerX = Integer.MIN_VALUE;
+
+    /**
+     * When the zoom ring is centered on screen, this will be the y value used
+     * for the container's layout params.
+     */
+    private int mCenteredContainerY = Integer.MIN_VALUE;
+
+    /**
+     * Scroller used to re-center the zoom ring if the user had dragged it to a
+     * corner and then double-taps any point on the owner view (the owner view
+     * will center the double-tapped point, but we should re-center the zoom
+     * ring).
+     * <p>
+     * The (x,y) of the scroller is the (x,y) of the container's layout params.
+     */
+    private Scroller mScroller;
+
+    /**
+     * When showing the zoom ring, we add the view as a new window. However,
+     * there is logic that needs to know the size of the zoom ring which is
+     * determined after it's laid out. Therefore, we must post this logic onto
+     * the UI thread so it will be exceuted AFTER the layout. This is the logic.
+     */
+    private Runnable mPostedVisibleInitializer;
+
+    /**
+     * Only touch from the main thread.
+     */
+    private static Dialog sTutorialDialog;
+    private static long sTutorialShowTime;
+    private static final int TUTORIAL_MIN_DISPLAY_TIME = 2000;
+
+    private IntentFilter mConfigurationChangedFilter =
+            new IntentFilter(Intent.ACTION_CONFIGURATION_CHANGED);
+
+    private BroadcastReceiver mConfigurationChangedReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            if (!mIsZoomRingVisible) return;
+
+            mHandler.removeMessages(MSG_POST_CONFIGURATION_CHANGED);
+            mHandler.sendEmptyMessage(MSG_POST_CONFIGURATION_CHANGED);
+        }
+    };
+
+    /** Keeps the scroller going (or starts it). */
+    private static final int MSG_SCROLLER_TICK = 1;
+    /** When configuration changes, this is called after the UI thread is idle. */
+    private static final int MSG_POST_CONFIGURATION_CHANGED = 2;
+    /** Used to delay the zoom ring dismissal. */
+    private static final int MSG_DISMISS_ZOOM_RING = 3;
+
+    /**
+     * If setVisible(true) is called and the owner view's window token is null,
+     * we delay the setVisible(true) call until it is not null.
+     */
+    private static final int MSG_POST_SET_VISIBLE = 4;
+    
+    private Handler mHandler = new Handler() {
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case MSG_SCROLLER_TICK:
+                    onScrollerTick();
+                    break;
+
+                case MSG_POST_CONFIGURATION_CHANGED:
+                    onPostConfigurationChanged();
+                    break;
+
+                case MSG_DISMISS_ZOOM_RING:
+                    setVisible(false);
+                    break;
+                    
+                case MSG_POST_SET_VISIBLE:
+                    if (mOwnerView.getWindowToken() == null) {
+                        // Doh, it is still null, throw an exception
+                        throw new IllegalArgumentException(
+                                "Cannot make the zoom ring visible if the owner view is " +
+                                "not attached to a window.");
+                    }
+                    setVisible(true);
+                    break;
+            }
+
+        }
+    };
+
+    public ZoomRingController(Context context, View ownerView) {
+        mContext = context;
+        mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
+
+        mPanner = new Panner();
+        mOwnerView = ownerView;
+
+        mZoomRing = new ZoomRing(context);
+        mZoomRing.setId(com.android.internal.R.id.zoomControls);
+        mZoomRing.setLayoutParams(new FrameLayout.LayoutParams(
+                FrameLayout.LayoutParams.WRAP_CONTENT,
+                FrameLayout.LayoutParams.WRAP_CONTENT,
+                Gravity.CENTER));
+        mZoomRing.setCallback(this);
+
+        createPanningArrows();
+
+        mContainerLayoutParams = new LayoutParams();
+        mContainerLayoutParams.gravity = Gravity.LEFT | Gravity.TOP;
+        mContainerLayoutParams.flags = LayoutParams.FLAG_NOT_TOUCHABLE |
+                LayoutParams.FLAG_NOT_FOCUSABLE |
+                LayoutParams.FLAG_LAYOUT_NO_LIMITS;
+        mContainerLayoutParams.height = LayoutParams.WRAP_CONTENT;
+        mContainerLayoutParams.width = LayoutParams.WRAP_CONTENT;
+        mContainerLayoutParams.type = LayoutParams.TYPE_APPLICATION_PANEL;
+        mContainerLayoutParams.format = PixelFormat.TRANSPARENT;
+        // TODO: make a new animation for this
+        mContainerLayoutParams.windowAnimations = com.android.internal.R.style.Animation_Dialog;
+
+        mContainer = new FrameLayout(context);
+        mContainer.setLayoutParams(mContainerLayoutParams);
+        mContainer.setMeasureAllChildren(true);
+
+        mContainer.addView(mZoomRing);
+        mContainer.addView(mPanningArrows);
+
+        mScroller = new Scroller(context, new DecelerateInterpolator());
+
+        mViewConfig = ViewConfiguration.get(context);
+    }
+
+    private void createPanningArrows() {
+        // TODO: style
+        mPanningArrows = new ImageView(mContext);
+        mPanningArrows.setImageResource(com.android.internal.R.drawable.zoom_ring_arrows);
+        mPanningArrows.setLayoutParams(new FrameLayout.LayoutParams(
+                FrameLayout.LayoutParams.WRAP_CONTENT,
+                FrameLayout.LayoutParams.WRAP_CONTENT,
+                Gravity.CENTER));
+        mPanningArrows.setVisibility(View.INVISIBLE);
+
+        mPanningArrowsEnterAnimation = AnimationUtils.loadAnimation(mContext,
+                com.android.internal.R.anim.fade_in);
+        mPanningArrowsExitAnimation = AnimationUtils.loadAnimation(mContext,
+                com.android.internal.R.anim.fade_out);
+    }
+
+    /**
+     * Sets the angle (in radians) a user must travel in order for the client to
+     * get a callback. Once there is a callback, the accumulator resets. For
+     * example, if you set this to PI/6, it will give a callback every time the
+     * user moves PI/6 amount on the ring.
+     *
+     * @param callbackThreshold The angle for the callback threshold, in radians
+     */
+    public void setZoomCallbackThreshold(float callbackThreshold) {
+        mZoomRing.setCallbackThreshold((int) (callbackThreshold * ZoomRing.RADIAN_INT_MULTIPLIER));
+    }
+
+    /**
+     * Sets a drawable for the zoom ring track.
+     *
+     * @param drawable The drawable to use for the track.
+     * @hide Need a better way of doing this, but this one-off for browser so it
+     *       can have its final look for the usability study
+     */
+    public void setZoomRingTrack(int drawable) {
+        mZoomRing.setBackgroundResource(drawable);
+    }
+
+    public void setCallback(OnZoomListener callback) {
+        mCallback = callback;
+    }
+
+    public void setThumbAngle(float angle) {
+        mZoomRing.setThumbAngle((int) (angle * ZoomRing.RADIAN_INT_MULTIPLIER));
+    }
+
+    public void setThumbAngleAnimated(float angle) {
+        mZoomRing.setThumbAngleAnimated((int) (angle * ZoomRing.RADIAN_INT_MULTIPLIER), 0);
+    }
+
+    public void setResetThumbAutomatically(boolean resetThumbAutomatically) {
+        mZoomRing.setResetThumbAutomatically(resetThumbAutomatically);
+    }
+
+    public void setVibration(boolean vibrate) {
+        mZoomRing.setVibration(vibrate);
+    }
+    
+    public void setThumbVisible(boolean thumbVisible) {
+        mZoomRing.setThumbVisible(thumbVisible);
+    }
+    
+    public void setThumbClockwiseBound(float angle) {
+        mZoomRing.setThumbClockwiseBound(angle >= 0 ?
+                (int) (angle * ZoomRing.RADIAN_INT_MULTIPLIER) :
+                Integer.MIN_VALUE);
+    }
+
+    public void setThumbCounterclockwiseBound(float angle) {
+        mZoomRing.setThumbCounterclockwiseBound(angle >= 0 ?
+                (int) (angle * ZoomRing.RADIAN_INT_MULTIPLIER) :
+                Integer.MIN_VALUE);
+    }
+
+    public boolean isVisible() {
+        return mIsZoomRingVisible;
+    }
+
+    public void setVisible(boolean visible) {
+
+        if (!useThisZoom(mContext)) return;
+        
+        if (visible) {
+            if (mOwnerView.getWindowToken() == null) {
+                /*
+                 * We need a window token to show ourselves, maybe the owner's
+                 * window hasn't been created yet but it will have been by the
+                 * time the looper is idle, so post the setVisible(true) call.
+                 */
+                if (!mHandler.hasMessages(MSG_POST_SET_VISIBLE)) {
+                    mHandler.sendEmptyMessage(MSG_POST_SET_VISIBLE);
+                }
+                return;
+            }
+            
+            dismissZoomRingDelayed(ZOOM_CONTROLS_TIMEOUT);
+        } else {
+            mPanner.stop();
+        }
+
+        if (mIsZoomRingVisible == visible) {
+            return;
+        }
+        mIsZoomRingVisible = visible;
+
+        if (visible) {
+            if (mContainerLayoutParams.token == null) {
+                mContainerLayoutParams.token = mOwnerView.getWindowToken();
+            }
+
+            mWindowManager.addView(mContainer, mContainerLayoutParams);
+
+            if (mPostedVisibleInitializer == null) {
+                mPostedVisibleInitializer = new Runnable() {
+                    public void run() {
+                        refreshPositioningVariables();
+                        resetZoomRing();
+
+                        // TODO: remove this 'update' and just center zoom ring before the
+                        // 'add', but need to make sure we have the width and height (which
+                        // probably can only be retrieved after it's measured, which happens
+                        // after it's added).
+                        mWindowManager.updateViewLayout(mContainer, mContainerLayoutParams);
+
+                        if (mCallback != null) {
+                            mCallback.onVisibilityChanged(true);
+                        }
+                    }
+                };
+            }
+
+            mPanningArrows.setAnimation(null);
+
+            mHandler.post(mPostedVisibleInitializer);
+
+            // Handle configuration changes when visible
+            mContext.registerReceiver(mConfigurationChangedReceiver, mConfigurationChangedFilter);
+
+            // Steal key/touches events from the owner
+            mOwnerView.setOnKeyListener(this);
+            mOwnerView.setOnTouchListener(this);
+            mReleaseTouchListenerOnUp = false;
+
+        } else {
+            // Don't want to steal any more keys/touches
+            mOwnerView.setOnKeyListener(null);
+            if (mTouchTargetView != null) {
+                // We are still stealing the touch events for this touch
+                // sequence, so release the touch listener later
+                mReleaseTouchListenerOnUp = true;
+            } else {
+                mOwnerView.setOnTouchListener(null);
+            }
+
+            // No longer care about configuration changes
+            mContext.unregisterReceiver(mConfigurationChangedReceiver);
+
+            mWindowManager.removeView(mContainer);
+            mHandler.removeCallbacks(mPostedVisibleInitializer);
+
+            if (mCallback != null) {
+                mCallback.onVisibilityChanged(false);
+            }
+        }
+
+    }
+
+    /**
+     * TODO: docs
+     *
+     * Notes:
+     * - Touch dispatching is different.  Only direct children who are clickable are eligble for touch events.
+     * - Please ensure you set your View to INVISIBLE not GONE when hiding it.
+     *
+     * @return
+     */
+    public FrameLayout getContainer() {
+        return mContainer;
+    }
+
+    public int getZoomRingId() {
+        return mZoomRing.getId();
+    }
+
+    private void dismissZoomRingDelayed(int delay) {
+        mHandler.removeMessages(MSG_DISMISS_ZOOM_RING);
+        mHandler.sendEmptyMessageDelayed(MSG_DISMISS_ZOOM_RING, delay);
+    }
+
+    private void resetZoomRing() {
+        mScroller.abortAnimation();
+
+        mContainerLayoutParams.x = mCenteredContainerX;
+        mContainerLayoutParams.y = mCenteredContainerY;
+
+        // Reset the thumb
+        mZoomRing.resetThumbAngle();
+    }
+
+    /**
+     * Should be called by the client for each event belonging to the second tap
+     * (the down, move, up, and cancel events).
+     *
+     * @param event The event belonging to the second tap.
+     * @return Whether the event was consumed.
+     */
+    public boolean handleDoubleTapEvent(MotionEvent event) {
+        if (!useThisZoom(mContext)) return false;
+        
+        int action = event.getAction();
+
+        // TODO: make sure this works well with the
+        // ownerView.setOnTouchListener(this) instead of window receiving
+        // touches
+        if (action == MotionEvent.ACTION_DOWN) {
+            mTouchMode = TOUCH_MODE_WAITING_FOR_TAP_DRAG_MOVEMENT;
+            int x = (int) event.getX();
+            int y = (int) event.getY();
+
+            refreshPositioningVariables();
+            setVisible(true);
+            centerPoint(x, y);
+            ensureZoomRingIsCentered();
+
+            // Tap drag mode stuff
+            mTapDragStartX = x;
+            mTapDragStartY = y;
+
+        } else if (action == MotionEvent.ACTION_CANCEL) {
+            mTouchMode = TOUCH_MODE_IDLE;
+
+        } else { // action is move or up
+            switch (mTouchMode) {
+                case TOUCH_MODE_WAITING_FOR_TAP_DRAG_MOVEMENT: {
+                    switch (action) {
+                        case MotionEvent.ACTION_MOVE:
+                            int x = (int) event.getX();
+                            int y = (int) event.getY();
+                            if (Math.abs(x - mTapDragStartX) > mViewConfig.getScaledTouchSlop() ||
+                                    Math.abs(y - mTapDragStartY) >
+                                    mViewConfig.getScaledTouchSlop()) {
+                                mZoomRing.setTapDragMode(true, x, y);
+                                mTouchMode = TOUCH_MODE_FORWARDING_FOR_TAP_DRAG;
+                                setTouchTargetView(mZoomRing);
+                            }
+                            return true;
+
+                        case MotionEvent.ACTION_UP:
+                            mTouchMode = TOUCH_MODE_IDLE;
+                            break;
+                    }
+                    break;
+                }
+
+                case TOUCH_MODE_FORWARDING_FOR_TAP_DRAG: {
+                    switch (action) {
+                        case MotionEvent.ACTION_MOVE:
+                            giveTouchToZoomRing(event);
+                            return true;
+
+                        case MotionEvent.ACTION_UP:
+                            mTouchMode = TOUCH_MODE_IDLE;
+
+                            /*
+                             * This is a power-user feature that only shows the
+                             * zoom while the user is performing the tap-drag.
+                             * That means once it is released, the zoom ring
+                             * should disappear.
+                             */
+                            mZoomRing.setTapDragMode(false, (int) event.getX(), (int) event.getY());
+                            dismissZoomRingDelayed(0);
+                            break;
+                    }
+                    break;
+                }
+            }
+        }
+
+        return true;
+    }
+
+    private void ensureZoomRingIsCentered() {
+        LayoutParams lp = mContainerLayoutParams;
+
+        if (lp.x != mCenteredContainerX || lp.y != mCenteredContainerY) {
+            int width = mContainer.getWidth();
+            int height = mContainer.getHeight();
+            mScroller.startScroll(lp.x, lp.y, mCenteredContainerX - lp.x,
+                    mCenteredContainerY - lp.y, ZOOM_RING_RECENTERING_DURATION);
+            mHandler.sendEmptyMessage(MSG_SCROLLER_TICK);
+        }
+    }
+
+    private void refreshPositioningVariables() {
+        mZoomRingWidth = mZoomRing.getWidth();
+        mZoomRingHeight = mZoomRing.getHeight();
+
+        // Calculate the owner view's bounds
+        mOwnerView.getGlobalVisibleRect(mOwnerViewBounds);
+
+        // Get the center
+        Gravity.apply(Gravity.CENTER, mContainer.getWidth(), mContainer.getHeight(),
+                mOwnerViewBounds, mTempRect);
+        mCenteredContainerX = mTempRect.left;
+        mCenteredContainerY = mTempRect.top;
+    }
+
+    /**
+     * Centers the point (in owner view's coordinates).
+     */
+    private void centerPoint(int x, int y) {
+        if (mCallback != null) {
+            mCallback.onCenter(x, y);
+        }
+    }
+
+    private void giveTouchToZoomRing(MotionEvent event) {
+        int rawX = (int) event.getRawX();
+        int rawY = (int) event.getRawY();
+        int x = rawX - mContainerLayoutParams.x - mZoomRing.getLeft();
+        int y = rawY - mContainerLayoutParams.y - mZoomRing.getTop();
+        mZoomRing.handleTouch(event.getAction(), event.getEventTime(), x, y, rawX, rawY);
+    }
+
+    public void onZoomRingSetMovableHintVisible(boolean visible) {
+        setPanningArrowsVisible(visible);
+    }
+
+    public void onUserInteractionStarted() {
+        mHandler.removeMessages(MSG_DISMISS_ZOOM_RING);
+    }
+
+    public void onUserInteractionStopped() {
+        dismissZoomRingDelayed(ZOOM_CONTROLS_TIMEOUT);
+    }
+
+    public void onZoomRingMovingStarted() {
+        mScroller.abortAnimation();
+        mTouchingEdgeStartTime = 0;
+        mMovingZoomRingOobX = 0;
+        mMovingZoomRingOobY = 0;
+        if (mCallback != null) {
+            mCallback.onBeginPan();
+        }
+    }
+
+    private void setPanningArrowsVisible(boolean visible) {
+        mPanningArrows.startAnimation(visible ? mPanningArrowsEnterAnimation
+                : mPanningArrowsExitAnimation);
+        mPanningArrows.setVisibility(visible ? View.VISIBLE : View.INVISIBLE);
+    }
+
+    public boolean onZoomRingMoved(int deltaX, int deltaY, int rawX, int rawY) {
+
+        if (mMovingZoomRingOobX != 0) {
+            /*
+             * The finger has moved off the point where it originally touched
+             * the zidget.
+             */
+            boolean wasOobLeft = mMovingZoomRingOobX < 0;
+            mMovingZoomRingOobX += deltaX;
+            if ((wasOobLeft && mMovingZoomRingOobX > 0) ||
+                    (!wasOobLeft && mMovingZoomRingOobX < 0)) {
+                /*
+                 * Woot, the finger is back on the original point. Infact, it
+                 * went PAST its original point, so take the amount it passed
+                 * and use that as the delta to move the zoom ring.
+                 */ 
+                deltaX = mMovingZoomRingOobX;
+                // No longer out-of-bounds, reset
+                mMovingZoomRingOobX = 0;
+            } else {
+                // The finger is still not back, eat this movement
+                deltaX = 0;
+            }
+        }
+        
+        if (mMovingZoomRingOobY != 0) {
+            // See above for comments
+            boolean wasOobUp = mMovingZoomRingOobY < 0;
+            mMovingZoomRingOobY += deltaY;
+            if ((wasOobUp && mMovingZoomRingOobY > 0) || (!wasOobUp && mMovingZoomRingOobY < 0)) {
+                deltaY = mMovingZoomRingOobY;
+                mMovingZoomRingOobY = 0;
+            } else {
+                deltaY = 0;
+            }
+        }
+        
+        WindowManager.LayoutParams lp = mContainerLayoutParams;
+        Rect ownerBounds = mOwnerViewBounds;
+
+        int zoomRingLeft = mZoomRing.getLeft();
+        int zoomRingTop = mZoomRing.getTop();
+
+        int newX = lp.x + deltaX;
+        int newZoomRingX = newX + zoomRingLeft;
+        newZoomRingX = (newZoomRingX <= ownerBounds.left) ? ownerBounds.left :
+                (newZoomRingX + mZoomRingWidth > ownerBounds.right) ?
+                        ownerBounds.right - mZoomRingWidth : newZoomRingX;
+        lp.x = newZoomRingX - zoomRingLeft;
+
+        int newY = lp.y + deltaY;
+        int newZoomRingY = newY + zoomRingTop;
+        newZoomRingY = (newZoomRingY <= ownerBounds.top) ? ownerBounds.top :
+            (newZoomRingY + mZoomRingHeight > ownerBounds.bottom) ?
+                    ownerBounds.bottom - mZoomRingHeight : newZoomRingY;
+        lp.y = newZoomRingY - zoomRingTop;
+
+        mWindowManager.updateViewLayout(mContainer, lp);
+
+        // Check for pan
+        boolean horizontalPanning = true;
+        int leftGap = newZoomRingX - ownerBounds.left;
+        if (leftGap < MAX_PAN_GAP) {
+            if (leftGap == 0 && deltaX != 0 && mMovingZoomRingOobX == 0) {
+                // Future moves in this direction should be accumulated in mMovingZoomRingOobX
+                mMovingZoomRingOobX = deltaX / Math.abs(deltaX);
+            }
+            if (shouldPan(leftGap)) {
+                mPanner.setHorizontalStrength(-getStrengthFromGap(leftGap));
+            }
+        } else {
+            int rightGap = ownerBounds.right - (lp.x + mZoomRingWidth + zoomRingLeft);
+            if (rightGap < MAX_PAN_GAP) {
+                if (rightGap == 0 && deltaX != 0 && mMovingZoomRingOobX == 0) {
+                    mMovingZoomRingOobX = deltaX / Math.abs(deltaX);
+                }
+                if (shouldPan(rightGap)) {
+                    mPanner.setHorizontalStrength(getStrengthFromGap(rightGap));
+                }
+            } else {
+                mPanner.setHorizontalStrength(0);
+                horizontalPanning = false;
+            }
+        }
+
+        int topGap = newZoomRingY - ownerBounds.top;
+        if (topGap < MAX_PAN_GAP) {
+            if (topGap == 0 && deltaY != 0 && mMovingZoomRingOobY == 0) {
+                mMovingZoomRingOobY = deltaY / Math.abs(deltaY);
+            }
+            if (shouldPan(topGap)) {
+                mPanner.setVerticalStrength(-getStrengthFromGap(topGap));
+            }
+        } else {
+            int bottomGap = ownerBounds.bottom - (lp.y + mZoomRingHeight + zoomRingTop);
+            if (bottomGap < MAX_PAN_GAP) {
+                if (bottomGap == 0 && deltaY != 0 && mMovingZoomRingOobY == 0) {
+                    mMovingZoomRingOobY = deltaY / Math.abs(deltaY);
+                }
+                if (shouldPan(bottomGap)) {
+                    mPanner.setVerticalStrength(getStrengthFromGap(bottomGap));
+                }
+            } else {
+                mPanner.setVerticalStrength(0);
+                if (!horizontalPanning) {
+                    // Neither are panning, reset any timer to start pan mode
+                    mTouchingEdgeStartTime = 0;
+                    mPanningEnabledForThisInteraction = false;
+                    mPanner.stop();
+                }
+            }
+        }
+
+        return true;
+    }
+
+    private boolean shouldPan(int gap) {
+        if (mPanningEnabledForThisInteraction) return true;
+
+        if (gap < MAX_INITIATE_PAN_GAP) {
+            long time = SystemClock.elapsedRealtime();
+            if (mTouchingEdgeStartTime != 0 &&
+                    mTouchingEdgeStartTime + INITIATE_PAN_DELAY < time) {
+                mPanningEnabledForThisInteraction = true;
+                return true;
+            } else if (mTouchingEdgeStartTime == 0) {
+                mTouchingEdgeStartTime = time;
+            } else {
+            }
+        } else {
+            // Moved away from the initiate pan gap, so reset the timer
+            mTouchingEdgeStartTime = 0;
+        }
+        return false;
+    }
+
+    public void onZoomRingMovingStopped() {
+        mPanner.stop();
+        setPanningArrowsVisible(false);
+        if (mCallback != null) {
+            mCallback.onEndPan();
+        }
+    }
+
+    private int getStrengthFromGap(int gap) {
+        return gap > MAX_PAN_GAP ? 0 :
+            (MAX_PAN_GAP - gap) * 100 / MAX_PAN_GAP;
+    }
+
+    public void onZoomRingThumbDraggingStarted() {
+        if (mCallback != null) {
+            mCallback.onBeginDrag();
+        }
+    }
+
+    public boolean onZoomRingThumbDragged(int numLevels, int startAngle, int curAngle) {
+        if (mCallback != null) {
+            int deltaZoomLevel = -numLevels;
+            int globalZoomCenterX = mContainerLayoutParams.x + mZoomRing.getLeft() +
+                    mZoomRingWidth / 2;
+            int globalZoomCenterY = mContainerLayoutParams.y + mZoomRing.getTop() +
+                    mZoomRingHeight / 2;
+
+            return mCallback.onDragZoom(deltaZoomLevel,
+                    globalZoomCenterX - mOwnerViewBounds.left,
+                    globalZoomCenterY - mOwnerViewBounds.top,
+                    (float) startAngle / ZoomRing.RADIAN_INT_MULTIPLIER,
+                    (float) curAngle / ZoomRing.RADIAN_INT_MULTIPLIER);
+        }
+
+        return false;
+    }
+
+    public void onZoomRingThumbDraggingStopped() {
+        if (mCallback != null) {
+            mCallback.onEndDrag();
+        }
+    }
+
+    public void onZoomRingDismissed(boolean dismissImmediately) {
+        if (dismissImmediately) {
+            mHandler.removeMessages(MSG_DISMISS_ZOOM_RING);
+            setVisible(false);
+        } else {
+            dismissZoomRingDelayed(ZOOM_RING_DISMISS_DELAY);
+        }
+    }
+
+    public void onRingDown(int tickAngle, int touchAngle) {
+    }
+
+    public boolean onTouch(View v, MotionEvent event) {
+        if (sTutorialDialog != null && sTutorialDialog.isShowing() &&
+                SystemClock.elapsedRealtime() - sTutorialShowTime >= TUTORIAL_MIN_DISPLAY_TIME) {
+            finishZoomTutorial();
+        }
+
+        int action = event.getAction();
+
+        if (mReleaseTouchListenerOnUp) {
+            // The ring was dismissed but we need to throw away all events until the up
+            if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
+                mOwnerView.setOnTouchListener(null);
+                setTouchTargetView(null);
+                mReleaseTouchListenerOnUp = false;
+            }
+
+            // Eat this event
+            return true;
+        }
+
+        View targetView = mTouchTargetView;
+
+        switch (action) {
+            case MotionEvent.ACTION_DOWN:
+                targetView = getViewForTouch((int) event.getRawX(), (int) event.getRawY());
+                setTouchTargetView(targetView);
+                break;
+
+            case MotionEvent.ACTION_UP:
+            case MotionEvent.ACTION_CANCEL:
+                setTouchTargetView(null);
+                break;
+        }
+
+        if (targetView != null) {
+            // The upperleft corner of the target view in raw coordinates
+            int targetViewRawX = mContainerLayoutParams.x + mTouchTargetLocationInWindow[0];
+            int targetViewRawY = mContainerLayoutParams.y + mTouchTargetLocationInWindow[1];
+
+            MotionEvent containerEvent = MotionEvent.obtain(event);
+            // Convert the motion event into the target view's coordinates (from
+            // owner view's coordinates)
+            containerEvent.offsetLocation(mOwnerViewBounds.left - targetViewRawX,
+                    mOwnerViewBounds.top - targetViewRawY);
+            boolean retValue = targetView.dispatchTouchEvent(containerEvent);
+            containerEvent.recycle();
+            return retValue;
+
+        } else {
+            if (action == MotionEvent.ACTION_DOWN) {
+                dismissZoomRingDelayed(ZOOM_RING_DISMISS_DELAY);
+            }
+
+            return false;
+        }
+    }
+
+    private void setTouchTargetView(View view) {
+        mTouchTargetView = view;
+        if (view != null) {
+            view.getLocationInWindow(mTouchTargetLocationInWindow);
+        }
+    }
+
+    /**
+     * Returns the View that should receive a touch at the given coordinates.
+     *
+     * @param rawX The raw X.
+     * @param rawY The raw Y.
+     * @return The view that should receive the touches, or null if there is not one.
+     */
+    private View getViewForTouch(int rawX, int rawY) {
+        // Check to see if it is touching the ring
+        int containerCenterX = mContainerLayoutParams.x + mContainer.getWidth() / 2;
+        int containerCenterY = mContainerLayoutParams.y + mContainer.getHeight() / 2;
+        int distanceFromCenterX = rawX - containerCenterX;
+        int distanceFromCenterY = rawY - containerCenterY;
+        int zoomRingRadius = mZoomRingWidth / 2 - ZOOM_RING_RADIUS_INSET;
+        if (distanceFromCenterX * distanceFromCenterX +
+                distanceFromCenterY * distanceFromCenterY <=
+                zoomRingRadius * zoomRingRadius) {
+            return mZoomRing;
+        }
+
+        // Check to see if it is touching any other clickable View.
+        // Reverse order so the child drawn on top gets first dibs.
+        int containerCoordsX = rawX - mContainerLayoutParams.x;
+        int containerCoordsY = rawY - mContainerLayoutParams.y;
+        Rect frame = mTempRect;
+        for (int i = mContainer.getChildCount() - 1; i >= 0; i--) {
+            View child = mContainer.getChildAt(i);
+            if (child == mZoomRing || child.getVisibility() != View.VISIBLE ||
+                    !child.isClickable()) {
+                continue;
+            }
+
+            child.getHitRect(frame);
+            if (frame.contains(containerCoordsX, containerCoordsY)) {
+                return child;
+            }
+        }
+
+        return null;
+    }
+
+    /** Steals key events from the owner view. */
+    public boolean onKey(View v, int keyCode, KeyEvent event) {
+        switch (keyCode) {
+            case KeyEvent.KEYCODE_DPAD_LEFT:
+            case KeyEvent.KEYCODE_DPAD_RIGHT:
+                // Eat these
+                return true;
+
+            case KeyEvent.KEYCODE_DPAD_UP:
+            case KeyEvent.KEYCODE_DPAD_DOWN:
+                // Keep the zoom alive a little longer
+                dismissZoomRingDelayed(ZOOM_CONTROLS_TIMEOUT);
+                // They started zooming, hide the thumb arrows
+                mZoomRing.setThumbArrowsVisible(false);
+
+                if (mCallback != null && event.getAction() == KeyEvent.ACTION_DOWN) {
+                    mCallback.onSimpleZoom(keyCode == KeyEvent.KEYCODE_DPAD_UP);
+                }
+
+                return true;
+        }
+
+        return false;
+    }
+
+    private void onScrollerTick() {
+        if (!mScroller.computeScrollOffset() || !mIsZoomRingVisible) return;
+
+        mContainerLayoutParams.x = mScroller.getCurrX();
+        mContainerLayoutParams.y = mScroller.getCurrY();
+        mWindowManager.updateViewLayout(mContainer, mContainerLayoutParams);
+
+        mHandler.sendEmptyMessage(MSG_SCROLLER_TICK);
+    }
+
+    private void onPostConfigurationChanged() {
+        dismissZoomRingDelayed(ZOOM_CONTROLS_TIMEOUT);
+        refreshPositioningVariables();
+        ensureZoomRingIsCentered();
+    }
+
+    /*
+     * This is static so Activities can call this instead of the Views
+     * (Activities usually do not have a reference to the ZoomRingController
+     * instance.)
+     */
+    /**
+     * Shows a "tutorial" (some text) to the user teaching her the new zoom
+     * invocation method. Must call from the main thread.
+     * <p>
+     * It checks the global system setting to ensure this has not been seen
+     * before. Furthermore, if the application does not have privilege to write
+     * to the system settings, it will store this bit locally in a shared
+     * preference.
+     *
+     * @hide This should only be used by our main apps--browser, maps, and
+     *       gallery
+     */
+    public static void showZoomTutorialOnce(Context context) {
+        ContentResolver cr = context.getContentResolver();
+        if (Settings.System.getInt(cr, SETTING_NAME_SHOWN_TOAST, 0) == 1) {
+            return;
+        }
+
+        SharedPreferences sp = context.getSharedPreferences("_zoom", Context.MODE_PRIVATE);
+        if (sp.getInt(SETTING_NAME_SHOWN_TOAST, 0) == 1) {
+            return;
+        }
+
+        if (sTutorialDialog != null && sTutorialDialog.isShowing()) {
+            sTutorialDialog.dismiss();
+        }
+
+        LayoutInflater layoutInflater =
+                (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+        TextView textView = (TextView) layoutInflater.inflate(
+                com.android.internal.R.layout.alert_dialog_simple_text, null)
+                .findViewById(android.R.id.text1);
+        textView.setText(com.android.internal.R.string.tutorial_double_tap_to_zoom_message_short);
+        
+        sTutorialDialog = new AlertDialog.Builder(context)
+                .setView(textView)
+                .setIcon(0)
+                .create();
+
+        Window window = sTutorialDialog.getWindow();
+        window.setGravity(Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM);
+        window.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND |
+                WindowManager.LayoutParams.FLAG_BLUR_BEHIND);
+        window.addFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE |
+                WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE);
+
+        sTutorialDialog.show();
+        sTutorialShowTime = SystemClock.elapsedRealtime();
+    }
+
+    public static void finishZoomTutorial(Context context, boolean userNotified) {
+        if (sTutorialDialog == null) return;
+
+        sTutorialDialog.dismiss();
+        sTutorialDialog = null;
+
+        // Record that they have seen the tutorial
+        if (userNotified) {
+            try {
+                Settings.System.putInt(context.getContentResolver(), SETTING_NAME_SHOWN_TOAST, 1);
+            } catch (SecurityException e) {
+                /*
+                 * The app does not have permission to clear this global flag, make
+                 * sure the user does not see the message when he comes back to this
+                 * same app at least.
+                 */
+                SharedPreferences sp = context.getSharedPreferences("_zoom", Context.MODE_PRIVATE);
+                sp.edit().putInt(SETTING_NAME_SHOWN_TOAST, 1).commit();
+            }
+        }
+    }
+
+    public void finishZoomTutorial() {
+        finishZoomTutorial(mContext, true);
+    }
+
+    public void setPannerStartVelocity(float startVelocity) {
+        mPanner.mStartVelocity = startVelocity;
+    }
+
+    public void setPannerAcceleration(float acceleration) {
+        mPanner.mAcceleration = acceleration;
+    }
+
+    public void setPannerMaxVelocity(float maxVelocity) {
+        mPanner.mMaxVelocity = maxVelocity;
+    }
+
+    public void setPannerStartAcceleratingDuration(int duration) {
+        mPanner.mStartAcceleratingDuration = duration;
+    }
+
+    private class Panner implements Runnable {
+        private static final int RUN_DELAY = 15;
+        private static final float STOP_SLOWDOWN = 0.8f;
+
+        private final Handler mUiHandler = new Handler();
+
+        private int mVerticalStrength;
+        private int mHorizontalStrength;
+
+        private boolean mStopping;
+
+        /** The time this current pan started. */
+        private long mStartTime;
+
+        /** The time of the last callback to pan the map/browser/etc. */
+        private long mPreviousCallbackTime;
+
+        // TODO Adjust to be DPI safe
+        private float mStartVelocity = 135;
+        private float mAcceleration = 160;
+        private float mMaxVelocity = 1000;
+        private int mStartAcceleratingDuration = 700;
+        private float mVelocity;
+
+        /** -100 (full left) to 0 (none) to 100 (full right) */
+        public void setHorizontalStrength(int horizontalStrength) {
+            if (mHorizontalStrength == 0 && mVerticalStrength == 0 && horizontalStrength != 0) {
+                start();
+            } else if (mVerticalStrength == 0 && horizontalStrength == 0) {
+                stop();
+            }
+
+            mHorizontalStrength = horizontalStrength;
+            mStopping = false;
+        }
+
+        /** -100 (full up) to 0 (none) to 100 (full down) */
+        public void setVerticalStrength(int verticalStrength) {
+            if (mHorizontalStrength == 0 && mVerticalStrength == 0 && verticalStrength != 0) {
+                start();
+            } else if (mHorizontalStrength == 0 && verticalStrength == 0) {
+                stop();
+            }
+
+            mVerticalStrength = verticalStrength;
+            mStopping = false;
+        }
+
+        private void start() {
+            mUiHandler.post(this);
+            mPreviousCallbackTime = 0;
+            mStartTime = 0;
+        }
+
+        public void stop() {
+            mStopping = true;
+        }
+
+        public void run() {
+            if (mStopping) {
+                mHorizontalStrength *= STOP_SLOWDOWN;
+                mVerticalStrength *= STOP_SLOWDOWN;
+            }
+
+            if (mHorizontalStrength == 0 && mVerticalStrength == 0) {
+                return;
+            }
+
+            boolean firstRun = mPreviousCallbackTime == 0;
+            long curTime = SystemClock.elapsedRealtime();
+            int panAmount = getPanAmount(mPreviousCallbackTime, curTime);
+            mPreviousCallbackTime = curTime;
+
+            if (firstRun) {
+                mStartTime = curTime;
+                mVelocity = mStartVelocity;
+            } else {
+                int panX = panAmount * mHorizontalStrength / 100;
+                int panY = panAmount * mVerticalStrength / 100;
+
+                if (mCallback != null) {
+                    mCallback.onPan(panX, panY);
+                }
+            }
+
+            mUiHandler.postDelayed(this, RUN_DELAY);
+        }
+
+        private int getPanAmount(long previousTime, long currentTime) {
+            if (mVelocity > mMaxVelocity) {
+                mVelocity = mMaxVelocity;
+            } else if (mVelocity < mMaxVelocity) {
+                // See if it's time to add in some acceleration
+                if (currentTime - mStartTime > mStartAcceleratingDuration) {
+                    mVelocity += (currentTime - previousTime) * mAcceleration / 1000;
+                }
+            }
+
+            return (int) ((currentTime - previousTime) * mVelocity) / 1000;
+        }
+
+    }
+
+    public interface OnZoomListener {
+        void onBeginDrag();
+        boolean onDragZoom(int deltaZoomLevel, int centerX, int centerY, float startAngle,
+                float curAngle);
+        void onEndDrag();
+        void onSimpleZoom(boolean deltaZoomLevel);
+        void onBeginPan();
+        boolean onPan(int deltaX, int deltaY);
+        void onEndPan();
+        void onCenter(int x, int y);
+        void onVisibilityChanged(boolean visible);
+    }
+}
diff --git a/core/java/android/widget/package.html b/core/java/android/widget/package.html
new file mode 100644
index 0000000..7d94a4b
--- /dev/null
+++ b/core/java/android/widget/package.html
@@ -0,0 +1,32 @@
+<HTML>
+<BODY>
+The widget package contains (mostly visual) UI elements to use
+on your Application screen. You can design your own <p>
+To create your own widget, extend {@link android.view.View} or a subclass. To
+use your widget in layout XML, there are two additional files for you to
+create. Here is a list of files you'll need to create to implement a custom
+widget:
+<ul>
+<li><b>Java implementation file</b> - This is the file that implements the
+behavior of the widget. If you can instantiate the object from layout XML,
+you will also have to code a constructor that retrieves all the attribute
+values from the layout XML file.</li>
+<li><b>XML definition file</b> - An XML file in res/values/ that defines
+the XML element used to instantiate your widget, and the attributes that it
+supports. Other applications will use this element and attributes in their in
+another in their layout XML.</li>
+<li><b>Layout XML</b> [<em>optional</em>]- An optional XML file inside
+res/layout/ that describes the layout of your widget. You could also do
+this in code in your Java file.</li>
+</ul>
+ApiDemos sample application has an example of creating a custom layout XML
+tag, LabelView. See the following files that demonstrate implementing and using
+a custom widget:</p>
+<ul>
+    <li><strong>LabelView.java</strong> - The implentation file</li>
+    <li><strong>res/values/attrs.xml</strong> - Definition file</li>
+    <li><strong>res/layout/custom_view_1.xml</strong> - Layout
+file</li>
+</ul>
+</BODY>
+</HTML>
diff --git a/core/java/com/android/internal/app/AlertActivity.java b/core/java/com/android/internal/app/AlertActivity.java
new file mode 100644
index 0000000..7251256
--- /dev/null
+++ b/core/java/com/android/internal/app/AlertActivity.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2007 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.internal.app;
+
+import android.app.Activity;
+import android.content.DialogInterface;
+import android.os.Bundle;
+import android.view.KeyEvent;
+
+/**
+ * An activity that follows the visual style of an AlertDialog.
+ * 
+ * @see #mAlert
+ * @see #mAlertParams
+ * @see #setupAlert()
+ */
+public abstract class AlertActivity extends Activity implements DialogInterface {
+
+    /**
+     * The model for the alert.
+     * 
+     * @see #mAlertParams
+     */
+    protected AlertController mAlert;
+    
+    /**
+     * The parameters for the alert.
+     */
+    protected AlertController.AlertParams mAlertParams;
+    
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        
+        mAlert = new AlertController(this, this, getWindow());
+        mAlertParams = new AlertController.AlertParams(this);        
+    }
+
+    public void cancel() {
+        finish();
+    }
+
+    public void dismiss() {
+        // This is called after the click, since we finish when handling the
+        // click, don't do that again here.
+        if (!isFinishing()) {
+            finish();
+        }
+    }
+
+    /**
+     * Sets up the alert, including applying the parameters to the alert model,
+     * and installing the alert's content.
+     * 
+     * @see #mAlert
+     * @see #mAlertParams
+     */
+    protected void setupAlert() {
+        mAlertParams.apply(mAlert);
+        mAlert.installContent();
+    }
+    
+    @Override
+    public boolean onKeyDown(int keyCode, KeyEvent event) {
+        if (mAlert.onKeyDown(keyCode, event)) return true;
+        return super.onKeyDown(keyCode, event);
+    }
+
+    @Override
+    public boolean onKeyUp(int keyCode, KeyEvent event) {
+        if (mAlert.onKeyUp(keyCode, event)) return true;
+        return super.onKeyUp(keyCode, event);
+    }
+    
+    
+}
diff --git a/core/java/com/android/internal/app/AlertController.java b/core/java/com/android/internal/app/AlertController.java
new file mode 100644
index 0000000..5c44b2d
--- /dev/null
+++ b/core/java/com/android/internal/app/AlertController.java
@@ -0,0 +1,888 @@
+/*
+ * 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.internal.app;
+
+import static android.view.ViewGroup.LayoutParams.FILL_PARENT;
+import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
+import android.app.AlertDialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.res.TypedArray;
+import android.database.Cursor;
+import android.graphics.drawable.Drawable;
+import android.os.Handler;
+import android.os.Message;
+import android.text.TextUtils;
+import android.view.Gravity;
+import android.view.KeyEvent;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.Window;
+import android.view.WindowManager;
+import android.view.ViewGroup.LayoutParams;
+import android.widget.AdapterView;
+import android.widget.ArrayAdapter;
+import android.widget.Button;
+import android.widget.CheckedTextView;
+import android.widget.CursorAdapter;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.ListAdapter;
+import android.widget.ListView;
+import android.widget.ScrollView;
+import android.widget.SimpleCursorAdapter;
+import android.widget.TextView;
+import android.widget.AdapterView.OnItemClickListener;
+
+import com.android.internal.R;
+
+import java.lang.ref.WeakReference;
+
+public class AlertController {
+
+    private final Context mContext;
+    private final DialogInterface mDialogInterface;
+    private final Window mWindow;
+    
+    private CharSequence mTitle;
+
+    private CharSequence mMessage;
+
+    private ListView mListView;
+    
+    private View mView;
+
+    private int mViewSpacingLeft;
+    
+    private int mViewSpacingTop;
+    
+    private int mViewSpacingRight;
+    
+    private int mViewSpacingBottom;
+    
+    private boolean mViewSpacingSpecified = false;
+    
+    private Button mButtonPositive;
+
+    private CharSequence mButtonPositiveText;
+
+    private Message mButtonPositiveMessage;
+
+    private Button mButtonNegative;
+
+    private CharSequence mButtonNegativeText;
+
+    private Message mButtonNegativeMessage;
+
+    private Button mButtonNeutral;
+
+    private CharSequence mButtonNeutralText;
+
+    private Message mButtonNeutralMessage;
+
+    private ScrollView mScrollView;
+    
+    private int mIconId = -1;
+    
+    private Drawable mIcon;
+    
+    private ImageView mIconView;
+    
+    private TextView mTitleView;
+
+    private TextView mMessageView;
+
+    private View mCustomTitleView;
+    
+    private boolean mForceInverseBackground;
+    
+    private ListAdapter mAdapter;
+    
+    private int mCheckedItem = -1;
+
+    private Handler mHandler;
+
+    View.OnClickListener mButtonHandler = new View.OnClickListener() {
+        public void onClick(View v) {
+            Message m = null;
+            if (v == mButtonPositive && mButtonPositiveMessage != null) {
+                m = Message.obtain(mButtonPositiveMessage);
+            } else if (v == mButtonNegative && mButtonNegativeMessage != null) {
+                m = Message.obtain(mButtonNegativeMessage);
+            } else if (v == mButtonNeutral && mButtonNeutralMessage != null) {
+                m = Message.obtain(mButtonNeutralMessage);
+            }
+            if (m != null) {
+                m.sendToTarget();
+            }
+
+            // Post a message so we dismiss after the above handlers are executed
+            mHandler.obtainMessage(ButtonHandler.MSG_DISMISS_DIALOG, mDialogInterface)
+                    .sendToTarget();
+        }
+    };
+
+    private static final class ButtonHandler extends Handler {
+        // Button clicks have Message.what as the BUTTON{1,2,3} constant
+        private static final int MSG_DISMISS_DIALOG = 1;
+        
+        private WeakReference<DialogInterface> mDialog;
+
+        public ButtonHandler(DialogInterface dialog) {
+            mDialog = new WeakReference<DialogInterface>(dialog);
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                
+                case DialogInterface.BUTTON_POSITIVE:
+                case DialogInterface.BUTTON_NEGATIVE:
+                case DialogInterface.BUTTON_NEUTRAL:
+                    ((DialogInterface.OnClickListener) msg.obj).onClick(mDialog.get(), msg.what);
+                    break;
+                    
+                case MSG_DISMISS_DIALOG:
+                    ((DialogInterface) msg.obj).dismiss();
+            }
+        }
+    }
+
+    public AlertController(Context context, DialogInterface di, Window window) {
+        mContext = context;
+        mDialogInterface = di;
+        mWindow = window;
+        mHandler = new ButtonHandler(di);
+    }
+    
+    static boolean canTextInput(View v) {
+        if (v.onCheckIsTextEditor()) {
+            return true;
+        }
+        
+        if (!(v instanceof ViewGroup)) {
+            return false;
+        }
+        
+        ViewGroup vg = (ViewGroup)v;
+        int i = vg.getChildCount();
+        while (i > 0) {
+            i--;
+            v = vg.getChildAt(i);
+            if (canTextInput(v)) {
+                return true;
+            }
+        }
+        
+        return false;
+    }
+    
+    public void installContent() {
+        /* We use a custom title so never request a window title */
+        mWindow.requestFeature(Window.FEATURE_NO_TITLE);
+        
+        if (mView == null || !canTextInput(mView)) {
+            mWindow.setFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM,
+                    WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM);
+        }
+        mWindow.setContentView(com.android.internal.R.layout.alert_dialog);
+        setupView();
+    }
+    
+    public void setTitle(CharSequence title) {
+        mTitle = title;
+        if (mTitleView != null) {
+            mTitleView.setText(title);
+        }
+    }
+
+    /**
+     * @see AlertDialog.Builder#setCustomTitle(View)
+     */
+    public void setCustomTitle(View customTitleView) {
+        mCustomTitleView = customTitleView;
+    }
+    
+    public void setMessage(CharSequence message) {
+        mMessage = message;
+        if (mMessageView != null) {
+            mMessageView.setText(message);
+        }
+    }
+
+    /**
+     * Set the view to display in the dialog.
+     */
+    public void setView(View view) {
+        mView = view;
+        mViewSpacingSpecified = false;
+    }
+    
+    /**
+     * Set the view to display in the dialog along with the spacing around that view
+     */
+    public void setView(View view, int viewSpacingLeft, int viewSpacingTop, int viewSpacingRight,
+            int viewSpacingBottom) {
+        mView = view;
+        mViewSpacingSpecified = true;
+        mViewSpacingLeft = viewSpacingLeft;
+        mViewSpacingTop = viewSpacingTop;
+        mViewSpacingRight = viewSpacingRight;
+        mViewSpacingBottom = viewSpacingBottom;
+    }
+
+    /**
+     * Sets a click listener or a message to be sent when the button is clicked.
+     * You only need to pass one of {@code listener} or {@code msg}.
+     * 
+     * @param whichButton Which button, can be one of
+     *            {@link DialogInterface#BUTTON_POSITIVE},
+     *            {@link DialogInterface#BUTTON_NEGATIVE}, or
+     *            {@link DialogInterface#BUTTON_NEUTRAL}
+     * @param text The text to display in positive button.
+     * @param listener The {@link DialogInterface.OnClickListener} to use.
+     * @param msg The {@link Message} to be sent when clicked.
+     */
+    public void setButton(int whichButton, CharSequence text,
+            DialogInterface.OnClickListener listener, Message msg) {
+
+        if (msg == null && listener != null) {
+            msg = mHandler.obtainMessage(whichButton, listener);
+        }
+        
+        switch (whichButton) {
+
+            case DialogInterface.BUTTON_POSITIVE:
+                mButtonPositiveText = text;
+                mButtonPositiveMessage = msg;
+                break;
+                
+            case DialogInterface.BUTTON_NEGATIVE:
+                mButtonNegativeText = text;
+                mButtonNegativeMessage = msg;
+                break;
+                
+            case DialogInterface.BUTTON_NEUTRAL:
+                mButtonNeutralText = text;
+                mButtonNeutralMessage = msg;
+                break;
+                
+            default:
+                throw new IllegalArgumentException("Button does not exist");
+        }
+    }
+
+    /**
+     * Set resId to 0 if you don't want an icon.
+     * @param resId the resourceId of the drawable to use as the icon or 0
+     * if you don't want an icon.
+     */
+    public void setIcon(int resId) {
+        mIconId = resId;
+        if (mIconView != null) {
+            if (resId > 0) {
+                mIconView.setImageResource(mIconId);
+            } else if (resId == 0) {
+                mIconView.setVisibility(View.GONE);
+            }
+        }
+    }
+    
+    public void setIcon(Drawable icon) {
+        mIcon = icon;
+        if ((mIconView != null) && (mIcon != null)) {
+            mIconView.setImageDrawable(icon);
+        }
+    }
+
+    public void setInverseBackgroundForced(boolean forceInverseBackground) {
+        mForceInverseBackground = forceInverseBackground;
+    }
+    
+    public ListView getListView() {
+        return mListView;
+    }
+    
+    public Button getButton(int whichButton) {
+        switch (whichButton) {
+            case DialogInterface.BUTTON_POSITIVE:
+                return mButtonPositiveMessage != null ? mButtonPositive : null;
+            case DialogInterface.BUTTON_NEGATIVE:
+                return mButtonNegativeMessage != null ? mButtonNegative : null;
+            case DialogInterface.BUTTON_NEUTRAL:
+                return mButtonNeutralMessage != null ? mButtonNeutral : null;
+            default:
+                return null;
+        }
+    }
+    
+    public boolean onKeyDown(int keyCode, KeyEvent event) {
+        if (mScrollView != null && mScrollView.executeKeyEvent(event)) return true;
+        return false;
+    }
+
+    public boolean onKeyUp(int keyCode, KeyEvent event) {
+        if (mScrollView != null && mScrollView.executeKeyEvent(event)) return true;
+        return false;
+    }
+    
+    private void setupView() {
+        LinearLayout contentPanel = (LinearLayout) mWindow.findViewById(R.id.contentPanel);
+        setupContent(contentPanel);
+        boolean hasButtons = setupButtons();
+        
+        LinearLayout topPanel = (LinearLayout) mWindow.findViewById(R.id.topPanel);
+        TypedArray a = mContext.obtainStyledAttributes(
+                null, com.android.internal.R.styleable.AlertDialog, com.android.internal.R.attr.alertDialogStyle, 0);
+        boolean hasTitle = setupTitle(topPanel);
+            
+        View buttonPanel = mWindow.findViewById(R.id.buttonPanel);
+        if (!hasButtons) {
+            buttonPanel.setVisibility(View.GONE);
+        }
+
+        FrameLayout customPanel = null;
+        if (mView != null) {
+            customPanel = (FrameLayout) mWindow.findViewById(R.id.customPanel);
+            FrameLayout custom = (FrameLayout) mWindow.findViewById(R.id.custom);
+            custom.addView(mView, new LayoutParams(FILL_PARENT, WRAP_CONTENT));
+            if (mViewSpacingSpecified) {
+                custom.setPadding(mViewSpacingLeft, mViewSpacingTop, mViewSpacingRight,
+                        mViewSpacingBottom);
+            }
+        } else {
+            mWindow.findViewById(R.id.customPanel).setVisibility(View.GONE);
+        }
+        
+        /* Only display the divider if we have a title and a 
+         * custom view or a message.
+         */
+        if (hasTitle && ((mMessage != null) || (mView != null))) {
+            View divider = mWindow.findViewById(R.id.titleDivider);
+            divider.setVisibility(View.VISIBLE);
+        }
+        
+        setBackground(topPanel, contentPanel, customPanel, hasButtons, a, hasTitle, buttonPanel);
+        a.recycle();
+    }
+
+    private boolean setupTitle(LinearLayout topPanel) {
+        boolean hasTitle = true;
+        
+        if (mCustomTitleView != null) {
+            // Add the custom title view directly to the topPanel layout
+            LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(
+                    LinearLayout.LayoutParams.FILL_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT);
+            
+            topPanel.addView(mCustomTitleView, lp);
+            
+            // Hide the title template
+            View titleTemplate = mWindow.findViewById(R.id.title_template);
+            titleTemplate.setVisibility(View.GONE);
+        } else {
+            final boolean hasTextTitle = !TextUtils.isEmpty(mTitle);
+            
+            mIconView = (ImageView) mWindow.findViewById(R.id.icon);
+            if (hasTextTitle) {
+                
+                /* Display the title if a title is supplied, else hide it */
+                mTitleView = (TextView) mWindow.findViewById(R.id.alertTitle);
+
+                mTitleView.setText(mTitle);
+                mIconView.setImageResource(R.drawable.ic_dialog_menu_generic);
+                
+                /* Do this last so that if the user has supplied any
+                 * icons we use them instead of the default ones. If the
+                 * user has specified 0 then make it disappear.
+                 */
+                if (mIconId > 0) {
+                    mIconView.setImageResource(mIconId);
+                } else if (mIcon != null) {
+                    mIconView.setImageDrawable(mIcon);
+                } else if (mIconId == 0) {
+                    
+                    /* Apply the padding from the icon to ensure the
+                     * title is aligned correctly.
+                     */
+                    mTitleView.setPadding(mIconView.getPaddingLeft(),
+                            mIconView.getPaddingTop(),
+                            mIconView.getPaddingRight(),
+                            mIconView.getPaddingBottom());
+                    mIconView.setVisibility(View.GONE);
+                }
+            } else {
+                
+                // Hide the title template
+                View titleTemplate = mWindow.findViewById(R.id.title_template);
+                titleTemplate.setVisibility(View.GONE);
+                mIconView.setVisibility(View.GONE);
+                hasTitle = false;
+            }
+        }
+        return hasTitle;
+    }
+
+    private void setupContent(LinearLayout contentPanel) {
+        mScrollView = (ScrollView) mWindow.findViewById(R.id.scrollView);
+        mScrollView.setFocusable(false);
+        
+        // Special case for users that only want to display a String
+        mMessageView = (TextView) mWindow.findViewById(R.id.message);
+        if (mMessageView == null) {
+            return;
+        }
+        
+        if (mMessage != null) {
+            mMessageView.setText(mMessage);
+        } else {
+            mMessageView.setVisibility(View.GONE);
+            mScrollView.removeView(mMessageView);
+            
+            if (mListView != null) {
+                contentPanel.removeView(mWindow.findViewById(R.id.scrollView));
+                contentPanel.addView(mListView, new LinearLayout.LayoutParams(FILL_PARENT, WRAP_CONTENT));
+            } else {
+                contentPanel.setVisibility(View.GONE);
+            }
+        }
+    }
+
+    private boolean setupButtons() {
+        View defaultButton = null;
+        int BIT_BUTTON_POSITIVE = 1;
+        int BIT_BUTTON_NEGATIVE = 2;
+        int BIT_BUTTON_NEUTRAL = 4;
+        int whichButtons = 0;
+        mButtonPositive = (Button) mWindow.findViewById(R.id.button1);
+        mButtonPositive.setOnClickListener(mButtonHandler);
+
+        if (TextUtils.isEmpty(mButtonPositiveText)) {
+            mButtonPositive.setVisibility(View.GONE);
+        } else {
+            mButtonPositive.setText(mButtonPositiveText);
+            mButtonPositive.setVisibility(View.VISIBLE);
+            defaultButton = mButtonPositive;
+            whichButtons = whichButtons | BIT_BUTTON_POSITIVE;
+        }
+
+        mButtonNegative = (Button) mWindow.findViewById(R.id.button2);
+        mButtonNegative.setOnClickListener(mButtonHandler);
+
+        if (TextUtils.isEmpty(mButtonNegativeText)) {
+            mButtonNegative.setVisibility(View.GONE);
+        } else {
+            mButtonNegative.setText(mButtonNegativeText);
+            mButtonNegative.setVisibility(View.VISIBLE);
+
+            if (defaultButton == null) {
+                defaultButton = mButtonNegative;
+            }
+            whichButtons = whichButtons | BIT_BUTTON_NEGATIVE;
+        }
+
+        mButtonNeutral = (Button) mWindow.findViewById(R.id.button3);
+        mButtonNeutral.setOnClickListener(mButtonHandler);
+
+        if (TextUtils.isEmpty(mButtonNeutralText)) {
+            mButtonNeutral.setVisibility(View.GONE);
+        } else {
+            mButtonNeutral.setText(mButtonNeutralText);
+            mButtonNeutral.setVisibility(View.VISIBLE);
+
+            if (defaultButton == null) {
+                defaultButton = mButtonNeutral;
+            }
+            whichButtons = whichButtons | BIT_BUTTON_NEUTRAL;
+        }
+
+        /*
+         * If we only have 1 button it should be centered on the layout and
+         * expand to fill 50% of the available space.
+         */
+        if (whichButtons == BIT_BUTTON_POSITIVE) {
+            centerButton(mButtonPositive);
+        } else if (whichButtons == BIT_BUTTON_NEGATIVE) {
+            centerButton(mButtonNeutral);
+        } else if (whichButtons == BIT_BUTTON_NEUTRAL) {
+            centerButton(mButtonNeutral);
+        }
+        
+        return whichButtons != 0;
+    }
+
+    private void centerButton(Button button) {
+        LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) button.getLayoutParams();
+        params.gravity = Gravity.CENTER_HORIZONTAL;
+        params.weight = 0.5f;
+        button.setLayoutParams(params);
+        View leftSpacer = mWindow.findViewById(R.id.leftSpacer);
+        leftSpacer.setVisibility(View.VISIBLE);
+        View rightSpacer = mWindow.findViewById(R.id.rightSpacer);
+        rightSpacer.setVisibility(View.VISIBLE);
+    }
+
+    private void setBackground(LinearLayout topPanel, LinearLayout contentPanel,
+            View customPanel, boolean hasButtons, TypedArray a, boolean hasTitle, 
+            View buttonPanel) {
+        
+        /* Get all the different background required */
+        int fullDark = a.getResourceId(
+                R.styleable.AlertDialog_fullDark, R.drawable.popup_full_dark);
+        int topDark = a.getResourceId(
+                R.styleable.AlertDialog_topDark, R.drawable.popup_top_dark);
+        int centerDark = a.getResourceId(
+                R.styleable.AlertDialog_centerDark, R.drawable.popup_center_dark);
+        int bottomDark = a.getResourceId(
+                R.styleable.AlertDialog_bottomDark, R.drawable.popup_bottom_dark);
+        int fullBright = a.getResourceId(
+                R.styleable.AlertDialog_fullBright, R.drawable.popup_full_bright);
+        int topBright = a.getResourceId(
+                R.styleable.AlertDialog_topBright, R.drawable.popup_top_bright);
+        int centerBright = a.getResourceId(
+                R.styleable.AlertDialog_centerBright, R.drawable.popup_center_bright);
+        int bottomBright = a.getResourceId(
+                R.styleable.AlertDialog_bottomBright, R.drawable.popup_bottom_bright);
+        int bottomMedium = a.getResourceId(
+                R.styleable.AlertDialog_bottomMedium, R.drawable.popup_bottom_medium);
+        int centerMedium = a.getResourceId(
+                R.styleable.AlertDialog_centerMedium, R.drawable.popup_center_medium);
+        
+        /*
+         * We now set the background of all of the sections of the alert.
+         * First collect together each section that is being displayed along
+         * with whether it is on a light or dark background, then run through
+         * them setting their backgrounds.  This is complicated because we need
+         * to correctly use the full, top, middle, and bottom graphics depending
+         * on how many views they are and where they appear.
+         */
+        
+        View[] views = new View[4];
+        boolean[] light = new boolean[4];
+        View lastView = null;
+        boolean lastLight = false;
+        
+        int pos = 0;
+        if (hasTitle) {
+            views[pos] = topPanel;
+            light[pos] = false;
+            pos++;
+        }
+        
+        /* The contentPanel displays either a custom text message or
+         * a ListView. If it's text we should use the dark background
+         * for ListView we should use the light background. If neither
+         * are there the contentPanel will be hidden so set it as null.
+         */
+        views[pos] = (contentPanel.getVisibility() == View.GONE) 
+                ? null : contentPanel;
+        light[pos] = mListView == null ? false : true;
+        pos++;
+        if (customPanel != null) {
+            views[pos] = customPanel;
+            light[pos] = mForceInverseBackground;
+            pos++;
+        }
+        if (hasButtons) {
+            views[pos] = buttonPanel;
+            light[pos] = true;
+        }
+        
+        boolean setView = false;
+        for (pos=0; pos<views.length; pos++) {
+            View v = views[pos];
+            if (v == null) {
+                continue;
+            }
+            if (lastView != null) {
+                if (!setView) {
+                    lastView.setBackgroundResource(lastLight ? topBright : topDark);
+                } else {
+                    lastView.setBackgroundResource(lastLight ? centerBright : centerDark);
+                }
+                setView = true;
+            }
+            lastView = v;
+            lastLight = light[pos];
+        }
+        
+        if (lastView != null) {
+            if (setView) {
+                
+                /* ListViews will use the Bright background but buttons use
+                 * the Medium background.
+                 */ 
+                lastView.setBackgroundResource(
+                        lastLight ? (hasButtons ? bottomMedium : bottomBright) : bottomDark);
+            } else {
+                lastView.setBackgroundResource(lastLight ? fullBright : fullDark);
+            }
+        }
+        
+        /* TODO: uncomment section below. The logic for this should be if 
+         * it's a Contextual menu being displayed AND only a Cancel button 
+         * is shown then do this.
+         */
+//        if (hasButtons && (mListView != null)) {
+            
+            /* Yet another *special* case. If there is a ListView with buttons
+             * don't put the buttons on the bottom but instead put them in the
+             * footer of the ListView this will allow more items to be
+             * displayed.
+             */
+            
+            /*
+            contentPanel.setBackgroundResource(bottomBright);
+            buttonPanel.setBackgroundResource(centerMedium);
+            ViewGroup parent = (ViewGroup) mWindow.findViewById(R.id.parentPanel);
+            parent.removeView(buttonPanel);
+            AbsListView.LayoutParams params = new AbsListView.LayoutParams(
+                    AbsListView.LayoutParams.FILL_PARENT, 
+                    AbsListView.LayoutParams.FILL_PARENT);
+            buttonPanel.setLayoutParams(params);
+            mListView.addFooterView(buttonPanel);
+            */
+//        }
+        
+        if ((mListView != null) && (mAdapter != null)) {
+            mListView.setAdapter(mAdapter);
+            if (mCheckedItem > -1) {
+                mListView.setItemChecked(mCheckedItem, true);
+                mListView.setSelection(mCheckedItem);
+            }
+        }
+    }
+    
+    public static class AlertParams {
+        public final Context mContext;
+        public final LayoutInflater mInflater;
+        
+        public int mIconId = -1;
+        public Drawable mIcon;
+        public CharSequence mTitle;
+        public View mCustomTitleView;
+        public CharSequence mMessage;
+        public CharSequence mPositiveButtonText;
+        public DialogInterface.OnClickListener mPositiveButtonListener;
+        public CharSequence mNegativeButtonText;
+        public DialogInterface.OnClickListener mNegativeButtonListener;
+        public CharSequence mNeutralButtonText;
+        public DialogInterface.OnClickListener mNeutralButtonListener;
+        public boolean mCancelable;
+        public DialogInterface.OnCancelListener mOnCancelListener;
+        public DialogInterface.OnKeyListener mOnKeyListener;
+        public CharSequence[] mItems;
+        public ListAdapter mAdapter;
+        public DialogInterface.OnClickListener mOnClickListener;
+        public View mView;
+        public int mViewSpacingLeft;
+        public int mViewSpacingTop;
+        public int mViewSpacingRight;
+        public int mViewSpacingBottom;
+        public boolean mViewSpacingSpecified = false;
+        public boolean[] mCheckedItems;
+        public boolean mIsMultiChoice;
+        public boolean mIsSingleChoice;
+        public int mCheckedItem = -1;
+        public DialogInterface.OnMultiChoiceClickListener mOnCheckboxClickListener;
+        public Cursor mCursor;
+        public String mLabelColumn;
+        public String mIsCheckedColumn;
+        public boolean mForceInverseBackground;
+        public AdapterView.OnItemSelectedListener mOnItemSelectedListener;
+        public OnPrepareListViewListener mOnPrepareListViewListener;
+        
+        /**
+         * Interface definition for a callback to be invoked before the ListView
+         * will be bound to an adapter.
+         */
+        public interface OnPrepareListViewListener {
+            
+            /**
+             * Called before the ListView is bound to an adapter.
+             * @param listView The ListView that will be shown in the dialog.
+             */
+            void onPrepareListView(ListView listView);
+        }
+        
+        public AlertParams(Context context) {
+            mContext = context;
+            mCancelable = true;
+            mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+        }
+    
+        public void apply(AlertController dialog) {
+            if (mCustomTitleView != null) {
+                dialog.setCustomTitle(mCustomTitleView);
+            } else {
+                if (mTitle != null) {
+                    dialog.setTitle(mTitle);
+                }
+                if (mIcon != null) {
+                    dialog.setIcon(mIcon);
+                }
+                if (mIconId >= 0) {
+                    dialog.setIcon(mIconId);
+                }
+            }
+            if (mMessage != null) {
+                dialog.setMessage(mMessage);
+            }
+            if (mPositiveButtonText != null) {
+                dialog.setButton(DialogInterface.BUTTON_POSITIVE, mPositiveButtonText,
+                        mPositiveButtonListener, null);
+            }
+            if (mNegativeButtonText != null) {
+                dialog.setButton(DialogInterface.BUTTON_NEGATIVE, mNegativeButtonText,
+                        mNegativeButtonListener, null);
+            }
+            if (mNeutralButtonText != null) {
+                dialog.setButton(DialogInterface.BUTTON_NEUTRAL, mNeutralButtonText,
+                        mNeutralButtonListener, null);
+            }
+            if (mForceInverseBackground) {
+                dialog.setInverseBackgroundForced(true);
+            }
+            // For a list, the client can either supply an array of items or an
+            // adapter or a cursor
+            if ((mItems != null) || (mCursor != null) || (mAdapter != null)) {
+                createListView(dialog);
+            }
+            if (mView != null) {
+                if (mViewSpacingSpecified) {
+                    dialog.setView(mView, mViewSpacingLeft, mViewSpacingTop, mViewSpacingRight,
+                            mViewSpacingBottom);
+                } else {
+                    dialog.setView(mView);
+                }
+            }
+            
+            /*
+            dialog.setCancelable(mCancelable);
+            dialog.setOnCancelListener(mOnCancelListener);
+            if (mOnKeyListener != null) {
+                dialog.setOnKeyListener(mOnKeyListener);
+            }
+            */
+        }
+        
+        private void createListView(final AlertController dialog) {
+            final ListView listView = (ListView) mInflater.inflate(R.layout.select_dialog, null);
+            ListAdapter adapter;
+            
+            if (mIsMultiChoice) {
+                if (mCursor == null) {
+                    adapter = new ArrayAdapter<CharSequence>(
+                            mContext, R.layout.select_dialog_multichoice, R.id.text1, mItems) {
+                        @Override
+                        public View getView(int position, View convertView, ViewGroup parent) {
+                            View view = super.getView(position, convertView, parent);
+                            if (mCheckedItems != null) {
+                                boolean isItemChecked = mCheckedItems[position];
+                                if (isItemChecked) {
+                                    listView.setItemChecked(position, true);
+                                }
+                            }
+                            return view;
+                        }
+                    };
+                } else {
+                    adapter = new CursorAdapter(mContext, mCursor, false) {
+                        private final int mLabelIndex;
+                        private final int mIsCheckedIndex;
+
+                        {
+                            final Cursor cursor = getCursor();
+                            mLabelIndex = cursor.getColumnIndexOrThrow(mLabelColumn);
+                            mIsCheckedIndex = cursor.getColumnIndexOrThrow(mIsCheckedColumn);
+                        }
+
+                        @Override
+                        public void bindView(View view, Context context, Cursor cursor) {
+                            CheckedTextView text = (CheckedTextView) view.findViewById(R.id.text1);
+                            text.setText(cursor.getString(mLabelIndex));
+                            listView.setItemChecked(cursor.getPosition(),
+                                    cursor.getInt(mIsCheckedIndex) == 1);
+                        }
+    
+                        @Override
+                        public View newView(Context context, Cursor cursor, ViewGroup parent) {
+                            return mInflater.inflate(R.layout.select_dialog_multichoice,
+                                    parent, false);
+                        }
+                        
+                    };
+                }
+            } else {
+                int layout = mIsSingleChoice 
+                        ? R.layout.select_dialog_singlechoice : R.layout.select_dialog_item;
+                if (mCursor == null) {
+                    adapter = (mAdapter != null) ? mAdapter
+                            : new ArrayAdapter<CharSequence>(mContext, layout, R.id.text1, mItems);
+                } else {
+                    adapter = new SimpleCursorAdapter(mContext, layout, 
+                            mCursor, new String[]{mLabelColumn}, new int[]{R.id.text1});
+                }
+            }
+            
+            if (mOnPrepareListViewListener != null) {
+                mOnPrepareListViewListener.onPrepareListView(listView);
+            }
+            
+            /* Don't directly set the adapter on the ListView as we might
+             * want to add a footer to the ListView later.
+             */
+            dialog.mAdapter = adapter;
+            dialog.mCheckedItem = mCheckedItem;
+            
+            if (mOnClickListener != null) {
+                listView.setOnItemClickListener(new OnItemClickListener() {
+                    public void onItemClick(AdapterView parent, View v, int position, long id) {
+                        mOnClickListener.onClick(dialog.mDialogInterface, position);
+                        if (!mIsSingleChoice) {
+                            dialog.mDialogInterface.dismiss();
+                        }
+                    }
+                });
+            } else if (mOnCheckboxClickListener != null) {
+                listView.setOnItemClickListener(new OnItemClickListener() {
+                    public void onItemClick(AdapterView parent, View v, int position, long id) {
+                        if (mCheckedItems != null) {
+                            mCheckedItems[position] = listView.isItemChecked(position);
+                        }
+                        mOnCheckboxClickListener.onClick(
+                                dialog.mDialogInterface, position, listView.isItemChecked(position));
+                    }
+                });
+            }
+            
+            // Attach a given OnItemSelectedListener to the ListView
+            if (mOnItemSelectedListener != null) {
+                listView.setOnItemSelectedListener(mOnItemSelectedListener);
+            }
+            
+            if (mIsSingleChoice) {
+                listView.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
+            } else if (mIsMultiChoice) {
+                listView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE);
+            }
+            dialog.mListView = listView;
+        }
+    }
+
+}
diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java
new file mode 100644
index 0000000..1697df2
--- /dev/null
+++ b/core/java/com/android/internal/app/ChooserActivity.java
@@ -0,0 +1,33 @@
+/*
+ * 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.internal.app;
+
+import android.content.Intent;
+import android.os.Bundle;
+
+public class ChooserActivity extends ResolverActivity {
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        Intent intent = getIntent();
+        Intent target = (Intent)intent.getParcelableExtra(Intent.EXTRA_INTENT);
+        CharSequence title = intent.getCharSequenceExtra(Intent.EXTRA_TITLE);
+        if (title == null) {
+            title = getResources().getText(com.android.internal.R.string.chooseActivity);
+        }
+        super.onCreate(savedInstanceState, target, title, false);
+    }
+}
diff --git a/core/java/com/android/internal/app/ExternalMediaFormatActivity.java b/core/java/com/android/internal/app/ExternalMediaFormatActivity.java
new file mode 100644
index 0000000..000f6c4
--- /dev/null
+++ b/core/java/com/android/internal/app/ExternalMediaFormatActivity.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2007 Google Inc.
+ *
+ * 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.internal.app;
+
+import android.app.AlertDialog;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IMountService;
+import android.os.Message;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.Environment;
+import android.widget.Toast;
+import android.util.Log;
+
+/**
+ * This activity is shown to the user to confirm formatting of external media.
+ * It uses the alert dialog style. It will be launched from a notification, or from settings
+ */
+public class ExternalMediaFormatActivity extends AlertActivity implements DialogInterface.OnClickListener {
+
+    private static final int POSITIVE_BUTTON = AlertDialog.BUTTON1;
+
+    /** Used to detect when the media state changes, in case we need to call finish() */
+    private BroadcastReceiver mStorageReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            String action = intent.getAction();
+            Log.d("ExternalMediaFormatActivity", "got action " + action);
+
+            if (action == Intent.ACTION_MEDIA_REMOVED ||
+                action == Intent.ACTION_MEDIA_CHECKING ||
+                action == Intent.ACTION_MEDIA_MOUNTED ||
+                action == Intent.ACTION_MEDIA_SHARED) {
+                finish();
+            }
+        }
+    };
+    
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        Log.d("ExternalMediaFormatActivity", "onCreate!");
+        // Set up the "dialog"
+        final AlertController.AlertParams p = mAlertParams;
+        p.mIconId = com.android.internal.R.drawable.stat_sys_warning;
+        p.mTitle = getString(com.android.internal.R.string.extmedia_format_title);
+        p.mMessage = getString(com.android.internal.R.string.extmedia_format_message);
+        p.mPositiveButtonText = getString(com.android.internal.R.string.extmedia_format_button_format);
+        p.mPositiveButtonListener = this;
+        p.mNegativeButtonText = getString(com.android.internal.R.string.cancel);
+        p.mNegativeButtonListener = this;
+        setupAlert();
+    }
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+
+        IntentFilter filter = new IntentFilter();
+        filter.addAction(Intent.ACTION_MEDIA_REMOVED);
+        filter.addAction(Intent.ACTION_MEDIA_CHECKING);
+        filter.addAction(Intent.ACTION_MEDIA_MOUNTED);
+        filter.addAction(Intent.ACTION_MEDIA_SHARED);
+        registerReceiver(mStorageReceiver, filter);
+    }
+
+    @Override
+    protected void onPause() {
+        super.onPause();
+        
+        unregisterReceiver(mStorageReceiver);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void onClick(DialogInterface dialog, int which) {
+
+        if (which == POSITIVE_BUTTON) {
+            IMountService mountService = IMountService.Stub.asInterface(ServiceManager
+                .getService("mount"));
+            if (mountService != null) {
+                try {
+                    mountService.formatMedia(Environment.getExternalStorageDirectory().toString());
+                } catch (RemoteException e) {
+                }
+            }
+        }
+
+        // No matter what, finish the activity
+        finish();
+    }
+}
diff --git a/core/java/com/android/internal/app/IBatteryStats.aidl b/core/java/com/android/internal/app/IBatteryStats.aidl
new file mode 100644
index 0000000..edda1d9
--- /dev/null
+++ b/core/java/com/android/internal/app/IBatteryStats.aidl
@@ -0,0 +1,36 @@
+/*
+ * 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.internal.app;
+
+import com.android.internal.os.BatteryStatsImpl;
+
+interface IBatteryStats {
+    byte[] getStatistics();
+    void noteStartWakelock(int uid, String name, int type);
+    void noteStopWakelock(int uid, String name, int type);
+    void noteStartSensor(int uid, int sensor);
+    void noteStopSensor(int uid, int sensor);
+    void noteStartGps(int uid);
+    void noteStopGps(int uid);
+    void noteScreenOn();
+    void noteScreenOff();
+    void notePhoneOn();
+    void notePhoneOff();
+    void setOnBattery(boolean onBattery);
+    long getAwakeTimeBattery();
+    long getAwakeTimePlugged();
+}
diff --git a/core/java/com/android/internal/app/IUsageStats.aidl b/core/java/com/android/internal/app/IUsageStats.aidl
new file mode 100755
index 0000000..6b053d5
--- /dev/null
+++ b/core/java/com/android/internal/app/IUsageStats.aidl
@@ -0,0 +1,27 @@
+/*
+ * 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.internal.app;
+
+import android.content.ComponentName;
+import com.android.internal.os.PkgUsageStats;
+
+interface IUsageStats {
+    void noteResumeComponent(in ComponentName componentName);
+    void notePauseComponent(in ComponentName componentName);
+    PkgUsageStats getPkgUsageStats(in ComponentName componentName);
+    PkgUsageStats[] getAllPkgUsageStats();
+}
diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java
new file mode 100644
index 0000000..e907a04
--- /dev/null
+++ b/core/java/com/android/internal/app/ResolverActivity.java
@@ -0,0 +1,382 @@
+/*
+ * 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.internal.app;
+
+import com.android.internal.R;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.ActivityInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.PatternMatcher;
+import android.util.Config;
+import android.util.Log;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.LayoutInflater;
+import android.widget.BaseAdapter;
+import android.widget.CheckBox;
+import android.widget.CompoundButton;
+import android.widget.ImageView;
+import android.widget.TextView;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * This activity is displayed when the system attempts to start an Intent for
+ * which there is more than one matching activity, allowing the user to decide
+ * which to go to.  It is not normally used directly by application developers.
+ */
+public class ResolverActivity extends AlertActivity implements
+        DialogInterface.OnClickListener, CheckBox.OnCheckedChangeListener {
+    private ResolveListAdapter mAdapter;
+    private CheckBox mAlwaysCheck;
+    private TextView mClearDefaultHint;
+    private PackageManager mPm;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        onCreate(savedInstanceState, new Intent(getIntent()),
+                getResources().getText(com.android.internal.R.string.whichApplication),
+                true);
+    }
+
+    protected void onCreate(Bundle savedInstanceState, Intent intent,
+            CharSequence title, boolean alwaysUseOption) {
+        super.onCreate(savedInstanceState);
+        mPm = getPackageManager();
+        intent.setComponent(null);
+
+        AlertController.AlertParams ap = mAlertParams;
+
+        ap.mTitle = title;
+        ap.mOnClickListener = this;
+
+        if (alwaysUseOption) {
+            LayoutInflater inflater = (LayoutInflater) getSystemService(
+                    Context.LAYOUT_INFLATER_SERVICE);
+            ap.mView = inflater.inflate(R.layout.always_use_checkbox, null);
+            mAlwaysCheck = (CheckBox)ap.mView.findViewById(com.android.internal.R.id.alwaysUse);
+            mAlwaysCheck.setText(R.string.alwaysUse);
+            mAlwaysCheck.setOnCheckedChangeListener(this);
+            mClearDefaultHint = (TextView)ap.mView.findViewById(
+                                                        com.android.internal.R.id.clearDefaultHint);
+            mClearDefaultHint.setVisibility(View.GONE);
+        }
+        mAdapter = new ResolveListAdapter(this, intent);
+        if (mAdapter.getCount() > 1) {
+            ap.mAdapter = mAdapter;
+        } else if (mAdapter.getCount() == 1) {
+            startActivity(mAdapter.intentForPosition(0));
+            finish();
+            return;
+        } else {
+            ap.mMessage = getResources().getText(com.android.internal.R.string.noApplications);
+        }
+
+        setupAlert();
+    }
+
+    public void onClick(DialogInterface dialog, int which) {
+        ResolveInfo ri = mAdapter.resolveInfoForPosition(which);
+        Intent intent = mAdapter.intentForPosition(which);
+
+        if ((mAlwaysCheck != null) && mAlwaysCheck.isChecked()) {
+            // Build a reasonable intent filter, based on what matched.
+            IntentFilter filter = new IntentFilter();
+
+            if (intent.getAction() != null) {
+                filter.addAction(intent.getAction());
+            }
+            Set<String> categories = intent.getCategories();
+            if (categories != null) {
+                for (String cat : categories) {
+                    filter.addCategory(cat);
+                }
+            }
+            filter.addCategory(Intent.CATEGORY_DEFAULT);
+
+            int cat = ri.match&IntentFilter.MATCH_CATEGORY_MASK;
+            Uri data = intent.getData();
+            if (cat == IntentFilter.MATCH_CATEGORY_TYPE) {
+                String mimeType = intent.resolveType(this);
+                if (mimeType != null) {
+                    try {
+                        filter.addDataType(mimeType);
+                    } catch (IntentFilter.MalformedMimeTypeException e) {
+                        Log.w("ResolverActivity", e);
+                        filter = null;
+                    }
+                }
+            } else if (data != null && data.getScheme() != null) {
+                filter.addDataScheme(data.getScheme());
+
+                // Look through the resolved filter to determine which part
+                // of it matched the original Intent.
+                Iterator<IntentFilter.AuthorityEntry> aIt = ri.filter.authoritiesIterator();
+                if (aIt != null) {
+                    while (aIt.hasNext()) {
+                        IntentFilter.AuthorityEntry a = aIt.next();
+                        if (a.match(data) >= 0) {
+                            int port = a.getPort();
+                            filter.addDataAuthority(a.getHost(),
+                                    port >= 0 ? Integer.toString(port) : null);
+                            break;
+                        }
+                    }
+                }
+                Iterator<PatternMatcher> pIt = ri.filter.pathsIterator();
+                if (pIt != null) {
+                    String path = data.getPath();
+                    while (path != null && pIt.hasNext()) {
+                        PatternMatcher p = pIt.next();
+                        if (p.match(path)) {
+                            filter.addDataPath(p.getPath(), p.getType());
+                            break;
+                        }
+                    }
+                }
+            }
+
+            if (filter != null) {
+                final int N = mAdapter.mList.size();
+                ComponentName[] set = new ComponentName[N];
+                int bestMatch = 0;
+                for (int i=0; i<N; i++) {
+                    ResolveInfo r = mAdapter.mList.get(i).ri;
+                    set[i] = new ComponentName(r.activityInfo.packageName,
+                            r.activityInfo.name);
+                    if (r.match > bestMatch) bestMatch = r.match;
+                }
+                getPackageManager().addPreferredActivity(filter, bestMatch, set,
+                        intent.getComponent());
+            }
+        }
+
+        if (intent != null) {
+            startActivity(intent);
+        }
+        finish();
+    }
+
+    private final class DisplayResolveInfo {
+        ResolveInfo ri;
+        CharSequence displayLabel;
+        CharSequence extendedInfo;
+
+        DisplayResolveInfo(ResolveInfo pri, CharSequence pLabel, CharSequence pInfo) {
+            ri = pri;
+            displayLabel = pLabel;
+            extendedInfo = pInfo;
+        }
+    }
+
+    private final class ResolveListAdapter extends BaseAdapter {
+        private final Intent mIntent;
+        private final LayoutInflater mInflater;
+
+        private List<DisplayResolveInfo> mList;
+
+        public ResolveListAdapter(Context context, Intent intent) {
+            mIntent = new Intent(intent);
+            mIntent.setComponent(null);
+            mInflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+
+            List<ResolveInfo> rList = mPm.queryIntentActivities(
+                    intent, PackageManager.MATCH_DEFAULT_ONLY
+                    | (mAlwaysCheck != null ? PackageManager.GET_RESOLVED_FILTER : 0));
+            int N;
+            if ((rList != null) && ((N = rList.size()) > 0)) {
+                // Only display the first matches that are either of equal
+                // priority or have asked to be default options.
+                ResolveInfo r0 = rList.get(0);
+                for (int i=1; i<N; i++) {
+                    ResolveInfo ri = rList.get(i);
+                    if (Config.LOGV) Log.v(
+                        "ResolveListActivity",
+                        r0.activityInfo.name + "=" +
+                        r0.priority + "/" + r0.isDefault + " vs " +
+                        ri.activityInfo.name + "=" +
+                        ri.priority + "/" + ri.isDefault);
+                   if (r0.priority != ri.priority ||
+                        r0.isDefault != ri.isDefault) {
+                        while (i < N) {
+                            rList.remove(i);
+                            N--;
+                        }
+                    }
+                }
+                if (N > 1) {
+                    ResolveInfo.DisplayNameComparator rComparator =
+                            new ResolveInfo.DisplayNameComparator(mPm);
+                    Collections.sort(rList, rComparator);
+                }
+                // Check for applications with same name and use application name or
+                // package name if necessary
+                mList = new ArrayList<DisplayResolveInfo>();
+                r0 = rList.get(0);
+                int start = 0;
+                CharSequence r0Label =  r0.loadLabel(mPm);
+                for (int i = 1; i < N; i++) {
+                    if (r0Label == null) {
+                        r0Label = r0.activityInfo.packageName;
+                    }
+                    ResolveInfo ri = rList.get(i);
+                    CharSequence riLabel = ri.loadLabel(mPm);
+                    if (riLabel == null) {
+                        riLabel = ri.activityInfo.packageName;
+                    }
+                    if (riLabel.equals(r0Label)) {
+                        continue;
+                    }
+                    processGroup(rList, start, (i-1), r0, r0Label);
+                    r0 = ri;
+                    r0Label = riLabel;
+                    start = i;
+                }
+                // Process last group
+                processGroup(rList, start, (N-1), r0, r0Label);
+            }
+        }
+
+        private void processGroup(List<ResolveInfo> rList, int start, int end, ResolveInfo ro,
+                CharSequence roLabel) {
+            // Process labels from start to i
+            int num = end - start+1;
+            if (num == 1) {
+                // No duplicate labels. Use label for entry at start
+                mList.add(new DisplayResolveInfo(ro, roLabel, null));
+            } else {
+                boolean usePkg = false;
+                CharSequence startApp = ro.activityInfo.applicationInfo.loadLabel(mPm);
+                if (startApp == null) {
+                    usePkg = true;
+                }
+                if (!usePkg) {
+                    // Use HashSet to track duplicates
+                    HashSet<CharSequence> duplicates =
+                        new HashSet<CharSequence>();
+                    duplicates.add(startApp);
+                    for (int j = start+1; j <= end ; j++) {
+                        ResolveInfo jRi = rList.get(j);
+                        CharSequence jApp = jRi.activityInfo.applicationInfo.loadLabel(mPm);
+                        if ( (jApp == null) || (duplicates.contains(jApp))) {
+                            usePkg = true;
+                            break;
+                        } else {
+                            duplicates.add(jApp);
+                        }
+                    }
+                    // Clear HashSet for later use
+                    duplicates.clear();
+                }
+                for (int k = start; k <= end; k++) {
+                    ResolveInfo add = rList.get(k);
+                    if (usePkg) {
+                        // Use application name for all entries from start to end-1
+                        mList.add(new DisplayResolveInfo(add, roLabel,
+                                add.activityInfo.packageName));
+                    } else {
+                        // Use package name for all entries from start to end-1
+                        mList.add(new DisplayResolveInfo(add, roLabel,
+                                add.activityInfo.applicationInfo.loadLabel(mPm)));
+                    }
+                }
+            }
+        }
+
+        public ResolveInfo resolveInfoForPosition(int position) {
+            if (mList == null) {
+                return null;
+            }
+
+            return mList.get(position).ri;
+        }
+
+        public Intent intentForPosition(int position) {
+            if (mList == null) {
+                return null;
+            }
+
+            Intent intent = new Intent(mIntent);
+            intent.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT
+                    |Intent.FLAG_ACTIVITY_PREVIOUS_IS_TOP);
+            ActivityInfo ai = mList.get(position).ri.activityInfo;
+            intent.setComponent(new ComponentName(
+                    ai.applicationInfo.packageName, ai.name));
+            return intent;
+        }
+
+        public int getCount() {
+            return mList != null ? mList.size() : 0;
+        }
+
+        public Object getItem(int position) {
+            return position;
+        }
+
+        public long getItemId(int position) {
+            return position;
+        }
+
+        public View getView(int position, View convertView, ViewGroup parent) {
+            View view;
+            if (convertView == null) {
+                view = mInflater.inflate(
+                        com.android.internal.R.layout.resolve_list_item, parent, false);
+            } else {
+                view = convertView;
+            }
+            bindView(view, mList.get(position));
+            return view;
+        }
+
+        private final void bindView(View view, DisplayResolveInfo info) {
+            TextView text = (TextView)view.findViewById(com.android.internal.R.id.text1);
+            TextView text2 = (TextView)view.findViewById(com.android.internal.R.id.text2);
+            ImageView icon = (ImageView)view.findViewById(R.id.icon);
+            text.setText(info.displayLabel);
+            if (info.extendedInfo != null) {
+                text2.setVisibility(View.VISIBLE);
+                text2.setText(info.extendedInfo);
+            } else {
+                text2.setVisibility(View.GONE);
+            }
+            icon.setImageDrawable(info.ri.loadIcon(mPm));
+        }
+    }
+
+    public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
+        if (mClearDefaultHint == null) return;
+
+        if(isChecked) {
+            mClearDefaultHint.setVisibility(View.VISIBLE);
+        } else {
+            mClearDefaultHint.setVisibility(View.GONE);
+        }
+    }
+}
+
diff --git a/core/java/com/android/internal/app/RingtonePickerActivity.java b/core/java/com/android/internal/app/RingtonePickerActivity.java
new file mode 100644
index 0000000..1f8e6f0
--- /dev/null
+++ b/core/java/com/android/internal/app/RingtonePickerActivity.java
@@ -0,0 +1,346 @@
+/*
+ * Copyright (C) 2007 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.internal.app;
+
+import com.android.internal.app.AlertActivity;
+import com.android.internal.app.AlertController;
+
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.database.Cursor;
+import android.media.Ringtone;
+import android.media.RingtoneManager;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Handler;
+import android.provider.MediaStore;
+import android.provider.Settings;
+import android.view.View;
+import android.widget.AdapterView;
+import android.widget.ListView;
+import android.widget.TextView;
+
+/**
+ * The {@link RingtonePickerActivity} allows the user to choose one from all of the
+ * available ringtones. The chosen ringtone's URI will be persisted as a string.
+ *
+ * @see RingtoneManager#ACTION_RINGTONE_PICKER
+ */
+public final class RingtonePickerActivity extends AlertActivity implements
+        AdapterView.OnItemSelectedListener, Runnable, DialogInterface.OnClickListener,
+        AlertController.AlertParams.OnPrepareListViewListener {
+
+    private static final String TAG = "RingtonePickerActivity";
+
+    private static final int DELAY_MS_SELECTION_PLAYED = 300;
+    
+    private RingtoneManager mRingtoneManager;
+    
+    private Cursor mCursor;
+    private Handler mHandler;
+
+    /** The position in the list of the 'Silent' item. */
+    private int mSilentPos = -1;
+    
+    /** The position in the list of the 'Default' item. */
+    private int mDefaultRingtonePos = -1;
+
+    /** The position in the list of the last clicked item. */
+    private int mClickedPos = -1;
+    
+    /** The position in the list of the ringtone to sample. */
+    private int mSampleRingtonePos = -1;
+
+    /** Whether this list has the 'Silent' item. */
+    private boolean mHasSilentItem;
+    
+    /** The Uri to place a checkmark next to. */
+    private Uri mExistingUri;
+    
+    /** The number of static items in the list. */
+    private int mStaticItemCount;
+    
+    /** Whether this list has the 'Default' item. */
+    private boolean mHasDefaultItem;
+    
+    /** The Uri to play when the 'Default' item is clicked. */
+    private Uri mUriForDefaultItem;
+    
+    /**
+     * A Ringtone for the default ringtone. In most cases, the RingtoneManager
+     * will stop the previous ringtone. However, the RingtoneManager doesn't
+     * manage the default ringtone for us, so we should stop this one manually.
+     */
+    private Ringtone mDefaultRingtone;
+    
+    private DialogInterface.OnClickListener mRingtoneClickListener =
+            new DialogInterface.OnClickListener() {
+
+        /*
+         * On item clicked
+         */
+        public void onClick(DialogInterface dialog, int which) {
+            // Save the position of most recently clicked item
+            mClickedPos = which;
+            
+            // Play clip
+            playRingtone(which, 0);
+        }
+        
+    };
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        mHandler = new Handler();
+
+        Intent intent = getIntent();
+
+        /*
+         * Get whether to show the 'Default' item, and the URI to play when the
+         * default is clicked
+         */
+        mHasDefaultItem = intent.getBooleanExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_DEFAULT, true);
+        mUriForDefaultItem = intent.getParcelableExtra(RingtoneManager.EXTRA_RINGTONE_DEFAULT_URI);
+        if (mUriForDefaultItem == null) {
+            mUriForDefaultItem = Settings.System.DEFAULT_RINGTONE_URI;
+        }
+        
+        // Get whether to show the 'Silent' item
+        mHasSilentItem = intent.getBooleanExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_SILENT, true);
+        
+        // Give the Activity so it can do managed queries
+        mRingtoneManager = new RingtoneManager(this);
+
+        // Get whether to include DRM ringtones
+        boolean includeDrm = intent.getBooleanExtra(RingtoneManager.EXTRA_RINGTONE_INCLUDE_DRM,
+                true);
+        mRingtoneManager.setIncludeDrm(includeDrm);
+        
+        // Get the types of ringtones to show
+        int types = intent.getIntExtra(RingtoneManager.EXTRA_RINGTONE_TYPE, -1);
+        if (types != -1) {
+            mRingtoneManager.setType(types);
+        }
+        
+        mCursor = mRingtoneManager.getCursor();
+        
+        // The volume keys will control the stream that we are choosing a ringtone for
+        setVolumeControlStream(mRingtoneManager.inferStreamType());
+
+        // Get the URI whose list item should have a checkmark
+        mExistingUri = intent
+                .getParcelableExtra(RingtoneManager.EXTRA_RINGTONE_EXISTING_URI);
+
+        final AlertController.AlertParams p = mAlertParams;
+        p.mCursor = mCursor;
+        p.mOnClickListener = mRingtoneClickListener;
+        p.mLabelColumn = MediaStore.Audio.Media.TITLE;
+        p.mIsSingleChoice = true;
+        p.mOnItemSelectedListener = this;
+        p.mPositiveButtonText = getString(com.android.internal.R.string.ok);
+        p.mPositiveButtonListener = this;
+        p.mNegativeButtonText = getString(com.android.internal.R.string.cancel);
+        p.mPositiveButtonListener = this;
+        p.mOnPrepareListViewListener = this;
+
+        p.mTitle = intent.getCharSequenceExtra(RingtoneManager.EXTRA_RINGTONE_TITLE);
+        if (p.mTitle == null) {
+            p.mTitle = getString(com.android.internal.R.string.ringtone_picker_title);
+        }
+        
+        setupAlert();
+    }
+
+    public void onPrepareListView(ListView listView) {
+        
+        if (mHasDefaultItem) {
+            mDefaultRingtonePos = addDefaultRingtoneItem(listView);
+            
+            if (RingtoneManager.isDefault(mExistingUri)) {
+                mClickedPos = mDefaultRingtonePos;
+            }
+        }
+        
+        if (mHasSilentItem) {
+            mSilentPos = addSilentItem(listView);
+            
+            // The 'Silent' item should use a null Uri
+            if (mExistingUri == null) {
+                mClickedPos = mSilentPos;
+            }
+        }
+
+        if (mClickedPos == -1) {
+            mClickedPos = getListPosition(mRingtoneManager.getRingtonePosition(mExistingUri));
+        }
+        
+        // Put a checkmark next to an item.
+        mAlertParams.mCheckedItem = mClickedPos;
+    }
+
+    /**
+     * Adds a static item to the top of the list. A static item is one that is not from the
+     * RingtoneManager.
+     * 
+     * @param listView The ListView to add to.
+     * @param textResId The resource ID of the text for the item.
+     * @return The position of the inserted item.
+     */
+    private int addStaticItem(ListView listView, int textResId) {
+        TextView textView = (TextView) getLayoutInflater().inflate(
+                com.android.internal.R.layout.select_dialog_singlechoice, listView, false);
+        textView.setText(textResId);
+        listView.addHeaderView(textView);
+        mStaticItemCount++;
+        return listView.getHeaderViewsCount() - 1;
+    }
+    
+    private int addDefaultRingtoneItem(ListView listView) {
+        return addStaticItem(listView, com.android.internal.R.string.ringtone_default);
+    }
+    
+    private int addSilentItem(ListView listView) {
+        return addStaticItem(listView, com.android.internal.R.string.ringtone_silent);
+    }
+    
+    /*
+     * On click of Ok/Cancel buttons
+     */
+    public void onClick(DialogInterface dialog, int which) {
+        boolean positiveResult = which == BUTTON1;
+        
+        // Stop playing the previous ringtone
+        mRingtoneManager.stopPreviousRingtone();
+        
+        if (positiveResult) {
+            Intent resultIntent = new Intent();
+            Uri uri = null;
+            
+            if (mClickedPos == mDefaultRingtonePos) {
+                // Set it to the default Uri that they originally gave us
+                uri = mUriForDefaultItem;
+            } else if (mClickedPos == mSilentPos) {
+                // A null Uri is for the 'Silent' item
+                uri = null;
+            } else {
+                uri = mRingtoneManager.getRingtoneUri(getRingtoneManagerPosition(mClickedPos));
+            }
+
+            resultIntent.putExtra(RingtoneManager.EXTRA_RINGTONE_PICKED_URI, uri);
+            setResult(RESULT_OK, resultIntent);
+        } else {
+            setResult(RESULT_CANCELED);
+        }
+        
+        mCursor.deactivate();
+        
+        finish();
+    }
+    
+    /*
+     * On item selected via keys
+     */
+    public void onItemSelected(AdapterView parent, View view, int position, long id) {
+        playRingtone(position, DELAY_MS_SELECTION_PLAYED);
+    }
+
+    public void onNothingSelected(AdapterView parent) {
+    }
+
+    private void playRingtone(int position, int delayMs) {
+        mHandler.removeCallbacks(this);
+        mSampleRingtonePos = position;
+        mHandler.postDelayed(this, delayMs);
+    }
+    
+    public void run() {
+        
+        if (mSampleRingtonePos == mSilentPos) {
+            return;
+        }
+        
+        /*
+         * Stop the default ringtone, if it's playing (other ringtones will be
+         * stopped by the RingtoneManager when we get another Ringtone from it.
+         */
+        if (mDefaultRingtone != null && mDefaultRingtone.isPlaying()) {
+            mDefaultRingtone.stop();
+            mDefaultRingtone = null;
+        }
+        
+        Ringtone ringtone;
+        if (mSampleRingtonePos == mDefaultRingtonePos) {
+            if (mDefaultRingtone == null) {
+                mDefaultRingtone = RingtoneManager.getRingtone(this, mUriForDefaultItem);
+            }
+            ringtone = mDefaultRingtone;
+            
+            /*
+             * Normally the non-static RingtoneManager.getRingtone stops the
+             * previous ringtone, but we're getting the default ringtone outside
+             * of the RingtoneManager instance, so let's stop the previous
+             * ringtone manually.
+             */
+            mRingtoneManager.stopPreviousRingtone();
+            
+        } else {
+            ringtone = mRingtoneManager.getRingtone(getRingtoneManagerPosition(mSampleRingtonePos));
+        }
+        
+        if (ringtone != null) {
+            ringtone.play();
+        }
+    }
+
+    @Override
+    protected void onStop() {
+        super.onStop();
+        stopAnyPlayingRingtone();
+    }
+
+    @Override
+    protected void onPause() {
+        super.onPause();
+        stopAnyPlayingRingtone();
+    }
+
+    private void stopAnyPlayingRingtone() {
+
+        if (mDefaultRingtone != null && mDefaultRingtone.isPlaying()) {
+            mDefaultRingtone.stop();
+        }
+        
+        if (mRingtoneManager != null) {
+            mRingtoneManager.stopPreviousRingtone();
+        }
+    }
+    
+    private int getRingtoneManagerPosition(int listPos) {
+        return listPos - mStaticItemCount;
+    }
+    
+    private int getListPosition(int ringtoneManagerPos) {
+        
+        // If the manager position is -1 (for not found), return that
+        if (ringtoneManagerPos < 0) return ringtoneManagerPos;
+        
+        return ringtoneManagerPos + mStaticItemCount;
+    }
+    
+}
diff --git a/core/java/com/android/internal/app/UsbStorageActivity.java b/core/java/com/android/internal/app/UsbStorageActivity.java
new file mode 100644
index 0000000..b8a2136
--- /dev/null
+++ b/core/java/com/android/internal/app/UsbStorageActivity.java
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2007 Google Inc.
+ *
+ * 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.internal.app;
+
+import android.app.AlertDialog;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IMountService;
+import android.os.Message;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.widget.Toast;
+
+/**
+ * This activity is shown to the user for him/her to enable USB mass storage
+ * on-demand (that is, when the USB cable is connected). It uses the alert
+ * dialog style. It will be launched from a notification.
+ */
+public class UsbStorageActivity extends AlertActivity implements DialogInterface.OnClickListener {
+
+    private static final int POSITIVE_BUTTON = AlertDialog.BUTTON1;
+
+    /** Used to detect when the USB cable is unplugged, so we can call finish() */
+    private BroadcastReceiver mBatteryReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            if (intent.getAction() == Intent.ACTION_BATTERY_CHANGED) {
+                handleBatteryChanged(intent);
+            }
+        }
+    };
+    
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        // Set up the "dialog"
+        final AlertController.AlertParams p = mAlertParams;
+        p.mIconId = com.android.internal.R.drawable.ic_dialog_usb;
+        p.mTitle = getString(com.android.internal.R.string.usb_storage_title);
+        p.mMessage = getString(com.android.internal.R.string.usb_storage_message);
+        p.mPositiveButtonText = getString(com.android.internal.R.string.usb_storage_button_mount);
+        p.mPositiveButtonListener = this;
+        p.mNegativeButtonText = getString(com.android.internal.R.string.usb_storage_button_unmount);
+        p.mNegativeButtonListener = this;
+        setupAlert();
+    }
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+
+        registerReceiver(mBatteryReceiver, new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
+    }
+
+    @Override
+    protected void onPause() {
+        super.onPause();
+        
+        unregisterReceiver(mBatteryReceiver);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void onClick(DialogInterface dialog, int which) {
+
+        if (which == POSITIVE_BUTTON) {
+            mountAsUsbStorage();
+        }
+
+        // No matter what, finish the activity
+        finish();
+    }
+
+    private void mountAsUsbStorage() {
+        IMountService mountService = IMountService.Stub.asInterface(ServiceManager
+                .getService("mount"));
+        if (mountService == null) {
+            showSharingError();
+            return;
+        }
+
+        try {
+            mountService.setMassStorageEnabled(true);
+        } catch (RemoteException e) {
+            showSharingError();
+            return;
+        }
+    }
+
+    private void handleBatteryChanged(Intent intent) {
+        int pluggedType = intent.getIntExtra("plugged", 0);
+        if (pluggedType == 0) {
+            // It was disconnected from the plug, so finish
+            finish();
+        }
+    }
+    
+    private void showSharingError() {
+        Toast.makeText(this, com.android.internal.R.string.usb_storage_error_message,
+                Toast.LENGTH_LONG).show();
+    }
+
+}
diff --git a/core/java/com/android/internal/app/UsbStorageStopActivity.java b/core/java/com/android/internal/app/UsbStorageStopActivity.java
new file mode 100644
index 0000000..557a523
--- /dev/null
+++ b/core/java/com/android/internal/app/UsbStorageStopActivity.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2007 Google Inc.
+ *
+ * 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.internal.app;
+
+import android.app.AlertDialog;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IMountService;
+import android.os.Message;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.widget.Toast;
+
+/**
+ * This activity is shown to the user for him/her to disable USB mass storage.
+ * It uses the alert dialog style. It will be launched from a notification.
+ */
+public class UsbStorageStopActivity extends AlertActivity implements DialogInterface.OnClickListener {
+
+    private static final int POSITIVE_BUTTON = AlertDialog.BUTTON1;
+
+    /** Used to detect when the USB cable is unplugged, so we can call finish() */
+    private BroadcastReceiver mBatteryReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            if (intent.getAction() == Intent.ACTION_BATTERY_CHANGED) {
+                handleBatteryChanged(intent);
+            }
+        }
+    };
+    
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        // Set up the "dialog"
+        final AlertController.AlertParams p = mAlertParams;
+        p.mIconId = com.android.internal.R.drawable.ic_dialog_alert;
+        p.mTitle = getString(com.android.internal.R.string.usb_storage_stop_title);
+        p.mMessage = getString(com.android.internal.R.string.usb_storage_stop_message);
+        p.mPositiveButtonText = getString(com.android.internal.R.string.usb_storage_stop_button_mount);
+        p.mPositiveButtonListener = this;
+        p.mNegativeButtonText = getString(com.android.internal.R.string.usb_storage_stop_button_unmount);
+        p.mNegativeButtonListener = this;
+        setupAlert();
+    }
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+
+        registerReceiver(mBatteryReceiver, new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
+    }
+
+    @Override
+    protected void onPause() {
+        super.onPause();
+        
+        unregisterReceiver(mBatteryReceiver);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void onClick(DialogInterface dialog, int which) {
+
+        if (which == POSITIVE_BUTTON) {
+            stopUsbStorage();
+        }
+
+        // No matter what, finish the activity
+        finish();
+    }
+
+    private void stopUsbStorage() {
+        IMountService mountService = IMountService.Stub.asInterface(ServiceManager
+                .getService("mount"));
+        if (mountService == null) {
+            showStoppingError();
+            return;
+        }
+
+        try {
+            mountService.setMassStorageEnabled(false);
+        } catch (RemoteException e) {
+            showStoppingError();
+            return;
+        }
+    }
+
+    private void handleBatteryChanged(Intent intent) {
+        int pluggedType = intent.getIntExtra("plugged", 0);
+        if (pluggedType == 0) {
+            // It was disconnected from the plug, so finish
+            finish();
+        }
+    }
+    
+    private void showStoppingError() {
+        Toast.makeText(this, com.android.internal.R.string.usb_storage_stop_error_message,
+                Toast.LENGTH_LONG).show();
+    }
+
+}
diff --git a/core/java/com/android/internal/app/package.html b/core/java/com/android/internal/app/package.html
new file mode 100644
index 0000000..db6f78b
--- /dev/null
+++ b/core/java/com/android/internal/app/package.html
@@ -0,0 +1,3 @@
+<body>
+{@hide}
+</body>
\ No newline at end of file
diff --git a/core/java/com/android/internal/database/ArrayListCursor.java b/core/java/com/android/internal/database/ArrayListCursor.java
new file mode 100644
index 0000000..2e1d8f1
--- /dev/null
+++ b/core/java/com/android/internal/database/ArrayListCursor.java
@@ -0,0 +1,171 @@
+/*
+ * 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.internal.database;
+
+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.
+ */
+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 boolean deleteRow() {
+        return false;
+    }
+
+    @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/core/java/com/android/internal/database/SortCursor.java b/core/java/com/android/internal/database/SortCursor.java
new file mode 100644
index 0000000..af0efc9
--- /dev/null
+++ b/core/java/com/android/internal/database/SortCursor.java
@@ -0,0 +1,313 @@
+/*
+ * Copyright (C) 2007 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.internal.database;
+
+import android.database.AbstractCursor;
+import android.database.Cursor;
+import android.database.DataSetObserver;
+import android.util.Log;
+
+/**
+ * A variant of MergeCursor that sorts the cursors being merged. If decent
+ * performance is ever obtained, it can be put back under android.database.
+ */
+public class SortCursor extends AbstractCursor
+{
+    private static final String TAG = "SortCursor";
+    private Cursor mCursor; // updated in onMove
+    private Cursor[] mCursors;
+    private int [] mSortColumns;
+    private final int ROWCACHESIZE = 64;
+    private int mRowNumCache[] = new int[ROWCACHESIZE];
+    private int mCursorCache[] = new int[ROWCACHESIZE];
+    private int mCurRowNumCache[][];
+    private int mLastCacheHit = -1;
+
+    private DataSetObserver mObserver = new DataSetObserver() {
+
+        @Override
+        public void onChanged() {
+            // Reset our position so the optimizations in move-related code
+            // don't screw us over
+            mPos = -1;
+        }
+
+        @Override
+        public void onInvalidated() {
+            mPos = -1;
+        }
+    };
+    
+    public SortCursor(Cursor[] cursors, String sortcolumn)
+    {
+        mCursors = cursors;
+
+        int length = mCursors.length;
+        mSortColumns = new int[length];
+        for (int i = 0 ; i < length ; i++) {
+            if (mCursors[i] == null) continue;
+            
+            // Register ourself as a data set observer
+            mCursors[i].registerDataSetObserver(mObserver);
+            
+            mCursors[i].moveToFirst();
+
+            // We don't catch the exception
+            mSortColumns[i] = mCursors[i].getColumnIndexOrThrow(sortcolumn);
+        }
+        mCursor = null;
+        String smallest = "";
+        for (int j = 0 ; j < length; j++) {
+            if (mCursors[j] == null || mCursors[j].isAfterLast())
+                continue;
+            String current = mCursors[j].getString(mSortColumns[j]);
+            if (mCursor == null || current.compareToIgnoreCase(smallest) < 0) {
+                smallest = current;
+                mCursor = mCursors[j];
+            }
+        }
+
+        for (int i = mRowNumCache.length - 1; i >= 0; i--) {
+            mRowNumCache[i] = -2;
+        }
+        mCurRowNumCache = new int[ROWCACHESIZE][length];
+    }
+
+    @Override
+    public int getCount()
+    {
+        int count = 0;
+        int length = mCursors.length;
+        for (int i = 0 ; i < length ; i++) {
+            if (mCursors[i] != null) {
+                count += mCursors[i].getCount();
+            }
+        }
+        return count;
+    }
+
+    @Override
+    public boolean onMove(int oldPosition, int newPosition)
+    {
+        if (oldPosition == newPosition)
+            return true;
+
+        /* Find the right cursor
+         * Because the client of this cursor (the listadapter/view) tends
+         * to jump around in the cursor somewhat, a simple cache strategy
+         * is used to avoid having to search all cursors from the start.
+         * TODO: investigate strategies for optimizing random access and
+         * reverse-order access.
+         */
+
+        int cache_entry = newPosition % ROWCACHESIZE;
+
+        if (mRowNumCache[cache_entry] == newPosition) {
+            int which = mCursorCache[cache_entry];
+            mCursor = mCursors[which];
+            if (mCursor == null) {
+                Log.w(TAG, "onMove: cache results in a null cursor.");
+                return false;
+            }
+            mCursor.moveToPosition(mCurRowNumCache[cache_entry][which]);
+            mLastCacheHit = cache_entry;
+            return true;
+        }
+
+        mCursor = null;
+        int length = mCursors.length;
+
+        if (mLastCacheHit >= 0) {
+            for (int i = 0; i < length; i++) {
+                if (mCursors[i] == null) continue;
+                mCursors[i].moveToPosition(mCurRowNumCache[mLastCacheHit][i]);
+            }
+        }
+
+        if (newPosition < oldPosition || oldPosition == -1) {
+            for (int i = 0 ; i < length; i++) {
+                if (mCursors[i] == null) continue;
+                mCursors[i].moveToFirst();
+            }
+            oldPosition = 0;
+        }
+        if (oldPosition < 0) {
+            oldPosition = 0;
+        }
+
+        // search forward to the new position
+        int smallestIdx = -1;
+        for(int i = oldPosition; i <= newPosition; i++) {
+            String smallest = "";
+            smallestIdx = -1;
+            for (int j = 0 ; j < length; j++) {
+                if (mCursors[j] == null || mCursors[j].isAfterLast()) {
+                    continue;
+                }
+                String current = mCursors[j].getString(mSortColumns[j]);
+                if (smallestIdx < 0 || current.compareToIgnoreCase(smallest) < 0) {
+                    smallest = current;
+                    smallestIdx = j;
+                }
+            }
+            if (i == newPosition) break;
+            if (mCursors[smallestIdx] != null) {
+                mCursors[smallestIdx].moveToNext();
+            }
+        }
+        mCursor = mCursors[smallestIdx];
+        mRowNumCache[cache_entry] = newPosition;
+        mCursorCache[cache_entry] = smallestIdx;
+        for (int i = 0; i < length; i++) {
+            if (mCursors[i] != null) {
+                mCurRowNumCache[cache_entry][i] = mCursors[i].getPosition();
+            }
+        }
+        mLastCacheHit = -1;
+        return true;
+    }
+
+    @Override
+    public boolean deleteRow()
+    {
+        return mCursor.deleteRow();
+    }
+
+    @Override
+    public boolean commitUpdates() {
+        int length = mCursors.length;
+        for (int i = 0 ; i < length ; i++) {
+            if (mCursors[i] != null) {
+                mCursors[i].commitUpdates();
+            }
+        }
+        onChange(true);
+        return true;
+    }
+
+    @Override
+    public String getString(int column)
+    {
+        return mCursor.getString(column);
+    }
+
+    @Override
+    public short getShort(int column)
+    {
+        return mCursor.getShort(column);
+    }
+
+    @Override
+    public int getInt(int column)
+    {
+        return mCursor.getInt(column);
+    }
+
+    @Override
+    public long getLong(int column)
+    {
+        return mCursor.getLong(column);
+    }
+
+    @Override
+    public float getFloat(int column)
+    {
+        return mCursor.getFloat(column);
+    }
+
+    @Override
+    public double getDouble(int column)
+    {
+        return mCursor.getDouble(column);
+    }
+
+    @Override
+    public boolean isNull(int column)
+    {
+        return mCursor.isNull(column);
+    }
+
+    @Override
+    public byte[] getBlob(int column)
+    {
+        return mCursor.getBlob(column);   
+    }
+    
+    @Override
+    public String[] getColumnNames()
+    {
+        if (mCursor != null) {
+            return mCursor.getColumnNames();
+        } else {
+            return new String[0];
+        }
+    }
+
+    @Override
+    public void deactivate()
+    {
+        int length = mCursors.length;
+        for (int i = 0 ; i < length ; i++) {
+            if (mCursors[i] == null) continue;
+            mCursors[i].deactivate();
+        }
+    }
+
+    @Override
+    public void close() {
+        int length = mCursors.length;
+        for (int i = 0 ; i < length ; i++) {
+            if (mCursors[i] == null) continue;
+            mCursors[i].close();
+        }
+    }
+
+    @Override
+    public void registerDataSetObserver(DataSetObserver observer) {
+        int length = mCursors.length;
+        for (int i = 0 ; i < length ; i++) {
+            if (mCursors[i] != null) {
+                mCursors[i].registerDataSetObserver(observer);
+            }
+        }
+    }
+    
+    @Override
+    public void unregisterDataSetObserver(DataSetObserver observer) {
+        int length = mCursors.length;
+        for (int i = 0 ; i < length ; i++) {
+            if (mCursors[i] != null) {
+                mCursors[i].unregisterDataSetObserver(observer);
+            }
+        }
+    }
+    
+    @Override
+    public boolean requery()
+    {
+        int length = mCursors.length;
+        for (int i = 0 ; i < length ; i++) {
+            if (mCursors[i] == null) continue;
+            
+            if (mCursors[i].requery() == false) {
+                return false;
+            }
+        }
+
+        return true;
+    }
+}
diff --git a/core/java/com/android/internal/gadget/IGadgetHost.aidl b/core/java/com/android/internal/gadget/IGadgetHost.aidl
new file mode 100644
index 0000000..e7b5a1e
--- /dev/null
+++ b/core/java/com/android/internal/gadget/IGadgetHost.aidl
@@ -0,0 +1,28 @@
+/*
+ * 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.internal.gadget;
+
+import android.content.ComponentName;
+import android.gadget.GadgetProviderInfo;
+import android.widget.RemoteViews;
+
+/** {@hide} */
+oneway interface IGadgetHost {
+    void updateGadget(int gadgetId, in RemoteViews views);
+    void providerChanged(int gadgetId, in GadgetProviderInfo info);
+}
+
diff --git a/core/java/com/android/internal/gadget/IGadgetService.aidl b/core/java/com/android/internal/gadget/IGadgetService.aidl
new file mode 100644
index 0000000..9c66b95
--- /dev/null
+++ b/core/java/com/android/internal/gadget/IGadgetService.aidl
@@ -0,0 +1,50 @@
+/*
+ * 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.internal.gadget;
+
+import android.content.ComponentName;
+import android.gadget.GadgetProviderInfo;
+import com.android.internal.gadget.IGadgetHost;
+import android.widget.RemoteViews;
+
+/** {@hide} */
+interface IGadgetService {
+    
+    //
+    // for GadgetHost
+    //
+    int[] startListening(IGadgetHost host, String packageName, int hostId,
+            out List<RemoteViews> updatedViews);
+    void stopListening(int hostId);
+    int allocateGadgetId(String packageName, int hostId);
+    void deleteGadgetId(int gadgetId);
+    void deleteHost(int hostId);
+    void deleteAllHosts();
+    RemoteViews getGadgetViews(int gadgetId);
+
+    //
+    // for GadgetManager
+    //
+    void updateGadgetIds(in int[] gadgetIds, in RemoteViews views);
+    void updateGadgetProvider(in ComponentName provider, in RemoteViews views);
+    List<GadgetProviderInfo> getInstalledProviders();
+    GadgetProviderInfo getGadgetInfo(int gadgetId);
+    void bindGadgetId(int gadgetId, in ComponentName provider);
+    int[] getGadgetIds(in ComponentName provider);
+
+}
+
diff --git a/core/java/com/android/internal/gadget/package.html b/core/java/com/android/internal/gadget/package.html
new file mode 100644
index 0000000..db6f78b
--- /dev/null
+++ b/core/java/com/android/internal/gadget/package.html
@@ -0,0 +1,3 @@
+<body>
+{@hide}
+</body>
\ No newline at end of file
diff --git a/core/java/com/android/internal/http/multipart/ByteArrayPartSource.java b/core/java/com/android/internal/http/multipart/ByteArrayPartSource.java
new file mode 100644
index 0000000..faaac7f
--- /dev/null
+++ b/core/java/com/android/internal/http/multipart/ByteArrayPartSource.java
@@ -0,0 +1,86 @@
+/*
+ * $Header: /home/jerenkrantz/tmp/commons/commons-convert/cvs/home/cvs/jakarta-commons//httpclient/src/java/org/apache/commons/httpclient/methods/multipart/ByteArrayPartSource.java,v 1.7 2004/04/18 23:51:37 jsdever Exp $
+ * $Revision: 480424 $
+ * $Date: 2006-11-29 06:56:49 +0100 (Wed, 29 Nov 2006) $
+ *
+ * ====================================================================
+ *
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation.  For more
+ * information on the Apache Software Foundation, please see
+ * <http://www.apache.org/>.
+ *
+ */
+
+package com.android.internal.http.multipart;
+
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+
+/**
+ * A PartSource that reads from a byte array.  This class should be used when
+ * the data to post is already loaded into memory.
+ * 
+ * @author <a href="mailto:becke@u.washington.edu">Michael Becke</a>
+ *   
+ * @since 2.0 
+ */
+public class ByteArrayPartSource implements PartSource {
+
+    /** Name of the source file. */
+    private String fileName;
+
+    /** Byte array of the source file. */
+    private byte[] bytes;
+
+    /**
+     * Constructor for ByteArrayPartSource.
+     * 
+     * @param fileName the name of the file these bytes represent
+     * @param bytes the content of this part
+     */
+    public ByteArrayPartSource(String fileName, byte[] bytes) {
+
+        this.fileName = fileName;
+        this.bytes = bytes;
+
+    }
+
+    /**
+     * @see PartSource#getLength()
+     */
+    public long getLength() {
+        return bytes.length;
+    }
+
+    /**
+     * @see PartSource#getFileName()
+     */
+    public String getFileName() {
+        return fileName;
+    }
+
+    /**
+     * @see PartSource#createInputStream()
+     */
+    public InputStream createInputStream() {
+        return new ByteArrayInputStream(bytes);
+    }
+
+}
diff --git a/core/java/com/android/internal/http/multipart/FilePart.java b/core/java/com/android/internal/http/multipart/FilePart.java
new file mode 100644
index 0000000..bfcda00
--- /dev/null
+++ b/core/java/com/android/internal/http/multipart/FilePart.java
@@ -0,0 +1,254 @@
+/*
+ * $Header: /home/jerenkrantz/tmp/commons/commons-convert/cvs/home/cvs/jakarta-commons//httpclient/src/java/org/apache/commons/httpclient/methods/multipart/FilePart.java,v 1.19 2004/04/18 23:51:37 jsdever Exp $
+ * $Revision: 480424 $
+ * $Date: 2006-11-29 06:56:49 +0100 (Wed, 29 Nov 2006) $
+ *
+ * ====================================================================
+ *
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation.  For more
+ * information on the Apache Software Foundation, please see
+ * <http://www.apache.org/>.
+ *
+ */
+
+package com.android.internal.http.multipart;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import org.apache.http.util.EncodingUtils;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+/**
+ * This class implements a part of a Multipart post object that
+ * consists of a file.  
+ *
+ * @author <a href="mailto:mattalbright@yahoo.com">Matthew Albright</a>
+ * @author <a href="mailto:jsdever@apache.org">Jeff Dever</a>
+ * @author <a href="mailto:adrian@ephox.com">Adrian Sutton</a>
+ * @author <a href="mailto:becke@u.washington.edu">Michael Becke</a>
+ * @author <a href="mailto:mdiggory@latte.harvard.edu">Mark Diggory</a>
+ * @author <a href="mailto:mbowler@GargoyleSoftware.com">Mike Bowler</a>
+ * @author <a href="mailto:oleg@ural.ru">Oleg Kalnichevski</a>
+ *   
+ * @since 2.0 
+ *
+ */
+public class FilePart extends PartBase {
+
+    /** Default content encoding of file attachments. */
+    public static final String DEFAULT_CONTENT_TYPE = "application/octet-stream";
+
+    /** Default charset of file attachments. */
+    public static final String DEFAULT_CHARSET = "ISO-8859-1";
+
+    /** Default transfer encoding of file attachments. */
+    public static final String DEFAULT_TRANSFER_ENCODING = "binary";
+
+    /** Log object for this class. */
+    private static final Log LOG = LogFactory.getLog(FilePart.class);
+
+    /** Attachment's file name */
+    protected static final String FILE_NAME = "; filename=";
+
+    /** Attachment's file name as a byte array */
+    private static final byte[] FILE_NAME_BYTES = 
+        EncodingUtils.getAsciiBytes(FILE_NAME);
+
+    /** Source of the file part. */
+    private PartSource source;
+
+    /**
+     * FilePart Constructor.
+     *
+     * @param name the name for this part
+     * @param partSource the source for this part
+     * @param contentType the content type for this part, if <code>null</code> the 
+     * {@link #DEFAULT_CONTENT_TYPE default} is used
+     * @param charset the charset encoding for this part, if <code>null</code> the 
+     * {@link #DEFAULT_CHARSET default} is used
+     */
+    public FilePart(String name, PartSource partSource, String contentType, String charset) {
+        
+        super(
+            name, 
+            contentType == null ? DEFAULT_CONTENT_TYPE : contentType, 
+            charset == null ? "ISO-8859-1" : charset, 
+            DEFAULT_TRANSFER_ENCODING
+        );
+
+        if (partSource == null) {
+            throw new IllegalArgumentException("Source may not be null");
+        }
+        this.source = partSource;
+    }
+        
+    /**
+     * FilePart Constructor.
+     *
+     * @param name the name for this part
+     * @param partSource the source for this part
+     */
+    public FilePart(String name, PartSource partSource) {
+        this(name, partSource, null, null);
+    }
+
+    /**
+     * FilePart Constructor.
+     *
+     * @param name the name of the file part
+     * @param file the file to post
+     *
+     * @throws FileNotFoundException if the <i>file</i> is not a normal
+     * file or if it is not readable.
+     */
+    public FilePart(String name, File file) 
+    throws FileNotFoundException {
+        this(name, new FilePartSource(file), null, null);
+    }
+
+    /**
+     * FilePart Constructor.
+     *
+     * @param name the name of the file part
+     * @param file the file to post
+     * @param contentType the content type for this part, if <code>null</code> the 
+     * {@link #DEFAULT_CONTENT_TYPE default} is used
+     * @param charset the charset encoding for this part, if <code>null</code> the 
+     * {@link #DEFAULT_CHARSET default} is used
+     *
+     * @throws FileNotFoundException if the <i>file</i> is not a normal
+     * file or if it is not readable.
+     */
+    public FilePart(String name, File file, String contentType, String charset) 
+    throws FileNotFoundException {
+        this(name, new FilePartSource(file), contentType, charset);
+    }
+
+     /**
+     * FilePart Constructor.
+     *
+     * @param name the name of the file part
+     * @param fileName the file name 
+     * @param file the file to post
+     *
+     * @throws FileNotFoundException if the <i>file</i> is not a normal
+     * file or if it is not readable.
+     */
+    public FilePart(String name, String fileName, File file) 
+    throws FileNotFoundException {
+        this(name, new FilePartSource(fileName, file), null, null);
+    }
+    
+     /**
+     * FilePart Constructor.
+     *
+     * @param name the name of the file part
+     * @param fileName the file name 
+     * @param file the file to post
+     * @param contentType the content type for this part, if <code>null</code> the 
+     * {@link #DEFAULT_CONTENT_TYPE default} is used
+     * @param charset the charset encoding for this part, if <code>null</code> the 
+     * {@link #DEFAULT_CHARSET default} is used
+     *
+     * @throws FileNotFoundException if the <i>file</i> is not a normal
+     * file or if it is not readable.
+     */
+    public FilePart(String name, String fileName, File file, String contentType, String charset) 
+    throws FileNotFoundException {
+        this(name, new FilePartSource(fileName, file), contentType, charset);
+    }
+    
+    /**
+     * Write the disposition header to the output stream
+     * @param out The output stream
+     * @throws IOException If an IO problem occurs
+     * @see Part#sendDispositionHeader(OutputStream)
+     */
+    @Override
+    protected void sendDispositionHeader(OutputStream out) 
+    throws IOException {
+        LOG.trace("enter sendDispositionHeader(OutputStream out)");
+        super.sendDispositionHeader(out);
+        String filename = this.source.getFileName();
+        if (filename != null) {
+            out.write(FILE_NAME_BYTES);
+            out.write(QUOTE_BYTES);
+            out.write(EncodingUtils.getAsciiBytes(filename));
+            out.write(QUOTE_BYTES);
+        }
+    }
+    
+    /**
+     * Write the data in "source" to the specified stream.
+     * @param out The output stream.
+     * @throws IOException if an IO problem occurs.
+     * @see Part#sendData(OutputStream)
+     */
+    @Override
+    protected void sendData(OutputStream out) throws IOException {
+        LOG.trace("enter sendData(OutputStream out)");
+        if (lengthOfData() == 0) {
+            
+            // this file contains no data, so there is nothing to send.
+            // we don't want to create a zero length buffer as this will
+            // cause an infinite loop when reading.
+            LOG.debug("No data to send.");
+            return;
+        }
+        
+        byte[] tmp = new byte[4096];
+        InputStream instream = source.createInputStream();
+        try {
+            int len;
+            while ((len = instream.read(tmp)) >= 0) {
+                out.write(tmp, 0, len);
+            }
+        } finally {
+            // we're done with the stream, close it
+            instream.close();
+        }
+    }
+
+    /** 
+     * Returns the source of the file part.
+     *  
+     * @return The source.
+     */
+    protected PartSource getSource() {
+        LOG.trace("enter getSource()");
+        return this.source;
+    }
+
+    /**
+     * Return the length of the data.
+     * @return The length.
+     * @see Part#lengthOfData()
+     */    
+    @Override
+    protected long lengthOfData() {
+        LOG.trace("enter lengthOfData()");
+        return source.getLength();
+    }    
+
+}
diff --git a/core/java/com/android/internal/http/multipart/FilePartSource.java b/core/java/com/android/internal/http/multipart/FilePartSource.java
new file mode 100644
index 0000000..eb5cc0f
--- /dev/null
+++ b/core/java/com/android/internal/http/multipart/FilePartSource.java
@@ -0,0 +1,131 @@
+/*
+ * $Header: /home/jerenkrantz/tmp/commons/commons-convert/cvs/home/cvs/jakarta-commons//httpclient/src/java/org/apache/commons/httpclient/methods/multipart/FilePartSource.java,v 1.10 2004/04/18 23:51:37 jsdever Exp $
+ * $Revision: 480424 $
+ * $Date: 2006-11-29 06:56:49 +0100 (Wed, 29 Nov 2006) $
+ *
+ * ====================================================================
+ *
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation.  For more
+ * information on the Apache Software Foundation, please see
+ * <http://www.apache.org/>.
+ *
+ */
+
+package com.android.internal.http.multipart;
+
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * A PartSource that reads from a File.
+ * 
+ * @author <a href="mailto:becke@u.washington.edu">Michael Becke</a>
+ * @author <a href="mailto:mdiggory@latte.harvard.edu">Mark Diggory</a>
+ * @author <a href="mailto:mbowler@GargoyleSoftware.com">Mike Bowler</a>
+ *   
+ * @since 2.0 
+ */
+public class FilePartSource implements PartSource {
+
+    /** File part file. */
+    private File file = null;
+
+    /** File part file name. */
+    private String fileName = null;
+    
+    /**
+     * Constructor for FilePartSource.
+     * 
+     * @param file the FilePart source File. 
+     *
+     * @throws FileNotFoundException if the file does not exist or 
+     * cannot be read
+     */
+    public FilePartSource(File file) throws FileNotFoundException {
+        this.file = file;
+        if (file != null) {
+            if (!file.isFile()) {
+                throw new FileNotFoundException("File is not a normal file.");
+            }
+            if (!file.canRead()) {
+                throw new FileNotFoundException("File is not readable.");
+            }
+            this.fileName = file.getName();       
+        }
+    }
+
+    /**
+     * Constructor for FilePartSource.
+     * 
+     * @param fileName the file name of the FilePart
+     * @param file the source File for the FilePart
+     *
+     * @throws FileNotFoundException if the file does not exist or 
+     * cannot be read
+     */
+    public FilePartSource(String fileName, File file) 
+      throws FileNotFoundException {
+        this(file);
+        if (fileName != null) {
+            this.fileName = fileName;
+        }
+    }
+    
+    /**
+     * Return the length of the file
+     * @return the length of the file.
+     * @see PartSource#getLength()
+     */
+    public long getLength() {
+        if (this.file != null) {
+            return this.file.length();
+        } else {
+            return 0;
+        }
+    }
+
+    /**
+     * Return the current filename
+     * @return the filename.
+     * @see PartSource#getFileName()
+     */
+    public String getFileName() {
+        return (fileName == null) ? "noname" : fileName;
+    }
+
+    /**
+     * Return a new {@link FileInputStream} for the current filename.
+     * @return the new input stream.
+     * @throws IOException If an IO problem occurs.
+     * @see PartSource#createInputStream()
+     */
+    public InputStream createInputStream() throws IOException {
+        if (this.file != null) {
+            return new FileInputStream(this.file);
+        } else {
+            return new ByteArrayInputStream(new byte[] {});
+        }
+    }
+
+}
diff --git a/core/java/com/android/internal/http/multipart/MultipartEntity.java b/core/java/com/android/internal/http/multipart/MultipartEntity.java
new file mode 100644
index 0000000..2c5e7f6
--- /dev/null
+++ b/core/java/com/android/internal/http/multipart/MultipartEntity.java
@@ -0,0 +1,230 @@
+/*
+ * $Header: /home/jerenkrantz/tmp/commons/commons-convert/cvs/home/cvs/jakarta-commons//httpclient/src/java/org/apache/commons/httpclient/methods/multipart/MultipartRequestEntity.java,v 1.1 2004/10/06 03:39:59 mbecke Exp $
+ * $Revision: 502647 $
+ * $Date: 2007-02-02 17:22:54 +0100 (Fri, 02 Feb 2007) $
+ *
+ * ====================================================================
+ *
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation.  For more
+ * information on the Apache Software Foundation, please see
+ * <http://www.apache.org/>.
+ *
+ */
+
+package com.android.internal.http.multipart;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.Random;
+
+import org.apache.http.Header;
+import org.apache.http.entity.AbstractHttpEntity;
+import org.apache.http.message.BasicHeader;
+import org.apache.http.params.HttpParams;
+import org.apache.http.protocol.HTTP;
+import org.apache.http.util.EncodingUtils;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+/**
+ * Implements a request entity suitable for an HTTP multipart POST method.
+ * <p>
+ * The HTTP multipart POST method is defined in section 3.3 of
+ * <a href="http://www.ietf.org/rfc/rfc1867.txt">RFC1867</a>:
+ * <blockquote>
+ * The media-type multipart/form-data follows the rules of all multipart
+ * MIME data streams as outlined in RFC 1521. The multipart/form-data contains 
+ * a series of parts. Each part is expected to contain a content-disposition 
+ * header where the value is "form-data" and a name attribute specifies 
+ * the field name within the form, e.g., 'content-disposition: form-data; 
+ * name="xxxxx"', where xxxxx is the field name corresponding to that field.
+ * Field names originally in non-ASCII character sets may be encoded using 
+ * the method outlined in RFC 1522.
+ * </blockquote>
+ * </p>
+ * <p>This entity is designed to be used in conjunction with the 
+ * {@link org.apache.http.HttpRequest} to provide
+ * multipart posts.  Example usage:</p>
+ * <pre>
+ *  File f = new File("/path/fileToUpload.txt");
+ *  HttpRequest request = new HttpRequest("http://host/some_path");
+ *  Part[] parts = {
+ *      new StringPart("param_name", "value"),
+ *      new FilePart(f.getName(), f)
+ *  };
+ *  filePost.setEntity(
+ *      new MultipartRequestEntity(parts, filePost.getParams())
+ *      );
+ *  HttpClient client = new HttpClient();
+ *  int status = client.executeMethod(filePost);
+ * </pre>
+ * 
+ * @since 3.0
+ */
+public class MultipartEntity extends AbstractHttpEntity {
+
+    private static final Log log = LogFactory.getLog(MultipartEntity.class);
+    
+    /** The Content-Type for multipart/form-data. */
+    private static final String MULTIPART_FORM_CONTENT_TYPE = "multipart/form-data";
+    
+    /**
+     * Sets the value to use as the multipart boundary.
+     * <p>
+     * This parameter expects a value if type {@link String}.
+     * </p>
+     */
+    public static final String MULTIPART_BOUNDARY = "http.method.multipart.boundary";
+    
+    /**
+     * The pool of ASCII chars to be used for generating a multipart boundary.
+     */
+    private static byte[] MULTIPART_CHARS = EncodingUtils.getAsciiBytes(
+        "-_1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ");
+    
+    /**
+     * Generates a random multipart boundary string.
+    */
+    private static byte[] generateMultipartBoundary() {
+        Random rand = new Random();
+        byte[] bytes = new byte[rand.nextInt(11) + 30]; // a random size from 30 to 40
+        for (int i = 0; i < bytes.length; i++) {
+            bytes[i] = MULTIPART_CHARS[rand.nextInt(MULTIPART_CHARS.length)];
+        }
+        return bytes;
+    }
+    
+    /** The MIME parts as set by the constructor */
+    protected Part[] parts;
+    
+    private byte[] multipartBoundary;
+    
+    private HttpParams params;
+    
+    private boolean contentConsumed = false;
+    
+    /**
+     * Creates a new multipart entity containing the given parts.
+     * @param parts The parts to include.
+     * @param params The params of the HttpMethod using this entity.
+     */
+    public MultipartEntity(Part[] parts, HttpParams params) {      
+      if (parts == null) {
+          throw new IllegalArgumentException("parts cannot be null");
+      }
+      if (params == null) {
+          throw new IllegalArgumentException("params cannot be null");
+      }
+      this.parts = parts;
+      this.params = params;
+    }
+    
+    public MultipartEntity(Part[] parts) {
+      setContentType(MULTIPART_FORM_CONTENT_TYPE);
+      if (parts == null) {
+          throw new IllegalArgumentException("parts cannot be null");
+      }
+      this.parts = parts;
+      this.params = null;
+    }
+    
+    /**
+     * Returns the MIME boundary string that is used to demarcate boundaries of
+     * this part. The first call to this method will implicitly create a new
+     * boundary string. To create a boundary string first the 
+     * HttpMethodParams.MULTIPART_BOUNDARY parameter is considered. Otherwise 
+     * a random one is generated.
+     * 
+     * @return The boundary string of this entity in ASCII encoding.
+     */
+    protected byte[] getMultipartBoundary() {
+        if (multipartBoundary == null) {
+            String temp = null;
+            if (params != null) {
+              temp = (String) params.getParameter(MULTIPART_BOUNDARY);
+            }
+            if (temp != null) {
+                multipartBoundary = EncodingUtils.getAsciiBytes(temp);
+            } else {
+                multipartBoundary = generateMultipartBoundary();
+            }
+        }
+        return multipartBoundary;
+    }
+
+    /**
+     * Returns <code>true</code> if all parts are repeatable, <code>false</code> otherwise.
+     */
+    public boolean isRepeatable() {
+        for (int i = 0; i < parts.length; i++) {
+            if (!parts[i].isRepeatable()) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /* (non-Javadoc)
+     */
+    public void writeTo(OutputStream out) throws IOException {
+        Part.sendParts(out, parts, getMultipartBoundary());
+    }
+    /* (non-Javadoc)
+     * @see org.apache.commons.http.AbstractHttpEntity.#getContentType()
+     */
+    @Override
+    public Header getContentType() {
+      StringBuffer buffer = new StringBuffer(MULTIPART_FORM_CONTENT_TYPE);
+      buffer.append("; boundary=");
+      buffer.append(EncodingUtils.getAsciiString(getMultipartBoundary()));
+      return new BasicHeader(HTTP.CONTENT_TYPE, buffer.toString());
+
+    }
+
+    /* (non-Javadoc)
+     */
+    public long getContentLength() {
+        try {
+            return Part.getLengthOfParts(parts, getMultipartBoundary());            
+        } catch (Exception e) {
+            log.error("An exception occurred while getting the length of the parts", e);
+            return 0;
+        }
+    }    
+ 
+    public InputStream getContent() throws IOException, IllegalStateException {
+          if(!isRepeatable() && this.contentConsumed ) {
+              throw new IllegalStateException("Content has been consumed");
+          }
+          this.contentConsumed = true;
+          
+          ByteArrayOutputStream baos = new ByteArrayOutputStream();
+          Part.sendParts(baos, this.parts, this.multipartBoundary);
+          ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
+          return bais;
+    }
+  
+    public boolean isStreaming() {
+        return false;
+    }
+}
diff --git a/core/java/com/android/internal/http/multipart/Part.java b/core/java/com/android/internal/http/multipart/Part.java
new file mode 100644
index 0000000..cb1b546
--- /dev/null
+++ b/core/java/com/android/internal/http/multipart/Part.java
@@ -0,0 +1,439 @@
+/*
+ * $Header: /home/jerenkrantz/tmp/commons/commons-convert/cvs/home/cvs/jakarta-commons//httpclient/src/java/org/apache/commons/httpclient/methods/multipart/Part.java,v 1.16 2005/01/14 21:16:40 olegk Exp $
+ * $Revision: 480424 $
+ * $Date: 2006-11-29 06:56:49 +0100 (Wed, 29 Nov 2006) $
+ *
+ * ====================================================================
+ *
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation.  For more
+ * information on the Apache Software Foundation, please see
+ * <http://www.apache.org/>.
+ *
+ */
+
+package com.android.internal.http.multipart;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+
+import org.apache.http.util.EncodingUtils;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+/**
+ * Abstract class for one Part of a multipart post object.
+ *
+ * @author <a href="mailto:mattalbright@yahoo.com">Matthew Albright</a>
+ * @author <a href="mailto:jsdever@apache.org">Jeff Dever</a>
+ * @author <a href="mailto:adrian@ephox.com">Adrian Sutton</a>
+ * @author <a href="mailto:mbowler@GargoyleSoftware.com">Mike Bowler</a>
+ * @author <a href="mailto:oleg@ural.ru">Oleg Kalnichevski</a>
+ *
+ * @since 2.0
+ */
+public abstract class Part {
+
+    /** Log object for this class. */
+    private static final Log LOG = LogFactory.getLog(Part.class);
+
+    /** 
+     * The boundary 
+     * @deprecated use {@link org.apache.http.client.methods.multipart#MULTIPART_BOUNDARY}
+     */
+    protected static final String BOUNDARY = "----------------314159265358979323846";
+    
+    /** 
+     * The boundary as a byte array.
+     * @deprecated
+     */
+    protected static final byte[] BOUNDARY_BYTES = EncodingUtils.getAsciiBytes(BOUNDARY);
+
+    /**
+     * The default boundary to be used if {@link #setPartBoundary(byte[])} has not
+     * been called.
+     */
+    private static final byte[] DEFAULT_BOUNDARY_BYTES = BOUNDARY_BYTES;    
+    
+    /** Carriage return/linefeed */
+    protected static final String CRLF = "\r\n";
+    
+    /** Carriage return/linefeed as a byte array */
+    protected static final byte[] CRLF_BYTES = EncodingUtils.getAsciiBytes(CRLF);
+    
+    /** Content dispostion characters */
+    protected static final String QUOTE = "\"";
+    
+    /** Content dispostion as a byte array */
+    protected static final byte[] QUOTE_BYTES = 
+      EncodingUtils.getAsciiBytes(QUOTE);
+
+    /** Extra characters */
+    protected static final String EXTRA = "--";
+    
+    /** Extra characters as a byte array */
+    protected static final byte[] EXTRA_BYTES = 
+      EncodingUtils.getAsciiBytes(EXTRA);
+    
+    /** Content dispostion characters */
+    protected static final String CONTENT_DISPOSITION = "Content-Disposition: form-data; name=";
+    
+    /** Content dispostion as a byte array */
+    protected static final byte[] CONTENT_DISPOSITION_BYTES = 
+      EncodingUtils.getAsciiBytes(CONTENT_DISPOSITION);
+
+    /** Content type header */
+    protected static final String CONTENT_TYPE = "Content-Type: ";
+
+    /** Content type header as a byte array */
+    protected static final byte[] CONTENT_TYPE_BYTES = 
+      EncodingUtils.getAsciiBytes(CONTENT_TYPE);
+
+    /** Content charset */
+    protected static final String CHARSET = "; charset=";
+
+    /** Content charset as a byte array */
+    protected static final byte[] CHARSET_BYTES = 
+      EncodingUtils.getAsciiBytes(CHARSET);
+
+    /** Content type header */
+    protected static final String CONTENT_TRANSFER_ENCODING = "Content-Transfer-Encoding: ";
+
+    /** Content type header as a byte array */
+    protected static final byte[] CONTENT_TRANSFER_ENCODING_BYTES = 
+      EncodingUtils.getAsciiBytes(CONTENT_TRANSFER_ENCODING);
+
+    /**
+     * Return the boundary string.
+     * @return the boundary string
+     * @deprecated uses a constant string. Rather use {@link #getPartBoundary}
+     */
+    public static String getBoundary() {
+        return BOUNDARY;
+    }
+
+    /**
+     * The ASCII bytes to use as the multipart boundary.
+     */
+    private byte[] boundaryBytes;
+    
+    /**
+     * Return the name of this part.
+     * @return The name.
+     */
+    public abstract String getName();
+    
+    /**
+     * Returns the content type of this part.
+     * @return the content type, or <code>null</code> to exclude the content type header
+     */
+    public abstract String getContentType();
+
+    /**
+     * Return the character encoding of this part.
+     * @return the character encoding, or <code>null</code> to exclude the character 
+     * encoding header
+     */
+    public abstract String getCharSet();
+
+    /**
+     * Return the transfer encoding of this part.
+     * @return the transfer encoding, or <code>null</code> to exclude the transfer encoding header
+     */
+    public abstract String getTransferEncoding();
+
+    /**
+     * Gets the part boundary to be used.
+     * @return the part boundary as an array of bytes.
+     * 
+     * @since 3.0
+     */
+    protected byte[] getPartBoundary() {
+        if (boundaryBytes == null) {
+            // custom boundary bytes have not been set, use the default.
+            return DEFAULT_BOUNDARY_BYTES;
+        } else {
+            return boundaryBytes;            
+        }
+    }
+    
+    /**
+     * Sets the part boundary.  Only meant to be used by 
+     * {@link Part#sendParts(OutputStream, Part[], byte[])}
+     * and {@link Part#getLengthOfParts(Part[], byte[])}
+     * @param boundaryBytes An array of ASCII bytes.
+     * @since 3.0
+     */
+    void setPartBoundary(byte[] boundaryBytes) {
+        this.boundaryBytes = boundaryBytes;
+    }
+    
+    /**
+     * Tests if this part can be sent more than once.
+     * @return <code>true</code> if {@link #sendData(OutputStream)} can be successfully called 
+     * more than once.
+     * @since 3.0
+     */
+    public boolean isRepeatable() {
+        return true;
+    }
+    
+    /**
+     * Write the start to the specified output stream
+     * @param out The output stream
+     * @throws IOException If an IO problem occurs.
+     */
+    protected void sendStart(OutputStream out) throws IOException {
+        LOG.trace("enter sendStart(OutputStream out)");
+        out.write(EXTRA_BYTES);
+        out.write(getPartBoundary());
+        out.write(CRLF_BYTES);
+    }
+    
+    /**
+     * Write the content disposition header to the specified output stream
+     * 
+     * @param out The output stream
+     * @throws IOException If an IO problem occurs.
+     */
+    protected void sendDispositionHeader(OutputStream out) throws IOException {
+        LOG.trace("enter sendDispositionHeader(OutputStream out)");
+        out.write(CONTENT_DISPOSITION_BYTES);
+        out.write(QUOTE_BYTES);
+        out.write(EncodingUtils.getAsciiBytes(getName()));
+        out.write(QUOTE_BYTES);
+    }
+    
+    /**
+     * Write the content type header to the specified output stream
+     * @param out The output stream
+     * @throws IOException If an IO problem occurs.
+     */
+     protected void sendContentTypeHeader(OutputStream out) throws IOException {
+        LOG.trace("enter sendContentTypeHeader(OutputStream out)");
+        String contentType = getContentType();
+        if (contentType != null) {
+            out.write(CRLF_BYTES);
+            out.write(CONTENT_TYPE_BYTES);
+            out.write(EncodingUtils.getAsciiBytes(contentType));
+            String charSet = getCharSet();
+            if (charSet != null) {
+                out.write(CHARSET_BYTES);
+                out.write(EncodingUtils.getAsciiBytes(charSet));
+            }
+        }
+    }
+
+    /**
+     * Write the content transfer encoding header to the specified 
+     * output stream
+     * 
+     * @param out The output stream
+     * @throws IOException If an IO problem occurs.
+     */
+     protected void sendTransferEncodingHeader(OutputStream out) throws IOException {
+        LOG.trace("enter sendTransferEncodingHeader(OutputStream out)");
+        String transferEncoding = getTransferEncoding();
+        if (transferEncoding != null) {
+            out.write(CRLF_BYTES);
+            out.write(CONTENT_TRANSFER_ENCODING_BYTES);
+            out.write(EncodingUtils.getAsciiBytes(transferEncoding));
+        }
+    }
+
+    /**
+     * Write the end of the header to the output stream
+     * @param out The output stream
+     * @throws IOException If an IO problem occurs.
+     */
+    protected void sendEndOfHeader(OutputStream out) throws IOException {
+        LOG.trace("enter sendEndOfHeader(OutputStream out)");
+        out.write(CRLF_BYTES);
+        out.write(CRLF_BYTES);
+    }
+    
+    /**
+     * Write the data to the specified output stream
+     * @param out The output stream
+     * @throws IOException If an IO problem occurs.
+     */
+    protected abstract void sendData(OutputStream out) throws IOException;
+    
+    /**
+     * Return the length of the main content
+     * 
+     * @return long The length.
+     * @throws IOException If an IO problem occurs
+     */
+    protected abstract long lengthOfData() throws IOException;
+    
+    /**
+     * Write the end data to the output stream.
+     * @param out The output stream
+     * @throws IOException If an IO problem occurs.
+     */
+    protected void sendEnd(OutputStream out) throws IOException {
+        LOG.trace("enter sendEnd(OutputStream out)");
+        out.write(CRLF_BYTES);
+    }
+    
+    /**
+     * Write all the data to the output stream.
+     * If you override this method make sure to override 
+     * #length() as well
+     * 
+     * @param out The output stream
+     * @throws IOException If an IO problem occurs.
+     */
+    public void send(OutputStream out) throws IOException {
+        LOG.trace("enter send(OutputStream out)");
+        sendStart(out);
+        sendDispositionHeader(out);
+        sendContentTypeHeader(out);
+        sendTransferEncodingHeader(out);
+        sendEndOfHeader(out);
+        sendData(out);
+        sendEnd(out);
+    }
+
+
+    /**
+     * Return the full length of all the data.
+     * If you override this method make sure to override 
+     * #send(OutputStream) as well
+     * 
+     * @return long The length.
+     * @throws IOException If an IO problem occurs
+     */
+    public long length() throws IOException {
+        LOG.trace("enter length()");
+        if (lengthOfData() < 0) {
+            return -1;
+        }
+        ByteArrayOutputStream overhead = new ByteArrayOutputStream();
+        sendStart(overhead);
+        sendDispositionHeader(overhead);
+        sendContentTypeHeader(overhead);
+        sendTransferEncodingHeader(overhead);
+        sendEndOfHeader(overhead);
+        sendEnd(overhead);
+        return overhead.size() + lengthOfData();
+    }
+
+    /**
+     * Return a string representation of this object.
+     * @return A string representation of this object.
+     * @see java.lang.Object#toString()
+     */    
+    @Override
+    public String toString() {
+        return this.getName();
+    }
+
+    /**
+     * Write all parts and the last boundary to the specified output stream.
+     * 
+     * @param out The stream to write to.
+     * @param parts The parts to write.
+     * 
+     * @throws IOException If an I/O error occurs while writing the parts.
+     */
+    public static void sendParts(OutputStream out, final Part[] parts)
+        throws IOException {
+        sendParts(out, parts, DEFAULT_BOUNDARY_BYTES);
+    }
+
+    /**
+     * Write all parts and the last boundary to the specified output stream.
+     * 
+     * @param out The stream to write to.
+     * @param parts The parts to write.
+     * @param partBoundary The ASCII bytes to use as the part boundary.
+     * 
+     * @throws IOException If an I/O error occurs while writing the parts.
+     * 
+     * @since 3.0
+     */
+    public static void sendParts(OutputStream out, Part[] parts, byte[] partBoundary)
+        throws IOException {
+        
+        if (parts == null) {
+            throw new IllegalArgumentException("Parts may not be null"); 
+        }
+        if (partBoundary == null || partBoundary.length == 0) {
+            throw new IllegalArgumentException("partBoundary may not be empty");
+        }
+        for (int i = 0; i < parts.length; i++) {
+            // set the part boundary before the part is sent
+            parts[i].setPartBoundary(partBoundary);
+            parts[i].send(out);
+        }
+        out.write(EXTRA_BYTES);
+        out.write(partBoundary);
+        out.write(EXTRA_BYTES);
+        out.write(CRLF_BYTES);
+    }
+    
+    /**
+     * Return the total sum of all parts and that of the last boundary
+     * 
+     * @param parts The parts.
+     * @return The total length
+     * 
+     * @throws IOException If an I/O error occurs while writing the parts.
+     */
+    public static long getLengthOfParts(Part[] parts)
+    throws IOException {
+        return getLengthOfParts(parts, DEFAULT_BOUNDARY_BYTES);
+    }
+    
+    /**
+     * Gets the length of the multipart message including the given parts.
+     * 
+     * @param parts The parts.
+     * @param partBoundary The ASCII bytes to use as the part boundary.
+     * @return The total length
+     * 
+     * @throws IOException If an I/O error occurs while writing the parts.
+     * 
+     * @since 3.0
+     */
+    public static long getLengthOfParts(Part[] parts, byte[] partBoundary) throws IOException {
+        LOG.trace("getLengthOfParts(Parts[])");
+        if (parts == null) {
+            throw new IllegalArgumentException("Parts may not be null"); 
+        }
+        long total = 0;
+        for (int i = 0; i < parts.length; i++) {
+            // set the part boundary before we calculate the part's length
+            parts[i].setPartBoundary(partBoundary);
+            long l = parts[i].length();
+            if (l < 0) {
+                return -1;
+            }
+            total += l;
+        }
+        total += EXTRA_BYTES.length;
+        total += partBoundary.length;
+        total += EXTRA_BYTES.length;
+        total += CRLF_BYTES.length;
+        return total;
+    }        
+}
diff --git a/core/java/com/android/internal/http/multipart/PartBase.java b/core/java/com/android/internal/http/multipart/PartBase.java
new file mode 100644
index 0000000..876d15d
--- /dev/null
+++ b/core/java/com/android/internal/http/multipart/PartBase.java
@@ -0,0 +1,150 @@
+/*
+ * $Header: /home/jerenkrantz/tmp/commons/commons-convert/cvs/home/cvs/jakarta-commons//httpclient/src/java/org/apache/commons/httpclient/methods/multipart/PartBase.java,v 1.5 2004/04/18 23:51:37 jsdever Exp $
+ * $Revision: 480424 $
+ * $Date: 2006-11-29 06:56:49 +0100 (Wed, 29 Nov 2006) $
+ *
+ * ====================================================================
+ *
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation.  For more
+ * information on the Apache Software Foundation, please see
+ * <http://www.apache.org/>.
+ *
+ */
+
+package com.android.internal.http.multipart;
+
+
+/**
+ * Provides setters and getters for the basic Part properties.
+ * 
+ * @author Michael Becke
+ */
+public abstract class PartBase extends Part {
+
+    /** Name of the file part. */
+    private String name;
+        
+    /** Content type of the file part. */
+    private String contentType;
+
+    /** Content encoding of the file part. */
+    private String charSet;
+    
+    /** The transfer encoding. */
+    private String transferEncoding;
+
+    /**
+     * Constructor.
+     * 
+     * @param name The name of the part
+     * @param contentType The content type, or <code>null</code>
+     * @param charSet The character encoding, or <code>null</code> 
+     * @param transferEncoding The transfer encoding, or <code>null</code>
+     */
+    public PartBase(String name, String contentType, String charSet, String transferEncoding) {
+
+        if (name == null) {
+            throw new IllegalArgumentException("Name must not be null");
+        }
+        this.name = name;
+        this.contentType = contentType;
+        this.charSet = charSet;
+        this.transferEncoding = transferEncoding;
+    }
+
+    /**
+     * Returns the name.
+     * @return The name.
+     * @see Part#getName()
+     */
+    @Override
+    public String getName() { 
+        return this.name; 
+    }
+
+    /**
+     * Returns the content type of this part.
+     * @return String The name.
+     */
+    @Override
+    public String getContentType() {
+        return this.contentType;
+    }
+
+    /**
+     * Return the character encoding of this part.
+     * @return String The name.
+     */
+    @Override
+    public String getCharSet() {
+        return this.charSet;
+    }
+
+    /**
+     * Returns the transfer encoding of this part.
+     * @return String The name.
+     */
+    @Override
+    public String getTransferEncoding() {
+        return transferEncoding;
+    }
+
+    /**
+     * Sets the character encoding.
+     * 
+     * @param charSet the character encoding, or <code>null</code> to exclude the character 
+     * encoding header
+     */
+    public void setCharSet(String charSet) {
+        this.charSet = charSet;
+    }
+
+    /**
+     * Sets the content type.
+     * 
+     * @param contentType the content type, or <code>null</code> to exclude the content type header
+     */
+    public void setContentType(String contentType) {
+        this.contentType = contentType;
+    }
+
+    /**
+     * Sets the part name.
+     * 
+     * @param name
+     */
+    public void setName(String name) {
+        if (name == null) {
+            throw new IllegalArgumentException("Name must not be null");
+        }
+        this.name = name;
+    }
+
+    /**
+     * Sets the transfer encoding.
+     * 
+     * @param transferEncoding the transfer encoding, or <code>null</code> to exclude the 
+     * transfer encoding header
+     */
+    public void setTransferEncoding(String transferEncoding) {
+        this.transferEncoding = transferEncoding;
+    }
+
+}
diff --git a/core/java/com/android/internal/http/multipart/PartSource.java b/core/java/com/android/internal/http/multipart/PartSource.java
new file mode 100644
index 0000000..3740696
--- /dev/null
+++ b/core/java/com/android/internal/http/multipart/PartSource.java
@@ -0,0 +1,72 @@
+/*
+ * $Header: /home/jerenkrantz/tmp/commons/commons-convert/cvs/home/cvs/jakarta-commons//httpclient/src/java/org/apache/commons/httpclient/methods/multipart/PartSource.java,v 1.6 2004/04/18 23:51:37 jsdever Exp $
+ * $Revision: 480424 $
+ * $Date: 2006-11-29 06:56:49 +0100 (Wed, 29 Nov 2006) $
+ *
+ * ====================================================================
+ *
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation.  For more
+ * information on the Apache Software Foundation, please see
+ * <http://www.apache.org/>.
+ *
+ */
+
+package com.android.internal.http.multipart;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * An interface for providing access to data when posting MultiPart messages.
+ * 
+ * @see FilePart
+ * 
+ * @author <a href="mailto:becke@u.washington.edu">Michael Becke</a>
+ *   
+ * @since 2.0 
+ */
+public interface PartSource {
+
+    /**
+     * Gets the number of bytes contained in this source.
+     * 
+     * @return a value >= 0
+     */
+    long getLength();
+    
+    /**
+     * Gets the name of the file this source represents.
+     * 
+     * @return the fileName used for posting a MultiPart file part
+     */
+    String getFileName();
+    
+    /**
+     * Gets a new InputStream for reading this source.  This method can be 
+     * called more than once and should therefore return a new stream every
+     * time.
+     * 
+     * @return a new InputStream
+     * 
+     * @throws IOException if an error occurs when creating the InputStream
+     */
+    InputStream createInputStream() throws IOException;
+
+}
diff --git a/core/java/com/android/internal/http/multipart/StringPart.java b/core/java/com/android/internal/http/multipart/StringPart.java
new file mode 100644
index 0000000..c98257e
--- /dev/null
+++ b/core/java/com/android/internal/http/multipart/StringPart.java
@@ -0,0 +1,150 @@
+/*
+ * $Header: /home/jerenkrantz/tmp/commons/commons-convert/cvs/home/cvs/jakarta-commons//httpclient/src/java/org/apache/commons/httpclient/methods/multipart/StringPart.java,v 1.11 2004/04/18 23:51:37 jsdever Exp $
+ * $Revision: 480424 $
+ * $Date: 2006-11-29 06:56:49 +0100 (Wed, 29 Nov 2006) $
+ *
+ * ====================================================================
+ *
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation.  For more
+ * information on the Apache Software Foundation, please see
+ * <http://www.apache.org/>.
+ *
+ */
+
+package com.android.internal.http.multipart;
+
+import java.io.OutputStream;
+import java.io.IOException;
+
+import org.apache.http.util.EncodingUtils;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+/**
+ * Simple string parameter for a multipart post
+ *
+ * @author <a href="mailto:mattalbright@yahoo.com">Matthew Albright</a>
+ * @author <a href="mailto:jsdever@apache.org">Jeff Dever</a>
+ * @author <a href="mailto:mbowler@GargoyleSoftware.com">Mike Bowler</a>
+ * @author <a href="mailto:oleg@ural.ru">Oleg Kalnichevski</a>
+ *
+ * @since 2.0
+ */
+public class StringPart extends PartBase {
+
+    /** Log object for this class. */
+    private static final Log LOG = LogFactory.getLog(StringPart.class);
+
+    /** Default content encoding of string parameters. */
+    public static final String DEFAULT_CONTENT_TYPE = "text/plain";
+
+    /** Default charset of string parameters*/
+    public static final String DEFAULT_CHARSET = "US-ASCII";
+
+    /** Default transfer encoding of string parameters*/
+    public static final String DEFAULT_TRANSFER_ENCODING = "8bit";
+
+    /** Contents of this StringPart. */
+    private byte[] content;
+    
+    /** The String value of this part. */
+    private String value;
+
+    /**
+     * Constructor.
+     *
+     * @param name The name of the part
+     * @param value the string to post
+     * @param charset the charset to be used to encode the string, if <code>null</code> 
+     * the {@link #DEFAULT_CHARSET default} is used
+     */
+    public StringPart(String name, String value, String charset) {
+        
+        super(
+            name,
+            DEFAULT_CONTENT_TYPE,
+            charset == null ? DEFAULT_CHARSET : charset,
+            DEFAULT_TRANSFER_ENCODING
+        );
+        if (value == null) {
+            throw new IllegalArgumentException("Value may not be null");
+        }
+        if (value.indexOf(0) != -1) {
+            // See RFC 2048, 2.8. "8bit Data"
+            throw new IllegalArgumentException("NULs may not be present in string parts");
+        }
+        this.value = value;
+    }
+
+    /**
+     * Constructor.
+     *
+     * @param name The name of the part
+     * @param value the string to post
+     */
+    public StringPart(String name, String value) {
+        this(name, value, null);
+    }
+    
+    /**
+     * Gets the content in bytes.  Bytes are lazily created to allow the charset to be changed
+     * after the part is created.
+     * 
+     * @return the content in bytes
+     */
+    private byte[] getContent() {
+        if (content == null) {
+            content = EncodingUtils.getBytes(value, getCharSet());
+        }
+        return content;
+    }
+    
+    /**
+     * Writes the data to the given OutputStream.
+     * @param out the OutputStream to write to
+     * @throws IOException if there is a write error
+     */
+    @Override
+    protected void sendData(OutputStream out) throws IOException {
+        LOG.trace("enter sendData(OutputStream)");
+        out.write(getContent());
+    }
+    
+    /**
+     * Return the length of the data.
+     * @return The length of the data.
+     * @see Part#lengthOfData()
+     */
+    @Override
+    protected long lengthOfData() {
+        LOG.trace("enter lengthOfData()");
+        return getContent().length;
+    }
+    
+    /* (non-Javadoc)
+     * @see org.apache.commons.httpclient.methods.multipart.BasePart#setCharSet(java.lang.String)
+     */
+    @Override
+    public void setCharSet(String charSet) {
+        super.setCharSet(charSet);
+        this.content = null;
+    }
+
+}
diff --git a/core/java/com/android/internal/logging/AndroidConfig.java b/core/java/com/android/internal/logging/AndroidConfig.java
new file mode 100644
index 0000000..f8002c6
--- /dev/null
+++ b/core/java/com/android/internal/logging/AndroidConfig.java
@@ -0,0 +1,47 @@
+/*
+ * 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.internal.logging;
+
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * Implements the java.util.logging configuration for Android. Activates a log
+ * handler that writes to the Android log.
+ */
+public class AndroidConfig {
+
+    /**
+     * This looks a bit weird, but it's the way the logging config works: A
+     * named class is instantiated, the constructor is assumed to tweak the
+     * configuration, the instance itself is of no interest.
+     */
+    public AndroidConfig() {
+        super();
+        
+        try {
+            Logger rootLogger = Logger.getLogger("");
+            rootLogger.addHandler(new AndroidHandler());
+            rootLogger.setLevel(Level.INFO);
+
+            // Turn down logging in Apache libraries.
+            Logger.getLogger("org.apache").setLevel(Level.WARNING);
+        } catch (Exception ex) {
+            ex.printStackTrace();
+        }
+    }    
+}
diff --git a/core/java/com/android/internal/logging/AndroidHandler.java b/core/java/com/android/internal/logging/AndroidHandler.java
new file mode 100644
index 0000000..d9fcf60
--- /dev/null
+++ b/core/java/com/android/internal/logging/AndroidHandler.java
@@ -0,0 +1,179 @@
+/*
+ * 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.internal.logging;
+
+import android.util.Log;
+
+import java.util.logging.*;
+import java.util.Date;
+import java.text.MessageFormat;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+
+/**
+ * Implements a {@link java.util.logging.Logger} handler that writes to the Android log. The
+ * implementation is rather straightforward. The name of the logger serves as
+ * the log tag. Only the log levels need to be converted appropriately. For
+ * this purpose, the following mapping is being used:
+ * 
+ * <table>
+ *   <tr>
+ *     <th>logger level</th>
+ *     <th>Android level</th>
+ *   </tr>
+ *   <tr>
+ *     <td>
+ *       SEVERE
+ *     </td>
+ *     <td>
+ *       ERROR
+ *     </td>
+ *   </tr>
+ *   <tr>
+ *     <td>
+ *       WARNING
+ *     </td>
+ *     <td>
+ *       WARN
+ *     </td>
+ *   </tr>
+ *   <tr>
+ *     <td>
+ *       INFO
+ *     </td>
+ *     <td>
+ *       INFO
+ *     </td>
+ *   </tr>
+ *   <tr>
+ *     <td>
+ *       CONFIG
+ *     </td>
+ *     <td>
+ *       DEBUG
+ *     </td>
+ *   </tr>
+ *   <tr>
+ *     <td>
+ *       FINE, FINER, FINEST
+ *     </td>
+ *     <td>
+ *       VERBOSE
+ *     </td>
+ *   </tr>
+ * </table>
+ */
+public class AndroidHandler extends Handler {
+    /**
+     * Holds the formatter for all Android log handlers.
+     */
+    private static final Formatter THE_FORMATTER = new Formatter() {
+        @Override
+        public String format(LogRecord r) {
+            Throwable thrown = r.getThrown();
+            if (thrown != null) {
+                StringWriter sw = new StringWriter();
+                PrintWriter pw = new PrintWriter(sw);
+                sw.write(r.getMessage());
+                sw.write("\n");
+                thrown.printStackTrace(pw);
+                pw.flush();
+                return sw.toString();
+            } else {
+                return r.getMessage();
+            }
+        }
+    };
+
+    /**
+     * Constructs a new instance of the Android log handler.
+     */
+    public AndroidHandler() {
+        setFormatter(THE_FORMATTER);
+    }
+
+    @Override
+    public void close() {
+        // No need to close, but must implement abstract method.
+    }
+
+    @Override
+    public void flush() {
+        // No need to flush, but must implement abstract method.
+    }
+
+    @Override
+    public void publish(LogRecord record) {
+        try {
+            int level = getAndroidLevel(record.getLevel());
+            String tag = record.getLoggerName();
+
+            if (tag == null) {
+                // Anonymous logger.
+                tag = "null";    
+            } else {
+                // Tags must be <= 23 characters.
+                int length = tag.length();
+                if (length > 23) {
+                    // Most loggers use the full class name. Try dropping the
+                    // package.
+                    int lastPeriod = tag.lastIndexOf(".");
+                    if (length - lastPeriod - 1 <= 23) {
+                        tag = tag.substring(lastPeriod + 1);
+                    } else {
+                        // Use last 23 chars.
+                        tag = tag.substring(tag.length() - 23);
+                    }
+                }
+            }
+
+            if (!Log.isLoggable(tag, level)) {
+                return;
+            }
+
+            String message = getFormatter().format(record);
+            Log.println(level, tag, message);
+        } catch (RuntimeException e) {
+            Log.e("AndroidHandler", "Error logging message.", e);
+        }
+    }
+    
+    /**
+     * Converts a {@link java.util.logging.Logger} logging level into an Android one.
+     * 
+     * @param level The {@link java.util.logging.Logger} logging level.
+     * 
+     * @return The resulting Android logging level. 
+     */
+    static int getAndroidLevel(Level level)
+    {
+        int value = level.intValue();
+        
+        if (value >= Level.SEVERE.intValue()) {
+            return Log.ERROR;
+        } else if (value >= Level.WARNING.intValue()) {
+            return Log.WARN;
+        } else if (value >= Level.INFO.intValue()) {
+            return Log.INFO;
+        } else if (value >= Level.CONFIG.intValue()) {
+            return Log.DEBUG;
+        }  else {
+            return Log.VERBOSE;
+        }
+    }
+    
+}
diff --git a/core/java/com/android/internal/net/DbSSLSessionCache.java b/core/java/com/android/internal/net/DbSSLSessionCache.java
new file mode 100644
index 0000000..842d40b
--- /dev/null
+++ b/core/java/com/android/internal/net/DbSSLSessionCache.java
@@ -0,0 +1,289 @@
+// Copyright 2009 The Android Open Source Project
+
+package com.android.internal.net;
+
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.Cursor;
+import android.database.SQLException;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteOpenHelper;
+import android.util.Log;
+
+import org.apache.commons.codec.binary.Base64;
+import org.apache.harmony.xnet.provider.jsse.SSLClientSessionCache;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.net.ssl.SSLSession;
+
+/**
+ * Hook into harmony SSL cache to persist the SSL sessions.
+ * 
+ * Current implementation is suitable for saving a small number of hosts -
+ * like google services. It can be extended with expiration and more features
+ * to support more hosts.  
+ * 
+ * {@hide}
+ */
+public class DbSSLSessionCache implements SSLClientSessionCache {
+    private static final String TAG = "DbSSLSessionCache";
+
+    /** 
+     * Table where sessions are stored. 
+     */
+    public static final String SSL_CACHE_TABLE = "ssl_sessions";
+
+    private static final String SSL_CACHE_ID = "_id";
+    
+    /**
+     * Key is host:port - port is not optional.
+     */
+    private static final String SSL_CACHE_HOSTPORT = "hostport";
+    
+    /**
+     * Base64-encoded DER value of the session.
+     */
+    private static final String SSL_CACHE_SESSION = "session";
+    
+    /**
+     * Time when the record was added - should be close to the time 
+     * of the initial session negotiation.
+     */
+    private static final String SSL_CACHE_TIME_SEC = "time_sec";
+
+    public static final String DATABASE_NAME = "ssl_sessions.db";
+
+    public static final int DATABASE_VERSION = 2;
+    
+    /** public for testing 
+     */
+    public static final int SSL_CACHE_ID_COL = 0;
+    public static final int SSL_CACHE_HOSTPORT_COL = 1;
+    public static final int SSL_CACHE_SESSION_COL = 2;
+    public static final int SSL_CACHE_TIME_SEC_COL = 3;
+    
+    public static final int MAX_CACHE_SIZE = 256;
+    
+    private final Map<String, byte[]> mExternalCache =
+        new HashMap<String, byte[]>();
+        
+        
+    private DatabaseHelper mDatabaseHelper;
+
+    private boolean mNeedsCacheLoad = true;
+    
+    public static final String[] PROJECTION = new String[] {
+      SSL_CACHE_ID, 
+      SSL_CACHE_HOSTPORT,
+      SSL_CACHE_SESSION,
+      SSL_CACHE_TIME_SEC
+    };
+
+    private static final Map<String,DbSSLSessionCache> sInstances =
+            new HashMap<String,DbSSLSessionCache>();
+
+    /**
+     * Returns a singleton instance of the DbSSLSessionCache that should be used for this
+     * context's package.
+     *
+     * @param context The context that should be used for getting/creating the singleton instance.
+     * @return The singleton instance for the context's package.
+     */
+    public static synchronized DbSSLSessionCache getInstanceForPackage(Context context) {
+        String packageName = context.getPackageName();
+        if (sInstances.containsKey(packageName)) {
+            return sInstances.get(packageName);
+        }
+        DbSSLSessionCache cache = new DbSSLSessionCache(context);
+        sInstances.put(packageName, cache);
+        return cache;
+    }
+    
+    /**
+     * Create a SslSessionCache instance, using the specified context to 
+     * initialize the database.
+     * 
+     * This constructor will use the default database - created for the application
+     * context.
+     * 
+     * @param activityContext
+     */
+    private DbSSLSessionCache(Context activityContext) {
+        Context appContext = activityContext.getApplicationContext();
+        mDatabaseHelper = new DatabaseHelper(appContext);
+    }
+    
+    /**
+     * Create a SslSessionCache that uses a specific database.
+     * 
+     * 
+     * @param database
+     */
+    public DbSSLSessionCache(DatabaseHelper database) {
+        this.mDatabaseHelper = database;
+    }
+    
+    public void putSessionData(SSLSession session, byte[] der) {
+        if (mDatabaseHelper == null) {
+            return;
+        }
+        synchronized (this.getClass()) {
+            SQLiteDatabase db = mDatabaseHelper.getWritableDatabase();
+            if (mExternalCache.size() == MAX_CACHE_SIZE) {
+                // remove oldest.
+                // TODO: check if the new one is in cached already ( i.e. update ).
+                Cursor byTime = mDatabaseHelper.getReadableDatabase().query(SSL_CACHE_TABLE, 
+                        PROJECTION, null, null, null, null, SSL_CACHE_TIME_SEC);
+                if (byTime.moveToFirst()) {
+                    // TODO: can I do byTime.deleteRow() ? 
+                    String hostPort = byTime.getString(SSL_CACHE_HOSTPORT_COL);
+                    db.delete(SSL_CACHE_TABLE, 
+                            SSL_CACHE_HOSTPORT + "= ?" , new String[] { hostPort });
+                    mExternalCache.remove(hostPort);
+                } else {
+                    Log.w(TAG, "No rows found");
+                    // something is wrong, clear it
+                    clear();
+                }
+            }
+            // Serialize native session to standard DER encoding    
+            long t0 = System.currentTimeMillis();
+
+            String b64 = new String(Base64.encodeBase64(der));
+            String key = session.getPeerHost() + ":" + session.getPeerPort();
+
+            ContentValues values = new ContentValues();
+            values.put(SSL_CACHE_HOSTPORT, key);
+            values.put(SSL_CACHE_SESSION, b64);
+            values.put(SSL_CACHE_TIME_SEC, System.currentTimeMillis() / 1000);
+
+            mExternalCache.put(key, der);
+
+            try {
+                db.insert(SSL_CACHE_TABLE, null /*nullColumnHack */ , values);
+            } catch(SQLException ex) {
+                // Ignore - nothing we can do to recover, and caller shouldn't 
+                // be affected.
+                Log.w(TAG, "Ignoring SQL exception when caching session", ex);
+            }
+            if (Log.isLoggable(TAG, Log.DEBUG)) {
+                long t1 = System.currentTimeMillis();
+                Log.d(TAG, "New SSL session " + session.getPeerHost() +
+                        " DER len: " + der.length + " " + (t1 - t0));
+            }
+        }
+
+    }
+
+    public byte[] getSessionData(String host, int port) {
+        // Current (simple) implementation does a single lookup to DB, then saves 
+        // all entries to the cache. 
+
+        // This works for google services - i.e. small number of certs.
+        // If we extend this to all processes - we should hold a separate cache 
+        // or do lookups to DB each time. 
+        if (mDatabaseHelper == null) {
+            return null;
+        }
+        synchronized(this.getClass()) {
+            if (mNeedsCacheLoad) {
+                // Don't try to load again, if something is wrong on the first 
+                // request it'll likely be wrong each time.
+                mNeedsCacheLoad = false;
+                long t0 = System.currentTimeMillis();
+
+                Cursor cur = null;
+                try {
+                    cur = mDatabaseHelper.getReadableDatabase().query(SSL_CACHE_TABLE, 
+                            PROJECTION, null, null, null, null, null);
+                    if (cur.moveToFirst()) {
+                        do {
+                            String hostPort = cur.getString(SSL_CACHE_HOSTPORT_COL);
+                            String value = cur.getString(SSL_CACHE_SESSION_COL);
+
+                            if (hostPort == null || value == null) {
+                                continue;
+                            }
+                            // TODO: blob support ?
+                            byte[] der = Base64.decodeBase64(value.getBytes());
+                            mExternalCache.put(hostPort, der);
+                        } while (cur.moveToNext());
+
+                    }
+                } catch (SQLException ex) {
+                    Log.d(TAG, "Error loading SSL cached entries ", ex);
+                } finally {
+                    if (cur != null) {
+                        cur.close();
+                    }
+                    if (Log.isLoggable(TAG, Log.DEBUG)) {
+                        long t1 = System.currentTimeMillis();
+                        Log.d(TAG, "LOADED CACHED SSL " + (t1 - t0) + " ms");
+                    }
+                }
+            }
+
+            String key = host + ":" + port;
+
+            return mExternalCache.get(key);
+        }
+    }
+
+    /** 
+     * Reset the database and internal state. 
+     * Used for testing or to free space.
+     */
+    public void clear() {
+        synchronized(this) {
+            try {
+                mExternalCache.clear();
+                mNeedsCacheLoad = true;
+                mDatabaseHelper.getWritableDatabase().delete(SSL_CACHE_TABLE, 
+                        null, null);
+            } catch (SQLException ex) {
+                Log.d(TAG, "Error removing SSL cached entries ", ex);
+                // ignore - nothing we can do about it
+            }
+        } 
+    }
+
+    public byte[] getSessionData(byte[] id) {
+        // We support client side only - the cache will do nothing for 
+        // server-side sessions. 
+        return null;
+    }
+
+    /** Visible for testing. 
+     */
+    public static class DatabaseHelper extends SQLiteOpenHelper {
+
+        public DatabaseHelper(Context context) {
+            super(context, DATABASE_NAME, null /* factory */, DATABASE_VERSION);
+        }   
+
+        @Override
+        public void onCreate(SQLiteDatabase db) {
+            db.execSQL("CREATE TABLE " + SSL_CACHE_TABLE + " (" +
+                    SSL_CACHE_ID + " INTEGER PRIMARY KEY AUTOINCREMENT," +
+                    SSL_CACHE_HOSTPORT + " TEXT UNIQUE ON CONFLICT REPLACE," +
+                    SSL_CACHE_SESSION + " TEXT," +
+                    SSL_CACHE_TIME_SEC + " INTEGER" +
+            ");");
+            
+            // No index - we load on startup, index would slow down inserts.
+            // If we want to scale this to lots of rows - we could use 
+            // index, but then we'll hit DB a bit too often ( including 
+            // negative hits )
+        }
+
+        @Override
+        public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
+            db.execSQL("DROP TABLE IF EXISTS " + SSL_CACHE_TABLE );
+            onCreate(db);
+        }
+
+    }
+    
+}
diff --git a/core/java/com/android/internal/os/AndroidPrintStream.java b/core/java/com/android/internal/os/AndroidPrintStream.java
new file mode 100644
index 0000000..7f4807a
--- /dev/null
+++ b/core/java/com/android/internal/os/AndroidPrintStream.java
@@ -0,0 +1,49 @@
+/*
+ * 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.internal.os;
+
+import android.util.Log;
+
+/**
+ * Print stream which log lines using Android's logging system.
+ *
+ * {@hide}
+ */
+class AndroidPrintStream extends LoggingPrintStream {
+
+    private final int priority;
+    private final String tag;
+
+    /**
+     * Constructs a new logging print stream.
+     *
+     * @param priority from {@link android.util.Log}
+     * @param tag to log
+     */
+    public AndroidPrintStream(int priority, String tag) {
+        if (tag == null) {
+            throw new NullPointerException("tag");
+        }
+
+        this.priority = priority;
+        this.tag = tag;
+    }
+
+    protected void log(String line) {
+        Log.println(priority, tag, line);
+    }
+}
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.aidl b/core/java/com/android/internal/os/BatteryStatsImpl.aidl
new file mode 100644
index 0000000..0c26a3c
--- /dev/null
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.aidl
@@ -0,0 +1,19 @@
+/*
+ * 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.internal.os;
+
+parcelable BatteryStatsImpl;
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
new file mode 100644
index 0000000..7eea8b7
--- /dev/null
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -0,0 +1,2076 @@
+/*
+ * Copyright (C) 2006-2007 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.internal.os;
+
+import android.os.BatteryStats;
+import android.os.NetStat;
+import android.os.Parcel;
+import android.os.ParcelFormatException;
+import android.os.Parcelable;
+import android.os.SystemClock;
+import android.util.Log;
+import android.util.Printer;
+import android.util.SparseArray;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * All information we are collecting about things that can happen that impact
+ * battery life.  All times are represented in microseconds except where indicated
+ * otherwise.
+ */
+public final class BatteryStatsImpl extends BatteryStats {
+    private static final String TAG = "BatteryStatsImpl";
+    private static final boolean DEBUG = false;
+
+    // In-memory Parcel magic number, used to detect attempts to unmarshall bad data
+    private static final int MAGIC = 0xBA757475; // 'BATSTATS' 
+
+    // Current on-disk Parcel version
+    private static final int VERSION = 25;
+
+    private final File mFile;
+    private final File mBackupFile;
+
+    /**
+     * The statistics we have collected organized by uids.
+     */
+    final SparseArray<BatteryStatsImpl.Uid> mUidStats =
+        new SparseArray<BatteryStatsImpl.Uid>();
+
+    // A set of pools of currently active timers.  When a timer is queried, we will divide the
+    // elapsed time by the number of active timers to arrive at that timer's share of the time.
+    // In order to do this, we must refresh each timer whenever the number of active timers
+    // changes.
+    final ArrayList<Timer> mPartialTimers = new ArrayList<Timer>();
+    final ArrayList<Timer> mFullTimers = new ArrayList<Timer>();
+    final ArrayList<Timer> mWindowTimers = new ArrayList<Timer>();
+    final SparseArray<ArrayList<Timer>> mSensorTimers
+            = new SparseArray<ArrayList<Timer>>();
+
+    // These are the objects that will want to do something when the device
+    // is unplugged from power.
+    final ArrayList<Unpluggable> mUnpluggables = new ArrayList<Unpluggable>();
+    
+    int mStartCount;
+
+    long mBatteryUptime;
+    long mBatteryLastUptime;
+    long mBatteryRealtime;
+    long mBatteryLastRealtime;
+
+    long mUptime;
+    long mUptimeStart;
+    long mLastUptime;
+    long mRealtime;
+    long mRealtimeStart;
+    long mLastRealtime;
+    
+    boolean mScreenOn;
+    Timer mScreenOnTimer;
+    
+    boolean mPhoneOn;
+    Timer mPhoneOnTimer;
+    
+    /**
+     * These provide time bases that discount the time the device is plugged
+     * in to power.
+     */
+    boolean mOnBattery;
+    boolean mOnBatteryInternal;
+    long mTrackBatteryPastUptime;
+    long mTrackBatteryUptimeStart;
+    long mTrackBatteryPastRealtime;
+    long mTrackBatteryRealtimeStart;
+    
+    long mUnpluggedBatteryUptime;
+    long mUnpluggedBatteryRealtime;
+    
+    long mLastWriteTime = 0; // Milliseconds
+
+    // For debugging
+    public BatteryStatsImpl() {
+        mFile = mBackupFile = null;
+    }
+
+    public static interface Unpluggable {
+        void unplug(long batteryUptime, long batteryRealtime);
+        void plug(long batteryUptime, long batteryRealtime);
+    }
+    
+    /**
+     * State for keeping track of timing information.
+     */
+    public static final class Timer extends BatteryStats.Timer implements Unpluggable {
+        final int mType;
+        final ArrayList<Timer> mTimerPool;
+        
+        int mNesting;
+        
+        int mCount;
+        int mLoadedCount;
+        int mLastCount;
+        int mUnpluggedCount;
+        
+        // Times are in microseconds for better accuracy when dividing by the
+        // lock count, and are in "battery realtime" units.
+        
+        /**
+         * The total time we have accumulated since the start of the original
+         * boot, to the last time something interesting happened in the
+         * current run.
+         */
+        long mTotalTime;
+        
+        /**
+         * The total time we loaded for the previous runs.  Subtract this from
+         * mTotalTime to find the time for the current run of the system.
+         */
+        long mLoadedTime;
+        
+        /**
+         * The run time of the last run of the system, as loaded from the
+         * saved data.
+         */
+        long mLastTime;
+        
+        /**
+         * The value of mTotalTime when unplug() was last called.  Subtract
+         * this from mTotalTime to find the time since the last unplug from
+         * power.
+         */
+        long mUnpluggedTime;
+
+        /**
+         * The last time at which we updated the timer.  If mNesting is > 0,
+         * subtract this from the current battery time to find the amount of
+         * time we have been running since we last computed an update.
+         */
+        long mUpdateTime;
+        
+        /**
+         * The total time at which the timer was acquired, to determine if
+         * was actually held for an interesting duration.
+         */
+        long mAcquireTime;
+        
+        Timer(int type, ArrayList<Timer> timerPool,
+                ArrayList<Unpluggable> unpluggables, Parcel in) {
+            mType = type;
+            mTimerPool = timerPool;
+            mCount = in.readInt();
+            mLoadedCount = in.readInt();
+            mLastCount = in.readInt();
+            mUnpluggedCount = in.readInt();
+            mTotalTime = in.readLong();
+            mLoadedTime = in.readLong();
+            mLastTime = in.readLong();
+            mUpdateTime = in.readLong();
+            mUnpluggedTime = in.readLong();
+            unpluggables.add(this);
+        }
+
+        Timer(int type, ArrayList<Timer> timerPool,
+                ArrayList<Unpluggable> unpluggables) {
+            mType = type;
+            mTimerPool = timerPool;
+            unpluggables.add(this);
+        }
+        
+        public void writeToParcel(Parcel out, long batteryRealtime) {
+            out.writeInt(mCount);
+            out.writeInt(mLoadedCount);
+            out.writeInt(mLastCount);
+            out.writeInt(mUnpluggedCount);
+            out.writeLong(computeRunTimeLocked(batteryRealtime));
+            out.writeLong(mLoadedTime);
+            out.writeLong(mLastTime);
+            out.writeLong(mUpdateTime);
+            out.writeLong(mUnpluggedTime);
+        }
+
+        public void unplug(long batteryUptime, long batteryRealtime) {
+            if (DEBUG && mType < 0) {
+                Log.v(TAG, "unplug #" + mType + ": realtime=" + batteryRealtime
+                        + " old mUnpluggedTime=" + mUnpluggedTime
+                        + " old mUnpluggedCount=" + mUnpluggedCount);
+            }
+            mUnpluggedTime = computeRunTimeLocked(batteryRealtime);
+            mUnpluggedCount = mCount;
+            if (DEBUG && mType < 0) {
+                Log.v(TAG, "unplug #" + mType
+                        + ": new mUnpluggedTime=" + mUnpluggedTime
+                        + " new mUnpluggedCount=" + mUnpluggedCount);
+            }
+        }
+
+        public void plug(long batteryUptime, long batteryRealtime) {
+            if (mNesting > 0) {
+                if (DEBUG && mType < 0) {
+                    Log.v(TAG, "plug #" + mType + ": realtime=" + batteryRealtime
+                            + " old mTotalTime=" + mTotalTime
+                            + " old mUpdateTime=" + mUpdateTime);
+                }
+                mTotalTime = computeRunTimeLocked(batteryRealtime);
+                mUpdateTime = batteryRealtime;
+                if (DEBUG && mType < 0) {
+                    Log.v(TAG, "plug #" + mType
+                            + ": new mTotalTime=" + mTotalTime
+                            + " old mUpdateTime=" + mUpdateTime);
+                }
+            }
+        }
+        
+        /**
+         * Writes a possibly null Timer to a Parcel.
+         *
+         * @param out the Parcel to be written to.
+         * @param timer a Timer, or null.
+         */
+        public static void writeTimerToParcel(Parcel out, Timer timer,
+                long batteryRealtime) {
+            if (timer == null) {
+                out.writeInt(0); // indicates null
+                return;
+            }
+            out.writeInt(1); // indicates non-null
+
+            timer.writeToParcel(out, batteryRealtime);
+        }
+
+        @Override
+        public long getTotalTime(long batteryRealtime, int which) {
+            long val;
+            if (which == STATS_LAST) {
+                val = mLastTime;
+            } else {
+                val = computeRunTimeLocked(batteryRealtime);
+                if (which == STATS_UNPLUGGED) {
+                    val -= mUnpluggedTime;
+                } else if (which != STATS_TOTAL) {
+                    val -= mLoadedTime;
+                }
+            }
+
+            return val;
+        }
+
+        @Override
+        public int getCount(int which) {
+            int val;
+            if (which == STATS_LAST) {
+                val = mLastCount;
+            } else {
+                val = mCount;
+                if (which == STATS_UNPLUGGED) {
+                    val -= mUnpluggedCount;
+                } else if (which != STATS_TOTAL) {
+                    val -= mLoadedCount;
+                }
+            }
+
+            return val;
+        }
+
+        public void logState() {
+            Log.i("foo", "mNesting=" + mNesting + " mCount=" + mCount
+                    + " mLoadedCount=" + mLoadedCount + " mLastCount=" + mLastCount
+                    + " mUnpluggedCount=" + mUnpluggedCount);
+            Log.i("foo", "mTotalTime=" + mTotalTime
+                    + " mLoadedTime=" + mLoadedTime);
+            Log.i("foo", "mLastTime=" + mLastTime
+                    + " mUnpluggedTime=" + mUnpluggedTime);
+            Log.i("foo", "mUpdateTime=" + mUpdateTime
+                    + " mAcquireTime=" + mAcquireTime);
+        }
+        
+        void startRunningLocked(BatteryStatsImpl stats) {
+            if (mNesting++ == 0) {
+                mUpdateTime = stats.getBatteryRealtimeLocked(
+                        SystemClock.elapsedRealtime() * 1000);
+                if (mTimerPool != null) {
+                    // Accumulate time to all currently active timers before adding
+                    // this new one to the pool.
+                    refreshTimersLocked(stats, mTimerPool);
+                    // Add this timer to the active pool
+                    mTimerPool.add(this);
+                }
+                // Increment the count
+                mCount++;
+                mAcquireTime = mTotalTime;
+                if (DEBUG && mType < 0) {
+                    Log.v(TAG, "start #" + mType + ": mUpdateTime=" + mUpdateTime
+                            + " mTotalTime=" + mTotalTime + " mCount=" + mCount
+                            + " mAcquireTime=" + mAcquireTime);
+                }
+            }
+        }
+
+        void stopRunningLocked(BatteryStatsImpl stats) {
+            // Ignore attempt to stop a timer that isn't running
+            if (mNesting == 0) {
+                return;
+            }
+            if (--mNesting == 0) {
+                if (mTimerPool != null) {
+                    // Accumulate time to all active counters, scaled by the total
+                    // active in the pool, before taking this one out of the pool.
+                    refreshTimersLocked(stats, mTimerPool);
+                    // Remove this timer from the active pool
+                    mTimerPool.remove(this);
+                } else {
+                    final long realtime = SystemClock.elapsedRealtime() * 1000; 
+                    final long batteryRealtime = stats.getBatteryRealtimeLocked(realtime);
+                    mNesting = 1;
+                    mTotalTime = computeRunTimeLocked(batteryRealtime);
+                    mNesting = 0;
+                }
+                
+                if (DEBUG && mType < 0) {
+                    Log.v(TAG, "stop #" + mType + ": mUpdateTime=" + mUpdateTime
+                            + " mTotalTime=" + mTotalTime + " mCount=" + mCount
+                            + " mAcquireTime=" + mAcquireTime);
+                }
+                
+                if (mTotalTime == mAcquireTime) {
+                    // If there was no change in the time, then discard this
+                    // count.  A somewhat cheezy strategy, but hey.
+                    mCount--;
+                }
+            }
+        }
+
+        // Update the total time for all other running Timers with the same type as this Timer
+        // due to a change in timer count
+        private static void refreshTimersLocked(final BatteryStatsImpl stats,
+                final ArrayList<Timer> pool) {
+            final long realtime = SystemClock.elapsedRealtime() * 1000; 
+            final long batteryRealtime = stats.getBatteryRealtimeLocked(realtime);
+            final int N = pool.size();
+            for (int i=N-1; i>= 0; i--) {
+                final Timer t = pool.get(i);
+                long heldTime = batteryRealtime - t.mUpdateTime;
+                if (heldTime > 0) {
+                    t.mTotalTime += heldTime / N;
+                }
+                t.mUpdateTime = batteryRealtime;
+            }
+        }
+
+        private long computeRunTimeLocked(long curBatteryRealtime) {
+            return mTotalTime + (mNesting > 0
+                    ? (curBatteryRealtime - mUpdateTime)
+                            / (mTimerPool != null ? mTimerPool.size() : 1)
+                    : 0);
+        }
+
+        void writeSummaryFromParcelLocked(Parcel out, long batteryRealtime) {
+            long runTime = computeRunTimeLocked(batteryRealtime);
+            // Divide by 1000 for backwards compatibility
+            out.writeLong((runTime + 500) / 1000);
+            out.writeLong(((runTime - mLoadedTime) + 500) / 1000);
+            out.writeInt(mCount);
+            out.writeInt(mCount - mLoadedCount);
+        }
+
+        void readSummaryFromParcelLocked(Parcel in) {
+            // Multiply by 1000 for backwards compatibility
+            mTotalTime = mLoadedTime = in.readLong() * 1000;
+            mLastTime = in.readLong() * 1000;
+            mUnpluggedTime = mTotalTime;
+            mCount = mLoadedCount = in.readInt();
+            mLastCount = in.readInt();
+            mUnpluggedCount = mCount;
+            mNesting = 0;
+        }
+    }
+    
+    public void doUnplug(long batteryUptime, long batteryRealtime) {
+        for (int iu = mUidStats.size() - 1; iu >= 0; iu--) {
+            Uid u = mUidStats.valueAt(iu);
+            u.mStartedTcpBytesReceived = NetStat.getUidRxBytes(u.mUid);
+            u.mStartedTcpBytesSent = NetStat.getUidTxBytes(u.mUid);
+            u.mTcpBytesReceivedAtLastUnplug = u.mCurrentTcpBytesReceived;
+            u.mTcpBytesSentAtLastUnplug = u.mCurrentTcpBytesSent;
+        }
+        for (int i = mUnpluggables.size() - 1; i >= 0; i--) {
+            mUnpluggables.get(i).unplug(batteryUptime, batteryRealtime);
+        }
+    }
+    
+    public void doPlug(long batteryUptime, long batteryRealtime) {
+        for (int iu = mUidStats.size() - 1; iu >= 0; iu--) {
+            Uid u = mUidStats.valueAt(iu);
+            if (u.mStartedTcpBytesReceived >= 0) {
+                u.mCurrentTcpBytesReceived = u.computeCurrentTcpBytesReceived();
+                u.mStartedTcpBytesReceived = -1;
+            }
+            if (u.mStartedTcpBytesSent >= 0) {
+                u.mCurrentTcpBytesSent = u.computeCurrentTcpBytesSent();
+                u.mStartedTcpBytesSent = -1;
+            }
+        }
+        for (int i = mUnpluggables.size() - 1; i >= 0; i--) {
+            mUnpluggables.get(i).plug(batteryUptime, batteryRealtime);
+        }
+    }
+    
+    public void noteStartGps(int uid) {
+        mUidStats.get(uid).noteStartGps();
+    }
+    
+    public void noteStopGps(int uid) {
+        mUidStats.get(uid).noteStopGps();
+    }
+    
+    public void noteScreenOnLocked() {
+        if (!mScreenOn) {
+            mScreenOn = true;
+            mScreenOnTimer.startRunningLocked(this);
+        }
+    }
+    
+    public void noteScreenOffLocked() {
+        if (mScreenOn) {
+            mScreenOn = false;
+            mScreenOnTimer.stopRunningLocked(this);
+        }
+    }
+    
+    public void notePhoneOnLocked() {
+        if (!mPhoneOn) {
+            mPhoneOn = true;
+            mPhoneOnTimer.startRunningLocked(this);
+        }
+    }
+    
+    public void notePhoneOffLocked() {
+        if (mPhoneOn) {
+            mPhoneOn = false;
+            mPhoneOnTimer.stopRunningLocked(this);
+        }
+    }
+    
+    @Override public long getScreenOnTime(long batteryRealtime, int which) {
+        return mScreenOnTimer.getTotalTime(batteryRealtime, which);
+    }
+    
+    @Override public long getPhoneOnTime(long batteryRealtime, int which) {
+        return mPhoneOnTimer.getTotalTime(batteryRealtime, which);
+    }
+    
+    @Override public boolean getIsOnBattery() {
+        return mOnBattery;
+    }
+    
+    @Override public SparseArray<? extends BatteryStats.Uid> getUidStats() {
+        return mUidStats;
+    }
+
+    /**
+     * The statistics associated with a particular uid.
+     */
+    public final class Uid extends BatteryStats.Uid {
+        
+        final int mUid;
+        long mLoadedTcpBytesReceived;
+        long mLoadedTcpBytesSent;
+        long mCurrentTcpBytesReceived;
+        long mCurrentTcpBytesSent;
+        long mTcpBytesReceivedAtLastUnplug;
+        long mTcpBytesSentAtLastUnplug;
+        
+        // These are not saved/restored when parcelling, since we want
+        // to return from the parcel with a snapshot of the state.
+        long mStartedTcpBytesReceived = -1;
+        long mStartedTcpBytesSent = -1;
+        
+        /**
+         * The statistics we have collected for this uid's wake locks.
+         */
+        final HashMap<String, Wakelock> mWakelockStats = new HashMap<String, Wakelock>();
+
+        /**
+         * The statistics we have collected for this uid's sensor activations.
+         */
+        final HashMap<Integer, Sensor> mSensorStats = new HashMap<Integer, Sensor>();
+
+        /**
+         * The statistics we have collected for this uid's processes.
+         */
+        final HashMap<String, Proc> mProcessStats = new HashMap<String, Proc>();
+
+        /**
+         * The statistics we have collected for this uid's processes.
+         */
+        final HashMap<String, Pkg> mPackageStats = new HashMap<String, Pkg>();
+        
+        public Uid(int uid) {
+            mUid = uid;
+        }
+
+        @Override
+        public Map<String, ? extends BatteryStats.Uid.Wakelock> getWakelockStats() {
+            return mWakelockStats;
+        }
+
+        @Override
+        public Map<Integer, ? extends BatteryStats.Uid.Sensor> getSensorStats() {
+            return mSensorStats;
+        }
+
+        @Override
+        public Map<String, ? extends BatteryStats.Uid.Proc> getProcessStats() {
+            return mProcessStats;
+        }
+
+        @Override
+        public Map<String, ? extends BatteryStats.Uid.Pkg> getPackageStats() {
+            return mPackageStats;
+        }
+        
+        public int getUid() {
+            return mUid;
+        }
+        
+        public long getTcpBytesReceived(int which) {
+            if (which == STATS_LAST) {
+                return mLoadedTcpBytesReceived;
+            } else {
+                long current = computeCurrentTcpBytesReceived();
+                if (which == STATS_UNPLUGGED) {
+                    current -= mTcpBytesReceivedAtLastUnplug;
+                } else if (which == STATS_TOTAL) {
+                    current += mLoadedTcpBytesReceived;
+                }
+                return current;
+            }
+        }
+        
+        public long computeCurrentTcpBytesReceived() {
+            return mCurrentTcpBytesReceived + (mStartedTcpBytesReceived >= 0
+                    ? (NetStat.getUidRxBytes(mUid) - mStartedTcpBytesReceived) : 0);
+        }
+        
+        public long getTcpBytesSent(int which) {
+            if (which == STATS_LAST) {
+                return mLoadedTcpBytesSent;
+            } else {
+                long current = computeCurrentTcpBytesSent();
+                if (which == STATS_UNPLUGGED) {
+                    current -= mTcpBytesSentAtLastUnplug;
+                } else if (which == STATS_TOTAL) {
+                    current += mLoadedTcpBytesSent;
+                }
+                return current;
+            }
+        }
+        
+        public long computeCurrentTcpBytesSent() {
+            return mCurrentTcpBytesSent + (mStartedTcpBytesSent >= 0
+                    ? (NetStat.getUidTxBytes(mUid) - mStartedTcpBytesSent) : 0);
+        }
+        
+        void writeToParcelLocked(Parcel out, long batteryRealtime) {
+            out.writeInt(mWakelockStats.size());
+            for (Map.Entry<String, Uid.Wakelock> wakelockEntry : mWakelockStats.entrySet()) {
+                out.writeString(wakelockEntry.getKey());
+                Uid.Wakelock wakelock = wakelockEntry.getValue();
+                wakelock.writeToParcelLocked(out, batteryRealtime);
+            }
+
+            out.writeInt(mSensorStats.size());
+            for (Map.Entry<Integer, Uid.Sensor> sensorEntry : mSensorStats.entrySet()) {
+                out.writeInt(sensorEntry.getKey());
+                Uid.Sensor sensor = sensorEntry.getValue();
+                sensor.writeToParcelLocked(out, batteryRealtime);
+            }
+
+            out.writeInt(mProcessStats.size());
+            for (Map.Entry<String, Uid.Proc> procEntry : mProcessStats.entrySet()) {
+                out.writeString(procEntry.getKey());
+                Uid.Proc proc = procEntry.getValue();
+                proc.writeToParcelLocked(out);
+            }
+
+            out.writeInt(mPackageStats.size());
+            for (Map.Entry<String, Uid.Pkg> pkgEntry : mPackageStats.entrySet()) {
+                out.writeString(pkgEntry.getKey());
+                Uid.Pkg pkg = pkgEntry.getValue();
+                pkg.writeToParcelLocked(out);
+            }
+            
+            out.writeLong(mLoadedTcpBytesReceived);
+            out.writeLong(mLoadedTcpBytesSent);
+            out.writeLong(computeCurrentTcpBytesReceived());
+            out.writeLong(computeCurrentTcpBytesSent());
+            out.writeLong(mTcpBytesReceivedAtLastUnplug);
+            out.writeLong(mTcpBytesSentAtLastUnplug);
+        }
+
+        void readFromParcelLocked(ArrayList<Unpluggable> unpluggables, Parcel in) {
+            int numWakelocks = in.readInt();
+            mWakelockStats.clear();
+            for (int j = 0; j < numWakelocks; j++) {
+                String wakelockName = in.readString();
+                Uid.Wakelock wakelock = new Wakelock();
+                wakelock.readFromParcelLocked(unpluggables, in);
+                mWakelockStats.put(wakelockName, wakelock);
+            }
+
+            int numSensors = in.readInt();
+            mSensorStats.clear();
+            for (int k = 0; k < numSensors; k++) {
+                int sensorNumber = in.readInt();
+                Uid.Sensor sensor = new Sensor(sensorNumber);
+                sensor.readFromParcelLocked(mUnpluggables, in);
+                mSensorStats.put(sensorNumber, sensor);
+            }
+
+            int numProcs = in.readInt();
+            mProcessStats.clear();
+            for (int k = 0; k < numProcs; k++) {
+                String processName = in.readString();
+                Uid.Proc proc = new Proc();
+                proc.readFromParcelLocked(in);
+                mProcessStats.put(processName, proc);
+            }
+
+            int numPkgs = in.readInt();
+            mPackageStats.clear();
+            for (int l = 0; l < numPkgs; l++) {
+                String packageName = in.readString();
+                Uid.Pkg pkg = new Pkg();
+                pkg.readFromParcelLocked(in);
+                mPackageStats.put(packageName, pkg);
+            }
+            
+            mLoadedTcpBytesReceived = in.readLong();
+            mLoadedTcpBytesSent = in.readLong();
+            mCurrentTcpBytesReceived = in.readLong();
+            mCurrentTcpBytesSent = in.readLong();
+            mTcpBytesReceivedAtLastUnplug = in.readLong();
+            mTcpBytesSentAtLastUnplug = in.readLong();
+        }
+
+        /**
+         * The statistics associated with a particular wake lock.
+         */
+        public final class Wakelock extends BatteryStats.Uid.Wakelock {
+            /**
+             * How long (in ms) this uid has been keeping the device partially awake.
+             */
+            Timer mTimerPartial;
+
+            /**
+             * How long (in ms) this uid has been keeping the device fully awake.
+             */
+            Timer mTimerFull;
+
+            /**
+             * How long (in ms) this uid has had a window keeping the device awake.
+             */
+            Timer mTimerWindow;
+
+            /**
+             * Reads a possibly null Timer from a Parcel.  The timer is associated with the
+             * proper timer pool from the given BatteryStatsImpl object.
+             *
+             * @param in the Parcel to be read from.
+             * return a new Timer, or null.
+             */
+            private Timer readTimerFromParcel(int type, ArrayList<Timer> pool,
+                    ArrayList<Unpluggable> unpluggables, Parcel in) {
+                if (in.readInt() == 0) {
+                    return null;
+                }
+
+                return new Timer(type, pool, unpluggables, in);
+            }
+
+            void readFromParcelLocked(ArrayList<Unpluggable> unpluggables, Parcel in) {
+                mTimerPartial = readTimerFromParcel(WAKE_TYPE_PARTIAL,
+                        mPartialTimers, unpluggables, in);
+                mTimerFull = readTimerFromParcel(WAKE_TYPE_FULL,
+                        mFullTimers, unpluggables, in);
+                mTimerWindow = readTimerFromParcel(WAKE_TYPE_WINDOW,
+                        mWindowTimers, unpluggables, in);
+            }
+
+            void writeToParcelLocked(Parcel out, long batteryRealtime) {
+                Timer.writeTimerToParcel(out, mTimerPartial, batteryRealtime);
+                Timer.writeTimerToParcel(out, mTimerFull, batteryRealtime);
+                Timer.writeTimerToParcel(out, mTimerWindow, batteryRealtime);
+            }
+
+            @Override
+            public Timer getWakeTime(int type) {
+                switch (type) {
+                case WAKE_TYPE_FULL: return mTimerFull;
+                case WAKE_TYPE_PARTIAL: return mTimerPartial;
+                case WAKE_TYPE_WINDOW: return mTimerWindow;
+                default: throw new IllegalArgumentException("type = " + type);
+                }
+            }
+        }
+
+        public final class Sensor extends BatteryStats.Uid.Sensor {
+            final int mHandle;
+            Timer mTimer;
+            
+            public Sensor(int handle) {
+                mHandle = handle;
+            }
+
+            private Timer readTimerFromParcel(ArrayList<Unpluggable> unpluggables,
+                    Parcel in) {
+                if (in.readInt() == 0) {
+                    return null;
+                }
+
+                ArrayList<Timer> pool = mSensorTimers.get(mHandle);
+                if (pool == null) {
+                    pool = new ArrayList<Timer>();
+                    mSensorTimers.put(mHandle, pool);
+                }
+                return new Timer(0, pool, unpluggables, in);
+            }
+
+            void readFromParcelLocked(ArrayList<Unpluggable> unpluggables, Parcel in) {
+                mTimer = readTimerFromParcel(unpluggables, in);
+            }
+
+            void writeToParcelLocked(Parcel out, long batteryRealtime) {
+                Timer.writeTimerToParcel(out, mTimer, batteryRealtime);
+            }
+
+            @Override
+            public Timer getSensorTime() {
+                return mTimer;
+            }
+            
+            public int getHandle() {
+                return mHandle;
+            }
+        }
+
+        /**
+         * The statistics associated with a particular process.
+         */
+        public final class Proc extends BatteryStats.Uid.Proc implements Unpluggable {
+            /**
+             * Total time (in 1/100 sec) spent executing in user code.
+             */
+            long mUserTime;
+
+            /**
+             * Total time (in 1/100 sec) spent executing in kernel code.
+             */
+            long mSystemTime;
+
+            /**
+             * Number of times the process has been started.
+             */
+            int mStarts;
+
+            /**
+             * The amount of user time loaded from a previous save.
+             */
+            long mLoadedUserTime;
+
+            /**
+             * The amount of system time loaded from a previous save.
+             */
+            long mLoadedSystemTime;
+
+            /**
+             * The number of times the process has started from a previous save.
+             */
+            int mLoadedStarts;
+
+            /**
+             * The amount of user time loaded from the previous run.
+             */
+            long mLastUserTime;
+
+            /**
+             * The amount of system time loaded from the previous run.
+             */
+            long mLastSystemTime;
+
+            /**
+             * The number of times the process has started from the previous run.
+             */
+            int mLastStarts;
+
+            /**
+             * The amount of user time when last unplugged.
+             */
+            long mUnpluggedUserTime;
+
+            /**
+             * The amount of system time when last unplugged.
+             */
+            long mUnpluggedSystemTime;
+
+            /**
+             * The number of times the process has started before unplugged.
+             */
+            int mUnpluggedStarts;
+
+            Proc() {
+                mUnpluggables.add(this);
+            }
+            
+            public void unplug(long batteryUptime, long batteryRealtime) {
+                mUnpluggedUserTime = mUserTime;
+                mUnpluggedSystemTime = mSystemTime;
+                mUnpluggedStarts = mStarts;
+            }
+
+            public void plug(long batteryUptime, long batteryRealtime) {
+            }
+            
+            void writeToParcelLocked(Parcel out) {
+                out.writeLong(mUserTime);
+                out.writeLong(mSystemTime);
+                out.writeInt(mStarts);
+                out.writeLong(mLoadedUserTime);
+                out.writeLong(mLoadedSystemTime);
+                out.writeInt(mLoadedStarts);
+                out.writeLong(mLastUserTime);
+                out.writeLong(mLastSystemTime);
+                out.writeInt(mLastStarts);
+                out.writeLong(mUnpluggedUserTime);
+                out.writeLong(mUnpluggedSystemTime);
+                out.writeInt(mUnpluggedStarts);
+            }
+
+            void readFromParcelLocked(Parcel in) {
+                mUserTime = in.readLong();
+                mSystemTime = in.readLong();
+                mStarts = in.readInt();
+                mLoadedUserTime = in.readLong();
+                mLoadedSystemTime = in.readLong();
+                mLoadedStarts = in.readInt();
+                mLastUserTime = in.readLong();
+                mLastSystemTime = in.readLong();
+                mLastStarts = in.readInt();
+                mUnpluggedUserTime = in.readLong();
+                mUnpluggedSystemTime = in.readLong();
+                mUnpluggedStarts = in.readInt();
+            }
+
+            public BatteryStatsImpl getBatteryStats() {
+                return BatteryStatsImpl.this;
+            }
+
+            public void addCpuTimeLocked(int utime, int stime) {
+                mUserTime += utime;
+                mSystemTime += stime;
+            }
+
+            public void incStartsLocked() {
+                mStarts++;
+            }
+
+            @Override
+            public long getUserTime(int which) {
+                long val;
+                if (which == STATS_LAST) {
+                    val = mLastUserTime;
+                } else {
+                    val = mUserTime;
+                    if (which == STATS_CURRENT) {
+                        val -= mLoadedUserTime;
+                    } else if (which == STATS_UNPLUGGED) {
+                        val -= mUnpluggedUserTime;
+                    }
+                }
+                return val;
+            }
+
+            @Override
+            public long getSystemTime(int which) {
+                long val;
+                if (which == STATS_LAST) {
+                    val = mLastSystemTime;
+                } else {
+                    val = mSystemTime;
+                    if (which == STATS_CURRENT) {
+                        val -= mLoadedSystemTime;
+                    } else if (which == STATS_UNPLUGGED) {
+                        val -= mUnpluggedSystemTime;
+                    }
+                }
+                return val;
+            }
+
+            @Override
+            public int getStarts(int which) {
+                int val;
+                if (which == STATS_LAST) {
+                    val = mLastStarts;
+                } else {
+                    val = mStarts;
+                    if (which == STATS_CURRENT) {
+                        val -= mLoadedStarts;
+                    } else if (which == STATS_UNPLUGGED) {
+                        val -= mUnpluggedStarts;
+                    }
+                }
+                return val;
+            }
+        }
+
+        /**
+         * The statistics associated with a particular package.
+         */
+        public final class Pkg extends BatteryStats.Uid.Pkg implements Unpluggable {
+            /**
+             * Number of times this package has done something that could wake up the
+             * device from sleep.
+             */
+            int mWakeups;
+
+            /**
+             * Number of things that could wake up the device loaded from a
+             * previous save.
+             */
+            int mLoadedWakeups;
+
+            /**
+             * Number of things that could wake up the device as of the
+             * last run.
+             */
+            int mLastWakeups;
+
+            /**
+             * Number of things that could wake up the device as of the
+             * last run.
+             */
+            int mUnpluggedWakeups;
+
+            /**
+             * The statics we have collected for this package's services.
+             */
+            final HashMap<String, Serv> mServiceStats = new HashMap<String, Serv>();
+
+            Pkg() {
+                mUnpluggables.add(this);
+            }
+            
+            public void unplug(long batteryUptime, long batteryRealtime) {
+                mUnpluggedWakeups = mWakeups;
+            }
+
+            public void plug(long batteryUptime, long batteryRealtime) {
+            }
+            
+            void readFromParcelLocked(Parcel in) {
+                mWakeups = in.readInt();
+                mLoadedWakeups = in.readInt();
+                mLastWakeups = in.readInt();
+                mUnpluggedWakeups = in.readInt();
+
+                int numServs = in.readInt();
+                mServiceStats.clear();
+                for (int m = 0; m < numServs; m++) {
+                    String serviceName = in.readString();
+                    Uid.Pkg.Serv serv = new Serv();
+                    mServiceStats.put(serviceName, serv);
+
+                    serv.readFromParcelLocked(in);
+                }
+            }
+
+            void writeToParcelLocked(Parcel out) {
+                out.writeInt(mWakeups);
+                out.writeInt(mLoadedWakeups);
+                out.writeInt(mLastWakeups);
+                out.writeInt(mUnpluggedWakeups);
+
+                out.writeInt(mServiceStats.size());
+                for (Map.Entry<String, Uid.Pkg.Serv> servEntry : mServiceStats.entrySet()) {
+                    out.writeString(servEntry.getKey());
+                    Uid.Pkg.Serv serv = servEntry.getValue();
+
+                    serv.writeToParcelLocked(out);
+                }
+            }
+
+            @Override
+            public Map<String, ? extends BatteryStats.Uid.Pkg.Serv> getServiceStats() {
+                return mServiceStats;
+            }
+
+            @Override
+            public int getWakeups(int which) {
+                int val;
+                if (which == STATS_LAST) {
+                    val = mLastWakeups;
+                } else {
+                    val = mWakeups;
+                    if (which == STATS_CURRENT) {
+                        val -= mLoadedWakeups;
+                    } else if (which == STATS_UNPLUGGED) {
+                        val -= mUnpluggedWakeups;
+                    }
+                }
+
+                return val;
+            }
+
+            /**
+             * The statistics associated with a particular service.
+             */
+            public final class Serv extends BatteryStats.Uid.Pkg.Serv implements Unpluggable {
+                /**
+                 * Total time (ms in battery uptime) the service has been left started.
+                 */
+                long mStartTime;
+
+                /**
+                 * If service has been started and not yet stopped, this is
+                 * when it was started.
+                 */
+                long mRunningSince;
+
+                /**
+                 * True if we are currently running.
+                 */
+                boolean mRunning;
+
+                /**
+                 * Total number of times startService() has been called.
+                 */
+                int mStarts;
+
+                /**
+                 * Total time (ms in battery uptime) the service has been left launched.
+                 */
+                long mLaunchedTime;
+
+                /**
+                 * If service has been launched and not yet exited, this is
+                 * when it was launched (ms in battery uptime).
+                 */
+                long mLaunchedSince;
+
+                /**
+                 * True if we are currently launched.
+                 */
+                boolean mLaunched;
+
+                /**
+                 * Total number times the service has been launched.
+                 */
+                int mLaunches;
+
+                /**
+                 * The amount of time spent started loaded from a previous save
+                 * (ms in battery uptime).
+                 */
+                long mLoadedStartTime;
+
+                /**
+                 * The number of starts loaded from a previous save.
+                 */
+                int mLoadedStarts;
+
+                /**
+                 * The number of launches loaded from a previous save.
+                 */
+                int mLoadedLaunches;
+
+                /**
+                 * The amount of time spent started as of the last run (ms
+                 * in battery uptime).
+                 */
+                long mLastStartTime;
+
+                /**
+                 * The number of starts as of the last run.
+                 */
+                int mLastStarts;
+
+                /**
+                 * The number of launches as of the last run.
+                 */
+                int mLastLaunches;
+
+                /**
+                 * The amount of time spent started when last unplugged (ms
+                 * in battery uptime).
+                 */
+                long mUnpluggedStartTime;
+
+                /**
+                 * The number of starts when last unplugged.
+                 */
+                int mUnpluggedStarts;
+
+                /**
+                 * The number of launches when last unplugged.
+                 */
+                int mUnpluggedLaunches;
+
+                Serv() {
+                    mUnpluggables.add(this);
+                }
+                
+                public void unplug(long batteryUptime, long batteryRealtime) {
+                    mUnpluggedStartTime = getStartTimeToNowLocked(batteryUptime);
+                    mUnpluggedStarts = mStarts;
+                    mUnpluggedLaunches = mLaunches;
+                }
+
+                public void plug(long batteryUptime, long batteryRealtime) {
+                }
+                
+                void readFromParcelLocked(Parcel in) {
+                    mStartTime = in.readLong();
+                    mRunningSince = in.readLong();
+                    mRunning = in.readInt() != 0;
+                    mStarts = in.readInt();
+                    mLaunchedTime = in.readLong();
+                    mLaunchedSince = in.readLong();
+                    mLaunched = in.readInt() != 0;
+                    mLaunches = in.readInt();
+                    mLoadedStartTime = in.readLong();
+                    mLoadedStarts = in.readInt();
+                    mLoadedLaunches = in.readInt();
+                    mLastStartTime = in.readLong();
+                    mLastStarts = in.readInt();
+                    mLastLaunches = in.readInt();
+                    mUnpluggedStartTime = in.readLong();
+                    mUnpluggedStarts = in.readInt();
+                    mUnpluggedLaunches = in.readInt();
+                }
+
+                void writeToParcelLocked(Parcel out) {
+                    out.writeLong(mStartTime);
+                    out.writeLong(mRunningSince);
+                    out.writeInt(mRunning ? 1 : 0);
+                    out.writeInt(mStarts);
+                    out.writeLong(mLaunchedTime);
+                    out.writeLong(mLaunchedSince);
+                    out.writeInt(mLaunched ? 1 : 0);
+                    out.writeInt(mLaunches);
+                    out.writeLong(mLoadedStartTime);
+                    out.writeInt(mLoadedStarts);
+                    out.writeInt(mLoadedLaunches);
+                    out.writeLong(mLastStartTime);
+                    out.writeInt(mLastStarts);
+                    out.writeInt(mLastLaunches);
+                    out.writeLong(mUnpluggedStartTime);
+                    out.writeInt(mUnpluggedStarts);
+                    out.writeInt(mUnpluggedLaunches);
+                }
+
+                long getLaunchTimeToNowLocked(long batteryUptime) {
+                    if (!mLaunched) return mLaunchedTime;
+                    return mLaunchedTime + batteryUptime - mLaunchedSince;
+                }
+
+                long getStartTimeToNowLocked(long batteryUptime) {
+                    if (!mRunning) return mStartTime;
+                    return mStartTime + batteryUptime - mRunningSince;
+                }
+
+                public void startLaunchedLocked() {
+                    if (!mLaunched) {
+                        mLaunches++;
+                        mLaunchedSince = getBatteryUptimeLocked();
+                        mLaunched = true;
+                    }
+                }
+
+                public void stopLaunchedLocked() {
+                    if (mLaunched) {
+                        long time = getBatteryUptimeLocked() - mLaunchedSince;
+                        if (time > 0) {
+                            mLaunchedTime += time;
+                        } else {
+                            mLaunches--;
+                        }
+                        mLaunched = false;
+                    }
+                }
+
+                public void startRunningLocked() {
+                    if (!mRunning) {
+                        mStarts++;
+                        mRunningSince = getBatteryUptimeLocked();
+                        mRunning = true;
+                    }
+                }
+
+                public void stopRunningLocked() {
+                    if (mRunning) {
+                        long time = getBatteryUptimeLocked() - mRunningSince;
+                        if (time > 0) {
+                            mStartTime += time;
+                        } else {
+                            mStarts--;
+                        }
+                        mRunning = false;
+                    }
+                }
+
+                public BatteryStatsImpl getBatteryStats() {
+                    return BatteryStatsImpl.this;
+                }
+
+                @Override
+                public int getLaunches(int which) {
+                    int val;
+
+                    if (which == STATS_LAST) {
+                        val = mLastLaunches;
+                    } else {
+                        val = mLaunches;
+                        if (which == STATS_CURRENT) {
+                            val -= mLoadedLaunches;
+                        } else if (which == STATS_UNPLUGGED) {
+                            val -= mUnpluggedLaunches;
+                        }
+                    }
+
+                    return val;
+                }
+
+                @Override
+                public long getStartTime(long now, int which) {
+                    long val;
+                    if (which == STATS_LAST) {
+                        val = mLastStartTime;
+                    } else {
+                        val = getStartTimeToNowLocked(now);
+                        if (which == STATS_CURRENT) {
+                            val -= mLoadedStartTime;
+                        } else if (which == STATS_UNPLUGGED) {
+                            val -= mUnpluggedStartTime;
+                        }
+                    }
+
+                    return val;
+                }
+
+                @Override
+                public int getStarts(int which) {
+                    int val;
+                    if (which == STATS_LAST) {
+                        val = mLastStarts;
+                    } else {
+                        val = mStarts;
+                        if (which == STATS_CURRENT) {
+                            val -= mLoadedStarts;
+                        } else if (which == STATS_UNPLUGGED) {
+                            val -= mUnpluggedStarts;
+                        }
+                    }
+
+                    return val;
+                }
+            }
+
+            public BatteryStatsImpl getBatteryStats() {
+                return BatteryStatsImpl.this;
+            }
+
+            public void incWakeupsLocked() {
+                mWakeups++;
+            }
+
+            final Serv newServiceStatsLocked() {
+                return new Serv();
+            }
+        }
+
+        /**
+         * Retrieve the statistics object for a particular process, creating
+         * if needed.
+         */
+        public Proc getProcessStatsLocked(String name) {
+            Proc ps = mProcessStats.get(name);
+            if (ps == null) {
+                ps = new Proc();
+                mProcessStats.put(name, ps);
+            }
+
+            return ps;
+        }
+
+        /**
+         * Retrieve the statistics object for a particular service, creating
+         * if needed.
+         */
+        public Pkg getPackageStatsLocked(String name) {
+            Pkg ps = mPackageStats.get(name);
+            if (ps == null) {
+                ps = new Pkg();
+                mPackageStats.put(name, ps);
+            }
+
+            return ps;
+        }
+
+        /**
+         * Retrieve the statistics object for a particular service, creating
+         * if needed.
+         */
+        public Pkg.Serv getServiceStatsLocked(String pkg, String serv) {
+            Pkg ps = getPackageStatsLocked(pkg);
+            Pkg.Serv ss = ps.mServiceStats.get(serv);
+            if (ss == null) {
+                ss = ps.newServiceStatsLocked();
+                ps.mServiceStats.put(serv, ss);
+            }
+
+            return ss;
+        }
+
+        public Timer getWakeTimerLocked(String name, int type) {
+            Wakelock wl = mWakelockStats.get(name);
+            if (wl == null) {
+                wl = new Wakelock();
+                mWakelockStats.put(name, wl);
+            }
+            Timer t = null;
+            switch (type) {
+                case WAKE_TYPE_PARTIAL:
+                    t = wl.mTimerPartial;
+                    if (t == null) {
+                        t = new Timer(WAKE_TYPE_PARTIAL, mPartialTimers, mUnpluggables);
+                        wl.mTimerPartial = t;
+                    }
+                    return t;
+                case WAKE_TYPE_FULL:
+                    t = wl.mTimerFull;
+                    if (t == null) {
+                        t = new Timer(WAKE_TYPE_FULL, mFullTimers, mUnpluggables);
+                        wl.mTimerFull = t;
+                    }
+                    return t;
+                case WAKE_TYPE_WINDOW:
+                    t = wl.mTimerWindow;
+                    if (t == null) {
+                        t = new Timer(WAKE_TYPE_WINDOW, mWindowTimers, mUnpluggables);
+                        wl.mTimerWindow = t;
+                    }
+                    return t;
+                default:
+                    throw new IllegalArgumentException("type=" + type);
+            }
+        }
+
+        public Timer getSensorTimerLocked(int sensor, boolean create) {
+            Sensor se = mSensorStats.get(sensor);
+            if (se == null) {
+                if (!create) {
+                    return null;
+                }
+                se = new Sensor(sensor);
+                mSensorStats.put(sensor, se);
+            }
+            Timer t = se.mTimer;
+            if (t != null) {
+                return t;
+            }
+            ArrayList<Timer> timers = mSensorTimers.get(sensor);
+            if (timers == null) {
+                timers = new ArrayList<Timer>();
+                mSensorTimers.put(sensor, timers);
+            }
+            t = new Timer(BatteryStats.SENSOR, timers, mUnpluggables);
+            se.mTimer = t;
+            return t;
+        }
+
+        public void noteStartWakeLocked(String name, int type) {
+            Timer t = getWakeTimerLocked(name, type);
+            if (t != null) {
+                t.startRunningLocked(BatteryStatsImpl.this);
+            }
+        }
+
+        public void noteStopWakeLocked(String name, int type) {
+            Timer t = getWakeTimerLocked(name, type);
+            if (t != null) {
+                t.stopRunningLocked(BatteryStatsImpl.this);
+            }
+        }
+        
+        public void noteStartSensor(int sensor) {
+            Timer t = getSensorTimerLocked(sensor, true);
+            if (t != null) {
+                t.startRunningLocked(BatteryStatsImpl.this);
+            }            
+        }
+
+        public void noteStopSensor(int sensor) {
+            // Don't create a timer if one doesn't already exist
+            Timer t = getSensorTimerLocked(sensor, false);
+            if (t != null) {
+                t.stopRunningLocked(BatteryStatsImpl.this);
+            }            
+        }
+        
+        public void noteStartGps() {
+            Timer t = getSensorTimerLocked(Sensor.GPS, true);
+            if (t != null) {
+                t.startRunningLocked(BatteryStatsImpl.this);
+            }  
+        }
+        
+        public void noteStopGps() {
+            Timer t = getSensorTimerLocked(Sensor.GPS, false);
+            if (t != null) {
+                t.stopRunningLocked(BatteryStatsImpl.this);
+            }  
+        }
+
+        public BatteryStatsImpl getBatteryStats() {
+            return BatteryStatsImpl.this;
+        }
+    }
+
+    public BatteryStatsImpl(String filename) {
+        mFile = new File(filename);
+        mBackupFile = new File(filename + ".bak");
+        mStartCount++;
+        mScreenOnTimer = new Timer(-1, null, mUnpluggables);
+        mPhoneOnTimer = new Timer(-2, null, mUnpluggables);
+        mOnBattery = mOnBatteryInternal = false;
+        mTrackBatteryPastUptime = 0;
+        mTrackBatteryPastRealtime = 0;
+        mUptimeStart = mTrackBatteryUptimeStart = SystemClock.uptimeMillis() * 1000;
+        mRealtimeStart = mTrackBatteryRealtimeStart = SystemClock.elapsedRealtime() * 1000;
+        mUnpluggedBatteryUptime = getBatteryUptimeLocked(mUptimeStart);
+        mUnpluggedBatteryRealtime = getBatteryRealtimeLocked(mRealtimeStart);
+    }
+
+    public BatteryStatsImpl(Parcel p) {
+        mFile = mBackupFile = null;
+        readFromParcel(p);
+    }
+
+    @Override
+    public int getStartCount() {
+        return mStartCount;
+    }
+
+    public boolean isOnBattery() {
+        return mOnBattery;
+    }
+
+    public void setOnBattery(boolean onBattery) {
+        synchronized(this) {
+            if (mOnBattery != onBattery) {
+                mOnBattery = mOnBatteryInternal = onBattery;
+                
+                long uptime = SystemClock.uptimeMillis() * 1000;
+                long mSecRealtime = SystemClock.elapsedRealtime();
+                long realtime = mSecRealtime * 1000;
+                if (onBattery) {
+                    mTrackBatteryUptimeStart = uptime;
+                    mTrackBatteryRealtimeStart = realtime;
+                    mUnpluggedBatteryUptime = getBatteryUptimeLocked(uptime);
+                    mUnpluggedBatteryRealtime = getBatteryRealtimeLocked(realtime);
+                    doUnplug(mUnpluggedBatteryUptime, mUnpluggedBatteryRealtime);
+                } else {
+                    mTrackBatteryPastUptime += uptime - mTrackBatteryUptimeStart;
+                    mTrackBatteryPastRealtime += realtime - mTrackBatteryRealtimeStart;
+                    doPlug(getBatteryUptimeLocked(uptime), getBatteryRealtimeLocked(realtime));
+                }
+                if ((mLastWriteTime + (60 * 1000)) < mSecRealtime) {
+                    if (mFile != null) {
+                        writeLocked();
+                    }
+                }
+            }
+        }
+    }
+
+    public long getAwakeTimeBattery() {
+        return computeBatteryUptime(getBatteryUptimeLocked(), STATS_CURRENT);
+    }
+
+    public long getAwakeTimePlugged() {
+        return (SystemClock.uptimeMillis() * 1000) - getAwakeTimeBattery();
+    }
+
+    @Override
+    public long computeUptime(long curTime, int which) {
+        switch (which) {
+            case STATS_TOTAL: return mUptime + (curTime-mUptimeStart);
+            case STATS_LAST: return mLastUptime;
+            case STATS_CURRENT: return (curTime-mUptimeStart);
+            case STATS_UNPLUGGED: return (curTime-mTrackBatteryUptimeStart);
+        }
+        return 0;
+    }
+
+    @Override
+    public long computeRealtime(long curTime, int which) {
+        switch (which) {
+            case STATS_TOTAL: return mRealtime + (curTime-mRealtimeStart);
+            case STATS_LAST: return mLastRealtime;
+            case STATS_CURRENT: return (curTime-mRealtimeStart);
+            case STATS_UNPLUGGED: return (curTime-mTrackBatteryRealtimeStart);
+        }
+        return 0;
+    }
+
+    @Override
+    public long computeBatteryUptime(long curTime, int which) {
+        switch (which) {
+            case STATS_TOTAL:
+                return mBatteryUptime + getBatteryUptime(curTime);
+            case STATS_LAST:
+                return mBatteryLastUptime;
+            case STATS_CURRENT:
+                return getBatteryUptime(curTime);
+            case STATS_UNPLUGGED:
+                return getBatteryUptimeLocked(curTime) - mUnpluggedBatteryUptime;
+        }
+        return 0;
+    }
+
+    @Override
+    public long computeBatteryRealtime(long curTime, int which) {
+        switch (which) {
+            case STATS_TOTAL:
+                return mBatteryRealtime + getBatteryRealtimeLocked(curTime);
+            case STATS_LAST:
+                return mBatteryLastRealtime;
+            case STATS_CURRENT:
+                return getBatteryRealtimeLocked(curTime);
+            case STATS_UNPLUGGED:
+                return getBatteryRealtimeLocked(curTime) - mUnpluggedBatteryRealtime;
+        }
+        return 0;
+    }
+
+    long getBatteryUptimeLocked(long curTime) {
+        long time = mTrackBatteryPastUptime;
+        if (mOnBatteryInternal) {
+            time += curTime - mTrackBatteryUptimeStart;
+        }
+        return time;
+    }
+
+    long getBatteryUptimeLocked() {
+        return getBatteryUptime(SystemClock.uptimeMillis() * 1000);
+    }
+
+    @Override
+    public long getBatteryUptime(long curTime) {
+        return getBatteryUptimeLocked(curTime);
+    }
+
+    long getBatteryRealtimeLocked(long curTime) {
+        long time = mTrackBatteryPastRealtime;
+        if (mOnBatteryInternal) {
+            time += curTime - mTrackBatteryRealtimeStart;
+        }
+        return time;
+    }
+
+    @Override
+    public long getBatteryRealtime(long curTime) {
+        return getBatteryRealtimeLocked(curTime);
+    }
+
+    /**
+     * Retrieve the statistics object for a particular uid, creating if needed.
+     */
+    public Uid getUidStatsLocked(int uid) {
+        Uid u = mUidStats.get(uid);
+        if (u == null) {
+            u = new Uid(uid);
+            mUidStats.put(uid, u);
+        }
+        return u;
+    }
+
+    /**
+     * Remove the statistics object for a particular uid.
+     */
+    public void removeUidStatsLocked(int uid) {
+        mUidStats.remove(uid);
+    }
+    
+    /**
+     * Retrieve the statistics object for a particular process, creating
+     * if needed.
+     */
+    public Uid.Proc getProcessStatsLocked(int uid, String name) {
+        Uid u = getUidStatsLocked(uid);
+        return u.getProcessStatsLocked(name);
+    }
+
+    /**
+     * Retrieve the statistics object for a particular process, creating
+     * if needed.
+     */
+    public Uid.Pkg getPackageStatsLocked(int uid, String pkg) {
+        Uid u = getUidStatsLocked(uid);
+        return u.getPackageStatsLocked(pkg);
+    }
+
+    /**
+     * Retrieve the statistics object for a particular service, creating
+     * if needed.
+     */
+    public Uid.Pkg.Serv getServiceStatsLocked(int uid, String pkg, String name) {
+        Uid u = getUidStatsLocked(uid);
+        return u.getServiceStatsLocked(pkg, name);
+    }
+
+    public void writeLocked() {
+        if ((mFile == null) || (mBackupFile == null)) {
+            Log.w("BatteryStats", "writeLocked: no file associated with this instance");
+            return;
+        }
+
+        // Keep the old file around until we know the new one has
+        // been successfully written.
+        if (mFile.exists()) {
+            if (mBackupFile.exists()) {
+                mBackupFile.delete();
+            }
+            mFile.renameTo(mBackupFile);
+        }
+
+        try {
+            FileOutputStream stream = new FileOutputStream(mFile);
+            Parcel out = Parcel.obtain();
+            writeSummaryToParcel(out);
+            stream.write(out.marshall());
+            out.recycle();
+
+            stream.flush();
+            stream.close();
+            mBackupFile.delete();
+
+            mLastWriteTime = SystemClock.elapsedRealtime();
+        } catch (IOException e) {
+            Log.e("BatteryStats", "Error writing battery statistics", e);
+        }
+    }
+
+    static byte[] readFully(FileInputStream stream) throws java.io.IOException {
+        int pos = 0;
+        int avail = stream.available();
+        byte[] data = new byte[avail];
+        while (true) {
+            int amt = stream.read(data, pos, data.length-pos);
+            //Log.i("foo", "Read " + amt + " bytes at " + pos
+            //        + " of avail " + data.length);
+            if (amt <= 0) {
+                //Log.i("foo", "**** FINISHED READING: pos=" + pos
+                //        + " len=" + data.length);
+                return data;
+            }
+            pos += amt;
+            avail = stream.available();
+            if (avail > data.length-pos) {
+                byte[] newData = new byte[pos+avail];
+                System.arraycopy(data, 0, newData, 0, pos);
+                data = newData;
+            }
+        }
+    }
+
+    public void readLocked() {
+        if ((mFile == null) || (mBackupFile == null)) {
+            Log.w("BatteryStats", "readLocked: no file associated with this instance");
+            return;
+        }
+
+        mUidStats.clear();
+
+        FileInputStream stream = null;
+        if (mBackupFile.exists()) {
+            try {
+                stream = new FileInputStream(mBackupFile);
+            } catch (java.io.IOException e) {
+                // We'll try for the normal settings file.
+            }
+        }
+
+        try {
+            if (stream == null) {
+                if (!mFile.exists()) {
+                    return;
+                }
+                stream = new FileInputStream(mFile);
+            }
+
+            byte[] raw = readFully(stream);
+            Parcel in = Parcel.obtain();
+            in.unmarshall(raw, 0, raw.length);
+            in.setDataPosition(0);
+            stream.close();
+
+            readSummaryFromParcel(in);
+        } catch(java.io.IOException e) {
+            Log.e("BatteryStats", "Error reading battery statistics", e);
+        }
+    }
+
+    public int describeContents() {
+        return 0;
+    }
+
+    private void readSummaryFromParcel(Parcel in) {
+        final int version = in.readInt();
+        if (version != VERSION) {
+            Log.w("BatteryStats", "readFromParcel: version got " + version
+                + ", expected " + VERSION + "; erasing old stats");
+            return;
+        }
+
+        mStartCount = in.readInt();
+        mBatteryUptime = in.readLong();
+        mBatteryLastUptime = in.readLong();
+        mBatteryRealtime = in.readLong();
+        mBatteryLastRealtime = in.readLong();
+        mUptime = in.readLong();
+        mLastUptime = in.readLong();
+        mRealtime = in.readLong();
+        mLastRealtime = in.readLong();
+        mStartCount++;
+        
+        mScreenOn = false;
+        mScreenOnTimer.readSummaryFromParcelLocked(in);
+        mPhoneOn = false;
+        mPhoneOnTimer.readSummaryFromParcelLocked(in);
+
+        final int NU = in.readInt();
+        for (int iu = 0; iu < NU; iu++) {
+            int uid = in.readInt();
+            Uid u = new Uid(uid);
+            mUidStats.put(uid, u);
+
+            int NW = in.readInt();
+            for (int iw = 0; iw < NW; iw++) {
+                String wlName = in.readString();
+                if (in.readInt() != 0) {
+                    u.getWakeTimerLocked(wlName, WAKE_TYPE_FULL).readSummaryFromParcelLocked(in);
+                }
+                if (in.readInt() != 0) {
+                    u.getWakeTimerLocked(wlName, WAKE_TYPE_PARTIAL).readSummaryFromParcelLocked(in);
+                }
+                if (in.readInt() != 0) {
+                    u.getWakeTimerLocked(wlName, WAKE_TYPE_WINDOW).readSummaryFromParcelLocked(in);
+                }
+            }
+
+            int NP = in.readInt();
+            for (int is = 0; is < NP; is++) {
+                int seNumber = in.readInt();
+                if (in.readInt() != 0) {
+                    u.getSensorTimerLocked(seNumber, true)
+                            .readSummaryFromParcelLocked(in);
+                }
+            }
+
+            NP = in.readInt();
+            for (int ip = 0; ip < NP; ip++) {
+                String procName = in.readString();
+                Uid.Proc p = u.getProcessStatsLocked(procName);
+                p.mUserTime = p.mLoadedUserTime = in.readLong();
+                p.mLastUserTime = in.readLong();
+                p.mSystemTime = p.mLoadedSystemTime = in.readLong();
+                p.mLastSystemTime = in.readLong();
+                p.mStarts = p.mLoadedStarts = in.readInt();
+                p.mLastStarts = in.readInt();
+            }
+
+            NP = in.readInt();
+            for (int ip = 0; ip < NP; ip++) {
+                String pkgName = in.readString();
+                Uid.Pkg p = u.getPackageStatsLocked(pkgName);
+                p.mWakeups = p.mLoadedWakeups = in.readInt();
+                p.mLastWakeups = in.readInt();
+                final int NS = in.readInt();
+                for (int is = 0; is < NS; is++) {
+                    String servName = in.readString();
+                    Uid.Pkg.Serv s = u.getServiceStatsLocked(pkgName, servName);
+                    s.mStartTime = s.mLoadedStartTime = in.readLong();
+                    s.mLastStartTime = in.readLong();
+                    s.mStarts = s.mLoadedStarts = in.readInt();
+                    s.mLastStarts = in.readInt();
+                    s.mLaunches = s.mLoadedLaunches = in.readInt();
+                    s.mLastLaunches = in.readInt();
+                }
+            }
+
+            u.mLoadedTcpBytesReceived = in.readLong();
+            u.mLoadedTcpBytesSent = in.readLong();
+        }
+    }
+
+    /**
+     * Writes a summary of the statistics to a Parcel, in a format suitable to be written to
+     * disk.  This format does not allow a lossless round-trip.
+     *
+     * @param out the Parcel to be written to.
+     */
+    public void writeSummaryToParcel(Parcel out) {
+        final long NOW_SYS = SystemClock.uptimeMillis() * 1000;
+        final long NOWREAL_SYS = SystemClock.elapsedRealtime() * 1000;
+        final long NOW = getBatteryUptimeLocked(NOW_SYS);
+        final long NOWREAL = getBatteryRealtimeLocked(NOWREAL_SYS);
+
+        out.writeInt(VERSION);
+
+        out.writeInt(mStartCount);
+        out.writeLong(computeBatteryUptime(NOW_SYS, STATS_TOTAL));
+        out.writeLong(computeBatteryUptime(NOW_SYS, STATS_CURRENT));
+        out.writeLong(computeBatteryRealtime(NOWREAL_SYS, STATS_TOTAL));
+        out.writeLong(computeBatteryRealtime(NOWREAL_SYS, STATS_CURRENT));
+        out.writeLong(computeUptime(NOW_SYS, STATS_TOTAL));
+        out.writeLong(computeUptime(NOW_SYS, STATS_CURRENT));
+        out.writeLong(computeRealtime(NOWREAL_SYS, STATS_TOTAL));
+        out.writeLong(computeRealtime(NOWREAL_SYS, STATS_CURRENT));
+        
+        mScreenOnTimer.writeSummaryFromParcelLocked(out, NOWREAL);
+        mPhoneOnTimer.writeSummaryFromParcelLocked(out, NOWREAL);
+
+        final int NU = mUidStats.size();
+        out.writeInt(NU);
+        for (int iu = 0; iu < NU; iu++) {
+            out.writeInt(mUidStats.keyAt(iu));
+            Uid u = mUidStats.valueAt(iu);
+
+            int NW = u.mWakelockStats.size();
+            out.writeInt(NW);
+            if (NW > 0) {
+                for (Map.Entry<String, BatteryStatsImpl.Uid.Wakelock> ent
+                        : u.mWakelockStats.entrySet()) {
+                    out.writeString(ent.getKey());
+                    Uid.Wakelock wl = ent.getValue();
+                    if (wl.mTimerFull != null) {
+                        out.writeInt(1);
+                        wl.mTimerFull.writeSummaryFromParcelLocked(out, NOWREAL);
+                    } else {
+                        out.writeInt(0);
+                    }
+                    if (wl.mTimerPartial != null) {
+                        out.writeInt(1);
+                        wl.mTimerPartial.writeSummaryFromParcelLocked(out, NOWREAL);
+                    } else {
+                        out.writeInt(0);
+                    }
+                    if (wl.mTimerWindow != null) {
+                        out.writeInt(1);
+                        wl.mTimerWindow.writeSummaryFromParcelLocked(out, NOWREAL);
+                    } else {
+                        out.writeInt(0);
+                    }
+                }
+            }
+
+            int NSE = u.mSensorStats.size();
+            out.writeInt(NSE);
+            if (NSE > 0) {
+                for (Map.Entry<Integer, BatteryStatsImpl.Uid.Sensor> ent
+                        : u.mSensorStats.entrySet()) {
+                    out.writeInt(ent.getKey());
+                    Uid.Sensor se = ent.getValue();
+                    if (se.mTimer != null) {
+                        out.writeInt(1);
+                        se.mTimer.writeSummaryFromParcelLocked(out, NOWREAL);
+                    } else {
+                        out.writeInt(0);
+                    }
+                }
+            }
+
+            int NP = u.mProcessStats.size();
+            out.writeInt(NP);
+            if (NP > 0) {
+                for (Map.Entry<String, BatteryStatsImpl.Uid.Proc> ent
+                    : u.mProcessStats.entrySet()) {
+                    out.writeString(ent.getKey());
+                    Uid.Proc ps = ent.getValue();
+                    out.writeLong(ps.mUserTime);
+                    out.writeLong(ps.mUserTime - ps.mLoadedUserTime);
+                    out.writeLong(ps.mSystemTime);
+                    out.writeLong(ps.mSystemTime - ps.mLoadedSystemTime);
+                    out.writeInt(ps.mStarts);
+                    out.writeInt(ps.mStarts - ps.mLoadedStarts);
+                }
+            }
+
+            NP = u.mPackageStats.size();
+            out.writeInt(NP);
+            if (NP > 0) {
+                for (Map.Entry<String, BatteryStatsImpl.Uid.Pkg> ent
+                    : u.mPackageStats.entrySet()) {
+                    out.writeString(ent.getKey());
+                    Uid.Pkg ps = ent.getValue();
+                    out.writeInt(ps.mWakeups);
+                    out.writeInt(ps.mWakeups - ps.mLoadedWakeups);
+                    final int NS = ps.mServiceStats.size();
+                    out.writeInt(NS);
+                    if (NS > 0) {
+                        for (Map.Entry<String, BatteryStatsImpl.Uid.Pkg.Serv> sent
+                                : ps.mServiceStats.entrySet()) {
+                            out.writeString(sent.getKey());
+                            BatteryStatsImpl.Uid.Pkg.Serv ss = sent.getValue();
+                            long time = ss.getStartTimeToNowLocked(NOW);
+                            out.writeLong(time);
+                            out.writeLong(time - ss.mLoadedStartTime);
+                            out.writeInt(ss.mStarts);
+                            out.writeInt(ss.mStarts - ss.mLoadedStarts);
+                            out.writeInt(ss.mLaunches);
+                            out.writeInt(ss.mLaunches - ss.mLoadedLaunches);
+                        }
+                    }
+                }
+            }
+            
+            out.writeLong(u.getTcpBytesReceived(STATS_TOTAL));
+            out.writeLong(u.getTcpBytesSent(STATS_TOTAL));
+        }
+    }
+
+    public void readFromParcel(Parcel in) {
+        readFromParcelLocked(in);
+    }
+    
+    void readFromParcelLocked(Parcel in) {
+        int magic = in.readInt();
+        if (magic != MAGIC) {
+            throw new ParcelFormatException("Bad magic number");
+        }
+
+        mStartCount = in.readInt();
+        mBatteryUptime = in.readLong();
+        mBatteryLastUptime = in.readLong();
+        mBatteryRealtime = in.readLong();
+        mBatteryLastRealtime = in.readLong();
+        mScreenOn = false;
+        mScreenOnTimer = new Timer(-1, null, mUnpluggables, in);
+        mPhoneOn = false;
+        mPhoneOnTimer = new Timer(-2, null, mUnpluggables, in);
+        mUptime = in.readLong();
+        mUptimeStart = in.readLong();
+        mLastUptime = in.readLong();
+        mRealtime = in.readLong();
+        mRealtimeStart = in.readLong();
+        mLastRealtime = in.readLong();
+        mOnBattery = in.readInt() != 0;
+        mOnBatteryInternal = false; // we are no longer really running.
+        mTrackBatteryPastUptime = in.readLong();
+        mTrackBatteryUptimeStart = in.readLong();
+        mTrackBatteryPastRealtime = in.readLong();
+        mTrackBatteryRealtimeStart = in.readLong();
+        mUnpluggedBatteryUptime = in.readLong();
+        mUnpluggedBatteryRealtime = in.readLong();
+        mLastWriteTime = in.readLong();
+
+        mPartialTimers.clear();
+        mFullTimers.clear();
+        mWindowTimers.clear();
+
+        int numUids = in.readInt();
+        mUidStats.clear();
+        for (int i = 0; i < numUids; i++) {
+            int uid = in.readInt();
+            Uid u = new Uid(uid);
+            u.readFromParcelLocked(mUnpluggables, in);
+            mUidStats.append(uid, u);
+        }
+    }
+
+    public void writeToParcel(Parcel out, int flags) {
+        writeToParcelLocked(out, flags);
+    }
+    
+    @SuppressWarnings("unused") 
+    void writeToParcelLocked(Parcel out, int flags) {
+        final long uSecUptime = SystemClock.uptimeMillis() * 1000;
+        final long uSecRealtime = SystemClock.elapsedRealtime() * 1000;
+        final long batteryUptime = getBatteryUptimeLocked(uSecUptime);
+        final long batteryRealtime = getBatteryRealtimeLocked(uSecRealtime);
+        
+        out.writeInt(MAGIC);
+        out.writeInt(mStartCount);
+        out.writeLong(mBatteryUptime);
+        out.writeLong(mBatteryLastUptime);
+        out.writeLong(mBatteryRealtime);
+        out.writeLong(mBatteryLastRealtime);
+        mScreenOnTimer.writeToParcel(out, batteryRealtime);
+        mPhoneOnTimer.writeToParcel(out, batteryRealtime);
+        out.writeLong(mUptime);
+        out.writeLong(mUptimeStart);
+        out.writeLong(mLastUptime);
+        out.writeLong(mRealtime);
+        out.writeLong(mRealtimeStart);
+        out.writeLong(mLastRealtime);
+        out.writeInt(mOnBattery ? 1 : 0);
+        out.writeLong(batteryUptime);
+        out.writeLong(mTrackBatteryUptimeStart);
+        out.writeLong(batteryRealtime);
+        out.writeLong(mTrackBatteryRealtimeStart);
+        out.writeLong(mUnpluggedBatteryUptime);
+        out.writeLong(mUnpluggedBatteryRealtime);
+        out.writeLong(mLastWriteTime);
+
+        int size = mUidStats.size();
+        out.writeInt(size);
+        for (int i = 0; i < size; i++) {
+            out.writeInt(mUidStats.keyAt(i));
+            Uid uid = mUidStats.valueAt(i);
+
+            uid.writeToParcelLocked(out, batteryRealtime);
+        }
+    }
+
+    public static final Parcelable.Creator<BatteryStatsImpl> CREATOR =
+        new Parcelable.Creator<BatteryStatsImpl>() {
+        public BatteryStatsImpl createFromParcel(Parcel in) {
+            return new BatteryStatsImpl(in);
+        }
+
+        public BatteryStatsImpl[] newArray(int size) {
+            return new BatteryStatsImpl[size];
+        }
+    };
+    
+    public void dumpLocked(Printer pw) {
+        if (DEBUG) {
+            Log.i(TAG, "*** Screen timer:");
+            mScreenOnTimer.logState();
+            Log.i(TAG, "*** Phone timer:");
+            mPhoneOnTimer.logState();
+        }
+        super.dumpLocked(pw);
+    }
+}
diff --git a/core/java/com/android/internal/os/BinderInternal.java b/core/java/com/android/internal/os/BinderInternal.java
new file mode 100644
index 0000000..eacf0ce
--- /dev/null
+++ b/core/java/com/android/internal/os/BinderInternal.java
@@ -0,0 +1,89 @@
+/*
+ * 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.internal.os;
+
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.SystemClock;
+import android.util.Config;
+import android.util.EventLog;
+import android.util.Log;
+
+import java.io.FileDescriptor;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.lang.ref.WeakReference;
+import java.lang.reflect.Modifier;
+
+/**
+ * Private and debugging Binder APIs.
+ * 
+ * @see IBinder
+ */
+public class BinderInternal {
+    static WeakReference<GcWatcher> mGcWatcher
+            = new WeakReference<GcWatcher>(new GcWatcher());
+    static long mLastGcTime;
+    
+    static final class GcWatcher {
+        @Override
+        protected void finalize() throws Throwable {
+            handleGc();
+            mLastGcTime = SystemClock.uptimeMillis();
+            mGcWatcher = new WeakReference<GcWatcher>(new GcWatcher());
+        }
+    }
+    
+    /**
+     * Add the calling thread to the IPC thread pool.  This function does
+     * not return until the current process is exiting.
+     */
+    public static final native void joinThreadPool();
+    
+    /**
+     * Return the system time (as reported by {@link SystemClock#uptimeMillis
+     * SystemClock.uptimeMillis()}) that the last garbage collection occurred
+     * in this process.  This is not for general application use, and the
+     * meaning of "when a garbage collection occurred" will change as the
+     * garbage collector evolves.
+     * 
+     * @return Returns the time as per {@link SystemClock#uptimeMillis
+     * SystemClock.uptimeMillis()} of the last garbage collection.
+     */
+    public static long getLastGcTime() {
+        return mLastGcTime;
+    }
+
+    /**
+     * Return the global "context object" of the system.  This is usually
+     * an implementation of IServiceManager, which you can use to find
+     * other services.
+     */
+    public static final native IBinder getContextObject();
+    
+    static native final void handleGc();
+    
+    public static void forceGc(String reason) {
+        EventLog.writeEvent(2741, reason);
+        Runtime.getRuntime().gc();
+    }
+    
+    static void forceBinderGc() {
+        forceGc("Binder");
+    }
+}
diff --git a/core/java/com/android/internal/os/HandlerCaller.java b/core/java/com/android/internal/os/HandlerCaller.java
new file mode 100644
index 0000000..bab1e21
--- /dev/null
+++ b/core/java/com/android/internal/os/HandlerCaller.java
@@ -0,0 +1,195 @@
+package com.android.internal.os;
+
+import android.content.Context;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+
+public class HandlerCaller {
+    private static final String TAG = "HandlerCaller";
+    private static final boolean DEBUG = false;
+    
+    public final Context mContext;
+    
+    final Looper mMainLooper;
+    final Handler mH;
+
+    final Callback mCallback;
+    
+    public static class SomeArgs {
+        SomeArgs next;
+        
+        public Object arg1;
+        public Object arg2;
+        public Object arg3;
+        public Object arg4;
+        public int argi1;
+        public int argi2;
+        public int argi3;
+        public int argi4;
+        public int argi5;
+        public int argi6;
+    }
+    
+    static final int ARGS_POOL_MAX_SIZE = 10;
+    int mArgsPoolSize;
+    SomeArgs mArgsPool;
+    
+    class MyHandler extends Handler {
+        MyHandler(Looper looper) {
+            super(looper);
+        }
+        
+        @Override
+        public void handleMessage(Message msg) {
+            mCallback.executeMessage(msg);
+        }
+    }
+    
+    public interface Callback {
+        public void executeMessage(Message msg);
+    }
+    
+    public HandlerCaller(Context context, Callback callback) {
+        mContext = context;
+        mMainLooper = context.getMainLooper();
+        mH = new MyHandler(mMainLooper);
+        mCallback = callback;
+    }
+
+    public SomeArgs obtainArgs() {
+        synchronized (mH) {
+            SomeArgs args = mArgsPool;
+            if (args != null) {
+                mArgsPool = args.next;
+                args.next = null;
+                mArgsPoolSize--;
+                return args;
+            }
+        }
+        return new SomeArgs();
+    }
+    
+    public void recycleArgs(SomeArgs args) {
+        synchronized (mH) {
+            if (mArgsPoolSize < ARGS_POOL_MAX_SIZE) {
+                args.next = mArgsPool;
+                mArgsPool = args;
+                mArgsPoolSize++;
+            }
+        }
+    }
+    
+    public void executeOrSendMessage(Message msg) {
+        // If we are calling this from the main thread, then we can call
+        // right through.  Otherwise, we need to send the message to the
+        // main thread.
+        if (Looper.myLooper() == mMainLooper) {
+            mCallback.executeMessage(msg);
+            msg.recycle();
+            return;
+        }
+        
+        mH.sendMessage(msg);
+    }
+    
+    public void sendMessage(Message msg) {
+        mH.sendMessage(msg);
+    }
+    
+    public Message obtainMessage(int what) {
+        return mH.obtainMessage(what);
+    }
+    
+    public Message obtainMessageBO(int what, boolean arg1, Object arg2) {
+        return mH.obtainMessage(what, arg1 ? 1 : 0, 0, arg2);
+    }
+    
+    public Message obtainMessageBOO(int what, boolean arg1, Object arg2, Object arg3) {
+        SomeArgs args = obtainArgs();
+        args.arg1 = arg2;
+        args.arg2 = arg3;
+        return mH.obtainMessage(what, arg1 ? 1 : 0, 0, args);
+    }
+    
+    public Message obtainMessageO(int what, Object arg1) {
+        return mH.obtainMessage(what, 0, 0, arg1);
+    }
+    
+    public Message obtainMessageI(int what, int arg1) {
+        return mH.obtainMessage(what, arg1, 0);
+    }
+    
+    public Message obtainMessageIO(int what, int arg1, Object arg2) {
+        return mH.obtainMessage(what, arg1, 0, arg2);
+    }
+    
+    public Message obtainMessageIIO(int what, int arg1, int arg2, Object arg3) {
+        return mH.obtainMessage(what, arg1, arg2, arg3);
+    }
+    
+    public Message obtainMessageIOO(int what, int arg1, Object arg2, Object arg3) {
+        SomeArgs args = obtainArgs();
+        args.arg1 = arg2;
+        args.arg2 = arg3;
+        return mH.obtainMessage(what, arg1, 0, args);
+    }
+    
+    public Message obtainMessageOO(int what, Object arg1, Object arg2) {
+        SomeArgs args = obtainArgs();
+        args.arg1 = arg1;
+        args.arg2 = arg2;
+        return mH.obtainMessage(what, 0, 0, args);
+    }
+    
+    public Message obtainMessageOOO(int what, Object arg1, Object arg2, Object arg3) {
+        SomeArgs args = obtainArgs();
+        args.arg1 = arg1;
+        args.arg2 = arg2;
+        args.arg3 = arg3;
+        return mH.obtainMessage(what, 0, 0, args);
+    }
+    
+    public Message obtainMessageOOOO(int what, Object arg1, Object arg2,
+            Object arg3, Object arg4) {
+        SomeArgs args = obtainArgs();
+        args.arg1 = arg1;
+        args.arg2 = arg2;
+        args.arg3 = arg3;
+        args.arg4 = arg4;
+        return mH.obtainMessage(what, 0, 0, args);
+    }
+    
+    public Message obtainMessageIIII(int what, int arg1, int arg2,
+            int arg3, int arg4) {
+        SomeArgs args = obtainArgs();
+        args.argi1 = arg1;
+        args.argi2 = arg2;
+        args.argi3 = arg3;
+        args.argi4 = arg4;
+        return mH.obtainMessage(what, 0, 0, args);
+    }
+    
+    public Message obtainMessageIIIIII(int what, int arg1, int arg2,
+            int arg3, int arg4, int arg5, int arg6) {
+        SomeArgs args = obtainArgs();
+        args.argi1 = arg1;
+        args.argi2 = arg2;
+        args.argi3 = arg3;
+        args.argi4 = arg4;
+        args.argi5 = arg5;
+        args.argi6 = arg6;
+        return mH.obtainMessage(what, 0, 0, args);
+    }
+    
+    public Message obtainMessageIIIIO(int what, int arg1, int arg2,
+            int arg3, int arg4, Object arg5) {
+        SomeArgs args = obtainArgs();
+        args.arg1 = arg5;
+        args.argi1 = arg1;
+        args.argi2 = arg2;
+        args.argi3 = arg3;
+        args.argi4 = arg4;
+        return mH.obtainMessage(what, 0, 0, args);
+    }
+}
diff --git a/core/java/com/android/internal/os/LoggingPrintStream.java b/core/java/com/android/internal/os/LoggingPrintStream.java
new file mode 100644
index 0000000..b3d6f20
--- /dev/null
+++ b/core/java/com/android/internal/os/LoggingPrintStream.java
@@ -0,0 +1,290 @@
+/*
+ * 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.internal.os;
+
+import java.io.PrintStream;
+import java.io.OutputStream;
+import java.io.IOException;
+import java.util.Locale;
+import java.util.Formatter;
+
+/**
+ * A print stream which logs output line by line.
+ *
+ * {@hide}
+ */
+abstract class LoggingPrintStream extends PrintStream {
+
+    private final StringBuilder builder = new StringBuilder();
+
+    protected LoggingPrintStream() {
+        super(new OutputStream() {
+            public void write(int oneByte) throws IOException {
+                throw new AssertionError();
+            }
+        });
+    }
+
+    /**
+     * Logs the given line.
+     */
+    protected abstract void log(String line);
+
+    @Override
+    public synchronized void flush() {
+        flush(true);
+    }
+
+    /**
+     * Searches buffer for line breaks and logs a message for each one.
+     *
+     * @param completely true if the ending chars should be treated as a line
+     *  even though they don't end in a line break
+     */
+    private void flush(boolean completely) {
+        int length = builder.length();
+
+        int start = 0;
+        int nextBreak;
+
+        // Log one line for each line break.
+        while (start < length
+                && (nextBreak = builder.indexOf("\n", start)) != -1) {
+            log(builder.substring(start, nextBreak));
+            start = nextBreak + 1;
+        }
+
+        if (completely) {
+            // Log the remainder of the buffer.
+            if (start < length) {
+                log(builder.substring(start));
+            }
+            builder.setLength(0);
+        } else {
+            // Delete characters leading up to the next starting point.
+            builder.delete(0, start);
+        }
+    }
+
+    /*
+     * We have no idea of how these bytes are encoded, so just ignore them.
+     */
+
+    /** Ignored. */
+    public void write(int oneByte) {}
+
+    /** Ignored. */
+    @Override
+    public void write(byte buffer[]) {}
+
+    /** Ignored. */
+    @Override
+    public void write(byte bytes[], int start, int count) {}
+
+    /** Always returns false. */
+    @Override
+    public boolean checkError() {
+        return false;
+    }
+
+    /** Ignored. */
+    @Override
+    protected void setError() { /* ignored */ }
+
+    /** Ignored. */
+    @Override
+    public void close() { /* ignored */ }
+
+    @Override
+    public PrintStream format(String format, Object... args) {
+        return format(Locale.getDefault(), format, args);
+    }
+
+    @Override
+    public PrintStream printf(String format, Object... args) {
+        return format(format, args);
+    }
+
+    @Override
+    public PrintStream printf(Locale l, String format, Object... args) {
+        return format(l, format, args);
+    }
+
+    private final Formatter formatter = new Formatter(builder, null);
+
+    @Override
+    public synchronized PrintStream format(
+            Locale l, String format, Object... args) {
+        if (format == null) {
+            throw new NullPointerException("format");
+        }
+
+        formatter.format(l, format, args);
+        flush(false);
+        return this;
+    }
+
+    @Override
+    public synchronized void print(char[] charArray) {
+        builder.append(charArray);
+        flush(false);
+    }
+
+    @Override
+    public synchronized void print(char ch) {
+        builder.append(ch);
+        if (ch == '\n') {
+            flush(false);
+        }
+    }
+
+    @Override
+    public synchronized void print(double dnum) {
+        builder.append(dnum);
+    }
+
+    @Override
+    public synchronized void print(float fnum) {
+        builder.append(fnum);
+    }
+
+    @Override
+    public synchronized void print(int inum) {
+        builder.append(inum);
+    }
+
+    @Override
+    public synchronized void print(long lnum) {
+        builder.append(lnum);
+    }
+
+    @Override
+    public synchronized void print(Object obj) {
+        builder.append(obj);
+        flush(false);
+    }
+
+    @Override
+    public synchronized void print(String str) {
+        builder.append(str);
+        flush(false);
+    }
+
+    @Override
+    public synchronized void print(boolean bool) {
+        builder.append(bool);
+    }
+
+    @Override
+    public synchronized void println() {
+        flush(true);
+    }
+
+    @Override
+    public synchronized void println(char[] charArray) {
+        builder.append(charArray);
+        flush(true);
+    }
+
+    @Override
+    public synchronized void println(char ch) {
+        builder.append(ch);
+        flush(true);
+    }
+
+    @Override
+    public synchronized void println(double dnum) {
+        builder.append(dnum);
+        flush(true);
+    }
+
+    @Override
+    public synchronized void println(float fnum) {
+        builder.append(fnum);
+        flush(true);
+    }
+
+    @Override
+    public synchronized void println(int inum) {
+        builder.append(inum);
+        flush(true);
+    }
+
+    @Override
+    public synchronized void println(long lnum) {
+        builder.append(lnum);
+        flush(true);
+    }
+
+    @Override
+    public synchronized void println(Object obj) {
+        builder.append(obj);
+        flush(true);
+    }
+
+    @Override
+    public synchronized void println(String s) {
+        if (builder.length() == 0) {
+            // Optimization for a simple println.
+            int length = s.length();
+
+            int start = 0;
+            int nextBreak;
+
+            // Log one line for each line break.
+            while (start < length
+                    && (nextBreak = s.indexOf('\n', start)) != -1) {
+                log(s.substring(start, nextBreak));
+                start = nextBreak + 1;
+            }
+
+            if (start < length) {
+                log(s.substring(start));
+            }
+        } else {
+            builder.append(s);
+            flush(true);
+        }
+    }
+
+    @Override
+    public synchronized void println(boolean bool) {
+        builder.append(bool);
+        flush(true);
+    }
+
+    @Override
+    public synchronized PrintStream append(char c) {
+        print(c);
+        return this;
+    }
+
+    @Override
+    public synchronized PrintStream append(CharSequence csq) {
+        builder.append(csq);
+        flush(false);
+        return this;
+    }
+
+    @Override
+    public synchronized PrintStream append(
+            CharSequence csq, int start, int end) {
+        builder.append(csq, start, end);
+        flush(false);
+        return this;
+    }
+}
diff --git a/core/java/com/android/internal/os/PkgUsageStats.aidl b/core/java/com/android/internal/os/PkgUsageStats.aidl
new file mode 100755
index 0000000..8305271
--- /dev/null
+++ b/core/java/com/android/internal/os/PkgUsageStats.aidl
@@ -0,0 +1,20 @@
+/* //device/java/android/android/content/Intent.aidl
+**
+** Copyright 2007, 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.internal.os;
+
+parcelable PkgUsageStats;
diff --git a/core/java/com/android/internal/os/PkgUsageStats.java b/core/java/com/android/internal/os/PkgUsageStats.java
new file mode 100755
index 0000000..e847878
--- /dev/null
+++ b/core/java/com/android/internal/os/PkgUsageStats.java
@@ -0,0 +1,60 @@
+package com.android.internal.os;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * implementation of PkgUsageStats associated with an
+ * application package.
+ *  @hide
+ */
+public class PkgUsageStats implements Parcelable {
+    public String packageName;
+    public int launchCount;
+    public long usageTime;
+    
+    public static final Parcelable.Creator<PkgUsageStats> CREATOR
+    = new Parcelable.Creator<PkgUsageStats>() {
+        public PkgUsageStats createFromParcel(Parcel in) {
+            return new PkgUsageStats(in);
+        }
+
+        public PkgUsageStats[] newArray(int size) {
+            return new PkgUsageStats[size];
+        }
+    };
+    
+    public String toString() {
+        return "PkgUsageStats{"
+        + Integer.toHexString(System.identityHashCode(this))
+        + " " + packageName + "}";
+    }
+    
+    public PkgUsageStats(String pkgName, int count, long time) {
+        packageName = pkgName;
+        launchCount = count;
+        usageTime = time;
+    }
+    
+    public PkgUsageStats(Parcel source) {
+        packageName = source.readString();
+        launchCount = source.readInt();
+        usageTime = source.readLong();
+    }
+    
+    public PkgUsageStats(PkgUsageStats pStats) {
+        packageName = pStats.packageName;
+        launchCount = pStats.launchCount;
+        usageTime = pStats.usageTime;
+    }
+
+    public int describeContents() {
+        return 0;
+    }
+
+    public void writeToParcel(Parcel dest, int parcelableFlags){
+        dest.writeString(packageName);
+        dest.writeInt(launchCount);
+        dest.writeLong(usageTime);
+    }
+}
diff --git a/core/java/com/android/internal/os/RecoverySystem.java b/core/java/com/android/internal/os/RecoverySystem.java
new file mode 100644
index 0000000..c938610
--- /dev/null
+++ b/core/java/com/android/internal/os/RecoverySystem.java
@@ -0,0 +1,128 @@
+/*
+ * 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.internal.os;
+
+import android.os.FileUtils;
+import android.os.Power;
+import android.util.Log;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.util.Iterator;
+import java.util.Map;
+
+/**
+ * Utility class for interacting with the Android recovery partition.
+ * The recovery partition is a small standalone system which can perform
+ * operations that are difficult while the main system is running, like
+ * upgrading system software or reformatting the data partition.
+ * Note that most of these operations must be run as root.
+ *
+ * @hide
+ */
+public class RecoverySystem {
+    private static final String TAG = "RecoverySystem";  // for logging
+
+    // Used to communicate with recovery.  See commands/recovery/recovery.c.
+    private static File RECOVERY_DIR = new File("/cache/recovery");
+    private static File COMMAND_FILE = new File(RECOVERY_DIR, "command");
+    private static File LOG_FILE = new File(RECOVERY_DIR, "log");
+
+    // Length limits for reading files.
+    private static int LOG_FILE_MAX_LENGTH = 8 * 1024;
+
+    /**
+     * Reboot into the recovery system to install a system update.
+     * @param update package to install (must be in /cache or /data).
+     * @throws IOException if something goes wrong.
+     */
+    public static void rebootAndUpdate(File update) throws IOException {
+        String path = update.getCanonicalPath();
+        if (path.startsWith("/cache/")) {
+            path = "CACHE:" + path.substring(7);
+        } else if (path.startsWith("/data/")) {
+            path = "DATA:" + path.substring(6);
+        } else {
+            throw new IllegalArgumentException(
+                    "Must start with /cache or /data: " + path);
+        }
+        bootCommand("--update_package=" + path);
+    }
+
+    /**
+     * Reboot into the recovery system to wipe the /data partition.
+     * @param extras to add to the RECOVERY_COMPLETED intent after rebooting.
+     * @throws IOException if something goes wrong.
+     */
+    public static void rebootAndWipe() throws IOException {
+        bootCommand("--wipe_data");
+    }
+
+    /**
+     * Reboot into the recovery system with the supplied argument.
+     * @param arg to pass to the recovery utility.
+     * @throws IOException if something goes wrong.
+     */
+    private static void bootCommand(String arg) throws IOException {
+        RECOVERY_DIR.mkdirs();  // In case we need it
+        COMMAND_FILE.delete();  // In case it's not writable
+        LOG_FILE.delete();
+
+        FileWriter command = new FileWriter(COMMAND_FILE);
+        try {
+            command.write(arg);
+            command.write("\n");
+        } finally {
+            command.close();
+        }
+
+        // Having written the command file, go ahead and reboot
+        Power.reboot("recovery");
+        throw new IOException("Reboot failed (no permissions?)");
+    }
+
+    /**
+     * Called after booting to process and remove recovery-related files.
+     * @return the log file from recovery, or null if none was found.
+     */
+    public static String handleAftermath() {
+        // Record the tail of the LOG_FILE
+        String log = null;
+        try {
+            log = FileUtils.readTextFile(LOG_FILE, -LOG_FILE_MAX_LENGTH, "...\n");
+        } catch (FileNotFoundException e) {
+            Log.i(TAG, "No recovery log file");
+        } catch (IOException e) {
+            Log.e(TAG, "Error reading recovery log", e);
+        }
+
+        // Delete everything in RECOVERY_DIR
+        String[] names = RECOVERY_DIR.list();
+        for (int i = 0; names != null && i < names.length; i++) {
+            File f = new File(RECOVERY_DIR, names[i]);
+            if (!f.delete()) {
+                Log.e(TAG, "Can't delete: " + f);
+            } else {
+                Log.i(TAG, "Deleted: " + f);
+            }
+        }
+
+        return log;
+    }
+}
diff --git a/core/java/com/android/internal/os/RuntimeInit.java b/core/java/com/android/internal/os/RuntimeInit.java
new file mode 100644
index 0000000..8486272
--- /dev/null
+++ b/core/java/com/android/internal/os/RuntimeInit.java
@@ -0,0 +1,440 @@
+/*
+ * 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.internal.os;
+
+import android.app.ActivityManagerNative;
+import android.app.IActivityManager;
+import android.os.Debug;
+import android.os.IBinder;
+import android.os.ICheckinService;
+import android.os.Process;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.SystemProperties;
+import android.server.data.CrashData;
+import android.util.Config;
+import android.util.Log;
+
+import com.android.internal.logging.AndroidConfig;
+
+import dalvik.system.VMRuntime;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.logging.LogManager;
+import java.util.TimeZone;
+
+import org.apache.harmony.luni.internal.util.TimezoneGetter;
+
+/**
+ * Main entry point for runtime initialization.  Not for
+ * public consumption.
+ * @hide
+ */
+public class RuntimeInit {
+    private final static String TAG = "AndroidRuntime";
+
+    /** true if commonInit() has been called */
+    private static boolean initialized;
+
+    /**
+     * Use this to log a message when a thread exits due to an uncaught
+     * exception.  The framework catches these for the main threads, so
+     * this should only matter for threads created by applications.
+     */
+    private static class UncaughtHandler implements Thread.UncaughtExceptionHandler {
+        public void uncaughtException(Thread t, Throwable e) {
+            try {
+                Log.e(TAG, "Uncaught handler: thread " + t.getName()
+                        + " exiting due to uncaught exception");
+            } catch (Throwable error) {
+                // Ignore the throwable, since we're in the process of crashing anyway.
+                // If we don't, the crash won't happen properly and the process will
+                // be left around in a bad state.
+            }
+            crash(TAG, e);
+        }
+    }
+
+    private static final void commonInit() {
+        if (Config.LOGV) Log.d(TAG, "Entered RuntimeInit!");
+
+        /* set default handler; this applies to all threads in the VM */
+        Thread.setDefaultUncaughtExceptionHandler(new UncaughtHandler());
+
+        int hasQwerty = getQwertyKeyboard();
+        
+        if (Config.LOGV) Log.d(TAG, ">>>>> qwerty keyboard = " + hasQwerty);
+        if (hasQwerty == 1) {
+            System.setProperty("qwerty", "1");
+        }
+
+        /*
+         * Install a TimezoneGetter subclass for ZoneInfo.db
+         */
+        TimezoneGetter.setInstance(new TimezoneGetter() {
+            @Override
+            public String getId() {
+                return SystemProperties.get("persist.sys.timezone");
+            }
+        });
+        TimeZone.setDefault(null);
+
+        /*
+         * Sets handler for java.util.logging to use Android log facilities.
+         * The odd "new instance-and-then-throw-away" is a mirror of how
+         * the "java.util.logging.config.class" system property works. We
+         * can't use the system property here since the logger has almost
+         * certainly already been initialized.
+         */
+        LogManager.getLogManager().reset();
+        new AndroidConfig();
+
+        /*
+         * If we're running in an emulator launched with "-trace", put the
+         * VM into emulator trace profiling mode so that the user can hit
+         * F9/F10 at any time to capture traces.  This has performance
+         * consequences, so it's not something you want to do always.
+         */
+        String trace = SystemProperties.get("ro.kernel.android.tracing");
+        if (trace.equals("1")) {
+            Log.i(TAG, "NOTE: emulator trace profiling enabled");
+            Debug.enableEmulatorTraceOutput();
+        }
+
+        initialized = true;
+    }
+
+    /**
+     * Invokes a static "main(argv[]) method on class "className".
+     * Converts various failing exceptions into RuntimeExceptions, with
+     * the assumption that they will then cause the VM instance to exit.
+     *
+     * @param className Fully-qualified class name
+     * @param argv Argument vector for main()
+     */
+    private static void invokeStaticMain(String className, String[] argv) 
+            throws ZygoteInit.MethodAndArgsCaller {
+        
+        // We want to be fairly aggressive about heap utilization, to avoid
+        // holding on to a lot of memory that isn't needed.
+        VMRuntime.getRuntime().setTargetHeapUtilization(0.75f);
+        
+        Class<?> cl;
+
+        try {
+            cl = Class.forName(className);
+        } catch (ClassNotFoundException ex) {
+            throw new RuntimeException(
+                    "Missing class when invoking static main " + className,
+                    ex);
+        }
+
+        Method m;
+        try {
+            m = cl.getMethod("main", new Class[] { String[].class });
+        } catch (NoSuchMethodException ex) {
+            throw new RuntimeException(
+                    "Missing static main on " + className, ex);
+        } catch (SecurityException ex) {
+            throw new RuntimeException(
+                    "Problem getting static main on " + className, ex);
+        }
+
+        int modifiers = m.getModifiers();
+        if (! (Modifier.isStatic(modifiers) && Modifier.isPublic(modifiers))) {
+            throw new RuntimeException(
+                    "Main method is not public and static on " + className);
+        }
+
+        /*
+         * This throw gets caught in ZygoteInit.main(), which responds
+         * by invoking the exception's run() method. This arrangement
+         * clears up all the stack frames that were required in setting
+         * up the process.
+         */
+        throw new ZygoteInit.MethodAndArgsCaller(m, argv);
+    }
+
+    public static final void main(String[] argv) {
+        commonInit();
+        
+        /*
+         * Now that we're running in interpreted code, call back into native code
+         * to run the system.
+         */
+        finishInit();
+
+        if (Config.LOGV) Log.d(TAG, "Leaving RuntimeInit!");
+    }
+    
+    public static final native void finishInit();
+
+    /**
+     * The main function called when started through the zygote process. This
+     * could be unified with main(), if the native code in finishInit()
+     * were rationalized with Zygote startup.<p>
+     *
+     * Current recognized args:
+     * <ul>
+     *   <li> --nice-name=<i>nice name to appear in ps</i>
+     *   <li> <code> [--] &lt;start class name&gt;  &lt;args&gt;
+     * </ul>
+     *
+     * @param argv arg strings
+     */
+    public static final void zygoteInit(String[] argv)
+            throws ZygoteInit.MethodAndArgsCaller {
+        // TODO: Doing this here works, but it seems kind of arbitrary. Find
+        // a better place. The goal is to set it up for applications, but not
+        // tools like am.
+        System.setOut(new AndroidPrintStream(Log.INFO, "System.out"));
+        System.setErr(new AndroidPrintStream(Log.WARN, "System.err"));
+
+        commonInit();
+        zygoteInitNative();
+
+        int curArg = 0;
+        for ( /* curArg */ ; curArg < argv.length; curArg++) {
+            String arg = argv[curArg];
+
+            if (arg.equals("--")) {
+                curArg++;
+                break;
+            } else if (!arg.startsWith("--")) {
+                break;
+            } else if (arg.startsWith("--nice-name=")) {
+                String niceName = arg.substring(arg.indexOf('=') + 1);
+                Process.setArgV0(niceName);
+            }
+        }
+
+        if (curArg == argv.length) {
+            Log.e(TAG, "Missing classname argument to RuntimeInit!");
+            // let the process exit
+            return;
+        }
+
+        // Remaining arguments are passed to the start class's static main
+        
+        String startClass = argv[curArg++];
+        String[] startArgs = new String[argv.length - curArg];
+
+        System.arraycopy(argv, curArg, startArgs, 0, startArgs.length);
+        invokeStaticMain(startClass, startArgs);
+    }
+
+    public static final native void zygoteInitNative();
+    
+    /**
+     * Returns 1 if the computer is on. If the computer isn't on, the value returned by this method is undefined.
+     */
+    public static final native int isComputerOn();
+
+    /**
+     * Turns the computer on if the computer is off. If the computer is on, the behavior of this method is undefined. 
+     */
+    public static final native void turnComputerOn();
+
+    /**
+     * 
+     * @return 1 if the device has a qwerty keyboard
+     */
+    public static native int getQwertyKeyboard();
+    
+    /**
+     * Report a fatal error in the current process.  If this is a user-process,
+     * a dialog may be displayed informing the user of the error.  This
+     * function does not return; it forces the current process to exit.
+     * 
+     * @param tag to use when logging the error
+     * @param t exception that was generated by the error
+     */
+    public static void crash(String tag, Throwable t) {
+        if (mApplicationObject != null) {
+            byte[] crashData = null;
+            try {
+                // Log exception.
+                Log.e(TAG, Log.getStackTraceString(t));
+                crashData = marshallException(tag, t);
+                if (crashData == null) {
+                    throw new NullPointerException("Can't marshall crash data");
+                }
+            } catch (Throwable t2) {
+                try {
+                    // Log exception as a string so we don't get in an infinite loop.
+                    Log.e(TAG, "Error reporting crash: "
+                            + Log.getStackTraceString(t2));
+                } catch (Throwable t3) {
+                    // Do nothing, must be OOM so we can't format the message
+                }
+            }
+
+            try {
+                // Display user-visible error message.
+                String msg = t.getMessage();
+                if (msg == null) {
+                    msg = t.toString();
+                }
+
+                IActivityManager am = ActivityManagerNative.getDefault();
+                try {
+                    int res = am.handleApplicationError(mApplicationObject,
+                            0, tag, msg, t.toString(), crashData);
+                    // Is waiting for the debugger the right thing?
+                    // For now I have turned off the Debug button, because
+                    // I'm not sure what we should do if it is actually
+                    // selected.
+                    //Log.i(TAG, "Got app error result: " + res);
+                    if (res == 1) {
+                        Debug.waitForDebugger();
+                        return;
+                    }
+                } catch (RemoteException e) {
+                }
+            } catch (Throwable t2) {
+                try {
+                    // Log exception as a string so we don't get in an infinite loop.
+                    Log.e(TAG, "Error reporting crash: "
+                            + Log.getStackTraceString(t2));
+                } catch (Throwable t3) {
+                    // Do nothing, must be OOM so we can't format the message
+                }
+            } finally {
+                // Try everything to make sure this process goes away.
+                Process.killProcess(Process.myPid());
+                System.exit(10);
+            }
+        } else {
+            try {
+                Log.e(TAG, "*** EXCEPTION IN SYSTEM PROCESS.  System will crash.");
+                Log.e(tag, Log.getStackTraceString(t));
+                reportException(tag, t, true);  // synchronous
+            } catch (Throwable t2) {
+                // Do nothing, must be OOM so we can't format the message
+            } finally {
+                // Try everything to make sure this process goes away.
+                Process.killProcess(Process.myPid());
+                System.exit(10);
+            }
+        }
+    }
+
+    /** Counter used to prevent reentrancy in {@link #reportException}. */
+    private static final AtomicInteger sInReportException = new AtomicInteger();
+
+    /**
+     * Report an error in the current process.  The exception information will
+     * be handed off to the checkin service and eventually uploaded for analysis.
+     * This is expensive!  Only use this when the exception indicates a programming
+     * error ("should not happen").
+     *
+     * @param tag to use when logging the error
+     * @param t exception that was generated by the error
+     * @param sync true to wait for the report, false to "fire and forget"
+     */
+    public static void reportException(String tag, Throwable t, boolean sync) {
+        if (!initialized) {
+            // Exceptions during, eg, zygote cannot use this mechanism
+            return;
+        }
+
+        // It's important to prevent an infinite crash-reporting loop:
+        // while this function is running, don't let it be called again.
+        int reenter = sInReportException.getAndIncrement();
+        if (reenter != 0) {
+            sInReportException.decrementAndGet();
+            Log.e(TAG, "Crash logging skipped, already logging another crash");
+            return;
+        }
+
+        // TODO: Enable callers to specify a level (i.e. warn or error).
+        try {
+            // Submit crash data to statistics service.
+            byte[] crashData = marshallException(tag, t);
+            ICheckinService checkin = ICheckinService.Stub.asInterface(
+                    ServiceManager.getService("checkin"));
+            if (checkin == null) {
+                Log.e(TAG, "Crash logging skipped, no checkin service");
+            } else if (sync) {
+                checkin.reportCrashSync(crashData);
+            } else {
+                checkin.reportCrashAsync(crashData);
+            }
+        } catch (Throwable t2) {
+            // Log exception as a string so we don't get in an infinite loop.
+            Log.e(TAG, "Crash logging failed: " + t2);
+        } finally {
+            sInReportException.decrementAndGet();
+        }
+    }
+
+    private static byte[] marshallException(String tag, Throwable t) {
+        // Convert crash data to bytes.
+        ByteArrayOutputStream bout = new ByteArrayOutputStream();
+        DataOutputStream dout = new DataOutputStream(bout);
+        try {
+            new CrashData(tag, t).write(dout);
+            dout.close();
+        } catch (IOException e) {
+            return null;
+        }
+        return bout.toByteArray();
+    }
+
+    /**
+     * Replay an encoded CrashData record back into a useable CrashData record.  This can be
+     * helpful for providing debugging output after a process error.
+     * 
+     * @param crashDataBytes The byte array containing the encoded crash record
+     * @return new CrashData record, or null if could not create one.
+     */
+    public static CrashData unmarshallException(byte[] crashDataBytes) {
+        try {
+            ByteArrayInputStream bin = new ByteArrayInputStream(crashDataBytes);
+            DataInputStream din = new DataInputStream(bin);
+            return new CrashData(din);
+        } catch (IOException e) {
+            return null;
+        }
+    }
+
+    /**
+     * Set the object identifying this application/process, for reporting VM
+     * errors.
+     */
+    public static final void setApplicationObject(IBinder app) {
+        mApplicationObject = app;
+    }
+
+    /**
+     * Enable debugging features.
+     */
+    static {
+        // Register handlers for DDM messages.
+        android.ddm.DdmRegister.registerHandlers();
+    }
+
+    private static IBinder mApplicationObject;
+
+}
diff --git a/core/java/com/android/internal/os/ZygoteConnection.java b/core/java/com/android/internal/os/ZygoteConnection.java
new file mode 100644
index 0000000..631e7d8
--- /dev/null
+++ b/core/java/com/android/internal/os/ZygoteConnection.java
@@ -0,0 +1,834 @@
+/*
+ * Copyright (C) 2007 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.internal.os;
+
+import android.net.Credentials;
+import android.net.LocalSocket;
+import android.os.Process;
+import android.os.SystemProperties;
+import android.util.Log;
+
+import dalvik.system.PathClassLoader;
+import dalvik.system.Zygote;
+
+import java.io.BufferedReader;
+import java.io.DataOutputStream;
+import java.io.FileDescriptor;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.PrintStream;
+import java.util.ArrayList;
+
+/**
+ * A connection that can make spawn requests.
+ */
+class ZygoteConnection {
+    private static final String TAG = "Zygote";
+
+    /** a prototype instance for a future List.toArray() */
+    private static final int[][] intArray2d = new int[0][0];
+
+    /**
+     * {@link android.net.LocalSocket#setSoTimeout} value for connections.
+     * Effectively, the amount of time a requestor has between the start of
+     * the request and the completed request. The select-loop mode Zygote
+     * doesn't have the logic to return to the select loop in the middle of
+     * a request, so we need to time out here to avoid being denial-of-serviced.
+     */
+    private static final int CONNECTION_TIMEOUT_MILLIS = 1000;
+
+    /** max number of arguments that a connection can specify */
+    private static final int MAX_ZYGOTE_ARGC=1024;
+
+    /**
+     * The command socket.
+     *
+     * mSocket is retained in the child process in "peer wait" mode, so
+     * that it closes when the child process terminates. In other cases,
+     * it is closed in the peer.
+     */
+    private final LocalSocket mSocket;
+    private final DataOutputStream mSocketOutStream;
+    private final BufferedReader mSocketReader;
+    private final Credentials peer;
+
+    /**
+     * A long-lived reference to the original command socket used to launch
+     * this peer. If "peer wait" mode is specified, the process that requested
+     * the new VM instance intends to track the lifetime of the spawned instance
+     * via the command socket. In this case, the command socket is closed
+     * in the Zygote and placed here in the spawned instance so that it will
+     * not be collected and finalized. This field remains null at all times
+     * in the original Zygote process, and in all spawned processes where
+     * "peer-wait" mode was not requested.
+     */
+    private static LocalSocket sPeerWaitSocket = null;
+
+    /**
+     * Constructs instance from connected socket.
+     *
+     * @param socket non-null; connected socket
+     * @throws IOException
+     */
+    ZygoteConnection(LocalSocket socket) throws IOException {
+        mSocket = socket;
+
+        mSocketOutStream
+                = new DataOutputStream(socket.getOutputStream());
+
+        mSocketReader = new BufferedReader(
+                new InputStreamReader(socket.getInputStream()), 256);
+
+        mSocket.setSoTimeout(CONNECTION_TIMEOUT_MILLIS);
+                
+        try {
+            peer = mSocket.getPeerCredentials();
+        } catch (IOException ex) {
+            Log.e(TAG, "Cannot read peer credentials", ex);
+            throw ex;
+        }
+    }
+
+    /**
+     * Returns the file descriptor of the associated socket.
+     *
+     * @return null-ok; file descriptor
+     */
+    FileDescriptor getFileDesciptor() {
+        return mSocket.getFileDescriptor();
+    }
+
+    /**
+     * Reads start commands from an open command socket.
+     * Start commands are presently a pair of newline-delimited lines
+     * indicating a) class to invoke main() on b) nice name to set argv[0] to.
+     * Continues to read commands and forkAndSpecialize children until
+     * the socket is closed. This method is used in ZYGOTE_FORK_MODE
+     *
+     * @throws ZygoteInit.MethodAndArgsCaller trampoline to invoke main()
+     * method in child process
+     */
+    void run() throws ZygoteInit.MethodAndArgsCaller {
+
+        int loopCount = ZygoteInit.GC_LOOP_COUNT;
+
+        while (true) {
+            /*
+             * Call gc() before we block in readArgumentList().
+             * It's work that has to be done anyway, and it's better
+             * to avoid making every child do it.  It will also
+             * madvise() any free memory as a side-effect.
+             *
+             * Don't call it every time, because walking the entire
+             * heap is a lot of overhead to free a few hundred bytes.
+             */
+            if (loopCount <= 0) {
+                ZygoteInit.gc();
+                loopCount = ZygoteInit.GC_LOOP_COUNT;
+            } else {
+                loopCount--;
+            }
+
+            if (runOnce()) {
+                break;
+            }
+        }
+    }
+
+    /**
+     * Reads one start command from the command socket. If successful,
+     * a child is forked and a {@link ZygoteInit.MethodAndArgsCaller}
+     * exception is thrown in that child while in the parent process,
+     * the method returns normally. On failure, the child is not
+     * spawned and messages are printed to the log and stderr. Returns
+     * a boolean status value indicating whether an end-of-file on the command
+     * socket has been encountered.
+     *
+     * @return false if command socket should continue to be read from, or
+     * true if an end-of-file has been encountered.
+     * @throws ZygoteInit.MethodAndArgsCaller trampoline to invoke main()
+     * method in child process
+     */
+    boolean runOnce() throws ZygoteInit.MethodAndArgsCaller {
+
+        String args[];
+        Arguments parsedArgs = null;
+        FileDescriptor[] descriptors;
+
+        try {
+            args = readArgumentList();
+            descriptors = mSocket.getAncillaryFileDescriptors();
+        } catch (IOException ex) {
+            Log.w(TAG, "IOException on command socket " + ex.getMessage());
+            closeSocket();
+            return true;
+        }
+
+        if (args == null) {
+            // EOF reached.
+            closeSocket();
+            return true;
+        }
+
+        /** the stderr of the most recent request, if avail */
+        PrintStream newStderr = null;
+
+        if (descriptors != null && descriptors.length >= 3) {
+            newStderr = new PrintStream(
+                    new FileOutputStream(descriptors[2]));
+        }
+
+        int pid;
+
+        try {
+            parsedArgs = new Arguments(args);
+
+            applyUidSecurityPolicy(parsedArgs, peer);
+            applyDebuggerSecurityPolicy(parsedArgs);
+            applyRlimitSecurityPolicy(parsedArgs, peer);
+            applyCapabilitiesSecurityPolicy(parsedArgs, peer);
+
+            int[][] rlimits = null;
+
+            if (parsedArgs.rlimits != null) {
+                rlimits = parsedArgs.rlimits.toArray(intArray2d);
+            }
+
+            pid = Zygote.forkAndSpecialize(parsedArgs.uid, parsedArgs.gid,
+                    parsedArgs.gids, parsedArgs.debugFlags, rlimits);
+        } catch (IllegalArgumentException ex) {
+            logAndPrintError (newStderr, "Invalid zygote arguments", ex);
+            pid = -1;
+        } catch (ZygoteSecurityException ex) {
+            logAndPrintError(newStderr,
+                    "Zygote security policy prevents request: ", ex);
+            pid = -1;
+        }
+
+        if (pid == 0) {
+            // in child
+            handleChildProc(parsedArgs, descriptors, newStderr);
+            // should never happen
+            return true;
+        } else { /* pid != 0 */
+            // in parent...pid of < 0 means failure
+            return handleParentProc(pid, descriptors, parsedArgs);
+        }
+    }
+
+    /**
+     * Closes socket associated with this connection.
+     */
+    void closeSocket() {
+        try {
+            mSocket.close();
+        } catch (IOException ex) {
+            Log.e(TAG, "Exception while closing command "
+                    + "socket in parent", ex);
+        }
+    }
+
+    /**
+     * Handles argument parsing for args related to the zygote spawner.<p>
+
+     * Current recognized args:
+     * <ul>
+     *   <li> --setuid=<i>uid of child process, defaults to 0</i>
+     *   <li> --setgid=<i>gid of child process, defaults to 0</i>
+     *   <li> --setgroups=<i>comma-separated list of supplimentary gid's</i>
+     *   <li> --capabilities=<i>a pair of comma-separated integer strings
+     * indicating Linux capabilities(2) set for child. The first string
+     * represents the <code>permitted</code> set, and the second the
+     * <code>effective</code> set. Precede each with 0 or
+     * 0x for octal or hexidecimal value. If unspecified, both default to 0.
+     * This parameter is only applied if the uid of the new process will
+     * be non-0. </i>
+     *   <li> --rlimit=r,c,m<i>tuple of values for setrlimit() call.
+     *    <code>r</code> is the resource, <code>c</code> and <code>m</code>
+     *    are the settings for current and max value.</i>
+     *   <li> --peer-wait indicates that the command socket should
+     * be inherited by (and set to close-on-exec in) the spawned process
+     * and used to track the lifetime of that process. The spawning process
+     * then exits. Without this flag, it is retained by the spawning process
+     * (and closed in the child) in expectation of a new spawn request.
+     *   <li> --classpath=<i>colon-separated classpath</i> indicates
+     * that the specified class (which must b first non-flag argument) should
+     * be loaded from jar files in the specified classpath. Incompatible with
+     * --runtime-init
+     *   <li> --runtime-init indicates that the remaining arg list should
+     * be handed off to com.android.internal.os.RuntimeInit, rather than
+     * processed directly
+     * Android runtime startup (eg, Binder initialization) is also eschewed.
+     *   <li> If <code>--runtime-init</code> is present:
+     *      [--] &lt;args for RuntimeInit &gt;
+     *   <li> If <code>--runtime-init</code> is absent:
+     *      [--] &lt;classname&gt; [args...]
+     * </ul>
+     */
+    static class Arguments {
+        /** from --setuid */
+        int uid = 0;
+        boolean uidSpecified;
+
+        /** from --setgid */
+        int gid = 0;
+        boolean gidSpecified;
+
+        /** from --setgroups */
+        int[] gids;
+
+        /** from --peer-wait */
+        boolean peerWait;
+
+        /** from --enable-debugger, --enable-checkjni, --enable-assert */
+        int debugFlags;
+
+        /** from --classpath */
+        String classpath;
+
+        /** from --runtime-init */
+        boolean runtimeInit;
+
+        /** from --capabilities */
+        boolean capabilitiesSpecified;
+        long permittedCapabilities;
+        long effectiveCapabilities;
+
+        /** from all --rlimit=r,c,m */
+        ArrayList<int[]> rlimits;
+
+        /**
+         * Any args after and including the first non-option arg
+         * (or after a '--')
+         */
+        String remainingArgs[];
+
+        /**
+         * Constructs instance and parses args
+         * @param args zygote command-line args
+         * @throws IllegalArgumentException
+         */
+        Arguments(String args[]) throws IllegalArgumentException {
+            parseArgs(args);
+        }
+
+        /**
+         * Parses the commandline arguments intended for the Zygote spawner
+         * (such as "--setuid=" and "--setgid=") and creates an array
+         * containing the remaining args.
+         *
+         * Per security review bug #1112214, duplicate args are disallowed in
+         * critical cases to make injection harder.
+         */
+        private void parseArgs(String args[])
+                throws IllegalArgumentException {
+            int curArg = 0;
+
+            for ( /* curArg */ ; curArg < args.length; curArg++) {
+                String arg = args[curArg];
+
+                if (arg.equals("--")) {
+                    curArg++;
+                    break;
+                } else if (arg.startsWith("--setuid=")) {
+                    if (uidSpecified) {
+                        throw new IllegalArgumentException(
+                                "Duplicate arg specified");
+                    }
+                    uidSpecified = true;
+                    uid = Integer.parseInt(
+                            arg.substring(arg.indexOf('=') + 1));
+                } else if (arg.startsWith("--setgid=")) {
+                    if (gidSpecified) {
+                        throw new IllegalArgumentException(
+                                "Duplicate arg specified");
+                    }
+                    gidSpecified = true;
+                    gid = Integer.parseInt(
+                            arg.substring(arg.indexOf('=') + 1));
+                } else if (arg.equals("--enable-debugger")) {
+                    debugFlags |= Zygote.DEBUG_ENABLE_DEBUGGER;
+                } else if (arg.equals("--enable-checkjni")) {
+                    debugFlags |= Zygote.DEBUG_ENABLE_CHECKJNI;
+                } else if (arg.equals("--enable-assert")) {
+                    debugFlags |= Zygote.DEBUG_ENABLE_ASSERT;
+                } else if (arg.equals("--peer-wait")) {
+                    peerWait = true;
+                } else if (arg.equals("--runtime-init")) {
+                    runtimeInit = true;
+                } else if (arg.startsWith("--capabilities=")) {
+                    if (capabilitiesSpecified) {
+                        throw new IllegalArgumentException(
+                                "Duplicate arg specified");
+                    }
+                    capabilitiesSpecified = true;
+                    String capString = arg.substring(arg.indexOf('=')+1);
+
+                    String[] capStrings = capString.split(",", 2);
+
+                    if (capStrings.length == 1) {
+                        effectiveCapabilities = Long.decode(capStrings[0]);
+                        permittedCapabilities = effectiveCapabilities;
+                    } else {
+                        permittedCapabilities = Long.decode(capStrings[0]);
+                        effectiveCapabilities = Long.decode(capStrings[1]);
+                    }
+                } else if (arg.startsWith("--rlimit=")) {
+                    // Duplicate --rlimit arguments are specifically allowed.
+                    String[] limitStrings
+                            = arg.substring(arg.indexOf('=')+1).split(",");
+
+                    if (limitStrings.length != 3) {
+                        throw new IllegalArgumentException(
+                                "--rlimit= should have 3 comma-delimited ints");
+                    }
+                    int[] rlimitTuple = new int[limitStrings.length];
+
+                    for(int i=0; i < limitStrings.length; i++) {
+                        rlimitTuple[i] = Integer.parseInt(limitStrings[i]);
+                    }
+
+                    if (rlimits == null) {
+                        rlimits = new ArrayList();
+                    }
+
+                    rlimits.add(rlimitTuple);
+                } else if (arg.equals("-classpath")) {
+                    if (classpath != null) {
+                        throw new IllegalArgumentException(
+                                "Duplicate arg specified");
+                    }
+                    try {
+                        classpath = args[++curArg];
+                    } catch (IndexOutOfBoundsException ex) {
+                        throw new IllegalArgumentException(
+                                "-classpath requires argument");
+                    }
+                } else if (arg.startsWith("--setgroups=")) {
+                    if (gids != null) {
+                        throw new IllegalArgumentException(
+                                "Duplicate arg specified");
+                    }
+
+                    String[] params
+                            = arg.substring(arg.indexOf('=') + 1).split(",");
+
+                    gids = new int[params.length];
+
+                    for (int i = params.length - 1; i >= 0 ; i--) {
+                        gids[i] = Integer.parseInt(params[i]);
+                    }
+                } else {
+                    break;
+                }
+            }
+
+            if (runtimeInit && classpath != null) {
+                throw new IllegalArgumentException(
+                        "--runtime-init and -classpath are incompatible");
+            }
+
+            remainingArgs = new String[args.length - curArg];
+
+            System.arraycopy(args, curArg, remainingArgs, 0,
+                    remainingArgs.length);
+        }
+    }
+
+    /**
+     * Reads an argument list from the command socket/
+     * @return Argument list or null if EOF is reached
+     * @throws IOException passed straight through
+     */
+    private String[] readArgumentList()
+            throws IOException {
+
+        /**
+         * See android.os.Process.zygoteSendArgsAndGetPid()
+         * Presently the wire format to the zygote process is:
+         * a) a count of arguments (argc, in essence)
+         * b) a number of newline-separated argument strings equal to count
+         *
+         * After the zygote process reads these it will write the pid of
+         * the child or -1 on failure.
+         */
+
+        int argc;
+
+        try {
+            String s = mSocketReader.readLine();
+
+            if (s == null) {
+                // EOF reached.
+                return null;
+            }
+            argc = Integer.parseInt(s);
+        } catch (NumberFormatException ex) {
+            Log.e(TAG, "invalid Zygote wire format: non-int at argc");
+            throw new IOException("invalid wire format");
+        }
+
+        // See bug 1092107: large argc can be used for a DOS attack
+        if (argc > MAX_ZYGOTE_ARGC) {   
+            throw new IOException("max arg count exceeded");
+        }
+
+        String[] result = new String[argc];
+        for (int i = 0; i < argc; i++) {
+            result[i] = mSocketReader.readLine();
+            if (result[i] == null) {
+                // We got an unexpected EOF.
+                throw new IOException("truncated request");
+            }
+        }
+
+        return result;
+    }
+
+    /**
+     * Applies zygote security policy per bugs #875058 and #1082165. 
+     * Based on the credentials of the process issuing a zygote command:
+     * <ol>
+     * <li> uid 0 (root) may specify any uid, gid, and setgroups() list
+     * <li> uid 1000 (Process.SYSTEM_UID) may specify any uid &gt; 1000 in normal
+     * operation. It may also specify any gid and setgroups() list it chooses.
+     * In factory test mode, it may specify any UID.
+     * <li> Any other uid may not specify any uid, gid, or setgroups list. The
+     * uid and gid will be inherited from the requesting process.
+     * </ul>
+     *
+     * @param args non-null; zygote spawner arguments
+     * @param peer non-null; peer credentials
+     * @throws ZygoteSecurityException
+     */
+    private static void applyUidSecurityPolicy(Arguments args, Credentials peer)
+            throws ZygoteSecurityException {
+
+        int peerUid = peer.getUid();
+
+        if (peerUid == 0) {
+            // Root can do what it wants
+        } else if (peerUid == Process.SYSTEM_UID ) {
+            // System UID is restricted, except in factory test mode
+            String factoryTest = SystemProperties.get("ro.factorytest");
+            boolean uidRestricted;
+
+            /* In normal operation, SYSTEM_UID can only specify a restricted
+             * set of UIDs. In factory test mode, SYSTEM_UID may specify any uid.
+             */
+            uidRestricted  
+                 = !(factoryTest.equals("1") || factoryTest.equals("2"));
+
+            if (uidRestricted
+                    && args.uidSpecified && (args.uid < Process.SYSTEM_UID)) {
+                throw new ZygoteSecurityException(
+                        "System UID may not launch process with UID < "
+                                + Process.SYSTEM_UID);
+            }
+        } else {
+            // Everything else
+            if (args.uidSpecified || args.gidSpecified
+                || args.gids != null) {
+                throw new ZygoteSecurityException(
+                        "App UIDs may not specify uid's or gid's");
+            }
+        }
+
+        // If not otherwise specified, uid and gid are inherited from peer
+        if (!args.uidSpecified) {
+            args.uid = peer.getUid();
+            args.uidSpecified = true;
+        }
+        if (!args.gidSpecified) {
+            args.gid = peer.getGid();
+            args.gidSpecified = true;
+        }
+    }
+
+
+    /**
+     * Applies debugger security policy.
+     * If "ro.debuggable" is "1", all apps are debuggable. Otherwise,
+     * the debugger state is specified via the "--enable-debugger" flag
+     * in the spawn request.
+     *
+     * @param args non-null; zygote spawner args
+     */
+    private static void applyDebuggerSecurityPolicy(Arguments args) {
+        if ("1".equals(SystemProperties.get("ro.debuggable"))) {
+            args.debugFlags |= Zygote.DEBUG_ENABLE_DEBUGGER;
+        }
+    }
+
+    /**
+     * Applies zygote security policy per bug #1042973. Based on the credentials
+     * of the process issuing a zygote command:
+     * <ol>
+     * <li> peers of  uid 0 (root) and uid 1000 (Process.SYSTEM_UID)
+     * may specify any rlimits.
+     * <li> All other uids may not specify rlimits.
+     * </ul>
+     * @param args non-null; zygote spawner arguments
+     * @param peer non-null; peer credentials
+     * @throws ZygoteSecurityException
+     */
+    private static void applyRlimitSecurityPolicy(
+            Arguments args, Credentials peer)
+            throws ZygoteSecurityException {
+
+        int peerUid = peer.getUid();
+
+        if (!(peerUid == 0 || peerUid == Process.SYSTEM_UID)) {
+            // All peers with UID other than root or SYSTEM_UID
+            if (args.rlimits != null) {
+                throw new ZygoteSecurityException(
+                        "This UID may not specify rlimits.");
+            }
+        }
+    }
+
+    /**
+     * Applies zygote security policy per bug #1042973. A root peer may
+     * spawn an instance with any capabilities. All other uids may spawn
+     * instances with any of the capabilities in the peer's permitted set
+     * but no more.
+     *
+     * @param args non-null; zygote spawner arguments
+     * @param peer non-null; peer credentials
+     * @throws ZygoteSecurityException
+     */
+    private static void applyCapabilitiesSecurityPolicy(
+            Arguments args, Credentials peer)
+            throws ZygoteSecurityException {
+
+        if (args.permittedCapabilities == 0
+                && args.effectiveCapabilities == 0) {
+            // nothing to check
+            return;
+        }
+
+        if (peer.getUid() == 0) {
+            // root may specify anything
+            return;
+        }
+
+        long permittedCaps;
+
+        try {
+            permittedCaps = ZygoteInit.capgetPermitted(peer.getPid());
+        } catch (IOException ex) {
+            throw new ZygoteSecurityException(
+                    "Error retrieving peer's capabilities.");
+        }
+
+        /*
+         * Ensure that the client did not specify an effective set larger
+         * than the permitted set. The kernel will enforce this too, but we
+         * do it here to make the following check easier.
+         */
+        if (((~args.permittedCapabilities) & args.effectiveCapabilities) != 0) {
+            throw new ZygoteSecurityException(
+                    "Effective capabilities cannot be superset of "
+                            + " permitted capabilities" );
+        }
+
+        /*
+         * Ensure that the new permitted (and thus the new effective) set is
+         * a subset of the peer process's permitted set
+         */
+
+        if (((~permittedCaps) & args.permittedCapabilities) != 0) {
+            throw new ZygoteSecurityException(
+                    "Peer specified unpermitted capabilities" );
+        }
+    }
+
+    /**
+     * Handles post-fork setup of child proc, closing sockets as appropriate,
+     * reopen stdio as appropriate, and ultimately throwing MethodAndArgsCaller
+     * if successful or returning if failed.
+     *
+     * @param parsedArgs non-null; zygote args
+     * @param descriptors null-ok; new file descriptors for stdio if available.
+     * @param newStderr null-ok; stream to use for stderr until stdio
+     * is reopened.
+     *
+     * @throws ZygoteInit.MethodAndArgsCaller on success to
+     * trampoline to code that invokes static main.
+     */
+    private void handleChildProc(Arguments parsedArgs,
+            FileDescriptor[] descriptors, PrintStream newStderr)
+            throws ZygoteInit.MethodAndArgsCaller {
+
+        /*
+         * First, set the capabilities if necessary
+         */
+
+        if (parsedArgs.uid != 0) {
+            try {
+                ZygoteInit.setCapabilities(parsedArgs.permittedCapabilities,
+                        parsedArgs.effectiveCapabilities);
+            } catch (IOException ex) {
+                Log.e(TAG, "Error setting capabilities", ex);
+            }
+        }
+
+        /*
+         * Close the socket, unless we're in "peer wait" mode, in which
+         * case it's used to track the liveness of this process.
+         */
+
+        if (parsedArgs.peerWait) {
+            try {
+                ZygoteInit.setCloseOnExec(mSocket.getFileDescriptor(), true);
+                sPeerWaitSocket = mSocket;
+            } catch (IOException ex) {
+                Log.e(TAG, "Zygote Child: error setting peer wait "
+                        + "socket to be close-on-exec", ex);
+            }
+        } else {
+            closeSocket();
+            ZygoteInit.closeServerSocket();
+        }
+
+        if (descriptors != null) {
+            try {
+                ZygoteInit.reopenStdio(descriptors[0],
+                        descriptors[1], descriptors[2]);
+
+                for (FileDescriptor fd: descriptors) {
+                    ZygoteInit.closeDescriptor(fd);
+                }
+                newStderr = System.err;
+            } catch (IOException ex) {
+                Log.e(TAG, "Error reopening stdio", ex);
+            }
+        }
+
+        if (parsedArgs.runtimeInit) {
+            RuntimeInit.zygoteInit(parsedArgs.remainingArgs);
+        } else {
+            ClassLoader cloader;
+
+            if (parsedArgs.classpath != null) {
+                cloader
+                    = new PathClassLoader(parsedArgs.classpath,
+                    ClassLoader.getSystemClassLoader());
+            } else {
+                cloader = ClassLoader.getSystemClassLoader();
+            }
+
+            String className;
+            try {
+                className = parsedArgs.remainingArgs[0];
+            } catch (ArrayIndexOutOfBoundsException ex) {
+                logAndPrintError (newStderr,
+                        "Missing required class name argument", null);
+                return;
+            }
+            String[] mainArgs
+                    = new String[parsedArgs.remainingArgs.length - 1];
+
+            System.arraycopy(parsedArgs.remainingArgs, 1,
+                    mainArgs, 0, mainArgs.length);
+
+            try {
+                ZygoteInit.invokeStaticMain(cloader, className, mainArgs);
+            } catch (RuntimeException ex) {
+                logAndPrintError (newStderr, "Error starting. ", ex);
+            }
+        }
+    }
+
+    /**
+     * Handles post-fork cleanup of parent proc
+     *
+     * @param pid != 0; pid of child if &gt; 0 or indication of failed fork
+     * if &lt; 0;
+     * @param descriptors null-ok; file descriptors for child's new stdio if
+     * specified.
+     * @param parsedArgs non-null; zygote args
+     * @return true for "exit command loop" and false for "continue command
+     * loop"
+     */
+    private boolean handleParentProc(int pid,
+            FileDescriptor[] descriptors, Arguments parsedArgs) {
+
+        if(pid > 0) {
+            // Try to move the new child into the peer's process group.
+            try {
+                ZygoteInit.setpgid(pid, ZygoteInit.getpgid(peer.getPid()));
+            } catch (IOException ex) {
+                // This exception is expected in the case where
+                // the peer is not in our session
+                // TODO get rid of this log message in the case where
+                // getsid(0) != getsid(peer.getPid())
+                Log.i(TAG, "Zygote: setpgid failed. This is "
+                    + "normal if peer is not in our session");
+            }
+        }
+
+        try {
+            if (descriptors != null) {
+                for (FileDescriptor fd: descriptors) {
+                    ZygoteInit.closeDescriptor(fd);
+                }
+            }
+        } catch (IOException ex) {
+            Log.e(TAG, "Error closing passed descriptors in "
+                    + "parent process", ex);
+        }
+
+        try {
+            mSocketOutStream.writeInt(pid);
+        } catch (IOException ex) {
+            Log.e(TAG, "Error reading from command socket", ex);
+            return true;
+        }
+
+        /*
+         * If the peer wants to use the socket to wait on the
+         * newly spawned process, then we're all done.
+         */
+        if (parsedArgs.peerWait) {
+            try {
+                mSocket.close();
+            } catch (IOException ex) {
+                Log.e(TAG, "Zygote: error closing sockets", ex);
+            }
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Logs an error message and prints it to the specified stream, if
+     * provided
+     *
+     * @param newStderr null-ok; a standard error stream
+     * @param message non-null; error message
+     * @param ex null-ok an exception
+     */
+    private static void logAndPrintError (PrintStream newStderr,
+            String message, Throwable ex) {
+        Log.e(TAG, message, ex);
+        if (newStderr != null) {
+            newStderr.println(message + (ex == null ? "" : ex));
+        }
+    }
+}
diff --git a/core/java/com/android/internal/os/ZygoteInit.java b/core/java/com/android/internal/os/ZygoteInit.java
new file mode 100644
index 0000000..ac8b589
--- /dev/null
+++ b/core/java/com/android/internal/os/ZygoteInit.java
@@ -0,0 +1,796 @@
+/*
+ * Copyright (C) 2007 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.internal.os;
+
+import android.content.pm.ActivityInfo;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.content.res.ColorStateList;
+import android.graphics.drawable.Drawable;
+import android.net.LocalServerSocket;
+import android.os.Debug;
+import android.os.SystemClock;
+import android.os.SystemProperties;
+import android.util.Config;
+import android.util.EventLog;
+import android.util.Log;
+
+import dalvik.system.VMRuntime;
+import dalvik.system.Zygote;
+
+import java.io.BufferedReader;
+import java.io.FileDescriptor;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.ArrayList;
+
+/**
+ * Startup class for the zygote process.
+ *
+ * Pre-initializes some classes, and then waits for commands on a UNIX domain
+ * socket. Based on these commands, forks of child processes that inherit
+ * the initial state of the VM.
+ *
+ * Please see {@link ZygoteConnection.Arguments} for documentation on the
+ * client protocol.
+ *
+ * @hide
+ */
+public class ZygoteInit {
+
+    private static final String TAG = "Zygote";
+
+    private static final String ANDROID_SOCKET_ENV = "ANDROID_SOCKET_zygote";
+
+    private static final int LOG_BOOT_PROGRESS_PRELOAD_START = 3020;
+    private static final int LOG_BOOT_PROGRESS_PRELOAD_END = 3030;
+
+    /** when preloading, GC after allocating this many bytes */
+    private static final int PRELOAD_GC_THRESHOLD = 50000;
+
+    private static LocalServerSocket sServerSocket;
+
+    /**
+     * Used to pre-load resources.  We hold a global reference on it so it
+     * never gets destroyed.
+     */
+    private static Resources mResources;
+    
+    /**
+     * The number of times that the main Zygote loop
+     * should run before calling gc() again.
+     */
+    static final int GC_LOOP_COUNT = 10;
+
+    /**
+     * If true, zygote forks for each peer. If false, a select loop is used
+     * inside a single process. The latter is preferred.
+     */
+    private static final boolean ZYGOTE_FORK_MODE = false;
+
+    /**
+     * The name of a resource file that contains classes to preload.
+     */
+    private static final String PRELOADED_CLASSES = "preloaded-classes";
+
+    /** Controls whether we should preload resources during zygote init. */
+    private static final boolean PRELOAD_RESOURCES = true;
+    
+    /**
+     * Invokes a static "main(argv[]) method on class "className".
+     * Converts various failing exceptions into RuntimeExceptions, with
+     * the assumption that they will then cause the VM instance to exit.
+     *
+     * @param loader class loader to use
+     * @param className Fully-qualified class name
+     * @param argv Argument vector for main()
+     */
+    static void invokeStaticMain(ClassLoader loader,
+            String className, String[] argv)
+            throws ZygoteInit.MethodAndArgsCaller {
+        Class<?> cl;
+
+        try {
+            cl = loader.loadClass(className);
+        } catch (ClassNotFoundException ex) {
+            throw new RuntimeException(
+                    "Missing class when invoking static main " + className,
+                    ex);
+        }
+
+        Method m;
+        try {
+            m = cl.getMethod("main", new Class[] { String[].class });
+        } catch (NoSuchMethodException ex) {
+            throw new RuntimeException(
+                    "Missing static main on " + className, ex);
+        } catch (SecurityException ex) {
+            throw new RuntimeException(
+                    "Problem getting static main on " + className, ex);
+        }
+
+        int modifiers = m.getModifiers();
+        if (! (Modifier.isStatic(modifiers) && Modifier.isPublic(modifiers))) {
+            throw new RuntimeException(
+                    "Main method is not public and static on " + className);
+        }
+
+        /*
+         * This throw gets caught in ZygoteInit.main(), which responds
+         * by invoking the exception's run() method. This arrangement
+         * clears up all the stack frames that were required in setting
+         * up the process.
+         */
+        throw new ZygoteInit.MethodAndArgsCaller(m, argv);
+    }
+
+    /**
+     * Registers a server socket for zygote command connections
+     *
+     * @throws RuntimeException when open fails
+     */
+    private static void registerZygoteSocket() {
+        if (sServerSocket == null) {
+            int fileDesc;
+            try {
+                String env = System.getenv(ANDROID_SOCKET_ENV);
+                fileDesc = Integer.parseInt(env);
+            } catch (RuntimeException ex) {
+                throw new RuntimeException(
+                        ANDROID_SOCKET_ENV + " unset or invalid", ex);
+            }
+
+            try {
+                sServerSocket = new LocalServerSocket(
+                        createFileDescriptor(fileDesc));
+            } catch (IOException ex) {
+                throw new RuntimeException(
+                        "Error binding to local socket '" + fileDesc + "'", ex);
+            }
+        }
+    }
+
+    /**
+     * Waits for and accepts a single command connection. Throws
+     * RuntimeException on failure.
+     */
+    private static ZygoteConnection acceptCommandPeer() {
+        try {            
+            return new ZygoteConnection(sServerSocket.accept());
+        } catch (IOException ex) {
+            throw new RuntimeException(
+                    "IOException during accept()", ex);
+        }
+    }
+
+    /**
+     * Close and clean up zygote sockets. Called on shutdown and on the
+     * child's exit path.
+     */
+    static void closeServerSocket() {
+        try {
+            if (sServerSocket != null) {
+                sServerSocket.close();
+            }
+        } catch (IOException ex) {
+            Log.e(TAG, "Zygote:  error closing sockets", ex);
+        }
+
+        sServerSocket = null;
+    }
+
+    private static final int UNPRIVILEGED_UID = 9999;
+    private static final int UNPRIVILEGED_GID = 9999;
+
+    private static final int ROOT_UID = 0;
+    private static final int ROOT_GID = 0;
+
+    /**
+     * Sets effective user ID.
+     */
+    private static void setEffectiveUser(int uid) {
+        int errno = setreuid(ROOT_UID, uid);
+        if (errno != 0) {
+            Log.e(TAG, "setreuid() failed. errno: " + errno);
+        }
+    }
+
+    /**
+     * Sets effective group ID.
+     */
+    private static void setEffectiveGroup(int gid) {
+        int errno = setregid(ROOT_GID, gid);
+        if (errno != 0) {
+            Log.e(TAG, "setregid() failed. errno: " + errno);
+        }
+    }
+
+    /**
+     * Performs Zygote process initialization. Loads and initializes
+     * commonly used classes.
+     *
+     * Most classes only cause a few hundred bytes to be allocated, but
+     * a few will allocate a dozen Kbytes (in one case, 500+K).
+     */
+    private static void preloadClasses() {
+        final VMRuntime runtime = VMRuntime.getRuntime();
+        
+        InputStream is = ZygoteInit.class.getClassLoader().getResourceAsStream(
+                PRELOADED_CLASSES);
+        if (is == null) {
+            Log.e(TAG, "Couldn't find " + PRELOADED_CLASSES + ".");
+        } else {
+            Log.i(TAG, "Preloading classes...");
+            long startTime = SystemClock.uptimeMillis();
+            
+            // Drop root perms while running static initializers.
+            setEffectiveGroup(UNPRIVILEGED_GID);
+            setEffectiveUser(UNPRIVILEGED_UID);
+
+            // Alter the target heap utilization.  With explicit GCs this
+            // is not likely to have any effect.
+            float defaultUtilization = runtime.getTargetHeapUtilization();
+            runtime.setTargetHeapUtilization(0.8f);
+
+            // Start with a clean slate.
+            runtime.gcSoftReferences();
+            runtime.runFinalizationSync();
+            Debug.startAllocCounting();
+
+            try {
+                BufferedReader br 
+                    = new BufferedReader(new InputStreamReader(is), 256);
+
+                int count = 0;
+                String line;
+                String missingClasses = null;
+                while ((line = br.readLine()) != null) {
+                    // Skip comments and blank lines.
+                    line = line.trim();
+                    if (line.startsWith("#") || line.equals("")) {
+                        continue;
+                    }
+
+                    try {
+                        if (Config.LOGV) {
+                            Log.v(TAG, "Preloading " + line + "...");
+                        }
+                        Class.forName(line);
+                        if (Debug.getGlobalAllocSize() > PRELOAD_GC_THRESHOLD) {
+                            if (Config.LOGV) {
+                                Log.v(TAG,
+                                    " GC at " + Debug.getGlobalAllocSize());
+                            }
+                            runtime.gcSoftReferences();
+                            runtime.runFinalizationSync();
+                            Debug.resetGlobalAllocSize();
+                        }
+                        count++;
+                    } catch (ClassNotFoundException e) {
+                        Log.e(TAG, "Class not found for preloading: " + line);
+                        if (missingClasses == null) {
+                            missingClasses = line;
+                        } else {
+                            missingClasses += " " + line;
+                        }
+                    }
+                }
+
+                if (missingClasses != null &&
+                        "1".equals(SystemProperties.get("persist.service.adb.enable"))) {
+                    throw new IllegalStateException(
+                            "Missing class(es) for preloading, update preloaded-classes ["
+                            + missingClasses + "]");
+                }
+
+                Log.i(TAG, "...preloaded " + count + " classes in "
+                        + (SystemClock.uptimeMillis()-startTime) + "ms.");
+            } catch (IOException e) {
+                Log.e(TAG, "Error reading " + PRELOADED_CLASSES + ".", e);
+            } finally {
+                // Restore default.
+                runtime.setTargetHeapUtilization(defaultUtilization);
+
+                Debug.stopAllocCounting();
+
+                // Bring back root. We'll need it later.
+                setEffectiveUser(ROOT_UID);
+                setEffectiveGroup(ROOT_GID);
+            }
+        }
+    }
+
+    /**
+     * Load in commonly used resources, so they can be shared across
+     * processes.
+     *
+     * These tend to be a few Kbytes, but are frequently in the 20-40K
+     * range, and occasionally even larger.
+     */
+    private static void preloadResources() {
+        final VMRuntime runtime = VMRuntime.getRuntime();
+        
+        Debug.startAllocCounting();
+        try {
+            runtime.gcSoftReferences();
+            runtime.runFinalizationSync();
+            mResources = Resources.getSystem();
+            mResources.startPreloading();
+            if (PRELOAD_RESOURCES) {
+                Log.i(TAG, "Preloading resources...");
+
+                long startTime = SystemClock.uptimeMillis();
+                TypedArray ar = mResources.obtainTypedArray(
+                        com.android.internal.R.array.preloaded_drawables);
+                int N = preloadDrawables(runtime, ar);
+                Log.i(TAG, "...preloaded " + N + " resources in "
+                        + (SystemClock.uptimeMillis()-startTime) + "ms.");
+
+                startTime = SystemClock.uptimeMillis();
+                ar = mResources.obtainTypedArray(
+                        com.android.internal.R.array.preloaded_color_state_lists);
+                N = preloadColorStateLists(runtime, ar);
+                Log.i(TAG, "...preloaded " + N + " resources in "
+                        + (SystemClock.uptimeMillis()-startTime) + "ms.");
+            }
+            mResources.finishPreloading();
+        } catch (RuntimeException e) {
+            Log.w(TAG, "Failure preloading resources", e);
+        } finally {
+            Debug.stopAllocCounting();
+        }
+    }
+
+    private static int preloadColorStateLists(VMRuntime runtime, TypedArray ar) {
+        int N = ar.length();
+        for (int i=0; i<N; i++) {
+            if (Debug.getGlobalAllocSize() > PRELOAD_GC_THRESHOLD) {
+                if (Config.LOGV) {
+                    Log.v(TAG, " GC at " + Debug.getGlobalAllocSize());
+                }
+                runtime.gcSoftReferences();
+                runtime.runFinalizationSync();
+                Debug.resetGlobalAllocSize();
+            }
+            int id = ar.getResourceId(i, 0);
+            if (Config.LOGV) {
+                Log.v(TAG, "Preloading resource #" + Integer.toHexString(id));
+            }
+            if (id != 0) {
+                mResources.getColorStateList(id);
+            }
+        }
+        return N;
+    }
+
+
+    private static int preloadDrawables(VMRuntime runtime, TypedArray ar) {
+        int N = ar.length();
+        for (int i=0; i<N; i++) {
+            if (Debug.getGlobalAllocSize() > PRELOAD_GC_THRESHOLD) {
+                if (Config.LOGV) {
+                    Log.v(TAG, " GC at " + Debug.getGlobalAllocSize());
+                }
+                runtime.gcSoftReferences();
+                runtime.runFinalizationSync();
+                Debug.resetGlobalAllocSize();
+            }
+            int id = ar.getResourceId(i, 0);
+            if (Config.LOGV) {
+                Log.v(TAG, "Preloading resource #" + Integer.toHexString(id));
+            }
+            if (id != 0) {
+                Drawable dr = mResources.getDrawable(id);
+                if ((dr.getChangingConfigurations()&~ActivityInfo.CONFIG_FONT_SCALE) != 0) {
+                    Log.w(TAG, "Preloaded drawable resource #0x"
+                            + Integer.toHexString(id)
+                            + " (" + ar.getString(i) + ") that varies with configuration!!");
+                }
+            }
+        }
+        return N;
+    }
+
+    /**
+     * Runs several special GCs to try to clean up a few generations of
+     * softly- and final-reachable objects, along with any other garbage.
+     * This is only useful just before a fork().
+     */
+    /*package*/ static void gc() {
+        final VMRuntime runtime = VMRuntime.getRuntime();
+
+        /* runFinalizationSync() lets finalizers be called in Zygote,
+         * which doesn't have a HeapWorker thread.
+         */
+        runtime.gcSoftReferences();
+        runtime.runFinalizationSync();
+        runtime.gcSoftReferences();
+        runtime.runFinalizationSync();
+        runtime.gcSoftReferences();
+        runtime.runFinalizationSync();
+    }
+
+    /**
+     * Finish remaining work for the newly forked system server process.
+     */
+    private static void handleSystemServerProcess(
+            ZygoteConnection.Arguments parsedArgs)
+            throws ZygoteInit.MethodAndArgsCaller {
+        /*
+         * First, set the capabilities if necessary
+         */
+
+        if (parsedArgs.uid != 0) {
+            try {
+                setCapabilities(parsedArgs.permittedCapabilities,
+                                parsedArgs.effectiveCapabilities);
+            } catch (IOException ex) {
+                Log.e(TAG, "Error setting capabilities", ex);
+            }
+        }
+
+        closeServerSocket();
+
+        /*
+         * Pass the remaining arguments to SystemServer.
+         * "--nice-name=system_server com.android.server.SystemServer"
+         */
+        RuntimeInit.zygoteInit(parsedArgs.remainingArgs);
+        /* should never reach here */
+    }
+
+    /**
+     * Prepare the arguments and fork for the system server process.
+     */
+    private static boolean startSystemServer() 
+            throws MethodAndArgsCaller, RuntimeException {
+        /* Hardcoded command line to start the system server */
+        String args[] = {
+            "--setuid=1000",
+            "--setgid=1000",
+            "--setgroups=1001,1002,1003,1004,1005,1006,1007,1008,1009,1010,3001,3002,3003",
+            "--capabilities=121715744,121715744",
+            "--runtime-init",
+            "--nice-name=system_server",
+            "com.android.server.SystemServer",
+        };
+        ZygoteConnection.Arguments parsedArgs = null;
+
+        int pid;
+
+        try {
+            parsedArgs = new ZygoteConnection.Arguments(args);
+
+            /*
+             * Enable debugging of the system process if *either* the command line flags
+             * indicate it should be debuggable or the ro.debuggable system property
+             * is set to "1"
+             */
+            int debugFlags = parsedArgs.debugFlags;
+            if ("1".equals(SystemProperties.get("ro.debuggable")))
+                debugFlags |= Zygote.DEBUG_ENABLE_DEBUGGER;
+
+            /* Request to fork the system server process */
+            pid = Zygote.forkSystemServer(
+                    parsedArgs.uid, parsedArgs.gid,
+                    parsedArgs.gids, debugFlags, null);
+        } catch (IllegalArgumentException ex) {
+            throw new RuntimeException(ex);
+        } 
+ 
+        /* For child process */
+        if (pid == 0) {
+            handleSystemServerProcess(parsedArgs);
+        }
+
+        return true;
+    }
+
+    public static void main(String argv[]) {
+        try {
+            registerZygoteSocket();
+            EventLog.writeEvent(LOG_BOOT_PROGRESS_PRELOAD_START,
+                SystemClock.uptimeMillis());
+            preloadClasses();
+            preloadResources();
+            EventLog.writeEvent(LOG_BOOT_PROGRESS_PRELOAD_END,
+                SystemClock.uptimeMillis());
+
+            // Do an initial gc to clean up after startup
+            gc();
+
+            // If requested, start system server directly from Zygote
+            if (argv.length != 2) {
+                throw new RuntimeException(
+                        "ZygoteInit.main expects two arguments");
+            }
+
+            if (argv[1].equals("true")) {
+                startSystemServer();
+            }
+
+            Log.i(TAG, "Accepting command socket connections");
+
+            if (ZYGOTE_FORK_MODE) {
+                runForkMode();
+            } else {
+                runSelectLoopMode();
+            }
+
+            closeServerSocket();
+        } catch (MethodAndArgsCaller caller) {
+            caller.run();
+        } catch (RuntimeException ex) {
+            Log.e(TAG, "Zygote died with exception", ex);
+            closeServerSocket();
+            throw ex;
+        }
+    }
+
+    /**
+     * Runs the zygote in accept-and-fork mode. In this mode, each peer
+     * gets its own zygote spawner process. This code is retained for
+     * reference only.
+     *
+     * @throws MethodAndArgsCaller in a child process when a main() should
+     * be executed.
+     */
+    private static void runForkMode() throws MethodAndArgsCaller {
+        while (true) {
+            ZygoteConnection peer = acceptCommandPeer();
+
+            int pid;
+
+            pid = Zygote.fork();
+
+            if (pid == 0) {
+                // The child process should handle the peer requests
+
+                // The child does not accept any more connections
+                try {
+                    sServerSocket.close();
+                } catch (IOException ex) {
+                    Log.e(TAG, "Zygote Child: error closing sockets", ex);
+                } finally {
+                    sServerSocket = null;
+                }
+
+                peer.run();
+                break;
+            } else if (pid > 0) {
+                peer.closeSocket();
+            } else {
+                throw new RuntimeException("Error invoking fork()");
+            }
+        }
+    }
+
+    /**
+     * Runs the zygote process's select loop. Accepts new connections as
+     * they happen, and reads commands from connections one spawn-request's
+     * worth at a time.
+     *
+     * @throws MethodAndArgsCaller in a child process when a main() should
+     * be executed.
+     */
+    private static void runSelectLoopMode() throws MethodAndArgsCaller {
+        ArrayList<FileDescriptor> fds = new ArrayList();
+        ArrayList<ZygoteConnection> peers = new ArrayList();
+        FileDescriptor[] fdArray = new FileDescriptor[4];
+
+        fds.add(sServerSocket.getFileDescriptor());
+        peers.add(null);
+
+        int loopCount = GC_LOOP_COUNT;
+        while (true) {
+            int index;
+
+            /*
+             * Call gc() before we block in select().
+             * It's work that has to be done anyway, and it's better
+             * to avoid making every child do it.  It will also
+             * madvise() any free memory as a side-effect.
+             *
+             * Don't call it every time, because walking the entire
+             * heap is a lot of overhead to free a few hundred bytes.
+             */
+            if (loopCount <= 0) {
+                gc();
+                loopCount = GC_LOOP_COUNT;
+            } else {
+                loopCount--;
+            }
+
+
+            try {
+                fdArray = fds.toArray(fdArray);
+                index = selectReadable(fdArray);
+            } catch (IOException ex) {
+                throw new RuntimeException("Error in select()", ex);
+            }
+
+            if (index < 0) {
+                throw new RuntimeException("Error in select()");
+            } else if (index == 0) {
+                ZygoteConnection newPeer = acceptCommandPeer();
+                peers.add(newPeer);
+                fds.add(newPeer.getFileDesciptor());
+            } else {
+                boolean done;
+                done = peers.get(index).runOnce();
+
+                if (done) {
+                    peers.remove(index);
+                    fds.remove(index);
+                }
+            }
+        }
+    }
+
+    /**
+     * The Linux syscall "setreuid()"
+     * @param ruid real uid
+     * @param euid effective uid
+     * @return 0 on success, non-zero errno on fail
+     */
+    static native int setreuid(int ruid, int euid);
+
+    /**
+     * The Linux syscall "setregid()"
+     * @param rgid real gid
+     * @param egid effective gid
+     * @return 0 on success, non-zero errno on fail
+     */
+    static native int setregid(int rgid, int egid);
+
+    /**
+     * Invokes the linux syscall "setpgid"
+     *
+     * @param pid pid to change
+     * @param pgid new process group of pid
+     * @return 0 on success or non-zero errno on fail
+     */
+    static native int setpgid(int pid, int pgid);
+
+    /**
+     * Invokes the linux syscall "getpgid"
+     *
+     * @param pid pid to query
+     * @return pgid of pid in question
+     * @throws IOException on error
+     */
+    static native int getpgid(int pid) throws IOException;
+
+    /**
+     * Invokes the syscall dup2() to copy the specified descriptors into
+     * stdin, stdout, and stderr. The existing stdio descriptors will be
+     * closed and errors during close will be ignored. The specified
+     * descriptors will also remain open at their original descriptor numbers,
+     * so the caller may want to close the original descriptors.
+     *
+     * @param in new stdin
+     * @param out new stdout
+     * @param err new stderr
+     * @throws IOException
+     */
+    static native void reopenStdio(FileDescriptor in,
+            FileDescriptor out, FileDescriptor err) throws IOException;
+
+    /**
+     * Calls close() on a file descriptor
+     *
+     * @param fd descriptor to close
+     * @throws IOException
+     */
+    static native void closeDescriptor(FileDescriptor fd)
+            throws IOException;
+
+    /**
+     * Toggles the close-on-exec flag for the specified file descriptor.
+     *
+     * @param fd non-null; file descriptor
+     * @param flag desired close-on-exec flag state
+     * @throws IOException
+     */
+    static native void setCloseOnExec(FileDescriptor fd, boolean flag)
+            throws IOException;
+
+    /**
+     * Retrieves the permitted capability set from another process.
+     *
+     * @param pid &gt;=0 process ID or 0 for this process
+     * @throws IOException on error
+     */
+    static native long capgetPermitted(int pid)
+            throws IOException;
+
+    /**
+     * Sets the permitted and effective capability sets of this process.
+     *
+     * @param permittedCapabilities permitted set
+     * @param effectiveCapabilities effective set
+     * @throws IOException on error
+     */
+    static native void setCapabilities(
+            long permittedCapabilities,
+            long effectiveCapabilities) throws IOException;
+
+    /**
+     * Invokes select() on the provider array of file descriptors (selecting
+     * for readability only). Array elements of null are ignored.
+     *
+     * @param fds non-null; array of readable file descriptors
+     * @return index of descriptor that is now readable or -1 for empty array.
+     * @throws IOException if an error occurs
+     */
+    static native int selectReadable(FileDescriptor[] fds) throws IOException;
+
+    /**
+     * Creates a file descriptor from an int fd.
+     *
+     * @param fd integer OS file descriptor
+     * @return non-null; FileDescriptor instance
+     * @throws IOException if fd is invalid
+     */
+    static native FileDescriptor createFileDescriptor(int fd)
+            throws IOException;
+
+    /**
+     * Class not instantiable.
+     */
+    private ZygoteInit() {
+    }
+
+    /**
+     * Helper exception class which holds a method and arguments and
+     * can call them. This is used as part of a trampoline to get rid of
+     * the initial process setup stack frames.
+     */
+    public static class MethodAndArgsCaller extends Exception
+            implements Runnable {
+        /** method to call */
+        private final Method mMethod;
+
+        /** argument array */
+        private final String[] mArgs;
+
+        public MethodAndArgsCaller(Method method, String[] args) {
+            mMethod = method;
+            mArgs = args;
+        }
+
+        public void run() {
+            try {
+                mMethod.invoke(null, new Object[] { mArgs });
+            } catch (IllegalAccessException ex) {
+                throw new RuntimeException(ex);
+            } catch (InvocationTargetException ex) {
+                Throwable cause = ex.getCause();
+                if (cause instanceof RuntimeException) {
+                    throw (RuntimeException) cause;
+                } else if (cause instanceof Error) {
+                    throw (Error) cause;
+                }
+                throw new RuntimeException(ex);
+            }
+        }
+    }
+}
diff --git a/core/java/com/android/internal/os/ZygoteSecurityException.java b/core/java/com/android/internal/os/ZygoteSecurityException.java
new file mode 100644
index 0000000..13b4759
--- /dev/null
+++ b/core/java/com/android/internal/os/ZygoteSecurityException.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2007 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.internal.os;
+
+/**
+ * Exception thrown when a security policy is violated.
+ */
+class ZygoteSecurityException extends RuntimeException {
+    ZygoteSecurityException(String message) {
+        super(message);
+    }
+}
diff --git a/core/java/com/android/internal/package.html b/core/java/com/android/internal/package.html
new file mode 100644
index 0000000..db6f78b
--- /dev/null
+++ b/core/java/com/android/internal/package.html
@@ -0,0 +1,3 @@
+<body>
+{@hide}
+</body>
\ No newline at end of file
diff --git a/core/java/com/android/internal/policy/IPolicy.java b/core/java/com/android/internal/policy/IPolicy.java
new file mode 100644
index 0000000..73db0b7
--- /dev/null
+++ b/core/java/com/android/internal/policy/IPolicy.java
@@ -0,0 +1,36 @@
+/*
+ * 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.internal.policy;
+
+import android.content.Context;
+import android.view.LayoutInflater;
+import android.view.Window;
+import android.view.WindowManagerPolicy;
+
+/**
+ * {@hide}
+ */
+
+/* The implementation of this interface must be called Policy and contained
+ * within the com.android.internal.policy.impl package */
+public interface IPolicy {
+    public Window makeNewWindow(Context context);
+
+    public LayoutInflater makeNewLayoutInflater(Context context);
+
+    public WindowManagerPolicy makeNewWindowManager();
+}
diff --git a/core/java/com/android/internal/policy/PolicyManager.java b/core/java/com/android/internal/policy/PolicyManager.java
new file mode 100644
index 0000000..4ed5a14
--- /dev/null
+++ b/core/java/com/android/internal/policy/PolicyManager.java
@@ -0,0 +1,68 @@
+/*
+ * 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.internal.policy;
+
+import android.content.Context;
+import android.view.LayoutInflater;
+import android.view.Window;
+import android.view.WindowManagerPolicy;
+
+import com.android.internal.policy.IPolicy;
+
+/**
+ * {@hide}
+ */
+
+public final class PolicyManager {
+    private static final String POLICY_IMPL_CLASS_NAME =
+        "com.android.internal.policy.impl.Policy";
+
+    private static final IPolicy sPolicy;
+
+    static {
+        // Pull in the actual implementation of the policy at run-time
+        try {
+            Class policyClass = Class.forName(POLICY_IMPL_CLASS_NAME);
+            sPolicy = (IPolicy)policyClass.newInstance();
+        } catch (ClassNotFoundException ex) {
+            throw new RuntimeException(
+                    POLICY_IMPL_CLASS_NAME + " could not be loaded", ex);
+        } catch (InstantiationException ex) {
+            throw new RuntimeException(
+                    POLICY_IMPL_CLASS_NAME + " could not be instantiated", ex);
+        } catch (IllegalAccessException ex) {
+            throw new RuntimeException(
+                    POLICY_IMPL_CLASS_NAME + " could not be instantiated", ex);
+        }
+    }
+
+    // Cannot instantiate this class
+    private PolicyManager() {}
+
+    // The static methods to spawn new policy-specific objects
+    public static Window makeNewWindow(Context context) {
+        return sPolicy.makeNewWindow(context);
+    }
+
+    public static LayoutInflater makeNewLayoutInflater(Context context) {
+        return sPolicy.makeNewLayoutInflater(context);
+    }
+
+    public static WindowManagerPolicy makeNewWindowManager() {
+        return sPolicy.makeNewWindowManager();
+    }
+}
diff --git a/core/java/com/android/internal/policy/package.html b/core/java/com/android/internal/policy/package.html
new file mode 100644
index 0000000..c9f96a6
--- /dev/null
+++ b/core/java/com/android/internal/policy/package.html
@@ -0,0 +1,5 @@
+<body>
+
+{@hide}
+
+</body>
diff --git a/core/java/com/android/internal/preference/YesNoPreference.java b/core/java/com/android/internal/preference/YesNoPreference.java
new file mode 100644
index 0000000..cf68a58
--- /dev/null
+++ b/core/java/com/android/internal/preference/YesNoPreference.java
@@ -0,0 +1,151 @@
+/*
+ * Copyright (C) 2007 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.internal.preference;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.preference.DialogPreference;
+import android.util.AttributeSet;
+
+/**
+ * The {@link YesNoPreference} is a preference to show a dialog with Yes and No
+ * buttons.
+ * <p>
+ * This preference will store a boolean into the SharedPreferences.
+ */
+public class YesNoPreference extends DialogPreference {
+    private boolean mWasPositiveResult;
+    
+    public YesNoPreference(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+    }
+
+    public YesNoPreference(Context context, AttributeSet attrs) {
+        this(context, attrs, com.android.internal.R.attr.yesNoPreferenceStyle);
+    }
+    
+    public YesNoPreference(Context context) {
+        this(context, null);
+    }
+
+    @Override
+    protected void onDialogClosed(boolean positiveResult) {
+        super.onDialogClosed(positiveResult);
+
+        if (callChangeListener(positiveResult)) {
+            setValue(positiveResult);
+        }
+    }
+
+    /**
+     * Sets the value of this preference, and saves it to the persistent store
+     * if required.
+     * 
+     * @param value The value of the preference.
+     */
+    public void setValue(boolean value) {
+        mWasPositiveResult = value;
+        
+        persistBoolean(value);
+        
+        notifyDependencyChange(!value);
+    }
+    
+    /**
+     * Gets the value of this preference.
+     * 
+     * @return The value of the preference.
+     */
+    public boolean getValue() {
+        return mWasPositiveResult;
+    }
+    
+    @Override
+    protected Object onGetDefaultValue(TypedArray a, int index) {
+        return a.getBoolean(index, false);
+    }
+
+    @Override
+    protected void onSetInitialValue(boolean restorePersistedValue, Object defaultValue) {
+        setValue(restorePersistedValue ? getPersistedBoolean(mWasPositiveResult) :
+            (Boolean) defaultValue);
+    }
+
+    @Override
+    public boolean shouldDisableDependents() {
+        return !mWasPositiveResult || super.shouldDisableDependents();
+    }
+    
+    @Override
+    protected Parcelable onSaveInstanceState() {
+        final Parcelable superState = super.onSaveInstanceState();
+        if (isPersistent()) {
+            // No need to save instance state since it's persistent
+            return superState;
+        }
+        
+        final SavedState myState = new SavedState(superState);
+        myState.wasPositiveResult = getValue();
+        return myState;
+    }
+
+    @Override
+    protected void onRestoreInstanceState(Parcelable state) {
+        if (!state.getClass().equals(SavedState.class)) {
+            // Didn't save state for us in onSaveInstanceState
+            super.onRestoreInstanceState(state);
+            return;
+        }
+         
+        SavedState myState = (SavedState) state;
+        super.onRestoreInstanceState(myState.getSuperState());
+        setValue(myState.wasPositiveResult);
+    }
+    
+    private static class SavedState extends BaseSavedState {
+        boolean wasPositiveResult;
+        
+        public SavedState(Parcel source) {
+            super(source);
+            wasPositiveResult = source.readInt() == 1;
+        }
+
+        @Override
+        public void writeToParcel(Parcel dest, int flags) {
+            super.writeToParcel(dest, flags);
+            dest.writeInt(wasPositiveResult ? 1 : 0);
+        }
+
+        public SavedState(Parcelable superState) {
+            super(superState);
+        }
+
+        public static final Parcelable.Creator<SavedState> CREATOR =
+                new Parcelable.Creator<SavedState>() {
+            public SavedState createFromParcel(Parcel in) {
+                return new SavedState(in);
+            }
+
+            public SavedState[] newArray(int size) {
+                return new SavedState[size];
+            }
+        };
+    }
+    
+}
diff --git a/core/java/com/android/internal/util/ArrayUtils.java b/core/java/com/android/internal/util/ArrayUtils.java
new file mode 100644
index 0000000..159929b
--- /dev/null
+++ b/core/java/com/android/internal/util/ArrayUtils.java
@@ -0,0 +1,136 @@
+/*
+ * 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.internal.util;
+
+import java.lang.reflect.Array;
+import java.util.Collection;
+
+// XXX these should be changed to reflect the actual memory allocator we use.
+// it looks like right now objects want to be powers of 2 minus 8
+// and the array size eats another 4 bytes
+
+/**
+ * ArrayUtils contains some methods that you can call to find out
+ * the most efficient increments by which to grow arrays.
+ */
+public class ArrayUtils
+{
+    private static Object[] EMPTY = new Object[0];
+    private static final int CACHE_SIZE = 73;
+    private static Object[] sCache = new Object[CACHE_SIZE];
+
+    private ArrayUtils() { /* cannot be instantiated */ }
+
+    public static int idealByteArraySize(int need) {
+        for (int i = 4; i < 32; i++)
+            if (need <= (1 << i) - 12)
+                return (1 << i) - 12;
+
+        return need;
+    }
+
+    public static int idealBooleanArraySize(int need) {
+        return idealByteArraySize(need);
+    }
+
+    public static int idealShortArraySize(int need) {
+        return idealByteArraySize(need * 2) / 2;
+    }
+
+    public static int idealCharArraySize(int need) {
+        return idealByteArraySize(need * 2) / 2;
+    }
+
+    public static int idealIntArraySize(int need) {
+        return idealByteArraySize(need * 4) / 4;
+    }
+
+    public static int idealFloatArraySize(int need) {
+        return idealByteArraySize(need * 4) / 4;
+    }
+
+    public static int idealObjectArraySize(int need) {
+        return idealByteArraySize(need * 4) / 4;
+    }
+
+    public static int idealLongArraySize(int need) {
+        return idealByteArraySize(need * 8) / 8;
+    }
+
+    /**
+     * Checks if the beginnings of two byte arrays are equal.
+     *
+     * @param array1 the first byte array
+     * @param array2 the second byte array
+     * @param length the number of bytes to check
+     * @return true if they're equal, false otherwise
+     */
+    public static boolean equals(byte[] array1, byte[] array2, int length) {
+        if (array1 == array2) {
+            return true;
+        }
+        if (array1 == null || array2 == null || array1.length < length || array2.length < length) {
+            return false;
+        }
+        for (int i = 0; i < length; i++) {
+            if (array1[i] != array2[i]) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Returns an empty array of the specified type.  The intent is that
+     * it will return the same empty array every time to avoid reallocation,
+     * although this is not guaranteed.
+     */
+    public static <T> T[] emptyArray(Class<T> kind) {
+        if (kind == Object.class) {
+            return (T[]) EMPTY;
+        }
+
+        int bucket = ((System.identityHashCode(kind) / 8) & 0x7FFFFFFF) % CACHE_SIZE;
+        Object cache = sCache[bucket];
+
+        if (cache == null || cache.getClass().getComponentType() != kind) {
+            cache = Array.newInstance(kind, 0);
+            sCache[bucket] = cache;
+
+            // Log.e("cache", "new empty " + kind.getName() + " at " + bucket);
+        }
+
+        return (T[]) cache;
+    }
+
+    /**
+     * Checks that value is present as at least one of the elements of the array.
+     * @param array the array to check in
+     * @param value the value to check for
+     * @return true if the value is present in the array
+     */
+    public static <T> boolean contains(T[] array, T value) {
+        for (T element : array) {
+            if (element == null) {
+                if (value == null) return true;
+            } else {
+                if (value != null && element.equals(value)) return true;
+            }
+        }
+        return false;
+    }
+}
diff --git a/core/java/com/android/internal/util/CharSequences.java b/core/java/com/android/internal/util/CharSequences.java
new file mode 100644
index 0000000..fdaa4bc
--- /dev/null
+++ b/core/java/com/android/internal/util/CharSequences.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright (C) 2007 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.internal.util;
+
+/**
+ * {@link CharSequence} utility methods.
+ */
+public class CharSequences {
+
+    /**
+     * Adapts {@link CharSequence} to an array of ASCII (7-bits per character)
+     * bytes.
+     * 
+     * @param bytes ASCII bytes
+     */
+    public static CharSequence forAsciiBytes(final byte[] bytes) {
+        return new CharSequence() {
+            public char charAt(int index) {
+                return (char) bytes[index];
+            }
+
+            public int length() {
+                return bytes.length;
+            }
+
+            public CharSequence subSequence(int start, int end) {
+                return forAsciiBytes(bytes, start, end);
+            }
+
+            public String toString() {
+                return new String(bytes);
+            }
+        };
+    }
+
+    /**
+     * Adapts {@link CharSequence} to an array of ASCII (7-bits per character)
+     * bytes.
+     *
+     * @param bytes ASCII bytes
+     * @param start index, inclusive
+     * @param end index, exclusive
+     *
+     * @throws IndexOutOfBoundsException if start or end are negative, if end
+     *  is greater than length(), or if start is greater than end
+     */
+    public static CharSequence forAsciiBytes(final byte[] bytes,
+            final int start, final int end) {
+        validate(start, end, bytes.length);
+        return new CharSequence() {
+            public char charAt(int index) {
+                return (char) bytes[index + start];
+            }
+
+            public int length() {
+                return end - start;
+            }
+
+            public CharSequence subSequence(int newStart, int newEnd) {
+                newStart -= start;
+                newEnd -= start;
+                validate(newStart, newEnd, length());
+                return forAsciiBytes(bytes, newStart, newEnd);
+            }
+
+            public String toString() {
+                return new String(bytes, start, length());
+            }
+        };
+    }
+
+    static void validate(int start, int end, int length) {
+        if (start < 0) throw new IndexOutOfBoundsException();
+        if (end < 0) throw new IndexOutOfBoundsException();
+        if (end > length) throw new IndexOutOfBoundsException();
+        if (start > end) throw new IndexOutOfBoundsException();
+    }
+
+    /**
+     * Compares two character sequences for equality.
+     */
+    public static boolean equals(CharSequence a, CharSequence b) {
+        if (a.length() != b.length()) {
+            return false;
+        }
+
+        int length = a.length();
+        for (int i = 0; i < length; i++) {
+            if (a.charAt(i) != b.charAt(i)) {
+                return false;
+            }
+        }
+        return true;
+    }
+    
+    /**
+     * Compares two character sequences with API like {@link Comparable#compareTo}.
+     * 
+     * @param me The CharSequence that receives the compareTo call.
+     * @param another The other CharSequence.
+     * @return See {@link Comparable#compareTo}.
+     */
+    public static int compareToIgnoreCase(CharSequence me, CharSequence another) {
+        // Code adapted from String#compareTo
+        int myLen = me.length(), anotherLen = another.length();
+        int myPos = 0, anotherPos = 0, result;
+        int end = (myLen < anotherLen) ? myLen : anotherLen;
+
+        while (myPos < end) {
+            if ((result = Character.toLowerCase(me.charAt(myPos++))
+                    - Character.toLowerCase(another.charAt(anotherPos++))) != 0) {
+                return result;
+            }
+        }
+        return myLen - anotherLen;
+    }
+}
diff --git a/core/java/com/android/internal/util/FastMath.java b/core/java/com/android/internal/util/FastMath.java
new file mode 100644
index 0000000..efd0871
--- /dev/null
+++ b/core/java/com/android/internal/util/FastMath.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2007 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.internal.util;
+
+/**
+ * Fast and loose math routines.
+ */
+public class FastMath {
+
+    /**
+     * Fast round from float to int. This is faster than Math.round()
+     * thought it may return slightly different results. It does not try to
+     * handle (in any meaningful way) NaN or infinities.
+     */
+    public static int round(float x) {
+        long lx = (long)(x * (65536 * 256f));
+        return (int)((lx + 0x800000) >> 24);
+    }
+}
diff --git a/core/java/com/android/internal/util/FastXmlSerializer.java b/core/java/com/android/internal/util/FastXmlSerializer.java
new file mode 100644
index 0000000..592a8fa
--- /dev/null
+++ b/core/java/com/android/internal/util/FastXmlSerializer.java
@@ -0,0 +1,365 @@
+/*
+ * 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.internal.util;
+
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.UnsupportedEncodingException;
+import java.io.Writer;
+import java.nio.ByteBuffer;
+import java.nio.CharBuffer;
+import java.nio.charset.Charset;
+import java.nio.charset.CharsetEncoder;
+import java.nio.charset.CoderResult;
+import java.nio.charset.IllegalCharsetNameException;
+import java.nio.charset.UnsupportedCharsetException;
+
+/**
+ * This is a quick and dirty implementation of XmlSerializer that isn't horribly
+ * painfully slow like the normal one.  It only does what is needed for the
+ * specific XML files being written with it.
+ */
+public class FastXmlSerializer implements XmlSerializer {
+    private static final String ESCAPE_TABLE[] = new String[] {
+        null,     null,     null,     null,     null,     null,     null,     null,  // 0-7
+        null,     null,     null,     null,     null,     null,     null,     null,  // 8-15
+        null,     null,     null,     null,     null,     null,     null,     null,  // 16-23
+        null,     null,     null,     null,     null,     null,     null,     null,  // 24-31
+        null,     null,     "&quot;", null,     null,     null,     "&amp;",  null,  // 32-39
+        null,     null,     null,     null,     null,     null,     null,     null,  // 40-47
+        null,     null,     null,     null,     null,     null,     null,     null,  // 48-55
+        null,     null,     null,     null,     "&lt;",   null,     "&gt;",   null,  // 56-63
+    };
+
+    private static final int BUFFER_LEN = 8192;
+
+    private final char[] mText = new char[BUFFER_LEN];
+    private int mPos;
+
+    private Writer mWriter;
+
+    private OutputStream mOutputStream;
+    private CharsetEncoder mCharset;
+    private ByteBuffer mBytes = ByteBuffer.allocate(BUFFER_LEN);
+
+    private boolean mInTag;
+
+    private void append(char c) throws IOException {
+        int pos = mPos;
+        if (pos >= (BUFFER_LEN-1)) {
+            flush();
+            pos = mPos;
+        }
+        mText[pos] = c;
+        mPos = pos+1;
+    }
+
+    private void append(String str, int i, final int length) throws IOException {
+        if (length > BUFFER_LEN) {
+            final int end = i + length;
+            while (i < end) {
+                int next = i + BUFFER_LEN;
+                append(str, i, next<end ? BUFFER_LEN : (end-i));
+                i = next;
+            }
+            return;
+        }
+        int pos = mPos;
+        if ((pos+length) > BUFFER_LEN) {
+            flush();
+            pos = mPos;
+        }
+        str.getChars(i, i+length, mText, pos);
+        mPos = pos + length;
+    }
+
+    private void append(char[] buf, int i, final int length) throws IOException {
+        if (length > BUFFER_LEN) {
+            final int end = i + length;
+            while (i < end) {
+                int next = i + BUFFER_LEN;
+                append(buf, i, next<end ? BUFFER_LEN : (end-i));
+                i = next;
+            }
+            return;
+        }
+        int pos = mPos;
+        if ((pos+length) > BUFFER_LEN) {
+            flush();
+            pos = mPos;
+        }
+        System.arraycopy(buf, i, mText, pos, length);
+        mPos = pos + length;
+    }
+
+    private void append(String str) throws IOException {
+        append(str, 0, str.length());
+    }
+
+    private void escapeAndAppendString(final String string) throws IOException {
+        final int N = string.length();
+        final char NE = (char)ESCAPE_TABLE.length;
+        final String[] escapes = ESCAPE_TABLE;
+        int lastPos = 0;
+        int pos;
+        for (pos=0; pos<N; pos++) {
+            char c = string.charAt(pos);
+            if (c >= NE) continue;
+            String escape = escapes[c];
+            if (escape == null) continue;
+            if (lastPos < pos) append(string, lastPos, pos-lastPos);
+            lastPos = pos + 1;
+            append(escape);
+        }
+        if (lastPos < pos) append(string, lastPos, pos-lastPos);
+    }
+
+    private void escapeAndAppendString(char[] buf, int start, int len) throws IOException {
+        final char NE = (char)ESCAPE_TABLE.length;
+        final String[] escapes = ESCAPE_TABLE;
+        int end = start+len;
+        int lastPos = start;
+        int pos;
+        for (pos=start; pos<end; pos++) {
+            char c = buf[pos];
+            if (c >= NE) continue;
+            String escape = escapes[c];
+            if (escape == null) continue;
+            if (lastPos < pos) append(buf, lastPos, pos-lastPos);
+            lastPos = pos + 1;
+            append(escape);
+        }
+        if (lastPos < pos) append(buf, lastPos, pos-lastPos);
+    }
+
+    public XmlSerializer attribute(String namespace, String name, String value) throws IOException,
+            IllegalArgumentException, IllegalStateException {
+        append(' ');
+        if (namespace != null) {
+            append(namespace);
+            append(':');
+        }
+        append(name);
+        append("=\"");
+
+        escapeAndAppendString(value);
+        append('"');
+        return this;
+    }
+
+    public void cdsect(String text) throws IOException, IllegalArgumentException,
+            IllegalStateException {
+        throw new UnsupportedOperationException();
+    }
+
+    public void comment(String text) throws IOException, IllegalArgumentException,
+            IllegalStateException {
+        throw new UnsupportedOperationException();
+    }
+
+    public void docdecl(String text) throws IOException, IllegalArgumentException,
+            IllegalStateException {
+        throw new UnsupportedOperationException();
+    }
+
+    public void endDocument() throws IOException, IllegalArgumentException, IllegalStateException {
+        flush();
+    }
+
+    public XmlSerializer endTag(String namespace, String name) throws IOException,
+            IllegalArgumentException, IllegalStateException {
+        if (mInTag) {
+            append(" />\n");
+        } else {
+            append("</");
+            if (namespace != null) {
+                append(namespace);
+                append(':');
+            }
+            append(name);
+            append(">\n");
+        }
+        mInTag = false;
+        return this;
+    }
+
+    public void entityRef(String text) throws IOException, IllegalArgumentException,
+            IllegalStateException {
+        throw new UnsupportedOperationException();
+    }
+
+    private void flushBytes() throws IOException {
+        int position;
+        if ((position = mBytes.position()) > 0) {
+            mBytes.flip();
+            mOutputStream.write(mBytes.array(), 0, position);
+            mBytes.clear();
+        }
+    }
+
+    public void flush() throws IOException {
+        //Log.i("PackageManager", "flush mPos=" + mPos);
+        if (mPos > 0) {
+            if (mOutputStream != null) {
+                CharBuffer charBuffer = CharBuffer.wrap(mText, 0, mPos);
+                CoderResult result = mCharset.encode(charBuffer, mBytes, true);
+                while (true) {
+                    if (result.isError()) {
+                        throw new IOException(result.toString());
+                    } else if (result.isOverflow()) {
+                        flushBytes();
+                        result = mCharset.encode(charBuffer, mBytes, true);
+                        continue;
+                    }
+                    break;
+                }
+                flushBytes();
+                mOutputStream.flush();
+            } else {
+                mWriter.write(mText, 0, mPos);
+                mWriter.flush();
+            }
+            mPos = 0;
+        }
+    }
+
+    public int getDepth() {
+        throw new UnsupportedOperationException();
+    }
+
+    public boolean getFeature(String name) {
+        throw new UnsupportedOperationException();
+    }
+
+    public String getName() {
+        throw new UnsupportedOperationException();
+    }
+
+    public String getNamespace() {
+        throw new UnsupportedOperationException();
+    }
+
+    public String getPrefix(String namespace, boolean generatePrefix)
+            throws IllegalArgumentException {
+        throw new UnsupportedOperationException();
+    }
+
+    public Object getProperty(String name) {
+        throw new UnsupportedOperationException();
+    }
+
+    public void ignorableWhitespace(String text) throws IOException, IllegalArgumentException,
+            IllegalStateException {
+        throw new UnsupportedOperationException();
+    }
+
+    public void processingInstruction(String text) throws IOException, IllegalArgumentException,
+            IllegalStateException {
+        throw new UnsupportedOperationException();
+    }
+
+    public void setFeature(String name, boolean state) throws IllegalArgumentException,
+            IllegalStateException {
+        if (name.equals("http://xmlpull.org/v1/doc/features.html#indent-output")) {
+            return;
+        }
+        throw new UnsupportedOperationException();
+    }
+
+    public void setOutput(OutputStream os, String encoding) throws IOException,
+            IllegalArgumentException, IllegalStateException {
+        if (os == null)
+            throw new IllegalArgumentException();
+        if (true) {
+            try {
+                mCharset = Charset.forName(encoding).newEncoder();
+            } catch (IllegalCharsetNameException e) {
+                throw (UnsupportedEncodingException) (new UnsupportedEncodingException(
+                        encoding).initCause(e));
+            } catch (UnsupportedCharsetException e) {
+                throw (UnsupportedEncodingException) (new UnsupportedEncodingException(
+                        encoding).initCause(e));
+            }
+            mOutputStream = os;
+        } else {
+            setOutput(
+                encoding == null
+                    ? new OutputStreamWriter(os)
+                    : new OutputStreamWriter(os, encoding));
+        }
+    }
+
+    public void setOutput(Writer writer) throws IOException, IllegalArgumentException,
+            IllegalStateException {
+        mWriter = writer;
+    }
+
+    public void setPrefix(String prefix, String namespace) throws IOException,
+            IllegalArgumentException, IllegalStateException {
+        throw new UnsupportedOperationException();
+    }
+
+    public void setProperty(String name, Object value) throws IllegalArgumentException,
+            IllegalStateException {
+        throw new UnsupportedOperationException();
+    }
+
+    public void startDocument(String encoding, Boolean standalone) throws IOException,
+            IllegalArgumentException, IllegalStateException {
+        append("<?xml version='1.0' encoding='utf-8' standalone='"
+                + (standalone ? "yes" : "no") + "' ?>\n");
+    }
+
+    public XmlSerializer startTag(String namespace, String name) throws IOException,
+            IllegalArgumentException, IllegalStateException {
+        if (mInTag) {
+            append(">\n");
+        }
+        append('<');
+        if (namespace != null) {
+            append(namespace);
+            append(':');
+        }
+        append(name);
+        mInTag = true;
+        return this;
+    }
+
+    public XmlSerializer text(char[] buf, int start, int len) throws IOException,
+            IllegalArgumentException, IllegalStateException {
+        if (mInTag) {
+            append(">");
+            mInTag = false;
+        }
+        escapeAndAppendString(buf, start, len);
+        return this;
+    }
+
+    public XmlSerializer text(String text) throws IOException, IllegalArgumentException,
+            IllegalStateException {
+        if (mInTag) {
+            append(">");
+            mInTag = false;
+        }
+        escapeAndAppendString(text);
+        return this;
+    }
+
+}
diff --git a/core/java/com/android/internal/util/HexDump.java b/core/java/com/android/internal/util/HexDump.java
new file mode 100644
index 0000000..3c7b7ac
--- /dev/null
+++ b/core/java/com/android/internal/util/HexDump.java
@@ -0,0 +1,164 @@
+/*
+ * 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.internal.util;
+
+public class HexDump
+{
+    private final static char[] HEX_DIGITS = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
+    
+    public static String dumpHexString(byte[] array)
+    {
+        return dumpHexString(array, 0, array.length);
+    }
+    
+    public static String dumpHexString(byte[] array, int offset, int length)
+    {
+        StringBuilder result = new StringBuilder();
+        
+        byte[] line = new byte[16];
+        int lineIndex = 0;
+        
+        result.append("\n0x");
+        result.append(toHexString(offset));
+        
+        for (int i = offset ; i < offset + length ; i++)
+        {
+            if (lineIndex == 16)
+            {
+                result.append(" ");
+                
+                for (int j = 0 ; j < 16 ; j++)
+                {
+                    if (line[j] > ' ' && line[j] < '~')
+                    {
+                        result.append(new String(line, j, 1));
+                    }
+                    else
+                    {
+                        result.append(".");
+                    }
+                }
+                
+                result.append("\n0x");
+                result.append(toHexString(i));
+                lineIndex = 0;
+            }
+            
+            byte b = array[i];
+            result.append(" ");
+            result.append(HEX_DIGITS[(b >>> 4) & 0x0F]);
+            result.append(HEX_DIGITS[b & 0x0F]);
+            
+            line[lineIndex++] = b;
+        }
+        
+        if (lineIndex != 16)
+        {
+            int count = (16 - lineIndex) * 3;
+            count++;
+            for (int i = 0 ; i < count ; i++)
+            {
+                result.append(" ");
+            }
+            
+            for (int i = 0 ; i < lineIndex ; i++)
+            {
+                if (line[i] > ' ' && line[i] < '~')
+                {
+                    result.append(new String(line, i, 1));
+                }
+                else
+                {
+                    result.append(".");
+                }
+            }
+        }
+        
+        return result.toString();
+    }
+    
+    public static String toHexString(byte b)
+    {
+        return toHexString(toByteArray(b));
+    }
+
+    public static String toHexString(byte[] array)
+    {
+        return toHexString(array, 0, array.length);
+    }
+    
+    public static String toHexString(byte[] array, int offset, int length)
+    {
+        char[] buf = new char[length * 2];
+
+        int bufIndex = 0;
+        for (int i = offset ; i < offset + length; i++) 
+        {
+            byte b = array[i];
+            buf[bufIndex++] = HEX_DIGITS[(b >>> 4) & 0x0F];
+            buf[bufIndex++] = HEX_DIGITS[b & 0x0F];
+        }
+
+        return new String(buf);        
+    }
+    
+    public static String toHexString(int i)
+    {
+        return toHexString(toByteArray(i));
+    }
+    
+    public static byte[] toByteArray(byte b)
+    {
+        byte[] array = new byte[1];
+        array[0] = b;
+        return array;
+    }
+    
+    public static byte[] toByteArray(int i)
+    {
+        byte[] array = new byte[4];
+        
+        array[3] = (byte)(i & 0xFF);
+        array[2] = (byte)((i >> 8) & 0xFF);
+        array[1] = (byte)((i >> 16) & 0xFF);
+        array[0] = (byte)((i >> 24) & 0xFF);
+        
+        return array;
+    }
+    
+    private static int toByte(char c)
+    {
+        if (c >= '0' && c <= '9') return (c - '0');
+        if (c >= 'A' && c <= 'F') return (c - 'A' + 10);
+        if (c >= 'a' && c <= 'f') return (c - 'a' + 10);
+
+        throw new RuntimeException ("Invalid hex char '" + c + "'");
+    }
+    
+    public static byte[] hexStringToByteArray(String hexString)
+    {
+        int length = hexString.length();
+        byte[] buffer = new byte[length / 2];
+
+        for (int i = 0 ; i < length ; i += 2)
+        {
+            buffer[i / 2] = (byte)((toByte(hexString.charAt(i)) << 4) | toByte(hexString.charAt(i+1)));
+        }
+        
+        return buffer;
+    }    
+}
diff --git a/core/java/com/android/internal/util/Objects.java b/core/java/com/android/internal/util/Objects.java
new file mode 100644
index 0000000..598a079
--- /dev/null
+++ b/core/java/com/android/internal/util/Objects.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2007 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.internal.util;
+
+/**
+ * Object utility methods.
+ */
+public class Objects {
+
+    /**
+     * Ensures the given object isn't {@code null}.
+     *
+     * @return the given object
+     * @throws NullPointerException if the object is null
+     */
+    public static <T> T nonNull(T t) {
+        if (t == null) {
+            throw new NullPointerException();
+        }
+        return t;
+    }
+
+    /**
+     * Ensures the given object isn't {@code null}.
+     *
+     * @return the given object
+     * @throws NullPointerException if the object is null
+     */
+    public static <T> T nonNull(T t, String message) {
+        if (t == null) {
+            throw new NullPointerException(message);
+        }
+        return t;
+    }
+}
diff --git a/core/java/com/android/internal/util/Predicate.java b/core/java/com/android/internal/util/Predicate.java
new file mode 100644
index 0000000..bc6d6b3
--- /dev/null
+++ b/core/java/com/android/internal/util/Predicate.java
@@ -0,0 +1,32 @@
+/*
+ * 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.internal.util;
+
+/**
+ * A Predicate can determine a true or false value for any input of its
+ * parameterized type. For example, a {@code RegexPredicate} might implement
+ * {@code Predicate<String>}, and return true for any String that matches its
+ * given regular expression.
+ * <p/>
+ * <p/>
+ * Implementors of Predicate which may cause side effects upon evaluation are
+ * strongly encouraged to state this fact clearly in their API documentation.
+ */
+public interface Predicate<T> {
+
+    boolean apply(T t);
+}
diff --git a/core/java/com/android/internal/util/Predicates.java b/core/java/com/android/internal/util/Predicates.java
new file mode 100644
index 0000000..f5051e4
--- /dev/null
+++ b/core/java/com/android/internal/util/Predicates.java
@@ -0,0 +1,125 @@
+/*
+ * 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.internal.util;
+
+import java.util.Arrays;
+
+/**
+ * Predicates contains static methods for creating the standard set of
+ * {@code Predicate} objects.
+ */
+public class Predicates {
+
+    private Predicates() {
+    }
+
+    /**
+     * Returns a Predicate that evaluates to true iff each of its components
+     * evaluates to true.  The components are evaluated in order, and evaluation
+     * will be "short-circuited" as soon as the answer is determined.
+     */
+    public static <T> Predicate<T> and(Predicate<? super T>... components) {
+        return and(Arrays.asList(components));
+    }
+
+    /**
+     * Returns a Predicate that evaluates to true iff each of its components
+     * evaluates to true.  The components are evaluated in order, and evaluation
+     * will be "short-circuited" as soon as the answer is determined.  Does not
+     * defensively copy the iterable passed in, so future changes to it will alter
+     * the behavior of this Predicate. If components is empty, the returned
+     * Predicate will always evaluate to true.
+     */
+    public static <T> Predicate<T> and(Iterable<? extends Predicate<? super T>> components) {
+        return new AndPredicate(components);
+    }
+
+    /**
+     * Returns a Predicate that evaluates to true iff any one of its components
+     * evaluates to true.  The components are evaluated in order, and evaluation
+     * will be "short-circuited" as soon as the answer is determined.
+     */
+    public static <T> Predicate<T> or(Predicate<? super T>... components) {
+        return or(Arrays.asList(components));
+    }
+
+    /**
+     * Returns a Predicate that evaluates to true iff any one of its components
+     * evaluates to true.  The components are evaluated in order, and evaluation
+     * will be "short-circuited" as soon as the answer is determined.  Does not
+     * defensively copy the iterable passed in, so future changes to it will alter
+     * the behavior of this Predicate. If components is empty, the returned
+     * Predicate will always evaluate to false.
+     */
+    public static <T> Predicate<T> or(Iterable<? extends Predicate<? super T>> components) {
+        return new OrPredicate(components);
+    }
+
+    /**
+     * Returns a Predicate that evaluates to true iff the given Predicate
+     * evaluates to false.
+     */
+    public static <T> Predicate<T> not(Predicate<? super T> predicate) {
+        return new NotPredicate<T>(predicate);
+    }
+
+    private static class AndPredicate<T> implements Predicate<T> {
+        private final Iterable<? extends Predicate<? super T>> components;
+
+        private AndPredicate(Iterable<? extends Predicate<? super T>> components) {
+            this.components = components;
+        }
+
+        public boolean apply(T t) {
+            for (Predicate<? super T> predicate : components) {
+                if (!predicate.apply(t)) {
+                    return false;
+                }
+            }
+            return true;
+        }
+    }
+
+    private static class OrPredicate<T> implements Predicate<T> {
+        private final Iterable<? extends Predicate<? super T>> components;
+
+        private OrPredicate(Iterable<? extends Predicate<? super T>> components) {
+            this.components = components;
+        }
+
+        public boolean apply(T t) {
+            for (Predicate<? super T> predicate : components) {
+                if (predicate.apply(t)) {
+                    return true;
+                }
+            }
+            return false;
+        }
+    }
+
+    private static class NotPredicate<T> implements Predicate<T> {
+        private final Predicate<? super T> predicate;
+
+        private NotPredicate(Predicate<? super T> predicate) {
+            this.predicate = predicate;
+        }
+
+        public boolean apply(T t) {
+            return !predicate.apply(t);
+        }
+    }
+}
diff --git a/core/java/com/android/internal/util/WithFramework.java b/core/java/com/android/internal/util/WithFramework.java
new file mode 100644
index 0000000..7d8596f
--- /dev/null
+++ b/core/java/com/android/internal/util/WithFramework.java
@@ -0,0 +1,58 @@
+/*
+ * 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.internal.util;
+
+import java.lang.reflect.Method;
+
+/**
+ * Binds native framework methods and then invokes a main class with the
+ * remaining arguments.
+ */
+class WithFramework {
+
+    /**
+     * Invokes main(String[]) method on class in args[0] with args[1..n].
+     */
+    public static void main(String[] args) throws Exception {
+        if (args.length == 0) {
+            printUsage();
+            return;
+        }
+
+        Class<?> mainClass = Class.forName(args[0]);
+
+        System.loadLibrary("android_runtime");
+        if (registerNatives() < 0) {
+            throw new RuntimeException("Error registering natives.");
+        }
+
+        String[] newArgs = new String[args.length - 1];
+        System.arraycopy(args, 1, newArgs, 0, newArgs.length);
+        Method mainMethod = mainClass.getMethod("main", String[].class);
+        mainMethod.invoke(null, new Object[] { newArgs });
+    }
+
+    private static void printUsage() {
+        System.err.println("Usage: dalvikvm " + WithFramework.class.getName()
+                + " [main class] [args]");
+    }
+
+    /**
+     * Registers native functions. See AndroidRuntime.cpp.
+     */
+    static native int registerNatives();
+}
diff --git a/core/java/com/android/internal/util/XmlUtils.java b/core/java/com/android/internal/util/XmlUtils.java
new file mode 100644
index 0000000..948e313
--- /dev/null
+++ b/core/java/com/android/internal/util/XmlUtils.java
@@ -0,0 +1,796 @@
+/*
+ * 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.internal.util;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import android.util.Xml;
+
+/** {@hide} */
+public class XmlUtils
+{
+
+    public static void skipCurrentTag(XmlPullParser parser)
+            throws XmlPullParserException, IOException {
+        int outerDepth = parser.getDepth();
+        int type;
+        while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
+               && (type != XmlPullParser.END_TAG
+                       || parser.getDepth() > outerDepth)) {
+        }
+    }
+
+    public static final int
+    convertValueToList(CharSequence value, String[] options, int defaultValue)
+    {
+        if (null != value) {
+            for (int i = 0; i < options.length; i++) {
+                if (value.equals(options[i]))
+                    return i;
+            }
+        }
+
+        return defaultValue;
+    }
+
+    public static final boolean
+    convertValueToBoolean(CharSequence value, boolean defaultValue)
+    {
+        boolean result = false;
+
+        if (null == value)
+            return defaultValue;
+
+        if (value.equals("1")
+        ||  value.equals("true")
+        ||  value.equals("TRUE"))
+            result = true;
+
+        return result;
+    }
+
+    public static final int
+    convertValueToInt(CharSequence charSeq, int defaultValue)
+    {
+        if (null == charSeq)
+            return defaultValue;
+
+        String nm = charSeq.toString();
+
+        // XXX This code is copied from Integer.decode() so we don't
+        // have to instantiate an Integer!
+        
+        int value;
+        int sign = 1;
+        int index = 0;
+        int len = nm.length();
+        int base = 10;
+
+        if ('-' == nm.charAt(0)) {
+            sign = -1;
+            index++;
+        }
+
+        if ('0' == nm.charAt(index)) {
+            //  Quick check for a zero by itself
+            if (index == (len - 1))
+                return 0;
+
+            char    c = nm.charAt(index + 1);
+
+            if ('x' == c || 'X' == c) {
+                index += 2;
+                base = 16;
+            } else {
+                index++;
+                base = 8;
+            }
+        }
+        else if ('#' == nm.charAt(index))
+        {
+            index++;
+            base = 16;
+        }
+
+        return Integer.parseInt(nm.substring(index), base) * sign;
+    }
+
+    public static final int
+    convertValueToUnsignedInt(String value, int defaultValue)
+    {
+        if (null == value)
+            return defaultValue;
+
+        return parseUnsignedIntAttribute(value);
+    }
+
+    public static final int
+    parseUnsignedIntAttribute(CharSequence charSeq)
+    {        
+        String  value = charSeq.toString();
+
+        long    bits;
+        int     index = 0;
+        int     len = value.length();
+        int     base = 10;
+        
+        if ('0' == value.charAt(index)) {
+            //  Quick check for zero by itself
+            if (index == (len - 1))
+                return 0;
+            
+            char    c = value.charAt(index + 1);
+            
+            if ('x' == c || 'X' == c) {     //  check for hex
+                index += 2;
+                base = 16;
+            } else {                        //  check for octal
+                index++;
+                base = 8;
+            }
+        } else if ('#' == value.charAt(index)) {
+            index++;
+            base = 16;
+        }
+        
+        return (int) Long.parseLong(value.substring(index), base);
+    }
+
+    /**
+     * Flatten a Map into an output stream as XML.  The map can later be
+     * read back with readMapXml().
+     * 
+     * @param val The map to be flattened.
+     * @param out Where to write the XML data.
+     * 
+     * @see #writeMapXml(Map, String, XmlSerializer)
+     * @see #writeListXml
+     * @see #writeValueXml
+     * @see #readMapXml
+     */
+    public static final void writeMapXml(Map val, OutputStream out)
+            throws XmlPullParserException, java.io.IOException {
+        XmlSerializer serializer = new FastXmlSerializer();
+        serializer.setOutput(out, "utf-8");
+        serializer.startDocument(null, true);
+        serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
+        writeMapXml(val, null, serializer);
+        serializer.endDocument();
+    }
+
+    /**
+     * Flatten a List into an output stream as XML.  The list can later be
+     * read back with readListXml().
+     * 
+     * @param val The list to be flattened.
+     * @param out Where to write the XML data.
+     * 
+     * @see #writeListXml(List, String, XmlSerializer)
+     * @see #writeMapXml
+     * @see #writeValueXml
+     * @see #readListXml
+     */
+    public static final void writeListXml(List val, OutputStream out)
+    throws XmlPullParserException, java.io.IOException
+    {
+        XmlSerializer serializer = Xml.newSerializer();
+        serializer.setOutput(out, "utf-8");
+        serializer.startDocument(null, true);
+        serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
+        writeListXml(val, null, serializer);
+        serializer.endDocument();
+    }
+
+    /**
+     * Flatten a Map into an XmlSerializer.  The map can later be read back
+     * with readThisMapXml().
+     * 
+     * @param val The map to be flattened.
+     * @param name Name attribute to include with this list's tag, or null for
+     *             none.
+     * @param out XmlSerializer to write the map into.
+     * 
+     * @see #writeMapXml(Map, OutputStream)
+     * @see #writeListXml
+     * @see #writeValueXml
+     * @see #readMapXml
+     */
+    public static final void writeMapXml(Map val, String name, XmlSerializer out)
+    throws XmlPullParserException, java.io.IOException
+    {
+        if (val == null) {
+            out.startTag(null, "null");
+            out.endTag(null, "null");
+            return;
+        }
+
+        Set s = val.entrySet();
+        Iterator i = s.iterator();
+
+        out.startTag(null, "map");
+        if (name != null) {
+            out.attribute(null, "name", name);
+        }
+
+        while (i.hasNext()) {
+            Map.Entry e = (Map.Entry)i.next();
+            writeValueXml(e.getValue(), (String)e.getKey(), out);
+        }
+
+        out.endTag(null, "map");
+    }
+
+    /**
+     * Flatten a List into an XmlSerializer.  The list can later be read back
+     * with readThisListXml().
+     * 
+     * @param val The list to be flattened.
+     * @param name Name attribute to include with this list's tag, or null for
+     *             none.
+     * @param out XmlSerializer to write the list into.
+     * 
+     * @see #writeListXml(List, OutputStream)
+     * @see #writeMapXml
+     * @see #writeValueXml
+     * @see #readListXml
+     */
+    public static final void writeListXml(List val, String name, XmlSerializer out)
+    throws XmlPullParserException, java.io.IOException
+    {
+        if (val == null) {
+            out.startTag(null, "null");
+            out.endTag(null, "null");
+            return;
+        }
+
+        out.startTag(null, "list");
+        if (name != null) {
+            out.attribute(null, "name", name);
+        }
+
+        int N = val.size();
+        int i=0;
+        while (i < N) {
+            writeValueXml(val.get(i), null, out);
+            i++;
+        }
+
+        out.endTag(null, "list");
+    }
+
+    /**
+     * Flatten a byte[] into an XmlSerializer.  The list can later be read back
+     * with readThisByteArrayXml().
+     * 
+     * @param val The byte array to be flattened.
+     * @param name Name attribute to include with this array's tag, or null for
+     *             none.
+     * @param out XmlSerializer to write the array into.
+     * 
+     * @see #writeMapXml
+     * @see #writeValueXml
+     */
+    public static final void writeByteArrayXml(byte[] val, String name,
+            XmlSerializer out)
+            throws XmlPullParserException, java.io.IOException {
+        
+        if (val == null) {
+            out.startTag(null, "null");
+            out.endTag(null, "null");
+            return;
+        }
+
+        out.startTag(null, "byte-array");
+        if (name != null) {
+            out.attribute(null, "name", name);
+        }
+
+        final int N = val.length;
+        out.attribute(null, "num", Integer.toString(N));
+        
+        StringBuilder sb = new StringBuilder(val.length*2);
+        for (int i=0; i<N; i++) {
+            int b = val[i];
+            int h = b>>4;
+            sb.append(h >= 10 ? ('a'+h-10) : ('0'+h));
+            h = b&0xff;
+            sb.append(h >= 10 ? ('a'+h-10) : ('0'+h));
+        }
+
+        out.text(sb.toString());
+
+        out.endTag(null, "byte-array");
+    }
+
+    /**
+     * Flatten an int[] into an XmlSerializer.  The list can later be read back
+     * with readThisIntArrayXml().
+     * 
+     * @param val The int array to be flattened.
+     * @param name Name attribute to include with this array's tag, or null for
+     *             none.
+     * @param out XmlSerializer to write the array into.
+     * 
+     * @see #writeMapXml
+     * @see #writeValueXml
+     * @see #readThisIntArrayXml
+     */
+    public static final void writeIntArrayXml(int[] val, String name,
+            XmlSerializer out)
+            throws XmlPullParserException, java.io.IOException {
+        
+        if (val == null) {
+            out.startTag(null, "null");
+            out.endTag(null, "null");
+            return;
+        }
+
+        out.startTag(null, "int-array");
+        if (name != null) {
+            out.attribute(null, "name", name);
+        }
+        
+        final int N = val.length;
+        out.attribute(null, "num", Integer.toString(N));
+
+        for (int i=0; i<N; i++) {
+            out.startTag(null, "item");
+            out.attribute(null, "value", Integer.toString(val[i]));
+            out.endTag(null, "item");
+        }
+
+        out.endTag(null, "int-array");
+    }
+
+    /**
+     * Flatten an object's value into an XmlSerializer.  The value can later
+     * be read back with readThisValueXml().
+     * 
+     * Currently supported value types are: null, String, Integer, Long,
+     * Float, Double Boolean, Map, List.
+     * 
+     * @param v The object to be flattened.
+     * @param name Name attribute to include with this value's tag, or null
+     *             for none.
+     * @param out XmlSerializer to write the object into.
+     * 
+     * @see #writeMapXml
+     * @see #writeListXml
+     * @see #readValueXml
+     */
+    public static final void writeValueXml(Object v, String name, XmlSerializer out)
+    throws XmlPullParserException, java.io.IOException
+    {
+        String typeStr;
+        if (v == null) {
+            out.startTag(null, "null");
+            if (name != null) {
+                out.attribute(null, "name", name);
+            }
+            out.endTag(null, "null");
+            return;
+        } else if (v instanceof String) {
+            out.startTag(null, "string");
+            if (name != null) {
+                out.attribute(null, "name", name);
+            }
+            out.text(v.toString());
+            out.endTag(null, "string");
+            return;
+        } else if (v instanceof Integer) {
+            typeStr = "int";
+        } else if (v instanceof Long) {
+            typeStr = "long";
+        } else if (v instanceof Float) {
+            typeStr = "float";
+        } else if (v instanceof Double) {
+            typeStr = "double";
+        } else if (v instanceof Boolean) {
+            typeStr = "boolean";
+        } else if (v instanceof byte[]) {
+            writeByteArrayXml((byte[])v, name, out);
+            return;
+        } else if (v instanceof int[]) {
+            writeIntArrayXml((int[])v, name, out);
+            return;
+        } else if (v instanceof Map) {
+            writeMapXml((Map)v, name, out);
+            return;
+        } else if (v instanceof List) {
+            writeListXml((List)v, name, out);
+            return;
+        } else if (v instanceof CharSequence) {
+            // XXX This is to allow us to at least write something if
+            // we encounter styled text...  but it means we will drop all
+            // of the styling information. :(
+            out.startTag(null, "string");
+            if (name != null) {
+                out.attribute(null, "name", name);
+            }
+            out.text(v.toString());
+            out.endTag(null, "string");
+            return;
+        } else {
+            throw new RuntimeException("writeValueXml: unable to write value " + v);
+        }
+
+        out.startTag(null, typeStr);
+        if (name != null) {
+            out.attribute(null, "name", name);
+        }
+        out.attribute(null, "value", v.toString());
+        out.endTag(null, typeStr);
+    }
+
+    /**
+     * Read a HashMap from an InputStream containing XML.  The stream can
+     * previously have been written by writeMapXml().
+     * 
+     * @param in The InputStream from which to read.
+     * 
+     * @return HashMap The resulting map.
+     * 
+     * @see #readListXml
+     * @see #readValueXml
+     * @see #readThisMapXml
+     * #see #writeMapXml
+     */
+    public static final HashMap readMapXml(InputStream in)
+    throws XmlPullParserException, java.io.IOException
+    {
+        XmlPullParser   parser = Xml.newPullParser();
+        parser.setInput(in, null);
+        return (HashMap)readValueXml(parser, new String[1]);
+    }
+
+    /**
+     * Read an ArrayList from an InputStream containing XML.  The stream can
+     * previously have been written by writeListXml().
+     * 
+     * @param in The InputStream from which to read.
+     * 
+     * @return HashMap The resulting list.
+     * 
+     * @see #readMapXml
+     * @see #readValueXml
+     * @see #readThisListXml
+     * @see #writeListXml
+     */
+    public static final ArrayList readListXml(InputStream in)
+    throws XmlPullParserException, java.io.IOException
+    {
+        XmlPullParser   parser = Xml.newPullParser();
+        parser.setInput(in, null);
+        return (ArrayList)readValueXml(parser, new String[1]);
+    }
+
+    /**
+     * Read a HashMap object from an XmlPullParser.  The XML data could
+     * previously have been generated by writeMapXml().  The XmlPullParser
+     * must be positioned <em>after</em> the tag that begins the map.
+     * 
+     * @param parser The XmlPullParser from which to read the map data.
+     * @param endTag Name of the tag that will end the map, usually "map".
+     * @param name An array of one string, used to return the name attribute
+     *             of the map's tag.
+     * 
+     * @return HashMap The newly generated map.
+     * 
+     * @see #readMapXml
+     */
+    public static final HashMap readThisMapXml(XmlPullParser parser, String endTag, String[] name)
+    throws XmlPullParserException, java.io.IOException
+    {
+        HashMap map = new HashMap();
+
+        int eventType = parser.getEventType();
+        do {
+            if (eventType == parser.START_TAG) {
+                Object val = readThisValueXml(parser, name);
+                if (name[0] != null) {
+                    //System.out.println("Adding to map: " + name + " -> " + val);
+                    map.put(name[0], val);
+                } else {
+                    throw new XmlPullParserException(
+                        "Map value without name attribute: " + parser.getName());
+                }
+            } else if (eventType == parser.END_TAG) {
+                if (parser.getName().equals(endTag)) {
+                    return map;
+                }
+                throw new XmlPullParserException(
+                    "Expected " + endTag + " end tag at: " + parser.getName());
+            }
+            eventType = parser.next();
+        } while (eventType != parser.END_DOCUMENT);
+
+        throw new XmlPullParserException(
+            "Document ended before " + endTag + " end tag");
+    }
+
+    /**
+     * Read an ArrayList object from an XmlPullParser.  The XML data could
+     * previously have been generated by writeListXml().  The XmlPullParser
+     * must be positioned <em>after</em> the tag that begins the list.
+     * 
+     * @param parser The XmlPullParser from which to read the list data.
+     * @param endTag Name of the tag that will end the list, usually "list".
+     * @param name An array of one string, used to return the name attribute
+     *             of the list's tag.
+     * 
+     * @return HashMap The newly generated list.
+     * 
+     * @see #readListXml
+     */
+    public static final ArrayList readThisListXml(XmlPullParser parser, String endTag, String[] name)
+    throws XmlPullParserException, java.io.IOException
+    {
+        ArrayList list = new ArrayList();
+
+        int eventType = parser.getEventType();
+        do {
+            if (eventType == parser.START_TAG) {
+                Object val = readThisValueXml(parser, name);
+                list.add(val);
+                //System.out.println("Adding to list: " + val);
+            } else if (eventType == parser.END_TAG) {
+                if (parser.getName().equals(endTag)) {
+                    return list;
+                }
+                throw new XmlPullParserException(
+                    "Expected " + endTag + " end tag at: " + parser.getName());
+            }
+            eventType = parser.next();
+        } while (eventType != parser.END_DOCUMENT);
+
+        throw new XmlPullParserException(
+            "Document ended before " + endTag + " end tag");
+    }
+
+    /**
+     * Read an int[] object from an XmlPullParser.  The XML data could
+     * previously have been generated by writeIntArrayXml().  The XmlPullParser
+     * must be positioned <em>after</em> the tag that begins the list.
+     * 
+     * @param parser The XmlPullParser from which to read the list data.
+     * @param endTag Name of the tag that will end the list, usually "list".
+     * @param name An array of one string, used to return the name attribute
+     *             of the list's tag.
+     * 
+     * @return Returns a newly generated int[].
+     * 
+     * @see #readListXml
+     */
+    public static final int[] readThisIntArrayXml(XmlPullParser parser,
+            String endTag, String[] name)
+            throws XmlPullParserException, java.io.IOException {
+        
+        int num;
+        try {
+            num = Integer.parseInt(parser.getAttributeValue(null, "num"));
+        } catch (NullPointerException e) {
+            throw new XmlPullParserException(
+                    "Need num attribute in byte-array");
+        } catch (NumberFormatException e) {
+            throw new XmlPullParserException(
+                    "Not a number in num attribute in byte-array");
+        }
+        
+        int[] array = new int[num];
+        int i = 0;
+
+        int eventType = parser.getEventType();
+        do {
+            if (eventType == parser.START_TAG) {
+                if (parser.getName().equals("item")) {
+                    try {
+                        array[i] = Integer.parseInt(
+                                parser.getAttributeValue(null, "value"));
+                    } catch (NullPointerException e) {
+                        throw new XmlPullParserException(
+                                "Need value attribute in item");
+                    } catch (NumberFormatException e) {
+                        throw new XmlPullParserException(
+                                "Not a number in value attribute in item");
+                    }                    
+                } else {
+                    throw new XmlPullParserException(
+                            "Expected item tag at: " + parser.getName());
+                }
+            } else if (eventType == parser.END_TAG) {
+                if (parser.getName().equals(endTag)) {
+                    return array;
+                } else if (parser.getName().equals("item")) {
+                    i++;
+                } else {
+                    throw new XmlPullParserException(
+                        "Expected " + endTag + " end tag at: "
+                        + parser.getName());
+                }
+            }
+            eventType = parser.next();
+        } while (eventType != parser.END_DOCUMENT);
+
+        throw new XmlPullParserException(
+            "Document ended before " + endTag + " end tag");
+    }
+
+    /**
+     * Read a flattened object from an XmlPullParser.  The XML data could
+     * previously have been written with writeMapXml(), writeListXml(), or
+     * writeValueXml().  The XmlPullParser must be positioned <em>at</em> the
+     * tag that defines the value.
+     * 
+     * @param parser The XmlPullParser from which to read the object.
+     * @param name An array of one string, used to return the name attribute
+     *             of the value's tag.
+     * 
+     * @return Object The newly generated value object.
+     * 
+     * @see #readMapXml
+     * @see #readListXml
+     * @see #writeValueXml
+     */
+    public static final Object readValueXml(XmlPullParser parser, String[] name)
+    throws XmlPullParserException, java.io.IOException
+    {
+        int eventType = parser.getEventType();
+        do {
+            if (eventType == parser.START_TAG) {
+                return readThisValueXml(parser, name);
+            } else if (eventType == parser.END_TAG) {
+                throw new XmlPullParserException(
+                    "Unexpected end tag at: " + parser.getName());
+            } else if (eventType == parser.TEXT) {
+                throw new XmlPullParserException(
+                    "Unexpected text: " + parser.getText());
+            }
+            eventType = parser.next();
+        } while (eventType != parser.END_DOCUMENT);
+
+        throw new XmlPullParserException(
+            "Unexpected end of document");
+    }
+
+    private static final Object readThisValueXml(XmlPullParser parser, String[] name)
+    throws XmlPullParserException, java.io.IOException
+    {
+        final String valueName = parser.getAttributeValue(null, "name");
+        final String tagName = parser.getName();
+
+        //System.out.println("Reading this value tag: " + tagName + ", name=" + valueName);
+
+        Object res;
+
+        if (tagName.equals("null")) {
+            res = null;
+        } else if (tagName.equals("string")) {
+            String value = "";
+            int eventType;
+            while ((eventType = parser.next()) != parser.END_DOCUMENT) {
+                if (eventType == parser.END_TAG) {
+                    if (parser.getName().equals("string")) {
+                        name[0] = valueName;
+                        //System.out.println("Returning value for " + valueName + ": " + value);
+                        return value;
+                    }
+                    throw new XmlPullParserException(
+                        "Unexpected end tag in <string>: " + parser.getName());
+                } else if (eventType == parser.TEXT) {
+                    value += parser.getText();
+                } else if (eventType == parser.START_TAG) {
+                    throw new XmlPullParserException(
+                        "Unexpected start tag in <string>: " + parser.getName());
+                }
+            }
+            throw new XmlPullParserException(
+                "Unexpected end of document in <string>");
+        } else if (tagName.equals("int")) {
+            res = Integer.parseInt(parser.getAttributeValue(null, "value"));
+        } else if (tagName.equals("long")) {
+            res = Long.valueOf(parser.getAttributeValue(null, "value"));
+        } else if (tagName.equals("float")) {
+            res = new Float(parser.getAttributeValue(null, "value"));
+        } else if (tagName.equals("double")) {
+            res = new Double(parser.getAttributeValue(null, "value"));
+        } else if (tagName.equals("boolean")) {
+            res = Boolean.valueOf(parser.getAttributeValue(null, "value"));
+        } else if (tagName.equals("int-array")) {
+            parser.next();
+            res = readThisIntArrayXml(parser, "int-array", name);
+            name[0] = valueName;
+            //System.out.println("Returning value for " + valueName + ": " + res);
+            return res;
+        } else if (tagName.equals("map")) {
+            parser.next();
+            res = readThisMapXml(parser, "map", name);
+            name[0] = valueName;
+            //System.out.println("Returning value for " + valueName + ": " + res);
+            return res;
+        } else if (tagName.equals("list")) {
+            parser.next();
+            res = readThisListXml(parser, "list", name);
+            name[0] = valueName;
+            //System.out.println("Returning value for " + valueName + ": " + res);
+            return res;
+        } else {
+            throw new XmlPullParserException(
+                "Unknown tag: " + tagName);
+        }
+
+        // Skip through to end tag.
+        int eventType;
+        while ((eventType = parser.next()) != parser.END_DOCUMENT) {
+            if (eventType == parser.END_TAG) {
+                if (parser.getName().equals(tagName)) {
+                    name[0] = valueName;
+                    //System.out.println("Returning value for " + valueName + ": " + res);
+                    return res;
+                }
+                throw new XmlPullParserException(
+                    "Unexpected end tag in <" + tagName + ">: " + parser.getName());
+            } else if (eventType == parser.TEXT) {
+                throw new XmlPullParserException(
+                "Unexpected text in <" + tagName + ">: " + parser.getName());
+            } else if (eventType == parser.START_TAG) {
+                throw new XmlPullParserException(
+                    "Unexpected start tag in <" + tagName + ">: " + parser.getName());
+            }
+        }
+        throw new XmlPullParserException(
+            "Unexpected end of document in <" + tagName + ">");
+    }
+
+    public static final void beginDocument(XmlPullParser parser, String firstElementName) throws XmlPullParserException, IOException
+    {
+        int type;
+        while ((type=parser.next()) != parser.START_TAG
+                   && type != parser.END_DOCUMENT) {
+            ;
+        }
+
+        if (type != parser.START_TAG) {
+            throw new XmlPullParserException("No start tag found");
+        }
+        
+        if (!parser.getName().equals(firstElementName)) {
+            throw new XmlPullParserException("Unexpected start tag: found " + parser.getName() +
+                    ", expected " + firstElementName);
+        }
+    }
+    
+    public static final void nextElement(XmlPullParser parser) throws XmlPullParserException, IOException
+    {
+        int type;
+        while ((type=parser.next()) != parser.START_TAG
+                   && type != parser.END_DOCUMENT) {
+            ;
+        }   
+    }
+}
diff --git a/core/java/com/android/internal/view/IInputConnectionCallback.aidl b/core/java/com/android/internal/view/IInputConnectionCallback.aidl
new file mode 100644
index 0000000..5b5b3df
--- /dev/null
+++ b/core/java/com/android/internal/view/IInputConnectionCallback.aidl
@@ -0,0 +1,31 @@
+/*
+ * 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.internal.view;
+
+import android.graphics.Rect;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+import android.view.inputmethod.TextBoxAttribute;
+import com.android.internal.view.IInputContext;
+import android.os.IBinder;
+
+/**
+ * {@hide}
+ */
+oneway interface IInputMethodCallback {
+    void finishedEvent(int seq, boolean handled);
+}
diff --git a/core/java/com/android/internal/view/IInputConnectionWrapper.java b/core/java/com/android/internal/view/IInputConnectionWrapper.java
new file mode 100644
index 0000000..106392d
--- /dev/null
+++ b/core/java/com/android/internal/view/IInputConnectionWrapper.java
@@ -0,0 +1,406 @@
+package com.android.internal.view;
+
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.RemoteException;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.inputmethod.CompletionInfo;
+import android.view.inputmethod.ExtractedTextRequest;
+import android.view.inputmethod.InputConnection;
+
+import java.lang.ref.WeakReference;
+
+public class IInputConnectionWrapper extends IInputContext.Stub {
+    static final String TAG = "IInputConnectionWrapper";
+    
+    private static final int DO_GET_TEXT_AFTER_CURSOR = 10;
+    private static final int DO_GET_TEXT_BEFORE_CURSOR = 20;
+    private static final int DO_GET_CURSOR_CAPS_MODE = 30;
+    private static final int DO_GET_EXTRACTED_TEXT = 40;
+    private static final int DO_COMMIT_TEXT = 50;
+    private static final int DO_COMMIT_COMPLETION = 55;
+    private static final int DO_SET_SELECTION = 57;
+    private static final int DO_PERFORM_EDITOR_ACTION = 58;
+    private static final int DO_PERFORM_CONTEXT_MENU_ACTION = 59;
+    private static final int DO_SET_COMPOSING_TEXT = 60;
+    private static final int DO_FINISH_COMPOSING_TEXT = 65;
+    private static final int DO_SEND_KEY_EVENT = 70;
+    private static final int DO_DELETE_SURROUNDING_TEXT = 80;
+    private static final int DO_BEGIN_BATCH_EDIT = 90;
+    private static final int DO_END_BATCH_EDIT = 95;
+    private static final int DO_REPORT_FULLSCREEN_MODE = 100;
+    private static final int DO_PERFORM_PRIVATE_COMMAND = 120;
+    private static final int DO_CLEAR_META_KEY_STATES = 130;
+        
+    private WeakReference<InputConnection> mInputConnection;
+
+    private Looper mMainLooper;
+    private Handler mH;
+
+    static class SomeArgs {
+        Object arg1;
+        Object arg2;
+        IInputContextCallback callback;
+        int seq;
+    }
+    
+    class MyHandler extends Handler {
+        MyHandler(Looper looper) {
+            super(looper);
+        }
+        
+        @Override
+        public void handleMessage(Message msg) {
+            executeMessage(msg);
+        }
+    }
+    
+    public IInputConnectionWrapper(Looper mainLooper, InputConnection conn) {
+        mInputConnection = new WeakReference<InputConnection>(conn);
+        mMainLooper = mainLooper;
+        mH = new MyHandler(mMainLooper);
+    }
+
+    public boolean isActive() {
+        return true;
+    }
+    
+    public void getTextAfterCursor(int length, int flags, int seq, IInputContextCallback callback) {
+        dispatchMessage(obtainMessageIISC(DO_GET_TEXT_AFTER_CURSOR, length, flags, seq, callback));
+    }
+    
+    public void getTextBeforeCursor(int length, int flags, int seq, IInputContextCallback callback) {
+        dispatchMessage(obtainMessageIISC(DO_GET_TEXT_BEFORE_CURSOR, length, flags, seq, callback));
+    }
+
+    public void getCursorCapsMode(int reqModes, int seq, IInputContextCallback callback) {
+        dispatchMessage(obtainMessageISC(DO_GET_CURSOR_CAPS_MODE, reqModes, seq, callback));
+    }
+
+    public void getExtractedText(ExtractedTextRequest request,
+            int flags, int seq, IInputContextCallback callback) {
+        dispatchMessage(obtainMessageIOSC(DO_GET_EXTRACTED_TEXT, flags,
+                request, seq, callback));
+    }
+    
+    public void commitText(CharSequence text, int newCursorPosition) {
+        dispatchMessage(obtainMessageIO(DO_COMMIT_TEXT, newCursorPosition, text));
+    }
+
+    public void commitCompletion(CompletionInfo text) {
+        dispatchMessage(obtainMessageO(DO_COMMIT_COMPLETION, text));
+    }
+
+    public void setSelection(int start, int end) {
+        dispatchMessage(obtainMessageII(DO_SET_SELECTION, start, end));
+    }
+
+    public void performEditorAction(int id) {
+        dispatchMessage(obtainMessageII(DO_PERFORM_EDITOR_ACTION, id, 0));
+    }
+    
+    public void performContextMenuAction(int id) {
+        dispatchMessage(obtainMessageII(DO_PERFORM_CONTEXT_MENU_ACTION, id, 0));
+    }
+    
+    public void setComposingText(CharSequence text, int newCursorPosition) {
+        dispatchMessage(obtainMessageIO(DO_SET_COMPOSING_TEXT, newCursorPosition, text));
+    }
+
+    public void finishComposingText() {
+        dispatchMessage(obtainMessage(DO_FINISH_COMPOSING_TEXT));
+    }
+
+    public void sendKeyEvent(KeyEvent event) {
+        dispatchMessage(obtainMessageO(DO_SEND_KEY_EVENT, event));
+    }
+
+    public void clearMetaKeyStates(int states) {
+        dispatchMessage(obtainMessageII(DO_CLEAR_META_KEY_STATES, states, 0));
+    }
+
+    public void deleteSurroundingText(int leftLength, int rightLength) {
+        dispatchMessage(obtainMessageII(DO_DELETE_SURROUNDING_TEXT,
+            leftLength, rightLength));
+    }
+
+    public void beginBatchEdit() {
+        dispatchMessage(obtainMessage(DO_BEGIN_BATCH_EDIT));
+    }
+
+    public void endBatchEdit() {
+        dispatchMessage(obtainMessage(DO_END_BATCH_EDIT));
+    }
+
+    public void reportFullscreenMode(boolean enabled) {
+        dispatchMessage(obtainMessageII(DO_REPORT_FULLSCREEN_MODE, enabled ? 1 : 0, 0));
+    }
+
+    public void performPrivateCommand(String action, Bundle data) {
+        dispatchMessage(obtainMessageOO(DO_PERFORM_PRIVATE_COMMAND, action, data));
+    }
+    
+    void dispatchMessage(Message msg) {
+        // If we are calling this from the main thread, then we can call
+        // right through.  Otherwise, we need to send the message to the
+        // main thread.
+        if (Looper.myLooper() == mMainLooper) {
+            executeMessage(msg);
+            msg.recycle();
+            return;
+        }
+        
+        mH.sendMessage(msg);
+    }
+    
+    void executeMessage(Message msg) {
+        switch (msg.what) {
+            case DO_GET_TEXT_AFTER_CURSOR: {
+                SomeArgs args = (SomeArgs)msg.obj;
+                try {
+                    InputConnection ic = mInputConnection.get();
+                    if (ic == null || !isActive()) {
+                        Log.w(TAG, "getTextAfterCursor on inactive InputConnection");
+                        args.callback.setTextAfterCursor(null, args.seq);
+                        return;
+                    }
+                    args.callback.setTextAfterCursor(ic.getTextAfterCursor(
+                            msg.arg1, msg.arg2), args.seq);
+                } catch (RemoteException e) {
+                    Log.w(TAG, "Got RemoteException calling setTextAfterCursor", e);
+                }
+                return;
+            }
+            case DO_GET_TEXT_BEFORE_CURSOR: {
+                SomeArgs args = (SomeArgs)msg.obj;
+                try {
+                    InputConnection ic = mInputConnection.get();
+                    if (ic == null || !isActive()) {
+                        Log.w(TAG, "getTextBeforeCursor on inactive InputConnection");
+                        args.callback.setTextBeforeCursor(null, args.seq);
+                        return;
+                    }
+                    args.callback.setTextBeforeCursor(ic.getTextBeforeCursor(
+                            msg.arg1, msg.arg2), args.seq);
+                } catch (RemoteException e) {
+                    Log.w(TAG, "Got RemoteException calling setTextBeforeCursor", e);
+                }
+                return;
+            }
+            case DO_GET_CURSOR_CAPS_MODE: {
+                SomeArgs args = (SomeArgs)msg.obj;
+                try {
+                    InputConnection ic = mInputConnection.get();
+                    if (ic == null || !isActive()) {
+                        Log.w(TAG, "getCursorCapsMode on inactive InputConnection");
+                        args.callback.setCursorCapsMode(0, args.seq);
+                        return;
+                    }
+                    args.callback.setCursorCapsMode(ic.getCursorCapsMode(msg.arg1),
+                            args.seq);
+                } catch (RemoteException e) {
+                    Log.w(TAG, "Got RemoteException calling setCursorCapsMode", e);
+                }
+                return;
+            }
+            case DO_GET_EXTRACTED_TEXT: {
+                SomeArgs args = (SomeArgs)msg.obj;
+                try {
+                    InputConnection ic = mInputConnection.get();
+                    if (ic == null || !isActive()) {
+                        Log.w(TAG, "getExtractedText on inactive InputConnection");
+                        args.callback.setExtractedText(null, args.seq);
+                        return;
+                    }
+                    args.callback.setExtractedText(ic.getExtractedText(
+                            (ExtractedTextRequest)args.arg1, msg.arg1), args.seq);
+                } catch (RemoteException e) {
+                    Log.w(TAG, "Got RemoteException calling setExtractedText", e);
+                }
+                return;
+            }
+            case DO_COMMIT_TEXT: {
+                InputConnection ic = mInputConnection.get();
+                if (ic == null || !isActive()) {
+                    Log.w(TAG, "commitText on inactive InputConnection");
+                    return;
+                }
+                ic.commitText((CharSequence)msg.obj, msg.arg1);
+                return;
+            }
+            case DO_SET_SELECTION: {
+                InputConnection ic = mInputConnection.get();
+                if (ic == null || !isActive()) {
+                    Log.w(TAG, "setSelection on inactive InputConnection");
+                    return;
+                }
+                ic.setSelection(msg.arg1, msg.arg2);
+                return;
+            }
+            case DO_PERFORM_EDITOR_ACTION: {
+                InputConnection ic = mInputConnection.get();
+                if (ic == null || !isActive()) {
+                    Log.w(TAG, "performEditorAction on inactive InputConnection");
+                    return;
+                }
+                ic.performEditorAction(msg.arg1);
+                return;
+            }
+            case DO_PERFORM_CONTEXT_MENU_ACTION: {
+                InputConnection ic = mInputConnection.get();
+                if (ic == null || !isActive()) {
+                    Log.w(TAG, "performContextMenuAction on inactive InputConnection");
+                    return;
+                }
+                ic.performContextMenuAction(msg.arg1);
+                return;
+            }
+            case DO_COMMIT_COMPLETION: {
+                InputConnection ic = mInputConnection.get();
+                if (ic == null || !isActive()) {
+                    Log.w(TAG, "commitCompletion on inactive InputConnection");
+                    return;
+                }
+                ic.commitCompletion((CompletionInfo)msg.obj);
+                return;
+            }
+            case DO_SET_COMPOSING_TEXT: {
+                InputConnection ic = mInputConnection.get();
+                if (ic == null || !isActive()) {
+                    Log.w(TAG, "setComposingText on inactive InputConnection");
+                    return;
+                }
+                ic.setComposingText((CharSequence)msg.obj, msg.arg1);
+                return;
+            }
+            case DO_FINISH_COMPOSING_TEXT: {
+                InputConnection ic = mInputConnection.get();
+                // Note we do NOT check isActive() here, because this is safe
+                // for an IME to call at any time, and we need to allow it
+                // through to clean up our state after the IME has switched to
+                // another client.
+                if (ic == null) {
+                    Log.w(TAG, "finishComposingText on inactive InputConnection");
+                    return;
+                }
+                ic.finishComposingText();
+                return;
+            }
+            case DO_SEND_KEY_EVENT: {
+                InputConnection ic = mInputConnection.get();
+                if (ic == null || !isActive()) {
+                    Log.w(TAG, "sendKeyEvent on inactive InputConnection");
+                    return;
+                }
+                ic.sendKeyEvent((KeyEvent)msg.obj);
+                return;
+            }
+            case DO_CLEAR_META_KEY_STATES: {
+                InputConnection ic = mInputConnection.get();
+                if (ic == null || !isActive()) {
+                    Log.w(TAG, "clearMetaKeyStates on inactive InputConnection");
+                    return;
+                }
+                ic.clearMetaKeyStates(msg.arg1);
+                return;
+            }
+            case DO_DELETE_SURROUNDING_TEXT: {
+                InputConnection ic = mInputConnection.get();
+                if (ic == null || !isActive()) {
+                    Log.w(TAG, "deleteSurroundingText on inactive InputConnection");
+                    return;
+                }
+                ic.deleteSurroundingText(msg.arg1, msg.arg2);
+                return;
+            }
+            case DO_BEGIN_BATCH_EDIT: {
+                InputConnection ic = mInputConnection.get();
+                if (ic == null || !isActive()) {
+                    Log.w(TAG, "beginBatchEdit on inactive InputConnection");
+                    return;
+                }
+                ic.beginBatchEdit();
+                return;
+            }
+            case DO_END_BATCH_EDIT: {
+                InputConnection ic = mInputConnection.get();
+                if (ic == null || !isActive()) {
+                    Log.w(TAG, "endBatchEdit on inactive InputConnection");
+                    return;
+                }
+                ic.endBatchEdit();
+                return;
+            }
+            case DO_REPORT_FULLSCREEN_MODE: {
+                InputConnection ic = mInputConnection.get();
+                if (ic == null || !isActive()) {
+                    Log.w(TAG, "showStatusIcon on inactive InputConnection");
+                    return;
+                }
+                ic.reportFullscreenMode(msg.arg1 == 1);
+                return;
+            }
+            case DO_PERFORM_PRIVATE_COMMAND: {
+                InputConnection ic = mInputConnection.get();
+                if (ic == null || !isActive()) {
+                    Log.w(TAG, "performPrivateCommand on inactive InputConnection");
+                    return;
+                }
+                SomeArgs args = (SomeArgs)msg.obj;
+                ic.performPrivateCommand((String)args.arg1,
+                        (Bundle)args.arg2);
+                return;
+            }
+        }
+        Log.w(TAG, "Unhandled message code: " + msg.what);
+    }
+    
+    Message obtainMessage(int what) {
+        return mH.obtainMessage(what);
+    }
+    
+    Message obtainMessageII(int what, int arg1, int arg2) {
+        return mH.obtainMessage(what, arg1, arg2);
+    }
+    
+    Message obtainMessageO(int what, Object arg1) {
+        return mH.obtainMessage(what, 0, 0, arg1);
+    }
+    
+    Message obtainMessageISC(int what, int arg1, int seq, IInputContextCallback callback) {
+        SomeArgs args = new SomeArgs();
+        args.callback = callback;
+        args.seq = seq;
+        return mH.obtainMessage(what, arg1, 0, args);
+    }
+    
+    Message obtainMessageIISC(int what, int arg1, int arg2, int seq, IInputContextCallback callback) {
+        SomeArgs args = new SomeArgs();
+        args.callback = callback;
+        args.seq = seq;
+        return mH.obtainMessage(what, arg1, arg2, args);
+    }
+    
+    Message obtainMessageIOSC(int what, int arg1, Object arg2, int seq,
+            IInputContextCallback callback) {
+        SomeArgs args = new SomeArgs();
+        args.arg1 = arg2;
+        args.callback = callback;
+        args.seq = seq;
+        return mH.obtainMessage(what, arg1, 0, args);
+    }
+    
+    Message obtainMessageIO(int what, int arg1, Object arg2) {
+        return mH.obtainMessage(what, arg1, 0, arg2);
+    }
+    
+    Message obtainMessageOO(int what, Object arg1, Object arg2) {
+        SomeArgs args = new SomeArgs();
+        args.arg1 = arg1;
+        args.arg2 = arg2;
+        return mH.obtainMessage(what, 0, 0, args);
+    }
+}
diff --git a/core/java/com/android/internal/view/IInputContext.aidl b/core/java/com/android/internal/view/IInputContext.aidl
new file mode 100644
index 0000000..02cb9e4
--- /dev/null
+++ b/core/java/com/android/internal/view/IInputContext.aidl
@@ -0,0 +1,68 @@
+/*
+ * 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.internal.view;
+
+import android.os.Bundle;
+import android.view.KeyEvent;
+import android.view.inputmethod.CompletionInfo;
+import android.view.inputmethod.ExtractedTextRequest;
+
+import com.android.internal.view.IInputContextCallback;
+
+/**
+ * Interface from an input method to the application, allowing it to perform
+ * edits on the current input field and other interactions with the application.
+ * {@hide}
+ */
+ oneway interface IInputContext {
+    void getTextBeforeCursor(int length, int flags, int seq, IInputContextCallback callback); 
+
+    void getTextAfterCursor(int length, int flags, int seq, IInputContextCallback callback);
+    
+    void getCursorCapsMode(int reqModes, int seq, IInputContextCallback callback);
+    
+    void getExtractedText(in ExtractedTextRequest request, int flags, int seq,
+            IInputContextCallback callback);
+    
+    void deleteSurroundingText(int leftLength, int rightLength);
+
+    void setComposingText(CharSequence text, int newCursorPosition);
+
+    void finishComposingText();
+    
+    void commitText(CharSequence text, int newCursorPosition);
+
+    void commitCompletion(in CompletionInfo completion);
+
+    void setSelection(int start, int end);
+    
+    void performEditorAction(int actionCode);
+    
+    void performContextMenuAction(int id);
+    
+    void beginBatchEdit();
+    
+    void endBatchEdit();
+    
+    void reportFullscreenMode(boolean enabled);
+    
+    void sendKeyEvent(in KeyEvent event);
+    
+    void clearMetaKeyStates(int states);
+    
+    void performPrivateCommand(String action, in Bundle data);
+}
diff --git a/core/java/com/android/internal/view/IInputContextCallback.aidl b/core/java/com/android/internal/view/IInputContextCallback.aidl
new file mode 100644
index 0000000..9b8c43c
--- /dev/null
+++ b/core/java/com/android/internal/view/IInputContextCallback.aidl
@@ -0,0 +1,29 @@
+/*
+ * 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.internal.view;
+
+import android.view.inputmethod.ExtractedText;
+
+/**
+ * {@hide}
+ */
+oneway interface IInputContextCallback {
+    void setTextBeforeCursor(CharSequence textBeforeCursor, int seq);
+    void setTextAfterCursor(CharSequence textAfterCursor, int seq);
+    void setCursorCapsMode(int capsMode, int seq);
+    void setExtractedText(in ExtractedText extractedText, int seq);
+}
diff --git a/core/java/com/android/internal/view/IInputMethod.aidl b/core/java/com/android/internal/view/IInputMethod.aidl
new file mode 100644
index 0000000..9b00402
--- /dev/null
+++ b/core/java/com/android/internal/view/IInputMethod.aidl
@@ -0,0 +1,54 @@
+/*
+ * 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.internal.view;
+
+import android.graphics.Rect;
+import android.os.IBinder;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.InputBinding;
+import com.android.internal.view.IInputContext;
+import com.android.internal.view.IInputMethodCallback;
+import com.android.internal.view.IInputMethodSession;
+
+/**
+ * Top-level interface to an input method component (implemented in a
+ * Service).
+ * {@hide}
+ */
+oneway interface IInputMethod {
+    void attachToken(IBinder token);
+    
+    void bindInput(in InputBinding binding);
+    
+    void unbindInput();
+
+    void startInput(in IInputContext inputContext, in EditorInfo attribute);
+
+    void restartInput(in IInputContext inputContext, in EditorInfo attribute);
+
+    void createSession(IInputMethodCallback callback);
+    
+    void setSessionEnabled(IInputMethodSession session, boolean enabled);
+    
+    void revokeSession(IInputMethodSession session);
+    
+    void showSoftInput(int flags);
+    
+    void hideSoftInput();
+}
diff --git a/core/java/com/android/internal/view/IInputMethodCallback.aidl b/core/java/com/android/internal/view/IInputMethodCallback.aidl
new file mode 100644
index 0000000..480cc0e
--- /dev/null
+++ b/core/java/com/android/internal/view/IInputMethodCallback.aidl
@@ -0,0 +1,34 @@
+/*
+ * 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.internal.view;
+
+import android.graphics.Rect;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+import com.android.internal.view.IInputContext;
+import com.android.internal.view.IInputMethodSession;
+import android.os.IBinder;
+
+/**
+ * Helper interface for IInputMethod to allow the input method to call back
+ * to its client with results from incoming calls.
+ * {@hide}
+ */
+oneway interface IInputMethodCallback {
+    void finishedEvent(int seq, boolean handled);
+    void sessionCreated(IInputMethodSession session);
+}
diff --git a/core/java/com/android/internal/view/IInputMethodClient.aidl b/core/java/com/android/internal/view/IInputMethodClient.aidl
new file mode 100644
index 0000000..ce4312d
--- /dev/null
+++ b/core/java/com/android/internal/view/IInputMethodClient.aidl
@@ -0,0 +1,30 @@
+/*
+ * 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.internal.view;
+
+import com.android.internal.view.InputBindResult;
+
+/**
+ * Interface a client of the IInputMethodManager implements, to identify
+ * itself and receive information about changes to the global manager state.
+ */
+oneway interface IInputMethodClient {
+    void setUsingInputMethod(boolean state);
+    void onBindMethod(in InputBindResult res);
+    void onUnbindMethod(int sequence);
+    void setActive(boolean active);
+}
diff --git a/core/java/com/android/internal/view/IInputMethodManager.aidl b/core/java/com/android/internal/view/IInputMethodManager.aidl
new file mode 100644
index 0000000..1b1c7f7
--- /dev/null
+++ b/core/java/com/android/internal/view/IInputMethodManager.aidl
@@ -0,0 +1,54 @@
+/*
+ * 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.internal.view;
+
+import android.view.inputmethod.InputMethodInfo;
+import android.view.inputmethod.EditorInfo;
+import com.android.internal.view.InputBindResult;
+import com.android.internal.view.IInputContext;
+import com.android.internal.view.IInputMethodClient;
+
+/**
+ * Public interface to the global input method manager, used by all client
+ * applications.
+ */
+interface IInputMethodManager {
+    List<InputMethodInfo> getInputMethodList();
+    List<InputMethodInfo> getEnabledInputMethodList();
+    
+    void addClient(in IInputMethodClient client,
+            in IInputContext inputContext, int uid, int pid);
+    void removeClient(in IInputMethodClient client);
+            
+    InputBindResult startInput(in IInputMethodClient client,
+            IInputContext inputContext, in EditorInfo attribute,
+            boolean initial, boolean needResult);
+    void finishInput(in IInputMethodClient client);
+    void showSoftInput(in IInputMethodClient client, int flags);
+    void hideSoftInput(in IInputMethodClient client, int flags);
+    void windowGainedFocus(in IInputMethodClient client,
+            boolean viewHasFocus, boolean isTextEditor,
+            int softInputMode, boolean first, int windowFlags);
+            
+    void showInputMethodPickerFromClient(in IInputMethodClient client);
+    void setInputMethod(in IBinder token, String id);
+    void hideMySoftInput(in IBinder token, int flags);
+    void updateStatusIcon(in IBinder token, String packageName, int iconId);
+    
+    boolean setInputMethodEnabled(String id, boolean enabled);
+}
+
diff --git a/core/java/com/android/internal/view/IInputMethodSession.aidl b/core/java/com/android/internal/view/IInputMethodSession.aidl
new file mode 100644
index 0000000..8a44976
--- /dev/null
+++ b/core/java/com/android/internal/view/IInputMethodSession.aidl
@@ -0,0 +1,49 @@
+/*
+ * 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.internal.view;
+
+import android.graphics.Rect;
+import android.os.Bundle;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+import android.view.inputmethod.CompletionInfo;
+import android.view.inputmethod.ExtractedText;
+import com.android.internal.view.IInputMethodCallback;
+
+/**
+ * Sub-interface of IInputMethod which is safe to give to client applications.
+ * {@hide}
+ */
+oneway interface IInputMethodSession {
+    void finishInput();
+
+    void updateExtractedText(int token, in ExtractedText text);
+    
+    void updateSelection(int oldSelStart, int oldSelEnd,
+            int newSelStart, int newSelEnd,
+            int candidatesStart, int candidatesEnd);
+    
+    void updateCursor(in Rect newCursor);
+    
+    void displayCompletions(in CompletionInfo[] completions);
+    
+    void dispatchKeyEvent(int seq, in KeyEvent event, IInputMethodCallback callback);
+
+    void dispatchTrackballEvent(int seq, in MotionEvent event, IInputMethodCallback callback);
+
+    void appPrivateCommand(String action, in Bundle data);
+}
diff --git a/core/java/com/android/internal/view/InputBindResult.aidl b/core/java/com/android/internal/view/InputBindResult.aidl
new file mode 100644
index 0000000..7ff5c4e
--- /dev/null
+++ b/core/java/com/android/internal/view/InputBindResult.aidl
@@ -0,0 +1,19 @@
+/*
+ * 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.internal.view;
+
+parcelable InputBindResult;
diff --git a/core/java/com/android/internal/view/InputBindResult.java b/core/java/com/android/internal/view/InputBindResult.java
new file mode 100644
index 0000000..658f098
--- /dev/null
+++ b/core/java/com/android/internal/view/InputBindResult.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2007-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.internal.view;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Bundle of information returned by input method manager about a successful
+ * binding to an input method.
+ */
+public final class InputBindResult implements Parcelable {
+    static final String TAG = "InputBindResult";
+    
+    /**
+     * The input method service.
+     */
+    public final IInputMethodSession method;
+    
+    /**
+     * The ID for this input method, as found in InputMethodInfo; null if
+     * no input method will be bound.
+     */
+    public final String id;
+    
+    /**
+     * Sequence number of this binding.
+     */
+    public final int sequence;
+    
+    public InputBindResult(IInputMethodSession _method, String _id, int _sequence) {
+        method = _method;
+        id = _id;
+        sequence = _sequence;
+    }
+    
+    InputBindResult(Parcel source) {
+        method = IInputMethodSession.Stub.asInterface(source.readStrongBinder());
+        id = source.readString();
+        sequence = source.readInt();
+    }
+    
+    @Override
+    public String toString() {
+        return "InputBindResult{" + method + " " + id
+                + " #" + sequence + "}";
+    }
+
+    /**
+     * Used to package this object into a {@link Parcel}.
+     * 
+     * @param dest The {@link Parcel} to be written.
+     * @param flags The flags used for parceling.
+     */
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeStrongInterface(method);
+        dest.writeString(id);
+        dest.writeInt(sequence);
+    }
+
+    /**
+     * Used to make this class parcelable.
+     */
+    public static final Parcelable.Creator<InputBindResult> CREATOR = new Parcelable.Creator<InputBindResult>() {
+        public InputBindResult createFromParcel(Parcel source) {
+            return new InputBindResult(source);
+        }
+
+        public InputBindResult[] newArray(int size) {
+            return new InputBindResult[size];
+        }
+    };
+
+    public int describeContents() {
+        return 0;
+    }
+}
diff --git a/core/java/com/android/internal/view/InputConnectionWrapper.java b/core/java/com/android/internal/view/InputConnectionWrapper.java
new file mode 100644
index 0000000..b92cb45
--- /dev/null
+++ b/core/java/com/android/internal/view/InputConnectionWrapper.java
@@ -0,0 +1,351 @@
+package com.android.internal.view;
+
+import com.android.internal.view.IInputContext;
+
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.inputmethod.CompletionInfo;
+import android.view.inputmethod.ExtractedText;
+import android.view.inputmethod.ExtractedTextRequest;
+import android.view.inputmethod.InputConnection;
+
+public class InputConnectionWrapper implements InputConnection {
+    private static final int MAX_WAIT_TIME_MILLIS = 2000;
+    private final IInputContext mIInputContext;
+    
+    static class InputContextCallback extends IInputContextCallback.Stub {
+        private static final String TAG = "InputConnectionWrapper.ICC";
+        public int mSeq;
+        public boolean mHaveValue;
+        public CharSequence mTextBeforeCursor;
+        public CharSequence mTextAfterCursor;
+        public ExtractedText mExtractedText;
+        public int mCursorCapsMode;
+        
+        // A 'pool' of one InputContextCallback.  Each ICW request will attempt to gain
+        // exclusive access to this object.
+        private static InputContextCallback sInstance = new InputContextCallback();
+        private static int sSequenceNumber = 1;
+        
+        /**
+         * Returns an InputContextCallback object that is guaranteed not to be in use by
+         * any other thread.  The returned object's 'have value' flag is cleared and its expected
+         * sequence number is set to a new integer.  We use a sequence number so that replies that
+         * occur after a timeout has expired are not interpreted as replies to a later request.
+         */
+        private static InputContextCallback getInstance() {
+            synchronized (InputContextCallback.class) {
+                // Return sInstance if it's non-null, otherwise construct a new callback
+                InputContextCallback callback;
+                if (sInstance != null) {
+                    callback = sInstance;
+                    sInstance = null;
+                    
+                    // Reset the callback
+                    callback.mHaveValue = false;
+                } else {
+                    callback = new InputContextCallback();
+                }
+                
+                // Set the sequence number
+                callback.mSeq = sSequenceNumber++;
+                return callback;
+            }
+        }
+        
+        /**
+         * Makes the given InputContextCallback available for use in the future.
+         */
+        private void dispose() {
+            synchronized (InputContextCallback.class) {
+                // If sInstance is non-null, just let this object be garbage-collected
+                if (sInstance == null) {
+                    // Allow any objects being held to be gc'ed
+                    mTextAfterCursor = null;
+                    mTextBeforeCursor = null;
+                    mExtractedText = null;
+                    sInstance = this;
+                }
+            }
+        }
+        
+        public void setTextBeforeCursor(CharSequence textBeforeCursor, int seq) {
+            synchronized (this) {
+                if (seq == mSeq) {
+                    mTextBeforeCursor = textBeforeCursor;
+                    mHaveValue = true;
+                    notifyAll();
+                } else {
+                    Log.i(TAG, "Got out-of-sequence callback " + seq + " (expected " + mSeq
+                            + ") in setTextBeforeCursor, ignoring.");
+                }
+            }
+        }
+
+        public void setTextAfterCursor(CharSequence textAfterCursor, int seq) {
+            synchronized (this) {
+                if (seq == mSeq) {
+                    mTextAfterCursor = textAfterCursor;
+                    mHaveValue = true;
+                    notifyAll();
+                } else {
+                    Log.i(TAG, "Got out-of-sequence callback " + seq + " (expected " + mSeq
+                            + ") in setTextAfterCursor, ignoring.");
+                }
+            }
+        }
+
+        public void setCursorCapsMode(int capsMode, int seq) {
+            synchronized (this) {
+                if (seq == mSeq) {
+                    mCursorCapsMode = capsMode; 
+                    mHaveValue = true;  
+                    notifyAll();
+                } else {
+                    Log.i(TAG, "Got out-of-sequence callback " + seq + " (expected " + mSeq
+                            + ") in setCursorCapsMode, ignoring.");
+                }
+            }
+        }
+
+        public void setExtractedText(ExtractedText extractedText, int seq) {
+            synchronized (this) {
+                if (seq == mSeq) {
+                    mExtractedText = extractedText;
+                    mHaveValue = true;
+                    notifyAll();
+                } else {
+                    Log.i(TAG, "Got out-of-sequence callback " + seq + " (expected " + mSeq
+                            + ") in setExtractedText, ignoring.");
+                }
+            }
+        }
+        
+        /**
+         * Waits for a result for up to {@link #MAX_WAIT_TIME_MILLIS} milliseconds.
+         * 
+         * <p>The caller must be synchronized on this callback object.
+         */
+        void waitForResultLocked() {
+            long startTime = SystemClock.uptimeMillis();
+            long endTime = startTime + MAX_WAIT_TIME_MILLIS;
+
+            while (!mHaveValue) {
+                long remainingTime = endTime - SystemClock.uptimeMillis();
+                if (remainingTime <= 0) {
+                    Log.w(TAG, "Timed out waiting on IInputContextCallback");
+                    return;
+                }
+                try {
+                    wait(remainingTime);
+                } catch (InterruptedException e) {
+                }
+            }
+        }
+    }
+
+    public InputConnectionWrapper(IInputContext inputContext) {
+        mIInputContext = inputContext;
+    }
+
+    public CharSequence getTextAfterCursor(int length, int flags) {
+        CharSequence value = null;
+        try {
+            InputContextCallback callback = InputContextCallback.getInstance();
+            mIInputContext.getTextAfterCursor(length, flags, callback.mSeq, callback);
+            synchronized (callback) {
+                callback.waitForResultLocked();
+                if (callback.mHaveValue) {
+                    value = callback.mTextAfterCursor;
+                }
+            }
+            callback.dispose();
+        } catch (RemoteException e) {
+            return null;
+        }
+        return value;
+    }
+    
+    public CharSequence getTextBeforeCursor(int length, int flags) {
+        CharSequence value = null;
+        try {
+            InputContextCallback callback = InputContextCallback.getInstance();
+            mIInputContext.getTextBeforeCursor(length, flags, callback.mSeq, callback);
+            synchronized (callback) {
+                callback.waitForResultLocked();
+                if (callback.mHaveValue) {
+                    value = callback.mTextBeforeCursor;
+                }
+            }
+            callback.dispose();
+        } catch (RemoteException e) {
+            return null;
+        }
+        return value;
+    }
+    
+    public int getCursorCapsMode(int reqModes) {
+        int value = 0;
+        try {
+            InputContextCallback callback = InputContextCallback.getInstance();
+            mIInputContext.getCursorCapsMode(reqModes, callback.mSeq, callback);
+            synchronized (callback) {
+                callback.waitForResultLocked();
+                if (callback.mHaveValue) {
+                    value = callback.mCursorCapsMode;
+                }
+            }
+            callback.dispose();
+        } catch (RemoteException e) {
+            return 0;
+        }
+        return value;
+    }
+    
+    public ExtractedText getExtractedText(ExtractedTextRequest request, int flags) {
+        ExtractedText value = null;
+        try {
+            InputContextCallback callback = InputContextCallback.getInstance();
+            mIInputContext.getExtractedText(request, flags, callback.mSeq, callback);
+            synchronized (callback) {
+                callback.waitForResultLocked();
+                if (callback.mHaveValue) {
+                    value = callback.mExtractedText;
+                }
+            }
+            callback.dispose();
+        } catch (RemoteException e) {
+            return null;
+        }
+        return value;
+    }
+    
+    public boolean commitText(CharSequence text, int newCursorPosition) {
+        try {
+            mIInputContext.commitText(text, newCursorPosition);
+            return true;
+        } catch (RemoteException e) {
+            return false;
+        }
+    }
+
+    public boolean commitCompletion(CompletionInfo text) {
+        try {
+            mIInputContext.commitCompletion(text);
+            return true;
+        } catch (RemoteException e) {
+            return false;
+        }
+    }
+
+    public boolean setSelection(int start, int end) {
+        try {
+            mIInputContext.setSelection(start, end);
+            return true;
+        } catch (RemoteException e) {
+            return false;
+        }
+    }
+    
+    public boolean performEditorAction(int actionCode) {
+        try {
+            mIInputContext.performEditorAction(actionCode);
+            return true;
+        } catch (RemoteException e) {
+            return false;
+        }
+    }
+    
+    public boolean performContextMenuAction(int id) {
+        try {
+            mIInputContext.performContextMenuAction(id);
+            return true;
+        } catch (RemoteException e) {
+            return false;
+        }
+    }
+    
+    public boolean setComposingText(CharSequence text, int newCursorPosition) {
+        try {
+            mIInputContext.setComposingText(text, newCursorPosition);
+            return true;
+        } catch (RemoteException e) {
+            return false;
+        }
+    }
+
+    public boolean finishComposingText() {
+        try {
+            mIInputContext.finishComposingText();
+            return true;
+        } catch (RemoteException e) {
+            return false;
+        }
+    }
+
+    public boolean beginBatchEdit() {
+        try {
+            mIInputContext.beginBatchEdit();
+            return true;
+        } catch (RemoteException e) {
+            return false;
+        }
+    }
+    
+    public boolean endBatchEdit() {
+        try {
+            mIInputContext.endBatchEdit();
+            return true;
+        } catch (RemoteException e) {
+            return false;
+        }
+    }
+    
+    public boolean sendKeyEvent(KeyEvent event) {
+        try {
+            mIInputContext.sendKeyEvent(event);
+            return true;
+        } catch (RemoteException e) {
+            return false;
+        }
+    }
+
+    public boolean clearMetaKeyStates(int states) {
+        try {
+            mIInputContext.clearMetaKeyStates(states);
+            return true;
+        } catch (RemoteException e) {
+            return false;
+        }
+    }
+    
+    public boolean deleteSurroundingText(int leftLength, int rightLength) {
+        try {
+            mIInputContext.deleteSurroundingText(leftLength, rightLength);
+            return true;
+        } catch (RemoteException e) {
+            return false;
+        }
+    }
+
+    public boolean reportFullscreenMode(boolean enabled) {
+        try {
+            mIInputContext.reportFullscreenMode(enabled);
+            return true;
+        } catch (RemoteException e) {
+            return false;
+        }
+    }
+
+    public boolean performPrivateCommand(String action, Bundle data) {
+        try {
+            mIInputContext.performPrivateCommand(action, data);
+            return true;
+        } catch (RemoteException e) {
+            return false;
+        }
+    }
+}
diff --git a/core/java/com/android/internal/view/menu/ContextMenuBuilder.java b/core/java/com/android/internal/view/menu/ContextMenuBuilder.java
new file mode 100644
index 0000000..bf44d51
--- /dev/null
+++ b/core/java/com/android/internal/view/menu/ContextMenuBuilder.java
@@ -0,0 +1,96 @@
+/*
+ * 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.internal.view.menu;
+
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.os.IBinder;
+import android.util.EventLog;
+import android.view.ContextMenu;
+import android.view.View;
+
+/**
+ * Implementation of the {@link android.view.ContextMenu} interface.
+ * <p>
+ * Most clients of the menu framework will never need to touch this
+ * class.  However, if the client has a window that
+ * is not a content view of a Dialog or Activity (for example, the
+ * view was added directly to the window manager) and needs to show
+ * context menus, it will use this class.
+ * <p>
+ * To use this class, instantiate it via {@link #ContextMenuBuilder(Context)},
+ * and optionally populate it with any of your custom items.  Finally,
+ * call {@link #show(View, IBinder)} which will populate the menu
+ * with a view's context menu items and show the context menu.
+ */
+public class ContextMenuBuilder extends MenuBuilder implements ContextMenu {
+    
+    public ContextMenuBuilder(Context context) {
+        super(context);
+    }
+
+    public ContextMenu setHeaderIcon(Drawable icon) {
+        return (ContextMenu) super.setHeaderIconInt(icon);
+    }
+
+    public ContextMenu setHeaderIcon(int iconRes) {
+        return (ContextMenu) super.setHeaderIconInt(iconRes);
+    }
+
+    public ContextMenu setHeaderTitle(CharSequence title) {
+        return (ContextMenu) super.setHeaderTitleInt(title);
+    }
+
+    public ContextMenu setHeaderTitle(int titleRes) {
+        return (ContextMenu) super.setHeaderTitleInt(titleRes);
+    }
+
+    public ContextMenu setHeaderView(View view) {
+        return (ContextMenu) super.setHeaderViewInt(view);
+    }
+
+    /**
+     * Shows this context menu, allowing the optional original view (and its
+     * ancestors) to add items.
+     * 
+     * @param originalView Optional, the original view that triggered the
+     *        context menu.
+     * @param token Optional, the window token that should be set on the context
+     *        menu's window.
+     * @return If the context menu was shown, the {@link MenuDialogHelper} for
+     *         dismissing it. Otherwise, null.
+     */
+    public MenuDialogHelper show(View originalView, IBinder token) {
+        if (originalView != null) {
+            // Let relevant views and their populate context listeners populate
+            // the context menu
+            originalView.createContextMenu(this);
+        }
+
+        if (getVisibleItems().size() > 0) {
+            EventLog.writeEvent(50001, 1);
+            
+            MenuDialogHelper helper = new MenuDialogHelper(this); 
+            helper.show(token);
+            
+            return helper;
+        }
+        
+        return null;
+    }
+    
+}
diff --git a/core/java/com/android/internal/view/menu/ExpandedMenuView.java b/core/java/com/android/internal/view/menu/ExpandedMenuView.java
new file mode 100644
index 0000000..9e4b4ce
--- /dev/null
+++ b/core/java/com/android/internal/view/menu/ExpandedMenuView.java
@@ -0,0 +1,100 @@
+/*
+ * 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.internal.view.menu;
+
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.AdapterView;
+import android.widget.BaseAdapter;
+import android.widget.ListAdapter;
+import android.widget.ListView;
+import android.widget.AdapterView.OnItemClickListener;
+
+import com.android.internal.view.menu.MenuBuilder.ItemInvoker;
+
+/**
+ * The expanded menu view is a list-like menu with all of the available menu items.  It is opened
+ * by the user clicking no the 'More' button on the icon menu view.
+ */
+public final class ExpandedMenuView extends ListView implements ItemInvoker, MenuView, OnItemClickListener {
+    private MenuBuilder mMenu;
+
+    /** Default animations for this menu */
+    private int mAnimations;
+    
+    /**
+     * Instantiates the ExpandedMenuView that is linked with the provided MenuBuilder.
+     * @param menu The model for the menu which this MenuView will display
+     */
+    public ExpandedMenuView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        
+        TypedArray a = context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.MenuView, 0, 0);
+        mAnimations = a.getResourceId(com.android.internal.R.styleable.MenuView_windowAnimationStyle, 0);
+        a.recycle();
+
+        setOnItemClickListener(this);
+    }
+
+    public void initialize(MenuBuilder menu, int menuType) {
+        mMenu = menu;
+
+        setAdapter(menu.new MenuAdapter(menuType));
+    }
+
+    public void updateChildren(boolean cleared) {
+        ListAdapter adapter = getAdapter();
+        // Tell adapter of the change, it will notify the mListView
+        if (adapter != null) {
+            if (cleared) {
+                ((BaseAdapter)adapter).notifyDataSetInvalidated();
+            }
+            else {
+                ((BaseAdapter)adapter).notifyDataSetChanged();
+            }
+        }
+    }
+
+    @Override
+    protected void onDetachedFromWindow() {
+        super.onDetachedFromWindow();
+        
+        // Clear the cached bitmaps of children
+        setChildrenDrawingCacheEnabled(false);
+    }
+
+    @Override
+    protected boolean recycleOnMeasure() {
+        return false;
+    }
+
+    public boolean invokeItem(MenuItemImpl item) {
+        return mMenu.performItemAction(item, 0);
+    }
+
+    public void onItemClick(AdapterView parent, View v, int position, long id) {
+        invokeItem((MenuItemImpl) getAdapter().getItem(position));
+    }
+
+    public int getWindowAnimations() {
+        return mAnimations;
+    }
+    
+}
diff --git a/core/java/com/android/internal/view/menu/IconMenuItemView.java b/core/java/com/android/internal/view/menu/IconMenuItemView.java
new file mode 100644
index 0000000..9e1f2ae
--- /dev/null
+++ b/core/java/com/android/internal/view/menu/IconMenuItemView.java
@@ -0,0 +1,284 @@
+/*
+ * 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.internal.view.menu;
+
+import com.android.internal.view.menu.MenuBuilder.ItemInvoker;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.util.AttributeSet;
+import android.view.Gravity;
+import android.view.SoundEffectConstants;
+import android.view.View;
+import android.view.ViewDebug;
+import android.widget.TextView;
+
+/**
+ * The item view for each item in the {@link IconMenuView}.  
+ */
+public final class IconMenuItemView extends TextView implements MenuView.ItemView {
+    
+    private static final int NO_ALPHA = 0xFF;
+    
+    private IconMenuView mIconMenuView;
+    
+    private ItemInvoker mItemInvoker;
+    private MenuItemImpl mItemData; 
+    
+    private Drawable mIcon;
+    
+    private int mTextAppearance;
+    private Context mTextAppearanceContext;
+    
+    private float mDisabledAlpha;
+
+    private Rect mPositionIconAvailable = new Rect();
+    private Rect mPositionIconOutput = new Rect();
+    
+    private boolean mShortcutCaptionMode;
+    private String mShortcutCaption;
+    
+    private static String sPrependShortcutLabel;
+
+    public IconMenuItemView(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs);
+
+        if (sPrependShortcutLabel == null) {
+            /*
+             * Views should only be constructed from the UI thread, so no
+             * synchronization needed
+             */
+            sPrependShortcutLabel = getResources().getString(
+                    com.android.internal.R.string.prepend_shortcut_label);
+        }
+        
+        TypedArray a =
+            context.obtainStyledAttributes(
+                attrs, com.android.internal.R.styleable.MenuView, defStyle, 0);
+
+        mDisabledAlpha = a.getFloat(
+                com.android.internal.R.styleable.MenuView_itemIconDisabledAlpha, 0.8f);
+        mTextAppearance = a.getResourceId(com.android.internal.R.styleable.
+                                          MenuView_itemTextAppearance, -1);
+        mTextAppearanceContext = context;
+        
+        a.recycle();
+    }
+    
+    public IconMenuItemView(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    /**
+     * Initializes with the provided title and icon
+     * @param title The title of this item
+     * @param icon The icon of this item
+     */
+    void initialize(CharSequence title, Drawable icon) {
+        setClickable(true);
+        setFocusable(true);
+
+        if (mTextAppearance != -1) {
+            setTextAppearance(mTextAppearanceContext, mTextAppearance);
+        }
+
+        setTitle(title);
+        setIcon(icon);
+    }
+    
+    public void initialize(MenuItemImpl itemData, int menuType) {
+        mItemData = itemData;
+
+        initialize(itemData.getTitleForItemView(this), itemData.getIcon());
+        
+        setVisibility(itemData.isVisible() ? View.VISIBLE : View.GONE);
+        setEnabled(itemData.isEnabled());
+    }
+
+    @Override
+    public boolean performClick() {
+        // Let the view's click listener have top priority (the More button relies on this)
+        if (super.performClick()) {
+            return true;
+        }
+        
+        if ((mItemInvoker != null) && (mItemInvoker.invokeItem(mItemData))) {
+            playSoundEffect(SoundEffectConstants.CLICK);
+            return true;
+        } else {
+            return false;
+        }
+    }
+    
+    public void setTitle(CharSequence title) {
+        
+        if (mShortcutCaptionMode) {
+            /*
+             * Don't set the title directly since it will replace the
+             * shortcut+title being shown. Instead, re-set the shortcut caption
+             * mode so the new title is shown.
+             */
+            setCaptionMode(true);
+            
+        } else if (title != null) {
+            setText(title);
+        }
+    }
+    
+    void setCaptionMode(boolean shortcut) {
+        /*
+         * If there is no item model, don't do any of the below (for example,
+         * the 'More' item doesn't have a model)
+         */
+        if (mItemData == null) {
+            return;
+        }
+        
+        mShortcutCaptionMode = shortcut && (mItemData.shouldShowShortcut());
+        
+        CharSequence text = mItemData.getTitleForItemView(this);
+        
+        if (mShortcutCaptionMode) {
+            
+            if (mShortcutCaption == null) {
+                mShortcutCaption = mItemData.getShortcutLabel();
+            }
+
+            text = mShortcutCaption;
+        }
+        
+        setText(text);
+    }
+    
+    public void setIcon(Drawable icon) {
+        mIcon = icon;
+        
+        if (icon != null) {
+            
+            /* Set the bounds of the icon since setCompoundDrawables needs it. */
+            icon.setBounds(0, 0, icon.getIntrinsicWidth(), icon.getIntrinsicHeight());
+            
+            // Set the compound drawables
+            setCompoundDrawables(null, icon, null, null);
+            
+            // When there is an icon, make sure the text is at the bottom
+            setGravity(Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL);
+
+            /*
+             * Request a layout to reposition the icon. The positioning of icon
+             * depends on this TextView's line bounds, which is only available
+             * after a layout.
+             */  
+            requestLayout();
+        } else {
+            setCompoundDrawables(null, null, null, null);
+            
+            // When there is no icon, make sure the text is centered vertically
+            setGravity(Gravity.CENTER_VERTICAL | Gravity.CENTER_HORIZONTAL);
+        }
+    }
+
+    public void setItemInvoker(ItemInvoker itemInvoker) {
+        mItemInvoker = itemInvoker;
+    }
+    
+    @ViewDebug.CapturedViewProperty(retrieveReturn = true)
+    public MenuItemImpl getItemData() {
+        return mItemData;
+    }
+
+    @Override
+    public void setVisibility(int v) {
+        super.setVisibility(v);
+        
+        if (mIconMenuView != null) {
+            // On visibility change, mark the IconMenuView to refresh itself eventually
+            mIconMenuView.markStaleChildren();
+        }
+    }
+    
+    void setIconMenuView(IconMenuView iconMenuView) {
+        mIconMenuView = iconMenuView;
+    }
+
+    @Override
+    protected void drawableStateChanged() {
+        super.drawableStateChanged();
+
+        if (mItemData != null && mIcon != null) {
+            // When disabled, the not-focused state and the pressed state should
+            // drop alpha on the icon
+            final boolean isInAlphaState = !mItemData.isEnabled() && (isPressed() || !isFocused());
+            mIcon.setAlpha(isInAlphaState ? (int) (mDisabledAlpha * NO_ALPHA) : NO_ALPHA);
+        }
+    }
+
+    @Override
+    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+        super.onLayout(changed, left, top, right, bottom);
+        
+        positionIcon();
+    }
+
+    /**
+     * Positions the icon vertically (horizontal centering is taken care of by
+     * the TextView's gravity).
+     */
+    private void positionIcon() {
+        
+        if (mIcon == null) {
+            return;
+        }
+        
+        // We reuse the output rectangle as a temp rect
+        Rect tmpRect = mPositionIconOutput;
+        getLineBounds(0, tmpRect);
+        mPositionIconAvailable.set(0, 0, getWidth(), tmpRect.top);
+        Gravity.apply(Gravity.CENTER_VERTICAL | Gravity.LEFT, mIcon.getIntrinsicWidth(), mIcon
+                .getIntrinsicHeight(), mPositionIconAvailable, mPositionIconOutput);
+        mIcon.setBounds(mPositionIconOutput);
+    }
+
+    public void setCheckable(boolean checkable) {
+    }
+
+    public void setChecked(boolean checked) {
+    }
+
+    public void setShortcut(boolean showShortcut, char shortcutKey) {
+        
+        if (mShortcutCaptionMode) {
+            /*
+             * Shortcut has changed and we're showing it right now, need to
+             * update (clear the old one first).
+             */
+            mShortcutCaption = null;
+            setCaptionMode(true);
+        }
+    }
+
+    public boolean prefersCondensedTitle() {
+        return true;
+    }
+
+    public boolean showsIcon() {
+        return true;
+    }
+    
+}
diff --git a/core/java/com/android/internal/view/menu/IconMenuView.java b/core/java/com/android/internal/view/menu/IconMenuView.java
new file mode 100644
index 0000000..781c608
--- /dev/null
+++ b/core/java/com/android/internal/view/menu/IconMenuView.java
@@ -0,0 +1,822 @@
+/*
+ * 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.internal.view.menu;
+
+import com.android.internal.view.menu.MenuBuilder.ItemInvoker;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.graphics.Canvas;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.Layout;
+import android.util.AttributeSet;
+import android.view.KeyEvent;
+import android.view.View;
+import android.view.ViewConfiguration;
+import android.view.ViewGroup;
+import android.view.LayoutInflater;
+
+import java.util.ArrayList;
+
+/**
+ * The icon menu view is an icon-based menu usually with a subset of all the menu items.
+ * It is opened as the default menu, and shows either the first five or all six of the menu items
+ * with text and icon.  In the situation of there being more than six items, the first five items
+ * will be accompanied with a 'More' button that opens an {@link ExpandedMenuView} which lists
+ * all the menu items. 
+ * 
+ * @attr ref android.R.styleable#IconMenuView_rowHeight
+ * @attr ref android.R.styleable#IconMenuView_maxRows
+ * @attr ref android.R.styleable#IconMenuView_maxItemsPerRow
+ * 
+ * @hide
+ */
+public final class IconMenuView extends ViewGroup implements ItemInvoker, MenuView, Runnable {
+    private static final int ITEM_CAPTION_CYCLE_DELAY = 1000;
+
+    private MenuBuilder mMenu;
+    
+    /** Height of each row */
+    private int mRowHeight;
+    /** Maximum number of rows to be shown */ 
+    private int mMaxRows;
+    /** Maximum number of items to show in the icon menu. */
+    private int mMaxItems;
+    /** Maximum number of items per row */
+    private int mMaxItemsPerRow;
+    /** Actual number of items (the 'More' view does not count as an item) shown */
+    private int mNumActualItemsShown;
+    
+    /** Divider that is drawn between all rows */
+    private Drawable mHorizontalDivider;
+    /** Height of the horizontal divider */
+    private int mHorizontalDividerHeight;
+    /** Set of horizontal divider positions where the horizontal divider will be drawn */
+    private ArrayList<Rect> mHorizontalDividerRects;
+
+    /** Divider that is drawn between all columns */
+    private Drawable mVerticalDivider;
+    /** Width of the vertical divider */
+    private int mVerticalDividerWidth;
+    /** Set of vertical divider positions where the vertical divider will be drawn */
+    private ArrayList<Rect> mVerticalDividerRects;
+    
+    /** Icon for the 'More' button */
+    private Drawable mMoreIcon;
+    
+    /** Item view for the 'More' button */
+    private IconMenuItemView mMoreItemView;
+    
+    /** Background of each item (should contain the selected and focused states) */
+    private Drawable mItemBackground;
+
+    /** Default animations for this menu */
+    private int mAnimations;
+    
+    /**
+     * Whether this IconMenuView has stale children and needs to update them.
+     * Set true by {@link #markStaleChildren()} and reset to false by
+     * {@link #onMeasure(int, int)}
+     */
+    private boolean mHasStaleChildren;
+
+    /**
+     * Longpress on MENU (while this is shown) switches to shortcut caption
+     * mode. When the user releases the longpress, we do not want to pass the
+     * key-up event up since that will dismiss the menu.
+     */
+    private boolean mMenuBeingLongpressed = false;
+
+    /**
+     * While {@link #mMenuBeingLongpressed}, we toggle the children's caption
+     * mode between each's title and its shortcut. This is the last caption mode
+     * we broadcasted to children.
+     */
+    private boolean mLastChildrenCaptionMode;
+
+    /**
+     * The layout to use for menu items. Each index is the row number (0 is the
+     * top-most). Each value contains the number of items in that row.
+     * <p>
+     * The length of this array should not be used to get the number of rows in
+     * the current layout, instead use {@link #mLayoutNumRows}.
+     */
+    private int[] mLayout;
+
+    /**
+     * The number of rows in the current layout. 
+     */
+    private int mLayoutNumRows;
+    
+    /**
+     * Instantiates the IconMenuView that is linked with the provided MenuBuilder.
+     */
+    public IconMenuView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+
+        TypedArray a = 
+            context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.IconMenuView, 0, 0);
+        mRowHeight = a.getDimensionPixelSize(com.android.internal.R.styleable.IconMenuView_rowHeight, 64);
+        mMaxRows = a.getInt(com.android.internal.R.styleable.IconMenuView_maxRows, 2);
+        mMaxItems = a.getInt(com.android.internal.R.styleable.IconMenuView_maxItems, 6);
+        mMaxItemsPerRow = a.getInt(com.android.internal.R.styleable.IconMenuView_maxItemsPerRow, 3);
+        mMoreIcon = a.getDrawable(com.android.internal.R.styleable.IconMenuView_moreIcon);
+        a.recycle();
+        
+        a = context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.MenuView, 0, 0);
+        mItemBackground = a.getDrawable(com.android.internal.R.styleable.MenuView_itemBackground);
+        mHorizontalDivider = a.getDrawable(com.android.internal.R.styleable.MenuView_horizontalDivider);
+        mHorizontalDividerRects = new ArrayList<Rect>();
+        mVerticalDivider =  a.getDrawable(com.android.internal.R.styleable.MenuView_verticalDivider);
+        mVerticalDividerRects = new ArrayList<Rect>();
+        mAnimations = a.getResourceId(com.android.internal.R.styleable.MenuView_windowAnimationStyle, 0);
+        a.recycle();
+        
+        if (mHorizontalDivider != null) {
+            mHorizontalDividerHeight = mHorizontalDivider.getIntrinsicHeight();
+            // Make sure to have some height for the divider
+            if (mHorizontalDividerHeight == -1) mHorizontalDividerHeight = 1;
+        }
+        
+        if (mVerticalDivider != null) {
+            mVerticalDividerWidth = mVerticalDivider.getIntrinsicWidth();
+            // Make sure to have some width for the divider
+            if (mVerticalDividerWidth == -1) mVerticalDividerWidth = 1;
+        }
+        
+        mLayout = new int[mMaxRows];
+        
+        // This view will be drawing the dividers        
+        setWillNotDraw(false);
+        
+        // This is so we'll receive the MENU key in touch mode
+        setFocusableInTouchMode(true);
+        // This is so our children can still be arrow-key focused
+        setDescendantFocusability(FOCUS_AFTER_DESCENDANTS);
+    }
+
+    /**
+     * Figures out the layout for the menu items.
+     * 
+     * @param width The available width for the icon menu.
+     */
+    private void layoutItems(int width) {
+        int numItems = getChildCount();
+        
+        // Start with the least possible number of rows
+        int curNumRows =
+                Math.min((int) Math.ceil(numItems / (float) mMaxItemsPerRow), mMaxRows);
+        
+        /*
+         * Increase the number of rows until we find a configuration that fits
+         * all of the items' titles. Worst case, we use mMaxRows.
+         */
+        for (; curNumRows <= mMaxRows; curNumRows++) {
+            layoutItemsUsingGravity(curNumRows, numItems);
+            
+            if (curNumRows >= numItems) {
+                // Can't have more rows than items
+                break;
+            }
+            
+            if (doItemsFit()) {
+                // All the items fit, so this is a good configuration
+                break;
+            }
+        }
+    }
+
+    /**
+     * Figures out the layout for the menu items by equally distributing, and
+     * adding any excess items equally to lower rows.
+     * 
+     * @param numRows The total number of rows for the menu view
+     * @param numItems The total number of items (across all rows) contained in
+     *            the menu view
+     * @return int[] Where the value of index i contains the number of items for row i
+     */
+    private void layoutItemsUsingGravity(int numRows, int numItems) {
+        int numBaseItemsPerRow = numItems / numRows;
+        int numLeftoverItems = numItems % numRows;
+        /**
+         * The bottom rows will each get a leftover item. Rows (indexed at 0)
+         * that are >= this get a leftover item. Note: if there are 0 leftover
+         * items, no rows will get them since this value will be greater than
+         * the last row.
+         */
+        int rowsThatGetALeftoverItem = numRows - numLeftoverItems;
+        
+        int[] layout = mLayout;
+        for (int i = 0; i < numRows; i++) {
+            layout[i] = numBaseItemsPerRow;
+
+            // Fill the bottom rows with a leftover item each
+            if (i >= rowsThatGetALeftoverItem) {
+                layout[i]++;
+            }
+        }
+        
+        mLayoutNumRows = numRows;
+    }
+
+    /**
+     * Checks whether each item's title is fully visible using the current
+     * layout.
+     * 
+     * @return True if the items fit (each item's text is fully visible), false
+     *         otherwise.
+     */
+    private boolean doItemsFit() {
+        int itemPos = 0;
+        
+        int[] layout = mLayout;
+        int numRows = mLayoutNumRows;
+        for (int row = 0; row < numRows; row++) {
+            int numItemsOnRow = layout[row];
+
+            /*
+             * If there is only one item on this row, increasing the
+             * number of rows won't help.
+             */ 
+            if (numItemsOnRow == 1) {
+                itemPos++;
+                continue;
+            }
+            
+            for (int itemsOnRowCounter = numItemsOnRow; itemsOnRowCounter > 0;
+                    itemsOnRowCounter--) {
+                View child = getChildAt(itemPos++);
+                LayoutParams lp = (LayoutParams) child.getLayoutParams();
+                if (lp.maxNumItemsOnRow < numItemsOnRow) {
+                    return false;
+                }
+            }
+        }
+        
+        return true;
+    }
+
+    /**
+     * Adds an IconMenuItemView to this icon menu view.
+     * @param itemView The item's view to add
+     */
+    private void addItemView(IconMenuItemView itemView) {
+        LayoutParams lp = (LayoutParams) itemView.getLayoutParams();
+        
+        if (lp == null) {
+            // Default layout parameters
+            lp = new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT);
+        }
+        
+        // Set ourselves on the item view
+        itemView.setIconMenuView(this);
+        
+        // Apply the background to the item view
+        itemView.setBackgroundDrawable(mItemBackground.getConstantState().newDrawable());
+
+        // This class is the invoker for all its item views 
+        itemView.setItemInvoker(this);
+        
+        // Set the desired width of item
+        lp.desiredWidth = (int) Layout.getDesiredWidth(itemView.getText(), itemView.getPaint());
+        
+        addView(itemView, lp);
+    }
+
+    /**
+     * Creates the item view for the 'More' button which is used to switch to
+     * the expanded menu view. This button is a special case since it does not
+     * have a MenuItemData backing it.
+     * @return The IconMenuItemView for the 'More' button
+     */
+    private IconMenuItemView createMoreItemView() {
+        LayoutInflater inflater = mMenu.getMenuType(MenuBuilder.TYPE_ICON).getInflater();
+        
+        final IconMenuItemView itemView = (IconMenuItemView) inflater.inflate(
+                com.android.internal.R.layout.icon_menu_item_layout, null);
+        
+        Resources r = getContext().getResources();
+        itemView.initialize(r.getText(com.android.internal.R.string.more_item_label), mMoreIcon);
+        
+        // Set up a click listener on the view since there will be no invocation sequence
+        // due to the lack of a MenuItemData this view
+        itemView.setOnClickListener(new OnClickListener() {
+            public void onClick(View v) {
+                // Switches the menu to expanded mode
+                MenuBuilder.Callback cb = mMenu.getCallback();
+                if (cb != null) {
+                    // Call callback
+                    cb.onMenuModeChange(mMenu);
+                }
+            }
+        });
+        
+        return itemView;
+    }
+    
+    
+    public void initialize(MenuBuilder menu, int menuType) {
+        mMenu = menu;
+        updateChildren(true);
+    }
+
+    public void updateChildren(boolean cleared) {
+        // This method does a clear refresh of children
+        removeAllViews();
+        
+        final ArrayList<MenuItemImpl> itemsToShow = mMenu.getVisibleItems();
+        final int numItems = itemsToShow.size();
+        final int numItemsThatCanFit = mMaxItems;
+        // Minimum of the num that can fit and the num that we have
+        final int minFitMinus1AndNumItems = Math.min(numItemsThatCanFit - 1, numItems);
+        
+        MenuItemImpl itemData;
+        // Traverse through all but the last item that can fit since that last item can either
+        // be a 'More' button or a sixth item
+        for (int i = 0; i < minFitMinus1AndNumItems; i++) {
+            itemData = itemsToShow.get(i);
+            addItemView((IconMenuItemView) itemData.getItemView(MenuBuilder.TYPE_ICON, this));
+        }
+
+        if (numItems > numItemsThatCanFit) {
+            // If there are more items than we can fit, show the 'More' button to
+            // switch to expanded mode
+            if (mMoreItemView == null) {
+                mMoreItemView = createMoreItemView();
+            }
+            
+            addItemView(mMoreItemView);
+            
+            // The last view is the more button, so the actual number of items is one less than
+            // the number that can fit
+            mNumActualItemsShown = numItemsThatCanFit - 1;
+        } else if (numItems == numItemsThatCanFit) {
+            // There are exactly the number we can show, so show the last item 
+            final MenuItemImpl lastItemData = itemsToShow.get(numItemsThatCanFit - 1);
+            addItemView((IconMenuItemView) lastItemData.getItemView(MenuBuilder.TYPE_ICON, this));
+            
+            // The items shown fit exactly
+            mNumActualItemsShown = numItemsThatCanFit;
+        }
+    }
+
+    /**
+     * The positioning algorithm that gets called from onMeasure.  It
+     * just computes positions for each child, and then stores them in the child's layout params.
+     * @param menuWidth The width of this menu to assume for positioning
+     * @param menuHeight The height of this menu to assume for positioning
+     */
+    private void positionChildren(int menuWidth, int menuHeight) {
+        // Clear the containers for the positions where the dividers should be drawn
+        if (mHorizontalDivider != null) mHorizontalDividerRects.clear();
+        if (mVerticalDivider != null) mVerticalDividerRects.clear();
+
+        // Get the minimum number of rows needed
+        final int numRows = mLayoutNumRows;
+        final int numRowsMinus1 = numRows - 1;
+        final int numItemsForRow[] = mLayout;
+        
+        // The item position across all rows
+        int itemPos = 0;
+        View child;
+        IconMenuView.LayoutParams childLayoutParams = null; 
+
+        // Use float for this to get precise positions (uniform item widths
+        // instead of last one taking any slack), and then convert to ints at last opportunity
+        float itemLeft;
+        float itemTop = 0;
+        // Since each row can have a different number of items, this will be computed per row
+        float itemWidth;
+        // Subtract the space needed for the horizontal dividers
+        final float itemHeight = (menuHeight - mHorizontalDividerHeight * (numRows - 1))
+                / (float)numRows;
+        
+        for (int row = 0; row < numRows; row++) {
+            // Start at the left
+            itemLeft = 0;
+            
+            // Subtract the space needed for the vertical dividers, and divide by the number of items
+            itemWidth = (menuWidth - mVerticalDividerWidth * (numItemsForRow[row] - 1))
+                    / (float)numItemsForRow[row];
+            
+            for (int itemPosOnRow = 0; itemPosOnRow < numItemsForRow[row]; itemPosOnRow++) {
+                // Tell the child to be exactly this size
+                child = getChildAt(itemPos);
+                child.measure(MeasureSpec.makeMeasureSpec((int) itemWidth, MeasureSpec.EXACTLY),
+                        MeasureSpec.makeMeasureSpec((int) itemHeight, MeasureSpec.EXACTLY));
+                
+                // Remember the child's position for layout
+                childLayoutParams = (IconMenuView.LayoutParams) child.getLayoutParams();
+                childLayoutParams.left = (int) itemLeft;
+                childLayoutParams.right = (int) (itemLeft + itemWidth);
+                childLayoutParams.top = (int) itemTop;
+                childLayoutParams.bottom = (int) (itemTop + itemHeight); 
+                
+                // Increment by item width
+                itemLeft += itemWidth;
+                itemPos++;
+
+                // Add a vertical divider to draw
+                if (mVerticalDivider != null) {
+                    mVerticalDividerRects.add(new Rect((int) itemLeft,
+                            (int) itemTop, (int) (itemLeft + mVerticalDividerWidth),
+                            (int) (itemTop + itemHeight)));
+                }
+
+                // Increment by divider width (even if we're not computing
+                // dividers, since we need to leave room for them when
+                // calculating item positions)
+                itemLeft += mVerticalDividerWidth;
+            }
+            
+            // Last child on each row should extend to very right edge
+            if (childLayoutParams != null) {
+                childLayoutParams.right = menuWidth;
+            }
+            
+            itemTop += itemHeight;
+
+            // Add a horizontal divider to draw
+            if ((mHorizontalDivider != null) && (row < numRowsMinus1)) {
+                mHorizontalDividerRects.add(new Rect(0, (int) itemTop, menuWidth,
+                        (int) (itemTop + mHorizontalDividerHeight)));
+
+                itemTop += mHorizontalDividerHeight;
+            }
+        }
+    }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        if (mHasStaleChildren) {
+            mHasStaleChildren = false;
+
+            // If we have stale data, resync with the menu
+            updateChildren(false);
+        }
+        
+        int measuredWidth = resolveSize(Integer.MAX_VALUE, widthMeasureSpec);
+        calculateItemFittingMetadata(measuredWidth);
+        layoutItems(measuredWidth);
+        
+        // Get the desired height of the icon menu view (last row of items does
+        // not have a divider below)
+        final int desiredHeight = (mRowHeight + mHorizontalDividerHeight) *
+                mLayoutNumRows - mHorizontalDividerHeight;
+        
+        // Maximum possible width and desired height
+        setMeasuredDimension(measuredWidth,
+                resolveSize(desiredHeight, heightMeasureSpec));
+
+        // Position the children
+        positionChildren(mMeasuredWidth, mMeasuredHeight);
+    }
+
+
+    @Override
+    protected void onLayout(boolean changed, int l, int t, int r, int b) {
+        View child;
+        IconMenuView.LayoutParams childLayoutParams;
+        
+        for (int i = getChildCount() - 1; i >= 0; i--) {
+            child = getChildAt(i);
+            childLayoutParams = (IconMenuView.LayoutParams)child
+                    .getLayoutParams();
+
+            // Layout children according to positions set during the measure
+            child.layout(childLayoutParams.left, childLayoutParams.top, childLayoutParams.right,
+                    childLayoutParams.bottom);
+        }
+    }
+
+    @Override
+    protected void onDraw(Canvas canvas) {
+        if (mHorizontalDivider != null) {
+            // If we have a horizontal divider to draw, draw it at the remembered positions
+            for (int i = mHorizontalDividerRects.size() - 1; i >= 0; i--) {
+                mHorizontalDivider.setBounds(mHorizontalDividerRects.get(i));
+                mHorizontalDivider.draw(canvas);
+            }
+        }
+        
+        if (mVerticalDivider != null) {
+            // If we have a vertical divider to draw, draw it at the remembered positions
+            for (int i = mVerticalDividerRects.size() - 1; i >= 0; i--) {
+                mVerticalDivider.setBounds(mVerticalDividerRects.get(i));
+                mVerticalDivider.draw(canvas);
+            }
+        }
+    }
+
+    public boolean invokeItem(MenuItemImpl item) {
+        return mMenu.performItemAction(item, 0);
+    }
+
+    @Override
+    public LayoutParams generateLayoutParams(AttributeSet attrs)
+    {
+        return new IconMenuView.LayoutParams(getContext(), attrs);
+    }
+
+    @Override
+    protected boolean checkLayoutParams(ViewGroup.LayoutParams p)
+    {
+        // Override to allow type-checking of LayoutParams. 
+        return p instanceof IconMenuView.LayoutParams;
+    }
+
+    /**
+     * Marks as having stale children.
+     */
+    void markStaleChildren() {
+        if (!mHasStaleChildren) {
+            mHasStaleChildren = true;
+            requestLayout();
+        }
+    }
+    
+    /**
+     * @return The number of actual items shown (those that are backed by an
+     *         {@link MenuView.ItemView} implementation--eg: excludes More
+     *         item).
+     */
+    int getNumActualItemsShown() {
+        return mNumActualItemsShown;
+    }
+    
+    
+    public int getWindowAnimations() {
+        return mAnimations;
+    }
+
+    /**
+     * Returns the number of items per row.
+     * <p>
+     * This should only be used for testing.
+     * 
+     * @return The length of the array is the number of rows. A value at a
+     *         position is the number of items in that row.
+     * @hide
+     */
+    public int[] getLayout() {
+        return mLayout;
+    }
+    
+    /**
+     * Returns the number of rows in the layout.
+     * <p>
+     * This should only be used for testing.
+     * 
+     * @return The length of the array is the number of rows. A value at a
+     *         position is the number of items in that row.
+     * @hide
+     */
+    public int getLayoutNumRows() {
+        return mLayoutNumRows;
+    }
+    
+    @Override
+    public boolean dispatchKeyEvent(KeyEvent event) {
+
+        if (event.getKeyCode() == KeyEvent.KEYCODE_MENU) {
+            if (event.getAction() == KeyEvent.ACTION_DOWN && event.getRepeatCount() == 0) {
+                removeCallbacks(this);
+                postDelayed(this, ViewConfiguration.getLongPressTimeout());
+            } else if (event.getAction() == KeyEvent.ACTION_UP) {
+                
+                if (mMenuBeingLongpressed) {
+                    // It was in cycle mode, so reset it (will also remove us
+                    // from being called back)
+                    setCycleShortcutCaptionMode(false);
+                    return true;
+                    
+                } else {
+                    // Just remove us from being called back
+                    removeCallbacks(this);
+                    // Fall through to normal processing too
+                }
+            }
+        }
+        
+        return super.dispatchKeyEvent(event);
+    }
+
+    @Override
+    protected void onAttachedToWindow() {
+        super.onAttachedToWindow();
+        
+        requestFocus();
+    }
+
+    @Override
+    protected void onDetachedFromWindow() {
+        setCycleShortcutCaptionMode(false);
+        super.onDetachedFromWindow();
+    }
+
+    @Override
+    public void onWindowFocusChanged(boolean hasWindowFocus) {
+
+        if (!hasWindowFocus) {
+            setCycleShortcutCaptionMode(false);
+        }
+
+        super.onWindowFocusChanged(hasWindowFocus);
+    }
+
+    /**
+     * Sets the shortcut caption mode for IconMenuView. This mode will
+     * continuously cycle between a child's shortcut and its title.
+     * 
+     * @param cycleShortcutAndNormal Whether to go into cycling shortcut mode,
+     *        or to go back to normal.
+     */
+    private void setCycleShortcutCaptionMode(boolean cycleShortcutAndNormal) {
+
+        if (!cycleShortcutAndNormal) {
+            /*
+             * We're setting back to title, so remove any callbacks for setting
+             * to shortcut
+             */
+            removeCallbacks(this);
+            setChildrenCaptionMode(false);
+            mMenuBeingLongpressed = false;
+            
+        } else {
+            
+            // Set it the first time (the cycle will be started in run()).
+            setChildrenCaptionMode(true);
+        }
+        
+    }
+
+    /**
+     * When this method is invoked if the menu is currently not being
+     * longpressed, it means that the longpress has just been reached (so we set
+     * longpress flag, and start cycling). If it is being longpressed, we cycle
+     * to the next mode.
+     */
+    public void run() {
+        
+        if (mMenuBeingLongpressed) {
+
+            // Cycle to other caption mode on the children
+            setChildrenCaptionMode(!mLastChildrenCaptionMode);
+
+        } else {
+            
+            // Switch ourselves to continuously cycle the items captions
+            mMenuBeingLongpressed = true;
+            setCycleShortcutCaptionMode(true);
+        }
+
+        // We should run again soon to cycle to the other caption mode
+        postDelayed(this, ITEM_CAPTION_CYCLE_DELAY);
+    }
+
+    /**
+     * Iterates children and sets the desired shortcut mode. Only
+     * {@link #setCycleShortcutCaptionMode(boolean)} and {@link #run()} should call
+     * this.
+     * 
+     * @param shortcut Whether to show shortcut or the title.
+     */
+    private void setChildrenCaptionMode(boolean shortcut) {
+        
+        // Set the last caption mode pushed to children
+        mLastChildrenCaptionMode = shortcut;
+        
+        for (int i = getChildCount() - 1; i >= 0; i--) {
+            ((IconMenuItemView) getChildAt(i)).setCaptionMode(shortcut);
+        }
+    }
+
+    /**
+     * For each item, calculates the most dense row that fully shows the item's
+     * title.
+     * 
+     * @param width The available width of the icon menu.
+     */
+    private void calculateItemFittingMetadata(int width) {
+        int maxNumItemsPerRow = mMaxItemsPerRow;
+        int numItems = getChildCount();
+        for (int i = 0; i < numItems; i++) {
+            LayoutParams lp = (LayoutParams) getChildAt(i).getLayoutParams();
+            // Start with 1, since that case does not get covered in the loop below
+            lp.maxNumItemsOnRow = 1;
+            for (int curNumItemsPerRow = maxNumItemsPerRow; curNumItemsPerRow > 0;
+                    curNumItemsPerRow--) {
+                // Check whether this item can fit into a row containing curNumItemsPerRow
+                if (lp.desiredWidth < width / curNumItemsPerRow) {
+                    // It can, mark this value as the most dense row it can fit into
+                    lp.maxNumItemsOnRow = curNumItemsPerRow;
+                    break;
+                }
+            }
+        }
+    }
+    
+    @Override
+    protected Parcelable onSaveInstanceState() {
+        Parcelable superState = super.onSaveInstanceState();
+        
+        View focusedView = getFocusedChild();
+        
+        for (int i = getChildCount() - 1; i >= 0; i--) {
+            if (getChildAt(i) == focusedView) {
+                return new SavedState(superState, i);
+            }
+        }
+        
+        return new SavedState(superState, -1);
+    }
+
+    @Override
+    protected void onRestoreInstanceState(Parcelable state) {
+        SavedState ss = (SavedState) state;
+        super.onRestoreInstanceState(ss.getSuperState());
+
+        if (ss.focusedPosition >= getChildCount()) {
+            return;
+        }
+        
+        View v = getChildAt(ss.focusedPosition);
+        if (v != null) {
+            v.requestFocus();
+        }
+    }
+
+    private static class SavedState extends BaseSavedState {
+        int focusedPosition;
+
+        /**
+         * Constructor called from {@link IconMenuView#onSaveInstanceState()}
+         */
+        public SavedState(Parcelable superState, int focusedPosition) {
+            super(superState);
+            this.focusedPosition = focusedPosition;
+        }
+        
+        /**
+         * Constructor called from {@link #CREATOR}
+         */
+        private SavedState(Parcel in) {
+            super(in);
+            focusedPosition = in.readInt();
+        }
+
+        @Override
+        public void writeToParcel(Parcel dest, int flags) {
+            super.writeToParcel(dest, flags);
+            dest.writeInt(focusedPosition);
+        }
+        
+        public static final Parcelable.Creator<SavedState> CREATOR = new Parcelable.Creator<SavedState>() {
+            public SavedState createFromParcel(Parcel in) {
+                return new SavedState(in);
+            }
+
+            public SavedState[] newArray(int size) {
+                return new SavedState[size];
+            }
+        };
+        
+    }
+    
+    /**
+     * Layout parameters specific to IconMenuView (stores the left, top, right, bottom from the
+     * measure pass). 
+     */
+    public static class LayoutParams extends ViewGroup.MarginLayoutParams
+    {
+        int left, top, right, bottom;
+        int desiredWidth;
+        int maxNumItemsOnRow;
+        
+        public LayoutParams(Context c, AttributeSet attrs) {
+            super(c, attrs);
+        }
+
+        public LayoutParams(int width, int height) {
+            super(width, height);
+        }
+    }
+}
diff --git a/core/java/com/android/internal/view/menu/ListMenuItemView.java b/core/java/com/android/internal/view/menu/ListMenuItemView.java
new file mode 100644
index 0000000..e155875
--- /dev/null
+++ b/core/java/com/android/internal/view/menu/ListMenuItemView.java
@@ -0,0 +1,242 @@
+/*
+ * 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.internal.view.menu;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.drawable.Drawable;
+import android.util.AttributeSet;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.CheckBox;
+import android.widget.CompoundButton;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.RadioButton;
+import android.widget.TextView;
+
+/**
+ * The item view for each item in the ListView-based MenuViews.
+ */
+public class ListMenuItemView extends LinearLayout implements MenuView.ItemView {
+    private MenuItemImpl mItemData; 
+    
+    private ImageView mIconView;
+    private RadioButton mRadioButton;
+    private TextView mTitleView;
+    private CheckBox mCheckBox;
+    private TextView mShortcutView;
+    
+    private Drawable mBackground;
+    private int mTextAppearance;
+    private Context mTextAppearanceContext;
+    
+    private int mMenuType;
+    
+    public ListMenuItemView(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs);
+    
+        TypedArray a =
+            context.obtainStyledAttributes(
+                attrs, com.android.internal.R.styleable.MenuView, defStyle, 0);
+        
+        mBackground = a.getDrawable(com.android.internal.R.styleable.MenuView_itemBackground);
+        mTextAppearance = a.getResourceId(com.android.internal.R.styleable.
+                                          MenuView_itemTextAppearance, -1);
+        mTextAppearanceContext = context;
+        
+        a.recycle();
+    }
+
+    public ListMenuItemView(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    @Override
+    protected void onFinishInflate() {
+        super.onFinishInflate();
+        
+        setBackgroundDrawable(mBackground);
+        
+        mTitleView = (TextView) findViewById(com.android.internal.R.id.title);
+        if (mTextAppearance != -1) {
+            mTitleView.setTextAppearance(mTextAppearanceContext,
+                                         mTextAppearance);
+        }
+        
+        mShortcutView = (TextView) findViewById(com.android.internal.R.id.shortcut);
+    }
+
+    public void initialize(MenuItemImpl itemData, int menuType) {
+        mItemData = itemData;
+        mMenuType = menuType;
+
+        setVisibility(itemData.isVisible() ? View.VISIBLE : View.GONE);
+        
+        setTitle(itemData.getTitleForItemView(this));
+        setCheckable(itemData.isCheckable());
+        setShortcut(itemData.shouldShowShortcut(), itemData.getShortcut());
+        setIcon(itemData.getIcon());
+        setEnabled(itemData.isEnabled());
+    }
+
+    public void setTitle(CharSequence title) {
+        if (title != null) {
+            mTitleView.setText(title);
+            
+            if (mTitleView.getVisibility() != VISIBLE) mTitleView.setVisibility(VISIBLE);
+        } else {
+            if (mTitleView.getVisibility() != GONE) mTitleView.setVisibility(GONE);
+        }
+    }
+    
+    public MenuItemImpl getItemData() {
+        return mItemData;
+    }
+
+    public void setCheckable(boolean checkable) {
+        
+        if (!checkable && mRadioButton == null && mCheckBox == null) {
+            return;
+        }
+        
+        if (mRadioButton == null) {
+            insertRadioButton();
+        }
+        if (mCheckBox == null) {
+            insertCheckBox();
+        }
+        
+        // Depending on whether its exclusive check or not, the checkbox or
+        // radio button will be the one in use (and the other will be otherCompoundButton)
+        final CompoundButton compoundButton;
+        final CompoundButton otherCompoundButton; 
+
+        if (mItemData.isExclusiveCheckable()) {
+            compoundButton = mRadioButton;
+            otherCompoundButton = mCheckBox;
+        } else {
+            compoundButton = mCheckBox;
+            otherCompoundButton = mRadioButton;
+        }
+        
+        if (checkable) {
+            compoundButton.setChecked(mItemData.isChecked());
+            
+            final int newVisibility = checkable ? VISIBLE : GONE;
+            if (compoundButton.getVisibility() != newVisibility) {
+                compoundButton.setVisibility(newVisibility);
+            }
+            
+            // Make sure the other compound button isn't visible
+            if (otherCompoundButton.getVisibility() != GONE) {
+                otherCompoundButton.setVisibility(GONE);
+            }
+        } else {
+            mCheckBox.setVisibility(GONE);
+            mRadioButton.setVisibility(GONE);
+        }
+    }
+    
+    public void setChecked(boolean checked) {
+        CompoundButton compoundButton;
+        
+        if (mItemData.isExclusiveCheckable()) {
+            if (mRadioButton == null) {
+                insertRadioButton();
+            }
+            compoundButton = mRadioButton;
+        } else {
+            if (mCheckBox == null) {
+                insertCheckBox();
+            }
+            compoundButton = mCheckBox;
+        }
+        
+        compoundButton.setChecked(checked);
+    }
+
+    public void setShortcut(boolean showShortcut, char shortcutKey) {
+        final int newVisibility = (showShortcut && mItemData.shouldShowShortcut())
+                ? VISIBLE : GONE;
+
+        if (newVisibility == VISIBLE) {
+            mShortcutView.setText(mItemData.getShortcutLabel());
+        }
+
+        if (mShortcutView.getVisibility() != newVisibility) {
+            mShortcutView.setVisibility(newVisibility);
+        }
+    }
+    
+    public void setIcon(Drawable icon) {
+        
+        if (!mItemData.shouldShowIcon(mMenuType)) {
+            return;
+        }
+        
+        if (mIconView == null && icon == null) {
+            return;
+        }
+        
+        if (mIconView == null) {
+            insertIconView();
+        }
+        
+        if (icon != null) {
+            mIconView.setImageDrawable(icon);
+
+            if (mIconView.getVisibility() != VISIBLE) {
+                mIconView.setVisibility(VISIBLE);
+            }
+        } else {
+            mIconView.setVisibility(GONE);
+        }
+    }
+    
+    private void insertIconView() {
+        LayoutInflater inflater = mItemData.getLayoutInflater(mMenuType);
+        mIconView = (ImageView) inflater.inflate(com.android.internal.R.layout.list_menu_item_icon,
+                this, false);
+        addView(mIconView, 0);
+    }
+    
+    private void insertRadioButton() {
+        LayoutInflater inflater = mItemData.getLayoutInflater(mMenuType);
+        mRadioButton =
+                (RadioButton) inflater.inflate(com.android.internal.R.layout.list_menu_item_radio,
+                this, false);
+        addView(mRadioButton);
+    }
+    
+    private void insertCheckBox() {
+        LayoutInflater inflater = mItemData.getLayoutInflater(mMenuType);
+        mCheckBox =
+                (CheckBox) inflater.inflate(com.android.internal.R.layout.list_menu_item_checkbox,
+                this, false);
+        addView(mCheckBox);
+    }
+
+    public boolean prefersCondensedTitle() {
+        return false;
+    }
+
+    public boolean showsIcon() {
+        return false;
+    }
+    
+}
diff --git a/core/java/com/android/internal/view/menu/MenuBuilder.java b/core/java/com/android/internal/view/menu/MenuBuilder.java
new file mode 100644
index 0000000..cbc4e9f
--- /dev/null
+++ b/core/java/com/android/internal/view/menu/MenuBuilder.java
@@ -0,0 +1,1120 @@
+/*
+ * 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.internal.view.menu;
+
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.os.Parcelable;
+import android.util.SparseArray;
+import android.view.ContextThemeWrapper;
+import android.view.KeyCharacterMap;
+import android.view.KeyEvent;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.SubMenu;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.LayoutInflater;
+import android.view.ContextMenu.ContextMenuInfo;
+import android.widget.AdapterView;
+import android.widget.BaseAdapter;
+
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Implementation of the {@link android.view.Menu} interface for creating a
+ * standard menu UI.
+ */
+public class MenuBuilder implements Menu {
+    private static final String LOGTAG = "MenuBuilder";
+    
+    /** The number of different menu types */
+    public static final int NUM_TYPES = 3;
+    /** The menu type that represents the icon menu view */
+    public static final int TYPE_ICON = 0;
+    /** The menu type that represents the expanded menu view */
+    public static final int TYPE_EXPANDED = 1;
+    /**
+     * The menu type that represents a menu dialog. Examples are context and sub
+     * menus. This menu type will not have a corresponding MenuView, but it will
+     * have an ItemView.
+     */
+    public static final int TYPE_DIALOG = 2;
+
+    private static final String VIEWS_TAG = "android:views";
+    
+    // Order must be the same order as the TYPE_*
+    static final int THEME_RES_FOR_TYPE[] = new int[] {
+        com.android.internal.R.style.Theme_IconMenu,
+        com.android.internal.R.style.Theme_ExpandedMenu,
+        0,
+    };
+    
+    // Order must be the same order as the TYPE_*
+    static final int LAYOUT_RES_FOR_TYPE[] = new int[] {
+        com.android.internal.R.layout.icon_menu_layout,
+        com.android.internal.R.layout.expanded_menu_layout,
+        0,
+    };
+
+    // Order must be the same order as the TYPE_*
+    static final int ITEM_LAYOUT_RES_FOR_TYPE[] = new int[] {
+        com.android.internal.R.layout.icon_menu_item_layout,
+        com.android.internal.R.layout.list_menu_item_layout,
+        com.android.internal.R.layout.list_menu_item_layout,
+    };
+
+    private static final int[]  sCategoryToOrder = new int[] {
+        1, /* No category */
+        4, /* CONTAINER */
+        5, /* SYSTEM */
+        3, /* SECONDARY */
+        2, /* ALTERNATIVE */
+        0, /* SELECTED_ALTERNATIVE */
+    };
+
+    private final Context mContext;
+    private final Resources mResources;
+
+    /**
+     * Whether the shortcuts should be qwerty-accessible. Use isQwertyMode()
+     * instead of accessing this directly.
+     */
+    private boolean mQwertyMode;
+
+    /**
+     * Whether the shortcuts should be visible on menus. Use isShortcutsVisible()
+     * instead of accessing this directly.
+     */ 
+    private boolean mShortcutsVisible;
+    
+    /**
+     * Callback that will receive the various menu-related events generated by
+     * this class. Use getCallback to get a reference to the callback.
+     */
+    private Callback mCallback;
+    
+    /** Contains all of the items for this menu */
+    private ArrayList<MenuItemImpl> mItems;
+
+    /** Contains only the items that are currently visible.  This will be created/refreshed from
+     * {@link #getVisibleItems()} */
+    private ArrayList<MenuItemImpl> mVisibleItems;
+    /**
+     * Whether or not the items (or any one item's shown state) has changed since it was last
+     * fetched from {@link #getVisibleItems()}
+     */ 
+    private boolean mIsVisibleItemsStale;
+
+    /**
+     * Current use case is Context Menus: As Views populate the context menu, each one has
+     * extra information that should be passed along.  This is the current menu info that
+     * should be set on all items added to this menu.
+     */
+    private ContextMenuInfo mCurrentMenuInfo;
+    
+    /** Header title for menu types that have a header (context and submenus) */
+    CharSequence mHeaderTitle;
+    /** Header icon for menu types that have a header and support icons (context) */
+    Drawable mHeaderIcon;
+    /** Header custom view for menu types that have a header and support custom views (context) */
+    View mHeaderView;
+
+    /**
+     * Contains the state of the View hierarchy for all menu views when the menu
+     * was frozen.
+     */
+    private SparseArray<Parcelable> mFrozenViewStates;
+
+    /**
+     * Prevents onItemsChanged from doing its junk, useful for batching commands
+     * that may individually call onItemsChanged.
+     */
+    private boolean mPreventDispatchingItemsChanged = false;
+    
+    private boolean mOptionalIconsVisible = false;
+    
+    private MenuType[] mMenuTypes;
+    class MenuType {
+        private int mMenuType;
+        
+        /** The layout inflater that uses the menu type's theme */
+        private LayoutInflater mInflater;
+
+        /** The lazily loaded {@link MenuView} */
+        private WeakReference<MenuView> mMenuView;
+
+        MenuType(int menuType) {
+            mMenuType = menuType;
+        }
+        
+        LayoutInflater getInflater() {
+            // Create an inflater that uses the given theme for the Views it inflates
+            if (mInflater == null) {
+                Context wrappedContext = new ContextThemeWrapper(mContext,
+                        THEME_RES_FOR_TYPE[mMenuType]); 
+                mInflater = (LayoutInflater) wrappedContext
+                        .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+            }
+            
+            return mInflater;
+        }
+        
+        MenuView getMenuView(ViewGroup parent) {
+            if (LAYOUT_RES_FOR_TYPE[mMenuType] == 0) {
+                return null;
+            }
+
+            synchronized (this) {
+                MenuView menuView = mMenuView != null ? mMenuView.get() : null;
+                
+                if (menuView == null) {
+                    menuView = (MenuView) getInflater().inflate(
+                            LAYOUT_RES_FOR_TYPE[mMenuType], parent, false);
+                    menuView.initialize(MenuBuilder.this, mMenuType);
+
+                    // Cache the view
+                    mMenuView = new WeakReference<MenuView>(menuView);
+                    
+                    if (mFrozenViewStates != null) {
+                        View view = (View) menuView;
+                        view.restoreHierarchyState(mFrozenViewStates);
+
+                        // Clear this menu type's frozen state, since we just restored it
+                        mFrozenViewStates.remove(view.getId());
+                    }
+                }
+            
+                return menuView;
+            }
+        }
+        
+        boolean hasMenuView() {
+            return mMenuView != null && mMenuView.get() != null;
+        }
+    }
+    
+    /**
+     * Called by menu to notify of close and selection changes
+     */
+    public interface Callback {
+        /**
+         * Called when a menu item is selected.
+         * @param menu The menu that is the parent of the item
+         * @param item The menu item that is selected
+         * @return whether the menu item selection was handled
+         */
+        public boolean onMenuItemSelected(MenuBuilder menu, MenuItem item);
+        
+        /**
+         * Called when a menu is closed.
+         * @param menu The menu that was closed.
+         * @param allMenusAreClosing Whether the menus are completely closing (true),
+         *            or whether there is another menu opening shortly
+         *            (false). For example, if the menu is closing because a
+         *            sub menu is about to be shown, <var>allMenusAreClosing</var>
+         *            is false.
+         */
+        public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing);
+        
+        /**
+         * Called when a sub menu is selected.  This is a cue to open the given sub menu's decor.
+         * @param subMenu the sub menu that is being opened
+         * @return whether the sub menu selection was handled by the callback
+         */
+        public boolean onSubMenuSelected(SubMenuBuilder subMenu);
+
+        /**
+         * Called when a sub menu is closed
+         * @param menu the sub menu that was closed
+         */
+        public void onCloseSubMenu(SubMenuBuilder menu);
+        
+        /**
+         * Called when the mode of the menu changes (for example, from icon to expanded).
+         * 
+         * @param menu the menu that has changed modes
+         */
+        public void onMenuModeChange(MenuBuilder menu);
+    }
+
+    /**
+     * Called by menu items to execute their associated action
+     */
+    public interface ItemInvoker {
+        public boolean invokeItem(MenuItemImpl item);
+    }
+
+    public MenuBuilder(Context context) {
+        mMenuTypes = new MenuType[NUM_TYPES];
+        
+        mContext = context;
+        mResources = context.getResources();
+        
+        mItems = new ArrayList<MenuItemImpl>();
+        
+        mVisibleItems = new ArrayList<MenuItemImpl>();
+        mIsVisibleItemsStale = true;
+        
+        mShortcutsVisible =
+                (mResources.getConfiguration().keyboard != Configuration.KEYBOARD_NOKEYS);
+    }
+    
+    public void setCallback(Callback callback) {
+        mCallback = callback;
+    }
+
+    MenuType getMenuType(int menuType) {
+        if (mMenuTypes[menuType] == null) {
+            mMenuTypes[menuType] = new MenuType(menuType);
+        }
+        
+        return mMenuTypes[menuType];
+    }
+    
+    /**
+     * Gets a menu View that contains this menu's items.
+     * 
+     * @param menuType The type of menu to get a View for (must be one of
+     *            {@link #TYPE_ICON}, {@link #TYPE_EXPANDED},
+     *            {@link #TYPE_DIALOG}).
+     * @param parent The ViewGroup that provides a set of LayoutParams values
+     *            for this menu view
+     * @return A View for the menu of type <var>menuType</var>
+     */
+    public View getMenuView(int menuType, ViewGroup parent) {
+        // The expanded menu depends on the number if items shown in the icon menu (which
+        // is adjustable as setters/XML attributes on IconMenuView [imagine a larger LCD
+        // wanting to show more icons]). If, for example, the activity goes through
+        // an orientation change while the expanded menu is open, the icon menu's view
+        // won't have an instance anymore; so here we make sure we have an icon menu view (matching
+        // the same parent so the layout parameters from the XML are used). This
+        // will create the icon menu view and cache it (if it doesn't already exist). 
+        if (menuType == TYPE_EXPANDED
+                && (mMenuTypes[TYPE_ICON] == null || !mMenuTypes[TYPE_ICON].hasMenuView())) {
+            getMenuType(TYPE_ICON).getMenuView(parent);
+        }
+        
+        return (View) getMenuType(menuType).getMenuView(parent);
+    }
+    
+    private int getNumIconMenuItemsShown() {
+        ViewGroup parent = null;
+        
+        if (!mMenuTypes[TYPE_ICON].hasMenuView()) {
+            /*
+             * There isn't an icon menu view instantiated, so when we get it
+             * below, it will lazily instantiate it. We should pass a proper
+             * parent so it uses the layout_ attributes present in the XML
+             * layout file.
+             */
+            if (mMenuTypes[TYPE_EXPANDED].hasMenuView()) {
+                View expandedMenuView = (View) mMenuTypes[TYPE_EXPANDED].getMenuView(null);
+                parent = (ViewGroup) expandedMenuView.getParent();
+            }
+        }
+        
+        return ((IconMenuView) getMenuView(TYPE_ICON, parent)).getNumActualItemsShown(); 
+    }
+    
+    /**
+     * Clears the cached menu views. Call this if the menu views need to another
+     * layout (for example, if the screen size has changed).
+     */
+    public void clearMenuViews() {
+        for (int i = NUM_TYPES - 1; i >= 0; i--) {
+            if (mMenuTypes[i] != null) {
+                mMenuTypes[i].mMenuView = null;
+            }
+        }
+        
+        for (int i = mItems.size() - 1; i >= 0; i--) {
+            MenuItemImpl item = mItems.get(i);
+            if (item.hasSubMenu()) {
+                ((SubMenuBuilder) item.getSubMenu()).clearMenuViews();
+            }
+            item.clearItemViews();
+        }
+    }
+    
+    /**
+     * Adds an item to the menu.  The other add methods funnel to this.
+     */
+    private MenuItem addInternal(int group, int id, int categoryOrder, CharSequence title) {
+        final int ordering = getOrdering(categoryOrder);
+        
+        final MenuItemImpl item = new MenuItemImpl(this, group, id, categoryOrder, ordering, title);
+
+        if (mCurrentMenuInfo != null) {
+            // Pass along the current menu info
+            item.setMenuInfo(mCurrentMenuInfo);
+        }
+        
+        mItems.add(findInsertIndex(mItems, ordering), item);
+        onItemsChanged(false);
+        
+        return item;
+    }
+    
+    public MenuItem add(CharSequence title) {
+        return addInternal(0, 0, 0, title);
+    }
+
+    public MenuItem add(int titleRes) {
+        return addInternal(0, 0, 0, mResources.getString(titleRes));
+    }
+
+    public MenuItem add(int group, int id, int categoryOrder, CharSequence title) {
+        return addInternal(group, id, categoryOrder, title);
+    }
+
+    public MenuItem add(int group, int id, int categoryOrder, int title) {
+        return addInternal(group, id, categoryOrder, mResources.getString(title));
+    }
+
+    public SubMenu addSubMenu(CharSequence title) {
+        return addSubMenu(0, 0, 0, title);
+    }
+
+    public SubMenu addSubMenu(int titleRes) {
+        return addSubMenu(0, 0, 0, mResources.getString(titleRes));
+    }
+
+    public SubMenu addSubMenu(int group, int id, int categoryOrder, CharSequence title) {
+        final MenuItemImpl item = (MenuItemImpl) addInternal(group, id, categoryOrder, title);
+        final SubMenuBuilder subMenu = new SubMenuBuilder(mContext, this, item);
+        item.setSubMenu(subMenu);
+        
+        return subMenu;
+    }
+
+    public SubMenu addSubMenu(int group, int id, int categoryOrder, int title) {
+        return addSubMenu(group, id, categoryOrder, mResources.getString(title));
+    }
+
+    public int addIntentOptions(int group, int id, int categoryOrder, ComponentName caller,
+            Intent[] specifics, Intent intent, int flags, MenuItem[] outSpecificItems) {
+        PackageManager pm = mContext.getPackageManager();
+        final List<ResolveInfo> lri =
+                pm.queryIntentActivityOptions(caller, specifics, intent, 0);
+        final int N = lri != null ? lri.size() : 0;
+
+        if ((flags & FLAG_APPEND_TO_GROUP) == 0) {
+            removeGroup(group);
+        }
+
+        for (int i=0; i<N; i++) {
+            final ResolveInfo ri = lri.get(i);
+            Intent rintent = new Intent(
+                ri.specificIndex < 0 ? intent : specifics[ri.specificIndex]);
+            rintent.setComponent(new ComponentName(
+                    ri.activityInfo.applicationInfo.packageName,
+                    ri.activityInfo.name));
+            final MenuItem item = add(group, id, categoryOrder, ri.loadLabel(pm))
+                    .setIcon(ri.loadIcon(pm))
+                    .setIntent(rintent);
+            if (outSpecificItems != null && ri.specificIndex >= 0) {
+                outSpecificItems[ri.specificIndex] = item;
+            }
+        }
+
+        return N;
+    }
+
+    public void removeItem(int id) {
+        removeItemAtInt(findItemIndex(id), true);
+    }
+
+    public void removeGroup(int group) {
+        final int i = findGroupIndex(group);
+
+        if (i >= 0) {
+            final int maxRemovable = mItems.size() - i;
+            int numRemoved = 0;
+            while ((numRemoved++ < maxRemovable) && (mItems.get(i).getGroupId() == group)) {
+                // Don't force update for each one, this method will do it at the end
+                removeItemAtInt(i, false);
+            }
+            
+            // Notify menu views
+            onItemsChanged(false);
+        }
+    }
+
+    /**
+     * Remove the item at the given index and optionally forces menu views to
+     * update.
+     * 
+     * @param index The index of the item to be removed. If this index is
+     *            invalid an exception is thrown.
+     * @param updateChildrenOnMenuViews Whether to force update on menu views.
+     *            Please make sure you eventually call this after your batch of
+     *            removals.
+     */
+    private void removeItemAtInt(int index, boolean updateChildrenOnMenuViews) {
+        if ((index < 0) || (index >= mItems.size())) return;
+
+        mItems.remove(index);
+        
+        if (updateChildrenOnMenuViews) onItemsChanged(false);
+    }
+    
+    public void removeItemAt(int index) {
+        removeItemAtInt(index, true);
+    }
+
+    public void clearAll() {
+        mPreventDispatchingItemsChanged = true;
+        clear();
+        clearHeader();
+        mPreventDispatchingItemsChanged = false;
+        onItemsChanged(true);
+    }
+    
+    public void clear() {
+        mItems.clear();
+        
+        onItemsChanged(true);
+    }
+
+    void setExclusiveItemChecked(MenuItem item) {
+        final int group = item.getGroupId();
+        
+        final int N = mItems.size();
+        for (int i = 0; i < N; i++) {
+            MenuItemImpl curItem = mItems.get(i);
+            if (curItem.getGroupId() == group) {
+                if (!curItem.isExclusiveCheckable()) continue;
+                if (!curItem.isCheckable()) continue;
+                
+                // Check the item meant to be checked, uncheck the others (that are in the group)
+                curItem.setCheckedInt(curItem == item);
+            }
+        }
+    }
+    
+    public void setGroupCheckable(int group, boolean checkable, boolean exclusive) {
+        final int N = mItems.size();
+       
+        for (int i = 0; i < N; i++) {
+            MenuItemImpl item = mItems.get(i);
+            if (item.getGroupId() == group) {
+                item.setExclusiveCheckable(exclusive);
+                item.setCheckable(checkable);
+            }
+        }
+    }
+
+    public void setGroupVisible(int group, boolean visible) {
+        final int N = mItems.size();
+
+        // We handle the notification of items being changed ourselves, so we use setVisibleInt rather
+        // than setVisible and at the end notify of items being changed
+        
+        boolean changedAtLeastOneItem = false;
+        for (int i = 0; i < N; i++) {
+            MenuItemImpl item = mItems.get(i);
+            if (item.getGroupId() == group) {
+                if (item.setVisibleInt(visible)) changedAtLeastOneItem = true;
+            }
+        }
+
+        if (changedAtLeastOneItem) onItemsChanged(false);
+    }
+
+    public void setGroupEnabled(int group, boolean enabled) {
+        final int N = mItems.size();
+
+        for (int i = 0; i < N; i++) {
+            MenuItemImpl item = mItems.get(i);
+            if (item.getGroupId() == group) {
+                item.setEnabled(enabled);
+            }
+        }
+    }
+
+    public boolean hasVisibleItems() {
+        final int size = size();
+
+        for (int i = 0; i < size; i++) {
+            MenuItemImpl item = mItems.get(i);
+            if (item.isVisible()) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    public MenuItem findItem(int id) {
+        final int size = size();
+        for (int i = 0; i < size; i++) {
+            MenuItemImpl item = mItems.get(i);
+            if (item.getItemId() == id) {
+                return item;
+            } else if (item.hasSubMenu()) {
+                MenuItem possibleItem = item.getSubMenu().findItem(id);
+                
+                if (possibleItem != null) {
+                    return possibleItem;
+                }
+            }
+        }
+        
+        return null;
+    }
+
+    public int findItemIndex(int id) {
+        final int size = size();
+
+        for (int i = 0; i < size; i++) {
+            MenuItemImpl item = mItems.get(i);
+            if (item.getItemId() == id) {
+                return i;
+            }
+        }
+
+        return -1;
+    }
+
+    public int findGroupIndex(int group) {
+        return findGroupIndex(group, 0);
+    }
+
+    public int findGroupIndex(int group, int start) {
+        final int size = size();
+        
+        if (start < 0) {
+            start = 0;
+        }
+        
+        for (int i = start; i < size; i++) {
+            final MenuItemImpl item = mItems.get(i);
+            
+            if (item.getGroupId() == group) {
+                return i;
+            }
+        }
+
+        return -1;
+    }
+    
+    public int size() {
+        return mItems.size();
+    }
+
+    /** {@inheritDoc} */
+    public MenuItem getItem(int index) {
+        return mItems.get(index);
+    }
+
+    public boolean isShortcutKey(int keyCode, KeyEvent event) {
+        return findItemWithShortcutForKey(keyCode, event) != null;
+    }
+
+    public void setQwertyMode(boolean isQwerty) {
+        mQwertyMode = isQwerty;
+        
+        refreshShortcuts(isShortcutsVisible(), isQwerty);
+    }
+
+    /**
+     * Returns the ordering across all items. This will grab the category from
+     * the upper bits, find out how to order the category with respect to other
+     * categories, and combine it with the lower bits.
+     * 
+     * @param categoryOrder The category order for a particular item (if it has
+     *            not been or/add with a category, the default category is
+     *            assumed).
+     * @return An ordering integer that can be used to order this item across
+     *         all the items (even from other categories).
+     */
+    private static int getOrdering(int categoryOrder)
+    {
+        final int index = (categoryOrder & CATEGORY_MASK) >> CATEGORY_SHIFT;
+        
+        if (index < 0 || index >= sCategoryToOrder.length) {
+            throw new IllegalArgumentException("order does not contain a valid category.");
+        }
+        
+        return (sCategoryToOrder[index] << CATEGORY_SHIFT) | (categoryOrder & USER_MASK);
+    }
+
+    /**
+     * @return whether the menu shortcuts are in qwerty mode or not
+     */
+    boolean isQwertyMode() {
+        return mQwertyMode;
+    }
+
+    /**
+     * Refreshes the shortcut labels on each of the displayed items.  Passes the arguments
+     * so submenus don't need to call their parent menu for the same values.
+     */
+    private void refreshShortcuts(boolean shortcutsVisible, boolean qwertyMode) {
+        MenuItemImpl item;
+        for (int i = mItems.size() - 1; i >= 0; i--) {
+            item = mItems.get(i);
+            
+            if (item.hasSubMenu()) {
+                ((MenuBuilder) item.getSubMenu()).refreshShortcuts(shortcutsVisible, qwertyMode);
+            }
+            
+            item.refreshShortcutOnItemViews(shortcutsVisible, qwertyMode);
+        }
+    }
+
+    /**
+     * Sets whether the shortcuts should be visible on menus.  Devices without hardware
+     * key input will never make shortcuts visible even if this method is passed 'true'.
+     * 
+     * @param shortcutsVisible Whether shortcuts should be visible (if true and a
+     *            menu item does not have a shortcut defined, that item will
+     *            still NOT show a shortcut)
+     */
+    public void setShortcutsVisible(boolean shortcutsVisible) {
+        if (mShortcutsVisible == shortcutsVisible) return;
+        
+        mShortcutsVisible =
+            (mResources.getConfiguration().keyboard != Configuration.KEYBOARD_NOKEYS)
+            && shortcutsVisible;
+        
+        refreshShortcuts(mShortcutsVisible, isQwertyMode());
+    }
+
+    /**
+     * @return Whether shortcuts should be visible on menus.
+     */
+    public boolean isShortcutsVisible() {
+        return mShortcutsVisible;
+    }
+    
+    Resources getResources() {
+        return mResources;
+    }
+
+    public Callback getCallback() {
+        return mCallback;
+    }
+    
+    public Context getContext() {
+        return mContext;
+    }
+    
+    private static int findInsertIndex(ArrayList<MenuItemImpl> items, int ordering) {
+        for (int i = items.size() - 1; i >= 0; i--) {
+            MenuItemImpl item = items.get(i);
+            if (item.getOrdering() <= ordering) {
+                return i + 1;
+            }
+        }
+        
+        return 0;
+    }
+    
+    public boolean performShortcut(int keyCode, KeyEvent event, int flags) {
+        final MenuItemImpl item = findItemWithShortcutForKey(keyCode, event);
+
+        boolean handled = false;
+        
+        if (item != null) {
+            handled = performItemAction(item, flags);
+        }
+        
+        if ((flags & FLAG_ALWAYS_PERFORM_CLOSE) != 0) {
+            close(true);
+        }
+        
+        return handled;
+    }
+
+    MenuItemImpl findItemWithShortcutForKey(int keyCode, KeyEvent event) {
+        final boolean qwerty = isQwertyMode();
+        final int metaState = event.getMetaState();
+        final KeyCharacterMap.KeyData possibleChars = new KeyCharacterMap.KeyData();
+        // Get the chars associated with the keyCode (i.e using any chording combo)
+        final boolean isKeyCodeMapped = event.getKeyData(possibleChars);
+        // The delete key is not mapped to '\b' so we treat it specially
+        if (!isKeyCodeMapped && (keyCode != KeyEvent.KEYCODE_DEL)) {
+            return null;
+        }
+
+        // Look for an item whose shortcut is this key.
+        final int N = mItems.size();
+        for (int i = 0; i < N; i++) {
+            MenuItemImpl item = mItems.get(i);
+            if (item.hasSubMenu()) {
+                MenuItemImpl subMenuItem = ((MenuBuilder)item.getSubMenu())
+                                .findItemWithShortcutForKey(keyCode, event);
+                if (subMenuItem != null) {
+                    return subMenuItem;
+                }
+            }
+            if (qwerty) {
+                final char shortcutAlphaChar = item.getAlphabeticShortcut();
+                if (((metaState & (KeyEvent.META_SHIFT_ON | KeyEvent.META_SYM_ON)) == 0) &&
+                        (shortcutAlphaChar != 0) &&
+                        (shortcutAlphaChar == possibleChars.meta[0]
+                         || shortcutAlphaChar == possibleChars.meta[2]
+                         || (shortcutAlphaChar == '\b' && keyCode == KeyEvent.KEYCODE_DEL)) &&
+                        item.isEnabled()) {
+                    return item;
+                }
+            } else {
+                final char shortcutNumericChar = item.getNumericShortcut();
+                if (((metaState & (KeyEvent.META_SHIFT_ON | KeyEvent.META_SYM_ON)) == 0) &&
+                        (shortcutNumericChar != 0) &&
+                        (shortcutNumericChar == possibleChars.meta[0]
+                            || shortcutNumericChar == possibleChars.meta[2]) &&
+                        item.isEnabled()) {
+                    return item;
+                }
+            }
+        }
+        return null;
+    }
+    
+    public boolean performIdentifierAction(int id, int flags) {
+        // Look for an item whose identifier is the id.
+        return performItemAction(findItem(id), flags);           
+    }
+
+    public boolean performItemAction(MenuItem item, int flags) {
+        MenuItemImpl itemImpl = (MenuItemImpl) item;
+        
+        if (itemImpl == null || !itemImpl.isEnabled()) {
+            return false;
+        }        
+        
+        boolean invoked = itemImpl.invoke();
+
+        if (item.hasSubMenu()) {
+            close(false);
+
+            if (mCallback != null) {
+                // Return true if the sub menu was invoked or the item was invoked previously
+                invoked = mCallback.onSubMenuSelected((SubMenuBuilder) item.getSubMenu())
+                        || invoked;
+            }
+        } else {
+            if ((flags & FLAG_PERFORM_NO_CLOSE) == 0) {
+                close(true);
+            }
+        }
+        
+        return invoked;
+    }
+    
+    /**
+     * Closes the visible menu.
+     * 
+     * @param allMenusAreClosing Whether the menus are completely closing (true),
+     *            or whether there is another menu coming in this menu's place
+     *            (false). For example, if the menu is closing because a
+     *            sub menu is about to be shown, <var>allMenusAreClosing</var>
+     *            is false.
+     */
+    final void close(boolean allMenusAreClosing) {
+        Callback callback = getCallback();
+        if (callback != null) {
+            callback.onCloseMenu(this, allMenusAreClosing);
+        }
+    }
+
+    /** {@inheritDoc} */
+    public void close() {
+        close(true);
+    }
+
+    /**
+     * Called when an item is added or removed.
+     * 
+     * @param cleared Whether the items were cleared or just changed.
+     */
+    private void onItemsChanged(boolean cleared) {
+        if (!mPreventDispatchingItemsChanged) {
+            if (mIsVisibleItemsStale == false) mIsVisibleItemsStale = true;
+            
+            MenuType[] menuTypes = mMenuTypes;
+            for (int i = 0; i < NUM_TYPES; i++) {
+                if ((menuTypes[i] != null) && (menuTypes[i].hasMenuView())) {
+                    MenuView menuView = menuTypes[i].mMenuView.get();
+                    menuView.updateChildren(cleared);
+                }
+            }
+        }
+    }
+
+    /**
+     * Called by {@link MenuItemImpl} when its visible flag is changed.
+     * @param item The item that has gone through a visibility change.
+     */
+    void onItemVisibleChanged(MenuItemImpl item) {
+        // Notify of items being changed
+        onItemsChanged(false);
+    }
+    
+    ArrayList<MenuItemImpl> getVisibleItems() {
+        if (!mIsVisibleItemsStale) return mVisibleItems;
+        
+        // Refresh the visible items
+        mVisibleItems.clear();
+        
+        final int itemsSize = mItems.size(); 
+        MenuItemImpl item;
+        for (int i = 0; i < itemsSize; i++) {
+            item = mItems.get(i);
+            if (item.isVisible()) mVisibleItems.add(item);
+        }
+        
+        mIsVisibleItemsStale = false;
+        
+        return mVisibleItems;
+    }
+
+    public void clearHeader() {
+        mHeaderIcon = null;
+        mHeaderTitle = null;
+        mHeaderView = null;
+        
+        onItemsChanged(false);
+    }
+    
+    private void setHeaderInternal(final int titleRes, final CharSequence title, final int iconRes,
+            final Drawable icon, final View view) {
+        final Resources r = getResources();
+
+        if (view != null) {
+            mHeaderView = view;
+            
+            // If using a custom view, then the title and icon aren't used
+            mHeaderTitle = null;
+            mHeaderIcon = null;
+        } else {
+            if (titleRes > 0) {
+                mHeaderTitle = r.getText(titleRes);
+            } else if (title != null) {
+                mHeaderTitle = title;
+            }
+            
+            if (iconRes > 0) {
+                mHeaderIcon = r.getDrawable(iconRes);
+            } else if (icon != null) {
+                mHeaderIcon = icon;
+            }
+            
+            // If using the title or icon, then a custom view isn't used
+            mHeaderView = null;
+        }
+        
+        // Notify of change
+        onItemsChanged(false);
+    }
+
+    /**
+     * Sets the header's title. This replaces the header view. Called by the
+     * builder-style methods of subclasses.
+     * 
+     * @param title The new title.
+     * @return This MenuBuilder so additional setters can be called.
+     */
+    protected MenuBuilder setHeaderTitleInt(CharSequence title) {
+        setHeaderInternal(0, title, 0, null, null);
+        return this;
+    }
+    
+    /**
+     * Sets the header's title. This replaces the header view. Called by the
+     * builder-style methods of subclasses.
+     * 
+     * @param titleRes The new title (as a resource ID).
+     * @return This MenuBuilder so additional setters can be called.
+     */
+    protected MenuBuilder setHeaderTitleInt(int titleRes) {
+        setHeaderInternal(titleRes, null, 0, null, null);
+        return this;
+    }
+    
+    /**
+     * Sets the header's icon. This replaces the header view. Called by the
+     * builder-style methods of subclasses.
+     * 
+     * @param icon The new icon.
+     * @return This MenuBuilder so additional setters can be called.
+     */
+    protected MenuBuilder setHeaderIconInt(Drawable icon) {
+        setHeaderInternal(0, null, 0, icon, null);
+        return this;
+    }
+    
+    /**
+     * Sets the header's icon. This replaces the header view. Called by the
+     * builder-style methods of subclasses.
+     * 
+     * @param iconRes The new icon (as a resource ID).
+     * @return This MenuBuilder so additional setters can be called.
+     */
+    protected MenuBuilder setHeaderIconInt(int iconRes) {
+        setHeaderInternal(0, null, iconRes, null, null);
+        return this;
+    }
+    
+    /**
+     * Sets the header's view. This replaces the title and icon. Called by the
+     * builder-style methods of subclasses.
+     * 
+     * @param view The new view.
+     * @return This MenuBuilder so additional setters can be called.
+     */
+    protected MenuBuilder setHeaderViewInt(View view) {
+        setHeaderInternal(0, null, 0, null, view);
+        return this;
+    }
+    
+    public CharSequence getHeaderTitle() {
+        return mHeaderTitle;
+    }
+    
+    public Drawable getHeaderIcon() {
+        return mHeaderIcon;
+    }
+    
+    public View getHeaderView() {
+        return mHeaderView;
+    }
+    
+    /**
+     * Gets the root menu (if this is a submenu, find its root menu).
+     * @return The root menu.
+     */
+    public MenuBuilder getRootMenu() {
+        return this;
+    }
+    
+    /**
+     * Sets the current menu info that is set on all items added to this menu
+     * (until this is called again with different menu info, in which case that
+     * one will be added to all subsequent item additions).
+     * 
+     * @param menuInfo The extra menu information to add.
+     */
+    public void setCurrentMenuInfo(ContextMenuInfo menuInfo) {
+        mCurrentMenuInfo = menuInfo;
+    }
+
+    /**
+     * Gets an adapter for providing items and their views.
+     * 
+     * @param menuType The type of menu to get an adapter for.
+     * @return A {@link MenuAdapter} for this menu with the given menu type.
+     */
+    public MenuAdapter getMenuAdapter(int menuType) {
+        return new MenuAdapter(menuType);
+    }
+
+    void setOptionalIconsVisible(boolean visible) {
+        mOptionalIconsVisible = visible;
+    }
+    
+    boolean getOptionalIconsVisible() {
+        return mOptionalIconsVisible;
+    }
+
+    public void saveHierarchyState(Bundle outState) {
+        SparseArray<Parcelable> viewStates = new SparseArray<Parcelable>();
+        
+        MenuType[] menuTypes = mMenuTypes;
+        for (int i = NUM_TYPES - 1; i >= 0; i--) {
+            if (menuTypes[i] == null) {
+                continue;
+            }
+
+            if (menuTypes[i].hasMenuView()) {
+                ((View) menuTypes[i].getMenuView(null)).saveHierarchyState(viewStates);
+            }
+        }
+        
+        outState.putSparseParcelableArray(VIEWS_TAG, viewStates);
+    }
+
+    public void restoreHierarchyState(Bundle inState) {
+        // Save this for menu views opened later
+        SparseArray<Parcelable> viewStates = mFrozenViewStates = inState
+                .getSparseParcelableArray(VIEWS_TAG);
+        
+        // Thaw those menu views already open
+        MenuType[] menuTypes = mMenuTypes;
+        for (int i = NUM_TYPES - 1; i >= 0; i--) {
+            if (menuTypes[i] == null) {
+                continue;
+            }
+            
+            if (menuTypes[i].hasMenuView()) {
+                ((View) menuTypes[i].getMenuView(null)).restoreHierarchyState(viewStates);
+            }
+        }
+    }
+    
+    /**
+     * An adapter that allows an {@link AdapterView} to use this {@link MenuBuilder} as a data
+     * source.  This adapter will use only the visible/shown items from the menu.
+     */
+    public class MenuAdapter extends BaseAdapter {
+        private int mMenuType;
+        
+        public MenuAdapter(int menuType) {
+            mMenuType = menuType;
+        }
+
+        public int getOffset() {
+            if (mMenuType == TYPE_EXPANDED) {
+                return getNumIconMenuItemsShown(); 
+            } else {
+                return 0;
+            }
+        }
+        
+        public int getCount() {
+            return getVisibleItems().size() - getOffset();
+        }
+
+        public MenuItemImpl getItem(int position) {
+            return getVisibleItems().get(position + getOffset());
+        }
+
+        public long getItemId(int position) {
+            // Since a menu item's ID is optional, we'll use the position as an
+            // ID for the item in the AdapterView
+            return position;
+        }
+
+        public View getView(int position, View convertView, ViewGroup parent) {
+            return ((MenuItemImpl) getItem(position)).getItemView(mMenuType, parent);
+        }
+
+    }
+}
diff --git a/core/java/com/android/internal/view/menu/MenuDialogHelper.java b/core/java/com/android/internal/view/menu/MenuDialogHelper.java
new file mode 100644
index 0000000..bc51cf3
--- /dev/null
+++ b/core/java/com/android/internal/view/menu/MenuDialogHelper.java
@@ -0,0 +1,122 @@
+/*
+ * 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.internal.view.menu;
+
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.content.DialogInterface;
+import android.os.IBinder;
+import android.view.KeyEvent;
+import android.view.View;
+import android.view.WindowManager;
+import android.widget.ListAdapter;
+
+/**
+ * Helper for menus that appear as Dialogs (context and submenus).
+ * 
+ * @hide
+ */
+public class MenuDialogHelper implements DialogInterface.OnKeyListener, DialogInterface.OnClickListener {
+    private MenuBuilder mMenu;
+    private ListAdapter mAdapter;
+    private AlertDialog mDialog;
+    
+    public MenuDialogHelper(MenuBuilder menu) {
+        mMenu = menu;
+    }
+
+    /**
+     * Shows menu as a dialog. 
+     * 
+     * @param windowToken Optional token to assign to the window.
+     */
+    public void show(IBinder windowToken) {
+        // Many references to mMenu, create local reference
+        final MenuBuilder menu = mMenu;
+        
+        // Get an adapter for the menu item views
+        mAdapter = menu.getMenuAdapter(MenuBuilder.TYPE_DIALOG);
+        
+        // Get the builder for the dialog
+        final AlertDialog.Builder builder = new AlertDialog.Builder(menu.getContext())
+                .setAdapter(mAdapter, this); 
+
+        // Set the title
+        final View headerView = menu.getHeaderView();
+        if (headerView != null) {
+            // Menu's client has given a custom header view, use it
+            builder.setCustomTitle(headerView);
+        } else {
+            // Otherwise use the (text) title and icon
+            builder.setIcon(menu.getHeaderIcon()).setTitle(menu.getHeaderTitle());
+        }
+        
+        // Set the key listener
+        builder.setOnKeyListener(this);
+        
+        // Show the menu
+        mDialog = builder.create();
+        
+        WindowManager.LayoutParams lp = mDialog.getWindow().getAttributes();
+        lp.type = WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG;
+        if (windowToken != null) {
+            lp.token = windowToken;
+        }
+        lp.flags |= WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
+        
+        mDialog.show();
+    }
+    
+    public boolean onKey(DialogInterface dialog, int keyCode, KeyEvent event) {
+        /*
+         * Close menu on key down (more responsive, and there's no way to cancel
+         * a key press so no point having it on key up. Note: This is also
+         * needed because when a top-level menu item that shows a submenu is
+         * invoked by chording, this onKey method will be called with the menu
+         * up event.
+         */
+        if (event.getAction() == KeyEvent.ACTION_DOWN && (keyCode == KeyEvent.KEYCODE_MENU)
+                || (keyCode == KeyEvent.KEYCODE_BACK)) {
+            mMenu.close(true);
+            dialog.dismiss();
+            return true;
+        }
+
+        // Menu shortcut matching
+        if (mMenu.performShortcut(keyCode, event, 0)) {
+            return true;
+        }
+        
+        return false;
+    }
+
+    /**
+     * Dismisses the menu's dialog.
+     * 
+     * @see Dialog#dismiss()
+     */
+    public void dismiss() {
+        if (mDialog != null) {
+            mDialog.dismiss();
+        }
+    }
+    
+    public void onClick(DialogInterface dialog, int which) {
+        mMenu.performItemAction((MenuItemImpl) mAdapter.getItem(which), 0);
+    }
+    
+}
diff --git a/core/java/com/android/internal/view/menu/MenuItemImpl.java b/core/java/com/android/internal/view/menu/MenuItemImpl.java
new file mode 100644
index 0000000..1543b62
--- /dev/null
+++ b/core/java/com/android/internal/view/menu/MenuItemImpl.java
@@ -0,0 +1,625 @@
+/*
+ * 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.internal.view.menu;
+
+import com.android.internal.view.menu.MenuView.ItemView;
+
+import android.content.Intent;
+import android.graphics.drawable.Drawable;
+import android.view.LayoutInflater;
+import android.view.MenuItem;
+import android.view.SubMenu;
+import android.view.View;
+import android.view.ViewDebug;
+import android.view.ViewGroup;
+import android.view.ContextMenu.ContextMenuInfo;
+
+import java.lang.ref.WeakReference;
+
+/**
+ * @hide
+ */
+public final class MenuItemImpl implements MenuItem {
+    private final int mId;
+    private final int mGroup;
+    private final int mCategoryOrder;
+    private final int mOrdering;
+    private CharSequence mTitle;
+    private CharSequence mTitleCondensed;
+    private Intent mIntent;
+    private char mShortcutNumericChar;
+    private char mShortcutAlphabeticChar;
+
+    /** The icon's drawable which is only created as needed */
+    private Drawable mIconDrawable;
+    /**
+     * The icon's resource ID which is used to get the Drawable when it is
+     * needed (if the Drawable isn't already obtained--only one of the two is
+     * needed).
+     */ 
+    private int mIconResId = NO_ICON;
+
+    /** The (cached) menu item views for this item */  
+    private WeakReference<ItemView> mItemViews[];
+    
+    /** The menu to which this item belongs */
+    private MenuBuilder mMenu;
+    /** If this item should launch a sub menu, this is the sub menu to launch */
+    private SubMenuBuilder mSubMenu;
+    
+    private Runnable mItemCallback;
+    private MenuItem.OnMenuItemClickListener mClickListener;
+
+    private int mFlags = ENABLED;
+    private static final int CHECKABLE      = 0x00000001;
+    private static final int CHECKED        = 0x00000002;
+    private static final int EXCLUSIVE      = 0x00000004;
+    private static final int HIDDEN         = 0x00000008;
+    private static final int ENABLED        = 0x00000010;
+
+    /** Used for the icon resource ID if this item does not have an icon */
+    static final int NO_ICON = 0;
+
+    /**
+     * Current use case is for context menu: Extra information linked to the
+     * View that added this item to the context menu.
+     */ 
+    private ContextMenuInfo mMenuInfo;
+    
+    private static String sPrependShortcutLabel;
+    private static String sEnterShortcutLabel;
+    private static String sDeleteShortcutLabel;
+    private static String sSpaceShortcutLabel;
+    
+    
+    /**
+     * Instantiates this menu item. The constructor
+     * {@link #MenuItemData(MenuBuilder, int, int, int, CharSequence, int)} is
+     * preferred due to lazy loading of the icon Drawable.
+     * 
+     * @param menu
+     * @param group Item ordering grouping control. The item will be added after
+     *            all other items whose order is <= this number, and before any
+     *            that are larger than it. This can also be used to define
+     *            groups of items for batch state changes. Normally use 0.
+     * @param id Unique item ID. Use 0 if you do not need a unique ID.
+     * @param categoryOrder The ordering for this item.
+     * @param title The text to display for the item.
+     */
+    MenuItemImpl(MenuBuilder menu, int group, int id, int categoryOrder, int ordering,
+            CharSequence title) {
+
+        if (sPrependShortcutLabel == null) {
+            // This is instantiated from the UI thread, so no chance of sync issues 
+            sPrependShortcutLabel = menu.getContext().getResources().getString(
+                    com.android.internal.R.string.prepend_shortcut_label);
+            sEnterShortcutLabel = menu.getContext().getResources().getString(
+                    com.android.internal.R.string.menu_enter_shortcut_label);
+            sDeleteShortcutLabel = menu.getContext().getResources().getString(
+                    com.android.internal.R.string.menu_delete_shortcut_label);
+            sSpaceShortcutLabel = menu.getContext().getResources().getString(
+                    com.android.internal.R.string.menu_space_shortcut_label);
+        }
+        
+        mItemViews = new WeakReference[MenuBuilder.NUM_TYPES];
+        mMenu = menu;
+        mId = id;
+        mGroup = group;
+        mCategoryOrder = categoryOrder;
+        mOrdering = ordering;
+        mTitle = title;
+    }
+    
+    /**
+     * Invokes the item by calling various listeners or callbacks.
+     * 
+     * @return true if the invocation was handled, false otherwise
+     */
+    public boolean invoke() {
+        if (mClickListener != null &&
+            mClickListener.onMenuItemClick(this)) {
+            return true;
+        }
+
+        MenuBuilder.Callback callback = mMenu.getCallback(); 
+        if (callback != null &&
+            callback.onMenuItemSelected(mMenu.getRootMenu(), this)) {
+            return true;
+        }
+
+        if (mItemCallback != null) {
+            mItemCallback.run();
+            return true;
+        }
+        
+        if (mIntent != null) {
+            mMenu.getContext().startActivity(mIntent);
+            return true;
+        }
+        
+        return false;
+    }
+    
+    private boolean hasItemView(int menuType) {
+        return mItemViews[menuType] != null && mItemViews[menuType].get() != null;
+    }
+    
+    public boolean isEnabled() {
+        return (mFlags & ENABLED) != 0;
+    }
+
+    public MenuItem setEnabled(boolean enabled) {
+        if (enabled) {
+            mFlags |= ENABLED;
+        } else {
+            mFlags &= ~ENABLED;
+        }
+
+        for (int i = MenuBuilder.NUM_TYPES - 1; i >= 0; i--) {
+            // If the item view prefers a condensed title, only set this title if there
+            // is no condensed title for this item
+            if (hasItemView(i)) {
+                mItemViews[i].get().setEnabled(enabled);
+            }
+        }
+        
+        return this;
+    }
+    
+    public int getGroupId() {
+        return mGroup;
+    }
+
+    @ViewDebug.CapturedViewProperty
+    public int getItemId() {
+        return mId;
+    }
+
+    public int getOrder() {
+        return mCategoryOrder;
+    }
+    
+    public int getOrdering() {
+        return mOrdering; 
+    }
+    
+    public Intent getIntent() {
+        return mIntent;
+    }
+
+    public MenuItem setIntent(Intent intent) {
+        mIntent = intent;
+        return this;
+    }
+
+    Runnable getCallback() {
+        return mItemCallback;
+    }
+    
+    public MenuItem setCallback(Runnable callback) {
+        mItemCallback = callback;
+        return this;
+    }
+    
+    public char getAlphabeticShortcut() {
+        return mShortcutAlphabeticChar;
+    }
+
+    public MenuItem setAlphabeticShortcut(char alphaChar) {
+        if (mShortcutAlphabeticChar == alphaChar) return this;
+        
+        mShortcutAlphabeticChar = Character.toLowerCase(alphaChar);
+        
+        refreshShortcutOnItemViews();
+        
+        return this;
+    }
+
+    public char getNumericShortcut() {
+        return mShortcutNumericChar;
+    }
+
+    public MenuItem setNumericShortcut(char numericChar) {
+        if (mShortcutNumericChar == numericChar) return this;
+        
+        mShortcutNumericChar = numericChar;
+        
+        refreshShortcutOnItemViews();
+        
+        return this;
+    }
+
+    public MenuItem setShortcut(char numericChar, char alphaChar) {
+        mShortcutNumericChar = numericChar;
+        mShortcutAlphabeticChar = Character.toLowerCase(alphaChar);
+        
+        refreshShortcutOnItemViews();
+        
+        return this;
+    }
+
+    /**
+     * @return The active shortcut (based on QWERTY-mode of the menu).
+     */
+    char getShortcut() {
+        return (mMenu.isQwertyMode() ? mShortcutAlphabeticChar : mShortcutNumericChar);
+    }
+    
+    /**
+     * @return The label to show for the shortcut. This includes the chording
+     *         key (for example 'Menu+a'). Also, any non-human readable
+     *         characters should be human readable (for example 'Menu+enter').
+     */
+    String getShortcutLabel() {
+
+        char shortcut = getShortcut();
+        if (shortcut == 0) {
+            return "";
+        }
+        
+        StringBuilder sb = new StringBuilder(sPrependShortcutLabel);
+        switch (shortcut) {
+        
+            case '\n':
+                sb.append(sEnterShortcutLabel);
+                break;
+            
+            case '\b':
+                sb.append(sDeleteShortcutLabel);
+                break;
+            
+            case ' ':
+                sb.append(sSpaceShortcutLabel);
+                break;
+            
+            default:
+                sb.append(shortcut);
+                break;
+        }
+        
+        return sb.toString();
+    }
+    
+    /**
+     * @return Whether this menu item should be showing shortcuts (depends on
+     *         whether the menu should show shortcuts and whether this item has
+     *         a shortcut defined)
+     */
+    boolean shouldShowShortcut() {
+        // Show shortcuts if the menu is supposed to show shortcuts AND this item has a shortcut
+        return mMenu.isShortcutsVisible() && (getShortcut() != 0);
+    }
+    
+    /**
+     * Refreshes the shortcut shown on the ItemViews.  This method retrieves current
+     * shortcut state (mode and shown) from the menu that contains this item.
+     */
+    private void refreshShortcutOnItemViews() {
+        refreshShortcutOnItemViews(mMenu.isShortcutsVisible(), mMenu.isQwertyMode());
+    }
+
+    /**
+     * Refreshes the shortcut shown on the ItemViews. This is usually called by
+     * the {@link MenuBuilder} when it is refreshing the shortcuts on all item
+     * views, so it passes arguments rather than each item calling a method on the menu to get
+     * the same values.
+     * 
+     * @param menuShortcutShown The menu's shortcut shown mode. In addition,
+     *            this method will ensure this item has a shortcut before it
+     *            displays the shortcut.
+     * @param isQwertyMode Whether the shortcut mode is qwerty mode
+     */
+    void refreshShortcutOnItemViews(boolean menuShortcutShown, boolean isQwertyMode) {
+        final char shortcutKey = (isQwertyMode) ? mShortcutAlphabeticChar : mShortcutNumericChar;
+
+        // Show shortcuts if the menu is supposed to show shortcuts AND this item has a shortcut
+        final boolean showShortcut = menuShortcutShown && (shortcutKey != 0);
+        
+        for (int i = MenuBuilder.NUM_TYPES - 1; i >= 0; i--) {
+            if (hasItemView(i)) {
+                mItemViews[i].get().setShortcut(showShortcut, shortcutKey);
+            }
+        }
+    }
+    
+    public SubMenu getSubMenu() {
+        return mSubMenu;
+    }
+
+    public boolean hasSubMenu() {
+        return mSubMenu != null;
+    }
+
+    void setSubMenu(SubMenuBuilder subMenu) {
+        if ((mMenu != null) && (mMenu instanceof SubMenu)) {
+            throw new UnsupportedOperationException(
+            "Attempt to add a sub-menu to a sub-menu.");
+        }
+        
+        mSubMenu = subMenu;
+        
+        subMenu.setHeaderTitle(getTitle());
+    }
+    
+    @ViewDebug.CapturedViewProperty
+    public CharSequence getTitle() {
+        return mTitle;
+    }
+
+    /**
+     * Gets the title for a particular {@link ItemView}
+     * 
+     * @param itemView The ItemView that is receiving the title
+     * @return Either the title or condensed title based on what the ItemView
+     *         prefers
+     */
+    CharSequence getTitleForItemView(MenuView.ItemView itemView) {
+        return ((itemView != null) && itemView.prefersCondensedTitle())
+                ? getTitleCondensed()
+                : getTitle();
+    }
+
+    public MenuItem setTitle(CharSequence title) {
+        mTitle = title;
+
+        for (int i = MenuBuilder.NUM_TYPES - 1; i >= 0; i--) {
+            // If the item view prefers a condensed title, only set this title if there
+            // is no condensed title for this item
+            if (!hasItemView(i)) {
+                continue;
+            }
+            
+            ItemView itemView = mItemViews[i].get(); 
+            if (!itemView.prefersCondensedTitle() || mTitleCondensed == null) {
+                itemView.setTitle(title);
+            }
+        }
+        
+        if (mSubMenu != null) {
+            mSubMenu.setHeaderTitle(title);
+        }
+        
+        return this;
+    }
+    
+    public MenuItem setTitle(int title) {
+        return setTitle(mMenu.getContext().getString(title));
+    }
+    
+    public CharSequence getTitleCondensed() {
+        return mTitleCondensed != null ? mTitleCondensed : mTitle;
+    }
+    
+    public MenuItem setTitleCondensed(CharSequence title) {
+        mTitleCondensed = title;
+
+        // Could use getTitle() in the loop below, but just cache what it would do here 
+        if (title == null) {
+            title = mTitle;
+        }
+        
+        for (int i = MenuBuilder.NUM_TYPES - 1; i >= 0; i--) {
+            // Refresh those item views that prefer a condensed title
+            if (hasItemView(i) && (mItemViews[i].get().prefersCondensedTitle())) {
+                mItemViews[i].get().setTitle(title);
+            }
+        }
+        
+        return this;
+    }
+
+    public Drawable getIcon() {
+        
+        if (mIconDrawable != null) {
+            return mIconDrawable;
+        }
+
+        if (mIconResId != NO_ICON) {
+            return mMenu.getResources().getDrawable(mIconResId);
+        }
+        
+        return null;
+    }
+    
+    public MenuItem setIcon(Drawable icon) {
+        mIconResId = NO_ICON;
+        mIconDrawable = icon;
+        setIconOnViews(icon);
+        
+        return this;
+    }
+    
+    public MenuItem setIcon(int iconResId) {
+        mIconDrawable = null;
+        mIconResId = iconResId;
+
+        // If we have a view, we need to push the Drawable to them
+        if (haveAnyOpenedIconCapableItemViews()) {
+            Drawable drawable = iconResId != NO_ICON ? mMenu.getResources().getDrawable(iconResId)
+                    : null;
+            setIconOnViews(drawable);
+        }
+        
+        return this;
+    }
+
+    private void setIconOnViews(Drawable icon) {
+        for (int i = MenuBuilder.NUM_TYPES - 1; i >= 0; i--) {
+            // Refresh those item views that are able to display an icon
+            if (hasItemView(i) && mItemViews[i].get().showsIcon()) {
+                mItemViews[i].get().setIcon(icon);
+            }
+        }
+    }
+    
+    private boolean haveAnyOpenedIconCapableItemViews() {
+        for (int i = MenuBuilder.NUM_TYPES - 1; i >= 0; i--) {
+            if (hasItemView(i) && mItemViews[i].get().showsIcon()) {
+                return true;
+            }
+        }
+        
+        return false;
+    }
+    
+    public boolean isCheckable() {
+        return (mFlags & CHECKABLE) == CHECKABLE;
+    }
+
+    public MenuItem setCheckable(boolean checkable) {
+        final int oldFlags = mFlags;
+        mFlags = (mFlags & ~CHECKABLE) | (checkable ? CHECKABLE : 0);
+        if (oldFlags != mFlags) {
+            for (int i = MenuBuilder.NUM_TYPES - 1; i >= 0; i--) {
+                if (hasItemView(i)) {
+                    mItemViews[i].get().setCheckable(checkable);
+                }
+            }
+        }
+        
+        return this;
+    }
+
+    public void setExclusiveCheckable(boolean exclusive)
+    {
+        mFlags = (mFlags&~EXCLUSIVE) | (exclusive ? EXCLUSIVE : 0);
+    }
+
+    public boolean isExclusiveCheckable() {
+        return (mFlags & EXCLUSIVE) != 0;
+    }
+    
+    public boolean isChecked() {
+        return (mFlags & CHECKED) == CHECKED;
+    }
+
+    public MenuItem setChecked(boolean checked) {
+        if ((mFlags & EXCLUSIVE) != 0) {
+            // Call the method on the Menu since it knows about the others in this
+            // exclusive checkable group
+            mMenu.setExclusiveItemChecked(this);
+        } else {
+            setCheckedInt(checked);
+        }
+        
+        return this;
+    }
+
+    void setCheckedInt(boolean checked) {
+        final int oldFlags = mFlags;
+        mFlags = (mFlags & ~CHECKED) | (checked ? CHECKED : 0);
+        if (oldFlags != mFlags) {
+            for (int i = MenuBuilder.NUM_TYPES - 1; i >= 0; i--) {
+                if (hasItemView(i)) {
+                    mItemViews[i].get().setChecked(checked);
+                }
+            }
+        }
+    }
+    
+    public boolean isVisible() {
+        return (mFlags & HIDDEN) == 0;
+    }
+
+    /**
+     * Changes the visibility of the item. This method DOES NOT notify the
+     * parent menu of a change in this item, so this should only be called from
+     * methods that will eventually trigger this change.  If unsure, use {@link #setVisible(boolean)}
+     * instead.
+     * 
+     * @param shown Whether to show (true) or hide (false).
+     * @return Whether the item's shown state was changed
+     */
+    boolean setVisibleInt(boolean shown) {
+        final int oldFlags = mFlags;
+        mFlags = (mFlags & ~HIDDEN) | (shown ? 0 : HIDDEN);
+        return oldFlags != mFlags;
+    }
+    
+    public MenuItem setVisible(boolean shown) {
+        // Try to set the shown state to the given state. If the shown state was changed
+        // (i.e. the previous state isn't the same as given state), notify the parent menu that
+        // the shown state has changed for this item
+        if (setVisibleInt(shown)) mMenu.onItemVisibleChanged(this);
+        
+        return this;
+    }
+
+   public MenuItem setOnMenuItemClickListener(MenuItem.OnMenuItemClickListener clickListener) {
+        mClickListener = clickListener;
+        return this;
+    }
+
+    View getItemView(int menuType, ViewGroup parent) {
+        if (!hasItemView(menuType)) {
+            mItemViews[menuType] = new WeakReference<ItemView>(createItemView(menuType, parent));
+        }
+        
+        return (View) mItemViews[menuType].get();
+    }
+
+    /**
+     * Create and initializes a menu item view that implements {@link MenuView.ItemView}.
+     * @param menuType The type of menu to get a View for (must be one of
+     *            {@link MenuBuilder#TYPE_ICON}, {@link MenuBuilder#TYPE_EXPANDED},
+     *            {@link MenuBuilder#TYPE_SUB}, {@link MenuBuilder#TYPE_CONTEXT}).
+     * @return The inflated {@link MenuView.ItemView} that is ready for use
+     */
+    private MenuView.ItemView createItemView(int menuType, ViewGroup parent) {
+        // Create the MenuView
+        MenuView.ItemView itemView = (MenuView.ItemView) getLayoutInflater(menuType)
+                .inflate(MenuBuilder.ITEM_LAYOUT_RES_FOR_TYPE[menuType], parent, false);
+        itemView.initialize(this, menuType);
+        return itemView;
+    }
+    
+    void clearItemViews() {
+        for (int i = mItemViews.length - 1; i >= 0; i--) {
+            mItemViews[i] = null;
+        }
+    }
+    
+    @Override
+    public String toString() {
+        return mTitle.toString();
+    }
+
+    void setMenuInfo(ContextMenuInfo menuInfo) {
+        mMenuInfo = menuInfo;
+    }
+    
+    public ContextMenuInfo getMenuInfo() {
+        return mMenuInfo;
+    }
+    
+    /**
+     * Returns a LayoutInflater that is themed for the given menu type.
+     * 
+     * @param menuType The type of menu.
+     * @return A LayoutInflater.
+     */
+    public LayoutInflater getLayoutInflater(int menuType) {
+        return mMenu.getMenuType(menuType).getInflater();
+    }
+
+    /**
+     * @return Whether the given menu type should show icons for menu items.
+     */
+    public boolean shouldShowIcon(int menuType) {
+        return menuType == MenuBuilder.TYPE_ICON || mMenu.getOptionalIconsVisible();
+    }
+}
diff --git a/core/java/com/android/internal/view/menu/MenuView.java b/core/java/com/android/internal/view/menu/MenuView.java
new file mode 100644
index 0000000..5090400
--- /dev/null
+++ b/core/java/com/android/internal/view/menu/MenuView.java
@@ -0,0 +1,133 @@
+/*
+ * 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.internal.view.menu;
+
+import com.android.internal.view.menu.MenuBuilder;
+import com.android.internal.view.menu.MenuItemImpl;
+
+import android.graphics.drawable.Drawable;
+
+/**
+ * Minimal interface for a menu view.  {@link #initialize(MenuBuilder, int)} must be called for the
+ * menu to be functional.
+ * 
+ * @hide
+ */
+public interface MenuView {
+    /**
+     * Initializes the menu to the given menu. This should be called after the
+     * view is inflated.
+     * 
+     * @param menu The menu that this MenuView should display.
+     * @param menuType The type of this menu, one of 
+     *            {@link MenuBuilder#TYPE_ICON}, {@link MenuBuilder#TYPE_EXPANDED},
+     *            {@link MenuBuilder#TYPE_DIALOG}).
+     */
+    public void initialize(MenuBuilder menu, int menuType);
+
+    /**
+     * Forces the menu view to update its view to reflect the new state of the menu.
+     * 
+     * @param cleared Whether the menu was cleared or just modified.
+     */
+    public void updateChildren(boolean cleared);
+
+    /**
+     * Returns the default animations to be used for this menu when entering/exiting.
+     * @return A resource ID for the default animations to be used for this menu.
+     */
+    public int getWindowAnimations();
+    
+    /**
+     * Minimal interface for a menu item view.  {@link #initialize(MenuItemImpl, int)} must be called
+     * for the item to be functional.
+     */
+    public interface ItemView {
+        /**
+         * Initializes with the provided MenuItemData.  This should be called after the view is
+         * inflated.
+         * @param itemData The item that this ItemView should display.
+         * @param menuType The type of this menu, one of 
+         *            {@link MenuBuilder#TYPE_ICON}, {@link MenuBuilder#TYPE_EXPANDED},
+         *            {@link MenuBuilder#TYPE_DIALOG}).
+         */
+        public void initialize(MenuItemImpl itemData, int menuType);
+        
+        /**
+         * Gets the item data that this view is displaying.
+         * @return the item data, or null if there is not one
+         */
+        public MenuItemImpl getItemData();
+        
+        /**
+         * Sets the title of the item view.
+         * @param title The title to set.
+         */
+        public void setTitle(CharSequence title);
+
+        /**
+         * Sets the enabled state of the item view.
+         * @param enabled Whether the item view should be enabled.
+         */
+        public void setEnabled(boolean enabled);
+        
+        /**
+         * Displays the checkbox for the item view.  This does not ensure the item view will be
+         * checked, for that use {@link #setChecked}. 
+         * @param checkable Whether to display the checkbox or to hide it
+         */
+        public void setCheckable(boolean checkable);
+        
+        /**
+         * Checks the checkbox for the item view.  If the checkbox is hidden, it will NOT be
+         * made visible, call {@link #setCheckable(boolean)} for that.
+         * @param checked Whether the checkbox should be checked
+         */
+        public void setChecked(boolean checked);
+
+        /**
+         * Sets the shortcut for the item.
+         * @param showShortcut Whether a shortcut should be shown(if false, the value of 
+         * shortcutKey should be ignored).
+         * @param shortcutKey The shortcut key that should be shown on the ItemView.
+         */
+        public void setShortcut(boolean showShortcut, char shortcutKey);
+        
+        /**
+         * Set the icon of this item view.
+         * @param icon The icon of this item. null to hide the icon.
+         */
+        public void setIcon(Drawable icon);
+        
+        /**
+         * Whether this item view prefers displaying the condensed title rather
+         * than the normal title. If a condensed title is not available, the
+         * normal title will be used.
+         * 
+         * @return Whether this item view prefers displaying the condensed
+         *         title.
+         */
+        public boolean prefersCondensedTitle();
+
+        /**
+         * Whether this item view shows an icon.
+         * 
+         * @return Whether this item view shows an icon.
+         */
+        public boolean showsIcon();
+    }
+}
diff --git a/core/java/com/android/internal/view/menu/SubMenuBuilder.java b/core/java/com/android/internal/view/menu/SubMenuBuilder.java
new file mode 100644
index 0000000..af1b996
--- /dev/null
+++ b/core/java/com/android/internal/view/menu/SubMenuBuilder.java
@@ -0,0 +1,114 @@
+/*
+ * 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.internal.view.menu;
+
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.SubMenu;
+import android.view.View;
+
+/**
+ * The model for a sub menu, which is an extension of the menu.  Most methods are proxied to
+ * the parent menu.
+ */
+public class SubMenuBuilder extends MenuBuilder implements SubMenu {
+    private MenuBuilder mParentMenu;
+    private MenuItemImpl mItem;
+    
+    public SubMenuBuilder(Context context, MenuBuilder parentMenu, MenuItemImpl item) {
+        super(context);
+
+        mParentMenu = parentMenu;
+        mItem = item;
+    }
+
+    @Override
+    public void setQwertyMode(boolean isQwerty) {
+        mParentMenu.setQwertyMode(isQwerty);
+    }
+
+    @Override
+    public boolean isQwertyMode() {
+        return mParentMenu.isQwertyMode();
+    }
+    
+    @Override
+    public void setShortcutsVisible(boolean shortcutsVisible) {
+        mParentMenu.setShortcutsVisible(shortcutsVisible);
+    }
+
+    @Override
+    public boolean isShortcutsVisible() {
+        return mParentMenu.isShortcutsVisible();
+    }
+
+    public Menu getParentMenu() {
+        return mParentMenu;
+    }
+
+    public MenuItem getItem() {
+        return mItem;
+    }
+
+    @Override
+    public Callback getCallback() {
+        return mParentMenu.getCallback();
+    }
+
+    @Override
+    public void setCallback(Callback callback) {
+        mParentMenu.setCallback(callback);
+    }
+
+    @Override
+    public MenuBuilder getRootMenu() {
+        return mParentMenu;
+    }
+
+    public SubMenu setIcon(Drawable icon) {
+        mItem.setIcon(icon);
+        return this;
+    }
+
+    public SubMenu setIcon(int iconRes) {
+        mItem.setIcon(iconRes);
+        return this;
+    }
+
+    public SubMenu setHeaderIcon(Drawable icon) {
+        return (SubMenu) super.setHeaderIconInt(icon);
+    }
+
+    public SubMenu setHeaderIcon(int iconRes) {
+        return (SubMenu) super.setHeaderIconInt(iconRes);
+    }
+
+    public SubMenu setHeaderTitle(CharSequence title) {
+        return (SubMenu) super.setHeaderTitleInt(title);
+    }
+
+    public SubMenu setHeaderTitle(int titleRes) {
+        return (SubMenu) super.setHeaderTitleInt(titleRes);
+    }
+
+    public SubMenu setHeaderView(View view) {
+        return (SubMenu) super.setHeaderViewInt(view);
+    }
+    
+}
diff --git a/core/java/com/android/internal/view/package.html b/core/java/com/android/internal/view/package.html
new file mode 100644
index 0000000..783d0a1
--- /dev/null
+++ b/core/java/com/android/internal/view/package.html
@@ -0,0 +1,3 @@
+<body>
+{@hide}
+</body>
diff --git a/core/java/com/android/internal/widget/DialogTitle.java b/core/java/com/android/internal/widget/DialogTitle.java
new file mode 100644
index 0000000..2eef0b6
--- /dev/null
+++ b/core/java/com/android/internal/widget/DialogTitle.java
@@ -0,0 +1,71 @@
+/* 
+ * Copyright (C) 2008 Google Inc.
+ *
+ * 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.internal.widget;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.text.Layout;
+import android.util.AttributeSet;
+import android.util.TypedValue;
+import android.widget.TextView;
+
+/**
+ * Used by dialogs to change the font size and number of lines to try to fit
+ * the text to the available space.
+ */
+public class DialogTitle extends TextView {
+    
+    public DialogTitle(Context context, AttributeSet attrs,
+            int defStyle) {
+        super(context, attrs, defStyle);
+    }
+
+    public DialogTitle(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    public DialogTitle(Context context) {
+        super(context);
+    }
+    
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+
+        final Layout layout = getLayout();
+        if (layout != null) {
+            final int lineCount = layout.getLineCount();
+            if (lineCount > 0) {
+                final int ellipsisCount = layout.getEllipsisCount(lineCount - 1);
+                if (ellipsisCount > 0) {
+                    setSingleLine(false);
+                    
+                    TypedArray a = mContext.obtainStyledAttributes(
+                            android.R.style.TextAppearance_Medium,
+                            android.R.styleable.TextAppearance);
+                    final int textSize = a.getDimensionPixelSize(
+                            android.R.styleable.TextAppearance_textSize, 20);
+
+                    setTextSize(TypedValue.COMPLEX_UNIT_SP, textSize);
+                    setMaxLines(2);
+                    super.onMeasure(widthMeasureSpec, heightMeasureSpec);      
+                }
+            }
+        }
+    }
+   
+}
\ No newline at end of file
diff --git a/core/java/com/android/internal/widget/EditableInputConnection.java b/core/java/com/android/internal/widget/EditableInputConnection.java
new file mode 100644
index 0000000..f2ec064
--- /dev/null
+++ b/core/java/com/android/internal/widget/EditableInputConnection.java
@@ -0,0 +1,129 @@
+/*
+ * Copyright (C) 2007-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.internal.widget;
+
+import android.os.Bundle;
+import android.text.Editable;
+import android.text.method.KeyListener;
+import android.util.Log;
+import android.view.inputmethod.BaseInputConnection;
+import android.view.inputmethod.CompletionInfo;
+import android.view.inputmethod.ExtractedText;
+import android.view.inputmethod.ExtractedTextRequest;
+import android.widget.TextView;
+
+public class EditableInputConnection extends BaseInputConnection {
+    private static final boolean DEBUG = false;
+    private static final String TAG = "EditableInputConnection";
+
+    private final TextView mTextView;
+
+    public EditableInputConnection(TextView textview) {
+        super(textview, false);
+        mTextView = textview;
+    }
+
+    public Editable getEditable() {
+        TextView tv = mTextView;
+        if (tv != null) {
+            return tv.getEditableText();
+        }
+        return null;
+    }
+    
+    public boolean beginBatchEdit() {
+        mTextView.beginBatchEdit();
+        return true;
+    }
+    
+    public boolean endBatchEdit() {
+        mTextView.endBatchEdit();
+        return true;
+    }
+    
+    public boolean clearMetaKeyStates(int states) {
+        final Editable content = getEditable();
+        if (content == null) return false;
+        KeyListener kl = mTextView.getKeyListener();
+        if (kl != null) {
+            try {
+                kl.clearMetaKeyState(mTextView, content, states);
+            } catch (AbstractMethodError e) {
+                // This is an old listener that doesn't implement the
+                // new method.
+            }
+        }
+        return true;
+    }
+    
+    public boolean commitCompletion(CompletionInfo text) {
+        if (DEBUG) Log.v(TAG, "commitCompletion " + text);
+        mTextView.beginBatchEdit();
+        mTextView.onCommitCompletion(text);
+        mTextView.endBatchEdit();
+        return true;
+    }
+
+    public boolean performEditorAction(int actionCode) {
+        if (DEBUG) Log.v(TAG, "performEditorAction " + actionCode);
+        mTextView.onEditorAction(actionCode);
+        return true;
+    }
+    
+    public boolean performContextMenuAction(int id) {
+        if (DEBUG) Log.v(TAG, "performContextMenuAction " + id);
+        mTextView.beginBatchEdit();
+        mTextView.onTextContextMenuItem(id);
+        mTextView.endBatchEdit();
+        return true;
+    }
+    
+    public ExtractedText getExtractedText(ExtractedTextRequest request, int flags) {
+        if (mTextView != null) {
+            ExtractedText et = new ExtractedText();
+            if (mTextView.extractText(request, et)) {
+                if ((flags&GET_EXTRACTED_TEXT_MONITOR) != 0) {
+                    mTextView.setExtracting(request);
+                }
+                return et;
+            }
+        }
+        return null;
+    }
+    
+    public boolean performPrivateCommand(String action, Bundle data) {
+        mTextView.onPrivateIMECommand(action, data);
+        return true;
+    }
+
+    @Override
+    public boolean commitText(CharSequence text, int newCursorPosition) {
+        if (mTextView == null) {
+            return super.commitText(text, newCursorPosition);
+        }
+
+        CharSequence errorBefore = mTextView.getError();
+        boolean success = super.commitText(text, newCursorPosition);
+        CharSequence errorAfter = mTextView.getError();
+
+        if (errorAfter != null && errorBefore == errorAfter) {
+            mTextView.setError(null, null);
+        }
+
+        return success;
+    }
+}
diff --git a/core/java/com/android/internal/widget/LinearLayoutWithDefaultTouchRecepient.java b/core/java/com/android/internal/widget/LinearLayoutWithDefaultTouchRecepient.java
new file mode 100644
index 0000000..b2001cb
--- /dev/null
+++ b/core/java/com/android/internal/widget/LinearLayoutWithDefaultTouchRecepient.java
@@ -0,0 +1,66 @@
+/*
+ * 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.internal.widget;
+
+import android.content.Context;
+import android.graphics.Rect;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.MotionEvent;
+import android.widget.LinearLayout;
+
+
+/**
+ * Like a normal linear layout, but supports dispatching all otherwise unhandled
+ * touch events to a particular descendant.  This is for the unlock screen, so
+ * that a wider range of touch events than just the lock pattern widget can kick
+ * off a lock pattern if the finger is eventually dragged into the bounds of the
+ * lock pattern view.
+ */
+public class LinearLayoutWithDefaultTouchRecepient extends LinearLayout {
+
+    private final Rect mTempRect = new Rect();
+    private View mDefaultTouchRecepient;
+
+    public LinearLayoutWithDefaultTouchRecepient(Context context) {
+        super(context);
+    }
+
+    public LinearLayoutWithDefaultTouchRecepient(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    public void setDefaultTouchRecepient(View defaultTouchRecepient) {
+        mDefaultTouchRecepient = defaultTouchRecepient;
+    }
+
+    @Override
+    public boolean dispatchTouchEvent(MotionEvent ev) {
+        if (mDefaultTouchRecepient == null) {
+            return super.dispatchTouchEvent(ev);
+        }
+
+        if (super.dispatchTouchEvent(ev)) {
+            return true;
+        }
+        mTempRect.set(0, 0, 0, 0);
+        offsetRectIntoDescendantCoords(mDefaultTouchRecepient, mTempRect);
+        ev.setLocation(ev.getX() + mTempRect.left, ev.getY() + mTempRect.top);
+        return mDefaultTouchRecepient.dispatchTouchEvent(ev);
+    }
+
+}
diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java
new file mode 100644
index 0000000..c8b3ad4
--- /dev/null
+++ b/core/java/com/android/internal/widget/LockPatternUtils.java
@@ -0,0 +1,359 @@
+/*
+ * Copyright (C) 2007 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.internal.widget;
+
+import android.content.ContentResolver;
+import android.os.SystemClock;
+import android.provider.Settings;
+import android.security.MessageDigest;
+import android.text.TextUtils;
+import android.util.Log;
+
+import com.google.android.collect.Lists;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.RandomAccessFile;
+import java.security.NoSuchAlgorithmException;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Utilities for the lock patten and its settings.
+ */
+public class LockPatternUtils {
+
+    private static final String TAG = "LockPatternUtils";
+    
+    private static final String LOCK_PATTERN_FILE = "/system/gesture.key";
+
+    /**
+     * The maximum number of incorrect attempts before the user is prevented
+     * from trying again for {@link #FAILED_ATTEMPT_TIMEOUT_MS}.
+     */
+    public static final int FAILED_ATTEMPTS_BEFORE_TIMEOUT = 5;
+
+    /**
+     * The number of incorrect attempts before which we fall back on an alternative
+     * method of verifying the user, and resetting their lock pattern.
+     */
+    public static final int FAILED_ATTEMPTS_BEFORE_RESET = 20;
+
+    /**
+     * How long the user is prevented from trying again after entering the
+     * wrong pattern too many times.
+     */
+    public static final long FAILED_ATTEMPT_TIMEOUT_MS = 30000L;
+
+    /**
+     * The interval of the countdown for showing progress of the lockout.
+     */
+    public static final long FAILED_ATTEMPT_COUNTDOWN_INTERVAL_MS = 1000L;
+
+    /**
+     * The minimum number of dots in a valid pattern.
+     */
+    public static final int MIN_LOCK_PATTERN_SIZE = 4;
+
+    /**
+     * The minimum number of dots the user must include in a wrong pattern
+     * attempt for it to be counted against the counts that affect
+     * {@link #FAILED_ATTEMPTS_BEFORE_TIMEOUT} and {@link #FAILED_ATTEMPTS_BEFORE_RESET}
+     */
+    public static final int MIN_PATTERN_REGISTER_FAIL = 3;    
+
+    private final static String LOCKOUT_PERMANENT_KEY = "lockscreen.lockedoutpermanently";
+    private final static String LOCKOUT_ATTEMPT_DEADLINE = "lockscreen.lockoutattemptdeadline";
+
+    private final ContentResolver mContentResolver;
+
+    private static String sLockPatternFilename;
+    
+    /**
+     * @param contentResolver Used to look up and save settings.
+     */
+    public LockPatternUtils(ContentResolver contentResolver) {
+        mContentResolver = contentResolver;
+        // Initialize the location of gesture lock file
+        if (sLockPatternFilename == null) {
+            sLockPatternFilename = android.os.Environment.getDataDirectory() 
+                    .getAbsolutePath() + LOCK_PATTERN_FILE;
+        }
+    }
+
+    /**
+     * Check to see if a pattern matches the saved pattern.  If no pattern exists,
+     * always returns true.
+     * @param pattern The pattern to check.
+     * @return Whether the pattern matchees the stored one.
+     */
+    public boolean checkPattern(List<LockPatternView.Cell> pattern) {
+        try {
+            // Read all the bytes from the file
+            RandomAccessFile raf = new RandomAccessFile(sLockPatternFilename, "r");
+            final byte[] stored = new byte[(int) raf.length()];
+            int got = raf.read(stored, 0, stored.length);
+            raf.close();
+            if (got <= 0) {
+                return true;
+            }
+            // Compare the hash from the file with the entered pattern's hash
+            return Arrays.equals(stored, LockPatternUtils.patternToHash(pattern));
+        } catch (FileNotFoundException fnfe) {
+            return true;
+        } catch (IOException ioe) {
+            return true;
+        }
+    }
+
+    /**
+     * Check to see if the user has stored a lock pattern.
+     * @return Whether a saved pattern exists.
+     */
+    public boolean savedPatternExists() {
+        try {
+            // Check if we can read a byte from the file
+            RandomAccessFile raf = new RandomAccessFile(sLockPatternFilename, "r");
+            byte first = raf.readByte();
+            raf.close();
+            return true;
+        } catch (FileNotFoundException fnfe) {
+            return false;
+        } catch (IOException ioe) {
+            return false;
+        }
+    }
+
+    /**
+     * Save a lock pattern.
+     * @param pattern The new pattern to save.
+     */
+    public void saveLockPattern(List<LockPatternView.Cell> pattern) {
+        // Compute the hash
+        final byte[] hash  = LockPatternUtils.patternToHash(pattern);
+        try {
+            // Write the hash to file
+            RandomAccessFile raf = new RandomAccessFile(sLockPatternFilename, "rw");
+            // Truncate the file if pattern is null, to clear the lock
+            if (pattern == null) {
+                raf.setLength(0);
+            } else {
+                raf.write(hash, 0, hash.length);
+            }
+            raf.close();
+        } catch (FileNotFoundException fnfe) {
+            // Cant do much, unless we want to fail over to using the settings provider
+            Log.e(TAG, "Unable to save lock pattern to " + sLockPatternFilename);
+        } catch (IOException ioe) {
+            // Cant do much
+            Log.e(TAG, "Unable to save lock pattern to " + sLockPatternFilename);
+        }
+    }
+
+    /**
+     * Deserialize a pattern.
+     * @param string The pattern serialized with {@link #patternToString}
+     * @return The pattern.
+     */
+    public static List<LockPatternView.Cell> stringToPattern(String string) {
+        List<LockPatternView.Cell> result = Lists.newArrayList();
+
+        final byte[] bytes = string.getBytes();
+        for (int i = 0; i < bytes.length; i++) {
+            byte b = bytes[i];
+            result.add(LockPatternView.Cell.of(b / 3, b % 3));
+        }
+        return result;
+    }
+
+    /**
+     * Serialize a pattern.
+     * @param pattern The pattern.
+     * @return The pattern in string form.
+     */
+    public static String patternToString(List<LockPatternView.Cell> pattern) {
+        if (pattern == null) {
+            return "";
+        }
+        final int patternSize = pattern.size();
+
+        byte[] res = new byte[patternSize];
+        for (int i = 0; i < patternSize; i++) {
+            LockPatternView.Cell cell = pattern.get(i);
+            res[i] = (byte) (cell.getRow() * 3 + cell.getColumn());
+        }
+        return new String(res);
+    }
+    
+    /*
+     * Generate an SHA-1 hash for the pattern. Not the most secure, but it is
+     * at least a second level of protection. First level is that the file
+     * is in a location only readable by the system process.
+     * @param pattern the gesture pattern.
+     * @return the hash of the pattern in a byte array.
+     */
+    static byte[] patternToHash(List<LockPatternView.Cell> pattern) {
+        if (pattern == null) {
+            return null;
+        }
+        
+        final int patternSize = pattern.size();
+        byte[] res = new byte[patternSize];
+        for (int i = 0; i < patternSize; i++) {
+            LockPatternView.Cell cell = pattern.get(i);
+            res[i] = (byte) (cell.getRow() * 3 + cell.getColumn());
+        }
+        try {
+            MessageDigest md = MessageDigest.getInstance("SHA-1");
+            byte[] hash = md.digest(res);
+            return hash;
+        } catch (NoSuchAlgorithmException nsa) {
+            return res;
+        }
+    }
+
+    /**
+     * @return Whether the lock pattern is enabled.
+     */
+    public boolean isLockPatternEnabled() {
+        return getBoolean(Settings.System.LOCK_PATTERN_ENABLED);
+    }
+
+    /**
+     * Set whether the lock pattern is enabled.
+     */
+    public void setLockPatternEnabled(boolean enabled) {
+        setBoolean(Settings.System.LOCK_PATTERN_ENABLED, enabled);
+    }
+
+    /**
+     * @return Whether the visible pattern is enabled.
+     */
+    public boolean isVisiblePatternEnabled() {
+        return getBoolean(Settings.System.LOCK_PATTERN_VISIBLE);
+    }
+
+    /**
+     * Set whether the visible pattern is enabled.
+     */
+    public void setVisiblePatternEnabled(boolean enabled) {
+        setBoolean(Settings.System.LOCK_PATTERN_VISIBLE, enabled);
+    }
+
+    /**
+     * @return Whether tactile feedback for the pattern is enabled.
+     */
+    public boolean isTactileFeedbackEnabled() {
+        return getBoolean(Settings.System.LOCK_PATTERN_TACTILE_FEEDBACK_ENABLED);
+    }
+
+    /**
+     * Set whether tactile feedback for the pattern is enabled.
+     */
+    public void setTactileFeedbackEnabled(boolean enabled) {
+        setBoolean(Settings.System.LOCK_PATTERN_TACTILE_FEEDBACK_ENABLED, enabled);
+    }
+
+    /**
+     * Set and store the lockout deadline, meaning the user can't attempt his/her unlock
+     * pattern until the deadline has passed.
+     * @return the chosen deadline.
+     */
+    public long setLockoutAttemptDeadline() {
+        final long deadline = SystemClock.elapsedRealtime() + FAILED_ATTEMPT_TIMEOUT_MS;
+        setLong(LOCKOUT_ATTEMPT_DEADLINE, deadline);
+        return deadline;
+    }
+
+    /**
+     * @return The elapsed time in millis in the future when the user is allowed to
+     *   attempt to enter his/her lock pattern, or 0 if the user is welcome to
+     *   enter a pattern.
+     */
+    public long getLockoutAttemptDeadline() {
+        final long deadline = getLong(LOCKOUT_ATTEMPT_DEADLINE, 0L);
+        final long now = SystemClock.elapsedRealtime();
+        if (deadline < now || deadline > (now + FAILED_ATTEMPT_TIMEOUT_MS)) {
+            return 0L;
+        }
+        return deadline;
+    }
+
+    /**
+     * @return Whether the user is permanently locked out until they verify their
+     *   credentials.  Occurs after {@link #FAILED_ATTEMPTS_BEFORE_RESET} failed
+     *   attempts.
+     */
+    public boolean isPermanentlyLocked() {
+        return getBoolean(LOCKOUT_PERMANENT_KEY);
+    }
+
+    /**
+     * Set the state of whether the device is permanently locked, meaning the user
+     * must authenticate via other means.  If false, that means the user has gone
+     * out of permanent lock, so the existing (forgotten) lock pattern needs to
+     * be cleared.
+     * @param locked Whether the user is permanently locked out until they verify their
+     *   credentials.  Occurs after {@link #FAILED_ATTEMPTS_BEFORE_RESET} failed
+     *   attempts.
+     */
+    public void setPermanentlyLocked(boolean locked) {
+        setBoolean(LOCKOUT_PERMANENT_KEY, locked);
+
+        if (!locked) {
+            setLockPatternEnabled(false);
+            saveLockPattern(null);
+        }
+    }
+
+    /**
+     * @return A formatted string of the next alarm (for showing on the lock screen),
+     *   or null if there is no next alarm.
+     */
+    public String getNextAlarm() {
+        String nextAlarm = Settings.System.getString(mContentResolver,
+                Settings.System.NEXT_ALARM_FORMATTED);
+        if (nextAlarm == null || TextUtils.isEmpty(nextAlarm)) {
+            return null;
+        }
+        return nextAlarm;
+    }
+
+    private boolean getBoolean(String systemSettingKey) {
+        return 1 ==
+                android.provider.Settings.System.getInt(
+                        mContentResolver,
+                        systemSettingKey, 0);
+    }
+
+    private void setBoolean(String systemSettingKey, boolean enabled) {
+        android.provider.Settings.System.putInt(
+                        mContentResolver,
+                        systemSettingKey,
+                        enabled ? 1 : 0);
+    }
+
+    private long getLong(String systemSettingKey, long def) {
+        return android.provider.Settings.System.getLong(mContentResolver, systemSettingKey, def);
+    }
+
+    private void setLong(String systemSettingKey, long value) {
+        android.provider.Settings.System.putLong(mContentResolver, systemSettingKey, value);
+    }
+
+
+}
diff --git a/core/java/com/android/internal/widget/LockPatternView.java b/core/java/com/android/internal/widget/LockPatternView.java
new file mode 100644
index 0000000..7f99ac8
--- /dev/null
+++ b/core/java/com/android/internal/widget/LockPatternView.java
@@ -0,0 +1,1024 @@
+/*
+ * Copyright (C) 2007 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.internal.widget;
+
+
+import com.android.internal.R;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Matrix;
+import android.graphics.Paint;
+import android.graphics.Path;
+import android.graphics.Rect;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.SystemClock;
+import android.os.Debug;
+import android.os.Vibrator;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.WindowManager;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Displays and detects the user's unlock attempt, which is a drag of a finger
+ * across 9 regions of the screen.
+ *
+ * Is also capable of displaying a static pattern in "in progress", "wrong" or
+ * "correct" states.
+ */
+public class LockPatternView extends View {
+    // Vibrator pattern for creating a tactile bump
+    private static final long[] VIBE_PATTERN = {0, 1, 40, 41};
+
+    private static final boolean PROFILE_DRAWING = false;
+    private boolean mDrawingProfilingStarted = false;
+
+    private Paint mPaint = new Paint();
+    private Paint mPathPaint = new Paint();
+
+    // TODO: make this common with PhoneWindow
+    static final int STATUS_BAR_HEIGHT = 25;
+
+    /**
+     * How many milliseconds we spend animating each circle of a lock pattern
+     * if the animating mode is set.  The entire animation should take this
+     * constant * the length of the pattern to complete.
+     */
+    private static final int MILLIS_PER_CIRCLE_ANIMATING = 700;
+
+    private OnPatternListener mOnPatternListener;
+    private ArrayList<Cell> mPattern = new ArrayList<Cell>(9);
+
+    /**
+     * Lookup table for the circles of the pattern we are currently drawing.
+     * This will be the cells of the complete pattern unless we are animating,
+     * in which case we use this to hold the cells we are drawing for the in
+     * progress animation.
+     */
+    private boolean[][] mPatternDrawLookup = new boolean[3][3];
+
+    /**
+     * the in progress point:
+     * - during interaction: where the user's finger is
+     * - during animation: the current tip of the animating line
+     */
+    private float mInProgressX = -1;
+    private float mInProgressY = -1;
+
+    private long mAnimatingPeriodStart;
+
+    private DisplayMode mPatternDisplayMode = DisplayMode.Correct;
+    private boolean mInputEnabled = true;
+    private boolean mInStealthMode = false;
+    private boolean mTactileFeedbackEnabled = true;
+    private boolean mPatternInProgress = false;
+
+    private float mDiameterFactor = 0.5f;
+    private float mHitFactor = 0.6f;
+
+    private float mSquareWidth;
+    private float mSquareHeight;
+
+    private Bitmap mBitmapBtnDefault;
+    private Bitmap mBitmapBtnTouched;
+    private Bitmap mBitmapCircleDefault;
+    private Bitmap mBitmapCircleGreen;
+    private Bitmap mBitmapCircleRed;
+
+    private Bitmap mBitmapArrowGreenUp;
+    private Bitmap mBitmapArrowRedUp;
+
+    private final Path mCurrentPath = new Path();
+    private final Rect mInvalidate = new Rect();
+
+    private int mBitmapWidth;
+    private int mBitmapHeight;
+   
+
+    private Vibrator vibe; // Vibrator for creating tactile feedback
+
+    /**
+     * Represents a cell in the 3 X 3 matrix of the unlock pattern view.
+     */
+    public static class Cell {
+        int row;
+        int column;
+
+        // keep # objects limited to 9
+        static Cell[][] sCells = new Cell[3][3];
+        static {
+            for (int i = 0; i < 3; i++) {
+                for (int j = 0; j < 3; j++) {
+                    sCells[i][j] = new Cell(i, j);
+                }
+            }
+        }
+
+        /**
+         * @param row The row of the cell.
+         * @param column The column of the cell.
+         */
+        private Cell(int row, int column) {
+            checkRange(row, column);
+            this.row = row;
+            this.column = column;
+        }
+
+        public int getRow() {
+            return row;
+        }
+
+        public int getColumn() {
+            return column;
+        }
+
+        /**
+         * @param row The row of the cell.
+         * @param column The column of the cell.
+         */
+        public static synchronized Cell of(int row, int column) {
+            checkRange(row, column);
+            return sCells[row][column];
+        }
+
+        private static void checkRange(int row, int column) {
+            if (row < 0 || row > 2) {
+                throw new IllegalArgumentException("row must be in range 0-2");
+            }
+            if (column < 0 || column > 2) {
+                throw new IllegalArgumentException("column must be in range 0-2");
+            }
+        }
+
+        public String toString() {
+            return "(row=" + row + ",clmn=" + column + ")";
+        }
+    }
+
+    /**
+     * How to display the current pattern.
+     */
+    public enum DisplayMode {
+
+        /**
+         * The pattern drawn is correct (i.e draw it in a friendly color)
+         */
+        Correct,
+
+        /**
+         * Animate the pattern (for demo, and help).
+         */
+        Animate,
+
+        /**
+         * The pattern is wrong (i.e draw a foreboding color)
+         */
+        Wrong
+    }
+
+    /**
+     * The call back interface for detecting patterns entered by the user.
+     */
+    public static interface OnPatternListener {
+
+        /**
+         * A new pattern has begun.
+         */
+        void onPatternStart();
+
+        /**
+         * The pattern was cleared.
+         */
+        void onPatternCleared();
+
+        /**
+         * A pattern was detected from the user.
+         * @param pattern The pattern.
+         */
+        void onPatternDetected(List<Cell> pattern);
+    }
+
+    public LockPatternView(Context context) {
+        this(context, null);
+    }
+
+    public LockPatternView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        vibe = new Vibrator();
+
+        setClickable(true);
+
+        mPathPaint.setAntiAlias(true);
+        mPathPaint.setDither(true);
+        mPathPaint.setColor(Color.WHITE);   // TODO this should be from the style
+        mPathPaint.setAlpha(128);
+        mPathPaint.setStyle(Paint.Style.STROKE);
+        mPathPaint.setStrokeJoin(Paint.Join.ROUND);
+        mPathPaint.setStrokeCap(Paint.Cap.ROUND);
+
+        // lot's of bitmaps!
+        mBitmapBtnDefault = getBitmapFor(R.drawable.btn_code_lock_default);
+        mBitmapBtnTouched = getBitmapFor(R.drawable.btn_code_lock_touched);
+        mBitmapCircleDefault = getBitmapFor(R.drawable.indicator_code_lock_point_area_default);
+        mBitmapCircleGreen = getBitmapFor(R.drawable.indicator_code_lock_point_area_green);
+        mBitmapCircleRed = getBitmapFor(R.drawable.indicator_code_lock_point_area_red);
+
+        mBitmapArrowGreenUp = getBitmapFor(R.drawable.indicator_code_lock_drag_direction_green_up);
+        mBitmapArrowRedUp = getBitmapFor(R.drawable.indicator_code_lock_drag_direction_red_up);
+
+        // we assume all bitmaps have the same size
+        mBitmapWidth = mBitmapBtnDefault.getWidth();
+        mBitmapHeight = mBitmapBtnDefault.getHeight();
+    }
+
+    private Bitmap getBitmapFor(int resId) {
+        return BitmapFactory.decodeResource(getContext().getResources(), resId);
+    }
+
+    /**
+     * @return Whether the view is in stealth mode.
+     */
+    public boolean isInStealthMode() {
+        return mInStealthMode;
+    }
+
+    /**
+     * @return Whether the view has tactile feedback enabled.
+     */
+    public boolean isTactileFeedbackEnabled() {
+        return mTactileFeedbackEnabled;
+    }
+
+    /**
+     * Set whether the view is in stealth mode.  If true, there will be no
+     * visible feedback as the user enters the pattern.
+     *
+     * @param inStealthMode Whether in stealth mode.
+     */
+    public void setInStealthMode(boolean inStealthMode) {
+        mInStealthMode = inStealthMode;
+    }
+
+    /**
+     * Set whether the view will use tactile feedback.  If true, there will be
+     * tactile feedback as the user enters the pattern.
+     *
+     * @param tactileFeedbackEnabled Whether tactile feedback is enabled
+     */
+    public void setTactileFeedbackEnabled(boolean tactileFeedbackEnabled) {
+        mTactileFeedbackEnabled = tactileFeedbackEnabled;
+    }
+
+    /**
+     * Set the call back for pattern detection.
+     * @param onPatternListener The call back.
+     */
+    public void setOnPatternListener(
+            OnPatternListener onPatternListener) {
+        mOnPatternListener = onPatternListener;
+    }
+
+    /**
+     * Set the pattern explicitely (rather than waiting for the user to input
+     * a pattern).
+     * @param displayMode How to display the pattern.
+     * @param pattern The pattern.
+     */
+    public void setPattern(DisplayMode displayMode, List<Cell> pattern) {
+        mPattern.clear();
+        mPattern.addAll(pattern);
+        clearPatternDrawLookup();
+        for (Cell cell : pattern) {
+            mPatternDrawLookup[cell.getRow()][cell.getColumn()] = true;
+        }
+
+        setDisplayMode(displayMode);
+    }
+
+    /**
+     * Set the display mode of the current pattern.  This can be useful, for
+     * instance, after detecting a pattern to tell this view whether change the
+     * in progress result to correct or wrong.
+     * @param displayMode The display mode.
+     */
+    public void setDisplayMode(DisplayMode displayMode) {
+        mPatternDisplayMode = displayMode;
+        if (displayMode == DisplayMode.Animate) {
+            if (mPattern.size() == 0) {
+                throw new IllegalStateException("you must have a pattern to "
+                        + "animate if you want to set the display mode to animate");
+            }
+            mAnimatingPeriodStart = SystemClock.elapsedRealtime();
+            final Cell first = mPattern.get(0);
+            mInProgressX = getCenterXForColumn(first.getColumn());
+            mInProgressY = getCenterYForRow(first.getRow());
+            clearPatternDrawLookup();
+        }
+        invalidate();
+    }
+
+    /**
+     * Clear the pattern.
+     */
+    public void clearPattern() {
+        resetPattern();
+    }
+
+    /**
+     * Reset all pattern state.
+     */
+    private void resetPattern() {
+        mPattern.clear();
+        clearPatternDrawLookup();
+        mPatternDisplayMode = DisplayMode.Correct;
+        invalidate();
+    }
+
+    /**
+     * Clear the pattern lookup table.
+     */
+    private void clearPatternDrawLookup() {
+        for (int i = 0; i < 3; i++) {
+            for (int j = 0; j < 3; j++) {
+                mPatternDrawLookup[i][j] = false;
+            }
+        }
+    }
+
+    /**
+     * Disable input (for instance when displaying a message that will
+     * timeout so user doesn't get view into messy state).
+     */
+    public void disableInput() {
+        mInputEnabled = false;
+    }
+
+    /**
+     * Enable input.
+     */
+    public void enableInput() {
+        mInputEnabled = true;
+    }
+
+    @Override
+    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
+        final int width = w - mPaddingLeft - mPaddingRight;
+        mSquareWidth = width / 3.0f;
+
+        final int height = h - mPaddingTop - mPaddingBottom;
+        mSquareHeight = height / 3.0f;
+    }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        final WindowManager wm = (WindowManager) getContext()
+                .getSystemService(Context.WINDOW_SERVICE);
+        final int width = wm.getDefaultDisplay().getWidth();
+        final int height = wm.getDefaultDisplay().getHeight();
+        int squareSide = Math.min(width, height);
+
+        // if in landscape...
+        if (width > height) {
+            squareSide -= STATUS_BAR_HEIGHT;
+        }
+
+        setMeasuredDimension(squareSide, squareSide);
+    }
+
+    /**
+     * Determines whether the point x, y will add a new point to the current
+     * pattern (in addition to finding the cell, also makes heuristic choices
+     * such as filling in gaps based on current pattern).
+     * @param x The x coordinate.
+     * @param y The y coordinate.
+     */
+    private Cell detectAndAddHit(float x, float y) {
+        final Cell cell = checkForNewHit(x, y);
+        if (cell != null) {
+
+            // check for gaps in existing pattern
+            Cell fillInGapCell = null;
+            final ArrayList<Cell> pattern = mPattern;
+            if (!pattern.isEmpty()) {
+                final Cell lastCell = pattern.get(pattern.size() - 1);
+                int dRow = cell.row - lastCell.row;
+                int dColumn = cell.column - lastCell.column;
+
+                int fillInRow = lastCell.row;
+                int fillInColumn = lastCell.column;
+
+                if (Math.abs(dRow) == 2 && Math.abs(dColumn) != 1) {
+                    fillInRow = lastCell.row + ((dRow > 0) ? 1 : -1);
+                }
+
+                if (Math.abs(dColumn) == 2 && Math.abs(dRow) != 1) {
+                    fillInColumn = lastCell.column + ((dColumn > 0) ? 1 : -1);
+                }
+
+                fillInGapCell = Cell.of(fillInRow, fillInColumn);
+            }
+
+            if (fillInGapCell != null &&
+                    !mPatternDrawLookup[fillInGapCell.row][fillInGapCell.column]) {
+                addCellToPattern(fillInGapCell);
+            }
+            addCellToPattern(cell);
+            if (mTactileFeedbackEnabled){
+                vibe.vibrate(VIBE_PATTERN, -1); // Generate tactile feedback
+            }
+            return cell;
+        }
+        return null;
+    }
+
+    private void addCellToPattern(Cell newCell) {
+        mPatternDrawLookup[newCell.getRow()][newCell.getColumn()] = true;
+        mPattern.add(newCell);
+    }
+
+    // helper method to find which cell a point maps to
+    private Cell checkForNewHit(float x, float y) {
+
+        final int rowHit = getRowHit(y);
+        if (rowHit < 0) {
+            return null;
+        }
+        final int columnHit = getColumnHit(x);
+        if (columnHit < 0) {
+            return null;
+        }
+
+        if (mPatternDrawLookup[rowHit][columnHit]) {
+            return null;
+        }
+        return Cell.of(rowHit, columnHit);
+    }
+
+    /**
+     * Helper method to find the row that y falls into.
+     * @param y The y coordinate
+     * @return The row that y falls in, or -1 if it falls in no row.
+     */
+    private int getRowHit(float y) {
+
+        final float squareHeight = mSquareHeight;
+        float hitSize = squareHeight * mHitFactor;
+
+        float offset = mPaddingTop + (squareHeight - hitSize) / 2f;
+        for (int i = 0; i < 3; i++) {
+
+            final float hitTop = offset + squareHeight * i;
+            if (y >= hitTop && y <= hitTop + hitSize) {
+                return i;
+            }
+        }
+        return -1;
+    }
+
+    /**
+     * Helper method to find the column x fallis into.
+     * @param x The x coordinate.
+     * @return The column that x falls in, or -1 if it falls in no column.
+     */
+    private int getColumnHit(float x) {
+        final float squareWidth = mSquareWidth;
+        float hitSize = squareWidth * mHitFactor;
+
+        float offset = mPaddingLeft + (squareWidth - hitSize) / 2f;
+        for (int i = 0; i < 3; i++) {
+
+            final float hitLeft = offset + squareWidth * i;
+            if (x >= hitLeft && x <= hitLeft + hitSize) {
+                return i;
+            }
+        }
+        return -1;
+    }
+
+    @Override
+    public boolean onTouchEvent(MotionEvent motionEvent) {
+        if (!mInputEnabled || !isEnabled()) {
+            return false;
+        }
+
+        final float x = motionEvent.getX();
+        final float y = motionEvent.getY();
+        Cell hitCell;
+        switch(motionEvent.getAction()) {
+            case MotionEvent.ACTION_DOWN:
+                resetPattern();
+                hitCell = detectAndAddHit(x, y);
+                if (hitCell != null && mOnPatternListener != null) {
+                    mPatternInProgress = true;
+                    mPatternDisplayMode = DisplayMode.Correct;
+                    mOnPatternListener.onPatternStart();
+                } else if (mOnPatternListener != null) {
+                    mPatternInProgress = false;
+                    mOnPatternListener.onPatternCleared();
+                }
+                if (hitCell != null) {
+                    final float startX = getCenterXForColumn(hitCell.column);
+                    final float startY = getCenterYForRow(hitCell.row);
+
+                    final float widthOffset = mSquareWidth / 2f;
+                    final float heightOffset = mSquareHeight / 2f;
+
+                    invalidate((int) (startX - widthOffset), (int) (startY - heightOffset),
+                            (int) (startX + widthOffset), (int) (startY + heightOffset));
+                }
+                mInProgressX = x;
+                mInProgressY = y;
+                if (PROFILE_DRAWING) {
+                    if (!mDrawingProfilingStarted) {
+                        Debug.startMethodTracing("LockPatternDrawing");
+                        mDrawingProfilingStarted = true;
+                    }
+                }
+                return true;
+            case MotionEvent.ACTION_UP:
+                // report pattern detected
+                if (!mPattern.isEmpty() && mOnPatternListener != null) {
+                    mPatternInProgress = false;
+                    mOnPatternListener.onPatternDetected(mPattern);
+                    invalidate();
+                }
+                if (PROFILE_DRAWING) {
+                    if (mDrawingProfilingStarted) {
+                        Debug.stopMethodTracing();
+                        mDrawingProfilingStarted = false;
+                    }
+                }
+                return true;
+            case MotionEvent.ACTION_MOVE:
+                final int patternSizePreHitDetect = mPattern.size();
+                hitCell = detectAndAddHit(x, y);
+                final int patternSize = mPattern.size();
+                if (hitCell != null && (mOnPatternListener != null) && (patternSize == 1)) {
+                    mPatternInProgress = true;
+                    mOnPatternListener.onPatternStart();
+                }
+                // note current x and y for rubber banding of in progress
+                // patterns
+                final float dx = Math.abs(x - mInProgressX);
+                final float dy = Math.abs(y - mInProgressY);
+                if (dx + dy > mSquareWidth * 0.01f) {
+                    float oldX = mInProgressX;
+                    float oldY = mInProgressY;
+
+                    mInProgressX = x;
+                    mInProgressY = y;
+
+                    if (mPatternInProgress) {
+                        final ArrayList<Cell> pattern = mPattern;
+                        final float radius = mSquareWidth * mDiameterFactor * 0.5f;
+
+                        Cell cell = pattern.get(patternSize - 1);
+
+                        float startX = getCenterXForColumn(cell.column);
+                        float startY = getCenterYForRow(cell.row);
+
+                        float left;
+                        float top;
+                        float right;
+                        float bottom;
+
+                        final Rect invalidateRect = mInvalidate;
+
+                        if (startX < x) {
+                            left = startX;
+                            right = x;
+                        } else {
+                            left = x;
+                            right = startX;
+                        }
+
+                        if (startY < y) {
+                            top = startY;
+                            bottom = y;
+                        } else {
+                            top = y;
+                            bottom = startY;
+                        }
+
+                        // Invalidate between the pattern's last cell and the current location
+                        invalidateRect.set((int) (left - radius), (int) (top - radius),
+                                (int) (right + radius), (int) (bottom + radius));
+
+                        if (startX < oldX) {
+                            left = startX;
+                            right = oldX;
+                        } else {
+                            left = oldX;
+                            right = startX;
+                        }
+
+                        if (startY < oldY) {
+                            top = startY;
+                            bottom = oldY;
+                        } else {
+                            top = oldY;
+                            bottom = startY;
+                        }
+
+                        // Invalidate between the pattern's last cell and the previous location
+                        invalidateRect.union((int) (left - radius), (int) (top - radius),
+                                (int) (right + radius), (int) (bottom + radius));
+
+                        // Invalidate between the pattern's new cell and the pattern's previous cell
+                        if (hitCell != null) {
+                            startX = getCenterXForColumn(hitCell.column);
+                            startY = getCenterYForRow(hitCell.row);
+
+                            if (patternSize >= 2) {
+                                // (re-using hitcell for old cell)
+                                hitCell = pattern.get(patternSize - 1 - (patternSize - patternSizePreHitDetect));
+                                oldX = getCenterXForColumn(hitCell.column);
+                                oldY = getCenterYForRow(hitCell.row);
+
+                                if (startX < oldX) {
+                                    left = startX;
+                                    right = oldX;
+                                } else {
+                                    left = oldX;
+                                    right = startX;
+                                }
+
+                                if (startY < oldY) {
+                                    top = startY;
+                                    bottom = oldY;
+                                } else {
+                                    top = oldY;
+                                    bottom = startY;
+                                }
+                            } else {
+                                left = right = startX;
+                                top = bottom = startY;
+                            }
+
+                            final float widthOffset = mSquareWidth / 2f;
+                            final float heightOffset = mSquareHeight / 2f;
+
+                            invalidateRect.set((int) (left - widthOffset),
+                                    (int) (top - heightOffset), (int) (right + widthOffset),
+                                    (int) (bottom + heightOffset));
+                        }
+
+                        invalidate(invalidateRect);
+                    } else {
+                        invalidate();
+                    }
+                }
+                return true;
+            case MotionEvent.ACTION_CANCEL:
+                resetPattern();
+                if (mOnPatternListener != null) {
+                    mPatternInProgress = false;
+                    mOnPatternListener.onPatternCleared();
+                }
+                if (PROFILE_DRAWING) {
+                    if (mDrawingProfilingStarted) {
+                        Debug.stopMethodTracing();
+                        mDrawingProfilingStarted = false;
+                    }
+                }
+                return true;
+        }
+        return false;
+    }
+
+    private float getCenterXForColumn(int column) {
+        return mPaddingLeft + column * mSquareWidth + mSquareWidth / 2f;
+    }
+
+    private float getCenterYForRow(int row) {
+        return mPaddingTop + row * mSquareHeight + mSquareHeight / 2f;
+    }
+
+    @Override
+    protected void onDraw(Canvas canvas) {
+        final ArrayList<Cell> pattern = mPattern;
+        final int count = pattern.size();
+        final boolean[][] drawLookup = mPatternDrawLookup;
+
+        if (mPatternDisplayMode == DisplayMode.Animate) {
+
+            // figure out which circles to draw
+
+            // + 1 so we pause on complete pattern
+            final int oneCycle = (count + 1) * MILLIS_PER_CIRCLE_ANIMATING;
+            final int spotInCycle = (int) (SystemClock.elapsedRealtime() -
+                    mAnimatingPeriodStart) % oneCycle;
+            final int numCircles = spotInCycle / MILLIS_PER_CIRCLE_ANIMATING;
+
+            clearPatternDrawLookup();
+            for (int i = 0; i < numCircles; i++) {
+                final Cell cell = pattern.get(i);
+                drawLookup[cell.getRow()][cell.getColumn()] = true;
+            }
+
+            // figure out in progress portion of ghosting line
+
+            final boolean needToUpdateInProgressPoint = numCircles > 0
+                    && numCircles < count;
+
+            if (needToUpdateInProgressPoint) {
+                final float percentageOfNextCircle =
+                        ((float) (spotInCycle % MILLIS_PER_CIRCLE_ANIMATING)) /
+                                MILLIS_PER_CIRCLE_ANIMATING;
+
+                final Cell currentCell = pattern.get(numCircles - 1);
+                final float centerX = getCenterXForColumn(currentCell.column);
+                final float centerY = getCenterYForRow(currentCell.row);
+
+                final Cell nextCell = pattern.get(numCircles);
+                final float dx = percentageOfNextCircle *
+                        (getCenterXForColumn(nextCell.column) - centerX);
+                final float dy = percentageOfNextCircle *
+                        (getCenterYForRow(nextCell.row) - centerY);
+                mInProgressX = centerX + dx;
+                mInProgressY = centerY + dy;
+            }
+            // TODO: Infinite loop here...
+            invalidate();
+        }
+
+        final float squareWidth = mSquareWidth;
+        final float squareHeight = mSquareHeight;
+
+        float radius = (squareWidth * mDiameterFactor * 0.5f);
+        mPathPaint.setStrokeWidth(radius);
+
+        final Path currentPath = mCurrentPath;
+        currentPath.rewind();
+
+        // TODO: the path should be created and cached every time we hit-detect a cell
+        // only the last segment of the path should be computed here
+        // draw the path of the pattern (unless the user is in progress, and
+        // we are in stealth mode)
+        final boolean drawPath = (!mInStealthMode || mPatternDisplayMode == DisplayMode.Wrong);
+        if (drawPath) {
+            boolean anyCircles = false;
+            for (int i = 0; i < count; i++) {
+                Cell cell = pattern.get(i);
+
+                // only draw the part of the pattern stored in
+                // the lookup table (this is only different in the case
+                // of animation).
+                if (!drawLookup[cell.row][cell.column]) {
+                    break;
+                }
+                anyCircles = true;
+
+                float centerX = getCenterXForColumn(cell.column);
+                float centerY = getCenterYForRow(cell.row);
+                if (i == 0) {
+                    currentPath.moveTo(centerX, centerY);
+                } else {
+                    currentPath.lineTo(centerX, centerY);
+                }
+            }
+
+            // add last in progress section
+            if ((mPatternInProgress || mPatternDisplayMode == DisplayMode.Animate)
+                    && anyCircles) {
+                currentPath.lineTo(mInProgressX, mInProgressY);
+            }
+            canvas.drawPath(currentPath, mPathPaint);
+        }
+
+        // draw the circles
+        final int paddingTop = mPaddingTop;
+        final int paddingLeft = mPaddingLeft;
+
+        for (int i = 0; i < 3; i++) {
+            float topY = paddingTop + i * squareHeight;
+            //float centerY = mPaddingTop + i * mSquareHeight + (mSquareHeight / 2);
+            for (int j = 0; j < 3; j++) {
+                float leftX = paddingLeft + j * squareWidth;
+                drawCircle(canvas, (int) leftX, (int) topY, drawLookup[i][j]);
+            }
+        }
+
+        // draw the arrows associated with the path (unless the user is in progress, and
+        // we are in stealth mode)
+        boolean oldFlag = (mPaint.getFlags() & Paint.FILTER_BITMAP_FLAG) != 0;
+        mPaint.setFilterBitmap(true); // draw with higher quality since we render with transforms
+        if (drawPath) {
+            for (int i = 0; i < count - 1; i++) {
+                Cell cell = pattern.get(i);
+                Cell next = pattern.get(i + 1);
+
+                // only draw the part of the pattern stored in
+                // the lookup table (this is only different in the case
+                // of animation).
+                if (!drawLookup[next.row][next.column]) {
+                    break;
+                }
+
+                float leftX = paddingLeft + cell.column * squareWidth;
+                float topY = paddingTop + cell.row * squareHeight;
+
+                drawArrow(canvas, leftX, topY, cell, next);
+            }
+        }
+        mPaint.setFilterBitmap(oldFlag); // restore default flag
+    }
+
+    private void drawArrow(Canvas canvas, float leftX, float topY, Cell start, Cell end) {
+        boolean green = mPatternDisplayMode != DisplayMode.Wrong;
+
+        final int endRow = end.row;
+        final int startRow = start.row;
+        final int endColumn = end.column;
+        final int startColumn = start.column;
+
+        // offsets for centering the bitmap in the cell
+        final int offsetX = ((int) mSquareWidth - mBitmapWidth) / 2;
+        final int offsetY = ((int) mSquareHeight - mBitmapHeight) / 2;
+
+        // compute transform to place arrow bitmaps at correct angle inside circle.
+        // This assumes that the arrow image is drawn at 12:00 with it's top edge
+        // coincident with the circle bitmap's top edge.
+        Bitmap arrow = green ? mBitmapArrowGreenUp : mBitmapArrowRedUp;
+        Matrix matrix = new Matrix();
+        final int cellWidth = mBitmapCircleDefault.getWidth();
+        final int cellHeight = mBitmapCircleDefault.getHeight();
+        
+        // the up arrow bitmap is at 12:00, so find the rotation from x axis and add 90 degrees.
+        final float theta = (float) Math.atan2(
+                (double) (endRow - startRow), (double) (endColumn - startColumn));
+        final float angle = (float) Math.toDegrees(theta) + 90.0f; 
+        
+        // compose matrix
+        matrix.setTranslate(leftX + offsetX, topY + offsetY); // transform to cell position
+        matrix.preRotate(angle, cellWidth / 2.0f, cellHeight / 2.0f);  // rotate about cell center
+        matrix.preTranslate((cellWidth - arrow.getWidth()) / 2.0f, 0.0f); // translate to 12:00 pos
+        canvas.drawBitmap(arrow, matrix, mPaint); 
+    }
+
+    /**
+     * @param canvas
+     * @param leftX
+     * @param topY
+     * @param partOfPattern Whether this circle is part of the pattern.
+     */
+    private void drawCircle(Canvas canvas, int leftX, int topY, boolean partOfPattern) {
+        Bitmap outerCircle;
+        Bitmap innerCircle;
+
+        if (!partOfPattern || (mInStealthMode && mPatternDisplayMode != DisplayMode.Wrong)) {
+            // unselected circle
+            outerCircle = mBitmapCircleDefault;
+            innerCircle = mBitmapBtnDefault;
+        } else if (mPatternInProgress) {
+            // user is in middle of drawing a pattern
+            outerCircle = mBitmapCircleGreen;
+            innerCircle = mBitmapBtnTouched;
+        } else if (mPatternDisplayMode == DisplayMode.Wrong) {
+            // the pattern is wrong
+            outerCircle = mBitmapCircleRed;
+            innerCircle = mBitmapBtnDefault;
+        } else if (mPatternDisplayMode == DisplayMode.Correct ||
+                mPatternDisplayMode == DisplayMode.Animate) {
+            // the pattern is correct
+            outerCircle = mBitmapCircleGreen;
+            innerCircle = mBitmapBtnDefault;
+        } else {
+            throw new IllegalStateException("unknown display mode " + mPatternDisplayMode);
+        }
+
+        final int width = mBitmapWidth;
+        final int height = mBitmapHeight;
+
+        final float squareWidth = mSquareWidth;
+        final float squareHeight = mSquareHeight;
+
+        int offsetX = (int) ((squareWidth - width) / 2f);
+        int offsetY = (int) ((squareHeight - height) / 2f);
+
+        canvas.drawBitmap(outerCircle, leftX + offsetX, topY + offsetY, mPaint);
+        canvas.drawBitmap(innerCircle, leftX + offsetX, topY + offsetY, mPaint);
+    }
+
+    @Override
+    protected Parcelable onSaveInstanceState() {
+        Parcelable superState = super.onSaveInstanceState();
+        return new SavedState(superState,
+                LockPatternUtils.patternToString(mPattern),
+                mPatternDisplayMode.ordinal(),
+                mInputEnabled, mInStealthMode, mTactileFeedbackEnabled);
+    }
+
+    @Override
+    protected void onRestoreInstanceState(Parcelable state) {
+        final SavedState ss = (SavedState) state;
+        super.onRestoreInstanceState(ss.getSuperState());
+        setPattern(
+                DisplayMode.Correct,
+                LockPatternUtils.stringToPattern(ss.getSerializedPattern()));
+        mPatternDisplayMode = DisplayMode.values()[ss.getDisplayMode()];
+        mInputEnabled = ss.isInputEnabled();
+        mInStealthMode = ss.isInStealthMode();
+        mTactileFeedbackEnabled = ss.isTactileFeedbackEnabled();
+    }
+
+    /**
+     * The parecelable for saving and restoring a lock pattern view.
+     */
+    private static class SavedState extends BaseSavedState {
+
+        private final String mSerializedPattern;
+        private final int mDisplayMode;
+        private final boolean mInputEnabled;
+        private final boolean mInStealthMode;
+        private final boolean mTactileFeedbackEnabled;
+
+        /**
+         * Constructor called from {@link LockPatternView#onSaveInstanceState()}
+         */
+        private SavedState(Parcelable superState, String serializedPattern, int displayMode,
+                boolean inputEnabled, boolean inStealthMode, boolean tactileFeedbackEnabled) {
+            super(superState);
+            mSerializedPattern = serializedPattern;
+            mDisplayMode = displayMode;
+            mInputEnabled = inputEnabled;
+            mInStealthMode = inStealthMode;
+            mTactileFeedbackEnabled = tactileFeedbackEnabled;
+        }
+
+        /**
+         * Constructor called from {@link #CREATOR}
+         */
+        private SavedState(Parcel in) {
+            super(in);
+            mSerializedPattern = in.readString();
+            mDisplayMode = in.readInt();
+            mInputEnabled = (Boolean) in.readValue(null);
+            mInStealthMode = (Boolean) in.readValue(null);
+            mTactileFeedbackEnabled = (Boolean) in.readValue(null);
+        }
+        
+        public String getSerializedPattern() {
+            return mSerializedPattern;
+        }
+
+        public int getDisplayMode() {
+            return mDisplayMode;
+        }
+
+        public boolean isInputEnabled() {
+            return mInputEnabled;
+        }
+
+        public boolean isInStealthMode() {
+            return mInStealthMode;
+        }
+
+        public boolean isTactileFeedbackEnabled(){
+            return mTactileFeedbackEnabled;
+        }
+
+        @Override
+        public void writeToParcel(Parcel dest, int flags) {
+            super.writeToParcel(dest, flags);
+            dest.writeString(mSerializedPattern);
+            dest.writeInt(mDisplayMode);
+            dest.writeValue(mInputEnabled);
+            dest.writeValue(mInStealthMode);
+            dest.writeValue(mTactileFeedbackEnabled);
+        }
+
+        public static final Parcelable.Creator<SavedState> CREATOR =
+                new Creator<SavedState>() {
+                    public SavedState createFromParcel(Parcel in) {
+                        return new SavedState(in);
+                    }
+
+                    public SavedState[] newArray(int size) {
+                        return new SavedState[size];
+                    }
+                };
+    }
+}
diff --git a/core/java/com/android/internal/widget/NumberPicker.java b/core/java/com/android/internal/widget/NumberPicker.java
new file mode 100644
index 0000000..1647c20
--- /dev/null
+++ b/core/java/com/android/internal/widget/NumberPicker.java
@@ -0,0 +1,406 @@
+/*
+ * 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.internal.widget;
+
+import android.content.Context;
+import android.os.Handler;
+import android.text.InputFilter;
+import android.text.InputType;
+import android.text.Spanned;
+import android.text.method.NumberKeyListener;
+import android.util.AttributeSet;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.View.OnFocusChangeListener;
+import android.view.View.OnLongClickListener;
+import android.widget.TextView;
+import android.widget.LinearLayout;
+
+import com.android.internal.R;
+
+public class NumberPicker extends LinearLayout implements OnClickListener,
+        OnFocusChangeListener, OnLongClickListener {
+        
+    public interface OnChangedListener {
+        void onChanged(NumberPicker picker, int oldVal, int newVal);
+    }
+
+    public interface Formatter {
+        String toString(int value);
+    }
+
+    /*
+     * Use a custom NumberPicker formatting callback to use two-digit
+     * minutes strings like "01".  Keeping a static formatter etc. is the
+     * most efficient way to do this; it avoids creating temporary objects
+     * on every call to format().
+     */
+    public static final NumberPicker.Formatter TWO_DIGIT_FORMATTER = 
+            new NumberPicker.Formatter() {
+                final StringBuilder mBuilder = new StringBuilder();
+                final java.util.Formatter mFmt = new java.util.Formatter(mBuilder);
+                final Object[] mArgs = new Object[1];
+                public String toString(int value) {
+                    mArgs[0] = value;
+                    mBuilder.delete(0, mBuilder.length());
+                    mFmt.format("%02d", mArgs);
+                    return mFmt.toString();
+                }
+        };
+    
+    private final Handler mHandler;
+    private final Runnable mRunnable = new Runnable() {
+        public void run() {
+            if (mIncrement) {
+                changeCurrent(mCurrent + 1);
+                mHandler.postDelayed(this, mSpeed);
+            } else if (mDecrement) {
+                changeCurrent(mCurrent - 1);
+                mHandler.postDelayed(this, mSpeed);
+            }
+        }
+    };
+
+    private final TextView mText;
+    private final InputFilter mNumberInputFilter;
+
+    private String[] mDisplayedValues;
+    private int mStart;
+    private int mEnd;
+    private int mCurrent;
+    private int mPrevious;
+    private OnChangedListener mListener;
+    private Formatter mFormatter;
+    private long mSpeed = 300;
+    
+    private boolean mIncrement;
+    private boolean mDecrement;
+    
+    public NumberPicker(Context context) {
+        this(context, null);
+    }
+    
+    public NumberPicker(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    @SuppressWarnings({"UnusedDeclaration"})
+    public NumberPicker(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs);
+        setOrientation(VERTICAL);
+        LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+        inflater.inflate(R.layout.number_picker, this, true);
+        mHandler = new Handler();
+        InputFilter inputFilter = new NumberPickerInputFilter();
+        mNumberInputFilter = new NumberRangeKeyListener();
+        mIncrementButton = (NumberPickerButton) findViewById(R.id.increment);
+        mIncrementButton.setOnClickListener(this);
+        mIncrementButton.setOnLongClickListener(this);
+        mIncrementButton.setNumberPicker(this);
+        mDecrementButton = (NumberPickerButton) findViewById(R.id.decrement);
+        mDecrementButton.setOnClickListener(this);
+        mDecrementButton.setOnLongClickListener(this);
+        mDecrementButton.setNumberPicker(this);
+        
+        mText = (TextView) findViewById(R.id.timepicker_input);
+        mText.setOnFocusChangeListener(this);
+        mText.setFilters(new InputFilter[] {inputFilter});
+        mText.setRawInputType(InputType.TYPE_CLASS_NUMBER);
+
+        if (!isEnabled()) {
+            setEnabled(false);
+        }
+    }
+    
+    @Override
+    public void setEnabled(boolean enabled) {
+        super.setEnabled(enabled);
+        mIncrementButton.setEnabled(enabled);
+        mDecrementButton.setEnabled(enabled);
+        mText.setEnabled(enabled);
+    }
+    
+    public void setOnChangeListener(OnChangedListener listener) {
+        mListener = listener;
+    }
+    
+    public void setFormatter(Formatter formatter) {
+        mFormatter = formatter;
+    }
+    
+    /**
+     * Set the range of numbers allowed for the number picker. The current
+     * value will be automatically set to the start.
+     * 
+     * @param start the start of the range (inclusive)
+     * @param end the end of the range (inclusive)
+     */
+    public void setRange(int start, int end) {
+        mStart = start;
+        mEnd = end;
+        mCurrent = start;
+        updateView();
+    }
+    
+    /**
+     * Set the range of numbers allowed for the number picker. The current
+     * value will be automatically set to the start. Also provide a mapping
+     * for values used to display to the user.
+     * 
+     * @param start the start of the range (inclusive)
+     * @param end the end of the range (inclusive)
+     * @param displayedValues the values displayed to the user.
+     */
+    public void setRange(int start, int end, String[] displayedValues) {
+        mDisplayedValues = displayedValues;
+        mStart = start;
+        mEnd = end;
+        mCurrent = start;
+        updateView();
+    }
+    
+    public void setCurrent(int current) {
+        mCurrent = current;
+        updateView();
+    }
+
+    /**
+     * The speed (in milliseconds) at which the numbers will scroll
+     * when the the +/- buttons are longpressed. Default is 300ms.
+     */
+    public void setSpeed(long speed) {
+        mSpeed = speed;
+    }
+    
+    public void onClick(View v) {
+        
+        /* The text view may still have focus so clear it's focus which will
+         * trigger the on focus changed and any typed values to be pulled.
+         */
+        mText.clearFocus();
+
+        // now perform the increment/decrement
+        if (R.id.increment == v.getId()) {
+            changeCurrent(mCurrent + 1);
+        } else if (R.id.decrement == v.getId()) {
+            changeCurrent(mCurrent - 1);
+        }
+    }
+    
+    private String formatNumber(int value) {
+        return (mFormatter != null)
+                ? mFormatter.toString(value)
+                : String.valueOf(value);
+    }
+ 
+    private void changeCurrent(int current) {
+        
+        // Wrap around the values if we go past the start or end
+        if (current > mEnd) {
+            current = mStart;
+        } else if (current < mStart) {
+            current = mEnd;
+        }
+        mPrevious = mCurrent;
+        mCurrent = current;
+        notifyChange();
+        updateView();
+    }
+    
+    private void notifyChange() {
+        if (mListener != null) {
+            mListener.onChanged(this, mPrevious, mCurrent);
+        }
+    }
+
+    private void updateView() {
+        
+        /* If we don't have displayed values then use the
+         * current number else find the correct value in the
+         * displayed values for the current number.
+         */
+        if (mDisplayedValues == null) {
+            mText.setText(formatNumber(mCurrent));
+        } else {
+            mText.setText(mDisplayedValues[mCurrent - mStart]);
+        }
+    }
+    
+    private void validateCurrentView(CharSequence str) {
+        int val = getSelectedPos(str.toString());
+        if ((val >= mStart) && (val <= mEnd)) {
+            mPrevious = mCurrent;
+            mCurrent = val;
+            notifyChange();
+        }
+        updateView();
+    }
+
+    public void onFocusChange(View v, boolean hasFocus) {
+        
+        /* When focus is lost check that the text field
+         * has valid values.
+         */
+        if (!hasFocus) {
+            String str = String.valueOf(((TextView) v).getText());
+            if ("".equals(str)) {
+                
+                // Restore to the old value as we don't allow empty values
+                updateView();
+            } else {
+                
+                // Check the new value and ensure it's in range
+                validateCurrentView(str);
+            }
+        }
+    }
+
+    /**
+     * We start the long click here but rely on the {@link NumberPickerButton}
+     * to inform us when the long click has ended.
+     */
+    public boolean onLongClick(View v) {
+        
+        /* The text view may still have focus so clear it's focus which will
+         * trigger the on focus changed and any typed values to be pulled.
+         */
+        mText.clearFocus();
+        
+        if (R.id.increment == v.getId()) {
+            mIncrement = true;
+            mHandler.post(mRunnable);
+        } else if (R.id.decrement == v.getId()) {
+            mDecrement = true;
+            mHandler.post(mRunnable);
+        }
+        return true;
+    }
+    
+    public void cancelIncrement() {
+        mIncrement = false;
+    }
+    
+    public void cancelDecrement() {
+        mDecrement = false;
+    }
+    
+    private static final char[] DIGIT_CHARACTERS = new char[] {
+        '0', '1', '2', '3', '4', '5', '6', '7', '8', '9'
+    };
+    
+    private NumberPickerButton mIncrementButton;
+    private NumberPickerButton mDecrementButton;
+    
+    private class NumberPickerInputFilter implements InputFilter {
+        public CharSequence filter(CharSequence source, int start, int end,
+                Spanned dest, int dstart, int dend) {
+            if (mDisplayedValues == null) {
+                return mNumberInputFilter.filter(source, start, end, dest, dstart, dend);
+            }
+            CharSequence filtered = String.valueOf(source.subSequence(start, end));
+            String result = String.valueOf(dest.subSequence(0, dstart))
+                    + filtered
+                    + dest.subSequence(dend, dest.length());
+            String str = String.valueOf(result).toLowerCase();
+            for (String val : mDisplayedValues) {
+                val = val.toLowerCase();
+                if (val.startsWith(str)) {
+                    return filtered;
+                }
+            }
+            return "";
+        }
+    }
+    
+    private class NumberRangeKeyListener extends NumberKeyListener {
+
+        // XXX This doesn't allow for range limits when controlled by a
+        // soft input method!
+        public int getInputType() {
+            return InputType.TYPE_CLASS_NUMBER;
+        }
+        
+        @Override
+        protected char[] getAcceptedChars() {
+            return DIGIT_CHARACTERS;
+        }
+        
+        @Override
+        public CharSequence filter(CharSequence source, int start, int end,
+                Spanned dest, int dstart, int dend) {
+
+            CharSequence filtered = super.filter(source, start, end, dest, dstart, dend);
+            if (filtered == null) {
+                filtered = source.subSequence(start, end);
+            }
+
+            String result = String.valueOf(dest.subSequence(0, dstart))
+                    + filtered
+                    + dest.subSequence(dend, dest.length());
+
+            if ("".equals(result)) {
+                return result;
+            }
+            int val = getSelectedPos(result);
+
+            /* Ensure the user can't type in a value greater
+             * than the max allowed. We have to allow less than min
+             * as the user might want to delete some numbers
+             * and then type a new number.
+             */
+            if (val > mEnd) {
+                return "";
+            } else {
+                return filtered;
+            }
+        }
+    }
+
+    private int getSelectedPos(String str) {
+        if (mDisplayedValues == null) {
+            return Integer.parseInt(str);
+        } else {
+            for (int i = 0; i < mDisplayedValues.length; i++) {
+                
+                /* Don't force the user to type in jan when ja will do */
+                str = str.toLowerCase();
+                if (mDisplayedValues[i].toLowerCase().startsWith(str)) {
+                    return mStart + i;
+                }
+            }
+            
+            /* The user might have typed in a number into the month field i.e.
+             * 10 instead of OCT so support that too.
+             */
+            try {
+                return Integer.parseInt(str);
+            } catch (NumberFormatException e) {
+                
+                /* Ignore as if it's not a number we don't care */
+            }
+        }
+        return mStart;
+    }
+
+    /**
+     * @return the current value.
+     */
+    public int getCurrent() {
+        return mCurrent;
+    }
+}
\ No newline at end of file
diff --git a/core/java/com/android/internal/widget/NumberPickerButton.java b/core/java/com/android/internal/widget/NumberPickerButton.java
new file mode 100644
index 0000000..39f1e2c
--- /dev/null
+++ b/core/java/com/android/internal/widget/NumberPickerButton.java
@@ -0,0 +1,86 @@
+/*
+ * 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.internal.widget;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+import android.widget.ImageButton;
+
+import com.android.internal.R;
+
+/**
+ * This class exists purely to cancel long click events.
+ */
+public class NumberPickerButton extends ImageButton {
+
+    private NumberPicker mNumberPicker;
+    
+    public NumberPickerButton(Context context, AttributeSet attrs,
+            int defStyle) {
+        super(context, attrs, defStyle);
+    }
+
+    public NumberPickerButton(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    public NumberPickerButton(Context context) {
+        super(context);
+    }
+    
+    public void setNumberPicker(NumberPicker picker) {
+        mNumberPicker = picker;
+    }
+    
+    @Override
+    public boolean onTouchEvent(MotionEvent event) {
+        cancelLongpressIfRequired(event);
+        return super.onTouchEvent(event);
+    }
+    
+    @Override
+    public boolean onTrackballEvent(MotionEvent event) {
+        cancelLongpressIfRequired(event);
+        return super.onTrackballEvent(event);
+    }
+    
+    @Override
+    public boolean onKeyUp(int keyCode, KeyEvent event) {
+        if ((keyCode == KeyEvent.KEYCODE_DPAD_CENTER)
+                || (keyCode == KeyEvent.KEYCODE_ENTER)) {
+            cancelLongpress();
+        }
+        return super.onKeyUp(keyCode, event);
+    }
+    
+    private void cancelLongpressIfRequired(MotionEvent event) {
+        if ((event.getAction() == MotionEvent.ACTION_CANCEL)
+                || (event.getAction() == MotionEvent.ACTION_UP)) {
+            cancelLongpress();
+        }
+    }
+
+    private void cancelLongpress() {
+        if (R.id.increment == getId()) {
+            mNumberPicker.cancelIncrement();
+        } else if (R.id.decrement == getId()) {
+            mNumberPicker.cancelDecrement();
+        }
+    }
+}
diff --git a/core/java/com/android/internal/widget/TextProgressBar.java b/core/java/com/android/internal/widget/TextProgressBar.java
new file mode 100644
index 0000000..aee7b76
--- /dev/null
+++ b/core/java/com/android/internal/widget/TextProgressBar.java
@@ -0,0 +1,180 @@
+/*
+ * 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.internal.widget;
+
+import android.content.Context;
+import android.os.SystemClock;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.Gravity;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.Chronometer;
+import android.widget.Chronometer.OnChronometerTickListener;
+import android.widget.ProgressBar;
+import android.widget.RelativeLayout;
+import android.widget.RemoteViews.RemoteView;
+
+/**
+ * Container that links together a {@link ProgressBar} and {@link Chronometer}
+ * as children. It subscribes to {@link Chronometer#OnChronometerTickListener}
+ * and updates the {@link ProgressBar} based on a preset finishing time.
+ * <p>
+ * This widget expects to contain two children with specific ids
+ * {@link android.R.id.progress} and {@link android.R.id.text1}.
+ * <p>
+ * If the {@link Chronometer} {@link android.R.attr#layout_width} is
+ * {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT}, then the
+ * {@link android.R.attr#gravity} will be used to automatically move it with
+ * respect to the {@link ProgressBar} position. For example, if
+ * {@link android.view.Gravity#LEFT} then the {@link Chronometer} will be placed
+ * just ahead of the leading edge of the {@link ProgressBar} position.
+ */
+@RemoteView
+public class TextProgressBar extends RelativeLayout implements OnChronometerTickListener {
+    public static final String TAG = "TextProgressBar"; 
+    
+    static final int CHRONOMETER_ID = android.R.id.text1;
+    static final int PROGRESSBAR_ID = android.R.id.progress;
+    
+    Chronometer mChronometer = null;
+    ProgressBar mProgressBar = null;
+    
+    long mDurationBase = -1;
+    int mDuration = -1;
+
+    boolean mChronometerFollow = false;
+    int mChronometerGravity = Gravity.NO_GRAVITY;
+    
+    public TextProgressBar(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+    }
+
+    public TextProgressBar(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    public TextProgressBar(Context context) {
+        super(context);
+    }
+
+    /**
+     * Catch any interesting children when they are added.
+     */
+    @Override
+    public void addView(View child, int index, ViewGroup.LayoutParams params) {
+        super.addView(child, index, params);
+        
+        int childId = child.getId();
+        if (childId == CHRONOMETER_ID && child instanceof Chronometer) {
+            mChronometer = (Chronometer) child;
+            mChronometer.setOnChronometerTickListener(this);
+            
+            // Check if Chronometer should move with with ProgressBar 
+            mChronometerFollow = (params.width == ViewGroup.LayoutParams.WRAP_CONTENT);
+            mChronometerGravity = (mChronometer.getGravity() & Gravity.HORIZONTAL_GRAVITY_MASK);
+            
+        } else if (childId == PROGRESSBAR_ID && child instanceof ProgressBar) {
+            mProgressBar = (ProgressBar) child;
+        }
+    }
+
+    /**
+     * Set the expected termination time of the running {@link Chronometer}.
+     * This value is used to adjust the {@link ProgressBar} against the elapsed
+     * time.
+     * <p>
+     * Call this <b>after</b> adjusting the {@link Chronometer} base, if
+     * necessary.
+     * 
+     * @param durationBase Use the {@link SystemClock#elapsedRealtime} time
+     *            base.
+     */
+    @android.view.RemotableViewMethod
+    public void setDurationBase(long durationBase) {
+        mDurationBase = durationBase;
+        
+        if (mProgressBar == null || mChronometer == null) {
+            throw new RuntimeException("Expecting child ProgressBar with id " +
+                    "'android.R.id.progress' and Chronometer id 'android.R.id.text1'");
+        }
+        
+        // Update the ProgressBar maximum relative to Chronometer base
+        mDuration = (int) (durationBase - mChronometer.getBase());
+        if (mDuration <= 0) {
+            mDuration = 1;
+        }
+        mProgressBar.setMax(mDuration);
+    }
+    
+    /**
+     * Callback when {@link Chronometer} changes, indicating that we should
+     * update the {@link ProgressBar} and change the layout if necessary.
+     */
+    public void onChronometerTick(Chronometer chronometer) {
+        if (mProgressBar == null) {
+            throw new RuntimeException(
+                "Expecting child ProgressBar with id 'android.R.id.progress'");
+        }
+        
+        // Stop Chronometer if we're past duration
+        long now = SystemClock.elapsedRealtime();
+        if (now >= mDurationBase) {
+            mChronometer.stop();
+        }
+
+        // Update the ProgressBar status
+        int remaining = (int) (mDurationBase - now);
+        mProgressBar.setProgress(mDuration - remaining);
+        
+        // Move the Chronometer if gravity is set correctly
+        if (mChronometerFollow) {
+            RelativeLayout.LayoutParams params;
+            
+            // Calculate estimate of ProgressBar leading edge position
+            params = (RelativeLayout.LayoutParams) mProgressBar.getLayoutParams();
+            int contentWidth = mProgressBar.getWidth() - (params.leftMargin + params.rightMargin);
+            int leadingEdge = ((contentWidth * mProgressBar.getProgress()) /
+                    mProgressBar.getMax()) + params.leftMargin;
+            
+            // Calculate any adjustment based on gravity
+            int adjustLeft = 0;
+            int textWidth = mChronometer.getWidth();
+            if (mChronometerGravity == Gravity.RIGHT) {
+                adjustLeft = -textWidth;
+            } else if (mChronometerGravity == Gravity.CENTER_HORIZONTAL) {
+                adjustLeft = -(textWidth / 2);
+            }
+            
+            // Limit margin to keep text inside ProgressBar bounds
+            leadingEdge += adjustLeft;
+            int rightLimit = contentWidth - params.rightMargin - textWidth;
+            if (leadingEdge < params.leftMargin) {
+                leadingEdge = params.leftMargin;
+            } else if (leadingEdge > rightLimit) {
+                leadingEdge = rightLimit;
+            }
+            
+            params = (RelativeLayout.LayoutParams) mChronometer.getLayoutParams();
+            params.leftMargin = leadingEdge;
+            
+            // Request layout to move Chronometer
+            mChronometer.requestLayout();
+            
+        }
+    }
+}
diff --git a/core/java/com/android/internal/widget/VerticalTextSpinner.java b/core/java/com/android/internal/widget/VerticalTextSpinner.java
new file mode 100644
index 0000000..50c528c
--- /dev/null
+++ b/core/java/com/android/internal/widget/VerticalTextSpinner.java
@@ -0,0 +1,467 @@
+/*
+ * 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.internal.widget;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.text.TextPaint;
+import android.util.AttributeSet;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+import android.view.View;
+
+
+public class VerticalTextSpinner extends View {
+
+    private static final int SELECTOR_ARROW_HEIGHT = 15;
+    
+    private static final int TEXT_SPACING = 18;
+    private static final int TEXT_MARGIN_RIGHT = 25;
+    private static final int TEXT_SIZE = 22;
+    
+    /* Keep the calculations as this is really a for loop from
+     * -2 to 2 but precalculated so we don't have to do in the onDraw.
+     */
+    private static final int TEXT1_Y = (TEXT_SIZE * (-2 + 2)) + (TEXT_SPACING * (-2 + 1));
+    private static final int TEXT2_Y = (TEXT_SIZE * (-1 + 2)) + (TEXT_SPACING * (-1 + 1));
+    private static final int TEXT3_Y = (TEXT_SIZE * (0 + 2)) + (TEXT_SPACING * (0 + 1));
+    private static final int TEXT4_Y = (TEXT_SIZE * (1 + 2)) + (TEXT_SPACING * (1 + 1));
+    private static final int TEXT5_Y = (TEXT_SIZE * (2 + 2)) + (TEXT_SPACING * (2 + 1));
+    
+    private static final int SCROLL_MODE_NONE = 0;
+    private static final int SCROLL_MODE_UP = 1;
+    private static final int SCROLL_MODE_DOWN = 2;
+    
+    private static final long DEFAULT_SCROLL_INTERVAL_MS = 400;
+    private static final int SCROLL_DISTANCE = TEXT_SIZE + TEXT_SPACING;
+    private static final int MIN_ANIMATIONS = 4;
+    
+    private final Drawable mBackgroundFocused;
+    private final Drawable mSelectorFocused;
+    private final Drawable mSelectorNormal;
+    private final int mSelectorDefaultY;
+    private final int mSelectorMinY;
+    private final int mSelectorMaxY;
+    private final int mSelectorHeight;
+    private final TextPaint mTextPaintDark;
+    private final TextPaint mTextPaintLight;
+    
+    private int mSelectorY;
+    private Drawable mSelector;
+    private int mDownY;
+    private boolean isDraggingSelector;
+    private int mScrollMode;
+    private long mScrollInterval;
+    private boolean mIsAnimationRunning;
+    private boolean mStopAnimation;
+    private boolean mWrapAround = true;
+    
+    private int mTotalAnimatedDistance;
+    private int mNumberOfAnimations;
+    private long mDelayBetweenAnimations;
+    private int mDistanceOfEachAnimation;
+    
+    private String[] mTextList;
+    private int mCurrentSelectedPos;
+    private OnChangedListener mListener;
+    
+    private String mText1;
+    private String mText2;
+    private String mText3;
+    private String mText4;
+    private String mText5;
+    
+    public interface OnChangedListener {
+        void onChanged(
+                VerticalTextSpinner spinner, int oldPos, int newPos, String[] items);
+    }
+    
+    public VerticalTextSpinner(Context context) {
+        this(context, null);
+    }
+    
+    public VerticalTextSpinner(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public VerticalTextSpinner(Context context, AttributeSet attrs,
+            int defStyle) {
+        super(context, attrs, defStyle);
+        
+        mBackgroundFocused = context.getResources().getDrawable(com.android.internal.R.drawable.pickerbox_background);
+        mSelectorFocused = context.getResources().getDrawable(com.android.internal.R.drawable.pickerbox_selected);
+        mSelectorNormal = context.getResources().getDrawable(com.android.internal.R.drawable.pickerbox_unselected);
+        
+        mSelectorHeight = mSelectorFocused.getIntrinsicHeight();
+        mSelectorDefaultY = (mBackgroundFocused.getIntrinsicHeight() - mSelectorHeight) / 2;
+        mSelectorMinY = 0;
+        mSelectorMaxY = mBackgroundFocused.getIntrinsicHeight() - mSelectorHeight;
+        
+        mSelector = mSelectorNormal;
+        mSelectorY = mSelectorDefaultY;
+        
+        mTextPaintDark = new TextPaint(Paint.ANTI_ALIAS_FLAG);
+        mTextPaintDark.setTextSize(TEXT_SIZE);
+        mTextPaintDark.setColor(context.getResources().getColor(com.android.internal.R.color.primary_text_light));
+        
+        mTextPaintLight = new TextPaint(Paint.ANTI_ALIAS_FLAG);
+        mTextPaintLight.setTextSize(TEXT_SIZE);
+        mTextPaintLight.setColor(context.getResources().getColor(com.android.internal.R.color.secondary_text_dark));
+        
+        mScrollMode = SCROLL_MODE_NONE;
+        mScrollInterval = DEFAULT_SCROLL_INTERVAL_MS;
+        calculateAnimationValues();
+    }
+    
+    public void setOnChangeListener(OnChangedListener listener) {
+        mListener = listener;
+    }
+    
+    public void setItems(String[] textList) {
+        mTextList = textList;
+        calculateTextPositions();
+    }
+    
+    public void setSelectedPos(int selectedPos) {
+        mCurrentSelectedPos = selectedPos;
+        calculateTextPositions();
+        postInvalidate();
+    }
+    
+    public void setScrollInterval(long interval) {
+        mScrollInterval = interval;
+        calculateAnimationValues();
+    }
+    
+    public void setWrapAround(boolean wrap) {
+        mWrapAround = wrap;
+    }
+    
+    @Override
+    public boolean onKeyDown(int keyCode, KeyEvent event) {
+        
+        /* This is a bit confusing, when we get the key event
+         * DPAD_DOWN we actually roll the spinner up. When the
+         * key event is DPAD_UP we roll the spinner down.
+         */
+        if ((keyCode == KeyEvent.KEYCODE_DPAD_UP) && canScrollDown()) {
+            mScrollMode = SCROLL_MODE_DOWN;
+            scroll();
+            mStopAnimation = true;
+            return true;
+        } else if ((keyCode == KeyEvent.KEYCODE_DPAD_DOWN) && canScrollUp()) {
+            mScrollMode = SCROLL_MODE_UP;
+            scroll();
+            mStopAnimation = true;
+            return true;
+        }
+        return super.onKeyDown(keyCode, event);
+    }
+
+    private boolean canScrollDown() {
+        return (mCurrentSelectedPos > 0) || mWrapAround;
+    }
+
+    private boolean canScrollUp() {
+        return ((mCurrentSelectedPos < (mTextList.length - 1)) || mWrapAround);
+    }
+    
+    @Override
+    protected void onFocusChanged(boolean gainFocus, int direction,
+            Rect previouslyFocusedRect) {
+        if (gainFocus) {
+            setBackgroundDrawable(mBackgroundFocused);
+            mSelector = mSelectorFocused;
+        } else {
+            setBackgroundDrawable(null);
+            mSelector = mSelectorNormal;
+            mSelectorY = mSelectorDefaultY;
+        }
+    }
+    
+    @Override
+    public boolean onTouchEvent(MotionEvent event) {        
+        final int action = event.getAction();
+        final int y = (int) event.getY();
+
+        switch (action) {
+        case MotionEvent.ACTION_DOWN:
+            requestFocus();
+            mDownY = y;
+            isDraggingSelector = (y >= mSelectorY) && (y <= (mSelectorY + mSelector.getIntrinsicHeight()));
+            break;
+
+        case MotionEvent.ACTION_MOVE:
+            if (isDraggingSelector) {
+                int top = mSelectorDefaultY + (y - mDownY);
+                if (top <= mSelectorMinY && canScrollDown()) {
+                    mSelectorY = mSelectorMinY;
+                    mStopAnimation = false;
+                    if (mScrollMode != SCROLL_MODE_DOWN) {
+                        mScrollMode = SCROLL_MODE_DOWN;
+                        scroll();
+                    }
+                } else if (top >= mSelectorMaxY && canScrollUp()) {
+                    mSelectorY = mSelectorMaxY;
+                    mStopAnimation = false;
+                    if (mScrollMode != SCROLL_MODE_UP) {
+                        mScrollMode = SCROLL_MODE_UP;
+                        scroll();
+                    }
+                } else {
+                    mSelectorY = top;
+                    mStopAnimation = true;
+                }
+            }
+            break;
+            
+        case MotionEvent.ACTION_UP:
+        case MotionEvent.ACTION_CANCEL:
+        default:
+            mSelectorY = mSelectorDefaultY;
+            mStopAnimation = true;
+            invalidate();
+            break;
+        }
+        return true;
+    }
+    
+    @Override
+    protected void onDraw(Canvas canvas) {
+        
+        /* The bounds of the selector */
+        final int selectorLeft = 0;
+        final int selectorTop = mSelectorY;
+        final int selectorRight = mMeasuredWidth;
+        final int selectorBottom = mSelectorY + mSelectorHeight;
+        
+        /* Draw the selector */
+        mSelector.setBounds(selectorLeft, selectorTop, selectorRight, selectorBottom);
+        mSelector.draw(canvas);
+        
+        if (mTextList == null) {
+            
+            /* We're not setup with values so don't draw anything else */
+            return;
+        }
+        
+        final TextPaint textPaintDark = mTextPaintDark;
+        if (hasFocus()) {
+            
+            /* The bounds of the top area where the text should be light */
+            final int topLeft = 0;
+            final int topTop = 0;
+            final int topRight = selectorRight;
+            final int topBottom = selectorTop + SELECTOR_ARROW_HEIGHT;
+
+            /* Assign a bunch of local finals for performance */
+            final String text1 = mText1;
+            final String text2 = mText2;
+            final String text3 = mText3;
+            final String text4 = mText4;
+            final String text5 = mText5;
+            final TextPaint textPaintLight = mTextPaintLight;
+            
+            /*
+             * Draw the 1st, 2nd and 3rd item in light only, clip it so it only
+             * draws in the area above the selector
+             */
+            canvas.save();
+            canvas.clipRect(topLeft, topTop, topRight, topBottom);
+            drawText(canvas, text1, TEXT1_Y
+                    + mTotalAnimatedDistance, textPaintLight);
+            drawText(canvas, text2, TEXT2_Y
+                    + mTotalAnimatedDistance, textPaintLight);
+            drawText(canvas, text3,
+                    TEXT3_Y + mTotalAnimatedDistance, textPaintLight);
+            canvas.restore();
+
+            /*
+             * Draw the 2nd, 3rd and 4th clipped to the selector bounds in dark
+             * paint
+             */
+            canvas.save();
+            canvas.clipRect(selectorLeft, selectorTop + SELECTOR_ARROW_HEIGHT,
+                    selectorRight, selectorBottom - SELECTOR_ARROW_HEIGHT);
+            drawText(canvas, text2, TEXT2_Y
+                    + mTotalAnimatedDistance, textPaintDark);
+            drawText(canvas, text3,
+                    TEXT3_Y + mTotalAnimatedDistance, textPaintDark);
+            drawText(canvas, text4,
+                    TEXT4_Y + mTotalAnimatedDistance, textPaintDark);
+            canvas.restore();
+
+            /* The bounds of the bottom area where the text should be light */
+            final int bottomLeft = 0;
+            final int bottomTop = selectorBottom - SELECTOR_ARROW_HEIGHT;
+            final int bottomRight = selectorRight;
+            final int bottomBottom = mMeasuredHeight;
+
+            /*
+             * Draw the 3rd, 4th and 5th in white text, clip it so it only draws
+             * in the area below the selector.
+             */
+            canvas.save();
+            canvas.clipRect(bottomLeft, bottomTop, bottomRight, bottomBottom);
+            drawText(canvas, text3,
+                    TEXT3_Y + mTotalAnimatedDistance, textPaintLight);
+            drawText(canvas, text4,
+                    TEXT4_Y + mTotalAnimatedDistance, textPaintLight);
+            drawText(canvas, text5,
+                    TEXT5_Y + mTotalAnimatedDistance, textPaintLight);
+            canvas.restore();
+            
+        } else {
+            drawText(canvas, mText3, TEXT3_Y, textPaintDark);
+        }
+        if (mIsAnimationRunning) {
+            if ((Math.abs(mTotalAnimatedDistance) + mDistanceOfEachAnimation) > SCROLL_DISTANCE) {
+                mTotalAnimatedDistance = 0;
+                if (mScrollMode == SCROLL_MODE_UP) {
+                    int oldPos = mCurrentSelectedPos;
+                    int newPos = getNewIndex(1);
+                    if (newPos >= 0) {
+                        mCurrentSelectedPos = newPos;
+                        if (mListener != null) {
+                            mListener.onChanged(this, oldPos, mCurrentSelectedPos, mTextList);
+                        }
+                    }
+                    if (newPos < 0 || ((newPos >= mTextList.length - 1) && !mWrapAround)) {
+                        mStopAnimation = true;
+                    }
+                    calculateTextPositions();
+                } else if (mScrollMode == SCROLL_MODE_DOWN) {
+                    int oldPos = mCurrentSelectedPos;
+                    int newPos = getNewIndex(-1);
+                    if (newPos >= 0) {
+                        mCurrentSelectedPos = newPos;
+                        if (mListener != null) {
+                            mListener.onChanged(this, oldPos, mCurrentSelectedPos, mTextList);
+                        }
+                    }
+                    if (newPos < 0 || (newPos == 0 && !mWrapAround)) {
+                        mStopAnimation = true;
+                    }
+                    calculateTextPositions();
+                }
+                if (mStopAnimation) {
+                    final int previousScrollMode = mScrollMode;
+                    
+                    /* No longer scrolling, we wait till the current animation
+                     * completes then we stop.
+                     */
+                    mIsAnimationRunning = false;
+                    mStopAnimation = false;
+                    mScrollMode = SCROLL_MODE_NONE;
+                    
+                    /* If the current selected item is an empty string
+                     * scroll past it.
+                     */
+                    if ("".equals(mTextList[mCurrentSelectedPos])) {
+                       mScrollMode = previousScrollMode;
+                       scroll();
+                       mStopAnimation = true;
+                    }
+                }
+            } else {
+                if (mScrollMode == SCROLL_MODE_UP) {
+                    mTotalAnimatedDistance -= mDistanceOfEachAnimation;
+                } else if (mScrollMode == SCROLL_MODE_DOWN) {
+                    mTotalAnimatedDistance += mDistanceOfEachAnimation;
+                }
+            }
+            if (mDelayBetweenAnimations > 0) {
+                postInvalidateDelayed(mDelayBetweenAnimations);
+            } else {
+                invalidate();
+            }
+        }
+    }
+
+    /**
+     * Called every time the text items or current position
+     * changes. We calculate store we don't have to calculate
+     * onDraw.
+     */
+    private void calculateTextPositions() {
+        mText1 = getTextToDraw(-2);
+        mText2 = getTextToDraw(-1);
+        mText3 = getTextToDraw(0);
+        mText4 = getTextToDraw(1);
+        mText5 = getTextToDraw(2);
+    }
+    
+    private String getTextToDraw(int offset) {
+        int index = getNewIndex(offset);
+        if (index < 0) {
+            return "";
+        }
+        return mTextList[index];
+    }
+
+    private int getNewIndex(int offset) {
+        int index = mCurrentSelectedPos + offset;
+        if (index < 0) {
+            if (mWrapAround) {
+                index += mTextList.length;
+            } else {
+                return -1;
+            }
+        } else if (index >= mTextList.length) {
+            if (mWrapAround) {
+                index -= mTextList.length;
+            } else {
+                return -1;
+            }
+        }
+        return index;
+    }
+    
+    private void scroll() {
+        if (mIsAnimationRunning) {
+            return;
+        }
+        mTotalAnimatedDistance = 0;
+        mIsAnimationRunning = true;
+        invalidate();
+    }
+
+    private void calculateAnimationValues() {
+        mNumberOfAnimations = (int) mScrollInterval / SCROLL_DISTANCE;
+        if (mNumberOfAnimations < MIN_ANIMATIONS) {
+            mNumberOfAnimations = MIN_ANIMATIONS;
+            mDistanceOfEachAnimation = SCROLL_DISTANCE / mNumberOfAnimations;
+            mDelayBetweenAnimations = 0;
+        } else {
+            mDistanceOfEachAnimation = SCROLL_DISTANCE / mNumberOfAnimations;
+            mDelayBetweenAnimations = mScrollInterval / mNumberOfAnimations;
+        }
+    }
+    
+    private void drawText(Canvas canvas, String text, int y, TextPaint paint) {
+        int width = (int) paint.measureText(text);
+        int x = getMeasuredWidth() - width - TEXT_MARGIN_RIGHT;
+        canvas.drawText(text, x, y, paint);
+    }
+    
+    public int getCurrentSelectedPos() {
+        return mCurrentSelectedPos;
+    }
+}
diff --git a/core/java/com/android/server/ResettableTimeout.java b/core/java/com/android/server/ResettableTimeout.java
new file mode 100644
index 0000000..ac5b160
--- /dev/null
+++ b/core/java/com/android/server/ResettableTimeout.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2007 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.server;
+
+import android.os.SystemClock;
+
+import android.os.ConditionVariable;
+
+/**
+ * Utility class that you can call on with a timeout, and get called back
+ * after a given time, dealing correctly with restarting the timeout.
+ *
+ * <p>For example, this class is used by the android.os.Vibrator class.
+ */
+abstract class ResettableTimeout
+{
+    /**
+     * Override this do what you need to do when it's starting
+     * This is called with the monitor on this method held, so be careful.
+     *
+     * @param alreadyOn is true if it's currently running
+     */
+    public abstract void on(boolean alreadyOn);
+
+    /**
+     * Override this to do what you need to do when it's stopping.
+     * This is called with the monitor on this method held, so be careful.
+     */
+    public abstract void off();
+
+    /**
+     * Does the following steps.
+     * <p>1. Call on()</p>
+     * <p>2. Start the timer.</p>
+     * <p>3. At the timeout, calls off()<p>
+     * <p>If you call this again, the timeout is reset to the new one</p>
+     */
+    public void go(long milliseconds)
+    {
+        synchronized (this) {
+            mOffAt = SystemClock.uptimeMillis() + milliseconds;
+
+            boolean alreadyOn;
+
+            // By starting the thread first and waiting, we ensure that if the
+            // thread to stop it can't start, we don't turn the vibrator on
+            // forever.  This still isn't really sufficient, because we don't
+            // have another processor watching us.  We really should have a
+            // service for this in case our process crashes.
+            if (mThread == null) {
+                alreadyOn = false;
+                mLock.close();
+                mThread = new T();
+                mThread.start();
+                mLock.block();
+                mOffCalled = false;
+            } else {
+                alreadyOn = true;
+                // poke the thread so it gets the new timeout.
+                mThread.interrupt();
+            }
+            on(alreadyOn);
+        }
+    }
+
+    /**
+     * Cancel the timeout and call off now.
+     */
+    public void cancel()
+    {
+        synchronized (this) {
+            mOffAt = 0;
+            if (mThread != null) {
+                mThread.interrupt();
+                mThread = null;
+            }
+            if (!mOffCalled) {
+                mOffCalled = true;
+                off();
+            }
+        }
+    }
+
+    private class T extends Thread
+    {
+        public void run()
+        {
+            mLock.open();
+            while (true) {
+                long diff;
+                synchronized (this) {
+                    diff = mOffAt - SystemClock.uptimeMillis();
+                    if (diff <= 0) {
+                        mOffCalled = true;
+                        off();
+                        mThread = null;
+                        break;
+                    }
+                }
+                try {
+                    sleep(diff);
+                }
+                catch (InterruptedException e) {
+                }
+            }
+        }
+    }
+
+    private ConditionVariable mLock = new ConditionVariable();
+
+    // turn it off at this time.
+    private volatile long mOffAt;
+    private volatile boolean mOffCalled;
+
+    private Thread mThread;
+
+}
+
diff --git a/core/java/com/google/android/collect/Lists.java b/core/java/com/google/android/collect/Lists.java
new file mode 100644
index 0000000..c029bb2
--- /dev/null
+++ b/core/java/com/google/android/collect/Lists.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2007 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.google.android.collect;
+
+import java.util.ArrayList;
+import java.util.Collections;
+
+/**
+ * Provides static methods for creating {@code List} instances easily, and other
+ * utility methods for working with lists. 
+ */
+public class Lists {
+
+    /**
+     * Creates an empty {@code ArrayList} instance.
+     *
+     * <p><b>Note:</b> if you only need an <i>immutable</i> empty List, use
+     * {@link Collections#emptyList} instead.
+     *
+     * @return a newly-created, initially-empty {@code ArrayList}
+     */
+    public static <E> ArrayList<E> newArrayList() {
+        return new ArrayList<E>();
+    }
+
+    /**
+     * Creates a resizable {@code ArrayList} instance containing the given
+     * elements.
+     *
+     * <p><b>Note:</b> due to a bug in javac 1.5.0_06, we cannot support the
+     * following:
+     *
+     * <p>{@code List<Base> list = Lists.newArrayList(sub1, sub2);}
+     *
+     * <p>where {@code sub1} and {@code sub2} are references to subtypes of
+     * {@code Base}, not of {@code Base} itself. To get around this, you must
+     * use:
+     *
+     * <p>{@code List<Base> list = Lists.<Base>newArrayList(sub1, sub2);}
+     *
+     * @param elements the elements that the list should contain, in order
+     * @return a newly-created {@code ArrayList} containing those elements
+     */
+    public static <E> ArrayList<E> newArrayList(E... elements) {
+        int capacity = (elements.length * 110) / 100 + 5;
+        ArrayList<E> list = new ArrayList<E>(capacity);
+        Collections.addAll(list, elements);
+        return list;
+    }
+}
diff --git a/core/java/com/google/android/collect/Maps.java b/core/java/com/google/android/collect/Maps.java
new file mode 100644
index 0000000..d537e0c
--- /dev/null
+++ b/core/java/com/google/android/collect/Maps.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2007 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.google.android.collect;
+
+import java.util.HashMap;
+
+/**
+ * Provides static methods for creating mutable {@code Maps} instances easily.
+ */
+public class Maps {
+    /**
+     * Creates a {@code HashMap} instance.
+     *
+     * @return a newly-created, initially-empty {@code HashMap}
+     */
+    public static <K, V> HashMap<K, V> newHashMap() {
+        return new HashMap<K, V>();
+    }
+}
diff --git a/core/java/com/google/android/collect/Sets.java b/core/java/com/google/android/collect/Sets.java
new file mode 100644
index 0000000..f5be0ec
--- /dev/null
+++ b/core/java/com/google/android/collect/Sets.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2007 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.google.android.collect;
+
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.HashSet;
+import java.util.SortedSet;
+import java.util.TreeSet;
+
+/**
+ * Provides static methods for creating mutable {@code Set} instances easily and
+ * other static methods for working with Sets.
+ *
+ */
+public class Sets {
+    
+    /**
+     * Creates an empty {@code HashSet} instance.
+     *
+     * <p><b>Note:</b> if {@code E} is an {@link Enum} type, use {@link
+     * EnumSet#noneOf} instead.
+     *
+     * <p><b>Note:</b> if you only need an <i>immutable</i> empty Set, 
+     * use {@link Collections#emptySet} instead.
+     *
+     * @return a newly-created, initially-empty {@code HashSet}
+     */
+    public static <K> HashSet<K> newHashSet() {
+        return new HashSet<K>();
+    }
+
+   /**
+    * Creates a {@code HashSet} instance containing the given elements.
+    *
+    * <p><b>Note:</b> due to a bug in javac 1.5.0_06, we cannot support the
+    * following:
+    *
+    * <p>{@code Set<Base> set = Sets.newHashSet(sub1, sub2);}
+    *
+    * <p>where {@code sub1} and {@code sub2} are references to subtypes of {@code
+    * Base}, not of {@code Base} itself. To get around this, you must use:
+    *
+    * <p>{@code Set<Base> set = Sets.<Base>newHashSet(sub1, sub2);}
+    *
+    * @param elements the elements that the set should contain
+    * @return a newly-created {@code HashSet} containing those elements (minus
+    *     duplicates)
+    */
+   public static <E> HashSet<E> newHashSet(E... elements) {
+     int capacity = elements.length * 4 / 3 + 1;
+     HashSet<E> set = new HashSet<E>(capacity);
+     Collections.addAll(set, elements);
+     return set;
+   }
+
+   /**
+    * Creates a {@code SortedSet} instance containing the given elements.
+    *
+    * @param elements the elements that the set should contain
+    * @return a newly-created {@code SortedSet} containing those elements (minus
+    *     duplicates)
+    */
+   public static <E> SortedSet<E> newSortedSet(E... elements) {
+       SortedSet<E> set = new TreeSet<E>();
+       Collections.addAll(set, elements);
+       return set;
+     }
+
+}
diff --git a/core/java/com/google/android/gdata/client/AndroidGDataClient.java b/core/java/com/google/android/gdata/client/AndroidGDataClient.java
new file mode 100644
index 0000000..fe7d860
--- /dev/null
+++ b/core/java/com/google/android/gdata/client/AndroidGDataClient.java
@@ -0,0 +1,480 @@
+// Copyright 2007 The Android Open Source Project
+
+package com.google.android.gdata.client;
+
+import com.google.android.net.GoogleHttpClient;
+import com.google.wireless.gdata.client.GDataClient;
+import com.google.wireless.gdata.client.HttpException;
+import com.google.wireless.gdata.client.QueryParams;
+import com.google.wireless.gdata.data.StringUtils;
+import com.google.wireless.gdata.parser.ParseException;
+import com.google.wireless.gdata.serializer.GDataSerializer;
+
+import org.apache.http.Header;
+import org.apache.http.HttpEntity;
+import org.apache.http.HttpResponse;
+import org.apache.http.StatusLine;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.client.methods.HttpUriRequest;
+import org.apache.http.entity.InputStreamEntity;
+import org.apache.http.entity.AbstractHttpEntity;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.net.http.AndroidHttpClient;
+import android.text.TextUtils;
+import android.util.Config;
+import android.util.Log;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.UnsupportedEncodingException;
+import java.io.BufferedInputStream;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.URLEncoder;
+
+/**
+ * Implementation of a GDataClient using GoogleHttpClient to make HTTP
+ * requests.  Always issues GETs and POSTs, using the X-HTTP-Method-Override
+ * header when a PUT or DELETE is desired, to avoid issues with firewalls, etc.,
+ * that do not allow methods other than GET or POST.
+ */
+public class AndroidGDataClient implements GDataClient {
+
+    private static final String TAG = "GDataClient";
+    private static final boolean DEBUG = false;
+    private static final boolean LOCAL_LOGV = DEBUG ? Config.LOGD : Config.LOGV;
+
+    private static final String X_HTTP_METHOD_OVERRIDE =
+        "X-HTTP-Method-Override";
+
+    private static final String USER_AGENT_APP_VERSION = "Android-GData/1.0";
+
+    private static final int MAX_REDIRECTS = 10;
+
+    private final GoogleHttpClient mHttpClient;
+    private ContentResolver mResolver;
+
+    /**
+     * Interface for creating HTTP requests.  Used by
+     * {@link AndroidGDataClient#createAndExecuteMethod}, since HttpUriRequest does not allow for
+     * changing the URI after creation, e.g., when you want to follow a redirect.
+     */
+    private interface HttpRequestCreator {
+        HttpUriRequest createRequest(URI uri);
+    }
+
+    private static class GetRequestCreator implements HttpRequestCreator {
+        public GetRequestCreator() {
+        }
+
+        public HttpUriRequest createRequest(URI uri) {
+            HttpGet get = new HttpGet(uri);
+            return get;
+        }
+    }
+
+    private static class PostRequestCreator implements HttpRequestCreator {
+        private final String mMethodOverride;
+        private final HttpEntity mEntity;
+        public PostRequestCreator(String methodOverride, HttpEntity entity) {
+            mMethodOverride = methodOverride;
+            mEntity = entity;
+        }
+
+        public HttpUriRequest createRequest(URI uri) {
+            HttpPost post = new HttpPost(uri);
+            if (mMethodOverride != null) {
+                post.addHeader(X_HTTP_METHOD_OVERRIDE, mMethodOverride);
+            }
+            post.setEntity(mEntity);
+            return post;
+        }
+    }
+
+    // MAJOR TODO: make this work across redirects (if we can reset the InputStream).
+    // OR, read the bits into a local buffer (yuck, the media could be large).
+    private static class MediaPutRequestCreator implements HttpRequestCreator {
+        private final InputStream mMediaInputStream;
+        private final String mContentType;
+        public MediaPutRequestCreator(InputStream mediaInputStream, String contentType) {
+            mMediaInputStream = mediaInputStream;
+            mContentType = contentType;
+        }
+
+        public HttpUriRequest createRequest(URI uri) {
+            HttpPost post = new HttpPost(uri);
+            post.addHeader(X_HTTP_METHOD_OVERRIDE, "PUT");
+            // mMediaInputStream.reset();
+            InputStreamEntity entity = new InputStreamEntity(mMediaInputStream,
+                    -1 /* read until EOF */);
+            entity.setContentType(mContentType);
+            post.setEntity(entity);
+            return post;
+        }
+    }
+
+    /**
+     * @deprecated Use AndroidGDAtaClient(Context) instead.
+     */
+    public AndroidGDataClient(ContentResolver resolver) {
+        mHttpClient = new GoogleHttpClient(resolver, USER_AGENT_APP_VERSION,
+                true /* gzip capable */);
+        mHttpClient.enableCurlLogging(TAG, Log.VERBOSE);
+        mResolver = resolver;
+    }
+
+    /**
+     * Creates a new AndroidGDataClient.
+     * 
+     * @param context The ContentResolver to get URL rewriting rules from
+     * through the Android proxy server, using null to indicate not using proxy.
+     * The context will also be used by GoogleHttpClient for configuration of 
+     * SSL session persistence.
+     */
+    public AndroidGDataClient(Context context) {
+        mHttpClient = new GoogleHttpClient(context, USER_AGENT_APP_VERSION,
+                true /* gzip capable */);
+        mHttpClient.enableCurlLogging(TAG, Log.VERBOSE);
+        mResolver = context.getContentResolver();
+    }
+
+    public void close() {
+        mHttpClient.close();
+    }
+
+    /*
+     * (non-Javadoc)
+     * @see GDataClient#encodeUri(java.lang.String)
+     */
+    public String encodeUri(String uri) {
+        String encodedUri;
+        try {
+            encodedUri = URLEncoder.encode(uri, "UTF-8");
+        } catch (UnsupportedEncodingException uee) {
+            // should not happen.
+            Log.e("JakartaGDataClient",
+                  "UTF-8 not supported -- should not happen.  "
+                  + "Using default encoding.", uee);
+            encodedUri = URLEncoder.encode(uri);
+        }
+        return encodedUri;
+    }
+
+    /*
+     * (non-Javadoc)
+     * @see com.google.wireless.gdata.client.GDataClient#createQueryParams()
+     */
+    public QueryParams createQueryParams() {
+        return new QueryParamsImpl();
+    }
+
+    // follows redirects
+    private InputStream createAndExecuteMethod(HttpRequestCreator creator,
+                                               String uriString,
+                                               String authToken)
+        throws HttpException, IOException {
+
+        HttpResponse response = null;
+        int status = 500;
+        int redirectsLeft = MAX_REDIRECTS;
+
+        URI uri;
+        try {
+            uri = new URI(uriString);
+        } catch (URISyntaxException use) {
+            Log.w(TAG, "Unable to parse " + uriString + " as URI.", use);
+            throw new IOException("Unable to parse " + uriString + " as URI: "
+                    + use.getMessage());
+        }
+
+        // we follow redirects ourselves, since we want to follow redirects even on POSTs, which
+        // the HTTP library does not do.  following redirects ourselves also allows us to log
+        // the redirects using our own logging.
+        while (redirectsLeft > 0) {
+
+            HttpUriRequest request = creator.createRequest(uri);
+
+            AndroidHttpClient.modifyRequestToAcceptGzipResponse(request);
+            // only add the auth token if not null (to allow for GData feeds that do not require
+            // authentication.)
+            if (!TextUtils.isEmpty(authToken)) {
+                request.addHeader("Authorization", "GoogleLogin auth=" + authToken);
+            }
+            if (LOCAL_LOGV) {
+                for (Header h : request.getAllHeaders()) {
+                    Log.v(TAG, h.getName() + ": " + h.getValue());
+                }
+            }
+
+            if (Log.isLoggable(TAG, Log.DEBUG)) {
+                    Log.d(TAG, "Executing " + request.getRequestLine().toString());
+            }
+
+            response = null;
+
+            try {
+                response = mHttpClient.execute(request);
+            } catch (IOException ioe) {
+                Log.w(TAG, "Unable to execute HTTP request." + ioe);
+                throw ioe;
+            }
+
+            StatusLine statusLine = response.getStatusLine();
+            if (statusLine == null) {
+                Log.w(TAG, "StatusLine is null.");
+                throw new NullPointerException("StatusLine is null -- should not happen.");
+            }
+
+            if (Log.isLoggable(TAG, Log.DEBUG)) {
+                Log.d(TAG, response.getStatusLine().toString());
+                for (Header h : response.getAllHeaders()) {
+                    Log.d(TAG, h.getName() + ": " + h.getValue());
+                }
+            }
+            status = statusLine.getStatusCode();
+
+            HttpEntity entity = response.getEntity();
+
+            if ((status >= 200) && (status < 300) && entity != null) {
+                InputStream in = AndroidHttpClient.getUngzippedContent(entity);
+                if (Log.isLoggable(TAG, Log.DEBUG)) {
+                    in = logInputStreamContents(in);
+                }
+                return in;
+            }
+
+            // TODO: handle 301, 307?
+            // TODO: let the http client handle the redirects, if we can be sure we'll never get a
+            // redirect on POST.
+            if (status == 302) {
+                // consume the content, so the connection can be closed.
+                entity.consumeContent();
+                Header location = response.getFirstHeader("Location");
+                if (location == null) {
+                    if (Log.isLoggable(TAG, Log.DEBUG)) {
+                        Log.d(TAG, "Redirect requested but no Location "
+                                + "specified.");
+                    }
+                    break;
+                }
+                if (Log.isLoggable(TAG, Log.DEBUG)) {
+                    Log.d(TAG, "Following redirect to " + location.getValue());
+                }
+                try {
+                    uri = new URI(location.getValue());
+                } catch (URISyntaxException use) {
+                    if (Log.isLoggable(TAG, Log.DEBUG)) {
+                        Log.d(TAG, "Unable to parse " + location.getValue() + " as URI.", use);
+                        throw new IOException("Unable to parse " + location.getValue()
+                                + " as URI.");
+                    }
+                    break;
+                }
+                --redirectsLeft;
+            } else {
+                break;
+            }
+        }
+
+        if (Log.isLoggable(TAG, Log.VERBOSE)) {
+            Log.v(TAG, "Received " + status + " status code.");
+        }
+        String errorMessage = null;
+        HttpEntity entity = response.getEntity();
+        try {
+            if (response != null && entity != null) {
+                InputStream in = AndroidHttpClient.getUngzippedContent(entity);
+                ByteArrayOutputStream baos = new ByteArrayOutputStream();
+                byte[] buf = new byte[8192];
+                int bytesRead = -1;
+                while ((bytesRead = in.read(buf)) != -1) {
+                    baos.write(buf, 0, bytesRead);
+                }
+                // TODO: use appropriate encoding, picked up from Content-Type.
+                errorMessage = new String(baos.toByteArray());
+                if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                    Log.v(TAG, errorMessage);
+                }
+            }
+        } finally {
+            if (entity != null) {
+                entity.consumeContent();
+            }
+        }
+        String exceptionMessage = "Received " + status + " status code";
+        if (errorMessage != null) {
+            exceptionMessage += (": " + errorMessage);
+        }
+        throw new HttpException(exceptionMessage, status, null /* InputStream */);
+    }
+
+    /*
+     * (non-Javadoc)
+     * @see GDataClient#getFeedAsStream(java.lang.String, java.lang.String)
+     */
+    public InputStream getFeedAsStream(String feedUrl,
+                                       String authToken)
+        throws HttpException, IOException {
+
+        InputStream in = createAndExecuteMethod(new GetRequestCreator(), feedUrl, authToken);
+        if (in != null) {
+            return in;
+        }
+        throw new IOException("Unable to access feed.");
+    }
+
+    /**
+     * Log the contents of the input stream.
+     * The original input stream is consumed, so the caller must use the
+     * BufferedInputStream that is returned.
+     * @param in InputStream
+     * @return replacement input stream for caller to use
+     * @throws IOException
+     */
+    private InputStream logInputStreamContents(InputStream in) throws IOException {
+        if (in == null) {
+            return in;
+        }
+        // bufferSize is the (arbitrary) maximum amount to log.
+        // The original InputStream is wrapped in a
+        // BufferedInputStream with a 16K buffer.  This lets
+        // us read up to 16K, write it to the log, and then
+        // reset the stream so the the original client can
+        // then read the data.  The BufferedInputStream
+        // provides the mark and reset support, even when
+        // the original InputStream does not.
+        final int bufferSize = 16384;
+        BufferedInputStream bin = new BufferedInputStream(in, bufferSize);
+        bin.mark(bufferSize);
+        int wanted = bufferSize;
+        int totalReceived = 0;
+        byte buf[] = new byte[wanted];
+        while (wanted > 0) {
+            int got = bin.read(buf, totalReceived, wanted);
+            if (got <= 0) break; // EOF
+            wanted -= got;
+            totalReceived += got;
+        }
+        Log.d(TAG, new String(buf, 0, totalReceived, "UTF-8"));
+        bin.reset();
+        return bin;
+    }
+
+    public InputStream getMediaEntryAsStream(String mediaEntryUrl, String authToken)
+            throws HttpException, IOException {
+
+        InputStream in = createAndExecuteMethod(new GetRequestCreator(), mediaEntryUrl, authToken);
+
+        if (in != null) {
+            return in;
+        }
+        throw new IOException("Unable to access media entry.");
+    }
+
+    /* (non-Javadoc)
+    * @see GDataClient#createEntry
+    */
+    public InputStream createEntry(String feedUrl,
+                                   String authToken,
+                                   GDataSerializer entry)
+        throws HttpException, IOException {
+
+        HttpEntity entity = createEntityForEntry(entry, GDataSerializer.FORMAT_CREATE);
+        InputStream in = createAndExecuteMethod(
+                new PostRequestCreator(null /* override */, entity),
+                feedUrl,
+                authToken);
+        if (in != null) {
+            return in;
+        }
+        throw new IOException("Unable to create entry.");
+    }
+
+    /* (non-Javadoc)
+     * @see GDataClient#updateEntry
+     */
+    public InputStream updateEntry(String editUri,
+                                   String authToken,
+                                   GDataSerializer entry)
+        throws HttpException, IOException {
+        HttpEntity entity = createEntityForEntry(entry, GDataSerializer.FORMAT_UPDATE);
+        InputStream in = createAndExecuteMethod(
+                new PostRequestCreator("PUT", entity),
+                editUri,
+                authToken);
+        if (in != null) {
+            return in;
+        }
+        throw new IOException("Unable to update entry.");
+    }
+
+    /* (non-Javadoc)
+     * @see GDataClient#deleteEntry
+     */
+    public void deleteEntry(String editUri, String authToken)
+        throws HttpException, IOException {
+        if (StringUtils.isEmpty(editUri)) {
+            throw new IllegalArgumentException(
+                    "you must specify an non-empty edit url");
+        }
+        InputStream in =
+            createAndExecuteMethod(
+                    new PostRequestCreator("DELETE", null /* entity */),
+                    editUri,
+                    authToken);
+        if (in == null) {
+            throw new IOException("Unable to delete entry.");
+        }
+        try {
+            in.close();
+        } catch (IOException ioe) {
+            // ignore
+        }
+    }
+
+    public InputStream updateMediaEntry(String editUri, String authToken,
+            InputStream mediaEntryInputStream, String contentType)
+        throws HttpException, IOException {
+        InputStream in = createAndExecuteMethod(
+                new MediaPutRequestCreator(mediaEntryInputStream, contentType),
+                editUri,
+                authToken);
+        if (in != null) {
+            return in;
+        }
+        throw new IOException("Unable to write media entry.");
+    }
+
+    private HttpEntity createEntityForEntry(GDataSerializer entry, int format) throws IOException {
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        try {
+            entry.serialize(baos, format);
+        } catch (IOException ioe) {
+            Log.e(TAG, "Unable to serialize entry.", ioe);
+            throw ioe;
+        } catch (ParseException pe) {
+            Log.e(TAG, "Unable to serialize entry.", pe);
+            throw new IOException("Unable to serialize entry: " + pe.getMessage());
+        }
+
+        byte[] entryBytes = baos.toByteArray();
+
+        if (entryBytes != null && Log.isLoggable(TAG, Log.DEBUG)) {
+            try {
+                Log.d(TAG, "Serialized entry: " + new String(entryBytes, "UTF-8"));
+            } catch (UnsupportedEncodingException uee) {
+                // should not happen
+                throw new IllegalStateException("UTF-8 should be supported!",
+                        uee);
+            }
+        }
+
+        AbstractHttpEntity entity = AndroidHttpClient.getCompressedEntity(entryBytes, mResolver);
+        entity.setContentType(entry.getContentType());
+        return entity;
+    }
+}
diff --git a/core/java/com/google/android/gdata/client/AndroidXmlParserFactory.java b/core/java/com/google/android/gdata/client/AndroidXmlParserFactory.java
new file mode 100644
index 0000000..a308fc0
--- /dev/null
+++ b/core/java/com/google/android/gdata/client/AndroidXmlParserFactory.java
@@ -0,0 +1,31 @@
+package com.google.android.gdata.client;
+
+import com.google.wireless.gdata.parser.xml.XmlParserFactory;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
+import android.util.Xml;
+
+/**
+ * XmlParserFactory for the Android platform.
+ */
+public class AndroidXmlParserFactory implements XmlParserFactory {
+
+    /*
+     * (non-javadoc)
+     * @see XmlParserFactory#createParser
+     */
+    public XmlPullParser createParser() throws XmlPullParserException {
+        return Xml.newPullParser();
+    }
+
+    /*
+     * (non-javadoc)
+     * @see XmlParserFactory#createSerializer
+     */
+    public XmlSerializer createSerializer() throws XmlPullParserException {
+        return Xml.newSerializer();
+    }
+}
diff --git a/core/java/com/google/android/gdata/client/QueryParamsImpl.java b/core/java/com/google/android/gdata/client/QueryParamsImpl.java
new file mode 100644
index 0000000..e27b36f
--- /dev/null
+++ b/core/java/com/google/android/gdata/client/QueryParamsImpl.java
@@ -0,0 +1,100 @@
+package com.google.android.gdata.client;
+
+import com.google.wireless.gdata.client.QueryParams;
+
+import android.text.TextUtils;
+import android.util.Log;
+
+import java.io.UnsupportedEncodingException;
+import java.net.URLEncoder;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Simple implementation of the QueryParams interface.
+ */
+// TODO: deal with categories
+public class QueryParamsImpl extends QueryParams {
+
+    private final Map<String,String> mParams = new HashMap<String,String>();
+
+    /**
+     * Creates a new empty QueryParamsImpl.
+     */
+    public QueryParamsImpl() {
+    }
+
+    @Override
+    public void clear() {
+        setEntryId(null);
+        mParams.clear();
+    }
+
+    @Override
+    public String generateQueryUrl(String feedUrl) {
+
+        if (TextUtils.isEmpty(getEntryId()) &&
+            mParams.isEmpty()) {
+            // nothing to do
+            return feedUrl;
+        }
+
+        // handle entry IDs
+        if (!TextUtils.isEmpty(getEntryId())) {
+            if (!mParams.isEmpty()) {
+                throw new IllegalStateException("Cannot set both an entry ID "
+                        + "and other query paramters.");
+            }
+            return feedUrl + '/' + getEntryId();
+        }
+
+        // otherwise, append the querystring params.
+        StringBuilder sb = new StringBuilder();
+        sb.append(feedUrl);
+        Set<String> params = mParams.keySet();
+        boolean first = true;
+        if (feedUrl.contains("?")) {
+            first = false;
+        } else {
+            sb.append('?');
+        }
+        for (String param : params) {
+            if (first) {
+                first = false;
+            } else {
+                sb.append('&');
+            }
+            sb.append(param);
+            sb.append('=');
+            String value = mParams.get(param);
+            String encodedValue = null;
+
+            try {
+                encodedValue = URLEncoder.encode(value, "UTF-8");
+            } catch (UnsupportedEncodingException uee) {
+                // should not happen.
+                Log.w("QueryParamsImpl",
+                      "UTF-8 not supported -- should not happen.  "
+                      + "Using default encoding.", uee);
+                encodedValue = URLEncoder.encode(value);
+            }
+            sb.append(encodedValue);
+        }
+        return sb.toString();
+    }
+
+    @Override
+    public String getParamValue(String param) {
+        if (!(mParams.containsKey(param))) {
+            return null;
+        }
+        return mParams.get(param);
+    }
+
+    @Override
+    public void setParamValue(String param, String value) {
+        mParams.put(param, value);
+    }
+
+}
diff --git a/core/java/com/google/android/mms/ContentType.java b/core/java/com/google/android/mms/ContentType.java
new file mode 100644
index 0000000..94bc9fd
--- /dev/null
+++ b/core/java/com/google/android/mms/ContentType.java
@@ -0,0 +1,222 @@
+/*
+ * Copyright (C) 2007-2008 Esmertec AG.
+ * Copyright (C) 2007-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.google.android.mms;
+
+import java.util.ArrayList;
+
+public class ContentType {
+    public static final String MMS_MESSAGE       = "application/vnd.wap.mms-message";
+    // The phony content type for generic PDUs (e.g. ReadOrig.ind,
+    // Notification.ind, Delivery.ind).
+    public static final String MMS_GENERIC       = "application/vnd.wap.mms-generic";
+    public static final String MULTIPART_MIXED   = "application/vnd.wap.multipart.mixed";
+    public static final String MULTIPART_RELATED = "application/vnd.wap.multipart.related";
+
+    public static final String TEXT_PLAIN        = "text/plain";
+    public static final String TEXT_HTML         = "text/html";
+    public static final String TEXT_VCALENDAR    = "text/x-vCalendar";
+    public static final String TEXT_VCARD        = "text/x-vCard";
+
+    public static final String IMAGE_UNSPECIFIED = "image/*";
+    public static final String IMAGE_JPEG        = "image/jpeg";
+    public static final String IMAGE_JPG         = "image/jpg";
+    public static final String IMAGE_GIF         = "image/gif";
+    public static final String IMAGE_WBMP        = "image/vnd.wap.wbmp";
+    public static final String IMAGE_PNG         = "image/png";
+
+    public static final String AUDIO_UNSPECIFIED = "audio/*";
+    public static final String AUDIO_AAC         = "audio/aac";
+    public static final String AUDIO_AMR         = "audio/amr";
+    public static final String AUDIO_IMELODY     = "audio/imelody";
+    public static final String AUDIO_MID         = "audio/mid";
+    public static final String AUDIO_MIDI        = "audio/midi";
+    public static final String AUDIO_MP3         = "audio/mp3";
+    public static final String AUDIO_MPEG3       = "audio/mpeg3";
+    public static final String AUDIO_MPEG        = "audio/mpeg";
+    public static final String AUDIO_MPG         = "audio/mpg";
+    public static final String AUDIO_MP4         = "audio/mp4";
+    public static final String AUDIO_X_MID       = "audio/x-mid";
+    public static final String AUDIO_X_MIDI      = "audio/x-midi";
+    public static final String AUDIO_X_MP3       = "audio/x-mp3";
+    public static final String AUDIO_X_MPEG3     = "audio/x-mpeg3";
+    public static final String AUDIO_X_MPEG      = "audio/x-mpeg";
+    public static final String AUDIO_X_MPG       = "audio/x-mpg";
+    public static final String AUDIO_3GPP        = "audio/3gpp";
+    public static final String AUDIO_OGG         = "application/ogg";
+
+    public static final String VIDEO_UNSPECIFIED = "video/*";
+    public static final String VIDEO_3GPP        = "video/3gpp";
+    public static final String VIDEO_3G2         = "video/3gpp2";
+    public static final String VIDEO_H263        = "video/h263";
+    public static final String VIDEO_MP4         = "video/mp4";
+
+    public static final String APP_SMIL          = "application/smil";
+    public static final String APP_WAP_XHTML     = "application/vnd.wap.xhtml+xml";
+    public static final String APP_XHTML         = "application/xhtml+xml";
+
+    public static final String APP_DRM_CONTENT   = "application/vnd.oma.drm.content";
+    public static final String APP_DRM_MESSAGE   = "application/vnd.oma.drm.message";
+
+    private static final ArrayList<String> sSupportedContentTypes = new ArrayList<String>();
+    private static final ArrayList<String> sSupportedImageTypes = new ArrayList<String>();
+    private static final ArrayList<String> sSupportedAudioTypes = new ArrayList<String>();
+    private static final ArrayList<String> sSupportedVideoTypes = new ArrayList<String>();
+
+    static {
+        sSupportedContentTypes.add(TEXT_PLAIN);
+        sSupportedContentTypes.add(TEXT_HTML);
+        sSupportedContentTypes.add(TEXT_VCALENDAR);
+        sSupportedContentTypes.add(TEXT_VCARD);
+
+        sSupportedContentTypes.add(IMAGE_JPEG);
+        sSupportedContentTypes.add(IMAGE_GIF);
+        sSupportedContentTypes.add(IMAGE_WBMP);
+        sSupportedContentTypes.add(IMAGE_PNG);
+        sSupportedContentTypes.add(IMAGE_JPG);
+        //supportedContentTypes.add(IMAGE_SVG); not yet supported.
+
+        sSupportedContentTypes.add(AUDIO_AAC);
+        sSupportedContentTypes.add(AUDIO_AMR);
+        sSupportedContentTypes.add(AUDIO_IMELODY);
+        sSupportedContentTypes.add(AUDIO_MID);
+        sSupportedContentTypes.add(AUDIO_MIDI);
+        sSupportedContentTypes.add(AUDIO_MP3);
+        sSupportedContentTypes.add(AUDIO_MPEG3);
+        sSupportedContentTypes.add(AUDIO_MPEG);
+        sSupportedContentTypes.add(AUDIO_MPG);
+        sSupportedContentTypes.add(AUDIO_X_MID);
+        sSupportedContentTypes.add(AUDIO_X_MIDI);
+        sSupportedContentTypes.add(AUDIO_X_MP3);
+        sSupportedContentTypes.add(AUDIO_X_MPEG3);
+        sSupportedContentTypes.add(AUDIO_X_MPEG);
+        sSupportedContentTypes.add(AUDIO_X_MPG);
+        sSupportedContentTypes.add(AUDIO_3GPP);
+        sSupportedContentTypes.add(AUDIO_OGG);
+
+        sSupportedContentTypes.add(VIDEO_3GPP);
+        sSupportedContentTypes.add(VIDEO_3G2);
+        sSupportedContentTypes.add(VIDEO_H263);
+        sSupportedContentTypes.add(VIDEO_MP4);
+
+        sSupportedContentTypes.add(APP_SMIL);
+        sSupportedContentTypes.add(APP_WAP_XHTML);
+        sSupportedContentTypes.add(APP_XHTML);
+
+        sSupportedContentTypes.add(APP_DRM_CONTENT);
+        sSupportedContentTypes.add(APP_DRM_MESSAGE);
+
+        // add supported image types
+        sSupportedImageTypes.add(IMAGE_JPEG);
+        sSupportedImageTypes.add(IMAGE_GIF);
+        sSupportedImageTypes.add(IMAGE_WBMP);
+        sSupportedImageTypes.add(IMAGE_PNG);
+        sSupportedImageTypes.add(IMAGE_JPG);
+
+        // add supported audio types
+        sSupportedAudioTypes.add(AUDIO_AAC);
+        sSupportedAudioTypes.add(AUDIO_AMR);
+        sSupportedAudioTypes.add(AUDIO_IMELODY);
+        sSupportedAudioTypes.add(AUDIO_MID);
+        sSupportedAudioTypes.add(AUDIO_MIDI);
+        sSupportedAudioTypes.add(AUDIO_MP3);
+        sSupportedAudioTypes.add(AUDIO_MPEG3);
+        sSupportedAudioTypes.add(AUDIO_MPEG);
+        sSupportedAudioTypes.add(AUDIO_MPG);
+        sSupportedAudioTypes.add(AUDIO_MP4);
+        sSupportedAudioTypes.add(AUDIO_X_MID);
+        sSupportedAudioTypes.add(AUDIO_X_MIDI);
+        sSupportedAudioTypes.add(AUDIO_X_MP3);
+        sSupportedAudioTypes.add(AUDIO_X_MPEG3);
+        sSupportedAudioTypes.add(AUDIO_X_MPEG);
+        sSupportedAudioTypes.add(AUDIO_X_MPG);
+        sSupportedAudioTypes.add(AUDIO_3GPP);
+        sSupportedAudioTypes.add(AUDIO_OGG);
+
+        // add supported video types
+        sSupportedVideoTypes.add(VIDEO_3GPP);
+        sSupportedVideoTypes.add(VIDEO_3G2);
+        sSupportedVideoTypes.add(VIDEO_H263);
+        sSupportedVideoTypes.add(VIDEO_MP4);
+    }
+
+    // This class should never be instantiated.
+    private ContentType() {
+    }
+
+    public static boolean isSupportedType(String contentType) {
+        return (null != contentType) && sSupportedContentTypes.contains(contentType);
+    }
+
+    public static boolean isSupportedImageType(String contentType) {
+        return isImageType(contentType) && isSupportedType(contentType);
+    }
+
+    public static boolean isSupportedAudioType(String contentType) {
+        return isAudioType(contentType) && isSupportedType(contentType);
+    }
+
+    public static boolean isSupportedVideoType(String contentType) {
+        return isVideoType(contentType) && isSupportedType(contentType);
+    }
+
+    public static boolean isTextType(String contentType) {
+        return (null != contentType) && contentType.startsWith("text/");
+    }
+
+    public static boolean isImageType(String contentType) {
+        return (null != contentType) && contentType.startsWith("image/");
+    }
+
+    public static boolean isAudioType(String contentType) {
+        return (null != contentType) && contentType.startsWith("audio/");
+    }
+
+    public static boolean isVideoType(String contentType) {
+        return (null != contentType) && contentType.startsWith("video/");
+    }
+
+    public static boolean isDrmType(String contentType) {
+        return (null != contentType)
+                && (contentType.equals(APP_DRM_CONTENT)
+                        || contentType.equals(APP_DRM_MESSAGE));
+    }
+
+    public static boolean isUnspecified(String contentType) {
+        return (null != contentType) && contentType.endsWith("*");
+    }
+
+    @SuppressWarnings("unchecked")
+    public static ArrayList<String> getImageTypes() {
+        return (ArrayList<String>) sSupportedImageTypes.clone();
+    }
+
+    @SuppressWarnings("unchecked")
+    public static ArrayList<String> getAudioTypes() {
+        return (ArrayList<String>) sSupportedAudioTypes.clone();
+    }
+
+    @SuppressWarnings("unchecked")
+    public static ArrayList<String> getVideoTypes() {
+        return (ArrayList<String>) sSupportedVideoTypes.clone();
+    }
+
+    @SuppressWarnings("unchecked")
+    public static ArrayList<String> getSupportedTypes() {
+        return (ArrayList<String>) sSupportedContentTypes.clone();
+    }
+}
diff --git a/core/java/com/google/android/mms/InvalidHeaderValueException.java b/core/java/com/google/android/mms/InvalidHeaderValueException.java
new file mode 100644
index 0000000..73d7832
--- /dev/null
+++ b/core/java/com/google/android/mms/InvalidHeaderValueException.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2007 Esmertec AG.
+ * Copyright (C) 2007 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.google.android.mms;
+
+/**
+ * Thrown when an invalid header value was set.
+ */
+public class InvalidHeaderValueException extends MmsException {
+    private static final long serialVersionUID = -2053384496042052262L;
+
+    /**
+     * Constructs an InvalidHeaderValueException with no detailed message.
+     */
+    public InvalidHeaderValueException() {
+        super();
+    }
+
+    /**
+     * Constructs an InvalidHeaderValueException with the specified detailed message.
+     *
+     * @param message the detailed message.
+     */
+    public InvalidHeaderValueException(String message) {
+        super(message);
+    }
+}
diff --git a/core/java/com/google/android/mms/MmsException.java b/core/java/com/google/android/mms/MmsException.java
new file mode 100644
index 0000000..6ca0c7e
--- /dev/null
+++ b/core/java/com/google/android/mms/MmsException.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2007 Esmertec AG.
+ * Copyright (C) 2007 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.google.android.mms;
+
+/**
+ * A generic exception that is thrown by the Mms client.
+ */
+public class MmsException extends Exception {
+    private static final long serialVersionUID = -7323249827281485390L;
+
+    /**
+     * Creates a new MmsException.
+     */
+    public MmsException() {
+        super();
+    }
+
+    /**
+     * Creates a new MmsException with the specified detail message.
+     *
+     * @param message the detail message.
+     */
+    public MmsException(String message) {
+        super(message);
+    }
+
+    /**
+     * Creates a new MmsException with the specified cause.
+     *
+     * @param cause the cause.
+     */
+    public MmsException(Throwable cause) {
+        super(cause);
+    }
+
+    /**
+     * Creates a new MmsException with the specified detail message and cause.
+     *
+     * @param message the detail message.
+     * @param cause the cause.
+     */
+    public MmsException(String message, Throwable cause) {
+        super(message, cause);
+    }
+}
diff --git a/core/java/com/google/android/mms/package.html b/core/java/com/google/android/mms/package.html
new file mode 100755
index 0000000..c9f96a6
--- /dev/null
+++ b/core/java/com/google/android/mms/package.html
@@ -0,0 +1,5 @@
+<body>
+
+{@hide}
+
+</body>
diff --git a/core/java/com/google/android/mms/pdu/AcknowledgeInd.java b/core/java/com/google/android/mms/pdu/AcknowledgeInd.java
new file mode 100644
index 0000000..0e96c60
--- /dev/null
+++ b/core/java/com/google/android/mms/pdu/AcknowledgeInd.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2007 Esmertec AG.
+ * Copyright (C) 2007 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.google.android.mms.pdu;
+
+import com.google.android.mms.InvalidHeaderValueException;
+
+/**
+ * M-Acknowledge.ind PDU.
+ */
+public class AcknowledgeInd extends GenericPdu {
+    /**
+     * Constructor, used when composing a M-Acknowledge.ind pdu.
+     *
+     * @param mmsVersion current viersion of mms
+     * @param transactionId the transaction-id value
+     * @throws InvalidHeaderValueException if parameters are invalid.
+     *         NullPointerException if transactionId is null.
+     */
+    public AcknowledgeInd(int mmsVersion, byte[] transactionId)
+            throws InvalidHeaderValueException {
+        super();
+
+        setMessageType(PduHeaders.MESSAGE_TYPE_ACKNOWLEDGE_IND);
+        setMmsVersion(mmsVersion);
+        setTransactionId(transactionId);
+    }
+
+    /**
+     * Constructor with given headers.
+     *
+     * @param headers Headers for this PDU.
+     */
+    AcknowledgeInd(PduHeaders headers) {
+        super(headers);
+    }
+
+    /**
+     * Get X-Mms-Report-Allowed field value.
+     *
+     * @return the X-Mms-Report-Allowed value
+     */
+    public int getReportAllowed() {
+        return mPduHeaders.getOctet(PduHeaders.REPORT_ALLOWED);
+    }
+
+    /**
+     * Set X-Mms-Report-Allowed field value.
+     *
+     * @param value the value
+     * @throws InvalidHeaderValueException if the value is invalid.
+     */
+    public void setReportAllowed(int value) throws InvalidHeaderValueException {
+        mPduHeaders.setOctet(value, PduHeaders.REPORT_ALLOWED);
+    }
+
+    /**
+     * Get X-Mms-Transaction-Id field value.
+     *
+     * @return the X-Mms-Report-Allowed value
+     */
+    public byte[] getTransactionId() {
+        return mPduHeaders.getTextString(PduHeaders.TRANSACTION_ID);
+    }
+
+    /**
+     * Set X-Mms-Transaction-Id field value.
+     *
+     * @param value the value
+     * @throws NullPointerException if the value is null.
+     */
+    public void setTransactionId(byte[] value) {
+        mPduHeaders.setTextString(value, PduHeaders.TRANSACTION_ID);
+    }
+}
diff --git a/core/java/com/google/android/mms/pdu/Base64.java b/core/java/com/google/android/mms/pdu/Base64.java
new file mode 100644
index 0000000..604bee0
--- /dev/null
+++ b/core/java/com/google/android/mms/pdu/Base64.java
@@ -0,0 +1,167 @@
+/*
+ * Copyright (C) 2007 Esmertec AG.
+ * Copyright (C) 2007 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.google.android.mms.pdu;
+
+public class Base64 {
+    /**
+     * Used to get the number of Quadruples.
+     */
+    static final int FOURBYTE = 4;
+
+    /**
+     * Byte used to pad output.
+     */
+    static final byte PAD = (byte) '=';
+
+    /**
+     * The base length.
+     */
+    static final int BASELENGTH = 255;
+
+    // Create arrays to hold the base64 characters
+    private static byte[] base64Alphabet = new byte[BASELENGTH];
+
+    // Populating the character arrays
+    static {
+        for (int i = 0; i < BASELENGTH; i++) {
+            base64Alphabet[i] = (byte) -1;
+        }
+        for (int i = 'Z'; i >= 'A'; i--) {
+            base64Alphabet[i] = (byte) (i - 'A');
+        }
+        for (int i = 'z'; i >= 'a'; i--) {
+            base64Alphabet[i] = (byte) (i - 'a' + 26);
+        }
+        for (int i = '9'; i >= '0'; i--) {
+            base64Alphabet[i] = (byte) (i - '0' + 52);
+        }
+
+        base64Alphabet['+'] = 62;
+        base64Alphabet['/'] = 63;
+    }
+
+    /**
+     * Decodes Base64 data into octects
+     *
+     * @param base64Data Byte array containing Base64 data
+     * @return Array containing decoded data.
+     */
+    public static byte[] decodeBase64(byte[] base64Data) {
+        // RFC 2045 requires that we discard ALL non-Base64 characters
+        base64Data = discardNonBase64(base64Data);
+
+        // handle the edge case, so we don't have to worry about it later
+        if (base64Data.length == 0) {
+            return new byte[0];
+        }
+
+        int numberQuadruple = base64Data.length / FOURBYTE;
+        byte decodedData[] = null;
+        byte b1 = 0, b2 = 0, b3 = 0, b4 = 0, marker0 = 0, marker1 = 0;
+
+        // Throw away anything not in base64Data
+
+        int encodedIndex = 0;
+        int dataIndex = 0;
+        {
+            // this sizes the output array properly - rlw
+            int lastData = base64Data.length;
+            // ignore the '=' padding
+            while (base64Data[lastData - 1] == PAD) {
+                if (--lastData == 0) {
+                    return new byte[0];
+                }
+            }
+            decodedData = new byte[lastData - numberQuadruple];
+        }
+
+        for (int i = 0; i < numberQuadruple; i++) {
+            dataIndex = i * 4;
+            marker0 = base64Data[dataIndex + 2];
+            marker1 = base64Data[dataIndex + 3];
+
+            b1 = base64Alphabet[base64Data[dataIndex]];
+            b2 = base64Alphabet[base64Data[dataIndex + 1]];
+
+            if (marker0 != PAD && marker1 != PAD) {
+                //No PAD e.g 3cQl
+                b3 = base64Alphabet[marker0];
+                b4 = base64Alphabet[marker1];
+
+                decodedData[encodedIndex] = (byte) (b1 << 2 | b2 >> 4);
+                decodedData[encodedIndex + 1] =
+                    (byte) (((b2 & 0xf) << 4) | ((b3 >> 2) & 0xf));
+                decodedData[encodedIndex + 2] = (byte) (b3 << 6 | b4);
+            } else if (marker0 == PAD) {
+                //Two PAD e.g. 3c[Pad][Pad]
+                decodedData[encodedIndex] = (byte) (b1 << 2 | b2 >> 4);
+            } else if (marker1 == PAD) {
+                //One PAD e.g. 3cQ[Pad]
+                b3 = base64Alphabet[marker0];
+
+                decodedData[encodedIndex] = (byte) (b1 << 2 | b2 >> 4);
+                decodedData[encodedIndex + 1] =
+                    (byte) (((b2 & 0xf) << 4) | ((b3 >> 2) & 0xf));
+            }
+            encodedIndex += 3;
+        }
+        return decodedData;
+    }
+
+    /**
+     * Check octect wheter it is a base64 encoding.
+     *
+     * @param octect to be checked byte
+     * @return ture if it is base64 encoding, false otherwise.
+     */
+    private static boolean isBase64(byte octect) {
+        if (octect == PAD) {
+            return true;
+        } else if (base64Alphabet[octect] == -1) {
+            return false;
+        } else {
+            return true;
+        }
+    }
+
+    /**
+     * Discards any characters outside of the base64 alphabet, per
+     * the requirements on page 25 of RFC 2045 - "Any characters
+     * outside of the base64 alphabet are to be ignored in base64
+     * encoded data."
+     *
+     * @param data The base-64 encoded data to groom
+     * @return The data, less non-base64 characters (see RFC 2045).
+     */
+    static byte[] discardNonBase64(byte[] data) {
+        byte groomedData[] = new byte[data.length];
+        int bytesCopied = 0;
+
+        for (int i = 0; i < data.length; i++) {
+            if (isBase64(data[i])) {
+                groomedData[bytesCopied++] = data[i];
+            }
+        }
+
+        byte packedData[] = new byte[bytesCopied];
+
+        System.arraycopy(groomedData, 0, packedData, 0, bytesCopied);
+
+        return packedData;
+    }
+}
diff --git a/core/java/com/google/android/mms/pdu/CharacterSets.java b/core/java/com/google/android/mms/pdu/CharacterSets.java
new file mode 100644
index 0000000..4e22ca5
--- /dev/null
+++ b/core/java/com/google/android/mms/pdu/CharacterSets.java
@@ -0,0 +1,172 @@
+/*
+ * Copyright (C) 2007 Esmertec AG.
+ * Copyright (C) 2007 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.google.android.mms.pdu;
+
+import java.io.UnsupportedEncodingException;
+import java.util.HashMap;
+
+public class CharacterSets {
+    /**
+     * IANA assigned MIB enum numbers.
+     *
+     * From wap-230-wsp-20010705-a.pdf
+     * Any-charset = <Octet 128>
+     * Equivalent to the special RFC2616 charset value "*"
+     */
+    public static final int ANY_CHARSET = 0x00;
+    public static final int US_ASCII    = 0x03;
+    public static final int ISO_8859_1  = 0x04;
+    public static final int ISO_8859_2  = 0x05;
+    public static final int ISO_8859_3  = 0x06;
+    public static final int ISO_8859_4  = 0x07;
+    public static final int ISO_8859_5  = 0x08;
+    public static final int ISO_8859_6  = 0x09;
+    public static final int ISO_8859_7  = 0x0A;
+    public static final int ISO_8859_8  = 0x0B;
+    public static final int ISO_8859_9  = 0x0C;
+    public static final int SHIFT_JIS   = 0x11;
+    public static final int UTF_8       = 0x6A;
+    public static final int BIG5        = 0x07EA;
+    public static final int UCS2        = 0x03E8;
+    public static final int UTF_16      = 0x03F7;
+
+    /**
+     * If the encoding of given data is unsupported, use UTF_8 to decode it.
+     */
+    public static final int DEFAULT_CHARSET = UTF_8;
+
+    /**
+     * Array of MIB enum numbers.
+     */
+    private static final int[] MIBENUM_NUMBERS = {
+        ANY_CHARSET,
+        US_ASCII,
+        ISO_8859_1,
+        ISO_8859_2,
+        ISO_8859_3,
+        ISO_8859_4,
+        ISO_8859_5,
+        ISO_8859_6,
+        ISO_8859_7,
+        ISO_8859_8,
+        ISO_8859_9,
+        SHIFT_JIS,
+        UTF_8,
+        BIG5,
+        UCS2,
+        UTF_16,
+    };
+
+    /**
+     * The Well-known-charset Mime name.
+     */
+    public static final String MIMENAME_ANY_CHARSET = "*";
+    public static final String MIMENAME_US_ASCII    = "us-ascii";
+    public static final String MIMENAME_ISO_8859_1  = "iso-8859-1";
+    public static final String MIMENAME_ISO_8859_2  = "iso-8859-2";
+    public static final String MIMENAME_ISO_8859_3  = "iso-8859-3";
+    public static final String MIMENAME_ISO_8859_4  = "iso-8859-4";
+    public static final String MIMENAME_ISO_8859_5  = "iso-8859-5";
+    public static final String MIMENAME_ISO_8859_6  = "iso-8859-6";
+    public static final String MIMENAME_ISO_8859_7  = "iso-8859-7";
+    public static final String MIMENAME_ISO_8859_8  = "iso-8859-8";
+    public static final String MIMENAME_ISO_8859_9  = "iso-8859-9";
+    public static final String MIMENAME_SHIFT_JIS   = "shift_JIS";
+    public static final String MIMENAME_UTF_8       = "utf-8";
+    public static final String MIMENAME_BIG5        = "big5";
+    public static final String MIMENAME_UCS2        = "iso-10646-ucs-2";
+    public static final String MIMENAME_UTF_16      = "utf-16";
+
+    public static final String DEFAULT_CHARSET_NAME = MIMENAME_UTF_8;
+
+    /**
+     * Array of the names of character sets.
+     */
+    private static final String[] MIME_NAMES = {
+        MIMENAME_ANY_CHARSET,
+        MIMENAME_US_ASCII,
+        MIMENAME_ISO_8859_1,
+        MIMENAME_ISO_8859_2,
+        MIMENAME_ISO_8859_3,
+        MIMENAME_ISO_8859_4,
+        MIMENAME_ISO_8859_5,
+        MIMENAME_ISO_8859_6,
+        MIMENAME_ISO_8859_7,
+        MIMENAME_ISO_8859_8,
+        MIMENAME_ISO_8859_9,
+        MIMENAME_SHIFT_JIS,
+        MIMENAME_UTF_8,
+        MIMENAME_BIG5,
+        MIMENAME_UCS2,
+        MIMENAME_UTF_16,
+    };
+
+    private static final HashMap<Integer, String> MIBENUM_TO_NAME_MAP;
+    private static final HashMap<String, Integer> NAME_TO_MIBENUM_MAP;
+
+    static {
+        // Create the HashMaps.
+        MIBENUM_TO_NAME_MAP = new HashMap<Integer, String>();
+        NAME_TO_MIBENUM_MAP = new HashMap<String, Integer>();
+        assert(MIBENUM_NUMBERS.length == MIME_NAMES.length);
+        int count = MIBENUM_NUMBERS.length - 1;
+        for(int i = 0; i <= count; i++) {
+            MIBENUM_TO_NAME_MAP.put(MIBENUM_NUMBERS[i], MIME_NAMES[i]);
+            NAME_TO_MIBENUM_MAP.put(MIME_NAMES[i], MIBENUM_NUMBERS[i]);
+        }
+    }
+
+    private CharacterSets() {} // Non-instantiatable
+
+    /**
+     * Map an MIBEnum number to the name of the charset which this number
+     * is assigned to by IANA.
+     *
+     * @param mibEnumValue An IANA assigned MIBEnum number.
+     * @return The name string of the charset.
+     * @throws UnsupportedEncodingException
+     */
+    public static String getMimeName(int mibEnumValue)
+            throws UnsupportedEncodingException {
+        String name = MIBENUM_TO_NAME_MAP.get(mibEnumValue);
+        if (name == null) {
+            throw new UnsupportedEncodingException();
+        }
+        return name;
+    }
+
+    /**
+     * Map a well-known charset name to its assigned MIBEnum number.
+     *
+     * @param mimeName The charset name.
+     * @return The MIBEnum number assigned by IANA for this charset.
+     * @throws UnsupportedEncodingException
+     */
+    public static int getMibEnumValue(String mimeName)
+            throws UnsupportedEncodingException {
+        if(null == mimeName) {
+            return -1;
+        }
+
+        Integer mibEnumValue = NAME_TO_MIBENUM_MAP.get(mimeName);
+        if (mibEnumValue == null) {
+            throw new UnsupportedEncodingException();
+        }
+        return mibEnumValue;
+    }
+}
diff --git a/core/java/com/google/android/mms/pdu/DeliveryInd.java b/core/java/com/google/android/mms/pdu/DeliveryInd.java
new file mode 100644
index 0000000..dafa8d1
--- /dev/null
+++ b/core/java/com/google/android/mms/pdu/DeliveryInd.java
@@ -0,0 +1,138 @@
+/*
+ * Copyright (C) 2007 Esmertec AG.
+ * Copyright (C) 2007 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.google.android.mms.pdu;
+
+import com.google.android.mms.InvalidHeaderValueException;
+
+/**
+ * M-Delivery.Ind Pdu.
+ */
+public class DeliveryInd extends GenericPdu {
+    /**
+     * Empty constructor.
+     * Since the Pdu corresponding to this class is constructed
+     * by the Proxy-Relay server, this class is only instantiated
+     * by the Pdu Parser.
+     *
+     * @throws InvalidHeaderValueException if error occurs.
+     */
+    public DeliveryInd() throws InvalidHeaderValueException {
+        super();
+        setMessageType(PduHeaders.MESSAGE_TYPE_DELIVERY_IND);
+    }
+
+    /**
+     * Constructor with given headers.
+     *
+     * @param headers Headers for this PDU.
+     */
+    DeliveryInd(PduHeaders headers) {
+        super(headers);
+    }
+
+    /**
+     * Get Date value.
+     *
+     * @return the value
+     */
+    public long getDate() {
+        return mPduHeaders.getLongInteger(PduHeaders.DATE);
+    }
+
+    /**
+     * Set Date value.
+     *
+     * @param value the value
+     */
+    public void setDate(long value) {
+        mPduHeaders.setLongInteger(value, PduHeaders.DATE);
+    }
+
+    /**
+     * Get Message-ID value.
+     *
+     * @return the value
+     */
+    public byte[] getMessageId() {
+        return mPduHeaders.getTextString(PduHeaders.MESSAGE_ID);
+    }
+
+    /**
+     * Set Message-ID value.
+     *
+     * @param value the value, should not be null
+     * @throws NullPointerException if the value is null.
+     */
+    public void setMessageId(byte[] value) {
+        mPduHeaders.setTextString(value, PduHeaders.MESSAGE_ID);
+    }
+
+    /**
+     * Get Status value.
+     *
+     * @return the value
+     */
+    public int getStatus() {
+        return mPduHeaders.getOctet(PduHeaders.STATUS);
+    }
+
+    /**
+     * Set Status value.
+     *
+     * @param value the value
+     * @throws InvalidHeaderValueException if the value is invalid.
+     */
+    public void setStatus(int value) throws InvalidHeaderValueException {
+        mPduHeaders.setOctet(value, PduHeaders.STATUS);
+    }
+
+    /**
+     * Get To value.
+     *
+     * @return the value
+     */
+    public EncodedStringValue[] getTo() {
+        return mPduHeaders.getEncodedStringValues(PduHeaders.TO);
+    }
+
+    /**
+     * set To value.
+     *
+     * @param value the value
+     * @throws NullPointerException if the value is null.
+     */
+    public void setTo(EncodedStringValue[] value) {
+        mPduHeaders.setEncodedStringValues(value, PduHeaders.TO);
+    }
+
+    /*
+     * Optional, not supported header fields:
+     *
+     *     public byte[] getApplicId() {return null;}
+     *     public void setApplicId(byte[] value) {}
+     *
+     *     public byte[] getAuxApplicId() {return null;}
+     *     public void getAuxApplicId(byte[] value) {}
+     *
+     *     public byte[] getReplyApplicId() {return 0x00;}
+     *     public void setReplyApplicId(byte[] value) {}
+     *
+     *     public EncodedStringValue getStatusText() {return null;}
+     *     public void setStatusText(EncodedStringValue value) {}
+     */
+}
diff --git a/core/java/com/google/android/mms/pdu/EncodedStringValue.java b/core/java/com/google/android/mms/pdu/EncodedStringValue.java
new file mode 100644
index 0000000..7696c5e
--- /dev/null
+++ b/core/java/com/google/android/mms/pdu/EncodedStringValue.java
@@ -0,0 +1,272 @@
+/*
+ * Copyright (C) 2007-2008 Esmertec AG.
+ * Copyright (C) 2007-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.google.android.mms.pdu;
+
+import android.util.Config;
+import android.util.Log;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.util.ArrayList;
+
+/**
+ * Encoded-string-value = Text-string | Value-length Char-set Text-string
+ */
+public class EncodedStringValue implements Cloneable {
+    private static final String TAG = "EncodedStringValue";
+    private static final boolean DEBUG = false;
+    private static final boolean LOCAL_LOGV = DEBUG ? Config.LOGD : Config.LOGV;
+
+    /**
+     * The Char-set value.
+     */
+    private int mCharacterSet;
+
+    /**
+     * The Text-string value.
+     */
+    private byte[] mData;
+
+    /**
+     * Constructor.
+     *
+     * @param charset the Char-set value
+     * @param data the Text-string value
+     * @throws NullPointerException if Text-string value is null.
+     */
+    public EncodedStringValue(int charset, byte[] data) {
+        // TODO: CharSet needs to be validated against MIBEnum.
+        if(null == data) {
+            throw new NullPointerException("EncodedStringValue: Text-string is null.");
+        }
+
+        mCharacterSet = charset;
+        mData = new byte[data.length];
+        System.arraycopy(data, 0, mData, 0, data.length);
+    }
+
+    /**
+     * Constructor.
+     *
+     * @param data the Text-string value
+     * @throws NullPointerException if Text-string value is null.
+     */
+    public EncodedStringValue(byte[] data) {
+        this(CharacterSets.DEFAULT_CHARSET, data);
+    }
+
+    public EncodedStringValue(String data) {
+        try {
+            mData = data.getBytes(CharacterSets.DEFAULT_CHARSET_NAME);
+            mCharacterSet = CharacterSets.DEFAULT_CHARSET;
+        } catch (UnsupportedEncodingException e) {
+            Log.e(TAG, "Default encoding must be supported.", e);
+        }
+    }
+
+    /**
+     * Get Char-set value.
+     *
+     * @return the value
+     */
+    public int getCharacterSet() {
+        return mCharacterSet;
+    }
+
+    /**
+     * Set Char-set value.
+     *
+     * @param charset the Char-set value
+     */
+    public void setCharacterSet(int charset) {
+        // TODO: CharSet needs to be validated against MIBEnum.
+        mCharacterSet = charset;
+    }
+
+    /**
+     * Get Text-string value.
+     *
+     * @return the value
+     */
+    public byte[] getTextString() {
+        byte[] byteArray = new byte[mData.length];
+
+        System.arraycopy(mData, 0, byteArray, 0, mData.length);
+        return byteArray;
+    }
+
+    /**
+     * Set Text-string value.
+     *
+     * @param textString the Text-string value
+     * @throws NullPointerException if Text-string value is null.
+     */
+    public void setTextString(byte[] textString) {
+        if(null == textString) {
+            throw new NullPointerException("EncodedStringValue: Text-string is null.");
+        }
+
+        mData = new byte[textString.length];
+        System.arraycopy(textString, 0, mData, 0, textString.length);
+    }
+
+    /**
+     * Convert this object to a {@link java.lang.String}. If the encoding of
+     * the EncodedStringValue is null or unsupported, it will be
+     * treated as iso-8859-1 encoding.
+     *
+     * @return The decoded String.
+     */
+    public String getString()  {
+        if (CharacterSets.ANY_CHARSET == mCharacterSet) {
+            return new String(mData); // system default encoding.
+        } else {
+            try {
+                String name = CharacterSets.getMimeName(mCharacterSet);
+                return new String(mData, name);
+            } catch (UnsupportedEncodingException e) {
+            	if (LOCAL_LOGV) {
+            		Log.v(TAG, e.getMessage(), e);
+            	}
+            	try {
+                    return new String(mData, CharacterSets.MIMENAME_ISO_8859_1);
+                } catch (UnsupportedEncodingException _) {
+                    return new String(mData); // system default encoding.
+                }
+            }
+        }
+    }
+
+    /**
+     * Append to Text-string.
+     *
+     * @param textString the textString to append
+     * @throws NullPointerException if the text String is null
+     *                      or an IOException occured.
+     */
+    public void appendTextString(byte[] textString) {
+        if(null == textString) {
+            throw new NullPointerException("Text-string is null.");
+        }
+
+        if(null == mData) {
+            mData = new byte[textString.length];
+            System.arraycopy(textString, 0, mData, 0, textString.length);
+        } else {
+            ByteArrayOutputStream newTextString = new ByteArrayOutputStream();
+            try {
+                newTextString.write(mData);
+                newTextString.write(textString);
+            } catch (IOException e) {
+                e.printStackTrace();
+                throw new NullPointerException(
+                        "appendTextString: failed when write a new Text-string");
+            }
+
+            mData = newTextString.toByteArray();
+        }
+    }
+
+    /*
+     * (non-Javadoc)
+     * @see java.lang.Object#clone()
+     */
+    @Override
+    public Object clone() throws CloneNotSupportedException {
+        super.clone();
+        int len = mData.length;
+        byte[] dstBytes = new byte[len];
+        System.arraycopy(mData, 0, dstBytes, 0, len);
+
+        try {
+            return new EncodedStringValue(mCharacterSet, dstBytes);
+        } catch (Exception e) {
+            Log.e(TAG, "failed to clone an EncodedStringValue: " + this);
+            e.printStackTrace();
+            throw new CloneNotSupportedException(e.getMessage());
+        }
+    }
+
+    /**
+     * Split this encoded string around matches of the given pattern.
+     *
+     * @param pattern the delimiting pattern
+     * @return the array of encoded strings computed by splitting this encoded
+     *         string around matches of the given pattern
+     */
+    public EncodedStringValue[] split(String pattern) {
+        String[] temp = getString().split(pattern);
+        EncodedStringValue[] ret = new EncodedStringValue[temp.length];
+        for (int i = 0; i < ret.length; ++i) {
+            try {
+                ret[i] = new EncodedStringValue(mCharacterSet,
+                        temp[i].getBytes());
+            } catch (NullPointerException _) {
+                // Can't arrive here
+                return null;
+            }
+        }
+        return ret;
+    }
+
+    /**
+     * Extract an EncodedStringValue[] from a given String.
+     */
+    public static EncodedStringValue[] extract(String src) {
+        String[] values = src.split(";");
+
+        ArrayList<EncodedStringValue> list = new ArrayList<EncodedStringValue>();
+        for (int i = 0; i < values.length; i++) {
+            if (values[i].length() > 0) {
+                list.add(new EncodedStringValue(values[i]));
+            }
+        }
+
+        int len = list.size();
+        if (len > 0) {
+            return list.toArray(new EncodedStringValue[len]);
+        } else {
+            return null;
+        }
+    }
+
+    /**
+     * Concatenate an EncodedStringValue[] into a single String.
+     */
+    public static String concat(EncodedStringValue[] addr) {
+        StringBuilder sb = new StringBuilder();
+        int maxIndex = addr.length - 1;
+        for (int i = 0; i <= maxIndex; i++) {
+            sb.append(addr[i].getString());
+            if (i < maxIndex) {
+                sb.append(";");
+            }
+        }
+
+        return sb.toString();
+    }
+
+    public static EncodedStringValue copy(EncodedStringValue value) {
+        if (value == null) {
+            return null;
+        }
+
+        return new EncodedStringValue(value.mCharacterSet, value.mData);
+    }
+}
diff --git a/core/java/com/google/android/mms/pdu/GenericPdu.java b/core/java/com/google/android/mms/pdu/GenericPdu.java
new file mode 100644
index 0000000..46c6e00
--- /dev/null
+++ b/core/java/com/google/android/mms/pdu/GenericPdu.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2007 Esmertec AG.
+ * Copyright (C) 2007 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.google.android.mms.pdu;
+
+import com.google.android.mms.InvalidHeaderValueException;
+
+public class GenericPdu {
+    /**
+     * The headers of pdu.
+     */
+    PduHeaders mPduHeaders = null;
+
+    /**
+     * Constructor.
+     */
+    public GenericPdu() {
+        mPduHeaders = new PduHeaders();
+    }
+
+    /**
+     * Constructor.
+     *
+     * @param headers Headers for this PDU.
+     */
+    GenericPdu(PduHeaders headers) {
+        mPduHeaders = headers;
+    }
+
+    /**
+     * Get the headers of this PDU.
+     *
+     * @return A PduHeaders of this PDU.
+     */
+    PduHeaders getPduHeaders() {
+        return mPduHeaders;
+    }
+
+    /**
+     * Get X-Mms-Message-Type field value.
+     *
+     * @return the X-Mms-Report-Allowed value
+     */
+    public int getMessageType() {
+        return mPduHeaders.getOctet(PduHeaders.MESSAGE_TYPE);
+    }
+
+    /**
+     * Set X-Mms-Message-Type field value.
+     *
+     * @param value the value
+     * @throws InvalidHeaderValueException if the value is invalid.
+     *         RuntimeException if field's value is not Octet.
+     */
+    public void setMessageType(int value) throws InvalidHeaderValueException {
+        mPduHeaders.setOctet(value, PduHeaders.MESSAGE_TYPE);
+    }
+
+    /**
+     * Get X-Mms-MMS-Version field value.
+     *
+     * @return the X-Mms-MMS-Version value
+     */
+    public int getMmsVersion() {
+        return mPduHeaders.getOctet(PduHeaders.MMS_VERSION);
+    }
+
+    /**
+     * Set X-Mms-MMS-Version field value.
+     *
+     * @param value the value
+     * @throws InvalidHeaderValueException if the value is invalid.
+     *         RuntimeException if field's value is not Octet.
+     */
+    public void setMmsVersion(int value) throws InvalidHeaderValueException {
+        mPduHeaders.setOctet(value, PduHeaders.MMS_VERSION);
+    }
+}
diff --git a/core/java/com/google/android/mms/pdu/MultimediaMessagePdu.java b/core/java/com/google/android/mms/pdu/MultimediaMessagePdu.java
new file mode 100644
index 0000000..5a85e0e
--- /dev/null
+++ b/core/java/com/google/android/mms/pdu/MultimediaMessagePdu.java
@@ -0,0 +1,150 @@
+/*
+ * Copyright (C) 2007 Esmertec AG.
+ * Copyright (C) 2007 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.google.android.mms.pdu;
+
+import com.google.android.mms.InvalidHeaderValueException;
+
+/**
+ * Multimedia message PDU.
+ */
+public class MultimediaMessagePdu extends GenericPdu{
+    /**
+     * The body.
+     */
+    private PduBody mMessageBody;
+
+    /**
+     * Constructor.
+     */
+    public MultimediaMessagePdu() {
+        super();
+    }
+
+    /**
+     * Constructor.
+     *
+     * @param header the header of this PDU
+     * @param body the body of this PDU
+     */
+    public MultimediaMessagePdu(PduHeaders header, PduBody body) {
+        super(header);
+        mMessageBody = body;
+    }
+
+    /**
+     * Constructor with given headers.
+     *
+     * @param headers Headers for this PDU.
+     */
+    MultimediaMessagePdu(PduHeaders headers) {
+        super(headers);
+    }
+
+    /**
+     * Get body of the PDU.
+     *
+     * @return the body
+     */
+    public PduBody getBody() {
+        return mMessageBody;
+    }
+
+    /**
+     * Set body of the PDU.
+     *
+     * @param body the body
+     */
+    public void setBody(PduBody body) {
+        mMessageBody = body;
+    }
+
+    /**
+     * Get subject.
+     *
+     * @return the value
+     */
+    public EncodedStringValue getSubject() {
+        return mPduHeaders.getEncodedStringValue(PduHeaders.SUBJECT);
+    }
+
+    /**
+     * Set subject.
+     *
+     * @param value the value
+     * @throws NullPointerException if the value is null.
+     */
+    public void setSubject(EncodedStringValue value) {
+        mPduHeaders.setEncodedStringValue(value, PduHeaders.SUBJECT);
+    }
+
+    /**
+     * Get To value.
+     *
+     * @return the value
+     */
+    public EncodedStringValue[] getTo() {
+        return mPduHeaders.getEncodedStringValues(PduHeaders.TO);
+    }
+
+    /**
+     * Add a "To" value.
+     *
+     * @param value the value
+     * @throws NullPointerException if the value is null.
+     */
+    public void addTo(EncodedStringValue value) {
+        mPduHeaders.appendEncodedStringValue(value, PduHeaders.TO);
+    }
+
+    /**
+     * Get X-Mms-Priority value.
+     *
+     * @return the value
+     */
+    public int getPriority() {
+        return mPduHeaders.getOctet(PduHeaders.PRIORITY);
+    }
+
+    /**
+     * Set X-Mms-Priority value.
+     *
+     * @param value the value
+     * @throws InvalidHeaderValueException if the value is invalid.
+     */
+    public void setPriority(int value) throws InvalidHeaderValueException {
+        mPduHeaders.setOctet(value, PduHeaders.PRIORITY);
+    }
+
+    /**
+     * Get Date value.
+     *
+     * @return the value
+     */
+    public long getDate() {
+        return mPduHeaders.getLongInteger(PduHeaders.DATE);
+    }
+
+    /**
+     * Set Date value in seconds.
+     *
+     * @param value the value
+     */
+    public void setDate(long value) {
+        mPduHeaders.setLongInteger(value, PduHeaders.DATE);
+    }
+}
diff --git a/core/java/com/google/android/mms/pdu/NotificationInd.java b/core/java/com/google/android/mms/pdu/NotificationInd.java
new file mode 100644
index 0000000..c56cba6
--- /dev/null
+++ b/core/java/com/google/android/mms/pdu/NotificationInd.java
@@ -0,0 +1,285 @@
+/*
+ * Copyright (C) 2007 Esmertec AG.
+ * Copyright (C) 2007 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.google.android.mms.pdu;
+
+import com.google.android.mms.InvalidHeaderValueException;
+
+/**
+ * M-Notification.ind PDU.
+ */
+public class NotificationInd extends GenericPdu {
+    /**
+     * Empty constructor.
+     * Since the Pdu corresponding to this class is constructed
+     * by the Proxy-Relay server, this class is only instantiated
+     * by the Pdu Parser.
+     *
+     * @throws InvalidHeaderValueException if error occurs.
+     *         RuntimeException if an undeclared error occurs.
+     */
+    public NotificationInd() throws InvalidHeaderValueException {
+        super();
+        setMessageType(PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND);
+    }
+
+    /**
+     * Constructor with given headers.
+     *
+     * @param headers Headers for this PDU.
+     */
+    NotificationInd(PduHeaders headers) {
+        super(headers);
+    }
+
+    /**
+     * Get X-Mms-Content-Class Value.
+     *
+     * @return the value
+     */
+    public int getContentClass() {
+        return mPduHeaders.getOctet(PduHeaders.CONTENT_CLASS);
+    }
+
+    /**
+     * Set X-Mms-Content-Class Value.
+     *
+     * @param value the value
+     * @throws InvalidHeaderValueException if the value is invalid.
+     *         RuntimeException if an undeclared error occurs.
+     */
+    public void setContentClass(int value) throws InvalidHeaderValueException {
+        mPduHeaders.setOctet(value, PduHeaders.CONTENT_CLASS);
+    }
+
+    /**
+     * Get X-Mms-Content-Location value.
+     * When used in a PDU other than M-Mbox-Delete.conf and M-Delete.conf:
+     * Content-location-value = Uri-value
+     *
+     * @return the value
+     */
+    public byte[] getContentLocation() {
+        return mPduHeaders.getTextString(PduHeaders.CONTENT_LOCATION);
+    }
+
+    /**
+     * Set X-Mms-Content-Location value.
+     *
+     * @param value the value
+     * @throws NullPointerException if the value is null.
+     *         RuntimeException if an undeclared error occurs.
+     */
+    public void setContentLocation(byte[] value) {
+        mPduHeaders.setTextString(value, PduHeaders.CONTENT_LOCATION);
+    }
+
+    /**
+     * Get X-Mms-Expiry value.
+     *
+     * Expiry-value = Value-length
+     *      (Absolute-token Date-value | Relative-token Delta-seconds-value)
+     *
+     * @return the value
+     */
+    public long getExpiry() {
+        return mPduHeaders.getLongInteger(PduHeaders.EXPIRY);
+    }
+
+    /**
+     * Set X-Mms-Expiry value.
+     *
+     * @param value the value
+     * @throws RuntimeException if an undeclared error occurs.
+     */
+    public void setExpiry(long value) {
+        mPduHeaders.setLongInteger(value, PduHeaders.EXPIRY);
+    }
+
+    /**
+     * Get From value.
+     * From-value = Value-length
+     *      (Address-present-token Encoded-string-value | Insert-address-token)
+     *
+     * @return the value
+     */
+    public EncodedStringValue getFrom() {
+       return mPduHeaders.getEncodedStringValue(PduHeaders.FROM);
+    }
+
+    /**
+     * Set From value.
+     *
+     * @param value the value
+     * @throws NullPointerException if the value is null.
+     *         RuntimeException if an undeclared error occurs.
+     */
+    public void setFrom(EncodedStringValue value) {
+        mPduHeaders.setEncodedStringValue(value, PduHeaders.FROM);
+    }
+
+    /**
+     * Get X-Mms-Message-Class value.
+     * Message-class-value = Class-identifier | Token-text
+     * Class-identifier = Personal | Advertisement | Informational | Auto
+     *
+     * @return the value
+     */
+    public byte[] getMessageClass() {
+        return mPduHeaders.getTextString(PduHeaders.MESSAGE_CLASS);
+    }
+
+    /**
+     * Set X-Mms-Message-Class value.
+     *
+     * @param value the value
+     * @throws NullPointerException if the value is null.
+     *         RuntimeException if an undeclared error occurs.
+     */
+    public void setMessageClass(byte[] value) {
+        mPduHeaders.setTextString(value, PduHeaders.MESSAGE_CLASS);
+    }
+
+    /**
+     * Get X-Mms-Message-Size value.
+     * Message-size-value = Long-integer
+     *
+     * @return the value
+     */
+    public long getMessageSize() {
+        return mPduHeaders.getLongInteger(PduHeaders.MESSAGE_SIZE);
+    }
+
+    /**
+     * Set X-Mms-Message-Size value.
+     *
+     * @param value the value
+     * @throws RuntimeException if an undeclared error occurs.
+     */
+    public void setMessageSize(long value) {
+        mPduHeaders.setLongInteger(value, PduHeaders.MESSAGE_SIZE);
+    }
+
+    /**
+     * Get subject.
+     *
+     * @return the value
+     */
+    public EncodedStringValue getSubject() {
+        return mPduHeaders.getEncodedStringValue(PduHeaders.SUBJECT);
+    }
+
+    /**
+     * Set subject.
+     *
+     * @param value the value
+     * @throws NullPointerException if the value is null.
+     *         RuntimeException if an undeclared error occurs.
+     */
+    public void setSubject(EncodedStringValue value) {
+        mPduHeaders.setEncodedStringValue(value, PduHeaders.SUBJECT);
+    }
+
+    /**
+     * Get X-Mms-Transaction-Id.
+     *
+     * @return the value
+     */
+    public byte[] getTransactionId() {
+        return mPduHeaders.getTextString(PduHeaders.TRANSACTION_ID);
+    }
+
+    /**
+     * Set X-Mms-Transaction-Id.
+     *
+     * @param value the value
+     * @throws NullPointerException if the value is null.
+     *         RuntimeException if an undeclared error occurs.
+     */
+    public void setTransactionId(byte[] value) {
+        mPduHeaders.setTextString(value, PduHeaders.TRANSACTION_ID);
+    }
+
+    /**
+     * Get X-Mms-Delivery-Report Value.
+     *
+     * @return the value
+     */
+    public int getDeliveryReport() {
+        return mPduHeaders.getOctet(PduHeaders.DELIVERY_REPORT);
+    }
+
+    /**
+     * Set X-Mms-Delivery-Report Value.
+     *
+     * @param value the value
+     * @throws InvalidHeaderValueException if the value is invalid.
+     *         RuntimeException if an undeclared error occurs.
+     */
+    public void setDeliveryReport(int value) throws InvalidHeaderValueException {
+        mPduHeaders.setOctet(value, PduHeaders.DELIVERY_REPORT);
+    }
+
+    /*
+     * Optional, not supported header fields:
+     *
+     *     public byte[] getApplicId() {return null;}
+     *     public void setApplicId(byte[] value) {}
+     *
+     *     public byte[] getAuxApplicId() {return null;}
+     *     public void getAuxApplicId(byte[] value) {}
+     *
+     *     public byte getDrmContent() {return 0x00;}
+     *     public void setDrmContent(byte value) {}
+     *
+     *     public byte getDistributionIndicator() {return 0x00;}
+     *     public void setDistributionIndicator(byte value) {}
+     *
+     *     public ElementDescriptorValue getElementDescriptor() {return null;}
+     *     public void getElementDescriptor(ElementDescriptorValue value) {}
+     *
+     *     public byte getPriority() {return 0x00;}
+     *     public void setPriority(byte value) {}
+     *
+     *     public byte getRecommendedRetrievalMode() {return 0x00;}
+     *     public void setRecommendedRetrievalMode(byte value) {}
+     *
+     *     public byte getRecommendedRetrievalModeText() {return 0x00;}
+     *     public void setRecommendedRetrievalModeText(byte value) {}
+     *
+     *     public byte[] getReplaceId() {return 0x00;}
+     *     public void setReplaceId(byte[] value) {}
+     *
+     *     public byte[] getReplyApplicId() {return 0x00;}
+     *     public void setReplyApplicId(byte[] value) {}
+     *
+     *     public byte getReplyCharging() {return 0x00;}
+     *     public void setReplyCharging(byte value) {}
+     *
+     *     public byte getReplyChargingDeadline() {return 0x00;}
+     *     public void setReplyChargingDeadline(byte value) {}
+     *
+     *     public byte[] getReplyChargingId() {return 0x00;}
+     *     public void setReplyChargingId(byte[] value) {}
+     *
+     *     public long getReplyChargingSize() {return 0;}
+     *     public void setReplyChargingSize(long value) {}
+     *
+     *     public byte getStored() {return 0x00;}
+     *     public void setStored(byte value) {}
+     */
+}
diff --git a/core/java/com/google/android/mms/pdu/NotifyRespInd.java b/core/java/com/google/android/mms/pdu/NotifyRespInd.java
new file mode 100644
index 0000000..2cc2fce
--- /dev/null
+++ b/core/java/com/google/android/mms/pdu/NotifyRespInd.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2007 Esmertec AG.
+ * Copyright (C) 2007 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.google.android.mms.pdu;
+
+import com.google.android.mms.InvalidHeaderValueException;
+
+/**
+ * M-NofifyResp.ind PDU.
+ */
+public class NotifyRespInd extends GenericPdu {
+    /**
+     * Constructor, used when composing a M-NotifyResp.ind pdu.
+     *
+     * @param mmsVersion current version of mms
+     * @param transactionId the transaction-id value
+     * @param status the status value
+     * @throws InvalidHeaderValueException if parameters are invalid.
+     *         NullPointerException if transactionId is null.
+     *         RuntimeException if an undeclared error occurs.
+     */
+    public NotifyRespInd(int mmsVersion,
+                         byte[] transactionId,
+                         int status) throws InvalidHeaderValueException {
+        super();
+        setMessageType(PduHeaders.MESSAGE_TYPE_NOTIFYRESP_IND);
+        setMmsVersion(mmsVersion);
+        setTransactionId(transactionId);
+        setStatus(status);
+    }
+
+    /**
+     * Constructor with given headers.
+     *
+     * @param headers Headers for this PDU.
+     */
+    NotifyRespInd(PduHeaders headers) {
+        super(headers);
+    }
+
+    /**
+     * Get X-Mms-Report-Allowed field value.
+     *
+     * @return the X-Mms-Report-Allowed value
+     */
+    public int getReportAllowed() {
+        return mPduHeaders.getOctet(PduHeaders.REPORT_ALLOWED);
+    }
+
+    /**
+     * Set X-Mms-Report-Allowed field value.
+     *
+     * @param value the value
+     * @throws InvalidHeaderValueException if the value is invalid.
+     *         RuntimeException if an undeclared error occurs.
+     */
+    public void setReportAllowed(int value) throws InvalidHeaderValueException {
+        mPduHeaders.setOctet(value, PduHeaders.REPORT_ALLOWED);
+    }
+
+    /**
+     * Set X-Mms-Status field value.
+     *
+     * @param value the value
+     * @throws InvalidHeaderValueException if the value is invalid.
+     *         RuntimeException if an undeclared error occurs.
+     */
+    public void setStatus(int value) throws InvalidHeaderValueException {
+        mPduHeaders.setOctet(value, PduHeaders.STATUS);
+    }
+
+    /**
+     * GetX-Mms-Status field value.
+     *
+     * @return the X-Mms-Status value
+     */
+    public int getStatus() {
+        return mPduHeaders.getOctet(PduHeaders.STATUS);
+    }
+
+    /**
+     * Get X-Mms-Transaction-Id field value.
+     *
+     * @return the X-Mms-Report-Allowed value
+     */
+    public byte[] getTransactionId() {
+        return mPduHeaders.getTextString(PduHeaders.TRANSACTION_ID);
+    }
+
+    /**
+     * Set X-Mms-Transaction-Id field value.
+     *
+     * @param value the value
+     * @throws NullPointerException if the value is null.
+     *         RuntimeException if an undeclared error occurs.
+     */
+    public void setTransactionId(byte[] value) {
+            mPduHeaders.setTextString(value, PduHeaders.TRANSACTION_ID);
+    }
+}
diff --git a/core/java/com/google/android/mms/pdu/PduBody.java b/core/java/com/google/android/mms/pdu/PduBody.java
new file mode 100644
index 0000000..fa0416c
--- /dev/null
+++ b/core/java/com/google/android/mms/pdu/PduBody.java
@@ -0,0 +1,191 @@
+/*
+ * Copyright (C) 2007 Esmertec AG.
+ * Copyright (C) 2007 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.google.android.mms.pdu;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Vector;
+
+public class PduBody {
+    private Vector<PduPart> mParts = null;
+
+    private Map<String, PduPart> mPartMapByContentId = null;
+    private Map<String, PduPart> mPartMapByContentLocation = null;
+    private Map<String, PduPart> mPartMapByName = null;
+    private Map<String, PduPart> mPartMapByFileName = null;
+
+    /**
+     * Constructor.
+     */
+    public PduBody() {
+        mParts = new Vector<PduPart>();
+
+        mPartMapByContentId = new HashMap<String, PduPart>();
+        mPartMapByContentLocation  = new HashMap<String, PduPart>();
+        mPartMapByName = new HashMap<String, PduPart>();
+        mPartMapByFileName = new HashMap<String, PduPart>();
+    }
+
+    private void putPartToMaps(PduPart part) {
+        // Put part to mPartMapByContentId.
+        byte[] contentId = part.getContentId();
+        if(null != contentId) {
+            mPartMapByContentId.put(new String(contentId), part);
+        }
+
+        // Put part to mPartMapByContentLocation.
+        byte[] contentLocation = part.getContentLocation();
+        if(null != contentLocation) {
+            String clc = new String(contentLocation);
+            mPartMapByContentLocation.put(clc, part);
+        }
+
+        // Put part to mPartMapByName.
+        byte[] name = part.getName();
+        if(null != name) {
+            String clc = new String(name);
+            mPartMapByName.put(clc, part);
+        }
+
+        // Put part to mPartMapByFileName.
+        byte[] fileName = part.getFilename();
+        if(null != fileName) {
+            String clc = new String(fileName);
+            mPartMapByFileName.put(clc, part);
+        }
+    }
+
+    /**
+     * Appends the specified part to the end of this body.
+     *
+     * @param part part to be appended
+     * @return true when success, false when fail
+     * @throws NullPointerException when part is null
+     */
+    public boolean addPart(PduPart part) {
+        if(null == part) {
+            throw new NullPointerException();
+        }
+
+        putPartToMaps(part);
+        return mParts.add(part);
+    }
+
+    /**
+     * Inserts the specified part at the specified position.
+     *
+     * @param index index at which the specified part is to be inserted
+     * @param part part to be inserted
+     * @throws NullPointerException when part is null
+     */
+    public void addPart(int index, PduPart part) {
+        if(null == part) {
+            throw new NullPointerException();
+        }
+
+        putPartToMaps(part);
+        mParts.add(index, part);
+    }
+
+    /**
+     * Removes the part at the specified position.
+     *
+     * @param index index of the part to return
+     * @return part at the specified index
+     */
+    public PduPart removePart(int index) {
+        return mParts.remove(index);
+    }
+
+    /**
+     * Remove all of the parts.
+     */
+    public void removeAll() {
+        mParts.clear();
+    }
+
+    /**
+     * Get the part at the specified position.
+     *
+     * @param index index of the part to return
+     * @return part at the specified index
+     */
+    public PduPart getPart(int index) {
+        return mParts.get(index);
+    }
+
+    /**
+     * Get the index of the specified part.
+     *
+     * @param part the part object
+     * @return index the index of the first occurrence of the part in this body
+     */
+    public int getPartIndex(PduPart part) {
+        return mParts.indexOf(part);
+    }
+
+    /**
+     * Get the number of parts.
+     *
+     * @return the number of parts
+     */
+    public int getPartsNum() {
+        return mParts.size();
+    }
+
+    /**
+     * Get pdu part by content id.
+     *
+     * @param cid the value of content id.
+     * @return the pdu part.
+     */
+    public PduPart getPartByContentId(String cid) {
+        return mPartMapByContentId.get(cid);
+    }
+
+    /**
+     * Get pdu part by Content-Location. Content-Location of part is
+     * the same as filename and name(param of content-type).
+     *
+     * @param fileName the value of filename.
+     * @return the pdu part.
+     */
+    public PduPart getPartByContentLocation(String contentLocation) {
+        return mPartMapByContentLocation.get(contentLocation);
+    }
+
+    /**
+     * Get pdu part by name.
+     *
+     * @param fileName the value of filename.
+     * @return the pdu part.
+     */
+    public PduPart getPartByName(String name) {
+        return mPartMapByName.get(name);
+    }
+
+    /**
+     * Get pdu part by filename.
+     *
+     * @param fileName the value of filename.
+     * @return the pdu part.
+     */
+    public PduPart getPartByFileName(String filename) {
+        return mPartMapByFileName.get(filename);
+    }
+}
diff --git a/core/java/com/google/android/mms/pdu/PduComposer.java b/core/java/com/google/android/mms/pdu/PduComposer.java
new file mode 100644
index 0000000..094e992
--- /dev/null
+++ b/core/java/com/google/android/mms/pdu/PduComposer.java
@@ -0,0 +1,1163 @@
+/*
+ * Copyright (C) 2007-2008 Esmertec AG.
+ * Copyright (C) 2007-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.google.android.mms.pdu;
+
+import android.content.ContentResolver;
+import android.content.Context;
+
+import java.io.ByteArrayOutputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Arrays;
+import java.util.HashMap;
+
+public class PduComposer {
+    /**
+     * Address type.
+     */
+    static private final int PDU_PHONE_NUMBER_ADDRESS_TYPE = 1;
+    static private final int PDU_EMAIL_ADDRESS_TYPE = 2;
+    static private final int PDU_IPV4_ADDRESS_TYPE = 3;
+    static private final int PDU_IPV6_ADDRESS_TYPE = 4;
+    static private final int PDU_UNKNOWN_ADDRESS_TYPE = 5;
+
+    /**
+     * Address regular expression string.
+     */
+    static final String REGEXP_PHONE_NUMBER_ADDRESS_TYPE = "\\+?[0-9|\\.|\\-]+";
+    static final String REGEXP_EMAIL_ADDRESS_TYPE = "[a-zA-Z| ]*\\<{0,1}[a-zA-Z| ]+@{1}" +
+            "[a-zA-Z| ]+\\.{1}[a-zA-Z| ]+\\>{0,1}";
+    static final String REGEXP_IPV6_ADDRESS_TYPE =
+        "[a-fA-F]{4}\\:{1}[a-fA-F0-9]{4}\\:{1}[a-fA-F0-9]{4}\\:{1}" +
+        "[a-fA-F0-9]{4}\\:{1}[a-fA-F0-9]{4}\\:{1}[a-fA-F0-9]{4}\\:{1}" +
+        "[a-fA-F0-9]{4}\\:{1}[a-fA-F0-9]{4}";
+    static final String REGEXP_IPV4_ADDRESS_TYPE = "[0-9]{1,3}\\.{1}[0-9]{1,3}\\.{1}" +
+            "[0-9]{1,3}\\.{1}[0-9]{1,3}";
+
+    /**
+     * The postfix strings of address.
+     */
+    static final String STRING_PHONE_NUMBER_ADDRESS_TYPE = "/TYPE=PLMN";
+    static final String STRING_IPV4_ADDRESS_TYPE = "/TYPE=IPV4";
+    static final String STRING_IPV6_ADDRESS_TYPE = "/TYPE=IPV6";
+
+    /**
+     * Error values.
+     */
+    static private final int PDU_COMPOSE_SUCCESS = 0;
+    static private final int PDU_COMPOSE_CONTENT_ERROR = 1;
+    static private final int PDU_COMPOSE_FIELD_NOT_SET = 2;
+    static private final int PDU_COMPOSE_FIELD_NOT_SUPPORTED = 3;
+
+    /**
+     * WAP values defined in WSP spec.
+     */
+    static private final int QUOTED_STRING_FLAG = 34;
+    static private final int END_STRING_FLAG = 0;
+    static private final int LENGTH_QUOTE = 31;
+    static private final int TEXT_MAX = 127;
+    static private final int SHORT_INTEGER_MAX = 127;
+    static private final int LONG_INTEGER_LENGTH_MAX = 8;
+
+    /**
+     * Block size when read data from InputStream.
+     */
+    static private final int PDU_COMPOSER_BLOCK_SIZE = 1024;
+
+    /**
+     * The output message.
+     */
+    protected ByteArrayOutputStream mMessage = null;
+
+    /**
+     * The PDU.
+     */
+    private GenericPdu mPdu = null;
+
+    /**
+     * Current visiting position of the mMessage.
+     */
+    protected int mPosition = 0;
+
+    /**
+     * Message compose buffer stack.
+     */
+    private BufferStack mStack = null;
+
+    /**
+     * Content resolver.
+     */
+    private final ContentResolver mResolver;
+
+    /**
+     * Header of this pdu.
+     */
+    private PduHeaders mPduHeader = null;
+
+    /**
+     * Map of all content type
+     */
+    private static HashMap<String, Integer> mContentTypeMap = null;
+
+    static {
+        mContentTypeMap = new HashMap<String, Integer>();
+
+        int i;
+        for (i = 0; i < PduContentTypes.contentTypes.length; i++) {
+            mContentTypeMap.put(PduContentTypes.contentTypes[i], i);
+        }
+    }
+
+    /**
+     * Constructor.
+     *
+     * @param context the context
+     * @param pdu the pdu to be composed
+     */
+    public PduComposer(Context context, GenericPdu pdu) {
+        mPdu = pdu;
+        mResolver = context.getContentResolver();
+        mPduHeader = pdu.getPduHeaders();
+        mStack = new BufferStack();
+        mMessage = new ByteArrayOutputStream();
+        mPosition = 0;
+    }
+
+    /**
+     * Make the message. No need to check whether mandatory fields are set,
+     * because the constructors of outgoing pdus are taking care of this.
+     *
+     * @return OutputStream of maked message. Return null if
+     *         the PDU is invalid.
+     */
+    public byte[] make() {
+        // Get Message-type.
+        int type = mPdu.getMessageType();
+
+        /* make the message */
+        switch (type) {
+            case PduHeaders.MESSAGE_TYPE_SEND_REQ:
+                if (makeSendReqPdu() != PDU_COMPOSE_SUCCESS) {
+                    return null;
+                }
+                break;
+            case PduHeaders.MESSAGE_TYPE_NOTIFYRESP_IND:
+                if (makeNotifyResp() != PDU_COMPOSE_SUCCESS) {
+                    return null;
+                }
+                break;
+            case PduHeaders.MESSAGE_TYPE_ACKNOWLEDGE_IND:
+                if (makeAckInd() != PDU_COMPOSE_SUCCESS) {
+                    return null;
+                }
+                break;
+            case PduHeaders.MESSAGE_TYPE_READ_REC_IND:
+                if (makeReadRecInd() != PDU_COMPOSE_SUCCESS) {
+                    return null;
+                }
+                break;
+            default:
+                return null;
+        }
+
+        return mMessage.toByteArray();
+    }
+
+    /**
+     *  Copy buf to mMessage.
+     */
+    protected void arraycopy(byte[] buf, int pos, int length) {
+        mMessage.write(buf, pos, length);
+        mPosition = mPosition + length;
+    }
+
+    /**
+     * Append a byte to mMessage.
+     */
+    protected void append(int value) {
+        mMessage.write(value);
+        mPosition ++;
+    }
+
+    /**
+     * Append short integer value to mMessage.
+     * This implementation doesn't check the validity of parameter, since it
+     * assumes that the values are validated in the GenericPdu setter methods.
+     */
+    protected void appendShortInteger(int value) {
+        /*
+         * From WAP-230-WSP-20010705-a:
+         * Short-integer = OCTET
+         * ; Integers in range 0-127 shall be encoded as a one octet value
+         * ; with the most significant bit set to one (1xxx xxxx) and with
+         * ; the value in the remaining least significant bits.
+         * In our implementation, only low 7 bits are stored and otherwise
+         * bits are ignored.
+         */
+        append((value | 0x80) & 0xff);
+    }
+
+    /**
+     * Append an octet number between 128 and 255 into mMessage.
+     * NOTE:
+     * A value between 0 and 127 should be appended by using appendShortInteger.
+     * This implementation doesn't check the validity of parameter, since it
+     * assumes that the values are validated in the GenericPdu setter methods.
+     */
+    protected void appendOctet(int number) {
+        append(number);
+    }
+
+    /**
+     * Append a short length into mMessage.
+     * This implementation doesn't check the validity of parameter, since it
+     * assumes that the values are validated in the GenericPdu setter methods.
+     */
+    protected void appendShortLength(int value) {
+        /*
+         * From WAP-230-WSP-20010705-a:
+         * Short-length = <Any octet 0-30>
+         */
+        append(value);
+    }
+
+    /**
+     * Append long integer into mMessage. it's used for really long integers.
+     * This implementation doesn't check the validity of parameter, since it
+     * assumes that the values are validated in the GenericPdu setter methods.
+     */
+    protected void appendLongInteger(long longInt) {
+        /*
+         * From WAP-230-WSP-20010705-a:
+         * Long-integer = Short-length Multi-octet-integer
+         * ; The Short-length indicates the length of the Multi-octet-integer
+         * Multi-octet-integer = 1*30 OCTET
+         * ; The content octets shall be an unsigned integer value with the
+         * ; most significant octet encoded first (big-endian representation).
+         * ; The minimum number of octets must be used to encode the value.
+         */
+        int size;
+        long temp = longInt;
+
+        // Count the length of the long integer.
+        for(size = 0; (temp != 0) && (size < LONG_INTEGER_LENGTH_MAX); size++) {
+            temp = (temp >>> 8);
+        }
+
+        // Set Length.
+        appendShortLength(size);
+
+        // Count and set the long integer.
+        int i;
+        int shift = (size -1) * 8;
+
+        for (i = 0; i < size; i++) {
+            append((int)((longInt >>> shift) & 0xff));
+            shift = shift - 8;
+        }
+    }
+
+    /**
+     * Append text string into mMessage.
+     * This implementation doesn't check the validity of parameter, since it
+     * assumes that the values are validated in the GenericPdu setter methods.
+     */
+    protected void appendTextString(byte[] text) {
+        /*
+         * From WAP-230-WSP-20010705-a:
+         * Text-string = [Quote] *TEXT End-of-string
+         * ; If the first character in the TEXT is in the range of 128-255,
+         * ; a Quote character must precede it. Otherwise the Quote character
+         * ;must be omitted. The Quote is not part of the contents.
+         */
+        if (((text[0])&0xff) > TEXT_MAX) { // No need to check for <= 255
+            append(TEXT_MAX);
+        }
+
+        arraycopy(text, 0, text.length);
+        append(0);
+    }
+
+    /**
+     * Append text string into mMessage.
+     * This implementation doesn't check the validity of parameter, since it
+     * assumes that the values are validated in the GenericPdu setter methods.
+     */
+    protected void appendTextString(String str) {
+        /*
+         * From WAP-230-WSP-20010705-a:
+         * Text-string = [Quote] *TEXT End-of-string
+         * ; If the first character in the TEXT is in the range of 128-255,
+         * ; a Quote character must precede it. Otherwise the Quote character
+         * ;must be omitted. The Quote is not part of the contents.
+         */
+        appendTextString(str.getBytes());
+    }
+
+    /**
+     * Append encoded string value to mMessage.
+     * This implementation doesn't check the validity of parameter, since it
+     * assumes that the values are validated in the GenericPdu setter methods.
+     */
+    protected void appendEncodedString(EncodedStringValue enStr) {
+        /*
+         * From OMA-TS-MMS-ENC-V1_3-20050927-C:
+         * Encoded-string-value = Text-string | Value-length Char-set Text-string
+         */
+        assert(enStr != null);
+
+        int charset = enStr.getCharacterSet();
+        byte[] textString = enStr.getTextString();
+        if (null == textString) {
+            return;
+        }
+
+        /*
+         * In the implementation of EncodedStringValue, the charset field will
+         * never be 0. It will always be composed as
+         * Encoded-string-value = Value-length Char-set Text-string
+         */
+        mStack.newbuf();
+        PositionMarker start = mStack.mark();
+
+        appendShortInteger(charset);
+        appendTextString(textString);
+
+        int len = start.getLength();
+        mStack.pop();
+        appendValueLength(len);
+        mStack.copy();
+    }
+
+    /**
+     * Append uintvar integer into mMessage.
+     * This implementation doesn't check the validity of parameter, since it
+     * assumes that the values are validated in the GenericPdu setter methods.
+     */
+    protected void appendUintvarInteger(long value) {
+        /*
+         * From WAP-230-WSP-20010705-a:
+         * To encode a large unsigned integer, split it into 7-bit fragments
+         * and place them in the payloads of multiple octets. The most significant
+         * bits are placed in the first octets with the least significant bits
+         * ending up in the last octet. All octets MUST set the Continue bit to 1
+         * except the last octet, which MUST set the Continue bit to 0.
+         */
+        int i;
+        long max = SHORT_INTEGER_MAX;
+
+        for (i = 0; i < 5; i++) {
+            if (value < max) {
+                break;
+            }
+
+            max = (max << 7) | 0x7fl;
+        }
+
+        while(i > 0) {
+            long temp = value >>> (i * 7);
+            temp = temp & 0x7f;
+
+            append((int)((temp | 0x80) & 0xff));
+
+            i--;
+        }
+
+        append((int)(value & 0x7f));
+    }
+
+    /**
+     * Append date value into mMessage.
+     * This implementation doesn't check the validity of parameter, since it
+     * assumes that the values are validated in the GenericPdu setter methods.
+     */
+    protected void appendDateValue(long date) {
+        /*
+         * From OMA-TS-MMS-ENC-V1_3-20050927-C:
+         * Date-value = Long-integer
+         */
+        appendLongInteger(date);
+    }
+
+    /**
+     * Append value length to mMessage.
+     * This implementation doesn't check the validity of parameter, since it
+     * assumes that the values are validated in the GenericPdu setter methods.
+     */
+    protected void appendValueLength(long value) {
+        /*
+         * From WAP-230-WSP-20010705-a:
+         * Value-length = Short-length | (Length-quote Length)
+         * ; Value length is used to indicate the length of the value to follow
+         * Short-length = <Any octet 0-30>
+         * Length-quote = <Octet 31>
+         * Length = Uintvar-integer
+         */
+        if (value < LENGTH_QUOTE) {
+            appendShortLength((int) value);
+            return;
+        }
+
+        append(LENGTH_QUOTE);
+        appendUintvarInteger(value);
+    }
+
+    /**
+     * Append quoted string to mMessage.
+     * This implementation doesn't check the validity of parameter, since it
+     * assumes that the values are validated in the GenericPdu setter methods.
+     */
+    protected void appendQuotedString(byte[] text) {
+        /*
+         * From WAP-230-WSP-20010705-a:
+         * Quoted-string = <Octet 34> *TEXT End-of-string
+         * ;The TEXT encodes an RFC2616 Quoted-string with the enclosing
+         * ;quotation-marks <"> removed.
+         */
+        append(QUOTED_STRING_FLAG);
+        arraycopy(text, 0, text.length);
+        append(END_STRING_FLAG);
+    }
+
+    /**
+     * Append quoted string to mMessage.
+     * This implementation doesn't check the validity of parameter, since it
+     * assumes that the values are validated in the GenericPdu setter methods.
+     */
+    protected void appendQuotedString(String str) {
+        /*
+         * From WAP-230-WSP-20010705-a:
+         * Quoted-string = <Octet 34> *TEXT End-of-string
+         * ;The TEXT encodes an RFC2616 Quoted-string with the enclosing
+         * ;quotation-marks <"> removed.
+         */
+        appendQuotedString(str.getBytes());
+    }
+
+    /**
+     * Append header to mMessage.
+     */
+    private int appendHeader(int field) {
+        switch (field) {
+            case PduHeaders.MMS_VERSION:
+                appendOctet(field);
+
+                int version = mPduHeader.getOctet(field);
+                if (0 == version) {
+                    appendShortInteger(PduHeaders.CURRENT_MMS_VERSION);
+                } else {
+                    appendShortInteger(version);
+                }
+
+                break;
+
+            case PduHeaders.MESSAGE_ID:
+            case PduHeaders.TRANSACTION_ID:
+                byte[] textString = mPduHeader.getTextString(field);
+                if (null == textString) {
+                    return PDU_COMPOSE_FIELD_NOT_SET;
+                }
+
+                appendOctet(field);
+                appendTextString(textString);
+                break;
+
+            case PduHeaders.TO:
+            case PduHeaders.BCC:
+            case PduHeaders.CC:
+                EncodedStringValue[] addr = mPduHeader.getEncodedStringValues(field);
+
+                if (null == addr) {
+                    return PDU_COMPOSE_FIELD_NOT_SET;
+                }
+
+                EncodedStringValue temp;
+                for (int i = 0; i < addr.length; i++) {
+                    try {
+                        int addressType = checkAddressType(addr[i].getString());
+                        temp = EncodedStringValue.copy(addr[i]);
+                        if (PDU_PHONE_NUMBER_ADDRESS_TYPE == addressType) {
+                            // Phone number.
+                            temp.appendTextString(
+                                    STRING_PHONE_NUMBER_ADDRESS_TYPE.getBytes());
+                        } else if (PDU_IPV4_ADDRESS_TYPE == addressType) {
+                            // Ipv4 address.
+                            temp.appendTextString(STRING_IPV4_ADDRESS_TYPE.getBytes());
+                        } else if (PDU_IPV6_ADDRESS_TYPE == addressType) {
+                            // Ipv6 address.
+                            temp.appendTextString(STRING_IPV6_ADDRESS_TYPE.getBytes());
+                        }
+                    } catch (NullPointerException e) {
+                        return PDU_COMPOSE_CONTENT_ERROR;
+                    }
+
+                    appendOctet(field);
+                    appendEncodedString(temp);
+                }
+                break;
+
+            case PduHeaders.FROM:
+                // Value-length (Address-present-token Encoded-string-value | Insert-address-token)
+                appendOctet(field);
+
+                EncodedStringValue from = mPduHeader.getEncodedStringValue(field);
+                if ((from == null)
+                        || new String(from.getTextString()).equals(
+                                PduHeaders.FROM_INSERT_ADDRESS_TOKEN_STR)) {
+                    // Length of from = 1
+                    append(1);
+                    // Insert-address-token = <Octet 129>
+                    append(PduHeaders.FROM_INSERT_ADDRESS_TOKEN);
+                } else {
+                    mStack.newbuf();
+                    PositionMarker fstart = mStack.mark();
+
+                    // Address-present-token = <Octet 128>
+                    append(PduHeaders.FROM_ADDRESS_PRESENT_TOKEN);
+                    appendEncodedString(from);
+
+                    int flen = fstart.getLength();
+                    mStack.pop();
+                    appendValueLength(flen);
+                    mStack.copy();
+                }
+                break;
+
+            case PduHeaders.READ_STATUS:
+            case PduHeaders.STATUS:
+            case PduHeaders.REPORT_ALLOWED:
+            case PduHeaders.PRIORITY:
+            case PduHeaders.DELIVERY_REPORT:
+            case PduHeaders.READ_REPORT:
+                int octet = mPduHeader.getOctet(field);
+                if (0 == octet) {
+                    return PDU_COMPOSE_FIELD_NOT_SET;
+                }
+
+                appendOctet(field);
+                appendOctet(octet);
+                break;
+
+            case PduHeaders.DATE:
+                long date = mPduHeader.getLongInteger(field);
+                if (-1 == date) {
+                    return PDU_COMPOSE_FIELD_NOT_SET;
+                }
+
+                appendOctet(field);
+                appendDateValue(date);
+                break;
+
+            case PduHeaders.SUBJECT:
+                EncodedStringValue enString =
+                    mPduHeader.getEncodedStringValue(field);
+                if (null == enString) {
+                    return PDU_COMPOSE_FIELD_NOT_SET;
+                }
+
+                appendOctet(field);
+                appendEncodedString(enString);
+                break;
+
+            case PduHeaders.MESSAGE_CLASS:
+                byte[] messageClass = mPduHeader.getTextString(field);
+                if (null == messageClass) {
+                    return PDU_COMPOSE_FIELD_NOT_SET;
+                }
+
+                appendOctet(field);
+                if (Arrays.equals(messageClass,
+                        PduHeaders.MESSAGE_CLASS_ADVERTISEMENT_STR.getBytes())) {
+                    appendOctet(PduHeaders.MESSAGE_CLASS_ADVERTISEMENT);
+                } else if (Arrays.equals(messageClass,
+                        PduHeaders.MESSAGE_CLASS_AUTO_STR.getBytes())) {
+                    appendOctet(PduHeaders.MESSAGE_CLASS_AUTO);
+                } else if (Arrays.equals(messageClass,
+                        PduHeaders.MESSAGE_CLASS_PERSONAL_STR.getBytes())) {
+                    appendOctet(PduHeaders.MESSAGE_CLASS_PERSONAL);
+                } else if (Arrays.equals(messageClass,
+                        PduHeaders.MESSAGE_CLASS_INFORMATIONAL_STR.getBytes())) {
+                    appendOctet(PduHeaders.MESSAGE_CLASS_INFORMATIONAL);
+                } else {
+                    appendTextString(messageClass);
+                }
+                break;
+
+            case PduHeaders.EXPIRY:
+                long expiry = mPduHeader.getLongInteger(field);
+                if (-1 == expiry) {
+                    return PDU_COMPOSE_FIELD_NOT_SET;
+                }
+
+                appendOctet(field);
+
+                mStack.newbuf();
+                PositionMarker expiryStart = mStack.mark();
+
+                append(PduHeaders.VALUE_RELATIVE_TOKEN);
+                appendLongInteger(expiry);
+
+                int expiryLength = expiryStart.getLength();
+                mStack.pop();
+                appendValueLength(expiryLength);
+                mStack.copy();
+                break;
+
+            default:
+                return PDU_COMPOSE_FIELD_NOT_SUPPORTED;
+        }
+
+        return PDU_COMPOSE_SUCCESS;
+    }
+
+    /**
+     * Make ReadRec.Ind.
+     */
+    private int makeReadRecInd() {
+        if (mMessage == null) {
+            mMessage = new ByteArrayOutputStream();
+            mPosition = 0;
+        }
+
+        // X-Mms-Message-Type
+        appendOctet(PduHeaders.MESSAGE_TYPE);
+        appendOctet(PduHeaders.MESSAGE_TYPE_READ_REC_IND);
+
+        // X-Mms-MMS-Version
+        if (appendHeader(PduHeaders.MMS_VERSION) != PDU_COMPOSE_SUCCESS) {
+            return PDU_COMPOSE_CONTENT_ERROR;
+        }
+
+        // Message-ID
+        if (appendHeader(PduHeaders.MESSAGE_ID) != PDU_COMPOSE_SUCCESS) {
+            return PDU_COMPOSE_CONTENT_ERROR;
+        }
+
+        // To
+        if (appendHeader(PduHeaders.TO) != PDU_COMPOSE_SUCCESS) {
+            return PDU_COMPOSE_CONTENT_ERROR;
+        }
+
+        // From
+        if (appendHeader(PduHeaders.FROM) != PDU_COMPOSE_SUCCESS) {
+            return PDU_COMPOSE_CONTENT_ERROR;
+        }
+
+        // Date Optional
+        appendHeader(PduHeaders.DATE);
+
+        // X-Mms-Read-Status
+        if (appendHeader(PduHeaders.READ_STATUS) != PDU_COMPOSE_SUCCESS) {
+            return PDU_COMPOSE_CONTENT_ERROR;
+        }
+
+        // X-Mms-Applic-ID Optional(not support)
+        // X-Mms-Reply-Applic-ID Optional(not support)
+        // X-Mms-Aux-Applic-Info Optional(not support)
+
+        return PDU_COMPOSE_SUCCESS;
+    }
+
+    /**
+     * Make NotifyResp.Ind.
+     */
+    private int makeNotifyResp() {
+        if (mMessage == null) {
+            mMessage = new ByteArrayOutputStream();
+            mPosition = 0;
+        }
+
+        //    X-Mms-Message-Type
+        appendOctet(PduHeaders.MESSAGE_TYPE);
+        appendOctet(PduHeaders.MESSAGE_TYPE_NOTIFYRESP_IND);
+
+        // X-Mms-Transaction-ID
+        if (appendHeader(PduHeaders.TRANSACTION_ID) != PDU_COMPOSE_SUCCESS) {
+            return PDU_COMPOSE_CONTENT_ERROR;
+        }
+
+        // X-Mms-MMS-Version
+        if (appendHeader(PduHeaders.MMS_VERSION) != PDU_COMPOSE_SUCCESS) {
+            return PDU_COMPOSE_CONTENT_ERROR;
+        }
+
+        //  X-Mms-Status
+        if (appendHeader(PduHeaders.STATUS) != PDU_COMPOSE_SUCCESS) {
+            return PDU_COMPOSE_CONTENT_ERROR;
+        }
+
+        // X-Mms-Report-Allowed Optional (not support)
+        return PDU_COMPOSE_SUCCESS;
+    }
+
+    /**
+     * Make Acknowledge.Ind.
+     */
+    private int makeAckInd() {
+        if (mMessage == null) {
+            mMessage = new ByteArrayOutputStream();
+            mPosition = 0;
+        }
+
+        //    X-Mms-Message-Type
+        appendOctet(PduHeaders.MESSAGE_TYPE);
+        appendOctet(PduHeaders.MESSAGE_TYPE_ACKNOWLEDGE_IND);
+
+        // X-Mms-Transaction-ID
+        if (appendHeader(PduHeaders.TRANSACTION_ID) != PDU_COMPOSE_SUCCESS) {
+            return PDU_COMPOSE_CONTENT_ERROR;
+        }
+
+        //     X-Mms-MMS-Version
+        if (appendHeader(PduHeaders.MMS_VERSION) != PDU_COMPOSE_SUCCESS) {
+            return PDU_COMPOSE_CONTENT_ERROR;
+        }
+
+        // X-Mms-Report-Allowed Optional
+        appendHeader(PduHeaders.REPORT_ALLOWED);
+
+        return PDU_COMPOSE_SUCCESS;
+    }
+
+    /**
+     * Make Send.req.
+     */
+    private int makeSendReqPdu() {
+        if (mMessage == null) {
+            mMessage = new ByteArrayOutputStream();
+            mPosition = 0;
+        }
+
+        // X-Mms-Message-Type
+        appendOctet(PduHeaders.MESSAGE_TYPE);
+        appendOctet(PduHeaders.MESSAGE_TYPE_SEND_REQ);
+
+        // X-Mms-Transaction-ID
+        appendOctet(PduHeaders.TRANSACTION_ID);
+
+        byte[] trid = mPduHeader.getTextString(PduHeaders.TRANSACTION_ID);
+        if (trid == null) {
+            // Transaction-ID should be set(by Transaction) before make().
+            throw new IllegalArgumentException("Transaction-ID is null.");
+        }
+        appendTextString(trid);
+
+        //  X-Mms-MMS-Version
+        if (appendHeader(PduHeaders.MMS_VERSION) != PDU_COMPOSE_SUCCESS) {
+            return PDU_COMPOSE_CONTENT_ERROR;
+        }
+
+        // Date Date-value Optional.
+        appendHeader(PduHeaders.DATE);
+
+        // From
+        if (appendHeader(PduHeaders.FROM) != PDU_COMPOSE_SUCCESS) {
+            return PDU_COMPOSE_CONTENT_ERROR;
+        }
+
+        boolean recipient = false;
+
+        // To
+        if (appendHeader(PduHeaders.TO) != PDU_COMPOSE_CONTENT_ERROR) {
+            recipient = true;
+        }
+
+        // Cc
+        if (appendHeader(PduHeaders.CC) != PDU_COMPOSE_CONTENT_ERROR) {
+            recipient = true;
+        }
+
+        // Bcc
+        if (appendHeader(PduHeaders.BCC) != PDU_COMPOSE_CONTENT_ERROR) {
+            recipient = true;
+        }
+
+        // Need at least one of "cc", "bcc" and "to".
+        if (false == recipient) {
+            return PDU_COMPOSE_CONTENT_ERROR;
+        }
+
+        // Subject Optional
+        appendHeader(PduHeaders.SUBJECT);
+
+        // X-Mms-Message-Class Optional
+        // Message-class-value = Class-identifier | Token-text
+        appendHeader(PduHeaders.MESSAGE_CLASS);
+
+        // X-Mms-Expiry Optional
+        appendHeader(PduHeaders.EXPIRY);
+
+        // X-Mms-Priority Optional
+        appendHeader(PduHeaders.PRIORITY);
+
+        // X-Mms-Delivery-Report Optional
+        appendHeader(PduHeaders.DELIVERY_REPORT);
+
+        // X-Mms-Read-Report Optional
+        appendHeader(PduHeaders.READ_REPORT);
+
+        //    Content-Type
+        appendOctet(PduHeaders.CONTENT_TYPE);
+
+        //  Message body
+        makeMessageBody();
+
+        return PDU_COMPOSE_SUCCESS;  // Composing the message is OK
+    }
+
+    /**
+     * Make message body.
+     */
+    private int makeMessageBody() {
+        // 1. add body informations
+        mStack.newbuf();  // Switching buffer because we need to
+
+        PositionMarker ctStart = mStack.mark();
+
+        // This contentTypeIdentifier should be used for type of attachment...
+        String contentType = new String(
+                mPduHeader.getTextString(PduHeaders.CONTENT_TYPE));
+        Integer contentTypeIdentifier = mContentTypeMap.get(contentType);
+        if (contentTypeIdentifier == null) {
+            // content type is mandatory
+            return PDU_COMPOSE_CONTENT_ERROR;
+        }
+
+        appendShortInteger(contentTypeIdentifier.intValue());
+
+        // content-type parameter: start
+        PduBody body = ((SendReq) mPdu).getBody();
+        if (null == body) {
+            // empty message
+            appendUintvarInteger(0);
+            mStack.pop();
+            mStack.copy();
+            return PDU_COMPOSE_SUCCESS;
+        }
+
+        PduPart part;
+        try {
+            part = body.getPart(0);
+
+            byte[] start = part.getContentId();
+            if (start != null) {
+                appendOctet(PduPart.P_DEP_START);
+                if (('<' == start[0]) && ('>' == start[start.length - 1])) {
+                    appendTextString(start);
+                } else {
+                    appendTextString("<" + new String(start) + ">");
+                }
+            }
+
+            // content-type parameter: type
+            appendOctet(PduPart.P_CT_MR_TYPE);
+            appendTextString(part.getContentType());
+        }
+        catch (ArrayIndexOutOfBoundsException e){
+            e.printStackTrace();
+        }
+
+        int ctLength = ctStart.getLength();
+        mStack.pop();
+        appendValueLength(ctLength);
+        mStack.copy();
+
+        // 3. add content
+        int partNum = body.getPartsNum();
+        appendUintvarInteger(partNum);
+        for (int i = 0; i < partNum; i++) {
+            part = body.getPart(i);
+            mStack.newbuf();  // Leaving space for header lengh and data length
+            PositionMarker attachment = mStack.mark();
+
+            mStack.newbuf();  // Leaving space for Content-Type length
+            PositionMarker contentTypeBegin = mStack.mark();
+
+            byte[] partContentType = part.getContentType();
+
+            if (partContentType == null) {
+                // content type is mandatory
+                return PDU_COMPOSE_CONTENT_ERROR;
+            }
+
+            // content-type value
+            Integer partContentTypeIdentifier =
+                mContentTypeMap.get(new String(partContentType));
+            if (partContentTypeIdentifier == null) {
+                appendTextString(partContentType);
+            } else {
+                appendShortInteger(partContentTypeIdentifier.intValue());
+            }
+
+            /* Content-type parameter : name.
+             * The value of name, filename, content-location is the same.
+             * Just one of them is enough for this PDU.
+             */
+            byte[] name = part.getName();
+
+            if (null == name) {
+                name = part.getFilename();
+
+                if (null == name) {
+                    name = part.getContentLocation();
+
+                    if (null == name) {
+                        /* at lease one of name, filename, Content-location
+                         * should be available.
+                         */
+                        return PDU_COMPOSE_CONTENT_ERROR;
+                    }
+                }
+            }
+            appendOctet(PduPart.P_DEP_NAME);
+            appendTextString(name);
+
+            // content-type parameter : charset
+            int charset = part.getCharset();
+            if (charset != 0) {
+                appendOctet(PduPart.P_CHARSET);
+                appendShortInteger(charset);
+            }
+
+            int contentTypeLength = contentTypeBegin.getLength();
+            mStack.pop();
+            appendValueLength(contentTypeLength);
+            mStack.copy();
+
+            // content id
+            byte[] contentId = part.getContentId();
+
+            if (null != contentId) {
+                appendOctet(PduPart.P_CONTENT_ID);
+                if (('<' == contentId[0]) && ('>' == contentId[contentId.length - 1])) {
+                    appendQuotedString(contentId);
+                } else {
+                    appendQuotedString("<" + new String(contentId) + ">");
+                }
+            }
+
+            // content-location
+            byte[] contentLocation = part.getContentLocation();
+            if (null != contentLocation) {
+            	appendOctet(PduPart.P_CONTENT_LOCATION);
+            	appendTextString(contentLocation);
+            }
+
+            // content
+            int headerLength = attachment.getLength();
+
+            int dataLength = 0; // Just for safety...
+            byte[] partData = part.getData();
+
+            if (partData != null) {
+                arraycopy(partData, 0, partData.length);
+                dataLength = partData.length;
+            } else {
+                InputStream cr;
+                try {
+                    byte[] buffer = new byte[PDU_COMPOSER_BLOCK_SIZE];
+                    cr = mResolver.openInputStream(part.getDataUri());
+                    int len = 0;
+                    while ((len = cr.read(buffer)) != -1) {
+                        mMessage.write(buffer, 0, len);
+                        mPosition += len;
+                        dataLength += len;
+                    }
+                } catch (FileNotFoundException e) {
+                    return PDU_COMPOSE_CONTENT_ERROR;
+                } catch (IOException e) {
+                    return PDU_COMPOSE_CONTENT_ERROR;
+                } catch (RuntimeException e) {
+                    return PDU_COMPOSE_CONTENT_ERROR;
+                }
+            }
+
+            if (dataLength != (attachment.getLength() - headerLength)) {
+                throw new RuntimeException("BUG: Length sanity check failed");
+            }
+
+            mStack.pop();
+            appendUintvarInteger(headerLength);
+            appendUintvarInteger(dataLength);
+            mStack.copy();
+        }
+
+        return PDU_COMPOSE_SUCCESS;
+    }
+
+    /**
+     *  Record current message informations.
+     */
+    static private class LengthRecordNode {
+        ByteArrayOutputStream currentMessage = null;
+        public int currentPosition = 0;
+
+        public LengthRecordNode next = null;
+    }
+
+    /**
+     * Mark current message position and stact size.
+     */
+    private class PositionMarker {
+        private int c_pos;   // Current position
+        private int currentStackSize;  // Current stack size
+
+        int getLength() {
+            // If these assert fails, likely that you are finding the
+            // size of buffer that is deep in BufferStack you can only
+            // find the length of the buffer that is on top
+            if (currentStackSize != mStack.stackSize) {
+                throw new RuntimeException("BUG: Invalid call to getLength()");
+            }
+
+            return mPosition - c_pos;
+        }
+    }
+
+    /**
+     * This implementation can be OPTIMIZED to use only
+     * 2 buffers. This optimization involves changing BufferStack
+     * only... Its usage (interface) will not change.
+     */
+    private class BufferStack {
+        private LengthRecordNode stack = null;
+        private LengthRecordNode toCopy = null;
+
+        int stackSize = 0;
+
+        /**
+         *  Create a new message buffer and push it into the stack.
+         */
+        void newbuf() {
+            // You can't create a new buff when toCopy != null
+            // That is after calling pop() and before calling copy()
+            // If you do, it is a bug
+            if (toCopy != null) {
+                throw new RuntimeException("BUG: Invalid newbuf() before copy()");
+            }
+
+            LengthRecordNode temp = new LengthRecordNode();
+
+            temp.currentMessage = mMessage;
+            temp.currentPosition = mPosition;
+
+            temp.next = stack;
+            stack = temp;
+
+            stackSize = stackSize + 1;
+
+            mMessage = new ByteArrayOutputStream();
+            mPosition = 0;
+        }
+
+        /**
+         *  Pop the message before and record current message in the stack.
+         */
+        void pop() {
+            ByteArrayOutputStream currentMessage = mMessage;
+            int currentPosition = mPosition;
+
+            mMessage = stack.currentMessage;
+            mPosition = stack.currentPosition;
+
+            toCopy = stack;
+            // Re using the top element of the stack to avoid memory allocation
+
+            stack = stack.next;
+            stackSize = stackSize - 1;
+
+            toCopy.currentMessage = currentMessage;
+            toCopy.currentPosition = currentPosition;
+        }
+
+        /**
+         *  Append current message to the message before.
+         */
+        void copy() {
+            arraycopy(toCopy.currentMessage.toByteArray(), 0,
+                    toCopy.currentPosition);
+
+            toCopy = null;
+        }
+
+        /**
+         *  Mark current message position
+         */
+        PositionMarker mark() {
+            PositionMarker m = new PositionMarker();
+
+            m.c_pos = mPosition;
+            m.currentStackSize = stackSize;
+
+            return m;
+        }
+    }
+
+    /**
+     * Check address type.
+     *
+     * @param address address string without the postfix stinng type,
+     *        such as "/TYPE=PLMN", "/TYPE=IPv6" and "/TYPE=IPv4"
+     * @return PDU_PHONE_NUMBER_ADDRESS_TYPE if it is phone number,
+     *         PDU_EMAIL_ADDRESS_TYPE if it is email address,
+     *         PDU_IPV4_ADDRESS_TYPE if it is ipv4 address,
+     *         PDU_IPV6_ADDRESS_TYPE if it is ipv6 address,
+     *         PDU_UNKNOWN_ADDRESS_TYPE if it is unknown.
+     */
+    protected static int checkAddressType(String address) {
+        /**
+         * From OMA-TS-MMS-ENC-V1_3-20050927-C.pdf, section 8.
+         * address = ( e-mail / device-address / alphanum-shortcode / num-shortcode)
+         * e-mail = mailbox; to the definition of mailbox as described in
+         * section 3.4 of [RFC2822], but excluding the
+         * obsolete definitions as indicated by the "obs-" prefix.
+         * device-address = ( global-phone-number "/TYPE=PLMN" )
+         * / ( ipv4 "/TYPE=IPv4" ) / ( ipv6 "/TYPE=IPv6" )
+         * / ( escaped-value "/TYPE=" address-type )
+         *
+         * global-phone-number = ["+"] 1*( DIGIT / written-sep )
+         * written-sep =("-"/".")
+         *
+         * ipv4 = 1*3DIGIT 3( "." 1*3DIGIT ) ; IPv4 address value
+         *
+         * ipv6 = 4HEXDIG 7( ":" 4HEXDIG ) ; IPv6 address per RFC 2373
+         */
+
+        if (null == address) {
+            return PDU_UNKNOWN_ADDRESS_TYPE;
+        }
+
+        if (address.matches(REGEXP_IPV4_ADDRESS_TYPE)) {
+            // Ipv4 address.
+            return PDU_IPV4_ADDRESS_TYPE;
+        }else if (address.matches(REGEXP_PHONE_NUMBER_ADDRESS_TYPE)) {
+            // Phone number.
+            return PDU_PHONE_NUMBER_ADDRESS_TYPE;
+        } else if (address.matches(REGEXP_EMAIL_ADDRESS_TYPE)) {
+            // Email address.
+            return PDU_EMAIL_ADDRESS_TYPE;
+        } else if (address.matches(REGEXP_IPV6_ADDRESS_TYPE)) {
+            // Ipv6 address.
+            return PDU_IPV6_ADDRESS_TYPE;
+        } else {
+            // Unknown address.
+            return PDU_UNKNOWN_ADDRESS_TYPE;
+        }
+    }
+}
diff --git a/core/java/com/google/android/mms/pdu/PduContentTypes.java b/core/java/com/google/android/mms/pdu/PduContentTypes.java
new file mode 100644
index 0000000..7799e0e
--- /dev/null
+++ b/core/java/com/google/android/mms/pdu/PduContentTypes.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2007 Esmertec AG.
+ * Copyright (C) 2007 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.google.android.mms.pdu;
+
+public class PduContentTypes {
+    /**
+     * All content types. From:
+     * http://www.openmobilealliance.org/tech/omna/omna-wsp-content-type.htm
+     */
+    static final String[] contentTypes = {
+        "*/*",                                        /* 0x00 */
+        "text/*",                                     /* 0x01 */
+        "text/html",                                  /* 0x02 */
+        "text/plain",                                 /* 0x03 */
+        "text/x-hdml",                                /* 0x04 */
+        "text/x-ttml",                                /* 0x05 */
+        "text/x-vCalendar",                           /* 0x06 */
+        "text/x-vCard",                               /* 0x07 */
+        "text/vnd.wap.wml",                           /* 0x08 */
+        "text/vnd.wap.wmlscript",                     /* 0x09 */
+        "text/vnd.wap.wta-event",                     /* 0x0A */
+        "multipart/*",                                /* 0x0B */
+        "multipart/mixed",                            /* 0x0C */
+        "multipart/form-data",                        /* 0x0D */
+        "multipart/byterantes",                       /* 0x0E */
+        "multipart/alternative",                      /* 0x0F */
+        "application/*",                              /* 0x10 */
+        "application/java-vm",                        /* 0x11 */
+        "application/x-www-form-urlencoded",          /* 0x12 */
+        "application/x-hdmlc",                        /* 0x13 */
+        "application/vnd.wap.wmlc",                   /* 0x14 */
+        "application/vnd.wap.wmlscriptc",             /* 0x15 */
+        "application/vnd.wap.wta-eventc",             /* 0x16 */
+        "application/vnd.wap.uaprof",                 /* 0x17 */
+        "application/vnd.wap.wtls-ca-certificate",    /* 0x18 */
+        "application/vnd.wap.wtls-user-certificate",  /* 0x19 */
+        "application/x-x509-ca-cert",                 /* 0x1A */
+        "application/x-x509-user-cert",               /* 0x1B */
+        "image/*",                                    /* 0x1C */
+        "image/gif",                                  /* 0x1D */
+        "image/jpeg",                                 /* 0x1E */
+        "image/tiff",                                 /* 0x1F */
+        "image/png",                                  /* 0x20 */
+        "image/vnd.wap.wbmp",                         /* 0x21 */
+        "application/vnd.wap.multipart.*",            /* 0x22 */
+        "application/vnd.wap.multipart.mixed",        /* 0x23 */
+        "application/vnd.wap.multipart.form-data",    /* 0x24 */
+        "application/vnd.wap.multipart.byteranges",   /* 0x25 */
+        "application/vnd.wap.multipart.alternative",  /* 0x26 */
+        "application/xml",                            /* 0x27 */
+        "text/xml",                                   /* 0x28 */
+        "application/vnd.wap.wbxml",                  /* 0x29 */
+        "application/x-x968-cross-cert",              /* 0x2A */
+        "application/x-x968-ca-cert",                 /* 0x2B */
+        "application/x-x968-user-cert",               /* 0x2C */
+        "text/vnd.wap.si",                            /* 0x2D */
+        "application/vnd.wap.sic",                    /* 0x2E */
+        "text/vnd.wap.sl",                            /* 0x2F */
+        "application/vnd.wap.slc",                    /* 0x30 */
+        "text/vnd.wap.co",                            /* 0x31 */
+        "application/vnd.wap.coc",                    /* 0x32 */
+        "application/vnd.wap.multipart.related",      /* 0x33 */
+        "application/vnd.wap.sia",                    /* 0x34 */
+        "text/vnd.wap.connectivity-xml",              /* 0x35 */
+        "application/vnd.wap.connectivity-wbxml",     /* 0x36 */
+        "application/pkcs7-mime",                     /* 0x37 */
+        "application/vnd.wap.hashed-certificate",     /* 0x38 */
+        "application/vnd.wap.signed-certificate",     /* 0x39 */
+        "application/vnd.wap.cert-response",          /* 0x3A */
+        "application/xhtml+xml",                      /* 0x3B */
+        "application/wml+xml",                        /* 0x3C */
+        "text/css",                                   /* 0x3D */
+        "application/vnd.wap.mms-message",            /* 0x3E */
+        "application/vnd.wap.rollover-certificate",   /* 0x3F */
+        "application/vnd.wap.locc+wbxml",             /* 0x40 */
+        "application/vnd.wap.loc+xml",                /* 0x41 */
+        "application/vnd.syncml.dm+wbxml",            /* 0x42 */
+        "application/vnd.syncml.dm+xml",              /* 0x43 */
+        "application/vnd.syncml.notification",        /* 0x44 */
+        "application/vnd.wap.xhtml+xml",              /* 0x45 */
+        "application/vnd.wv.csp.cir",                 /* 0x46 */
+        "application/vnd.oma.dd+xml",                 /* 0x47 */
+        "application/vnd.oma.drm.message",            /* 0x48 */
+        "application/vnd.oma.drm.content",            /* 0x49 */
+        "application/vnd.oma.drm.rights+xml",         /* 0x4A */
+        "application/vnd.oma.drm.rights+wbxml",       /* 0x4B */
+        "application/vnd.wv.csp+xml",                 /* 0x4C */
+        "application/vnd.wv.csp+wbxml",               /* 0x4D */
+        "application/vnd.syncml.ds.notification",     /* 0x4E */
+        "audio/*",                                    /* 0x4F */
+        "video/*",                                    /* 0x50 */
+        "application/vnd.oma.dd2+xml",                /* 0x51 */
+        "application/mikey"                           /* 0x52 */
+    };
+}
diff --git a/core/java/com/google/android/mms/pdu/PduHeaders.java b/core/java/com/google/android/mms/pdu/PduHeaders.java
new file mode 100644
index 0000000..4313815
--- /dev/null
+++ b/core/java/com/google/android/mms/pdu/PduHeaders.java
@@ -0,0 +1,721 @@
+/*
+ * Copyright (C) 2007 Esmertec AG.
+ * Copyright (C) 2007 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.google.android.mms.pdu;
+
+import com.google.android.mms.InvalidHeaderValueException;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+
+public class PduHeaders {
+    /**
+     * All pdu header fields.
+     */
+    public static final int BCC                             = 0x81;
+    public static final int CC                              = 0x82;
+    public static final int CONTENT_LOCATION                = 0x83;
+    public static final int CONTENT_TYPE                    = 0x84;
+    public static final int DATE                            = 0x85;
+    public static final int DELIVERY_REPORT                 = 0x86;
+    public static final int DELIVERY_TIME                   = 0x87;
+    public static final int EXPIRY                          = 0x88;
+    public static final int FROM                            = 0x89;
+    public static final int MESSAGE_CLASS                   = 0x8A;
+    public static final int MESSAGE_ID                      = 0x8B;
+    public static final int MESSAGE_TYPE                    = 0x8C;
+    public static final int MMS_VERSION                     = 0x8D;
+    public static final int MESSAGE_SIZE                    = 0x8E;
+    public static final int PRIORITY                        = 0x8F;
+
+    public static final int READ_REPLY                      = 0x90;
+    public static final int READ_REPORT                     = 0x90;
+    public static final int REPORT_ALLOWED                  = 0x91;
+    public static final int RESPONSE_STATUS                 = 0x92;
+    public static final int RESPONSE_TEXT                   = 0x93;
+    public static final int SENDER_VISIBILITY               = 0x94;
+    public static final int STATUS                          = 0x95;
+    public static final int SUBJECT                         = 0x96;
+    public static final int TO                              = 0x97;
+    public static final int TRANSACTION_ID                  = 0x98;
+    public static final int RETRIEVE_STATUS                 = 0x99;
+    public static final int RETRIEVE_TEXT                   = 0x9A;
+    public static final int READ_STATUS                     = 0x9B;
+    public static final int REPLY_CHARGING                  = 0x9C;
+    public static final int REPLY_CHARGING_DEADLINE         = 0x9D;
+    public static final int REPLY_CHARGING_ID               = 0x9E;
+    public static final int REPLY_CHARGING_SIZE             = 0x9F;
+
+    public static final int PREVIOUSLY_SENT_BY              = 0xA0;
+    public static final int PREVIOUSLY_SENT_DATE            = 0xA1;
+    public static final int STORE                           = 0xA2;
+    public static final int MM_STATE                        = 0xA3;
+    public static final int MM_FLAGS                        = 0xA4;
+    public static final int STORE_STATUS                    = 0xA5;
+    public static final int STORE_STATUS_TEXT               = 0xA6;
+    public static final int STORED                          = 0xA7;
+    public static final int ATTRIBUTES                      = 0xA8;
+    public static final int TOTALS                          = 0xA9;
+    public static final int MBOX_TOTALS                     = 0xAA;
+    public static final int QUOTAS                          = 0xAB;
+    public static final int MBOX_QUOTAS                     = 0xAC;
+    public static final int MESSAGE_COUNT                   = 0xAD;
+    public static final int CONTENT                         = 0xAE;
+    public static final int START                           = 0xAF;
+
+    public static final int ADDITIONAL_HEADERS              = 0xB0;
+    public static final int DISTRIBUTION_INDICATOR          = 0xB1;
+    public static final int ELEMENT_DESCRIPTOR              = 0xB2;
+    public static final int LIMIT                           = 0xB3;
+    public static final int RECOMMENDED_RETRIEVAL_MODE      = 0xB4;
+    public static final int RECOMMENDED_RETRIEVAL_MODE_TEXT = 0xB5;
+    public static final int STATUS_TEXT                     = 0xB6;
+    public static final int APPLIC_ID                       = 0xB7;
+    public static final int REPLY_APPLIC_ID                 = 0xB8;
+    public static final int AUX_APPLIC_ID                   = 0xB9;
+    public static final int CONTENT_CLASS                   = 0xBA;
+    public static final int DRM_CONTENT                     = 0xBB;
+    public static final int ADAPTATION_ALLOWED              = 0xBC;
+    public static final int REPLACE_ID                      = 0xBD;
+    public static final int CANCEL_ID                       = 0xBE;
+    public static final int CANCEL_STATUS                   = 0xBF;
+
+    /**
+     * X-Mms-Message-Type field types.
+     */
+    public static final int MESSAGE_TYPE_SEND_REQ           = 0x80;
+    public static final int MESSAGE_TYPE_SEND_CONF          = 0x81;
+    public static final int MESSAGE_TYPE_NOTIFICATION_IND   = 0x82;
+    public static final int MESSAGE_TYPE_NOTIFYRESP_IND     = 0x83;
+    public static final int MESSAGE_TYPE_RETRIEVE_CONF      = 0x84;
+    public static final int MESSAGE_TYPE_ACKNOWLEDGE_IND    = 0x85;
+    public static final int MESSAGE_TYPE_DELIVERY_IND       = 0x86;
+    public static final int MESSAGE_TYPE_READ_REC_IND       = 0x87;
+    public static final int MESSAGE_TYPE_READ_ORIG_IND      = 0x88;
+    public static final int MESSAGE_TYPE_FORWARD_REQ        = 0x89;
+    public static final int MESSAGE_TYPE_FORWARD_CONF       = 0x8A;
+    public static final int MESSAGE_TYPE_MBOX_STORE_REQ     = 0x8B;
+    public static final int MESSAGE_TYPE_MBOX_STORE_CONF    = 0x8C;
+    public static final int MESSAGE_TYPE_MBOX_VIEW_REQ      = 0x8D;
+    public static final int MESSAGE_TYPE_MBOX_VIEW_CONF     = 0x8E;
+    public static final int MESSAGE_TYPE_MBOX_UPLOAD_REQ    = 0x8F;
+    public static final int MESSAGE_TYPE_MBOX_UPLOAD_CONF   = 0x90;
+    public static final int MESSAGE_TYPE_MBOX_DELETE_REQ    = 0x91;
+    public static final int MESSAGE_TYPE_MBOX_DELETE_CONF   = 0x92;
+    public static final int MESSAGE_TYPE_MBOX_DESCR         = 0x93;
+    public static final int MESSAGE_TYPE_DELETE_REQ         = 0x94;
+    public static final int MESSAGE_TYPE_DELETE_CONF        = 0x95;
+    public static final int MESSAGE_TYPE_CANCEL_REQ         = 0x96;
+    public static final int MESSAGE_TYPE_CANCEL_CONF        = 0x97;
+
+    /**
+     *  X-Mms-Delivery-Report |
+     *  X-Mms-Read-Report |
+     *  X-Mms-Report-Allowed |
+     *  X-Mms-Sender-Visibility |
+     *  X-Mms-Store |
+     *  X-Mms-Stored |
+     *  X-Mms-Totals |
+     *  X-Mms-Quotas |
+     *  X-Mms-Distribution-Indicator |
+     *  X-Mms-DRM-Content |
+     *  X-Mms-Adaptation-Allowed |
+     *  field types.
+     */
+    public static final int VALUE_YES                       = 0x80;
+    public static final int VALUE_NO                        = 0x81;
+
+    /**
+     *  Delivery-Time |
+     *  Expiry and Reply-Charging-Deadline |
+     *  field type components.
+     */
+    public static final int VALUE_ABSOLUTE_TOKEN            = 0x80;
+    public static final int VALUE_RELATIVE_TOKEN            = 0x81;
+
+    /**
+     * X-Mms-MMS-Version field types.
+     */
+    public static final int MMS_VERSION_1_3                 = ((1 << 4) | 3);
+    public static final int MMS_VERSION_1_2                 = ((1 << 4) | 2);
+    public static final int MMS_VERSION_1_1                 = ((1 << 4) | 1);
+    public static final int MMS_VERSION_1_0                 = ((1 << 4) | 0);
+
+    // Current version is 1.2.
+    public static final int CURRENT_MMS_VERSION             = MMS_VERSION_1_2;
+
+    /**
+     *  From field type components.
+     */
+    public static final int FROM_ADDRESS_PRESENT_TOKEN      = 0x80;
+    public static final int FROM_INSERT_ADDRESS_TOKEN       = 0x81;
+
+    public static final String FROM_ADDRESS_PRESENT_TOKEN_STR = "address-present-token";
+    public static final String FROM_INSERT_ADDRESS_TOKEN_STR = "insert-address-token";
+
+    /**
+     *  X-Mms-Status Field.
+     */
+    public static final int STATUS_EXPIRED                  = 0x80;
+    public static final int STATUS_RETRIEVED                = 0x81;
+    public static final int STATUS_REJECTED                 = 0x82;
+    public static final int STATUS_DEFERRED                 = 0x83;
+    public static final int STATUS_UNRECOGNIZED             = 0x84;
+    public static final int STATUS_INDETERMINATE            = 0x85;
+    public static final int STATUS_FORWARDED                = 0x86;
+    public static final int STATUS_UNREACHABLE              = 0x87;
+
+    /**
+     *  MM-Flags field type components.
+     */
+    public static final int MM_FLAGS_ADD_TOKEN              = 0x80;
+    public static final int MM_FLAGS_REMOVE_TOKEN           = 0x81;
+    public static final int MM_FLAGS_FILTER_TOKEN           = 0x82;
+
+    /**
+     *  X-Mms-Message-Class field types.
+     */
+    public static final int MESSAGE_CLASS_PERSONAL          = 0x80;
+    public static final int MESSAGE_CLASS_ADVERTISEMENT     = 0x81;
+    public static final int MESSAGE_CLASS_INFORMATIONAL     = 0x82;
+    public static final int MESSAGE_CLASS_AUTO              = 0x83;
+
+    public static final String MESSAGE_CLASS_PERSONAL_STR = "personal";
+    public static final String MESSAGE_CLASS_ADVERTISEMENT_STR = "advertisement";
+    public static final String MESSAGE_CLASS_INFORMATIONAL_STR = "informational";
+    public static final String MESSAGE_CLASS_AUTO_STR = "auto";
+
+    /**
+     *  X-Mms-Priority field types.
+     */
+    public static final int PRIORITY_LOW                    = 0x80;
+    public static final int PRIORITY_NORMAL                 = 0x81;
+    public static final int PRIORITY_HIGH                   = 0x82;
+
+    /**
+     *  X-Mms-Response-Status field types.
+     */
+    public static final int RESPONSE_STATUS_OK                   = 0x80;
+    public static final int RESPONSE_STATUS_ERROR_UNSPECIFIED    = 0x81;
+    public static final int RESPONSE_STATUS_ERROR_SERVICE_DENIED = 0x82;
+
+    public static final int RESPONSE_STATUS_ERROR_MESSAGE_FORMAT_CORRUPT     = 0x83;
+    public static final int RESPONSE_STATUS_ERROR_SENDING_ADDRESS_UNRESOLVED = 0x84;
+
+    public static final int RESPONSE_STATUS_ERROR_MESSAGE_NOT_FOUND    = 0x85;
+    public static final int RESPONSE_STATUS_ERROR_NETWORK_PROBLEM      = 0x86;
+    public static final int RESPONSE_STATUS_ERROR_CONTENT_NOT_ACCEPTED = 0x87;
+    public static final int RESPONSE_STATUS_ERROR_UNSUPPORTED_MESSAGE  = 0x88;
+    public static final int RESPONSE_STATUS_ERROR_TRANSIENT_FAILURE    = 0xC0;
+
+    public static final int RESPONSE_STATUS_ERROR_TRANSIENT_SENDNG_ADDRESS_UNRESOLVED = 0xC1;
+    public static final int RESPONSE_STATUS_ERROR_TRANSIENT_MESSAGE_NOT_FOUND         = 0xC2;
+    public static final int RESPONSE_STATUS_ERROR_TRANSIENT_NETWORK_PROBLEM           = 0xC3;
+    public static final int RESPONSE_STATUS_ERROR_TRANSIENT_PARTIAL_SUCCESS           = 0xC4;
+
+    public static final int RESPONSE_STATUS_ERROR_PERMANENT_FAILURE                             = 0xE0;
+    public static final int RESPONSE_STATUS_ERROR_PERMANENT_SERVICE_DENIED                      = 0xE1;
+    public static final int RESPONSE_STATUS_ERROR_PERMANENT_MESSAGE_FORMAT_CORRUPT              = 0xE2;
+    public static final int RESPONSE_STATUS_ERROR_PERMANENT_SENDING_ADDRESS_UNRESOLVED          = 0xE3;
+    public static final int RESPONSE_STATUS_ERROR_PERMANENT_MESSAGE_NOT_FOUND                   = 0xE4;
+    public static final int RESPONSE_STATUS_ERROR_PERMANENT_CONTENT_NOT_ACCEPTED                = 0xE5;
+    public static final int RESPONSE_STATUS_ERROR_PERMANENT_REPLY_CHARGING_LIMITATIONS_NOT_MET  = 0xE6;
+    public static final int RESPONSE_STATUS_ERROR_PERMANENT_REPLY_CHARGING_REQUEST_NOT_ACCEPTED = 0xE6;
+    public static final int RESPONSE_STATUS_ERROR_PERMANENT_REPLY_CHARGING_FORWARDING_DENIED    = 0xE8;
+    public static final int RESPONSE_STATUS_ERROR_PERMANENT_REPLY_CHARGING_NOT_SUPPORTED        = 0xE9;
+    public static final int RESPONSE_STATUS_ERROR_PERMANENT_ADDRESS_HIDING_NOT_SUPPORTED        = 0xEA;
+    public static final int RESPONSE_STATUS_ERROR_PERMANENT_LACK_OF_PREPAID                     = 0xEB;
+    public static final int RESPONSE_STATUS_ERROR_PERMANENT_END                                 = 0xFF;
+
+    /**
+     *  X-Mms-Retrieve-Status field types.
+     */
+    public static final int RETRIEVE_STATUS_OK                                  = 0x80;
+    public static final int RETRIEVE_STATUS_ERROR_TRANSIENT_FAILURE             = 0xC0;
+    public static final int RETRIEVE_STATUS_ERROR_TRANSIENT_MESSAGE_NOT_FOUND   = 0xC1;
+    public static final int RETRIEVE_STATUS_ERROR_TRANSIENT_NETWORK_PROBLEM     = 0xC2;
+    public static final int RETRIEVE_STATUS_ERROR_PERMANENT_FAILURE             = 0xE0;
+    public static final int RETRIEVE_STATUS_ERROR_PERMANENT_SERVICE_DENIED      = 0xE1;
+    public static final int RETRIEVE_STATUS_ERROR_PERMANENT_MESSAGE_NOT_FOUND   = 0xE2;
+    public static final int RETRIEVE_STATUS_ERROR_PERMANENT_CONTENT_UNSUPPORTED = 0xE3;
+    public static final int RETRIEVE_STATUS_ERROR_END                           = 0xFF;
+
+    /**
+     *  X-Mms-Sender-Visibility field types.
+     */
+    public static final int SENDER_VISIBILITY_HIDE          = 0x80;
+    public static final int SENDER_VISIBILITY_SHOW          = 0x81;
+
+    /**
+     *  X-Mms-Read-Status field types.
+     */
+    public static final int READ_STATUS_READ                        = 0x80;
+    public static final int READ_STATUS__DELETED_WITHOUT_BEING_READ = 0x81;
+
+    /**
+     *  X-Mms-Cancel-Status field types.
+     */
+    public static final int CANCEL_STATUS_REQUEST_SUCCESSFULLY_RECEIVED = 0x80;
+    public static final int CANCEL_STATUS_REQUEST_CORRUPTED             = 0x81;
+
+    /**
+     *  X-Mms-Reply-Charging field types.
+     */
+    public static final int REPLY_CHARGING_REQUESTED           = 0x80;
+    public static final int REPLY_CHARGING_REQUESTED_TEXT_ONLY = 0x81;
+    public static final int REPLY_CHARGING_ACCEPTED            = 0x82;
+    public static final int REPLY_CHARGING_ACCEPTED_TEXT_ONLY  = 0x83;
+
+    /**
+     *  X-Mms-MM-State field types.
+     */
+    public static final int MM_STATE_DRAFT                  = 0x80;
+    public static final int MM_STATE_SENT                   = 0x81;
+    public static final int MM_STATE_NEW                    = 0x82;
+    public static final int MM_STATE_RETRIEVED              = 0x83;
+    public static final int MM_STATE_FORWARDED              = 0x84;
+
+    /**
+     * X-Mms-Recommended-Retrieval-Mode field types.
+     */
+    public static final int RECOMMENDED_RETRIEVAL_MODE_MANUAL = 0x80;
+
+    /**
+     *  X-Mms-Content-Class field types.
+     */
+    public static final int CONTENT_CLASS_TEXT              = 0x80;
+    public static final int CONTENT_CLASS_IMAGE_BASIC       = 0x81;
+    public static final int CONTENT_CLASS_IMAGE_RICH        = 0x82;
+    public static final int CONTENT_CLASS_VIDEO_BASIC       = 0x83;
+    public static final int CONTENT_CLASS_VIDEO_RICH        = 0x84;
+    public static final int CONTENT_CLASS_MEGAPIXEL         = 0x85;
+    public static final int CONTENT_CLASS_CONTENT_BASIC     = 0x86;
+    public static final int CONTENT_CLASS_CONTENT_RICH      = 0x87;
+
+    /**
+     *  X-Mms-Store-Status field types.
+     */
+    public static final int STORE_STATUS_SUCCESS                                = 0x80;
+    public static final int STORE_STATUS_ERROR_TRANSIENT_FAILURE                = 0xC0;
+    public static final int STORE_STATUS_ERROR_TRANSIENT_NETWORK_PROBLEM        = 0xC1;
+    public static final int STORE_STATUS_ERROR_PERMANENT_FAILURE                = 0xE0;
+    public static final int STORE_STATUS_ERROR_PERMANENT_SERVICE_DENIED         = 0xE1;
+    public static final int STORE_STATUS_ERROR_PERMANENT_MESSAGE_FORMAT_CORRUPT = 0xE2;
+    public static final int STORE_STATUS_ERROR_PERMANENT_MESSAGE_NOT_FOUND      = 0xE3;
+    public static final int STORE_STATUS_ERROR_PERMANENT_MMBOX_FULL             = 0xE4;
+    public static final int STORE_STATUS_ERROR_END                              = 0xFF;
+
+    /**
+     * The map contains the value of all headers.
+     */
+    private HashMap<Integer, Object> mHeaderMap = null;
+
+    /**
+     * Constructor of PduHeaders.
+     */
+    public PduHeaders() {
+        mHeaderMap = new HashMap<Integer, Object>();
+    }
+
+    /**
+     * Get octet value by header field.
+     *
+     * @param field the field
+     * @return the octet value of the pdu header
+     *          with specified header field. Return 0 if
+     *          the value is not set.
+     */
+    protected int getOctet(int field) {
+        Integer octet = (Integer) mHeaderMap.get(field);
+        if (null == octet) {
+            return 0;
+        }
+
+        return octet;
+    }
+
+    /**
+     * Set octet value to pdu header by header field.
+     *
+     * @param value the value
+     * @param field the field
+     * @throws InvalidHeaderValueException if the value is invalid.
+     */
+    protected void setOctet(int value, int field)
+            throws InvalidHeaderValueException{
+        /**
+         * Check whether this field can be set for specific
+         * header and check validity of the field.
+         */
+        switch (field) {
+            case REPORT_ALLOWED:
+            case ADAPTATION_ALLOWED:
+            case DELIVERY_REPORT:
+            case DRM_CONTENT:
+            case DISTRIBUTION_INDICATOR:
+            case QUOTAS:
+            case READ_REPORT:
+            case STORE:
+            case STORED:
+            case TOTALS:
+            case SENDER_VISIBILITY:
+                if ((VALUE_YES != value) && (VALUE_NO != value)) {
+                    // Invalid value.
+                    throw new InvalidHeaderValueException("Invalid Octet value!");
+                }
+                break;
+            case READ_STATUS:
+                if ((READ_STATUS_READ != value) &&
+                        (READ_STATUS__DELETED_WITHOUT_BEING_READ != value)) {
+                    // Invalid value.
+                    throw new InvalidHeaderValueException("Invalid Octet value!");
+                }
+                break;
+            case CANCEL_STATUS:
+                if ((CANCEL_STATUS_REQUEST_SUCCESSFULLY_RECEIVED != value) &&
+                        (CANCEL_STATUS_REQUEST_CORRUPTED != value)) {
+                    // Invalid value.
+                    throw new InvalidHeaderValueException("Invalid Octet value!");
+                }
+                break;
+            case PRIORITY:
+                if ((value < PRIORITY_LOW) || (value > PRIORITY_HIGH)) {
+                    // Invalid value.
+                    throw new InvalidHeaderValueException("Invalid Octet value!");
+                }
+                break;
+            case STATUS:
+                if ((value < STATUS_EXPIRED) || (value > STATUS_UNREACHABLE)) {
+                    // Invalid value.
+                    throw new InvalidHeaderValueException("Invalid Octet value!");
+                }
+                break;
+            case REPLY_CHARGING:
+                if ((value < REPLY_CHARGING_REQUESTED)
+                        || (value > REPLY_CHARGING_ACCEPTED_TEXT_ONLY)) {
+                    // Invalid value.
+                    throw new InvalidHeaderValueException("Invalid Octet value!");
+                }
+                break;
+            case MM_STATE:
+                if ((value < MM_STATE_DRAFT) || (value > MM_STATE_FORWARDED)) {
+                    // Invalid value.
+                    throw new InvalidHeaderValueException("Invalid Octet value!");
+                }
+                break;
+            case RECOMMENDED_RETRIEVAL_MODE:
+                if (RECOMMENDED_RETRIEVAL_MODE_MANUAL != value) {
+                    // Invalid value.
+                    throw new InvalidHeaderValueException("Invalid Octet value!");
+                }
+                break;
+            case CONTENT_CLASS:
+                if ((value < CONTENT_CLASS_TEXT)
+                        || (value > CONTENT_CLASS_CONTENT_RICH)) {
+                    // Invalid value.
+                    throw new InvalidHeaderValueException("Invalid Octet value!");
+                }
+                break;
+            case RETRIEVE_STATUS:
+                // According to oma-ts-mms-enc-v1_3, section 7.3.50, we modify the invalid value.
+                if ((value > RETRIEVE_STATUS_ERROR_TRANSIENT_NETWORK_PROBLEM) &&
+                        (value < RETRIEVE_STATUS_ERROR_PERMANENT_FAILURE)) {
+                    value = RETRIEVE_STATUS_ERROR_TRANSIENT_FAILURE;
+                } else if ((value > RETRIEVE_STATUS_ERROR_PERMANENT_CONTENT_UNSUPPORTED) &&
+                        (value <= RETRIEVE_STATUS_ERROR_END)) {
+                    value = RETRIEVE_STATUS_ERROR_PERMANENT_FAILURE;
+                } else if ((value < RETRIEVE_STATUS_OK) ||
+                        ((value > RETRIEVE_STATUS_OK) &&
+                                (value < RETRIEVE_STATUS_ERROR_TRANSIENT_FAILURE)) ||
+                                (value > RETRIEVE_STATUS_ERROR_END)) {
+                    value = RETRIEVE_STATUS_ERROR_PERMANENT_FAILURE;
+                }
+                break;
+            case STORE_STATUS:
+                // According to oma-ts-mms-enc-v1_3, section 7.3.58, we modify the invalid value.
+                if ((value > STORE_STATUS_ERROR_TRANSIENT_NETWORK_PROBLEM) &&
+                        (value < STORE_STATUS_ERROR_PERMANENT_FAILURE)) {
+                    value = STORE_STATUS_ERROR_TRANSIENT_FAILURE;
+                } else if ((value > STORE_STATUS_ERROR_PERMANENT_MMBOX_FULL) &&
+                        (value <= STORE_STATUS_ERROR_END)) {
+                    value = STORE_STATUS_ERROR_PERMANENT_FAILURE;
+                } else if ((value < STORE_STATUS_SUCCESS) ||
+                        ((value > STORE_STATUS_SUCCESS) &&
+                                (value < STORE_STATUS_ERROR_TRANSIENT_FAILURE)) ||
+                                (value > STORE_STATUS_ERROR_END)) {
+                    value = STORE_STATUS_ERROR_PERMANENT_FAILURE;
+                }
+                break;
+            case RESPONSE_STATUS:
+                // According to oma-ts-mms-enc-v1_3, section 7.3.48, we modify the invalid value.
+                if ((value > RESPONSE_STATUS_ERROR_TRANSIENT_PARTIAL_SUCCESS) &&
+                        (value < RESPONSE_STATUS_ERROR_PERMANENT_FAILURE)) {
+                    value = RESPONSE_STATUS_ERROR_TRANSIENT_FAILURE;
+                } else if (((value > RESPONSE_STATUS_ERROR_PERMANENT_LACK_OF_PREPAID) &&
+                        (value <= RESPONSE_STATUS_ERROR_PERMANENT_END)) ||
+                        (value < RESPONSE_STATUS_OK) ||
+                        ((value > RESPONSE_STATUS_ERROR_UNSUPPORTED_MESSAGE) &&
+                                (value < RESPONSE_STATUS_ERROR_TRANSIENT_FAILURE)) ||
+                                (value > RESPONSE_STATUS_ERROR_PERMANENT_END)) {
+                    value = RESPONSE_STATUS_ERROR_PERMANENT_FAILURE;
+                }
+                break;
+            case MMS_VERSION:
+                if ((value < MMS_VERSION_1_0)|| (value > MMS_VERSION_1_3)) {
+                    value = CURRENT_MMS_VERSION; // Current version is the default value.
+                }
+                break;
+            case MESSAGE_TYPE:
+                if ((value < MESSAGE_TYPE_SEND_REQ) || (value > MESSAGE_TYPE_CANCEL_CONF)) {
+                    // Invalid value.
+                    throw new InvalidHeaderValueException("Invalid Octet value!");
+                }
+                break;
+            default:
+                // This header value should not be Octect.
+                throw new RuntimeException("Invalid header field!");
+        }
+        mHeaderMap.put(field, value);
+    }
+
+    /**
+     * Get TextString value by header field.
+     *
+     * @param field the field
+     * @return the TextString value of the pdu header
+     *          with specified header field
+     */
+    protected byte[] getTextString(int field) {
+        return (byte[]) mHeaderMap.get(field);
+    }
+
+    /**
+     * Set TextString value to pdu header by header field.
+     *
+     * @param value the value
+     * @param field the field
+     * @return the TextString value of the pdu header
+     *          with specified header field
+     * @throws NullPointerException if the value is null.
+     */
+    protected void setTextString(byte[] value, int field) {
+        /**
+         * Check whether this field can be set for specific
+         * header and check validity of the field.
+         */
+        if (null == value) {
+            throw new NullPointerException();
+        }
+
+        switch (field) {
+            case TRANSACTION_ID:
+            case REPLY_CHARGING_ID:
+            case AUX_APPLIC_ID:
+            case APPLIC_ID:
+            case REPLY_APPLIC_ID:
+            case MESSAGE_ID:
+            case REPLACE_ID:
+            case CANCEL_ID:
+            case CONTENT_LOCATION:
+            case MESSAGE_CLASS:
+            case CONTENT_TYPE:
+                break;
+            default:
+                // This header value should not be Text-String.
+                throw new RuntimeException("Invalid header field!");
+        }
+        mHeaderMap.put(field, value);
+    }
+
+    /**
+     * Get EncodedStringValue value by header field.
+     *
+     * @param field the field
+     * @return the EncodedStringValue value of the pdu header
+     *          with specified header field
+     */
+    protected EncodedStringValue getEncodedStringValue(int field) {
+        return (EncodedStringValue) mHeaderMap.get(field);
+    }
+
+    /**
+     * Get TO, CC or BCC header value.
+     *
+     * @param field the field
+     * @return the EncodeStringValue array of the pdu header
+     *          with specified header field
+     */
+    protected EncodedStringValue[] getEncodedStringValues(int field) {
+        ArrayList<EncodedStringValue> list =
+                (ArrayList<EncodedStringValue>) mHeaderMap.get(field);
+        if (null == list) {
+            return null;
+        }
+        EncodedStringValue[] values = new EncodedStringValue[list.size()];
+        return list.toArray(values);
+    }
+
+    /**
+     * Set EncodedStringValue value to pdu header by header field.
+     *
+     * @param value the value
+     * @param field the field
+     * @return the EncodedStringValue value of the pdu header
+     *          with specified header field
+     * @throws NullPointerException if the value is null.
+     */
+    protected void setEncodedStringValue(EncodedStringValue value, int field) {
+        /**
+         * Check whether this field can be set for specific
+         * header and check validity of the field.
+         */
+        if (null == value) {
+            throw new NullPointerException();
+        }
+
+        switch (field) {
+            case SUBJECT:
+            case RECOMMENDED_RETRIEVAL_MODE_TEXT:
+            case RETRIEVE_TEXT:
+            case STATUS_TEXT:
+            case STORE_STATUS_TEXT:
+            case RESPONSE_TEXT:
+            case FROM:
+            case PREVIOUSLY_SENT_BY:
+            case MM_FLAGS:
+                break;
+            default:
+                // This header value should not be Encoded-String-Value.
+                throw new RuntimeException("Invalid header field!");
+        }
+
+        mHeaderMap.put(field, value);
+    }
+
+    /**
+     * Set TO, CC or BCC header value.
+     *
+     * @param value the value
+     * @param field the field
+     * @return the EncodedStringValue value array of the pdu header
+     *          with specified header field
+     * @throws NullPointerException if the value is null.
+     */
+    protected void setEncodedStringValues(EncodedStringValue[] value, int field) {
+        /**
+         * Check whether this field can be set for specific
+         * header and check validity of the field.
+         */
+        if (null == value) {
+            throw new NullPointerException();
+        }
+
+        switch (field) {
+            case BCC:
+            case CC:
+            case TO:
+                break;
+            default:
+                // This header value should not be Encoded-String-Value.
+                throw new RuntimeException("Invalid header field!");
+        }
+
+        ArrayList<EncodedStringValue> list = new ArrayList<EncodedStringValue>();
+        for (int i = 0; i < value.length; i++) {
+            list.add(value[i]);
+        }
+        mHeaderMap.put(field, list);
+    }
+
+    /**
+     * Append one EncodedStringValue to another.
+     *
+     * @param value the EncodedStringValue to append
+     * @param field the field
+     * @throws NullPointerException if the value is null.
+     */
+    protected void appendEncodedStringValue(EncodedStringValue value,
+                                    int field) {
+        if (null == value) {
+            throw new NullPointerException();
+        }
+
+        switch (field) {
+            case BCC:
+            case CC:
+            case TO:
+                break;
+            default:
+                throw new RuntimeException("Invalid header field!");
+        }
+
+        ArrayList<EncodedStringValue> list =
+            (ArrayList<EncodedStringValue>) mHeaderMap.get(field);
+        if (null == list) {
+            list  = new ArrayList<EncodedStringValue>();
+        }
+        list.add(value);
+        mHeaderMap.put(field, list);
+    }
+
+    /**
+     * Get LongInteger value by header field.
+     *
+     * @param field the field
+     * @return the LongInteger value of the pdu header
+     *          with specified header field. if return -1, the
+     *          field is not existed in pdu header.
+     */
+    protected long getLongInteger(int field) {
+        Long longInteger = (Long) mHeaderMap.get(field);
+        if (null == longInteger) {
+            return -1;
+        }
+
+        return longInteger.longValue();
+    }
+
+    /**
+     * Set LongInteger value to pdu header by header field.
+     *
+     * @param value the value
+     * @param field the field
+     */
+    protected void setLongInteger(long value, int field) {
+        /**
+         * Check whether this field can be set for specific
+         * header and check validity of the field.
+         */
+        switch (field) {
+            case DATE:
+            case REPLY_CHARGING_SIZE:
+            case MESSAGE_SIZE:
+            case MESSAGE_COUNT:
+            case START:
+            case LIMIT:
+            case DELIVERY_TIME:
+            case EXPIRY:
+            case REPLY_CHARGING_DEADLINE:
+            case PREVIOUSLY_SENT_DATE:
+                break;
+            default:
+                // This header value should not be LongInteger.
+                throw new RuntimeException("Invalid header field!");
+        }
+        mHeaderMap.put(field, value);
+    }
+}
diff --git a/core/java/com/google/android/mms/pdu/PduParser.java b/core/java/com/google/android/mms/pdu/PduParser.java
new file mode 100644
index 0000000..d465c5a
--- /dev/null
+++ b/core/java/com/google/android/mms/pdu/PduParser.java
@@ -0,0 +1,1868 @@
+/*
+ * Copyright (C) 2007-2008 Esmertec AG.
+ * Copyright (C) 2007-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.google.android.mms.pdu;
+
+import com.google.android.mms.ContentType;
+import com.google.android.mms.InvalidHeaderValueException;
+
+import android.util.Config;
+import android.util.Log;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.UnsupportedEncodingException;
+import java.util.Arrays;
+import java.util.HashMap;
+
+public class PduParser {
+    /**
+     *  The next are WAP values defined in WSP specification.
+     */
+    private static final int QUOTE = 127;
+    private static final int LENGTH_QUOTE = 31;
+    private static final int TEXT_MIN = 32;
+    private static final int TEXT_MAX = 127;
+    private static final int SHORT_INTEGER_MAX = 127;
+    private static final int SHORT_LENGTH_MAX = 30;
+    private static final int LONG_INTEGER_LENGTH_MAX = 8;
+    private static final int QUOTED_STRING_FLAG = 34;
+    private static final int END_STRING_FLAG = 0x00;
+    //The next two are used by the interface "parseWapString" to
+    //distinguish Text-String and Quoted-String.
+    private static final int TYPE_TEXT_STRING = 0;
+    private static final int TYPE_QUOTED_STRING = 1;
+    private static final int TYPE_TOKEN_STRING = 2;
+
+    /**
+     * Specify the part position.
+     */
+    private static final int THE_FIRST_PART = 0;
+    private static final int THE_LAST_PART = 1;
+
+    /**
+     * The pdu data.
+     */
+    private ByteArrayInputStream mPduDataStream = null;
+
+    /**
+     * Store pdu headers
+     */
+    private PduHeaders mHeaders = null;
+
+    /**
+     * Store pdu parts.
+     */
+    private PduBody mBody = null;
+
+    /**
+     * Store the "type" parameter in "Content-Type" header field.
+     */
+    private static byte[] mTypeParam = null;
+
+    /**
+     * Store the "start" parameter in "Content-Type" header field.
+     */
+    private static byte[] mStartParam = null;
+
+    /**
+     * The log tag.
+     */
+    private static final String LOG_TAG = "PduParser";
+    private static final boolean DEBUG = false;
+    private static final boolean LOCAL_LOGV = DEBUG ? Config.LOGD : Config.LOGV;
+
+    /**
+     * Constructor.
+     *
+     * @param pduDataStream pdu data to be parsed
+     */
+    public PduParser(byte[] pduDataStream) {
+        mPduDataStream = new ByteArrayInputStream(pduDataStream);
+    }
+
+    /**
+     * Parse the pdu.
+     *
+     * @return the pdu structure if parsing successfully.
+     *         null if parsing error happened or mandatory fields are not set.
+     */
+    public GenericPdu parse(){
+        if (mPduDataStream == null) {
+            return null;
+        }
+
+        /* parse headers */
+        mHeaders = parseHeaders(mPduDataStream);
+        if (null == mHeaders) {
+            // Parse headers failed.
+            return null;
+        }
+
+        /* get the message type */
+        int messageType = mHeaders.getOctet(PduHeaders.MESSAGE_TYPE);
+
+        /* check mandatory header fields */
+        if (false == checkMandatoryHeader(mHeaders)) {
+            log("check mandatory headers failed!");
+            return null;
+        }
+
+        if ((PduHeaders.MESSAGE_TYPE_SEND_REQ == messageType) ||
+                (PduHeaders.MESSAGE_TYPE_RETRIEVE_CONF == messageType)) {
+            /* need to parse the parts */
+            mBody = parseParts(mPduDataStream);
+            if (null == mBody) {
+                // Parse parts failed.
+                return null;
+            }
+        }
+
+        switch (messageType) {
+            case PduHeaders.MESSAGE_TYPE_SEND_REQ:
+                SendReq sendReq = new SendReq(mHeaders, mBody);
+                return sendReq;
+            case PduHeaders.MESSAGE_TYPE_SEND_CONF:
+                SendConf sendConf = new SendConf(mHeaders);
+                return sendConf;
+            case PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND:
+                NotificationInd notificationInd =
+                    new NotificationInd(mHeaders);
+                return notificationInd;
+            case PduHeaders.MESSAGE_TYPE_NOTIFYRESP_IND:
+                NotifyRespInd notifyRespInd =
+                    new NotifyRespInd(mHeaders);
+                return notifyRespInd;
+            case PduHeaders.MESSAGE_TYPE_RETRIEVE_CONF:
+                RetrieveConf retrieveConf =
+                    new RetrieveConf(mHeaders, mBody);
+
+                byte[] contentType = retrieveConf.getContentType();
+                if (null == contentType) {
+                    return null;
+                }
+                String ctTypeStr = new String(contentType);
+                if (ctTypeStr.equals(ContentType.MULTIPART_MIXED)
+                        || ctTypeStr.equals(ContentType.MULTIPART_RELATED)) {
+                    // The MMS content type must be "application/vnd.wap.multipart.mixed"
+                    // or "application/vnd.wap.multipart.related"
+                    return retrieveConf;
+                }
+                return null;
+            case PduHeaders.MESSAGE_TYPE_DELIVERY_IND:
+                DeliveryInd deliveryInd =
+                    new DeliveryInd(mHeaders);
+                return deliveryInd;
+            case PduHeaders.MESSAGE_TYPE_ACKNOWLEDGE_IND:
+                AcknowledgeInd acknowledgeInd =
+                    new AcknowledgeInd(mHeaders);
+                return acknowledgeInd;
+            case PduHeaders.MESSAGE_TYPE_READ_ORIG_IND:
+                ReadOrigInd readOrigInd =
+                    new ReadOrigInd(mHeaders);
+                return readOrigInd;
+            case PduHeaders.MESSAGE_TYPE_READ_REC_IND:
+                ReadRecInd readRecInd =
+                    new ReadRecInd(mHeaders);
+                return readRecInd;
+            default:
+                log("Parser doesn't support this message type in this version!");
+            return null;
+        }
+    }
+
+    /**
+     * Parse pdu headers.
+     *
+     * @param pduDataStream pdu data input stream
+     * @return headers in PduHeaders structure, null when parse fail
+     */
+    protected PduHeaders parseHeaders(ByteArrayInputStream pduDataStream){
+        if (pduDataStream == null) {
+            return null;
+        }
+
+        boolean keepParsing = true;
+        PduHeaders headers = new PduHeaders();
+
+        while (keepParsing && (pduDataStream.available() > 0)) {
+            int headerField = extractByteValue(pduDataStream);
+            switch (headerField) {
+                case PduHeaders.MESSAGE_TYPE:
+                {
+                    int messageType = extractByteValue(pduDataStream);
+                    switch (messageType) {
+                        // We don't support these kind of messages now.
+                        case PduHeaders.MESSAGE_TYPE_FORWARD_REQ:
+                        case PduHeaders.MESSAGE_TYPE_FORWARD_CONF:
+                        case PduHeaders.MESSAGE_TYPE_MBOX_STORE_REQ:
+                        case PduHeaders.MESSAGE_TYPE_MBOX_STORE_CONF:
+                        case PduHeaders.MESSAGE_TYPE_MBOX_VIEW_REQ:
+                        case PduHeaders.MESSAGE_TYPE_MBOX_VIEW_CONF:
+                        case PduHeaders.MESSAGE_TYPE_MBOX_UPLOAD_REQ:
+                        case PduHeaders.MESSAGE_TYPE_MBOX_UPLOAD_CONF:
+                        case PduHeaders.MESSAGE_TYPE_MBOX_DELETE_REQ:
+                        case PduHeaders.MESSAGE_TYPE_MBOX_DELETE_CONF:
+                        case PduHeaders.MESSAGE_TYPE_MBOX_DESCR:
+                        case PduHeaders.MESSAGE_TYPE_DELETE_REQ:
+                        case PduHeaders.MESSAGE_TYPE_DELETE_CONF:
+                        case PduHeaders.MESSAGE_TYPE_CANCEL_REQ:
+                        case PduHeaders.MESSAGE_TYPE_CANCEL_CONF:
+                            return null;
+                    }
+                    try {
+                        headers.setOctet(messageType, headerField);
+                    } catch(InvalidHeaderValueException e) {
+                        log("Set invalid Octet value: " + messageType +
+                                " into the header filed: " + headerField);
+                        return null;
+                    } catch(RuntimeException e) {
+                        log(headerField + "is not Octet header field!");
+                        return null;
+                    }
+                    break;
+                }
+                /* Octect value */
+                case PduHeaders.REPORT_ALLOWED:
+                case PduHeaders.ADAPTATION_ALLOWED:
+                case PduHeaders.DELIVERY_REPORT:
+                case PduHeaders.DRM_CONTENT:
+                case PduHeaders.DISTRIBUTION_INDICATOR:
+                case PduHeaders.QUOTAS:
+                case PduHeaders.READ_REPORT:
+                case PduHeaders.STORE:
+                case PduHeaders.STORED:
+                case PduHeaders.TOTALS:
+                case PduHeaders.SENDER_VISIBILITY:
+                case PduHeaders.READ_STATUS:
+                case PduHeaders.CANCEL_STATUS:
+                case PduHeaders.PRIORITY:
+                case PduHeaders.STATUS:
+                case PduHeaders.REPLY_CHARGING:
+                case PduHeaders.MM_STATE:
+                case PduHeaders.RECOMMENDED_RETRIEVAL_MODE:
+                case PduHeaders.CONTENT_CLASS:
+                case PduHeaders.RETRIEVE_STATUS:
+                case PduHeaders.STORE_STATUS:
+                    /**
+                     * The following field has a different value when
+                     * used in the M-Mbox-Delete.conf and M-Delete.conf PDU.
+                     * For now we ignore this fact, since we do not support these PDUs
+                     */
+                case PduHeaders.RESPONSE_STATUS:
+                {
+                    int value = extractByteValue(pduDataStream);
+
+                    try {
+                        headers.setOctet(value, headerField);
+                    } catch(InvalidHeaderValueException e) {
+                        log("Set invalid Octet value: " + value +
+                                " into the header filed: " + headerField);
+                        return null;
+                    } catch(RuntimeException e) {
+                        log(headerField + "is not Octet header field!");
+                        return null;
+                    }
+                    break;
+                }
+
+                /* Long-Integer */
+                case PduHeaders.DATE:
+                case PduHeaders.REPLY_CHARGING_SIZE:
+                case PduHeaders.MESSAGE_SIZE:
+                {
+                    try {
+                        long value = parseLongInteger(pduDataStream);
+                        headers.setLongInteger(value, headerField);
+                    } catch(RuntimeException e) {
+                        log(headerField + "is not Long-Integer header field!");
+                        return null;
+                    }
+                    break;
+                }
+
+                /* Integer-Value */
+                case PduHeaders.MESSAGE_COUNT:
+                case PduHeaders.START:
+                case PduHeaders.LIMIT:
+                {
+                    try {
+                        long value = parseIntegerValue(pduDataStream);
+                        headers.setLongInteger(value, headerField);
+                    } catch(RuntimeException e) {
+                        log(headerField + "is not Long-Integer header field!");
+                        return null;
+                    }
+                    break;
+                }
+
+                /* Text-String */
+                case PduHeaders.TRANSACTION_ID:
+                case PduHeaders.REPLY_CHARGING_ID:
+                case PduHeaders.AUX_APPLIC_ID:
+                case PduHeaders.APPLIC_ID:
+                case PduHeaders.REPLY_APPLIC_ID:
+                    /**
+                     * The next three header fields are email addresses
+                     * as defined in RFC2822,
+                     * not including the characters "<" and ">"
+                     */
+                case PduHeaders.MESSAGE_ID:
+                case PduHeaders.REPLACE_ID:
+                case PduHeaders.CANCEL_ID:
+                    /**
+                     * The following field has a different value when
+                     * used in the M-Mbox-Delete.conf and M-Delete.conf PDU.
+                     * For now we ignore this fact, since we do not support these PDUs
+                     */
+                case PduHeaders.CONTENT_LOCATION:
+                {
+                    byte[] value = parseWapString(pduDataStream, TYPE_TEXT_STRING);
+                    if (null != value) {
+                        try {
+                            headers.setTextString(value, headerField);
+                        } catch(NullPointerException e) {
+                            log("null pointer error!");
+                        } catch(RuntimeException e) {
+                            log(headerField + "is not Text-String header field!");
+                            return null;
+                        }
+                    }
+                    break;
+                }
+
+                /* Encoded-string-value */
+                case PduHeaders.SUBJECT:
+                case PduHeaders.RECOMMENDED_RETRIEVAL_MODE_TEXT:
+                case PduHeaders.RETRIEVE_TEXT:
+                case PduHeaders.STATUS_TEXT:
+                case PduHeaders.STORE_STATUS_TEXT:
+                    /* the next one is not support
+                     * M-Mbox-Delete.conf and M-Delete.conf now */
+                case PduHeaders.RESPONSE_TEXT:
+                {
+                    EncodedStringValue value =
+                        parseEncodedStringValue(pduDataStream);
+                    if (null != value) {
+                        try {
+                            headers.setEncodedStringValue(value, headerField);
+                        } catch(NullPointerException e) {
+                            log("null pointer error!");
+                        } catch (RuntimeException e) {
+                            log(headerField + "is not Encoded-String-Value header field!");
+                            return null;
+                        }
+                    }
+                    break;
+                }
+
+                /* Addressing model */
+                case PduHeaders.BCC:
+                case PduHeaders.CC:
+                case PduHeaders.TO:
+                {
+                    EncodedStringValue value =
+                        parseEncodedStringValue(pduDataStream);
+                    if (null != value) {
+                        byte[] address = value.getTextString();
+                        if (null != address) {
+                            String str = new String(address);
+                            int endIndex = str.indexOf("/");
+                            if (endIndex > 0) {
+                                str = str.substring(0, endIndex);
+                            }
+                            try {
+                                value.setTextString(str.getBytes());
+                            } catch(NullPointerException e) {
+                                log("null pointer error!");
+                                return null;
+                            }
+                        }
+
+                        try {
+                            headers.appendEncodedStringValue(value, headerField);
+                        } catch(NullPointerException e) {
+                            log("null pointer error!");
+                        } catch(RuntimeException e) {
+                            log(headerField + "is not Encoded-String-Value header field!");
+                            return null;
+                        }
+                    }
+                    break;
+                }
+
+                /* Value-length
+                 * (Absolute-token Date-value | Relative-token Delta-seconds-value) */
+                case PduHeaders.DELIVERY_TIME:
+                case PduHeaders.EXPIRY:
+                case PduHeaders.REPLY_CHARGING_DEADLINE:
+                {
+                    /* parse Value-length */
+                    parseValueLength(pduDataStream);
+
+                    /* Absolute-token or Relative-token */
+                    int token = extractByteValue(pduDataStream);
+
+                    /* Date-value or Delta-seconds-value */
+                    long timeValue;
+                    try {
+                        timeValue = parseLongInteger(pduDataStream);
+                    } catch(RuntimeException e) {
+                        log(headerField + "is not Long-Integer header field!");
+                        return null;
+                    }
+                    if (PduHeaders.VALUE_RELATIVE_TOKEN == token) {
+                        /* need to convert the Delta-seconds-value
+                         * into Date-value */
+                        timeValue = System.currentTimeMillis()/1000 + timeValue;
+                    }
+
+                    try {
+                        headers.setLongInteger(timeValue, headerField);
+                    } catch(RuntimeException e) {
+                        log(headerField + "is not Long-Integer header field!");
+                        return null;
+                    }
+                    break;
+                }
+
+                case PduHeaders.FROM: {
+                    /* From-value =
+                     * Value-length
+                     * (Address-present-token Encoded-string-value | Insert-address-token)
+                     */
+                    EncodedStringValue from = null;
+                    parseValueLength(pduDataStream); /* parse value-length */
+
+                    /* Address-present-token or Insert-address-token */
+                    int fromToken = extractByteValue(pduDataStream);
+
+                    /* Address-present-token or Insert-address-token */
+                    if (PduHeaders.FROM_ADDRESS_PRESENT_TOKEN == fromToken) {
+                        /* Encoded-string-value */
+                        from = parseEncodedStringValue(pduDataStream);
+                        if (null != from) {
+                            byte[] address = from.getTextString();
+                            if (null != address) {
+                                String str = new String(address);
+                                int endIndex = str.indexOf("/");
+                                if (endIndex > 0) {
+                                    str = str.substring(0, endIndex);
+                                }
+                                try {
+                                    from.setTextString(str.getBytes());
+                                } catch(NullPointerException e) {
+                                    log("null pointer error!");
+                                    return null;
+                                }
+                            }
+                        }
+                    } else {
+                        try {
+                            from = new EncodedStringValue(
+                                    PduHeaders.FROM_INSERT_ADDRESS_TOKEN_STR.getBytes());
+                        } catch(NullPointerException e) {
+                            log(headerField + "is not Encoded-String-Value header field!");
+                            return null;
+                        }
+                    }
+
+                    try {
+                        headers.setEncodedStringValue(from, PduHeaders.FROM);
+                    } catch(NullPointerException e) {
+                        log("null pointer error!");
+                    } catch(RuntimeException e) {
+                        log(headerField + "is not Encoded-String-Value header field!");
+                        return null;
+                    }
+                    break;
+                }
+
+                case PduHeaders.MESSAGE_CLASS: {
+                    /* Message-class-value = Class-identifier | Token-text */
+                    pduDataStream.mark(1);
+                    int messageClass = extractByteValue(pduDataStream);
+
+                    if (messageClass >= PduHeaders.MESSAGE_CLASS_PERSONAL) {
+                        /* Class-identifier */
+                        try {
+                            if (PduHeaders.MESSAGE_CLASS_PERSONAL == messageClass) {
+                                headers.setTextString(
+                                        PduHeaders.MESSAGE_CLASS_PERSONAL_STR.getBytes(),
+                                        PduHeaders.MESSAGE_CLASS);
+                            } else if (PduHeaders.MESSAGE_CLASS_ADVERTISEMENT == messageClass) {
+                                headers.setTextString(
+                                        PduHeaders.MESSAGE_CLASS_ADVERTISEMENT_STR.getBytes(),
+                                        PduHeaders.MESSAGE_CLASS);
+                            } else if (PduHeaders.MESSAGE_CLASS_INFORMATIONAL == messageClass) {
+                                headers.setTextString(
+                                        PduHeaders.MESSAGE_CLASS_INFORMATIONAL_STR.getBytes(),
+                                        PduHeaders.MESSAGE_CLASS);
+                            } else if (PduHeaders.MESSAGE_CLASS_AUTO == messageClass) {
+                                headers.setTextString(
+                                        PduHeaders.MESSAGE_CLASS_AUTO_STR.getBytes(),
+                                        PduHeaders.MESSAGE_CLASS);
+                            }
+                        } catch(NullPointerException e) {
+                            log("null pointer error!");
+                        } catch(RuntimeException e) {
+                            log(headerField + "is not Text-String header field!");
+                            return null;
+                        }
+                    } else {
+                        /* Token-text */
+                        pduDataStream.reset();
+                        byte[] messageClassString = parseWapString(pduDataStream, TYPE_TEXT_STRING);
+                        if (null != messageClassString) {
+                            try {
+                                headers.setTextString(messageClassString, PduHeaders.MESSAGE_CLASS);
+                            } catch(NullPointerException e) {
+                                log("null pointer error!");
+                            } catch(RuntimeException e) {
+                                log(headerField + "is not Text-String header field!");
+                                return null;
+                            }
+                        }
+                    }
+                    break;
+                }
+
+                case PduHeaders.MMS_VERSION: {
+                    int version = parseShortInteger(pduDataStream);
+
+                    try {
+                        headers.setOctet(version, PduHeaders.MMS_VERSION);
+                    } catch(InvalidHeaderValueException e) {
+                        log("Set invalid Octet value: " + version +
+                                " into the header filed: " + headerField);
+                        return null;
+                    } catch(RuntimeException e) {
+                        log(headerField + "is not Octet header field!");
+                        return null;
+                    }
+                    break;
+                }
+
+                case PduHeaders.PREVIOUSLY_SENT_BY: {
+                    /* Previously-sent-by-value =
+                     * Value-length Forwarded-count-value Encoded-string-value */
+                    /* parse value-length */
+                    parseValueLength(pduDataStream);
+
+                    /* parse Forwarded-count-value */
+                    try {
+                        parseIntegerValue(pduDataStream);
+                    } catch(RuntimeException e) {
+                        log(headerField + " is not Integer-Value");
+                        return null;
+                    }
+
+                    /* parse Encoded-string-value */
+                    EncodedStringValue previouslySentBy =
+                        parseEncodedStringValue(pduDataStream);
+                    if (null != previouslySentBy) {
+                        try {
+                            headers.setEncodedStringValue(previouslySentBy,
+                                    PduHeaders.PREVIOUSLY_SENT_BY);
+                        } catch(NullPointerException e) {
+                            log("null pointer error!");
+                        } catch(RuntimeException e) {
+                            log(headerField + "is not Encoded-String-Value header field!");
+                            return null;
+                        }
+                    }
+                    break;
+                }
+
+                case PduHeaders.PREVIOUSLY_SENT_DATE: {
+                    /* Previously-sent-date-value =
+                     * Value-length Forwarded-count-value Date-value */
+                    /* parse value-length */
+                    parseValueLength(pduDataStream);
+
+                    /* parse Forwarded-count-value */
+                    try {
+                        parseIntegerValue(pduDataStream);
+                    } catch(RuntimeException e) {
+                        log(headerField + " is not Integer-Value");
+                        return null;
+                    }
+
+                    /* Date-value */
+                    try {
+                        long perviouslySentDate = parseLongInteger(pduDataStream);
+                        headers.setLongInteger(perviouslySentDate,
+                                PduHeaders.PREVIOUSLY_SENT_DATE);
+                    } catch(RuntimeException e) {
+                        log(headerField + "is not Long-Integer header field!");
+                        return null;
+                    }
+                    break;
+                }
+
+                case PduHeaders.MM_FLAGS: {
+                    /* MM-flags-value =
+                     * Value-length
+                     * ( Add-token | Remove-token | Filter-token )
+                     * Encoded-string-value
+                     */
+
+                    /* parse Value-length */
+                    parseValueLength(pduDataStream);
+
+                    /* Add-token | Remove-token | Filter-token */
+                    extractByteValue(pduDataStream);
+
+                    /* Encoded-string-value */
+                    parseEncodedStringValue(pduDataStream);
+
+                    /* not store this header filed in "headers",
+                     * because now PduHeaders doesn't support it */
+                    break;
+                }
+
+                /* Value-length
+                 * (Message-total-token | Size-total-token) Integer-Value */
+                case PduHeaders.MBOX_TOTALS:
+                case PduHeaders.MBOX_QUOTAS:
+                {
+                    /* Value-length */
+                    parseValueLength(pduDataStream);
+
+                    /* Message-total-token | Size-total-token */
+                    extractByteValue(pduDataStream);
+
+                    /*Integer-Value*/
+                    try {
+                        parseIntegerValue(pduDataStream);
+                    } catch(RuntimeException e) {
+                        log(headerField + " is not Integer-Value");
+                        return null;
+                    }
+
+                    /* not store these headers filed in "headers",
+                    because now PduHeaders doesn't support them */
+                    break;
+                }
+
+                case PduHeaders.ELEMENT_DESCRIPTOR: {
+                    parseContentType(pduDataStream, null);
+
+                    /* not store this header filed in "headers",
+                    because now PduHeaders doesn't support it */
+                    break;
+                }
+
+                case PduHeaders.CONTENT_TYPE: {
+                    HashMap<Integer, Object> map =
+                        new HashMap<Integer, Object>();
+                    byte[] contentType =
+                        parseContentType(pduDataStream, map);
+
+                    if (null != contentType) {
+                        try {
+                            headers.setTextString(contentType, PduHeaders.CONTENT_TYPE);
+                        } catch(NullPointerException e) {
+                            log("null pointer error!");
+                        } catch(RuntimeException e) {
+                            log(headerField + "is not Text-String header field!");
+                            return null;
+                        }
+                    }
+
+                    /* get start parameter */
+                    mStartParam = (byte[]) map.get(PduPart.P_START);
+
+                    /* get charset parameter */
+                    mTypeParam= (byte[]) map.get(PduPart.P_TYPE);
+
+                    keepParsing = false;
+                    break;
+                }
+
+                case PduHeaders.CONTENT:
+                case PduHeaders.ADDITIONAL_HEADERS:
+                case PduHeaders.ATTRIBUTES:
+                default: {
+                    log("Unknown header");
+                }
+            }
+        }
+
+        return headers;
+    }
+
+    /**
+     * Parse pdu parts.
+     *
+     * @param pduDataStream pdu data input stream
+     * @return parts in PduBody structure
+     */
+    protected static PduBody parseParts(ByteArrayInputStream pduDataStream) {
+        if (pduDataStream == null) {
+            return null;
+        }
+
+        int count = parseUnsignedInt(pduDataStream); // get the number of parts
+        PduBody body = new PduBody();
+
+        for (int i = 0 ; i < count ; i++) {
+            int headerLength = parseUnsignedInt(pduDataStream);
+            int dataLength = parseUnsignedInt(pduDataStream);
+            PduPart part = new PduPart();
+            int startPos = pduDataStream.available();
+            if (startPos <= 0) {
+                // Invalid part.
+                return null;
+            }
+
+            /* parse part's content-type */
+            HashMap<Integer, Object> map = new HashMap<Integer, Object>();
+            byte[] contentType = parseContentType(pduDataStream, map);
+            if (null != contentType) {
+                part.setContentType(contentType);
+            } else {
+                part.setContentType((PduContentTypes.contentTypes[0]).getBytes()); //"*/*"
+            }
+
+            /* get name parameter */
+            byte[] name = (byte[]) map.get(PduPart.P_NAME);
+            if (null != name) {
+                part.setName(name);
+            }
+
+            /* get charset parameter */
+            Integer charset = (Integer) map.get(PduPart.P_CHARSET);
+            if (null != charset) {
+                part.setCharset(charset);
+            }
+
+            /* parse part's headers */
+            int endPos = pduDataStream.available();
+            int partHeaderLen = headerLength - (startPos - endPos);
+            if (partHeaderLen > 0) {
+                if (false == parsePartHeaders(pduDataStream, part, partHeaderLen)) {
+                    // Parse part header faild.
+                    return null;
+                }
+            } else if (partHeaderLen < 0) {
+                // Invalid length of content-type.
+                return null;
+            }
+
+            /* FIXME: check content-id, name, filename and content location,
+             * if not set anyone of them, generate a default content-location
+             */
+            if ((null == part.getContentLocation())
+                    && (null == part.getName())
+                    && (null == part.getFilename())
+                    && (null == part.getContentId())) {
+                part.setContentLocation(Long.toOctalString(
+                        System.currentTimeMillis()).getBytes());
+            }
+
+            /* get part's data */
+            if (dataLength > 0) {
+                byte[] partData = new byte[dataLength];
+                pduDataStream.read(partData, 0, dataLength);
+                // Check Content-Transfer-Encoding.
+                byte[] partDataEncoding = part.getContentTransferEncoding();
+                if (null != partDataEncoding) {
+                    String encoding = new String(partDataEncoding);
+                    if (encoding.equalsIgnoreCase(PduPart.P_BASE64)) {
+                        // Decode "base64" into "binary".
+                        partData = Base64.decodeBase64(partData);
+                    } else if (encoding.equalsIgnoreCase(PduPart.P_QUOTED_PRINTABLE)) {
+                        // Decode "quoted-printable" into "binary".
+                        partData = QuotedPrintable.decodeQuotedPrintable(partData);
+                    } else {
+                        // "binary" is the default encoding.
+                    }
+                }
+                if (null == partData) {
+                    log("Decode part data error!");
+                    return null;
+                }
+                part.setData(partData);
+            }
+
+            /* add this part to body */
+            if (THE_FIRST_PART == checkPartPosition(part)) {
+                /* this is the first part */
+                body.addPart(0, part);
+            } else {
+                /* add the part to the end */
+                body.addPart(part);
+            }
+        }
+
+        return body;
+    }
+
+    /**
+     * Log status.
+     *
+     * @param text log information
+     */
+    private static void log(String text) {
+        if (LOCAL_LOGV) {
+            Log.v(LOG_TAG, text);
+        }
+    }
+
+    /**
+     * Parse unsigned integer.
+     *
+     * @param pduDataStream pdu data input stream
+     * @return the integer, -1 when failed
+     */
+    protected static int parseUnsignedInt(ByteArrayInputStream pduDataStream) {
+        /**
+         * From wap-230-wsp-20010705-a.pdf
+         * The maximum size of a uintvar is 32 bits.
+         * So it will be encoded in no more than 5 octets.
+         */
+        assert(null != pduDataStream);
+        int result = 0;
+        int temp = pduDataStream.read();
+        if (temp == -1) {
+            return temp;
+        }
+
+        while((temp & 0x80) != 0) {
+            result = result << 7;
+            result |= temp & 0x7F;
+            temp = pduDataStream.read();
+            if (temp == -1) {
+                return temp;
+            }
+        }
+
+        result = result << 7;
+        result |= temp & 0x7F;
+
+        return result;
+    }
+
+    /**
+     * Parse value length.
+     *
+     * @param pduDataStream pdu data input stream
+     * @return the integer
+     */
+    protected static int parseValueLength(ByteArrayInputStream pduDataStream) {
+        /**
+         * From wap-230-wsp-20010705-a.pdf
+         * Value-length = Short-length | (Length-quote Length)
+         * Short-length = <Any octet 0-30>
+         * Length-quote = <Octet 31>
+         * Length = Uintvar-integer
+         * Uintvar-integer = 1*5 OCTET
+         */
+        assert(null != pduDataStream);
+        int temp = pduDataStream.read();
+        assert(-1 != temp);
+        int first = temp & 0xFF;
+
+        if (first <= SHORT_LENGTH_MAX) {
+            return first;
+        } else if (first == LENGTH_QUOTE) {
+            return parseUnsignedInt(pduDataStream);
+        }
+
+        throw new RuntimeException ("Value length > LENGTH_QUOTE!");
+    }
+
+    /**
+     * Parse encoded string value.
+     *
+     * @param pduDataStream pdu data input stream
+     * @return the EncodedStringValue
+     */
+    protected static EncodedStringValue parseEncodedStringValue(ByteArrayInputStream pduDataStream){
+        /**
+         * From OMA-TS-MMS-ENC-V1_3-20050927-C.pdf
+         * Encoded-string-value = Text-string | Value-length Char-set Text-string
+         */
+        assert(null != pduDataStream);
+        pduDataStream.mark(1);
+        EncodedStringValue returnValue = null;
+        int charset = 0;
+        int temp = pduDataStream.read();
+        assert(-1 != temp);
+        int first = temp & 0xFF;
+
+        pduDataStream.reset();
+        if (first < TEXT_MIN) {
+            parseValueLength(pduDataStream);
+
+            charset = parseShortInteger(pduDataStream); //get the "Charset"
+        }
+
+        byte[] textString = parseWapString(pduDataStream, TYPE_TEXT_STRING);
+
+        try {
+            if (0 != charset) {
+                returnValue = new EncodedStringValue(charset, textString);
+            } else {
+                returnValue = new EncodedStringValue(textString);
+            }
+        } catch(Exception e) {
+            return null;
+        }
+
+        return returnValue;
+    }
+
+    /**
+     * Parse Text-String or Quoted-String.
+     *
+     * @param pduDataStream pdu data input stream
+     * @param stringType TYPE_TEXT_STRING or TYPE_QUOTED_STRING
+     * @return the string without End-of-string in byte array
+     */
+    protected static byte[] parseWapString(ByteArrayInputStream pduDataStream,
+            int stringType) {
+        assert(null != pduDataStream);
+        /**
+         * From wap-230-wsp-20010705-a.pdf
+         * Text-string = [Quote] *TEXT End-of-string
+         * If the first character in the TEXT is in the range of 128-255,
+         * a Quote character must precede it.
+         * Otherwise the Quote character must be omitted.
+         * The Quote is not part of the contents.
+         * Quote = <Octet 127>
+         * End-of-string = <Octet 0>
+         *
+         * Quoted-string = <Octet 34> *TEXT End-of-string
+         *
+         * Token-text = Token End-of-string
+         */
+
+        // Mark supposed beginning of Text-string
+        // We will have to mark again if first char is QUOTE or QUOTED_STRING_FLAG
+        pduDataStream.mark(1);
+
+        // Check first char
+        int temp = pduDataStream.read();
+        assert(-1 != temp);
+        if ((TYPE_QUOTED_STRING == stringType) &&
+                (QUOTED_STRING_FLAG == temp)) {
+            // Mark again if QUOTED_STRING_FLAG and ignore it
+            pduDataStream.mark(1);
+        } else if ((TYPE_TEXT_STRING == stringType) &&
+                (QUOTE == temp)) {
+            // Mark again if QUOTE and ignore it
+            pduDataStream.mark(1);
+        } else {
+            // Otherwise go back to origin
+            pduDataStream.reset();
+        }
+
+        // We are now definitely at the beginning of string
+        /**
+         * Return *TOKEN or *TEXT (Text-String without QUOTE,
+         * Quoted-String without QUOTED_STRING_FLAG and without End-of-string)
+         */
+        return getWapString(pduDataStream, stringType);
+    }
+
+    /**
+     * Check TOKEN data defined in RFC2616.
+     * @param ch checking data
+     * @return true when ch is TOKEN, false when ch is not TOKEN
+     */
+    protected static boolean isTokenCharacter(int ch) {
+        /**
+         * Token      = 1*<any CHAR except CTLs or separators>
+         * separators = "("(40) | ")"(41) | "<"(60) | ">"(62) | "@"(64)
+         *            | ","(44) | ";"(59) | ":"(58) | "\"(92) | <">(34)
+         *            | "/"(47) | "["(91) | "]"(93) | "?"(63) | "="(61)
+         *            | "{"(123) | "}"(125) | SP(32) | HT(9)
+         * CHAR       = <any US-ASCII character (octets 0 - 127)>
+         * CTL        = <any US-ASCII control character
+         *            (octets 0 - 31) and DEL (127)>
+         * SP         = <US-ASCII SP, space (32)>
+         * HT         = <US-ASCII HT, horizontal-tab (9)>
+         */
+        if((ch < 33) || (ch > 126)) {
+            return false;
+        }
+
+        switch(ch) {
+            case '"': /* '"' */
+            case '(': /* '(' */
+            case ')': /* ')' */
+            case ',': /* ',' */
+            case '/': /* '/' */
+            case ':': /* ':' */
+            case ';': /* ';' */
+            case '<': /* '<' */
+            case '=': /* '=' */
+            case '>': /* '>' */
+            case '?': /* '?' */
+            case '@': /* '@' */
+            case '[': /* '[' */
+            case '\\': /* '\' */
+            case ']': /* ']' */
+            case '{': /* '{' */
+            case '}': /* '}' */
+                return false;
+        }
+
+        return true;
+    }
+
+    /**
+     * Check TEXT data defined in RFC2616.
+     * @param ch checking data
+     * @return true when ch is TEXT, false when ch is not TEXT
+     */
+    protected static boolean isText(int ch) {
+        /**
+         * TEXT = <any OCTET except CTLs,
+         *      but including LWS>
+         * CTL  = <any US-ASCII control character
+         *      (octets 0 - 31) and DEL (127)>
+         * LWS  = [CRLF] 1*( SP | HT )
+         * CRLF = CR LF
+         * CR   = <US-ASCII CR, carriage return (13)>
+         * LF   = <US-ASCII LF, linefeed (10)>
+         */
+        if(((ch >= 32) && (ch <= 126)) || ((ch >= 128) && (ch <= 255))) {
+            return true;
+        }
+
+        switch(ch) {
+            case '\t': /* '\t' */
+            case '\n': /* '\n' */
+            case '\r': /* '\r' */
+                return true;
+        }
+
+        return false;
+    }
+
+    protected static byte[] getWapString(ByteArrayInputStream pduDataStream,
+            int stringType) {
+        assert(null != pduDataStream);
+        ByteArrayOutputStream out = new ByteArrayOutputStream();
+        int temp = pduDataStream.read();
+        assert(-1 != temp);
+        while((-1 != temp) && ('\0' != temp)) {
+            // check each of the character
+            if (stringType == TYPE_TOKEN_STRING) {
+                if (isTokenCharacter(temp)) {
+                    out.write(temp);
+                }
+            } else {
+                if (isText(temp)) {
+                    out.write(temp);
+                }
+            }
+
+            temp = pduDataStream.read();
+            assert(-1 != temp);
+        }
+
+        if (out.size() > 0) {
+            return out.toByteArray();
+        }
+
+        return null;
+    }
+
+    /**
+     * Extract a byte value from the input stream.
+     *
+     * @param pduDataStream pdu data input stream
+     * @return the byte
+     */
+    protected static int extractByteValue(ByteArrayInputStream pduDataStream) {
+        assert(null != pduDataStream);
+        int temp = pduDataStream.read();
+        assert(-1 != temp);
+        return temp & 0xFF;
+    }
+
+    /**
+     * Parse Short-Integer.
+     *
+     * @param pduDataStream pdu data input stream
+     * @return the byte
+     */
+    protected static int parseShortInteger(ByteArrayInputStream pduDataStream) {
+        /**
+         * From wap-230-wsp-20010705-a.pdf
+         * Short-integer = OCTET
+         * Integers in range 0-127 shall be encoded as a one
+         * octet value with the most significant bit set to one (1xxx xxxx)
+         * and with the value in the remaining least significant bits.
+         */
+        assert(null != pduDataStream);
+        int temp = pduDataStream.read();
+        assert(-1 != temp);
+        return temp & 0x7F;
+    }
+
+    /**
+     * Parse Long-Integer.
+     *
+     * @param pduDataStream pdu data input stream
+     * @return long integer
+     */
+    protected static long parseLongInteger(ByteArrayInputStream pduDataStream) {
+        /**
+         * From wap-230-wsp-20010705-a.pdf
+         * Long-integer = Short-length Multi-octet-integer
+         * The Short-length indicates the length of the Multi-octet-integer
+         * Multi-octet-integer = 1*30 OCTET
+         * The content octets shall be an unsigned integer value
+         * with the most significant octet encoded first (big-endian representation).
+         * The minimum number of octets must be used to encode the value.
+         * Short-length = <Any octet 0-30>
+         */
+        assert(null != pduDataStream);
+        int temp = pduDataStream.read();
+        assert(-1 != temp);
+        int count = temp & 0xFF;
+
+        if (count > LONG_INTEGER_LENGTH_MAX) {
+            throw new RuntimeException("Octet count greater than 8 and I can't represent that!");
+        }
+
+        long result = 0;
+
+        for (int i = 0 ; i < count ; i++) {
+            temp = pduDataStream.read();
+            assert(-1 != temp);
+            result <<= 8;
+            result += (temp & 0xFF);
+        }
+
+        return result;
+    }
+
+    /**
+     * Parse Integer-Value.
+     *
+     * @param pduDataStream pdu data input stream
+     * @return long integer
+     */
+    protected static long parseIntegerValue(ByteArrayInputStream pduDataStream) {
+        /**
+         * From wap-230-wsp-20010705-a.pdf
+         * Integer-Value = Short-integer | Long-integer
+         */
+        assert(null != pduDataStream);
+        pduDataStream.mark(1);
+        int temp = pduDataStream.read();
+        assert(-1 != temp);
+        pduDataStream.reset();
+        if (temp > SHORT_INTEGER_MAX) {
+            return parseShortInteger(pduDataStream);
+        } else {
+            return parseLongInteger(pduDataStream);
+        }
+    }
+
+    /**
+     * To skip length of the wap value.
+     *
+     * @param pduDataStream pdu data input stream
+     * @param length area size
+     * @return the values in this area
+     */
+    protected static int skipWapValue(ByteArrayInputStream pduDataStream, int length) {
+        assert(null != pduDataStream);
+        byte[] area = new byte[length];
+        int readLen = pduDataStream.read(area, 0, length);
+        if (readLen < length) { //The actually read length is lower than the length
+            return -1;
+        } else {
+            return readLen;
+        }
+    }
+
+    /**
+     * Parse content type parameters. For now we just support
+     * four parameters used in mms: "type", "start", "name", "charset".
+     *
+     * @param pduDataStream pdu data input stream
+     * @param map to store parameters of Content-Type field
+     * @param length length of all the parameters
+     */
+    protected static void parseContentTypeParams(ByteArrayInputStream pduDataStream,
+            HashMap<Integer, Object> map, Integer length) {
+        /**
+         * From wap-230-wsp-20010705-a.pdf
+         * Parameter = Typed-parameter | Untyped-parameter
+         * Typed-parameter = Well-known-parameter-token Typed-value
+         * the actual expected type of the value is implied by the well-known parameter
+         * Well-known-parameter-token = Integer-value
+         * the code values used for parameters are specified in the Assigned Numbers appendix
+         * Typed-value = Compact-value | Text-value
+         * In addition to the expected type, there may be no value.
+         * If the value cannot be encoded using the expected type, it shall be encoded as text.
+         * Compact-value = Integer-value |
+         * Date-value | Delta-seconds-value | Q-value | Version-value |
+         * Uri-value
+         * Untyped-parameter = Token-text Untyped-value
+         * the type of the value is unknown, but it shall be encoded as an integer,
+         * if that is possible.
+         * Untyped-value = Integer-value | Text-value
+         */
+        assert(null != pduDataStream);
+        assert(length > 0);
+
+        int startPos = pduDataStream.available();
+        int tempPos = 0;
+        int lastLen = length;
+        while(0 < lastLen) {
+            int param = pduDataStream.read();
+            assert(-1 != param);
+            lastLen--;
+
+            switch (param) {
+                /**
+                 * From rfc2387, chapter 3.1
+                 * The type parameter must be specified and its value is the MIME media
+                 * type of the "root" body part. It permits a MIME user agent to
+                 * determine the content-type without reference to the enclosed body
+                 * part. If the value of the type parameter and the root body part's
+                 * content-type differ then the User Agent's behavior is undefined.
+                 *
+                 * From wap-230-wsp-20010705-a.pdf
+                 * type = Constrained-encoding
+                 * Constrained-encoding = Extension-Media | Short-integer
+                 * Extension-media = *TEXT End-of-string
+                 */
+                case PduPart.P_TYPE:
+                case PduPart.P_CT_MR_TYPE:
+                    pduDataStream.mark(1);
+                    int first = extractByteValue(pduDataStream);
+                    pduDataStream.reset();
+                    if (first > TEXT_MAX) {
+                        // Short-integer (well-known type)
+                        int index = parseShortInteger(pduDataStream);
+
+                        if (index < PduContentTypes.contentTypes.length) {
+                            byte[] type = (PduContentTypes.contentTypes[index]).getBytes();
+                            map.put(PduPart.P_TYPE, type);
+                        } else {
+                            //not support this type, ignore it.
+                        }
+                    } else {
+                        // Text-String (extension-media)
+                        byte[] type = parseWapString(pduDataStream, TYPE_TEXT_STRING);
+                        if ((null != type) && (null != map)) {
+                            map.put(PduPart.P_TYPE, type);
+                        }
+                    }
+
+                    tempPos = pduDataStream.available();
+                    lastLen = length - (startPos - tempPos);
+                    break;
+
+                    /**
+                     * From oma-ts-mms-conf-v1_3.pdf, chapter 10.2.3.
+                     * Start Parameter Referring to Presentation
+                     *
+                     * From rfc2387, chapter 3.2
+                     * The start parameter, if given, is the content-ID of the compound
+                     * object's "root". If not present the "root" is the first body part in
+                     * the Multipart/Related entity. The "root" is the element the
+                     * applications processes first.
+                     *
+                     * From wap-230-wsp-20010705-a.pdf
+                     * start = Text-String
+                     */
+                case PduPart.P_START:
+                case PduPart.P_DEP_START:
+                    byte[] start = parseWapString(pduDataStream, TYPE_TEXT_STRING);
+                    if ((null != start) && (null != map)) {
+                        map.put(PduPart.P_START, start);
+                    }
+
+                    tempPos = pduDataStream.available();
+                    lastLen = length - (startPos - tempPos);
+                    break;
+
+                    /**
+                     * From oma-ts-mms-conf-v1_3.pdf
+                     * In creation, the character set SHALL be either us-ascii
+                     * (IANA MIBenum 3) or utf-8 (IANA MIBenum 106)[Unicode].
+                     * In retrieval, both us-ascii and utf-8 SHALL be supported.
+                     *
+                     * From wap-230-wsp-20010705-a.pdf
+                     * charset = Well-known-charset|Text-String
+                     * Well-known-charset = Any-charset | Integer-value
+                     * Both are encoded using values from Character Set
+                     * Assignments table in Assigned Numbers
+                     * Any-charset = <Octet 128>
+                     * Equivalent to the special RFC2616 charset value "*"
+                     */
+                case PduPart.P_CHARSET:
+                    pduDataStream.mark(1);
+                    int firstValue = extractByteValue(pduDataStream);
+                    pduDataStream.reset();
+                    //Check first char
+                    if (((firstValue > TEXT_MIN) && (firstValue < TEXT_MAX)) ||
+                            (END_STRING_FLAG == firstValue)) {
+                        //Text-String (extension-charset)
+                        byte[] charsetStr = parseWapString(pduDataStream, TYPE_TEXT_STRING);
+                        try {
+                            int charsetInt = CharacterSets.getMibEnumValue(
+                                    new String(charsetStr));
+                            map.put(PduPart.P_CHARSET, charsetInt);
+                        } catch (UnsupportedEncodingException e) {
+                            // Not a well-known charset, use "*".
+                            Log.e(LOG_TAG, Arrays.toString(charsetStr), e);
+                            map.put(PduPart.P_CHARSET, CharacterSets.ANY_CHARSET);
+                        }
+                    } else {
+                        //Well-known-charset
+                        int charset = (int) parseIntegerValue(pduDataStream);
+                        if (map != null) {
+                            map.put(PduPart.P_CHARSET, charset);
+                        }
+                    }
+
+                    tempPos = pduDataStream.available();
+                    lastLen = length - (startPos - tempPos);
+                    break;
+
+                    /**
+                     * From oma-ts-mms-conf-v1_3.pdf
+                     * A name for multipart object SHALL be encoded using name-parameter
+                     * for Content-Type header in WSP multipart headers.
+                     *
+                     * From wap-230-wsp-20010705-a.pdf
+                     * name = Text-String
+                     */
+                case PduPart.P_DEP_NAME:
+                case PduPart.P_NAME:
+                    byte[] name = parseWapString(pduDataStream, TYPE_TEXT_STRING);
+                    if ((null != name) && (null != map)) {
+                        map.put(PduPart.P_NAME, name);
+                    }
+
+                    tempPos = pduDataStream.available();
+                    lastLen = length - (startPos - tempPos);
+                    break;
+                default:
+                    if (LOCAL_LOGV) {
+                        Log.v(LOG_TAG, "Not supported Content-Type parameter");
+                    }
+                if (-1 == skipWapValue(pduDataStream, lastLen)) {
+                    Log.e(LOG_TAG, "Corrupt Content-Type");
+                } else {
+                    lastLen = 0;
+                }
+                break;
+            }
+        }
+
+        if (0 != lastLen) {
+            Log.e(LOG_TAG, "Corrupt Content-Type");
+        }
+    }
+
+    /**
+     * Parse content type.
+     *
+     * @param pduDataStream pdu data input stream
+     * @param map to store parameters in Content-Type header field
+     * @return Content-Type value
+     */
+    protected static byte[] parseContentType(ByteArrayInputStream pduDataStream,
+            HashMap<Integer, Object> map) {
+        /**
+         * From wap-230-wsp-20010705-a.pdf
+         * Content-type-value = Constrained-media | Content-general-form
+         * Content-general-form = Value-length Media-type
+         * Media-type = (Well-known-media | Extension-Media) *(Parameter)
+         */
+        assert(null != pduDataStream);
+
+        byte[] contentType = null;
+        pduDataStream.mark(1);
+        int temp = pduDataStream.read();
+        assert(-1 != temp);
+        pduDataStream.reset();
+
+        int cur = (temp & 0xFF);
+
+        if (cur < TEXT_MIN) {
+            int length = parseValueLength(pduDataStream);
+            int startPos = pduDataStream.available();
+            pduDataStream.mark(1);
+            temp = pduDataStream.read();
+            assert(-1 != temp);
+            pduDataStream.reset();
+            int first = (temp & 0xFF);
+
+            if ((first >= TEXT_MIN) && (first <= TEXT_MAX)) {
+                contentType = parseWapString(pduDataStream, TYPE_TEXT_STRING);
+            } else if (first > TEXT_MAX) {
+                int index = parseShortInteger(pduDataStream);
+
+                if (index < PduContentTypes.contentTypes.length) { //well-known type
+                    contentType = (PduContentTypes.contentTypes[index]).getBytes();
+                } else {
+                    pduDataStream.reset();
+                    contentType = parseWapString(pduDataStream, TYPE_TEXT_STRING);
+                }
+            } else {
+                Log.e(LOG_TAG, "Corrupt content-type");
+                return (PduContentTypes.contentTypes[0]).getBytes(); //"*/*"
+            }
+
+            int endPos = pduDataStream.available();
+            int parameterLen = length - (startPos - endPos);
+            if (parameterLen > 0) {//have parameters
+                parseContentTypeParams(pduDataStream, map, parameterLen);
+            }
+
+            if (parameterLen < 0) {
+                Log.e(LOG_TAG, "Corrupt MMS message");
+                return (PduContentTypes.contentTypes[0]).getBytes(); //"*/*"
+            }
+        } else if (cur <= TEXT_MAX) {
+            contentType = parseWapString(pduDataStream, TYPE_TEXT_STRING);
+        } else {
+            contentType =
+                (PduContentTypes.contentTypes[parseShortInteger(pduDataStream)]).getBytes();
+        }
+
+        return contentType;
+    }
+
+    /**
+     * Parse part's headers.
+     *
+     * @param pduDataStream pdu data input stream
+     * @param part to store the header informations of the part
+     * @param length length of the headers
+     * @return true if parse successfully, false otherwise
+     */
+    protected static boolean parsePartHeaders(ByteArrayInputStream pduDataStream,
+            PduPart part, int length) {
+        assert(null != pduDataStream);
+        assert(null != part);
+        assert(length > 0);
+
+        /**
+         * From oma-ts-mms-conf-v1_3.pdf, chapter 10.2.
+         * A name for multipart object SHALL be encoded using name-parameter
+         * for Content-Type header in WSP multipart headers.
+         * In decoding, name-parameter of Content-Type SHALL be used if available.
+         * If name-parameter of Content-Type is not available,
+         * filename parameter of Content-Disposition header SHALL be used if available.
+         * If neither name-parameter of Content-Type header nor filename parameter
+         * of Content-Disposition header is available,
+         * Content-Location header SHALL be used if available.
+         *
+         * Within SMIL part the reference to the media object parts SHALL use
+         * either Content-ID or Content-Location mechanism [RFC2557]
+         * and the corresponding WSP part headers in media object parts
+         * contain the corresponding definitions.
+         */
+        int startPos = pduDataStream.available();
+        int tempPos = 0;
+        int lastLen = length;
+        while(0 < lastLen) {
+            int header = pduDataStream.read();
+            assert(-1 != header);
+            lastLen--;
+
+            if (header > TEXT_MAX) {
+                // Number assigned headers.
+                switch (header) {
+                    case PduPart.P_CONTENT_LOCATION:
+                        /**
+                         * From wap-230-wsp-20010705-a.pdf, chapter 8.4.2.21
+                         * Content-location-value = Uri-value
+                         */
+                        byte[] contentLocation = parseWapString(pduDataStream, TYPE_TEXT_STRING);
+                        if (null != contentLocation) {
+                            part.setContentLocation(contentLocation);
+                        }
+
+                        tempPos = pduDataStream.available();
+                        lastLen = length - (startPos - tempPos);
+                        break;
+                    case PduPart.P_CONTENT_ID:
+                        /**
+                         * From wap-230-wsp-20010705-a.pdf, chapter 8.4.2.21
+                         * Content-ID-value = Quoted-string
+                         */
+                        byte[] contentId = parseWapString(pduDataStream, TYPE_QUOTED_STRING);
+                        if (null != contentId) {
+                            part.setContentId(contentId);
+                        }
+
+                        tempPos = pduDataStream.available();
+                        lastLen = length - (startPos - tempPos);
+                        break;
+                    case PduPart.P_DEP_CONTENT_DISPOSITION:
+                    case PduPart.P_CONTENT_DISPOSITION:
+                        /**
+                         * From wap-230-wsp-20010705-a.pdf, chapter 8.4.2.21
+                         * Content-disposition-value = Value-length Disposition *(Parameter)
+                         * Disposition = Form-data | Attachment | Inline | Token-text
+                         * Form-data = <Octet 128>
+                         * Attachment = <Octet 129>
+                         * Inline = <Octet 130>
+                         */
+                        int len = parseValueLength(pduDataStream);
+                        pduDataStream.mark(1);
+                        int thisStartPos = pduDataStream.available();
+                        int thisEndPos = 0;
+                        int value = pduDataStream.read();
+
+                        if (value == PduPart.P_DISPOSITION_FROM_DATA ) {
+                            part.setContentDisposition(PduPart.DISPOSITION_FROM_DATA);
+                        } else if (value == PduPart.P_DISPOSITION_ATTACHMENT) {
+                            part.setContentDisposition(PduPart.DISPOSITION_ATTACHMENT);
+                        } else if (value == PduPart.P_DISPOSITION_INLINE) {
+                            part.setContentDisposition(PduPart.DISPOSITION_INLINE);
+                        } else {
+                            pduDataStream.reset();
+                            /* Token-text */
+                            part.setContentDisposition(parseWapString(pduDataStream, TYPE_TEXT_STRING));
+                        }
+
+                        /* get filename parameter and skip other parameters */
+                        thisEndPos = pduDataStream.available();
+                        if (thisStartPos - thisEndPos < len) {
+                            value = pduDataStream.read();
+                            if (value == PduPart.P_FILENAME) { //filename is text-string
+                                part.setFilename(parseWapString(pduDataStream, TYPE_TEXT_STRING));
+                            }
+
+                            /* skip other parameters */
+                            thisEndPos = pduDataStream.available();
+                            if (thisStartPos - thisEndPos < len) {
+                                int last = len - (thisStartPos - thisEndPos);
+                                byte[] temp = new byte[last];
+                                pduDataStream.read(temp, 0, last);
+                            }
+                        }
+
+                        tempPos = pduDataStream.available();
+                        lastLen = length - (startPos - tempPos);
+                        break;
+                    default:
+                        if (LOCAL_LOGV) {
+                            Log.v(LOG_TAG, "Not supported Part headers: " + header);
+                        }
+                    if (-1 == skipWapValue(pduDataStream, lastLen)) {
+                        Log.e(LOG_TAG, "Corrupt Part headers");
+                        return false;
+                    }
+                    lastLen = 0;
+                    break;
+                }
+            } else if ((header >= TEXT_MIN) && (header <= TEXT_MAX)) {
+                // Not assigned header.
+                byte[] tempHeader = parseWapString(pduDataStream, TYPE_TEXT_STRING);
+                byte[] tempValue = parseWapString(pduDataStream, TYPE_TEXT_STRING);
+
+                // Check the header whether it is "Content-Transfer-Encoding".
+                if (true ==
+                    PduPart.CONTENT_TRANSFER_ENCODING.equalsIgnoreCase(new String(tempHeader))) {
+                    part.setContentTransferEncoding(tempValue);
+                }
+
+                tempPos = pduDataStream.available();
+                lastLen = length - (startPos - tempPos);
+            } else {
+                if (LOCAL_LOGV) {
+                    Log.v(LOG_TAG, "Not supported Part headers: " + header);
+                }
+                // Skip all headers of this part.
+                if (-1 == skipWapValue(pduDataStream, lastLen)) {
+                    Log.e(LOG_TAG, "Corrupt Part headers");
+                    return false;
+                }
+                lastLen = 0;
+            }
+        }
+
+        if (0 != lastLen) {
+            Log.e(LOG_TAG, "Corrupt Part headers");
+            return false;
+        }
+
+        return true;
+    }
+
+    /**
+     * Check the position of a specified part.
+     *
+     * @param part the part to be checked
+     * @return part position, THE_FIRST_PART when it's the
+     * first one, THE_LAST_PART when it's the last one.
+     */
+    private static int checkPartPosition(PduPart part) {
+        assert(null != part);
+        if ((null == mTypeParam) &&
+                (null == mStartParam)) {
+            return THE_LAST_PART;
+        }
+
+        /* check part's content-id */
+        if (null != mStartParam) {
+            byte[] contentId = part.getContentId();
+            if (null != contentId) {
+                if (true == Arrays.equals(mStartParam, contentId)) {
+                    return THE_FIRST_PART;
+                }
+            }
+        }
+
+        /* check part's content-type */
+        if (null != mTypeParam) {
+            byte[] contentType = part.getContentType();
+            if (null != contentType) {
+                if (true == Arrays.equals(mTypeParam, contentType)) {
+                    return THE_FIRST_PART;
+                }
+            }
+        }
+
+        return THE_LAST_PART;
+    }
+
+    /**
+     * Check mandatory headers of a pdu.
+     *
+     * @param headers pdu headers
+     * @return true if the pdu has all of the mandatory headers, false otherwise.
+     */
+    protected static boolean checkMandatoryHeader(PduHeaders headers) {
+        if (null == headers) {
+            return false;
+        }
+
+        /* get message type */
+        int messageType = headers.getOctet(PduHeaders.MESSAGE_TYPE);
+
+        /* check Mms-Version field */
+        int mmsVersion = headers.getOctet(PduHeaders.MMS_VERSION);
+        if (0 == mmsVersion) {
+            // Every message should have Mms-Version field.
+            return false;
+        }
+
+        /* check mandatory header fields */
+        switch (messageType) {
+            case PduHeaders.MESSAGE_TYPE_SEND_REQ:
+                // Content-Type field.
+                byte[] srContentType = headers.getTextString(PduHeaders.CONTENT_TYPE);
+                if (null == srContentType) {
+                    return false;
+                }
+
+                // From field.
+                EncodedStringValue srFrom = headers.getEncodedStringValue(PduHeaders.FROM);
+                if (null == srFrom) {
+                    return false;
+                }
+
+                // Transaction-Id field.
+                byte[] srTransactionId = headers.getTextString(PduHeaders.TRANSACTION_ID);
+                if (null == srTransactionId) {
+                    return false;
+                }
+
+                break;
+            case PduHeaders.MESSAGE_TYPE_SEND_CONF:
+                // Response-Status field.
+                int scResponseStatus = headers.getOctet(PduHeaders.RESPONSE_STATUS);
+                if (0 == scResponseStatus) {
+                    return false;
+                }
+
+                // Transaction-Id field.
+                byte[] scTransactionId = headers.getTextString(PduHeaders.TRANSACTION_ID);
+                if (null == scTransactionId) {
+                    return false;
+                }
+
+                break;
+            case PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND:
+                // Content-Location field.
+                byte[] niContentLocation = headers.getTextString(PduHeaders.CONTENT_LOCATION);
+                if (null == niContentLocation) {
+                    return false;
+                }
+
+                // Expiry field.
+                long niExpiry = headers.getLongInteger(PduHeaders.EXPIRY);
+                if (-1 == niExpiry) {
+                    return false;
+                }
+
+                // Message-Class field.
+                byte[] niMessageClass = headers.getTextString(PduHeaders.MESSAGE_CLASS);
+                if (null == niMessageClass) {
+                    return false;
+                }
+
+                // Message-Size field.
+                long niMessageSize = headers.getLongInteger(PduHeaders.MESSAGE_SIZE);
+                if (-1 == niMessageSize) {
+                    return false;
+                }
+
+                // Transaction-Id field.
+                byte[] niTransactionId = headers.getTextString(PduHeaders.TRANSACTION_ID);
+                if (null == niTransactionId) {
+                    return false;
+                }
+
+                break;
+            case PduHeaders.MESSAGE_TYPE_NOTIFYRESP_IND:
+                // Status field.
+                int nriStatus = headers.getOctet(PduHeaders.STATUS);
+                if (0 == nriStatus) {
+                    return false;
+                }
+
+                // Transaction-Id field.
+                byte[] nriTransactionId = headers.getTextString(PduHeaders.TRANSACTION_ID);
+                if (null == nriTransactionId) {
+                    return false;
+                }
+
+                break;
+            case PduHeaders.MESSAGE_TYPE_RETRIEVE_CONF:
+                // Content-Type field.
+                byte[] rcContentType = headers.getTextString(PduHeaders.CONTENT_TYPE);
+                if (null == rcContentType) {
+                    return false;
+                }
+
+                // Date field.
+                long rcDate = headers.getLongInteger(PduHeaders.DATE);
+                if (-1 == rcDate) {
+                    return false;
+                }
+
+                break;
+            case PduHeaders.MESSAGE_TYPE_DELIVERY_IND:
+                // Date field.
+                long diDate = headers.getLongInteger(PduHeaders.DATE);
+                if (-1 == diDate) {
+                    return false;
+                }
+
+                // Message-Id field.
+                byte[] diMessageId = headers.getTextString(PduHeaders.MESSAGE_ID);
+                if (null == diMessageId) {
+                    return false;
+                }
+
+                // Status field.
+                int diStatus = headers.getOctet(PduHeaders.STATUS);
+                if (0 == diStatus) {
+                    return false;
+                }
+
+                // To field.
+                EncodedStringValue[] diTo = headers.getEncodedStringValues(PduHeaders.TO);
+                if (null == diTo) {
+                    return false;
+                }
+
+                break;
+            case PduHeaders.MESSAGE_TYPE_ACKNOWLEDGE_IND:
+                // Transaction-Id field.
+                byte[] aiTransactionId = headers.getTextString(PduHeaders.TRANSACTION_ID);
+                if (null == aiTransactionId) {
+                    return false;
+                }
+
+                break;
+            case PduHeaders.MESSAGE_TYPE_READ_ORIG_IND:
+                // Date field.
+                long roDate = headers.getLongInteger(PduHeaders.DATE);
+                if (-1 == roDate) {
+                    return false;
+                }
+
+                // From field.
+                EncodedStringValue roFrom = headers.getEncodedStringValue(PduHeaders.FROM);
+                if (null == roFrom) {
+                    return false;
+                }
+
+                // Message-Id field.
+                byte[] roMessageId = headers.getTextString(PduHeaders.MESSAGE_ID);
+                if (null == roMessageId) {
+                    return false;
+                }
+
+                // Read-Status field.
+                int roReadStatus = headers.getOctet(PduHeaders.READ_STATUS);
+                if (0 == roReadStatus) {
+                    return false;
+                }
+
+                // To field.
+                EncodedStringValue[] roTo = headers.getEncodedStringValues(PduHeaders.TO);
+                if (null == roTo) {
+                    return false;
+                }
+
+                break;
+            case PduHeaders.MESSAGE_TYPE_READ_REC_IND:
+                // From field.
+                EncodedStringValue rrFrom = headers.getEncodedStringValue(PduHeaders.FROM);
+                if (null == rrFrom) {
+                    return false;
+                }
+
+                // Message-Id field.
+                byte[] rrMessageId = headers.getTextString(PduHeaders.MESSAGE_ID);
+                if (null == rrMessageId) {
+                    return false;
+                }
+
+                // Read-Status field.
+                int rrReadStatus = headers.getOctet(PduHeaders.READ_STATUS);
+                if (0 == rrReadStatus) {
+                    return false;
+                }
+
+                // To field.
+                EncodedStringValue[] rrTo = headers.getEncodedStringValues(PduHeaders.TO);
+                if (null == rrTo) {
+                    return false;
+                }
+
+                break;
+            default:
+                // Parser doesn't support this message type in this version.
+                return false;
+        }
+
+        return true;
+    }
+}
diff --git a/core/java/com/google/android/mms/pdu/PduPart.java b/core/java/com/google/android/mms/pdu/PduPart.java
new file mode 100644
index 0000000..b43e388
--- /dev/null
+++ b/core/java/com/google/android/mms/pdu/PduPart.java
@@ -0,0 +1,402 @@
+/*
+ * Copyright (C) 2007-2008 Esmertec AG.
+ * Copyright (C) 2007-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.google.android.mms.pdu;
+
+import android.net.Uri;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * The pdu part.
+ */
+public class PduPart {
+    /**
+     * Well-Known Parameters.
+     */
+    public static final int P_Q                  = 0x80;
+    public static final int P_CHARSET            = 0x81;
+    public static final int P_LEVEL              = 0x82;
+    public static final int P_TYPE               = 0x83;
+    public static final int P_DEP_NAME           = 0x85;
+    public static final int P_DEP_FILENAME       = 0x86;
+    public static final int P_DIFFERENCES        = 0x87;
+    public static final int P_PADDING            = 0x88;
+    // This value of "TYPE" s used with Content-Type: multipart/related
+    public static final int P_CT_MR_TYPE         = 0x89;
+    public static final int P_DEP_START          = 0x8A;
+    public static final int P_DEP_START_INFO     = 0x8B;
+    public static final int P_DEP_COMMENT        = 0x8C;
+    public static final int P_DEP_DOMAIN         = 0x8D;
+    public static final int P_MAX_AGE            = 0x8E;
+    public static final int P_DEP_PATH           = 0x8F;
+    public static final int P_SECURE             = 0x90;
+    public static final int P_SEC                = 0x91;
+    public static final int P_MAC                = 0x92;
+    public static final int P_CREATION_DATE      = 0x93;
+    public static final int P_MODIFICATION_DATE  = 0x94;
+    public static final int P_READ_DATE          = 0x95;
+    public static final int P_SIZE               = 0x96;
+    public static final int P_NAME               = 0x97;
+    public static final int P_FILENAME           = 0x98;
+    public static final int P_START              = 0x99;
+    public static final int P_START_INFO         = 0x9A;
+    public static final int P_COMMENT            = 0x9B;
+    public static final int P_DOMAIN             = 0x9C;
+    public static final int P_PATH               = 0x9D;
+
+    /**
+     *  Header field names.
+     */
+     public static final int P_CONTENT_TYPE       = 0x91;
+     public static final int P_CONTENT_LOCATION   = 0x8E;
+     public static final int P_CONTENT_ID         = 0xC0;
+     public static final int P_DEP_CONTENT_DISPOSITION = 0xAE;
+     public static final int P_CONTENT_DISPOSITION = 0xC5;
+    // The next header is unassigned header, use reserved header(0x48) value.
+     public static final int P_CONTENT_TRANSFER_ENCODING = 0xC8;
+
+     /**
+      * Content=Transfer-Encoding string.
+      */
+     public static final String CONTENT_TRANSFER_ENCODING =
+             "Content-Transfer-Encoding";
+
+     /**
+      * Value of Content-Transfer-Encoding.
+      */
+     public static final String P_BINARY = "binary";
+     public static final String P_7BIT = "7bit";
+     public static final String P_8BIT = "8bit";
+     public static final String P_BASE64 = "base64";
+     public static final String P_QUOTED_PRINTABLE = "quoted-printable";
+
+     /**
+      * Value of disposition can be set to PduPart when the value is octet in
+      * the PDU.
+      * "from-data" instead of Form-data<Octet 128>.
+      * "attachment" instead of Attachment<Octet 129>.
+      * "inline" instead of Inline<Octet 130>.
+      */
+     static final byte[] DISPOSITION_FROM_DATA = "from-data".getBytes();
+     static final byte[] DISPOSITION_ATTACHMENT = "attachment".getBytes();
+     static final byte[] DISPOSITION_INLINE = "inline".getBytes();
+
+     /**
+      * Content-Disposition value.
+      */
+     public static final int P_DISPOSITION_FROM_DATA  = 0x80;
+     public static final int P_DISPOSITION_ATTACHMENT = 0x81;
+     public static final int P_DISPOSITION_INLINE     = 0x82;
+
+     /**
+      * Header of part.
+      */
+     private Map<Integer, Object> mPartHeader = null;
+
+     /**
+      * Data uri.
+      */
+     private Uri mUri = null;
+
+     /**
+      * Part data.
+      */
+     private byte[] mPartData = null;
+
+     private static final String TAG = "PduPart";
+
+     /**
+      * Empty Constructor.
+      */
+     public PduPart() {
+         mPartHeader = new HashMap<Integer, Object>();
+     }
+
+     /**
+      * Set part data. The data are stored as byte array.
+      *
+      * @param data the data
+      */
+     public void setData(byte[] data) {
+         if(data == null) {
+            return;
+        }
+
+         mPartData = new byte[data.length];
+         System.arraycopy(data, 0, mPartData, 0, data.length);
+     }
+
+     /**
+      * @return A copy of the part data or null if the data wasn't set or
+      *         the data is stored as Uri.
+      * @see #getDataUri
+      */
+     public byte[] getData() {
+         if(mPartData == null) {
+            return null;
+         }
+
+         byte[] byteArray = new byte[mPartData.length];
+         System.arraycopy(mPartData, 0, byteArray, 0, mPartData.length);
+         return byteArray;
+     }
+
+     /**
+      * Set data uri. The data are stored as Uri.
+      *
+      * @param uri the uri
+      */
+     public void setDataUri(Uri uri) {
+         mUri = uri;
+     }
+
+     /**
+      * @return The Uri of the part data or null if the data wasn't set or
+      *         the data is stored as byte array.
+      * @see #getData
+      */
+     public Uri getDataUri() {
+         return mUri;
+     }
+
+     /**
+      * Set Content-id value
+      *
+      * @param contentId the content-id value
+      * @throws NullPointerException if the value is null.
+      */
+     public void setContentId(byte[] contentId) {
+         if((contentId == null) || (contentId.length == 0)) {
+             throw new IllegalArgumentException(
+                     "Content-Id may not be null or empty.");
+         }
+
+         if ((contentId.length > 1)
+                 && ((char) contentId[0] == '<')
+                 && ((char) contentId[contentId.length - 1] == '>')) {
+             mPartHeader.put(P_CONTENT_ID, contentId);
+             return;
+         }
+
+         // Insert beginning '<' and trailing '>' for Content-Id.
+         byte[] buffer = new byte[contentId.length + 2];
+         buffer[0] = (byte) (0xff & '<');
+         buffer[buffer.length - 1] = (byte) (0xff & '>');
+         System.arraycopy(contentId, 0, buffer, 1, contentId.length);
+         mPartHeader.put(P_CONTENT_ID, buffer);
+     }
+
+     /**
+      * Get Content-id value.
+      *
+      * @return the value
+      */
+     public byte[] getContentId() {
+         return (byte[]) mPartHeader.get(P_CONTENT_ID);
+     }
+
+     /**
+      * Set Char-set value.
+      *
+      * @param charset the value
+      */
+     public void setCharset(int charset) {
+         mPartHeader.put(P_CHARSET, charset);
+     }
+
+     /**
+      * Get Char-set value
+      *
+      * @return the charset value. Return 0 if charset was not set.
+      */
+     public int getCharset() {
+         Integer charset = (Integer) mPartHeader.get(P_CHARSET);
+         if(charset == null) {
+             return 0;
+         } else {
+             return charset.intValue();
+         }
+     }
+
+     /**
+      * Set Content-Location value.
+      *
+      * @param contentLocation the value
+      * @throws NullPointerException if the value is null.
+      */
+     public void setContentLocation(byte[] contentLocation) {
+         if(contentLocation == null) {
+             throw new NullPointerException("null content-location");
+         }
+
+         mPartHeader.put(P_CONTENT_LOCATION, contentLocation);
+     }
+
+     /**
+      * Get Content-Location value.
+      *
+      * @return the value
+      *     return PduPart.disposition[0] instead of <Octet 128> (Form-data).
+      *     return PduPart.disposition[1] instead of <Octet 129> (Attachment).
+      *     return PduPart.disposition[2] instead of <Octet 130> (Inline).
+      */
+     public byte[] getContentLocation() {
+         return (byte[]) mPartHeader.get(P_CONTENT_LOCATION);
+     }
+
+     /**
+      * Set Content-Disposition value.
+      * Use PduPart.disposition[0] instead of <Octet 128> (Form-data).
+      * Use PduPart.disposition[1] instead of <Octet 129> (Attachment).
+      * Use PduPart.disposition[2] instead of <Octet 130> (Inline).
+      *
+      * @param contentDisposition the value
+      * @throws NullPointerException if the value is null.
+      */
+     public void setContentDisposition(byte[] contentDisposition) {
+         if(contentDisposition == null) {
+             throw new NullPointerException("null content-disposition");
+         }
+
+         mPartHeader.put(P_CONTENT_DISPOSITION, contentDisposition);
+     }
+
+     /**
+      * Get Content-Disposition value.
+      *
+      * @return the value
+      */
+     public byte[] getContentDisposition() {
+         return (byte[]) mPartHeader.get(P_CONTENT_DISPOSITION);
+     }
+
+     /**
+      *  Set Content-Type value.
+      *
+      *  @param value the value
+      *  @throws NullPointerException if the value is null.
+      */
+     public void setContentType(byte[] contentType) {
+         if(contentType == null) {
+             throw new NullPointerException("null content-type");
+         }
+
+         mPartHeader.put(P_CONTENT_TYPE, contentType);
+     }
+
+     /**
+      * Get Content-Type value of part.
+      *
+      * @return the value
+      */
+     public byte[] getContentType() {
+         return (byte[]) mPartHeader.get(P_CONTENT_TYPE);
+     }
+
+     /**
+      * Set Content-Transfer-Encoding value
+      *
+      * @param contentId the content-id value
+      * @throws NullPointerException if the value is null.
+      */
+     public void setContentTransferEncoding(byte[] contentTransferEncoding) {
+         if(contentTransferEncoding == null) {
+             throw new NullPointerException("null content-transfer-encoding");
+         }
+
+         mPartHeader.put(P_CONTENT_TRANSFER_ENCODING, contentTransferEncoding);
+     }
+
+     /**
+      * Get Content-Transfer-Encoding value.
+      *
+      * @return the value
+      */
+     public byte[] getContentTransferEncoding() {
+         return (byte[]) mPartHeader.get(P_CONTENT_TRANSFER_ENCODING);
+     }
+
+     /**
+      * Set Content-type parameter: name.
+      *
+      * @param name the name value
+      * @throws NullPointerException if the value is null.
+      */
+     public void setName(byte[] name) {
+         if(null == name) {
+             throw new NullPointerException("null content-id");
+         }
+
+         mPartHeader.put(P_NAME, name);
+     }
+
+     /**
+      *  Get content-type parameter: name.
+      *
+      *  @return the name
+      */
+     public byte[] getName() {
+         return (byte[]) mPartHeader.get(P_NAME);
+     }
+
+     /**
+      * Get Content-disposition parameter: filename
+      *
+      * @param fileName the filename value
+      * @throws NullPointerException if the value is null.
+      */
+     public void setFilename(byte[] fileName) {
+         if(null == fileName) {
+             throw new NullPointerException("null content-id");
+         }
+
+         mPartHeader.put(P_FILENAME, fileName);
+     }
+
+     /**
+      * Set Content-disposition parameter: filename
+      *
+      * @return the filename
+      */
+     public byte[] getFilename() {
+         return (byte[]) mPartHeader.get(P_FILENAME);
+     }
+
+    public String generateLocation() {
+        // Assumption: At least one of the content-location / name / filename
+        // or content-id should be set. This is guaranteed by the PduParser
+        // for incoming messages and by MM composer for outgoing messages.
+        byte[] location = (byte[]) mPartHeader.get(P_NAME);
+        if(null == location) {
+            location = (byte[]) mPartHeader.get(P_FILENAME);
+
+            if (null == location) {
+                location = (byte[]) mPartHeader.get(P_CONTENT_LOCATION);
+            }
+        }
+
+        if (null == location) {
+            byte[] contentId = (byte[]) mPartHeader.get(P_CONTENT_ID);
+            return "cid:" + new String(contentId);
+        } else {
+            return new String(location);
+        }
+    }
+}
+
diff --git a/core/java/com/google/android/mms/pdu/PduPersister.java b/core/java/com/google/android/mms/pdu/PduPersister.java
new file mode 100644
index 0000000..d2a41f1
--- /dev/null
+++ b/core/java/com/google/android/mms/pdu/PduPersister.java
@@ -0,0 +1,1225 @@
+/*
+ * Copyright (C) 2007-2008 Esmertec AG.
+ * Copyright (C) 2007-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.google.android.mms.pdu;
+
+import com.google.android.mms.ContentType;
+import com.google.android.mms.InvalidHeaderValueException;
+import com.google.android.mms.MmsException;
+import com.google.android.mms.util.PduCache;
+import com.google.android.mms.util.PduCacheEntry;
+import com.google.android.mms.util.SqliteWrapper;
+
+import android.content.ContentResolver;
+import android.content.ContentUris;
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.Cursor;
+import android.database.DatabaseUtils;
+import android.net.Uri;
+import android.provider.Telephony.Mms;
+import android.provider.Telephony.MmsSms;
+import android.provider.Telephony.Threads;
+import android.provider.Telephony.Mms.Addr;
+import android.provider.Telephony.Mms.Part;
+import android.provider.Telephony.MmsSms.PendingMessages;
+import android.text.TextUtils;
+import android.util.Config;
+import android.util.Log;
+
+import java.io.ByteArrayOutputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.UnsupportedEncodingException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.Map.Entry;
+
+/**
+ * This class is the high-level manager of PDU storage.
+ */
+public class PduPersister {
+    private static final String TAG = "PduPersister";
+    private static final boolean DEBUG = false;
+    private static final boolean LOCAL_LOGV = DEBUG ? Config.LOGD : Config.LOGV;
+
+    private static final long DUMMY_THREAD_ID = Long.MAX_VALUE;
+
+    /**
+     * The uri of temporary drm objects.
+     */
+    public static final String TEMPORARY_DRM_OBJECT_URI =
+        "content://mms/" + Long.MAX_VALUE + "/part";
+    /**
+     * Indicate that we transiently failed to process a MM.
+     */
+    public static final int PROC_STATUS_TRANSIENT_FAILURE   = 1;
+    /**
+     * Indicate that we permanently failed to process a MM.
+     */
+    public static final int PROC_STATUS_PERMANENTLY_FAILURE = 2;
+    /**
+     * Indicate that we have successfully processed a MM.
+     */
+    public static final int PROC_STATUS_COMPLETED           = 3;
+
+    private static PduPersister sPersister;
+    private static final PduCache PDU_CACHE_INSTANCE;
+
+    private static final int[] ADDRESS_FIELDS = new int[] {
+            PduHeaders.BCC,
+            PduHeaders.CC,
+            PduHeaders.FROM,
+            PduHeaders.TO
+    };
+
+    private static final String[] PDU_PROJECTION = new String[] {
+        Mms._ID,
+        Mms.MESSAGE_BOX,
+        Mms.THREAD_ID,
+        Mms.RETRIEVE_TEXT,
+        Mms.SUBJECT,
+        Mms.CONTENT_LOCATION,
+        Mms.CONTENT_TYPE,
+        Mms.MESSAGE_CLASS,
+        Mms.MESSAGE_ID,
+        Mms.RESPONSE_TEXT,
+        Mms.TRANSACTION_ID,
+        Mms.CONTENT_CLASS,
+        Mms.DELIVERY_REPORT,
+        Mms.MESSAGE_TYPE,
+        Mms.MMS_VERSION,
+        Mms.PRIORITY,
+        Mms.READ_REPORT,
+        Mms.READ_STATUS,
+        Mms.REPORT_ALLOWED,
+        Mms.RETRIEVE_STATUS,
+        Mms.STATUS,
+        Mms.DATE,
+        Mms.DELIVERY_TIME,
+        Mms.EXPIRY,
+        Mms.MESSAGE_SIZE,
+        Mms.SUBJECT_CHARSET,
+        Mms.RETRIEVE_TEXT_CHARSET,
+    };
+
+    private static final int PDU_COLUMN_ID                    = 0;
+    private static final int PDU_COLUMN_MESSAGE_BOX           = 1;
+    private static final int PDU_COLUMN_THREAD_ID             = 2;
+    private static final int PDU_COLUMN_RETRIEVE_TEXT         = 3;
+    private static final int PDU_COLUMN_SUBJECT               = 4;
+    private static final int PDU_COLUMN_CONTENT_LOCATION      = 5;
+    private static final int PDU_COLUMN_CONTENT_TYPE          = 6;
+    private static final int PDU_COLUMN_MESSAGE_CLASS         = 7;
+    private static final int PDU_COLUMN_MESSAGE_ID            = 8;
+    private static final int PDU_COLUMN_RESPONSE_TEXT         = 9;
+    private static final int PDU_COLUMN_TRANSACTION_ID        = 10;
+    private static final int PDU_COLUMN_CONTENT_CLASS         = 11;
+    private static final int PDU_COLUMN_DELIVERY_REPORT       = 12;
+    private static final int PDU_COLUMN_MESSAGE_TYPE          = 13;
+    private static final int PDU_COLUMN_MMS_VERSION           = 14;
+    private static final int PDU_COLUMN_PRIORITY              = 15;
+    private static final int PDU_COLUMN_READ_REPORT           = 16;
+    private static final int PDU_COLUMN_READ_STATUS           = 17;
+    private static final int PDU_COLUMN_REPORT_ALLOWED        = 18;
+    private static final int PDU_COLUMN_RETRIEVE_STATUS       = 19;
+    private static final int PDU_COLUMN_STATUS                = 20;
+    private static final int PDU_COLUMN_DATE                  = 21;
+    private static final int PDU_COLUMN_DELIVERY_TIME         = 22;
+    private static final int PDU_COLUMN_EXPIRY                = 23;
+    private static final int PDU_COLUMN_MESSAGE_SIZE          = 24;
+    private static final int PDU_COLUMN_SUBJECT_CHARSET       = 25;
+    private static final int PDU_COLUMN_RETRIEVE_TEXT_CHARSET = 26;
+
+    private static final String[] PART_PROJECTION = new String[] {
+        Part._ID,
+        Part.CHARSET,
+        Part.CONTENT_DISPOSITION,
+        Part.CONTENT_ID,
+        Part.CONTENT_LOCATION,
+        Part.CONTENT_TYPE,
+        Part.FILENAME,
+        Part.NAME,
+    };
+
+    private static final int PART_COLUMN_ID                  = 0;
+    private static final int PART_COLUMN_CHARSET             = 1;
+    private static final int PART_COLUMN_CONTENT_DISPOSITION = 2;
+    private static final int PART_COLUMN_CONTENT_ID          = 3;
+    private static final int PART_COLUMN_CONTENT_LOCATION    = 4;
+    private static final int PART_COLUMN_CONTENT_TYPE        = 5;
+    private static final int PART_COLUMN_FILENAME            = 6;
+    private static final int PART_COLUMN_NAME                = 7;
+
+    private static final HashMap<Uri, Integer> MESSAGE_BOX_MAP;
+    // These map are used for convenience in persist() and load().
+    private static final HashMap<Integer, Integer> CHARSET_COLUMN_INDEX_MAP;
+    private static final HashMap<Integer, Integer> ENCODED_STRING_COLUMN_INDEX_MAP;
+    private static final HashMap<Integer, Integer> TEXT_STRING_COLUMN_INDEX_MAP;
+    private static final HashMap<Integer, Integer> OCTET_COLUMN_INDEX_MAP;
+    private static final HashMap<Integer, Integer> LONG_COLUMN_INDEX_MAP;
+    private static final HashMap<Integer, String> CHARSET_COLUMN_NAME_MAP;
+    private static final HashMap<Integer, String> ENCODED_STRING_COLUMN_NAME_MAP;
+    private static final HashMap<Integer, String> TEXT_STRING_COLUMN_NAME_MAP;
+    private static final HashMap<Integer, String> OCTET_COLUMN_NAME_MAP;
+    private static final HashMap<Integer, String> LONG_COLUMN_NAME_MAP;
+
+    static {
+        MESSAGE_BOX_MAP = new HashMap<Uri, Integer>();
+        MESSAGE_BOX_MAP.put(Mms.Inbox.CONTENT_URI,  Mms.MESSAGE_BOX_INBOX);
+        MESSAGE_BOX_MAP.put(Mms.Sent.CONTENT_URI,   Mms.MESSAGE_BOX_SENT);
+        MESSAGE_BOX_MAP.put(Mms.Draft.CONTENT_URI,  Mms.MESSAGE_BOX_DRAFTS);
+        MESSAGE_BOX_MAP.put(Mms.Outbox.CONTENT_URI, Mms.MESSAGE_BOX_OUTBOX);
+
+        CHARSET_COLUMN_INDEX_MAP = new HashMap<Integer, Integer>();
+        CHARSET_COLUMN_INDEX_MAP.put(PduHeaders.SUBJECT, PDU_COLUMN_SUBJECT_CHARSET);
+        CHARSET_COLUMN_INDEX_MAP.put(PduHeaders.RETRIEVE_TEXT, PDU_COLUMN_RETRIEVE_TEXT_CHARSET);
+
+        CHARSET_COLUMN_NAME_MAP = new HashMap<Integer, String>();
+        CHARSET_COLUMN_NAME_MAP.put(PduHeaders.SUBJECT, Mms.SUBJECT_CHARSET);
+        CHARSET_COLUMN_NAME_MAP.put(PduHeaders.RETRIEVE_TEXT, Mms.RETRIEVE_TEXT_CHARSET);
+
+        // Encoded string field code -> column index/name map.
+        ENCODED_STRING_COLUMN_INDEX_MAP = new HashMap<Integer, Integer>();
+        ENCODED_STRING_COLUMN_INDEX_MAP.put(PduHeaders.RETRIEVE_TEXT, PDU_COLUMN_RETRIEVE_TEXT);
+        ENCODED_STRING_COLUMN_INDEX_MAP.put(PduHeaders.SUBJECT, PDU_COLUMN_SUBJECT);
+
+        ENCODED_STRING_COLUMN_NAME_MAP = new HashMap<Integer, String>();
+        ENCODED_STRING_COLUMN_NAME_MAP.put(PduHeaders.RETRIEVE_TEXT, Mms.RETRIEVE_TEXT);
+        ENCODED_STRING_COLUMN_NAME_MAP.put(PduHeaders.SUBJECT, Mms.SUBJECT);
+
+        // Text string field code -> column index/name map.
+        TEXT_STRING_COLUMN_INDEX_MAP = new HashMap<Integer, Integer>();
+        TEXT_STRING_COLUMN_INDEX_MAP.put(PduHeaders.CONTENT_LOCATION, PDU_COLUMN_CONTENT_LOCATION);
+        TEXT_STRING_COLUMN_INDEX_MAP.put(PduHeaders.CONTENT_TYPE, PDU_COLUMN_CONTENT_TYPE);
+        TEXT_STRING_COLUMN_INDEX_MAP.put(PduHeaders.MESSAGE_CLASS, PDU_COLUMN_MESSAGE_CLASS);
+        TEXT_STRING_COLUMN_INDEX_MAP.put(PduHeaders.MESSAGE_ID, PDU_COLUMN_MESSAGE_ID);
+        TEXT_STRING_COLUMN_INDEX_MAP.put(PduHeaders.RESPONSE_TEXT, PDU_COLUMN_RESPONSE_TEXT);
+        TEXT_STRING_COLUMN_INDEX_MAP.put(PduHeaders.TRANSACTION_ID, PDU_COLUMN_TRANSACTION_ID);
+
+        TEXT_STRING_COLUMN_NAME_MAP = new HashMap<Integer, String>();
+        TEXT_STRING_COLUMN_NAME_MAP.put(PduHeaders.CONTENT_LOCATION, Mms.CONTENT_LOCATION);
+        TEXT_STRING_COLUMN_NAME_MAP.put(PduHeaders.CONTENT_TYPE, Mms.CONTENT_TYPE);
+        TEXT_STRING_COLUMN_NAME_MAP.put(PduHeaders.MESSAGE_CLASS, Mms.MESSAGE_CLASS);
+        TEXT_STRING_COLUMN_NAME_MAP.put(PduHeaders.MESSAGE_ID, Mms.MESSAGE_ID);
+        TEXT_STRING_COLUMN_NAME_MAP.put(PduHeaders.RESPONSE_TEXT, Mms.RESPONSE_TEXT);
+        TEXT_STRING_COLUMN_NAME_MAP.put(PduHeaders.TRANSACTION_ID, Mms.TRANSACTION_ID);
+
+        // Octet field code -> column index/name map.
+        OCTET_COLUMN_INDEX_MAP = new HashMap<Integer, Integer>();
+        OCTET_COLUMN_INDEX_MAP.put(PduHeaders.CONTENT_CLASS, PDU_COLUMN_CONTENT_CLASS);
+        OCTET_COLUMN_INDEX_MAP.put(PduHeaders.DELIVERY_REPORT, PDU_COLUMN_DELIVERY_REPORT);
+        OCTET_COLUMN_INDEX_MAP.put(PduHeaders.MESSAGE_TYPE, PDU_COLUMN_MESSAGE_TYPE);
+        OCTET_COLUMN_INDEX_MAP.put(PduHeaders.MMS_VERSION, PDU_COLUMN_MMS_VERSION);
+        OCTET_COLUMN_INDEX_MAP.put(PduHeaders.PRIORITY, PDU_COLUMN_PRIORITY);
+        OCTET_COLUMN_INDEX_MAP.put(PduHeaders.READ_REPORT, PDU_COLUMN_READ_REPORT);
+        OCTET_COLUMN_INDEX_MAP.put(PduHeaders.READ_STATUS, PDU_COLUMN_READ_STATUS);
+        OCTET_COLUMN_INDEX_MAP.put(PduHeaders.REPORT_ALLOWED, PDU_COLUMN_REPORT_ALLOWED);
+        OCTET_COLUMN_INDEX_MAP.put(PduHeaders.RETRIEVE_STATUS, PDU_COLUMN_RETRIEVE_STATUS);
+        OCTET_COLUMN_INDEX_MAP.put(PduHeaders.STATUS, PDU_COLUMN_STATUS);
+
+        OCTET_COLUMN_NAME_MAP = new HashMap<Integer, String>();
+        OCTET_COLUMN_NAME_MAP.put(PduHeaders.CONTENT_CLASS, Mms.CONTENT_CLASS);
+        OCTET_COLUMN_NAME_MAP.put(PduHeaders.DELIVERY_REPORT, Mms.DELIVERY_REPORT);
+        OCTET_COLUMN_NAME_MAP.put(PduHeaders.MESSAGE_TYPE, Mms.MESSAGE_TYPE);
+        OCTET_COLUMN_NAME_MAP.put(PduHeaders.MMS_VERSION, Mms.MMS_VERSION);
+        OCTET_COLUMN_NAME_MAP.put(PduHeaders.PRIORITY, Mms.PRIORITY);
+        OCTET_COLUMN_NAME_MAP.put(PduHeaders.READ_REPORT, Mms.READ_REPORT);
+        OCTET_COLUMN_NAME_MAP.put(PduHeaders.READ_STATUS, Mms.READ_STATUS);
+        OCTET_COLUMN_NAME_MAP.put(PduHeaders.REPORT_ALLOWED, Mms.REPORT_ALLOWED);
+        OCTET_COLUMN_NAME_MAP.put(PduHeaders.RETRIEVE_STATUS, Mms.RETRIEVE_STATUS);
+        OCTET_COLUMN_NAME_MAP.put(PduHeaders.STATUS, Mms.STATUS);
+
+        // Long field code -> column index/name map.
+        LONG_COLUMN_INDEX_MAP = new HashMap<Integer, Integer>();
+        LONG_COLUMN_INDEX_MAP.put(PduHeaders.DATE, PDU_COLUMN_DATE);
+        LONG_COLUMN_INDEX_MAP.put(PduHeaders.DELIVERY_TIME, PDU_COLUMN_DELIVERY_TIME);
+        LONG_COLUMN_INDEX_MAP.put(PduHeaders.EXPIRY, PDU_COLUMN_EXPIRY);
+        LONG_COLUMN_INDEX_MAP.put(PduHeaders.MESSAGE_SIZE, PDU_COLUMN_MESSAGE_SIZE);
+
+        LONG_COLUMN_NAME_MAP = new HashMap<Integer, String>();
+        LONG_COLUMN_NAME_MAP.put(PduHeaders.DATE, Mms.DATE);
+        LONG_COLUMN_NAME_MAP.put(PduHeaders.DELIVERY_TIME, Mms.DELIVERY_TIME);
+        LONG_COLUMN_NAME_MAP.put(PduHeaders.EXPIRY, Mms.EXPIRY);
+        LONG_COLUMN_NAME_MAP.put(PduHeaders.MESSAGE_SIZE, Mms.MESSAGE_SIZE);
+
+        PDU_CACHE_INSTANCE = PduCache.getInstance();
+     }
+
+    private final Context mContext;
+    private final ContentResolver mContentResolver;
+
+    private PduPersister(Context context) {
+        mContext = context;
+        mContentResolver = context.getContentResolver();
+     }
+
+    /** Get(or create if not exist) an instance of PduPersister */
+    public static PduPersister getPduPersister(Context context) {
+        if ((sPersister == null) || !context.equals(sPersister.mContext)) {
+            sPersister = new PduPersister(context);
+        }
+
+        return sPersister;
+    }
+
+    private void setEncodedStringValueToHeaders(
+            Cursor c, int columnIndex,
+            PduHeaders headers, int mapColumn) {
+        String s = c.getString(columnIndex);
+        if ((s != null) && (s.length() > 0)) {
+            int charsetColumnIndex = CHARSET_COLUMN_INDEX_MAP.get(mapColumn);
+            int charset = c.getInt(charsetColumnIndex);
+            EncodedStringValue value = new EncodedStringValue(
+                    charset, getBytes(s));
+            headers.setEncodedStringValue(value, mapColumn);
+        }
+    }
+
+    private void setTextStringToHeaders(
+            Cursor c, int columnIndex,
+            PduHeaders headers, int mapColumn) {
+        String s = c.getString(columnIndex);
+        if (s != null) {
+            headers.setTextString(getBytes(s), mapColumn);
+        }
+    }
+
+    private void setOctetToHeaders(
+            Cursor c, int columnIndex,
+            PduHeaders headers, int mapColumn) throws InvalidHeaderValueException {
+        if (!c.isNull(columnIndex)) {
+            int b = c.getInt(columnIndex);
+            headers.setOctet(b, mapColumn);
+        }
+    }
+
+    private void setLongToHeaders(
+            Cursor c, int columnIndex,
+            PduHeaders headers, int mapColumn) {
+        if (!c.isNull(columnIndex)) {
+            long l = c.getLong(columnIndex);
+            headers.setLongInteger(l, mapColumn);
+        }
+    }
+
+    private Integer getIntegerFromPartColumn(Cursor c, int columnIndex) {
+        if (!c.isNull(columnIndex)) {
+            return c.getInt(columnIndex);
+        }
+        return null;
+    }
+
+    private byte[] getByteArrayFromPartColumn(Cursor c, int columnIndex) {
+        if (!c.isNull(columnIndex)) {
+            return getBytes(c.getString(columnIndex));
+        }
+        return null;
+    }
+
+    private PduPart[] loadParts(long msgId) throws MmsException {
+        Cursor c = SqliteWrapper.query(mContext, mContentResolver,
+                Uri.parse("content://mms/" + msgId + "/part"),
+                PART_PROJECTION, null, null, null);
+
+        PduPart[] parts = null;
+
+        try {
+            if ((c == null) || (c.getCount() == 0)) {
+                if (LOCAL_LOGV) {
+                    Log.v(TAG, "loadParts(" + msgId + "): no part to load.");
+                }
+                return null;
+            }
+
+            int partCount = c.getCount();
+            int partIdx = 0;
+            parts = new PduPart[partCount];
+            while (c.moveToNext()) {
+                PduPart part = new PduPart();
+                Integer charset = getIntegerFromPartColumn(
+                        c, PART_COLUMN_CHARSET);
+                if (charset != null) {
+                    part.setCharset(charset);
+                }
+
+                byte[] contentDisposition = getByteArrayFromPartColumn(
+                        c, PART_COLUMN_CONTENT_DISPOSITION);
+                if (contentDisposition != null) {
+                    part.setContentDisposition(contentDisposition);
+                }
+
+                byte[] contentId = getByteArrayFromPartColumn(
+                        c, PART_COLUMN_CONTENT_ID);
+                if (contentId != null) {
+                    part.setContentId(contentId);
+                }
+
+                byte[] contentLocation = getByteArrayFromPartColumn(
+                        c, PART_COLUMN_CONTENT_LOCATION);
+                if (contentLocation != null) {
+                    part.setContentLocation(contentLocation);
+                }
+
+                byte[] contentType = getByteArrayFromPartColumn(
+                        c, PART_COLUMN_CONTENT_TYPE);
+                if (contentType != null) {
+                    part.setContentType(contentType);
+                } else {
+                    throw new MmsException("Content-Type must be set.");
+                }
+
+                byte[] fileName = getByteArrayFromPartColumn(
+                        c, PART_COLUMN_FILENAME);
+                if (fileName != null) {
+                    part.setFilename(fileName);
+                }
+
+                byte[] name = getByteArrayFromPartColumn(
+                        c, PART_COLUMN_NAME);
+                if (name != null) {
+                    part.setName(name);
+                }
+
+                // Construct a Uri for this part.
+                long partId = c.getLong(PART_COLUMN_ID);
+                Uri partURI = Uri.parse("content://mms/part/" + partId);
+                part.setDataUri(partURI);
+
+                // For images/audio/video, we won't keep their data in Part
+                // because their renderer accept Uri as source.
+                String type = toIsoString(contentType);
+                if (!ContentType.isImageType(type)
+                        && !ContentType.isAudioType(type)
+                        && !ContentType.isVideoType(type)) {
+                    ByteArrayOutputStream baos = new ByteArrayOutputStream();
+                    InputStream is = null;
+
+                    try {
+                        is = mContentResolver.openInputStream(partURI);
+
+                        byte[] buffer = new byte[256];
+                        int len = is.read(buffer);
+                        while (len >= 0) {
+                            baos.write(buffer, 0, len);
+                            len = is.read(buffer);
+                        }
+                    } catch (IOException e) {
+                        Log.e(TAG, "Failed to load part data", e);
+                        c.close();
+                        throw new MmsException(e);
+                    } finally {
+                        if (is != null) {
+                            try {
+                                is.close();
+                            } catch (IOException e) {
+                                Log.e(TAG, "Failed to close stream", e);
+                            } // Ignore
+                        }
+                    }
+                    part.setData(baos.toByteArray());
+                }
+                parts[partIdx++] = part;
+            }
+        } finally {
+            if (c != null) {
+                c.close();
+            }
+        }
+
+        return parts;
+    }
+
+    private void loadAddress(long msgId, PduHeaders headers) {
+        Cursor c = SqliteWrapper.query(mContext, mContentResolver,
+                Uri.parse("content://mms/" + msgId + "/addr"),
+                new String[] { Addr.ADDRESS, Addr.CHARSET, Addr.TYPE },
+                null, null, null);
+
+        if (c != null) {
+            try {
+                while (c.moveToNext()) {
+                    String addr = c.getString(0);
+                    if (!TextUtils.isEmpty(addr)) {
+                        int addrType = c.getInt(2);
+                        switch (addrType) {
+                            case PduHeaders.FROM:
+                                headers.setEncodedStringValue(
+                                        new EncodedStringValue(c.getInt(1), getBytes(addr)),
+                                        addrType);
+                                break;
+                            case PduHeaders.TO:
+                            case PduHeaders.CC:
+                            case PduHeaders.BCC:
+                                headers.appendEncodedStringValue(
+                                        new EncodedStringValue(c.getInt(1), getBytes(addr)),
+                                        addrType);
+                                break;
+                            default:
+                                Log.e(TAG, "Unknown address type: " + addrType);
+                                break;
+                        }
+                    }
+                }
+            } finally {
+                c.close();
+            }
+        }
+    }
+
+    /**
+     * Load a PDU from storage by given Uri.
+     *
+     * @param uri The Uri of the PDU to be loaded.
+     * @return A generic PDU object, it may be cast to dedicated PDU.
+     * @throws MmsException Failed to load some fields of a PDU.
+     */
+    public GenericPdu load(Uri uri) throws MmsException {
+        PduCacheEntry cacheEntry = PDU_CACHE_INSTANCE.get(uri);
+        if (cacheEntry != null) {
+            return cacheEntry.getPdu();
+        }
+
+        Cursor c = SqliteWrapper.query(mContext, mContentResolver, uri,
+                        PDU_PROJECTION, null, null, null);
+        PduHeaders headers = new PduHeaders();
+        Set<Entry<Integer, Integer>> set;
+        long msgId = ContentUris.parseId(uri);
+        int msgBox;
+        long threadId;
+
+        try {
+            if ((c == null) || (c.getCount() != 1) || !c.moveToFirst()) {
+                throw new MmsException("Bad uri: " + uri);
+            }
+
+            msgBox = c.getInt(PDU_COLUMN_MESSAGE_BOX);
+            threadId = c.getLong(PDU_COLUMN_THREAD_ID);
+
+            set = ENCODED_STRING_COLUMN_INDEX_MAP.entrySet();
+            for (Entry<Integer, Integer> e : set) {
+                setEncodedStringValueToHeaders(
+                        c, e.getValue(), headers, e.getKey());
+            }
+
+            set = TEXT_STRING_COLUMN_INDEX_MAP.entrySet();
+            for (Entry<Integer, Integer> e : set) {
+                setTextStringToHeaders(
+                        c, e.getValue(), headers, e.getKey());
+            }
+
+            set = OCTET_COLUMN_INDEX_MAP.entrySet();
+            for (Entry<Integer, Integer> e : set) {
+                setOctetToHeaders(
+                        c, e.getValue(), headers, e.getKey());
+            }
+
+            set = LONG_COLUMN_INDEX_MAP.entrySet();
+            for (Entry<Integer, Integer> e : set) {
+                setLongToHeaders(
+                        c, e.getValue(), headers, e.getKey());
+            }
+        } finally {
+            if (c != null) {
+                c.close();
+            }
+        }
+
+        // Check whether 'msgId' has been assigned a valid value.
+        if (msgId == -1L) {
+            throw new MmsException("Error! ID of the message: -1.");
+        }
+
+        // Load address information of the MM.
+        loadAddress(msgId, headers);
+
+        int msgType = headers.getOctet(PduHeaders.MESSAGE_TYPE);
+        PduBody body = new PduBody();
+
+        // For PDU which type is M_retrieve.conf or Send.req, we should
+        // load multiparts and put them into the body of the PDU.
+        if ((msgType == PduHeaders.MESSAGE_TYPE_RETRIEVE_CONF)
+                || (msgType == PduHeaders.MESSAGE_TYPE_SEND_REQ)) {
+            PduPart[] parts = loadParts(msgId);
+            if (parts != null) {
+                int partsNum = parts.length;
+                for (int i = 0; i < partsNum; i++) {
+                    body.addPart(parts[i]);
+                }
+            }
+        }
+
+        GenericPdu pdu = null;
+        switch (msgType) {
+            case PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND:
+                pdu = new NotificationInd(headers);
+                break;
+            case PduHeaders.MESSAGE_TYPE_DELIVERY_IND:
+                pdu = new DeliveryInd(headers);
+                break;
+            case PduHeaders.MESSAGE_TYPE_READ_ORIG_IND:
+                pdu = new ReadOrigInd(headers);
+                break;
+            case PduHeaders.MESSAGE_TYPE_RETRIEVE_CONF:
+                pdu = new RetrieveConf(headers, body);
+                break;
+            case PduHeaders.MESSAGE_TYPE_SEND_REQ:
+                pdu = new SendReq(headers, body);
+                break;
+            case PduHeaders.MESSAGE_TYPE_ACKNOWLEDGE_IND:
+                pdu = new AcknowledgeInd(headers);
+                break;
+            case PduHeaders.MESSAGE_TYPE_NOTIFYRESP_IND:
+                pdu = new NotifyRespInd(headers);
+                break;
+            case PduHeaders.MESSAGE_TYPE_READ_REC_IND:
+                pdu = new ReadRecInd(headers);
+                break;
+            case PduHeaders.MESSAGE_TYPE_SEND_CONF:
+            case PduHeaders.MESSAGE_TYPE_FORWARD_REQ:
+            case PduHeaders.MESSAGE_TYPE_FORWARD_CONF:
+            case PduHeaders.MESSAGE_TYPE_MBOX_STORE_REQ:
+            case PduHeaders.MESSAGE_TYPE_MBOX_STORE_CONF:
+            case PduHeaders.MESSAGE_TYPE_MBOX_VIEW_REQ:
+            case PduHeaders.MESSAGE_TYPE_MBOX_VIEW_CONF:
+            case PduHeaders.MESSAGE_TYPE_MBOX_UPLOAD_REQ:
+            case PduHeaders.MESSAGE_TYPE_MBOX_UPLOAD_CONF:
+            case PduHeaders.MESSAGE_TYPE_MBOX_DELETE_REQ:
+            case PduHeaders.MESSAGE_TYPE_MBOX_DELETE_CONF:
+            case PduHeaders.MESSAGE_TYPE_MBOX_DESCR:
+            case PduHeaders.MESSAGE_TYPE_DELETE_REQ:
+            case PduHeaders.MESSAGE_TYPE_DELETE_CONF:
+            case PduHeaders.MESSAGE_TYPE_CANCEL_REQ:
+            case PduHeaders.MESSAGE_TYPE_CANCEL_CONF:
+                throw new MmsException(
+                        "Unsupported PDU type: " + Integer.toHexString(msgType));
+
+            default:
+                throw new MmsException(
+                        "Unrecognized PDU type: " + Integer.toHexString(msgType));
+        }
+
+        cacheEntry = new PduCacheEntry(pdu, msgBox, threadId);
+        PDU_CACHE_INSTANCE.put(uri, cacheEntry);
+        return pdu;
+    }
+
+    private void persistAddress(
+            long msgId, int type, EncodedStringValue[] array) {
+        ContentValues values = new ContentValues(3);
+
+        for (EncodedStringValue addr : array) {
+            values.clear(); // Clear all values first.
+            values.put(Addr.ADDRESS, toIsoString(addr.getTextString()));
+            values.put(Addr.CHARSET, addr.getCharacterSet());
+            values.put(Addr.TYPE, type);
+
+            Uri uri = Uri.parse("content://mms/" + msgId + "/addr");
+            SqliteWrapper.insert(mContext, mContentResolver, uri, values);
+        }
+    }
+
+    public Uri persistPart(PduPart part, long msgId)
+            throws MmsException {
+        Uri uri = Uri.parse("content://mms/" + msgId + "/part");
+        ContentValues values = new ContentValues(8);
+
+        int charset = part.getCharset();
+        if (charset != 0 ) {
+            values.put(Part.CHARSET, charset);
+        }
+
+        String contentType = null;
+        if (part.getContentType() != null) {
+            contentType = toIsoString(part.getContentType());
+            values.put(Part.CONTENT_TYPE, contentType);
+            // To ensure the SMIL part is always the first part.
+            if (ContentType.APP_SMIL.equals(contentType)) {
+                values.put(Part.SEQ, -1);
+            }
+        } else {
+            throw new MmsException("MIME type of the part must be set.");
+        }
+
+        if (part.getFilename() != null) {
+            String fileName = new String(part.getFilename());
+            values.put(Part.FILENAME, fileName);
+        }
+
+        if (part.getName() != null) {
+            String name = new String(part.getName());
+            values.put(Part.NAME, name);
+        }
+
+        Object value = null;
+        if (part.getContentDisposition() != null) {
+            value = toIsoString(part.getContentDisposition());
+            values.put(Part.CONTENT_DISPOSITION, (String) value);
+        }
+
+        if (part.getContentId() != null) {
+            value = toIsoString(part.getContentId());
+            values.put(Part.CONTENT_ID, (String) value);
+        }
+
+        if (part.getContentLocation() != null) {
+            value = toIsoString(part.getContentLocation());
+            values.put(Part.CONTENT_LOCATION, (String) value);
+        }
+
+        Uri res = SqliteWrapper.insert(mContext, mContentResolver, uri, values);
+        if (res == null) {
+            throw new MmsException("Failed to persist part, return null.");
+        }
+
+        persistData(part, res, contentType);
+        // After successfully store the data, we should update
+        // the dataUri of the part.
+        part.setDataUri(res);
+
+        return res;
+    }
+
+    /**
+     * Save data of the part into storage. The source data may be given
+     * by a byte[] or a Uri. If it's a byte[], directly save it
+     * into storage, otherwise load source data from the dataUri and then
+     * save it. If the data is an image, we may scale down it according
+     * to user preference.
+     *
+     * @param part The PDU part which contains data to be saved.
+     * @param uri The URI of the part.
+     * @param contentType The MIME type of the part.
+     * @throws MmsException Cannot find source data or error occurred
+     *         while saving the data.
+     */
+    private void persistData(PduPart part, Uri uri,
+            String contentType)
+            throws MmsException {
+        OutputStream os = null;
+        InputStream is = null;
+
+        try {
+            os = mContentResolver.openOutputStream(uri);
+            byte[] data = part.getData();
+            if (data == null) {
+                Uri dataUri = part.getDataUri();
+                if ((dataUri == null) || (dataUri == uri)) {
+                    Log.w(TAG, "Can't find data for this part.");
+                    return;
+                }
+                is = mContentResolver.openInputStream(dataUri);
+                
+                if (LOCAL_LOGV) {
+                    Log.v(TAG, "Saving data to: " + uri);
+                }
+
+                byte[] buffer = new byte[256];
+                for (int len = 0; (len = is.read(buffer)) != -1; ) {
+                    os.write(buffer, 0, len);
+                }
+            } else {
+                if (LOCAL_LOGV) {
+                    Log.v(TAG, "Saving data to: " + uri);
+                }
+                os.write(data);
+            }
+        } catch (FileNotFoundException e) {
+            Log.e(TAG, "Failed to open Input/Output stream.", e);
+            throw new MmsException(e);
+        } catch (IOException e) {
+            Log.e(TAG, "Failed to read/write data.", e);
+            throw new MmsException(e);
+        } finally {
+            if (os != null) {
+                try {
+                    os.close();
+                } catch (IOException e) {
+                    Log.e(TAG, "IOException while closing: " + os, e);
+                } // Ignore
+            }
+            if (is != null) {
+                try {
+                    is.close();
+                } catch (IOException e) {
+                    Log.e(TAG, "IOException while closing: " + is, e);
+                } // Ignore
+            }
+        }
+    }
+
+    private void updateAddress(
+            long msgId, int type, EncodedStringValue[] array) {
+        // Delete old address information and then insert new ones.
+        SqliteWrapper.delete(mContext, mContentResolver,
+                Uri.parse("content://mms/" + msgId + "/addr"),
+                Addr.TYPE + "=" + type, null);
+
+        persistAddress(msgId, type, array);
+    }
+
+    /**
+     * Update headers of a SendReq.
+     *
+     * @param uri The PDU which need to be updated.
+     * @param pdu New headers.
+     * @throws MmsException Bad URI or updating failed.
+     */
+    public void updateHeaders(Uri uri, SendReq sendReq) {
+        PDU_CACHE_INSTANCE.purge(uri);
+
+        ContentValues values = new ContentValues(9);
+        byte[] contentType = sendReq.getContentType();
+        if (contentType != null) {
+            values.put(Mms.CONTENT_TYPE, toIsoString(contentType));
+        }
+
+        long date = sendReq.getDate();
+        if (date != -1) {
+            values.put(Mms.DATE, date);
+        }
+
+        int deliveryReport = sendReq.getDeliveryReport();
+        if (deliveryReport != 0) {
+            values.put(Mms.DELIVERY_REPORT, deliveryReport);
+        }
+
+        long expiry = sendReq.getExpiry();
+        if (expiry != -1) {
+            values.put(Mms.EXPIRY, expiry);
+        }
+
+        byte[] msgClass = sendReq.getMessageClass();
+        if (msgClass != null) {
+            values.put(Mms.MESSAGE_CLASS, toIsoString(msgClass));
+        }
+
+        int priority = sendReq.getPriority();
+        if (priority != 0) {
+            values.put(Mms.PRIORITY, priority);
+        }
+
+        int readReport = sendReq.getReadReport();
+        if (readReport != 0) {
+            values.put(Mms.READ_REPORT, readReport);
+        }
+
+        byte[] transId = sendReq.getTransactionId();
+        if (transId != null) {
+            values.put(Mms.TRANSACTION_ID, toIsoString(transId));
+        }
+
+        EncodedStringValue subject = sendReq.getSubject();
+        if (subject != null) {
+            values.put(Mms.SUBJECT, toIsoString(subject.getTextString()));
+            values.put(Mms.SUBJECT_CHARSET, subject.getCharacterSet());
+        }
+
+        PduHeaders headers = sendReq.getPduHeaders();
+        HashSet<String> recipients = new HashSet<String>();
+        for (int addrType : ADDRESS_FIELDS) {
+            EncodedStringValue[] array = null;
+            if (addrType == PduHeaders.FROM) {
+                EncodedStringValue v = headers.getEncodedStringValue(addrType);
+                if (v != null) {
+                    array = new EncodedStringValue[1];
+                    array[0] = v;
+                }
+            } else {
+                array = headers.getEncodedStringValues(addrType);
+            }
+
+            if (array != null) {
+                long msgId = ContentUris.parseId(uri);
+                updateAddress(msgId, addrType, array);
+                if (addrType == PduHeaders.TO) {
+                    for (EncodedStringValue v : array) {
+                        if (v != null) {
+                            recipients.add(v.getString());
+                        }
+                    }
+                }
+            }
+        }
+
+        long threadId = Threads.getOrCreateThreadId(mContext, recipients);
+        values.put(Mms.THREAD_ID, threadId);
+
+        SqliteWrapper.update(mContext, mContentResolver, uri, values, null, null);
+    }
+
+    private void updatePart(Uri uri, PduPart part) throws MmsException {
+        ContentValues values = new ContentValues(7);
+
+        int charset = part.getCharset();
+        if (charset != 0 ) {
+            values.put(Part.CHARSET, charset);
+        }
+
+        String contentType = null;
+        if (part.getContentType() != null) {
+            contentType = toIsoString(part.getContentType());
+            values.put(Part.CONTENT_TYPE, contentType);
+        } else {
+            throw new MmsException("MIME type of the part must be set.");
+        }
+
+        if (part.getFilename() != null) {
+            String fileName = new String(part.getFilename());
+            values.put(Part.FILENAME, fileName);
+        }
+
+        if (part.getName() != null) {
+            String name = new String(part.getName());
+            values.put(Part.NAME, name);
+        }
+
+        Object value = null;
+        if (part.getContentDisposition() != null) {
+            value = toIsoString(part.getContentDisposition());
+            values.put(Part.CONTENT_DISPOSITION, (String) value);
+        }
+
+        if (part.getContentId() != null) {
+            value = toIsoString(part.getContentId());
+            values.put(Part.CONTENT_ID, (String) value);
+        }
+
+        if (part.getContentLocation() != null) {
+            value = toIsoString(part.getContentLocation());
+            values.put(Part.CONTENT_LOCATION, (String) value);
+        }
+
+        SqliteWrapper.update(mContext, mContentResolver, uri, values, null, null);
+
+        // Only update the data when:
+        // 1. New binary data supplied or
+        // 2. The Uri of the part is different from the current one.
+        if ((part.getData() != null)
+                || (uri != part.getDataUri())) {
+            persistData(part, uri, contentType);
+        }
+    }
+
+    /**
+     * Update all parts of a PDU.
+     *
+     * @param uri The PDU which need to be updated.
+     * @param body New message body of the PDU.
+     * @throws MmsException Bad URI or updating failed.
+     */
+    public void updateParts(Uri uri, PduBody body)
+            throws MmsException {
+        PduCacheEntry cacheEntry = PDU_CACHE_INSTANCE.get(uri);
+        if (cacheEntry != null) {
+            ((MultimediaMessagePdu) cacheEntry.getPdu()).setBody(body);
+        }
+
+        ArrayList<PduPart> toBeCreated = new ArrayList<PduPart>();
+        HashMap<Uri, PduPart> toBeUpdated = new HashMap<Uri, PduPart>();
+
+        int partsNum = body.getPartsNum();
+        StringBuilder filter = new StringBuilder().append('(');
+        for (int i = 0; i < partsNum; i++) {
+            PduPart part = body.getPart(i);
+            Uri partUri = part.getDataUri();
+            if ((partUri == null) || !partUri.getAuthority().startsWith("mms")) {
+                toBeCreated.add(part);
+            } else {
+                toBeUpdated.put(partUri, part);
+
+                // Don't use 'i > 0' to determine whether we should append
+                // 'AND' since 'i = 0' may be skipped in another branch.
+                if (filter.length() > 1) {
+                    filter.append(" AND ");
+                }
+
+                filter.append(Part._ID);
+                filter.append("!=");
+                DatabaseUtils.appendEscapedSQLString(filter, partUri.getLastPathSegment());
+            }
+        }
+        filter.append(')');
+
+        long msgId = ContentUris.parseId(uri);
+
+        // Remove the parts which doesn't exist anymore.
+        SqliteWrapper.delete(mContext, mContentResolver,
+                Uri.parse(Mms.CONTENT_URI + "/" + msgId + "/part"),
+                filter.length() > 2 ? filter.toString() : null, null);
+
+        // Create new parts which didn't exist before.
+        for (PduPart part : toBeCreated) {
+            persistPart(part, msgId);
+        }
+
+        // Update the modified parts.
+        for (Map.Entry<Uri, PduPart> e : toBeUpdated.entrySet()) {
+            updatePart(e.getKey(), e.getValue());
+        }
+    }
+
+    /**
+     * Persist a PDU object to specific location in the storage.
+     *
+     * @param pdu The PDU object to be stored.
+     * @param uri Where to store the given PDU object.
+     * @return A Uri which can be used to access the stored PDU.
+     */
+    public Uri persist(GenericPdu pdu, Uri uri) throws MmsException {
+        if (uri == null) {
+            throw new MmsException("Uri may not be null.");
+        }
+
+        Integer msgBox = MESSAGE_BOX_MAP.get(uri);
+        if (msgBox == null) {
+            throw new MmsException(
+                    "Bad destination, must be one of "
+                    + "content://mms/inbox, content://mms/sent, "
+                    + "content://mms/drafts, content://mms/outbox, "
+                    + "content://mms/temp.");
+        }
+
+        PduHeaders header = pdu.getPduHeaders();
+        PduBody body = null;
+        ContentValues values = new ContentValues();
+        Set<Entry<Integer, String>> set;
+
+        set = ENCODED_STRING_COLUMN_NAME_MAP.entrySet();
+        for (Entry<Integer, String> e : set) {
+            int field = e.getKey();
+            EncodedStringValue encodedString = header.getEncodedStringValue(field);
+            if (encodedString != null) {
+                String charsetColumn = CHARSET_COLUMN_NAME_MAP.get(field);
+                values.put(e.getValue(), toIsoString(encodedString.getTextString()));
+                values.put(charsetColumn, encodedString.getCharacterSet());
+            }
+        }
+
+        set = TEXT_STRING_COLUMN_NAME_MAP.entrySet();
+        for (Entry<Integer, String> e : set){
+            byte[] text = header.getTextString(e.getKey());
+            if (text != null) {
+                values.put(e.getValue(), toIsoString(text));
+            }
+        }
+
+        set = OCTET_COLUMN_NAME_MAP.entrySet();
+        for (Entry<Integer, String> e : set){
+            int b = header.getOctet(e.getKey());
+            if (b != 0) {
+                values.put(e.getValue(), b);
+            }
+        }
+
+        set = LONG_COLUMN_NAME_MAP.entrySet();
+        for (Entry<Integer, String> e : set){
+            long l = header.getLongInteger(e.getKey());
+            if (l != -1L) {
+                values.put(e.getValue(), l);
+            }
+        }
+
+        HashMap<Integer, EncodedStringValue[]> addressMap =
+                new HashMap<Integer, EncodedStringValue[]>(ADDRESS_FIELDS.length);
+        // Save address information.
+        for (int addrType : ADDRESS_FIELDS) {
+            EncodedStringValue[] array = null;
+            if (addrType == PduHeaders.FROM) {
+                EncodedStringValue v = header.getEncodedStringValue(addrType);
+                if (v != null) {
+                    array = new EncodedStringValue[1];
+                    array[0] = v;
+                }
+            } else {
+                array = header.getEncodedStringValues(addrType);
+            }
+            addressMap.put(addrType, array);
+        }
+
+        HashSet<String> recipients = new HashSet<String>();
+        long threadId = DUMMY_THREAD_ID;
+        int msgType = pdu.getMessageType();
+        // Here we only allocate thread ID for M-Notification.ind,
+        // M-Retrieve.conf and M-Send.req.
+        // Some of other PDU types may be allocated a thread ID outside
+        // this scope.
+        if ((msgType == PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND)
+                || (msgType == PduHeaders.MESSAGE_TYPE_RETRIEVE_CONF)
+                || (msgType == PduHeaders.MESSAGE_TYPE_SEND_REQ)) {
+            EncodedStringValue[] array = null;
+            switch (msgType) {
+                case PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND:
+                case PduHeaders.MESSAGE_TYPE_RETRIEVE_CONF:
+                    array = addressMap.get(PduHeaders.FROM);
+                    break;
+                case PduHeaders.MESSAGE_TYPE_SEND_REQ:
+                    array = addressMap.get(PduHeaders.TO);
+                    break;
+            }
+
+            if (array != null) {
+                for (EncodedStringValue v : array) {
+                    if (v != null) {
+                        recipients.add(v.getString());
+                    }
+                }
+            }
+            threadId = Threads.getOrCreateThreadId(mContext, recipients);
+        }
+        values.put(Mms.THREAD_ID, threadId);
+
+        // Save parts first to avoid inconsistent message is loaded
+        // while saving the parts.
+        long dummyId = System.currentTimeMillis(); // Dummy ID of the msg.
+        // Get body if the PDU is a RetrieveConf or SendReq.
+        if (pdu instanceof MultimediaMessagePdu) {
+            body = ((MultimediaMessagePdu) pdu).getBody();
+            // Start saving parts if necessary.
+            if (body != null) {
+                int partsNum = body.getPartsNum();
+                for (int i = 0; i < partsNum; i++) {
+                    PduPart part = body.getPart(i);
+                    persistPart(part, dummyId);
+                }
+            }
+        }
+
+        Uri res = SqliteWrapper.insert(mContext, mContentResolver, uri, values);
+        if (res == null) {
+            throw new MmsException("persist() failed: return null.");
+        }
+
+        // Get the real ID of the PDU and update all parts which were
+        // saved with the dummy ID.
+        long msgId = ContentUris.parseId(res);
+        values = new ContentValues(1);
+        values.put(Part.MSG_ID, msgId);
+        SqliteWrapper.update(mContext, mContentResolver,
+                             Uri.parse("content://mms/" + dummyId + "/part"),
+                             values, null, null);
+        // We should return the longest URI of the persisted PDU, for
+        // example, if input URI is "content://mms/inbox" and the _ID of
+        // persisted PDU is '8', we should return "content://mms/inbox/8"
+        // instead of "content://mms/8".
+        // FIXME: Should the MmsProvider be responsible for this???
+        res = Uri.parse(uri + "/" + msgId);
+
+        // Save address information.
+        for (int addrType : ADDRESS_FIELDS) {
+            EncodedStringValue[] array = addressMap.get(addrType);
+            if (array != null) {
+                persistAddress(msgId, addrType, array);
+            }
+        }
+
+        return res;
+    }
+
+    /**
+     * Move a PDU object from one location to another.
+     *
+     * @param from Specify the PDU object to be moved.
+     * @param to The destination location, should be one of the following:
+     *        "content://mms/inbox", "content://mms/sent",
+     *        "content://mms/drafts", "content://mms/outbox",
+     *        "content://mms/trash".
+     * @return New Uri of the moved PDU.
+     * @throws MmsException Error occurred while moving the message.
+     */
+    public Uri move(Uri from, Uri to) throws MmsException {
+        // Check whether the 'msgId' has been assigned a valid value.
+        long msgId = ContentUris.parseId(from);
+        if (msgId == -1L) {
+            throw new MmsException("Error! ID of the message: -1.");
+        }
+
+        // Get corresponding int value of destination box.
+        Integer msgBox = MESSAGE_BOX_MAP.get(to);
+        if (msgBox == null) {
+            throw new MmsException(
+                    "Bad destination, must be one of "
+                    + "content://mms/inbox, content://mms/sent, "
+                    + "content://mms/drafts, content://mms/outbox, "
+                    + "content://mms/temp.");
+        }
+
+        ContentValues values = new ContentValues(1);
+        values.put(Mms.MESSAGE_BOX, msgBox);
+        SqliteWrapper.update(mContext, mContentResolver, from, values, null, null);
+        return ContentUris.withAppendedId(to, msgId);
+    }
+
+    /**
+     * Wrap a byte[] into a String.
+     */
+    public static String toIsoString(byte[] bytes) {
+        try {
+            return new String(bytes, CharacterSets.MIMENAME_ISO_8859_1);
+        } catch (UnsupportedEncodingException e) {
+            // Impossible to reach here!
+            Log.e(TAG, "ISO_8859_1 must be supported!", e);
+            return "";
+        }
+    }
+
+    /**
+     * Unpack a given String into a byte[].
+     */
+    public static byte[] getBytes(String data) {
+        try {
+            return data.getBytes(CharacterSets.MIMENAME_ISO_8859_1);
+        } catch (UnsupportedEncodingException e) {
+            // Impossible to reach here!
+            Log.e(TAG, "ISO_8859_1 must be supported!", e);
+            return new byte[0];
+        }
+    }
+
+    /**
+     * Remove all objects in the temporary path.
+     */
+    public void release() {
+        Uri uri = Uri.parse(TEMPORARY_DRM_OBJECT_URI);
+        SqliteWrapper.delete(mContext, mContentResolver, uri, null, null);
+    }
+
+    /**
+     * Find all messages to be sent or downloaded before certain time.
+     */
+    public Cursor getPendingMessages(long dueTime) {
+        Uri.Builder uriBuilder = PendingMessages.CONTENT_URI.buildUpon();
+        uriBuilder.appendQueryParameter("protocol", "mms");
+
+        String selection = PendingMessages.ERROR_TYPE + " < ?"
+                + " AND " + PendingMessages.DUE_TIME + " <= ?";
+
+        String[] selectionArgs = new String[] {
+                String.valueOf(MmsSms.ERR_TYPE_GENERIC_PERMANENT),
+                String.valueOf(dueTime)
+        };
+
+        return SqliteWrapper.query(mContext, mContentResolver,
+                uriBuilder.build(), null, selection, selectionArgs,
+                PendingMessages.DUE_TIME);
+    }
+}
diff --git a/core/java/com/google/android/mms/pdu/QuotedPrintable.java b/core/java/com/google/android/mms/pdu/QuotedPrintable.java
new file mode 100644
index 0000000..a34ed12
--- /dev/null
+++ b/core/java/com/google/android/mms/pdu/QuotedPrintable.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2007 Esmertec AG.
+ * Copyright (C) 2007 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.google.android.mms.pdu;
+
+import java.io.ByteArrayOutputStream;
+
+public class QuotedPrintable {
+    private static byte ESCAPE_CHAR = '=';
+
+    /**
+     * Decodes an array quoted-printable characters into an array of original bytes.
+     * Escaped characters are converted back to their original representation.
+     *
+     * <p>
+     * This function implements a subset of
+     * quoted-printable encoding specification (rule #1 and rule #2)
+     * as defined in RFC 1521.
+     * </p>
+     *
+     * @param bytes array of quoted-printable characters
+     * @return array of original bytes,
+     *         null if quoted-printable decoding is unsuccessful.
+     */
+    public static final byte[] decodeQuotedPrintable(byte[] bytes) {
+        if (bytes == null) {
+            return null;
+        }
+        ByteArrayOutputStream buffer = new ByteArrayOutputStream();
+        for (int i = 0; i < bytes.length; i++) {
+            int b = bytes[i];
+            if (b == ESCAPE_CHAR) {
+                try {
+                    if('\r' == (char)bytes[i + 1] &&
+                            '\n' == (char)bytes[i + 2]) {
+                        i += 2;
+                        continue;
+                    }
+                    int u = Character.digit((char) bytes[++i], 16);
+                    int l = Character.digit((char) bytes[++i], 16);
+                    if (u == -1 || l == -1) {
+                        return null;
+                    }
+                    buffer.write((char) ((u << 4) + l));
+                } catch (ArrayIndexOutOfBoundsException e) {
+                    return null;
+                }
+            } else {
+                buffer.write(b);
+            }
+        }
+        return buffer.toByteArray();
+    }
+}
diff --git a/core/java/com/google/android/mms/pdu/ReadOrigInd.java b/core/java/com/google/android/mms/pdu/ReadOrigInd.java
new file mode 100644
index 0000000..1bfc0bb
--- /dev/null
+++ b/core/java/com/google/android/mms/pdu/ReadOrigInd.java
@@ -0,0 +1,153 @@
+/*
+ * Copyright (C) 2007 Esmertec AG.
+ * Copyright (C) 2007 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.google.android.mms.pdu;
+
+import com.google.android.mms.InvalidHeaderValueException;
+
+public class ReadOrigInd extends GenericPdu {
+    /**
+     * Empty constructor.
+     * Since the Pdu corresponding to this class is constructed
+     * by the Proxy-Relay server, this class is only instantiated
+     * by the Pdu Parser.
+     *
+     * @throws InvalidHeaderValueException if error occurs.
+     */
+    public ReadOrigInd() throws InvalidHeaderValueException {
+        super();
+        setMessageType(PduHeaders.MESSAGE_TYPE_READ_ORIG_IND);
+    }
+
+    /**
+     * Constructor with given headers.
+     *
+     * @param headers Headers for this PDU.
+     */
+    ReadOrigInd(PduHeaders headers) {
+        super(headers);
+    }
+
+    /**
+     * Get Date value.
+     *
+     * @return the value
+     */
+    public long getDate() {
+        return mPduHeaders.getLongInteger(PduHeaders.DATE);
+    }
+
+    /**
+     * Set Date value.
+     *
+     * @param value the value
+     */
+    public void setDate(long value) {
+        mPduHeaders.setLongInteger(value, PduHeaders.DATE);
+    }
+
+    /**
+     * Get From value.
+     * From-value = Value-length
+     *      (Address-present-token Encoded-string-value | Insert-address-token)
+     *
+     * @return the value
+     */
+    public EncodedStringValue getFrom() {
+       return mPduHeaders.getEncodedStringValue(PduHeaders.FROM);
+    }
+
+    /**
+     * Set From value.
+     *
+     * @param value the value
+     * @throws NullPointerException if the value is null.
+     */
+    public void setFrom(EncodedStringValue value) {
+        mPduHeaders.setEncodedStringValue(value, PduHeaders.FROM);
+    }
+
+    /**
+     * Get Message-ID value.
+     *
+     * @return the value
+     */
+    public byte[] getMessageId() {
+        return mPduHeaders.getTextString(PduHeaders.MESSAGE_ID);
+    }
+
+    /**
+     * Set Message-ID value.
+     *
+     * @param value the value
+     * @throws NullPointerException if the value is null.
+     */
+    public void setMessageId(byte[] value) {
+        mPduHeaders.setTextString(value, PduHeaders.MESSAGE_ID);
+    }
+
+    /**
+     * Get X-MMS-Read-status value.
+     *
+     * @return the value
+     */
+    public int getReadStatus() {
+        return mPduHeaders.getOctet(PduHeaders.READ_STATUS);
+    }
+
+    /**
+     * Set X-MMS-Read-status value.
+     *
+     * @param value the value
+     * @throws InvalidHeaderValueException if the value is invalid.
+     */
+    public void setReadStatus(int value) throws InvalidHeaderValueException {
+        mPduHeaders.setOctet(value, PduHeaders.READ_STATUS);
+    }
+
+    /**
+     * Get To value.
+     *
+     * @return the value
+     */
+    public EncodedStringValue[] getTo() {
+        return mPduHeaders.getEncodedStringValues(PduHeaders.TO);
+    }
+
+    /**
+     * Set To value.
+     *
+     * @param value the value
+     * @throws NullPointerException if the value is null.
+     */
+    public void setTo(EncodedStringValue[] value) {
+        mPduHeaders.setEncodedStringValues(value, PduHeaders.TO);
+    }
+
+    /*
+     * Optional, not supported header fields:
+     *
+     *     public byte[] getApplicId() {return null;}
+     *     public void setApplicId(byte[] value) {}
+     *
+     *     public byte[] getAuxApplicId() {return null;}
+     *     public void getAuxApplicId(byte[] value) {}
+     *
+     *     public byte[] getReplyApplicId() {return 0x00;}
+     *     public void setReplyApplicId(byte[] value) {}
+     */
+}
diff --git a/core/java/com/google/android/mms/pdu/ReadRecInd.java b/core/java/com/google/android/mms/pdu/ReadRecInd.java
new file mode 100644
index 0000000..0a4dbf0
--- /dev/null
+++ b/core/java/com/google/android/mms/pdu/ReadRecInd.java
@@ -0,0 +1,165 @@
+/*
+ * Copyright (C) 2007 Esmertec AG.
+ * Copyright (C) 2007 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.google.android.mms.pdu;
+
+import com.google.android.mms.InvalidHeaderValueException;
+
+public class ReadRecInd extends GenericPdu {
+    /**
+     * Constructor, used when composing a M-ReadRec.ind pdu.
+     *
+     * @param from the from value
+     * @param messageId the message ID value
+     * @param mmsVersion current viersion of mms
+     * @param readStatus the read status value
+     * @param to the to value
+     * @throws InvalidHeaderValueException if parameters are invalid.
+     *         NullPointerException if messageId or to is null.
+     */
+    public ReadRecInd(EncodedStringValue from,
+                      byte[] messageId,
+                      int mmsVersion,
+                      int readStatus,
+                      EncodedStringValue[] to) throws InvalidHeaderValueException {
+        super();
+        setMessageType(PduHeaders.MESSAGE_TYPE_READ_REC_IND);
+        setFrom(from);
+        setMessageId(messageId);
+        setMmsVersion(mmsVersion);
+        setTo(to);
+        setReadStatus(readStatus);
+    }
+
+    /**
+     * Constructor with given headers.
+     *
+     * @param headers Headers for this PDU.
+     */
+    ReadRecInd(PduHeaders headers) {
+        super(headers);
+    }
+
+    /**
+     * Get Date value.
+     *
+     * @return the value
+     */
+    public long getDate() {
+        return mPduHeaders.getLongInteger(PduHeaders.DATE);
+    }
+
+    /**
+     * Set Date value.
+     *
+     * @param value the value
+     */
+    public void setDate(long value) {
+        mPduHeaders.setLongInteger(value, PduHeaders.DATE);
+    }
+
+    /**
+     * Get From value.
+     * From-value = Value-length
+     *      (Address-present-token Encoded-string-value | Insert-address-token)
+     *
+     * @return the value
+     */
+    public EncodedStringValue getFrom() {
+       return mPduHeaders.getEncodedStringValue(PduHeaders.FROM);
+    }
+
+    /**
+     * Set From value.
+     *
+     * @param value the value
+     * @throws NullPointerException if the value is null.
+     */
+    public void setFrom(EncodedStringValue value) {
+        mPduHeaders.setEncodedStringValue(value, PduHeaders.FROM);
+    }
+
+    /**
+     * Get Message-ID value.
+     *
+     * @return the value
+     */
+    public byte[] getMessageId() {
+        return mPduHeaders.getTextString(PduHeaders.MESSAGE_ID);
+    }
+
+    /**
+     * Set Message-ID value.
+     *
+     * @param value the value
+     * @throws NullPointerException if the value is null.
+     */
+    public void setMessageId(byte[] value) {
+        mPduHeaders.setTextString(value, PduHeaders.MESSAGE_ID);
+    }
+
+    /**
+     * Get To value.
+     *
+     * @return the value
+     */
+    public EncodedStringValue[] getTo() {
+        return mPduHeaders.getEncodedStringValues(PduHeaders.TO);
+    }
+
+    /**
+     * Set To value.
+     *
+     * @param value the value
+     * @throws NullPointerException if the value is null.
+     */
+    public void setTo(EncodedStringValue[] value) {
+        mPduHeaders.setEncodedStringValues(value, PduHeaders.TO);
+    }
+
+    /**
+     * Get X-MMS-Read-status value.
+     *
+     * @return the value
+     */
+    public int getReadStatus() {
+        return mPduHeaders.getOctet(PduHeaders.READ_STATUS);
+    }
+
+    /**
+     * Set X-MMS-Read-status value.
+     *
+     * @param value the value
+     * @throws InvalidHeaderValueException if the value is invalid.
+     */
+    public void setReadStatus(int value) throws InvalidHeaderValueException {
+        mPduHeaders.setOctet(value, PduHeaders.READ_STATUS);
+    }
+
+    /*
+     * Optional, not supported header fields:
+     *
+     *     public byte[] getApplicId() {return null;}
+     *     public void setApplicId(byte[] value) {}
+     *
+     *     public byte[] getAuxApplicId() {return null;}
+     *     public void getAuxApplicId(byte[] value) {}
+     *
+     *     public byte[] getReplyApplicId() {return 0x00;}
+     *     public void setReplyApplicId(byte[] value) {}
+     */
+}
diff --git a/core/java/com/google/android/mms/pdu/RetrieveConf.java b/core/java/com/google/android/mms/pdu/RetrieveConf.java
new file mode 100644
index 0000000..98e67c0
--- /dev/null
+++ b/core/java/com/google/android/mms/pdu/RetrieveConf.java
@@ -0,0 +1,300 @@
+/*
+ * Copyright (C) 2007 Esmertec AG.
+ * Copyright (C) 2007 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.google.android.mms.pdu;
+
+import com.google.android.mms.InvalidHeaderValueException;
+
+/**
+ * M-Retrive.conf Pdu.
+ */
+public class RetrieveConf extends MultimediaMessagePdu {
+    /**
+     * Empty constructor.
+     * Since the Pdu corresponding to this class is constructed
+     * by the Proxy-Relay server, this class is only instantiated
+     * by the Pdu Parser.
+     *
+     * @throws InvalidHeaderValueException if error occurs.
+     */
+    public RetrieveConf() throws InvalidHeaderValueException {
+        super();
+        setMessageType(PduHeaders.MESSAGE_TYPE_RETRIEVE_CONF);
+    }
+
+    /**
+     * Constructor with given headers.
+     *
+     * @param headers Headers for this PDU.
+     */
+    RetrieveConf(PduHeaders headers) {
+        super(headers);
+    }
+
+    /**
+     * Constructor with given headers and body
+     *
+     * @param headers Headers for this PDU.
+     * @param body Body of this PDu.
+     */
+    RetrieveConf(PduHeaders headers, PduBody body) {
+        super(headers, body);
+    }
+
+    /**
+     * Get CC value.
+     *
+     * @return the value
+     */
+    public EncodedStringValue[] getCc() {
+        return mPduHeaders.getEncodedStringValues(PduHeaders.CC);
+    }
+
+    /**
+     * Add a "CC" value.
+     *
+     * @param value the value
+     * @throws NullPointerException if the value is null.
+     */
+    public void addCc(EncodedStringValue value) {
+        mPduHeaders.appendEncodedStringValue(value, PduHeaders.CC);
+    }
+
+    /**
+     * Get Content-type value.
+     *
+     * @return the value
+     */
+    public byte[] getContentType() {
+        return mPduHeaders.getTextString(PduHeaders.CONTENT_TYPE);
+    }
+
+    /**
+     * Set Content-type value.
+     *
+     * @param value the value
+     * @throws NullPointerException if the value is null.
+     */
+    public void setContentType(byte[] value) {
+        mPduHeaders.setTextString(value, PduHeaders.CONTENT_TYPE);
+    }
+
+    /**
+     * Get X-Mms-Delivery-Report value.
+     *
+     * @return the value
+     */
+    public int getDeliveryReport() {
+        return mPduHeaders.getOctet(PduHeaders.DELIVERY_REPORT);
+    }
+
+    /**
+     * Set X-Mms-Delivery-Report value.
+     *
+     * @param value the value
+     * @throws InvalidHeaderValueException if the value is invalid.
+     */
+    public void setDeliveryReport(int value) throws InvalidHeaderValueException {
+        mPduHeaders.setOctet(value, PduHeaders.DELIVERY_REPORT);
+    }
+
+    /**
+     * Get From value.
+     * From-value = Value-length
+     *      (Address-present-token Encoded-string-value | Insert-address-token)
+     *
+     * @return the value
+     */
+    public EncodedStringValue getFrom() {
+       return mPduHeaders.getEncodedStringValue(PduHeaders.FROM);
+    }
+
+    /**
+     * Set From value.
+     *
+     * @param value the value
+     * @throws NullPointerException if the value is null.
+     */
+    public void setFrom(EncodedStringValue value) {
+        mPduHeaders.setEncodedStringValue(value, PduHeaders.FROM);
+    }
+
+    /**
+     * Get X-Mms-Message-Class value.
+     * Message-class-value = Class-identifier | Token-text
+     * Class-identifier = Personal | Advertisement | Informational | Auto
+     *
+     * @return the value
+     */
+    public byte[] getMessageClass() {
+        return mPduHeaders.getTextString(PduHeaders.MESSAGE_CLASS);
+    }
+
+    /**
+     * Set X-Mms-Message-Class value.
+     *
+     * @param value the value
+     * @throws NullPointerException if the value is null.
+     */
+    public void setMessageClass(byte[] value) {
+        mPduHeaders.setTextString(value, PduHeaders.MESSAGE_CLASS);
+    }
+
+    /**
+     * Get Message-ID value.
+     *
+     * @return the value
+     */
+    public byte[] getMessageId() {
+        return mPduHeaders.getTextString(PduHeaders.MESSAGE_ID);
+    }
+
+    /**
+     * Set Message-ID value.
+     *
+     * @param value the value
+     * @throws NullPointerException if the value is null.
+     */
+    public void setMessageId(byte[] value) {
+        mPduHeaders.setTextString(value, PduHeaders.MESSAGE_ID);
+    }
+
+    /**
+     * Get X-Mms-Read-Report value.
+     *
+     * @return the value
+     */
+    public int getReadReport() {
+        return mPduHeaders.getOctet(PduHeaders.READ_REPORT);
+    }
+
+    /**
+     * Set X-Mms-Read-Report value.
+     *
+     * @param value the value
+     * @throws InvalidHeaderValueException if the value is invalid.
+     */
+    public void setReadReport(int value) throws InvalidHeaderValueException {
+        mPduHeaders.setOctet(value, PduHeaders.READ_REPORT);
+    }
+
+    /**
+     * Get X-Mms-Retrieve-Status value.
+     *
+     * @return the value
+     */
+    public int getRetrieveStatus() {
+        return mPduHeaders.getOctet(PduHeaders.RETRIEVE_STATUS);
+    }
+
+    /**
+     * Set X-Mms-Retrieve-Status value.
+     *
+     * @param value the value
+     * @throws InvalidHeaderValueException if the value is invalid.
+     */
+    public void setRetrieveStatus(int value) throws InvalidHeaderValueException {
+        mPduHeaders.setOctet(value, PduHeaders.RETRIEVE_STATUS);
+    }
+
+    /**
+     * Get X-Mms-Retrieve-Text value.
+     *
+     * @return the value
+     */
+    public EncodedStringValue getRetrieveText() {
+        return mPduHeaders.getEncodedStringValue(PduHeaders.RETRIEVE_TEXT);
+    }
+
+    /**
+     * Set X-Mms-Retrieve-Text value.
+     *
+     * @param value the value
+     * @throws NullPointerException if the value is null.
+     */
+    public void setRetrieveText(EncodedStringValue value) {
+        mPduHeaders.setEncodedStringValue(value, PduHeaders.RETRIEVE_TEXT);
+    }
+
+    /**
+     * Get X-Mms-Transaction-Id.
+     *
+     * @return the value
+     */
+    public byte[] getTransactionId() {
+        return mPduHeaders.getTextString(PduHeaders.TRANSACTION_ID);
+    }
+
+    /**
+     * Set X-Mms-Transaction-Id.
+     *
+     * @param value the value
+     * @throws NullPointerException if the value is null.
+     */
+    public void setTransactionId(byte[] value) {
+        mPduHeaders.setTextString(value, PduHeaders.TRANSACTION_ID);
+    }
+
+    /*
+     * Optional, not supported header fields:
+     *
+     *     public byte[] getApplicId() {return null;}
+     *     public void setApplicId(byte[] value) {}
+     *
+     *     public byte[] getAuxApplicId() {return null;}
+     *     public void getAuxApplicId(byte[] value) {}
+     *
+     *     public byte getContentClass() {return 0x00;}
+     *     public void setApplicId(byte value) {}
+     *
+     *     public byte getDrmContent() {return 0x00;}
+     *     public void setDrmContent(byte value) {}
+     *
+     *     public byte getDistributionIndicator() {return 0x00;}
+     *     public void setDistributionIndicator(byte value) {}
+     *
+     *     public PreviouslySentByValue getPreviouslySentBy() {return null;}
+     *     public void setPreviouslySentBy(PreviouslySentByValue value) {}
+     *
+     *     public PreviouslySentDateValue getPreviouslySentDate() {}
+     *     public void setPreviouslySentDate(PreviouslySentDateValue value) {}
+     *
+     *     public MmFlagsValue getMmFlags() {return null;}
+     *     public void setMmFlags(MmFlagsValue value) {}
+     *
+     *     public MmStateValue getMmState() {return null;}
+     *     public void getMmState(MmStateValue value) {}
+     *
+     *     public byte[] getReplaceId() {return 0x00;}
+     *     public void setReplaceId(byte[] value) {}
+     *
+     *     public byte[] getReplyApplicId() {return 0x00;}
+     *     public void setReplyApplicId(byte[] value) {}
+     *
+     *     public byte getReplyCharging() {return 0x00;}
+     *     public void setReplyCharging(byte value) {}
+     *
+     *     public byte getReplyChargingDeadline() {return 0x00;}
+     *     public void setReplyChargingDeadline(byte value) {}
+     *
+     *     public byte[] getReplyChargingId() {return 0x00;}
+     *     public void setReplyChargingId(byte[] value) {}
+     *
+     *     public long getReplyChargingSize() {return 0;}
+     *     public void setReplyChargingSize(long value) {}
+     */
+}
diff --git a/core/java/com/google/android/mms/pdu/SendConf.java b/core/java/com/google/android/mms/pdu/SendConf.java
new file mode 100644
index 0000000..0568fe79
--- /dev/null
+++ b/core/java/com/google/android/mms/pdu/SendConf.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2007 Esmertec AG.
+ * Copyright (C) 2007 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.google.android.mms.pdu;
+
+import com.google.android.mms.InvalidHeaderValueException;
+
+public class SendConf extends GenericPdu {
+    /**
+     * Empty constructor.
+     * Since the Pdu corresponding to this class is constructed
+     * by the Proxy-Relay server, this class is only instantiated
+     * by the Pdu Parser.
+     *
+     * @throws InvalidHeaderValueException if error occurs.
+     */
+    public SendConf() throws InvalidHeaderValueException {
+        super();
+        setMessageType(PduHeaders.MESSAGE_TYPE_SEND_CONF);
+    }
+
+    /**
+     * Constructor with given headers.
+     *
+     * @param headers Headers for this PDU.
+     */
+    SendConf(PduHeaders headers) {
+        super(headers);
+    }
+
+    /**
+     * Get Message-ID value.
+     *
+     * @return the value
+     */
+    public byte[] getMessageId() {
+        return mPduHeaders.getTextString(PduHeaders.MESSAGE_ID);
+    }
+
+    /**
+     * Set Message-ID value.
+     *
+     * @param value the value
+     * @throws NullPointerException if the value is null.
+     */
+    public void setMessageId(byte[] value) {
+        mPduHeaders.setTextString(value, PduHeaders.MESSAGE_ID);
+    }
+
+    /**
+     * Get X-Mms-Response-Status.
+     *
+     * @return the value
+     */
+    public int getResponseStatus() {
+        return mPduHeaders.getOctet(PduHeaders.RESPONSE_STATUS);
+    }
+
+    /**
+     * Set X-Mms-Response-Status.
+     *
+     * @param value the values
+     * @throws InvalidHeaderValueException if the value is invalid.
+     */
+    public void setResponseStatus(int value) throws InvalidHeaderValueException {
+        mPduHeaders.setOctet(value, PduHeaders.RESPONSE_STATUS);
+    }
+
+    /**
+     * Get X-Mms-Transaction-Id field value.
+     *
+     * @return the X-Mms-Report-Allowed value
+     */
+    public byte[] getTransactionId() {
+        return mPduHeaders.getTextString(PduHeaders.TRANSACTION_ID);
+    }
+
+    /**
+     * Set X-Mms-Transaction-Id field value.
+     *
+     * @param value the value
+     * @throws NullPointerException if the value is null.
+     */
+    public void setTransactionId(byte[] value) {
+            mPduHeaders.setTextString(value, PduHeaders.TRANSACTION_ID);
+    }
+
+    /*
+     * Optional, not supported header fields:
+     *
+     *    public byte[] getContentLocation() {return null;}
+     *    public void setContentLocation(byte[] value) {}
+     *
+     *    public EncodedStringValue getResponseText() {return null;}
+     *    public void setResponseText(EncodedStringValue value) {}
+     *
+     *    public byte getStoreStatus() {return 0x00;}
+     *    public void setStoreStatus(byte value) {}
+     *
+     *    public byte[] getStoreStatusText() {return null;}
+     *    public void setStoreStatusText(byte[] value) {}
+     */
+}
diff --git a/core/java/com/google/android/mms/pdu/SendReq.java b/core/java/com/google/android/mms/pdu/SendReq.java
new file mode 100644
index 0000000..9081b0c
--- /dev/null
+++ b/core/java/com/google/android/mms/pdu/SendReq.java
@@ -0,0 +1,346 @@
+/*
+ * Copyright (C) 2007-2008 Esmertec AG.
+ * Copyright (C) 2007-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.google.android.mms.pdu;
+
+import android.util.Log;
+
+import com.google.android.mms.InvalidHeaderValueException;
+
+public class SendReq extends MultimediaMessagePdu {
+    private static final String TAG = "SendReq";
+
+    public SendReq() {
+        super();
+
+        try {
+            setMessageType(PduHeaders.MESSAGE_TYPE_SEND_REQ);
+            setMmsVersion(PduHeaders.CURRENT_MMS_VERSION);
+            // FIXME: Content-type must be decided according to whether
+            // SMIL part present.
+            setContentType("application/vnd.wap.multipart.related".getBytes());
+            setFrom(new EncodedStringValue(PduHeaders.FROM_INSERT_ADDRESS_TOKEN_STR.getBytes()));
+            setTransactionId(generateTransactionId());
+        } catch (InvalidHeaderValueException e) {
+            // Impossible to reach here since all headers we set above are valid.
+            Log.e(TAG, "Unexpected InvalidHeaderValueException.", e);
+            throw new RuntimeException(e);
+        }
+    }
+
+    private byte[] generateTransactionId() {
+        String transactionId = "T" + Long.toHexString(System.currentTimeMillis());
+        return transactionId.getBytes();
+    }
+
+    /**
+     * Constructor, used when composing a M-Send.req pdu.
+     *
+     * @param contentType the content type value
+     * @param from the from value
+     * @param mmsVersion current viersion of mms
+     * @param transactionId the transaction-id value
+     * @throws InvalidHeaderValueException if parameters are invalid.
+     *         NullPointerException if contentType, form or transactionId is null.
+     */
+    public SendReq(byte[] contentType,
+                   EncodedStringValue from,
+                   int mmsVersion,
+                   byte[] transactionId) throws InvalidHeaderValueException {
+        super();
+        setMessageType(PduHeaders.MESSAGE_TYPE_SEND_REQ);
+        setContentType(contentType);
+        setFrom(from);
+        setMmsVersion(mmsVersion);
+        setTransactionId(transactionId);
+    }
+
+    /**
+     * Constructor with given headers.
+     *
+     * @param headers Headers for this PDU.
+     */
+    SendReq(PduHeaders headers) {
+        super(headers);
+    }
+
+    /**
+     * Constructor with given headers and body
+     *
+     * @param headers Headers for this PDU.
+     * @param body Body of this PDu.
+     */
+    SendReq(PduHeaders headers, PduBody body) {
+        super(headers, body);
+    }
+
+    /**
+     * Get Bcc value.
+     *
+     * @return the value
+     */
+    public EncodedStringValue[] getBcc() {
+        return mPduHeaders.getEncodedStringValues(PduHeaders.BCC);
+    }
+
+    /**
+     * Add a "BCC" value.
+     *
+     * @param value the value
+     * @throws NullPointerException if the value is null.
+     */
+    public void addBcc(EncodedStringValue value) {
+        mPduHeaders.appendEncodedStringValue(value, PduHeaders.BCC);
+    }
+
+    /**
+     * Set "BCC" value.
+     *
+     * @param value the value
+     * @throws NullPointerException if the value is null.
+     */
+    public void setBcc(EncodedStringValue[] value) {
+        mPduHeaders.setEncodedStringValues(value, PduHeaders.BCC);
+    }
+
+    /**
+     * Get CC value.
+     *
+     * @return the value
+     */
+    public EncodedStringValue[] getCc() {
+        return mPduHeaders.getEncodedStringValues(PduHeaders.CC);
+    }
+
+    /**
+     * Add a "CC" value.
+     *
+     * @param value the value
+     * @throws NullPointerException if the value is null.
+     */
+    public void addCc(EncodedStringValue value) {
+        mPduHeaders.appendEncodedStringValue(value, PduHeaders.CC);
+    }
+
+    /**
+     * Set "CC" value.
+     *
+     * @param value the value
+     * @throws NullPointerException if the value is null.
+     */
+    public void setCc(EncodedStringValue[] value) {
+        mPduHeaders.setEncodedStringValues(value, PduHeaders.CC);
+    }
+
+    /**
+     * Get Content-type value.
+     *
+     * @return the value
+     */
+    public byte[] getContentType() {
+        return mPduHeaders.getTextString(PduHeaders.CONTENT_TYPE);
+    }
+
+    /**
+     * Set Content-type value.
+     *
+     * @param value the value
+     * @throws NullPointerException if the value is null.
+     */
+    public void setContentType(byte[] value) {
+        mPduHeaders.setTextString(value, PduHeaders.CONTENT_TYPE);
+    }
+
+    /**
+     * Get X-Mms-Delivery-Report value.
+     *
+     * @return the value
+     */
+    public int getDeliveryReport() {
+        return mPduHeaders.getOctet(PduHeaders.DELIVERY_REPORT);
+    }
+
+    /**
+     * Set X-Mms-Delivery-Report value.
+     *
+     * @param value the value
+     * @throws InvalidHeaderValueException if the value is invalid.
+     */
+    public void setDeliveryReport(int value) throws InvalidHeaderValueException {
+        mPduHeaders.setOctet(value, PduHeaders.DELIVERY_REPORT);
+    }
+
+    /**
+     * Get X-Mms-Expiry value.
+     *
+     * Expiry-value = Value-length
+     *      (Absolute-token Date-value | Relative-token Delta-seconds-value)
+     *
+     * @return the value
+     */
+    public long getExpiry() {
+        return mPduHeaders.getLongInteger(PduHeaders.EXPIRY);
+    }
+
+    /**
+     * Set X-Mms-Expiry value.
+     *
+     * @param value the value
+     */
+    public void setExpiry(long value) {
+        mPduHeaders.setLongInteger(value, PduHeaders.EXPIRY);
+    }
+
+    /**
+     * Get From value.
+     * From-value = Value-length
+     *      (Address-present-token Encoded-string-value | Insert-address-token)
+     *
+     * @return the value
+     */
+    public EncodedStringValue getFrom() {
+       return mPduHeaders.getEncodedStringValue(PduHeaders.FROM);
+    }
+
+    /**
+     * Set From value.
+     *
+     * @param value the value
+     * @throws NullPointerException if the value is null.
+     */
+    public void setFrom(EncodedStringValue value) {
+        mPduHeaders.setEncodedStringValue(value, PduHeaders.FROM);
+    }
+
+    /**
+     * Get X-Mms-Message-Class value.
+     * Message-class-value = Class-identifier | Token-text
+     * Class-identifier = Personal | Advertisement | Informational | Auto
+     *
+     * @return the value
+     */
+    public byte[] getMessageClass() {
+        return mPduHeaders.getTextString(PduHeaders.MESSAGE_CLASS);
+    }
+
+    /**
+     * Set X-Mms-Message-Class value.
+     *
+     * @param value the value
+     * @throws NullPointerException if the value is null.
+     */
+    public void setMessageClass(byte[] value) {
+        mPduHeaders.setTextString(value, PduHeaders.MESSAGE_CLASS);
+    }
+
+    /**
+     * Get X-Mms-Read-Report value.
+     *
+     * @return the value
+     */
+    public int getReadReport() {
+        return mPduHeaders.getOctet(PduHeaders.READ_REPORT);
+    }
+
+    /**
+     * Set X-Mms-Read-Report value.
+     *
+     * @param value the value
+     * @throws InvalidHeaderValueException if the value is invalid.
+     */
+    public void setReadReport(int value) throws InvalidHeaderValueException {
+        mPduHeaders.setOctet(value, PduHeaders.READ_REPORT);
+    }
+
+    /**
+     * Set "To" value.
+     *
+     * @param value the value
+     * @throws NullPointerException if the value is null.
+     */
+    public void setTo(EncodedStringValue[] value) {
+        mPduHeaders.setEncodedStringValues(value, PduHeaders.TO);
+    }
+
+    /**
+     * Get X-Mms-Transaction-Id field value.
+     *
+     * @return the X-Mms-Report-Allowed value
+     */
+    public byte[] getTransactionId() {
+        return mPduHeaders.getTextString(PduHeaders.TRANSACTION_ID);
+    }
+
+    /**
+     * Set X-Mms-Transaction-Id field value.
+     *
+     * @param value the value
+     * @throws NullPointerException if the value is null.
+     */
+    public void setTransactionId(byte[] value) {
+        mPduHeaders.setTextString(value, PduHeaders.TRANSACTION_ID);
+    }
+
+    /*
+     * Optional, not supported header fields:
+     *
+     *     public byte getAdaptationAllowed() {return 0};
+     *     public void setAdaptationAllowed(btye value) {};
+     *
+     *     public byte[] getApplicId() {return null;}
+     *     public void setApplicId(byte[] value) {}
+     *
+     *     public byte[] getAuxApplicId() {return null;}
+     *     public void getAuxApplicId(byte[] value) {}
+     *
+     *     public byte getContentClass() {return 0x00;}
+     *     public void setApplicId(byte value) {}
+     *
+     *     public long getDeliveryTime() {return 0};
+     *     public void setDeliveryTime(long value) {};
+     *
+     *     public byte getDrmContent() {return 0x00;}
+     *     public void setDrmContent(byte value) {}
+     *
+     *     public MmFlagsValue getMmFlags() {return null;}
+     *     public void setMmFlags(MmFlagsValue value) {}
+     *
+     *     public MmStateValue getMmState() {return null;}
+     *     public void getMmState(MmStateValue value) {}
+     *
+     *     public byte[] getReplyApplicId() {return 0x00;}
+     *     public void setReplyApplicId(byte[] value) {}
+     *
+     *     public byte getReplyCharging() {return 0x00;}
+     *     public void setReplyCharging(byte value) {}
+     *
+     *     public byte getReplyChargingDeadline() {return 0x00;}
+     *     public void setReplyChargingDeadline(byte value) {}
+     *
+     *     public byte[] getReplyChargingId() {return 0x00;}
+     *     public void setReplyChargingId(byte[] value) {}
+     *
+     *     public long getReplyChargingSize() {return 0;}
+     *     public void setReplyChargingSize(long value) {}
+     *
+     *     public byte[] getReplyApplicId() {return 0x00;}
+     *     public void setReplyApplicId(byte[] value) {}
+     *
+     *     public byte getStore() {return 0x00;}
+     *     public void setStore(byte value) {}
+     */
+}
diff --git a/core/java/com/google/android/mms/pdu/package.html b/core/java/com/google/android/mms/pdu/package.html
new file mode 100755
index 0000000..c9f96a6
--- /dev/null
+++ b/core/java/com/google/android/mms/pdu/package.html
@@ -0,0 +1,5 @@
+<body>
+
+{@hide}
+
+</body>
diff --git a/core/java/com/google/android/mms/util/AbstractCache.java b/core/java/com/google/android/mms/util/AbstractCache.java
new file mode 100644
index 0000000..670439c
--- /dev/null
+++ b/core/java/com/google/android/mms/util/AbstractCache.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2008 Esmertec AG.
+ * 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.google.android.mms.util;
+
+import android.util.Config;
+import android.util.Log;
+
+import java.util.HashMap;
+
+public abstract class AbstractCache<K, V> {
+    private static final String TAG = "AbstractCache";
+    private static final boolean DEBUG = false;
+    private static final boolean LOCAL_LOGV = DEBUG ? Config.LOGD : Config.LOGV;
+
+    private static final int MAX_CACHED_ITEMS  = 500;
+
+    private final HashMap<K, CacheEntry<V>> mCacheMap;
+
+    protected AbstractCache() {
+        mCacheMap = new HashMap<K, CacheEntry<V>>();
+    }
+
+    public boolean put(K key, V value) {
+        if (LOCAL_LOGV) {
+            Log.v(TAG, "Trying to put " + key + " into cache.");
+        }
+
+        if (mCacheMap.size() >= MAX_CACHED_ITEMS) {
+            // TODO Should remove the oldest or least hit cached entry
+            // and then cache the new one.
+            if (LOCAL_LOGV) {
+                Log.v(TAG, "Failed! size limitation reached.");
+            }
+            return false;
+        }
+
+        if (key != null) {
+            CacheEntry<V> cacheEntry = new CacheEntry<V>();
+            cacheEntry.value = value;
+            mCacheMap.put(key, cacheEntry);
+
+            if (LOCAL_LOGV) {
+                Log.v(TAG, key + " cached, " + mCacheMap.size() + " items total.");
+            }
+            return true;
+        }
+        return false;
+    }
+
+    public V get(K key) {
+        if (LOCAL_LOGV) {
+            Log.v(TAG, "Trying to get " + key + " from cache.");
+        }
+
+        if (key != null) {
+            CacheEntry<V> cacheEntry = mCacheMap.get(key);
+            if (cacheEntry != null) {
+                cacheEntry.hit++;
+                if (LOCAL_LOGV) {
+                    Log.v(TAG, key + " hit " + cacheEntry.hit + " times.");
+                }
+                return cacheEntry.value;
+            }
+        }
+        return null;
+    }
+
+    public V purge(K key) {
+        if (LOCAL_LOGV) {
+            Log.v(TAG, "Trying to purge " + key);
+        }
+
+        CacheEntry<V> v = mCacheMap.remove(key);
+
+        if (LOCAL_LOGV) {
+            Log.v(TAG, mCacheMap.size() + " items cached.");
+        }
+
+        return v != null ? v.value : null;
+    }
+
+    public void purgeAll() {
+        if (LOCAL_LOGV) {
+            Log.v(TAG, "Purging cache, " + mCacheMap.size()
+                    + " items dropped.");
+        }
+        mCacheMap.clear();
+    }
+
+    public int size() {
+        return mCacheMap.size();
+    }
+
+    private static class CacheEntry<V> {
+        int hit;
+        V value;
+    }
+}
diff --git a/core/java/com/google/android/mms/util/PduCache.java b/core/java/com/google/android/mms/util/PduCache.java
new file mode 100644
index 0000000..7c3fad7
--- /dev/null
+++ b/core/java/com/google/android/mms/util/PduCache.java
@@ -0,0 +1,243 @@
+/*
+ * Copyright (C) 2008 Esmertec AG.
+ * 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.google.android.mms.util;
+
+import android.content.ContentUris;
+import android.content.UriMatcher;
+import android.net.Uri;
+import android.provider.Telephony.Mms;
+import android.util.Config;
+import android.util.Log;
+
+import java.util.HashMap;
+import java.util.HashSet;
+
+public final class PduCache extends AbstractCache<Uri, PduCacheEntry> {
+    private static final String TAG = "PduCache";
+    private static final boolean DEBUG = false;
+    private static final boolean LOCAL_LOGV = DEBUG ? Config.LOGD : Config.LOGV;
+
+    private static final int MMS_ALL             = 0;
+    private static final int MMS_ALL_ID          = 1;
+    private static final int MMS_INBOX           = 2;
+    private static final int MMS_INBOX_ID        = 3;
+    private static final int MMS_SENT            = 4;
+    private static final int MMS_SENT_ID         = 5;
+    private static final int MMS_DRAFTS          = 6;
+    private static final int MMS_DRAFTS_ID       = 7;
+    private static final int MMS_OUTBOX          = 8;
+    private static final int MMS_OUTBOX_ID       = 9;
+    private static final int MMS_CONVERSATION    = 10;
+    private static final int MMS_CONVERSATION_ID = 11;
+
+    private static final UriMatcher URI_MATCHER;
+    private static final HashMap<Integer, Integer> MATCH_TO_MSGBOX_ID_MAP;
+
+    private static PduCache sInstance;
+
+    static {
+        URI_MATCHER = new UriMatcher(UriMatcher.NO_MATCH);
+        URI_MATCHER.addURI("mms", null,         MMS_ALL);
+        URI_MATCHER.addURI("mms", "#",          MMS_ALL_ID);
+        URI_MATCHER.addURI("mms", "inbox",      MMS_INBOX);
+        URI_MATCHER.addURI("mms", "inbox/#",    MMS_INBOX_ID);
+        URI_MATCHER.addURI("mms", "sent",       MMS_SENT);
+        URI_MATCHER.addURI("mms", "sent/#",     MMS_SENT_ID);
+        URI_MATCHER.addURI("mms", "drafts",     MMS_DRAFTS);
+        URI_MATCHER.addURI("mms", "drafts/#",   MMS_DRAFTS_ID);
+        URI_MATCHER.addURI("mms", "outbox",     MMS_OUTBOX);
+        URI_MATCHER.addURI("mms", "outbox/#",   MMS_OUTBOX_ID);
+        URI_MATCHER.addURI("mms-sms", "conversations",   MMS_CONVERSATION);
+        URI_MATCHER.addURI("mms-sms", "conversations/#", MMS_CONVERSATION_ID);
+
+        MATCH_TO_MSGBOX_ID_MAP = new HashMap<Integer, Integer>();
+        MATCH_TO_MSGBOX_ID_MAP.put(MMS_INBOX,  Mms.MESSAGE_BOX_INBOX);
+        MATCH_TO_MSGBOX_ID_MAP.put(MMS_SENT,   Mms.MESSAGE_BOX_SENT);
+        MATCH_TO_MSGBOX_ID_MAP.put(MMS_DRAFTS, Mms.MESSAGE_BOX_DRAFTS);
+        MATCH_TO_MSGBOX_ID_MAP.put(MMS_OUTBOX, Mms.MESSAGE_BOX_OUTBOX);
+    }
+
+    private final HashMap<Integer, HashSet<Uri>> mMessageBoxes;
+    private final HashMap<Long, HashSet<Uri>> mThreads;
+
+    private PduCache() {
+        mMessageBoxes = new HashMap<Integer, HashSet<Uri>>();
+        mThreads = new HashMap<Long, HashSet<Uri>>();
+    }
+
+    synchronized public static final PduCache getInstance() {
+        if (sInstance == null) {
+            if (LOCAL_LOGV) {
+                Log.v(TAG, "Constructing new PduCache instance.");
+            }
+            sInstance = new PduCache();
+        }
+        return sInstance;
+    }
+
+    @Override
+    synchronized public boolean put(Uri uri, PduCacheEntry entry) {
+        int msgBoxId = entry.getMessageBox();
+        HashSet<Uri> msgBox = mMessageBoxes.get(msgBoxId);
+        if (msgBox == null) {
+            msgBox = new HashSet<Uri>();
+            mMessageBoxes.put(msgBoxId, msgBox);
+        }
+
+        long threadId = entry.getThreadId();
+        HashSet<Uri> thread = mThreads.get(threadId);
+        if (thread == null) {
+            thread = new HashSet<Uri>();
+            mThreads.put(threadId, thread);
+        }
+
+        Uri finalKey = normalizeKey(uri);
+        boolean result = super.put(finalKey, entry);
+        if (result) {
+            msgBox.add(finalKey);
+            thread.add(finalKey);
+        }
+        return result;
+    }
+
+    @Override
+    synchronized public PduCacheEntry purge(Uri uri) {
+        int match = URI_MATCHER.match(uri);
+        switch (match) {
+            case MMS_ALL_ID:
+                return purgeSingleEntry(uri);
+            case MMS_INBOX_ID:
+            case MMS_SENT_ID:
+            case MMS_DRAFTS_ID:
+            case MMS_OUTBOX_ID:
+                String msgId = uri.getLastPathSegment();
+                return purgeSingleEntry(Uri.withAppendedPath(Mms.CONTENT_URI, msgId));
+            // Implicit batch of purge, return null.
+            case MMS_ALL:
+            case MMS_CONVERSATION:
+                purgeAll();
+                return null;
+            case MMS_INBOX:
+            case MMS_SENT:
+            case MMS_DRAFTS:
+            case MMS_OUTBOX:
+                purgeByMessageBox(MATCH_TO_MSGBOX_ID_MAP.get(match));
+                return null;
+            case MMS_CONVERSATION_ID:
+                purgeByThreadId(ContentUris.parseId(uri));
+                return null;
+            default:
+                return null;
+        }
+    }
+
+    private PduCacheEntry purgeSingleEntry(Uri key) {
+        PduCacheEntry entry = super.purge(key);
+        if (entry != null) {
+            removeFromThreads(key, entry);
+            removeFromMessageBoxes(key, entry);
+            return entry;
+        }
+        return null;
+    }
+
+    @Override
+    synchronized public void purgeAll() {
+        super.purgeAll();
+
+        mMessageBoxes.clear();
+        mThreads.clear();
+    }
+
+    /**
+     * @param uri The Uri to be normalized.
+     * @return Uri The normalized key of cached entry.
+     */
+    private Uri normalizeKey(Uri uri) {
+        int match = URI_MATCHER.match(uri);
+        Uri normalizedKey = null;
+
+        switch (match) {
+            case MMS_ALL_ID:
+                normalizedKey = uri;
+                break;
+            case MMS_INBOX_ID:
+            case MMS_SENT_ID:
+            case MMS_DRAFTS_ID:
+            case MMS_OUTBOX_ID:
+                String msgId = uri.getLastPathSegment();
+                normalizedKey = Uri.withAppendedPath(Mms.CONTENT_URI, msgId);
+                break;
+            default:
+                return null;
+        }
+
+        if (LOCAL_LOGV) {
+            Log.v(TAG, uri + " -> " + normalizedKey);
+        }
+        return normalizedKey;
+    }
+
+    private void purgeByMessageBox(Integer msgBoxId) {
+        if (LOCAL_LOGV) {
+            Log.v(TAG, "Purge cache in message box: " + msgBoxId);
+        }
+
+        if (msgBoxId != null) {
+            HashSet<Uri> msgBox = mMessageBoxes.remove(msgBoxId);
+            if (msgBox != null) {
+                for (Uri key : msgBox) {
+                    PduCacheEntry entry = super.purge(key);
+                    if (entry != null) {
+                        removeFromThreads(key, entry);
+                    }
+                }
+            }
+        }
+    }
+
+    private void removeFromThreads(Uri key, PduCacheEntry entry) {
+        HashSet<Uri> thread = mThreads.get(entry.getThreadId());
+        if (thread != null) {
+            thread.remove(key);
+        }
+    }
+
+    private void purgeByThreadId(long threadId) {
+        if (LOCAL_LOGV) {
+            Log.v(TAG, "Purge cache in thread: " + threadId);
+        }
+
+        HashSet<Uri> thread = mThreads.remove(threadId);
+        if (thread != null) {
+            for (Uri key : thread) {
+                PduCacheEntry entry = super.purge(key);
+                if (entry != null) {
+                    removeFromMessageBoxes(key, entry);
+                }
+            }
+        }
+    }
+
+    private void removeFromMessageBoxes(Uri key, PduCacheEntry entry) {
+        HashSet<Uri> msgBox = mThreads.get(entry.getMessageBox());
+        if (msgBox != null) {
+            msgBox.remove(key);
+        }
+    }
+}
diff --git a/core/java/com/google/android/mms/util/PduCacheEntry.java b/core/java/com/google/android/mms/util/PduCacheEntry.java
new file mode 100644
index 0000000..8b41386
--- /dev/null
+++ b/core/java/com/google/android/mms/util/PduCacheEntry.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2008 Esmertec AG.
+ * 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.google.android.mms.util;
+
+import com.google.android.mms.pdu.GenericPdu;
+
+public final class PduCacheEntry {
+    private final GenericPdu mPdu;
+    private final int mMessageBox;
+    private final long mThreadId;
+
+    public PduCacheEntry(GenericPdu pdu, int msgBox, long threadId) {
+        mPdu = pdu;
+        mMessageBox = msgBox;
+        mThreadId = threadId;
+    }
+
+    public GenericPdu getPdu() {
+        return mPdu;
+    }
+
+    public int getMessageBox() {
+        return mMessageBox;
+    }
+
+    public long getThreadId() {
+        return mThreadId;
+    }
+}
diff --git a/core/java/com/google/android/mms/util/SqliteWrapper.java b/core/java/com/google/android/mms/util/SqliteWrapper.java
new file mode 100644
index 0000000..bcdac22
--- /dev/null
+++ b/core/java/com/google/android/mms/util/SqliteWrapper.java
@@ -0,0 +1,120 @@
+/*
+ * Copyright (C) 2008 Esmertec AG.
+ * 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.google.android.mms.util;
+
+import android.app.ActivityManager;
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteException;
+import android.net.Uri;
+import android.util.Log;
+import android.widget.Toast;
+
+public final class SqliteWrapper {
+    private static final String TAG = "SqliteWrapper";
+    private static final String SQLITE_EXCEPTION_DETAIL_MESSAGE
+                = "unable to open database file";
+
+    private SqliteWrapper() {
+        // Forbidden being instantiated.
+    }
+
+    // FIXME: It looks like outInfo.lowMemory does not work well as we expected.
+    // after run command: adb shell fillup -p 100, outInfo.lowMemory is still false.
+    private static boolean isLowMemory(Context context) {
+        if (null == context) {
+            return false;
+        }
+
+        ActivityManager am = (ActivityManager)
+                        context.getSystemService(Context.ACTIVITY_SERVICE);
+        ActivityManager.MemoryInfo outInfo = new ActivityManager.MemoryInfo();
+        am.getMemoryInfo(outInfo);
+
+        return outInfo.lowMemory;
+    }
+
+    // FIXME: need to optimize this method.
+    private static boolean isLowMemory(SQLiteException e) {
+        return e.getMessage().equals(SQLITE_EXCEPTION_DETAIL_MESSAGE);
+    }
+
+    public static void checkSQLiteException(Context context, SQLiteException e) {
+        if (isLowMemory(e)) {
+            Toast.makeText(context, com.android.internal.R.string.low_memory,
+                    Toast.LENGTH_SHORT).show();
+        } else {
+            throw e;
+        }
+    }
+
+    public static Cursor query(Context context, ContentResolver resolver, Uri uri,
+            String[] projection, String selection, String[] selectionArgs, String sortOrder) {
+        try {
+            return resolver.query(uri, projection, selection, selectionArgs, sortOrder);
+        } catch (SQLiteException e) {
+            Log.e(TAG, "Catch a SQLiteException when query: ", e);
+            checkSQLiteException(context, e);
+            return null;
+        }
+    }
+
+    public static boolean requery(Context context, Cursor cursor) {
+        try {
+            return cursor.requery();
+        } catch (SQLiteException e) {
+            Log.e(TAG, "Catch a SQLiteException when requery: ", e);
+            checkSQLiteException(context, e);
+            return false;
+        }
+    }
+    public static int update(Context context, ContentResolver resolver, Uri uri,
+            ContentValues values, String where, String[] selectionArgs) {
+        try {
+            return resolver.update(uri, values, where, selectionArgs);
+        } catch (SQLiteException e) {
+            Log.e(TAG, "Catch a SQLiteException when update: ", e);
+            checkSQLiteException(context, e);
+            return -1;
+        }
+    }
+
+    public static int delete(Context context, ContentResolver resolver, Uri uri,
+            String where, String[] selectionArgs) {
+        try {
+            return resolver.delete(uri, where, selectionArgs);
+        } catch (SQLiteException e) {
+            Log.e(TAG, "Catch a SQLiteException when delete: ", e);
+            checkSQLiteException(context, e);
+            return -1;
+        }
+    }
+
+    public static Uri insert(Context context, ContentResolver resolver,
+            Uri uri, ContentValues values) {
+        try {
+            return resolver.insert(uri, values);
+        } catch (SQLiteException e) {
+            Log.e(TAG, "Catch a SQLiteException when insert: ", e);
+            checkSQLiteException(context, e);
+            return null;
+        }
+    }
+}
diff --git a/core/java/com/google/android/mms/util/package.html b/core/java/com/google/android/mms/util/package.html
new file mode 100755
index 0000000..c9f96a6
--- /dev/null
+++ b/core/java/com/google/android/mms/util/package.html
@@ -0,0 +1,5 @@
+<body>
+
+{@hide}
+
+</body>
diff --git a/core/java/com/google/android/net/GoogleHttpClient.java b/core/java/com/google/android/net/GoogleHttpClient.java
new file mode 100644
index 0000000..ac9ad73
--- /dev/null
+++ b/core/java/com/google/android/net/GoogleHttpClient.java
@@ -0,0 +1,335 @@
+/*
+ * 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.google.android.net;
+
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.content.Context;
+import android.net.http.AndroidHttpClient;
+import android.os.Build;
+import android.os.NetStat;
+import android.os.SystemClock;
+import android.provider.Checkin;
+import android.util.Config;
+import android.util.Log;
+import org.apache.http.HttpEntity;
+import org.apache.http.HttpEntityEnclosingRequest;
+import org.apache.http.HttpHost;
+import org.apache.http.HttpRequest;
+import org.apache.http.HttpResponse;
+import org.apache.http.ProtocolException;
+import org.apache.http.client.ClientProtocolException;
+import org.apache.http.client.HttpClient;
+import org.apache.http.client.ResponseHandler;
+import org.apache.http.client.methods.HttpUriRequest;
+import org.apache.http.conn.ClientConnectionManager;
+import org.apache.http.impl.client.EntityEnclosingRequestWrapper;
+import org.apache.http.impl.client.RequestWrapper;
+import org.apache.http.params.HttpParams;
+import org.apache.http.protocol.HttpContext;
+import org.apache.harmony.xnet.provider.jsse.SSLClientSessionCache;
+
+import java.io.IOException;
+import java.net.URI;
+import java.net.URISyntaxException;
+
+/**
+ * {@link AndroidHttpClient} wrapper that uses {@link UrlRules} to rewrite URLs
+ * and otherwise tweak HTTP requests.
+ */
+public class GoogleHttpClient implements HttpClient {
+
+    private static final String TAG = "GoogleHttpClient";
+
+    /** Exception thrown when a request is blocked by the URL rules. */
+    public static class BlockedRequestException extends IOException {
+        private final UrlRules.Rule mRule;
+        BlockedRequestException(UrlRules.Rule rule) {
+            super("Blocked by rule: " + rule.mName);
+            mRule = rule;
+        }
+    }
+
+    private final AndroidHttpClient mClient;
+    private final ContentResolver mResolver;
+    private final String mUserAgent;
+
+    /**
+     * Create an HTTP client.  Normally one client is shared throughout an app.
+     * @param resolver to use for accessing URL rewriting rules.
+     * @param userAgent to report in your HTTP requests.
+     * @deprecated Use {@link #GoogleHttpClient(android.content.ContentResolver, String, boolean)}
+     */
+    public GoogleHttpClient(ContentResolver resolver, String userAgent) {
+        mClient = AndroidHttpClient.newInstance(userAgent);
+        mResolver = resolver;
+        mUserAgent = userAgent;
+    }
+
+    /**
+     * GoogleHttpClient(Context, String, boolean) - without SSL session
+     * persistence.
+     *
+     * @deprecated use Context instead of ContentResolver.
+     */
+    public GoogleHttpClient(ContentResolver resolver, String appAndVersion,
+            boolean gzipCapable) {
+        this(resolver, null /* cache */, appAndVersion, gzipCapable);
+    }
+
+    /**
+     * Create an HTTP client.  Normaly this client is shared throughout an app.
+     * The HTTP client will construct its User-Agent as follows:
+     *
+     * <appAndVersion> (<build device> <build id>)
+     * or
+     * <appAndVersion> (<build device> <build id>); gzip
+     * (if gzip capable)
+     *
+     * The context has settings for URL rewriting rules and is used to enable
+     *  SSL session persistence.
+     *
+     * @param context application context.
+     * @param appAndVersion Base app and version to use in the User-Agent.
+     * e.g., "MyApp/1.0"
+     * @param gzipCapable Whether or not this client is able to consume gzip'd
+     * responses.  Only used to modify the User-Agent, not other request
+     * headers.  Needed because Google servers require gzip in the User-Agent
+     * in order to return gzip'd content.
+     */
+    public GoogleHttpClient(Context context, String appAndVersion,
+        boolean gzipCapable) {
+        this(context.getContentResolver(), SSLClientSessionCacheFactory.getCache(context),
+                appAndVersion, gzipCapable);
+    }
+
+    private GoogleHttpClient(ContentResolver resolver, SSLClientSessionCache cache,
+            String appAndVersion, boolean gzipCapable) {
+        String userAgent = appAndVersion + " (" + Build.DEVICE + " " + Build.ID + ")";
+        if (gzipCapable) {
+            userAgent = userAgent + "; gzip";
+        }
+        mClient = AndroidHttpClient.newInstance(userAgent, cache);
+        mResolver = resolver;
+        mUserAgent = userAgent;
+    }
+
+    /**
+     * Release resources associated with this client.  You must call this,
+     * or significant resources (sockets and memory) may be leaked.
+     */
+    public void close() {
+        mClient.close();
+    }
+
+    /** Execute a request without applying and rewrite rules. */
+    public HttpResponse executeWithoutRewriting(
+            HttpUriRequest request, HttpContext context)
+            throws IOException {
+        String code = "Error";
+        long start = SystemClock.elapsedRealtime();
+        try {
+            HttpResponse response;
+            // TODO: if we're logging network stats, and if the apache library is configured
+            // to follow redirects, count each redirect as an additional round trip.
+
+            // see if we're logging network stats.
+            boolean logNetworkStats = NetworkStatsEntity.shouldLogNetworkStats();
+
+            if (logNetworkStats) {
+                int uid = android.os.Process.myUid();
+                long startTx = NetStat.getUidTxBytes(uid);
+                long startRx = NetStat.getUidRxBytes(uid);
+
+                response = mClient.execute(request, context);
+                code = Integer.toString(response.getStatusLine().getStatusCode());
+
+                HttpEntity origEntity = response == null ? null : response.getEntity();
+                if (origEntity != null) {
+                    // yeah, we compute the same thing below.  we do need to compute this here
+                    // so we can wrap the HttpEntity in the response.
+                    long now = SystemClock.elapsedRealtime();
+                    long elapsed = now - start;
+                    NetworkStatsEntity entity = new NetworkStatsEntity(origEntity,
+                            mUserAgent, uid, startTx, startRx,
+                            elapsed /* response latency */, now /* processing start time */);
+                    response.setEntity(entity);
+                }
+            } else {
+                response = mClient.execute(request, context);
+                code = Integer.toString(response.getStatusLine().getStatusCode());
+            }
+
+            return response;
+        } catch (IOException e) {
+            code = "IOException";
+            throw e;
+        } finally {
+            // Record some statistics to the checkin service about the outcome.
+            // Note that this is only describing execute(), not body download.
+            try {
+                long elapsed = SystemClock.elapsedRealtime() - start;
+                ContentValues values = new ContentValues();
+                values.put(Checkin.Stats.TAG,
+                         Checkin.Stats.Tag.HTTP_STATUS + ":" +
+                         mUserAgent + ":" + code);
+                values.put(Checkin.Stats.COUNT, 1);
+                values.put(Checkin.Stats.SUM, elapsed / 1000.0);
+                mResolver.insert(Checkin.Stats.CONTENT_URI, values);
+            } catch (Exception e) {
+                Log.e(TAG, "Error recording stats", e);
+            }
+        }
+    }
+
+    public HttpResponse execute(HttpUriRequest request, HttpContext context)
+            throws IOException {
+        // Rewrite the supplied URL...
+        URI uri = request.getURI();
+        String original = uri.toString();
+        UrlRules rules = UrlRules.getRules(mResolver);
+        UrlRules.Rule rule = rules.matchRule(original);
+        String rewritten = rule.apply(original);
+
+        if (rewritten == null) {
+            Log.w(TAG, "Blocked by " + rule.mName + ": " + original);
+            throw new BlockedRequestException(rule);
+        } else if (rewritten == original) {
+            return executeWithoutRewriting(request, context);  // Pass through
+        }
+
+        try {
+            uri = new URI(rewritten);
+        } catch (URISyntaxException e) {
+            throw new RuntimeException("Bad URL from rule: " + rule.mName, e);
+        }
+
+        // Wrap request so we can replace the URI.
+        RequestWrapper wrapper = wrapRequest(request);
+        wrapper.setURI(uri);
+        request = wrapper;
+
+        if (Config.LOGV) {
+            Log.v(TAG, "Rule " + rule.mName + ": " + original + " -> " + rewritten);
+        }
+        return executeWithoutRewriting(request, context);
+    }
+
+    /**
+     * Wraps the request making it mutable.
+     */
+    private static RequestWrapper wrapRequest(HttpUriRequest request)
+            throws IOException {
+        try {
+            // We have to wrap it with the right type. Some code performs
+            // instanceof checks.
+            RequestWrapper wrapped;
+            if (request instanceof HttpEntityEnclosingRequest) {
+                wrapped = new EntityEnclosingRequestWrapper(
+                        (HttpEntityEnclosingRequest) request);
+            } else {
+                wrapped = new RequestWrapper(request);
+            }
+
+            // Copy the headers from the original request into the wrapper.
+            wrapped.resetHeaders();
+
+            return wrapped;
+        } catch (ProtocolException e) {
+            throw new ClientProtocolException(e);
+        }
+    }
+
+    /**
+     * Mark a user agent as one Google will trust to handle gzipped content.
+     * {@link AndroidHttpClient#modifyRequestToAcceptGzipResponse} is (also)
+     * necessary but not sufficient -- many browsers claim to accept gzip but
+     * have broken handling, so Google checks the user agent as well.
+     *
+     * @param originalUserAgent to modify (however you identify yourself)
+     * @return user agent with a "yes, I really can handle gzip" token added.
+     * @deprecated Use {@link #GoogleHttpClient(android.content.ContentResolver, String, boolean)} 
+     */
+    public static String getGzipCapableUserAgent(String originalUserAgent) {
+        return originalUserAgent + "; gzip";
+    }
+
+    // HttpClient wrapper methods.
+
+    public HttpParams getParams() {
+        return mClient.getParams();
+    }
+
+    public ClientConnectionManager getConnectionManager() {
+        return mClient.getConnectionManager();
+    }
+
+    public HttpResponse execute(HttpUriRequest request) throws IOException {
+        return execute(request, (HttpContext) null);
+    }
+
+    public HttpResponse execute(HttpHost target, HttpRequest request)
+            throws IOException {
+        return mClient.execute(target, request);
+    }
+
+    public HttpResponse execute(HttpHost target, HttpRequest request,
+            HttpContext context) throws IOException {
+        return mClient.execute(target, request, context);
+    }
+
+    public <T> T execute(HttpUriRequest request,
+            ResponseHandler<? extends T> responseHandler)
+            throws IOException, ClientProtocolException {
+        return mClient.execute(request, responseHandler);
+    }
+
+    public <T> T execute(HttpUriRequest request,
+            ResponseHandler<? extends T> responseHandler, HttpContext context)
+            throws IOException, ClientProtocolException {
+        return mClient.execute(request, responseHandler, context);
+    }
+
+    public <T> T execute(HttpHost target, HttpRequest request,
+            ResponseHandler<? extends T> responseHandler) throws IOException,
+            ClientProtocolException {
+        return mClient.execute(target, request, responseHandler);
+    }
+
+    public <T> T execute(HttpHost target, HttpRequest request,
+            ResponseHandler<? extends T> responseHandler, HttpContext context)
+            throws IOException, ClientProtocolException {
+        return mClient.execute(target, request, responseHandler, context);
+    }
+
+    /**
+     * Enables cURL request logging for this client.
+     *
+     * @param name to log messages with
+     * @param level at which to log messages (see {@link android.util.Log})
+     */
+    public void enableCurlLogging(String name, int level) {
+        mClient.enableCurlLogging(name, level);
+    }
+
+    /**
+     * Disables cURL logging for this client.
+     */
+    public void disableCurlLogging() {
+        mClient.disableCurlLogging();
+    }
+}
diff --git a/core/java/com/google/android/net/NetworkStatsEntity.java b/core/java/com/google/android/net/NetworkStatsEntity.java
new file mode 100644
index 0000000..f5d2349
--- /dev/null
+++ b/core/java/com/google/android/net/NetworkStatsEntity.java
@@ -0,0 +1,85 @@
+/*
+ * 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.google.android.net;
+
+import android.os.NetStat;
+import android.os.SystemClock;
+import android.os.SystemProperties;
+import android.util.EventLog;
+
+import org.apache.http.HttpEntity;
+import org.apache.http.entity.HttpEntityWrapper;
+
+import java.io.FilterInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+
+public class NetworkStatsEntity extends HttpEntityWrapper {
+
+    private static final int HTTP_STATS_EVENT = 52001;
+
+    private class NetworkStatsInputStream extends FilterInputStream {
+
+        public NetworkStatsInputStream(InputStream wrapped) {
+            super(wrapped);
+        }
+
+        @Override
+        public void close() throws IOException {
+            try {
+                super.close();
+            } finally {
+                long processingTime = SystemClock.elapsedRealtime() - mProcessingStartTime;
+                long tx = NetStat.getUidTxBytes(mUid);
+                long rx = NetStat.getUidRxBytes(mUid);
+
+                EventLog.writeEvent(HTTP_STATS_EVENT, mUa, mResponseLatency, processingTime,
+                        tx - mStartTx, rx - mStartRx);
+            }
+        }
+    }
+
+    private final String mUa;
+    private final int mUid;
+    private final long mStartTx;
+    private final long mStartRx;
+    private final long mResponseLatency;
+    private final long mProcessingStartTime;
+
+    public NetworkStatsEntity(HttpEntity orig, String ua,
+            int uid, long startTx, long startRx, long responseLatency,
+            long processingStartTime) {
+        super(orig);
+        this.mUa = ua;
+        this.mUid = uid;
+        this.mStartTx = startTx;
+        this.mStartRx = startRx;
+        this.mResponseLatency = responseLatency;
+        this.mProcessingStartTime = processingStartTime;
+    }
+
+    public static boolean shouldLogNetworkStats() {
+      return "1".equals(SystemProperties.get("googlehttpclient.logstats"));
+    }
+
+    @Override
+    public InputStream getContent() throws IOException {
+        InputStream orig = super.getContent();
+        return new NetworkStatsInputStream(orig);
+    }
+}
diff --git a/core/java/com/google/android/net/ParentalControl.java b/core/java/com/google/android/net/ParentalControl.java
new file mode 100644
index 0000000..71a3958
--- /dev/null
+++ b/core/java/com/google/android/net/ParentalControl.java
@@ -0,0 +1,73 @@
+/*
+ * 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.google.android.net;
+
+import android.os.ICheckinService;
+import android.os.IParentalControlCallback;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.util.Log;
+
+public class ParentalControl {
+    /**
+     * Strings to identify your app. To enable parental control checking for
+     * new apps, please add it here, and configure GServices accordingly.
+     */
+    public static final String VENDING = "vending";
+    public static final String YOUTUBE = "youtube";
+
+    /**
+     * This interface is supplied to getParentalControlState and is callback upon with
+     * the state of parental control.
+     */
+    public interface Callback {
+        /**
+         * This method will be called when the state of parental control is known. If state is
+         * null, then the state of parental control is unknown.
+         * @param state The state of parental control.
+         */
+        void onResult(ParentalControlState state);
+    }
+
+    private static class RemoteCallback extends IParentalControlCallback.Stub {
+        private Callback mCallback;
+
+        public RemoteCallback(Callback callback) {
+            mCallback = callback;
+        }
+
+        public void onResult(ParentalControlState state) {
+            if (mCallback != null) {
+                mCallback.onResult(state);
+            }
+        }
+    };
+
+    public static void getParentalControlState(Callback callback,
+                                               String requestingApp) {
+        ICheckinService service =
+          ICheckinService.Stub.asInterface(ServiceManager.getService("checkin"));
+
+        RemoteCallback remoteCallback = new RemoteCallback(callback);
+        try {
+            service.getParentalControlState(remoteCallback, requestingApp);
+        } catch (RemoteException e) {
+            // This should never happen.
+            Log.e("ParentalControl", "Failed to talk to the checkin service.");
+        }
+    }
+}
diff --git a/core/java/com/google/android/net/ParentalControlState.aidl b/core/java/com/google/android/net/ParentalControlState.aidl
new file mode 100644
index 0000000..ed1326a
--- /dev/null
+++ b/core/java/com/google/android/net/ParentalControlState.aidl
@@ -0,0 +1,18 @@
+/**
+ * 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.google.android.net;
+parcelable ParentalControlState;
diff --git a/core/java/com/google/android/net/ParentalControlState.java b/core/java/com/google/android/net/ParentalControlState.java
new file mode 100644
index 0000000..162a1f6
--- /dev/null
+++ b/core/java/com/google/android/net/ParentalControlState.java
@@ -0,0 +1,56 @@
+/*
+ * 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.google.android.net;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+public class ParentalControlState implements Parcelable {
+    public boolean isEnabled;
+    public String redirectUrl;
+
+    /**
+     * Used to read a ParentalControlStatus from a Parcel.
+     */
+    public static final Parcelable.Creator<ParentalControlState> CREATOR =
+        new Parcelable.Creator<ParentalControlState>() {
+              public ParentalControlState createFromParcel(Parcel source) {
+                    ParentalControlState status = new ParentalControlState();
+                    status.isEnabled = (source.readInt() == 1);
+                    status.redirectUrl = source.readString();
+                    return status;
+              }
+
+              public ParentalControlState[] newArray(int size) {
+                  return new ParentalControlState[size];
+              }
+        };
+
+    public int describeContents() {
+        return 0;
+    }
+
+    public void writeToParcel(Parcel dest, int flags) {
+      dest.writeInt(isEnabled ? 1 : 0);
+      dest.writeString(redirectUrl);
+    }
+
+    @Override
+    public String toString() {
+        return isEnabled + ", " + redirectUrl;
+    }
+};
diff --git a/core/java/com/google/android/net/SSLClientSessionCacheFactory.java b/core/java/com/google/android/net/SSLClientSessionCacheFactory.java
new file mode 100644
index 0000000..6570a9bd
--- /dev/null
+++ b/core/java/com/google/android/net/SSLClientSessionCacheFactory.java
@@ -0,0 +1,62 @@
+package com.google.android.net;
+
+import org.apache.harmony.xnet.provider.jsse.SSLClientSessionCache;
+import org.apache.harmony.xnet.provider.jsse.FileClientSessionCache;
+import android.content.Context;
+import android.provider.Settings;
+import android.util.Log;
+
+import java.io.File;
+import java.io.IOException;
+
+import com.android.internal.net.DbSSLSessionCache;
+
+/**
+ * Factory that returns the appropriate implementation of a {@link SSLClientSessionCache} based
+ * on gservices.
+ *
+ * @hide
+ */
+// TODO: return a proxied implementation that is updated as the gservices value changes.
+public final class SSLClientSessionCacheFactory {
+
+    private static final String TAG = "SSLClientSessionCacheFactory";
+
+    public static final String DB = "db";
+    public static final String FILE = "file";
+
+    // utility class
+    private SSLClientSessionCacheFactory() {}
+
+    /**
+     * Returns a new {@link SSLClientSessionCache} based on the persistent cache that's specified,
+     * if any, in gservices.  If no cache is specified, returns null.
+     * @param context The application context used for the per-process persistent cache.
+     * @return A new {@link SSLClientSessionCache}, or null if no persistent cache is configured.
+     */
+    public static SSLClientSessionCache getCache(Context context) {
+        String type = Settings.Gservices.getString(context.getContentResolver(),
+                Settings.Gservices.SSL_SESSION_CACHE);
+
+        if (type != null) {
+            if (DB.equals(type)) {
+                return DbSSLSessionCache.getInstanceForPackage(context);
+            } else if (FILE.equals(type)) {
+                File dir = context.getFilesDir();
+                File cacheDir = new File(dir, "sslcache");
+                if (!cacheDir.exists()) {
+                    cacheDir.mkdir();
+                }
+                try {
+                    return FileClientSessionCache.usingDirectory(cacheDir);
+                } catch (IOException ioe) {
+                    Log.w(TAG, "Unable to create FileClientSessionCache in " + cacheDir.getName(), ioe);
+                    return null;
+                }
+            } else {
+                Log.w(TAG, "Ignoring unrecognized type: '" + type + "'");       
+            }
+        }
+        return null;
+    }
+}
diff --git a/core/java/com/google/android/net/UrlRules.java b/core/java/com/google/android/net/UrlRules.java
new file mode 100644
index 0000000..c269d1b
--- /dev/null
+++ b/core/java/com/google/android/net/UrlRules.java
@@ -0,0 +1,229 @@
+/*
+ * 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.google.android.net;
+
+import android.content.ContentResolver;
+import android.database.Cursor;
+import android.provider.Checkin;
+import android.provider.Settings;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * A set of rules rewriting and blocking URLs.  Used to offer a point of
+ * control for redirecting HTTP requests, often to the Android proxy server.
+ *
+ * <p>Each rule has the following format:
+ *
+ * <pre><em>url-prefix</em> [REWRITE <em>new-prefix</em>] [BLOCK]</pre>
+ *
+ * <p>Any URL which starts with <em>url-prefix</em> will trigger the rule.
+ * If BLOCK is specified, requests to that URL will be blocked and fail.
+ * If REWRITE is specified, the matching prefix will be removed and replaced
+ * with <em>new-prefix</em>.  (If both are specified, BLOCK wins.)  Case is
+ * insensitive for the REWRITE and BLOCK keywords, but sensitive for URLs.
+ *
+ * <p>In Gservices, the value of any key that starts with "url:" will be
+ * interpreted as a rule.  The full name of the key is unimportant (but can
+ * be used to document the intent of the rule, and must be unique).
+ * Example gservices keys:
+ *
+ * <pre>
+ * url:use_proxy_for_calendar = "http://www.google.com/calendar/ REWRITE http://android.clients.google.com/proxy/calendar/"
+ * url:stop_crash_reports = "http://android.clients.google.com/crash/ BLOCK"
+ * url:use_ssl_for_contacts = "http://www.google.com/m8/ REWRITE https://www.google.com/m8/"
+ * </pre>
+ */
+public class UrlRules {
+    /** Thrown when the rewrite rules can't be parsed. */
+    public static class RuleFormatException extends Exception {
+        public RuleFormatException(String msg) { super(msg); }
+    }
+
+    /** A single rule specifying actions for URLs matching a certain prefix. */
+    public static class Rule implements Comparable {
+        /** Name assigned to the rule (for logging and debugging). */
+        public final String mName;
+
+        /** Prefix required to match this rule. */
+        public final String mPrefix;
+
+        /** Text to replace mPrefix with (null to leave alone). */
+        public final String mRewrite;
+
+        /** True if matching URLs should be blocked. */
+        public final boolean mBlock;
+
+        /** Default rule that does nothing. */
+        public static final Rule DEFAULT = new Rule();
+
+        /** Parse a rewrite rule as given in a Gservices value. */
+        public Rule(String name, String rule) throws RuleFormatException {
+            mName = name;
+            String[] words = PATTERN_SPACE_PLUS.split(rule);
+            if (words.length == 0) throw new RuleFormatException("Empty rule");
+
+            mPrefix = words[0];
+            String rewrite = null;
+            boolean block = false;
+            for (int pos = 1; pos < words.length; ) {
+                String word = words[pos].toLowerCase();
+                if (word.equals("rewrite") && pos + 1 < words.length) {
+                    rewrite = words[pos + 1];
+                    pos += 2;
+                } else if (word.equals("block")) {
+                    block = true;
+                    pos += 1;
+                } else {
+                    throw new RuleFormatException("Illegal rule: " + rule);
+                }
+                // TODO: Parse timeout specifications, etc.
+            }
+
+            mRewrite = rewrite;
+            mBlock = block;
+        }
+
+        /** Create the default Rule. */
+        private Rule() {
+            mName = "DEFAULT";
+            mPrefix = "";
+            mRewrite = null;
+            mBlock = false;
+        }
+
+        /**
+         * Apply the rule to a particular URL (assumed to match the rule).
+         * @param url to rewrite or modify.
+         * @return modified URL, or null if the URL is blocked.
+         */
+         public String apply(String url) {
+             if (mBlock) {
+                 return null;
+             } else if (mRewrite != null) {
+                 return mRewrite + url.substring(mPrefix.length());
+             } else {
+                 return url;
+             }
+         }
+
+         /** More generic rules are greater than more specific rules. */
+         public int compareTo(Object o) {
+             return ((Rule) o).mPrefix.compareTo(mPrefix);
+         }
+    }
+
+    /** Cached rule set from Gservices. */
+    private static UrlRules sCachedRules = new UrlRules(new Rule[] {});
+
+    private static final Pattern PATTERN_SPACE_PLUS = Pattern.compile(" +");
+    private static final Pattern RULE_PATTERN = Pattern.compile("\\W");
+
+    /** Gservices digest when sCachedRules was cached. */
+    private static String sCachedDigest = null;
+
+    /** Currently active set of Rules. */
+    private final Rule[] mRules;
+
+    /** Regular expression with one capturing group for each Rule. */
+    private final Pattern mPattern;
+
+    /**
+     * Create a rewriter from an array of Rules.  Normally used only for
+     * testing.  Instead, use {@link #getRules} to get rules from Gservices.
+     * @param rules to use.
+     */
+    public UrlRules(Rule[] rules) {
+        // Sort the rules to put the most specific rules first.
+        Arrays.sort(rules);
+
+        // Construct a regular expression, escaping all the prefix strings.
+        StringBuilder pattern = new StringBuilder("(");
+        for (int i = 0; i < rules.length; ++i) {
+            if (i > 0) pattern.append(")|(");
+            pattern.append(RULE_PATTERN.matcher(rules[i].mPrefix).replaceAll("\\\\$0"));
+        }
+        mPattern = Pattern.compile(pattern.append(")").toString());
+        mRules = rules;
+    }
+
+    /**
+     * Match a string against every Rule and find one that matches.
+     * @param uri to match against the Rules in the rewriter.
+     * @return the most specific matching Rule, or Rule.DEFAULT if none match.
+     */
+    public Rule matchRule(String url) {
+        Matcher matcher = mPattern.matcher(url);
+        if (matcher.lookingAt()) {
+            for (int i = 0; i < mRules.length; ++i) {
+                if (matcher.group(i + 1) != null) {
+                    return mRules[i];  // Rules are sorted most specific first.
+                }
+            }
+        }
+        return Rule.DEFAULT;
+    }
+
+    /**
+     * Get the (possibly cached) UrlRules based on the rules in Gservices.
+     * @param resolver to use for accessing the Gservices database.
+     * @return an updated UrlRules instance
+     */
+    public static synchronized UrlRules getRules(ContentResolver resolver) {
+        String digest = Settings.Gservices.getString(resolver,
+                Settings.Gservices.PROVISIONING_DIGEST);
+        if (sCachedDigest != null && sCachedDigest.equals(digest)) {
+            // The digest is the same, so the rules are the same.
+            return sCachedRules;
+        }
+
+        // Get all the Gservices settings with names starting with "url:".
+        Cursor cursor = resolver.query(Settings.Gservices.CONTENT_URI,
+                new String[] {
+                    Settings.Gservices.NAME,
+                    Settings.Gservices.VALUE
+                },
+                Settings.Gservices.NAME + " like \"url:%\"", null,
+                Settings.Gservices.NAME);
+        try {
+            ArrayList<Rule> rules = new ArrayList<Rule>();
+            while (cursor.moveToNext()) {
+                try {
+                    String name = cursor.getString(0).substring(4);  // "url:X"
+                    String value = cursor.getString(1);
+                    if (value == null || value.length() == 0) continue;
+                    rules.add(new Rule(name, value));
+                } catch (RuleFormatException e) {
+                    // Oops, Gservices has an invalid rule!  Skip it.
+                    Log.e("UrlRules", "Invalid rule from Gservices", e);
+                    Checkin.logEvent(resolver,
+                        Checkin.Events.Tag.GSERVICES_ERROR, e.toString());
+                }
+            }
+            sCachedRules = new UrlRules(rules.toArray(new Rule[rules.size()]));
+            sCachedDigest = digest;
+        } finally {
+            cursor.close();
+        }
+
+        return sCachedRules;
+    }
+}
diff --git a/core/java/com/google/android/util/AbstractMessageParser.java b/core/java/com/google/android/util/AbstractMessageParser.java
new file mode 100644
index 0000000..25f6b33
--- /dev/null
+++ b/core/java/com/google/android/util/AbstractMessageParser.java
@@ -0,0 +1,1496 @@
+// Copyright 2007 The Android Open Source Project
+// All Rights Reserved.
+
+package com.google.android.util;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.Set;
+import java.util.List;
+
+/**
+ *
+ * Logic for parsing a text message typed by the user looking for smileys,
+ * urls, acronyms,formatting (e.g., '*'s for bold), me commands
+ * (e.g., "/me is asleep"), and punctuation.
+ *
+ * It constructs an array, which breaks the text up into its
+ * constituent pieces, which we return to the client.
+ *
+ */
+public abstract class AbstractMessageParser {
+/**
+ * Interface representing the set of resources needed by a message parser
+ *
+ * @author jessan (Jessan Hutchison-Quillian)
+ */
+  public static interface Resources {
+
+    /** Get the known set of URL schemes. */
+    public Set<String> getSchemes();
+
+    /** Get the possible values for the last part of a domain name.
+     *  Values are expected to be reversed in the Trie.
+     */
+    public TrieNode getDomainSuffixes();
+
+    /** Get the smileys accepted by the parser. */
+    public TrieNode getSmileys();
+
+    /** Get the acronyms accepted by the parser. */
+    public TrieNode getAcronyms();
+  }
+
+  /**
+   * Subclasses must define the schemes, domains, smileys and acronyms
+   * that are necessary for parsing
+   */
+  protected abstract Resources getResources();
+
+  /** Music note that indicates user is listening to a music track. */
+  public static final String musicNote = "\u266B ";
+
+  private String text;
+  private int nextChar;
+  private int nextClass;
+  private ArrayList<Part> parts;
+  private ArrayList<Token> tokens;
+  private HashMap<Character,Format> formatStart;
+  private boolean parseSmilies;
+  private boolean parseAcronyms;
+  private boolean parseFormatting;
+  private boolean parseUrls;
+  private boolean parseMeText;
+  private boolean parseMusic;
+
+  /**
+   * Create a message parser to parse urls, formatting, acronyms, smileys,
+   * /me text and  music
+   *
+   * @param text the text to parse
+   */
+  public AbstractMessageParser(String text) {
+    this(text, true, true, true, true, true, true);
+  }
+
+  /**
+   * Create a message parser, specifying the kinds of text to parse
+   *
+   * @param text the text to parse
+   *
+   */
+  public AbstractMessageParser(String text, boolean parseSmilies,
+      boolean parseAcronyms, boolean parseFormatting, boolean parseUrls,
+      boolean parseMusic, boolean parseMeText) {
+    this.text = text;
+    this.nextChar = 0;
+    this.nextClass = 10;
+    this.parts = new ArrayList<Part>();
+    this.tokens = new ArrayList<Token>();
+    this.formatStart = new HashMap<Character,Format>();
+    this.parseSmilies = parseSmilies;
+    this.parseAcronyms = parseAcronyms;
+    this.parseFormatting = parseFormatting;
+    this.parseUrls = parseUrls;
+    this.parseMusic = parseMusic;
+    this.parseMeText = parseMeText;
+  }
+
+  /** Returns the raw text being parsed. */
+  public final String getRawText() { return text; }
+
+  /** Return the number of parts. */
+  public final int getPartCount() { return parts.size(); }
+
+  /** Return the part at the given index. */
+  public final Part getPart(int index) { return parts.get(index); }
+
+  /** Return the list of parts from the parsed text */
+  public final List<Part> getParts() { return parts; }
+
+  /** Parses the text string into an internal representation. */
+  public void parse() {
+    // Look for music track (of which there would be only one and it'll be the
+    // first token)
+    if (parseMusicTrack()) {
+      buildParts(null);
+      return;
+    }
+
+    // Look for me commands.
+    String meText = null;
+    if (parseMeText && text.startsWith("/me") && (text.length() > 3) &&
+        Character.isWhitespace(text.charAt(3))) {
+      meText = text.substring(0, 4);
+      text = text.substring(4);
+    }
+
+    // Break the text into tokens.
+    boolean wasSmiley = false;
+    while (nextChar < text.length()) {
+      if (!isWordBreak(nextChar)) {
+        if (!wasSmiley || !isSmileyBreak(nextChar)) {
+          throw new AssertionError("last chunk did not end at word break");
+        }
+      }
+
+      if (parseSmiley()) {
+        wasSmiley = true;
+      } else {
+        wasSmiley = false;
+
+        if (!parseAcronym() && !parseURL() && !parseFormatting()) {
+          parseText();
+        }
+      }
+    }
+
+    // Trim the whitespace before and after media components.
+    for (int i = 0; i < tokens.size(); ++i) {
+      if (tokens.get(i).isMedia()) {
+        if ((i > 0) && (tokens.get(i - 1) instanceof Html)) {
+          ((Html)tokens.get(i - 1)).trimLeadingWhitespace();
+        }
+        if ((i + 1 < tokens.size()) && (tokens.get(i + 1) instanceof Html)) {
+          ((Html)tokens.get(i + 1)).trimTrailingWhitespace();
+        }
+      }
+    }
+
+    // Remove any empty html tokens.
+    for (int i = 0; i < tokens.size(); ++i) {
+      if (tokens.get(i).isHtml() &&
+          (tokens.get(i).toHtml(true).length() == 0)) {
+        tokens.remove(i);
+        --i;  // visit this index again
+      }
+    }
+
+    buildParts(meText);
+  }
+
+  /**
+   * Get a the appropriate Token for a given URL
+   *
+   * @param text the anchor text
+   * @param url the url
+   *
+   */
+  public static Token tokenForUrl(String url, String text) {
+    if(url == null) {
+      return null;
+    }
+
+    //Look for video links
+    Video video = Video.matchURL(url, text);
+    if (video != null) {
+      return video;
+    }
+
+    // Look for video links.
+    YouTubeVideo ytVideo = YouTubeVideo.matchURL(url, text);
+    if (ytVideo != null) {
+      return ytVideo;
+    }
+
+    // Look for photo links.
+    Photo photo = Photo.matchURL(url, text);
+    if (photo != null) {
+      return photo;
+    }
+
+    // Look for photo links.
+    FlickrPhoto flickrPhoto = FlickrPhoto.matchURL(url, text);
+    if (flickrPhoto != null) {
+      return flickrPhoto;
+    }
+
+    //Not media, so must be a regular URL
+    return new Link(url, text);
+  }
+
+  /**
+   * Builds the parts list.
+   *
+   * @param meText any meText parsed from the message
+   */
+  private void buildParts(String meText) {
+    for (int i = 0; i < tokens.size(); ++i) {
+      Token token = tokens.get(i);
+      if (token.isMedia() || (parts.size() == 0) || lastPart().isMedia()) {
+        parts.add(new Part());
+      }
+      lastPart().add(token);
+    }
+
+    // The first part inherits the meText of the line.
+    if (parts.size() > 0) {
+      parts.get(0).setMeText(meText);
+    }
+  }
+
+  /** Returns the last part in the list. */
+  private Part lastPart() { return parts.get(parts.size() - 1); }
+
+  /**
+   * Looks for a music track (\u266B is first character, everything else is
+   * track info).
+   */
+  private boolean parseMusicTrack() {
+
+    if (parseMusic && text.startsWith(musicNote)) {
+      addToken(new MusicTrack(text.substring(musicNote.length())));
+      nextChar = text.length();
+      return true;
+    }
+    return false;
+  }
+
+  /** Consumes all of the text in the next word . */
+  private void parseText() {
+    StringBuilder buf = new StringBuilder();
+    int start = nextChar;
+    do {
+      char ch = text.charAt(nextChar++);
+      switch (ch) {
+        case '<':  buf.append("&lt;"); break;
+        case '>':  buf.append("&gt;"); break;
+        case '&':  buf.append("&amp;"); break;
+        case '"':  buf.append("&quot;"); break;
+        case '\'':  buf.append("&apos;"); break;
+        case '\n':  buf.append("<br>"); break;
+        default:  buf.append(ch); break;
+      }
+    } while (!isWordBreak(nextChar));
+
+    addToken(new Html(text.substring(start, nextChar), buf.toString()));
+  }
+
+  /**
+   * Looks for smileys (e.g., ":)") in the text.  The set of known smileys is
+   * loaded from a file into a trie at server start.
+   */
+  private boolean parseSmiley() {
+    if(!parseSmilies) {
+      return false;
+    }
+    TrieNode match = longestMatch(getResources().getSmileys(), this, nextChar,
+                                  true);
+    if (match == null) {
+      return false;
+    } else {
+      int previousCharClass = getCharClass(nextChar - 1);
+      int nextCharClass = getCharClass(nextChar + match.getText().length());
+      if ((previousCharClass == 2 || previousCharClass == 3)
+          && (nextCharClass == 2 || nextCharClass == 3)) {
+        return false;
+      }
+      addToken(new Smiley(match.getText()));
+      nextChar += match.getText().length();
+      return true;
+    }
+  }
+
+  /** Looks for acronyms (e.g., "lol") in the text.
+   */
+  private boolean parseAcronym() {
+    if(!parseAcronyms) {
+      return false;
+    }
+    TrieNode match = longestMatch(getResources().getAcronyms(), this, nextChar);
+    if (match == null) {
+      return false;
+    } else {
+      addToken(new Acronym(match.getText(), match.getValue()));
+      nextChar += match.getText().length();
+      return true;
+    }
+  }
+
+  /** Determines if this is an allowable domain character. */
+  private boolean isDomainChar(char c) {
+    return c == '-' || Character.isLetter(c) || Character.isDigit(c);
+  }
+
+  /** Determines if the given string is a valid domain. */
+  private boolean isValidDomain(String domain) {
+    // For hostnames, check that it ends with a known domain suffix
+    if (matches(getResources().getDomainSuffixes(), reverse(domain))) {
+      return true;
+    }
+    return false;
+  }
+
+  /**
+   * Looks for a URL in two possible forms:  either a proper URL with a known
+   * scheme or a domain name optionally followed by a path, query, or query.
+   */
+  private boolean parseURL() {
+    // Make sure this is a valid place to start a URL.
+    if (!parseUrls || !isURLBreak(nextChar)) {
+      return false;
+    }
+
+    int start = nextChar;
+
+    // Search for the first block of letters.
+    int index = start;
+    while ((index < text.length()) && isDomainChar(text.charAt(index))) {
+      index += 1;
+    }
+
+    String url = "";
+    boolean done = false;
+
+    if (index == text.length()) {
+      return false;
+    } else if (text.charAt(index) == ':') {
+      // Make sure this is a known scheme.
+      String scheme = text.substring(nextChar, index);
+      if (!getResources().getSchemes().contains(scheme)) {
+        return false;
+      }
+    } else if (text.charAt(index) == '.') {
+      // Search for the end of the domain name.
+      while (index < text.length()) {
+        char ch = text.charAt(index);
+        if ((ch != '.') && !isDomainChar(ch)) {
+          break;
+        } else {
+          index += 1;
+        }
+      }
+
+      // Make sure the domain name has a valid suffix.  Since tries look for
+      // prefix matches, we reverse all the strings to get suffix comparisons.
+      String domain = text.substring(nextChar, index);
+      if (!isValidDomain(domain)) {
+        return false;
+      }
+
+      // Search for a port.  We deal with this specially because a colon can
+      // also be a punctuation character.
+      if ((index + 1 < text.length()) && (text.charAt(index) == ':')) {
+        char ch = text.charAt(index + 1);
+        if (Character.isDigit(ch)) {
+          index += 1;
+          while ((index < text.length()) &&
+                 Character.isDigit(text.charAt(index))) {
+            index += 1;
+          }
+        }
+      }
+
+      // The domain name should be followed by end of line, whitespace,
+      // punctuation, or a colon, slash, question, or hash character.  The
+      // tricky part here is that some URL characters are also punctuation, so
+      // we need to distinguish them.  Since we looked for ports above, a colon
+      // is always punctuation here.  To distinguish '?' cases, we look at the
+      // character that follows it.
+      if (index == text.length()) {
+        done = true;
+      } else {
+        char ch = text.charAt(index);
+        if (ch == '?') {
+          // If the next character is whitespace or punctuation (or missing),
+          // then this question mark looks like punctuation.
+          if (index + 1 == text.length()) {
+            done = true;
+          } else {
+            char ch2 = text.charAt(index + 1);
+            if (Character.isWhitespace(ch2) || isPunctuation(ch2)) {
+              done = true;
+            }
+          }
+        } else if (isPunctuation(ch)) {
+          done = true;
+        } else if (Character.isWhitespace(ch)) {
+          done = true;
+        } else if ((ch == '/') || (ch == '#')) {
+          // In this case, the URL is not done.  We will search for the end of
+          // it below.
+        } else {
+          return false;
+        }
+      }
+
+      // We will assume the user meant HTTP.  (One weird case is where they
+      // type a port of 443.  That could mean HTTPS, but they might also want
+      // HTTP.  We'll let them specify if they don't want HTTP.)
+      url = "http://";
+    } else {
+      return false;
+    }
+
+    // If the URL is not done, search for the end, which is just before the
+    // next whitespace character.
+    if (!done) {
+      while ((index < text.length()) &&
+             !Character.isWhitespace(text.charAt(index))) {
+        index += 1;
+      }
+    }
+
+    String urlText = text.substring(start, index);
+    url += urlText;
+
+    // Figure out the appropriate token type.
+    addURLToken(url, urlText);
+
+    nextChar = index;
+    return true;
+  }
+
+  /**
+   * Adds the appropriate token for the given URL.  This might be a simple
+   * link or it might be a recognized media type.
+   */
+  private void addURLToken(String url, String text) {
+     addToken(tokenForUrl(url, text));
+  }
+
+  /**
+   * Deal with formatting characters.
+   *
+   * Parsing is as follows:
+   *  - Treat all contiguous strings of formatting characters as one block.
+   *    (This method processes one block.)
+   *  - Only a single instance of a particular format character within a block
+   *    is used to determine whether to turn on/off that type of formatting;
+   *    other instances simply print the character itself.
+   *  - If the format is to be turned on, we use the _first_ instance; if it
+   *    is to be turned off, we use the _last_ instance (by appending the
+   *    format.)
+   *
+   * Example:
+   *   **string** turns into <b>*string*</b>
+   */
+  private boolean parseFormatting() {
+    if(!parseFormatting) {
+      return false;
+    }
+    int endChar = nextChar;
+    while ((endChar < text.length()) && isFormatChar(text.charAt(endChar))) {
+      endChar += 1;
+    }
+
+    if ((endChar == nextChar) || !isWordBreak(endChar)) {
+      return false;
+    }
+
+    // Keeps track of whether we've seen a character (in map if we've seen it)
+    // and whether we should append a closing format token (if value in
+    // map is TRUE).  Linked hashmap for consistent ordering.
+    LinkedHashMap<Character, Boolean> seenCharacters =
+        new LinkedHashMap<Character, Boolean>();
+
+    for (int index = nextChar; index < endChar; ++index) {
+      char ch = text.charAt(index);
+      Character key = Character.valueOf(ch);
+      if (seenCharacters.containsKey(key)) {
+        // Already seen this character, just append an unmatched token, which
+        // will print plaintext character
+        addToken(new Format(ch, false));
+      } else {
+        Format start = formatStart.get(key);
+        if (start != null) {
+          // Match the start token, and ask an end token to be appended
+          start.setMatched(true);
+          formatStart.remove(key);
+          seenCharacters.put(key, Boolean.TRUE);
+        } else {
+          // Append start token
+          start = new Format(ch, true);
+          formatStart.put(key, start);
+          addToken(start);
+          seenCharacters.put(key, Boolean.FALSE);
+        }
+      }
+    }
+
+    // Append any necessary end tokens
+    for (Character key : seenCharacters.keySet()) {
+      if (seenCharacters.get(key) == Boolean.TRUE) {
+        Format end = new Format(key.charValue(), false);
+        end.setMatched(true);
+        addToken(end);
+      }
+    }
+
+    nextChar = endChar;
+    return true;
+  }
+
+  /** Determines whether the given index could be a possible word break. */
+  private boolean isWordBreak(int index) {
+    return getCharClass(index - 1) != getCharClass(index);
+  }
+
+  /** Determines whether the given index could be a possible smiley break. */
+  private boolean isSmileyBreak(int index) {
+    if (index > 0 && index < text.length()) {
+      if (isSmileyBreak(text.charAt(index - 1), text.charAt(index))) {
+        return true;
+      }
+    }
+
+    return false;
+  }
+
+  /**
+   * Verifies that the character before the given index is end of line,
+   * whitespace, or punctuation.
+   */
+  private boolean isURLBreak(int index) {
+    switch (getCharClass(index - 1)) {
+      case 2:
+      case 3:
+      case 4:
+        return false;
+
+      case 0:
+      case 1:
+      default:
+        return true;
+    }
+  }
+
+  /** Returns the class for the character at the given index. */
+  private int getCharClass(int index) {
+    if ((index < 0) || (text.length() <= index)) {
+      return 0;
+    }
+
+    char ch = text.charAt(index);
+    if (Character.isWhitespace(ch)) {
+      return 1;
+    } else if (Character.isLetter(ch)) {
+      return 2;
+    } else if (Character.isDigit(ch)) {
+      return 3;
+    } else if (isPunctuation(ch)) {
+      // For punctuation, we return a unique value every time so that they are
+      // always different from any other character.  Punctuation should always
+      // be considered a possible word break.
+      return ++nextClass;
+    } else {
+      return 4;
+    }
+  }
+
+  /**
+   * Returns true if <code>c1</code> could be the last character of
+   * a smiley and <code>c2</code> could be the first character of
+   * a different smiley, if {@link #isWordBreak} would not already
+   * recognize that this is possible.
+   */
+  private static boolean isSmileyBreak(char c1, char c2) {
+    switch (c1) {
+      /*    
+       * These characters can end smileys, but don't normally end words.
+       */
+      case '$': case '&': case '*': case '+': case '-':
+      case '/': case '<': case '=': case '>': case '@':
+      case '[': case '\\': case ']': case '^': case '|':
+      case '}': case '~':
+        switch (c2) {
+          /*
+           * These characters can begin smileys, but don't normally
+           * begin words.
+           */
+          case '#': case '$': case '%': case '*': case '/':
+          case '<': case '=': case '>': case '@': case '[':
+          case '\\': case '^': case '~':
+            return true;
+        }
+    }
+
+    return false;
+  }
+
+  /** Determines whether the given character is punctuation. */
+  private static boolean isPunctuation(char ch) {
+    switch (ch) {
+      case '.': case ',': case '"': case ':': case ';':
+      case '?': case '!': case '(': case ')':
+        return true;
+
+      default:
+        return false;
+    }
+  }
+
+  /**
+   * Determines whether the given character is the beginning or end of a
+   * section with special formatting.
+   */
+  private static boolean isFormatChar(char ch) {
+    switch (ch) {
+      case '*': case '_': case '^':
+        return true;
+
+      default:
+        return false;
+    }
+  }
+
+  /** Represents a unit of parsed output. */
+  public static abstract class Token {
+    public enum Type {
+
+      HTML ("html"),
+      FORMAT ("format"),  // subtype of HTML
+      LINK ("l"),
+      SMILEY ("e"),
+      ACRONYM ("a"),
+      MUSIC ("m"),
+      GOOGLE_VIDEO ("v"),
+      YOUTUBE_VIDEO ("yt"),
+      PHOTO ("p"),
+      FLICKR ("f");
+
+      //stringreps for HTML and FORMAT don't really matter
+      //because they don't define getInfo(), which is where it is used
+      //For the other types, code depends on their stringreps
+      private String stringRep;
+
+      Type(String stringRep) {
+        this.stringRep = stringRep;
+      }
+
+      /** {@inheritDoc} */
+      public String toString() {
+        return this.stringRep;
+      }
+    }
+
+    protected Type type;
+    protected String text;
+
+    protected Token(Type type, String text) {
+      this.type = type;
+      this.text = text;
+    }
+
+    /** Returns the type of the token. */
+    public Type getType() { return type; }
+
+    /**
+     * Get the relevant information about a token
+     *
+     * @return a list of strings representing the token, not null
+     *         The first item is always a string representation of the type
+     */
+    public List<String> getInfo() {
+      List<String> info = new ArrayList<String>();
+      info.add(getType().toString());
+      return info;
+    }
+
+    /** Returns the raw text of the token. */
+    public String getRawText() { return text; }
+
+    public boolean isMedia() { return false; }
+    public abstract boolean isHtml();
+    public boolean isArray() { return !isHtml(); }
+
+    public String toHtml(boolean caps) { throw new AssertionError("not html"); }
+
+    // The token can change the caps of the text after that point.
+    public boolean controlCaps() { return false; }
+    public boolean setCaps() { return false; }
+  }
+
+  /** Represents a simple string of html text. */
+  public static class Html extends Token {
+    private String html;
+
+    public Html(String text, String html) {
+      super(Type.HTML, text);
+      this.html = html;
+    }
+
+    public boolean isHtml() { return true; }
+    public String toHtml(boolean caps) {
+      return caps ? html.toUpperCase() : html;
+    }
+    /**
+     * Not supported. Info should not be needed for this type
+     */
+    public List<String> getInfo() {
+      throw new UnsupportedOperationException();
+    }
+
+    public void trimLeadingWhitespace() {
+      text = trimLeadingWhitespace(text);
+      html = trimLeadingWhitespace(html);
+    }
+
+    public void trimTrailingWhitespace() {
+      text = trimTrailingWhitespace(text);
+      html = trimTrailingWhitespace(html);
+    }
+
+    private static String trimLeadingWhitespace(String text) {
+      int index = 0;
+      while ((index < text.length()) &&
+             Character.isWhitespace(text.charAt(index))) {
+        ++index;
+      }
+      return text.substring(index);
+    }
+
+    public static String trimTrailingWhitespace(String text) {
+      int index = text.length();
+      while ((index > 0) && Character.isWhitespace(text.charAt(index - 1))) {
+        --index;
+      }
+      return text.substring(0, index);
+    }
+  }
+
+  /** Represents a music track token at the beginning. */
+  public static class MusicTrack extends Token {
+    private String track;
+
+    public MusicTrack(String track) {
+      super(Type.MUSIC, track);
+      this.track = track;
+    }
+
+    public String getTrack() { return track; }
+
+    public boolean isHtml() { return false; }
+
+    public List<String> getInfo() {
+      List<String> info = super.getInfo();
+      info.add(getTrack());
+      return info;
+    }
+  }
+
+  /** Represents a link that was found in the input. */
+  public static class Link extends Token {
+    private String url;
+
+    public Link(String url, String text) {
+      super(Type.LINK, text);
+      this.url = url;
+    }
+
+    public String getURL() { return url; }
+
+    public boolean isHtml() { return false; }
+
+    public List<String> getInfo() {
+      List<String> info = super.getInfo();
+      info.add(getURL());
+      info.add(getRawText());
+      return info;
+    }
+  }
+
+  /** Represents a link to a Google Video. */
+  public static class Video extends Token {
+    /** Pattern for a video URL. */
+    private static final Pattern URL_PATTERN = Pattern.compile(
+        "(?i)http://video\\.google\\.[a-z0-9]+(?:\\.[a-z0-9]+)?/videoplay\\?"
+        + ".*?\\bdocid=(-?\\d+).*");
+
+    private String docid;
+
+    public Video(String docid, String text) {
+      super(Type.GOOGLE_VIDEO, text);
+      this.docid = docid;
+    }
+
+    public String getDocID() { return docid; }
+
+    public boolean isHtml() { return false; }
+    public boolean isMedia() { return true; }
+
+    /** Returns a Video object if the given url is to a video. */
+    public static Video matchURL(String url, String text) {
+      Matcher m = URL_PATTERN.matcher(url);
+      if (m.matches()) {
+        return new Video(m.group(1), text);
+      } else {
+        return null;
+      }
+    }
+
+    public List<String> getInfo() {
+      List<String> info = super.getInfo();
+      info.add(getRssUrl(docid));
+      info.add(getURL(docid));
+      return info;
+    }
+
+    /** Returns the URL for the RSS description of the given video. */
+    public static String getRssUrl(String docid) {
+      return "http://video.google.com/videofeed"
+             + "?type=docid&output=rss&sourceid=gtalk&docid=" + docid;
+    }
+
+    /** (For testing purposes:) Returns a video URL with the given parts.  */
+    public static String getURL(String docid) {
+      return getURL(docid, null);
+    }
+
+    /** (For testing purposes:) Returns a video URL with the given parts.  */
+    public static String getURL(String docid, String extraParams) {
+      if (extraParams == null) {
+        extraParams = "";
+      } else if (extraParams.length() > 0) {
+        extraParams += "&";
+      }
+      return "http://video.google.com/videoplay?" + extraParams
+             + "docid=" + docid;
+    }
+  }
+
+  /** Represents a link to a YouTube video. */
+  public static class YouTubeVideo extends Token {
+    /** Pattern for a video URL. */
+    private static final Pattern URL_PATTERN = Pattern.compile(
+        "(?i)http://(?:[a-z0-9]+\\.)?youtube\\.[a-z0-9]+(?:\\.[a-z0-9]+)?/watch\\?"
+        + ".*\\bv=([-_a-zA-Z0-9=]+).*");
+
+    private String docid;
+
+    public YouTubeVideo(String docid, String text) {
+      super(Type.YOUTUBE_VIDEO, text);
+      this.docid = docid;
+    }
+
+    public String getDocID() { return docid; }
+
+    public boolean isHtml() { return false; }
+    public boolean isMedia() { return true; }
+
+    /** Returns a Video object if the given url is to a video. */
+    public static YouTubeVideo matchURL(String url, String text) {
+      Matcher m = URL_PATTERN.matcher(url);
+      if (m.matches()) {
+        return new YouTubeVideo(m.group(1), text);
+      } else {
+        return null;
+      }
+    }
+
+    public List<String> getInfo() {
+      List<String> info = super.getInfo();
+      info.add(getRssUrl(docid));
+      info.add(getURL(docid));
+      return info;
+    }
+
+    /** Returns the URL for the RSS description of the given video. */
+    public static String getRssUrl(String docid) {
+      return "http://youtube.com/watch?v=" + docid;
+    }
+
+    /** (For testing purposes:) Returns a video URL with the given parts.  */
+    public static String getURL(String docid) {
+      return getURL(docid, null);
+    }
+
+    /** (For testing purposes:) Returns a video URL with the given parts.  */
+    public static String getURL(String docid, String extraParams) {
+      if (extraParams == null) {
+        extraParams = "";
+      } else if (extraParams.length() > 0) {
+        extraParams += "&";
+      }
+      return "http://youtube.com/watch?" + extraParams + "v=" + docid;
+    }
+
+    /** (For testing purposes:) Returns a video URL with the given parts.
+      * @param http If true, includes http://
+      * @param prefix If non-null/non-blank, adds to URL before youtube.com.
+      *   (e.g., prefix="br." --> "br.youtube.com")
+      */
+    public static String getPrefixedURL(boolean http, String prefix,
+                                        String docid, String extraParams) {
+      String protocol = "";
+
+      if (http) {
+        protocol = "http://";
+      }
+
+      if (prefix == null) {
+        prefix = "";
+      }
+
+      if (extraParams == null) {
+        extraParams = "";
+      } else if (extraParams.length() > 0) {
+        extraParams += "&";
+      }
+
+      return protocol + prefix + "youtube.com/watch?" + extraParams + "v=" +
+              docid;
+    }
+  }
+
+  /** Represents a link to a Picasa photo or album. */
+  public static class Photo extends Token {
+    /** Pattern for an album or photo URL. */
+    // TODO (katyarogers) searchbrowse includes search lists and tags,
+    // it follows a different pattern than albums - would be nice to add later
+    private static final Pattern URL_PATTERN = Pattern.compile(
+        "http://picasaweb.google.com/([^/?#&]+)/+((?!searchbrowse)[^/?#&]+)(?:/|/photo)?(?:\\?[^#]*)?(?:#(.*))?");
+
+    private String user;
+    private String album;
+    private String photo;  // null for albums
+
+    public Photo(String user, String album, String photo, String text) {
+      super(Type.PHOTO, text);
+      this.user = user;
+      this.album = album;
+      this.photo = photo;
+    }
+
+    public String getUser() { return user; }
+    public String getAlbum() { return album; }
+    public String getPhoto() { return photo; }
+
+    public boolean isHtml() { return false; }
+    public boolean isMedia() { return true; }
+
+    /** Returns a Photo object if the given url is to a photo or album. */
+    public static Photo matchURL(String url, String text) {
+      Matcher m = URL_PATTERN.matcher(url);
+      if (m.matches()) {
+        return new Photo(m.group(1), m.group(2), m.group(3), text);
+      } else {
+        return null;
+      }
+    }
+
+    public List<String> getInfo() {
+      List<String> info = super.getInfo();
+      info.add(getRssUrl(getUser()));
+      info.add(getAlbumURL(getUser(), getAlbum()));
+      if (getPhoto() != null) {
+        info.add(getPhotoURL(getUser(), getAlbum(), getPhoto()));
+      } else {
+        info.add((String)null);
+      }
+      return info;
+    }
+
+    /** Returns the URL for the RSS description of the user's albums. */
+    public static String getRssUrl(String user) {
+      return "http://picasaweb.google.com/data/feed/api/user/" + user +
+        "?category=album&alt=rss";
+    }
+
+    /** Returns the URL for an album. */
+    public static String getAlbumURL(String user, String album) {
+      return "http://picasaweb.google.com/" + user + "/" + album;
+    }
+
+    /** Returns the URL for a particular photo. */
+    public static String getPhotoURL(String user, String album, String photo) {
+      return "http://picasaweb.google.com/" + user + "/" + album + "/photo#"
+             + photo;
+    }
+  }
+
+  /** Represents a link to a Flickr photo or album. */
+  public static class FlickrPhoto extends Token {
+    /** Pattern for a user album or photo URL. */
+    private static final Pattern URL_PATTERN = Pattern.compile(
+        "http://(?:www.)?flickr.com/photos/([^/?#&]+)/?([^/?#&]+)?/?.*");
+    private static final Pattern GROUPING_PATTERN = Pattern.compile(
+        "http://(?:www.)?flickr.com/photos/([^/?#&]+)/(tags|sets)/" +
+        "([^/?#&]+)/?");
+
+    private static final String SETS = "sets";
+    private static final String TAGS = "tags";
+
+    private String user;
+    private String photo;      // null for user album
+    private String grouping;   // either "tags" or "sets"
+    private String groupingId; // sets or tags identifier
+
+    public FlickrPhoto(String user, String photo, String grouping,
+                       String groupingId, String text) {
+      super(Type.FLICKR, text);
+
+      /* System wide tags look like the URL to a Flickr user. */
+      if (!TAGS.equals(user)) {
+        this.user = user;
+        // Don't consider slide show URL a photo
+        this.photo = (!"show".equals(photo) ? photo : null);
+        this.grouping = grouping;
+        this.groupingId = groupingId;
+      } else {
+        this.user = null;
+        this.photo = null;
+        this.grouping = TAGS;
+        this.groupingId = photo;
+      }
+    }
+
+    public String getUser() { return user; }
+    public String getPhoto() { return photo; }
+    public String getGrouping() { return grouping; }
+    public String getGroupingId() { return groupingId; }
+
+    public boolean isHtml() { return false; }
+    public boolean isMedia() { return true; }
+
+    /**
+     * Returns a FlickrPhoto object if the given url is to a photo or Flickr
+     * user.
+     */
+    public static FlickrPhoto matchURL(String url, String text) {
+      Matcher m = GROUPING_PATTERN.matcher(url);
+      if (m.matches()) {
+        return new FlickrPhoto(m.group(1), null, m.group(2), m.group(3), text);
+      }
+
+      m = URL_PATTERN.matcher(url);
+      if (m.matches()) {
+        return new FlickrPhoto(m.group(1), m.group(2), null, null, text);
+      } else {
+        return null;
+      }
+    }
+
+    public List<String> getInfo() {
+      List<String> info = super.getInfo();
+      info.add(getUrl());
+      info.add(getUser() != null ? getUser() : "");
+      info.add(getPhoto() != null ? getPhoto() : "");
+      info.add(getGrouping() != null ? getGrouping() : "");
+      info.add(getGroupingId() != null ? getGroupingId() : "");
+      return info;
+    }
+
+    public String getUrl() {
+      if (SETS.equals(grouping)) {
+        return getUserSetsURL(user, groupingId);
+      } else if (TAGS.equals(grouping)) {
+        if (user != null) {
+          return getUserTagsURL(user, groupingId);
+        } else {
+          return getTagsURL(groupingId);
+        }
+      } else if (photo != null) {
+        return getPhotoURL(user, photo);
+      } else {
+        return getUserURL(user);
+      }
+    }
+
+    /** Returns the URL for the RSS description. */
+    public static String getRssUrl(String user) {
+      return null;
+    }
+
+    /** Returns the URL for a particular tag. */
+    public static String getTagsURL(String tag) {
+      return "http://flickr.com/photos/tags/" + tag;
+    }
+
+    /** Returns the URL to the user's Flickr homepage. */
+    public static String getUserURL(String user) {
+      return "http://flickr.com/photos/" + user;
+    }
+
+    /** Returns the URL for a particular photo. */
+    public static String getPhotoURL(String user, String photo) {
+      return "http://flickr.com/photos/" + user + "/" + photo;
+    }
+
+    /** Returns the URL for a user tag photo set. */
+    public static String getUserTagsURL(String user, String tagId) {
+      return "http://flickr.com/photos/" + user + "/tags/" + tagId;
+    }
+
+    /** Returns the URL for user set. */
+    public static String getUserSetsURL(String user, String setId) {
+      return "http://flickr.com/photos/" + user + "/sets/" + setId;
+    }
+  }
+
+  /** Represents a smiley that was found in the input. */
+  public static class Smiley extends Token {
+    // TODO: Pass the SWF URL down to the client.
+
+    public Smiley(String text) {
+      super(Type.SMILEY, text);
+    }
+
+    public boolean isHtml() { return false; }
+
+    public List<String> getInfo() {
+      List<String> info = super.getInfo();
+      info.add(getRawText());
+      return info;
+    }
+  }
+
+  /** Represents an acronym that was found in the input. */
+  public static class Acronym extends Token {
+    private String value;
+    // TODO: SWF
+
+    public Acronym(String text, String value) {
+      super(Type.ACRONYM, text);
+      this.value = value;
+    }
+
+    public String getValue() { return value; }
+
+    public boolean isHtml() { return false; }
+
+    public List<String> getInfo() {
+      List<String> info = super.getInfo();
+      info.add(getRawText());
+      info.add(getValue());
+      return info;
+    }
+  }
+
+  /** Represents a character that changes formatting. */
+  public static class Format extends Token {
+    private char ch;
+    private boolean start;
+    private boolean matched;
+
+    public Format(char ch, boolean start) {
+      super(Type.FORMAT, String.valueOf(ch));
+      this.ch = ch;
+      this.start = start;
+    }
+
+    public void setMatched(boolean matched) { this.matched = matched; }
+
+    public boolean isHtml() { return true; }
+
+    public String toHtml(boolean caps) {
+      // This character only implies special formatting if it was matched.
+      // Otherwise, it was just a plain old character.
+      if (matched) {
+        return start ? getFormatStart(ch) : getFormatEnd(ch);
+      } else {
+        // We have to make sure we escape HTML characters as usual.
+        return (ch == '"') ? "&quot;" : String.valueOf(ch);
+      }
+    }
+
+    /**
+     * Not supported. Info should not be needed for this type
+     */
+    public List<String> getInfo() {
+      throw new UnsupportedOperationException();
+    }
+
+    public boolean controlCaps() { return (ch == '^'); }
+    public boolean setCaps() { return start; }
+
+    private String getFormatStart(char ch) {
+      switch (ch) {
+        case '*': return "<b>";
+        case '_': return "<i>";
+        case '^': return "<b><font color=\"#005FFF\">"; // TODO: all caps
+        case '"': return "<font color=\"#999999\">\u201c";
+        default: throw new AssertionError("unknown format '" + ch + "'");
+      }
+    }
+
+    private String getFormatEnd(char ch) {
+      switch (ch) {
+        case '*': return "</b>";
+        case '_': return "</i>";
+        case '^': return "</font></b>"; // TODO: all caps
+        case '"': return "\u201d</font>";
+        default: throw new AssertionError("unknown format '" + ch + "'");
+      }
+    }
+  }
+
+  /** Adds the given token to the parsed output. */
+  private void addToken(Token token) {
+    tokens.add(token);
+  }
+
+  /** Converts the entire message into a single HTML display string. */
+  public String toHtml() {
+    StringBuilder html = new StringBuilder();
+
+    for (Part part : parts) {
+      boolean caps = false;
+
+      html.append("<p>");
+      for (Token token : part.getTokens()) {
+        if (token.isHtml()) {
+          html.append(token.toHtml(caps));
+        } else {
+          switch (token.getType()) {
+          case LINK:
+            html.append("<a href=\"");
+            html.append(((Link)token).getURL());
+            html.append("\">");
+            html.append(token.getRawText());
+            html.append("</a>");
+            break;
+
+          case SMILEY:
+            // TODO: link to an appropriate image
+            html.append(token.getRawText());
+            break;
+
+          case ACRONYM:
+            html.append(token.getRawText());
+            break;
+
+          case MUSIC:
+            // TODO: include a music glyph
+            html.append(((MusicTrack)token).getTrack());
+            break;
+
+          case GOOGLE_VIDEO:
+            // TODO: include a Google Video icon
+            html.append("<a href=\"");
+            html.append(((Video)token).getURL(((Video)token).getDocID()));
+            html.append("\">");
+            html.append(token.getRawText());
+            html.append("</a>");
+            break;
+
+          case YOUTUBE_VIDEO:
+            // TODO: include a YouTube icon
+            html.append("<a href=\"");
+            html.append(((YouTubeVideo)token).getURL(
+                ((YouTubeVideo)token).getDocID()));
+            html.append("\">");
+            html.append(token.getRawText());
+            html.append("</a>");
+            break;
+
+          case PHOTO: {
+            // TODO: include a Picasa Web icon
+            html.append("<a href=\"");
+            html.append(Photo.getAlbumURL(
+                ((Photo)token).getUser(), ((Photo)token).getAlbum()));
+            html.append("\">");
+            html.append(token.getRawText());
+            html.append("</a>");
+            break;
+          }
+
+          case FLICKR:
+            // TODO: include a Flickr icon
+            Photo p = (Photo) token;
+            html.append("<a href=\"");
+            html.append(((FlickrPhoto)token).getUrl());
+            html.append("\">");
+            html.append(token.getRawText());
+            html.append("</a>");
+            break;
+
+          default:
+            throw new AssertionError("unknown token type: " + token.getType());
+          }
+        }
+
+        if (token.controlCaps()) {
+          caps = token.setCaps();
+        }
+      }
+      html.append("</p>\n");
+    }
+
+    return html.toString();
+  }
+
+  /** Returns the reverse of the given string. */
+  protected static String reverse(String str) {
+    StringBuilder buf = new StringBuilder();
+    for (int i = str.length() - 1; i >= 0; --i) {
+      buf.append(str.charAt(i));
+    }
+    return buf.toString();
+  }
+
+  public static class TrieNode {
+    private final HashMap<Character,TrieNode> children =
+        new HashMap<Character,TrieNode>();
+    private String text;
+    private String value;
+
+    public TrieNode() { this(""); }
+    public TrieNode(String text) {
+      this.text = text;
+    }
+
+    public final boolean exists() { return value != null; }
+    public final String getText() { return text; }
+    public final String getValue() { return value; }
+    public void setValue(String value) { this.value = value; }
+
+    public TrieNode getChild(char ch) {
+      return children.get(Character.valueOf(ch));
+    }
+
+    public TrieNode getOrCreateChild(char ch) {
+      Character key = Character.valueOf(ch);
+      TrieNode node = children.get(key);
+      if (node == null) {
+        node = new TrieNode(text + String.valueOf(ch));
+        children.put(key, node);
+      }
+      return node;
+    }
+
+    /** Adds the given string into the trie. */
+    public static  void addToTrie(TrieNode root, String str, String value) {
+      int index = 0;
+      while (index < str.length()) {
+        root = root.getOrCreateChild(str.charAt(index++));
+      }
+      root.setValue(value);
+    }
+  }
+
+
+
+  /** Determines whether the given string is in the given trie. */
+  private static boolean matches(TrieNode root, String str) {
+    int index = 0;
+    while (index < str.length()) {
+      root = root.getChild(str.charAt(index++));
+      if (root == null) {
+        break;
+      } else if (root.exists()) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  /**
+   * Returns the longest substring of the given string, starting at the given
+   * index, that exists in the trie.
+   */
+  private static TrieNode longestMatch(
+      TrieNode root, AbstractMessageParser p, int start) {
+    return longestMatch(root, p, start, false);
+  }
+
+  /**
+   * Returns the longest substring of the given string, starting at the given
+   * index, that exists in the trie, with a special tokenizing case for
+   * smileys if specified.
+   */
+  private static TrieNode longestMatch(
+      TrieNode root, AbstractMessageParser p, int start, boolean smiley) {
+    int index = start;
+    TrieNode bestMatch = null;
+    while (index < p.getRawText().length()) {
+      root = root.getChild(p.getRawText().charAt(index++));
+      if (root == null) {
+        break;
+      } else if (root.exists()) {
+        if (p.isWordBreak(index)) {
+          bestMatch = root;
+        } else if (smiley && p.isSmileyBreak(index)) {
+          bestMatch = root;
+        }
+      }
+    }
+    return bestMatch;
+  }
+
+
+  /** Represents set of tokens that are delivered as a single message. */
+  public static class Part {
+    private String meText;
+    private ArrayList<Token> tokens;
+
+    public Part() {
+      this.tokens = new ArrayList<Token>();
+    }
+
+    public String getType(boolean isSend) {
+      return (isSend ? "s" : "r") + getPartType();
+    }
+
+    private String getPartType() {
+      if (isMedia()) {
+        return "d";
+      } else if (meText != null) {
+        return "m";
+      } else {
+        return "";
+      }
+    }
+
+    public boolean isMedia() {
+      return (tokens.size() == 1) && tokens.get(0).isMedia();
+    }
+    /**
+     * Convenience method for getting the Token of a Part that represents
+     * a media Token. Parts of this kind will always only have a single Token
+     *
+     * @return if this.isMedia(),
+     *         returns the Token representing the media contained in this Part,
+     *         otherwise returns null;
+     */
+    public Token getMediaToken() {
+      if(isMedia()) {
+        return tokens.get(0);
+      }
+      return null;
+    }
+
+    /** Adds the given token to this part. */
+    public void add(Token token) {
+      if (isMedia()) {
+        throw new AssertionError("media ");
+      }
+       tokens.add(token);
+    }
+
+    public void setMeText(String meText) {
+      this.meText = meText;
+    }
+
+    /** Returns the original text of this part. */
+    public String getRawText() {
+      StringBuilder buf = new StringBuilder();
+      if (meText != null) {
+        buf.append(meText);
+      }
+      for (int i = 0; i < tokens.size(); ++i) {
+        buf.append(tokens.get(i).getRawText());
+      }
+      return buf.toString();
+    }
+
+    /** Returns the tokens in this part. */
+    public ArrayList<Token> getTokens() { return tokens; }
+
+    /** Adds the tokens into the given builder as an array. */
+//    public void toArray(JSArrayBuilder array) {
+//      if (isMedia()) {
+//        // For media, we send its array (i.e., we don't wrap this in another
+//        // array as we do for non-media parts).
+//        tokens.get(0).toArray(array);
+//      } else {
+//        array.beginArray();
+//        addToArray(array);
+//        array.endArray();
+//      }
+//    }
+  }
+}
diff --git a/core/java/com/google/android/util/GoogleWebContentHelper.java b/core/java/com/google/android/util/GoogleWebContentHelper.java
new file mode 100644
index 0000000..2911420
--- /dev/null
+++ b/core/java/com/google/android/util/GoogleWebContentHelper.java
@@ -0,0 +1,270 @@
+/*
+ * 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.google.android.util;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.net.http.SslError;
+import android.os.Message;
+import android.provider.Settings;
+import android.text.TextUtils;
+import android.view.KeyEvent;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.webkit.HttpAuthHandler;
+import android.webkit.SslErrorHandler;
+import android.webkit.WebSettings;
+import android.webkit.WebView;
+import android.webkit.WebViewClient;
+import android.widget.TextView;
+
+import java.util.Locale;
+
+/**
+ * Helper to display Google web content, and fallback on a static message if the
+ * web content is unreachable. For example, this can be used to display
+ * "Legal terms".
+ * <p>
+ * The typical usage pattern is to have two Gservices settings defined:
+ * <ul>
+ * <li>A secure URL that will be displayed on the device. This should be HTTPS
+ * so hotspots won't intercept it giving us a false positive that the page
+ * loaded successfully.
+ * <li>A pretty human-readable URL that will be displayed to the user in case we
+ * cannot reach the above URL.
+ * </ul>
+ * <p>
+ * The typical call sequence is {@link #setUrlsFromGservices(String, String)},
+ * {@link #setUnsuccessfulMessage(String)}, and {@link #loadUrl()}. At some
+ * point, you'll want to display the layout via {@link #getLayout()}.
+ */
+public class GoogleWebContentHelper {
+    
+    private Context mContext;
+    
+    private String mSecureUrl;
+    private String mPrettyUrl;
+
+    private String mUnsuccessfulMessage;
+    
+    private ViewGroup mLayout;
+    private WebView mWebView;
+    private View mProgressBar;
+    private TextView mTextView;
+    
+    private boolean mReceivedResponse;
+    
+    public GoogleWebContentHelper(Context context) {
+        mContext = context;
+    }
+    
+    /**
+     * Fetches the URLs from Gservices.
+     * 
+     * @param secureSetting The setting key whose value contains the HTTPS URL.
+     * @param prettySetting The setting key whose value contains the pretty URL.
+     * @return This {@link GoogleWebContentHelper} so methods can be chained.
+     */
+    public GoogleWebContentHelper setUrlsFromGservices(String secureSetting, String prettySetting) {
+        ContentResolver contentResolver = mContext.getContentResolver();
+        mSecureUrl = fillUrl(Settings.Gservices.getString(contentResolver, secureSetting),
+                mContext);
+        mPrettyUrl = fillUrl(Settings.Gservices.getString(contentResolver, prettySetting), 
+                mContext);
+        return this;
+    }
+    
+    /**
+     * Fetch directly from provided urls.
+     * 
+     * @param secureUrl The HTTPS URL.
+     * @param prettyUrl The pretty URL.
+     * @return This {@link GoogleWebContentHelper} so methods can be chained.
+     */
+    public GoogleWebContentHelper setUrls(String secureUrl, String prettyUrl) {
+        mSecureUrl = fillUrl(secureUrl, mContext);
+        mPrettyUrl = fillUrl(prettyUrl, mContext);
+        return this;
+    }
+    
+
+    /**
+     * Sets the message that will be shown if we are unable to load the page.
+     * <p>
+     * This should be called after {@link #setUrlsFromGservices(String, String)}
+     * .
+     * 
+     * @param message The message to load. The first argument, according to
+     *        {@link java.util.Formatter}, will be substituted with the pretty
+     *        URL.
+     * @return This {@link GoogleWebContentHelper} so methods can be chained.
+     */
+    public GoogleWebContentHelper setUnsuccessfulMessage(String message) {
+        Locale locale = mContext.getResources().getConfiguration().locale;
+        mUnsuccessfulMessage = String.format(locale, message, mPrettyUrl);
+        return this;
+    }
+
+    /**
+     * Begins loading the secure URL.
+     * 
+     * @return This {@link GoogleWebContentHelper} so methods can be chained.
+     */
+    public GoogleWebContentHelper loadUrl() {
+        ensureViews();
+        mWebView.loadUrl(mSecureUrl);
+        return this;
+    }
+    
+    /**
+     * Helper to handle the back key. Returns true if the back key was handled, 
+     * otherwise returns false.
+     * @param event the key event sent to {@link Activity#dispatchKeyEvent()}
+     */
+    public boolean handleKey(KeyEvent event) {
+        if (event.getKeyCode() == KeyEvent.KEYCODE_BACK 
+                && event.getAction() == KeyEvent.ACTION_DOWN) {
+            if (mWebView.canGoBack()) {
+                mWebView.goBack();
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Returns the layout containing the web view, progress bar, and text view.
+     * This class takes care of setting each one's visibility based on current
+     * state.
+     * 
+     * @return The layout you should display.
+     */
+    public ViewGroup getLayout() {
+        ensureViews();
+        return mLayout;
+    }
+
+    private synchronized void ensureViews() {
+        if (mLayout == null) {
+            initializeViews();
+        }
+    }
+
+    /**
+     * Fills the URL with the locale.
+     * 
+     * @param url The URL in Formatter style for the extra info to be filled in.
+     * @return The filled URL.
+     */
+    private static String fillUrl(String url, Context context) {
+        
+        if (TextUtils.isEmpty(url)) {
+            return "";
+        }
+
+        /* We add another layer of indirection here to allow mcc's to fill
+         * in Locales for TOS.  TODO - REMOVE when needed locales supported
+         * natively (when not shipping devices to country X without support
+         * for their locale).
+         */
+        String localeReplacement = context.
+                getString(com.android.internal.R.string.locale_replacement);
+        if (localeReplacement != null && localeReplacement.length() != 0) {
+            url = String.format(url, localeReplacement);
+        }
+
+        Locale locale = Locale.getDefault();
+        String tmp = locale.getLanguage() + "_" + locale.getCountry().toLowerCase();
+        return String.format(url, tmp);
+    }
+    
+    private void initializeViews() {
+
+        LayoutInflater inflater =
+                (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+        
+        mLayout = (ViewGroup) inflater.inflate(
+                com.android.internal.R.layout.google_web_content_helper_layout, null);
+
+        mWebView = (WebView) mLayout.findViewById(com.android.internal.R.id.web);
+        mWebView.setWebViewClient(new MyWebViewClient());
+        WebSettings settings = mWebView.getSettings();
+        settings.setCacheMode(WebSettings.LOAD_NO_CACHE);
+        
+        mProgressBar = mLayout.findViewById(com.android.internal.R.id.progressContainer);
+        TextView message = (TextView) mProgressBar.findViewById(com.android.internal.R.id.message);
+        message.setText(com.android.internal.R.string.googlewebcontenthelper_loading);
+        
+        mTextView = (TextView) mLayout.findViewById(com.android.internal.R.id.text);
+        mTextView.setText(mUnsuccessfulMessage);
+    }
+
+    private synchronized void handleWebViewCompletion(boolean success) {
+        
+        if (mReceivedResponse) {
+            return;
+        } else {
+            mReceivedResponse = true;
+        }
+        
+        // In both cases, remove the progress bar
+        ((ViewGroup) mProgressBar.getParent()).removeView(mProgressBar);
+
+        // Remove the view that isn't relevant
+        View goneView = success ? mTextView : mWebView;
+        ((ViewGroup) goneView.getParent()).removeView(goneView);
+
+        // Show the next view, which depends on success
+        View visibleView = success ? mWebView : mTextView;
+        visibleView.setVisibility(View.VISIBLE);
+    }
+    
+    private class MyWebViewClient extends WebViewClient {
+
+        @Override
+        public void onPageFinished(WebView view, String url) {
+            handleWebViewCompletion(true);
+        }
+
+        @Override
+        public void onReceivedError(WebView view, int errorCode,
+                String description, String failingUrl) {
+            handleWebViewCompletion(false);
+        }
+
+        @Override
+        public void onReceivedHttpAuthRequest(WebView view,
+                HttpAuthHandler handler, String host, String realm) {
+            handleWebViewCompletion(false);
+        }
+
+        @Override
+        public void onReceivedSslError(WebView view, SslErrorHandler handler,
+                SslError error) {
+            handleWebViewCompletion(false);
+        }
+
+        @Override
+        public void onTooManyRedirects(WebView view, Message cancelMsg,
+                Message continueMsg) {
+            handleWebViewCompletion(false);
+        }
+        
+    }
+    
+}
diff --git a/core/java/com/google/android/util/Procedure.java b/core/java/com/google/android/util/Procedure.java
new file mode 100644
index 0000000..5ede2f0
--- /dev/null
+++ b/core/java/com/google/android/util/Procedure.java
@@ -0,0 +1,28 @@
+/*
+** Copyright 2007, 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.google.android.util;
+
+/**
+ * A procedure.
+ */
+public interface Procedure<T> {
+
+    /**
+     * Applies this procedure to the given parameter.
+     */
+    void apply(T t);
+}
diff --git a/core/java/com/google/android/util/SimplePullParser.java b/core/java/com/google/android/util/SimplePullParser.java
new file mode 100644
index 0000000..031790b
--- /dev/null
+++ b/core/java/com/google/android/util/SimplePullParser.java
@@ -0,0 +1,391 @@
+/*
+ * 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.google.android.util;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.StringReader;
+import java.io.Reader;
+import java.io.Closeable;
+
+import android.util.Xml;
+import android.util.Log;
+
+/**
+ * This is an abstraction of a pull parser that provides several benefits:<ul>
+ *   <li>it is easier to use robustly because it makes it trivial to handle unexpected tags (which
+ *   might have children)</li>
+ *   <li>it makes the handling of text (cdata) blocks more convenient</li>
+ *   <li>it provides convenient methods for getting a mandatory attribute (and throwing an exception
+ *   if it is missing) or an optional attribute (and using a default value if it is missing)
+ * </ul>
+ */
+public class SimplePullParser {
+    public static final String TEXT_TAG = "![CDATA[";
+
+    private String mLogTag = null;
+    private final XmlPullParser mParser;
+    private Closeable source;
+    private String mCurrentStartTag;
+
+    /**
+     * Constructs a new SimplePullParser to parse the stream
+     * @param stream stream to parse
+     * @param encoding the encoding to use
+     */
+    public SimplePullParser(InputStream stream, String encoding)
+            throws ParseException, IOException {
+        try {
+            XmlPullParser parser = Xml.newPullParser();
+            parser.setInput(stream, encoding);
+            moveToStartDocument(parser);
+            mParser = parser;
+            mCurrentStartTag = null;
+            source = stream;
+        } catch (XmlPullParserException e) {
+            throw new ParseException(e);
+        }
+    }
+
+    /**
+     * Constructs a new SimplePullParser to parse the xml
+     * @param parser the underlying parser to use
+     */
+    public SimplePullParser(XmlPullParser parser) {
+        mParser = parser;
+        mCurrentStartTag = null;
+        source = null;
+    }
+
+    /**
+     * Constructs a new SimplePullParser to parse the xml
+     * @param xml the xml to parse
+     */
+    public SimplePullParser(String xml) throws IOException, ParseException {
+        this(new StringReader(xml));
+    }
+
+    /**
+     * Constructs a new SimplePullParser to parse the xml
+     * @param reader a reader containing the xml
+     */
+    public SimplePullParser(Reader reader) throws IOException, ParseException {
+        try {
+            XmlPullParser parser = Xml.newPullParser();
+            parser.setInput(reader);
+            moveToStartDocument(parser);
+            mParser = parser;
+            mCurrentStartTag = null;
+            source = reader;
+        } catch (XmlPullParserException e) {
+            throw new ParseException(e);
+        }
+    }
+
+    private static void moveToStartDocument(XmlPullParser parser)
+            throws XmlPullParserException, IOException {
+        int eventType;
+        eventType = parser.getEventType();
+        if (eventType != XmlPullParser.START_DOCUMENT) {
+            throw new XmlPullParserException("Not at start of response");
+        }
+    }
+
+    /**
+     * Enables logging to the provided log tag. A basic representation of the xml will be logged as
+     * the xml is parsed. No logging is done unless this is called.
+     *
+     * @param logTag the log tag to use when logging
+     */
+    public void setLogTag(String logTag) {
+        mLogTag = logTag;
+    }
+
+    /**
+     * Returns the tag of the next element whose depth is parentDepth plus one
+     * or null if there are no more such elements before the next start tag. When this returns,
+     * getDepth() and all methods relating to attributes will refer to the element whose tag is
+     * returned.
+     *
+     * @param parentDepth the depth of the parrent of the item to be returned
+     * @param textBuilder if null then text blocks will be ignored. If
+     *   non-null then text blocks will be added to the builder and TEXT_TAG
+     *   will be returned when one is found
+     * @return the next of the next child element's tag, TEXT_TAG if a text block is found, or null
+     *   if there are no more child elements or DATA blocks
+     * @throws IOException propogated from the underlying parser
+     * @throws ParseException if there was an error parsing the xml.
+     */
+    public String nextTagOrText(int parentDepth, StringBuilder textBuilder)
+            throws IOException, ParseException {
+        while (true) {
+            int eventType = 0;
+            try {
+                eventType = mParser.next();
+            } catch (XmlPullParserException e) {
+                throw new ParseException(e);
+            }
+            int depth = mParser.getDepth();
+            mCurrentStartTag = null;
+
+            if (eventType == XmlPullParser.START_TAG && depth == parentDepth + 1) {
+                mCurrentStartTag = mParser.getName();
+                if (mLogTag != null && Log.isLoggable(mLogTag, Log.DEBUG)) {
+                    StringBuilder sb = new StringBuilder();
+                    for (int i = 0; i < depth; i++) sb.append("  ");
+                    sb.append("<").append(mParser.getName());
+                    int count = mParser.getAttributeCount();
+                    for (int i = 0; i < count; i++) {
+                        sb.append(" ");
+                        sb.append(mParser.getAttributeName(i));
+                        sb.append("=\"");
+                        sb.append(mParser.getAttributeValue(i));
+                        sb.append("\"");
+                    }
+                    sb.append(">");
+                    Log.d(mLogTag, sb.toString());
+                }
+                return mParser.getName();
+            }
+
+            if (eventType == XmlPullParser.END_TAG && depth == parentDepth) {
+                if (mLogTag != null && Log.isLoggable(mLogTag, Log.DEBUG)) {
+                    StringBuilder sb = new StringBuilder();
+                    for (int i = 0; i < depth; i++) sb.append("  ");
+                    sb.append("</>"); // Not quite valid xml but it gets the job done.
+                    Log.d(mLogTag, sb.toString());
+                }
+                return null;
+            }
+
+            if (eventType == XmlPullParser.END_DOCUMENT && parentDepth == 0) {
+                // we could just rely on the caller calling close(), which it should, but try
+                // to auto-close for clients that might have missed doing so.
+                if (source != null) {
+                    source.close();
+                    source = null;
+                }
+                return null;
+            }
+
+            if (eventType == XmlPullParser.TEXT && depth == parentDepth) {
+                if (textBuilder == null) {
+                    continue;
+                }
+                String text = mParser.getText();
+                textBuilder.append(text);
+                return TEXT_TAG;
+            }
+        }
+    }
+
+    /**
+     * The same as nextTagOrTexxt(int, StringBuilder) but ignores text blocks.
+     */
+    public String nextTag(int parentDepth) throws IOException, ParseException {
+        return nextTagOrText(parentDepth, null /* ignore text */);
+    }
+
+    /**
+     * Returns the depth of the current element. The depth is 0 before the first
+     * element has been returned, 1 after that, etc.
+     *
+     * @return the depth of the current element
+     */
+    public int getDepth() {
+        return mParser.getDepth();
+    }
+
+    /**
+     * Consumes the rest of the children, accumulating any text at this level into the builder.
+     *
+     * @param textBuilder the builder to contain any text
+     * @throws IOException propogated from the XmlPullParser
+     * @throws ParseException if there was an error parsing the xml.
+     */
+    public void readRemainingText(int parentDepth, StringBuilder textBuilder)
+            throws IOException, ParseException {
+        while (nextTagOrText(parentDepth, textBuilder) != null) {
+        }
+    }
+
+    /**
+     * Returns the number of attributes on the current element.
+     *
+     * @return the number of attributes on the current element
+     */
+    public int numAttributes() {
+        return mParser.getAttributeCount();
+    }
+
+    /**
+     * Returns the name of the nth attribute on the current element.
+     *
+     * @return the name of the nth attribute on the current element
+     */
+    public String getAttributeName(int i) {
+        return mParser.getAttributeName(i);
+    }
+
+    /**
+     * Returns the namespace of the nth attribute on the current element.
+     *
+     * @return the namespace of the nth attribute on the current element
+     */
+    public String getAttributeNamespace(int i) {
+        return mParser.getAttributeNamespace(i);
+    }
+
+    /**
+     * Returns the string value of the named attribute.
+     *
+     * @param namespace the namespace of the attribute
+     * @param name the name of the attribute
+     * @param defaultValue the value to return if the attribute is not specified
+     * @return the value of the attribute
+     */
+    public String getStringAttribute(
+            String namespace, String name, String defaultValue) {
+        String value = mParser.getAttributeValue(namespace, name);
+        if (null == value) return defaultValue;
+        return value;
+    }
+
+    /**
+     * Returns the string value of the named attribute. An exception will
+     * be thrown if the attribute is not present.
+     *
+     * @param namespace the namespace of the attribute
+     * @param name the name of the attribute @return the value of the attribute
+     * @throws ParseException thrown if the attribute is missing
+     */
+    public String getStringAttribute(String namespace, String name) throws ParseException {
+        String value = mParser.getAttributeValue(namespace, name);
+        if (null == value) {
+            throw new ParseException(
+                    "missing '" + name + "' attribute on '" + mCurrentStartTag + "' element");
+        }
+        return value;
+    }
+
+    /**
+     * Returns the string value of the named attribute. An exception will
+     * be thrown if the attribute is not a valid integer.
+     *
+     * @param namespace the namespace of the attribute
+     * @param name the name of the attribute
+     * @param defaultValue the value to return if the attribute is not specified
+     * @return the value of the attribute
+     * @throws ParseException thrown if the attribute not a valid integer.
+     */
+    public int getIntAttribute(String namespace, String name, int defaultValue)
+            throws ParseException {
+        String value = mParser.getAttributeValue(namespace, name);
+        if (null == value) return defaultValue;
+        try {
+            return Integer.parseInt(value);
+        } catch (NumberFormatException e) {
+            throw new ParseException("Cannot parse '" + value + "' as an integer");
+        }
+    }
+
+    /**
+     * Returns the string value of the named attribute. An exception will
+     * be thrown if the attribute is not present or is not a valid integer.
+     *
+     * @param namespace the namespace of the attribute
+     * @param name the name of the attribute @return the value of the attribute
+     * @throws ParseException thrown if the attribute is missing or not a valid integer.
+     */
+    public int getIntAttribute(String namespace, String name)
+            throws ParseException {
+        String value = getStringAttribute(namespace, name);
+        try {
+            return Integer.parseInt(value);
+        } catch (NumberFormatException e) {
+            throw new ParseException("Cannot parse '" + value + "' as an integer");
+        }
+    }
+
+    /**
+     * Returns the string value of the named attribute. An exception will
+     * be thrown if the attribute is not a valid long.
+     *
+     * @param namespace the namespace of the attribute
+     * @param name the name of the attribute @return the value of the attribute
+     * @throws ParseException thrown if the attribute is not a valid long.
+     */
+    public long getLongAttribute(String namespace, String name, long defaultValue)
+            throws ParseException {
+        String value = mParser.getAttributeValue(namespace, name);
+        if (null == value) return defaultValue;
+        try {
+            return Long.parseLong(value);
+        } catch (NumberFormatException e) {
+            throw new ParseException("Cannot parse '" + value + "' as a long");
+        }
+    }
+
+    /**
+     * Close this SimplePullParser and any underlying resources (e.g., its InputStream or
+     * Reader source) used by this SimplePullParser.
+     */
+    public void close() {
+        if (source != null) {
+            try {
+                source.close();
+            } catch (IOException ioe) {
+                // ignore
+            }
+        }
+    }
+
+    /**
+     * Returns the string value of the named attribute. An exception will
+     * be thrown if the attribute is not present or is not a valid long.
+     *
+     * @param namespace the namespace of the attribute
+     * @param name the name of the attribute @return the value of the attribute
+     * @throws ParseException thrown if the attribute is missing or not a valid long.
+     */
+    public long getLongAttribute(String namespace, String name)
+            throws ParseException {
+        String value = getStringAttribute(namespace, name);
+        try {
+            return Long.parseLong(value);
+        } catch (NumberFormatException e) {
+            throw new ParseException("Cannot parse '" + value + "' as a long");
+        }
+    }
+
+    public static final class ParseException extends Exception {
+        public ParseException(String message) {
+            super(message);
+        }
+
+        public ParseException(String message, Throwable cause) {
+            super(message, cause);
+        }
+
+        public ParseException(Throwable cause) {
+            super(cause);
+        }
+    }
+}
diff --git a/core/java/com/google/android/util/SmileyParser.java b/core/java/com/google/android/util/SmileyParser.java
new file mode 100644
index 0000000..ef5d2a9
--- /dev/null
+++ b/core/java/com/google/android/util/SmileyParser.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2007 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.google.android.util;
+
+import android.content.Context;
+import android.text.Spannable;
+import android.text.SpannableStringBuilder;
+import android.text.style.ImageSpan;
+
+import java.util.ArrayList;
+
+/**
+ * Parses a text message typed by the user looking for smileys.
+ */
+public class SmileyParser extends AbstractMessageParser {
+
+    private SmileyResources mRes;
+
+    public SmileyParser(String text, SmileyResources res) {
+        super(text,
+                true,   // smilies
+                false,  // acronyms
+                false,  // formatting
+                false,  // urls
+                false,  // music
+                false   // me text
+        );
+        mRes = res;
+    }
+
+    @Override
+    protected Resources getResources() {
+        return mRes;
+    }
+
+    /**
+     * Retrieves the parsed text as a spannable string object.
+     * @param context the context for fetching smiley resources.
+     * @return the spannable string as CharSequence.
+     */
+    public CharSequence getSpannableString(Context context) {
+        SpannableStringBuilder builder = new SpannableStringBuilder();
+
+        if (getPartCount() == 0) {
+            return "";
+        }
+
+        // should have only one part since we parse smiley only
+        Part part = getPart(0);
+        ArrayList<Token> tokens = part.getTokens();
+        int len = tokens.size();
+        for (int i = 0; i < len; i++) {
+            Token token = tokens.get(i);
+            int start = builder.length();
+            builder.append(token.getRawText());
+            if (token.getType() == AbstractMessageParser.Token.Type.SMILEY) {
+                int resid = mRes.getSmileyRes(token.getRawText());
+                if (resid != -1) {
+                    builder.setSpan(new ImageSpan(context, resid),
+                            start,
+                            builder.length(),
+                            Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+                }
+            }
+        }
+        return builder;
+    }
+
+}
diff --git a/core/java/com/google/android/util/SmileyResources.java b/core/java/com/google/android/util/SmileyResources.java
new file mode 100644
index 0000000..789158f
--- /dev/null
+++ b/core/java/com/google/android/util/SmileyResources.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2007 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.google.android.util;
+
+import com.google.android.util.AbstractMessageParser.TrieNode;
+
+import java.util.HashMap;
+import java.util.Set;
+
+/**
+ * Resources for smiley parser.
+ */
+public class SmileyResources implements AbstractMessageParser.Resources {
+    private HashMap<String, Integer> mSmileyToRes = new HashMap<String, Integer>();
+
+    /**
+     * 
+     * @param smilies Smiley text, e.g. ":)", "8-)"
+     * @param smileyResIds Resource IDs associated with the smileys.
+     */
+    public SmileyResources(String[] smilies, int[] smileyResIds) {
+        for (int i = 0; i < smilies.length; i++) {
+            TrieNode.addToTrie(smileys, smilies[i], "");
+            mSmileyToRes.put(smilies[i], smileyResIds[i]);
+        }
+    }
+
+    /**
+     * Looks up the resource id of a given smiley. 
+     * @param smiley The smiley to look up.
+     * @return the resource id of the specified smiley, or -1 if no resource
+     *         id is associated with it.  
+     */
+    public int getSmileyRes(String smiley) {
+        Integer i = mSmileyToRes.get(smiley);
+        if (i == null) {
+            return -1;
+        }
+        return i.intValue();
+    }
+
+    private final TrieNode smileys = new TrieNode();
+
+    public Set<String> getSchemes() {
+        return null;
+    }
+
+    public TrieNode getDomainSuffixes() {
+        return null;
+    }
+
+    public TrieNode getSmileys() {
+        return smileys;
+    }
+
+    public TrieNode getAcronyms() {
+        return null;
+    }
+
+}
diff --git a/core/java/jarjar-rules.txt b/core/java/jarjar-rules.txt
new file mode 100644
index 0000000..5fdb022
--- /dev/null
+++ b/core/java/jarjar-rules.txt
@@ -0,0 +1,2 @@
+rule org.apache.commons com.android.internal.apache.commons
+
diff --git a/core/java/overview.html b/core/java/overview.html
new file mode 100644
index 0000000..b3af0e0
--- /dev/null
+++ b/core/java/overview.html
@@ -0,0 +1,3 @@
+<body>
+    These are the Android APIs.
+</body>
diff --git a/core/jni/ActivityManager.cpp b/core/jni/ActivityManager.cpp
new file mode 100644
index 0000000..9017827
--- /dev/null
+++ b/core/jni/ActivityManager.cpp
@@ -0,0 +1,60 @@
+/*
+ * 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.
+ */
+
+#include <unistd.h>
+#include <android_runtime/ActivityManager.h>
+#include <utils/IBinder.h>
+#include <utils/IServiceManager.h>
+#include <utils/Parcel.h>
+#include <utils/String8.h>
+
+namespace android {
+
+const uint32_t OPEN_CONTENT_URI_TRANSACTION = IBinder::FIRST_CALL_TRANSACTION + 4;
+
+// Perform ContentProvider.openFile() on the given URI, returning
+// the resulting native file descriptor.  Returns < 0 on error.
+int openContentProviderFile(const String16& uri)
+{
+    int fd = -1;
+
+    sp<IServiceManager> sm = defaultServiceManager();
+    sp<IBinder> am = sm->getService(String16("activity"));
+    if (am != NULL) {
+        Parcel data, reply;
+        data.writeInterfaceToken(String16("android.app.IActivityManager"));
+        data.writeString16(uri);
+        status_t ret = am->transact(OPEN_CONTENT_URI_TRANSACTION, data, &reply);
+        if (ret == NO_ERROR) {
+            int32_t exceptionCode = reply.readInt32();
+            if (!exceptionCode) {
+                // Success is indicated here by a nonzero int followed by the fd;
+                // failure by a zero int with no data following.
+                if (reply.readInt32() != 0) {
+                    fd = dup(reply.readFileDescriptor());
+                }
+            } else {
+                // An exception was thrown back; fall through to return failure
+                LOGD("openContentUri(%s) caught exception %d\n",
+                        String8(uri).string(), exceptionCode);
+            }
+        }
+    }
+
+    return fd;
+}
+
+} /* namespace android */
diff --git a/core/jni/Android.mk b/core/jni/Android.mk
new file mode 100644
index 0000000..6e5c4e0
--- /dev/null
+++ b/core/jni/Android.mk
@@ -0,0 +1,194 @@
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_CFLAGS += -DHAVE_CONFIG_H -DKHTML_NO_EXCEPTIONS -DGKWQ_NO_JAVA
+LOCAL_CFLAGS += -DNO_SUPPORT_JS_BINDING -DQT_NO_WHEELEVENT -DKHTML_NO_XBL
+LOCAL_CFLAGS += -U__APPLE__
+
+ifeq ($(TARGET_ARCH), arm)
+	LOCAL_CFLAGS += -DPACKED="__attribute__ ((packed))"
+else
+	LOCAL_CFLAGS += -DPACKED=""
+endif
+
+ifneq ($(USE_CUSTOM_RUNTIME_HEAP_MAX),)
+  LOCAL_CFLAGS += -DCUSTOM_RUNTIME_HEAP_MAX=$(USE_CUSTOM_RUNTIME_HEAP_MAX)
+endif
+
+LOCAL_SRC_FILES:= \
+	ActivityManager.cpp \
+	AndroidRuntime.cpp \
+	CursorWindow.cpp \
+	com_google_android_gles_jni_EGLImpl.cpp \
+	com_google_android_gles_jni_GLImpl.cpp.arm \
+	android_database_CursorWindow.cpp \
+	android_database_SQLiteDebug.cpp \
+	android_database_SQLiteDatabase.cpp \
+	android_database_SQLiteProgram.cpp \
+	android_database_SQLiteQuery.cpp \
+	android_database_SQLiteStatement.cpp \
+	android_view_Display.cpp \
+	android_view_Surface.cpp \
+	android_view_ViewRoot.cpp \
+	android_text_AndroidCharacter.cpp \
+	android_text_KeyCharacterMap.cpp \
+	android_os_Debug.cpp \
+	android_os_Exec.cpp \
+	android_os_FileUtils.cpp \
+	android_os_MemoryFile.cpp \
+	android_os_ParcelFileDescriptor.cpp \
+	android_os_Power.cpp \
+	android_os_StatFs.cpp \
+	android_os_SystemClock.cpp \
+	android_os_SystemProperties.cpp \
+	android_os_UEventObserver.cpp \
+	android_os_Hardware.cpp \
+	android_net_LocalSocketImpl.cpp \
+	android_net_NetUtils.cpp \
+	android_net_wifi_Wifi.cpp \
+	android_nio_utils.cpp \
+	android_pim_EventRecurrence.cpp \
+	android_text_format_Time.cpp \
+	android_security_Md5MessageDigest.cpp \
+	android_util_AssetManager.cpp \
+	android_util_Binder.cpp \
+	android_util_EventLog.cpp \
+	android_util_Log.cpp \
+	android_util_FloatMath.cpp \
+	android_util_Process.cpp \
+	android_util_StringBlock.cpp \
+	android_util_XmlBlock.cpp \
+	android_util_Base64.cpp \
+	android/graphics/Bitmap.cpp \
+	android/graphics/BitmapFactory.cpp \
+	android/graphics/Camera.cpp \
+	android/graphics/Canvas.cpp \
+	android/graphics/ColorFilter.cpp \
+	android/graphics/DrawFilter.cpp \
+	android/graphics/CreateJavaOutputStreamAdaptor.cpp \
+	android/graphics/Graphics.cpp \
+	android/graphics/Interpolator.cpp \
+	android/graphics/LayerRasterizer.cpp \
+	android/graphics/MaskFilter.cpp \
+	android/graphics/Matrix.cpp \
+	android/graphics/Movie.cpp \
+	android/graphics/NIOBuffer.cpp \
+	android/graphics/NinePatch.cpp \
+	android/graphics/NinePatchImpl.cpp \
+	android/graphics/Paint.cpp \
+	android/graphics/Path.cpp \
+	android/graphics/PathMeasure.cpp \
+	android/graphics/PathEffect.cpp \
+	android_graphics_PixelFormat.cpp \
+	android/graphics/Picture.cpp \
+	android/graphics/PorterDuff.cpp \
+	android/graphics/Rasterizer.cpp \
+	android/graphics/Region.cpp \
+	android/graphics/Shader.cpp \
+	android/graphics/Typeface.cpp \
+	android/graphics/Xfermode.cpp \
+	android_media_AudioRecord.cpp \
+	android_media_AudioSystem.cpp \
+	android_media_AudioTrack.cpp \
+	android_media_JetPlayer.cpp \
+	android_media_ToneGenerator.cpp \
+	android_hardware_Camera.cpp \
+	android_hardware_SensorManager.cpp \
+	android_debug_JNITest.cpp \
+	android_util_FileObserver.cpp \
+	android/opengl/poly_clip.cpp.arm \
+	android/opengl/util.cpp.arm \
+	android_bluetooth_Database.cpp \
+	android_bluetooth_HeadsetBase.cpp \
+	android_bluetooth_common.cpp \
+	android_bluetooth_BluetoothAudioGateway.cpp \
+	android_bluetooth_RfcommSocket.cpp \
+	android_bluetooth_ScoSocket.cpp \
+	android_server_BluetoothDeviceService.cpp \
+	android_server_BluetoothEventLoop.cpp \
+	android_server_BluetoothA2dpService.cpp \
+	android_message_digest_sha1.cpp \
+	android_ddm_DdmHandleNativeHeap.cpp \
+	android_location_GpsLocationProvider.cpp \
+	com_android_internal_os_ZygoteInit.cpp \
+	com_android_internal_graphics_NativeUtils.cpp
+
+LOCAL_C_INCLUDES += \
+	$(JNI_H_INCLUDE) \
+	$(LOCAL_PATH)/android/graphics \
+	$(call include-path-for, bluedroid) \
+	$(call include-path-for, libhardware)/hardware \
+	$(call include-path-for, libhardware_legacy)/hardware_legacy \
+	$(LOCAL_PATH)/../../include/ui \
+	$(LOCAL_PATH)/../../include/utils \
+	external/skia/include/core \
+	external/skia/include/effects \
+	external/skia/include/images \
+	external/skia/include/utils \
+	external/sqlite/dist \
+	external/sqlite/android \
+	external/expat/lib \
+	external/openssl/include \
+	external/tremor/Tremor \
+	external/icu4c/i18n \
+	external/icu4c/common \
+
+LOCAL_SHARED_LIBRARIES := \
+	libexpat \
+	libnativehelper \
+	libcutils \
+	libutils \
+	libnetutils \
+	libui \
+	libskiagl \
+	libsgl \
+	libcorecg \
+	libsqlite \
+	libdvm \
+	libEGL \
+	libGLESv1_CM \
+	libhardware \
+	libhardware_legacy \
+	libsonivox \
+	libcrypto \
+	libssl \
+	libicuuc \
+	libicui18n \
+	libicudata \
+	libmedia \
+	libwpa_client
+
+ifeq ($(BOARD_HAVE_BLUETOOTH),true)
+LOCAL_C_INCLUDES += \
+	external/dbus \
+	external/bluez/libs/include
+LOCAL_CFLAGS += -DHAVE_BLUETOOTH
+LOCAL_SHARED_LIBRARIES += libbluedroid libdbus
+endif
+
+ifneq ($(TARGET_SIMULATOR),true)
+LOCAL_SHARED_LIBRARIES += \
+	libdl
+  # we need to access the private Bionic header
+  # <bionic_tls.h> in com_google_android_gles_jni_GLImpl.cpp
+  LOCAL_CFLAGS += -I$(LOCAL_PATH)/../../../../bionic/libc/private
+endif
+
+LOCAL_LDLIBS += -lpthread -ldl
+
+ifeq ($(TARGET_SIMULATOR),true)
+ifeq ($(TARGET_OS)-$(TARGET_ARCH),linux-x86)
+LOCAL_LDLIBS += -lrt
+endif
+endif
+
+ifeq ($(WITH_MALLOC_LEAK_CHECK),true)
+	LOCAL_CFLAGS += -DMALLOC_LEAK_CHECK
+endif
+
+LOCAL_MODULE:= libandroid_runtime
+
+include $(BUILD_SHARED_LIBRARY)
+
+
+include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
new file mode 100644
index 0000000..40dc2a1
--- /dev/null
+++ b/core/jni/AndroidRuntime.cpp
@@ -0,0 +1,1167 @@
+/* //device/libs/android_runtime/AndroidRuntime.cpp
+**
+** Copyright 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.
+*/
+
+#define LOG_TAG "AndroidRuntime"
+//#define LOG_NDEBUG 0
+
+#include <android_runtime/AndroidRuntime.h>
+#include <utils/IBinder.h>
+#include <utils/IServiceManager.h>
+#include <utils/Log.h>
+#include <utils/misc.h>
+#include <utils/Parcel.h>
+#include <utils/string_array.h>
+#include <utils/threads.h>
+#include <cutils/properties.h>
+
+#include <SkGraphics.h>
+#include <SkImageDecoder.h>
+
+#include "jni.h"
+#include "JNIHelp.h"
+#include "android_util_Binder.h"
+
+#include <stdio.h>
+#include <signal.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <signal.h>
+#include <dirent.h>
+#include <assert.h>
+
+
+using namespace android;
+
+extern void register_BindTest();
+
+extern int register_android_os_Binder(JNIEnv* env);
+extern int register_android_os_Process(JNIEnv* env);
+extern int register_android_graphics_Bitmap(JNIEnv*);
+extern int register_android_graphics_BitmapFactory(JNIEnv*);
+extern int register_android_graphics_Camera(JNIEnv* env);
+extern int register_android_graphics_Graphics(JNIEnv* env);
+extern int register_android_graphics_Interpolator(JNIEnv* env);
+extern int register_android_graphics_LayerRasterizer(JNIEnv*);
+extern int register_android_graphics_MaskFilter(JNIEnv* env);
+extern int register_android_graphics_Movie(JNIEnv* env);
+extern int register_android_graphics_NinePatch(JNIEnv*);
+extern int register_android_graphics_PathEffect(JNIEnv* env);
+extern int register_android_graphics_Region(JNIEnv* env);
+extern int register_android_graphics_Shader(JNIEnv* env);
+extern int register_android_graphics_Typeface(JNIEnv* env);
+
+extern int register_com_google_android_gles_jni_EGLImpl(JNIEnv* env);
+extern int register_com_google_android_gles_jni_GLImpl(JNIEnv* env);
+
+extern int register_android_hardware_Camera(JNIEnv *env);
+
+extern int register_android_hardware_SensorManager(JNIEnv *env);
+
+extern int register_android_media_AudioRecord(JNIEnv *env);
+extern int register_android_media_AudioSystem(JNIEnv *env);
+extern int register_android_media_AudioTrack(JNIEnv *env);
+extern int register_android_media_JetPlayer(JNIEnv *env);
+extern int register_android_media_ToneGenerator(JNIEnv *env);
+
+extern int register_android_message_digest_sha1(JNIEnv *env);
+
+extern int register_android_util_FloatMath(JNIEnv* env);
+
+namespace android {
+
+/*
+ * JNI-based registration functions.  Note these are properly contained in
+ * namespace android.
+ */
+extern int register_android_content_AssetManager(JNIEnv* env);
+extern int register_android_util_EventLog(JNIEnv* env);
+extern int register_android_util_Log(JNIEnv* env);
+extern int register_android_content_StringBlock(JNIEnv* env);
+extern int register_android_content_XmlBlock(JNIEnv* env);
+extern int register_android_graphics_Canvas(JNIEnv* env);
+extern int register_android_graphics_ColorFilter(JNIEnv* env);
+extern int register_android_graphics_DrawFilter(JNIEnv* env);
+extern int register_android_graphics_Matrix(JNIEnv* env);
+extern int register_android_graphics_Paint(JNIEnv* env);
+extern int register_android_graphics_Path(JNIEnv* env);
+extern int register_android_graphics_PathMeasure(JNIEnv* env);
+extern int register_android_graphics_Picture(JNIEnv*);
+extern int register_android_graphics_PorterDuff(JNIEnv* env);
+extern int register_android_graphics_Rasterizer(JNIEnv* env);
+extern int register_android_graphics_Xfermode(JNIEnv* env);
+extern int register_android_graphics_PixelFormat(JNIEnv* env);
+extern int register_com_android_internal_graphics_NativeUtils(JNIEnv *env);
+extern int register_android_view_Display(JNIEnv* env);
+extern int register_android_view_Surface(JNIEnv* env);
+extern int register_android_view_ViewRoot(JNIEnv* env);
+extern int register_android_database_CursorWindow(JNIEnv* env);
+extern int register_android_database_SQLiteDatabase(JNIEnv* env);
+extern int register_android_database_SQLiteDebug(JNIEnv* env);
+extern int register_android_database_SQLiteProgram(JNIEnv* env);
+extern int register_android_database_SQLiteQuery(JNIEnv* env);
+extern int register_android_database_SQLiteStatement(JNIEnv* env);
+extern int register_android_debug_JNITest(JNIEnv* env);
+extern int register_android_nio_utils(JNIEnv* env);
+extern int register_android_pim_EventRecurrence(JNIEnv* env);
+extern int register_android_text_format_Time(JNIEnv* env);
+extern int register_android_os_Debug(JNIEnv* env);
+extern int register_android_os_ParcelFileDescriptor(JNIEnv *env);
+extern int register_android_os_Power(JNIEnv *env);
+extern int register_android_os_StatFs(JNIEnv *env);
+extern int register_android_os_SystemProperties(JNIEnv *env);
+extern int register_android_os_Hardware(JNIEnv* env);
+extern int register_android_os_Exec(JNIEnv *env);
+extern int register_android_os_SystemClock(JNIEnv* env);
+extern int register_android_os_FileObserver(JNIEnv *env);
+extern int register_android_os_FileUtils(JNIEnv *env);
+extern int register_android_os_UEventObserver(JNIEnv* env);
+extern int register_android_os_MemoryFile(JNIEnv* env);
+extern int register_android_net_LocalSocketImpl(JNIEnv* env);
+extern int register_android_net_NetworkUtils(JNIEnv* env);
+extern int register_android_net_wifi_WifiManager(JNIEnv* env);
+extern int register_android_security_Md5MessageDigest(JNIEnv *env);
+extern int register_android_text_AndroidCharacter(JNIEnv *env);
+extern int register_android_text_KeyCharacterMap(JNIEnv *env);
+extern int register_android_opengl_classes(JNIEnv *env);
+extern int register_android_bluetooth_Database(JNIEnv* env);
+extern int register_android_bluetooth_HeadsetBase(JNIEnv* env);
+extern int register_android_bluetooth_BluetoothAudioGateway(JNIEnv* env);
+extern int register_android_bluetooth_RfcommSocket(JNIEnv *env);
+extern int register_android_bluetooth_ScoSocket(JNIEnv *env);
+extern int register_android_server_BluetoothDeviceService(JNIEnv* env);
+extern int register_android_server_BluetoothEventLoop(JNIEnv *env);
+extern int register_android_server_BluetoothA2dpService(JNIEnv* env);
+extern int register_android_ddm_DdmHandleNativeHeap(JNIEnv *env);
+extern int register_com_android_internal_os_ZygoteInit(JNIEnv* env);
+extern int register_android_util_Base64(JNIEnv* env);
+extern int register_android_location_GpsLocationProvider(JNIEnv* env);
+
+static AndroidRuntime* gCurRuntime = NULL;
+
+static void doThrow(JNIEnv* env, const char* exc, const char* msg = NULL)
+{
+    if (jniThrowException(env, exc, msg) != 0)
+        assert(false);
+}
+
+/*
+ * Code written in the Java Programming Language calls here from main().
+ */
+static void com_android_internal_os_RuntimeInit_finishInit(JNIEnv* env, jobject clazz)
+{
+    gCurRuntime->onStarted();
+}
+
+static void com_android_internal_os_RuntimeInit_zygoteInit(JNIEnv* env, jobject clazz)
+{
+    gCurRuntime->onZygoteInit();
+}
+
+static jint com_android_internal_os_RuntimeInit_isComputerOn(JNIEnv* env, jobject clazz)
+{
+    return 1;
+}
+
+static void com_android_internal_os_RuntimeInit_turnComputerOn(JNIEnv* env, jobject clazz)
+{
+}
+
+static jint com_android_internal_os_RuntimeInit_getQwertyKeyboard(JNIEnv* env, jobject clazz)
+{
+    char* value = getenv("qwerty");
+    if (value != NULL && strcmp(value, "true") == 0) {
+        return 1;
+    }
+    
+    return 0;
+}
+
+/*
+ * JNI registration.
+ */
+static JNINativeMethod gMethods[] = {
+    { "finishInit", "()V",
+        (void*) com_android_internal_os_RuntimeInit_finishInit },
+    { "zygoteInitNative", "()V",
+        (void*) com_android_internal_os_RuntimeInit_zygoteInit },
+    { "isComputerOn", "()I",
+        (void*) com_android_internal_os_RuntimeInit_isComputerOn },
+    { "turnComputerOn", "()V",
+        (void*) com_android_internal_os_RuntimeInit_turnComputerOn },    
+    { "getQwertyKeyboard", "()I",
+        (void*) com_android_internal_os_RuntimeInit_getQwertyKeyboard },
+};
+
+int register_com_android_internal_os_RuntimeInit(JNIEnv* env)
+{
+    return jniRegisterNativeMethods(env, "com/android/internal/os/RuntimeInit",
+        gMethods, NELEM(gMethods));
+}
+
+// ----------------------------------------------------------------------
+
+/*static*/ JavaVM* AndroidRuntime::mJavaVM = NULL;
+
+
+AndroidRuntime::AndroidRuntime()
+{
+    SkGraphics::Init(false);    // true means run unittests (slow)
+    // this sets our preference for 16bit images during decode
+    // in case the src is opaque and 24bit
+    SkImageDecoder::SetDeviceConfig(SkBitmap::kRGB_565_Config);
+
+    // Pre-allocate enough space to hold a fair number of options.
+    mOptions.setCapacity(20);
+
+    assert(gCurRuntime == NULL);        // one per process
+    gCurRuntime = this;
+}
+
+AndroidRuntime::~AndroidRuntime()
+{
+    SkGraphics::Term();
+}
+
+/*
+ * Register native methods using JNI.
+ */
+/*static*/ int AndroidRuntime::registerNativeMethods(JNIEnv* env,
+    const char* className, const JNINativeMethod* gMethods, int numMethods)
+{
+    return jniRegisterNativeMethods(env, className, gMethods, numMethods);
+}
+
+/*
+ * Call a static Java Programming Language function that takes no arguments and returns void.
+ */
+status_t AndroidRuntime::callStatic(const char* className, const char* methodName)
+{
+    JNIEnv* env;
+    jclass clazz;
+    jmethodID methodId;
+
+    env = getJNIEnv();
+    if (env == NULL)
+        return UNKNOWN_ERROR;
+
+    clazz = findClass(env, className);
+    if (clazz == NULL) {
+        LOGE("ERROR: could not find class '%s'\n", className);
+        return UNKNOWN_ERROR;
+    }
+    methodId = env->GetStaticMethodID(clazz, methodName, "()V");
+    if (methodId == NULL) {
+        LOGE("ERROR: could not find method %s.%s\n", className, methodName);
+        return UNKNOWN_ERROR;
+    }
+
+    env->CallStaticVoidMethod(clazz, methodId);
+
+    return NO_ERROR;
+}
+
+status_t AndroidRuntime::callMain(
+    const char* className, int argc, const char* const argv[])
+{
+    JNIEnv* env;
+    jclass clazz;
+    jmethodID methodId;
+
+    env = getJNIEnv();
+    if (env == NULL)
+        return UNKNOWN_ERROR;
+
+    clazz = findClass(env, className);
+    if (clazz == NULL) {
+        LOGE("ERROR: could not find class '%s'\n", className);
+        return UNKNOWN_ERROR;
+    }
+
+    methodId = env->GetStaticMethodID(clazz, "main", "([Ljava/lang/String;)V");
+    if (methodId == NULL) {
+        LOGE("ERROR: could not find method %s.main(String[])\n", className);
+        return UNKNOWN_ERROR;
+    }
+
+    /*
+     * We want to call main() with a String array with our arguments in it.
+     * Create an array and populate it.
+     */
+    jclass stringClass;
+    jobjectArray strArray;
+
+    stringClass = env->FindClass("java/lang/String");
+    strArray = env->NewObjectArray(argc, stringClass, NULL);
+
+    for (int i = 0; i < argc; i++) {
+        jstring argStr = env->NewStringUTF(argv[i]);
+        env->SetObjectArrayElement(strArray, i, argStr);
+    }
+
+    env->CallStaticVoidMethod(clazz, methodId, strArray);
+    return NO_ERROR;
+}
+
+/*
+ * Find the named class.
+ */
+jclass AndroidRuntime::findClass(JNIEnv* env, const char* className)
+{
+    char* convName = NULL;
+
+    if (env->ExceptionCheck()) {
+        LOGE("ERROR: exception pending on entry to findClass()\n");
+        return NULL;
+    }
+
+    /*
+     * JNI FindClass uses class names with slashes, but ClassLoader.loadClass
+     * uses the dotted "binary name" format.  We don't need to convert the
+     * name with the new approach.
+     */
+#if 0
+    /* (convName only created if necessary -- use className) */
+    for (char* cp = const_cast<char*>(className); *cp != '\0'; cp++) {
+        if (*cp == '.') {
+            if (convName == NULL) {
+                convName = strdup(className);
+                cp = convName + (cp-className);
+                className = convName;
+            }
+            *cp = '/';
+        }
+    }
+#endif
+
+    /*
+     * This is a little awkward because the JNI FindClass call uses the
+     * class loader associated with the native method we're executing in.
+     * Because this native method is part of a "boot" class, JNI doesn't
+     * look for the class in CLASSPATH, which unfortunately is a likely
+     * location for it.  (Had we issued the FindClass call before calling
+     * into the VM -- at which point there isn't a native method frame on
+     * the stack -- the VM would have checked CLASSPATH.  We have to do
+     * this because we call into Java Programming Language code and
+     * bounce back out.)
+     *
+     * JNI lacks a "find class in a specific class loader" operation, so we
+     * have to do things the hard way.
+     */
+    jclass cls = NULL;
+    //cls = env->FindClass(className);
+
+    jclass javaLangClassLoader;
+    jmethodID getSystemClassLoader, loadClass;
+    jobject systemClassLoader;
+    jstring strClassName;
+
+    /* find the "system" class loader; none of this is expected to fail */
+    javaLangClassLoader = env->FindClass("java/lang/ClassLoader");
+    assert(javaLangClassLoader != NULL);
+    getSystemClassLoader = env->GetStaticMethodID(javaLangClassLoader,
+        "getSystemClassLoader", "()Ljava/lang/ClassLoader;");
+    loadClass = env->GetMethodID(javaLangClassLoader,
+        "loadClass", "(Ljava/lang/String;)Ljava/lang/Class;");
+    assert(getSystemClassLoader != NULL && loadClass != NULL);
+    systemClassLoader = env->CallStaticObjectMethod(javaLangClassLoader,
+        getSystemClassLoader);
+    assert(systemClassLoader != NULL);
+
+    /* create an object for the class name string; alloc could fail */
+    strClassName = env->NewStringUTF(className);
+    if (env->ExceptionCheck()) {
+        LOGE("ERROR: unable to convert '%s' to string\n", className);
+        goto bail;
+    }
+    LOGV("system class loader is %p, loading %p (%s)\n",
+        systemClassLoader, strClassName, className);
+
+    /* try to find the named class */
+    cls = (jclass) env->CallObjectMethod(systemClassLoader, loadClass,
+                        strClassName);
+    if (env->ExceptionCheck()) {
+        LOGE("ERROR: unable to load class '%s' from %p\n",
+            className, systemClassLoader);
+        cls = NULL;
+        goto bail;
+    }
+
+bail:
+    free(convName);
+    return cls;
+}
+
+/*
+ * The VM calls this through the "exit" hook.
+ */
+static void runtime_exit(int code)
+{
+    gCurRuntime->onExit(code);
+    exit(code);
+}
+
+/*
+ * The VM calls this through the "vfprintf" hook.
+ *
+ * We ignore "fp" and just write the results to the log file.
+ */
+static void runtime_vfprintf(FILE* fp, const char* format, va_list ap)
+{
+    LOG_PRI_VA(ANDROID_LOG_INFO, "vm-printf", format, ap);
+}
+
+
+/**
+ * Add VM arguments to the to-be-executed VM
+ * Stops at first non '-' argument (also stops at an argument of '--')
+ * Returns the number of args consumed
+ */
+int AndroidRuntime::addVmArguments(int argc, const char* const argv[])
+{
+    int i;
+    
+    for (i = 0; i<argc; i++) {
+        if (argv[i][0] != '-') {
+            return i;
+        }
+        if (argv[i][1] == '-' && argv[i][2] == 0) {
+            return i+1;
+        }
+
+        JavaVMOption opt;
+        memset(&opt, 0, sizeof(opt));
+        opt.optionString = (char*)argv[i];
+        mOptions.add(opt);
+    }
+    return i;
+}
+
+static int hasDir(const char* dir)
+{
+    struct stat s;
+    int res = stat(dir, &s);
+    if (res == 0) {
+        return S_ISDIR(s.st_mode);
+    }
+    return 0;
+}
+
+/*
+ * We just want failed write() calls to just return with an error.
+ */
+static void blockSigpipe()
+{
+    sigset_t mask;
+
+    sigemptyset(&mask);
+    sigaddset(&mask, SIGPIPE);
+    if (sigprocmask(SIG_BLOCK, &mask, NULL) != 0)
+        LOGW("WARNING: SIGPIPE not blocked\n");
+}
+
+/*
+ * Read the persistent locale.
+ */
+static void readLocale(char* language, char* region)
+{
+    char propLang[PROPERTY_VALUE_MAX], propRegn[PROPERTY_VALUE_MAX];
+    
+    property_get("persist.sys.language", propLang, "");
+    property_get("persist.sys.country", propRegn, "");
+    if (*propLang == 0 && *propRegn == 0) {
+        /* Set to ro properties, default is en_US */
+        property_get("ro.product.locale.language", propLang, "en");
+        property_get("ro.product.locale.region", propRegn, "US");
+    }
+    strncat(language, propLang, 2);
+    strncat(region, propRegn, 2);
+    //LOGD("language=%s region=%s\n", language, region);
+}
+
+void AndroidRuntime::start(const char* className, const bool startSystemServer)
+{
+    LOGD("\n>>>>>>>>>>>>>> AndroidRuntime START <<<<<<<<<<<<<<\n");
+
+    JNIEnv* env;
+    JavaVMInitArgs initArgs;
+    JavaVMOption opt;
+    char propBuf[PROPERTY_VALUE_MAX];
+    char stackTraceFileBuf[PROPERTY_VALUE_MAX];
+    char dexoptFlagsBuf[PROPERTY_VALUE_MAX];
+    char enableAssertBuf[sizeof("-ea:")-1 + PROPERTY_VALUE_MAX];
+    char jniOptsBuf[sizeof("-Xjniopts:")-1 + PROPERTY_VALUE_MAX];
+    char* stackTraceFile = NULL;
+    char* slashClassName = NULL;
+    char* cp;
+    bool checkJni = false;
+    bool logStdio = false;
+    enum { kEMDefault, kEMIntPortable, kEMIntFast } executionMode = kEMDefault;
+
+    blockSigpipe();
+
+    /* 
+     * 'startSystemServer == true' means runtime is obslete and not run from 
+     * init.rc anymore, so we print out the boot start event here.
+     */
+    if (startSystemServer) {
+        /* track our progress through the boot sequence */
+        const int LOG_BOOT_PROGRESS_START = 3000;
+        LOG_EVENT_LONG(LOG_BOOT_PROGRESS_START, 
+                       ns2ms(systemTime(SYSTEM_TIME_MONOTONIC)));
+    }
+
+    property_get("dalvik.vm.checkjni", propBuf, "");
+    if (strcmp(propBuf, "true") == 0) {
+        checkJni = true;
+    } else if (strcmp(propBuf, "false") != 0) {
+        /* property is neither true nor false; fall back on kernel parameter */
+        property_get("ro.kernel.android.checkjni", propBuf, "");
+        if (propBuf[0] == '1') {
+            checkJni = true;
+        }
+    }
+
+    property_get("dalvik.vm.execution-mode", propBuf, "");
+    if (strcmp(propBuf, "int:portable") == 0) {
+        executionMode = kEMIntPortable;
+    } else if (strcmp(propBuf, "int:fast") == 0) {
+        executionMode = kEMIntFast;
+    }
+
+    property_get("dalvik.vm.stack-trace-file", stackTraceFileBuf, "");
+
+    property_get("log.redirect-stdio", propBuf, "");
+    if (strcmp(propBuf, "true") == 0) {
+        logStdio = true;
+    }
+
+    strcpy(enableAssertBuf, "-ea:");
+    property_get("dalvik.vm.enableassertions", enableAssertBuf+4, "");
+
+    strcpy(jniOptsBuf, "-Xjniopts:");
+    property_get("dalvik.vm.jniopts", jniOptsBuf+10, "");
+
+    const char* rootDir = getenv("ANDROID_ROOT");
+    if (rootDir == NULL) {
+        rootDir = "/system";
+        if (!hasDir("/system")) {
+            LOG_FATAL("No root directory specified, and /android does not exist.");
+            return;
+        }
+        setenv("ANDROID_ROOT", rootDir, 1);
+    }
+
+    const char* kernelHack = getenv("LD_ASSUME_KERNEL");
+    //LOGD("Found LD_ASSUME_KERNEL='%s'\n", kernelHack);
+
+    /* route exit() to our handler */
+    opt.extraInfo = (void*) runtime_exit;
+    opt.optionString = "exit";
+    mOptions.add(opt);
+
+    /* route fprintf() to our handler */
+    opt.extraInfo = (void*) runtime_vfprintf;
+    opt.optionString = "vfprintf";
+    mOptions.add(opt);
+
+    opt.extraInfo = NULL;
+
+    /* enable verbose; standard options are { jni, gc, class } */
+    //options[curOpt++].optionString = "-verbose:jni";
+    opt.optionString = "-verbose:gc";
+    mOptions.add(opt);
+    //options[curOpt++].optionString = "-verbose:class";
+
+#ifdef CUSTOM_RUNTIME_HEAP_MAX
+#define __make_max_heap_opt(val) #val
+#define _make_max_heap_opt(val) "-Xmx" __make_max_heap_opt(val)
+    opt.optionString = _make_max_heap_opt(CUSTOM_RUNTIME_HEAP_MAX);
+#undef __make_max_heap_opt
+#undef _make_max_heap_opt
+#else
+    /* limit memory use to 16MB */
+    opt.optionString = "-Xmx16m";
+#endif
+    mOptions.add(opt);
+
+    /*
+     * Enable or disable dexopt features, such as bytecode verification and
+     * calculation of register maps for precise GC.
+     */
+    property_get("dalvik.vm.dexopt-flags", dexoptFlagsBuf, "");
+    if (dexoptFlagsBuf[0] != '\0') {
+        const char* opc;
+        const char* val;
+
+        opc = strstr(dexoptFlagsBuf, "v=");     /* verification */
+        if (opc != NULL) {
+            switch (*(opc+2)) {
+            case 'n':   val = "-Xverify:none";      break;
+            case 'r':   val = "-Xverify:remote";    break;
+            case 'a':   val = "-Xverify:all";       break;
+            default:    val = NULL;                 break;
+            }
+
+            if (val != NULL) {
+                opt.optionString = val;
+                mOptions.add(opt);
+            }
+        }
+
+        opc = strstr(dexoptFlagsBuf, "o=");     /* optimization */
+        if (opc != NULL) {
+            switch (*(opc+2)) {
+            case 'n':   val = "-Xdexopt:none";      break;
+            case 'v':   val = "-Xdexopt:verified";  break;
+            case 'a':   val = "-Xdexopt:all";       break;
+            default:    val = NULL;                 break;
+            }
+
+            if (val != NULL) {
+                opt.optionString = val;
+                mOptions.add(opt);
+            }
+        }
+
+        opc = strstr(dexoptFlagsBuf, "m=y");    /* register map */
+        if (opc != NULL) {
+            opt.optionString = "-Xgenregmap";
+            mOptions.add(opt);
+        }
+    }
+
+    /* enable debugging; set suspend=y to pause during VM init */
+#ifdef HAVE_ANDROID_OS
+    /* use android ADB transport */
+    opt.optionString =
+        "-agentlib:jdwp=transport=dt_android_adb,suspend=n,server=y";
+#else
+    /* use TCP socket; address=0 means start at port 8000 and probe up */
+    LOGI("Using TCP socket for JDWP\n");
+    opt.optionString =
+        "-agentlib:jdwp=transport=dt_socket,suspend=n,server=y,address=0";
+#endif
+    mOptions.add(opt);
+
+    char enableDPBuf[sizeof("-Xdeadlockpredict:") + PROPERTY_VALUE_MAX];
+    property_get("dalvik.vm.deadlock-predict", propBuf, "");
+    if (strlen(propBuf) > 0) {
+        strcpy(enableDPBuf, "-Xdeadlockpredict:");
+        strcat(enableDPBuf, propBuf);
+        opt.optionString = enableDPBuf;
+        mOptions.add(opt);
+    }
+
+    LOGD("CheckJNI is %s\n", checkJni ? "ON" : "OFF");
+    if (checkJni) {
+        /* extended JNI checking */
+        opt.optionString = "-Xcheck:jni";
+        mOptions.add(opt);
+
+        /* set a cap on JNI global references */
+        opt.optionString = "-Xjnigreflimit:2000";
+        mOptions.add(opt);
+
+        /* with -Xcheck:jni, this provides a JNI function call trace */
+        //opt.optionString = "-verbose:jni";
+        //mOptions.add(opt);
+    }
+    if (executionMode == kEMIntPortable) {
+        opt.optionString = "-Xint:portable";
+        mOptions.add(opt);
+    } else if (executionMode == kEMIntFast) {
+        opt.optionString = "-Xint:fast";
+        mOptions.add(opt);
+    }
+    if (logStdio) {
+        /* convert stdout/stderr to log messages */
+        opt.optionString = "-Xlog-stdio";
+        mOptions.add(opt);
+    }
+
+    if (enableAssertBuf[4] != '\0') {
+        /* accept "all" to mean "all classes and packages" */
+        if (strcmp(enableAssertBuf+4, "all") == 0)
+            enableAssertBuf[3] = '\0';
+        LOGI("Assertions enabled: '%s'\n", enableAssertBuf);
+        opt.optionString = enableAssertBuf;
+        mOptions.add(opt);
+    } else {
+        LOGV("Assertions disabled\n");
+    }
+
+    if (jniOptsBuf[10] != '\0') {
+        LOGI("JNI options: '%s'\n", jniOptsBuf);
+        opt.optionString = jniOptsBuf;
+        mOptions.add(opt);
+    }
+
+    if (stackTraceFileBuf[0] != '\0') {
+        static const char* stfOptName = "-Xstacktracefile:";
+
+        stackTraceFile = (char*) malloc(strlen(stfOptName) +
+            strlen(stackTraceFileBuf) +1);
+        strcpy(stackTraceFile, stfOptName);
+        strcat(stackTraceFile, stackTraceFileBuf);
+        opt.optionString = stackTraceFile;
+        mOptions.add(opt);
+    }
+    
+    /* Set the properties for locale */
+    {
+        char langOption[sizeof("-Duser.language=") + 3];
+        char regionOption[sizeof("-Duser.region=") + 3];
+        strcpy(langOption, "-Duser.language=");
+        strcpy(regionOption, "-Duser.region=");
+        readLocale(langOption, regionOption);
+        opt.extraInfo = NULL;
+        opt.optionString = langOption;
+        mOptions.add(opt);
+        opt.optionString = regionOption;
+        mOptions.add(opt);
+    }
+
+    /*
+     * We don't have /tmp on the device, but we often have an SD card.  Apps
+     * shouldn't use this, but some test suites might want to exercise it.
+     */
+    opt.optionString = "-Djava.io.tmpdir=/sdcard";
+    mOptions.add(opt);
+
+    initArgs.version = JNI_VERSION_1_4;
+    initArgs.options = mOptions.editArray();
+    initArgs.nOptions = mOptions.size();
+    initArgs.ignoreUnrecognized = JNI_FALSE;
+
+    /*
+     * Initialize the VM.
+     *
+     * The JavaVM* is essentially per-process, and the JNIEnv* is per-thread.
+     * If this call succeeds, the VM is ready, and we can start issuing
+     * JNI calls.
+     */
+    if (JNI_CreateJavaVM(&mJavaVM, &env, &initArgs) < 0) {
+        LOGE("JNI_CreateJavaVM failed\n");
+        goto bail;
+    }
+
+    /*
+     * Register android functions.
+     */
+    if (startReg(env) < 0) {
+        LOGE("Unable to register all android natives\n");
+        goto bail;
+    }
+
+    /*
+     * We want to call main() with a String array with arguments in it.
+     * At present we only have one argument, the class name.  Create an
+     * array to hold it.
+     */
+    jclass stringClass;
+    jobjectArray strArray;
+    jstring classNameStr;
+    jstring startSystemServerStr;
+
+    stringClass = env->FindClass("java/lang/String");
+    assert(stringClass != NULL);
+    strArray = env->NewObjectArray(2, stringClass, NULL);
+    assert(strArray != NULL);
+    classNameStr = env->NewStringUTF(className);
+    assert(classNameStr != NULL);
+    env->SetObjectArrayElement(strArray, 0, classNameStr);
+    startSystemServerStr = env->NewStringUTF(startSystemServer ? 
+                                                 "true" : "false");
+    env->SetObjectArrayElement(strArray, 1, startSystemServerStr);
+
+    /*
+     * Start VM.  This thread becomes the main thread of the VM, and will
+     * not return until the VM exits.
+     */
+    jclass startClass;
+    jmethodID startMeth;
+
+    slashClassName = strdup(className);
+    for (cp = slashClassName; *cp != '\0'; cp++)
+        if (*cp == '.')
+            *cp = '/';
+
+    startClass = env->FindClass(slashClassName);
+    if (startClass == NULL) {
+        LOGE("JavaVM unable to locate class '%s'\n", slashClassName);
+        /* keep going */
+    } else {
+        startMeth = env->GetStaticMethodID(startClass, "main",
+            "([Ljava/lang/String;)V");
+        if (startMeth == NULL) {
+            LOGE("JavaVM unable to find main() in '%s'\n", className);
+            /* keep going */
+        } else {
+            env->CallStaticVoidMethod(startClass, startMeth, strArray);
+
+#if 0
+            if (env->ExceptionCheck())
+                threadExitUncaughtException(env);
+#endif
+        }
+    }
+
+    LOGD("Shutting down VM\n");
+    if (mJavaVM->DetachCurrentThread() != JNI_OK)
+        LOGW("Warning: unable to detach main thread\n");
+    if (mJavaVM->DestroyJavaVM() != 0)
+        LOGW("Warning: VM did not shut down cleanly\n");
+
+bail:
+    free(slashClassName);
+    free(stackTraceFile);
+}
+
+void AndroidRuntime::start()
+{
+    start("com.android.internal.os.RuntimeInit",
+        false /* Don't start the system server */);
+}
+
+void AndroidRuntime::onExit(int code)
+{
+    LOGI("AndroidRuntime onExit calling exit(%d)", code);
+    exit(code);
+}
+
+/*
+ * Get the JNIEnv pointer for this thread.
+ *
+ * Returns NULL if the slot wasn't allocated or populated.
+ */
+/*static*/ JNIEnv* AndroidRuntime::getJNIEnv()
+{
+    JNIEnv* env;
+    JavaVM* vm = AndroidRuntime::getJavaVM();
+    assert(vm != NULL);
+
+    if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK)
+        return NULL;
+    return env;
+}
+
+/*
+ * Makes the current thread visible to the VM.
+ *
+ * The JNIEnv pointer returned is only valid for the current thread, and
+ * thus must be tucked into thread-local storage.
+ */
+static int javaAttachThread(const char* threadName, JNIEnv** pEnv)
+{
+    JavaVMAttachArgs args;
+    JavaVM* vm;
+    jint result;
+
+    vm = AndroidRuntime::getJavaVM();
+    assert(vm != NULL);
+
+    args.version = JNI_VERSION_1_4;
+    args.name = (char*) threadName;
+    args.group = NULL;
+
+    result = vm->AttachCurrentThread(pEnv, (void*) &args);
+    if (result != JNI_OK)
+        LOGE("ERROR: thread attach failed\n");
+
+    return result;
+}
+
+/*
+ * Detach the current thread from the set visible to the VM.
+ */
+static int javaDetachThread(void)
+{
+    JavaVM* vm;
+    jint result;
+
+    vm = AndroidRuntime::getJavaVM();
+    assert(vm != NULL);
+
+    result = vm->DetachCurrentThread();
+    if (result != JNI_OK)
+        LOGE("ERROR: thread detach failed\n");
+    return result;
+}
+
+/*
+ * When starting a native thread that will be visible from the VM, we
+ * bounce through this to get the right attach/detach action.
+ * Note that this function calls free(args)
+ */
+/*static*/ int AndroidRuntime::javaThreadShell(void* args) {
+    void* start = ((void**)args)[0];
+    void* userData = ((void **)args)[1];
+    char* name = (char*) ((void **)args)[2];        // we own this storage
+    free(args);
+    JNIEnv* env;
+    int result;
+
+    /* hook us into the VM */
+    if (javaAttachThread(name, &env) != JNI_OK)
+        return -1;
+
+    /* start the thread running */
+    result = (*(android_thread_func_t)start)(userData);
+
+    /* unhook us */
+    javaDetachThread();
+    free(name);
+
+    return result;
+}
+
+/*
+ * This is invoked from androidCreateThreadEtc() via the callback
+ * set with androidSetCreateThreadFunc().
+ *
+ * We need to create the new thread in such a way that it gets hooked
+ * into the VM before it really starts executing.
+ */
+/*static*/ int AndroidRuntime::javaCreateThreadEtc(
+                                android_thread_func_t entryFunction, 
+                                void* userData,
+                                const char* threadName,
+                                int32_t threadPriority,
+                                size_t threadStackSize,
+                                android_thread_id_t* threadId)
+{
+    void** args = (void**) malloc(3 * sizeof(void*));   // javaThreadShell must free
+    int result;
+
+    assert(threadName != NULL);
+
+    args[0] = (void*) entryFunction;
+    args[1] = userData;
+    args[2] = (void*) strdup(threadName);   // javaThreadShell must free
+
+    result = androidCreateRawThreadEtc(AndroidRuntime::javaThreadShell, args,
+        threadName, threadPriority, threadStackSize, threadId);
+    return result;
+}
+
+/*
+ * Create a thread that is visible from the VM.
+ *
+ * This is called from elsewhere in the library.
+ */
+/*static*/ void AndroidRuntime::createJavaThread(const char* name,
+    void (*start)(void *), void* arg)
+{
+    javaCreateThreadEtc((android_thread_func_t) start, arg, name,
+        ANDROID_PRIORITY_DEFAULT, 0, NULL);
+}
+
+#if 0
+static void quickTest(void* arg)
+{
+    const char* str = (const char*) arg;
+
+    printf("In quickTest: %s\n", str);
+}
+#endif
+
+#ifdef NDEBUG
+    #define REG_JNI(name)      { name }
+    struct RegJNIRec {
+        int (*mProc)(JNIEnv*);
+    };
+#else
+    #define REG_JNI(name)      { name, #name }
+    struct RegJNIRec {
+        int (*mProc)(JNIEnv*);
+        const char* mName;
+    };
+#endif
+
+typedef void (*RegJAMProc)();
+
+static int register_jni_procs(const RegJNIRec array[], size_t count, JNIEnv* env)
+{
+    for (size_t i = 0; i < count; i++) {
+        if (array[i].mProc(env) < 0) {
+#ifndef NDEBUG
+            LOGD("----------!!! %s failed to load\n", array[i].mName);
+#endif
+            return -1;
+        }
+    }
+    return 0;
+}
+
+static void register_jam_procs(const RegJAMProc array[], size_t count)
+{
+    for (size_t i = 0; i < count; i++) {
+        array[i]();
+    }
+}
+
+static const RegJNIRec gRegJNI[] = {
+    REG_JNI(register_android_debug_JNITest),
+    REG_JNI(register_com_android_internal_os_RuntimeInit),
+    REG_JNI(register_android_os_SystemClock),
+    REG_JNI(register_android_util_EventLog),
+    REG_JNI(register_android_util_Log),
+    REG_JNI(register_android_util_FloatMath),
+    REG_JNI(register_android_text_format_Time),
+    REG_JNI(register_android_pim_EventRecurrence),
+    REG_JNI(register_android_content_AssetManager),
+    REG_JNI(register_android_content_StringBlock),
+    REG_JNI(register_android_content_XmlBlock),
+    REG_JNI(register_android_security_Md5MessageDigest),
+    REG_JNI(register_android_text_AndroidCharacter),
+    REG_JNI(register_android_text_KeyCharacterMap),
+    REG_JNI(register_android_os_Process),
+    REG_JNI(register_android_os_Binder),
+    REG_JNI(register_android_os_Hardware),
+    REG_JNI(register_android_view_Display),
+    REG_JNI(register_android_nio_utils),
+    REG_JNI(register_android_graphics_PixelFormat),
+    REG_JNI(register_android_graphics_Graphics),
+    REG_JNI(register_android_view_Surface),
+    REG_JNI(register_android_view_ViewRoot),
+    REG_JNI(register_com_google_android_gles_jni_EGLImpl),
+    REG_JNI(register_com_google_android_gles_jni_GLImpl),
+
+    REG_JNI(register_android_graphics_Bitmap),
+    REG_JNI(register_android_graphics_BitmapFactory),
+    REG_JNI(register_android_graphics_Camera),
+    REG_JNI(register_android_graphics_Canvas),
+    REG_JNI(register_android_graphics_ColorFilter),
+    REG_JNI(register_android_graphics_DrawFilter),
+    REG_JNI(register_android_graphics_Interpolator),
+    REG_JNI(register_android_graphics_LayerRasterizer),
+    REG_JNI(register_android_graphics_MaskFilter),
+    REG_JNI(register_android_graphics_Matrix),
+    REG_JNI(register_android_graphics_Movie),
+    REG_JNI(register_android_graphics_NinePatch),
+    REG_JNI(register_android_graphics_Paint),
+    REG_JNI(register_android_graphics_Path),
+    REG_JNI(register_android_graphics_PathMeasure),
+    REG_JNI(register_android_graphics_PathEffect),
+    REG_JNI(register_android_graphics_Picture),
+    REG_JNI(register_android_graphics_PorterDuff),
+    REG_JNI(register_android_graphics_Rasterizer),
+    REG_JNI(register_android_graphics_Region),
+    REG_JNI(register_android_graphics_Shader),
+    REG_JNI(register_android_graphics_Typeface),
+    REG_JNI(register_android_graphics_Xfermode),
+    REG_JNI(register_com_android_internal_graphics_NativeUtils),
+
+    REG_JNI(register_android_database_CursorWindow),
+    REG_JNI(register_android_database_SQLiteDatabase),
+    REG_JNI(register_android_database_SQLiteDebug),
+    REG_JNI(register_android_database_SQLiteProgram),
+    REG_JNI(register_android_database_SQLiteQuery),
+    REG_JNI(register_android_database_SQLiteStatement),
+    REG_JNI(register_android_os_Debug),
+    REG_JNI(register_android_os_Exec),
+    REG_JNI(register_android_os_FileObserver),
+    REG_JNI(register_android_os_FileUtils),
+    REG_JNI(register_android_os_ParcelFileDescriptor),
+    REG_JNI(register_android_os_Power),
+    REG_JNI(register_android_os_StatFs),
+    REG_JNI(register_android_os_SystemProperties),
+    REG_JNI(register_android_os_UEventObserver),
+    REG_JNI(register_android_net_LocalSocketImpl),
+    REG_JNI(register_android_net_NetworkUtils),
+    REG_JNI(register_android_net_wifi_WifiManager),
+    REG_JNI(register_android_os_MemoryFile),
+    REG_JNI(register_com_android_internal_os_ZygoteInit),
+    REG_JNI(register_android_hardware_Camera),
+    REG_JNI(register_android_hardware_SensorManager),
+    REG_JNI(register_android_media_AudioRecord),
+    REG_JNI(register_android_media_AudioSystem),
+    REG_JNI(register_android_media_AudioTrack),
+    REG_JNI(register_android_media_JetPlayer),
+    REG_JNI(register_android_media_ToneGenerator),
+
+    REG_JNI(register_android_opengl_classes),
+    REG_JNI(register_android_bluetooth_Database),
+    REG_JNI(register_android_bluetooth_HeadsetBase),
+    REG_JNI(register_android_bluetooth_BluetoothAudioGateway),
+    REG_JNI(register_android_bluetooth_RfcommSocket),
+    REG_JNI(register_android_bluetooth_ScoSocket),
+    REG_JNI(register_android_server_BluetoothDeviceService),
+    REG_JNI(register_android_server_BluetoothEventLoop),
+    REG_JNI(register_android_server_BluetoothA2dpService),
+    REG_JNI(register_android_message_digest_sha1),
+    REG_JNI(register_android_ddm_DdmHandleNativeHeap),
+    REG_JNI(register_android_util_Base64),
+    REG_JNI(register_android_location_GpsLocationProvider),
+};
+
+/*
+ * Register android native functions with the VM.
+ */
+/*static*/ int AndroidRuntime::startReg(JNIEnv* env)
+{
+    /*
+     * This hook causes all future threads created in this process to be
+     * attached to the JavaVM.  (This needs to go away in favor of JNI
+     * Attach calls.)
+     */
+    androidSetCreateThreadFunc((android_create_thread_fn) javaCreateThreadEtc);
+
+    LOGD("--- registering native functions ---\n");
+
+    /*
+     * Every "register" function calls one or more things that return
+     * a local reference (e.g. FindClass).  Because we haven't really
+     * started the VM yet, they're all getting stored in the base frame
+     * and never released.  Use Push/Pop to manage the storage.
+     */
+    env->PushLocalFrame(200);
+
+    if (register_jni_procs(gRegJNI, NELEM(gRegJNI), env) < 0) {
+        env->PopLocalFrame(NULL);
+        return -1;
+    }
+    env->PopLocalFrame(NULL);
+
+    //createJavaThread("fubar", quickTest, (void*) "hello");
+
+    return 0;
+}
+
+AndroidRuntime* AndroidRuntime::getRuntime()
+{
+    return gCurRuntime;
+}
+
+/**
+ * Used by WithFramework to register native functions.
+ */
+extern "C"
+jint Java_com_android_internal_util_WithFramework_registerNatives(
+        JNIEnv* env, jclass clazz) {
+    return register_jni_procs(gRegJNI, NELEM(gRegJNI), env);
+}
+
+/**
+ * Used by LoadClass to register native functions.
+ */
+extern "C"
+jint Java_LoadClass_registerNatives(JNIEnv* env, jclass clazz) {
+    return register_jni_procs(gRegJNI, NELEM(gRegJNI), env);
+}
+
+}   // namespace android
diff --git a/core/jni/BindTest.cpp b/core/jni/BindTest.cpp
new file mode 100644
index 0000000..5fae400
--- /dev/null
+++ b/core/jni/BindTest.cpp
@@ -0,0 +1,289 @@
+/* //device/libs/android_runtime/BindTest.cpp
+**
+** Copyright 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.
+*/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <jam-public.h>
+
+static u4 offset_instanceString;
+static FieldBlock *fb_classString = NULL;
+static Class *class_ReturnedObject = NULL;
+static MethodBlock *mb_ReturnedObject_setReturnedString = NULL;
+static MethodBlock *mb_Java_Lang_Object_Equals = NULL;
+
+static u4 offset_mObj;
+static u4 offset_mBool;
+static u4 offset_mInt;
+static u4 offset_mString;
+static u4 offset_mDouble;
+static u4 offset_mLong;
+
+
+/* native String getString(); */
+static uintptr_t *
+getString(Class *clazz, MethodBlock *mb, uintptr_t *ostack)
+{
+    RETURN_OBJ (createString ("String"));
+}
+
+/* native String getNullString(); */
+static uintptr_t *
+getNullString(Class *clazz, MethodBlock *mb, uintptr_t *ostack)
+{
+    RETURN_OBJ (createString (NULL));
+}
+
+/* native String getBooleanTrue(); */
+static uintptr_t *
+getBooleanTrue(Class *clazz, MethodBlock *mb, uintptr_t *ostack)
+{
+    RETURN_BOOLEAN (TRUE);
+}
+
+/* native String getBooleanFalse(); */
+static uintptr_t *
+getBooleanFalse(Class *clazz, MethodBlock *mb, uintptr_t *ostack)
+{
+    RETURN_BOOLEAN (FALSE);
+}
+
+/* native Object nonvoidThrowsException() */
+static uintptr_t *
+nonvoidThrowsException (Class *clazz, MethodBlock *mb, uintptr_t *ostack)
+{
+    if (1) {
+        signalException("java/lang/NullPointerException", NULL);
+        goto exception;
+    } 
+    
+    RETURN_OBJ (NULL);
+exception:
+    RETURN_VOID;
+}
+
+/* native void setInstanceString(String s); */
+static uintptr_t *
+setInstanceString (Class *clazz, MethodBlock *mb, uintptr_t *ostack)
+{
+    Object *jthis = (Object *) ostack[0];
+    
+    JOBJ_set_obj(jthis, offset_instanceString, ostack[1]);
+
+    RETURN_VOID;
+}
+
+/* native void setClassString(String s) */
+static uintptr_t *
+setClassString (Class *clazz, MethodBlock *mb, uintptr_t *ostack)
+{
+//    Object *jthis = (Object *) ostack[0];
+
+    fb_classString->static_value = ostack[1];
+
+    RETURN_VOID;
+}
+
+/* native String makeStringFromThreeChars(char a, char b, char c); */
+static uintptr_t *
+makeStringFromThreeChars (Class *clazz, MethodBlock *mb, uintptr_t *ostack)
+{
+    // Object *jthis =  ostack[0];
+    char a = (char) ostack[1];
+    char b = (char) ostack[2];
+    char c = (char) ostack[3];
+
+    char str[4];
+
+    str[0] = a;
+    str[1] = b;
+    str[2] = c;
+    str[3] = 0;
+    
+    RETURN_OBJ(createString(str));
+}
+
+/* native ReturnedObject makeReturnedObject(String a); */
+static uintptr_t *
+makeReturnedObject (Class *clazz, MethodBlock *mb, uintptr_t *ostack)
+{
+    //Object *jthis = (Object*)ostack[0];
+    Object *a = (Object*)ostack[1];
+
+    Object *ret;
+
+    ret = allocObject(class_ReturnedObject);
+
+    executeMethod(ret, mb_ReturnedObject_setReturnedString, a);
+    
+    RETURN_OBJ (ret);
+}
+
+/* native double addDoubles(double a, double b); */
+static uintptr_t *
+addDoubles (Class *clazz, MethodBlock *mb, uintptr_t *ostack)
+{
+    //Object *jthis = (Object*)ostack[0];        
+    double a = JARG_get_double(1);
+    double b = JARG_get_double(3);
+
+    RETURN_DOUBLE(a+b);
+}
+
+/* native void setAll (Object obj, boolean bool, int i, String str, double d, long l) */
+static uintptr_t *
+setAll  (Class *clazz, MethodBlock *mb, uintptr_t *ostack)
+{
+    Object *jthis = JARG_get_obj(0);
+
+    Object *obj     = JARG_get_obj(1);
+    bool b          = JARG_get_bool(2);
+    int i           = JARG_get_int(3);
+    char *str       = JARG_get_cstr_strdup(4);
+    double d        = JARG_get_double(5);
+    long long ll    = JARG_get_long_long(5+2);
+
+    JOBJ_set_obj(jthis, offset_mObj, obj);
+    JOBJ_set_bool(jthis, offset_mBool, b);
+    JOBJ_set_int(jthis, offset_mInt, i);
+    JOBJ_set_cstr(jthis, offset_mString, str);
+    free(str);
+    str = NULL;
+    JOBJ_set_double(jthis, offset_mDouble, d);
+    JOBJ_set_long_long(jthis, offset_mLong, ll);
+
+    RETURN_VOID;
+}
+
+/* native void compareAll (Object obj, boolean bool, int i, String str, double d, long l) */
+static uintptr_t *
+compareAll  (Class *clazz, MethodBlock *mb, uintptr_t *ostack)
+{
+    Object *jthis = JARG_get_obj(0);
+
+    Object *obj     = JARG_get_obj(1);
+    bool b          = JARG_get_bool(2);
+    int i           = JARG_get_int(3);
+    Object *strObj  = JARG_get_obj(4);
+    double d        = JARG_get_double(5);
+    long long ll    = JARG_get_long_long(5+2);
+
+    bool ret;
+
+    void *result;
+
+    Object *mStringObj = JOBJ_get_obj(jthis, offset_mString);
+
+    char *s = JARG_get_cstr_strdup(4);
+    
+    result = executeMethod (strObj, lookupVirtualMethod(strObj,mb_Java_Lang_Object_Equals), 
+                JOBJ_get_obj(jthis, offset_mString));
+
+    if (exceptionOccurred()) {
+        RETURN_VOID;
+    }
+
+    ret =   (*(uintptr_t *)result != 0)
+            && (obj == JOBJ_get_obj(jthis, offset_mObj))
+            && (b == JOBJ_get_bool(jthis, offset_mBool))
+            && (i == JOBJ_get_int(jthis, offset_mInt))
+            && (d == JOBJ_get_double(jthis, offset_mDouble))
+            && (ll == JOBJ_get_long_long(jthis, offset_mLong));
+
+
+    RETURN_BOOLEAN(ret);
+}
+
+static VMMethod methods[] = {
+    {"getString", getString},
+    {"getNullString", getNullString},
+    {"getBooleanTrue", getBooleanTrue},
+    {"getBooleanFalse", getBooleanFalse},
+    {"nonvoidThrowsException", nonvoidThrowsException},
+    {"setInstanceString",     setInstanceString},
+    {"setClassString",     setClassString},
+    {"makeStringFromThreeChars", makeStringFromThreeChars}, 
+    {"makeReturnedObject", makeReturnedObject},
+    {"addDoubles", addDoubles},
+    {"setAll", setAll},
+    {"compareAll", compareAll},
+    {NULL, NULL}
+};
+
+
+void register_BindTest()
+{
+    jamvm_registerClass("BindTest", methods);
+
+    Class *clazz = NULL;
+
+    clazz = findClassFromClassLoader("BindTest", getSystemClassLoader());
+
+    if (clazz == NULL) {
+        fprintf(stderr, "Error: BindTest not found\n");
+	clearException();
+        return;
+    }
+    
+    FieldBlock *fb;
+
+    fb = findField(clazz, "instanceString", "Ljava/lang/String;");
+
+    if (fb == NULL || ((fb->access_flags & ACC_STATIC) == ACC_STATIC)) {
+        fprintf(stderr, "Error: BindTest.instanceString not found or error\n");        
+        return;
+    }  
+
+    offset_instanceString = fb->offset;
+
+    fb_classString = findField(clazz, "classString", "Ljava/lang/String;");
+
+    if (fb_classString == NULL || ((fb_classString->access_flags & ACC_STATIC) != ACC_STATIC)) {
+        fprintf(stderr, "Error: BindTest.classString not found or error\n");        
+        return;
+    }  
+
+
+    class_ReturnedObject = findClassFromClassLoader("ReturnedObject", getSystemClassLoader());
+
+    if (class_ReturnedObject == NULL) {
+        fprintf(stderr, "Error: ReturnedObject class not found or error\n");        
+        return;
+    }
+    
+    mb_ReturnedObject_setReturnedString=
+           findMethod (class_ReturnedObject, "setReturnedString", "(Ljava/lang/String;)V");
+
+    if (mb_ReturnedObject_setReturnedString == NULL) {
+        fprintf(stderr, "Error: ReturnedObject.setReturnedString class not found or error\n");        
+        return;
+    }
+
+    offset_mObj = findField(clazz, "mObj", "Ljava/lang/Object;")->offset;
+    offset_mBool = findField(clazz, "mBool", "Z" )->offset;
+    offset_mInt = findField(clazz, "mInt", "I")->offset;
+    offset_mString = findField(clazz, "mString", "Ljava/lang/String;")->offset;
+    offset_mDouble = findField(clazz, "mDouble", "D")->offset;
+    offset_mLong = findField(clazz, "mLong", "J")->offset;
+
+
+    mb_Java_Lang_Object_Equals = findMethod (
+                                    findClassFromClassLoader("java/lang/Object", getSystemClassLoader()),
+                                    "equals", "(Ljava/lang/Object;)Z");
+
+}
+
+
diff --git a/core/jni/CursorWindow.cpp b/core/jni/CursorWindow.cpp
new file mode 100644
index 0000000..fb891c9
--- /dev/null
+++ b/core/jni/CursorWindow.cpp
@@ -0,0 +1,412 @@
+/*
+ * Copyright (C) 2006-2007 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.
+ */
+
+#undef LOG_TAG
+#define LOG_TAG "CursorWindow"
+
+#include <utils/Log.h>
+#include <utils/MemoryDealer.h>
+
+#include <assert.h>
+#include <string.h>
+#include <stdlib.h>
+
+#include <jni.h>
+#include <JNIHelp.h>
+
+#include "CursorWindow.h"
+
+
+namespace android {
+
+CursorWindow::CursorWindow(size_t maxSize) :
+    mMaxSize(maxSize)
+{
+}
+
+bool CursorWindow::setMemory(sp<IMemory> memory)     
+{
+    mMemory = memory;
+    mData = (uint8_t *) memory->pointer();
+    if (mData == NULL) {
+        return false;
+    }
+    mHeader = (window_header_t *) mData;
+
+    // Make the window read-only
+    mHeap = NULL;
+    ssize_t size = memory->size();
+    mSize = size;
+    mMaxSize = size;
+    mFreeOffset = size;
+LOG_WINDOW("Created CursorWindow from existing IMemory: mFreeOffset = %d, numRows = %d, numColumns = %d, mSize = %d, mMaxSize = %d, mData = %p", mFreeOffset, mHeader->numRows, mHeader->numColumns, mSize, mMaxSize, mData);
+    return true;
+}
+
+bool CursorWindow::initBuffer(bool localOnly)
+{
+    //TODO Use a non-memory dealer mmap region for localOnly
+
+    mHeap = new MemoryDealer(new SharedHeap(mMaxSize, 0, "CursorWindow"));
+    if (mHeap != NULL) {
+        mMemory = mHeap->allocate(mMaxSize);
+        if (mMemory != NULL) {
+            mData = (uint8_t *) mMemory->pointer();
+            if (mData) {
+                mHeader = (window_header_t *) mData;
+                mSize = mMaxSize;
+
+                // Put the window into a clean state
+                clear();
+            LOG_WINDOW("Created CursorWindow with new MemoryDealer: mFreeOffset = %d, mSize = %d, mMaxSize = %d, mData = %p", mFreeOffset, mSize, mMaxSize, mData);
+                return true;                
+            }
+        } 
+        LOGE("memory dealer allocation failed");
+        return false;
+    } else {
+        LOGE("failed to create the memory dealer");
+        return false;
+    }
+}
+
+CursorWindow::~CursorWindow()
+{
+    // Everything that matters is a smart pointer
+}
+
+void CursorWindow::clear()
+{
+    mHeader->numRows = 0;
+    mHeader->numColumns = 0;
+    mFreeOffset = sizeof(window_header_t) + ROW_SLOT_CHUNK_SIZE;
+    // Mark the first chunk's next 'pointer' as null
+    *((uint32_t *)(mData + mFreeOffset - sizeof(uint32_t))) = 0;
+}
+
+int32_t CursorWindow::freeSpace()
+{
+    int32_t freeSpace = mSize - mFreeOffset;
+    if (freeSpace < 0) {
+        freeSpace = 0;
+    }
+    return freeSpace;
+}
+
+field_slot_t * CursorWindow::allocRow()
+{
+    // Fill in the row slot
+    row_slot_t * rowSlot = allocRowSlot();
+    if (rowSlot == NULL) {
+        return NULL;
+    }
+
+    // Allocate the slots for the field directory
+    size_t fieldDirSize = mHeader->numColumns * sizeof(field_slot_t);
+    uint32_t fieldDirOffset = alloc(fieldDirSize);
+    if (!fieldDirOffset) {
+        mHeader->numRows--;
+        LOGE("The row failed, so back out the new row accounting from allocRowSlot %d", mHeader->numRows);
+        return NULL;
+    }
+    field_slot_t * fieldDir = (field_slot_t *)offsetToPtr(fieldDirOffset);
+    memset(fieldDir, 0x0, fieldDirSize);
+
+LOG_WINDOW("Allocated row %u, rowSlot is at offset %u, fieldDir is %d bytes at offset %u\n", (mHeader->numRows - 1), ((uint8_t *)rowSlot) - mData, fieldDirSize, fieldDirOffset);
+    rowSlot->offset = fieldDirOffset;
+
+    return fieldDir;
+}
+
+uint32_t CursorWindow::alloc(size_t requestedSize, bool aligned)
+{
+    int32_t size;
+    uint32_t padding;
+    if (aligned) {
+        // 4 byte alignment
+        padding = 4 - (mFreeOffset & 0x3);
+    } else {
+        padding = 0;
+    }
+
+    size = requestedSize + padding;
+
+    if (size > freeSpace()) {
+        LOGE("need to grow: mSize = %d, size = %d, freeSpace() = %d, numRows = %d", mSize, size, freeSpace(), mHeader->numRows);
+        // Only grow the window if the first row doesn't fit
+        if (mHeader->numRows > 1) {
+LOGE("not growing since there are already %d row(s), max size %d", mHeader->numRows, mMaxSize);
+            return 0;
+        }
+
+        // Find a new size that will fit the allocation
+        int allocated = mSize - freeSpace();
+        int newSize = mSize + WINDOW_ALLOCATION_SIZE;
+        while (size > (newSize - allocated)) {
+            newSize += WINDOW_ALLOCATION_SIZE;
+            if (newSize > mMaxSize) {
+                LOGE("Attempting to grow window beyond max size (%d)", mMaxSize);
+                return 0;
+            }
+        }
+LOG_WINDOW("found size %d", newSize);
+        mSize = newSize;
+    }
+
+    uint32_t offset = mFreeOffset + padding;
+    mFreeOffset += size;
+    return offset;
+}
+
+row_slot_t * CursorWindow::getRowSlot(int row)
+{
+    LOG_WINDOW("enter getRowSlot current row num %d, this row %d", mHeader->numRows, row);
+    int chunkNum = row / ROW_SLOT_CHUNK_NUM_ROWS;
+    int chunkPos = row % ROW_SLOT_CHUNK_NUM_ROWS;
+    int chunkPtrOffset = sizeof(window_header_t) + ROW_SLOT_CHUNK_SIZE - sizeof(uint32_t);
+    uint8_t * rowChunk = mData + sizeof(window_header_t);
+    for (int i = 0; i < chunkNum; i++) {
+        rowChunk = offsetToPtr(*((uint32_t *)(mData + chunkPtrOffset)));
+        chunkPtrOffset = rowChunk - mData + (ROW_SLOT_CHUNK_NUM_ROWS * sizeof(row_slot_t));
+    }
+    return (row_slot_t *)(rowChunk + (chunkPos * sizeof(row_slot_t)));
+    LOG_WINDOW("exit getRowSlot current row num %d, this row %d", mHeader->numRows, row);    
+}
+
+row_slot_t * CursorWindow::allocRowSlot()
+{
+    int chunkNum = mHeader->numRows / ROW_SLOT_CHUNK_NUM_ROWS;
+    int chunkPos = mHeader->numRows % ROW_SLOT_CHUNK_NUM_ROWS;
+    int chunkPtrOffset = sizeof(window_header_t) + ROW_SLOT_CHUNK_SIZE - sizeof(uint32_t);
+    uint8_t * rowChunk = mData + sizeof(window_header_t);
+LOG_WINDOW("Allocating row slot, mHeader->numRows is %d, chunkNum is %d, chunkPos is %d", mHeader->numRows, chunkNum, chunkPos);
+    for (int i = 0; i < chunkNum; i++) {
+        uint32_t nextChunkOffset = *((uint32_t *)(mData + chunkPtrOffset));
+LOG_WINDOW("nextChunkOffset is %d", nextChunkOffset);
+        if (nextChunkOffset == 0) {
+            // Allocate a new row chunk
+            nextChunkOffset = alloc(ROW_SLOT_CHUNK_SIZE, true);
+            if (nextChunkOffset == 0) {
+                return NULL;
+            }
+            rowChunk = offsetToPtr(nextChunkOffset);
+LOG_WINDOW("allocated new chunk at %d, rowChunk = %p", nextChunkOffset, rowChunk);
+            *((uint32_t *)(mData + chunkPtrOffset)) = rowChunk - mData;
+            // Mark the new chunk's next 'pointer' as null
+            *((uint32_t *)(rowChunk + ROW_SLOT_CHUNK_SIZE - sizeof(uint32_t))) = 0;
+        } else {
+LOG_WINDOW("follwing 'pointer' to next chunk, offset of next pointer is %d", chunkPtrOffset);
+            rowChunk = offsetToPtr(nextChunkOffset);
+            chunkPtrOffset = rowChunk - mData + (ROW_SLOT_CHUNK_NUM_ROWS * sizeof(row_slot_t));
+        }
+    }
+    mHeader->numRows++;
+
+    return (row_slot_t *)(rowChunk + (chunkPos * sizeof(row_slot_t)));
+}
+
+field_slot_t * CursorWindow::getFieldSlotWithCheck(int row, int column)
+{
+  if (row < 0 || row >= mHeader->numRows || column < 0 || column >= mHeader->numColumns) {
+      LOGE("Bad request for field slot %d,%d. numRows = %d, numColumns = %d", row, column, mHeader->numRows, mHeader->numColumns);
+      return NULL;
+  }        
+  row_slot_t * rowSlot = getRowSlot(row);
+  if (!rowSlot) {
+      LOGE("Failed to find rowSlot for row %d", row);
+      return NULL;
+  }
+  if (rowSlot->offset == 0 || rowSlot->offset >= mSize) {
+      LOGE("Invalid rowSlot, offset = %d", rowSlot->offset);
+      return NULL;
+  }  
+  int fieldDirOffset = rowSlot->offset;
+  return ((field_slot_t *)offsetToPtr(fieldDirOffset)) + column;  
+}
+
+uint32_t CursorWindow::read_field_slot(int row, int column, field_slot_t * slotOut)
+{
+    if (row < 0 || row >= mHeader->numRows || column < 0 || column >= mHeader->numColumns) {
+        LOGE("Bad request for field slot %d,%d. numRows = %d, numColumns = %d", row, column, mHeader->numRows, mHeader->numColumns);
+        return -1;
+    }        
+    row_slot_t * rowSlot = getRowSlot(row);
+    if (!rowSlot) {
+        LOGE("Failed to find rowSlot for row %d", row);
+        return -1;
+    }
+    if (rowSlot->offset == 0 || rowSlot->offset >= mSize) {
+        LOGE("Invalid rowSlot, offset = %d", rowSlot->offset);
+        return -1;
+    }
+LOG_WINDOW("Found field directory for %d,%d at rowSlot %d, offset %d", row, column, (uint8_t *)rowSlot - mData, rowSlot->offset);
+    field_slot_t * fieldDir = (field_slot_t *)offsetToPtr(rowSlot->offset);
+LOG_WINDOW("Read field_slot_t %d,%d: offset = %d, size = %d, type = %d", row, column, fieldDir[column].data.buffer.offset, fieldDir[column].data.buffer.size, fieldDir[column].type);
+
+    // Copy the data to the out param
+    slotOut->data.buffer.offset = fieldDir[column].data.buffer.offset;
+    slotOut->data.buffer.size = fieldDir[column].data.buffer.size;
+    slotOut->type = fieldDir[column].type;
+    return 0;
+}
+
+void CursorWindow::copyIn(uint32_t offset, uint8_t const * data, size_t size)
+{
+    assert(offset + size <= mSize);    
+    memcpy(mData + offset, data, size);
+}
+
+void CursorWindow::copyIn(uint32_t offset, int64_t data)
+{
+    assert(offset + sizeof(int64_t) <= mSize);
+    memcpy(mData + offset, (uint8_t *)&data, sizeof(int64_t));
+}
+
+void CursorWindow::copyIn(uint32_t offset, double data)
+{
+    assert(offset + sizeof(double) <= mSize);
+    memcpy(mData + offset, (uint8_t *)&data, sizeof(double));
+}
+
+void CursorWindow::copyOut(uint32_t offset, uint8_t * data, size_t size)
+{
+    assert(offset + size <= mSize);
+    memcpy(data, mData + offset, size);
+}
+
+int64_t CursorWindow::copyOutLong(uint32_t offset)
+{
+    int64_t value;
+    assert(offset + sizeof(int64_t) <= mSize);
+    memcpy(&value, mData + offset, sizeof(int64_t));
+    return value;
+}
+
+double CursorWindow::copyOutDouble(uint32_t offset)
+{
+    double value;
+    assert(offset + sizeof(double) <= mSize);
+    memcpy(&value, mData + offset, sizeof(double));
+    return value;
+}
+
+bool CursorWindow::putLong(unsigned int row, unsigned int col, int64_t value)
+{
+    field_slot_t * fieldSlot = getFieldSlotWithCheck(row, col);
+    if (!fieldSlot) {
+        return false;
+    }
+
+#if WINDOW_STORAGE_INLINE_NUMERICS
+    fieldSlot->data.l = value;
+#else
+    int offset = alloc(sizeof(int64_t));
+    if (!offset) {
+        return false;
+    }
+
+    copyIn(offset, value);
+
+    fieldSlot->data.buffer.offset = offset;
+    fieldSlot->data.buffer.size = sizeof(int64_t);
+#endif
+    fieldSlot->type = FIELD_TYPE_INTEGER;
+    return true;
+}
+
+bool CursorWindow::putDouble(unsigned int row, unsigned int col, double value)
+{
+    field_slot_t * fieldSlot = getFieldSlotWithCheck(row, col);
+    if (!fieldSlot) {
+        return false;
+    }
+
+#if WINDOW_STORAGE_INLINE_NUMERICS
+    fieldSlot->data.d = value;
+#else
+    int offset = alloc(sizeof(int64_t));
+    if (!offset) {
+        return false;
+    }
+
+    copyIn(offset, value);
+
+    fieldSlot->data.buffer.offset = offset;
+    fieldSlot->data.buffer.size = sizeof(double);
+#endif
+    fieldSlot->type = FIELD_TYPE_FLOAT;
+    return true;
+}
+
+bool CursorWindow::putNull(unsigned int row, unsigned int col)
+{
+    field_slot_t * fieldSlot = getFieldSlotWithCheck(row, col);
+    if (!fieldSlot) {
+        return false;
+    }
+
+    fieldSlot->type = FIELD_TYPE_NULL;
+    fieldSlot->data.buffer.offset = 0;
+    fieldSlot->data.buffer.size = 0;
+    return true;
+}
+
+bool CursorWindow::getLong(unsigned int row, unsigned int col, int64_t * valueOut)
+{
+    field_slot_t * fieldSlot = getFieldSlotWithCheck(row, col);
+    if (!fieldSlot || fieldSlot->type != FIELD_TYPE_INTEGER) {
+        return false;
+    }
+    
+#if WINDOW_STORAGE_INLINE_NUMERICS
+    *valueOut = fieldSlot->data.l;
+#else
+    *valueOut = copyOutLong(fieldSlot->data.buffer.offset);
+#endif
+    return true;
+}
+
+bool CursorWindow::getDouble(unsigned int row, unsigned int col, double * valueOut)
+{
+    field_slot_t * fieldSlot = getFieldSlotWithCheck(row, col);
+    if (!fieldSlot || fieldSlot->type != FIELD_TYPE_FLOAT) {
+        return false;
+    }
+
+#if WINDOW_STORAGE_INLINE_NUMERICS
+    *valueOut = fieldSlot->data.d;
+#else
+    *valueOut = copyOutDouble(fieldSlot->data.buffer.offset);
+#endif
+    return true;
+}
+
+bool CursorWindow::getNull(unsigned int row, unsigned int col, bool * valueOut)
+{
+    field_slot_t * fieldSlot = getFieldSlotWithCheck(row, col);
+    if (!fieldSlot) {
+        return false;
+    }
+    
+    if (fieldSlot->type != FIELD_TYPE_NULL) {
+        *valueOut = false;
+    } else {
+        *valueOut = true;
+    }
+    return true;
+}
+
+}; // namespace android
diff --git a/core/jni/CursorWindow.h b/core/jni/CursorWindow.h
new file mode 100644
index 0000000..0fb074f
--- /dev/null
+++ b/core/jni/CursorWindow.h
@@ -0,0 +1,203 @@
+/*
+ * 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.
+ */
+
+#ifndef _ANDROID__DATABASE_WINDOW_H
+#define _ANDROID__DATABASE_WINDOW_H
+
+#include <cutils/log.h>
+#include <stddef.h>
+#include <stdint.h>
+
+#include <utils/MemoryDealer.h>
+#include <utils/RefBase.h>
+
+#include <jni.h>
+
+#define DEFAULT_WINDOW_SIZE 4096
+#define MAX_WINDOW_SIZE (1024 * 1024)
+#define WINDOW_ALLOCATION_SIZE 4096
+
+#define ROW_SLOT_CHUNK_NUM_ROWS 16
+
+// Row slots are allocated in chunks of ROW_SLOT_CHUNK_NUM_ROWS,
+// with an offset after the rows that points to the next chunk
+#define ROW_SLOT_CHUNK_SIZE ((ROW_SLOT_CHUNK_NUM_ROWS * sizeof(row_slot_t)) + sizeof(uint32_t))
+
+
+#if LOG_NDEBUG
+
+#define IF_LOG_WINDOW() if (false)
+#define LOG_WINDOW(...)
+
+#else
+
+#define IF_LOG_WINDOW() IF_LOG(LOG_DEBUG, "CursorWindow")
+#define LOG_WINDOW(...) LOG(LOG_DEBUG, "CursorWindow", __VA_ARGS__)
+
+#endif
+
+
+// When defined to true strings are stored as UTF8, otherwise they're UTF16
+#define WINDOW_STORAGE_UTF8 1
+
+// When defined to true numberic values are stored inline in the field_slot_t, otherwise they're allocated in the window
+#define WINDOW_STORAGE_INLINE_NUMERICS 1
+
+namespace android {
+
+typedef struct
+{
+    uint32_t numRows;
+    uint32_t numColumns;
+} window_header_t;
+
+typedef struct
+{
+    uint32_t offset;
+} row_slot_t;
+
+typedef struct
+{
+    uint8_t type;
+    union {
+        double d;
+        int64_t l;
+        struct {
+            uint32_t offset;
+            uint32_t size;
+        } buffer;
+    } data;
+} __attribute__((packed)) field_slot_t;
+
+#define FIELD_TYPE_INTEGER 1
+#define FIELD_TYPE_FLOAT 2
+#define FIELD_TYPE_STRING 3
+#define FIELD_TYPE_BLOB 4
+#define FIELD_TYPE_NULL 5
+
+/**
+ * This class stores a set of rows from a database in a buffer. The begining of the
+ * window has first chunk of row_slot_ts, which are offsets to the row directory, followed by
+ * an offset to the next chunk in a linked-list of additional chunk of row_slot_ts in case
+ * the pre-allocated chunk isn't big enough to refer to all rows. Each row directory has a
+ * field_slot_t per column, which has the size, offset, and type of the data for that field.
+ * Note that the data types come from sqlite3.h.
+ */
+class CursorWindow
+{
+public:
+                        CursorWindow(size_t maxSize);
+                        CursorWindow(){}
+    bool                setMemory(sp<IMemory>);
+                        ~CursorWindow();
+
+    bool                initBuffer(bool localOnly);
+    sp<IMemory>         getMemory() {return mMemory;}
+
+    size_t              size() {return mSize;}
+    uint8_t *           data() {return mData;}
+    uint32_t            getNumRows() {return mHeader->numRows;}
+    uint32_t            getNumColumns() {return mHeader->numColumns;}
+    void                freeLastRow() {
+                            if (mHeader->numRows > 0) {
+                                mHeader->numRows--;
+                            }
+                        }
+    bool                setNumColumns(uint32_t numColumns)
+                            {
+                                uint32_t cur = mHeader->numColumns;
+                                if (cur > 0 && cur != numColumns) {
+                                    LOGE("Trying to go from %d columns to %d", cur, numColumns);
+                                    return false;
+                                }
+                                mHeader->numColumns = numColumns;
+                                return true;
+                            }
+
+    int32_t             freeSpace();
+
+    void                clear();
+
+                        /**
+                         * Allocate a row slot and its directory. The returned
+                         * pointer points to the begining of the row's directory
+                         * or NULL if there wasn't room. The directory is
+                         * initialied with NULL entries for each field.
+                         */
+    field_slot_t *      allocRow();
+
+                        /**
+                         * Allocate a portion of the window. Returns the offset
+                         * of the allocation, or 0 if there isn't enough space.
+                         * If aligned is true, the allocation gets 4 byte alignment.
+                         */
+    uint32_t            alloc(size_t size, bool aligned = false);
+
+    uint32_t            read_field_slot(int row, int column, field_slot_t * slot);
+
+                        /**
+                         * Copy data into the window at the given offset.
+                         */
+    void                copyIn(uint32_t offset, uint8_t const * data, size_t size);
+    void                copyIn(uint32_t offset, int64_t data);
+    void                copyIn(uint32_t offset, double data);
+
+    void                copyOut(uint32_t offset, uint8_t * data, size_t size);
+    int64_t             copyOutLong(uint32_t offset);
+    double              copyOutDouble(uint32_t offset);
+
+    bool                putLong(unsigned int row, unsigned int col, int64_t value);
+    bool                putDouble(unsigned int row, unsigned int col, double value);
+    bool                putNull(unsigned int row, unsigned int col);
+
+    bool                getLong(unsigned int row, unsigned int col, int64_t * valueOut);
+    bool                getDouble(unsigned int row, unsigned int col, double * valueOut);
+    bool                getNull(unsigned int row, unsigned int col, bool * valueOut);
+
+    uint8_t *           offsetToPtr(uint32_t offset) {return mData + offset;}
+
+    row_slot_t *        allocRowSlot();
+
+    row_slot_t *        getRowSlot(int row);
+    
+                        /**
+                         * return NULL if Failed to find rowSlot or
+                         * Invalid rowSlot
+                         */
+    field_slot_t *      getFieldSlotWithCheck(int row, int column);
+    field_slot_t *      getFieldSlot(int row, int column)
+                            {
+                                int fieldDirOffset = getRowSlot(row)->offset;
+                                return ((field_slot_t *)offsetToPtr(fieldDirOffset)) + column;
+                            }
+
+private:
+    uint8_t * mData;
+    size_t mSize;
+    size_t mMaxSize;
+    window_header_t * mHeader;
+    sp<MemoryDealer> mHeap;
+    sp<IMemory> mMemory;
+
+    /**
+     * Offset of the lowest unused data byte in the array.
+     */
+    uint32_t mFreeOffset;
+};
+
+}; // namespace android
+
+#endif
diff --git a/core/jni/GraphicsExternGlue.h b/core/jni/GraphicsExternGlue.h
new file mode 100644
index 0000000..290dbd56
--- /dev/null
+++ b/core/jni/GraphicsExternGlue.h
@@ -0,0 +1,25 @@
+/* //device/libs/android_runtime/GraphicsExternGlue.h
+**
+** Copyright 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.
+*/
+
+extern void register_android_Paint();
+extern void register_android_Canvas();
+extern void register_android_ColorFilter();
+extern void register_android_Matrix();
+extern void register_android_Path();
+extern void register_android_PorterDuff();
+extern void register_android_Rasterizer();
+extern void register_android_Xfermode();
diff --git a/core/jni/GraphicsRegisterGlue.h b/core/jni/GraphicsRegisterGlue.h
new file mode 100644
index 0000000..4054e83
--- /dev/null
+++ b/core/jni/GraphicsRegisterGlue.h
@@ -0,0 +1,25 @@
+/* //device/libs/android_runtime/GraphicsRegisterGlue.h
+**
+** Copyright 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.
+*/
+
+register_android_Paint();
+register_android_Canvas();
+register_android_ColorFilter();
+register_android_Matrix();
+register_android_Path();
+register_android_PorterDuff();
+register_android_Rasterizer();
+register_android_Xfermode();
diff --git a/core/jni/MODULE_LICENSE_APACHE2 b/core/jni/MODULE_LICENSE_APACHE2
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/core/jni/MODULE_LICENSE_APACHE2
diff --git a/core/jni/NOTICE b/core/jni/NOTICE
new file mode 100644
index 0000000..c5b1efa
--- /dev/null
+++ b/core/jni/NOTICE
@@ -0,0 +1,190 @@
+
+   Copyright (c) 2005-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.
+
+   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.
+
+
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
diff --git a/core/jni/android/graphics/Bitmap.cpp b/core/jni/android/graphics/Bitmap.cpp
new file mode 100644
index 0000000..65f44d5
--- /dev/null
+++ b/core/jni/android/graphics/Bitmap.cpp
@@ -0,0 +1,566 @@
+#include "SkBitmap.h"

+#include "SkImageEncoder.h"

+#include "SkColorPriv.h"

+#include "GraphicsJNI.h"

+#include "SkDither.h"

+#include "SkUnPreMultiply.h"

+

+#include "Parcel.h"

+#include "android_util_Binder.h"

+#include "android_nio_utils.h"

+#include "CreateJavaOutputStreamAdaptor.h"

+

+#include <jni.h>

+

+#if 0

+    #define TRACE_BITMAP(code)  code

+#else

+    #define TRACE_BITMAP(code)

+#endif

+

+///////////////////////////////////////////////////////////////////////////////

+// Conversions to/from SkColor, for get/setPixels, and the create method, which

+// is basically like setPixels

+

+typedef void (*FromColorProc)(void* dst, const SkColor src[], int width,

+                              int x, int y);

+

+static void FromColor_D32(void* dst, const SkColor src[], int width,

+                          int, int) {

+    SkPMColor* d = (SkPMColor*)dst;

+    

+    for (int i = 0; i < width; i++) {

+        *d++ = SkPreMultiplyColor(*src++);

+    }

+}

+

+static void FromColor_D565(void* dst, const SkColor src[], int width,

+                           int x, int y) {

+    uint16_t* d = (uint16_t*)dst;

+    

+    DITHER_565_SCAN(y);

+    for (int stop = x + width; x < stop; x++) {

+        SkColor c = *src++;

+        *d++ = SkDitherRGBTo565(SkColorGetR(c), SkColorGetG(c), SkColorGetB(c),

+                                DITHER_VALUE(x));

+    }

+}

+

+static void FromColor_D4444(void* dst, const SkColor src[], int width,

+                            int x, int y) {

+    SkPMColor16* d = (SkPMColor16*)dst;

+    

+    DITHER_4444_SCAN(y);

+    for (int stop = x + width; x < stop; x++) {

+        SkPMColor c = SkPreMultiplyColor(*src++);

+        *d++ = SkDitherARGB32To4444(c, DITHER_VALUE(x));

+//        *d++ = SkPixel32ToPixel4444(c);

+    }

+}

+

+// can return NULL

+static FromColorProc ChooseFromColorProc(SkBitmap::Config config) {

+    switch (config) {

+        case SkBitmap::kARGB_8888_Config:

+            return FromColor_D32;

+        case SkBitmap::kARGB_4444_Config:

+            return FromColor_D4444;

+        case SkBitmap::kRGB_565_Config:

+            return FromColor_D565;

+        default:

+            break;

+    }

+    return NULL;

+}

+

+bool GraphicsJNI::SetPixels(JNIEnv* env, jintArray srcColors,

+                            int srcOffset, int srcStride,

+                            int x, int y, int width, int height,

+                            const SkBitmap& dstBitmap) {

+    SkAutoLockPixels alp(dstBitmap);

+    void* dst = dstBitmap.getPixels();

+    FromColorProc proc = ChooseFromColorProc(dstBitmap.config());

+    

+    if (NULL == dst || NULL == proc) {

+        return false;

+    }

+    

+    const jint* array = env->GetIntArrayElements(srcColors, NULL);

+    const SkColor* src = (const SkColor*)array + srcOffset;

+    

+    // reset to to actual choice from caller

+    dst = dstBitmap.getAddr(x, y);

+    // now copy/convert each scanline

+    for (int y = 0; y < height; y++) {

+        proc(dst, src, width, x, y);

+        src += srcStride;

+        dst = (char*)dst + dstBitmap.rowBytes();

+    }

+    

+    env->ReleaseIntArrayElements(srcColors, const_cast<jint*>(array),

+                                 JNI_ABORT);

+    return true;

+}

+

+//////////////////// ToColor procs

+

+typedef void (*ToColorProc)(SkColor dst[], const void* src, int width,

+                            SkColorTable*);

+

+static void ToColor_S32_Alpha(SkColor dst[], const void* src, int width,

+                              SkColorTable*) {

+    SkASSERT(width > 0);

+    const SkPMColor* s = (const SkPMColor*)src;

+    do {

+        *dst++ = SkUnPreMultiply::PMColorToColor(*s++);

+    } while (--width != 0);

+}

+

+static void ToColor_S32_Opaque(SkColor dst[], const void* src, int width,

+                               SkColorTable*) {

+    SkASSERT(width > 0);

+    const SkPMColor* s = (const SkPMColor*)src;

+    do {

+        SkPMColor c = *s++;

+        *dst++ = SkColorSetRGB(SkGetPackedR32(c), SkGetPackedG32(c),

+                               SkGetPackedB32(c));

+    } while (--width != 0);

+}

+

+static void ToColor_S4444_Alpha(SkColor dst[], const void* src, int width,

+                                SkColorTable*) {

+    SkASSERT(width > 0);

+    const SkPMColor16* s = (const SkPMColor16*)src;

+    do {

+        *dst++ = SkUnPreMultiply::PMColorToColor(SkPixel4444ToPixel32(*s++));

+    } while (--width != 0);

+}

+

+static void ToColor_S4444_Opaque(SkColor dst[], const void* src, int width,

+                                 SkColorTable*) {

+    SkASSERT(width > 0);

+    const SkPMColor* s = (const SkPMColor*)src;

+    do {

+        SkPMColor c = SkPixel4444ToPixel32(*s++);

+        *dst++ = SkColorSetRGB(SkGetPackedR32(c), SkGetPackedG32(c),

+                               SkGetPackedB32(c));

+    } while (--width != 0);

+}

+

+static void ToColor_S565(SkColor dst[], const void* src, int width,

+                         SkColorTable*) {

+    SkASSERT(width > 0);

+    const uint16_t* s = (const uint16_t*)src;

+    do {

+        uint16_t c = *s++;

+        *dst++ =  SkColorSetRGB(SkPacked16ToR32(c), SkPacked16ToG32(c),

+                                SkPacked16ToB32(c));

+    } while (--width != 0);

+}

+

+static void ToColor_SI8_Alpha(SkColor dst[], const void* src, int width,

+                              SkColorTable* ctable) {

+    SkASSERT(width > 0);

+    const uint8_t* s = (const uint8_t*)src;

+    const SkPMColor* colors = ctable->lockColors();

+    do {

+        *dst++ = SkUnPreMultiply::PMColorToColor(colors[*s++]);

+    } while (--width != 0);

+    ctable->unlockColors(false);

+}

+

+static void ToColor_SI8_Opaque(SkColor dst[], const void* src, int width,

+                               SkColorTable* ctable) {

+    SkASSERT(width > 0);

+    const uint8_t* s = (const uint8_t*)src;

+    const SkPMColor* colors = ctable->lockColors();

+    do {

+        SkPMColor c = colors[*s++];

+        *dst++ = SkColorSetRGB(SkGetPackedR32(c), SkGetPackedG32(c),

+                               SkGetPackedB32(c));

+    } while (--width != 0);

+    ctable->unlockColors(false);

+}

+

+// can return NULL

+static ToColorProc ChooseToColorProc(const SkBitmap& src) {

+    switch (src.config()) {

+        case SkBitmap::kARGB_8888_Config:

+            return src.isOpaque() ? ToColor_S32_Opaque : ToColor_S32_Alpha;

+        case SkBitmap::kARGB_4444_Config:

+            return src.isOpaque() ? ToColor_S4444_Opaque : ToColor_S4444_Alpha;

+        case SkBitmap::kRGB_565_Config:

+            return ToColor_S565;

+        case SkBitmap::kIndex8_Config:

+            if (src.getColorTable() == NULL) {

+                return NULL;

+            }

+            return src.isOpaque() ? ToColor_SI8_Opaque : ToColor_SI8_Alpha;

+        default:

+            break;

+    }

+    return NULL;

+}

+

+///////////////////////////////////////////////////////////////////////////////

+///////////////////////////////////////////////////////////////////////////////

+

+static jobject Bitmap_creator(JNIEnv* env, jobject, jintArray jColors,

+                              int offset, int stride, int width, int height,

+                              SkBitmap::Config config, jboolean isMutable) {

+    if (width <= 0 || height <= 0) {

+        doThrowIAE(env, "width and height must be > 0");

+        return NULL;

+    }

+    

+    if (NULL != jColors) {

+        size_t n = env->GetArrayLength(jColors);

+        if (n < SkAbs32(stride) * (size_t)height) {

+            doThrowAIOOBE(env);

+            return NULL;

+        }

+    }

+

+    SkBitmap bitmap;

+    

+    bitmap.setConfig(config, width, height);

+    if (!GraphicsJNI::setJavaPixelRef(env, &bitmap, NULL)) {

+        return NULL;

+    }

+

+    if (jColors != NULL) {

+        GraphicsJNI::SetPixels(env, jColors, offset, stride,

+                               0, 0, width, height, bitmap);

+    }

+    

+    return GraphicsJNI::createBitmap(env, new SkBitmap(bitmap), isMutable,

+                                     NULL);

+}

+

+static jobject Bitmap_copy(JNIEnv* env, jobject, const SkBitmap* src,

+                           SkBitmap::Config dstConfig, jboolean isMutable) {

+    SkBitmap            result;

+    JavaPixelAllocator  allocator(env);

+

+    if (!src->copyTo(&result, dstConfig, &allocator)) {

+        return NULL;

+    }

+    

+    return GraphicsJNI::createBitmap(env, new SkBitmap(result), isMutable,

+                                     NULL);

+}

+

+static void Bitmap_destructor(JNIEnv* env, jobject, SkBitmap* bitmap) {

+    delete bitmap;

+}

+

+static void Bitmap_recycle(JNIEnv* env, jobject, SkBitmap* bitmap) {

+    bitmap->setPixels(NULL, NULL);

+}

+

+// These must match the int values in Bitmap.java

+enum JavaEncodeFormat {

+    kJPEG_JavaEncodeFormat = 0,

+    kPNG_JavaEncodeFormat = 1

+};

+

+static bool Bitmap_compress(JNIEnv* env, jobject clazz, SkBitmap* bitmap,

+                            int format, int quality,

+                            jobject jstream, jbyteArray jstorage) {

+    SkImageEncoder::Type fm;

+

+    switch (format) {

+    case kJPEG_JavaEncodeFormat:

+        fm = SkImageEncoder::kJPEG_Type;

+        break;

+    case kPNG_JavaEncodeFormat:

+        fm = SkImageEncoder::kPNG_Type;

+        break;

+    default:

+        return false;

+    }

+

+    bool success = false;

+    SkWStream* strm = CreateJavaOutputStreamAdaptor(env, jstream, jstorage);

+    if (NULL != strm) {

+        SkImageEncoder* encoder = SkImageEncoder::Create(fm);

+        if (NULL != encoder) {

+            success = encoder->encodeStream(strm, *bitmap, quality);

+            delete encoder;

+        }

+        delete strm;

+    }

+    return success;

+}

+

+static void Bitmap_erase(JNIEnv* env, jobject, SkBitmap* bitmap, jint color) {

+    bitmap->eraseColor(color);

+}

+

+static int Bitmap_width(JNIEnv* env, jobject, SkBitmap* bitmap) {

+    return bitmap->width();

+}

+

+static int Bitmap_height(JNIEnv* env, jobject, SkBitmap* bitmap) {

+    return bitmap->height();

+}

+

+static int Bitmap_rowBytes(JNIEnv* env, jobject, SkBitmap* bitmap) {

+    return bitmap->rowBytes();

+}

+

+static int Bitmap_config(JNIEnv* env, jobject, SkBitmap* bitmap) {

+    return bitmap->config();

+}

+

+static jboolean Bitmap_hasAlpha(JNIEnv* env, jobject, SkBitmap* bitmap) {

+    return !bitmap->isOpaque();

+}

+

+///////////////////////////////////////////////////////////////////////////////

+

+static jobject Bitmap_createFromParcel(JNIEnv* env, jobject, jobject parcel) {

+    if (parcel == NULL) {

+        SkDebugf("-------- unparcel parcel is NULL\n");

+        return NULL;

+    }

+    

+    android::Parcel* p = android::parcelForJavaObject(env, parcel);

+    

+    const bool              isMutable = p->readInt32() != 0;

+    const SkBitmap::Config  config = (SkBitmap::Config)p->readInt32();

+    const int               width = p->readInt32();

+    const int               height = p->readInt32();

+    const int               rowBytes = p->readInt32();

+    

+    if (SkBitmap::kARGB_8888_Config != config &&

+            SkBitmap::kRGB_565_Config != config &&

+            SkBitmap::kARGB_4444_Config != config &&

+            SkBitmap::kIndex8_Config != config &&

+            SkBitmap::kA8_Config != config) {

+        SkDebugf("Bitmap_createFromParcel unknown config: %d\n", config);

+        return NULL;

+    }

+

+    SkBitmap* bitmap = new SkBitmap;

+

+    bitmap->setConfig(config, width, height, rowBytes);

+

+    SkColorTable* ctable = NULL;

+    if (config == SkBitmap::kIndex8_Config) {

+        int count = p->readInt32();

+        if (count > 0) {

+            size_t size = count * sizeof(SkPMColor);

+            const SkPMColor* src = (const SkPMColor*)p->readInplace(size);

+            ctable = new SkColorTable(src, count);

+        }

+    }

+    

+    if (!GraphicsJNI::setJavaPixelRef(env, bitmap, ctable)) {

+        ctable->safeUnref();

+        delete bitmap;

+        return NULL;

+    }

+

+    ctable->safeUnref();

+

+    size_t size = bitmap->getSize();

+    bitmap->lockPixels();

+    memcpy(bitmap->getPixels(), p->readInplace(size), size);

+    bitmap->unlockPixels();

+    

+    return GraphicsJNI::createBitmap(env, bitmap, isMutable, NULL);

+}

+

+static jboolean Bitmap_writeToParcel(JNIEnv* env, jobject,

+                                     const SkBitmap* bitmap,

+                                     jboolean isMutable, jobject parcel) {

+    if (parcel == NULL) {

+        SkDebugf("------- writeToParcel null parcel\n");

+        return false;

+    }

+

+    android::Parcel* p = android::parcelForJavaObject(env, parcel);

+    

+    p->writeInt32(isMutable);

+    p->writeInt32(bitmap->config());

+    p->writeInt32(bitmap->width());

+    p->writeInt32(bitmap->height());

+    p->writeInt32(bitmap->rowBytes());

+

+    if (bitmap->getConfig() == SkBitmap::kIndex8_Config) {

+        SkColorTable* ctable = bitmap->getColorTable();

+        if (ctable != NULL) {

+            int count = ctable->count();

+            p->writeInt32(count);

+            memcpy(p->writeInplace(count * sizeof(SkPMColor)),

+                   ctable->lockColors(), count * sizeof(SkPMColor));

+            ctable->unlockColors(false);

+        } else {

+            p->writeInt32(0);   // indicate no ctable

+        }

+    }

+

+    size_t size = bitmap->getSize();

+    bitmap->lockPixels();

+    memcpy(p->writeInplace(size), bitmap->getPixels(), size);

+    bitmap->unlockPixels();

+    return true;

+}

+

+static jobject Bitmap_extractAlpha(JNIEnv* env, jobject clazz,

+                                   const SkBitmap* src, const SkPaint* paint,

+                                   jintArray offsetXY) {

+    SkIPoint  offset;

+    SkBitmap* dst = new SkBitmap;

+    

+    src->extractAlpha(dst, paint, &offset);

+    if (offsetXY != 0 && env->GetArrayLength(offsetXY) >= 2) {

+        int* array = env->GetIntArrayElements(offsetXY, NULL);

+        array[0] = offset.fX;

+        array[1] = offset.fY;

+        env->ReleaseIntArrayElements(offsetXY, array, 0);

+    }

+    

+    return GraphicsJNI::createBitmap(env, dst, true, NULL);

+}

+

+///////////////////////////////////////////////////////////////////////////////

+

+static int Bitmap_getPixel(JNIEnv* env, jobject, const SkBitmap* bitmap,

+                           int x, int y) {

+    SkAutoLockPixels alp(*bitmap);

+

+    ToColorProc proc = ChooseToColorProc(*bitmap);

+    if (NULL == proc) {

+        return 0;

+    }

+    const void* src = bitmap->getAddr(x, y);

+    if (NULL == src) {

+        return 0;

+    }

+    

+    SkColor dst[1];

+    proc(dst, src, 1, bitmap->getColorTable());

+    return dst[0];

+}

+

+static void Bitmap_getPixels(JNIEnv* env, jobject, const SkBitmap* bitmap,

+                             jintArray pixelArray, int offset, int stride,

+                             int x, int y, int width, int height) {

+    SkAutoLockPixels alp(*bitmap);

+    

+    ToColorProc proc = ChooseToColorProc(*bitmap);

+    if (NULL == proc) {

+        return;

+    }

+    const void* src = bitmap->getAddr(x, y);

+    if (NULL == src) {

+        return;

+    }

+

+    SkColorTable* ctable = bitmap->getColorTable();

+    jint* dst = env->GetIntArrayElements(pixelArray, NULL);

+    SkColor* d = (SkColor*)dst + offset;

+    while (--height >= 0) {

+        proc(d, src, width, ctable);

+        d += stride;

+        src = (void*)((const char*)src + bitmap->rowBytes());

+    }

+    env->ReleaseIntArrayElements(pixelArray, dst, 0);

+}

+

+///////////////////////////////////////////////////////////////////////////////

+

+static void Bitmap_setPixel(JNIEnv* env, jobject, const SkBitmap* bitmap,

+                            int x, int y, SkColor color) {

+    SkAutoLockPixels alp(*bitmap);

+    if (NULL == bitmap->getPixels()) {

+        return;

+    }

+

+    FromColorProc proc = ChooseFromColorProc(bitmap->config());

+    if (NULL == proc) {

+        return;

+    }

+

+    proc(bitmap->getAddr(x, y), &color, 1, x, y);

+}

+

+static void Bitmap_setPixels(JNIEnv* env, jobject, const SkBitmap* bitmap,

+                             jintArray pixelArray, int offset, int stride,

+                             int x, int y, int width, int height) {

+    GraphicsJNI::SetPixels(env, pixelArray, offset, stride,

+                           x, y, width, height, *bitmap);

+}

+

+static void Bitmap_copyPixelsToBuffer(JNIEnv* env, jobject,

+                                      const SkBitmap* bitmap, jobject jbuffer) {

+    SkAutoLockPixels alp(*bitmap);

+    const void* src = bitmap->getPixels();

+    

+    if (NULL != src) {

+        android::AutoBufferPointer abp(env, jbuffer, JNI_TRUE);

+

+        // the java side has already checked that buffer is large enough

+        memcpy(abp.pointer(), src, bitmap->getSize());

+    }

+}

+

+static void Bitmap_copyPixelsFromBuffer(JNIEnv* env, jobject,

+                                    const SkBitmap* bitmap, jobject jbuffer) {

+    SkAutoLockPixels alp(*bitmap);

+    void* dst = bitmap->getPixels();

+    

+    if (NULL != dst) {

+        android::AutoBufferPointer abp(env, jbuffer, JNI_FALSE);

+        // the java side has already checked that buffer is large enough

+        memcpy(dst, abp.pointer(), bitmap->getSize());

+    }

+}

+

+///////////////////////////////////////////////////////////////////////////////

+

+#include <android_runtime/AndroidRuntime.h>

+

+static JNINativeMethod gBitmapMethods[] = {

+    {   "nativeCreate",             "([IIIIIIZ)Landroid/graphics/Bitmap;",

+        (void*)Bitmap_creator },

+    {   "nativeCopy",               "(IIZ)Landroid/graphics/Bitmap;",

+        (void*)Bitmap_copy },

+    {   "nativeDestructor",         "(I)V", (void*)Bitmap_destructor },

+    {   "nativeRecycle",            "(I)V", (void*)Bitmap_recycle },

+    {   "nativeCompress",           "(IIILjava/io/OutputStream;[B)Z",

+        (void*)Bitmap_compress },

+    {   "nativeErase",              "(II)V", (void*)Bitmap_erase },

+    {   "nativeWidth",              "(I)I", (void*)Bitmap_width },

+    {   "nativeHeight",             "(I)I", (void*)Bitmap_height },

+    {   "nativeRowBytes",           "(I)I", (void*)Bitmap_rowBytes },

+    {   "nativeConfig",             "(I)I", (void*)Bitmap_config },

+    {   "nativeHasAlpha",           "(I)Z", (void*)Bitmap_hasAlpha },

+    {   "nativeCreateFromParcel",

+        "(Landroid/os/Parcel;)Landroid/graphics/Bitmap;",

+        (void*)Bitmap_createFromParcel },

+    {   "nativeWriteToParcel",      "(IZLandroid/os/Parcel;)Z",

+        (void*)Bitmap_writeToParcel },

+    {   "nativeExtractAlpha",       "(II[I)Landroid/graphics/Bitmap;",

+        (void*)Bitmap_extractAlpha },

+    {   "nativeGetPixel",           "(III)I", (void*)Bitmap_getPixel },

+    {   "nativeGetPixels",          "(I[IIIIIII)V", (void*)Bitmap_getPixels },

+    {   "nativeSetPixel",           "(IIII)V", (void*)Bitmap_setPixel },

+    {   "nativeSetPixels",          "(I[IIIIIII)V", (void*)Bitmap_setPixels },

+    {   "nativeCopyPixelsToBuffer", "(ILjava/nio/Buffer;)V",

+                                            (void*)Bitmap_copyPixelsToBuffer },

+    {   "nativeCopyPixelsFromBuffer", "(ILjava/nio/Buffer;)V",

+                                            (void*)Bitmap_copyPixelsFromBuffer }

+};

+

+#define kClassPathName  "android/graphics/Bitmap"

+

+int register_android_graphics_Bitmap(JNIEnv* env);

+int register_android_graphics_Bitmap(JNIEnv* env)

+{

+    return android::AndroidRuntime::registerNativeMethods(env, kClassPathName,

+                                gBitmapMethods, SK_ARRAY_COUNT(gBitmapMethods));

+}

+

diff --git a/core/jni/android/graphics/BitmapFactory.cpp b/core/jni/android/graphics/BitmapFactory.cpp
new file mode 100644
index 0000000..332b01c
--- /dev/null
+++ b/core/jni/android/graphics/BitmapFactory.cpp
@@ -0,0 +1,604 @@
+#define LOG_TAG "BitmapFactory"
+
+#include "SkImageDecoder.h"
+#include "SkPixelRef.h"
+#include "SkStream.h"
+#include "GraphicsJNI.h"
+#include "SkTemplates.h"
+#include "SkUtils.h"
+#include "CreateJavaOutputStreamAdaptor.h"
+
+#include <android_runtime/AndroidRuntime.h>
+#include <utils/Asset.h>
+#include <utils/ResourceTypes.h>
+#include <netinet/in.h>
+#include <sys/mman.h>
+
+static jclass gOptions_class;
+static jfieldID gOptions_justBoundsFieldID;
+static jfieldID gOptions_sampleSizeFieldID;
+static jfieldID gOptions_configFieldID;
+static jfieldID gOptions_ditherFieldID;
+static jfieldID gOptions_widthFieldID;
+static jfieldID gOptions_heightFieldID;
+static jfieldID gOptions_mimeFieldID;
+
+static jclass gFileDescriptor_class;
+static jfieldID gFileDescriptor_descriptor;
+
+#if 0
+    #define TRACE_BITMAP(code)  code
+#else
+    #define TRACE_BITMAP(code)
+#endif
+
+//#define MIN_SIZE_TO_USE_MMAP    (4*1024)
+
+///////////////////////////////////////////////////////////////////////////////
+
+class AutoDecoderCancel {
+public:
+    AutoDecoderCancel(jobject options, SkImageDecoder* decoder);
+    ~AutoDecoderCancel();
+
+    static bool RequestCancel(jobject options);
+    
+private:
+    AutoDecoderCancel*  fNext;
+    AutoDecoderCancel*  fPrev;
+    jobject             fJOptions;  // java options object
+    SkImageDecoder*     fDecoder;
+    
+#ifdef SK_DEBUG
+    static void Validate();
+#else
+    static void Validate() {}
+#endif
+};
+
+static SkMutex  gAutoDecoderCancelMutex;
+static AutoDecoderCancel* gAutoDecoderCancel;
+#ifdef SK_DEBUG
+    static int gAutoDecoderCancelCount;
+#endif
+
+AutoDecoderCancel::AutoDecoderCancel(jobject joptions,
+                                       SkImageDecoder* decoder) {
+    fJOptions = joptions;
+    fDecoder = decoder;
+
+    if (NULL != joptions) {
+        SkAutoMutexAcquire ac(gAutoDecoderCancelMutex);
+
+        // Add us as the head of the list
+        fPrev = NULL;
+        fNext = gAutoDecoderCancel;
+        if (gAutoDecoderCancel) {
+            gAutoDecoderCancel->fPrev = this;
+        }
+        gAutoDecoderCancel = this;
+        
+        SkDEBUGCODE(gAutoDecoderCancelCount += 1;)
+        Validate();
+    }
+}
+
+AutoDecoderCancel::~AutoDecoderCancel() {
+    if (NULL != fJOptions) {
+        SkAutoMutexAcquire ac(gAutoDecoderCancelMutex);
+        
+        // take us out of the dllist
+        AutoDecoderCancel* prev = fPrev;
+        AutoDecoderCancel* next = fNext;
+        
+        if (prev) {
+            SkASSERT(prev->fNext == this);
+            prev->fNext = next;
+        } else {
+            SkASSERT(gAutoDecoderCancel == this);
+            gAutoDecoderCancel = next;
+        }
+        if (next) {
+            SkASSERT(next->fPrev == this);
+            next->fPrev = prev;
+        }
+
+        SkDEBUGCODE(gAutoDecoderCancelCount -= 1;)
+        Validate();
+    }
+}
+
+bool AutoDecoderCancel::RequestCancel(jobject joptions) {
+    SkAutoMutexAcquire ac(gAutoDecoderCancelMutex);
+
+    Validate();
+
+    AutoDecoderCancel* pair = gAutoDecoderCancel;
+    while (pair != NULL) {
+        if (pair->fJOptions == joptions) {
+            pair->fDecoder->cancelDecode();
+            return true;
+        }
+        pair = pair->fNext;
+    }
+    return false;
+}
+
+#ifdef SK_DEBUG
+// can only call this inside a lock on gAutoDecoderCancelMutex 
+void AutoDecoderCancel::Validate() {
+    const int gCount = gAutoDecoderCancelCount;
+
+    if (gCount == 0) {
+        SkASSERT(gAutoDecoderCancel == NULL);
+    } else {
+        SkASSERT(gCount > 0);
+        
+        AutoDecoderCancel* curr = gAutoDecoderCancel;
+        SkASSERT(curr);
+        SkASSERT(curr->fPrev == NULL);
+
+        int count = 0;
+        while (curr) {
+            count += 1;
+            SkASSERT(count <= gCount);
+            if (curr->fPrev) {
+                SkASSERT(curr->fPrev->fNext == curr);
+            }
+            if (curr->fNext) {
+                SkASSERT(curr->fNext->fPrev == curr);
+            }
+            curr = curr->fNext;
+        }
+        SkASSERT(count == gCount);
+    }
+}
+#endif
+
+///////////////////////////////////////////////////////////////////////////////
+
+using namespace android;
+
+class NinePatchPeeker : public SkImageDecoder::Peeker {
+public:
+    NinePatchPeeker() {
+        fPatchIsValid = false;
+    }
+
+    ~NinePatchPeeker() {
+        if (fPatchIsValid) {
+            free(fPatch);
+        }
+    }
+
+    bool    fPatchIsValid;
+    Res_png_9patch*  fPatch;
+
+    virtual bool peek(const char tag[], const void* data, size_t length) {
+        if (strcmp("npTc", tag) == 0 && length >= sizeof(Res_png_9patch)) {
+            Res_png_9patch* patch = (Res_png_9patch*) data;
+            size_t patchSize = patch->serializedSize();
+            assert(length == patchSize);
+            // You have to copy the data because it is owned by the png reader
+            Res_png_9patch* patchNew = (Res_png_9patch*) malloc(patchSize);
+            memcpy(patchNew, patch, patchSize);
+            // this relies on deserialization being done in place
+            Res_png_9patch::deserialize(patchNew);
+            patchNew->fileToDevice();
+            if (fPatchIsValid) {
+                free(fPatch);
+            }
+            fPatch = patchNew;
+            //printf("9patch: (%d,%d)-(%d,%d)\n",
+            //       fPatch.sizeLeft, fPatch.sizeTop,
+            //       fPatch.sizeRight, fPatch.sizeBottom);
+            fPatchIsValid = true;
+        } else {
+            fPatch = NULL;
+        }
+        return true;    // keep on decoding
+    }
+};
+
+class AssetStreamAdaptor : public SkStream {
+public:
+    AssetStreamAdaptor(Asset* a) : fAsset(a) {}
+    
+	virtual bool rewind() {
+        off_t pos = fAsset->seek(0, SEEK_SET);
+        return pos != (off_t)-1;
+    }
+    
+	virtual size_t read(void* buffer, size_t size) {
+        ssize_t amount;
+        
+        if (NULL == buffer) {
+            if (0 == size) {  // caller is asking us for our total length
+                return fAsset->getLength();
+            }
+            // asset->seek returns new total offset
+            // we want to return amount that was skipped
+
+            off_t oldOffset = fAsset->seek(0, SEEK_CUR);
+            if (-1 == oldOffset) {
+                return 0;
+            }
+            off_t newOffset = fAsset->seek(size, SEEK_CUR);
+            if (-1 == newOffset) {
+                return 0;
+            }
+            amount = newOffset - oldOffset;
+        } else {
+            amount = fAsset->read(buffer, size);
+        }
+        
+        if (amount < 0) {
+            amount = 0;
+        }
+        return amount;
+    }
+    
+private:
+    Asset*  fAsset;
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+static inline int32_t validOrNeg1(bool isValid, int32_t value) {
+//    return isValid ? value : -1;
+    SkASSERT((int)isValid == 0 || (int)isValid == 1);
+    return ((int32_t)isValid - 1) | value;
+}
+
+static jstring getMimeTypeString(JNIEnv* env, SkImageDecoder::Format format) {
+    static const struct {
+        SkImageDecoder::Format fFormat;
+        const char*            fMimeType;
+    } gMimeTypes[] = {
+        { SkImageDecoder::kBMP_Format,  "image/bmp" },
+        { SkImageDecoder::kGIF_Format,  "image/gif" },
+        { SkImageDecoder::kICO_Format,  "image/x-ico" },
+        { SkImageDecoder::kJPEG_Format, "image/jpeg" },
+        { SkImageDecoder::kPNG_Format,  "image/png" },
+        { SkImageDecoder::kWBMP_Format, "image/vnd.wap.wbmp" }
+    };
+    
+    const char* cstr = NULL;
+    for (size_t i = 0; i < SK_ARRAY_COUNT(gMimeTypes); i++) {
+        if (gMimeTypes[i].fFormat == format) {
+            cstr = gMimeTypes[i].fMimeType;
+            break;
+        }
+    }
+
+    jstring jstr = 0;
+    if (NULL != cstr) {
+        jstr = env->NewStringUTF(cstr);
+    }
+    return jstr;
+}
+
+static jobject doDecode(JNIEnv* env, SkStream* stream, jobject padding,
+                        jobject options) {
+
+    int sampleSize = 1;
+    SkImageDecoder::Mode mode = SkImageDecoder::kDecodePixels_Mode;
+    SkBitmap::Config prefConfig = SkBitmap::kNo_Config;
+    bool doDither = true;
+    
+    if (NULL != options) {
+        sampleSize = env->GetIntField(options, gOptions_sampleSizeFieldID);
+        if (env->GetBooleanField(options, gOptions_justBoundsFieldID)) {
+            mode = SkImageDecoder::kDecodeBounds_Mode;
+        }
+        // initialize these, in case we fail later on
+        env->SetIntField(options, gOptions_widthFieldID, -1);
+        env->SetIntField(options, gOptions_heightFieldID, -1);
+        env->SetObjectField(options, gOptions_mimeFieldID, 0);
+        
+        jobject jconfig = env->GetObjectField(options, gOptions_configFieldID);
+        prefConfig = GraphicsJNI::getNativeBitmapConfig(env, jconfig);
+        doDither = env->GetBooleanField(options, gOptions_ditherFieldID);
+    }
+
+    SkImageDecoder* decoder = SkImageDecoder::Factory(stream);
+    if (NULL == decoder) {
+        return NULL;
+    }
+    
+    decoder->setSampleSize(sampleSize);
+    decoder->setDitherImage(doDither);
+
+    NinePatchPeeker     peeker;
+    JavaPixelAllocator  allocator(env);
+    SkBitmap*           bitmap = new SkBitmap;
+    Res_png_9patch      dummy9Patch;
+
+    SkAutoTDelete<SkImageDecoder>   add(decoder);
+    SkAutoTDelete<SkBitmap>         adb(bitmap);
+
+    decoder->setPeeker(&peeker);
+    decoder->setAllocator(&allocator);
+    
+    AutoDecoderCancel   adc(options, decoder);
+
+    if (!decoder->decode(stream, bitmap, prefConfig, mode)) {
+        return NULL;
+    }
+    
+    // update options (if any)
+    if (NULL != options) {
+        env->SetIntField(options, gOptions_widthFieldID, bitmap->width());
+        env->SetIntField(options, gOptions_heightFieldID, bitmap->height());
+        // TODO: set the mimeType field with the data from the codec.
+        // but how to reuse a set of strings, rather than allocating new one
+        // each time?
+        env->SetObjectField(options, gOptions_mimeFieldID,
+                            getMimeTypeString(env, decoder->getFormat()));
+    }
+    
+    // if we're in justBounds mode, return now (skip the java bitmap)
+    if (SkImageDecoder::kDecodeBounds_Mode == mode) {
+        return NULL;
+    }
+
+    jbyteArray ninePatchChunk = NULL;
+    if (peeker.fPatchIsValid) {
+        size_t ninePatchArraySize = peeker.fPatch->serializedSize();
+        ninePatchChunk = env->NewByteArray(ninePatchArraySize);
+        if (NULL == ninePatchChunk) {
+            return NULL;
+        }
+        jbyte* array = (jbyte*)env->GetPrimitiveArrayCritical(ninePatchChunk,
+                                                              NULL);
+        if (NULL == array) {
+            return NULL;
+        }
+        peeker.fPatch->serialize(array);
+        env->ReleasePrimitiveArrayCritical(ninePatchChunk, array, 0);
+    }
+
+    // detach bitmap from its autotdeleter, since we want to own it now
+    adb.detach();
+
+    if (padding) {
+        if (peeker.fPatchIsValid) {
+            GraphicsJNI::set_jrect(env, padding,
+                                   peeker.fPatch->paddingLeft,
+                                   peeker.fPatch->paddingTop,
+                                   peeker.fPatch->paddingRight,
+                                   peeker.fPatch->paddingBottom);
+        } else {
+            GraphicsJNI::set_jrect(env, padding, -1, -1, -1, -1);
+        }
+    }
+    
+    // promise we will never change our pixels (great for sharing and pictures)
+    SkPixelRef* ref = bitmap->pixelRef();
+    SkASSERT(ref);
+    ref->setImmutable();
+
+    return GraphicsJNI::createBitmap(env, bitmap, false, ninePatchChunk);
+}
+
+static jobject nativeDecodeStream(JNIEnv* env, jobject clazz,
+                                  jobject is,       // InputStream
+                                  jbyteArray storage,   // byte[]
+                                  jobject padding,
+                                  jobject options) {  // BitmapFactory$Options
+    jobject bitmap = NULL;
+    SkStream* stream = CreateJavaInputStreamAdaptor(env, is, storage);
+
+    if (stream) {
+        bitmap = doDecode(env, stream, padding, options);
+        stream->unref();
+    }
+    return bitmap;
+}
+
+static ssize_t getFDSize(int fd) {
+    off_t curr = ::lseek(fd, 0, SEEK_CUR);
+    if (curr < 0) {
+        return 0;
+    }
+    size_t size = ::lseek(fd, 0, SEEK_END);
+    ::lseek(fd, curr, SEEK_SET);
+    return size;
+}
+
+/** Restore the file descriptor's offset in our destructor
+ */
+class AutoFDSeek {
+public:
+    AutoFDSeek(int fd) : fFD(fd) {
+        fCurr = ::lseek(fd, 0, SEEK_CUR);
+    }
+    ~AutoFDSeek() {
+        if (fCurr >= 0) {
+            ::lseek(fFD, fCurr, SEEK_SET);
+        }
+    }
+private:
+    int     fFD;
+    off_t   fCurr;
+};
+
+static jobject nativeDecodeFileDescriptor(JNIEnv* env, jobject clazz,
+                                          jobject fileDescriptor,
+                                          jobject padding,
+                                          jobject bitmapFactoryOptions) {
+    NPE_CHECK_RETURN_ZERO(env, fileDescriptor);
+
+    jint descriptor = env->GetIntField(fileDescriptor,
+                                       gFileDescriptor_descriptor);
+    
+#ifdef MIN_SIZE_TO_USE_MMAP
+    // First try to use mmap
+    size_t size = getFDSize(descriptor);
+    if (size >= MIN_SIZE_TO_USE_MMAP) {
+        void* addr = mmap(NULL, size, PROT_READ, MAP_PRIVATE, descriptor, 0);
+//        SkDebugf("-------- mmap returned %p %d\n", addr, size);
+        if (MAP_FAILED != addr) {
+            SkMemoryStream strm(addr, size);
+            jobject obj = doDecode(env, &strm, padding, bitmapFactoryOptions);
+            munmap(addr, size);
+            return obj;
+        }
+    }
+#endif
+
+    // we pass false for closeWhenDone, since the caller owns the descriptor    
+    SkFDStream file(descriptor, false);
+    if (!file.isValid()) {
+        return NULL;
+    }
+    
+    /* Restore our offset when we leave, so the caller doesn't have to.
+       This is a real feature, so we can be called more than once with the
+       same descriptor.
+    */
+    AutoFDSeek as(descriptor);
+
+    return doDecode(env, &file, padding, bitmapFactoryOptions);
+}
+
+static jobject nativeDecodeAsset(JNIEnv* env, jobject clazz,
+                                 jint native_asset,    // Asset
+                                 jobject padding,       // Rect
+                                 jobject options) { // BitmapFactory$Options
+    AssetStreamAdaptor  mystream((Asset*)native_asset);
+
+    return doDecode(env, &mystream, padding, options);
+}
+
+static jobject nativeDecodeByteArray(JNIEnv* env, jobject, jbyteArray byteArray,
+                                     int offset, int length, jobject options) {
+    AutoJavaByteArray   ar(env, byteArray);
+    SkMemoryStream  stream(ar.ptr() + offset, length);
+
+    return doDecode(env, &stream, NULL, options);
+}
+
+static void nativeRequestCancel(JNIEnv*, jobject joptions) {
+    (void)AutoDecoderCancel::RequestCancel(joptions);
+}
+
+static jbyteArray nativeScaleNinePatch(JNIEnv* env, jobject, jbyteArray chunkObject, jfloat scale,
+        jobject padding) {
+
+    jbyte* array = env->GetByteArrayElements(chunkObject, 0);
+    if (array != NULL) {
+        size_t chunkSize = env->GetArrayLength(chunkObject);
+        void* storage = alloca(chunkSize);
+        android::Res_png_9patch* chunk = static_cast<android::Res_png_9patch*>(storage);
+        memcpy(chunk, array, chunkSize);
+        android::Res_png_9patch::deserialize(chunk);
+
+        chunk->paddingLeft = int(chunk->paddingLeft * scale + 0.5f);
+        chunk->paddingTop = int(chunk->paddingTop * scale + 0.5f);
+        chunk->paddingRight = int(chunk->paddingRight * scale + 0.5f);
+        chunk->paddingBottom = int(chunk->paddingBottom * scale + 0.5f);
+
+        for (int i = 0; i < chunk->numXDivs; i++) {
+            chunk->xDivs[i] = int(chunk->xDivs[i] * scale + 0.5f);
+            if (i > 0 && chunk->xDivs[i] == chunk->xDivs[i - 1]) {
+                chunk->xDivs[i]++;
+            }
+        }
+
+        for (int i = 0; i < chunk->numYDivs; i++) {
+            chunk->yDivs[i] = int(chunk->yDivs[i] * scale + 0.5f);            
+            if (i > 0 && chunk->yDivs[i] == chunk->yDivs[i - 1]) {
+                chunk->yDivs[i]++;
+            }
+        }
+
+        memcpy(array, chunk, chunkSize);
+
+        if (padding) {
+            GraphicsJNI::set_jrect(env, padding, chunk->paddingLeft, chunk->paddingTop,
+                    chunk->paddingRight, chunk->paddingBottom);
+        }
+
+        env->ReleaseByteArrayElements(chunkObject, array, 0);
+    }
+    return chunkObject;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+static JNINativeMethod gMethods[] = {
+    {   "nativeDecodeStream",
+        "(Ljava/io/InputStream;[BLandroid/graphics/Rect;Landroid/graphics/BitmapFactory$Options;)Landroid/graphics/Bitmap;",
+        (void*)nativeDecodeStream
+    },
+
+    {   "nativeDecodeFileDescriptor",
+        "(Ljava/io/FileDescriptor;Landroid/graphics/Rect;Landroid/graphics/BitmapFactory$Options;)Landroid/graphics/Bitmap;",
+        (void*)nativeDecodeFileDescriptor
+    },
+
+    {   "nativeDecodeAsset",
+        "(ILandroid/graphics/Rect;Landroid/graphics/BitmapFactory$Options;)Landroid/graphics/Bitmap;",
+        (void*)nativeDecodeAsset
+    },
+
+    {   "nativeDecodeByteArray",
+        "([BIILandroid/graphics/BitmapFactory$Options;)Landroid/graphics/Bitmap;",
+        (void*)nativeDecodeByteArray
+    },
+
+    {   "nativeScaleNinePatch",
+        "([BFLandroid/graphics/Rect;)[B",
+        (void*)nativeScaleNinePatch
+    }
+
+};
+
+static JNINativeMethod gOptionsMethods[] = {
+    {   "requestCancel", "()V", (void*)nativeRequestCancel }
+};
+
+static jclass make_globalref(JNIEnv* env, const char classname[]) {
+    jclass c = env->FindClass(classname);
+    SkASSERT(c);
+    return (jclass)env->NewGlobalRef(c);
+}
+
+static jfieldID getFieldIDCheck(JNIEnv* env, jclass clazz,
+                                const char fieldname[], const char type[]) {
+    jfieldID id = env->GetFieldID(clazz, fieldname, type);
+    SkASSERT(id);
+    return id;
+}
+
+#define kClassPathName  "android/graphics/BitmapFactory"
+
+#define RETURN_ERR_IF_NULL(value) \
+    do { if (!(value)) { assert(0); return -1; } } while (false)
+
+int register_android_graphics_BitmapFactory(JNIEnv* env);
+int register_android_graphics_BitmapFactory(JNIEnv* env) {
+    gOptions_class = make_globalref(env, "android/graphics/BitmapFactory$Options");
+    gOptions_justBoundsFieldID = getFieldIDCheck(env, gOptions_class, "inJustDecodeBounds", "Z");
+    gOptions_sampleSizeFieldID = getFieldIDCheck(env, gOptions_class, "inSampleSize", "I");
+    gOptions_configFieldID = getFieldIDCheck(env, gOptions_class, "inPreferredConfig",
+            "Landroid/graphics/Bitmap$Config;");
+    gOptions_ditherFieldID = getFieldIDCheck(env, gOptions_class, "inDither", "Z");
+    gOptions_widthFieldID = getFieldIDCheck(env, gOptions_class, "outWidth", "I");
+    gOptions_heightFieldID = getFieldIDCheck(env, gOptions_class, "outHeight", "I");
+    gOptions_mimeFieldID = getFieldIDCheck(env, gOptions_class, "outMimeType", "Ljava/lang/String;");
+
+    gFileDescriptor_class = make_globalref(env, "java/io/FileDescriptor");
+    gFileDescriptor_descriptor = getFieldIDCheck(env, gFileDescriptor_class, "descriptor", "I");
+
+    int ret = AndroidRuntime::registerNativeMethods(env,
+                                    "android/graphics/BitmapFactory$Options",
+                                    gOptionsMethods,
+                                    SK_ARRAY_COUNT(gOptionsMethods));
+    if (ret) {
+        return ret;
+    }
+    return android::AndroidRuntime::registerNativeMethods(env, kClassPathName,
+                                         gMethods, SK_ARRAY_COUNT(gMethods));
+}
diff --git a/core/jni/android/graphics/Camera.cpp b/core/jni/android/graphics/Camera.cpp
new file mode 100644
index 0000000..980003e
--- /dev/null
+++ b/core/jni/android/graphics/Camera.cpp
@@ -0,0 +1,102 @@
+#include "jni.h"
+#include <android_runtime/AndroidRuntime.h>
+
+#include "SkCamera.h"
+
+static jfieldID gNativeInstanceFieldID;
+
+static void Camera_constructor(JNIEnv* env, jobject obj) {
+    Sk3DView* view = new Sk3DView;
+    env->SetIntField(obj, gNativeInstanceFieldID, (int)view);
+}
+
+static void Camera_destructor(JNIEnv* env, jobject obj) {
+    delete (Sk3DView*)env->GetIntField(obj, gNativeInstanceFieldID);
+}
+
+static void Camera_save(JNIEnv* env, jobject obj) {
+    Sk3DView* v = (Sk3DView*)env->GetIntField(obj, gNativeInstanceFieldID);
+    v->save();
+}
+
+static void Camera_restore(JNIEnv* env, jobject obj) {
+    Sk3DView* v = (Sk3DView*)env->GetIntField(obj, gNativeInstanceFieldID);
+    v->restore();
+}
+
+static void Camera_translate(JNIEnv* env, jobject obj,
+                             float dx, float dy, float dz) {
+    Sk3DView* v = (Sk3DView*)env->GetIntField(obj, gNativeInstanceFieldID);
+    v->translate(SkFloatToScalar(dx), SkFloatToScalar(dy), SkFloatToScalar(dz));
+}
+
+static void Camera_rotateX(JNIEnv* env, jobject obj, float degrees) {
+    Sk3DView* v = (Sk3DView*)env->GetIntField(obj, gNativeInstanceFieldID);
+    v->rotateX(SkFloatToScalar(degrees));
+}
+
+static void Camera_rotateY(JNIEnv* env, jobject obj, float degrees) {
+    Sk3DView* v = (Sk3DView*)env->GetIntField(obj, gNativeInstanceFieldID);
+    v->rotateY(SkFloatToScalar(degrees));
+}
+
+static void Camera_rotateZ(JNIEnv* env, jobject obj, float degrees) {
+    Sk3DView* v = (Sk3DView*)env->GetIntField(obj, gNativeInstanceFieldID);
+    v->rotateZ(SkFloatToScalar(degrees));
+}
+
+static void Camera_getMatrix(JNIEnv* env, jobject obj, int native_matrix) {
+    Sk3DView* v = (Sk3DView*)env->GetIntField(obj, gNativeInstanceFieldID);
+    v->getMatrix((SkMatrix*)native_matrix);
+}
+
+static void Camera_applyToCanvas(JNIEnv* env, jobject obj, int native_canvas) {
+    Sk3DView* v = (Sk3DView*)env->GetIntField(obj, gNativeInstanceFieldID);
+    v->applyToCanvas((SkCanvas*)native_canvas);
+}
+
+static float Camera_dotWithNormal(JNIEnv* env, jobject obj,
+                                  float x, float y, float z) {
+    Sk3DView* v = (Sk3DView*)env->GetIntField(obj, gNativeInstanceFieldID);
+    SkScalar dot = v->dotWithNormal(SkFloatToScalar(x), SkFloatToScalar(y),
+                                    SkFloatToScalar(z));
+    return SkScalarToFloat(dot);
+}
+
+// ----------------------------------------------------------------------------
+
+/*
+ * JNI registration.
+ */
+static JNINativeMethod gCameraMethods[] = {
+    /* name, signature, funcPtr */
+
+    { "nativeConstructor",   "()V",    (void*)Camera_constructor   },
+    { "nativeDestructor",    "()V",    (void*)Camera_destructor    },
+    { "save",                "()V",    (void*)Camera_save          },
+    { "restore",             "()V",    (void*)Camera_restore       },
+    { "translate",           "(FFF)V", (void*)Camera_translate     },
+    { "rotateX",             "(F)V",   (void*)Camera_rotateX       },
+    { "rotateY",             "(F)V",   (void*)Camera_rotateY       },
+    { "rotateZ",             "(F)V",   (void*)Camera_rotateZ       },
+    { "nativeGetMatrix",     "(I)V",   (void*)Camera_getMatrix     },
+    { "nativeApplyToCanvas", "(I)V",   (void*)Camera_applyToCanvas },
+    { "dotWithNormal",       "(FFF)F", (void*)Camera_dotWithNormal }
+};
+
+int register_android_graphics_Camera(JNIEnv* env);
+int register_android_graphics_Camera(JNIEnv* env) {
+    jclass clazz = env->FindClass("android/graphics/Camera");
+    if (clazz == 0) {
+        return -1;
+    }
+    gNativeInstanceFieldID = env->GetFieldID(clazz, "native_instance", "I");
+    if (gNativeInstanceFieldID == 0) {
+        return -1;
+    }
+    return android::AndroidRuntime::registerNativeMethods(env,
+                                               "android/graphics/Camera",
+                                               gCameraMethods,
+                                               SK_ARRAY_COUNT(gCameraMethods));
+}
+
diff --git a/core/jni/android/graphics/Canvas.cpp b/core/jni/android/graphics/Canvas.cpp
new file mode 100644
index 0000000..605e4b8
--- /dev/null
+++ b/core/jni/android/graphics/Canvas.cpp
@@ -0,0 +1,954 @@
+/*
+ * Copyright (C) 2006-2007 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.
+ */
+
+#include "jni.h"
+#include "GraphicsJNI.h"
+#include <android_runtime/AndroidRuntime.h>
+
+#include "SkCanvas.h"
+#include "SkDevice.h"
+#include "SkGLCanvas.h"
+#include "SkShader.h"
+#include "SkTemplates.h"
+
+#define TIME_DRAWx
+
+static uint32_t get_thread_msec() {
+#if defined(HAVE_POSIX_CLOCKS)
+    struct timespec tm;
+    
+    clock_gettime(CLOCK_THREAD_CPUTIME_ID, &tm);
+    
+    return tm.tv_sec * 1000LL + tm.tv_nsec / 1000000;
+#else
+    struct timeval tv;
+    
+    gettimeofday(&tv, NULL);
+    return tv.tv_sec * 1000LL + tv.tv_usec / 1000;
+#endif
+}
+
+namespace android {
+
+class SkCanvasGlue {
+public:
+
+    static void finalizer(JNIEnv* env, jobject clazz, SkCanvas* canvas) {
+        canvas->unref();
+    }
+
+    static SkCanvas* initRaster(JNIEnv* env, jobject, SkBitmap* bitmap) {
+        return bitmap ? new SkCanvas(*bitmap) : new SkCanvas;
+    }
+    
+    static SkCanvas* initGL(JNIEnv* env, jobject) {
+        return new SkGLCanvas;
+    }
+    
+    static void freeGlCaches(JNIEnv* env, jobject) {
+        SkGLCanvas::DeleteAllTextures();
+    }
+    
+    static jboolean isOpaque(JNIEnv* env, jobject jcanvas) {
+        NPE_CHECK_RETURN_ZERO(env, jcanvas);
+        SkCanvas* canvas = GraphicsJNI::getNativeCanvas(env, jcanvas);
+
+        /*
+            Currently we cannot support transparency in GL-based canvas' at
+            the view level. Therefore we cannot base our answer on the device's
+            bitmap, but need to hard-code the answer. If we relax this
+            limitation in views, we can simplify the following code as well.
+         
+            Use the getViewport() call to find out if we're gl-based...
+        */
+        if (canvas->getViewport(NULL)) {
+            return true;
+        }
+        
+        // normal technique, rely on the device's bitmap for the answer
+        return canvas->getDevice()->accessBitmap(false).isOpaque();
+    }
+    
+    static int getWidth(JNIEnv* env, jobject jcanvas) {
+        NPE_CHECK_RETURN_ZERO(env, jcanvas);
+        SkCanvas* canvas = GraphicsJNI::getNativeCanvas(env, jcanvas);
+        return canvas->getDevice()->accessBitmap(false).width();
+    }
+    
+    static int getHeight(JNIEnv* env, jobject jcanvas) {
+        NPE_CHECK_RETURN_ZERO(env, jcanvas);
+        SkCanvas* canvas = GraphicsJNI::getNativeCanvas(env, jcanvas);
+        return canvas->getDevice()->accessBitmap(false).height();
+    }
+
+    static void setViewport(JNIEnv* env, jobject, SkCanvas* canvas,
+                            int width, int height) {
+        canvas->setViewport(width, height);
+    }
+    
+    static void setBitmap(JNIEnv* env, jobject, SkCanvas* canvas,
+                          SkBitmap* bitmap) {
+        canvas->setBitmapDevice(*bitmap);
+    }
+ 
+    static int saveAll(JNIEnv* env, jobject jcanvas) {
+        NPE_CHECK_RETURN_ZERO(env, jcanvas);
+        return GraphicsJNI::getNativeCanvas(env, jcanvas)->save();
+    }
+    
+    static int save(JNIEnv* env, jobject jcanvas, SkCanvas::SaveFlags flags) {
+        NPE_CHECK_RETURN_ZERO(env, jcanvas);
+        return GraphicsJNI::getNativeCanvas(env, jcanvas)->save(flags);
+    }
+    
+    static int saveLayer(JNIEnv* env, jobject, SkCanvas* canvas, jobject bounds,
+                         SkPaint* paint, int flags) {
+        SkRect* bounds_ = NULL;
+        SkRect  storage;
+        if (bounds != NULL) {
+            GraphicsJNI::jrectf_to_rect(env, bounds, &storage);
+            bounds_ = &storage;
+        }
+        return canvas->saveLayer(bounds_, paint, (SkCanvas::SaveFlags)flags);
+    }
+ 
+    static int saveLayer4F(JNIEnv* env, jobject, SkCanvas* canvas,
+                           jfloat l, jfloat t, jfloat r, jfloat b,
+                           SkPaint* paint, int flags) {
+        SkRect bounds;
+        bounds.set(SkFloatToScalar(l), SkFloatToScalar(t), SkFloatToScalar(r),
+                   SkFloatToScalar(b));
+        return canvas->saveLayer(&bounds, paint, (SkCanvas::SaveFlags)flags);
+    }
+ 
+    static int saveLayerAlpha(JNIEnv* env, jobject, SkCanvas* canvas,
+                              jobject bounds, int alpha, int flags) {
+        SkRect* bounds_ = NULL;
+        SkRect  storage;
+        if (bounds != NULL) {
+            GraphicsJNI::jrectf_to_rect(env, bounds, &storage);
+            bounds_ = &storage;
+        }
+        return canvas->saveLayerAlpha(bounds_, alpha,
+                                      (SkCanvas::SaveFlags)flags);
+    }
+ 
+    static int saveLayerAlpha4F(JNIEnv* env, jobject, SkCanvas* canvas,
+                                jfloat l, jfloat t, jfloat r, jfloat b,
+                                int alpha, int flags) {
+        SkRect  bounds;
+        bounds.set(SkFloatToScalar(l), SkFloatToScalar(t), SkFloatToScalar(r),
+                   SkFloatToScalar(b));
+        return canvas->saveLayerAlpha(&bounds, alpha,
+                                      (SkCanvas::SaveFlags)flags);
+    }
+ 
+    static void restore(JNIEnv* env, jobject jcanvas) {
+        NPE_CHECK_RETURN_VOID(env, jcanvas);
+        SkCanvas* canvas = GraphicsJNI::getNativeCanvas(env, jcanvas);
+        if (canvas->getSaveCount() <= 1) {  // cannot restore anymore
+            doThrowISE(env, "Underflow in restore");
+            return;
+        }
+        canvas->restore();
+    }
+ 
+    static int getSaveCount(JNIEnv* env, jobject jcanvas) {
+        NPE_CHECK_RETURN_ZERO(env, jcanvas);
+        return GraphicsJNI::getNativeCanvas(env, jcanvas)->getSaveCount();
+    }
+ 
+    static void restoreToCount(JNIEnv* env, jobject jcanvas, int restoreCount) {
+        NPE_CHECK_RETURN_VOID(env, jcanvas);
+        SkCanvas* canvas = GraphicsJNI::getNativeCanvas(env, jcanvas);
+        if (restoreCount < 1) {
+            doThrowIAE(env, "Underflow in restoreToCount");
+            return;
+        }
+        canvas->restoreToCount(restoreCount);
+    }
+ 
+    static void translate(JNIEnv* env, jobject jcanvas, jfloat dx, jfloat dy) {
+        NPE_CHECK_RETURN_VOID(env, jcanvas);
+        SkScalar dx_ = SkFloatToScalar(dx);
+        SkScalar dy_ = SkFloatToScalar(dy);
+        (void)GraphicsJNI::getNativeCanvas(env, jcanvas)->translate(dx_, dy_);
+    }
+ 
+    static void scale__FF(JNIEnv* env, jobject jcanvas, jfloat sx, jfloat sy) {
+        NPE_CHECK_RETURN_VOID(env, jcanvas);
+        SkScalar sx_ = SkFloatToScalar(sx);
+        SkScalar sy_ = SkFloatToScalar(sy);
+        (void)GraphicsJNI::getNativeCanvas(env, jcanvas)->scale(sx_, sy_);
+    }
+ 
+    static void rotate__F(JNIEnv* env, jobject jcanvas, jfloat degrees) {
+        NPE_CHECK_RETURN_VOID(env, jcanvas);
+        SkScalar degrees_ = SkFloatToScalar(degrees);
+        (void)GraphicsJNI::getNativeCanvas(env, jcanvas)->rotate(degrees_);
+    }
+ 
+    static void skew__FF(JNIEnv* env, jobject jcanvas, jfloat sx, jfloat sy) {
+        NPE_CHECK_RETURN_VOID(env, jcanvas);
+        SkScalar sx_ = SkFloatToScalar(sx);
+        SkScalar sy_ = SkFloatToScalar(sy);
+        (void)GraphicsJNI::getNativeCanvas(env, jcanvas)->skew(sx_, sy_);
+    }
+ 
+    static void concat(JNIEnv* env, jobject, SkCanvas* canvas,
+                       const SkMatrix* matrix) {
+        canvas->concat(*matrix);
+    }
+    
+    static void setMatrix(JNIEnv* env, jobject, SkCanvas* canvas,
+                          const SkMatrix* matrix) {
+        if (NULL == matrix) {
+            canvas->resetMatrix();
+        } else {
+            canvas->setMatrix(*matrix);
+        }
+    }
+    
+    static jboolean clipRect_FFFF(JNIEnv* env, jobject jcanvas, jfloat left,
+                                  jfloat top, jfloat right, jfloat bottom) {
+        NPE_CHECK_RETURN_ZERO(env, jcanvas);
+        SkRect  r;
+        r.set(SkFloatToScalar(left), SkFloatToScalar(top),
+              SkFloatToScalar(right), SkFloatToScalar(bottom));
+        SkCanvas* c = GraphicsJNI::getNativeCanvas(env, jcanvas);
+        return c->clipRect(r);
+    }
+    
+    static jboolean clipRect_IIII(JNIEnv* env, jobject jcanvas, jint left,
+                                  jint top, jint right, jint bottom) {
+        NPE_CHECK_RETURN_ZERO(env, jcanvas);
+        SkRect  r;
+        r.set(SkIntToScalar(left), SkIntToScalar(top),
+              SkIntToScalar(right), SkIntToScalar(bottom));
+        return GraphicsJNI::getNativeCanvas(env, jcanvas)->clipRect(r);
+    }
+    
+    static jboolean clipRect_RectF(JNIEnv* env, jobject jcanvas, jobject rectf) {
+        NPE_CHECK_RETURN_ZERO(env, jcanvas);
+        NPE_CHECK_RETURN_ZERO(env, rectf);
+        SkCanvas* c = GraphicsJNI::getNativeCanvas(env, jcanvas);
+        SkRect tmp;
+        return c->clipRect(*GraphicsJNI::jrectf_to_rect(env, rectf, &tmp));
+    }
+    
+    static jboolean clipRect_Rect(JNIEnv* env, jobject jcanvas, jobject rect) {
+        NPE_CHECK_RETURN_ZERO(env, jcanvas);
+        NPE_CHECK_RETURN_ZERO(env, rect);
+        SkCanvas* c = GraphicsJNI::getNativeCanvas(env, jcanvas);
+        SkRect tmp;
+        return c->clipRect(*GraphicsJNI::jrect_to_rect(env, rect, &tmp));
+    }
+    
+    static jboolean clipRect(JNIEnv* env, jobject, SkCanvas* canvas,
+                             float left, float top, float right, float bottom,
+                             int op) {
+        SkRect rect;
+        rect.set(SkFloatToScalar(left), SkFloatToScalar(top),
+                 SkFloatToScalar(right), SkFloatToScalar(bottom));
+        return canvas->clipRect(rect, (SkRegion::Op)op);
+    }
+ 
+    static jboolean clipPath(JNIEnv* env, jobject, SkCanvas* canvas,
+                             SkPath* path, int op) {
+        return canvas->clipPath(*path, (SkRegion::Op)op);
+    }
+ 
+    static jboolean clipRegion(JNIEnv* env, jobject, SkCanvas* canvas,
+                               SkRegion* deviceRgn, int op) {
+        return canvas->clipRegion(*deviceRgn, (SkRegion::Op)op);
+    }
+    
+    static void setDrawFilter(JNIEnv* env, jobject, SkCanvas* canvas,
+                              SkDrawFilter* filter) {
+        canvas->setDrawFilter(filter);
+    }
+    
+    static jboolean quickReject__RectFI(JNIEnv* env, jobject, SkCanvas* canvas,
+                                        jobject rect, int edgetype) {
+        SkRect rect_;
+        GraphicsJNI::jrectf_to_rect(env, rect, &rect_);
+        return canvas->quickReject(rect_, (SkCanvas::EdgeType)edgetype);
+    }
+ 
+    static jboolean quickReject__PathI(JNIEnv* env, jobject, SkCanvas* canvas,
+                                       SkPath* path, int edgetype) {
+        return canvas->quickReject(*path, (SkCanvas::EdgeType)edgetype);
+    }
+ 
+    static jboolean quickReject__FFFFI(JNIEnv* env, jobject, SkCanvas* canvas,
+                                       jfloat left, jfloat top, jfloat right,
+                                       jfloat bottom, int edgetype) {
+        SkRect r;
+        r.set(SkFloatToScalar(left), SkFloatToScalar(top),
+              SkFloatToScalar(right), SkFloatToScalar(bottom));
+        return canvas->quickReject(r, (SkCanvas::EdgeType)edgetype);
+    }
+ 
+    static void drawRGB(JNIEnv* env, jobject, SkCanvas* canvas,
+                        jint r, jint g, jint b) {
+        canvas->drawARGB(0xFF, r, g, b);
+    }
+ 
+    static void drawARGB(JNIEnv* env, jobject, SkCanvas* canvas,
+                         jint a, jint r, jint g, jint b) {
+        canvas->drawARGB(a, r, g, b);
+    }
+ 
+    static void drawColor__I(JNIEnv* env, jobject, SkCanvas* canvas,
+                             jint color) {
+        canvas->drawColor(color);
+    }
+ 
+    static void drawColor__II(JNIEnv* env, jobject, SkCanvas* canvas,
+                              jint color, SkPorterDuff::Mode mode) {
+        canvas->drawColor(color, mode);
+    }
+ 
+    static void drawPaint(JNIEnv* env, jobject, SkCanvas* canvas,
+                          SkPaint* paint) {
+        canvas->drawPaint(*paint);
+    }
+    
+    static void doPoints(JNIEnv* env, jobject jcanvas, jfloatArray jptsArray,
+                         jint offset, jint count, jobject jpaint,
+                         SkCanvas::PointMode mode) {
+        NPE_CHECK_RETURN_VOID(env, jcanvas);
+        NPE_CHECK_RETURN_VOID(env, jptsArray);
+        NPE_CHECK_RETURN_VOID(env, jpaint);
+        SkCanvas* canvas = GraphicsJNI::getNativeCanvas(env, jcanvas);
+        const SkPaint& paint = *GraphicsJNI::getNativePaint(env, jpaint);
+        
+        AutoJavaFloatArray autoPts(env, jptsArray);
+        float* floats = autoPts.ptr();
+        const int length = autoPts.length();
+        
+        if ((offset | count) < 0 || offset + count > length) {
+            doThrowAIOOBE(env);
+            return;
+        }
+        
+        // now convert the floats into SkPoints
+        count >>= 1;    // now it is the number of points
+        SkAutoSTMalloc<32, SkPoint> storage(count);
+        SkPoint* pts = storage.get();
+        const float* src = floats + offset;
+        for (int i = 0; i < count; i++) {
+            pts[i].set(SkFloatToScalar(src[0]), SkFloatToScalar(src[1]));
+            src += 2;
+        }        
+        canvas->drawPoints(mode, count, pts, paint);
+    }
+    
+    static void drawPoints(JNIEnv* env, jobject jcanvas, jfloatArray jptsArray,
+                           jint offset, jint count, jobject jpaint) {
+        doPoints(env, jcanvas, jptsArray, offset, count, jpaint,
+                 SkCanvas::kPoints_PointMode);
+    }
+    
+    static void drawLines(JNIEnv* env, jobject jcanvas, jfloatArray jptsArray,
+                           jint offset, jint count, jobject jpaint) {
+        doPoints(env, jcanvas, jptsArray, offset, count, jpaint,
+                 SkCanvas::kLines_PointMode);
+    }
+    
+    static void drawPoint(JNIEnv* env, jobject jcanvas, float x, float y,
+                          jobject jpaint) {
+        NPE_CHECK_RETURN_VOID(env, jcanvas);
+        NPE_CHECK_RETURN_VOID(env, jpaint);
+        SkCanvas* canvas = GraphicsJNI::getNativeCanvas(env, jcanvas);
+        const SkPaint& paint = *GraphicsJNI::getNativePaint(env, jpaint);
+        
+        canvas->drawPoint(SkFloatToScalar(x), SkFloatToScalar(y), paint);
+    }
+ 
+    static void drawLine__FFFFPaint(JNIEnv* env, jobject, SkCanvas* canvas,
+                                    jfloat startX, jfloat startY, jfloat stopX,
+                                    jfloat stopY, SkPaint* paint) {
+        canvas->drawLine(SkFloatToScalar(startX), SkFloatToScalar(startY),
+                         SkFloatToScalar(stopX), SkFloatToScalar(stopY),
+                         *paint);
+    }
+ 
+    static void drawRect__RectFPaint(JNIEnv* env, jobject, SkCanvas* canvas,
+                                     jobject rect, SkPaint* paint) {
+        SkRect rect_;
+        GraphicsJNI::jrectf_to_rect(env, rect, &rect_);
+        canvas->drawRect(rect_, *paint);
+    }
+ 
+    static void drawRect__FFFFPaint(JNIEnv* env, jobject, SkCanvas* canvas,
+                                    jfloat left, jfloat top, jfloat right,
+                                    jfloat bottom, SkPaint* paint) {
+        SkScalar left_ = SkFloatToScalar(left);
+        SkScalar top_ = SkFloatToScalar(top);
+        SkScalar right_ = SkFloatToScalar(right);
+        SkScalar bottom_ = SkFloatToScalar(bottom);
+        canvas->drawRectCoords(left_, top_, right_, bottom_, *paint);
+    }
+ 
+    static void drawOval(JNIEnv* env, jobject, SkCanvas* canvas, jobject joval,
+                         SkPaint* paint) {
+        SkRect oval;
+        GraphicsJNI::jrectf_to_rect(env, joval, &oval);
+        canvas->drawOval(oval, *paint);
+    }
+ 
+    static void drawCircle(JNIEnv* env, jobject, SkCanvas* canvas, jfloat cx,
+                           jfloat cy, jfloat radius, SkPaint* paint) {
+        canvas->drawCircle(SkFloatToScalar(cx), SkFloatToScalar(cy),
+                           SkFloatToScalar(radius), *paint);
+    }
+ 
+    static void drawArc(JNIEnv* env, jobject, SkCanvas* canvas, jobject joval,
+                        jfloat startAngle, jfloat sweepAngle,
+                        jboolean useCenter, SkPaint* paint) {
+        SkRect oval;
+        GraphicsJNI::jrectf_to_rect(env, joval, &oval);
+        canvas->drawArc(oval, SkFloatToScalar(startAngle),
+                        SkFloatToScalar(sweepAngle), useCenter, *paint);
+    }
+ 
+    static void drawRoundRect(JNIEnv* env, jobject, SkCanvas* canvas,
+                              jobject jrect, jfloat rx, jfloat ry,
+                              SkPaint* paint) {
+        SkRect rect;
+        GraphicsJNI::jrectf_to_rect(env, jrect, &rect);
+        canvas->drawRoundRect(rect, SkFloatToScalar(rx), SkFloatToScalar(ry),
+                              *paint);
+    }
+ 
+    static void drawPath(JNIEnv* env, jobject, SkCanvas* canvas, SkPath* path,
+                         SkPaint* paint) {
+        canvas->drawPath(*path, *paint);
+    }
+ 
+    static void drawPicture(JNIEnv* env, jobject, SkCanvas* canvas,
+                            SkPicture* picture) {
+        SkASSERT(canvas);
+        SkASSERT(picture);
+        
+#ifdef TIME_DRAW
+        SkMSec now = get_thread_msec(); //SkTime::GetMSecs();
+#endif
+        canvas->drawPicture(*picture);
+#ifdef TIME_DRAW
+        LOGD("---- picture playback %d ms\n", get_thread_msec() - now);
+#endif
+    }
+
+    static void drawBitmap__BitmapFFPaint(JNIEnv* env, jobject jcanvas,
+                                          SkCanvas* canvas, SkBitmap* bitmap,
+                                          jfloat left, jfloat top,
+                                          SkPaint* paint,
+                                          jboolean autoScale, jfloat densityScale) {
+        SkScalar left_ = SkFloatToScalar(left);
+        SkScalar top_ = SkFloatToScalar(top);
+
+        if (!autoScale || densityScale <= 0.0f) {
+            canvas->drawBitmap(*bitmap, left_, top_, paint);
+        } else {
+            canvas->save();
+            SkScalar canvasScale = GraphicsJNI::getCanvasDensityScale(env, jcanvas);
+            SkScalar scale = canvasScale / SkFloatToScalar(densityScale);
+            canvas->scale(scale, scale);
+
+            SkPaint filteredPaint;
+            if (paint) {
+                filteredPaint = *paint;
+            }
+            filteredPaint.setFilterBitmap(true);
+
+            canvas->drawBitmap(*bitmap, left_, top_, &filteredPaint);
+
+            canvas->restore();
+        }
+    }
+
+    static void doDrawBitmap(JNIEnv* env, SkCanvas* canvas, SkBitmap* bitmap,
+                        jobject srcIRect, const SkRect& dst, SkPaint* paint) {
+        SkIRect    src, *srcPtr = NULL;
+
+        if (NULL != srcIRect) {
+            GraphicsJNI::jrect_to_irect(env, srcIRect, &src);
+            srcPtr = &src;
+        }
+        canvas->drawBitmapRect(*bitmap, srcPtr, dst, paint);
+    }
+
+    static void drawBitmapRF(JNIEnv* env, jobject, SkCanvas* canvas,
+                             SkBitmap* bitmap, jobject srcIRect,
+                             jobject dstRectF, SkPaint* paint) {
+        SkRect      dst;
+        GraphicsJNI::jrectf_to_rect(env, dstRectF, &dst);
+        doDrawBitmap(env, canvas, bitmap, srcIRect, dst, paint);
+    }
+    
+    static void drawBitmapRR(JNIEnv* env, jobject, SkCanvas* canvas,
+                             SkBitmap* bitmap, jobject srcIRect,
+                             jobject dstRect, SkPaint* paint) {
+        SkRect      dst;
+        GraphicsJNI::jrect_to_rect(env, dstRect, &dst);
+        doDrawBitmap(env, canvas, bitmap, srcIRect, dst, paint);
+    }
+    
+    static void drawBitmapArray(JNIEnv* env, jobject, SkCanvas* canvas,
+                                jintArray jcolors, int offset, int stride,
+                                jfloat x, jfloat y, int width, int height,
+                                jboolean hasAlpha, SkPaint* paint)
+    {
+        SkBitmap    bitmap;
+        
+        bitmap.setConfig(hasAlpha ? SkBitmap::kARGB_8888_Config :
+                         SkBitmap::kRGB_565_Config, width, height);
+        if (!bitmap.allocPixels()) {
+            return;
+        }
+        
+        if (!GraphicsJNI::SetPixels(env, jcolors, offset, stride,
+                                    0, 0, width, height, bitmap)) {
+            return;
+        }
+        
+        canvas->drawBitmap(bitmap, SkFloatToScalar(x), SkFloatToScalar(y),
+                           paint);
+    }
+    
+    static void drawBitmapMatrix(JNIEnv* env, jobject, SkCanvas* canvas,
+                                 const SkBitmap* bitmap, const SkMatrix* matrix,
+                                 const SkPaint* paint) {
+        canvas->drawBitmapMatrix(*bitmap, *matrix, paint);
+    }
+    
+    static void drawBitmapMesh(JNIEnv* env, jobject, SkCanvas* canvas,
+                          const SkBitmap* bitmap, int meshWidth, int meshHeight,
+                          jfloatArray jverts, int vertIndex, jintArray jcolors,
+                          int colorIndex, const SkPaint* paint) {
+
+        const int ptCount = (meshWidth + 1) * (meshHeight + 1);
+        const int indexCount = meshWidth * meshHeight * 6;
+
+        AutoJavaFloatArray  vertA(env, jverts, vertIndex + (ptCount << 1));
+        AutoJavaIntArray    colorA(env, jcolors, colorIndex + ptCount);
+        
+        /*  Our temp storage holds 2 or 3 arrays.
+            texture points [ptCount * sizeof(SkPoint)]
+            optionally vertex points [ptCount * sizeof(SkPoint)] if we need a
+                copy to convert from float to fixed
+            indices [ptCount * sizeof(uint16_t)]
+        */
+        ssize_t storageSize = ptCount * sizeof(SkPoint); // texs[]
+#ifdef SK_SCALAR_IS_FIXED
+        storageSize += ptCount * sizeof(SkPoint);  // storage for verts
+#endif
+        storageSize += indexCount * sizeof(uint16_t);  // indices[]
+
+        SkAutoMalloc storage(storageSize);
+        SkPoint* texs = (SkPoint*)storage.get();
+        SkPoint* verts;
+        uint16_t* indices;
+#ifdef SK_SCALAR_IS_FLOAT
+        verts = (SkPoint*)(vertA.ptr() + vertIndex);
+        indices = (uint16_t*)(texs + ptCount);
+#else
+        verts = texs + ptCount;
+        indices = (uint16_t*)(verts + ptCount);
+        // convert floats to fixed
+        {
+            const float* src = vertA.ptr() + vertIndex;
+            for (int i = 0; i < ptCount; i++) {
+                verts[i].set(SkFloatToFixed(src[0]), SkFloatToFixed(src[1]));
+                src += 2;
+            }
+        }
+#endif
+
+        // cons up texture coordinates and indices
+        {
+            const SkScalar w = SkIntToScalar(bitmap->width());
+            const SkScalar h = SkIntToScalar(bitmap->height());
+            const SkScalar dx = w / meshWidth;
+            const SkScalar dy = h / meshHeight;
+            
+            SkPoint* texsPtr = texs;
+            SkScalar y = 0;
+            for (int i = 0; i <= meshHeight; i++) {
+                if (i == meshHeight) {
+                    y = h;  // to ensure numerically we hit h exactly
+                }
+                SkScalar x = 0;
+                for (int j = 0; j < meshWidth; j++) {
+                    texsPtr->set(x, y);
+                    texsPtr += 1;
+                    x += dx;
+                }
+                texsPtr->set(w, y);
+                texsPtr += 1;
+                y += dy;
+            }
+            SkASSERT(texsPtr - texs == ptCount);
+        }
+        
+        // cons up indices
+        {
+            uint16_t* indexPtr = indices;
+            int index = 0;
+            for (int i = 0; i < meshHeight; i++) {
+                for (int j = 0; j < meshWidth; j++) {
+                    // lower-left triangle
+                    *indexPtr++ = index;
+                    *indexPtr++ = index + meshWidth + 1;
+                    *indexPtr++ = index + meshWidth + 2;
+                    // upper-right triangle
+                    *indexPtr++ = index;
+                    *indexPtr++ = index + meshWidth + 2;
+                    *indexPtr++ = index + 1;
+                    // bump to the next cell
+                    index += 1;
+                }
+                // bump to the next row
+                index += 1;
+            }
+            SkASSERT(indexPtr - indices == indexCount);
+            SkASSERT((char*)indexPtr - (char*)storage.get() == storageSize);
+        }
+
+        // double-check that we have legal indices
+#ifdef SK_DEBUG
+        {
+            for (int i = 0; i < indexCount; i++) {
+                SkASSERT((unsigned)indices[i] < (unsigned)ptCount);
+            }
+        }
+#endif
+
+        // cons-up a shader for the bitmap
+        SkPaint tmpPaint;
+        if (paint) {
+            tmpPaint = *paint;
+        }
+        SkShader* shader = SkShader::CreateBitmapShader(*bitmap,
+                        SkShader::kClamp_TileMode, SkShader::kClamp_TileMode);
+        tmpPaint.setShader(shader)->safeUnref();
+
+        canvas->drawVertices(SkCanvas::kTriangles_VertexMode, ptCount, verts,
+                             texs, (const SkColor*)colorA.ptr(), NULL, indices,
+                             indexCount, tmpPaint);
+    }
+
+    static void drawVertices(JNIEnv* env, jobject, SkCanvas* canvas,
+                             SkCanvas::VertexMode mode, int vertexCount,
+                             jfloatArray jverts, int vertIndex,
+                             jfloatArray jtexs, int texIndex,
+                             jintArray jcolors, int colorIndex,
+                             jshortArray jindices, int indexIndex,
+                             int indexCount, const SkPaint* paint) {
+
+        AutoJavaFloatArray  vertA(env, jverts, vertIndex + vertexCount);
+        AutoJavaFloatArray  texA(env, jtexs, texIndex + vertexCount);
+        AutoJavaIntArray    colorA(env, jcolors, colorIndex + vertexCount);
+        AutoJavaShortArray  indexA(env, jindices, indexIndex + indexCount);
+
+        const int ptCount = vertexCount >> 1;
+
+        SkPoint* verts;
+        SkPoint* texs = NULL;
+#ifdef SK_SCALAR_IS_FLOAT
+        verts = (SkPoint*)(vertA.ptr() + vertIndex);
+        if (jtexs != NULL) {
+            texs = (SkPoint*)(texA.ptr() + texIndex);
+        }
+#else
+        int count = ptCount;    // for verts
+        if (jtexs != NULL) {
+            count += ptCount;   // += for texs
+        }
+        SkAutoMalloc storage(count * sizeof(SkPoint));
+        verts = (SkPoint*)storage.get();        
+        const float* src = vertA.ptr() + vertIndex;
+        for (int i = 0; i < ptCount; i++) {
+            verts[i].set(SkFloatToFixed(src[0]), SkFloatToFixed(src[1]));
+            src += 2;
+        }
+        if (jtexs != NULL) {
+            texs = verts + ptCount;
+            src = texA.ptr() + texIndex;
+            for (int i = 0; i < ptCount; i++) {
+                texs[i].set(SkFloatToFixed(src[0]), SkFloatToFixed(src[1]));
+                src += 2;
+            }
+        }
+#endif
+
+        const SkColor* colors = NULL;
+        const uint16_t* indices = NULL;
+        if (jcolors != NULL) {
+            colors = (const SkColor*)(colorA.ptr() + colorIndex);
+        }
+        if (jindices != NULL) {
+            indices = (const uint16_t*)(indexA.ptr() + indexIndex);
+        }
+
+        canvas->drawVertices(mode, ptCount, verts, texs, colors, NULL,
+                             indices, indexCount, *paint);
+    }
+    
+    static void drawText___CIIFFPaint(JNIEnv* env, jobject, SkCanvas* canvas,
+                                      jcharArray text, int index, int count,
+                                      jfloat x, jfloat y, SkPaint* paint) {
+        jchar* textArray = env->GetCharArrayElements(text, NULL);
+        jsize textCount = env->GetArrayLength(text);
+        SkScalar x_ = SkFloatToScalar(x);
+        SkScalar y_ = SkFloatToScalar(y);
+        textArray += index;
+        canvas->drawText(textArray, count << 1, x_, y_, *paint);
+        env->ReleaseCharArrayElements(text, textArray, 0);
+    }
+ 
+    static void drawText__StringIIFFPaint(JNIEnv* env, jobject,
+                            SkCanvas* canvas, jstring text, int start, int end,
+                                          jfloat x, jfloat y, SkPaint* paint) {
+        const void* text_ = env->GetStringChars(text, NULL);
+        SkScalar x_ = SkFloatToScalar(x);
+        SkScalar y_ = SkFloatToScalar(y);
+        canvas->drawText((const uint16_t*)text_ + start, (end - start) << 1,
+                         x_, y_, *paint);
+        env->ReleaseStringChars(text, (const jchar*) text_);
+    }
+    
+    static void drawString(JNIEnv* env, jobject canvas, jstring text,
+                           jfloat x, jfloat y, jobject paint) {
+        NPE_CHECK_RETURN_VOID(env, canvas);
+        NPE_CHECK_RETURN_VOID(env, paint);
+        NPE_CHECK_RETURN_VOID(env, text);
+        size_t count = env->GetStringLength(text);
+        if (0 == count) {
+            return;
+        }
+        const jchar* text_ = env->GetStringChars(text, NULL);
+        SkCanvas* c = GraphicsJNI::getNativeCanvas(env, canvas);
+        c->drawText(text_, count << 1, SkFloatToScalar(x), SkFloatToScalar(y),
+                    *GraphicsJNI::getNativePaint(env, paint));
+        env->ReleaseStringChars(text, text_);
+    }
+    
+    static void drawPosText___CII_FPaint(JNIEnv* env, jobject, SkCanvas* canvas,
+                                         jcharArray text, int index, int count,
+                                         jfloatArray pos, SkPaint* paint) {
+        jchar* textArray = text ? env->GetCharArrayElements(text, NULL) : NULL;
+        jsize textCount = text ? env->GetArrayLength(text) : NULL;
+        float* posArray = pos ? env->GetFloatArrayElements(pos, NULL) : NULL;
+        int posCount = pos ? env->GetArrayLength(pos) >> 1: 0;
+        SkPoint* posPtr = posCount > 0 ? new SkPoint[posCount] : NULL;
+        int indx;
+        for (indx = 0; indx < posCount; indx++) {
+            posPtr[indx].fX = SkFloatToScalar(posArray[indx << 1]);
+            posPtr[indx].fY = SkFloatToScalar(posArray[(indx << 1) + 1]);
+        }
+        textArray += index;
+        canvas->drawPosText(textArray, count << 1, posPtr, *paint);
+        if (text) {
+            env->ReleaseCharArrayElements(text, textArray, 0);
+        }
+        if (pos) {
+            env->ReleaseFloatArrayElements(pos, posArray, 0);
+        }
+        delete[] posPtr;
+    }
+ 
+    static void drawPosText__String_FPaint(JNIEnv* env, jobject,
+                                           SkCanvas* canvas, jstring text,
+                                           jfloatArray pos, SkPaint* paint) {
+        const void* text_ = text ? env->GetStringChars(text, NULL) : NULL;
+        int byteLength = text ? env->GetStringLength(text) : 0;
+        float* posArray = pos ? env->GetFloatArrayElements(pos, NULL) : NULL;
+        int posCount = pos ? env->GetArrayLength(pos) >> 1: 0;
+        SkPoint* posPtr = posCount > 0 ? new SkPoint[posCount] : NULL;
+
+        for (int indx = 0; indx < posCount; indx++) {
+            posPtr[indx].fX = SkFloatToScalar(posArray[indx << 1]);
+            posPtr[indx].fY = SkFloatToScalar(posArray[(indx << 1) + 1]);
+        }
+        canvas->drawPosText(text_, byteLength << 1, posPtr, *paint);
+        if (text) {
+            env->ReleaseStringChars(text, (const jchar*) text_);
+        }
+        if (pos) {
+            env->ReleaseFloatArrayElements(pos, posArray, 0);
+        }
+        delete[] posPtr;
+    }
+ 
+    static void drawTextOnPath___CIIPathFFPaint(JNIEnv* env, jobject,
+                        SkCanvas* canvas, jcharArray text, int index, int count,
+                SkPath* path, jfloat hOffset, jfloat vOffset, SkPaint* paint) {
+
+        jchar* textArray = env->GetCharArrayElements(text, NULL);
+        canvas->drawTextOnPathHV(textArray + index, count << 1, *path,
+                    SkFloatToScalar(hOffset), SkFloatToScalar(vOffset), *paint);
+        env->ReleaseCharArrayElements(text, textArray, 0);
+    }
+ 
+    static void drawTextOnPath__StringPathFFPaint(JNIEnv* env, jobject,
+                            SkCanvas* canvas, jstring text, SkPath* path,
+                            jfloat hOffset, jfloat vOffset, SkPaint* paint) {
+        const jchar* text_ = env->GetStringChars(text, NULL);
+        int byteLength = env->GetStringLength(text) << 1;
+        canvas->drawTextOnPathHV(text_, byteLength, *path,
+                    SkFloatToScalar(hOffset), SkFloatToScalar(vOffset), *paint);
+        env->ReleaseStringChars(text, text_);
+    }
+ 
+    static bool getClipBounds(JNIEnv* env, jobject, SkCanvas* canvas,
+                              jobject bounds) {
+        SkRect   r;
+        SkIRect ir;
+        bool     result = canvas->getClipBounds(&r, SkCanvas::kBW_EdgeType);
+
+        r.round(&ir);
+        (void)GraphicsJNI::irect_to_jrect(ir, env, bounds);
+        return result;
+    }
+
+    static void getCTM(JNIEnv* env, jobject, SkCanvas* canvas,
+                       SkMatrix* matrix) {
+        *matrix = canvas->getTotalMatrix();
+    }
+};
+    
+///////////////////////////////////////////////////////////////////////////////
+
+static JNINativeMethod gCanvasMethods[] = {
+    {"finalizer", "(I)V", (void*) SkCanvasGlue::finalizer},
+    {"initRaster","(I)I", (void*) SkCanvasGlue::initRaster},
+    {"initGL","()I", (void*) SkCanvasGlue::initGL},
+    {"isOpaque","()Z", (void*) SkCanvasGlue::isOpaque},
+    {"getWidth","()I", (void*) SkCanvasGlue::getWidth},
+    {"getHeight","()I", (void*) SkCanvasGlue::getHeight},
+    {"native_setBitmap","(II)V", (void*) SkCanvasGlue::setBitmap},
+    {"nativeSetViewport", "(III)V", (void*) SkCanvasGlue::setViewport},
+    {"save","()I", (void*) SkCanvasGlue::saveAll},
+    {"save","(I)I", (void*) SkCanvasGlue::save},
+    {"native_saveLayer","(ILandroid/graphics/RectF;II)I",
+        (void*) SkCanvasGlue::saveLayer},
+    {"native_saveLayer","(IFFFFII)I", (void*) SkCanvasGlue::saveLayer4F},
+    {"native_saveLayerAlpha","(ILandroid/graphics/RectF;II)I",
+        (void*) SkCanvasGlue::saveLayerAlpha},
+    {"native_saveLayerAlpha","(IFFFFII)I",
+        (void*) SkCanvasGlue::saveLayerAlpha4F},
+    {"restore","()V", (void*) SkCanvasGlue::restore},
+    {"getSaveCount","()I", (void*) SkCanvasGlue::getSaveCount},
+    {"restoreToCount","(I)V", (void*) SkCanvasGlue::restoreToCount},
+    {"translate","(FF)V", (void*) SkCanvasGlue::translate},
+    {"scale","(FF)V", (void*) SkCanvasGlue::scale__FF},
+    {"rotate","(F)V", (void*) SkCanvasGlue::rotate__F},
+    {"skew","(FF)V", (void*) SkCanvasGlue::skew__FF},
+    {"native_concat","(II)V", (void*) SkCanvasGlue::concat},
+    {"native_setMatrix","(II)V", (void*) SkCanvasGlue::setMatrix},
+    {"clipRect","(FFFF)Z", (void*) SkCanvasGlue::clipRect_FFFF},
+    {"clipRect","(IIII)Z", (void*) SkCanvasGlue::clipRect_IIII},
+    {"clipRect","(Landroid/graphics/RectF;)Z",
+        (void*) SkCanvasGlue::clipRect_RectF},
+    {"clipRect","(Landroid/graphics/Rect;)Z",
+        (void*) SkCanvasGlue::clipRect_Rect},
+    {"native_clipRect","(IFFFFI)Z", (void*) SkCanvasGlue::clipRect},
+    {"native_clipPath","(III)Z", (void*) SkCanvasGlue::clipPath},
+    {"native_clipRegion","(III)Z", (void*) SkCanvasGlue::clipRegion},
+    {"nativeSetDrawFilter", "(II)V", (void*) SkCanvasGlue::setDrawFilter},
+    {"native_getClipBounds","(ILandroid/graphics/Rect;)Z",
+        (void*) SkCanvasGlue::getClipBounds},
+    {"native_getCTM", "(II)V", (void*)SkCanvasGlue::getCTM},
+    {"native_quickReject","(ILandroid/graphics/RectF;I)Z",
+        (void*) SkCanvasGlue::quickReject__RectFI},
+    {"native_quickReject","(III)Z", (void*) SkCanvasGlue::quickReject__PathI},
+    {"native_quickReject","(IFFFFI)Z", (void*)SkCanvasGlue::quickReject__FFFFI},
+    {"native_drawRGB","(IIII)V", (void*) SkCanvasGlue::drawRGB},
+    {"native_drawARGB","(IIIII)V", (void*) SkCanvasGlue::drawARGB},
+    {"native_drawColor","(II)V", (void*) SkCanvasGlue::drawColor__I},
+    {"native_drawColor","(III)V", (void*) SkCanvasGlue::drawColor__II},
+    {"native_drawPaint","(II)V", (void*) SkCanvasGlue::drawPaint},
+    {"drawPoint", "(FFLandroid/graphics/Paint;)V",
+    (void*) SkCanvasGlue::drawPoint},
+    {"drawPoints", "([FIILandroid/graphics/Paint;)V",
+        (void*) SkCanvasGlue::drawPoints},
+    {"drawLines", "([FIILandroid/graphics/Paint;)V",
+        (void*) SkCanvasGlue::drawLines},
+    {"native_drawLine","(IFFFFI)V", (void*) SkCanvasGlue::drawLine__FFFFPaint},
+    {"native_drawRect","(ILandroid/graphics/RectF;I)V",
+        (void*) SkCanvasGlue::drawRect__RectFPaint},
+    {"native_drawRect","(IFFFFI)V", (void*) SkCanvasGlue::drawRect__FFFFPaint},
+    {"native_drawOval","(ILandroid/graphics/RectF;I)V",
+        (void*) SkCanvasGlue::drawOval},
+    {"native_drawCircle","(IFFFI)V", (void*) SkCanvasGlue::drawCircle},
+    {"native_drawArc","(ILandroid/graphics/RectF;FFZI)V",
+        (void*) SkCanvasGlue::drawArc},
+    {"native_drawRoundRect","(ILandroid/graphics/RectF;FFI)V",
+        (void*) SkCanvasGlue::drawRoundRect},
+    {"native_drawPath","(III)V", (void*) SkCanvasGlue::drawPath},
+    {"native_drawBitmap","(IIFFIZF)V",
+        (void*) SkCanvasGlue::drawBitmap__BitmapFFPaint},
+    {"native_drawBitmap","(IILandroid/graphics/Rect;Landroid/graphics/RectF;I)V",
+        (void*) SkCanvasGlue::drawBitmapRF},
+    {"native_drawBitmap","(IILandroid/graphics/Rect;Landroid/graphics/Rect;I)V",
+        (void*) SkCanvasGlue::drawBitmapRR},
+    {"native_drawBitmap", "(I[IIIFFIIZI)V",
+    (void*)SkCanvasGlue::drawBitmapArray},
+    
+    {"nativeDrawBitmapMatrix", "(IIII)V",
+        (void*)SkCanvasGlue::drawBitmapMatrix},
+    {"nativeDrawBitmapMesh", "(IIII[FI[III)V",
+        (void*)SkCanvasGlue::drawBitmapMesh},
+    {"nativeDrawVertices", "(III[FI[FI[II[SIII)V",
+        (void*)SkCanvasGlue::drawVertices},
+    {"native_drawText","(I[CIIFFI)V",
+        (void*) SkCanvasGlue::drawText___CIIFFPaint},
+    {"native_drawText","(ILjava/lang/String;IIFFI)V",
+        (void*) SkCanvasGlue::drawText__StringIIFFPaint},
+    {"drawText","(Ljava/lang/String;FFLandroid/graphics/Paint;)V",
+        (void*) SkCanvasGlue::drawString},
+    {"native_drawPosText","(I[CII[FI)V",
+        (void*) SkCanvasGlue::drawPosText___CII_FPaint},
+    {"native_drawPosText","(ILjava/lang/String;[FI)V",
+        (void*) SkCanvasGlue::drawPosText__String_FPaint},
+    {"native_drawTextOnPath","(I[CIIIFFI)V",
+        (void*) SkCanvasGlue::drawTextOnPath___CIIPathFFPaint},
+    {"native_drawTextOnPath","(ILjava/lang/String;IFFI)V",
+        (void*) SkCanvasGlue::drawTextOnPath__StringPathFFPaint},
+    {"native_drawPicture", "(II)V", (void*) SkCanvasGlue::drawPicture},
+
+    {"freeGlCaches", "()V", (void*) SkCanvasGlue::freeGlCaches}
+};
+
+#include <android_runtime/AndroidRuntime.h>
+
+#define REG(env, name, array) \
+    result = android::AndroidRuntime::registerNativeMethods(env, name, array, \
+                                                    SK_ARRAY_COUNT(array));  \
+    if (result < 0) return result
+
+int register_android_graphics_Canvas(JNIEnv* env) {
+    int result;
+
+    REG(env, "android/graphics/Canvas", gCanvasMethods);
+    
+    return result;
+}
+
+}
diff --git a/core/jni/android/graphics/ColorFilter.cpp b/core/jni/android/graphics/ColorFilter.cpp
new file mode 100644
index 0000000..b6ec4a2
--- /dev/null
+++ b/core/jni/android/graphics/ColorFilter.cpp
@@ -0,0 +1,98 @@
+/* libs/android_runtime/android/graphics/ColorFilter.cpp
+**
+** Copyright 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.
+*/
+
+#include "jni.h"
+#include "GraphicsJNI.h"
+#include <android_runtime/AndroidRuntime.h>
+
+#include "SkColorFilter.h"
+#include "SkColorMatrixFilter.h"
+
+namespace android {
+
+class SkColorFilterGlue {
+public:
+
+    static void finalizer(JNIEnv* env, jobject clazz, SkColorFilter* obj) {
+        obj->safeUnref();
+    }
+
+    static SkColorFilter* CreatePorterDuffFilter(JNIEnv* env, jobject,
+                            jint srcColor, SkPorterDuff::Mode porterDuffMode) {
+        return SkColorFilter::CreatePorterDuffFilter(srcColor, porterDuffMode);
+    }
+ 
+    static SkColorFilter* CreateLightingFilter(JNIEnv* env, jobject,
+                                               jint mul, jint add) {
+        return SkColorFilter::CreateLightingFilter(mul, add);
+    }
+    
+    static SkColorFilter* CreateColorMatrixFilter(JNIEnv* env, jobject,
+                                                  jfloatArray jarray) {
+        AutoJavaFloatArray autoArray(env, jarray, 20);
+        const float* src = autoArray.ptr();
+
+#ifdef SK_SCALAR_IS_FIXED
+        SkFixed array[20];
+        for (int i = 0; i < 20; i++) {
+            array[i] = SkFloatToScalar(src[i]);
+        }
+        return new SkColorMatrixFilter(array);
+#else
+        return new SkColorMatrixFilter(src);
+#endif
+    }
+ 
+};
+
+static JNINativeMethod colorfilter_methods[] = {
+    {"finalizer", "(I)V", (void*) SkColorFilterGlue::finalizer}
+};
+
+static JNINativeMethod porterduff_methods[] = {
+    {"native_CreatePorterDuffFilter","(II)I",
+        (void*) SkColorFilterGlue::CreatePorterDuffFilter}
+};
+
+static JNINativeMethod lighting_methods[] = {
+    {"native_CreateLightingFilter","(II)I",
+        (void*) SkColorFilterGlue::CreateLightingFilter}
+};
+
+static JNINativeMethod colormatrix_methods[] = {
+    {"nativeColorMatrixFilter","([F)I",
+        (void*) SkColorFilterGlue::CreateColorMatrixFilter}
+};
+
+#define REG(env, name, array) \
+    result = android::AndroidRuntime::registerNativeMethods(env, name, array, \
+                                                    SK_ARRAY_COUNT(array));  \
+    if (result < 0) return result
+
+
+int register_android_graphics_ColorFilter(JNIEnv* env) {
+    int result;
+    
+    REG(env, "android/graphics/ColorFilter", colorfilter_methods);
+    REG(env, "android/graphics/PorterDuffColorFilter", porterduff_methods);
+    REG(env, "android/graphics/LightingColorFilter", lighting_methods);
+    REG(env, "android/graphics/ColorMatrixColorFilter", colormatrix_methods);
+    
+    return 0;
+}
+
+}
diff --git a/core/jni/android/graphics/CreateJavaOutputStreamAdaptor.cpp b/core/jni/android/graphics/CreateJavaOutputStreamAdaptor.cpp
new file mode 100644
index 0000000..a285def
--- /dev/null
+++ b/core/jni/android/graphics/CreateJavaOutputStreamAdaptor.cpp
@@ -0,0 +1,251 @@
+#include "CreateJavaOutputStreamAdaptor.h"
+
+#define RETURN_NULL_IF_NULL(value) \
+    do { if (!(value)) { SkASSERT(0); return NULL; } } while (false)
+
+static jclass       gInputStream_Clazz;
+static jmethodID    gInputStream_resetMethodID;
+static jmethodID    gInputStream_availableMethodID;
+static jmethodID    gInputStream_readMethodID;
+static jmethodID    gInputStream_skipMethodID;
+
+class JavaInputStreamAdaptor : public SkStream {
+public:
+    JavaInputStreamAdaptor(JNIEnv* env, jobject js, jbyteArray ar)
+        : fEnv(env), fJavaInputStream(js), fJavaByteArray(ar) {
+        SkASSERT(ar);
+        fCapacity   = env->GetArrayLength(ar);
+        SkASSERT(fCapacity > 0);
+        fBytesRead  = 0;
+    }
+    
+	virtual bool rewind() {
+        JNIEnv* env = fEnv;
+        
+        fBytesRead = 0;
+
+        env->CallVoidMethod(fJavaInputStream, gInputStream_resetMethodID);
+        if (env->ExceptionCheck()) {
+            env->ExceptionDescribe();
+            env->ExceptionClear();
+            SkDebugf("------- reset threw an exception\n");
+            return false;
+        }
+        return true;
+    }
+    
+    size_t doRead(void* buffer, size_t size) {
+        JNIEnv* env = fEnv;
+        size_t bytesRead = 0;
+        // read the bytes
+        do {
+            size_t requested = size;
+            if (requested > fCapacity)
+                requested = fCapacity;
+            
+            jint n = env->CallIntMethod(fJavaInputStream,
+                                        gInputStream_readMethodID, fJavaByteArray, 0, requested);
+            if (env->ExceptionCheck()) {
+                env->ExceptionDescribe();
+                env->ExceptionClear();
+                SkDebugf("---- read threw an exception\n");
+                return 0;
+            }
+            
+            if (n <= 0) {
+                break;  // eof
+            }
+            
+            env->GetByteArrayRegion(fJavaByteArray, 0, n,
+                                    reinterpret_cast<jbyte*>(buffer));
+            if (env->ExceptionCheck()) {
+                env->ExceptionDescribe();
+                env->ExceptionClear();
+                SkDebugf("---- read:GetByteArrayRegion threw an exception\n");
+                return 0;
+            }
+            
+            buffer = (void*)((char*)buffer + n);
+            bytesRead += n;
+            size -= n;
+            fBytesRead += n;
+        } while (size != 0);
+        
+        return bytesRead;
+    }
+    
+    size_t doSkip(size_t size) {
+        JNIEnv* env = fEnv;
+        jlong skipped = env->CallLongMethod(fJavaInputStream,
+                                            gInputStream_skipMethodID, (jlong)size);
+        if (env->ExceptionCheck()) {
+            env->ExceptionDescribe();
+            env->ExceptionClear();
+            SkDebugf("------- available threw an exception\n");
+            return 0;
+        }
+        if (skipped < 0) {
+            skipped = 0;
+        }
+        return (size_t)skipped;
+    }
+    
+    size_t doSize() {
+        JNIEnv* env = fEnv;
+        jint avail = env->CallIntMethod(fJavaInputStream,
+                                        gInputStream_availableMethodID);
+        if (env->ExceptionCheck()) {
+            env->ExceptionDescribe();
+            env->ExceptionClear();
+            SkDebugf("------- available threw an exception\n");
+            avail = 0;
+        }
+        return avail;
+    }
+    
+	virtual size_t read(void* buffer, size_t size) {
+        JNIEnv* env = fEnv;
+        if (NULL == buffer) {
+            if (0 == size) {
+                return this->doSize();
+            } else {
+                /*  InputStream.skip(n) can return <=0 but still not be at EOF
+                    If we see that value, we need to call read(), which will
+                    block if waiting for more data, or return -1 at EOF
+                 */
+                size_t amountSkipped = 0;
+                do {
+                    size_t amount = this->doSkip(size);
+                    if (0 == amount) {
+                        char tmp;
+                        amount = this->doRead(&tmp, 1);
+                        if (0 == amount) {
+                            // if read returned 0, we're at EOF
+                            break;
+                        }
+                    }
+                    amountSkipped += amount;
+                } while (amountSkipped < size);
+                return amountSkipped;
+            }
+        }
+        return this->doRead(buffer, size);
+    }
+    
+private:
+    JNIEnv*     fEnv;
+    jobject     fJavaInputStream;   // the caller owns this object
+    jbyteArray  fJavaByteArray;     // the caller owns this object
+    size_t      fCapacity;
+    size_t      fBytesRead;
+};
+
+SkStream* CreateJavaInputStreamAdaptor(JNIEnv* env, jobject stream,
+                                       jbyteArray storage) {
+    static bool gInited;
+
+    if (!gInited) {
+        gInputStream_Clazz = env->FindClass("java/io/InputStream");
+        RETURN_NULL_IF_NULL(gInputStream_Clazz);
+        gInputStream_Clazz = (jclass)env->NewGlobalRef(gInputStream_Clazz);
+
+        gInputStream_resetMethodID      = env->GetMethodID(gInputStream_Clazz,
+                                                           "reset", "()V");
+        gInputStream_availableMethodID  = env->GetMethodID(gInputStream_Clazz,
+                                                           "available", "()I");
+        gInputStream_readMethodID       = env->GetMethodID(gInputStream_Clazz,
+                                                           "read", "([BII)I");
+        gInputStream_skipMethodID       = env->GetMethodID(gInputStream_Clazz,
+                                                           "skip", "(J)J");
+
+        RETURN_NULL_IF_NULL(gInputStream_resetMethodID);
+        RETURN_NULL_IF_NULL(gInputStream_availableMethodID);
+        RETURN_NULL_IF_NULL(gInputStream_availableMethodID);
+        RETURN_NULL_IF_NULL(gInputStream_skipMethodID);
+
+        gInited = true;
+    }
+
+    return new JavaInputStreamAdaptor(env, stream, storage);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+static jclass       gOutputStream_Clazz;
+static jmethodID    gOutputStream_writeMethodID;
+static jmethodID    gOutputStream_flushMethodID;
+
+class SkJavaOutputStream : public SkWStream {
+public:
+    SkJavaOutputStream(JNIEnv* env, jobject stream, jbyteArray storage)
+        : fEnv(env), fJavaOutputStream(stream), fJavaByteArray(storage) {
+        fCapacity = env->GetArrayLength(storage);
+    }
+    
+	virtual bool write(const void* buffer, size_t size) {
+        JNIEnv* env = fEnv;
+        jbyteArray storage = fJavaByteArray;
+        
+        while (size > 0) {
+            size_t requested = size;
+            if (requested > fCapacity) {
+                requested = fCapacity;
+            }
+
+            env->SetByteArrayRegion(storage, 0, requested,
+                                    reinterpret_cast<const jbyte*>(buffer));
+            if (env->ExceptionCheck()) {
+                env->ExceptionDescribe();
+                env->ExceptionClear();
+                SkDebugf("--- write:SetByteArrayElements threw an exception\n");
+                return false;
+            }
+            
+            fEnv->CallVoidMethod(fJavaOutputStream, gOutputStream_writeMethodID,
+                                 storage, 0, requested);
+            if (env->ExceptionCheck()) {
+                env->ExceptionDescribe();
+                env->ExceptionClear();
+                SkDebugf("------- write threw an exception\n");
+                return false;
+            }
+            
+            buffer = (void*)((char*)buffer + requested);
+            size -= requested;
+        }
+        return true;
+    }
+    
+    virtual void flush() {
+        fEnv->CallVoidMethod(fJavaOutputStream, gOutputStream_flushMethodID);
+    }
+    
+private:
+    JNIEnv*     fEnv;
+    jobject     fJavaOutputStream;  // the caller owns this object
+    jbyteArray  fJavaByteArray;     // the caller owns this object
+    size_t      fCapacity;
+};
+
+SkWStream* CreateJavaOutputStreamAdaptor(JNIEnv* env, jobject stream,
+                                         jbyteArray storage) {
+    static bool gInited;
+
+    if (!gInited) {
+        gOutputStream_Clazz = env->FindClass("java/io/OutputStream");
+        RETURN_NULL_IF_NULL(gOutputStream_Clazz);
+        gOutputStream_Clazz = (jclass)env->NewGlobalRef(gOutputStream_Clazz);
+
+        gOutputStream_writeMethodID = env->GetMethodID(gOutputStream_Clazz,
+                                                       "write", "([BII)V");
+        RETURN_NULL_IF_NULL(gOutputStream_writeMethodID);
+        gOutputStream_flushMethodID = env->GetMethodID(gOutputStream_Clazz,
+                                                       "flush", "()V");
+        RETURN_NULL_IF_NULL(gOutputStream_flushMethodID);
+
+        gInited = true;
+    }
+
+    return new SkJavaOutputStream(env, stream, storage);
+}
+
diff --git a/core/jni/android/graphics/CreateJavaOutputStreamAdaptor.h b/core/jni/android/graphics/CreateJavaOutputStreamAdaptor.h
new file mode 100644
index 0000000..cf21dde
--- /dev/null
+++ b/core/jni/android/graphics/CreateJavaOutputStreamAdaptor.h
@@ -0,0 +1,13 @@
+#ifndef CreateJavaOutputStream_DEFINED
+#define CreateJavaOutputStream_DEFINED
+
+//#include <android_runtime/AndroidRuntime.h>
+#include "jni.h"
+#include "SkStream.h"
+
+SkStream* CreateJavaInputStreamAdaptor(JNIEnv* env, jobject stream,
+                                       jbyteArray storage);
+SkWStream* CreateJavaOutputStreamAdaptor(JNIEnv* env, jobject stream,
+                                         jbyteArray storage);
+
+#endif
diff --git a/core/jni/android/graphics/DrawFilter.cpp b/core/jni/android/graphics/DrawFilter.cpp
new file mode 100644
index 0000000..496e712
--- /dev/null
+++ b/core/jni/android/graphics/DrawFilter.cpp
@@ -0,0 +1,76 @@
+/* libs/android_runtime/android/graphics/ColorFilter.cpp
+**
+** Copyright 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.
+*/
+
+// This file was generated from the C++ include file: SkColorFilter.h
+// Any changes made to this file will be discarded by the build.
+// To change this file, either edit the include, or device/tools/gluemaker/main.cpp, 
+// or one of the auxilary file specifications in device/tools/gluemaker.
+
+#include "jni.h"
+#include "GraphicsJNI.h"
+#include <android_runtime/AndroidRuntime.h>
+
+#include "SkDrawFilter.h"
+#include "SkPaintFlagsDrawFilter.h"
+#include "SkPaint.h"
+
+namespace android {
+
+class SkDrawFilterGlue {
+public:
+
+    static void finalizer(JNIEnv* env, jobject clazz, SkDrawFilter* obj) {
+        obj->safeUnref();
+    }
+
+    static SkDrawFilter* CreatePaintFlagsDF(JNIEnv* env, jobject clazz,
+                                           int clearFlags, int setFlags) {
+        // trim off any out-of-range bits
+        clearFlags &= SkPaint::kAllFlags;
+        setFlags &= SkPaint::kAllFlags;
+
+        if (clearFlags | setFlags) {
+            return new SkPaintFlagsDrawFilter(clearFlags, setFlags);
+        } else {
+            return NULL;
+        }
+    }
+};
+
+static JNINativeMethod drawfilter_methods[] = {
+    {"nativeDestructor", "(I)V", (void*) SkDrawFilterGlue::finalizer}
+};
+
+static JNINativeMethod paintflags_methods[] = {
+    {"nativeConstructor","(II)I", (void*) SkDrawFilterGlue::CreatePaintFlagsDF}
+};
+
+#define REG(env, name, array)                                                                       \
+    result = android::AndroidRuntime::registerNativeMethods(env, name, array, SK_ARRAY_COUNT(array));  \
+    if (result < 0) return result
+
+
+int register_android_graphics_DrawFilter(JNIEnv* env) {
+    int result;
+    
+    REG(env, "android/graphics/DrawFilter", drawfilter_methods);
+    REG(env, "android/graphics/PaintFlagsDrawFilter", paintflags_methods);
+    
+    return 0;
+}
+
+}
diff --git a/core/jni/android/graphics/Graphics.cpp b/core/jni/android/graphics/Graphics.cpp
new file mode 100644
index 0000000..6eebbdc
--- /dev/null
+++ b/core/jni/android/graphics/Graphics.cpp
@@ -0,0 +1,587 @@
+#include "jni.h"
+#include "GraphicsJNI.h"
+#include "NIOBuffer.h"
+#include "SkPicture.h"
+#include "SkRegion.h"
+#include <android_runtime/AndroidRuntime.h>
+
+//#define TRACK_LOCK_COUNT
+
+void doThrow(JNIEnv* env, const char* exc, const char* msg) {
+    // don't throw a new exception if we already have one pending
+    if (env->ExceptionCheck() == JNI_FALSE) {
+        jclass npeClazz;
+        
+        npeClazz = env->FindClass(exc);
+        LOG_FATAL_IF(npeClazz == NULL, "Unable to find class %s", exc);
+        
+        env->ThrowNew(npeClazz, msg);
+    }
+}
+
+void doThrowNPE(JNIEnv* env) {
+    doThrow(env, "java/lang/NullPointerException");
+}
+
+void doThrowAIOOBE(JNIEnv* env) {
+    doThrow(env, "java/lang/ArrayIndexOutOfBoundsException");
+}
+
+void doThrowRE(JNIEnv* env, const char* msg) {
+    doThrow(env, "java/lang/RuntimeException", msg);
+}
+
+void doThrowIAE(JNIEnv* env, const char* msg) {
+    doThrow(env, "java/lang/IllegalArgumentException", msg);
+}
+
+void doThrowISE(JNIEnv* env, const char* msg) {
+    doThrow(env, "java/lang/IllegalStateException", msg);
+}
+
+void doThrowOOME(JNIEnv* env, const char* msg) {
+    doThrow(env, "java/lang/OutOfMemoryError", msg);
+}
+
+bool GraphicsJNI::hasException(JNIEnv *env) {
+    if (env->ExceptionCheck() != 0) {
+        LOGE("*** Uncaught exception returned from Java call!\n");
+        env->ExceptionDescribe();
+        return true;
+    }
+    return false;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+AutoJavaFloatArray::AutoJavaFloatArray(JNIEnv* env, jfloatArray array,
+                                       int minLength)
+: fEnv(env), fArray(array), fPtr(NULL), fLen(0) {
+    SkASSERT(env);
+    if (array) {
+        fLen = env->GetArrayLength(array);
+        if (fLen < minLength) {
+            sk_throw();
+        }
+        fPtr = env->GetFloatArrayElements(array, NULL);
+    }
+}
+
+AutoJavaFloatArray::~AutoJavaFloatArray() {
+    if (fPtr) {
+        fEnv->ReleaseFloatArrayElements(fArray, fPtr, 0);
+    }
+}
+
+AutoJavaIntArray::AutoJavaIntArray(JNIEnv* env, jintArray array,
+                                       int minLength)
+: fEnv(env), fArray(array), fPtr(NULL), fLen(0) {
+    SkASSERT(env);
+    if (array) {
+        fLen = env->GetArrayLength(array);
+        if (fLen < minLength) {
+            sk_throw();
+        }
+        fPtr = env->GetIntArrayElements(array, NULL);
+    }
+}
+
+AutoJavaIntArray::~AutoJavaIntArray() {
+    if (fPtr) {
+        fEnv->ReleaseIntArrayElements(fArray, fPtr, 0);
+    }
+}
+
+AutoJavaShortArray::AutoJavaShortArray(JNIEnv* env, jshortArray array,
+                                       int minLength)
+: fEnv(env), fArray(array), fPtr(NULL), fLen(0) {
+    SkASSERT(env);
+    if (array) {
+        fLen = env->GetArrayLength(array);
+        if (fLen < minLength) {
+            sk_throw();
+        }
+        fPtr = env->GetShortArrayElements(array, NULL);
+    }
+}
+
+AutoJavaShortArray::~AutoJavaShortArray() {
+    if (fPtr) {
+        fEnv->ReleaseShortArrayElements(fArray, fPtr, 0);
+    }
+}
+
+AutoJavaByteArray::AutoJavaByteArray(JNIEnv* env, jbyteArray array,
+                                       int minLength)
+: fEnv(env), fArray(array), fPtr(NULL), fLen(0) {
+    SkASSERT(env);
+    if (array) {
+        fLen = env->GetArrayLength(array);
+        if (fLen < minLength) {
+            sk_throw();
+        }
+        fPtr = env->GetByteArrayElements(array, NULL);
+    }
+}
+
+AutoJavaByteArray::~AutoJavaByteArray() {
+    if (fPtr) {
+        fEnv->ReleaseByteArrayElements(fArray, fPtr, 0);
+    }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+static jclass   gRect_class;
+static jfieldID gRect_leftFieldID;
+static jfieldID gRect_topFieldID;
+static jfieldID gRect_rightFieldID;
+static jfieldID gRect_bottomFieldID;
+
+static jclass   gRectF_class;
+static jfieldID gRectF_leftFieldID;
+static jfieldID gRectF_topFieldID;
+static jfieldID gRectF_rightFieldID;
+static jfieldID gRectF_bottomFieldID;
+
+static jclass   gPoint_class;
+static jfieldID gPoint_xFieldID;
+static jfieldID gPoint_yFieldID;
+
+static jclass   gPointF_class;
+static jfieldID gPointF_xFieldID;
+static jfieldID gPointF_yFieldID;
+
+static jclass   gBitmap_class;
+static jfieldID gBitmap_nativeInstanceID;
+static jmethodID gBitmap_constructorMethodID;
+static jmethodID gBitmap_allocBufferMethodID;
+
+static jclass   gBitmapConfig_class;
+static jfieldID gBitmapConfig_nativeInstanceID;
+
+static jclass   gCanvas_class;
+static jfieldID gCanvas_nativeInstanceID;
+static jfieldID gCanvas_densityScaleID;
+
+static jclass   gPaint_class;
+static jfieldID gPaint_nativeInstanceID;
+
+static jclass   gPicture_class;
+static jfieldID gPicture_nativeInstanceID;
+
+static jclass   gRegion_class;
+static jfieldID gRegion_nativeInstanceID;
+static jmethodID gRegion_constructorMethodID;
+
+static jobject   gVMRuntime_singleton;
+static jmethodID gVMRuntime_trackExternalAllocationMethodID;
+static jmethodID gVMRuntime_trackExternalFreeMethodID;
+
+///////////////////////////////////////////////////////////////////////////////
+
+void GraphicsJNI::get_jrect(JNIEnv* env, jobject obj, int* L, int* T, int* R, int* B)
+{
+    SkASSERT(env->IsInstanceOf(obj, gRect_class));
+
+    *L = env->GetIntField(obj, gRect_leftFieldID);
+    *T = env->GetIntField(obj, gRect_topFieldID);
+    *R = env->GetIntField(obj, gRect_rightFieldID);
+    *B = env->GetIntField(obj, gRect_bottomFieldID);
+}
+
+void GraphicsJNI::set_jrect(JNIEnv* env, jobject obj, int L, int T, int R, int B)
+{
+    SkASSERT(env->IsInstanceOf(obj, gRect_class));
+
+    env->SetIntField(obj, gRect_leftFieldID, L);
+    env->SetIntField(obj, gRect_topFieldID, T);
+    env->SetIntField(obj, gRect_rightFieldID, R);
+    env->SetIntField(obj, gRect_bottomFieldID, B);
+}
+
+SkIRect* GraphicsJNI::jrect_to_irect(JNIEnv* env, jobject obj, SkIRect* ir)
+{
+    SkASSERT(env->IsInstanceOf(obj, gRect_class));
+
+    ir->set(env->GetIntField(obj, gRect_leftFieldID),
+            env->GetIntField(obj, gRect_topFieldID),
+            env->GetIntField(obj, gRect_rightFieldID),
+            env->GetIntField(obj, gRect_bottomFieldID));
+    return ir;
+}
+
+void GraphicsJNI::irect_to_jrect(const SkIRect& ir, JNIEnv* env, jobject obj)
+{
+    SkASSERT(env->IsInstanceOf(obj, gRect_class));
+
+    env->SetIntField(obj, gRect_leftFieldID, ir.fLeft);
+    env->SetIntField(obj, gRect_topFieldID, ir.fTop);
+    env->SetIntField(obj, gRect_rightFieldID, ir.fRight);
+    env->SetIntField(obj, gRect_bottomFieldID, ir.fBottom);
+}
+
+SkRect* GraphicsJNI::jrectf_to_rect(JNIEnv* env, jobject obj, SkRect* r)
+{
+    SkASSERT(env->IsInstanceOf(obj, gRectF_class));
+    
+    r->set(SkFloatToScalar(env->GetFloatField(obj, gRectF_leftFieldID)),
+           SkFloatToScalar(env->GetFloatField(obj, gRectF_topFieldID)),
+           SkFloatToScalar(env->GetFloatField(obj, gRectF_rightFieldID)),
+           SkFloatToScalar(env->GetFloatField(obj, gRectF_bottomFieldID)));
+    return r;
+}
+
+SkRect* GraphicsJNI::jrect_to_rect(JNIEnv* env, jobject obj, SkRect* r)
+{
+    SkASSERT(env->IsInstanceOf(obj, gRect_class));
+    
+    r->set(SkIntToScalar(env->GetIntField(obj, gRect_leftFieldID)),
+           SkIntToScalar(env->GetIntField(obj, gRect_topFieldID)),
+           SkIntToScalar(env->GetIntField(obj, gRect_rightFieldID)),
+           SkIntToScalar(env->GetIntField(obj, gRect_bottomFieldID)));
+    return r;
+}
+
+void GraphicsJNI::rect_to_jrectf(const SkRect& r, JNIEnv* env, jobject obj)
+{
+    SkASSERT(env->IsInstanceOf(obj, gRectF_class));
+
+    env->SetFloatField(obj, gRectF_leftFieldID, SkScalarToFloat(r.fLeft));
+    env->SetFloatField(obj, gRectF_topFieldID, SkScalarToFloat(r.fTop));
+    env->SetFloatField(obj, gRectF_rightFieldID, SkScalarToFloat(r.fRight));
+    env->SetFloatField(obj, gRectF_bottomFieldID, SkScalarToFloat(r.fBottom));
+}
+
+SkIPoint* GraphicsJNI::jpoint_to_ipoint(JNIEnv* env, jobject obj, SkIPoint* point)
+{
+    SkASSERT(env->IsInstanceOf(obj, gPoint_class));
+    
+    point->set(env->GetIntField(obj, gPoint_xFieldID),
+               env->GetIntField(obj, gPoint_yFieldID));
+    return point;
+}
+
+void GraphicsJNI::ipoint_to_jpoint(const SkIPoint& ir, JNIEnv* env, jobject obj)
+{
+    SkASSERT(env->IsInstanceOf(obj, gPoint_class));
+
+    env->SetIntField(obj, gPoint_xFieldID, ir.fX);
+    env->SetIntField(obj, gPoint_yFieldID, ir.fY);
+}
+
+SkPoint* GraphicsJNI::jpointf_to_point(JNIEnv* env, jobject obj, SkPoint* point)
+{
+    SkASSERT(env->IsInstanceOf(obj, gPointF_class));
+        
+    point->set(SkFloatToScalar(env->GetIntField(obj, gPointF_xFieldID)),
+               SkFloatToScalar(env->GetIntField(obj, gPointF_yFieldID)));
+    return point;
+}
+
+void GraphicsJNI::point_to_jpointf(const SkPoint& r, JNIEnv* env, jobject obj)
+{
+    SkASSERT(env->IsInstanceOf(obj, gPointF_class));
+
+    env->SetFloatField(obj, gPointF_xFieldID, SkScalarToFloat(r.fX));
+    env->SetFloatField(obj, gPointF_yFieldID, SkScalarToFloat(r.fY));
+}
+
+SkBitmap* GraphicsJNI::getNativeBitmap(JNIEnv* env, jobject bitmap) {
+    SkASSERT(env);
+    SkASSERT(bitmap);
+    SkASSERT(env->IsInstanceOf(bitmap, gBitmap_class));
+    SkBitmap* b = (SkBitmap*)env->GetIntField(bitmap, gBitmap_nativeInstanceID);
+    SkASSERT(b);
+    return b;
+}
+
+SkBitmap::Config GraphicsJNI::getNativeBitmapConfig(JNIEnv* env,
+                                                    jobject jconfig) {
+    SkASSERT(env);
+    if (NULL == jconfig) {
+        return SkBitmap::kNo_Config;
+    }
+    SkASSERT(env->IsInstanceOf(jconfig, gBitmapConfig_class));
+    int c = env->GetIntField(jconfig, gBitmapConfig_nativeInstanceID);
+    if (c < 0 || c >= SkBitmap::kConfigCount) {
+        c = SkBitmap::kNo_Config;
+    }
+    return static_cast<SkBitmap::Config>(c);
+}
+
+SkCanvas* GraphicsJNI::getNativeCanvas(JNIEnv* env, jobject canvas) {
+    SkASSERT(env);
+    SkASSERT(canvas);
+    SkASSERT(env->IsInstanceOf(canvas, gCanvas_class));
+    SkCanvas* c = (SkCanvas*)env->GetIntField(canvas, gCanvas_nativeInstanceID);
+    SkASSERT(c);
+    return c;
+}
+
+SkScalar GraphicsJNI::getCanvasDensityScale(JNIEnv* env, jobject canvas) {
+    SkASSERT(env);
+    SkASSERT(canvas);
+    SkASSERT(env->IsInstanceOf(canvas, gCanvas_class));
+    return SkFloatToScalar(env->GetFloatField(canvas, gCanvas_densityScaleID));
+}
+
+SkPaint* GraphicsJNI::getNativePaint(JNIEnv* env, jobject paint) {
+    SkASSERT(env);
+    SkASSERT(paint);
+    SkASSERT(env->IsInstanceOf(paint, gPaint_class));
+    SkPaint* p = (SkPaint*)env->GetIntField(paint, gPaint_nativeInstanceID);
+    SkASSERT(p);
+    return p;
+}
+
+SkPicture* GraphicsJNI::getNativePicture(JNIEnv* env, jobject picture)
+{
+    SkASSERT(env);
+    SkASSERT(picture);
+    SkASSERT(env->IsInstanceOf(picture, gPicture_class));
+    SkPicture* p = (SkPicture*)env->GetIntField(picture, gPicture_nativeInstanceID);
+    SkASSERT(p);
+    return p;
+}
+
+SkRegion* GraphicsJNI::getNativeRegion(JNIEnv* env, jobject region)
+{
+    SkASSERT(env);
+    SkASSERT(region);
+    SkASSERT(env->IsInstanceOf(region, gRegion_class));
+    SkRegion* r = (SkRegion*)env->GetIntField(region, gRegion_nativeInstanceID);
+    SkASSERT(r);
+    return r;
+}
+
+///////////////////////////////////////////////////////////////////////////////////////////
+
+jobject GraphicsJNI::createBitmap(JNIEnv* env, SkBitmap* bitmap, bool isMutable,
+                                  jbyteArray ninepatch)
+{
+    SkASSERT(bitmap != NULL);
+    SkASSERT(NULL != bitmap->pixelRef());
+    
+    jobject obj = env->AllocObject(gBitmap_class);
+    if (obj) {
+        env->CallVoidMethod(obj, gBitmap_constructorMethodID,
+                            (jint)bitmap, isMutable, ninepatch);
+        if (hasException(env)) {
+            obj = NULL;
+        }
+    }
+    return obj;
+}
+
+jobject GraphicsJNI::createRegion(JNIEnv* env, SkRegion* region)
+{
+    SkASSERT(region != NULL);
+    jobject obj = env->AllocObject(gRegion_class);
+    if (obj) {
+        env->CallVoidMethod(obj, gRegion_constructorMethodID, (jint)region, 0);
+        if (hasException(env)) {
+            obj = NULL;
+        }
+    }
+    return obj;
+}
+
+#include "SkPixelRef.h"
+
+static JNIEnv* vm2env(JavaVM* vm)
+{
+    JNIEnv* env = NULL;
+    if (vm->GetEnv((void**)&env, JNI_VERSION_1_4) != JNI_OK || NULL == env)
+    {
+        SkDebugf("------- [%p] vm->GetEnv() failed\n", vm);
+        sk_throw();
+    }
+    return env;
+}
+
+#ifdef TRACK_LOCK_COUNT
+    static int gLockCount;
+#endif
+
+///////////////////////////////////////////////////////////////////////////////
+
+#include "SkMallocPixelRef.h"
+
+/*  Extend SkMallocPixelRef to inform the VM when we free up the storage
+*/
+class AndroidPixelRef : public SkMallocPixelRef {
+public:
+    /** Allocate the specified buffer for pixels. The memory is freed when the
+        last owner of this pixelref is gone. Our caller has already informed
+        the VM of our allocation.
+    */
+    AndroidPixelRef(JNIEnv* env, void* storage, size_t size,
+            SkColorTable* ctable) : SkMallocPixelRef(storage, size, ctable) {
+        SkASSERT(storage);
+        SkASSERT(env);
+
+        if (env->GetJavaVM(&fVM) != JNI_OK) {
+            SkDebugf("------ [%p] env->GetJavaVM failed\n", env);
+            sk_throw();
+        }        
+    }
+
+    virtual ~AndroidPixelRef() {
+        JNIEnv* env = vm2env(fVM);
+//        SkDebugf("-------------- inform VM we're releasing %d bytes\n", this->getSize());
+        jlong jsize = this->getSize();  // the VM wants longs for the size
+        env->CallVoidMethod(gVMRuntime_singleton,
+                            gVMRuntime_trackExternalFreeMethodID,
+                            jsize);
+        if (GraphicsJNI::hasException(env)) {
+            env->ExceptionClear();
+        }
+    }
+
+private:
+    JavaVM* fVM;
+};
+
+bool GraphicsJNI::setJavaPixelRef(JNIEnv* env, SkBitmap* bitmap,
+                                  SkColorTable* ctable) {
+    Sk64 size64 = bitmap->getSize64();
+    if (size64.isNeg() || !size64.is32()) {
+        doThrow(env, "java/lang/IllegalArgumentException",
+                     "bitmap size exceeds 32bits");
+        return false;
+    }
+    
+    size_t size = size64.get32();
+    //    SkDebugf("-------------- inform VM we've allocated %d bytes\n", size);
+    jlong jsize = size;  // the VM wants longs for the size
+    bool r = env->CallBooleanMethod(gVMRuntime_singleton,
+                                     gVMRuntime_trackExternalAllocationMethodID,
+                                     jsize);
+    if (GraphicsJNI::hasException(env)) {
+        return false;
+    }
+    if (!r) {
+        LOGE("VM won't let us allocate %zd bytes\n", size);
+        doThrowOOME(env, "bitmap size exceeds VM budget");
+        return false;
+    }
+    
+    // call the version of malloc that returns null on failure
+    void* addr = sk_malloc_flags(size, 0);
+    if (NULL == addr) {
+        //        SkDebugf("-------------- inform VM we're releasing %d bytes which we couldn't allocate\n", size);
+        // we didn't actually allocate it, so inform the VM
+        env->CallVoidMethod(gVMRuntime_singleton,
+                             gVMRuntime_trackExternalFreeMethodID,
+                             jsize);
+        if (!GraphicsJNI::hasException(env)) {
+            doThrowOOME(env, "bitmap size too large for malloc");
+        }
+        return false;
+    }
+    
+    bitmap->setPixelRef(new AndroidPixelRef(env, addr, size, ctable))->unref();
+    // since we're already allocated, we lockPixels right away
+    // HeapAllocator behaves this way too
+    bitmap->lockPixels();
+    return true;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+JavaPixelAllocator::JavaPixelAllocator(JNIEnv* env) : fEnv(env)
+{
+}
+    
+bool JavaPixelAllocator::allocPixelRef(SkBitmap* bitmap, SkColorTable* ctable) {
+    return GraphicsJNI::setJavaPixelRef(fEnv, bitmap, ctable);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+static jclass make_globalref(JNIEnv* env, const char classname[])
+{
+    jclass c = env->FindClass(classname);
+    SkASSERT(c);
+    return (jclass)env->NewGlobalRef(c);
+}
+
+static jfieldID getFieldIDCheck(JNIEnv* env, jclass clazz,
+                                const char fieldname[], const char type[])
+{
+    jfieldID id = env->GetFieldID(clazz, fieldname, type);
+    SkASSERT(id);
+    return id;
+}
+
+int register_android_graphics_Graphics(JNIEnv* env)
+{
+    jmethodID m;
+    jclass c;
+
+    gRect_class = make_globalref(env, "android/graphics/Rect");
+    gRect_leftFieldID = getFieldIDCheck(env, gRect_class, "left", "I");
+    gRect_topFieldID = getFieldIDCheck(env, gRect_class, "top", "I");
+    gRect_rightFieldID = getFieldIDCheck(env, gRect_class, "right", "I");
+    gRect_bottomFieldID = getFieldIDCheck(env, gRect_class, "bottom", "I");
+
+    gRectF_class = make_globalref(env, "android/graphics/RectF");
+    gRectF_leftFieldID = getFieldIDCheck(env, gRectF_class, "left", "F");
+    gRectF_topFieldID = getFieldIDCheck(env, gRectF_class, "top", "F");
+    gRectF_rightFieldID = getFieldIDCheck(env, gRectF_class, "right", "F");
+    gRectF_bottomFieldID = getFieldIDCheck(env, gRectF_class, "bottom", "F");
+
+    gPoint_class = make_globalref(env, "android/graphics/Point");
+    gPoint_xFieldID = getFieldIDCheck(env, gPoint_class, "x", "I");
+    gPoint_yFieldID = getFieldIDCheck(env, gPoint_class, "y", "I");
+
+    gPointF_class = make_globalref(env, "android/graphics/PointF");
+    gPointF_xFieldID = getFieldIDCheck(env, gPointF_class, "x", "F");
+    gPointF_yFieldID = getFieldIDCheck(env, gPointF_class, "y", "F");
+
+    gBitmap_class = make_globalref(env, "android/graphics/Bitmap");
+    gBitmap_nativeInstanceID = getFieldIDCheck(env, gBitmap_class, "mNativeBitmap", "I");    
+    gBitmap_constructorMethodID = env->GetMethodID(gBitmap_class, "<init>",
+                                            "(IZ[B)V");
+
+    gBitmapConfig_class = make_globalref(env, "android/graphics/Bitmap$Config");
+    gBitmapConfig_nativeInstanceID = getFieldIDCheck(env, gBitmapConfig_class,
+                                                     "nativeInt", "I");    
+
+    gCanvas_class = make_globalref(env, "android/graphics/Canvas");
+    gCanvas_nativeInstanceID = getFieldIDCheck(env, gCanvas_class, "mNativeCanvas", "I");
+    gCanvas_densityScaleID = getFieldIDCheck(env, gCanvas_class, "mDensityScale", "F");
+
+    gPaint_class = make_globalref(env, "android/graphics/Paint");
+    gPaint_nativeInstanceID = getFieldIDCheck(env, gPaint_class, "mNativePaint", "I");
+    
+    gPicture_class = make_globalref(env, "android/graphics/Picture");
+    gPicture_nativeInstanceID = getFieldIDCheck(env, gPicture_class, "mNativePicture", "I");
+
+    gRegion_class = make_globalref(env, "android/graphics/Region");
+    gRegion_nativeInstanceID = getFieldIDCheck(env, gRegion_class, "mNativeRegion", "I");
+    gRegion_constructorMethodID = env->GetMethodID(gRegion_class, "<init>",
+        "(II)V");
+    
+    // Get the VMRuntime class.
+    c = env->FindClass("dalvik/system/VMRuntime");
+    SkASSERT(c);
+    // Look up VMRuntime.getRuntime().
+    m = env->GetStaticMethodID(c, "getRuntime", "()Ldalvik/system/VMRuntime;");
+    SkASSERT(m);
+    // Call VMRuntime.getRuntime() and hold onto its result.
+    gVMRuntime_singleton = env->CallStaticObjectMethod(c, m);
+    SkASSERT(gVMRuntime_singleton);
+    gVMRuntime_singleton = (jobject)env->NewGlobalRef(gVMRuntime_singleton);
+    // Look up the VMRuntime methods we'll be using.
+    gVMRuntime_trackExternalAllocationMethodID =
+                        env->GetMethodID(c, "trackExternalAllocation", "(J)Z");
+    gVMRuntime_trackExternalFreeMethodID =
+                            env->GetMethodID(c, "trackExternalFree", "(J)V");
+
+    NIOBuffer::RegisterJNI(env);
+
+    return 0;
+}
+
diff --git a/core/jni/android/graphics/GraphicsJNI.h b/core/jni/android/graphics/GraphicsJNI.h
new file mode 100644
index 0000000..e2dc9ac
--- /dev/null
+++ b/core/jni/android/graphics/GraphicsJNI.h
@@ -0,0 +1,157 @@
+#ifndef GraphicsJNI_DEFINED
+#define GraphicsJNI_DEFINED
+
+#include "SkPoint.h"
+#include "SkRect.h"
+#include "SkBitmap.h"
+#include <jni.h>
+
+class SkCanvas;
+class SkPaint;
+class SkPicture;
+
+class GraphicsJNI {
+public:
+    // returns true if an exception is set (and dumps it out to the Log)
+    static bool hasException(JNIEnv*);
+
+    static void get_jrect(JNIEnv*, jobject jrect, int* L, int* T, int* R, int* B);
+    static void set_jrect(JNIEnv*, jobject jrect, int L, int T, int R, int B);
+
+    static SkIRect* jrect_to_irect(JNIEnv*, jobject jrect, SkIRect*);
+    static void irect_to_jrect(const SkIRect&, JNIEnv*, jobject jrect);
+
+    static SkRect* jrectf_to_rect(JNIEnv*, jobject jrectf, SkRect*);
+    static SkRect* jrect_to_rect(JNIEnv*, jobject jrect, SkRect*);
+    static void rect_to_jrectf(const SkRect&, JNIEnv*, jobject jrectf);
+    
+    static void set_jpoint(JNIEnv*, jobject jrect, int x, int y);
+    
+    static SkIPoint* jpoint_to_ipoint(JNIEnv*, jobject jpoint, SkIPoint* point);
+    static void ipoint_to_jpoint(const SkIPoint& point, JNIEnv*, jobject jpoint);
+    
+    static SkPoint* jpointf_to_point(JNIEnv*, jobject jpointf, SkPoint* point);
+    static void point_to_jpointf(const SkPoint& point, JNIEnv*, jobject jpointf);
+  
+    static SkCanvas* getNativeCanvas(JNIEnv*, jobject canvas);
+    static SkPaint*  getNativePaint(JNIEnv*, jobject paint);
+    static SkBitmap* getNativeBitmap(JNIEnv*, jobject bitmap);
+    static SkPicture* getNativePicture(JNIEnv*, jobject picture);
+    static SkRegion* getNativeRegion(JNIEnv*, jobject region);
+    static SkScalar getCanvasDensityScale(JNIEnv*, jobject canvas);
+    
+    /** Return the corresponding native config from the java Config enum,
+        or kNo_Config if the java object is null.
+    */
+    static SkBitmap::Config getNativeBitmapConfig(JNIEnv*, jobject jconfig);
+    
+    /** Create a java Bitmap object given the native bitmap (required) and optional
+        storage array (may be null). If storage is specified, then it must already be
+        locked, and its native address set as the bitmap's pixels. If storage is null,
+        then the bitmap must be an owner of its natively allocated pixels (via allocPixels).
+        */
+    static jobject createBitmap(JNIEnv* env, SkBitmap* bitmap, bool isMutable,
+                                jbyteArray ninePatch);
+    
+    static jobject createRegion(JNIEnv* env, SkRegion* region);
+
+    /** Set a pixelref for the bitmap (needs setConfig to already be called)
+        Returns true on success. If it returns false, then it failed, and the
+        appropriate exception will have been raised.
+    */
+    static bool setJavaPixelRef(JNIEnv*, SkBitmap*, SkColorTable* ctable);
+
+    /** Copy the colors in colors[] to the bitmap, convert to the correct
+        format along the way.
+    */
+    static bool SetPixels(JNIEnv* env, jintArray colors, int srcOffset,
+                          int srcStride, int x, int y, int width, int height,
+                          const SkBitmap& dstBitmap);
+};
+
+class JavaPixelAllocator : public SkBitmap::Allocator {
+public:
+    JavaPixelAllocator(JNIEnv* env);
+    // overrides
+    virtual bool allocPixelRef(SkBitmap* bitmap, SkColorTable* ctable);
+    
+private:
+    JNIEnv* fEnv;
+};
+
+class AutoJavaFloatArray {
+public:
+    AutoJavaFloatArray(JNIEnv* env, jfloatArray array, int minLength = 0);
+    ~AutoJavaFloatArray();
+    
+    float* ptr() const { return fPtr; }
+    int    length() const { return fLen; }
+    
+private:
+    JNIEnv*     fEnv;
+    jfloatArray fArray;
+    float*      fPtr;
+    int         fLen;
+};
+
+class AutoJavaIntArray {
+public:
+    AutoJavaIntArray(JNIEnv* env, jintArray array, int minLength = 0);
+    ~AutoJavaIntArray();
+    
+    jint* ptr() const { return fPtr; }
+    int    length() const { return fLen; }
+    
+private:
+    JNIEnv*     fEnv;
+    jintArray fArray;
+    jint*      fPtr;
+    int         fLen;
+};
+
+class AutoJavaShortArray {
+public:
+    AutoJavaShortArray(JNIEnv* env, jshortArray array, int minLength = 0);
+    ~AutoJavaShortArray();
+    
+    jshort* ptr() const { return fPtr; }
+    int    length() const { return fLen; }
+    
+private:
+    JNIEnv*     fEnv;
+    jshortArray fArray;
+    jshort*      fPtr;
+    int         fLen;
+};
+
+class AutoJavaByteArray {
+public:
+    AutoJavaByteArray(JNIEnv* env, jbyteArray array, int minLength = 0);
+    ~AutoJavaByteArray();
+    
+    jbyte* ptr() const { return fPtr; }
+    int    length() const { return fLen; }
+    
+private:
+    JNIEnv*     fEnv;
+    jbyteArray fArray;
+    jbyte*      fPtr;
+    int         fLen;
+};
+
+void doThrow(JNIEnv* env, const char* exc, const char* msg = NULL);
+void doThrowNPE(JNIEnv* env);
+void doThrowAIOOBE(JNIEnv* env); // Array Index Out Of Bounds Exception
+void doThrowIAE(JNIEnv* env, const char* msg = NULL);   // Illegal Argument
+void doThrowRE(JNIEnv* env, const char* msg = NULL);   // Runtime
+void doThrowISE(JNIEnv* env, const char* msg = NULL);   // Illegal State
+void doThrowOOME(JNIEnv* env, const char* msg = NULL);   // Out of memory
+
+#define NPE_CHECK_RETURN_ZERO(env, object)    \
+    do { if (NULL == (object)) { doThrowNPE(env); return 0; } } while (0)
+
+#define NPE_CHECK_RETURN_VOID(env, object)    \
+    do { if (NULL == (object)) { doThrowNPE(env); return; } } while (0)
+
+#endif
+
diff --git a/core/jni/android/graphics/Interpolator.cpp b/core/jni/android/graphics/Interpolator.cpp
new file mode 100644
index 0000000..beec351
--- /dev/null
+++ b/core/jni/android/graphics/Interpolator.cpp
@@ -0,0 +1,97 @@
+#include "jni.h"
+#include <android_runtime/AndroidRuntime.h>
+
+#include "GraphicsJNI.h"
+#include "SkInterpolator.h"
+#include "SkTemplates.h"
+
+static SkInterpolator* Interpolator_constructor(JNIEnv* env, jobject clazz, int valueCount, int frameCount)
+{
+    return new SkInterpolator(valueCount, frameCount);
+}
+
+static void Interpolator_destructor(JNIEnv* env, jobject clazz, SkInterpolator* interp)
+{
+    delete interp;
+}
+
+static void Interpolator_reset(JNIEnv* env, jobject clazz, SkInterpolator* interp, int valueCount, int frameCount)
+{
+    interp->reset(valueCount, frameCount);
+}
+
+static void Interpolator_setKeyFrame(JNIEnv* env, jobject clazz, SkInterpolator* interp, int index, int msec, jfloatArray valueArray, jfloatArray blendArray)
+{
+    SkScalar    blendStorage[4];
+    SkScalar*   blend = NULL;
+
+    AutoJavaFloatArray  autoValues(env, valueArray);
+    float* values = autoValues.ptr();
+    int i, n = autoValues.length();
+
+    SkAutoSTMalloc<16, SkScalar>  storage(n);
+    SkScalar*                     scalars = storage.get();
+
+    for (i = 0; i < n; i++)
+        scalars[i] = SkFloatToScalar(values[i]);
+
+    if (blendArray != NULL) {
+        AutoJavaFloatArray autoBlend(env, blendArray, 4);
+        values = autoBlend.ptr();
+        for (i = 0; i < 4; i++)
+            blendStorage[i] = SkFloatToScalar(values[i]);
+        blend = blendStorage;
+    }
+
+    interp->setKeyFrame(index, msec, scalars, blend);
+}
+
+static void Interpolator_setRepeatMirror(JNIEnv* env, jobject clazz, SkInterpolator* interp, float repeatCount, jboolean mirror)
+{
+    if (repeatCount > 32000)
+        repeatCount = 32000;
+
+    interp->setRepeatCount(SkFloatToScalar(repeatCount));
+    interp->setMirror(mirror != 0);
+}
+
+static int Interpolator_timeToValues(JNIEnv* env, jobject clazz, SkInterpolator* interp, int msec, jfloatArray valueArray)
+{
+    SkInterpolatorBase::Result result;
+
+    float* values = valueArray ? env->GetFloatArrayElements(valueArray, NULL) : NULL;
+    result = interp->timeToValues(msec, (SkScalar*)values);
+    
+    if (valueArray) {
+        int n = env->GetArrayLength(valueArray);
+        for (int i = 0; i < n; i++) {
+            values[i] = SkScalarToFloat(*(SkScalar*)&values[i]);
+        }
+        env->ReleaseFloatArrayElements(valueArray, values, 0);
+    }
+    
+    return result;
+}
+
+// ----------------------------------------------------------------------------
+
+/*
+ * JNI registration.
+ */
+static JNINativeMethod gInterpolatorMethods[] = {
+    { "nativeConstructor",      "(II)I",        (void*)Interpolator_constructor     },
+    { "nativeDestructor",       "(I)V",         (void*)Interpolator_destructor      },
+    { "nativeReset",            "(III)V",       (void*)Interpolator_reset           },
+    { "nativeSetKeyFrame",      "(III[F[F)V",   (void*)Interpolator_setKeyFrame     },
+    { "nativeSetRepeatMirror",  "(IFZ)V",       (void*)Interpolator_setRepeatMirror },
+    { "nativeTimeToValues",     "(II[F)I",      (void*)Interpolator_timeToValues    }
+};
+
+int register_android_graphics_Interpolator(JNIEnv* env);
+int register_android_graphics_Interpolator(JNIEnv* env)
+{
+    return android::AndroidRuntime::registerNativeMethods(env,
+                                                       "android/graphics/Interpolator",
+                                                       gInterpolatorMethods,
+                                                       SK_ARRAY_COUNT(gInterpolatorMethods));
+}
diff --git a/core/jni/android/graphics/LayerRasterizer.cpp b/core/jni/android/graphics/LayerRasterizer.cpp
new file mode 100644
index 0000000..7c458899
--- /dev/null
+++ b/core/jni/android/graphics/LayerRasterizer.cpp
@@ -0,0 +1,34 @@
+#include "SkLayerRasterizer.h"
+#include <jni.h>
+
+class SkLayerRasterizerGlue {
+public:
+    static SkRasterizer* create(JNIEnv* env, jobject) {
+        return new SkLayerRasterizer();
+    }
+
+    static void addLayer(JNIEnv* env, jobject, SkLayerRasterizer* layer, const SkPaint* paint, float dx, float dy) {
+        SkASSERT(layer);
+        SkASSERT(paint);
+        layer->addLayer(*paint, SkFloatToScalar(dx), SkFloatToScalar(dy));
+    } 
+};
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+#include <android_runtime/AndroidRuntime.h>
+
+static JNINativeMethod gLayerRasterizerMethods[] = {
+    { "nativeConstructor",  "()I",      (void*)SkLayerRasterizerGlue::create    },
+    { "nativeAddLayer",     "(IIFF)V",  (void*)SkLayerRasterizerGlue::addLayer  }
+};
+
+int register_android_graphics_LayerRasterizer(JNIEnv* env);
+int register_android_graphics_LayerRasterizer(JNIEnv* env)
+{
+    return android::AndroidRuntime::registerNativeMethods(env,
+                                                       "android/graphics/LayerRasterizer",
+                                                       gLayerRasterizerMethods,
+                                                       SK_ARRAY_COUNT(gLayerRasterizerMethods));
+}
+
diff --git a/core/jni/android/graphics/MaskFilter.cpp b/core/jni/android/graphics/MaskFilter.cpp
new file mode 100644
index 0000000..e6048cd
--- /dev/null
+++ b/core/jni/android/graphics/MaskFilter.cpp
@@ -0,0 +1,61 @@
+#include "GraphicsJNI.h"
+#include "SkMaskFilter.h"
+#include "SkBlurMaskFilter.h"
+
+#include <jni.h>
+
+class SkMaskFilterGlue {
+public:
+    static void destructor(JNIEnv* env, jobject, SkMaskFilter* filter) {
+        SkASSERT(filter);
+        filter->unref();
+    }
+
+    static SkMaskFilter* createBlur(JNIEnv* env, jobject, float radius, int blurStyle) {
+        return SkBlurMaskFilter::Create(SkFloatToScalar(radius), (SkBlurMaskFilter::BlurStyle)blurStyle);
+    }
+ 
+    static SkMaskFilter* createEmboss(JNIEnv* env, jobject, jfloatArray dirArray, float ambient, float specular, float radius) {
+        SkScalar direction[3];
+
+        AutoJavaFloatArray autoDir(env, dirArray, 3);
+        float* values = autoDir.ptr();
+        for (int i = 0; i < 3; i++) {
+            direction[i] = SkFloatToScalar(values[i]);
+        }
+
+        return SkBlurMaskFilter::CreateEmboss(direction, SkFloatToScalar(ambient),
+                                              SkFloatToScalar(specular), SkFloatToScalar(radius));
+    }
+};
+
+static JNINativeMethod gMaskFilterMethods[] = {
+    { "nativeDestructor",   "(I)V",     (void*)SkMaskFilterGlue::destructor      }
+};
+
+static JNINativeMethod gBlurMaskFilterMethods[] = {
+    { "nativeConstructor",  "(FI)I",    (void*)SkMaskFilterGlue::createBlur      }
+};
+
+static JNINativeMethod gEmbossMaskFilterMethods[] = {
+    { "nativeConstructor",  "([FFFF)I", (void*)SkMaskFilterGlue::createEmboss    }
+};
+
+#include <android_runtime/AndroidRuntime.h>
+
+#define REG(env, name, array)                                                                       \
+    result = android::AndroidRuntime::registerNativeMethods(env, name, array, SK_ARRAY_COUNT(array));  \
+    if (result < 0) return result
+
+int register_android_graphics_MaskFilter(JNIEnv* env);
+int register_android_graphics_MaskFilter(JNIEnv* env)
+{
+    int result;
+    
+    REG(env, "android/graphics/MaskFilter", gMaskFilterMethods);
+    REG(env, "android/graphics/BlurMaskFilter", gBlurMaskFilterMethods);
+    REG(env, "android/graphics/EmbossMaskFilter", gEmbossMaskFilterMethods);
+    
+    return 0;
+}
+
diff --git a/core/jni/android/graphics/Matrix.cpp b/core/jni/android/graphics/Matrix.cpp
new file mode 100644
index 0000000..b782766
--- /dev/null
+++ b/core/jni/android/graphics/Matrix.cpp
@@ -0,0 +1,412 @@
+/* libs/android_runtime/android/graphics/Matrix.cpp
+**
+** Copyright 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.
+*/
+
+// This file was generated from the C++ include file: SkMatrix.h
+// Any changes made to this file will be discarded by the build.
+// To change this file, either edit the include, or device/tools/gluemaker/main.cpp, 
+// or one of the auxilary file specifications in device/tools/gluemaker.
+
+#include "jni.h"
+#include "GraphicsJNI.h"
+#include <android_runtime/AndroidRuntime.h>
+
+#include "SkMatrix.h"
+#include "SkTemplates.h"
+
+namespace android {
+
+class SkMatrixGlue {
+public:
+
+    static void finalizer(JNIEnv* env, jobject clazz, SkMatrix* obj) {
+        delete obj;
+    }
+
+    static SkMatrix* create(JNIEnv* env, jobject clazz, const SkMatrix* src) {
+        SkMatrix* obj = new SkMatrix();
+        if (src)
+            *obj = *src;
+        else
+            obj->reset();
+        return obj;
+    }
+ 
+    static jboolean isIdentity(JNIEnv* env, jobject clazz, SkMatrix* obj) {
+        return obj->isIdentity();
+    }
+ 
+    static jboolean rectStaysRect(JNIEnv* env, jobject clazz, SkMatrix* obj) {
+        return obj->rectStaysRect();
+    }
+ 
+    static void reset(JNIEnv* env, jobject clazz, SkMatrix* obj) {
+        obj->reset();
+    }
+ 
+    static void set(JNIEnv* env, jobject clazz, SkMatrix* obj, SkMatrix* other) {
+        *obj = *other;
+    }
+ 
+    static void setTranslate(JNIEnv* env, jobject clazz, SkMatrix* obj, jfloat dx, jfloat dy) {
+        SkScalar dx_ = SkFloatToScalar(dx);
+        SkScalar dy_ = SkFloatToScalar(dy);
+        obj->setTranslate(dx_, dy_);
+    }
+ 
+    static void setScale__FFFF(JNIEnv* env, jobject clazz, SkMatrix* obj, jfloat sx, jfloat sy, jfloat px, jfloat py) {
+        SkScalar sx_ = SkFloatToScalar(sx);
+        SkScalar sy_ = SkFloatToScalar(sy);
+        SkScalar px_ = SkFloatToScalar(px);
+        SkScalar py_ = SkFloatToScalar(py);
+        obj->setScale(sx_, sy_, px_, py_);
+    }
+ 
+    static void setScale__FF(JNIEnv* env, jobject clazz, SkMatrix* obj, jfloat sx, jfloat sy) {
+        SkScalar sx_ = SkFloatToScalar(sx);
+        SkScalar sy_ = SkFloatToScalar(sy);
+        obj->setScale(sx_, sy_);
+    }
+ 
+    static void setRotate__FFF(JNIEnv* env, jobject clazz, SkMatrix* obj, jfloat degrees, jfloat px, jfloat py) {
+        SkScalar degrees_ = SkFloatToScalar(degrees);
+        SkScalar px_ = SkFloatToScalar(px);
+        SkScalar py_ = SkFloatToScalar(py);
+        obj->setRotate(degrees_, px_, py_);
+    }
+ 
+    static void setRotate__F(JNIEnv* env, jobject clazz, SkMatrix* obj, jfloat degrees) {
+        SkScalar degrees_ = SkFloatToScalar(degrees);
+        obj->setRotate(degrees_);
+    }
+ 
+    static void setSinCos__FFFF(JNIEnv* env, jobject clazz, SkMatrix* obj, jfloat sinValue, jfloat cosValue, jfloat px, jfloat py) {
+        SkScalar sinValue_ = SkFloatToScalar(sinValue);
+        SkScalar cosValue_ = SkFloatToScalar(cosValue);
+        SkScalar px_ = SkFloatToScalar(px);
+        SkScalar py_ = SkFloatToScalar(py);
+        obj->setSinCos(sinValue_, cosValue_, px_, py_);
+    }
+ 
+    static void setSinCos__FF(JNIEnv* env, jobject clazz, SkMatrix* obj, jfloat sinValue, jfloat cosValue) {
+        SkScalar sinValue_ = SkFloatToScalar(sinValue);
+        SkScalar cosValue_ = SkFloatToScalar(cosValue);
+        obj->setSinCos(sinValue_, cosValue_);
+    }
+ 
+    static void setSkew__FFFF(JNIEnv* env, jobject clazz, SkMatrix* obj, jfloat kx, jfloat ky, jfloat px, jfloat py) {
+        SkScalar kx_ = SkFloatToScalar(kx);
+        SkScalar ky_ = SkFloatToScalar(ky);
+        SkScalar px_ = SkFloatToScalar(px);
+        SkScalar py_ = SkFloatToScalar(py);
+        obj->setSkew(kx_, ky_, px_, py_);
+    }
+ 
+    static void setSkew__FF(JNIEnv* env, jobject clazz, SkMatrix* obj, jfloat kx, jfloat ky) {
+        SkScalar kx_ = SkFloatToScalar(kx);
+        SkScalar ky_ = SkFloatToScalar(ky);
+        obj->setSkew(kx_, ky_);
+    }
+ 
+    static jboolean setConcat(JNIEnv* env, jobject clazz, SkMatrix* obj, SkMatrix* a, SkMatrix* b) {
+        return obj->setConcat(*a, *b);
+    }
+ 
+    static jboolean preTranslate(JNIEnv* env, jobject clazz, SkMatrix* obj, jfloat dx, jfloat dy) {
+        SkScalar dx_ = SkFloatToScalar(dx);
+        SkScalar dy_ = SkFloatToScalar(dy);
+        return obj->preTranslate(dx_, dy_);
+    }
+ 
+    static jboolean preScale__FFFF(JNIEnv* env, jobject clazz, SkMatrix* obj, jfloat sx, jfloat sy, jfloat px, jfloat py) {
+        SkScalar sx_ = SkFloatToScalar(sx);
+        SkScalar sy_ = SkFloatToScalar(sy);
+        SkScalar px_ = SkFloatToScalar(px);
+        SkScalar py_ = SkFloatToScalar(py);
+        return obj->preScale(sx_, sy_, px_, py_);
+    }
+ 
+    static jboolean preScale__FF(JNIEnv* env, jobject clazz, SkMatrix* obj, jfloat sx, jfloat sy) {
+        SkScalar sx_ = SkFloatToScalar(sx);
+        SkScalar sy_ = SkFloatToScalar(sy);
+        return obj->preScale(sx_, sy_);
+    }
+ 
+    static jboolean preRotate__FFF(JNIEnv* env, jobject clazz, SkMatrix* obj, jfloat degrees, jfloat px, jfloat py) {
+        SkScalar degrees_ = SkFloatToScalar(degrees);
+        SkScalar px_ = SkFloatToScalar(px);
+        SkScalar py_ = SkFloatToScalar(py);
+        return obj->preRotate(degrees_, px_, py_);
+    }
+ 
+    static jboolean preRotate__F(JNIEnv* env, jobject clazz, SkMatrix* obj, jfloat degrees) {
+        SkScalar degrees_ = SkFloatToScalar(degrees);
+        return obj->preRotate(degrees_);
+    }
+ 
+    static jboolean preSkew__FFFF(JNIEnv* env, jobject clazz, SkMatrix* obj, jfloat kx, jfloat ky, jfloat px, jfloat py) {
+        SkScalar kx_ = SkFloatToScalar(kx);
+        SkScalar ky_ = SkFloatToScalar(ky);
+        SkScalar px_ = SkFloatToScalar(px);
+        SkScalar py_ = SkFloatToScalar(py);
+        return obj->preSkew(kx_, ky_, px_, py_);
+    }
+ 
+    static jboolean preSkew__FF(JNIEnv* env, jobject clazz, SkMatrix* obj, jfloat kx, jfloat ky) {
+        SkScalar kx_ = SkFloatToScalar(kx);
+        SkScalar ky_ = SkFloatToScalar(ky);
+        return obj->preSkew(kx_, ky_);
+    }
+ 
+    static jboolean preConcat(JNIEnv* env, jobject clazz, SkMatrix* obj, SkMatrix* other) {
+        return obj->preConcat(*other);
+    }
+ 
+    static jboolean postTranslate(JNIEnv* env, jobject clazz, SkMatrix* obj, jfloat dx, jfloat dy) {
+        SkScalar dx_ = SkFloatToScalar(dx);
+        SkScalar dy_ = SkFloatToScalar(dy);
+        return obj->postTranslate(dx_, dy_);
+    }
+ 
+    static jboolean postScale__FFFF(JNIEnv* env, jobject clazz, SkMatrix* obj, jfloat sx, jfloat sy, jfloat px, jfloat py) {
+        SkScalar sx_ = SkFloatToScalar(sx);
+        SkScalar sy_ = SkFloatToScalar(sy);
+        SkScalar px_ = SkFloatToScalar(px);
+        SkScalar py_ = SkFloatToScalar(py);
+        return obj->postScale(sx_, sy_, px_, py_);
+    }
+ 
+    static jboolean postScale__FF(JNIEnv* env, jobject clazz, SkMatrix* obj, jfloat sx, jfloat sy) {
+        SkScalar sx_ = SkFloatToScalar(sx);
+        SkScalar sy_ = SkFloatToScalar(sy);
+        return obj->postScale(sx_, sy_);
+    }
+ 
+    static jboolean postRotate__FFF(JNIEnv* env, jobject clazz, SkMatrix* obj, jfloat degrees, jfloat px, jfloat py) {
+        SkScalar degrees_ = SkFloatToScalar(degrees);
+        SkScalar px_ = SkFloatToScalar(px);
+        SkScalar py_ = SkFloatToScalar(py);
+        return obj->postRotate(degrees_, px_, py_);
+    }
+ 
+    static jboolean postRotate__F(JNIEnv* env, jobject clazz, SkMatrix* obj, jfloat degrees) {
+        SkScalar degrees_ = SkFloatToScalar(degrees);
+        return obj->postRotate(degrees_);
+    }
+ 
+    static jboolean postSkew__FFFF(JNIEnv* env, jobject clazz, SkMatrix* obj, jfloat kx, jfloat ky, jfloat px, jfloat py) {
+        SkScalar kx_ = SkFloatToScalar(kx);
+        SkScalar ky_ = SkFloatToScalar(ky);
+        SkScalar px_ = SkFloatToScalar(px);
+        SkScalar py_ = SkFloatToScalar(py);
+        return obj->postSkew(kx_, ky_, px_, py_);
+    }
+ 
+    static jboolean postSkew__FF(JNIEnv* env, jobject clazz, SkMatrix* matrix, jfloat kx, jfloat ky) {
+        SkScalar kx_ = SkFloatToScalar(kx);
+        SkScalar ky_ = SkFloatToScalar(ky);
+        return matrix->postSkew(kx_, ky_);
+    }
+ 
+    static jboolean postConcat(JNIEnv* env, jobject clazz, SkMatrix* matrix, SkMatrix* other) {
+        return matrix->postConcat(*other);
+    }
+ 
+    static jboolean setRectToRect(JNIEnv* env, jobject clazz, SkMatrix* matrix, jobject src, jobject dst, SkMatrix::ScaleToFit stf) {
+        SkRect src_;
+        GraphicsJNI::jrectf_to_rect(env, src, &src_);
+        SkRect dst_;
+        GraphicsJNI::jrectf_to_rect(env, dst, &dst_);
+        return matrix->setRectToRect(src_, dst_, stf);
+    }
+ 
+    static jboolean setPolyToPoly(JNIEnv* env, jobject clazz, SkMatrix* matrix,
+                                  jfloatArray jsrc, int srcIndex,
+                                  jfloatArray jdst, int dstIndex, int ptCount) {
+        SkASSERT(srcIndex >= 0);
+        SkASSERT(dstIndex >= 0);
+        SkASSERT((unsigned)ptCount <= 4);
+
+        AutoJavaFloatArray autoSrc(env, jsrc, srcIndex + (ptCount << 1));
+        AutoJavaFloatArray autoDst(env, jdst, dstIndex + (ptCount << 1));
+        float* src = autoSrc.ptr() + srcIndex;
+        float* dst = autoDst.ptr() + dstIndex;
+
+#ifdef SK_SCALAR_IS_FIXED        
+        SkPoint srcPt[4], dstPt[4];
+        for (int i = 0; i < ptCount; i++) {
+            int x = i << 1;
+            int y = x + 1;
+            srcPt[i].set(SkFloatToScalar(src[x]), SkFloatToScalar(src[y]));
+            dstPt[i].set(SkFloatToScalar(dst[x]), SkFloatToScalar(dst[y]));
+        }
+        return matrix->setPolyToPoly(srcPt, dstPt, ptCount);
+#else
+        return matrix->setPolyToPoly((const SkPoint*)src, (const SkPoint*)dst,
+                                     ptCount);
+#endif
+    }
+ 
+    static jboolean invert(JNIEnv* env, jobject clazz, SkMatrix* matrix, SkMatrix* inverse) {
+        return matrix->invert(inverse);
+    }
+ 
+    static void mapPoints(JNIEnv* env, jobject clazz, SkMatrix* matrix,
+                              jfloatArray dst, int dstIndex,
+                              jfloatArray src, int srcIndex,
+                              int ptCount, bool isPts) {
+        SkASSERT(ptCount >= 0);
+        AutoJavaFloatArray autoSrc(env, src, srcIndex + (ptCount << 1));
+        AutoJavaFloatArray autoDst(env, dst, dstIndex + (ptCount << 1));
+        float* srcArray = autoSrc.ptr() + srcIndex;
+        float* dstArray = autoDst.ptr() + dstIndex;
+        
+#ifdef SK_SCALAR_IS_FIXED        
+        // we allocate twice the count, 1 set for src, 1 for dst
+        SkAutoSTMalloc<32, SkPoint> storage(ptCount * 2);
+        SkPoint* pts = storage.get();
+        SkPoint* srcPt = pts;
+        SkPoint* dstPt = pts + ptCount;
+        
+        int i;
+        for (i = 0; i < ptCount; i++) {
+            srcPt[i].set(SkFloatToScalar(srcArray[i << 1]),
+                         SkFloatToScalar(srcArray[(i << 1) + 1]));
+        }
+        
+        if (isPts)
+            matrix->mapPoints(dstPt, srcPt, ptCount);
+        else
+            matrix->mapVectors(dstPt, srcPt, ptCount);
+        
+        for (i = 0; i < ptCount; i++) {
+            dstArray[i << 1]  = SkScalarToFloat(dstPt[i].fX);
+            dstArray[(i << 1) + 1]  = SkScalarToFloat(dstPt[i].fY);
+        }
+#else
+        if (isPts)
+            matrix->mapPoints((SkPoint*)dstArray, (const SkPoint*)srcArray,
+                              ptCount);
+        else
+            matrix->mapVectors((SkVector*)dstArray, (const SkVector*)srcArray,
+                               ptCount);
+#endif
+    }
+ 
+    static jboolean mapRect__RectFRectF(JNIEnv* env, jobject clazz, SkMatrix* matrix, jobjectArray dst, jobject src) {
+        SkRect dst_, src_;
+        GraphicsJNI::jrectf_to_rect(env, src, &src_);
+        jboolean rectStaysRect = matrix->mapRect(&dst_, src_);
+        GraphicsJNI::rect_to_jrectf(dst_, env, dst);
+        return rectStaysRect;
+    }
+ 
+    static jfloat mapRadius(JNIEnv* env, jobject clazz, SkMatrix* matrix, jfloat radius) {
+        return SkScalarToFloat(matrix->mapRadius(SkFloatToScalar(radius)));
+    }
+ 
+    static void getValues(JNIEnv* env, jobject clazz, SkMatrix* matrix, jfloatArray values) {
+        AutoJavaFloatArray autoValues(env, values, 9);
+        float* dst = autoValues.ptr();
+
+#ifdef SK_SCALAR_IS_FIXED
+        for (int i = 0; i < 6; i++) {
+            dst[i] = SkFixedToFloat(matrix->get(i));
+        }
+        for (int j = 6; j < 9; j++) {
+            dst[j] = SkFractToFloat(matrix->get(j));
+        }
+#else
+        for (int i = 0; i < 9; i++) {
+            dst[i] = matrix->get(i);
+        }
+#endif
+    }
+ 
+    static void setValues(JNIEnv* env, jobject clazz, SkMatrix* matrix, jfloatArray values) {
+        AutoJavaFloatArray autoValues(env, values, 9);
+        const float* src = autoValues.ptr();
+
+#ifdef SK_SCALAR_IS_FIXED
+        for (int i = 0; i < 6; i++) {
+            matrix->set(i, SkFloatToFixed(src[i]));
+        }
+        for (int j = 6; j < 9; j++) {
+            matrix->set(j, SkFloatToFract(src[j]));
+        }
+#else
+        for (int i = 0; i < 9; i++) {
+            matrix->set(i, src[i]);
+        }
+#endif
+    }
+
+    static jboolean equals(JNIEnv* env, jobject clazz, const SkMatrix* a, const SkMatrix* b) {
+        return *a == *b;
+    }
+ };
+
+static JNINativeMethod methods[] = {
+    {"finalizer", "(I)V", (void*) SkMatrixGlue::finalizer},
+    {"native_create","(I)I", (void*) SkMatrixGlue::create},
+    {"native_isIdentity","(I)Z", (void*) SkMatrixGlue::isIdentity},
+    {"native_rectStaysRect","(I)Z", (void*) SkMatrixGlue::rectStaysRect},
+    {"native_reset","(I)V", (void*) SkMatrixGlue::reset},
+    {"native_set","(II)V", (void*) SkMatrixGlue::set},
+    {"native_setTranslate","(IFF)V", (void*) SkMatrixGlue::setTranslate},
+    {"native_setScale","(IFFFF)V", (void*) SkMatrixGlue::setScale__FFFF},
+    {"native_setScale","(IFF)V", (void*) SkMatrixGlue::setScale__FF},
+    {"native_setRotate","(IFFF)V", (void*) SkMatrixGlue::setRotate__FFF},
+    {"native_setRotate","(IF)V", (void*) SkMatrixGlue::setRotate__F},
+    {"native_setSinCos","(IFFFF)V", (void*) SkMatrixGlue::setSinCos__FFFF},
+    {"native_setSinCos","(IFF)V", (void*) SkMatrixGlue::setSinCos__FF},
+    {"native_setSkew","(IFFFF)V", (void*) SkMatrixGlue::setSkew__FFFF},
+    {"native_setSkew","(IFF)V", (void*) SkMatrixGlue::setSkew__FF},
+    {"native_setConcat","(III)Z", (void*) SkMatrixGlue::setConcat},
+    {"native_preTranslate","(IFF)Z", (void*) SkMatrixGlue::preTranslate},
+    {"native_preScale","(IFFFF)Z", (void*) SkMatrixGlue::preScale__FFFF},
+    {"native_preScale","(IFF)Z", (void*) SkMatrixGlue::preScale__FF},
+    {"native_preRotate","(IFFF)Z", (void*) SkMatrixGlue::preRotate__FFF},
+    {"native_preRotate","(IF)Z", (void*) SkMatrixGlue::preRotate__F},
+    {"native_preSkew","(IFFFF)Z", (void*) SkMatrixGlue::preSkew__FFFF},
+    {"native_preSkew","(IFF)Z", (void*) SkMatrixGlue::preSkew__FF},
+    {"native_preConcat","(II)Z", (void*) SkMatrixGlue::preConcat},
+    {"native_postTranslate","(IFF)Z", (void*) SkMatrixGlue::postTranslate},
+    {"native_postScale","(IFFFF)Z", (void*) SkMatrixGlue::postScale__FFFF},
+    {"native_postScale","(IFF)Z", (void*) SkMatrixGlue::postScale__FF},
+    {"native_postRotate","(IFFF)Z", (void*) SkMatrixGlue::postRotate__FFF},
+    {"native_postRotate","(IF)Z", (void*) SkMatrixGlue::postRotate__F},
+    {"native_postSkew","(IFFFF)Z", (void*) SkMatrixGlue::postSkew__FFFF},
+    {"native_postSkew","(IFF)Z", (void*) SkMatrixGlue::postSkew__FF},
+    {"native_postConcat","(II)Z", (void*) SkMatrixGlue::postConcat},
+    {"native_setRectToRect","(ILandroid/graphics/RectF;Landroid/graphics/RectF;I)Z", (void*) SkMatrixGlue::setRectToRect},
+    {"native_setPolyToPoly","(I[FI[FII)Z", (void*) SkMatrixGlue::setPolyToPoly},
+    {"native_invert","(II)Z", (void*) SkMatrixGlue::invert},
+    {"native_mapPoints","(I[FI[FIIZ)V", (void*) SkMatrixGlue::mapPoints},
+    {"native_mapRect","(ILandroid/graphics/RectF;Landroid/graphics/RectF;)Z", (void*) SkMatrixGlue::mapRect__RectFRectF},
+    {"native_mapRadius","(IF)F", (void*) SkMatrixGlue::mapRadius},
+    {"native_getValues","(I[F)V", (void*) SkMatrixGlue::getValues},
+    {"native_setValues","(I[F)V", (void*) SkMatrixGlue::setValues},
+    {"native_equals", "(II)Z", (void*) SkMatrixGlue::equals}
+};
+
+int register_android_graphics_Matrix(JNIEnv* env) {
+    int result = AndroidRuntime::registerNativeMethods(env, "android/graphics/Matrix", methods,
+        sizeof(methods) / sizeof(methods[0]));
+    return result;
+}
+
+}
diff --git a/core/jni/android/graphics/Movie.cpp b/core/jni/android/graphics/Movie.cpp
new file mode 100644
index 0000000..de18f9f
--- /dev/null
+++ b/core/jni/android/graphics/Movie.cpp
@@ -0,0 +1,155 @@
+#include "SkMovie.h"
+#include "SkStream.h"
+#include "GraphicsJNI.h"
+#include "SkTemplates.h"
+#include "SkUtils.h"
+#include "CreateJavaOutputStreamAdaptor.h"
+
+#include <utils/Asset.h>
+#include <utils/ResourceTypes.h>
+#include <netinet/in.h>
+
+#if 0
+    #define TRACE_BITMAP(code)  code
+#else
+    #define TRACE_BITMAP(code)
+#endif
+
+static jclass       gMovie_class;
+static jmethodID    gMovie_constructorMethodID;
+static jfieldID     gMovie_nativeInstanceID;
+
+jobject create_jmovie(JNIEnv* env, SkMovie* moov) {
+    if (NULL == moov) {
+        return NULL;
+    }
+    jobject obj = env->AllocObject(gMovie_class);
+    if (obj) {
+        env->CallVoidMethod(obj, gMovie_constructorMethodID, (jint)moov);
+    }
+    return obj;
+}
+
+static SkMovie* J2Movie(JNIEnv* env, jobject movie) {
+    SkASSERT(env);
+    SkASSERT(movie);
+    SkASSERT(env->IsInstanceOf(movie, gMovie_class));
+    SkMovie* m = (SkMovie*)env->GetIntField(movie, gMovie_nativeInstanceID);
+    SkASSERT(m);
+    return m;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+static int movie_width(JNIEnv* env, jobject movie) {
+    NPE_CHECK_RETURN_ZERO(env, movie);
+    return J2Movie(env, movie)->width();
+}
+
+static int movie_height(JNIEnv* env, jobject movie) {
+    NPE_CHECK_RETURN_ZERO(env, movie);
+    return J2Movie(env, movie)->height();
+}
+
+static jboolean movie_isOpaque(JNIEnv* env, jobject movie) {
+    NPE_CHECK_RETURN_ZERO(env, movie);
+    return J2Movie(env, movie)->isOpaque();
+}
+
+static int movie_duration(JNIEnv* env, jobject movie) {
+    NPE_CHECK_RETURN_ZERO(env, movie);
+    return J2Movie(env, movie)->duration();
+}
+
+static jboolean movie_setTime(JNIEnv* env, jobject movie, int ms) {
+    NPE_CHECK_RETURN_ZERO(env, movie);
+    return J2Movie(env, movie)->setTime(ms);
+}
+
+static void movie_draw(JNIEnv* env, jobject movie, jobject canvas,
+                       jfloat fx, jfloat fy, jobject jpaint) {
+    NPE_CHECK_RETURN_VOID(env, movie);
+    NPE_CHECK_RETURN_VOID(env, canvas);
+    // its OK for paint to be null
+
+    SkMovie* m = J2Movie(env, movie);
+    SkCanvas* c = GraphicsJNI::getNativeCanvas(env, canvas);
+    SkScalar sx = SkFloatToScalar(fx);
+    SkScalar sy = SkFloatToScalar(fy);
+    const SkBitmap& b = m->bitmap();
+    const SkPaint* p = jpaint ? GraphicsJNI::getNativePaint(env, jpaint) : NULL;
+
+    c->drawBitmap(b, sx, sy, p);
+}
+
+static jobject movie_decodeStream(JNIEnv* env, jobject clazz, jobject istream) {
+                              
+    NPE_CHECK_RETURN_ZERO(env, istream);
+
+    // what is the lifetime of the array? Can the skstream hold onto it?
+    jbyteArray byteArray = env->NewByteArray(16*1024);
+    SkStream* strm = CreateJavaInputStreamAdaptor(env, istream, byteArray);
+    if (NULL == strm) {
+        return 0;
+    }
+
+    SkMovie* moov = SkMovie::DecodeStream(strm);
+    strm->unref();
+    return create_jmovie(env, moov);
+}
+
+static jobject movie_decodeByteArray(JNIEnv* env, jobject clazz,
+                                     jbyteArray byteArray,
+                                     int offset, int length) {
+                              
+    NPE_CHECK_RETURN_ZERO(env, byteArray);
+
+    int totalLength = env->GetArrayLength(byteArray);
+    if ((offset | length) < 0 || offset + length > totalLength) {
+        doThrow(env, "java/lang/ArrayIndexOutOfBoundsException");
+        return 0;
+    }
+
+    AutoJavaByteArray   ar(env, byteArray);
+    SkMovie* moov = SkMovie::DecodeMemory(ar.ptr() + offset, length);
+    return create_jmovie(env, moov);
+}
+
+//////////////////////////////////////////////////////////////////////////////////////////////
+
+#include <android_runtime/AndroidRuntime.h>
+
+static JNINativeMethod gMethods[] = {
+    {   "width",    "()I",  (void*)movie_width  },
+    {   "height",   "()I",  (void*)movie_height  },
+    {   "isOpaque", "()Z",  (void*)movie_isOpaque  },
+    {   "duration", "()I",  (void*)movie_duration  },
+    {   "setTime",  "(I)Z", (void*)movie_setTime  },
+    {   "draw",     "(Landroid/graphics/Canvas;FFLandroid/graphics/Paint;)V",
+                            (void*)movie_draw  },
+    { "decodeStream", "(Ljava/io/InputStream;)Landroid/graphics/Movie;",
+                            (void*)movie_decodeStream },
+    { "decodeByteArray", "([BII)Landroid/graphics/Movie;",
+                            (void*)movie_decodeByteArray },
+};
+
+#define kClassPathName  "android/graphics/Movie"
+
+#define RETURN_ERR_IF_NULL(value)   do { if (!(value)) { assert(0); return -1; } } while (false)
+
+int register_android_graphics_Movie(JNIEnv* env);
+int register_android_graphics_Movie(JNIEnv* env)
+{
+    gMovie_class = env->FindClass(kClassPathName);
+    RETURN_ERR_IF_NULL(gMovie_class);
+    gMovie_class = (jclass)env->NewGlobalRef(gMovie_class);
+    
+    gMovie_constructorMethodID = env->GetMethodID(gMovie_class, "<init>", "(I)V");
+    RETURN_ERR_IF_NULL(gMovie_constructorMethodID);
+
+    gMovie_nativeInstanceID = env->GetFieldID(gMovie_class, "mNativeMovie", "I");
+    RETURN_ERR_IF_NULL(gMovie_nativeInstanceID);
+
+    return android::AndroidRuntime::registerNativeMethods(env, kClassPathName,
+                                                       gMethods, SK_ARRAY_COUNT(gMethods));
+}
diff --git a/core/jni/android/graphics/NIOBuffer.cpp b/core/jni/android/graphics/NIOBuffer.cpp
new file mode 100644
index 0000000..cb937a3
--- /dev/null
+++ b/core/jni/android/graphics/NIOBuffer.cpp
@@ -0,0 +1,143 @@
+#include "NIOBuffer.h"
+#include "GraphicsJNI.h"
+
+// enable this to dump each time we ref/unref a global java object (buffer)
+//
+//#define TRACE_GLOBAL_REFS
+
+//#define TRACE_ARRAY_LOCKS
+
+static jclass gNIOAccess_classID;
+static jmethodID gNIOAccess_getBasePointer;
+static jmethodID gNIOAccess_getBaseArray;
+static jmethodID gNIOAccess_getBaseArrayOffset;
+static jmethodID gNIOAccess_getRemainingBytes;
+
+void NIOBuffer::RegisterJNI(JNIEnv* env) {
+    if (0 != gNIOAccess_classID) {
+        return; // already called
+    }
+
+    jclass c = env->FindClass("java/nio/NIOAccess");
+    gNIOAccess_classID = (jclass)env->NewGlobalRef(c);
+
+    gNIOAccess_getBasePointer = env->GetStaticMethodID(gNIOAccess_classID,
+                                    "getBasePointer", "(Ljava/nio/Buffer;)J");
+    gNIOAccess_getBaseArray = env->GetStaticMethodID(gNIOAccess_classID,
+                    "getBaseArray", "(Ljava/nio/Buffer;)Ljava/lang/Object;");
+    gNIOAccess_getBaseArrayOffset = env->GetStaticMethodID(gNIOAccess_classID,
+                                "getBaseArrayOffset", "(Ljava/nio/Buffer;)I");
+    gNIOAccess_getRemainingBytes = env->GetStaticMethodID(gNIOAccess_classID,
+                                "getRemainingBytes", "(Ljava/nio/Buffer;)I");
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+#ifdef TRACE_GLOBAL_REFS
+    static int gGlobalRefs;
+#endif
+
+#ifdef TRACE_ARRAY_LOCKS
+    static int gLockCount;
+#endif
+
+NIOBuffer::NIOBuffer(JNIEnv* env, jobject buffer) {
+    fBuffer = env->NewGlobalRef(buffer);
+#ifdef TRACE_GLOBAL_REFS
+    SkDebugf("------------ newglobalref bbuffer %X %d\n", buffer, gGlobalRefs++);
+#endif
+    fLockedPtr = NULL;
+    fLockedArray = NULL;
+}
+
+NIOBuffer::~NIOBuffer() {
+    // free() needs to have already been called
+    if (NULL != fBuffer) {
+        SkDebugf("----- leaked fBuffer in NIOBuffer");
+        sk_throw();
+    }
+}
+
+void NIOBuffer::free(JNIEnv* env) {
+
+    if (NULL != fLockedPtr) {
+        SkDebugf("======= free: array still locked %x %p\n", fLockedArray, fLockedPtr);
+    }
+    
+    
+    if (NULL != fBuffer) {
+#ifdef TRACE_GLOBAL_REFS
+        SkDebugf("----------- deleteglobalref buffer %X %d\n", fBuffer, --gGlobalRefs);
+#endif
+        env->DeleteGlobalRef(fBuffer);
+        fBuffer = NULL;
+    }
+}
+
+void* NIOBuffer::lock(JNIEnv* env, int* remaining) {
+    if (NULL != fLockedPtr) {
+        SkDebugf("======= lock: array still locked %x %p\n", fLockedArray, fLockedPtr);
+    }
+
+    fLockedPtr = NULL;
+    fLockedArray = NULL;
+
+    if (NULL != remaining) {
+        *remaining = env->CallStaticIntMethod(gNIOAccess_classID,
+                                              gNIOAccess_getRemainingBytes,
+                                              fBuffer);
+        if (GraphicsJNI::hasException(env)) {
+            return NULL;
+        }
+    }
+    
+    jlong pointer = env->CallStaticLongMethod(gNIOAccess_classID,
+                                              gNIOAccess_getBasePointer,
+                                              fBuffer);
+    if (GraphicsJNI::hasException(env)) {
+        return NULL;
+    }
+    if (0 != pointer) {
+        return reinterpret_cast<void*>(pointer);
+    }
+    
+    fLockedArray = (jbyteArray)env->CallStaticObjectMethod(gNIOAccess_classID,
+                                                        gNIOAccess_getBaseArray,
+                                                        fBuffer);
+    if (GraphicsJNI::hasException(env) || NULL == fLockedArray) {
+        return NULL;
+    }
+    jint offset = env->CallStaticIntMethod(gNIOAccess_classID,
+                                           gNIOAccess_getBaseArrayOffset,
+                                           fBuffer);
+    fLockedPtr = env->GetByteArrayElements(fLockedArray, NULL);
+    if (GraphicsJNI::hasException(env)) {
+        SkDebugf("------------ failed to lockarray %x\n", fLockedArray);
+        return NULL;
+    }
+#ifdef TRACE_ARRAY_LOCKS
+    SkDebugf("------------ lockarray %x %p %d\n",
+             fLockedArray, fLockedPtr, gLockCount++);
+#endif
+    if (NULL == fLockedPtr) {
+        offset = 0;
+    }
+    return (char*)fLockedPtr + offset;
+}
+
+void NIOBuffer::unlock(JNIEnv* env, bool dataChanged) {
+    if (NULL != fLockedPtr) {
+#ifdef TRACE_ARRAY_LOCKS
+        SkDebugf("------------ unlockarray %x %p %d\n",
+                 fLockedArray, fLockedPtr, --gLockCount);
+#endif
+        env->ReleaseByteArrayElements(fLockedArray, (jbyte*)fLockedPtr,
+                                      dataChanged ? 0 : JNI_ABORT);
+        
+        fLockedPtr = NULL;
+        fLockedArray = NULL;
+    } else {
+        SkDebugf("============= unlock called with null ptr %x\n", fLockedArray);
+    }
+}
+
diff --git a/core/jni/android/graphics/NIOBuffer.h b/core/jni/android/graphics/NIOBuffer.h
new file mode 100644
index 0000000..36b5554
--- /dev/null
+++ b/core/jni/android/graphics/NIOBuffer.h
@@ -0,0 +1,27 @@
+#ifndef NIOBuffer_DEFINED
+#define NIOBuffer_DEFINED
+
+#include <jni.h>
+#include "SkBitmap.h"
+
+class NIOBuffer {
+public:
+    NIOBuffer(JNIEnv* env, jobject buffer);
+    // this checks to ensure that free() was called
+    ~NIOBuffer();
+
+    void* lock(JNIEnv* env, int* remaining);
+    void unlock(JNIEnv* env, bool dataChanged);
+    // must be called before destructor
+    void free(JNIEnv* env);
+
+    // call once on boot, to setup JNI globals
+    static void RegisterJNI(JNIEnv*);
+
+private:
+    jobject     fBuffer;
+    void*       fLockedPtr;
+    jbyteArray  fLockedArray;
+};
+
+#endif
diff --git a/core/jni/android/graphics/NinePatch.cpp b/core/jni/android/graphics/NinePatch.cpp
new file mode 100644
index 0000000..b11edfc
--- /dev/null
+++ b/core/jni/android/graphics/NinePatch.cpp
@@ -0,0 +1,142 @@
+#include <utils/ResourceTypes.h>
+
+#include "SkRegion.h"
+#include "GraphicsJNI.h"
+
+#include "JNIHelp.h"
+
+extern void NinePatch_Draw(SkCanvas* canvas, const SkRect& bounds,
+                const SkBitmap& bitmap, const android::Res_png_9patch& chunk,
+                           const SkPaint* paint, SkRegion** outRegion);
+    
+using namespace android;
+
+class SkNinePatchGlue {
+public:
+    static jboolean isNinePatchChunk(JNIEnv* env, jobject, jbyteArray obj)
+    {
+        if (NULL == obj) {
+            return false;
+        }
+        if (env->GetArrayLength(obj) < (int)sizeof(Res_png_9patch)) {
+            return false;
+        }
+        const jbyte* array = env->GetByteArrayElements(obj, 0);
+        if (array != NULL) {
+            const Res_png_9patch* chunk =
+                                reinterpret_cast<const Res_png_9patch*>(array);
+            int8_t wasDeserialized = chunk->wasDeserialized;
+            env->ReleaseByteArrayElements(obj, const_cast<jbyte*>(array),
+                                          JNI_ABORT);
+            return wasDeserialized != -1;
+        }
+        return false;
+    }
+
+    static void validateNinePatchChunk(JNIEnv* env, jobject, jint, jbyteArray obj)
+    {
+        if (env->GetArrayLength(obj) < (int) (sizeof(Res_png_9patch))) {
+            jniThrowException(env, "java/lang/RuntimeException",
+                              "Array too small for chunk.");
+            return;
+        }
+
+        // XXX Also check that dimensions are correct.
+    }
+
+    static void draw(JNIEnv* env, SkCanvas* canvas, SkRect& bounds,
+                      const SkBitmap* bitmap, jbyteArray chunkObj, const SkPaint* paint)
+    {
+        size_t chunkSize = env->GetArrayLength(chunkObj);
+        void* storage = alloca(chunkSize);
+        env->GetByteArrayRegion(chunkObj, 0, chunkSize,
+                                reinterpret_cast<jbyte*>(storage));
+        if (!env->ExceptionCheck()) {
+            // need to deserialize the chunk
+            Res_png_9patch* chunk = static_cast<Res_png_9patch*>(storage);
+            assert(chunkSize == chunk->serializedSize());
+            // this relies on deserialization being done in place
+            Res_png_9patch::deserialize(chunk);            
+            NinePatch_Draw(canvas, bounds, *bitmap, *chunk, paint, NULL);
+        }
+    } 
+
+    static void drawF(JNIEnv* env, jobject, SkCanvas* canvas, jobject boundsRectF,
+                      const SkBitmap* bitmap, jbyteArray chunkObj, const SkPaint* paint)
+    {
+        SkASSERT(canvas);
+        SkASSERT(boundsRectF);
+        SkASSERT(bitmap);
+        SkASSERT(chunkObj);
+        // paint is optional
+
+        SkRect      bounds;
+        GraphicsJNI::jrectf_to_rect(env, boundsRectF, &bounds);
+
+        draw(env, canvas, bounds, bitmap, chunkObj, paint);
+    }
+ 
+    static void drawI(JNIEnv* env, jobject, SkCanvas* canvas, jobject boundsRect,
+                      const SkBitmap* bitmap, jbyteArray chunkObj, const SkPaint* paint)
+    {
+        SkASSERT(canvas);
+        SkASSERT(boundsRect);
+        SkASSERT(bitmap);
+        SkASSERT(chunkObj);
+        // paint is optional
+
+        SkRect      bounds;
+        GraphicsJNI::jrect_to_rect(env, boundsRect, &bounds);
+        draw(env, canvas, bounds, bitmap, chunkObj, paint);
+    }
+    
+    static jint getTransparentRegion(JNIEnv* env, jobject,
+                    const SkBitmap* bitmap, jbyteArray chunkObj,
+                    jobject boundsRect)
+    {
+        SkASSERT(bitmap);
+        SkASSERT(chunkObj);
+        SkASSERT(boundsRect);
+        
+        SkRect      bounds;
+        GraphicsJNI::jrect_to_rect(env, boundsRect, &bounds);
+        size_t chunkSize = env->GetArrayLength(chunkObj);
+        void* storage = alloca(chunkSize);
+        env->GetByteArrayRegion(chunkObj, 0, chunkSize,
+                                reinterpret_cast<jbyte*>(storage));
+        if (!env->ExceptionCheck()) {
+            // need to deserialize the chunk
+            Res_png_9patch* chunk = static_cast<Res_png_9patch*>(storage);
+            assert(chunkSize == chunk->serializedSize());
+            // this relies on deserialization being done in place
+            Res_png_9patch::deserialize(chunk);
+            SkRegion* region = NULL;
+            NinePatch_Draw(NULL, bounds, *bitmap, *chunk, NULL, &region);
+            return (jint)region;
+        }
+        return 0;
+    }
+
+};
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+#include <android_runtime/AndroidRuntime.h>
+
+static JNINativeMethod gNinePatchMethods[] = {
+    { "isNinePatchChunk", "([B)Z",                      (void*)SkNinePatchGlue::isNinePatchChunk   },
+    { "validateNinePatchChunk", "(I[B)V",               (void*)SkNinePatchGlue::validateNinePatchChunk   },
+    { "nativeDraw", "(ILandroid/graphics/RectF;I[BI)V", (void*)SkNinePatchGlue::drawF   },
+    { "nativeDraw", "(ILandroid/graphics/Rect;I[BI)V",  (void*)SkNinePatchGlue::drawI   },
+    { "nativeGetTransparentRegion", "(I[BLandroid/graphics/Rect;)I", 
+                                                        (void*)SkNinePatchGlue::getTransparentRegion   }
+};
+
+int register_android_graphics_NinePatch(JNIEnv* env);
+int register_android_graphics_NinePatch(JNIEnv* env)
+{
+    return android::AndroidRuntime::registerNativeMethods(env,
+                                                       "android/graphics/NinePatch",
+                                                       gNinePatchMethods,
+                                                       SK_ARRAY_COUNT(gNinePatchMethods));
+}
diff --git a/core/jni/android/graphics/NinePatchImpl.cpp b/core/jni/android/graphics/NinePatchImpl.cpp
new file mode 100644
index 0000000..f82053c
--- /dev/null
+++ b/core/jni/android/graphics/NinePatchImpl.cpp
@@ -0,0 +1,324 @@
+/*
+**
+** Copyright 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.
+*/
+
+#define LOG_TAG "NinePatch"
+
+#include <utils/ResourceTypes.h>
+
+#include "SkBitmap.h"
+#include "SkCanvas.h"
+#include "SkNinePatch.h"
+#include "SkPaint.h"
+#include "SkUnPreMultiply.h"
+
+#define USE_TRACEx
+
+#ifdef USE_TRACE
+    static bool gTrace;
+#endif
+
+#include "SkColorPriv.h"
+
+#include <utils/Log.h>
+
+static bool getColor(const SkBitmap& bitmap, int x, int y, SkColor* c) {
+    switch (bitmap.getConfig()) {
+        case SkBitmap::kARGB_8888_Config:
+            *c = SkUnPreMultiply::PMColorToColor(*bitmap.getAddr32(x, y));
+            break;
+        case SkBitmap::kRGB_565_Config:
+            *c = SkPixel16ToPixel32(*bitmap.getAddr16(x, y));
+            break;
+        case SkBitmap::kARGB_4444_Config:
+            *c = SkUnPreMultiply::PMColorToColor(
+                                SkPixel4444ToPixel32(*bitmap.getAddr16(x, y)));
+            break;
+        case SkBitmap::kIndex8_Config: {
+            SkColorTable* ctable = bitmap.getColorTable();
+            *c = SkUnPreMultiply::PMColorToColor(
+                                            (*ctable)[*bitmap.getAddr8(x, y)]);
+            break;
+        }
+        default:
+            return false;
+    }
+    return true;
+}
+
+static SkColor modAlpha(SkColor c, int alpha) {
+    int scale = alpha + (alpha >> 7);
+    int a = SkColorGetA(c) * scale >> 8;
+    return SkColorSetA(c, a);
+}
+
+static void drawStretchyPatch(SkCanvas* canvas, SkIRect& src, const SkRect& dst,
+                              const SkBitmap& bitmap, const SkPaint& paint,
+                              SkColor initColor, uint32_t colorHint,
+                              bool hasXfer) {
+    if (colorHint !=  android::Res_png_9patch::NO_COLOR) {
+        ((SkPaint*)&paint)->setColor(modAlpha(colorHint, paint.getAlpha()));
+        canvas->drawRect(dst, paint);
+        ((SkPaint*)&paint)->setColor(initColor);
+    } else if (src.width() == 1 && src.height() == 1) {
+        SkColor c;
+        if (!getColor(bitmap, src.fLeft, src.fTop, &c)) {
+            goto SLOW_CASE;
+        }
+        if (0 != c || hasXfer) {
+            SkColor prev = paint.getColor();
+            ((SkPaint*)&paint)->setColor(c);
+            canvas->drawRect(dst, paint);
+            ((SkPaint*)&paint)->setColor(prev);
+        }
+    } else {
+    SLOW_CASE:
+        canvas->drawBitmapRect(bitmap, &src, dst, &paint);
+    }
+}
+
+SkScalar calculateStretch(SkScalar boundsLimit, SkScalar startingPoint,
+                          int srcSpace, int numStrechyPixelsRemaining,
+                          int numFixedPixelsRemaining) {
+    SkScalar spaceRemaining = boundsLimit - startingPoint;
+    SkScalar stretchySpaceRemaining =
+                spaceRemaining - SkIntToScalar(numFixedPixelsRemaining);
+    return SkScalarMulDiv(srcSpace, stretchySpaceRemaining,
+                          numStrechyPixelsRemaining);
+}
+
+void NinePatch_Draw(SkCanvas* canvas, const SkRect& bounds,
+                       const SkBitmap& bitmap, const android::Res_png_9patch& chunk,
+                       const SkPaint* paint, SkRegion** outRegion) {
+    if (canvas && canvas->quickReject(bounds, SkCanvas::kBW_EdgeType)) {
+        return;
+    }
+    
+    // if our canvas is GL, draw this as a mesh, which will be faster than
+    // in parts (which is faster for raster)
+    if (canvas && canvas->getViewport(NULL)) {
+        SkNinePatch::DrawMesh(canvas, bounds, bitmap,
+                              chunk.xDivs, chunk.numXDivs,
+                              chunk.yDivs, chunk.numYDivs,
+                              paint);
+        return;
+    }
+
+#ifdef USE_TRACE
+    gTrace = true;
+#endif
+
+    SkASSERT(canvas || outRegion);
+
+#if 0
+    if (canvas) {
+        const SkMatrix& m = canvas->getTotalMatrix();
+        SkDebugf("ninepatch [%g %g %g] [%g %g %g]\n",
+                 SkScalarToFloat(m[0]), SkScalarToFloat(m[1]), SkScalarToFloat(m[2]),
+                 SkScalarToFloat(m[3]), SkScalarToFloat(m[4]), SkScalarToFloat(m[5]));
+    }
+#endif
+
+#ifdef USE_TRACE
+    if (gTrace) {
+        SkDEBUGF(("======== ninepatch bounds [%g %g]\n", SkScalarToFloat(bounds.width()), SkScalarToFloat(bounds.height())));
+        SkDEBUGF(("======== ninepatch paint bm [%d,%d]\n", bitmap.width(), bitmap.height()));
+        SkDEBUGF(("======== ninepatch xDivs [%d,%d]\n", chunk.xDivs[0], chunk.xDivs[1]));
+        SkDEBUGF(("======== ninepatch yDivs [%d,%d]\n", chunk.yDivs[0], chunk.yDivs[1]));
+    }
+#endif
+
+    if (bounds.isEmpty() ||
+        bitmap.width() == 0 || bitmap.height() == 0 ||
+        (paint && paint->getXfermode() == NULL && paint->getAlpha() == 0))
+    {
+#ifdef USE_TRACE
+        if (gTrace) SkDEBUGF(("======== abort ninepatch draw\n"));
+#endif
+        return;
+    }
+    
+    // should try a quick-reject test before calling lockPixels 
+
+    SkAutoLockPixels alp(bitmap);
+    // after the lock, it is valid to check getPixels()
+    if (bitmap.getPixels() == NULL)
+        return;
+
+    SkPaint defaultPaint;
+    if (NULL == paint) {
+        paint = &defaultPaint;
+    }
+
+    const bool hasXfer = paint->getXfermode() != NULL;
+    SkRect      dst;
+    SkIRect     src;
+
+    const int32_t x0 = chunk.xDivs[0];
+    const int32_t y0 = chunk.yDivs[0];
+    const SkColor initColor = ((SkPaint*)paint)->getColor();
+    const uint8_t numXDivs = chunk.numXDivs;
+    const uint8_t numYDivs = chunk.numYDivs;
+    int i;
+    int j;
+    int colorIndex = 0;
+    uint32_t color;
+    bool xIsStretchable;
+    const bool initialXIsStretchable =  (x0 == 0);
+    bool yIsStretchable = (y0 == 0);
+    const int bitmapWidth = bitmap.width();
+    const int bitmapHeight = bitmap.height();
+
+    SkScalar* dstRights = (SkScalar*) alloca((numXDivs + 1) * sizeof(SkScalar));
+    bool dstRightsHaveBeenCached = false;
+
+    int numStretchyXPixelsRemaining = 0;
+    for (i = 0; i < numXDivs; i += 2) {
+        numStretchyXPixelsRemaining += chunk.xDivs[i + 1] - chunk.xDivs[i];
+    }
+    int numFixedXPixelsRemaining = bitmapWidth - numStretchyXPixelsRemaining;
+    int numStretchyYPixelsRemaining = 0;
+    for (i = 0; i < numYDivs; i += 2) {
+        numStretchyYPixelsRemaining += chunk.yDivs[i + 1] - chunk.yDivs[i];
+    }
+    int numFixedYPixelsRemaining = bitmapHeight - numStretchyYPixelsRemaining;
+
+#if 0
+    SkDebugf("NinePatch [%d %d] bounds [%g %g %g %g] divs [%d %d]\n",
+             bitmap.width(), bitmap.height(),
+             SkScalarToFloat(bounds.fLeft), SkScalarToFloat(bounds.fTop),
+             SkScalarToFloat(bounds.width()), SkScalarToFloat(bounds.height()),
+             numXDivs, numYDivs);
+#endif
+
+    src.fTop = 0;
+    dst.fTop = bounds.fTop;
+    // The first row always starts with the top being at y=0 and the bottom
+    // being either yDivs[1] (if yDivs[0]=0) of yDivs[0].  In the former case
+    // the first row is stretchable along the Y axis, otherwise it is fixed.
+    // The last row always ends with the bottom being bitmap.height and the top
+    // being either yDivs[numYDivs-2] (if yDivs[numYDivs-1]=bitmap.height) or
+    // yDivs[numYDivs-1]. In the former case the last row is stretchable along
+    // the Y axis, otherwise it is fixed.
+    //
+    // The first and last columns are similarly treated with respect to the X
+    // axis.
+    //
+    // The above is to help explain some of the special casing that goes on the
+    // code below.
+
+    // The initial yDiv and whether the first row is considered stretchable or
+    // not depends on whether yDiv[0] was zero or not.
+    for (j = yIsStretchable ? 1 : 0;
+          j <= numYDivs && src.fTop < bitmapHeight;
+          j++, yIsStretchable = !yIsStretchable) {
+        src.fLeft = 0;
+        dst.fLeft = bounds.fLeft;
+        if (j == numYDivs) {
+            src.fBottom = bitmapHeight;
+            dst.fBottom = bounds.fBottom;
+        } else {
+            src.fBottom = chunk.yDivs[j];
+            const int srcYSize = src.fBottom - src.fTop;
+            if (yIsStretchable) {
+                dst.fBottom = dst.fTop + calculateStretch(bounds.fBottom, dst.fTop,
+                                                          srcYSize,
+                                                          numStretchyYPixelsRemaining,
+                                                          numFixedYPixelsRemaining);
+                numStretchyYPixelsRemaining -= srcYSize;
+            } else {
+                dst.fBottom = dst.fTop + SkIntToScalar(srcYSize);
+                numFixedYPixelsRemaining -= srcYSize;
+            }
+        }
+
+        xIsStretchable = initialXIsStretchable;
+        // The initial xDiv and whether the first column is considered
+        // stretchable or not depends on whether xDiv[0] was zero or not.
+        for (i = xIsStretchable ? 1 : 0;
+              i <= numXDivs && src.fLeft < bitmapWidth;
+              i++, xIsStretchable = !xIsStretchable) {
+            color = chunk.colors[colorIndex++];
+            if (i == numXDivs) {
+                src.fRight = bitmapWidth;
+                dst.fRight = bounds.fRight;
+            } else {
+                src.fRight = chunk.xDivs[i];
+                if (dstRightsHaveBeenCached) {
+                    dst.fRight = dstRights[i];
+                } else {
+                    const int srcXSize = src.fRight - src.fLeft;
+                    if (xIsStretchable) {
+                        dst.fRight = dst.fLeft + calculateStretch(bounds.fRight, dst.fLeft,
+                                                                  srcXSize,
+                                                                  numStretchyXPixelsRemaining,
+                                                                  numFixedXPixelsRemaining);
+                        numStretchyXPixelsRemaining -= srcXSize;
+                    } else {
+                        dst.fRight = dst.fLeft + SkIntToScalar(srcXSize);
+                        numFixedXPixelsRemaining -= srcXSize;
+                    }
+                    dstRights[i] = dst.fRight;
+                }
+            }
+            // If this horizontal patch is too small to be displayed, leave
+            // the destination left edge where it is and go on to the next patch
+            // in the source.
+            if (src.fLeft >= src.fRight) {
+                src.fLeft = src.fRight;
+                continue;
+            }
+            // Make sure that we actually have room to draw any bits
+            if (dst.fRight <= dst.fLeft || dst.fBottom <= dst.fTop) {
+                goto nextDiv;
+            }
+            // If this patch is transparent, skip and don't draw.
+            if (color == android::Res_png_9patch::TRANSPARENT_COLOR && !hasXfer) {
+                if (outRegion) {
+                    if (*outRegion == NULL) {
+                        *outRegion = new SkRegion();
+                    }
+                    SkIRect idst;
+                    dst.round(&idst);
+                    //LOGI("Adding trans rect: (%d,%d)-(%d,%d)\n",
+                    //     idst.fLeft, idst.fTop, idst.fRight, idst.fBottom);
+                    (*outRegion)->op(idst, SkRegion::kUnion_Op);
+                }
+                goto nextDiv;
+            }
+            if (canvas) {
+#if 0
+                SkDebugf("-- src [%d %d %d %d] dst [%g %g %g %g]\n",
+                         src.fLeft, src.fTop, src.width(), src.height(),
+                         SkScalarToFloat(dst.fLeft), SkScalarToFloat(dst.fTop),
+                         SkScalarToFloat(dst.width()), SkScalarToFloat(dst.height()));
+                if (2 == src.width() && SkIntToScalar(5) == dst.width()) {
+                    SkDebugf("--- skip patch\n");
+                }
+#endif
+                drawStretchyPatch(canvas, src, dst, bitmap, *paint, initColor,
+                                  color, hasXfer);
+            }
+
+nextDiv:
+            src.fLeft = src.fRight;
+            dst.fLeft = dst.fRight;
+        }
+        src.fTop = src.fBottom;
+        dst.fTop = dst.fBottom;
+        dstRightsHaveBeenCached = true;
+    }
+}
diff --git a/core/jni/android/graphics/Paint.cpp b/core/jni/android/graphics/Paint.cpp
new file mode 100644
index 0000000..76e6f02
--- /dev/null
+++ b/core/jni/android/graphics/Paint.cpp
@@ -0,0 +1,612 @@
+/* libs/android_runtime/android/graphics/Paint.cpp
+**
+** Copyright 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.
+*/
+
+// This file was generated from the C++ include file: SkPaint.h
+// Any changes made to this file will be discarded by the build.
+// To change this file, either edit the include, or device/tools/gluemaker/main.cpp, 
+// or one of the auxilary file specifications in device/tools/gluemaker.
+
+#include "jni.h"
+#include "GraphicsJNI.h"
+#include <android_runtime/AndroidRuntime.h>
+
+#include "SkBlurDrawLooper.h"
+#include "SkColorFilter.h"
+#include "SkMaskFilter.h"
+#include "SkRasterizer.h"
+#include "SkShader.h"
+#include "SkTypeface.h"
+#include "SkXfermode.h"
+
+namespace android {
+
+struct JMetricsID {
+    jfieldID    top;
+    jfieldID    ascent;
+    jfieldID    descent;
+    jfieldID    bottom;
+    jfieldID    leading;
+};
+
+static jclass   gFontMetrics_class;
+static JMetricsID gFontMetrics_fieldID;
+
+static jclass   gFontMetricsInt_class;
+static JMetricsID gFontMetricsInt_fieldID;
+
+class SkPaintGlue {
+public:
+
+    static void finalizer(JNIEnv* env, jobject clazz, SkPaint* obj) {
+        delete obj;
+    }
+
+    static SkPaint* init(JNIEnv* env, jobject clazz) {
+        SkPaint* obj = new SkPaint();
+        // utf16 is required for java
+        obj->setTextEncoding(SkPaint::kUTF16_TextEncoding);
+        return obj;
+    }
+
+    static SkPaint* intiWithPaint(JNIEnv* env, jobject clazz, SkPaint* paint) {
+        SkPaint* obj = new SkPaint(*paint);
+        return obj;
+    }
+ 
+    static void reset(JNIEnv* env, jobject clazz, SkPaint* obj) {
+        obj->reset();
+    }
+ 
+    static void assign(JNIEnv* env, jobject clazz, SkPaint* dst, const SkPaint* src) {
+        *dst = *src;
+    }
+ 
+    static jint getFlags(JNIEnv* env, jobject paint) {
+        NPE_CHECK_RETURN_ZERO(env, paint);
+        return GraphicsJNI::getNativePaint(env, paint)->getFlags();
+    }
+ 
+    static void setFlags(JNIEnv* env, jobject paint, jint flags) {
+        NPE_CHECK_RETURN_VOID(env, paint);
+        GraphicsJNI::getNativePaint(env, paint)->setFlags(flags);
+    }
+ 
+    static void setAntiAlias(JNIEnv* env, jobject paint, jboolean aa) {
+        NPE_CHECK_RETURN_VOID(env, paint);
+        GraphicsJNI::getNativePaint(env, paint)->setAntiAlias(aa);
+    }
+ 
+    static void setLinearText(JNIEnv* env, jobject paint, jboolean linearText) {
+        NPE_CHECK_RETURN_VOID(env, paint);
+        GraphicsJNI::getNativePaint(env, paint)->setLinearText(linearText);
+    }
+    
+    static void setSubpixelText(JNIEnv* env, jobject paint, jboolean subpixelText) {
+        NPE_CHECK_RETURN_VOID(env, paint);
+        GraphicsJNI::getNativePaint(env, paint)->setSubpixelText(subpixelText);
+    }
+    
+    static void setUnderlineText(JNIEnv* env, jobject paint, jboolean underlineText) {
+        NPE_CHECK_RETURN_VOID(env, paint);
+        GraphicsJNI::getNativePaint(env, paint)->setUnderlineText(underlineText);
+    }
+ 
+    static void setStrikeThruText(JNIEnv* env, jobject paint, jboolean strikeThruText) {
+        NPE_CHECK_RETURN_VOID(env, paint);
+        GraphicsJNI::getNativePaint(env, paint)->setStrikeThruText(strikeThruText);
+    }
+ 
+    static void setFakeBoldText(JNIEnv* env, jobject paint, jboolean fakeBoldText) {
+        NPE_CHECK_RETURN_VOID(env, paint);
+        GraphicsJNI::getNativePaint(env, paint)->setFakeBoldText(fakeBoldText);
+    }
+    
+    static void setFilterBitmap(JNIEnv* env, jobject paint, jboolean filterBitmap) {
+        NPE_CHECK_RETURN_VOID(env, paint);
+        GraphicsJNI::getNativePaint(env, paint)->setFilterBitmap(filterBitmap);
+    }
+    
+    static void setDither(JNIEnv* env, jobject paint, jboolean dither) {
+        NPE_CHECK_RETURN_VOID(env, paint);
+        GraphicsJNI::getNativePaint(env, paint)->setDither(dither);
+    }
+    
+    static jint getStyle(JNIEnv* env, jobject clazz, SkPaint* obj) {
+        return obj->getStyle();
+    }
+ 
+    static void setStyle(JNIEnv* env, jobject clazz, SkPaint* obj, SkPaint::Style style) {
+        obj->setStyle(style);
+    }
+ 
+    static jint getColor(JNIEnv* env, jobject paint) {
+        NPE_CHECK_RETURN_ZERO(env, paint);
+        return GraphicsJNI::getNativePaint(env, paint)->getColor();
+    }
+ 
+    static jint getAlpha(JNIEnv* env, jobject paint) {
+        NPE_CHECK_RETURN_ZERO(env, paint);
+        return GraphicsJNI::getNativePaint(env, paint)->getAlpha();
+    }
+ 
+    static void setColor(JNIEnv* env, jobject paint, jint color) {
+        NPE_CHECK_RETURN_VOID(env, paint);
+        GraphicsJNI::getNativePaint(env, paint)->setColor(color);
+    }
+ 
+    static void setAlpha(JNIEnv* env, jobject paint, jint a) {
+        NPE_CHECK_RETURN_VOID(env, paint);
+        GraphicsJNI::getNativePaint(env, paint)->setAlpha(a);
+    }
+ 
+    static jfloat getStrokeWidth(JNIEnv* env, jobject paint) {
+        NPE_CHECK_RETURN_ZERO(env, paint);
+        return SkScalarToFloat(GraphicsJNI::getNativePaint(env, paint)->getStrokeWidth());
+    }
+ 
+    static void setStrokeWidth(JNIEnv* env, jobject paint, jfloat width) {
+        NPE_CHECK_RETURN_VOID(env, paint);
+        GraphicsJNI::getNativePaint(env, paint)->setStrokeWidth(SkFloatToScalar(width));
+    }
+ 
+    static jfloat getStrokeMiter(JNIEnv* env, jobject paint) {
+        NPE_CHECK_RETURN_ZERO(env, paint);
+        return SkScalarToFloat(GraphicsJNI::getNativePaint(env, paint)->getStrokeMiter());
+    }
+ 
+    static void setStrokeMiter(JNIEnv* env, jobject paint, jfloat miter) {
+        NPE_CHECK_RETURN_VOID(env, paint);
+        GraphicsJNI::getNativePaint(env, paint)->setStrokeMiter(SkFloatToScalar(miter));
+    }
+ 
+    static jint getStrokeCap(JNIEnv* env, jobject clazz, SkPaint* obj) {
+        return obj->getStrokeCap();
+    }
+ 
+    static void setStrokeCap(JNIEnv* env, jobject clazz, SkPaint* obj, SkPaint::Cap cap) {
+        obj->setStrokeCap(cap);
+    }
+ 
+    static jint getStrokeJoin(JNIEnv* env, jobject clazz, SkPaint* obj) {
+        return obj->getStrokeJoin();
+    }
+ 
+    static void setStrokeJoin(JNIEnv* env, jobject clazz, SkPaint* obj, SkPaint::Join join) {
+        obj->setStrokeJoin(join);
+    }
+ 
+    static jboolean getFillPath(JNIEnv* env, jobject clazz, SkPaint* obj, SkPath* src, SkPath* dst) {
+        return obj->getFillPath(*src, dst);
+    }
+ 
+    static SkShader* setShader(JNIEnv* env, jobject clazz, SkPaint* obj, SkShader* shader) {
+        return obj->setShader(shader);
+    }
+ 
+    static SkColorFilter* setColorFilter(JNIEnv* env, jobject clazz, SkPaint* obj, SkColorFilter* filter) {
+        return obj->setColorFilter(filter);
+    }
+ 
+    static SkXfermode* setXfermode(JNIEnv* env, jobject clazz, SkPaint* obj, SkXfermode* xfermode) {
+        return obj->setXfermode(xfermode);
+    }
+ 
+    static SkPathEffect* setPathEffect(JNIEnv* env, jobject clazz, SkPaint* obj, SkPathEffect* effect) {
+        return obj->setPathEffect(effect);
+    }
+ 
+    static SkMaskFilter* setMaskFilter(JNIEnv* env, jobject clazz, SkPaint* obj, SkMaskFilter* maskfilter) {
+        return obj->setMaskFilter(maskfilter);
+    }
+ 
+    static SkTypeface* setTypeface(JNIEnv* env, jobject clazz, SkPaint* obj, SkTypeface* typeface) {
+        return obj->setTypeface(typeface);
+    }
+ 
+    static SkRasterizer* setRasterizer(JNIEnv* env, jobject clazz, SkPaint* obj, SkRasterizer* rasterizer) {
+        return obj->setRasterizer(rasterizer);
+    }
+ 
+    static jint getTextAlign(JNIEnv* env, jobject clazz, SkPaint* obj) {
+        return obj->getTextAlign();
+    }
+ 
+    static void setTextAlign(JNIEnv* env, jobject clazz, SkPaint* obj, SkPaint::Align align) {
+        obj->setTextAlign(align);
+    }
+ 
+    static jfloat getTextSize(JNIEnv* env, jobject paint) {
+        NPE_CHECK_RETURN_ZERO(env, paint);
+        return SkScalarToFloat(GraphicsJNI::getNativePaint(env, paint)->getTextSize());
+    }
+ 
+    static void setTextSize(JNIEnv* env, jobject paint, jfloat textSize) {
+        NPE_CHECK_RETURN_VOID(env, paint);
+        GraphicsJNI::getNativePaint(env, paint)->setTextSize(SkFloatToScalar(textSize));
+    }
+ 
+    static jfloat getTextScaleX(JNIEnv* env, jobject paint) {
+        NPE_CHECK_RETURN_ZERO(env, paint);
+        return SkScalarToFloat(GraphicsJNI::getNativePaint(env, paint)->getTextScaleX());
+    }
+ 
+    static void setTextScaleX(JNIEnv* env, jobject paint, jfloat scaleX) {
+        NPE_CHECK_RETURN_VOID(env, paint);
+        GraphicsJNI::getNativePaint(env, paint)->setTextScaleX(SkFloatToScalar(scaleX));
+    }
+ 
+    static jfloat getTextSkewX(JNIEnv* env, jobject paint) {
+        NPE_CHECK_RETURN_ZERO(env, paint);
+        return SkScalarToFloat(GraphicsJNI::getNativePaint(env, paint)->getTextSkewX());
+    }
+ 
+    static void setTextSkewX(JNIEnv* env, jobject paint, jfloat skewX) {
+        NPE_CHECK_RETURN_VOID(env, paint);
+        GraphicsJNI::getNativePaint(env, paint)->setTextSkewX(SkFloatToScalar(skewX));
+    }
+ 
+    static jfloat ascent(JNIEnv* env, jobject paint) {
+        NPE_CHECK_RETURN_ZERO(env, paint);
+        SkPaint::FontMetrics    metrics;
+        (void)GraphicsJNI::getNativePaint(env, paint)->getFontMetrics(&metrics);
+        return SkScalarToFloat(metrics.fAscent);
+    }
+ 
+    static jfloat descent(JNIEnv* env, jobject paint) {
+        NPE_CHECK_RETURN_ZERO(env, paint);
+        SkPaint::FontMetrics    metrics;
+        (void)GraphicsJNI::getNativePaint(env, paint)->getFontMetrics(&metrics);
+        return SkScalarToFloat(metrics.fDescent);
+    }
+ 
+    static jfloat getFontMetrics(JNIEnv* env, jobject paint, jobject metricsObj) {
+        NPE_CHECK_RETURN_ZERO(env, paint);
+        SkPaint::FontMetrics metrics;
+        SkScalar             spacing = GraphicsJNI::getNativePaint(env, paint)->getFontMetrics(&metrics);
+
+        if (metricsObj) {
+            SkASSERT(env->IsInstanceOf(metricsObj, gFontMetrics_class));
+            env->SetFloatField(metricsObj, gFontMetrics_fieldID.top, SkScalarToFloat(metrics.fTop));
+            env->SetFloatField(metricsObj, gFontMetrics_fieldID.ascent, SkScalarToFloat(metrics.fAscent));
+            env->SetFloatField(metricsObj, gFontMetrics_fieldID.descent, SkScalarToFloat(metrics.fDescent));
+            env->SetFloatField(metricsObj, gFontMetrics_fieldID.bottom, SkScalarToFloat(metrics.fBottom));
+            env->SetFloatField(metricsObj, gFontMetrics_fieldID.leading, SkScalarToFloat(metrics.fLeading));
+        }
+        return SkScalarToFloat(spacing);
+    }
+    
+    static jint getFontMetricsInt(JNIEnv* env, jobject paint, jobject metricsObj) {
+        NPE_CHECK_RETURN_ZERO(env, paint);
+        SkPaint::FontMetrics metrics;
+        
+        GraphicsJNI::getNativePaint(env, paint)->getFontMetrics(&metrics);
+        int ascent = SkScalarRound(metrics.fAscent);
+        int descent = SkScalarRound(metrics.fDescent);
+        int leading = SkScalarRound(metrics.fLeading);
+
+        if (metricsObj) {
+            SkASSERT(env->IsInstanceOf(metricsObj, gFontMetricsInt_class));
+            env->SetIntField(metricsObj, gFontMetricsInt_fieldID.top, SkScalarFloor(metrics.fTop));
+            env->SetIntField(metricsObj, gFontMetricsInt_fieldID.ascent, ascent);
+            env->SetIntField(metricsObj, gFontMetricsInt_fieldID.descent, descent);
+            env->SetIntField(metricsObj, gFontMetricsInt_fieldID.bottom, SkScalarCeil(metrics.fBottom));
+            env->SetIntField(metricsObj, gFontMetricsInt_fieldID.leading, leading);
+        }
+        return descent - ascent + leading;
+    }
+
+    static jfloat measureText_CII(JNIEnv* env, jobject jpaint, jcharArray text, int index, int count) {
+        NPE_CHECK_RETURN_ZERO(env, jpaint);
+        NPE_CHECK_RETURN_ZERO(env, text);
+
+        size_t textLength = env->GetArrayLength(text);
+        
+        if ((index | count) < 0 || (size_t)(index + count) > textLength) {
+            doThrow(env, "java/lang/ArrayIndexOutOfBoundsException");
+            return 0;
+        }
+
+        const SkPaint* paint = GraphicsJNI::getNativePaint(env, jpaint);        
+        const jchar* textArray = env->GetCharArrayElements(text, NULL);
+        // we double count, since measureText wants a byteLength
+        SkScalar width = paint->measureText(textArray + index, count << 1);
+        env->ReleaseCharArrayElements(text, const_cast<jchar*>(textArray),
+                                      JNI_ABORT);
+
+        return SkScalarToFloat(width);
+    }
+ 
+    static jfloat measureText_StringII(JNIEnv* env, jobject jpaint, jstring text, int start, int end) {
+        NPE_CHECK_RETURN_ZERO(env, jpaint);
+        NPE_CHECK_RETURN_ZERO(env, text);
+        
+        SkPaint* paint = GraphicsJNI::getNativePaint(env, jpaint);        
+        const jchar* textArray = env->GetStringChars(text, NULL);
+        size_t textLength = env->GetStringLength(text);
+        
+        int count = end - start;
+        if ((start | count) < 0 || (size_t)count > textLength) {
+            doThrow(env, "java/lang/IndexOutOfBoundsException");
+            return 0;
+        }
+        
+        jfloat width = SkScalarToFloat(paint->measureText(textArray + start, count << 1));
+        env->ReleaseStringChars(text, textArray);
+        return width;
+    }
+    
+    static jfloat measureText_String(JNIEnv* env, jobject jpaint, jstring text) {
+        NPE_CHECK_RETURN_ZERO(env, jpaint);
+        NPE_CHECK_RETURN_ZERO(env, text);
+        
+        SkPaint* paint = GraphicsJNI::getNativePaint(env, jpaint);        
+        const jchar* textArray = env->GetStringChars(text, NULL);
+        size_t textLength = env->GetStringLength(text);
+        
+        jfloat width = SkScalarToFloat(paint->measureText(textArray, textLength << 1));
+        env->ReleaseStringChars(text, textArray);
+        return width;
+    }
+    
+    static int dotextwidths(JNIEnv* env, SkPaint* paint, const jchar text[], int count, jfloatArray widths) {
+        AutoJavaFloatArray autoWidths(env, widths, count);
+        jfloat* widthsArray = autoWidths.ptr();
+        SkScalar* scalarArray = (SkScalar*)widthsArray;
+
+        count = paint->getTextWidths(text, count << 1, scalarArray);        
+        for (int i = 0; i < count; i++) {
+            widthsArray[i] = SkScalarToFloat(scalarArray[i]);
+        }
+        return count;
+    }
+    
+    static int getTextWidths___CII_F(JNIEnv* env, jobject clazz, SkPaint* paint, jcharArray text, int index, int count, jfloatArray widths) {
+        const jchar* textArray = env->GetCharArrayElements(text, NULL);
+        count = dotextwidths(env, paint, textArray + index, count, widths);
+        env->ReleaseCharArrayElements(text, const_cast<jchar*>(textArray),
+                                      JNI_ABORT);        
+        return count;
+    }
+ 
+    static int getTextWidths__StringII_F(JNIEnv* env, jobject clazz, SkPaint* paint, jstring text, int start, int end, jfloatArray widths) {
+        const jchar* textArray = env->GetStringChars(text, NULL);
+        int count = dotextwidths(env, paint, textArray + start, end - start, widths);
+        env->ReleaseStringChars(text, textArray);
+        return count;
+    }
+    
+    static void getTextPath___CIIFFPath(JNIEnv* env, jobject clazz, SkPaint* paint, jcharArray text, int index, int count, jfloat x, jfloat y, SkPath* path) {
+        const jchar* textArray = env->GetCharArrayElements(text, NULL);
+        paint->getTextPath(textArray + index, count << 1, SkFloatToScalar(x), SkFloatToScalar(y), path);
+        env->ReleaseCharArrayElements(text, const_cast<jchar*>(textArray),
+                                      JNI_ABORT);
+    }
+ 
+    static void getTextPath__StringIIFFPath(JNIEnv* env, jobject clazz, SkPaint* paint, jstring text, int start, int end, jfloat x, jfloat y, SkPath* path) {
+        const jchar* textArray = env->GetStringChars(text, NULL);
+        paint->getTextPath(textArray + start, (end - start) << 1, SkFloatToScalar(x), SkFloatToScalar(y), path);
+        env->ReleaseStringChars(text, textArray);
+    }
+ 
+    static void setShadowLayer(JNIEnv* env, jobject jpaint, jfloat radius,
+                               jfloat dx, jfloat dy, int color) {
+        NPE_CHECK_RETURN_VOID(env, jpaint);
+        
+        SkPaint* paint = GraphicsJNI::getNativePaint(env, jpaint);        
+        if (radius <= 0) {
+            paint->setLooper(NULL);
+        }
+        else {
+            paint->setLooper(new SkBlurDrawLooper(SkFloatToScalar(radius),
+                                                  SkFloatToScalar(dx),
+                                                  SkFloatToScalar(dy),
+                                                  (SkColor)color))->unref();
+        }
+    }
+
+    static int breakText(JNIEnv* env, const SkPaint& paint, const jchar text[],
+                         int count, float maxWidth, jfloatArray jmeasured,
+                         SkPaint::TextBufferDirection tbd) {
+        SkASSERT(paint.getTextEncoding() == SkPaint::kUTF16_TextEncoding);
+
+        SkScalar     measured;
+        size_t       bytes = paint.breakText(text, count << 1,
+                                   SkFloatToScalar(maxWidth), &measured, tbd);
+        SkASSERT((bytes & 1) == 0);
+
+        if (jmeasured && env->GetArrayLength(jmeasured) > 0) {
+            AutoJavaFloatArray autoMeasured(env, jmeasured, 1);
+            jfloat* array = autoMeasured.ptr();
+            array[0] = SkScalarToFloat(measured);
+        }
+        return bytes >> 1;
+    }
+
+    static int breakTextC(JNIEnv* env, jobject jpaint, jcharArray jtext,
+            int index, int count, float maxWidth, jfloatArray jmeasuredWidth) {
+        NPE_CHECK_RETURN_ZERO(env, jpaint);
+        NPE_CHECK_RETURN_ZERO(env, jtext);
+
+        SkPaint::TextBufferDirection tbd;
+        if (count < 0) {
+            tbd = SkPaint::kBackward_TextBufferDirection;
+            count = -count;
+        }
+        else {
+            tbd = SkPaint::kForward_TextBufferDirection;
+        }
+
+        if ((index < 0) || (index + count > env->GetArrayLength(jtext))) {
+            doThrow(env, "java/lang/ArrayIndexOutOfBoundsException");
+            return 0;
+        }
+
+        SkPaint*     paint = GraphicsJNI::getNativePaint(env, jpaint);
+        const jchar* text = env->GetCharArrayElements(jtext, NULL);
+        count = breakText(env, *paint, text + index, count, maxWidth,
+                          jmeasuredWidth, tbd);
+        env->ReleaseCharArrayElements(jtext, const_cast<jchar*>(text),
+                                      JNI_ABORT);
+        return count;
+    }
+
+    static int breakTextS(JNIEnv* env, jobject jpaint, jstring jtext,
+                bool forwards, float maxWidth, jfloatArray jmeasuredWidth) {
+        NPE_CHECK_RETURN_ZERO(env, jpaint);
+        NPE_CHECK_RETURN_ZERO(env, jtext);
+
+        SkPaint::TextBufferDirection tbd = forwards ?
+                                        SkPaint::kForward_TextBufferDirection :
+                                        SkPaint::kBackward_TextBufferDirection;
+
+        SkPaint* paint = GraphicsJNI::getNativePaint(env, jpaint);
+        int count = env->GetStringLength(jtext);
+        const jchar* text = env->GetStringChars(jtext, NULL);
+        count = breakText(env, *paint, text, count, maxWidth,
+                          jmeasuredWidth, tbd);
+        env->ReleaseStringChars(jtext, text);
+        return count;
+    }
+
+    static void doTextBounds(JNIEnv* env, const jchar* text, int count,
+                             jobject bounds, const SkPaint& paint)
+    {
+        SkRect  r;
+        SkIRect ir;
+        
+        paint.measureText(text, count << 1, &r);
+        r.roundOut(&ir);
+        GraphicsJNI::irect_to_jrect(ir, env, bounds);
+    }
+
+    static void getStringBounds(JNIEnv* env, jobject, const SkPaint* paint,
+                                jstring text, int start, int end, jobject bounds)
+    {
+        const jchar* textArray = env->GetStringChars(text, NULL);
+        doTextBounds(env, textArray + start, end - start, bounds, *paint);
+        env->ReleaseStringChars(text, textArray);
+    }
+    
+    static void getCharArrayBounds(JNIEnv* env, jobject, const SkPaint* paint,
+                        jcharArray text, int index, int count, jobject bounds)
+    {
+        const jchar* textArray = env->GetCharArrayElements(text, NULL);
+        doTextBounds(env, textArray + index, count, bounds, *paint);
+        env->ReleaseCharArrayElements(text, const_cast<jchar*>(textArray),
+                                      JNI_ABORT);
+    }
+    
+};
+
+static JNINativeMethod methods[] = {
+    {"finalizer", "(I)V", (void*) SkPaintGlue::finalizer},
+    {"native_init","()I", (void*) SkPaintGlue::init},
+    {"native_initWithPaint","(I)I", (void*) SkPaintGlue::intiWithPaint},
+    {"native_reset","(I)V", (void*) SkPaintGlue::reset},
+    {"native_set","(II)V", (void*) SkPaintGlue::assign},
+    {"getFlags","()I", (void*) SkPaintGlue::getFlags},
+    {"setFlags","(I)V", (void*) SkPaintGlue::setFlags},
+    {"setAntiAlias","(Z)V", (void*) SkPaintGlue::setAntiAlias},
+    {"setSubpixelText","(Z)V", (void*) SkPaintGlue::setSubpixelText},
+    {"setLinearText","(Z)V", (void*) SkPaintGlue::setLinearText},
+    {"setUnderlineText","(Z)V", (void*) SkPaintGlue::setUnderlineText},
+    {"setStrikeThruText","(Z)V", (void*) SkPaintGlue::setStrikeThruText},
+    {"setFakeBoldText","(Z)V", (void*) SkPaintGlue::setFakeBoldText},
+    {"setFilterBitmap","(Z)V", (void*) SkPaintGlue::setFilterBitmap},
+    {"setDither","(Z)V", (void*) SkPaintGlue::setDither},
+    {"native_getStyle","(I)I", (void*) SkPaintGlue::getStyle},
+    {"native_setStyle","(II)V", (void*) SkPaintGlue::setStyle},
+    {"getColor","()I", (void*) SkPaintGlue::getColor},
+    {"setColor","(I)V", (void*) SkPaintGlue::setColor},
+    {"getAlpha","()I", (void*) SkPaintGlue::getAlpha},
+    {"setAlpha","(I)V", (void*) SkPaintGlue::setAlpha},
+    {"getStrokeWidth","()F", (void*) SkPaintGlue::getStrokeWidth},
+    {"setStrokeWidth","(F)V", (void*) SkPaintGlue::setStrokeWidth},
+    {"getStrokeMiter","()F", (void*) SkPaintGlue::getStrokeMiter},
+    {"setStrokeMiter","(F)V", (void*) SkPaintGlue::setStrokeMiter},
+    {"native_getStrokeCap","(I)I", (void*) SkPaintGlue::getStrokeCap},
+    {"native_setStrokeCap","(II)V", (void*) SkPaintGlue::setStrokeCap},
+    {"native_getStrokeJoin","(I)I", (void*) SkPaintGlue::getStrokeJoin},
+    {"native_setStrokeJoin","(II)V", (void*) SkPaintGlue::setStrokeJoin},
+    {"native_getFillPath","(III)Z", (void*) SkPaintGlue::getFillPath},
+    {"native_setShader","(II)I", (void*) SkPaintGlue::setShader},
+    {"native_setColorFilter","(II)I", (void*) SkPaintGlue::setColorFilter},
+    {"native_setXfermode","(II)I", (void*) SkPaintGlue::setXfermode},
+    {"native_setPathEffect","(II)I", (void*) SkPaintGlue::setPathEffect},
+    {"native_setMaskFilter","(II)I", (void*) SkPaintGlue::setMaskFilter},
+    {"native_setTypeface","(II)I", (void*) SkPaintGlue::setTypeface},
+    {"native_setRasterizer","(II)I", (void*) SkPaintGlue::setRasterizer},
+    {"native_getTextAlign","(I)I", (void*) SkPaintGlue::getTextAlign},
+    {"native_setTextAlign","(II)V", (void*) SkPaintGlue::setTextAlign},
+    {"getTextSize","()F", (void*) SkPaintGlue::getTextSize},
+    {"setTextSize","(F)V", (void*) SkPaintGlue::setTextSize},
+    {"getTextScaleX","()F", (void*) SkPaintGlue::getTextScaleX},
+    {"setTextScaleX","(F)V", (void*) SkPaintGlue::setTextScaleX},
+    {"getTextSkewX","()F", (void*) SkPaintGlue::getTextSkewX},
+    {"setTextSkewX","(F)V", (void*) SkPaintGlue::setTextSkewX},
+    {"ascent","()F", (void*) SkPaintGlue::ascent},
+    {"descent","()F", (void*) SkPaintGlue::descent},
+    {"getFontMetrics", "(Landroid/graphics/Paint$FontMetrics;)F", (void*)SkPaintGlue::getFontMetrics},
+    {"getFontMetricsInt", "(Landroid/graphics/Paint$FontMetricsInt;)I", (void*)SkPaintGlue::getFontMetricsInt},
+    {"measureText","([CII)F", (void*) SkPaintGlue::measureText_CII},
+    {"measureText","(Ljava/lang/String;)F", (void*) SkPaintGlue::measureText_String},
+    {"measureText","(Ljava/lang/String;II)F", (void*) SkPaintGlue::measureText_StringII},
+    {"breakText","([CIIF[F)I", (void*) SkPaintGlue::breakTextC},
+    {"breakText","(Ljava/lang/String;ZF[F)I", (void*) SkPaintGlue::breakTextS},
+    {"native_getTextWidths","(I[CII[F)I", (void*) SkPaintGlue::getTextWidths___CII_F},
+    {"native_getTextWidths","(ILjava/lang/String;II[F)I", (void*) SkPaintGlue::getTextWidths__StringII_F},
+    {"native_getTextPath","(I[CIIFFI)V", (void*) SkPaintGlue::getTextPath___CIIFFPath},
+    {"native_getTextPath","(ILjava/lang/String;IIFFI)V", (void*) SkPaintGlue::getTextPath__StringIIFFPath},
+    {"nativeGetStringBounds", "(ILjava/lang/String;IILandroid/graphics/Rect;)V",
+                                        (void*) SkPaintGlue::getStringBounds },
+    {"nativeGetCharArrayBounds", "(I[CIILandroid/graphics/Rect;)V",
+                                    (void*) SkPaintGlue::getCharArrayBounds },
+    {"setShadowLayer", "(FFFI)V", (void*)SkPaintGlue::setShadowLayer}
+};
+
+static jfieldID req_fieldID(jfieldID id) {
+    SkASSERT(id);
+    return id;
+}
+
+int register_android_graphics_Paint(JNIEnv* env) {
+    gFontMetrics_class = env->FindClass("android/graphics/Paint$FontMetrics");
+    SkASSERT(gFontMetrics_class);
+    gFontMetrics_class = (jclass)env->NewGlobalRef(gFontMetrics_class);
+
+    gFontMetrics_fieldID.top = req_fieldID(env->GetFieldID(gFontMetrics_class, "top", "F"));
+    gFontMetrics_fieldID.ascent = req_fieldID(env->GetFieldID(gFontMetrics_class, "ascent", "F"));
+    gFontMetrics_fieldID.descent = req_fieldID(env->GetFieldID(gFontMetrics_class, "descent", "F"));
+    gFontMetrics_fieldID.bottom = req_fieldID(env->GetFieldID(gFontMetrics_class, "bottom", "F"));
+    gFontMetrics_fieldID.leading = req_fieldID(env->GetFieldID(gFontMetrics_class, "leading", "F"));
+
+    gFontMetricsInt_class = env->FindClass("android/graphics/Paint$FontMetricsInt");
+    SkASSERT(gFontMetricsInt_class);
+    gFontMetricsInt_class = (jclass)env->NewGlobalRef(gFontMetricsInt_class);
+
+    gFontMetricsInt_fieldID.top = req_fieldID(env->GetFieldID(gFontMetricsInt_class, "top", "I"));
+    gFontMetricsInt_fieldID.ascent = req_fieldID(env->GetFieldID(gFontMetricsInt_class, "ascent", "I"));
+    gFontMetricsInt_fieldID.descent = req_fieldID(env->GetFieldID(gFontMetricsInt_class, "descent", "I"));
+    gFontMetricsInt_fieldID.bottom = req_fieldID(env->GetFieldID(gFontMetricsInt_class, "bottom", "I"));
+    gFontMetricsInt_fieldID.leading = req_fieldID(env->GetFieldID(gFontMetricsInt_class, "leading", "I"));
+
+    int result = AndroidRuntime::registerNativeMethods(env, "android/graphics/Paint", methods,
+        sizeof(methods) / sizeof(methods[0]));
+    return result;
+}
+
+}
diff --git a/core/jni/android/graphics/Path.cpp b/core/jni/android/graphics/Path.cpp
new file mode 100644
index 0000000..effb1c8
--- /dev/null
+++ b/core/jni/android/graphics/Path.cpp
@@ -0,0 +1,306 @@
+/* libs/android_runtime/android/graphics/Path.cpp
+**
+** Copyright 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.
+*/
+
+// This file was generated from the C++ include file: SkPath.h
+// Any changes made to this file will be discarded by the build.
+// To change this file, either edit the include, or device/tools/gluemaker/main.cpp, 
+// or one of the auxilary file specifications in device/tools/gluemaker.
+
+#include "jni.h"
+#include "GraphicsJNI.h"
+#include <android_runtime/AndroidRuntime.h>
+
+#include "SkPath.h"
+
+namespace android {
+
+class SkPathGlue {
+public:
+
+    static void finalizer(JNIEnv* env, jobject clazz, SkPath* obj) {
+        delete obj;
+    }
+
+    static SkPath* init1(JNIEnv* env, jobject clazz) {
+        return new SkPath();
+    }
+ 
+    static SkPath* init2(JNIEnv* env, jobject clazz, SkPath* val) {
+        return new SkPath(*val);
+    }
+ 
+    static void reset(JNIEnv* env, jobject clazz, SkPath* obj) {
+        obj->reset();
+    }
+
+    static void rewind(JNIEnv* env, jobject clazz, SkPath* obj) {
+        obj->rewind();
+    }
+
+    static void assign(JNIEnv* env, jobject clazz, SkPath* dst, const SkPath* src) {
+        *dst = *src;
+    }
+ 
+    static jint getFillType(JNIEnv* env, jobject clazz, SkPath* obj) {
+        return obj->getFillType();
+    }
+ 
+    static void setFillType(JNIEnv* env, jobject clazz, SkPath* path,
+                            SkPath::FillType ft) {
+        path->setFillType(ft);
+    }
+ 
+    static jboolean isEmpty(JNIEnv* env, jobject clazz, SkPath* obj) {
+        return obj->isEmpty();
+    }
+ 
+    static jboolean isRect(JNIEnv* env, jobject clazz, SkPath* obj, jobject rect) {
+        SkRect rect_;
+        jboolean result = obj->isRect(&rect_);
+        GraphicsJNI::rect_to_jrectf(rect_, env, rect);
+        return result;
+    }
+ 
+    static void computeBounds(JNIEnv* env, jobject clazz, SkPath* obj, jobject bounds, SkPath::BoundsType btype) {
+        SkRect bounds_;
+        obj->computeBounds(&bounds_, btype);
+        GraphicsJNI::rect_to_jrectf(bounds_, env, bounds);
+    }
+ 
+    static void incReserve(JNIEnv* env, jobject clazz, SkPath* obj, jint extraPtCount) {
+        obj->incReserve(extraPtCount);
+    }
+ 
+    static void moveTo__FF(JNIEnv* env, jobject clazz, SkPath* obj, jfloat x, jfloat y) {
+        SkScalar x_ = SkFloatToScalar(x);
+        SkScalar y_ = SkFloatToScalar(y);
+        obj->moveTo(x_, y_);
+    }
+ 
+    static void rMoveTo(JNIEnv* env, jobject clazz, SkPath* obj, jfloat dx, jfloat dy) {
+        SkScalar dx_ = SkFloatToScalar(dx);
+        SkScalar dy_ = SkFloatToScalar(dy);
+        obj->rMoveTo(dx_, dy_);
+    }
+ 
+    static void lineTo__FF(JNIEnv* env, jobject clazz, SkPath* obj, jfloat x, jfloat y) {
+        SkScalar x_ = SkFloatToScalar(x);
+        SkScalar y_ = SkFloatToScalar(y);
+        obj->lineTo(x_, y_);
+    }
+ 
+    static void rLineTo(JNIEnv* env, jobject clazz, SkPath* obj, jfloat dx, jfloat dy) {
+        SkScalar dx_ = SkFloatToScalar(dx);
+        SkScalar dy_ = SkFloatToScalar(dy);
+        obj->rLineTo(dx_, dy_);
+    }
+ 
+    static void quadTo__FFFF(JNIEnv* env, jobject clazz, SkPath* obj, jfloat x1, jfloat y1, jfloat x2, jfloat y2) {
+        SkScalar x1_ = SkFloatToScalar(x1);
+        SkScalar y1_ = SkFloatToScalar(y1);
+        SkScalar x2_ = SkFloatToScalar(x2);
+        SkScalar y2_ = SkFloatToScalar(y2);
+        obj->quadTo(x1_, y1_, x2_, y2_);
+    }
+ 
+    static void rQuadTo(JNIEnv* env, jobject clazz, SkPath* obj, jfloat dx1, jfloat dy1, jfloat dx2, jfloat dy2) {
+        SkScalar dx1_ = SkFloatToScalar(dx1);
+        SkScalar dy1_ = SkFloatToScalar(dy1);
+        SkScalar dx2_ = SkFloatToScalar(dx2);
+        SkScalar dy2_ = SkFloatToScalar(dy2);
+        obj->rQuadTo(dx1_, dy1_, dx2_, dy2_);
+    }
+ 
+    static void cubicTo__FFFFFF(JNIEnv* env, jobject clazz, SkPath* obj, jfloat x1, jfloat y1, jfloat x2, jfloat y2, jfloat x3, jfloat y3) {
+        SkScalar x1_ = SkFloatToScalar(x1);
+        SkScalar y1_ = SkFloatToScalar(y1);
+        SkScalar x2_ = SkFloatToScalar(x2);
+        SkScalar y2_ = SkFloatToScalar(y2);
+        SkScalar x3_ = SkFloatToScalar(x3);
+        SkScalar y3_ = SkFloatToScalar(y3);
+        obj->cubicTo(x1_, y1_, x2_, y2_, x3_, y3_);
+    }
+ 
+    static void rCubicTo(JNIEnv* env, jobject clazz, SkPath* obj, jfloat x1, jfloat y1, jfloat x2, jfloat y2, jfloat x3, jfloat y3) {
+        SkScalar x1_ = SkFloatToScalar(x1);
+        SkScalar y1_ = SkFloatToScalar(y1);
+        SkScalar x2_ = SkFloatToScalar(x2);
+        SkScalar y2_ = SkFloatToScalar(y2);
+        SkScalar x3_ = SkFloatToScalar(x3);
+        SkScalar y3_ = SkFloatToScalar(y3);
+        obj->rCubicTo(x1_, y1_, x2_, y2_, x3_, y3_);
+    }
+ 
+    static void arcTo(JNIEnv* env, jobject clazz, SkPath* obj, jobject oval, jfloat startAngle, jfloat sweepAngle, jboolean forceMoveTo) {
+        SkRect oval_;
+        GraphicsJNI::jrectf_to_rect(env, oval, &oval_);
+        SkScalar startAngle_ = SkFloatToScalar(startAngle);
+        SkScalar sweepAngle_ = SkFloatToScalar(sweepAngle);
+        obj->arcTo(oval_, startAngle_, sweepAngle_, forceMoveTo);
+    }
+ 
+    static void close(JNIEnv* env, jobject clazz, SkPath* obj) {
+        obj->close();
+    }
+ 
+    static void addRect__RectFI(JNIEnv* env, jobject clazz, SkPath* obj, jobject rect, SkPath::Direction dir) {
+        SkRect rect_;
+        GraphicsJNI::jrectf_to_rect(env, rect, &rect_);
+        obj->addRect(rect_, dir);
+    }
+ 
+    static void addRect__FFFFI(JNIEnv* env, jobject clazz, SkPath* obj, jfloat left, jfloat top, jfloat right, jfloat bottom, SkPath::Direction dir) {
+        SkScalar left_ = SkFloatToScalar(left);
+        SkScalar top_ = SkFloatToScalar(top);
+        SkScalar right_ = SkFloatToScalar(right);
+        SkScalar bottom_ = SkFloatToScalar(bottom);
+        obj->addRect(left_, top_, right_, bottom_, dir);
+    }
+ 
+    static void addOval(JNIEnv* env, jobject clazz, SkPath* obj, jobject oval, SkPath::Direction dir) {
+        SkRect oval_;
+        GraphicsJNI::jrectf_to_rect(env, oval, &oval_);
+        obj->addOval(oval_, dir);
+    }
+ 
+    static void addCircle(JNIEnv* env, jobject clazz, SkPath* obj, jfloat x, jfloat y, jfloat radius, SkPath::Direction dir) {
+        SkScalar x_ = SkFloatToScalar(x);
+        SkScalar y_ = SkFloatToScalar(y);
+        SkScalar radius_ = SkFloatToScalar(radius);
+        obj->addCircle(x_, y_, radius_, dir);
+    }
+ 
+    static void addArc(JNIEnv* env, jobject clazz, SkPath* obj, jobject oval, jfloat startAngle, jfloat sweepAngle) {
+        SkRect oval_;
+        GraphicsJNI::jrectf_to_rect(env, oval, &oval_);
+        SkScalar startAngle_ = SkFloatToScalar(startAngle);
+        SkScalar sweepAngle_ = SkFloatToScalar(sweepAngle);
+        obj->addArc(oval_, startAngle_, sweepAngle_);
+    }
+ 
+    static void addRoundRectXY(JNIEnv* env, jobject clazz, SkPath* obj, jobject rect,
+                               jfloat rx, jfloat ry, SkPath::Direction dir) {
+        SkRect rect_;
+        GraphicsJNI::jrectf_to_rect(env, rect, &rect_);
+        SkScalar rx_ = SkFloatToScalar(rx);
+        SkScalar ry_ = SkFloatToScalar(ry);
+        obj->addRoundRect(rect_, rx_, ry_, dir);
+    }
+    
+    static void addRoundRect8(JNIEnv* env, jobject, SkPath* obj, jobject rect,
+                              jfloatArray array, SkPath::Direction dir) {
+        SkRect rect_;
+        GraphicsJNI::jrectf_to_rect(env, rect, &rect_);
+        AutoJavaFloatArray  afa(env, array, 8);
+        const float* src = afa.ptr();
+        SkScalar dst[8];
+        
+        for (int i = 0; i < 8; i++) {
+            dst[i] = SkFloatToScalar(src[i]);
+        }
+        obj->addRoundRect(rect_, dst, dir);
+    }
+    
+    static void addPath__PathFF(JNIEnv* env, jobject clazz, SkPath* obj, SkPath* src, jfloat dx, jfloat dy) {
+        SkScalar dx_ = SkFloatToScalar(dx);
+        SkScalar dy_ = SkFloatToScalar(dy);
+        obj->addPath(*src, dx_, dy_);
+    }
+ 
+    static void addPath__Path(JNIEnv* env, jobject clazz, SkPath* obj, SkPath* src) {
+        obj->addPath(*src);
+    }
+ 
+    static void addPath__PathMatrix(JNIEnv* env, jobject clazz, SkPath* obj, SkPath* src, SkMatrix* matrix) {
+        obj->addPath(*src, *matrix);
+    }
+ 
+    static void offset__FFPath(JNIEnv* env, jobject clazz, SkPath* obj, jfloat dx, jfloat dy, SkPath* dst) {
+        SkScalar dx_ = SkFloatToScalar(dx);
+        SkScalar dy_ = SkFloatToScalar(dy);
+        obj->offset(dx_, dy_, dst);
+    }
+ 
+    static void offset__FF(JNIEnv* env, jobject clazz, SkPath* obj, jfloat dx, jfloat dy) {
+        SkScalar dx_ = SkFloatToScalar(dx);
+        SkScalar dy_ = SkFloatToScalar(dy);
+        obj->offset(dx_, dy_);
+    }
+
+    static void setLastPoint(JNIEnv* env, jobject clazz, SkPath* obj, jfloat dx, jfloat dy) {
+        SkScalar dx_ = SkFloatToScalar(dx);
+        SkScalar dy_ = SkFloatToScalar(dy);
+        obj->setLastPt(dx_, dy_);
+    }
+ 
+    static void transform__MatrixPath(JNIEnv* env, jobject clazz, SkPath* obj, SkMatrix* matrix, SkPath* dst) {
+        obj->transform(*matrix, dst);
+    }
+ 
+    static void transform__Matrix(JNIEnv* env, jobject clazz, SkPath* obj, SkMatrix* matrix) {
+        obj->transform(*matrix);
+    }
+ 
+};
+
+static JNINativeMethod methods[] = {
+    {"finalizer", "(I)V", (void*) SkPathGlue::finalizer},
+    {"init1","()I", (void*) SkPathGlue::init1},
+    {"init2","(I)I", (void*) SkPathGlue::init2},
+    {"native_reset","(I)V", (void*) SkPathGlue::reset},
+    {"native_rewind","(I)V", (void*) SkPathGlue::rewind},
+    {"native_set","(II)V", (void*) SkPathGlue::assign},
+    {"native_getFillType","(I)I", (void*) SkPathGlue::getFillType},
+    {"native_setFillType","(II)V", (void*) SkPathGlue::setFillType},
+    {"native_isEmpty","(I)Z", (void*) SkPathGlue::isEmpty},
+    {"native_isRect","(ILandroid/graphics/RectF;)Z", (void*) SkPathGlue::isRect},
+    {"native_computeBounds","(ILandroid/graphics/RectF;I)V", (void*) SkPathGlue::computeBounds},
+    {"native_incReserve","(II)V", (void*) SkPathGlue::incReserve},
+    {"native_moveTo","(IFF)V", (void*) SkPathGlue::moveTo__FF},
+    {"native_rMoveTo","(IFF)V", (void*) SkPathGlue::rMoveTo},
+    {"native_lineTo","(IFF)V", (void*) SkPathGlue::lineTo__FF},
+    {"native_rLineTo","(IFF)V", (void*) SkPathGlue::rLineTo},
+    {"native_quadTo","(IFFFF)V", (void*) SkPathGlue::quadTo__FFFF},
+    {"native_rQuadTo","(IFFFF)V", (void*) SkPathGlue::rQuadTo},
+    {"native_cubicTo","(IFFFFFF)V", (void*) SkPathGlue::cubicTo__FFFFFF},
+    {"native_rCubicTo","(IFFFFFF)V", (void*) SkPathGlue::rCubicTo},
+    {"native_arcTo","(ILandroid/graphics/RectF;FFZ)V", (void*) SkPathGlue::arcTo},
+    {"native_close","(I)V", (void*) SkPathGlue::close},
+    {"native_addRect","(ILandroid/graphics/RectF;I)V", (void*) SkPathGlue::addRect__RectFI},
+    {"native_addRect","(IFFFFI)V", (void*) SkPathGlue::addRect__FFFFI},
+    {"native_addOval","(ILandroid/graphics/RectF;I)V", (void*) SkPathGlue::addOval},
+    {"native_addCircle","(IFFFI)V", (void*) SkPathGlue::addCircle},
+    {"native_addArc","(ILandroid/graphics/RectF;FF)V", (void*) SkPathGlue::addArc},
+    {"native_addRoundRect","(ILandroid/graphics/RectF;FFI)V", (void*) SkPathGlue::addRoundRectXY},
+    {"native_addRoundRect","(ILandroid/graphics/RectF;[FI)V", (void*) SkPathGlue::addRoundRect8},
+    {"native_addPath","(IIFF)V", (void*) SkPathGlue::addPath__PathFF},
+    {"native_addPath","(II)V", (void*) SkPathGlue::addPath__Path},
+    {"native_addPath","(III)V", (void*) SkPathGlue::addPath__PathMatrix},
+    {"native_offset","(IFFI)V", (void*) SkPathGlue::offset__FFPath},
+    {"native_offset","(IFF)V", (void*) SkPathGlue::offset__FF},
+    {"native_setLastPoint","(IFF)V", (void*) SkPathGlue::setLastPoint},
+    {"native_transform","(III)V", (void*) SkPathGlue::transform__MatrixPath},
+    {"native_transform","(II)V", (void*) SkPathGlue::transform__Matrix}
+};
+
+int register_android_graphics_Path(JNIEnv* env) {
+    int result = AndroidRuntime::registerNativeMethods(env, "android/graphics/Path", methods,
+        sizeof(methods) / sizeof(methods[0]));
+    return result;
+}
+
+}
diff --git a/core/jni/android/graphics/PathEffect.cpp b/core/jni/android/graphics/PathEffect.cpp
new file mode 100644
index 0000000..0ecb004
--- /dev/null
+++ b/core/jni/android/graphics/PathEffect.cpp
@@ -0,0 +1,113 @@
+#include <jni.h>
+#include "GraphicsJNI.h"
+
+#include "SkPathEffect.h"
+#include "SkCornerPathEffect.h"
+#include "SkDashPathEffect.h"
+#include "SkDiscretePathEffect.h"
+#include "Sk1DPathEffect.h"
+#include "SkTemplates.h"
+
+class SkPathEffectGlue {
+public:
+
+    static void destructor(JNIEnv* env, jobject, SkPathEffect* effect) {
+        effect->safeUnref();
+    }
+
+    static SkPathEffect* Compose_constructor(JNIEnv* env, jobject,
+                                   SkPathEffect* outer, SkPathEffect* inner) {
+        return new SkComposePathEffect(outer, inner);
+    }
+    
+    static SkPathEffect* Sum_constructor(JNIEnv* env, jobject,
+                                  SkPathEffect* first, SkPathEffect* second) {
+        return new SkSumPathEffect(first, second);
+    }
+    
+    static SkPathEffect* Dash_constructor(JNIEnv* env, jobject,
+                                      jfloatArray intervalArray, float phase) {
+        AutoJavaFloatArray autoInterval(env, intervalArray);
+        int     count = autoInterval.length() & ~1;  // even number
+        float*  values = autoInterval.ptr();
+
+        SkAutoSTMalloc<32, SkScalar>    storage(count);
+        SkScalar*                       intervals = storage.get();        
+        for (int i = 0; i < count; i++) {
+            intervals[i] = SkFloatToScalar(values[i]);
+        }
+        return new SkDashPathEffect(intervals, count, SkFloatToScalar(phase));
+    }
+ 
+    static SkPathEffect* OneD_constructor(JNIEnv* env, jobject,
+                  const SkPath* shape, float advance, float phase, int style) {
+        SkASSERT(shape != NULL);
+        return new SkPath1DPathEffect(*shape, SkFloatToScalar(advance),
+                     SkFloatToScalar(phase), (SkPath1DPathEffect::Style)style);
+    }
+    
+    static SkPathEffect* Corner_constructor(JNIEnv* env, jobject, float radius){
+        return new SkCornerPathEffect(SkFloatToScalar(radius));
+    }
+    
+    static SkPathEffect* Discrete_constructor(JNIEnv* env, jobject,
+                                              float length, float deviation) {
+        return new SkDiscretePathEffect(SkFloatToScalar(length),
+                                        SkFloatToScalar(deviation));
+    }
+    
+};
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+static JNINativeMethod gPathEffectMethods[] = {
+    { "nativeDestructor", "(I)V", (void*)SkPathEffectGlue::destructor }
+};
+
+static JNINativeMethod gComposePathEffectMethods[] = {
+    { "nativeCreate", "(II)I", (void*)SkPathEffectGlue::Compose_constructor }
+};
+
+static JNINativeMethod gSumPathEffectMethods[] = {
+    { "nativeCreate", "(II)I", (void*)SkPathEffectGlue::Sum_constructor }
+};
+
+static JNINativeMethod gDashPathEffectMethods[] = {
+    { "nativeCreate", "([FF)I", (void*)SkPathEffectGlue::Dash_constructor }
+};
+
+static JNINativeMethod gPathDashPathEffectMethods[] = {
+    { "nativeCreate", "(IFFI)I", (void*)SkPathEffectGlue::OneD_constructor }
+};
+
+static JNINativeMethod gCornerPathEffectMethods[] = {
+    { "nativeCreate", "(F)I", (void*)SkPathEffectGlue::Corner_constructor }
+};
+
+static JNINativeMethod gDiscretePathEffectMethods[] = {
+    { "nativeCreate", "(FF)I", (void*)SkPathEffectGlue::Discrete_constructor }
+};
+
+#include <android_runtime/AndroidRuntime.h>
+
+#define REG(env, name, array)                                              \
+    result = android::AndroidRuntime::registerNativeMethods(env, name, array, \
+                                                  SK_ARRAY_COUNT(array));  \
+    if (result < 0) return result
+
+int register_android_graphics_PathEffect(JNIEnv* env);
+int register_android_graphics_PathEffect(JNIEnv* env)
+{
+    int result;
+    
+    REG(env, "android/graphics/PathEffect", gPathEffectMethods);
+    REG(env, "android/graphics/ComposePathEffect", gComposePathEffectMethods);
+    REG(env, "android/graphics/SumPathEffect", gSumPathEffectMethods);
+    REG(env, "android/graphics/DashPathEffect", gDashPathEffectMethods);
+    REG(env, "android/graphics/PathDashPathEffect", gPathDashPathEffectMethods);
+    REG(env, "android/graphics/CornerPathEffect", gCornerPathEffectMethods);
+    REG(env, "android/graphics/DiscretePathEffect", gDiscretePathEffectMethods);
+    
+    return 0;
+}
+
diff --git a/core/jni/android/graphics/PathMeasure.cpp b/core/jni/android/graphics/PathMeasure.cpp
new file mode 100644
index 0000000..51a3f3a
--- /dev/null
+++ b/core/jni/android/graphics/PathMeasure.cpp
@@ -0,0 +1,138 @@
+/* libs/android_runtime/android/graphics/PathMeasure.cpp
+**
+** Copyright 2007, 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.
+*/
+
+#include "jni.h"
+#include "GraphicsJNI.h"
+#include <android_runtime/AndroidRuntime.h>
+
+#include "SkPathMeasure.h"
+
+/*  We declare an explicit pair, so that we don't have to rely on the java
+    client to be sure not to edit the path while we have an active measure
+    object associated with it.
+ 
+    This costs us the copy of the path, for the sake of not allowing a bad
+    java client to randomly crash (since we can't detect the case where the
+    native path has been modified).
+ 
+    The C side does have this risk, but it chooses for speed over safety. If it
+    later changes this, and is internally safe from changes to the path, then
+    we can remove this explicit copy from our JNI code.
+ 
+    Note that we do not have a reference on the java side to the java path.
+    Were we to not need the native copy here, we would want to add a java
+    reference, so that the java path would not get GD'd while the measure object
+    was still alive.
+*/
+struct PathMeasurePair {
+    PathMeasurePair() {}
+    PathMeasurePair(const SkPath& path, bool forceClosed)
+        : fPath(path), fMeasure(fPath, forceClosed) {}
+
+    SkPath          fPath;      // copy of the user's path
+    SkPathMeasure   fMeasure;   // this guy points to fPath
+};
+
+namespace android {
+    
+class SkPathMeasureGlue {
+public:
+
+    static PathMeasurePair* create(JNIEnv* env, jobject clazz, const SkPath* path, jboolean forceClosed) {
+        return path ? new PathMeasurePair(*path, forceClosed) : new PathMeasurePair;
+    }
+ 
+    static void setPath(JNIEnv* env, jobject clazz, PathMeasurePair* pair, const SkPath* path, jboolean forceClosed) {
+        if (NULL == path) {
+            pair->fPath.reset();
+        } else {
+            pair->fPath = *path;
+        }
+        pair->fMeasure.setPath(&pair->fPath, forceClosed);
+    }
+ 
+    static jfloat getLength(JNIEnv* env, jobject clazz, PathMeasurePair* pair) {
+        return SkScalarToFloat(pair->fMeasure.getLength());
+    }
+ 
+    static void convertTwoElemFloatArray(JNIEnv* env, jfloatArray array, const SkScalar src[2]) {
+        AutoJavaFloatArray autoArray(env, array, 2);
+        jfloat* ptr = autoArray.ptr();
+        ptr[0] = SkScalarToFloat(src[0]);
+        ptr[1] = SkScalarToFloat(src[1]);
+    }
+
+    static jboolean getPosTan(JNIEnv* env, jobject clazz, PathMeasurePair* pair, jfloat dist, jfloatArray pos, jfloatArray tan) {
+        SkScalar    tmpPos[2], tmpTan[2];
+        SkScalar*   posPtr = pos ? tmpPos : NULL;
+        SkScalar*   tanPtr = tan ? tmpTan : NULL;
+        
+        if (!pair->fMeasure.getPosTan(SkFloatToScalar(dist), (SkPoint*)posPtr, (SkVector*)tanPtr)) {
+            return false;
+        }
+    
+        if (pos) {
+            convertTwoElemFloatArray(env, pos, tmpPos);
+        }
+        if (tan) {
+            convertTwoElemFloatArray(env, tan, tmpTan);
+        }
+        return true;
+    }
+ 
+    static jboolean getMatrix(JNIEnv* env, jobject clazz, PathMeasurePair* pair, jfloat dist,
+                          SkMatrix* matrix, int flags) {
+        return pair->fMeasure.getMatrix(SkFloatToScalar(dist), matrix, (SkPathMeasure::MatrixFlags)flags);
+    }
+ 
+    static jboolean getSegment(JNIEnv* env, jobject clazz, PathMeasurePair* pair, jfloat startF,
+                               jfloat stopF, SkPath* dst, jboolean startWithMoveTo) {
+        return pair->fMeasure.getSegment(SkFloatToScalar(startF), SkFloatToScalar(stopF), dst, startWithMoveTo);
+    }
+ 
+    static jboolean isClosed(JNIEnv* env, jobject clazz, PathMeasurePair* pair) {
+        return pair->fMeasure.isClosed();
+    }
+ 
+    static jboolean nextContour(JNIEnv* env, jobject clazz, PathMeasurePair* pair) {
+        return pair->fMeasure.nextContour();
+    }
+ 
+    static void destroy(JNIEnv* env, jobject clazz, PathMeasurePair* pair) {
+        delete pair;
+    } 
+};
+
+static JNINativeMethod methods[] = {
+    {"native_create",       "(IZ)I",        (void*) SkPathMeasureGlue::create      },
+    {"native_setPath",      "(IIZ)V",       (void*) SkPathMeasureGlue::setPath     },
+    {"native_getLength",    "(I)F",         (void*) SkPathMeasureGlue::getLength   },
+    {"native_getPosTan",    "(IF[F[F)Z",    (void*) SkPathMeasureGlue::getPosTan   },
+    {"native_getMatrix",    "(IFII)Z",      (void*) SkPathMeasureGlue::getMatrix   },
+    {"native_getSegment",   "(IFFIZ)Z",     (void*) SkPathMeasureGlue::getSegment  },
+    {"native_isClosed",     "(I)Z",         (void*) SkPathMeasureGlue::isClosed    },
+    {"native_nextContour",  "(I)Z",         (void*) SkPathMeasureGlue::nextContour },
+    {"native_destroy",      "(I)V",         (void*) SkPathMeasureGlue::destroy     }
+};
+
+int register_android_graphics_PathMeasure(JNIEnv* env) {
+    int result = AndroidRuntime::registerNativeMethods(env, "android/graphics/PathMeasure", methods,
+        sizeof(methods) / sizeof(methods[0]));
+    return result;
+}
+
+}
diff --git a/core/jni/android/graphics/Picture.cpp b/core/jni/android/graphics/Picture.cpp
new file mode 100644
index 0000000..5ab6dd3
--- /dev/null
+++ b/core/jni/android/graphics/Picture.cpp
@@ -0,0 +1,128 @@
+/*
+ * 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.
+ */
+
+#include "jni.h"
+#include "GraphicsJNI.h"
+#include <android_runtime/AndroidRuntime.h>
+
+#include "SkCanvas.h"
+#include "SkPicture.h"
+#include "SkTemplates.h"
+#include "CreateJavaOutputStreamAdaptor.h"
+
+namespace android {
+
+class SkPictureGlue {
+public:
+    static SkPicture* newPicture(JNIEnv* env, jobject, const SkPicture* src) {
+        if (src) {
+            return new SkPicture(*src);
+        } else {
+            return new SkPicture;
+        }
+    }
+    
+    static SkPicture* deserialize(JNIEnv* env, jobject, jobject jstream,
+                                  jbyteArray jstorage) {
+        SkPicture* picture = NULL;
+        SkStream* strm = CreateJavaInputStreamAdaptor(env, jstream, jstorage);
+        if (strm) {
+            picture = new SkPicture(strm);
+            delete strm;
+        }
+        return picture;
+    }
+    
+    static void killPicture(JNIEnv* env, jobject, SkPicture* picture) {
+        SkASSERT(picture);
+        delete picture;
+    }
+    
+    static void draw(JNIEnv* env, jobject, SkCanvas* canvas,
+                            SkPicture* picture) {
+        SkASSERT(canvas);
+        SkASSERT(picture);
+        picture->draw(canvas);
+    }
+    
+    static bool serialize(JNIEnv* env, jobject, SkPicture* picture,
+                          jobject jstream, jbyteArray jstorage) {
+        SkWStream* strm = CreateJavaOutputStreamAdaptor(env, jstream, jstorage);
+        
+        if (NULL != strm) {
+            picture->serialize(strm);
+            delete strm;
+            return true;
+        }
+        return false;
+    }
+        
+    static int getWidth(JNIEnv* env, jobject jpic) {
+        NPE_CHECK_RETURN_ZERO(env, jpic);
+        return GraphicsJNI::getNativePicture(env, jpic)->width();
+    }
+    
+    static int getHeight(JNIEnv* env, jobject jpic) {
+        NPE_CHECK_RETURN_ZERO(env, jpic);
+        return GraphicsJNI::getNativePicture(env, jpic)->height();
+    }
+    
+    static SkCanvas* beginRecording(JNIEnv* env, jobject, SkPicture* pict,
+                                    int w, int h) {
+        // beginRecording does not ref its return value, it just returns it.
+        SkCanvas* canvas = pict->beginRecording(w, h);
+        // the java side will wrap this guy in a Canvas.java, which will call
+        // unref in its finalizer, so we have to ref it here, so that both that
+        // Canvas.java and our picture can both be owners
+        canvas->ref();
+        return canvas;
+    }
+    
+    static void endRecording(JNIEnv* env, jobject, SkPicture* pict) {
+        pict->endRecording();
+    }
+};
+
+static JNINativeMethod gPictureMethods[] = {
+    {"getWidth", "()I", (void*) SkPictureGlue::getWidth},
+    {"getHeight", "()I", (void*) SkPictureGlue::getHeight},
+    {"nativeConstructor", "(I)I", (void*) SkPictureGlue::newPicture},
+    {"nativeCreateFromStream", "(Ljava/io/InputStream;[B)I", (void*)SkPictureGlue::deserialize},
+    {"nativeBeginRecording", "(III)I", (void*) SkPictureGlue::beginRecording},
+    {"nativeEndRecording", "(I)V", (void*) SkPictureGlue::endRecording},
+    {"nativeDraw", "(II)V", (void*) SkPictureGlue::draw},
+    {"nativeWriteToStream", "(ILjava/io/OutputStream;[B)Z", (void*)SkPictureGlue::serialize},
+    {"nativeDestructor","(I)V", (void*) SkPictureGlue::killPicture}
+};
+
+#include <android_runtime/AndroidRuntime.h>
+    
+#define REG(env, name, array) \
+    result = android::AndroidRuntime::registerNativeMethods(env, name, array, \
+    SK_ARRAY_COUNT(array));  \
+    if (result < 0) return result
+    
+int register_android_graphics_Picture(JNIEnv* env) {
+    int result;
+    
+    REG(env, "android/graphics/Picture", gPictureMethods);
+    
+    return result;
+}
+    
+}
+
+
diff --git a/core/jni/android/graphics/PorterDuff.cpp b/core/jni/android/graphics/PorterDuff.cpp
new file mode 100644
index 0000000..47de601
--- /dev/null
+++ b/core/jni/android/graphics/PorterDuff.cpp
@@ -0,0 +1,52 @@
+/* libs/android_runtime/android/graphics/PorterDuff.cpp
+**
+** Copyright 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.
+*/
+
+// This file was generated from the C++ include file: SkPorterDuff.h
+// Any changes made to this file will be discarded by the build.
+// To change this file, either edit the include, or device/tools/gluemaker/main.cpp, 
+// or one of the auxilary file specifications in device/tools/gluemaker.
+
+#include "jni.h"
+#include "GraphicsJNI.h"
+#include <android_runtime/AndroidRuntime.h>
+
+#include "SkPorterDuff.h"
+
+namespace android {
+
+class SkPorterDuffGlue {
+public:
+
+    static SkXfermode* CreateXfermode(JNIEnv* env, jobject,
+                                      SkPorterDuff::Mode mode) {
+        return SkPorterDuff::CreateXfermode(mode);
+    }
+ 
+};
+
+static JNINativeMethod methods[] = {
+    {"nativeCreateXfermode","(I)I", (void*) SkPorterDuffGlue::CreateXfermode},
+};
+
+int register_android_graphics_PorterDuff(JNIEnv* env) {
+    int result = AndroidRuntime::registerNativeMethods(env,
+                                "android/graphics/PorterDuffXfermode", methods,
+                                        sizeof(methods) / sizeof(methods[0]));
+    return result;
+}
+
+}
diff --git a/core/jni/android/graphics/Rasterizer.cpp b/core/jni/android/graphics/Rasterizer.cpp
new file mode 100644
index 0000000..db70b57
--- /dev/null
+++ b/core/jni/android/graphics/Rasterizer.cpp
@@ -0,0 +1,50 @@
+/* libs/android_runtime/android/graphics/Rasterizer.cpp
+**
+** Copyright 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.
+*/
+
+// This file was generated from the C++ include file: SkRasterizer.h
+// Any changes made to this file will be discarded by the build.
+// To change this file, either edit the include, or device/tools/gluemaker/main.cpp, 
+// or one of the auxilary file specifications in device/tools/gluemaker.
+
+#include "jni.h"
+#include "GraphicsJNI.h"
+#include <android_runtime/AndroidRuntime.h>
+
+#include "SkRasterizer.h"
+
+namespace android {
+
+class SkRasterizerGlue {
+public:
+
+    static void finalizer(JNIEnv* env, jobject clazz, SkRasterizer* obj) {
+        obj->safeUnref();
+    }
+ 
+};
+
+static JNINativeMethod methods[] = {
+    {"finalizer", "(I)V", (void*) SkRasterizerGlue::finalizer}
+};
+
+int register_android_graphics_Rasterizer(JNIEnv* env) {
+    int result = AndroidRuntime::registerNativeMethods(env, "android/graphics/Rasterizer", methods,
+        sizeof(methods) / sizeof(methods[0]));
+    return result;
+}
+
+}
diff --git a/core/jni/android/graphics/Region.cpp b/core/jni/android/graphics/Region.cpp
new file mode 100644
index 0000000..00d6cd9
--- /dev/null
+++ b/core/jni/android/graphics/Region.cpp
@@ -0,0 +1,231 @@
+#include "SkRegion.h"
+#include "SkPath.h"
+#include "GraphicsJNI.h"
+
+#include <jni.h>
+
+static jfieldID gRegion_nativeInstanceFieldID;
+
+static inline SkRegion* GetSkRegion(JNIEnv* env, jobject regionObject) {
+    SkRegion* rgn = (SkRegion*)env->GetIntField(regionObject, gRegion_nativeInstanceFieldID);
+    SkASSERT(rgn != NULL);
+    return rgn;
+}
+
+static SkRegion* Region_constructor(JNIEnv* env, jobject) {
+    return new SkRegion;
+}
+
+static void Region_destructor(JNIEnv* env, jobject, SkRegion* region) {
+    SkASSERT(region);
+    delete region;
+}
+
+static void Region_setRegion(JNIEnv* env, jobject, SkRegion* dst, const SkRegion* src) {
+    SkASSERT(dst && src);
+    *dst = *src;
+}
+
+static jboolean Region_setRect(JNIEnv* env, jobject, SkRegion* dst, int left, int top, int right, int bottom) {
+    return dst->setRect(left, top, right, bottom);
+}
+
+static jboolean Region_setPath(JNIEnv* env, jobject, SkRegion* dst,
+                               const SkPath* path, const SkRegion* clip) {
+    SkASSERT(dst && path && clip);
+    return dst->setPath(*path, *clip);
+}
+
+static jboolean Region_getBounds(JNIEnv* env, jobject, SkRegion* region, jobject rectBounds) {
+    GraphicsJNI::irect_to_jrect(region->getBounds(), env, rectBounds);
+    return !region->isEmpty();
+}
+
+static jboolean Region_getBoundaryPath(JNIEnv* env, jobject, const SkRegion* region, SkPath* path) {
+    return region->getBoundaryPath(path);
+}
+
+static jboolean Region_op0(JNIEnv* env, jobject, SkRegion* dst, int left, int top, int right, int bottom, int op) {
+    SkIRect ir;
+    
+    ir.set(left, top, right, bottom);
+    return dst->op(ir, (SkRegion::Op)op);
+}
+
+static jboolean Region_op1(JNIEnv* env, jobject, SkRegion* dst, jobject rectObject, const SkRegion* region, int op) {
+    SkIRect    ir;
+    GraphicsJNI::jrect_to_irect(env, rectObject, &ir);
+    return dst->op(ir, *region, (SkRegion::Op)op);
+}
+
+static jboolean Region_op2(JNIEnv* env, jobject, SkRegion* dst, const SkRegion* region1, const SkRegion* region2, int op) {
+    return dst->op(*region1, *region2, (SkRegion::Op)op);
+}
+
+////////////////////////////////////  These are methods, not static 
+
+static jboolean Region_isEmpty(JNIEnv* env, jobject region) {
+    return GetSkRegion(env, region)->isEmpty();
+}
+ 
+static jboolean Region_isRect(JNIEnv* env, jobject region) {
+    return GetSkRegion(env, region)->isRect();
+}
+ 
+static jboolean Region_isComplex(JNIEnv* env, jobject region) {
+    return GetSkRegion(env, region)->isComplex();
+}
+
+static jboolean Region_contains(JNIEnv* env, jobject region, int x, int y) {
+    return GetSkRegion(env, region)->contains(x, y);
+}
+ 
+static jboolean Region_quickContains(JNIEnv* env, jobject region, int left, int top, int right, int bottom) {
+    return GetSkRegion(env, region)->quickContains(left, top, right, bottom);
+}
+ 
+static jboolean Region_quickRejectIIII(JNIEnv* env, jobject region, int left, int top, int right, int bottom) {
+    SkIRect ir;
+    ir.set(left, top, right, bottom);
+    return GetSkRegion(env, region)->quickReject(ir);
+}
+ 
+static jboolean Region_quickRejectRgn(JNIEnv* env, jobject region, jobject other) {
+    return GetSkRegion(env, region)->quickReject(*GetSkRegion(env, other));
+}
+ 
+static void Region_translate(JNIEnv* env, jobject region, int x, int y, jobject dst) {
+    SkRegion* rgn = GetSkRegion(env, region);
+    if (dst)
+        rgn->translate(x, y, GetSkRegion(env, dst));
+    else
+        rgn->translate(x, y);
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+#include "Parcel.h"
+#include "android_util_Binder.h"
+
+static SkRegion* Region_createFromParcel(JNIEnv* env, jobject clazz, jobject parcel)
+{
+    if (parcel == NULL) {
+        return NULL;
+    }
+    
+    android::Parcel* p = android::parcelForJavaObject(env, parcel);
+    
+    SkRegion* region = new SkRegion;
+    size_t size = p->readInt32();
+    region->unflatten(p->readInplace(size));
+    
+    return region;
+}
+
+static jboolean Region_writeToParcel(JNIEnv* env, jobject clazz, const SkRegion* region, jobject parcel)
+{
+    if (parcel == NULL) {
+        return false;
+    }
+    
+    android::Parcel* p = android::parcelForJavaObject(env, parcel);
+
+    size_t size = region->flatten(NULL);
+    p->writeInt32(size);
+    region->flatten(p->writeInplace(size));
+
+    return true;
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+struct RgnIterPair {
+    SkRegion            fRgn;   // a copy of the caller's region
+    SkRegion::Iterator  fIter;  // an iterator acting upon the copy (fRgn)
+    
+    RgnIterPair(const SkRegion& rgn) : fRgn(rgn) {
+        // have our iterator reference our copy (fRgn), so we know it will be
+        // unchanged for the lifetime of the iterator
+        fIter.reset(fRgn);
+    }
+};
+
+static RgnIterPair* RegionIter_constructor(JNIEnv* env, jobject, const SkRegion* region)
+{
+    SkASSERT(region);    
+    return new RgnIterPair(*region);
+}
+
+static void RegionIter_destructor(JNIEnv* env, jobject, RgnIterPair* pair)
+{
+    SkASSERT(pair);
+    delete pair;
+}
+
+static jboolean RegionIter_next(JNIEnv* env, jobject, RgnIterPair* pair, jobject rectObject)
+{
+    // the caller has checked that rectObject is not nul
+    SkASSERT(pair);
+    SkASSERT(rectObject);
+
+    if (!pair->fIter.done()) {
+        GraphicsJNI::irect_to_jrect(pair->fIter.rect(), env, rectObject);
+        pair->fIter.next();
+        return true;
+    }
+    return false;
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+#include <android_runtime/AndroidRuntime.h>
+
+static JNINativeMethod gRegionIterMethods[] = {
+    { "nativeConstructor",  "(I)I",                         (void*)RegionIter_constructor   },
+    { "nativeDestructor",   "(I)V",                         (void*)RegionIter_destructor    },
+    { "nativeNext",         "(ILandroid/graphics/Rect;)Z",  (void*)RegionIter_next          }
+};
+
+static JNINativeMethod gRegionMethods[] = {
+    // these are static methods
+    { "nativeConstructor",      "()I",                              (void*)Region_constructor       },
+    { "nativeDestructor",       "(I)V",                             (void*)Region_destructor        },
+    { "nativeSetRegion",        "(II)Z",                            (void*)Region_setRegion         },
+    { "nativeSetRect",          "(IIIII)Z",                         (void*)Region_setRect           },
+    { "nativeSetPath",          "(III)Z",                           (void*)Region_setPath           },
+    { "nativeGetBounds",        "(ILandroid/graphics/Rect;)Z",      (void*)Region_getBounds         },
+    { "nativeGetBoundaryPath",  "(II)Z",                            (void*)Region_getBoundaryPath   },
+    { "nativeOp",               "(IIIIII)Z",                        (void*)Region_op0               },
+    { "nativeOp",               "(ILandroid/graphics/Rect;II)Z",    (void*)Region_op1               },
+    { "nativeOp",               "(IIII)Z",                          (void*)Region_op2               },
+    // these are methods that take the java region object
+    { "isEmpty",                "()Z",                              (void*)Region_isEmpty           },
+    { "isRect",                 "()Z",                              (void*)Region_isRect            },
+    { "isComplex",              "()Z",                              (void*)Region_isComplex         },
+    { "contains",               "(II)Z",                            (void*)Region_contains          },
+    { "quickContains",          "(IIII)Z",                          (void*)Region_quickContains     },
+    { "quickReject",            "(IIII)Z",                          (void*)Region_quickRejectIIII   },
+    { "quickReject",            "(Landroid/graphics/Region;)Z",     (void*)Region_quickRejectRgn    },
+    { "translate",              "(IILandroid/graphics/Region;)V",   (void*)Region_translate         },
+    // parceling methods
+    { "nativeCreateFromParcel", "(Landroid/os/Parcel;)I",           (void*)Region_createFromParcel  },
+    { "nativeWriteToParcel",    "(ILandroid/os/Parcel;)Z",          (void*)Region_writeToParcel     }
+};
+
+int register_android_graphics_Region(JNIEnv* env);
+int register_android_graphics_Region(JNIEnv* env)
+{
+    jclass clazz = env->FindClass("android/graphics/Region");
+    SkASSERT(clazz);
+    
+    gRegion_nativeInstanceFieldID = env->GetFieldID(clazz, "mNativeRegion", "I");
+    SkASSERT(gRegion_nativeInstanceFieldID);
+
+    int result = android::AndroidRuntime::registerNativeMethods(env, "android/graphics/Region",
+                                                             gRegionMethods, SK_ARRAY_COUNT(gRegionMethods));
+    if (result < 0)
+        return result;
+
+    return android::AndroidRuntime::registerNativeMethods(env, "android/graphics/RegionIterator",
+                                                       gRegionIterMethods, SK_ARRAY_COUNT(gRegionIterMethods));
+}
diff --git a/core/jni/android/graphics/Shader.cpp b/core/jni/android/graphics/Shader.cpp
new file mode 100644
index 0000000..b28eb90
--- /dev/null
+++ b/core/jni/android/graphics/Shader.cpp
@@ -0,0 +1,299 @@
+#include <jni.h>
+#include "GraphicsJNI.h"
+
+#include "SkShader.h"
+#include "SkGradientShader.h"
+#include "SkPorterDuff.h"
+#include "SkComposeShader.h"
+#include "SkTemplates.h"
+#include "SkXfermode.h"
+
+static void ThrowIAE_IfNull(JNIEnv* env, void* ptr) {
+    if (NULL == ptr) {
+        doThrowIAE(env);
+    }
+}
+
+static void Color_RGBToHSV(JNIEnv* env, jobject, int red, int green, int blue, jfloatArray hsvArray)
+{
+    SkScalar hsv[3];
+    SkRGBToHSV(red, green, blue, hsv);
+
+    AutoJavaFloatArray  autoHSV(env, hsvArray, 3);
+    float* values = autoHSV.ptr();
+    for (int i = 0; i < 3; i++) {
+        values[i] = SkScalarToFloat(hsv[i]);
+    }
+}
+ 
+static int Color_HSVToColor(JNIEnv* env, jobject, int alpha, jfloatArray hsvArray)
+{
+    AutoJavaFloatArray  autoHSV(env, hsvArray, 3);
+    float*      values = autoHSV.ptr();;
+    SkScalar    hsv[3];
+
+    for (int i = 0; i < 3; i++) {
+        hsv[i] = SkFloatToScalar(values[i]);
+    }
+    
+    return SkHSVToColor(alpha, hsv);
+}
+
+///////////////////////////////////////////////////////////////////////////////////////////////
+
+static void Shader_destructor(JNIEnv* env, jobject, SkShader* shader)
+{
+    SkASSERT(shader != NULL);
+    shader->unref();
+}
+
+static bool Shader_getLocalMatrix(JNIEnv* env, jobject, const SkShader* shader, SkMatrix* matrix)
+{
+    SkASSERT(shader != NULL);    
+    return shader->getLocalMatrix(matrix);
+}
+ 
+static void Shader_setLocalMatrix(JNIEnv* env, jobject, SkShader* shader, const SkMatrix* matrix)
+{
+    SkASSERT(shader != NULL);
+    
+    if (NULL == matrix) {
+        shader->resetLocalMatrix();
+    }
+    else {
+        shader->setLocalMatrix(*matrix);
+    }
+}
+
+///////////////////////////////////////////////////////////////////////////////////////////////
+
+static SkShader* BitmapShader_constructor(JNIEnv* env, jobject, const SkBitmap* bitmap,
+                                          int tileModeX, int tileModeY)
+{
+    SkShader* s = SkShader::CreateBitmapShader(*bitmap,
+                                        (SkShader::TileMode)tileModeX,
+                                        (SkShader::TileMode)tileModeY);
+    ThrowIAE_IfNull(env, s);
+    return s;
+}
+    
+///////////////////////////////////////////////////////////////////////////////////////////////
+
+static SkShader* LinearGradient_create1(JNIEnv* env, jobject,
+                                        float x0, float y0, float x1, float y1,
+                                        jintArray colorArray, jfloatArray posArray, int tileMode)
+{
+    SkPoint pts[2];
+    pts[0].set(SkFloatToScalar(x0), SkFloatToScalar(y0));
+    pts[1].set(SkFloatToScalar(x1), SkFloatToScalar(y1));
+
+    size_t      count = env->GetArrayLength(colorArray);
+    const jint* colorValues = env->GetIntArrayElements(colorArray, NULL);
+
+    SkAutoSTMalloc<8, SkScalar> storage(posArray ? count : 0);
+    SkScalar*                   pos = NULL;
+    
+    if (posArray) {
+        AutoJavaFloatArray autoPos(env, posArray, count);
+        const float* posValues = autoPos.ptr();
+        pos = (SkScalar*)storage.get();
+        for (size_t i = 0; i < count; i++)
+            pos[i] = SkFloatToScalar(posValues[i]);
+    }
+
+    SkShader* shader = SkGradientShader::CreateLinear(pts,
+                                reinterpret_cast<const SkColor*>(colorValues),
+                                pos, count,
+                                static_cast<SkShader::TileMode>(tileMode));
+    env->ReleaseIntArrayElements(colorArray, const_cast<jint*>(colorValues),
+                                 JNI_ABORT);
+    ThrowIAE_IfNull(env, shader);
+    return shader;
+}
+
+static SkShader* LinearGradient_create2(JNIEnv* env, jobject,
+                                        float x0, float y0, float x1, float y1,
+                                        int color0, int color1, int tileMode)
+{
+    SkPoint pts[2];
+    pts[0].set(SkFloatToScalar(x0), SkFloatToScalar(y0));
+    pts[1].set(SkFloatToScalar(x1), SkFloatToScalar(y1));
+
+    SkColor colors[2];
+    colors[0] = color0;
+    colors[1] = color1;
+
+    SkShader* s = SkGradientShader::CreateLinear(pts, colors, NULL, 2, (SkShader::TileMode)tileMode);
+    ThrowIAE_IfNull(env, s);
+    return s;
+}
+
+///////////////////////////////////////////////////////////////////////////////////////////////
+
+static SkShader* RadialGradient_create1(JNIEnv* env, jobject,
+                                        float x, float y, float radius,
+                                        jintArray colorArray, jfloatArray posArray, int tileMode)
+{
+    SkPoint center;
+    center.set(SkFloatToScalar(x), SkFloatToScalar(y));
+
+    size_t      count = env->GetArrayLength(colorArray);
+    const jint* colorValues = env->GetIntArrayElements(colorArray, NULL);
+
+    SkAutoSTMalloc<8, SkScalar> storage(posArray ? count : 0);
+    SkScalar*                   pos = NULL;
+    
+    if (posArray) {
+        AutoJavaFloatArray autoPos(env, posArray, count);
+        const float* posValues = autoPos.ptr();
+        pos = (SkScalar*)storage.get();
+        for (size_t i = 0; i < count; i++)
+            pos[i] = SkFloatToScalar(posValues[i]);
+    }
+
+    SkShader* shader = SkGradientShader::CreateRadial(center,
+                                SkFloatToScalar(radius),
+                                reinterpret_cast<const SkColor*>(colorValues),
+                                pos, count,
+                                static_cast<SkShader::TileMode>(tileMode));
+    env->ReleaseIntArrayElements(colorArray, const_cast<jint*>(colorValues),
+                                 JNI_ABORT);
+
+    ThrowIAE_IfNull(env, shader);
+    return shader;
+}
+
+static SkShader* RadialGradient_create2(JNIEnv* env, jobject,
+                                        float x, float y, float radius,
+                                        int color0, int color1, int tileMode)
+{
+    SkPoint center;
+    center.set(SkFloatToScalar(x), SkFloatToScalar(y));
+
+    SkColor colors[2];
+    colors[0] = color0;
+    colors[1] = color1;
+
+    SkShader* s = SkGradientShader::CreateRadial(center, SkFloatToScalar(radius), colors, NULL,
+                                          2, (SkShader::TileMode)tileMode);
+    ThrowIAE_IfNull(env, s);
+    return s;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+static SkShader* SweepGradient_create1(JNIEnv* env, jobject, float x, float y,
+                                    jintArray jcolors, jfloatArray jpositions)
+{
+    size_t      count = env->GetArrayLength(jcolors);
+    const jint* colors = env->GetIntArrayElements(jcolors, NULL);
+    
+    SkAutoSTMalloc<8, SkScalar> storage(jpositions ? count : 0);
+    SkScalar*                   pos = NULL;
+    
+    if (NULL != jpositions) {
+        AutoJavaFloatArray autoPos(env, jpositions, count);
+        const float* posValues = autoPos.ptr();
+        pos = (SkScalar*)storage.get();
+        for (size_t i = 0; i < count; i++) {
+            pos[i] = SkFloatToScalar(posValues[i]);
+        }
+    }
+
+    SkShader* shader = SkGradientShader::CreateSweep(SkFloatToScalar(x),
+                                     SkFloatToScalar(y),
+                                     reinterpret_cast<const SkColor*>(colors),
+                                     pos, count);
+    env->ReleaseIntArrayElements(jcolors, const_cast<jint*>(colors),
+                                 JNI_ABORT);
+    ThrowIAE_IfNull(env, shader);
+    return shader;
+}
+
+static SkShader* SweepGradient_create2(JNIEnv* env, jobject, float x, float y,
+                                        int color0, int color1)
+{
+    SkColor colors[2];
+    colors[0] = color0;
+    colors[1] = color1;
+    SkShader* s = SkGradientShader::CreateSweep(SkFloatToScalar(x), SkFloatToScalar(y),
+                                         colors, NULL, 2);
+    ThrowIAE_IfNull(env, s);
+    return s;
+}
+
+///////////////////////////////////////////////////////////////////////////////////////////////
+
+static SkShader* ComposeShader_create1(JNIEnv* env, jobject,
+                                       SkShader* shaderA, SkShader* shaderB, SkXfermode* mode)
+{
+    return new SkComposeShader(shaderA, shaderB, mode);
+}
+
+static SkShader* ComposeShader_create2(JNIEnv* env, jobject,
+                                       SkShader* shaderA, SkShader* shaderB, SkPorterDuff::Mode mode)
+{
+    SkAutoUnref au(SkPorterDuff::CreateXfermode(mode));
+
+    return new SkComposeShader(shaderA, shaderB, (SkXfermode*)au.get());
+}
+
+///////////////////////////////////////////////////////////////////////////////////////////////
+
+static JNINativeMethod gColorMethods[] = {
+    { "nativeRGBToHSV",     "(III[F)V", (void*)Color_RGBToHSV   },
+    { "nativeHSVToColor",   "(I[F)I",   (void*)Color_HSVToColor }
+};
+
+static JNINativeMethod gShaderMethods[] = {
+    { "nativeDestructor",        "(I)V",     (void*)Shader_destructor        },
+    { "nativeGetLocalMatrix",    "(II)Z",    (void*)Shader_getLocalMatrix    },
+    { "nativeSetLocalMatrix",    "(II)V",    (void*)Shader_setLocalMatrix    }
+};
+
+static JNINativeMethod gBitmapShaderMethods[] = {
+    { "nativeCreate",   "(III)I",  (void*)BitmapShader_constructor }
+};
+
+static JNINativeMethod gLinearGradientMethods[] = {
+    { "nativeCreate1",  "(FFFF[I[FI)I", (void*)LinearGradient_create1   },
+    { "nativeCreate2",  "(FFFFIII)I",   (void*)LinearGradient_create2   }
+};
+
+static JNINativeMethod gRadialGradientMethods[] = {
+    {"nativeCreate1",   "(FFF[I[FI)I",  (void*)RadialGradient_create1   },
+    {"nativeCreate2",   "(FFFIII)I",    (void*)RadialGradient_create2   }
+};
+
+static JNINativeMethod gSweepGradientMethods[] = {
+    {"nativeCreate1",   "(FF[I[F)I",  (void*)SweepGradient_create1   },
+    {"nativeCreate2",   "(FFII)I",    (void*)SweepGradient_create2   }
+};
+
+static JNINativeMethod gComposeShaderMethods[] = {
+    {"nativeCreate1",  "(III)I",    (void*)ComposeShader_create1 },
+    {"nativeCreate2",  "(III)I",    (void*)ComposeShader_create2 }
+};
+
+#include <android_runtime/AndroidRuntime.h>
+
+#define REG(env, name, array)                                                                       \
+    result = android::AndroidRuntime::registerNativeMethods(env, name, array, SK_ARRAY_COUNT(array));  \
+    if (result < 0) return result
+
+int register_android_graphics_Shader(JNIEnv* env);
+int register_android_graphics_Shader(JNIEnv* env)
+{
+    int result;
+    
+    REG(env, "android/graphics/Color", gColorMethods);
+    REG(env, "android/graphics/Shader", gShaderMethods);
+    REG(env, "android/graphics/BitmapShader", gBitmapShaderMethods);
+    REG(env, "android/graphics/LinearGradient", gLinearGradientMethods);
+    REG(env, "android/graphics/RadialGradient", gRadialGradientMethods);
+    REG(env, "android/graphics/SweepGradient", gSweepGradientMethods);
+    REG(env, "android/graphics/ComposeShader", gComposeShaderMethods);
+    
+    return result;
+}
+
diff --git a/core/jni/android/graphics/Typeface.cpp b/core/jni/android/graphics/Typeface.cpp
new file mode 100644
index 0000000..32954ce
--- /dev/null
+++ b/core/jni/android/graphics/Typeface.cpp
@@ -0,0 +1,156 @@
+#include "jni.h"
+#include <android_runtime/AndroidRuntime.h>
+
+#include "GraphicsJNI.h"
+#include <android_runtime/android_util_AssetManager.h>
+#include "SkStream.h"
+#include "SkTypeface.h"
+#include <utils/AssetManager.h>
+
+using namespace android;
+
+class AutoJavaStringToUTF8 {
+public:
+    AutoJavaStringToUTF8(JNIEnv* env, jstring str) : fEnv(env), fJStr(str)
+    {
+        fCStr = env->GetStringUTFChars(str, NULL);
+    }
+    ~AutoJavaStringToUTF8()
+    {
+        fEnv->ReleaseStringUTFChars(fJStr, fCStr);
+    }
+    const char* c_str() const { return fCStr; }
+
+private:
+    JNIEnv*     fEnv;
+    jstring     fJStr;
+    const char* fCStr;
+};
+
+static SkTypeface* Typeface_create(JNIEnv* env, jobject, jstring name,
+                                   SkTypeface::Style style) {
+    SkTypeface* face;
+
+    if (NULL == name) {
+        face = SkTypeface::Create(NULL, (SkTypeface::Style)style);
+    }
+    else {
+        AutoJavaStringToUTF8    str(env, name);
+        face = SkTypeface::Create(str.c_str(), style);
+    }
+    return face;
+}
+
+static SkTypeface* Typeface_createFromTypeface(JNIEnv* env, jobject, SkTypeface* family, int style) {
+    return SkTypeface::CreateFromTypeface(family, (SkTypeface::Style)style);
+}
+ 
+static void Typeface_unref(JNIEnv* env, jobject obj, SkTypeface* face) {
+    face->unref();
+}
+
+static int Typeface_getStyle(JNIEnv* env, jobject obj, SkTypeface* face) {
+    return face->getStyle();
+}
+
+class AssetStream : public SkStream {
+public:
+    AssetStream(Asset* asset, bool hasMemoryBase) : fAsset(asset)
+    {
+        fMemoryBase = hasMemoryBase ? fAsset->getBuffer(false) : NULL;
+    }
+
+    virtual ~AssetStream()
+    {
+        delete fAsset;
+    }
+    
+    virtual const void* getMemoryBase()
+    {
+        return fMemoryBase;
+    }
+
+	virtual bool rewind()
+    {
+        off_t pos = fAsset->seek(0, SEEK_SET);
+        return pos != (off_t)-1;
+    }
+    
+	virtual size_t read(void* buffer, size_t size)
+    {
+        ssize_t amount;
+        
+        if (NULL == buffer)
+        {
+            if (0 == size)  // caller is asking us for our total length
+                return fAsset->getLength();
+            
+            // asset->seek returns new total offset
+            // we want to return amount that was skipped
+            
+            off_t oldOffset = fAsset->seek(0, SEEK_CUR);
+            if (-1 == oldOffset)
+                return 0;
+            off_t newOffset = fAsset->seek(size, SEEK_CUR);
+            if (-1 == newOffset)
+                return 0;
+            
+            amount = newOffset - oldOffset;
+        }
+        else
+        {
+            amount = fAsset->read(buffer, size);
+        }
+        
+        if (amount < 0)
+            amount = 0;
+        return amount;
+    }
+    
+private:
+    Asset*      fAsset;
+    const void* fMemoryBase;
+};
+
+static SkTypeface* Typeface_createFromAsset(JNIEnv* env, jobject,
+                                            jobject jassetMgr,
+                                            jstring jpath) {
+    
+    NPE_CHECK_RETURN_ZERO(env, jassetMgr);
+    NPE_CHECK_RETURN_ZERO(env, jpath);
+    
+    AssetManager* mgr = assetManagerForJavaObject(env, jassetMgr);
+    if (NULL == mgr) {
+        return NULL;
+    }
+    
+    AutoJavaStringToUTF8    str(env, jpath);
+    Asset* asset = mgr->open(str.c_str(), Asset::ACCESS_BUFFER);
+    if (NULL == asset) {
+        return NULL;
+    }
+    
+    return SkTypeface::CreateFromStream(new AssetStream(asset, true));
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+static JNINativeMethod gTypefaceMethods[] = {
+    { "nativeCreate",        "(Ljava/lang/String;I)I", (void*)Typeface_create },
+    { "nativeCreateFromTypeface", "(II)I", (void*)Typeface_createFromTypeface },
+    { "nativeUnref",              "(I)V",  (void*)Typeface_unref },
+    { "nativeGetStyle",           "(I)I",  (void*)Typeface_getStyle },
+    { "nativeCreateFromAsset",
+                        "(Landroid/content/res/AssetManager;Ljava/lang/String;)I",
+                                            (void*)Typeface_createFromAsset }
+};
+
+int register_android_graphics_Typeface(JNIEnv* env);
+int register_android_graphics_Typeface(JNIEnv* env)
+{
+    return android::AndroidRuntime::registerNativeMethods(env,
+                                                       "android/graphics/Typeface",
+                                                       gTypefaceMethods,
+                                                       SK_ARRAY_COUNT(gTypefaceMethods));
+}
+
diff --git a/core/jni/android/graphics/Xfermode.cpp b/core/jni/android/graphics/Xfermode.cpp
new file mode 100644
index 0000000..2b53d28
--- /dev/null
+++ b/core/jni/android/graphics/Xfermode.cpp
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2007 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.
+ */
+
+#include "jni.h"
+#include "GraphicsJNI.h"
+#include <android_runtime/AndroidRuntime.h>
+
+#include "SkAvoidXfermode.h"
+#include "SkPixelXorXfermode.h"
+
+namespace android {
+
+class SkXfermodeGlue {
+public:
+
+    static void finalizer(JNIEnv* env, jobject, SkXfermode* obj)
+    {
+        obj->safeUnref();
+    }
+    
+    static SkXfermode* avoid_create(JNIEnv* env, jobject, SkColor opColor,
+                                U8CPU tolerance, SkAvoidXfermode::Mode mode)
+    {
+        return new SkAvoidXfermode(opColor, tolerance, mode);
+    }
+    
+    static SkXfermode* pixelxor_create(JNIEnv* env, jobject, SkColor opColor)
+    {
+        return new SkPixelXorXfermode(opColor);
+    }
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+static JNINativeMethod gXfermodeMethods[] = {
+    {"finalizer", "(I)V", (void*) SkXfermodeGlue::finalizer}
+};
+
+static JNINativeMethod gAvoidMethods[] = {
+    {"nativeCreate", "(III)I", (void*) SkXfermodeGlue::avoid_create}
+};
+
+static JNINativeMethod gPixelXorMethods[] = {
+    {"nativeCreate", "(I)I", (void*) SkXfermodeGlue::pixelxor_create}
+};
+
+#include <android_runtime/AndroidRuntime.h>
+
+#define REG(env, name, array)                                              \
+    result = android::AndroidRuntime::registerNativeMethods(env, name, array, \
+                                                  SK_ARRAY_COUNT(array));  \
+    if (result < 0) return result
+
+int register_android_graphics_Xfermode(JNIEnv* env) {
+    int result;
+    
+    REG(env, "android/graphics/Xfermode", gXfermodeMethods);
+    REG(env, "android/graphics/AvoidXfermode", gAvoidMethods);
+    REG(env, "android/graphics/PixelXorXfermode", gPixelXorMethods);
+
+    return 0;
+}
+
+}
diff --git a/core/jni/android/opengl/poly.h b/core/jni/android/opengl/poly.h
new file mode 100644
index 0000000..85b44e3
--- /dev/null
+++ b/core/jni/android/opengl/poly.h
@@ -0,0 +1,51 @@
+/*
+**
+** Copyright 2007, 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.
+*/
+
+/* Based on the public domain code:
+ * Generic Convex Polygon Scan Conversion and Clipping
+ * by Paul Heckbert
+ * from "Graphics Gems", Academic Press, 1990
+ */
+
+
+#ifndef POLY_HDR
+#define POLY_HDR
+
+namespace android {
+
+#define POLY_NMAX 10		/* max #sides to a polygon; change if needed */
+/* note that poly_clip, given an n-gon as input, might output an (n+6)gon */
+/* POLY_NMAX=10 is thus appropriate if input polygons are triangles or quads */
+
+typedef struct {		/* A POLYGON VERTEX */
+    float sx, sy, sz, sw;	/* screen space position (sometimes homo.) */
+} Poly_vert;
+
+typedef struct {		/* A POLYGON */
+    int n;			/* number of sides */
+    Poly_vert vert[POLY_NMAX];	/* vertices */
+} Poly;
+
+#define POLY_CLIP_OUT 0		/* polygon entirely outside box */
+#define POLY_CLIP_PARTIAL 1	/* polygon partially inside */
+#define POLY_CLIP_IN 2		/* polygon entirely inside box */
+
+int	poly_clip_to_frustum(Poly *p1);
+
+} // namespace android
+
+#endif
diff --git a/core/jni/android/opengl/poly_clip.cpp b/core/jni/android/opengl/poly_clip.cpp
new file mode 100644
index 0000000..04e4b17
--- /dev/null
+++ b/core/jni/android/opengl/poly_clip.cpp
@@ -0,0 +1,155 @@
+/*
+**
+** Copyright 2007, 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.
+*/
+
+/*
+ * Generic Convex Polygon Scan Conversion and Clipping
+ * by Paul Heckbert
+ * from "Graphics Gems", Academic Press, 1990
+ */
+
+/* Based on the public domain code:
+ * poly_clip.c: homogeneous 3-D convex polygon clipper
+ *
+ * Paul Heckbert	1985, Dec 1989
+ */
+
+#include "poly.h"
+#include "string.h"
+
+#define LOG_TAG "StreetView"
+#include <utils/Log.h>
+
+namespace android {
+
+#define SWAP(a, b, temp)	{temp = a; a = b; b = temp;}
+#define COORD(vert, i) ((float *)(vert))[i]
+
+#define CLIP_AND_SWAP(elem, sign, k, p, q, r) { \
+    poly_clip_to_halfspace(p, q, &v->elem-(float *)v, sign, sign*k); \
+    if (q->n==0) {p1->n = 0; return POLY_CLIP_OUT;} \
+    SWAP(p, q, r); \
+}
+
+/*
+ * poly_clip_to_halfspace: clip convex polygon p against a plane,
+ * copying the portion satisfying sign*s[index] < k*sw into q,
+ * where s is a Poly_vert* cast as a float*.
+ * index is an index into the array of floats at each vertex, such that
+ * s[index] is sx, sy, or sz (screen space x, y, or z).
+ * Thus, to clip against xmin, use
+ *	poly_clip_to_halfspace(p, q, XINDEX, -1., -xmin);
+ * and to clip against xmax, use
+ *	poly_clip_to_halfspace(p, q, XINDEX,  1.,  xmax);
+ */
+
+void poly_clip_to_halfspace(Poly* p, Poly* q, int index, float sign, float k)
+{
+    unsigned long m;
+    float *up, *vp, *wp;
+    Poly_vert *v;
+    int i;
+    Poly_vert *u;
+    float t, tu, tv;
+
+    q->n = 0;
+
+    /* start with u=vert[n-1], v=vert[0] */
+    u = &p->vert[p->n-1];
+    tu = sign*COORD(u, index) - u->sw*k;
+    for (v= &p->vert[0], i=p->n; i>0; i--, u=v, tu=tv, v++) {
+	/* on old polygon (p), u is previous vertex, v is current vertex */
+	/* tv is negative if vertex v is in */
+	tv = sign*COORD(v, index) - v->sw*k;
+	if ((tu <= 0.0f) ^ (tv <= 0.0f)) {
+	    /* edge crosses plane; add intersection point to q */
+	    t = tu/(tu-tv);
+	    up = (float *)u;
+	    vp = (float *)v;
+	    wp = (float *)&q->vert[q->n].sx;
+		for(int i = 0; i < 4; i++, wp++, up++, vp++) {
+			*wp = *up+t*(*vp-*up);
+		}
+	    q->n++;
+	}
+	if (tv<=0.0f)		/* vertex v is in, copy it to q */
+	    q->vert[q->n++] = *v;
+    }
+}
+
+/*
+ * poly_clip_to_frustum: Clip the convex polygon p1 to the screen space frustum
+ * using the homogeneous screen coordinates (sx, sy, sz, sw) of each vertex,
+ * testing if v->sx/v->sw > box->x0 and v->sx/v->sw < box->x1,
+ * and similar tests for y and z, for each vertex v of the polygon.
+ * If polygon is entirely inside box, then POLY_CLIP_IN is returned.
+ * If polygon is entirely outside box, then POLY_CLIP_OUT is returned.
+ * Otherwise, if the polygon is cut by the box, p1 is modified and
+ * POLY_CLIP_PARTIAL is returned.
+ *
+ * Given an n-gon as input, clipping against 6 planes could generate an
+ * (n+6)gon, so POLY_NMAX in poly.h must be big enough to allow that.
+ */
+
+int poly_clip_to_frustum(Poly *p1)
+{
+    int x0out = 0, x1out = 0, y0out = 0, y1out = 0, z0out = 0, z1out = 0;
+    int i;
+    Poly_vert *v;
+    Poly p2, *p, *q, *r;
+
+    /* count vertices "outside" with respect to each of the six planes */
+    for (v=p1->vert, i=p1->n; i>0; i--, v++) {
+		float sw = v->sw;
+		if (v->sx < -sw) x0out++;	/* out on left */
+		if (v->sx > sw) x1out++;	/* out on right */
+		if (v->sy < -sw) y0out++;	/* out on top */
+		if (v->sy > sw) y1out++;	/* out on bottom */
+		if (v->sz < -sw) z0out++;	/* out on near */
+		if (v->sz > sw) z1out++;	/* out on far */
+    }
+
+    /* check if all vertices inside */
+    if (x0out+x1out+y0out+y1out+z0out+z1out == 0)
+    	return POLY_CLIP_IN;
+
+    /* check if all vertices are "outside" any of the six planes */
+    if (x0out==p1->n || x1out==p1->n || y0out==p1->n ||
+	y1out==p1->n || z0out==p1->n || z1out==p1->n) {
+	    p1->n = 0;
+	    return POLY_CLIP_OUT;
+	}
+
+    /*
+     * now clip against each of the planes that might cut the polygon,
+     * at each step toggling between polygons p1 and p2
+     */
+    p = p1;
+    q = &p2;
+    if (x0out) CLIP_AND_SWAP(sx, -1.0f, -1.0f, p, q, r);
+    if (x1out) CLIP_AND_SWAP(sx,  1.0f, 1.0f, p, q, r);
+    if (y0out) CLIP_AND_SWAP(sy, -1.0f, -1.0f, p, q, r);
+    if (y1out) CLIP_AND_SWAP(sy,  1.0f, 1.0f, p, q, r);
+    if (z0out) CLIP_AND_SWAP(sz, -1.0f, -1.0f, p, q, r);
+    if (z1out) CLIP_AND_SWAP(sz,  1.0f, 1.0f, p, q, r);
+
+    /* if result ended up in p2 then copy it to p1 */
+    if (p==&p2)
+	memcpy(p1, &p2, sizeof(Poly)-(POLY_NMAX-p2.n)*sizeof(Poly_vert));
+    return POLY_CLIP_PARTIAL;
+}
+
+} // namespace android
diff --git a/core/jni/android/opengl/util.cpp b/core/jni/android/opengl/util.cpp
new file mode 100644
index 0000000..a1059e5
--- /dev/null
+++ b/core/jni/android/opengl/util.cpp
@@ -0,0 +1,730 @@
+/**
+ ** Copyright 2007, 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.
+ */
+
+#include <nativehelper/jni.h>
+#include <math.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+#include <dlfcn.h>
+
+#include <GLES/gl.h>
+
+#include <core/SkBitmap.h>
+
+#include "android_runtime/AndroidRuntime.h"
+
+#undef LOG_TAG
+#define LOG_TAG "OpenGLUtil"
+#include <utils/Log.h>
+#include "utils/misc.h"
+
+#include "poly.h"
+
+namespace android {
+
+static jclass gIAEClass;
+static jclass gUOEClass;
+
+static inline
+void mx4transform(float x, float y, float z, float w, const float* pM, float* pDest) {
+    pDest[0] = pM[0 + 4 * 0] * x + pM[0 + 4 * 1] * y + pM[0 + 4 * 2] * z + pM[0 + 4 * 3] * w;
+    pDest[1] = pM[1 + 4 * 0] * x + pM[1 + 4 * 1] * y + pM[1 + 4 * 2] * z + pM[1 + 4 * 3] * w;
+    pDest[2] = pM[2 + 4 * 0] * x + pM[2 + 4 * 1] * y + pM[2 + 4 * 2] * z + pM[2 + 4 * 3] * w;
+    pDest[3] = pM[3 + 4 * 0] * x + pM[3 + 4 * 1] * y + pM[3 + 4 * 2] * z + pM[3 + 4 * 3] * w;
+}
+
+class MallocHelper {
+public:
+    MallocHelper() {
+        mData = 0;
+    }
+    
+    ~MallocHelper() {
+        if (mData != 0) {
+            free(mData);
+        }
+    }
+    
+    void* alloc(size_t size) {
+        mData = malloc(size);
+        return mData;
+    }
+    
+private:
+    void* mData;
+};
+
+#if 0
+static
+void
+print_poly(const char* label, Poly* pPoly) {
+    LOGI("%s: %d verts", label, pPoly->n);
+    for(int i = 0; i < pPoly->n; i++) {
+        Poly_vert* pV = & pPoly->vert[i];
+        LOGI("[%d] %g, %g, %g %g", i, pV->sx, pV->sy, pV->sz, pV->sw);
+    }
+}
+#endif
+
+static
+int visibilityTest(float* pWS, float* pPositions, int positionsLength,
+        unsigned short* pIndices, int indexCount) {
+    MallocHelper mallocHelper;
+    int result = POLY_CLIP_OUT;
+    float* pTransformed = 0;
+    int transformedIndexCount = 0;
+    
+    if ( indexCount < 3 ) {
+        return POLY_CLIP_OUT;
+    }
+    
+    // Find out how many vertices we need to transform
+    // We transform every vertex between the min and max indices, inclusive.
+    // This is OK for the data sets we expect to use with this function, but
+    // for other loads it might be better to use a more sophisticated vertex
+    // cache of some sort.
+    
+    int minIndex = 65536;
+    int maxIndex = -1;
+    for(int i = 0; i < indexCount; i++) {
+        int index = pIndices[i];
+        if ( index < minIndex ) {
+            minIndex = index;
+        }
+        if ( index > maxIndex ) {
+            maxIndex = index;
+        }
+    }
+    
+    if ( maxIndex * 3 > positionsLength) {
+        return -1;
+    }
+    
+    transformedIndexCount = maxIndex - minIndex + 1;
+    pTransformed = (float*) mallocHelper.alloc(transformedIndexCount * 4 * sizeof(float));
+    
+    if (pTransformed == 0 ) {
+        return -2;
+    }
+    
+    // Transform the vertices
+    {
+        const float* pSrc = pPositions + 3 * minIndex;
+        float* pDst = pTransformed;
+        for (int i = 0; i < transformedIndexCount; i++, pSrc += 3, pDst += 4) {
+            mx4transform(pSrc[0], pSrc[1], pSrc[2], 1.0f, pWS,  pDst);
+        }
+    }
+    
+    // Clip the triangles
+    
+    Poly poly;
+    float* pDest = & poly.vert[0].sx;
+    for (int i = 0; i < indexCount; i += 3) {
+        poly.n = 3;
+        memcpy(pDest    , pTransformed + 4 * (pIndices[i    ] - minIndex), 4 * sizeof(float));
+        memcpy(pDest + 4, pTransformed + 4 * (pIndices[i + 1] - minIndex), 4 * sizeof(float));
+        memcpy(pDest + 8, pTransformed + 4 * (pIndices[i + 2] - minIndex), 4 * sizeof(float));
+        result = poly_clip_to_frustum(&poly);
+        if ( result != POLY_CLIP_OUT) {
+            return result;
+        }
+    }
+
+    return result;
+}
+
+template<class JArray, class T>
+class ArrayHelper {
+public:
+    ArrayHelper(JNIEnv* env, JArray ref, jint offset, jint minSize) {
+        mEnv = env;
+        mRef = ref;
+        mOffset = offset;
+        mMinSize = minSize;
+        mBase = 0;
+        mReleaseParam = JNI_ABORT;
+    }
+
+    ~ArrayHelper() {
+        if (mBase) {
+            mEnv->ReleasePrimitiveArrayCritical(mRef, mBase, mReleaseParam);
+        }
+    }
+    
+    // We seperate the bounds check from the initialization because we want to
+    // be able to bounds-check multiple arrays, and we can't throw an exception
+    // after we've called GetPrimitiveArrayCritical.
+    
+    // Return true if the bounds check succeeded
+    // Else instruct the runtime to throw an exception
+    
+    bool check() {
+        if ( ! mRef) {
+            mEnv->ThrowNew(gIAEClass, "array == null");
+            return false;
+        }
+        if ( mOffset < 0) {
+            mEnv->ThrowNew(gIAEClass, "offset < 0");
+            return false;
+        }
+        mLength = mEnv->GetArrayLength(mRef) - mOffset;
+        if (mLength < mMinSize ) {
+            mEnv->ThrowNew(gIAEClass, "length - offset < n");
+            return false;
+        }
+        return true;
+    }
+    
+    // Bind the array.
+    
+    void bind() {
+        mBase = (T*) mEnv->GetPrimitiveArrayCritical(mRef, (jboolean *) 0);
+        mData = mBase + mOffset;
+    }
+
+    void commitChanges() {
+        mReleaseParam = 0;
+    }
+    
+    T* mData;
+    int mLength;
+    
+private:
+    T* mBase;
+    JNIEnv* mEnv;
+    JArray mRef;
+    jint mOffset;
+    jint mMinSize;
+    int mReleaseParam;
+};
+
+typedef ArrayHelper<jfloatArray, float> FloatArrayHelper;
+typedef ArrayHelper<jcharArray, unsigned short> UnsignedShortArrayHelper;
+typedef ArrayHelper<jintArray, int> IntArrayHelper;
+typedef ArrayHelper<jbyteArray, unsigned char> ByteArrayHelper;
+
+inline float distance2(float x, float y, float z) {
+    return x * x + y * y + z * z;
+}
+
+inline float distance(float x, float y, float z) {
+    return sqrtf(distance2(x, y, z));
+}
+    
+static
+void util_computeBoundingSphere(JNIEnv *env, jclass clazz,
+        jfloatArray positions_ref, jint positionsOffset, jint positionsCount,
+        jfloatArray sphere_ref, jint sphereOffset) {
+    FloatArrayHelper positions(env, positions_ref, positionsOffset, 0);
+    FloatArrayHelper sphere(env, sphere_ref, sphereOffset, 4);
+    
+    bool checkOK = positions.check() && sphere.check();
+        if (! checkOK) {
+        return;
+    }
+    
+    positions.bind();
+    sphere.bind();
+    
+    if ( positionsCount < 1 ) {
+        env->ThrowNew(gIAEClass, "positionsCount < 1");
+        return;
+    }
+    
+    const float* pSrc = positions.mData;
+    
+    // find bounding box
+    float x0 = *pSrc++;
+    float x1 = x0;
+    float y0 = *pSrc++;
+    float y1 = y0;
+    float z0 = *pSrc++;
+    float z1 = z0;
+    
+    for(int i = 1; i < positionsCount; i++) {
+        {
+            float x = *pSrc++;
+            if (x < x0) {
+                x0 = x;
+            }
+            else if (x > x1) {
+                x1 = x;
+            }
+        }
+        {
+            float y = *pSrc++;
+            if (y < y0) {
+                y0 = y;
+            }
+            else if (y > y1) {
+                y1 = y;
+            }
+        }
+        {
+            float z = *pSrc++;
+            if (z < z0) {
+                z0 = z;
+            }
+            else if (z > z1) {
+                z1 = z;
+            }
+        }
+    }
+    
+    // Because we know our input meshes fit pretty well into bounding boxes,
+    // just take the diagonal of the box as defining our sphere.
+    float* pSphere = sphere.mData;
+    float dx = x1 - x0;
+    float dy = y1 - y0;
+    float dz = z1 - z0;
+    *pSphere++ = x0 + dx * 0.5f;
+    *pSphere++ = y0 + dy * 0.5f;
+    *pSphere++ = z0 + dz * 0.5f;
+    *pSphere++ = distance(dx, dy, dz) * 0.5f;
+    
+    sphere.commitChanges();
+}
+
+static void normalizePlane(float* p) {
+    float rdist = 1.0f / distance(p[0], p[1], p[2]);
+    for(int i = 0; i < 4; i++) {
+        p[i] *= rdist;
+    }
+}
+
+static inline float dot3(float x0, float y0, float z0, float x1, float y1, float z1) {
+    return x0 * x1 + y0 * y1 + z0 * z1;
+}
+
+static inline float signedDistance(const float* pPlane, float x, float y, float z) {
+    return dot3(pPlane[0], pPlane[1], pPlane[2], x, y, z) + pPlane[3];
+}
+
+// Return true if the sphere intersects or is inside the frustum
+
+static bool sphereHitsFrustum(const float* pFrustum, const float* pSphere) {
+    float x = pSphere[0];
+    float y = pSphere[1];
+    float z = pSphere[2];
+    float negRadius = -pSphere[3];
+    for (int i = 0; i < 6; i++, pFrustum += 4) {
+        if (signedDistance(pFrustum, x, y, z) <= negRadius) {
+            return false;
+        }
+    }
+    return true;
+}
+
+static void computeFrustum(const float* m, float* f) {
+    float m3 = m[3];
+    float m7 = m[7];
+    float m11 = m[11];
+    float m15 = m[15];
+    // right
+    f[0] = m3  - m[0];
+    f[1] = m7  - m[4];
+    f[2] = m11 - m[8];
+    f[3] = m15 - m[12];
+    normalizePlane(f);
+    f+= 4;
+
+    // left    
+    f[0] = m3  + m[0];
+    f[1] = m7  + m[4];
+    f[2] = m11 + m[8];
+    f[3] = m15 + m[12];
+    normalizePlane(f);
+    f+= 4;
+
+    // top
+    f[0] = m3  - m[1];
+    f[1] = m7  - m[5];
+    f[2] = m11 - m[9];
+    f[3] = m15 - m[13];
+    normalizePlane(f);
+    f+= 4;
+
+    // bottom
+    f[0] = m3  + m[1];
+    f[1] = m7  + m[5];
+    f[2] = m11 + m[9];
+    f[3] = m15 + m[13];
+    normalizePlane(f);
+    f+= 4;
+
+    // far
+    f[0] = m3  - m[2];
+    f[1] = m7  - m[6];
+    f[2] = m11 - m[10];
+    f[3] = m15 - m[14];
+    normalizePlane(f);
+    f+= 4;
+
+    // near
+    f[0] = m3  + m[2];
+    f[1] = m7  + m[6];
+    f[2] = m11 + m[10];
+    f[3] = m15 + m[14];
+    normalizePlane(f);
+}
+
+static
+int util_frustumCullSpheres(JNIEnv *env, jclass clazz,
+        jfloatArray mvp_ref, jint mvpOffset,
+        jfloatArray spheres_ref, jint spheresOffset, jint spheresCount,
+        jintArray results_ref, jint resultsOffset, jint resultsCapacity) {
+    float frustum[6*4];
+    int outputCount;
+    int* pResults;
+    float* pSphere;
+    FloatArrayHelper mvp(env, mvp_ref, mvpOffset, 16);
+    FloatArrayHelper spheres(env, spheres_ref, spheresOffset, spheresCount * 4);
+    IntArrayHelper results(env, results_ref, resultsOffset, resultsCapacity);
+    
+    bool initializedOK = mvp.check() && spheres.check() && results.check();
+        if (! initializedOK) {
+        return -1;
+    }
+    
+    mvp.bind();
+    spheres.bind();
+    results.bind();
+        
+    computeFrustum(mvp.mData, frustum);
+    
+    // Cull the spheres
+    
+    pSphere = spheres.mData;
+    pResults = results.mData;
+    outputCount = 0;
+    for(int i = 0; i < spheresCount; i++, pSphere += 4) {
+        if (sphereHitsFrustum(frustum, pSphere)) {
+            if (outputCount < resultsCapacity) {
+                *pResults++ = i;
+            }
+            outputCount++;
+        }
+    }
+    results.commitChanges();
+    return outputCount;
+}
+
+/*
+ public native int visibilityTest(float[] ws, int wsOffset,
+ float[] positions, int positionsOffset,
+ char[] indices, int indicesOffset, int indexCount);
+ */
+
+static
+int util_visibilityTest(JNIEnv *env, jclass clazz,
+        jfloatArray ws_ref, jint wsOffset,
+        jfloatArray positions_ref, jint positionsOffset,
+        jcharArray indices_ref, jint indicesOffset, jint indexCount) {
+    
+    FloatArrayHelper ws(env, ws_ref, wsOffset, 16);
+    FloatArrayHelper positions(env, positions_ref, positionsOffset, 0);
+    UnsignedShortArrayHelper indices(env, indices_ref, indicesOffset, 0);
+    
+    bool checkOK = ws.check() && positions.check() && indices.check();
+    if (! checkOK) {
+        // Return value will be ignored, because an exception has been thrown.
+        return -1;
+    }
+    
+    if (indices.mLength < indexCount) {
+        env->ThrowNew(gIAEClass, "length < offset + indexCount");
+        // Return value will be ignored, because an exception has been thrown.
+        return -1;
+    }
+    
+    ws.bind();
+    positions.bind();
+    indices.bind();
+        
+    return visibilityTest(ws.mData,
+            positions.mData, positions.mLength,
+            indices.mData, indexCount);
+}
+
+#define I(_i, _j) ((_j)+ 4*(_i))
+
+static
+void multiplyMM(float* r, const float* lhs, const float* rhs)
+{
+    for (int i=0 ; i<4 ; i++) {
+        register const float rhs_i0 = rhs[ I(i,0) ];
+        register float ri0 = lhs[ I(0,0) ] * rhs_i0;
+        register float ri1 = lhs[ I(0,1) ] * rhs_i0;
+        register float ri2 = lhs[ I(0,2) ] * rhs_i0;
+        register float ri3 = lhs[ I(0,3) ] * rhs_i0;
+        for (int j=1 ; j<4 ; j++) {
+            register const float rhs_ij = rhs[ I(i,j) ];
+            ri0 += lhs[ I(j,0) ] * rhs_ij;
+            ri1 += lhs[ I(j,1) ] * rhs_ij;
+            ri2 += lhs[ I(j,2) ] * rhs_ij;
+            ri3 += lhs[ I(j,3) ] * rhs_ij;
+        }
+        r[ I(i,0) ] = ri0;
+        r[ I(i,1) ] = ri1;
+        r[ I(i,2) ] = ri2;
+        r[ I(i,3) ] = ri3;
+    }
+}
+
+static
+void util_multiplyMM(JNIEnv *env, jclass clazz,
+    jfloatArray result_ref, jint resultOffset,
+    jfloatArray lhs_ref, jint lhsOffset,
+    jfloatArray rhs_ref, jint rhsOffset) {
+
+    FloatArrayHelper resultMat(env, result_ref, resultOffset, 16);
+    FloatArrayHelper lhs(env, lhs_ref, lhsOffset, 16);
+    FloatArrayHelper rhs(env, rhs_ref, rhsOffset, 16);
+    
+    bool checkOK = resultMat.check() && lhs.check() && rhs.check();
+    
+    if ( !checkOK ) {
+        return;
+    }
+    
+    resultMat.bind();
+    lhs.bind();
+    rhs.bind();
+    
+    multiplyMM(resultMat.mData, lhs.mData, rhs.mData);
+    
+    resultMat.commitChanges();
+}
+
+static
+void multiplyMV(float* r, const float* lhs, const float* rhs)
+{
+    mx4transform(rhs[0], rhs[1], rhs[2], rhs[3], lhs, r);
+}
+
+static
+void util_multiplyMV(JNIEnv *env, jclass clazz,
+    jfloatArray result_ref, jint resultOffset,
+    jfloatArray lhs_ref, jint lhsOffset,
+    jfloatArray rhs_ref, jint rhsOffset) {
+
+    FloatArrayHelper resultV(env, result_ref, resultOffset, 4);
+    FloatArrayHelper lhs(env, lhs_ref, lhsOffset, 16);
+    FloatArrayHelper rhs(env, rhs_ref, rhsOffset, 4);
+    
+    bool checkOK = resultV.check() && lhs.check() && rhs.check();
+    
+    if ( !checkOK ) {
+        return;
+    }
+    
+    resultV.bind();
+    lhs.bind();
+    rhs.bind();
+    
+    multiplyMV(resultV.mData, lhs.mData, rhs.mData);
+    
+    resultV.commitChanges();
+}
+
+// ---------------------------------------------------------------------------
+
+static jfieldID nativeBitmapID = 0;
+
+void nativeUtilsClassInit(JNIEnv *env, jclass clazz)
+{
+    jclass bitmapClass = env->FindClass("android/graphics/Bitmap");
+    nativeBitmapID = env->GetFieldID(bitmapClass, "mNativeBitmap", "I");    
+}
+
+static int checkFormat(SkBitmap::Config config, int format, int type)
+{
+    switch(config) {
+        case SkBitmap::kIndex8_Config:
+            if (format == GL_PALETTE8_RGBA8_OES)
+                return 0;
+        case SkBitmap::kARGB_8888_Config:
+        case SkBitmap::kA8_Config:
+            if (type == GL_UNSIGNED_BYTE)
+                return 0;
+        case SkBitmap::kARGB_4444_Config:
+        case SkBitmap::kRGB_565_Config:
+            switch (type) {
+                case GL_UNSIGNED_SHORT_4_4_4_4:
+                case GL_UNSIGNED_SHORT_5_6_5:
+                case GL_UNSIGNED_SHORT_5_5_5_1:
+                    return 0;
+                case GL_UNSIGNED_BYTE:
+                    if (format == GL_LUMINANCE_ALPHA)
+                        return 0;
+            }
+            break;
+        default:
+            break;
+    }
+    return -1;
+}
+
+static int getInternalFormat(SkBitmap::Config config)
+{
+    switch(config) {
+        case SkBitmap::kA8_Config:
+            return GL_ALPHA;
+        case SkBitmap::kARGB_4444_Config:
+            return GL_RGBA;
+        case SkBitmap::kARGB_8888_Config:
+            return GL_RGBA;
+        case SkBitmap::kIndex8_Config:
+            return GL_PALETTE8_RGBA8_OES;
+        case SkBitmap::kRGB_565_Config:
+            return GL_RGB;
+        default:
+            return -1;
+    }
+}
+
+static jint util_texImage2D(JNIEnv *env, jclass clazz,
+        jint target, jint level, jint internalformat,
+        jobject jbitmap, jint type, jint border)
+{
+    SkBitmap const * nativeBitmap =
+            (SkBitmap const *)env->GetIntField(jbitmap, nativeBitmapID);
+    const SkBitmap& bitmap(*nativeBitmap);
+    SkBitmap::Config config = bitmap.getConfig();
+    if (internalformat < 0) {
+        internalformat = getInternalFormat(config);
+    }
+    int err = checkFormat(config, internalformat, type);
+    if (err)
+        return err; 
+    bitmap.lockPixels();
+    const int w = bitmap.width();
+    const int h = bitmap.height();
+    const void* p = bitmap.getPixels();
+    if (internalformat == GL_PALETTE8_RGBA8_OES) {
+        if (sizeof(SkPMColor) != sizeof(uint32_t)) {
+            err = -1;
+            goto error;
+        }
+        const size_t size = bitmap.getSize();
+        const size_t palette_size = 256*sizeof(SkPMColor);
+        void* const data = malloc(size + palette_size);
+        if (data) {
+            void* const pixels = (char*)data + palette_size;
+            SkColorTable* ctable = bitmap.getColorTable();
+            memcpy(data, ctable->lockColors(), ctable->count() * sizeof(SkPMColor));
+            memcpy(pixels, p, size);
+            ctable->unlockColors(false);
+            glCompressedTexImage2D(target, level, internalformat, w, h, border, 0, data);
+            free(data);
+        } else {
+            err = -1;
+        }
+    } else {
+        glTexImage2D(target, level, internalformat, w, h, border, internalformat, type, p);
+    }
+error:
+    bitmap.unlockPixels();
+    return err;
+}
+
+static jint util_texSubImage2D(JNIEnv *env, jclass clazz,
+        jint target, jint level, jint xoffset, jint yoffset,
+        jobject jbitmap, jint format, jint type)
+{
+    SkBitmap const * nativeBitmap =
+            (SkBitmap const *)env->GetIntField(jbitmap, nativeBitmapID);
+    const SkBitmap& bitmap(*nativeBitmap);
+    SkBitmap::Config config = bitmap.getConfig();
+    if (format < 0) {
+        format = getInternalFormat(config);
+        if (format == GL_PALETTE8_RGBA8_OES)
+            return -1; // glCompressedTexSubImage2D() not supported
+    }
+    int err = checkFormat(config, format, type);
+    if (err)
+        return err; 
+    bitmap.lockPixels();
+    const int w = bitmap.width();
+    const int h = bitmap.height();
+    const void* p = bitmap.getPixels();
+    glTexSubImage2D(target, level, xoffset, yoffset, w, h, format, type, p);
+    bitmap.unlockPixels();
+    return 0;
+}
+
+/*
+ * JNI registration
+ */
+
+static void
+lookupClasses(JNIEnv* env) {
+    gIAEClass = (jclass) env->NewGlobalRef(
+            env->FindClass("java/lang/IllegalArgumentException"));
+    gUOEClass = (jclass) env->NewGlobalRef(
+            env->FindClass("java/lang/UnsupportedOperationException"));
+}
+
+static JNINativeMethod gMatrixMethods[] = {
+    { "multiplyMM", "([FI[FI[FI)V", (void*)util_multiplyMM }, 
+    { "multiplyMV", "([FI[FI[FI)V", (void*)util_multiplyMV }, 
+};
+
+static JNINativeMethod gVisiblityMethods[] = {
+    { "computeBoundingSphere", "([FII[FI)V", (void*)util_computeBoundingSphere }, 
+    { "frustumCullSpheres", "([FI[FII[III)I", (void*)util_frustumCullSpheres },
+    { "visibilityTest", "([FI[FI[CII)I", (void*)util_visibilityTest }, 
+};
+
+static JNINativeMethod gUtilsMethods[] = {
+    {"nativeClassInit", "()V",                          (void*)nativeUtilsClassInit },
+    { "native_texImage2D", "(IIILandroid/graphics/Bitmap;II)I", (void*)util_texImage2D }, 
+    { "native_texSubImage2D", "(IIIILandroid/graphics/Bitmap;II)I", (void*)util_texSubImage2D }, 
+};
+
+typedef struct _ClassRegistrationInfo {
+    const char* classPath;
+    JNINativeMethod* methods;
+    size_t methodCount;
+} ClassRegistrationInfo;
+
+static ClassRegistrationInfo gClasses[] = {
+        {"android/opengl/Matrix", gMatrixMethods, NELEM(gMatrixMethods)},
+        {"android/opengl/Visibility", gVisiblityMethods, NELEM(gVisiblityMethods)},
+        {"android/opengl/GLUtils", gUtilsMethods, NELEM(gUtilsMethods)},
+};
+
+int register_android_opengl_classes(JNIEnv* env)
+{
+    lookupClasses(env);
+    int result = 0;
+    for (int i = 0; i < NELEM(gClasses); i++) {
+        ClassRegistrationInfo* cri = &gClasses[i];
+        result = AndroidRuntime::registerNativeMethods(env,
+                cri->classPath, cri->methods, cri->methodCount);
+        if (result < 0) {
+            LOGE("Failed to register %s: %d", cri->classPath, result);
+            break;
+        }
+    }
+    return result;
+}
+
+} // namespace android
+
diff --git a/core/jni/android_bluetooth_BluetoothAudioGateway.cpp b/core/jni/android_bluetooth_BluetoothAudioGateway.cpp
new file mode 100755
index 0000000..7f87d80
--- /dev/null
+++ b/core/jni/android_bluetooth_BluetoothAudioGateway.cpp
@@ -0,0 +1,553 @@
+/*
+** Copyright 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.
+*/
+
+#define LOG_TAG "BluetoothAudioGateway.cpp"
+
+#include "android_bluetooth_common.h"
+#include "android_runtime/AndroidRuntime.h"
+#include "JNIHelp.h"
+#include "jni.h"
+#include "utils/Log.h"
+#include "utils/misc.h"
+
+#define USE_ACCEPT_DIRECTLY (0)
+#define USE_SELECT (0) /* 1 for select(), 0 for poll(); used only when
+                          USE_ACCEPT_DIRECTLY == 0 */
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/socket.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/uio.h>
+#include <ctype.h>
+
+#if USE_SELECT
+#include <sys/select.h>
+#else
+#include <sys/poll.h>
+#endif
+
+#ifdef HAVE_BLUETOOTH
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/hci.h>
+#include <bluetooth/hci_lib.h>
+#include <bluetooth/rfcomm.h>
+#include <bluetooth/sco.h>
+#endif
+
+namespace android {
+
+#ifdef HAVE_BLUETOOTH
+static jfieldID field_mNativeData;
+    /* in */
+static jfieldID field_mHandsfreeAgRfcommChannel;
+static jfieldID field_mHeadsetAgRfcommChannel;
+    /* out */
+static jfieldID field_mTimeoutRemainingMs; /* out */
+
+static jfieldID field_mConnectingHeadsetAddress;
+static jfieldID field_mConnectingHeadsetRfcommChannel; /* -1 when not connected */
+static jfieldID field_mConnectingHeadsetSocketFd;
+
+static jfieldID field_mConnectingHandsfreeAddress;
+static jfieldID field_mConnectingHandsfreeRfcommChannel; /* -1 when not connected */
+static jfieldID field_mConnectingHandsfreeSocketFd;
+
+
+typedef struct {
+    int hcidev;
+    int hf_ag_rfcomm_channel;
+    int hs_ag_rfcomm_channel;
+    int hf_ag_rfcomm_sock;
+    int hs_ag_rfcomm_sock;
+} native_data_t;
+
+static inline native_data_t * get_native_data(JNIEnv *env, jobject object) {
+    return (native_data_t *)(env->GetIntField(object,
+                                                 field_mNativeData));
+}
+
+static int setup_listening_socket(int dev, int channel);
+#endif
+
+static void classInitNative(JNIEnv* env, jclass clazz) {
+    LOGV(__FUNCTION__);
+#ifdef HAVE_BLUETOOTH
+
+    /* in */
+    field_mNativeData = get_field(env, clazz, "mNativeData", "I");
+    field_mHandsfreeAgRfcommChannel = 
+        get_field(env, clazz, "mHandsfreeAgRfcommChannel", "I");
+    field_mHeadsetAgRfcommChannel = 
+        get_field(env, clazz, "mHeadsetAgRfcommChannel", "I");
+
+    /* out */
+    field_mConnectingHeadsetAddress = 
+        get_field(env, clazz, 
+                  "mConnectingHeadsetAddress", "Ljava/lang/String;");
+    field_mConnectingHeadsetRfcommChannel = 
+        get_field(env, clazz, "mConnectingHeadsetRfcommChannel", "I");
+    field_mConnectingHeadsetSocketFd = 
+        get_field(env, clazz, "mConnectingHeadsetSocketFd", "I");
+
+    field_mConnectingHandsfreeAddress = 
+        get_field(env, clazz, 
+                  "mConnectingHandsfreeAddress", "Ljava/lang/String;");
+    field_mConnectingHandsfreeRfcommChannel = 
+        get_field(env, clazz, "mConnectingHandsfreeRfcommChannel", "I");
+    field_mConnectingHandsfreeSocketFd = 
+        get_field(env, clazz, "mConnectingHandsfreeSocketFd", "I");
+
+    field_mTimeoutRemainingMs = 
+        get_field(env, clazz, "mTimeoutRemainingMs", "I");
+#endif
+}
+
+static void initializeNativeDataNative(JNIEnv* env, jobject object) {
+    LOGV(__FUNCTION__);
+#ifdef HAVE_BLUETOOTH
+    native_data_t *nat = (native_data_t *)calloc(1, sizeof(native_data_t));
+    if (NULL == nat) {
+        LOGE("%s: out of memory!", __FUNCTION__);
+        return;
+    }
+
+    nat->hcidev = BLUETOOTH_ADAPTER_HCI_NUM;
+
+    env->SetIntField(object, field_mNativeData, (jint)nat);
+    nat->hf_ag_rfcomm_channel =
+        env->GetIntField(object, field_mHandsfreeAgRfcommChannel);
+    nat->hs_ag_rfcomm_channel =
+        env->GetIntField(object, field_mHeadsetAgRfcommChannel);
+    LOGV("HF RFCOMM channel = %d.", nat->hf_ag_rfcomm_channel);
+    LOGV("HS RFCOMM channel = %d.", nat->hs_ag_rfcomm_channel);
+
+    /* Set the default values of these to -1. */
+    env->SetIntField(object, field_mConnectingHeadsetRfcommChannel, -1);
+    env->SetIntField(object, field_mConnectingHandsfreeRfcommChannel, -1);
+
+    nat->hf_ag_rfcomm_sock = -1;
+    nat->hs_ag_rfcomm_sock = -1;
+#endif
+}
+
+static void cleanupNativeDataNative(JNIEnv* env, jobject object) {
+    LOGV(__FUNCTION__);
+#ifdef HAVE_BLUETOOTH
+    native_data_t *nat = get_native_data(env, object);
+    if (nat) {
+        free(nat);
+    }
+#endif
+}
+
+#ifdef HAVE_BLUETOOTH
+
+#if USE_ACCEPT_DIRECTLY==0
+static int set_nb(int sk, bool nb) {
+    int flags = fcntl(sk, F_GETFL);
+    if (flags < 0) {
+        LOGE("Can't get socket flags with fcntl(): %s (%d)", 
+             strerror(errno), errno);
+        close(sk);
+        return -1;
+    }
+    flags &= ~O_NONBLOCK;
+    if (nb) flags |= O_NONBLOCK;
+    int status = fcntl(sk, F_SETFL, flags);
+    if (status < 0) {
+        LOGE("Can't set socket to nonblocking mode with fcntl(): %s (%d)",
+             strerror(errno), errno);
+        close(sk);
+        return -1;
+    }
+    return 0;
+}
+#endif /*USE_ACCEPT_DIRECTLY==0*/
+
+static int do_accept(JNIEnv* env, jobject object, int ag_fd,
+                     jfieldID out_fd,
+                     jfieldID out_address,
+                     jfieldID out_channel) {
+
+#if USE_ACCEPT_DIRECTLY==0
+    if (set_nb(ag_fd, true) < 0)
+        return -1;
+#endif
+
+    struct sockaddr_rc raddr;
+    int alen = sizeof(raddr);
+    int nsk = accept(ag_fd, (struct sockaddr *) &raddr, &alen);
+    if (nsk < 0) {
+        LOGE("Error on accept from socket fd %d: %s (%d).",
+             ag_fd,
+             strerror(errno),
+             errno);
+#if USE_ACCEPT_DIRECTLY==0
+        set_nb(ag_fd, false);
+#endif
+        return -1;
+    }
+
+    env->SetIntField(object, out_fd, nsk);
+    env->SetIntField(object, out_channel, raddr.rc_channel);
+
+    char addr[BTADDR_SIZE];
+    get_bdaddr_as_string(&raddr.rc_bdaddr, addr);
+    env->SetObjectField(object, out_address, env->NewStringUTF(addr));
+
+    LOGI("Successful accept() on AG socket %d: new socket %d, address %s, RFCOMM channel %d",
+         ag_fd,
+         nsk,
+         addr,
+         raddr.rc_channel);
+#if USE_ACCEPT_DIRECTLY==0
+    set_nb(ag_fd, false);
+#endif
+    return 0;
+}
+
+#if USE_SELECT
+static inline int on_accept_set_fields(JNIEnv* env, jobject object,
+                                       fd_set *rset, int ag_fd,
+                                       jfieldID out_fd,
+                                       jfieldID out_address,
+                                       jfieldID out_channel) {
+
+    env->SetIntField(object, out_channel, -1);
+
+    if (ag_fd >= 0 && FD_ISSET(ag_fd, &rset)) {
+        return do_accept(env, object, ag_fd,
+                         out_fd, out_address, out_channel);
+    }
+    else {
+        LOGI("fd = %d, FD_ISSET() = %d",
+             ag_fd,
+             FD_ISSET(ag_fd, &rset));
+        if (ag_fd >= 0 && !FD_ISSET(ag_fd, &rset)) {
+            LOGE("WTF???");
+            return -1;
+        }
+    }
+
+    return 0;
+}
+#endif
+#endif /* HAVE_BLUETOOTH */
+
+static jboolean waitForHandsfreeConnectNative(JNIEnv* env, jobject object,
+                                              jint timeout_ms) {
+//    LOGV(__FUNCTION__);
+#ifdef HAVE_BLUETOOTH
+
+    env->SetIntField(object, field_mTimeoutRemainingMs, timeout_ms);
+
+    int n = 0;
+    native_data_t *nat = get_native_data(env, object);
+#if USE_ACCEPT_DIRECTLY
+    if (nat->hf_ag_rfcomm_channel > 0) {
+        LOGI("Setting HF AG server socket to RFCOMM port %d!", 
+             nat->hf_ag_rfcomm_channel);
+        struct timeval tv;
+        int len = sizeof(tv);
+        if (getsockopt(nat->hf_ag_rfcomm_channel, 
+                       SOL_SOCKET, SO_RCVTIMEO, &tv, &len) < 0) {
+            LOGE("getsockopt(%d, SOL_SOCKET, SO_RCVTIMEO): %s (%d)",
+                 nat->hf_ag_rfcomm_channel,
+                 strerror(errno),
+                 errno);
+            return JNI_FALSE;
+        }
+        LOGI("Current HF AG server socket RCVTIMEO is (%d(s), %d(us))!", 
+             (int)tv.tv_sec, (int)tv.tv_usec);
+        if (timeout_ms >= 0) {
+            tv.tv_sec = timeout_ms / 1000;
+            tv.tv_usec = 1000 * (timeout_ms % 1000);
+            if (setsockopt(nat->hf_ag_rfcomm_channel, 
+                           SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)) < 0) {
+                LOGE("setsockopt(%d, SOL_SOCKET, SO_RCVTIMEO): %s (%d)",
+                     nat->hf_ag_rfcomm_channel,
+                     strerror(errno),
+                     errno);
+                return JNI_FALSE;
+            }
+            LOGI("Changed HF AG server socket RCVTIMEO to (%d(s), %d(us))!", 
+                 (int)tv.tv_sec, (int)tv.tv_usec);
+        }
+
+        if (!do_accept(env, object, nat->hf_ag_rfcomm_sock, 
+                       field_mConnectingHandsfreeSocketFd,
+                       field_mConnectingHandsfreeAddress,
+                       field_mConnectingHandsfreeRfcommChannel))
+        {
+            env->SetIntField(object, field_mTimeoutRemainingMs, 0);
+            return JNI_TRUE;
+        }
+        return JNI_FALSE;
+    }
+#else
+#if USE_SELECT
+    fd_set rset;
+    FD_ZERO(&rset);
+    int cnt = 0;
+    if (nat->hf_ag_rfcomm_channel > 0) {
+        LOGI("Setting HF AG server socket to RFCOMM port %d!", 
+             nat->hf_ag_rfcomm_channel);
+        cnt++;
+        FD_SET(nat->hf_ag_rfcomm_sock, &rset);
+    }
+    if (nat->hs_ag_rfcomm_channel > 0) {
+        LOGI("Setting HS AG server socket to RFCOMM port %d!", 
+             nat->hs_ag_rfcomm_channel);
+        cnt++;
+        FD_SET(nat->hs_ag_rfcomm_sock, &rset);
+    }
+    if (cnt == 0) {
+        LOGE("Neither HF nor HS listening sockets are open!");
+        return JNI_FALSE;
+    }
+
+    struct timeval to;
+    if (timeout_ms >= 0) {
+        to.tv_sec = timeout_ms / 1000;
+        to.tv_usec = 1000 * (timeout_ms % 1000);
+    }
+    n = select(MAX(nat->hf_ag_rfcomm_sock, 
+                       nat->hs_ag_rfcomm_sock) + 1,
+                   &rset,
+                   NULL,
+                   NULL,
+                   (timeout_ms < 0 ? NULL : &to));
+    if (timeout_ms > 0) {
+        jint remaining = to.tv_sec*1000 + to.tv_usec/1000;
+        LOGI("Remaining time %ldms", (long)remaining);
+        env->SetIntField(object, field_mTimeoutRemainingMs,
+                         remaining);
+    }
+
+    LOGI("listening select() returned %d", n);
+
+    if (n <= 0) {
+        if (n < 0)  {
+            LOGE("listening select() on RFCOMM sockets: %s (%d)",
+                 strerror(errno),
+                 errno);
+        }
+        return JNI_FALSE;
+    }
+
+    n = on_accept_set_fields(env, object, 
+                             &rset, nat->hf_ag_rfcomm_sock,
+                             field_mConnectingHandsfreeSocketFd,
+                             field_mConnectingHandsfreeAddress,
+                             field_mConnectingHandsfreeRfcommChannel);
+
+    n += on_accept_set_fields(env, object,
+                              &rset, nat->hs_ag_rfcomm_sock,
+                              field_mConnectingHeadsetSocketFd,
+                              field_mConnectingHeadsetAddress,
+                              field_mConnectingHeadsetRfcommChannel);
+
+    return !n ? JNI_TRUE : JNI_FALSE;
+#else
+    struct pollfd fds[2];
+    int cnt = 0;
+    if (nat->hf_ag_rfcomm_channel > 0) {
+//        LOGI("Setting HF AG server socket %d to RFCOMM port %d!", 
+//             nat->hf_ag_rfcomm_sock,
+//             nat->hf_ag_rfcomm_channel);
+        fds[cnt].fd = nat->hf_ag_rfcomm_sock;
+        fds[cnt].events = POLLIN | POLLPRI | POLLOUT | POLLERR;
+        cnt++;
+    }
+    if (nat->hs_ag_rfcomm_channel > 0) {
+//        LOGI("Setting HS AG server socket %d to RFCOMM port %d!", 
+//             nat->hs_ag_rfcomm_sock,
+//             nat->hs_ag_rfcomm_channel);
+        fds[cnt].fd = nat->hs_ag_rfcomm_sock;
+        fds[cnt].events = POLLIN | POLLPRI | POLLOUT | POLLERR;
+        cnt++;
+    }
+    if (cnt == 0) {
+        LOGE("Neither HF nor HS listening sockets are open!");
+        return JNI_FALSE;
+    }
+    n = poll(fds, cnt, timeout_ms);
+    if (n <= 0) {
+        if (n < 0)  {
+            LOGE("listening poll() on RFCOMM sockets: %s (%d)",
+                 strerror(errno),
+                 errno);
+        }
+        else {
+            env->SetIntField(object, field_mTimeoutRemainingMs, 0);
+//            LOGI("listening poll() on RFCOMM socket timed out");
+        }
+        return JNI_FALSE;
+    }
+
+    //LOGI("listening poll() on RFCOMM socket returned %d", n);
+    int err = 0;
+    for (cnt = 0; cnt < (int)(sizeof(fds)/sizeof(fds[0])); cnt++) {
+        //LOGI("Poll on fd %d revent = %d.", fds[cnt].fd, fds[cnt].revents);
+        if (fds[cnt].fd == nat->hf_ag_rfcomm_sock) {
+            if (fds[cnt].revents & (POLLIN | POLLPRI | POLLOUT)) {
+                LOGI("Accepting HF connection.\n");
+                err += do_accept(env, object, fds[cnt].fd, 
+                               field_mConnectingHandsfreeSocketFd,
+                               field_mConnectingHandsfreeAddress,
+                               field_mConnectingHandsfreeRfcommChannel);
+                n--;
+            }
+        }
+        else if (fds[cnt].fd == nat->hs_ag_rfcomm_sock) {
+            if (fds[cnt].revents & (POLLIN | POLLPRI | POLLOUT)) {
+                LOGI("Accepting HS connection.\n");
+                err += do_accept(env, object, fds[cnt].fd, 
+                               field_mConnectingHeadsetSocketFd,
+                               field_mConnectingHeadsetAddress,
+                               field_mConnectingHeadsetRfcommChannel);
+                n--;
+            }
+        }
+    } /* for */
+
+    if (n != 0) {
+        LOGI("Bogus poll(): %d fake pollfd entrie(s)!", n);
+        return JNI_FALSE;
+    }
+
+    return !err ? JNI_TRUE : JNI_FALSE;
+#endif /* USE_SELECT */
+#endif /* USE_ACCEPT_DIRECTLY */
+#else
+    return JNI_FALSE;
+#endif /* HAVE_BLUETOOTH */
+}
+
+static jboolean setUpListeningSocketsNative(JNIEnv* env, jobject object) {
+    LOGV(__FUNCTION__);
+#ifdef HAVE_BLUETOOTH
+    native_data_t *nat = get_native_data(env, object);
+
+    nat->hf_ag_rfcomm_sock =
+        setup_listening_socket(nat->hcidev, nat->hf_ag_rfcomm_channel);
+    if (nat->hf_ag_rfcomm_sock < 0)
+        return JNI_FALSE;
+
+    nat->hs_ag_rfcomm_sock =
+        setup_listening_socket(nat->hcidev, nat->hs_ag_rfcomm_channel);
+    if (nat->hs_ag_rfcomm_sock < 0) {
+        close(nat->hf_ag_rfcomm_sock);
+        nat->hf_ag_rfcomm_sock = -1;
+        return JNI_FALSE;
+    }
+
+    return JNI_TRUE;
+#else
+    return JNI_FALSE;
+#endif /* HAVE_BLUETOOTH */
+}
+
+#ifdef HAVE_BLUETOOTH
+static int setup_listening_socket(int dev, int channel) {
+    struct sockaddr_rc laddr;
+    int sk, lm;
+
+    sk = socket(AF_BLUETOOTH, SOCK_STREAM, BTPROTO_RFCOMM);
+    if (sk < 0) {
+        LOGE("Can't create RFCOMM socket");
+        return -1;
+    }
+
+    if (debug_no_encrypt()) {
+        lm = RFCOMM_LM_AUTH;
+    } else {
+        lm = RFCOMM_LM_AUTH | RFCOMM_LM_ENCRYPT;
+    }
+
+	if (lm && setsockopt(sk, SOL_RFCOMM, RFCOMM_LM, &lm, sizeof(lm)) < 0) {
+		LOGE("Can't set RFCOMM link mode");
+		close(sk);
+		return -1;
+	}
+
+    laddr.rc_family = AF_BLUETOOTH;
+    bacpy(&laddr.rc_bdaddr, BDADDR_ANY);
+    laddr.rc_channel = channel;
+
+	if (bind(sk, (struct sockaddr *)&laddr, sizeof(laddr)) < 0) {
+		LOGE("Can't bind RFCOMM socket");
+		close(sk);
+		return -1;
+	}
+
+    listen(sk, 10);
+    return sk;
+}
+#endif /* HAVE_BLUETOOTH */
+
+/*
+    private native void tearDownListeningSocketsNative();
+*/
+static void tearDownListeningSocketsNative(JNIEnv *env, jobject object) {
+    LOGV(__FUNCTION__);
+#ifdef HAVE_BLUETOOTH
+    native_data_t *nat = get_native_data(env, object);
+
+    if (nat->hf_ag_rfcomm_sock > 0) {
+        if (close(nat->hf_ag_rfcomm_sock) < 0) {
+            LOGE("Could not close HF server socket: %s (%d)\n",
+                 strerror(errno), errno);
+        }
+        nat->hf_ag_rfcomm_sock = -1;
+    }
+    if (nat->hs_ag_rfcomm_sock > 0) {
+        if (close(nat->hs_ag_rfcomm_sock) < 0) {
+            LOGE("Could not close HS server socket: %s (%d)\n",
+                 strerror(errno), errno);
+        }
+        nat->hs_ag_rfcomm_sock = -1;
+    }
+#endif /* HAVE_BLUETOOTH */
+}
+
+static JNINativeMethod sMethods[] = {
+     /* name, signature, funcPtr */
+
+    {"classInitNative", "()V", (void*)classInitNative},
+    {"initializeNativeDataNative", "()V", (void *)initializeNativeDataNative},
+    {"cleanupNativeDataNative", "()V", (void *)cleanupNativeDataNative},
+
+    {"setUpListeningSocketsNative", "()Z", (void *)setUpListeningSocketsNative},
+    {"tearDownListeningSocketsNative", "()V", (void *)tearDownListeningSocketsNative},
+    {"waitForHandsfreeConnectNative", "(I)Z", (void *)waitForHandsfreeConnectNative},
+};
+
+int register_android_bluetooth_BluetoothAudioGateway(JNIEnv *env) {
+    return AndroidRuntime::registerNativeMethods(env,
+            "android/bluetooth/BluetoothAudioGateway", sMethods,
+            NELEM(sMethods));
+}
+
+} /* namespace android */
diff --git a/core/jni/android_bluetooth_Database.cpp b/core/jni/android_bluetooth_Database.cpp
new file mode 100644
index 0000000..136c9a3
--- /dev/null
+++ b/core/jni/android_bluetooth_Database.cpp
@@ -0,0 +1,183 @@
+/*
+ * Copyright (C) 2007 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.
+ */
+
+#define DBUS_CLASS_NAME BLUEZ_DBUS_BASE_IFC ".Database"
+#define LOG_TAG "bluetooth_Database.cpp"
+
+#include "android_bluetooth_common.h"
+#include "android_runtime/AndroidRuntime.h"
+#include "JNIHelp.h"
+#include "jni.h"
+#include "utils/Log.h"
+
+#ifdef HAVE_BLUETOOTH
+#include <dbus/dbus.h>
+#endif
+
+namespace android {
+
+#ifdef HAVE_BLUETOOTH
+static DBusConnection* conn = NULL;   // Singleton thread-safe connection
+#endif
+
+static void classInitNative(JNIEnv* env, jclass clazz) {
+    LOGV(__FUNCTION__);
+#ifdef HAVE_BLUETOOTH
+    conn = NULL;
+#endif
+}
+
+static void initializeNativeDataNative(JNIEnv* env, jobject object) {
+    LOGV(__FUNCTION__);
+
+#ifdef HAVE_BLUETOOTH
+    if (conn == NULL) {
+        DBusError err;
+        dbus_error_init(&err);
+        dbus_threads_init_default();
+        conn = dbus_bus_get(DBUS_BUS_SYSTEM, &err);
+        if (dbus_error_is_set(&err)) {
+            LOGE("Could not get onto the system bus!");
+            dbus_error_free(&err);
+        }
+    }
+#endif
+}
+
+static void cleanupNativeDataNative(JNIEnv* env, jobject object) {
+    LOGV(__FUNCTION__);
+}
+
+static jint addServiceRecordNative(JNIEnv *env, jobject object,
+                                   jbyteArray record) {
+    LOGV(__FUNCTION__);
+#ifdef HAVE_BLUETOOTH
+    if (conn != NULL) {
+        jbyte* c_record = env->GetByteArrayElements(record, NULL);
+        DBusMessage *reply = dbus_func_args(env,
+                                            conn,
+                                            BLUEZ_DBUS_BASE_PATH,
+                                            DBUS_CLASS_NAME,
+                                            "AddServiceRecord",
+                                            DBUS_TYPE_ARRAY, DBUS_TYPE_BYTE,
+                                            &c_record,
+                                            env->GetArrayLength(record),
+                                            DBUS_TYPE_INVALID);
+        env->ReleaseByteArrayElements(record, c_record, JNI_ABORT);
+        return reply ? dbus_returns_uint32(env, reply) : -1;
+    }
+#endif
+    return -1;
+}
+
+static jint addServiceRecordFromXmlNative(JNIEnv *env, jobject object,
+                                          jstring record) {
+    LOGV(__FUNCTION__);
+#ifdef HAVE_BLUETOOTH
+    if (conn != NULL) {
+        const char *c_record = env->GetStringUTFChars(record, NULL);
+        DBusMessage *reply = dbus_func_args(env,
+                                            conn,
+                                            BLUEZ_DBUS_BASE_PATH,
+                                            DBUS_CLASS_NAME,
+                                            "AddServiceRecordFromXML",
+                                            DBUS_TYPE_STRING, &c_record,
+                                            DBUS_TYPE_INVALID);
+        env->ReleaseStringUTFChars(record, c_record);
+        return reply ? dbus_returns_uint32(env, reply) : -1;
+    }
+#endif
+    return -1;
+}
+
+static void updateServiceRecordNative(JNIEnv *env, jobject object,
+                                      jint handle,
+                                      jbyteArray record) {
+    LOGV(__FUNCTION__);
+#ifdef HAVE_BLUETOOTH
+    if (conn != NULL) {
+        jbyte* c_record = env->GetByteArrayElements(record, NULL);
+        DBusMessage *reply = dbus_func_args(env,
+                                            conn,
+                                            BLUEZ_DBUS_BASE_PATH,
+                                            DBUS_CLASS_NAME,
+                                            "UpdateServiceRecord",
+                                            DBUS_TYPE_UINT32, &handle,
+                                            DBUS_TYPE_ARRAY, DBUS_TYPE_BYTE,
+                                            &c_record,
+                                            env->GetArrayLength(record),
+                                            DBUS_TYPE_INVALID);
+        env->ReleaseByteArrayElements(record, c_record, JNI_ABORT);
+    }
+#endif
+}
+
+static void updateServiceRecordFromXmlNative(JNIEnv *env, jobject object,
+                                             jint handle,
+                                             jstring record) {
+    LOGV(__FUNCTION__);
+#ifdef HAVE_BLUETOOTH
+    if (conn != NULL) {
+        const char *c_record = env->GetStringUTFChars(record, NULL);
+        DBusMessage *reply = dbus_func_args(env,
+                                            conn,
+                                            BLUEZ_DBUS_BASE_PATH,
+                                            DBUS_CLASS_NAME,
+                                            "UpdateServiceRecordFromXML",
+                                            DBUS_TYPE_UINT32, &handle,
+                                            DBUS_TYPE_STRING, &c_record,
+                                            DBUS_TYPE_INVALID);
+        env->ReleaseStringUTFChars(record, c_record);
+    }
+#endif
+}
+
+/* private static native void removeServiceRecordNative(int handle); */
+static void removeServiceRecordNative(JNIEnv *env, jobject object,
+                                      jint handle) {
+    LOGV(__FUNCTION__);
+#ifdef HAVE_BLUETOOTH
+    if (conn != NULL) {
+        DBusMessage *reply = dbus_func_args(env,
+                                            conn,
+                                            BLUEZ_DBUS_BASE_PATH,
+                                            DBUS_CLASS_NAME,
+                                            "RemoveServiceRecord",
+                                            DBUS_TYPE_UINT32, &handle,
+                                            DBUS_TYPE_INVALID);
+    }
+#endif
+}
+
+
+static JNINativeMethod sMethods[] = {
+     /* name, signature, funcPtr */
+    {"classInitNative", "()V", (void*)classInitNative},
+    {"initializeNativeDataNative", "()V", (void *)initializeNativeDataNative},
+    {"cleanupNativeDataNative", "()V", (void *)cleanupNativeDataNative},
+    {"addServiceRecordNative", "([B)I", (void*)addServiceRecordNative},
+    {"addServiceRecordFromXmlNative", "(Ljava/lang/String;)I", (void*)addServiceRecordFromXmlNative},
+    {"updateServiceRecordNative", "(I[B)V", (void*)updateServiceRecordNative},
+    {"updateServiceRecordFromXmlNative", "(ILjava/lang/String;)V", (void*)updateServiceRecordFromXmlNative},
+    {"removeServiceRecordNative", "(I)V", (void*)removeServiceRecordNative},
+};
+
+int register_android_bluetooth_Database(JNIEnv *env) {
+    return AndroidRuntime::registerNativeMethods(env,
+            "android/bluetooth/Database", sMethods, NELEM(sMethods));
+}
+
+} /* namespace android */
diff --git a/core/jni/android_bluetooth_HeadsetBase.cpp b/core/jni/android_bluetooth_HeadsetBase.cpp
new file mode 100644
index 0000000..bb19e92
--- /dev/null
+++ b/core/jni/android_bluetooth_HeadsetBase.cpp
@@ -0,0 +1,548 @@
+/*
+** Copyright 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.
+*/
+
+#define LOG_TAG "BT HSHFP"
+
+#include "android_bluetooth_common.h"
+#include "android_runtime/AndroidRuntime.h"
+#include "JNIHelp.h"
+#include "jni.h"
+#include "utils/Log.h"
+#include "utils/misc.h"
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/socket.h>
+#include <sys/uio.h>
+#include <sys/poll.h>
+
+#ifdef HAVE_BLUETOOTH
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/rfcomm.h>
+#include <bluetooth/sco.h>
+#endif
+
+namespace android {
+
+#ifdef HAVE_BLUETOOTH
+static jfieldID field_mNativeData;
+static jfieldID field_mAddress;
+static jfieldID field_mRfcommChannel;
+static jfieldID field_mTimeoutRemainingMs;
+
+typedef struct {
+    jstring address;
+    const char *c_address;
+    int rfcomm_channel;
+    int last_read_err;
+    int rfcomm_sock;
+    int rfcomm_connected; // -1 in progress, 0 not connected, 1 connected
+    int rfcomm_sock_flags;
+} native_data_t;
+
+static inline native_data_t * get_native_data(JNIEnv *env, jobject object) {
+    return (native_data_t *)(env->GetIntField(object, field_mNativeData));
+}
+
+static const char CRLF[] = "\xd\xa";
+static const int CRLF_LEN = 2;
+
+static inline int write_error_check(int fd, const char* line, int len) {
+    int ret;
+    errno = 0;
+    ret = write(fd, line, len);
+    if (ret < 0) {
+        LOGE("%s: write() failed: %s (%d)", __FUNCTION__, strerror(errno),
+             errno);
+        return -1;
+    }
+    if (ret != len) {
+        LOGE("%s: write() only wrote %d of %d bytes", __FUNCTION__, ret, len);
+        return -1;
+    }
+    return 0;
+}
+
+static int send_line(int fd, const char* line) {
+    int nw;
+    int len = strlen(line);
+    int llen = len + CRLF_LEN * 2 + 1;
+    char *buffer = (char *)calloc(llen, sizeof(char));
+
+    snprintf(buffer, llen, "%s%s%s", CRLF, line, CRLF);
+
+    if (write_error_check(fd, buffer, llen - 1)) {
+        free(buffer);
+        return -1;
+    }
+    free(buffer);
+    return 0;
+}
+
+static const char* get_line(int fd, char *buf, int len, int timeout_ms,
+                            int *err) {
+    char *bufit=buf;
+    int fd_flags = fcntl(fd, F_GETFL, 0);
+    struct pollfd pfd;
+
+again:
+    *bufit = 0;
+    pfd.fd = fd;
+    pfd.events = POLLIN;
+    *err = errno = 0;
+    int ret = poll(&pfd, 1, timeout_ms);
+    if (ret < 0) {
+        LOGE("poll() error\n");
+        *err = errno;
+        return NULL;
+    }
+    if (ret == 0) {
+        return NULL;
+    }
+
+    if (pfd.revents & (POLLHUP | POLLERR | POLLNVAL)) {
+        LOGW("RFCOMM poll() returned  success (%d), "
+             "but with an unexpected revents bitmask: %#x\n", ret, pfd.revents);
+        errno = EIO;
+        *err = errno;
+        return NULL;
+    }
+
+    while ((int)(bufit - buf) < len)
+    {
+        errno = 0;
+        int rc = read(fd, bufit, 1);
+
+        if (!rc)
+            break;
+
+        if (rc < 0) {
+            if (errno == EBUSY) {
+                LOGI("read() error %s (%d): repeating read()...",
+                     strerror(errno), errno);
+                goto again;
+            }
+            *err = errno;
+            LOGE("read() error %s (%d)", strerror(errno), errno);
+            return NULL;
+        }
+
+
+        if (*bufit=='\xd') {
+            break;
+        }
+
+        if (*bufit=='\xa')
+            bufit = buf;
+        else
+            bufit++;
+    }
+
+    *bufit = '\x0';
+    LOG(LOG_INFO, "Bluetooth AT recv", buf);
+
+    return buf;
+}
+#endif
+
+static void classInitNative(JNIEnv* env, jclass clazz) {
+    LOGV(__FUNCTION__);
+#ifdef HAVE_BLUETOOTH
+    field_mNativeData = get_field(env, clazz, "mNativeData", "I");
+    field_mAddress = get_field(env, clazz, "mAddress", "Ljava/lang/String;");
+    field_mTimeoutRemainingMs = get_field(env, clazz, "mTimeoutRemainingMs", "I");
+    field_mRfcommChannel = get_field(env, clazz, "mRfcommChannel", "I");
+#endif
+}
+
+static void initializeNativeDataNative(JNIEnv* env, jobject object,
+                                       jint socketFd) {
+    LOGV(__FUNCTION__);
+#ifdef HAVE_BLUETOOTH
+    native_data_t *nat = (native_data_t *)calloc(1, sizeof(native_data_t));
+    if (NULL == nat) {
+        LOGE("%s: out of memory!", __FUNCTION__);
+        return;
+    }
+
+    env->SetIntField(object, field_mNativeData, (jint)nat);
+    nat->address =
+        (jstring)env->NewGlobalRef(env->GetObjectField(object,
+                                                       field_mAddress));
+    nat->c_address = env->GetStringUTFChars(nat->address, NULL);
+    nat->rfcomm_channel = env->GetIntField(object, field_mRfcommChannel);
+    nat->rfcomm_sock = socketFd;
+    nat->rfcomm_connected = socketFd >= 0;
+    if (nat->rfcomm_connected)
+        LOGI("%s: ALREADY CONNECTED!", __FUNCTION__);
+#endif
+}
+
+static void cleanupNativeDataNative(JNIEnv* env, jobject object) {
+    LOGV(__FUNCTION__);
+#ifdef HAVE_BLUETOOTH
+    native_data_t *nat =
+        (native_data_t *)env->GetIntField(object, field_mNativeData);
+    env->ReleaseStringUTFChars(nat->address, nat->c_address);
+    env->DeleteGlobalRef(nat->address);
+    if (nat)
+        free(nat);
+#endif
+}
+
+static jboolean connectNative(JNIEnv *env, jobject obj)
+{
+    LOGV(__FUNCTION__);
+#ifdef HAVE_BLUETOOTH
+    int lm;
+    struct sockaddr_rc addr;
+    native_data_t *nat = get_native_data(env, obj);
+
+    nat->rfcomm_sock = socket(PF_BLUETOOTH, SOCK_STREAM, BTPROTO_RFCOMM);
+
+    if (nat->rfcomm_sock < 0) {
+        LOGE("%s: Could not create RFCOMM socket: %s\n", __FUNCTION__,
+             strerror(errno));
+        return JNI_FALSE;
+    }
+
+    if (debug_no_encrypt()) {
+        lm = RFCOMM_LM_AUTH;
+    } else {
+        lm = RFCOMM_LM_AUTH | RFCOMM_LM_ENCRYPT;
+    }
+
+    if (lm && setsockopt(nat->rfcomm_sock, SOL_RFCOMM, RFCOMM_LM, &lm,
+                sizeof(lm)) < 0) {
+        LOGE("%s: Can't set RFCOMM link mode", __FUNCTION__);
+        close(nat->rfcomm_sock);
+        return JNI_FALSE;
+    }
+
+    memset(&addr, 0, sizeof(struct sockaddr_rc));
+    get_bdaddr(nat->c_address, &addr.rc_bdaddr);
+    addr.rc_channel = nat->rfcomm_channel;
+    addr.rc_family = AF_BLUETOOTH;
+    nat->rfcomm_connected = 0;
+    while (nat->rfcomm_connected == 0) {
+        if (connect(nat->rfcomm_sock, (struct sockaddr *)&addr,
+                      sizeof(addr)) < 0) {
+            if (errno == EINTR) continue;
+            LOGE("%s: connect() failed: %s\n", __FUNCTION__, strerror(errno));
+            close(nat->rfcomm_sock);
+            nat->rfcomm_sock = -1;
+            return JNI_FALSE;
+        } else {
+            nat->rfcomm_connected = 1;
+        }
+    }
+
+    return JNI_TRUE;
+#else
+    return JNI_FALSE;
+#endif
+}
+
+static jboolean connectAsyncNative(JNIEnv *env, jobject obj) {
+    LOGV(__FUNCTION__);
+#ifdef HAVE_BLUETOOTH
+    struct sockaddr_rc addr;
+    native_data_t *nat = get_native_data(env, obj);
+
+    if (nat->rfcomm_connected) {
+        LOGV("RFCOMM socket is already connected or connection is in progress.");
+        return JNI_TRUE;
+    }
+
+    if (nat->rfcomm_sock < 0) {
+        int lm;
+
+        nat->rfcomm_sock = socket(PF_BLUETOOTH, SOCK_STREAM, BTPROTO_RFCOMM);
+        if (nat->rfcomm_sock < 0) {
+            LOGE("%s: Could not create RFCOMM socket: %s\n", __FUNCTION__,
+                 strerror(errno));
+            return JNI_FALSE;
+        }
+
+        if (debug_no_encrypt()) {
+            lm = RFCOMM_LM_AUTH;
+        } else {
+            lm = RFCOMM_LM_AUTH | RFCOMM_LM_ENCRYPT;
+        }
+
+        if (lm && setsockopt(nat->rfcomm_sock, SOL_RFCOMM, RFCOMM_LM, &lm,
+                    sizeof(lm)) < 0) {
+            LOGE("%s: Can't set RFCOMM link mode", __FUNCTION__);
+            close(nat->rfcomm_sock);
+            return JNI_FALSE;
+        }
+        LOGI("Created RFCOMM socket fd %d.", nat->rfcomm_sock);
+    }
+
+    memset(&addr, 0, sizeof(struct sockaddr_rc));
+    get_bdaddr(nat->c_address, &addr.rc_bdaddr);
+    addr.rc_channel = nat->rfcomm_channel;
+    addr.rc_family = AF_BLUETOOTH;
+    if (nat->rfcomm_sock_flags >= 0) {
+        nat->rfcomm_sock_flags = fcntl(nat->rfcomm_sock, F_GETFL, 0);
+        if (fcntl(nat->rfcomm_sock,
+                  F_SETFL, nat->rfcomm_sock_flags | O_NONBLOCK) >= 0) {
+            int rc;
+            nat->rfcomm_connected = 0;
+            errno = 0;
+            rc = connect(nat->rfcomm_sock,
+                        (struct sockaddr *)&addr,
+                         sizeof(addr));
+
+            if (rc >= 0) {
+                nat->rfcomm_connected = 1;
+                LOGI("async connect successful");
+                return JNI_TRUE;
+            }
+            else if (rc < 0) {
+                if (errno == EINPROGRESS || errno == EAGAIN)
+                {
+                    LOGI("async connect is in progress (%s)",
+                         strerror(errno));
+                    nat->rfcomm_connected = -1;
+                    return JNI_TRUE;
+                }
+                else
+                {
+                    LOGE("async connect error: %s (%d)", strerror(errno), errno);
+                    close(nat->rfcomm_sock);
+                    nat->rfcomm_sock = -1;
+                    return JNI_FALSE;
+                }
+            }
+        } // fcntl(nat->rfcomm_sock ...)
+    } // if (nat->rfcomm_sock_flags >= 0)
+#endif
+    return JNI_FALSE;
+}
+
+static jint waitForAsyncConnectNative(JNIEnv *env, jobject obj,
+                                           jint timeout_ms) {
+    LOGV(__FUNCTION__);
+#ifdef HAVE_BLUETOOTH
+    struct sockaddr_rc addr;
+    native_data_t *nat = get_native_data(env, obj);
+
+    env->SetIntField(obj, field_mTimeoutRemainingMs, timeout_ms);
+
+    if (nat->rfcomm_connected > 0) {
+        LOGI("RFCOMM is already connected!");
+        return 1;
+    }
+
+    if (nat->rfcomm_sock >= 0 && nat->rfcomm_connected == 0) {
+        LOGI("Re-opening RFCOMM socket.");
+        close(nat->rfcomm_sock);
+        nat->rfcomm_sock = -1;
+    }
+    if (JNI_FALSE == connectAsyncNative(env, obj)) {
+        LOGI("Failed to re-open RFCOMM socket!");
+        return -1;
+    }
+
+    if (nat->rfcomm_sock >= 0) {
+        /* Do an asynchronous select() */
+        int n;
+        fd_set rset, wset;
+        struct timeval to;
+
+        FD_ZERO(&rset);
+        FD_ZERO(&wset);
+        FD_SET(nat->rfcomm_sock, &rset);
+        FD_SET(nat->rfcomm_sock, &wset);
+        if (timeout_ms >= 0) {
+            to.tv_sec = timeout_ms / 1000;
+            to.tv_usec = 1000 * (timeout_ms % 1000);
+        }
+        n = select(nat->rfcomm_sock + 1,
+                   &rset,
+                   &wset,
+                   NULL,
+                   (timeout_ms < 0 ? NULL : &to));
+
+        if (timeout_ms > 0) {
+            jint remaining = to.tv_sec*1000 + to.tv_usec/1000;
+            LOGV("Remaining time %ldms", (long)remaining);
+            env->SetIntField(obj, field_mTimeoutRemainingMs,
+                             remaining);
+        }
+
+        if (n <= 0) {
+            if (n < 0)  {
+                LOGE("select() on RFCOMM socket: %s (%d)",
+                     strerror(errno),
+                     errno);
+                return -1;
+            }
+            return 0;
+        }
+        /* n must be equal to 1 and either rset or wset must have the
+           file descriptor set. */
+        LOGV("select() returned %d.", n);
+        if (FD_ISSET(nat->rfcomm_sock, &rset) ||
+            FD_ISSET(nat->rfcomm_sock, &wset))
+        {
+            /* A trial async read() will tell us if everything is OK. */
+            {
+                char ch;
+                errno = 0;
+                int nr = read(nat->rfcomm_sock, &ch, 1);
+                /* It should be that nr != 1 because we just opened a socket
+                   and we haven't sent anything over it for the other side to
+                   respond... but one can't be paranoid enough.
+                */
+                if (nr >= 0 || errno != EAGAIN) {
+                    LOGE("RFCOMM async connect() error: %s (%d), nr = %d\n",
+                         strerror(errno),
+                         errno,
+                         nr);
+                    /* Clear the rfcomm_connected flag to cause this function
+                       to re-create the socket and re-attempt the connect()
+                       the next time it is called.
+                    */
+                    nat->rfcomm_connected = 0;
+                    /* Restore the blocking properties of the socket. */
+                    fcntl(nat->rfcomm_sock, F_SETFL, nat->rfcomm_sock_flags);
+                    close(nat->rfcomm_sock);
+                    nat->rfcomm_sock = -1;
+                    return -1;
+                }
+            }
+            /* Restore the blocking properties of the socket. */
+            fcntl(nat->rfcomm_sock, F_SETFL, nat->rfcomm_sock_flags);
+            LOGI("Successful RFCOMM socket connect.");
+            nat->rfcomm_connected = 1;
+            return 1;
+        }
+    }
+    else LOGE("RFCOMM socket file descriptor %d is bad!",
+              nat->rfcomm_sock);
+#endif
+    return -1;
+}
+
+static void disconnectNative(JNIEnv *env, jobject obj) {
+    LOGV(__FUNCTION__);
+#ifdef HAVE_BLUETOOTH
+    native_data_t *nat = get_native_data(env, obj);
+    if (nat->rfcomm_sock >= 0) {
+        close(nat->rfcomm_sock);
+        nat->rfcomm_sock = -1;
+        nat->rfcomm_connected = 0;
+    }
+#endif
+}
+
+static void pretty_log_urc(const char *urc) {
+    size_t i;
+    bool in_line_break = false;
+    char *buf = (char *)calloc(strlen(urc) + 1, sizeof(char));
+
+    strcpy(buf, urc);
+    for (i = 0; i < strlen(buf); i++) {
+        switch(buf[i]) {
+        case '\r':
+        case '\n':
+            in_line_break = true;
+            buf[i] = ' ';
+            break;
+        default:
+            if (in_line_break) {
+                in_line_break = false;
+                buf[i-1] = '\n';
+            }
+        }
+    }
+    LOG(LOG_INFO, "Bluetooth AT sent", buf);
+
+    free(buf);
+}
+
+static jboolean sendURCNative(JNIEnv *env, jobject obj, jstring urc) {
+#ifdef HAVE_BLUETOOTH
+    native_data_t *nat = get_native_data(env, obj);
+    if (nat->rfcomm_connected) {
+        const char *c_urc = env->GetStringUTFChars(urc, NULL);
+        jboolean ret = send_line(nat->rfcomm_sock, c_urc) == 0 ? JNI_TRUE : JNI_FALSE;
+        if (ret == JNI_TRUE) pretty_log_urc(c_urc);
+        env->ReleaseStringUTFChars(urc, c_urc);
+        return ret;
+    }
+#endif
+    return JNI_FALSE;
+}
+
+static jstring readNative(JNIEnv *env, jobject obj, jint timeout_ms) {
+#ifdef HAVE_BLUETOOTH
+    {
+        native_data_t *nat = get_native_data(env, obj);
+        if (nat->rfcomm_connected) {
+            char buf[128];
+            const char *ret = get_line(nat->rfcomm_sock,
+                                       buf, sizeof(buf),
+                                       timeout_ms,
+                                       &nat->last_read_err);
+            return ret ? env->NewStringUTF(ret) : NULL;
+        }
+        return NULL;
+    }
+#else
+    return NULL;
+#endif
+}
+
+static jint getLastReadStatusNative(JNIEnv *env, jobject obj) {
+#ifdef HAVE_BLUETOOTH
+    {
+        native_data_t *nat = get_native_data(env, obj);
+        if (nat->rfcomm_connected)
+            return (jint)nat->last_read_err;
+        return 0;
+    }
+#else
+    return 0;
+#endif
+}
+
+static JNINativeMethod sMethods[] = {
+     /* name, signature, funcPtr */
+    {"classInitNative", "()V", (void*)classInitNative},
+    {"initializeNativeDataNative", "(I)V", (void *)initializeNativeDataNative},
+    {"cleanupNativeDataNative", "()V", (void *)cleanupNativeDataNative},
+    {"connectNative", "()Z", (void *)connectNative},
+    {"connectAsyncNative", "()Z", (void *)connectAsyncNative},
+    {"waitForAsyncConnectNative", "(I)I", (void *)waitForAsyncConnectNative},
+    {"disconnectNative", "()V", (void *)disconnectNative},
+    {"sendURCNative", "(Ljava/lang/String;)Z", (void *)sendURCNative},
+    {"readNative", "(I)Ljava/lang/String;", (void *)readNative},
+    {"getLastReadStatusNative", "()I", (void *)getLastReadStatusNative},
+};
+
+int register_android_bluetooth_HeadsetBase(JNIEnv *env) {
+    return AndroidRuntime::registerNativeMethods(env,
+            "android/bluetooth/HeadsetBase", sMethods, NELEM(sMethods));
+}
+
+} /* namespace android */
diff --git a/core/jni/android_bluetooth_RfcommSocket.cpp b/core/jni/android_bluetooth_RfcommSocket.cpp
new file mode 100644
index 0000000..3ed35d9
--- /dev/null
+++ b/core/jni/android_bluetooth_RfcommSocket.cpp
@@ -0,0 +1,621 @@
+/*
+** Copyright 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.
+*/
+
+#define LOG_TAG "bluetooth_RfcommSocket.cpp"
+
+#include "android_bluetooth_common.h"
+#include "android_runtime/AndroidRuntime.h"
+#include "JNIHelp.h"
+#include "jni.h"
+#include "utils/Log.h"
+#include "utils/misc.h"
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/socket.h>
+#include <sys/uio.h>
+#include <sys/poll.h>
+
+#ifdef HAVE_BLUETOOTH
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/rfcomm.h>
+#include <bluetooth/sco.h>
+#endif
+
+namespace android {
+
+#ifdef HAVE_BLUETOOTH
+static jfieldID field_mNativeData;
+static jfieldID field_mTimeoutRemainingMs;
+static jfieldID field_mAcceptTimeoutRemainingMs;
+static jfieldID field_mAddress;
+static jfieldID field_mPort;
+
+typedef struct {
+    jstring address;
+    const char *c_address;
+    int rfcomm_channel;
+    int last_read_err;
+    int rfcomm_sock;
+    // < 0 -- in progress, 
+    //   0 -- not connected
+    // > 0 connected
+    //     1 input is open
+    //     2 output is open
+    //     3 both input and output are open
+    int rfcomm_connected; 
+    int rfcomm_sock_flags;
+} native_data_t;
+
+static inline native_data_t * get_native_data(JNIEnv *env, jobject object) {
+    return (native_data_t *)(env->GetIntField(object, field_mNativeData));
+}
+
+static inline void init_socket_info(
+    JNIEnv *env, jobject object,
+    native_data_t *nat,
+    jstring address,
+    jint rfcomm_channel) {
+    nat->address = (jstring)env->NewGlobalRef(address);
+    nat->c_address = env->GetStringUTFChars(nat->address, NULL);
+    nat->rfcomm_channel = (int)rfcomm_channel;
+}
+
+static inline void cleanup_socket_info(JNIEnv *env, native_data_t *nat) {
+    if (nat->c_address != NULL) {
+        env->ReleaseStringUTFChars(nat->address, nat->c_address);
+        env->DeleteGlobalRef(nat->address);
+        nat->c_address = NULL;
+    }
+}
+#endif
+
+static void classInitNative(JNIEnv* env, jclass clazz) {
+    LOGV(__FUNCTION__);
+#ifdef HAVE_BLUETOOTH
+    field_mNativeData = get_field(env, clazz, "mNativeData", "I");
+    field_mTimeoutRemainingMs = get_field(env, clazz, "mTimeoutRemainingMs", "I");
+    field_mAcceptTimeoutRemainingMs = get_field(env, clazz, "mAcceptTimeoutRemainingMs", "I");
+    field_mAddress = get_field(env, clazz, "mAddress", "Ljava/lang/String;");
+    field_mPort = get_field(env, clazz, "mPort", "I");
+#endif
+}
+
+static void initializeNativeDataNative(JNIEnv* env, jobject object) {
+    LOGV(__FUNCTION__);
+#ifdef HAVE_BLUETOOTH
+
+    native_data_t *nat = (native_data_t *)calloc(1, sizeof(native_data_t));
+    if (nat == NULL) {
+        LOGE("%s: out of memory!", __FUNCTION__);
+        return;
+    }
+
+    env->SetIntField(object, field_mNativeData, (jint)nat);
+    nat->rfcomm_sock = -1;
+    nat->rfcomm_connected = 0;
+#endif
+}
+
+static void cleanupNativeDataNative(JNIEnv* env, jobject object) {
+    LOGV(__FUNCTION__);
+#ifdef HAVE_BLUETOOTH
+    native_data_t *nat = get_native_data(env, object);
+    if (nat) {
+        free(nat);
+    }
+#endif
+}
+
+static jobject createNative(JNIEnv *env, jobject obj) {
+    LOGV(__FUNCTION__);
+#ifdef HAVE_BLUETOOTH
+    int lm;
+    native_data_t *nat = get_native_data(env, obj);
+    nat->rfcomm_sock = socket(PF_BLUETOOTH, SOCK_STREAM, BTPROTO_RFCOMM);
+
+    if (nat->rfcomm_sock < 0) {
+        LOGE("%s: Could not create RFCOMM socket: %s\n", __FUNCTION__,
+             strerror(errno));
+        return NULL;
+    }
+        
+    lm = RFCOMM_LM_AUTH | RFCOMM_LM_ENCRYPT;
+
+    if (lm && setsockopt(nat->rfcomm_sock, SOL_RFCOMM, RFCOMM_LM, &lm,
+                sizeof(lm)) < 0) {
+        LOGE("%s: Can't set RFCOMM link mode", __FUNCTION__);
+        close(nat->rfcomm_sock);
+        return NULL;
+    }
+
+    return jniCreateFileDescriptor(env, nat->rfcomm_sock);
+#else
+    return NULL;
+#endif
+}
+
+static void destroyNative(JNIEnv *env, jobject obj) {
+    LOGV(__FUNCTION__);
+#ifdef HAVE_BLUETOOTH
+    native_data_t *nat = get_native_data(env, obj);
+    cleanup_socket_info(env, nat);
+    if (nat->rfcomm_sock >= 0) {
+        close(nat->rfcomm_sock);
+        nat->rfcomm_sock = -1;
+    }
+#endif
+}
+
+
+static jboolean connectNative(JNIEnv *env, jobject obj,
+                              jstring address, jint port) {
+    LOGV(__FUNCTION__);
+#ifdef HAVE_BLUETOOTH
+    native_data_t *nat = get_native_data(env, obj);
+
+    if (nat->rfcomm_sock >= 0) {
+        if (nat->rfcomm_connected) {
+            LOGI("RFCOMM socket: %s.",
+                 (nat->rfcomm_connected > 0) ? "already connected" : "connection is in progress");
+            return JNI_TRUE;
+        }
+
+        init_socket_info(env, obj, nat, address, port);
+
+        struct sockaddr_rc addr;
+        memset(&addr, 0, sizeof(struct sockaddr_rc));
+        get_bdaddr(nat->c_address, &addr.rc_bdaddr);
+        addr.rc_channel = nat->rfcomm_channel;
+        addr.rc_family = AF_BLUETOOTH;
+        nat->rfcomm_connected = 0;
+
+        while (nat->rfcomm_connected == 0) {
+            if (connect(nat->rfcomm_sock, (struct sockaddr *)&addr,
+                    sizeof(addr)) < 0) {
+                if (errno == EINTR) continue;
+                LOGE("connect error: %s (%d)\n", strerror(errno), errno);
+                break;
+            } else {
+                nat->rfcomm_connected = 3; // input and output
+            }
+        }
+    } else {
+        LOGE("%s: socket(RFCOMM) error: socket not created", __FUNCTION__);
+    }
+
+    if (nat->rfcomm_connected > 0) {
+        env->SetIntField(obj, field_mPort, port);
+        return JNI_TRUE;
+    }
+#endif
+    return JNI_FALSE;
+}
+
+static jboolean connectAsyncNative(JNIEnv *env, jobject obj,
+                                   jstring address, jint port) {
+    LOGV(__FUNCTION__);
+#ifdef HAVE_BLUETOOTH
+    native_data_t *nat = get_native_data(env, obj);
+
+    if (nat->rfcomm_sock < 0) {
+        LOGE("%s: socket(RFCOMM) error: socket not created", __FUNCTION__);
+        return JNI_FALSE;
+    }
+
+    if (nat->rfcomm_connected) {
+        LOGI("RFCOMM socket: %s.",
+             (nat->rfcomm_connected > 0) ?
+             "already connected" : "connection is in progress");
+        return JNI_TRUE;
+    }
+
+    init_socket_info(env, obj, nat, address, port);
+
+    struct sockaddr_rc addr;
+    memset(&addr, 0, sizeof(struct sockaddr_rc));
+    get_bdaddr(nat->c_address, &addr.rc_bdaddr);
+    addr.rc_channel = nat->rfcomm_channel;
+    addr.rc_family = AF_BLUETOOTH;
+
+    nat->rfcomm_sock_flags = fcntl(nat->rfcomm_sock, F_GETFL, 0);
+    if (fcntl(nat->rfcomm_sock,
+              F_SETFL, nat->rfcomm_sock_flags | O_NONBLOCK) >= 0) {
+        int rc;
+        nat->rfcomm_connected = 0;
+        errno = 0;
+        rc = connect(nat->rfcomm_sock,
+                     (struct sockaddr *)&addr,
+                     sizeof(addr));
+
+        if (rc >= 0) {
+            nat->rfcomm_connected = 3;
+            LOGI("RFCOMM async connect immediately successful");
+            env->SetIntField(obj, field_mPort, port);
+            return JNI_TRUE;
+        }
+        else if (rc < 0) {
+            if (errno == EINPROGRESS || errno == EAGAIN)
+                {
+                    LOGI("RFCOMM async connect is in progress (%s)",
+                         strerror(errno));
+                    nat->rfcomm_connected = -1;
+                    env->SetIntField(obj, field_mPort, port);
+                    return JNI_TRUE;
+                }
+            else
+                {
+                    LOGE("RFCOMM async connect error (%d): %s (%d)",
+                         nat->rfcomm_sock, strerror(errno), errno);
+                    return JNI_FALSE;
+                }
+        }
+    } // fcntl(nat->rfcomm_sock ...)
+#endif
+    return JNI_FALSE;
+}
+
+static jboolean interruptAsyncConnectNative(JNIEnv *env, jobject obj) {
+    //WRITEME
+    return JNI_TRUE;
+}
+
+static jint waitForAsyncConnectNative(JNIEnv *env, jobject obj,
+                                      jint timeout_ms) {
+    LOGV(__FUNCTION__);
+#ifdef HAVE_BLUETOOTH
+    struct sockaddr_rc addr;
+    native_data_t *nat = get_native_data(env, obj);
+
+    env->SetIntField(obj, field_mTimeoutRemainingMs, timeout_ms);
+
+    if (nat->rfcomm_sock < 0) {
+        LOGE("%s: socket(RFCOMM) error: socket not created", __FUNCTION__);
+        return -1;
+    }
+
+    if (nat->rfcomm_connected > 0) {
+        LOGI("%s: RFCOMM is already connected!", __FUNCTION__);
+        return 1;
+    }
+
+    /* Do an asynchronous select() */
+    int n;
+    fd_set rset, wset;
+    struct timeval to;
+
+    FD_ZERO(&rset);
+    FD_ZERO(&wset);
+    FD_SET(nat->rfcomm_sock, &rset);
+    FD_SET(nat->rfcomm_sock, &wset);
+    if (timeout_ms >= 0) {
+        to.tv_sec = timeout_ms / 1000;
+        to.tv_usec = 1000 * (timeout_ms % 1000);
+    }
+    n = select(nat->rfcomm_sock + 1,
+               &rset,
+               &wset,
+               NULL,
+               (timeout_ms < 0 ? NULL : &to));
+
+    if (timeout_ms > 0) {
+        jint remaining = to.tv_sec*1000 + to.tv_usec/1000;
+        LOGI("Remaining time %ldms", (long)remaining);
+        env->SetIntField(obj, field_mTimeoutRemainingMs,
+                         remaining);
+    }
+
+    if (n <= 0) {
+        if (n < 0)  {
+            LOGE("select() on RFCOMM socket: %s (%d)",
+                 strerror(errno),
+                 errno);
+            return -1;
+        }
+        return 0;
+    }
+    /* n must be equal to 1 and either rset or wset must have the
+       file descriptor set. */
+    LOGI("select() returned %d.", n);
+    if (FD_ISSET(nat->rfcomm_sock, &rset) ||
+        FD_ISSET(nat->rfcomm_sock, &wset)) {
+        /* A trial async read() will tell us if everything is OK. */
+        char ch;
+        errno = 0;
+        int nr = read(nat->rfcomm_sock, &ch, 1);
+        /* It should be that nr != 1 because we just opened a socket
+           and we haven't sent anything over it for the other side to
+           respond... but one can't be paranoid enough.
+        */
+        if (nr >= 0 || errno != EAGAIN) {
+            LOGE("RFCOMM async connect() error: %s (%d), nr = %d\n",
+                 strerror(errno),
+                 errno,
+                 nr);
+            /* Clear the rfcomm_connected flag to cause this function
+               to re-create the socket and re-attempt the connect()
+               the next time it is called.
+            */
+            nat->rfcomm_connected = 0;
+            /* Restore the blocking properties of the socket. */
+            fcntl(nat->rfcomm_sock, F_SETFL, nat->rfcomm_sock_flags);
+            return -1;
+        }
+        /* Restore the blocking properties of the socket. */
+        fcntl(nat->rfcomm_sock, F_SETFL, nat->rfcomm_sock_flags);
+        LOGI("Successful RFCOMM socket connect.");
+        nat->rfcomm_connected = 3; // input and output
+        return 1;
+    }
+#endif
+    return -1;
+}
+
+static jboolean shutdownNative(JNIEnv *env, jobject obj,
+                jboolean shutdownInput) {
+    LOGV(__FUNCTION__);
+#ifdef HAVE_BLUETOOTH
+    /* NOTE: If you change the bcode to modify nat, make sure you 
+       add synchronize(this) to the method calling this native
+       method. 
+    */
+    native_data_t *nat = get_native_data(env, obj);
+    if (nat->rfcomm_sock < 0) {
+        LOGE("socket(RFCOMM) error: socket not created");
+        return JNI_FALSE;
+    }
+    int rc = shutdown(nat->rfcomm_sock, 
+            shutdownInput ? SHUT_RD : SHUT_WR);
+    if (!rc) {
+        nat->rfcomm_connected &= 
+            shutdownInput ? ~1 : ~2;
+        return JNI_TRUE;
+    }
+#endif
+    return JNI_FALSE;
+}
+
+static jint isConnectedNative(JNIEnv *env, jobject obj) {
+    LOGI(__FUNCTION__);
+#ifdef HAVE_BLUETOOTH
+    const native_data_t *nat = get_native_data(env, obj);
+    return nat->rfcomm_connected;
+#endif
+    return 0;
+}
+
+//@@@@@@@@@ bind to device???
+static jboolean bindNative(JNIEnv *env, jobject obj, jstring device,
+                           jint port) {
+    LOGV(__FUNCTION__);
+#ifdef HAVE_BLUETOOTH
+
+    /* NOTE: If you change the code to modify nat, make sure you
+       add synchronize(this) to the method calling this native
+       method.
+    */
+    const native_data_t *nat = get_native_data(env, obj);
+    if (nat->rfcomm_sock < 0) {
+        LOGE("socket(RFCOMM) error: socket not created");
+        return JNI_FALSE;
+    }
+
+    struct sockaddr_rc laddr;
+    int lm;
+
+    lm = 0;
+/*
+    lm |= RFCOMM_LM_MASTER;
+    lm |= RFCOMM_LM_AUTH;
+    lm |= RFCOMM_LM_ENCRYPT;
+    lm |= RFCOMM_LM_SECURE;
+*/
+
+    if (lm && setsockopt(nat->rfcomm_sock, SOL_RFCOMM, RFCOMM_LM, &lm, sizeof(lm)) < 0) {
+        LOGE("Can't set RFCOMM link mode");
+        return JNI_FALSE;
+    }
+
+    laddr.rc_family = AF_BLUETOOTH;
+    bacpy(&laddr.rc_bdaddr, BDADDR_ANY);
+    laddr.rc_channel = port;
+
+    if (bind(nat->rfcomm_sock, (struct sockaddr *)&laddr, sizeof(laddr)) < 0) {
+        LOGE("Can't bind RFCOMM socket");
+        return JNI_FALSE;
+    }
+
+    env->SetIntField(obj, field_mPort, port);
+
+    return JNI_TRUE;
+#endif
+    return JNI_FALSE;
+}
+
+static jboolean listenNative(JNIEnv *env, jobject obj, jint backlog) {
+    LOGV(__FUNCTION__);
+#ifdef HAVE_BLUETOOTH
+    /* NOTE: If you change the code to modify nat, make sure you
+       add synchronize(this) to the method calling this native
+       method.
+    */
+    const native_data_t *nat = get_native_data(env, obj);
+    if (nat->rfcomm_sock < 0) {
+        LOGE("socket(RFCOMM) error: socket not created");
+        return JNI_FALSE;
+    }
+    return listen(nat->rfcomm_sock, backlog) < 0 ? JNI_FALSE : JNI_TRUE;
+#else
+    return JNI_FALSE;
+#endif
+}
+
+static int set_nb(int sk, bool nb) {
+    int flags = fcntl(sk, F_GETFL);
+    if (flags < 0) {
+        LOGE("Can't get socket flags with fcntl(): %s (%d)",
+             strerror(errno), errno);
+        close(sk);
+        return -1;
+    }
+    flags &= ~O_NONBLOCK;
+    if (nb) flags |= O_NONBLOCK;
+    int status = fcntl(sk, F_SETFL, flags);
+    if (status < 0) {
+        LOGE("Can't set socket to nonblocking mode with fcntl(): %s (%d)",
+             strerror(errno), errno);
+        close(sk);
+        return -1;
+    }
+    return 0;
+}
+
+// Note: the code should check at a higher level to see whether
+// listen() has been called.
+#ifdef HAVE_BLUETOOTH
+static int do_accept(JNIEnv* env, jobject object, int sock,
+                     jobject newsock,
+                     jfieldID out_address,
+                     bool must_succeed) {
+
+    if (must_succeed && set_nb(sock, true) < 0)
+        return -1;
+
+    struct sockaddr_rc raddr;
+    int alen = sizeof(raddr);
+    int nsk = accept(sock, (struct sockaddr *) &raddr, &alen);
+    if (nsk < 0) {
+        LOGE("Error on accept from socket fd %d: %s (%d).",
+             sock,
+             strerror(errno),
+             errno);
+        if (must_succeed) set_nb(sock, false);
+        return -1;
+    }
+
+    char addr[BTADDR_SIZE];
+    get_bdaddr_as_string(&raddr.rc_bdaddr, addr);
+    env->SetObjectField(newsock, out_address, env->NewStringUTF(addr));
+
+    LOGI("Successful accept() on AG socket %d: new socket %d, address %s, RFCOMM channel %d",
+         sock,
+         nsk,
+         addr,
+         raddr.rc_channel);
+    if (must_succeed) set_nb(sock, false);
+    return nsk;
+}
+#endif /*HAVE_BLUETOOTH*/
+
+static jobject acceptNative(JNIEnv *env, jobject obj,
+                            jobject newsock, jint timeoutMs) {
+    LOGV(__FUNCTION__);
+#ifdef HAVE_BLUETOOTH
+    native_data_t *nat = get_native_data(env, obj);
+    if (nat->rfcomm_sock < 0) {
+        LOGE("socket(RFCOMM) error: socket not created");
+        return JNI_FALSE;
+    }
+
+    if (newsock == NULL) {
+        LOGE("%s: newsock = NULL\n", __FUNCTION__);
+        return JNI_FALSE;
+    }
+
+    int nsk = -1;
+    if (timeoutMs < 0) {
+        /* block until accept() succeeds */
+        nsk = do_accept(env, obj, nat->rfcomm_sock,
+                        newsock, field_mAddress, false);
+        if (nsk < 0) {
+            return NULL;
+        }
+    }
+    else {
+        /* wait with a timeout */
+
+        struct pollfd fds;
+        fds.fd = nat->rfcomm_sock;
+        fds.events = POLLIN | POLLPRI | POLLOUT | POLLERR;
+
+        env->SetIntField(obj, field_mAcceptTimeoutRemainingMs, 0);
+        int n = poll(&fds, 1, timeoutMs);
+        if (n <= 0) {
+            if (n < 0)  {
+                LOGE("listening poll() on RFCOMM socket: %s (%d)",
+                     strerror(errno),
+                     errno);
+                env->SetIntField(obj, field_mAcceptTimeoutRemainingMs, timeoutMs);
+            }
+            else {
+                LOGI("listening poll() on RFCOMM socket timed out");
+            }
+            return NULL;
+        }
+
+        LOGI("listening poll() on RFCOMM socket returned %d", n);
+        if (fds.fd == nat->rfcomm_sock) {
+            if (fds.revents & (POLLIN | POLLPRI | POLLOUT)) {
+                LOGI("Accepting connection.\n");
+                nsk = do_accept(env, obj, nat->rfcomm_sock,
+                                newsock, field_mAddress, true);
+                if (nsk < 0) {
+                    return NULL;
+                }
+            }
+        }
+    }
+
+    LOGI("Connection accepted, new socket fd = %d.", nsk);
+    native_data_t *newnat = get_native_data(env, newsock);
+    newnat->rfcomm_sock = nsk;
+    newnat->rfcomm_connected = 3;
+    return jniCreateFileDescriptor(env, nsk);
+#else
+    return NULL;
+#endif
+}
+
+static JNINativeMethod sMethods[] = {
+     /* name, signature, funcPtr */
+    {"classInitNative", "()V", (void*)classInitNative},
+    {"initializeNativeDataNative", "()V", (void *)initializeNativeDataNative},
+    {"cleanupNativeDataNative", "()V", (void *)cleanupNativeDataNative},
+
+    {"createNative", "()Ljava/io/FileDescriptor;", (void *)createNative},
+    {"destroyNative", "()V", (void *)destroyNative},
+    {"connectNative", "(Ljava/lang/String;I)Z", (void *)connectNative},
+    {"connectAsyncNative", "(Ljava/lang/String;I)Z", (void *)connectAsyncNative},
+    {"interruptAsyncConnectNative", "()Z", (void *)interruptAsyncConnectNative},
+    {"waitForAsyncConnectNative", "(I)I", (void *)waitForAsyncConnectNative},
+    {"shutdownNative", "(Z)Z", (void *)shutdownNative},
+    {"isConnectedNative", "()I", (void *)isConnectedNative},
+
+    {"bindNative", "(Ljava/lang/String;I)Z", (void*)bindNative},
+    {"listenNative", "(I)Z", (void*)listenNative},
+    {"acceptNative", "(Landroid/bluetooth/RfcommSocket;I)Ljava/io/FileDescriptor;", (void*)acceptNative},
+};
+
+int register_android_bluetooth_RfcommSocket(JNIEnv *env) {
+    return AndroidRuntime::registerNativeMethods(env,
+        "android/bluetooth/RfcommSocket", sMethods, NELEM(sMethods));
+}
+
+} /* namespace android */
diff --git a/core/jni/android_bluetooth_ScoSocket.cpp b/core/jni/android_bluetooth_ScoSocket.cpp
new file mode 100644
index 0000000..3afe5f5
--- /dev/null
+++ b/core/jni/android_bluetooth_ScoSocket.cpp
@@ -0,0 +1,506 @@
+/*
+** Copyright 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.
+*/
+
+#define LOG_TAG "bluetooth_ScoSocket.cpp"
+
+#include "android_bluetooth_common.h"
+#include "android_runtime/AndroidRuntime.h"
+#include "JNIHelp.h"
+#include "jni.h"
+#include "utils/Log.h"
+#include "utils/misc.h"
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <unistd.h>
+#include <pthread.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <sys/uio.h>
+#include <sys/poll.h>
+
+#ifdef HAVE_BLUETOOTH
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/sco.h>
+#endif
+
+/* Ideally, blocking I/O on a SCO socket would return when another thread
+ * calls close(). However it does not right now, in fact close() on a SCO
+ * socket has strange behavior (returns a bogus value) when other threads
+ * are performing blocking I/O on that socket. So, to workaround, we always
+ * call close() from the same thread that does blocking I/O. This requires the
+ * use of a socketpair to signal the blocking I/O to abort.
+ *
+ * Unfortunately I don't know a way to abort connect() yet, but at least this
+ * times out after the BT page timeout (10 seconds currently), so the thread
+ * will die eventually. The fact that the thread can outlive
+ * the Java object forces us to use a mutex in destoryNative().
+ *
+ * The JNI API is entirely async.
+ *
+ * Also note this class deals only with SCO connections, not with data
+ * transmission.
+ */
+namespace android {
+#ifdef HAVE_BLUETOOTH
+
+static JavaVM *jvm;
+static jfieldID field_mNativeData;
+static jmethodID method_onAccepted;
+static jmethodID method_onConnected;
+static jmethodID method_onClosed;
+
+struct thread_data_t;
+static void *work_thread(void *arg);
+static int connect_work(const char *address);
+static int accept_work(int signal_sk);
+static void wait_for_close(int sk, int signal_sk);
+static void closeNative(JNIEnv *env, jobject object);
+
+/* shared native data - protected by mutex */
+typedef struct {
+    pthread_mutex_t mutex;
+    int signal_sk;        // socket to signal blocked I/O to unblock
+    jobject object;       // JNI global ref to the Java object
+    thread_data_t *thread_data;  // pointer to thread local data
+                                 // max 1 thread per sco socket
+} native_data_t;
+
+/* thread local data */
+struct thread_data_t {
+    native_data_t *nat;
+    bool is_accept;        // accept (listening) or connect (outgoing) thread
+    int signal_sk;         // socket for thread to listen for unblock signal
+    char address[BTADDR_SIZE];  // BT addres as string
+};
+
+static inline native_data_t * get_native_data(JNIEnv *env, jobject object) {
+    return (native_data_t *)(env->GetIntField(object, field_mNativeData));
+}
+#endif
+
+static void classInitNative(JNIEnv* env, jclass clazz) {
+    LOGV(__FUNCTION__);
+#ifdef HAVE_BLUETOOTH
+    if (env->GetJavaVM(&jvm) < 0) {
+        LOGE("Could not get handle to the VM");
+    }
+    field_mNativeData = get_field(env, clazz, "mNativeData", "I");
+    method_onAccepted = env->GetMethodID(clazz, "onAccepted", "(I)V");
+    method_onConnected = env->GetMethodID(clazz, "onConnected", "(I)V");
+    method_onClosed = env->GetMethodID(clazz, "onClosed", "()V");
+#endif
+}
+
+/* Returns false if a serious error occured */
+static jboolean initNative(JNIEnv* env, jobject object) {
+    LOGV(__FUNCTION__);
+#ifdef HAVE_BLUETOOTH
+
+    native_data_t *nat = (native_data_t *) calloc(1, sizeof(native_data_t));
+    if (nat == NULL) {
+        LOGE("%s: out of memory!", __FUNCTION__);
+        return JNI_FALSE;
+    }
+
+    pthread_mutex_init(&nat->mutex, NULL);
+    env->SetIntField(object, field_mNativeData, (jint)nat);
+    nat->signal_sk = -1;
+    nat->object = NULL;
+    nat->thread_data = NULL;
+
+#endif
+    return JNI_TRUE;
+}
+
+static void destroyNative(JNIEnv* env, jobject object) {
+    LOGV(__FUNCTION__);
+#ifdef HAVE_BLUETOOTH
+    native_data_t *nat = get_native_data(env, object);
+    
+    closeNative(env, object);
+    
+    pthread_mutex_lock(&nat->mutex);
+    if (nat->thread_data != NULL) {
+        nat->thread_data->nat = NULL;
+    }
+    pthread_mutex_unlock(&nat->mutex);
+    pthread_mutex_destroy(&nat->mutex);
+
+    free(nat);
+#endif
+}
+
+static jboolean acceptNative(JNIEnv *env, jobject object) {
+    LOGV(__FUNCTION__);
+#ifdef HAVE_BLUETOOTH
+    native_data_t *nat = get_native_data(env, object);
+    int signal_sks[2];
+    pthread_t thread;
+    struct thread_data_t *data = NULL;
+
+    pthread_mutex_lock(&nat->mutex);
+    if (nat->signal_sk != -1) {
+        pthread_mutex_unlock(&nat->mutex);
+        return JNI_FALSE;
+    }
+
+    // setup socketpair to pass messages between threads
+    if (socketpair(AF_UNIX, SOCK_STREAM, 0, signal_sks) < 0) {
+        LOGE("%s: socketpair() failed: %s", __FUNCTION__, strerror(errno));
+        pthread_mutex_unlock(&nat->mutex);
+        return JNI_FALSE;
+    }
+    nat->signal_sk = signal_sks[0];
+    nat->object = env->NewGlobalRef(object);
+
+    data = (thread_data_t *)calloc(1, sizeof(thread_data_t));
+    if (data == NULL) {
+        LOGE("%s: out of memory", __FUNCTION__);
+        pthread_mutex_unlock(&nat->mutex);
+        return JNI_FALSE;
+    }
+    nat->thread_data = data;
+    pthread_mutex_unlock(&nat->mutex);
+
+    data->signal_sk = signal_sks[1];
+    data->nat = nat;
+    data->is_accept = true;
+
+    if (pthread_create(&thread, NULL, &work_thread, (void *)data) < 0) {
+        LOGE("%s: pthread_create() failed: %s", __FUNCTION__, strerror(errno));
+        return JNI_FALSE;
+    }
+    return JNI_TRUE;
+
+#endif
+    return JNI_FALSE;
+}
+
+static jboolean connectNative(JNIEnv *env, jobject object, jstring address) {
+    LOGV(__FUNCTION__);
+#ifdef HAVE_BLUETOOTH
+    native_data_t *nat = get_native_data(env, object);
+    int signal_sks[2];
+    pthread_t thread;
+    struct thread_data_t *data;
+    const char *c_address;
+
+    pthread_mutex_lock(&nat->mutex);
+    if (nat->signal_sk != -1) {
+        pthread_mutex_unlock(&nat->mutex);
+        return JNI_FALSE;
+    }
+
+    // setup socketpair to pass messages between threads
+    if (socketpair(AF_UNIX, SOCK_STREAM, 0, signal_sks) < 0) {
+        LOGE("%s: socketpair() failed: %s\n", __FUNCTION__, strerror(errno));
+        pthread_mutex_unlock(&nat->mutex);
+        return JNI_FALSE;
+    }
+    nat->signal_sk = signal_sks[0];
+    nat->object = env->NewGlobalRef(object);
+
+    data = (thread_data_t *)calloc(1, sizeof(thread_data_t));
+    if (data == NULL) {
+        LOGE("%s: out of memory", __FUNCTION__);
+        pthread_mutex_unlock(&nat->mutex);
+        return JNI_FALSE;
+    }
+    pthread_mutex_unlock(&nat->mutex);
+
+    data->signal_sk = signal_sks[1];
+    data->nat = nat;
+    c_address = env->GetStringUTFChars(address, NULL);
+    strlcpy(data->address, c_address, BTADDR_SIZE);
+    env->ReleaseStringUTFChars(address, c_address);
+    data->is_accept = false;
+
+    if (pthread_create(&thread, NULL, &work_thread, (void *)data) < 0) {
+        LOGE("%s: pthread_create() failed: %s", __FUNCTION__, strerror(errno));
+        return JNI_FALSE;
+    }
+    return JNI_TRUE;
+
+#endif
+    return JNI_FALSE;
+}
+
+static void closeNative(JNIEnv *env, jobject object) {
+    LOGV(__FUNCTION__);
+#ifdef HAVE_BLUETOOTH
+    native_data_t *nat = get_native_data(env, object);
+    int signal_sk;
+
+    pthread_mutex_lock(&nat->mutex);
+    signal_sk = nat->signal_sk;
+    nat->signal_sk = -1;
+    env->DeleteGlobalRef(nat->object);
+    nat->object = NULL;
+    pthread_mutex_unlock(&nat->mutex);
+
+    if (signal_sk >= 0) {
+        LOGV("%s: signal_sk = %d", __FUNCTION__, signal_sk);
+        unsigned char dummy;
+        write(signal_sk, &dummy, sizeof(dummy));
+        close(signal_sk);
+    }
+#endif
+}
+
+#ifdef HAVE_BLUETOOTH
+/* thread entry point */
+static void *work_thread(void *arg) {
+    JNIEnv* env;
+    thread_data_t *data = (thread_data_t *)arg;
+    int sk;
+
+    LOGV(__FUNCTION__);
+    if (jvm->AttachCurrentThread(&env, NULL) != JNI_OK) {
+        LOGE("%s: AttachCurrentThread() failed", __FUNCTION__);
+        return NULL;
+    }
+
+    /* connect the SCO socket */
+    if (data->is_accept) {
+        LOGV("SCO OBJECT %p ACCEPT #####", data->nat->object);
+        sk = accept_work(data->signal_sk);
+        LOGV("SCO OBJECT %p END ACCEPT *****", data->nat->object);
+    } else {
+        sk = connect_work(data->address);
+    }
+
+    /* callback with connection result */
+    if (data->nat == NULL) {
+        LOGV("%s: object destroyed!", __FUNCTION__);
+        goto done;
+    }
+    pthread_mutex_lock(&data->nat->mutex);
+    if (data->nat->object == NULL) {
+        pthread_mutex_unlock(&data->nat->mutex);
+        LOGV("%s: callback cancelled", __FUNCTION__);
+        goto done;
+    }
+    if (data->is_accept) {
+        env->CallVoidMethod(data->nat->object, method_onAccepted, sk);
+    } else {
+        env->CallVoidMethod(data->nat->object, method_onConnected, sk);
+    }
+    pthread_mutex_unlock(&data->nat->mutex);
+
+    if (sk < 0) {
+        goto done;
+    }
+
+    LOGV("SCO OBJECT %p %d CONNECTED +++ (%s)", data->nat->object, sk,
+         data->is_accept ? "in" : "out");
+
+    /* wait for the socket to close */
+    LOGV("wait_for_close()...");
+    wait_for_close(sk, data->signal_sk);
+    LOGV("wait_for_close() returned");
+
+    /* callback with close result */
+    if (data->nat == NULL) {
+        LOGV("%s: object destroyed!", __FUNCTION__);
+        goto done;
+    }
+    pthread_mutex_lock(&data->nat->mutex);
+    if (data->nat->object == NULL) {
+        LOGV("%s: callback cancelled", __FUNCTION__);
+    } else {
+        env->CallVoidMethod(data->nat->object, method_onClosed);
+    }
+    pthread_mutex_unlock(&data->nat->mutex);
+
+done:
+    if (sk >= 0) {
+        close(sk);
+        LOGV("SCO OBJECT %p %d CLOSED --- (%s)", data->nat->object, sk, data->is_accept ? "in" : "out");
+    }
+    if (data->signal_sk >= 0) {
+        close(data->signal_sk);
+    }
+    LOGV("SCO socket closed");
+
+    if (data->nat != NULL) {
+        pthread_mutex_lock(&data->nat->mutex);
+        env->DeleteGlobalRef(data->nat->object);
+        data->nat->object = NULL;
+        data->nat->thread_data = NULL;
+        pthread_mutex_unlock(&data->nat->mutex);
+    }
+
+    free(data);
+    if (jvm->DetachCurrentThread() != JNI_OK) {
+        LOGE("%s: DetachCurrentThread() failed", __FUNCTION__);
+    }
+
+    LOGV("work_thread() done");
+    return NULL;
+}
+
+static int accept_work(int signal_sk) {
+    LOGV(__FUNCTION__);
+    int sk;
+    int nsk;
+    int addr_sz;
+    int max_fd;
+    fd_set fds;
+    struct sockaddr_sco addr;
+
+    sk = socket(PF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_SCO);
+    if (sk < 0) {
+        LOGE("%s socket() failed: %s", __FUNCTION__, strerror(errno));
+        return -1;
+    }
+
+    memset(&addr, 0, sizeof(addr));
+    addr.sco_family = AF_BLUETOOTH;
+    memcpy(&addr.sco_bdaddr, BDADDR_ANY, sizeof(bdaddr_t));
+    if (bind(sk, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
+        LOGE("%s bind() failed: %s", __FUNCTION__, strerror(errno));
+        goto error;
+    }
+
+    if (listen(sk, 1)) {
+        LOGE("%s: listen() failed: %s", __FUNCTION__, strerror(errno));
+        goto error;
+    }
+
+    memset(&addr, 0, sizeof(addr));
+    addr_sz = sizeof(addr);
+
+    FD_ZERO(&fds);
+    FD_SET(sk, &fds);
+    FD_SET(signal_sk, &fds);
+
+    max_fd = (sk > signal_sk) ? sk : signal_sk;
+    LOGI("Listening SCO socket...");
+    while (select(max_fd + 1, &fds, NULL, NULL, NULL) < 0) {
+        if (errno != EINTR) {
+            LOGE("%s: select() failed: %s", __FUNCTION__, strerror(errno));
+            goto error;
+        }
+        LOGV("%s: select() EINTR, retrying", __FUNCTION__);
+    }
+    LOGV("select() returned");
+    if (FD_ISSET(signal_sk, &fds)) {
+        // signal to cancel listening
+        LOGV("cancelled listening socket, closing");
+        goto error;
+    }
+    if (!FD_ISSET(sk, &fds)) {
+        LOGE("error: select() returned >= 0 with no fds set");
+        goto error;
+    }
+
+    nsk = accept(sk, (struct sockaddr *)&addr, &addr_sz);
+    if (nsk < 0) {
+        LOGE("%s: accept() failed: %s", __FUNCTION__, strerror(errno));
+        goto error;
+    }
+    LOGI("Connected SCO socket (incoming)");
+    close(sk);  // The listening socket
+
+    return nsk;
+
+error:
+    close(sk);
+
+    return -1;
+}
+
+static int connect_work(const char *address) {
+    LOGV(__FUNCTION__);
+    struct sockaddr_sco addr;
+    int sk = -1;
+
+    sk = socket(PF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_SCO);
+    if (sk < 0) {
+        LOGE("%s: socket() failed: %s", __FUNCTION__, strerror(errno));
+        return -1;
+    }
+
+    /* Bind to local address */
+    memset(&addr, 0, sizeof(addr));
+    addr.sco_family = AF_BLUETOOTH;
+    memcpy(&addr.sco_bdaddr, BDADDR_ANY, sizeof(bdaddr_t));
+    if (bind(sk, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
+        LOGE("%s: bind() failed: %s", __FUNCTION__, strerror(errno));
+        goto error;
+    }
+
+    memset(&addr, 0, sizeof(addr));
+    addr.sco_family = AF_BLUETOOTH;
+    get_bdaddr(address, &addr.sco_bdaddr);
+    LOGI("Connecting to socket");
+    while (connect(sk, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
+        if (errno != EINTR) {
+            LOGE("%s: connect() failed: %s", __FUNCTION__, strerror(errno));
+            goto error;
+        }
+        LOGV("%s: connect() EINTR, retrying", __FUNCTION__);
+    }
+    LOGI("SCO socket connected (outgoing)");
+
+    return sk;
+
+error:
+    if (sk >= 0) close(sk);
+    return -1;
+}
+
+static void wait_for_close(int sk, int signal_sk) {
+    LOGV(__FUNCTION__);
+    pollfd p[2];
+
+    memset(p, 0, 2 * sizeof(pollfd));
+    p[0].fd = sk;
+    p[1].fd = signal_sk;
+    p[1].events = POLLIN | POLLPRI;
+
+    LOGV("poll...");
+
+    while (poll(p, 2, -1) < 0) {  // blocks
+        if (errno != EINTR) {
+            LOGE("%s: poll() failed: %s", __FUNCTION__, strerror(errno));
+            break;
+        }
+        LOGV("%s: poll() EINTR, retrying", __FUNCTION__);
+    }
+
+    LOGV("poll() returned");
+}
+#endif
+
+static JNINativeMethod sMethods[] = {
+    {"classInitNative", "()V", (void*)classInitNative},
+    {"initNative", "()V", (void *)initNative},
+    {"destroyNative", "()V", (void *)destroyNative},
+    {"connectNative", "(Ljava/lang/String;)Z", (void *)connectNative},
+    {"acceptNative", "()Z", (void *)acceptNative},
+    {"closeNative", "()V", (void *)closeNative},
+};
+
+int register_android_bluetooth_ScoSocket(JNIEnv *env) {
+    return AndroidRuntime::registerNativeMethods(env,
+            "android/bluetooth/ScoSocket", sMethods, NELEM(sMethods));
+}
+
+} /* namespace android */
diff --git a/core/jni/android_bluetooth_common.cpp b/core/jni/android_bluetooth_common.cpp
new file mode 100644
index 0000000..c81af1ce
--- /dev/null
+++ b/core/jni/android_bluetooth_common.cpp
@@ -0,0 +1,423 @@
+/*
+** Copyright 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.
+*/
+
+#define LOG_TAG "bluetooth_common.cpp"
+
+#include "android_bluetooth_common.h"
+#include "JNIHelp.h"
+#include "jni.h"
+#include "utils/Log.h"
+#include "utils/misc.h"
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <unistd.h>
+#include <cutils/properties.h>
+
+#ifdef HAVE_BLUETOOTH
+#include <dbus/dbus.h>
+#endif
+
+namespace android {
+
+#ifdef HAVE_BLUETOOTH
+jfieldID get_field(JNIEnv *env, jclass clazz, const char *member,
+                   const char *mtype) {
+    jfieldID field = env->GetFieldID(clazz, member, mtype);
+    if (field == NULL) {
+        LOGE("Can't find member %s", member);
+    }
+    return field;
+}
+
+typedef struct {
+    void (*user_cb)(DBusMessage *, void *);
+    void *user;
+    JNIEnv *env;
+} dbus_async_call_t;
+
+void dbus_func_args_async_callback(DBusPendingCall *call, void *data) {
+
+    dbus_async_call_t *req = (dbus_async_call_t *)data;
+    DBusMessage *msg;
+
+    /* This is guaranteed to be non-NULL, because this function is called only
+       when once the remote method invokation returns. */
+    msg = dbus_pending_call_steal_reply(call);
+
+    if (msg) {
+        if (req->user_cb) {
+            // The user may not deref the message object.
+            req->user_cb(msg, req->user);
+        }
+        dbus_message_unref(msg);
+    }
+
+    //dbus_message_unref(req->method);
+    dbus_pending_call_cancel(call);
+    dbus_pending_call_unref(call);
+    free(req);
+}
+
+dbus_bool_t dbus_func_args_async_valist(JNIEnv *env,
+                                        DBusConnection *conn,
+                                        int timeout_ms,
+                                        void (*user_cb)(DBusMessage *, void *),
+                                        void *user,
+                                        const char *path,
+                                        const char *ifc,
+                                        const char *func,
+                                        int first_arg_type,
+                                        va_list args) {
+    DBusMessage *msg = NULL;
+    const char *name;
+    dbus_async_call_t *pending;
+    dbus_bool_t reply = FALSE;
+
+    /* Compose the command */
+    msg = dbus_message_new_method_call(BLUEZ_DBUS_BASE_IFC, path, ifc, func);
+
+    if (msg == NULL) {
+        LOGE("Could not allocate D-Bus message object!");
+        goto done;
+    }
+
+    /* append arguments */
+    if (!dbus_message_append_args_valist(msg, first_arg_type, args)) {
+        LOGE("Could not append argument to method call!");
+        goto done;
+    }
+
+    /* Make the call. */
+    pending = (dbus_async_call_t *)malloc(sizeof(dbus_async_call_t));
+    if (pending) {
+        DBusPendingCall *call;
+
+        pending->env = env;
+        pending->user_cb = user_cb;
+        pending->user = user;
+        //pending->method = msg;
+
+        reply = dbus_connection_send_with_reply(conn, msg,
+                                                &call,
+                                                timeout_ms);
+        if (reply == TRUE) {
+            dbus_pending_call_set_notify(call,
+                                         dbus_func_args_async_callback,
+                                         pending,
+                                         NULL);
+        }
+    }
+
+done:
+    if (msg) dbus_message_unref(msg);
+    return reply;
+}
+
+dbus_bool_t dbus_func_args_async(JNIEnv *env,
+                                 DBusConnection *conn,
+                                 int timeout_ms,
+                                 void (*reply)(DBusMessage *, void *),
+                                 void *user,
+                                 const char *path,
+                                 const char *ifc,
+                                 const char *func,
+                                 int first_arg_type,
+                                 ...) {
+    dbus_bool_t ret;
+    va_list lst;
+    va_start(lst, first_arg_type);
+    ret = dbus_func_args_async_valist(env, conn,
+                                      timeout_ms,
+                                      reply, user,
+                                      path, ifc, func,
+                                      first_arg_type, lst);
+    va_end(lst);
+    return ret;
+}
+
+// If err is NULL, then any errors will be LOGE'd, and free'd and the reply
+// will be NULL.
+// If err is not NULL, then it is assumed that dbus_error_init was already
+// called, and error's will be returned to the caller without logging. The
+// return value is NULL iff an error was set. The client must free the error if
+// set.
+DBusMessage * dbus_func_args_timeout_valist(JNIEnv *env,
+                                            DBusConnection *conn,
+                                            int timeout_ms,
+                                            DBusError *err,
+                                            const char *path,
+                                            const char *ifc,
+                                            const char *func,
+                                            int first_arg_type,
+                                            va_list args) {
+
+    DBusMessage *msg = NULL, *reply = NULL;
+    const char *name;
+    bool return_error = (err != NULL);
+
+    if (!return_error) {
+        err = (DBusError*)malloc(sizeof(DBusError));
+        dbus_error_init(err);
+    }
+
+    /* Compose the command */
+    msg = dbus_message_new_method_call(BLUEZ_DBUS_BASE_IFC, path, ifc, func);
+
+    if (msg == NULL) {
+        LOGE("Could not allocate D-Bus message object!");
+        goto done;
+    }
+
+    /* append arguments */
+    if (!dbus_message_append_args_valist(msg, first_arg_type, args)) {
+        LOGE("Could not append argument to method call!");
+        goto done;
+    }
+
+    /* Make the call. */
+    reply = dbus_connection_send_with_reply_and_block(conn, msg, timeout_ms, err);
+    if (!return_error && dbus_error_is_set(err)) {
+        LOG_AND_FREE_DBUS_ERROR_WITH_MSG(err, msg);
+    }
+
+done:
+    if (!return_error) {
+        free(err);
+    }
+    if (msg) dbus_message_unref(msg);
+    return reply;
+}
+
+DBusMessage * dbus_func_args_timeout(JNIEnv *env,
+                                     DBusConnection *conn,
+                                     int timeout_ms,
+                                     const char *path,
+                                     const char *ifc,
+                                     const char *func,
+                                     int first_arg_type,
+                                     ...) {
+    DBusMessage *ret;
+    va_list lst;
+    va_start(lst, first_arg_type);
+    ret = dbus_func_args_timeout_valist(env, conn, timeout_ms, NULL,
+                                        path, ifc, func,
+                                        first_arg_type, lst);
+    va_end(lst);
+    return ret;
+}
+
+DBusMessage * dbus_func_args(JNIEnv *env,
+                             DBusConnection *conn,
+                             const char *path,
+                             const char *ifc,
+                             const char *func,
+                             int first_arg_type,
+                             ...) {
+    DBusMessage *ret;
+    va_list lst;
+    va_start(lst, first_arg_type);
+    ret = dbus_func_args_timeout_valist(env, conn, -1, NULL,
+                                        path, ifc, func,
+                                        first_arg_type, lst);
+    va_end(lst);
+    return ret;
+}
+
+DBusMessage * dbus_func_args_error(JNIEnv *env,
+                                   DBusConnection *conn,
+                                   DBusError *err,
+                                   const char *path,
+                                   const char *ifc,
+                                   const char *func,
+                                   int first_arg_type,
+                                   ...) {
+    DBusMessage *ret;
+    va_list lst;
+    va_start(lst, first_arg_type);
+    ret = dbus_func_args_timeout_valist(env, conn, -1, err,
+                                        path, ifc, func,
+                                        first_arg_type, lst);
+    va_end(lst);
+    return ret;
+}
+
+jint dbus_returns_int32(JNIEnv *env, DBusMessage *reply) {
+
+    DBusError err;
+    jint ret = -1;
+
+    dbus_error_init(&err);
+    if (!dbus_message_get_args(reply, &err,
+                               DBUS_TYPE_INT32, &ret,
+                               DBUS_TYPE_INVALID)) {
+        LOG_AND_FREE_DBUS_ERROR_WITH_MSG(&err, reply);
+    }
+    dbus_message_unref(reply);
+    return ret;
+}
+
+jint dbus_returns_uint32(JNIEnv *env, DBusMessage *reply) {
+
+    DBusError err;
+    jint ret = -1;
+
+    dbus_error_init(&err);
+    if (!dbus_message_get_args(reply, &err,
+                               DBUS_TYPE_UINT32, &ret,
+                               DBUS_TYPE_INVALID)) {
+        LOG_AND_FREE_DBUS_ERROR_WITH_MSG(&err, reply);
+    }
+    dbus_message_unref(reply);
+    return ret;
+}
+
+jstring dbus_returns_string(JNIEnv *env, DBusMessage *reply) {
+
+    DBusError err;
+    jstring ret = NULL;
+    const char *name;
+
+    dbus_error_init(&err);
+    if (dbus_message_get_args(reply, &err,
+                               DBUS_TYPE_STRING, &name,
+                               DBUS_TYPE_INVALID)) {
+        ret = env->NewStringUTF(name);
+    } else {
+        LOG_AND_FREE_DBUS_ERROR_WITH_MSG(&err, reply);
+    }
+    dbus_message_unref(reply);
+
+    return ret;
+}
+
+jboolean dbus_returns_boolean(JNIEnv *env, DBusMessage *reply) {
+    DBusError err;
+    jboolean ret = JNI_FALSE;
+    dbus_bool_t val = FALSE;
+
+    dbus_error_init(&err);
+
+    /* Check the return value. */
+    if (dbus_message_get_args(reply, &err,
+                               DBUS_TYPE_BOOLEAN, &val,
+                               DBUS_TYPE_INVALID)) {
+        ret = val == TRUE ? JNI_TRUE : JNI_FALSE;
+    } else {
+        LOG_AND_FREE_DBUS_ERROR_WITH_MSG(&err, reply);
+    }
+
+    dbus_message_unref(reply);
+    return ret;
+}
+
+jobjectArray dbus_returns_array_of_strings(JNIEnv *env, DBusMessage *reply) {
+
+    DBusError err;
+    char **list;
+    int i, len;
+    jobjectArray strArray = NULL;
+
+    dbus_error_init(&err);
+    if (dbus_message_get_args (reply,
+                               &err,
+                               DBUS_TYPE_ARRAY, DBUS_TYPE_STRING,
+                               &list, &len,
+                               DBUS_TYPE_INVALID)) {
+        jclass stringClass;
+        jstring classNameStr;
+
+        //LOGV("%s: there are %d elements in string array!", __FUNCTION__, len);
+
+        stringClass = env->FindClass("java/lang/String");
+        strArray = env->NewObjectArray(len, stringClass, NULL);
+
+        for (i = 0; i < len; i++) {
+            //LOGV("%s:    array[%d] = [%s]", __FUNCTION__, i, list[i]);
+            env->SetObjectArrayElement(strArray, i,
+                                       env->NewStringUTF(list[i]));
+        }
+    } else {
+        LOG_AND_FREE_DBUS_ERROR_WITH_MSG(&err, reply);
+    }
+
+    dbus_message_unref(reply);
+    return strArray;
+}
+
+jbyteArray dbus_returns_array_of_bytes(JNIEnv *env, DBusMessage *reply) {
+
+    DBusError err;
+    int i, len;
+    jbyte *list;
+    jbyteArray byteArray = NULL;
+
+    dbus_error_init(&err);
+    if (dbus_message_get_args(reply, &err,
+                              DBUS_TYPE_ARRAY, DBUS_TYPE_BYTE, &list, &len,
+                              DBUS_TYPE_INVALID)) {
+        //LOGV("%s: there are %d elements in byte array!", __FUNCTION__, len);
+        byteArray = env->NewByteArray(len);
+        if (byteArray)
+            env->SetByteArrayRegion(byteArray, 0, len, list);
+
+    } else {
+        LOG_AND_FREE_DBUS_ERROR_WITH_MSG(&err, reply);
+    }
+
+    dbus_message_unref(reply);
+    return byteArray;
+}
+
+void get_bdaddr(const char *str, bdaddr_t *ba) {
+    char *d = ((char *)ba) + 5, *endp;
+    int i;
+    for(i = 0; i < 6; i++) {
+        *d-- = strtol(str, &endp, 16);
+        if (*endp != ':' && i != 5) {
+            memset(ba, 0, sizeof(bdaddr_t));
+            return;
+        }
+        str = endp + 1;
+    }
+}
+
+void get_bdaddr_as_string(const bdaddr_t *ba, char *str) {
+    const uint8_t *b = (const uint8_t *)ba;
+    sprintf(str, "%2.2X:%2.2X:%2.2X:%2.2X:%2.2X:%2.2X",
+            b[5], b[4], b[3], b[2], b[1], b[0]);
+}
+
+bool debug_no_encrypt() {
+    return false;
+#if 0
+    char value[PROPERTY_VALUE_MAX] = "";
+
+    property_get("debug.bt.no_encrypt", value, "");
+    if (!strncmp("true", value, PROPERTY_VALUE_MAX) ||
+        !strncmp("1", value, PROPERTY_VALUE_MAX)) {
+        LOGD("mandatory bluetooth encryption disabled");
+        return true;
+    } else {
+        return false;
+    }
+#endif
+}
+#endif
+
+} /* namespace android */
diff --git a/core/jni/android_bluetooth_common.h b/core/jni/android_bluetooth_common.h
new file mode 100644
index 0000000..c30ba22
--- /dev/null
+++ b/core/jni/android_bluetooth_common.h
@@ -0,0 +1,146 @@
+/*
+** Copyright 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.
+*/
+
+#ifndef ANDROID_BLUETOOTH_COMMON_H
+#define ANDROID_BLUETOOTH_COMMON_H
+
+// Set to 0 to enable verbose bluetooth logging
+#define LOG_NDEBUG 1
+
+#include "jni.h"
+#include "utils/Log.h"
+
+#include <errno.h>
+#include <stdint.h>
+
+#ifdef HAVE_BLUETOOTH
+#include <dbus/dbus.h>
+#include <bluetooth/bluetooth.h>
+#endif
+
+namespace android {
+
+#ifdef HAVE_BLUETOOTH
+#define BLUEZ_DBUS_BASE_PATH      "/org/bluez"
+#define BLUEZ_DBUS_BASE_IFC       "org.bluez"
+
+// It would be nicer to retrieve this from bluez using GetDefaultAdapter,
+// but this is only possible when the adapter is up (and hcid is running).
+// It is much easier just to hardcode bluetooth adapter to hci0
+#define BLUETOOTH_ADAPTER_HCI_NUM 0
+#define BLUEZ_ADAPTER_OBJECT_NAME BLUEZ_DBUS_BASE_PATH "/hci0"
+
+#define BTADDR_SIZE 18   // size of BT address character array (including null)
+
+jfieldID get_field(JNIEnv *env,
+                   jclass clazz,
+                   const char *member,
+                   const char *mtype);
+
+// LOGE and free a D-Bus error
+// Using #define so that __FUNCTION__ resolves usefully
+#define LOG_AND_FREE_DBUS_ERROR_WITH_MSG(err, msg) \
+    {   LOGE("%s: D-Bus error in %s: %s (%s)", __FUNCTION__, \
+        dbus_message_get_member((msg)), (err)->name, (err)->message); \
+         dbus_error_free((err)); }
+#define LOG_AND_FREE_DBUS_ERROR(err) \
+    {   LOGE("%s: D-Bus error: %s (%s)", __FUNCTION__, \
+        (err)->name, (err)->message); \
+        dbus_error_free((err)); }
+
+struct event_loop_native_data_t {
+    DBusConnection *conn;
+    /* These variables are set in waitForAndDispatchEventNative() and are
+       valid only within the scope of this function.  At any other time, they
+       are NULL. */
+    jobject me;
+    JNIEnv *env;
+};
+
+dbus_bool_t dbus_func_args_async_valist(JNIEnv *env,
+                                        DBusConnection *conn,
+                                        int timeout_ms,
+                                        void (*reply)(DBusMessage *, void *),
+                                        void *user,
+                                        const char *path,
+                                        const char *ifc,
+                                        const char *func,
+                                        int first_arg_type,
+                                        va_list args);
+
+dbus_bool_t dbus_func_args_async(JNIEnv *env,
+                                 DBusConnection *conn,
+                                 int timeout_ms,
+                                 void (*reply)(DBusMessage *, void *),
+                                 void *user,
+                                 const char *path,
+                                 const char *ifc,
+                                 const char *func,
+                                 int first_arg_type,
+                                 ...);
+
+DBusMessage * dbus_func_args(JNIEnv *env,
+                             DBusConnection *conn,
+                             const char *path,
+                             const char *ifc,
+                             const char *func,
+                             int first_arg_type,
+                             ...);
+
+DBusMessage * dbus_func_args_error(JNIEnv *env,
+                                   DBusConnection *conn,
+                                   DBusError *err,
+                                   const char *path,
+                                   const char *ifc,
+                                   const char *func,
+                                   int first_arg_type,
+                                   ...);
+
+DBusMessage * dbus_func_args_timeout(JNIEnv *env,
+                                     DBusConnection *conn,
+                                     int timeout_ms,
+                                     const char *path,
+                                     const char *ifc,
+                                     const char *func,
+                                     int first_arg_type,
+                                     ...);
+
+DBusMessage * dbus_func_args_timeout_valist(JNIEnv *env,
+                                            DBusConnection *conn,
+                                            int timeout_ms,
+                                            DBusError *err,
+                                            const char *path,
+                                            const char *ifc,
+                                            const char *func,
+                                            int first_arg_type,
+                                            va_list args);
+
+jint dbus_returns_int32(JNIEnv *env, DBusMessage *reply);
+jint dbus_returns_uint32(JNIEnv *env, DBusMessage *reply);
+jstring dbus_returns_string(JNIEnv *env, DBusMessage *reply);
+jboolean dbus_returns_boolean(JNIEnv *env, DBusMessage *reply);
+jobjectArray dbus_returns_array_of_strings(JNIEnv *env, DBusMessage *reply);
+jbyteArray dbus_returns_array_of_bytes(JNIEnv *env, DBusMessage *reply);
+
+void get_bdaddr(const char *str, bdaddr_t *ba);
+void get_bdaddr_as_string(const bdaddr_t *ba, char *str);
+
+bool debug_no_encrypt();
+
+#endif
+} /* namespace android */
+
+#endif/*ANDROID_BLUETOOTH_COMMON_H*/
diff --git a/core/jni/android_database_CursorWindow.cpp b/core/jni/android_database_CursorWindow.cpp
new file mode 100644
index 0000000..f19fbbf
--- /dev/null
+++ b/core/jni/android_database_CursorWindow.cpp
@@ -0,0 +1,674 @@
+/*
+ * Copyright (C) 2007 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.
+ */
+
+#undef LOG_TAG
+#define LOG_TAG "CursorWindow"
+
+#include <jni.h>
+#include <JNIHelp.h>
+#include <android_runtime/AndroidRuntime.h>
+
+#include <utils/Log.h>
+#include <utils/String8.h>
+#include <utils/String16.h>
+
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "CursorWindow.h"
+#include "sqlite3_exception.h"
+#include "android_util_Binder.h"
+
+
+namespace android {
+
+static jfieldID gWindowField;
+static jfieldID gBufferField;
+static jfieldID gSizeCopiedField;
+
+#define GET_WINDOW(env, object) ((CursorWindow *)env->GetIntField(object, gWindowField))
+#define SET_WINDOW(env, object, window) (env->SetIntField(object, gWindowField, (int)window))
+#define SET_BUFFER(env, object, buf) (env->SetObjectField(object, gBufferField, buf))
+#define SET_SIZE_COPIED(env, object, size) (env->SetIntField(object, gSizeCopiedField, size))
+
+CursorWindow * get_window_from_object(JNIEnv * env, jobject javaWindow)
+{
+    return GET_WINDOW(env, javaWindow);
+}
+
+static void native_init_empty(JNIEnv * env, jobject object, jboolean localOnly)
+{
+    uint8_t * data;
+    size_t size;
+    CursorWindow * window;
+
+    window = new CursorWindow(MAX_WINDOW_SIZE);
+    if (!window) {
+        jniThrowException(env, "java/lang/RuntimeException", "No memory for native window object");
+        return;
+    }
+
+    if (!window->initBuffer(localOnly)) {
+        jniThrowException(env, "java/lang/IllegalStateException", "Couldn't init cursor window");
+        delete window;
+        return;
+    }
+
+LOG_WINDOW("native_init_empty: window = %p", window);
+    SET_WINDOW(env, object, window);
+}
+
+static void native_init_memory(JNIEnv * env, jobject object, jobject memObj)
+{
+    sp<IMemory> memory = interface_cast<IMemory>(ibinderForJavaObject(env, memObj));
+    if (memory == NULL) {
+        jniThrowException(env, "java/lang/IllegalStateException", "Couldn't get native binder");
+        return;
+    }
+
+    CursorWindow * window = new CursorWindow();
+    if (!window) {
+        jniThrowException(env, "java/lang/RuntimeException", "No memory for native window object");
+        return;
+    }
+    if (!window->setMemory(memory)) {
+        jniThrowException(env, "java/lang/RuntimeException", "No memory in memObj");
+        delete window;
+        return;
+    }
+
+LOG_WINDOW("native_init_memory: numRows = %d, numColumns = %d, window = %p", window->getNumRows(), window->getNumColumns(), window);
+    SET_WINDOW(env, object, window);
+}
+
+static jobject native_getBinder(JNIEnv * env, jobject object)
+{
+    CursorWindow * window = GET_WINDOW(env, object);
+    if (window) {
+        sp<IMemory> memory = window->getMemory();
+        if (memory != NULL) {
+            sp<IBinder> binder = memory->asBinder();
+            return javaObjectForIBinder(env, binder);
+        }
+    }
+    return NULL;
+}
+
+static void native_clear(JNIEnv * env, jobject object)
+{
+    CursorWindow * window = GET_WINDOW(env, object);
+LOG_WINDOW("Clearing window %p", window);
+    if (window == NULL) {
+        jniThrowException(env, "java/lang/IllegalStateException", "clear() called after close()");
+        return;
+    }
+    window->clear();
+}
+
+static void native_close(JNIEnv * env, jobject object)
+{
+    CursorWindow * window = GET_WINDOW(env, object);
+    if (window) {
+LOG_WINDOW("Closing window %p", window);
+        delete window;
+        SET_WINDOW(env, object, 0);
+    }
+}
+
+static void throwExceptionWithRowCol(JNIEnv * env, jint row, jint column)
+{
+    char buf[100];
+    snprintf(buf, sizeof(buf), "get field slot from row %d col %d failed", row, column);
+    jniThrowException(env, "java/lang/IllegalStateException", buf);
+}
+
+static void throwUnknowTypeException(JNIEnv * env, jint type)
+{
+    char buf[80];
+    snprintf(buf, sizeof(buf), "UNKNOWN type %d", type);
+    jniThrowException(env, "java/lang/IllegalStateException", buf);
+}
+
+static jlong getLong_native(JNIEnv * env, jobject object, jint row, jint column)
+{
+    int32_t err;
+    CursorWindow * window = GET_WINDOW(env, object);
+LOG_WINDOW("Getting long for %d,%d from %p", row, column, window);
+
+    field_slot_t field;
+    err = window->read_field_slot(row, column, &field);
+    if (err != 0) {
+        throwExceptionWithRowCol(env, row, column);
+        return 0;
+    }
+
+    uint8_t type = field.type;
+    if (type == FIELD_TYPE_INTEGER) {
+        int64_t value;
+        if (window->getLong(row, column, &value)) {
+            return value;
+        }
+        return 0;
+    } else if (type == FIELD_TYPE_STRING) {
+        uint32_t size = field.data.buffer.size;
+        if (size > 0) {
+#if WINDOW_STORAGE_UTF8
+            return strtoll((char const *)window->offsetToPtr(field.data.buffer.offset), NULL, 0);
+#else
+            String8 ascii((char16_t *) window->offsetToPtr(field.data.buffer.offset), size / 2);
+            char const * str = ascii.string();
+            return strtoll(str, NULL, 0);
+#endif
+        } else {
+            return 0;
+        }
+    } else if (type == FIELD_TYPE_FLOAT) {
+        double value;
+        if (window->getDouble(row, column, &value)) {
+            return value;
+        }
+        return 0;
+    } else if (type == FIELD_TYPE_NULL) {
+        return 0;
+    } else if (type == FIELD_TYPE_BLOB) {
+        throw_sqlite3_exception(env, "Unable to convert BLOB to long");
+        return 0;
+    } else {
+        throwUnknowTypeException(env, type);
+        return 0;
+    }
+}
+
+static jbyteArray getBlob_native(JNIEnv* env, jobject object, jint row, jint column)
+{
+    int32_t err;
+    CursorWindow * window = GET_WINDOW(env, object);
+LOG_WINDOW("Getting blob for %d,%d from %p", row, column, window);
+
+    field_slot_t field;
+    err = window->read_field_slot(row, column, &field);
+    if (err != 0) {
+        throwExceptionWithRowCol(env, row, column);
+        return NULL;
+    }
+
+    uint8_t type = field.type;
+    if (type == FIELD_TYPE_BLOB || type == FIELD_TYPE_STRING) {
+        jbyteArray byteArray = env->NewByteArray(field.data.buffer.size);
+        LOG_ASSERT(byteArray, "Native could not create new byte[]");
+        env->SetByteArrayRegion(byteArray, 0, field.data.buffer.size,
+            (const jbyte*)window->offsetToPtr(field.data.buffer.offset));
+        return byteArray;
+    } else if (type == FIELD_TYPE_INTEGER) {
+        throw_sqlite3_exception(env, "INTEGER data in getBlob_native ");
+    } else if (type == FIELD_TYPE_FLOAT) {
+        throw_sqlite3_exception(env, "FLOAT data in getBlob_native ");
+    } else if (type == FIELD_TYPE_NULL) {
+        // do nothing
+    } else {
+        throwUnknowTypeException(env, type);
+    }
+    return NULL;
+}
+
+static jboolean isBlob_native(JNIEnv* env, jobject object, jint row, jint column)
+{
+    int32_t err;
+    CursorWindow * window = GET_WINDOW(env, object);
+LOG_WINDOW("Checking if column is a blob for %d,%d from %p", row, column, window);
+
+    field_slot_t field;
+    err = window->read_field_slot(row, column, &field);
+    if (err != 0) {
+        throwExceptionWithRowCol(env, row, column);
+        return NULL;
+    }
+
+    return field.type == FIELD_TYPE_BLOB || field.type == FIELD_TYPE_NULL;
+}
+
+static jstring getString_native(JNIEnv* env, jobject object, jint row, jint column)
+{
+    int32_t err;
+    CursorWindow * window = GET_WINDOW(env, object);
+LOG_WINDOW("Getting string for %d,%d from %p", row, column, window);
+
+    field_slot_t field;
+    err = window->read_field_slot(row, column, &field);
+    if (err != 0) {
+        throwExceptionWithRowCol(env, row, column);
+        return NULL;
+    }
+
+    uint8_t type = field.type;
+    if (type == FIELD_TYPE_STRING) {
+        uint32_t size = field.data.buffer.size;
+        if (size > 0) {
+#if WINDOW_STORAGE_UTF8
+            // Pass size - 1 since the UTF8 is null terminated and we don't want a null terminator on the UTF16 string
+            String16 utf16((char const *)window->offsetToPtr(field.data.buffer.offset), size - 1);
+            return env->NewString((jchar const *)utf16.string(), utf16.size());
+#else
+            return env->NewString((jchar const *)window->offsetToPtr(field.data.buffer.offset), size / 2);
+#endif
+        } else {
+            return env->NewStringUTF("");
+        }
+    } else if (type == FIELD_TYPE_INTEGER) {
+        int64_t value;
+        if (window->getLong(row, column, &value)) {
+            char buf[32];
+            snprintf(buf, sizeof(buf), "%lld", value);
+            return env->NewStringUTF(buf);
+        }
+        return NULL;
+    } else if (type == FIELD_TYPE_FLOAT) {
+        double value;
+        if (window->getDouble(row, column, &value)) {
+            char buf[32];
+            snprintf(buf, sizeof(buf), "%g", value);
+            return env->NewStringUTF(buf);
+        }
+        return NULL;
+    } else if (type == FIELD_TYPE_NULL) {
+        return NULL;
+    } else if (type == FIELD_TYPE_BLOB) {
+        throw_sqlite3_exception(env, "Unable to convert BLOB to string");
+        return NULL;
+    } else {
+        throwUnknowTypeException(env, type);
+        return NULL;
+    }
+}
+
+/**
+ * Use this only to convert characters that are known to be within the
+ * 0-127 range for direct conversion to UTF-16
+ */
+static jint charToJchar(const char* src, jchar* dst, jint bufferSize)
+{
+    int32_t len = strlen(src);
+
+    if (bufferSize < len) {
+        len = bufferSize;
+    }
+
+    for (int i = 0; i < len; i++) {
+        *dst++ = (*src++ & 0x7F);
+    }
+    return len;
+}
+
+static jcharArray copyStringToBuffer_native(JNIEnv* env, jobject object, jint row,
+                                      jint column, jint bufferSize, jobject buf)
+{
+    int32_t err;
+    CursorWindow * window = GET_WINDOW(env, object);
+LOG_WINDOW("Copying string for %d,%d from %p", row, column, window);
+
+    field_slot_t field;
+    err = window->read_field_slot(row, column, &field);
+    if (err != 0) {
+        jniThrowException(env, "java/lang/IllegalStateException", "Unable to get field slot");
+        return NULL;
+    }
+    
+    jcharArray buffer = (jcharArray)env->GetObjectField(buf, gBufferField);
+    if (buffer == NULL) {
+        jniThrowException(env, "java/lang/IllegalStateException", "buf should not be null");
+        return NULL;        
+    }
+    jchar* dst = env->GetCharArrayElements(buffer, NULL);
+    uint8_t type = field.type;
+    uint32_t sizeCopied = 0;
+    jcharArray newArray = NULL;
+    if (type == FIELD_TYPE_STRING) {
+        uint32_t size = field.data.buffer.size;
+        if (size > 0) {            
+#if WINDOW_STORAGE_UTF8
+            // Pass size - 1 since the UTF8 is null terminated and we don't want a null terminator on the UTF16 string
+            String16 utf16((char const *)window->offsetToPtr(field.data.buffer.offset), size - 1);
+            int32_t strSize = utf16.size();
+            if (strSize > bufferSize || dst == NULL) {
+                newArray = env->NewCharArray(strSize);
+                env->SetCharArrayRegion(newArray, 0, strSize, (jchar const *)utf16.string());
+            } else {                
+                memcpy(dst, (jchar const *)utf16.string(), strSize * 2);
+            }
+            sizeCopied = strSize;
+#else
+            sizeCopied = size/2 + size % 2;
+            if (size > bufferSize * 2 || dst == NULL) {
+                newArray = env->NewCharArray(sizeCopied);
+                memcpy(newArray, (jchar const *)window->offsetToPtr(field.data.buffer.offset), size);
+            } else {
+                memcpy(dst, (jchar const *)window->offsetToPtr(field.data.buffer.offset), size);
+            }
+#endif
+        } 
+    } else if (type == FIELD_TYPE_INTEGER) {
+        int64_t value;
+        if (window->getLong(row, column, &value)) {
+            char buf[32];
+            int len;
+            snprintf(buf, sizeof(buf), "%lld", value);
+            jchar* dst = env->GetCharArrayElements(buffer, NULL);
+            sizeCopied = charToJchar(buf, dst, bufferSize);
+         }
+    } else if (type == FIELD_TYPE_FLOAT) {
+        double value;
+        if (window->getDouble(row, column, &value)) {
+            char tempbuf[32];
+            snprintf(tempbuf, sizeof(tempbuf), "%g", value);
+            jchar* dst = env->GetCharArrayElements(buffer, NULL);
+            sizeCopied = charToJchar(tempbuf, dst, bufferSize);
+        }
+    } else if (type == FIELD_TYPE_NULL) {
+    } else if (type == FIELD_TYPE_BLOB) {
+        throw_sqlite3_exception(env, "Unable to convert BLOB to string");
+    } else {
+        LOGE("Unknown field type %d", type);
+        throw_sqlite3_exception(env, "UNKNOWN type in copyStringToBuffer_native()");
+    }
+    SET_SIZE_COPIED(env, buf, sizeCopied);
+    env->ReleaseCharArrayElements(buffer, dst, JNI_OK);
+    return newArray;
+}
+
+static jdouble getDouble_native(JNIEnv* env, jobject object, jint row, jint column)
+{
+    int32_t err;
+    CursorWindow * window = GET_WINDOW(env, object);
+LOG_WINDOW("Getting double for %d,%d from %p", row, column, window);
+
+    field_slot_t field;
+    err = window->read_field_slot(row, column, &field);
+    if (err != 0) {
+        throwExceptionWithRowCol(env, row, column);
+        return 0.0;
+    }
+
+    uint8_t type = field.type;
+    if (type == FIELD_TYPE_FLOAT) {
+        double value;
+        if (window->getDouble(row, column, &value)) {
+            return value;
+        }
+        return 0.0;
+    } else if (type == FIELD_TYPE_STRING) {
+        uint32_t size = field.data.buffer.size;
+        if (size > 0) {
+#if WINDOW_STORAGE_UTF8
+            return strtod((char const *)window->offsetToPtr(field.data.buffer.offset), NULL);
+#else
+            String8 ascii((char16_t *) window->offsetToPtr(field.data.buffer.offset), size / 2);
+            char const * str = ascii.string();
+            return strtod(str, NULL);
+#endif
+        } else {
+            return 0.0;
+        }
+    } else if (type == FIELD_TYPE_INTEGER) {
+        int64_t value;
+        if (window->getLong(row, column, &value)) {
+            return (double) value;
+        }
+        return 0.0;
+    } else if (type == FIELD_TYPE_NULL) {
+        return 0.0;
+    } else if (type == FIELD_TYPE_BLOB) {
+        throw_sqlite3_exception(env, "Unable to convert BLOB to double");
+        return 0.0;
+    } else {
+        throwUnknowTypeException(env, type);
+        return 0.0;
+    }
+}
+
+static jboolean isNull_native(JNIEnv* env, jobject object, jint row, jint column)
+{
+    CursorWindow * window = GET_WINDOW(env, object);
+LOG_WINDOW("Checking for NULL at %d,%d from %p", row, column, window);
+
+    bool isNull;
+    if (window->getNull(row, column, &isNull)) {
+        return isNull;
+    }
+
+    //TODO throw execption?
+    return true;
+}
+
+static jint getNumRows(JNIEnv * env, jobject object)
+{
+    CursorWindow * window = GET_WINDOW(env, object);
+    return window->getNumRows();
+}
+
+static jboolean setNumColumns(JNIEnv * env, jobject object, jint columnNum)
+{
+    CursorWindow * window = GET_WINDOW(env, object);
+    return window->setNumColumns(columnNum);
+}
+
+static jboolean allocRow(JNIEnv * env, jobject object)
+{
+    CursorWindow * window = GET_WINDOW(env, object);
+    return window->allocRow() != NULL;
+}
+
+static jboolean putBlob_native(JNIEnv * env, jobject object, jbyteArray value, jint row, jint col)
+{
+    CursorWindow * window = GET_WINDOW(env, object);
+    if (!value) {
+        LOG_WINDOW("How did a null value send to here");
+        return false;
+    }
+    field_slot_t * fieldSlot = window->getFieldSlotWithCheck(row, col);
+    if (fieldSlot == NULL) {
+        LOG_WINDOW(" getFieldSlotWithCheck error ");
+        return false;
+    }
+
+    jint len = env->GetArrayLength(value);
+    int offset = window->alloc(len);
+    if (!offset) {
+        LOG_WINDOW("Failed allocating %u bytes", len);
+        return false;
+    }
+    jbyte * bytes = env->GetByteArrayElements(value, NULL);
+    window->copyIn(offset, (uint8_t const *)bytes, len);
+
+    // This must be updated after the call to alloc(), since that
+    // may move the field around in the window
+    fieldSlot->type = FIELD_TYPE_BLOB;
+    fieldSlot->data.buffer.offset = offset;
+    fieldSlot->data.buffer.size = len;
+    env->ReleaseByteArrayElements(value, bytes, JNI_ABORT);
+    LOG_WINDOW("%d,%d is BLOB with %u bytes @ %d", row, col, len, offset);
+    return true;
+}
+
+static jboolean putString_native(JNIEnv * env, jobject object, jstring value, jint row, jint col)
+{
+    CursorWindow * window = GET_WINDOW(env, object);
+    if (!value) {
+        LOG_WINDOW("How did a null value send to here");
+        return false;
+    }
+    field_slot_t * fieldSlot = window->getFieldSlotWithCheck(row, col);
+    if (fieldSlot == NULL) {
+        LOG_WINDOW(" getFieldSlotWithCheck error ");
+        return false;
+    }
+
+#if WINDOW_STORAGE_UTF8
+    int len = env->GetStringUTFLength(value) + 1;
+    char const * valStr = env->GetStringUTFChars(value, NULL);
+#else
+    int len = env->GetStringLength(value);
+    // GetStringLength return number of chars and one char takes 2 bytes
+    len *= 2;
+    const jchar* valStr = env->GetStringChars(value, NULL);
+#endif
+    if (!valStr) {
+        LOG_WINDOW("value can't be transfer to UTFChars");
+        return false;
+    }
+
+    int offset = window->alloc(len);
+    if (!offset) {
+        LOG_WINDOW("Failed allocating %u bytes", len);
+#if WINDOW_STORAGE_UTF8
+        env->ReleaseStringUTFChars(value, valStr);
+#else
+        env->ReleaseStringChars(value, valStr);
+#endif
+        return false;
+    }
+
+    window->copyIn(offset, (uint8_t const *)valStr, len);
+
+    // This must be updated after the call to alloc(), since that
+    // may move the field around in the window
+    fieldSlot->type = FIELD_TYPE_STRING;
+    fieldSlot->data.buffer.offset = offset;
+    fieldSlot->data.buffer.size = len;
+
+    LOG_WINDOW("%d,%d is TEXT with %u bytes @ %d", row, col, len, offset);
+#if WINDOW_STORAGE_UTF8
+    env->ReleaseStringUTFChars(value, valStr);
+#else
+    env->ReleaseStringChars(value, valStr);
+#endif
+
+    return true;
+}
+
+static jboolean putLong_native(JNIEnv * env, jobject object, jlong value, jint row, jint col)
+{
+    CursorWindow * window = GET_WINDOW(env, object);
+    if (!window->putLong(row, col, value)) {
+        LOG_WINDOW(" getFieldSlotWithCheck error ");
+        return false;
+    }
+
+    LOG_WINDOW("%d,%d is INTEGER 0x%016llx", row, col, value);
+
+    return true;
+}
+
+static jboolean putDouble_native(JNIEnv * env, jobject object, jdouble value, jint row, jint col)
+{
+    CursorWindow * window = GET_WINDOW(env, object);
+    if (!window->putDouble(row, col, value)) {
+        LOG_WINDOW(" getFieldSlotWithCheck error ");
+        return false;
+    }
+
+    LOG_WINDOW("%d,%d is FLOAT %lf", row, col, value);
+
+    return true;
+}
+
+static jboolean putNull_native(JNIEnv * env, jobject object, jint row, jint col)
+{
+    CursorWindow * window = GET_WINDOW(env, object);
+    if (!window->putNull(row, col)) {
+        LOG_WINDOW(" getFieldSlotWithCheck error ");
+        return false;
+    }
+
+    LOG_WINDOW("%d,%d is NULL", row, col);
+
+    return true;
+}
+
+// free the last row
+static void freeLastRow(JNIEnv * env, jobject object) {
+    CursorWindow * window = GET_WINDOW(env, object);
+    window->freeLastRow();
+}
+
+static JNINativeMethod sMethods[] =
+{
+     /* name, signature, funcPtr */
+    {"native_init", "(Z)V", (void *)native_init_empty},
+    {"native_init", "(Landroid/os/IBinder;)V", (void *)native_init_memory},
+    {"native_getBinder", "()Landroid/os/IBinder;", (void *)native_getBinder},
+    {"native_clear", "()V", (void *)native_clear},
+    {"close_native", "()V", (void *)native_close},
+    {"getLong_native", "(II)J", (void *)getLong_native},
+    {"getBlob_native", "(II)[B", (void *)getBlob_native},
+    {"isBlob_native", "(II)Z", (void *)isBlob_native},
+    {"getString_native", "(II)Ljava/lang/String;", (void *)getString_native},
+    {"copyStringToBuffer_native", "(IIILandroid/database/CharArrayBuffer;)[C", (void *)copyStringToBuffer_native},
+    {"getDouble_native", "(II)D", (void *)getDouble_native},
+    {"isNull_native", "(II)Z", (void *)isNull_native},
+    {"getNumRows_native", "()I", (void *)getNumRows},
+    {"setNumColumns_native", "(I)Z", (void *)setNumColumns},
+    {"allocRow_native", "()Z", (void *)allocRow},
+    {"putBlob_native", "([BII)Z", (void *)putBlob_native},
+    {"putString_native", "(Ljava/lang/String;II)Z", (void *)putString_native},
+    {"putLong_native", "(JII)Z", (void *)putLong_native},
+    {"putDouble_native", "(DII)Z", (void *)putDouble_native},
+    {"freeLastRow_native", "()V", (void *)freeLastRow},
+    {"putNull_native", "(II)Z", (void *)putNull_native},
+};
+
+int register_android_database_CursorWindow(JNIEnv * env)
+{
+    jclass clazz;
+
+    clazz = env->FindClass("android/database/CursorWindow");
+    if (clazz == NULL) {
+        LOGE("Can't find android/database/CursorWindow");
+        return -1;
+    }
+
+    gWindowField = env->GetFieldID(clazz, "nWindow", "I");
+
+    if (gWindowField == NULL) {
+        LOGE("Error locating fields");
+        return -1;
+    }
+    
+    clazz =  env->FindClass("android/database/CharArrayBuffer");
+    if (clazz == NULL) {
+        LOGE("Can't find android/database/CharArrayBuffer");
+        return -1;
+    }
+
+    gBufferField = env->GetFieldID(clazz, "data", "[C");
+
+    if (gBufferField == NULL) {
+        LOGE("Error locating fields data in CharArrayBuffer");
+        return -1;
+    }
+
+    gSizeCopiedField = env->GetFieldID(clazz, "sizeCopied", "I");
+
+    if (gSizeCopiedField == NULL) {
+        LOGE("Error locating fields sizeCopied in CharArrayBuffer");
+        return -1;
+    }
+
+    return AndroidRuntime::registerNativeMethods(env, "android/database/CursorWindow",
+            sMethods, NELEM(sMethods));
+}
+
+} // namespace android
diff --git a/core/jni/android_database_SQLiteDatabase.cpp b/core/jni/android_database_SQLiteDatabase.cpp
new file mode 100644
index 0000000..66858f9
--- /dev/null
+++ b/core/jni/android_database_SQLiteDatabase.cpp
@@ -0,0 +1,470 @@
+/*
+ * Copyright (C) 2006-2007 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.
+ */
+
+#undef LOG_TAG
+#define LOG_TAG "Database"
+
+#include <utils/Log.h>
+#include <utils/String8.h>
+#include <utils/String16.h>
+
+#include <jni.h>
+#include <JNIHelp.h>
+#include <android_runtime/AndroidRuntime.h>
+
+#include <sqlite3.h>
+#include <sqlite3_android.h>
+#include <string.h>
+#include <utils.h>
+#include <ctype.h>
+
+#include <stdio.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <string.h>
+#include <netdb.h>
+#include <sys/ioctl.h>
+
+#include "sqlite3_exception.h"
+
+#define UTF16_STORAGE 0
+#define INVALID_VERSION -1
+#define SQLITE_SOFT_HEAP_LIMIT (4 * 1024 * 1024)
+#define ANDROID_TABLE "android_metadata"
+/* uncomment the next line to force-enable logging of all statements */
+// #define DB_LOG_STATEMENTS
+
+namespace android {
+
+enum {
+    OPEN_READWRITE          = 0x00000000,
+    OPEN_READONLY           = 0x00000001,
+    OPEN_READ_MASK          = 0x00000001,
+    NO_LOCALIZED_COLLATORS  = 0x00000010,
+    CREATE_IF_NECESSARY     = 0x10000000
+};
+
+static jfieldID offset_db_handle;
+
+/* public native void dbopen(String path, int flags, String locale); */
+static void dbopen(JNIEnv* env, jobject object, jstring pathString, jint flags)
+{
+    int err;
+    sqlite3 * handle = NULL;
+    sqlite3_stmt * statement = NULL;
+    char const * path8 = env->GetStringUTFChars(pathString, NULL);
+    int sqliteFlags;
+
+    // convert our flags into the sqlite flags
+    if (flags & CREATE_IF_NECESSARY) {
+        sqliteFlags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE;
+    } else if (flags & OPEN_READONLY) {
+        sqliteFlags = SQLITE_OPEN_READONLY;
+    } else {
+        sqliteFlags = SQLITE_OPEN_READWRITE;
+    }
+
+    err = sqlite3_open_v2(path8, &handle, sqliteFlags, NULL);
+    if (err != SQLITE_OK) {
+        LOGE("sqlite3_open_v2(\"%s\", &handle, %d, NULL) failed\n", path8, sqliteFlags);
+        throw_sqlite3_exception(env, handle);
+        goto done;
+    }
+
+    // The soft heap limit prevents the page cache allocations from growing
+    // beyond the given limit, no matter what the max page cache sizes are
+    // set to. The limit does not, as of 3.5.0, affect any other allocations.
+    sqlite3_soft_heap_limit(SQLITE_SOFT_HEAP_LIMIT);
+
+    // Set the default busy handler to retry for 1000ms and then return SQLITE_BUSY
+    err = sqlite3_busy_timeout(handle, 1000 /* ms */);
+    if (err != SQLITE_OK) {
+        LOGE("sqlite3_busy_timeout(handle, 1000) failed for \"%s\"\n", path8);
+        throw_sqlite3_exception(env, handle);
+        goto done;
+    }
+
+#ifdef DB_INTEGRITY_CHECK
+    static const char* integritySql = "pragma integrity_check(1);";
+    err = sqlite3_prepare_v2(handle, integritySql, -1, &statement, NULL);
+    if (err != SQLITE_OK) {
+        LOGE("sqlite_prepare_v2(handle, \"%s\") failed for \"%s\"\n", integritySql, path8);
+        throw_sqlite3_exception(env, handle);
+        goto done;
+    }
+
+    // first is OK or error message
+    err = sqlite3_step(statement);
+    if (err != SQLITE_ROW) {
+        LOGE("integrity check failed for \"%s\"\n", integritySql, path8);
+        throw_sqlite3_exception(env, handle);
+        goto done;
+    } else {
+        const char *text = (const char*)sqlite3_column_text(statement, 0);
+        if (strcmp(text, "ok") != 0) {
+            LOGE("integrity check failed for \"%s\": %s\n", integritySql, path8, text);
+            jniThrowException(env, "android/database/sqlite/SQLiteDatabaseCorruptException", text);
+            goto done;
+        }
+    }
+#endif
+
+    err = register_android_functions(handle, UTF16_STORAGE);
+    if (err) {
+        throw_sqlite3_exception(env, handle);
+        goto done;
+    }
+
+    LOGV("Opened '%s' - %p\n", path8, handle);
+    env->SetIntField(object, offset_db_handle, (int) handle);
+    handle = NULL;  // The caller owns the handle now.
+
+done:
+    // Release allocated resources
+    if (path8 != NULL) env->ReleaseStringUTFChars(pathString, path8);
+    if (statement != NULL) sqlite3_finalize(statement);
+    if (handle != NULL) sqlite3_close(handle);
+}
+
+/* public native void close(); */
+static void dbclose(JNIEnv* env, jobject object)
+{
+    sqlite3 * handle = (sqlite3 *)env->GetIntField(object, offset_db_handle);
+
+    if (handle != NULL) {
+        LOGV("Closing database: handle=%p\n", handle);
+        int result = sqlite3_close(handle);
+        if (result == SQLITE_OK) {
+            LOGV("Closed %p\n", handle);
+            env->SetIntField(object, offset_db_handle, 0);
+        } else {
+            // This can happen if sub-objects aren't closed first.  Make sure the caller knows.
+            throw_sqlite3_exception(env, handle);
+            LOGE("sqlite3_close(%p) failed: %d\n", handle, result);
+        }
+    }
+}
+
+/* public native void native_execSQL(String sql); */
+static void native_execSQL(JNIEnv* env, jobject object, jstring sqlString)
+{
+    int err;
+    int stepErr;
+    sqlite3_stmt * statement = NULL;
+    sqlite3 * handle = (sqlite3 *)env->GetIntField(object, offset_db_handle);
+    jchar const * sql = env->GetStringChars(sqlString, NULL);
+    jsize sqlLen = env->GetStringLength(sqlString);
+
+    if (sql == NULL || sqlLen == 0) {
+        jniThrowException(env, "java/lang/IllegalArgumentException", "You must supply an SQL string");
+        return;
+    }
+
+    err = sqlite3_prepare16_v2(handle, sql, sqlLen * 2, &statement, NULL);
+
+    env->ReleaseStringChars(sqlString, sql);
+
+    if (err != SQLITE_OK) {
+        char const * sql8 = env->GetStringUTFChars(sqlString, NULL);
+        LOGE("Failure %d (%s) on %p when preparing '%s'.\n", err, sqlite3_errmsg(handle), handle, sql8);
+        throw_sqlite3_exception(env, handle, sql8);
+        env->ReleaseStringUTFChars(sqlString, sql8);
+        return;
+    }
+
+    stepErr = sqlite3_step(statement);
+    err = sqlite3_finalize(statement);
+
+    if (stepErr != SQLITE_DONE) {
+        if (stepErr == SQLITE_ROW) {
+            throw_sqlite3_exception(env, "Queries cannot be performed using execSQL(), use query() instead.");
+        } else {
+            char const * sql8 = env->GetStringUTFChars(sqlString, NULL);
+            LOGE("Failure %d (%s) on %p when executing '%s'\n", err, sqlite3_errmsg(handle), handle, sql8);
+            throw_sqlite3_exception(env, handle, sql8);
+            env->ReleaseStringUTFChars(sqlString, sql8);
+
+        }
+    } else
+#ifndef DB_LOG_STATEMENTS
+    IF_LOGV()
+#endif
+    {
+        char const * sql8 = env->GetStringUTFChars(sqlString, NULL);
+        LOGV("Success on %p when executing '%s'\n", handle, sql8);
+        env->ReleaseStringUTFChars(sqlString, sql8);
+    }
+}
+
+/* native long lastInsertRow(); */
+static jlong lastInsertRow(JNIEnv* env, jobject object)
+{
+    sqlite3 * handle = (sqlite3 *)env->GetIntField(object, offset_db_handle);
+
+    return sqlite3_last_insert_rowid(handle);
+}
+
+/* native int lastChangeCount(); */
+static jint lastChangeCount(JNIEnv* env, jobject object)
+{
+    sqlite3 * handle = (sqlite3 *)env->GetIntField(object, offset_db_handle);
+
+    return sqlite3_changes(handle);
+}
+
+/* set locale in the android_metadata table, install localized collators, and rebuild indexes */
+static void native_setLocale(JNIEnv* env, jobject object, jstring localeString, jint flags)
+{
+    if ((flags & NO_LOCALIZED_COLLATORS)) return;
+
+    int err;
+    char const* locale8 = env->GetStringUTFChars(localeString, NULL);
+    sqlite3 * handle = (sqlite3 *)env->GetIntField(object, offset_db_handle);
+    sqlite3_stmt* stmt = NULL;
+    char** meta = NULL;
+    int rowCount, colCount;
+    char* dbLocale = NULL;
+
+    // create the table, if necessary and possible
+    if (!(flags & OPEN_READONLY)) {
+        static const char *createSql ="CREATE TABLE IF NOT EXISTS " ANDROID_TABLE " (locale TEXT)";
+        err = sqlite3_exec(handle, createSql, NULL, NULL, NULL);
+        if (err != SQLITE_OK) {
+            LOGE("CREATE TABLE " ANDROID_TABLE " failed\n");
+            throw_sqlite3_exception(env, handle);
+            goto done;
+        }
+    }
+
+    // try to read from the table
+    static const char *selectSql = "SELECT locale FROM " ANDROID_TABLE " LIMIT 1";
+    err = sqlite3_get_table(handle, selectSql, &meta, &rowCount, &colCount, NULL);
+    if (err != SQLITE_OK) {
+        LOGE("SELECT locale FROM " ANDROID_TABLE " failed\n");
+        throw_sqlite3_exception(env, handle);
+        goto done;
+    }
+
+    dbLocale = (rowCount >= 1) ? meta[1 * colCount + 0] : NULL;
+
+    if (dbLocale != NULL && !strcmp(dbLocale, locale8)) {
+        // database locale is the same as the desired locale; set up the collators and go
+        err = register_localized_collators(handle, locale8, UTF16_STORAGE);
+        if (err != SQLITE_OK) throw_sqlite3_exception(env, handle);
+        goto done;   // no database changes needed
+    }
+
+    if ((flags & OPEN_READONLY)) {
+        // read-only database, so we're going to have to put up with whatever we got
+        err = register_localized_collators(handle, dbLocale ? dbLocale : locale8, UTF16_STORAGE);
+        if (err != SQLITE_OK) throw_sqlite3_exception(env, handle);
+        goto done;
+    }
+
+    // need to update android_metadata and indexes atomically, so use a transaction...
+    err = sqlite3_exec(handle, "BEGIN TRANSACTION", NULL, NULL, NULL);
+    if (err != SQLITE_OK) {
+        LOGE("BEGIN TRANSACTION failed setting locale\n");
+        throw_sqlite3_exception(env, handle);
+        goto done;
+    }
+
+    err = register_localized_collators(handle, dbLocale ? dbLocale : locale8, UTF16_STORAGE);
+    if (err != SQLITE_OK) {
+        LOGE("register_localized_collators() failed setting locale\n");
+        throw_sqlite3_exception(env, handle);
+        goto done;
+    }
+
+    err = sqlite3_exec(handle, "DELETE FROM " ANDROID_TABLE, NULL, NULL, NULL);
+    if (err != SQLITE_OK) {
+        LOGE("DELETE failed setting locale\n");
+        throw_sqlite3_exception(env, handle);
+        goto rollback;
+    }
+
+    static const char *sql = "INSERT INTO " ANDROID_TABLE " (locale) VALUES(?);";
+    err = sqlite3_prepare_v2(handle, sql, -1, &stmt, NULL);
+    if (err != SQLITE_OK) {
+        LOGE("sqlite3_prepare_v2(\"%s\") failed\n", sql);
+        throw_sqlite3_exception(env, handle);
+        goto rollback;
+    }
+
+    err = sqlite3_bind_text(stmt, 1, locale8, -1, SQLITE_TRANSIENT);
+    if (err != SQLITE_OK) {
+        LOGE("sqlite3_bind_text() failed setting locale\n");
+        throw_sqlite3_exception(env, handle);
+        goto rollback;
+    }
+
+    err = sqlite3_step(stmt);
+    if (err != SQLITE_OK && err != SQLITE_DONE) {
+        LOGE("sqlite3_step(\"%s\") failed setting locale\n", sql);
+        throw_sqlite3_exception(env, handle);
+        goto rollback;
+    }
+
+    err = sqlite3_exec(handle, "REINDEX LOCALIZED", NULL, NULL, NULL);
+    if (err != SQLITE_OK) {
+        LOGE("REINDEX LOCALIZED failed\n");
+        throw_sqlite3_exception(env, handle);
+        goto rollback;
+    }
+
+    // all done, yay!
+    err = sqlite3_exec(handle, "COMMIT TRANSACTION", NULL, NULL, NULL);
+    if (err != SQLITE_OK) {
+        LOGE("COMMIT TRANSACTION failed setting locale\n");
+        throw_sqlite3_exception(env, handle);
+        goto done;
+    }
+
+rollback:
+    sqlite3_exec(handle, "ROLLBACK TRANSACTION", NULL, NULL, NULL);
+
+done:
+    if (locale8 != NULL) env->ReleaseStringUTFChars(localeString, locale8);
+    if (stmt != NULL) sqlite3_finalize(stmt);
+    if (meta != NULL) sqlite3_free_table(meta);
+}
+
+static jint native_releaseMemory(JNIEnv *env, jobject clazz)
+{
+    // Attempt to release as much memory from the
+    return sqlite3_release_memory(SQLITE_SOFT_HEAP_LIMIT);
+}
+
+static JNINativeMethod sMethods[] =
+{
+    /* name, signature, funcPtr */
+    {"dbopen", "(Ljava/lang/String;I)V", (void *)dbopen},
+    {"dbclose", "()V", (void *)dbclose},
+    {"native_execSQL", "(Ljava/lang/String;)V", (void *)native_execSQL},
+    {"lastInsertRow", "()J", (void *)lastInsertRow},
+    {"lastChangeCount", "()I", (void *)lastChangeCount},
+    {"native_setLocale", "(Ljava/lang/String;I)V", (void *)native_setLocale},
+    {"releaseMemory", "()I", (void *)native_releaseMemory},
+};
+
+int register_android_database_SQLiteDatabase(JNIEnv *env)
+{
+    jclass clazz;
+
+    clazz = env->FindClass("android/database/sqlite/SQLiteDatabase");
+    if (clazz == NULL) {
+        LOGE("Can't find android/database/sqlite/SQLiteDatabase\n");
+        return -1;
+    }
+
+    offset_db_handle = env->GetFieldID(clazz, "mNativeHandle", "I");
+    if (offset_db_handle == NULL) {
+        LOGE("Can't find SQLiteDatabase.mNativeHandle\n");
+        return -1;
+    }
+
+    return AndroidRuntime::registerNativeMethods(env, "android/database/sqlite/SQLiteDatabase", sMethods, NELEM(sMethods));
+}
+
+/* throw a SQLiteException with a message appropriate for the error in handle */
+void throw_sqlite3_exception(JNIEnv* env, sqlite3* handle) {
+    throw_sqlite3_exception(env, handle, NULL);
+}
+
+/* throw a SQLiteException with the given message */
+void throw_sqlite3_exception(JNIEnv* env, const char* message) {
+    throw_sqlite3_exception(env, NULL, message);
+}
+
+/* throw a SQLiteException with a message appropriate for the error in handle
+   concatenated with the given message
+ */
+void throw_sqlite3_exception(JNIEnv* env, sqlite3* handle, const char* message) {
+    if (handle) {
+        throw_sqlite3_exception(env, sqlite3_errcode(handle),
+                                sqlite3_errmsg(handle), message);
+    } else {
+        // we use SQLITE_OK so that a generic SQLiteException is thrown;
+        // any code not specified in the switch statement below would do.
+        throw_sqlite3_exception(env, SQLITE_OK, "unknown error", message);
+    }
+}
+
+/* throw a SQLiteException for a given error code */
+void throw_sqlite3_exception_errcode(JNIEnv* env, int errcode, const char* message) {
+    if (errcode == SQLITE_DONE) {
+        throw_sqlite3_exception(env, errcode, NULL, message);
+    } else {
+        char temp[20];
+        sprintf(temp, "error code %d", errcode);
+        throw_sqlite3_exception(env, errcode, temp, message);
+    }
+}
+
+/* throw a SQLiteException for a given error code, sqlite3message, and
+   user message
+ */
+void throw_sqlite3_exception(JNIEnv* env, int errcode,
+                             const char* sqlite3Message, const char* message) {
+    const char* exceptionClass;
+    switch (errcode) {
+        case SQLITE_IOERR:
+            exceptionClass = "android/database/sqlite/SQLiteDiskIOException";
+            break;
+        case SQLITE_CORRUPT:
+            exceptionClass = "android/database/sqlite/SQLiteDatabaseCorruptException";
+            break;
+        case SQLITE_CONSTRAINT:
+           exceptionClass = "android/database/sqlite/SQLiteConstraintException";
+           break;
+        case SQLITE_ABORT:
+           exceptionClass = "android/database/sqlite/SQLiteAbortException";
+           break;
+        case SQLITE_DONE:
+           exceptionClass = "android/database/sqlite/SQLiteDoneException";
+           break;
+        case SQLITE_FULL:
+           exceptionClass = "android/database/sqlite/SQLiteFullException";
+           break;
+        case SQLITE_MISUSE:
+           exceptionClass = "android/database/sqlite/SQLiteMisuseException";
+           break;
+        default:
+           exceptionClass = "android/database/sqlite/SQLiteException";
+           break;
+    }
+
+    if (sqlite3Message != NULL && message != NULL) {
+        char* fullMessage = (char *)malloc(strlen(sqlite3Message) + strlen(message) + 3);
+        if (fullMessage != NULL) {
+            strcpy(fullMessage, sqlite3Message);
+            strcat(fullMessage, ": ");
+            strcat(fullMessage, message);
+            jniThrowException(env, exceptionClass, fullMessage);
+            free(fullMessage);
+        } else {
+            jniThrowException(env, exceptionClass, sqlite3Message);
+        }
+    } else if (sqlite3Message != NULL) {
+        jniThrowException(env, exceptionClass, sqlite3Message);
+    } else {
+        jniThrowException(env, exceptionClass, message);
+    }
+}
+
+
+} // namespace android
diff --git a/core/jni/android_database_SQLiteDebug.cpp b/core/jni/android_database_SQLiteDebug.cpp
new file mode 100644
index 0000000..916df35
--- /dev/null
+++ b/core/jni/android_database_SQLiteDebug.cpp
@@ -0,0 +1,244 @@
+/*
+ * Copyright (C) 2007 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.
+ */
+
+#include <JNIHelp.h>
+#include <jni.h>
+#include <utils/misc.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <cutils/mspace.h>
+#include <utils/Log.h>
+
+#include <sqlite3.h>
+
+// From mem_mspace.c in libsqlite
+extern "C" mspace sqlite3_get_mspace();
+
+// From sqlite.c, hacked in for Android
+extern "C" void sqlite3_get_pager_stats(sqlite3_int64 * totalBytesOut,
+                                       sqlite3_int64 * referencedBytesOut,
+                                       sqlite3_int64 * dbBytesOut,
+                                       int * numPagersOut);
+
+namespace android {
+
+static jfieldID gTotalBytesField;
+static jfieldID gReferencedBytesField;
+static jfieldID gDbBytesField;
+static jfieldID gNumPagersField;
+
+
+#define USE_MSPACE 0
+
+static void getPagerStats(JNIEnv *env, jobject clazz, jobject statsObj)
+{
+    sqlite3_int64 totalBytes;
+    sqlite3_int64 referencedBytes;
+    sqlite3_int64 dbBytes;
+    int numPagers;
+
+    sqlite3_get_pager_stats(&totalBytes, &referencedBytes, &dbBytes,
+            &numPagers);
+
+    env->SetLongField(statsObj, gTotalBytesField, totalBytes);
+    env->SetLongField(statsObj, gReferencedBytesField, referencedBytes);
+    env->SetLongField(statsObj, gDbBytesField, dbBytes);
+    env->SetIntField(statsObj, gNumPagersField, numPagers);
+}
+
+static jlong getHeapSize(JNIEnv *env, jobject clazz)
+{
+#if !NO_MALLINFO
+    struct mallinfo info = mspace_mallinfo(sqlite3_get_mspace());
+    struct mallinfo info = dlmallinfo();
+    return (jlong) info.usmblks;
+#elif USE_MSPACE
+    mspace space = sqlite3_get_mspace();
+    if (space != 0) {
+        return mspace_footprint(space);
+    } else {
+        return 0;
+    }
+#else
+    return 0;
+#endif
+}
+
+static jlong getHeapAllocatedSize(JNIEnv *env, jobject clazz)
+{
+#if !NO_MALLINFO
+    struct mallinfo info = mspace_mallinfo(sqlite3_get_mspace());
+    return (jlong) info.uordblks;
+#else
+    return sqlite3_memory_used();
+#endif
+}
+
+static jlong getHeapFreeSize(JNIEnv *env, jobject clazz)
+{
+#if !NO_MALLINFO
+    struct mallinfo info = mspace_mallinfo(sqlite3_get_mspace());
+    return (jlong) info.fordblks;
+#else
+    return getHeapSize(env, clazz) - sqlite3_memory_used();
+#endif
+}
+
+static int read_mapinfo(FILE *fp,
+        int *sharedPages, int *privatePages)
+{
+    char line[1024];
+    int len;
+    int skip;
+
+    unsigned start = 0, size = 0, resident = 0;
+    unsigned shared_clean = 0, shared_dirty = 0;
+    unsigned private_clean = 0, private_dirty = 0;
+    unsigned referenced = 0;
+
+    int isAnon = 0;
+    int isHeap = 0;
+
+again:
+    skip = 0;
+    
+    if(fgets(line, 1024, fp) == 0) return 0;
+
+    len = strlen(line);
+    if (len < 1) return 0;
+    line[--len] = 0;
+
+    /* ignore guard pages */
+    if (line[18] == '-') skip = 1;
+
+    start = strtoul(line, 0, 16);
+
+    if (len > 50 && !strncmp(line + 49, "/tmp/sqlite-heap", strlen("/tmp/sqlite-heap"))) {
+        isHeap = 1;
+    }
+
+    if (fgets(line, 1024, fp) == 0) return 0;
+    if (sscanf(line, "Size: %d kB", &size) != 1) return 0;
+    if (fgets(line, 1024, fp) == 0) return 0;
+    if (sscanf(line, "Rss: %d kB", &resident) != 1) return 0;
+    if (fgets(line, 1024, fp) == 0) return 0;
+    if (sscanf(line, "Shared_Clean: %d kB", &shared_clean) != 1) return 0;
+    if (fgets(line, 1024, fp) == 0) return 0;
+    if (sscanf(line, "Shared_Dirty: %d kB", &shared_dirty) != 1) return 0;
+    if (fgets(line, 1024, fp) == 0) return 0;
+    if (sscanf(line, "Private_Clean: %d kB", &private_clean) != 1) return 0;
+    if (fgets(line, 1024, fp) == 0) return 0;
+    if (sscanf(line, "Private_Dirty: %d kB", &private_dirty) != 1) return 0;
+    if (fgets(line, 1024, fp) == 0) return 0;
+    if (sscanf(line, "Referenced: %d kB", &referenced) != 1) return 0;
+    
+    if (skip) {
+        goto again;
+    }
+
+    if (isHeap) {
+        *sharedPages += shared_dirty;
+        *privatePages += private_dirty;
+    }
+    return 1;
+}
+
+static void load_maps(int pid, int *sharedPages, int *privatePages)
+{
+    char tmp[128];
+    FILE *fp;
+    
+    sprintf(tmp, "/proc/%d/smaps", pid);
+    fp = fopen(tmp, "r");
+    if (fp == 0) return;
+    
+    while (read_mapinfo(fp, sharedPages, privatePages) != 0) {
+        // Do nothing
+    }
+    fclose(fp);
+}
+
+static void getHeapDirtyPages(JNIEnv *env, jobject clazz, jintArray pages)
+{
+    int _pages[2];
+
+    _pages[0] = 0;
+    _pages[1] = 0;
+
+    load_maps(getpid(), &_pages[0], &_pages[1]);
+
+    // Convert from kbytes to 4K pages
+    _pages[0] /= 4;
+    _pages[1] /= 4;
+
+    env->SetIntArrayRegion(pages, 0, 2, _pages);
+}
+
+/*
+ * JNI registration.
+ */
+
+static JNINativeMethod gMethods[] =
+{
+    { "getPagerStats", "(Landroid/database/sqlite/SQLiteDebug$PagerStats;)V",
+            (void*) getPagerStats },
+    { "getHeapSize", "()J", (void*) getHeapSize },
+    { "getHeapAllocatedSize", "()J", (void*) getHeapAllocatedSize },
+    { "getHeapFreeSize", "()J", (void*) getHeapFreeSize },
+    { "getHeapDirtyPages", "([I)V", (void*) getHeapDirtyPages },
+};
+
+int register_android_database_SQLiteDebug(JNIEnv *env)
+{
+    jclass clazz;
+
+    clazz = env->FindClass("android/database/sqlite/SQLiteDebug$PagerStats");
+    if (clazz == NULL) {
+        LOGE("Can't find android/database/sqlite/SQLiteDebug$PagerStats");
+        return -1;
+    }
+
+    gTotalBytesField = env->GetFieldID(clazz, "totalBytes", "J");
+    if (gTotalBytesField == NULL) {
+        LOGE("Can't find totalBytes");
+        return -1;
+    }
+
+    gReferencedBytesField = env->GetFieldID(clazz, "referencedBytes", "J");
+    if (gReferencedBytesField == NULL) {
+        LOGE("Can't find referencedBytes");
+        return -1;
+    }
+
+    gDbBytesField = env->GetFieldID(clazz, "databaseBytes", "J");
+    if (gDbBytesField == NULL) {
+        LOGE("Can't find databaseBytes");
+        return -1;
+    }
+
+    gNumPagersField = env->GetFieldID(clazz, "numPagers", "I");
+    if (gNumPagersField == NULL) {
+        LOGE("Can't find numPagers");
+        return -1;
+    }
+
+    return jniRegisterNativeMethods(env, "android/database/sqlite/SQLiteDebug",
+            gMethods, NELEM(gMethods));
+}
+
+} // namespace android
diff --git a/core/jni/android_database_SQLiteProgram.cpp b/core/jni/android_database_SQLiteProgram.cpp
new file mode 100644
index 0000000..7bda004
--- /dev/null
+++ b/core/jni/android_database_SQLiteProgram.cpp
@@ -0,0 +1,240 @@
+/*
+ * Copyright (C) 2006-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.
+ */
+
+#undef LOG_TAG
+#define LOG_TAG "Cursor"
+
+#include <jni.h>
+#include <JNIHelp.h>
+#include <android_runtime/AndroidRuntime.h>
+
+#include <sqlite3.h>
+
+#include <utils/Log.h>
+
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "sqlite3_exception.h"
+
+
+namespace android {
+
+static jfieldID gHandleField;
+static jfieldID gStatementField;
+
+
+#define GET_STATEMENT(env, object) \
+        (sqlite3_stmt *)env->GetIntField(object, gStatementField)
+#define GET_HANDLE(env, object) \
+        (sqlite3 *)env->GetIntField(object, gHandleField)
+
+
+sqlite3_stmt * compile(JNIEnv* env, jobject object,
+                       sqlite3 * handle, jstring sqlString)
+{
+    int err;
+    jchar const * sql;
+    jsize sqlLen;
+    sqlite3_stmt * statement = GET_STATEMENT(env, object);
+
+    // Make sure not to leak the statement if it already exists
+    if (statement != NULL) {
+        sqlite3_finalize(statement);
+        env->SetIntField(object, gStatementField, 0);
+    }
+
+    // Compile the SQL
+    sql = env->GetStringChars(sqlString, NULL);
+    sqlLen = env->GetStringLength(sqlString);
+    err = sqlite3_prepare16_v2(handle, sql, sqlLen * 2, &statement, NULL);
+    env->ReleaseStringChars(sqlString, sql);
+
+    if (err == SQLITE_OK) {
+        // Store the statement in the Java object for future calls
+        LOGV("Prepared statement %p on %p", statement, handle);
+        env->SetIntField(object, gStatementField, (int)statement);
+        return statement;
+    } else {
+        // Error messages like 'near ")": syntax error' are not
+        // always helpful enough, so construct an error string that
+        // includes the query itself.
+        const char *query = env->GetStringUTFChars(sqlString, NULL);
+        char *message = (char*) malloc(strlen(query) + 50);
+        if (message) {
+            strcpy(message, ", while compiling: "); // less than 50 chars
+            strcat(message, query);
+        }
+        env->ReleaseStringUTFChars(sqlString, query);
+        throw_sqlite3_exception(env, handle, message);
+        free(message);
+        return NULL;
+    }
+}
+
+static void native_compile(JNIEnv* env, jobject object, jstring sqlString)
+{
+    compile(env, object, GET_HANDLE(env, object), sqlString);
+}
+
+static void native_bind_null(JNIEnv* env, jobject object,
+                             jint index)
+{
+    int err;
+    sqlite3_stmt * statement = GET_STATEMENT(env, object);
+
+    err = sqlite3_bind_null(statement, index);
+    if (err != SQLITE_OK) {
+        char buf[32];
+        sprintf(buf, "handle %p", statement);
+        throw_sqlite3_exception(env, GET_HANDLE(env, object), buf);
+        return;
+    }
+}
+
+static void native_bind_long(JNIEnv* env, jobject object,
+                             jint index, jlong value)
+{
+    int err;
+    sqlite3_stmt * statement = GET_STATEMENT(env, object);
+
+    err = sqlite3_bind_int64(statement, index, value);
+    if (err != SQLITE_OK) {
+        char buf[32];
+        sprintf(buf, "handle %p", statement);
+        throw_sqlite3_exception(env, GET_HANDLE(env, object), buf);
+        return;
+    }
+}
+
+static void native_bind_double(JNIEnv* env, jobject object,
+                             jint index, jdouble value)
+{
+    int err;
+    sqlite3_stmt * statement = GET_STATEMENT(env, object);
+
+    err = sqlite3_bind_double(statement, index, value);
+    if (err != SQLITE_OK) {
+        char buf[32];
+        sprintf(buf, "handle %p", statement);
+        throw_sqlite3_exception(env, GET_HANDLE(env, object), buf);
+        return;
+    }
+}
+
+static void native_bind_string(JNIEnv* env, jobject object,
+                               jint index, jstring sqlString)
+{
+    int err;
+    jchar const * sql;
+    jsize sqlLen;
+    sqlite3_stmt * statement= GET_STATEMENT(env, object);
+
+    sql = env->GetStringChars(sqlString, NULL);
+    sqlLen = env->GetStringLength(sqlString);
+    err = sqlite3_bind_text16(statement, index, sql, sqlLen * 2, SQLITE_TRANSIENT);
+    env->ReleaseStringChars(sqlString, sql);
+    if (err != SQLITE_OK) {
+        char buf[32];
+        sprintf(buf, "handle %p", statement);
+        throw_sqlite3_exception(env, GET_HANDLE(env, object), buf);
+        return;
+    }
+}
+
+static void native_bind_blob(JNIEnv* env, jobject object,
+                               jint index, jbyteArray value)
+{
+    int err;
+    jchar const * sql;
+    jsize sqlLen;
+    sqlite3_stmt * statement= GET_STATEMENT(env, object);
+
+    jint len = env->GetArrayLength(value);    
+    jbyte * bytes = env->GetByteArrayElements(value, NULL);
+
+    err = sqlite3_bind_blob(statement, index, bytes, len, SQLITE_TRANSIENT);
+    env->ReleaseByteArrayElements(value, bytes, JNI_ABORT);
+
+    if (err != SQLITE_OK) {
+        char buf[32];
+        sprintf(buf, "statement %p", statement);
+        throw_sqlite3_exception(env, GET_HANDLE(env, object), buf);
+        return;
+    }
+}
+
+static void native_clear_bindings(JNIEnv* env, jobject object)
+{
+    int err;
+    sqlite3_stmt * statement = GET_STATEMENT(env, object);
+
+    err = sqlite3_clear_bindings(statement);
+    if (err != SQLITE_OK) {
+        throw_sqlite3_exception(env, GET_HANDLE(env, object));
+        return;
+    }
+}
+
+static void native_finalize(JNIEnv* env, jobject object)
+{
+    int err;
+    sqlite3_stmt * statement = GET_STATEMENT(env, object);
+
+    if (statement != NULL) {
+        sqlite3_finalize(statement);
+        env->SetIntField(object, gStatementField, 0);
+    }
+}
+
+
+static JNINativeMethod sMethods[] =
+{
+     /* name, signature, funcPtr */
+    {"native_compile", "(Ljava/lang/String;)V", (void *)native_compile},
+    {"native_bind_null", "(I)V", (void *)native_bind_null},
+    {"native_bind_long", "(IJ)V", (void *)native_bind_long},
+    {"native_bind_double", "(ID)V", (void *)native_bind_double},
+    {"native_bind_string", "(ILjava/lang/String;)V", (void *)native_bind_string},
+    {"native_bind_blob", "(I[B)V", (void *)native_bind_blob},    
+    {"native_clear_bindings", "()V", (void *)native_clear_bindings},
+    {"native_finalize", "()V", (void *)native_finalize},
+};
+
+int register_android_database_SQLiteProgram(JNIEnv * env)
+{
+    jclass clazz;
+
+    clazz = env->FindClass("android/database/sqlite/SQLiteProgram");
+    if (clazz == NULL) {
+        LOGE("Can't find android/database/sqlite/SQLiteProgram");
+        return -1;
+    }
+
+    gHandleField = env->GetFieldID(clazz, "nHandle", "I");
+    gStatementField = env->GetFieldID(clazz, "nStatement", "I");
+
+    if (gHandleField == NULL || gStatementField == NULL) {
+        LOGE("Error locating fields");
+        return -1;
+    }
+
+    return AndroidRuntime::registerNativeMethods(env,
+        "android/database/sqlite/SQLiteProgram", sMethods, NELEM(sMethods));
+}
+
+} // namespace android
diff --git a/core/jni/android_database_SQLiteQuery.cpp b/core/jni/android_database_SQLiteQuery.cpp
new file mode 100644
index 0000000..44271683
--- /dev/null
+++ b/core/jni/android_database_SQLiteQuery.cpp
@@ -0,0 +1,366 @@
+/*
+ * 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.
+ */
+
+#undef LOG_TAG
+#define LOG_TAG "Cursor"
+
+#include <jni.h>
+#include <JNIHelp.h>
+#include <android_runtime/AndroidRuntime.h>
+
+#include <sqlite3.h>
+
+#include <utils/Log.h>
+
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "CursorWindow.h"
+#include "sqlite3_exception.h"
+
+
+namespace android {
+
+sqlite3_stmt * compile(JNIEnv* env, jobject object,
+                       sqlite3 * handle, jstring sqlString);
+
+// From android_database_CursorWindow.cpp
+CursorWindow * get_window_from_object(JNIEnv * env, jobject javaWindow);
+
+static jfieldID gHandleField;
+static jfieldID gStatementField;
+
+
+#define GET_STATEMENT(env, object) \
+        (sqlite3_stmt *)env->GetIntField(object, gStatementField)
+#define GET_HANDLE(env, object) \
+        (sqlite3 *)env->GetIntField(object, gHandleField)
+
+static int skip_rows(sqlite3_stmt *statement, int maxRows) {
+    int retryCount = 0;
+    for (int i = 0; i < maxRows; i++) {
+        int err = sqlite3_step(statement);
+        if (err == SQLITE_ROW){
+            // do nothing
+        } else if (err == SQLITE_DONE) {
+            return i;
+        } else if (err == SQLITE_LOCKED || err == SQLITE_BUSY) {
+            // The table is locked, retry
+            LOG_WINDOW("Database locked, retrying");
+           if (retryCount > 50) {
+                LOGE("Bailing on database busy rety");
+                break;
+            }
+            // Sleep to give the thread holding the lock a chance to finish
+            usleep(1000);
+            retryCount++;
+            continue;
+        } else {
+            return -1;
+        }
+    }
+    LOGD("skip_rows row %d", maxRows);
+    return maxRows;
+}
+
+static int finish_program_and_get_row_count(sqlite3_stmt *statement) {
+    int numRows = 0;
+    int retryCount = 0;
+    while (true) {
+        int err = sqlite3_step(statement);
+        if (err == SQLITE_ROW){
+            numRows++;
+        } else if (err == SQLITE_LOCKED || err == SQLITE_BUSY) {
+            // The table is locked, retry
+            LOG_WINDOW("Database locked, retrying");
+            if (retryCount > 50) {
+                LOGE("Bailing on database busy rety");
+                break;
+            }
+            // Sleep to give the thread holding the lock a chance to finish
+            usleep(1000);
+            retryCount++;
+            continue;
+        } else {
+            // no need to throw exception
+            break;
+        }
+    }
+    sqlite3_reset(statement);
+    LOGD("finish_program_and_get_row_count row %d", numRows);
+    return numRows;
+}
+
+static jint native_fill_window(JNIEnv* env, jobject object, jobject javaWindow,
+                               jint startPos, jint offsetParam, jint maxRead, jint lastPos)
+{
+    int err;
+    sqlite3_stmt * statement = GET_STATEMENT(env, object);
+    int numRows = lastPos;
+    maxRead += lastPos;
+    int numColumns;
+    int retryCount;
+    int boundParams;
+    CursorWindow * window;
+    
+    if (statement == NULL) {
+        LOGE("Invalid statement in fillWindow()");
+        jniThrowException(env, "java/lang/IllegalStateException",
+                          "Attempting to access a deactivated, closed, or empty cursor");
+        return 0;
+    }
+
+    // Only do the binding if there is a valid offsetParam. If no binding needs to be done
+    // offsetParam will be set to 0, an invliad value.
+    if(offsetParam > 0) {
+        // Bind the offset parameter, telling the program which row to start with
+        err = sqlite3_bind_int(statement, offsetParam, startPos);
+        if (err != SQLITE_OK) {
+            LOGE("Unable to bind offset position, offsetParam = %d", offsetParam);
+            jniThrowException(env, "java/lang/IllegalArgumentException",
+                              sqlite3_errmsg(GET_HANDLE(env, object)));
+            return 0;
+        }
+        LOG_WINDOW("Bound to startPos %d", startPos);
+    } else {
+        LOG_WINDOW("Not binding to startPos %d", startPos);
+    }
+
+    // Get the native window
+    window = get_window_from_object(env, javaWindow);
+    if (!window) {
+        LOGE("Invalid CursorWindow");
+        jniThrowException(env, "java/lang/IllegalArgumentException",
+                          "Bad CursorWindow");
+        return 0;
+    }
+    LOG_WINDOW("Window: numRows = %d, size = %d, freeSpace = %d", window->getNumRows(), window->size(), window->freeSpace());
+
+    numColumns = sqlite3_column_count(statement);
+    if (!window->setNumColumns(numColumns)) {
+        LOGE("Failed to change column count from %d to %d", window->getNumColumns(), numColumns);
+        jniThrowException(env, "java/lang/IllegalStateException", "numColumns mismatch");
+        return 0;
+    }
+
+    retryCount = 0;
+    if (startPos > 0) {
+        int num = skip_rows(statement, startPos);
+        if (num < 0) {
+            throw_sqlite3_exception(env, GET_HANDLE(env, object));
+            return 0;
+        } else if (num < startPos) {
+            LOGE("startPos %d > actual rows %d", startPos, num);
+            return num;
+        }
+    } 
+    
+    while(startPos != 0 || numRows < maxRead) {
+        err = sqlite3_step(statement);
+        if (err == SQLITE_ROW) {
+            LOG_WINDOW("\nStepped statement %p to row %d", statement, startPos + numRows);
+            retryCount = 0;
+
+            // Allocate a new field directory for the row. This pointer is not reused
+            // since it mey be possible for it to be relocated on a call to alloc() when
+            // the field data is being allocated.
+            {
+                field_slot_t * fieldDir = window->allocRow();
+                if (!fieldDir) {
+                    LOGE("Failed allocating fieldDir at startPos %d row %d", startPos, numRows);
+                    return startPos + numRows + finish_program_and_get_row_count(statement) + 1;
+                }
+            }
+
+            // Pack the row into the window
+            int i;
+            for (i = 0; i < numColumns; i++) {
+                int type = sqlite3_column_type(statement, i);
+                if (type == SQLITE_TEXT) {
+                    // TEXT data
+#if WINDOW_STORAGE_UTF8
+                    uint8_t const * text = (uint8_t const *)sqlite3_column_text(statement, i);
+                    // SQLite does not include the NULL terminator in size, but does
+                    // ensure all strings are NULL terminated, so increase size by
+                    // one to make sure we store the terminator.
+                    size_t size = sqlite3_column_bytes(statement, i) + 1;
+#else
+                    uint8_t const * text = (uint8_t const *)sqlite3_column_text16(statement, i);
+                    size_t size = sqlite3_column_bytes16(statement, i);
+#endif
+                    int offset = window->alloc(size);
+                    if (!offset) {
+                        window->freeLastRow();
+                        LOGE("Failed allocating %u bytes for text/blob at %d,%d", size,
+                                   startPos + numRows, i);
+                        return startPos + numRows + finish_program_and_get_row_count(statement) + 1;
+                    }
+
+                    window->copyIn(offset, text, size);
+
+                    // This must be updated after the call to alloc(), since that
+                    // may move the field around in the window
+                    field_slot_t * fieldSlot = window->getFieldSlot(numRows, i);
+                    fieldSlot->type = FIELD_TYPE_STRING;
+                    fieldSlot->data.buffer.offset = offset;
+                    fieldSlot->data.buffer.size = size;
+
+                    LOG_WINDOW("%d,%d is TEXT with %u bytes", startPos + numRows, i, size);
+                } else if (type == SQLITE_INTEGER) {
+                    // INTEGER data
+                    int64_t value = sqlite3_column_int64(statement, i);
+                    if (!window->putLong(numRows, i, value)) {
+                        window->freeLastRow();
+                        LOGE("Failed allocating space for a long in column %d", i);
+                        return startPos + numRows + finish_program_and_get_row_count(statement) + 1;
+                    }
+                    LOG_WINDOW("%d,%d is INTEGER 0x%016llx", startPos + numRows, i, value);
+                } else if (type == SQLITE_FLOAT) {
+                    // FLOAT data
+                    double value = sqlite3_column_double(statement, i);
+                    if (!window->putDouble(numRows, i, value)) {
+                        window->freeLastRow();
+                        LOGE("Failed allocating space for a double in column %d", i);
+                        return startPos + numRows + finish_program_and_get_row_count(statement) + 1;
+                    }
+                    LOG_WINDOW("%d,%d is FLOAT %lf", startPos + numRows, i, value);
+                } else if (type == SQLITE_BLOB) {
+                    // BLOB data
+                    uint8_t const * blob = (uint8_t const *)sqlite3_column_blob(statement, i);
+                    size_t size = sqlite3_column_bytes16(statement, i);
+                    int offset = window->alloc(size);
+                    if (!offset) {
+                        window->freeLastRow();
+                        LOGE("Failed allocating %u bytes for blob at %d,%d", size,
+                                   startPos + numRows, i);
+                        return startPos + numRows + finish_program_and_get_row_count(statement) + 1;
+                    }
+
+                    window->copyIn(offset, blob, size);
+
+                    // This must be updated after the call to alloc(), since that
+                    // may move the field around in the window
+                    field_slot_t * fieldSlot = window->getFieldSlot(numRows, i);
+                    fieldSlot->type = FIELD_TYPE_BLOB;
+                    fieldSlot->data.buffer.offset = offset;
+                    fieldSlot->data.buffer.size = size;
+
+                    LOG_WINDOW("%d,%d is Blob with %u bytes @ %d", startPos + numRows, i, size, offset);
+                } else if (type == SQLITE_NULL) {
+                    // NULL field
+                    window->putNull(numRows, i);
+
+                    LOG_WINDOW("%d,%d is NULL", startPos + numRows, i);
+                } else {
+                    // Unknown data
+                    LOGE("Unknown column type when filling database window");
+                    throw_sqlite3_exception(env, "Unknown column type when filling window");
+                    break;
+                }
+            }
+
+            if (i < numColumns) {
+                // Not all the fields fit in the window
+                // Unknown data error happened
+                break;
+            }
+
+            // Mark the row as complete in the window
+            numRows++;
+        } else if (err == SQLITE_DONE) {
+            // All rows processed, bail
+            LOG_WINDOW("Processed all rows");
+            break;
+        } else if (err == SQLITE_LOCKED || err == SQLITE_BUSY) {
+            // The table is locked, retry
+            LOG_WINDOW("Database locked, retrying");
+            if (retryCount > 50) {
+                LOGE("Bailing on database busy rety");
+                break;
+            }
+
+            // Sleep to give the thread holding the lock a chance to finish
+            usleep(1000);
+
+            retryCount++;
+            continue;
+        } else {
+            throw_sqlite3_exception(env, GET_HANDLE(env, object));
+            break;
+        }
+    }
+
+    LOG_WINDOW("Resetting statement %p after fetching %d rows in %d bytes\n\n\n\n", statement,
+            numRows, window->size() - window->freeSpace());
+//    LOGI("Filled window with %d rows in %d bytes", numRows, window->size() - window->freeSpace());
+    if (err == SQLITE_ROW) {
+        return -1;
+    } else {
+        sqlite3_reset(statement);
+        return startPos + numRows;
+    }
+}
+
+static jint native_column_count(JNIEnv* env, jobject object)
+{
+    sqlite3_stmt * statement = GET_STATEMENT(env, object);
+
+    return sqlite3_column_count(statement);
+}
+
+static jstring native_column_name(JNIEnv* env, jobject object, jint columnIndex)
+{
+    sqlite3_stmt * statement = GET_STATEMENT(env, object);
+    char const * name;
+
+    name = sqlite3_column_name(statement, columnIndex);
+
+    return env->NewStringUTF(name);
+}
+
+
+static JNINativeMethod sMethods[] =
+{
+     /* name, signature, funcPtr */
+    {"native_fill_window", "(Landroid/database/CursorWindow;IIII)I", (void *)native_fill_window},
+    {"native_column_count", "()I", (void*)native_column_count},
+    {"native_column_name", "(I)Ljava/lang/String;", (void *)native_column_name},
+};
+
+int register_android_database_SQLiteQuery(JNIEnv * env)
+{
+    jclass clazz;
+
+    clazz = env->FindClass("android/database/sqlite/SQLiteQuery");
+    if (clazz == NULL) {
+        LOGE("Can't find android/database/sqlite/SQLiteQuery");
+        return -1;
+    }
+
+    gHandleField = env->GetFieldID(clazz, "nHandle", "I");
+    gStatementField = env->GetFieldID(clazz, "nStatement", "I");
+
+    if (gHandleField == NULL || gStatementField == NULL) {
+        LOGE("Error locating fields");
+        return -1;
+    }
+
+    return AndroidRuntime::registerNativeMethods(env,
+        "android/database/sqlite/SQLiteQuery", sMethods, NELEM(sMethods));
+}
+
+} // namespace android
diff --git a/core/jni/android_database_SQLiteStatement.cpp b/core/jni/android_database_SQLiteStatement.cpp
new file mode 100644
index 0000000..ff2ed5d
--- /dev/null
+++ b/core/jni/android_database_SQLiteStatement.cpp
@@ -0,0 +1,149 @@
+/* //device/libs/android_runtime/android_database_SQLiteCursor.cpp
+**
+** Copyright 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.
+*/
+
+#undef LOG_TAG
+#define LOG_TAG "Cursor"
+
+#include <jni.h>
+#include <JNIHelp.h>
+#include <android_runtime/AndroidRuntime.h>
+
+#include <sqlite3.h>
+
+#include <utils/Log.h>
+
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "sqlite3_exception.h"
+
+namespace android {
+
+
+sqlite3_stmt * compile(JNIEnv* env, jobject object,
+                       sqlite3 * handle, jstring sqlString);
+
+static jfieldID gHandleField;
+static jfieldID gStatementField;
+
+
+#define GET_STATEMENT(env, object) \
+        (sqlite3_stmt *)env->GetIntField(object, gStatementField)
+#define GET_HANDLE(env, object) \
+        (sqlite3 *)env->GetIntField(object, gHandleField)
+
+
+static void native_execute(JNIEnv* env, jobject object)
+{
+    int err;
+    sqlite3 * handle = GET_HANDLE(env, object);
+    sqlite3_stmt * statement = GET_STATEMENT(env, object);
+
+    // Execute the statement
+    err = sqlite3_step(statement);
+
+    // Throw an exception if an error occured
+    if (err != SQLITE_DONE) {
+        throw_sqlite3_exception_errcode(env, err, sqlite3_errmsg(handle));
+    }
+
+    // Reset the statment so it's ready to use again
+    sqlite3_reset(statement);
+}
+
+static jlong native_1x1_long(JNIEnv* env, jobject object)
+{
+    int err;
+    sqlite3 * handle = GET_HANDLE(env, object);
+    sqlite3_stmt * statement = GET_STATEMENT(env, object);
+    jlong value = -1;
+
+    // Execute the statement
+    err = sqlite3_step(statement);
+
+    // Handle the result
+    if (err == SQLITE_ROW) {
+        // No errors, read the data and return it
+        value = sqlite3_column_int64(statement, 0);
+    } else {
+        throw_sqlite3_exception_errcode(env, err, sqlite3_errmsg(handle));
+    }
+
+    // Reset the statment so it's ready to use again
+    sqlite3_reset(statement);
+
+    return value;
+}
+
+static jstring native_1x1_string(JNIEnv* env, jobject object)
+{
+    int err;
+    sqlite3 * handle = GET_HANDLE(env, object);
+    sqlite3_stmt * statement = GET_STATEMENT(env, object);
+    jstring value = NULL;
+
+    // Execute the statement
+    err = sqlite3_step(statement);
+
+    // Handle the result
+    if (err == SQLITE_ROW) {
+        // No errors, read the data and return it
+        char const * text = (char const *)sqlite3_column_text(statement, 0);
+        value = env->NewStringUTF(text);
+    } else {
+        throw_sqlite3_exception_errcode(env, err, sqlite3_errmsg(handle));
+    }
+
+    // Reset the statment so it's ready to use again
+    sqlite3_reset(statement);
+
+    return value;
+}
+
+
+static JNINativeMethod sMethods[] =
+{
+     /* name, signature, funcPtr */
+    {"native_execute", "()V", (void *)native_execute},
+    {"native_1x1_long", "()J", (void *)native_1x1_long},
+    {"native_1x1_string", "()Ljava/lang/String;", (void *)native_1x1_string},
+};
+
+int register_android_database_SQLiteStatement(JNIEnv * env)
+{
+    jclass clazz;
+
+    clazz = env->FindClass("android/database/sqlite/SQLiteStatement");
+    if (clazz == NULL) {
+        LOGE("Can't find android/database/sqlite/SQLiteStatement");
+        return -1;
+    }
+
+    gHandleField = env->GetFieldID(clazz, "nHandle", "I");
+    gStatementField = env->GetFieldID(clazz, "nStatement", "I");
+
+    if (gHandleField == NULL || gStatementField == NULL) {
+        LOGE("Error locating fields");
+        return -1;
+    }
+
+    return AndroidRuntime::registerNativeMethods(env,
+        "android/database/sqlite/SQLiteStatement", sMethods, NELEM(sMethods));
+}
+
+} // namespace android
diff --git a/core/jni/android_ddm_DdmHandleNativeHeap.cpp b/core/jni/android_ddm_DdmHandleNativeHeap.cpp
new file mode 100644
index 0000000..c3b4e3c
--- /dev/null
+++ b/core/jni/android_ddm_DdmHandleNativeHeap.cpp
@@ -0,0 +1,144 @@
+/* //device/libs/android_runtime/android_ddm_DdmHandleNativeHeap.cpp
+**
+** Copyright 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.
+*/
+
+#undef LOG_TAG
+#define LOG_TAG "DdmHandleNativeHeap"
+
+#include <JNIHelp.h>
+#include <jni.h>
+#include <android_runtime/AndroidRuntime.h>
+
+#include <utils/Log.h>
+
+#include <fcntl.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#if defined(__arm__)
+extern "C" void get_malloc_leak_info(uint8_t** info, size_t* overallSize, 
+        size_t* infoSize, size_t* totalMemory, size_t* backtraceSize);
+        
+extern "C" void free_malloc_leak_info(uint8_t* info);
+#endif
+
+#define MAPS_FILE_SIZE 65 * 1024
+
+struct Header {
+    size_t mapSize;
+    size_t allocSize;
+    size_t allocInfoSize;
+    size_t totalMemory;
+    size_t backtraceSize;
+};
+
+namespace android {
+
+/*
+ * Retrieve the native heap information and the info from /proc/<self>/maps,
+ * copy them into a byte[] with a "struct Header" that holds data offsets,
+ * and return the array.
+ */
+static jbyteArray getLeakInfo(JNIEnv *env, jobject clazz)
+{
+#if defined(__arm__)
+    // get the info in /proc/[pid]/map
+    Header header;
+    memset(&header, 0, sizeof(header));
+
+    pid_t pid = getpid();
+
+    char path[FILENAME_MAX];
+    sprintf(path, "/proc/%d/maps", pid);
+
+    struct stat sb;
+    int ret = stat(path, &sb);
+
+    uint8_t* mapsFile = NULL;
+    if (ret == 0) {
+        mapsFile = (uint8_t*)malloc(MAPS_FILE_SIZE);
+        int fd = open(path, O_RDONLY);
+    
+        if (mapsFile != NULL && fd != -1) {
+            int amount = 0;
+            do {
+                uint8_t* ptr = mapsFile + header.mapSize;
+                amount = read(fd, ptr, MAPS_FILE_SIZE);
+                if (amount <= 0) {
+                    if (errno != EINTR)
+                        break; 
+                    else
+                        continue;
+                }
+                header.mapSize += amount;
+            } while (header.mapSize < MAPS_FILE_SIZE);
+            
+            LOGD("**** read %d bytes from '%s'", (int) header.mapSize, path);
+        }
+    }
+
+    uint8_t* allocBytes;
+    get_malloc_leak_info(&allocBytes, &header.allocSize, &header.allocInfoSize, 
+            &header.totalMemory, &header.backtraceSize);
+
+    jbyte* bytes = NULL;
+    jbyte* ptr = NULL;
+    jbyteArray array = env->NewByteArray(sizeof(Header) + header.mapSize + header.allocSize);
+    if (array == NULL) {
+        goto done;
+    }
+
+    bytes = env->GetByteArrayElements(array, NULL);
+    ptr = bytes;
+
+//    LOGD("*** mapSize: %d allocSize: %d allocInfoSize: %d totalMemory: %d", 
+//            header.mapSize, header.allocSize, header.allocInfoSize, header.totalMemory);
+
+    memcpy(ptr, &header, sizeof(header));
+    ptr += sizeof(header);
+    
+    if (header.mapSize > 0 && mapsFile != NULL) {
+        memcpy(ptr, mapsFile, header.mapSize);
+        ptr += header.mapSize;
+    }
+    
+    memcpy(ptr, allocBytes, header.allocSize);
+    env->ReleaseByteArrayElements(array, bytes, 0);
+
+done:
+    if (mapsFile != NULL) {
+        free(mapsFile);
+    }
+    // free the info up!
+    free_malloc_leak_info(allocBytes);
+
+    return array;
+#else
+    return NULL;
+#endif
+}
+
+static JNINativeMethod method_table[] = {
+    { "getLeakInfo", "()[B", (void*)getLeakInfo },
+};
+
+int register_android_ddm_DdmHandleNativeHeap(JNIEnv *env)
+{
+    return AndroidRuntime::registerNativeMethods(env, "android/ddm/DdmHandleNativeHeap", method_table, NELEM(method_table));
+}
+
+};
diff --git a/core/jni/android_debug_JNITest.cpp b/core/jni/android_debug_JNITest.cpp
new file mode 100644
index 0000000..f14201e
--- /dev/null
+++ b/core/jni/android_debug_JNITest.cpp
@@ -0,0 +1,119 @@
+/* //device/libs/android_runtime/android_debug_JNITest.cpp
+**
+** Copyright 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.
+*/
+
+#define LOG_TAG "DebugJNI"
+
+#include "jni.h"
+#include "nativehelper/JNIHelp.h"
+#include "utils/Log.h"
+#include "utils/misc.h"
+//#include "android_runtime/AndroidRuntime.h"
+
+namespace android {
+
+/*
+ * Implements:
+ *  native int part1(int intArg, double doubleArg, String stringArg,
+ *      int[] arrayArg)
+ */
+static jint android_debug_JNITest_part1(JNIEnv* env, jobject object,
+    jint intArg, jdouble doubleArg, jstring stringArg, jobjectArray arrayArg)
+{
+    jclass clazz;
+    jmethodID part2id;
+    jsize arrayLen;
+    jint arrayVal;
+    int result = -2;
+
+    LOGI("JNI test: in part1, intArg=%d, doubleArg=%.3f\n", intArg, doubleArg);
+
+    /* find "int part2(double doubleArg, int fromArray, String stringArg)" */
+    clazz = env->GetObjectClass(object);
+    part2id = env->GetMethodID(clazz,
+                "part2", "(DILjava/lang/String;)I");
+    if (part2id == NULL) {
+        LOGE("JNI test: unable to find part2\n");
+        return -1;
+    }
+
+    /* get the length of the array */
+    arrayLen = env->GetArrayLength(arrayArg);
+    LOGI("  array size is %d\n", arrayLen);
+
+    /*
+     * Get the last element in the array.
+     * Use the Get<type>ArrayElements functions instead if you need access
+     * to multiple elements.
+     */
+    arrayVal = (int) env->GetObjectArrayElement(arrayArg, arrayLen-1);
+    LOGI("  array val is %d\n", arrayVal);
+
+    /* call this->part2 */
+    result = env->CallIntMethod(object, part2id,
+        doubleArg, arrayVal, stringArg);
+
+    return result;
+}
+
+/*
+ * Implements:
+ *  private static native int part3(String stringArg);
+ */
+static jint android_debug_JNITest_part3(JNIEnv* env, jclass clazz,
+    jstring stringArg)
+{
+    const char* utfChars;
+    jboolean isCopy;
+
+    LOGI("JNI test: in part3\n");
+
+    utfChars = env->GetStringUTFChars(stringArg, &isCopy);
+
+    LOGI("  String is '%s', isCopy=%d\n", (const char*) utfChars, isCopy);
+
+    env->ReleaseStringUTFChars(stringArg, utfChars);
+
+    return 2000;
+}
+
+/*
+ * JNI registration.
+ */
+static JNINativeMethod gMethods[] = {
+    /* name, signature, funcPtr */
+    { "part1",      "(IDLjava/lang/String;[I)I",
+            (void*) android_debug_JNITest_part1 },
+    { "part3",      "(Ljava/lang/String;)I",
+            (void*) android_debug_JNITest_part3 },
+};
+int register_android_debug_JNITest(JNIEnv* env)
+{
+    return jniRegisterNativeMethods(env, "android/debug/JNITest",
+        gMethods, NELEM(gMethods));
+}
+
+#if 0
+/* trampoline into C++ */
+extern "C"
+int register_android_debug_JNITest_C(JNIEnv* env)
+{
+    return android::register_android_debug_JNITest(env);
+}
+#endif
+
+}; // namespace android
+
diff --git a/core/jni/android_graphics_PixelFormat.cpp b/core/jni/android_graphics_PixelFormat.cpp
new file mode 100644
index 0000000..0643622
--- /dev/null
+++ b/core/jni/android_graphics_PixelFormat.cpp
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2007 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.
+ */
+
+#include <stdio.h>
+#include <assert.h>
+
+#include <ui/PixelFormat.h>
+
+#include "jni.h"
+#include <android_runtime/AndroidRuntime.h>
+#include <utils/misc.h>
+
+// ----------------------------------------------------------------------------
+
+namespace android {
+
+// ----------------------------------------------------------------------------
+
+struct offsets_t {
+    jfieldID bytesPerPixel;
+    jfieldID bitsPerPixel;
+};
+
+static offsets_t offsets;
+
+static void doThrow(JNIEnv* env, const char* exc, const char* msg = NULL)
+{
+    jclass npeClazz = env->FindClass(exc);
+    env->ThrowNew(npeClazz, msg);
+}
+
+// ----------------------------------------------------------------------------
+
+static void android_graphics_getPixelFormatInfo(
+        JNIEnv* env, jobject clazz, jint format, jobject pixelFormatObject)
+{
+    PixelFormatInfo info;
+    status_t err = getPixelFormatInfo(format, &info);
+    if (err < 0) {
+        doThrow(env, "java/lang/IllegalArgumentException");
+        return;
+    }
+    env->SetIntField(pixelFormatObject, offsets.bytesPerPixel, info.bytesPerPixel);
+    env->SetIntField(pixelFormatObject, offsets.bitsPerPixel,  info.bitsPerPixel);
+}
+// ----------------------------------------------------------------------------
+
+const char* const kClassPathName = "android/graphics/PixelFormat";
+
+static void nativeClassInit(JNIEnv* env, jclass clazz);
+
+static JNINativeMethod gMethods[] = {
+    {   "nativeClassInit", "()V",
+        (void*)nativeClassInit },
+	{   "getPixelFormatInfo", "(ILandroid/graphics/PixelFormat;)V",
+        (void*)android_graphics_getPixelFormatInfo
+    }
+};
+
+void nativeClassInit(JNIEnv* env, jclass clazz)
+{
+    offsets.bytesPerPixel = env->GetFieldID(clazz, "bytesPerPixel", "I");
+    offsets.bitsPerPixel  = env->GetFieldID(clazz, "bitsPerPixel", "I");    
+}
+
+int register_android_graphics_PixelFormat(JNIEnv* env)
+{
+    return AndroidRuntime::registerNativeMethods(env,
+            kClassPathName, gMethods, NELEM(gMethods));
+}
+
+};
diff --git a/core/jni/android_hardware_Camera.cpp b/core/jni/android_hardware_Camera.cpp
new file mode 100644
index 0000000..f6cd211
--- /dev/null
+++ b/core/jni/android_hardware_Camera.cpp
@@ -0,0 +1,562 @@
+/*
+**
+** Copyright 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.
+*/
+
+//#define LOG_NDEBUG 0
+#define LOG_TAG "Camera-JNI"
+#include <utils/Log.h>
+
+#include "jni.h"
+#include "JNIHelp.h"
+#include "android_runtime/AndroidRuntime.h"
+
+#include <ui/Surface.h>
+#include <ui/Camera.h>
+#include <utils/IMemory.h>
+
+using namespace android;
+
+enum CallbackMessageID {
+    kShutterCallback = 0,
+    kRawCallback = 1,
+    kJpegCallback = 2,
+    kPreviewCallback = 3,
+    kAutoFocusCallback = 4,
+    kErrorCallback = 5
+};
+
+enum CameraError {
+    kCameraErrorUnknown = 1,
+    kCameraErrorMediaServer = 100
+};
+
+
+struct fields_t {
+    jfieldID    context;
+    jfieldID    surface;
+    jmethodID   post_event;
+};
+
+static fields_t fields;
+static Mutex sLock;
+
+struct camera_context_t {
+    jobject     mCameraJObjectWeak;     // weak reference to java object
+    jclass      mCameraJClass;          // strong reference to java class
+    sp<Camera>  mCamera;                // strong reference to native object 
+};
+
+sp<Camera> get_native_camera(JNIEnv *env, jobject thiz, camera_context_t** pContext)
+{
+    sp<Camera> camera;
+    Mutex::Autolock _l(sLock);
+    camera_context_t* context = reinterpret_cast<camera_context_t*>(env->GetIntField(thiz, fields.context));
+    if (context != NULL) {
+        camera = context->mCamera;
+    }
+    LOGV("get_native_camera: context=%p, camera=%p", context, camera.get());
+    if (camera == 0) {
+        jniThrowException(env, "java/lang/RuntimeException", "Method called after release()");
+    }
+
+    if (pContext != NULL) *pContext = context;
+    return camera;
+}
+
+static void err_callback(status_t err, void *cookie)
+{
+    camera_context_t* context = reinterpret_cast<camera_context_t*>(cookie);
+    if ((context == NULL) || (context->mCamera == 0)) return;
+
+    LOGV("err_callback: context=%p, camera=%p", context, context->mCamera.get());
+
+    int error;
+    switch (err) {
+    case DEAD_OBJECT:
+        error = kCameraErrorMediaServer;
+        break;
+    default:
+        error = kCameraErrorUnknown;
+        break;
+    }
+
+    JNIEnv *env = AndroidRuntime::getJNIEnv();
+    if (env == NULL) {
+        LOGE("err_callback on dead VM");
+        return;
+    }
+    env->CallStaticVoidMethod(context->mCameraJClass, fields.post_event,
+            context->mCameraJObjectWeak, kErrorCallback, error, 0, NULL);
+}
+
+// connect to camera service
+static void android_hardware_Camera_native_setup(JNIEnv *env, jobject thiz, jobject weak_this)
+{
+    sp<Camera> camera = Camera::connect();
+
+    if (camera == NULL) {
+        jniThrowException(env, "java/lang/RuntimeException", "Out of memory");
+        return;
+    }
+
+    // make sure camera hardware is alive
+    if (camera->getStatus() != NO_ERROR) {
+        jniThrowException(env, "java/io/IOException", "Camera initialization failed");
+        return;
+    }
+
+    jclass clazz = env->GetObjectClass(thiz);
+    if (clazz == NULL) {
+        LOGE("Can't find android/hardware/Camera");
+        // XXX no idea what to throw here, can this even happen?
+        jniThrowException(env, "java/lang/Exception", NULL);
+        return;
+    }
+
+    // We use a weak reference so the Camera object can be garbage collected.
+    // The reference is only used as a proxy for callbacks.
+    camera_context_t* context = new camera_context_t;
+    context->mCameraJObjectWeak = env->NewGlobalRef(weak_this);
+    context->mCameraJClass = (jclass)env->NewGlobalRef(clazz);
+    context->mCamera = camera;
+
+    // save context in opaque field
+    env->SetIntField(thiz, fields.context, (int)context);
+
+    LOGV("native_setup: mCameraJObjectWeak=%x, camera_obj=%x, context=%p",
+            (int)context->mCameraJObjectWeak, (int)thiz, context);
+
+    // set error callback
+    camera->setErrorCallback(err_callback, context);
+}
+
+// disconnect from camera service
+// It's okay to call this when the native camera context is already null.
+// This handles the case where the user has called release() and the
+// finalizer is invoked later.
+static void android_hardware_Camera_release(JNIEnv *env, jobject thiz)
+{
+    camera_context_t* context = NULL;
+    sp<Camera> camera;
+    {
+        Mutex::Autolock _l(sLock);
+        context = reinterpret_cast<camera_context_t*>(env->GetIntField(thiz, fields.context));
+
+        // Make sure we do not attempt to callback on a deleted Java object.
+        env->SetIntField(thiz, fields.context, 0);
+    }
+
+    // clean up if release has not been called before
+    if (context != NULL) {
+        camera = context->mCamera;
+        context->mCamera.clear();
+        LOGV("native_release: context=%p camera=%p", context, camera.get());
+
+        // clear callbacks
+        if (camera != NULL) {
+            camera->setPreviewCallback(NULL, NULL, FRAME_CALLBACK_FLAG_NOOP);
+            camera->setErrorCallback(NULL, NULL);
+            camera->disconnect();
+            env->DeleteGlobalRef(context->mCameraJObjectWeak);
+            env->DeleteGlobalRef(context->mCameraJClass);
+        }
+
+        // remove context to prevent further Java access
+        delete context;
+    }
+}
+
+static void android_hardware_Camera_setPreviewDisplay(JNIEnv *env, jobject thiz, jobject jSurface)
+{
+    LOGV("setPreviewDisplay");
+    sp<Camera> camera = get_native_camera(env, thiz, NULL);
+    if (camera == 0) return;
+
+    sp<Surface> surface = reinterpret_cast<Surface*>(env->GetIntField(jSurface, fields.surface));
+    if (camera->setPreviewDisplay(surface) != NO_ERROR) {
+        jniThrowException(env, "java/io/IOException", "setPreviewDisplay failed");
+    }
+}
+
+static void preview_callback(const sp<IMemory>& mem, void *cookie)
+{
+    LOGV("preview_callback");
+    JNIEnv *env = AndroidRuntime::getJNIEnv();
+    if (env == NULL) {
+        LOGE("preview_callback on dead VM");
+        return;
+    }
+    camera_context_t* context = reinterpret_cast<camera_context_t*>(cookie);
+    if ((context == NULL) || (context->mCamera == 0)) {
+        LOGW("context or camera is NULL in preview_callback");
+        return;
+    }
+    LOGV("native_release: context=%p camera=%p", context, context->mCamera.get());
+
+    int arg1 = 0, arg2 = 0;
+    jobject obj = NULL;
+
+    ssize_t offset;
+    size_t size;
+    sp<IMemoryHeap> heap = mem->getMemory(&offset, &size);
+
+    uint8_t *data = ((uint8_t *)heap->base()) + offset;
+
+    jbyteArray array = env->NewByteArray(size);
+    if (array == NULL) {
+        LOGE("Couldn't allocate byte array for YUV data");
+        env->ExceptionClear();
+        return;
+    }
+
+    jbyte *bytes = env->GetByteArrayElements(array, NULL);
+    memcpy(bytes, data, size);
+    env->ReleaseByteArrayElements(array, bytes, 0);
+
+    obj = array;
+
+    env->CallStaticVoidMethod(context->mCameraJClass, fields.post_event,
+            context->mCameraJObjectWeak, kPreviewCallback, arg1, arg2, obj);
+    env->DeleteLocalRef(array);
+}
+
+static void android_hardware_Camera_startPreview(JNIEnv *env, jobject thiz)
+{
+    LOGV("startPreview");
+    sp<Camera> camera = get_native_camera(env, thiz, NULL);
+    if (camera == 0) return;
+
+    if (camera->startPreview() != NO_ERROR) {
+        jniThrowException(env, "java/io/IOException", "startPreview failed");
+        return;
+    }
+}
+
+static void android_hardware_Camera_stopPreview(JNIEnv *env, jobject thiz)
+{
+    LOGV("stopPreview");
+    sp<Camera> c = get_native_camera(env, thiz, NULL);
+    if (c == 0) return;
+
+    c->stopPreview();
+}
+
+static bool android_hardware_Camera_previewEnabled(JNIEnv *env, jobject thiz)
+{
+    LOGV("previewEnabled");
+    sp<Camera> c = get_native_camera(env, thiz, NULL);
+    if (c == 0) return false;
+
+    return c->previewEnabled();
+}
+
+static void android_hardware_Camera_setHasPreviewCallback(JNIEnv *env, jobject thiz, jboolean installed, jboolean oneshot)
+{
+    // Important: Only install preview_callback if the Java code has called
+    // setPreviewCallback() with a non-null value, otherwise we'd pay to memcpy
+    // each preview frame for nothing.
+    camera_context_t* context;
+    sp<Camera> camera = get_native_camera(env, thiz, &context);
+    if (camera == 0) return;
+
+    int callback_flag;
+    if (installed) {
+        callback_flag = oneshot ? FRAME_CALLBACK_FLAG_BARCODE_SCANNER : FRAME_CALLBACK_FLAG_CAMERA;
+    } else {
+        callback_flag = FRAME_CALLBACK_FLAG_NOOP;
+    }
+    camera->setPreviewCallback(installed ? preview_callback : NULL, context, callback_flag);
+}
+
+static void autofocus_callback_impl(bool success, void *cookie)
+{
+    LOGV("autoFocusCallback");
+    camera_context_t* context = reinterpret_cast<camera_context_t*>(cookie);
+
+    JNIEnv *env = AndroidRuntime::getJNIEnv();
+    if (env == NULL) {
+        LOGE("autofocus_callback on dead VM");
+        return;
+    }
+    env->CallStaticVoidMethod(context->mCameraJClass, fields.post_event,
+            context->mCameraJObjectWeak, kAutoFocusCallback, success, 0, NULL);
+}
+
+static void android_hardware_Camera_autoFocus(JNIEnv *env, jobject thiz)
+{
+    LOGV("autoFocus");
+    camera_context_t* context;
+    sp<Camera> c = get_native_camera(env, thiz, &context);
+    if (c == 0) return;
+
+    c->setAutoFocusCallback(autofocus_callback_impl, context);
+    if (c->autoFocus() != NO_ERROR) {
+        jniThrowException(env, "java/io/IOException", "autoFocus failed");
+    }
+}
+
+static void jpeg_callback(const sp<IMemory>& mem, void *cookie)
+{
+    LOGV("jpegCallback");
+    camera_context_t* context = reinterpret_cast<camera_context_t*>(cookie);
+
+    JNIEnv *env = AndroidRuntime::getJNIEnv();
+    if (env == NULL) {
+        LOGE("jpeg`_callback on dead VM");
+        return;
+    }
+    int arg1 = 0, arg2 = 0;
+    jobject obj = NULL;
+
+    if (mem == NULL) {
+        env->CallStaticVoidMethod(context->mCameraJClass, fields.post_event,
+                                  context->mCameraJObjectWeak, kJpegCallback, arg1, arg2, NULL);
+        return;
+    }
+    ssize_t offset;
+    size_t size;
+    sp<IMemoryHeap> heap = mem->getMemory(&offset, &size);
+    LOGV("jpeg_callback: mem off=%d, size=%d", offset, size);
+
+    uint8_t *heap_base = (uint8_t *)heap->base();
+    if (heap_base == NULL) {
+        LOGE("YUV heap is NULL");
+        return;
+    }
+
+    uint8_t *data = heap_base + offset;
+
+    jbyteArray array = env->NewByteArray(size);
+    if (array == NULL) {
+        LOGE("Couldn't allocate byte array for JPEG data");
+        env->ExceptionClear();
+        return;
+    }
+
+    jbyte *bytes = env->GetByteArrayElements(array, NULL);
+    memcpy(bytes, data, size);
+    env->ReleaseByteArrayElements(array, bytes, 0);
+
+    obj = array;
+
+    env->CallStaticVoidMethod(context->mCameraJClass, fields.post_event,
+                              context->mCameraJObjectWeak, kJpegCallback, arg1, arg2, obj);
+    env->DeleteLocalRef(array);
+}
+
+static void shutter_callback_impl(void *cookie)
+{
+    LOGV("shutterCallback");
+    camera_context_t* context = reinterpret_cast<camera_context_t*>(cookie);
+
+    JNIEnv *env = AndroidRuntime::getJNIEnv();
+    if (env == NULL) {
+        LOGE("shutter_callback on dead VM");
+        return;
+    }
+    env->CallStaticVoidMethod(context->mCameraJClass, fields.post_event,
+                              context->mCameraJObjectWeak, kShutterCallback, 0, 0, NULL);
+}
+
+static void raw_callback(const sp<IMemory>& mem __attribute__((unused)),
+                         void *cookie)
+{
+    LOGV("rawCallback");
+    camera_context_t* context = reinterpret_cast<camera_context_t*>(cookie);
+
+    JNIEnv *env = AndroidRuntime::getJNIEnv();
+    if (env == NULL) {
+        LOGE("raw_callback on dead VM");
+        return;
+    }
+    env->CallStaticVoidMethod(context->mCameraJClass, fields.post_event,
+                              context->mCameraJObjectWeak, kRawCallback, 0, 0, NULL);
+}
+
+static void android_hardware_Camera_takePicture(JNIEnv *env, jobject thiz)
+{
+    LOGV("takePicture");
+    camera_context_t* context;
+    sp<Camera> camera = get_native_camera(env, thiz, &context);
+    if (camera == 0) return;
+
+    camera->setShutterCallback(shutter_callback_impl, context);
+    camera->setRawCallback(raw_callback, context);
+    camera->setJpegCallback(jpeg_callback, context);
+    if (camera->takePicture() != NO_ERROR) {
+        jniThrowException(env, "java/io/IOException", "takePicture failed");
+        return;
+    }
+
+    return;
+}
+
+static void android_hardware_Camera_setParameters(JNIEnv *env, jobject thiz, jstring params)
+{
+    LOGV("setParameters");
+    sp<Camera> camera = get_native_camera(env, thiz, NULL);
+    if (camera == 0) return;
+
+    const jchar* str = env->GetStringCritical(params, 0);
+    String8 params8;
+    if (params) {
+        params8 = String8(str, env->GetStringLength(params));
+        env->ReleaseStringCritical(params, str);
+    }
+    if (camera->setParameters(params8) != NO_ERROR) {
+        jniThrowException(env, "java/lang/IllegalArgumentException", "setParameters failed");
+        return;
+    }
+}
+
+static jstring android_hardware_Camera_getParameters(JNIEnv *env, jobject thiz)
+{
+    LOGV("getParameters");
+    sp<Camera> camera = get_native_camera(env, thiz, NULL);
+    if (camera == 0) return 0;
+
+    return env->NewStringUTF(camera->getParameters().string());
+}
+
+static void android_hardware_Camera_reconnect(JNIEnv *env, jobject thiz)
+{
+    LOGV("reconnect");
+    sp<Camera> camera = get_native_camera(env, thiz, NULL);
+    if (camera == 0) return;
+
+    if (camera->reconnect() != NO_ERROR) {
+        jniThrowException(env, "java/io/IOException", "reconnect failed");
+        return;
+    }
+}
+
+static jint android_hardware_Camera_lock(JNIEnv *env, jobject thiz)
+{
+    LOGV("lock");
+    sp<Camera> camera = get_native_camera(env, thiz, NULL);
+    if (camera == 0) return INVALID_OPERATION;
+    return (jint) camera->lock();
+}
+
+static jint android_hardware_Camera_unlock(JNIEnv *env, jobject thiz)
+{
+    LOGV("unlock");
+    sp<Camera> camera = get_native_camera(env, thiz, NULL);
+    if (camera == 0) return INVALID_OPERATION;
+    return (jint) camera->unlock();
+}
+
+//-------------------------------------------------
+
+static JNINativeMethod camMethods[] = {
+  { "native_setup",
+    "(Ljava/lang/Object;)V",
+    (void*)android_hardware_Camera_native_setup },
+  { "native_release",
+    "()V",
+    (void*)android_hardware_Camera_release },
+  { "setPreviewDisplay",
+    "(Landroid/view/Surface;)V",
+    (void *)android_hardware_Camera_setPreviewDisplay },
+  { "startPreview",
+    "()V",
+    (void *)android_hardware_Camera_startPreview },
+  { "stopPreview",
+    "()V",
+    (void *)android_hardware_Camera_stopPreview },
+  { "previewEnabled",
+    "()Z",
+    (void *)android_hardware_Camera_previewEnabled },
+  { "setHasPreviewCallback",
+    "(ZZ)V",
+    (void *)android_hardware_Camera_setHasPreviewCallback },
+  { "native_autoFocus",
+    "()V",
+    (void *)android_hardware_Camera_autoFocus },
+  { "native_takePicture",
+    "()V",
+    (void *)android_hardware_Camera_takePicture },
+  { "native_setParameters",
+    "(Ljava/lang/String;)V",
+    (void *)android_hardware_Camera_setParameters },
+  { "native_getParameters",
+    "()Ljava/lang/String;",
+    (void *)android_hardware_Camera_getParameters },
+  { "reconnect",
+    "()V",
+    (void*)android_hardware_Camera_reconnect },
+  { "lock",
+    "()I",
+    (void*)android_hardware_Camera_lock },
+  { "unlock",
+    "()I",
+    (void*)android_hardware_Camera_unlock },
+};
+
+struct field {
+    const char *class_name;
+    const char *field_name;
+    const char *field_type;
+    jfieldID   *jfield;
+};
+
+static int find_fields(JNIEnv *env, field *fields, int count)
+{
+    for (int i = 0; i < count; i++) {
+        field *f = &fields[i];
+        jclass clazz = env->FindClass(f->class_name);
+        if (clazz == NULL) {
+            LOGE("Can't find %s", f->class_name);
+            return -1;
+        }
+
+        jfieldID field = env->GetFieldID(clazz, f->field_name, f->field_type);
+        if (field == NULL) {
+            LOGE("Can't find %s.%s", f->class_name, f->field_name);
+            return -1;
+        }
+
+        *(f->jfield) = field;
+    }
+
+    return 0;
+}
+
+// Get all the required offsets in java class and register native functions
+int register_android_hardware_Camera(JNIEnv *env)
+{
+    field fields_to_find[] = {
+        { "android/hardware/Camera", "mNativeContext",   "I", &fields.context },
+        { "android/view/Surface",    "mSurface",         "I", &fields.surface }
+    };
+
+    if (find_fields(env, fields_to_find, NELEM(fields_to_find)) < 0)
+        return -1;
+
+    jclass clazz = env->FindClass("android/hardware/Camera");
+    fields.post_event = env->GetStaticMethodID(clazz, "postEventFromNative",
+                                               "(Ljava/lang/Object;IIILjava/lang/Object;)V");
+    if (fields.post_event == NULL) {
+        LOGE("Can't find android/hardware/Camera.postEventFromNative");
+        return -1;
+    }
+
+
+    // Register native functions
+    return AndroidRuntime::registerNativeMethods(env, "android/hardware/Camera",
+                                              camMethods, NELEM(camMethods));
+}
+
diff --git a/core/jni/android_hardware_SensorManager.cpp b/core/jni/android_hardware_SensorManager.cpp
new file mode 100644
index 0000000..75aa458
--- /dev/null
+++ b/core/jni/android_hardware_SensorManager.cpp
@@ -0,0 +1,173 @@
+/*
+ * Copyright 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.
+ */
+
+#define LOG_TAG "Sensors"
+
+#include <hardware/sensors.h>
+
+#include "jni.h"
+#include "JNIHelp.h"
+
+
+namespace android {
+
+struct SensorOffsets
+{
+    jfieldID    name;
+    jfieldID    vendor;
+    jfieldID    version;
+    jfieldID    handle;
+    jfieldID    type;
+    jfieldID    range;
+    jfieldID    resolution;
+    jfieldID    power;
+} gSensorOffsets;
+
+/*
+ * The method below are not thread-safe and not intended to be
+ */
+
+static sensors_module_t* sSensorModule = 0;
+static sensors_data_device_t* sSensorDevice = 0;
+
+static jint
+sensors_module_init(JNIEnv *env, jclass clazz)
+{
+    int err = 0;
+    sensors_module_t const* module;
+    err = hw_get_module(SENSORS_HARDWARE_MODULE_ID, (const hw_module_t **)&module);
+    if (err == 0)
+        sSensorModule = (sensors_module_t*)module;
+    return err;
+}
+
+static jint
+sensors_module_get_next_sensor(JNIEnv *env, jobject clazz, jobject sensor, jint next)
+{
+    if (sSensorModule == NULL)
+        return 0;
+
+    SensorOffsets& sensorOffsets = gSensorOffsets;
+    const struct sensor_t* list;
+    int count = sSensorModule->get_sensors_list(sSensorModule, &list);
+    if (next >= count)
+        return -1;
+    
+    list += next;
+
+    jstring name = env->NewStringUTF(list->name);
+    jstring vendor = env->NewStringUTF(list->vendor);
+    env->SetObjectField(sensor, sensorOffsets.name,      name);
+    env->SetObjectField(sensor, sensorOffsets.vendor,    vendor);
+    env->SetIntField(sensor, sensorOffsets.version,      list->version);
+    env->SetIntField(sensor, sensorOffsets.handle,       list->handle);
+    env->SetIntField(sensor, sensorOffsets.type,         list->type);
+    env->SetFloatField(sensor, sensorOffsets.range,      list->maxRange);
+    env->SetFloatField(sensor, sensorOffsets.resolution, list->resolution);
+    env->SetFloatField(sensor, sensorOffsets.power,      list->power);
+    
+    next++;
+    return next<count ? next : 0;
+}
+
+//----------------------------------------------------------------------------
+static jint
+sensors_data_init(JNIEnv *env, jclass clazz)
+{
+    if (sSensorModule == NULL)
+        return -1;
+    int err = sensors_data_open(&sSensorModule->common, &sSensorDevice);
+    return err;
+}
+
+static jint
+sensors_data_uninit(JNIEnv *env, jclass clazz)
+{
+    int err = 0;
+    if (sSensorDevice) {
+        err = sensors_data_close(sSensorDevice);
+        if (err == 0)
+            sSensorDevice = 0;
+    }
+    return err;
+}
+
+static jint
+sensors_data_open(JNIEnv *env, jclass clazz, jobject fdo)
+{
+    jclass FileDescriptor = env->FindClass("java/io/FileDescriptor");
+    jfieldID offset = env->GetFieldID(FileDescriptor, "descriptor", "I");
+    int fd = env->GetIntField(fdo, offset);
+    return sSensorDevice->data_open(sSensorDevice, fd); // doesn't take ownership of fd
+}
+
+static jint
+sensors_data_close(JNIEnv *env, jclass clazz)
+{
+    return sSensorDevice->data_close(sSensorDevice);
+}
+
+static jint
+sensors_data_poll(JNIEnv *env, jclass clazz, 
+        jfloatArray values, jintArray status, jlongArray timestamp)
+{
+    sensors_data_t data;
+    int res = sSensorDevice->poll(sSensorDevice, &data);
+    if (res >= 0) {
+        jint accuracy = data.vector.status;
+        env->SetFloatArrayRegion(values, 0, 3, data.vector.v);
+        env->SetIntArrayRegion(status, 0, 1, &accuracy);
+        env->SetLongArrayRegion(timestamp, 0, 1, &data.time);
+    }
+    return res;
+}
+
+static void
+nativeClassInit (JNIEnv *_env, jclass _this)
+{
+    jclass sensorClass = _env->FindClass("android/hardware/Sensor");
+    SensorOffsets& sensorOffsets = gSensorOffsets;
+    sensorOffsets.name        = _env->GetFieldID(sensorClass, "mName",      "Ljava/lang/String;");
+    sensorOffsets.vendor      = _env->GetFieldID(sensorClass, "mVendor",    "Ljava/lang/String;");
+    sensorOffsets.version     = _env->GetFieldID(sensorClass, "mVersion",   "I");
+    sensorOffsets.handle      = _env->GetFieldID(sensorClass, "mHandle",    "I");
+    sensorOffsets.type        = _env->GetFieldID(sensorClass, "mType",      "I");
+    sensorOffsets.range       = _env->GetFieldID(sensorClass, "mMaxRange",  "F");
+    sensorOffsets.resolution  = _env->GetFieldID(sensorClass, "mResolution","F");
+    sensorOffsets.power       = _env->GetFieldID(sensorClass, "mPower",     "F");
+}
+
+static JNINativeMethod gMethods[] = {
+    {"nativeClassInit", "()V",              (void*)nativeClassInit },
+    {"sensors_module_init","()I",           (void*)sensors_module_init },
+    {"sensors_module_get_next_sensor","(Landroid/hardware/Sensor;I)I",
+                                            (void*)sensors_module_get_next_sensor },
+    {"sensors_data_init", "()I",            (void*)sensors_data_init },
+    {"sensors_data_uninit", "()I",          (void*)sensors_data_uninit },
+    {"sensors_data_open",  "(Ljava/io/FileDescriptor;)I",  (void*)sensors_data_open },
+    {"sensors_data_close", "()I",           (void*)sensors_data_close },
+    {"sensors_data_poll",  "([F[I[J)I",     (void*)sensors_data_poll },
+};
+
+}; // namespace android
+
+using namespace android;
+
+int register_android_hardware_SensorManager(JNIEnv *env)
+{
+    return jniRegisterNativeMethods(env, "android/hardware/SensorManager",
+            gMethods, NELEM(gMethods));
+}
diff --git a/core/jni/android_location_GpsLocationProvider.cpp b/core/jni/android_location_GpsLocationProvider.cpp
new file mode 100644
index 0000000..dee3fdd
--- /dev/null
+++ b/core/jni/android_location_GpsLocationProvider.cpp
@@ -0,0 +1,293 @@
+/*
+ * 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.
+ */
+
+#define LOG_TAG "GpsLocationProvider"
+
+#include "JNIHelp.h"
+#include "jni.h"
+#include "hardware_legacy/gps.h"
+#include "utils/Log.h"
+#include "utils/misc.h"
+
+#include <string.h>
+#include <pthread.h>
+
+
+static pthread_mutex_t sEventMutex = PTHREAD_MUTEX_INITIALIZER;
+static pthread_cond_t sEventCond = PTHREAD_COND_INITIALIZER;
+static jmethodID method_reportLocation;
+static jmethodID method_reportStatus;
+static jmethodID method_reportSvStatus;
+static jmethodID method_xtraDownloadRequest;
+
+static const GpsInterface* sGpsInterface = NULL;
+static const GpsXtraInterface* sGpsXtraInterface = NULL;
+
+// data written to by GPS callbacks
+static GpsLocation  sGpsLocation;
+static GpsStatus    sGpsStatus;
+static GpsSvStatus  sGpsSvStatus;
+
+// a copy of the data shared by android_location_GpsLocationProvider_wait_for_event
+// and android_location_GpsLocationProvider_read_status
+static GpsLocation  sGpsLocationCopy;
+static GpsStatus    sGpsStatusCopy;
+static GpsSvStatus  sGpsSvStatusCopy;
+
+enum CallbackType {
+    kLocation = 1,
+    kStatus = 2,
+    kSvStatus = 4,
+    kXtraDownloadRequest = 8,
+    kDisableRequest = 16,
+}; 
+static int sPendingCallbacks;
+
+namespace android {
+
+static void location_callback(GpsLocation* location)
+{
+    pthread_mutex_lock(&sEventMutex);
+
+    sPendingCallbacks |= kLocation;
+    memcpy(&sGpsLocation, location, sizeof(sGpsLocation));
+
+    pthread_cond_signal(&sEventCond);
+    pthread_mutex_unlock(&sEventMutex);
+}
+
+static void status_callback(GpsStatus* status)
+{
+    pthread_mutex_lock(&sEventMutex);
+
+    sPendingCallbacks |= kStatus;
+    memcpy(&sGpsStatus, status, sizeof(sGpsStatus));
+
+    pthread_cond_signal(&sEventCond);
+    pthread_mutex_unlock(&sEventMutex);
+}
+
+static void sv_status_callback(GpsSvStatus* sv_status)
+{
+    pthread_mutex_lock(&sEventMutex);
+
+    sPendingCallbacks |= kSvStatus;
+    memcpy(&sGpsSvStatus, sv_status, sizeof(GpsSvStatus));
+
+    pthread_cond_signal(&sEventCond);
+    pthread_mutex_unlock(&sEventMutex);
+}
+
+GpsCallbacks sGpsCallbacks = {
+    location_callback,
+    status_callback,
+    sv_status_callback,
+};
+
+static void
+download_request_callback()
+{
+    pthread_mutex_lock(&sEventMutex);
+    sPendingCallbacks |= kXtraDownloadRequest;
+    pthread_cond_signal(&sEventCond);
+    pthread_mutex_unlock(&sEventMutex);
+}
+
+GpsXtraCallbacks sGpsXtraCallbacks = {
+    download_request_callback,
+};
+
+
+static void android_location_GpsLocationProvider_class_init_native(JNIEnv* env, jclass clazz) {
+    method_reportLocation = env->GetMethodID(clazz, "reportLocation", "(IDDDFFFJ)V");
+    method_reportStatus = env->GetMethodID(clazz, "reportStatus", "(I)V");
+    method_reportSvStatus = env->GetMethodID(clazz, "reportSvStatus", "()V");
+    method_xtraDownloadRequest = env->GetMethodID(clazz, "xtraDownloadRequest", "()V");
+}
+
+static jboolean android_location_GpsLocationProvider_is_supported(JNIEnv* env, jclass clazz) {
+    if (!sGpsInterface)
+        sGpsInterface = gps_get_interface();
+    return (sGpsInterface != NULL);
+}
+
+static jboolean android_location_GpsLocationProvider_init(JNIEnv* env, jobject obj)
+{
+    if (!sGpsInterface)
+        sGpsInterface = gps_get_interface();
+    return (sGpsInterface && sGpsInterface->init(&sGpsCallbacks) == 0);
+}
+
+static void android_location_GpsLocationProvider_disable(JNIEnv* env, jobject obj)
+{
+    pthread_mutex_lock(&sEventMutex);
+    sPendingCallbacks |= kDisableRequest;
+    pthread_cond_signal(&sEventCond);
+    pthread_mutex_unlock(&sEventMutex);
+}
+
+static void android_location_GpsLocationProvider_cleanup(JNIEnv* env, jobject obj)
+{
+    sGpsInterface->cleanup();
+}
+
+static jboolean android_location_GpsLocationProvider_start(JNIEnv* env, jobject obj, jboolean singleFix, jint fixFrequency)
+{
+    int result = sGpsInterface->set_position_mode(GPS_POSITION_MODE_STANDALONE, (singleFix ? 0 : fixFrequency));
+    if (result) {
+        return result;
+    }
+
+    return (sGpsInterface->start() == 0);
+}
+
+static jboolean android_location_GpsLocationProvider_stop(JNIEnv* env, jobject obj)
+{
+    return (sGpsInterface->stop() == 0);
+}
+
+static void android_location_GpsLocationProvider_set_fix_frequency(JNIEnv* env, jobject obj, jint fixFrequency)
+{
+    if (sGpsInterface->set_fix_frequency)
+        sGpsInterface->set_fix_frequency(fixFrequency);
+}
+
+static void android_location_GpsLocationProvider_delete_aiding_data(JNIEnv* env, jobject obj, jint flags)
+{
+    sGpsInterface->delete_aiding_data(flags);
+}
+
+static void android_location_GpsLocationProvider_wait_for_event(JNIEnv* env, jobject obj)
+{
+    pthread_mutex_lock(&sEventMutex);
+    pthread_cond_wait(&sEventCond, &sEventMutex);
+    
+    // copy and clear the callback flags
+    int pendingCallbacks = sPendingCallbacks;
+    sPendingCallbacks = 0;
+    
+    // copy everything and unlock the mutex before calling into Java code to avoid the possibility
+    // of timeouts in the GPS engine.
+    memcpy(&sGpsLocationCopy, &sGpsLocation, sizeof(sGpsLocationCopy));
+    memcpy(&sGpsStatusCopy, &sGpsStatus, sizeof(sGpsStatusCopy));
+    memcpy(&sGpsSvStatusCopy, &sGpsSvStatus, sizeof(sGpsSvStatusCopy));
+    pthread_mutex_unlock(&sEventMutex);   
+
+    if (pendingCallbacks & kLocation) { 
+        env->CallVoidMethod(obj, method_reportLocation, sGpsLocationCopy.flags,
+                (jdouble)sGpsLocationCopy.latitude, (jdouble)sGpsLocationCopy.longitude,
+                (jdouble)sGpsLocationCopy.altitude, 
+                (jfloat)sGpsLocationCopy.speed, (jfloat)sGpsLocationCopy.bearing, 
+                (jfloat)sGpsLocationCopy.accuracy, (jlong)sGpsLocationCopy.timestamp);
+    }
+    if (pendingCallbacks & kStatus) {
+        env->CallVoidMethod(obj, method_reportStatus, sGpsStatusCopy.status);
+    }  
+    if (pendingCallbacks & kSvStatus) {
+        env->CallVoidMethod(obj, method_reportSvStatus);
+    }
+    if (pendingCallbacks & kXtraDownloadRequest) {    
+        env->CallVoidMethod(obj, method_xtraDownloadRequest);
+    }
+    if (pendingCallbacks & kDisableRequest) {
+        // don't need to do anything - we are just poking so wait_for_event will return.
+    }
+}
+
+static jint android_location_GpsLocationProvider_read_sv_status(JNIEnv* env, jobject obj, 
+        jintArray prnArray, jfloatArray snrArray, jfloatArray elevArray, jfloatArray azumArray, 
+        jintArray maskArray)
+{
+    // this should only be called from within a call to reportStatus, so we don't need to lock here
+
+    jint* prns = env->GetIntArrayElements(prnArray, 0);
+    jfloat* snrs = env->GetFloatArrayElements(snrArray, 0);
+    jfloat* elev = env->GetFloatArrayElements(elevArray, 0);
+    jfloat* azim = env->GetFloatArrayElements(azumArray, 0);
+    jint* mask = env->GetIntArrayElements(maskArray, 0);
+
+    int num_svs = sGpsSvStatusCopy.num_svs;
+    for (int i = 0; i < num_svs; i++) {
+        prns[i] = sGpsSvStatusCopy.sv_list[i].prn;
+        snrs[i] = sGpsSvStatusCopy.sv_list[i].snr;
+        elev[i] = sGpsSvStatusCopy.sv_list[i].elevation;
+        azim[i] = sGpsSvStatusCopy.sv_list[i].azimuth;
+    }
+    mask[0] = sGpsSvStatusCopy.ephemeris_mask;
+    mask[1] = sGpsSvStatusCopy.almanac_mask;
+    mask[2] = sGpsSvStatusCopy.used_in_fix_mask;
+
+    env->ReleaseIntArrayElements(prnArray, prns, 0);
+    env->ReleaseFloatArrayElements(snrArray, snrs, 0);
+    env->ReleaseFloatArrayElements(elevArray, elev, 0);
+    env->ReleaseFloatArrayElements(azumArray, azim, 0);
+    env->ReleaseIntArrayElements(maskArray, mask, 0);
+    return num_svs;
+}
+
+static void android_location_GpsLocationProvider_inject_time(JNIEnv* env, jobject obj, jlong time, 
+        jlong timeReference, jint uncertainty)
+{
+    sGpsInterface->inject_time(time, timeReference, uncertainty);
+}
+
+static jboolean android_location_GpsLocationProvider_supports_xtra(JNIEnv* env, jobject obj)
+{
+    if (!sGpsXtraInterface) {
+        sGpsXtraInterface = (const GpsXtraInterface*)sGpsInterface->get_extension(GPS_XTRA_INTERFACE);
+        if (sGpsXtraInterface) {
+            int result = sGpsXtraInterface->init(&sGpsXtraCallbacks);
+            if (result) {
+                sGpsXtraInterface = NULL;
+            }
+        }
+    }
+
+    return (sGpsXtraInterface != NULL);
+}
+
+static void android_location_GpsLocationProvider_inject_xtra_data(JNIEnv* env, jobject obj, 
+        jbyteArray data, jint length)
+{
+    jbyte* bytes = env->GetByteArrayElements(data, 0);
+    sGpsXtraInterface->inject_xtra_data((char *)bytes, length);
+    env->ReleaseByteArrayElements(data, bytes, 0);
+}
+
+static JNINativeMethod sMethods[] = {
+     /* name, signature, funcPtr */
+    {"class_init_native", "()V", (void *)android_location_GpsLocationProvider_class_init_native},
+	{"native_is_supported", "()Z", (void*)android_location_GpsLocationProvider_is_supported},
+	{"native_init", "()Z", (void*)android_location_GpsLocationProvider_init},
+	{"native_disable", "()V", (void*)android_location_GpsLocationProvider_disable},
+	{"native_cleanup", "()V", (void*)android_location_GpsLocationProvider_cleanup},
+	{"native_start", "(ZI)Z", (void*)android_location_GpsLocationProvider_start},
+	{"native_stop", "()Z", (void*)android_location_GpsLocationProvider_stop},
+	{"native_set_fix_frequency", "(I)V", (void*)android_location_GpsLocationProvider_set_fix_frequency},
+	{"native_delete_aiding_data", "(I)V", (void*)android_location_GpsLocationProvider_delete_aiding_data},
+	{"native_wait_for_event", "()V", (void*)android_location_GpsLocationProvider_wait_for_event},
+	{"native_read_sv_status", "([I[F[F[F[I)I", (void*)android_location_GpsLocationProvider_read_sv_status},
+	{"native_inject_time", "(JJI)V", (void*)android_location_GpsLocationProvider_inject_time},
+	{"native_supports_xtra", "()Z", (void*)android_location_GpsLocationProvider_supports_xtra},
+	{"native_inject_xtra_data", "([BI)V", (void*)android_location_GpsLocationProvider_inject_xtra_data},
+};
+
+int register_android_location_GpsLocationProvider(JNIEnv* env)
+{
+    return jniRegisterNativeMethods(env, "com/android/internal/location/GpsLocationProvider", sMethods, NELEM(sMethods));
+}
+
+} /* namespace android */
diff --git a/core/jni/android_media_AudioRecord.cpp b/core/jni/android_media_AudioRecord.cpp
new file mode 100644
index 0000000..288433a
--- /dev/null
+++ b/core/jni/android_media_AudioRecord.cpp
@@ -0,0 +1,601 @@
+/*
+ * 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.
+ */
+
+//#define LOG_NDEBUG 0
+
+#define LOG_TAG "AudioRecord-JNI"
+
+#include <stdio.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <math.h>
+
+#include "jni.h"
+#include "JNIHelp.h"
+#include "android_runtime/AndroidRuntime.h"
+
+#include "utils/Log.h"
+#include "media/AudioSystem.h"
+#include "media/AudioRecord.h"
+
+
+// ----------------------------------------------------------------------------
+
+using namespace android;
+
+// ----------------------------------------------------------------------------
+static const char* const kClassPathName = "android/media/AudioRecord";
+
+struct fields_t {
+    // these fields provide access from C++ to the...
+    jclass    audioRecordClass;      //... AudioRecord class
+    jmethodID postNativeEventInJava; //... event post callback method
+    int       PCM16;                 //...  format constants
+    int       PCM8;                  //...  format constants
+    int       SOURCE_DEFAULT;        //...  record source constants
+    int       SOURCE_MIC;            //...  record source constants
+    jfieldID  nativeRecorderInJavaObj; // provides access to the C++ AudioRecord object
+    jfieldID  nativeCallbackCookie;    // provides access to the AudioRecord callback data
+};
+static fields_t javaAudioRecordFields;
+
+struct audiorecord_callback_cookie {
+    jclass      audioRecord_class;
+    jobject     audioRecord_ref;
+ };
+
+// ----------------------------------------------------------------------------
+
+#define AUDIORECORD_SUCCESS                         0
+#define AUDIORECORD_ERROR                           -1
+#define AUDIORECORD_ERROR_BAD_VALUE                 -2
+#define AUDIORECORD_ERROR_INVALID_OPERATION         -3
+#define AUDIORECORD_ERROR_SETUP_ZEROFRAMECOUNT      -16
+#define AUDIORECORD_ERROR_SETUP_INVALIDCHANNELCOUNT -17
+#define AUDIORECORD_ERROR_SETUP_INVALIDFORMAT       -18
+#define AUDIORECORD_ERROR_SETUP_INVALIDSTREAMTYPE   -19
+#define AUDIORECORD_ERROR_SETUP_NATIVEINITFAILED    -20
+
+jint android_media_translateRecorderErrorCode(int code) {
+    switch(code) {
+    case NO_ERROR:
+        return AUDIORECORD_SUCCESS;
+    case BAD_VALUE:
+        return AUDIORECORD_ERROR_BAD_VALUE;
+    case INVALID_OPERATION:
+        return AUDIORECORD_ERROR_INVALID_OPERATION;
+    default:
+        return AUDIORECORD_ERROR;
+    }   
+}
+
+
+// ----------------------------------------------------------------------------
+static void recorderCallback(int event, void* user, void *info) {
+    if (event == AudioRecord::EVENT_MORE_DATA) {
+        // set size to 0 to signal we're not using the callback to read more data
+        AudioRecord::Buffer* pBuff = (AudioRecord::Buffer*)info;
+        pBuff->size = 0;  
+    
+    } else if (event == AudioRecord::EVENT_MARKER) {
+        audiorecord_callback_cookie *callbackInfo = (audiorecord_callback_cookie *)user;
+        JNIEnv *env = AndroidRuntime::getJNIEnv();
+        if (user && env) {
+            env->CallStaticVoidMethod(
+                callbackInfo->audioRecord_class, 
+                javaAudioRecordFields.postNativeEventInJava,
+                callbackInfo->audioRecord_ref, event, 0,0, NULL);
+            if (env->ExceptionCheck()) {
+                env->ExceptionDescribe();
+                env->ExceptionClear();
+            }
+        }
+
+    } else if (event == AudioRecord::EVENT_NEW_POS) {
+        audiorecord_callback_cookie *callbackInfo = (audiorecord_callback_cookie *)user;
+        JNIEnv *env = AndroidRuntime::getJNIEnv();
+        if (user && env) {
+            env->CallStaticVoidMethod(
+                callbackInfo->audioRecord_class, 
+                javaAudioRecordFields.postNativeEventInJava,
+                callbackInfo->audioRecord_ref, event, 0,0, NULL);
+            if (env->ExceptionCheck()) {
+                env->ExceptionDescribe();
+                env->ExceptionClear();
+            }
+        }
+    }
+}
+
+
+// ----------------------------------------------------------------------------
+static int
+android_media_AudioRecord_setup(JNIEnv *env, jobject thiz, jobject weak_this,
+        jint source, jint sampleRateInHertz, jint nbChannels, 
+        jint audioFormat, jint buffSizeInBytes)
+{
+    //LOGV(">> Entering android_media_AudioRecord_setup");
+    //LOGV("sampleRate=%d, audioFormat=%d, nbChannels=%d, buffSizeInBytes=%d",
+    //     sampleRateInHertz, audioFormat, nbChannels,     buffSizeInBytes);
+
+    if ((nbChannels == 0) || (nbChannels > 2)) {
+        LOGE("Error creating AudioRecord: channel count is not 1 or 2.");
+        return AUDIORECORD_ERROR_SETUP_INVALIDCHANNELCOUNT;
+    }
+
+    // compare the format against the Java constants
+    if ((audioFormat != javaAudioRecordFields.PCM16) 
+        && (audioFormat != javaAudioRecordFields.PCM8)) {
+        LOGE("Error creating AudioRecord: unsupported audio format.");
+        return AUDIORECORD_ERROR_SETUP_INVALIDFORMAT;
+    }
+
+    int bytesPerSample = audioFormat==javaAudioRecordFields.PCM16 ? 2 : 1;
+    int format = audioFormat==javaAudioRecordFields.PCM16 ? 
+            AudioSystem::PCM_16_BIT : AudioSystem::PCM_8_BIT;
+
+    if (buffSizeInBytes == 0) {
+         LOGE("Error creating AudioRecord: frameCount is 0.");
+        return AUDIORECORD_ERROR_SETUP_ZEROFRAMECOUNT;
+    }
+    int frameSize = nbChannels * bytesPerSample;
+    size_t frameCount = buffSizeInBytes / frameSize;
+    
+    // compare the source against the Java constants
+    AudioRecord::stream_type arSource;
+    if (source == javaAudioRecordFields.SOURCE_DEFAULT) {
+        arSource = AudioRecord::DEFAULT_INPUT;
+    } else if (source == javaAudioRecordFields.SOURCE_MIC) {
+        arSource = AudioRecord::MIC_INPUT;
+    } else {
+        LOGE("Error creating AudioRecord: unknown source.");
+        return AUDIORECORD_ERROR_SETUP_INVALIDSTREAMTYPE;
+    }
+     
+    audiorecord_callback_cookie *lpCallbackData = NULL;
+    AudioRecord* lpRecorder = NULL;
+
+    // create an uninitialized AudioRecord object
+    lpRecorder = new AudioRecord();
+        if(lpRecorder == NULL) {
+        LOGE("Error creating AudioRecord instance.");
+        return AUDIORECORD_ERROR_SETUP_NATIVEINITFAILED;
+    }
+    
+    // create the callback information:
+    // this data will be passed with every AudioRecord callback
+    jclass clazz = env->GetObjectClass(thiz);
+    if (clazz == NULL) {
+        LOGE("Can't find %s when setting up callback.", kClassPathName);
+        goto native_track_failure;
+    }
+    lpCallbackData = new audiorecord_callback_cookie;
+    lpCallbackData->audioRecord_class = (jclass)env->NewGlobalRef(clazz);
+    // we use a weak reference so the AudioRecord object can be garbage collected.
+    lpCallbackData->audioRecord_ref = env->NewGlobalRef(weak_this);
+    
+    lpRecorder->set(arSource,
+        sampleRateInHertz,
+        format,        // word length, PCM
+        nbChannels,
+        frameCount,
+        0,             // flags
+        recorderCallback,// callback_t
+        lpCallbackData,// void* user
+        0,             // notificationFrames,
+        true);         // threadCanCallJava)
+
+    if(lpRecorder->initCheck() != NO_ERROR) {
+        LOGE("Error creating AudioRecord instance: initialization check failed.");
+        goto native_init_failure;
+    }
+
+    // save our newly created C++ AudioRecord in the "nativeRecorderInJavaObj" field 
+    // of the Java object
+    env->SetIntField(thiz, javaAudioRecordFields.nativeRecorderInJavaObj, (int)lpRecorder);
+    
+    // save our newly created callback information in the "nativeCallbackCookie" field
+    // of the Java object (in mNativeCallbackCookie) so we can free the memory in finalize()
+    env->SetIntField(thiz, javaAudioRecordFields.nativeCallbackCookie, (int)lpCallbackData);
+    
+    return AUDIORECORD_SUCCESS;
+    
+    // failure:
+native_init_failure:
+    delete lpCallbackData;
+    
+native_track_failure:
+    delete lpRecorder;
+
+    env->SetIntField(thiz, javaAudioRecordFields.nativeRecorderInJavaObj, 0);
+    env->SetIntField(thiz, javaAudioRecordFields.nativeCallbackCookie, 0);
+    
+    return AUDIORECORD_ERROR_SETUP_NATIVEINITFAILED;
+}
+
+
+
+// ----------------------------------------------------------------------------
+static void
+android_media_AudioRecord_start(JNIEnv *env, jobject thiz)
+{
+    AudioRecord *lpRecorder = 
+            (AudioRecord *)env->GetIntField(thiz, javaAudioRecordFields.nativeRecorderInJavaObj);
+    if (lpRecorder == NULL ) {
+        jniThrowException(env, "java/lang/IllegalStateException", NULL);
+        return;
+    }
+    
+    lpRecorder->start();
+}
+
+
+// ----------------------------------------------------------------------------
+static void
+android_media_AudioRecord_stop(JNIEnv *env, jobject thiz)
+{
+    AudioRecord *lpRecorder = 
+            (AudioRecord *)env->GetIntField(thiz, javaAudioRecordFields.nativeRecorderInJavaObj);
+    if (lpRecorder == NULL ) {
+        jniThrowException(env, "java/lang/IllegalStateException", NULL);
+        return;
+    }
+
+    lpRecorder->stop();
+    //LOGV("Called lpRecorder->stop()");
+}
+
+
+// ----------------------------------------------------------------------------
+static void android_media_AudioRecord_finalize(JNIEnv *env,  jobject thiz) {
+    
+    // delete the AudioRecord object
+    AudioRecord *lpRecorder = 
+            (AudioRecord *)env->GetIntField(thiz, javaAudioRecordFields.nativeRecorderInJavaObj);
+
+    if (lpRecorder) {
+        LOGV("About to delete lpRecorder: %x\n", (int)lpRecorder);
+        lpRecorder->stop();
+        delete lpRecorder;
+    }
+    
+    // delete the callback information
+    audiorecord_callback_cookie *lpCookie = (audiorecord_callback_cookie *)env->GetIntField(
+        thiz, javaAudioRecordFields.nativeCallbackCookie);
+    if (lpCookie) {
+        LOGV("deleting lpCookie: %x\n", (int)lpCookie);
+        delete lpCookie;
+    }
+
+}
+
+
+// ----------------------------------------------------------------------------
+static void android_media_AudioRecord_release(JNIEnv *env,  jobject thiz) {
+       
+    // do everything a call to finalize would
+    android_media_AudioRecord_finalize(env, thiz);
+    // + reset the native resources in the Java object so any attempt to access
+    // them after a call to release fails.
+    env->SetIntField(thiz, javaAudioRecordFields.nativeRecorderInJavaObj, 0);
+    env->SetIntField(thiz, javaAudioRecordFields.nativeCallbackCookie, 0);
+}
+
+
+// ----------------------------------------------------------------------------
+static jint android_media_AudioRecord_readInByteArray(JNIEnv *env,  jobject thiz,
+                                                        jbyteArray javaAudioData,
+                                                        jint offsetInBytes, jint sizeInBytes) {
+    jbyte* recordBuff = NULL;
+    AudioRecord *lpRecorder = NULL;
+
+    // get the audio recorder from which we'll read new audio samples
+    lpRecorder = 
+            (AudioRecord *)env->GetIntField(thiz, javaAudioRecordFields.nativeRecorderInJavaObj);
+    if (lpRecorder == NULL) {
+        LOGE("Unable to retrieve AudioRecord object, can't record");
+        return 0;
+    }
+
+    if (!javaAudioData) {
+        LOGE("Invalid Java array to store recorded audio, can't record");
+        return 0;
+    }
+
+    // get the pointer to where we'll record the audio
+    recordBuff = (jbyte *)env->GetPrimitiveArrayCritical(javaAudioData, NULL);
+
+    if (recordBuff == NULL) {
+        LOGE("Error retrieving destination for recorded audio data, can't record");
+        return 0;
+    }
+
+    // read the new audio data from the native AudioRecord object
+    ssize_t recorderBuffSize = lpRecorder->frameCount()*lpRecorder->frameSize();
+    ssize_t readSize = lpRecorder->read(recordBuff + offsetInBytes, 
+                                        sizeInBytes > (jint)recorderBuffSize ? 
+                                            (jint)recorderBuffSize : sizeInBytes );
+    env->ReleasePrimitiveArrayCritical(javaAudioData, recordBuff, 0);
+
+    return (jint) readSize;
+}
+
+// ----------------------------------------------------------------------------
+static jint android_media_AudioRecord_readInShortArray(JNIEnv *env,  jobject thiz,
+                                                        jshortArray javaAudioData,
+                                                        jint offsetInShorts, jint sizeInShorts) {
+
+    return (android_media_AudioRecord_readInByteArray(env, thiz,
+                                                        (jbyteArray) javaAudioData,
+                                                        offsetInShorts*2, sizeInShorts*2)
+            / 2);
+}
+
+// ----------------------------------------------------------------------------
+static jint android_media_AudioRecord_readInDirectBuffer(JNIEnv *env,  jobject thiz,
+                                                  jobject jBuffer, jint sizeInBytes) {
+    AudioRecord *lpRecorder = NULL;
+    //LOGV("Entering android_media_AudioRecord_readInBuffer");
+
+    // get the audio recorder from which we'll read new audio samples
+    lpRecorder = 
+        (AudioRecord *)env->GetIntField(thiz, javaAudioRecordFields.nativeRecorderInJavaObj);
+    if(lpRecorder==NULL)
+        return 0;
+
+    // direct buffer and direct access supported?
+    long capacity = env->GetDirectBufferCapacity(jBuffer);
+    if(capacity == -1) {
+        // buffer direct access is not supported
+        LOGE("Buffer direct access is not supported, can't record");
+        return 0;
+    }
+    //LOGV("capacity = %ld", capacity);
+    jbyte* nativeFromJavaBuf = (jbyte*) env->GetDirectBufferAddress(jBuffer);
+    if(nativeFromJavaBuf==NULL) {
+        LOGE("Buffer direct access is not supported, can't record");
+        return 0;
+    } 
+
+    // read new data from the recorder
+    return (jint) lpRecorder->read(nativeFromJavaBuf, 
+                                   capacity < sizeInBytes ? capacity : sizeInBytes);
+}
+
+
+// ----------------------------------------------------------------------------
+static jint android_media_AudioRecord_set_marker_pos(JNIEnv *env,  jobject thiz, 
+        jint markerPos) {
+            
+    AudioRecord *lpRecorder = (AudioRecord *)env->GetIntField(
+                thiz, javaAudioRecordFields.nativeRecorderInJavaObj);
+                
+    if (lpRecorder) {
+        return 
+            android_media_translateRecorderErrorCode( lpRecorder->setMarkerPosition(markerPos) );   
+    } else {
+        jniThrowException(env, "java/lang/IllegalStateException",
+            "Unable to retrieve AudioRecord pointer for setMarkerPosition()");
+        return AUDIORECORD_ERROR;
+    }
+}
+
+
+// ----------------------------------------------------------------------------
+static jint android_media_AudioRecord_get_marker_pos(JNIEnv *env,  jobject thiz) {
+    
+    AudioRecord *lpRecorder = (AudioRecord *)env->GetIntField(
+                thiz, javaAudioRecordFields.nativeRecorderInJavaObj);
+    uint32_t markerPos = 0;
+                
+    if (lpRecorder) {
+        lpRecorder->getMarkerPosition(&markerPos);
+        return (jint)markerPos;
+    } else {
+        jniThrowException(env, "java/lang/IllegalStateException",
+            "Unable to retrieve AudioRecord pointer for getMarkerPosition()");
+        return AUDIORECORD_ERROR;
+    }
+}
+
+
+// ----------------------------------------------------------------------------
+static jint android_media_AudioRecord_set_pos_update_period(JNIEnv *env,  jobject thiz,
+        jint period) {
+            
+    AudioRecord *lpRecorder = (AudioRecord *)env->GetIntField(
+                thiz, javaAudioRecordFields.nativeRecorderInJavaObj);
+                
+    if (lpRecorder) {
+        return 
+            android_media_translateRecorderErrorCode( lpRecorder->setPositionUpdatePeriod(period) );   
+    } else {
+        jniThrowException(env, "java/lang/IllegalStateException",
+            "Unable to retrieve AudioRecord pointer for setPositionUpdatePeriod()");
+        return AUDIORECORD_ERROR;
+    }            
+}
+
+
+// ----------------------------------------------------------------------------
+static jint android_media_AudioRecord_get_pos_update_period(JNIEnv *env,  jobject thiz) {
+    
+    AudioRecord *lpRecorder = (AudioRecord *)env->GetIntField(
+                thiz, javaAudioRecordFields.nativeRecorderInJavaObj);
+    uint32_t period = 0;
+                
+    if (lpRecorder) {
+        lpRecorder->getPositionUpdatePeriod(&period);
+        return (jint)period;
+    } else {
+        jniThrowException(env, "java/lang/IllegalStateException",
+            "Unable to retrieve AudioRecord pointer for getPositionUpdatePeriod()");
+        return AUDIORECORD_ERROR;
+    }
+}
+
+
+// ----------------------------------------------------------------------------
+// returns the minimum required size for the successful creation of an AudioRecord instance.
+// returns 0 if the parameter combination is not supported.
+// return -1 if there was an error querying the buffer size.
+static jint android_media_AudioRecord_get_min_buff_size(JNIEnv *env,  jobject thiz,
+    jint sampleRateInHertz, jint nbChannels, jint audioFormat) {
+    
+    size_t inputBuffSize = 0;
+    LOGV(">> android_media_AudioRecord_get_min_buff_size(%d, %d, %d)", sampleRateInHertz, nbChannels, audioFormat);
+    
+    status_t result = AudioSystem::getInputBufferSize(
+                        sampleRateInHertz, 
+                        (audioFormat == javaAudioRecordFields.PCM16 ? 
+                            AudioSystem::PCM_16_BIT : AudioSystem::PCM_8_BIT), 
+                        nbChannels, &inputBuffSize);
+    switch(result) {
+    case(NO_ERROR):
+        if(inputBuffSize == 0) {
+            LOGV("Recording parameters are not supported: %dHz, %d channel(s), (java) format %d",
+                sampleRateInHertz, nbChannels, audioFormat);
+            return 0;
+        } else {
+            // the minimum buffer size is twice the hardware input buffer size
+            return 2*inputBuffSize;
+        }
+        break;
+    case(PERMISSION_DENIED):
+    default:
+        return -1; 
+    }
+}
+
+
+// ----------------------------------------------------------------------------
+// ----------------------------------------------------------------------------
+static JNINativeMethod gMethods[] = {
+    // name,               signature,  funcPtr
+    {"native_start",         "()V",    (void *)android_media_AudioRecord_start},
+    {"native_stop",          "()V",    (void *)android_media_AudioRecord_stop},
+    {"native_setup",         "(Ljava/lang/Object;IIIII)I",
+                                       (void *)android_media_AudioRecord_setup},
+    {"native_finalize",      "()V",    (void *)android_media_AudioRecord_finalize},
+    {"native_release",       "()V",    (void *)android_media_AudioRecord_release},
+    {"native_read_in_byte_array", 
+                             "([BII)I", (void *)android_media_AudioRecord_readInByteArray},
+    {"native_read_in_short_array",
+                             "([SII)I", (void *)android_media_AudioRecord_readInShortArray},
+    {"native_read_in_direct_buffer","(Ljava/lang/Object;I)I",
+                                       (void *)android_media_AudioRecord_readInDirectBuffer},
+    {"native_set_marker_pos","(I)I",   (void *)android_media_AudioRecord_set_marker_pos},
+    {"native_get_marker_pos","()I",    (void *)android_media_AudioRecord_get_marker_pos},
+    {"native_set_pos_update_period",
+                             "(I)I",   (void *)android_media_AudioRecord_set_pos_update_period},
+    {"native_get_pos_update_period",
+                             "()I",    (void *)android_media_AudioRecord_get_pos_update_period},
+    {"native_get_min_buff_size",
+                             "(III)I",   (void *)android_media_AudioRecord_get_min_buff_size},
+};
+
+// field names found in android/media/AudioRecord.java
+#define JAVA_POSTEVENT_CALLBACK_NAME  "postEventFromNative"
+#define JAVA_CONST_PCM16_NAME         "ENCODING_PCM_16BIT"
+#define JAVA_CONST_PCM8_NAME          "ENCODING_PCM_8BIT"
+#define JAVA_CONST_SOURCEDEFAULT_NAME "SOURCE_DEFAULT"
+#define JAVA_CONST_SOURCEMIC_NAME     "SOURCE_MIC"
+#define JAVA_NATIVERECORDERINJAVAOBJ_FIELD_NAME  "mNativeRecorderInJavaObj"
+#define JAVA_NATIVECALLBACKINFO_FIELD_NAME       "mNativeCallbackCookie"
+
+#define JAVA_AUDIOFORMAT_CLASS_NAME "android/media/AudioFormat"
+
+// ----------------------------------------------------------------------------
+
+extern bool android_media_getIntConstantFromClass(JNIEnv* pEnv, 
+                jclass theClass, const char* className, const char* constName, int* constVal);
+
+// ----------------------------------------------------------------------------
+int register_android_media_AudioRecord(JNIEnv *env)
+{
+    javaAudioRecordFields.audioRecordClass = NULL;
+    javaAudioRecordFields.postNativeEventInJava = NULL;
+    javaAudioRecordFields.nativeRecorderInJavaObj = NULL;
+    javaAudioRecordFields.nativeCallbackCookie = NULL;
+    
+
+    // Get the AudioRecord class
+    javaAudioRecordFields.audioRecordClass = env->FindClass(kClassPathName);
+    if (javaAudioRecordFields.audioRecordClass == NULL) {
+        LOGE("Can't find %s", kClassPathName);
+        return -1;
+    }
+    
+    // Get the postEvent method
+    javaAudioRecordFields.postNativeEventInJava = env->GetStaticMethodID(
+            javaAudioRecordFields.audioRecordClass,
+            JAVA_POSTEVENT_CALLBACK_NAME, "(Ljava/lang/Object;IIILjava/lang/Object;)V");
+    if (javaAudioRecordFields.postNativeEventInJava == NULL) {
+        LOGE("Can't find AudioRecord.%s", JAVA_POSTEVENT_CALLBACK_NAME);
+        return -1;
+    }
+
+    // Get the variables
+    //    mNativeRecorderInJavaObj
+    javaAudioRecordFields.nativeRecorderInJavaObj = 
+        env->GetFieldID(javaAudioRecordFields.audioRecordClass,
+                        JAVA_NATIVERECORDERINJAVAOBJ_FIELD_NAME, "I");
+    if (javaAudioRecordFields.nativeRecorderInJavaObj == NULL) {
+        LOGE("Can't find AudioRecord.%s", JAVA_NATIVERECORDERINJAVAOBJ_FIELD_NAME);
+        return -1;
+    }
+    //     mNativeCallbackCookie
+    javaAudioRecordFields.nativeCallbackCookie = env->GetFieldID(
+            javaAudioRecordFields.audioRecordClass,
+            JAVA_NATIVECALLBACKINFO_FIELD_NAME, "I");
+    if (javaAudioRecordFields.nativeCallbackCookie == NULL) {
+        LOGE("Can't find AudioRecord.%s", JAVA_NATIVECALLBACKINFO_FIELD_NAME);
+        return -1;
+    }
+
+    // Get the format constants from the AudioFormat class
+    jclass audioFormatClass = NULL;
+    audioFormatClass = env->FindClass(JAVA_AUDIOFORMAT_CLASS_NAME);
+    if (audioFormatClass == NULL) {
+        LOGE("Can't find %s", JAVA_AUDIOFORMAT_CLASS_NAME);
+        return -1;
+    }
+    if ( !android_media_getIntConstantFromClass(env, audioFormatClass, 
+                JAVA_AUDIOFORMAT_CLASS_NAME, 
+                JAVA_CONST_PCM16_NAME, &(javaAudioRecordFields.PCM16))
+           || !android_media_getIntConstantFromClass(env, audioFormatClass, 
+                JAVA_AUDIOFORMAT_CLASS_NAME, 
+                JAVA_CONST_PCM8_NAME, &(javaAudioRecordFields.PCM8)) ) {
+        // error log performed in getIntConstantFromClass() 
+        return -1;
+    }
+
+    // Get the recording source constants from the AudioRecord class
+    if ( !android_media_getIntConstantFromClass(env, javaAudioRecordFields.audioRecordClass, 
+                kClassPathName,
+                JAVA_CONST_SOURCEDEFAULT_NAME, &(javaAudioRecordFields.SOURCE_DEFAULT))
+        || !android_media_getIntConstantFromClass(env, javaAudioRecordFields.audioRecordClass, 
+                kClassPathName,
+                JAVA_CONST_SOURCEMIC_NAME, &(javaAudioRecordFields.SOURCE_MIC)) ) {
+        // error log performed in getIntConstantFromClass() 
+        return -1;
+    }
+
+    return AndroidRuntime::registerNativeMethods(env,
+            kClassPathName, gMethods, NELEM(gMethods));
+}
+
+// ----------------------------------------------------------------------------
diff --git a/core/jni/android_media_AudioSystem.cpp b/core/jni/android_media_AudioSystem.cpp
new file mode 100644
index 0000000..692610e
--- /dev/null
+++ b/core/jni/android_media_AudioSystem.cpp
@@ -0,0 +1,178 @@
+/* //device/libs/android_runtime/android_media_AudioSystem.cpp
+**
+** Copyright 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.
+*/
+
+#define LOG_TAG "AudioSystem"
+#include "utils/Log.h"
+
+#include <stdio.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <math.h>
+
+#include "jni.h"
+#include "JNIHelp.h"
+#include "android_runtime/AndroidRuntime.h"
+
+#include <media/AudioSystem.h>
+#include <media/AudioTrack.h>
+
+// ----------------------------------------------------------------------------
+
+using namespace android;
+
+enum AudioError {
+    kAudioStatusOk = 0,
+    kAudioStatusError = 1,
+    kAudioStatusMediaServerDied = 100
+};
+
+static int check_AudioSystem_Command(status_t status)
+{
+    if (status == NO_ERROR) {
+        return kAudioStatusOk;
+    } else {
+        return kAudioStatusError;
+    }
+}
+
+static int
+android_media_AudioSystem_setVolume(JNIEnv *env, jobject clazz, jint type, jint volume)
+{
+    LOGV("setVolume(%d)", int(volume));
+    
+    return check_AudioSystem_Command(AudioSystem::setStreamVolume(type, AudioSystem::linearToLog(volume)));
+}
+
+static int
+android_media_AudioSystem_getVolume(JNIEnv *env, jobject clazz, jint type)
+{
+    float v;
+    int v_int = -1;
+    if (AudioSystem::getStreamVolume(int(type), &v) == NO_ERROR) {
+        v_int = AudioSystem::logToLinear(v);
+    }
+    return v_int;
+}
+
+static int
+android_media_AudioSystem_muteMicrophone(JNIEnv *env, jobject thiz, jboolean on)
+{
+    return check_AudioSystem_Command(AudioSystem::muteMicrophone(on));
+}
+
+static jboolean
+android_media_AudioSystem_isMicrophoneMuted(JNIEnv *env, jobject thiz)
+{
+    bool state = false;
+    AudioSystem::isMicrophoneMuted(&state);
+    return state;
+}
+
+static int
+android_media_AudioSystem_setRouting(JNIEnv *env, jobject clazz, jint mode, jint routes, jint mask)
+{
+    return check_AudioSystem_Command(AudioSystem::setRouting(mode, uint32_t(routes), uint32_t(mask)));
+}
+
+static jint
+android_media_AudioSystem_getRouting(JNIEnv *env, jobject clazz, jint mode)
+{
+    uint32_t routes = -1;
+    AudioSystem::getRouting(mode, &routes);
+    return jint(routes);
+}
+
+static int
+android_media_AudioSystem_setMode(JNIEnv *env, jobject clazz, jint mode)
+{
+    return check_AudioSystem_Command(AudioSystem::setMode(mode));
+}
+
+static jint
+android_media_AudioSystem_getMode(JNIEnv *env, jobject clazz)
+{
+    int mode = AudioSystem::MODE_INVALID;
+    AudioSystem::getMode(&mode);
+    return jint(mode);
+}
+
+static jboolean
+android_media_AudioSystem_isMusicActive(JNIEnv *env, jobject thiz)
+{
+    bool state = false;
+    AudioSystem::isMusicActive(&state);
+    return state;
+}
+
+// Temporary interface, do not use
+// TODO: Replace with a more generic key:value get/set mechanism
+static void
+android_media_AudioSystem_setParameter(JNIEnv *env, jobject thiz, jstring key, jstring value)
+{
+    const char *c_key = env->GetStringUTFChars(key, NULL);
+    const char *c_value = env->GetStringUTFChars(value, NULL);
+    AudioSystem::setParameter(c_key, c_value);
+    env->ReleaseStringUTFChars(key, c_key);
+    env->ReleaseStringUTFChars(value, c_value);
+}
+
+void android_media_AudioSystem_error_callback(status_t err)
+{
+    JNIEnv *env = AndroidRuntime::getJNIEnv();
+    jclass clazz = env->FindClass("android/media/AudioSystem");
+
+    int error;
+
+    switch (err) {
+    case DEAD_OBJECT:
+        error = kAudioStatusMediaServerDied;
+        break;
+    case NO_ERROR:
+        error = kAudioStatusOk;
+        break;
+    default:
+        error = kAudioStatusError;
+        break;
+    }
+
+    env->CallStaticVoidMethod(clazz, env->GetStaticMethodID(clazz, "errorCallbackFromNative","(I)V"), error);
+}
+
+// ----------------------------------------------------------------------------
+
+static JNINativeMethod gMethods[] = {
+    {"setVolume",           "(II)I",    (void *)android_media_AudioSystem_setVolume},
+    {"getVolume",           "(I)I",     (void *)android_media_AudioSystem_getVolume},
+    {"setParameter",        "(Ljava/lang/String;Ljava/lang/String;)V", (void *)android_media_AudioSystem_setParameter},
+    {"muteMicrophone",      "(Z)I",     (void *)android_media_AudioSystem_muteMicrophone},
+    {"isMicrophoneMuted",   "()Z",      (void *)android_media_AudioSystem_isMicrophoneMuted},
+    {"setRouting",          "(III)I",   (void *)android_media_AudioSystem_setRouting},
+    {"getRouting",          "(I)I",     (void *)android_media_AudioSystem_getRouting},
+    {"setMode",             "(I)I",     (void *)android_media_AudioSystem_setMode},
+    {"getMode",             "()I",      (void *)android_media_AudioSystem_getMode},
+    {"isMusicActive",       "()Z",      (void *)android_media_AudioSystem_isMusicActive},
+};
+
+const char* const kClassPathName = "android/media/AudioSystem";
+
+int register_android_media_AudioSystem(JNIEnv *env)
+{
+    AudioSystem::setErrorCallback(android_media_AudioSystem_error_callback);
+    
+    return AndroidRuntime::registerNativeMethods(env,
+                "android/media/AudioSystem", gMethods, NELEM(gMethods));
+}
diff --git a/core/jni/android_media_AudioTrack.cpp b/core/jni/android_media_AudioTrack.cpp
new file mode 100644
index 0000000..f625ffb
--- /dev/null
+++ b/core/jni/android_media_AudioTrack.cpp
@@ -0,0 +1,936 @@
+/*
+ * 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.
+ */
+//#define LOG_NDEBUG 0
+
+#define LOG_TAG "AudioTrack-JNI"
+
+#include <stdio.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <math.h>
+
+#include "jni.h"
+#include "JNIHelp.h"
+#include "android_runtime/AndroidRuntime.h"
+
+#include "utils/Log.h"
+#include "media/AudioSystem.h"
+#include "media/AudioTrack.h"
+
+#include <utils/MemoryHeapBase.h>
+#include <utils/MemoryBase.h>
+
+
+// ----------------------------------------------------------------------------
+
+using namespace android;
+
+// ----------------------------------------------------------------------------
+static const char* const kClassPathName = "android/media/AudioTrack";
+
+struct fields_t {
+    // these fields provide access from C++ to the...
+    jclass    audioTrackClass;       //... AudioTrack class
+    jmethodID postNativeEventInJava; //... event post callback method
+    int       PCM16;                 //...  format constants
+    int       PCM8;                  //...  format constants
+    int       STREAM_VOICE_CALL;     //...  stream type constants
+    int       STREAM_SYSTEM;         //...  stream type constants
+    int       STREAM_RING;           //...  stream type constants
+    int       STREAM_MUSIC;          //...  stream type constants
+    int       STREAM_ALARM;          //...  stream type constants
+    int       STREAM_NOTIFICATION;   //...  stream type constants
+    int       STREAM_BLUETOOTH_SCO;   //...  stream type constants
+    int       MODE_STREAM;           //...  memory mode
+    int       MODE_STATIC;           //...  memory mode
+    jfieldID  nativeTrackInJavaObj; // stores in Java the native AudioTrack object
+    jfieldID  jniData;      // stores in Java additional resources used by the native AudioTrack
+};
+static fields_t javaAudioTrackFields;
+
+struct audiotrack_callback_cookie {
+    jclass      audioTrack_class;
+    jobject     audioTrack_ref;
+ };
+
+// ----------------------------------------------------------------------------
+class AudioTrackJniStorage {
+    public:
+        sp<MemoryHeapBase>         mMemHeap;
+        sp<MemoryBase>             mMemBase;
+        audiotrack_callback_cookie mCallbackData;
+        int                        mStreamType;
+
+    AudioTrackJniStorage() {
+    }
+
+    ~AudioTrackJniStorage() {
+        mMemBase.clear();
+        mMemHeap.clear();
+    }
+
+    bool allocSharedMem(int sizeInBytes) {
+        mMemHeap = new MemoryHeapBase(sizeInBytes, 0, "AudioTrack Heap Base");
+        if (mMemHeap->getHeapID() < 0) {
+            return false;
+        }
+        mMemBase = new MemoryBase(mMemHeap, 0, sizeInBytes);
+        return true;
+    }
+};
+
+// ----------------------------------------------------------------------------
+#define DEFAULT_OUTPUT_SAMPLE_RATE   44100
+
+#define AUDIOTRACK_SUCCESS                         0
+#define AUDIOTRACK_ERROR                           -1
+#define AUDIOTRACK_ERROR_BAD_VALUE                 -2
+#define AUDIOTRACK_ERROR_INVALID_OPERATION         -3
+#define AUDIOTRACK_ERROR_SETUP_AUDIOSYSTEM         -16
+#define AUDIOTRACK_ERROR_SETUP_INVALIDCHANNELCOUNT -17
+#define AUDIOTRACK_ERROR_SETUP_INVALIDFORMAT       -18
+#define AUDIOTRACK_ERROR_SETUP_INVALIDSTREAMTYPE   -19
+#define AUDIOTRACK_ERROR_SETUP_NATIVEINITFAILED    -20
+
+
+jint android_media_translateErrorCode(int code) {
+    switch(code) {
+    case NO_ERROR:
+        return AUDIOTRACK_SUCCESS;
+    case BAD_VALUE:
+        return AUDIOTRACK_ERROR_BAD_VALUE;
+    case INVALID_OPERATION:
+        return AUDIOTRACK_ERROR_INVALID_OPERATION;
+    default:
+        return AUDIOTRACK_ERROR;
+    }   
+}
+
+
+// ----------------------------------------------------------------------------
+static void audioCallback(int event, void* user, void *info) {
+    if (event == AudioTrack::EVENT_MORE_DATA) {
+        // set size to 0 to signal we're not using the callback to write more data
+        AudioTrack::Buffer* pBuff = (AudioTrack::Buffer*)info;
+        pBuff->size = 0;  
+    
+    } else if (event == AudioTrack::EVENT_MARKER) {
+        audiotrack_callback_cookie *callbackInfo = (audiotrack_callback_cookie *)user;
+        JNIEnv *env = AndroidRuntime::getJNIEnv();
+        if (user && env) {
+            env->CallStaticVoidMethod(
+                callbackInfo->audioTrack_class, 
+                javaAudioTrackFields.postNativeEventInJava,
+                callbackInfo->audioTrack_ref, event, 0,0, NULL);
+            if (env->ExceptionCheck()) {
+                env->ExceptionDescribe();
+                env->ExceptionClear();
+            }
+        }
+
+    } else if (event == AudioTrack::EVENT_NEW_POS) {
+        audiotrack_callback_cookie *callbackInfo = (audiotrack_callback_cookie *)user;
+        JNIEnv *env = AndroidRuntime::getJNIEnv();
+        if (user && env) {
+            env->CallStaticVoidMethod(
+                callbackInfo->audioTrack_class, 
+                javaAudioTrackFields.postNativeEventInJava,
+                callbackInfo->audioTrack_ref, event, 0,0, NULL);
+            if (env->ExceptionCheck()) {
+                env->ExceptionDescribe();
+                env->ExceptionClear();
+            }
+        }
+    }
+}
+
+
+// ----------------------------------------------------------------------------
+static int
+android_media_AudioTrack_native_setup(JNIEnv *env, jobject thiz, jobject weak_this,
+        jint streamType, jint sampleRateInHertz, jint nbChannels, 
+        jint audioFormat, jint buffSizeInBytes, jint memoryMode)
+{
+    LOGV("sampleRate=%d, audioFormat(from Java)=%d, nbChannels=%d, buffSize=%d", 
+        sampleRateInHertz, audioFormat, nbChannels, buffSizeInBytes);
+    int afSampleRate;
+    int afFrameCount;
+
+    if (AudioSystem::getOutputFrameCount(&afFrameCount, streamType) != NO_ERROR) {
+        LOGE("Error creating AudioTrack: Could not get AudioSystem frame count.");
+        return AUDIOTRACK_ERROR_SETUP_AUDIOSYSTEM;
+    }
+    if (AudioSystem::getOutputSamplingRate(&afSampleRate, streamType) != NO_ERROR) {
+        LOGE("Error creating AudioTrack: Could not get AudioSystem sampling rate.");
+        return AUDIOTRACK_ERROR_SETUP_AUDIOSYSTEM;
+    }
+
+    if ((nbChannels == 0) || (nbChannels > 2)) {
+        LOGE("Error creating AudioTrack: channel count is not 1 or 2.");
+        return AUDIOTRACK_ERROR_SETUP_INVALIDCHANNELCOUNT;
+    }
+    
+    // check the stream type
+    AudioSystem::stream_type atStreamType;
+    if (streamType == javaAudioTrackFields.STREAM_VOICE_CALL) {
+        atStreamType = AudioSystem::VOICE_CALL;
+    } else if (streamType == javaAudioTrackFields.STREAM_SYSTEM) {
+        atStreamType = AudioSystem::SYSTEM;
+    } else if (streamType == javaAudioTrackFields.STREAM_RING) {
+        atStreamType = AudioSystem::RING;
+    } else if (streamType == javaAudioTrackFields.STREAM_MUSIC) {
+        atStreamType = AudioSystem::MUSIC;
+    } else if (streamType == javaAudioTrackFields.STREAM_ALARM) {
+        atStreamType = AudioSystem::ALARM;
+    } else if (streamType == javaAudioTrackFields.STREAM_NOTIFICATION) {
+        atStreamType = AudioSystem::NOTIFICATION;
+    } else if (streamType == javaAudioTrackFields.STREAM_BLUETOOTH_SCO) {
+        atStreamType = AudioSystem::BLUETOOTH_SCO;
+    } else {
+        LOGE("Error creating AudioTrack: unknown stream type.");
+        return AUDIOTRACK_ERROR_SETUP_INVALIDSTREAMTYPE;
+    }
+
+    // check the format.
+    // This function was called from Java, so we compare the format against the Java constants
+    if ((audioFormat != javaAudioTrackFields.PCM16) && (audioFormat != javaAudioTrackFields.PCM8)) {
+        LOGE("Error creating AudioTrack: unsupported audio format.");
+        return AUDIOTRACK_ERROR_SETUP_INVALIDFORMAT;
+    }
+
+    // for the moment 8bitPCM in MODE_STATIC is not supported natively in the AudioTrack C++ class
+    // so we declare everything as 16bitPCM, the 8->16bit conversion for MODE_STATIC will be handled
+    // in android_media_AudioTrack_native_write()
+    if ((audioFormat == javaAudioTrackFields.PCM8) 
+        && (memoryMode == javaAudioTrackFields.MODE_STATIC)) {
+        LOGV("android_media_AudioTrack_native_setup(): requesting MODE_STATIC for 8bit \
+            buff size of %dbytes, switching to 16bit, buff size of %dbytes",
+            buffSizeInBytes, 2*buffSizeInBytes);
+        audioFormat = javaAudioTrackFields.PCM16;
+        // we will need twice the memory to store the data
+        buffSizeInBytes *= 2;
+    }
+
+    // compute the frame count
+    int bytesPerSample = audioFormat == javaAudioTrackFields.PCM16 ? 2 : 1;
+    int format = audioFormat == javaAudioTrackFields.PCM16 ? 
+            AudioSystem::PCM_16_BIT : AudioSystem::PCM_8_BIT;
+    int frameCount;
+    if (buffSizeInBytes == -1) {
+        // compute the frame count based on the system's output frame count 
+        // and the native sample rate
+        frameCount = (sampleRateInHertz*afFrameCount)/afSampleRate;
+    } else {
+        // compute the frame count based on the specified buffer size
+        frameCount = buffSizeInBytes / (nbChannels * bytesPerSample);
+    }
+    
+    AudioTrackJniStorage* lpJniStorage = new AudioTrackJniStorage();
+    
+    // initialize the callback information:
+    // this data will be passed with every AudioTrack callback
+    jclass clazz = env->GetObjectClass(thiz);
+    if (clazz == NULL) {
+        LOGE("Can't find %s when setting up callback.", kClassPathName);
+        delete lpJniStorage;
+        return AUDIOTRACK_ERROR_SETUP_NATIVEINITFAILED;
+    }
+    lpJniStorage->mCallbackData.audioTrack_class = (jclass)env->NewGlobalRef(clazz);
+    // we use a weak reference so the AudioTrack object can be garbage collected.
+    lpJniStorage->mCallbackData.audioTrack_ref = env->NewGlobalRef(weak_this);
+    
+    lpJniStorage->mStreamType = atStreamType;
+    
+    // create the native AudioTrack object
+    AudioTrack* lpTrack = new AudioTrack();
+    if (lpTrack == NULL) {
+        LOGE("Error creating uninitialized AudioTrack");
+        goto native_track_failure;
+    }
+    
+    // initialize the native AudioTrack object
+    if (memoryMode == javaAudioTrackFields.MODE_STREAM) {
+
+        lpTrack->set(
+            atStreamType,// stream type
+            sampleRateInHertz,
+            format,// word length, PCM
+            nbChannels,
+            frameCount,
+            0,// flags
+            audioCallback, &(lpJniStorage->mCallbackData),//callback, callback data (user)
+            0,// notificationFrames == 0 since not using EVENT_MORE_DATA to feed the AudioTrack
+            0,// shared mem
+            true);// thread can call Java
+            
+    } else if (memoryMode == javaAudioTrackFields.MODE_STATIC) {
+        // AudioTrack is using shared memory
+        
+        if (!lpJniStorage->allocSharedMem(buffSizeInBytes)) {
+            LOGE("Error creating AudioTrack in static mode: error creating mem heap base");
+            goto native_init_failure;
+        }
+        
+        lpTrack->set(
+            atStreamType,// stream type
+            sampleRateInHertz,
+            format,// word length, PCM
+            nbChannels,
+            frameCount,
+            0,// flags
+            audioCallback, &(lpJniStorage->mCallbackData),//callback, callback data (user));
+            0,// notificationFrames == 0 since not using EVENT_MORE_DATA to feed the AudioTrack 
+            lpJniStorage->mMemBase,// shared mem
+            true);// thread can call Java 
+    }
+
+    if (lpTrack->initCheck() != NO_ERROR) {
+        LOGE("Error initializing AudioTrack");
+        goto native_init_failure;
+    }
+
+    // save our newly created C++ AudioTrack in the "nativeTrackInJavaObj" field 
+    // of the Java object (in mNativeTrackInJavaObj)
+    env->SetIntField(thiz, javaAudioTrackFields.nativeTrackInJavaObj, (int)lpTrack);
+    
+    // save the JNI resources so we can free them later
+    //LOGV("storing lpJniStorage: %x\n", (int)lpJniStorage);
+    env->SetIntField(thiz, javaAudioTrackFields.jniData, (int)lpJniStorage);
+
+    return AUDIOTRACK_SUCCESS;
+    
+    // failures:
+native_init_failure:
+    delete lpTrack;
+    env->SetIntField(thiz, javaAudioTrackFields.nativeTrackInJavaObj, 0);
+    
+native_track_failure:
+    delete lpJniStorage;
+    env->SetIntField(thiz, javaAudioTrackFields.jniData, 0);
+    return AUDIOTRACK_ERROR_SETUP_NATIVEINITFAILED;
+    
+}
+
+
+// ----------------------------------------------------------------------------
+static void
+android_media_AudioTrack_start(JNIEnv *env, jobject thiz)
+{
+    AudioTrack *lpTrack = (AudioTrack *)env->GetIntField(
+        thiz, javaAudioTrackFields.nativeTrackInJavaObj);
+    if (lpTrack == NULL ) {
+        jniThrowException(env, "java/lang/IllegalStateException",
+            "Unable to retrieve AudioTrack pointer for start()");
+    }
+    
+    lpTrack->start();
+}
+
+
+// ----------------------------------------------------------------------------
+static void
+android_media_AudioTrack_stop(JNIEnv *env, jobject thiz)
+{
+    AudioTrack *lpTrack = (AudioTrack *)env->GetIntField(
+        thiz, javaAudioTrackFields.nativeTrackInJavaObj);
+    if (lpTrack == NULL ) {
+        jniThrowException(env, "java/lang/IllegalStateException",
+            "Unable to retrieve AudioTrack pointer for stop()");
+    }
+
+    lpTrack->stop();
+}
+
+
+// ----------------------------------------------------------------------------
+static void
+android_media_AudioTrack_pause(JNIEnv *env, jobject thiz)
+{
+    AudioTrack *lpTrack = (AudioTrack *)env->GetIntField(
+        thiz, javaAudioTrackFields.nativeTrackInJavaObj);
+    if (lpTrack == NULL ) {
+        jniThrowException(env, "java/lang/IllegalStateException",
+            "Unable to retrieve AudioTrack pointer for pause()");
+    }
+
+    lpTrack->pause();
+}
+
+
+// ----------------------------------------------------------------------------
+static void
+android_media_AudioTrack_flush(JNIEnv *env, jobject thiz)
+{
+    AudioTrack *lpTrack = (AudioTrack *)env->GetIntField(
+        thiz, javaAudioTrackFields.nativeTrackInJavaObj);
+    if (lpTrack == NULL ) {
+        jniThrowException(env, "java/lang/IllegalStateException",
+            "Unable to retrieve AudioTrack pointer for flush()");
+    }
+
+    lpTrack->flush();
+}
+
+// ----------------------------------------------------------------------------
+static void
+android_media_AudioTrack_set_volume(JNIEnv *env, jobject thiz, jfloat leftVol, jfloat rightVol )
+{
+    AudioTrack *lpTrack = (AudioTrack *)env->GetIntField(
+        thiz, javaAudioTrackFields.nativeTrackInJavaObj);
+    if (lpTrack == NULL ) {
+        jniThrowException(env, "java/lang/IllegalStateException",
+            "Unable to retrieve AudioTrack pointer for setVolume()");
+    }
+
+    lpTrack->setVolume(leftVol, rightVol);
+}
+
+// ----------------------------------------------------------------------------
+static void android_media_AudioTrack_native_finalize(JNIEnv *env,  jobject thiz) {
+    //LOGV("android_media_AudioTrack_native_finalize jobject: %x\n", (int)thiz);
+       
+    // delete the AudioTrack object
+    AudioTrack *lpTrack = (AudioTrack *)env->GetIntField(
+        thiz, javaAudioTrackFields.nativeTrackInJavaObj);
+    if (lpTrack) {
+        //LOGV("deleting lpTrack: %x\n", (int)lpTrack);
+        lpTrack->stop();
+        delete lpTrack;
+    }
+    
+    // delete the JNI data
+    AudioTrackJniStorage* pJniStorage = (AudioTrackJniStorage *)env->GetIntField(
+        thiz, javaAudioTrackFields.jniData);
+    if (pJniStorage) {
+        //LOGV("deleting pJniStorage: %x\n", (int)pJniStorage);
+        delete pJniStorage;
+    }
+}
+
+// ----------------------------------------------------------------------------
+static void android_media_AudioTrack_native_release(JNIEnv *env,  jobject thiz) {
+       
+    // do everything a call to finalize would
+    android_media_AudioTrack_native_finalize(env, thiz);
+    // + reset the native resources in the Java object so any attempt to access
+    // them after a call to release fails.
+    env->SetIntField(thiz, javaAudioTrackFields.nativeTrackInJavaObj, 0);
+    env->SetIntField(thiz, javaAudioTrackFields.jniData, 0);
+}
+
+
+// ----------------------------------------------------------------------------
+static jint android_media_AudioTrack_native_write(JNIEnv *env,  jobject thiz,
+                                                  jbyteArray javaAudioData,
+                                                  jint offsetInBytes, jint sizeInBytes,
+                                                  jint javaAudioFormat) {
+    jbyte* cAudioData = NULL;
+    AudioTrack *lpTrack = NULL;
+    //LOGV("android_media_AudioTrack_native_write(offset=%d, sizeInBytes=%d) called",
+    //    offsetInBytes, sizeInBytes);
+    
+    // get the audio track to load with samples
+    lpTrack = (AudioTrack *)env->GetIntField(thiz, javaAudioTrackFields.nativeTrackInJavaObj);
+    if (lpTrack == NULL) {
+        jniThrowException(env, "java/lang/IllegalStateException",
+            "Unable to retrieve AudioTrack pointer for write()");
+    }
+
+    // get the pointer for the audio data from the java array
+    if (javaAudioData) {
+        cAudioData = (jbyte *)env->GetPrimitiveArrayCritical(javaAudioData, NULL);
+        if (cAudioData == NULL) {
+            LOGE("Error retrieving source of audio data to play, can't play");
+            return 0; // out of memory or no data to load
+        }
+    } else {
+        LOGE("NULL java array of audio data to play, can't play");
+        return 0;
+    }
+
+    // give the data to the native AudioTrack object (the data starts at the offset)
+    ssize_t written = 0;
+    // regular write() or copy the data to the AudioTrack's shared memory?
+    if (lpTrack->sharedBuffer() == 0) {
+        written = lpTrack->write(cAudioData + offsetInBytes, sizeInBytes);
+    } else {
+        if (javaAudioFormat == javaAudioTrackFields.PCM16) {
+            memcpy(lpTrack->sharedBuffer()->pointer(), cAudioData + offsetInBytes, sizeInBytes);
+            written = sizeInBytes;
+        } else if (javaAudioFormat == javaAudioTrackFields.PCM8) {
+            // cAudioData contains 8bit data we need to expand to 16bit before copying
+            // to the shared memory
+            int count = sizeInBytes;
+            int16_t *dst = (int16_t *)lpTrack->sharedBuffer()->pointer();
+            const int8_t *src = (const int8_t *)(cAudioData + offsetInBytes);            
+            while(count--) {
+                *dst++ = (int16_t)(*src++^0x80) << 8;
+            }
+            // even though we wrote 2*sizeInBytes, we only report sizeInBytes as written to hide
+            // the 8bit mixer restriction from the user of this function
+            written = sizeInBytes;
+        }
+    }
+
+    env->ReleasePrimitiveArrayCritical(javaAudioData, cAudioData, 0);
+
+    //LOGV("write wrote %d (tried %d) bytes in the native AudioTrack with offset %d",
+    //     (int)written, (int)(sizeInBytes), (int)offsetInBytes);
+    return (int)written;
+}
+
+
+// ----------------------------------------------------------------------------
+static jint android_media_AudioTrack_native_write_short(JNIEnv *env,  jobject thiz,
+                                                  jshortArray javaAudioData,
+                                                  jint offsetInShorts, jint sizeInShorts,
+                                                  jint javaAudioFormat) {
+    return (android_media_AudioTrack_native_write(env, thiz,
+                                                 (jbyteArray) javaAudioData,
+                                                 offsetInShorts*2, sizeInShorts*2,
+                                                 javaAudioFormat)
+            / 2);
+}
+
+
+// ----------------------------------------------------------------------------
+static jint android_media_AudioTrack_get_native_frame_count(JNIEnv *env,  jobject thiz) {
+    AudioTrack *lpTrack = (AudioTrack *)env->GetIntField(
+        thiz, javaAudioTrackFields.nativeTrackInJavaObj);
+
+    if (lpTrack) {
+        return lpTrack->frameCount();
+    } else {
+        jniThrowException(env, "java/lang/IllegalStateException",
+            "Unable to retrieve AudioTrack pointer for frameCount()");
+        return AUDIOTRACK_ERROR;
+    }
+}
+
+
+// ----------------------------------------------------------------------------
+static void android_media_AudioTrack_set_playback_rate(JNIEnv *env,  jobject thiz,
+        jint sampleRateInHz) {
+    AudioTrack *lpTrack = (AudioTrack *)env->GetIntField(
+                thiz, javaAudioTrackFields.nativeTrackInJavaObj);
+
+    if (lpTrack) {
+        lpTrack->setSampleRate(sampleRateInHz);   
+    } else {
+        jniThrowException(env, "java/lang/IllegalStateException",
+            "Unable to retrieve AudioTrack pointer for setSampleRate()");
+    }
+}
+
+
+// ----------------------------------------------------------------------------
+static jint android_media_AudioTrack_get_playback_rate(JNIEnv *env,  jobject thiz) {
+    AudioTrack *lpTrack = (AudioTrack *)env->GetIntField(
+                thiz, javaAudioTrackFields.nativeTrackInJavaObj);
+
+    if (lpTrack) {
+        return (jint) lpTrack->getSampleRate();   
+    } else {
+        jniThrowException(env, "java/lang/IllegalStateException",
+            "Unable to retrieve AudioTrack pointer for getSampleRate()");
+        return AUDIOTRACK_ERROR;
+    }
+}
+
+
+// ----------------------------------------------------------------------------
+static jint android_media_AudioTrack_set_marker_pos(JNIEnv *env,  jobject thiz, 
+        jint markerPos) {
+            
+    AudioTrack *lpTrack = (AudioTrack *)env->GetIntField(
+                thiz, javaAudioTrackFields.nativeTrackInJavaObj);
+                
+    if (lpTrack) {
+        return android_media_translateErrorCode( lpTrack->setMarkerPosition(markerPos) );   
+    } else {
+        jniThrowException(env, "java/lang/IllegalStateException",
+            "Unable to retrieve AudioTrack pointer for setMarkerPosition()");
+        return AUDIOTRACK_ERROR;
+    }
+}
+
+
+// ----------------------------------------------------------------------------
+static jint android_media_AudioTrack_get_marker_pos(JNIEnv *env,  jobject thiz) {
+    
+    AudioTrack *lpTrack = (AudioTrack *)env->GetIntField(
+                thiz, javaAudioTrackFields.nativeTrackInJavaObj);
+    uint32_t markerPos = 0;
+                
+    if (lpTrack) {
+        lpTrack->getMarkerPosition(&markerPos);
+        return (jint)markerPos;
+    } else {
+        jniThrowException(env, "java/lang/IllegalStateException",
+            "Unable to retrieve AudioTrack pointer for getMarkerPosition()");
+        return AUDIOTRACK_ERROR;
+    }
+}
+
+
+// ----------------------------------------------------------------------------
+static jint android_media_AudioTrack_set_pos_update_period(JNIEnv *env,  jobject thiz,
+        jint period) {
+            
+    AudioTrack *lpTrack = (AudioTrack *)env->GetIntField(
+                thiz, javaAudioTrackFields.nativeTrackInJavaObj);
+                
+    if (lpTrack) {
+        return android_media_translateErrorCode( lpTrack->setPositionUpdatePeriod(period) );   
+    } else {
+        jniThrowException(env, "java/lang/IllegalStateException",
+            "Unable to retrieve AudioTrack pointer for setPositionUpdatePeriod()");
+        return AUDIOTRACK_ERROR;
+    }            
+}
+
+
+// ----------------------------------------------------------------------------
+static jint android_media_AudioTrack_get_pos_update_period(JNIEnv *env,  jobject thiz) {
+    
+    AudioTrack *lpTrack = (AudioTrack *)env->GetIntField(
+                thiz, javaAudioTrackFields.nativeTrackInJavaObj);
+    uint32_t period = 0;
+                
+    if (lpTrack) {
+        lpTrack->getPositionUpdatePeriod(&period);
+        return (jint)period;
+    } else {
+        jniThrowException(env, "java/lang/IllegalStateException",
+            "Unable to retrieve AudioTrack pointer for getPositionUpdatePeriod()");
+        return AUDIOTRACK_ERROR;
+    }
+}
+
+
+// ----------------------------------------------------------------------------
+static jint android_media_AudioTrack_set_position(JNIEnv *env,  jobject thiz, 
+        jint position) {
+            
+    AudioTrack *lpTrack = (AudioTrack *)env->GetIntField(
+                thiz, javaAudioTrackFields.nativeTrackInJavaObj);
+                
+    if (lpTrack) {
+        return android_media_translateErrorCode( lpTrack->setPosition(position) );
+    } else {
+        jniThrowException(env, "java/lang/IllegalStateException",
+            "Unable to retrieve AudioTrack pointer for setPosition()");
+        return AUDIOTRACK_ERROR;
+    }
+}
+
+
+// ----------------------------------------------------------------------------
+static jint android_media_AudioTrack_get_position(JNIEnv *env,  jobject thiz) {
+    
+    AudioTrack *lpTrack = (AudioTrack *)env->GetIntField(
+                thiz, javaAudioTrackFields.nativeTrackInJavaObj);
+    uint32_t position = 0;
+                
+    if (lpTrack) {
+        lpTrack->getPosition(&position);
+        return (jint)position;
+    }  else {
+        jniThrowException(env, "java/lang/IllegalStateException",
+            "Unable to retrieve AudioTrack pointer for getPosition()");
+        return AUDIOTRACK_ERROR;
+    }
+}
+
+
+// ----------------------------------------------------------------------------
+static jint android_media_AudioTrack_set_loop(JNIEnv *env,  jobject thiz,
+        jint loopStart, jint loopEnd, jint loopCount) {
+
+     AudioTrack *lpTrack = (AudioTrack *)env->GetIntField(
+                thiz, javaAudioTrackFields.nativeTrackInJavaObj);
+     if (lpTrack) {
+        return android_media_translateErrorCode( lpTrack->setLoop(loopStart, loopEnd, loopCount) );
+     }  else {
+        jniThrowException(env, "java/lang/IllegalStateException",
+            "Unable to retrieve AudioTrack pointer for setLoop()");
+        return AUDIOTRACK_ERROR;
+    }
+}
+
+
+// ----------------------------------------------------------------------------
+static jint android_media_AudioTrack_reload(JNIEnv *env,  jobject thiz) {
+
+     AudioTrack *lpTrack = (AudioTrack *)env->GetIntField(
+                thiz, javaAudioTrackFields.nativeTrackInJavaObj);
+     if (lpTrack) {
+        return android_media_translateErrorCode( lpTrack->reload() );
+     } else {
+        jniThrowException(env, "java/lang/IllegalStateException",
+            "Unable to retrieve AudioTrack pointer for reload()");
+        return AUDIOTRACK_ERROR;
+    }
+}
+
+
+// ----------------------------------------------------------------------------
+static jint android_media_AudioTrack_get_output_sample_rate(JNIEnv *env,  jobject thiz,
+        jint javaStreamType) {
+    int afSamplingRate;
+    // convert the stream type from Java to native value
+    // FIXME: code duplication with android_media_AudioTrack_native_setup()
+    AudioSystem::stream_type nativeStreamType;
+    if (javaStreamType == javaAudioTrackFields.STREAM_VOICE_CALL) {
+        nativeStreamType = AudioSystem::VOICE_CALL;
+    } else if (javaStreamType == javaAudioTrackFields.STREAM_SYSTEM) {
+        nativeStreamType = AudioSystem::SYSTEM;
+    } else if (javaStreamType == javaAudioTrackFields.STREAM_RING) {
+        nativeStreamType = AudioSystem::RING;
+    } else if (javaStreamType == javaAudioTrackFields.STREAM_MUSIC) {
+        nativeStreamType = AudioSystem::MUSIC;
+    } else if (javaStreamType == javaAudioTrackFields.STREAM_ALARM) {
+        nativeStreamType = AudioSystem::ALARM;
+    } else if (javaStreamType == javaAudioTrackFields.STREAM_NOTIFICATION) {
+        nativeStreamType = AudioSystem::NOTIFICATION;
+    } else if (javaStreamType == javaAudioTrackFields.STREAM_BLUETOOTH_SCO) {
+        nativeStreamType = AudioSystem::BLUETOOTH_SCO;
+    } else {
+        nativeStreamType = AudioSystem::DEFAULT;
+    }
+
+    if (AudioSystem::getOutputSamplingRate(&afSamplingRate, nativeStreamType) != NO_ERROR) {
+        LOGE("AudioSystem::getOutputSamplingRate() for stream type %d failed in AudioTrack JNI",
+            nativeStreamType);
+        return DEFAULT_OUTPUT_SAMPLE_RATE;
+    } else {
+        return afSamplingRate;
+    }
+}
+
+
+// ----------------------------------------------------------------------------
+// returns the minimum required size for the successful creation of a streaming AudioTrack
+// returns -1 if there was an error querying the hardware.
+static jint android_media_AudioTrack_get_min_buff_size(JNIEnv *env,  jobject thiz,
+    jint sampleRateInHertz, jint nbChannels, jint audioFormat) {
+    int afSamplingRate;
+    int afFrameCount;
+    uint32_t afLatency;
+    
+    if (AudioSystem::getOutputSamplingRate(&afSamplingRate) != NO_ERROR) {
+        return -1;
+    }
+    if (AudioSystem::getOutputFrameCount(&afFrameCount) != NO_ERROR) {
+        return -1;
+    }
+    
+    if (AudioSystem::getOutputLatency(&afLatency) != NO_ERROR) {
+        return -1;
+    }
+    
+    // Ensure that buffer depth covers at least audio hardware latency
+    uint32_t minBufCount = afLatency / ((1000 * afFrameCount)/afSamplingRate);
+    uint32_t minFrameCount = (afFrameCount*sampleRateInHertz*minBufCount)/afSamplingRate;
+    int minBuffSize = minFrameCount 
+            * (audioFormat == javaAudioTrackFields.PCM16 ? 2 : 1)
+            * nbChannels;
+    return minBuffSize;
+}
+
+
+// ----------------------------------------------------------------------------
+// ----------------------------------------------------------------------------
+static JNINativeMethod gMethods[] = {
+    // name,              signature,     funcPtr
+    {"native_start",         "()V",      (void *)android_media_AudioTrack_start},
+    {"native_stop",          "()V",      (void *)android_media_AudioTrack_stop},
+    {"native_pause",         "()V",      (void *)android_media_AudioTrack_pause},
+    {"native_flush",         "()V",      (void *)android_media_AudioTrack_flush},
+    {"native_setup",         "(Ljava/lang/Object;IIIIII)I", 
+                                         (void *)android_media_AudioTrack_native_setup},
+    {"native_finalize",      "()V",      (void *)android_media_AudioTrack_native_finalize},
+    {"native_release",       "()V",      (void *)android_media_AudioTrack_native_release},
+    {"native_write_byte",    "([BIII)I", (void *)android_media_AudioTrack_native_write},
+    {"native_write_short",   "([SIII)I", (void *)android_media_AudioTrack_native_write_short},
+    {"native_setVolume",     "(FF)V",    (void *)android_media_AudioTrack_set_volume},
+    {"native_get_native_frame_count",
+                             "()I",      (void *)android_media_AudioTrack_get_native_frame_count},
+    {"native_set_playback_rate",
+                             "(I)V",     (void *)android_media_AudioTrack_set_playback_rate},
+    {"native_get_playback_rate",
+                             "()I",      (void *)android_media_AudioTrack_get_playback_rate},
+    {"native_set_marker_pos","(I)I",     (void *)android_media_AudioTrack_set_marker_pos},
+    {"native_get_marker_pos","()I",      (void *)android_media_AudioTrack_get_marker_pos},
+    {"native_set_pos_update_period",
+                             "(I)I",     (void *)android_media_AudioTrack_set_pos_update_period},
+    {"native_get_pos_update_period",
+                             "()I",      (void *)android_media_AudioTrack_get_pos_update_period},
+    {"native_set_position",  "(I)I",     (void *)android_media_AudioTrack_set_position},
+    {"native_get_position",  "()I",      (void *)android_media_AudioTrack_get_position},
+    {"native_set_loop",      "(III)I",   (void *)android_media_AudioTrack_set_loop},
+    {"native_reload_static", "()I",      (void *)android_media_AudioTrack_reload},
+    {"native_get_output_sample_rate",
+                             "(I)I",      (void *)android_media_AudioTrack_get_output_sample_rate},
+    {"native_get_min_buff_size",
+                             "(III)I",   (void *)android_media_AudioTrack_get_min_buff_size},
+};
+
+
+// field names found in android/media/AudioTrack.java
+#define JAVA_POSTEVENT_CALLBACK_NAME                    "postEventFromNative"
+#define JAVA_CONST_PCM16_NAME                           "ENCODING_PCM_16BIT"
+#define JAVA_CONST_PCM8_NAME                            "ENCODING_PCM_8BIT"
+#define JAVA_CONST_BUFFER_COUNT_NAME                    "BUFFER_COUNT"
+#define JAVA_CONST_STREAM_VOICE_CALL_NAME               "STREAM_VOICE_CALL"
+#define JAVA_CONST_STREAM_SYSTEM_NAME                   "STREAM_SYSTEM"
+#define JAVA_CONST_STREAM_RING_NAME                     "STREAM_RING"
+#define JAVA_CONST_STREAM_MUSIC_NAME                    "STREAM_MUSIC"
+#define JAVA_CONST_STREAM_ALARM_NAME                    "STREAM_ALARM"
+#define JAVA_CONST_STREAM_NOTIFICATION_NAME             "STREAM_NOTIFICATION"
+#define JAVA_CONST_STREAM_BLUETOOTH_SCO_NAME            "STREAM_BLUETOOTH_SCO"
+#define JAVA_CONST_MODE_STREAM_NAME                     "MODE_STREAM"
+#define JAVA_CONST_MODE_STATIC_NAME                     "MODE_STATIC"
+#define JAVA_NATIVETRACKINJAVAOBJ_FIELD_NAME            "mNativeTrackInJavaObj"
+#define JAVA_JNIDATA_FIELD_NAME                         "mJniData"
+
+#define JAVA_AUDIOFORMAT_CLASS_NAME             "android/media/AudioFormat"
+#define JAVA_AUDIOMANAGER_CLASS_NAME            "android/media/AudioManager"
+
+// ----------------------------------------------------------------------------
+// preconditions:
+//    theClass is valid
+bool android_media_getIntConstantFromClass(JNIEnv* pEnv, jclass theClass, const char* className,
+                             const char* constName, int* constVal) {
+    jfieldID javaConst = NULL;
+    javaConst = pEnv->GetStaticFieldID(theClass, constName, "I");
+    if (javaConst != NULL) {
+        *constVal = pEnv->GetStaticIntField(theClass, javaConst);
+        return true;
+    } else {
+        LOGE("Can't find %s.%s", className, constName);
+        return false;
+    }
+}
+
+
+// ----------------------------------------------------------------------------
+int register_android_media_AudioTrack(JNIEnv *env)
+{
+    javaAudioTrackFields.audioTrackClass = NULL;
+    javaAudioTrackFields.nativeTrackInJavaObj = NULL;
+    javaAudioTrackFields.postNativeEventInJava = NULL;
+
+    // Get the AudioTrack class
+    javaAudioTrackFields.audioTrackClass = env->FindClass(kClassPathName);
+    if (javaAudioTrackFields.audioTrackClass == NULL) {
+        LOGE("Can't find %s", kClassPathName);
+        return -1;
+    }
+
+    // Get the postEvent method
+    javaAudioTrackFields.postNativeEventInJava = env->GetStaticMethodID(
+            javaAudioTrackFields.audioTrackClass,
+            JAVA_POSTEVENT_CALLBACK_NAME, "(Ljava/lang/Object;IIILjava/lang/Object;)V");
+    if (javaAudioTrackFields.postNativeEventInJava == NULL) {
+        LOGE("Can't find AudioTrack.%s", JAVA_POSTEVENT_CALLBACK_NAME);
+        return -1;
+    }
+
+    // Get the variables fields
+    //      nativeTrackInJavaObj
+    javaAudioTrackFields.nativeTrackInJavaObj = env->GetFieldID(
+            javaAudioTrackFields.audioTrackClass,
+            JAVA_NATIVETRACKINJAVAOBJ_FIELD_NAME, "I");
+    if (javaAudioTrackFields.nativeTrackInJavaObj == NULL) {
+        LOGE("Can't find AudioTrack.%s", JAVA_NATIVETRACKINJAVAOBJ_FIELD_NAME);
+        return -1;
+    }
+    //      jniData;
+    javaAudioTrackFields.jniData = env->GetFieldID(
+            javaAudioTrackFields.audioTrackClass,
+            JAVA_JNIDATA_FIELD_NAME, "I");
+    if (javaAudioTrackFields.jniData == NULL) {
+        LOGE("Can't find AudioTrack.%s", JAVA_JNIDATA_FIELD_NAME);
+        return -1;
+    }
+
+    // Get the memory mode constants
+    if ( !android_media_getIntConstantFromClass(env, javaAudioTrackFields.audioTrackClass,
+               kClassPathName, 
+               JAVA_CONST_MODE_STATIC_NAME, &(javaAudioTrackFields.MODE_STATIC))
+         || !android_media_getIntConstantFromClass(env, javaAudioTrackFields.audioTrackClass,
+               kClassPathName, 
+               JAVA_CONST_MODE_STREAM_NAME, &(javaAudioTrackFields.MODE_STREAM)) ) {
+        // error log performed in android_media_getIntConstantFromClass() 
+        return -1;
+    }
+
+    // Get the format constants from the AudioFormat class
+    jclass audioFormatClass = NULL;
+    audioFormatClass = env->FindClass(JAVA_AUDIOFORMAT_CLASS_NAME);
+    if (audioFormatClass == NULL) {
+        LOGE("Can't find %s", JAVA_AUDIOFORMAT_CLASS_NAME);
+        return -1;
+    }
+    if ( !android_media_getIntConstantFromClass(env, audioFormatClass, 
+                JAVA_AUDIOFORMAT_CLASS_NAME, 
+                JAVA_CONST_PCM16_NAME, &(javaAudioTrackFields.PCM16))
+           || !android_media_getIntConstantFromClass(env, audioFormatClass, 
+                JAVA_AUDIOFORMAT_CLASS_NAME, 
+                JAVA_CONST_PCM8_NAME, &(javaAudioTrackFields.PCM8)) ) {
+        // error log performed in android_media_getIntConstantFromClass() 
+        return -1;
+    }
+ 
+    // Get the stream types from the AudioManager class
+    jclass audioManagerClass = NULL;
+    audioManagerClass = env->FindClass(JAVA_AUDIOMANAGER_CLASS_NAME);
+    if (audioManagerClass == NULL) {
+       LOGE("Can't find %s", JAVA_AUDIOMANAGER_CLASS_NAME);
+       return -1;
+    }
+    if ( !android_media_getIntConstantFromClass(env, audioManagerClass,
+               JAVA_AUDIOMANAGER_CLASS_NAME,
+               JAVA_CONST_STREAM_VOICE_CALL_NAME, &(javaAudioTrackFields.STREAM_VOICE_CALL))
+          || !android_media_getIntConstantFromClass(env, audioManagerClass,
+               JAVA_AUDIOMANAGER_CLASS_NAME,
+               JAVA_CONST_STREAM_MUSIC_NAME, &(javaAudioTrackFields.STREAM_MUSIC))
+          || !android_media_getIntConstantFromClass(env, audioManagerClass,
+               JAVA_AUDIOMANAGER_CLASS_NAME,
+               JAVA_CONST_STREAM_SYSTEM_NAME, &(javaAudioTrackFields.STREAM_SYSTEM))
+          || !android_media_getIntConstantFromClass(env, audioManagerClass,
+               JAVA_AUDIOMANAGER_CLASS_NAME,
+               JAVA_CONST_STREAM_RING_NAME, &(javaAudioTrackFields.STREAM_RING))
+          || !android_media_getIntConstantFromClass(env, audioManagerClass,
+               JAVA_AUDIOMANAGER_CLASS_NAME,
+               JAVA_CONST_STREAM_ALARM_NAME, &(javaAudioTrackFields.STREAM_ALARM))
+          || !android_media_getIntConstantFromClass(env, audioManagerClass,
+               JAVA_AUDIOMANAGER_CLASS_NAME,
+               JAVA_CONST_STREAM_NOTIFICATION_NAME, &(javaAudioTrackFields.STREAM_NOTIFICATION))
+          || !android_media_getIntConstantFromClass(env, audioManagerClass,
+               JAVA_AUDIOMANAGER_CLASS_NAME,
+               JAVA_CONST_STREAM_BLUETOOTH_SCO_NAME,
+               &(javaAudioTrackFields.STREAM_BLUETOOTH_SCO))) {
+       // error log performed in android_media_getIntConstantFromClass()
+       return -1;
+    }
+
+    return AndroidRuntime::registerNativeMethods(env, kClassPathName, gMethods, NELEM(gMethods));
+}
+
+
+// ----------------------------------------------------------------------------
diff --git a/core/jni/android_media_JetPlayer.cpp b/core/jni/android_media_JetPlayer.cpp
new file mode 100644
index 0000000..e345af6
--- /dev/null
+++ b/core/jni/android_media_JetPlayer.cpp
@@ -0,0 +1,542 @@
+/*
+ * 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.
+ */
+
+//FIXME: remove log before release
+#define LOG_NDEBUG 0
+#define LOG_TAG "JET_JNI"
+
+
+#include <stdio.h>
+#include <unistd.h>
+#include <fcntl.h>
+
+#include "jni.h"
+#include "JNIHelp.h"
+#include "android_runtime/AndroidRuntime.h"
+
+#include "utils/Log.h"
+#include "media/JetPlayer.h"
+
+
+using namespace android;
+
+// ----------------------------------------------------------------------------
+static const char* const kClassPathName = "android/media/JetPlayer";
+
+// ----------------------------------------------------------------------------
+struct fields_t {
+    // these fields provide access from C++ to the...
+    jclass    jetClass;              // JetPlayer java class global ref
+    jmethodID postNativeEventInJava; // java method to post events to the Java thread from native
+    jfieldID  nativePlayerInJavaObj; // stores in Java the native JetPlayer object
+};
+
+static fields_t javaJetPlayerFields;
+
+
+// ----------------------------------------------------------------------------
+// ----------------------------------------------------------------------------
+
+/*
+ * This function is called from JetPlayer instance's render thread
+ */
+static void
+jetPlayerEventCallback(int what, int arg1=0, int arg2=0, void* javaTarget = NULL)
+{
+    JNIEnv *env = AndroidRuntime::getJNIEnv();
+    if(env) {
+        env->CallStaticVoidMethod(
+            javaJetPlayerFields.jetClass, javaJetPlayerFields.postNativeEventInJava,
+            javaTarget,
+            what, arg1, arg2);
+        if (env->ExceptionCheck()) {
+            env->ExceptionDescribe();
+            env->ExceptionClear();
+        }
+    } else {
+        LOGE("JET jetPlayerEventCallback(): No JNI env for JET event callback, can't post event.");
+        return;
+    }
+}
+
+
+// ----------------------------------------------------------------------------
+// ----------------------------------------------------------------------------
+
+static jboolean
+android_media_JetPlayer_setup(JNIEnv *env, jobject thiz, jobject weak_this,
+    jint maxTracks, jint trackBufferSize)
+{
+    //LOGV("android_media_JetPlayer_setup(): entering.");
+    JetPlayer* lpJet = new JetPlayer(env->NewGlobalRef(weak_this), maxTracks, trackBufferSize);
+
+    EAS_RESULT result = lpJet->init();
+
+    if(result==EAS_SUCCESS) {
+        // save our newly created C++ JetPlayer in the "nativePlayerInJavaObj" field 
+        // of the Java object (in mNativePlayerInJavaObj)
+        env->SetIntField(thiz, javaJetPlayerFields.nativePlayerInJavaObj, (int)lpJet);
+        return JNI_TRUE;
+    } else {
+        LOGE("android_media_JetPlayer_setup(): initialization failed with EAS error code %d", (int)result);
+        delete lpJet;
+        env->SetIntField(weak_this, javaJetPlayerFields.nativePlayerInJavaObj, 0);
+        return JNI_FALSE;
+    }
+}
+
+
+// ----------------------------------------------------------------------------
+static void
+android_media_JetPlayer_finalize(JNIEnv *env, jobject thiz)
+{
+    LOGV("android_media_JetPlayer_finalize(): entering.");
+    JetPlayer *lpJet = (JetPlayer *)env->GetIntField(
+        thiz, javaJetPlayerFields.nativePlayerInJavaObj);
+    if(lpJet != NULL) {
+        lpJet->release();
+        delete lpJet;
+    }
+
+    LOGV("android_media_JetPlayer_finalize(): exiting.");
+}
+
+
+// ----------------------------------------------------------------------------
+static void
+android_media_JetPlayer_release(JNIEnv *env, jobject thiz)
+{
+    android_media_JetPlayer_finalize(env, thiz);
+    env->SetIntField(thiz, javaJetPlayerFields.nativePlayerInJavaObj, 0);
+    LOGV("android_media_JetPlayer_release() done");   
+}
+
+
+// ----------------------------------------------------------------------------
+static jboolean
+android_media_JetPlayer_loadFromFile(JNIEnv *env, jobject thiz, jstring path)
+{
+    JetPlayer *lpJet = (JetPlayer *)env->GetIntField(
+        thiz, javaJetPlayerFields.nativePlayerInJavaObj);
+    if (lpJet == NULL ) {
+        jniThrowException(env, "java/lang/IllegalStateException",
+            "Unable to retrieve JetPlayer pointer for openFile()");
+    }
+    
+    // set up event callback function
+    lpJet->setEventCallback(jetPlayerEventCallback);
+
+    const char *pathStr = env->GetStringUTFChars(path, NULL);
+    if (pathStr == NULL) {  // Out of memory
+        LOGE("android_media_JetPlayer_openFile(): aborting, out of memory");
+        jniThrowException(env, "java/lang/RuntimeException", "Out of memory");
+        return JNI_FALSE;
+    }
+
+    LOGV("android_media_JetPlayer_openFile(): trying to open %s", pathStr );
+    EAS_RESULT result = lpJet->loadFromFile(pathStr);
+    env->ReleaseStringUTFChars(path, pathStr);
+
+    if(result==EAS_SUCCESS) {
+        //LOGV("android_media_JetPlayer_openFile(): file successfully opened");
+        return JNI_TRUE;
+    } else {
+        LOGE("android_media_JetPlayer_openFile(): failed to open file with EAS error %d",
+            (int)result);
+        return JNI_FALSE;
+    }
+}
+
+
+// ----------------------------------------------------------------------------
+static jboolean
+android_media_JetPlayer_loadFromFileD(JNIEnv *env, jobject thiz,
+    jobject fileDescriptor, jlong offset, jlong length)
+{
+    JetPlayer *lpJet = (JetPlayer *)env->GetIntField(
+        thiz, javaJetPlayerFields.nativePlayerInJavaObj);
+    if (lpJet == NULL ) {
+        jniThrowException(env, "java/lang/IllegalStateException",
+            "Unable to retrieve JetPlayer pointer for openFile()");
+    }
+    
+    // set up event callback function
+    lpJet->setEventCallback(jetPlayerEventCallback);
+    
+    LOGV("android_media_JetPlayer_openFileDescr(): trying to load JET file through its fd" );
+    EAS_RESULT result = lpJet->loadFromFD(getParcelFileDescriptorFD(env, fileDescriptor),
+        (long long)offset, (long long)length); // cast params to types used by EAS_FILE
+    
+    if(result==EAS_SUCCESS) {
+        LOGV("android_media_JetPlayer_openFileDescr(): file successfully opened");
+        return JNI_TRUE;
+    } else {
+        LOGE("android_media_JetPlayer_openFileDescr(): failed to open file with EAS error %d",
+            (int)result);
+        return JNI_FALSE;
+    }
+}
+
+
+// ----------------------------------------------------------------------------
+static jboolean
+android_media_JetPlayer_closeFile(JNIEnv *env, jobject thiz)
+{
+    JetPlayer *lpJet = (JetPlayer *)env->GetIntField(
+        thiz, javaJetPlayerFields.nativePlayerInJavaObj);
+    if (lpJet == NULL ) {
+        jniThrowException(env, "java/lang/IllegalStateException",
+            "Unable to retrieve JetPlayer pointer for closeFile()");
+    }
+    
+    if( lpJet->closeFile()==EAS_SUCCESS) {
+        //LOGV("android_media_JetPlayer_closeFile(): file successfully closed");
+        return JNI_TRUE;
+    } else {
+        LOGE("android_media_JetPlayer_closeFile(): failed to close file");
+        return JNI_FALSE;
+    }
+}
+
+
+// ----------------------------------------------------------------------------
+static jboolean
+android_media_JetPlayer_play(JNIEnv *env, jobject thiz)
+{
+    JetPlayer *lpJet = (JetPlayer *)env->GetIntField(
+        thiz, javaJetPlayerFields.nativePlayerInJavaObj);
+    if (lpJet == NULL ) {
+        jniThrowException(env, "java/lang/IllegalStateException",
+            "Unable to retrieve JetPlayer pointer for play()");
+    }
+    
+    EAS_RESULT result = lpJet->play();
+    if( result==EAS_SUCCESS) {
+        //LOGV("android_media_JetPlayer_play(): play successful");
+        return JNI_TRUE;
+    } else {
+        LOGE("android_media_JetPlayer_play(): failed to play with EAS error code %ld", 
+            result);
+        return JNI_FALSE;
+    }
+}
+
+
+// ----------------------------------------------------------------------------
+static jboolean
+android_media_JetPlayer_pause(JNIEnv *env, jobject thiz)
+{
+    JetPlayer *lpJet = (JetPlayer *)env->GetIntField(
+        thiz, javaJetPlayerFields.nativePlayerInJavaObj);
+    if (lpJet == NULL ) {
+        jniThrowException(env, "java/lang/IllegalStateException",
+            "Unable to retrieve JetPlayer pointer for pause()");
+    }
+    
+    EAS_RESULT result = lpJet->pause();
+    if( result==EAS_SUCCESS) {
+        //LOGV("android_media_JetPlayer_pause(): pause successful");
+        return JNI_TRUE;
+    } else {
+        if(result==EAS_ERROR_QUEUE_IS_EMPTY) {
+            LOGV("android_media_JetPlayer_pause(): paused with an empty queue");
+            return JNI_TRUE;
+        } else
+            LOGE("android_media_JetPlayer_pause(): failed to pause with EAS error code %ld",
+                result);
+        return JNI_FALSE;
+    }
+}
+
+
+// ----------------------------------------------------------------------------
+static jboolean
+android_media_JetPlayer_queueSegment(JNIEnv *env, jobject thiz,
+        jint segmentNum, jint libNum, jint repeatCount, jint transpose, jint muteFlags,
+        jbyte userID)
+{
+    JetPlayer *lpJet = (JetPlayer *)env->GetIntField(
+        thiz, javaJetPlayerFields.nativePlayerInJavaObj);
+    if (lpJet == NULL ) {
+        jniThrowException(env, "java/lang/IllegalStateException",
+            "Unable to retrieve JetPlayer pointer for queueSegment()");
+    }
+    
+    EAS_RESULT result
+        = lpJet->queueSegment(segmentNum, libNum, repeatCount, transpose, muteFlags, userID);
+    if(result==EAS_SUCCESS) {
+        //LOGV("android_media_JetPlayer_queueSegment(): segment successfully queued");
+        return JNI_TRUE;
+    } else {
+        LOGE("android_media_JetPlayer_queueSegment(): failed with EAS error code %ld",
+            result);
+        return JNI_FALSE;
+    }
+}
+
+
+// ----------------------------------------------------------------------------
+static jboolean
+android_media_JetPlayer_queueSegmentMuteArray(JNIEnv *env, jobject thiz,
+        jint segmentNum, jint libNum, jint repeatCount, jint transpose, jbooleanArray muteArray,
+        jbyte userID)
+{
+    JetPlayer *lpJet = (JetPlayer *)env->GetIntField(
+        thiz, javaJetPlayerFields.nativePlayerInJavaObj);
+    if (lpJet == NULL ) {
+        jniThrowException(env, "java/lang/IllegalStateException",
+            "Unable to retrieve JetPlayer pointer for queueSegmentMuteArray()");
+    }
+    
+    EAS_RESULT result=EAS_FAILURE;
+
+    jboolean *muteTracks = NULL;
+    muteTracks = env->GetBooleanArrayElements(muteArray, NULL);
+    if (muteTracks == NULL) {
+        LOGE("android_media_JetPlayer_queueSegment(): failed to read track mute mask.");
+        return JNI_FALSE;
+    }
+
+    EAS_U32 muteMask=0;
+    int maxTracks = lpJet->getMaxTracks();
+    for (jint trackIndex=0; trackIndex<maxTracks; trackIndex++) {
+        if(muteTracks[maxTracks-1-trackIndex]==JNI_TRUE)
+            muteMask = (muteMask << 1) | 0x00000001;
+        else
+            muteMask = muteMask << 1;
+    }
+    //LOGV("android_media_JetPlayer_queueSegmentMuteArray(): FINAL mute mask =0x%08lX", mask);
+
+    result = lpJet->queueSegment(segmentNum, libNum, repeatCount, transpose, muteMask, userID);
+
+    env->ReleaseBooleanArrayElements(muteArray, muteTracks, 0);
+    if(result==EAS_SUCCESS) {
+        //LOGV("android_media_JetPlayer_queueSegmentMuteArray(): segment successfully queued");
+        return JNI_TRUE;
+    } else {
+        LOGE("android_media_JetPlayer_queueSegmentMuteArray(): failed with EAS error code %ld", 
+            result);
+        return JNI_FALSE;
+    }
+}
+
+
+// ----------------------------------------------------------------------------
+static jboolean
+android_media_JetPlayer_setMuteFlags(JNIEnv *env, jobject thiz,
+         jint muteFlags /*unsigned?*/, jboolean bSync)
+{
+    JetPlayer *lpJet = (JetPlayer *)env->GetIntField(
+        thiz, javaJetPlayerFields.nativePlayerInJavaObj);
+    if (lpJet == NULL ) {
+        jniThrowException(env, "java/lang/IllegalStateException",
+            "Unable to retrieve JetPlayer pointer for setMuteFlags()");
+    }
+    
+    EAS_RESULT result;
+    result = lpJet->setMuteFlags(muteFlags, bSync==JNI_TRUE ? true : false);
+    if(result==EAS_SUCCESS) {
+        //LOGV("android_media_JetPlayer_setMuteFlags(): mute flags successfully updated");
+        return JNI_TRUE;
+    } else {
+        LOGE("android_media_JetPlayer_setMuteFlags(): failed with EAS error code %ld", result);
+        return JNI_FALSE;
+    }
+}
+
+
+// ----------------------------------------------------------------------------
+static jboolean
+android_media_JetPlayer_setMuteArray(JNIEnv *env, jobject thiz,
+        jbooleanArray muteArray, jboolean bSync)
+{
+    JetPlayer *lpJet = (JetPlayer *)env->GetIntField(
+        thiz, javaJetPlayerFields.nativePlayerInJavaObj);
+    if (lpJet == NULL ) {
+        jniThrowException(env, "java/lang/IllegalStateException",
+            "Unable to retrieve JetPlayer pointer for setMuteArray()");
+    }
+    
+    EAS_RESULT result=EAS_FAILURE;
+
+    jboolean *muteTracks = NULL;
+    muteTracks = env->GetBooleanArrayElements(muteArray, NULL);
+    if (muteTracks == NULL) {
+        LOGE("android_media_JetPlayer_setMuteArray(): failed to read track mute mask.");
+        return JNI_FALSE;
+    }
+
+    EAS_U32 muteMask=0;
+    int maxTracks = lpJet->getMaxTracks();
+    for (jint trackIndex=0; trackIndex<maxTracks; trackIndex++) {
+        if(muteTracks[maxTracks-1-trackIndex]==JNI_TRUE)
+            muteMask = (muteMask << 1) | 0x00000001;
+        else
+            muteMask = muteMask << 1;
+    }
+    //LOGV("android_media_JetPlayer_setMuteArray(): FINAL mute mask =0x%08lX", muteMask);
+
+    result = lpJet->setMuteFlags(muteMask, bSync==JNI_TRUE ? true : false);
+
+    env->ReleaseBooleanArrayElements(muteArray, muteTracks, 0);
+    if(result==EAS_SUCCESS) {
+        //LOGV("android_media_JetPlayer_setMuteArray(): mute flags successfully updated");
+        return JNI_TRUE;
+    } else {
+        LOGE("android_media_JetPlayer_setMuteArray(): \
+            failed to update mute flags with EAS error code %ld", result);
+        return JNI_FALSE;
+    }
+}
+
+
+// ----------------------------------------------------------------------------
+static jboolean
+android_media_JetPlayer_setMuteFlag(JNIEnv *env, jobject thiz,
+         jint trackId, jboolean muteFlag, jboolean bSync)
+{
+    JetPlayer *lpJet = (JetPlayer *)env->GetIntField(
+        thiz, javaJetPlayerFields.nativePlayerInJavaObj);
+    if (lpJet == NULL ) {
+        jniThrowException(env, "java/lang/IllegalStateException",
+            "Unable to retrieve JetPlayer pointer for setMuteFlag()");
+    }
+    
+    EAS_RESULT result;
+    result = lpJet->setMuteFlag(trackId, 
+        muteFlag==JNI_TRUE ? true : false, bSync==JNI_TRUE ? true : false);
+    if(result==EAS_SUCCESS) {
+        //LOGV("android_media_JetPlayer_setMuteFlag(): mute flag successfully updated for track %d", trackId);
+        return JNI_TRUE;
+    } else {
+        LOGE("android_media_JetPlayer_setMuteFlag(): failed to update mute flag for track %d with EAS error code %ld",
+                trackId, result);
+        return JNI_FALSE;
+    }
+}
+
+
+// ----------------------------------------------------------------------------
+static jboolean
+android_media_JetPlayer_triggerClip(JNIEnv *env, jobject thiz, jint clipId)
+{
+    JetPlayer *lpJet = (JetPlayer *)env->GetIntField(
+        thiz, javaJetPlayerFields.nativePlayerInJavaObj);
+    if (lpJet == NULL ) {
+        jniThrowException(env, "java/lang/IllegalStateException",
+            "Unable to retrieve JetPlayer pointer for triggerClip()");
+    }
+    
+    EAS_RESULT result;
+    result = lpJet->triggerClip(clipId);
+    if(result==EAS_SUCCESS) {
+        //LOGV("android_media_JetPlayer_triggerClip(): triggerClip successful for clip %d", clipId);
+        return JNI_TRUE;
+    } else {
+        LOGE("android_media_JetPlayer_triggerClip(): triggerClip for clip %d failed with EAS error code %ld",
+                clipId, result);
+        return JNI_FALSE;
+    }
+}
+
+
+// ----------------------------------------------------------------------------
+static jboolean
+android_media_JetPlayer_clearQueue(JNIEnv *env, jobject thiz)
+{
+    JetPlayer *lpJet = (JetPlayer *)env->GetIntField(
+        thiz, javaJetPlayerFields.nativePlayerInJavaObj);
+    if (lpJet == NULL ) {
+        jniThrowException(env, "java/lang/IllegalStateException",
+            "Unable to retrieve JetPlayer pointer for clearQueue()");
+    }
+    
+    EAS_RESULT result = lpJet->clearQueue();
+    if(result==EAS_SUCCESS) {
+        //LOGV("android_media_JetPlayer_clearQueue(): clearQueue successful");
+        return JNI_TRUE;
+    } else {
+        LOGE("android_media_JetPlayer_clearQueue(): clearQueue failed with EAS error code %ld",
+                result);
+        return JNI_FALSE;
+    }
+}
+
+
+// ----------------------------------------------------------------------------
+// ----------------------------------------------------------------------------
+static JNINativeMethod gMethods[] = {
+    // name,               signature,               funcPtr
+    {"native_setup",       "(Ljava/lang/Object;II)Z", (void *)android_media_JetPlayer_setup},
+    {"native_finalize",    "()V",                   (void *)android_media_JetPlayer_finalize},
+    {"native_release",     "()V",                   (void *)android_media_JetPlayer_release},
+    {"native_loadJetFromFile", 
+                           "(Ljava/lang/String;)Z", (void *)android_media_JetPlayer_loadFromFile},
+    {"native_loadJetFromFileD", "(Ljava/io/FileDescriptor;JJ)Z",
+                                                    (void *)android_media_JetPlayer_loadFromFileD},
+    {"native_closeJetFile","()Z",                   (void *)android_media_JetPlayer_closeFile},
+    {"native_playJet",     "()Z",                   (void *)android_media_JetPlayer_play},
+    {"native_pauseJet",    "()Z",                   (void *)android_media_JetPlayer_pause},
+    {"native_queueJetSegment",  
+                           "(IIIIIB)Z",             (void *)android_media_JetPlayer_queueSegment},
+    {"native_queueJetSegmentMuteArray", 
+                           "(IIII[ZB)Z",     (void *)android_media_JetPlayer_queueSegmentMuteArray},
+    {"native_setMuteFlags","(IZ)Z",                 (void *)android_media_JetPlayer_setMuteFlags},
+    {"native_setMuteArray","([ZZ)Z",                (void *)android_media_JetPlayer_setMuteArray},
+    {"native_setMuteFlag", "(IZZ)Z",                (void *)android_media_JetPlayer_setMuteFlag},
+    {"native_triggerClip", "(I)Z",                  (void *)android_media_JetPlayer_triggerClip},
+    {"native_clearQueue",  "()Z",                   (void *)android_media_JetPlayer_clearQueue},
+};
+
+#define JAVA_NATIVEJETPLAYERINJAVAOBJ_FIELD_NAME "mNativePlayerInJavaObj"
+#define JAVA_NATIVEJETPOSTEVENT_CALLBACK_NAME "postEventFromNative"
+
+
+int register_android_media_JetPlayer(JNIEnv *env)
+{
+    jclass jetPlayerClass = NULL;
+    javaJetPlayerFields.jetClass = NULL;
+    javaJetPlayerFields.postNativeEventInJava = NULL;
+    javaJetPlayerFields.nativePlayerInJavaObj = NULL;
+    
+    // Get the JetPlayer java class
+    jetPlayerClass = env->FindClass(kClassPathName);
+    if (jetPlayerClass == NULL) {
+        LOGE("Can't find %s", kClassPathName);
+        return -1;
+    }
+    javaJetPlayerFields.jetClass = (jclass)env->NewGlobalRef(jetPlayerClass);
+
+    // Get the mNativePlayerInJavaObj variable field
+    javaJetPlayerFields.nativePlayerInJavaObj = env->GetFieldID(
+            jetPlayerClass,
+            JAVA_NATIVEJETPLAYERINJAVAOBJ_FIELD_NAME, "I");
+    if (javaJetPlayerFields.nativePlayerInJavaObj == NULL) {
+        LOGE("Can't find AudioTrack.%s", JAVA_NATIVEJETPLAYERINJAVAOBJ_FIELD_NAME);
+        return -1;
+    }
+
+    // Get the callback to post events from this native code to Java
+    javaJetPlayerFields.postNativeEventInJava = env->GetStaticMethodID(javaJetPlayerFields.jetClass,
+            JAVA_NATIVEJETPOSTEVENT_CALLBACK_NAME, "(Ljava/lang/Object;III)V");
+    if (javaJetPlayerFields.postNativeEventInJava == NULL) {
+        LOGE("Can't find Jet.%s", JAVA_NATIVEJETPOSTEVENT_CALLBACK_NAME);
+        return -1;
+    }
+
+    return AndroidRuntime::registerNativeMethods(env,
+            kClassPathName, gMethods, NELEM(gMethods));
+}
diff --git a/core/jni/android_media_ToneGenerator.cpp b/core/jni/android_media_ToneGenerator.cpp
new file mode 100644
index 0000000..a4388de
--- /dev/null
+++ b/core/jni/android_media_ToneGenerator.cpp
@@ -0,0 +1,149 @@
+/* //device/libs/android_runtime/android_media_AudioSystem.cpp
+ **
+ ** Copyright 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.
+ */
+
+#define LOG_TAG "ToneGenerator"
+
+#include <stdio.h>
+#include <unistd.h>
+#include <fcntl.h>
+
+#include "jni.h"
+#include "JNIHelp.h"
+#include "android_runtime/AndroidRuntime.h"
+
+#include "utils/Log.h"
+#include "media/AudioSystem.h"
+#include "media/ToneGenerator.h"
+
+// ----------------------------------------------------------------------------
+
+using namespace android;
+
+struct fields_t {
+    jfieldID context;
+};
+static fields_t fields;
+
+static jboolean android_media_ToneGenerator_startTone(JNIEnv *env, jobject thiz, jint toneType) {
+    LOGV("android_media_ToneGenerator_startTone: %x\n", (int)thiz);
+
+    ToneGenerator *lpToneGen = (ToneGenerator *)env->GetIntField(thiz,
+            fields.context);
+    if (lpToneGen == NULL) {
+        jniThrowException(env, "java/lang/RuntimeException", "Method called after release()");
+        return false;
+    }
+
+    return lpToneGen->startTone(toneType);
+}
+
+static void android_media_ToneGenerator_stopTone(JNIEnv *env, jobject thiz) {
+    LOGV("android_media_ToneGenerator_stopTone: %x\n", (int)thiz);
+
+    ToneGenerator *lpToneGen = (ToneGenerator *)env->GetIntField(thiz,
+            fields.context);
+
+    LOGV("ToneGenerator lpToneGen: %x\n", (unsigned int)lpToneGen);
+    if (lpToneGen == NULL) {
+        jniThrowException(env, "java/lang/RuntimeException", "Method called after release()");
+        return;
+    }
+    lpToneGen->stopTone();
+}
+
+static void android_media_ToneGenerator_release(JNIEnv *env, jobject thiz) {
+    ToneGenerator *lpToneGen = (ToneGenerator *)env->GetIntField(thiz,
+            fields.context);
+    LOGV("android_media_ToneGenerator_release lpToneGen: %x\n", (int)lpToneGen);
+
+    env->SetIntField(thiz, fields.context, 0);
+
+    if (lpToneGen) {
+        delete lpToneGen;
+    }
+}
+
+static void android_media_ToneGenerator_native_setup(JNIEnv *env, jobject thiz,
+        jint streamType, jint volume) {
+    ToneGenerator *lpToneGen = new ToneGenerator(streamType, AudioSystem::linearToLog(volume));
+
+    env->SetIntField(thiz, fields.context, 0);
+
+    LOGV("android_media_ToneGenerator_native_setup jobject: %x\n", (int)thiz);
+
+    if (lpToneGen == NULL) {
+        LOGE("ToneGenerator creation failed \n");
+        jniThrowException(env, "java/lang/OutOfMemoryError", NULL);
+        return;
+    }
+    LOGV("ToneGenerator lpToneGen: %x\n", (unsigned int)lpToneGen);
+
+    if (!lpToneGen->isInited()) {
+        LOGE("ToneGenerator init failed \n");
+        jniThrowException(env, "java/lang/RuntimeException", "Init failed");
+        return;
+    }
+
+    // Stow our new C++ ToneGenerator in an opaque field in the Java object.
+    env->SetIntField(thiz, fields.context, (int)lpToneGen);
+
+    LOGV("ToneGenerator fields.context: %x\n", env->GetIntField(thiz, fields.context));
+}
+
+static void android_media_ToneGenerator_native_finalize(JNIEnv *env,
+        jobject thiz) {
+    LOGV("android_media_ToneGenerator_native_finalize jobject: %x\n", (int)thiz);
+
+    ToneGenerator *lpToneGen = (ToneGenerator *)env->GetIntField(thiz,
+            fields.context);
+
+    if (lpToneGen) {
+        LOGV("delete lpToneGen: %x\n", (int)lpToneGen);
+        delete lpToneGen;
+    }
+}
+
+// ----------------------------------------------------------------------------
+
+static JNINativeMethod gMethods[] = {
+    { "startTone", "(I)Z", (void *)android_media_ToneGenerator_startTone },
+    { "stopTone", "()V", (void *)android_media_ToneGenerator_stopTone },
+    { "release", "()V", (void *)android_media_ToneGenerator_release },
+    { "native_setup", "(II)V", (void *)android_media_ToneGenerator_native_setup },
+    { "native_finalize", "()V", (void *)android_media_ToneGenerator_native_finalize }
+};
+
+
+int register_android_media_ToneGenerator(JNIEnv *env) {
+    jclass clazz;
+
+    clazz = env->FindClass("android/media/ToneGenerator");
+    if (clazz == NULL) {
+        LOGE("Can't find %s", "android/media/ToneGenerator");
+        return -1;
+    }
+
+    fields.context = env->GetFieldID(clazz, "mNativeContext", "I");
+    if (fields.context == NULL) {
+        LOGE("Can't find ToneGenerator.mNativeContext");
+        return -1;
+    }
+    LOGV("register_android_media_ToneGenerator ToneGenerator fields.context: %x", (unsigned int)fields.context);
+
+    return AndroidRuntime::registerNativeMethods(env,
+            "android/media/ToneGenerator", gMethods, NELEM(gMethods));
+}
diff --git a/core/jni/android_message_digest_sha1.cpp b/core/jni/android_message_digest_sha1.cpp
new file mode 100644
index 0000000..480bbf8
--- /dev/null
+++ b/core/jni/android_message_digest_sha1.cpp
@@ -0,0 +1,146 @@
+/* //device/libs/android_runtime/android_message_digest_sha1.cpp
+**
+** Copyright 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.
+*/
+
+#include "jni.h"
+#include <JNIHelp.h>
+#include "android_runtime/AndroidRuntime.h"
+
+#include <openssl/sha.h>
+
+//#define _DEBUG 1
+
+// ----------------------------------------------------------------------------
+
+using namespace android;
+
+// ----------------------------------------------------------------------------
+
+struct fields_t {
+    jfieldID	context;
+};
+static fields_t fields;
+
+static void native_init(JNIEnv *env, jobject clazz)
+{
+	SHA_CTX* context;
+	
+#ifdef _DEBUG
+	printf("sha1.native_init\n");
+#endif
+	
+	context = (SHA_CTX *)malloc(sizeof(SHA_CTX));
+	SHA1_Init(context);
+	
+	env->SetIntField(clazz, fields.context, (int)context);
+}
+
+static void native_reset(JNIEnv *env, jobject clazz)
+{
+    SHA_CTX *context = (SHA_CTX *)env->GetIntField(clazz, fields.context);
+	if (context != NULL) {
+#ifdef _DEBUG
+		printf("sha1.native_reset: free context\n");
+#endif
+		free(context);
+  		env->SetIntField(clazz, fields.context, 0 );
+	}	
+}
+
+
+static void native_update(JNIEnv *env, jobject clazz, jbyteArray dataArray)
+{
+#ifdef _DEBUG
+	printf("sha1.native_update\n");
+#endif
+	jbyte * data;
+    jsize dataSize;
+    SHA_CTX *context = (SHA_CTX *)env->GetIntField(clazz, fields.context);
+    
+    if (context == NULL) {
+#ifdef _DEBUG
+		printf("sha1.native_update: context is NULL, call init...\n");
+#endif
+    	native_init(env, clazz);
+    	context = (SHA_CTX *)env->GetIntField(clazz, fields.context);
+    }
+    
+    data = env->GetByteArrayElements(dataArray, NULL);
+    if (data == NULL) {
+        LOGE("Unable to get byte array elements");
+        jniThrowException(env, "java/lang/IllegalArgumentException",
+                          "Invalid data array when calling MessageDigest.update()");
+        return;
+    }
+    dataSize = env->GetArrayLength(dataArray);   
+    
+    SHA1_Update(context, data, dataSize);
+
+    env->ReleaseByteArrayElements(dataArray, data, 0);
+}
+	
+static jbyteArray native_digest(JNIEnv *env, jobject clazz)
+{
+#ifdef _DEBUG
+	printf("sha1.native_digest\n");
+#endif
+	jbyteArray array;
+	jbyte md[SHA_DIGEST_LENGTH];
+	SHA_CTX *context = (SHA_CTX *)env->GetIntField(clazz, fields.context);
+  	
+  	SHA1_Final((uint8_t*)md, context);	
+  	
+  	array = env->NewByteArray(SHA_DIGEST_LENGTH);
+    LOG_ASSERT(array, "Native could not create new byte[]");
+  	
+  	env->SetByteArrayRegion(array, 0, SHA_DIGEST_LENGTH, md);
+  	
+  	native_reset(env, clazz);
+  	  	
+  	return array;
+}
+
+
+static JNINativeMethod method_table[] = 
+{
+     /* name, signature, funcPtr */
+	{"init", "()V", (void *)native_init},
+    {"update", "([B)V", (void *)native_update},
+    {"digest", "()[B", (void *)native_digest},
+	{"reset", "()V", (void *)native_reset},
+};
+
+int register_android_message_digest_sha1(JNIEnv *env)
+{
+    jclass clazz;
+
+    clazz = env->FindClass("android/security/Sha1MessageDigest");
+    if (clazz == NULL) {
+        LOGE("Can't find android/security/Sha1MessageDigest");
+        return -1;
+    }
+    
+	fields.context = env->GetFieldID(clazz, "mNativeSha1Context", "I");
+	if (fields.context == NULL) {
+		LOGE("Can't find Sha1MessageDigest.mNativeSha1Context");
+		return -1;
+	}
+
+    return AndroidRuntime::registerNativeMethods(
+    					env, "android/security/Sha1MessageDigest",
+    					method_table, NELEM(method_table));
+}
+
diff --git a/core/jni/android_net_LocalSocketImpl.cpp b/core/jni/android_net_LocalSocketImpl.cpp
new file mode 100644
index 0000000..f14b9fa
--- /dev/null
+++ b/core/jni/android_net_LocalSocketImpl.cpp
@@ -0,0 +1,966 @@
+/*
+ * 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.
+ */
+
+#define LOG_TAG "LocalSocketImpl"
+
+#include "JNIHelp.h"
+#include "jni.h"
+#include "utils/Log.h"
+#include "utils/misc.h"
+
+#include <stdio.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <arpa/inet.h>
+#include <netinet/in.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <unistd.h>
+#include <sys/ioctl.h>
+
+#include <cutils/sockets.h>
+#include <netinet/tcp.h>
+#include <cutils/properties.h>
+#include <cutils/adb_networking.h>
+
+namespace android {
+
+static jfieldID field_inboundFileDescriptors;
+static jfieldID field_outboundFileDescriptors;
+static jclass class_Credentials;
+static jclass class_FileDescriptor;
+static jmethodID method_CredentialsInit;
+
+/*
+ * private native FileDescriptor
+ * create_native(boolean stream)
+ *               throws IOException;
+ */
+static jobject
+socket_create (JNIEnv *env, jobject object, jboolean stream)
+{
+    int ret;
+
+    ret = socket(PF_LOCAL, stream ? SOCK_STREAM : SOCK_DGRAM, 0);
+
+    if (ret < 0) {
+        jniThrowIOException(env, errno);
+        return NULL;
+    }
+
+    return jniCreateFileDescriptor(env,ret);
+}
+
+/* private native void connectLocal(FileDescriptor fd,
+ * String name, int namespace) throws IOException
+ */
+static void
+socket_connect_local(JNIEnv *env, jobject object,
+                        jobject fileDescriptor, jstring name, jint namespaceId)
+{
+    int ret;
+    const char *nameUtf8;
+    int fd;
+
+    nameUtf8 = env->GetStringUTFChars(name, NULL);
+
+    fd = jniGetFDFromFileDescriptor(env, fileDescriptor);
+
+    if (env->ExceptionOccurred() != NULL) {
+        return;
+    }
+
+    ret = socket_local_client_connect(
+                fd,
+                nameUtf8,
+                namespaceId,
+                SOCK_STREAM);
+
+    env->ReleaseStringUTFChars(name, nameUtf8);
+
+    if (ret < 0) {
+        jniThrowIOException(env, errno);
+        return;
+    }
+}
+
+#define DEFAULT_BACKLOG 4
+
+/* private native void bindLocal(FileDescriptor fd, String name, namespace) 
+ * throws IOException; 
+ */ 
+
+static void
+socket_bind_local (JNIEnv *env, jobject object, jobject fileDescriptor,
+                jstring name, jint namespaceId)
+{
+    int ret;
+    int fd;
+    const char *nameUtf8;
+
+
+    if (name == NULL) {
+        jniThrowException(env, "java/lang/NullPointerException", NULL);
+    }
+
+    fd = jniGetFDFromFileDescriptor(env, fileDescriptor);
+
+    if (env->ExceptionOccurred() != NULL) {
+        return;
+    }
+
+    nameUtf8 = env->GetStringUTFChars(name, NULL);
+
+    ret = socket_local_server_bind(fd, nameUtf8, namespaceId);
+
+    env->ReleaseStringUTFChars(name, nameUtf8);
+ 
+    if (ret < 0) {
+        jniThrowIOException(env, errno);
+        return;
+    }
+}
+
+/* private native void listen_native(int fd, int backlog) throws IOException; */
+static void
+socket_listen (JNIEnv *env, jobject object, jobject fileDescriptor, int backlog)
+{
+    int ret;
+    int fd;
+
+    fd = jniGetFDFromFileDescriptor(env, fileDescriptor);
+
+    if (env->ExceptionOccurred() != NULL) {
+        return;
+    }
+
+    ret = listen(fd, backlog);
+
+    if (ret < 0) {
+        jniThrowIOException(env, errno);
+        return;
+    }
+}
+
+/*    private native FileDescriptor
+**    accept (FileDescriptor fd, LocalSocketImpl s)
+**                                   throws IOException;
+*/
+static jobject
+socket_accept (JNIEnv *env, jobject object, jobject fileDescriptor, jobject s)
+{
+    union {
+        struct sockaddr address;
+        struct sockaddr_un un_address;
+    } sa;
+    
+    int ret;
+    int retFD;
+    int fd;
+    socklen_t addrlen;
+
+    if (s == NULL) {
+        jniThrowException(env, "java/lang/NullPointerException", NULL);
+        return NULL;
+    }
+
+    fd = jniGetFDFromFileDescriptor(env, fileDescriptor);
+
+    if (env->ExceptionOccurred() != NULL) {
+        return NULL;
+    }
+
+    do {
+        addrlen = sizeof(sa);
+        ret = accept(fd, &(sa.address), &addrlen);
+    } while (ret < 0 && errno == EINTR);
+
+    if (ret < 0) {
+        jniThrowIOException(env, errno);
+        return NULL;
+    }
+
+    retFD = ret;
+
+    return jniCreateFileDescriptor(env, retFD);
+}
+
+/* private native void shutdown(FileDescriptor fd, boolean shutdownInput) */
+
+static void
+socket_shutdown (JNIEnv *env, jobject object, jobject fileDescriptor,
+                    jboolean shutdownInput)
+{
+    int ret;
+    int fd;
+
+    fd = jniGetFDFromFileDescriptor(env, fileDescriptor);
+
+    if (env->ExceptionOccurred() != NULL) {
+        return;
+    }
+
+    ret = shutdown(fd, shutdownInput ? SHUT_RD : SHUT_WR);
+
+    if (ret < 0) {
+        jniThrowIOException(env, errno);
+        return;
+    }
+}
+
+static bool
+java_opt_to_real(int optID, int* opt, int* level)
+{
+    switch (optID)
+    {
+        case 4098:
+            *opt = SO_RCVBUF;
+            *level = SOL_SOCKET;
+            return true;
+        case 4097:
+            *opt = SO_SNDBUF;
+            *level = SOL_SOCKET;
+            return true;
+        case 4102:
+            *opt = SO_SNDTIMEO;
+            *level = SOL_SOCKET;
+            return true;
+        case 128:
+            *opt = SO_LINGER;
+            *level = SOL_SOCKET;
+            return true;
+        case 1:
+            *opt = TCP_NODELAY;
+            *level = IPPROTO_TCP;
+            return true;
+        case 4:
+            *opt = SO_REUSEADDR;
+            *level = SOL_SOCKET;
+            return true;
+
+    }
+    return false;
+}
+
+static jint
+socket_getOption(JNIEnv *env, jobject object, jobject fileDescriptor, int optID)
+{
+    int ret, value;
+    int opt, level;
+    int fd;
+
+    socklen_t size = sizeof(int);
+
+    if (!java_opt_to_real(optID, &opt, &level)) {
+        jniThrowIOException(env, -1);
+        return 0;
+    }
+
+    fd = jniGetFDFromFileDescriptor(env, fileDescriptor);
+
+    if (env->ExceptionOccurred() != NULL) {
+        return 0;
+    }
+
+    switch (opt)
+    {
+        case SO_LINGER:
+        {
+            struct linger lingr;
+            size = sizeof(lingr);
+            ret = getsockopt(fd, level, opt, &lingr, &size);
+            if (!lingr.l_onoff) {
+                value = -1;
+            } else {
+                value = lingr.l_linger;
+            }
+            break;
+        }
+        default:
+            ret = getsockopt(fd, level, opt, &value, &size);
+            break;
+    }
+
+
+    if (ret != 0) {
+        jniThrowIOException(env, errno);
+        return 0;
+    }
+
+    return value;
+}
+
+static void socket_setOption(
+        JNIEnv *env, jobject object, jobject fileDescriptor, int optID,
+        jint boolValue, jint intValue) {
+    int ret;
+    int optname;
+    int level;
+    int fd;
+
+    if (!java_opt_to_real(optID, &optname, &level)) {
+        jniThrowIOException(env, -1);
+        return;
+    }
+
+    fd = jniGetFDFromFileDescriptor(env, fileDescriptor);
+
+    if (env->ExceptionOccurred() != NULL) {
+        return;
+    }
+
+    switch (optname) {
+        case SO_LINGER: {
+            /*
+             * SO_LINGER is special because it needs to use a special
+             * "linger" struct as well as use the incoming boolean
+             * argument specially.
+             */
+            struct linger lingr;
+            lingr.l_onoff = boolValue ? 1 : 0; // Force it to be 0 or 1.
+            lingr.l_linger = intValue;
+            ret = setsockopt(fd, level, optname, &lingr, sizeof(lingr));
+            break;
+        }
+        case SO_SNDTIMEO: {
+            /*
+             * SO_TIMEOUT from the core library gets converted to
+             * SO_SNDTIMEO, but the option is supposed to set both
+             * send and receive timeouts. Note: The incoming timeout
+             * value is in milliseconds.
+             */
+            struct timeval timeout;
+            timeout.tv_sec = intValue / 1000;
+            timeout.tv_usec = (intValue % 1000) * 1000;
+            
+            ret = setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO,
+                    (void *)&timeout, sizeof(timeout));
+
+            if (ret == 0) {
+                ret = setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO,
+                        (void *)&timeout, sizeof(timeout));
+            }
+            
+            break;
+        }
+        default: {
+            /*
+             * In all other cases, the translated option level and
+             * optname may be used directly for a call to setsockopt().
+             */
+            ret = setsockopt(fd, level, optname, &intValue, sizeof(intValue));
+            break;
+        }
+    }
+
+    if (ret != 0) {
+        jniThrowIOException(env, errno);
+        return;
+    }
+}
+
+static jint socket_available (JNIEnv *env, jobject object, 
+        jobject fileDescriptor)
+{
+    int fd;
+
+    fd = jniGetFDFromFileDescriptor(env, fileDescriptor);
+
+    if (env->ExceptionOccurred() != NULL) {
+        return (jint)-1;
+    }
+
+#if 1
+    int avail;
+    int ret = ioctl(fd, FIONREAD, &avail);
+
+    // If this were a non-socket fd, there would be other cases to worry
+    // about...
+
+    if (ret < 0) {
+        jniThrowIOException(env, errno);
+        return (jint) 0;
+    }
+
+    return (jint)avail;
+#else
+// there appears to be a bionic bug that prevents this version from working.
+    
+    ssize_t ret;
+    struct msghdr msg;
+
+    memset(&msg, 0, sizeof(msg));
+
+    do {
+        ret = recvmsg(fd, &msg, MSG_PEEK | MSG_DONTWAIT | MSG_NOSIGNAL);
+    } while (ret < 0 && errno == EINTR);
+
+    
+    // MSG_PEEK returns 0 on EOF and EWOULDBLOCK on none available
+    if (ret < 0 && errno == EWOULDBLOCK) {
+        return 0;
+    } if (ret < 0) {
+        jniThrowIOException(env, errno);
+        return -1;
+    }
+
+    return (jint)ret;
+#endif
+}
+
+static void socket_close (JNIEnv *env, jobject object, jobject fileDescriptor)
+{
+    int fd;
+    int err;
+
+    if (fileDescriptor == NULL) {
+        jniThrowException(env, "java/lang/NullPointerException", NULL);
+        return;
+    }
+
+    fd = jniGetFDFromFileDescriptor(env, fileDescriptor);
+
+    if (env->ExceptionOccurred() != NULL) {
+        return;
+    }
+
+    do {
+        err = close(fd);
+    } while (err < 0 && errno == EINTR);
+
+    if (err < 0) {
+        jniThrowIOException(env, errno);
+        return;
+    }
+}
+
+/**
+ * Processes ancillary data, handling only 
+ * SCM_RIGHTS. Creates appropriate objects and sets appropriate
+ * fields in the LocalSocketImpl object. Returns 0 on success
+ * or -1 if an exception was thrown.
+ */
+static int socket_process_cmsg(JNIEnv *env, jobject thisJ, struct msghdr * pMsg)
+{
+    struct cmsghdr *cmsgptr;
+
+    for (cmsgptr = CMSG_FIRSTHDR(pMsg); 
+            cmsgptr != NULL; cmsgptr = CMSG_NXTHDR(pMsg, cmsgptr)) {
+
+        if (cmsgptr->cmsg_level != SOL_SOCKET) {
+            continue;
+        }
+
+        if (cmsgptr->cmsg_type == SCM_RIGHTS) {
+            int *pDescriptors = (int *)CMSG_DATA(cmsgptr);
+            jobjectArray fdArray;
+            int count 
+                = ((cmsgptr->cmsg_len - CMSG_LEN(0)) / sizeof(int));
+
+            if (count < 0) {
+                jniThrowException(env, "java/io/IOException", 
+                    "invalid cmsg length");
+            }
+
+            fdArray = env->NewObjectArray(count, class_FileDescriptor, NULL);
+
+            if (fdArray == NULL) {
+                return -1;
+            }
+
+            for (int i = 0; i < count; i++) {
+                jobject fdObject 
+                        = jniCreateFileDescriptor(env, pDescriptors[i]);
+
+                if (env->ExceptionOccurred() != NULL) {
+                    return -1;
+                }
+
+                env->SetObjectArrayElement(fdArray, i, fdObject);
+
+                if (env->ExceptionOccurred() != NULL) {
+                    return -1;
+                }
+            }
+
+            env->SetObjectField(thisJ, field_inboundFileDescriptors, fdArray);
+
+            if (env->ExceptionOccurred() != NULL) {
+                return -1;
+            }
+        }
+    }
+
+    return 0;
+}
+
+/**
+ * Reads data from a socket into buf, processing any ancillary data
+ * and adding it to thisJ.
+ *
+ * Returns the length of normal data read, or -1 if an exception has
+ * been thrown in this function.
+ */
+static ssize_t socket_read_all(JNIEnv *env, jobject thisJ, int fd, 
+        void *buffer, size_t len)
+{
+    ssize_t ret;
+    ssize_t bytesread = 0;
+    struct msghdr msg;
+    struct iovec iv;
+    unsigned char *buf = (unsigned char *)buffer;
+    // Enough buffer for a pile of fd's. We throw an exception if
+    // this buffer is too small.
+    struct cmsghdr cmsgbuf[2*sizeof(cmsghdr) + 0x100];
+
+    memset(&msg, 0, sizeof(msg));
+    memset(&iv, 0, sizeof(iv));
+
+    iv.iov_base = buf;
+    iv.iov_len = len;
+
+    msg.msg_iov = &iv;
+    msg.msg_iovlen = 1;
+    msg.msg_control = cmsgbuf;
+    msg.msg_controllen = sizeof(cmsgbuf);
+
+    do {
+        ret = recvmsg(fd, &msg, MSG_NOSIGNAL);
+    } while (ret < 0 && errno == EINTR);
+
+    if (ret < 0 && errno == EPIPE) {
+        // Treat this as an end of stream
+        return 0;
+    }
+
+    if (ret < 0) {
+        jniThrowIOException(env, errno);
+        return -1;
+    }
+
+    if ((msg.msg_flags & (MSG_CTRUNC | MSG_OOB | MSG_ERRQUEUE)) != 0) {
+        // To us, any of the above flags are a fatal error
+
+        jniThrowException(env, "java/io/IOException", 
+                "Unexpected error or truncation during recvmsg()");
+
+        return -1;
+    }
+
+    if (ret >= 0) {
+        socket_process_cmsg(env, thisJ, &msg);
+    }
+
+    return ret;
+}
+
+/**
+ * Writes all the data in the specified buffer to the specified socket.
+ *
+ * Returns 0 on success or -1 if an exception was thrown.
+ */
+static int socket_write_all(JNIEnv *env, jobject object, int fd,
+        void *buf, size_t len)
+{
+    ssize_t ret;
+    struct msghdr msg;
+    unsigned char *buffer = (unsigned char *)buf;
+    memset(&msg, 0, sizeof(msg));
+
+    jobjectArray outboundFds 
+            = (jobjectArray)env->GetObjectField(
+                object, field_outboundFileDescriptors);
+
+    if (env->ExceptionOccurred() != NULL) {
+        return -1;
+    }
+
+    struct cmsghdr *cmsg;
+    int countFds = outboundFds == NULL ? 0 : env->GetArrayLength(outboundFds);
+    int fds[countFds];
+    char msgbuf[CMSG_SPACE(countFds)];
+
+    // Add any pending outbound file descriptors to the message
+    if (outboundFds != NULL) {
+
+        if (env->ExceptionOccurred() != NULL) {
+            return -1;
+        }
+
+        for (int i = 0; i < countFds; i++) {
+            jobject fdObject = env->GetObjectArrayElement(outboundFds, i);
+            if (env->ExceptionOccurred() != NULL) {
+                return -1;
+            }
+
+            fds[i] = jniGetFDFromFileDescriptor(env, fdObject);
+            if (env->ExceptionOccurred() != NULL) {
+                return -1;
+            }
+        }
+
+        // See "man cmsg" really
+        msg.msg_control = msgbuf;
+        msg.msg_controllen = sizeof msgbuf;
+        cmsg = CMSG_FIRSTHDR(&msg);
+        cmsg->cmsg_level = SOL_SOCKET;
+        cmsg->cmsg_type = SCM_RIGHTS;
+        cmsg->cmsg_len = CMSG_LEN(sizeof fds);
+        memcpy(CMSG_DATA(cmsg), fds, sizeof fds);
+    }
+
+    // We only write our msg_control during the first write
+    while (len > 0) {
+        struct iovec iv;
+        memset(&iv, 0, sizeof(iv));
+
+        iv.iov_base = buffer;
+        iv.iov_len = len;
+
+        msg.msg_iov = &iv;
+        msg.msg_iovlen = 1;
+        
+        do {
+            ret = sendmsg(fd, &msg, MSG_NOSIGNAL);
+        } while (ret < 0 && errno == EINTR);
+
+        if (ret < 0) {
+            jniThrowIOException(env, errno);
+            return -1;
+        }
+
+        buffer += ret;
+        len -= ret;
+
+        // Wipes out any msg_control too
+        memset(&msg, 0, sizeof(msg));
+    }
+
+    return 0;
+}
+
+static jint socket_read (JNIEnv *env, jobject object, jobject fileDescriptor)
+{
+    int fd;
+    int err;
+
+    if (fileDescriptor == NULL) {
+        jniThrowException(env, "java/lang/NullPointerException", NULL);
+        return (jint)-1;
+    }
+
+    fd = jniGetFDFromFileDescriptor(env, fileDescriptor);
+
+    if (env->ExceptionOccurred() != NULL) {
+        return (jint)0;
+    }
+
+    unsigned char buf;
+
+    err = socket_read_all(env, object, fd, &buf, 1);
+
+    if (err < 0) {
+        jniThrowIOException(env, errno);
+        return (jint)0;
+    }
+
+    if (err == 0) {
+        // end of file
+        return (jint)-1;
+    }
+
+    return (jint)buf;
+}
+
+static jint socket_readba (JNIEnv *env, jobject object, 
+        jbyteArray buffer, jint off, jint len, jobject fileDescriptor)
+{
+    int fd;
+    jbyte* byteBuffer;
+    int ret;
+
+    if (fileDescriptor == NULL || buffer == NULL) {
+        jniThrowException(env, "java/lang/NullPointerException", NULL);
+        return (jint)-1;
+    }
+
+    if (off < 0 || len < 0 || (off + len) > env->GetArrayLength(buffer)) {
+        jniThrowException(env, "java/lang/ArrayIndexOutOfBoundsException", NULL);
+        return (jint)-1;
+    }
+
+    if (len == 0) {
+        // because socket_read_all returns 0 on EOF
+        return 0;
+    }
+
+    fd = jniGetFDFromFileDescriptor(env, fileDescriptor);
+
+    if (env->ExceptionOccurred() != NULL) {
+        return (jint)-1;
+    }
+
+    byteBuffer = env->GetByteArrayElements(buffer, NULL);
+
+    if (NULL == byteBuffer) {
+        // an exception will have been thrown
+        return (jint)-1;
+    }
+
+    ret = socket_read_all(env, object, 
+            fd, byteBuffer + off, len);
+
+    // A return of -1 above means an exception is pending
+
+    env->ReleaseByteArrayElements(buffer, byteBuffer, 0);
+
+    return (jint) ((ret == 0) ? -1 : ret);
+}
+
+static void socket_write (JNIEnv *env, jobject object, 
+        jint b, jobject fileDescriptor)
+{
+    int fd;
+    int err;
+
+    if (fileDescriptor == NULL) {
+        jniThrowException(env, "java/lang/NullPointerException", NULL);
+        return;
+    }
+
+    fd = jniGetFDFromFileDescriptor(env, fileDescriptor);
+
+    if (env->ExceptionOccurred() != NULL) {
+        return;
+    }
+
+    err = socket_write_all(env, object, fd, &b, 1);
+
+    // A return of -1 above means an exception is pending
+}
+
+static void socket_writeba (JNIEnv *env, jobject object, 
+        jbyteArray buffer, jint off, jint len, jobject fileDescriptor)
+{
+    int fd;
+    int err;
+    jbyte* byteBuffer;
+
+    if (fileDescriptor == NULL || buffer == NULL) {
+        jniThrowException(env, "java/lang/NullPointerException", NULL);
+        return;
+    }
+
+    if (off < 0 || len < 0 || (off + len) > env->GetArrayLength(buffer)) {
+        jniThrowException(env, "java/lang/ArrayIndexOutOfBoundsException", NULL);
+        return;
+    }
+
+    fd = jniGetFDFromFileDescriptor(env, fileDescriptor);
+
+    if (env->ExceptionOccurred() != NULL) {
+        return;
+    }
+
+    byteBuffer = env->GetByteArrayElements(buffer,NULL);
+
+    if (NULL == byteBuffer) {
+        // an exception will have been thrown
+        return;
+    }
+
+    err = socket_write_all(env, object, fd, 
+            byteBuffer + off, len);
+
+    // A return of -1 above means an exception is pending
+
+    env->ReleaseByteArrayElements(buffer, byteBuffer, JNI_ABORT);
+}
+
+static jobject socket_get_peer_credentials(JNIEnv *env, 
+        jobject object, jobject fileDescriptor)
+{
+    int err;
+    int fd;
+
+    if (fileDescriptor == NULL) {
+        jniThrowException(env, "java/lang/NullPointerException", NULL);
+        return NULL;
+    }
+
+    fd = jniGetFDFromFileDescriptor(env, fileDescriptor);
+
+    if (env->ExceptionOccurred() != NULL) {
+        return NULL;
+    }
+
+    struct ucred creds;
+
+    memset(&creds, 0, sizeof(creds));
+    socklen_t szCreds = sizeof(creds);
+
+    err = getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &creds, &szCreds); 
+
+    if (err < 0) {
+        jniThrowIOException(env, errno);
+        return NULL;
+    }
+
+    if (szCreds == 0) {
+        return NULL;
+    }
+
+    return env->NewObject(class_Credentials, method_CredentialsInit, 
+            creds.pid, creds.uid, creds.gid);
+}
+
+#if 0
+//TODO change this to return an instance of LocalSocketAddress
+static jobject socket_getSockName(JNIEnv *env, 
+        jobject object, jobject fileDescriptor)
+{
+    int err;
+    int fd;
+
+    if (fileDescriptor == NULL) {
+        jniThrowException(env, "java/lang/NullPointerException", NULL);
+        return NULL;
+    }
+
+    fd = jniGetFDFromFileDescriptor(env, fileDescriptor);
+
+    if (env->ExceptionOccurred() != NULL) {
+        return NULL;
+    }
+
+    union {
+        struct sockaddr address;
+        struct sockaddr_un un_address;
+    } sa;
+
+    memset(&sa, 0, sizeof(sa));
+
+    socklen_t namelen = sizeof(sa);
+    err = getsockname(fd, &(sa.address), &namelen);
+
+    if (err < 0) {
+        jniThrowIOException(env, errno);
+        return NULL;
+    }
+
+    if (sa.address.sa_family != AF_UNIX) {
+        // We think we're an impl only for AF_UNIX, so this should never happen.
+
+        jniThrowIOException(env, EINVAL);
+        return NULL;
+    }
+
+    if (sa.un_address.sun_path[0] == '\0') {
+    } else {
+    }
+
+
+
+
+}
+#endif
+
+/*
+ * JNI registration.
+ */
+static JNINativeMethod gMethods[] = {
+     /* name, signature, funcPtr */
+    {"getOption_native", "(Ljava/io/FileDescriptor;I)I", (void*)socket_getOption},
+    {"setOption_native", "(Ljava/io/FileDescriptor;III)V", (void*)socket_setOption},
+    {"create_native", "(Z)Ljava/io/FileDescriptor;", (void*)socket_create},
+    {"connectLocal", "(Ljava/io/FileDescriptor;Ljava/lang/String;I)V",
+                                                (void*)socket_connect_local},
+    {"bindLocal", "(Ljava/io/FileDescriptor;Ljava/lang/String;I)V", (void*)socket_bind_local},
+    {"listen_native", "(Ljava/io/FileDescriptor;I)V", (void*)socket_listen},
+    {"accept", "(Ljava/io/FileDescriptor;Landroid/net/LocalSocketImpl;)Ljava/io/FileDescriptor;", (void*)socket_accept},
+    {"shutdown", "(Ljava/io/FileDescriptor;Z)V", (void*)socket_shutdown},
+    {"available_native", "(Ljava/io/FileDescriptor;)I", (void*) socket_available},
+    {"close_native", "(Ljava/io/FileDescriptor;)V", (void*) socket_close},
+    {"read_native", "(Ljava/io/FileDescriptor;)I", (void*) socket_read},
+    {"readba_native", "([BIILjava/io/FileDescriptor;)I", (void*) socket_readba},
+    {"writeba_native", "([BIILjava/io/FileDescriptor;)V", (void*) socket_writeba},
+    {"write_native", "(ILjava/io/FileDescriptor;)V", (void*) socket_write},
+    {"getPeerCredentials_native", 
+            "(Ljava/io/FileDescriptor;)Landroid/net/Credentials;", 
+            (void*) socket_get_peer_credentials}
+    //,{"getSockName_native", "(Ljava/io/FileDescriptor;)Ljava/lang/String;", 
+    //        (void *) socket_getSockName}
+
+};
+
+int register_android_net_LocalSocketImpl(JNIEnv *env)
+{
+    jclass clazz;
+
+    clazz = env->FindClass("android/net/LocalSocketImpl");
+
+    if (clazz == NULL) {
+        goto error;
+    }
+
+    field_inboundFileDescriptors = env->GetFieldID(clazz, 
+            "inboundFileDescriptors", "[Ljava/io/FileDescriptor;");
+
+    if (field_inboundFileDescriptors == NULL) {
+        goto error;
+    }
+
+    field_outboundFileDescriptors = env->GetFieldID(clazz, 
+            "outboundFileDescriptors", "[Ljava/io/FileDescriptor;");
+
+    if (field_outboundFileDescriptors == NULL) {
+        goto error;
+    }
+
+    class_Credentials = env->FindClass("android/net/Credentials");
+    
+    if (class_Credentials == NULL) {
+        goto error;
+    }
+
+    class_Credentials = (jclass)env->NewGlobalRef(class_Credentials);
+
+    class_FileDescriptor = env->FindClass("java/io/FileDescriptor");
+
+    if (class_FileDescriptor == NULL) {
+        goto error;
+    }
+
+    class_FileDescriptor = (jclass)env->NewGlobalRef(class_FileDescriptor);
+
+    method_CredentialsInit 
+            = env->GetMethodID(class_Credentials, "<init>", "(III)V");
+
+    if (method_CredentialsInit == NULL) {
+        goto error;
+    }
+
+    return jniRegisterNativeMethods(env,
+        "android/net/LocalSocketImpl", gMethods, NELEM(gMethods));
+
+error:
+    LOGE("Error registering android.net.LocalSocketImpl");
+    return -1;
+}
+
+};
diff --git a/core/jni/android_net_NetUtils.cpp b/core/jni/android_net_NetUtils.cpp
new file mode 100644
index 0000000..8383deb
--- /dev/null
+++ b/core/jni/android_net_NetUtils.cpp
@@ -0,0 +1,247 @@
+/*
+ * Copyright 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.
+ */
+
+#define LOG_TAG "NetUtils"
+
+#include "jni.h"
+#include <utils/misc.h>
+#include <android_runtime/AndroidRuntime.h>
+#include <utils/Log.h>
+#include <arpa/inet.h>
+
+extern "C" {
+int ifc_disable(const char *ifname);
+int ifc_add_host_route(const char *ifname, uint32_t addr);
+int ifc_remove_host_routes(const char *ifname);
+int ifc_set_default_route(const char *ifname, uint32_t gateway);
+int ifc_get_default_route(const char *ifname);
+int ifc_remove_default_route(const char *ifname);
+int ifc_reset_connections(const char *ifname);
+int ifc_configure(const char *ifname, in_addr_t ipaddr, in_addr_t netmask, in_addr_t gateway, in_addr_t dns1, in_addr_t dns2);
+
+int dhcp_do_request(const char *ifname,
+                    in_addr_t *ipaddr,
+                    in_addr_t *gateway,
+                    in_addr_t *mask,
+                    in_addr_t *dns1,
+                    in_addr_t *dns2,
+                    in_addr_t *server,
+                    uint32_t  *lease);
+int dhcp_stop(const char *ifname);
+int dhcp_release_lease(const char *ifname);
+char *dhcp_get_errmsg();
+}
+
+#define NETUTILS_PKG_NAME "android/net/NetworkUtils"
+
+namespace android {
+
+/*
+ * The following remembers the jfieldID's of the fields
+ * of the DhcpInfo Java object, so that we don't have
+ * to look them up every time.
+ */
+static struct fieldIds {
+    jclass dhcpInfoClass;
+    jmethodID constructorId;
+    jfieldID ipaddress;
+    jfieldID gateway;
+    jfieldID netmask;
+    jfieldID dns1;
+    jfieldID dns2;
+    jfieldID serverAddress;
+    jfieldID leaseDuration;
+} dhcpInfoFieldIds;
+
+static jint android_net_utils_disableInterface(JNIEnv* env, jobject clazz, jstring ifname)
+{
+    int result;
+
+    const char *nameStr = env->GetStringUTFChars(ifname, NULL);
+    result = ::ifc_disable(nameStr);
+    env->ReleaseStringUTFChars(ifname, nameStr);
+    return (jint)result;
+}
+
+static jint android_net_utils_addHostRoute(JNIEnv* env, jobject clazz, jstring ifname, jint addr)
+{
+    int result;
+
+    const char *nameStr = env->GetStringUTFChars(ifname, NULL);
+    result = ::ifc_add_host_route(nameStr, addr);
+    env->ReleaseStringUTFChars(ifname, nameStr);
+    return (jint)result;
+}
+
+static jint android_net_utils_removeHostRoutes(JNIEnv* env, jobject clazz, jstring ifname)
+{
+    int result;
+
+    const char *nameStr = env->GetStringUTFChars(ifname, NULL);
+    result = ::ifc_remove_host_routes(nameStr);
+    env->ReleaseStringUTFChars(ifname, nameStr);
+    return (jint)result;
+}
+
+static jint android_net_utils_setDefaultRoute(JNIEnv* env, jobject clazz, jstring ifname, jint gateway)
+{
+    int result;
+
+    const char *nameStr = env->GetStringUTFChars(ifname, NULL);
+    result = ::ifc_set_default_route(nameStr, gateway);
+    env->ReleaseStringUTFChars(ifname, nameStr);
+    return (jint)result;
+}
+
+static jint android_net_utils_getDefaultRoute(JNIEnv* env, jobject clazz, jstring ifname)
+{
+    int result;
+
+    const char *nameStr = env->GetStringUTFChars(ifname, NULL);
+    result = ::ifc_get_default_route(nameStr);
+    env->ReleaseStringUTFChars(ifname, nameStr);
+    return (jint)result;
+}
+
+static jint android_net_utils_removeDefaultRoute(JNIEnv* env, jobject clazz, jstring ifname)
+{
+    int result;
+
+    const char *nameStr = env->GetStringUTFChars(ifname, NULL);
+    result = ::ifc_remove_default_route(nameStr);
+    env->ReleaseStringUTFChars(ifname, nameStr);
+    return (jint)result;
+}
+
+static jint android_net_utils_resetConnections(JNIEnv* env, jobject clazz, jstring ifname)
+{
+    int result;
+
+    const char *nameStr = env->GetStringUTFChars(ifname, NULL);
+    result = ::ifc_reset_connections(nameStr);
+    env->ReleaseStringUTFChars(ifname, nameStr);
+    return (jint)result;
+}
+
+static jboolean android_net_utils_runDhcp(JNIEnv* env, jobject clazz, jstring ifname, jobject info)
+{
+    int result;
+    in_addr_t ipaddr, gateway, mask, dns1, dns2, server;
+    uint32_t lease;
+
+    const char *nameStr = env->GetStringUTFChars(ifname, NULL);
+    result = ::dhcp_do_request(nameStr, &ipaddr, &gateway, &mask,
+                                        &dns1, &dns2, &server, &lease);
+    env->ReleaseStringUTFChars(ifname, nameStr);
+    if (result == 0 && dhcpInfoFieldIds.dhcpInfoClass != NULL) {
+        env->SetIntField(info, dhcpInfoFieldIds.ipaddress, ipaddr);
+        env->SetIntField(info, dhcpInfoFieldIds.gateway, gateway);
+        env->SetIntField(info, dhcpInfoFieldIds.netmask, mask);
+        env->SetIntField(info, dhcpInfoFieldIds.dns1, dns1);
+        env->SetIntField(info, dhcpInfoFieldIds.dns2, dns2);
+        env->SetIntField(info, dhcpInfoFieldIds.serverAddress, server);
+        env->SetIntField(info, dhcpInfoFieldIds.leaseDuration, lease);
+    }
+    return (jboolean)(result == 0);
+}
+
+static jboolean android_net_utils_stopDhcp(JNIEnv* env, jobject clazz, jstring ifname)
+{
+    int result;
+
+    const char *nameStr = env->GetStringUTFChars(ifname, NULL);
+    result = ::dhcp_stop(nameStr);
+    env->ReleaseStringUTFChars(ifname, nameStr);
+    return (jboolean)(result == 0);
+}
+
+static jboolean android_net_utils_releaseDhcpLease(JNIEnv* env, jobject clazz, jstring ifname)
+{
+    int result;
+
+    const char *nameStr = env->GetStringUTFChars(ifname, NULL);
+    result = ::dhcp_release_lease(nameStr);
+    env->ReleaseStringUTFChars(ifname, nameStr);
+    return (jboolean)(result == 0);
+}
+
+static jstring android_net_utils_getDhcpError(JNIEnv* env, jobject clazz)
+{
+    return env->NewStringUTF(::dhcp_get_errmsg());
+}
+
+static jboolean android_net_utils_configureInterface(JNIEnv* env,
+        jobject clazz,
+        jstring ifname,
+        jint ipaddr,
+        jint mask,
+        jint gateway,
+        jint dns1,
+        jint dns2)
+{
+    int result;
+    uint32_t lease;
+
+    const char *nameStr = env->GetStringUTFChars(ifname, NULL);
+    result = ::ifc_configure(nameStr, ipaddr, mask, gateway, dns1, dns2);
+    env->ReleaseStringUTFChars(ifname, nameStr);
+    return (jboolean)(result == 0);
+}
+
+// ----------------------------------------------------------------------------
+
+/*
+ * JNI registration.
+ */
+static JNINativeMethod gNetworkUtilMethods[] = {
+    /* name, signature, funcPtr */
+
+    { "disableInterface", "(Ljava/lang/String;)I",  (void *)android_net_utils_disableInterface },
+    { "addHostRoute", "(Ljava/lang/String;I)I",  (void *)android_net_utils_addHostRoute },
+    { "removeHostRoutes", "(Ljava/lang/String;)I",  (void *)android_net_utils_removeHostRoutes },
+    { "setDefaultRoute", "(Ljava/lang/String;I)I",  (void *)android_net_utils_setDefaultRoute },
+    { "getDefaultRoute", "(Ljava/lang/String;)I",  (void *)android_net_utils_getDefaultRoute },
+    { "removeDefaultRoute", "(Ljava/lang/String;)I",  (void *)android_net_utils_removeDefaultRoute },
+    { "resetConnections", "(Ljava/lang/String;)I",  (void *)android_net_utils_resetConnections },
+    { "runDhcp", "(Ljava/lang/String;Landroid/net/DhcpInfo;)Z",  (void *)android_net_utils_runDhcp },
+    { "stopDhcp", "(Ljava/lang/String;)Z",  (void *)android_net_utils_stopDhcp },
+    { "releaseDhcpLease", "(Ljava/lang/String;)Z",  (void *)android_net_utils_releaseDhcpLease },
+    { "configureNative", "(Ljava/lang/String;IIIII)Z",  (void *)android_net_utils_configureInterface },
+    { "getDhcpError", "()Ljava/lang/String;", (void*) android_net_utils_getDhcpError },
+};
+
+int register_android_net_NetworkUtils(JNIEnv* env)
+{
+    jclass netutils = env->FindClass(NETUTILS_PKG_NAME);
+    LOG_FATAL_IF(netutils == NULL, "Unable to find class " NETUTILS_PKG_NAME);
+
+    dhcpInfoFieldIds.dhcpInfoClass = env->FindClass("android/net/DhcpInfo");
+    if (dhcpInfoFieldIds.dhcpInfoClass != NULL) {
+        dhcpInfoFieldIds.constructorId = env->GetMethodID(dhcpInfoFieldIds.dhcpInfoClass, "<init>", "()V");
+        dhcpInfoFieldIds.ipaddress = env->GetFieldID(dhcpInfoFieldIds.dhcpInfoClass, "ipAddress", "I");
+        dhcpInfoFieldIds.gateway = env->GetFieldID(dhcpInfoFieldIds.dhcpInfoClass, "gateway", "I");
+        dhcpInfoFieldIds.netmask = env->GetFieldID(dhcpInfoFieldIds.dhcpInfoClass, "netmask", "I");
+        dhcpInfoFieldIds.dns1 = env->GetFieldID(dhcpInfoFieldIds.dhcpInfoClass, "dns1", "I");
+        dhcpInfoFieldIds.dns2 = env->GetFieldID(dhcpInfoFieldIds.dhcpInfoClass, "dns2", "I");
+        dhcpInfoFieldIds.serverAddress = env->GetFieldID(dhcpInfoFieldIds.dhcpInfoClass, "serverAddress", "I");
+        dhcpInfoFieldIds.leaseDuration = env->GetFieldID(dhcpInfoFieldIds.dhcpInfoClass, "leaseDuration", "I");
+    }
+
+    return AndroidRuntime::registerNativeMethods(env,
+            NETUTILS_PKG_NAME, gNetworkUtilMethods, NELEM(gNetworkUtilMethods));
+}
+
+}; // namespace android
diff --git a/core/jni/android_net_wifi_Wifi.cpp b/core/jni/android_net_wifi_Wifi.cpp
new file mode 100644
index 0000000..c98207a
--- /dev/null
+++ b/core/jni/android_net_wifi_Wifi.cpp
@@ -0,0 +1,540 @@
+/*
+ * Copyright 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.
+ */
+
+#define LOG_TAG "wifi"
+
+#include "jni.h"
+#include <utils/misc.h>
+#include <android_runtime/AndroidRuntime.h>
+#include <utils/Log.h>
+
+#include "wifi.h"
+
+#define WIFI_PKG_NAME "android/net/wifi/WifiNative"
+
+namespace android {
+
+/*
+ * The following remembers the jfieldID's of the fields
+ * of the DhcpInfo Java object, so that we don't have
+ * to look them up every time.
+ */
+static struct fieldIds {
+    jclass dhcpInfoClass;
+    jmethodID constructorId;
+    jfieldID ipaddress;
+    jfieldID gateway;
+    jfieldID netmask;
+    jfieldID dns1;
+    jfieldID dns2;
+    jfieldID serverAddress;
+    jfieldID leaseDuration;
+} dhcpInfoFieldIds;
+
+static int doCommand(const char *cmd, char *replybuf, int replybuflen)
+{
+    size_t reply_len = replybuflen - 1;
+
+    if (::wifi_command(cmd, replybuf, &reply_len) != 0)
+        return -1;
+    else {
+        // Strip off trailing newline
+        if (reply_len > 0 && replybuf[reply_len-1] == '\n')
+            replybuf[reply_len-1] = '\0';
+        else
+            replybuf[reply_len] = '\0';
+        return 0;
+    }
+}
+
+static jint doIntCommand(const char *cmd)
+{
+    char reply[256];
+
+    if (doCommand(cmd, reply, sizeof(reply)) != 0) {
+        return (jint)-1;
+    } else {
+        return (jint)atoi(reply);
+    }
+}
+
+static jboolean doBooleanCommand(const char *cmd, const char *expect)
+{
+    char reply[256];
+
+    if (doCommand(cmd, reply, sizeof(reply)) != 0) {
+        return (jboolean)JNI_FALSE;
+    } else {
+        return (jboolean)(strcmp(reply, expect) == 0);
+    }
+}
+
+// Send a command to the supplicant, and return the reply as a String
+static jstring doStringCommand(JNIEnv *env, const char *cmd)
+{
+    char reply[4096];
+
+    if (doCommand(cmd, reply, sizeof(reply)) != 0) {
+        return env->NewStringUTF(NULL);
+    } else {
+        return env->NewStringUTF(reply);
+    }
+}
+
+static jboolean android_net_wifi_loadDriver(JNIEnv* env, jobject clazz)
+{
+    return (jboolean)(::wifi_load_driver() == 0);
+}
+
+static jboolean android_net_wifi_unloadDriver(JNIEnv* env, jobject clazz)
+{
+    return (jboolean)(::wifi_unload_driver() == 0);
+}
+
+static jboolean android_net_wifi_startSupplicant(JNIEnv* env, jobject clazz)
+{
+    return (jboolean)(::wifi_start_supplicant() == 0);
+}
+
+static jboolean android_net_wifi_stopSupplicant(JNIEnv* env, jobject clazz)
+{
+    return (jboolean)(::wifi_stop_supplicant() == 0);
+}
+
+static jboolean android_net_wifi_connectToSupplicant(JNIEnv* env, jobject clazz)
+{
+    return (jboolean)(::wifi_connect_to_supplicant() == 0);
+}
+
+static void android_net_wifi_closeSupplicantConnection(JNIEnv* env, jobject clazz)
+{
+    ::wifi_close_supplicant_connection();
+}
+
+static jstring android_net_wifi_waitForEvent(JNIEnv* env, jobject clazz)
+{
+    char buf[256];
+
+    int nread = ::wifi_wait_for_event(buf, sizeof buf);
+    if (nread > 0) {
+        return env->NewStringUTF(buf);
+    } else {
+        return  env->NewStringUTF(NULL);
+    }
+}
+
+static jstring android_net_wifi_listNetworksCommand(JNIEnv* env, jobject clazz)
+{
+    return doStringCommand(env, "LIST_NETWORKS");
+}
+
+static jint android_net_wifi_addNetworkCommand(JNIEnv* env, jobject clazz)
+{
+    return doIntCommand("ADD_NETWORK");
+}
+
+static jboolean android_net_wifi_setNetworkVariableCommand(JNIEnv* env,
+                                                           jobject clazz,
+                                                           jint netId,
+                                                           jstring name,
+                                                           jstring value)
+{
+    char cmdstr[256];
+    jboolean isCopy;
+
+    const char *nameStr = env->GetStringUTFChars(name, &isCopy);
+    const char *valueStr = env->GetStringUTFChars(value, &isCopy);
+
+    if (nameStr == NULL || valueStr == NULL)
+        return JNI_FALSE;
+
+    int cmdTooLong = snprintf(cmdstr, sizeof(cmdstr), "SET_NETWORK %d %s %s",
+                 netId, nameStr, valueStr) >= (int)sizeof(cmdstr);
+
+    env->ReleaseStringUTFChars(name, nameStr);
+    env->ReleaseStringUTFChars(value, valueStr);
+
+    return (jboolean)!cmdTooLong && doBooleanCommand(cmdstr, "OK");
+}
+
+static jstring android_net_wifi_getNetworkVariableCommand(JNIEnv* env,
+                                                          jobject clazz,
+                                                          jint netId,
+                                                          jstring name)
+{
+    char cmdstr[256];
+    jboolean isCopy;
+
+    const char *nameStr = env->GetStringUTFChars(name, &isCopy);
+
+    if (nameStr == NULL)
+        return env->NewStringUTF(NULL);
+
+    int cmdTooLong = snprintf(cmdstr, sizeof(cmdstr), "GET_NETWORK %d %s",
+                             netId, nameStr) >= (int)sizeof(cmdstr);
+
+    env->ReleaseStringUTFChars(name, nameStr);
+
+    return cmdTooLong ? env->NewStringUTF(NULL) : doStringCommand(env, cmdstr);
+}
+
+static jboolean android_net_wifi_removeNetworkCommand(JNIEnv* env, jobject clazz, jint netId)
+{
+    char cmdstr[256];
+
+    int numWritten = snprintf(cmdstr, sizeof(cmdstr), "REMOVE_NETWORK %d", netId);
+    int cmdTooLong = numWritten >= (int)sizeof(cmdstr);
+
+    return (jboolean)!cmdTooLong && doBooleanCommand(cmdstr, "OK");
+}
+
+static jboolean android_net_wifi_enableNetworkCommand(JNIEnv* env,
+                                                  jobject clazz,
+                                                  jint netId,
+                                                  jboolean disableOthers)
+{
+    char cmdstr[256];
+    const char *cmd = disableOthers ? "SELECT_NETWORK" : "ENABLE_NETWORK";
+
+    int numWritten = snprintf(cmdstr, sizeof(cmdstr), "%s %d", cmd, netId);
+    int cmdTooLong = numWritten >= (int)sizeof(cmdstr);
+
+    return (jboolean)!cmdTooLong && doBooleanCommand(cmdstr, "OK");
+}
+
+static jboolean android_net_wifi_disableNetworkCommand(JNIEnv* env, jobject clazz, jint netId)
+{
+    char cmdstr[256];
+
+    int numWritten = snprintf(cmdstr, sizeof(cmdstr), "DISABLE_NETWORK %d", netId);
+    int cmdTooLong = numWritten >= (int)sizeof(cmdstr);
+
+    return (jboolean)!cmdTooLong && doBooleanCommand(cmdstr, "OK");
+}
+
+static jstring android_net_wifi_statusCommand(JNIEnv* env, jobject clazz)
+{
+    return doStringCommand(env, "STATUS");
+}
+
+static jboolean android_net_wifi_pingCommand(JNIEnv* env, jobject clazz)
+{
+    return doBooleanCommand("PING", "PONG");
+}
+
+static jstring android_net_wifi_scanResultsCommand(JNIEnv* env, jobject clazz)
+{
+    return doStringCommand(env, "SCAN_RESULTS");
+}
+
+static jboolean android_net_wifi_disconnectCommand(JNIEnv* env, jobject clazz)
+{
+    return doBooleanCommand("DISCONNECT", "OK");
+}
+
+static jboolean android_net_wifi_reconnectCommand(JNIEnv* env, jobject clazz)
+{
+    return doBooleanCommand("RECONNECT", "OK");
+}
+static jboolean android_net_wifi_reassociateCommand(JNIEnv* env, jobject clazz)
+{
+    return doBooleanCommand("REASSOCIATE", "OK");
+}
+
+static jboolean android_net_wifi_scanCommand(JNIEnv* env, jobject clazz)
+{
+    jboolean result;
+    // Ignore any error from setting the scan mode.
+    // The scan will still work.
+    (void)doBooleanCommand("DRIVER SCAN-ACTIVE", "OK");
+    result = doBooleanCommand("SCAN", "OK");
+    (void)doBooleanCommand("DRIVER SCAN-PASSIVE", "OK");
+    return result;
+}
+
+static jboolean android_net_wifi_setScanModeCommand(JNIEnv* env, jobject clazz, jboolean setActive)
+{
+    jboolean result;
+    // Ignore any error from setting the scan mode.
+    // The scan will still work.
+    if (setActive) {
+        return doBooleanCommand("DRIVER SCAN-ACTIVE", "OK");
+    } else {
+        return doBooleanCommand("DRIVER SCAN-PASSIVE", "OK");
+    }
+}
+
+static jboolean android_net_wifi_startDriverCommand(JNIEnv* env, jobject clazz)
+{
+    return doBooleanCommand("DRIVER START", "OK");
+}
+
+static jboolean android_net_wifi_stopDriverCommand(JNIEnv* env, jobject clazz)
+{
+    return doBooleanCommand("DRIVER STOP", "OK");
+}
+
+static jboolean android_net_wifi_startPacketFiltering(JNIEnv* env, jobject clazz)
+{
+    return doBooleanCommand("DRIVER RXFILTER-ADD 0", "OK")
+	&& doBooleanCommand("DRIVER RXFILTER-ADD 1", "OK")
+	&& doBooleanCommand("DRIVER RXFILTER-START", "OK");
+}
+
+static jboolean android_net_wifi_stopPacketFiltering(JNIEnv* env, jobject clazz)
+{
+    jboolean result = doBooleanCommand("DRIVER RXFILTER-STOP", "OK");
+    if (result) {
+	(void)doBooleanCommand("DRIVER RXFILTER-REMOVE 1", "OK");
+	(void)doBooleanCommand("DRIVER RXFILTER-REMOVE 0", "OK");
+    }
+
+    return result;
+}
+
+static jint android_net_wifi_getRssiCommand(JNIEnv* env, jobject clazz)
+{
+    char reply[256];
+    int rssi = -200;
+
+    if (doCommand("DRIVER RSSI", reply, sizeof(reply)) != 0) {
+        return (jint)-1;
+    }
+    // reply comes back in the form "<SSID> rssi XX" where XX is the
+    // number we're interested in.  if we're associating, it returns "OK".
+    if (strcmp(reply, "OK") != 0) {
+    	sscanf(reply, "%*s %*s %d", &rssi);
+    }
+    return (jint)rssi;
+}
+
+static jint android_net_wifi_getLinkSpeedCommand(JNIEnv* env, jobject clazz)
+{
+    char reply[256];
+    int linkspeed;
+
+    if (doCommand("DRIVER LINKSPEED", reply, sizeof(reply)) != 0) {
+        return (jint)-1;
+    }
+    // reply comes back in the form "LinkSpeed XX" where XX is the
+    // number we're interested in.
+    sscanf(reply, "%*s %u", &linkspeed);
+    return (jint)linkspeed;
+}
+
+static jstring android_net_wifi_getMacAddressCommand(JNIEnv* env, jobject clazz)
+{
+    char reply[256];
+    char buf[256];
+
+    if (doCommand("DRIVER MACADDR", reply, sizeof(reply)) != 0) {
+        return env->NewStringUTF(NULL);
+    }
+    // reply comes back in the form "Macaddr = XX.XX.XX.XX.XX.XX" where XX
+    // is the part of the string we're interested in.
+    if (sscanf(reply, "%*s = %255s", buf) == 1)
+        return env->NewStringUTF(buf);
+    else
+        return env->NewStringUTF(NULL);
+}
+
+static jboolean android_net_wifi_setPowerModeCommand(JNIEnv* env, jobject clazz, jint mode)
+{
+    char cmdstr[256];
+
+    int numWritten = snprintf(cmdstr, sizeof(cmdstr), "DRIVER POWERMODE %d", mode);
+    int cmdTooLong = numWritten >= (int)sizeof(cmdstr);
+
+    return (jboolean)!cmdTooLong && doBooleanCommand(cmdstr, "OK");
+}
+
+static jboolean android_net_wifi_setNumAllowedChannelsCommand(JNIEnv* env, jobject clazz, jint numChannels)
+{
+    char cmdstr[256];
+
+    int numWritten = snprintf(cmdstr, sizeof(cmdstr), "DRIVER SCAN-CHANNELS %u", numChannels);
+    int cmdTooLong = numWritten >= (int)sizeof(cmdstr);
+
+    return (jboolean)!cmdTooLong && doBooleanCommand(cmdstr, "OK");
+}
+
+static jint android_net_wifi_getNumAllowedChannelsCommand(JNIEnv* env, jobject clazz)
+{
+    char reply[256];
+    int numChannels;
+
+    if (doCommand("DRIVER SCAN-CHANNELS", reply, sizeof(reply)) != 0) {
+        return -1;
+    }
+    // reply comes back in the form "Scan-Channels = X" where X is the
+    // number of channels
+    if (sscanf(reply, "%*s = %u", &numChannels) == 1)
+        return numChannels;
+    else
+        return -1;
+}
+
+static jboolean android_net_wifi_setBluetoothCoexistenceModeCommand(JNIEnv* env, jobject clazz, jint mode)
+{
+    char cmdstr[256];
+
+    int numWritten = snprintf(cmdstr, sizeof(cmdstr), "DRIVER BTCOEXMODE %d", mode);
+    int cmdTooLong = numWritten >= (int)sizeof(cmdstr);
+
+    return (jboolean)!cmdTooLong && doBooleanCommand(cmdstr, "OK");
+}
+
+static jboolean android_net_wifi_saveConfigCommand(JNIEnv* env, jobject clazz)
+{
+    // Make sure we never write out a value for AP_SCAN other than 1
+    (void)doBooleanCommand("AP_SCAN 1", "OK");
+    return doBooleanCommand("SAVE_CONFIG", "OK");
+}
+
+static jboolean android_net_wifi_reloadConfigCommand(JNIEnv* env, jobject clazz)
+{
+    return doBooleanCommand("RECONFIGURE", "OK");
+}
+
+static jboolean android_net_wifi_setScanResultHandlingCommand(JNIEnv* env, jobject clazz, jint mode)
+{
+    char cmdstr[256];
+
+    int numWritten = snprintf(cmdstr, sizeof(cmdstr), "AP_SCAN %d", mode);
+    int cmdTooLong = numWritten >= (int)sizeof(cmdstr);
+
+    return (jboolean)!cmdTooLong && doBooleanCommand(cmdstr, "OK");
+}
+
+static jboolean android_net_wifi_addToBlacklistCommand(JNIEnv* env, jobject clazz, jstring bssid)
+{
+    char cmdstr[256];
+    jboolean isCopy;
+
+    const char *bssidStr = env->GetStringUTFChars(bssid, &isCopy);
+
+    int cmdTooLong = snprintf(cmdstr, sizeof(cmdstr), "BLACKLIST %s", bssidStr) >= (int)sizeof(cmdstr);
+
+    env->ReleaseStringUTFChars(bssid, bssidStr);
+
+    return (jboolean)!cmdTooLong && doBooleanCommand(cmdstr, "OK");
+}
+
+static jboolean android_net_wifi_clearBlacklistCommand(JNIEnv* env, jobject clazz)
+{
+    return doBooleanCommand("BLACKLIST clear", "OK");
+}
+
+static jboolean android_net_wifi_doDhcpRequest(JNIEnv* env, jobject clazz, jobject info)
+{
+    jint ipaddr, gateway, mask, dns1, dns2, server, lease;
+    jboolean succeeded = ((jboolean)::do_dhcp_request(&ipaddr, &gateway, &mask,
+                                        &dns1, &dns2, &server, &lease) == 0);
+    if (succeeded && dhcpInfoFieldIds.dhcpInfoClass != NULL) {
+        env->SetIntField(info, dhcpInfoFieldIds.ipaddress, ipaddr);
+        env->SetIntField(info, dhcpInfoFieldIds.gateway, gateway);
+        env->SetIntField(info, dhcpInfoFieldIds.netmask, mask);
+        env->SetIntField(info, dhcpInfoFieldIds.dns1, dns1);
+        env->SetIntField(info, dhcpInfoFieldIds.dns2, dns2);
+        env->SetIntField(info, dhcpInfoFieldIds.serverAddress, server);
+        env->SetIntField(info, dhcpInfoFieldIds.leaseDuration, lease);
+    }
+    return succeeded;
+}
+
+static jstring android_net_wifi_getDhcpError(JNIEnv* env, jobject clazz)
+{
+    return env->NewStringUTF(::get_dhcp_error_string());
+}
+
+// ----------------------------------------------------------------------------
+
+/*
+ * JNI registration.
+ */
+static JNINativeMethod gWifiMethods[] = {
+    /* name, signature, funcPtr */
+
+    { "loadDriver", "()Z",  (void *)android_net_wifi_loadDriver },
+    { "unloadDriver", "()Z",  (void *)android_net_wifi_unloadDriver },
+    { "startSupplicant", "()Z",  (void *)android_net_wifi_startSupplicant },
+    { "stopSupplicant", "()Z",  (void *)android_net_wifi_stopSupplicant },
+    { "connectToSupplicant", "()Z",  (void *)android_net_wifi_connectToSupplicant },
+    { "closeSupplicantConnection", "()V",  (void *)android_net_wifi_closeSupplicantConnection },
+
+    { "listNetworksCommand", "()Ljava/lang/String;",
+        (void*) android_net_wifi_listNetworksCommand },
+    { "addNetworkCommand", "()I", (void*) android_net_wifi_addNetworkCommand },
+    { "setNetworkVariableCommand", "(ILjava/lang/String;Ljava/lang/String;)Z",
+        (void*) android_net_wifi_setNetworkVariableCommand },
+    { "getNetworkVariableCommand", "(ILjava/lang/String;)Ljava/lang/String;",
+        (void*) android_net_wifi_getNetworkVariableCommand },
+    { "removeNetworkCommand", "(I)Z", (void*) android_net_wifi_removeNetworkCommand },
+    { "enableNetworkCommand", "(IZ)Z", (void*) android_net_wifi_enableNetworkCommand },
+    { "disableNetworkCommand", "(I)Z", (void*) android_net_wifi_disableNetworkCommand },
+    { "waitForEvent", "()Ljava/lang/String;", (void*) android_net_wifi_waitForEvent },
+    { "statusCommand", "()Ljava/lang/String;", (void*) android_net_wifi_statusCommand },
+    { "scanResultsCommand", "()Ljava/lang/String;", (void*) android_net_wifi_scanResultsCommand },
+    { "pingCommand", "()Z",  (void *)android_net_wifi_pingCommand },
+    { "disconnectCommand", "()Z",  (void *)android_net_wifi_disconnectCommand },
+    { "reconnectCommand", "()Z",  (void *)android_net_wifi_reconnectCommand },
+    { "reassociateCommand", "()Z",  (void *)android_net_wifi_reassociateCommand },
+    { "scanCommand", "()Z", (void*) android_net_wifi_scanCommand },
+    { "setScanModeCommand", "(Z)Z", (void*) android_net_wifi_setScanModeCommand },
+    { "startDriverCommand", "()Z", (void*) android_net_wifi_startDriverCommand },
+    { "stopDriverCommand", "()Z", (void*) android_net_wifi_stopDriverCommand },
+    { "startPacketFiltering", "()Z", (void*) android_net_wifi_startPacketFiltering },
+    { "stopPacketFiltering", "()Z", (void*) android_net_wifi_stopPacketFiltering },
+    { "setPowerModeCommand", "(I)Z", (void*) android_net_wifi_setPowerModeCommand },
+    { "setNumAllowedChannelsCommand", "(I)Z", (void*) android_net_wifi_setNumAllowedChannelsCommand },
+    { "getNumAllowedChannelsCommand", "()I", (void*) android_net_wifi_getNumAllowedChannelsCommand },
+    { "setBluetoothCoexistenceModeCommand", "(I)Z",
+    		(void*) android_net_wifi_setBluetoothCoexistenceModeCommand },
+    { "getRssiCommand", "()I", (void*) android_net_wifi_getRssiCommand },
+    { "getLinkSpeedCommand", "()I", (void*) android_net_wifi_getLinkSpeedCommand },
+    { "getMacAddressCommand", "()Ljava/lang/String;", (void*) android_net_wifi_getMacAddressCommand },
+    { "saveConfigCommand", "()Z", (void*) android_net_wifi_saveConfigCommand },
+    { "reloadConfigCommand", "()Z", (void*) android_net_wifi_reloadConfigCommand },
+    { "setScanResultHandlingCommand", "(I)Z", (void*) android_net_wifi_setScanResultHandlingCommand },
+    { "addToBlacklistCommand", "(Ljava/lang/String;)Z", (void*) android_net_wifi_addToBlacklistCommand },
+    { "clearBlacklistCommand", "()Z", (void*) android_net_wifi_clearBlacklistCommand },
+
+    { "doDhcpRequest", "(Landroid/net/DhcpInfo;)Z", (void*) android_net_wifi_doDhcpRequest },
+    { "getDhcpError", "()Ljava/lang/String;", (void*) android_net_wifi_getDhcpError },
+};
+
+int register_android_net_wifi_WifiManager(JNIEnv* env)
+{
+    jclass wifi = env->FindClass(WIFI_PKG_NAME);
+    LOG_FATAL_IF(wifi == NULL, "Unable to find class " WIFI_PKG_NAME);
+
+    dhcpInfoFieldIds.dhcpInfoClass = env->FindClass("android/net/DhcpInfo");
+    if (dhcpInfoFieldIds.dhcpInfoClass != NULL) {
+        dhcpInfoFieldIds.constructorId = env->GetMethodID(dhcpInfoFieldIds.dhcpInfoClass, "<init>", "()V");
+        dhcpInfoFieldIds.ipaddress = env->GetFieldID(dhcpInfoFieldIds.dhcpInfoClass, "ipAddress", "I");
+        dhcpInfoFieldIds.gateway = env->GetFieldID(dhcpInfoFieldIds.dhcpInfoClass, "gateway", "I");
+        dhcpInfoFieldIds.netmask = env->GetFieldID(dhcpInfoFieldIds.dhcpInfoClass, "netmask", "I");
+        dhcpInfoFieldIds.dns1 = env->GetFieldID(dhcpInfoFieldIds.dhcpInfoClass, "dns1", "I");
+        dhcpInfoFieldIds.dns2 = env->GetFieldID(dhcpInfoFieldIds.dhcpInfoClass, "dns2", "I");
+        dhcpInfoFieldIds.serverAddress = env->GetFieldID(dhcpInfoFieldIds.dhcpInfoClass, "serverAddress", "I");
+        dhcpInfoFieldIds.leaseDuration = env->GetFieldID(dhcpInfoFieldIds.dhcpInfoClass, "leaseDuration", "I");
+    }
+
+    return AndroidRuntime::registerNativeMethods(env,
+            WIFI_PKG_NAME, gWifiMethods, NELEM(gWifiMethods));
+}
+
+}; // namespace android
diff --git a/core/jni/android_nio_utils.cpp b/core/jni/android_nio_utils.cpp
new file mode 100644
index 0000000..584e7a4
--- /dev/null
+++ b/core/jni/android_nio_utils.cpp
@@ -0,0 +1,114 @@
+/*
+ * 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.
+ */
+
+#include "android_nio_utils.h"
+
+struct NioJNIData {
+    jclass nioAccessClass;
+
+    jmethodID getBasePointerID;
+    jmethodID getBaseArrayID;
+    jmethodID getBaseArrayOffsetID;
+};
+
+static NioJNIData gNioJNI;
+
+void* android::nio_getPointer(JNIEnv *_env, jobject buffer, jarray *array) {
+    assert(array);
+
+    jlong pointer;
+    jint offset;
+    void *data;
+    
+    pointer = _env->CallStaticLongMethod(gNioJNI.nioAccessClass,
+                                         gNioJNI.getBasePointerID, buffer);
+    if (pointer != 0L) {
+        *array = NULL;
+        return (void *) (jint) pointer;
+    }
+    
+    *array = (jarray) _env->CallStaticObjectMethod(gNioJNI.nioAccessClass,
+                                               gNioJNI.getBaseArrayID, buffer);
+    offset = _env->CallStaticIntMethod(gNioJNI.nioAccessClass,
+                                       gNioJNI.getBaseArrayOffsetID, buffer);
+    data = _env->GetPrimitiveArrayCritical(*array, (jboolean *) 0);
+    
+    return (void *) ((char *) data + offset);
+}
+
+
+void android::nio_releasePointer(JNIEnv *_env, jarray array, void *data,
+                                jboolean commit) {
+    _env->ReleasePrimitiveArrayCritical(array, data,
+                                        commit ? 0 : JNI_ABORT);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+android::AutoBufferPointer::AutoBufferPointer(JNIEnv* env, jobject nioBuffer,
+                                              jboolean commit) {
+    fEnv = env;
+    fCommit = commit;
+    fPointer = android::nio_getPointer(env, nioBuffer, &fArray);
+}
+
+android::AutoBufferPointer::~AutoBufferPointer() {
+    if (NULL != fArray) {
+        android::nio_releasePointer(fEnv, fArray, fPointer, fCommit);
+    }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+static jclass findClass(JNIEnv* env, const char name[]) {
+    jclass c = env->FindClass(name);
+    LOG_FATAL_IF(!c, "Unable to find class %s", name);
+    return c;
+}
+
+static jmethodID findStaticMethod(JNIEnv* env, jclass c, const char method[],
+                                  const char params[]) {
+    jmethodID m = env->GetStaticMethodID(c, method, params);
+    LOG_FATAL_IF(!m, "Unable to find method %s", method);
+    return m;
+}
+
+static jfieldID getFieldID(JNIEnv* env, jclass c, const char name[],
+                           const char type[]) {
+    jfieldID f = env->GetFieldID(c, name, type);
+    LOG_FATAL_IF(!f, "Unable to find field %s", name);
+    return f;
+}
+
+namespace android {
+    
+int register_android_nio_utils(JNIEnv* env);
+int register_android_nio_utils(JNIEnv* env) {
+    jclass localClass = findClass(env, "java/nio/NIOAccess");
+    gNioJNI.getBasePointerID = findStaticMethod(env, localClass,
+                                    "getBasePointer", "(Ljava/nio/Buffer;)J");
+    gNioJNI.getBaseArrayID = findStaticMethod(env, localClass,
+                    "getBaseArray", "(Ljava/nio/Buffer;)Ljava/lang/Object;");
+    gNioJNI.getBaseArrayOffsetID = findStaticMethod(env, localClass,
+                                "getBaseArrayOffset", "(Ljava/nio/Buffer;)I");
+
+    // now record a permanent version of the class ID
+    gNioJNI.nioAccessClass = (jclass) env->NewGlobalRef(localClass);
+
+    return 0;
+}
+
+}
diff --git a/core/jni/android_nio_utils.h b/core/jni/android_nio_utils.h
new file mode 100644
index 0000000..69c360c
--- /dev/null
+++ b/core/jni/android_nio_utils.h
@@ -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.
+ */
+
+#ifndef android_nio_utils_DEFINED
+#define android_nio_utils_DEFINED
+
+#include <android_runtime/AndroidRuntime.h>
+
+namespace android {
+    
+/**
+ * Given an nio.Buffer, return a pointer to it, beginning at its current
+ * position. The returned pointer is only valid for the current JNI stack-frame.
+ * For performance, it does not create any global references, so the getPointer
+ * (and releasePointer if array is returned non-null) must be done in the
+ * same JNI stack-frame.
+ *
+ * @param env   The current JNI env
+ * @param buffer    The nio.Buffer object
+ * @param array     REQUIRED. Output. If on return it is set to non-null, then
+ *                  nio_releasePointer must be called with the array
+ *                  and the returned pointer when the caller is through with it.
+ *                  If on return it is set to null, do not call
+ *                  nio_releasePointer.
+ * @return The pointer to the memory in the buffer object
+ */
+void* nio_getPointer(JNIEnv *env, jobject buffer, jarray *array);
+
+/**
+ * Call this if android_nio_getPointer returned non-null in its array parameter.
+ * Pass that array and the returned pointer when you are done accessing the
+ * pointer. If called (i.e. array is non-null), it must be called in the same
+ * JNI stack-frame as getPointer
+ *
+ * @param env   The current JNI env
+ * @param buffer    The array returned from android_nio_getPointer (!= null)
+ * @param pointer   The pointer returned by android_nio_getPointer
+ * @param commit    JNI_FALSE if the pointer was just read, and JNI_TRUE if
+ *                  the pointer was written to.
+ */
+void nio_releasePointer(JNIEnv *env, jarray array, void *pointer,
+                                jboolean commit);
+
+class AutoBufferPointer {
+public:
+    AutoBufferPointer(JNIEnv* env, jobject nioBuffer, jboolean commit);
+    ~AutoBufferPointer();
+    
+    void* pointer() const { return fPointer; }
+    
+private:
+    JNIEnv* fEnv;
+    void*   fPointer;
+    jarray  fArray;
+    jint    fRemaining;
+    jboolean fCommit;
+};
+
+}   /* namespace android */
+
+#endif
diff --git a/core/jni/android_os_Debug.cpp b/core/jni/android_os_Debug.cpp
new file mode 100644
index 0000000..a6b63d8
--- /dev/null
+++ b/core/jni/android_os_Debug.cpp
@@ -0,0 +1,313 @@
+/*
+ * Copyright (C) 2007 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.
+ */
+
+#include "JNIHelp.h"
+#include "jni.h"
+#include "utils/misc.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <time.h>
+#include <sys/time.h>
+
+#ifdef HAVE_MALLOC_H
+#include <malloc.h>
+#endif
+
+namespace android
+{
+
+static jfieldID dalvikPss_field;
+static jfieldID dalvikPrivateDirty_field;
+static jfieldID dalvikSharedDirty_field;
+static jfieldID nativePss_field;
+static jfieldID nativePrivateDirty_field;
+static jfieldID nativeSharedDirty_field;
+static jfieldID otherPss_field;
+static jfieldID otherPrivateDirty_field;
+static jfieldID otherSharedDirty_field;
+
+struct stats_t {
+    int dalvikPss;
+    int dalvikPrivateDirty;
+    int dalvikSharedDirty;
+    
+    int nativePss;
+    int nativePrivateDirty;
+    int nativeSharedDirty;
+    
+    int otherPss;
+    int otherPrivateDirty;
+    int otherSharedDirty;
+};
+
+#define BINDER_STATS "/proc/binder/stats"
+
+static jlong android_os_Debug_getNativeHeapSize(JNIEnv *env, jobject clazz)
+{
+#ifdef HAVE_MALLOC_H
+    struct mallinfo info = mallinfo();
+    return (jlong) info.usmblks;
+#else
+    return -1;
+#endif
+}
+
+static jlong android_os_Debug_getNativeHeapAllocatedSize(JNIEnv *env, jobject clazz)
+{
+#ifdef HAVE_MALLOC_H
+    struct mallinfo info = mallinfo();
+    return (jlong) info.uordblks;
+#else
+    return -1;
+#endif
+}
+
+static jlong android_os_Debug_getNativeHeapFreeSize(JNIEnv *env, jobject clazz)
+{
+#ifdef HAVE_MALLOC_H    
+    struct mallinfo info = mallinfo();
+    return (jlong) info.fordblks;
+#else
+    return -1;
+#endif
+}
+
+static void read_mapinfo(FILE *fp, stats_t* stats)
+{
+    char line[1024];
+    int len;
+    bool skip, done = false;
+
+    unsigned start = 0, size = 0, resident = 0, pss = 0;
+    unsigned shared_clean = 0, shared_dirty = 0;
+    unsigned private_clean = 0, private_dirty = 0;
+    unsigned referenced = 0;
+    unsigned temp;
+
+    int isNativeHeap;
+    int isDalvikHeap;
+    int isSqliteHeap;
+
+    if(fgets(line, 1024, fp) == 0) return;
+
+    while (!done) {
+        isNativeHeap = 0;
+        isDalvikHeap = 0;
+        isSqliteHeap = 0;
+        skip = false;
+
+        len = strlen(line);
+        if (len < 1) return;
+        line[--len] = 0;
+
+        /* ignore guard pages */
+        if (len > 18 && line[17] == '-') skip = true;
+
+        start = strtoul(line, 0, 16);
+
+        if (strstr(line, "[heap]")) {
+            isNativeHeap = 1;
+        } else if (strstr(line, "/dalvik-LinearAlloc")) {
+            isDalvikHeap = 1;
+        } else if (strstr(line, "/mspace/dalvik-heap")) {
+            isDalvikHeap = 1;
+        } else if (strstr(line, "/dalvik-heap-bitmap/")) {
+            isDalvikHeap = 1;    
+        } else if (strstr(line, "/tmp/sqlite-heap")) {
+            isSqliteHeap = 1;
+        }
+
+        //LOGI("native=%d dalvik=%d sqlite=%d: %s\n", isNativeHeap, isDalvikHeap,
+        //    isSqliteHeap, line);
+            
+        while (true) {
+            if (fgets(line, 1024, fp) == 0) {
+                done = true;
+                break;
+            }
+
+            if (sscanf(line, "Size: %d kB", &temp) == 1) {
+                size = temp;
+            } else if (sscanf(line, "Rss: %d kB", &temp) == 1) {
+                resident = temp;
+            } else if (sscanf(line, "Pss: %d kB", &temp) == 1) {
+                pss = temp;
+            } else if (sscanf(line, "Shared_Clean: %d kB", &temp) == 1) {
+                shared_clean = temp;
+            } else if (sscanf(line, "Shared_Dirty: %d kB", &temp) == 1) {
+                shared_dirty = temp;
+            } else if (sscanf(line, "Private_Clean: %d kB", &temp) == 1) {
+                private_clean = temp;
+            } else if (sscanf(line, "Private_Dirty: %d kB", &temp) == 1) {
+                private_dirty = temp;
+            } else if (sscanf(line, "Referenced: %d kB", &temp) == 1) {
+                referenced = temp;
+            } else if (strlen(line) > 40 && line[8] == '-' && line[17] == ' ') {
+                // looks like a new mapping
+                // example: "0000a000-00232000 rwxp 0000a000 00:00 0          [heap]"
+                break;
+            }
+        }
+
+        if (!skip) {
+            if (isNativeHeap) {
+                stats->nativePss += pss;
+                stats->nativePrivateDirty += private_dirty;
+                stats->nativeSharedDirty += shared_dirty;
+            } else if (isDalvikHeap) {
+                stats->dalvikPss += pss;
+                stats->dalvikPrivateDirty += private_dirty;
+                stats->dalvikSharedDirty += shared_dirty;
+            } else if ( isSqliteHeap) {
+                // ignore
+            } else {
+                stats->otherPss += pss;
+                stats->otherPrivateDirty += shared_dirty;
+                stats->otherSharedDirty += private_dirty;
+            }
+        }
+    }
+}
+
+static void load_maps(int pid, stats_t* stats)
+{
+    char tmp[128];
+    FILE *fp;
+    
+    sprintf(tmp, "/proc/%d/smaps", pid);
+    fp = fopen(tmp, "r");
+    if (fp == 0) return;
+
+    read_mapinfo(fp, stats);
+    fclose(fp);
+}
+
+static void android_os_Debug_getDirtyPages(JNIEnv *env, jobject clazz, jobject object)
+{
+    stats_t stats;
+    memset(&stats, 0, sizeof(stats_t));
+    
+    load_maps(getpid(), &stats);
+
+    env->SetIntField(object, dalvikPss_field, stats.dalvikPss);
+    env->SetIntField(object, dalvikPrivateDirty_field, stats.dalvikPrivateDirty);
+    env->SetIntField(object, dalvikSharedDirty_field, stats.dalvikSharedDirty);
+    
+    env->SetIntField(object, nativePss_field, stats.nativePss);
+    env->SetIntField(object, nativePrivateDirty_field, stats.nativePrivateDirty);
+    env->SetIntField(object, nativeSharedDirty_field, stats.nativeSharedDirty);
+    
+    env->SetIntField(object, otherPss_field, stats.otherPss);
+    env->SetIntField(object, otherPrivateDirty_field, stats.otherPrivateDirty);
+    env->SetIntField(object, otherSharedDirty_field, stats.otherSharedDirty);
+}
+
+static jint read_binder_stat(const char* stat)
+{
+    FILE* fp = fopen(BINDER_STATS, "r");
+    if (fp == NULL) {
+        return -1;
+    }
+
+    char line[1024];
+
+    char compare[128];
+    int len = snprintf(compare, 128, "proc %d", getpid());
+    
+    // loop until we have the block that represents this process
+    do {
+        if (fgets(line, 1024, fp) == 0) {
+            return -1;
+        }
+    } while (strncmp(compare, line, len));
+
+    // now that we have this process, read until we find the stat that we are looking for 
+    len = snprintf(compare, 128, "  %s: ", stat);
+    
+    do {
+        if (fgets(line, 1024, fp) == 0) {
+            return -1;
+        }
+    } while (strncmp(compare, line, len));
+    
+    // we have the line, now increment the line ptr to the value
+    char* ptr = line + len;
+    return atoi(ptr);
+}
+
+static jint android_os_Debug_getBinderSentTransactions(JNIEnv *env, jobject clazz)
+{
+    return read_binder_stat("bcTRANSACTION");
+}
+
+static jint android_os_getBinderReceivedTransactions(JNIEnv *env, jobject clazz)
+{
+    return read_binder_stat("brTRANSACTION");
+}
+
+// these are implemented in android_util_Binder.cpp
+jint android_os_Debug_getLocalObjectCount(JNIEnv* env, jobject clazz);
+jint android_os_Debug_getProxyObjectCount(JNIEnv* env, jobject clazz);
+jint android_os_Debug_getDeathObjectCount(JNIEnv* env, jobject clazz);
+
+/*
+ * JNI registration.
+ */
+
+static JNINativeMethod gMethods[] = {
+    { "getNativeHeapSize",      "()J",
+            (void*) android_os_Debug_getNativeHeapSize },
+    { "getNativeHeapAllocatedSize", "()J",
+            (void*) android_os_Debug_getNativeHeapAllocatedSize },
+    { "getNativeHeapFreeSize",  "()J",
+            (void*) android_os_Debug_getNativeHeapFreeSize },
+    { "getMemoryInfo",          "(Landroid/os/Debug$MemoryInfo;)V",
+            (void*) android_os_Debug_getDirtyPages },
+    { "getBinderSentTransactions", "()I",
+            (void*) android_os_Debug_getBinderSentTransactions },
+    { "getBinderReceivedTransactions", "()I",
+            (void*) android_os_getBinderReceivedTransactions },
+    { "getBinderLocalObjectCount", "()I",
+            (void*)android_os_Debug_getLocalObjectCount },
+    { "getBinderProxyObjectCount", "()I",
+            (void*)android_os_Debug_getProxyObjectCount },
+    { "getBinderDeathObjectCount", "()I",
+            (void*)android_os_Debug_getDeathObjectCount },
+};
+
+int register_android_os_Debug(JNIEnv *env)
+{
+    jclass clazz = env->FindClass("android/os/Debug$MemoryInfo");
+    
+    dalvikPss_field = env->GetFieldID(clazz, "dalvikPss", "I");
+    dalvikPrivateDirty_field = env->GetFieldID(clazz, "dalvikPrivateDirty", "I");
+    dalvikSharedDirty_field = env->GetFieldID(clazz, "dalvikSharedDirty", "I");
+
+    nativePss_field = env->GetFieldID(clazz, "nativePss", "I");
+    nativePrivateDirty_field = env->GetFieldID(clazz, "nativePrivateDirty", "I");
+    nativeSharedDirty_field = env->GetFieldID(clazz, "nativeSharedDirty", "I");
+    
+    otherPss_field = env->GetFieldID(clazz, "otherPss", "I");
+    otherPrivateDirty_field = env->GetFieldID(clazz, "otherPrivateDirty", "I");
+    otherSharedDirty_field = env->GetFieldID(clazz, "otherSharedDirty", "I");
+    
+    return jniRegisterNativeMethods(env, "android/os/Debug", gMethods, NELEM(gMethods));
+}
+
+};
diff --git a/core/jni/android_os_Exec.cpp b/core/jni/android_os_Exec.cpp
new file mode 100644
index 0000000..ca5e695
--- /dev/null
+++ b/core/jni/android_os_Exec.cpp
@@ -0,0 +1,215 @@
+/*
+ * Copyright (C) 2007 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.
+ */
+
+#define LOG_TAG "Exec"
+
+#include "JNIHelp.h"
+#include "jni.h"
+#include "utils/Log.h"
+#include "utils/misc.h"
+#include "android_runtime/AndroidRuntime.h"
+
+#include <sys/types.h>
+#include <sys/ioctl.h>
+#include <sys/wait.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <termios.h>
+
+namespace android
+{
+
+static jclass class_fileDescriptor;
+static jfieldID field_fileDescriptor_descriptor;
+static jmethodID method_fileDescriptor_init;
+ 
+
+static int create_subprocess(const char *cmd, const char *arg0, const char *arg1,
+    int* pProcessId)
+{
+    char *devname;
+    int ptm;
+    pid_t pid;
+
+    ptm = open("/dev/ptmx", O_RDWR); // | O_NOCTTY);
+    if(ptm < 0){
+        LOGE("[ cannot open /dev/ptmx - %s ]\n",strerror(errno));
+        return -1;
+    }
+    fcntl(ptm, F_SETFD, FD_CLOEXEC);
+
+    if(grantpt(ptm) || unlockpt(ptm) ||
+       ((devname = (char*) ptsname(ptm)) == 0)){
+        LOGE("[ trouble with /dev/ptmx - %s ]\n", strerror(errno));
+        return -1;
+    }
+    
+    pid = fork();
+    if(pid < 0) {
+        LOGE("- fork failed: %s -\n", strerror(errno));
+        return -1;
+    }
+
+    if(pid == 0){
+        int pts;
+
+        setsid();
+        
+        pts = open(devname, O_RDWR);
+        if(pts < 0) exit(-1);
+
+        dup2(pts, 0);
+        dup2(pts, 1);
+        dup2(pts, 2);
+
+        close(ptm);
+
+        execl(cmd, cmd, arg0, arg1, NULL);
+        exit(-1);
+    } else {
+        *pProcessId = (int) pid;
+        return ptm;
+    }
+}
+
+
+static jobject android_os_Exec_createSubProcess(JNIEnv *env, jobject clazz,
+    jstring cmd, jstring arg0, jstring arg1, jintArray processIdArray)
+{
+    const jchar* str = cmd ? env->GetStringCritical(cmd, 0) : 0;
+    String8 cmd_8;
+    if (str) {
+        cmd_8 = String8(str, env->GetStringLength(cmd));
+        env->ReleaseStringCritical(cmd, str);
+    }
+
+    str = arg0 ? env->GetStringCritical(arg0, 0) : 0;
+    const char* arg0Str = 0;
+    String8 arg0_8;
+    if (str) {
+        arg0_8 = String8(str, env->GetStringLength(arg0));
+        env->ReleaseStringCritical(arg0, str);
+        arg0Str = arg0_8.string();
+    }
+
+    str = arg1 ? env->GetStringCritical(arg1, 0) : 0;
+    const char* arg1Str = 0;
+    String8 arg1_8;
+    if (str) {
+        arg1_8 = String8(str, env->GetStringLength(arg1));
+        env->ReleaseStringCritical(arg1, str);
+        arg1Str = arg1_8.string();
+    }
+
+    int procId;
+    int ptm = create_subprocess(cmd_8.string(), arg0Str, arg1Str, &procId);
+    
+    if (processIdArray) {
+        int procIdLen = env->GetArrayLength(processIdArray);
+        if (procIdLen > 0) {
+            jboolean isCopy;
+    
+            int* pProcId = (int*) env->GetPrimitiveArrayCritical(processIdArray, &isCopy);
+            if (pProcId) {
+                *pProcId = procId;
+                env->ReleasePrimitiveArrayCritical(processIdArray, pProcId, 0);
+            }
+        }
+    }
+    
+    jobject result = env->NewObject(class_fileDescriptor, method_fileDescriptor_init);
+    
+    if (!result) {
+        LOGE("Couldn't create a FileDescriptor.");
+    }
+    else {
+        env->SetIntField(result, field_fileDescriptor_descriptor, ptm);
+    }
+    
+    return result;
+}
+
+
+static void android_os_Exec_setPtyWindowSize(JNIEnv *env, jobject clazz,
+    jobject fileDescriptor, jint row, jint col, jint xpixel, jint ypixel)
+{
+    int fd;
+    struct winsize sz;
+
+    fd = jniGetFDFromFileDescriptor(env, fileDescriptor);
+
+    if (env->ExceptionOccurred() != NULL) {
+        return;
+    }
+    
+    sz.ws_row = row;
+    sz.ws_col = col;
+    sz.ws_xpixel = xpixel;
+    sz.ws_ypixel = ypixel;
+    
+    ioctl(fd, TIOCSWINSZ, &sz);
+}
+
+static int android_os_Exec_waitFor(JNIEnv *env, jobject clazz,
+    jint procId) {
+    int status;
+    waitpid(procId, &status, 0);
+    int result = 0;
+    if (WIFEXITED(status)) {
+        result = WEXITSTATUS(status);
+    }
+    return result;
+}
+
+static JNINativeMethod method_table[] = {
+    { "createSubprocess", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;[I)Ljava/io/FileDescriptor;",
+        (void*) android_os_Exec_createSubProcess },
+    { "setPtyWindowSize", "(Ljava/io/FileDescriptor;IIII)V",
+        (void*) android_os_Exec_setPtyWindowSize},
+    { "waitFor", "(I)I",
+        (void*) android_os_Exec_waitFor}
+};
+
+int register_android_os_Exec(JNIEnv *env)
+{
+    class_fileDescriptor = env->FindClass("java/io/FileDescriptor");
+
+    if (class_fileDescriptor == NULL) {
+        LOGE("Can't find java/io/FileDescriptor");
+        return -1;
+    }
+
+    field_fileDescriptor_descriptor = env->GetFieldID(class_fileDescriptor, "descriptor", "I");
+
+    if (field_fileDescriptor_descriptor == NULL) {
+        LOGE("Can't find FileDescriptor.descriptor");
+        return -1;
+    }
+
+    method_fileDescriptor_init = env->GetMethodID(class_fileDescriptor, "<init>", "()V");
+    if (method_fileDescriptor_init == NULL) {
+        LOGE("Can't find FileDescriptor.init");
+        return -1;
+     }
+
+    return AndroidRuntime::registerNativeMethods(
+        env, "android/os/Exec",
+        method_table, NELEM(method_table));
+}
+
+};
diff --git a/core/jni/android_os_FileUtils.cpp b/core/jni/android_os_FileUtils.cpp
new file mode 100644
index 0000000..21cb919
--- /dev/null
+++ b/core/jni/android_os_FileUtils.cpp
@@ -0,0 +1,208 @@
+/* //device/libs/android_runtime/android_util_Process.cpp
+**
+** Copyright 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.
+*/
+
+#define LOG_TAG "FileUtils"
+
+#include <utils/Log.h>
+
+#include <android_runtime/AndroidRuntime.h>
+
+#include "JNIHelp.h"
+
+#include <sys/errno.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <fcntl.h>
+#include <signal.h>
+
+#if HAVE_ANDROID_OS
+#include <sys/ioctl.h>
+#include <linux/msdos_fs.h>
+#endif
+
+namespace android {
+
+static jclass gFileStatusClass;
+static jfieldID gFileStatusDevFieldID;
+static jfieldID gFileStatusInoFieldID;
+static jfieldID gFileStatusModeFieldID;
+static jfieldID gFileStatusNlinkFieldID;
+static jfieldID gFileStatusUidFieldID;
+static jfieldID gFileStatusGidFieldID;
+static jfieldID gFileStatusSizeFieldID;
+static jfieldID gFileStatusBlksizeFieldID;
+static jfieldID gFileStatusBlocksFieldID;
+static jfieldID gFileStatusAtimeFieldID;
+static jfieldID gFileStatusMtimeFieldID;
+static jfieldID gFileStatusCtimeFieldID;
+
+jint android_os_FileUtils_setPermissions(JNIEnv* env, jobject clazz,
+                                         jstring file, jint mode,
+                                         jint uid, jint gid)
+{
+    #if HAVE_ANDROID_OS
+    const jchar* str = env->GetStringCritical(file, 0);
+    String8 file8;
+    if (str) {
+        file8 = String8(str, env->GetStringLength(file));
+        env->ReleaseStringCritical(file, str);
+    }
+    if (file8.size() <= 0) {
+        return ENOENT;
+    }
+    if (uid >= 0 || gid >= 0) {
+        int res = chown(file8.string(), uid, gid);
+        if (res != 0) {
+            return errno;
+        }
+    }
+    return chmod(file8.string(), mode) == 0 ? 0 : errno;
+    #else
+    return ENOSYS;
+    #endif
+}
+
+jint android_os_FileUtils_getPermissions(JNIEnv* env, jobject clazz,
+                                         jstring file, jintArray outArray)
+{
+    #if HAVE_ANDROID_OS
+    const jchar* str = env->GetStringCritical(file, 0);
+    String8 file8;
+    if (str) {
+        file8 = String8(str, env->GetStringLength(file));
+        env->ReleaseStringCritical(file, str);
+    }
+    if (file8.size() <= 0) {
+        return ENOENT;
+    }
+    struct stat st;
+    if (stat(file8.string(), &st) != 0) {
+        return errno;
+    }
+    jint* array = (jint*)env->GetPrimitiveArrayCritical(outArray, 0);
+    if (array) {
+        int len = env->GetArrayLength(outArray);
+        if (len >= 1) {
+            array[0] = st.st_mode;
+        }
+        if (len >= 2) {
+            array[1] = st.st_uid;
+        }
+        if (len >= 3) {
+            array[2] = st.st_gid;
+        }
+    }
+    env->ReleasePrimitiveArrayCritical(outArray, array, 0);
+    return 0;
+    #else
+    return ENOSYS;
+    #endif
+}
+
+jint android_os_FileUtils_getFatVolumeId(JNIEnv* env, jobject clazz, jstring path)
+{
+    #if HAVE_ANDROID_OS
+    if (path == NULL) {
+        jniThrowException(env, "java/lang/IllegalArgumentException", NULL);
+        return -1;
+    }
+    const char *pathStr = env->GetStringUTFChars(path, NULL);
+    int result = -1;
+    // only if our system supports this ioctl
+    #ifdef VFAT_IOCTL_GET_VOLUME_ID
+    int fd = open(pathStr, O_RDONLY);
+    if (fd >= 0) {
+        result = ioctl(fd, VFAT_IOCTL_GET_VOLUME_ID);
+        close(fd);
+    }
+    #endif
+
+    env->ReleaseStringUTFChars(path, pathStr);
+    return result;
+    #else
+    return -1;
+    #endif
+}
+
+jboolean android_os_FileUtils_getFileStatus(JNIEnv* env, jobject clazz, jstring path, jobject fileStatus) {
+    const char* pathStr = env->GetStringUTFChars(path, NULL);
+    jboolean ret = false;
+    
+    struct stat s;
+    int res = stat(pathStr, &s);
+    if (res == 0) {
+        ret = true;
+        if (fileStatus != NULL) {
+            env->SetIntField(fileStatus, gFileStatusDevFieldID, s.st_dev);
+            env->SetIntField(fileStatus, gFileStatusInoFieldID, s.st_ino);
+            env->SetIntField(fileStatus, gFileStatusModeFieldID, s.st_mode);
+            env->SetIntField(fileStatus, gFileStatusNlinkFieldID, s.st_nlink);
+            env->SetIntField(fileStatus, gFileStatusUidFieldID, s.st_uid);
+            env->SetIntField(fileStatus, gFileStatusGidFieldID, s.st_gid);
+            env->SetLongField(fileStatus, gFileStatusSizeFieldID, s.st_size);
+            env->SetIntField(fileStatus, gFileStatusBlksizeFieldID, s.st_blksize);
+            env->SetLongField(fileStatus, gFileStatusBlocksFieldID, s.st_blocks);
+            env->SetLongField(fileStatus, gFileStatusAtimeFieldID, s.st_atime);
+            env->SetLongField(fileStatus, gFileStatusMtimeFieldID, s.st_mtime);
+            env->SetLongField(fileStatus, gFileStatusCtimeFieldID, s.st_ctime);
+        }
+    }
+    
+    env->ReleaseStringUTFChars(path, pathStr);
+    
+    return ret;
+}
+
+static const JNINativeMethod methods[] = {
+    {"setPermissions",  "(Ljava/lang/String;III)I", (void*)android_os_FileUtils_setPermissions},
+    {"getPermissions",  "(Ljava/lang/String;[I)I", (void*)android_os_FileUtils_getPermissions},
+    {"getFatVolumeId",  "(Ljava/lang/String;)I", (void*)android_os_FileUtils_getFatVolumeId},
+    {"getFileStatus",  "(Ljava/lang/String;Landroid/os/FileUtils$FileStatus;)Z", (void*)android_os_FileUtils_getFileStatus},
+};
+
+static const char* const kFileUtilsPathName = "android/os/FileUtils";
+
+int register_android_os_FileUtils(JNIEnv* env)
+{
+    jclass clazz;
+
+    clazz = env->FindClass(kFileUtilsPathName);
+    LOG_FATAL_IF(clazz == NULL, "Unable to find class android.os.FileUtils");
+    
+    gFileStatusClass = env->FindClass("android/os/FileUtils$FileStatus");
+    LOG_FATAL_IF(gFileStatusClass == NULL, "Unable to find class android.os.FileUtils$FileStatus");
+
+    gFileStatusDevFieldID = env->GetFieldID(gFileStatusClass, "dev", "I");
+    gFileStatusInoFieldID = env->GetFieldID(gFileStatusClass, "ino", "I");
+    gFileStatusModeFieldID = env->GetFieldID(gFileStatusClass, "mode", "I");
+    gFileStatusNlinkFieldID = env->GetFieldID(gFileStatusClass, "nlink", "I");
+    gFileStatusUidFieldID = env->GetFieldID(gFileStatusClass, "uid", "I");
+    gFileStatusGidFieldID = env->GetFieldID(gFileStatusClass, "gid", "I");
+    gFileStatusSizeFieldID = env->GetFieldID(gFileStatusClass, "size", "J");
+    gFileStatusBlksizeFieldID = env->GetFieldID(gFileStatusClass, "blksize", "I");
+    gFileStatusBlocksFieldID = env->GetFieldID(gFileStatusClass, "blocks", "J");
+    gFileStatusAtimeFieldID = env->GetFieldID(gFileStatusClass, "atime", "J");
+    gFileStatusMtimeFieldID = env->GetFieldID(gFileStatusClass, "mtime", "J");
+    gFileStatusCtimeFieldID = env->GetFieldID(gFileStatusClass, "ctime", "J");
+
+    return AndroidRuntime::registerNativeMethods(
+        env, kFileUtilsPathName,
+        methods, NELEM(methods));
+}
+
+}
+
diff --git a/core/jni/android_os_Hardware.cpp b/core/jni/android_os_Hardware.cpp
new file mode 100644
index 0000000..bc8af78
--- /dev/null
+++ b/core/jni/android_os_Hardware.cpp
@@ -0,0 +1,91 @@
+/*
+ * Copyright 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.
+*/
+
+#include <hardware_legacy/flashlight.h>
+#include <hardware_legacy/led.h>
+#include <hardware_legacy/power.h>
+
+#include <nativehelper/jni.h>
+#include <android_runtime/AndroidRuntime.h>
+#include <nativehelper/JNIHelp.h>
+
+namespace android {
+
+static jboolean
+setLedState(JNIEnv *env, jobject clazz, jint colorARGB, jint onMS, jint offMS)
+{
+    return set_led_state(colorARGB, onMS, offMS);
+}
+
+static jint
+getFlashlightEnabled(JNIEnv *env, jobject clazz)
+{
+    return get_flashlight_enabled();
+}
+
+static void
+setFlashlightEnabled(JNIEnv *env, jobject clazz, jboolean on)
+{
+    set_flashlight_enabled(on);
+}
+
+static void
+enableCameraFlash(JNIEnv *env, jobject clazz, jint milliseconds)
+{
+    enable_camera_flash(milliseconds);
+}
+
+static void
+setScreenBacklight(JNIEnv *env, jobject clazz, jint brightness)
+{
+    set_light_brightness(SCREEN_LIGHT, brightness);
+}
+
+static void
+setKeyboardBacklight(JNIEnv *env, jobject clazz, jboolean on)
+{
+    set_light_brightness(KEYBOARD_LIGHT, (on ? 255 : 0));
+}
+
+static void
+setButtonBacklight(JNIEnv *env, jobject clazz, jboolean on)
+{
+    set_light_brightness(BUTTON_LIGHT, (on ? 255 : 0));
+}
+
+// ============================================================================
+/*
+ * JNI registration.
+ */
+
+static JNINativeMethod g_methods[] = {
+    /* name, signature, funcPtr */
+    { "setLedState",       "(III)I", (void*)setLedState },
+    { "getFlashlightEnabled", "()Z", (void*)getFlashlightEnabled },
+    { "setFlashlightEnabled", "(Z)V", (void*)setFlashlightEnabled },
+    { "enableCameraFlash", "(I)V", (void*)enableCameraFlash },
+    { "setScreenBacklight", "(I)V", (void*)setScreenBacklight },
+    { "setKeyboardBacklight", "(Z)V", (void*)setKeyboardBacklight },
+    { "setButtonBacklight", "(Z)V", (void*)setButtonBacklight },
+};
+
+int register_android_os_Hardware(JNIEnv* env)
+{
+    return AndroidRuntime::registerNativeMethods(env,
+            "android/os/Hardware", g_methods, NELEM(g_methods));
+}
+
+}; // namespace android
diff --git a/core/jni/android_os_MemoryFile.cpp b/core/jni/android_os_MemoryFile.cpp
new file mode 100644
index 0000000..edf7dc4
--- /dev/null
+++ b/core/jni/android_os_MemoryFile.cpp
@@ -0,0 +1,129 @@
+/*
+ * 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.
+ */
+
+#define LOG_TAG "MemoryFile"
+#include <utils/Log.h>
+
+#include <cutils/ashmem.h>
+#include <android_runtime/AndroidRuntime.h>
+#include "JNIHelp.h"
+#include <unistd.h>
+#include <sys/mman.h>
+
+
+namespace android {
+
+static jint android_os_MemoryFile_open(JNIEnv* env, jobject clazz, jstring name, jint length)
+{
+    const char* namestr = (name ? env->GetStringUTFChars(name, NULL) : NULL);
+
+    // round up length to page boundary
+    length = (((length - 1) / getpagesize()) + 1) * getpagesize();
+    int result = ashmem_create_region(namestr, length);
+
+    if (name)
+        env->ReleaseStringUTFChars(name, namestr);
+
+    if (result < 0)
+        jniThrowException(env, "java/io/IOException", "ashmem_create_region failed");
+    return result;
+}
+
+static jint android_os_MemoryFile_mmap(JNIEnv* env, jobject clazz, jint fd, jint length)
+{
+    jint result = (jint)mmap(NULL, length, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0);
+    if (!result)
+        jniThrowException(env, "java/io/IOException", "mmap failed");
+    return result;
+}
+
+static void android_os_MemoryFile_close(JNIEnv* env, jobject clazz, jint fd)
+{
+    close(fd);
+}
+
+static jint android_os_MemoryFile_read(JNIEnv* env, jobject clazz,
+        jint fd, jint address, jbyteArray buffer, jint srcOffset, jint destOffset,
+        jint count, jboolean unpinned)
+{
+    if (unpinned && ashmem_pin_region(fd, 0, 0) == ASHMEM_WAS_PURGED) {
+        ashmem_unpin_region(fd, 0, 0);
+        jniThrowException(env, "java/io/IOException", "ashmem region was purged");
+        return -1;
+    }
+
+    jbyte* bytes = env->GetByteArrayElements(buffer, 0);
+    memcpy(bytes + destOffset, (const char *)address + srcOffset, count);
+    env->ReleaseByteArrayElements(buffer, bytes, 0);
+
+    if (unpinned) {
+        ashmem_unpin_region(fd, 0, 0);
+    }
+    return count;
+}
+
+static jint android_os_MemoryFile_write(JNIEnv* env, jobject clazz,
+        jint fd, jint address, jbyteArray buffer, jint srcOffset, jint destOffset,
+        jint count, jboolean unpinned)
+{
+    if (unpinned && ashmem_pin_region(fd, 0, 0) == ASHMEM_WAS_PURGED) {
+        ashmem_unpin_region(fd, 0, 0);
+        jniThrowException(env, "java/io/IOException", "ashmem region was purged");
+        return -1;
+    }
+
+    jbyte* bytes = env->GetByteArrayElements(buffer, 0);
+    memcpy((char *)address + destOffset, bytes + srcOffset, count);
+    env->ReleaseByteArrayElements(buffer, bytes, 0);
+
+    if (unpinned) {
+        ashmem_unpin_region(fd, 0, 0);
+    }
+    return count;
+}
+
+static void android_os_MemoryFile_pin(JNIEnv* env, jobject clazz, jint fd, jboolean pin)
+{
+    int result = (pin ? ashmem_pin_region(fd, 0, 0) : ashmem_unpin_region(fd, 0, 0));
+    if (result < 0) {
+        jniThrowException(env, "java/io/IOException", NULL);
+    }
+}
+
+static const JNINativeMethod methods[] = {
+	{"native_open",  "(Ljava/lang/String;I)I", (void*)android_os_MemoryFile_open},
+    {"native_mmap",  "(II)I", (void*)android_os_MemoryFile_mmap},
+    {"native_close", "(I)V", (void*)android_os_MemoryFile_close},
+    {"native_read",  "(II[BIIIZ)I", (void*)android_os_MemoryFile_read},
+    {"native_write", "(II[BIIIZ)V", (void*)android_os_MemoryFile_write},
+    {"native_pin",   "(IZ)V", (void*)android_os_MemoryFile_pin},
+};
+
+static const char* const kClassPathName = "android/os/MemoryFile";
+
+int register_android_os_MemoryFile(JNIEnv* env)
+{
+    jclass clazz;
+
+    clazz = env->FindClass(kClassPathName);
+    LOG_FATAL_IF(clazz == NULL, "Unable to find class android.os.FileUtils");
+
+    return AndroidRuntime::registerNativeMethods(
+        env, kClassPathName,
+        methods, NELEM(methods));
+}
+
+}
diff --git a/core/jni/android_os_ParcelFileDescriptor.cpp b/core/jni/android_os_ParcelFileDescriptor.cpp
new file mode 100644
index 0000000..1429f58
--- /dev/null
+++ b/core/jni/android_os_ParcelFileDescriptor.cpp
@@ -0,0 +1,130 @@
+/* //device/libs/android_runtime/android_os_ParcelFileDescriptor.cpp
+**
+** Copyright 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.
+*/
+
+#include "JNIHelp.h"
+
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <stdio.h>
+
+#include <utils/Log.h>
+
+#include <android_runtime/AndroidRuntime.h>
+
+namespace android
+{
+
+static struct file_descriptor_offsets_t
+{
+    jclass mClass;
+    jmethodID mConstructor;
+    jfieldID mDescriptor;
+} gFileDescriptorOffsets;
+
+static struct socket_offsets_t
+{
+    jfieldID mSocketImpl;
+} gSocketOffsets;
+
+static struct socket_impl_offsets_t
+{
+    jfieldID mFileDescriptor;
+} gSocketImplOffsets;
+
+
+static jobject android_os_ParcelFileDescriptor_getFileDescriptorFromSocket(JNIEnv* env,
+    jobject clazz, jobject object)
+{
+    jobject socketImpl = env->GetObjectField(object, gSocketOffsets.mSocketImpl);
+    jobject fileDescriptor = env->GetObjectField(socketImpl, gSocketImplOffsets.mFileDescriptor);
+    jint fd = env->GetIntField(fileDescriptor, gFileDescriptorOffsets.mDescriptor);
+    jobject fileDescriptorClone = env->NewObject(gFileDescriptorOffsets.mClass,
+        gFileDescriptorOffsets.mConstructor);
+    if (fileDescriptorClone != NULL) {
+        env->SetIntField(fileDescriptorClone, gFileDescriptorOffsets.mDescriptor, dup(fd));
+    }
+    return fileDescriptorClone;
+}
+
+static jlong android_os_ParcelFileDescriptor_getStatSize(JNIEnv* env,
+    jobject clazz)
+{
+    jint fd = env->GetIntField(clazz, gFileDescriptorOffsets.mDescriptor);
+    
+    struct stat st;
+    if (fstat(fd, &st) != 0) {
+        return -1;
+    }
+    
+    if (S_ISREG(st.st_mode) || S_ISLNK(st.st_mode)) {
+        return st.st_size;
+    }
+    
+    return -1;
+}
+
+static jlong android_os_ParcelFileDescriptor_seekTo(JNIEnv* env,
+    jobject clazz, jlong pos)
+{
+    jint fd = env->GetIntField(clazz, gFileDescriptorOffsets.mDescriptor);
+    return lseek(fd, pos, SEEK_SET);
+}
+
+static const JNINativeMethod gParcelFileDescriptorMethods[] = {
+    {"getFileDescriptorFromSocket", "(Ljava/net/Socket;)Ljava/io/FileDescriptor;",
+        (void*)android_os_ParcelFileDescriptor_getFileDescriptorFromSocket},
+    {"getStatSize", "()J",
+        (void*)android_os_ParcelFileDescriptor_getStatSize},
+    {"seekTo", "(J)J",
+        (void*)android_os_ParcelFileDescriptor_seekTo}
+};
+
+const char* const kParcelFileDescriptorPathName = "android/os/ParcelFileDescriptor";
+
+int register_android_os_ParcelFileDescriptor(JNIEnv* env)
+{
+    jclass clazz;
+
+    clazz = env->FindClass("java/net/Socket");
+    LOG_FATAL_IF(clazz == NULL, "Unable to find class java.net.Socket");
+    gSocketOffsets.mSocketImpl = env->GetFieldID(clazz, "impl", "Ljava/net/SocketImpl;");
+    LOG_FATAL_IF(gSocketOffsets.mSocketImpl == NULL,
+        "Unable to find impl field in java.net.Socket");
+
+    clazz = env->FindClass("java/net/SocketImpl");
+    LOG_FATAL_IF(clazz == NULL, "Unable to find class java.net.SocketImpl");
+    gSocketImplOffsets.mFileDescriptor = env->GetFieldID(clazz, "fd", "Ljava/io/FileDescriptor;");
+    LOG_FATAL_IF(gSocketImplOffsets.mFileDescriptor == NULL,
+                 "Unable to find fd field in java.net.SocketImpl");
+
+    clazz = env->FindClass("java/io/FileDescriptor");
+    LOG_FATAL_IF(clazz == NULL, "Unable to find class java.io.FileDescriptor");
+    gFileDescriptorOffsets.mClass = (jclass) env->NewGlobalRef(clazz);
+    gFileDescriptorOffsets.mConstructor = env->GetMethodID(clazz, "<init>", "()V");
+    gFileDescriptorOffsets.mDescriptor = env->GetFieldID(clazz, "descriptor", "I");
+    LOG_FATAL_IF(gFileDescriptorOffsets.mDescriptor == NULL,
+                 "Unable to find descriptor field in java.io.FileDescriptor");
+    
+    clazz = env->FindClass(kParcelFileDescriptorPathName);
+    LOG_FATAL_IF(clazz == NULL, "Unable to find class android.os.ParcelFileDescriptor");
+
+    return AndroidRuntime::registerNativeMethods(
+        env, kParcelFileDescriptorPathName,
+        gParcelFileDescriptorMethods, NELEM(gParcelFileDescriptorMethods));
+}
+
+}
diff --git a/core/jni/android_os_Power.cpp b/core/jni/android_os_Power.cpp
new file mode 100644
index 0000000..c2d75b1
--- /dev/null
+++ b/core/jni/android_os_Power.cpp
@@ -0,0 +1,125 @@
+/* //device/libs/android_runtime/android_os_Power.cpp
+**
+** Copyright 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.
+*/
+
+#include "JNIHelp.h"
+#include "jni.h"
+#include "android_runtime/AndroidRuntime.h"
+#include <utils/misc.h>
+#include <hardware_legacy/power.h>
+#include <sys/reboot.h>
+
+namespace android
+{
+
+static void throw_NullPointerException(JNIEnv *env, const char* msg)
+{
+    jclass clazz;
+    clazz = env->FindClass("java/lang/NullPointerException");
+    env->ThrowNew(clazz, msg);
+}
+
+static void
+acquireWakeLock(JNIEnv *env, jobject clazz, jint lock, jstring idObj)
+{
+    if (idObj == NULL) {
+        throw_NullPointerException(env, "id is null");
+        return ;
+    }
+
+    const char *id = env->GetStringUTFChars(idObj, NULL);
+
+    acquire_wake_lock(lock, id);
+
+    env->ReleaseStringUTFChars(idObj, id);
+}
+
+static void
+releaseWakeLock(JNIEnv *env, jobject clazz, jstring idObj)
+{
+    if (idObj == NULL) {
+        throw_NullPointerException(env, "id is null");
+        return ;
+    }
+
+    const char *id = env->GetStringUTFChars(idObj, NULL);
+
+    release_wake_lock(id);
+
+    env->ReleaseStringUTFChars(idObj, id);
+
+}
+
+static int
+setLastUserActivityTimeout(JNIEnv *env, jobject clazz, jlong timeMS)
+{
+    return set_last_user_activity_timeout(timeMS/1000);
+}
+
+static int
+setLightBrightness(JNIEnv *env, jobject clazz, jint mask, jint brightness)
+{
+    return set_light_brightness(mask, brightness);
+}
+
+static int
+setScreenState(JNIEnv *env, jobject clazz, jboolean on)
+{
+    return set_screen_state(on);
+}
+
+static void android_os_Power_shutdown(JNIEnv *env, jobject clazz)
+{
+    sync();
+#ifdef HAVE_ANDROID_OS
+    reboot(RB_POWER_OFF);
+#endif
+}
+
+static void android_os_Power_reboot(JNIEnv *env, jobject clazz, jstring reason)
+{
+    sync();
+#ifdef HAVE_ANDROID_OS
+    if (reason == NULL) {
+        reboot(RB_AUTOBOOT);
+    } else {
+        const char *chars = env->GetStringUTFChars(reason, NULL);
+        __reboot(LINUX_REBOOT_MAGIC1, LINUX_REBOOT_MAGIC2,
+                 LINUX_REBOOT_CMD_RESTART2, (char*) chars);
+        env->ReleaseStringUTFChars(reason, chars);  // In case it fails.
+    }
+    jniThrowIOException(env, errno);
+#endif
+}
+
+static JNINativeMethod method_table[] = {
+    { "acquireWakeLock", "(ILjava/lang/String;)V", (void*)acquireWakeLock },
+    { "releaseWakeLock", "(Ljava/lang/String;)V", (void*)releaseWakeLock },
+    { "setLastUserActivityTimeout", "(J)I", (void*)setLastUserActivityTimeout },
+    { "setLightBrightness", "(II)I", (void*)setLightBrightness },
+    { "setScreenState", "(Z)I", (void*)setScreenState },
+    { "shutdown", "()V", (void*)android_os_Power_shutdown },
+    { "reboot", "(Ljava/lang/String;)V", (void*)android_os_Power_reboot },
+};
+
+int register_android_os_Power(JNIEnv *env)
+{
+    return AndroidRuntime::registerNativeMethods(
+        env, "android/os/Power",
+        method_table, NELEM(method_table));
+}
+
+};
diff --git a/core/jni/android_os_StatFs.cpp b/core/jni/android_os_StatFs.cpp
new file mode 100644
index 0000000..c658aa5
--- /dev/null
+++ b/core/jni/android_os_StatFs.cpp
@@ -0,0 +1,163 @@
+/*
+ * Copyright 2007, 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.
+ */
+
+#if INCLUDE_SYS_MOUNT_FOR_STATFS
+#include <sys/mount.h>
+#else
+#include <sys/statfs.h>
+#endif
+
+#include <errno.h>
+
+#include "jni.h"
+#include "JNIHelp.h"
+#include "android_runtime/AndroidRuntime.h"
+
+
+namespace android
+{
+
+// ----------------------------------------------------------------------------
+
+struct fields_t {
+    jfieldID    context;
+};
+static fields_t fields;
+
+// ----------------------------------------------------------------------------
+
+static jint
+android_os_StatFs_getBlockSize(JNIEnv *env, jobject thiz)
+{
+    struct statfs *stat = (struct statfs *)env->GetIntField(thiz, fields.context);
+    return stat->f_bsize;
+}
+
+static jint
+android_os_StatFs_getBlockCount(JNIEnv *env, jobject thiz)
+{
+    struct statfs *stat = (struct statfs *)env->GetIntField(thiz, fields.context);
+    return stat->f_blocks;
+}
+
+static jint
+android_os_StatFs_getFreeBlocks(JNIEnv *env, jobject thiz)
+{
+    struct statfs *stat = (struct statfs *)env->GetIntField(thiz, fields.context);
+    return stat->f_bfree;
+}
+
+static jint
+android_os_StatFs_getAvailableBlocks(JNIEnv *env, jobject thiz)
+{
+    struct statfs *stat = (struct statfs *)env->GetIntField(thiz, fields.context);
+    return stat->f_bavail;
+}
+
+static void
+android_os_StatFs_native_restat(JNIEnv *env, jobject thiz, jstring path)
+{
+    if (path == NULL) {
+        jniThrowException(env, "java/lang/IllegalArgumentException", NULL);
+        return;
+    }
+
+    // get the object handle
+    struct statfs *stat = (struct statfs *)env->GetIntField(thiz, fields.context);
+    if (stat == NULL) {
+        jniThrowException(env, "java/lang/NoSuchFieldException", NULL);
+        return;
+    }
+
+    const char* pathstr = env->GetStringUTFChars(path, NULL);
+    if (pathstr == NULL) {
+        jniThrowException(env, "java/lang/RuntimeException", "Out of memory");
+        return;
+    }
+
+    // note that stat will contain the new file data corresponding to
+    // pathstr
+    if (statfs(pathstr, stat) != 0) {
+        LOGE("statfs %s failed, errno: %d", pathstr, errno);
+        delete stat;
+        env->SetIntField(thiz, fields.context, 0);
+        jniThrowException(env, "java/lang/IllegalArgumentException", NULL);
+    }
+    // Release pathstr
+    env->ReleaseStringUTFChars(path, pathstr);
+}
+
+static void
+android_os_StatFs_native_setup(JNIEnv *env, jobject thiz, jstring path)
+{
+    if (path == NULL) {
+        jniThrowException(env, "java/lang/IllegalArgumentException", NULL);
+        return;
+    }
+
+    struct statfs* stat = new struct statfs;
+    if (stat == NULL) {
+        jniThrowException(env, "java/lang/RuntimeException", "Out of memory");
+        return;
+    }
+    env->SetIntField(thiz, fields.context, (int)stat);
+    android_os_StatFs_native_restat(env, thiz, path);
+}
+
+static void
+android_os_StatFs_native_finalize(JNIEnv *env, jobject thiz)
+{
+    struct statfs *stat = (struct statfs *)env->GetIntField(thiz, fields.context);
+    if (stat != NULL) {
+        delete stat;
+        env->SetIntField(thiz, fields.context, 0);
+    }
+}
+
+// ----------------------------------------------------------------------------
+
+static JNINativeMethod gMethods[] = {
+    {"getBlockSize",       "()I",                       (void *)android_os_StatFs_getBlockSize},
+    {"getBlockCount",      "()I",                       (void *)android_os_StatFs_getBlockCount},
+    {"getFreeBlocks",      "()I",                       (void *)android_os_StatFs_getFreeBlocks},
+    {"getAvailableBlocks", "()I",                       (void *)android_os_StatFs_getAvailableBlocks},
+    {"native_setup",       "(Ljava/lang/String;)V",     (void *)android_os_StatFs_native_setup},
+    {"native_finalize",    "()V",                       (void *)android_os_StatFs_native_finalize},
+    {"native_restat",      "(Ljava/lang/String;)V",     (void *)android_os_StatFs_native_restat},
+};
+
+
+int register_android_os_StatFs(JNIEnv *env)
+{
+    jclass clazz;
+
+    clazz = env->FindClass("android/os/StatFs");
+    if (clazz == NULL) {
+        LOGE("Can't find android/os/StatFs");
+        return -1;
+    }
+
+    fields.context = env->GetFieldID(clazz, "mNativeContext", "I");
+    if (fields.context == NULL) {
+        LOGE("Can't find StatFs.mNativeContext");
+        return -1;
+    }
+
+    return AndroidRuntime::registerNativeMethods(env,
+            "android/os/StatFs", gMethods, NELEM(gMethods));
+}
+
+}   // namespace android
diff --git a/core/jni/android_os_SystemClock.cpp b/core/jni/android_os_SystemClock.cpp
new file mode 100644
index 0000000..ffd0c1e
--- /dev/null
+++ b/core/jni/android_os_SystemClock.cpp
@@ -0,0 +1,103 @@
+/*
+ * 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.
+ */
+
+
+/*
+ * System clock functions.
+ */
+
+#include "JNIHelp.h"
+#include "jni.h"
+#include "android_runtime/AndroidRuntime.h"
+
+#include "utils/SystemClock.h"
+
+#include <sys/time.h>
+#include <time.h>
+
+namespace android {
+
+/*
+ * native public static void setCurrentTimeMillis(long millis)
+ *
+ * Set the current time.  This only works when running as root.
+ */
+static jboolean android_os_SystemClock_setCurrentTimeMillis(JNIEnv* env,
+    jobject clazz, jlong millis)
+{
+    return (setCurrentTimeMillis(millis) == 0);
+}
+
+/*
+ * native public static long uptimeMillis();
+ */
+static jlong android_os_SystemClock_uptimeMillis(JNIEnv* env,
+        jobject clazz)
+{
+    return (jlong)uptimeMillis();
+}
+
+/*
+ * native public static long elapsedRealtime();
+ */
+static jlong android_os_SystemClock_elapsedRealtime(JNIEnv* env,
+        jobject clazz)
+{
+    return (jlong)elapsedRealtime();
+}
+
+/*
+ * native public static long currentThreadTimeMillis();
+ */
+static jlong android_os_SystemClock_currentThreadTimeMillis(JNIEnv* env,
+        jobject clazz)
+{
+#if defined(HAVE_POSIX_CLOCKS)
+    struct timespec tm;
+
+    clock_gettime(CLOCK_THREAD_CPUTIME_ID, &tm);
+
+    return tm.tv_sec * 1000LL + tm.tv_nsec / 1000000;
+#else
+    struct timeval tv;
+
+    gettimeofday(&tv, NULL);
+    return tv.tv_sec * 1000LL + tv.tv_usec / 1000;
+#endif
+}
+
+/*
+ * JNI registration.
+ */
+static JNINativeMethod gMethods[] = {
+    /* name, signature, funcPtr */
+    { "setCurrentTimeMillis",      "(J)Z",
+            (void*) android_os_SystemClock_setCurrentTimeMillis },
+    { "uptimeMillis",      "()J",
+            (void*) android_os_SystemClock_uptimeMillis },
+    { "elapsedRealtime",      "()J",
+            (void*) android_os_SystemClock_elapsedRealtime },
+    { "currentThreadTimeMillis",      "()J",
+            (void*) android_os_SystemClock_currentThreadTimeMillis },
+};
+int register_android_os_SystemClock(JNIEnv* env)
+{
+    return AndroidRuntime::registerNativeMethods(env,
+            "android/os/SystemClock", gMethods, NELEM(gMethods));
+}
+
+}; // namespace android
+
diff --git a/core/jni/android_os_SystemProperties.cpp b/core/jni/android_os_SystemProperties.cpp
new file mode 100644
index 0000000..ca4fa11
--- /dev/null
+++ b/core/jni/android_os_SystemProperties.cpp
@@ -0,0 +1,108 @@
+/* //device/libs/android_runtime/android_os_SystemProperties.cpp
+**
+** Copyright 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.
+*/
+
+#include "cutils/properties.h"
+#include "jni.h"
+#include "android_runtime/AndroidRuntime.h"
+#include <nativehelper/JNIHelp.h>
+
+namespace android
+{
+
+static jstring SystemProperties_getSS(JNIEnv *env, jobject clazz,
+                                      jstring keyJ, jstring defJ)
+{
+    int len;
+    const char* key;
+    char buf[PROPERTY_VALUE_MAX];
+    jstring rvJ = NULL;
+
+    if (keyJ == NULL) {
+        jniThrowException(env, "java/lang/NullPointerException",
+                                "key must not be null.");
+        goto error;
+    }
+    
+    key = env->GetStringUTFChars(keyJ, NULL);
+    
+    len = property_get(key, buf, "");
+    if ((len <= 0) && (defJ != NULL)) {
+        rvJ = defJ;
+    } else if (len >= 0) {
+        rvJ = env->NewStringUTF(buf);
+    } else {
+        rvJ = env->NewStringUTF("");
+    }
+    
+    env->ReleaseStringUTFChars(keyJ, key);
+    
+error:
+    return rvJ;
+}
+
+static jstring SystemProperties_getS(JNIEnv *env, jobject clazz,
+                                      jstring keyJ)
+{
+    return SystemProperties_getSS(env, clazz, keyJ, NULL);
+}
+
+static void SystemProperties_set(JNIEnv *env, jobject clazz,
+                                      jstring keyJ, jstring valJ)
+{
+    int err;
+    const char* key;
+    const char* val;
+
+    if (keyJ == NULL) {
+        jniThrowException(env, "java/lang/NullPointerException",
+                                "key must not be null.");
+        return ;
+    }
+    key = env->GetStringUTFChars(keyJ, NULL);
+
+    if (valJ == NULL) {
+        val = "";       /* NULL pointer not allowed here */
+    } else {
+        val = env->GetStringUTFChars(valJ, NULL);
+    }
+    
+    err = property_set(key, val);
+    
+    env->ReleaseStringUTFChars(keyJ, key);
+    
+    if (valJ != NULL) {
+    	env->ReleaseStringUTFChars(valJ, val);
+    }
+} 
+
+static JNINativeMethod method_table[] = {
+    { "native_get", "(Ljava/lang/String;)Ljava/lang/String;",
+      (void*) SystemProperties_getS },
+    { "native_get", "(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;",
+      (void*) SystemProperties_getSS },
+    { "native_set", "(Ljava/lang/String;Ljava/lang/String;)V",
+      (void*) SystemProperties_set },
+};
+
+int register_android_os_SystemProperties(JNIEnv *env)
+{
+    return AndroidRuntime::registerNativeMethods(
+        env, "android/os/SystemProperties",
+        method_table, NELEM(method_table));
+}
+
+};
diff --git a/core/jni/android_os_UEventObserver.cpp b/core/jni/android_os_UEventObserver.cpp
new file mode 100644
index 0000000..7f31b00
--- /dev/null
+++ b/core/jni/android_os_UEventObserver.cpp
@@ -0,0 +1,70 @@
+/*
+ * 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.
+ */
+
+#define LOG_TAG "UEventObserver"
+#include "utils/Log.h"
+
+#include "hardware_legacy/uevent.h"
+#include "jni.h"
+#include "JNIHelp.h"
+#include "android_runtime/AndroidRuntime.h"
+
+namespace android
+{
+
+static void
+android_os_UEventObserver_native_setup(JNIEnv *env, jclass clazz)
+{
+    if (!uevent_init()) {
+        jniThrowException(env, "java/lang/RuntimeException",
+                          "Unable to open socket for UEventObserver");
+    }
+}
+
+static int
+android_os_UEventObserver_next_event(JNIEnv *env, jclass clazz, jbyteArray jbuffer)
+{
+    int buf_sz = env->GetArrayLength(jbuffer);
+    char *buffer = (char*)env->GetByteArrayElements(jbuffer, NULL);
+
+    int length = uevent_next_event(buffer, buf_sz - 1);
+
+    env->ReleaseByteArrayElements(jbuffer, (jbyte*)buffer, 0);
+
+    return length;
+}
+
+static JNINativeMethod gMethods[] = {
+    {"native_setup", "()V",   (void *)android_os_UEventObserver_native_setup},
+    {"next_event",   "([B)I", (void *)android_os_UEventObserver_next_event},
+};
+
+
+int register_android_os_UEventObserver(JNIEnv *env)
+{
+    jclass clazz;
+
+    clazz = env->FindClass("android/os/UEventObserver");
+    if (clazz == NULL) {
+        LOGE("Can't find android/os/UEventObserver");
+        return -1;
+    }
+
+    return AndroidRuntime::registerNativeMethods(env,
+                "android/os/UEventObserver", gMethods, NELEM(gMethods));
+}
+
+}   // namespace android
diff --git a/core/jni/android_pim_EventRecurrence.cpp b/core/jni/android_pim_EventRecurrence.cpp
new file mode 100644
index 0000000..cbe99bc
--- /dev/null
+++ b/core/jni/android_pim_EventRecurrence.cpp
@@ -0,0 +1,199 @@
+/* //device/libs/android_runtime/android_pim_EventRecurrence.cpp

+**

+** Copyright 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.

+*/

+

+#include <pim/EventRecurrence.h>

+#include "jni.h"

+#include "nativehelper/JNIHelp.h"

+#include <utils/String8.h>

+

+namespace android {

+

+struct cached_array_fields_t

+{

+    jfieldID array;

+    jfieldID count;

+};

+

+static jclass clazz;

+static jfieldID freq_field;

+static jfieldID until_field;

+static jfieldID count_field;

+static jfieldID interval_field;

+static jfieldID wkst_field;

+static cached_array_fields_t bysecond_fields;

+static cached_array_fields_t byminute_fields;

+static cached_array_fields_t byhour_fields;

+static cached_array_fields_t byday_fields;

+static cached_array_fields_t bydayNum_fields;

+static cached_array_fields_t bymonthday_fields;

+static cached_array_fields_t byyearday_fields;

+static cached_array_fields_t byweekno_fields;

+static cached_array_fields_t bymonth_fields;

+static cached_array_fields_t bysetpos_fields;

+

+static status_t

+set_array(JNIEnv* env, int inCount, int* inArray,

+            jobject This, const cached_array_fields_t& fields)

+{

+    if (inCount > 0) {

+        jintArray array = (jintArray) env->GetObjectField(This, fields.array);

+        if (array == NULL || env->GetArrayLength(array) < inCount) {

+            // +4 because it's cheap to allocate a little extra here, and

+            // that reduces the chance that we'll come back here again

+            array = env->NewIntArray(inCount+4);

+            env->SetObjectField(This, fields.array, array);

+        }

+        if (array == NULL) {

+            return NO_MEMORY;

+        }

+        env->SetIntArrayRegion(array, 0, inCount, inArray);

+

+    }

+    env->SetIntField(This, fields.count, inCount);

+    return NO_ERROR;

+}

+

+/*

+ * In class android.pim.EventRecurrence

+ *  public native int parse(String str);

+ */

+#define SET_ARRAY_AND_CHECK(name) \

+    /*printf("setting " #name " to %d elements\n", er.name##Count);*/ \

+    if (set_array(env, er.name##Count, er.name, This, name##_fields) \

+            != NO_ERROR) { \

+        jniThrowException(env, "java/lang/RuntimeException", \

+                "EventRecurrence.parse error setting field " #name " or " \

+                #name "Count."); \

+        return ; \

+    }

+static void

+EventRecurrence_parse(JNIEnv* env, jobject This, jstring jstr)

+{

+    if (jstr == NULL) {

+        jniThrowException(env, "java/lang/NullPointerException", 

+                "EventRecurrence.parse str parameter null"); 

+        return ;

+    }

+    jboolean isCopy;

+    const jchar* jchars = env->GetStringChars(jstr, &isCopy);

+    jsize len = env->GetStringLength(jstr);

+    String16 str(jchars, len);

+    env->ReleaseStringChars(jstr, jchars);

+

+    //printf("the string was '%s'\n", String8(str).string());

+

+    EventRecurrence er;

+    if (NO_ERROR != er.parse(str)) {

+        String8 msg("Error parsing recurrence: '");

+        msg.append(String8(str));

+        msg.append("'");

+

+        jniThrowException(env,

+                "android/pim/EventRecurrence$InvalidFormatException",

+                msg.string());

+        return ;

+    }

+

+    jstring untilStr;

+    if (er.until.size() > 0) {

+        untilStr = env->NewString(er.until.string(), er.until.size());

+        if (untilStr == NULL) {

+            jniThrowException(env, "java/lang/RuntimeException", 

+                    "EventRecurrence.parse error setting field 'until'"); 

+            return ;

+        }

+    } else {

+        untilStr = NULL;

+    }

+    env->SetObjectField(This, until_field, untilStr);

+

+    env->SetIntField(This, freq_field, er.freq);

+    env->SetIntField(This, count_field, er.count);

+    env->SetIntField(This, interval_field, er.interval);

+    env->SetIntField(This, wkst_field, er.wkst);

+

+    SET_ARRAY_AND_CHECK(bysecond)

+    SET_ARRAY_AND_CHECK(byminute)

+    SET_ARRAY_AND_CHECK(byhour)

+    SET_ARRAY_AND_CHECK(byday)

+    // we'll just set the bydayCount field twice, it'll be less code total

+    if (set_array(env, er.bydayCount, er.bydayNum, This, bydayNum_fields)

+            != NO_ERROR) { 

+        jniThrowException(env, "java/lang/RuntimeException",

+                "EventRecurrence.parse error setting field bydayNum or "

+                "bydayCount.");

+        return ;

+    }

+    SET_ARRAY_AND_CHECK(bymonthday)

+    SET_ARRAY_AND_CHECK(byyearday)

+    SET_ARRAY_AND_CHECK(byweekno)

+    SET_ARRAY_AND_CHECK(bymonth)

+    SET_ARRAY_AND_CHECK(bysetpos)

+}

+

+/*

+ * JNI registration.

+ */

+static JNINativeMethod METHODS[] = {

+    /* name, signature, funcPtr */

+    { "parse", "(Ljava/lang/String;)V", (void*)EventRecurrence_parse }

+};

+

+static const char*const CLASS_NAME = "android/pim/EventRecurrence";

+

+int register_android_pim_EventRecurrence(JNIEnv* env)

+{

+    clazz = env->FindClass(CLASS_NAME);

+    if (clazz == NULL) {

+        LOGE("Field lookup unable to find class '%s'\n", CLASS_NAME);

+        return -1;

+    }

+

+    freq_field = env->GetFieldID(clazz, "freq", "I");

+    count_field = env->GetFieldID(clazz, "count", "I");

+    interval_field = env->GetFieldID(clazz, "interval", "I");

+    wkst_field = env->GetFieldID(clazz, "wkst", "I");

+

+    until_field = env->GetFieldID(clazz, "until", "Ljava/lang/String;");

+

+    bysecond_fields.array = env->GetFieldID(clazz, "bysecond", "[I");

+    bysecond_fields.count = env->GetFieldID(clazz, "bysecondCount", "I");

+    byminute_fields.array = env->GetFieldID(clazz, "byminute", "[I");

+    byminute_fields.count = env->GetFieldID(clazz, "byminuteCount", "I");

+    byhour_fields.array = env->GetFieldID(clazz, "byhour", "[I");

+    byhour_fields.count = env->GetFieldID(clazz, "byhourCount", "I");

+    byday_fields.array = env->GetFieldID(clazz, "byday", "[I");

+    byday_fields.count = env->GetFieldID(clazz, "bydayCount", "I");

+    bydayNum_fields.array = env->GetFieldID(clazz, "bydayNum", "[I");

+    bydayNum_fields.count = byday_fields.count;

+    bymonthday_fields.array = env->GetFieldID(clazz, "bymonthday", "[I");

+    bymonthday_fields.count = env->GetFieldID(clazz, "bymonthdayCount", "I");

+    byyearday_fields.array = env->GetFieldID(clazz, "byyearday", "[I");

+    byyearday_fields.count = env->GetFieldID(clazz, "byyeardayCount", "I");

+    byweekno_fields.array = env->GetFieldID(clazz, "byweekno", "[I");

+    byweekno_fields.count = env->GetFieldID(clazz, "byweeknoCount", "I");

+    bymonth_fields.array = env->GetFieldID(clazz, "bymonth", "[I");

+    bymonth_fields.count = env->GetFieldID(clazz, "bymonthCount", "I");

+    bysetpos_fields.array = env->GetFieldID(clazz, "bysetpos", "[I");

+    bysetpos_fields.count = env->GetFieldID(clazz, "bysetposCount", "I");

+

+    return jniRegisterNativeMethods(env, CLASS_NAME,

+        METHODS, sizeof(METHODS)/sizeof(METHODS[0]));

+}

+

+}; // namespace android

+

diff --git a/core/jni/android_security_Md5MessageDigest.cpp b/core/jni/android_security_Md5MessageDigest.cpp
new file mode 100644
index 0000000..3533559
--- /dev/null
+++ b/core/jni/android_security_Md5MessageDigest.cpp
@@ -0,0 +1,126 @@
+/*
+ * 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.
+ */
+
+#include "jni.h"
+#include <JNIHelp.h>
+#include <stdlib.h>
+#include <stdint.h>
+
+#include <openssl/md5.h>
+
+namespace android
+{
+
+struct fields_t {
+    jfieldID    context;
+};
+static fields_t fields;
+
+static void native_init(JNIEnv *env, jobject clazz)
+{
+    MD5_CTX* context = (MD5_CTX *)malloc(sizeof(MD5_CTX));
+    MD5_Init(context);
+    
+    env->SetIntField(clazz, fields.context, (int)context);
+}
+
+static void native_reset(JNIEnv *env, jobject clazz)
+{
+    MD5_CTX *context = (MD5_CTX *)env->GetIntField(clazz, fields.context);
+    if (context != NULL) {
+        free(context);
+        env->SetIntField(clazz, fields.context, 0 );
+    }   
+}
+
+static void native_update(JNIEnv *env, jobject clazz, jbyteArray dataArray)
+{
+    jbyte * data;
+    jsize dataSize;
+    MD5_CTX *context = (MD5_CTX *)env->GetIntField(clazz, fields.context);
+    
+    if (context == NULL) {
+        native_init(env, clazz);
+        context = (MD5_CTX *)env->GetIntField(clazz, fields.context);
+    }
+    
+    data = env->GetByteArrayElements(dataArray, NULL);
+    if (data == NULL) {
+        LOGE("Unable to get byte array elements");
+        jniThrowException(env, "java/lang/IllegalArgumentException",
+                          "Invalid data array when calling MessageDigest.update()");
+        return;
+    }
+    dataSize = env->GetArrayLength(dataArray);   
+    
+    MD5_Update(context, data, dataSize);
+
+    env->ReleaseByteArrayElements(dataArray, data, 0);
+}
+    
+static jbyteArray native_digest(JNIEnv *env, jobject clazz)
+{
+    jbyteArray array;
+    jbyte md[MD5_DIGEST_LENGTH];
+    MD5_CTX *context = (MD5_CTX *)env->GetIntField(clazz, fields.context);
+    
+    MD5_Final((uint8_t*)md, context);  
+    
+    array = env->NewByteArray(MD5_DIGEST_LENGTH);
+    LOG_ASSERT(array, "Native could not create new byte[]");
+    
+    env->SetByteArrayRegion(array, 0, MD5_DIGEST_LENGTH, md);
+    
+    native_reset(env, clazz);
+        
+    return array;
+}
+
+
+/*
+ * JNI registration.
+ */
+
+static JNINativeMethod gMethods[] = 
+{
+     /* name, signature, funcPtr */
+    {"init", "()V", (void *)native_init},
+    {"update", "([B)V", (void *)native_update},
+    {"digest", "()[B", (void *)native_digest},
+    {"reset", "()V", (void *)native_reset},
+};
+
+int register_android_security_Md5MessageDigest(JNIEnv *env)
+{
+    jclass clazz;
+
+    clazz = env->FindClass("android/security/Md5MessageDigest");
+    if (clazz == NULL) {
+        LOGE("Can't find android/security/Md5MessageDigest");
+        return -1;
+    }
+    
+    fields.context = env->GetFieldID(clazz, "mNativeMd5Context", "I");
+    if (fields.context == NULL) {
+        LOGE("Can't find Md5MessageDigest.mNativeMd5Context");
+        return -1;
+    }
+
+    return jniRegisterNativeMethods(env, "android/security/Md5MessageDigest",
+        gMethods, NELEM(gMethods));
+}
+
+};
diff --git a/core/jni/android_server_BluetoothA2dpService.cpp b/core/jni/android_server_BluetoothA2dpService.cpp
new file mode 100644
index 0000000..062f893
--- /dev/null
+++ b/core/jni/android_server_BluetoothA2dpService.cpp
@@ -0,0 +1,424 @@
+/*
+** Copyright 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.
+*/
+
+#define LOG_TAG "BluetoothA2dpService.cpp"
+
+#include "android_bluetooth_common.h"
+#include "android_runtime/AndroidRuntime.h"
+#include "JNIHelp.h"
+#include "jni.h"
+#include "utils/Log.h"
+#include "utils/misc.h"
+
+#include <ctype.h>
+#include <errno.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#ifdef HAVE_BLUETOOTH
+#include <dbus/dbus.h>
+#endif
+
+namespace android {
+
+#ifdef HAVE_BLUETOOTH
+static jmethodID method_onHeadsetCreated;
+static jmethodID method_onHeadsetRemoved;
+static jmethodID method_onSinkConnected;
+static jmethodID method_onSinkDisconnected;
+static jmethodID method_onSinkPlaying;
+static jmethodID method_onSinkStopped;
+
+typedef struct {
+    JNIEnv *env;
+    DBusConnection *conn;
+    jobject me;  // for callbacks to java
+} native_data_t;
+
+static native_data_t *nat = NULL;  // global native data
+
+extern event_loop_native_data_t *event_loop_nat;  // for the event loop JNIEnv
+#endif
+
+#ifdef HAVE_BLUETOOTH
+static void onConnectSinkResult(DBusMessage *msg, void *user);
+static void onDisconnectSinkResult(DBusMessage *msg, void *user);
+#endif
+
+/* Returns true on success (even if adapter is present but disabled).
+ * Return false if dbus is down, or another serious error (out of memory)
+*/
+static bool initNative(JNIEnv* env, jobject object) {
+    LOGV(__FUNCTION__);
+#ifdef HAVE_BLUETOOTH
+    nat = (native_data_t *)calloc(1, sizeof(native_data_t));
+    if (NULL == nat) {
+        LOGE("%s: out of memory!", __FUNCTION__);
+        return false;
+    }
+    nat->env = env;
+    nat->me = env->NewGlobalRef(object);
+
+    DBusError err;
+    dbus_error_init(&err);
+    dbus_threads_init_default();
+    nat->conn = dbus_bus_get(DBUS_BUS_SYSTEM, &err);
+    if (dbus_error_is_set(&err)) {
+        LOGE("Could not get onto the system bus: %s", err.message);
+        dbus_error_free(&err);
+        return false;
+    }
+#endif  /*HAVE_BLUETOOTH*/
+    return true;
+}
+
+static void cleanupNative(JNIEnv* env, jobject object) {
+#ifdef HAVE_BLUETOOTH
+    LOGV(__FUNCTION__);
+    if (nat) {
+        dbus_connection_close(nat->conn);
+        env->DeleteGlobalRef(nat->me);
+        free(nat);
+        nat = NULL;
+    }
+#endif
+}
+static jobjectArray listHeadsetsNative(JNIEnv *env, jobject object) {
+#ifdef HAVE_BLUETOOTH
+    LOGV(__FUNCTION__);
+    if (nat) {
+        DBusMessage *reply =
+            dbus_func_args(env, nat->conn, "/org/bluez/audio",
+                           "org.bluez.audio.Manager", "ListHeadsets",
+                           DBUS_TYPE_INVALID);
+        return reply ? dbus_returns_array_of_strings(env, reply) : NULL;
+    }
+#endif
+    return NULL;
+}
+
+static jstring createHeadsetNative(JNIEnv *env, jobject object,
+                                     jstring address) {
+#ifdef HAVE_BLUETOOTH
+    LOGV(__FUNCTION__);
+    if (nat) {
+        const char *c_address = env->GetStringUTFChars(address, NULL);
+        LOGV("... address = %s\n", c_address);
+        DBusMessage *reply =
+            dbus_func_args(env, nat->conn, "/org/bluez/audio",
+                           "org.bluez.audio.Manager", "CreateHeadset",
+                           DBUS_TYPE_STRING, &c_address,
+                           DBUS_TYPE_INVALID);
+        env->ReleaseStringUTFChars(address, c_address);
+        return reply ? dbus_returns_string(env, reply) : NULL;
+    }
+#endif
+    return NULL;
+}
+
+static jstring removeHeadsetNative(JNIEnv *env, jobject object, jstring path) {
+#ifdef HAVE_BLUETOOTH
+    LOGV(__FUNCTION__);
+    if (nat) {
+        const char *c_path = env->GetStringUTFChars(path, NULL);
+        DBusMessage *reply =
+            dbus_func_args(env, nat->conn, "/org/bluez/audio",
+                           "org.bluez.audio.Manager", "RemoveHeadset",
+                           DBUS_TYPE_STRING, &c_path,
+                           DBUS_TYPE_INVALID);
+        env->ReleaseStringUTFChars(path, c_path);
+        return reply ? dbus_returns_string(env, reply) : NULL;
+    }
+#endif
+    return NULL;
+}
+
+static jstring getAddressNative(JNIEnv *env, jobject object, jstring path) {
+#ifdef HAVE_BLUETOOTH
+    LOGV(__FUNCTION__);
+    if (nat) {
+        const char *c_path = env->GetStringUTFChars(path, NULL);
+        DBusMessage *reply =
+            dbus_func_args(env, nat->conn, c_path,
+                           "org.bluez.audio.Device", "GetAddress",
+                           DBUS_TYPE_INVALID);
+        env->ReleaseStringUTFChars(path, c_path);
+        return reply ? dbus_returns_string(env, reply) : NULL;
+    }
+#endif
+    return NULL;
+}
+
+static jboolean connectSinkNative(JNIEnv *env, jobject object, jstring path) {
+#ifdef HAVE_BLUETOOTH
+    LOGV(__FUNCTION__);
+    if (nat) {
+        const char *c_path = env->GetStringUTFChars(path, NULL);
+        size_t path_sz = env->GetStringUTFLength(path) + 1;
+        char *c_path_copy = (char *)malloc(path_sz);  // callback data
+        strncpy(c_path_copy, c_path, path_sz);
+
+        bool ret =
+            dbus_func_args_async(env, nat->conn, -1,
+                           onConnectSinkResult, (void *)c_path_copy, c_path,
+                           "org.bluez.audio.Sink", "Connect",
+                           DBUS_TYPE_INVALID);
+
+        env->ReleaseStringUTFChars(path, c_path);
+        if (!ret) {
+            free(c_path_copy);
+            return JNI_FALSE;
+        }
+        return JNI_TRUE;
+    }
+#endif
+    return JNI_FALSE;
+}
+
+static jboolean disconnectSinkNative(JNIEnv *env, jobject object,
+                                     jstring path) {
+#ifdef HAVE_BLUETOOTH
+    LOGV(__FUNCTION__);
+    if (nat) {
+        const char *c_path = env->GetStringUTFChars(path, NULL);
+        size_t path_sz = env->GetStringUTFLength(path) + 1;
+        char *c_path_copy = (char *)malloc(path_sz);  // callback data
+        strncpy(c_path_copy, c_path, path_sz);
+
+        bool ret =
+            dbus_func_args_async(env, nat->conn, -1,
+                           onDisconnectSinkResult, (void *)c_path_copy, c_path,
+                           "org.bluez.audio.Sink", "Disconnect",
+                           DBUS_TYPE_INVALID);
+        env->ReleaseStringUTFChars(path, c_path);
+        if (!ret) {
+            free(c_path_copy);
+            return JNI_FALSE;
+        }
+        return JNI_TRUE;
+    }
+#endif
+    return JNI_FALSE;
+}
+
+static jboolean isSinkConnectedNative(JNIEnv *env, jobject object, jstring path) {
+#ifdef HAVE_BLUETOOTH
+    LOGV(__FUNCTION__);
+    if (nat) {
+        const char *c_path = env->GetStringUTFChars(path, NULL);
+        DBusMessage *reply =
+            dbus_func_args(env, nat->conn, c_path,
+                           "org.bluez.audio.Sink", "IsConnected",
+                           DBUS_TYPE_INVALID);
+        env->ReleaseStringUTFChars(path, c_path);
+        return reply ? dbus_returns_boolean(env, reply) : JNI_FALSE;
+    }
+#endif
+    return JNI_FALSE;
+}
+
+#ifdef HAVE_BLUETOOTH
+static void onConnectSinkResult(DBusMessage *msg, void *user) {
+    LOGV(__FUNCTION__);
+
+    char *c_path = (char *)user;
+    DBusError err;
+    dbus_error_init(&err);
+    JNIEnv *env = event_loop_nat->env;
+
+    LOGV("... path = %s", c_path);
+    if (dbus_set_error_from_message(&err, msg)) {
+        /* if (!strcmp(err.name, BLUEZ_DBUS_BASE_IFC ".Error.AuthenticationFailed")) */
+        LOGE("%s: D-Bus error: %s (%s)\n", __FUNCTION__, err.name, err.message);
+        dbus_error_free(&err);
+        env->CallVoidMethod(nat->me,
+                            method_onSinkDisconnected,
+                            env->NewStringUTF(c_path));
+        if (env->ExceptionCheck()) {
+            LOGE("VM Exception occurred in native function %s (%s:%d)",
+                 __FUNCTION__, __FILE__, __LINE__);
+        }
+    } // else Java callback is triggered by signal in a2dp_event_filter
+
+    free(c_path);
+}
+
+static void onDisconnectSinkResult(DBusMessage *msg, void *user) {
+    LOGV(__FUNCTION__);
+
+    char *c_path = (char *)user;
+    DBusError err;
+    dbus_error_init(&err);
+    JNIEnv *env = event_loop_nat->env;
+
+    LOGV("... path = %s", c_path);
+    if (dbus_set_error_from_message(&err, msg)) {
+        /* if (!strcmp(err.name, BLUEZ_DBUS_BASE_IFC ".Error.AuthenticationFailed")) */
+        LOGE("%s: D-Bus error: %s (%s)\n", __FUNCTION__, err.name, err.message);
+        if (strcmp(err.name, "org.bluez.Error.NotConnected") == 0) {
+            // we were already disconnected, so report disconnect
+            env->CallVoidMethod(nat->me,
+                                method_onSinkDisconnected,
+                                env->NewStringUTF(c_path));
+        } else {
+            // Assume it is still connected
+            env->CallVoidMethod(nat->me,
+                                method_onSinkConnected,
+                                env->NewStringUTF(c_path));
+        }
+        dbus_error_free(&err);
+        if (env->ExceptionCheck()) {
+            LOGE("VM Exception occurred in native function %s (%s:%d)",
+                 __FUNCTION__, __FILE__, __LINE__);
+        }
+    } // else Java callback is triggered by signal in a2dp_event_filter
+
+    free(c_path);
+}
+
+DBusHandlerResult a2dp_event_filter(DBusMessage *msg, JNIEnv *env) {
+    DBusError err;
+
+    if (!nat) {
+        LOGV("... skipping %s\n", __FUNCTION__);
+        LOGV("... ignored\n");
+        return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+    }
+
+    dbus_error_init(&err);
+
+    if (dbus_message_get_type(msg) != DBUS_MESSAGE_TYPE_SIGNAL) {
+        return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+    }
+
+    DBusHandlerResult result = DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+
+    if (dbus_message_is_signal(msg,
+                               "org.bluez.audio.Manager",
+                               "HeadsetCreated")) {
+        char *c_path;
+        if (dbus_message_get_args(msg, &err,
+                                  DBUS_TYPE_STRING, &c_path,
+                                  DBUS_TYPE_INVALID)) {
+            LOGV("... path = %s", c_path);
+            env->CallVoidMethod(nat->me,
+                                method_onHeadsetCreated,
+                                env->NewStringUTF(c_path));
+        } else LOG_AND_FREE_DBUS_ERROR_WITH_MSG(&err, msg);
+        result = DBUS_HANDLER_RESULT_HANDLED;
+    } else if (dbus_message_is_signal(msg,
+                                      "org.bluez.audio.Manager",
+                                      "HeadsetRemoved")) {
+        char *c_path;
+        if (dbus_message_get_args(msg, &err,
+                                  DBUS_TYPE_STRING, &c_path,
+                                  DBUS_TYPE_INVALID)) {
+            LOGV("... path = %s", c_path);
+            env->CallVoidMethod(nat->me,
+                                method_onHeadsetRemoved,
+                                env->NewStringUTF(c_path));
+        } else LOG_AND_FREE_DBUS_ERROR_WITH_MSG(&err, msg);
+        result = DBUS_HANDLER_RESULT_HANDLED;
+    } else if (dbus_message_is_signal(msg,
+                                      "org.bluez.audio.Sink",
+                                      "Connected")) {
+        const char *c_path = dbus_message_get_path(msg);
+        LOGV("... path = %s", c_path);
+        env->CallVoidMethod(nat->me,
+                            method_onSinkConnected,
+                            env->NewStringUTF(c_path));
+        result = DBUS_HANDLER_RESULT_HANDLED;
+    } else if (dbus_message_is_signal(msg,
+                                      "org.bluez.audio.Sink",
+                                      "Disconnected")) {
+        const char *c_path = dbus_message_get_path(msg);
+        LOGV("... path = %s", c_path);
+        env->CallVoidMethod(nat->me,
+                            method_onSinkDisconnected,
+                            env->NewStringUTF(c_path));
+        result = DBUS_HANDLER_RESULT_HANDLED;
+    } else if (dbus_message_is_signal(msg,
+                                      "org.bluez.audio.Sink",
+                                      "Playing")) {
+        const char *c_path = dbus_message_get_path(msg);
+        LOGV("... path = %s", c_path);
+        env->CallVoidMethod(nat->me,
+                            method_onSinkPlaying,
+                            env->NewStringUTF(c_path));
+        result = DBUS_HANDLER_RESULT_HANDLED;
+    } else if (dbus_message_is_signal(msg,
+                                      "org.bluez.audio.Sink",
+                                      "Stopped")) {
+        const char *c_path = dbus_message_get_path(msg);
+        LOGV("... path = %s", c_path);
+        env->CallVoidMethod(nat->me,
+                            method_onSinkStopped,
+                            env->NewStringUTF(c_path));
+        result = DBUS_HANDLER_RESULT_HANDLED;
+    }
+
+    if (result == DBUS_HANDLER_RESULT_NOT_YET_HANDLED) {
+        LOGV("... ignored");
+    }
+    if (env->ExceptionCheck()) {
+        LOGE("VM Exception occurred while handling %s.%s (%s) in %s,"
+             " leaving for VM",
+             dbus_message_get_interface(msg), dbus_message_get_member(msg),
+             dbus_message_get_path(msg), __FUNCTION__);
+    }
+
+    return result;
+}
+#endif
+
+
+static JNINativeMethod sMethods[] = {
+    {"initNative", "()Z", (void *)initNative},
+    {"cleanupNative", "()V", (void *)cleanupNative},
+
+    /* Bluez audio 3.36 API */
+    {"listHeadsetsNative", "()[Ljava/lang/String;", (void*)listHeadsetsNative},
+    {"createHeadsetNative", "(Ljava/lang/String;)Ljava/lang/String;", (void*)createHeadsetNative},
+    {"removeHeadsetNative", "(Ljava/lang/String;)Z", (void*)removeHeadsetNative},
+    {"getAddressNative", "(Ljava/lang/String;)Ljava/lang/String;", (void*)getAddressNative},
+    {"connectSinkNative", "(Ljava/lang/String;)Z", (void*)connectSinkNative},
+    {"disconnectSinkNative", "(Ljava/lang/String;)Z", (void*)disconnectSinkNative},
+    {"isSinkConnectedNative", "(Ljava/lang/String;)Z", (void*)isSinkConnectedNative},
+};
+
+int register_android_server_BluetoothA2dpService(JNIEnv *env) {
+    jclass clazz = env->FindClass("android/server/BluetoothA2dpService");
+    if (clazz == NULL) {
+        LOGE("Can't find android/server/BluetoothA2dpService");
+        return -1;
+    }
+
+#ifdef HAVE_BLUETOOTH
+    method_onHeadsetCreated = env->GetMethodID(clazz, "onHeadsetCreated", "(Ljava/lang/String;)V");
+    method_onHeadsetRemoved = env->GetMethodID(clazz, "onHeadsetRemoved", "(Ljava/lang/String;)V");
+    method_onSinkConnected = env->GetMethodID(clazz, "onSinkConnected", "(Ljava/lang/String;)V");
+    method_onSinkDisconnected = env->GetMethodID(clazz, "onSinkDisconnected", "(Ljava/lang/String;)V");
+    method_onSinkPlaying = env->GetMethodID(clazz, "onSinkPlaying", "(Ljava/lang/String;)V");
+    method_onSinkStopped = env->GetMethodID(clazz, "onSinkStopped", "(Ljava/lang/String;)V");
+#endif
+
+    return AndroidRuntime::registerNativeMethods(env,
+                "android/server/BluetoothA2dpService", sMethods, NELEM(sMethods));
+}
+
+} /* namespace android */
diff --git a/core/jni/android_server_BluetoothDeviceService.cpp b/core/jni/android_server_BluetoothDeviceService.cpp
new file mode 100644
index 0000000..796da15
--- /dev/null
+++ b/core/jni/android_server_BluetoothDeviceService.cpp
@@ -0,0 +1,1020 @@
+/*
+** Copyright 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.
+*/
+
+#define DBUS_CLASS_NAME BLUEZ_DBUS_BASE_IFC ".Adapter"
+#define LOG_TAG "BluetoothDeviceService.cpp"
+
+#include "android_bluetooth_common.h"
+#include "android_runtime/AndroidRuntime.h"
+#include "JNIHelp.h"
+#include "jni.h"
+#include "utils/Log.h"
+#include "utils/misc.h"
+
+#include <ctype.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <unistd.h>
+
+#include <sys/socket.h>
+#include <sys/ioctl.h>
+#include <fcntl.h>
+
+#ifdef HAVE_BLUETOOTH
+#include <dbus/dbus.h>
+#include <bluedroid/bluetooth.h>
+#endif
+
+#include <cutils/properties.h>
+
+namespace android {
+
+#ifdef HAVE_BLUETOOTH
+// We initialize these variables when we load class
+// android.server.BluetoothDeviceService
+static jfieldID field_mNativeData;
+
+typedef struct {
+    JNIEnv *env;
+    DBusConnection *conn;
+    const char *adapter;  // dbus object name of the local adapter
+} native_data_t;
+
+void onCreateBondingResult(DBusMessage *msg, void *user);
+void onGetRemoteServiceChannelResult(DBusMessage *msg, void *user);
+
+/** Get native data stored in the opaque (Java code maintained) pointer mNativeData
+ *  Perform quick sanity check, if there are any problems return NULL
+ */
+static inline native_data_t * get_native_data(JNIEnv *env, jobject object) {
+    native_data_t *nat =
+            (native_data_t *)(env->GetIntField(object, field_mNativeData));
+    if (nat == NULL || nat->conn == NULL) {
+        LOGE("Uninitialized native data\n");
+        return NULL;
+    }
+    return nat;
+}
+#endif
+
+static void classInitNative(JNIEnv* env, jclass clazz) {
+    LOGV(__FUNCTION__);
+#ifdef HAVE_BLUETOOTH
+    field_mNativeData = get_field(env, clazz, "mNativeData", "I");
+#endif
+}
+
+/* Returns true on success (even if adapter is present but disabled).
+ * Return false if dbus is down, or another serious error (out of memory)
+*/
+static bool initializeNativeDataNative(JNIEnv* env, jobject object) {
+    LOGV(__FUNCTION__);
+#ifdef HAVE_BLUETOOTH
+    native_data_t *nat = (native_data_t *)calloc(1, sizeof(native_data_t));
+    if (NULL == nat) {
+        LOGE("%s: out of memory!", __FUNCTION__);
+        return false;
+    }
+    nat->env = env;
+
+    env->SetIntField(object, field_mNativeData, (jint)nat);
+    DBusError err;
+    dbus_error_init(&err);
+    dbus_threads_init_default();
+    nat->conn = dbus_bus_get(DBUS_BUS_SYSTEM, &err);
+    if (dbus_error_is_set(&err)) {
+        LOGE("Could not get onto the system bus: %s", err.message);
+        dbus_error_free(&err);
+        return false;
+    }
+
+    nat->adapter = BLUEZ_ADAPTER_OBJECT_NAME;
+#endif  /*HAVE_BLUETOOTH*/
+    return true;
+}
+
+static void cleanupNativeDataNative(JNIEnv* env, jobject object) {
+    LOGV(__FUNCTION__);
+#ifdef HAVE_BLUETOOTH
+    native_data_t *nat =
+        (native_data_t *)env->GetIntField(object, field_mNativeData);
+    if (nat) {
+        free(nat);
+        nat = NULL;
+    }
+#endif
+}
+
+static jstring getNameNative(JNIEnv *env, jobject object){
+    LOGV(__FUNCTION__);
+#ifdef HAVE_BLUETOOTH
+    native_data_t *nat = get_native_data(env, object);
+    if (nat) {
+        DBusMessage *reply = dbus_func_args(env, nat->conn, nat->adapter,
+                                            DBUS_CLASS_NAME, "GetName",
+                                            DBUS_TYPE_INVALID);
+        return reply ? dbus_returns_string(env, reply) : NULL;
+    }
+#endif
+    return NULL;
+}
+
+static jstring getAdapterPathNative(JNIEnv *env, jobject object) {
+    LOGV(__FUNCTION__);
+#ifdef HAVE_BLUETOOTH
+    native_data_t *nat = get_native_data(env, object);
+    if (nat) {
+        return (env->NewStringUTF(nat->adapter));
+    }
+#endif
+    return NULL;
+}
+
+
+static jboolean startDiscoveryNative(JNIEnv *env, jobject object) {
+    LOGV(__FUNCTION__);
+#ifdef HAVE_BLUETOOTH
+    DBusMessage *msg = NULL;
+    DBusMessage *reply = NULL;
+    DBusError err;
+    const char *name;
+    jboolean ret = JNI_FALSE;
+
+    native_data_t *nat = get_native_data(env, object);
+    if (nat == NULL) {
+        goto done;
+    }
+
+    dbus_error_init(&err);
+
+    /* Compose the command */
+    msg = dbus_message_new_method_call(BLUEZ_DBUS_BASE_IFC, nat->adapter,
+                                       DBUS_CLASS_NAME, "DiscoverDevices");
+
+    if (msg == NULL) {
+        LOGE("%s: Could not allocate D-Bus message object!", __FUNCTION__);
+        goto done;
+    }
+
+    /* Send the command. */
+    reply = dbus_connection_send_with_reply_and_block(nat->conn, msg, -1, &err);
+    if (dbus_error_is_set(&err)) {
+        /* We treat the in-progress error code as success. */
+        if(strcmp(err.message, BLUEZ_DBUS_BASE_IFC ".Error.InProgress") == 0) {
+            LOGW("%s: D-Bus error: %s, treating as startDiscoveryNative success\n",
+                 __FUNCTION__, err.message);
+            ret = JNI_TRUE;
+            goto done;
+        } else {
+            LOGE("%s: D-Bus error: %s (%s)\n", __FUNCTION__, err.name, err.message);
+            ret = JNI_FALSE;
+            goto done;
+        }
+    }
+
+    ret = JNI_TRUE;
+done:
+    if (reply) dbus_message_unref(reply);
+    if (msg) dbus_message_unref(msg);
+    return ret;
+#else
+    return JNI_FALSE;
+#endif
+}
+
+static void cancelDiscoveryNative(JNIEnv *env, jobject object) {
+    LOGV(__FUNCTION__);
+#ifdef HAVE_BLUETOOTH
+    DBusMessage *msg = NULL;
+    DBusMessage *reply = NULL;
+    DBusError err;
+    const char *name;
+    jstring ret;
+    native_data_t *nat;
+
+    dbus_error_init(&err);
+
+    nat = get_native_data(env, object);
+    if (nat == NULL) {
+        goto done;
+    }
+
+    /* Compose the command */
+    msg = dbus_message_new_method_call(BLUEZ_DBUS_BASE_IFC, nat->adapter,
+                                       DBUS_CLASS_NAME, "CancelDiscovery");
+
+    if (msg == NULL) {
+        LOGE("%s: Could not allocate D-Bus message object!", __FUNCTION__);
+        goto done;
+    }
+
+    /* Send the command. */
+    reply = dbus_connection_send_with_reply_and_block(nat->conn, msg, -1, &err);
+    if (dbus_error_is_set(&err)) {
+        if(strcmp(err.name, BLUEZ_DBUS_BASE_IFC ".Error.NotAuthorized") == 0) {
+            // hcid sends this if there is no active discovery to cancel
+            LOGV("%s: There was no active discovery to cancel", __FUNCTION__);
+            dbus_error_free(&err);
+        } else {
+            LOG_AND_FREE_DBUS_ERROR_WITH_MSG(&err, msg);
+        }
+    }
+
+done:
+    if (msg) dbus_message_unref(msg);
+    if (reply) dbus_message_unref(reply);
+#endif
+}
+
+static jboolean startPeriodicDiscoveryNative(JNIEnv *env, jobject object) {
+    LOGV(__FUNCTION__);
+#ifdef HAVE_BLUETOOTH
+    DBusMessage *msg = NULL;
+    DBusMessage *reply = NULL;
+    DBusError err;
+    jboolean ret = JNI_FALSE;
+
+    native_data_t *nat = get_native_data(env, object);
+    if (nat == NULL) {
+        goto done;
+    }
+
+    dbus_error_init(&err);
+    msg = dbus_message_new_method_call(BLUEZ_DBUS_BASE_IFC, nat->adapter,
+            DBUS_CLASS_NAME, "StartPeriodicDiscovery");
+    if (msg == NULL) {
+        LOGE("%s: Could not allocate DBUS message object\n", __FUNCTION__);
+        goto done;
+    }
+
+    /* Send the command. */
+    reply = dbus_connection_send_with_reply_and_block(nat->conn, msg, -1, &err);
+    if (dbus_error_is_set(&err)) {
+        /* We treat the in-progress error code as success. */
+        if(strcmp(err.name, BLUEZ_DBUS_BASE_IFC ".Error.InProgress") == 0) {
+            LOGW("%s: D-Bus error: %s (%s), treating as "
+                 "startPeriodicDiscoveryNative success\n",
+                 __FUNCTION__, err.name, err.message);
+        } else {
+            LOGE("%s: D-Bus error: %s (%s)\n", __FUNCTION__, err.name, err.message);
+            ret = JNI_FALSE;
+            goto done;
+        }
+    }
+
+    ret = JNI_TRUE;
+done:
+    if (reply) dbus_message_unref(reply);
+    if (msg) dbus_message_unref(msg);
+    return ret;
+#else
+    return JNI_FALSE;
+#endif
+}
+
+static jboolean stopPeriodicDiscoveryNative(JNIEnv *env, jobject object) {
+    LOGV(__FUNCTION__);
+#ifdef HAVE_BLUETOOTH
+    DBusMessage *msg = NULL;
+    DBusMessage *reply = NULL;
+    DBusError err;
+    const char *name;
+    jboolean ret = JNI_FALSE;
+
+    native_data_t *nat = get_native_data(env, object);
+    if (nat == NULL) {
+        goto done;
+    }
+
+    dbus_error_init(&err);
+    msg = dbus_message_new_method_call(BLUEZ_DBUS_BASE_IFC, nat->adapter,
+            DBUS_CLASS_NAME, "StopPeriodicDiscovery");
+    if (msg == NULL) {
+        LOGE("%s: Could not allocate DBUS message object\n", __FUNCTION__);
+        goto done;
+    }
+
+    /* Send the command. */
+    reply = dbus_connection_send_with_reply_and_block(nat->conn, msg, -1, &err);
+    if (dbus_error_is_set(&err)) {
+        /* We treat the in-progress error code as success. */
+        if(strcmp(err.name, BLUEZ_DBUS_BASE_IFC ".Error.InProgress") == 0) {
+            LOGW("%s: D-Bus error: %s (%s), treating as "
+                 "stopPeriodicDiscoveryNative success\n",
+                 __FUNCTION__, err.name, err.message);
+        } else {
+            LOGE("%s: D-Bus error: %s (%s)\n", __FUNCTION__, err.name, err.message);
+            ret = JNI_FALSE;
+            goto done;
+        }
+    }
+
+    ret = JNI_TRUE;
+done:
+    if (reply) dbus_message_unref(reply);
+    if (msg) dbus_message_unref(msg);
+    return ret;
+#else
+    return JNI_FALSE;
+#endif
+}
+
+static jboolean isPeriodicDiscoveryNative(JNIEnv *env, jobject object) {
+#ifdef HAVE_BLUETOOTH
+    LOGV(__FUNCTION__);
+    native_data_t *nat = get_native_data(env, object);
+    if (nat) {
+        DBusMessage *reply =
+            dbus_func_args(env, nat->conn, nat->adapter,
+                           DBUS_CLASS_NAME, "IsPeriodicDiscovery",
+                           DBUS_TYPE_INVALID);
+        return reply ? dbus_returns_boolean(env, reply) : JNI_FALSE;
+    }
+#endif
+    return JNI_FALSE;
+}
+
+static jboolean setDiscoverableTimeoutNative(JNIEnv *env, jobject object, jint timeout_s) {
+#ifdef HAVE_BLUETOOTH
+    LOGV(__FUNCTION__);
+
+    if (timeout_s < 0) {
+        return JNI_FALSE;
+    }
+
+    native_data_t *nat = get_native_data(env, object);
+    if (nat) {
+        DBusMessage *reply =
+            dbus_func_args(env, nat->conn, nat->adapter,
+                           DBUS_CLASS_NAME, "SetDiscoverableTimeout",
+                           DBUS_TYPE_UINT32, &timeout_s,
+                           DBUS_TYPE_INVALID);
+        if (reply != NULL) {
+            dbus_message_unref(reply);
+            return JNI_TRUE;
+        }
+    }
+#endif
+    return JNI_FALSE;
+}
+
+static jint getDiscoverableTimeoutNative(JNIEnv *env, jobject object) {
+#ifdef HAVE_BLUETOOTH
+    LOGV(__FUNCTION__);
+    native_data_t *nat = get_native_data(env, object);
+    if (nat) {
+        DBusMessage *reply =
+            dbus_func_args(env, nat->conn, nat->adapter,
+                           DBUS_CLASS_NAME, "GetDiscoverableTimeout",
+                           DBUS_TYPE_INVALID);
+        return reply ? dbus_returns_uint32(env, reply) : -1;
+    }
+#endif
+    return -1;
+}
+
+static jboolean isConnectedNative(JNIEnv *env, jobject object, jstring address) {
+#ifdef HAVE_BLUETOOTH
+    LOGV(__FUNCTION__);
+    native_data_t *nat = get_native_data(env, object);
+    if (nat) {
+        const char *c_address = env->GetStringUTFChars(address, NULL);
+        DBusMessage *reply =
+            dbus_func_args(env, nat->conn, nat->adapter,
+                           DBUS_CLASS_NAME, "IsConnected",
+                           DBUS_TYPE_STRING, &c_address,
+                           DBUS_TYPE_INVALID);
+        env->ReleaseStringUTFChars(address, c_address);
+        return reply ? dbus_returns_boolean(env, reply) : JNI_FALSE;
+    }
+#endif
+    return JNI_FALSE;
+}
+
+static void disconnectRemoteDeviceNative(JNIEnv *env, jobject object, jstring address) {
+#ifdef HAVE_BLUETOOTH
+    LOGV(__FUNCTION__);
+    native_data_t *nat = get_native_data(env, object);
+    if (nat) {
+        const char *c_address = env->GetStringUTFChars(address, NULL);
+        // Set a timeout of 5 seconds.  Specifying the default timeout is
+        // not long enough, as a remote-device disconnect results in
+        // signal RemoteDisconnectRequested being sent, followed by a
+        // delay of 2 seconds, after which the actual disconnect takes
+        // place.
+        DBusMessage *reply =
+            dbus_func_args_timeout(env, nat->conn, 60000, nat->adapter,
+                                   DBUS_CLASS_NAME, "DisconnectRemoteDevice",
+                                   DBUS_TYPE_STRING, &c_address,
+                                   DBUS_TYPE_INVALID);
+        env->ReleaseStringUTFChars(address, c_address);
+        if (reply) dbus_message_unref(reply);
+    }
+#endif
+}
+
+static jstring getModeNative(JNIEnv *env, jobject object) {
+#ifdef HAVE_BLUETOOTH
+    LOGV(__FUNCTION__);
+    native_data_t *nat = get_native_data(env, object);
+    if (nat) {
+        DBusMessage *reply =
+            dbus_func_args(env, nat->conn, nat->adapter,
+                           DBUS_CLASS_NAME, "GetMode",
+                           DBUS_TYPE_INVALID);
+        return reply ? dbus_returns_string(env, reply) : NULL;
+    }
+#endif
+    return NULL;
+}
+
+static jboolean setModeNative(JNIEnv *env, jobject object, jstring mode) {
+#ifdef HAVE_BLUETOOTH
+    LOGV(__FUNCTION__);
+    native_data_t *nat = get_native_data(env, object);
+    if (nat) {
+        const char *c_mode = env->GetStringUTFChars(mode, NULL);
+        DBusMessage *reply =
+            dbus_func_args(env, nat->conn, nat->adapter,
+                           DBUS_CLASS_NAME, "SetMode",
+                           DBUS_TYPE_STRING, &c_mode,
+                           DBUS_TYPE_INVALID);
+        env->ReleaseStringUTFChars(mode, c_mode);
+        if (reply) {
+            dbus_message_unref(reply);
+            return JNI_TRUE;
+        }
+        return JNI_FALSE;
+    }
+#endif
+    return JNI_FALSE;
+}
+
+static jboolean createBondingNative(JNIEnv *env, jobject object,
+                                    jstring address, jint timeout_ms) {
+    LOGV(__FUNCTION__);
+#ifdef HAVE_BLUETOOTH
+    native_data_t *nat = get_native_data(env, object);
+    if (nat) {
+        const char *c_address = env->GetStringUTFChars(address, NULL);
+        LOGV("... address = %s", c_address);
+        char *context_address = (char *)calloc(BTADDR_SIZE, sizeof(char));
+        strlcpy(context_address, c_address, BTADDR_SIZE);  // for callback
+        bool ret = dbus_func_args_async(env, nat->conn, (int)timeout_ms,
+                                        onCreateBondingResult, // callback
+                                        context_address, // user data
+                                        nat->adapter,
+                                        DBUS_CLASS_NAME, "CreateBonding",
+                                        DBUS_TYPE_STRING, &c_address,
+                                        DBUS_TYPE_INVALID);
+        env->ReleaseStringUTFChars(address, c_address);
+        return ret ? JNI_TRUE : JNI_FALSE;
+
+    }
+#endif
+    return JNI_FALSE;
+}
+
+static jboolean cancelBondingProcessNative(JNIEnv *env, jobject object,
+                                       jstring address) {
+    LOGV(__FUNCTION__);
+#ifdef HAVE_BLUETOOTH
+    native_data_t *nat = get_native_data(env, object);
+    if (nat) {
+        const char *c_address = env->GetStringUTFChars(address, NULL);
+        LOGV("... address = %s", c_address);
+        DBusMessage *reply =
+            dbus_func_args_timeout(env, nat->conn, -1, nat->adapter,
+                                   DBUS_CLASS_NAME, "CancelBondingProcess",
+                                   DBUS_TYPE_STRING, &c_address,
+                                   DBUS_TYPE_INVALID);
+        env->ReleaseStringUTFChars(address, c_address);
+        if (reply) {
+            dbus_message_unref(reply);
+        }
+        return JNI_TRUE;
+    }
+#endif
+    return JNI_FALSE;
+}
+
+static jboolean removeBondingNative(JNIEnv *env, jobject object, jstring address) {
+    LOGV(__FUNCTION__);
+    jboolean result = JNI_FALSE;
+#ifdef HAVE_BLUETOOTH
+    native_data_t *nat = get_native_data(env, object);
+    if (nat) {
+        const char *c_address = env->GetStringUTFChars(address, NULL);
+        LOGV("... address = %s", c_address);
+        DBusError err;
+        dbus_error_init(&err);
+        DBusMessage *reply =
+            dbus_func_args_error(env, nat->conn, &err, nat->adapter,
+                                 DBUS_CLASS_NAME, "RemoveBonding",
+                                 DBUS_TYPE_STRING, &c_address,
+                                 DBUS_TYPE_INVALID);
+        if (dbus_error_is_set(&err)) {
+            if (dbus_error_has_name(&err,
+                    BLUEZ_DBUS_BASE_IFC ".Error.DoesNotExist")) {
+                LOGW("%s: Warning: %s (%s)", __FUNCTION__, err.message,
+                     c_address);
+                result = JNI_TRUE;
+            } else {
+                LOGE("%s: D-Bus error %s (%s)", __FUNCTION__, err.name,
+                        err.message);
+            }
+        } else {
+            result = JNI_TRUE;
+        }
+
+        env->ReleaseStringUTFChars(address, c_address);
+        dbus_error_free(&err);
+        if (reply) dbus_message_unref(reply);
+    }
+#endif
+    return result;
+}
+
+static jobjectArray listBondingsNative(JNIEnv *env, jobject object) {
+#ifdef HAVE_BLUETOOTH
+    LOGV(__FUNCTION__);
+    native_data_t *nat = get_native_data(env, object);
+    if (nat) {
+        DBusMessage *reply =
+            dbus_func_args(env, nat->conn, nat->adapter,
+                           DBUS_CLASS_NAME, "ListBondings",
+                           DBUS_TYPE_INVALID);
+        // return String[]
+        return reply ? dbus_returns_array_of_strings(env, reply) : NULL;
+    }
+#endif
+    return NULL;
+}
+
+static jobjectArray listConnectionsNative(JNIEnv *env, jobject object) {
+#ifdef HAVE_BLUETOOTH
+    LOGV(__FUNCTION__);
+    native_data_t *nat = get_native_data(env, object);
+    if (nat) {
+        DBusMessage *reply =
+            dbus_func_args(env, nat->conn, nat->adapter,
+                           DBUS_CLASS_NAME, "ListConnections",
+                           DBUS_TYPE_INVALID);
+        // return String[]
+        return reply ? dbus_returns_array_of_strings(env, reply) : NULL;
+    }
+#endif
+    return NULL;
+}
+
+static jobjectArray listRemoteDevicesNative(JNIEnv *env, jobject object) {
+#ifdef HAVE_BLUETOOTH
+    LOGV(__FUNCTION__);
+    native_data_t *nat = get_native_data(env, object);
+    if (nat) {
+        DBusMessage *reply =
+            dbus_func_args(env, nat->conn, nat->adapter,
+                           DBUS_CLASS_NAME, "ListRemoteDevices",
+                           DBUS_TYPE_INVALID);
+        return reply ? dbus_returns_array_of_strings(env, reply) : NULL;
+    }
+#endif
+    return NULL;
+}
+
+static jstring common_Get(JNIEnv *env, jobject object, const char *func) {
+    LOGV("%s:%s", __FUNCTION__, func);
+#ifdef HAVE_BLUETOOTH
+    native_data_t *nat = get_native_data(env, object);
+    if (nat) {
+        DBusError err;
+        dbus_error_init(&err);
+        DBusMessage *reply =
+            dbus_func_args_error(env, nat->conn, &err, nat->adapter,
+                                 DBUS_CLASS_NAME, func,
+                                 DBUS_TYPE_INVALID);
+        if (reply) {
+            return dbus_returns_string(env, reply);
+        } else {
+            LOG_AND_FREE_DBUS_ERROR(&err);
+            return NULL;
+        }
+    }
+#endif
+    return NULL;
+}
+
+static jstring getAddressNative(JNIEnv *env, jobject obj) {
+    return common_Get(env, obj, "GetAddress");
+}
+
+static jstring getVersionNative(JNIEnv *env, jobject obj) {
+    return common_Get(env, obj, "GetVersion");
+}
+
+static jstring getRevisionNative(JNIEnv *env, jobject obj) {
+    return common_Get(env, obj, "GetRevision");
+}
+
+static jstring getManufacturerNative(JNIEnv *env, jobject obj) {
+    return common_Get(env, obj, "GetManufacturer");
+}
+
+static jstring getCompanyNative(JNIEnv *env, jobject obj) {
+    return common_Get(env, obj, "GetCompany");
+}
+
+static jboolean setNameNative(JNIEnv *env, jobject obj, jstring name) {
+#ifdef HAVE_BLUETOOTH
+    LOGV(__FUNCTION__);
+    native_data_t *nat = get_native_data(env, obj);
+    if (nat) {
+        const char *c_name = env->GetStringUTFChars(name, NULL);
+        DBusMessage *reply = dbus_func_args(env, nat->conn, nat->adapter,
+                                            DBUS_CLASS_NAME, "SetName",
+                                            DBUS_TYPE_STRING, &c_name,
+                                            DBUS_TYPE_INVALID);
+        env->ReleaseStringUTFChars(name, c_name);
+        if (reply) {
+            dbus_message_unref(reply);
+            return JNI_TRUE;
+        }
+    }
+#endif
+    return JNI_FALSE;
+}
+
+static jstring common_getRemote(JNIEnv *env, jobject object, const char *func,
+                                jstring address) {
+    LOGV("%s:%s", __FUNCTION__, func);
+#ifdef HAVE_BLUETOOTH
+    native_data_t *nat = get_native_data(env, object);
+    if (nat) {
+        const char *c_address = env->GetStringUTFChars(address, NULL);
+        DBusError err;
+        dbus_error_init(&err);
+
+        LOGV("... address = %s", c_address);
+
+        DBusMessage *reply =
+            dbus_func_args_error(env, nat->conn, &err, nat->adapter,
+                                 DBUS_CLASS_NAME, func,
+                                 DBUS_TYPE_STRING, &c_address,
+                                 DBUS_TYPE_INVALID);
+        env->ReleaseStringUTFChars(address, c_address);
+        if (reply) {
+            return dbus_returns_string(env, reply);
+        } else if (!strcmp(func, "GetRemoteName") &&
+                dbus_error_has_name(&err, "org.bluez.Error.RequestDeferred")) {
+            // This error occurs if we request name during device discovery,
+            // its fine
+            LOGV("... %s: %s", func, err.message);
+            dbus_error_free(&err);
+            return NULL;
+        } else {
+            LOG_AND_FREE_DBUS_ERROR(&err);
+            return NULL;
+        }
+    }
+#endif
+    return NULL;
+}
+
+static jstring getRemoteVersionNative(JNIEnv *env, jobject obj, jstring address) {
+    return common_getRemote(env, obj, "GetRemoteVersion", address);
+}
+
+static jstring getRemoteRevisionNative(JNIEnv *env, jobject obj, jstring address) {
+    return common_getRemote(env, obj, "GetRemoteRevision", address);
+}
+
+static jstring getRemoteManufacturerNative(JNIEnv *env, jobject obj, jstring address) {
+    return common_getRemote(env, obj, "GetRemoteManufacturer", address);
+}
+
+static jstring getRemoteCompanyNative(JNIEnv *env, jobject obj, jstring address) {
+    return common_getRemote(env, obj, "GetRemoteCompany", address);
+}
+
+static jstring getRemoteNameNative(JNIEnv *env, jobject obj, jstring address) {
+    return common_getRemote(env, obj, "GetRemoteName", address);
+}
+
+static jstring lastSeenNative(JNIEnv *env, jobject obj, jstring address) {
+    return common_getRemote(env, obj, "LastSeen", address);
+}
+
+static jstring lastUsedNative(JNIEnv *env, jobject obj, jstring address) {
+    return common_getRemote(env, obj, "LastUsed", address);
+}
+
+static jint getRemoteClassNative(JNIEnv *env, jobject object, jstring address) {
+#ifdef HAVE_BLUETOOTH
+    LOGV(__FUNCTION__);
+    native_data_t *nat = get_native_data(env, object);
+    if (nat) {
+        jint ret = 0;
+        const char *c_address = env->GetStringUTFChars(address, NULL);
+
+        LOGV("... address = %s", c_address);
+
+        DBusMessage *reply =
+            dbus_func_args(env, nat->conn, nat->adapter,
+                           DBUS_CLASS_NAME, "GetRemoteClass",
+                           DBUS_TYPE_STRING, &c_address,
+                           DBUS_TYPE_INVALID);
+        env->ReleaseStringUTFChars(address, c_address);
+        if (reply)
+        {
+            DBusError err;
+            dbus_error_init(&err);
+            if (!dbus_message_get_args(reply, &err,
+                                      DBUS_TYPE_UINT32, &ret,
+                                      DBUS_TYPE_INVALID)) {
+                LOG_AND_FREE_DBUS_ERROR_WITH_MSG(&err, reply);
+            }
+            dbus_message_unref(reply);
+        }
+
+        return ret;
+    }
+#endif
+    return 0;
+}
+
+static jbyteArray getRemoteFeaturesNative(JNIEnv *env, jobject object,
+                                          jstring address) {
+#ifdef HAVE_BLUETOOTH
+    LOGV(__FUNCTION__);
+    native_data_t *nat = get_native_data(env, object);
+    if (nat) {
+        const char *c_address = env->GetStringUTFChars(address, NULL);
+
+        LOGV("... address = %s", c_address);
+
+        DBusMessage *reply =
+            dbus_func_args(env, nat->conn, nat->adapter,
+                           DBUS_CLASS_NAME, "GetRemoteFeatures",
+                           DBUS_TYPE_STRING, &c_address,
+                           DBUS_TYPE_INVALID);
+        env->ReleaseStringUTFChars(address, c_address);
+        /* array of DBUS_TYPE_BYTE_AS_STRING */
+        return reply ? dbus_returns_array_of_bytes(env, reply) : NULL;
+    }
+#endif
+    return NULL;
+}
+
+static jintArray getRemoteServiceHandlesNative(JNIEnv *env, jobject object,
+                                               jstring address, jstring match) {
+#ifdef HAVE_BLUETOOTH
+    LOGV(__FUNCTION__);
+    native_data_t *nat = get_native_data(env, object);
+    if (nat) {
+        jintArray intArray = NULL;
+        const char *c_address = env->GetStringUTFChars(address, NULL);
+        const char *c_match = env->GetStringUTFChars(match, NULL);
+
+        LOGV("... address = %s match = %s", c_address, c_match);
+
+        DBusMessage *reply =
+            dbus_func_args(env, nat->conn, nat->adapter,
+                           DBUS_CLASS_NAME, "GetRemoteServiceHandles",
+                           DBUS_TYPE_STRING, &c_address,
+                           DBUS_TYPE_STRING, &c_match,
+                           DBUS_TYPE_INVALID);
+        env->ReleaseStringUTFChars(address, c_address);
+        env->ReleaseStringUTFChars(match, c_match);
+        if (reply)
+        {
+            DBusError err;
+            jint *list;
+            int i, len;
+
+            dbus_error_init(&err);
+            if (dbus_message_get_args (reply, &err,
+                                       DBUS_TYPE_ARRAY, DBUS_TYPE_UINT32,
+                                       &list, &len,
+                                       DBUS_TYPE_INVALID)) {
+                if (len) {
+                    intArray = env->NewIntArray(len);
+                    if (intArray)
+                        env->SetIntArrayRegion(intArray, 0, len, list);
+                }
+            } else {
+                LOG_AND_FREE_DBUS_ERROR_WITH_MSG(&err, reply);
+            }
+
+            dbus_message_unref(reply);
+        }
+        return intArray;
+    }
+#endif
+    return NULL;
+}
+
+static jbyteArray getRemoteServiceRecordNative(JNIEnv *env, jobject object,
+                                                 jstring address, jint handle) {
+#ifdef HAVE_BLUETOOTH
+    LOGV(__FUNCTION__);
+    native_data_t *nat = get_native_data(env, object);
+    if (nat) {
+        const char *c_address = env->GetStringUTFChars(address, NULL);
+
+        LOGV("... address = %s", c_address);
+
+        DBusMessage *reply =
+            dbus_func_args(env, nat->conn, nat->adapter,
+                           DBUS_CLASS_NAME, "GetRemoteServiceRecord",
+                           DBUS_TYPE_STRING, &c_address,
+                           DBUS_TYPE_UINT32, &handle,
+                           DBUS_TYPE_INVALID);
+        env->ReleaseStringUTFChars(address, c_address);
+        return reply ? dbus_returns_array_of_bytes(env, reply) : NULL;
+    }
+#endif
+    return NULL;
+}
+
+static jboolean getRemoteServiceChannelNative(JNIEnv *env, jobject object,
+                                          jstring address, jshort uuid16) {
+#ifdef HAVE_BLUETOOTH
+    LOGV(__FUNCTION__);
+    native_data_t *nat = get_native_data(env, object);
+    if (nat) {
+        const char *c_address = env->GetStringUTFChars(address, NULL);
+        char *context_address = (char *)calloc(BTADDR_SIZE, sizeof(char));
+        strlcpy(context_address, c_address, BTADDR_SIZE);
+
+        LOGV("... address = %s", c_address);
+        LOGV("... uuid16 = %#X", uuid16);
+
+        bool ret = dbus_func_args_async(env, nat->conn, 20000,  // ms
+                           onGetRemoteServiceChannelResult, context_address,
+                           nat->adapter,
+                           DBUS_CLASS_NAME, "GetRemoteServiceChannel",
+                           DBUS_TYPE_STRING, &c_address,
+                           DBUS_TYPE_UINT16, &uuid16,
+                           DBUS_TYPE_INVALID);
+        env->ReleaseStringUTFChars(address, c_address);
+        return ret ? JNI_TRUE : JNI_FALSE;
+    }
+#endif
+    return JNI_FALSE;
+}
+
+static jint enableNative(JNIEnv *env, jobject object) {
+#ifdef HAVE_BLUETOOTH
+    LOGV(__FUNCTION__);
+    return bt_enable();
+#endif
+    return -1;
+}
+
+static jint disableNative(JNIEnv *env, jobject object) {
+#ifdef HAVE_BLUETOOTH
+    LOGV(__FUNCTION__);
+    return bt_disable();
+#endif
+    return -1;
+}
+
+static jint isEnabledNative(JNIEnv *env, jobject object) {
+#ifdef HAVE_BLUETOOTH
+    LOGV(__FUNCTION__);
+    return bt_is_enabled();
+#endif
+    return -1;
+}
+
+static jboolean setPinNative(JNIEnv *env, jobject object, jstring address,
+                         jstring pin, int nativeData) {
+#ifdef HAVE_BLUETOOTH
+    LOGV(__FUNCTION__);
+    native_data_t *nat = get_native_data(env, object);
+    if (nat) {
+        DBusMessage *msg = (DBusMessage *)nativeData;
+        DBusMessage *reply = dbus_message_new_method_return(msg);
+        if (!reply) {
+            LOGE("%s: Cannot create message reply to return PIN code to "
+                 "D-Bus\n", __FUNCTION__);
+            dbus_message_unref(msg);
+            return JNI_FALSE;
+        }
+
+        const char *c_pin = env->GetStringUTFChars(pin, NULL);
+
+        dbus_message_append_args(reply, DBUS_TYPE_STRING, &c_pin,
+                                 DBUS_TYPE_INVALID);
+
+        dbus_connection_send(nat->conn, reply, NULL);
+        dbus_message_unref(msg);
+        dbus_message_unref(reply);
+        env->ReleaseStringUTFChars(pin, c_pin);
+        return JNI_TRUE;
+    }
+#endif
+    return JNI_FALSE;
+}
+
+static jboolean cancelPinNative(JNIEnv *env, jobject object, jstring address,
+                            int nativeData) {
+#ifdef HAVE_BLUETOOTH
+    LOGV(__FUNCTION__);
+    native_data_t *nat = get_native_data(env, object);
+    if (nat) {
+        DBusMessage *msg = (DBusMessage *)nativeData;
+        DBusMessage *reply = dbus_message_new_error(msg,
+                "org.bluez.Error.Canceled", "PIN Entry was canceled");
+        if (!reply) {
+            LOGE("%s: Cannot create message reply to return PIN cancel to "
+                 "D-BUS\n", __FUNCTION__);
+            dbus_message_unref(msg);
+            return JNI_FALSE;
+        }
+
+        dbus_connection_send(nat->conn, reply, NULL);
+        dbus_message_unref(msg);
+        dbus_message_unref(reply);
+        return JNI_TRUE;
+    }
+#endif
+    return JNI_FALSE;
+}
+
+static JNINativeMethod sMethods[] = {
+     /* name, signature, funcPtr */
+    {"classInitNative", "()V", (void*)classInitNative},
+    {"initializeNativeDataNative", "()V", (void *)initializeNativeDataNative},
+    {"cleanupNativeDataNative", "()V", (void *)cleanupNativeDataNative},
+    {"getAdapterPathNative", "()Ljava/lang/String;", (void*)getAdapterPathNative},
+
+    {"isEnabledNative", "()I", (void *)isEnabledNative},
+    {"enableNative", "()I", (void *)enableNative},
+    {"disableNative", "()I", (void *)disableNative},
+
+    {"getAddressNative", "()Ljava/lang/String;", (void *)getAddressNative},
+    {"getNameNative", "()Ljava/lang/String;", (void*)getNameNative},
+    {"setNameNative", "(Ljava/lang/String;)Z", (void *)setNameNative},
+    {"getVersionNative", "()Ljava/lang/String;", (void *)getVersionNative},
+    {"getRevisionNative", "()Ljava/lang/String;", (void *)getRevisionNative},
+    {"getManufacturerNative", "()Ljava/lang/String;", (void *)getManufacturerNative},
+    {"getCompanyNative", "()Ljava/lang/String;", (void *)getCompanyNative},
+
+    {"getModeNative", "()Ljava/lang/String;", (void *)getModeNative},
+    {"setModeNative", "(Ljava/lang/String;)Z", (void *)setModeNative},
+
+    {"getDiscoverableTimeoutNative", "()I", (void *)getDiscoverableTimeoutNative},
+    {"setDiscoverableTimeoutNative", "(I)Z", (void *)setDiscoverableTimeoutNative},
+
+    {"startDiscoveryNative", "(Z)Z", (void*)startDiscoveryNative},
+    {"cancelDiscoveryNative", "()Z", (void *)cancelDiscoveryNative},
+    {"startPeriodicDiscoveryNative", "()Z", (void *)startPeriodicDiscoveryNative},
+    {"stopPeriodicDiscoveryNative", "()Z", (void *)stopPeriodicDiscoveryNative},
+    {"isPeriodicDiscoveryNative", "()Z", (void *)isPeriodicDiscoveryNative},
+    {"listRemoteDevicesNative", "()[Ljava/lang/String;", (void *)listRemoteDevicesNative},
+
+    {"listConnectionsNative", "()[Ljava/lang/String;", (void *)listConnectionsNative},
+    {"isConnectedNative", "(Ljava/lang/String;)Z", (void *)isConnectedNative},
+    {"disconnectRemoteDeviceNative", "(Ljava/lang/String;)Z", (void *)disconnectRemoteDeviceNative},
+
+    {"createBondingNative", "(Ljava/lang/String;I)Z", (void *)createBondingNative},
+    {"cancelBondingProcessNative", "(Ljava/lang/String;)Z", (void *)cancelBondingProcessNative},
+    {"listBondingsNative", "()[Ljava/lang/String;", (void *)listBondingsNative},
+    {"removeBondingNative", "(Ljava/lang/String;)Z", (void *)removeBondingNative},
+
+    {"getRemoteNameNative", "(Ljava/lang/String;)Ljava/lang/String;", (void *)getRemoteNameNative},
+    {"getRemoteVersionNative", "(Ljava/lang/String;)Ljava/lang/String;", (void *)getRemoteVersionNative},
+    {"getRemoteRevisionNative", "(Ljava/lang/String;)Ljava/lang/String;", (void *)getRemoteRevisionNative},
+    {"getRemoteClassNative", "(Ljava/lang/String;)I", (void *)getRemoteClassNative},
+    {"getRemoteManufacturerNative", "(Ljava/lang/String;)Ljava/lang/String;", (void *)getRemoteManufacturerNative},
+    {"getRemoteCompanyNative", "(Ljava/lang/String;)Ljava/lang/String;", (void *)getRemoteCompanyNative},
+    {"getRemoteServiceChannelNative", "(Ljava/lang/String;S)Z", (void *)getRemoteServiceChannelNative},
+    {"getRemoteFeaturesNative", "(Ljava/lang/String;)[B", (void *)getRemoteFeaturesNative},
+    {"lastSeenNative", "(Ljava/lang/String;)Ljava/lang/String;", (void *)lastSeenNative},
+    {"lastUsedNative", "(Ljava/lang/String;)Ljava/lang/String;", (void *)lastUsedNative},
+    {"setPinNative", "(Ljava/lang/String;Ljava/lang/String;I)Z", (void *)setPinNative},
+    {"cancelPinNative", "(Ljava/lang/String;I)Z", (void *)cancelPinNative},
+};
+
+int register_android_server_BluetoothDeviceService(JNIEnv *env) {
+    return AndroidRuntime::registerNativeMethods(env,
+                "android/server/BluetoothDeviceService", sMethods, NELEM(sMethods));
+}
+
+} /* namespace android */
diff --git a/core/jni/android_server_BluetoothEventLoop.cpp b/core/jni/android_server_BluetoothEventLoop.cpp
new file mode 100644
index 0000000..adfd912
--- /dev/null
+++ b/core/jni/android_server_BluetoothEventLoop.cpp
@@ -0,0 +1,826 @@
+/*
+** Copyright 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.
+*/
+
+#define LOG_TAG "BluetoothEventLoop.cpp"
+
+#include "android_bluetooth_common.h"
+#include "android_runtime/AndroidRuntime.h"
+#include "JNIHelp.h"
+#include "jni.h"
+#include "utils/Log.h"
+#include "utils/misc.h"
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <unistd.h>
+
+#ifdef HAVE_BLUETOOTH
+#include <dbus/dbus.h>
+#endif
+
+namespace android {
+
+#ifdef HAVE_BLUETOOTH
+static jfieldID field_mNativeData;
+
+static jmethodID method_onModeChanged;
+static jmethodID method_onNameChanged;
+static jmethodID method_onDiscoveryStarted;
+static jmethodID method_onDiscoveryCompleted;
+static jmethodID method_onRemoteDeviceFound;
+static jmethodID method_onRemoteDeviceDisappeared;
+static jmethodID method_onRemoteClassUpdated;
+static jmethodID method_onRemoteNameUpdated;
+static jmethodID method_onRemoteNameFailed;
+static jmethodID method_onRemoteDeviceConnected;
+static jmethodID method_onRemoteDeviceDisconnectRequested;
+static jmethodID method_onRemoteDeviceDisconnected;
+static jmethodID method_onBondingCreated;
+static jmethodID method_onBondingRemoved;
+
+static jmethodID method_onCreateBondingResult;
+static jmethodID method_onGetRemoteServiceChannelResult;
+
+static jmethodID method_onPasskeyAgentRequest;
+static jmethodID method_onPasskeyAgentCancel;
+static jmethodID method_onAuthAgentAuthorize;
+static jmethodID method_onAuthAgentCancel;
+
+typedef event_loop_native_data_t native_data_t;
+
+// Only valid during waitForAndDispatchEventNative()
+native_data_t *event_loop_nat;
+
+static inline native_data_t * get_native_data(JNIEnv *env, jobject object) {
+    return (native_data_t *)(env->GetIntField(object,
+                                                 field_mNativeData));
+}
+
+#endif
+static void classInitNative(JNIEnv* env, jclass clazz) {
+    LOGV(__FUNCTION__);
+
+#ifdef HAVE_BLUETOOTH
+    method_onModeChanged = env->GetMethodID(clazz, "onModeChanged", "(Ljava/lang/String;)V");
+    method_onNameChanged = env->GetMethodID(clazz, "onNameChanged", "(Ljava/lang/String;)V");
+    method_onDiscoveryStarted = env->GetMethodID(clazz, "onDiscoveryStarted", "()V");
+    method_onDiscoveryCompleted = env->GetMethodID(clazz, "onDiscoveryCompleted", "()V");
+    method_onRemoteDeviceFound = env->GetMethodID(clazz, "onRemoteDeviceFound", "(Ljava/lang/String;IS)V");
+    method_onRemoteDeviceDisappeared = env->GetMethodID(clazz, "onRemoteDeviceDisappeared", "(Ljava/lang/String;)V");
+    method_onRemoteClassUpdated = env->GetMethodID(clazz, "onRemoteClassUpdated", "(Ljava/lang/String;I)V");
+    method_onRemoteNameUpdated = env->GetMethodID(clazz, "onRemoteNameUpdated", "(Ljava/lang/String;Ljava/lang/String;)V");
+    method_onRemoteNameFailed = env->GetMethodID(clazz, "onRemoteNameFailed", "(Ljava/lang/String;)V");
+    method_onRemoteDeviceConnected = env->GetMethodID(clazz, "onRemoteDeviceConnected", "(Ljava/lang/String;)V");
+    method_onRemoteDeviceDisconnectRequested = env->GetMethodID(clazz, "onRemoteDeviceDisconnectRequested", "(Ljava/lang/String;)V");
+    method_onRemoteDeviceDisconnected = env->GetMethodID(clazz, "onRemoteDeviceDisconnected", "(Ljava/lang/String;)V");
+    method_onBondingCreated = env->GetMethodID(clazz, "onBondingCreated", "(Ljava/lang/String;)V");
+    method_onBondingRemoved = env->GetMethodID(clazz, "onBondingRemoved", "(Ljava/lang/String;)V");
+
+    method_onCreateBondingResult = env->GetMethodID(clazz, "onCreateBondingResult", "(Ljava/lang/String;I)V");
+
+    method_onPasskeyAgentRequest = env->GetMethodID(clazz, "onPasskeyAgentRequest", "(Ljava/lang/String;I)V");
+    method_onPasskeyAgentCancel = env->GetMethodID(clazz, "onPasskeyAgentCancel", "(Ljava/lang/String;)V");
+    method_onAuthAgentAuthorize = env->GetMethodID(clazz, "onAuthAgentAuthorize", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Z");
+    method_onAuthAgentCancel = env->GetMethodID(clazz, "onAuthAgentCancel", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V");
+    method_onGetRemoteServiceChannelResult = env->GetMethodID(clazz, "onGetRemoteServiceChannelResult", "(Ljava/lang/String;I)V");
+
+    field_mNativeData = env->GetFieldID(clazz, "mNativeData", "I");
+#endif
+}
+
+static void initializeNativeDataNative(JNIEnv* env, jobject object) {
+    LOGV(__FUNCTION__);
+#ifdef HAVE_BLUETOOTH
+    native_data_t *nat = (native_data_t *)calloc(1, sizeof(native_data_t));
+    if (NULL == nat) {
+        LOGE("%s: out of memory!", __FUNCTION__);
+        return;
+    }
+    env->SetIntField(object, field_mNativeData, (jint)nat);
+
+    {
+        DBusError err;
+        dbus_error_init(&err);
+        dbus_threads_init_default();
+        nat->conn = dbus_bus_get(DBUS_BUS_SYSTEM, &err);
+        if (dbus_error_is_set(&err)) {
+            LOGE("%s: Could not get onto the system bus!", __FUNCTION__);
+            dbus_error_free(&err);
+        }
+    }
+#endif
+}
+
+static void cleanupNativeDataNative(JNIEnv* env, jobject object) {
+    LOGV(__FUNCTION__);
+#ifdef HAVE_BLUETOOTH
+    native_data_t *nat =
+            (native_data_t *)env->GetIntField(object, field_mNativeData);
+    if (nat) {
+        free(nat);
+    }
+#endif
+}
+
+#ifdef HAVE_BLUETOOTH
+static DBusHandlerResult event_filter(DBusConnection *conn, DBusMessage *msg,
+                                      void *data);
+static DBusHandlerResult agent_event_filter(DBusConnection *conn,
+                                            DBusMessage *msg,
+                                            void *data);
+
+static const DBusObjectPathVTable agent_vtable = {
+    NULL, agent_event_filter, NULL, NULL, NULL, NULL
+};
+#endif
+
+static jboolean setUpEventLoopNative(JNIEnv *env, jobject object) {
+#ifdef HAVE_BLUETOOTH
+    LOGV(__FUNCTION__);
+    dbus_threads_init_default();
+    native_data_t *nat = get_native_data(env, object);
+    DBusError err;
+    dbus_error_init(&err);
+
+    if (nat != NULL && nat->conn != NULL) {
+        // Add a filter for all incoming messages
+        if (!dbus_connection_add_filter(nat->conn, event_filter, nat, NULL)){
+            return JNI_FALSE;
+        }
+
+        // Set which messages will be processed by this dbus connection
+        dbus_bus_add_match(nat->conn,
+                "type='signal',interface='"BLUEZ_DBUS_BASE_IFC".Adapter'",
+                &err);
+        if (dbus_error_is_set(&err)) {
+            LOG_AND_FREE_DBUS_ERROR(&err);
+            return JNI_FALSE;
+        }
+        dbus_bus_add_match(nat->conn,
+                "type='signal',interface='org.bluez.audio.Manager'",
+                &err);
+        if (dbus_error_is_set(&err)) {
+            LOG_AND_FREE_DBUS_ERROR(&err);
+            return JNI_FALSE;
+        }
+        dbus_bus_add_match(nat->conn,
+                "type='signal',interface='org.bluez.audio.Device'",
+                &err);
+        if (dbus_error_is_set(&err)) {
+            LOG_AND_FREE_DBUS_ERROR(&err);
+            return JNI_FALSE;
+        }
+        dbus_bus_add_match(nat->conn,
+                "type='signal',interface='org.bluez.audio.Sink'",
+                &err);
+        if (dbus_error_is_set(&err)) {
+            LOG_AND_FREE_DBUS_ERROR(&err);
+            return JNI_FALSE;
+        }
+
+        // Add an object handler for passkey agent method calls
+        const char *path = "/android/bluetooth/Agent";
+        if (!dbus_connection_register_object_path(nat->conn, path,
+                &agent_vtable, NULL)) {
+            LOGE("%s: Can't register object path %s for agent!",
+                 __FUNCTION__, path);
+            return JNI_FALSE;
+        }
+
+        // RegisterDefaultPasskeyAgent() will fail until hcid is up, so keep
+        // trying for 10 seconds.
+        int attempt;
+        for (attempt = 0; attempt < 1000; attempt++) {
+            DBusMessage *reply = dbus_func_args_error(env, nat->conn, &err,
+                    BLUEZ_DBUS_BASE_PATH,
+                    "org.bluez.Security", "RegisterDefaultPasskeyAgent",
+                    DBUS_TYPE_STRING, &path,
+                    DBUS_TYPE_INVALID);
+            if (reply) {
+                // Success
+                dbus_message_unref(reply);
+                LOGV("Registered agent on attempt %d of 1000\n", attempt);
+                break;
+            } else if (dbus_error_has_name(&err,
+                    "org.freedesktop.DBus.Error.ServiceUnknown")) {
+                // hcid is still down, retry
+                dbus_error_free(&err);
+                usleep(10000);  // 10 ms
+            } else {
+                // Some other error we weren't expecting
+                LOG_AND_FREE_DBUS_ERROR(&err);
+                return JNI_FALSE;
+            }
+        }
+        if (attempt == 1000) {
+            LOGE("Time-out trying to call RegisterDefaultPasskeyAgent(), "
+                 "is hcid running?");
+            return JNI_FALSE;
+        }
+
+        // Now register the Auth agent
+        DBusMessage *reply = dbus_func_args_error(env, nat->conn, &err,
+                BLUEZ_DBUS_BASE_PATH,
+                "org.bluez.Security", "RegisterDefaultAuthorizationAgent",
+                DBUS_TYPE_STRING, &path,
+                DBUS_TYPE_INVALID);
+        if (!reply) {
+            LOG_AND_FREE_DBUS_ERROR(&err);
+            return JNI_FALSE;
+        }
+
+        dbus_message_unref(reply);
+        return JNI_TRUE;
+    }
+
+#endif
+    return JNI_FALSE;
+}
+
+static void tearDownEventLoopNative(JNIEnv *env, jobject object) {
+    LOGV(__FUNCTION__);
+#ifdef HAVE_BLUETOOTH
+    native_data_t *nat = get_native_data(env, object);
+    if (nat != NULL && nat->conn != NULL) {
+
+        DBusError err;
+        dbus_error_init(&err);
+
+        const char *path = "/android/bluetooth/Agent";
+        DBusMessage *reply =
+            dbus_func_args(env, nat->conn, BLUEZ_DBUS_BASE_PATH,
+                    "org.bluez.Security", "UnregisterDefaultPasskeyAgent",
+                    DBUS_TYPE_STRING, &path,
+                    DBUS_TYPE_INVALID);
+        if (reply) dbus_message_unref(reply);
+
+        reply =
+            dbus_func_args(env, nat->conn, BLUEZ_DBUS_BASE_PATH,
+                    "org.bluez.Security", "UnregisterDefaultAuthorizationAgent",
+                    DBUS_TYPE_STRING, &path,
+                    DBUS_TYPE_INVALID);
+        if (reply) dbus_message_unref(reply);
+
+        dbus_connection_unregister_object_path(nat->conn, path);
+
+        dbus_bus_remove_match(nat->conn,
+                "type='signal',interface='org.bluez.audio.Sink'",
+                &err);
+        if (dbus_error_is_set(&err)) {
+            LOG_AND_FREE_DBUS_ERROR(&err);
+        }
+        dbus_bus_remove_match(nat->conn,
+                "type='signal',interface='org.bluez.audio.Device'",
+                &err);
+        if (dbus_error_is_set(&err)) {
+            LOG_AND_FREE_DBUS_ERROR(&err);
+        }
+        dbus_bus_remove_match(nat->conn,
+                "type='signal',interface='org.bluez.audio.Manager'",
+                &err);
+        if (dbus_error_is_set(&err)) {
+            LOG_AND_FREE_DBUS_ERROR(&err);
+        }
+        dbus_bus_remove_match(nat->conn,
+                "type='signal',interface='"BLUEZ_DBUS_BASE_IFC".Adapter'",
+                &err);
+        if (dbus_error_is_set(&err)) {
+            LOG_AND_FREE_DBUS_ERROR(&err);
+        }
+
+        dbus_connection_remove_filter(nat->conn, event_filter, nat);
+    }
+#endif
+}
+
+#ifdef HAVE_BLUETOOTH
+extern DBusHandlerResult a2dp_event_filter(DBusMessage *msg, JNIEnv *env);
+
+// Called by dbus during WaitForAndDispatchEventNative()
+static DBusHandlerResult event_filter(DBusConnection *conn, DBusMessage *msg,
+                                      void *data) {
+    native_data_t *nat;
+    JNIEnv *env;
+    DBusError err;
+
+    dbus_error_init(&err);
+
+    nat = (native_data_t *)data;
+    env = nat->env;
+    if (dbus_message_get_type(msg) != DBUS_MESSAGE_TYPE_SIGNAL) {
+        LOGV("%s: not interested (not a signal).", __FUNCTION__);
+        return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+    }
+
+    LOGV("%s: Received signal %s:%s from %s", __FUNCTION__,
+         dbus_message_get_interface(msg), dbus_message_get_member(msg),
+         dbus_message_get_path(msg));
+
+    if (dbus_message_is_signal(msg,
+                               "org.bluez.Adapter",
+                               "RemoteDeviceFound")) {
+        char *c_address;
+        int n_class;
+        short n_rssi;
+        if (dbus_message_get_args(msg, &err,
+                                  DBUS_TYPE_STRING, &c_address,
+                                  DBUS_TYPE_UINT32, &n_class,
+                                  DBUS_TYPE_INT16, &n_rssi,
+                                  DBUS_TYPE_INVALID)) {
+            LOGV("... address = %s class = %#X rssi = %hd", c_address, n_class,
+                 n_rssi);
+            env->CallVoidMethod(nat->me,
+                                method_onRemoteDeviceFound,
+                                env->NewStringUTF(c_address),
+                                (jint)n_class,
+                                (jshort)n_rssi);
+        } else LOG_AND_FREE_DBUS_ERROR_WITH_MSG(&err, msg);
+        return DBUS_HANDLER_RESULT_HANDLED;
+    } else if (dbus_message_is_signal(msg,
+                                      "org.bluez.Adapter",
+                                      "DiscoveryStarted")) {
+        LOGI("DiscoveryStarted signal received");
+        env->CallVoidMethod(nat->me, method_onDiscoveryStarted);
+        return DBUS_HANDLER_RESULT_HANDLED;
+    } else if (dbus_message_is_signal(msg,
+                                    "org.bluez.Adapter",
+                                    "DiscoveryCompleted")) {
+        LOGI("DiscoveryCompleted signal received");
+        env->CallVoidMethod(nat->me, method_onDiscoveryCompleted);
+        return DBUS_HANDLER_RESULT_HANDLED;
+    } else if (dbus_message_is_signal(msg,
+                                      "org.bluez.Adapter",
+                                      "RemoteDeviceDisappeared")) {
+        char *c_address;
+        if (dbus_message_get_args(msg, &err,
+                                  DBUS_TYPE_STRING, &c_address,
+                                  DBUS_TYPE_INVALID)) {
+            LOGV("... address = %s", c_address);
+            env->CallVoidMethod(nat->me, method_onRemoteDeviceDisappeared,
+                                env->NewStringUTF(c_address));
+        } else LOG_AND_FREE_DBUS_ERROR_WITH_MSG(&err, msg);
+        return DBUS_HANDLER_RESULT_HANDLED;
+    } else if (dbus_message_is_signal(msg,
+                                      "org.bluez.Adapter",
+                                      "RemoteClassUpdated")) {
+        char *c_address;
+        int n_class;
+        if (dbus_message_get_args(msg, &err,
+                                  DBUS_TYPE_STRING, &c_address,
+                                  DBUS_TYPE_UINT32, &n_class,
+                                  DBUS_TYPE_INVALID)) {
+            LOGV("... address = %s", c_address);
+            env->CallVoidMethod(nat->me, method_onRemoteClassUpdated,
+                                env->NewStringUTF(c_address), (jint)n_class);
+        } else LOG_AND_FREE_DBUS_ERROR_WITH_MSG(&err, msg);
+        return DBUS_HANDLER_RESULT_HANDLED;
+    } else if (dbus_message_is_signal(msg,
+                                      "org.bluez.Adapter",
+                                      "RemoteNameUpdated")) {
+        char *c_address;
+        char *c_name;
+        if (dbus_message_get_args(msg, &err,
+                                  DBUS_TYPE_STRING, &c_address,
+                                  DBUS_TYPE_STRING, &c_name,
+                                  DBUS_TYPE_INVALID)) {
+            LOGV("... address = %s, name = %s", c_address, c_name);
+            env->CallVoidMethod(nat->me,
+                                method_onRemoteNameUpdated,
+                                env->NewStringUTF(c_address),
+                                env->NewStringUTF(c_name));
+        } else LOG_AND_FREE_DBUS_ERROR_WITH_MSG(&err, msg);
+        return DBUS_HANDLER_RESULT_HANDLED;
+    } else if (dbus_message_is_signal(msg,
+                                      "org.bluez.Adapter",
+                                      "RemoteNameFailed")) {
+        char *c_address;
+        if (dbus_message_get_args(msg, &err,
+                                  DBUS_TYPE_STRING, &c_address,
+                                  DBUS_TYPE_INVALID)) {
+            LOGV("... address = %s", c_address);
+            env->CallVoidMethod(nat->me,
+                                method_onRemoteNameFailed,
+                                env->NewStringUTF(c_address));
+        } else LOG_AND_FREE_DBUS_ERROR_WITH_MSG(&err, msg);
+        return DBUS_HANDLER_RESULT_HANDLED;
+    } else if (dbus_message_is_signal(msg,
+                                      "org.bluez.Adapter",
+                                      "RemoteDeviceConnected")) {
+        char *c_address;
+        if (dbus_message_get_args(msg, &err,
+                                  DBUS_TYPE_STRING, &c_address,
+                                  DBUS_TYPE_INVALID)) {
+            LOGV("... address = %s", c_address);
+            env->CallVoidMethod(nat->me,
+                                method_onRemoteDeviceConnected,
+                                env->NewStringUTF(c_address));
+        } else LOG_AND_FREE_DBUS_ERROR_WITH_MSG(&err, msg);
+        return DBUS_HANDLER_RESULT_HANDLED;
+    } else if (dbus_message_is_signal(msg,
+                                      "org.bluez.Adapter",
+                                      "RemoteDeviceDisconnectRequested")) {
+        char *c_address;
+        if (dbus_message_get_args(msg, &err,
+                                  DBUS_TYPE_STRING, &c_address,
+                                  DBUS_TYPE_INVALID)) {
+            LOGV("... address = %s", c_address);
+            env->CallVoidMethod(nat->me,
+                                method_onRemoteDeviceDisconnectRequested,
+                                env->NewStringUTF(c_address));
+        } else LOG_AND_FREE_DBUS_ERROR_WITH_MSG(&err, msg);
+        return DBUS_HANDLER_RESULT_HANDLED;
+    } else if (dbus_message_is_signal(msg,
+                                      "org.bluez.Adapter",
+                                      "RemoteDeviceDisconnected")) {
+        char *c_address;
+        if (dbus_message_get_args(msg, &err,
+                                  DBUS_TYPE_STRING, &c_address,
+                                  DBUS_TYPE_INVALID)) {
+            LOGV("... address = %s", c_address);
+            env->CallVoidMethod(nat->me,
+                                method_onRemoteDeviceDisconnected,
+                                env->NewStringUTF(c_address));
+        } else LOG_AND_FREE_DBUS_ERROR_WITH_MSG(&err, msg);
+        return DBUS_HANDLER_RESULT_HANDLED;
+    } else if (dbus_message_is_signal(msg,
+                                      "org.bluez.Adapter",
+                                      "BondingCreated")) {
+        char *c_address;
+        if (dbus_message_get_args(msg, &err,
+                                  DBUS_TYPE_STRING, &c_address,
+                                  DBUS_TYPE_INVALID)) {
+            LOGV("... address = %s", c_address);
+            env->CallVoidMethod(nat->me,
+                                method_onBondingCreated,
+                                env->NewStringUTF(c_address));
+        } else LOG_AND_FREE_DBUS_ERROR_WITH_MSG(&err, msg);
+        return DBUS_HANDLER_RESULT_HANDLED;
+    } else if (dbus_message_is_signal(msg,
+                                      "org.bluez.Adapter",
+                                      "BondingRemoved")) {
+        char *c_address;
+        if (dbus_message_get_args(msg, &err,
+                                  DBUS_TYPE_STRING, &c_address,
+                                  DBUS_TYPE_INVALID)) {
+            LOGV("... address = %s", c_address);
+            env->CallVoidMethod(nat->me,
+                                method_onBondingRemoved,
+                                env->NewStringUTF(c_address));
+        } else LOG_AND_FREE_DBUS_ERROR_WITH_MSG(&err, msg);
+        return DBUS_HANDLER_RESULT_HANDLED;
+    } else if (dbus_message_is_signal(msg,
+                                      "org.bluez.Adapter",
+                                      "ModeChanged")) {
+        char *c_mode;
+        if (dbus_message_get_args(msg, &err,
+                                  DBUS_TYPE_STRING, &c_mode,
+                                  DBUS_TYPE_INVALID)) {
+            LOGV("... mode = %s", c_mode);
+            env->CallVoidMethod(nat->me,
+                                method_onModeChanged,
+                                env->NewStringUTF(c_mode));
+        } else LOG_AND_FREE_DBUS_ERROR_WITH_MSG(&err, msg);
+        return DBUS_HANDLER_RESULT_HANDLED;
+    } else if (dbus_message_is_signal(msg,
+                                      "org.bluez.Adapter",
+                                      "NameChanged")) {
+        char *c_name;
+        if (dbus_message_get_args(msg, &err,
+                                  DBUS_TYPE_STRING, &c_name,
+                                  DBUS_TYPE_INVALID)) {
+            LOGV("... name = %s", c_name);
+            env->CallVoidMethod(nat->me,
+                                method_onNameChanged,
+                                env->NewStringUTF(c_name));
+        } else LOG_AND_FREE_DBUS_ERROR_WITH_MSG(&err, msg);
+        return DBUS_HANDLER_RESULT_HANDLED;
+    }
+
+    return a2dp_event_filter(msg, env);
+}
+
+// Called by dbus during WaitForAndDispatchEventNative()
+static DBusHandlerResult agent_event_filter(DBusConnection *conn,
+                                            DBusMessage *msg, void *data) {
+    native_data_t *nat = event_loop_nat;
+    JNIEnv *env;
+
+
+    env = nat->env;
+    if (dbus_message_get_type(msg) != DBUS_MESSAGE_TYPE_METHOD_CALL) {
+        LOGV("%s: not interested (not a method call).", __FUNCTION__);
+        return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+    }
+    LOGV("%s: Received method %s:%s", __FUNCTION__,
+         dbus_message_get_interface(msg), dbus_message_get_member(msg));
+
+    if (dbus_message_is_method_call(msg,
+            "org.bluez.PasskeyAgent", "Request")) {
+
+        const char *adapter;
+        const char *address;
+        if (!dbus_message_get_args(msg, NULL,
+                                   DBUS_TYPE_STRING, &adapter,
+                                   DBUS_TYPE_STRING, &address,
+                                   DBUS_TYPE_INVALID)) {
+            LOGE("%s: Invalid arguments for Request() method", __FUNCTION__);
+            return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+        }
+
+        LOGV("... address = %s", address);
+
+        dbus_message_ref(msg);  // increment refcount because we pass to java
+
+        env->CallVoidMethod(nat->me, method_onPasskeyAgentRequest,
+                            env->NewStringUTF(address), (int)msg);
+
+        return DBUS_HANDLER_RESULT_HANDLED;
+
+    } else if (dbus_message_is_method_call(msg,
+            "org.bluez.PasskeyAgent", "Cancel")) {
+
+        const char *adapter;
+        const char *address;
+        if (!dbus_message_get_args(msg, NULL,
+                                   DBUS_TYPE_STRING, &adapter,
+                                   DBUS_TYPE_STRING, &address,
+                                   DBUS_TYPE_INVALID)) {
+            LOGE("%s: Invalid arguments for Cancel() method", __FUNCTION__);
+            return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+        }
+
+        LOGV("... address = %s", address);
+
+        env->CallVoidMethod(nat->me, method_onPasskeyAgentCancel,
+                            env->NewStringUTF(address));
+
+        // reply
+        DBusMessage *reply = dbus_message_new_method_return(msg);
+        if (!reply) {
+            LOGE("%s: Cannot create message reply\n", __FUNCTION__);
+            return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+        }
+        dbus_connection_send(nat->conn, reply, NULL);
+        dbus_message_unref(reply);
+        return DBUS_HANDLER_RESULT_HANDLED;
+
+    } else if (dbus_message_is_method_call(msg,
+            "org.bluez.PasskeyAgent", "Release")) {
+        LOGW("We are no longer the passkey agent!");
+
+        // reply
+        DBusMessage *reply = dbus_message_new_method_return(msg);
+        if (!reply) {
+            LOGE("%s: Cannot create message reply\n", __FUNCTION__);
+            return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+        }
+        dbus_connection_send(nat->conn, reply, NULL);
+        dbus_message_unref(reply);
+        return DBUS_HANDLER_RESULT_HANDLED;
+    } else if (dbus_message_is_method_call(msg,
+            "org.bluez.AuthorizationAgent", "Authorize")) {
+        const char *adapter;
+        const char *address;
+        const char *service;
+        const char *uuid;
+        if (!dbus_message_get_args(msg, NULL,
+                                   DBUS_TYPE_STRING, &adapter,
+                                   DBUS_TYPE_STRING, &address,
+                                   DBUS_TYPE_STRING, &service,
+                                   DBUS_TYPE_STRING, &uuid,
+                                   DBUS_TYPE_INVALID)) {
+            LOGE("%s: Invalid arguments for Authorize() method", __FUNCTION__);
+            return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+        }
+
+        LOGV("... address = %s", address);
+        LOGV("... service = %s", service);
+        LOGV("... uuid = %s", uuid);
+
+        bool auth_granted = env->CallBooleanMethod(nat->me,
+                method_onAuthAgentAuthorize, env->NewStringUTF(address),
+                env->NewStringUTF(service), env->NewStringUTF(uuid));
+
+        // reply
+        if (auth_granted) {
+            DBusMessage *reply = dbus_message_new_method_return(msg);
+            if (!reply) {
+                LOGE("%s: Cannot create message reply\n", __FUNCTION__);
+                return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+            }
+            dbus_connection_send(nat->conn, reply, NULL);
+            dbus_message_unref(reply);
+        } else {
+            DBusMessage *reply = dbus_message_new_error(msg,
+                    "org.bluez.Error.Rejected", "Authorization rejected");
+            if (!reply) {
+                LOGE("%s: Cannot create message reply\n", __FUNCTION__);
+                return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+            }
+            dbus_connection_send(nat->conn, reply, NULL);
+            dbus_message_unref(reply);
+        }
+        return DBUS_HANDLER_RESULT_HANDLED;
+    } else if (dbus_message_is_method_call(msg,
+            "org.bluez.AuthorizationAgent", "Cancel")) {
+        const char *adapter;
+        const char *address;
+        const char *service;
+        const char *uuid;
+        if (!dbus_message_get_args(msg, NULL,
+                                   DBUS_TYPE_STRING, &adapter,
+                                   DBUS_TYPE_STRING, &address,
+                                   DBUS_TYPE_STRING, &service,
+                                   DBUS_TYPE_STRING, &uuid,
+                                   DBUS_TYPE_INVALID)) {
+            LOGE("%s: Invalid arguments for Cancel() method", __FUNCTION__);
+            return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+        }
+
+        LOGV("... address = %s", address);
+        LOGV("... service = %s", service);
+        LOGV("... uuid = %s", uuid);
+
+        env->CallVoidMethod(nat->me,
+                method_onAuthAgentCancel, env->NewStringUTF(address),
+                env->NewStringUTF(service), env->NewStringUTF(uuid));
+
+        // reply
+        DBusMessage *reply = dbus_message_new_method_return(msg);
+        if (!reply) {
+            LOGE("%s: Cannot create message reply\n", __FUNCTION__);
+            return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+        }
+        dbus_connection_send(nat->conn, reply, NULL);
+        dbus_message_unref(reply);
+        return DBUS_HANDLER_RESULT_HANDLED;
+
+    } else if (dbus_message_is_method_call(msg,
+            "org.bluez.AuthorizationAgent", "Release")) {
+        LOGW("We are no longer the auth agent!");
+
+        // reply
+        DBusMessage *reply = dbus_message_new_method_return(msg);
+        if (!reply) {
+            LOGE("%s: Cannot create message reply\n", __FUNCTION__);
+            return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+        }
+        dbus_connection_send(nat->conn, reply, NULL);
+        dbus_message_unref(reply);
+        return DBUS_HANDLER_RESULT_HANDLED;
+    } else {
+        LOGV("... ignored");
+    }
+
+    return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+}
+#endif
+
+static jboolean waitForAndDispatchEventNative(JNIEnv *env, jobject object,
+                                               jint timeout_ms) {
+#ifdef HAVE_BLUETOOTH
+    //LOGV("%s: %8d (pid %d tid %d)",__FUNCTION__, time(NULL), getpid(), gettid()); // too chatty
+    native_data_t *nat = get_native_data(env, object);
+    if (nat != NULL && nat->conn != NULL) {
+        jboolean ret;
+        nat->me = object;
+        nat->env = env;
+        event_loop_nat = nat;
+        ret = dbus_connection_read_write_dispatch_greedy(nat->conn,
+                                                         timeout_ms) == TRUE ?
+              JNI_TRUE : JNI_FALSE;
+        event_loop_nat = NULL;
+        nat->me = NULL;
+        nat->env = NULL;
+        return ret;
+    }
+#endif
+    return JNI_FALSE;
+}
+
+#ifdef HAVE_BLUETOOTH
+//TODO: Unify result codes in a header
+#define BOND_RESULT_ERROR -1000
+#define BOND_RESULT_SUCCESS 0
+#define BOND_RESULT_AUTH_FAILED 1
+#define BOND_RESULT_AUTH_REJECTED 2
+#define BOND_RESULT_AUTH_CANCELED 3
+#define BOND_RESULT_REMOTE_DEVICE_DOWN 4
+#define BOND_RESULT_DISCOVERY_IN_PROGRESS 5
+void onCreateBondingResult(DBusMessage *msg, void *user) {
+    LOGV(__FUNCTION__);
+
+    const char *address = (const char *)user;
+    DBusError err;
+    dbus_error_init(&err);
+    JNIEnv *env = event_loop_nat->env;
+
+    LOGV("... address = %s", address);
+
+    jint result = BOND_RESULT_SUCCESS;
+    if (dbus_set_error_from_message(&err, msg)) {
+        if (!strcmp(err.name, BLUEZ_DBUS_BASE_IFC ".Error.AuthenticationFailed")) {
+            // Pins did not match, or remote device did not respond to pin
+            // request in time
+            LOGV("... error = %s (%s)\n", err.name, err.message);
+            result = BOND_RESULT_AUTH_FAILED;
+        } else if (!strcmp(err.name, BLUEZ_DBUS_BASE_IFC ".Error.AuthenticationRejected")) {
+            // We rejected pairing, or the remote side rejected pairing. This
+            // happens if either side presses 'cancel' at the pairing dialog.
+            LOGV("... error = %s (%s)\n", err.name, err.message);
+            result = BOND_RESULT_AUTH_REJECTED;
+        } else if (!strcmp(err.name, BLUEZ_DBUS_BASE_IFC ".Error.AuthenticationCanceled")) {
+            // Not sure if this happens
+            LOGV("... error = %s (%s)\n", err.name, err.message);
+            result = BOND_RESULT_AUTH_CANCELED;
+        } else if (!strcmp(err.name, BLUEZ_DBUS_BASE_IFC ".Error.ConnectionAttemptFailed")) {
+            // Other device is not responding at all
+            LOGV("... error = %s (%s)\n", err.name, err.message);
+            result = BOND_RESULT_REMOTE_DEVICE_DOWN;
+        } else if (!strcmp(err.name, BLUEZ_DBUS_BASE_IFC ".Error.AlreadyExists")) {
+            // already bonded
+            LOGV("... error = %s (%s)\n", err.name, err.message);
+            result = BOND_RESULT_SUCCESS;
+        } else if (!strcmp(err.name, BLUEZ_DBUS_BASE_IFC ".Error.InProgress") &&
+                   !strcmp(err.message, "Bonding in progress")) {
+            LOGV("... error = %s (%s)\n", err.name, err.message);
+            goto done;
+        } else if (!strcmp(err.name, BLUEZ_DBUS_BASE_IFC ".Error.InProgress") &&
+                   !strcmp(err.message, "Discover in progress")) {
+            LOGV("... error = %s (%s)\n", err.name, err.message);
+            result = BOND_RESULT_DISCOVERY_IN_PROGRESS;
+        } else {
+            LOGE("%s: D-Bus error: %s (%s)\n", __FUNCTION__, err.name, err.message);
+            result = BOND_RESULT_ERROR;
+        }
+    }
+
+    env->CallVoidMethod(event_loop_nat->me,
+                        method_onCreateBondingResult,
+                        env->NewStringUTF(address),
+                        result);
+done:
+    dbus_error_free(&err);
+    free(user);
+}
+
+void onGetRemoteServiceChannelResult(DBusMessage *msg, void *user) {
+    LOGV(__FUNCTION__);
+
+    const char *address = (const char *) user;
+    DBusError err;
+    dbus_error_init(&err);
+    JNIEnv *env = event_loop_nat->env;
+    jint channel = -2;
+
+    LOGV("... address = %s", address);
+
+    if (dbus_set_error_from_message(&err, msg) ||
+        !dbus_message_get_args(msg, &err,
+                               DBUS_TYPE_INT32, &channel,
+                               DBUS_TYPE_INVALID)) {
+        /* if (!strcmp(err.name, BLUEZ_DBUS_BASE_IFC ".Error.AuthenticationFailed")) */
+        LOGE("%s: D-Bus error: %s (%s)\n", __FUNCTION__, err.name, err.message);
+        dbus_error_free(&err);
+    }
+
+done:
+    env->CallVoidMethod(event_loop_nat->me,
+                        method_onGetRemoteServiceChannelResult,
+                        env->NewStringUTF(address),
+                        channel);
+    free(user);
+}
+#endif
+
+static JNINativeMethod sMethods[] = {
+     /* name, signature, funcPtr */
+    {"classInitNative", "()V", (void *)classInitNative},
+    {"initializeNativeDataNative", "()V", (void *)initializeNativeDataNative},
+    {"cleanupNativeDataNative", "()V", (void *)cleanupNativeDataNative},
+    {"setUpEventLoopNative", "()Z", (void *)setUpEventLoopNative},
+    {"tearDownEventLoopNative", "()V", (void *)tearDownEventLoopNative},
+    {"waitForAndDispatchEventNative", "(I)Z", (void *)waitForAndDispatchEventNative}
+};
+
+int register_android_server_BluetoothEventLoop(JNIEnv *env) {
+    return AndroidRuntime::registerNativeMethods(env,
+            "android/server/BluetoothEventLoop", sMethods, NELEM(sMethods));
+}
+
+} /* namespace android */
diff --git a/core/jni/android_text_AndroidCharacter.cpp b/core/jni/android_text_AndroidCharacter.cpp
new file mode 100644
index 0000000..450cee2
--- /dev/null
+++ b/core/jni/android_text_AndroidCharacter.cpp
@@ -0,0 +1,128 @@
+/* //device/libs/android_runtime/android_text_AndroidCharacter.cpp
+**
+** Copyright 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.
+*/
+
+#define LOG_TAG "AndroidUnicode"
+
+#include <jni.h>
+#include <android_runtime/AndroidRuntime.h>
+#include "utils/misc.h"
+#include "utils/AndroidUnicode.h"
+#include "utils/Log.h"
+
+namespace android {
+    
+static void jniThrowException(JNIEnv* env, const char* exc, const char* msg = NULL)
+{
+    jclass excClazz = env->FindClass(exc);
+    LOG_ASSERT(excClazz, "Unable to find class %s", exc);
+
+    env->ThrowNew(excClazz, msg);
+}
+
+static void getDirectionalities(JNIEnv* env, jobject obj, jcharArray srcArray, jbyteArray destArray, int count)
+{
+    jchar* src = env->GetCharArrayElements(srcArray, NULL);
+    jbyte* dest = env->GetByteArrayElements(destArray, NULL);
+    if (src == NULL || dest == NULL) {
+        jniThrowException(env, "java/lang/NullPointerException", NULL);
+        goto DIRECTION_END;
+    }
+
+    if (env->GetArrayLength(srcArray) < count || env->GetArrayLength(destArray) < count) {
+        jniThrowException(env, "java/lang/ArrayIndexOutOfBoundsException", NULL);
+        goto DIRECTION_END;
+    }
+
+    for (int i = 0; i < count; i++) {
+        if (src[i] >= 0xD800 && src[i] <= 0xDBFF &&
+            i + 1 < count &&
+            src[i + 1] >= 0xDC00 && src[i + 1] <= 0xDFFF) {
+            int c = 0x00010000 + ((src[i] - 0xD800) << 10) +
+                                 (src[i + 1] & 0x3FF);
+            int dir = android::Unicode::getDirectionality(c);
+
+            dest[i++] = dir;
+            dest[i] = dir;
+        } else {
+            int c = src[i];
+            int dir = android::Unicode::getDirectionality(c);
+
+            dest[i] = dir;
+        }
+    }
+    
+DIRECTION_END:
+    env->ReleaseCharArrayElements(srcArray, src, JNI_ABORT);
+    env->ReleaseByteArrayElements(destArray, dest, JNI_ABORT);
+}
+
+static jboolean mirror(JNIEnv* env, jobject obj, jcharArray charArray, int start, int count)
+{
+    jchar* data = env->GetCharArrayElements(charArray, NULL);
+    bool ret = false;
+
+    if (data == NULL) {
+        jniThrowException(env, "java/lang/NullPointerException", NULL);
+        goto MIRROR_END;
+    }
+
+    if (start > start + count || env->GetArrayLength(charArray) < count) {
+        jniThrowException(env, "java/lang/ArrayIndexOutOfBoundsException", NULL);
+        goto MIRROR_END;
+    }
+
+    for (int i = start; i < start + count; i++) {
+        // XXX this thinks it knows that surrogates are never mirrored
+
+        int c1 = data[i];
+        int c2 = android::Unicode::toMirror(c1);
+
+        if (c1 != c2) {
+            data[i] = c2;
+            ret = true;
+        }
+    }
+
+MIRROR_END:
+    env->ReleaseCharArrayElements(charArray, data, JNI_ABORT);
+	return ret;
+}
+
+static jchar getMirror(JNIEnv* env, jobject obj, jchar c)
+{   
+    return android::Unicode::toMirror(c);
+}
+
+static JNINativeMethod gMethods[] = {
+	{ "getDirectionalities", "([C[BI)V",
+        (void*) getDirectionalities },
+	{ "mirror", "([CII)Z",
+        (void*) mirror },
+	{ "getMirror", "(C)C",
+        (void*) getMirror }
+};
+
+int register_android_text_AndroidCharacter(JNIEnv* env)
+{
+    jclass clazz = env->FindClass("android/text/AndroidCharacter");
+    LOG_ASSERT(clazz, "Cannot find android/text/AndroidCharacter");
+    
+    return AndroidRuntime::registerNativeMethods(env, "android/text/AndroidCharacter",
+            gMethods, NELEM(gMethods));
+}
+
+}
diff --git a/core/jni/android_text_KeyCharacterMap.cpp b/core/jni/android_text_KeyCharacterMap.cpp
new file mode 100644
index 0000000..2a23a71
--- /dev/null
+++ b/core/jni/android_text_KeyCharacterMap.cpp
@@ -0,0 +1,173 @@
+/*
+ * Copyright 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.
+*/
+
+#include <ui/KeyCharacterMap.h>
+
+#include <nativehelper/jni.h>
+#include <android_runtime/AndroidRuntime.h>
+#include <nativehelper/JNIHelp.h>
+
+namespace android {
+
+static jint
+ctor(JNIEnv *env, jobject clazz, jint id)
+{
+    return reinterpret_cast<int>(KeyCharacterMap::load(id));
+}
+
+static void
+dtor(JNIEnv *env, jobject clazz, jint ptr)
+{
+    delete reinterpret_cast<KeyCharacterMap*>(ptr);
+}
+
+static jchar
+get(JNIEnv *env, jobject clazz, jint ptr, jint keycode, jint meta)
+{
+    return reinterpret_cast<KeyCharacterMap*>(ptr)->get(keycode, meta);
+}
+
+static jchar
+getNumber(JNIEnv *env, jobject clazz, jint ptr, jint keycode)
+{
+    return reinterpret_cast<KeyCharacterMap*>(ptr)->getNumber(keycode);
+}
+
+static jchar
+getMatch(JNIEnv *env, jobject clazz, jint ptr, jint keycode, jcharArray chars, jint modifiers)
+{
+    jchar rv;
+    jchar* ch = env->GetCharArrayElements(chars, NULL);
+    jsize chsize = env->GetArrayLength(chars);
+
+    rv = reinterpret_cast<KeyCharacterMap*>(ptr)->getMatch(keycode, ch, chsize, modifiers);
+
+    env->ReleaseCharArrayElements(chars, ch, JNI_ABORT);
+    return rv;
+}
+
+static jchar
+getDisplayLabel(JNIEnv *env, jobject clazz, jint ptr, jint keycode)
+{
+    return reinterpret_cast<KeyCharacterMap*>(ptr)->getDisplayLabel(keycode);
+}
+
+static jfieldID gKeyDataMetaField;
+static jfieldID gKeyDataNumberField;
+static jfieldID gKeyDataDisplayLabelField;
+
+static jboolean
+getKeyData(JNIEnv *env, jobject clazz, jint ptr, jint keycode, jobject keydata)
+{
+    jboolean rv;
+
+    unsigned short displayLabel = env->GetCharField(keydata, gKeyDataDisplayLabelField);
+    unsigned short number = env->GetCharField(keydata, gKeyDataNumberField);
+
+    jcharArray chars = (jcharArray) env->GetObjectField(keydata, gKeyDataMetaField);
+    jchar* ch = env->GetCharArrayElements(chars, NULL);
+
+    KeyCharacterMap* kmap = reinterpret_cast<KeyCharacterMap*>(ptr);
+    rv = kmap->getKeyData(keycode, &displayLabel, &number, ch);
+
+    env->SetCharField(keydata, gKeyDataDisplayLabelField, displayLabel);
+    env->SetCharField(keydata, gKeyDataNumberField, number);
+
+    env->ReleaseCharArrayElements(chars, ch, 0);
+    return rv;
+}
+
+static jint
+getKeyboardType(JNIEnv *env, jobject clazz, jint ptr)
+{
+    return reinterpret_cast<KeyCharacterMap*>(ptr)->getKeyboardType();
+}
+
+static jlongArray
+getEvents(JNIEnv *env, jobject clazz, jint ptr, jcharArray jchars)
+{
+    KeyCharacterMap* kmap = reinterpret_cast<KeyCharacterMap*>(ptr);
+
+    uint16_t* chars = env->GetCharArrayElements(jchars, NULL);
+    size_t len = env->GetArrayLength(jchars);
+
+    Vector<int32_t> keys;
+    Vector<uint32_t> modifiers;
+    bool success = kmap->getEvents(chars, len, &keys, &modifiers);
+
+    env->ReleaseCharArrayElements(jchars, chars, JNI_ABORT);
+
+    if (success) {
+        size_t N = keys.size();
+
+        jlongArray rv = env->NewLongArray(N);
+        uint64_t* results = (uint64_t*)env->GetLongArrayElements(rv, NULL);
+
+        for (size_t i=0; i<N; i++) {
+            uint64_t v = modifiers[i];
+            v <<= 32;
+            v |= keys[i];
+            results[i] = v;
+        }
+
+        env->ReleaseLongArrayElements(rv, (jlong*)results, 0);
+        return rv;
+    } else {
+        return NULL;
+    }
+}
+
+// ============================================================================
+/*
+ * JNI registration.
+ */
+
+static JNINativeMethod g_methods[] = {
+    /* name, signature, funcPtr */
+    { "ctor_native",             "(I)I",    (void*)ctor },
+    { "dtor_native",             "(I)V",    (void*)dtor },
+    { "get_native",              "(III)C", (void*)get },
+    { "getNumber_native",        "(II)C",   (void*)getNumber },
+    { "getMatch_native",         "(II[CI)C", (void*)getMatch },
+    { "getDisplayLabel_native",  "(II)C",   (void*)getDisplayLabel },
+    { "getKeyData_native",       "(IILandroid/view/KeyCharacterMap$KeyData;)Z",
+                                            (void*)getKeyData },
+    { "getKeyboardType_native",  "(I)I",    (void*)getKeyboardType },
+    { "getEvents_native",        "(I[C)[J", (void*)getEvents }
+};
+
+int register_android_text_KeyCharacterMap(JNIEnv* env)
+{
+    jclass clazz;
+
+    clazz = env->FindClass("android/view/KeyCharacterMap$KeyData");
+    if (clazz == NULL) {
+        LOGE("Can't find android/view/KeyCharacterMap$KeyData");
+        return -1;
+    }
+
+    gKeyDataMetaField = env->GetFieldID(clazz, "meta", "[C");
+    gKeyDataNumberField = env->GetFieldID(clazz, "number", "C");
+    gKeyDataDisplayLabelField = env->GetFieldID(clazz, "displayLabel", "C");
+
+    return AndroidRuntime::registerNativeMethods(env,
+            "android/view/KeyCharacterMap", g_methods, NELEM(g_methods));
+}
+
+}; // namespace android
+
+
+
diff --git a/core/jni/android_text_format_Time.cpp b/core/jni/android_text_format_Time.cpp
new file mode 100644
index 0000000..8e41ec7
--- /dev/null
+++ b/core/jni/android_text_format_Time.cpp
@@ -0,0 +1,623 @@
+/*
+** Copyright 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.
+*/
+
+#define LOG_TAG "Log_println"
+
+#include <utils/Log.h>
+#include <utils/String8.h>
+#include <assert.h>
+
+#include "jni.h"
+#include "utils/misc.h"
+#include "android_runtime/AndroidRuntime.h"
+#include <utils/TimeUtils.h>
+#include <nativehelper/JNIHelp.h>
+#include <cutils/tztime.h>
+
+namespace android {
+
+static jfieldID g_allDayField = 0;
+static jfieldID g_secField = 0;
+static jfieldID g_minField = 0;
+static jfieldID g_hourField = 0;
+static jfieldID g_mdayField = 0;
+static jfieldID g_monField = 0;
+static jfieldID g_yearField = 0;
+static jfieldID g_wdayField = 0;
+static jfieldID g_ydayField = 0;
+static jfieldID g_isdstField = 0;
+static jfieldID g_gmtoffField = 0;
+static jfieldID g_timezoneField = 0;
+
+static jfieldID g_shortMonthsField = 0;
+static jfieldID g_longMonthsField = 0;
+static jfieldID g_shortWeekdaysField = 0;
+static jfieldID g_longWeekdaysField = 0;
+static jfieldID g_timeOnlyFormatField = 0;
+static jfieldID g_dateOnlyFormatField = 0;
+static jfieldID g_dateTimeFormatField = 0;
+static jfieldID g_amField = 0;
+static jfieldID g_pmField = 0;
+static jfieldID g_dateCommandField = 0;
+
+static inline bool java2time(JNIEnv* env, Time* t, jobject o)
+{
+    t->t.tm_sec = env->GetIntField(o, g_secField);
+    t->t.tm_min = env->GetIntField(o, g_minField);
+    t->t.tm_hour = env->GetIntField(o, g_hourField);
+    t->t.tm_mday = env->GetIntField(o, g_mdayField);
+    t->t.tm_mon = env->GetIntField(o, g_monField);
+    t->t.tm_year = (env->GetIntField(o, g_yearField))-1900;
+    t->t.tm_wday = env->GetIntField(o, g_wdayField);
+    t->t.tm_yday = env->GetIntField(o, g_ydayField);
+    t->t.tm_isdst = env->GetIntField(o, g_isdstField);
+    t->t.tm_gmtoff = env->GetLongField(o, g_gmtoffField);
+    bool allDay = env->GetIntField(o, g_allDayField);
+    if (allDay &&
+	((t->t.tm_sec !=0) || (t->t.tm_min != 0) || (t->t.tm_hour != 0))) {
+        char msg[100];
+	sprintf(msg, "allDay is true but sec, min, hour are not 0.");
+	jniThrowException(env, "java/lang/IllegalArgumentException", msg);
+	return false;
+    }
+    return true;
+}
+
+static inline void time2java(JNIEnv* env, jobject o, const Time &t)
+{
+    env->SetIntField(o, g_secField, t.t.tm_sec);
+    env->SetIntField(o, g_minField, t.t.tm_min);
+    env->SetIntField(o, g_hourField, t.t.tm_hour);
+    env->SetIntField(o, g_mdayField, t.t.tm_mday);
+    env->SetIntField(o, g_monField, t.t.tm_mon);
+    env->SetIntField(o, g_yearField, t.t.tm_year+1900);
+    env->SetIntField(o, g_wdayField, t.t.tm_wday);
+    env->SetIntField(o, g_ydayField, t.t.tm_yday);
+    env->SetIntField(o, g_isdstField, t.t.tm_isdst);
+    env->SetLongField(o, g_gmtoffField, t.t.tm_gmtoff);
+}
+
+#define ACQUIRE_TIMEZONE(This, t) \
+    jstring timezoneString_##This \
+            = (jstring) env->GetObjectField(This, g_timezoneField); \
+    t.timezone = env->GetStringUTFChars(timezoneString_##This, NULL);
+
+#define RELEASE_TIMEZONE(This, t) \
+    env->ReleaseStringUTFChars(timezoneString_##This, t.timezone);
+
+
+// ============================================================================
+
+static jlong android_text_format_Time_normalize(JNIEnv* env, jobject This,
+                                           jboolean ignoreDst)
+{
+    Time t;
+    if (!java2time(env, &t, This)) return 0L;
+    ACQUIRE_TIMEZONE(This, t)
+
+    int64_t result = t.toMillis(ignoreDst != 0);
+
+    time2java(env, This, t);
+    RELEASE_TIMEZONE(This, t)
+
+    return result;
+}
+
+static void android_text_format_Time_switchTimezone(JNIEnv* env, jobject This,
+                            jstring timezoneObject)
+{
+    Time t;
+    if (!java2time(env, &t, This)) return;
+    ACQUIRE_TIMEZONE(This, t)
+
+    const char* timezone = env->GetStringUTFChars(timezoneObject, NULL);
+
+    t.switchTimezone(timezone);
+
+    time2java(env, This, t);
+    env->ReleaseStringUTFChars(timezoneObject, timezone);
+    RELEASE_TIMEZONE(This, t)
+
+    // we do this here because there's no point in reallocating the string
+    env->SetObjectField(This, g_timezoneField, timezoneObject);
+}
+
+static jint android_text_format_Time_compare(JNIEnv* env, jobject clazz,
+                            jobject aObject, jobject bObject)
+{
+    Time a, b;
+
+    if (!java2time(env, &a, aObject)) return 0;
+    ACQUIRE_TIMEZONE(aObject, a)
+
+    if (!java2time(env, &b, bObject)) return 0;
+    ACQUIRE_TIMEZONE(bObject, b)
+
+    int result = Time::compare(a, b);
+
+    RELEASE_TIMEZONE(aObject, a)
+    RELEASE_TIMEZONE(bObject, b)
+
+    return result;
+}
+
+static jstring android_text_format_Time_format2445(JNIEnv* env, jobject This)
+{
+    Time t;
+    if (!java2time(env, &t, This)) return env->NewStringUTF("");
+    bool allDay = env->GetIntField(This, g_allDayField);
+    
+    if (!allDay) {
+        ACQUIRE_TIMEZONE(This, t)
+        bool inUtc = strcmp("UTC", t.timezone) == 0;
+        short buf[16];
+        t.format2445(buf, true);
+        RELEASE_TIMEZONE(This, t)
+        if (inUtc) {
+            // The letter 'Z' is appended to the end so allow for one
+            // more character in the buffer.
+            return env->NewString((jchar*)buf, 16);
+        } else {
+            return env->NewString((jchar*)buf, 15);
+        }
+    } else {
+        short buf[8];
+        t.format2445(buf, false);
+        return env->NewString((jchar*)buf, 8);
+    }
+}
+
+static jstring android_text_format_Time_format(JNIEnv* env, jobject This,
+                            jstring formatObject)
+{
+    Time t;
+    struct strftime_locale locale;
+    jclass timeClass = env->FindClass("android/text/format/Time");
+    jstring js_mon[12], js_month[12], js_wday[7], js_weekday[7];
+    jstring js_X_fmt, js_x_fmt, js_c_fmt, js_am, js_pm, js_date_fmt;
+    jobjectArray ja;
+
+    if (!java2time(env, &t, This)) return env->NewStringUTF("");
+
+    ja = (jobjectArray) env->GetStaticObjectField(timeClass, g_shortMonthsField);
+    for (int i = 0; i < 12; i++) {
+        js_mon[i] = (jstring) env->GetObjectArrayElement(ja, i);
+        locale.mon[i] = env->GetStringUTFChars(js_mon[i], NULL);
+    }
+
+    ja = (jobjectArray) env->GetStaticObjectField(timeClass, g_longMonthsField);
+    for (int i = 0; i < 12; i++) {
+        js_month[i] = (jstring) env->GetObjectArrayElement(ja, i);
+        locale.month[i] = env->GetStringUTFChars(js_month[i], NULL);
+    }
+
+    ja = (jobjectArray) env->GetStaticObjectField(timeClass, g_shortWeekdaysField);
+    for (int i = 0; i < 7; i++) {
+        js_wday[i] = (jstring) env->GetObjectArrayElement(ja, i);
+        locale.wday[i] = env->GetStringUTFChars(js_wday[i], NULL);
+    }
+
+    ja = (jobjectArray) env->GetStaticObjectField(timeClass, g_longWeekdaysField);
+    for (int i = 0; i < 7; i++) {
+        js_weekday[i] = (jstring) env->GetObjectArrayElement(ja, i);
+        locale.weekday[i] = env->GetStringUTFChars(js_weekday[i], NULL);
+    }
+
+    js_X_fmt = (jstring) env->GetStaticObjectField(timeClass, g_timeOnlyFormatField);
+    locale.X_fmt = env->GetStringUTFChars(js_X_fmt, NULL);
+
+    js_x_fmt = (jstring) env->GetStaticObjectField(timeClass, g_dateOnlyFormatField);
+    locale.x_fmt = env->GetStringUTFChars(js_x_fmt, NULL);
+
+    js_c_fmt = (jstring) env->GetStaticObjectField(timeClass, g_dateTimeFormatField);
+    locale.c_fmt = env->GetStringUTFChars(js_c_fmt, NULL);
+
+    js_am = (jstring) env->GetStaticObjectField(timeClass, g_amField);
+    locale.am = env->GetStringUTFChars(js_am, NULL);
+
+    js_pm = (jstring) env->GetStaticObjectField(timeClass, g_pmField);
+    locale.pm = env->GetStringUTFChars(js_pm, NULL);
+
+    js_date_fmt = (jstring) env->GetStaticObjectField(timeClass, g_dateCommandField);
+    locale.date_fmt = env->GetStringUTFChars(js_date_fmt, NULL);
+
+    ACQUIRE_TIMEZONE(This, t)
+
+    const char* format = env->GetStringUTFChars(formatObject, NULL);
+
+    String8 r = t.format(format, &locale);
+
+    env->ReleaseStringUTFChars(formatObject, format);
+    RELEASE_TIMEZONE(This, t)
+
+    for (int i = 0; i < 12; i++) {
+        env->ReleaseStringUTFChars(js_mon[i], locale.mon[i]);
+        env->ReleaseStringUTFChars(js_month[i], locale.month[i]);
+    }
+
+    for (int i = 0; i < 7; i++) {
+        env->ReleaseStringUTFChars(js_wday[i], locale.wday[i]);
+        env->ReleaseStringUTFChars(js_weekday[i], locale.weekday[i]);
+    }
+
+    env->ReleaseStringUTFChars(js_X_fmt, locale.X_fmt);
+    env->ReleaseStringUTFChars(js_x_fmt, locale.x_fmt);
+    env->ReleaseStringUTFChars(js_c_fmt, locale.c_fmt);
+    env->ReleaseStringUTFChars(js_am, locale.am);
+    env->ReleaseStringUTFChars(js_pm, locale.pm);
+    env->ReleaseStringUTFChars(js_date_fmt, locale.date_fmt);
+
+    return env->NewStringUTF(r.string());
+}
+
+
+static jstring android_text_format_Time_toString(JNIEnv* env, jobject This)
+{
+    Time t;
+    if (!java2time(env, &t, This)) return env->NewStringUTF("");;
+    ACQUIRE_TIMEZONE(This, t)
+
+    String8 r = t.toString();
+
+    RELEASE_TIMEZONE(This, t)
+
+    return env->NewStringUTF(r.string());
+}
+
+static void android_text_format_Time_setToNow(JNIEnv* env, jobject This)
+{
+    env->SetBooleanField(This, g_allDayField, JNI_FALSE);
+    Time t;
+    ACQUIRE_TIMEZONE(This, t)
+
+    t.setToNow();
+
+    time2java(env, This, t);
+    RELEASE_TIMEZONE(This, t)
+}
+
+static jlong android_text_format_Time_toMillis(JNIEnv* env, jobject This,
+                                        jboolean ignoreDst)
+{
+    Time t;
+    if (!java2time(env, &t, This)) return 0L;
+    ACQUIRE_TIMEZONE(This, t)
+
+    int64_t result = t.toMillis(ignoreDst != 0);
+
+    RELEASE_TIMEZONE(This, t)
+
+    return result;
+}
+
+static void android_text_format_Time_set(JNIEnv* env, jobject This, jlong millis)
+{
+    env->SetBooleanField(This, g_allDayField, JNI_FALSE);
+    Time t;
+    if (!java2time(env, &t, This)) return;
+    ACQUIRE_TIMEZONE(This, t)
+
+    t.set(millis);
+
+    time2java(env, This, t);
+    RELEASE_TIMEZONE(This, t)
+}
+
+
+// ============================================================================
+// Just do this here because it's not worth recreating the strings
+
+static int get_char(JNIEnv* env, const jchar *s, int spos, int mul,
+                    bool *thrown)
+{
+    jchar c = s[spos];
+    if (c >= '0' && c <= '9') {
+        return (c - '0') * mul;
+    } else {
+        char msg[100];
+        sprintf(msg, "Parse error at pos=%d", spos);
+        jniThrowException(env, "android/util/TimeFormatException", msg);
+        *thrown = true;
+        return 0;
+    }
+}
+
+static bool check_char(JNIEnv* env, const jchar *s, int spos, jchar expected)
+{
+    jchar c = s[spos];
+    if (c != expected) {
+        char msg[100];
+	sprintf(msg, "Unexpected %c at pos=%d.  Expected %c.", c, spos,
+		expected);
+	jniThrowException(env, "android/util/TimeFormatException", msg);
+	return false;
+    }
+    return true;
+}
+
+
+static jboolean android_text_format_Time_parse(JNIEnv* env, jobject This, jstring strObj)
+{
+    jsize len = env->GetStringLength(strObj);
+    const jchar *s = env->GetStringChars(strObj, NULL);
+
+    bool thrown = false;
+    int n;
+    jboolean inUtc = false;
+
+    if (len < 8) {
+        char msg[100];
+        sprintf(msg, "String too short -- expected at least 8 characters.");
+	jniThrowException(env, "android/util/TimeFormatException", msg);
+	return false;
+    }
+
+    // year
+    n = get_char(env, s, 0, 1000, &thrown);
+    n += get_char(env, s, 1, 100, &thrown);
+    n += get_char(env, s, 2, 10, &thrown);
+    n += get_char(env, s, 3, 1, &thrown);
+    if (thrown) return false;
+    env->SetIntField(This, g_yearField, n);
+
+    // month
+    n = get_char(env, s, 4, 10, &thrown);
+    n += get_char(env, s, 5, 1, &thrown);
+    n--;
+    if (thrown) return false;
+    env->SetIntField(This, g_monField, n);
+
+    // day of month
+    n = get_char(env, s, 6, 10, &thrown);
+    n += get_char(env, s, 7, 1, &thrown);
+    if (thrown) return false;
+    env->SetIntField(This, g_mdayField, n);
+
+    if (len > 8) {
+        // T
+        if (!check_char(env, s, 8, 'T')) return false;
+        env->SetBooleanField(This, g_allDayField, JNI_FALSE);
+
+        // hour
+        n = get_char(env, s, 9, 10, &thrown);
+        n += get_char(env, s, 10, 1, &thrown);
+        if (thrown) return false;
+        env->SetIntField(This, g_hourField, n);
+
+        // min
+        n = get_char(env, s, 11, 10, &thrown);
+        n += get_char(env, s, 12, 1, &thrown);
+        if (thrown) return false;
+        env->SetIntField(This, g_minField, n);
+
+        // sec
+        n = get_char(env, s, 13, 10, &thrown);
+        n += get_char(env, s, 14, 1, &thrown);
+        if (thrown) return false;
+        env->SetIntField(This, g_secField, n);
+
+        if (len > 15) {
+            // Z
+            if (!check_char(env, s, 15, 'Z')) return false;
+	    inUtc = true;
+        }
+    } else {
+        env->SetBooleanField(This, g_allDayField, JNI_TRUE);
+        env->SetIntField(This, g_hourField, 0);
+        env->SetIntField(This, g_minField, 0);
+        env->SetIntField(This, g_secField, 0);
+    }
+
+    env->SetIntField(This, g_wdayField, 0);
+    env->SetIntField(This, g_ydayField, 0);
+    env->SetIntField(This, g_isdstField, -1);
+    env->SetLongField(This, g_gmtoffField, 0);
+    
+    env->ReleaseStringChars(strObj, s);
+    return inUtc;
+}
+
+static jboolean android_text_format_Time_parse3339(JNIEnv* env, 
+                                           jobject This, 
+                                           jstring strObj)
+{
+    jsize len = env->GetStringLength(strObj);
+    const jchar *s = env->GetStringChars(strObj, NULL);
+
+    bool thrown = false;
+    int n;
+    jboolean inUtc = false;
+
+    // year
+    n = get_char(env, s, 0, 1000, &thrown);    
+    n += get_char(env, s, 1, 100, &thrown);
+    n += get_char(env, s, 2, 10, &thrown);
+    n += get_char(env, s, 3, 1, &thrown);
+    if (thrown) return false;
+    env->SetIntField(This, g_yearField, n);
+    
+    // -
+    if (!check_char(env, s, 4, '-')) return false;
+    
+    // month
+    n = get_char(env, s, 5, 10, &thrown);
+    n += get_char(env, s, 6, 1, &thrown);
+    --n;
+    if (thrown) return false;
+    env->SetIntField(This, g_monField, n);
+
+    // -
+    if (!check_char(env, s, 7, '-')) return false;
+
+    // day
+    n = get_char(env, s, 8, 10, &thrown);
+    n += get_char(env, s, 9, 1, &thrown);
+    if (thrown) return false;
+    env->SetIntField(This, g_mdayField, n);
+
+    if (len >= 17) {
+        // T
+        if (!check_char(env, s, 10, 'T')) return false;
+
+	env->SetBooleanField(This, g_allDayField, JNI_FALSE);
+        // hour
+        n = get_char(env, s, 11, 10, &thrown);
+        n += get_char(env, s, 12, 1, &thrown);
+        if (thrown) return false;
+	int hour = n;
+        // env->SetIntField(This, g_hourField, n);
+	
+	// :
+	if (!check_char(env, s, 13, ':')) return false;
+
+	// minute
+        n = get_char(env, s, 14, 10, &thrown);
+        n += get_char(env, s, 15, 1, &thrown);
+        if (thrown) return false;
+	int minute = n;
+        // env->SetIntField(This, g_minField, n);
+
+	// :
+	if (!check_char(env, s, 16, ':')) return false;
+
+	// second
+        n = get_char(env, s, 17, 10, &thrown);
+        n += get_char(env, s, 18, 1, &thrown);
+        if (thrown) return false;
+        env->SetIntField(This, g_secField, n);
+
+	// skip the '.XYZ' -- we don't care about subsecond precision.
+        int offset = 0;
+	if (len >= 23) {
+	    char c = s[23];
+
+	    // NOTE: the offset is meant to be subtracted to get from local time
+	    // to UTC.  we therefore use 1 for '-' and -1 for '+'.
+	    switch (c) {
+	    case 'Z':
+	        // Zulu time -- UTC
+	        offset = 0;
+		break;
+	    case '-': 
+                offset = 1;
+	        break;
+	    case '+': 
+                offset = -1;
+	        break;
+	    default:
+	        char msg[100];
+	        sprintf(msg, "Unexpected %c at position 19.  Expected + or -",
+			c);
+	        jniThrowException(env, "android/util/TimeFormatException", msg);
+	        return false;
+	    }
+            inUtc = true;
+
+	    if (offset != 0) {
+	        // hour
+	        n = get_char(env, s, 24, 10, &thrown);
+		n += get_char(env, s, 25, 1, &thrown);
+		if (thrown) return false;
+		n *= offset;
+		hour += n;
+
+		// :
+		if (!check_char(env, s, 26, ':')) return false;
+	    
+		// minute
+		n = get_char(env, s, 27, 10, &thrown);
+		n += get_char(env, s, 28, 1, &thrown);
+		if (thrown) return false;
+		n *= offset;
+		minute += n;
+	    }
+	}
+	env->SetIntField(This, g_hourField, hour);
+        env->SetIntField(This, g_minField, minute);
+
+	if (offset != 0) {
+	    // we need to normalize after applying the hour and minute offsets
+	    android_text_format_Time_normalize(env, This, false /* use isdst */);
+	    // The timezone is set to UTC in the calling Java code.
+	}
+    } else {
+	env->SetBooleanField(This, g_allDayField, JNI_TRUE);
+        env->SetIntField(This, g_hourField, 0);
+        env->SetIntField(This, g_minField, 0);
+        env->SetIntField(This, g_secField, 0);
+    }
+
+    env->SetIntField(This, g_wdayField, 0);
+    env->SetIntField(This, g_ydayField, 0);
+    env->SetIntField(This, g_isdstField, -1);
+    env->SetLongField(This, g_gmtoffField, 0);
+    
+    env->ReleaseStringChars(strObj, s);
+    return inUtc;
+}
+
+// ============================================================================
+/*
+ * JNI registration.
+ */
+static JNINativeMethod gMethods[] = {
+    /* name, signature, funcPtr */
+    { "normalize",               "(Z)J",                                        (void*)android_text_format_Time_normalize },
+    { "switchTimezone",          "(Ljava/lang/String;)V",                       (void*)android_text_format_Time_switchTimezone },
+    { "compare",                 "(Landroid/text/format/Time;Landroid/text/format/Time;)I",     (void*)android_text_format_Time_compare },
+    { "format1",                 "(Ljava/lang/String;)Ljava/lang/String;",      (void*)android_text_format_Time_format },
+    { "format2445",              "()Ljava/lang/String;",                        (void*)android_text_format_Time_format2445 },
+    { "toString",                "()Ljava/lang/String;",                        (void*)android_text_format_Time_toString },
+    { "nativeParse",             "(Ljava/lang/String;)Z",                       (void*)android_text_format_Time_parse },
+    { "nativeParse3339",         "(Ljava/lang/String;)Z",                       (void*)android_text_format_Time_parse3339 },
+    { "setToNow",                "()V",                                         (void*)android_text_format_Time_setToNow },
+    { "toMillis",                "(Z)J",                                        (void*)android_text_format_Time_toMillis },
+    { "set",                     "(J)V",                                        (void*)android_text_format_Time_set }
+};
+
+int register_android_text_format_Time(JNIEnv* env)
+{
+    jclass timeClass = env->FindClass("android/text/format/Time");
+
+    g_allDayField = env->GetFieldID(timeClass, "allDay", "Z");
+    g_secField = env->GetFieldID(timeClass, "second", "I");
+    g_minField = env->GetFieldID(timeClass, "minute", "I");
+    g_hourField = env->GetFieldID(timeClass, "hour", "I");
+    g_mdayField = env->GetFieldID(timeClass, "monthDay", "I");
+    g_monField = env->GetFieldID(timeClass, "month", "I");
+    g_yearField = env->GetFieldID(timeClass, "year", "I");
+    g_wdayField = env->GetFieldID(timeClass, "weekDay", "I");
+    g_ydayField = env->GetFieldID(timeClass, "yearDay", "I");
+    g_isdstField = env->GetFieldID(timeClass, "isDst", "I");
+    g_gmtoffField = env->GetFieldID(timeClass, "gmtoff", "J");
+    g_timezoneField = env->GetFieldID(timeClass, "timezone", "Ljava/lang/String;");
+
+    g_shortMonthsField = env->GetStaticFieldID(timeClass, "sShortMonths", "[Ljava/lang/String;");
+    g_longMonthsField = env->GetStaticFieldID(timeClass, "sLongMonths", "[Ljava/lang/String;");
+    g_shortWeekdaysField = env->GetStaticFieldID(timeClass, "sShortWeekdays", "[Ljava/lang/String;");
+    g_longWeekdaysField = env->GetStaticFieldID(timeClass, "sLongWeekdays", "[Ljava/lang/String;");
+    g_timeOnlyFormatField = env->GetStaticFieldID(timeClass, "sTimeOnlyFormat", "Ljava/lang/String;");
+    g_dateOnlyFormatField = env->GetStaticFieldID(timeClass, "sDateOnlyFormat", "Ljava/lang/String;");
+    g_dateTimeFormatField = env->GetStaticFieldID(timeClass, "sDateTimeFormat", "Ljava/lang/String;");
+    g_amField = env->GetStaticFieldID(timeClass, "sAm", "Ljava/lang/String;");
+    g_pmField = env->GetStaticFieldID(timeClass, "sPm", "Ljava/lang/String;");
+    g_dateCommandField = env->GetStaticFieldID(timeClass, "sDateCommand", "Ljava/lang/String;");
+
+    return AndroidRuntime::registerNativeMethods(env, "android/text/format/Time", gMethods, NELEM(gMethods));
+}
+
+}; // namespace android
+
diff --git a/core/jni/android_util_AssetManager.cpp b/core/jni/android_util_AssetManager.cpp
new file mode 100644
index 0000000..d147bcc
--- /dev/null
+++ b/core/jni/android_util_AssetManager.cpp
@@ -0,0 +1,1687 @@
+/* //device/libs/android_runtime/android_util_AssetManager.cpp
+**
+** Copyright 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.
+*/
+
+#define LOG_TAG "asset"
+
+#include <android_runtime/android_util_AssetManager.h>
+
+#include "jni.h"
+#include "JNIHelp.h"
+#include "android_util_Binder.h"
+#include <utils/misc.h>
+#include <android_runtime/AndroidRuntime.h>
+#include <utils/Log.h>
+
+#include <utils/Asset.h>
+#include <utils/AssetManager.h>
+#include <utils/ResourceTypes.h>
+
+#include <stdio.h>
+
+namespace android {
+
+// ----------------------------------------------------------------------------
+
+static struct typedvalue_offsets_t
+{
+    jfieldID mType;
+    jfieldID mData;
+    jfieldID mString;
+    jfieldID mAssetCookie;
+    jfieldID mResourceId;
+    jfieldID mChangingConfigurations;
+    jfieldID mDensity;
+} gTypedValueOffsets;
+
+static struct assetfiledescriptor_offsets_t
+{
+    jfieldID mFd;
+    jfieldID mStartOffset;
+    jfieldID mLength;
+} gAssetFileDescriptorOffsets;
+
+static struct assetmanager_offsets_t
+{
+    jfieldID mObject;
+} gAssetManagerOffsets;
+
+jclass g_stringClass = NULL;
+
+// ----------------------------------------------------------------------------
+
+static void doThrow(JNIEnv* env, const char* exc, const char* msg = NULL)
+{
+    jclass npeClazz;
+
+    npeClazz = env->FindClass(exc);
+    LOG_FATAL_IF(npeClazz == NULL, "Unable to find class %s", exc);
+
+    env->ThrowNew(npeClazz, msg);
+}
+
+enum {
+    STYLE_NUM_ENTRIES = 5,
+    STYLE_TYPE = 0,
+    STYLE_DATA = 1,
+    STYLE_ASSET_COOKIE = 2,
+    STYLE_RESOURCE_ID = 3,
+    STYLE_CHANGING_CONFIGURATIONS = 4
+};
+
+static jint copyValue(JNIEnv* env, jobject outValue, const ResTable* table,
+                      const Res_value& value, uint32_t ref, ssize_t block,
+                      uint32_t typeSpecFlags, ResTable_config* config = NULL);
+
+jint copyValue(JNIEnv* env, jobject outValue, const ResTable* table,
+               const Res_value& value, uint32_t ref, ssize_t block,
+               uint32_t typeSpecFlags, ResTable_config* config)
+{
+    env->SetIntField(outValue, gTypedValueOffsets.mType, value.dataType);
+    env->SetIntField(outValue, gTypedValueOffsets.mAssetCookie,
+                        (jint)table->getTableCookie(block));
+    env->SetIntField(outValue, gTypedValueOffsets.mData, value.data);
+    env->SetObjectField(outValue, gTypedValueOffsets.mString, NULL);
+    env->SetIntField(outValue, gTypedValueOffsets.mResourceId, ref);
+    env->SetIntField(outValue, gTypedValueOffsets.mChangingConfigurations,
+            typeSpecFlags);
+    if (config != NULL) {
+        env->SetIntField(outValue, gTypedValueOffsets.mDensity, config->density);
+    }
+    return block;
+}
+
+// ----------------------------------------------------------------------------
+
+// this guy is exported to other jni routines
+AssetManager* assetManagerForJavaObject(JNIEnv* env, jobject obj)
+{
+    AssetManager* am = (AssetManager*)env->GetIntField(obj, gAssetManagerOffsets.mObject);
+    if (am != NULL) {
+        return am;
+    }
+    jniThrowException(env, "java/lang/IllegalStateException", "AssetManager has been finalized!");
+    return NULL;
+}
+
+static jint android_content_AssetManager_openAsset(JNIEnv* env, jobject clazz,
+                                                jstring fileName, jint mode)
+{
+    AssetManager* am = assetManagerForJavaObject(env, clazz);
+    if (am == NULL) {
+        return 0;
+    }
+
+    LOGV("openAsset in %p (Java object %p)\n", am, clazz);
+
+    if (fileName == NULL || am == NULL) {
+        doThrow(env, "java/lang/NullPointerException");
+        return -1;
+    }
+
+    if (mode != Asset::ACCESS_UNKNOWN && mode != Asset::ACCESS_RANDOM
+        && mode != Asset::ACCESS_STREAMING && mode != Asset::ACCESS_BUFFER) {
+        doThrow(env, "java/lang/IllegalArgumentException");
+        return -1;
+    }
+
+    const char* fileName8 = env->GetStringUTFChars(fileName, NULL);
+    Asset* a = am->open(fileName8, (Asset::AccessMode)mode);
+
+    if (a == NULL) {
+        doThrow(env, "java/io/FileNotFoundException", fileName8);
+        env->ReleaseStringUTFChars(fileName, fileName8);
+        return -1;
+    }
+    env->ReleaseStringUTFChars(fileName, fileName8);
+
+    //printf("Created Asset Stream: %p\n", a);
+
+    return (jint)a;
+}
+
+static jobject returnParcelFileDescriptor(JNIEnv* env, Asset* a, jlongArray outOffsets)
+{
+    off_t startOffset, length;
+    int fd = a->openFileDescriptor(&startOffset, &length);
+    delete a;
+    
+    if (fd < 0) {
+        doThrow(env, "java/io/FileNotFoundException",
+                "This file can not be opened as a file descriptor; it is probably compressed");
+        return NULL;
+    }
+    
+    jlong* offsets = (jlong*)env->GetPrimitiveArrayCritical(outOffsets, 0);
+    if (offsets == NULL) {
+        close(fd);
+        return NULL;
+    }
+    
+    offsets[0] = startOffset;
+    offsets[1] = length;
+    
+    env->ReleasePrimitiveArrayCritical(outOffsets, offsets, 0);
+    
+    jobject fileDesc = newFileDescriptor(env, fd);
+    if (fileDesc == NULL) {
+        close(fd);
+        return NULL;
+    }
+    
+    return newParcelFileDescriptor(env, fileDesc);
+}
+
+static jobject android_content_AssetManager_openAssetFd(JNIEnv* env, jobject clazz,
+                                                jstring fileName, jlongArray outOffsets)
+{
+    AssetManager* am = assetManagerForJavaObject(env, clazz);
+    if (am == NULL) {
+        return NULL;
+    }
+
+    LOGV("openAssetFd in %p (Java object %p)\n", am, clazz);
+
+    if (fileName == NULL || am == NULL) {
+        doThrow(env, "java/lang/NullPointerException");
+        return NULL;
+    }
+
+    const char* fileName8 = env->GetStringUTFChars(fileName, NULL);
+    Asset* a = am->open(fileName8, Asset::ACCESS_RANDOM);
+
+    if (a == NULL) {
+        doThrow(env, "java/io/FileNotFoundException", fileName8);
+        env->ReleaseStringUTFChars(fileName, fileName8);
+        return NULL;
+    }
+    env->ReleaseStringUTFChars(fileName, fileName8);
+
+    //printf("Created Asset Stream: %p\n", a);
+
+    return returnParcelFileDescriptor(env, a, outOffsets);
+}
+
+static jint android_content_AssetManager_openNonAssetNative(JNIEnv* env, jobject clazz,
+                                                         jint cookie,
+                                                         jstring fileName,
+                                                         jint mode)
+{
+    AssetManager* am = assetManagerForJavaObject(env, clazz);
+    if (am == NULL) {
+        return 0;
+    }
+
+    LOGV("openNonAssetNative in %p (Java object %p)\n", am, clazz);
+
+    if (fileName == NULL || am == NULL) {
+        doThrow(env, "java/lang/NullPointerException");
+        return -1;
+    }
+
+    if (mode != Asset::ACCESS_UNKNOWN && mode != Asset::ACCESS_RANDOM
+        && mode != Asset::ACCESS_STREAMING && mode != Asset::ACCESS_BUFFER) {
+        doThrow(env, "java/lang/IllegalArgumentException");
+        return -1;
+    }
+
+    const char* fileName8 = env->GetStringUTFChars(fileName, NULL);
+    Asset* a = cookie
+        ? am->openNonAsset((void*)cookie, fileName8, (Asset::AccessMode)mode)
+        : am->openNonAsset(fileName8, (Asset::AccessMode)mode);
+
+    if (a == NULL) {
+        doThrow(env, "java/io/FileNotFoundException", fileName8);
+        env->ReleaseStringUTFChars(fileName, fileName8);
+        return -1;
+    }
+    env->ReleaseStringUTFChars(fileName, fileName8);
+
+    //printf("Created Asset Stream: %p\n", a);
+
+    return (jint)a;
+}
+
+static jobject android_content_AssetManager_openNonAssetFdNative(JNIEnv* env, jobject clazz,
+                                                         jint cookie,
+                                                         jstring fileName,
+                                                         jlongArray outOffsets)
+{
+    AssetManager* am = assetManagerForJavaObject(env, clazz);
+    if (am == NULL) {
+        return NULL;
+    }
+
+    LOGV("openNonAssetFd in %p (Java object %p)\n", am, clazz);
+
+    if (fileName == NULL || am == NULL) {
+        doThrow(env, "java/lang/NullPointerException");
+        return NULL;
+    }
+
+    const char* fileName8 = env->GetStringUTFChars(fileName, NULL);
+    Asset* a = cookie
+        ? am->openNonAsset((void*)cookie, fileName8, Asset::ACCESS_RANDOM)
+        : am->openNonAsset(fileName8, Asset::ACCESS_RANDOM);
+
+    if (a == NULL) {
+        doThrow(env, "java/io/FileNotFoundException", fileName8);
+        env->ReleaseStringUTFChars(fileName, fileName8);
+        return NULL;
+    }
+    env->ReleaseStringUTFChars(fileName, fileName8);
+
+    //printf("Created Asset Stream: %p\n", a);
+
+    return returnParcelFileDescriptor(env, a, outOffsets);
+}
+
+static jobjectArray android_content_AssetManager_list(JNIEnv* env, jobject clazz,
+                                                   jstring fileName)
+{
+    AssetManager* am = assetManagerForJavaObject(env, clazz);
+    if (am == NULL) {
+        return NULL;
+    }
+
+    if (fileName == NULL || am == NULL) {
+        doThrow(env, "java/lang/NullPointerException");
+        return NULL;
+    }
+
+    const char* fileName8 = env->GetStringUTFChars(fileName, NULL);
+
+    AssetDir* dir = am->openDir(fileName8);
+
+    env->ReleaseStringUTFChars(fileName, fileName8);
+
+    if (dir == NULL) {
+        doThrow(env, "java/io/FileNotFoundException", fileName8);
+        return NULL;
+    }
+
+    jclass cls = env->FindClass("java/lang/String");
+    LOG_FATAL_IF(cls == NULL, "No string class?!?");
+    if (cls == NULL) {
+        delete dir;
+        return NULL;
+    }
+
+    size_t N = dir->getFileCount();
+
+    jobjectArray array = env->NewObjectArray(dir->getFileCount(),
+                                                cls, NULL);
+    if (array == NULL) {
+        doThrow(env, "java/lang/OutOfMemoryError");
+        delete dir;
+        return NULL;
+    }
+
+    for (size_t i=0; i<N; i++) {
+        const String8& name = dir->getFileName(i);
+        jstring str = env->NewStringUTF(name.string());
+        if (str == NULL) {
+            doThrow(env, "java/lang/OutOfMemoryError");
+            delete dir;
+            return NULL;
+        }
+        env->SetObjectArrayElement(array, i, str);
+    }
+
+    delete dir;
+
+    return array;
+}
+
+static void android_content_AssetManager_destroyAsset(JNIEnv* env, jobject clazz,
+                                                   jint asset)
+{
+    Asset* a = (Asset*)asset;
+
+    //printf("Destroying Asset Stream: %p\n", a);
+
+    if (a == NULL) {
+        doThrow(env, "java/lang/NullPointerException");
+        return;
+    }
+
+    delete a;
+}
+
+static jint android_content_AssetManager_readAssetChar(JNIEnv* env, jobject clazz,
+                                                    jint asset)
+{
+    Asset* a = (Asset*)asset;
+
+    if (a == NULL) {
+        doThrow(env, "java/lang/NullPointerException");
+        return -1;
+    }
+
+    uint8_t b;
+    ssize_t res = a->read(&b, 1);
+    return res == 1 ? b : -1;
+}
+
+static jint android_content_AssetManager_readAsset(JNIEnv* env, jobject clazz,
+                                                jint asset, jbyteArray bArray,
+                                                jint off, jint len)
+{
+    Asset* a = (Asset*)asset;
+
+    if (a == NULL || bArray == NULL) {
+        doThrow(env, "java/lang/NullPointerException");
+        return -1;
+    }
+
+    if (len == 0) {
+        return 0;
+    }
+    
+    jsize bLen = env->GetArrayLength(bArray);
+    if (off < 0 || off >= bLen || len < 0 || len > bLen || (off+len) > bLen) {
+        doThrow(env, "java/lang/IndexOutOfBoundsException");
+        return -1;
+    }
+
+    jbyte* b = env->GetByteArrayElements(bArray, NULL);
+    ssize_t res = a->read(b+off, len);
+    env->ReleaseByteArrayElements(bArray, b, 0);
+
+    if (res > 0) return res;
+
+    if (res < 0) {
+        doThrow(env, "java/io/IOException");
+    }
+    return -1;
+}
+
+static jlong android_content_AssetManager_seekAsset(JNIEnv* env, jobject clazz,
+                                                 jint asset,
+                                                 jlong offset, jint whence)
+{
+    Asset* a = (Asset*)asset;
+
+    if (a == NULL) {
+        doThrow(env, "java/lang/NullPointerException");
+        return -1;
+    }
+
+    return a->seek(
+        offset, (whence > 0) ? SEEK_END : (whence < 0 ? SEEK_SET : SEEK_CUR));
+}
+
+static jlong android_content_AssetManager_getAssetLength(JNIEnv* env, jobject clazz,
+                                                      jint asset)
+{
+    Asset* a = (Asset*)asset;
+
+    if (a == NULL) {
+        doThrow(env, "java/lang/NullPointerException");
+        return -1;
+    }
+
+    return a->getLength();
+}
+
+static jlong android_content_AssetManager_getAssetRemainingLength(JNIEnv* env, jobject clazz,
+                                                               jint asset)
+{
+    Asset* a = (Asset*)asset;
+
+    if (a == NULL) {
+        doThrow(env, "java/lang/NullPointerException");
+        return -1;
+    }
+
+    return a->getRemainingLength();
+}
+
+static jint android_content_AssetManager_addAssetPath(JNIEnv* env, jobject clazz,
+                                                       jstring path)
+{
+    if (path == NULL) {
+        doThrow(env, "java/lang/NullPointerException");
+        return JNI_FALSE;
+    }
+
+    AssetManager* am = assetManagerForJavaObject(env, clazz);
+    if (am == NULL) {
+        return JNI_FALSE;
+    }
+
+    const char* path8 = env->GetStringUTFChars(path, NULL);
+
+    void* cookie;
+    bool res = am->addAssetPath(String8(path8), &cookie);
+
+    env->ReleaseStringUTFChars(path, path8);
+
+    return (res) ? (jint)cookie : 0;
+}
+
+static jboolean android_content_AssetManager_isUpToDate(JNIEnv* env, jobject clazz)
+{
+    AssetManager* am = assetManagerForJavaObject(env, clazz);
+    if (am == NULL) {
+        return JNI_TRUE;
+    }
+    return am->isUpToDate() ? JNI_TRUE : JNI_FALSE;
+}
+
+static void android_content_AssetManager_setLocale(JNIEnv* env, jobject clazz,
+                                                jstring locale)
+{
+    if (locale == NULL) {
+        doThrow(env, "java/lang/NullPointerException");
+        return;
+    }
+
+    const char* locale8 = env->GetStringUTFChars(locale, NULL);
+
+    AssetManager* am = assetManagerForJavaObject(env, clazz);
+    if (am == NULL) {
+        return;
+    }
+
+    am->setLocale(locale8);
+
+    env->ReleaseStringUTFChars(locale, locale8);
+}
+
+static jobjectArray android_content_AssetManager_getLocales(JNIEnv* env, jobject clazz)
+{
+    Vector<String8> locales;
+
+    AssetManager* am = assetManagerForJavaObject(env, clazz);
+    if (am == NULL) {
+        return NULL;
+    }
+
+    am->getLocales(&locales);
+
+    const int N = locales.size();
+
+    jobjectArray result = env->NewObjectArray(N, g_stringClass, NULL);
+    if (result == NULL) {
+        return NULL;
+    }
+
+    for (int i=0; i<N; i++) {
+        LOGD("locale %2d: '%s'", i, locales[i].string());
+        env->SetObjectArrayElement(result, i, env->NewStringUTF(locales[i].string()));
+    }
+
+    return result;
+}
+
+static void android_content_AssetManager_setConfiguration(JNIEnv* env, jobject clazz,
+                                                          jint mcc, jint mnc,
+                                                          jstring locale, jint orientation,
+                                                          jint touchscreen, jint density,
+                                                          jint keyboard, jint keyboardHidden,
+                                                          jint navigation,
+                                                          jint screenWidth, jint screenHeight,
+                                                          jint sdkVersion)
+{
+    AssetManager* am = assetManagerForJavaObject(env, clazz);
+    if (am == NULL) {
+        return;
+    }
+
+    ResTable_config config;
+    memset(&config, 0, sizeof(config));
+    
+    const char* locale8 = locale != NULL ? env->GetStringUTFChars(locale, NULL) : NULL;
+    
+    config.mcc = (uint16_t)mcc;
+    config.mnc = (uint16_t)mnc;
+    config.orientation = (uint8_t)orientation;
+    config.touchscreen = (uint8_t)touchscreen;
+    config.density = (uint16_t)density;
+    config.keyboard = (uint8_t)keyboard;
+    config.inputFlags = (uint8_t)keyboardHidden<<ResTable_config::SHIFT_KEYSHIDDEN;
+    config.navigation = (uint8_t)navigation;
+    config.screenWidth = (uint16_t)screenWidth;
+    config.screenHeight = (uint16_t)screenHeight;
+    config.sdkVersion = (uint16_t)sdkVersion;
+    config.minorVersion = 0;
+    am->setConfiguration(config, locale8);
+    
+    if (locale != NULL) env->ReleaseStringUTFChars(locale, locale8);
+}
+
+static jint android_content_AssetManager_getResourceIdentifier(JNIEnv* env, jobject clazz,
+                                                            jstring name,
+                                                            jstring defType,
+                                                            jstring defPackage)
+{
+    if (name == NULL) {
+        doThrow(env, "java/lang/NullPointerException");
+        return 0;
+    }
+
+    AssetManager* am = assetManagerForJavaObject(env, clazz);
+    if (am == NULL) {
+        return 0;
+    }
+
+    const char16_t* name16 = env->GetStringChars(name, NULL);
+    jsize nameLen = env->GetStringLength(name);
+    const char16_t* defType16 = defType
+        ? env->GetStringChars(defType, NULL) : NULL;
+    jsize defTypeLen = defType
+        ? env->GetStringLength(defType) : 0;
+    const char16_t* defPackage16 = defPackage
+        ? env->GetStringChars(defPackage, NULL) : NULL;
+    jsize defPackageLen = defPackage
+        ? env->GetStringLength(defPackage) : 0;
+
+    jint ident = am->getResources().identifierForName(
+        name16, nameLen, defType16, defTypeLen, defPackage16, defPackageLen);
+
+    if (defPackage16) {
+        env->ReleaseStringChars(defPackage, defPackage16);
+    }
+    if (defType16) {
+        env->ReleaseStringChars(defType, defType16);
+    }
+    env->ReleaseStringChars(name, name16);
+
+    return ident;
+}
+
+static jstring android_content_AssetManager_getResourceName(JNIEnv* env, jobject clazz,
+                                                            jint resid)
+{
+    AssetManager* am = assetManagerForJavaObject(env, clazz);
+    if (am == NULL) {
+        return NULL;
+    }
+    
+    ResTable::resource_name name;
+    if (!am->getResources().getResourceName(resid, &name)) {
+        return NULL;
+    }
+    
+    String16 str;
+    if (name.package != NULL) {
+        str.setTo(name.package, name.packageLen);
+    }
+    if (name.type != NULL) {
+        if (str.size() > 0) {
+            char16_t div = ':';
+            str.append(&div, 1);
+        }
+        str.append(name.type, name.typeLen);
+    }
+    if (name.name != NULL) {
+        if (str.size() > 0) {
+            char16_t div = '/';
+            str.append(&div, 1);
+        }
+        str.append(name.name, name.nameLen);
+    }
+    
+    return env->NewString((const jchar*)str.string(), str.size());
+}
+
+static jstring android_content_AssetManager_getResourcePackageName(JNIEnv* env, jobject clazz,
+                                                                   jint resid)
+{
+    AssetManager* am = assetManagerForJavaObject(env, clazz);
+    if (am == NULL) {
+        return NULL;
+    }
+    
+    ResTable::resource_name name;
+    if (!am->getResources().getResourceName(resid, &name)) {
+        return NULL;
+    }
+    
+    if (name.package != NULL) {
+        return env->NewString((const jchar*)name.package, name.packageLen);
+    }
+    
+    return NULL;
+}
+
+static jstring android_content_AssetManager_getResourceTypeName(JNIEnv* env, jobject clazz,
+                                                                jint resid)
+{
+    AssetManager* am = assetManagerForJavaObject(env, clazz);
+    if (am == NULL) {
+        return NULL;
+    }
+    
+    ResTable::resource_name name;
+    if (!am->getResources().getResourceName(resid, &name)) {
+        return NULL;
+    }
+    
+    if (name.type != NULL) {
+        return env->NewString((const jchar*)name.type, name.typeLen);
+    }
+    
+    return NULL;
+}
+
+static jstring android_content_AssetManager_getResourceEntryName(JNIEnv* env, jobject clazz,
+                                                                 jint resid)
+{
+    AssetManager* am = assetManagerForJavaObject(env, clazz);
+    if (am == NULL) {
+        return NULL;
+    }
+    
+    ResTable::resource_name name;
+    if (!am->getResources().getResourceName(resid, &name)) {
+        return NULL;
+    }
+    
+    if (name.name != NULL) {
+        return env->NewString((const jchar*)name.name, name.nameLen);
+    }
+    
+    return NULL;
+}
+
+static jint android_content_AssetManager_loadResourceValue(JNIEnv* env, jobject clazz,
+                                                           jint ident,
+                                                           jobject outValue,
+                                                           jboolean resolve)
+{
+    AssetManager* am = assetManagerForJavaObject(env, clazz);
+    if (am == NULL) {
+        return 0;
+    }
+    const ResTable& res(am->getResources());
+
+    Res_value value;
+    ResTable_config config;
+    uint32_t typeSpecFlags;
+    ssize_t block = res.getResource(ident, &value, false, &typeSpecFlags, &config);
+    uint32_t ref = ident;
+    if (resolve) {
+        block = res.resolveReference(&value, block, &ref);
+    }
+    return block >= 0 ? copyValue(env, outValue, &res, value, ref, block, typeSpecFlags, &config) : block;
+}
+
+static jint android_content_AssetManager_loadResourceBagValue(JNIEnv* env, jobject clazz,
+                                                           jint ident, jint bagEntryId,
+                                                           jobject outValue, jboolean resolve)
+{
+    AssetManager* am = assetManagerForJavaObject(env, clazz);
+    if (am == NULL) {
+        return 0;
+    }
+    const ResTable& res(am->getResources());
+    
+    // Now lock down the resource object and start pulling stuff from it.
+    res.lock();
+    
+    ssize_t block = -1;
+    Res_value value;
+
+    const ResTable::bag_entry* entry = NULL;
+    uint32_t typeSpecFlags;
+    ssize_t entryCount = res.getBagLocked(ident, &entry, &typeSpecFlags);
+
+    for (ssize_t i=0; i<entryCount; i++) {
+        if (((uint32_t)bagEntryId) == entry->map.name.ident) {
+            block = entry->stringBlock;
+            value = entry->map.value;
+        }
+        entry++;
+    }
+
+    res.unlock();
+
+    if (block < 0) {
+        return block;
+    }
+    
+    uint32_t ref = ident;
+    if (resolve) {
+        block = res.resolveReference(&value, block, &ref, &typeSpecFlags);
+    }
+    return block >= 0 ? copyValue(env, outValue, &res, value, ref, block, typeSpecFlags) : block;
+}
+
+static jint android_content_AssetManager_getStringBlockCount(JNIEnv* env, jobject clazz)
+{
+    AssetManager* am = assetManagerForJavaObject(env, clazz);
+    if (am == NULL) {
+        return 0;
+    }
+    return am->getResources().getTableCount();
+}
+
+static jint android_content_AssetManager_getNativeStringBlock(JNIEnv* env, jobject clazz,
+                                                           jint block)
+{
+    AssetManager* am = assetManagerForJavaObject(env, clazz);
+    if (am == NULL) {
+        return 0;
+    }
+    return (jint)am->getResources().getTableStringBlock(block);
+}
+
+static jstring android_content_AssetManager_getCookieName(JNIEnv* env, jobject clazz,
+                                                       jint cookie)
+{
+    AssetManager* am = assetManagerForJavaObject(env, clazz);
+    if (am == NULL) {
+        return NULL;
+    }
+    String8 name(am->getAssetPath((void*)cookie));
+    if (name.length() == 0) {
+        doThrow(env, "java/lang/IndexOutOfBoundsException");
+        return NULL;
+    }
+    jstring str = env->NewStringUTF(name.string());
+    if (str == NULL) {
+        doThrow(env, "java/lang/OutOfMemoryError");
+        return NULL;
+    }
+    return str;
+}
+
+static jint android_content_AssetManager_newTheme(JNIEnv* env, jobject clazz)
+{
+    AssetManager* am = assetManagerForJavaObject(env, clazz);
+    if (am == NULL) {
+        return 0;
+    }
+    return (jint)(new ResTable::Theme(am->getResources()));
+}
+
+static void android_content_AssetManager_deleteTheme(JNIEnv* env, jobject clazz,
+                                                     jint themeInt)
+{
+    ResTable::Theme* theme = (ResTable::Theme*)themeInt;
+    delete theme;
+}
+
+static void android_content_AssetManager_applyThemeStyle(JNIEnv* env, jobject clazz,
+                                                         jint themeInt,
+                                                         jint styleRes,
+                                                         jboolean force)
+{
+    ResTable::Theme* theme = (ResTable::Theme*)themeInt;
+    theme->applyStyle(styleRes, force ? true : false);
+}
+
+static void android_content_AssetManager_copyTheme(JNIEnv* env, jobject clazz,
+                                                   jint destInt, jint srcInt)
+{
+    ResTable::Theme* dest = (ResTable::Theme*)destInt;
+    ResTable::Theme* src = (ResTable::Theme*)srcInt;
+    dest->setTo(*src);
+}
+
+static jint android_content_AssetManager_loadThemeAttributeValue(
+    JNIEnv* env, jobject clazz, jint themeInt, jint ident, jobject outValue, jboolean resolve)
+{
+    ResTable::Theme* theme = (ResTable::Theme*)themeInt;
+    const ResTable& res(theme->getResTable());
+
+    Res_value value;
+    // XXX value could be different in different configs!
+    uint32_t typeSpecFlags = 0;
+    ssize_t block = theme->getAttribute(ident, &value, &typeSpecFlags);
+    uint32_t ref = 0;
+    if (resolve) {
+        block = res.resolveReference(&value, block, &ref, &typeSpecFlags);
+    }
+    return block >= 0 ? copyValue(env, outValue, &res, value, ref, block, typeSpecFlags) : block;
+}
+
+static void android_content_AssetManager_dumpTheme(JNIEnv* env, jobject clazz,
+                                                   jint themeInt, jint pri,
+                                                   jstring tag, jstring prefix)
+{
+    ResTable::Theme* theme = (ResTable::Theme*)themeInt;
+    const ResTable& res(theme->getResTable());
+    
+    if (tag == NULL) {
+        doThrow(env, "java/lang/NullPointerException");
+        return;
+    }
+    
+    const char* tag8 = env->GetStringUTFChars(tag, NULL);
+    const char* prefix8 = NULL;
+    if (prefix != NULL) {
+        prefix8 = env->GetStringUTFChars(prefix, NULL);
+    }
+    
+    // XXX Need to use params.
+    theme->dumpToLog();
+    
+    if (prefix8 != NULL) {
+        env->ReleaseStringUTFChars(prefix, prefix8);
+    }
+    env->ReleaseStringUTFChars(tag, tag8);
+}
+
+static jboolean android_content_AssetManager_applyStyle(JNIEnv* env, jobject clazz,
+                                                        jint themeToken,
+                                                        jint defStyleAttr,
+                                                        jint defStyleRes,
+                                                        jint xmlParserToken,
+                                                        jintArray attrs,
+                                                        jintArray outValues,
+                                                        jintArray outIndices)
+{
+    if (themeToken == 0 || attrs == NULL || outValues == NULL) {
+        doThrow(env, "java/lang/NullPointerException");
+        return JNI_FALSE;
+    }
+
+    ResTable::Theme* theme = (ResTable::Theme*)themeToken;
+    const ResTable& res = theme->getResTable();
+    ResXMLParser* xmlParser = (ResXMLParser*)xmlParserToken;
+    Res_value value;
+
+    const jsize NI = env->GetArrayLength(attrs);
+    const jsize NV = env->GetArrayLength(outValues);
+    if (NV < (NI*STYLE_NUM_ENTRIES)) {
+        doThrow(env, "java/lang/IndexOutOfBoundsException");
+        return JNI_FALSE;
+    }
+
+    jint* src = (jint*)env->GetPrimitiveArrayCritical(attrs, 0);
+    if (src == NULL) {
+        doThrow(env, "java/lang/OutOfMemoryError");
+        return JNI_FALSE;
+    }
+
+    jint* baseDest = (jint*)env->GetPrimitiveArrayCritical(outValues, 0);
+    jint* dest = baseDest;
+    if (dest == NULL) {
+        env->ReleasePrimitiveArrayCritical(attrs, src, 0);
+        doThrow(env, "java/lang/OutOfMemoryError");
+        return JNI_FALSE;
+    }
+
+    jint* indices = NULL;
+    int indicesIdx = 0;
+    if (outIndices != NULL) {
+        if (env->GetArrayLength(outIndices) > NI) {
+            indices = (jint*)env->GetPrimitiveArrayCritical(outIndices, 0);
+        }
+    }
+
+    // Load default style from attribute, if specified...
+    uint32_t defStyleBagTypeSetFlags = 0;
+    if (defStyleAttr != 0) {
+        Res_value value;
+        if (theme->getAttribute(defStyleAttr, &value, &defStyleBagTypeSetFlags) >= 0) {
+            if (value.dataType == Res_value::TYPE_REFERENCE) {
+                defStyleRes = value.data;
+            }
+        }
+    }
+
+    // Retrieve the style class associated with the current XML tag.
+    int style = 0;
+    uint32_t styleBagTypeSetFlags = 0;
+    if (xmlParser != NULL) {
+        ssize_t idx = xmlParser->indexOfStyle();
+        if (idx >= 0 && xmlParser->getAttributeValue(idx, &value) >= 0) {
+            if (value.dataType == value.TYPE_ATTRIBUTE) {
+                if (theme->getAttribute(value.data, &value, &styleBagTypeSetFlags) < 0) {
+                    value.dataType = Res_value::TYPE_NULL;
+                }
+            }
+            if (value.dataType == value.TYPE_REFERENCE) {
+                style = value.data;
+            }
+        }
+    }
+
+    // Now lock down the resource object and start pulling stuff from it.
+    res.lock();
+
+    // Retrieve the default style bag, if requested.
+    const ResTable::bag_entry* defStyleEnt = NULL;
+    uint32_t defStyleTypeSetFlags = 0;
+    ssize_t bagOff = defStyleRes != 0
+            ? res.getBagLocked(defStyleRes, &defStyleEnt, &defStyleTypeSetFlags) : -1;
+    defStyleTypeSetFlags |= defStyleBagTypeSetFlags;
+    const ResTable::bag_entry* endDefStyleEnt = defStyleEnt +
+        (bagOff >= 0 ? bagOff : 0);
+
+    // Retrieve the style class bag, if requested.
+    const ResTable::bag_entry* styleEnt = NULL;
+    uint32_t styleTypeSetFlags = 0;
+    bagOff = style != 0 ? res.getBagLocked(style, &styleEnt, &styleTypeSetFlags) : -1;
+    styleTypeSetFlags |= styleBagTypeSetFlags;
+    const ResTable::bag_entry* endStyleEnt = styleEnt +
+        (bagOff >= 0 ? bagOff : 0);
+
+    // Retrieve the XML attributes, if requested.
+    const jsize NX = xmlParser ? xmlParser->getAttributeCount() : 0;
+    jsize ix=0;
+    uint32_t curXmlAttr = xmlParser ? xmlParser->getAttributeNameResID(ix) : 0;
+
+    static const ssize_t kXmlBlock = 0x10000000;
+
+    // Now iterate through all of the attributes that the client has requested,
+    // filling in each with whatever data we can find.
+    ssize_t block = 0;
+    uint32_t typeSetFlags;
+    for (jsize ii=0; ii<NI; ii++) {
+        const uint32_t curIdent = (uint32_t)src[ii];
+
+        // Try to find a value for this attribute...  we prioritize values
+        // coming from, first XML attributes, then XML style, then default
+        // style, and finally the theme.
+        value.dataType = Res_value::TYPE_NULL;
+        value.data = 0;
+        typeSetFlags = 0;
+
+        // Skip through XML attributes until the end or the next possible match.
+        while (ix < NX && curIdent > curXmlAttr) {
+            ix++;
+            curXmlAttr = xmlParser->getAttributeNameResID(ix);
+        }
+        // Retrieve the current XML attribute if it matches, and step to next.
+        if (ix < NX && curIdent == curXmlAttr) {
+            block = kXmlBlock;
+            xmlParser->getAttributeValue(ix, &value);
+            ix++;
+            curXmlAttr = xmlParser->getAttributeNameResID(ix);
+        }
+
+        // Skip through the style values until the end or the next possible match.
+        while (styleEnt < endStyleEnt && curIdent > styleEnt->map.name.ident) {
+            styleEnt++;
+        }
+        // Retrieve the current style attribute if it matches, and step to next.
+        if (styleEnt < endStyleEnt && curIdent == styleEnt->map.name.ident) {
+            if (value.dataType == Res_value::TYPE_NULL) {
+                block = styleEnt->stringBlock;
+                typeSetFlags = styleTypeSetFlags;
+                value = styleEnt->map.value;
+            }
+            styleEnt++;
+        }
+
+        // Skip through the default style values until the end or the next possible match.
+        while (defStyleEnt < endDefStyleEnt && curIdent > defStyleEnt->map.name.ident) {
+            defStyleEnt++;
+        }
+        // Retrieve the current default style attribute if it matches, and step to next.
+        if (defStyleEnt < endDefStyleEnt && curIdent == defStyleEnt->map.name.ident) {
+            if (value.dataType == Res_value::TYPE_NULL) {
+                block = defStyleEnt->stringBlock;
+                typeSetFlags = defStyleTypeSetFlags;
+                value = defStyleEnt->map.value;
+            }
+            defStyleEnt++;
+        }
+
+        //printf("Attribute 0x%08x: type=0x%x, data=0x%08x\n", curIdent, value.dataType, value.data);
+        uint32_t resid = 0;
+        if (value.dataType != Res_value::TYPE_NULL) {
+            // Take care of resolving the found resource to its final value.
+            //printf("Resolving attribute reference\n");
+            ssize_t newBlock = theme->resolveAttributeReference(&value, block, &resid, &typeSetFlags);
+            if (newBlock >= 0) block = newBlock;
+        } else {
+            // If we still don't have a value for this attribute, try to find
+            // it in the theme!
+            //printf("Looking up in theme\n");
+            ssize_t newBlock = theme->getAttribute(curIdent, &value, &typeSetFlags);
+            if (newBlock >= 0) {
+                //printf("Resolving resource reference\n");
+                newBlock = res.resolveReference(&value, block, &resid, &typeSetFlags);
+                if (newBlock >= 0) block = newBlock;
+            }
+        }
+
+        // Deal with the special @null value -- it turns back to TYPE_NULL.
+        if (value.dataType == Res_value::TYPE_REFERENCE && value.data == 0) {
+            value.dataType = Res_value::TYPE_NULL;
+        }
+
+        //printf("Attribute 0x%08x: final type=0x%x, data=0x%08x\n", curIdent, value.dataType, value.data);
+
+        // Write the final value back to Java.
+        dest[STYLE_TYPE] = value.dataType;
+        dest[STYLE_DATA] = value.data;
+        dest[STYLE_ASSET_COOKIE] =
+            block != kXmlBlock ? (jint)res.getTableCookie(block) : (jint)-1;
+        dest[STYLE_RESOURCE_ID] = resid;
+        dest[STYLE_CHANGING_CONFIGURATIONS] = typeSetFlags;
+        
+        if (indices != NULL && value.dataType != Res_value::TYPE_NULL) {
+            indicesIdx++;
+            indices[indicesIdx] = ii;
+        }
+        
+        dest += STYLE_NUM_ENTRIES;
+    }
+
+    res.unlock();
+
+    if (indices != NULL) {
+        indices[0] = indicesIdx;
+        env->ReleasePrimitiveArrayCritical(outIndices, indices, 0);
+    }
+    env->ReleasePrimitiveArrayCritical(outValues, baseDest, 0);
+    env->ReleasePrimitiveArrayCritical(attrs, src, 0);
+
+    return JNI_TRUE;
+}
+
+static jboolean android_content_AssetManager_retrieveAttributes(JNIEnv* env, jobject clazz,
+                                                        jint xmlParserToken,
+                                                        jintArray attrs,
+                                                        jintArray outValues,
+                                                        jintArray outIndices)
+{
+    if (xmlParserToken == 0 || attrs == NULL || outValues == NULL) {
+        doThrow(env, "java/lang/NullPointerException");
+        return JNI_FALSE;
+    }
+    
+    AssetManager* am = assetManagerForJavaObject(env, clazz);
+    if (am == NULL) {
+        return JNI_FALSE;
+    }
+    const ResTable& res(am->getResources());
+    ResXMLParser* xmlParser = (ResXMLParser*)xmlParserToken;
+    Res_value value;
+    
+    const jsize NI = env->GetArrayLength(attrs);
+    const jsize NV = env->GetArrayLength(outValues);
+    if (NV < (NI*STYLE_NUM_ENTRIES)) {
+        doThrow(env, "java/lang/IndexOutOfBoundsException");
+        return JNI_FALSE;
+    }
+    
+    jint* src = (jint*)env->GetPrimitiveArrayCritical(attrs, 0);
+    if (src == NULL) {
+        doThrow(env, "java/lang/OutOfMemoryError");
+        return JNI_FALSE;
+    }
+    
+    jint* baseDest = (jint*)env->GetPrimitiveArrayCritical(outValues, 0);
+    jint* dest = baseDest;
+    if (dest == NULL) {
+        env->ReleasePrimitiveArrayCritical(attrs, src, 0);
+        doThrow(env, "java/lang/OutOfMemoryError");
+        return JNI_FALSE;
+    }
+    
+    jint* indices = NULL;
+    int indicesIdx = 0;
+    if (outIndices != NULL) {
+        if (env->GetArrayLength(outIndices) > NI) {
+            indices = (jint*)env->GetPrimitiveArrayCritical(outIndices, 0);
+        }
+    }
+
+    // Now lock down the resource object and start pulling stuff from it.
+    res.lock();
+    
+    // Retrieve the XML attributes, if requested.
+    const jsize NX = xmlParser->getAttributeCount();
+    jsize ix=0;
+    uint32_t curXmlAttr = xmlParser->getAttributeNameResID(ix);
+    
+    static const ssize_t kXmlBlock = 0x10000000;
+    
+    // Now iterate through all of the attributes that the client has requested,
+    // filling in each with whatever data we can find.
+    ssize_t block = 0;
+    uint32_t typeSetFlags;
+    for (jsize ii=0; ii<NI; ii++) {
+        const uint32_t curIdent = (uint32_t)src[ii];
+        
+        // Try to find a value for this attribute...
+        value.dataType = Res_value::TYPE_NULL;
+        value.data = 0;
+        typeSetFlags = 0;
+        
+        // Skip through XML attributes until the end or the next possible match.
+        while (ix < NX && curIdent > curXmlAttr) {
+            ix++;
+            curXmlAttr = xmlParser->getAttributeNameResID(ix);
+        }
+        // Retrieve the current XML attribute if it matches, and step to next.
+        if (ix < NX && curIdent == curXmlAttr) {
+            block = kXmlBlock;
+            xmlParser->getAttributeValue(ix, &value);
+            ix++;
+            curXmlAttr = xmlParser->getAttributeNameResID(ix);
+        }
+        
+        //printf("Attribute 0x%08x: type=0x%x, data=0x%08x\n", curIdent, value.dataType, value.data);
+        uint32_t resid = 0;
+        if (value.dataType != Res_value::TYPE_NULL) {
+            // Take care of resolving the found resource to its final value.
+            //printf("Resolving attribute reference\n");
+            ssize_t newBlock = res.resolveReference(&value, block, &resid, &typeSetFlags);
+            if (newBlock >= 0) block = newBlock;
+        }
+        
+        // Deal with the special @null value -- it turns back to TYPE_NULL.
+        if (value.dataType == Res_value::TYPE_REFERENCE && value.data == 0) {
+            value.dataType = Res_value::TYPE_NULL;
+        }
+        
+        //printf("Attribute 0x%08x: final type=0x%x, data=0x%08x\n", curIdent, value.dataType, value.data);
+        
+        // Write the final value back to Java.
+        dest[STYLE_TYPE] = value.dataType;
+        dest[STYLE_DATA] = value.data;
+        dest[STYLE_ASSET_COOKIE] =
+            block != kXmlBlock ? (jint)res.getTableCookie(block) : (jint)-1;
+        dest[STYLE_RESOURCE_ID] = resid;
+        dest[STYLE_CHANGING_CONFIGURATIONS] = typeSetFlags;
+        
+        if (indices != NULL && value.dataType != Res_value::TYPE_NULL) {
+            indicesIdx++;
+            indices[indicesIdx] = ii;
+        }
+        
+        dest += STYLE_NUM_ENTRIES;
+    }
+    
+    res.unlock();
+    
+    if (indices != NULL) {
+        indices[0] = indicesIdx;
+        env->ReleasePrimitiveArrayCritical(outIndices, indices, 0);
+    }
+    
+    env->ReleasePrimitiveArrayCritical(outValues, baseDest, 0);
+    env->ReleasePrimitiveArrayCritical(attrs, src, 0);
+    
+    return JNI_TRUE;
+}
+
+static jint android_content_AssetManager_getArraySize(JNIEnv* env, jobject clazz,
+                                                       jint id)
+{
+    AssetManager* am = assetManagerForJavaObject(env, clazz);
+    if (am == NULL) {
+        return NULL;
+    }
+    const ResTable& res(am->getResources());
+    
+    res.lock();
+    const ResTable::bag_entry* defStyleEnt = NULL;
+    ssize_t bagOff = res.getBagLocked(id, &defStyleEnt);
+    res.unlock();
+    
+    return bagOff;
+}
+
+static jint android_content_AssetManager_retrieveArray(JNIEnv* env, jobject clazz,
+                                                        jint id,
+                                                        jintArray outValues)
+{
+    if (outValues == NULL) {
+        doThrow(env, "java/lang/NullPointerException");
+        return JNI_FALSE;
+    }
+    
+    AssetManager* am = assetManagerForJavaObject(env, clazz);
+    if (am == NULL) {
+        return JNI_FALSE;
+    }
+    const ResTable& res(am->getResources());
+    Res_value value;
+    ssize_t block;
+    
+    const jsize NV = env->GetArrayLength(outValues);
+    
+    jint* baseDest = (jint*)env->GetPrimitiveArrayCritical(outValues, 0);
+    jint* dest = baseDest;
+    if (dest == NULL) {
+        doThrow(env, "java/lang/OutOfMemoryError");
+        return JNI_FALSE;
+    }
+    
+    // Now lock down the resource object and start pulling stuff from it.
+    res.lock();
+    
+    const ResTable::bag_entry* arrayEnt = NULL;
+    uint32_t arrayTypeSetFlags = 0;
+    ssize_t bagOff = res.getBagLocked(id, &arrayEnt, &arrayTypeSetFlags);
+    const ResTable::bag_entry* endArrayEnt = arrayEnt +
+        (bagOff >= 0 ? bagOff : 0);
+    
+    int i = 0;
+    uint32_t typeSetFlags;
+    while (i < NV && arrayEnt < endArrayEnt) {
+        block = arrayEnt->stringBlock;
+        typeSetFlags = arrayTypeSetFlags;
+        value = arrayEnt->map.value;
+                
+        uint32_t resid = 0;
+        if (value.dataType != Res_value::TYPE_NULL) {
+            // Take care of resolving the found resource to its final value.
+            //printf("Resolving attribute reference\n");
+            ssize_t newBlock = res.resolveReference(&value, block, &resid, &typeSetFlags);
+            if (newBlock >= 0) block = newBlock;
+        }
+
+        // Deal with the special @null value -- it turns back to TYPE_NULL.
+        if (value.dataType == Res_value::TYPE_REFERENCE && value.data == 0) {
+            value.dataType = Res_value::TYPE_NULL;
+        }
+
+        //printf("Attribute 0x%08x: final type=0x%x, data=0x%08x\n", curIdent, value.dataType, value.data);
+
+        // Write the final value back to Java.
+        dest[STYLE_TYPE] = value.dataType;
+        dest[STYLE_DATA] = value.data;
+        dest[STYLE_ASSET_COOKIE] = (jint)res.getTableCookie(block);
+        dest[STYLE_RESOURCE_ID] = resid;
+        dest[STYLE_CHANGING_CONFIGURATIONS] = typeSetFlags;
+        dest += STYLE_NUM_ENTRIES;
+        i+= STYLE_NUM_ENTRIES;
+        arrayEnt++;
+    }
+    
+    i /= STYLE_NUM_ENTRIES;
+    
+    res.unlock();
+    
+    env->ReleasePrimitiveArrayCritical(outValues, baseDest, 0);
+    
+    return i;
+}
+
+static jint android_content_AssetManager_openXmlAssetNative(JNIEnv* env, jobject clazz,
+                                                         jint cookie,
+                                                         jstring fileName)
+{
+    AssetManager* am = assetManagerForJavaObject(env, clazz);
+    if (am == NULL) {
+        return 0;
+    }
+
+    LOGV("openXmlAsset in %p (Java object %p)\n", am, clazz);
+
+    if (fileName == NULL || am == NULL) {
+        doThrow(env, "java/lang/NullPointerException");
+        return 0;
+    }
+
+    const char* fileName8 = env->GetStringUTFChars(fileName, NULL);
+    Asset* a = cookie
+        ? am->openNonAsset((void*)cookie, fileName8, Asset::ACCESS_BUFFER)
+        : am->openNonAsset(fileName8, Asset::ACCESS_BUFFER);
+
+    if (a == NULL) {
+        doThrow(env, "java/io/FileNotFoundException", fileName8);
+        env->ReleaseStringUTFChars(fileName, fileName8);
+        return 0;
+    }
+    env->ReleaseStringUTFChars(fileName, fileName8);
+
+    ResXMLTree* block = new ResXMLTree();
+    status_t err = block->setTo(a->getBuffer(true), a->getLength(), true);
+    a->close();
+    delete a;
+
+    if (err != NO_ERROR) {
+        doThrow(env, "java/io/FileNotFoundException", "Corrupt XML binary file");
+        return 0;
+    }
+
+    return (jint)block;
+}
+
+static jintArray android_content_AssetManager_getArrayStringInfo(JNIEnv* env, jobject clazz,
+                                                                 jint arrayResId)
+{
+    AssetManager* am = assetManagerForJavaObject(env, clazz);
+    if (am == NULL) {
+        return NULL;
+    }
+    const ResTable& res(am->getResources());
+
+    const ResTable::bag_entry* startOfBag;
+    const ssize_t N = res.lockBag(arrayResId, &startOfBag);
+    if (N < 0) {
+        return NULL;
+    }
+
+    jintArray array = env->NewIntArray(N * 2);
+    if (array == NULL) {
+        doThrow(env, "java/lang/OutOfMemoryError");
+        res.unlockBag(startOfBag);
+        return NULL;
+    }
+
+    Res_value value;
+    const ResTable::bag_entry* bag = startOfBag;
+    for (size_t i = 0, j = 0; ((ssize_t)i)<N; i++, bag++) {
+        jint stringIndex = -1;
+        jint stringBlock = 0;
+        value = bag->map.value;
+        
+        // Take care of resolving the found resource to its final value.
+        stringBlock = res.resolveReference(&value, bag->stringBlock, NULL);
+        if (value.dataType == Res_value::TYPE_STRING) {
+            stringIndex = value.data;
+        }
+        
+        //todo: It might be faster to allocate a C array to contain
+        //      the blocknums and indices, put them in there and then
+        //      do just one SetIntArrayRegion()
+        env->SetIntArrayRegion(array, j, 1, &stringBlock);
+        env->SetIntArrayRegion(array, j + 1, 1, &stringIndex);
+        j = j + 2;
+    }
+    res.unlockBag(startOfBag);
+    return array;
+}
+
+static jobjectArray android_content_AssetManager_getArrayStringResource(JNIEnv* env, jobject clazz,
+                                                                        jint arrayResId)
+{
+    AssetManager* am = assetManagerForJavaObject(env, clazz);
+    if (am == NULL) {
+        return NULL;
+    }
+    const ResTable& res(am->getResources());
+
+    jclass cls = env->FindClass("java/lang/String");
+    LOG_FATAL_IF(cls == NULL, "No string class?!?");
+    if (cls == NULL) {
+        return NULL;
+    }
+
+    const ResTable::bag_entry* startOfBag;
+    const ssize_t N = res.lockBag(arrayResId, &startOfBag);
+    if (N < 0) {
+        return NULL;
+    }
+
+    jobjectArray array = env->NewObjectArray(N, cls, NULL);
+    if (array == NULL) {
+        doThrow(env, "java/lang/OutOfMemoryError");
+        res.unlockBag(startOfBag);
+        return NULL;
+    }
+
+    Res_value value;
+    const ResTable::bag_entry* bag = startOfBag;
+    size_t strLen = 0;
+    for (size_t i=0; ((ssize_t)i)<N; i++, bag++) {
+        value = bag->map.value;
+        jstring str = NULL;
+        
+        // Take care of resolving the found resource to its final value.
+        ssize_t block = res.resolveReference(&value, bag->stringBlock, NULL);
+        if (value.dataType == Res_value::TYPE_STRING) {
+            const char16_t* str16 = res.getTableStringBlock(block)->stringAt(value.data, &strLen);
+            str = env->NewString(str16, strLen);
+            if (str == NULL) {
+                doThrow(env, "java/lang/OutOfMemoryError");
+                res.unlockBag(startOfBag);
+                return NULL;
+            }
+        }
+        
+        env->SetObjectArrayElement(array, i, str);
+    }
+    res.unlockBag(startOfBag);
+    return array;
+}
+
+static jintArray android_content_AssetManager_getArrayIntResource(JNIEnv* env, jobject clazz,
+                                                                        jint arrayResId)
+{
+    AssetManager* am = assetManagerForJavaObject(env, clazz);
+    if (am == NULL) {
+        return NULL;
+    }
+    const ResTable& res(am->getResources());
+
+    const ResTable::bag_entry* startOfBag;
+    const ssize_t N = res.lockBag(arrayResId, &startOfBag);
+    if (N < 0) {
+        return NULL;
+    }
+
+    jintArray array = env->NewIntArray(N);
+    if (array == NULL) {
+        doThrow(env, "java/lang/OutOfMemoryError");
+        res.unlockBag(startOfBag);
+        return NULL;
+    }
+
+    Res_value value;
+    const ResTable::bag_entry* bag = startOfBag;
+    for (size_t i=0; ((ssize_t)i)<N; i++, bag++) {
+        value = bag->map.value;
+        
+        // Take care of resolving the found resource to its final value.
+        ssize_t block = res.resolveReference(&value, bag->stringBlock, NULL);
+        if (value.dataType >= Res_value::TYPE_FIRST_INT
+                && value.dataType <= Res_value::TYPE_LAST_INT) {
+            int intVal = value.data;
+            env->SetIntArrayRegion(array, i, 1, &intVal);
+        }
+    }
+    res.unlockBag(startOfBag);
+    return array;
+}
+
+static void android_content_AssetManager_init(JNIEnv* env, jobject clazz)
+{
+    AssetManager* am = new AssetManager();
+    if (am == NULL) {
+        doThrow(env, "java/lang/OutOfMemoryError");
+        return;
+    }
+
+    am->addDefaultAssets();
+
+    LOGV("Created AssetManager %p for Java object %p\n", am, clazz);
+    env->SetIntField(clazz, gAssetManagerOffsets.mObject, (jint)am);
+}
+
+static void android_content_AssetManager_destroy(JNIEnv* env, jobject clazz)
+{
+    AssetManager* am = (AssetManager*)
+        (env->GetIntField(clazz, gAssetManagerOffsets.mObject));
+    LOGV("Destroying AssetManager %p for Java object %p\n", am, clazz);
+    if (am != NULL) {
+        delete am;
+        env->SetIntField(clazz, gAssetManagerOffsets.mObject, 0);
+    }
+}
+
+static jint android_content_AssetManager_getGlobalAssetCount(JNIEnv* env, jobject clazz)
+{
+    return Asset::getGlobalCount();
+}
+
+static jint android_content_AssetManager_getGlobalAssetManagerCount(JNIEnv* env, jobject clazz)
+{
+    return AssetManager::getGlobalCount();
+}
+
+// ----------------------------------------------------------------------------
+
+/*
+ * JNI registration.
+ */
+static JNINativeMethod gAssetManagerMethods[] = {
+    /* name, signature, funcPtr */
+
+    // Basic asset stuff.
+    { "openAsset",      "(Ljava/lang/String;I)I",
+        (void*) android_content_AssetManager_openAsset },
+    { "openAssetFd",      "(Ljava/lang/String;[J)Landroid/os/ParcelFileDescriptor;",
+        (void*) android_content_AssetManager_openAssetFd },
+    { "openNonAssetNative", "(ILjava/lang/String;I)I",
+        (void*) android_content_AssetManager_openNonAssetNative },
+    { "openNonAssetFdNative", "(ILjava/lang/String;[J)Landroid/os/ParcelFileDescriptor;",
+        (void*) android_content_AssetManager_openNonAssetFdNative },
+    { "list",           "(Ljava/lang/String;)[Ljava/lang/String;",
+        (void*) android_content_AssetManager_list },
+    { "destroyAsset",   "(I)V",
+        (void*) android_content_AssetManager_destroyAsset },
+    { "readAssetChar",  "(I)I",
+        (void*) android_content_AssetManager_readAssetChar },
+    { "readAsset",      "(I[BII)I",
+        (void*) android_content_AssetManager_readAsset },
+    { "seekAsset",      "(IJI)J",
+        (void*) android_content_AssetManager_seekAsset },
+    { "getAssetLength", "(I)J",
+        (void*) android_content_AssetManager_getAssetLength },
+    { "getAssetRemainingLength", "(I)J",
+        (void*) android_content_AssetManager_getAssetRemainingLength },
+    { "addAssetPath",   "(Ljava/lang/String;)I",
+        (void*) android_content_AssetManager_addAssetPath },
+    { "isUpToDate",     "()Z",
+        (void*) android_content_AssetManager_isUpToDate },
+
+    // Resources.
+    { "setLocale",      "(Ljava/lang/String;)V",
+        (void*) android_content_AssetManager_setLocale },
+    { "getLocales",      "()[Ljava/lang/String;",
+        (void*) android_content_AssetManager_getLocales },
+    { "setConfiguration", "(IILjava/lang/String;IIIIIIIII)V",
+        (void*) android_content_AssetManager_setConfiguration },
+    { "getResourceIdentifier","(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)I",
+        (void*) android_content_AssetManager_getResourceIdentifier },
+    { "getResourceName","(I)Ljava/lang/String;",
+        (void*) android_content_AssetManager_getResourceName },
+    { "getResourcePackageName","(I)Ljava/lang/String;",
+        (void*) android_content_AssetManager_getResourcePackageName },
+    { "getResourceTypeName","(I)Ljava/lang/String;",
+        (void*) android_content_AssetManager_getResourceTypeName },
+    { "getResourceEntryName","(I)Ljava/lang/String;",
+        (void*) android_content_AssetManager_getResourceEntryName },
+    { "loadResourceValue","(ILandroid/util/TypedValue;Z)I",
+        (void*) android_content_AssetManager_loadResourceValue },
+    { "loadResourceBagValue","(IILandroid/util/TypedValue;Z)I",
+        (void*) android_content_AssetManager_loadResourceBagValue },
+    { "getStringBlockCount","()I",
+        (void*) android_content_AssetManager_getStringBlockCount },
+    { "getNativeStringBlock","(I)I",
+        (void*) android_content_AssetManager_getNativeStringBlock },
+    { "getCookieName","(I)Ljava/lang/String;",
+        (void*) android_content_AssetManager_getCookieName },
+
+    // Themes.
+    { "newTheme", "()I",
+        (void*) android_content_AssetManager_newTheme },
+    { "deleteTheme", "(I)V",
+        (void*) android_content_AssetManager_deleteTheme },
+    { "applyThemeStyle", "(IIZ)V",
+        (void*) android_content_AssetManager_applyThemeStyle },
+    { "copyTheme", "(II)V",
+        (void*) android_content_AssetManager_copyTheme },
+    { "loadThemeAttributeValue", "(IILandroid/util/TypedValue;Z)I",
+        (void*) android_content_AssetManager_loadThemeAttributeValue },
+    { "dumpTheme", "(IILjava/lang/String;Ljava/lang/String;)V",
+        (void*) android_content_AssetManager_dumpTheme },
+    { "applyStyle","(IIII[I[I[I)Z",
+        (void*) android_content_AssetManager_applyStyle },
+    { "retrieveAttributes","(I[I[I[I)Z",
+        (void*) android_content_AssetManager_retrieveAttributes },
+    { "getArraySize","(I)I",
+        (void*) android_content_AssetManager_getArraySize },
+    { "retrieveArray","(I[I)I",
+        (void*) android_content_AssetManager_retrieveArray },
+
+    // XML files.
+    { "openXmlAssetNative", "(ILjava/lang/String;)I",
+        (void*) android_content_AssetManager_openXmlAssetNative },
+
+    // Arrays.
+    { "getArrayStringResource","(I)[Ljava/lang/String;",
+        (void*) android_content_AssetManager_getArrayStringResource },
+    { "getArrayStringInfo","(I)[I",
+        (void*) android_content_AssetManager_getArrayStringInfo },
+    { "getArrayIntResource","(I)[I",
+        (void*) android_content_AssetManager_getArrayIntResource },
+
+    // Bookkeeping.
+    { "init",           "()V",
+        (void*) android_content_AssetManager_init },
+    { "destroy",        "()V",
+        (void*) android_content_AssetManager_destroy },
+    { "getGlobalAssetCount", "()I",
+        (void*) android_content_AssetManager_getGlobalAssetCount },
+    { "getGlobalAssetManagerCount", "()I",
+        (void*) android_content_AssetManager_getGlobalAssetCount },
+};
+
+int register_android_content_AssetManager(JNIEnv* env)
+{
+    jclass typedValue = env->FindClass("android/util/TypedValue");
+    LOG_FATAL_IF(typedValue == NULL, "Unable to find class android/util/TypedValue");
+    gTypedValueOffsets.mType
+        = env->GetFieldID(typedValue, "type", "I");
+    LOG_FATAL_IF(gTypedValueOffsets.mType == NULL, "Unable to find TypedValue.type");
+    gTypedValueOffsets.mData
+        = env->GetFieldID(typedValue, "data", "I");
+    LOG_FATAL_IF(gTypedValueOffsets.mData == NULL, "Unable to find TypedValue.data");
+    gTypedValueOffsets.mString
+        = env->GetFieldID(typedValue, "string", "Ljava/lang/CharSequence;");
+    LOG_FATAL_IF(gTypedValueOffsets.mString == NULL, "Unable to find TypedValue.string");
+    gTypedValueOffsets.mAssetCookie
+        = env->GetFieldID(typedValue, "assetCookie", "I");
+    LOG_FATAL_IF(gTypedValueOffsets.mAssetCookie == NULL, "Unable to find TypedValue.assetCookie");
+    gTypedValueOffsets.mResourceId
+        = env->GetFieldID(typedValue, "resourceId", "I");
+    LOG_FATAL_IF(gTypedValueOffsets.mResourceId == NULL, "Unable to find TypedValue.resourceId");
+    gTypedValueOffsets.mChangingConfigurations
+        = env->GetFieldID(typedValue, "changingConfigurations", "I");
+    LOG_FATAL_IF(gTypedValueOffsets.mChangingConfigurations == NULL, "Unable to find TypedValue.changingConfigurations");
+    gTypedValueOffsets.mDensity = env->GetFieldID(typedValue, "density", "I");
+    LOG_FATAL_IF(gTypedValueOffsets.mDensity == NULL, "Unable to find TypedValue.density");
+
+    jclass assetFd = env->FindClass("android/content/res/AssetFileDescriptor");
+    LOG_FATAL_IF(assetFd == NULL, "Unable to find class android/content/res/AssetFileDescriptor");
+    gAssetFileDescriptorOffsets.mFd
+        = env->GetFieldID(assetFd, "mFd", "Landroid/os/ParcelFileDescriptor;");
+    LOG_FATAL_IF(gAssetFileDescriptorOffsets.mFd == NULL, "Unable to find AssetFileDescriptor.mFd");
+    gAssetFileDescriptorOffsets.mStartOffset
+        = env->GetFieldID(assetFd, "mStartOffset", "J");
+    LOG_FATAL_IF(gAssetFileDescriptorOffsets.mStartOffset == NULL, "Unable to find AssetFileDescriptor.mStartOffset");
+    gAssetFileDescriptorOffsets.mLength
+        = env->GetFieldID(assetFd, "mLength", "J");
+    LOG_FATAL_IF(gAssetFileDescriptorOffsets.mLength == NULL, "Unable to find AssetFileDescriptor.mLength");
+
+    jclass assetManager = env->FindClass("android/content/res/AssetManager");
+    LOG_FATAL_IF(assetManager == NULL, "Unable to find class android/content/res/AssetManager");
+    gAssetManagerOffsets.mObject
+        = env->GetFieldID(assetManager, "mObject", "I");
+    LOG_FATAL_IF(gAssetManagerOffsets.mObject == NULL, "Unable to find AssetManager.mObject");
+
+    g_stringClass = env->FindClass("java/lang/String");
+
+    return AndroidRuntime::registerNativeMethods(env,
+            "android/content/res/AssetManager", gAssetManagerMethods, NELEM(gAssetManagerMethods));
+}
+
+}; // namespace android
diff --git a/core/jni/android_util_Base64.cpp b/core/jni/android_util_Base64.cpp
new file mode 100644
index 0000000..bc69747
--- /dev/null
+++ b/core/jni/android_util_Base64.cpp
@@ -0,0 +1,160 @@
+/* //device/libs/android_runtime/android_util_Base64.cpp
+**
+** Copyright 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.
+*/
+
+/*********************************************************
+*
+* This code was copied from
+* system/extra/ssh/dropbear-0.49/libtomcrypt/src/misc/base64/base64_decode.c
+*
+*********************************************************/
+
+#define LOG_TAG "Base64"
+
+#include <utils/Log.h>
+
+#include <android_runtime/AndroidRuntime.h>
+
+#include "JNIHelp.h"
+
+#include <sys/errno.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <fcntl.h>
+#include <signal.h>
+
+namespace android {
+
+static const unsigned char map[256] = {
+255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+255, 255, 255, 255, 255, 255, 255,  62, 255, 255, 255,  63,
+ 52,  53,  54,  55,  56,  57,  58,  59,  60,  61, 255, 255,
+255, 254, 255, 255, 255,   0,   1,   2,   3,   4,   5,   6,
+  7,   8,   9,  10,  11,  12,  13,  14,  15,  16,  17,  18,
+ 19,  20,  21,  22,  23,  24,  25, 255, 255, 255, 255, 255,
+255,  26,  27,  28,  29,  30,  31,  32,  33,  34,  35,  36,
+ 37,  38,  39,  40,  41,  42,  43,  44,  45,  46,  47,  48,
+ 49,  50,  51, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+255, 255, 255, 255 };
+
+/**
+   base64 decode a block of memory
+   @param in       The base64 data to decode
+   @param inlen    The length of the base64 data
+   @param out      [out] The destination of the binary decoded data
+   @param outlen   [in/out] The max size and resulting size of the decoded data
+   @return 0 if successful
+*/
+int base64_decode(const unsigned char *in,  unsigned long inlen, 
+                        unsigned char *out, unsigned long *outlen)
+{
+   unsigned long t, x, y, z;
+   unsigned char c;
+   int           g;
+
+   g = 3;
+   for (x = y = z = t = 0; x < inlen; x++) {
+       c = map[in[x]&0xFF];
+       if (c == 255) continue;
+       /* the final = symbols are read and used to trim the remaining bytes */
+       if (c == 254) { 
+          c = 0; 
+          /* prevent g < 0 which would potentially allow an overflow later */
+          if (--g < 0) {
+             return -3;
+          }
+       } else if (g != 3) {
+          /* we only allow = to be at the end */
+          return -4;
+       }
+
+       t = (t<<6)|c;
+
+       if (++y == 4) {
+          if (z + g > *outlen) { 
+             return -2; 
+          }
+          out[z++] = (unsigned char)((t>>16)&255);
+          if (g > 1) out[z++] = (unsigned char)((t>>8)&255);
+          if (g > 2) out[z++] = (unsigned char)(t&255);
+          y = t = 0;
+       }
+   }
+   if (y != 0) {
+       return -5;
+   }
+   *outlen = z;
+   return 0;
+}
+
+static jbyteArray decodeBase64(JNIEnv *env, jobject jobj, jstring jdata)
+{
+    const char * rawData = env->GetStringUTFChars(jdata, NULL);
+    int stringLength = env->GetStringUTFLength(jdata);
+
+    int resultLength = stringLength / 4 * 3;
+    if (rawData[stringLength-1] == '=') {
+        resultLength -= 1;
+        if (rawData[stringLength-2] == '=') {
+            resultLength -= 1;
+        }
+    }
+
+    jbyteArray byteArray = env->NewByteArray(resultLength);
+    jbyte* byteArrayData = env->GetByteArrayElements(byteArray, NULL);
+
+    unsigned long outlen = resultLength;
+    int result = base64_decode((const unsigned char*)rawData, stringLength, (unsigned char *)byteArrayData, &outlen);
+    if (result != 0)
+        memset((unsigned char *)byteArrayData, -result, resultLength);
+    
+    env->ReleaseStringUTFChars(jdata, rawData);
+    env->ReleaseByteArrayElements(byteArray, byteArrayData, 0);
+
+    return byteArray;
+}
+
+static const JNINativeMethod methods[] = {
+  {"decodeBase64Native", "(Ljava/lang/String;)[B", (void*)decodeBase64 }
+};
+
+static const char* const kBase64PathName = "android/os/Base64Utils";
+
+int register_android_util_Base64(JNIEnv* env)
+{
+    jclass clazz;
+
+    clazz = env->FindClass(kBase64PathName);
+    LOG_FATAL_IF(clazz == NULL, "Unable to find class android.os.Base64Utils");
+
+    return AndroidRuntime::registerNativeMethods(
+        env, kBase64PathName,
+        methods, NELEM(methods));
+}
+
+}
diff --git a/core/jni/android_util_Binder.cpp b/core/jni/android_util_Binder.cpp
new file mode 100644
index 0000000..7325432
--- /dev/null
+++ b/core/jni/android_util_Binder.cpp
@@ -0,0 +1,1511 @@
+/*
+ * 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.
+ */
+
+#define LOG_TAG "JavaBinder"
+//#define LOG_NDEBUG 0
+
+#include "android_util_Binder.h"
+#include "JNIHelp.h"
+
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <stdio.h>
+
+#include <utils/Atomic.h>
+#include <utils/IInterface.h>
+#include <utils/IPCThreadState.h>
+#include <utils/Log.h>
+#include <utils/Parcel.h>
+#include <utils/ProcessState.h>
+#include <utils/IServiceManager.h>
+
+#include <android_runtime/AndroidRuntime.h>
+
+//#undef LOGV
+//#define LOGV(...) fprintf(stderr, __VA_ARGS__)
+
+using namespace android;
+
+// ----------------------------------------------------------------------------
+
+static struct bindernative_offsets_t
+{
+    // Class state.
+    jclass mClass;
+    jmethodID mExecTransact;
+
+    // Object state.
+    jfieldID mObject;
+
+} gBinderOffsets;
+
+// ----------------------------------------------------------------------------
+
+static struct binderinternal_offsets_t
+{
+    // Class state.
+    jclass mClass;
+    jmethodID mForceGc;
+
+} gBinderInternalOffsets;
+
+// ----------------------------------------------------------------------------
+
+static struct debug_offsets_t
+{
+    // Class state.
+    jclass mClass;
+
+} gDebugOffsets;
+
+// ----------------------------------------------------------------------------
+
+static struct weakreference_offsets_t
+{
+    // Class state.
+    jclass mClass;
+    jmethodID mGet;
+
+} gWeakReferenceOffsets;
+
+static struct error_offsets_t
+{
+    jclass mClass;
+} gErrorOffsets;
+
+// ----------------------------------------------------------------------------
+
+static struct binderproxy_offsets_t
+{
+    // Class state.
+    jclass mClass;
+    jmethodID mConstructor;
+    jmethodID mSendDeathNotice;
+
+    // Object state.
+    jfieldID mObject;
+    jfieldID mSelf;
+
+} gBinderProxyOffsets;
+
+// ----------------------------------------------------------------------------
+
+static struct parcel_offsets_t
+{
+    jfieldID mObject;
+    jfieldID mOwnObject;
+} gParcelOffsets;
+
+static struct log_offsets_t
+{
+    // Class state.
+    jclass mClass;
+    jmethodID mLogE;
+} gLogOffsets;
+
+static struct file_descriptor_offsets_t
+{
+    jclass mClass;
+    jmethodID mConstructor;
+    jfieldID mDescriptor;
+} gFileDescriptorOffsets;
+
+static struct parcel_file_descriptor_offsets_t
+{
+    jclass mClass;
+    jmethodID mConstructor;
+} gParcelFileDescriptorOffsets;
+
+// ****************************************************************************
+// ****************************************************************************
+// ****************************************************************************
+
+static volatile int32_t gNumRefsCreated = 0;
+static volatile int32_t gNumProxyRefs = 0;
+static volatile int32_t gNumLocalRefs = 0;
+static volatile int32_t gNumDeathRefs = 0;
+
+static void incRefsCreated(JNIEnv* env)
+{
+    int old = android_atomic_inc(&gNumRefsCreated);
+    if (old == 200) {
+        android_atomic_and(0, &gNumRefsCreated);
+        env->CallStaticVoidMethod(gBinderInternalOffsets.mClass,
+                gBinderInternalOffsets.mForceGc);
+    } else {
+        LOGV("Now have %d binder ops", old);
+    }
+}
+
+static JavaVM* jnienv_to_javavm(JNIEnv* env)
+{
+    JavaVM* vm;
+    return env->GetJavaVM(&vm) >= 0 ? vm : NULL;
+}
+
+static JNIEnv* javavm_to_jnienv(JavaVM* vm)
+{
+    JNIEnv* env;
+    return vm->GetEnv((void **)&env, JNI_VERSION_1_4) >= 0 ? env : NULL;
+}
+
+static void report_exception(JNIEnv* env, jthrowable excep, const char* msg)
+{
+    env->ExceptionClear();
+
+    jstring tagstr = env->NewStringUTF(LOG_TAG);
+    jstring msgstr = env->NewStringUTF(msg);
+
+    if ((tagstr == NULL) || (msgstr == NULL)) {
+        env->ExceptionClear();      /* assume exception (OOM?) was thrown */
+        LOGE("Unable to call Log.e()\n");
+        LOGE("%s", msg);
+        goto bail;
+    }
+
+    env->CallStaticIntMethod(
+        gLogOffsets.mClass, gLogOffsets.mLogE, tagstr, msgstr, excep);
+    if (env->ExceptionCheck()) {
+        /* attempting to log the failure has failed */
+        LOGW("Failed trying to log exception, msg='%s'\n", msg);
+        env->ExceptionClear();
+    }
+
+    if (env->IsInstanceOf(excep, gErrorOffsets.mClass)) {
+        /*
+         * It's an Error: Reraise the exception, detach this thread, and
+         * wait for the fireworks. Die even more blatantly after a minute
+         * if the gentler attempt doesn't do the trick.
+         *
+         * The GetJavaVM function isn't on the "approved" list of JNI calls
+         * that can be made while an exception is pending, so we want to
+         * get the VM ptr, throw the exception, and then detach the thread.
+         */
+        JavaVM* vm = jnienv_to_javavm(env);
+        env->Throw(excep);
+        vm->DetachCurrentThread();
+        sleep(60);
+        LOGE("Forcefully exiting");
+        exit(1);
+        *((int *) 1) = 1;
+    }
+
+bail:
+    /* discard local refs created for us by VM */
+    env->DeleteLocalRef(tagstr);
+    env->DeleteLocalRef(msgstr);
+}
+
+class JavaBBinderHolder;
+
+class JavaBBinder : public BBinder
+{
+public:
+    JavaBBinder(JNIEnv* env, jobject object)
+        : mVM(jnienv_to_javavm(env)), mObject(env->NewGlobalRef(object))
+    {
+        LOGV("Creating JavaBBinder %p\n", this);
+        android_atomic_inc(&gNumLocalRefs);
+        incRefsCreated(env);
+    }
+
+    bool    checkSubclass(const void* subclassID) const
+    {
+        return subclassID == &gBinderOffsets;
+    }
+
+    jobject object() const
+    {
+        return mObject;
+    }
+
+protected:
+    virtual ~JavaBBinder()
+    {
+        LOGV("Destroying JavaBBinder %p\n", this);
+        android_atomic_dec(&gNumLocalRefs);
+        JNIEnv* env = javavm_to_jnienv(mVM);
+        env->DeleteGlobalRef(mObject);
+    }
+
+    virtual status_t onTransact(
+        uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags = 0)
+    {
+        JNIEnv* env = javavm_to_jnienv(mVM);
+
+        LOGV("onTransact() on %p calling object %p in env %p vm %p\n", this, mObject, env, mVM);
+
+        //printf("Transact from %p to Java code sending: ", this);
+        //data.print();
+        //printf("\n");
+        jboolean res = env->CallBooleanMethod(mObject, gBinderOffsets.mExecTransact,
+            code, (int32_t)&data, (int32_t)reply, flags);
+        jthrowable excep = env->ExceptionOccurred();
+        if (excep) {
+            report_exception(env, excep,
+                "*** Uncaught remote exception!  "
+                "(Exceptions are not yet supported across processes.)");
+            res = JNI_FALSE;
+
+            /* clean up JNI local ref -- we don't return to Java code */
+            env->DeleteLocalRef(excep);
+        }
+
+        //aout << "onTransact to Java code; result=" << res << endl
+        //    << "Transact from " << this << " to Java code returning "
+        //    << reply << ": " << *reply << endl;
+        return res != JNI_FALSE ? NO_ERROR : UNKNOWN_TRANSACTION;
+    }
+
+    virtual status_t dump(int fd, const Vector<String16>& args)
+    {
+        return 0;
+    }
+
+private:
+    JavaVM* const   mVM;
+    jobject const   mObject;
+};
+
+// ----------------------------------------------------------------------------
+
+class JavaBBinderHolder : public RefBase
+{
+public:
+    JavaBBinderHolder(JNIEnv* env, jobject object)
+        : mObject(object)
+    {
+        LOGV("Creating JavaBBinderHolder for Object %p\n", object);
+    }
+    ~JavaBBinderHolder()
+    {
+        LOGV("Destroying JavaBBinderHolder for Object %p\n", mObject);
+    }
+
+    sp<JavaBBinder> get(JNIEnv* env)
+    {
+        AutoMutex _l(mLock);
+        sp<JavaBBinder> b = mBinder.promote();
+        if (b == NULL) {
+            b = new JavaBBinder(env, mObject);
+            mBinder = b;
+            LOGV("Creating JavaBinder %p (refs %p) for Object %p, weakCount=%d\n",
+                 b.get(), b->getWeakRefs(), mObject, b->getWeakRefs()->getWeakCount());
+        }
+
+        return b;
+    }
+
+    sp<JavaBBinder> getExisting()
+    {
+        AutoMutex _l(mLock);
+        return mBinder.promote();
+    }
+
+private:
+    Mutex           mLock;
+    jobject         mObject;
+    wp<JavaBBinder> mBinder;
+};
+
+// ----------------------------------------------------------------------------
+
+class JavaDeathRecipient : public IBinder::DeathRecipient
+{
+public:
+    JavaDeathRecipient(JNIEnv* env, jobject object)
+        : mVM(jnienv_to_javavm(env)), mObject(env->NewGlobalRef(object)),
+          mHoldsRef(true)
+    {
+        incStrong(this);
+        android_atomic_inc(&gNumDeathRefs);
+        incRefsCreated(env);
+    }
+
+    void binderDied(const wp<IBinder>& who)
+    {
+        JNIEnv* env = javavm_to_jnienv(mVM);
+
+        LOGV("Receiving binderDied() on JavaDeathRecipient %p\n", this);
+
+        env->CallStaticVoidMethod(gBinderProxyOffsets.mClass,
+            gBinderProxyOffsets.mSendDeathNotice, mObject);
+        jthrowable excep = env->ExceptionOccurred();
+        if (excep) {
+            report_exception(env, excep,
+                "*** Uncaught exception returned from death notification!");
+        }
+
+        clearReference();
+    }
+
+    void clearReference()
+    {
+        bool release = false;
+        mLock.lock();
+        if (mHoldsRef) {
+            mHoldsRef = false;
+            release = true;
+        }
+        mLock.unlock();
+        if (release) {
+            decStrong(this);
+        }
+    }
+
+protected:
+    virtual ~JavaDeathRecipient()
+    {
+        //LOGI("Removing death ref: recipient=%p\n", mObject);
+        android_atomic_dec(&gNumDeathRefs);
+        JNIEnv* env = javavm_to_jnienv(mVM);
+        env->DeleteGlobalRef(mObject);
+    }
+
+private:
+    JavaVM* const   mVM;
+    jobject const   mObject;
+    Mutex           mLock;
+    bool            mHoldsRef;
+};
+
+// ----------------------------------------------------------------------------
+
+namespace android {
+
+static void proxy_cleanup(const void* id, void* obj, void* cleanupCookie)
+{
+    android_atomic_dec(&gNumProxyRefs);
+    JNIEnv* env = javavm_to_jnienv((JavaVM*)cleanupCookie);
+    env->DeleteGlobalRef((jobject)obj);
+}
+
+static Mutex mProxyLock;
+
+jobject javaObjectForIBinder(JNIEnv* env, const sp<IBinder>& val)
+{
+    if (val == NULL) return NULL;
+
+    if (val->checkSubclass(&gBinderOffsets)) {
+        // One of our own!
+        jobject object = static_cast<JavaBBinder*>(val.get())->object();
+        //printf("objectForBinder %p: it's our own %p!\n", val.get(), object);
+        return object;
+    }
+
+    // For the rest of the function we will hold this lock, to serialize
+    // looking/creation of Java proxies for native Binder proxies.
+    AutoMutex _l(mProxyLock);
+
+    // Someone else's...  do we know about it?
+    jobject object = (jobject)val->findObject(&gBinderProxyOffsets);
+    if (object != NULL) {
+        jobject res = env->CallObjectMethod(object, gWeakReferenceOffsets.mGet);
+        if (res != NULL) {
+            LOGV("objectForBinder %p: found existing %p!\n", val.get(), res);
+            return res;
+        }
+        LOGV("Proxy object %p of IBinder %p no longer in working set!!!", object, val.get());
+        android_atomic_dec(&gNumProxyRefs);
+        val->detachObject(&gBinderProxyOffsets);
+        env->DeleteGlobalRef(object);
+    }
+
+    object = env->NewObject(gBinderProxyOffsets.mClass, gBinderProxyOffsets.mConstructor);
+    if (object != NULL) {
+        LOGV("objectForBinder %p: created new %p!\n", val.get(), object);
+        // The proxy holds a reference to the native object.
+        env->SetIntField(object, gBinderProxyOffsets.mObject, (int)val.get());
+        val->incStrong(object);
+
+        // The native object needs to hold a weak reference back to the
+        // proxy, so we can retrieve the same proxy if it is still active.
+        jobject refObject = env->NewGlobalRef(
+                env->GetObjectField(object, gBinderProxyOffsets.mSelf));
+        val->attachObject(&gBinderProxyOffsets, refObject,
+                jnienv_to_javavm(env), proxy_cleanup);
+
+        // Note that a new object reference has been created.
+        android_atomic_inc(&gNumProxyRefs);
+        incRefsCreated(env);
+    }
+
+    return object;
+}
+
+sp<IBinder> ibinderForJavaObject(JNIEnv* env, jobject obj)
+{
+    if (obj == NULL) return NULL;
+
+    if (env->IsInstanceOf(obj, gBinderOffsets.mClass)) {
+        JavaBBinderHolder* jbh = (JavaBBinderHolder*)
+            env->GetIntField(obj, gBinderOffsets.mObject);
+        return jbh != NULL ? jbh->get(env) : NULL;
+    }
+
+    if (env->IsInstanceOf(obj, gBinderProxyOffsets.mClass)) {
+        return (IBinder*)
+            env->GetIntField(obj, gBinderProxyOffsets.mObject);
+    }
+
+    LOGW("ibinderForJavaObject: %p is not a Binder object", obj);
+    return NULL;
+}
+
+Parcel* parcelForJavaObject(JNIEnv* env, jobject obj)
+{
+    if (obj) {
+        Parcel* p = (Parcel*)env->GetIntField(obj, gParcelOffsets.mObject);
+        if (p != NULL) {
+            return p;
+        }
+        jniThrowException(env, "java/lang/IllegalStateException", "Parcel has been finalized!");
+    }
+    return NULL;
+}
+
+jobject newFileDescriptor(JNIEnv* env, int fd)
+{
+    jobject object = env->NewObject(
+            gFileDescriptorOffsets.mClass, gFileDescriptorOffsets.mConstructor);
+    if (object != NULL) {
+        //LOGI("Created new FileDescriptor %p with fd %d\n", object, fd);
+        env->SetIntField(object, gFileDescriptorOffsets.mDescriptor, fd);
+    }
+    return object;
+}
+
+jobject newParcelFileDescriptor(JNIEnv* env, jobject fileDesc)
+{
+    return env->NewObject(
+            gParcelFileDescriptorOffsets.mClass, gParcelFileDescriptorOffsets.mConstructor, fileDesc);
+}
+
+void signalExceptionForError(JNIEnv* env, jobject obj, status_t err)
+{
+    switch (err) {
+        case UNKNOWN_ERROR:
+            jniThrowException(env, "java/lang/RuntimeException", "Unknown error");
+            break;
+        case NO_MEMORY:
+            jniThrowException(env, "java/lang/OutOfMemoryError", NULL);
+            break;
+        case INVALID_OPERATION:
+            jniThrowException(env, "java/lang/UnsupportedOperationException", NULL);
+            break;
+        case BAD_VALUE:
+            jniThrowException(env, "java/lang/IllegalArgumentException", NULL);
+            break;
+        case BAD_INDEX:
+            jniThrowException(env, "java/lang/IndexOutOfBoundsException", NULL);
+            break;
+        case BAD_TYPE:
+            jniThrowException(env, "java/lang/IllegalArgumentException", NULL);
+            break;
+        case NAME_NOT_FOUND:
+            jniThrowException(env, "java/util/NoSuchElementException", NULL);
+            break;
+        case PERMISSION_DENIED:
+            jniThrowException(env, "java/lang/SecurityException", NULL);
+            break;
+        case NOT_ENOUGH_DATA:
+            jniThrowException(env, "android/os/ParcelFormatException", "Not enough data");
+            break;
+        case NO_INIT:
+            jniThrowException(env, "java/lang/RuntimeException", "Not initialized");
+            break;
+        case ALREADY_EXISTS:
+            jniThrowException(env, "java/lang/RuntimeException", "Item already exists");
+            break;
+        case DEAD_OBJECT:
+            jniThrowException(env, "android/os/DeadObjectException", NULL);
+            break;
+        case UNKNOWN_TRANSACTION:
+            jniThrowException(env, "java/lang/RuntimeException", "Unknown transaction code");
+            break;
+        case FAILED_TRANSACTION:
+            LOGE("!!! FAILED BINDER TRANSACTION !!!");
+            //jniThrowException(env, "java/lang/OutOfMemoryError", "Binder transaction too large");
+            break;
+        default:
+            LOGE("Unknown binder error code. 0x%x", err);
+    }
+}
+
+}
+
+// ----------------------------------------------------------------------------
+
+static jint android_os_Binder_getCallingPid(JNIEnv* env, jobject clazz)
+{
+    return IPCThreadState::self()->getCallingPid();
+}
+
+static jint android_os_Binder_getCallingUid(JNIEnv* env, jobject clazz)
+{
+    return IPCThreadState::self()->getCallingUid();
+}
+
+static jlong android_os_Binder_clearCallingIdentity(JNIEnv* env, jobject clazz)
+{
+    return IPCThreadState::self()->clearCallingIdentity();
+}
+
+static void android_os_Binder_restoreCallingIdentity(JNIEnv* env, jobject clazz, jlong token)
+{
+    IPCThreadState::self()->restoreCallingIdentity(token);
+}
+
+static void android_os_Binder_flushPendingCommands(JNIEnv* env, jobject clazz)
+{
+    IPCThreadState::self()->flushCommands();
+}
+
+static void android_os_Binder_init(JNIEnv* env, jobject clazz)
+{
+    JavaBBinderHolder* jbh = new JavaBBinderHolder(env, clazz);
+    if (jbh == NULL) {
+        jniThrowException(env, "java/lang/OutOfMemoryError", NULL);
+        return;
+    }
+    LOGV("Java Binder %p: acquiring first ref on holder %p", clazz, jbh);
+    jbh->incStrong(clazz);
+    env->SetIntField(clazz, gBinderOffsets.mObject, (int)jbh);
+}
+
+static void android_os_Binder_destroy(JNIEnv* env, jobject clazz)
+{
+    JavaBBinderHolder* jbh = (JavaBBinderHolder*)
+        env->GetIntField(clazz, gBinderOffsets.mObject);
+    env->SetIntField(clazz, gBinderOffsets.mObject, 0);
+    LOGV("Java Binder %p: removing ref on holder %p", clazz, jbh);
+    jbh->decStrong(clazz);
+}
+
+// ----------------------------------------------------------------------------
+
+static const JNINativeMethod gBinderMethods[] = {
+     /* name, signature, funcPtr */
+    { "getCallingPid", "()I", (void*)android_os_Binder_getCallingPid },
+    { "getCallingUid", "()I", (void*)android_os_Binder_getCallingUid },
+    { "clearCallingIdentity", "()J", (void*)android_os_Binder_clearCallingIdentity },
+    { "restoreCallingIdentity", "(J)V", (void*)android_os_Binder_restoreCallingIdentity },
+    { "flushPendingCommands", "()V", (void*)android_os_Binder_flushPendingCommands },
+    { "init", "()V", (void*)android_os_Binder_init },
+    { "destroy", "()V", (void*)android_os_Binder_destroy }
+};
+
+const char* const kBinderPathName = "android/os/Binder";
+
+static int int_register_android_os_Binder(JNIEnv* env)
+{
+    jclass clazz;
+
+    clazz = env->FindClass(kBinderPathName);
+    LOG_FATAL_IF(clazz == NULL, "Unable to find class android.os.Binder");
+
+    gBinderOffsets.mClass = (jclass) env->NewGlobalRef(clazz);
+    gBinderOffsets.mExecTransact
+        = env->GetMethodID(clazz, "execTransact", "(IIII)Z");
+    assert(gBinderOffsets.mExecTransact);
+
+    gBinderOffsets.mObject
+        = env->GetFieldID(clazz, "mObject", "I");
+    assert(gBinderOffsets.mObject);
+
+    return AndroidRuntime::registerNativeMethods(
+        env, kBinderPathName,
+        gBinderMethods, NELEM(gBinderMethods));
+}
+
+// ****************************************************************************
+// ****************************************************************************
+// ****************************************************************************
+
+namespace android {
+
+jint android_os_Debug_getLocalObjectCount(JNIEnv* env, jobject clazz)
+{
+    return gNumLocalRefs;
+}
+
+jint android_os_Debug_getProxyObjectCount(JNIEnv* env, jobject clazz)
+{
+    return gNumProxyRefs;
+}
+
+jint android_os_Debug_getDeathObjectCount(JNIEnv* env, jobject clazz)
+{
+    return gNumDeathRefs;
+}
+
+}
+
+// ****************************************************************************
+// ****************************************************************************
+// ****************************************************************************
+
+static jobject android_os_BinderInternal_getContextObject(JNIEnv* env, jobject clazz)
+{
+    sp<IBinder> b = ProcessState::self()->getContextObject(NULL);
+    return javaObjectForIBinder(env, b);
+}
+
+static void android_os_BinderInternal_joinThreadPool(JNIEnv* env, jobject clazz)
+{
+    sp<IBinder> b = ProcessState::self()->getContextObject(NULL);
+    android::IPCThreadState::self()->joinThreadPool();
+}
+
+static void android_os_BinderInternal_handleGc(JNIEnv* env, jobject clazz)
+{
+    LOGV("Gc has executed, clearing binder ops");
+    android_atomic_and(0, &gNumRefsCreated);
+}
+
+// ----------------------------------------------------------------------------
+
+static const JNINativeMethod gBinderInternalMethods[] = {
+     /* name, signature, funcPtr */
+    { "getContextObject", "()Landroid/os/IBinder;", (void*)android_os_BinderInternal_getContextObject },
+    { "joinThreadPool", "()V", (void*)android_os_BinderInternal_joinThreadPool },
+    { "handleGc", "()V", (void*)android_os_BinderInternal_handleGc }
+};
+
+const char* const kBinderInternalPathName = "com/android/internal/os/BinderInternal";
+
+static int int_register_android_os_BinderInternal(JNIEnv* env)
+{
+    jclass clazz;
+
+    clazz = env->FindClass(kBinderInternalPathName);
+    LOG_FATAL_IF(clazz == NULL, "Unable to find class com.android.internal.os.BinderInternal");
+
+    gBinderInternalOffsets.mClass = (jclass) env->NewGlobalRef(clazz);
+    gBinderInternalOffsets.mForceGc
+        = env->GetStaticMethodID(clazz, "forceBinderGc", "()V");
+    assert(gBinderInternalOffsets.mForceGc);
+
+    return AndroidRuntime::registerNativeMethods(
+        env, kBinderInternalPathName,
+        gBinderInternalMethods, NELEM(gBinderInternalMethods));
+}
+
+// ****************************************************************************
+// ****************************************************************************
+// ****************************************************************************
+
+static jboolean android_os_BinderProxy_pingBinder(JNIEnv* env, jobject obj)
+{
+    IBinder* target = (IBinder*)
+        env->GetIntField(obj, gBinderProxyOffsets.mObject);
+    if (target == NULL) {
+        return JNI_FALSE;
+    }
+    status_t err = target->pingBinder();
+    return err == NO_ERROR ? JNI_TRUE : JNI_FALSE;
+}
+
+static jstring android_os_BinderProxy_getInterfaceDescriptor(JNIEnv* env, jobject obj)
+{
+    IBinder* target = (IBinder*) env->GetIntField(obj, gBinderProxyOffsets.mObject);
+    if (target != NULL) {
+        String16 desc = target->getInterfaceDescriptor();
+        return env->NewString(desc.string(), desc.size());
+    }
+    jniThrowException(env, "java/lang/RuntimeException",
+            "No binder found for object");
+    return NULL;
+}
+
+static jboolean android_os_BinderProxy_isBinderAlive(JNIEnv* env, jobject obj)
+{
+    IBinder* target = (IBinder*)
+        env->GetIntField(obj, gBinderProxyOffsets.mObject);
+    if (target == NULL) {
+        return JNI_FALSE;
+    }
+    bool alive = target->isBinderAlive();
+    return alive ? JNI_TRUE : JNI_FALSE;
+}
+
+static jboolean android_os_BinderProxy_transact(JNIEnv* env, jobject obj,
+                                                jint code, jobject dataObj,
+                                                jobject replyObj, jint flags)
+{
+    if (dataObj == NULL) {
+        jniThrowException(env, "java/lang/NullPointerException", NULL);
+        return JNI_FALSE;
+    }
+
+    Parcel* data = parcelForJavaObject(env, dataObj);
+    if (data == NULL) {
+        return JNI_FALSE;
+    }
+    Parcel* reply = parcelForJavaObject(env, replyObj);
+    if (reply == NULL && replyObj != NULL) {
+        return JNI_FALSE;
+    }
+
+    IBinder* target = (IBinder*)
+        env->GetIntField(obj, gBinderProxyOffsets.mObject);
+    if (target == NULL) {
+        jniThrowException(env, "java/lang/IllegalStateException", "Binder has been finalized!");
+        return JNI_FALSE;
+    }
+
+    LOGV("Java code calling transact on %p in Java object %p with code %d\n",
+            target, obj, code);
+    //printf("Transact from Java code to %p sending: ", target); data->print();
+    status_t err = target->transact(code, *data, reply, flags);
+    //if (reply) printf("Transact from Java code to %p received: ", target); reply->print();
+    if (err == NO_ERROR) {
+        return JNI_TRUE;
+    } else if (err == UNKNOWN_TRANSACTION) {
+        return JNI_FALSE;
+    }
+
+    signalExceptionForError(env, obj, err);
+    return JNI_FALSE;
+}
+
+static void android_os_BinderProxy_linkToDeath(JNIEnv* env, jobject obj,
+                                               jobject recipient, jint flags)
+{
+    if (recipient == NULL) {
+        jniThrowException(env, "java/lang/NullPointerException", NULL);
+        return;
+    }
+
+    IBinder* target = (IBinder*)
+        env->GetIntField(obj, gBinderProxyOffsets.mObject);
+    if (target == NULL) {
+        LOGW("Binder has been finalized when calling linkToDeath() with recip=%p)\n", recipient);
+        assert(false);
+    }
+
+    LOGV("linkToDeath: binder=%p recipient=%p\n", target, recipient);
+
+    if (!target->localBinder()) {
+        sp<JavaDeathRecipient> jdr = new JavaDeathRecipient(env, recipient);
+        status_t err = target->linkToDeath(jdr, recipient, flags);
+        if (err != NO_ERROR) {
+            // Failure adding the death recipient, so clear its reference
+            // now.
+            jdr->clearReference();
+            signalExceptionForError(env, obj, err);
+        }
+    }
+}
+
+static jboolean android_os_BinderProxy_unlinkToDeath(JNIEnv* env, jobject obj,
+                                                 jobject recipient, jint flags)
+{
+    jboolean res = JNI_FALSE;
+    if (recipient == NULL) {
+        jniThrowException(env, "java/lang/NullPointerException", NULL);
+        return res;
+    }
+
+    IBinder* target = (IBinder*)
+        env->GetIntField(obj, gBinderProxyOffsets.mObject);
+    if (target == NULL) {
+        LOGW("Binder has been finalized when calling linkToDeath() with recip=%p)\n", recipient);
+        return JNI_FALSE;
+    }
+
+    LOGV("unlinkToDeath: binder=%p recipient=%p\n", target, recipient);
+
+    if (!target->localBinder()) {
+        wp<IBinder::DeathRecipient> dr;
+        status_t err = target->unlinkToDeath(NULL, recipient, flags, &dr);
+        if (err == NO_ERROR && dr != NULL) {
+            sp<IBinder::DeathRecipient> sdr = dr.promote();
+            JavaDeathRecipient* jdr = static_cast<JavaDeathRecipient*>(sdr.get());
+            if (jdr != NULL) {
+                jdr->clearReference();
+            }
+        }
+        if (err == NO_ERROR || err == DEAD_OBJECT) {
+            res = JNI_TRUE;
+        } else {
+            jniThrowException(env, "java/util/NoSuchElementException",
+                              "Death link does not exist");
+        }
+    }
+
+    return res;
+}
+
+static void android_os_BinderProxy_destroy(JNIEnv* env, jobject obj)
+{
+    IBinder* b = (IBinder*)
+        env->GetIntField(obj, gBinderProxyOffsets.mObject);
+    LOGV("Destroying BinderProxy %p: binder=%p\n", obj, b);
+    env->SetIntField(obj, gBinderProxyOffsets.mObject, 0);
+    b->decStrong(obj);
+    IPCThreadState::self()->flushCommands();
+}
+
+// ----------------------------------------------------------------------------
+
+static const JNINativeMethod gBinderProxyMethods[] = {
+     /* name, signature, funcPtr */
+    {"pingBinder",          "()Z", (void*)android_os_BinderProxy_pingBinder},
+    {"isBinderAlive",       "()Z", (void*)android_os_BinderProxy_isBinderAlive},
+    {"getInterfaceDescriptor", "()Ljava/lang/String;", (void*)android_os_BinderProxy_getInterfaceDescriptor},
+    {"transact",            "(ILandroid/os/Parcel;Landroid/os/Parcel;I)Z", (void*)android_os_BinderProxy_transact},
+    {"linkToDeath",         "(Landroid/os/IBinder$DeathRecipient;I)V", (void*)android_os_BinderProxy_linkToDeath},
+    {"unlinkToDeath",       "(Landroid/os/IBinder$DeathRecipient;I)Z", (void*)android_os_BinderProxy_unlinkToDeath},
+    {"destroy",             "()V", (void*)android_os_BinderProxy_destroy},
+};
+
+const char* const kBinderProxyPathName = "android/os/BinderProxy";
+
+static int int_register_android_os_BinderProxy(JNIEnv* env)
+{
+    jclass clazz;
+
+    clazz = env->FindClass("java/lang/ref/WeakReference");
+    LOG_FATAL_IF(clazz == NULL, "Unable to find class java.lang.ref.WeakReference");
+    gWeakReferenceOffsets.mClass = (jclass) env->NewGlobalRef(clazz);
+    gWeakReferenceOffsets.mGet
+        = env->GetMethodID(clazz, "get", "()Ljava/lang/Object;");
+    assert(gWeakReferenceOffsets.mGet);
+
+    clazz = env->FindClass("java/lang/Error");
+    LOG_FATAL_IF(clazz == NULL, "Unable to find class java.lang.Error");
+    gErrorOffsets.mClass = (jclass) env->NewGlobalRef(clazz);
+    
+    clazz = env->FindClass(kBinderProxyPathName);
+    LOG_FATAL_IF(clazz == NULL, "Unable to find class android.os.BinderProxy");
+
+    gBinderProxyOffsets.mClass = (jclass) env->NewGlobalRef(clazz);
+    gBinderProxyOffsets.mConstructor
+        = env->GetMethodID(clazz, "<init>", "()V");
+    assert(gBinderProxyOffsets.mConstructor);
+    gBinderProxyOffsets.mSendDeathNotice
+        = env->GetStaticMethodID(clazz, "sendDeathNotice", "(Landroid/os/IBinder$DeathRecipient;)V");
+    assert(gBinderProxyOffsets.mSendDeathNotice);
+
+    gBinderProxyOffsets.mObject
+        = env->GetFieldID(clazz, "mObject", "I");
+    assert(gBinderProxyOffsets.mObject);
+    gBinderProxyOffsets.mSelf
+        = env->GetFieldID(clazz, "mSelf", "Ljava/lang/ref/WeakReference;");
+    assert(gBinderProxyOffsets.mSelf);
+
+    return AndroidRuntime::registerNativeMethods(
+        env, kBinderProxyPathName,
+        gBinderProxyMethods, NELEM(gBinderProxyMethods));
+}
+
+// ****************************************************************************
+// ****************************************************************************
+// ****************************************************************************
+
+static jint android_os_Parcel_dataSize(JNIEnv* env, jobject clazz)
+{
+    Parcel* parcel = parcelForJavaObject(env, clazz);
+    return parcel ? parcel->dataSize() : 0;
+}
+
+static jint android_os_Parcel_dataAvail(JNIEnv* env, jobject clazz)
+{
+    Parcel* parcel = parcelForJavaObject(env, clazz);
+    return parcel ? parcel->dataAvail() : 0;
+}
+
+static jint android_os_Parcel_dataPosition(JNIEnv* env, jobject clazz)
+{
+    Parcel* parcel = parcelForJavaObject(env, clazz);
+    return parcel ? parcel->dataPosition() : 0;
+}
+
+static jint android_os_Parcel_dataCapacity(JNIEnv* env, jobject clazz)
+{
+    Parcel* parcel = parcelForJavaObject(env, clazz);
+    return parcel ? parcel->dataCapacity() : 0;
+}
+
+static void android_os_Parcel_setDataSize(JNIEnv* env, jobject clazz, jint size)
+{
+    Parcel* parcel = parcelForJavaObject(env, clazz);
+    if (parcel != NULL) {
+        const status_t err = parcel->setDataSize(size);
+        if (err != NO_ERROR) {
+            jniThrowException(env, "java/lang/OutOfMemoryError", NULL);
+        }
+    }
+}
+
+static void android_os_Parcel_setDataPosition(JNIEnv* env, jobject clazz, jint pos)
+{
+    Parcel* parcel = parcelForJavaObject(env, clazz);
+    if (parcel != NULL) {
+        parcel->setDataPosition(pos);
+    }
+}
+
+static void android_os_Parcel_setDataCapacity(JNIEnv* env, jobject clazz, jint size)
+{
+    Parcel* parcel = parcelForJavaObject(env, clazz);
+    if (parcel != NULL) {
+        const status_t err = parcel->setDataCapacity(size);
+        if (err != NO_ERROR) {
+            jniThrowException(env, "java/lang/OutOfMemoryError", NULL);
+        }
+    }
+}
+
+static void android_os_Parcel_writeNative(JNIEnv* env, jobject clazz,
+                                          jobject data, jint offset,
+                                          jint length)
+{
+    Parcel* parcel = parcelForJavaObject(env, clazz);
+    if (parcel == NULL) {
+        return;
+    }
+    void *dest;
+
+    const status_t err = parcel->writeInt32(length);
+    if (err != NO_ERROR) {
+        jniThrowException(env, "java/lang/OutOfMemoryError", NULL);
+    }
+
+    dest = parcel->writeInplace(length);
+
+    if (dest == NULL) {
+        jniThrowException(env, "java/lang/OutOfMemoryError", NULL);
+        return;
+    }
+
+    jbyte* ar = (jbyte*)env->GetPrimitiveArrayCritical((jarray)data, 0);
+    if (ar) {
+        memcpy(dest, ar, length);
+        env->ReleasePrimitiveArrayCritical((jarray)data, ar, 0);
+    }
+}
+
+
+static void android_os_Parcel_writeInt(JNIEnv* env, jobject clazz, jint val)
+{
+    Parcel* parcel = parcelForJavaObject(env, clazz);
+    if (parcel != NULL) {
+        const status_t err = parcel->writeInt32(val);
+        if (err != NO_ERROR) {
+            jniThrowException(env, "java/lang/OutOfMemoryError", NULL);
+        }
+    }
+}
+
+static void android_os_Parcel_writeLong(JNIEnv* env, jobject clazz, jlong val)
+{
+    Parcel* parcel = parcelForJavaObject(env, clazz);
+    if (parcel != NULL) {
+        const status_t err = parcel->writeInt64(val);
+        if (err != NO_ERROR) {
+            jniThrowException(env, "java/lang/OutOfMemoryError", NULL);
+        }
+    }
+}
+
+static void android_os_Parcel_writeFloat(JNIEnv* env, jobject clazz, jfloat val)
+{
+    Parcel* parcel = parcelForJavaObject(env, clazz);
+    if (parcel != NULL) {
+        const status_t err = parcel->writeFloat(val);
+        if (err != NO_ERROR) {
+            jniThrowException(env, "java/lang/OutOfMemoryError", NULL);
+        }
+    }
+}
+
+static void android_os_Parcel_writeDouble(JNIEnv* env, jobject clazz, jdouble val)
+{
+    Parcel* parcel = parcelForJavaObject(env, clazz);
+    if (parcel != NULL) {
+        const status_t err = parcel->writeDouble(val);
+        if (err != NO_ERROR) {
+            jniThrowException(env, "java/lang/OutOfMemoryError", NULL);
+        }
+    }
+}
+
+static void android_os_Parcel_writeString(JNIEnv* env, jobject clazz, jstring val)
+{
+    Parcel* parcel = parcelForJavaObject(env, clazz);
+    if (parcel != NULL) {
+        status_t err = NO_MEMORY;
+        if (val) {
+            const jchar* str = env->GetStringCritical(val, 0);
+            if (str) {
+                err = parcel->writeString16(str, env->GetStringLength(val));
+                env->ReleaseStringCritical(val, str);
+            }
+        } else {
+            err = parcel->writeString16(NULL, 0);
+        }
+        if (err != NO_ERROR) {
+            jniThrowException(env, "java/lang/OutOfMemoryError", NULL);
+        }
+    }
+}
+
+static void android_os_Parcel_writeStrongBinder(JNIEnv* env, jobject clazz, jobject object)
+{
+    Parcel* parcel = parcelForJavaObject(env, clazz);
+    if (parcel != NULL) {
+        const status_t err = parcel->writeStrongBinder(ibinderForJavaObject(env, object));
+        if (err != NO_ERROR) {
+            jniThrowException(env, "java/lang/OutOfMemoryError", NULL);
+        }
+    }
+}
+
+static void android_os_Parcel_writeFileDescriptor(JNIEnv* env, jobject clazz, jobject object)
+{
+    Parcel* parcel = parcelForJavaObject(env, clazz);
+    if (parcel != NULL) {
+        const status_t err = parcel->writeDupFileDescriptor(
+                env->GetIntField(object, gFileDescriptorOffsets.mDescriptor));
+        if (err != NO_ERROR) {
+            jniThrowException(env, "java/lang/OutOfMemoryError", NULL);
+        }
+    }
+}
+
+static jbyteArray android_os_Parcel_createByteArray(JNIEnv* env, jobject clazz)
+{
+    jbyteArray ret = NULL;
+
+    Parcel* parcel = parcelForJavaObject(env, clazz);
+    if (parcel != NULL) {
+        int32_t len = parcel->readInt32();
+
+        // sanity check the stored length against the true data size
+        if (len >= 0 && len <= (int32_t)parcel->dataAvail()) {
+            ret = env->NewByteArray(len);
+
+            if (ret != NULL) {
+                jbyte* a2 = (jbyte*)env->GetPrimitiveArrayCritical(ret, 0);
+                if (a2) {
+                    const void* data = parcel->readInplace(len);
+                    memcpy(a2, data, len);
+                    env->ReleasePrimitiveArrayCritical(ret, a2, 0);
+                }
+            }
+        }
+    }
+
+    return ret;
+}
+
+static jint android_os_Parcel_readInt(JNIEnv* env, jobject clazz)
+{
+    Parcel* parcel = parcelForJavaObject(env, clazz);
+    if (parcel != NULL) {
+        return parcel->readInt32();
+    }
+    return 0;
+}
+
+static jlong android_os_Parcel_readLong(JNIEnv* env, jobject clazz)
+{
+    Parcel* parcel = parcelForJavaObject(env, clazz);
+    if (parcel != NULL) {
+        return parcel->readInt64();
+    }
+    return 0;
+}
+
+static jfloat android_os_Parcel_readFloat(JNIEnv* env, jobject clazz)
+{
+    Parcel* parcel = parcelForJavaObject(env, clazz);
+    if (parcel != NULL) {
+        return parcel->readFloat();
+    }
+    return 0;
+}
+
+static jdouble android_os_Parcel_readDouble(JNIEnv* env, jobject clazz)
+{
+    Parcel* parcel = parcelForJavaObject(env, clazz);
+    if (parcel != NULL) {
+        return parcel->readDouble();
+    }
+    return 0;
+}
+
+static jstring android_os_Parcel_readString(JNIEnv* env, jobject clazz)
+{
+    Parcel* parcel = parcelForJavaObject(env, clazz);
+    if (parcel != NULL) {
+        size_t len;
+        const char16_t* str = parcel->readString16Inplace(&len);
+        if (str) {
+            return env->NewString(str, len);
+        }
+        return NULL;
+    }
+    return NULL;
+}
+
+static jobject android_os_Parcel_readStrongBinder(JNIEnv* env, jobject clazz)
+{
+    Parcel* parcel = parcelForJavaObject(env, clazz);
+    if (parcel != NULL) {
+        return javaObjectForIBinder(env, parcel->readStrongBinder());
+    }
+    return NULL;
+}
+
+static jobject android_os_Parcel_readFileDescriptor(JNIEnv* env, jobject clazz)
+{
+    Parcel* parcel = parcelForJavaObject(env, clazz);
+    if (parcel != NULL) {
+        int fd = parcel->readFileDescriptor();
+        if (fd < 0) return NULL;
+        fd = dup(fd);
+        if (fd < 0) return NULL;
+        jobject object = env->NewObject(
+                gFileDescriptorOffsets.mClass, gFileDescriptorOffsets.mConstructor);
+        if (object != NULL) {
+            //LOGI("Created new FileDescriptor %p with fd %d\n", object, fd);
+            env->SetIntField(object, gFileDescriptorOffsets.mDescriptor, fd);
+        }
+        return object;
+    }
+    return NULL;
+}
+
+static jobject android_os_Parcel_openFileDescriptor(JNIEnv* env, jobject clazz,
+                                                    jstring name, jint mode)
+{
+    if (name == NULL) {
+        jniThrowException(env, "java/lang/NullPointerException", NULL);
+        return NULL;
+    }
+    const jchar* str = env->GetStringCritical(name, 0);
+    if (str == NULL) {
+        // Whatever, whatever.
+        jniThrowException(env, "java/lang/IllegalStateException", NULL);
+        return NULL;
+    }
+    String8 name8(str, env->GetStringLength(name));
+    env->ReleaseStringCritical(name, str);
+    int flags=0;
+    switch (mode&0x30000000) {
+        case 0:
+        case 0x10000000:
+            flags = O_RDONLY;
+            break;
+        case 0x20000000:
+            flags = O_WRONLY;
+            break;
+        case 0x30000000:
+            flags = O_RDWR;
+            break;
+    }
+
+    if (mode&0x08000000) flags |= O_CREAT;
+    if (mode&0x04000000) flags |= O_TRUNC;
+    if (mode&0x02000000) flags |= O_APPEND;
+
+    int realMode = S_IRWXU|S_IRWXG;
+    if (mode&0x00000001) realMode |= S_IROTH;
+    if (mode&0x00000002) realMode |= S_IWOTH;
+
+    int fd = open(name8.string(), flags, realMode);
+    if (fd < 0) {
+        jniThrowException(env, "java/io/FileNotFoundException", NULL);
+        return NULL;
+    }
+    jobject object = newFileDescriptor(env, fd);
+    if (object == NULL) {
+        close(fd);
+    }
+    return object;
+}
+
+static void android_os_Parcel_closeFileDescriptor(JNIEnv* env, jobject clazz, jobject object)
+{
+    int fd = env->GetIntField(object, gFileDescriptorOffsets.mDescriptor);
+    if (fd >= 0) {
+        env->SetIntField(object, gFileDescriptorOffsets.mDescriptor, -1);
+        //LOGI("Closing ParcelFileDescriptor %d\n", fd);
+        close(fd);
+    }
+}
+
+static void android_os_Parcel_freeBuffer(JNIEnv* env, jobject clazz)
+{
+    int32_t own = env->GetIntField(clazz, gParcelOffsets.mOwnObject);
+    if (own) {
+        Parcel* parcel = parcelForJavaObject(env, clazz);
+        if (parcel != NULL) {
+            //LOGI("Parcel.freeBuffer() called for C++ Parcel %p\n", parcel);
+            parcel->freeData();
+        }
+    }
+}
+
+static void android_os_Parcel_init(JNIEnv* env, jobject clazz, jint parcelInt)
+{
+    Parcel* parcel = (Parcel*)parcelInt;
+    int own = 0;
+    if (!parcel) {
+        //LOGI("Initializing obj %p: creating new Parcel\n", clazz);
+        own = 1;
+        parcel = new Parcel;
+    } else {
+        //LOGI("Initializing obj %p: given existing Parcel %p\n", clazz, parcel);
+    }
+    if (parcel == NULL) {
+        jniThrowException(env, "java/lang/OutOfMemoryError", NULL);
+        return;
+    }
+    //LOGI("Initializing obj %p from C++ Parcel %p, own=%d\n", clazz, parcel, own);
+    env->SetIntField(clazz, gParcelOffsets.mOwnObject, own);
+    env->SetIntField(clazz, gParcelOffsets.mObject, (int)parcel);
+}
+
+static void android_os_Parcel_destroy(JNIEnv* env, jobject clazz)
+{
+    int32_t own = env->GetIntField(clazz, gParcelOffsets.mOwnObject);
+    if (own) {
+        Parcel* parcel = parcelForJavaObject(env, clazz);
+        env->SetIntField(clazz, gParcelOffsets.mObject, 0);
+        //LOGI("Destroying obj %p: deleting C++ Parcel %p\n", clazz, parcel);
+        delete parcel;
+    } else {
+        env->SetIntField(clazz, gParcelOffsets.mObject, 0);
+        //LOGI("Destroying obj %p: leaving C++ Parcel %p\n", clazz);
+    }
+}
+
+static jbyteArray android_os_Parcel_marshall(JNIEnv* env, jobject clazz)
+{
+    Parcel* parcel = parcelForJavaObject(env, clazz);
+    if (parcel == NULL) {
+       return NULL;
+    }
+
+    // do not marshall if there are binder objects in the parcel
+    if (parcel->objectsCount())
+    {
+        jniThrowException(env, "java/lang/RuntimeException", "Tried to marshall a Parcel that contained Binder objects.");
+        return NULL;
+    }
+
+    jbyteArray ret = env->NewByteArray(parcel->dataSize());
+
+    if (ret != NULL)
+    {
+        jbyte* array = (jbyte*)env->GetPrimitiveArrayCritical(ret, 0);
+        if (array != NULL)
+        {
+            memcpy(array, parcel->data(), parcel->dataSize());
+            env->ReleasePrimitiveArrayCritical(ret, array, 0);
+        }
+    }
+
+    return ret;
+}
+
+static void android_os_Parcel_unmarshall(JNIEnv* env, jobject clazz, jbyteArray data, jint offset, jint length)
+{
+    Parcel* parcel = parcelForJavaObject(env, clazz);
+    if (parcel == NULL || length < 0) {
+       return;
+    }
+
+    jbyte* array = (jbyte*)env->GetPrimitiveArrayCritical(data, 0);
+    if (array)
+    {
+        parcel->setDataSize(length);
+        parcel->setDataPosition(0);
+
+        void* raw = parcel->writeInplace(length);
+        memcpy(raw, (array + offset), length);
+
+        env->ReleasePrimitiveArrayCritical(data, array, 0);
+    }
+}
+
+static void android_os_Parcel_appendFrom(JNIEnv* env, jobject clazz, jobject parcel, jint offset, jint length)
+{
+    Parcel* thisParcel = parcelForJavaObject(env, clazz);
+    if (thisParcel == NULL) {
+       return;
+    }
+    Parcel* otherParcel = parcelForJavaObject(env, parcel);
+    if (otherParcel == NULL) {
+       return;
+    }
+
+    (void) thisParcel->appendFrom(otherParcel, offset, length);
+}
+
+static jboolean android_os_Parcel_hasFileDescriptors(JNIEnv* env, jobject clazz)
+{
+    jboolean ret = JNI_FALSE;
+    Parcel* parcel = parcelForJavaObject(env, clazz);
+    if (parcel != NULL) {
+        if (parcel->hasFileDescriptors()) {
+            ret = JNI_TRUE;
+        }
+    }
+    return ret;
+}
+
+static void android_os_Parcel_writeInterfaceToken(JNIEnv* env, jobject clazz, jstring name)
+{
+    Parcel* parcel = parcelForJavaObject(env, clazz);
+    if (parcel != NULL) {
+        // In the current implementation, the token is just the serialized interface name that
+        // the caller expects to be invoking
+        const jchar* str = env->GetStringCritical(name, 0);
+        if (str != NULL) {
+            parcel->writeInterfaceToken(String16(str, env->GetStringLength(name)));
+            env->ReleaseStringCritical(name, str);
+        }
+    }
+}
+
+static void android_os_Parcel_enforceInterface(JNIEnv* env, jobject clazz, jstring name)
+{
+    jboolean ret = JNI_FALSE;
+
+    Parcel* parcel = parcelForJavaObject(env, clazz);
+    if (parcel != NULL) {
+        const jchar* str = env->GetStringCritical(name, 0);
+        if (str) {
+            bool isValid = parcel->enforceInterface(String16(str, env->GetStringLength(name)));
+            env->ReleaseStringCritical(name, str);
+            if (isValid) {
+                return;     // everything was correct -> return silently
+            }
+        }
+    }
+
+    // all error conditions wind up here
+    jniThrowException(env, "java/lang/SecurityException",
+            "Binder invocation to an incorrect interface");
+}
+
+// ----------------------------------------------------------------------------
+
+static const JNINativeMethod gParcelMethods[] = {
+    {"dataSize",            "()I", (void*)android_os_Parcel_dataSize},
+    {"dataAvail",           "()I", (void*)android_os_Parcel_dataAvail},
+    {"dataPosition",        "()I", (void*)android_os_Parcel_dataPosition},
+    {"dataCapacity",        "()I", (void*)android_os_Parcel_dataCapacity},
+    {"setDataSize",         "(I)V", (void*)android_os_Parcel_setDataSize},
+    {"setDataPosition",     "(I)V", (void*)android_os_Parcel_setDataPosition},
+    {"setDataCapacity",     "(I)V", (void*)android_os_Parcel_setDataCapacity},
+    {"writeNative",         "([BII)V", (void*)android_os_Parcel_writeNative},
+    {"writeInt",            "(I)V", (void*)android_os_Parcel_writeInt},
+    {"writeLong",           "(J)V", (void*)android_os_Parcel_writeLong},
+    {"writeFloat",          "(F)V", (void*)android_os_Parcel_writeFloat},
+    {"writeDouble",         "(D)V", (void*)android_os_Parcel_writeDouble},
+    {"writeString",         "(Ljava/lang/String;)V", (void*)android_os_Parcel_writeString},
+    {"writeStrongBinder",   "(Landroid/os/IBinder;)V", (void*)android_os_Parcel_writeStrongBinder},
+    {"writeFileDescriptor", "(Ljava/io/FileDescriptor;)V", (void*)android_os_Parcel_writeFileDescriptor},
+    {"createByteArray",     "()[B", (void*)android_os_Parcel_createByteArray},
+    {"readInt",             "()I", (void*)android_os_Parcel_readInt},
+    {"readLong",            "()J", (void*)android_os_Parcel_readLong},
+    {"readFloat",           "()F", (void*)android_os_Parcel_readFloat},
+    {"readDouble",          "()D", (void*)android_os_Parcel_readDouble},
+    {"readString",          "()Ljava/lang/String;", (void*)android_os_Parcel_readString},
+    {"readStrongBinder",    "()Landroid/os/IBinder;", (void*)android_os_Parcel_readStrongBinder},
+    {"internalReadFileDescriptor",  "()Ljava/io/FileDescriptor;", (void*)android_os_Parcel_readFileDescriptor},
+    {"openFileDescriptor",  "(Ljava/lang/String;I)Ljava/io/FileDescriptor;", (void*)android_os_Parcel_openFileDescriptor},
+    {"closeFileDescriptor", "(Ljava/io/FileDescriptor;)V", (void*)android_os_Parcel_closeFileDescriptor},
+    {"freeBuffer",          "()V", (void*)android_os_Parcel_freeBuffer},
+    {"init",                "(I)V", (void*)android_os_Parcel_init},
+    {"destroy",             "()V", (void*)android_os_Parcel_destroy},
+    {"marshall",            "()[B", (void*)android_os_Parcel_marshall},
+    {"unmarshall",          "([BII)V", (void*)android_os_Parcel_unmarshall},
+    {"appendFrom",          "(Landroid/os/Parcel;II)V", (void*)android_os_Parcel_appendFrom},
+    {"hasFileDescriptors",  "()Z", (void*)android_os_Parcel_hasFileDescriptors},
+    {"writeInterfaceToken", "(Ljava/lang/String;)V", (void*)android_os_Parcel_writeInterfaceToken},
+    {"enforceInterface",    "(Ljava/lang/String;)V", (void*)android_os_Parcel_enforceInterface},
+};
+
+const char* const kParcelPathName = "android/os/Parcel";
+
+static int int_register_android_os_Parcel(JNIEnv* env)
+{
+    jclass clazz;
+
+    clazz = env->FindClass("android/util/Log");
+    LOG_FATAL_IF(clazz == NULL, "Unable to find class android.util.Log");
+    gLogOffsets.mClass = (jclass) env->NewGlobalRef(clazz);
+    gLogOffsets.mLogE = env->GetStaticMethodID(
+        clazz, "e", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/Throwable;)I");
+    assert(gLogOffsets.mLogE);
+
+    clazz = env->FindClass("java/io/FileDescriptor");
+    LOG_FATAL_IF(clazz == NULL, "Unable to find class java.io.FileDescriptor");
+    gFileDescriptorOffsets.mClass = (jclass) env->NewGlobalRef(clazz);
+    gFileDescriptorOffsets.mConstructor
+        = env->GetMethodID(clazz, "<init>", "()V");
+    gFileDescriptorOffsets.mDescriptor = env->GetFieldID(clazz, "descriptor", "I");
+    LOG_FATAL_IF(gFileDescriptorOffsets.mDescriptor == NULL,
+                 "Unable to find descriptor field in java.io.FileDescriptor");
+
+    clazz = env->FindClass("android/os/ParcelFileDescriptor");
+    LOG_FATAL_IF(clazz == NULL, "Unable to find class android.os.ParcelFileDescriptor");
+    gParcelFileDescriptorOffsets.mClass = (jclass) env->NewGlobalRef(clazz);
+    gParcelFileDescriptorOffsets.mConstructor
+        = env->GetMethodID(clazz, "<init>", "(Ljava/io/FileDescriptor;)V");
+
+    clazz = env->FindClass(kParcelPathName);
+    LOG_FATAL_IF(clazz == NULL, "Unable to find class android.os.Parcel");
+
+    gParcelOffsets.mObject
+        = env->GetFieldID(clazz, "mObject", "I");
+    gParcelOffsets.mOwnObject
+        = env->GetFieldID(clazz, "mOwnObject", "I");
+
+    return AndroidRuntime::registerNativeMethods(
+        env, kParcelPathName,
+        gParcelMethods, NELEM(gParcelMethods));
+}
+
+int register_android_os_Binder(JNIEnv* env)
+{
+    if (int_register_android_os_Binder(env) < 0)
+        return -1;
+    if (int_register_android_os_BinderInternal(env) < 0)
+        return -1;
+    if (int_register_android_os_BinderProxy(env) < 0)
+        return -1;
+    if (int_register_android_os_Parcel(env) < 0)
+        return -1;
+    return 0;
+}
+
+namespace android {
+
+// Returns the Unix file descriptor for a ParcelFileDescriptor object
+int getParcelFileDescriptorFD(JNIEnv* env, jobject object)
+{
+    return env->GetIntField(object, gFileDescriptorOffsets.mDescriptor);
+}
+
+}
diff --git a/core/jni/android_util_Binder.h b/core/jni/android_util_Binder.h
new file mode 100644
index 0000000..16d993d
--- /dev/null
+++ b/core/jni/android_util_Binder.h
@@ -0,0 +1,35 @@
+/* //device/libs/android_runtime/android_util_Binder.h
+**
+** Copyright 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.
+*/
+
+#include <utils/IBinder.h>
+
+#include "jni.h"
+
+namespace android {
+
+// Converstion to/from Java IBinder Object and C++ IBinder instance.
+extern jobject javaObjectForIBinder(JNIEnv* env, const sp<IBinder>& val);
+extern sp<IBinder> ibinderForJavaObject(JNIEnv* env, jobject obj);
+
+// Conversion from Java Parcel Object to C++ Parcel instance.
+// Note: does not type checking; must guarantee jobject is a Java Parcel
+extern Parcel* parcelForJavaObject(JNIEnv* env, jobject obj);
+
+extern jobject newFileDescriptor(JNIEnv* env, int fd);
+extern jobject newParcelFileDescriptor(JNIEnv* env, jobject fileDesc);
+
+}
diff --git a/core/jni/android_util_EventLog.cpp b/core/jni/android_util_EventLog.cpp
new file mode 100644
index 0000000..5e5103a
--- /dev/null
+++ b/core/jni/android_util_EventLog.cpp
@@ -0,0 +1,353 @@
+/*
+ * Copyright (C) 2007 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.
+ */
+
+#include <fcntl.h>
+
+#include "JNIHelp.h"
+#include "android_runtime/AndroidRuntime.h"
+#include "jni.h"
+#include "cutils/logger.h"
+
+#define END_DELIMITER '\n'
+#define INT_BUFFER_SIZE (sizeof(jbyte)+sizeof(jint)+sizeof(END_DELIMITER))
+#define LONG_BUFFER_SIZE (sizeof(jbyte)+sizeof(jlong)+sizeof(END_DELIMITER))
+#define INITAL_BUFFER_CAPACITY 256
+
+#define MAX(a,b) ((a>b)?a:b)
+
+namespace android {
+
+static jclass gCollectionClass;
+static jmethodID gCollectionAddID;
+
+static jclass gEventClass;
+static jmethodID gEventInitID;
+
+static jclass gIntegerClass;
+static jfieldID gIntegerValueID;
+
+static jclass gListClass;
+static jfieldID gListItemsID;
+
+static jclass gLongClass;
+static jfieldID gLongValueID;
+
+static jclass gStringClass;
+
+struct ByteBuf {
+    size_t len;
+    size_t capacity;
+    uint8_t* buf;
+    
+    ByteBuf(size_t initSize) {
+        buf = (uint8_t*)malloc(initSize);
+        len = 0;
+        capacity = initSize;        
+    }
+    
+    ~ByteBuf() {
+        free(buf);
+    }
+    
+    bool ensureExtraCapacity(size_t extra) {
+        size_t spaceNeeded = len + extra;
+        if (spaceNeeded > capacity) {
+            size_t newCapacity = MAX(spaceNeeded, 2 * capacity);
+            void* newBuf = realloc(buf, newCapacity);
+            if (newBuf == NULL) {
+                return false;
+            }
+            capacity = newCapacity;
+            buf = (uint8_t*)newBuf;
+            return true;
+        } else {
+            return true;
+        }
+    }
+ 
+    void putIntEvent(jint value) {
+        bool succeeded = ensureExtraCapacity(INT_BUFFER_SIZE);
+        buf[len++] = EVENT_TYPE_INT;
+        memcpy(buf+len, &value, sizeof(jint));
+        len += sizeof(jint);
+    }
+
+    void putByte(uint8_t value) {
+        bool succeeded = ensureExtraCapacity(sizeof(uint8_t));
+        buf[len++] = value;
+    }
+
+    void putLongEvent(jlong value) {
+        bool succeeded = ensureExtraCapacity(LONG_BUFFER_SIZE);
+        buf[len++] = EVENT_TYPE_LONG;
+        memcpy(buf+len, &value, sizeof(jlong));
+        len += sizeof(jlong);
+    }
+
+
+    void putStringEvent(JNIEnv* env, jstring value) {
+        const char* strValue = env->GetStringUTFChars(value, NULL);
+        uint32_t strLen = strlen(strValue); //env->GetStringUTFLength(value);
+        bool succeeded = ensureExtraCapacity(1 + sizeof(uint32_t) + strLen);
+        buf[len++] = EVENT_TYPE_STRING;
+        memcpy(buf+len, &strLen, sizeof(uint32_t));
+        len += sizeof(uint32_t);
+        memcpy(buf+len, strValue, strLen);
+        env->ReleaseStringUTFChars(value, strValue);
+        len += strLen;
+    }
+
+    void putList(JNIEnv* env, jobject list) {
+        jobjectArray items = (jobjectArray) env->GetObjectField(list, gListItemsID);
+        if (items == NULL) {
+            jniThrowException(env, "java/lang/NullPointerException", NULL);
+            return;
+        }
+
+        jsize numItems = env->GetArrayLength(items);
+        putByte(EVENT_TYPE_LIST);
+        putByte(numItems);
+        // We'd like to call GetPrimitveArrayCritical() but that might
+        // not be safe since we're going to be doing some I/O
+        for (int i = 0; i < numItems; i++) {
+            jobject item = env->GetObjectArrayElement(items, i);
+            if (env->IsInstanceOf(item, gIntegerClass)) {
+                jint intVal = env->GetIntField(item, gIntegerValueID);
+                putIntEvent(intVal);
+            } else if (env->IsInstanceOf(item, gLongClass)) {
+                jlong longVal = env->GetLongField(item, gLongValueID);
+                putLongEvent(longVal);
+            } else if (env->IsInstanceOf(item, gStringClass)) {
+                putStringEvent(env, (jstring)item);
+            } else if (env->IsInstanceOf(item, gListClass)) {
+                putList(env, item);
+            } else {
+                jniThrowException(
+                        env,
+                        "java/lang/IllegalArgumentException",
+                        "Attempt to log an illegal item type.");
+                return;
+            }
+            env->DeleteLocalRef(item);
+        }
+
+        env->DeleteLocalRef(items);
+    }
+};
+
+/*
+ * In class android.util.EventLog:
+ *  static native int writeEvent(int tag, int value)
+ */
+static jint android_util_EventLog_writeEvent_Integer(JNIEnv* env, jobject clazz,
+                                                     jint tag, jint value)
+{
+    return android_btWriteLog(tag, EVENT_TYPE_INT, &value, sizeof(value));
+}
+
+/*
+ * In class android.util.EventLog:
+ *  static native int writeEvent(long tag, long value)
+ */
+static jint android_util_EventLog_writeEvent_Long(JNIEnv* env, jobject clazz, 
+                                                  jint tag, jlong value)
+{
+    return android_btWriteLog(tag, EVENT_TYPE_LONG, &value, sizeof(value));
+}
+
+/*
+ * In class android.util.EventLog:
+ *  static native int writeEvent(long tag, List value)
+ */
+static jint android_util_EventLog_writeEvent_List(JNIEnv* env, jobject clazz,
+                                                  jint tag, jobject value) {
+    if (value == NULL) {
+        jclass clazz = env->FindClass("java/lang/IllegalArgumentException");
+        env->ThrowNew(clazz, "writeEvent needs a value.");
+        return -1;
+    }
+    ByteBuf byteBuf(INITAL_BUFFER_CAPACITY);
+    byteBuf.putList(env, value);
+    byteBuf.putByte((uint8_t)END_DELIMITER);
+    int numBytesPut = byteBuf.len;
+    int bytesWritten = android_bWriteLog(tag, byteBuf.buf, numBytesPut);
+    return bytesWritten;
+}
+
+/*
+ * In class android.util.EventLog:
+ *  static native int writeEvent(int tag, String value)
+ */
+static jint android_util_EventLog_writeEvent_String(JNIEnv* env, jobject clazz,
+                                                    jint tag, jstring value) {
+    if (value == NULL) {
+        jclass clazz = env->FindClass("java/lang/IllegalArgumentException");
+        env->ThrowNew(clazz, "logEvent needs a value.");
+        return -1;
+    }
+
+    ByteBuf byteBuf(INITAL_BUFFER_CAPACITY);
+    byteBuf.putStringEvent(env, value);
+    byteBuf.putByte((uint8_t)END_DELIMITER);
+    int numBytesPut = byteBuf.len;
+    int bytesWritten = android_bWriteLog(tag, byteBuf.buf, numBytesPut);
+    return bytesWritten;
+}
+
+/*
+ * In class android.util.EventLog:
+ *  static native void readEvents(int[] tags, Collection<Event> output)
+ */
+static void android_util_EventLog_readEvents(JNIEnv* env, jobject clazz,
+                                             jintArray tags,
+                                             jobject out) {
+    if (tags == NULL || out == NULL) {
+        jniThrowException(env, "java/lang/NullPointerException", NULL);
+        return;
+    }
+
+    int fd = open("/dev/" LOGGER_LOG_EVENTS, O_RDONLY | O_NONBLOCK);
+    if (fd < 0) {
+        jniThrowIOException(env, errno);
+        return;
+    }
+
+    jsize tagLength = env->GetArrayLength(tags);
+    jint *tagValues = env->GetIntArrayElements(tags, NULL);
+
+    uint8_t buf[LOGGER_ENTRY_MAX_LEN];
+    for (;;) {
+        int len = read(fd, buf, sizeof(buf));
+        if (len == 0 || (len < 0 && errno == EAGAIN)) {
+            break;
+        } else if (len < 0) {
+            // This calls env->ThrowNew(), which doesn't throw an exception
+            // now, but sets a flag to trigger an exception after we return.
+            jniThrowIOException(env, errno);
+            break;
+        } else if ((size_t) len < sizeof(logger_entry) + sizeof(int32_t)) {
+            jniThrowException(env, "java/io/IOException", "Event too short");
+            break;
+        }
+
+        logger_entry* entry = (logger_entry*) buf;
+        int32_t tag = * (int32_t*) (buf + sizeof(*entry));
+
+        int found = 0;
+        for (int i = 0; !found && i < tagLength; ++i) {
+            found = (tag == tagValues[i]);
+        }
+
+        if (found) {
+            jsize len = sizeof(*entry) + entry->len;
+            jbyteArray array = env->NewByteArray(len);
+            if (array == NULL) break;
+
+            jbyte *bytes = env->GetByteArrayElements(array, NULL);
+            memcpy(bytes, buf, len);
+            env->ReleaseByteArrayElements(array, bytes, 0);
+
+            jobject event = env->NewObject(gEventClass, gEventInitID, array);
+            if (event == NULL) break;
+
+            env->CallBooleanMethod(out, gCollectionAddID, event);
+            env->DeleteLocalRef(event);
+            env->DeleteLocalRef(array);
+        }
+    }
+
+    close(fd);
+    env->ReleaseIntArrayElements(tags, tagValues, 0);
+}
+
+
+/*
+ * JNI registration.
+ */
+static JNINativeMethod gRegisterMethods[] = {
+    /* name, signature, funcPtr */
+    { "writeEvent", "(II)I", (void*) android_util_EventLog_writeEvent_Integer },
+    { "writeEvent", "(IJ)I", (void*) android_util_EventLog_writeEvent_Long },
+    { "writeEvent",
+      "(ILjava/lang/String;)I",
+      (void*) android_util_EventLog_writeEvent_String
+    },
+    { "writeEvent",
+      "(ILandroid/util/EventLog$List;)I",
+      (void*) android_util_EventLog_writeEvent_List
+    },
+    { "readEvents",
+      "([ILjava/util/Collection;)V",
+      (void*) android_util_EventLog_readEvents
+    }
+};
+
+static struct { const char *name; jclass *clazz; } gClasses[] = {
+    { "android/util/EventLog$Event", &gEventClass },
+    { "android/util/EventLog$List", &gListClass },
+    { "java/lang/Integer", &gIntegerClass },
+    { "java/lang/Long", &gLongClass },
+    { "java/lang/String", &gStringClass },
+    { "java/util/Collection", &gCollectionClass },
+};
+
+static struct { jclass *c; const char *name, *ft; jfieldID *id; } gFields[] = {
+    { &gIntegerClass, "value", "I", &gIntegerValueID },
+    { &gListClass, "mItems", "[Ljava/lang/Object;", &gListItemsID },
+    { &gLongClass, "value", "J", &gLongValueID },
+};
+
+static struct { jclass *c; const char *name, *mt; jmethodID *id; } gMethods[] = {
+    { &gEventClass, "<init>", "([B)V", &gEventInitID },
+    { &gCollectionClass, "add", "(Ljava/lang/Object;)Z", &gCollectionAddID },
+};
+
+int register_android_util_EventLog(JNIEnv* env) {
+    for (int i = 0; i < NELEM(gClasses); ++i) {
+        jclass clazz = env->FindClass(gClasses[i].name);
+        if (clazz == NULL) {
+            LOGE("Can't find class: %s\n", gClasses[i].name);
+            return -1;
+        }
+        *gClasses[i].clazz = (jclass) env->NewGlobalRef(clazz);
+    }
+
+    for (int i = 0; i < NELEM(gFields); ++i) {
+        *gFields[i].id = env->GetFieldID(
+                *gFields[i].c, gFields[i].name, gFields[i].ft);
+        if (*gFields[i].id == NULL) {
+            LOGE("Can't find field: %s\n", gFields[i].name);
+            return -1;
+        }
+    }
+
+    for (int i = 0; i < NELEM(gMethods); ++i) {
+        *gMethods[i].id = env->GetMethodID(
+                *gMethods[i].c, gMethods[i].name, gMethods[i].mt);
+        if (*gMethods[i].id == NULL) {
+            LOGE("Can't find method: %s\n", gMethods[i].name);
+            return -1;
+        }
+    }
+
+    return AndroidRuntime::registerNativeMethods(
+            env,
+            "android/util/EventLog",
+            gRegisterMethods, NELEM(gRegisterMethods));
+}
+
+}; // namespace android
+
diff --git a/core/jni/android_util_FileObserver.cpp b/core/jni/android_util_FileObserver.cpp
new file mode 100644
index 0000000..794478a
--- /dev/null
+++ b/core/jni/android_util_FileObserver.cpp
@@ -0,0 +1,157 @@
+/* //device/libs/android_runtime/android_util_FileObserver.cpp
+**
+** Copyright 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.
+*/
+
+#include "JNIHelp.h"
+#include "jni.h"
+#include "utils/Log.h"
+#include "utils/misc.h"
+#include "android_runtime/AndroidRuntime.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdint.h>
+#include <fcntl.h>
+#include <sys/ioctl.h>
+#include <errno.h>
+
+#ifdef HAVE_INOTIFY
+#include <sys/inotify.h>
+#endif
+
+namespace android {
+
+static jmethodID method_onEvent;
+
+static jint android_os_fileobserver_init(JNIEnv* env, jobject object)
+{
+#ifdef HAVE_INOTIFY
+
+    return (jint)inotify_init();    
+
+#else // HAVE_INOTIFY
+
+    return -1;
+
+#endif // HAVE_INOTIFY
+}
+
+static void android_os_fileobserver_observe(JNIEnv* env, jobject object, jint fd)
+{
+#ifdef HAVE_INOTIFY
+ 
+    char event_buf[512];
+    struct inotify_event* event;
+         
+    while (1)
+    {
+        int event_pos = 0;
+        int num_bytes = read(fd, event_buf, sizeof(event_buf));
+        
+        if (num_bytes < (int)sizeof(*event))
+        {
+            if (errno == EINTR)
+                continue;
+
+            LOGE("***** ERROR! android_os_fileobserver_observe() got a short event!");
+            return;
+        }
+        
+    	while (num_bytes >= (int)sizeof(*event))
+    	{
+			int event_size;
+			event = (struct inotify_event *)(event_buf + event_pos);
+			
+            jstring path = NULL;
+            
+            if (event->len > 0)
+            {
+                path = env->NewStringUTF(event->name);
+            }
+            
+		    env->CallVoidMethod(object, method_onEvent, event->wd, event->mask, path);
+		    
+		    event_size = sizeof(*event) + event->len;
+			num_bytes -= event_size;
+			event_pos += event_size;
+		}
+    }
+    
+#endif // HAVE_INOTIFY
+}
+
+static jint android_os_fileobserver_startWatching(JNIEnv* env, jobject object, jint fd, jstring pathString, jint mask)
+{
+    int res = -1;
+    
+#ifdef HAVE_INOTIFY
+   
+    if (fd >= 0)
+    {
+        const char* path = env->GetStringUTFChars(pathString, NULL);
+        
+        res = inotify_add_watch(fd, path, mask);
+        
+        env->ReleaseStringUTFChars(pathString, path);
+    }
+
+#endif // HAVE_INOTIFY
+    
+    return res;
+}
+
+static void android_os_fileobserver_stopWatching(JNIEnv* env, jobject object, jint fd, jint wfd)
+{
+#ifdef HAVE_INOTIFY
+
+    inotify_rm_watch((int)fd, (uint32_t)wfd);
+
+#endif // HAVE_INOTIFY
+}
+
+static JNINativeMethod sMethods[] = {
+     /* name, signature, funcPtr */
+    { "init", "()I", (void*)android_os_fileobserver_init },
+    { "observe", "(I)V", (void*)android_os_fileobserver_observe },
+    { "startWatching", "(ILjava/lang/String;I)I", (void*)android_os_fileobserver_startWatching },
+    { "stopWatching", "(II)V", (void*)android_os_fileobserver_stopWatching }
+    
+};
+
+int register_android_os_FileObserver(JNIEnv* env)
+{
+    jclass clazz;
+
+    clazz = env->FindClass("android/os/FileObserver$ObserverThread");
+
+    if (clazz == NULL)
+	{
+        LOGE("Can't find android/os/FileObserver$ObserverThread");
+        return -1;
+    }
+
+    method_onEvent = env->GetMethodID(clazz, "onEvent", "(IILjava/lang/String;)V");
+    if (method_onEvent == NULL)
+    {
+        LOGE("Can't find FileObserver.onEvent(int, int, String)");
+        return -1;
+    }
+
+    return AndroidRuntime::registerNativeMethods(env, "android/os/FileObserver$ObserverThread", sMethods, NELEM(sMethods));
+}
+
+} /* namespace android */
diff --git a/core/jni/android_util_FloatMath.cpp b/core/jni/android_util_FloatMath.cpp
new file mode 100644
index 0000000..f38faa9
--- /dev/null
+++ b/core/jni/android_util_FloatMath.cpp
@@ -0,0 +1,46 @@
+#include "jni.h"
+#include <android_runtime/AndroidRuntime.h>
+#include <math.h>
+#include <float.h>
+#include "SkTypes.h"
+
+class MathUtilsGlue {
+public:
+    static float FloorF(JNIEnv* env, jobject clazz, float x) {
+        return floorf(x);
+    }
+    
+    static float CeilF(JNIEnv* env, jobject clazz, float x) {
+        return ceilf(x);
+    }
+    
+    static float SinF(JNIEnv* env, jobject clazz, float x) {
+        return sinf(x);
+    }
+    
+    static float CosF(JNIEnv* env, jobject clazz, float x) {
+        return cosf(x);
+    }
+    
+    static float SqrtF(JNIEnv* env, jobject clazz, float x) {
+        return sqrtf(x);
+    }
+};
+
+static JNINativeMethod gMathUtilsMethods[] = {
+    {"floor", "(F)F", (void*) MathUtilsGlue::FloorF},
+    {"ceil", "(F)F", (void*) MathUtilsGlue::CeilF},
+    {"sin", "(F)F", (void*) MathUtilsGlue::SinF},
+    {"cos", "(F)F", (void*) MathUtilsGlue::CosF},
+    {"sqrt", "(F)F", (void*) MathUtilsGlue::SqrtF}
+};
+
+int register_android_util_FloatMath(JNIEnv* env)
+{
+    int result = android::AndroidRuntime::registerNativeMethods(env,
+                                            "android/util/FloatMath",
+                                            gMathUtilsMethods,
+                                            SK_ARRAY_COUNT(gMathUtilsMethods));
+    return result;
+}
+
diff --git a/core/jni/android_util_Log.cpp b/core/jni/android_util_Log.cpp
new file mode 100644
index 0000000..8316b03
--- /dev/null
+++ b/core/jni/android_util_Log.cpp
@@ -0,0 +1,161 @@
+/* //device/libs/android_runtime/android_util_Log.cpp
+**
+** Copyright 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.
+*/
+
+#define LOG_NAMESPACE "log.tag."
+#define LOG_TAG "Log_println"
+
+#include <assert.h>
+#include <cutils/properties.h>
+#include <utils/Log.h>
+#include <utils/String8.h>
+
+#include "jni.h"
+#include "utils/misc.h"
+#include "android_runtime/AndroidRuntime.h"
+
+#define MIN(a,b) ((a<b)?a:b)
+
+namespace android {
+
+struct levels_t {
+    jint verbose;
+    jint debug;
+    jint info;
+    jint warn;
+    jint error;
+    jint assert;
+};
+static levels_t levels;
+
+static int toLevel(const char* value) 
+{
+    switch (value[0]) {
+        case 'V': return levels.verbose;
+        case 'D': return levels.debug;
+        case 'I': return levels.info;
+        case 'W': return levels.warn;
+        case 'E': return levels.error;
+        case 'A': return levels.assert;
+        case 'S': return -1; // SUPPRESS
+    }
+    return levels.info;
+}
+
+static jboolean android_util_Log_isLoggable(JNIEnv* env, jobject clazz, jstring tag, jint level)
+{
+#ifndef HAVE_ANDROID_OS
+    return false;
+#else /* HAVE_ANDROID_OS */
+    int len;
+    char key[PROPERTY_KEY_MAX];
+    char buf[PROPERTY_VALUE_MAX];
+
+    if (tag == NULL) {
+        return false;
+    }
+    
+    jboolean result = false;
+    
+    const char* chars = env->GetStringUTFChars(tag, NULL);
+
+    if ((strlen(chars)+sizeof(LOG_NAMESPACE)) > PROPERTY_KEY_MAX) {
+        jclass clazz = env->FindClass("java/lang/IllegalArgumentException");
+        char buf2[200];
+        snprintf(buf2, sizeof(buf2), "Log tag \"%s\" exceeds limit of %d characters\n",
+                chars, PROPERTY_KEY_MAX - sizeof(LOG_NAMESPACE));
+
+        // release the chars!
+        env->ReleaseStringUTFChars(tag, chars);
+
+        env->ThrowNew(clazz, buf2);
+        return false;
+    } else {
+        strncpy(key, LOG_NAMESPACE, sizeof(LOG_NAMESPACE)-1);
+        strcpy(key + sizeof(LOG_NAMESPACE) - 1, chars);
+    }
+    
+    env->ReleaseStringUTFChars(tag, chars);
+
+    len = property_get(key, buf, "");
+    int logLevel = toLevel(buf);
+    return (logLevel >= 0 && level >= logLevel) ? true : false;
+#endif /* HAVE_ANDROID_OS */
+}
+
+/*
+ * In class android.util.Log:
+ *  public static native int println(int priority, String tag, String msg)
+ */
+static jint android_util_Log_println(JNIEnv* env, jobject clazz,
+    jint priority, jstring tagObj, jstring msgObj)
+{
+    const char* tag = NULL;
+    const char* msg = NULL;
+
+    if (msgObj == NULL) {
+        jclass npeClazz;
+
+        npeClazz = env->FindClass("java/lang/NullPointerException");
+        assert(npeClazz != NULL);
+
+        env->ThrowNew(npeClazz, "println needs a message");
+        return -1;
+    }
+
+    if (tagObj != NULL)
+        tag = env->GetStringUTFChars(tagObj, NULL);
+    msg = env->GetStringUTFChars(msgObj, NULL);
+
+    int res = android_writeLog((android_LogPriority) priority, tag, msg);
+
+    if (tag != NULL)
+        env->ReleaseStringUTFChars(tagObj, tag);
+    env->ReleaseStringUTFChars(msgObj, msg);
+
+    return res;
+}
+
+/*
+ * JNI registration.
+ */
+static JNINativeMethod gMethods[] = {
+    /* name, signature, funcPtr */
+    { "isLoggable",      "(Ljava/lang/String;I)Z", (void*) android_util_Log_isLoggable },
+    { "println",      "(ILjava/lang/String;Ljava/lang/String;)I", (void*) android_util_Log_println },
+};
+
+int register_android_util_Log(JNIEnv* env)
+{
+    jclass clazz = env->FindClass("android/util/Log");
+
+    if (clazz == NULL) {
+        LOGE("Can't find android/util/Log");
+        return -1;
+    }
+    
+    levels.verbose = env->GetStaticIntField(clazz, env->GetStaticFieldID(clazz, "VERBOSE", "I"));
+    levels.debug = env->GetStaticIntField(clazz, env->GetStaticFieldID(clazz, "DEBUG", "I"));
+    levels.info = env->GetStaticIntField(clazz, env->GetStaticFieldID(clazz, "INFO", "I"));
+    levels.warn = env->GetStaticIntField(clazz, env->GetStaticFieldID(clazz, "WARN", "I"));
+    levels.error = env->GetStaticIntField(clazz, env->GetStaticFieldID(clazz, "ERROR", "I"));
+    levels.assert = env->GetStaticIntField(clazz, env->GetStaticFieldID(clazz, "ASSERT", "I"));
+                
+    return AndroidRuntime::registerNativeMethods(env, "android/util/Log", gMethods, NELEM(gMethods));
+}
+
+}; // namespace android
+
diff --git a/core/jni/android_util_Process.cpp b/core/jni/android_util_Process.cpp
new file mode 100644
index 0000000..3feccde
--- /dev/null
+++ b/core/jni/android_util_Process.cpp
@@ -0,0 +1,749 @@
+/* //device/libs/android_runtime/android_util_Process.cpp
+**
+** Copyright 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.
+*/
+
+#define LOG_TAG "Process"
+
+#include <utils/Log.h>
+#include <utils/IPCThreadState.h>
+#include <utils/ProcessState.h>
+#include <utils/IServiceManager.h>
+#include <utils/String8.h>
+#include <utils/Vector.h>
+
+#include <android_runtime/AndroidRuntime.h>
+
+#include "android_util_Binder.h"
+#include "JNIHelp.h"
+
+#include <sys/errno.h>
+#include <sys/resource.h>
+#include <sys/types.h>
+#include <dirent.h>
+#include <fcntl.h>
+#include <grp.h>
+#include <pwd.h>
+#include <signal.h>
+
+/* desktop Linux needs a little help with gettid() */
+#if defined(HAVE_GETTID) && !defined(HAVE_ANDROID_OS)
+#define __KERNEL__
+# include <linux/unistd.h>
+#ifdef _syscall0
+_syscall0(pid_t,gettid)
+#else
+pid_t gettid() { return syscall(__NR_gettid);}
+#endif
+#undef __KERNEL__
+#endif
+
+using namespace android;
+
+static void signalExceptionForPriorityError(JNIEnv* env, jobject obj, int err)
+{
+    switch (err) {
+        case EINVAL:
+            jniThrowException(env, "java/lang/IllegalArgumentException", NULL);
+            break;
+        case ESRCH:
+            jniThrowException(env, "java/lang/IllegalArgumentException", "Given thread does not exist");
+            break;
+        case EPERM:
+            jniThrowException(env, "java/lang/SecurityException", "No permission to modify given thread");
+            break;
+        case EACCES:
+            jniThrowException(env, "java/lang/SecurityException", "No permission to set to given priority");
+            break;
+        default:
+            jniThrowException(env, "java/lang/RuntimeException", "Unknown error");
+            break;
+    }
+}
+
+static void fakeProcessEntry(void* arg)
+{
+    String8* cls = (String8*)arg;
+    
+    AndroidRuntime* jr = AndroidRuntime::getRuntime();
+    jr->callMain(cls->string(), 0, NULL);
+        
+    delete cls;
+}
+
+jint android_os_Process_myPid(JNIEnv* env, jobject clazz)
+{
+    return getpid();
+}
+
+jint android_os_Process_myUid(JNIEnv* env, jobject clazz)
+{
+    return getuid();
+}
+
+jint android_os_Process_myTid(JNIEnv* env, jobject clazz)
+{
+#ifdef HAVE_GETTID
+    return gettid();
+#else
+    return getpid();
+#endif
+}
+
+jint android_os_Process_getUidForName(JNIEnv* env, jobject clazz, jstring name)
+{
+    if (name == NULL) {
+        jniThrowException(env, "java/lang/NullPointerException", NULL);
+        return -1;
+    }
+    
+    const jchar* str16 = env->GetStringCritical(name, 0);
+    String8 name8;
+    if (str16) {
+        name8 = String8(str16, env->GetStringLength(name));
+        env->ReleaseStringCritical(name, str16);
+    }
+
+    const size_t N = name8.size();
+    if (N > 0) {
+        const char* str = name8.string();
+        for (size_t i=0; i<N; i++) {
+            if (str[i] < '0' || str[i] > '9') {
+                struct passwd* pwd = getpwnam(str);
+                if (pwd == NULL) {
+                    return -1;
+                }
+                return pwd->pw_uid;
+            }
+        }
+        return atoi(str);
+    }
+    return -1;
+}
+
+jint android_os_Process_getGidForName(JNIEnv* env, jobject clazz, jstring name)
+{
+    if (name == NULL) {
+        jniThrowException(env, "java/lang/NullPointerException", NULL);
+        return -1;
+    }
+    
+    const jchar* str16 = env->GetStringCritical(name, 0);
+    String8 name8;
+    if (str16) {
+        name8 = String8(str16, env->GetStringLength(name));
+        env->ReleaseStringCritical(name, str16);
+    }
+
+    const size_t N = name8.size();
+    if (N > 0) {
+        const char* str = name8.string();
+        for (size_t i=0; i<N; i++) {
+            if (str[i] < '0' || str[i] > '9') {
+                struct group* grp = getgrnam(str);
+                if (grp == NULL) {
+                    return -1;
+                }
+                return grp->gr_gid;
+            }
+        }
+        return atoi(str);
+    }
+    return -1;
+}
+
+void android_os_Process_setThreadPriority(JNIEnv* env, jobject clazz,
+                                              jint pid, jint pri)
+{
+    if (setpriority(PRIO_PROCESS, pid, pri) < 0) {
+        signalExceptionForPriorityError(env, clazz, errno);
+    }
+    //LOGI("Setting priority of %d: %d, getpriority returns %d\n",
+    //     pid, pri, getpriority(PRIO_PROCESS, pid));
+}
+
+void android_os_Process_setCallingThreadPriority(JNIEnv* env, jobject clazz,
+                                                        jint pri)
+{
+    jint tid = android_os_Process_myTid(env, clazz);
+    android_os_Process_setThreadPriority(env, clazz, tid, pri);
+}
+
+jint android_os_Process_getThreadPriority(JNIEnv* env, jobject clazz,
+                                              jint pid)
+{
+    errno = 0;
+    jint pri = getpriority(PRIO_PROCESS, pid);
+    if (errno != 0) {
+        signalExceptionForPriorityError(env, clazz, errno);
+    }
+    //LOGI("Returning priority of %d: %d\n", pid, pri);
+    return pri;
+}
+
+jboolean android_os_Process_setOomAdj(JNIEnv* env, jobject clazz,
+                                      jint pid, jint adj)
+{
+#ifdef HAVE_OOM_ADJ
+    if (ProcessState::self()->supportsProcesses()) {
+        char text[64];
+        sprintf(text, "/proc/%d/oom_adj", pid);
+        int fd = open(text, O_WRONLY);
+        if (fd >= 0) {
+            sprintf(text, "%d", adj);
+            write(fd, text, strlen(text));
+            close(fd);
+            return true;
+        }
+    }
+#endif
+    return false;
+}
+
+void android_os_Process_setArgV0(JNIEnv* env, jobject clazz, jstring name)
+{
+    if (name == NULL) {
+        jniThrowException(env, "java/lang/NullPointerException", NULL);
+        return;
+    }
+    
+    const jchar* str = env->GetStringCritical(name, 0);
+    String8 name8;
+    if (str) {
+        name8 = String8(str, env->GetStringLength(name));
+        env->ReleaseStringCritical(name, str);
+    }
+
+    if (name8.size() > 0) {
+        ProcessState::self()->setArgV0(name8.string());
+    }
+}
+
+jint android_os_Process_setUid(JNIEnv* env, jobject clazz, jint uid)
+{
+    #if HAVE_ANDROID_OS
+    return setuid(uid) == 0 ? 0 : errno;
+    #else
+    return ENOSYS;
+    #endif
+}
+
+jint android_os_Process_setGid(JNIEnv* env, jobject clazz, jint uid)
+{
+    #if HAVE_ANDROID_OS
+    return setgid(uid) == 0 ? 0 : errno;
+    #else
+    return ENOSYS;
+    #endif
+}
+
+jboolean android_os_Process_supportsProcesses(JNIEnv* env, jobject clazz)
+{
+    return ProcessState::self()->supportsProcesses();
+}
+
+static int pid_compare(const void* v1, const void* v2)
+{
+    //LOGI("Compare %d vs %d\n", *((const jint*)v1), *((const jint*)v2));
+    return *((const jint*)v1) - *((const jint*)v2);
+}
+
+jint android_os_Process_getFreeMemory(JNIEnv* env, jobject clazz)
+{
+    int fd = open("/proc/meminfo", O_RDONLY);
+    
+    if (fd < 0) {
+        LOGW("Unable to open /proc/meminfo");
+        return -1;
+    }
+    
+    char buffer[256];
+    const int len = read(fd, buffer, sizeof(buffer)-1);
+    close(fd);
+    
+    if (len < 0) {
+        LOGW("Unable to read /proc/meminfo");
+        return -1;
+    }
+    buffer[len] = 0;
+
+    int numFound = 0;
+    int mem = 0;
+    
+    static const char* const sums[] = { "MemFree:", "Cached:", NULL };
+    static const int sumsLen[] = { strlen("MemFree:"), strlen("Cached:"), NULL };
+    
+    char* p = buffer;
+    while (*p && numFound < 2) {
+        int i = 0;
+        while (sums[i]) {
+            if (strncmp(p, sums[i], sumsLen[i]) == 0) {
+                p += sumsLen[i];
+                while (*p == ' ') p++;
+                char* num = p;
+                while (*p >= '0' && *p <= '9') p++;
+                if (*p != 0) {
+                    *p = 0;
+                    p++;
+                    if (*p == 0) p--;
+                }
+                mem += atoi(num) * 1024;
+                numFound++;
+                break;
+            }
+            i++;
+        }
+        p++;
+    }
+    
+    return numFound > 0 ? mem : -1;
+}
+
+void android_os_Process_readProcLines(JNIEnv* env, jobject clazz, jstring fileStr,
+                                      jobjectArray reqFields, jlongArray outFields)
+{
+    //LOGI("getMemInfo: %p %p", reqFields, outFields);
+    
+    if (fileStr == NULL || reqFields == NULL || outFields == NULL) {
+        jniThrowException(env, "java/lang/NullPointerException", NULL);
+        return;
+    }
+    
+    const char* file8 = env->GetStringUTFChars(fileStr, NULL);
+    if (file8 == NULL) {
+        return;
+    }
+    String8 file(file8);
+    env->ReleaseStringUTFChars(fileStr, file8);
+    
+    jsize count = env->GetArrayLength(reqFields);
+    if (count > env->GetArrayLength(outFields)) {
+        jniThrowException(env, "java/lang/IllegalArgumentException", "Array lengths differ");
+        return;
+    }
+    
+    Vector<String8> fields;
+    int i;
+    
+    for (i=0; i<count; i++) {
+        jobject obj = env->GetObjectArrayElement(reqFields, i);
+        if (obj != NULL) {
+            const char* str8 = env->GetStringUTFChars((jstring)obj, NULL);
+            //LOGI("String at %d: %p = %s", i, obj, str8);
+            if (str8 == NULL) {
+                jniThrowException(env, "java/lang/NullPointerException", "Element in reqFields");
+                return;
+            }
+            fields.add(String8(str8));
+            env->ReleaseStringUTFChars((jstring)obj, str8);
+        } else {
+            jniThrowException(env, "java/lang/NullPointerException", "Element in reqFields");
+            return;
+        }
+    }
+    
+    jlong* sizesArray = env->GetLongArrayElements(outFields, 0);
+    if (sizesArray == NULL) {
+        return;
+    }
+    
+    //LOGI("Clearing %d sizes", count);
+    for (i=0; i<count; i++) {
+        sizesArray[i] = 0;
+    }
+    
+    int fd = open(file.string(), O_RDONLY);
+    
+    if (fd >= 0) {
+        const size_t BUFFER_SIZE = 2048;
+        char* buffer = (char*)malloc(BUFFER_SIZE);
+        int len = read(fd, buffer, BUFFER_SIZE-1);
+        close(fd);
+        
+        if (len < 0) {
+            LOGW("Unable to read %s", file.string());
+            len = 0;
+        }
+        buffer[len] = 0;
+    
+        int foundCount = 0;
+        
+        char* p = buffer;
+        while (*p && foundCount < count) {
+            bool skipToEol = true;
+            //LOGI("Parsing at: %s", p);
+            for (i=0; i<count; i++) {
+                const String8& field = fields[i];
+                if (strncmp(p, field.string(), field.length()) == 0) {
+                    p += field.length();
+                    while (*p == ' ') p++;
+                    char* num = p;
+                    while (*p >= '0' && *p <= '9') p++;
+                    skipToEol = *p != '\n';
+                    if (*p != 0) {
+                        *p = 0;
+                        p++;
+                    }
+                    char* end;
+                    sizesArray[i] = strtoll(num, &end, 10);
+                    //LOGI("Field %s = %d", field.string(), sizesArray[i]);
+                    foundCount++;
+                    break;
+                }
+            }
+            if (skipToEol) {
+                while (*p && *p != '\n') {
+                    p++;
+                }
+                if (*p == '\n') {
+                    p++;
+                }
+            }
+        }
+        
+        free(buffer);
+    } else {
+        LOGW("Unable to open %s", file.string());
+    }
+    
+    //LOGI("Done!");
+    env->ReleaseLongArrayElements(outFields, sizesArray, 0);
+}
+
+jintArray android_os_Process_getPids(JNIEnv* env, jobject clazz,
+                                     jstring file, jintArray lastArray)
+{
+    if (file == NULL) {
+        jniThrowException(env, "java/lang/NullPointerException", NULL);
+        return NULL;
+    }
+    
+    const char* file8 = env->GetStringUTFChars(file, NULL);
+    if (file8 == NULL) {
+        jniThrowException(env, "java/lang/OutOfMemoryError", NULL);
+        return NULL;
+    }
+    
+    DIR* dirp = opendir(file8);
+    
+    env->ReleaseStringUTFChars(file, file8);
+    
+    if(dirp == NULL) {
+        return NULL;
+    }
+    
+    jsize curCount = 0;
+    jint* curData = NULL;
+    if (lastArray != NULL) {
+        curCount = env->GetArrayLength(lastArray);
+        curData = env->GetIntArrayElements(lastArray, 0);
+    }
+    
+    jint curPos = 0;
+    
+    struct dirent* entry;
+    while ((entry=readdir(dirp)) != NULL) {
+        const char* p = entry->d_name;
+        while (*p) {
+            if (*p < '0' || *p > '9') break;
+            p++;
+        }
+        if (*p != 0) continue;
+        
+        char* end;
+        int pid = strtol(entry->d_name, &end, 10);
+        //LOGI("File %s pid=%d\n", entry->d_name, pid);
+        if (curPos >= curCount) {
+            jsize newCount = (curCount == 0) ? 10 : (curCount*2);
+            jintArray newArray = env->NewIntArray(newCount);
+            if (newArray == NULL) {
+                closedir(dirp);
+                jniThrowException(env, "java/lang/OutOfMemoryError", NULL);
+                return NULL;
+            }
+            jint* newData = env->GetIntArrayElements(newArray, 0);
+            if (curData != NULL) {
+                memcpy(newData, curData, sizeof(jint)*curCount);
+                env->ReleaseIntArrayElements(lastArray, curData, 0);
+            }
+            lastArray = newArray;
+            curCount = newCount;
+            curData = newData;
+        }
+        
+        curData[curPos] = pid;
+        curPos++;
+    }
+    
+    closedir(dirp);
+    
+    if (curData != NULL && curPos > 0) {
+        qsort(curData, curPos, sizeof(jint), pid_compare);
+    }
+    
+    while (curPos < curCount) {
+        curData[curPos] = -1;
+        curPos++;
+    }
+    
+    if (curData != NULL) {
+        env->ReleaseIntArrayElements(lastArray, curData, 0);
+    }
+    
+    return lastArray;
+}
+
+enum {
+    PROC_TERM_MASK = 0xff,
+    PROC_ZERO_TERM = 0,
+    PROC_SPACE_TERM = ' ',
+    PROC_COMBINE = 0x100,
+    PROC_PARENS = 0x200,
+    PROC_OUT_STRING = 0x1000,
+    PROC_OUT_LONG = 0x2000,
+    PROC_OUT_FLOAT = 0x4000,
+};
+
+jboolean android_os_Process_readProcFile(JNIEnv* env, jobject clazz,
+        jstring file, jintArray format, jobjectArray outStrings,
+        jlongArray outLongs, jfloatArray outFloats)
+{
+    if (file == NULL || format == NULL) {
+        jniThrowException(env, "java/lang/NullPointerException", NULL);
+        return JNI_FALSE;
+    }
+    
+    const char* file8 = env->GetStringUTFChars(file, NULL);
+    if (file8 == NULL) {
+        jniThrowException(env, "java/lang/OutOfMemoryError", NULL);
+        return JNI_FALSE;
+    }
+    int fd = open(file8, O_RDONLY);
+    env->ReleaseStringUTFChars(file, file8);
+    
+    if (fd < 0) {
+        //LOGW("Unable to open process file: %s\n", file8);
+        return JNI_FALSE;
+    }
+    
+    char buffer[256];
+    const int len = read(fd, buffer, sizeof(buffer)-1);
+    close(fd);
+    
+    if (len < 0) {
+        //LOGW("Unable to open process file: %s fd=%d\n", file8, fd);
+        return JNI_FALSE;
+    }
+    buffer[len] = 0;
+    
+    //LOGI("Process file %s: %s\n", file8, buffer);
+    
+    const jsize NF = env->GetArrayLength(format);
+    const jsize NS = outStrings ? env->GetArrayLength(outStrings) : 0;
+    const jsize NL = outLongs ? env->GetArrayLength(outLongs) : 0;
+    const jsize NR = outFloats ? env->GetArrayLength(outFloats) : 0;
+    
+    jint* formatData = env->GetIntArrayElements(format, 0);
+    jlong* longsData = outLongs ?
+        env->GetLongArrayElements(outLongs, 0) : NULL;
+    jfloat* floatsData = outFloats ?
+        env->GetFloatArrayElements(outFloats, 0) : NULL;
+    if (formatData == NULL || (NL > 0 && longsData == NULL)
+            || (NR > 0 && floatsData == NULL)) {
+        if (formatData != NULL) {
+            env->ReleaseIntArrayElements(format, formatData, 0);
+        }
+        if (longsData != NULL) {
+            env->ReleaseLongArrayElements(outLongs, longsData, 0);
+        }
+        if (floatsData != NULL) {
+            env->ReleaseFloatArrayElements(outFloats, floatsData, 0);
+        }
+        jniThrowException(env, "java/lang/OutOfMemoryError", NULL);
+        return JNI_FALSE;
+    }
+
+    jsize i = 0;
+    jsize di = 0;
+    
+    jboolean res = JNI_TRUE;
+    
+    for (jsize fi=0; fi<NF; fi++) {
+        const jint mode = formatData[fi];
+        if ((mode&PROC_PARENS) != 0) {
+            i++;
+        }
+        const char term = (char)(mode&PROC_TERM_MASK);
+        const jsize start = i;
+        if (i >= len) {
+            res = JNI_FALSE;
+            break;
+        }
+        
+        jsize end = -1;
+        if ((mode&PROC_PARENS) != 0) {
+            while (buffer[i] != ')' && i < len) {
+                i++;
+            }
+            end = i;
+            i++;
+        }
+        while (buffer[i] != term && i < len) {
+            i++;
+        }
+        if (end < 0) {
+            end = i;
+        }
+        
+        if (i < len) {
+            i++;
+            if ((mode&PROC_COMBINE) != 0) {
+                while (buffer[i] == term && i < len) {
+                    i++;
+                }
+            }
+        }
+        
+        //LOGI("Field %d: %d-%d dest=%d mode=0x%x\n", i, start, end, di, mode);
+        
+        if ((mode&(PROC_OUT_FLOAT|PROC_OUT_LONG|PROC_OUT_STRING)) != 0) {
+            char c = buffer[end];
+            buffer[end] = 0;
+            if ((mode&PROC_OUT_FLOAT) != 0 && di < NR) {
+                char* end;
+                floatsData[di] = strtof(buffer+start, &end);
+            }
+            if ((mode&PROC_OUT_LONG) != 0 && di < NL) {
+                char* end;
+                longsData[di] = strtoll(buffer+start, &end, 10);
+            }
+            if ((mode&PROC_OUT_STRING) != 0 && di < NS) {
+                jstring str = env->NewStringUTF(buffer+start);
+                env->SetObjectArrayElement(outStrings, di, str);
+            }
+            buffer[end] = c;
+            di++;
+        }
+    }
+    
+    env->ReleaseIntArrayElements(format, formatData, 0);
+    if (longsData != NULL) {
+        env->ReleaseLongArrayElements(outLongs, longsData, 0);
+    }
+    if (floatsData != NULL) {
+        env->ReleaseFloatArrayElements(outFloats, floatsData, 0);
+    }
+    
+    return res;
+}
+
+void android_os_Process_setApplicationObject(JNIEnv* env, jobject clazz,
+                                             jobject binderObject)
+{
+    if (binderObject == NULL) {
+        jniThrowException(env, "java/lang/NullPointerException", NULL);
+        return;
+    }
+
+    sp<IBinder> binder = ibinderForJavaObject(env, binderObject);
+}
+
+void android_os_Process_sendSignal(JNIEnv* env, jobject clazz, jint pid, jint sig)
+{
+    if (pid > 0) {
+        LOGI("Sending signal. PID: %d SIG: %d", pid, sig);
+        kill(pid, sig);
+    }
+}
+
+static jlong android_os_Process_getElapsedCpuTime(JNIEnv* env, jobject clazz)
+{
+    struct timespec ts;
+
+    int res = clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &ts);
+    
+    if (res != 0) {
+        return (jlong) 0;
+    } 
+    
+    nsecs_t when = seconds_to_nanoseconds(ts.tv_sec) + ts.tv_nsec;
+    return (jlong) nanoseconds_to_milliseconds(when);
+}
+
+static jlong android_os_Process_getPss(JNIEnv* env, jobject clazz, jint pid)
+{
+    char filename[64];
+
+    snprintf(filename, sizeof(filename), "/proc/%d/smaps", pid);
+
+    FILE * file = fopen(filename, "r");
+    if (!file) {
+        return (jlong) -1;
+    }
+
+    // Tally up all of the Pss from the various maps
+    char line[256];
+    jlong pss = 0;
+    while (fgets(line, sizeof(line), file)) {
+        jlong v;
+        if (sscanf(line, "Pss: %lld kB", &v) == 1) {
+            pss += v;
+        }
+    }
+
+    fclose(file);
+
+    // Return the Pss value in bytes, not kilobytes
+    return pss * 1024;
+}
+
+static const JNINativeMethod methods[] = {
+    {"myPid",       "()I", (void*)android_os_Process_myPid},
+    {"myTid",       "()I", (void*)android_os_Process_myTid},
+    {"myUid",       "()I", (void*)android_os_Process_myUid},
+    {"getUidForName",       "(Ljava/lang/String;)I", (void*)android_os_Process_getUidForName},
+    {"getGidForName",       "(Ljava/lang/String;)I", (void*)android_os_Process_getGidForName},
+    {"setThreadPriority",   "(II)V", (void*)android_os_Process_setThreadPriority},
+    {"setThreadPriority",   "(I)V", (void*)android_os_Process_setCallingThreadPriority},
+    {"getThreadPriority",   "(I)I", (void*)android_os_Process_getThreadPriority},
+    {"setOomAdj",   "(II)Z", (void*)android_os_Process_setOomAdj},
+    {"setArgV0",    "(Ljava/lang/String;)V", (void*)android_os_Process_setArgV0},
+    {"setUid", "(I)I", (void*)android_os_Process_setUid},
+    {"setGid", "(I)I", (void*)android_os_Process_setGid},
+    {"sendSignal", "(II)V", (void*)android_os_Process_sendSignal},
+    {"supportsProcesses", "()Z", (void*)android_os_Process_supportsProcesses},
+    {"getFreeMemory", "()I", (void*)android_os_Process_getFreeMemory},
+    {"readProcLines", "(Ljava/lang/String;[Ljava/lang/String;[J)V", (void*)android_os_Process_readProcLines},
+    {"getPids", "(Ljava/lang/String;[I)[I", (void*)android_os_Process_getPids},
+    {"readProcFile", "(Ljava/lang/String;[I[Ljava/lang/String;[J[F)Z", (void*)android_os_Process_readProcFile},
+    {"getElapsedCpuTime", "()J", (void*)android_os_Process_getElapsedCpuTime},
+    {"getPss", "(I)J", (void*)android_os_Process_getPss},
+    //{"setApplicationObject", "(Landroid/os/IBinder;)V", (void*)android_os_Process_setApplicationObject},
+};
+
+const char* const kProcessPathName = "android/os/Process";
+
+int register_android_os_Process(JNIEnv* env)
+{
+    jclass clazz;
+
+    clazz = env->FindClass(kProcessPathName);
+    LOG_FATAL_IF(clazz == NULL, "Unable to find class android.os.Process");
+
+    return AndroidRuntime::registerNativeMethods(
+        env, kProcessPathName,
+        methods, NELEM(methods));
+}
+
diff --git a/core/jni/android_util_StringBlock.cpp b/core/jni/android_util_StringBlock.cpp
new file mode 100644
index 0000000..ffb271c
--- /dev/null
+++ b/core/jni/android_util_StringBlock.cpp
@@ -0,0 +1,204 @@
+/* //device/libs/android_runtime/android_util_StringBlock.cpp
+**
+** Copyright 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.
+*/
+
+#define LOG_TAG "StringBlock"
+
+#include "jni.h"
+#include <utils/misc.h>
+#include <android_runtime/AndroidRuntime.h>
+#include <utils/Log.h>
+
+#include <utils/ResourceTypes.h>
+
+#include <stdio.h>
+
+namespace android {
+
+// ----------------------------------------------------------------------------
+
+static void doThrow(JNIEnv* env, const char* exc, const char* msg = NULL)
+{
+    jclass npeClazz;
+
+    npeClazz = env->FindClass(exc);
+    LOG_FATAL_IF(npeClazz == NULL, "Unable to find class %s", exc);
+
+    env->ThrowNew(npeClazz, msg);
+}
+
+static jint android_content_StringBlock_nativeCreate(JNIEnv* env, jobject clazz,
+                                                  jbyteArray bArray,
+                                                  jint off, jint len)
+{
+    if (bArray == NULL) {
+        doThrow(env, "java/lang/NullPointerException");
+        return 0;
+    }
+
+    jsize bLen = env->GetArrayLength(bArray);
+    if (off < 0 || off >= bLen || len < 0 || len > bLen || (off+len) > bLen) {
+        doThrow(env, "java/lang/IndexOutOfBoundsException");
+        return 0;
+    }
+
+    jbyte* b = env->GetByteArrayElements(bArray, NULL);
+    ResStringPool* osb = new ResStringPool(b+off, len, true);
+    env->ReleaseByteArrayElements(bArray, b, 0);
+
+    if (osb == NULL || osb->getError() != NO_ERROR) {
+        doThrow(env, "java/lang/IllegalArgumentException");
+        return 0;
+    }
+
+    return (jint)osb;
+}
+
+static jint android_content_StringBlock_nativeGetSize(JNIEnv* env, jobject clazz,
+                                                   jint token)
+{
+    ResStringPool* osb = (ResStringPool*)token;
+    if (osb == NULL) {
+        doThrow(env, "java/lang/NullPointerException");
+        return 0;
+    }
+
+    return osb->size();
+}
+
+static jstring android_content_StringBlock_nativeGetString(JNIEnv* env, jobject clazz,
+                                                        jint token, jint idx)
+{
+    ResStringPool* osb = (ResStringPool*)token;
+    if (osb == NULL) {
+        doThrow(env, "java/lang/NullPointerException");
+        return 0;
+    }
+
+    size_t len;
+    const char16_t* str = osb->stringAt(idx, &len);
+    if (str == NULL) {
+        doThrow(env, "java/lang/IndexOutOfBoundsException");
+        return 0;
+    }
+
+    return env->NewString((const jchar*)str, len);
+}
+
+static jintArray android_content_StringBlock_nativeGetStyle(JNIEnv* env, jobject clazz,
+                                                         jint token, jint idx)
+{
+    ResStringPool* osb = (ResStringPool*)token;
+    if (osb == NULL) {
+        doThrow(env, "java/lang/NullPointerException");
+        return NULL;
+    }
+
+    const ResStringPool_span* spans = osb->styleAt(idx);
+    if (spans == NULL) {
+        return NULL;
+    }
+
+    const ResStringPool_span* pos = spans;
+    int num = 0;
+    while (pos->name.index != ResStringPool_span::END) {
+        num++;
+        pos++;
+    }
+
+    if (num == 0) {
+        return NULL;
+    }
+
+    jintArray array = env->NewIntArray((num*sizeof(ResStringPool_span))/sizeof(jint));
+    if (array == NULL) {
+        doThrow(env, "java/lang/OutOfMemoryError");
+        return NULL;
+    }
+
+    num = 0;
+    static const int numInts = sizeof(ResStringPool_span)/sizeof(jint);
+    while (spans->name.index != ResStringPool_span::END) {
+        env->SetIntArrayRegion(array,
+                                  num*numInts, numInts,
+                                  (jint*)spans);
+        spans++;
+        num++;
+    }
+
+    return array;
+}
+
+static jint android_content_StringBlock_nativeIndexOfString(JNIEnv* env, jobject clazz,
+                                                         jint token, jstring str)
+{
+    ResStringPool* osb = (ResStringPool*)token;
+    if (osb == NULL || str == NULL) {
+        doThrow(env, "java/lang/NullPointerException");
+        return 0;
+    }
+
+    const char16_t* str16 = env->GetStringChars(str, NULL);
+    jsize strLen = env->GetStringLength(str);
+
+    ssize_t idx = osb->indexOfString(str16, strLen);
+
+    env->ReleaseStringChars(str, str16);
+
+    return idx;
+}
+
+static void android_content_StringBlock_nativeDestroy(JNIEnv* env, jobject clazz,
+                                                   jint token)
+{
+    ResStringPool* osb = (ResStringPool*)token;
+    if (osb == NULL) {
+        doThrow(env, "java/lang/NullPointerException");
+        return;
+    }
+
+    delete osb;
+}
+
+// ----------------------------------------------------------------------------
+
+/*
+ * JNI registration.
+ */
+static JNINativeMethod gStringBlockMethods[] = {
+    /* name, signature, funcPtr */
+    { "nativeCreate",      "([BII)I",
+            (void*) android_content_StringBlock_nativeCreate },
+    { "nativeGetSize",      "(I)I",
+            (void*) android_content_StringBlock_nativeGetSize },
+    { "nativeGetString",    "(II)Ljava/lang/String;",
+            (void*) android_content_StringBlock_nativeGetString },
+    { "nativeGetStyle",    "(II)[I",
+            (void*) android_content_StringBlock_nativeGetStyle },
+    { "nativeIndexOfString","(ILjava/lang/String;)I",
+            (void*) android_content_StringBlock_nativeIndexOfString },
+    { "nativeDestroy",      "(I)V",
+            (void*) android_content_StringBlock_nativeDestroy },
+};
+
+int register_android_content_StringBlock(JNIEnv* env)
+{
+    return AndroidRuntime::registerNativeMethods(env,
+            "android/content/res/StringBlock", gStringBlockMethods, NELEM(gStringBlockMethods));
+}
+
+}; // namespace android
+
diff --git a/core/jni/android_util_XmlBlock.cpp b/core/jni/android_util_XmlBlock.cpp
new file mode 100644
index 0000000..8887fdc
--- /dev/null
+++ b/core/jni/android_util_XmlBlock.cpp
@@ -0,0 +1,426 @@
+/* //device/libs/android_runtime/android_util_XmlBlock.cpp
+**
+** Copyright 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.
+*/
+
+#define LOG_TAG "XmlBlock"
+
+#include "jni.h"
+#include <utils/misc.h>
+#include <android_runtime/AndroidRuntime.h>
+#include <utils/AssetManager.h>
+#include <utils/Log.h>
+
+#include <utils/ResourceTypes.h>
+
+#include <stdio.h>
+
+namespace android {
+
+// ----------------------------------------------------------------------------
+
+static void doThrow(JNIEnv* env, const char* exc, const char* msg = NULL)
+{
+    jclass npeClazz;
+
+    npeClazz = env->FindClass(exc);
+    LOG_FATAL_IF(npeClazz == NULL, "Unable to find class %s", exc);
+
+    env->ThrowNew(npeClazz, msg);
+}
+
+static jint android_content_XmlBlock_nativeCreate(JNIEnv* env, jobject clazz,
+                                               jbyteArray bArray,
+                                               jint off, jint len)
+{
+    if (bArray == NULL) {
+        doThrow(env, "java/lang/NullPointerException");
+        return 0;
+    }
+
+    jsize bLen = env->GetArrayLength(bArray);
+    if (off < 0 || off >= bLen || len < 0 || len > bLen || (off+len) > bLen) {
+        doThrow(env, "java/lang/IndexOutOfBoundsException");
+        return 0;
+    }
+
+    jbyte* b = env->GetByteArrayElements(bArray, NULL);
+    ResXMLTree* osb = new ResXMLTree(b+off, len, true);
+    env->ReleaseByteArrayElements(bArray, b, 0);
+
+    if (osb == NULL || osb->getError() != NO_ERROR) {
+        doThrow(env, "java/lang/IllegalArgumentException");
+        return 0;
+    }
+
+    return (jint)osb;
+}
+
+static jint android_content_XmlBlock_nativeGetStringBlock(JNIEnv* env, jobject clazz,
+                                                       jint token)
+{
+    ResXMLTree* osb = (ResXMLTree*)token;
+    if (osb == NULL) {
+        doThrow(env, "java/lang/NullPointerException");
+        return 0;
+    }
+
+    return (jint)&osb->getStrings();
+}
+
+static jint android_content_XmlBlock_nativeCreateParseState(JNIEnv* env, jobject clazz,
+                                                          jint token)
+{
+    ResXMLTree* osb = (ResXMLTree*)token;
+    if (osb == NULL) {
+        doThrow(env, "java/lang/NullPointerException");
+        return 0;
+    }
+
+    ResXMLParser* st = new ResXMLParser(*osb);
+    if (st == NULL) {
+        doThrow(env, "java/lang/OutOfMemoryError");
+        return 0;
+    }
+
+    st->restart();
+
+    return (jint)st;
+}
+
+static jint android_content_XmlBlock_nativeNext(JNIEnv* env, jobject clazz,
+                                             jint token)
+{
+    ResXMLParser* st = (ResXMLParser*)token;
+    if (st == NULL) {
+        return ResXMLParser::END_DOCUMENT;
+    }
+
+    do {
+        jint code = (jint)st->next();
+        switch (code) {
+            case ResXMLParser::START_TAG:
+                return 2;
+            case ResXMLParser::END_TAG:
+                return 3;
+            case ResXMLParser::TEXT:
+                return 4;
+            case ResXMLParser::START_DOCUMENT:
+                return 0;
+            case ResXMLParser::END_DOCUMENT:
+                return 1;
+            case ResXMLParser::BAD_DOCUMENT:
+                goto bad;
+        }
+    } while (true);
+    
+bad:
+    doThrow(env, "org/xmlpull/v1/XmlPullParserException",
+            "Corrupt XML binary file");
+    return ResXMLParser::BAD_DOCUMENT;
+}
+
+static jint android_content_XmlBlock_nativeGetNamespace(JNIEnv* env, jobject clazz,
+                                                   jint token)
+{
+    ResXMLParser* st = (ResXMLParser*)token;
+    if (st == NULL) {
+        return -1;
+    }
+    
+    return (jint)st->getElementNamespaceID();
+}
+
+static jint android_content_XmlBlock_nativeGetName(JNIEnv* env, jobject clazz,
+                                                jint token)
+{
+    ResXMLParser* st = (ResXMLParser*)token;
+    if (st == NULL) {
+        return -1;
+    }
+
+    return (jint)st->getElementNameID();
+}
+
+static jint android_content_XmlBlock_nativeGetText(JNIEnv* env, jobject clazz,
+                                                jint token)
+{
+    ResXMLParser* st = (ResXMLParser*)token;
+    if (st == NULL) {
+        return -1;
+    }
+
+    return (jint)st->getTextID();
+}
+
+static jint android_content_XmlBlock_nativeGetLineNumber(JNIEnv* env, jobject clazz,
+                                                         jint token)
+{
+    ResXMLParser* st = (ResXMLParser*)token;
+    if (st == NULL) {
+        doThrow(env, "java/lang/NullPointerException");
+        return 0;
+    }
+
+    return (jint)st->getLineNumber();
+}
+
+static jint android_content_XmlBlock_nativeGetAttributeCount(JNIEnv* env, jobject clazz,
+                                                          jint token)
+{
+    ResXMLParser* st = (ResXMLParser*)token;
+    if (st == NULL) {
+        doThrow(env, "java/lang/NullPointerException");
+        return 0;
+    }
+
+    return (jint)st->getAttributeCount();
+}
+
+static jint android_content_XmlBlock_nativeGetAttributeNamespace(JNIEnv* env, jobject clazz,
+                                                                 jint token, jint idx)
+{
+    ResXMLParser* st = (ResXMLParser*)token;
+    if (st == NULL) {
+        doThrow(env, "java/lang/NullPointerException");
+        return 0;
+    }
+    
+    return (jint)st->getAttributeNamespaceID(idx);
+}
+
+static jint android_content_XmlBlock_nativeGetAttributeName(JNIEnv* env, jobject clazz,
+                                                         jint token, jint idx)
+{
+    ResXMLParser* st = (ResXMLParser*)token;
+    if (st == NULL) {
+        doThrow(env, "java/lang/NullPointerException");
+        return 0;
+    }
+
+    return (jint)st->getAttributeNameID(idx);
+}
+
+static jint android_content_XmlBlock_nativeGetAttributeResource(JNIEnv* env, jobject clazz,
+                                                             jint token, jint idx)
+{
+    ResXMLParser* st = (ResXMLParser*)token;
+    if (st == NULL) {
+        doThrow(env, "java/lang/NullPointerException");
+        return 0;
+    }
+
+    return (jint)st->getAttributeNameResID(idx);
+}
+
+static jint android_content_XmlBlock_nativeGetAttributeDataType(JNIEnv* env, jobject clazz,
+                                                                jint token, jint idx)
+{
+    ResXMLParser* st = (ResXMLParser*)token;
+    if (st == NULL) {
+        doThrow(env, "java/lang/NullPointerException");
+        return 0;
+    }
+
+    return (jint)st->getAttributeDataType(idx);
+}
+
+static jint android_content_XmlBlock_nativeGetAttributeData(JNIEnv* env, jobject clazz,
+                                                            jint token, jint idx)
+{
+    ResXMLParser* st = (ResXMLParser*)token;
+    if (st == NULL) {
+        doThrow(env, "java/lang/NullPointerException");
+        return 0;
+    }
+
+    return (jint)st->getAttributeData(idx);
+}
+
+static jint android_content_XmlBlock_nativeGetAttributeStringValue(JNIEnv* env, jobject clazz,
+                                                                   jint token, jint idx)
+{
+    ResXMLParser* st = (ResXMLParser*)token;
+    if (st == NULL) {
+        doThrow(env, "java/lang/NullPointerException");
+        return 0;
+    }
+
+    return (jint)st->getAttributeValueStringID(idx);
+}
+
+static jint android_content_XmlBlock_nativeGetAttributeIndex(JNIEnv* env, jobject clazz,
+                                                             jint token,
+                                                             jstring ns, jstring name)
+{
+    ResXMLParser* st = (ResXMLParser*)token;
+    if (st == NULL || name == NULL) {
+        doThrow(env, "java/lang/NullPointerException");
+        return 0;
+    }
+
+    const char16_t* ns16 = NULL;
+    jsize nsLen = 0;
+    if (ns) {
+        ns16 = env->GetStringChars(ns, NULL);
+        nsLen = env->GetStringLength(ns);
+    }
+    
+    const char16_t* name16 = env->GetStringChars(name, NULL);
+    jsize nameLen = env->GetStringLength(name);
+
+    jint idx = (jint)st->indexOfAttribute(ns16, nsLen, name16, nameLen);
+
+    if (ns) {
+        env->ReleaseStringChars(ns, ns16);
+    }
+    env->ReleaseStringChars(name, name16);
+
+    return idx;
+}
+
+static jint android_content_XmlBlock_nativeGetIdAttribute(JNIEnv* env, jobject clazz,
+                                                          jint token)
+{
+    ResXMLParser* st = (ResXMLParser*)token;
+    if (st == NULL) {
+        doThrow(env, "java/lang/NullPointerException");
+        return 0;
+    }
+
+    ssize_t idx = st->indexOfID();
+    return idx >= 0 ? (jint)st->getAttributeValueStringID(idx) : -1;
+}
+
+static jint android_content_XmlBlock_nativeGetClassAttribute(JNIEnv* env, jobject clazz,
+                                                             jint token)
+{
+    ResXMLParser* st = (ResXMLParser*)token;
+    if (st == NULL) {
+        doThrow(env, "java/lang/NullPointerException");
+        return 0;
+    }
+    
+    ssize_t idx = st->indexOfClass();
+    return idx >= 0 ? (jint)st->getAttributeValueStringID(idx) : -1;
+}
+
+static jint android_content_XmlBlock_nativeGetStyleAttribute(JNIEnv* env, jobject clazz,
+                                                             jint token)
+{
+    ResXMLParser* st = (ResXMLParser*)token;
+    if (st == NULL) {
+        doThrow(env, "java/lang/NullPointerException");
+        return 0;
+    }
+
+    ssize_t idx = st->indexOfStyle();
+    if (idx < 0) {
+        return 0;
+    }
+
+    Res_value value;
+    if (st->getAttributeValue(idx, &value) < 0) {
+        return 0;
+    }
+
+    return value.dataType == value.TYPE_REFERENCE 
+        || value.dataType == value.TYPE_ATTRIBUTE
+        ? value.data : 0;
+}
+
+static void android_content_XmlBlock_nativeDestroyParseState(JNIEnv* env, jobject clazz,
+                                                          jint token)
+{
+    ResXMLParser* st = (ResXMLParser*)token;
+    if (st == NULL) {
+        doThrow(env, "java/lang/NullPointerException");
+        return;
+    }
+
+    delete st;
+}
+
+static void android_content_XmlBlock_nativeDestroy(JNIEnv* env, jobject clazz,
+                                                   jint token)
+{
+    ResXMLTree* osb = (ResXMLTree*)token;
+    if (osb == NULL) {
+        doThrow(env, "java/lang/NullPointerException");
+        return;
+    }
+
+    delete osb;
+}
+
+// ----------------------------------------------------------------------------
+
+/*
+ * JNI registration.
+ */
+static JNINativeMethod gXmlBlockMethods[] = {
+    /* name, signature, funcPtr */
+    { "nativeCreate",               "([BII)I",
+            (void*) android_content_XmlBlock_nativeCreate },
+    { "nativeGetStringBlock",       "(I)I",
+            (void*) android_content_XmlBlock_nativeGetStringBlock },
+    { "nativeCreateParseState",     "(I)I",
+            (void*) android_content_XmlBlock_nativeCreateParseState },
+    { "nativeNext",                 "(I)I",
+            (void*) android_content_XmlBlock_nativeNext },
+    { "nativeGetNamespace",         "(I)I",
+            (void*) android_content_XmlBlock_nativeGetNamespace },
+    { "nativeGetName",              "(I)I",
+            (void*) android_content_XmlBlock_nativeGetName },
+    { "nativeGetText",              "(I)I",
+            (void*) android_content_XmlBlock_nativeGetText },
+    { "nativeGetLineNumber",        "(I)I",
+            (void*) android_content_XmlBlock_nativeGetLineNumber },
+    { "nativeGetAttributeCount",    "(I)I",
+            (void*) android_content_XmlBlock_nativeGetAttributeCount },
+    { "nativeGetAttributeNamespace","(II)I",
+            (void*) android_content_XmlBlock_nativeGetAttributeNamespace },
+    { "nativeGetAttributeName",     "(II)I",
+            (void*) android_content_XmlBlock_nativeGetAttributeName },
+    { "nativeGetAttributeResource", "(II)I",
+            (void*) android_content_XmlBlock_nativeGetAttributeResource },
+    { "nativeGetAttributeDataType", "(II)I",
+            (void*) android_content_XmlBlock_nativeGetAttributeDataType },
+    { "nativeGetAttributeData",    "(II)I",
+            (void*) android_content_XmlBlock_nativeGetAttributeData },
+    { "nativeGetAttributeStringValue", "(II)I",
+            (void*) android_content_XmlBlock_nativeGetAttributeStringValue },
+    { "nativeGetAttributeIndex",    "(ILjava/lang/String;Ljava/lang/String;)I",
+            (void*) android_content_XmlBlock_nativeGetAttributeIndex },
+    { "nativeGetIdAttribute",      "(I)I",
+            (void*) android_content_XmlBlock_nativeGetIdAttribute },
+    { "nativeGetClassAttribute",   "(I)I",
+            (void*) android_content_XmlBlock_nativeGetClassAttribute },
+    { "nativeGetStyleAttribute",   "(I)I",
+            (void*) android_content_XmlBlock_nativeGetStyleAttribute },
+    { "nativeDestroyParseState",    "(I)V",
+            (void*) android_content_XmlBlock_nativeDestroyParseState },
+    { "nativeDestroy",              "(I)V",
+            (void*) android_content_XmlBlock_nativeDestroy },
+};
+
+int register_android_content_XmlBlock(JNIEnv* env)
+{
+    return AndroidRuntime::registerNativeMethods(env,
+            "android/content/res/XmlBlock", gXmlBlockMethods, NELEM(gXmlBlockMethods));
+}
+
+}; // namespace android
+
diff --git a/core/jni/android_view_Display.cpp b/core/jni/android_view_Display.cpp
new file mode 100644
index 0000000..bb7b5ef
--- /dev/null
+++ b/core/jni/android_view_Display.cpp
@@ -0,0 +1,133 @@
+/*
+ * Copyright (C) 2007 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.
+ */
+
+#include <stdio.h>
+#include <assert.h>
+
+#include <ui/SurfaceComposerClient.h>
+#include <ui/PixelFormat.h>
+#include <ui/DisplayInfo.h>
+
+#include "jni.h"
+#include <android_runtime/AndroidRuntime.h>
+#include <utils/misc.h>
+
+// ----------------------------------------------------------------------------
+
+namespace android {
+
+// ----------------------------------------------------------------------------
+
+struct offsets_t {
+    jfieldID display;
+    jfieldID pixelFormat;
+    jfieldID fps;
+    jfieldID density;
+    jfieldID xdpi;
+    jfieldID ydpi;
+};
+static offsets_t offsets;
+
+static void doThrow(JNIEnv* env, const char* exc, const char* msg = NULL)
+{
+    jclass npeClazz = env->FindClass(exc);
+    env->ThrowNew(npeClazz, msg);
+}
+
+// ----------------------------------------------------------------------------
+
+static void android_view_Display_init(
+        JNIEnv* env, jobject clazz, jint dpy)
+{
+    DisplayInfo info;
+    status_t err = SurfaceComposerClient::getDisplayInfo(DisplayID(dpy), &info);
+    if (err < 0) {
+        doThrow(env, "java/lang/IllegalArgumentException");
+        return;
+    }
+    env->SetIntField(clazz, offsets.pixelFormat,info.pixelFormatInfo.format);
+    env->SetFloatField(clazz, offsets.fps,      info.fps);
+    env->SetFloatField(clazz, offsets.density,  info.density);
+    env->SetFloatField(clazz, offsets.xdpi,     info.xdpi);
+    env->SetFloatField(clazz, offsets.ydpi,     info.ydpi);
+}
+
+static jint android_view_Display_getWidth(
+        JNIEnv* env, jobject clazz)
+{
+    DisplayID dpy = env->GetIntField(clazz, offsets.display);
+    return SurfaceComposerClient::getDisplayWidth(dpy);
+}
+
+static jint android_view_Display_getHeight(
+        JNIEnv* env, jobject clazz)
+{
+    DisplayID dpy = env->GetIntField(clazz, offsets.display);
+    return SurfaceComposerClient::getDisplayHeight(dpy);
+}
+
+static jint android_view_Display_getOrientation(
+        JNIEnv* env, jobject clazz)
+{
+    DisplayID dpy = env->GetIntField(clazz, offsets.display);
+    return SurfaceComposerClient::getDisplayOrientation(dpy);
+}
+
+static jint android_view_Display_getDisplayCount(
+        JNIEnv* env, jclass clazz)
+{
+    return SurfaceComposerClient::getNumberOfDisplays();
+}
+
+// ----------------------------------------------------------------------------
+
+const char* const kClassPathName = "android/view/Display";
+
+static void nativeClassInit(JNIEnv* env, jclass clazz);
+
+static JNINativeMethod gMethods[] = {
+    {   "nativeClassInit", "()V",
+            (void*)nativeClassInit },
+    {   "getDisplayCount", "()I",
+            (void*)android_view_Display_getDisplayCount },
+	{   "init", "(I)V",
+            (void*)android_view_Display_init },
+    {   "getWidth", "()I",
+            (void*)android_view_Display_getWidth },
+    {   "getHeight", "()I",
+            (void*)android_view_Display_getHeight },
+    {   "getOrientation", "()I",
+            (void*)android_view_Display_getOrientation }
+};
+
+void nativeClassInit(JNIEnv* env, jclass clazz)
+{
+    offsets.display     = env->GetFieldID(clazz, "mDisplay", "I");
+    offsets.pixelFormat = env->GetFieldID(clazz, "mPixelFormat", "I");
+    offsets.fps         = env->GetFieldID(clazz, "mRefreshRate", "F");
+    offsets.density     = env->GetFieldID(clazz, "mDensity", "F");
+    offsets.xdpi        = env->GetFieldID(clazz, "mDpiX", "F");
+    offsets.ydpi        = env->GetFieldID(clazz, "mDpiY", "F");
+}
+
+int register_android_view_Display(JNIEnv* env)
+{
+    return AndroidRuntime::registerNativeMethods(env,
+            kClassPathName, gMethods, NELEM(gMethods));
+}
+
+};
+
diff --git a/core/jni/android_view_Surface.cpp b/core/jni/android_view_Surface.cpp
new file mode 100644
index 0000000..8baaa84
--- /dev/null
+++ b/core/jni/android_view_Surface.cpp
@@ -0,0 +1,629 @@
+/*
+ * Copyright (C) 2007 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.
+ */
+
+#include <stdio.h>
+
+#include "android_util_Binder.h"
+
+#include <ui/SurfaceComposerClient.h>
+#include <ui/Region.h>
+#include <ui/Rect.h>
+
+#include <SkCanvas.h>
+#include <SkBitmap.h>
+
+#include "jni.h"
+#include <android_runtime/AndroidRuntime.h>
+#include <utils/misc.h>
+
+
+// ----------------------------------------------------------------------------
+
+namespace android {
+
+// ----------------------------------------------------------------------------
+
+static const char* const OutOfResourcesException =
+    "android/view/Surface$OutOfResourcesException";
+
+struct sso_t {
+    jfieldID client;
+};
+static sso_t sso;
+
+struct so_t {
+    jfieldID surface;
+    jfieldID saveCount;
+    jfieldID canvas;
+};
+static so_t so;
+
+struct ro_t {
+    jfieldID l;
+    jfieldID t;
+    jfieldID r;
+    jfieldID b;
+};
+static ro_t ro;
+
+struct po_t {
+    jfieldID x;
+    jfieldID y;
+};
+static po_t po;
+
+struct co_t {
+    jfieldID surfaceFormat;
+};
+static co_t co;
+
+struct no_t {
+    jfieldID native_canvas;
+    jfieldID native_region;
+    jfieldID native_parcel;
+};
+static no_t no;
+
+
+static __attribute__((noinline))
+void doThrow(JNIEnv* env, const char* exc, const char* msg = NULL)
+{
+    if (!env->ExceptionOccurred()) {
+        jclass npeClazz = env->FindClass(exc);
+        env->ThrowNew(npeClazz, msg);
+    }
+}
+
+// ----------------------------------------------------------------------------
+// ----------------------------------------------------------------------------
+// ----------------------------------------------------------------------------
+
+static void SurfaceSession_init(JNIEnv* env, jobject clazz)
+{
+    sp<SurfaceComposerClient> client = new SurfaceComposerClient;
+    client->incStrong(clazz);
+    env->SetIntField(clazz, sso.client, (int)client.get());
+}
+
+static void SurfaceSession_destroy(JNIEnv* env, jobject clazz)
+{
+    SurfaceComposerClient* client =
+            (SurfaceComposerClient*)env->GetIntField(clazz, sso.client);
+    if (client != 0) {
+        client->decStrong(clazz);
+        env->SetIntField(clazz, sso.client, 0);
+    }
+}
+
+static void SurfaceSession_kill(JNIEnv* env, jobject clazz)
+{
+    SurfaceComposerClient* client =
+            (SurfaceComposerClient*)env->GetIntField(clazz, sso.client);
+    if (client != 0) {
+        client->dispose();
+        client->decStrong(clazz);
+        env->SetIntField(clazz, sso.client, 0);
+    }
+}
+
+// ----------------------------------------------------------------------------
+
+static sp<Surface> getSurface(JNIEnv* env, jobject clazz)
+{
+    Surface* const p = (Surface*)env->GetIntField(clazz, so.surface);
+    return sp<Surface>(p);
+}
+
+static void setSurface(JNIEnv* env, jobject clazz, const sp<Surface>& surface)
+{
+    Surface* const p = (Surface*)env->GetIntField(clazz, so.surface);
+    if (surface.get()) {
+        surface->incStrong(clazz);
+    }
+    if (p) {
+        p->decStrong(clazz);
+    }
+    env->SetIntField(clazz, so.surface, (int)surface.get());
+}
+
+// ----------------------------------------------------------------------------
+
+static void Surface_init(
+        JNIEnv* env, jobject clazz, 
+        jobject session, jint pid, jint dpy, jint w, jint h, jint format, jint flags)
+{
+    if (session == NULL) {
+        doThrow(env, "java/lang/NullPointerException");
+        return;
+    }
+    
+    SurfaceComposerClient* client =
+            (SurfaceComposerClient*)env->GetIntField(session, sso.client);
+
+    sp<Surface> surface(client->createSurface(pid, dpy, w, h, format, flags));
+    if (surface == 0) {
+        doThrow(env, OutOfResourcesException);
+        return;
+    }
+    setSurface(env, clazz, surface);
+}
+
+static void Surface_initParcel(JNIEnv* env, jobject clazz, jobject argParcel)
+{
+    Parcel* parcel = (Parcel*)env->GetIntField(argParcel, no.native_parcel);
+    if (parcel == NULL) {
+        doThrow(env, "java/lang/NullPointerException", NULL);
+        return;
+    }
+    const sp<Surface>& rhs = Surface::readFromParcel(parcel);
+    setSurface(env, clazz, rhs);
+}
+
+static void Surface_clear(JNIEnv* env, jobject clazz, uintptr_t *ostack)
+{
+    setSurface(env, clazz, 0);
+}
+
+static jboolean Surface_isValid(JNIEnv* env, jobject clazz)
+{
+    const sp<Surface>& surface = getSurface(env, clazz);
+    return surface->isValid() ? JNI_TRUE : JNI_FALSE;
+}
+
+static inline SkBitmap::Config convertPixelFormat(PixelFormat format)
+{
+    /* note: if PIXEL_FORMAT_XRGB_8888 means that all alpha bytes are 0xFF, then
+        we can map to SkBitmap::kARGB_8888_Config, and optionally call
+        bitmap.setIsOpaque(true) on the resulting SkBitmap (as an accelerator)
+    */
+	switch (format) {
+    case PIXEL_FORMAT_RGBA_8888:    return SkBitmap::kARGB_8888_Config;
+    case PIXEL_FORMAT_RGBA_4444:    return SkBitmap::kARGB_4444_Config;
+	case PIXEL_FORMAT_RGB_565:		return SkBitmap::kRGB_565_Config;
+	case PIXEL_FORMAT_A_8:          return SkBitmap::kA8_Config;
+	default:                        return SkBitmap::kNo_Config;
+	}
+}
+
+static jobject Surface_lockCanvas(JNIEnv* env, jobject clazz, jobject dirtyRect)
+{
+    const sp<Surface>& surface = getSurface(env, clazz);
+    if (!surface->isValid())
+        return 0;
+
+    // get dirty region
+    Region dirtyRegion;
+    if (dirtyRect) {
+        Rect dirty;
+        dirty.left  = env->GetIntField(dirtyRect, ro.l);
+        dirty.top   = env->GetIntField(dirtyRect, ro.t);
+        dirty.right = env->GetIntField(dirtyRect, ro.r);
+        dirty.bottom= env->GetIntField(dirtyRect, ro.b);
+        if (dirty.left < dirty.right && dirty.top < dirty.bottom) {
+            dirtyRegion.set(dirty);    
+        }
+    } else {
+        dirtyRegion.set(Rect(0x3FFF,0x3FFF));
+    }
+
+    Surface::SurfaceInfo info;
+    status_t err = surface->lock(&info, &dirtyRegion);
+    if (err < 0) {
+        const char* const exception = (err == NO_MEMORY) ?
+            OutOfResourcesException :
+            "java/lang/IllegalArgumentException";
+        doThrow(env, exception, NULL);
+        return 0;
+    }
+
+    // Associate a SkCanvas object to this surface
+    jobject canvas = env->GetObjectField(clazz, so.canvas);
+    env->SetIntField(canvas, co.surfaceFormat, info.format);
+
+    SkCanvas* nativeCanvas = (SkCanvas*)env->GetIntField(canvas, no.native_canvas);
+    SkBitmap bitmap;
+    bitmap.setConfig(convertPixelFormat(info.format), info.w, info.h, info.bpr);
+    if (info.w > 0 && info.h > 0) {
+        bitmap.setPixels(info.bits);
+    } else {
+        // be safe with an empty bitmap.
+        bitmap.setPixels(NULL);
+    }
+    nativeCanvas->setBitmapDevice(bitmap);
+    nativeCanvas->clipRegion(dirtyRegion.toSkRegion());
+    
+    int saveCount = nativeCanvas->save();
+    env->SetIntField(clazz, so.saveCount, saveCount);
+
+    if (dirtyRect) {
+        Rect bounds(dirtyRegion.bounds());
+        env->SetIntField(dirtyRect, ro.l, bounds.left);
+        env->SetIntField(dirtyRect, ro.t, bounds.top);
+        env->SetIntField(dirtyRect, ro.r, bounds.right);
+        env->SetIntField(dirtyRect, ro.b, bounds.bottom);
+    }
+    
+	return canvas;
+}
+
+static void Surface_unlockCanvasAndPost(
+        JNIEnv* env, jobject clazz, jobject argCanvas)
+{
+    jobject canvas = env->GetObjectField(clazz, so.canvas);
+    if (canvas != argCanvas) {
+        doThrow(env, "java/lang/IllegalArgumentException", NULL);
+        return;
+    }
+    
+    const sp<Surface>& surface = getSurface(env, clazz);
+    if (!surface->isValid())
+        return;
+
+    // detach the canvas from the surface
+    SkCanvas* nativeCanvas = (SkCanvas*)env->GetIntField(canvas, no.native_canvas);
+    int saveCount = env->GetIntField(clazz, so.saveCount);
+    nativeCanvas->restoreToCount(saveCount);
+    nativeCanvas->setBitmapDevice(SkBitmap());
+    env->SetIntField(clazz, so.saveCount, 0);
+
+    // unlock surface
+    status_t err = surface->unlockAndPost();
+    if (err < 0) {
+        doThrow(env, "java/lang/IllegalArgumentException", NULL);
+    }
+}
+
+static void Surface_unlockCanvas(
+        JNIEnv* env, jobject clazz, jobject argCanvas)
+{
+    jobject canvas = env->GetObjectField(clazz, so.canvas);
+    if (canvas != argCanvas) {
+        doThrow(env, "java/lang/IllegalArgumentException", NULL);
+        return;
+    }
+    
+    const sp<Surface>& surface = getSurface(env, clazz);
+    if (!surface->isValid())
+        return;
+    
+    status_t err = surface->unlock();
+    if (err < 0) {
+        doThrow(env, "java/lang/IllegalArgumentException", NULL);
+        return;
+    }
+    SkCanvas* nativeCanvas = (SkCanvas*)env->GetIntField(canvas, no.native_canvas);
+    int saveCount = env->GetIntField(clazz, so.saveCount);
+    nativeCanvas->restoreToCount(saveCount);
+    nativeCanvas->setBitmapDevice(SkBitmap());
+    env->SetIntField(clazz, so.saveCount, 0);
+}
+
+static void Surface_openTransaction(
+        JNIEnv* env, jobject clazz)
+{
+    SurfaceComposerClient::openGlobalTransaction();
+}
+
+static void Surface_closeTransaction(
+        JNIEnv* env, jobject clazz)
+{
+    SurfaceComposerClient::closeGlobalTransaction();
+}
+
+static void Surface_setOrientation(
+        JNIEnv* env, jobject clazz, jint display, jint orientation)
+{
+    int err = SurfaceComposerClient::setOrientation(display, orientation);
+    if (err < 0) {
+        doThrow(env, "java/lang/IllegalArgumentException", NULL);
+    }
+}
+
+static void Surface_freezeDisplay(
+        JNIEnv* env, jobject clazz, jint display)
+{
+    int err = SurfaceComposerClient::freezeDisplay(display, 0);
+    if (err < 0) {
+        doThrow(env, "java/lang/IllegalArgumentException", NULL);
+    }
+}
+
+static void Surface_unfreezeDisplay(
+        JNIEnv* env, jobject clazz, jint display)
+{
+    int err = SurfaceComposerClient::unfreezeDisplay(display, 0);
+    if (err < 0) {
+        doThrow(env, "java/lang/IllegalArgumentException", NULL);
+    }
+}
+
+static void Surface_setLayer(
+        JNIEnv* env, jobject clazz, jint zorder)
+{
+    const sp<Surface>& surface = getSurface(env, clazz);
+    if (surface->isValid()) {
+        if (surface->setLayer(zorder) < 0) {
+            doThrow(env, "java/lang/IllegalArgumentException", NULL);
+        }
+    }
+}
+
+static void Surface_setPosition(
+        JNIEnv* env, jobject clazz, jint x, jint y)
+{
+    const sp<Surface>& surface = getSurface(env, clazz);
+    if (surface->isValid()) {
+        if (surface->setPosition(x, y) < 0) {
+            doThrow(env, "java/lang/IllegalArgumentException", NULL);
+        }
+    }
+}
+
+static void Surface_setSize(
+        JNIEnv* env, jobject clazz, jint w, jint h)
+{
+    const sp<Surface>& surface = getSurface(env, clazz);
+    if (surface->isValid()) {
+        if (surface->setSize(w, h) < 0) {
+            doThrow(env, "java/lang/IllegalArgumentException", NULL);
+        }
+    }
+}
+
+static void Surface_hide(
+        JNIEnv* env, jobject clazz)
+{
+    const sp<Surface>& surface = getSurface(env, clazz);
+    if (surface->isValid()) {
+        if (surface->hide() < 0) {
+            doThrow(env, "java/lang/IllegalArgumentException", NULL);
+        }
+    }
+}
+
+static void Surface_show(
+        JNIEnv* env, jobject clazz)
+{
+    const sp<Surface>& surface = getSurface(env, clazz);
+    if (surface->isValid()) {
+        if (surface->show() < 0) {
+            doThrow(env, "java/lang/IllegalArgumentException", NULL);
+        }
+    }
+}
+
+static void Surface_freeze(
+        JNIEnv* env, jobject clazz)
+{
+    const sp<Surface>& surface = getSurface(env, clazz);
+    if (surface->isValid()) {
+        if (surface->freeze() < 0) {
+            doThrow(env, "java/lang/IllegalArgumentException", NULL);
+        }
+    }
+}
+
+static void Surface_unfreeze(
+        JNIEnv* env, jobject clazz)
+{
+    const sp<Surface>& surface = getSurface(env, clazz);
+    if (surface->isValid()) {
+        if (surface->unfreeze() < 0) {
+            doThrow(env, "java/lang/IllegalArgumentException", NULL);
+        }
+    }
+}
+
+static void Surface_setFlags(
+        JNIEnv* env, jobject clazz, jint flags, jint mask)
+{
+    const sp<Surface>& surface = getSurface(env, clazz);
+    if (surface->isValid()) {
+        if (surface->setFlags(flags, mask) < 0) {
+            doThrow(env, "java/lang/IllegalArgumentException", NULL);
+        }
+    }
+}
+
+static void Surface_setTransparentRegion(
+        JNIEnv* env, jobject clazz, jobject argRegion)
+{
+    const sp<Surface>& surface = getSurface(env, clazz);
+    if (surface->isValid()) {
+        SkRegion* nativeRegion = (SkRegion*)env->GetIntField(argRegion, no.native_region);
+        if (surface->setTransparentRegionHint(Region(*nativeRegion)) < 0) {
+            doThrow(env, "java/lang/IllegalArgumentException", NULL);
+        }
+    }
+}
+
+static void Surface_setAlpha(
+        JNIEnv* env, jobject clazz, jfloat alpha)
+{
+    const sp<Surface>& surface = getSurface(env, clazz);
+    if (surface->isValid()) {
+        if (surface->setAlpha(alpha) < 0) {
+            doThrow(env, "java/lang/IllegalArgumentException", NULL);
+        }
+    }
+}
+
+static void Surface_setMatrix(
+        JNIEnv* env, jobject clazz,
+        jfloat dsdx, jfloat dtdx, jfloat dsdy, jfloat dtdy)
+{
+    const sp<Surface>& surface = getSurface(env, clazz);
+    if (surface->isValid()) {
+        if (surface->setMatrix(dsdx, dtdx, dsdy, dtdy) < 0) {
+            doThrow(env, "java/lang/IllegalArgumentException", NULL);
+        }
+    }
+}
+
+static void Surface_setFreezeTint(
+        JNIEnv* env, jobject clazz,
+        jint tint)
+{
+    const sp<Surface>& surface = getSurface(env, clazz);
+    if (surface->isValid()) {
+        if (surface->setFreezeTint(tint) < 0) {
+            doThrow(env, "java/lang/IllegalArgumentException", NULL);
+        }
+    }
+}
+
+static void Surface_copyFrom(
+        JNIEnv* env, jobject clazz, jobject other)
+{
+    if (clazz == other)
+        return;
+
+    if (other == NULL) {
+        doThrow(env, "java/lang/NullPointerException", NULL);
+        return;
+    }
+
+    const sp<Surface>& surface = getSurface(env, clazz);
+    const sp<Surface>& rhs = getSurface(env, other);
+    if (!Surface::isSameSurface(surface, rhs)) {
+        // we reassign the surface only if it's a different one
+        // otherwise we would loose our client-side state.
+        setSurface(env, clazz, rhs->dup());
+    }
+}
+
+
+static void Surface_readFromParcel(
+        JNIEnv* env, jobject clazz, jobject argParcel)
+{
+    Parcel* parcel = (Parcel*)env->GetIntField( argParcel, no.native_parcel);
+    if (parcel == NULL) {
+        doThrow(env, "java/lang/NullPointerException", NULL);
+        return;
+    }
+
+    const sp<Surface>& surface = getSurface(env, clazz);
+    const sp<Surface>& rhs = Surface::readFromParcel(parcel);
+    if (!Surface::isSameSurface(surface, rhs)) {
+        // we reassign the surface only if it's a different one
+        // otherwise we would loose our client-side state.
+        setSurface(env, clazz, rhs);
+    }
+}
+
+static void Surface_writeToParcel(
+        JNIEnv* env, jobject clazz, jobject argParcel, jint flags)
+{
+    Parcel* parcel = (Parcel*)env->GetIntField(
+            argParcel, no.native_parcel);
+
+    if (parcel == NULL) {
+        doThrow(env, "java/lang/NullPointerException", NULL);
+        return;
+    }
+
+    const sp<Surface>& surface = getSurface(env, clazz);
+    Surface::writeToParcel(surface, parcel);
+}
+
+// ----------------------------------------------------------------------------
+// ----------------------------------------------------------------------------
+// ----------------------------------------------------------------------------
+
+const char* const kSurfaceSessionClassPathName = "android/view/SurfaceSession";
+const char* const kSurfaceClassPathName = "android/view/Surface";
+static void nativeClassInit(JNIEnv* env, jclass clazz);
+
+static JNINativeMethod gSurfaceSessionMethods[] = {
+	{"init",     "()V",  (void*)SurfaceSession_init },
+	{"destroy",  "()V",  (void*)SurfaceSession_destroy },
+    {"kill",     "()V",  (void*)SurfaceSession_kill },
+};
+
+static JNINativeMethod gSurfaceMethods[] = {
+    {"nativeClassInit",     "()V",  (void*)nativeClassInit },
+    {"init",                "(Landroid/view/SurfaceSession;IIIIII)V",  (void*)Surface_init },
+    {"init",                "(Landroid/os/Parcel;)V",  (void*)Surface_initParcel },
+	{"clear",               "()V",  (void*)Surface_clear },
+	{"copyFrom",            "(Landroid/view/Surface;)V",  (void*)Surface_copyFrom },
+	{"isValid",             "()Z",  (void*)Surface_isValid },
+	{"lockCanvasNative",    "(Landroid/graphics/Rect;)Landroid/graphics/Canvas;",  (void*)Surface_lockCanvas },
+	{"unlockCanvasAndPost", "(Landroid/graphics/Canvas;)V", (void*)Surface_unlockCanvasAndPost },
+	{"unlockCanvas",        "(Landroid/graphics/Canvas;)V", (void*)Surface_unlockCanvas },
+	{"openTransaction",     "()V",  (void*)Surface_openTransaction },
+    {"closeTransaction",    "()V",  (void*)Surface_closeTransaction },
+    {"setOrientation",      "(II)V", (void*)Surface_setOrientation },
+    {"freezeDisplay",       "(I)V", (void*)Surface_freezeDisplay },
+    {"unfreezeDisplay",     "(I)V", (void*)Surface_unfreezeDisplay },
+    {"setLayer",            "(I)V", (void*)Surface_setLayer },
+	{"setPosition",         "(II)V",(void*)Surface_setPosition },
+	{"setSize",             "(II)V",(void*)Surface_setSize },
+	{"hide",                "()V",  (void*)Surface_hide },
+	{"show",                "()V",  (void*)Surface_show },
+	{"freeze",              "()V",  (void*)Surface_freeze },
+	{"unfreeze",            "()V",  (void*)Surface_unfreeze },
+	{"setFlags",            "(II)V",(void*)Surface_setFlags },
+	{"setTransparentRegionHint","(Landroid/graphics/Region;)V", (void*)Surface_setTransparentRegion },
+	{"setAlpha",            "(F)V", (void*)Surface_setAlpha },
+	{"setMatrix",           "(FFFF)V",  (void*)Surface_setMatrix },
+	{"setFreezeTint",       "(I)V",  (void*)Surface_setFreezeTint },
+	{"readFromParcel",      "(Landroid/os/Parcel;)V", (void*)Surface_readFromParcel },
+	{"writeToParcel",       "(Landroid/os/Parcel;I)V", (void*)Surface_writeToParcel },
+};
+
+void nativeClassInit(JNIEnv* env, jclass clazz)
+{
+	so.surface   = env->GetFieldID(clazz, "mSurface", "I");
+	so.saveCount = env->GetFieldID(clazz, "mSaveCount", "I");
+	so.canvas    = env->GetFieldID(clazz, "mCanvas", "Landroid/graphics/Canvas;");
+
+    jclass surfaceSession = env->FindClass("android/view/SurfaceSession");
+ 	sso.client = env->GetFieldID(surfaceSession, "mClient", "I");
+
+    jclass canvas = env->FindClass("android/graphics/Canvas");
+    no.native_canvas = env->GetFieldID(canvas, "mNativeCanvas", "I");
+    co.surfaceFormat = env->GetFieldID(canvas, "mSurfaceFormat", "I");
+
+    jclass region = env->FindClass("android/graphics/Region");
+    no.native_region = env->GetFieldID(region, "mNativeRegion", "I");
+
+    jclass parcel = env->FindClass("android/os/Parcel");
+    no.native_parcel = env->GetFieldID(parcel, "mObject", "I");
+
+    jclass rect = env->FindClass("android/graphics/Rect");
+    ro.l = env->GetFieldID(rect, "left", "I");
+    ro.t = env->GetFieldID(rect, "top", "I");
+    ro.r = env->GetFieldID(rect, "right", "I");
+    ro.b = env->GetFieldID(rect, "bottom", "I");
+
+    jclass point = env->FindClass("android/graphics/Point");
+    po.x = env->GetFieldID(point, "x", "I");
+    po.y = env->GetFieldID(point, "y", "I");
+}
+
+int register_android_view_Surface(JNIEnv* env)
+{
+    int err;
+    err = AndroidRuntime::registerNativeMethods(env, kSurfaceSessionClassPathName,
+            gSurfaceSessionMethods, NELEM(gSurfaceSessionMethods));
+
+    err |= AndroidRuntime::registerNativeMethods(env, kSurfaceClassPathName,
+            gSurfaceMethods, NELEM(gSurfaceMethods));
+    return err;
+}
+
+};
+
diff --git a/core/jni/android_view_ViewRoot.cpp b/core/jni/android_view_ViewRoot.cpp
new file mode 100644
index 0000000..9d62d89
--- /dev/null
+++ b/core/jni/android_view_ViewRoot.cpp
@@ -0,0 +1,98 @@
+/*
+ * 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.
+ */
+
+#include <stdio.h>
+#include <assert.h>
+
+#include <core/SkCanvas.h>
+#include <core/SkDevice.h>
+#include <core/SkPaint.h>
+#include <utils/SkGLCanvas.h>
+#include "GraphicsJNI.h"
+
+#include "jni.h"
+#include <android_runtime/AndroidRuntime.h>
+#include <utils/misc.h>
+
+// ----------------------------------------------------------------------------
+
+namespace android {
+
+static int gPrevDur;
+
+static void android_view_ViewRoot_showFPS(JNIEnv* env, jobject, jobject jcanvas,
+                                          jint dur) {
+    NPE_CHECK_RETURN_VOID(env, jcanvas);
+    SkCanvas* canvas = GraphicsJNI::getNativeCanvas(env, jcanvas);
+    const SkBitmap& bm = canvas->getDevice()->accessBitmap(false);
+    int height = bm.height();
+    SkScalar bot = SkIntToScalar(height);
+
+    if (height < 200) {
+        return;
+    }
+
+    SkMatrix m;
+    SkRect   r;
+    SkPaint  p;
+    char    str[4];
+
+    dur = (gPrevDur + dur) >> 1;
+    gPrevDur = dur;
+
+    dur = 1000 / dur;
+    str[3] = (char)('0' + dur % 10); dur /= 10;
+    str[2] = (char)('0' + dur % 10); dur /= 10;
+    str[1] = (char)('0' + dur % 10); dur /= 10;
+    str[0] = (char)('0' + dur % 10);
+
+    m.reset();
+    r.set(0, bot-SkIntToScalar(10), SkIntToScalar(26), bot);
+    p.setAntiAlias(true);
+    p.setTextSize(SkIntToScalar(10));
+
+    canvas->save();
+    canvas->setMatrix(m);
+    canvas->clipRect(r, SkRegion::kReplace_Op);
+    p.setColor(SK_ColorWHITE);
+    canvas->drawPaint(p);
+    p.setColor(SK_ColorBLACK);
+    canvas->drawText(str, 4, SkIntToScalar(1), bot - SK_Scalar1, p);
+    canvas->restore();
+}
+
+static void android_view_ViewRoot_abandonGlCaches(JNIEnv* env, jobject) {
+    SkGLCanvas::AbandonAllTextures();
+}
+
+// ----------------------------------------------------------------------------
+
+const char* const kClassPathName = "android/view/ViewRoot";
+
+static JNINativeMethod gMethods[] = {
+    {   "nativeShowFPS", "(Landroid/graphics/Canvas;I)V",
+                                        (void*)android_view_ViewRoot_showFPS },
+    {   "nativeAbandonGlCaches", "()V", 
+                                (void*)android_view_ViewRoot_abandonGlCaches }
+};
+
+int register_android_view_ViewRoot(JNIEnv* env) {
+    return AndroidRuntime::registerNativeMethods(env,
+            kClassPathName, gMethods, NELEM(gMethods));
+}
+
+};
+
diff --git a/core/jni/com_android_internal_graphics_NativeUtils.cpp b/core/jni/com_android_internal_graphics_NativeUtils.cpp
new file mode 100644
index 0000000..0829532
--- /dev/null
+++ b/core/jni/com_android_internal_graphics_NativeUtils.cpp
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2007 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.
+ */
+
+#define LOG_TAG "AWT"
+
+#include "jni.h"
+#include "JNIHelp.h"
+#include "GraphicsJNI.h"
+#include <android_runtime/AndroidRuntime.h>
+
+#include "SkCanvas.h"
+#include "SkDevice.h"
+#include "SkPicture.h"
+#include "SkTemplates.h"
+
+namespace android
+{
+
+static jclass class_fileDescriptor;
+static jfieldID field_fileDescriptor_descriptor;
+static jmethodID method_fileDescriptor_init;
+ 
+
+static jboolean scrollRect(JNIEnv* env, jobject graphics2D, jobject canvas, jobject rect, int dx, int dy) {
+    if (canvas == NULL) {
+        jniThrowException(env, "java/lang/NullPointerException", NULL);
+        return false;
+    }
+  
+    SkIRect src, *srcPtr = NULL;
+    if (NULL != rect) {
+        GraphicsJNI::jrect_to_irect(env, rect, &src);
+        srcPtr = &src;
+    }
+    SkCanvas* c = GraphicsJNI::getNativeCanvas(env, canvas);
+    const SkBitmap& bitmap = c->getDevice()->accessBitmap(true);
+    return bitmap.scrollRect(srcPtr, dx, dy, NULL);
+}
+
+static JNINativeMethod method_table[] = {
+    { "nativeScrollRect",
+      "(Landroid/graphics/Canvas;Landroid/graphics/Rect;II)Z",
+      (void*)scrollRect}
+};
+
+int register_com_android_internal_graphics_NativeUtils(JNIEnv *env) {
+    return AndroidRuntime::registerNativeMethods(
+        env, "com/android/internal/graphics/NativeUtils",
+        method_table, NELEM(method_table));
+}
+
+}
diff --git a/core/jni/com_android_internal_os_ZygoteInit.cpp b/core/jni/com_android_internal_os_ZygoteInit.cpp
new file mode 100644
index 0000000..ada4dd3
--- /dev/null
+++ b/core/jni/com_android_internal_os_ZygoteInit.cpp
@@ -0,0 +1,367 @@
+/*
+ * Copyright (C) 2007 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.
+ */
+
+#define LOG_TAG "Zygote"
+
+#include <sys/types.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <utils/misc.h>
+#include <errno.h>
+#include <sys/select.h>
+
+#include "jni.h"
+#include <JNIHelp.h>
+#include "android_runtime/AndroidRuntime.h"
+
+#ifdef HAVE_ANDROID_OS
+#include <linux/capability.h>
+#include <linux/prctl.h>
+#include <sys/prctl.h>
+extern "C" int capget(cap_user_header_t hdrp, cap_user_data_t datap);
+extern "C" int capset(cap_user_header_t hdrp, const cap_user_data_t datap);
+#endif
+
+
+namespace android {
+
+/*
+ * In class com.android.internal.os.ZygoteInit:
+ * private static native boolean setreuid(int ruid, int euid)
+ */
+static jint com_android_internal_os_ZygoteInit_setreuid(
+    JNIEnv* env, jobject clazz, jint ruid, jint euid)
+{
+    int err;
+
+    errno = 0;
+    err = setreuid(ruid, euid);
+
+    //LOGI("setreuid(%d,%d) err %d errno %d", ruid, euid, err, errno);
+
+    return errno;
+}
+
+/*
+ * In class com.android.internal.os.ZygoteInit:
+ * private static native int setregid(int rgid, int egid)
+ */
+static jint com_android_internal_os_ZygoteInit_setregid(
+    JNIEnv* env, jobject clazz, jint rgid, jint egid)
+{
+    int err;
+
+    errno = 0;
+    err = setregid(rgid, egid);
+
+    //LOGI("setregid(%d,%d) err %d errno %d", rgid, egid, err, errno);
+
+    return errno;
+}
+
+/*
+ * In class com.android.internal.os.ZygoteInit:
+ * private static native int setpgid(int rgid, int egid)
+ */
+static jint com_android_internal_os_ZygoteInit_setpgid(
+    JNIEnv* env, jobject clazz, jint pid, jint pgid)
+{
+    int err;
+
+    errno = 0;
+
+    err = setpgid(pid, pgid);
+
+    return errno;
+}
+
+/*
+ * In class com.android.internal.os.ZygoteInit:
+ * private static native int getpgid(int pid)
+ */
+static jint com_android_internal_os_ZygoteInit_getpgid(
+    JNIEnv* env, jobject clazz, jint pid)
+{
+    pid_t ret;
+    ret = getpgid(pid);
+
+    if (ret < 0) {
+        jniThrowIOException(env, errno);
+    }
+
+    return ret;
+}
+
+static void com_android_internal_os_ZygoteInit_reopenStdio(JNIEnv* env, 
+        jobject clazz, jobject in, jobject out, jobject errfd)
+{
+    int fd;
+    int err;
+
+    fd = jniGetFDFromFileDescriptor(env, in);
+
+    if  (env->ExceptionOccurred() != NULL) {
+        return;
+    }
+
+    do {
+        err = dup2(fd, STDIN_FILENO);
+    } while (err < 0 && errno == EINTR);
+
+    fd = jniGetFDFromFileDescriptor(env, out);
+
+    if  (env->ExceptionOccurred() != NULL) {
+        return;
+    }
+
+    do {
+        err = dup2(fd, STDOUT_FILENO);
+    } while (err < 0 && errno == EINTR);
+
+    fd = jniGetFDFromFileDescriptor(env, errfd);
+
+    if  (env->ExceptionOccurred() != NULL) {
+        return;
+    }
+
+    do {
+        err = dup2(fd, STDERR_FILENO);
+    } while (err < 0 && errno == EINTR);
+}
+
+static void com_android_internal_os_ZygoteInit_closeDescriptor(JNIEnv* env, 
+        jobject clazz, jobject descriptor)
+{
+    int fd;
+    int err;
+
+    fd = jniGetFDFromFileDescriptor(env, descriptor);
+
+    if  (env->ExceptionOccurred() != NULL) {
+        return;
+    }
+
+    do {
+        err = close(fd);
+    } while (err < 0 && errno == EINTR);
+
+    if (err < 0) {
+        jniThrowIOException(env, errno);
+        return;
+    }
+}
+
+static void com_android_internal_os_ZygoteInit_setCloseOnExec (JNIEnv *env,
+    jobject clazz, jobject descriptor, jboolean flag)
+{
+    int fd;
+    int err;
+    int fdFlags;
+
+    fd = jniGetFDFromFileDescriptor(env, descriptor);
+
+    if  (env->ExceptionOccurred() != NULL) {
+        return;
+    }
+
+    fdFlags = fcntl(fd, F_GETFD);
+
+    if (fdFlags < 0) {
+        jniThrowIOException(env, errno);
+        return;
+    }
+
+    if (flag) {
+        fdFlags |= FD_CLOEXEC;
+    } else {
+        fdFlags &= ~FD_CLOEXEC;
+    }
+
+    err = fcntl(fd, F_SETFD, fdFlags);
+
+    if (err < 0) {
+        jniThrowIOException(env, errno);
+        return;
+    }
+}
+
+static void com_android_internal_os_ZygoteInit_setCapabilities (JNIEnv *env,
+    jobject clazz, jlong permitted, jlong effective)
+{
+#ifdef HAVE_ANDROID_OS
+    struct __user_cap_header_struct capheader;
+    struct __user_cap_data_struct capdata;
+    int err;
+
+    memset (&capheader, 0, sizeof(capheader));
+    memset (&capdata, 0, sizeof(capdata));
+
+    capheader.version = _LINUX_CAPABILITY_VERSION;
+    capheader.pid = 0;
+
+    // As of this writing, capdata is __u32, but that's expected
+    // to change...
+    capdata.effective = effective;
+    capdata.permitted = permitted;
+
+    err = capset (&capheader, &capdata); 
+
+    if (err < 0) {
+        jniThrowIOException(env, errno);
+        return;
+    }
+#endif /* HAVE_ANDROID_OS */
+}
+
+static jlong com_android_internal_os_ZygoteInit_capgetPermitted (JNIEnv *env,
+    jobject clazz, jint pid)
+{
+#ifndef HAVE_ANDROID_OS
+    return (jlong)0;
+#else
+    struct __user_cap_header_struct capheader;
+    struct __user_cap_data_struct capdata;
+    int err;
+
+    memset (&capheader, 0, sizeof(capheader));
+    memset (&capdata, 0, sizeof(capdata));
+
+    capheader.version = _LINUX_CAPABILITY_VERSION;
+    capheader.pid = pid;
+
+    err = capget (&capheader, &capdata); 
+
+    if (err < 0) {
+        jniThrowIOException(env, errno);
+        return 0;
+    }
+
+    return (jlong) capdata.permitted;
+#endif /* HAVE_ANDROID_OS */
+}
+
+static jint com_android_internal_os_ZygoteInit_selectReadable (
+        JNIEnv *env, jobject clazz, jobjectArray fds) 
+{
+    if (fds == NULL) {
+        jniThrowException(env, "java/lang/NullPointerException",
+            "fds == null");
+        return -1;
+    }
+
+    jsize length = env->GetArrayLength(fds);
+    fd_set fdset;
+
+    if (env->ExceptionOccurred() != NULL) {
+        return -1;
+    }
+
+    FD_ZERO(&fdset);
+
+    int nfds = 0;
+    for (jsize i = 0; i < length; i++) {
+        jobject fdObj = env->GetObjectArrayElement(fds, i);
+        if  (env->ExceptionOccurred() != NULL) {
+            return -1;
+        }
+        if (fdObj == NULL) {
+            continue;
+        }
+        int fd = jniGetFDFromFileDescriptor(env, fdObj);
+        if  (env->ExceptionOccurred() != NULL) {
+            return -1;
+        }
+
+        FD_SET(fd, &fdset);
+
+        if (fd >= nfds) {
+            nfds = fd + 1;
+        }
+    }
+
+    int err;
+    do {
+        err = select (nfds, &fdset, NULL, NULL, NULL);
+    } while (err < 0 && errno == EINTR);
+
+    if (err < 0) {
+        jniThrowIOException(env, errno);
+        return -1;
+    }
+
+    for (jsize i = 0; i < length; i++) {
+        jobject fdObj = env->GetObjectArrayElement(fds, i);
+        if  (env->ExceptionOccurred() != NULL) {
+            return -1;
+        }
+        if (fdObj == NULL) {
+            continue;
+        }
+        int fd = jniGetFDFromFileDescriptor(env, fdObj);
+        if  (env->ExceptionOccurred() != NULL) {
+            return -1;
+        }
+        if (FD_ISSET(fd, &fdset)) {
+            return (jint)i;
+        }
+    }
+    return -1;
+}
+
+static jobject com_android_internal_os_ZygoteInit_createFileDescriptor (
+        JNIEnv *env, jobject clazz, jint fd) 
+{
+    return jniCreateFileDescriptor(env, fd);
+}
+
+/*
+ * JNI registration.
+ */
+static JNINativeMethod gMethods[] = {
+    /* name, signature, funcPtr */
+    { "setreuid", "(II)I",
+      (void*) com_android_internal_os_ZygoteInit_setreuid },
+    { "setregid", "(II)I",
+      (void*) com_android_internal_os_ZygoteInit_setregid },
+    { "setpgid", "(II)I",
+      (void *) com_android_internal_os_ZygoteInit_setpgid },
+    { "getpgid", "(I)I",
+      (void *) com_android_internal_os_ZygoteInit_getpgid },
+    { "reopenStdio",   
+        "(Ljava/io/FileDescriptor;Ljava/io/FileDescriptor;"
+        "Ljava/io/FileDescriptor;)V", 
+            (void *) com_android_internal_os_ZygoteInit_reopenStdio},
+    { "closeDescriptor", "(Ljava/io/FileDescriptor;)V", 
+        (void *) com_android_internal_os_ZygoteInit_closeDescriptor},
+    { "setCloseOnExec", "(Ljava/io/FileDescriptor;Z)V", 
+        (void *)  com_android_internal_os_ZygoteInit_setCloseOnExec},
+    { "setCapabilities", "(JJ)V", 
+        (void *) com_android_internal_os_ZygoteInit_setCapabilities },
+    { "capgetPermitted", "(I)J", 
+        (void *) com_android_internal_os_ZygoteInit_capgetPermitted },
+    { "selectReadable", "([Ljava/io/FileDescriptor;)I",
+        (void *) com_android_internal_os_ZygoteInit_selectReadable },
+    { "createFileDescriptor", "(I)Ljava/io/FileDescriptor;",
+        (void *) com_android_internal_os_ZygoteInit_createFileDescriptor }
+};
+int register_com_android_internal_os_ZygoteInit(JNIEnv* env)
+{
+    return AndroidRuntime::registerNativeMethods(env,
+            "com/android/internal/os/ZygoteInit", gMethods, NELEM(gMethods));
+}
+
+}; // namespace android
+
diff --git a/core/jni/com_google_android_gles_jni_EGLImpl.cpp b/core/jni/com_google_android_gles_jni_EGLImpl.cpp
new file mode 100644
index 0000000..fbbd852
--- /dev/null
+++ b/core/jni/com_google_android_gles_jni_EGLImpl.cpp
@@ -0,0 +1,470 @@
+/*
+**
+** Copyright 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.
+*/
+
+#include <android_runtime/AndroidRuntime.h>
+#include <utils/misc.h>
+
+#include <EGL/egl.h>
+#include <GLES/gl.h>
+
+#include <ui/EGLNativeWindowSurface.h>
+#include <ui/Surface.h>
+#include <SkBitmap.h>
+#include <SkPixelRef.h>
+
+namespace android {
+
+static jclass gDisplay_class;
+static jclass gContext_class;
+static jclass gSurface_class;
+static jclass gConfig_class;
+
+static jmethodID gConfig_ctorID;
+
+static jfieldID gDisplay_EGLDisplayFieldID;
+static jfieldID gContext_EGLContextFieldID;
+static jfieldID gSurface_EGLSurfaceFieldID;
+static jfieldID gSurface_NativePixelRefFieldID;
+static jfieldID gConfig_EGLConfigFieldID;
+static jfieldID gSurface_SurfaceFieldID;
+static jfieldID gBitmap_NativeBitmapFieldID;
+
+static __attribute__((noinline))
+void doThrow(JNIEnv* env, const char* exc, const char* msg = NULL)
+{
+    jclass npeClazz = env->FindClass(exc);
+    env->ThrowNew(npeClazz, msg);
+}
+
+static __attribute__((noinline))
+bool hasException(JNIEnv *env) {
+    if (env->ExceptionCheck() != 0) {
+        env->ExceptionDescribe();
+        return true;
+    }
+    return false;
+}
+
+static __attribute__((noinline))
+jclass make_globalref(JNIEnv* env, const char classname[]) {
+    jclass c = env->FindClass(classname);
+    return (jclass)env->NewGlobalRef(c);
+}
+
+static inline EGLDisplay getDisplay(JNIEnv* env, jobject o) {
+    if (!o) return EGL_NO_DISPLAY;
+    return (EGLDisplay)env->GetIntField(o, gDisplay_EGLDisplayFieldID);
+}
+static inline EGLSurface getSurface(JNIEnv* env, jobject o) {
+    if (!o) return EGL_NO_SURFACE;
+    return (EGLSurface)env->GetIntField(o, gSurface_EGLSurfaceFieldID);
+}
+static inline EGLContext getContext(JNIEnv* env, jobject o) {
+    if (!o) return EGL_NO_CONTEXT;
+    return (EGLContext)env->GetIntField(o, gContext_EGLContextFieldID);
+}
+static inline EGLConfig getConfig(JNIEnv* env, jobject o) {
+    if (!o) return 0;
+    return (EGLConfig)env->GetIntField(o, gConfig_EGLConfigFieldID);
+}
+static void nativeClassInit(JNIEnv *_env, jclass eglImplClass)
+{
+    gDisplay_class = make_globalref(_env, "com/google/android/gles_jni/EGLDisplayImpl");
+    gContext_class = make_globalref(_env, "com/google/android/gles_jni/EGLContextImpl");
+    gSurface_class = make_globalref(_env, "com/google/android/gles_jni/EGLSurfaceImpl");
+    gConfig_class  = make_globalref(_env, "com/google/android/gles_jni/EGLConfigImpl");
+
+    gConfig_ctorID  = _env->GetMethodID(gConfig_class,  "<init>", "(I)V");
+    
+    gDisplay_EGLDisplayFieldID = _env->GetFieldID(gDisplay_class, "mEGLDisplay", "I");
+    gContext_EGLContextFieldID = _env->GetFieldID(gContext_class, "mEGLContext", "I");
+    gSurface_EGLSurfaceFieldID = _env->GetFieldID(gSurface_class, "mEGLSurface", "I");
+    gSurface_NativePixelRefFieldID = _env->GetFieldID(gSurface_class, "mNativePixelRef", "I");
+    gConfig_EGLConfigFieldID   = _env->GetFieldID(gConfig_class,  "mEGLConfig",  "I");
+
+    jclass surface_class = _env->FindClass("android/view/Surface");
+    gSurface_SurfaceFieldID = _env->GetFieldID(surface_class, "mSurface", "I");
+
+    jclass bitmap_class = _env->FindClass("android/graphics/Bitmap");
+    gBitmap_NativeBitmapFieldID = _env->GetFieldID(bitmap_class, "mNativeBitmap", "I");    
+}
+
+jboolean jni_eglInitialize(JNIEnv *_env, jobject _this, jobject display,
+        jintArray major_minor) {
+    
+    EGLDisplay dpy = getDisplay(_env, display);
+    jboolean success = eglInitialize(dpy, NULL, NULL);
+    if (success && major_minor) {
+        int len = _env->GetArrayLength(major_minor);
+        if (len) {
+            // we're exposing only EGL 1.0
+            jint* base = (jint *)_env->GetPrimitiveArrayCritical(major_minor, (jboolean *)0);
+            if (len >= 1) base[0] = 1;
+            if (len >= 2) base[1] = 0;
+            _env->ReleasePrimitiveArrayCritical(major_minor, base, JNI_ABORT);
+        }
+    }
+    return success;
+}
+
+jboolean jni_eglQueryContext(JNIEnv *_env, jobject _this, jobject display,
+        jobject context, jint attribute, jintArray value) {
+    EGLDisplay dpy = getDisplay(_env, display);
+    EGLContext ctx = getContext(_env, context);
+    if (value == NULL) {
+        doThrow(_env, "java/lang/NullPointerException");
+        return JNI_FALSE;
+    }
+    jboolean success = JNI_FALSE;
+    int len = _env->GetArrayLength(value);
+    if (len) {
+        jint* base = (jint *)_env->GetPrimitiveArrayCritical(value, (jboolean *)0);
+        success = eglQueryContext(dpy, ctx, attribute, base);
+        _env->ReleasePrimitiveArrayCritical(value, base, JNI_ABORT);
+    }
+    return success;
+}
+    
+jboolean jni_eglQuerySurface(JNIEnv *_env, jobject _this, jobject display,
+        jobject surface, jint attribute, jintArray value) {
+    EGLDisplay dpy = getDisplay(_env, display);
+    EGLContext sur = getSurface(_env, surface);
+    if (value == NULL) {
+        doThrow(_env, "java/lang/NullPointerException");
+        return JNI_FALSE;
+    }
+    jboolean success = JNI_FALSE;
+    int len = _env->GetArrayLength(value);
+    if (len) {
+        jint* base = (jint *)_env->GetPrimitiveArrayCritical(value, (jboolean *)0);
+        success = eglQuerySurface(dpy, sur, attribute, base);
+        _env->ReleasePrimitiveArrayCritical(value, base, JNI_ABORT);
+    }
+    return success;
+}
+
+jboolean jni_eglChooseConfig(JNIEnv *_env, jobject _this, jobject display,
+        jintArray attrib_list, jobjectArray configs, jint config_size, jintArray num_config) {    
+    EGLDisplay dpy = getDisplay(_env, display);
+    if (attrib_list==NULL || configs==NULL || num_config==NULL) {
+        doThrow(_env, "java/lang/NullPointerException");
+        return JNI_FALSE;
+    }
+    jboolean success = JNI_FALSE;
+    jint* attrib_base  = (jint *)_env->GetPrimitiveArrayCritical(attrib_list, (jboolean *)0);
+    jint* num_base     = (jint *)_env->GetPrimitiveArrayCritical(num_config, (jboolean *)0);
+    EGLConfig nativeConfigs[config_size];
+    success = eglChooseConfig(dpy, attrib_base, nativeConfigs, config_size, num_base);
+    int num = num_base[0];
+    _env->ReleasePrimitiveArrayCritical(num_config, num_base, JNI_ABORT);
+    _env->ReleasePrimitiveArrayCritical(attrib_list, attrib_base, JNI_ABORT);
+    if (success) {
+        for (int i=0 ; i<num ; i++) {
+            jobject obj = _env->NewObject(gConfig_class, gConfig_ctorID, (jint)nativeConfigs[i]);
+            _env->SetObjectArrayElement(configs, i, obj);
+        }
+    }
+    return success;
+} 
+
+jint jni_eglCreateContext(JNIEnv *_env, jobject _this, jobject display,
+        jobject config, jobject share_context, jintArray attrib_list) {
+    EGLDisplay dpy = getDisplay(_env, display);
+    EGLConfig  cnf = getConfig(_env, config);
+    EGLContext shr = getContext(_env, share_context);
+    jint* base = 0;
+    if (attrib_list) {
+        // XXX: if array is malformed, we should return an NPE instead of segfault
+        base = (jint *)_env->GetPrimitiveArrayCritical(attrib_list, (jboolean *)0);
+    }
+    EGLContext ctx = eglCreateContext(dpy, cnf, shr, base);
+    if (attrib_list) {
+        _env->ReleasePrimitiveArrayCritical(attrib_list, base, JNI_ABORT);
+    }
+    return (jint)ctx;
+}
+
+jint jni_eglCreatePbufferSurface(JNIEnv *_env, jobject _this, jobject display,
+        jobject config, jintArray attrib_list) {
+    EGLDisplay dpy = getDisplay(_env, display);
+    EGLConfig  cnf = getConfig(_env, config);
+    jint* base = 0;
+    if (attrib_list) {
+        // XXX: if array is malformed, we should return an NPE instead of segfault
+        base = (jint *)_env->GetPrimitiveArrayCritical(attrib_list, (jboolean *)0);
+    }
+    EGLSurface sur = eglCreatePbufferSurface(dpy, cnf, base);
+    if (attrib_list) {
+        _env->ReleasePrimitiveArrayCritical(attrib_list, base, JNI_ABORT);
+    }
+    return (jint)sur;
+}
+
+static PixelFormat convertPixelFormat(SkBitmap::Config format)
+{
+    switch (format) {
+    case SkBitmap::kARGB_8888_Config:   return PIXEL_FORMAT_RGBA_8888;
+    case SkBitmap::kARGB_4444_Config:   return PIXEL_FORMAT_RGBA_4444;
+    case SkBitmap::kRGB_565_Config:     return PIXEL_FORMAT_RGB_565;
+    case SkBitmap::kA8_Config:          return PIXEL_FORMAT_A_8;
+    default:                            return PIXEL_FORMAT_NONE;
+    }
+}
+
+void jni_eglCreatePixmapSurface(JNIEnv *_env, jobject _this, jobject out_sur,
+        jobject display, jobject config, jobject native_pixmap,
+        jintArray attrib_list) 
+{
+    EGLDisplay dpy = getDisplay(_env, display);
+    EGLConfig  cnf = getConfig(_env, config);
+    jint* base = 0;
+
+    SkBitmap const * nativeBitmap =
+            (SkBitmap const *)_env->GetIntField(native_pixmap,
+                    gBitmap_NativeBitmapFieldID);
+    SkPixelRef* ref = nativeBitmap ? nativeBitmap->pixelRef() : 0;
+    if (ref == NULL) {
+        doThrow(_env, "java/lang/NullPointerException", "Bitmap has no PixelRef");
+        return;
+    }
+    
+    ref->safeRef();
+    ref->lockPixels();
+    
+    egl_native_pixmap_t pixmap;
+    pixmap.version = sizeof(pixmap);
+    pixmap.width  = nativeBitmap->width();
+    pixmap.height = nativeBitmap->height();
+    pixmap.stride = nativeBitmap->rowBytes() / nativeBitmap->bytesPerPixel();
+    pixmap.format = convertPixelFormat(nativeBitmap->config());
+    pixmap.data   = (uint8_t*)ref->pixels();
+    
+    if (attrib_list) {
+        // XXX: if array is malformed, we should return an NPE instead of segfault
+        base = (jint *)_env->GetPrimitiveArrayCritical(attrib_list, (jboolean *)0);
+    }
+    EGLSurface sur = eglCreatePixmapSurface(dpy, cnf, &pixmap, base);
+    if (attrib_list) {
+        _env->ReleasePrimitiveArrayCritical(attrib_list, base, JNI_ABORT);
+    }
+
+    if (sur != EGL_NO_SURFACE) {
+        _env->SetIntField(out_sur, gSurface_EGLSurfaceFieldID, (int)sur);
+        _env->SetIntField(out_sur, gSurface_NativePixelRefFieldID, (int)ref);
+    } else {
+        ref->unlockPixels();
+        ref->safeUnref();
+    }
+}
+
+jint jni_eglCreateWindowSurface(JNIEnv *_env, jobject _this, jobject display,
+        jobject config, jobject native_window, jintArray attrib_list) {
+    EGLDisplay dpy = getDisplay(_env, display);
+    EGLContext cnf = getConfig(_env, config);
+    Surface* window = 0;
+    if (native_window == NULL) {
+not_valid_surface:
+        doThrow(_env, "java/lang/NullPointerException",
+                "Make sure the SurfaceView or associated SurfaceHolder has a valid Surface");
+        return 0;
+    }
+    window = (Surface*)_env->GetIntField(native_window, gSurface_SurfaceFieldID);
+    if (window == NULL)
+        goto not_valid_surface;
+
+    jint* base = 0;
+    if (attrib_list) {
+        // XXX: if array is malformed, we should return an NPE instead of segfault
+        base = (jint *)_env->GetPrimitiveArrayCritical(attrib_list, (jboolean *)0);
+    }
+    EGLSurface sur = eglCreateWindowSurface(dpy, cnf, new EGLNativeWindowSurface(window), base);
+    if (attrib_list) {
+        _env->ReleasePrimitiveArrayCritical(attrib_list, base, JNI_ABORT);
+    }
+    return (jint)sur;
+}
+
+jboolean jni_eglGetConfigAttrib(JNIEnv *_env, jobject _this, jobject display,
+        jobject config, jint attribute, jintArray value) {
+    EGLDisplay dpy = getDisplay(_env, display);
+    EGLContext cnf = getConfig(_env, config);
+    if (value == NULL) {
+        doThrow(_env, "java/lang/NullPointerException");
+        return JNI_FALSE;
+    }
+    jboolean success = JNI_FALSE;
+    int len = _env->GetArrayLength(value);
+    if (len) {
+        jint* base = (jint *)_env->GetPrimitiveArrayCritical(value, (jboolean *)0);
+        success = eglGetConfigAttrib(dpy, cnf, attribute, base);
+        _env->ReleasePrimitiveArrayCritical(value, base, JNI_ABORT);
+    }
+    return success;
+}
+
+jboolean jni_eglGetConfigs(JNIEnv *_env, jobject _this, jobject display,
+        jobjectArray configs, jint config_size, jintArray num_config) {
+    EGLDisplay dpy = getDisplay(_env, display);
+    jboolean success = JNI_FALSE;
+    if (num_config == NULL) {
+        doThrow(_env, "java/lang/NullPointerException");
+        return JNI_FALSE;
+    }
+    jint* num_base = (jint *)_env->GetPrimitiveArrayCritical(num_config, (jboolean *)0);
+    EGLConfig nativeConfigs[config_size];
+    success = eglGetConfigs(dpy, configs ? nativeConfigs : 0, config_size, num_base);
+    int num = num_base[0];
+    _env->ReleasePrimitiveArrayCritical(num_config, num_base, JNI_ABORT);
+
+    if (success && configs) {
+        for (int i=0 ; i<num ; i++) {
+            jobject obj = _env->NewObject(gConfig_class, gConfig_ctorID, (jint)nativeConfigs[i]);
+            _env->SetObjectArrayElement(configs, i, obj);
+        }
+    }
+    return success;
+}
+    
+jint jni_eglGetError(JNIEnv *_env, jobject _this) {
+    EGLint error = eglGetError();
+    return error;
+}
+
+jint jni_eglGetCurrentContext(JNIEnv *_env, jobject _this) {
+    return (jint)eglGetCurrentContext();
+}
+
+jint jni_eglGetCurrentDisplay(JNIEnv *_env, jobject _this) {
+    return (jint)eglGetCurrentDisplay();
+}
+
+jint jni_eglGetCurrentSurface(JNIEnv *_env, jobject _this, jint readdraw) {
+    return (jint)eglGetCurrentSurface(readdraw);
+}
+
+jboolean jni_eglDestroyContext(JNIEnv *_env, jobject _this, jobject display, jobject context) {
+    EGLDisplay dpy = getDisplay(_env, display);
+    EGLContext ctx = getContext(_env, context);
+    return eglDestroyContext(dpy, ctx);
+}
+
+jboolean jni_eglDestroySurface(JNIEnv *_env, jobject _this, jobject display, jobject surface) {
+    EGLDisplay dpy = getDisplay(_env, display);
+    EGLSurface sur = getSurface(_env, surface);
+
+    if (sur) {
+        SkPixelRef* ref = (SkPixelRef*)(_env->GetIntField(surface,
+                gSurface_NativePixelRefFieldID));
+        if (ref) {
+            ref->unlockPixels();
+            ref->safeUnref();
+        }
+    }
+    return eglDestroySurface(dpy, sur);
+}
+
+jint jni_eglGetDisplay(JNIEnv *_env, jobject _this, jobject native_display) {
+    return (jint)eglGetDisplay(EGL_DEFAULT_DISPLAY);
+}
+
+jboolean jni_eglMakeCurrent(JNIEnv *_env, jobject _this, jobject display, jobject draw, jobject read, jobject context) {
+    EGLDisplay dpy = getDisplay(_env, display);
+    EGLSurface sdr = getSurface(_env, draw);
+    EGLSurface srd = getSurface(_env, read);
+    EGLContext ctx = getContext(_env, context);
+    return eglMakeCurrent(dpy, sdr, srd, ctx);
+}
+
+jstring jni_eglQueryString(JNIEnv *_env, jobject _this, jobject display, jint name) {
+    EGLDisplay dpy = getDisplay(_env, display);
+    const char* chars = eglQueryString(dpy, name);
+    return _env->NewStringUTF(chars);
+}
+
+jboolean jni_eglSwapBuffers(JNIEnv *_env, jobject _this, jobject display, jobject surface) {
+    EGLDisplay dpy = getDisplay(_env, display);
+    EGLSurface sur = getSurface(_env, surface);
+    return eglSwapBuffers(dpy, sur);
+}
+
+jboolean jni_eglTerminate(JNIEnv *_env, jobject _this, jobject display) {
+    EGLDisplay dpy = getDisplay(_env, display);
+    return eglTerminate(dpy);
+}
+
+jboolean jni_eglCopyBuffers(JNIEnv *_env, jobject _this, jobject display,
+        jobject surface, jobject native_pixmap) {
+    // TODO: implement me
+    return JNI_FALSE;
+}
+
+jboolean jni_eglWaitGL(JNIEnv *_env, jobject _this) {
+    return eglWaitGL();
+}
+
+jboolean jni_eglWaitNative(JNIEnv *_env, jobject _this, jint engine, jobject bindTarget) {
+    return eglWaitNative(engine);
+}
+
+
+static const char *classPathName = "com/google/android/gles_jni/EGLImpl";
+
+#define DISPLAY "Ljavax/microedition/khronos/egl/EGLDisplay;"
+#define CONTEXT "Ljavax/microedition/khronos/egl/EGLContext;"
+#define CONFIG  "Ljavax/microedition/khronos/egl/EGLConfig;"
+#define SURFACE "Ljavax/microedition/khronos/egl/EGLSurface;"
+#define OBJECT  "Ljava/lang/Object;"
+#define STRING  "Ljava/lang/String;"
+
+static JNINativeMethod methods[] = {
+{"_nativeClassInit","()V", (void*)nativeClassInit },
+{"eglWaitGL",       "()Z", (void*)jni_eglWaitGL },
+{"eglInitialize",   "(" DISPLAY "[I)Z", (void*)jni_eglInitialize },
+{"eglQueryContext", "(" DISPLAY CONTEXT "I[I)Z", (void*)jni_eglQueryContext },
+{"eglQuerySurface", "(" DISPLAY SURFACE "I[I)Z", (void*)jni_eglQuerySurface },
+{"eglChooseConfig", "(" DISPLAY "[I[" CONFIG "I[I)Z", (void*)jni_eglChooseConfig },
+{"_eglCreateContext","(" DISPLAY CONFIG CONTEXT "[I)I", (void*)jni_eglCreateContext },
+{"eglGetConfigs",   "(" DISPLAY "[" CONFIG "I[I)Z", (void*)jni_eglGetConfigs },
+{"eglTerminate",    "(" DISPLAY ")Z", (void*)jni_eglTerminate },
+{"eglCopyBuffers",  "(" DISPLAY SURFACE OBJECT ")Z", (void*)jni_eglCopyBuffers },
+{"eglWaitNative",   "(I" OBJECT ")Z", (void*)jni_eglWaitNative },
+{"eglGetError",     "()I", (void*)jni_eglGetError },
+{"eglGetConfigAttrib", "(" DISPLAY CONFIG "I[I)Z", (void*)jni_eglGetConfigAttrib },
+{"_eglGetDisplay",   "(" OBJECT ")I", (void*)jni_eglGetDisplay },
+{"_eglGetCurrentContext",  "()I", (void*)jni_eglGetCurrentContext },
+{"_eglGetCurrentDisplay",  "()I", (void*)jni_eglGetCurrentDisplay },
+{"_eglGetCurrentSurface",  "(I)I", (void*)jni_eglGetCurrentSurface },
+{"_eglCreatePbufferSurface","(" DISPLAY CONFIG "[I)I", (void*)jni_eglCreatePbufferSurface },
+{"_eglCreatePixmapSurface", "(" SURFACE DISPLAY CONFIG OBJECT "[I)V", (void*)jni_eglCreatePixmapSurface },
+{"_eglCreateWindowSurface", "(" DISPLAY CONFIG OBJECT "[I)I", (void*)jni_eglCreateWindowSurface },
+{"eglDestroyContext",      "(" DISPLAY CONTEXT ")Z", (void*)jni_eglDestroyContext },
+{"eglDestroySurface",      "(" DISPLAY SURFACE ")Z", (void*)jni_eglDestroySurface },
+{"eglMakeCurrent",         "(" DISPLAY SURFACE SURFACE CONTEXT")Z", (void*)jni_eglMakeCurrent },
+{"eglQueryString",         "(" DISPLAY "I)" STRING, (void*)jni_eglQueryString },
+{"eglSwapBuffers",         "(" DISPLAY SURFACE ")Z", (void*)jni_eglSwapBuffers },
+};
+
+} // namespace android
+
+int register_com_google_android_gles_jni_EGLImpl(JNIEnv *_env)
+{
+    int err;
+    err = android::AndroidRuntime::registerNativeMethods(_env,
+            android::classPathName, android::methods, NELEM(android::methods));
+    return err;
+}
+
diff --git a/core/jni/com_google_android_gles_jni_GLImpl.cpp b/core/jni/com_google_android_gles_jni_GLImpl.cpp
new file mode 100644
index 0000000..9b09c9b
--- /dev/null
+++ b/core/jni/com_google_android_gles_jni_GLImpl.cpp
@@ -0,0 +1,6569 @@
+/* //device/libs/android_runtime/com_google_android_gles_jni_GLImpl.cpp
+**
+** Copyright 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.
+*/
+
+// This source file is automatically generated
+
+#include <android_runtime/AndroidRuntime.h>
+#include <utils/misc.h>
+
+#include <assert.h>
+#include <GLES/gl.h>
+
+#include <private/opengles/gl_context.h>
+
+#define _NUM_COMPRESSED_TEXTURE_FORMATS \
+        (::android::OGLES_NUM_COMPRESSED_TEXTURE_FORMATS)
+
+static int initialized = 0;
+
+static jclass nioAccessClass;
+static jclass bufferClass;
+static jclass OOMEClass;
+static jclass UOEClass;
+static jclass IAEClass;
+static jclass AIOOBEClass;
+static jmethodID getBasePointerID;
+static jmethodID getBaseArrayID;
+static jmethodID getBaseArrayOffsetID;
+static jfieldID positionID;
+static jfieldID limitID;
+static jfieldID elementSizeShiftID;
+
+/* Cache method IDs each time the class is loaded. */
+
+void
+nativeClassInitBuffer(JNIEnv *_env)
+{
+    jclass nioAccessClassLocal = _env->FindClass("java/nio/NIOAccess");
+    nioAccessClass = (jclass) _env->NewGlobalRef(nioAccessClassLocal);
+
+    jclass bufferClassLocal = _env->FindClass("java/nio/Buffer");
+    bufferClass = (jclass) _env->NewGlobalRef(bufferClassLocal);
+
+    getBasePointerID = _env->GetStaticMethodID(nioAccessClass,
+            "getBasePointer", "(Ljava/nio/Buffer;)J");
+    getBaseArrayID = _env->GetStaticMethodID(nioAccessClass,
+            "getBaseArray", "(Ljava/nio/Buffer;)Ljava/lang/Object;");
+    getBaseArrayOffsetID = _env->GetStaticMethodID(nioAccessClass,
+            "getBaseArrayOffset", "(Ljava/nio/Buffer;)I");
+
+    positionID = _env->GetFieldID(bufferClass, "position", "I");
+    limitID = _env->GetFieldID(bufferClass, "limit", "I");
+    elementSizeShiftID =
+        _env->GetFieldID(bufferClass, "_elementSizeShift", "I");
+}
+
+
+static void
+nativeClassInit(JNIEnv *_env, jclass glImplClass)
+{
+    nativeClassInitBuffer(_env);
+
+    jclass IAEClassLocal =
+        _env->FindClass("java/lang/IllegalArgumentException");
+    jclass OOMEClassLocal =
+         _env->FindClass("java/lang/OutOfMemoryError");
+    jclass UOEClassLocal =
+         _env->FindClass("java/lang/UnsupportedOperationException");
+    jclass AIOOBEClassLocal =
+         _env->FindClass("java/lang/ArrayIndexOutOfBoundsException");
+
+    IAEClass = (jclass) _env->NewGlobalRef(IAEClassLocal);
+    OOMEClass = (jclass) _env->NewGlobalRef(OOMEClassLocal);
+    UOEClass = (jclass) _env->NewGlobalRef(UOEClassLocal);
+    AIOOBEClass = (jclass) _env->NewGlobalRef(AIOOBEClassLocal);
+}
+
+static void *
+getPointer(JNIEnv *_env, jobject buffer, jarray *array, jint *remaining)
+{
+    jint position;
+    jint limit;
+    jint elementSizeShift;
+    jlong pointer;
+    jint offset;
+    void *data;
+
+    position = _env->GetIntField(buffer, positionID);
+    limit = _env->GetIntField(buffer, limitID);
+    elementSizeShift = _env->GetIntField(buffer, elementSizeShiftID);
+    *remaining = (limit - position) << elementSizeShift;
+    pointer = _env->CallStaticLongMethod(nioAccessClass,
+            getBasePointerID, buffer);
+    if (pointer != 0L) {
+        *array = NULL;
+        return (void *) (jint) pointer;
+    }
+    
+    *array = (jarray) _env->CallStaticObjectMethod(nioAccessClass,
+            getBaseArrayID, buffer);
+    offset = _env->CallStaticIntMethod(nioAccessClass,
+            getBaseArrayOffsetID, buffer);
+    data = _env->GetPrimitiveArrayCritical(*array, (jboolean *) 0);
+    
+    return (void *) ((char *) data + offset);
+}
+
+
+static void
+releasePointer(JNIEnv *_env, jarray array, void *data, jboolean commit)
+{
+    _env->ReleasePrimitiveArrayCritical(array, data,
+					   commit ? 0 : JNI_ABORT);
+}
+
+// --------------------------------------------------------------------------
+
+/* void glActiveTexture ( GLenum texture ) */
+static void
+android_glActiveTexture__I
+  (JNIEnv *_env, jobject _this, jint texture) {
+    glActiveTexture(
+        (GLenum)texture
+    );
+}
+
+/* void glAlphaFunc ( GLenum func, GLclampf ref ) */
+static void
+android_glAlphaFunc__IF
+  (JNIEnv *_env, jobject _this, jint func, jfloat ref) {
+    glAlphaFunc(
+        (GLenum)func,
+        (GLclampf)ref
+    );
+}
+
+/* void glAlphaFuncx ( GLenum func, GLclampx ref ) */
+static void
+android_glAlphaFuncx__II
+  (JNIEnv *_env, jobject _this, jint func, jint ref) {
+    glAlphaFuncx(
+        (GLenum)func,
+        (GLclampx)ref
+    );
+}
+
+/* void glBindTexture ( GLenum target, GLuint texture ) */
+static void
+android_glBindTexture__II
+  (JNIEnv *_env, jobject _this, jint target, jint texture) {
+    glBindTexture(
+        (GLenum)target,
+        (GLuint)texture
+    );
+}
+
+/* void glBlendFunc ( GLenum sfactor, GLenum dfactor ) */
+static void
+android_glBlendFunc__II
+  (JNIEnv *_env, jobject _this, jint sfactor, jint dfactor) {
+    glBlendFunc(
+        (GLenum)sfactor,
+        (GLenum)dfactor
+    );
+}
+
+/* void glClear ( GLbitfield mask ) */
+static void
+android_glClear__I
+  (JNIEnv *_env, jobject _this, jint mask) {
+    glClear(
+        (GLbitfield)mask
+    );
+}
+
+/* void glClearColor ( GLclampf red, GLclampf green, GLclampf blue, GLclampf alpha ) */
+static void
+android_glClearColor__FFFF
+  (JNIEnv *_env, jobject _this, jfloat red, jfloat green, jfloat blue, jfloat alpha) {
+    glClearColor(
+        (GLclampf)red,
+        (GLclampf)green,
+        (GLclampf)blue,
+        (GLclampf)alpha
+    );
+}
+
+/* void glClearColorx ( GLclampx red, GLclampx green, GLclampx blue, GLclampx alpha ) */
+static void
+android_glClearColorx__IIII
+  (JNIEnv *_env, jobject _this, jint red, jint green, jint blue, jint alpha) {
+    glClearColorx(
+        (GLclampx)red,
+        (GLclampx)green,
+        (GLclampx)blue,
+        (GLclampx)alpha
+    );
+}
+
+/* void glClearDepthf ( GLclampf depth ) */
+static void
+android_glClearDepthf__F
+  (JNIEnv *_env, jobject _this, jfloat depth) {
+    glClearDepthf(
+        (GLclampf)depth
+    );
+}
+
+/* void glClearDepthx ( GLclampx depth ) */
+static void
+android_glClearDepthx__I
+  (JNIEnv *_env, jobject _this, jint depth) {
+    glClearDepthx(
+        (GLclampx)depth
+    );
+}
+
+/* void glClearStencil ( GLint s ) */
+static void
+android_glClearStencil__I
+  (JNIEnv *_env, jobject _this, jint s) {
+    glClearStencil(
+        (GLint)s
+    );
+}
+
+/* void glClientActiveTexture ( GLenum texture ) */
+static void
+android_glClientActiveTexture__I
+  (JNIEnv *_env, jobject _this, jint texture) {
+    glClientActiveTexture(
+        (GLenum)texture
+    );
+}
+
+/* void glColor4f ( GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha ) */
+static void
+android_glColor4f__FFFF
+  (JNIEnv *_env, jobject _this, jfloat red, jfloat green, jfloat blue, jfloat alpha) {
+    glColor4f(
+        (GLfloat)red,
+        (GLfloat)green,
+        (GLfloat)blue,
+        (GLfloat)alpha
+    );
+}
+
+/* void glColor4x ( GLfixed red, GLfixed green, GLfixed blue, GLfixed alpha ) */
+static void
+android_glColor4x__IIII
+  (JNIEnv *_env, jobject _this, jint red, jint green, jint blue, jint alpha) {
+    glColor4x(
+        (GLfixed)red,
+        (GLfixed)green,
+        (GLfixed)blue,
+        (GLfixed)alpha
+    );
+}
+
+/* void glColorMask ( GLboolean red, GLboolean green, GLboolean blue, GLboolean alpha ) */
+static void
+android_glColorMask__ZZZZ
+  (JNIEnv *_env, jobject _this, jboolean red, jboolean green, jboolean blue, jboolean alpha) {
+    glColorMask(
+        (GLboolean)red,
+        (GLboolean)green,
+        (GLboolean)blue,
+        (GLboolean)alpha
+    );
+}
+
+/* void glColorPointer ( GLint size, GLenum type, GLsizei stride, const GLvoid *pointer ) */
+static void
+android_glColorPointerBounds__IIILjava_nio_Buffer_2I
+  (JNIEnv *_env, jobject _this, jint size, jint type, jint stride, jobject pointer_buf, jint remaining) {
+    jarray _array = (jarray) 0;
+    jint _remaining;
+    GLvoid *pointer = (GLvoid *) 0;
+
+    pointer = (GLvoid *)getPointer(_env, pointer_buf, &_array, &_remaining);
+    glColorPointerBounds(
+        (GLint)size,
+        (GLenum)type,
+        (GLsizei)stride,
+        (GLvoid *)pointer,
+        (GLsizei)remaining
+    );
+    if (_array) {
+        releasePointer(_env, _array, pointer, JNI_FALSE);
+    }
+}
+
+/* void glCompressedTexImage2D ( GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLint border, GLsizei imageSize, const GLvoid *data ) */
+static void
+android_glCompressedTexImage2D__IIIIIIILjava_nio_Buffer_2
+  (JNIEnv *_env, jobject _this, jint target, jint level, jint internalformat, jint width, jint height, jint border, jint imageSize, jobject data_buf) {
+    jarray _array = (jarray) 0;
+    jint _remaining;
+    GLvoid *data = (GLvoid *) 0;
+
+    data = (GLvoid *)getPointer(_env, data_buf, &_array, &_remaining);
+    glCompressedTexImage2D(
+        (GLenum)target,
+        (GLint)level,
+        (GLenum)internalformat,
+        (GLsizei)width,
+        (GLsizei)height,
+        (GLint)border,
+        (GLsizei)imageSize,
+        (GLvoid *)data
+    );
+    if (_array) {
+        releasePointer(_env, _array, data, JNI_FALSE);
+    }
+}
+
+/* void glCompressedTexSubImage2D ( GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLsizei imageSize, const GLvoid *data ) */
+static void
+android_glCompressedTexSubImage2D__IIIIIIIILjava_nio_Buffer_2
+  (JNIEnv *_env, jobject _this, jint target, jint level, jint xoffset, jint yoffset, jint width, jint height, jint format, jint imageSize, jobject data_buf) {
+    jarray _array = (jarray) 0;
+    jint _remaining;
+    GLvoid *data = (GLvoid *) 0;
+
+    data = (GLvoid *)getPointer(_env, data_buf, &_array, &_remaining);
+    glCompressedTexSubImage2D(
+        (GLenum)target,
+        (GLint)level,
+        (GLint)xoffset,
+        (GLint)yoffset,
+        (GLsizei)width,
+        (GLsizei)height,
+        (GLenum)format,
+        (GLsizei)imageSize,
+        (GLvoid *)data
+    );
+    if (_array) {
+        releasePointer(_env, _array, data, JNI_FALSE);
+    }
+}
+
+/* void glCopyTexImage2D ( GLenum target, GLint level, GLenum internalformat, GLint x, GLint y, GLsizei width, GLsizei height, GLint border ) */
+static void
+android_glCopyTexImage2D__IIIIIIII
+  (JNIEnv *_env, jobject _this, jint target, jint level, jint internalformat, jint x, jint y, jint width, jint height, jint border) {
+    glCopyTexImage2D(
+        (GLenum)target,
+        (GLint)level,
+        (GLenum)internalformat,
+        (GLint)x,
+        (GLint)y,
+        (GLsizei)width,
+        (GLsizei)height,
+        (GLint)border
+    );
+}
+
+/* void glCopyTexSubImage2D ( GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y, GLsizei width, GLsizei height ) */
+static void
+android_glCopyTexSubImage2D__IIIIIIII
+  (JNIEnv *_env, jobject _this, jint target, jint level, jint xoffset, jint yoffset, jint x, jint y, jint width, jint height) {
+    glCopyTexSubImage2D(
+        (GLenum)target,
+        (GLint)level,
+        (GLint)xoffset,
+        (GLint)yoffset,
+        (GLint)x,
+        (GLint)y,
+        (GLsizei)width,
+        (GLsizei)height
+    );
+}
+
+/* void glCullFace ( GLenum mode ) */
+static void
+android_glCullFace__I
+  (JNIEnv *_env, jobject _this, jint mode) {
+    glCullFace(
+        (GLenum)mode
+    );
+}
+
+/* void glDeleteTextures ( GLsizei n, const GLuint *textures ) */
+static void
+android_glDeleteTextures__I_3II
+  (JNIEnv *_env, jobject _this, jint n, jintArray textures_ref, jint offset) {
+    GLuint *textures_base = (GLuint *) 0;
+    jint _remaining;
+    GLuint *textures = (GLuint *) 0;
+
+    if (!textures_ref) {
+        _env->ThrowNew(IAEClass, "textures == null");
+        goto exit;
+    }
+    if (offset < 0) {
+        _env->ThrowNew(IAEClass, "offset < 0");
+        goto exit;
+    }
+    _remaining = _env->GetArrayLength(textures_ref) - offset;
+    if (_remaining < n) {
+        _env->ThrowNew(IAEClass, "length - offset < n");
+        goto exit;
+    }
+    textures_base = (GLuint *)
+        _env->GetPrimitiveArrayCritical(textures_ref, (jboolean *)0);
+    textures = textures_base + offset;
+
+    glDeleteTextures(
+        (GLsizei)n,
+        (GLuint *)textures
+    );
+
+exit:
+    if (textures_base) {
+        _env->ReleasePrimitiveArrayCritical(textures_ref, textures_base,
+            JNI_ABORT);
+    }
+}
+
+/* void glDeleteTextures ( GLsizei n, const GLuint *textures ) */
+static void
+android_glDeleteTextures__ILjava_nio_IntBuffer_2
+  (JNIEnv *_env, jobject _this, jint n, jobject textures_buf) {
+    jarray _array = (jarray) 0;
+    jint _remaining;
+    GLuint *textures = (GLuint *) 0;
+
+    textures = (GLuint *)getPointer(_env, textures_buf, &_array, &_remaining);
+    if (_remaining < n) {
+        _env->ThrowNew(IAEClass, "remaining() < n");
+        goto exit;
+    }
+    glDeleteTextures(
+        (GLsizei)n,
+        (GLuint *)textures
+    );
+
+exit:
+    if (_array) {
+        releasePointer(_env, _array, textures, JNI_FALSE);
+    }
+}
+
+/* void glDepthFunc ( GLenum func ) */
+static void
+android_glDepthFunc__I
+  (JNIEnv *_env, jobject _this, jint func) {
+    glDepthFunc(
+        (GLenum)func
+    );
+}
+
+/* void glDepthMask ( GLboolean flag ) */
+static void
+android_glDepthMask__Z
+  (JNIEnv *_env, jobject _this, jboolean flag) {
+    glDepthMask(
+        (GLboolean)flag
+    );
+}
+
+/* void glDepthRangef ( GLclampf zNear, GLclampf zFar ) */
+static void
+android_glDepthRangef__FF
+  (JNIEnv *_env, jobject _this, jfloat zNear, jfloat zFar) {
+    glDepthRangef(
+        (GLclampf)zNear,
+        (GLclampf)zFar
+    );
+}
+
+/* void glDepthRangex ( GLclampx zNear, GLclampx zFar ) */
+static void
+android_glDepthRangex__II
+  (JNIEnv *_env, jobject _this, jint zNear, jint zFar) {
+    glDepthRangex(
+        (GLclampx)zNear,
+        (GLclampx)zFar
+    );
+}
+
+/* void glDisable ( GLenum cap ) */
+static void
+android_glDisable__I
+  (JNIEnv *_env, jobject _this, jint cap) {
+    glDisable(
+        (GLenum)cap
+    );
+}
+
+/* void glDisableClientState ( GLenum array ) */
+static void
+android_glDisableClientState__I
+  (JNIEnv *_env, jobject _this, jint array) {
+    glDisableClientState(
+        (GLenum)array
+    );
+}
+
+/* void glDrawArrays ( GLenum mode, GLint first, GLsizei count ) */
+static void
+android_glDrawArrays__III
+  (JNIEnv *_env, jobject _this, jint mode, jint first, jint count) {
+    glDrawArrays(
+        (GLenum)mode,
+        (GLint)first,
+        (GLsizei)count
+    );
+}
+
+/* void glDrawElements ( GLenum mode, GLsizei count, GLenum type, const GLvoid *indices ) */
+static void
+android_glDrawElements__IIILjava_nio_Buffer_2
+  (JNIEnv *_env, jobject _this, jint mode, jint count, jint type, jobject indices_buf) {
+    jarray _array = (jarray) 0;
+    jint _remaining;
+    GLvoid *indices = (GLvoid *) 0;
+
+    indices = (GLvoid *)getPointer(_env, indices_buf, &_array, &_remaining);
+    if (_remaining < count) {
+        _env->ThrowNew(AIOOBEClass, "remaining() < count");
+        goto exit;
+    }
+    glDrawElements(
+        (GLenum)mode,
+        (GLsizei)count,
+        (GLenum)type,
+        (GLvoid *)indices
+    );
+
+exit:
+    if (_array) {
+        releasePointer(_env, _array, indices, JNI_FALSE);
+    }
+}
+
+/* void glEnable ( GLenum cap ) */
+static void
+android_glEnable__I
+  (JNIEnv *_env, jobject _this, jint cap) {
+    glEnable(
+        (GLenum)cap
+    );
+}
+
+/* void glEnableClientState ( GLenum array ) */
+static void
+android_glEnableClientState__I
+  (JNIEnv *_env, jobject _this, jint array) {
+    glEnableClientState(
+        (GLenum)array
+    );
+}
+
+/* void glFinish ( void ) */
+static void
+android_glFinish__
+  (JNIEnv *_env, jobject _this) {
+    glFinish();
+}
+
+/* void glFlush ( void ) */
+static void
+android_glFlush__
+  (JNIEnv *_env, jobject _this) {
+    glFlush();
+}
+
+/* void glFogf ( GLenum pname, GLfloat param ) */
+static void
+android_glFogf__IF
+  (JNIEnv *_env, jobject _this, jint pname, jfloat param) {
+    glFogf(
+        (GLenum)pname,
+        (GLfloat)param
+    );
+}
+
+/* void glFogfv ( GLenum pname, const GLfloat *params ) */
+static void
+android_glFogfv__I_3FI
+  (JNIEnv *_env, jobject _this, jint pname, jfloatArray params_ref, jint offset) {
+    GLfloat *params_base = (GLfloat *) 0;
+    jint _remaining;
+    GLfloat *params = (GLfloat *) 0;
+
+    if (!params_ref) {
+        _env->ThrowNew(IAEClass, "params == null");
+        goto exit;
+    }
+    if (offset < 0) {
+        _env->ThrowNew(IAEClass, "offset < 0");
+        goto exit;
+    }
+    _remaining = _env->GetArrayLength(params_ref) - offset;
+    int _needed;
+    switch (pname) {
+#if defined(GL_FOG_MODE)
+        case GL_FOG_MODE:
+#endif // defined(GL_FOG_MODE)
+#if defined(GL_FOG_DENSITY)
+        case GL_FOG_DENSITY:
+#endif // defined(GL_FOG_DENSITY)
+#if defined(GL_FOG_START)
+        case GL_FOG_START:
+#endif // defined(GL_FOG_START)
+#if defined(GL_FOG_END)
+        case GL_FOG_END:
+#endif // defined(GL_FOG_END)
+            _needed = 1;
+            break;
+#if defined(GL_FOG_COLOR)
+        case GL_FOG_COLOR:
+#endif // defined(GL_FOG_COLOR)
+            _needed = 4;
+            break;
+        default:
+            _needed = 0;
+            break;
+    }
+    if (_remaining < _needed) {
+        _env->ThrowNew(IAEClass, "length - offset < needed");
+        goto exit;
+    }
+    params_base = (GLfloat *)
+        _env->GetPrimitiveArrayCritical(params_ref, (jboolean *)0);
+    params = params_base + offset;
+
+    glFogfv(
+        (GLenum)pname,
+        (GLfloat *)params
+    );
+
+exit:
+    if (params_base) {
+        _env->ReleasePrimitiveArrayCritical(params_ref, params_base,
+            JNI_ABORT);
+    }
+}
+
+/* void glFogfv ( GLenum pname, const GLfloat *params ) */
+static void
+android_glFogfv__ILjava_nio_FloatBuffer_2
+  (JNIEnv *_env, jobject _this, jint pname, jobject params_buf) {
+    jarray _array = (jarray) 0;
+    jint _remaining;
+    GLfloat *params = (GLfloat *) 0;
+
+    params = (GLfloat *)getPointer(_env, params_buf, &_array, &_remaining);
+    int _needed;
+    switch (pname) {
+#if defined(GL_FOG_MODE)
+        case GL_FOG_MODE:
+#endif // defined(GL_FOG_MODE)
+#if defined(GL_FOG_DENSITY)
+        case GL_FOG_DENSITY:
+#endif // defined(GL_FOG_DENSITY)
+#if defined(GL_FOG_START)
+        case GL_FOG_START:
+#endif // defined(GL_FOG_START)
+#if defined(GL_FOG_END)
+        case GL_FOG_END:
+#endif // defined(GL_FOG_END)
+            _needed = 1;
+            break;
+#if defined(GL_FOG_COLOR)
+        case GL_FOG_COLOR:
+#endif // defined(GL_FOG_COLOR)
+            _needed = 4;
+            break;
+        default:
+            _needed = 0;
+            break;
+    }
+    if (_remaining < _needed) {
+        _env->ThrowNew(IAEClass, "remaining() < needed");
+        goto exit;
+    }
+    glFogfv(
+        (GLenum)pname,
+        (GLfloat *)params
+    );
+
+exit:
+    if (_array) {
+        releasePointer(_env, _array, params, JNI_FALSE);
+    }
+}
+
+/* void glFogx ( GLenum pname, GLfixed param ) */
+static void
+android_glFogx__II
+  (JNIEnv *_env, jobject _this, jint pname, jint param) {
+    glFogx(
+        (GLenum)pname,
+        (GLfixed)param
+    );
+}
+
+/* void glFogxv ( GLenum pname, const GLfixed *params ) */
+static void
+android_glFogxv__I_3II
+  (JNIEnv *_env, jobject _this, jint pname, jintArray params_ref, jint offset) {
+    GLfixed *params_base = (GLfixed *) 0;
+    jint _remaining;
+    GLfixed *params = (GLfixed *) 0;
+
+    if (!params_ref) {
+        _env->ThrowNew(IAEClass, "params == null");
+        goto exit;
+    }
+    if (offset < 0) {
+        _env->ThrowNew(IAEClass, "offset < 0");
+        goto exit;
+    }
+    _remaining = _env->GetArrayLength(params_ref) - offset;
+    int _needed;
+    switch (pname) {
+#if defined(GL_FOG_MODE)
+        case GL_FOG_MODE:
+#endif // defined(GL_FOG_MODE)
+#if defined(GL_FOG_DENSITY)
+        case GL_FOG_DENSITY:
+#endif // defined(GL_FOG_DENSITY)
+#if defined(GL_FOG_START)
+        case GL_FOG_START:
+#endif // defined(GL_FOG_START)
+#if defined(GL_FOG_END)
+        case GL_FOG_END:
+#endif // defined(GL_FOG_END)
+            _needed = 1;
+            break;
+#if defined(GL_FOG_COLOR)
+        case GL_FOG_COLOR:
+#endif // defined(GL_FOG_COLOR)
+            _needed = 4;
+            break;
+        default:
+            _needed = 0;
+            break;
+    }
+    if (_remaining < _needed) {
+        _env->ThrowNew(IAEClass, "length - offset < needed");
+        goto exit;
+    }
+    params_base = (GLfixed *)
+        _env->GetPrimitiveArrayCritical(params_ref, (jboolean *)0);
+    params = params_base + offset;
+
+    glFogxv(
+        (GLenum)pname,
+        (GLfixed *)params
+    );
+
+exit:
+    if (params_base) {
+        _env->ReleasePrimitiveArrayCritical(params_ref, params_base,
+            JNI_ABORT);
+    }
+}
+
+/* void glFogxv ( GLenum pname, const GLfixed *params ) */
+static void
+android_glFogxv__ILjava_nio_IntBuffer_2
+  (JNIEnv *_env, jobject _this, jint pname, jobject params_buf) {
+    jarray _array = (jarray) 0;
+    jint _remaining;
+    GLfixed *params = (GLfixed *) 0;
+
+    params = (GLfixed *)getPointer(_env, params_buf, &_array, &_remaining);
+    int _needed;
+    switch (pname) {
+#if defined(GL_FOG_MODE)
+        case GL_FOG_MODE:
+#endif // defined(GL_FOG_MODE)
+#if defined(GL_FOG_DENSITY)
+        case GL_FOG_DENSITY:
+#endif // defined(GL_FOG_DENSITY)
+#if defined(GL_FOG_START)
+        case GL_FOG_START:
+#endif // defined(GL_FOG_START)
+#if defined(GL_FOG_END)
+        case GL_FOG_END:
+#endif // defined(GL_FOG_END)
+            _needed = 1;
+            break;
+#if defined(GL_FOG_COLOR)
+        case GL_FOG_COLOR:
+#endif // defined(GL_FOG_COLOR)
+            _needed = 4;
+            break;
+        default:
+            _needed = 0;
+            break;
+    }
+    if (_remaining < _needed) {
+        _env->ThrowNew(IAEClass, "remaining() < needed");
+        goto exit;
+    }
+    glFogxv(
+        (GLenum)pname,
+        (GLfixed *)params
+    );
+
+exit:
+    if (_array) {
+        releasePointer(_env, _array, params, JNI_FALSE);
+    }
+}
+
+/* void glFrontFace ( GLenum mode ) */
+static void
+android_glFrontFace__I
+  (JNIEnv *_env, jobject _this, jint mode) {
+    glFrontFace(
+        (GLenum)mode
+    );
+}
+
+/* void glFrustumf ( GLfloat left, GLfloat right, GLfloat bottom, GLfloat top, GLfloat zNear, GLfloat zFar ) */
+static void
+android_glFrustumf__FFFFFF
+  (JNIEnv *_env, jobject _this, jfloat left, jfloat right, jfloat bottom, jfloat top, jfloat zNear, jfloat zFar) {
+    glFrustumf(
+        (GLfloat)left,
+        (GLfloat)right,
+        (GLfloat)bottom,
+        (GLfloat)top,
+        (GLfloat)zNear,
+        (GLfloat)zFar
+    );
+}
+
+/* void glFrustumx ( GLfixed left, GLfixed right, GLfixed bottom, GLfixed top, GLfixed zNear, GLfixed zFar ) */
+static void
+android_glFrustumx__IIIIII
+  (JNIEnv *_env, jobject _this, jint left, jint right, jint bottom, jint top, jint zNear, jint zFar) {
+    glFrustumx(
+        (GLfixed)left,
+        (GLfixed)right,
+        (GLfixed)bottom,
+        (GLfixed)top,
+        (GLfixed)zNear,
+        (GLfixed)zFar
+    );
+}
+
+/* void glGenTextures ( GLsizei n, GLuint *textures ) */
+static void
+android_glGenTextures__I_3II
+  (JNIEnv *_env, jobject _this, jint n, jintArray textures_ref, jint offset) {
+    jint _exception = 0;
+    GLuint *textures_base = (GLuint *) 0;
+    jint _remaining;
+    GLuint *textures = (GLuint *) 0;
+
+    if (!textures_ref) {
+        _exception = 1;
+        _env->ThrowNew(IAEClass, "textures == null");
+        goto exit;
+    }
+    if (offset < 0) {
+        _exception = 1;
+        _env->ThrowNew(IAEClass, "offset < 0");
+        goto exit;
+    }
+    _remaining = _env->GetArrayLength(textures_ref) - offset;
+    if (_remaining < n) {
+        _exception = 1;
+        _env->ThrowNew(IAEClass, "length - offset < n");
+        goto exit;
+    }
+    textures_base = (GLuint *)
+        _env->GetPrimitiveArrayCritical(textures_ref, (jboolean *)0);
+    textures = textures_base + offset;
+
+    glGenTextures(
+        (GLsizei)n,
+        (GLuint *)textures
+    );
+
+exit:
+    if (textures_base) {
+        _env->ReleasePrimitiveArrayCritical(textures_ref, textures_base,
+            _exception ? JNI_ABORT: 0);
+    }
+}
+
+/* void glGenTextures ( GLsizei n, GLuint *textures ) */
+static void
+android_glGenTextures__ILjava_nio_IntBuffer_2
+  (JNIEnv *_env, jobject _this, jint n, jobject textures_buf) {
+    jint _exception = 0;
+    jarray _array = (jarray) 0;
+    jint _remaining;
+    GLuint *textures = (GLuint *) 0;
+
+    textures = (GLuint *)getPointer(_env, textures_buf, &_array, &_remaining);
+    if (_remaining < n) {
+        _exception = 1;
+        _env->ThrowNew(IAEClass, "remaining() < n");
+        goto exit;
+    }
+    glGenTextures(
+        (GLsizei)n,
+        (GLuint *)textures
+    );
+
+exit:
+    if (_array) {
+        releasePointer(_env, _array, textures, _exception ? JNI_FALSE : JNI_TRUE);
+    }
+}
+
+/* GLenum glGetError ( void ) */
+static jint
+android_glGetError__
+  (JNIEnv *_env, jobject _this) {
+    GLenum _returnValue;
+    _returnValue = glGetError();
+    return _returnValue;
+}
+
+/* void glGetIntegerv ( GLenum pname, GLint *params ) */
+static void
+android_glGetIntegerv__I_3II
+  (JNIEnv *_env, jobject _this, jint pname, jintArray params_ref, jint offset) {
+    jint _exception = 0;
+    GLint *params_base = (GLint *) 0;
+    jint _remaining;
+    GLint *params = (GLint *) 0;
+
+    if (!params_ref) {
+        _exception = 1;
+        _env->ThrowNew(IAEClass, "params == null");
+        goto exit;
+    }
+    if (offset < 0) {
+        _exception = 1;
+        _env->ThrowNew(IAEClass, "offset < 0");
+        goto exit;
+    }
+    _remaining = _env->GetArrayLength(params_ref) - offset;
+    int _needed;
+    switch (pname) {
+#if defined(GL_ALPHA_BITS)
+        case GL_ALPHA_BITS:
+#endif // defined(GL_ALPHA_BITS)
+#if defined(GL_ALPHA_TEST_FUNC)
+        case GL_ALPHA_TEST_FUNC:
+#endif // defined(GL_ALPHA_TEST_FUNC)
+#if defined(GL_ALPHA_TEST_REF)
+        case GL_ALPHA_TEST_REF:
+#endif // defined(GL_ALPHA_TEST_REF)
+#if defined(GL_BLEND_DST)
+        case GL_BLEND_DST:
+#endif // defined(GL_BLEND_DST)
+#if defined(GL_BLUE_BITS)
+        case GL_BLUE_BITS:
+#endif // defined(GL_BLUE_BITS)
+#if defined(GL_COLOR_ARRAY_BUFFER_BINDING)
+        case GL_COLOR_ARRAY_BUFFER_BINDING:
+#endif // defined(GL_COLOR_ARRAY_BUFFER_BINDING)
+#if defined(GL_COLOR_ARRAY_SIZE)
+        case GL_COLOR_ARRAY_SIZE:
+#endif // defined(GL_COLOR_ARRAY_SIZE)
+#if defined(GL_COLOR_ARRAY_STRIDE)
+        case GL_COLOR_ARRAY_STRIDE:
+#endif // defined(GL_COLOR_ARRAY_STRIDE)
+#if defined(GL_COLOR_ARRAY_TYPE)
+        case GL_COLOR_ARRAY_TYPE:
+#endif // defined(GL_COLOR_ARRAY_TYPE)
+#if defined(GL_CULL_FACE)
+        case GL_CULL_FACE:
+#endif // defined(GL_CULL_FACE)
+#if defined(GL_DEPTH_BITS)
+        case GL_DEPTH_BITS:
+#endif // defined(GL_DEPTH_BITS)
+#if defined(GL_DEPTH_CLEAR_VALUE)
+        case GL_DEPTH_CLEAR_VALUE:
+#endif // defined(GL_DEPTH_CLEAR_VALUE)
+#if defined(GL_DEPTH_FUNC)
+        case GL_DEPTH_FUNC:
+#endif // defined(GL_DEPTH_FUNC)
+#if defined(GL_DEPTH_WRITEMASK)
+        case GL_DEPTH_WRITEMASK:
+#endif // defined(GL_DEPTH_WRITEMASK)
+#if defined(GL_FOG_DENSITY)
+        case GL_FOG_DENSITY:
+#endif // defined(GL_FOG_DENSITY)
+#if defined(GL_FOG_END)
+        case GL_FOG_END:
+#endif // defined(GL_FOG_END)
+#if defined(GL_FOG_MODE)
+        case GL_FOG_MODE:
+#endif // defined(GL_FOG_MODE)
+#if defined(GL_FOG_START)
+        case GL_FOG_START:
+#endif // defined(GL_FOG_START)
+#if defined(GL_FRONT_FACE)
+        case GL_FRONT_FACE:
+#endif // defined(GL_FRONT_FACE)
+#if defined(GL_GREEN_BITS)
+        case GL_GREEN_BITS:
+#endif // defined(GL_GREEN_BITS)
+#if defined(GL_IMPLEMENTATION_COLOR_READ_FORMAT_OES)
+        case GL_IMPLEMENTATION_COLOR_READ_FORMAT_OES:
+#endif // defined(GL_IMPLEMENTATION_COLOR_READ_FORMAT_OES)
+#if defined(GL_IMPLEMENTATION_COLOR_READ_TYPE_OES)
+        case GL_IMPLEMENTATION_COLOR_READ_TYPE_OES:
+#endif // defined(GL_IMPLEMENTATION_COLOR_READ_TYPE_OES)
+#if defined(GL_LIGHT_MODEL_TWO_SIDE)
+        case GL_LIGHT_MODEL_TWO_SIDE:
+#endif // defined(GL_LIGHT_MODEL_TWO_SIDE)
+#if defined(GL_LINE_SMOOTH_HINT)
+        case GL_LINE_SMOOTH_HINT:
+#endif // defined(GL_LINE_SMOOTH_HINT)
+#if defined(GL_LINE_WIDTH)
+        case GL_LINE_WIDTH:
+#endif // defined(GL_LINE_WIDTH)
+#if defined(GL_LOGIC_OP_MODE)
+        case GL_LOGIC_OP_MODE:
+#endif // defined(GL_LOGIC_OP_MODE)
+#if defined(GL_MATRIX_INDEX_ARRAY_BUFFER_BINDING_OES)
+        case GL_MATRIX_INDEX_ARRAY_BUFFER_BINDING_OES:
+#endif // defined(GL_MATRIX_INDEX_ARRAY_BUFFER_BINDING_OES)
+#if defined(GL_MATRIX_INDEX_ARRAY_SIZE_OES)
+        case GL_MATRIX_INDEX_ARRAY_SIZE_OES:
+#endif // defined(GL_MATRIX_INDEX_ARRAY_SIZE_OES)
+#if defined(GL_MATRIX_INDEX_ARRAY_STRIDE_OES)
+        case GL_MATRIX_INDEX_ARRAY_STRIDE_OES:
+#endif // defined(GL_MATRIX_INDEX_ARRAY_STRIDE_OES)
+#if defined(GL_MATRIX_INDEX_ARRAY_TYPE_OES)
+        case GL_MATRIX_INDEX_ARRAY_TYPE_OES:
+#endif // defined(GL_MATRIX_INDEX_ARRAY_TYPE_OES)
+#if defined(GL_MATRIX_MODE)
+        case GL_MATRIX_MODE:
+#endif // defined(GL_MATRIX_MODE)
+#if defined(GL_MAX_CLIP_PLANES)
+        case GL_MAX_CLIP_PLANES:
+#endif // defined(GL_MAX_CLIP_PLANES)
+#if defined(GL_MAX_ELEMENTS_INDICES)
+        case GL_MAX_ELEMENTS_INDICES:
+#endif // defined(GL_MAX_ELEMENTS_INDICES)
+#if defined(GL_MAX_ELEMENTS_VERTICES)
+        case GL_MAX_ELEMENTS_VERTICES:
+#endif // defined(GL_MAX_ELEMENTS_VERTICES)
+#if defined(GL_MAX_LIGHTS)
+        case GL_MAX_LIGHTS:
+#endif // defined(GL_MAX_LIGHTS)
+#if defined(GL_MAX_MODELVIEW_STACK_DEPTH)
+        case GL_MAX_MODELVIEW_STACK_DEPTH:
+#endif // defined(GL_MAX_MODELVIEW_STACK_DEPTH)
+#if defined(GL_MAX_PALETTE_MATRICES_OES)
+        case GL_MAX_PALETTE_MATRICES_OES:
+#endif // defined(GL_MAX_PALETTE_MATRICES_OES)
+#if defined(GL_MAX_PROJECTION_STACK_DEPTH)
+        case GL_MAX_PROJECTION_STACK_DEPTH:
+#endif // defined(GL_MAX_PROJECTION_STACK_DEPTH)
+#if defined(GL_MAX_TEXTURE_SIZE)
+        case GL_MAX_TEXTURE_SIZE:
+#endif // defined(GL_MAX_TEXTURE_SIZE)
+#if defined(GL_MAX_TEXTURE_STACK_DEPTH)
+        case GL_MAX_TEXTURE_STACK_DEPTH:
+#endif // defined(GL_MAX_TEXTURE_STACK_DEPTH)
+#if defined(GL_MAX_TEXTURE_UNITS)
+        case GL_MAX_TEXTURE_UNITS:
+#endif // defined(GL_MAX_TEXTURE_UNITS)
+#if defined(GL_MAX_VERTEX_UNITS_OES)
+        case GL_MAX_VERTEX_UNITS_OES:
+#endif // defined(GL_MAX_VERTEX_UNITS_OES)
+#if defined(GL_MODELVIEW_STACK_DEPTH)
+        case GL_MODELVIEW_STACK_DEPTH:
+#endif // defined(GL_MODELVIEW_STACK_DEPTH)
+#if defined(GL_NORMAL_ARRAY_BUFFER_BINDING)
+        case GL_NORMAL_ARRAY_BUFFER_BINDING:
+#endif // defined(GL_NORMAL_ARRAY_BUFFER_BINDING)
+#if defined(GL_NORMAL_ARRAY_STRIDE)
+        case GL_NORMAL_ARRAY_STRIDE:
+#endif // defined(GL_NORMAL_ARRAY_STRIDE)
+#if defined(GL_NORMAL_ARRAY_TYPE)
+        case GL_NORMAL_ARRAY_TYPE:
+#endif // defined(GL_NORMAL_ARRAY_TYPE)
+#if defined(GL_NUM_COMPRESSED_TEXTURE_FORMATS)
+        case GL_NUM_COMPRESSED_TEXTURE_FORMATS:
+#endif // defined(GL_NUM_COMPRESSED_TEXTURE_FORMATS)
+#if defined(GL_PACK_ALIGNMENT)
+        case GL_PACK_ALIGNMENT:
+#endif // defined(GL_PACK_ALIGNMENT)
+#if defined(GL_PERSPECTIVE_CORRECTION_HINT)
+        case GL_PERSPECTIVE_CORRECTION_HINT:
+#endif // defined(GL_PERSPECTIVE_CORRECTION_HINT)
+#if defined(GL_POINT_SIZE)
+        case GL_POINT_SIZE:
+#endif // defined(GL_POINT_SIZE)
+#if defined(GL_POINT_SIZE_ARRAY_BUFFER_BINDING_OES)
+        case GL_POINT_SIZE_ARRAY_BUFFER_BINDING_OES:
+#endif // defined(GL_POINT_SIZE_ARRAY_BUFFER_BINDING_OES)
+#if defined(GL_POINT_SIZE_ARRAY_STRIDE_OES)
+        case GL_POINT_SIZE_ARRAY_STRIDE_OES:
+#endif // defined(GL_POINT_SIZE_ARRAY_STRIDE_OES)
+#if defined(GL_POINT_SIZE_ARRAY_TYPE_OES)
+        case GL_POINT_SIZE_ARRAY_TYPE_OES:
+#endif // defined(GL_POINT_SIZE_ARRAY_TYPE_OES)
+#if defined(GL_POINT_SMOOTH_HINT)
+        case GL_POINT_SMOOTH_HINT:
+#endif // defined(GL_POINT_SMOOTH_HINT)
+#if defined(GL_POLYGON_OFFSET_FACTOR)
+        case GL_POLYGON_OFFSET_FACTOR:
+#endif // defined(GL_POLYGON_OFFSET_FACTOR)
+#if defined(GL_POLYGON_OFFSET_UNITS)
+        case GL_POLYGON_OFFSET_UNITS:
+#endif // defined(GL_POLYGON_OFFSET_UNITS)
+#if defined(GL_PROJECTION_STACK_DEPTH)
+        case GL_PROJECTION_STACK_DEPTH:
+#endif // defined(GL_PROJECTION_STACK_DEPTH)
+#if defined(GL_RED_BITS)
+        case GL_RED_BITS:
+#endif // defined(GL_RED_BITS)
+#if defined(GL_SHADE_MODEL)
+        case GL_SHADE_MODEL:
+#endif // defined(GL_SHADE_MODEL)
+#if defined(GL_STENCIL_BITS)
+        case GL_STENCIL_BITS:
+#endif // defined(GL_STENCIL_BITS)
+#if defined(GL_STENCIL_CLEAR_VALUE)
+        case GL_STENCIL_CLEAR_VALUE:
+#endif // defined(GL_STENCIL_CLEAR_VALUE)
+#if defined(GL_STENCIL_FAIL)
+        case GL_STENCIL_FAIL:
+#endif // defined(GL_STENCIL_FAIL)
+#if defined(GL_STENCIL_FUNC)
+        case GL_STENCIL_FUNC:
+#endif // defined(GL_STENCIL_FUNC)
+#if defined(GL_STENCIL_PASS_DEPTH_FAIL)
+        case GL_STENCIL_PASS_DEPTH_FAIL:
+#endif // defined(GL_STENCIL_PASS_DEPTH_FAIL)
+#if defined(GL_STENCIL_PASS_DEPTH_PASS)
+        case GL_STENCIL_PASS_DEPTH_PASS:
+#endif // defined(GL_STENCIL_PASS_DEPTH_PASS)
+#if defined(GL_STENCIL_REF)
+        case GL_STENCIL_REF:
+#endif // defined(GL_STENCIL_REF)
+#if defined(GL_STENCIL_VALUE_MASK)
+        case GL_STENCIL_VALUE_MASK:
+#endif // defined(GL_STENCIL_VALUE_MASK)
+#if defined(GL_STENCIL_WRITEMASK)
+        case GL_STENCIL_WRITEMASK:
+#endif // defined(GL_STENCIL_WRITEMASK)
+#if defined(GL_SUBPIXEL_BITS)
+        case GL_SUBPIXEL_BITS:
+#endif // defined(GL_SUBPIXEL_BITS)
+#if defined(GL_TEXTURE_BINDING_2D)
+        case GL_TEXTURE_BINDING_2D:
+#endif // defined(GL_TEXTURE_BINDING_2D)
+#if defined(GL_TEXTURE_COORD_ARRAY_BUFFER_BINDING)
+        case GL_TEXTURE_COORD_ARRAY_BUFFER_BINDING:
+#endif // defined(GL_TEXTURE_COORD_ARRAY_BUFFER_BINDING)
+#if defined(GL_TEXTURE_COORD_ARRAY_SIZE)
+        case GL_TEXTURE_COORD_ARRAY_SIZE:
+#endif // defined(GL_TEXTURE_COORD_ARRAY_SIZE)
+#if defined(GL_TEXTURE_COORD_ARRAY_STRIDE)
+        case GL_TEXTURE_COORD_ARRAY_STRIDE:
+#endif // defined(GL_TEXTURE_COORD_ARRAY_STRIDE)
+#if defined(GL_TEXTURE_COORD_ARRAY_TYPE)
+        case GL_TEXTURE_COORD_ARRAY_TYPE:
+#endif // defined(GL_TEXTURE_COORD_ARRAY_TYPE)
+#if defined(GL_TEXTURE_STACK_DEPTH)
+        case GL_TEXTURE_STACK_DEPTH:
+#endif // defined(GL_TEXTURE_STACK_DEPTH)
+#if defined(GL_UNPACK_ALIGNMENT)
+        case GL_UNPACK_ALIGNMENT:
+#endif // defined(GL_UNPACK_ALIGNMENT)
+#if defined(GL_VERTEX_ARRAY_BUFFER_BINDING)
+        case GL_VERTEX_ARRAY_BUFFER_BINDING:
+#endif // defined(GL_VERTEX_ARRAY_BUFFER_BINDING)
+#if defined(GL_VERTEX_ARRAY_SIZE)
+        case GL_VERTEX_ARRAY_SIZE:
+#endif // defined(GL_VERTEX_ARRAY_SIZE)
+#if defined(GL_VERTEX_ARRAY_STRIDE)
+        case GL_VERTEX_ARRAY_STRIDE:
+#endif // defined(GL_VERTEX_ARRAY_STRIDE)
+#if defined(GL_VERTEX_ARRAY_TYPE)
+        case GL_VERTEX_ARRAY_TYPE:
+#endif // defined(GL_VERTEX_ARRAY_TYPE)
+#if defined(GL_WEIGHT_ARRAY_BUFFER_BINDING_OES)
+        case GL_WEIGHT_ARRAY_BUFFER_BINDING_OES:
+#endif // defined(GL_WEIGHT_ARRAY_BUFFER_BINDING_OES)
+#if defined(GL_WEIGHT_ARRAY_SIZE_OES)
+        case GL_WEIGHT_ARRAY_SIZE_OES:
+#endif // defined(GL_WEIGHT_ARRAY_SIZE_OES)
+#if defined(GL_WEIGHT_ARRAY_STRIDE_OES)
+        case GL_WEIGHT_ARRAY_STRIDE_OES:
+#endif // defined(GL_WEIGHT_ARRAY_STRIDE_OES)
+#if defined(GL_WEIGHT_ARRAY_TYPE_OES)
+        case GL_WEIGHT_ARRAY_TYPE_OES:
+#endif // defined(GL_WEIGHT_ARRAY_TYPE_OES)
+            _needed = 1;
+            break;
+#if defined(GL_ALIASED_POINT_SIZE_RANGE)
+        case GL_ALIASED_POINT_SIZE_RANGE:
+#endif // defined(GL_ALIASED_POINT_SIZE_RANGE)
+#if defined(GL_ALIASED_LINE_WIDTH_RANGE)
+        case GL_ALIASED_LINE_WIDTH_RANGE:
+#endif // defined(GL_ALIASED_LINE_WIDTH_RANGE)
+#if defined(GL_DEPTH_RANGE)
+        case GL_DEPTH_RANGE:
+#endif // defined(GL_DEPTH_RANGE)
+#if defined(GL_MAX_VIEWPORT_DIMS)
+        case GL_MAX_VIEWPORT_DIMS:
+#endif // defined(GL_MAX_VIEWPORT_DIMS)
+#if defined(GL_SMOOTH_LINE_WIDTH_RANGE)
+        case GL_SMOOTH_LINE_WIDTH_RANGE:
+#endif // defined(GL_SMOOTH_LINE_WIDTH_RANGE)
+#if defined(GL_SMOOTH_POINT_SIZE_RANGE)
+        case GL_SMOOTH_POINT_SIZE_RANGE:
+#endif // defined(GL_SMOOTH_POINT_SIZE_RANGE)
+            _needed = 2;
+            break;
+#if defined(GL_COLOR_CLEAR_VALUE)
+        case GL_COLOR_CLEAR_VALUE:
+#endif // defined(GL_COLOR_CLEAR_VALUE)
+#if defined(GL_COLOR_WRITEMASK)
+        case GL_COLOR_WRITEMASK:
+#endif // defined(GL_COLOR_WRITEMASK)
+#if defined(GL_SCISSOR_BOX)
+        case GL_SCISSOR_BOX:
+#endif // defined(GL_SCISSOR_BOX)
+#if defined(GL_VIEWPORT)
+        case GL_VIEWPORT:
+#endif // defined(GL_VIEWPORT)
+            _needed = 4;
+            break;
+#if defined(GL_MODELVIEW_MATRIX)
+        case GL_MODELVIEW_MATRIX:
+#endif // defined(GL_MODELVIEW_MATRIX)
+#if defined(GL_MODELVIEW_MATRIX_FLOAT_AS_INT_BITS_OES)
+        case GL_MODELVIEW_MATRIX_FLOAT_AS_INT_BITS_OES:
+#endif // defined(GL_MODELVIEW_MATRIX_FLOAT_AS_INT_BITS_OES)
+#if defined(GL_PROJECTION_MATRIX)
+        case GL_PROJECTION_MATRIX:
+#endif // defined(GL_PROJECTION_MATRIX)
+#if defined(GL_PROJECTION_MATRIX_FLOAT_AS_INT_BITS_OES)
+        case GL_PROJECTION_MATRIX_FLOAT_AS_INT_BITS_OES:
+#endif // defined(GL_PROJECTION_MATRIX_FLOAT_AS_INT_BITS_OES)
+#if defined(GL_TEXTURE_MATRIX)
+        case GL_TEXTURE_MATRIX:
+#endif // defined(GL_TEXTURE_MATRIX)
+#if defined(GL_TEXTURE_MATRIX_FLOAT_AS_INT_BITS_OES)
+        case GL_TEXTURE_MATRIX_FLOAT_AS_INT_BITS_OES:
+#endif // defined(GL_TEXTURE_MATRIX_FLOAT_AS_INT_BITS_OES)
+            _needed = 16;
+            break;
+#if defined(GL_COMPRESSED_TEXTURE_FORMATS)
+        case GL_COMPRESSED_TEXTURE_FORMATS:
+#endif // defined(GL_COMPRESSED_TEXTURE_FORMATS)
+#if defined(GL_FOG_COLOR)
+        case GL_FOG_COLOR:
+#endif // defined(GL_FOG_COLOR)
+#if defined(GL_LIGHT_MODEL_AMBIENT)
+        case GL_LIGHT_MODEL_AMBIENT:
+#endif // defined(GL_LIGHT_MODEL_AMBIENT)
+            _needed = _NUM_COMPRESSED_TEXTURE_FORMATS;
+            break;
+        default:
+            _needed = 0;
+            break;
+    }
+    if (_remaining < _needed) {
+        _exception = 1;
+        _env->ThrowNew(IAEClass, "length - offset < needed");
+        goto exit;
+    }
+    params_base = (GLint *)
+        _env->GetPrimitiveArrayCritical(params_ref, (jboolean *)0);
+    params = params_base + offset;
+
+    glGetIntegerv(
+        (GLenum)pname,
+        (GLint *)params
+    );
+
+exit:
+    if (params_base) {
+        _env->ReleasePrimitiveArrayCritical(params_ref, params_base,
+            _exception ? JNI_ABORT: 0);
+    }
+}
+
+/* void glGetIntegerv ( GLenum pname, GLint *params ) */
+static void
+android_glGetIntegerv__ILjava_nio_IntBuffer_2
+  (JNIEnv *_env, jobject _this, jint pname, jobject params_buf) {
+    jint _exception = 0;
+    jarray _array = (jarray) 0;
+    jint _remaining;
+    GLint *params = (GLint *) 0;
+
+    params = (GLint *)getPointer(_env, params_buf, &_array, &_remaining);
+    int _needed;
+    switch (pname) {
+#if defined(GL_ALPHA_BITS)
+        case GL_ALPHA_BITS:
+#endif // defined(GL_ALPHA_BITS)
+#if defined(GL_ALPHA_TEST_FUNC)
+        case GL_ALPHA_TEST_FUNC:
+#endif // defined(GL_ALPHA_TEST_FUNC)
+#if defined(GL_ALPHA_TEST_REF)
+        case GL_ALPHA_TEST_REF:
+#endif // defined(GL_ALPHA_TEST_REF)
+#if defined(GL_BLEND_DST)
+        case GL_BLEND_DST:
+#endif // defined(GL_BLEND_DST)
+#if defined(GL_BLUE_BITS)
+        case GL_BLUE_BITS:
+#endif // defined(GL_BLUE_BITS)
+#if defined(GL_COLOR_ARRAY_BUFFER_BINDING)
+        case GL_COLOR_ARRAY_BUFFER_BINDING:
+#endif // defined(GL_COLOR_ARRAY_BUFFER_BINDING)
+#if defined(GL_COLOR_ARRAY_SIZE)
+        case GL_COLOR_ARRAY_SIZE:
+#endif // defined(GL_COLOR_ARRAY_SIZE)
+#if defined(GL_COLOR_ARRAY_STRIDE)
+        case GL_COLOR_ARRAY_STRIDE:
+#endif // defined(GL_COLOR_ARRAY_STRIDE)
+#if defined(GL_COLOR_ARRAY_TYPE)
+        case GL_COLOR_ARRAY_TYPE:
+#endif // defined(GL_COLOR_ARRAY_TYPE)
+#if defined(GL_CULL_FACE)
+        case GL_CULL_FACE:
+#endif // defined(GL_CULL_FACE)
+#if defined(GL_DEPTH_BITS)
+        case GL_DEPTH_BITS:
+#endif // defined(GL_DEPTH_BITS)
+#if defined(GL_DEPTH_CLEAR_VALUE)
+        case GL_DEPTH_CLEAR_VALUE:
+#endif // defined(GL_DEPTH_CLEAR_VALUE)
+#if defined(GL_DEPTH_FUNC)
+        case GL_DEPTH_FUNC:
+#endif // defined(GL_DEPTH_FUNC)
+#if defined(GL_DEPTH_WRITEMASK)
+        case GL_DEPTH_WRITEMASK:
+#endif // defined(GL_DEPTH_WRITEMASK)
+#if defined(GL_FOG_DENSITY)
+        case GL_FOG_DENSITY:
+#endif // defined(GL_FOG_DENSITY)
+#if defined(GL_FOG_END)
+        case GL_FOG_END:
+#endif // defined(GL_FOG_END)
+#if defined(GL_FOG_MODE)
+        case GL_FOG_MODE:
+#endif // defined(GL_FOG_MODE)
+#if defined(GL_FOG_START)
+        case GL_FOG_START:
+#endif // defined(GL_FOG_START)
+#if defined(GL_FRONT_FACE)
+        case GL_FRONT_FACE:
+#endif // defined(GL_FRONT_FACE)
+#if defined(GL_GREEN_BITS)
+        case GL_GREEN_BITS:
+#endif // defined(GL_GREEN_BITS)
+#if defined(GL_IMPLEMENTATION_COLOR_READ_FORMAT_OES)
+        case GL_IMPLEMENTATION_COLOR_READ_FORMAT_OES:
+#endif // defined(GL_IMPLEMENTATION_COLOR_READ_FORMAT_OES)
+#if defined(GL_IMPLEMENTATION_COLOR_READ_TYPE_OES)
+        case GL_IMPLEMENTATION_COLOR_READ_TYPE_OES:
+#endif // defined(GL_IMPLEMENTATION_COLOR_READ_TYPE_OES)
+#if defined(GL_LIGHT_MODEL_TWO_SIDE)
+        case GL_LIGHT_MODEL_TWO_SIDE:
+#endif // defined(GL_LIGHT_MODEL_TWO_SIDE)
+#if defined(GL_LINE_SMOOTH_HINT)
+        case GL_LINE_SMOOTH_HINT:
+#endif // defined(GL_LINE_SMOOTH_HINT)
+#if defined(GL_LINE_WIDTH)
+        case GL_LINE_WIDTH:
+#endif // defined(GL_LINE_WIDTH)
+#if defined(GL_LOGIC_OP_MODE)
+        case GL_LOGIC_OP_MODE:
+#endif // defined(GL_LOGIC_OP_MODE)
+#if defined(GL_MATRIX_INDEX_ARRAY_BUFFER_BINDING_OES)
+        case GL_MATRIX_INDEX_ARRAY_BUFFER_BINDING_OES:
+#endif // defined(GL_MATRIX_INDEX_ARRAY_BUFFER_BINDING_OES)
+#if defined(GL_MATRIX_INDEX_ARRAY_SIZE_OES)
+        case GL_MATRIX_INDEX_ARRAY_SIZE_OES:
+#endif // defined(GL_MATRIX_INDEX_ARRAY_SIZE_OES)
+#if defined(GL_MATRIX_INDEX_ARRAY_STRIDE_OES)
+        case GL_MATRIX_INDEX_ARRAY_STRIDE_OES:
+#endif // defined(GL_MATRIX_INDEX_ARRAY_STRIDE_OES)
+#if defined(GL_MATRIX_INDEX_ARRAY_TYPE_OES)
+        case GL_MATRIX_INDEX_ARRAY_TYPE_OES:
+#endif // defined(GL_MATRIX_INDEX_ARRAY_TYPE_OES)
+#if defined(GL_MATRIX_MODE)
+        case GL_MATRIX_MODE:
+#endif // defined(GL_MATRIX_MODE)
+#if defined(GL_MAX_CLIP_PLANES)
+        case GL_MAX_CLIP_PLANES:
+#endif // defined(GL_MAX_CLIP_PLANES)
+#if defined(GL_MAX_ELEMENTS_INDICES)
+        case GL_MAX_ELEMENTS_INDICES:
+#endif // defined(GL_MAX_ELEMENTS_INDICES)
+#if defined(GL_MAX_ELEMENTS_VERTICES)
+        case GL_MAX_ELEMENTS_VERTICES:
+#endif // defined(GL_MAX_ELEMENTS_VERTICES)
+#if defined(GL_MAX_LIGHTS)
+        case GL_MAX_LIGHTS:
+#endif // defined(GL_MAX_LIGHTS)
+#if defined(GL_MAX_MODELVIEW_STACK_DEPTH)
+        case GL_MAX_MODELVIEW_STACK_DEPTH:
+#endif // defined(GL_MAX_MODELVIEW_STACK_DEPTH)
+#if defined(GL_MAX_PALETTE_MATRICES_OES)
+        case GL_MAX_PALETTE_MATRICES_OES:
+#endif // defined(GL_MAX_PALETTE_MATRICES_OES)
+#if defined(GL_MAX_PROJECTION_STACK_DEPTH)
+        case GL_MAX_PROJECTION_STACK_DEPTH:
+#endif // defined(GL_MAX_PROJECTION_STACK_DEPTH)
+#if defined(GL_MAX_TEXTURE_SIZE)
+        case GL_MAX_TEXTURE_SIZE:
+#endif // defined(GL_MAX_TEXTURE_SIZE)
+#if defined(GL_MAX_TEXTURE_STACK_DEPTH)
+        case GL_MAX_TEXTURE_STACK_DEPTH:
+#endif // defined(GL_MAX_TEXTURE_STACK_DEPTH)
+#if defined(GL_MAX_TEXTURE_UNITS)
+        case GL_MAX_TEXTURE_UNITS:
+#endif // defined(GL_MAX_TEXTURE_UNITS)
+#if defined(GL_MAX_VERTEX_UNITS_OES)
+        case GL_MAX_VERTEX_UNITS_OES:
+#endif // defined(GL_MAX_VERTEX_UNITS_OES)
+#if defined(GL_MODELVIEW_STACK_DEPTH)
+        case GL_MODELVIEW_STACK_DEPTH:
+#endif // defined(GL_MODELVIEW_STACK_DEPTH)
+#if defined(GL_NORMAL_ARRAY_BUFFER_BINDING)
+        case GL_NORMAL_ARRAY_BUFFER_BINDING:
+#endif // defined(GL_NORMAL_ARRAY_BUFFER_BINDING)
+#if defined(GL_NORMAL_ARRAY_STRIDE)
+        case GL_NORMAL_ARRAY_STRIDE:
+#endif // defined(GL_NORMAL_ARRAY_STRIDE)
+#if defined(GL_NORMAL_ARRAY_TYPE)
+        case GL_NORMAL_ARRAY_TYPE:
+#endif // defined(GL_NORMAL_ARRAY_TYPE)
+#if defined(GL_NUM_COMPRESSED_TEXTURE_FORMATS)
+        case GL_NUM_COMPRESSED_TEXTURE_FORMATS:
+#endif // defined(GL_NUM_COMPRESSED_TEXTURE_FORMATS)
+#if defined(GL_PACK_ALIGNMENT)
+        case GL_PACK_ALIGNMENT:
+#endif // defined(GL_PACK_ALIGNMENT)
+#if defined(GL_PERSPECTIVE_CORRECTION_HINT)
+        case GL_PERSPECTIVE_CORRECTION_HINT:
+#endif // defined(GL_PERSPECTIVE_CORRECTION_HINT)
+#if defined(GL_POINT_SIZE)
+        case GL_POINT_SIZE:
+#endif // defined(GL_POINT_SIZE)
+#if defined(GL_POINT_SIZE_ARRAY_BUFFER_BINDING_OES)
+        case GL_POINT_SIZE_ARRAY_BUFFER_BINDING_OES:
+#endif // defined(GL_POINT_SIZE_ARRAY_BUFFER_BINDING_OES)
+#if defined(GL_POINT_SIZE_ARRAY_STRIDE_OES)
+        case GL_POINT_SIZE_ARRAY_STRIDE_OES:
+#endif // defined(GL_POINT_SIZE_ARRAY_STRIDE_OES)
+#if defined(GL_POINT_SIZE_ARRAY_TYPE_OES)
+        case GL_POINT_SIZE_ARRAY_TYPE_OES:
+#endif // defined(GL_POINT_SIZE_ARRAY_TYPE_OES)
+#if defined(GL_POINT_SMOOTH_HINT)
+        case GL_POINT_SMOOTH_HINT:
+#endif // defined(GL_POINT_SMOOTH_HINT)
+#if defined(GL_POLYGON_OFFSET_FACTOR)
+        case GL_POLYGON_OFFSET_FACTOR:
+#endif // defined(GL_POLYGON_OFFSET_FACTOR)
+#if defined(GL_POLYGON_OFFSET_UNITS)
+        case GL_POLYGON_OFFSET_UNITS:
+#endif // defined(GL_POLYGON_OFFSET_UNITS)
+#if defined(GL_PROJECTION_STACK_DEPTH)
+        case GL_PROJECTION_STACK_DEPTH:
+#endif // defined(GL_PROJECTION_STACK_DEPTH)
+#if defined(GL_RED_BITS)
+        case GL_RED_BITS:
+#endif // defined(GL_RED_BITS)
+#if defined(GL_SHADE_MODEL)
+        case GL_SHADE_MODEL:
+#endif // defined(GL_SHADE_MODEL)
+#if defined(GL_STENCIL_BITS)
+        case GL_STENCIL_BITS:
+#endif // defined(GL_STENCIL_BITS)
+#if defined(GL_STENCIL_CLEAR_VALUE)
+        case GL_STENCIL_CLEAR_VALUE:
+#endif // defined(GL_STENCIL_CLEAR_VALUE)
+#if defined(GL_STENCIL_FAIL)
+        case GL_STENCIL_FAIL:
+#endif // defined(GL_STENCIL_FAIL)
+#if defined(GL_STENCIL_FUNC)
+        case GL_STENCIL_FUNC:
+#endif // defined(GL_STENCIL_FUNC)
+#if defined(GL_STENCIL_PASS_DEPTH_FAIL)
+        case GL_STENCIL_PASS_DEPTH_FAIL:
+#endif // defined(GL_STENCIL_PASS_DEPTH_FAIL)
+#if defined(GL_STENCIL_PASS_DEPTH_PASS)
+        case GL_STENCIL_PASS_DEPTH_PASS:
+#endif // defined(GL_STENCIL_PASS_DEPTH_PASS)
+#if defined(GL_STENCIL_REF)
+        case GL_STENCIL_REF:
+#endif // defined(GL_STENCIL_REF)
+#if defined(GL_STENCIL_VALUE_MASK)
+        case GL_STENCIL_VALUE_MASK:
+#endif // defined(GL_STENCIL_VALUE_MASK)
+#if defined(GL_STENCIL_WRITEMASK)
+        case GL_STENCIL_WRITEMASK:
+#endif // defined(GL_STENCIL_WRITEMASK)
+#if defined(GL_SUBPIXEL_BITS)
+        case GL_SUBPIXEL_BITS:
+#endif // defined(GL_SUBPIXEL_BITS)
+#if defined(GL_TEXTURE_BINDING_2D)
+        case GL_TEXTURE_BINDING_2D:
+#endif // defined(GL_TEXTURE_BINDING_2D)
+#if defined(GL_TEXTURE_COORD_ARRAY_BUFFER_BINDING)
+        case GL_TEXTURE_COORD_ARRAY_BUFFER_BINDING:
+#endif // defined(GL_TEXTURE_COORD_ARRAY_BUFFER_BINDING)
+#if defined(GL_TEXTURE_COORD_ARRAY_SIZE)
+        case GL_TEXTURE_COORD_ARRAY_SIZE:
+#endif // defined(GL_TEXTURE_COORD_ARRAY_SIZE)
+#if defined(GL_TEXTURE_COORD_ARRAY_STRIDE)
+        case GL_TEXTURE_COORD_ARRAY_STRIDE:
+#endif // defined(GL_TEXTURE_COORD_ARRAY_STRIDE)
+#if defined(GL_TEXTURE_COORD_ARRAY_TYPE)
+        case GL_TEXTURE_COORD_ARRAY_TYPE:
+#endif // defined(GL_TEXTURE_COORD_ARRAY_TYPE)
+#if defined(GL_TEXTURE_STACK_DEPTH)
+        case GL_TEXTURE_STACK_DEPTH:
+#endif // defined(GL_TEXTURE_STACK_DEPTH)
+#if defined(GL_UNPACK_ALIGNMENT)
+        case GL_UNPACK_ALIGNMENT:
+#endif // defined(GL_UNPACK_ALIGNMENT)
+#if defined(GL_VERTEX_ARRAY_BUFFER_BINDING)
+        case GL_VERTEX_ARRAY_BUFFER_BINDING:
+#endif // defined(GL_VERTEX_ARRAY_BUFFER_BINDING)
+#if defined(GL_VERTEX_ARRAY_SIZE)
+        case GL_VERTEX_ARRAY_SIZE:
+#endif // defined(GL_VERTEX_ARRAY_SIZE)
+#if defined(GL_VERTEX_ARRAY_STRIDE)
+        case GL_VERTEX_ARRAY_STRIDE:
+#endif // defined(GL_VERTEX_ARRAY_STRIDE)
+#if defined(GL_VERTEX_ARRAY_TYPE)
+        case GL_VERTEX_ARRAY_TYPE:
+#endif // defined(GL_VERTEX_ARRAY_TYPE)
+#if defined(GL_WEIGHT_ARRAY_BUFFER_BINDING_OES)
+        case GL_WEIGHT_ARRAY_BUFFER_BINDING_OES:
+#endif // defined(GL_WEIGHT_ARRAY_BUFFER_BINDING_OES)
+#if defined(GL_WEIGHT_ARRAY_SIZE_OES)
+        case GL_WEIGHT_ARRAY_SIZE_OES:
+#endif // defined(GL_WEIGHT_ARRAY_SIZE_OES)
+#if defined(GL_WEIGHT_ARRAY_STRIDE_OES)
+        case GL_WEIGHT_ARRAY_STRIDE_OES:
+#endif // defined(GL_WEIGHT_ARRAY_STRIDE_OES)
+#if defined(GL_WEIGHT_ARRAY_TYPE_OES)
+        case GL_WEIGHT_ARRAY_TYPE_OES:
+#endif // defined(GL_WEIGHT_ARRAY_TYPE_OES)
+            _needed = 1;
+            break;
+#if defined(GL_ALIASED_POINT_SIZE_RANGE)
+        case GL_ALIASED_POINT_SIZE_RANGE:
+#endif // defined(GL_ALIASED_POINT_SIZE_RANGE)
+#if defined(GL_ALIASED_LINE_WIDTH_RANGE)
+        case GL_ALIASED_LINE_WIDTH_RANGE:
+#endif // defined(GL_ALIASED_LINE_WIDTH_RANGE)
+#if defined(GL_DEPTH_RANGE)
+        case GL_DEPTH_RANGE:
+#endif // defined(GL_DEPTH_RANGE)
+#if defined(GL_MAX_VIEWPORT_DIMS)
+        case GL_MAX_VIEWPORT_DIMS:
+#endif // defined(GL_MAX_VIEWPORT_DIMS)
+#if defined(GL_SMOOTH_LINE_WIDTH_RANGE)
+        case GL_SMOOTH_LINE_WIDTH_RANGE:
+#endif // defined(GL_SMOOTH_LINE_WIDTH_RANGE)
+#if defined(GL_SMOOTH_POINT_SIZE_RANGE)
+        case GL_SMOOTH_POINT_SIZE_RANGE:
+#endif // defined(GL_SMOOTH_POINT_SIZE_RANGE)
+            _needed = 2;
+            break;
+#if defined(GL_COLOR_CLEAR_VALUE)
+        case GL_COLOR_CLEAR_VALUE:
+#endif // defined(GL_COLOR_CLEAR_VALUE)
+#if defined(GL_COLOR_WRITEMASK)
+        case GL_COLOR_WRITEMASK:
+#endif // defined(GL_COLOR_WRITEMASK)
+#if defined(GL_SCISSOR_BOX)
+        case GL_SCISSOR_BOX:
+#endif // defined(GL_SCISSOR_BOX)
+#if defined(GL_VIEWPORT)
+        case GL_VIEWPORT:
+#endif // defined(GL_VIEWPORT)
+            _needed = 4;
+            break;
+#if defined(GL_MODELVIEW_MATRIX)
+        case GL_MODELVIEW_MATRIX:
+#endif // defined(GL_MODELVIEW_MATRIX)
+#if defined(GL_MODELVIEW_MATRIX_FLOAT_AS_INT_BITS_OES)
+        case GL_MODELVIEW_MATRIX_FLOAT_AS_INT_BITS_OES:
+#endif // defined(GL_MODELVIEW_MATRIX_FLOAT_AS_INT_BITS_OES)
+#if defined(GL_PROJECTION_MATRIX)
+        case GL_PROJECTION_MATRIX:
+#endif // defined(GL_PROJECTION_MATRIX)
+#if defined(GL_PROJECTION_MATRIX_FLOAT_AS_INT_BITS_OES)
+        case GL_PROJECTION_MATRIX_FLOAT_AS_INT_BITS_OES:
+#endif // defined(GL_PROJECTION_MATRIX_FLOAT_AS_INT_BITS_OES)
+#if defined(GL_TEXTURE_MATRIX)
+        case GL_TEXTURE_MATRIX:
+#endif // defined(GL_TEXTURE_MATRIX)
+#if defined(GL_TEXTURE_MATRIX_FLOAT_AS_INT_BITS_OES)
+        case GL_TEXTURE_MATRIX_FLOAT_AS_INT_BITS_OES:
+#endif // defined(GL_TEXTURE_MATRIX_FLOAT_AS_INT_BITS_OES)
+            _needed = 16;
+            break;
+#if defined(GL_COMPRESSED_TEXTURE_FORMATS)
+        case GL_COMPRESSED_TEXTURE_FORMATS:
+#endif // defined(GL_COMPRESSED_TEXTURE_FORMATS)
+#if defined(GL_FOG_COLOR)
+        case GL_FOG_COLOR:
+#endif // defined(GL_FOG_COLOR)
+#if defined(GL_LIGHT_MODEL_AMBIENT)
+        case GL_LIGHT_MODEL_AMBIENT:
+#endif // defined(GL_LIGHT_MODEL_AMBIENT)
+            _needed = _NUM_COMPRESSED_TEXTURE_FORMATS;
+            break;
+        default:
+            _needed = 0;
+            break;
+    }
+    if (_remaining < _needed) {
+        _exception = 1;
+        _env->ThrowNew(IAEClass, "remaining() < needed");
+        goto exit;
+    }
+    glGetIntegerv(
+        (GLenum)pname,
+        (GLint *)params
+    );
+
+exit:
+    if (_array) {
+        releasePointer(_env, _array, params, _exception ? JNI_FALSE : JNI_TRUE);
+    }
+}
+
+#include <string.h>
+
+/* const GLubyte * glGetString ( GLenum name ) */
+jstring
+android_glGetString
+  (JNIEnv *_env, jobject _this, jint name) {
+    const char * chars = (const char *)glGetString((GLenum)name);
+    jstring output = _env->NewStringUTF(chars);
+    return output;
+}
+/* void glHint ( GLenum target, GLenum mode ) */
+static void
+android_glHint__II
+  (JNIEnv *_env, jobject _this, jint target, jint mode) {
+    glHint(
+        (GLenum)target,
+        (GLenum)mode
+    );
+}
+
+/* void glLightModelf ( GLenum pname, GLfloat param ) */
+static void
+android_glLightModelf__IF
+  (JNIEnv *_env, jobject _this, jint pname, jfloat param) {
+    glLightModelf(
+        (GLenum)pname,
+        (GLfloat)param
+    );
+}
+
+/* void glLightModelfv ( GLenum pname, const GLfloat *params ) */
+static void
+android_glLightModelfv__I_3FI
+  (JNIEnv *_env, jobject _this, jint pname, jfloatArray params_ref, jint offset) {
+    GLfloat *params_base = (GLfloat *) 0;
+    jint _remaining;
+    GLfloat *params = (GLfloat *) 0;
+
+    if (!params_ref) {
+        _env->ThrowNew(IAEClass, "params == null");
+        goto exit;
+    }
+    if (offset < 0) {
+        _env->ThrowNew(IAEClass, "offset < 0");
+        goto exit;
+    }
+    _remaining = _env->GetArrayLength(params_ref) - offset;
+    int _needed;
+    switch (pname) {
+#if defined(GL_LIGHT_MODEL_TWO_SIDE)
+        case GL_LIGHT_MODEL_TWO_SIDE:
+#endif // defined(GL_LIGHT_MODEL_TWO_SIDE)
+            _needed = 1;
+            break;
+#if defined(GL_LIGHT_MODEL_AMBIENT)
+        case GL_LIGHT_MODEL_AMBIENT:
+#endif // defined(GL_LIGHT_MODEL_AMBIENT)
+            _needed = 4;
+            break;
+        default:
+            _needed = 0;
+            break;
+    }
+    if (_remaining < _needed) {
+        _env->ThrowNew(IAEClass, "length - offset < needed");
+        goto exit;
+    }
+    params_base = (GLfloat *)
+        _env->GetPrimitiveArrayCritical(params_ref, (jboolean *)0);
+    params = params_base + offset;
+
+    glLightModelfv(
+        (GLenum)pname,
+        (GLfloat *)params
+    );
+
+exit:
+    if (params_base) {
+        _env->ReleasePrimitiveArrayCritical(params_ref, params_base,
+            JNI_ABORT);
+    }
+}
+
+/* void glLightModelfv ( GLenum pname, const GLfloat *params ) */
+static void
+android_glLightModelfv__ILjava_nio_FloatBuffer_2
+  (JNIEnv *_env, jobject _this, jint pname, jobject params_buf) {
+    jarray _array = (jarray) 0;
+    jint _remaining;
+    GLfloat *params = (GLfloat *) 0;
+
+    params = (GLfloat *)getPointer(_env, params_buf, &_array, &_remaining);
+    int _needed;
+    switch (pname) {
+#if defined(GL_LIGHT_MODEL_TWO_SIDE)
+        case GL_LIGHT_MODEL_TWO_SIDE:
+#endif // defined(GL_LIGHT_MODEL_TWO_SIDE)
+            _needed = 1;
+            break;
+#if defined(GL_LIGHT_MODEL_AMBIENT)
+        case GL_LIGHT_MODEL_AMBIENT:
+#endif // defined(GL_LIGHT_MODEL_AMBIENT)
+            _needed = 4;
+            break;
+        default:
+            _needed = 0;
+            break;
+    }
+    if (_remaining < _needed) {
+        _env->ThrowNew(IAEClass, "remaining() < needed");
+        goto exit;
+    }
+    glLightModelfv(
+        (GLenum)pname,
+        (GLfloat *)params
+    );
+
+exit:
+    if (_array) {
+        releasePointer(_env, _array, params, JNI_FALSE);
+    }
+}
+
+/* void glLightModelx ( GLenum pname, GLfixed param ) */
+static void
+android_glLightModelx__II
+  (JNIEnv *_env, jobject _this, jint pname, jint param) {
+    glLightModelx(
+        (GLenum)pname,
+        (GLfixed)param
+    );
+}
+
+/* void glLightModelxv ( GLenum pname, const GLfixed *params ) */
+static void
+android_glLightModelxv__I_3II
+  (JNIEnv *_env, jobject _this, jint pname, jintArray params_ref, jint offset) {
+    GLfixed *params_base = (GLfixed *) 0;
+    jint _remaining;
+    GLfixed *params = (GLfixed *) 0;
+
+    if (!params_ref) {
+        _env->ThrowNew(IAEClass, "params == null");
+        goto exit;
+    }
+    if (offset < 0) {
+        _env->ThrowNew(IAEClass, "offset < 0");
+        goto exit;
+    }
+    _remaining = _env->GetArrayLength(params_ref) - offset;
+    int _needed;
+    switch (pname) {
+#if defined(GL_LIGHT_MODEL_TWO_SIDE)
+        case GL_LIGHT_MODEL_TWO_SIDE:
+#endif // defined(GL_LIGHT_MODEL_TWO_SIDE)
+            _needed = 1;
+            break;
+#if defined(GL_LIGHT_MODEL_AMBIENT)
+        case GL_LIGHT_MODEL_AMBIENT:
+#endif // defined(GL_LIGHT_MODEL_AMBIENT)
+            _needed = 4;
+            break;
+        default:
+            _needed = 0;
+            break;
+    }
+    if (_remaining < _needed) {
+        _env->ThrowNew(IAEClass, "length - offset < needed");
+        goto exit;
+    }
+    params_base = (GLfixed *)
+        _env->GetPrimitiveArrayCritical(params_ref, (jboolean *)0);
+    params = params_base + offset;
+
+    glLightModelxv(
+        (GLenum)pname,
+        (GLfixed *)params
+    );
+
+exit:
+    if (params_base) {
+        _env->ReleasePrimitiveArrayCritical(params_ref, params_base,
+            JNI_ABORT);
+    }
+}
+
+/* void glLightModelxv ( GLenum pname, const GLfixed *params ) */
+static void
+android_glLightModelxv__ILjava_nio_IntBuffer_2
+  (JNIEnv *_env, jobject _this, jint pname, jobject params_buf) {
+    jarray _array = (jarray) 0;
+    jint _remaining;
+    GLfixed *params = (GLfixed *) 0;
+
+    params = (GLfixed *)getPointer(_env, params_buf, &_array, &_remaining);
+    int _needed;
+    switch (pname) {
+#if defined(GL_LIGHT_MODEL_TWO_SIDE)
+        case GL_LIGHT_MODEL_TWO_SIDE:
+#endif // defined(GL_LIGHT_MODEL_TWO_SIDE)
+            _needed = 1;
+            break;
+#if defined(GL_LIGHT_MODEL_AMBIENT)
+        case GL_LIGHT_MODEL_AMBIENT:
+#endif // defined(GL_LIGHT_MODEL_AMBIENT)
+            _needed = 4;
+            break;
+        default:
+            _needed = 0;
+            break;
+    }
+    if (_remaining < _needed) {
+        _env->ThrowNew(IAEClass, "remaining() < needed");
+        goto exit;
+    }
+    glLightModelxv(
+        (GLenum)pname,
+        (GLfixed *)params
+    );
+
+exit:
+    if (_array) {
+        releasePointer(_env, _array, params, JNI_FALSE);
+    }
+}
+
+/* void glLightf ( GLenum light, GLenum pname, GLfloat param ) */
+static void
+android_glLightf__IIF
+  (JNIEnv *_env, jobject _this, jint light, jint pname, jfloat param) {
+    glLightf(
+        (GLenum)light,
+        (GLenum)pname,
+        (GLfloat)param
+    );
+}
+
+/* void glLightfv ( GLenum light, GLenum pname, const GLfloat *params ) */
+static void
+android_glLightfv__II_3FI
+  (JNIEnv *_env, jobject _this, jint light, jint pname, jfloatArray params_ref, jint offset) {
+    GLfloat *params_base = (GLfloat *) 0;
+    jint _remaining;
+    GLfloat *params = (GLfloat *) 0;
+
+    if (!params_ref) {
+        _env->ThrowNew(IAEClass, "params == null");
+        goto exit;
+    }
+    if (offset < 0) {
+        _env->ThrowNew(IAEClass, "offset < 0");
+        goto exit;
+    }
+    _remaining = _env->GetArrayLength(params_ref) - offset;
+    int _needed;
+    switch (pname) {
+#if defined(GL_SPOT_EXPONENT)
+        case GL_SPOT_EXPONENT:
+#endif // defined(GL_SPOT_EXPONENT)
+#if defined(GL_SPOT_CUTOFF)
+        case GL_SPOT_CUTOFF:
+#endif // defined(GL_SPOT_CUTOFF)
+#if defined(GL_CONSTANT_ATTENUATION)
+        case GL_CONSTANT_ATTENUATION:
+#endif // defined(GL_CONSTANT_ATTENUATION)
+#if defined(GL_LINEAR_ATTENUATION)
+        case GL_LINEAR_ATTENUATION:
+#endif // defined(GL_LINEAR_ATTENUATION)
+#if defined(GL_QUADRATIC_ATTENUATION)
+        case GL_QUADRATIC_ATTENUATION:
+#endif // defined(GL_QUADRATIC_ATTENUATION)
+            _needed = 1;
+            break;
+#if defined(GL_SPOT_DIRECTION)
+        case GL_SPOT_DIRECTION:
+#endif // defined(GL_SPOT_DIRECTION)
+            _needed = 3;
+            break;
+#if defined(GL_AMBIENT)
+        case GL_AMBIENT:
+#endif // defined(GL_AMBIENT)
+#if defined(GL_DIFFUSE)
+        case GL_DIFFUSE:
+#endif // defined(GL_DIFFUSE)
+#if defined(GL_SPECULAR)
+        case GL_SPECULAR:
+#endif // defined(GL_SPECULAR)
+#if defined(GL_EMISSION)
+        case GL_EMISSION:
+#endif // defined(GL_EMISSION)
+            _needed = 4;
+            break;
+        default:
+            _needed = 0;
+            break;
+    }
+    if (_remaining < _needed) {
+        _env->ThrowNew(IAEClass, "length - offset < needed");
+        goto exit;
+    }
+    params_base = (GLfloat *)
+        _env->GetPrimitiveArrayCritical(params_ref, (jboolean *)0);
+    params = params_base + offset;
+
+    glLightfv(
+        (GLenum)light,
+        (GLenum)pname,
+        (GLfloat *)params
+    );
+
+exit:
+    if (params_base) {
+        _env->ReleasePrimitiveArrayCritical(params_ref, params_base,
+            JNI_ABORT);
+    }
+}
+
+/* void glLightfv ( GLenum light, GLenum pname, const GLfloat *params ) */
+static void
+android_glLightfv__IILjava_nio_FloatBuffer_2
+  (JNIEnv *_env, jobject _this, jint light, jint pname, jobject params_buf) {
+    jarray _array = (jarray) 0;
+    jint _remaining;
+    GLfloat *params = (GLfloat *) 0;
+
+    params = (GLfloat *)getPointer(_env, params_buf, &_array, &_remaining);
+    int _needed;
+    switch (pname) {
+#if defined(GL_SPOT_EXPONENT)
+        case GL_SPOT_EXPONENT:
+#endif // defined(GL_SPOT_EXPONENT)
+#if defined(GL_SPOT_CUTOFF)
+        case GL_SPOT_CUTOFF:
+#endif // defined(GL_SPOT_CUTOFF)
+#if defined(GL_CONSTANT_ATTENUATION)
+        case GL_CONSTANT_ATTENUATION:
+#endif // defined(GL_CONSTANT_ATTENUATION)
+#if defined(GL_LINEAR_ATTENUATION)
+        case GL_LINEAR_ATTENUATION:
+#endif // defined(GL_LINEAR_ATTENUATION)
+#if defined(GL_QUADRATIC_ATTENUATION)
+        case GL_QUADRATIC_ATTENUATION:
+#endif // defined(GL_QUADRATIC_ATTENUATION)
+            _needed = 1;
+            break;
+#if defined(GL_SPOT_DIRECTION)
+        case GL_SPOT_DIRECTION:
+#endif // defined(GL_SPOT_DIRECTION)
+            _needed = 3;
+            break;
+#if defined(GL_AMBIENT)
+        case GL_AMBIENT:
+#endif // defined(GL_AMBIENT)
+#if defined(GL_DIFFUSE)
+        case GL_DIFFUSE:
+#endif // defined(GL_DIFFUSE)
+#if defined(GL_SPECULAR)
+        case GL_SPECULAR:
+#endif // defined(GL_SPECULAR)
+#if defined(GL_EMISSION)
+        case GL_EMISSION:
+#endif // defined(GL_EMISSION)
+            _needed = 4;
+            break;
+        default:
+            _needed = 0;
+            break;
+    }
+    if (_remaining < _needed) {
+        _env->ThrowNew(IAEClass, "remaining() < needed");
+        goto exit;
+    }
+    glLightfv(
+        (GLenum)light,
+        (GLenum)pname,
+        (GLfloat *)params
+    );
+
+exit:
+    if (_array) {
+        releasePointer(_env, _array, params, JNI_FALSE);
+    }
+}
+
+/* void glLightx ( GLenum light, GLenum pname, GLfixed param ) */
+static void
+android_glLightx__III
+  (JNIEnv *_env, jobject _this, jint light, jint pname, jint param) {
+    glLightx(
+        (GLenum)light,
+        (GLenum)pname,
+        (GLfixed)param
+    );
+}
+
+/* void glLightxv ( GLenum light, GLenum pname, const GLfixed *params ) */
+static void
+android_glLightxv__II_3II
+  (JNIEnv *_env, jobject _this, jint light, jint pname, jintArray params_ref, jint offset) {
+    GLfixed *params_base = (GLfixed *) 0;
+    jint _remaining;
+    GLfixed *params = (GLfixed *) 0;
+
+    if (!params_ref) {
+        _env->ThrowNew(IAEClass, "params == null");
+        goto exit;
+    }
+    if (offset < 0) {
+        _env->ThrowNew(IAEClass, "offset < 0");
+        goto exit;
+    }
+    _remaining = _env->GetArrayLength(params_ref) - offset;
+    int _needed;
+    switch (pname) {
+#if defined(GL_SPOT_EXPONENT)
+        case GL_SPOT_EXPONENT:
+#endif // defined(GL_SPOT_EXPONENT)
+#if defined(GL_SPOT_CUTOFF)
+        case GL_SPOT_CUTOFF:
+#endif // defined(GL_SPOT_CUTOFF)
+#if defined(GL_CONSTANT_ATTENUATION)
+        case GL_CONSTANT_ATTENUATION:
+#endif // defined(GL_CONSTANT_ATTENUATION)
+#if defined(GL_LINEAR_ATTENUATION)
+        case GL_LINEAR_ATTENUATION:
+#endif // defined(GL_LINEAR_ATTENUATION)
+#if defined(GL_QUADRATIC_ATTENUATION)
+        case GL_QUADRATIC_ATTENUATION:
+#endif // defined(GL_QUADRATIC_ATTENUATION)
+            _needed = 1;
+            break;
+#if defined(GL_SPOT_DIRECTION)
+        case GL_SPOT_DIRECTION:
+#endif // defined(GL_SPOT_DIRECTION)
+            _needed = 3;
+            break;
+#if defined(GL_AMBIENT)
+        case GL_AMBIENT:
+#endif // defined(GL_AMBIENT)
+#if defined(GL_DIFFUSE)
+        case GL_DIFFUSE:
+#endif // defined(GL_DIFFUSE)
+#if defined(GL_SPECULAR)
+        case GL_SPECULAR:
+#endif // defined(GL_SPECULAR)
+#if defined(GL_EMISSION)
+        case GL_EMISSION:
+#endif // defined(GL_EMISSION)
+            _needed = 4;
+            break;
+        default:
+            _needed = 0;
+            break;
+    }
+    if (_remaining < _needed) {
+        _env->ThrowNew(IAEClass, "length - offset < needed");
+        goto exit;
+    }
+    params_base = (GLfixed *)
+        _env->GetPrimitiveArrayCritical(params_ref, (jboolean *)0);
+    params = params_base + offset;
+
+    glLightxv(
+        (GLenum)light,
+        (GLenum)pname,
+        (GLfixed *)params
+    );
+
+exit:
+    if (params_base) {
+        _env->ReleasePrimitiveArrayCritical(params_ref, params_base,
+            JNI_ABORT);
+    }
+}
+
+/* void glLightxv ( GLenum light, GLenum pname, const GLfixed *params ) */
+static void
+android_glLightxv__IILjava_nio_IntBuffer_2
+  (JNIEnv *_env, jobject _this, jint light, jint pname, jobject params_buf) {
+    jarray _array = (jarray) 0;
+    jint _remaining;
+    GLfixed *params = (GLfixed *) 0;
+
+    params = (GLfixed *)getPointer(_env, params_buf, &_array, &_remaining);
+    int _needed;
+    switch (pname) {
+#if defined(GL_SPOT_EXPONENT)
+        case GL_SPOT_EXPONENT:
+#endif // defined(GL_SPOT_EXPONENT)
+#if defined(GL_SPOT_CUTOFF)
+        case GL_SPOT_CUTOFF:
+#endif // defined(GL_SPOT_CUTOFF)
+#if defined(GL_CONSTANT_ATTENUATION)
+        case GL_CONSTANT_ATTENUATION:
+#endif // defined(GL_CONSTANT_ATTENUATION)
+#if defined(GL_LINEAR_ATTENUATION)
+        case GL_LINEAR_ATTENUATION:
+#endif // defined(GL_LINEAR_ATTENUATION)
+#if defined(GL_QUADRATIC_ATTENUATION)
+        case GL_QUADRATIC_ATTENUATION:
+#endif // defined(GL_QUADRATIC_ATTENUATION)
+            _needed = 1;
+            break;
+#if defined(GL_SPOT_DIRECTION)
+        case GL_SPOT_DIRECTION:
+#endif // defined(GL_SPOT_DIRECTION)
+            _needed = 3;
+            break;
+#if defined(GL_AMBIENT)
+        case GL_AMBIENT:
+#endif // defined(GL_AMBIENT)
+#if defined(GL_DIFFUSE)
+        case GL_DIFFUSE:
+#endif // defined(GL_DIFFUSE)
+#if defined(GL_SPECULAR)
+        case GL_SPECULAR:
+#endif // defined(GL_SPECULAR)
+#if defined(GL_EMISSION)
+        case GL_EMISSION:
+#endif // defined(GL_EMISSION)
+            _needed = 4;
+            break;
+        default:
+            _needed = 0;
+            break;
+    }
+    if (_remaining < _needed) {
+        _env->ThrowNew(IAEClass, "remaining() < needed");
+        goto exit;
+    }
+    glLightxv(
+        (GLenum)light,
+        (GLenum)pname,
+        (GLfixed *)params
+    );
+
+exit:
+    if (_array) {
+        releasePointer(_env, _array, params, JNI_FALSE);
+    }
+}
+
+/* void glLineWidth ( GLfloat width ) */
+static void
+android_glLineWidth__F
+  (JNIEnv *_env, jobject _this, jfloat width) {
+    glLineWidth(
+        (GLfloat)width
+    );
+}
+
+/* void glLineWidthx ( GLfixed width ) */
+static void
+android_glLineWidthx__I
+  (JNIEnv *_env, jobject _this, jint width) {
+    glLineWidthx(
+        (GLfixed)width
+    );
+}
+
+/* void glLoadIdentity ( void ) */
+static void
+android_glLoadIdentity__
+  (JNIEnv *_env, jobject _this) {
+    glLoadIdentity();
+}
+
+/* void glLoadMatrixf ( const GLfloat *m ) */
+static void
+android_glLoadMatrixf___3FI
+  (JNIEnv *_env, jobject _this, jfloatArray m_ref, jint offset) {
+    GLfloat *m_base = (GLfloat *) 0;
+    jint _remaining;
+    GLfloat *m = (GLfloat *) 0;
+
+    if (!m_ref) {
+        _env->ThrowNew(IAEClass, "m == null");
+        goto exit;
+    }
+    if (offset < 0) {
+        _env->ThrowNew(IAEClass, "offset < 0");
+        goto exit;
+    }
+    _remaining = _env->GetArrayLength(m_ref) - offset;
+    m_base = (GLfloat *)
+        _env->GetPrimitiveArrayCritical(m_ref, (jboolean *)0);
+    m = m_base + offset;
+
+    glLoadMatrixf(
+        (GLfloat *)m
+    );
+
+exit:
+    if (m_base) {
+        _env->ReleasePrimitiveArrayCritical(m_ref, m_base,
+            JNI_ABORT);
+    }
+}
+
+/* void glLoadMatrixf ( const GLfloat *m ) */
+static void
+android_glLoadMatrixf__Ljava_nio_FloatBuffer_2
+  (JNIEnv *_env, jobject _this, jobject m_buf) {
+    jarray _array = (jarray) 0;
+    jint _remaining;
+    GLfloat *m = (GLfloat *) 0;
+
+    m = (GLfloat *)getPointer(_env, m_buf, &_array, &_remaining);
+    glLoadMatrixf(
+        (GLfloat *)m
+    );
+    if (_array) {
+        releasePointer(_env, _array, m, JNI_FALSE);
+    }
+}
+
+/* void glLoadMatrixx ( const GLfixed *m ) */
+static void
+android_glLoadMatrixx___3II
+  (JNIEnv *_env, jobject _this, jintArray m_ref, jint offset) {
+    GLfixed *m_base = (GLfixed *) 0;
+    jint _remaining;
+    GLfixed *m = (GLfixed *) 0;
+
+    if (!m_ref) {
+        _env->ThrowNew(IAEClass, "m == null");
+        goto exit;
+    }
+    if (offset < 0) {
+        _env->ThrowNew(IAEClass, "offset < 0");
+        goto exit;
+    }
+    _remaining = _env->GetArrayLength(m_ref) - offset;
+    m_base = (GLfixed *)
+        _env->GetPrimitiveArrayCritical(m_ref, (jboolean *)0);
+    m = m_base + offset;
+
+    glLoadMatrixx(
+        (GLfixed *)m
+    );
+
+exit:
+    if (m_base) {
+        _env->ReleasePrimitiveArrayCritical(m_ref, m_base,
+            JNI_ABORT);
+    }
+}
+
+/* void glLoadMatrixx ( const GLfixed *m ) */
+static void
+android_glLoadMatrixx__Ljava_nio_IntBuffer_2
+  (JNIEnv *_env, jobject _this, jobject m_buf) {
+    jarray _array = (jarray) 0;
+    jint _remaining;
+    GLfixed *m = (GLfixed *) 0;
+
+    m = (GLfixed *)getPointer(_env, m_buf, &_array, &_remaining);
+    glLoadMatrixx(
+        (GLfixed *)m
+    );
+    if (_array) {
+        releasePointer(_env, _array, m, JNI_FALSE);
+    }
+}
+
+/* void glLogicOp ( GLenum opcode ) */
+static void
+android_glLogicOp__I
+  (JNIEnv *_env, jobject _this, jint opcode) {
+    glLogicOp(
+        (GLenum)opcode
+    );
+}
+
+/* void glMaterialf ( GLenum face, GLenum pname, GLfloat param ) */
+static void
+android_glMaterialf__IIF
+  (JNIEnv *_env, jobject _this, jint face, jint pname, jfloat param) {
+    glMaterialf(
+        (GLenum)face,
+        (GLenum)pname,
+        (GLfloat)param
+    );
+}
+
+/* void glMaterialfv ( GLenum face, GLenum pname, const GLfloat *params ) */
+static void
+android_glMaterialfv__II_3FI
+  (JNIEnv *_env, jobject _this, jint face, jint pname, jfloatArray params_ref, jint offset) {
+    GLfloat *params_base = (GLfloat *) 0;
+    jint _remaining;
+    GLfloat *params = (GLfloat *) 0;
+
+    if (!params_ref) {
+        _env->ThrowNew(IAEClass, "params == null");
+        goto exit;
+    }
+    if (offset < 0) {
+        _env->ThrowNew(IAEClass, "offset < 0");
+        goto exit;
+    }
+    _remaining = _env->GetArrayLength(params_ref) - offset;
+    int _needed;
+    switch (pname) {
+#if defined(GL_SHININESS)
+        case GL_SHININESS:
+#endif // defined(GL_SHININESS)
+            _needed = 1;
+            break;
+#if defined(GL_AMBIENT)
+        case GL_AMBIENT:
+#endif // defined(GL_AMBIENT)
+#if defined(GL_DIFFUSE)
+        case GL_DIFFUSE:
+#endif // defined(GL_DIFFUSE)
+#if defined(GL_SPECULAR)
+        case GL_SPECULAR:
+#endif // defined(GL_SPECULAR)
+#if defined(GL_EMISSION)
+        case GL_EMISSION:
+#endif // defined(GL_EMISSION)
+#if defined(GL_AMBIENT_AND_DIFFUSE)
+        case GL_AMBIENT_AND_DIFFUSE:
+#endif // defined(GL_AMBIENT_AND_DIFFUSE)
+            _needed = 4;
+            break;
+        default:
+            _needed = 0;
+            break;
+    }
+    if (_remaining < _needed) {
+        _env->ThrowNew(IAEClass, "length - offset < needed");
+        goto exit;
+    }
+    params_base = (GLfloat *)
+        _env->GetPrimitiveArrayCritical(params_ref, (jboolean *)0);
+    params = params_base + offset;
+
+    glMaterialfv(
+        (GLenum)face,
+        (GLenum)pname,
+        (GLfloat *)params
+    );
+
+exit:
+    if (params_base) {
+        _env->ReleasePrimitiveArrayCritical(params_ref, params_base,
+            JNI_ABORT);
+    }
+}
+
+/* void glMaterialfv ( GLenum face, GLenum pname, const GLfloat *params ) */
+static void
+android_glMaterialfv__IILjava_nio_FloatBuffer_2
+  (JNIEnv *_env, jobject _this, jint face, jint pname, jobject params_buf) {
+    jarray _array = (jarray) 0;
+    jint _remaining;
+    GLfloat *params = (GLfloat *) 0;
+
+    params = (GLfloat *)getPointer(_env, params_buf, &_array, &_remaining);
+    int _needed;
+    switch (pname) {
+#if defined(GL_SHININESS)
+        case GL_SHININESS:
+#endif // defined(GL_SHININESS)
+            _needed = 1;
+            break;
+#if defined(GL_AMBIENT)
+        case GL_AMBIENT:
+#endif // defined(GL_AMBIENT)
+#if defined(GL_DIFFUSE)
+        case GL_DIFFUSE:
+#endif // defined(GL_DIFFUSE)
+#if defined(GL_SPECULAR)
+        case GL_SPECULAR:
+#endif // defined(GL_SPECULAR)
+#if defined(GL_EMISSION)
+        case GL_EMISSION:
+#endif // defined(GL_EMISSION)
+#if defined(GL_AMBIENT_AND_DIFFUSE)
+        case GL_AMBIENT_AND_DIFFUSE:
+#endif // defined(GL_AMBIENT_AND_DIFFUSE)
+            _needed = 4;
+            break;
+        default:
+            _needed = 0;
+            break;
+    }
+    if (_remaining < _needed) {
+        _env->ThrowNew(IAEClass, "remaining() < needed");
+        goto exit;
+    }
+    glMaterialfv(
+        (GLenum)face,
+        (GLenum)pname,
+        (GLfloat *)params
+    );
+
+exit:
+    if (_array) {
+        releasePointer(_env, _array, params, JNI_FALSE);
+    }
+}
+
+/* void glMaterialx ( GLenum face, GLenum pname, GLfixed param ) */
+static void
+android_glMaterialx__III
+  (JNIEnv *_env, jobject _this, jint face, jint pname, jint param) {
+    glMaterialx(
+        (GLenum)face,
+        (GLenum)pname,
+        (GLfixed)param
+    );
+}
+
+/* void glMaterialxv ( GLenum face, GLenum pname, const GLfixed *params ) */
+static void
+android_glMaterialxv__II_3II
+  (JNIEnv *_env, jobject _this, jint face, jint pname, jintArray params_ref, jint offset) {
+    GLfixed *params_base = (GLfixed *) 0;
+    jint _remaining;
+    GLfixed *params = (GLfixed *) 0;
+
+    if (!params_ref) {
+        _env->ThrowNew(IAEClass, "params == null");
+        goto exit;
+    }
+    if (offset < 0) {
+        _env->ThrowNew(IAEClass, "offset < 0");
+        goto exit;
+    }
+    _remaining = _env->GetArrayLength(params_ref) - offset;
+    int _needed;
+    switch (pname) {
+#if defined(GL_SHININESS)
+        case GL_SHININESS:
+#endif // defined(GL_SHININESS)
+            _needed = 1;
+            break;
+#if defined(GL_AMBIENT)
+        case GL_AMBIENT:
+#endif // defined(GL_AMBIENT)
+#if defined(GL_DIFFUSE)
+        case GL_DIFFUSE:
+#endif // defined(GL_DIFFUSE)
+#if defined(GL_SPECULAR)
+        case GL_SPECULAR:
+#endif // defined(GL_SPECULAR)
+#if defined(GL_EMISSION)
+        case GL_EMISSION:
+#endif // defined(GL_EMISSION)
+#if defined(GL_AMBIENT_AND_DIFFUSE)
+        case GL_AMBIENT_AND_DIFFUSE:
+#endif // defined(GL_AMBIENT_AND_DIFFUSE)
+            _needed = 4;
+            break;
+        default:
+            _needed = 0;
+            break;
+    }
+    if (_remaining < _needed) {
+        _env->ThrowNew(IAEClass, "length - offset < needed");
+        goto exit;
+    }
+    params_base = (GLfixed *)
+        _env->GetPrimitiveArrayCritical(params_ref, (jboolean *)0);
+    params = params_base + offset;
+
+    glMaterialxv(
+        (GLenum)face,
+        (GLenum)pname,
+        (GLfixed *)params
+    );
+
+exit:
+    if (params_base) {
+        _env->ReleasePrimitiveArrayCritical(params_ref, params_base,
+            JNI_ABORT);
+    }
+}
+
+/* void glMaterialxv ( GLenum face, GLenum pname, const GLfixed *params ) */
+static void
+android_glMaterialxv__IILjava_nio_IntBuffer_2
+  (JNIEnv *_env, jobject _this, jint face, jint pname, jobject params_buf) {
+    jarray _array = (jarray) 0;
+    jint _remaining;
+    GLfixed *params = (GLfixed *) 0;
+
+    params = (GLfixed *)getPointer(_env, params_buf, &_array, &_remaining);
+    int _needed;
+    switch (pname) {
+#if defined(GL_SHININESS)
+        case GL_SHININESS:
+#endif // defined(GL_SHININESS)
+            _needed = 1;
+            break;
+#if defined(GL_AMBIENT)
+        case GL_AMBIENT:
+#endif // defined(GL_AMBIENT)
+#if defined(GL_DIFFUSE)
+        case GL_DIFFUSE:
+#endif // defined(GL_DIFFUSE)
+#if defined(GL_SPECULAR)
+        case GL_SPECULAR:
+#endif // defined(GL_SPECULAR)
+#if defined(GL_EMISSION)
+        case GL_EMISSION:
+#endif // defined(GL_EMISSION)
+#if defined(GL_AMBIENT_AND_DIFFUSE)
+        case GL_AMBIENT_AND_DIFFUSE:
+#endif // defined(GL_AMBIENT_AND_DIFFUSE)
+            _needed = 4;
+            break;
+        default:
+            _needed = 0;
+            break;
+    }
+    if (_remaining < _needed) {
+        _env->ThrowNew(IAEClass, "remaining() < needed");
+        goto exit;
+    }
+    glMaterialxv(
+        (GLenum)face,
+        (GLenum)pname,
+        (GLfixed *)params
+    );
+
+exit:
+    if (_array) {
+        releasePointer(_env, _array, params, JNI_FALSE);
+    }
+}
+
+/* void glMatrixMode ( GLenum mode ) */
+static void
+android_glMatrixMode__I
+  (JNIEnv *_env, jobject _this, jint mode) {
+    glMatrixMode(
+        (GLenum)mode
+    );
+}
+
+/* void glMultMatrixf ( const GLfloat *m ) */
+static void
+android_glMultMatrixf___3FI
+  (JNIEnv *_env, jobject _this, jfloatArray m_ref, jint offset) {
+    GLfloat *m_base = (GLfloat *) 0;
+    jint _remaining;
+    GLfloat *m = (GLfloat *) 0;
+
+    if (!m_ref) {
+        _env->ThrowNew(IAEClass, "m == null");
+        goto exit;
+    }
+    if (offset < 0) {
+        _env->ThrowNew(IAEClass, "offset < 0");
+        goto exit;
+    }
+    _remaining = _env->GetArrayLength(m_ref) - offset;
+    m_base = (GLfloat *)
+        _env->GetPrimitiveArrayCritical(m_ref, (jboolean *)0);
+    m = m_base + offset;
+
+    glMultMatrixf(
+        (GLfloat *)m
+    );
+
+exit:
+    if (m_base) {
+        _env->ReleasePrimitiveArrayCritical(m_ref, m_base,
+            JNI_ABORT);
+    }
+}
+
+/* void glMultMatrixf ( const GLfloat *m ) */
+static void
+android_glMultMatrixf__Ljava_nio_FloatBuffer_2
+  (JNIEnv *_env, jobject _this, jobject m_buf) {
+    jarray _array = (jarray) 0;
+    jint _remaining;
+    GLfloat *m = (GLfloat *) 0;
+
+    m = (GLfloat *)getPointer(_env, m_buf, &_array, &_remaining);
+    glMultMatrixf(
+        (GLfloat *)m
+    );
+    if (_array) {
+        releasePointer(_env, _array, m, JNI_FALSE);
+    }
+}
+
+/* void glMultMatrixx ( const GLfixed *m ) */
+static void
+android_glMultMatrixx___3II
+  (JNIEnv *_env, jobject _this, jintArray m_ref, jint offset) {
+    GLfixed *m_base = (GLfixed *) 0;
+    jint _remaining;
+    GLfixed *m = (GLfixed *) 0;
+
+    if (!m_ref) {
+        _env->ThrowNew(IAEClass, "m == null");
+        goto exit;
+    }
+    if (offset < 0) {
+        _env->ThrowNew(IAEClass, "offset < 0");
+        goto exit;
+    }
+    _remaining = _env->GetArrayLength(m_ref) - offset;
+    m_base = (GLfixed *)
+        _env->GetPrimitiveArrayCritical(m_ref, (jboolean *)0);
+    m = m_base + offset;
+
+    glMultMatrixx(
+        (GLfixed *)m
+    );
+
+exit:
+    if (m_base) {
+        _env->ReleasePrimitiveArrayCritical(m_ref, m_base,
+            JNI_ABORT);
+    }
+}
+
+/* void glMultMatrixx ( const GLfixed *m ) */
+static void
+android_glMultMatrixx__Ljava_nio_IntBuffer_2
+  (JNIEnv *_env, jobject _this, jobject m_buf) {
+    jarray _array = (jarray) 0;
+    jint _remaining;
+    GLfixed *m = (GLfixed *) 0;
+
+    m = (GLfixed *)getPointer(_env, m_buf, &_array, &_remaining);
+    glMultMatrixx(
+        (GLfixed *)m
+    );
+    if (_array) {
+        releasePointer(_env, _array, m, JNI_FALSE);
+    }
+}
+
+/* void glMultiTexCoord4f ( GLenum target, GLfloat s, GLfloat t, GLfloat r, GLfloat q ) */
+static void
+android_glMultiTexCoord4f__IFFFF
+  (JNIEnv *_env, jobject _this, jint target, jfloat s, jfloat t, jfloat r, jfloat q) {
+    glMultiTexCoord4f(
+        (GLenum)target,
+        (GLfloat)s,
+        (GLfloat)t,
+        (GLfloat)r,
+        (GLfloat)q
+    );
+}
+
+/* void glMultiTexCoord4x ( GLenum target, GLfixed s, GLfixed t, GLfixed r, GLfixed q ) */
+static void
+android_glMultiTexCoord4x__IIIII
+  (JNIEnv *_env, jobject _this, jint target, jint s, jint t, jint r, jint q) {
+    glMultiTexCoord4x(
+        (GLenum)target,
+        (GLfixed)s,
+        (GLfixed)t,
+        (GLfixed)r,
+        (GLfixed)q
+    );
+}
+
+/* void glNormal3f ( GLfloat nx, GLfloat ny, GLfloat nz ) */
+static void
+android_glNormal3f__FFF
+  (JNIEnv *_env, jobject _this, jfloat nx, jfloat ny, jfloat nz) {
+    glNormal3f(
+        (GLfloat)nx,
+        (GLfloat)ny,
+        (GLfloat)nz
+    );
+}
+
+/* void glNormal3x ( GLfixed nx, GLfixed ny, GLfixed nz ) */
+static void
+android_glNormal3x__III
+  (JNIEnv *_env, jobject _this, jint nx, jint ny, jint nz) {
+    glNormal3x(
+        (GLfixed)nx,
+        (GLfixed)ny,
+        (GLfixed)nz
+    );
+}
+
+/* void glNormalPointer ( GLenum type, GLsizei stride, const GLvoid *pointer ) */
+static void
+android_glNormalPointerBounds__IILjava_nio_Buffer_2I
+  (JNIEnv *_env, jobject _this, jint type, jint stride, jobject pointer_buf, jint remaining) {
+    jarray _array = (jarray) 0;
+    jint _remaining;
+    GLvoid *pointer = (GLvoid *) 0;
+
+    pointer = (GLvoid *)getPointer(_env, pointer_buf, &_array, &_remaining);
+    glNormalPointerBounds(
+        (GLenum)type,
+        (GLsizei)stride,
+        (GLvoid *)pointer,
+        (GLsizei)remaining
+    );
+    if (_array) {
+        releasePointer(_env, _array, pointer, JNI_FALSE);
+    }
+}
+
+/* void glOrthof ( GLfloat left, GLfloat right, GLfloat bottom, GLfloat top, GLfloat zNear, GLfloat zFar ) */
+static void
+android_glOrthof__FFFFFF
+  (JNIEnv *_env, jobject _this, jfloat left, jfloat right, jfloat bottom, jfloat top, jfloat zNear, jfloat zFar) {
+    glOrthof(
+        (GLfloat)left,
+        (GLfloat)right,
+        (GLfloat)bottom,
+        (GLfloat)top,
+        (GLfloat)zNear,
+        (GLfloat)zFar
+    );
+}
+
+/* void glOrthox ( GLfixed left, GLfixed right, GLfixed bottom, GLfixed top, GLfixed zNear, GLfixed zFar ) */
+static void
+android_glOrthox__IIIIII
+  (JNIEnv *_env, jobject _this, jint left, jint right, jint bottom, jint top, jint zNear, jint zFar) {
+    glOrthox(
+        (GLfixed)left,
+        (GLfixed)right,
+        (GLfixed)bottom,
+        (GLfixed)top,
+        (GLfixed)zNear,
+        (GLfixed)zFar
+    );
+}
+
+/* void glPixelStorei ( GLenum pname, GLint param ) */
+static void
+android_glPixelStorei__II
+  (JNIEnv *_env, jobject _this, jint pname, jint param) {
+    glPixelStorei(
+        (GLenum)pname,
+        (GLint)param
+    );
+}
+
+/* void glPointSize ( GLfloat size ) */
+static void
+android_glPointSize__F
+  (JNIEnv *_env, jobject _this, jfloat size) {
+    glPointSize(
+        (GLfloat)size
+    );
+}
+
+/* void glPointSizex ( GLfixed size ) */
+static void
+android_glPointSizex__I
+  (JNIEnv *_env, jobject _this, jint size) {
+    glPointSizex(
+        (GLfixed)size
+    );
+}
+
+/* void glPolygonOffset ( GLfloat factor, GLfloat units ) */
+static void
+android_glPolygonOffset__FF
+  (JNIEnv *_env, jobject _this, jfloat factor, jfloat units) {
+    glPolygonOffset(
+        (GLfloat)factor,
+        (GLfloat)units
+    );
+}
+
+/* void glPolygonOffsetx ( GLfixed factor, GLfixed units ) */
+static void
+android_glPolygonOffsetx__II
+  (JNIEnv *_env, jobject _this, jint factor, jint units) {
+    glPolygonOffsetx(
+        (GLfixed)factor,
+        (GLfixed)units
+    );
+}
+
+/* void glPopMatrix ( void ) */
+static void
+android_glPopMatrix__
+  (JNIEnv *_env, jobject _this) {
+    glPopMatrix();
+}
+
+/* void glPushMatrix ( void ) */
+static void
+android_glPushMatrix__
+  (JNIEnv *_env, jobject _this) {
+    glPushMatrix();
+}
+
+/* void glReadPixels ( GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, GLvoid *pixels ) */
+static void
+android_glReadPixels__IIIIIILjava_nio_Buffer_2
+  (JNIEnv *_env, jobject _this, jint x, jint y, jint width, jint height, jint format, jint type, jobject pixels_buf) {
+    jint _exception = 0;
+    jarray _array = (jarray) 0;
+    jint _remaining;
+    GLvoid *pixels = (GLvoid *) 0;
+
+    pixels = (GLvoid *)getPointer(_env, pixels_buf, &_array, &_remaining);
+    glReadPixels(
+        (GLint)x,
+        (GLint)y,
+        (GLsizei)width,
+        (GLsizei)height,
+        (GLenum)format,
+        (GLenum)type,
+        (GLvoid *)pixels
+    );
+    if (_array) {
+        releasePointer(_env, _array, pixels, _exception ? JNI_FALSE : JNI_TRUE);
+    }
+}
+
+/* void glRotatef ( GLfloat angle, GLfloat x, GLfloat y, GLfloat z ) */
+static void
+android_glRotatef__FFFF
+  (JNIEnv *_env, jobject _this, jfloat angle, jfloat x, jfloat y, jfloat z) {
+    glRotatef(
+        (GLfloat)angle,
+        (GLfloat)x,
+        (GLfloat)y,
+        (GLfloat)z
+    );
+}
+
+/* void glRotatex ( GLfixed angle, GLfixed x, GLfixed y, GLfixed z ) */
+static void
+android_glRotatex__IIII
+  (JNIEnv *_env, jobject _this, jint angle, jint x, jint y, jint z) {
+    glRotatex(
+        (GLfixed)angle,
+        (GLfixed)x,
+        (GLfixed)y,
+        (GLfixed)z
+    );
+}
+
+/* void glSampleCoverage ( GLclampf value, GLboolean invert ) */
+static void
+android_glSampleCoverage__FZ
+  (JNIEnv *_env, jobject _this, jfloat value, jboolean invert) {
+    glSampleCoverage(
+        (GLclampf)value,
+        (GLboolean)invert
+    );
+}
+
+/* void glSampleCoveragex ( GLclampx value, GLboolean invert ) */
+static void
+android_glSampleCoveragex__IZ
+  (JNIEnv *_env, jobject _this, jint value, jboolean invert) {
+    glSampleCoveragex(
+        (GLclampx)value,
+        (GLboolean)invert
+    );
+}
+
+/* void glScalef ( GLfloat x, GLfloat y, GLfloat z ) */
+static void
+android_glScalef__FFF
+  (JNIEnv *_env, jobject _this, jfloat x, jfloat y, jfloat z) {
+    glScalef(
+        (GLfloat)x,
+        (GLfloat)y,
+        (GLfloat)z
+    );
+}
+
+/* void glScalex ( GLfixed x, GLfixed y, GLfixed z ) */
+static void
+android_glScalex__III
+  (JNIEnv *_env, jobject _this, jint x, jint y, jint z) {
+    glScalex(
+        (GLfixed)x,
+        (GLfixed)y,
+        (GLfixed)z
+    );
+}
+
+/* void glScissor ( GLint x, GLint y, GLsizei width, GLsizei height ) */
+static void
+android_glScissor__IIII
+  (JNIEnv *_env, jobject _this, jint x, jint y, jint width, jint height) {
+    glScissor(
+        (GLint)x,
+        (GLint)y,
+        (GLsizei)width,
+        (GLsizei)height
+    );
+}
+
+/* void glShadeModel ( GLenum mode ) */
+static void
+android_glShadeModel__I
+  (JNIEnv *_env, jobject _this, jint mode) {
+    glShadeModel(
+        (GLenum)mode
+    );
+}
+
+/* void glStencilFunc ( GLenum func, GLint ref, GLuint mask ) */
+static void
+android_glStencilFunc__III
+  (JNIEnv *_env, jobject _this, jint func, jint ref, jint mask) {
+    glStencilFunc(
+        (GLenum)func,
+        (GLint)ref,
+        (GLuint)mask
+    );
+}
+
+/* void glStencilMask ( GLuint mask ) */
+static void
+android_glStencilMask__I
+  (JNIEnv *_env, jobject _this, jint mask) {
+    glStencilMask(
+        (GLuint)mask
+    );
+}
+
+/* void glStencilOp ( GLenum fail, GLenum zfail, GLenum zpass ) */
+static void
+android_glStencilOp__III
+  (JNIEnv *_env, jobject _this, jint fail, jint zfail, jint zpass) {
+    glStencilOp(
+        (GLenum)fail,
+        (GLenum)zfail,
+        (GLenum)zpass
+    );
+}
+
+/* void glTexCoordPointer ( GLint size, GLenum type, GLsizei stride, const GLvoid *pointer ) */
+static void
+android_glTexCoordPointerBounds__IIILjava_nio_Buffer_2I
+  (JNIEnv *_env, jobject _this, jint size, jint type, jint stride, jobject pointer_buf, jint remaining) {
+    jarray _array = (jarray) 0;
+    jint _remaining;
+    GLvoid *pointer = (GLvoid *) 0;
+
+    pointer = (GLvoid *)getPointer(_env, pointer_buf, &_array, &_remaining);
+    glTexCoordPointerBounds(
+        (GLint)size,
+        (GLenum)type,
+        (GLsizei)stride,
+        (GLvoid *)pointer,
+        (GLsizei)remaining
+    );
+    if (_array) {
+        releasePointer(_env, _array, pointer, JNI_FALSE);
+    }
+}
+
+/* void glTexEnvf ( GLenum target, GLenum pname, GLfloat param ) */
+static void
+android_glTexEnvf__IIF
+  (JNIEnv *_env, jobject _this, jint target, jint pname, jfloat param) {
+    glTexEnvf(
+        (GLenum)target,
+        (GLenum)pname,
+        (GLfloat)param
+    );
+}
+
+/* void glTexEnvfv ( GLenum target, GLenum pname, const GLfloat *params ) */
+static void
+android_glTexEnvfv__II_3FI
+  (JNIEnv *_env, jobject _this, jint target, jint pname, jfloatArray params_ref, jint offset) {
+    GLfloat *params_base = (GLfloat *) 0;
+    jint _remaining;
+    GLfloat *params = (GLfloat *) 0;
+
+    if (!params_ref) {
+        _env->ThrowNew(IAEClass, "params == null");
+        goto exit;
+    }
+    if (offset < 0) {
+        _env->ThrowNew(IAEClass, "offset < 0");
+        goto exit;
+    }
+    _remaining = _env->GetArrayLength(params_ref) - offset;
+    int _needed;
+    switch (pname) {
+#if defined(GL_TEXTURE_ENV_MODE)
+        case GL_TEXTURE_ENV_MODE:
+#endif // defined(GL_TEXTURE_ENV_MODE)
+#if defined(GL_COMBINE_RGB)
+        case GL_COMBINE_RGB:
+#endif // defined(GL_COMBINE_RGB)
+#if defined(GL_COMBINE_ALPHA)
+        case GL_COMBINE_ALPHA:
+#endif // defined(GL_COMBINE_ALPHA)
+            _needed = 1;
+            break;
+#if defined(GL_TEXTURE_ENV_COLOR)
+        case GL_TEXTURE_ENV_COLOR:
+#endif // defined(GL_TEXTURE_ENV_COLOR)
+            _needed = 4;
+            break;
+        default:
+            _needed = 0;
+            break;
+    }
+    if (_remaining < _needed) {
+        _env->ThrowNew(IAEClass, "length - offset < needed");
+        goto exit;
+    }
+    params_base = (GLfloat *)
+        _env->GetPrimitiveArrayCritical(params_ref, (jboolean *)0);
+    params = params_base + offset;
+
+    glTexEnvfv(
+        (GLenum)target,
+        (GLenum)pname,
+        (GLfloat *)params
+    );
+
+exit:
+    if (params_base) {
+        _env->ReleasePrimitiveArrayCritical(params_ref, params_base,
+            JNI_ABORT);
+    }
+}
+
+/* void glTexEnvfv ( GLenum target, GLenum pname, const GLfloat *params ) */
+static void
+android_glTexEnvfv__IILjava_nio_FloatBuffer_2
+  (JNIEnv *_env, jobject _this, jint target, jint pname, jobject params_buf) {
+    jarray _array = (jarray) 0;
+    jint _remaining;
+    GLfloat *params = (GLfloat *) 0;
+
+    params = (GLfloat *)getPointer(_env, params_buf, &_array, &_remaining);
+    int _needed;
+    switch (pname) {
+#if defined(GL_TEXTURE_ENV_MODE)
+        case GL_TEXTURE_ENV_MODE:
+#endif // defined(GL_TEXTURE_ENV_MODE)
+#if defined(GL_COMBINE_RGB)
+        case GL_COMBINE_RGB:
+#endif // defined(GL_COMBINE_RGB)
+#if defined(GL_COMBINE_ALPHA)
+        case GL_COMBINE_ALPHA:
+#endif // defined(GL_COMBINE_ALPHA)
+            _needed = 1;
+            break;
+#if defined(GL_TEXTURE_ENV_COLOR)
+        case GL_TEXTURE_ENV_COLOR:
+#endif // defined(GL_TEXTURE_ENV_COLOR)
+            _needed = 4;
+            break;
+        default:
+            _needed = 0;
+            break;
+    }
+    if (_remaining < _needed) {
+        _env->ThrowNew(IAEClass, "remaining() < needed");
+        goto exit;
+    }
+    glTexEnvfv(
+        (GLenum)target,
+        (GLenum)pname,
+        (GLfloat *)params
+    );
+
+exit:
+    if (_array) {
+        releasePointer(_env, _array, params, JNI_FALSE);
+    }
+}
+
+/* void glTexEnvx ( GLenum target, GLenum pname, GLfixed param ) */
+static void
+android_glTexEnvx__III
+  (JNIEnv *_env, jobject _this, jint target, jint pname, jint param) {
+    glTexEnvx(
+        (GLenum)target,
+        (GLenum)pname,
+        (GLfixed)param
+    );
+}
+
+/* void glTexEnvxv ( GLenum target, GLenum pname, const GLfixed *params ) */
+static void
+android_glTexEnvxv__II_3II
+  (JNIEnv *_env, jobject _this, jint target, jint pname, jintArray params_ref, jint offset) {
+    GLfixed *params_base = (GLfixed *) 0;
+    jint _remaining;
+    GLfixed *params = (GLfixed *) 0;
+
+    if (!params_ref) {
+        _env->ThrowNew(IAEClass, "params == null");
+        goto exit;
+    }
+    if (offset < 0) {
+        _env->ThrowNew(IAEClass, "offset < 0");
+        goto exit;
+    }
+    _remaining = _env->GetArrayLength(params_ref) - offset;
+    int _needed;
+    switch (pname) {
+#if defined(GL_TEXTURE_ENV_MODE)
+        case GL_TEXTURE_ENV_MODE:
+#endif // defined(GL_TEXTURE_ENV_MODE)
+#if defined(GL_COMBINE_RGB)
+        case GL_COMBINE_RGB:
+#endif // defined(GL_COMBINE_RGB)
+#if defined(GL_COMBINE_ALPHA)
+        case GL_COMBINE_ALPHA:
+#endif // defined(GL_COMBINE_ALPHA)
+            _needed = 1;
+            break;
+#if defined(GL_TEXTURE_ENV_COLOR)
+        case GL_TEXTURE_ENV_COLOR:
+#endif // defined(GL_TEXTURE_ENV_COLOR)
+            _needed = 4;
+            break;
+        default:
+            _needed = 0;
+            break;
+    }
+    if (_remaining < _needed) {
+        _env->ThrowNew(IAEClass, "length - offset < needed");
+        goto exit;
+    }
+    params_base = (GLfixed *)
+        _env->GetPrimitiveArrayCritical(params_ref, (jboolean *)0);
+    params = params_base + offset;
+
+    glTexEnvxv(
+        (GLenum)target,
+        (GLenum)pname,
+        (GLfixed *)params
+    );
+
+exit:
+    if (params_base) {
+        _env->ReleasePrimitiveArrayCritical(params_ref, params_base,
+            JNI_ABORT);
+    }
+}
+
+/* void glTexEnvxv ( GLenum target, GLenum pname, const GLfixed *params ) */
+static void
+android_glTexEnvxv__IILjava_nio_IntBuffer_2
+  (JNIEnv *_env, jobject _this, jint target, jint pname, jobject params_buf) {
+    jarray _array = (jarray) 0;
+    jint _remaining;
+    GLfixed *params = (GLfixed *) 0;
+
+    params = (GLfixed *)getPointer(_env, params_buf, &_array, &_remaining);
+    int _needed;
+    switch (pname) {
+#if defined(GL_TEXTURE_ENV_MODE)
+        case GL_TEXTURE_ENV_MODE:
+#endif // defined(GL_TEXTURE_ENV_MODE)
+#if defined(GL_COMBINE_RGB)
+        case GL_COMBINE_RGB:
+#endif // defined(GL_COMBINE_RGB)
+#if defined(GL_COMBINE_ALPHA)
+        case GL_COMBINE_ALPHA:
+#endif // defined(GL_COMBINE_ALPHA)
+            _needed = 1;
+            break;
+#if defined(GL_TEXTURE_ENV_COLOR)
+        case GL_TEXTURE_ENV_COLOR:
+#endif // defined(GL_TEXTURE_ENV_COLOR)
+            _needed = 4;
+            break;
+        default:
+            _needed = 0;
+            break;
+    }
+    if (_remaining < _needed) {
+        _env->ThrowNew(IAEClass, "remaining() < needed");
+        goto exit;
+    }
+    glTexEnvxv(
+        (GLenum)target,
+        (GLenum)pname,
+        (GLfixed *)params
+    );
+
+exit:
+    if (_array) {
+        releasePointer(_env, _array, params, JNI_FALSE);
+    }
+}
+
+/* void glTexImage2D ( GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const GLvoid *pixels ) */
+static void
+android_glTexImage2D__IIIIIIIILjava_nio_Buffer_2
+  (JNIEnv *_env, jobject _this, jint target, jint level, jint internalformat, jint width, jint height, jint border, jint format, jint type, jobject pixels_buf) {
+    jarray _array = (jarray) 0;
+    jint _remaining;
+    GLvoid *pixels = (GLvoid *) 0;
+
+    if (pixels_buf) {
+        pixels = (GLvoid *)getPointer(_env, pixels_buf, &_array, &_remaining);
+    }
+    glTexImage2D(
+        (GLenum)target,
+        (GLint)level,
+        (GLint)internalformat,
+        (GLsizei)width,
+        (GLsizei)height,
+        (GLint)border,
+        (GLenum)format,
+        (GLenum)type,
+        (GLvoid *)pixels
+    );
+    if (_array) {
+        releasePointer(_env, _array, pixels, JNI_FALSE);
+    }
+}
+
+/* void glTexParameterf ( GLenum target, GLenum pname, GLfloat param ) */
+static void
+android_glTexParameterf__IIF
+  (JNIEnv *_env, jobject _this, jint target, jint pname, jfloat param) {
+    glTexParameterf(
+        (GLenum)target,
+        (GLenum)pname,
+        (GLfloat)param
+    );
+}
+
+/* void glTexParameterx ( GLenum target, GLenum pname, GLfixed param ) */
+static void
+android_glTexParameterx__III
+  (JNIEnv *_env, jobject _this, jint target, jint pname, jint param) {
+    glTexParameterx(
+        (GLenum)target,
+        (GLenum)pname,
+        (GLfixed)param
+    );
+}
+
+/* void glTexSubImage2D ( GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, const GLvoid *pixels ) */
+static void
+android_glTexSubImage2D__IIIIIIIILjava_nio_Buffer_2
+  (JNIEnv *_env, jobject _this, jint target, jint level, jint xoffset, jint yoffset, jint width, jint height, jint format, jint type, jobject pixels_buf) {
+    jarray _array = (jarray) 0;
+    jint _remaining;
+    GLvoid *pixels = (GLvoid *) 0;
+
+    if (pixels_buf) {
+        pixels = (GLvoid *)getPointer(_env, pixels_buf, &_array, &_remaining);
+    }
+    glTexSubImage2D(
+        (GLenum)target,
+        (GLint)level,
+        (GLint)xoffset,
+        (GLint)yoffset,
+        (GLsizei)width,
+        (GLsizei)height,
+        (GLenum)format,
+        (GLenum)type,
+        (GLvoid *)pixels
+    );
+    if (_array) {
+        releasePointer(_env, _array, pixels, JNI_FALSE);
+    }
+}
+
+/* void glTranslatef ( GLfloat x, GLfloat y, GLfloat z ) */
+static void
+android_glTranslatef__FFF
+  (JNIEnv *_env, jobject _this, jfloat x, jfloat y, jfloat z) {
+    glTranslatef(
+        (GLfloat)x,
+        (GLfloat)y,
+        (GLfloat)z
+    );
+}
+
+/* void glTranslatex ( GLfixed x, GLfixed y, GLfixed z ) */
+static void
+android_glTranslatex__III
+  (JNIEnv *_env, jobject _this, jint x, jint y, jint z) {
+    glTranslatex(
+        (GLfixed)x,
+        (GLfixed)y,
+        (GLfixed)z
+    );
+}
+
+/* void glVertexPointer ( GLint size, GLenum type, GLsizei stride, const GLvoid *pointer ) */
+static void
+android_glVertexPointerBounds__IIILjava_nio_Buffer_2I
+  (JNIEnv *_env, jobject _this, jint size, jint type, jint stride, jobject pointer_buf, jint remaining) {
+    jarray _array = (jarray) 0;
+    jint _remaining;
+    GLvoid *pointer = (GLvoid *) 0;
+
+    pointer = (GLvoid *)getPointer(_env, pointer_buf, &_array, &_remaining);
+    glVertexPointerBounds(
+        (GLint)size,
+        (GLenum)type,
+        (GLsizei)stride,
+        (GLvoid *)pointer,
+        (GLsizei)remaining
+    );
+    if (_array) {
+        releasePointer(_env, _array, pointer, JNI_FALSE);
+    }
+}
+
+/* void glViewport ( GLint x, GLint y, GLsizei width, GLsizei height ) */
+static void
+android_glViewport__IIII
+  (JNIEnv *_env, jobject _this, jint x, jint y, jint width, jint height) {
+    glViewport(
+        (GLint)x,
+        (GLint)y,
+        (GLsizei)width,
+        (GLsizei)height
+    );
+}
+
+/* GLbitfield glQueryMatrixxOES ( GLfixed *mantissa, GLint *exponent ) */
+static jint
+android_glQueryMatrixxOES___3II_3II
+  (JNIEnv *_env, jobject _this, jintArray mantissa_ref, jint mantissaOffset, jintArray exponent_ref, jint exponentOffset) {
+    jint _exception = 0;
+    GLbitfield _returnValue = -1;
+    GLfixed *mantissa_base = (GLfixed *) 0;
+    jint _mantissaRemaining;
+    GLfixed *mantissa = (GLfixed *) 0;
+    GLint *exponent_base = (GLint *) 0;
+    jint _exponentRemaining;
+    GLint *exponent = (GLint *) 0;
+
+    if (!mantissa_ref) {
+        _exception = 1;
+        _env->ThrowNew(IAEClass, "mantissa == null");
+        goto exit;
+    }
+    if (mantissaOffset < 0) {
+        _exception = 1;
+        _env->ThrowNew(IAEClass, "mantissaOffset < 0");
+        goto exit;
+    }
+    _mantissaRemaining = _env->GetArrayLength(mantissa_ref) - mantissaOffset;
+    if (_mantissaRemaining < 16) {
+        _exception = 1;
+        _env->ThrowNew(IAEClass, "length - mantissaOffset < 16");
+        goto exit;
+    }
+    mantissa_base = (GLfixed *)
+        _env->GetPrimitiveArrayCritical(mantissa_ref, (jboolean *)0);
+    mantissa = mantissa_base + mantissaOffset;
+
+    if (!exponent_ref) {
+        _exception = 1;
+        _env->ThrowNew(IAEClass, "exponent == null");
+        goto exit;
+    }
+    if (exponentOffset < 0) {
+        _exception = 1;
+        _env->ThrowNew(IAEClass, "exponentOffset < 0");
+        goto exit;
+    }
+    _exponentRemaining = _env->GetArrayLength(exponent_ref) - exponentOffset;
+    if (_exponentRemaining < 16) {
+        _exception = 1;
+        _env->ThrowNew(IAEClass, "length - exponentOffset < 16");
+        goto exit;
+    }
+    exponent_base = (GLint *)
+        _env->GetPrimitiveArrayCritical(exponent_ref, (jboolean *)0);
+    exponent = exponent_base + exponentOffset;
+
+    _returnValue = glQueryMatrixxOES(
+        (GLfixed *)mantissa,
+        (GLint *)exponent
+    );
+
+exit:
+    if (exponent_base) {
+        _env->ReleasePrimitiveArrayCritical(exponent_ref, exponent_base,
+            _exception ? JNI_ABORT: 0);
+    }
+    if (mantissa_base) {
+        _env->ReleasePrimitiveArrayCritical(mantissa_ref, mantissa_base,
+            _exception ? JNI_ABORT: 0);
+    }
+    return _returnValue;
+}
+
+/* GLbitfield glQueryMatrixxOES ( GLfixed *mantissa, GLint *exponent ) */
+static jint
+android_glQueryMatrixxOES__Ljava_nio_IntBuffer_2Ljava_nio_IntBuffer_2
+  (JNIEnv *_env, jobject _this, jobject mantissa_buf, jobject exponent_buf) {
+    jint _exception = 0;
+    jarray _mantissaArray = (jarray) 0;
+    jarray _exponentArray = (jarray) 0;
+    GLbitfield _returnValue = -1;
+    jint _mantissaRemaining;
+    GLfixed *mantissa = (GLfixed *) 0;
+    jint _exponentRemaining;
+    GLint *exponent = (GLint *) 0;
+
+    mantissa = (GLfixed *)getPointer(_env, mantissa_buf, &_mantissaArray, &_mantissaRemaining);
+    if (_mantissaRemaining < 16) {
+        _exception = 1;
+        _env->ThrowNew(IAEClass, "remaining() < 16");
+        goto exit;
+    }
+    exponent = (GLint *)getPointer(_env, exponent_buf, &_exponentArray, &_exponentRemaining);
+    if (_exponentRemaining < 16) {
+        _exception = 1;
+        _env->ThrowNew(IAEClass, "remaining() < 16");
+        goto exit;
+    }
+    _returnValue = glQueryMatrixxOES(
+        (GLfixed *)mantissa,
+        (GLint *)exponent
+    );
+
+exit:
+    if (_mantissaArray) {
+        releasePointer(_env, _mantissaArray, exponent, _exception ? JNI_FALSE : JNI_TRUE);
+    }
+    if (_exponentArray) {
+        releasePointer(_env, _exponentArray, mantissa, _exception ? JNI_FALSE : JNI_TRUE);
+    }
+    return _returnValue;
+}
+
+/* void glBindBuffer ( GLenum target, GLuint buffer ) */
+static void
+android_glBindBuffer__II
+  (JNIEnv *_env, jobject _this, jint target, jint buffer) {
+    glBindBuffer(
+        (GLenum)target,
+        (GLuint)buffer
+    );
+}
+
+/* void glBufferData ( GLenum target, GLsizeiptr size, const GLvoid *data, GLenum usage ) */
+static void
+android_glBufferData__IILjava_nio_Buffer_2I
+  (JNIEnv *_env, jobject _this, jint target, jint size, jobject data_buf, jint usage) {
+    jarray _array = (jarray) 0;
+    jint _remaining;
+    GLvoid *data = (GLvoid *) 0;
+
+    if (data_buf) {
+        data = (GLvoid *)getPointer(_env, data_buf, &_array, &_remaining);
+    }
+    glBufferData(
+        (GLenum)target,
+        (GLsizeiptr)size,
+        (GLvoid *)data,
+        (GLenum)usage
+    );
+    if (_array) {
+        releasePointer(_env, _array, data, JNI_FALSE);
+    }
+}
+
+/* void glBufferSubData ( GLenum target, GLintptr offset, GLsizeiptr size, const GLvoid *data ) */
+static void
+android_glBufferSubData__IIILjava_nio_Buffer_2
+  (JNIEnv *_env, jobject _this, jint target, jint offset, jint size, jobject data_buf) {
+    jarray _array = (jarray) 0;
+    jint _remaining;
+    GLvoid *data = (GLvoid *) 0;
+
+    data = (GLvoid *)getPointer(_env, data_buf, &_array, &_remaining);
+    glBufferSubData(
+        (GLenum)target,
+        (GLintptr)offset,
+        (GLsizeiptr)size,
+        (GLvoid *)data
+    );
+    if (_array) {
+        releasePointer(_env, _array, data, JNI_FALSE);
+    }
+}
+
+/* void glClipPlanef ( GLenum plane, const GLfloat *equation ) */
+static void
+android_glClipPlanef__I_3FI
+  (JNIEnv *_env, jobject _this, jint plane, jfloatArray equation_ref, jint offset) {
+    GLfloat *equation_base = (GLfloat *) 0;
+    jint _remaining;
+    GLfloat *equation = (GLfloat *) 0;
+
+    if (!equation_ref) {
+        _env->ThrowNew(IAEClass, "equation == null");
+        goto exit;
+    }
+    if (offset < 0) {
+        _env->ThrowNew(IAEClass, "offset < 0");
+        goto exit;
+    }
+    _remaining = _env->GetArrayLength(equation_ref) - offset;
+    if (_remaining < 4) {
+        _env->ThrowNew(IAEClass, "length - offset < 4");
+        goto exit;
+    }
+    equation_base = (GLfloat *)
+        _env->GetPrimitiveArrayCritical(equation_ref, (jboolean *)0);
+    equation = equation_base + offset;
+
+    glClipPlanef(
+        (GLenum)plane,
+        (GLfloat *)equation
+    );
+
+exit:
+    if (equation_base) {
+        _env->ReleasePrimitiveArrayCritical(equation_ref, equation_base,
+            JNI_ABORT);
+    }
+}
+
+/* void glClipPlanef ( GLenum plane, const GLfloat *equation ) */
+static void
+android_glClipPlanef__ILjava_nio_FloatBuffer_2
+  (JNIEnv *_env, jobject _this, jint plane, jobject equation_buf) {
+    jarray _array = (jarray) 0;
+    jint _remaining;
+    GLfloat *equation = (GLfloat *) 0;
+
+    equation = (GLfloat *)getPointer(_env, equation_buf, &_array, &_remaining);
+    if (_remaining < 4) {
+        _env->ThrowNew(IAEClass, "remaining() < 4");
+        goto exit;
+    }
+    glClipPlanef(
+        (GLenum)plane,
+        (GLfloat *)equation
+    );
+
+exit:
+    if (_array) {
+        releasePointer(_env, _array, equation, JNI_FALSE);
+    }
+}
+
+/* void glClipPlanex ( GLenum plane, const GLfixed *equation ) */
+static void
+android_glClipPlanex__I_3II
+  (JNIEnv *_env, jobject _this, jint plane, jintArray equation_ref, jint offset) {
+    GLfixed *equation_base = (GLfixed *) 0;
+    jint _remaining;
+    GLfixed *equation = (GLfixed *) 0;
+
+    if (!equation_ref) {
+        _env->ThrowNew(IAEClass, "equation == null");
+        goto exit;
+    }
+    if (offset < 0) {
+        _env->ThrowNew(IAEClass, "offset < 0");
+        goto exit;
+    }
+    _remaining = _env->GetArrayLength(equation_ref) - offset;
+    if (_remaining < 4) {
+        _env->ThrowNew(IAEClass, "length - offset < 4");
+        goto exit;
+    }
+    equation_base = (GLfixed *)
+        _env->GetPrimitiveArrayCritical(equation_ref, (jboolean *)0);
+    equation = equation_base + offset;
+
+    glClipPlanex(
+        (GLenum)plane,
+        (GLfixed *)equation
+    );
+
+exit:
+    if (equation_base) {
+        _env->ReleasePrimitiveArrayCritical(equation_ref, equation_base,
+            JNI_ABORT);
+    }
+}
+
+/* void glClipPlanex ( GLenum plane, const GLfixed *equation ) */
+static void
+android_glClipPlanex__ILjava_nio_IntBuffer_2
+  (JNIEnv *_env, jobject _this, jint plane, jobject equation_buf) {
+    jarray _array = (jarray) 0;
+    jint _remaining;
+    GLfixed *equation = (GLfixed *) 0;
+
+    equation = (GLfixed *)getPointer(_env, equation_buf, &_array, &_remaining);
+    if (_remaining < 4) {
+        _env->ThrowNew(IAEClass, "remaining() < 4");
+        goto exit;
+    }
+    glClipPlanex(
+        (GLenum)plane,
+        (GLfixed *)equation
+    );
+
+exit:
+    if (_array) {
+        releasePointer(_env, _array, equation, JNI_FALSE);
+    }
+}
+
+/* void glColor4ub ( GLubyte red, GLubyte green, GLubyte blue, GLubyte alpha ) */
+static void
+android_glColor4ub__BBBB
+  (JNIEnv *_env, jobject _this, jbyte red, jbyte green, jbyte blue, jbyte alpha) {
+    glColor4ub(
+        (GLubyte)red,
+        (GLubyte)green,
+        (GLubyte)blue,
+        (GLubyte)alpha
+    );
+}
+
+/* void glColorPointer ( GLint size, GLenum type, GLsizei stride, GLint offset ) */
+static void
+android_glColorPointer__IIII
+  (JNIEnv *_env, jobject _this, jint size, jint type, jint stride, jint offset) {
+    glColorPointer(
+        (GLint)size,
+        (GLenum)type,
+        (GLsizei)stride,
+        (const GLvoid *)offset
+    );
+}
+
+/* void glDeleteBuffers ( GLsizei n, const GLuint *buffers ) */
+static void
+android_glDeleteBuffers__I_3II
+  (JNIEnv *_env, jobject _this, jint n, jintArray buffers_ref, jint offset) {
+    GLuint *buffers_base = (GLuint *) 0;
+    jint _remaining;
+    GLuint *buffers = (GLuint *) 0;
+
+    if (!buffers_ref) {
+        _env->ThrowNew(IAEClass, "buffers == null");
+        goto exit;
+    }
+    if (offset < 0) {
+        _env->ThrowNew(IAEClass, "offset < 0");
+        goto exit;
+    }
+    _remaining = _env->GetArrayLength(buffers_ref) - offset;
+    if (_remaining < n) {
+        _env->ThrowNew(IAEClass, "length - offset < n");
+        goto exit;
+    }
+    buffers_base = (GLuint *)
+        _env->GetPrimitiveArrayCritical(buffers_ref, (jboolean *)0);
+    buffers = buffers_base + offset;
+
+    glDeleteBuffers(
+        (GLsizei)n,
+        (GLuint *)buffers
+    );
+
+exit:
+    if (buffers_base) {
+        _env->ReleasePrimitiveArrayCritical(buffers_ref, buffers_base,
+            JNI_ABORT);
+    }
+}
+
+/* void glDeleteBuffers ( GLsizei n, const GLuint *buffers ) */
+static void
+android_glDeleteBuffers__ILjava_nio_IntBuffer_2
+  (JNIEnv *_env, jobject _this, jint n, jobject buffers_buf) {
+    jarray _array = (jarray) 0;
+    jint _remaining;
+    GLuint *buffers = (GLuint *) 0;
+
+    buffers = (GLuint *)getPointer(_env, buffers_buf, &_array, &_remaining);
+    if (_remaining < n) {
+        _env->ThrowNew(IAEClass, "remaining() < n");
+        goto exit;
+    }
+    glDeleteBuffers(
+        (GLsizei)n,
+        (GLuint *)buffers
+    );
+
+exit:
+    if (_array) {
+        releasePointer(_env, _array, buffers, JNI_FALSE);
+    }
+}
+
+/* void glDrawElements ( GLenum mode, GLsizei count, GLenum type, GLint offset ) */
+static void
+android_glDrawElements__IIII
+  (JNIEnv *_env, jobject _this, jint mode, jint count, jint type, jint offset) {
+    glDrawElements(
+        (GLenum)mode,
+        (GLsizei)count,
+        (GLenum)type,
+        (const GLvoid *)offset
+    );
+}
+
+/* void glGenBuffers ( GLsizei n, GLuint *buffers ) */
+static void
+android_glGenBuffers__I_3II
+  (JNIEnv *_env, jobject _this, jint n, jintArray buffers_ref, jint offset) {
+    jint _exception = 0;
+    GLuint *buffers_base = (GLuint *) 0;
+    jint _remaining;
+    GLuint *buffers = (GLuint *) 0;
+
+    if (!buffers_ref) {
+        _exception = 1;
+        _env->ThrowNew(IAEClass, "buffers == null");
+        goto exit;
+    }
+    if (offset < 0) {
+        _exception = 1;
+        _env->ThrowNew(IAEClass, "offset < 0");
+        goto exit;
+    }
+    _remaining = _env->GetArrayLength(buffers_ref) - offset;
+    if (_remaining < n) {
+        _exception = 1;
+        _env->ThrowNew(IAEClass, "length - offset < n");
+        goto exit;
+    }
+    buffers_base = (GLuint *)
+        _env->GetPrimitiveArrayCritical(buffers_ref, (jboolean *)0);
+    buffers = buffers_base + offset;
+
+    glGenBuffers(
+        (GLsizei)n,
+        (GLuint *)buffers
+    );
+
+exit:
+    if (buffers_base) {
+        _env->ReleasePrimitiveArrayCritical(buffers_ref, buffers_base,
+            _exception ? JNI_ABORT: 0);
+    }
+}
+
+/* void glGenBuffers ( GLsizei n, GLuint *buffers ) */
+static void
+android_glGenBuffers__ILjava_nio_IntBuffer_2
+  (JNIEnv *_env, jobject _this, jint n, jobject buffers_buf) {
+    jint _exception = 0;
+    jarray _array = (jarray) 0;
+    jint _remaining;
+    GLuint *buffers = (GLuint *) 0;
+
+    buffers = (GLuint *)getPointer(_env, buffers_buf, &_array, &_remaining);
+    if (_remaining < n) {
+        _exception = 1;
+        _env->ThrowNew(IAEClass, "remaining() < n");
+        goto exit;
+    }
+    glGenBuffers(
+        (GLsizei)n,
+        (GLuint *)buffers
+    );
+
+exit:
+    if (_array) {
+        releasePointer(_env, _array, buffers, _exception ? JNI_FALSE : JNI_TRUE);
+    }
+}
+
+/* void glGetBooleanv ( GLenum pname, GLboolean *params ) */
+static void
+android_glGetBooleanv__I_3ZI
+  (JNIEnv *_env, jobject _this, jint pname, jbooleanArray params_ref, jint offset) {
+    jint _exception = 0;
+    GLboolean *params_base = (GLboolean *) 0;
+    jint _remaining;
+    GLboolean *params = (GLboolean *) 0;
+
+    if (!params_ref) {
+        _exception = 1;
+        _env->ThrowNew(IAEClass, "params == null");
+        goto exit;
+    }
+    if (offset < 0) {
+        _exception = 1;
+        _env->ThrowNew(IAEClass, "offset < 0");
+        goto exit;
+    }
+    _remaining = _env->GetArrayLength(params_ref) - offset;
+    params_base = (GLboolean *)
+        _env->GetPrimitiveArrayCritical(params_ref, (jboolean *)0);
+    params = params_base + offset;
+
+    glGetBooleanv(
+        (GLenum)pname,
+        (GLboolean *)params
+    );
+
+exit:
+    if (params_base) {
+        _env->ReleasePrimitiveArrayCritical(params_ref, params_base,
+            _exception ? JNI_ABORT: 0);
+    }
+}
+
+/* void glGetBooleanv ( GLenum pname, GLboolean *params ) */
+static void
+android_glGetBooleanv__ILjava_nio_IntBuffer_2
+  (JNIEnv *_env, jobject _this, jint pname, jobject params_buf) {
+    jint _exception = 0;
+    jarray _array = (jarray) 0;
+    jint _remaining;
+    GLboolean *params = (GLboolean *) 0;
+
+    params = (GLboolean *)getPointer(_env, params_buf, &_array, &_remaining);
+    glGetBooleanv(
+        (GLenum)pname,
+        (GLboolean *)params
+    );
+    if (_array) {
+        releasePointer(_env, _array, params, _exception ? JNI_FALSE : JNI_TRUE);
+    }
+}
+
+/* void glGetBufferParameteriv ( GLenum target, GLenum pname, GLint *params ) */
+static void
+android_glGetBufferParameteriv__II_3II
+  (JNIEnv *_env, jobject _this, jint target, jint pname, jintArray params_ref, jint offset) {
+    _env->ThrowNew(UOEClass,
+        "glGetBufferParameteriv");
+}
+
+/* void glGetBufferParameteriv ( GLenum target, GLenum pname, GLint *params ) */
+static void
+android_glGetBufferParameteriv__IILjava_nio_IntBuffer_2
+  (JNIEnv *_env, jobject _this, jint target, jint pname, jobject params_buf) {
+    _env->ThrowNew(UOEClass,
+        "glGetBufferParameteriv");
+}
+
+/* void glGetClipPlanef ( GLenum pname, GLfloat *eqn ) */
+static void
+android_glGetClipPlanef__I_3FI
+  (JNIEnv *_env, jobject _this, jint pname, jfloatArray eqn_ref, jint offset) {
+    jint _exception = 0;
+    GLfloat *eqn_base = (GLfloat *) 0;
+    jint _remaining;
+    GLfloat *eqn = (GLfloat *) 0;
+
+    if (!eqn_ref) {
+        _exception = 1;
+        _env->ThrowNew(IAEClass, "eqn == null");
+        goto exit;
+    }
+    if (offset < 0) {
+        _exception = 1;
+        _env->ThrowNew(IAEClass, "offset < 0");
+        goto exit;
+    }
+    _remaining = _env->GetArrayLength(eqn_ref) - offset;
+    eqn_base = (GLfloat *)
+        _env->GetPrimitiveArrayCritical(eqn_ref, (jboolean *)0);
+    eqn = eqn_base + offset;
+
+    glGetClipPlanef(
+        (GLenum)pname,
+        (GLfloat *)eqn
+    );
+
+exit:
+    if (eqn_base) {
+        _env->ReleasePrimitiveArrayCritical(eqn_ref, eqn_base,
+            _exception ? JNI_ABORT: 0);
+    }
+}
+
+/* void glGetClipPlanef ( GLenum pname, GLfloat *eqn ) */
+static void
+android_glGetClipPlanef__ILjava_nio_FloatBuffer_2
+  (JNIEnv *_env, jobject _this, jint pname, jobject eqn_buf) {
+    jint _exception = 0;
+    jarray _array = (jarray) 0;
+    jint _remaining;
+    GLfloat *eqn = (GLfloat *) 0;
+
+    eqn = (GLfloat *)getPointer(_env, eqn_buf, &_array, &_remaining);
+    glGetClipPlanef(
+        (GLenum)pname,
+        (GLfloat *)eqn
+    );
+    if (_array) {
+        releasePointer(_env, _array, eqn, _exception ? JNI_FALSE : JNI_TRUE);
+    }
+}
+
+/* void glGetClipPlanex ( GLenum pname, GLfixed *eqn ) */
+static void
+android_glGetClipPlanex__I_3II
+  (JNIEnv *_env, jobject _this, jint pname, jintArray eqn_ref, jint offset) {
+    jint _exception = 0;
+    GLfixed *eqn_base = (GLfixed *) 0;
+    jint _remaining;
+    GLfixed *eqn = (GLfixed *) 0;
+
+    if (!eqn_ref) {
+        _exception = 1;
+        _env->ThrowNew(IAEClass, "eqn == null");
+        goto exit;
+    }
+    if (offset < 0) {
+        _exception = 1;
+        _env->ThrowNew(IAEClass, "offset < 0");
+        goto exit;
+    }
+    _remaining = _env->GetArrayLength(eqn_ref) - offset;
+    eqn_base = (GLfixed *)
+        _env->GetPrimitiveArrayCritical(eqn_ref, (jboolean *)0);
+    eqn = eqn_base + offset;
+
+    glGetClipPlanex(
+        (GLenum)pname,
+        (GLfixed *)eqn
+    );
+
+exit:
+    if (eqn_base) {
+        _env->ReleasePrimitiveArrayCritical(eqn_ref, eqn_base,
+            _exception ? JNI_ABORT: 0);
+    }
+}
+
+/* void glGetClipPlanex ( GLenum pname, GLfixed *eqn ) */
+static void
+android_glGetClipPlanex__ILjava_nio_IntBuffer_2
+  (JNIEnv *_env, jobject _this, jint pname, jobject eqn_buf) {
+    jint _exception = 0;
+    jarray _array = (jarray) 0;
+    jint _remaining;
+    GLfixed *eqn = (GLfixed *) 0;
+
+    eqn = (GLfixed *)getPointer(_env, eqn_buf, &_array, &_remaining);
+    glGetClipPlanex(
+        (GLenum)pname,
+        (GLfixed *)eqn
+    );
+    if (_array) {
+        releasePointer(_env, _array, eqn, _exception ? JNI_FALSE : JNI_TRUE);
+    }
+}
+
+/* void glGetFixedv ( GLenum pname, GLfixed *params ) */
+static void
+android_glGetFixedv__I_3II
+  (JNIEnv *_env, jobject _this, jint pname, jintArray params_ref, jint offset) {
+    jint _exception = 0;
+    GLfixed *params_base = (GLfixed *) 0;
+    jint _remaining;
+    GLfixed *params = (GLfixed *) 0;
+
+    if (!params_ref) {
+        _exception = 1;
+        _env->ThrowNew(IAEClass, "params == null");
+        goto exit;
+    }
+    if (offset < 0) {
+        _exception = 1;
+        _env->ThrowNew(IAEClass, "offset < 0");
+        goto exit;
+    }
+    _remaining = _env->GetArrayLength(params_ref) - offset;
+    params_base = (GLfixed *)
+        _env->GetPrimitiveArrayCritical(params_ref, (jboolean *)0);
+    params = params_base + offset;
+
+    glGetFixedv(
+        (GLenum)pname,
+        (GLfixed *)params
+    );
+
+exit:
+    if (params_base) {
+        _env->ReleasePrimitiveArrayCritical(params_ref, params_base,
+            _exception ? JNI_ABORT: 0);
+    }
+}
+
+/* void glGetFixedv ( GLenum pname, GLfixed *params ) */
+static void
+android_glGetFixedv__ILjava_nio_IntBuffer_2
+  (JNIEnv *_env, jobject _this, jint pname, jobject params_buf) {
+    jint _exception = 0;
+    jarray _array = (jarray) 0;
+    jint _remaining;
+    GLfixed *params = (GLfixed *) 0;
+
+    params = (GLfixed *)getPointer(_env, params_buf, &_array, &_remaining);
+    glGetFixedv(
+        (GLenum)pname,
+        (GLfixed *)params
+    );
+    if (_array) {
+        releasePointer(_env, _array, params, _exception ? JNI_FALSE : JNI_TRUE);
+    }
+}
+
+/* void glGetFloatv ( GLenum pname, GLfloat *params ) */
+static void
+android_glGetFloatv__I_3FI
+  (JNIEnv *_env, jobject _this, jint pname, jfloatArray params_ref, jint offset) {
+    jint _exception = 0;
+    GLfloat *params_base = (GLfloat *) 0;
+    jint _remaining;
+    GLfloat *params = (GLfloat *) 0;
+
+    if (!params_ref) {
+        _exception = 1;
+        _env->ThrowNew(IAEClass, "params == null");
+        goto exit;
+    }
+    if (offset < 0) {
+        _exception = 1;
+        _env->ThrowNew(IAEClass, "offset < 0");
+        goto exit;
+    }
+    _remaining = _env->GetArrayLength(params_ref) - offset;
+    params_base = (GLfloat *)
+        _env->GetPrimitiveArrayCritical(params_ref, (jboolean *)0);
+    params = params_base + offset;
+
+    glGetFloatv(
+        (GLenum)pname,
+        (GLfloat *)params
+    );
+
+exit:
+    if (params_base) {
+        _env->ReleasePrimitiveArrayCritical(params_ref, params_base,
+            _exception ? JNI_ABORT: 0);
+    }
+}
+
+/* void glGetFloatv ( GLenum pname, GLfloat *params ) */
+static void
+android_glGetFloatv__ILjava_nio_FloatBuffer_2
+  (JNIEnv *_env, jobject _this, jint pname, jobject params_buf) {
+    jint _exception = 0;
+    jarray _array = (jarray) 0;
+    jint _remaining;
+    GLfloat *params = (GLfloat *) 0;
+
+    params = (GLfloat *)getPointer(_env, params_buf, &_array, &_remaining);
+    glGetFloatv(
+        (GLenum)pname,
+        (GLfloat *)params
+    );
+    if (_array) {
+        releasePointer(_env, _array, params, _exception ? JNI_FALSE : JNI_TRUE);
+    }
+}
+
+/* void glGetLightfv ( GLenum light, GLenum pname, GLfloat *params ) */
+static void
+android_glGetLightfv__II_3FI
+  (JNIEnv *_env, jobject _this, jint light, jint pname, jfloatArray params_ref, jint offset) {
+    jint _exception = 0;
+    GLfloat *params_base = (GLfloat *) 0;
+    jint _remaining;
+    GLfloat *params = (GLfloat *) 0;
+
+    if (!params_ref) {
+        _exception = 1;
+        _env->ThrowNew(IAEClass, "params == null");
+        goto exit;
+    }
+    if (offset < 0) {
+        _exception = 1;
+        _env->ThrowNew(IAEClass, "offset < 0");
+        goto exit;
+    }
+    _remaining = _env->GetArrayLength(params_ref) - offset;
+    int _needed;
+    switch (pname) {
+#if defined(GL_SPOT_EXPONENT)
+        case GL_SPOT_EXPONENT:
+#endif // defined(GL_SPOT_EXPONENT)
+#if defined(GL_SPOT_CUTOFF)
+        case GL_SPOT_CUTOFF:
+#endif // defined(GL_SPOT_CUTOFF)
+#if defined(GL_CONSTANT_ATTENUATION)
+        case GL_CONSTANT_ATTENUATION:
+#endif // defined(GL_CONSTANT_ATTENUATION)
+#if defined(GL_LINEAR_ATTENUATION)
+        case GL_LINEAR_ATTENUATION:
+#endif // defined(GL_LINEAR_ATTENUATION)
+#if defined(GL_QUADRATIC_ATTENUATION)
+        case GL_QUADRATIC_ATTENUATION:
+#endif // defined(GL_QUADRATIC_ATTENUATION)
+            _needed = 1;
+            break;
+#if defined(GL_SPOT_DIRECTION)
+        case GL_SPOT_DIRECTION:
+#endif // defined(GL_SPOT_DIRECTION)
+            _needed = 3;
+            break;
+#if defined(GL_AMBIENT)
+        case GL_AMBIENT:
+#endif // defined(GL_AMBIENT)
+#if defined(GL_DIFFUSE)
+        case GL_DIFFUSE:
+#endif // defined(GL_DIFFUSE)
+#if defined(GL_SPECULAR)
+        case GL_SPECULAR:
+#endif // defined(GL_SPECULAR)
+#if defined(GL_EMISSION)
+        case GL_EMISSION:
+#endif // defined(GL_EMISSION)
+            _needed = 4;
+            break;
+        default:
+            _needed = 0;
+            break;
+    }
+    if (_remaining < _needed) {
+        _exception = 1;
+        _env->ThrowNew(IAEClass, "length - offset < needed");
+        goto exit;
+    }
+    params_base = (GLfloat *)
+        _env->GetPrimitiveArrayCritical(params_ref, (jboolean *)0);
+    params = params_base + offset;
+
+    glGetLightfv(
+        (GLenum)light,
+        (GLenum)pname,
+        (GLfloat *)params
+    );
+
+exit:
+    if (params_base) {
+        _env->ReleasePrimitiveArrayCritical(params_ref, params_base,
+            _exception ? JNI_ABORT: 0);
+    }
+}
+
+/* void glGetLightfv ( GLenum light, GLenum pname, GLfloat *params ) */
+static void
+android_glGetLightfv__IILjava_nio_FloatBuffer_2
+  (JNIEnv *_env, jobject _this, jint light, jint pname, jobject params_buf) {
+    jint _exception = 0;
+    jarray _array = (jarray) 0;
+    jint _remaining;
+    GLfloat *params = (GLfloat *) 0;
+
+    params = (GLfloat *)getPointer(_env, params_buf, &_array, &_remaining);
+    int _needed;
+    switch (pname) {
+#if defined(GL_SPOT_EXPONENT)
+        case GL_SPOT_EXPONENT:
+#endif // defined(GL_SPOT_EXPONENT)
+#if defined(GL_SPOT_CUTOFF)
+        case GL_SPOT_CUTOFF:
+#endif // defined(GL_SPOT_CUTOFF)
+#if defined(GL_CONSTANT_ATTENUATION)
+        case GL_CONSTANT_ATTENUATION:
+#endif // defined(GL_CONSTANT_ATTENUATION)
+#if defined(GL_LINEAR_ATTENUATION)
+        case GL_LINEAR_ATTENUATION:
+#endif // defined(GL_LINEAR_ATTENUATION)
+#if defined(GL_QUADRATIC_ATTENUATION)
+        case GL_QUADRATIC_ATTENUATION:
+#endif // defined(GL_QUADRATIC_ATTENUATION)
+            _needed = 1;
+            break;
+#if defined(GL_SPOT_DIRECTION)
+        case GL_SPOT_DIRECTION:
+#endif // defined(GL_SPOT_DIRECTION)
+            _needed = 3;
+            break;
+#if defined(GL_AMBIENT)
+        case GL_AMBIENT:
+#endif // defined(GL_AMBIENT)
+#if defined(GL_DIFFUSE)
+        case GL_DIFFUSE:
+#endif // defined(GL_DIFFUSE)
+#if defined(GL_SPECULAR)
+        case GL_SPECULAR:
+#endif // defined(GL_SPECULAR)
+#if defined(GL_EMISSION)
+        case GL_EMISSION:
+#endif // defined(GL_EMISSION)
+            _needed = 4;
+            break;
+        default:
+            _needed = 0;
+            break;
+    }
+    if (_remaining < _needed) {
+        _exception = 1;
+        _env->ThrowNew(IAEClass, "remaining() < needed");
+        goto exit;
+    }
+    glGetLightfv(
+        (GLenum)light,
+        (GLenum)pname,
+        (GLfloat *)params
+    );
+
+exit:
+    if (_array) {
+        releasePointer(_env, _array, params, _exception ? JNI_FALSE : JNI_TRUE);
+    }
+}
+
+/* void glGetLightxv ( GLenum light, GLenum pname, GLfixed *params ) */
+static void
+android_glGetLightxv__II_3II
+  (JNIEnv *_env, jobject _this, jint light, jint pname, jintArray params_ref, jint offset) {
+    jint _exception = 0;
+    GLfixed *params_base = (GLfixed *) 0;
+    jint _remaining;
+    GLfixed *params = (GLfixed *) 0;
+
+    if (!params_ref) {
+        _exception = 1;
+        _env->ThrowNew(IAEClass, "params == null");
+        goto exit;
+    }
+    if (offset < 0) {
+        _exception = 1;
+        _env->ThrowNew(IAEClass, "offset < 0");
+        goto exit;
+    }
+    _remaining = _env->GetArrayLength(params_ref) - offset;
+    int _needed;
+    switch (pname) {
+#if defined(GL_SPOT_EXPONENT)
+        case GL_SPOT_EXPONENT:
+#endif // defined(GL_SPOT_EXPONENT)
+#if defined(GL_SPOT_CUTOFF)
+        case GL_SPOT_CUTOFF:
+#endif // defined(GL_SPOT_CUTOFF)
+#if defined(GL_CONSTANT_ATTENUATION)
+        case GL_CONSTANT_ATTENUATION:
+#endif // defined(GL_CONSTANT_ATTENUATION)
+#if defined(GL_LINEAR_ATTENUATION)
+        case GL_LINEAR_ATTENUATION:
+#endif // defined(GL_LINEAR_ATTENUATION)
+#if defined(GL_QUADRATIC_ATTENUATION)
+        case GL_QUADRATIC_ATTENUATION:
+#endif // defined(GL_QUADRATIC_ATTENUATION)
+            _needed = 1;
+            break;
+#if defined(GL_SPOT_DIRECTION)
+        case GL_SPOT_DIRECTION:
+#endif // defined(GL_SPOT_DIRECTION)
+            _needed = 3;
+            break;
+#if defined(GL_AMBIENT)
+        case GL_AMBIENT:
+#endif // defined(GL_AMBIENT)
+#if defined(GL_DIFFUSE)
+        case GL_DIFFUSE:
+#endif // defined(GL_DIFFUSE)
+#if defined(GL_SPECULAR)
+        case GL_SPECULAR:
+#endif // defined(GL_SPECULAR)
+#if defined(GL_EMISSION)
+        case GL_EMISSION:
+#endif // defined(GL_EMISSION)
+            _needed = 4;
+            break;
+        default:
+            _needed = 0;
+            break;
+    }
+    if (_remaining < _needed) {
+        _exception = 1;
+        _env->ThrowNew(IAEClass, "length - offset < needed");
+        goto exit;
+    }
+    params_base = (GLfixed *)
+        _env->GetPrimitiveArrayCritical(params_ref, (jboolean *)0);
+    params = params_base + offset;
+
+    glGetLightxv(
+        (GLenum)light,
+        (GLenum)pname,
+        (GLfixed *)params
+    );
+
+exit:
+    if (params_base) {
+        _env->ReleasePrimitiveArrayCritical(params_ref, params_base,
+            _exception ? JNI_ABORT: 0);
+    }
+}
+
+/* void glGetLightxv ( GLenum light, GLenum pname, GLfixed *params ) */
+static void
+android_glGetLightxv__IILjava_nio_IntBuffer_2
+  (JNIEnv *_env, jobject _this, jint light, jint pname, jobject params_buf) {
+    jint _exception = 0;
+    jarray _array = (jarray) 0;
+    jint _remaining;
+    GLfixed *params = (GLfixed *) 0;
+
+    params = (GLfixed *)getPointer(_env, params_buf, &_array, &_remaining);
+    int _needed;
+    switch (pname) {
+#if defined(GL_SPOT_EXPONENT)
+        case GL_SPOT_EXPONENT:
+#endif // defined(GL_SPOT_EXPONENT)
+#if defined(GL_SPOT_CUTOFF)
+        case GL_SPOT_CUTOFF:
+#endif // defined(GL_SPOT_CUTOFF)
+#if defined(GL_CONSTANT_ATTENUATION)
+        case GL_CONSTANT_ATTENUATION:
+#endif // defined(GL_CONSTANT_ATTENUATION)
+#if defined(GL_LINEAR_ATTENUATION)
+        case GL_LINEAR_ATTENUATION:
+#endif // defined(GL_LINEAR_ATTENUATION)
+#if defined(GL_QUADRATIC_ATTENUATION)
+        case GL_QUADRATIC_ATTENUATION:
+#endif // defined(GL_QUADRATIC_ATTENUATION)
+            _needed = 1;
+            break;
+#if defined(GL_SPOT_DIRECTION)
+        case GL_SPOT_DIRECTION:
+#endif // defined(GL_SPOT_DIRECTION)
+            _needed = 3;
+            break;
+#if defined(GL_AMBIENT)
+        case GL_AMBIENT:
+#endif // defined(GL_AMBIENT)
+#if defined(GL_DIFFUSE)
+        case GL_DIFFUSE:
+#endif // defined(GL_DIFFUSE)
+#if defined(GL_SPECULAR)
+        case GL_SPECULAR:
+#endif // defined(GL_SPECULAR)
+#if defined(GL_EMISSION)
+        case GL_EMISSION:
+#endif // defined(GL_EMISSION)
+            _needed = 4;
+            break;
+        default:
+            _needed = 0;
+            break;
+    }
+    if (_remaining < _needed) {
+        _exception = 1;
+        _env->ThrowNew(IAEClass, "remaining() < needed");
+        goto exit;
+    }
+    glGetLightxv(
+        (GLenum)light,
+        (GLenum)pname,
+        (GLfixed *)params
+    );
+
+exit:
+    if (_array) {
+        releasePointer(_env, _array, params, _exception ? JNI_FALSE : JNI_TRUE);
+    }
+}
+
+/* void glGetMaterialfv ( GLenum face, GLenum pname, GLfloat *params ) */
+static void
+android_glGetMaterialfv__II_3FI
+  (JNIEnv *_env, jobject _this, jint face, jint pname, jfloatArray params_ref, jint offset) {
+    jint _exception = 0;
+    GLfloat *params_base = (GLfloat *) 0;
+    jint _remaining;
+    GLfloat *params = (GLfloat *) 0;
+
+    if (!params_ref) {
+        _exception = 1;
+        _env->ThrowNew(IAEClass, "params == null");
+        goto exit;
+    }
+    if (offset < 0) {
+        _exception = 1;
+        _env->ThrowNew(IAEClass, "offset < 0");
+        goto exit;
+    }
+    _remaining = _env->GetArrayLength(params_ref) - offset;
+    int _needed;
+    switch (pname) {
+#if defined(GL_SHININESS)
+        case GL_SHININESS:
+#endif // defined(GL_SHININESS)
+            _needed = 1;
+            break;
+#if defined(GL_AMBIENT)
+        case GL_AMBIENT:
+#endif // defined(GL_AMBIENT)
+#if defined(GL_DIFFUSE)
+        case GL_DIFFUSE:
+#endif // defined(GL_DIFFUSE)
+#if defined(GL_SPECULAR)
+        case GL_SPECULAR:
+#endif // defined(GL_SPECULAR)
+#if defined(GL_EMISSION)
+        case GL_EMISSION:
+#endif // defined(GL_EMISSION)
+#if defined(GL_AMBIENT_AND_DIFFUSE)
+        case GL_AMBIENT_AND_DIFFUSE:
+#endif // defined(GL_AMBIENT_AND_DIFFUSE)
+            _needed = 4;
+            break;
+        default:
+            _needed = 0;
+            break;
+    }
+    if (_remaining < _needed) {
+        _exception = 1;
+        _env->ThrowNew(IAEClass, "length - offset < needed");
+        goto exit;
+    }
+    params_base = (GLfloat *)
+        _env->GetPrimitiveArrayCritical(params_ref, (jboolean *)0);
+    params = params_base + offset;
+
+    glGetMaterialfv(
+        (GLenum)face,
+        (GLenum)pname,
+        (GLfloat *)params
+    );
+
+exit:
+    if (params_base) {
+        _env->ReleasePrimitiveArrayCritical(params_ref, params_base,
+            _exception ? JNI_ABORT: 0);
+    }
+}
+
+/* void glGetMaterialfv ( GLenum face, GLenum pname, GLfloat *params ) */
+static void
+android_glGetMaterialfv__IILjava_nio_FloatBuffer_2
+  (JNIEnv *_env, jobject _this, jint face, jint pname, jobject params_buf) {
+    jint _exception = 0;
+    jarray _array = (jarray) 0;
+    jint _remaining;
+    GLfloat *params = (GLfloat *) 0;
+
+    params = (GLfloat *)getPointer(_env, params_buf, &_array, &_remaining);
+    int _needed;
+    switch (pname) {
+#if defined(GL_SHININESS)
+        case GL_SHININESS:
+#endif // defined(GL_SHININESS)
+            _needed = 1;
+            break;
+#if defined(GL_AMBIENT)
+        case GL_AMBIENT:
+#endif // defined(GL_AMBIENT)
+#if defined(GL_DIFFUSE)
+        case GL_DIFFUSE:
+#endif // defined(GL_DIFFUSE)
+#if defined(GL_SPECULAR)
+        case GL_SPECULAR:
+#endif // defined(GL_SPECULAR)
+#if defined(GL_EMISSION)
+        case GL_EMISSION:
+#endif // defined(GL_EMISSION)
+#if defined(GL_AMBIENT_AND_DIFFUSE)
+        case GL_AMBIENT_AND_DIFFUSE:
+#endif // defined(GL_AMBIENT_AND_DIFFUSE)
+            _needed = 4;
+            break;
+        default:
+            _needed = 0;
+            break;
+    }
+    if (_remaining < _needed) {
+        _exception = 1;
+        _env->ThrowNew(IAEClass, "remaining() < needed");
+        goto exit;
+    }
+    glGetMaterialfv(
+        (GLenum)face,
+        (GLenum)pname,
+        (GLfloat *)params
+    );
+
+exit:
+    if (_array) {
+        releasePointer(_env, _array, params, _exception ? JNI_FALSE : JNI_TRUE);
+    }
+}
+
+/* void glGetMaterialxv ( GLenum face, GLenum pname, GLfixed *params ) */
+static void
+android_glGetMaterialxv__II_3II
+  (JNIEnv *_env, jobject _this, jint face, jint pname, jintArray params_ref, jint offset) {
+    jint _exception = 0;
+    GLfixed *params_base = (GLfixed *) 0;
+    jint _remaining;
+    GLfixed *params = (GLfixed *) 0;
+
+    if (!params_ref) {
+        _exception = 1;
+        _env->ThrowNew(IAEClass, "params == null");
+        goto exit;
+    }
+    if (offset < 0) {
+        _exception = 1;
+        _env->ThrowNew(IAEClass, "offset < 0");
+        goto exit;
+    }
+    _remaining = _env->GetArrayLength(params_ref) - offset;
+    int _needed;
+    switch (pname) {
+#if defined(GL_SHININESS)
+        case GL_SHININESS:
+#endif // defined(GL_SHININESS)
+            _needed = 1;
+            break;
+#if defined(GL_AMBIENT)
+        case GL_AMBIENT:
+#endif // defined(GL_AMBIENT)
+#if defined(GL_DIFFUSE)
+        case GL_DIFFUSE:
+#endif // defined(GL_DIFFUSE)
+#if defined(GL_SPECULAR)
+        case GL_SPECULAR:
+#endif // defined(GL_SPECULAR)
+#if defined(GL_EMISSION)
+        case GL_EMISSION:
+#endif // defined(GL_EMISSION)
+#if defined(GL_AMBIENT_AND_DIFFUSE)
+        case GL_AMBIENT_AND_DIFFUSE:
+#endif // defined(GL_AMBIENT_AND_DIFFUSE)
+            _needed = 4;
+            break;
+        default:
+            _needed = 0;
+            break;
+    }
+    if (_remaining < _needed) {
+        _exception = 1;
+        _env->ThrowNew(IAEClass, "length - offset < needed");
+        goto exit;
+    }
+    params_base = (GLfixed *)
+        _env->GetPrimitiveArrayCritical(params_ref, (jboolean *)0);
+    params = params_base + offset;
+
+    glGetMaterialxv(
+        (GLenum)face,
+        (GLenum)pname,
+        (GLfixed *)params
+    );
+
+exit:
+    if (params_base) {
+        _env->ReleasePrimitiveArrayCritical(params_ref, params_base,
+            _exception ? JNI_ABORT: 0);
+    }
+}
+
+/* void glGetMaterialxv ( GLenum face, GLenum pname, GLfixed *params ) */
+static void
+android_glGetMaterialxv__IILjava_nio_IntBuffer_2
+  (JNIEnv *_env, jobject _this, jint face, jint pname, jobject params_buf) {
+    jint _exception = 0;
+    jarray _array = (jarray) 0;
+    jint _remaining;
+    GLfixed *params = (GLfixed *) 0;
+
+    params = (GLfixed *)getPointer(_env, params_buf, &_array, &_remaining);
+    int _needed;
+    switch (pname) {
+#if defined(GL_SHININESS)
+        case GL_SHININESS:
+#endif // defined(GL_SHININESS)
+            _needed = 1;
+            break;
+#if defined(GL_AMBIENT)
+        case GL_AMBIENT:
+#endif // defined(GL_AMBIENT)
+#if defined(GL_DIFFUSE)
+        case GL_DIFFUSE:
+#endif // defined(GL_DIFFUSE)
+#if defined(GL_SPECULAR)
+        case GL_SPECULAR:
+#endif // defined(GL_SPECULAR)
+#if defined(GL_EMISSION)
+        case GL_EMISSION:
+#endif // defined(GL_EMISSION)
+#if defined(GL_AMBIENT_AND_DIFFUSE)
+        case GL_AMBIENT_AND_DIFFUSE:
+#endif // defined(GL_AMBIENT_AND_DIFFUSE)
+            _needed = 4;
+            break;
+        default:
+            _needed = 0;
+            break;
+    }
+    if (_remaining < _needed) {
+        _exception = 1;
+        _env->ThrowNew(IAEClass, "remaining() < needed");
+        goto exit;
+    }
+    glGetMaterialxv(
+        (GLenum)face,
+        (GLenum)pname,
+        (GLfixed *)params
+    );
+
+exit:
+    if (_array) {
+        releasePointer(_env, _array, params, _exception ? JNI_FALSE : JNI_TRUE);
+    }
+}
+
+/* void glGetTexEnviv ( GLenum env, GLenum pname, GLint *params ) */
+static void
+android_glGetTexEnviv__II_3II
+  (JNIEnv *_env, jobject _this, jint env, jint pname, jintArray params_ref, jint offset) {
+    jint _exception = 0;
+    GLint *params_base = (GLint *) 0;
+    jint _remaining;
+    GLint *params = (GLint *) 0;
+
+    if (!params_ref) {
+        _exception = 1;
+        _env->ThrowNew(IAEClass, "params == null");
+        goto exit;
+    }
+    if (offset < 0) {
+        _exception = 1;
+        _env->ThrowNew(IAEClass, "offset < 0");
+        goto exit;
+    }
+    _remaining = _env->GetArrayLength(params_ref) - offset;
+    int _needed;
+    switch (pname) {
+#if defined(GL_TEXTURE_ENV_MODE)
+        case GL_TEXTURE_ENV_MODE:
+#endif // defined(GL_TEXTURE_ENV_MODE)
+#if defined(GL_COMBINE_RGB)
+        case GL_COMBINE_RGB:
+#endif // defined(GL_COMBINE_RGB)
+#if defined(GL_COMBINE_ALPHA)
+        case GL_COMBINE_ALPHA:
+#endif // defined(GL_COMBINE_ALPHA)
+            _needed = 1;
+            break;
+#if defined(GL_TEXTURE_ENV_COLOR)
+        case GL_TEXTURE_ENV_COLOR:
+#endif // defined(GL_TEXTURE_ENV_COLOR)
+            _needed = 4;
+            break;
+        default:
+            _needed = 0;
+            break;
+    }
+    if (_remaining < _needed) {
+        _exception = 1;
+        _env->ThrowNew(IAEClass, "length - offset < needed");
+        goto exit;
+    }
+    params_base = (GLint *)
+        _env->GetPrimitiveArrayCritical(params_ref, (jboolean *)0);
+    params = params_base + offset;
+
+    glGetTexEnviv(
+        (GLenum)env,
+        (GLenum)pname,
+        (GLint *)params
+    );
+
+exit:
+    if (params_base) {
+        _env->ReleasePrimitiveArrayCritical(params_ref, params_base,
+            _exception ? JNI_ABORT: 0);
+    }
+}
+
+/* void glGetTexEnviv ( GLenum env, GLenum pname, GLint *params ) */
+static void
+android_glGetTexEnviv__IILjava_nio_IntBuffer_2
+  (JNIEnv *_env, jobject _this, jint env, jint pname, jobject params_buf) {
+    jint _exception = 0;
+    jarray _array = (jarray) 0;
+    jint _remaining;
+    GLint *params = (GLint *) 0;
+
+    params = (GLint *)getPointer(_env, params_buf, &_array, &_remaining);
+    int _needed;
+    switch (pname) {
+#if defined(GL_TEXTURE_ENV_MODE)
+        case GL_TEXTURE_ENV_MODE:
+#endif // defined(GL_TEXTURE_ENV_MODE)
+#if defined(GL_COMBINE_RGB)
+        case GL_COMBINE_RGB:
+#endif // defined(GL_COMBINE_RGB)
+#if defined(GL_COMBINE_ALPHA)
+        case GL_COMBINE_ALPHA:
+#endif // defined(GL_COMBINE_ALPHA)
+            _needed = 1;
+            break;
+#if defined(GL_TEXTURE_ENV_COLOR)
+        case GL_TEXTURE_ENV_COLOR:
+#endif // defined(GL_TEXTURE_ENV_COLOR)
+            _needed = 4;
+            break;
+        default:
+            _needed = 0;
+            break;
+    }
+    if (_remaining < _needed) {
+        _exception = 1;
+        _env->ThrowNew(IAEClass, "remaining() < needed");
+        goto exit;
+    }
+    glGetTexEnviv(
+        (GLenum)env,
+        (GLenum)pname,
+        (GLint *)params
+    );
+
+exit:
+    if (_array) {
+        releasePointer(_env, _array, params, _exception ? JNI_FALSE : JNI_TRUE);
+    }
+}
+
+/* void glGetTexEnvxv ( GLenum env, GLenum pname, GLfixed *params ) */
+static void
+android_glGetTexEnvxv__II_3II
+  (JNIEnv *_env, jobject _this, jint env, jint pname, jintArray params_ref, jint offset) {
+    jint _exception = 0;
+    GLfixed *params_base = (GLfixed *) 0;
+    jint _remaining;
+    GLfixed *params = (GLfixed *) 0;
+
+    if (!params_ref) {
+        _exception = 1;
+        _env->ThrowNew(IAEClass, "params == null");
+        goto exit;
+    }
+    if (offset < 0) {
+        _exception = 1;
+        _env->ThrowNew(IAEClass, "offset < 0");
+        goto exit;
+    }
+    _remaining = _env->GetArrayLength(params_ref) - offset;
+    int _needed;
+    switch (pname) {
+#if defined(GL_TEXTURE_ENV_MODE)
+        case GL_TEXTURE_ENV_MODE:
+#endif // defined(GL_TEXTURE_ENV_MODE)
+#if defined(GL_COMBINE_RGB)
+        case GL_COMBINE_RGB:
+#endif // defined(GL_COMBINE_RGB)
+#if defined(GL_COMBINE_ALPHA)
+        case GL_COMBINE_ALPHA:
+#endif // defined(GL_COMBINE_ALPHA)
+            _needed = 1;
+            break;
+#if defined(GL_TEXTURE_ENV_COLOR)
+        case GL_TEXTURE_ENV_COLOR:
+#endif // defined(GL_TEXTURE_ENV_COLOR)
+            _needed = 4;
+            break;
+        default:
+            _needed = 0;
+            break;
+    }
+    if (_remaining < _needed) {
+        _exception = 1;
+        _env->ThrowNew(IAEClass, "length - offset < needed");
+        goto exit;
+    }
+    params_base = (GLfixed *)
+        _env->GetPrimitiveArrayCritical(params_ref, (jboolean *)0);
+    params = params_base + offset;
+
+    glGetTexEnvxv(
+        (GLenum)env,
+        (GLenum)pname,
+        (GLfixed *)params
+    );
+
+exit:
+    if (params_base) {
+        _env->ReleasePrimitiveArrayCritical(params_ref, params_base,
+            _exception ? JNI_ABORT: 0);
+    }
+}
+
+/* void glGetTexEnvxv ( GLenum env, GLenum pname, GLfixed *params ) */
+static void
+android_glGetTexEnvxv__IILjava_nio_IntBuffer_2
+  (JNIEnv *_env, jobject _this, jint env, jint pname, jobject params_buf) {
+    jint _exception = 0;
+    jarray _array = (jarray) 0;
+    jint _remaining;
+    GLfixed *params = (GLfixed *) 0;
+
+    params = (GLfixed *)getPointer(_env, params_buf, &_array, &_remaining);
+    int _needed;
+    switch (pname) {
+#if defined(GL_TEXTURE_ENV_MODE)
+        case GL_TEXTURE_ENV_MODE:
+#endif // defined(GL_TEXTURE_ENV_MODE)
+#if defined(GL_COMBINE_RGB)
+        case GL_COMBINE_RGB:
+#endif // defined(GL_COMBINE_RGB)
+#if defined(GL_COMBINE_ALPHA)
+        case GL_COMBINE_ALPHA:
+#endif // defined(GL_COMBINE_ALPHA)
+            _needed = 1;
+            break;
+#if defined(GL_TEXTURE_ENV_COLOR)
+        case GL_TEXTURE_ENV_COLOR:
+#endif // defined(GL_TEXTURE_ENV_COLOR)
+            _needed = 4;
+            break;
+        default:
+            _needed = 0;
+            break;
+    }
+    if (_remaining < _needed) {
+        _exception = 1;
+        _env->ThrowNew(IAEClass, "remaining() < needed");
+        goto exit;
+    }
+    glGetTexEnvxv(
+        (GLenum)env,
+        (GLenum)pname,
+        (GLfixed *)params
+    );
+
+exit:
+    if (_array) {
+        releasePointer(_env, _array, params, _exception ? JNI_FALSE : JNI_TRUE);
+    }
+}
+
+/* void glGetTexParameterfv ( GLenum target, GLenum pname, GLfloat *params ) */
+static void
+android_glGetTexParameterfv__II_3FI
+  (JNIEnv *_env, jobject _this, jint target, jint pname, jfloatArray params_ref, jint offset) {
+    jint _exception = 0;
+    GLfloat *params_base = (GLfloat *) 0;
+    jint _remaining;
+    GLfloat *params = (GLfloat *) 0;
+
+    if (!params_ref) {
+        _exception = 1;
+        _env->ThrowNew(IAEClass, "params == null");
+        goto exit;
+    }
+    if (offset < 0) {
+        _exception = 1;
+        _env->ThrowNew(IAEClass, "offset < 0");
+        goto exit;
+    }
+    _remaining = _env->GetArrayLength(params_ref) - offset;
+    if (_remaining < 1) {
+        _exception = 1;
+        _env->ThrowNew(IAEClass, "length - offset < 1");
+        goto exit;
+    }
+    params_base = (GLfloat *)
+        _env->GetPrimitiveArrayCritical(params_ref, (jboolean *)0);
+    params = params_base + offset;
+
+    glGetTexParameterfv(
+        (GLenum)target,
+        (GLenum)pname,
+        (GLfloat *)params
+    );
+
+exit:
+    if (params_base) {
+        _env->ReleasePrimitiveArrayCritical(params_ref, params_base,
+            _exception ? JNI_ABORT: 0);
+    }
+}
+
+/* void glGetTexParameterfv ( GLenum target, GLenum pname, GLfloat *params ) */
+static void
+android_glGetTexParameterfv__IILjava_nio_FloatBuffer_2
+  (JNIEnv *_env, jobject _this, jint target, jint pname, jobject params_buf) {
+    jint _exception = 0;
+    jarray _array = (jarray) 0;
+    jint _remaining;
+    GLfloat *params = (GLfloat *) 0;
+
+    params = (GLfloat *)getPointer(_env, params_buf, &_array, &_remaining);
+    if (_remaining < 1) {
+        _exception = 1;
+        _env->ThrowNew(IAEClass, "remaining() < 1");
+        goto exit;
+    }
+    glGetTexParameterfv(
+        (GLenum)target,
+        (GLenum)pname,
+        (GLfloat *)params
+    );
+
+exit:
+    if (_array) {
+        releasePointer(_env, _array, params, _exception ? JNI_FALSE : JNI_TRUE);
+    }
+}
+
+/* void glGetTexParameteriv ( GLenum target, GLenum pname, GLint *params ) */
+static void
+android_glGetTexParameteriv__II_3II
+  (JNIEnv *_env, jobject _this, jint target, jint pname, jintArray params_ref, jint offset) {
+    jint _exception = 0;
+    GLint *params_base = (GLint *) 0;
+    jint _remaining;
+    GLint *params = (GLint *) 0;
+
+    if (!params_ref) {
+        _exception = 1;
+        _env->ThrowNew(IAEClass, "params == null");
+        goto exit;
+    }
+    if (offset < 0) {
+        _exception = 1;
+        _env->ThrowNew(IAEClass, "offset < 0");
+        goto exit;
+    }
+    _remaining = _env->GetArrayLength(params_ref) - offset;
+    if (_remaining < 1) {
+        _exception = 1;
+        _env->ThrowNew(IAEClass, "length - offset < 1");
+        goto exit;
+    }
+    params_base = (GLint *)
+        _env->GetPrimitiveArrayCritical(params_ref, (jboolean *)0);
+    params = params_base + offset;
+
+    glGetTexParameteriv(
+        (GLenum)target,
+        (GLenum)pname,
+        (GLint *)params
+    );
+
+exit:
+    if (params_base) {
+        _env->ReleasePrimitiveArrayCritical(params_ref, params_base,
+            _exception ? JNI_ABORT: 0);
+    }
+}
+
+/* void glGetTexParameteriv ( GLenum target, GLenum pname, GLint *params ) */
+static void
+android_glGetTexParameteriv__IILjava_nio_IntBuffer_2
+  (JNIEnv *_env, jobject _this, jint target, jint pname, jobject params_buf) {
+    jint _exception = 0;
+    jarray _array = (jarray) 0;
+    jint _remaining;
+    GLint *params = (GLint *) 0;
+
+    params = (GLint *)getPointer(_env, params_buf, &_array, &_remaining);
+    if (_remaining < 1) {
+        _exception = 1;
+        _env->ThrowNew(IAEClass, "remaining() < 1");
+        goto exit;
+    }
+    glGetTexParameteriv(
+        (GLenum)target,
+        (GLenum)pname,
+        (GLint *)params
+    );
+
+exit:
+    if (_array) {
+        releasePointer(_env, _array, params, _exception ? JNI_FALSE : JNI_TRUE);
+    }
+}
+
+/* void glGetTexParameterxv ( GLenum target, GLenum pname, GLfixed *params ) */
+static void
+android_glGetTexParameterxv__II_3II
+  (JNIEnv *_env, jobject _this, jint target, jint pname, jintArray params_ref, jint offset) {
+    jint _exception = 0;
+    GLfixed *params_base = (GLfixed *) 0;
+    jint _remaining;
+    GLfixed *params = (GLfixed *) 0;
+
+    if (!params_ref) {
+        _exception = 1;
+        _env->ThrowNew(IAEClass, "params == null");
+        goto exit;
+    }
+    if (offset < 0) {
+        _exception = 1;
+        _env->ThrowNew(IAEClass, "offset < 0");
+        goto exit;
+    }
+    _remaining = _env->GetArrayLength(params_ref) - offset;
+    if (_remaining < 1) {
+        _exception = 1;
+        _env->ThrowNew(IAEClass, "length - offset < 1");
+        goto exit;
+    }
+    params_base = (GLfixed *)
+        _env->GetPrimitiveArrayCritical(params_ref, (jboolean *)0);
+    params = params_base + offset;
+
+    glGetTexParameterxv(
+        (GLenum)target,
+        (GLenum)pname,
+        (GLfixed *)params
+    );
+
+exit:
+    if (params_base) {
+        _env->ReleasePrimitiveArrayCritical(params_ref, params_base,
+            _exception ? JNI_ABORT: 0);
+    }
+}
+
+/* void glGetTexParameterxv ( GLenum target, GLenum pname, GLfixed *params ) */
+static void
+android_glGetTexParameterxv__IILjava_nio_IntBuffer_2
+  (JNIEnv *_env, jobject _this, jint target, jint pname, jobject params_buf) {
+    jint _exception = 0;
+    jarray _array = (jarray) 0;
+    jint _remaining;
+    GLfixed *params = (GLfixed *) 0;
+
+    params = (GLfixed *)getPointer(_env, params_buf, &_array, &_remaining);
+    if (_remaining < 1) {
+        _exception = 1;
+        _env->ThrowNew(IAEClass, "remaining() < 1");
+        goto exit;
+    }
+    glGetTexParameterxv(
+        (GLenum)target,
+        (GLenum)pname,
+        (GLfixed *)params
+    );
+
+exit:
+    if (_array) {
+        releasePointer(_env, _array, params, _exception ? JNI_FALSE : JNI_TRUE);
+    }
+}
+
+/* GLboolean glIsBuffer ( GLuint buffer ) */
+static jboolean
+android_glIsBuffer__I
+  (JNIEnv *_env, jobject _this, jint buffer) {
+    GLboolean _returnValue;
+    _returnValue = glIsBuffer(
+        (GLuint)buffer
+    );
+    return _returnValue;
+}
+
+/* GLboolean glIsEnabled ( GLenum cap ) */
+static jboolean
+android_glIsEnabled__I
+  (JNIEnv *_env, jobject _this, jint cap) {
+    GLboolean _returnValue;
+    _returnValue = glIsEnabled(
+        (GLenum)cap
+    );
+    return _returnValue;
+}
+
+/* GLboolean glIsTexture ( GLuint texture ) */
+static jboolean
+android_glIsTexture__I
+  (JNIEnv *_env, jobject _this, jint texture) {
+    GLboolean _returnValue;
+    _returnValue = glIsTexture(
+        (GLuint)texture
+    );
+    return _returnValue;
+}
+
+/* void glNormalPointer ( GLenum type, GLsizei stride, GLint offset ) */
+static void
+android_glNormalPointer__III
+  (JNIEnv *_env, jobject _this, jint type, jint stride, jint offset) {
+    glNormalPointer(
+        (GLenum)type,
+        (GLsizei)stride,
+        (const GLvoid *)offset
+    );
+}
+
+/* void glPointParameterf ( GLenum pname, GLfloat param ) */
+static void
+android_glPointParameterf__IF
+  (JNIEnv *_env, jobject _this, jint pname, jfloat param) {
+    glPointParameterf(
+        (GLenum)pname,
+        (GLfloat)param
+    );
+}
+
+/* void glPointParameterfv ( GLenum pname, const GLfloat *params ) */
+static void
+android_glPointParameterfv__I_3FI
+  (JNIEnv *_env, jobject _this, jint pname, jfloatArray params_ref, jint offset) {
+    GLfloat *params_base = (GLfloat *) 0;
+    jint _remaining;
+    GLfloat *params = (GLfloat *) 0;
+
+    if (!params_ref) {
+        _env->ThrowNew(IAEClass, "params == null");
+        goto exit;
+    }
+    if (offset < 0) {
+        _env->ThrowNew(IAEClass, "offset < 0");
+        goto exit;
+    }
+    _remaining = _env->GetArrayLength(params_ref) - offset;
+    if (_remaining < 1) {
+        _env->ThrowNew(IAEClass, "length - offset < 1");
+        goto exit;
+    }
+    params_base = (GLfloat *)
+        _env->GetPrimitiveArrayCritical(params_ref, (jboolean *)0);
+    params = params_base + offset;
+
+    glPointParameterfv(
+        (GLenum)pname,
+        (GLfloat *)params
+    );
+
+exit:
+    if (params_base) {
+        _env->ReleasePrimitiveArrayCritical(params_ref, params_base,
+            JNI_ABORT);
+    }
+}
+
+/* void glPointParameterfv ( GLenum pname, const GLfloat *params ) */
+static void
+android_glPointParameterfv__ILjava_nio_FloatBuffer_2
+  (JNIEnv *_env, jobject _this, jint pname, jobject params_buf) {
+    jarray _array = (jarray) 0;
+    jint _remaining;
+    GLfloat *params = (GLfloat *) 0;
+
+    params = (GLfloat *)getPointer(_env, params_buf, &_array, &_remaining);
+    if (_remaining < 1) {
+        _env->ThrowNew(IAEClass, "remaining() < 1");
+        goto exit;
+    }
+    glPointParameterfv(
+        (GLenum)pname,
+        (GLfloat *)params
+    );
+
+exit:
+    if (_array) {
+        releasePointer(_env, _array, params, JNI_FALSE);
+    }
+}
+
+/* void glPointParameterx ( GLenum pname, GLfixed param ) */
+static void
+android_glPointParameterx__II
+  (JNIEnv *_env, jobject _this, jint pname, jint param) {
+    glPointParameterx(
+        (GLenum)pname,
+        (GLfixed)param
+    );
+}
+
+/* void glPointParameterxv ( GLenum pname, const GLfixed *params ) */
+static void
+android_glPointParameterxv__I_3II
+  (JNIEnv *_env, jobject _this, jint pname, jintArray params_ref, jint offset) {
+    GLfixed *params_base = (GLfixed *) 0;
+    jint _remaining;
+    GLfixed *params = (GLfixed *) 0;
+
+    if (!params_ref) {
+        _env->ThrowNew(IAEClass, "params == null");
+        goto exit;
+    }
+    if (offset < 0) {
+        _env->ThrowNew(IAEClass, "offset < 0");
+        goto exit;
+    }
+    _remaining = _env->GetArrayLength(params_ref) - offset;
+    if (_remaining < 1) {
+        _env->ThrowNew(IAEClass, "length - offset < 1");
+        goto exit;
+    }
+    params_base = (GLfixed *)
+        _env->GetPrimitiveArrayCritical(params_ref, (jboolean *)0);
+    params = params_base + offset;
+
+    glPointParameterxv(
+        (GLenum)pname,
+        (GLfixed *)params
+    );
+
+exit:
+    if (params_base) {
+        _env->ReleasePrimitiveArrayCritical(params_ref, params_base,
+            JNI_ABORT);
+    }
+}
+
+/* void glPointParameterxv ( GLenum pname, const GLfixed *params ) */
+static void
+android_glPointParameterxv__ILjava_nio_IntBuffer_2
+  (JNIEnv *_env, jobject _this, jint pname, jobject params_buf) {
+    jarray _array = (jarray) 0;
+    jint _remaining;
+    GLfixed *params = (GLfixed *) 0;
+
+    params = (GLfixed *)getPointer(_env, params_buf, &_array, &_remaining);
+    if (_remaining < 1) {
+        _env->ThrowNew(IAEClass, "remaining() < 1");
+        goto exit;
+    }
+    glPointParameterxv(
+        (GLenum)pname,
+        (GLfixed *)params
+    );
+
+exit:
+    if (_array) {
+        releasePointer(_env, _array, params, JNI_FALSE);
+    }
+}
+
+/* void glPointSizePointerOES ( GLenum type, GLsizei stride, const GLvoid *pointer ) */
+static void
+android_glPointSizePointerOES__IILjava_nio_Buffer_2
+  (JNIEnv *_env, jobject _this, jint type, jint stride, jobject pointer_buf) {
+    jarray _array = (jarray) 0;
+    jint _remaining;
+    GLvoid *pointer = (GLvoid *) 0;
+
+    pointer = (GLvoid *)getPointer(_env, pointer_buf, &_array, &_remaining);
+    glPointSizePointerOES(
+        (GLenum)type,
+        (GLsizei)stride,
+        (GLvoid *)pointer
+    );
+    if (_array) {
+        releasePointer(_env, _array, pointer, JNI_FALSE);
+    }
+}
+
+/* void glTexCoordPointer ( GLint size, GLenum type, GLsizei stride, GLint offset ) */
+static void
+android_glTexCoordPointer__IIII
+  (JNIEnv *_env, jobject _this, jint size, jint type, jint stride, jint offset) {
+    glTexCoordPointer(
+        (GLint)size,
+        (GLenum)type,
+        (GLsizei)stride,
+        (const GLvoid *)offset
+    );
+}
+
+/* void glTexEnvi ( GLenum target, GLenum pname, GLint param ) */
+static void
+android_glTexEnvi__III
+  (JNIEnv *_env, jobject _this, jint target, jint pname, jint param) {
+    glTexEnvi(
+        (GLenum)target,
+        (GLenum)pname,
+        (GLint)param
+    );
+}
+
+/* void glTexEnviv ( GLenum target, GLenum pname, const GLint *params ) */
+static void
+android_glTexEnviv__II_3II
+  (JNIEnv *_env, jobject _this, jint target, jint pname, jintArray params_ref, jint offset) {
+    GLint *params_base = (GLint *) 0;
+    jint _remaining;
+    GLint *params = (GLint *) 0;
+
+    if (!params_ref) {
+        _env->ThrowNew(IAEClass, "params == null");
+        goto exit;
+    }
+    if (offset < 0) {
+        _env->ThrowNew(IAEClass, "offset < 0");
+        goto exit;
+    }
+    _remaining = _env->GetArrayLength(params_ref) - offset;
+    int _needed;
+    switch (pname) {
+#if defined(GL_TEXTURE_ENV_MODE)
+        case GL_TEXTURE_ENV_MODE:
+#endif // defined(GL_TEXTURE_ENV_MODE)
+#if defined(GL_COMBINE_RGB)
+        case GL_COMBINE_RGB:
+#endif // defined(GL_COMBINE_RGB)
+#if defined(GL_COMBINE_ALPHA)
+        case GL_COMBINE_ALPHA:
+#endif // defined(GL_COMBINE_ALPHA)
+            _needed = 1;
+            break;
+#if defined(GL_TEXTURE_ENV_COLOR)
+        case GL_TEXTURE_ENV_COLOR:
+#endif // defined(GL_TEXTURE_ENV_COLOR)
+            _needed = 4;
+            break;
+        default:
+            _needed = 0;
+            break;
+    }
+    if (_remaining < _needed) {
+        _env->ThrowNew(IAEClass, "length - offset < needed");
+        goto exit;
+    }
+    params_base = (GLint *)
+        _env->GetPrimitiveArrayCritical(params_ref, (jboolean *)0);
+    params = params_base + offset;
+
+    glTexEnviv(
+        (GLenum)target,
+        (GLenum)pname,
+        (GLint *)params
+    );
+
+exit:
+    if (params_base) {
+        _env->ReleasePrimitiveArrayCritical(params_ref, params_base,
+            JNI_ABORT);
+    }
+}
+
+/* void glTexEnviv ( GLenum target, GLenum pname, const GLint *params ) */
+static void
+android_glTexEnviv__IILjava_nio_IntBuffer_2
+  (JNIEnv *_env, jobject _this, jint target, jint pname, jobject params_buf) {
+    jarray _array = (jarray) 0;
+    jint _remaining;
+    GLint *params = (GLint *) 0;
+
+    params = (GLint *)getPointer(_env, params_buf, &_array, &_remaining);
+    int _needed;
+    switch (pname) {
+#if defined(GL_TEXTURE_ENV_MODE)
+        case GL_TEXTURE_ENV_MODE:
+#endif // defined(GL_TEXTURE_ENV_MODE)
+#if defined(GL_COMBINE_RGB)
+        case GL_COMBINE_RGB:
+#endif // defined(GL_COMBINE_RGB)
+#if defined(GL_COMBINE_ALPHA)
+        case GL_COMBINE_ALPHA:
+#endif // defined(GL_COMBINE_ALPHA)
+            _needed = 1;
+            break;
+#if defined(GL_TEXTURE_ENV_COLOR)
+        case GL_TEXTURE_ENV_COLOR:
+#endif // defined(GL_TEXTURE_ENV_COLOR)
+            _needed = 4;
+            break;
+        default:
+            _needed = 0;
+            break;
+    }
+    if (_remaining < _needed) {
+        _env->ThrowNew(IAEClass, "remaining() < needed");
+        goto exit;
+    }
+    glTexEnviv(
+        (GLenum)target,
+        (GLenum)pname,
+        (GLint *)params
+    );
+
+exit:
+    if (_array) {
+        releasePointer(_env, _array, params, JNI_FALSE);
+    }
+}
+
+/* void glTexParameterfv ( GLenum target, GLenum pname, const GLfloat *params ) */
+static void
+android_glTexParameterfv__II_3FI
+  (JNIEnv *_env, jobject _this, jint target, jint pname, jfloatArray params_ref, jint offset) {
+    GLfloat *params_base = (GLfloat *) 0;
+    jint _remaining;
+    GLfloat *params = (GLfloat *) 0;
+
+    if (!params_ref) {
+        _env->ThrowNew(IAEClass, "params == null");
+        goto exit;
+    }
+    if (offset < 0) {
+        _env->ThrowNew(IAEClass, "offset < 0");
+        goto exit;
+    }
+    _remaining = _env->GetArrayLength(params_ref) - offset;
+    if (_remaining < 1) {
+        _env->ThrowNew(IAEClass, "length - offset < 1");
+        goto exit;
+    }
+    params_base = (GLfloat *)
+        _env->GetPrimitiveArrayCritical(params_ref, (jboolean *)0);
+    params = params_base + offset;
+
+    glTexParameterfv(
+        (GLenum)target,
+        (GLenum)pname,
+        (GLfloat *)params
+    );
+
+exit:
+    if (params_base) {
+        _env->ReleasePrimitiveArrayCritical(params_ref, params_base,
+            JNI_ABORT);
+    }
+}
+
+/* void glTexParameterfv ( GLenum target, GLenum pname, const GLfloat *params ) */
+static void
+android_glTexParameterfv__IILjava_nio_FloatBuffer_2
+  (JNIEnv *_env, jobject _this, jint target, jint pname, jobject params_buf) {
+    jarray _array = (jarray) 0;
+    jint _remaining;
+    GLfloat *params = (GLfloat *) 0;
+
+    params = (GLfloat *)getPointer(_env, params_buf, &_array, &_remaining);
+    if (_remaining < 1) {
+        _env->ThrowNew(IAEClass, "remaining() < 1");
+        goto exit;
+    }
+    glTexParameterfv(
+        (GLenum)target,
+        (GLenum)pname,
+        (GLfloat *)params
+    );
+
+exit:
+    if (_array) {
+        releasePointer(_env, _array, params, JNI_FALSE);
+    }
+}
+
+/* void glTexParameteri ( GLenum target, GLenum pname, GLint param ) */
+static void
+android_glTexParameteri__III
+  (JNIEnv *_env, jobject _this, jint target, jint pname, jint param) {
+    glTexParameteri(
+        (GLenum)target,
+        (GLenum)pname,
+        (GLint)param
+    );
+}
+
+/* void glTexParameteriv ( GLenum target, GLenum pname, const GLint *params ) */
+static void
+android_glTexParameteriv__II_3II
+  (JNIEnv *_env, jobject _this, jint target, jint pname, jintArray params_ref, jint offset) {
+    GLint *params_base = (GLint *) 0;
+    jint _remaining;
+    GLint *params = (GLint *) 0;
+
+    if (!params_ref) {
+        _env->ThrowNew(IAEClass, "params == null");
+        goto exit;
+    }
+    if (offset < 0) {
+        _env->ThrowNew(IAEClass, "offset < 0");
+        goto exit;
+    }
+    _remaining = _env->GetArrayLength(params_ref) - offset;
+    if (_remaining < 1) {
+        _env->ThrowNew(IAEClass, "length - offset < 1");
+        goto exit;
+    }
+    params_base = (GLint *)
+        _env->GetPrimitiveArrayCritical(params_ref, (jboolean *)0);
+    params = params_base + offset;
+
+    glTexParameteriv(
+        (GLenum)target,
+        (GLenum)pname,
+        (GLint *)params
+    );
+
+exit:
+    if (params_base) {
+        _env->ReleasePrimitiveArrayCritical(params_ref, params_base,
+            JNI_ABORT);
+    }
+}
+
+/* void glTexParameteriv ( GLenum target, GLenum pname, const GLint *params ) */
+static void
+android_glTexParameteriv__IILjava_nio_IntBuffer_2
+  (JNIEnv *_env, jobject _this, jint target, jint pname, jobject params_buf) {
+    jarray _array = (jarray) 0;
+    jint _remaining;
+    GLint *params = (GLint *) 0;
+
+    params = (GLint *)getPointer(_env, params_buf, &_array, &_remaining);
+    if (_remaining < 1) {
+        _env->ThrowNew(IAEClass, "remaining() < 1");
+        goto exit;
+    }
+    glTexParameteriv(
+        (GLenum)target,
+        (GLenum)pname,
+        (GLint *)params
+    );
+
+exit:
+    if (_array) {
+        releasePointer(_env, _array, params, JNI_FALSE);
+    }
+}
+
+/* void glTexParameterxv ( GLenum target, GLenum pname, const GLfixed *params ) */
+static void
+android_glTexParameterxv__II_3II
+  (JNIEnv *_env, jobject _this, jint target, jint pname, jintArray params_ref, jint offset) {
+    GLfixed *params_base = (GLfixed *) 0;
+    jint _remaining;
+    GLfixed *params = (GLfixed *) 0;
+
+    if (!params_ref) {
+        _env->ThrowNew(IAEClass, "params == null");
+        goto exit;
+    }
+    if (offset < 0) {
+        _env->ThrowNew(IAEClass, "offset < 0");
+        goto exit;
+    }
+    _remaining = _env->GetArrayLength(params_ref) - offset;
+    if (_remaining < 1) {
+        _env->ThrowNew(IAEClass, "length - offset < 1");
+        goto exit;
+    }
+    params_base = (GLfixed *)
+        _env->GetPrimitiveArrayCritical(params_ref, (jboolean *)0);
+    params = params_base + offset;
+
+    glTexParameterxv(
+        (GLenum)target,
+        (GLenum)pname,
+        (GLfixed *)params
+    );
+
+exit:
+    if (params_base) {
+        _env->ReleasePrimitiveArrayCritical(params_ref, params_base,
+            JNI_ABORT);
+    }
+}
+
+/* void glTexParameterxv ( GLenum target, GLenum pname, const GLfixed *params ) */
+static void
+android_glTexParameterxv__IILjava_nio_IntBuffer_2
+  (JNIEnv *_env, jobject _this, jint target, jint pname, jobject params_buf) {
+    jarray _array = (jarray) 0;
+    jint _remaining;
+    GLfixed *params = (GLfixed *) 0;
+
+    params = (GLfixed *)getPointer(_env, params_buf, &_array, &_remaining);
+    if (_remaining < 1) {
+        _env->ThrowNew(IAEClass, "remaining() < 1");
+        goto exit;
+    }
+    glTexParameterxv(
+        (GLenum)target,
+        (GLenum)pname,
+        (GLfixed *)params
+    );
+
+exit:
+    if (_array) {
+        releasePointer(_env, _array, params, JNI_FALSE);
+    }
+}
+
+/* void glVertexPointer ( GLint size, GLenum type, GLsizei stride, GLint offset ) */
+static void
+android_glVertexPointer__IIII
+  (JNIEnv *_env, jobject _this, jint size, jint type, jint stride, jint offset) {
+    glVertexPointer(
+        (GLint)size,
+        (GLenum)type,
+        (GLsizei)stride,
+        (const GLvoid *)offset
+    );
+}
+
+/* void glCurrentPaletteMatrixOES ( GLuint matrixpaletteindex ) */
+static void
+android_glCurrentPaletteMatrixOES__I
+  (JNIEnv *_env, jobject _this, jint matrixpaletteindex) {
+    _env->ThrowNew(UOEClass,
+        "glCurrentPaletteMatrixOES");
+}
+
+/* void glDrawTexfOES ( GLfloat x, GLfloat y, GLfloat z, GLfloat width, GLfloat height ) */
+static void
+android_glDrawTexfOES__FFFFF
+  (JNIEnv *_env, jobject _this, jfloat x, jfloat y, jfloat z, jfloat width, jfloat height) {
+    glDrawTexfOES(
+        (GLfloat)x,
+        (GLfloat)y,
+        (GLfloat)z,
+        (GLfloat)width,
+        (GLfloat)height
+    );
+}
+
+/* void glDrawTexfvOES ( const GLfloat *coords ) */
+static void
+android_glDrawTexfvOES___3FI
+  (JNIEnv *_env, jobject _this, jfloatArray coords_ref, jint offset) {
+    GLfloat *coords_base = (GLfloat *) 0;
+    jint _remaining;
+    GLfloat *coords = (GLfloat *) 0;
+
+    if (!coords_ref) {
+        _env->ThrowNew(IAEClass, "coords == null");
+        goto exit;
+    }
+    if (offset < 0) {
+        _env->ThrowNew(IAEClass, "offset < 0");
+        goto exit;
+    }
+    _remaining = _env->GetArrayLength(coords_ref) - offset;
+    if (_remaining < 5) {
+        _env->ThrowNew(IAEClass, "length - offset < 5");
+        goto exit;
+    }
+    coords_base = (GLfloat *)
+        _env->GetPrimitiveArrayCritical(coords_ref, (jboolean *)0);
+    coords = coords_base + offset;
+
+    glDrawTexfvOES(
+        (GLfloat *)coords
+    );
+
+exit:
+    if (coords_base) {
+        _env->ReleasePrimitiveArrayCritical(coords_ref, coords_base,
+            JNI_ABORT);
+    }
+}
+
+/* void glDrawTexfvOES ( const GLfloat *coords ) */
+static void
+android_glDrawTexfvOES__Ljava_nio_FloatBuffer_2
+  (JNIEnv *_env, jobject _this, jobject coords_buf) {
+    jarray _array = (jarray) 0;
+    jint _remaining;
+    GLfloat *coords = (GLfloat *) 0;
+
+    coords = (GLfloat *)getPointer(_env, coords_buf, &_array, &_remaining);
+    if (_remaining < 5) {
+        _env->ThrowNew(IAEClass, "remaining() < 5");
+        goto exit;
+    }
+    glDrawTexfvOES(
+        (GLfloat *)coords
+    );
+
+exit:
+    if (_array) {
+        releasePointer(_env, _array, coords, JNI_FALSE);
+    }
+}
+
+/* void glDrawTexiOES ( GLint x, GLint y, GLint z, GLint width, GLint height ) */
+static void
+android_glDrawTexiOES__IIIII
+  (JNIEnv *_env, jobject _this, jint x, jint y, jint z, jint width, jint height) {
+    glDrawTexiOES(
+        (GLint)x,
+        (GLint)y,
+        (GLint)z,
+        (GLint)width,
+        (GLint)height
+    );
+}
+
+/* void glDrawTexivOES ( const GLint *coords ) */
+static void
+android_glDrawTexivOES___3II
+  (JNIEnv *_env, jobject _this, jintArray coords_ref, jint offset) {
+    GLint *coords_base = (GLint *) 0;
+    jint _remaining;
+    GLint *coords = (GLint *) 0;
+
+    if (!coords_ref) {
+        _env->ThrowNew(IAEClass, "coords == null");
+        goto exit;
+    }
+    if (offset < 0) {
+        _env->ThrowNew(IAEClass, "offset < 0");
+        goto exit;
+    }
+    _remaining = _env->GetArrayLength(coords_ref) - offset;
+    if (_remaining < 5) {
+        _env->ThrowNew(IAEClass, "length - offset < 5");
+        goto exit;
+    }
+    coords_base = (GLint *)
+        _env->GetPrimitiveArrayCritical(coords_ref, (jboolean *)0);
+    coords = coords_base + offset;
+
+    glDrawTexivOES(
+        (GLint *)coords
+    );
+
+exit:
+    if (coords_base) {
+        _env->ReleasePrimitiveArrayCritical(coords_ref, coords_base,
+            JNI_ABORT);
+    }
+}
+
+/* void glDrawTexivOES ( const GLint *coords ) */
+static void
+android_glDrawTexivOES__Ljava_nio_IntBuffer_2
+  (JNIEnv *_env, jobject _this, jobject coords_buf) {
+    jarray _array = (jarray) 0;
+    jint _remaining;
+    GLint *coords = (GLint *) 0;
+
+    coords = (GLint *)getPointer(_env, coords_buf, &_array, &_remaining);
+    if (_remaining < 5) {
+        _env->ThrowNew(IAEClass, "remaining() < 5");
+        goto exit;
+    }
+    glDrawTexivOES(
+        (GLint *)coords
+    );
+
+exit:
+    if (_array) {
+        releasePointer(_env, _array, coords, JNI_FALSE);
+    }
+}
+
+/* void glDrawTexsOES ( GLshort x, GLshort y, GLshort z, GLshort width, GLshort height ) */
+static void
+android_glDrawTexsOES__SSSSS
+  (JNIEnv *_env, jobject _this, jshort x, jshort y, jshort z, jshort width, jshort height) {
+    glDrawTexsOES(
+        (GLshort)x,
+        (GLshort)y,
+        (GLshort)z,
+        (GLshort)width,
+        (GLshort)height
+    );
+}
+
+/* void glDrawTexsvOES ( const GLshort *coords ) */
+static void
+android_glDrawTexsvOES___3SI
+  (JNIEnv *_env, jobject _this, jshortArray coords_ref, jint offset) {
+    GLshort *coords_base = (GLshort *) 0;
+    jint _remaining;
+    GLshort *coords = (GLshort *) 0;
+
+    if (!coords_ref) {
+        _env->ThrowNew(IAEClass, "coords == null");
+        goto exit;
+    }
+    if (offset < 0) {
+        _env->ThrowNew(IAEClass, "offset < 0");
+        goto exit;
+    }
+    _remaining = _env->GetArrayLength(coords_ref) - offset;
+    if (_remaining < 5) {
+        _env->ThrowNew(IAEClass, "length - offset < 5");
+        goto exit;
+    }
+    coords_base = (GLshort *)
+        _env->GetPrimitiveArrayCritical(coords_ref, (jboolean *)0);
+    coords = coords_base + offset;
+
+    glDrawTexsvOES(
+        (GLshort *)coords
+    );
+
+exit:
+    if (coords_base) {
+        _env->ReleasePrimitiveArrayCritical(coords_ref, coords_base,
+            JNI_ABORT);
+    }
+}
+
+/* void glDrawTexsvOES ( const GLshort *coords ) */
+static void
+android_glDrawTexsvOES__Ljava_nio_ShortBuffer_2
+  (JNIEnv *_env, jobject _this, jobject coords_buf) {
+    jarray _array = (jarray) 0;
+    jint _remaining;
+    GLshort *coords = (GLshort *) 0;
+
+    coords = (GLshort *)getPointer(_env, coords_buf, &_array, &_remaining);
+    if (_remaining < 5) {
+        _env->ThrowNew(IAEClass, "remaining() < 5");
+        goto exit;
+    }
+    glDrawTexsvOES(
+        (GLshort *)coords
+    );
+
+exit:
+    if (_array) {
+        releasePointer(_env, _array, coords, JNI_FALSE);
+    }
+}
+
+/* void glDrawTexxOES ( GLfixed x, GLfixed y, GLfixed z, GLfixed width, GLfixed height ) */
+static void
+android_glDrawTexxOES__IIIII
+  (JNIEnv *_env, jobject _this, jint x, jint y, jint z, jint width, jint height) {
+    glDrawTexxOES(
+        (GLfixed)x,
+        (GLfixed)y,
+        (GLfixed)z,
+        (GLfixed)width,
+        (GLfixed)height
+    );
+}
+
+/* void glDrawTexxvOES ( const GLfixed *coords ) */
+static void
+android_glDrawTexxvOES___3II
+  (JNIEnv *_env, jobject _this, jintArray coords_ref, jint offset) {
+    GLfixed *coords_base = (GLfixed *) 0;
+    jint _remaining;
+    GLfixed *coords = (GLfixed *) 0;
+
+    if (!coords_ref) {
+        _env->ThrowNew(IAEClass, "coords == null");
+        goto exit;
+    }
+    if (offset < 0) {
+        _env->ThrowNew(IAEClass, "offset < 0");
+        goto exit;
+    }
+    _remaining = _env->GetArrayLength(coords_ref) - offset;
+    if (_remaining < 5) {
+        _env->ThrowNew(IAEClass, "length - offset < 5");
+        goto exit;
+    }
+    coords_base = (GLfixed *)
+        _env->GetPrimitiveArrayCritical(coords_ref, (jboolean *)0);
+    coords = coords_base + offset;
+
+    glDrawTexxvOES(
+        (GLfixed *)coords
+    );
+
+exit:
+    if (coords_base) {
+        _env->ReleasePrimitiveArrayCritical(coords_ref, coords_base,
+            JNI_ABORT);
+    }
+}
+
+/* void glDrawTexxvOES ( const GLfixed *coords ) */
+static void
+android_glDrawTexxvOES__Ljava_nio_IntBuffer_2
+  (JNIEnv *_env, jobject _this, jobject coords_buf) {
+    jarray _array = (jarray) 0;
+    jint _remaining;
+    GLfixed *coords = (GLfixed *) 0;
+
+    coords = (GLfixed *)getPointer(_env, coords_buf, &_array, &_remaining);
+    if (_remaining < 5) {
+        _env->ThrowNew(IAEClass, "remaining() < 5");
+        goto exit;
+    }
+    glDrawTexxvOES(
+        (GLfixed *)coords
+    );
+
+exit:
+    if (_array) {
+        releasePointer(_env, _array, coords, JNI_FALSE);
+    }
+}
+
+/* void glLoadPaletteFromModelViewMatrixOES ( void ) */
+static void
+android_glLoadPaletteFromModelViewMatrixOES__
+  (JNIEnv *_env, jobject _this) {
+    _env->ThrowNew(UOEClass,
+        "glLoadPaletteFromModelViewMatrixOES");
+}
+
+/* void glMatrixIndexPointerOES ( GLint size, GLenum type, GLsizei stride, const GLvoid *pointer ) */
+static void
+android_glMatrixIndexPointerOES__IIILjava_nio_Buffer_2
+  (JNIEnv *_env, jobject _this, jint size, jint type, jint stride, jobject pointer_buf) {
+    _env->ThrowNew(UOEClass,
+        "glMatrixIndexPointerOES");
+}
+
+/* void glMatrixIndexPointerOES ( GLint size, GLenum type, GLsizei stride, GLint offset ) */
+static void
+android_glMatrixIndexPointerOES__IIII
+  (JNIEnv *_env, jobject _this, jint size, jint type, jint stride, jint offset) {
+    _env->ThrowNew(UOEClass,
+        "glMatrixIndexPointerOES");
+}
+
+/* void glWeightPointerOES ( GLint size, GLenum type, GLsizei stride, const GLvoid *pointer ) */
+static void
+android_glWeightPointerOES__IIILjava_nio_Buffer_2
+  (JNIEnv *_env, jobject _this, jint size, jint type, jint stride, jobject pointer_buf) {
+    _env->ThrowNew(UOEClass,
+        "glWeightPointerOES");
+}
+
+/* void glWeightPointerOES ( GLint size, GLenum type, GLsizei stride, GLint offset ) */
+static void
+android_glWeightPointerOES__IIII
+  (JNIEnv *_env, jobject _this, jint size, jint type, jint stride, jint offset) {
+    _env->ThrowNew(UOEClass,
+        "glWeightPointerOES");
+}
+
+/* void glBindFramebufferOES ( GLint target, GLint framebuffer ) */
+static void
+android_glBindFramebufferOES__II
+  (JNIEnv *_env, jobject _this, jint target, jint framebuffer) {
+    _env->ThrowNew(UOEClass,
+        "glBindFramebufferOES");
+}
+
+/* void glBindRenderbufferOES ( GLint target, GLint renderbuffer ) */
+static void
+android_glBindRenderbufferOES__II
+  (JNIEnv *_env, jobject _this, jint target, jint renderbuffer) {
+    _env->ThrowNew(UOEClass,
+        "glBindRenderbufferOES");
+}
+
+/* void glBlendEquation ( GLint mode ) */
+static void
+android_glBlendEquation__I
+  (JNIEnv *_env, jobject _this, jint mode) {
+    _env->ThrowNew(UOEClass,
+        "glBlendEquation");
+}
+
+/* void glBlendEquationSeparate ( GLint modeRGB, GLint modeAlpha ) */
+static void
+android_glBlendEquationSeparate__II
+  (JNIEnv *_env, jobject _this, jint modeRGB, jint modeAlpha) {
+    _env->ThrowNew(UOEClass,
+        "glBlendEquationSeparate");
+}
+
+/* void glBlendFuncSeparate ( GLint srcRGB, GLint dstRGB, GLint srcAlpha, GLint dstAlpha ) */
+static void
+android_glBlendFuncSeparate__IIII
+  (JNIEnv *_env, jobject _this, jint srcRGB, jint dstRGB, jint srcAlpha, jint dstAlpha) {
+    _env->ThrowNew(UOEClass,
+        "glBlendFuncSeparate");
+}
+
+/* GLint glCheckFramebufferStatusOES ( GLint target ) */
+static jint
+android_glCheckFramebufferStatusOES__I
+  (JNIEnv *_env, jobject _this, jint target) {
+    _env->ThrowNew(UOEClass,
+        "glCheckFramebufferStatusOES");
+    return 0;
+}
+
+/* void glDeleteFramebuffersOES ( GLint n, GLint *framebuffers ) */
+static void
+android_glDeleteFramebuffersOES__I_3II
+  (JNIEnv *_env, jobject _this, jint n, jintArray framebuffers_ref, jint offset) {
+    _env->ThrowNew(UOEClass,
+        "glDeleteFramebuffersOES");
+}
+
+/* void glDeleteFramebuffersOES ( GLint n, GLint *framebuffers ) */
+static void
+android_glDeleteFramebuffersOES__ILjava_nio_IntBuffer_2
+  (JNIEnv *_env, jobject _this, jint n, jobject framebuffers_buf) {
+    _env->ThrowNew(UOEClass,
+        "glDeleteFramebuffersOES");
+}
+
+/* void glDeleteRenderbuffersOES ( GLint n, GLint *renderbuffers ) */
+static void
+android_glDeleteRenderbuffersOES__I_3II
+  (JNIEnv *_env, jobject _this, jint n, jintArray renderbuffers_ref, jint offset) {
+    _env->ThrowNew(UOEClass,
+        "glDeleteRenderbuffersOES");
+}
+
+/* void glDeleteRenderbuffersOES ( GLint n, GLint *renderbuffers ) */
+static void
+android_glDeleteRenderbuffersOES__ILjava_nio_IntBuffer_2
+  (JNIEnv *_env, jobject _this, jint n, jobject renderbuffers_buf) {
+    _env->ThrowNew(UOEClass,
+        "glDeleteRenderbuffersOES");
+}
+
+/* void glFramebufferRenderbufferOES ( GLint target, GLint attachment, GLint renderbuffertarget, GLint renderbuffer ) */
+static void
+android_glFramebufferRenderbufferOES__IIII
+  (JNIEnv *_env, jobject _this, jint target, jint attachment, jint renderbuffertarget, jint renderbuffer) {
+    _env->ThrowNew(UOEClass,
+        "glFramebufferRenderbufferOES");
+}
+
+/* void glFramebufferTexture2DOES ( GLint target, GLint attachment, GLint textarget, GLint texture, GLint level ) */
+static void
+android_glFramebufferTexture2DOES__IIIII
+  (JNIEnv *_env, jobject _this, jint target, jint attachment, jint textarget, jint texture, jint level) {
+    _env->ThrowNew(UOEClass,
+        "glFramebufferTexture2DOES");
+}
+
+/* void glGenerateMipmapOES ( GLint target ) */
+static void
+android_glGenerateMipmapOES__I
+  (JNIEnv *_env, jobject _this, jint target) {
+    _env->ThrowNew(UOEClass,
+        "glGenerateMipmapOES");
+}
+
+/* void glGenFramebuffersOES ( GLint n, GLint *framebuffers ) */
+static void
+android_glGenFramebuffersOES__I_3II
+  (JNIEnv *_env, jobject _this, jint n, jintArray framebuffers_ref, jint offset) {
+    _env->ThrowNew(UOEClass,
+        "glGenFramebuffersOES");
+}
+
+/* void glGenFramebuffersOES ( GLint n, GLint *framebuffers ) */
+static void
+android_glGenFramebuffersOES__ILjava_nio_IntBuffer_2
+  (JNIEnv *_env, jobject _this, jint n, jobject framebuffers_buf) {
+    _env->ThrowNew(UOEClass,
+        "glGenFramebuffersOES");
+}
+
+/* void glGenRenderbuffersOES ( GLint n, GLint *renderbuffers ) */
+static void
+android_glGenRenderbuffersOES__I_3II
+  (JNIEnv *_env, jobject _this, jint n, jintArray renderbuffers_ref, jint offset) {
+    _env->ThrowNew(UOEClass,
+        "glGenRenderbuffersOES");
+}
+
+/* void glGenRenderbuffersOES ( GLint n, GLint *renderbuffers ) */
+static void
+android_glGenRenderbuffersOES__ILjava_nio_IntBuffer_2
+  (JNIEnv *_env, jobject _this, jint n, jobject renderbuffers_buf) {
+    _env->ThrowNew(UOEClass,
+        "glGenRenderbuffersOES");
+}
+
+/* void glGetFramebufferAttachmentParameterivOES ( GLint target, GLint attachment, GLint pname, GLint *params ) */
+static void
+android_glGetFramebufferAttachmentParameterivOES__III_3II
+  (JNIEnv *_env, jobject _this, jint target, jint attachment, jint pname, jintArray params_ref, jint offset) {
+    _env->ThrowNew(UOEClass,
+        "glGetFramebufferAttachmentParameterivOES");
+}
+
+/* void glGetFramebufferAttachmentParameterivOES ( GLint target, GLint attachment, GLint pname, GLint *params ) */
+static void
+android_glGetFramebufferAttachmentParameterivOES__IIILjava_nio_IntBuffer_2
+  (JNIEnv *_env, jobject _this, jint target, jint attachment, jint pname, jobject params_buf) {
+    _env->ThrowNew(UOEClass,
+        "glGetFramebufferAttachmentParameterivOES");
+}
+
+/* void glGetRenderbufferParameterivOES ( GLint target, GLint pname, GLint *params ) */
+static void
+android_glGetRenderbufferParameterivOES__II_3II
+  (JNIEnv *_env, jobject _this, jint target, jint pname, jintArray params_ref, jint offset) {
+    _env->ThrowNew(UOEClass,
+        "glGetRenderbufferParameterivOES");
+}
+
+/* void glGetRenderbufferParameterivOES ( GLint target, GLint pname, GLint *params ) */
+static void
+android_glGetRenderbufferParameterivOES__IILjava_nio_IntBuffer_2
+  (JNIEnv *_env, jobject _this, jint target, jint pname, jobject params_buf) {
+    _env->ThrowNew(UOEClass,
+        "glGetRenderbufferParameterivOES");
+}
+
+/* void glGetTexGenfv ( GLint coord, GLint pname, GLfloat *params ) */
+static void
+android_glGetTexGenfv__II_3FI
+  (JNIEnv *_env, jobject _this, jint coord, jint pname, jfloatArray params_ref, jint offset) {
+    _env->ThrowNew(UOEClass,
+        "glGetTexGenfv");
+}
+
+/* void glGetTexGenfv ( GLint coord, GLint pname, GLfloat *params ) */
+static void
+android_glGetTexGenfv__IILjava_nio_FloatBuffer_2
+  (JNIEnv *_env, jobject _this, jint coord, jint pname, jobject params_buf) {
+    _env->ThrowNew(UOEClass,
+        "glGetTexGenfv");
+}
+
+/* void glGetTexGeniv ( GLint coord, GLint pname, GLint *params ) */
+static void
+android_glGetTexGeniv__II_3II
+  (JNIEnv *_env, jobject _this, jint coord, jint pname, jintArray params_ref, jint offset) {
+    _env->ThrowNew(UOEClass,
+        "glGetTexGeniv");
+}
+
+/* void glGetTexGeniv ( GLint coord, GLint pname, GLint *params ) */
+static void
+android_glGetTexGeniv__IILjava_nio_IntBuffer_2
+  (JNIEnv *_env, jobject _this, jint coord, jint pname, jobject params_buf) {
+    _env->ThrowNew(UOEClass,
+        "glGetTexGeniv");
+}
+
+/* void glGetTexGenxv ( GLint coord, GLint pname, GLint *params ) */
+static void
+android_glGetTexGenxv__II_3II
+  (JNIEnv *_env, jobject _this, jint coord, jint pname, jintArray params_ref, jint offset) {
+    _env->ThrowNew(UOEClass,
+        "glGetTexGenxv");
+}
+
+/* void glGetTexGenxv ( GLint coord, GLint pname, GLint *params ) */
+static void
+android_glGetTexGenxv__IILjava_nio_IntBuffer_2
+  (JNIEnv *_env, jobject _this, jint coord, jint pname, jobject params_buf) {
+    _env->ThrowNew(UOEClass,
+        "glGetTexGenxv");
+}
+
+/* GLboolean glIsFramebufferOES ( GLint framebuffer ) */
+static jboolean
+android_glIsFramebufferOES__I
+  (JNIEnv *_env, jobject _this, jint framebuffer) {
+    _env->ThrowNew(UOEClass,
+        "glIsFramebufferOES");
+    return JNI_FALSE;
+}
+
+/* GLboolean glIsRenderbufferOES ( GLint renderbuffer ) */
+static jboolean
+android_glIsRenderbufferOES__I
+  (JNIEnv *_env, jobject _this, jint renderbuffer) {
+    _env->ThrowNew(UOEClass,
+        "glIsRenderbufferOES");
+    return JNI_FALSE;
+}
+
+/* void glRenderbufferStorageOES ( GLint target, GLint internalformat, GLint width, GLint height ) */
+static void
+android_glRenderbufferStorageOES__IIII
+  (JNIEnv *_env, jobject _this, jint target, jint internalformat, jint width, jint height) {
+    _env->ThrowNew(UOEClass,
+        "glRenderbufferStorageOES");
+}
+
+/* void glTexGenf ( GLint coord, GLint pname, GLfloat param ) */
+static void
+android_glTexGenf__IIF
+  (JNIEnv *_env, jobject _this, jint coord, jint pname, jfloat param) {
+    _env->ThrowNew(UOEClass,
+        "glTexGenf");
+}
+
+/* void glTexGenfv ( GLint coord, GLint pname, GLfloat *params ) */
+static void
+android_glTexGenfv__II_3FI
+  (JNIEnv *_env, jobject _this, jint coord, jint pname, jfloatArray params_ref, jint offset) {
+    _env->ThrowNew(UOEClass,
+        "glTexGenfv");
+}
+
+/* void glTexGenfv ( GLint coord, GLint pname, GLfloat *params ) */
+static void
+android_glTexGenfv__IILjava_nio_FloatBuffer_2
+  (JNIEnv *_env, jobject _this, jint coord, jint pname, jobject params_buf) {
+    _env->ThrowNew(UOEClass,
+        "glTexGenfv");
+}
+
+/* void glTexGeni ( GLint coord, GLint pname, GLint param ) */
+static void
+android_glTexGeni__III
+  (JNIEnv *_env, jobject _this, jint coord, jint pname, jint param) {
+    _env->ThrowNew(UOEClass,
+        "glTexGeni");
+}
+
+/* void glTexGeniv ( GLint coord, GLint pname, GLint *params ) */
+static void
+android_glTexGeniv__II_3II
+  (JNIEnv *_env, jobject _this, jint coord, jint pname, jintArray params_ref, jint offset) {
+    _env->ThrowNew(UOEClass,
+        "glTexGeniv");
+}
+
+/* void glTexGeniv ( GLint coord, GLint pname, GLint *params ) */
+static void
+android_glTexGeniv__IILjava_nio_IntBuffer_2
+  (JNIEnv *_env, jobject _this, jint coord, jint pname, jobject params_buf) {
+    _env->ThrowNew(UOEClass,
+        "glTexGeniv");
+}
+
+/* void glTexGenx ( GLint coord, GLint pname, GLint param ) */
+static void
+android_glTexGenx__III
+  (JNIEnv *_env, jobject _this, jint coord, jint pname, jint param) {
+    _env->ThrowNew(UOEClass,
+        "glTexGenx");
+}
+
+/* void glTexGenxv ( GLint coord, GLint pname, GLint *params ) */
+static void
+android_glTexGenxv__II_3II
+  (JNIEnv *_env, jobject _this, jint coord, jint pname, jintArray params_ref, jint offset) {
+    _env->ThrowNew(UOEClass,
+        "glTexGenxv");
+}
+
+/* void glTexGenxv ( GLint coord, GLint pname, GLint *params ) */
+static void
+android_glTexGenxv__IILjava_nio_IntBuffer_2
+  (JNIEnv *_env, jobject _this, jint coord, jint pname, jobject params_buf) {
+    _env->ThrowNew(UOEClass,
+        "glTexGenxv");
+}
+
+static const char *classPathName = "com/google/android/gles_jni/GLImpl";
+
+static JNINativeMethod methods[] = {
+{"_nativeClassInit", "()V", (void*)nativeClassInit },
+{"glActiveTexture", "(I)V", (void *) android_glActiveTexture__I },
+{"glAlphaFunc", "(IF)V", (void *) android_glAlphaFunc__IF },
+{"glAlphaFuncx", "(II)V", (void *) android_glAlphaFuncx__II },
+{"glBindTexture", "(II)V", (void *) android_glBindTexture__II },
+{"glBlendFunc", "(II)V", (void *) android_glBlendFunc__II },
+{"glClear", "(I)V", (void *) android_glClear__I },
+{"glClearColor", "(FFFF)V", (void *) android_glClearColor__FFFF },
+{"glClearColorx", "(IIII)V", (void *) android_glClearColorx__IIII },
+{"glClearDepthf", "(F)V", (void *) android_glClearDepthf__F },
+{"glClearDepthx", "(I)V", (void *) android_glClearDepthx__I },
+{"glClearStencil", "(I)V", (void *) android_glClearStencil__I },
+{"glClientActiveTexture", "(I)V", (void *) android_glClientActiveTexture__I },
+{"glColor4f", "(FFFF)V", (void *) android_glColor4f__FFFF },
+{"glColor4x", "(IIII)V", (void *) android_glColor4x__IIII },
+{"glColorMask", "(ZZZZ)V", (void *) android_glColorMask__ZZZZ },
+{"glColorPointerBounds", "(IIILjava/nio/Buffer;I)V", (void *) android_glColorPointerBounds__IIILjava_nio_Buffer_2I },
+{"glCompressedTexImage2D", "(IIIIIIILjava/nio/Buffer;)V", (void *) android_glCompressedTexImage2D__IIIIIIILjava_nio_Buffer_2 },
+{"glCompressedTexSubImage2D", "(IIIIIIIILjava/nio/Buffer;)V", (void *) android_glCompressedTexSubImage2D__IIIIIIIILjava_nio_Buffer_2 },
+{"glCopyTexImage2D", "(IIIIIIII)V", (void *) android_glCopyTexImage2D__IIIIIIII },
+{"glCopyTexSubImage2D", "(IIIIIIII)V", (void *) android_glCopyTexSubImage2D__IIIIIIII },
+{"glCullFace", "(I)V", (void *) android_glCullFace__I },
+{"glDeleteTextures", "(I[II)V", (void *) android_glDeleteTextures__I_3II },
+{"glDeleteTextures", "(ILjava/nio/IntBuffer;)V", (void *) android_glDeleteTextures__ILjava_nio_IntBuffer_2 },
+{"glDepthFunc", "(I)V", (void *) android_glDepthFunc__I },
+{"glDepthMask", "(Z)V", (void *) android_glDepthMask__Z },
+{"glDepthRangef", "(FF)V", (void *) android_glDepthRangef__FF },
+{"glDepthRangex", "(II)V", (void *) android_glDepthRangex__II },
+{"glDisable", "(I)V", (void *) android_glDisable__I },
+{"glDisableClientState", "(I)V", (void *) android_glDisableClientState__I },
+{"glDrawArrays", "(III)V", (void *) android_glDrawArrays__III },
+{"glDrawElements", "(IIILjava/nio/Buffer;)V", (void *) android_glDrawElements__IIILjava_nio_Buffer_2 },
+{"glEnable", "(I)V", (void *) android_glEnable__I },
+{"glEnableClientState", "(I)V", (void *) android_glEnableClientState__I },
+{"glFinish", "()V", (void *) android_glFinish__ },
+{"glFlush", "()V", (void *) android_glFlush__ },
+{"glFogf", "(IF)V", (void *) android_glFogf__IF },
+{"glFogfv", "(I[FI)V", (void *) android_glFogfv__I_3FI },
+{"glFogfv", "(ILjava/nio/FloatBuffer;)V", (void *) android_glFogfv__ILjava_nio_FloatBuffer_2 },
+{"glFogx", "(II)V", (void *) android_glFogx__II },
+{"glFogxv", "(I[II)V", (void *) android_glFogxv__I_3II },
+{"glFogxv", "(ILjava/nio/IntBuffer;)V", (void *) android_glFogxv__ILjava_nio_IntBuffer_2 },
+{"glFrontFace", "(I)V", (void *) android_glFrontFace__I },
+{"glFrustumf", "(FFFFFF)V", (void *) android_glFrustumf__FFFFFF },
+{"glFrustumx", "(IIIIII)V", (void *) android_glFrustumx__IIIIII },
+{"glGenTextures", "(I[II)V", (void *) android_glGenTextures__I_3II },
+{"glGenTextures", "(ILjava/nio/IntBuffer;)V", (void *) android_glGenTextures__ILjava_nio_IntBuffer_2 },
+{"glGetError", "()I", (void *) android_glGetError__ },
+{"glGetIntegerv", "(I[II)V", (void *) android_glGetIntegerv__I_3II },
+{"glGetIntegerv", "(ILjava/nio/IntBuffer;)V", (void *) android_glGetIntegerv__ILjava_nio_IntBuffer_2 },
+{"_glGetString", "(I)Ljava/lang/String;", (void *) android_glGetString },
+{"glHint", "(II)V", (void *) android_glHint__II },
+{"glLightModelf", "(IF)V", (void *) android_glLightModelf__IF },
+{"glLightModelfv", "(I[FI)V", (void *) android_glLightModelfv__I_3FI },
+{"glLightModelfv", "(ILjava/nio/FloatBuffer;)V", (void *) android_glLightModelfv__ILjava_nio_FloatBuffer_2 },
+{"glLightModelx", "(II)V", (void *) android_glLightModelx__II },
+{"glLightModelxv", "(I[II)V", (void *) android_glLightModelxv__I_3II },
+{"glLightModelxv", "(ILjava/nio/IntBuffer;)V", (void *) android_glLightModelxv__ILjava_nio_IntBuffer_2 },
+{"glLightf", "(IIF)V", (void *) android_glLightf__IIF },
+{"glLightfv", "(II[FI)V", (void *) android_glLightfv__II_3FI },
+{"glLightfv", "(IILjava/nio/FloatBuffer;)V", (void *) android_glLightfv__IILjava_nio_FloatBuffer_2 },
+{"glLightx", "(III)V", (void *) android_glLightx__III },
+{"glLightxv", "(II[II)V", (void *) android_glLightxv__II_3II },
+{"glLightxv", "(IILjava/nio/IntBuffer;)V", (void *) android_glLightxv__IILjava_nio_IntBuffer_2 },
+{"glLineWidth", "(F)V", (void *) android_glLineWidth__F },
+{"glLineWidthx", "(I)V", (void *) android_glLineWidthx__I },
+{"glLoadIdentity", "()V", (void *) android_glLoadIdentity__ },
+{"glLoadMatrixf", "([FI)V", (void *) android_glLoadMatrixf___3FI },
+{"glLoadMatrixf", "(Ljava/nio/FloatBuffer;)V", (void *) android_glLoadMatrixf__Ljava_nio_FloatBuffer_2 },
+{"glLoadMatrixx", "([II)V", (void *) android_glLoadMatrixx___3II },
+{"glLoadMatrixx", "(Ljava/nio/IntBuffer;)V", (void *) android_glLoadMatrixx__Ljava_nio_IntBuffer_2 },
+{"glLogicOp", "(I)V", (void *) android_glLogicOp__I },
+{"glMaterialf", "(IIF)V", (void *) android_glMaterialf__IIF },
+{"glMaterialfv", "(II[FI)V", (void *) android_glMaterialfv__II_3FI },
+{"glMaterialfv", "(IILjava/nio/FloatBuffer;)V", (void *) android_glMaterialfv__IILjava_nio_FloatBuffer_2 },
+{"glMaterialx", "(III)V", (void *) android_glMaterialx__III },
+{"glMaterialxv", "(II[II)V", (void *) android_glMaterialxv__II_3II },
+{"glMaterialxv", "(IILjava/nio/IntBuffer;)V", (void *) android_glMaterialxv__IILjava_nio_IntBuffer_2 },
+{"glMatrixMode", "(I)V", (void *) android_glMatrixMode__I },
+{"glMultMatrixf", "([FI)V", (void *) android_glMultMatrixf___3FI },
+{"glMultMatrixf", "(Ljava/nio/FloatBuffer;)V", (void *) android_glMultMatrixf__Ljava_nio_FloatBuffer_2 },
+{"glMultMatrixx", "([II)V", (void *) android_glMultMatrixx___3II },
+{"glMultMatrixx", "(Ljava/nio/IntBuffer;)V", (void *) android_glMultMatrixx__Ljava_nio_IntBuffer_2 },
+{"glMultiTexCoord4f", "(IFFFF)V", (void *) android_glMultiTexCoord4f__IFFFF },
+{"glMultiTexCoord4x", "(IIIII)V", (void *) android_glMultiTexCoord4x__IIIII },
+{"glNormal3f", "(FFF)V", (void *) android_glNormal3f__FFF },
+{"glNormal3x", "(III)V", (void *) android_glNormal3x__III },
+{"glNormalPointerBounds", "(IILjava/nio/Buffer;I)V", (void *) android_glNormalPointerBounds__IILjava_nio_Buffer_2I },
+{"glOrthof", "(FFFFFF)V", (void *) android_glOrthof__FFFFFF },
+{"glOrthox", "(IIIIII)V", (void *) android_glOrthox__IIIIII },
+{"glPixelStorei", "(II)V", (void *) android_glPixelStorei__II },
+{"glPointSize", "(F)V", (void *) android_glPointSize__F },
+{"glPointSizex", "(I)V", (void *) android_glPointSizex__I },
+{"glPolygonOffset", "(FF)V", (void *) android_glPolygonOffset__FF },
+{"glPolygonOffsetx", "(II)V", (void *) android_glPolygonOffsetx__II },
+{"glPopMatrix", "()V", (void *) android_glPopMatrix__ },
+{"glPushMatrix", "()V", (void *) android_glPushMatrix__ },
+{"glReadPixels", "(IIIIIILjava/nio/Buffer;)V", (void *) android_glReadPixels__IIIIIILjava_nio_Buffer_2 },
+{"glRotatef", "(FFFF)V", (void *) android_glRotatef__FFFF },
+{"glRotatex", "(IIII)V", (void *) android_glRotatex__IIII },
+{"glSampleCoverage", "(FZ)V", (void *) android_glSampleCoverage__FZ },
+{"glSampleCoveragex", "(IZ)V", (void *) android_glSampleCoveragex__IZ },
+{"glScalef", "(FFF)V", (void *) android_glScalef__FFF },
+{"glScalex", "(III)V", (void *) android_glScalex__III },
+{"glScissor", "(IIII)V", (void *) android_glScissor__IIII },
+{"glShadeModel", "(I)V", (void *) android_glShadeModel__I },
+{"glStencilFunc", "(III)V", (void *) android_glStencilFunc__III },
+{"glStencilMask", "(I)V", (void *) android_glStencilMask__I },
+{"glStencilOp", "(III)V", (void *) android_glStencilOp__III },
+{"glTexCoordPointerBounds", "(IIILjava/nio/Buffer;I)V", (void *) android_glTexCoordPointerBounds__IIILjava_nio_Buffer_2I },
+{"glTexEnvf", "(IIF)V", (void *) android_glTexEnvf__IIF },
+{"glTexEnvfv", "(II[FI)V", (void *) android_glTexEnvfv__II_3FI },
+{"glTexEnvfv", "(IILjava/nio/FloatBuffer;)V", (void *) android_glTexEnvfv__IILjava_nio_FloatBuffer_2 },
+{"glTexEnvx", "(III)V", (void *) android_glTexEnvx__III },
+{"glTexEnvxv", "(II[II)V", (void *) android_glTexEnvxv__II_3II },
+{"glTexEnvxv", "(IILjava/nio/IntBuffer;)V", (void *) android_glTexEnvxv__IILjava_nio_IntBuffer_2 },
+{"glTexImage2D", "(IIIIIIIILjava/nio/Buffer;)V", (void *) android_glTexImage2D__IIIIIIIILjava_nio_Buffer_2 },
+{"glTexParameterf", "(IIF)V", (void *) android_glTexParameterf__IIF },
+{"glTexParameterx", "(III)V", (void *) android_glTexParameterx__III },
+{"glTexSubImage2D", "(IIIIIIIILjava/nio/Buffer;)V", (void *) android_glTexSubImage2D__IIIIIIIILjava_nio_Buffer_2 },
+{"glTranslatef", "(FFF)V", (void *) android_glTranslatef__FFF },
+{"glTranslatex", "(III)V", (void *) android_glTranslatex__III },
+{"glVertexPointerBounds", "(IIILjava/nio/Buffer;I)V", (void *) android_glVertexPointerBounds__IIILjava_nio_Buffer_2I },
+{"glViewport", "(IIII)V", (void *) android_glViewport__IIII },
+{"glQueryMatrixxOES", "([II[II)I", (void *) android_glQueryMatrixxOES___3II_3II },
+{"glQueryMatrixxOES", "(Ljava/nio/IntBuffer;Ljava/nio/IntBuffer;)I", (void *) android_glQueryMatrixxOES__Ljava_nio_IntBuffer_2Ljava_nio_IntBuffer_2 },
+{"glBindBuffer", "(II)V", (void *) android_glBindBuffer__II },
+{"glBufferData", "(IILjava/nio/Buffer;I)V", (void *) android_glBufferData__IILjava_nio_Buffer_2I },
+{"glBufferSubData", "(IIILjava/nio/Buffer;)V", (void *) android_glBufferSubData__IIILjava_nio_Buffer_2 },
+{"glClipPlanef", "(I[FI)V", (void *) android_glClipPlanef__I_3FI },
+{"glClipPlanef", "(ILjava/nio/FloatBuffer;)V", (void *) android_glClipPlanef__ILjava_nio_FloatBuffer_2 },
+{"glClipPlanex", "(I[II)V", (void *) android_glClipPlanex__I_3II },
+{"glClipPlanex", "(ILjava/nio/IntBuffer;)V", (void *) android_glClipPlanex__ILjava_nio_IntBuffer_2 },
+{"glColor4ub", "(BBBB)V", (void *) android_glColor4ub__BBBB },
+{"glColorPointer", "(IIII)V", (void *) android_glColorPointer__IIII },
+{"glDeleteBuffers", "(I[II)V", (void *) android_glDeleteBuffers__I_3II },
+{"glDeleteBuffers", "(ILjava/nio/IntBuffer;)V", (void *) android_glDeleteBuffers__ILjava_nio_IntBuffer_2 },
+{"glDrawElements", "(IIII)V", (void *) android_glDrawElements__IIII },
+{"glGenBuffers", "(I[II)V", (void *) android_glGenBuffers__I_3II },
+{"glGenBuffers", "(ILjava/nio/IntBuffer;)V", (void *) android_glGenBuffers__ILjava_nio_IntBuffer_2 },
+{"glGetBooleanv", "(I[ZI)V", (void *) android_glGetBooleanv__I_3ZI },
+{"glGetBooleanv", "(ILjava/nio/IntBuffer;)V", (void *) android_glGetBooleanv__ILjava_nio_IntBuffer_2 },
+{"glGetBufferParameteriv", "(II[II)V", (void *) android_glGetBufferParameteriv__II_3II },
+{"glGetBufferParameteriv", "(IILjava/nio/IntBuffer;)V", (void *) android_glGetBufferParameteriv__IILjava_nio_IntBuffer_2 },
+{"glGetClipPlanef", "(I[FI)V", (void *) android_glGetClipPlanef__I_3FI },
+{"glGetClipPlanef", "(ILjava/nio/FloatBuffer;)V", (void *) android_glGetClipPlanef__ILjava_nio_FloatBuffer_2 },
+{"glGetClipPlanex", "(I[II)V", (void *) android_glGetClipPlanex__I_3II },
+{"glGetClipPlanex", "(ILjava/nio/IntBuffer;)V", (void *) android_glGetClipPlanex__ILjava_nio_IntBuffer_2 },
+{"glGetFixedv", "(I[II)V", (void *) android_glGetFixedv__I_3II },
+{"glGetFixedv", "(ILjava/nio/IntBuffer;)V", (void *) android_glGetFixedv__ILjava_nio_IntBuffer_2 },
+{"glGetFloatv", "(I[FI)V", (void *) android_glGetFloatv__I_3FI },
+{"glGetFloatv", "(ILjava/nio/FloatBuffer;)V", (void *) android_glGetFloatv__ILjava_nio_FloatBuffer_2 },
+{"glGetLightfv", "(II[FI)V", (void *) android_glGetLightfv__II_3FI },
+{"glGetLightfv", "(IILjava/nio/FloatBuffer;)V", (void *) android_glGetLightfv__IILjava_nio_FloatBuffer_2 },
+{"glGetLightxv", "(II[II)V", (void *) android_glGetLightxv__II_3II },
+{"glGetLightxv", "(IILjava/nio/IntBuffer;)V", (void *) android_glGetLightxv__IILjava_nio_IntBuffer_2 },
+{"glGetMaterialfv", "(II[FI)V", (void *) android_glGetMaterialfv__II_3FI },
+{"glGetMaterialfv", "(IILjava/nio/FloatBuffer;)V", (void *) android_glGetMaterialfv__IILjava_nio_FloatBuffer_2 },
+{"glGetMaterialxv", "(II[II)V", (void *) android_glGetMaterialxv__II_3II },
+{"glGetMaterialxv", "(IILjava/nio/IntBuffer;)V", (void *) android_glGetMaterialxv__IILjava_nio_IntBuffer_2 },
+{"glGetTexEnviv", "(II[II)V", (void *) android_glGetTexEnviv__II_3II },
+{"glGetTexEnviv", "(IILjava/nio/IntBuffer;)V", (void *) android_glGetTexEnviv__IILjava_nio_IntBuffer_2 },
+{"glGetTexEnvxv", "(II[II)V", (void *) android_glGetTexEnvxv__II_3II },
+{"glGetTexEnvxv", "(IILjava/nio/IntBuffer;)V", (void *) android_glGetTexEnvxv__IILjava_nio_IntBuffer_2 },
+{"glGetTexParameterfv", "(II[FI)V", (void *) android_glGetTexParameterfv__II_3FI },
+{"glGetTexParameterfv", "(IILjava/nio/FloatBuffer;)V", (void *) android_glGetTexParameterfv__IILjava_nio_FloatBuffer_2 },
+{"glGetTexParameteriv", "(II[II)V", (void *) android_glGetTexParameteriv__II_3II },
+{"glGetTexParameteriv", "(IILjava/nio/IntBuffer;)V", (void *) android_glGetTexParameteriv__IILjava_nio_IntBuffer_2 },
+{"glGetTexParameterxv", "(II[II)V", (void *) android_glGetTexParameterxv__II_3II },
+{"glGetTexParameterxv", "(IILjava/nio/IntBuffer;)V", (void *) android_glGetTexParameterxv__IILjava_nio_IntBuffer_2 },
+{"glIsBuffer", "(I)Z", (void *) android_glIsBuffer__I },
+{"glIsEnabled", "(I)Z", (void *) android_glIsEnabled__I },
+{"glIsTexture", "(I)Z", (void *) android_glIsTexture__I },
+{"glNormalPointer", "(III)V", (void *) android_glNormalPointer__III },
+{"glPointParameterf", "(IF)V", (void *) android_glPointParameterf__IF },
+{"glPointParameterfv", "(I[FI)V", (void *) android_glPointParameterfv__I_3FI },
+{"glPointParameterfv", "(ILjava/nio/FloatBuffer;)V", (void *) android_glPointParameterfv__ILjava_nio_FloatBuffer_2 },
+{"glPointParameterx", "(II)V", (void *) android_glPointParameterx__II },
+{"glPointParameterxv", "(I[II)V", (void *) android_glPointParameterxv__I_3II },
+{"glPointParameterxv", "(ILjava/nio/IntBuffer;)V", (void *) android_glPointParameterxv__ILjava_nio_IntBuffer_2 },
+{"glPointSizePointerOES", "(IILjava/nio/Buffer;)V", (void *) android_glPointSizePointerOES__IILjava_nio_Buffer_2 },
+{"glTexCoordPointer", "(IIII)V", (void *) android_glTexCoordPointer__IIII },
+{"glTexEnvi", "(III)V", (void *) android_glTexEnvi__III },
+{"glTexEnviv", "(II[II)V", (void *) android_glTexEnviv__II_3II },
+{"glTexEnviv", "(IILjava/nio/IntBuffer;)V", (void *) android_glTexEnviv__IILjava_nio_IntBuffer_2 },
+{"glTexParameterfv", "(II[FI)V", (void *) android_glTexParameterfv__II_3FI },
+{"glTexParameterfv", "(IILjava/nio/FloatBuffer;)V", (void *) android_glTexParameterfv__IILjava_nio_FloatBuffer_2 },
+{"glTexParameteri", "(III)V", (void *) android_glTexParameteri__III },
+{"glTexParameteriv", "(II[II)V", (void *) android_glTexParameteriv__II_3II },
+{"glTexParameteriv", "(IILjava/nio/IntBuffer;)V", (void *) android_glTexParameteriv__IILjava_nio_IntBuffer_2 },
+{"glTexParameterxv", "(II[II)V", (void *) android_glTexParameterxv__II_3II },
+{"glTexParameterxv", "(IILjava/nio/IntBuffer;)V", (void *) android_glTexParameterxv__IILjava_nio_IntBuffer_2 },
+{"glVertexPointer", "(IIII)V", (void *) android_glVertexPointer__IIII },
+{"glCurrentPaletteMatrixOES", "(I)V", (void *) android_glCurrentPaletteMatrixOES__I },
+{"glDrawTexfOES", "(FFFFF)V", (void *) android_glDrawTexfOES__FFFFF },
+{"glDrawTexfvOES", "([FI)V", (void *) android_glDrawTexfvOES___3FI },
+{"glDrawTexfvOES", "(Ljava/nio/FloatBuffer;)V", (void *) android_glDrawTexfvOES__Ljava_nio_FloatBuffer_2 },
+{"glDrawTexiOES", "(IIIII)V", (void *) android_glDrawTexiOES__IIIII },
+{"glDrawTexivOES", "([II)V", (void *) android_glDrawTexivOES___3II },
+{"glDrawTexivOES", "(Ljava/nio/IntBuffer;)V", (void *) android_glDrawTexivOES__Ljava_nio_IntBuffer_2 },
+{"glDrawTexsOES", "(SSSSS)V", (void *) android_glDrawTexsOES__SSSSS },
+{"glDrawTexsvOES", "([SI)V", (void *) android_glDrawTexsvOES___3SI },
+{"glDrawTexsvOES", "(Ljava/nio/ShortBuffer;)V", (void *) android_glDrawTexsvOES__Ljava_nio_ShortBuffer_2 },
+{"glDrawTexxOES", "(IIIII)V", (void *) android_glDrawTexxOES__IIIII },
+{"glDrawTexxvOES", "([II)V", (void *) android_glDrawTexxvOES___3II },
+{"glDrawTexxvOES", "(Ljava/nio/IntBuffer;)V", (void *) android_glDrawTexxvOES__Ljava_nio_IntBuffer_2 },
+{"glLoadPaletteFromModelViewMatrixOES", "()V", (void *) android_glLoadPaletteFromModelViewMatrixOES__ },
+{"glMatrixIndexPointerOES", "(IIILjava/nio/Buffer;)V", (void *) android_glMatrixIndexPointerOES__IIILjava_nio_Buffer_2 },
+{"glMatrixIndexPointerOES", "(IIII)V", (void *) android_glMatrixIndexPointerOES__IIII },
+{"glWeightPointerOES", "(IIILjava/nio/Buffer;)V", (void *) android_glWeightPointerOES__IIILjava_nio_Buffer_2 },
+{"glWeightPointerOES", "(IIII)V", (void *) android_glWeightPointerOES__IIII },
+{"glBindFramebufferOES", "(II)V", (void *) android_glBindFramebufferOES__II },
+{"glBindRenderbufferOES", "(II)V", (void *) android_glBindRenderbufferOES__II },
+{"glBlendEquation", "(I)V", (void *) android_glBlendEquation__I },
+{"glBlendEquationSeparate", "(II)V", (void *) android_glBlendEquationSeparate__II },
+{"glBlendFuncSeparate", "(IIII)V", (void *) android_glBlendFuncSeparate__IIII },
+{"glCheckFramebufferStatusOES", "(I)I", (void *) android_glCheckFramebufferStatusOES__I },
+{"glDeleteFramebuffersOES", "(I[II)V", (void *) android_glDeleteFramebuffersOES__I_3II },
+{"glDeleteFramebuffersOES", "(ILjava/nio/IntBuffer;)V", (void *) android_glDeleteFramebuffersOES__ILjava_nio_IntBuffer_2 },
+{"glDeleteRenderbuffersOES", "(I[II)V", (void *) android_glDeleteRenderbuffersOES__I_3II },
+{"glDeleteRenderbuffersOES", "(ILjava/nio/IntBuffer;)V", (void *) android_glDeleteRenderbuffersOES__ILjava_nio_IntBuffer_2 },
+{"glFramebufferRenderbufferOES", "(IIII)V", (void *) android_glFramebufferRenderbufferOES__IIII },
+{"glFramebufferTexture2DOES", "(IIIII)V", (void *) android_glFramebufferTexture2DOES__IIIII },
+{"glGenerateMipmapOES", "(I)V", (void *) android_glGenerateMipmapOES__I },
+{"glGenFramebuffersOES", "(I[II)V", (void *) android_glGenFramebuffersOES__I_3II },
+{"glGenFramebuffersOES", "(ILjava/nio/IntBuffer;)V", (void *) android_glGenFramebuffersOES__ILjava_nio_IntBuffer_2 },
+{"glGenRenderbuffersOES", "(I[II)V", (void *) android_glGenRenderbuffersOES__I_3II },
+{"glGenRenderbuffersOES", "(ILjava/nio/IntBuffer;)V", (void *) android_glGenRenderbuffersOES__ILjava_nio_IntBuffer_2 },
+{"glGetFramebufferAttachmentParameterivOES", "(III[II)V", (void *) android_glGetFramebufferAttachmentParameterivOES__III_3II },
+{"glGetFramebufferAttachmentParameterivOES", "(IIILjava/nio/IntBuffer;)V", (void *) android_glGetFramebufferAttachmentParameterivOES__IIILjava_nio_IntBuffer_2 },
+{"glGetRenderbufferParameterivOES", "(II[II)V", (void *) android_glGetRenderbufferParameterivOES__II_3II },
+{"glGetRenderbufferParameterivOES", "(IILjava/nio/IntBuffer;)V", (void *) android_glGetRenderbufferParameterivOES__IILjava_nio_IntBuffer_2 },
+{"glGetTexGenfv", "(II[FI)V", (void *) android_glGetTexGenfv__II_3FI },
+{"glGetTexGenfv", "(IILjava/nio/FloatBuffer;)V", (void *) android_glGetTexGenfv__IILjava_nio_FloatBuffer_2 },
+{"glGetTexGeniv", "(II[II)V", (void *) android_glGetTexGeniv__II_3II },
+{"glGetTexGeniv", "(IILjava/nio/IntBuffer;)V", (void *) android_glGetTexGeniv__IILjava_nio_IntBuffer_2 },
+{"glGetTexGenxv", "(II[II)V", (void *) android_glGetTexGenxv__II_3II },
+{"glGetTexGenxv", "(IILjava/nio/IntBuffer;)V", (void *) android_glGetTexGenxv__IILjava_nio_IntBuffer_2 },
+{"glIsFramebufferOES", "(I)Z", (void *) android_glIsFramebufferOES__I },
+{"glIsRenderbufferOES", "(I)Z", (void *) android_glIsRenderbufferOES__I },
+{"glRenderbufferStorageOES", "(IIII)V", (void *) android_glRenderbufferStorageOES__IIII },
+{"glTexGenf", "(IIF)V", (void *) android_glTexGenf__IIF },
+{"glTexGenfv", "(II[FI)V", (void *) android_glTexGenfv__II_3FI },
+{"glTexGenfv", "(IILjava/nio/FloatBuffer;)V", (void *) android_glTexGenfv__IILjava_nio_FloatBuffer_2 },
+{"glTexGeni", "(III)V", (void *) android_glTexGeni__III },
+{"glTexGeniv", "(II[II)V", (void *) android_glTexGeniv__II_3II },
+{"glTexGeniv", "(IILjava/nio/IntBuffer;)V", (void *) android_glTexGeniv__IILjava_nio_IntBuffer_2 },
+{"glTexGenx", "(III)V", (void *) android_glTexGenx__III },
+{"glTexGenxv", "(II[II)V", (void *) android_glTexGenxv__II_3II },
+{"glTexGenxv", "(IILjava/nio/IntBuffer;)V", (void *) android_glTexGenxv__IILjava_nio_IntBuffer_2 },
+};
+
+int register_com_google_android_gles_jni_GLImpl(JNIEnv *_env)
+{
+    int err;
+    err = android::AndroidRuntime::registerNativeMethods(_env, classPathName, methods, NELEM(methods));
+    return err;
+}
diff --git a/core/jni/server/Android.mk b/core/jni/server/Android.mk
new file mode 100644
index 0000000..2f48edf
--- /dev/null
+++ b/core/jni/server/Android.mk
@@ -0,0 +1,40 @@
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES:= \
+    com_android_server_AlarmManagerService.cpp \
+    com_android_server_BatteryService.cpp \
+    com_android_server_HardwareService.cpp \
+    com_android_server_KeyInputQueue.cpp \
+    com_android_server_SensorService.cpp \
+    com_android_server_SystemServer.cpp \
+    onload.cpp
+
+LOCAL_C_INCLUDES += \
+	$(JNI_H_INCLUDE)
+
+LOCAL_SHARED_LIBRARIES := \
+	libcutils \
+	libhardware \
+	libhardware_legacy \
+	libnativehelper \
+    libsystem_server \
+	libutils \
+	libui
+
+ifeq ($(TARGET_SIMULATOR),true)
+ifeq ($(TARGET_OS),linux)
+ifeq ($(TARGET_ARCH),x86)
+LOCAL_LDLIBS += -lpthread -ldl -lrt
+endif
+endif
+endif
+
+ifeq ($(WITH_MALLOC_LEAK_CHECK),true)
+	LOCAL_CFLAGS += -DMALLOC_LEAK_CHECK
+endif
+
+LOCAL_MODULE:= libandroid_servers
+
+include $(BUILD_SHARED_LIBRARY)
+    
diff --git a/core/jni/server/com_android_server_AlarmManagerService.cpp b/core/jni/server/com_android_server_AlarmManagerService.cpp
new file mode 100644
index 0000000..1d66fb1
--- /dev/null
+++ b/core/jni/server/com_android_server_AlarmManagerService.cpp
@@ -0,0 +1,143 @@
+/* //device/libs/android_runtime/android_server_AlarmManagerService.cpp
+**
+** Copyright 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.
+*/
+
+#define LOG_TAG "AlarmManagerService"
+
+#include "JNIHelp.h"
+#include "jni.h"
+#include "utils/Log.h"
+#include "utils/misc.h"
+
+#include <fcntl.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <arpa/inet.h>
+#include <netinet/in.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <unistd.h>
+
+#if HAVE_ANDROID_OS
+#include <linux/ioctl.h>
+#include <linux/android_alarm.h>
+#endif
+
+#define ONE_NANOSECOND 1000000000LL
+#define NANOSECONDS_TO_SECONDS(x) (x / ONE_NANOSECOND)
+#define SECONDS_TO_NANOSECONDS(x) (x * ONE_NANOSECOND)
+
+namespace android {
+
+static jint android_server_AlarmManagerService_setKernelTimezone(JNIEnv* env, jobject obj, jint fd, jint minswest)
+{
+#if HAVE_ANDROID_OS
+    struct timezone tz;
+
+    tz.tz_minuteswest = minswest;
+    tz.tz_dsttime = 0;
+
+    int result = settimeofday(NULL, &tz);
+    if (result < 0) {
+        LOGE("Unable to set kernel timezone to %d: %s\n", minswest, strerror(errno));
+        return -1;
+    } else {
+        LOGD("Kernel timezone updated to %d minutes west of GMT\n", minswest);
+    }
+
+    return 0;
+#else
+    return -ENOSYS;
+#endif
+}
+
+static jint android_server_AlarmManagerService_init(JNIEnv* env, jobject obj)
+{
+#if HAVE_ANDROID_OS
+    return open("/dev/alarm", O_RDWR);
+#else
+	return -1;
+#endif
+}
+
+static void android_server_AlarmManagerService_close(JNIEnv* env, jobject obj, jint fd)
+{
+#if HAVE_ANDROID_OS
+	close(fd);
+#endif
+}
+
+static void android_server_AlarmManagerService_set(JNIEnv* env, jobject obj, jint fd, jint type, jlong nanoseconds)
+{
+#if HAVE_ANDROID_OS
+    struct timespec ts;
+    ts.tv_sec = NANOSECONDS_TO_SECONDS(nanoseconds);
+    ts.tv_nsec = nanoseconds - SECONDS_TO_NANOSECONDS(ts.tv_sec);
+    
+	int result = ioctl(fd, ANDROID_ALARM_SET(type), &ts);
+	if (result < 0)
+	{
+        LOGE("Unable to set alarm to %lld: %s\n", nanoseconds, strerror(errno));
+    }
+#endif
+}
+
+static jint android_server_AlarmManagerService_waitForAlarm(JNIEnv* env, jobject obj, jint fd)
+{
+#if HAVE_ANDROID_OS
+	int result = 0;
+	
+	do
+	{
+		result = ioctl(fd, ANDROID_ALARM_WAIT);
+	} while (result < 0 && errno == EINTR);
+	
+	if (result < 0)
+	{
+        LOGE("Unable to wait on alarm: %s\n", strerror(errno));
+        return 0;
+    }
+    
+    return result;
+#endif
+}
+
+static JNINativeMethod sMethods[] = {
+     /* name, signature, funcPtr */
+	{"init", "()I", (void*)android_server_AlarmManagerService_init},
+	{"close", "(I)V", (void*)android_server_AlarmManagerService_close},
+	{"set", "(IIJ)V", (void*)android_server_AlarmManagerService_set},
+    {"waitForAlarm", "(I)I", (void*)android_server_AlarmManagerService_waitForAlarm},
+    {"setKernelTimezone", "(II)I", (void*)android_server_AlarmManagerService_setKernelTimezone},
+};
+
+int register_android_server_AlarmManagerService(JNIEnv* env)
+{
+    jclass clazz = env->FindClass("com/android/server/AlarmManagerService");
+
+    if (clazz == NULL)
+	{
+        LOGE("Can't find com/android/server/AlarmManagerService");
+        return -1;
+    }
+
+    return jniRegisterNativeMethods(env, "com/android/server/AlarmManagerService",
+                                    sMethods, NELEM(sMethods));
+}
+
+} /* namespace android */
diff --git a/core/jni/server/com_android_server_BatteryService.cpp b/core/jni/server/com_android_server_BatteryService.cpp
new file mode 100644
index 0000000..6636a97
--- /dev/null
+++ b/core/jni/server/com_android_server_BatteryService.cpp
@@ -0,0 +1,274 @@
+/*
+ * 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.
+ */
+
+#define LOG_TAG "BatteryService"
+
+#include "JNIHelp.h"
+#include "jni.h"
+#include "utils/Log.h"
+#include "utils/misc.h"
+
+#include <fcntl.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <arpa/inet.h>
+#include <netinet/in.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <unistd.h>
+
+#if HAVE_ANDROID_OS
+#include <linux/ioctl.h>
+#endif
+
+namespace android {
+
+#define AC_ONLINE_PATH "/sys/class/power_supply/ac/online"
+#define USB_ONLINE_PATH "/sys/class/power_supply/usb/online"
+#define BATTERY_STATUS_PATH "/sys/class/power_supply/battery/status"
+#define BATTERY_HEALTH_PATH "/sys/class/power_supply/battery/health"
+#define BATTERY_PRESENT_PATH "/sys/class/power_supply/battery/present"
+#define BATTERY_CAPACITY_PATH "/sys/class/power_supply/battery/capacity"
+#define BATTERY_VOLTAGE_PATH "/sys/class/power_supply/battery/batt_vol"
+#define BATTERY_TEMPERATURE_PATH "/sys/class/power_supply/battery/batt_temp"
+#define BATTERY_TECHNOLOGY_PATH "/sys/class/power_supply/battery/technology"
+
+struct FieldIds {
+    // members
+    jfieldID mAcOnline;
+    jfieldID mUsbOnline;
+    jfieldID mBatteryStatus;
+    jfieldID mBatteryHealth;
+    jfieldID mBatteryPresent;
+    jfieldID mBatteryLevel;
+    jfieldID mBatteryVoltage;
+    jfieldID mBatteryTemperature;
+    jfieldID mBatteryTechnology;
+};
+static FieldIds gFieldIds;
+
+struct BatteryManagerConstants {
+    jint statusUnknown;
+    jint statusCharging;
+    jint statusDischarging;
+    jint statusNotCharging;
+    jint statusFull;
+    jint healthUnknown;
+    jint healthGood;
+    jint healthOverheat;
+    jint healthDead;
+    jint healthOverVoltage;
+    jint healthUnspecifiedFailure;
+};
+static BatteryManagerConstants gConstants;
+
+static jint getBatteryStatus(const char* status)
+{
+    switch (status[0]) {
+        case 'C': return gConstants.statusCharging;         // Charging
+        case 'D': return gConstants.statusDischarging;      // Discharging
+        case 'F': return gConstants.statusFull;             // Not charging
+        case 'N': return gConstants.statusNotCharging;      // Full
+        case 'U': return gConstants.statusUnknown;          // Unknown
+            
+        default: {
+            LOGW("Unknown battery status '%s'", status);
+            return gConstants.statusUnknown;
+        }
+    }
+}
+
+static jint getBatteryHealth(const char* status)
+{
+    switch (status[0]) {
+        case 'D': return gConstants.healthDead;         // Dead
+        case 'G': return gConstants.healthGood;         // Good
+        case 'O': {
+            if (strcmp(status, "Overheat") == 0) {
+                return gConstants.healthOverheat;
+            } else if (strcmp(status, "Over voltage") == 0) {
+                return gConstants.healthOverVoltage;
+            }
+            LOGW("Unknown battery health[1] '%s'", status);
+            return gConstants.healthUnknown;
+        }
+        
+        case 'U': {
+            if (strcmp(status, "Unspecified failure") == 0) {
+                return gConstants.healthUnspecifiedFailure;
+            } else if (strcmp(status, "Unknown") == 0) {
+                return gConstants.healthUnknown;
+            }
+            // fall through
+        }
+            
+        default: {
+            LOGW("Unknown battery health[2] '%s'", status);
+            return gConstants.healthUnknown;
+        }
+    }
+}
+
+static int readFromFile(const char* path, char* buf, size_t size)
+{
+    int fd = open(path, O_RDONLY, 0);
+    if (fd == -1) {
+        LOGE("Could not open '%s'", path);
+        return -1;
+    }
+    
+    size_t count = read(fd, buf, size);
+    if (count > 0) {
+        count = (count < size) ? count : size - 1;
+        while (count > 0 && buf[count-1] == '\n') count--;
+        buf[count] = '\0';
+    } else {
+        buf[0] = '\0';
+    } 
+
+    close(fd);
+    return count;
+}
+
+static void setBooleanField(JNIEnv* env, jobject obj, const char* path, jfieldID fieldID)
+{
+    const int SIZE = 16;
+    char buf[SIZE];
+    
+    jboolean value = false;
+    if (readFromFile(path, buf, SIZE) > 0) {
+        if (buf[0] == '1') {
+            value = true;
+        }
+    }
+    env->SetBooleanField(obj, fieldID, value);
+}
+
+static void setIntField(JNIEnv* env, jobject obj, const char* path, jfieldID fieldID)
+{
+    const int SIZE = 128;
+    char buf[SIZE];
+    
+    jint value = 0;
+    if (readFromFile(path, buf, SIZE) > 0) {
+        value = atoi(buf);
+    }
+    env->SetIntField(obj, fieldID, value);
+}
+
+static void android_server_BatteryService_update(JNIEnv* env, jobject obj)
+{
+    setBooleanField(env, obj, AC_ONLINE_PATH, gFieldIds.mAcOnline);
+    setBooleanField(env, obj, USB_ONLINE_PATH, gFieldIds.mUsbOnline);
+    setBooleanField(env, obj, BATTERY_PRESENT_PATH, gFieldIds.mBatteryPresent);
+    
+    setIntField(env, obj, BATTERY_CAPACITY_PATH, gFieldIds.mBatteryLevel);
+    setIntField(env, obj, BATTERY_VOLTAGE_PATH, gFieldIds.mBatteryVoltage);
+    setIntField(env, obj, BATTERY_TEMPERATURE_PATH, gFieldIds.mBatteryTemperature);
+    
+    const int SIZE = 128;
+    char buf[SIZE];
+    
+    if (readFromFile(BATTERY_STATUS_PATH, buf, SIZE) > 0)
+        env->SetIntField(obj, gFieldIds.mBatteryStatus, getBatteryStatus(buf));
+    
+    if (readFromFile(BATTERY_HEALTH_PATH, buf, SIZE) > 0)
+        env->SetIntField(obj, gFieldIds.mBatteryHealth, getBatteryHealth(buf));
+
+    if (readFromFile(BATTERY_TECHNOLOGY_PATH, buf, SIZE) > 0)
+        env->SetObjectField(obj, gFieldIds.mBatteryTechnology, env->NewStringUTF(buf));
+}
+
+static JNINativeMethod sMethods[] = {
+     /* name, signature, funcPtr */
+	{"native_update", "()V", (void*)android_server_BatteryService_update},
+};
+
+int register_android_server_BatteryService(JNIEnv* env)
+{
+    jclass clazz = env->FindClass("com/android/server/BatteryService");
+
+    if (clazz == NULL) {
+        LOGE("Can't find com/android/server/BatteryService");
+        return -1;
+    }
+    
+    gFieldIds.mAcOnline = env->GetFieldID(clazz, "mAcOnline", "Z");
+    gFieldIds.mUsbOnline = env->GetFieldID(clazz, "mUsbOnline", "Z");
+    gFieldIds.mBatteryStatus = env->GetFieldID(clazz, "mBatteryStatus", "I");
+    gFieldIds.mBatteryHealth = env->GetFieldID(clazz, "mBatteryHealth", "I");
+    gFieldIds.mBatteryPresent = env->GetFieldID(clazz, "mBatteryPresent", "Z");
+    gFieldIds.mBatteryLevel = env->GetFieldID(clazz, "mBatteryLevel", "I");
+    gFieldIds.mBatteryTechnology = env->GetFieldID(clazz, "mBatteryTechnology", "Ljava/lang/String;");
+    gFieldIds.mBatteryVoltage = env->GetFieldID(clazz, "mBatteryVoltage", "I");
+    gFieldIds.mBatteryTemperature = env->GetFieldID(clazz, "mBatteryTemperature", "I");
+
+    LOG_FATAL_IF(gFieldIds.mAcOnline == NULL, "Unable to find BatteryService.AC_ONLINE_PATH");
+    LOG_FATAL_IF(gFieldIds.mUsbOnline == NULL, "Unable to find BatteryService.USB_ONLINE_PATH");
+    LOG_FATAL_IF(gFieldIds.mBatteryStatus == NULL, "Unable to find BatteryService.BATTERY_STATUS_PATH");
+    LOG_FATAL_IF(gFieldIds.mBatteryHealth == NULL, "Unable to find BatteryService.BATTERY_HEALTH_PATH");
+    LOG_FATAL_IF(gFieldIds.mBatteryPresent == NULL, "Unable to find BatteryService.BATTERY_PRESENT_PATH");
+    LOG_FATAL_IF(gFieldIds.mBatteryLevel == NULL, "Unable to find BatteryService.BATTERY_CAPACITY_PATH");
+    LOG_FATAL_IF(gFieldIds.mBatteryVoltage == NULL, "Unable to find BatteryService.BATTERY_VOLTAGE_PATH");
+    LOG_FATAL_IF(gFieldIds.mBatteryTemperature == NULL, "Unable to find BatteryService.BATTERY_TEMPERATURE_PATH");
+    LOG_FATAL_IF(gFieldIds.mBatteryTechnology == NULL, "Unable to find BatteryService.BATTERY_TECHNOLOGY_PATH");
+    
+    clazz = env->FindClass("android/os/BatteryManager");
+    
+    if (clazz == NULL) {
+        LOGE("Can't find android/os/BatteryManager");
+        return -1;
+    }
+    
+    gConstants.statusUnknown = env->GetStaticIntField(clazz, 
+            env->GetStaticFieldID(clazz, "BATTERY_STATUS_UNKNOWN", "I"));
+            
+    gConstants.statusCharging = env->GetStaticIntField(clazz, 
+            env->GetStaticFieldID(clazz, "BATTERY_STATUS_CHARGING", "I"));
+            
+    gConstants.statusDischarging = env->GetStaticIntField(clazz, 
+            env->GetStaticFieldID(clazz, "BATTERY_STATUS_DISCHARGING", "I"));
+    
+    gConstants.statusNotCharging = env->GetStaticIntField(clazz, 
+            env->GetStaticFieldID(clazz, "BATTERY_STATUS_NOT_CHARGING", "I"));
+    
+    gConstants.statusFull = env->GetStaticIntField(clazz, 
+            env->GetStaticFieldID(clazz, "BATTERY_STATUS_FULL", "I"));
+
+    gConstants.healthUnknown = env->GetStaticIntField(clazz, 
+            env->GetStaticFieldID(clazz, "BATTERY_HEALTH_UNKNOWN", "I"));
+
+    gConstants.healthGood = env->GetStaticIntField(clazz, 
+            env->GetStaticFieldID(clazz, "BATTERY_HEALTH_GOOD", "I"));
+
+    gConstants.healthOverheat = env->GetStaticIntField(clazz, 
+            env->GetStaticFieldID(clazz, "BATTERY_HEALTH_OVERHEAT", "I"));
+
+    gConstants.healthDead = env->GetStaticIntField(clazz, 
+            env->GetStaticFieldID(clazz, "BATTERY_HEALTH_DEAD", "I"));
+
+    gConstants.healthOverVoltage = env->GetStaticIntField(clazz, 
+            env->GetStaticFieldID(clazz, "BATTERY_HEALTH_OVER_VOLTAGE", "I"));
+            
+    gConstants.healthUnspecifiedFailure = env->GetStaticIntField(clazz, 
+            env->GetStaticFieldID(clazz, "BATTERY_HEALTH_UNSPECIFIED_FAILURE", "I"));
+    
+    return jniRegisterNativeMethods(env, "com/android/server/BatteryService", sMethods, NELEM(sMethods));
+}
+
+} /* namespace android */
diff --git a/core/jni/server/com_android_server_HardwareService.cpp b/core/jni/server/com_android_server_HardwareService.cpp
new file mode 100644
index 0000000..ac36348
--- /dev/null
+++ b/core/jni/server/com_android_server_HardwareService.cpp
@@ -0,0 +1,54 @@
+/* //device/libs/android_runtime/android_os_Vibrator.cpp
+**
+** Copyright 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.
+*/
+
+#define LOG_TAG "Vibrator"
+
+#include "jni.h"
+#include "JNIHelp.h"
+#include <stdio.h>
+#include "android_runtime/AndroidRuntime.h"
+#include <utils/misc.h>
+#include <utils/Log.h>
+#include <hardware_legacy/vibrator.h>
+
+namespace android
+{
+
+static void vibratorOn(JNIEnv *env, jobject clazz, jlong timeout_ms)
+{
+    // LOGI("vibratorOn\n");
+    vibrator_on(timeout_ms);
+}
+
+static void vibratorOff(JNIEnv *env, jobject clazz)
+{
+    // LOGI("vibratorOff\n");
+    vibrator_off();
+}
+
+static JNINativeMethod method_table[] = {
+    { "vibratorOn", "(J)V", (void*)vibratorOn },
+    { "vibratorOff", "()V", (void*)vibratorOff }
+};
+
+int register_android_os_Vibrator(JNIEnv *env)
+{
+    return jniRegisterNativeMethods(env, "com/android/server/HardwareService",
+            method_table, NELEM(method_table));
+}
+
+};
diff --git a/core/jni/server/com_android_server_KeyInputQueue.cpp b/core/jni/server/com_android_server_KeyInputQueue.cpp
new file mode 100644
index 0000000..63830d5
--- /dev/null
+++ b/core/jni/server/com_android_server_KeyInputQueue.cpp
@@ -0,0 +1,320 @@
+/*
+ * Copyright (C) 2007 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.
+ */
+
+#define LOG_TAG "Input"
+
+#include "jni.h"
+#include "JNIHelp.h"
+#include <utils/misc.h>
+#include <utils/Log.h>
+
+#include <ui/EventHub.h>
+#include <utils/threads.h>
+
+#include <stdio.h>
+
+namespace android {
+
+// ----------------------------------------------------------------------------
+
+static struct input_offsets_t
+{
+    jfieldID mMinValue;
+    jfieldID mMaxValue;
+    jfieldID mFlat;
+    jfieldID mFuzz;
+    
+    jfieldID mDeviceId;
+    jfieldID mType;
+    jfieldID mScancode;
+    jfieldID mKeycode;
+    jfieldID mFlags;
+    jfieldID mValue;
+    jfieldID mWhen;
+} gInputOffsets;
+
+// ----------------------------------------------------------------------------
+
+static Mutex gLock;
+static sp<EventHub> gHub;
+
+static jboolean
+android_server_KeyInputQueue_readEvent(JNIEnv* env, jobject clazz,
+                                          jobject event)
+{
+    gLock.lock();
+    sp<EventHub> hub = gHub;
+    if (hub == NULL) {
+        hub = new EventHub;
+        gHub = hub;
+    }
+    gLock.unlock();
+
+    int32_t deviceId;
+    int32_t type;
+    int32_t scancode, keycode;
+    uint32_t flags;
+    int32_t value;
+    nsecs_t when;
+    bool res = hub->getEvent(&deviceId, &type, &scancode, &keycode,
+            &flags, &value, &when);
+
+    env->SetIntField(event, gInputOffsets.mDeviceId, (jint)deviceId);
+    env->SetIntField(event, gInputOffsets.mType, (jint)type);
+    env->SetIntField(event, gInputOffsets.mScancode, (jint)scancode);
+    env->SetIntField(event, gInputOffsets.mKeycode, (jint)keycode);
+    env->SetIntField(event, gInputOffsets.mFlags, (jint)flags);
+    env->SetIntField(event, gInputOffsets.mValue, value);
+    env->SetLongField(event, gInputOffsets.mWhen,
+                        (jlong)(nanoseconds_to_milliseconds(when)));
+
+    return res;
+}
+
+static jint
+android_server_KeyInputQueue_getDeviceClasses(JNIEnv* env, jobject clazz,
+                                              jint deviceId)
+{
+    jint classes = 0;
+    gLock.lock();
+    if (gHub != NULL) classes = gHub->getDeviceClasses(deviceId);
+    gLock.unlock();
+    return classes;
+}
+
+static jstring
+android_server_KeyInputQueue_getDeviceName(JNIEnv* env, jobject clazz,
+                                              jint deviceId)
+{
+    String8 name;
+    gLock.lock();
+    if (gHub != NULL) name = gHub->getDeviceName(deviceId);
+    gLock.unlock();
+    
+    if (name.size() > 0) {
+        return env->NewStringUTF(name.string());
+    }
+    return NULL;
+}
+
+static jboolean
+android_server_KeyInputQueue_getAbsoluteInfo(JNIEnv* env, jobject clazz,
+                                             jint deviceId, jint axis,
+                                             jobject info)
+{
+    int32_t minValue, maxValue, flat, fuzz;
+    int res = -1;
+    gLock.lock();
+    if (gHub != NULL) {
+        res = gHub->getAbsoluteInfo(deviceId, axis,
+                &minValue, &maxValue, &flat, &fuzz);
+    }
+    gLock.unlock();
+    
+    if (res < 0) return JNI_FALSE;
+    
+    env->SetIntField(info, gInputOffsets.mMinValue, (jint)minValue);
+    env->SetIntField(info, gInputOffsets.mMaxValue, (jint)maxValue);
+    env->SetIntField(info, gInputOffsets.mFlat, (jint)flat);
+    env->SetIntField(info, gInputOffsets.mFuzz, (jint)fuzz);
+    return JNI_TRUE;
+}
+
+static jint
+android_server_KeyInputQueue_getSwitchState(JNIEnv* env, jobject clazz,
+                                           jint sw)
+{
+    jint st = -1;
+    gLock.lock();
+    if (gHub != NULL) st = gHub->getSwitchState(sw);
+    gLock.unlock();
+    
+    return st;
+}
+
+static jint
+android_server_KeyInputQueue_getSwitchStateDevice(JNIEnv* env, jobject clazz,
+                                            jint deviceId, jint sw)
+{
+    jint st = -1;
+    gLock.lock();
+    if (gHub != NULL) st = gHub->getSwitchState(deviceId, sw);
+    gLock.unlock();
+    
+    return st;
+}
+
+static jint
+android_server_KeyInputQueue_getScancodeState(JNIEnv* env, jobject clazz,
+                                           jint sw)
+{
+    jint st = -1;
+    gLock.lock();
+    if (gHub != NULL) st = gHub->getScancodeState(sw);
+    gLock.unlock();
+    
+    return st;
+}
+
+static jint
+android_server_KeyInputQueue_getScancodeStateDevice(JNIEnv* env, jobject clazz,
+                                            jint deviceId, jint sw)
+{
+    jint st = -1;
+    gLock.lock();
+    if (gHub != NULL) st = gHub->getScancodeState(deviceId, sw);
+    gLock.unlock();
+    
+    return st;
+}
+
+static jint
+android_server_KeyInputQueue_getKeycodeState(JNIEnv* env, jobject clazz,
+                                           jint sw)
+{
+    jint st = -1;
+    gLock.lock();
+    if (gHub != NULL) st = gHub->getKeycodeState(sw);
+    gLock.unlock();
+    
+    return st;
+}
+
+static jint
+android_server_KeyInputQueue_getKeycodeStateDevice(JNIEnv* env, jobject clazz,
+                                            jint deviceId, jint sw)
+{
+    jint st = -1;
+    gLock.lock();
+    if (gHub != NULL) st = gHub->getKeycodeState(deviceId, sw);
+    gLock.unlock();
+    
+    return st;
+}
+
+static jboolean
+android_server_KeyInputQueue_hasKeys(JNIEnv* env, jobject clazz,
+                                     jintArray keyCodes, jbooleanArray outFlags)
+{
+    jboolean ret = JNI_FALSE;
+
+    int32_t* codes = env->GetIntArrayElements(keyCodes, NULL);
+    uint8_t* flags = env->GetBooleanArrayElements(outFlags, NULL);
+    size_t numCodes = env->GetArrayLength(keyCodes);
+    if (numCodes == env->GetArrayLength(outFlags)) {
+        gLock.lock();
+        if (gHub != NULL) ret = gHub->hasKeys(numCodes, codes, flags);
+        gLock.unlock();
+    }
+
+    env->ReleaseBooleanArrayElements(outFlags, flags, 0);
+    env->ReleaseIntArrayElements(keyCodes, codes, 0);
+    return ret;
+}
+
+// ----------------------------------------------------------------------------
+
+/*
+ * JNI registration.
+ */
+static JNINativeMethod gInputMethods[] = {
+    /* name, signature, funcPtr */
+    { "readEvent",       "(Landroid/view/RawInputEvent;)Z",
+            (void*) android_server_KeyInputQueue_readEvent },
+    { "getDeviceClasses", "(I)I",
+        (void*) android_server_KeyInputQueue_getDeviceClasses },
+    { "getDeviceName", "(I)Ljava/lang/String;",
+        (void*) android_server_KeyInputQueue_getDeviceName },
+    { "getAbsoluteInfo", "(IILcom/android/server/InputDevice$AbsoluteInfo;)Z",
+        (void*) android_server_KeyInputQueue_getAbsoluteInfo },
+    { "getSwitchState", "(I)I",
+        (void*) android_server_KeyInputQueue_getSwitchState },
+    { "getSwitchState", "(II)I",
+        (void*) android_server_KeyInputQueue_getSwitchStateDevice },
+    { "getScancodeState", "(I)I",
+        (void*) android_server_KeyInputQueue_getScancodeState },
+    { "getScancodeState", "(II)I",
+        (void*) android_server_KeyInputQueue_getScancodeStateDevice },
+    { "getKeycodeState", "(I)I",
+        (void*) android_server_KeyInputQueue_getKeycodeState },
+    { "getKeycodeState", "(II)I",
+        (void*) android_server_KeyInputQueue_getKeycodeStateDevice },
+    { "hasKeys", "([I[Z)Z",
+        (void*) android_server_KeyInputQueue_hasKeys },
+};
+
+int register_android_server_KeyInputQueue(JNIEnv* env)
+{
+    jclass input = env->FindClass("com/android/server/KeyInputQueue");
+    LOG_FATAL_IF(input == NULL, "Unable to find class com/android/server/KeyInputQueue");
+    int res = jniRegisterNativeMethods(env, "com/android/server/KeyInputQueue",
+                                        gInputMethods, NELEM(gInputMethods));
+
+    jclass absoluteInfo = env->FindClass("com/android/server/InputDevice$AbsoluteInfo");
+    LOG_FATAL_IF(absoluteInfo == NULL, "Unable to find class com/android/server/InputDevice$AbsoluteInfo");
+    
+    gInputOffsets.mMinValue
+        = env->GetFieldID(absoluteInfo, "minValue", "I");
+    LOG_FATAL_IF(gInputOffsets.mMinValue == NULL, "Unable to find InputDevice.AbsoluteInfo.minValue");
+    
+    gInputOffsets.mMaxValue
+        = env->GetFieldID(absoluteInfo, "maxValue", "I");
+    LOG_FATAL_IF(gInputOffsets.mMaxValue == NULL, "Unable to find InputDevice.AbsoluteInfo.maxValue");
+    
+    gInputOffsets.mFlat
+        = env->GetFieldID(absoluteInfo, "flat", "I");
+    LOG_FATAL_IF(gInputOffsets.mFlat == NULL, "Unable to find InputDevice.AbsoluteInfo.flat");
+    
+    gInputOffsets.mFuzz
+        = env->GetFieldID(absoluteInfo, "fuzz", "I");
+    LOG_FATAL_IF(gInputOffsets.mFuzz == NULL, "Unable to find InputDevice.AbsoluteInfo.fuzz");
+    
+    jclass inputEvent = env->FindClass("android/view/RawInputEvent");
+    LOG_FATAL_IF(inputEvent == NULL, "Unable to find class android/view/RawInputEvent");
+
+    gInputOffsets.mDeviceId
+        = env->GetFieldID(inputEvent, "deviceId", "I");
+    LOG_FATAL_IF(gInputOffsets.mDeviceId == NULL, "Unable to find RawInputEvent.deviceId");
+    
+    gInputOffsets.mType
+        = env->GetFieldID(inputEvent, "type", "I");
+    LOG_FATAL_IF(gInputOffsets.mType == NULL, "Unable to find RawInputEvent.type");
+    
+    gInputOffsets.mScancode
+        = env->GetFieldID(inputEvent, "scancode", "I");
+    LOG_FATAL_IF(gInputOffsets.mScancode == NULL, "Unable to find RawInputEvent.scancode");
+
+    gInputOffsets.mKeycode
+        = env->GetFieldID(inputEvent, "keycode", "I");
+    LOG_FATAL_IF(gInputOffsets.mKeycode == NULL, "Unable to find RawInputEvent.keycode");
+
+    gInputOffsets.mFlags
+        = env->GetFieldID(inputEvent, "flags", "I");
+    LOG_FATAL_IF(gInputOffsets.mFlags == NULL, "Unable to find RawInputEvent.flags");
+
+    gInputOffsets.mValue
+        = env->GetFieldID(inputEvent, "value", "I");
+    LOG_FATAL_IF(gInputOffsets.mValue == NULL, "Unable to find RawInputEvent.value");
+    
+    gInputOffsets.mWhen
+        = env->GetFieldID(inputEvent, "when", "J");
+    LOG_FATAL_IF(gInputOffsets.mWhen == NULL, "Unable to find RawInputEvent.when");
+
+    return res;
+}
+
+}; // namespace android
+
diff --git a/core/jni/server/com_android_server_SensorService.cpp b/core/jni/server/com_android_server_SensorService.cpp
new file mode 100644
index 0000000..695a8a3
--- /dev/null
+++ b/core/jni/server/com_android_server_SensorService.cpp
@@ -0,0 +1,125 @@
+/*
+ * Copyright 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.
+ */
+
+#define LOG_TAG "Sensors"
+
+#include <hardware/sensors.h>
+
+#include "jni.h"
+#include "JNIHelp.h"
+
+namespace android {
+
+static struct file_descriptor_offsets_t
+{
+    jclass mClass;
+    jmethodID mConstructor;
+    jfieldID mDescriptor;
+} gFileDescriptorOffsets;
+
+static struct parcel_file_descriptor_offsets_t
+{
+    jclass mClass;
+    jmethodID mConstructor;
+} gParcelFileDescriptorOffsets;
+
+/*
+ * The method below are not thread-safe and not intended to be 
+ */
+
+static sensors_control_device_t* sSensorDevice = 0;
+
+static jint
+android_init(JNIEnv *env, jclass clazz)
+{
+    sensors_module_t* module;
+    if (hw_get_module(SENSORS_HARDWARE_MODULE_ID, (const hw_module_t**)&module) == 0) {
+        if (sensors_control_open(&module->common, &sSensorDevice) == 0) {
+            const struct sensor_t* list;
+            int count = module->get_sensors_list(module, &list);
+            return count;
+        }
+    }
+    return 0;
+}
+
+static jobject
+android_open(JNIEnv *env, jclass clazz)
+{
+    int fd = sSensorDevice->open_data_source(sSensorDevice);
+    // new FileDescriptor()
+    jobject filedescriptor = env->NewObject(
+            gFileDescriptorOffsets.mClass, 
+            gFileDescriptorOffsets.mConstructor);
+    
+    if (filedescriptor != NULL) {
+        env->SetIntField(filedescriptor, gFileDescriptorOffsets.mDescriptor, fd);
+        // new ParcelFileDescriptor()
+        return env->NewObject(gParcelFileDescriptorOffsets.mClass,
+                gParcelFileDescriptorOffsets.mConstructor, 
+                filedescriptor);
+    }
+    close(fd);
+    return NULL;
+}
+
+static jboolean
+android_activate(JNIEnv *env, jclass clazz, jint sensor, jboolean activate)
+{
+    int active = sSensorDevice->activate(sSensorDevice, sensor, activate);
+    return (active<0) ? false : true;
+}
+
+static jint
+android_set_delay(JNIEnv *env, jclass clazz, jint ms)
+{
+    return sSensorDevice->set_delay(sSensorDevice, ms);
+}
+
+static jint
+android_data_wake(JNIEnv *env, jclass clazz)
+{
+    int res = sSensorDevice->wake(sSensorDevice);
+    return res;
+}
+
+
+static JNINativeMethod gMethods[] = {
+    {"_sensors_control_init",     "()I",   (void*) android_init },
+    {"_sensors_control_open",     "()Landroid/os/ParcelFileDescriptor;",  (void*) android_open },
+    {"_sensors_control_activate", "(IZ)Z", (void*) android_activate },
+    {"_sensors_control_wake",     "()I", (void*) android_data_wake },
+    {"_sensors_control_set_delay","(I)I", (void*) android_set_delay },
+};
+
+int register_android_server_SensorService(JNIEnv *env)
+{
+    jclass clazz;
+
+    clazz = env->FindClass("java/io/FileDescriptor");
+    gFileDescriptorOffsets.mClass = (jclass)env->NewGlobalRef(clazz);
+    gFileDescriptorOffsets.mConstructor = env->GetMethodID(clazz, "<init>", "()V");
+    gFileDescriptorOffsets.mDescriptor = env->GetFieldID(clazz, "descriptor", "I");
+
+    clazz = env->FindClass("android/os/ParcelFileDescriptor");
+    gParcelFileDescriptorOffsets.mClass = (jclass) env->NewGlobalRef(clazz);
+    gParcelFileDescriptorOffsets.mConstructor = env->GetMethodID(clazz, "<init>", "(Ljava/io/FileDescriptor;)V");
+
+    return jniRegisterNativeMethods(env, "com/android/server/SensorService",
+            gMethods, NELEM(gMethods));
+}
+
+}; // namespace android
diff --git a/core/jni/server/com_android_server_SystemServer.cpp b/core/jni/server/com_android_server_SystemServer.cpp
new file mode 100644
index 0000000..ae29405
--- /dev/null
+++ b/core/jni/server/com_android_server_SystemServer.cpp
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2007 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.
+ */
+#include <utils/Log.h>
+#include <utils/misc.h>
+
+#include "jni.h"
+#include "JNIHelp.h"
+
+namespace android {
+
+extern "C" int system_init();
+
+static void android_server_SystemServer_init1(JNIEnv* env, jobject clazz)
+{
+    system_init();
+}
+
+/*
+ * JNI registration.
+ */
+static JNINativeMethod gMethods[] = {
+    /* name, signature, funcPtr */
+    { "init1", "([Ljava/lang/String;)V", (void*) android_server_SystemServer_init1 },
+};
+
+int register_android_server_SystemServer(JNIEnv* env)
+{
+    return jniRegisterNativeMethods(env, "com/android/server/SystemServer",
+            gMethods, NELEM(gMethods));
+}
+
+}; // namespace android
+
+
diff --git a/core/jni/server/onload.cpp b/core/jni/server/onload.cpp
new file mode 100644
index 0000000..3d68cfb
--- /dev/null
+++ b/core/jni/server/onload.cpp
@@ -0,0 +1,36 @@
+#include "JNIHelp.h"
+#include "jni.h"
+#include "utils/Log.h"
+#include "utils/misc.h"
+
+namespace android {
+int register_android_server_AlarmManagerService(JNIEnv* env);
+int register_android_server_BatteryService(JNIEnv* env);
+int register_android_server_KeyInputQueue(JNIEnv* env);
+int register_android_os_Vibrator(JNIEnv* env);
+int register_android_server_SensorService(JNIEnv* env);
+int register_android_server_SystemServer(JNIEnv* env);
+};
+
+using namespace android;
+
+extern "C" jint JNI_OnLoad(JavaVM* vm, void* reserved)
+{
+    JNIEnv* env = NULL;
+    jint result = -1;
+
+    if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {
+        LOGE("GetEnv failed!");
+        return result;
+    }
+    LOG_ASSERT(env, "Could not retrieve the env!");
+
+    register_android_server_KeyInputQueue(env);
+    register_android_os_Vibrator(env);
+    register_android_server_AlarmManagerService(env);
+    register_android_server_BatteryService(env);
+    register_android_server_SensorService(env);
+    register_android_server_SystemServer(env);
+
+    return JNI_VERSION_1_4;
+}
diff --git a/core/jni/sqlite3_exception.h b/core/jni/sqlite3_exception.h
new file mode 100644
index 0000000..13735a1
--- /dev/null
+++ b/core/jni/sqlite3_exception.h
@@ -0,0 +1,47 @@
+/* //device/libs/include/android_runtime/sqlite3_exception.h
+**
+** Copyright 2007, 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.
+*/
+
+#ifndef _SQLITE3_EXCEPTION_H
+#define _SQLITE3_EXCEPTION_H 1
+
+#include <jni.h>
+#include <JNIHelp.h>
+//#include <android_runtime/AndroidRuntime.h>
+
+#include <sqlite3.h>
+
+namespace android {
+
+/* throw a SQLiteException with a message appropriate for the error in handle */
+void throw_sqlite3_exception(JNIEnv* env, sqlite3* handle);
+
+/* throw a SQLiteException with the given message */
+void throw_sqlite3_exception(JNIEnv* env, const char* message);
+
+/* throw a SQLiteException with a message appropriate for the error in handle
+   concatenated with the given message
+ */
+void throw_sqlite3_exception(JNIEnv* env, sqlite3* handle, const char* message);
+
+/* throw a SQLiteException for a given error code */
+void throw_sqlite3_exception_errcode(JNIEnv* env, int errcode, const char* message);
+
+void throw_sqlite3_exception(JNIEnv* env, int errcode,
+                             const char* sqlite3Message, const char* message);
+}
+
+#endif // _SQLITE3_EXCEPTION_H
diff --git a/core/res/Android.mk b/core/res/Android.mk
new file mode 100644
index 0000000..5fca5d0
--- /dev/null
+++ b/core/res/Android.mk
@@ -0,0 +1,36 @@
+#
+# 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.
+#
+LOCAL_PATH := $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_NO_STANDARD_LIBRARIES := true
+LOCAL_PACKAGE_NAME := framework-res
+LOCAL_CERTIFICATE := platform
+
+# Tell aapt to create "extending (non-application)" resource IDs,
+# since these resources will be used by many apps.
+LOCAL_AAPT_FLAGS := -x
+
+LOCAL_MODULE_TAGS := user development
+
+# Install this alongside the libraries.
+LOCAL_MODULE_PATH := $(TARGET_OUT_JAVA_LIBRARIES)
+
+# Create package-export.apk, which other packages can use to get
+# PRODUCT-agnostic resource data like IDs and type definitions.
+LOCAL_EXPORT_PACKAGE_RESOURCES := true
+
+include $(BUILD_PACKAGE)
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
new file mode 100644
index 0000000..e9db5f0
--- /dev/null
+++ b/core/res/AndroidManifest.xml
@@ -0,0 +1,1007 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* //device/apps/common/AndroidManifest.xml
+**
+** Copyright 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.
+*/
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="android" android:sharedUserId="android.uid.system"
+    android:sharedUserLabel="@string/android_system_label">
+
+    <!-- ====================================== -->
+    <!-- Permissions for things that cost money -->
+    <!-- ====================================== -->
+    <eat-comment />
+    
+    <!-- Used for permissions that can be used to make the user spend money
+         without their direct involvement.  For example, this is the group
+         for permissions that allow you to directly place phone calls,
+         directly send SMS messages, etc. -->
+    <permission-group android:name="android.permission-group.COST_MONEY"
+        android:label="@string/permgrouplab_costMoney"
+        android:description="@string/permgroupdesc_costMoney" />
+
+    <!-- Allows an application to send SMS messages. -->
+    <permission android:name="android.permission.SEND_SMS"
+        android:permissionGroup="android.permission-group.COST_MONEY"
+        android:protectionLevel="dangerous"
+        android:label="@string/permlab_sendSms"
+        android:description="@string/permdesc_sendSms" />
+
+    <!-- Allows an application to initiate a phone call without going through
+         the Dialer user interface for the user to confirm the call
+         being placed. -->
+    <permission android:name="android.permission.CALL_PHONE"
+        android:permissionGroup="android.permission-group.COST_MONEY"
+        android:protectionLevel="dangerous"
+        android:label="@string/permlab_callPhone"
+        android:description="@string/permdesc_callPhone" />
+
+    <!-- ================================== -->
+    <!-- Permissions for accessing messages -->
+    <!-- ================================== -->
+    <eat-comment />
+    
+    <!-- Used for permissions that allow an application to send messages
+         on behalf of the user or intercept messages being received by the
+         user.  This is primarily intended for SMS/MMS messaging, such as
+         receiving or reading an MMS. -->
+    <permission-group android:name="android.permission-group.MESSAGES"
+        android:label="@string/permgrouplab_messages"
+        android:description="@string/permgroupdesc_messages" />
+
+    <!-- Allows an application to monitor incoming SMS messages, to record
+         or perform processing on them. -->
+    <permission android:name="android.permission.RECEIVE_SMS"
+        android:permissionGroup="android.permission-group.MESSAGES"
+        android:protectionLevel="dangerous"
+        android:label="@string/permlab_receiveSms"
+        android:description="@string/permdesc_receiveSms" />
+
+    <!-- Allows an application to monitor incoming MMS messages, to record
+         or perform processing on them. -->
+    <permission android:name="android.permission.RECEIVE_MMS"
+        android:permissionGroup="android.permission-group.MESSAGES"
+        android:protectionLevel="dangerous"
+        android:label="@string/permlab_receiveMms"
+        android:description="@string/permdesc_receiveMms" />
+
+    <!-- Allows an application to read SMS messages. -->
+    <permission android:name="android.permission.READ_SMS"
+        android:permissionGroup="android.permission-group.MESSAGES"
+        android:protectionLevel="dangerous"
+        android:label="@string/permlab_readSms"
+        android:description="@string/permdesc_readSms" />
+
+    <!-- Allows an application to write SMS messages. -->
+    <permission android:name="android.permission.WRITE_SMS"
+        android:permissionGroup="android.permission-group.MESSAGES"
+        android:protectionLevel="dangerous"
+        android:label="@string/permlab_writeSms"
+        android:description="@string/permdesc_writeSms" />
+
+    <!-- Allows an application to monitor incoming WAP push messages. -->
+    <permission android:name="android.permission.RECEIVE_WAP_PUSH"
+        android:permissionGroup="android.permission-group.MESSAGES"
+        android:protectionLevel="dangerous"
+        android:label="@string/permlab_receiveWapPush"
+        android:description="@string/permdesc_receiveWapPush" />
+
+    <!-- =============================================================== -->
+    <!-- Permissions for accessing personal info (contacts and calendar) -->
+    <!-- =============================================================== -->
+    <eat-comment />
+    
+    <!-- Used for permissions that provide access to the user's private data,
+         such as contacts, calendar events, e-mail messages, etc.  This includes
+         both reading and writing of this data (which should generally be
+         expressed as two distinct permissions). -->
+    <permission-group android:name="android.permission-group.PERSONAL_INFO"
+        android:label="@string/permgrouplab_personalInfo"
+        android:description="@string/permgroupdesc_personalInfo" />
+
+    <!-- Allows an application to read the user's contacts data. -->
+    <permission android:name="android.permission.READ_CONTACTS"
+        android:permissionGroup="android.permission-group.PERSONAL_INFO"
+        android:protectionLevel="dangerous"
+        android:label="@string/permlab_readContacts"
+        android:description="@string/permdesc_readContacts" />
+
+    <!-- Allows an application to write (but not read) the user's
+         contacts data. -->
+    <permission android:name="android.permission.WRITE_CONTACTS"
+        android:permissionGroup="android.permission-group.PERSONAL_INFO"
+        android:protectionLevel="dangerous"
+        android:label="@string/permlab_writeContacts"
+        android:description="@string/permdesc_writeContacts" />
+
+    <!-- Allows an application to read the owner's data. -->
+    <permission android:name="android.permission.READ_OWNER_DATA"
+        android:permissionGroup="android.permission-group.PERSONAL_INFO"
+        android:protectionLevel="dangerous"
+        android:label="@string/permlab_readOwnerData"
+        android:description="@string/permdesc_readOwnerData" />
+
+    <!-- Allows an application to write (but not read) the owner's data. -->
+    <permission android:name="android.permission.WRITE_OWNER_DATA"
+        android:permissionGroup="android.permission-group.PERSONAL_INFO"
+        android:protectionLevel="dangerous"
+        android:label="@string/permlab_writeOwnerData"
+        android:description="@string/permdesc_writeOwnerData" />
+
+    <!-- Allows an application to read the user's calendar data. -->
+    <permission android:name="android.permission.READ_CALENDAR"
+        android:permissionGroup="android.permission-group.PERSONAL_INFO"
+        android:protectionLevel="dangerous"
+        android:label="@string/permlab_readCalendar"
+        android:description="@string/permdesc_readCalendar" />
+
+    <!-- Allows an application to write (but not read) the user's
+         calendar data. -->
+    <permission android:name="android.permission.WRITE_CALENDAR"
+        android:permissionGroup="android.permission-group.PERSONAL_INFO"
+        android:protectionLevel="dangerous"
+        android:label="@string/permlab_writeCalendar"
+        android:description="@string/permdesc_writeCalendar" />
+
+    <!-- Allows an application to read the user dictionary. This should
+         really only be required by an IME, or a dictionary editor like 
+         the Settings app. 
+         @hide Pending API council approval -->
+    <permission android:name="android.permission.READ_USER_DICTIONARY"
+        android:permissionGroup="android.permission-group.PERSONAL_INFO"
+        android:protectionLevel="dangerous"
+        android:label="@string/permlab_readDictionary"
+        android:description="@string/permdesc_readDictionary" />
+
+    <!-- Allows an application to write to the user dictionary.
+         @hide Pending API council approval -->
+    <permission android:name="android.permission.WRITE_USER_DICTIONARY"
+        android:permissionGroup="android.permission-group.PERSONAL_INFO"
+        android:protectionLevel="normal"
+        android:label="@string/permlab_writeDictionary"
+        android:description="@string/permdesc_writeDictionary" />
+
+    <!-- ======================================= -->
+    <!-- Permissions for accessing location info -->
+    <!-- ======================================= -->
+    <eat-comment />
+    
+    <!-- Used for permissions that allow access to the user's current
+         location. -->
+    <permission-group android:name="android.permission-group.LOCATION"
+        android:label="@string/permgrouplab_location"
+        android:description="@string/permgroupdesc_location" />
+
+    <!-- Allows an application to access fine (e.g., GPS) location -->
+    <permission android:name="android.permission.ACCESS_FINE_LOCATION"
+        android:permissionGroup="android.permission-group.LOCATION"
+        android:protectionLevel="dangerous"
+        android:label="@string/permlab_accessFineLocation"
+        android:description="@string/permdesc_accessFineLocation" />
+
+    <!-- Allows an application to access coarse (e.g., Cell-ID, WiFi) location -->
+    <permission android:name="android.permission.ACCESS_COARSE_LOCATION"
+        android:permissionGroup="android.permission-group.LOCATION"
+        android:protectionLevel="dangerous"
+        android:label="@string/permlab_accessCoarseLocation"
+        android:description="@string/permdesc_accessCoarseLocation" />
+
+    <!-- Allows an application to create mock location providers for testing -->
+    <permission android:name="android.permission.ACCESS_MOCK_LOCATION"
+        android:permissionGroup="android.permission-group.LOCATION"
+        android:label="@string/permlab_accessMockLocation"
+        android:description="@string/permdesc_accessMockLocation" />
+
+    <!-- Allows an application to access extra location provider commands -->
+    <permission android:name="android.permission.ACCESS_LOCATION_EXTRA_COMMANDS"
+        android:permissionGroup="android.permission-group.LOCATION"
+        android:label="@string/permlab_accessLocationExtraCommands"
+        android:description="@string/permdesc_accessLocationExtraCommands" />
+
+    <!-- ======================================= -->
+    <!-- Permissions for accessing networks -->
+    <!-- ======================================= -->
+    <eat-comment />
+    
+    <!-- Used for permissions that provide access to networking services.  The
+         main permission here is internet access, but this is also an
+         appropriate group for accessing or modifying any network configuration
+         or other related network operations. -->
+    <permission-group android:name="android.permission-group.NETWORK"
+        android:label="@string/permgrouplab_network"
+        android:description="@string/permgroupdesc_network" />
+
+    <!-- Allows applications to open network sockets. -->
+    <permission android:name="android.permission.INTERNET"
+        android:permissionGroup="android.permission-group.NETWORK"
+        android:protectionLevel="dangerous"
+        android:description="@string/permdesc_createNetworkSockets"
+        android:label="@string/permlab_createNetworkSockets" />
+
+    <!-- Allows applications to access information about networks -->
+    <permission android:name="android.permission.ACCESS_NETWORK_STATE"
+        android:permissionGroup="android.permission-group.NETWORK"
+        android:protectionLevel="normal"
+        android:description="@string/permdesc_accessNetworkState"
+        android:label="@string/permlab_accessNetworkState" />
+
+    <!-- Allows applications to access information about Wi-Fi networks -->
+    <permission android:name="android.permission.ACCESS_WIFI_STATE"
+        android:permissionGroup="android.permission-group.NETWORK"
+        android:protectionLevel="normal"
+        android:description="@string/permdesc_accessWifiState"
+        android:label="@string/permlab_accessWifiState" />
+
+    <!-- Allows applications to connect to paired bluetooth devices -->
+    <permission android:name="android.permission.BLUETOOTH"
+        android:permissionGroup="android.permission-group.NETWORK"
+        android:protectionLevel="dangerous"
+        android:description="@string/permdesc_bluetooth"
+        android:label="@string/permlab_bluetooth" />
+
+    <!-- ================================== -->
+    <!-- Permissions for accessing accounts -->
+    <!-- ================================== -->
+    <eat-comment />
+    
+    <!-- Permissions for direct access to Google accounts.
+         Note that while right now this is only used for Google accounts,
+         we expect in the future to have a more general account management
+         facility so this is specified as a general platform permission
+         group for accessing accounts. -->
+    <permission-group android:name="android.permission-group.ACCOUNTS"
+        android:label="@string/permgrouplab_accounts"
+        android:description="@string/permgroupdesc_accounts" />
+
+    <!-- Allows access to the list of accounts in the Accounts Service -->
+    <permission android:name="android.permission.GET_ACCOUNTS"
+        android:permissionGroup="android.permission-group.ACCOUNTS"
+        android:protectionLevel="normal"
+        android:description="@string/permdesc_getAccounts"
+        android:label="@string/permlab_getAccounts" />
+
+    <!-- ================================== -->
+    <!-- Permissions for accessing hardware -->
+    <!-- ================================== -->
+    <eat-comment />
+    
+    <!-- Used for permissions that provide direct access to the hardware on
+         the device.  This includes audio, the camera, vibrator, etc. -->
+    <permission-group android:name="android.permission-group.HARDWARE_CONTROLS"
+        android:label="@string/permgrouplab_hardwareControls"
+        android:description="@string/permgroupdesc_hardwareControls" />
+
+    <!-- Allows an application to modify global audio settings -->
+    <permission android:name="android.permission.MODIFY_AUDIO_SETTINGS"
+        android:permissionGroup="android.permission-group.HARDWARE_CONTROLS"
+        android:protectionLevel="dangerous"
+        android:label="@string/permlab_modifyAudioSettings"
+        android:description="@string/permdesc_modifyAudioSettings" />
+
+    <!-- Allows an application to record audio -->
+    <permission android:name="android.permission.RECORD_AUDIO"
+        android:permissionGroup="android.permission-group.HARDWARE_CONTROLS"
+        android:protectionLevel="dangerous"
+        android:label="@string/permlab_recordAudio"
+        android:description="@string/permdesc_recordAudio" />
+
+    <!-- Required to be able to access the camera device. -->
+    <permission android:name="android.permission.CAMERA"
+        android:permissionGroup="android.permission-group.HARDWARE_CONTROLS"
+        android:protectionLevel="dangerous"
+        android:label="@string/permlab_camera"
+        android:description="@string/permdesc_camera" />
+
+    <!-- Allows access to the vibrator -->
+    <permission android:name="android.permission.VIBRATE"
+        android:permissionGroup="android.permission-group.HARDWARE_CONTROLS"
+        android:protectionLevel="normal"
+        android:label="@string/permlab_vibrate"
+        android:description="@string/permdesc_vibrate" />
+
+    <!-- Allows access to the flashlight -->
+    <permission android:name="android.permission.FLASHLIGHT"
+        android:permissionGroup="android.permission-group.HARDWARE_CONTROLS"
+        android:protectionLevel="normal"
+        android:label="@string/permlab_flashlight"
+        android:description="@string/permdesc_flashlight" />
+
+    <!-- Allows access to hardware peripherals.  Intended only for hardware testing -->
+    <permission android:name="android.permission.HARDWARE_TEST"
+        android:permissionGroup="android.permission-group.HARDWARE_CONTROLS"
+        android:protectionLevel="signature"
+        android:label="@string/permlab_hardware_test"
+        android:description="@string/permdesc_hardware_test" />
+
+    <!-- =========================================== -->
+    <!-- Permissions associated with telephony state -->
+    <!-- =========================================== -->
+    <eat-comment />
+    
+    <!-- Used for permissions that are associated with accessing and modifyign
+         telephony state: intercepting outgoing calls, reading
+         and modifying the phone state.  Note that
+         placing phone calls is not in this group, since that is in the
+         more important "takin' yer moneys" group. -->
+    <permission-group android:name="android.permission-group.PHONE_CALLS"
+        android:label="@string/permgrouplab_phoneCalls"
+        android:description="@string/permgroupdesc_phoneCalls" />
+
+    <!-- Allows an application to monitor, modify, or abort outgoing
+         calls. -->
+    <permission android:name="android.permission.PROCESS_OUTGOING_CALLS"
+        android:permissionGroup="android.permission-group.PHONE_CALLS"
+        android:protectionLevel="dangerous"
+        android:label="@string/permlab_processOutgoingCalls"
+        android:description="@string/permdesc_processOutgoingCalls" />
+
+    <!-- Allows modification of the telephony state - power on, mmi, etc.
+         Does not include placing calls. -->
+    <permission android:name="android.permission.MODIFY_PHONE_STATE"
+        android:permissionGroup="android.permission-group.PHONE_CALLS"
+        android:protectionLevel="dangerous"
+        android:label="@string/permlab_modifyPhoneState"
+        android:description="@string/permdesc_modifyPhoneState" />
+
+    <!-- Allows read only access to phone state. -->
+    <permission android:name="android.permission.READ_PHONE_STATE"
+        android:permissionGroup="android.permission-group.PHONE_CALLS"
+        android:protectionLevel="dangerous"
+        android:label="@string/permlab_readPhoneState"
+        android:description="@string/permdesc_readPhoneState" />
+
+    <!-- ============================================ -->
+    <!-- Permissions for low-level system interaction -->
+    <!-- ============================================ -->
+    <eat-comment />
+    
+    <!-- Group of permissions that are related to system APIs.  Many
+         of these are not permissions the user will be expected to understand,
+         and such permissions should generally be marked as "normal" protection
+         level so they don't get displayed.  This can also, however, be used
+         for miscellaneous features that provide access to the operating system,
+         such as writing the global system settings. -->
+    <permission-group android:name="android.permission-group.SYSTEM_TOOLS"
+        android:label="@string/permgrouplab_systemTools"
+        android:description="@string/permgroupdesc_systemTools" />
+
+    <!-- Allows an application to read or write the system settings. -->
+    <permission android:name="android.permission.WRITE_SETTINGS"
+        android:permissionGroup="android.permission-group.SYSTEM_TOOLS"
+        android:protectionLevel="dangerous"
+        android:label="@string/permlab_writeSettings"
+        android:description="@string/permdesc_writeSettings" />
+
+    <!-- Allows an application to read or write the secure system settings. -->
+    <permission android:name="android.permission.WRITE_SECURE_SETTINGS"
+        android:protectionLevel="signatureOrSystem"
+        android:label="@string/permlab_writeSecureSettings"
+        android:description="@string/permdesc_writeSecureSettings" />
+
+    <!-- Allows an application to modify the Google service map. -->
+    <permission android:name="android.permission.WRITE_GSERVICES"
+        android:protectionLevel="signature"
+        android:label="@string/permlab_writeGservices"
+        android:description="@string/permdesc_writeGservices" />
+
+    <!-- Allows an application to expand or collapse the status bar. -->
+    <permission android:name="android.permission.EXPAND_STATUS_BAR"
+        android:permissionGroup="android.permission-group.SYSTEM_TOOLS"
+        android:protectionLevel="normal"
+        android:label="@string/permlab_expandStatusBar"
+        android:description="@string/permdesc_expandStatusBar" />
+
+    <!-- Allows an application to get information about the currently
+         or recently running tasks: a thumbnail representation of the tasks,
+         what activities are running in it, etc. -->
+    <permission android:name="android.permission.GET_TASKS"
+        android:permissionGroup="android.permission-group.SYSTEM_TOOLS"
+        android:protectionLevel="dangerous"
+        android:label="@string/permlab_getTasks"
+        android:description="@string/permdesc_getTasks" />
+
+    <!-- Allows an application to change the Z-order of tasks -->
+    <permission android:name="android.permission.REORDER_TASKS"
+        android:permissionGroup="android.permission-group.SYSTEM_TOOLS"
+        android:protectionLevel="dangerous"
+        android:label="@string/permlab_reorderTasks"
+        android:description="@string/permdesc_reorderTasks" />
+
+    <!-- Allows an application to modify the current configuration, such
+         as locale. -->
+    <permission android:name="android.permission.CHANGE_CONFIGURATION"
+        android:permissionGroup="android.permission-group.SYSTEM_TOOLS"
+        android:protectionLevel="dangerous"
+        android:label="@string/permlab_changeConfiguration"
+        android:description="@string/permdesc_changeConfiguration" />
+
+    <!-- Allows an application to restart other applications. -->
+    <permission android:name="android.permission.RESTART_PACKAGES"
+        android:permissionGroup="android.permission-group.SYSTEM_TOOLS"
+        android:protectionLevel="dangerous"
+        android:label="@string/permlab_restartPackages"
+        android:description="@string/permdesc_restartPackages" />
+
+    <!-- Allows an application to retrieve state dump information from system
+         services. -->
+    <permission android:name="android.permission.DUMP"
+        android:permissionGroup="android.permission-group.SYSTEM_TOOLS"
+        android:protectionLevel="dangerous"
+        android:label="@string/permlab_dump"
+        android:description="@string/permdesc_dump" />
+
+    <!-- Allows an application to open windows using the type
+         {@link android.view.WindowManager.LayoutParams#TYPE_SYSTEM_ALERT},
+         shown on top of all other applications.  Very few applications
+         should use this permission; these windows are intended for
+         system-level interaction with the user. -->
+    <permission android:name="android.permission.SYSTEM_ALERT_WINDOW"
+        android:permissionGroup="android.permission-group.SYSTEM_TOOLS"
+        android:protectionLevel="dangerous"
+        android:label="@string/permlab_systemAlertWindow"
+        android:description="@string/permdesc_systemAlertWindow" />
+
+    <!-- Modify the global animation scaling factor. -->
+    <permission android:name="android.permission.SET_ANIMATION_SCALE"
+        android:permissionGroup="android.permission-group.SYSTEM_TOOLS"
+        android:protectionLevel="dangerous"
+        android:label="@string/permlab_setAnimationScale"
+        android:description="@string/permdesc_setAnimationScale" />
+
+    <!-- Allow an application to make its activities persistent. -->
+    <permission android:name="android.permission.PERSISTENT_ACTIVITY"
+        android:permissionGroup="android.permission-group.SYSTEM_TOOLS"
+        android:protectionLevel="dangerous"
+        android:label="@string/permlab_persistentActivity"
+        android:description="@string/permdesc_persistentActivity" />
+
+    <!-- Allows an application to find out the space used by any package. -->
+    <permission android:name="android.permission.GET_PACKAGE_SIZE"
+        android:permissionGroup="android.permission-group.SYSTEM_TOOLS"
+        android:protectionLevel="normal"
+        android:label="@string/permlab_getPackageSize"
+        android:description="@string/permdesc_getPackageSize" />
+
+    <!-- Allows an application to modify the list of preferred applications
+         with the {@link android.content.pm.PackageManager#addPackageToPreferred
+         PackageManager.addPackageToPreferred()} and
+         {@link android.content.pm.PackageManager#removePackageFromPreferred
+         PackageManager.removePackageFromPreferred()} methods. -->
+    <permission android:name="android.permission.SET_PREFERRED_APPLICATIONS"
+        android:permissionGroup="android.permission-group.SYSTEM_TOOLS"
+        android:protectionLevel="dangerous"
+        android:label="@string/permlab_setPreferredApplications"
+        android:description="@string/permdesc_setPreferredApplications" />
+
+    <!-- Allows an application to receive the
+         {@link android.content.Intent#ACTION_BOOT_COMPLETED} that is
+         broadcast after the system finishes booting.  If you don't
+         request this permission, you will not receive the broadcast at
+         that time.  Though holding this permission does not have any
+         security implications, it can have a negative impact on the
+         user experience by increasing the amount of time it takes the
+         system to start and allowing applications to have themselves
+         running without the user being aware of them.  As such, you must
+         explicitly declare your use of this facility to make that visible
+         to the user. -->
+    <permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"
+        android:permissionGroup="android.permission-group.SYSTEM_TOOLS"
+        android:protectionLevel="normal"
+        android:label="@string/permlab_receiveBootCompleted"
+        android:description="@string/permdesc_receiveBootCompleted" />
+
+    <!-- Allows an application to broadcast sticky intents.  These are
+         broadcasts whose data is held by the system after being finished,
+         so that clients can quickly retrieve that data without having
+         to wait for the next broadcast. -->
+    <permission android:name="android.permission.BROADCAST_STICKY"
+        android:permissionGroup="android.permission-group.SYSTEM_TOOLS"
+        android:protectionLevel="normal"
+        android:label="@string/permlab_broadcastSticky"
+        android:description="@string/permdesc_broadcastSticky" />
+
+    <!-- Allows using PowerManager WakeLocks to keep processor from sleeping or screen
+         from dimming -->
+    <permission android:name="android.permission.WAKE_LOCK"
+        android:permissionGroup="android.permission-group.SYSTEM_TOOLS"
+        android:protectionLevel="dangerous"
+        android:label="@string/permlab_wakeLock"
+        android:description="@string/permdesc_wakeLock" />
+
+    <!-- Allows applications to set the wallpaper -->
+    <permission android:name="android.permission.SET_WALLPAPER"
+        android:permissionGroup="android.permission-group.SYSTEM_TOOLS"
+        android:protectionLevel="normal"
+        android:label="@string/permlab_setWallpaper"
+        android:description="@string/permdesc_setWallpaper" />
+
+    <!-- Allows applications to set the wallpaper hints -->
+    <permission android:name="android.permission.SET_WALLPAPER_HINTS"
+        android:permissionGroup="android.permission-group.SYSTEM_TOOLS"
+        android:protectionLevel="normal"
+        android:label="@string/permlab_setWallpaperHints"
+        android:description="@string/permdesc_setWallpaperHints" />
+
+    <!-- Allows applications to set the system time zone -->
+    <permission android:name="android.permission.SET_TIME_ZONE"
+        android:permissionGroup="android.permission-group.SYSTEM_TOOLS"
+        android:protectionLevel="dangerous"
+        android:label="@string/permlab_setTimeZone"
+        android:description="@string/permdesc_setTimeZone" />
+
+    <!-- Allows mounting and unmounting file systems for removable storage. -->
+    <permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"
+        android:permissionGroup="android.permission-group.SYSTEM_TOOLS"
+        android:protectionLevel="dangerous"
+        android:label="@string/permlab_mount_unmount_filesystems"
+        android:description="@string/permdesc_mount_unmount_filesystems" />
+
+    <!-- Allows formatting file systems for removable storage. -->
+    <permission android:name="android.permission.MOUNT_FORMAT_FILESYSTEMS"
+        android:permissionGroup="android.permission-group.SYSTEM_TOOLS"
+        android:protectionLevel="dangerous"
+        android:label="@string/permlab_mount_format_filesystems"
+        android:description="@string/permdesc_mount_format_filesystems" />
+
+    <!-- Allows applications to disable the keyguard -->
+    <permission android:name="android.permission.DISABLE_KEYGUARD"
+        android:permissionGroup="android.permission-group.SYSTEM_TOOLS"
+        android:protectionLevel="normal"
+        android:description="@string/permdesc_disableKeyguard"
+        android:label="@string/permlab_disableKeyguard" />
+
+    <!-- Allows applications to read the sync settings -->
+    <permission android:name="android.permission.READ_SYNC_SETTINGS"
+        android:permissionGroup="android.permission-group.SYSTEM_TOOLS"
+        android:protectionLevel="normal"
+        android:description="@string/permdesc_readSyncSettings"
+        android:label="@string/permlab_readSyncSettings" />
+
+    <!-- Allows applications to write the sync settings -->
+    <permission android:name="android.permission.WRITE_SYNC_SETTINGS"
+        android:permissionGroup="android.permission-group.SYSTEM_TOOLS"
+        android:protectionLevel="dangerous"
+        android:description="@string/permdesc_writeSyncSettings"
+        android:label="@string/permlab_writeSyncSettings" />
+
+    <!-- Allows applications to read the sync stats -->
+    <permission android:name="android.permission.READ_SYNC_STATS"
+        android:permissionGroup="android.permission-group.SYSTEM_TOOLS"
+        android:protectionLevel="normal"
+        android:description="@string/permdesc_readSyncStats"
+        android:label="@string/permlab_readSyncStats" />
+
+    <!-- Allows applications to write the apn settings -->
+    <permission android:name="android.permission.WRITE_APN_SETTINGS"
+                android:permissionGroup="android.permission-group.SYSTEM_TOOLS"
+                android:protectionLevel="dangerous"
+                android:description="@string/permdesc_writeApnSettings"
+                android:label="@string/permlab_writeApnSettings" />
+
+    <!-- Allows an application to allow access the subscribed feeds 
+         ContentProvider. -->
+    <permission android:name="android.permission.SUBSCRIBED_FEEDS_READ"
+        android:permissionGroup="android.permission-group.SYSTEM_TOOLS"
+        android:label="@string/permlab_subscribedFeedsRead"
+        android:description="@string/permdesc_subscribedFeedsRead"
+        android:protectionLevel="normal" />
+    <permission android:name="android.permission.SUBSCRIBED_FEEDS_WRITE"
+        android:permissionGroup="android.permission-group.SYSTEM_TOOLS"
+        android:label="@string/permlab_subscribedFeedsWrite"
+        android:description="@string/permdesc_subscribedFeedsWrite"
+        android:protectionLevel="dangerous" />
+        
+    <!-- Allows applications to change network connectivity state -->
+    <permission android:name="android.permission.CHANGE_NETWORK_STATE"
+        android:permissionGroup="android.permission-group.SYSTEM_TOOLS"
+        android:protectionLevel="dangerous"
+        android:description="@string/permdesc_changeNetworkState"
+        android:label="@string/permlab_changeNetworkState" />
+
+    <!-- Allows applications to change Wi-Fi connectivity state -->
+    <permission android:name="android.permission.CHANGE_WIFI_STATE"
+        android:permissionGroup="android.permission-group.SYSTEM_TOOLS"
+        android:protectionLevel="dangerous"
+        android:description="@string/permdesc_changeWifiState"
+        android:label="@string/permlab_changeWifiState" />
+
+    <!-- Allows applications to discover and pair bluetooth devices -->
+    <permission android:name="android.permission.BLUETOOTH_ADMIN"
+        android:permissionGroup="android.permission-group.SYSTEM_TOOLS"
+        android:protectionLevel="dangerous"
+        android:description="@string/permdesc_bluetoothAdmin"
+        android:label="@string/permlab_bluetoothAdmin" />
+
+    <!-- Allows an application to clear the caches of all installed
+         applications on the device.  -->
+    <permission android:name="android.permission.CLEAR_APP_CACHE"
+        android:permissionGroup="android.permission-group.SYSTEM_TOOLS"
+        android:protectionLevel="dangerous"
+        android:label="@string/permlab_clearAppCache"
+        android:description="@string/permdesc_clearAppCache" />
+
+    <!-- Allows an application to read the low-level system log files.
+         These can contain slightly private information about what is
+         happening on the device, but should never contain the user's
+         private information. -->
+    <permission android:name="android.permission.READ_LOGS"
+        android:permissionGroup="android.permission-group.SYSTEM_TOOLS"
+        android:protectionLevel="dangerous"
+        android:label="@string/permlab_readLogs"
+        android:description="@string/permdesc_readLogs" />
+
+    <!-- ========================================= -->
+    <!-- Permissions for special development tools -->
+    <!-- ========================================= -->
+    <eat-comment />
+    
+    <!-- Group of permissions that are related to development features.  These
+         are not permissions that should appear in normal applications; they
+         protect APIs that are intended only to be used for development
+         purposes. -->
+    <permission-group android:name="android.permission-group.DEVELOPMENT_TOOLS"
+        android:label="@string/permgrouplab_developmentTools"
+        android:description="@string/permgroupdesc_developmentTools" />
+
+    <!-- Configure an application for debugging. -->
+    <permission android:name="android.permission.SET_DEBUG_APP"
+        android:permissionGroup="android.permission-group.DEVELOPMENT_TOOLS"
+        android:protectionLevel="dangerous"
+        android:label="@string/permlab_setDebugApp"
+        android:description="@string/permdesc_setDebugApp" />
+
+    <!-- Allows an application to set the maximum number of (not needed)
+         application processes that can be running. -->
+    <permission android:name="android.permission.SET_PROCESS_LIMIT"
+        android:permissionGroup="android.permission-group.DEVELOPMENT_TOOLS"
+        android:protectionLevel="dangerous"
+        android:label="@string/permlab_setProcessLimit"
+        android:description="@string/permdesc_setProcessLimit" />
+
+    <!-- Allows an application to control whether activities are immediately
+         finished when put in the background. -->
+    <permission android:name="android.permission.SET_ALWAYS_FINISH"
+        android:permissionGroup="android.permission-group.DEVELOPMENT_TOOLS"
+        android:protectionLevel="dangerous"
+        android:label="@string/permlab_setAlwaysFinish"
+        android:description="@string/permdesc_setAlwaysFinish" />
+
+    <!-- Allow an application to request that a signal be sent to all persistent processes -->
+    <permission android:name="android.permission.SIGNAL_PERSISTENT_PROCESSES"
+        android:permissionGroup="android.permission-group.DEVELOPMENT_TOOLS"
+        android:protectionLevel="dangerous"
+        android:label="@string/permlab_signalPersistentProcesses"
+        android:description="@string/permdesc_signalPersistentProcesses" />
+
+    <!-- ==================================== -->
+    <!-- Private (signature-only) permissions -->
+    <!-- ==================================== -->
+    <eat-comment />
+
+    <!-- Allows applications to RW to diagnostic resources. -->
+    <permission android:name="android.permission.DIAGNOSTIC"
+        android:permissionGroup="android.permission-group.SYSTEM_TOOLS"
+        android:protectionLevel="signature"
+        android:description="@string/permdesc_diagnostic"
+        android:label="@string/permlab_diagnostic" />
+
+    <!-- Allows an application to open, close, or disable the status bar
+         and its icons. -->
+    <permission android:name="android.permission.STATUS_BAR"
+        android:label="@string/permlab_statusBar"
+        android:description="@string/permdesc_statusBar"
+        android:protectionLevel="signatureOrSystem" />
+
+    <!-- Allows an application to force any currently running process to be
+         in the foreground. -->
+    <permission android:name="android.permission.SET_PROCESS_FOREGROUND"
+        android:label="@string/permlab_setProcessForeground"
+        android:description="@string/permdesc_setProcessForeground"
+        android:protectionLevel="signature" />
+
+    <!-- Allows an application to force a BACK operation on whatever is the
+         top activity. -->
+    <permission android:name="android.permission.FORCE_BACK"
+        android:label="@string/permlab_forceBack"
+        android:description="@string/permdesc_forceBack"
+        android:protectionLevel="signature" />
+
+    <!-- Allows an application to publish system-level services.  Such services
+         can only be published from processes that never go away, so this is
+         not something that any normal application can do. -->
+    <permission android:name="android.permission.ADD_SYSTEM_SERVICE"
+        android:label="@string/permlab_addSystemService"
+        android:description="@string/permdesc_addSystemService"
+        android:protectionLevel="signature" />
+
+    <permission android:name="android.permission.FOTA_UPDATE"
+        android:label="@string/permlab_fotaUpdate"
+        android:description="@string/permdesc_fotaUpdate"
+        android:protectionLevel="signature" />
+
+    <!-- Allows an application to update device statistics. Not for
+         use by third party apps. -->
+    <permission android:name="android.permission.UPDATE_DEVICE_STATS"
+        android:label="@string/permlab_batteryStats"
+        android:description="@string/permdesc_batteryStats"
+        android:protectionLevel="signature" />
+
+    <!-- Allows an application to open windows that are for use by parts
+         of the system user interface.  Not for use by third party apps. -->
+    <permission android:name="android.permission.INTERNAL_SYSTEM_WINDOW"
+        android:label="@string/permlab_internalSystemWindow"
+        android:description="@string/permdesc_internalSystemWindow"
+        android:protectionLevel="signature" />
+
+    <!-- Allows an application to manage (create, destroy,
+         Z-order) application tokens in the window manager.  This is only
+         for use by the system. -->
+    <permission android:name="android.permission.MANAGE_APP_TOKENS"
+        android:label="@string/permlab_manageAppTokens"
+        android:description="@string/permdesc_manageAppTokens"
+        android:protectionLevel="signature" />
+
+    <!-- Allows an application to inject user events (keys, touch, trackball)
+         into the event stream and deliver them to ANY window.  Without this
+         permission, you can only deliver events to windows in your own process.
+         Very few applications should need to use this permission. -->
+    <permission android:name="android.permission.INJECT_EVENTS"
+        android:label="@string/permlab_injectEvents"
+        android:description="@string/permdesc_injectEvents"
+        android:protectionLevel="signature" />
+
+    <!-- Allows an application to watch and control how activities are
+         started globally in the system.  Only for is in debugging
+         (usually the monkey command). -->
+    <permission android:name="android.permission.SET_ACTIVITY_WATCHER"
+        android:label="@string/permlab_runSetActivityWatcher"
+        android:description="@string/permdesc_runSetActivityWatcher"
+        android:protectionLevel="signature" />
+
+    <!-- Allows an application to retrieve the current state of keys and
+         switches.  This is only for use by the system.-->
+    <permission android:name="android.permission.READ_INPUT_STATE"
+        android:label="@string/permlab_readInputState"
+        android:description="@string/permdesc_readInputState"
+        android:protectionLevel="signature" />
+
+    <!-- Must be required by input method services, to ensure that only the
+         system can bind to them. -->
+    <permission android:name="android.permission.BIND_INPUT_METHOD"
+        android:label="@string/permlab_bindInputMethod"
+        android:description="@string/permdesc_bindInputMethod"
+        android:protectionLevel="signature" />
+
+    <!-- Allows low-level access to setting the orientation (actually
+         rotation) of the screen.  Not for use by normal applications. -->
+    <permission android:name="android.permission.SET_ORIENTATION"
+        android:label="@string/permlab_setOrientation"
+        android:description="@string/permdesc_setOrientation"
+        android:protectionLevel="signature" />
+
+    <!-- Allows an application to install packages. -->
+    <permission android:name="android.permission.INSTALL_PACKAGES"
+        android:label="@string/permlab_installPackages"
+        android:description="@string/permdesc_installPackages"
+        android:protectionLevel="signature" />
+
+    <!-- Allows an application to clear user data -->
+    <permission android:name="android.permission.CLEAR_APP_USER_DATA"
+        android:label="@string/permlab_clearAppUserData"
+        android:description="@string/permdesc_clearAppUserData"
+        android:protectionLevel="signature" />
+
+    <!-- Allows an application to delete cache files. -->
+    <permission android:name="android.permission.DELETE_CACHE_FILES"
+        android:label="@string/permlab_deleteCacheFiles"
+        android:description="@string/permdesc_deleteCacheFiles"
+        android:protectionLevel="signature" />
+
+    <!-- Allows an application to delete packages. -->
+    <permission android:name="android.permission.DELETE_PACKAGES"
+        android:label="@string/permlab_deletePackages"
+        android:description="@string/permdesc_deletePackages"
+        android:protectionLevel="signature" />
+
+    <!-- Allows an application to change whether an application component (other than its own) is
+         enabled or not. -->
+    <permission android:name="android.permission.CHANGE_COMPONENT_ENABLED_STATE"
+        android:label="@string/permlab_changeComponentState"
+        android:description="@string/permdesc_changeComponentState"
+        android:protectionLevel="signature" />
+
+    <!-- Allows an application to use SurfaceFlinger's low level features -->
+    <permission android:name="android.permission.ACCESS_SURFACE_FLINGER"
+        android:label="@string/permlab_accessSurfaceFlinger"
+        android:description="@string/permdesc_accessSurfaceFlinger"
+        android:protectionLevel="signature" />
+
+    <!-- Allows an application to take screen shots and more generally
+         get access to the frame buffer data -->
+    <permission android:name="android.permission.READ_FRAME_BUFFER"
+        android:label="@string/permlab_readFrameBuffer"
+        android:description="@string/permdesc_readFrameBuffer"
+        android:protectionLevel="signature" />
+
+    <!-- Required to be able to disable the device (very dangerous!). -->
+    <permission android:name="android.permission.BRICK"
+        android:label="@string/permlab_brick"
+        android:description="@string/permdesc_brick"
+        android:protectionLevel="signature" />
+
+    <!-- Required to be able to reboot the device. -->
+    <permission android:name="android.permission.REBOOT"
+        android:label="@string/permlab_reboot"
+        android:description="@string/permdesc_reboot"
+        android:protectionLevel="signature" />
+
+   <!-- Allows low-level access to power management -->
+    <permission android:name="android.permission.DEVICE_POWER"
+        android:label="@string/permlab_devicePower"
+        android:description="@string/permdesc_devicePower"
+        android:protectionLevel="signature" />
+
+    <!-- Run as a manufacturer test application, running as the root user.
+         Only available when the device is running in manufacturer test mode. -->
+    <permission android:name="android.permission.FACTORY_TEST"
+        android:label="@string/permlab_factoryTest"
+        android:description="@string/permdesc_factoryTest"
+        android:protectionLevel="signature" />
+
+    <!-- Allows an application to broadcast a notification that an application
+         package has been removed. -->
+    <permission android:name="android.permission.BROADCAST_PACKAGE_REMOVED"
+        android:permissionGroup="android.permission-group.SYSTEM_TOOLS"
+        android:label="@string/permlab_broadcastPackageRemoved"
+        android:description="@string/permdesc_broadcastPackageRemoved"
+        android:protectionLevel="signature" />
+
+    <!-- Allows an application to broadcast an SMS receipt notification -->
+    <permission android:name="android.permission.BROADCAST_SMS"
+        android:permissionGroup="android.permission-group.MESSAGES"
+        android:label="@string/permlab_broadcastSmsReceived"
+        android:description="@string/permdesc_broadcastSmsReceived"
+        android:protectionLevel="signature" />
+
+    <!-- Allows an application to broadcast a WAP PUSH receipt notification -->
+    <permission android:name="android.permission.BROADCAST_WAP_PUSH"
+        android:permissionGroup="android.permission-group.MESSAGES"
+        android:label="@string/permlab_broadcastWapPush"
+        android:description="@string/permdesc_broadcastWapPush"
+        android:protectionLevel="signature" />
+
+    <permission android:name="android.permission.MASTER_CLEAR"
+        android:label="@string/permlab_masterClear"
+        android:description="@string/permdesc_masterClear"
+        android:protectionLevel="signatureOrSystem" />
+
+    <!-- Allows an application to call any phone number, including emergency
+         numbers, without going through the Dialer user interface for the user
+         to confirm the call being placed. -->
+    <permission android:name="android.permission.CALL_PRIVILEGED"
+        android:label="@string/permlab_callPrivileged"
+        android:description="@string/permdesc_callPrivileged"
+        android:protectionLevel="signatureOrSystem" />
+
+    <!-- Allows enabling/disabling location update notifications from
+         the radio. Not for use by normal applications. -->
+    <permission android:name="android.permission.CONTROL_LOCATION_UPDATES"
+        android:label="@string/permlab_locationUpdates"
+        android:description="@string/permdesc_locationUpdates"
+        android:protectionLevel="signature" />
+
+    <!-- Allows read/write access to the "properties" table in the checkin
+         database, to change values that get uploaded. -->
+    <permission android:name="android.permission.ACCESS_CHECKIN_PROPERTIES"
+        android:label="@string/permlab_checkinProperties"
+        android:description="@string/permdesc_checkinProperties"
+        android:protectionLevel="signature" />
+
+    <!-- Allows an application to collect component usage
+         statistics -->
+    <permission android:name="android.permission.PACKAGE_USAGE_STATS"
+        android:label="@string/permlab_pkgUsageStats"
+        android:description="@string/permdesc_pkgUsageStats"
+        android:protectionLevel="normal" />
+
+    <!-- Allows an application to collect battery statistics -->
+    <permission android:name="android.permission.BATTERY_STATS"
+        android:label="@string/permlab_batteryStats"
+        android:description="@string/permdesc_batteryStats"
+        android:protectionLevel="normal" />
+
+    <!-- Allows an application to tell the gadget service which application
+         can access gadget's data.  The normal user flow is that a user
+         picks a gadget to go into a particular host, thereby giving that
+         host application access to the private data from the gadget app.
+         An application that has this permission should honor that contract.
+         Very few applications should need to use this permission. -->
+    <permission android:name="android.permission.BIND_GADGET"
+        android:permissionGroup="android.permission-group.PERSONAL_INFO"
+        android:label="@string/permlab_bindGadget"
+        android:description="@string/permdesc_bindGadget"
+        android:protectionLevel="signature" />
+
+    <!-- Allows applications to change the background data setting
+         @hide pending API council -->
+    <permission android:name="android.permission.CHANGE_BACKGROUND_DATA_SETTING"
+        android:permissionGroup="android.permission-group.SYSTEM_TOOLS"
+        android:protectionLevel="signature"
+        android:description="@string/permdesc_changeBackgroundDataSetting"
+        android:label="@string/permlab_changeBackgroundDataSetting" />
+
+    <application android:process="system"
+                 android:persistent="true"
+                 android:hasCode="false"
+                 android:label="@string/android_system_label"
+                 android:allowClearUserData="false"
+                 android:icon="@drawable/ic_launcher_android">
+        <activity android:name="com.android.internal.app.ChooserActivity"
+                android:theme="@style/Theme.Dialog.Alert"
+                android:excludeFromRecents="true"
+                android:multiprocess="true">
+            <intent-filter>
+                <action android:name="android.intent.action.CHOOSER" />
+                <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
+        </activity>
+        <activity android:name="com.android.internal.app.RingtonePickerActivity"
+                android:theme="@style/Theme.Dialog.Alert"
+                android:excludeFromRecents="true"
+                android:multiprocess="true">
+            <intent-filter>
+                <action android:name="android.intent.action.RINGTONE_PICKER" />
+                <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
+        </activity>
+        <activity android:name="com.android.internal.app.UsbStorageActivity"
+                android:theme="@style/Theme.Dialog.Alert"
+                android:excludeFromRecents="true">
+        </activity>
+        <activity android:name="com.android.internal.app.UsbStorageStopActivity"
+                android:theme="@style/Theme.Dialog.Alert"
+                android:excludeFromRecents="true">
+        </activity>
+        <activity android:name="com.android.internal.app.ExternalMediaFormatActivity"
+                android:theme="@style/Theme.Dialog.Alert"
+                android:excludeFromRecents="true">
+        </activity>
+
+        <provider android:name=".content.SyncProvider"
+                android:authorities="sync" android:multiprocess="false" />
+
+        <service android:name="com.android.server.LoadAverageService"
+            android:exported="true" />
+
+        <receiver android:name="com.android.server.BootReceiver" >
+            <intent-filter>
+                <action android:name="android.intent.action.BOOT_COMPLETED" />
+            </intent-filter>
+        </receiver>
+
+        <receiver android:name="com.android.server.MasterClearReceiver"
+            android:permission="android.permission.MASTER_CLEAR" >
+            <intent-filter>
+                <action android:name="android.intent.action.GTALK_DATA_MESSAGE_RECEIVED" />
+                <category android:name="android.intent.category.MASTER_CLEAR" />
+            </intent-filter>
+        </receiver>
+    </application>
+
+</manifest>
+
+
diff --git a/core/res/MODULE_LICENSE_APACHE2 b/core/res/MODULE_LICENSE_APACHE2
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/core/res/MODULE_LICENSE_APACHE2
diff --git a/core/res/NOTICE b/core/res/NOTICE
new file mode 100644
index 0000000..c5b1efa
--- /dev/null
+++ b/core/res/NOTICE
@@ -0,0 +1,190 @@
+
+   Copyright (c) 2005-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.
+
+   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.
+
+
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
diff --git a/core/res/assets/images/android_320x480.png b/core/res/assets/images/android_320x480.png
new file mode 100644
index 0000000..d2665bb
--- /dev/null
+++ b/core/res/assets/images/android_320x480.png
Binary files differ
diff --git a/core/res/assets/images/boot_robot.png b/core/res/assets/images/boot_robot.png
new file mode 100644
index 0000000..f4555af
--- /dev/null
+++ b/core/res/assets/images/boot_robot.png
Binary files differ
diff --git a/core/res/assets/images/boot_robot_glow.png b/core/res/assets/images/boot_robot_glow.png
new file mode 100644
index 0000000..2a67a3f
--- /dev/null
+++ b/core/res/assets/images/boot_robot_glow.png
Binary files differ
diff --git a/core/res/assets/images/combobox-disabled.png b/core/res/assets/images/combobox-disabled.png
new file mode 100644
index 0000000..42fc0c5
--- /dev/null
+++ b/core/res/assets/images/combobox-disabled.png
Binary files differ
diff --git a/core/res/assets/images/combobox-noHighlight.png b/core/res/assets/images/combobox-noHighlight.png
new file mode 100644
index 0000000..838dc65
--- /dev/null
+++ b/core/res/assets/images/combobox-noHighlight.png
Binary files differ
diff --git a/core/res/assets/images/cylon_dot.png b/core/res/assets/images/cylon_dot.png
new file mode 100644
index 0000000..150b8b1
--- /dev/null
+++ b/core/res/assets/images/cylon_dot.png
Binary files differ
diff --git a/core/res/assets/images/cylon_left.png b/core/res/assets/images/cylon_left.png
new file mode 100644
index 0000000..50c6296
--- /dev/null
+++ b/core/res/assets/images/cylon_left.png
Binary files differ
diff --git a/core/res/assets/images/cylon_right.png b/core/res/assets/images/cylon_right.png
new file mode 100644
index 0000000..89b7d71
--- /dev/null
+++ b/core/res/assets/images/cylon_right.png
Binary files differ
diff --git a/core/res/assets/sounds/bootanim0.raw b/core/res/assets/sounds/bootanim0.raw
new file mode 100644
index 0000000..46b8c0f
--- /dev/null
+++ b/core/res/assets/sounds/bootanim0.raw
Binary files differ
diff --git a/core/res/assets/sounds/bootanim1.raw b/core/res/assets/sounds/bootanim1.raw
new file mode 100644
index 0000000..ce69944
--- /dev/null
+++ b/core/res/assets/sounds/bootanim1.raw
Binary files differ
diff --git a/core/res/assets/webkit/android-weberror.png b/core/res/assets/webkit/android-weberror.png
new file mode 100644
index 0000000..cea4638
--- /dev/null
+++ b/core/res/assets/webkit/android-weberror.png
Binary files differ
diff --git a/core/res/assets/webkit/missingImage.png b/core/res/assets/webkit/missingImage.png
new file mode 100644
index 0000000..f49a98d
--- /dev/null
+++ b/core/res/assets/webkit/missingImage.png
Binary files differ
diff --git a/core/res/assets/webkit/nullplugin.png b/core/res/assets/webkit/nullplugin.png
new file mode 100644
index 0000000..96a52e3
--- /dev/null
+++ b/core/res/assets/webkit/nullplugin.png
Binary files differ
diff --git a/core/res/assets/webkit/play.png b/core/res/assets/webkit/play.png
new file mode 100644
index 0000000..26fe286
--- /dev/null
+++ b/core/res/assets/webkit/play.png
Binary files differ
diff --git a/core/res/assets/webkit/textAreaResizeCorner.png b/core/res/assets/webkit/textAreaResizeCorner.png
new file mode 100644
index 0000000..777eff0
--- /dev/null
+++ b/core/res/assets/webkit/textAreaResizeCorner.png
Binary files differ
diff --git a/core/res/assets/webkit/youtube.html b/core/res/assets/webkit/youtube.html
new file mode 100644
index 0000000..5a40c1e
--- /dev/null
+++ b/core/res/assets/webkit/youtube.html
@@ -0,0 +1,59 @@
+<html>
+  <head>
+    <style type="text/css">
+      body      { background-color: black; }
+      a:hover   { text-decoration: none; }
+      a:link    { color: black; }
+      a:visited { color: black; }
+      #bg {
+        position: fixed;
+        margin: 0px;
+        border: 0px;
+        padding: 0px;
+        left: 0px;
+        top: 0px;
+        width: 100%;
+        height: 100%;
+        overflow: hidden;
+        z-index: 0;
+      }
+      #main {
+        position: absolute;
+        left: 0%;
+        top: 0%;
+        width: 100%;
+        height: 100%;
+        padding: 0%;
+        z-index: 10;
+      }
+    </style>
+  </head>
+  <body>
+    <div id="bg">
+      <table height="100%" width="100%" border="0" cellpadding="0"
+        cellspacing="0">
+        <tr>
+          <td valign="middle">
+            <img src="http://img.youtube.com/vi/VIDEO_ID/0.jpg" width="100%"/>
+          </td>
+        </tr>
+      </table>
+    </div>
+    <div id="main">
+      <table height="100%" width="100%">
+        <tr>
+          <td align="center" valign="middle" height="100%">
+            <a id="url" href="youtube:VIDEO_ID" target="_top">
+              <img src="play.png" style="border:0px"/>
+            </a>
+          </td>
+        </tr>
+        <tr>
+          <td valign="bottom" align="right">
+            <img src="youtube.png" style="opacity:.7"/>
+          </td>
+        </tr>
+      </table>
+    </div>
+  </body>
+</html>
diff --git a/core/res/assets/webkit/youtube.png b/core/res/assets/webkit/youtube.png
new file mode 100644
index 0000000..87779b1
--- /dev/null
+++ b/core/res/assets/webkit/youtube.png
Binary files differ
diff --git a/core/res/res/anim/accelerate_decelerate_interpolator.xml b/core/res/res/anim/accelerate_decelerate_interpolator.xml
new file mode 100644
index 0000000..724ed0a
--- /dev/null
+++ b/core/res/res/anim/accelerate_decelerate_interpolator.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* //device/apps/common/res/anim/ease_in_out_interpolator.xml
+**
+** Copyright 2007, 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.
+*/
+-->
+
+<accelerateDecelerateInterpolator xmlns:android="http://schemas.android.com/apk/res/android"/>
diff --git a/core/res/res/anim/accelerate_interpolator.xml b/core/res/res/anim/accelerate_interpolator.xml
new file mode 100644
index 0000000..c689c35
--- /dev/null
+++ b/core/res/res/anim/accelerate_interpolator.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* //device/apps/common/res/anim/ease_in_interpolator.xml
+**
+** Copyright 2007, 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.
+*/
+-->
+
+<accelerateInterpolator xmlns:android="http://schemas.android.com/apk/res/android" factor="1" />
diff --git a/core/res/res/anim/app_starting_exit.xml b/core/res/res/anim/app_starting_exit.xml
new file mode 100644
index 0000000..c6f94b0
--- /dev/null
+++ b/core/res/res/anim/app_starting_exit.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* //device/apps/common/res/anim/options_panel_exit.xml
+**
+** Copyright 2007, 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.
+*/
+-->
+
+<set xmlns:android="http://schemas.android.com/apk/res/android" android:interpolator="@anim/decelerate_interpolator">
+	<alpha android:fromAlpha="1.0" android:toAlpha="0.0" android:duration="200" />
+</set>
+
diff --git a/core/res/res/anim/decelerate_interpolator.xml b/core/res/res/anim/decelerate_interpolator.xml
new file mode 100644
index 0000000..fff6616
--- /dev/null
+++ b/core/res/res/anim/decelerate_interpolator.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* //device/apps/common/res/anim/ease_out_interpolator.xml
+**
+** Copyright 2007, 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.
+*/
+-->
+
+<decelerateInterpolator xmlns:android="http://schemas.android.com/apk/res/android" factor="1" />
diff --git a/core/res/res/anim/dialog_enter.xml b/core/res/res/anim/dialog_enter.xml
new file mode 100644
index 0000000..f48dd37
--- /dev/null
+++ b/core/res/res/anim/dialog_enter.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* //device/apps/common/res/anim/fade_in.xml
+**
+** Copyright 2007, 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.
+*/
+-->
+
+<set xmlns:android="http://schemas.android.com/apk/res/android"
+        android:interpolator="@anim/decelerate_interpolator">
+    <scale android:fromXScale="0.9" android:toXScale="1.0"
+           android:fromYScale="0.9" android:toYScale="1.0"
+           android:pivotX="50%" android:pivotY="50%"
+           android:duration="75" />
+    <alpha android:fromAlpha="0.0" android:toAlpha="1.0"
+            android:duration="75" />
+</set>
diff --git a/core/res/res/anim/dialog_exit.xml b/core/res/res/anim/dialog_exit.xml
new file mode 100644
index 0000000..24de6e7
--- /dev/null
+++ b/core/res/res/anim/dialog_exit.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* //device/apps/common/res/anim/fade_out.xml
+**
+** Copyright 2007, 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.
+*/
+-->
+<set xmlns:android="http://schemas.android.com/apk/res/android"
+        android:interpolator="@anim/accelerate_interpolator">
+    <scale android:fromXScale="1.0" android:toXScale="0.9"
+           android:fromYScale="1.0" android:toYScale="0.9"
+           android:pivotX="50%" android:pivotY="50%"
+           android:duration="75" />
+    <alpha android:fromAlpha="1.0" android:toAlpha="0.0"
+            android:duration="75"/>
+</set>
diff --git a/core/res/res/anim/fade_in.xml b/core/res/res/anim/fade_in.xml
new file mode 100644
index 0000000..32eea01
--- /dev/null
+++ b/core/res/res/anim/fade_in.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* //device/apps/common/res/anim/fade_in.xml
+**
+** Copyright 2007, 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.
+*/
+-->
+
+<alpha xmlns:android="http://schemas.android.com/apk/res/android" android:interpolator="@anim/decelerate_interpolator" android:fromAlpha="0.0" android:toAlpha="1.0" android:duration="300" />
diff --git a/core/res/res/anim/fade_out.xml b/core/res/res/anim/fade_out.xml
new file mode 100644
index 0000000..e8ae9c1
--- /dev/null
+++ b/core/res/res/anim/fade_out.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* //device/apps/common/res/anim/fade_out.xml
+**
+** Copyright 2007, 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.
+*/
+-->
+
+<alpha xmlns:android="http://schemas.android.com/apk/res/android" android:interpolator="@anim/accelerate_interpolator" 
+    android:fromAlpha="1.0"
+    android:toAlpha="0.0"
+    android:duration="300" 
+/>
diff --git a/core/res/res/anim/grow_fade_in.xml b/core/res/res/anim/grow_fade_in.xml
new file mode 100644
index 0000000..0969857
--- /dev/null
+++ b/core/res/res/anim/grow_fade_in.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* //device/apps/common/res/anim/fade_in.xml
+**
+** Copyright 2007, 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.
+*/
+-->
+
+<set xmlns:android="http://schemas.android.com/apk/res/android">
+    <scale android:fromXScale="1.0" android:toXScale="1.0"
+           android:fromYScale="0.3" android:toYScale="1.0"
+           android:pivotX="0%" android:pivotY="0%" android:duration="125" />
+    <alpha android:interpolator="@anim/decelerate_interpolator" android:fromAlpha="0.0" android:toAlpha="1.0" android:duration="75" />
+</set>
diff --git a/core/res/res/anim/grow_fade_in_center.xml b/core/res/res/anim/grow_fade_in_center.xml
new file mode 100644
index 0000000..01d7a77
--- /dev/null
+++ b/core/res/res/anim/grow_fade_in_center.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* //device/apps/common/res/anim/fade_in.xml
+**
+** Copyright 2007, 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.
+*/
+-->
+
+<set xmlns:android="http://schemas.android.com/apk/res/android">
+    <scale android:fromXScale="0.6" android:toXScale="1.0"
+           android:fromYScale="0.6" android:toYScale="1.0"
+           android:pivotX="50%" android:pivotY="50%" android:duration="125" />
+    <alpha android:interpolator="@anim/decelerate_interpolator" android:fromAlpha="0.0" android:toAlpha="1.0" android:duration="75" />
+</set>
diff --git a/core/res/res/anim/grow_fade_in_from_bottom.xml b/core/res/res/anim/grow_fade_in_from_bottom.xml
new file mode 100644
index 0000000..d3e468d
--- /dev/null
+++ b/core/res/res/anim/grow_fade_in_from_bottom.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* //device/apps/common/res/anim/fade_in.xml
+**
+** Copyright 2007, 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.
+*/
+-->
+
+<set xmlns:android="http://schemas.android.com/apk/res/android">
+    <scale android:fromXScale="0.5" android:toXScale="1.0"
+           android:fromYScale="0.5" android:toYScale="1.0"
+           android:pivotX="0%" android:pivotY="100%" android:duration="125" />
+    <alpha android:interpolator="@anim/decelerate_interpolator" android:fromAlpha="0.0" android:toAlpha="1.0" android:duration="75" />
+</set>
diff --git a/core/res/res/anim/input_method_enter.xml b/core/res/res/anim/input_method_enter.xml
new file mode 100644
index 0000000..6263c37649
--- /dev/null
+++ b/core/res/res/anim/input_method_enter.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* //device/apps/common/res/anim/fade_in.xml
+**
+** Copyright 2007, 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.
+*/
+-->
+
+<set xmlns:android="http://schemas.android.com/apk/res/android"
+        android:interpolator="@anim/decelerate_interpolator">
+    <translate android:fromYDelta="20%" android:toYDelta="0" android:duration="100"/>
+    <alpha android:fromAlpha="0.5" android:toAlpha="1.0"
+            android:duration="100" />
+</set>
diff --git a/core/res/res/anim/input_method_exit.xml b/core/res/res/anim/input_method_exit.xml
new file mode 100644
index 0000000..af9382c
--- /dev/null
+++ b/core/res/res/anim/input_method_exit.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* //device/apps/common/res/anim/fade_out.xml
+**
+** Copyright 2007, 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.
+*/
+-->
+<set xmlns:android="http://schemas.android.com/apk/res/android"
+        android:interpolator="@anim/accelerate_interpolator">
+    <translate android:fromYDelta="0" android:toYDelta="20%" android:duration="100"/>
+    <alpha android:fromAlpha="1.0" android:toAlpha="0.0"
+            android:duration="100"/>
+</set>
diff --git a/core/res/res/anim/input_method_fancy_enter.xml b/core/res/res/anim/input_method_fancy_enter.xml
new file mode 100644
index 0000000..15f5ad7
--- /dev/null
+++ b/core/res/res/anim/input_method_fancy_enter.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* //device/apps/common/res/anim/fade_in.xml
+**
+** Copyright 2007, 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.
+*/
+-->
+
+<set xmlns:android="http://schemas.android.com/apk/res/android"
+        android:interpolator="@anim/decelerate_interpolator">
+    <scale android:fromXScale="2.0" android:toXScale="1.0"
+           android:fromYScale="2.0" android:toYScale="1.0"
+           android:pivotX="50%" android:pivotY="50%"
+           android:duration="100" />
+    <translate android:fromYDelta="100%" android:toYDelta="0" android:duration="100"/>
+</set>
diff --git a/core/res/res/anim/input_method_fancy_exit.xml b/core/res/res/anim/input_method_fancy_exit.xml
new file mode 100644
index 0000000..ecc5de3
--- /dev/null
+++ b/core/res/res/anim/input_method_fancy_exit.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* //device/apps/common/res/anim/fade_out.xml
+**
+** Copyright 2007, 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.
+*/
+-->
+<set xmlns:android="http://schemas.android.com/apk/res/android"
+        android:interpolator="@anim/accelerate_interpolator">
+    <scale android:fromXScale="1.0" android:toXScale="2.0"
+           android:fromYScale="1.0" android:toYScale="2.0"
+           android:pivotX="50%" android:pivotY="50%"
+           android:duration="100" />
+    <translate android:fromYDelta="0" android:toYDelta="100%" android:duration="100"/>
+</set>
diff --git a/core/res/res/anim/linear_interpolator.xml b/core/res/res/anim/linear_interpolator.xml
new file mode 100644
index 0000000..70bbecc
--- /dev/null
+++ b/core/res/res/anim/linear_interpolator.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* //device/apps/common/res/anim/linear_interpolator.xml
+**
+** Copyright 2007, 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.
+*/
+-->
+
+<linearInterpolator xmlns:android="http://schemas.android.com/apk/res/android"/>
diff --git a/core/res/res/anim/lock_screen_exit.xml b/core/res/res/anim/lock_screen_exit.xml
new file mode 100644
index 0000000..54d8e93
--- /dev/null
+++ b/core/res/res/anim/lock_screen_exit.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* //device/apps/common/res/anim/options_panel_exit.xml
+**
+** Copyright 2007, 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.
+*/
+-->
+
+<set xmlns:android="http://schemas.android.com/apk/res/android" android:interpolator="@anim/accelerate_interpolator">
+	<alpha android:fromAlpha="1.0" android:toAlpha="0.0" android:duration="200" />
+</set>
diff --git a/core/res/res/anim/options_panel_enter.xml b/core/res/res/anim/options_panel_enter.xml
new file mode 100644
index 0000000..9614014
--- /dev/null
+++ b/core/res/res/anim/options_panel_enter.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* //device/apps/common/res/anim/options_panel_enter.xml
+**
+** Copyright 2007, 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.
+*/
+-->
+
+<set xmlns:android="http://schemas.android.com/apk/res/android" android:interpolator="@anim/decelerate_interpolator">
+	<translate android:fromYDelta="25%" android:toYDelta="0" android:duration="75"/>
+	<alpha android:fromAlpha="0.0" android:toAlpha="1.0" android:duration="75" />
+</set>
diff --git a/core/res/res/anim/options_panel_exit.xml b/core/res/res/anim/options_panel_exit.xml
new file mode 100644
index 0000000..c9bee1d
--- /dev/null
+++ b/core/res/res/anim/options_panel_exit.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* //device/apps/common/res/anim/options_panel_exit.xml
+**
+** Copyright 2007, 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.
+*/
+-->
+
+<set xmlns:android="http://schemas.android.com/apk/res/android" android:interpolator="@anim/accelerate_interpolator">
+	<translate android:fromYDelta="0" android:toYDelta="50%" android:duration="50"/>
+	<alpha android:fromAlpha="1.0" android:toAlpha="0.0" android:duration="50" />
+</set>
+
diff --git a/core/res/res/anim/push_down_in.xml b/core/res/res/anim/push_down_in.xml
new file mode 100644
index 0000000..1cb1597
--- /dev/null
+++ b/core/res/res/anim/push_down_in.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2007 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.
+-->
+
+<set xmlns:android="http://schemas.android.com/apk/res/android">
+	<translate android:fromYDelta="-100%p" android:toYDelta="0" android:duration="300"/>
+	<alpha android:fromAlpha="0.0" android:toAlpha="1.0" android:duration="300" />
+</set>
diff --git a/core/res/res/anim/push_down_out.xml b/core/res/res/anim/push_down_out.xml
new file mode 100644
index 0000000..1ad49b0
--- /dev/null
+++ b/core/res/res/anim/push_down_out.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2007 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.
+-->
+
+<set xmlns:android="http://schemas.android.com/apk/res/android">
+	<translate android:fromYDelta="0" android:toYDelta="100%p" android:duration="300"/>
+	<alpha android:fromAlpha="1.0" android:toAlpha="0.0" android:duration="300" />
+</set>
diff --git a/core/res/res/anim/push_up_in.xml b/core/res/res/anim/push_up_in.xml
new file mode 100644
index 0000000..a2f47e9
--- /dev/null
+++ b/core/res/res/anim/push_up_in.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2007 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.
+-->
+
+<set xmlns:android="http://schemas.android.com/apk/res/android">
+	<translate android:fromYDelta="100%p" android:toYDelta="0" android:duration="300"/>
+	<alpha android:fromAlpha="0.0" android:toAlpha="1.0" android:duration="300" />
+</set>
diff --git a/core/res/res/anim/push_up_out.xml b/core/res/res/anim/push_up_out.xml
new file mode 100644
index 0000000..2803435
--- /dev/null
+++ b/core/res/res/anim/push_up_out.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2007 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.
+-->
+
+<set xmlns:android="http://schemas.android.com/apk/res/android">
+	<translate android:fromYDelta="0" android:toYDelta="-100%p" android:duration="300"/>
+	<alpha android:fromAlpha="1.0" android:toAlpha="0.0" android:duration="300" />
+</set>
diff --git a/core/res/res/anim/search_bar_enter.xml b/core/res/res/anim/search_bar_enter.xml
new file mode 100644
index 0000000..ecb86c1
--- /dev/null
+++ b/core/res/res/anim/search_bar_enter.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* //device/apps/common/res/anim/options_panel_enter.xml
+**
+** Copyright 2007, 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.
+*/
+-->
+
+<set xmlns:android="http://schemas.android.com/apk/res/android" android:interpolator="@anim/decelerate_interpolator">
+	<translate android:fromYDelta="-25%" android:toYDelta="0" android:duration="75"/>
+	<alpha android:fromAlpha="0.0" android:toAlpha="1.0" android:duration="75" />
+</set>
diff --git a/core/res/res/anim/search_bar_exit.xml b/core/res/res/anim/search_bar_exit.xml
new file mode 100644
index 0000000..db7142e
--- /dev/null
+++ b/core/res/res/anim/search_bar_exit.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* //device/apps/common/res/anim/options_panel_exit.xml
+**
+** Copyright 2007, 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.
+*/
+-->
+
+<set xmlns:android="http://schemas.android.com/apk/res/android" android:interpolator="@anim/accelerate_interpolator">
+	<translate android:fromYDelta="0" android:toYDelta="-50%" android:duration="50"/>
+	<alpha android:fromAlpha="1.0" android:toAlpha="0.0" android:duration="50" />
+</set>
+
diff --git a/core/res/res/anim/shrink_fade_out.xml b/core/res/res/anim/shrink_fade_out.xml
new file mode 100644
index 0000000..ae15ff9
--- /dev/null
+++ b/core/res/res/anim/shrink_fade_out.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* //device/apps/common/res/anim/fade_out.xml
+**
+** Copyright 2007, 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.
+*/
+-->
+<set xmlns:android="http://schemas.android.com/apk/res/android">
+    <scale android:fromXScale="1.0" android:toXScale="1.0"
+           android:fromYScale="1.0" android:toYScale="0.3"
+           android:pivotX="0%" android:pivotY="0%" android:duration="125" />
+    <alpha android:interpolator="@anim/accelerate_interpolator" android:fromAlpha="1.0" android:toAlpha="0.0" android:duration="75"/>
+</set>
diff --git a/core/res/res/anim/shrink_fade_out_center.xml b/core/res/res/anim/shrink_fade_out_center.xml
new file mode 100644
index 0000000..7b0be34
--- /dev/null
+++ b/core/res/res/anim/shrink_fade_out_center.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* //device/apps/common/res/anim/fade_out.xml
+**
+** Copyright 2007, 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.
+*/
+-->
+<set xmlns:android="http://schemas.android.com/apk/res/android">
+    <scale android:fromXScale="1.0" android:toXScale="0.5"
+           android:fromYScale="1.0" android:toYScale="0.5"
+           android:pivotX="50%" android:pivotY="50%" android:duration="125" />
+    <alpha android:interpolator="@anim/accelerate_interpolator" android:fromAlpha="1.0" android:toAlpha="0.0" android:duration="75"/>
+</set>
diff --git a/core/res/res/anim/shrink_fade_out_from_bottom.xml b/core/res/res/anim/shrink_fade_out_from_bottom.xml
new file mode 100644
index 0000000..61f29d5
--- /dev/null
+++ b/core/res/res/anim/shrink_fade_out_from_bottom.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* //device/apps/common/res/anim/fade_out.xml
+**
+** Copyright 2007, 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.
+*/
+-->
+<set xmlns:android="http://schemas.android.com/apk/res/android">
+    <scale android:fromXScale="1.0" android:toXScale="1.0"
+           android:fromYScale="1.0" android:toYScale="0.3"
+           android:pivotX="0%" android:pivotY="100%" android:duration="125" />
+    <alpha android:interpolator="@anim/accelerate_interpolator" android:fromAlpha="1.0" android:toAlpha="0.0" android:duration="75"/>
+</set>
diff --git a/core/res/res/anim/slide_in_bottom.xml b/core/res/res/anim/slide_in_bottom.xml
new file mode 100644
index 0000000..569d974
--- /dev/null
+++ b/core/res/res/anim/slide_in_bottom.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* //device/apps/common/res/anim/slide_in_right.xml
+**
+** Copyright 2007, 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.
+*/
+-->
+
+<set xmlns:android="http://schemas.android.com/apk/res/android">
+	<translate android:fromYDelta="50%p" android:toYDelta="0" android:duration="200"/>
+	<alpha android:fromAlpha="0.0" android:toAlpha="1.0" android:duration="200" />
+</set>
diff --git a/core/res/res/anim/slide_in_child_bottom.xml b/core/res/res/anim/slide_in_child_bottom.xml
new file mode 100644
index 0000000..ce51c36
--- /dev/null
+++ b/core/res/res/anim/slide_in_child_bottom.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* //device/apps/common/res/anim/slide_in_child_bottom.xml
+**
+** Copyright 2007, 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.
+*/
+-->
+
+<set xmlns:android="http://schemas.android.com/apk/res/android" android:interpolator="@anim/decelerate_interpolator">
+	<translate android:fromYDelta="100%" android:toYDelta="0" android:duration="200"/>
+	<alpha android:fromAlpha="0.0" android:toAlpha="1.0" android:duration="200" />
+</set>
diff --git a/core/res/res/anim/slide_in_left.xml b/core/res/res/anim/slide_in_left.xml
new file mode 100644
index 0000000..033bfe2
--- /dev/null
+++ b/core/res/res/anim/slide_in_left.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* //device/apps/common/res/anim/slide_in_left.xml
+**
+** Copyright 2007, 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.
+*/
+-->
+
+<set xmlns:android="http://schemas.android.com/apk/res/android">
+	<translate android:fromXDelta="-50%p" android:toXDelta="0" android:duration="200"/>
+	<alpha android:fromAlpha="0.0" android:toAlpha="1.0" android:duration="200" />
+</set>
diff --git a/core/res/res/anim/slide_in_right.xml b/core/res/res/anim/slide_in_right.xml
new file mode 100644
index 0000000..76b336c
--- /dev/null
+++ b/core/res/res/anim/slide_in_right.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* //device/apps/common/res/anim/slide_in_right.xml
+**
+** Copyright 2007, 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.
+*/
+-->
+
+<set xmlns:android="http://schemas.android.com/apk/res/android">
+	<translate android:fromXDelta="50%p" android:toXDelta="0" android:duration="200"/>
+	<alpha android:fromAlpha="0.0" android:toAlpha="1.0" android:duration="200" />
+</set>
diff --git a/core/res/res/anim/slide_in_top.xml b/core/res/res/anim/slide_in_top.xml
new file mode 100644
index 0000000..15df913
--- /dev/null
+++ b/core/res/res/anim/slide_in_top.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* //device/apps/common/res/anim/slide_in_left.xml
+**
+** Copyright 2007, 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.
+*/
+-->
+
+<set xmlns:android="http://schemas.android.com/apk/res/android">
+	<translate android:fromYDelta="-50%p" android:toYDelta="0" android:duration="200"/>
+	<alpha android:fromAlpha="0.0" android:toAlpha="1.0" android:duration="200" />
+</set>
diff --git a/core/res/res/anim/slide_out_bottom.xml b/core/res/res/anim/slide_out_bottom.xml
new file mode 100644
index 0000000..8f6e8b0
--- /dev/null
+++ b/core/res/res/anim/slide_out_bottom.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* //device/apps/common/res/anim/slide_out_right.xml
+**
+** Copyright 2007, 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.
+*/
+-->
+
+<set xmlns:android="http://schemas.android.com/apk/res/android">
+	<translate android:fromYDelta="0" android:toYDelta="50%p" android:duration="200"/>
+	<alpha android:fromAlpha="1.0" android:toAlpha="0.0" android:duration="200" />
+</set>
diff --git a/core/res/res/anim/slide_out_left.xml b/core/res/res/anim/slide_out_left.xml
new file mode 100644
index 0000000..979d10a
--- /dev/null
+++ b/core/res/res/anim/slide_out_left.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* //device/apps/common/res/anim/slide_out_left.xml
+**
+** Copyright 2007, 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.
+*/
+-->
+
+<set xmlns:android="http://schemas.android.com/apk/res/android">
+	<translate android:fromXDelta="0" android:toXDelta="-50%p" android:duration="200"/>
+	<alpha android:fromAlpha="1.0" android:toAlpha="0.0" android:duration="200" />
+</set>
diff --git a/core/res/res/anim/slide_out_right.xml b/core/res/res/anim/slide_out_right.xml
new file mode 100644
index 0000000..b5f9bb9
--- /dev/null
+++ b/core/res/res/anim/slide_out_right.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* //device/apps/common/res/anim/slide_out_right.xml
+**
+** Copyright 2007, 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.
+*/
+-->
+
+<set xmlns:android="http://schemas.android.com/apk/res/android">
+	<translate android:fromXDelta="0" android:toXDelta="50%p" android:duration="200"/>
+	<alpha android:fromAlpha="1.0" android:toAlpha="0.0" android:duration="200" />
+</set>
diff --git a/core/res/res/anim/slide_out_top.xml b/core/res/res/anim/slide_out_top.xml
new file mode 100644
index 0000000..b15323e
--- /dev/null
+++ b/core/res/res/anim/slide_out_top.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* //device/apps/common/res/anim/slide_out_left.xml
+**
+** Copyright 2007, 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.
+*/
+-->
+
+<set xmlns:android="http://schemas.android.com/apk/res/android">
+	<translate android:fromYDelta="0" android:toYDelta="-50%p" android:duration="200"/>
+	<alpha android:fromAlpha="1.0" android:toAlpha="0.0" android:duration="200" />
+</set>
diff --git a/core/res/res/anim/status_bar_enter.xml b/core/res/res/anim/status_bar_enter.xml
new file mode 100644
index 0000000..b57d8e7
--- /dev/null
+++ b/core/res/res/anim/status_bar_enter.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* //device/apps/common/res/anim/options_panel_enter.xml
+**
+** Copyright 2007, 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.
+*/
+-->
+
+<set xmlns:android="http://schemas.android.com/apk/res/android" android:interpolator="@anim/decelerate_interpolator">
+	<translate android:fromYDelta="-75%" android:toYDelta="0"
+        android:duration="200"/>
+	<alpha android:fromAlpha="0.0" android:toAlpha="1.0"
+        android:duration="200" />
+</set>
diff --git a/core/res/res/anim/status_bar_exit.xml b/core/res/res/anim/status_bar_exit.xml
new file mode 100644
index 0000000..8c6dc1c
--- /dev/null
+++ b/core/res/res/anim/status_bar_exit.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* //device/apps/common/res/anim/options_panel_exit.xml
+**
+** Copyright 2007, 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.
+*/
+-->
+
+<set xmlns:android="http://schemas.android.com/apk/res/android" android:interpolator="@anim/accelerate_interpolator">
+	<translate android:fromYDelta="0" android:toYDelta="-75%"
+        android:startOffset="100" android:duration="200"/>
+	<alpha android:fromAlpha="1.0" android:toAlpha="0.0"
+        android:startOffset="100" android:duration="200" />
+</set>
diff --git a/core/res/res/anim/submenu_enter.xml b/core/res/res/anim/submenu_enter.xml
new file mode 100644
index 0000000..32a8054
--- /dev/null
+++ b/core/res/res/anim/submenu_enter.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* //device/apps/common/res/anim/slide_in_left.xml
+**
+** Copyright 2007, 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.
+*/
+-->
+
+<set xmlns:android="http://schemas.android.com/apk/res/android" android:interpolator="@anim/decelerate_interpolator">
+	<translate android:fromXDelta="-25%" android:toXDelta="0" android:duration="100"/>
+	<alpha android:fromAlpha="0.0" android:toAlpha="1.0" android:duration="100" />
+</set>
diff --git a/core/res/res/anim/submenu_exit.xml b/core/res/res/anim/submenu_exit.xml
new file mode 100644
index 0000000..9283751
--- /dev/null
+++ b/core/res/res/anim/submenu_exit.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* //device/apps/common/res/anim/slide_out_left.xml
+**
+** Copyright 2007, 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.
+*/
+-->
+
+<set xmlns:android="http://schemas.android.com/apk/res/android">
+	<translate android:fromXDelta="0" android:toXDelta="25%" android:duration="50"/>
+	<alpha android:fromAlpha="1.0" android:toAlpha="0.0" android:duration="50" />
+</set>
diff --git a/core/res/res/anim/task_close_enter.xml b/core/res/res/anim/task_close_enter.xml
new file mode 100644
index 0000000..6bef7a4
--- /dev/null
+++ b/core/res/res/anim/task_close_enter.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* //device/apps/common/res/anim/options_panel_exit.xml
+**
+** Copyright 2007, 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.
+*/
+-->
+
+<set xmlns:android="http://schemas.android.com/apk/res/android"
+        android:interpolator="@anim/decelerate_interpolator"
+        android:zAdjustment="top">
+	<translate android:fromXDelta="-100%" android:toXDelta="0" android:duration="200"/>
+</set>
diff --git a/core/res/res/anim/task_close_exit.xml b/core/res/res/anim/task_close_exit.xml
new file mode 100644
index 0000000..48dc31a
--- /dev/null
+++ b/core/res/res/anim/task_close_exit.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* //device/apps/common/res/anim/options_panel_exit.xml
+**
+** Copyright 2007, 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.
+*/
+-->
+
+<set xmlns:android="http://schemas.android.com/apk/res/android"
+        android:interpolator="@anim/decelerate_interpolator">
+	<translate android:fromXDelta="0%" android:toXDelta="33%" android:duration="200"/>
+</set>
diff --git a/core/res/res/anim/task_open_enter.xml b/core/res/res/anim/task_open_enter.xml
new file mode 100644
index 0000000..e498c9d
--- /dev/null
+++ b/core/res/res/anim/task_open_enter.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* //device/apps/common/res/anim/options_panel_exit.xml
+**
+** Copyright 2007, 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.
+*/
+-->
+
+<set xmlns:android="http://schemas.android.com/apk/res/android"
+        android:interpolator="@anim/decelerate_interpolator">
+	<translate android:fromXDelta="33%" android:toXDelta="0" android:duration="200"/>
+</set>
diff --git a/core/res/res/anim/task_open_exit.xml b/core/res/res/anim/task_open_exit.xml
new file mode 100644
index 0000000..8ef4006
--- /dev/null
+++ b/core/res/res/anim/task_open_exit.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* //device/apps/common/res/anim/options_panel_exit.xml
+**
+** Copyright 2007, 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.
+*/
+-->
+
+<set xmlns:android="http://schemas.android.com/apk/res/android"
+        android:interpolator="@anim/decelerate_interpolator"
+        android:zAdjustment="top">
+	<translate android:fromXDelta="0%" android:toXDelta="-100%" android:duration="200"/>
+</set>
diff --git a/core/res/res/anim/toast_enter.xml b/core/res/res/anim/toast_enter.xml
new file mode 100644
index 0000000..83cb1fe
--- /dev/null
+++ b/core/res/res/anim/toast_enter.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* //device/apps/common/res/anim/fade_in.xml
+**
+** Copyright 2007, 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.
+*/
+-->
+
+<alpha xmlns:android="http://schemas.android.com/apk/res/android"
+        android:interpolator="@anim/decelerate_interpolator"
+        android:fromAlpha="0.0" android:toAlpha="1.0" android:duration="400" />
diff --git a/core/res/res/anim/toast_exit.xml b/core/res/res/anim/toast_exit.xml
new file mode 100644
index 0000000..f922085
--- /dev/null
+++ b/core/res/res/anim/toast_exit.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* //device/apps/common/res/anim/fade_out.xml
+**
+** Copyright 2007, 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.
+*/
+-->
+
+<alpha xmlns:android="http://schemas.android.com/apk/res/android"
+        android:interpolator="@anim/accelerate_interpolator" 
+        android:fromAlpha="1.0" android:toAlpha="0.0" android:duration="400" 
+/>
diff --git a/core/res/res/color/primary_text_dark.xml b/core/res/res/color/primary_text_dark.xml
new file mode 100644
index 0000000..2ec46b9
--- /dev/null
+++ b/core/res/res/color/primary_text_dark.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_enabled="false" android:color="@android:color/bright_foreground_dark_disabled"/>
+	<item android:state_window_focused="false" android:color="@android:color/bright_foreground_dark"/>
+    <item android:state_pressed="true" android:color="@android:color/bright_foreground_dark_inverse"/>
+    <item android:state_selected="true" android:color="@android:color/bright_foreground_dark_inverse"/>
+    <item android:color="@android:color/bright_foreground_dark"/> <!-- not selected -->
+
+</selector>
+
diff --git a/core/res/res/color/primary_text_dark_disable_only.xml b/core/res/res/color/primary_text_dark_disable_only.xml
new file mode 100644
index 0000000..9a187a4
--- /dev/null
+++ b/core/res/res/color/primary_text_dark_disable_only.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_enabled="false" android:color="@android:color/bright_foreground_dark_disabled"/>
+    <item android:color="@android:color/bright_foreground_dark"/>
+</selector>
+
diff --git a/core/res/res/color/primary_text_dark_focused.xml b/core/res/res/color/primary_text_dark_focused.xml
new file mode 100644
index 0000000..7f3906a
--- /dev/null
+++ b/core/res/res/color/primary_text_dark_focused.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_selected="true" android:color="@android:color/bright_foreground_dark_inverse" />
+    <item android:state_focused="true" android:color="@android:color/bright_foreground_dark_inverse" />
+    <item android:state_pressed="true" android:color="@android:color/bright_foreground_dark_inverse" />
+    <item android:color="@android:color/bright_foreground_dark" />
+</selector>
+
diff --git a/core/res/res/color/primary_text_dark_nodisable.xml b/core/res/res/color/primary_text_dark_nodisable.xml
new file mode 100644
index 0000000..be1b9f9
--- /dev/null
+++ b/core/res/res/color/primary_text_dark_nodisable.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_selected="true" android:color="@android:color/bright_foreground_dark_inverse"/>
+    <item android:color="@android:color/bright_foreground_dark"/> <!-- not selected -->
+</selector>
+
diff --git a/core/res/res/color/primary_text_light.xml b/core/res/res/color/primary_text_light.xml
new file mode 100644
index 0000000..bd9ac8b
--- /dev/null
+++ b/core/res/res/color/primary_text_light.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_enabled="false" android:color="@android:color/bright_foreground_light_disabled"/>
+    <item android:state_window_focused="false" android:color="@android:color/bright_foreground_light"/>
+    <item android:state_pressed="true" android:color="@android:color/bright_foreground_light"/>
+    <item android:state_selected="true" android:color="@android:color/bright_foreground_light"/>
+    <item android:color="@android:color/bright_foreground_light"/> <!-- not selected -->
+    
+</selector>
+
diff --git a/core/res/res/color/primary_text_light_disable_only.xml b/core/res/res/color/primary_text_light_disable_only.xml
new file mode 100644
index 0000000..f8afb63
--- /dev/null
+++ b/core/res/res/color/primary_text_light_disable_only.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_enabled="false" android:color="@android:color/bright_foreground_light_disabled"/>
+    <item android:color="@android:color/bright_foreground_light"/>
+</selector>
+
diff --git a/core/res/res/color/primary_text_light_nodisable.xml b/core/res/res/color/primary_text_light_nodisable.xml
new file mode 100644
index 0000000..2d35470
--- /dev/null
+++ b/core/res/res/color/primary_text_light_nodisable.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_selected="true" android:color="@android:color/bright_foreground_light"/>
+    <item android:color="@android:color/bright_foreground_light"/> <!-- not selected -->
+</selector>
+
diff --git a/core/res/res/color/secondary_text_dark.xml b/core/res/res/color/secondary_text_dark.xml
new file mode 100644
index 0000000..0d96221
--- /dev/null
+++ b/core/res/res/color/secondary_text_dark.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_window_focused="false" android:state_enabled="false" android:color="@android:color/dim_foreground_dark_disabled"/>
+    <item android:state_window_focused="false" android:color="@android:color/dim_foreground_dark"/>
+    <item android:state_selected="true" android:state_enabled="false" android:color="@android:color/dim_foreground_dark_inverse_disabled"/>
+    <item android:state_pressed="true" android:state_enabled="false" android:color="@android:color/dim_foreground_dark_inverse_disabled"/>
+    <item android:state_selected="true" android:color="@android:color/dim_foreground_dark_inverse"/>
+    <item android:state_pressed="true" android:color="@android:color/dim_foreground_dark_inverse"/>
+    <item android:state_enabled="false" android:color="@android:color/dim_foreground_dark_disabled"/>
+    <item android:color="@android:color/dim_foreground_dark"/> <!-- not selected -->
+</selector>
diff --git a/core/res/res/color/secondary_text_dark_nodisable.xml b/core/res/res/color/secondary_text_dark_nodisable.xml
new file mode 100644
index 0000000..2c87a25
--- /dev/null
+++ b/core/res/res/color/secondary_text_dark_nodisable.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_selected="true" android:color="@android:color/dim_foreground_dark_inverse"/>
+    <item android:color="@android:color/dim_foreground_dark"/> <!-- not selected -->
+</selector>
diff --git a/core/res/res/color/secondary_text_light.xml b/core/res/res/color/secondary_text_light.xml
new file mode 100644
index 0000000..0846b5e
--- /dev/null
+++ b/core/res/res/color/secondary_text_light.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_window_focused="false" android:state_enabled="false" android:color="@android:color/dim_foreground_light_disabled"/>
+    <item android:state_window_focused="false" android:color="@android:color/dim_foreground_light"/>
+    <!-- Since there is only one selector (for both light and dark), the light's selected state shouldn't be inversed like the dark's. -->
+    <item android:state_pressed="true" android:state_enabled="false" android:color="@android:color/dim_foreground_light_disabled"/>
+    <item android:state_selected="true" android:state_enabled="false" android:color="@android:color/dim_foreground_light_disabled"/>
+    <item android:state_pressed="true" android:color="@android:color/dim_foreground_light"/>
+    <item android:state_selected="true" android:color="@android:color/dim_foreground_light"/>
+    <item android:state_enabled="false" android:color="@android:color/dim_foreground_light_disabled"/>
+    <item android:color="@android:color/dim_foreground_light"/> <!-- not selected -->
+</selector>
diff --git a/core/res/res/color/secondary_text_light_nodisable.xml b/core/res/res/color/secondary_text_light_nodisable.xml
new file mode 100644
index 0000000..2c87a25
--- /dev/null
+++ b/core/res/res/color/secondary_text_light_nodisable.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_selected="true" android:color="@android:color/dim_foreground_dark_inverse"/>
+    <item android:color="@android:color/dim_foreground_dark"/> <!-- not selected -->
+</selector>
diff --git a/core/res/res/color/tab_indicator_text.xml b/core/res/res/color/tab_indicator_text.xml
new file mode 100644
index 0000000..ce321db
--- /dev/null
+++ b/core/res/res/color/tab_indicator_text.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_selected="true" android:color="#323232"/>
+    <item android:color="#FFF"/> <!-- not selected -->
+</selector>
diff --git a/core/res/res/color/tertiary_text_dark.xml b/core/res/res/color/tertiary_text_dark.xml
new file mode 100644
index 0000000..7ce3580
--- /dev/null
+++ b/core/res/res/color/tertiary_text_dark.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_enabled="false" android:color="#808080"/>
+	<item android:state_window_focused="false" android:color="#808080"/>
+    <item android:state_pressed="true" android:color="#808080"/>
+    <item android:state_selected="true" android:color="@android:color/dim_foreground_light"/>
+    <item android:color="#808080"/> <!-- not selected -->
+</selector>
+
diff --git a/core/res/res/color/tertiary_text_light.xml b/core/res/res/color/tertiary_text_light.xml
new file mode 100644
index 0000000..7e61fc89
--- /dev/null
+++ b/core/res/res/color/tertiary_text_light.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_enabled="false" android:color="#808080"/>
+	<item android:state_window_focused="false" android:color="#808080"/>
+    <item android:state_pressed="true" android:color="#808080"/>
+    <item android:state_selected="true" android:color="#808080"/>
+    <item android:color="#808080"/> <!-- not selected -->
+</selector>
+
diff --git a/core/res/res/color/theme_panel_text.xml b/core/res/res/color/theme_panel_text.xml
new file mode 100644
index 0000000..1268a89
--- /dev/null
+++ b/core/res/res/color/theme_panel_text.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2007 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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_enabled="true" android:color="#FF000000"/>
+    <item android:color="#80888888"/> <!-- disabled -->
+</selector>
diff --git a/core/res/res/color/widget_autocompletetextview_dark.xml b/core/res/res/color/widget_autocompletetextview_dark.xml
new file mode 100644
index 0000000..802f6c6
--- /dev/null
+++ b/core/res/res/color/widget_autocompletetextview_dark.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2007 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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_focused="true" android:color="#ff000000"/>
+    <item android:color="#ffffffff"/> <!-- unfocused -->
+</selector>
diff --git a/core/res/res/color/widget_button.xml b/core/res/res/color/widget_button.xml
new file mode 100644
index 0000000..66f37cb
--- /dev/null
+++ b/core/res/res/color/widget_button.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_focused="true" android:color="#ff000000"/>
+    <item android:color="#ff000000"/> <!-- unfocused -->
+</selector>
diff --git a/core/res/res/color/widget_edittext_dark.xml b/core/res/res/color/widget_edittext_dark.xml
new file mode 100644
index 0000000..d6effbe
--- /dev/null
+++ b/core/res/res/color/widget_edittext_dark.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2007 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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_focused="false" android:color="@android:color/bright_foreground_light"/> <!-- unfocused -->
+    <item android:color="@android:color/bright_foreground_light"/>
+</selector>
diff --git a/core/res/res/color/widget_edittext_dark_hint.xml b/core/res/res/color/widget_edittext_dark_hint.xml
new file mode 100644
index 0000000..094025e
--- /dev/null
+++ b/core/res/res/color/widget_edittext_dark_hint.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <item android:state_enabled="false" android:color="#A0A0A0"/> <!-- not enabled -->
+    <item android:color="#7c7c7c"/>
+    
+</selector>
+
diff --git a/core/res/res/color/widget_paneltabwidget.xml b/core/res/res/color/widget_paneltabwidget.xml
new file mode 100644
index 0000000..fcec9c0
--- /dev/null
+++ b/core/res/res/color/widget_paneltabwidget.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2007 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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_enabled="true" android:color="#ffffffff"/>
+    <item android:color="#ff000000"/> <!-- disabled -->
+</selector>
diff --git a/core/res/res/color/widget_textview_bigspinneritem_dark.xml b/core/res/res/color/widget_textview_bigspinneritem_dark.xml
new file mode 100644
index 0000000..d98fa46
--- /dev/null
+++ b/core/res/res/color/widget_textview_bigspinneritem_dark.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2007 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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_selected="true" android:color="#ff000000"/>
+    <item android:color="#ffffffff"/> <!-- unselected -->
+</selector>
diff --git a/core/res/res/color/widget_textview_spinneritem_dark.xml b/core/res/res/color/widget_textview_spinneritem_dark.xml
new file mode 100644
index 0000000..d98fa46
--- /dev/null
+++ b/core/res/res/color/widget_textview_spinneritem_dark.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2007 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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_selected="true" android:color="#ff000000"/>
+    <item android:color="#ffffffff"/> <!-- unselected -->
+</selector>
diff --git a/core/res/res/drawable-land/bottombar_565.png b/core/res/res/drawable-land/bottombar_565.png
new file mode 100644
index 0000000..6121856
--- /dev/null
+++ b/core/res/res/drawable-land/bottombar_565.png
Binary files differ
diff --git a/core/res/res/drawable-land/statusbar_background.png b/core/res/res/drawable-land/statusbar_background.png
new file mode 100644
index 0000000..8ecc24c
--- /dev/null
+++ b/core/res/res/drawable-land/statusbar_background.png
Binary files differ
diff --git a/core/res/res/drawable-land/title_bar_tall.png b/core/res/res/drawable-land/title_bar_tall.png
new file mode 100644
index 0000000..16290fb
--- /dev/null
+++ b/core/res/res/drawable-land/title_bar_tall.png
Binary files differ
diff --git a/core/res/res/drawable/activity_title_bar.9.png b/core/res/res/drawable/activity_title_bar.9.png
new file mode 100644
index 0000000..bd0680d
--- /dev/null
+++ b/core/res/res/drawable/activity_title_bar.9.png
Binary files differ
diff --git a/core/res/res/drawable/app_icon_background.xml b/core/res/res/drawable/app_icon_background.xml
new file mode 100644
index 0000000..9224b5f
--- /dev/null
+++ b/core/res/res/drawable/app_icon_background.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* //device/apps/common/res/drawable/app_icon_background.xml
+**
+** Copyright 2007, 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.
+*/
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_selected="true" android:drawable="@drawable/icon_highlight_square" />
+    <item android:drawable="@color/transparent" />
+</selector>
diff --git a/core/res/res/drawable/arrow_down_float.png b/core/res/res/drawable/arrow_down_float.png
new file mode 100644
index 0000000..dd82523
--- /dev/null
+++ b/core/res/res/drawable/arrow_down_float.png
Binary files differ
diff --git a/core/res/res/drawable/arrow_up_float.png b/core/res/res/drawable/arrow_up_float.png
new file mode 100644
index 0000000..9bc3d1c
--- /dev/null
+++ b/core/res/res/drawable/arrow_up_float.png
Binary files differ
diff --git a/core/res/res/drawable/battery_charge_background.png b/core/res/res/drawable/battery_charge_background.png
new file mode 100644
index 0000000..9219745
--- /dev/null
+++ b/core/res/res/drawable/battery_charge_background.png
Binary files differ
diff --git a/core/res/res/drawable/battery_charge_fill.xml b/core/res/res/drawable/battery_charge_fill.xml
new file mode 100644
index 0000000..7f65733
--- /dev/null
+++ b/core/res/res/drawable/battery_charge_fill.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<level-list xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:maxLevel="29" android:drawable="@android:drawable/battery_charge_fill_empty" />
+    <item android:maxLevel="49" android:drawable="@android:drawable/battery_charge_fill_warning" />
+    <item android:maxLevel="100" android:drawable="@android:drawable/battery_charge_fill_full" />
+</level-list>
+
diff --git a/core/res/res/drawable/battery_charge_fill_empty.9.png b/core/res/res/drawable/battery_charge_fill_empty.9.png
new file mode 100644
index 0000000..9ed20ba
--- /dev/null
+++ b/core/res/res/drawable/battery_charge_fill_empty.9.png
Binary files differ
diff --git a/core/res/res/drawable/battery_charge_fill_full.9.png b/core/res/res/drawable/battery_charge_fill_full.9.png
new file mode 100644
index 0000000..8e6aaca
--- /dev/null
+++ b/core/res/res/drawable/battery_charge_fill_full.9.png
Binary files differ
diff --git a/core/res/res/drawable/battery_charge_fill_warning.9.png b/core/res/res/drawable/battery_charge_fill_warning.9.png
new file mode 100644
index 0000000..d3287db
--- /dev/null
+++ b/core/res/res/drawable/battery_charge_fill_warning.9.png
Binary files differ
diff --git a/core/res/res/drawable/battery_low_battery.png b/core/res/res/drawable/battery_low_battery.png
new file mode 100644
index 0000000..60bbe6c
--- /dev/null
+++ b/core/res/res/drawable/battery_low_battery.png
Binary files differ
diff --git a/core/res/res/drawable/blank_tile.png b/core/res/res/drawable/blank_tile.png
new file mode 100644
index 0000000..63b9296
--- /dev/null
+++ b/core/res/res/drawable/blank_tile.png
Binary files differ
diff --git a/core/res/res/drawable/boot_robot.png b/core/res/res/drawable/boot_robot.png
new file mode 100644
index 0000000..f4555af
--- /dev/null
+++ b/core/res/res/drawable/boot_robot.png
Binary files differ
diff --git a/core/res/res/drawable/bottom_bar.png b/core/res/res/drawable/bottom_bar.png
new file mode 100644
index 0000000..1fdb078
--- /dev/null
+++ b/core/res/res/drawable/bottom_bar.png
Binary files differ
diff --git a/core/res/res/drawable/box.xml b/core/res/res/drawable/box.xml
new file mode 100644
index 0000000..6849bd34
--- /dev/null
+++ b/core/res/res/drawable/box.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* //device/apps/common/res/drawable/box.xml
+**
+** Copyright 2007, 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.
+*/
+-->
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android">
+    <solid android:color="#00000000"/>
+    <stroke android:width="1dp" color="#ff000000"/>
+    <padding android:left="1dp" android:top="1dp"
+        android:right="1dp" android:bottom="1dp" />
+</shape>
diff --git a/core/res/res/drawable/btn_application_selector.xml b/core/res/res/drawable/btn_application_selector.xml
new file mode 100644
index 0000000..5575b85
--- /dev/null
+++ b/core/res/res/drawable/btn_application_selector.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2007, 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.
+*/
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_pressed="true" 
+          android:drawable="@drawable/pressed_application_background_static" />
+    <item android:state_focused="true" 
+          android:drawable="@drawable/focused_application_background_static" />
+</selector>
diff --git a/core/res/res/drawable/btn_check.xml b/core/res/res/drawable/btn_check.xml
new file mode 100644
index 0000000..824aff0
--- /dev/null
+++ b/core/res/res/drawable/btn_check.xml
@@ -0,0 +1,65 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <!-- Enabled states -->
+        
+    <item android:state_checked="true" android:state_window_focused="false"
+          android:state_enabled="true"
+          android:drawable="@drawable/btn_check_on" />
+    <item android:state_checked="false" android:state_window_focused="false"
+          android:state_enabled="true"
+          android:drawable="@drawable/btn_check_off" />
+
+    <item android:state_checked="true" android:state_pressed="true"
+          android:state_enabled="true"
+          android:drawable="@drawable/btn_check_on_pressed" />
+    <item android:state_checked="false" android:state_pressed="true"
+          android:state_enabled="true"
+          android:drawable="@drawable/btn_check_off_pressed" />
+
+    <item android:state_checked="true" android:state_focused="true"
+          android:state_enabled="true"
+          android:drawable="@drawable/btn_check_on_selected" />
+    <item android:state_checked="false" android:state_focused="true"
+          android:state_enabled="true"
+          android:drawable="@drawable/btn_check_off_selected" />
+
+    <item android:state_checked="false"
+          android:state_enabled="true"
+          android:drawable="@drawable/btn_check_off" />
+    <item android:state_checked="true"
+          android:state_enabled="true"
+          android:drawable="@drawable/btn_check_on" />
+
+
+    <!-- Disabled states -->
+
+    <item android:state_checked="true" android:state_window_focused="false"
+          android:drawable="@drawable/btn_check_on_disable" />
+    <item android:state_checked="false" android:state_window_focused="false"
+          android:drawable="@drawable/btn_check_off_disable" />
+
+    <item android:state_checked="true" android:state_focused="true"
+          android:drawable="@drawable/btn_check_on_disable_focused" />
+    <item android:state_checked="false" android:state_focused="true"
+          android:drawable="@drawable/btn_check_off_disable_focused" />
+
+    <item android:state_checked="false" android:drawable="@drawable/btn_check_off_disable" />
+    <item android:state_checked="true" android:drawable="@drawable/btn_check_on_disable" />
+
+</selector>
diff --git a/core/res/res/drawable/btn_check_buttonless_off.png b/core/res/res/drawable/btn_check_buttonless_off.png
new file mode 100755
index 0000000..f8972fc
--- /dev/null
+++ b/core/res/res/drawable/btn_check_buttonless_off.png
Binary files differ
diff --git a/core/res/res/drawable/btn_check_buttonless_on.png b/core/res/res/drawable/btn_check_buttonless_on.png
new file mode 100755
index 0000000..a10a37a
--- /dev/null
+++ b/core/res/res/drawable/btn_check_buttonless_on.png
Binary files differ
diff --git a/core/res/res/drawable/btn_check_label_background.9.png b/core/res/res/drawable/btn_check_label_background.9.png
new file mode 100644
index 0000000..79367b8
--- /dev/null
+++ b/core/res/res/drawable/btn_check_label_background.9.png
Binary files differ
diff --git a/core/res/res/drawable/btn_check_off.png b/core/res/res/drawable/btn_check_off.png
new file mode 100644
index 0000000..56d3861
--- /dev/null
+++ b/core/res/res/drawable/btn_check_off.png
Binary files differ
diff --git a/core/res/res/drawable/btn_check_off_disable.png b/core/res/res/drawable/btn_check_off_disable.png
new file mode 100644
index 0000000..e012afd
--- /dev/null
+++ b/core/res/res/drawable/btn_check_off_disable.png
Binary files differ
diff --git a/core/res/res/drawable/btn_check_off_disable_focused.png b/core/res/res/drawable/btn_check_off_disable_focused.png
new file mode 100644
index 0000000..0837bbd
--- /dev/null
+++ b/core/res/res/drawable/btn_check_off_disable_focused.png
Binary files differ
diff --git a/core/res/res/drawable/btn_check_off_pressed.png b/core/res/res/drawable/btn_check_off_pressed.png
new file mode 100644
index 0000000..984dfd7
--- /dev/null
+++ b/core/res/res/drawable/btn_check_off_pressed.png
Binary files differ
diff --git a/core/res/res/drawable/btn_check_off_selected.png b/core/res/res/drawable/btn_check_off_selected.png
new file mode 100644
index 0000000..20842d4
--- /dev/null
+++ b/core/res/res/drawable/btn_check_off_selected.png
Binary files differ
diff --git a/core/res/res/drawable/btn_check_on.png b/core/res/res/drawable/btn_check_on.png
new file mode 100644
index 0000000..791ac1d
--- /dev/null
+++ b/core/res/res/drawable/btn_check_on.png
Binary files differ
diff --git a/core/res/res/drawable/btn_check_on_disable.png b/core/res/res/drawable/btn_check_on_disable.png
new file mode 100644
index 0000000..6cb02f3
--- /dev/null
+++ b/core/res/res/drawable/btn_check_on_disable.png
Binary files differ
diff --git a/core/res/res/drawable/btn_check_on_disable_focused.png b/core/res/res/drawable/btn_check_on_disable_focused.png
new file mode 100644
index 0000000..8a73b33
--- /dev/null
+++ b/core/res/res/drawable/btn_check_on_disable_focused.png
Binary files differ
diff --git a/core/res/res/drawable/btn_check_on_pressed.png b/core/res/res/drawable/btn_check_on_pressed.png
new file mode 100644
index 0000000..300d64a
--- /dev/null
+++ b/core/res/res/drawable/btn_check_on_pressed.png
Binary files differ
diff --git a/core/res/res/drawable/btn_check_on_selected.png b/core/res/res/drawable/btn_check_on_selected.png
new file mode 100644
index 0000000..0b36adb
--- /dev/null
+++ b/core/res/res/drawable/btn_check_on_selected.png
Binary files differ
diff --git a/core/res/res/drawable/btn_close.xml b/core/res/res/drawable/btn_close.xml
new file mode 100644
index 0000000..9d90e4b
--- /dev/null
+++ b/core/res/res/drawable/btn_close.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <item android:state_pressed="false"
+        android:drawable="@android:drawable/btn_close_normal" />
+
+    <item android:state_pressed="true"
+        android:drawable="@android:drawable/btn_close_pressed" />
+
+</selector>
diff --git a/core/res/res/drawable/btn_close_normal.png b/core/res/res/drawable/btn_close_normal.png
new file mode 100644
index 0000000..ecc4dde
--- /dev/null
+++ b/core/res/res/drawable/btn_close_normal.png
Binary files differ
diff --git a/core/res/res/drawable/btn_close_pressed.png b/core/res/res/drawable/btn_close_pressed.png
new file mode 100644
index 0000000..49223c5
--- /dev/null
+++ b/core/res/res/drawable/btn_close_pressed.png
Binary files differ
diff --git a/core/res/res/drawable/btn_code_lock_default.png b/core/res/res/drawable/btn_code_lock_default.png
new file mode 100755
index 0000000..c2e0b05
--- /dev/null
+++ b/core/res/res/drawable/btn_code_lock_default.png
Binary files differ
diff --git a/core/res/res/drawable/btn_code_lock_default_trackball_pressed.png b/core/res/res/drawable/btn_code_lock_default_trackball_pressed.png
new file mode 100755
index 0000000..0d3f094
--- /dev/null
+++ b/core/res/res/drawable/btn_code_lock_default_trackball_pressed.png
Binary files differ
diff --git a/core/res/res/drawable/btn_code_lock_touched.png b/core/res/res/drawable/btn_code_lock_touched.png
new file mode 100755
index 0000000..70e95a2
--- /dev/null
+++ b/core/res/res/drawable/btn_code_lock_touched.png
Binary files differ
diff --git a/core/res/res/drawable/btn_default.xml b/core/res/res/drawable/btn_default.xml
new file mode 100644
index 0000000..b8ce2bf
--- /dev/null
+++ b/core/res/res/drawable/btn_default.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_window_focused="false" android:state_enabled="true"
+        android:drawable="@drawable/btn_default_normal" />
+    <item android:state_window_focused="false" android:state_enabled="false"
+        android:drawable="@drawable/btn_default_normal_disable" />
+    <item android:state_pressed="true" 
+        android:drawable="@drawable/btn_default_pressed" />
+    <item android:state_focused="true" android:state_enabled="true"
+        android:drawable="@drawable/btn_default_selected" />
+    <item android:state_enabled="true"
+        android:drawable="@drawable/btn_default_normal" />
+    <item android:state_focused="true"
+        android:drawable="@drawable/btn_default_normal_disable_focused" />
+    <item
+         android:drawable="@drawable/btn_default_normal_disable" />
+</selector>
diff --git a/core/res/res/drawable/btn_default_normal.9.png b/core/res/res/drawable/btn_default_normal.9.png
new file mode 100644
index 0000000..a2d5ccd
--- /dev/null
+++ b/core/res/res/drawable/btn_default_normal.9.png
Binary files differ
diff --git a/core/res/res/drawable/btn_default_normal_disable.9.png b/core/res/res/drawable/btn_default_normal_disable.9.png
new file mode 100644
index 0000000..edd3a3e
--- /dev/null
+++ b/core/res/res/drawable/btn_default_normal_disable.9.png
Binary files differ
diff --git a/core/res/res/drawable/btn_default_normal_disable_focused.9.png b/core/res/res/drawable/btn_default_normal_disable_focused.9.png
new file mode 100644
index 0000000..f506179
--- /dev/null
+++ b/core/res/res/drawable/btn_default_normal_disable_focused.9.png
Binary files differ
diff --git a/core/res/res/drawable/btn_default_pressed.9.png b/core/res/res/drawable/btn_default_pressed.9.png
new file mode 100644
index 0000000..033bf89
--- /dev/null
+++ b/core/res/res/drawable/btn_default_pressed.9.png
Binary files differ
diff --git a/core/res/res/drawable/btn_default_selected.9.png b/core/res/res/drawable/btn_default_selected.9.png
new file mode 100644
index 0000000..1e900bf
--- /dev/null
+++ b/core/res/res/drawable/btn_default_selected.9.png
Binary files differ
diff --git a/core/res/res/drawable/btn_default_small.xml b/core/res/res/drawable/btn_default_small.xml
new file mode 100644
index 0000000..247e9e2
--- /dev/null
+++ b/core/res/res/drawable/btn_default_small.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_window_focused="false" android:state_enabled="true"
+        android:drawable="@drawable/btn_default_small_normal" />
+    <item android:state_window_focused="false" android:state_enabled="false"
+        android:drawable="@drawable/btn_default_small_normal_disable" />
+    <item android:state_pressed="true" 
+        android:drawable="@drawable/btn_default_small_pressed" />
+    <item android:state_focused="true" android:state_enabled="true"
+        android:drawable="@drawable/btn_default_small_selected" />
+    <item android:state_enabled="true"
+        android:drawable="@drawable/btn_default_small_normal" />
+    <item android:state_focused="true"
+        android:drawable="@drawable/btn_default_small_normal_disable_focused" />
+    <item
+         android:drawable="@drawable/btn_default_small_normal_disable" />
+</selector>
+
diff --git a/core/res/res/drawable/btn_default_small_normal.9.png b/core/res/res/drawable/btn_default_small_normal.9.png
new file mode 100644
index 0000000..bcedd5f
--- /dev/null
+++ b/core/res/res/drawable/btn_default_small_normal.9.png
Binary files differ
diff --git a/core/res/res/drawable/btn_default_small_normal_disable.9.png b/core/res/res/drawable/btn_default_small_normal_disable.9.png
new file mode 100644
index 0000000..ac6260f
--- /dev/null
+++ b/core/res/res/drawable/btn_default_small_normal_disable.9.png
Binary files differ
diff --git a/core/res/res/drawable/btn_default_small_normal_disable_focused.9.png b/core/res/res/drawable/btn_default_small_normal_disable_focused.9.png
new file mode 100644
index 0000000..4ee1b3f
--- /dev/null
+++ b/core/res/res/drawable/btn_default_small_normal_disable_focused.9.png
Binary files differ
diff --git a/core/res/res/drawable/btn_default_small_pressed.9.png b/core/res/res/drawable/btn_default_small_pressed.9.png
new file mode 100644
index 0000000..25e38f4
--- /dev/null
+++ b/core/res/res/drawable/btn_default_small_pressed.9.png
Binary files differ
diff --git a/core/res/res/drawable/btn_default_small_selected.9.png b/core/res/res/drawable/btn_default_small_selected.9.png
new file mode 100644
index 0000000..cc209c6
--- /dev/null
+++ b/core/res/res/drawable/btn_default_small_selected.9.png
Binary files differ
diff --git a/core/res/res/drawable/btn_dialog.xml b/core/res/res/drawable/btn_dialog.xml
new file mode 100644
index 0000000..ed4c28a
--- /dev/null
+++ b/core/res/res/drawable/btn_dialog.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2007 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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_window_focused="false" android:state_enabled="true"
+        android:drawable="@drawable/btn_dialog_normal" />
+    <item android:state_window_focused="false" android:state_enabled="false"
+        android:drawable="@drawable/btn_dialog_disable" />
+    <item android:state_pressed="true"
+        android:drawable="@drawable/btn_dialog_pressed" />
+    <item android:state_enabled="true" android:state_focused="true"
+        android:drawable="@drawable/btn_dialog_selected" />
+    <item android:drawable="@drawable/btn_dialog_normal" />
+</selector>
+
diff --git a/core/res/res/drawable/btn_dialog_disable.png b/core/res/res/drawable/btn_dialog_disable.png
new file mode 100755
index 0000000..f041cab
--- /dev/null
+++ b/core/res/res/drawable/btn_dialog_disable.png
Binary files differ
diff --git a/core/res/res/drawable/btn_dialog_normal.png b/core/res/res/drawable/btn_dialog_normal.png
new file mode 100755
index 0000000..a2d27fa
--- /dev/null
+++ b/core/res/res/drawable/btn_dialog_normal.png
Binary files differ
diff --git a/core/res/res/drawable/btn_dialog_pressed.png b/core/res/res/drawable/btn_dialog_pressed.png
new file mode 100755
index 0000000..9c9922a
--- /dev/null
+++ b/core/res/res/drawable/btn_dialog_pressed.png
Binary files differ
diff --git a/core/res/res/drawable/btn_dialog_selected.png b/core/res/res/drawable/btn_dialog_selected.png
new file mode 100755
index 0000000..7656de5
--- /dev/null
+++ b/core/res/res/drawable/btn_dialog_selected.png
Binary files differ
diff --git a/core/res/res/drawable/btn_dropdown.xml b/core/res/res/drawable/btn_dropdown.xml
new file mode 100644
index 0000000..8ec8ece
--- /dev/null
+++ b/core/res/res/drawable/btn_dropdown.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item  android:state_window_focused="false" android:drawable="@drawable/btn_dropdown_normal" />
+    <item android:state_pressed="true" android:drawable="@drawable/btn_dropdown_pressed" />
+    <item android:state_focused="true" android:state_pressed="false"
+        android:drawable="@drawable/btn_dropdown_selected" />
+    <item android:drawable="@drawable/btn_dropdown_normal" />
+</selector>
+
diff --git a/core/res/res/drawable/btn_dropdown_normal.9.png b/core/res/res/drawable/btn_dropdown_normal.9.png
new file mode 100644
index 0000000..f6e9a19
--- /dev/null
+++ b/core/res/res/drawable/btn_dropdown_normal.9.png
Binary files differ
diff --git a/core/res/res/drawable/btn_dropdown_pressed.9.png b/core/res/res/drawable/btn_dropdown_pressed.9.png
new file mode 100644
index 0000000..3bdf52d
--- /dev/null
+++ b/core/res/res/drawable/btn_dropdown_pressed.9.png
Binary files differ
diff --git a/core/res/res/drawable/btn_dropdown_selected.9.png b/core/res/res/drawable/btn_dropdown_selected.9.png
new file mode 100644
index 0000000..087956e
--- /dev/null
+++ b/core/res/res/drawable/btn_dropdown_selected.9.png
Binary files differ
diff --git a/core/res/res/drawable/btn_erase_default.9.png b/core/res/res/drawable/btn_erase_default.9.png
new file mode 100755
index 0000000..c3bf60c
--- /dev/null
+++ b/core/res/res/drawable/btn_erase_default.9.png
Binary files differ
diff --git a/core/res/res/drawable/btn_erase_pressed.9.png b/core/res/res/drawable/btn_erase_pressed.9.png
new file mode 100755
index 0000000..727aafe
--- /dev/null
+++ b/core/res/res/drawable/btn_erase_pressed.9.png
Binary files differ
diff --git a/core/res/res/drawable/btn_erase_selected.9.png b/core/res/res/drawable/btn_erase_selected.9.png
new file mode 100755
index 0000000..c6bd020
--- /dev/null
+++ b/core/res/res/drawable/btn_erase_selected.9.png
Binary files differ
diff --git a/core/res/res/drawable/btn_keyboard_key.xml b/core/res/res/drawable/btn_keyboard_key.xml
new file mode 100644
index 0000000..45578e5
--- /dev/null
+++ b/core/res/res/drawable/btn_keyboard_key.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <!-- Toggle keys. Use checkable/checked state. -->
+    
+    <item android:state_checkable="true" android:state_checked="true" 
+          android:state_pressed="true"
+          android:drawable="@drawable/btn_keyboard_key_pressed_on" />
+    <item android:state_checkable="true" android:state_pressed="true"
+          android:drawable="@drawable/btn_keyboard_key_pressed_off" />
+    <item android:state_checkable="true" android:state_checked="true"
+          android:drawable="@drawable/btn_keyboard_key_normal_on" />
+    <item android:state_checkable="true"
+          android:drawable="@drawable/btn_keyboard_key_normal_off" />
+
+    <!-- Normal keys -->
+
+    <item android:state_pressed="true"
+          android:drawable="@drawable/btn_keyboard_key_pressed" />
+    <item
+          android:drawable="@drawable/btn_keyboard_key_normal" />
+          
+</selector>
diff --git a/core/res/res/drawable/btn_keyboard_key_normal.9.png b/core/res/res/drawable/btn_keyboard_key_normal.9.png
new file mode 100644
index 0000000..7ba18dd
--- /dev/null
+++ b/core/res/res/drawable/btn_keyboard_key_normal.9.png
Binary files differ
diff --git a/core/res/res/drawable/btn_keyboard_key_normal_off.9.png b/core/res/res/drawable/btn_keyboard_key_normal_off.9.png
new file mode 100644
index 0000000..bda9b83
--- /dev/null
+++ b/core/res/res/drawable/btn_keyboard_key_normal_off.9.png
Binary files differ
diff --git a/core/res/res/drawable/btn_keyboard_key_normal_on.9.png b/core/res/res/drawable/btn_keyboard_key_normal_on.9.png
new file mode 100644
index 0000000..0c16ed5
--- /dev/null
+++ b/core/res/res/drawable/btn_keyboard_key_normal_on.9.png
Binary files differ
diff --git a/core/res/res/drawable/btn_keyboard_key_pressed.9.png b/core/res/res/drawable/btn_keyboard_key_pressed.9.png
new file mode 100755
index 0000000..39b9314
--- /dev/null
+++ b/core/res/res/drawable/btn_keyboard_key_pressed.9.png
Binary files differ
diff --git a/core/res/res/drawable/btn_keyboard_key_pressed_off.9.png b/core/res/res/drawable/btn_keyboard_key_pressed_off.9.png
new file mode 100644
index 0000000..bdcf06e
--- /dev/null
+++ b/core/res/res/drawable/btn_keyboard_key_pressed_off.9.png
Binary files differ
diff --git a/core/res/res/drawable/btn_keyboard_key_pressed_on.9.png b/core/res/res/drawable/btn_keyboard_key_pressed_on.9.png
new file mode 100644
index 0000000..79621a9
--- /dev/null
+++ b/core/res/res/drawable/btn_keyboard_key_pressed_on.9.png
Binary files differ
diff --git a/core/res/res/drawable/btn_media_player.9.png b/core/res/res/drawable/btn_media_player.9.png
new file mode 100755
index 0000000..3ec3f683
--- /dev/null
+++ b/core/res/res/drawable/btn_media_player.9.png
Binary files differ
diff --git a/core/res/res/drawable/btn_media_player_disabled.9.png b/core/res/res/drawable/btn_media_player_disabled.9.png
new file mode 100755
index 0000000..e74335b
--- /dev/null
+++ b/core/res/res/drawable/btn_media_player_disabled.9.png
Binary files differ
diff --git a/core/res/res/drawable/btn_media_player_disabled_selected.9.png b/core/res/res/drawable/btn_media_player_disabled_selected.9.png
new file mode 100755
index 0000000..2c6517f
--- /dev/null
+++ b/core/res/res/drawable/btn_media_player_disabled_selected.9.png
Binary files differ
diff --git a/core/res/res/drawable/btn_media_player_pressed.9.png b/core/res/res/drawable/btn_media_player_pressed.9.png
new file mode 100755
index 0000000..40bee47
--- /dev/null
+++ b/core/res/res/drawable/btn_media_player_pressed.9.png
Binary files differ
diff --git a/core/res/res/drawable/btn_media_player_selected.9.png b/core/res/res/drawable/btn_media_player_selected.9.png
new file mode 100755
index 0000000..28d809f
--- /dev/null
+++ b/core/res/res/drawable/btn_media_player_selected.9.png
Binary files differ
diff --git a/core/res/res/drawable/btn_minus.xml b/core/res/res/drawable/btn_minus.xml
new file mode 100644
index 0000000..f427375
--- /dev/null
+++ b/core/res/res/drawable/btn_minus.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_enabled="false" android:state_focused="false"
+        android:drawable="@drawable/btn_minus_disable" />
+    <item android:state_enabled="false" android:state_focused="true"
+        android:drawable="@drawable/btn_minus_disable_focused" />
+    <item android:state_pressed="true" android:drawable="@drawable/btn_minus_pressed" />
+    <item android:state_focused="true" android:state_pressed="false"
+        android:drawable="@drawable/btn_minus_selected" />
+    <item android:drawable="@drawable/btn_minus_default" />
+</selector>
+
diff --git a/core/res/res/drawable/btn_minus_default.png b/core/res/res/drawable/btn_minus_default.png
new file mode 100644
index 0000000..ee95879
--- /dev/null
+++ b/core/res/res/drawable/btn_minus_default.png
Binary files differ
diff --git a/core/res/res/drawable/btn_minus_disable.png b/core/res/res/drawable/btn_minus_disable.png
new file mode 100644
index 0000000..dff7bf7
--- /dev/null
+++ b/core/res/res/drawable/btn_minus_disable.png
Binary files differ
diff --git a/core/res/res/drawable/btn_minus_disable_focused.png b/core/res/res/drawable/btn_minus_disable_focused.png
new file mode 100644
index 0000000..3f04557
--- /dev/null
+++ b/core/res/res/drawable/btn_minus_disable_focused.png
Binary files differ
diff --git a/core/res/res/drawable/btn_minus_pressed.png b/core/res/res/drawable/btn_minus_pressed.png
new file mode 100644
index 0000000..758d958
--- /dev/null
+++ b/core/res/res/drawable/btn_minus_pressed.png
Binary files differ
diff --git a/core/res/res/drawable/btn_minus_selected.png b/core/res/res/drawable/btn_minus_selected.png
new file mode 100644
index 0000000..752a249
--- /dev/null
+++ b/core/res/res/drawable/btn_minus_selected.png
Binary files differ
diff --git a/core/res/res/drawable/btn_plus.xml b/core/res/res/drawable/btn_plus.xml
new file mode 100644
index 0000000..57eba30
--- /dev/null
+++ b/core/res/res/drawable/btn_plus.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_enabled="false" android:state_focused="false"
+        android:drawable="@drawable/btn_plus_disable" />
+    <item android:state_enabled="false" android:state_focused="true"
+        android:drawable="@drawable/btn_plus_disable_focused" />
+    <item android:state_pressed="true" android:drawable="@drawable/btn_plus_pressed" />
+    <item android:state_focused="true" android:state_pressed="false"
+        android:drawable="@drawable/btn_plus_selected" />
+    <item android:drawable="@drawable/btn_plus_default" />
+</selector>
+
diff --git a/core/res/res/drawable/btn_plus_default.png b/core/res/res/drawable/btn_plus_default.png
new file mode 100644
index 0000000..aa31e37
--- /dev/null
+++ b/core/res/res/drawable/btn_plus_default.png
Binary files differ
diff --git a/core/res/res/drawable/btn_plus_disable.png b/core/res/res/drawable/btn_plus_disable.png
new file mode 100644
index 0000000..c373cd3
--- /dev/null
+++ b/core/res/res/drawable/btn_plus_disable.png
Binary files differ
diff --git a/core/res/res/drawable/btn_plus_disable_focused.png b/core/res/res/drawable/btn_plus_disable_focused.png
new file mode 100644
index 0000000..8f72a5f
--- /dev/null
+++ b/core/res/res/drawable/btn_plus_disable_focused.png
Binary files differ
diff --git a/core/res/res/drawable/btn_plus_pressed.png b/core/res/res/drawable/btn_plus_pressed.png
new file mode 100644
index 0000000..1fb8413
--- /dev/null
+++ b/core/res/res/drawable/btn_plus_pressed.png
Binary files differ
diff --git a/core/res/res/drawable/btn_plus_selected.png b/core/res/res/drawable/btn_plus_selected.png
new file mode 100644
index 0000000..47fe9bf
--- /dev/null
+++ b/core/res/res/drawable/btn_plus_selected.png
Binary files differ
diff --git a/core/res/res/drawable/btn_radio.xml b/core/res/res/drawable/btn_radio.xml
new file mode 100644
index 0000000..9b2ca71
--- /dev/null
+++ b/core/res/res/drawable/btn_radio.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_checked="true" android:state_window_focused="false"
+          android:drawable="@drawable/btn_radio_on" />
+    <item android:state_checked="false" android:state_window_focused="false"
+          android:drawable="@drawable/btn_radio_off" />
+          
+    <item android:state_checked="true" android:state_pressed="true"
+          android:drawable="@drawable/btn_radio_on_pressed" />
+    <item android:state_checked="false" android:state_pressed="true"
+          android:drawable="@drawable/btn_radio_off_pressed" />
+
+    <item android:state_checked="true" android:state_focused="true"
+          android:drawable="@drawable/btn_radio_on_selected" />
+    <item android:state_checked="false" android:state_focused="true"
+          android:drawable="@drawable/btn_radio_off_selected" />
+
+    <item android:state_checked="false" android:drawable="@drawable/btn_radio_off" />
+    <item android:state_checked="true" android:drawable="@drawable/btn_radio_on" />
+</selector>
diff --git a/core/res/res/drawable/btn_radio_label_background.9.png b/core/res/res/drawable/btn_radio_label_background.9.png
new file mode 100644
index 0000000..16e8939
--- /dev/null
+++ b/core/res/res/drawable/btn_radio_label_background.9.png
Binary files differ
diff --git a/core/res/res/drawable/btn_radio_off.png b/core/res/res/drawable/btn_radio_off.png
new file mode 100644
index 0000000..407632b
--- /dev/null
+++ b/core/res/res/drawable/btn_radio_off.png
Binary files differ
diff --git a/core/res/res/drawable/btn_radio_off_pressed.png b/core/res/res/drawable/btn_radio_off_pressed.png
new file mode 100644
index 0000000..d6d8a9d
--- /dev/null
+++ b/core/res/res/drawable/btn_radio_off_pressed.png
Binary files differ
diff --git a/core/res/res/drawable/btn_radio_off_selected.png b/core/res/res/drawable/btn_radio_off_selected.png
new file mode 100644
index 0000000..53f3e87
--- /dev/null
+++ b/core/res/res/drawable/btn_radio_off_selected.png
Binary files differ
diff --git a/core/res/res/drawable/btn_radio_on.png b/core/res/res/drawable/btn_radio_on.png
new file mode 100644
index 0000000..25a3ccc
--- /dev/null
+++ b/core/res/res/drawable/btn_radio_on.png
Binary files differ
diff --git a/core/res/res/drawable/btn_radio_on_pressed.png b/core/res/res/drawable/btn_radio_on_pressed.png
new file mode 100644
index 0000000..c904a35
--- /dev/null
+++ b/core/res/res/drawable/btn_radio_on_pressed.png
Binary files differ
diff --git a/core/res/res/drawable/btn_radio_on_selected.png b/core/res/res/drawable/btn_radio_on_selected.png
new file mode 100644
index 0000000..78e1fc0
--- /dev/null
+++ b/core/res/res/drawable/btn_radio_on_selected.png
Binary files differ
diff --git a/core/res/res/drawable/btn_rating_star_off_normal.png b/core/res/res/drawable/btn_rating_star_off_normal.png
new file mode 100644
index 0000000..a99441d
--- /dev/null
+++ b/core/res/res/drawable/btn_rating_star_off_normal.png
Binary files differ
diff --git a/core/res/res/drawable/btn_rating_star_off_pressed.png b/core/res/res/drawable/btn_rating_star_off_pressed.png
new file mode 100644
index 0000000..f47a454
--- /dev/null
+++ b/core/res/res/drawable/btn_rating_star_off_pressed.png
Binary files differ
diff --git a/core/res/res/drawable/btn_rating_star_off_selected.png b/core/res/res/drawable/btn_rating_star_off_selected.png
new file mode 100644
index 0000000..5f71e08
--- /dev/null
+++ b/core/res/res/drawable/btn_rating_star_off_selected.png
Binary files differ
diff --git a/core/res/res/drawable/btn_rating_star_on_normal.png b/core/res/res/drawable/btn_rating_star_on_normal.png
new file mode 100644
index 0000000..b7825d3
--- /dev/null
+++ b/core/res/res/drawable/btn_rating_star_on_normal.png
Binary files differ
diff --git a/core/res/res/drawable/btn_rating_star_on_pressed.png b/core/res/res/drawable/btn_rating_star_on_pressed.png
new file mode 100644
index 0000000..4052445
--- /dev/null
+++ b/core/res/res/drawable/btn_rating_star_on_pressed.png
Binary files differ
diff --git a/core/res/res/drawable/btn_rating_star_on_selected.png b/core/res/res/drawable/btn_rating_star_on_selected.png
new file mode 100644
index 0000000..5d139b6
--- /dev/null
+++ b/core/res/res/drawable/btn_rating_star_on_selected.png
Binary files differ
diff --git a/core/res/res/drawable/btn_star.xml b/core/res/res/drawable/btn_star.xml
new file mode 100644
index 0000000..6198006
--- /dev/null
+++ b/core/res/res/drawable/btn_star.xml
@@ -0,0 +1,49 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_checked="false" android:state_window_focused="false" 
+          android:drawable="@drawable/btn_star_big_off" />
+    <item android:state_checked="true" android:state_window_focused="false" 
+          android:drawable="@drawable/btn_star_big_on" />
+    <item android:state_checked="true" android:state_window_focused="false" 
+          android:state_enabled="false" android:drawable="@drawable/btn_star_big_on_disable" />
+    <item android:state_checked="false" android:state_window_focused="false" 
+          android:state_enabled="false" android:drawable="@drawable/btn_star_big_off_disable" />
+          
+    <item android:state_checked="true" android:state_pressed="true"
+          android:drawable="@drawable/btn_star_big_on_pressed" />
+    <item android:state_checked="false" android:state_pressed="true"
+          android:drawable="@drawable/btn_star_big_off_pressed" />
+
+    <item android:state_checked="true" android:state_focused="true"
+          android:drawable="@drawable/btn_star_big_on_selected" />
+    <item android:state_checked="false" android:state_focused="true"
+          android:drawable="@drawable/btn_star_big_off_selected" />
+
+    <item android:state_checked="true" android:state_focused="true" android:state_enabled="false"
+          android:drawable="@drawable/btn_star_big_on_disable_focused" />
+    <item android:state_checked="true" android:state_focused="false" android:state_enabled="false"
+          android:drawable="@drawable/btn_star_big_on_disable" />
+          
+    <item android:state_checked="false" android:state_focused="true" android:state_enabled="false"
+          android:drawable="@drawable/btn_star_big_off_disable_focused" />
+    <item android:state_checked="false" android:state_focused="false" android:state_enabled="false"
+          android:drawable="@drawable/btn_star_big_off_disable" />
+          
+    <item android:state_checked="false" android:drawable="@drawable/btn_star_big_off" />
+    <item android:state_checked="true" android:drawable="@drawable/btn_star_big_on" />
+</selector>
diff --git a/core/res/res/drawable/btn_star_big_buttonless_off.png b/core/res/res/drawable/btn_star_big_buttonless_off.png
new file mode 100755
index 0000000..5b9cd81
--- /dev/null
+++ b/core/res/res/drawable/btn_star_big_buttonless_off.png
Binary files differ
diff --git a/core/res/res/drawable/btn_star_big_buttonless_on.png b/core/res/res/drawable/btn_star_big_buttonless_on.png
new file mode 100755
index 0000000..5957c65
--- /dev/null
+++ b/core/res/res/drawable/btn_star_big_buttonless_on.png
Binary files differ
diff --git a/core/res/res/drawable/btn_star_big_off.png b/core/res/res/drawable/btn_star_big_off.png
new file mode 100755
index 0000000..7e9342b
--- /dev/null
+++ b/core/res/res/drawable/btn_star_big_off.png
Binary files differ
diff --git a/core/res/res/drawable/btn_star_big_off_disable.png b/core/res/res/drawable/btn_star_big_off_disable.png
new file mode 100755
index 0000000..066d920
--- /dev/null
+++ b/core/res/res/drawable/btn_star_big_off_disable.png
Binary files differ
diff --git a/core/res/res/drawable/btn_star_big_off_disable_focused.png b/core/res/res/drawable/btn_star_big_off_disable_focused.png
new file mode 100755
index 0000000..1855d2c
--- /dev/null
+++ b/core/res/res/drawable/btn_star_big_off_disable_focused.png
Binary files differ
diff --git a/core/res/res/drawable/btn_star_big_off_pressed.png b/core/res/res/drawable/btn_star_big_off_pressed.png
new file mode 100755
index 0000000..f1b8912
--- /dev/null
+++ b/core/res/res/drawable/btn_star_big_off_pressed.png
Binary files differ
diff --git a/core/res/res/drawable/btn_star_big_off_selected.png b/core/res/res/drawable/btn_star_big_off_selected.png
new file mode 100755
index 0000000..0be64c4
--- /dev/null
+++ b/core/res/res/drawable/btn_star_big_off_selected.png
Binary files differ
diff --git a/core/res/res/drawable/btn_star_big_on.png b/core/res/res/drawable/btn_star_big_on.png
new file mode 100755
index 0000000..a9bdb05
--- /dev/null
+++ b/core/res/res/drawable/btn_star_big_on.png
Binary files differ
diff --git a/core/res/res/drawable/btn_star_big_on_disable.png b/core/res/res/drawable/btn_star_big_on_disable.png
new file mode 100755
index 0000000..5e65a2f
--- /dev/null
+++ b/core/res/res/drawable/btn_star_big_on_disable.png
Binary files differ
diff --git a/core/res/res/drawable/btn_star_big_on_disable_focused.png b/core/res/res/drawable/btn_star_big_on_disable_focused.png
new file mode 100755
index 0000000..de57571
--- /dev/null
+++ b/core/res/res/drawable/btn_star_big_on_disable_focused.png
Binary files differ
diff --git a/core/res/res/drawable/btn_star_big_on_pressed.png b/core/res/res/drawable/btn_star_big_on_pressed.png
new file mode 100755
index 0000000..159a84b
--- /dev/null
+++ b/core/res/res/drawable/btn_star_big_on_pressed.png
Binary files differ
diff --git a/core/res/res/drawable/btn_star_big_on_selected.png b/core/res/res/drawable/btn_star_big_on_selected.png
new file mode 100755
index 0000000..0592d51
--- /dev/null
+++ b/core/res/res/drawable/btn_star_big_on_selected.png
Binary files differ
diff --git a/core/res/res/drawable/btn_star_buttonless.xml b/core/res/res/drawable/btn_star_buttonless.xml
new file mode 100644
index 0000000..8d60ed2
--- /dev/null
+++ b/core/res/res/drawable/btn_star_buttonless.xml
@@ -0,0 +1,51 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_checked="false" android:state_window_focused="false" 
+          android:drawable="@drawable/btn_star_big_buttonless_off" />
+    <item android:state_checked="true" android:state_window_focused="false" 
+          android:drawable="@drawable/btn_star_big_buttonless_on" />
+<!-- 
+    <item android:state_checked="true" android:state_window_focused="false" 
+          android:state_enabled="false" android:drawable="@drawable/btn_star_big_on_disable" />
+    <item android:state_checked="false" android:state_window_focused="false" 
+          android:state_enabled="false" android:drawable="@drawable/btn_star_big_off_disable" />
+          
+    <item android:state_checked="true" android:state_pressed="true"
+          android:drawable="@drawable/btn_star_big_on_pressed" />
+    <item android:state_checked="false" android:state_pressed="true"
+          android:drawable="@drawable/btn_star_big_off_pressed" />
+
+    <item android:state_checked="true" android:state_focused="true"
+          android:drawable="@drawable/btn_star_big_on_selected" />
+    <item android:state_checked="false" android:state_focused="true"
+          android:drawable="@drawable/btn_star_big_off_selected" />
+
+    <item android:state_checked="true" android:state_focused="true" android:state_enabled="false"
+          android:drawable="@drawable/btn_star_big_on_disable_focused" />
+    <item android:state_checked="true" android:state_focused="false" android:state_enabled="false"
+          android:drawable="@drawable/btn_star_big_on_disable" />
+          
+    <item android:state_checked="false" android:state_focused="true" android:state_enabled="false"
+          android:drawable="@drawable/btn_star_big_off_disable_focused" />
+    <item android:state_checked="false" android:state_focused="false" android:state_enabled="false"
+          android:drawable="@drawable/btn_star_big_off_disable" />
+-->
+          
+    <item android:state_checked="false" android:drawable="@drawable/btn_star_big_buttonless_off" />
+    <item android:state_checked="true" android:drawable="@drawable/btn_star_big_buttonless_on" />
+</selector>
diff --git a/core/res/res/drawable/btn_star_label_background.9.png b/core/res/res/drawable/btn_star_label_background.9.png
new file mode 100644
index 0000000..e493171
--- /dev/null
+++ b/core/res/res/drawable/btn_star_label_background.9.png
Binary files differ
diff --git a/core/res/res/drawable/btn_toggle.xml b/core/res/res/drawable/btn_toggle.xml
new file mode 100644
index 0000000..13b4701
--- /dev/null
+++ b/core/res/res/drawable/btn_toggle.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_checked="false" android:drawable="@drawable/btn_toggle_off" />
+    <item android:state_checked="true" android:drawable="@drawable/btn_toggle_on" />
+</selector>
diff --git a/core/res/res/drawable/btn_toggle_bg.xml b/core/res/res/drawable/btn_toggle_bg.xml
new file mode 100644
index 0000000..897a21d
--- /dev/null
+++ b/core/res/res/drawable/btn_toggle_bg.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:id="@+android:id/background" android:drawable="@android:drawable/btn_default_small" />
+    <item android:id="@+android:id/toggle" android:drawable="@android:drawable/btn_toggle" />
+</layer-list>
diff --git a/core/res/res/drawable/btn_toggle_off.9.png b/core/res/res/drawable/btn_toggle_off.9.png
new file mode 100644
index 0000000..26ee1c2
--- /dev/null
+++ b/core/res/res/drawable/btn_toggle_off.9.png
Binary files differ
diff --git a/core/res/res/drawable/btn_toggle_on.9.png b/core/res/res/drawable/btn_toggle_on.9.png
new file mode 100644
index 0000000..53e95af
--- /dev/null
+++ b/core/res/res/drawable/btn_toggle_on.9.png
Binary files differ
diff --git a/core/res/res/drawable/btn_zoom_page.xml b/core/res/res/drawable/btn_zoom_page.xml
new file mode 100644
index 0000000..71840ad
--- /dev/null
+++ b/core/res/res/drawable/btn_zoom_page.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <item android:state_focused="true" android:state_pressed="true"
+          android:drawable="@drawable/btn_zoom_page_press" />
+    <item android:state_focused="false" android:state_pressed="true"
+          android:drawable="@drawable/btn_zoom_page_press" />
+    <item android:state_focused="true" android:state_pressed="false"
+          android:drawable="@drawable/btn_zoom_page_press" />
+    <item android:state_focused="false" android:state_pressed="false"
+          android:drawable="@drawable/btn_zoom_page_normal" />
+
+</selector>
diff --git a/core/res/res/drawable/btn_zoom_page_normal.png b/core/res/res/drawable/btn_zoom_page_normal.png
new file mode 100644
index 0000000..839915b
--- /dev/null
+++ b/core/res/res/drawable/btn_zoom_page_normal.png
Binary files differ
diff --git a/core/res/res/drawable/btn_zoom_page_press.png b/core/res/res/drawable/btn_zoom_page_press.png
new file mode 100644
index 0000000..e8af276
--- /dev/null
+++ b/core/res/res/drawable/btn_zoom_page_press.png
Binary files differ
diff --git a/core/res/res/drawable/button_inset.xml b/core/res/res/drawable/button_inset.xml
new file mode 100644
index 0000000..fd274982
--- /dev/null
+++ b/core/res/res/drawable/button_inset.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_pressed="true" android:drawable="@drawable/btn_erase_pressed" />
+    <item android:state_focused="true" android:state_pressed="false"
+        android:drawable="@drawable/btn_erase_selected" />
+    <item android:drawable="@drawable/btn_erase_default" />
+</selector>
+
diff --git a/core/res/res/drawable/button_onoff_indicator_off.png b/core/res/res/drawable/button_onoff_indicator_off.png
new file mode 100644
index 0000000..91e7244
--- /dev/null
+++ b/core/res/res/drawable/button_onoff_indicator_off.png
Binary files differ
diff --git a/core/res/res/drawable/button_onoff_indicator_on.png b/core/res/res/drawable/button_onoff_indicator_on.png
new file mode 100644
index 0000000..361364b
--- /dev/null
+++ b/core/res/res/drawable/button_onoff_indicator_on.png
Binary files differ
diff --git a/core/res/res/drawable/checkbox_off_background.png b/core/res/res/drawable/checkbox_off_background.png
new file mode 100644
index 0000000..6b2124f
--- /dev/null
+++ b/core/res/res/drawable/checkbox_off_background.png
Binary files differ
diff --git a/core/res/res/drawable/checkbox_on_background.png b/core/res/res/drawable/checkbox_on_background.png
new file mode 100644
index 0000000..56495fc
--- /dev/null
+++ b/core/res/res/drawable/checkbox_on_background.png
Binary files differ
diff --git a/core/res/res/drawable/clock_dial.png b/core/res/res/drawable/clock_dial.png
new file mode 100644
index 0000000..82f73fe
--- /dev/null
+++ b/core/res/res/drawable/clock_dial.png
Binary files differ
diff --git a/core/res/res/drawable/clock_hand_hour.png b/core/res/res/drawable/clock_hand_hour.png
new file mode 100644
index 0000000..1f0aec8
--- /dev/null
+++ b/core/res/res/drawable/clock_hand_hour.png
Binary files differ
diff --git a/core/res/res/drawable/clock_hand_minute.png b/core/res/res/drawable/clock_hand_minute.png
new file mode 100644
index 0000000..6cd8a4b
--- /dev/null
+++ b/core/res/res/drawable/clock_hand_minute.png
Binary files differ
diff --git a/core/res/res/drawable/code_lock_bottom.9.png b/core/res/res/drawable/code_lock_bottom.9.png
new file mode 100644
index 0000000..812cf00
--- /dev/null
+++ b/core/res/res/drawable/code_lock_bottom.9.png
Binary files differ
diff --git a/core/res/res/drawable/code_lock_left.9.png b/core/res/res/drawable/code_lock_left.9.png
new file mode 100644
index 0000000..a53264a
--- /dev/null
+++ b/core/res/res/drawable/code_lock_left.9.png
Binary files differ
diff --git a/core/res/res/drawable/code_lock_top.9.png b/core/res/res/drawable/code_lock_top.9.png
new file mode 100644
index 0000000..2b75a7c
--- /dev/null
+++ b/core/res/res/drawable/code_lock_top.9.png
Binary files differ
diff --git a/core/res/res/drawable/compass_arrow.png b/core/res/res/drawable/compass_arrow.png
new file mode 100644
index 0000000..5a4d8c1
--- /dev/null
+++ b/core/res/res/drawable/compass_arrow.png
Binary files differ
diff --git a/core/res/res/drawable/compass_base.png b/core/res/res/drawable/compass_base.png
new file mode 100644
index 0000000..3d694f0
--- /dev/null
+++ b/core/res/res/drawable/compass_base.png
Binary files differ
diff --git a/core/res/res/drawable/dark_header.9.png b/core/res/res/drawable/dark_header.9.png
new file mode 100644
index 0000000..8fa5f09
--- /dev/null
+++ b/core/res/res/drawable/dark_header.9.png
Binary files differ
diff --git a/core/res/res/drawable/default_wallpaper.jpg b/core/res/res/drawable/default_wallpaper.jpg
new file mode 100644
index 0000000..5ba522f
--- /dev/null
+++ b/core/res/res/drawable/default_wallpaper.jpg
Binary files differ
diff --git a/core/res/res/drawable/dialog_divider_horizontal_light.9.png b/core/res/res/drawable/dialog_divider_horizontal_light.9.png
new file mode 100755
index 0000000..b69619b
--- /dev/null
+++ b/core/res/res/drawable/dialog_divider_horizontal_light.9.png
Binary files differ
diff --git a/core/res/res/drawable/divider_horizontal_bright.9.png b/core/res/res/drawable/divider_horizontal_bright.9.png
new file mode 100644
index 0000000..144fc22
--- /dev/null
+++ b/core/res/res/drawable/divider_horizontal_bright.9.png
Binary files differ
diff --git a/core/res/res/drawable/divider_horizontal_dark.9.png b/core/res/res/drawable/divider_horizontal_dark.9.png
new file mode 100644
index 0000000..08838ca
--- /dev/null
+++ b/core/res/res/drawable/divider_horizontal_dark.9.png
Binary files differ
diff --git a/core/res/res/drawable/divider_horizontal_dim_dark.9.png b/core/res/res/drawable/divider_horizontal_dim_dark.9.png
new file mode 100644
index 0000000..08838ca
--- /dev/null
+++ b/core/res/res/drawable/divider_horizontal_dim_dark.9.png
Binary files differ
diff --git a/core/res/res/drawable/divider_horizontal_textfield.9.png b/core/res/res/drawable/divider_horizontal_textfield.9.png
new file mode 100644
index 0000000..43eb51d
--- /dev/null
+++ b/core/res/res/drawable/divider_horizontal_textfield.9.png
Binary files differ
diff --git a/core/res/res/drawable/divider_vertical_bright.9.png b/core/res/res/drawable/divider_vertical_bright.9.png
new file mode 100644
index 0000000..da6e4ec
--- /dev/null
+++ b/core/res/res/drawable/divider_vertical_bright.9.png
Binary files differ
diff --git a/core/res/res/drawable/edit_text.xml b/core/res/res/drawable/edit_text.xml
new file mode 100644
index 0000000..23a97e9
--- /dev/null
+++ b/core/res/res/drawable/edit_text.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2007 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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_window_focused="false" android:state_enabled="true"
+        android:drawable="@drawable/textfield_default" />
+    <item android:state_window_focused="false" android:state_enabled="false"
+        android:drawable="@drawable/textfield_disabled" />
+    <item android:state_pressed="true" android:drawable="@drawable/textfield_pressed" />
+    <item android:state_enabled="true" android:state_focused="true" android:drawable="@drawable/textfield_selected" />
+    <item android:state_enabled="true" android:drawable="@drawable/textfield_default" />
+    <item android:state_focused="true" android:drawable="@drawable/textfield_disabled_selected" />
+    <item android:drawable="@drawable/textfield_disabled" />
+</selector>
+
diff --git a/core/res/res/drawable/editbox_background.xml b/core/res/res/drawable/editbox_background.xml
new file mode 100644
index 0000000..976a212
--- /dev/null
+++ b/core/res/res/drawable/editbox_background.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* //device/apps/common/assets/res/any/drawable/editbox_background.xml
+**
+** Copyright 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.
+*/
+-->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_focused="true" android:drawable="@drawable/editbox_background_focus_yellow" />
+    <item android:drawable="@drawable/editbox_background_normal" />
+</selector>
+
diff --git a/core/res/res/drawable/editbox_background_focus_yellow.9.png b/core/res/res/drawable/editbox_background_focus_yellow.9.png
new file mode 100644
index 0000000..faf52ed
--- /dev/null
+++ b/core/res/res/drawable/editbox_background_focus_yellow.9.png
Binary files differ
diff --git a/core/res/res/drawable/editbox_background_normal.9.png b/core/res/res/drawable/editbox_background_normal.9.png
new file mode 100644
index 0000000..9b8be77
--- /dev/null
+++ b/core/res/res/drawable/editbox_background_normal.9.png
Binary files differ
diff --git a/core/res/res/drawable/editbox_dropdown_background.9.png b/core/res/res/drawable/editbox_dropdown_background.9.png
new file mode 100644
index 0000000..ed1bc29
--- /dev/null
+++ b/core/res/res/drawable/editbox_dropdown_background.9.png
Binary files differ
diff --git a/core/res/res/drawable/editbox_dropdown_background_dark.9.png b/core/res/res/drawable/editbox_dropdown_background_dark.9.png
new file mode 100644
index 0000000..88c1d9d
--- /dev/null
+++ b/core/res/res/drawable/editbox_dropdown_background_dark.9.png
Binary files differ
diff --git a/core/res/res/drawable/emo_im_angel.png b/core/res/res/drawable/emo_im_angel.png
new file mode 100644
index 0000000..c34dfa6
--- /dev/null
+++ b/core/res/res/drawable/emo_im_angel.png
Binary files differ
diff --git a/core/res/res/drawable/emo_im_cool.png b/core/res/res/drawable/emo_im_cool.png
new file mode 100644
index 0000000..d8eeb34
--- /dev/null
+++ b/core/res/res/drawable/emo_im_cool.png
Binary files differ
diff --git a/core/res/res/drawable/emo_im_crying.png b/core/res/res/drawable/emo_im_crying.png
new file mode 100644
index 0000000..1cafdb3
--- /dev/null
+++ b/core/res/res/drawable/emo_im_crying.png
Binary files differ
diff --git a/core/res/res/drawable/emo_im_embarrassed.png b/core/res/res/drawable/emo_im_embarrassed.png
new file mode 100644
index 0000000..e4db963
--- /dev/null
+++ b/core/res/res/drawable/emo_im_embarrassed.png
Binary files differ
diff --git a/core/res/res/drawable/emo_im_foot_in_mouth.png b/core/res/res/drawable/emo_im_foot_in_mouth.png
new file mode 100644
index 0000000..09d1fba
--- /dev/null
+++ b/core/res/res/drawable/emo_im_foot_in_mouth.png
Binary files differ
diff --git a/core/res/res/drawable/emo_im_happy.png b/core/res/res/drawable/emo_im_happy.png
new file mode 100644
index 0000000..b86602a
--- /dev/null
+++ b/core/res/res/drawable/emo_im_happy.png
Binary files differ
diff --git a/core/res/res/drawable/emo_im_kissing.png b/core/res/res/drawable/emo_im_kissing.png
new file mode 100644
index 0000000..56378f6
--- /dev/null
+++ b/core/res/res/drawable/emo_im_kissing.png
Binary files differ
diff --git a/core/res/res/drawable/emo_im_laughing.png b/core/res/res/drawable/emo_im_laughing.png
new file mode 100644
index 0000000..980bf28
--- /dev/null
+++ b/core/res/res/drawable/emo_im_laughing.png
Binary files differ
diff --git a/core/res/res/drawable/emo_im_lips_are_sealed.png b/core/res/res/drawable/emo_im_lips_are_sealed.png
new file mode 100644
index 0000000..f2de993
--- /dev/null
+++ b/core/res/res/drawable/emo_im_lips_are_sealed.png
Binary files differ
diff --git a/core/res/res/drawable/emo_im_money_mouth.png b/core/res/res/drawable/emo_im_money_mouth.png
new file mode 100644
index 0000000..08c53fd
--- /dev/null
+++ b/core/res/res/drawable/emo_im_money_mouth.png
Binary files differ
diff --git a/core/res/res/drawable/emo_im_sad.png b/core/res/res/drawable/emo_im_sad.png
new file mode 100644
index 0000000..31c08d0
--- /dev/null
+++ b/core/res/res/drawable/emo_im_sad.png
Binary files differ
diff --git a/core/res/res/drawable/emo_im_surprised.png b/core/res/res/drawable/emo_im_surprised.png
new file mode 100644
index 0000000..abe8c7a
--- /dev/null
+++ b/core/res/res/drawable/emo_im_surprised.png
Binary files differ
diff --git a/core/res/res/drawable/emo_im_tongue_sticking_out.png b/core/res/res/drawable/emo_im_tongue_sticking_out.png
new file mode 100644
index 0000000..6f0f47b
--- /dev/null
+++ b/core/res/res/drawable/emo_im_tongue_sticking_out.png
Binary files differ
diff --git a/core/res/res/drawable/emo_im_undecided.png b/core/res/res/drawable/emo_im_undecided.png
new file mode 100644
index 0000000..eb4f8c5
--- /dev/null
+++ b/core/res/res/drawable/emo_im_undecided.png
Binary files differ
diff --git a/core/res/res/drawable/emo_im_winking.png b/core/res/res/drawable/emo_im_winking.png
new file mode 100644
index 0000000..568562a
--- /dev/null
+++ b/core/res/res/drawable/emo_im_winking.png
Binary files differ
diff --git a/core/res/res/drawable/emo_im_wtf.png b/core/res/res/drawable/emo_im_wtf.png
new file mode 100644
index 0000000..41dd47f
--- /dev/null
+++ b/core/res/res/drawable/emo_im_wtf.png
Binary files differ
diff --git a/core/res/res/drawable/emo_im_yelling.png b/core/res/res/drawable/emo_im_yelling.png
new file mode 100644
index 0000000..c3c8612
--- /dev/null
+++ b/core/res/res/drawable/emo_im_yelling.png
Binary files differ
diff --git a/core/res/res/drawable/expander_group.xml b/core/res/res/drawable/expander_group.xml
new file mode 100644
index 0000000..ac258b3
--- /dev/null
+++ b/core/res/res/drawable/expander_group.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item
+        android:state_expanded="true"
+        android:drawable="@drawable/expander_ic_maximized" />
+    <item
+        android:drawable="@drawable/expander_ic_minimized" />
+</selector>
diff --git a/core/res/res/drawable/expander_ic_maximized.9.png b/core/res/res/drawable/expander_ic_maximized.9.png
new file mode 100644
index 0000000..bad4b82
--- /dev/null
+++ b/core/res/res/drawable/expander_ic_maximized.9.png
Binary files differ
diff --git a/core/res/res/drawable/expander_ic_minimized.9.png b/core/res/res/drawable/expander_ic_minimized.9.png
new file mode 100644
index 0000000..af89072
--- /dev/null
+++ b/core/res/res/drawable/expander_ic_minimized.9.png
Binary files differ
diff --git a/core/res/res/drawable/extract_edit_text.xml b/core/res/res/drawable/extract_edit_text.xml
new file mode 100644
index 0000000..9c6c4ba
--- /dev/null
+++ b/core/res/res/drawable/extract_edit_text.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2007 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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_pressed="true" android:drawable="@drawable/keyboard_textfield_pressed" />
+    <item android:state_focused="true" android:drawable="@drawable/keyboard_textfield_selected" />
+    <item android:drawable="@drawable/textfield_disabled" />
+</selector>
+
diff --git a/core/res/res/drawable/focused_application_background_static.png b/core/res/res/drawable/focused_application_background_static.png
new file mode 100644
index 0000000..fd18d30
--- /dev/null
+++ b/core/res/res/drawable/focused_application_background_static.png
Binary files differ
diff --git a/core/res/res/drawable/frame_gallery_thumb.9.png b/core/res/res/drawable/frame_gallery_thumb.9.png
new file mode 100755
index 0000000..804f6f3
--- /dev/null
+++ b/core/res/res/drawable/frame_gallery_thumb.9.png
Binary files differ
diff --git a/core/res/res/drawable/frame_gallery_thumb_pressed.9.png b/core/res/res/drawable/frame_gallery_thumb_pressed.9.png
new file mode 100755
index 0000000..e1ffa06
--- /dev/null
+++ b/core/res/res/drawable/frame_gallery_thumb_pressed.9.png
Binary files differ
diff --git a/core/res/res/drawable/frame_gallery_thumb_selected.9.png b/core/res/res/drawable/frame_gallery_thumb_selected.9.png
new file mode 100755
index 0000000..8bae932
--- /dev/null
+++ b/core/res/res/drawable/frame_gallery_thumb_selected.9.png
Binary files differ
diff --git a/core/res/res/drawable/gallery_item_background.xml b/core/res/res/drawable/gallery_item_background.xml
new file mode 100644
index 0000000..c7eb7ea
--- /dev/null
+++ b/core/res/res/drawable/gallery_item_background.xml
@@ -0,0 +1,57 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+
+
+    <!-- When the window does not have focus. -->
+    
+    <item android:drawable="@drawable/gallery_selected_default" 
+        android:state_selected="true"
+        android:state_window_focused="false"
+        />
+        
+    <item android:drawable="@drawable/gallery_unselected_default" 
+        android:state_selected="false"
+        android:state_window_focused="false"
+        />
+    
+    
+    <!-- When the window does have focus. -->
+        
+    <item android:drawable="@drawable/gallery_selected_pressed" 
+        android:state_selected="true"
+        android:state_pressed="true"
+        />
+        
+    <item android:drawable="@drawable/gallery_selected_focused" 
+        android:state_selected="true"
+        android:state_focused="true"
+        />
+        
+    <item android:drawable="@drawable/gallery_selected_default" 
+        android:state_selected="true"
+        />
+        
+    <item android:drawable="@drawable/gallery_unselected_pressed" 
+        android:state_selected="false"
+        android:state_pressed="true"
+        />
+        
+    <item android:drawable="@drawable/gallery_unselected_default" 
+        />
+        
+</selector>
diff --git a/core/res/res/drawable/gallery_selected_default.9.png b/core/res/res/drawable/gallery_selected_default.9.png
new file mode 100755
index 0000000..22122b2
--- /dev/null
+++ b/core/res/res/drawable/gallery_selected_default.9.png
Binary files differ
diff --git a/core/res/res/drawable/gallery_selected_focused.9.png b/core/res/res/drawable/gallery_selected_focused.9.png
new file mode 100755
index 0000000..1332745
--- /dev/null
+++ b/core/res/res/drawable/gallery_selected_focused.9.png
Binary files differ
diff --git a/core/res/res/drawable/gallery_selected_pressed.9.png b/core/res/res/drawable/gallery_selected_pressed.9.png
new file mode 100755
index 0000000..306e543
--- /dev/null
+++ b/core/res/res/drawable/gallery_selected_pressed.9.png
Binary files differ
diff --git a/core/res/res/drawable/gallery_thumb.xml b/core/res/res/drawable/gallery_thumb.xml
new file mode 100644
index 0000000..4425727
--- /dev/null
+++ b/core/res/res/drawable/gallery_thumb.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_focused="true" android:state_pressed="false" android:drawable="@drawable/frame_gallery_thumb_selected" />
+    <item android:state_focused="true" android:state_pressed="true" android:drawable="@drawable/frame_gallery_thumb_pressed" />
+    <item android:state_focused="false" android:state_pressed="true" android:drawable="@drawable/frame_gallery_thumb_pressed" />
+    <item android:drawable="@drawable/frame_gallery_thumb" />
+</selector>
+
diff --git a/core/res/res/drawable/gallery_unselected_default.9.png b/core/res/res/drawable/gallery_unselected_default.9.png
new file mode 100755
index 0000000..0df06fa
--- /dev/null
+++ b/core/res/res/drawable/gallery_unselected_default.9.png
Binary files differ
diff --git a/core/res/res/drawable/gallery_unselected_pressed.9.png b/core/res/res/drawable/gallery_unselected_pressed.9.png
new file mode 100644
index 0000000..4b25c3f
--- /dev/null
+++ b/core/res/res/drawable/gallery_unselected_pressed.9.png
Binary files differ
diff --git a/core/res/res/drawable/grid_selector_background.xml b/core/res/res/drawable/grid_selector_background.xml
new file mode 100644
index 0000000..232aebc
--- /dev/null
+++ b/core/res/res/drawable/grid_selector_background.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_focused="true" android:state_pressed="false"
+        android:drawable="@drawable/grid_selector_background_focus" />
+    <item android:state_focused="true" android:state_pressed="true"
+        android:drawable="@drawable/grid_selector_background_pressed" />
+    <item android:state_focused="false" android:state_pressed="true"
+        android:drawable="@drawable/grid_selector_background_pressed" />
+</selector>
diff --git a/core/res/res/drawable/grid_selector_background_focus.9.png b/core/res/res/drawable/grid_selector_background_focus.9.png
new file mode 100644
index 0000000..2e28232
--- /dev/null
+++ b/core/res/res/drawable/grid_selector_background_focus.9.png
Binary files differ
diff --git a/core/res/res/drawable/grid_selector_background_pressed.9.png b/core/res/res/drawable/grid_selector_background_pressed.9.png
new file mode 100644
index 0000000..e20f091
--- /dev/null
+++ b/core/res/res/drawable/grid_selector_background_pressed.9.png
Binary files differ
diff --git a/core/res/res/drawable/highlight_disabled.9.png b/core/res/res/drawable/highlight_disabled.9.png
new file mode 100644
index 0000000..1393262
--- /dev/null
+++ b/core/res/res/drawable/highlight_disabled.9.png
Binary files differ
diff --git a/core/res/res/drawable/highlight_pressed.9.png b/core/res/res/drawable/highlight_pressed.9.png
new file mode 100644
index 0000000..9bd2b50
--- /dev/null
+++ b/core/res/res/drawable/highlight_pressed.9.png
Binary files differ
diff --git a/core/res/res/drawable/highlight_selected.9.png b/core/res/res/drawable/highlight_selected.9.png
new file mode 100644
index 0000000..ecf0cad
--- /dev/null
+++ b/core/res/res/drawable/highlight_selected.9.png
Binary files differ
diff --git a/core/res/res/drawable/ic_btn_search.png b/core/res/res/drawable/ic_btn_search.png
new file mode 100644
index 0000000..3f8913e
--- /dev/null
+++ b/core/res/res/drawable/ic_btn_search.png
Binary files differ
diff --git a/core/res/res/drawable/ic_btn_speak_now.png b/core/res/res/drawable/ic_btn_speak_now.png
new file mode 100644
index 0000000..83ee68b
--- /dev/null
+++ b/core/res/res/drawable/ic_btn_speak_now.png
Binary files differ
diff --git a/core/res/res/drawable/ic_bullet_key_permission.png b/core/res/res/drawable/ic_bullet_key_permission.png
new file mode 100755
index 0000000..c8a4939
--- /dev/null
+++ b/core/res/res/drawable/ic_bullet_key_permission.png
Binary files differ
diff --git a/core/res/res/drawable/ic_delete.png b/core/res/res/drawable/ic_delete.png
new file mode 100644
index 0000000..f074db3
--- /dev/null
+++ b/core/res/res/drawable/ic_delete.png
Binary files differ
diff --git a/core/res/res/drawable/ic_dialog_alert.png b/core/res/res/drawable/ic_dialog_alert.png
new file mode 100644
index 0000000..0a7de047
--- /dev/null
+++ b/core/res/res/drawable/ic_dialog_alert.png
Binary files differ
diff --git a/core/res/res/drawable/ic_dialog_dialer.png b/core/res/res/drawable/ic_dialog_dialer.png
new file mode 100644
index 0000000..f0c1838
--- /dev/null
+++ b/core/res/res/drawable/ic_dialog_dialer.png
Binary files differ
diff --git a/core/res/res/drawable/ic_dialog_email.png b/core/res/res/drawable/ic_dialog_email.png
new file mode 100644
index 0000000..20ebb13
--- /dev/null
+++ b/core/res/res/drawable/ic_dialog_email.png
Binary files differ
diff --git a/core/res/res/drawable/ic_dialog_info.png b/core/res/res/drawable/ic_dialog_info.png
new file mode 100755
index 0000000..e8b0229
--- /dev/null
+++ b/core/res/res/drawable/ic_dialog_info.png
Binary files differ
diff --git a/core/res/res/drawable/ic_dialog_map.png b/core/res/res/drawable/ic_dialog_map.png
new file mode 100644
index 0000000..b126354
--- /dev/null
+++ b/core/res/res/drawable/ic_dialog_map.png
Binary files differ
diff --git a/core/res/res/drawable/ic_dialog_menu_generic.png b/core/res/res/drawable/ic_dialog_menu_generic.png
new file mode 100755
index 0000000..de07bda
--- /dev/null
+++ b/core/res/res/drawable/ic_dialog_menu_generic.png
Binary files differ
diff --git a/core/res/res/drawable/ic_dialog_time.png b/core/res/res/drawable/ic_dialog_time.png
new file mode 100755
index 0000000..dffec29
--- /dev/null
+++ b/core/res/res/drawable/ic_dialog_time.png
Binary files differ
diff --git a/core/res/res/drawable/ic_dialog_usb.png b/core/res/res/drawable/ic_dialog_usb.png
new file mode 100644
index 0000000..fbc8a9d
--- /dev/null
+++ b/core/res/res/drawable/ic_dialog_usb.png
Binary files differ
diff --git a/core/res/res/drawable/ic_emergency.png b/core/res/res/drawable/ic_emergency.png
new file mode 100755
index 0000000..d99abf8
--- /dev/null
+++ b/core/res/res/drawable/ic_emergency.png
Binary files differ
diff --git a/core/res/res/drawable/ic_input_add.png b/core/res/res/drawable/ic_input_add.png
new file mode 100644
index 0000000..00770f8
--- /dev/null
+++ b/core/res/res/drawable/ic_input_add.png
Binary files differ
diff --git a/core/res/res/drawable/ic_input_delete.png b/core/res/res/drawable/ic_input_delete.png
new file mode 100644
index 0000000..ee4c911
--- /dev/null
+++ b/core/res/res/drawable/ic_input_delete.png
Binary files differ
diff --git a/core/res/res/drawable/ic_input_get.png b/core/res/res/drawable/ic_input_get.png
new file mode 100644
index 0000000..2f2cfcf
--- /dev/null
+++ b/core/res/res/drawable/ic_input_get.png
Binary files differ
diff --git a/core/res/res/drawable/ic_launcher_android.png b/core/res/res/drawable/ic_launcher_android.png
new file mode 100644
index 0000000..855484a
--- /dev/null
+++ b/core/res/res/drawable/ic_launcher_android.png
Binary files differ
diff --git a/core/res/res/drawable/ic_lock_airplane_mode.png b/core/res/res/drawable/ic_lock_airplane_mode.png
new file mode 100755
index 0000000..caafcb2
--- /dev/null
+++ b/core/res/res/drawable/ic_lock_airplane_mode.png
Binary files differ
diff --git a/core/res/res/drawable/ic_lock_airplane_mode_off.png b/core/res/res/drawable/ic_lock_airplane_mode_off.png
new file mode 100755
index 0000000..cb2cbdf
--- /dev/null
+++ b/core/res/res/drawable/ic_lock_airplane_mode_off.png
Binary files differ
diff --git a/core/res/res/drawable/ic_lock_idle_alarm.png b/core/res/res/drawable/ic_lock_idle_alarm.png
new file mode 100644
index 0000000..8c8899f
--- /dev/null
+++ b/core/res/res/drawable/ic_lock_idle_alarm.png
Binary files differ
diff --git a/core/res/res/drawable/ic_lock_idle_charging.png b/core/res/res/drawable/ic_lock_idle_charging.png
new file mode 100755
index 0000000..20d6320
--- /dev/null
+++ b/core/res/res/drawable/ic_lock_idle_charging.png
Binary files differ
diff --git a/core/res/res/drawable/ic_lock_idle_lock.png b/core/res/res/drawable/ic_lock_idle_lock.png
new file mode 100755
index 0000000..0206aee
--- /dev/null
+++ b/core/res/res/drawable/ic_lock_idle_lock.png
Binary files differ
diff --git a/core/res/res/drawable/ic_lock_idle_low_battery.png b/core/res/res/drawable/ic_lock_idle_low_battery.png
new file mode 100755
index 0000000..bb96782
--- /dev/null
+++ b/core/res/res/drawable/ic_lock_idle_low_battery.png
Binary files differ
diff --git a/core/res/res/drawable/ic_lock_lock.png b/core/res/res/drawable/ic_lock_lock.png
new file mode 100644
index 0000000..b662b03
--- /dev/null
+++ b/core/res/res/drawable/ic_lock_lock.png
Binary files differ
diff --git a/core/res/res/drawable/ic_lock_power_off.png b/core/res/res/drawable/ic_lock_power_off.png
new file mode 100644
index 0000000..4405b43
--- /dev/null
+++ b/core/res/res/drawable/ic_lock_power_off.png
Binary files differ
diff --git a/core/res/res/drawable/ic_lock_silent_mode.png b/core/res/res/drawable/ic_lock_silent_mode.png
new file mode 100644
index 0000000..439a6f5
--- /dev/null
+++ b/core/res/res/drawable/ic_lock_silent_mode.png
Binary files differ
diff --git a/core/res/res/drawable/ic_lock_silent_mode_off.png b/core/res/res/drawable/ic_lock_silent_mode_off.png
new file mode 100644
index 0000000..fc7e960
--- /dev/null
+++ b/core/res/res/drawable/ic_lock_silent_mode_off.png
Binary files differ
diff --git a/core/res/res/drawable/ic_maps_indicator_current_position.png b/core/res/res/drawable/ic_maps_indicator_current_position.png
new file mode 100644
index 0000000..4e427d8
--- /dev/null
+++ b/core/res/res/drawable/ic_maps_indicator_current_position.png
Binary files differ
diff --git a/core/res/res/drawable/ic_maps_indicator_current_position_anim.xml b/core/res/res/drawable/ic_maps_indicator_current_position_anim.xml
new file mode 100644
index 0000000..dcc6d46
--- /dev/null
+++ b/core/res/res/drawable/ic_maps_indicator_current_position_anim.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* //device/apps/common/res/drawable/status_icon_background.xml
+**
+** Copyright 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.
+*/
+-->
+<!-- Levels should be evenly spaced between 0 - 10000 -->
+
+<level-list xmlns:android="http://schemas.android.com/apk/res/android" >
+    <item android:maxLevel="2500" android:drawable="@drawable/ic_maps_indicator_current_position" />
+    <item android:maxLevel="5000" android:drawable="@drawable/ic_maps_indicator_current_position_anim1" />
+    <item android:maxLevel="7500" android:drawable="@drawable/ic_maps_indicator_current_position_anim2" />
+    <item android:maxLevel="10000" android:drawable="@drawable/ic_maps_indicator_current_position_anim3" />
+</level-list>
diff --git a/core/res/res/drawable/ic_maps_indicator_current_position_anim1.png b/core/res/res/drawable/ic_maps_indicator_current_position_anim1.png
new file mode 100644
index 0000000..47bb9fa
--- /dev/null
+++ b/core/res/res/drawable/ic_maps_indicator_current_position_anim1.png
Binary files differ
diff --git a/core/res/res/drawable/ic_maps_indicator_current_position_anim2.png b/core/res/res/drawable/ic_maps_indicator_current_position_anim2.png
new file mode 100644
index 0000000..b1167bc
--- /dev/null
+++ b/core/res/res/drawable/ic_maps_indicator_current_position_anim2.png
Binary files differ
diff --git a/core/res/res/drawable/ic_maps_indicator_current_position_anim3.png b/core/res/res/drawable/ic_maps_indicator_current_position_anim3.png
new file mode 100644
index 0000000..f681a4c
--- /dev/null
+++ b/core/res/res/drawable/ic_maps_indicator_current_position_anim3.png
Binary files differ
diff --git a/core/res/res/drawable/ic_media_ff.png b/core/res/res/drawable/ic_media_ff.png
new file mode 100755
index 0000000..ce7e195
--- /dev/null
+++ b/core/res/res/drawable/ic_media_ff.png
Binary files differ
diff --git a/core/res/res/drawable/ic_media_next.png b/core/res/res/drawable/ic_media_next.png
new file mode 100755
index 0000000..84f38e8
--- /dev/null
+++ b/core/res/res/drawable/ic_media_next.png
Binary files differ
diff --git a/core/res/res/drawable/ic_media_pause.png b/core/res/res/drawable/ic_media_pause.png
new file mode 100755
index 0000000..688118e
--- /dev/null
+++ b/core/res/res/drawable/ic_media_pause.png
Binary files differ
diff --git a/core/res/res/drawable/ic_media_play.png b/core/res/res/drawable/ic_media_play.png
new file mode 100755
index 0000000..7aa7af8
--- /dev/null
+++ b/core/res/res/drawable/ic_media_play.png
Binary files differ
diff --git a/core/res/res/drawable/ic_media_previous.png b/core/res/res/drawable/ic_media_previous.png
new file mode 100755
index 0000000..1bba544
--- /dev/null
+++ b/core/res/res/drawable/ic_media_previous.png
Binary files differ
diff --git a/core/res/res/drawable/ic_media_rew.png b/core/res/res/drawable/ic_media_rew.png
new file mode 100755
index 0000000..132df7f
--- /dev/null
+++ b/core/res/res/drawable/ic_media_rew.png
Binary files differ
diff --git a/core/res/res/drawable/ic_menu_account_list.png b/core/res/res/drawable/ic_menu_account_list.png
new file mode 100644
index 0000000..f0945b2
--- /dev/null
+++ b/core/res/res/drawable/ic_menu_account_list.png
Binary files differ
diff --git a/core/res/res/drawable/ic_menu_add.png b/core/res/res/drawable/ic_menu_add.png
new file mode 100755
index 0000000..6752bfd
--- /dev/null
+++ b/core/res/res/drawable/ic_menu_add.png
Binary files differ
diff --git a/core/res/res/drawable/ic_menu_agenda.png b/core/res/res/drawable/ic_menu_agenda.png
new file mode 100755
index 0000000..9f2c1dc
--- /dev/null
+++ b/core/res/res/drawable/ic_menu_agenda.png
Binary files differ
diff --git a/core/res/res/drawable/ic_menu_allfriends.png b/core/res/res/drawable/ic_menu_allfriends.png
new file mode 100755
index 0000000..a5bd331
--- /dev/null
+++ b/core/res/res/drawable/ic_menu_allfriends.png
Binary files differ
diff --git a/core/res/res/drawable/ic_menu_always_landscape_portrait.png b/core/res/res/drawable/ic_menu_always_landscape_portrait.png
new file mode 100644
index 0000000..68911c4
--- /dev/null
+++ b/core/res/res/drawable/ic_menu_always_landscape_portrait.png
Binary files differ
diff --git a/core/res/res/drawable/ic_menu_archive.png b/core/res/res/drawable/ic_menu_archive.png
new file mode 100644
index 0000000..a4599e3
--- /dev/null
+++ b/core/res/res/drawable/ic_menu_archive.png
Binary files differ
diff --git a/core/res/res/drawable/ic_menu_attachment.png b/core/res/res/drawable/ic_menu_attachment.png
new file mode 100644
index 0000000..89d626f
--- /dev/null
+++ b/core/res/res/drawable/ic_menu_attachment.png
Binary files differ
diff --git a/core/res/res/drawable/ic_menu_back.png b/core/res/res/drawable/ic_menu_back.png
new file mode 100644
index 0000000..5ce50eb
--- /dev/null
+++ b/core/res/res/drawable/ic_menu_back.png
Binary files differ
diff --git a/core/res/res/drawable/ic_menu_block.png b/core/res/res/drawable/ic_menu_block.png
new file mode 100644
index 0000000..422eeb1
--- /dev/null
+++ b/core/res/res/drawable/ic_menu_block.png
Binary files differ
diff --git a/core/res/res/drawable/ic_menu_blocked_user.png b/core/res/res/drawable/ic_menu_blocked_user.png
new file mode 100644
index 0000000..5a5619b
--- /dev/null
+++ b/core/res/res/drawable/ic_menu_blocked_user.png
Binary files differ
diff --git a/core/res/res/drawable/ic_menu_call.png b/core/res/res/drawable/ic_menu_call.png
new file mode 100644
index 0000000..a63f86b
--- /dev/null
+++ b/core/res/res/drawable/ic_menu_call.png
Binary files differ
diff --git a/core/res/res/drawable/ic_menu_camera.png b/core/res/res/drawable/ic_menu_camera.png
new file mode 100755
index 0000000..cdf7ca3
--- /dev/null
+++ b/core/res/res/drawable/ic_menu_camera.png
Binary files differ
diff --git a/core/res/res/drawable/ic_menu_cc.png b/core/res/res/drawable/ic_menu_cc.png
new file mode 100644
index 0000000..4876021
--- /dev/null
+++ b/core/res/res/drawable/ic_menu_cc.png
Binary files differ
diff --git a/core/res/res/drawable/ic_menu_chat_dashboard.png b/core/res/res/drawable/ic_menu_chat_dashboard.png
new file mode 100644
index 0000000..37fd3cbd
--- /dev/null
+++ b/core/res/res/drawable/ic_menu_chat_dashboard.png
Binary files differ
diff --git a/core/res/res/drawable/ic_menu_clear_playlist.png b/core/res/res/drawable/ic_menu_clear_playlist.png
new file mode 100644
index 0000000..750db62
--- /dev/null
+++ b/core/res/res/drawable/ic_menu_clear_playlist.png
Binary files differ
diff --git a/core/res/res/drawable/ic_menu_close_clear_cancel.png b/core/res/res/drawable/ic_menu_close_clear_cancel.png
new file mode 100644
index 0000000..78222ea
--- /dev/null
+++ b/core/res/res/drawable/ic_menu_close_clear_cancel.png
Binary files differ
diff --git a/core/res/res/drawable/ic_menu_compass.png b/core/res/res/drawable/ic_menu_compass.png
new file mode 100644
index 0000000..7717dde
--- /dev/null
+++ b/core/res/res/drawable/ic_menu_compass.png
Binary files differ
diff --git a/core/res/res/drawable/ic_menu_compose.png b/core/res/res/drawable/ic_menu_compose.png
new file mode 100644
index 0000000..1b4733e
--- /dev/null
+++ b/core/res/res/drawable/ic_menu_compose.png
Binary files differ
diff --git a/core/res/res/drawable/ic_menu_crop.png b/core/res/res/drawable/ic_menu_crop.png
new file mode 100755
index 0000000..c0df996
--- /dev/null
+++ b/core/res/res/drawable/ic_menu_crop.png
Binary files differ
diff --git a/core/res/res/drawable/ic_menu_day.png b/core/res/res/drawable/ic_menu_day.png
new file mode 100755
index 0000000..db5d3a4
--- /dev/null
+++ b/core/res/res/drawable/ic_menu_day.png
Binary files differ
diff --git a/core/res/res/drawable/ic_menu_delete.png b/core/res/res/drawable/ic_menu_delete.png
new file mode 100755
index 0000000..7d95494
--- /dev/null
+++ b/core/res/res/drawable/ic_menu_delete.png
Binary files differ
diff --git a/core/res/res/drawable/ic_menu_directions.png b/core/res/res/drawable/ic_menu_directions.png
new file mode 100755
index 0000000..00a288f
--- /dev/null
+++ b/core/res/res/drawable/ic_menu_directions.png
Binary files differ
diff --git a/core/res/res/drawable/ic_menu_edit.png b/core/res/res/drawable/ic_menu_edit.png
new file mode 100755
index 0000000..41a9c2e
--- /dev/null
+++ b/core/res/res/drawable/ic_menu_edit.png
Binary files differ
diff --git a/core/res/res/drawable/ic_menu_emoticons.png b/core/res/res/drawable/ic_menu_emoticons.png
new file mode 100644
index 0000000..e8c4e47
--- /dev/null
+++ b/core/res/res/drawable/ic_menu_emoticons.png
Binary files differ
diff --git a/core/res/res/drawable/ic_menu_end_conversation.png b/core/res/res/drawable/ic_menu_end_conversation.png
new file mode 100644
index 0000000..0ea0fcb
--- /dev/null
+++ b/core/res/res/drawable/ic_menu_end_conversation.png
Binary files differ
diff --git a/core/res/res/drawable/ic_menu_forward.png b/core/res/res/drawable/ic_menu_forward.png
new file mode 100644
index 0000000..0936fac
--- /dev/null
+++ b/core/res/res/drawable/ic_menu_forward.png
Binary files differ
diff --git a/core/res/res/drawable/ic_menu_friendslist.png b/core/res/res/drawable/ic_menu_friendslist.png
new file mode 100644
index 0000000..8ec6b1a
--- /dev/null
+++ b/core/res/res/drawable/ic_menu_friendslist.png
Binary files differ
diff --git a/core/res/res/drawable/ic_menu_gallery.png b/core/res/res/drawable/ic_menu_gallery.png
new file mode 100755
index 0000000..f61bbd8
--- /dev/null
+++ b/core/res/res/drawable/ic_menu_gallery.png
Binary files differ
diff --git a/core/res/res/drawable/ic_menu_goto.png b/core/res/res/drawable/ic_menu_goto.png
new file mode 100644
index 0000000..40183eb
--- /dev/null
+++ b/core/res/res/drawable/ic_menu_goto.png
Binary files differ
diff --git a/core/res/res/drawable/ic_menu_help.png b/core/res/res/drawable/ic_menu_help.png
new file mode 100644
index 0000000..7c55dfd
--- /dev/null
+++ b/core/res/res/drawable/ic_menu_help.png
Binary files differ
diff --git a/core/res/res/drawable/ic_menu_home.png b/core/res/res/drawable/ic_menu_home.png
new file mode 100644
index 0000000..34943f6
--- /dev/null
+++ b/core/res/res/drawable/ic_menu_home.png
Binary files differ
diff --git a/core/res/res/drawable/ic_menu_info_details.png b/core/res/res/drawable/ic_menu_info_details.png
new file mode 100755
index 0000000..1786d1e
--- /dev/null
+++ b/core/res/res/drawable/ic_menu_info_details.png
Binary files differ
diff --git a/core/res/res/drawable/ic_menu_invite.png b/core/res/res/drawable/ic_menu_invite.png
new file mode 100644
index 0000000..7577e6d
--- /dev/null
+++ b/core/res/res/drawable/ic_menu_invite.png
Binary files differ
diff --git a/core/res/res/drawable/ic_menu_login.png b/core/res/res/drawable/ic_menu_login.png
new file mode 100644
index 0000000..2b856bc
--- /dev/null
+++ b/core/res/res/drawable/ic_menu_login.png
Binary files differ
diff --git a/core/res/res/drawable/ic_menu_manage.png b/core/res/res/drawable/ic_menu_manage.png
new file mode 100755
index 0000000..f155bbc
--- /dev/null
+++ b/core/res/res/drawable/ic_menu_manage.png
Binary files differ
diff --git a/core/res/res/drawable/ic_menu_mapmode.png b/core/res/res/drawable/ic_menu_mapmode.png
new file mode 100644
index 0000000..d85cab5
--- /dev/null
+++ b/core/res/res/drawable/ic_menu_mapmode.png
Binary files differ
diff --git a/core/res/res/drawable/ic_menu_mark.png b/core/res/res/drawable/ic_menu_mark.png
new file mode 100644
index 0000000..5e95da7
--- /dev/null
+++ b/core/res/res/drawable/ic_menu_mark.png
Binary files differ
diff --git a/core/res/res/drawable/ic_menu_month.png b/core/res/res/drawable/ic_menu_month.png
new file mode 100755
index 0000000..bf6cb89
--- /dev/null
+++ b/core/res/res/drawable/ic_menu_month.png
Binary files differ
diff --git a/core/res/res/drawable/ic_menu_more.png b/core/res/res/drawable/ic_menu_more.png
new file mode 100644
index 0000000..b9fc5fa
--- /dev/null
+++ b/core/res/res/drawable/ic_menu_more.png
Binary files differ
diff --git a/core/res/res/drawable/ic_menu_my_calendar.png b/core/res/res/drawable/ic_menu_my_calendar.png
new file mode 100755
index 0000000..0c88fd3
--- /dev/null
+++ b/core/res/res/drawable/ic_menu_my_calendar.png
Binary files differ
diff --git a/core/res/res/drawable/ic_menu_mylocation.png b/core/res/res/drawable/ic_menu_mylocation.png
new file mode 100755
index 0000000..fdbd5ca
--- /dev/null
+++ b/core/res/res/drawable/ic_menu_mylocation.png
Binary files differ
diff --git a/core/res/res/drawable/ic_menu_myplaces.png b/core/res/res/drawable/ic_menu_myplaces.png
new file mode 100644
index 0000000..06f11ba
--- /dev/null
+++ b/core/res/res/drawable/ic_menu_myplaces.png
Binary files differ
diff --git a/core/res/res/drawable/ic_menu_notifications.png b/core/res/res/drawable/ic_menu_notifications.png
new file mode 100644
index 0000000..866d4e0
--- /dev/null
+++ b/core/res/res/drawable/ic_menu_notifications.png
Binary files differ
diff --git a/core/res/res/drawable/ic_menu_play_clip.png b/core/res/res/drawable/ic_menu_play_clip.png
new file mode 100644
index 0000000..4669947
--- /dev/null
+++ b/core/res/res/drawable/ic_menu_play_clip.png
Binary files differ
diff --git a/core/res/res/drawable/ic_menu_preferences.png b/core/res/res/drawable/ic_menu_preferences.png
new file mode 100644
index 0000000..b8e7141
--- /dev/null
+++ b/core/res/res/drawable/ic_menu_preferences.png
Binary files differ
diff --git a/core/res/res/drawable/ic_menu_recent_history.png b/core/res/res/drawable/ic_menu_recent_history.png
new file mode 100644
index 0000000..4ccae5d
--- /dev/null
+++ b/core/res/res/drawable/ic_menu_recent_history.png
Binary files differ
diff --git a/core/res/res/drawable/ic_menu_refresh.png b/core/res/res/drawable/ic_menu_refresh.png
new file mode 100644
index 0000000..77d70dd
--- /dev/null
+++ b/core/res/res/drawable/ic_menu_refresh.png
Binary files differ
diff --git a/core/res/res/drawable/ic_menu_report_image.png b/core/res/res/drawable/ic_menu_report_image.png
new file mode 100644
index 0000000..393d727
--- /dev/null
+++ b/core/res/res/drawable/ic_menu_report_image.png
Binary files differ
diff --git a/core/res/res/drawable/ic_menu_revert.png b/core/res/res/drawable/ic_menu_revert.png
new file mode 100644
index 0000000..e7e04f5
--- /dev/null
+++ b/core/res/res/drawable/ic_menu_revert.png
Binary files differ
diff --git a/core/res/res/drawable/ic_menu_rotate.png b/core/res/res/drawable/ic_menu_rotate.png
new file mode 100755
index 0000000..27368b2
--- /dev/null
+++ b/core/res/res/drawable/ic_menu_rotate.png
Binary files differ
diff --git a/core/res/res/drawable/ic_menu_save.png b/core/res/res/drawable/ic_menu_save.png
new file mode 100644
index 0000000..36d50b3
--- /dev/null
+++ b/core/res/res/drawable/ic_menu_save.png
Binary files differ
diff --git a/core/res/res/drawable/ic_menu_search.png b/core/res/res/drawable/ic_menu_search.png
new file mode 100755
index 0000000..94446db
--- /dev/null
+++ b/core/res/res/drawable/ic_menu_search.png
Binary files differ
diff --git a/core/res/res/drawable/ic_menu_send.png b/core/res/res/drawable/ic_menu_send.png
new file mode 100755
index 0000000..74c096d
--- /dev/null
+++ b/core/res/res/drawable/ic_menu_send.png
Binary files differ
diff --git a/core/res/res/drawable/ic_menu_set_as.png b/core/res/res/drawable/ic_menu_set_as.png
new file mode 100755
index 0000000..cb9dc49
--- /dev/null
+++ b/core/res/res/drawable/ic_menu_set_as.png
Binary files differ
diff --git a/core/res/res/drawable/ic_menu_share.png b/core/res/res/drawable/ic_menu_share.png
new file mode 100755
index 0000000..44db9b1
--- /dev/null
+++ b/core/res/res/drawable/ic_menu_share.png
Binary files differ
diff --git a/core/res/res/drawable/ic_menu_slideshow.png b/core/res/res/drawable/ic_menu_slideshow.png
new file mode 100644
index 0000000..38dd8f0
--- /dev/null
+++ b/core/res/res/drawable/ic_menu_slideshow.png
Binary files differ
diff --git a/core/res/res/drawable/ic_menu_sort_alphabetically.png b/core/res/res/drawable/ic_menu_sort_alphabetically.png
new file mode 100755
index 0000000..2583eb8
--- /dev/null
+++ b/core/res/res/drawable/ic_menu_sort_alphabetically.png
Binary files differ
diff --git a/core/res/res/drawable/ic_menu_sort_by_size.png b/core/res/res/drawable/ic_menu_sort_by_size.png
new file mode 100755
index 0000000..65e2786
--- /dev/null
+++ b/core/res/res/drawable/ic_menu_sort_by_size.png
Binary files differ
diff --git a/core/res/res/drawable/ic_menu_star.png b/core/res/res/drawable/ic_menu_star.png
new file mode 100755
index 0000000..527d74a
--- /dev/null
+++ b/core/res/res/drawable/ic_menu_star.png
Binary files differ
diff --git a/core/res/res/drawable/ic_menu_start_conversation.png b/core/res/res/drawable/ic_menu_start_conversation.png
new file mode 100644
index 0000000..aadcc2f
--- /dev/null
+++ b/core/res/res/drawable/ic_menu_start_conversation.png
Binary files differ
diff --git a/core/res/res/drawable/ic_menu_stop.png b/core/res/res/drawable/ic_menu_stop.png
new file mode 100644
index 0000000..4fc825c
--- /dev/null
+++ b/core/res/res/drawable/ic_menu_stop.png
Binary files differ
diff --git a/core/res/res/drawable/ic_menu_today.png b/core/res/res/drawable/ic_menu_today.png
new file mode 100755
index 0000000..c63b6af
--- /dev/null
+++ b/core/res/res/drawable/ic_menu_today.png
Binary files differ
diff --git a/core/res/res/drawable/ic_menu_upload.png b/core/res/res/drawable/ic_menu_upload.png
new file mode 100755
index 0000000..1c0dd3f
--- /dev/null
+++ b/core/res/res/drawable/ic_menu_upload.png
Binary files differ
diff --git a/core/res/res/drawable/ic_menu_upload_you_tube.png b/core/res/res/drawable/ic_menu_upload_you_tube.png
new file mode 100755
index 0000000..0095564
--- /dev/null
+++ b/core/res/res/drawable/ic_menu_upload_you_tube.png
Binary files differ
diff --git a/core/res/res/drawable/ic_menu_view.png b/core/res/res/drawable/ic_menu_view.png
new file mode 100755
index 0000000..69828a9
--- /dev/null
+++ b/core/res/res/drawable/ic_menu_view.png
Binary files differ
diff --git a/core/res/res/drawable/ic_menu_week.png b/core/res/res/drawable/ic_menu_week.png
new file mode 100755
index 0000000..62cd65e
--- /dev/null
+++ b/core/res/res/drawable/ic_menu_week.png
Binary files differ
diff --git a/core/res/res/drawable/ic_menu_zoom.png b/core/res/res/drawable/ic_menu_zoom.png
new file mode 100644
index 0000000..0b8c4e8
--- /dev/null
+++ b/core/res/res/drawable/ic_menu_zoom.png
Binary files differ
diff --git a/core/res/res/drawable/ic_notification_clear_all.png b/core/res/res/drawable/ic_notification_clear_all.png
new file mode 100644
index 0000000..f2114d7
--- /dev/null
+++ b/core/res/res/drawable/ic_notification_clear_all.png
Binary files differ
diff --git a/core/res/res/drawable/ic_notification_overlay.9.png b/core/res/res/drawable/ic_notification_overlay.9.png
new file mode 100644
index 0000000..1a3063c
--- /dev/null
+++ b/core/res/res/drawable/ic_notification_overlay.9.png
Binary files differ
diff --git a/core/res/res/drawable/ic_partial_secure.png b/core/res/res/drawable/ic_partial_secure.png
new file mode 100644
index 0000000..76ba96a
--- /dev/null
+++ b/core/res/res/drawable/ic_partial_secure.png
Binary files differ
diff --git a/core/res/res/drawable/ic_popup_disk_full.png b/core/res/res/drawable/ic_popup_disk_full.png
new file mode 100644
index 0000000..e6da5d0
--- /dev/null
+++ b/core/res/res/drawable/ic_popup_disk_full.png
Binary files differ
diff --git a/core/res/res/drawable/ic_popup_reminder.png b/core/res/res/drawable/ic_popup_reminder.png
new file mode 100755
index 0000000..af15279
--- /dev/null
+++ b/core/res/res/drawable/ic_popup_reminder.png
Binary files differ
diff --git a/core/res/res/drawable/ic_popup_sync.xml b/core/res/res/drawable/ic_popup_sync.xml
new file mode 100644
index 0000000..aa2c8d4
--- /dev/null
+++ b/core/res/res/drawable/ic_popup_sync.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* //device/apps/common/res/drawable/ic_popup_sync.xml
+**
+** Copyright 2007, 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.
+*/
+-->
+<animation-list
+        xmlns:android="http://schemas.android.com/apk/res/android"
+        android:oneshot="false">
+    <item android:drawable="@drawable/ic_popup_sync_1" android:duration="200" />
+    <item android:drawable="@drawable/ic_popup_sync_2" android:duration="200" />
+    <item android:drawable="@drawable/ic_popup_sync_3" android:duration="200" />
+    <item android:drawable="@drawable/ic_popup_sync_4" android:duration="200" />
+    <item android:drawable="@drawable/ic_popup_sync_5" android:duration="200" />
+    <item android:drawable="@drawable/ic_popup_sync_6" android:duration="200" />
+</animation-list>
+
diff --git a/core/res/res/drawable/ic_popup_sync_1.png b/core/res/res/drawable/ic_popup_sync_1.png
new file mode 100644
index 0000000..13d8cdd
--- /dev/null
+++ b/core/res/res/drawable/ic_popup_sync_1.png
Binary files differ
diff --git a/core/res/res/drawable/ic_popup_sync_2.png b/core/res/res/drawable/ic_popup_sync_2.png
new file mode 100644
index 0000000..6ca162a
--- /dev/null
+++ b/core/res/res/drawable/ic_popup_sync_2.png
Binary files differ
diff --git a/core/res/res/drawable/ic_popup_sync_3.png b/core/res/res/drawable/ic_popup_sync_3.png
new file mode 100644
index 0000000..a7c21dd
--- /dev/null
+++ b/core/res/res/drawable/ic_popup_sync_3.png
Binary files differ
diff --git a/core/res/res/drawable/ic_popup_sync_4.png b/core/res/res/drawable/ic_popup_sync_4.png
new file mode 100644
index 0000000..e9be04e
--- /dev/null
+++ b/core/res/res/drawable/ic_popup_sync_4.png
Binary files differ
diff --git a/core/res/res/drawable/ic_popup_sync_5.png b/core/res/res/drawable/ic_popup_sync_5.png
new file mode 100644
index 0000000..65d87c4
--- /dev/null
+++ b/core/res/res/drawable/ic_popup_sync_5.png
Binary files differ
diff --git a/core/res/res/drawable/ic_popup_sync_6.png b/core/res/res/drawable/ic_popup_sync_6.png
new file mode 100644
index 0000000..2015c88
--- /dev/null
+++ b/core/res/res/drawable/ic_popup_sync_6.png
Binary files differ
diff --git a/core/res/res/drawable/ic_search_category_default.png b/core/res/res/drawable/ic_search_category_default.png
new file mode 100755
index 0000000..7eea584
--- /dev/null
+++ b/core/res/res/drawable/ic_search_category_default.png
Binary files differ
diff --git a/core/res/res/drawable/ic_secure.png b/core/res/res/drawable/ic_secure.png
new file mode 100644
index 0000000..4f15fc4
--- /dev/null
+++ b/core/res/res/drawable/ic_secure.png
Binary files differ
diff --git a/core/res/res/drawable/ic_settings_indicator_next_page.png b/core/res/res/drawable/ic_settings_indicator_next_page.png
new file mode 100755
index 0000000..c30e6e4
--- /dev/null
+++ b/core/res/res/drawable/ic_settings_indicator_next_page.png
Binary files differ
diff --git a/core/res/res/drawable/ic_text_dot.png b/core/res/res/drawable/ic_text_dot.png
new file mode 100755
index 0000000..bb02379
--- /dev/null
+++ b/core/res/res/drawable/ic_text_dot.png
Binary files differ
diff --git a/core/res/res/drawable/ic_vibrate.png b/core/res/res/drawable/ic_vibrate.png
new file mode 100755
index 0000000..eb24e50
--- /dev/null
+++ b/core/res/res/drawable/ic_vibrate.png
Binary files differ
diff --git a/core/res/res/drawable/ic_volume.png b/core/res/res/drawable/ic_volume.png
new file mode 100755
index 0000000..cee70f0
--- /dev/null
+++ b/core/res/res/drawable/ic_volume.png
Binary files differ
diff --git a/core/res/res/drawable/ic_volume_bluetooth_ad2p.png b/core/res/res/drawable/ic_volume_bluetooth_ad2p.png
new file mode 100644
index 0000000..cf86ab3
--- /dev/null
+++ b/core/res/res/drawable/ic_volume_bluetooth_ad2p.png
Binary files differ
diff --git a/core/res/res/drawable/ic_volume_bluetooth_in_call.png b/core/res/res/drawable/ic_volume_bluetooth_in_call.png
new file mode 100644
index 0000000..94801fc
--- /dev/null
+++ b/core/res/res/drawable/ic_volume_bluetooth_in_call.png
Binary files differ
diff --git a/core/res/res/drawable/ic_volume_off.png b/core/res/res/drawable/ic_volume_off.png
new file mode 100644
index 0000000..f3850fc
--- /dev/null
+++ b/core/res/res/drawable/ic_volume_off.png
Binary files differ
diff --git a/core/res/res/drawable/ic_volume_off_small.png b/core/res/res/drawable/ic_volume_off_small.png
new file mode 100755
index 0000000..ae55bd6
--- /dev/null
+++ b/core/res/res/drawable/ic_volume_off_small.png
Binary files differ
diff --git a/core/res/res/drawable/ic_volume_small.png b/core/res/res/drawable/ic_volume_small.png
new file mode 100755
index 0000000..00a4f89
--- /dev/null
+++ b/core/res/res/drawable/ic_volume_small.png
Binary files differ
diff --git a/core/res/res/drawable/icon_highlight_rectangle.9.png b/core/res/res/drawable/icon_highlight_rectangle.9.png
new file mode 100644
index 0000000..3dafde3
--- /dev/null
+++ b/core/res/res/drawable/icon_highlight_rectangle.9.png
Binary files differ
diff --git a/core/res/res/drawable/icon_highlight_square.9.png b/core/res/res/drawable/icon_highlight_square.9.png
new file mode 100644
index 0000000..a93a3f8
--- /dev/null
+++ b/core/res/res/drawable/icon_highlight_square.9.png
Binary files differ
diff --git a/core/res/res/drawable/ime_qwerty.png b/core/res/res/drawable/ime_qwerty.png
new file mode 100644
index 0000000..e6e5cda
--- /dev/null
+++ b/core/res/res/drawable/ime_qwerty.png
Binary files differ
diff --git a/core/res/res/drawable/indicator_check_mark_dark.xml b/core/res/res/drawable/indicator_check_mark_dark.xml
new file mode 100644
index 0000000..f363a2d
--- /dev/null
+++ b/core/res/res/drawable/indicator_check_mark_dark.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <item android:state_checked="true"
+        android:drawable="@drawable/btn_check_buttonless_on" />
+
+    <item android:state_checked="false"
+        android:drawable="@drawable/btn_check_buttonless_off" />
+        
+    <item
+        android:drawable="@drawable/btn_check_buttonless_off" />
+
+</selector>
diff --git a/core/res/res/drawable/indicator_check_mark_light.xml b/core/res/res/drawable/indicator_check_mark_light.xml
new file mode 100644
index 0000000..3c8bb6c
--- /dev/null
+++ b/core/res/res/drawable/indicator_check_mark_light.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <item android:state_checked="true"
+        android:drawable="@drawable/btn_check_buttonless_on" />
+
+    <item android:state_checked="false"
+        android:drawable="@drawable/btn_check_buttonless_off" />
+
+    <item
+        android:drawable="@drawable/btn_check_buttonless_off" />
+
+</selector>
diff --git a/core/res/res/drawable/indicator_code_lock_drag_direction_green_up.png b/core/res/res/drawable/indicator_code_lock_drag_direction_green_up.png
new file mode 100644
index 0000000..ef91dc4
--- /dev/null
+++ b/core/res/res/drawable/indicator_code_lock_drag_direction_green_up.png
Binary files differ
diff --git a/core/res/res/drawable/indicator_code_lock_drag_direction_red_up.png b/core/res/res/drawable/indicator_code_lock_drag_direction_red_up.png
new file mode 100644
index 0000000..f3d4204b
--- /dev/null
+++ b/core/res/res/drawable/indicator_code_lock_drag_direction_red_up.png
Binary files differ
diff --git a/core/res/res/drawable/indicator_code_lock_point_area_default.png b/core/res/res/drawable/indicator_code_lock_point_area_default.png
new file mode 100755
index 0000000..4e88b37
--- /dev/null
+++ b/core/res/res/drawable/indicator_code_lock_point_area_default.png
Binary files differ
diff --git a/core/res/res/drawable/indicator_code_lock_point_area_green.png b/core/res/res/drawable/indicator_code_lock_point_area_green.png
new file mode 100755
index 0000000..8020846
--- /dev/null
+++ b/core/res/res/drawable/indicator_code_lock_point_area_green.png
Binary files differ
diff --git a/core/res/res/drawable/indicator_code_lock_point_area_red.png b/core/res/res/drawable/indicator_code_lock_point_area_red.png
new file mode 100755
index 0000000..b7aee1ba
--- /dev/null
+++ b/core/res/res/drawable/indicator_code_lock_point_area_red.png
Binary files differ
diff --git a/core/res/res/drawable/indicator_input_error.png b/core/res/res/drawable/indicator_input_error.png
new file mode 100755
index 0000000..ee60165
--- /dev/null
+++ b/core/res/res/drawable/indicator_input_error.png
Binary files differ
diff --git a/core/res/res/drawable/keyboard_accessory_bg_landscape.9.png b/core/res/res/drawable/keyboard_accessory_bg_landscape.9.png
new file mode 100644
index 0000000..8f828f6
--- /dev/null
+++ b/core/res/res/drawable/keyboard_accessory_bg_landscape.9.png
Binary files differ
diff --git a/core/res/res/drawable/keyboard_background.9.png b/core/res/res/drawable/keyboard_background.9.png
new file mode 100644
index 0000000..1d3ce05
--- /dev/null
+++ b/core/res/res/drawable/keyboard_background.9.png
Binary files differ
diff --git a/core/res/res/drawable/keyboard_key_feedback.xml b/core/res/res/drawable/keyboard_key_feedback.xml
new file mode 100644
index 0000000..e55854d
--- /dev/null
+++ b/core/res/res/drawable/keyboard_key_feedback.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_long_pressable="true"
+            android:drawable="@android:drawable/keyboard_key_feedback_more_background" />
+
+    <item android:drawable="@android:drawable/keyboard_key_feedback_background" />
+</selector>
diff --git a/core/res/res/drawable/keyboard_key_feedback_background.9.png b/core/res/res/drawable/keyboard_key_feedback_background.9.png
new file mode 100644
index 0000000..2a80f09
--- /dev/null
+++ b/core/res/res/drawable/keyboard_key_feedback_background.9.png
Binary files differ
diff --git a/core/res/res/drawable/keyboard_key_feedback_more_background.9.png b/core/res/res/drawable/keyboard_key_feedback_more_background.9.png
new file mode 100755
index 0000000..29aa285
--- /dev/null
+++ b/core/res/res/drawable/keyboard_key_feedback_more_background.9.png
Binary files differ
diff --git a/core/res/res/drawable/keyboard_popup_panel_background.9.png b/core/res/res/drawable/keyboard_popup_panel_background.9.png
new file mode 100644
index 0000000..36d75df
--- /dev/null
+++ b/core/res/res/drawable/keyboard_popup_panel_background.9.png
Binary files differ
diff --git a/core/res/res/drawable/keyboard_suggest_strip_shadow.9.png b/core/res/res/drawable/keyboard_suggest_strip_shadow.9.png
new file mode 100644
index 0000000..d231ae6
--- /dev/null
+++ b/core/res/res/drawable/keyboard_suggest_strip_shadow.9.png
Binary files differ
diff --git a/core/res/res/drawable/keyboard_textfield_pressed.9.png b/core/res/res/drawable/keyboard_textfield_pressed.9.png
new file mode 100644
index 0000000..f4e3f10
--- /dev/null
+++ b/core/res/res/drawable/keyboard_textfield_pressed.9.png
Binary files differ
diff --git a/core/res/res/drawable/keyboard_textfield_selected.9.png b/core/res/res/drawable/keyboard_textfield_selected.9.png
new file mode 100644
index 0000000..6e703af
--- /dev/null
+++ b/core/res/res/drawable/keyboard_textfield_selected.9.png
Binary files differ
diff --git a/core/res/res/drawable/list_highlight.xml b/core/res/res/drawable/list_highlight.xml
new file mode 100644
index 0000000..132c1ca
--- /dev/null
+++ b/core/res/res/drawable/list_highlight.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* //device/apps/common/res/drawable/list_highlight.xml
+**
+** Copyright 2007, 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.
+*/
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_window_focused="true" android:drawable="@drawable/list_highlight_active" />
+    <item android:state_window_focused="false" android:drawable="@drawable/list_highlight_inactive" />
+</selector>
diff --git a/core/res/res/drawable/list_highlight_active.xml b/core/res/res/drawable/list_highlight_active.xml
new file mode 100644
index 0000000..9e13a96
--- /dev/null
+++ b/core/res/res/drawable/list_highlight_active.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* //device/apps/common/res/drawable/list_highlight_active.xml
+**
+** Copyright 2007, 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.
+*/
+-->
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android">
+    <gradient android:startColor="#FFFFFFFF" android:endColor="#A8EFC123"
+            android:angle="270"/>
+    <stroke android:width="1dp" android:color="#FFE3AE00"/>
+    <corners android:radius="0dp"/>
+    <padding android:left="6dp" android:top="2dp"
+            android:right="6dp" android:bottom="2dp" />
+</shape>
diff --git a/core/res/res/drawable/list_highlight_inactive.xml b/core/res/res/drawable/list_highlight_inactive.xml
new file mode 100644
index 0000000..b258ea6
--- /dev/null
+++ b/core/res/res/drawable/list_highlight_inactive.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* //device/apps/common/res/drawable/list_highlight_inactive.xml
+**
+** Copyright 2007, 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.
+*/
+-->
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android">
+	<gradient android:startColor="#10f3d465" android:endColor="#00000000"
+            android:angle="270"/>
+    <stroke android:width="1dp" android:color="#FFE3AE00"/>
+    <corners android:radius="0dp"/>
+    <padding android:left="6dp" android:top="2dp"
+            android:right="6dp" android:bottom="2dp" />
+</shape>
diff --git a/core/res/res/drawable/list_selector_background.xml b/core/res/res/drawable/list_selector_background.xml
new file mode 100644
index 0000000..bca996c
--- /dev/null
+++ b/core/res/res/drawable/list_selector_background.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <item android:state_window_focused="false"
+        android:drawable="@color/transparent" />
+
+    <!-- Even though these two point to the same resource, have two states so the drawable will invalidate itself when coming out of pressed state. -->
+    <item android:state_focused="true" android:state_enabled="false"
+        android:state_pressed="true"
+        android:drawable="@drawable/list_selector_background_disabled" />
+    <item android:state_focused="true" android:state_enabled="false"
+        android:drawable="@drawable/list_selector_background_disabled" />
+
+    <item android:state_focused="true" android:state_pressed="true"
+        android:drawable="@drawable/list_selector_background_transition" />
+    <item android:state_focused="false" android:state_pressed="true"
+        android:drawable="@drawable/list_selector_background_transition" />
+
+    <item android:state_focused="true"
+        android:drawable="@drawable/list_selector_background_focus" />
+
+</selector>
diff --git a/core/res/res/drawable/list_selector_background_disabled.9.png b/core/res/res/drawable/list_selector_background_disabled.9.png
new file mode 100644
index 0000000..bf970b0
--- /dev/null
+++ b/core/res/res/drawable/list_selector_background_disabled.9.png
Binary files differ
diff --git a/core/res/res/drawable/list_selector_background_focus.9.png b/core/res/res/drawable/list_selector_background_focus.9.png
new file mode 100644
index 0000000..c3e24158
--- /dev/null
+++ b/core/res/res/drawable/list_selector_background_focus.9.png
Binary files differ
diff --git a/core/res/res/drawable/list_selector_background_longpress.9.png b/core/res/res/drawable/list_selector_background_longpress.9.png
new file mode 100644
index 0000000..5cbb251
--- /dev/null
+++ b/core/res/res/drawable/list_selector_background_longpress.9.png
Binary files differ
diff --git a/core/res/res/drawable/list_selector_background_pressed.9.png b/core/res/res/drawable/list_selector_background_pressed.9.png
new file mode 100644
index 0000000..02b4e9a
--- /dev/null
+++ b/core/res/res/drawable/list_selector_background_pressed.9.png
Binary files differ
diff --git a/core/res/res/drawable/list_selector_background_transition.xml b/core/res/res/drawable/list_selector_background_transition.xml
new file mode 100644
index 0000000..695f0c7
--- /dev/null
+++ b/core/res/res/drawable/list_selector_background_transition.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<transition xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:drawable="@android:drawable/list_selector_background_pressed"  />
+    <item android:drawable="@android:drawable/list_selector_background_longpress"  />
+</transition>
diff --git a/core/res/res/drawable/load_average_background.xml b/core/res/res/drawable/load_average_background.xml
new file mode 100644
index 0000000..584e4f5
--- /dev/null
+++ b/core/res/res/drawable/load_average_background.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2007 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.
+-->
+
+<color xmlns:android="http://schemas.android.com/apk/res/android" value="#a0000000">
+    <padding left="1" top="1" right="1" bottom="1" />
+</color>
+
diff --git a/core/res/res/drawable/loading_tile.png b/core/res/res/drawable/loading_tile.png
new file mode 100644
index 0000000..f5a80c9
--- /dev/null
+++ b/core/res/res/drawable/loading_tile.png
Binary files differ
diff --git a/core/res/res/drawable/maps_google_logo.png b/core/res/res/drawable/maps_google_logo.png
new file mode 100644
index 0000000..1374aaa
--- /dev/null
+++ b/core/res/res/drawable/maps_google_logo.png
Binary files differ
diff --git a/core/res/res/drawable/media_button_background.xml b/core/res/res/drawable/media_button_background.xml
new file mode 100644
index 0000000..ebf8c495
--- /dev/null
+++ b/core/res/res/drawable/media_button_background.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* //device/apps/common/assets/res/any/drawable/button_background.xml
+**
+** Copyright 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.
+*/
+-->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_focused="true" android:state_pressed="false" android:state_enabled="true"
+          android:drawable="@drawable/btn_media_player_selected" />
+    <item android:state_enabled="true" android:state_pressed="true" android:drawable="@drawable/btn_media_player_pressed" />
+    <item android:state_enabled="true" android:state_focused="false" android:state_pressed="false"
+          android:drawable="@drawable/btn_media_player" />
+    <item android:state_focused="true" android:state_enabled="false" android:drawable="@drawable/btn_media_player_disabled_selected" />
+    <item android:state_focused="false" android:state_enabled="false" android:drawable="@drawable/btn_media_player_disabled" />
+    <item android:drawable="@drawable/btn_media_player" />
+</selector>
diff --git a/core/res/res/drawable/menu_background.9.png b/core/res/res/drawable/menu_background.9.png
new file mode 100644
index 0000000..ee99583
--- /dev/null
+++ b/core/res/res/drawable/menu_background.9.png
Binary files differ
diff --git a/core/res/res/drawable/menu_background_fill_parent_width.9.png b/core/res/res/drawable/menu_background_fill_parent_width.9.png
new file mode 100644
index 0000000..d368983
--- /dev/null
+++ b/core/res/res/drawable/menu_background_fill_parent_width.9.png
Binary files differ
diff --git a/core/res/res/drawable/menu_selector.xml b/core/res/res/drawable/menu_selector.xml
new file mode 100644
index 0000000..96f80aeb
--- /dev/null
+++ b/core/res/res/drawable/menu_selector.xml
@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <item
+        android:state_pressed="true"
+        android:state_enabled="true"
+        android:drawable="@drawable/highlight_pressed" />
+
+    <item
+        android:state_selected="true"
+        android:state_enabled="false"
+        android:drawable="@drawable/highlight_disabled" />
+
+    <item
+        android:state_selected="true"
+        android:state_enabled="true"
+        android:drawable="@drawable/highlight_selected" />
+
+    <item
+        android:state_focused="true"
+        android:state_enabled="false"
+        android:drawable="@drawable/highlight_disabled" />
+
+    <item
+        android:state_focused="true"
+        android:state_enabled="true"
+        android:drawable="@drawable/highlight_selected" />
+
+</selector>
diff --git a/core/res/res/drawable/menu_separator.9.png b/core/res/res/drawable/menu_separator.9.png
new file mode 100644
index 0000000..8a1a336
--- /dev/null
+++ b/core/res/res/drawable/menu_separator.9.png
Binary files differ
diff --git a/core/res/res/drawable/menu_submenu_background.9.png b/core/res/res/drawable/menu_submenu_background.9.png
new file mode 100644
index 0000000..a153532
--- /dev/null
+++ b/core/res/res/drawable/menu_submenu_background.9.png
Binary files differ
diff --git a/core/res/res/drawable/menuitem_background.xml b/core/res/res/drawable/menuitem_background.xml
new file mode 100644
index 0000000..6e07efc
--- /dev/null
+++ b/core/res/res/drawable/menuitem_background.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2007 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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_focused="true" android:state_pressed="true"
+    	android:drawable="@drawable/menuitem_background_pressed" />
+    <item android:state_focused="false" android:state_pressed="true"
+    	android:drawable="@drawable/menuitem_background_pressed" />
+    <item android:state_focused="true"
+        android:drawable="@drawable/menuitem_background_focus" />
+</selector>
diff --git a/core/res/res/drawable/menuitem_background_focus.9.png b/core/res/res/drawable/menuitem_background_focus.9.png
new file mode 100644
index 0000000..c3e24158
--- /dev/null
+++ b/core/res/res/drawable/menuitem_background_focus.9.png
Binary files differ
diff --git a/core/res/res/drawable/menuitem_background_pressed.9.png b/core/res/res/drawable/menuitem_background_pressed.9.png
new file mode 100644
index 0000000..02b4e9a
--- /dev/null
+++ b/core/res/res/drawable/menuitem_background_pressed.9.png
Binary files differ
diff --git a/core/res/res/drawable/menuitem_background_solid.xml b/core/res/res/drawable/menuitem_background_solid.xml
new file mode 100644
index 0000000..be46645
--- /dev/null
+++ b/core/res/res/drawable/menuitem_background_solid.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2007 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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_focused="true" android:state_pressed="false"
+    	android:drawable="@drawable/menuitem_background_solid_focused" />
+    <item android:state_focused="true" android:state_pressed="true"
+    	android:drawable="@drawable/menuitem_background_solid_pressed" />
+    <item android:state_focused="false" android:state_pressed="true"
+    	android:drawable="@drawable/menuitem_background_solid_pressed" />
+</selector>
diff --git a/core/res/res/drawable/menuitem_background_solid_focused.9.png b/core/res/res/drawable/menuitem_background_solid_focused.9.png
new file mode 100644
index 0000000..99dd9b1
--- /dev/null
+++ b/core/res/res/drawable/menuitem_background_solid_focused.9.png
Binary files differ
diff --git a/core/res/res/drawable/menuitem_background_solid_pressed.9.png b/core/res/res/drawable/menuitem_background_solid_pressed.9.png
new file mode 100644
index 0000000..389063a
--- /dev/null
+++ b/core/res/res/drawable/menuitem_background_solid_pressed.9.png
Binary files differ
diff --git a/core/res/res/drawable/menuitem_checkbox.xml b/core/res/res/drawable/menuitem_checkbox.xml
new file mode 100644
index 0000000..8f6ffc0
--- /dev/null
+++ b/core/res/res/drawable/menuitem_checkbox.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* //device/apps/common/res/drawable/checkbox.xml
+**
+** Copyright 2007, 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.
+*/
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android" android:constantSize="true">
+    <item android:state_checked="true"
+          android:drawable="@drawable/menuitem_checkbox_on" />
+</selector>
+
diff --git a/core/res/res/drawable/menuitem_checkbox_on.png b/core/res/res/drawable/menuitem_checkbox_on.png
new file mode 100644
index 0000000..bd8ff93
--- /dev/null
+++ b/core/res/res/drawable/menuitem_checkbox_on.png
Binary files differ
diff --git a/core/res/res/drawable/no_tile_128.png b/core/res/res/drawable/no_tile_128.png
new file mode 100644
index 0000000..a9b007d
--- /dev/null
+++ b/core/res/res/drawable/no_tile_128.png
Binary files differ
diff --git a/core/res/res/drawable/padlock.png b/core/res/res/drawable/padlock.png
new file mode 100644
index 0000000..558340b
--- /dev/null
+++ b/core/res/res/drawable/padlock.png
Binary files differ
diff --git a/core/res/res/drawable/panel_background.9.png b/core/res/res/drawable/panel_background.9.png
new file mode 100644
index 0000000..2305be4
--- /dev/null
+++ b/core/res/res/drawable/panel_background.9.png
Binary files differ
diff --git a/core/res/res/drawable/panel_picture_frame_background.xml b/core/res/res/drawable/panel_picture_frame_background.xml
new file mode 100644
index 0000000..f588106
--- /dev/null
+++ b/core/res/res/drawable/panel_picture_frame_background.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* //device/apps/common/assets/res/any/drawable/panel_picture_frame_background.xml
+**
+** Copyright 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.
+*/
+-->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_focused="true" android:drawable="@drawable/panel_picture_frame_bg_focus_blue" />
+    <item android:state_pressed="true" android:drawable="@drawable/panel_picture_frame_bg_pressed_blue" />
+    <item android:drawable="@drawable/panel_picture_frame_bg_normal" />
+</selector>
diff --git a/core/res/res/drawable/panel_picture_frame_bg_focus_blue.9.png b/core/res/res/drawable/panel_picture_frame_bg_focus_blue.9.png
new file mode 100644
index 0000000..7ebdbe5
--- /dev/null
+++ b/core/res/res/drawable/panel_picture_frame_bg_focus_blue.9.png
Binary files differ
diff --git a/core/res/res/drawable/panel_picture_frame_bg_normal.9.png b/core/res/res/drawable/panel_picture_frame_bg_normal.9.png
new file mode 100644
index 0000000..fd17d09
--- /dev/null
+++ b/core/res/res/drawable/panel_picture_frame_bg_normal.9.png
Binary files differ
diff --git a/core/res/res/drawable/panel_picture_frame_bg_pressed_blue.9.png b/core/res/res/drawable/panel_picture_frame_bg_pressed_blue.9.png
new file mode 100644
index 0000000..7bb0216
--- /dev/null
+++ b/core/res/res/drawable/panel_picture_frame_bg_pressed_blue.9.png
Binary files differ
diff --git a/core/res/res/drawable/panel_separator.9.png b/core/res/res/drawable/panel_separator.9.png
new file mode 100644
index 0000000..0c07bf8
--- /dev/null
+++ b/core/res/res/drawable/panel_separator.9.png
Binary files differ
diff --git a/core/res/res/drawable/pickerbox.xml b/core/res/res/drawable/pickerbox.xml
new file mode 100644
index 0000000..9cb2436
--- /dev/null
+++ b/core/res/res/drawable/pickerbox.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_focused="false" android:state_pressed="true" android:drawable="@drawable/pickerbox_selected" />
+    <item android:drawable="@drawable/pickerbox_unselected" />
+</selector>
+
diff --git a/core/res/res/drawable/pickerbox_background.png b/core/res/res/drawable/pickerbox_background.png
new file mode 100644
index 0000000..6494cd8
--- /dev/null
+++ b/core/res/res/drawable/pickerbox_background.png
Binary files differ
diff --git a/core/res/res/drawable/pickerbox_selected.9.png b/core/res/res/drawable/pickerbox_selected.9.png
new file mode 100644
index 0000000..d986a31
--- /dev/null
+++ b/core/res/res/drawable/pickerbox_selected.9.png
Binary files differ
diff --git a/core/res/res/drawable/pickerbox_unselected.9.png b/core/res/res/drawable/pickerbox_unselected.9.png
new file mode 100644
index 0000000..27ec6b9
--- /dev/null
+++ b/core/res/res/drawable/pickerbox_unselected.9.png
Binary files differ
diff --git a/core/res/res/drawable/picture_emergency.png b/core/res/res/drawable/picture_emergency.png
new file mode 100644
index 0000000..3690b07
--- /dev/null
+++ b/core/res/res/drawable/picture_emergency.png
Binary files differ
diff --git a/core/res/res/drawable/picture_frame.9.png b/core/res/res/drawable/picture_frame.9.png
new file mode 100644
index 0000000..ba71570
--- /dev/null
+++ b/core/res/res/drawable/picture_frame.9.png
Binary files differ
diff --git a/core/res/res/drawable/popup_bottom_bright.9.png b/core/res/res/drawable/popup_bottom_bright.9.png
new file mode 100644
index 0000000..e8e203b
--- /dev/null
+++ b/core/res/res/drawable/popup_bottom_bright.9.png
Binary files differ
diff --git a/core/res/res/drawable/popup_bottom_dark.9.png b/core/res/res/drawable/popup_bottom_dark.9.png
new file mode 100644
index 0000000..76a2a7f
--- /dev/null
+++ b/core/res/res/drawable/popup_bottom_dark.9.png
Binary files differ
diff --git a/core/res/res/drawable/popup_bottom_medium.9.png b/core/res/res/drawable/popup_bottom_medium.9.png
new file mode 100755
index 0000000..dee6d6b
--- /dev/null
+++ b/core/res/res/drawable/popup_bottom_medium.9.png
Binary files differ
diff --git a/core/res/res/drawable/popup_center_bright.9.png b/core/res/res/drawable/popup_center_bright.9.png
new file mode 100644
index 0000000..c817338db
--- /dev/null
+++ b/core/res/res/drawable/popup_center_bright.9.png
Binary files differ
diff --git a/core/res/res/drawable/popup_center_dark.9.png b/core/res/res/drawable/popup_center_dark.9.png
new file mode 100644
index 0000000..79ffdaa
--- /dev/null
+++ b/core/res/res/drawable/popup_center_dark.9.png
Binary files differ
diff --git a/core/res/res/drawable/popup_center_medium.9.png b/core/res/res/drawable/popup_center_medium.9.png
new file mode 100755
index 0000000..ba2e9bf
--- /dev/null
+++ b/core/res/res/drawable/popup_center_medium.9.png
Binary files differ
diff --git a/core/res/res/drawable/popup_full_bright.9.png b/core/res/res/drawable/popup_full_bright.9.png
new file mode 100644
index 0000000..d33ff2b
--- /dev/null
+++ b/core/res/res/drawable/popup_full_bright.9.png
Binary files differ
diff --git a/core/res/res/drawable/popup_full_dark.9.png b/core/res/res/drawable/popup_full_dark.9.png
new file mode 100644
index 0000000..2305be4
--- /dev/null
+++ b/core/res/res/drawable/popup_full_dark.9.png
Binary files differ
diff --git a/core/res/res/drawable/popup_inline_error.9.png b/core/res/res/drawable/popup_inline_error.9.png
new file mode 100755
index 0000000..6a8297a
--- /dev/null
+++ b/core/res/res/drawable/popup_inline_error.9.png
Binary files differ
diff --git a/core/res/res/drawable/popup_inline_error_above.9.png b/core/res/res/drawable/popup_inline_error_above.9.png
new file mode 100644
index 0000000..2e601d0
--- /dev/null
+++ b/core/res/res/drawable/popup_inline_error_above.9.png
Binary files differ
diff --git a/core/res/res/drawable/popup_top_bright.9.png b/core/res/res/drawable/popup_top_bright.9.png
new file mode 100644
index 0000000..727a948
--- /dev/null
+++ b/core/res/res/drawable/popup_top_bright.9.png
Binary files differ
diff --git a/core/res/res/drawable/popup_top_dark.9.png b/core/res/res/drawable/popup_top_dark.9.png
new file mode 100644
index 0000000..af511f2
--- /dev/null
+++ b/core/res/res/drawable/popup_top_dark.9.png
Binary files differ
diff --git a/core/res/res/drawable/presence_away.png b/core/res/res/drawable/presence_away.png
new file mode 100644
index 0000000..f8120df
--- /dev/null
+++ b/core/res/res/drawable/presence_away.png
Binary files differ
diff --git a/core/res/res/drawable/presence_busy.png b/core/res/res/drawable/presence_busy.png
new file mode 100644
index 0000000..9d7620b
--- /dev/null
+++ b/core/res/res/drawable/presence_busy.png
Binary files differ
diff --git a/core/res/res/drawable/presence_invisible.png b/core/res/res/drawable/presence_invisible.png
new file mode 100644
index 0000000..21399a4
--- /dev/null
+++ b/core/res/res/drawable/presence_invisible.png
Binary files differ
diff --git a/core/res/res/drawable/presence_offline.png b/core/res/res/drawable/presence_offline.png
new file mode 100644
index 0000000..3941b82
--- /dev/null
+++ b/core/res/res/drawable/presence_offline.png
Binary files differ
diff --git a/core/res/res/drawable/presence_online.png b/core/res/res/drawable/presence_online.png
new file mode 100644
index 0000000..22d5683
--- /dev/null
+++ b/core/res/res/drawable/presence_online.png
Binary files differ
diff --git a/core/res/res/drawable/pressed_application_background_static.png b/core/res/res/drawable/pressed_application_background_static.png
new file mode 100644
index 0000000..070f6fd
--- /dev/null
+++ b/core/res/res/drawable/pressed_application_background_static.png
Binary files differ
diff --git a/core/res/res/drawable/progress.xml b/core/res/res/drawable/progress.xml
new file mode 100644
index 0000000..d270520
--- /dev/null
+++ b/core/res/res/drawable/progress.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* //device/apps/common/res/drawable/progress.xml
+**
+** Copyright 2007, 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.
+*/
+-->
+
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:drawable="@android:drawable/progress_circular_background" />
+    <item>
+        <shape android:shape="ring"
+               android:innerRadiusRatio="3.4"
+               android:thicknessRatio="6.0">
+            <gradient
+                   android:useLevel="true"
+                   android:type="sweep"
+                   android:startColor="#ff000000"
+                   android:endColor="#ffffffff" />
+        </shape>
+    </item>
+    <item>
+        <rotate
+            android:pivotX="50%" android:pivotY="50%"
+            android:fromDegrees="0" android:toDegrees="360"
+            android:drawable="@android:drawable/progress_particle" />
+    </item>
+</layer-list>
diff --git a/core/res/res/drawable/progress_circular_background.png b/core/res/res/drawable/progress_circular_background.png
new file mode 100644
index 0000000..7c637fd
--- /dev/null
+++ b/core/res/res/drawable/progress_circular_background.png
Binary files differ
diff --git a/core/res/res/drawable/progress_circular_background_small.png b/core/res/res/drawable/progress_circular_background_small.png
new file mode 100644
index 0000000..6b8ba9b
--- /dev/null
+++ b/core/res/res/drawable/progress_circular_background_small.png
Binary files differ
diff --git a/core/res/res/drawable/progress_circular_indeterminate.png b/core/res/res/drawable/progress_circular_indeterminate.png
new file mode 100644
index 0000000..125a264
--- /dev/null
+++ b/core/res/res/drawable/progress_circular_indeterminate.png
Binary files differ
diff --git a/core/res/res/drawable/progress_circular_indeterminate_small.png b/core/res/res/drawable/progress_circular_indeterminate_small.png
new file mode 100644
index 0000000..15418cb
--- /dev/null
+++ b/core/res/res/drawable/progress_circular_indeterminate_small.png
Binary files differ
diff --git a/core/res/res/drawable/progress_horizontal.xml b/core/res/res/drawable/progress_horizontal.xml
new file mode 100644
index 0000000..57d8589
--- /dev/null
+++ b/core/res/res/drawable/progress_horizontal.xml
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+    
+    <item android:id="@android:id/background">
+        <shape>
+            <corners android:radius="5dip" />
+            <gradient
+                    android:startColor="#ff9d9e9d"
+                    android:centerColor="#ff5a5d5a"
+                    android:centerY="0.75"
+                    android:endColor="#ff747674"
+                    android:angle="270"
+            />
+        </shape>
+    </item>
+    
+    <item android:id="@android:id/secondaryProgress">
+        <clip>
+            <shape>
+                <corners android:radius="5dip" />
+                <gradient
+                        android:startColor="#80ffd300"
+                        android:centerColor="#80ffb600"
+                        android:centerY="0.75"
+                        android:endColor="#a0ffcb00"
+                        android:angle="270"
+                />
+            </shape>
+        </clip>
+    </item>
+    
+    <item android:id="@android:id/progress">
+        <clip>
+            <shape>
+                <corners android:radius="5dip" />
+                <gradient
+                        android:startColor="#ffffd300"
+                        android:centerColor="#ffffb600"
+                        android:centerY="0.75"
+                        android:endColor="#ffffcb00"
+                        android:angle="270"
+                />
+            </shape>
+        </clip>
+    </item>
+    
+</layer-list>
+
diff --git a/core/res/res/drawable/progress_indeterminate.xml b/core/res/res/drawable/progress_indeterminate.xml
new file mode 100644
index 0000000..1bf715e5
--- /dev/null
+++ b/core/res/res/drawable/progress_indeterminate.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* //device/apps/common/res/drawable/progress.xml
+**
+** Copyright 2007, 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.
+*/
+-->
+
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+    <item
+        android:drawable="@android:drawable/progress_circular_background" />
+
+    <item><rotate
+        android:pivotX="50%"
+        android:pivotY="50%"
+        android:fromDegrees="0"
+        android:toDegrees="360"
+        android:drawable="@android:drawable/progress_circular_indeterminate" />
+    </item>
+</layer-list>
diff --git a/core/res/res/drawable/progress_indeterminate_horizontal.xml b/core/res/res/drawable/progress_indeterminate_horizontal.xml
new file mode 100644
index 0000000..66ed1f2
--- /dev/null
+++ b/core/res/res/drawable/progress_indeterminate_horizontal.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* //device/apps/common/res/drawable/ic_popup_sync.xml
+**
+** Copyright 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.
+*/
+-->
+<animation-list
+        xmlns:android="http://schemas.android.com/apk/res/android"
+        android:oneshot="false">
+    <item android:drawable="@drawable/progressbar_indeterminate1" android:duration="200" />
+    <item android:drawable="@drawable/progressbar_indeterminate2" android:duration="200" />
+    <item android:drawable="@drawable/progressbar_indeterminate3" android:duration="200" />
+</animation-list>
diff --git a/core/res/res/drawable/progress_indeterminate_small.xml b/core/res/res/drawable/progress_indeterminate_small.xml
new file mode 100644
index 0000000..a55fe35
--- /dev/null
+++ b/core/res/res/drawable/progress_indeterminate_small.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* //device/apps/common/res/drawable/progress.xml
+**
+** Copyright 2007, 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.
+*/
+-->
+
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+    <item
+        android:drawable="@android:drawable/progress_circular_background_small" />
+
+    <item><rotate
+        android:pivotX="50%"
+        android:pivotY="50%"
+        android:fromDegrees="0"
+        android:toDegrees="360"
+        android:drawable="@android:drawable/progress_circular_indeterminate_small" />
+    </item>
+</layer-list>
diff --git a/core/res/res/drawable/progress_large.xml b/core/res/res/drawable/progress_large.xml
new file mode 100644
index 0000000..4669104
--- /dev/null
+++ b/core/res/res/drawable/progress_large.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2007 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.
+-->
+
+    
+<rotate xmlns:android="http://schemas.android.com/apk/res/android"
+        android:pivotX="50%" android:pivotY="50%"
+        android:fromDegrees="0" android:toDegrees="360">
+        
+    <shape
+            android:shape="ring"
+            android:innerRadiusRatio="3"
+            android:thicknessRatio="8"
+            android:useLevel="false">
+
+        <size
+                android:width="76dip"
+                android:height="76dip"
+        />
+        
+        <gradient
+                android:type="sweep"
+                android:useLevel="false"
+                android:startColor="#4c737373"
+                android:centerColor="#4c737373"
+                android:centerY="0.50"
+                android:endColor="#ffffd300"
+        />
+        
+    </shape>
+    
+</rotate>
+    
diff --git a/core/res/res/drawable/progress_medium.xml b/core/res/res/drawable/progress_medium.xml
new file mode 100644
index 0000000..92aebb5
--- /dev/null
+++ b/core/res/res/drawable/progress_medium.xml
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2007 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.
+-->
+
+<rotate xmlns:android="http://schemas.android.com/apk/res/android"
+        android:pivotX="50%" android:pivotY="50%"
+        android:fromDegrees="0" android:toDegrees="360">
+        
+    <shape
+            android:shape="ring"
+            android:innerRadiusRatio="3"
+            android:thicknessRatio="8"
+            android:useLevel="false">
+
+        <size
+                android:width="48dip"
+                android:height="48dip"
+        />
+        
+        <gradient
+                android:type="sweep"
+                android:useLevel="false"
+                android:startColor="#4c737373"
+                android:centerColor="#4c737373"
+                android:centerY="0.50"
+                android:endColor="#ffffd300"
+        />
+        
+    </shape>
+    
+</rotate>
diff --git a/core/res/res/drawable/progress_particle.png b/core/res/res/drawable/progress_particle.png
new file mode 100644
index 0000000..9160108
--- /dev/null
+++ b/core/res/res/drawable/progress_particle.png
Binary files differ
diff --git a/core/res/res/drawable/progress_small.xml b/core/res/res/drawable/progress_small.xml
new file mode 100644
index 0000000..e5b0021
--- /dev/null
+++ b/core/res/res/drawable/progress_small.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2007 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.
+-->
+
+ 
+<rotate xmlns:android="http://schemas.android.com/apk/res/android"
+        android:pivotX="50%" android:pivotY="50%"
+        android:fromDegrees="0" android:toDegrees="360">
+
+    <!-- An extra pixel is added on both ratios for stroke -->        
+    <shape
+            android:shape="ring"
+            android:innerRadiusRatio="3.2"
+            android:thicknessRatio="5.333"
+            android:useLevel="false">
+
+        <size
+                android:width="16dip"
+                android:height="16dip"
+        />
+        
+        <gradient
+                android:type="sweep"
+                android:useLevel="false"
+                android:startColor="#4c737373"
+                android:centerColor="#4c737373"
+                android:centerY="0.50"
+                android:endColor="#ffffd300"
+        />
+        
+    </shape>
+    
+</rotate>
diff --git a/core/res/res/drawable/progress_small_titlebar.xml b/core/res/res/drawable/progress_small_titlebar.xml
new file mode 100644
index 0000000..cf8e41cb
--- /dev/null
+++ b/core/res/res/drawable/progress_small_titlebar.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2007 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.
+-->
+
+ 
+<rotate xmlns:android="http://schemas.android.com/apk/res/android"
+        android:pivotX="50%" android:pivotY="50%"
+        android:fromDegrees="0" android:toDegrees="360">
+
+    <!-- An extra pixel is added on both ratios for stroke -->        
+    <shape
+            android:shape="ring"
+            android:innerRadiusRatio="3.2"
+            android:thicknessRatio="5.333"
+            android:useLevel="false">
+
+        <size
+                android:width="16dip"
+                android:height="16dip"
+        />
+        
+        <gradient
+                android:type="sweep"
+                android:useLevel="false"
+                android:startColor="#ff666666"
+                android:centerColor="#ff666666"
+                android:centerY="0.50"
+                android:endColor="#ffffd300"
+        />
+        
+    </shape>
+    
+</rotate>
diff --git a/core/res/res/drawable/progressbar_indeterminate1.png b/core/res/res/drawable/progressbar_indeterminate1.png
new file mode 100644
index 0000000..5eddb30
--- /dev/null
+++ b/core/res/res/drawable/progressbar_indeterminate1.png
Binary files differ
diff --git a/core/res/res/drawable/progressbar_indeterminate2.png b/core/res/res/drawable/progressbar_indeterminate2.png
new file mode 100644
index 0000000..4ca3a63
--- /dev/null
+++ b/core/res/res/drawable/progressbar_indeterminate2.png
Binary files differ
diff --git a/core/res/res/drawable/progressbar_indeterminate3.png b/core/res/res/drawable/progressbar_indeterminate3.png
new file mode 100644
index 0000000..da8e601
--- /dev/null
+++ b/core/res/res/drawable/progressbar_indeterminate3.png
Binary files differ
diff --git a/core/res/res/drawable/radiobutton_off_background.png b/core/res/res/drawable/radiobutton_off_background.png
new file mode 100644
index 0000000..1b94e21
--- /dev/null
+++ b/core/res/res/drawable/radiobutton_off_background.png
Binary files differ
diff --git a/core/res/res/drawable/radiobutton_on_background.png b/core/res/res/drawable/radiobutton_on_background.png
new file mode 100644
index 0000000..636a803
--- /dev/null
+++ b/core/res/res/drawable/radiobutton_on_background.png
Binary files differ
diff --git a/core/res/res/drawable/rate_star_big_half.png b/core/res/res/drawable/rate_star_big_half.png
new file mode 100644
index 0000000..e73ca79
--- /dev/null
+++ b/core/res/res/drawable/rate_star_big_half.png
Binary files differ
diff --git a/core/res/res/drawable/rate_star_big_off.png b/core/res/res/drawable/rate_star_big_off.png
new file mode 100644
index 0000000..b4dfa9d
--- /dev/null
+++ b/core/res/res/drawable/rate_star_big_off.png
Binary files differ
diff --git a/core/res/res/drawable/rate_star_big_on.png b/core/res/res/drawable/rate_star_big_on.png
new file mode 100644
index 0000000..7442c93
--- /dev/null
+++ b/core/res/res/drawable/rate_star_big_on.png
Binary files differ
diff --git a/core/res/res/drawable/rate_star_small_half.png b/core/res/res/drawable/rate_star_small_half.png
new file mode 100644
index 0000000..a81449b
--- /dev/null
+++ b/core/res/res/drawable/rate_star_small_half.png
Binary files differ
diff --git a/core/res/res/drawable/rate_star_small_off.png b/core/res/res/drawable/rate_star_small_off.png
new file mode 100644
index 0000000..618766f
--- /dev/null
+++ b/core/res/res/drawable/rate_star_small_off.png
Binary files differ
diff --git a/core/res/res/drawable/rate_star_small_on.png b/core/res/res/drawable/rate_star_small_on.png
new file mode 100644
index 0000000..74e3280
--- /dev/null
+++ b/core/res/res/drawable/rate_star_small_on.png
Binary files differ
diff --git a/core/res/res/drawable/ratingbar.xml b/core/res/res/drawable/ratingbar.xml
new file mode 100644
index 0000000..2be391f
--- /dev/null
+++ b/core/res/res/drawable/ratingbar.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:id="@+android:id/background" android:drawable="@android:drawable/rate_star_big_off" />
+    <item android:id="@+android:id/secondaryProgress" android:drawable="@android:drawable/rate_star_big_half" />
+    <item android:id="@+android:id/progress" android:drawable="@android:drawable/rate_star_big_on" />
+</layer-list>
+
diff --git a/core/res/res/drawable/ratingbar_full.xml b/core/res/res/drawable/ratingbar_full.xml
new file mode 100644
index 0000000..3bd1a6a
--- /dev/null
+++ b/core/res/res/drawable/ratingbar_full.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:id="@+android:id/background" android:drawable="@android:drawable/ratingbar_full_empty" />
+    <item android:id="@+android:id/secondaryProgress" android:drawable="@android:drawable/ratingbar_full_empty" />
+    <item android:id="@+android:id/progress" android:drawable="@android:drawable/ratingbar_full_filled" />
+</layer-list>
+
diff --git a/core/res/res/drawable/ratingbar_full_empty.xml b/core/res/res/drawable/ratingbar_full_empty.xml
new file mode 100644
index 0000000..527efc3
--- /dev/null
+++ b/core/res/res/drawable/ratingbar_full_empty.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<!-- This is the rating bar drawable that is used to a show a filled star. -->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <item android:state_pressed="true"
+          android:state_window_focused="true"
+          android:drawable="@drawable/btn_rating_star_off_pressed" />
+
+    <item android:state_focused="true"
+          android:state_window_focused="true"
+          android:drawable="@drawable/btn_rating_star_off_selected" />
+
+    <item android:state_selected="true"
+          android:state_window_focused="true"
+          android:drawable="@drawable/btn_rating_star_off_selected" />
+
+    <item android:drawable="@drawable/btn_rating_star_off_normal" />
+
+</selector>
diff --git a/core/res/res/drawable/ratingbar_full_filled.xml b/core/res/res/drawable/ratingbar_full_filled.xml
new file mode 100644
index 0000000..e95ba6d
--- /dev/null
+++ b/core/res/res/drawable/ratingbar_full_filled.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<!-- This is the rating bar drawable that is used to a show a filled star. -->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <item android:state_pressed="true"
+          android:state_window_focused="true"
+          android:drawable="@drawable/btn_rating_star_on_pressed" />
+
+    <item android:state_focused="true"
+          android:state_window_focused="true"
+          android:drawable="@drawable/btn_rating_star_on_selected" />
+
+    <item android:state_selected="true"
+          android:state_window_focused="true"
+          android:drawable="@drawable/btn_rating_star_on_selected" />
+
+    <item android:drawable="@drawable/btn_rating_star_on_normal" />
+
+</selector>
diff --git a/core/res/res/drawable/ratingbar_small.xml b/core/res/res/drawable/ratingbar_small.xml
new file mode 100644
index 0000000..6095c61
--- /dev/null
+++ b/core/res/res/drawable/ratingbar_small.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:id="@+android:id/background" android:drawable="@android:drawable/rate_star_small_off" />
+    <item android:id="@+android:id/secondaryProgress" android:drawable="@android:drawable/rate_star_small_half" />
+    <item android:id="@+android:id/progress" android:drawable="@android:drawable/rate_star_small_on" />
+</layer-list>
+
diff --git a/core/res/res/drawable/reticle.png b/core/res/res/drawable/reticle.png
new file mode 100644
index 0000000..c6ccf8e
--- /dev/null
+++ b/core/res/res/drawable/reticle.png
Binary files differ
diff --git a/core/res/res/drawable/screen_progress.xml b/core/res/res/drawable/screen_progress.xml
new file mode 100644
index 0000000..aed23a6
--- /dev/null
+++ b/core/res/res/drawable/screen_progress.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* //device/apps/common/res/drawable/progress.xml
+**
+** Copyright 2007, 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.
+*/
+-->
+
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:drawable="@android:drawable/screen_progress_frame" />
+    <item>
+        <scale scaleWidth="100%" scaleGravity="0x3" drawable="@android:drawable/screen_progress_inner" />
+    </item>
+</layer-list>
+
diff --git a/core/res/res/drawable/screen_progress_frame.9.png b/core/res/res/drawable/screen_progress_frame.9.png
new file mode 100644
index 0000000..0e92429
--- /dev/null
+++ b/core/res/res/drawable/screen_progress_frame.9.png
Binary files differ
diff --git a/core/res/res/drawable/screen_progress_inner.9.png b/core/res/res/drawable/screen_progress_inner.9.png
new file mode 100644
index 0000000..1799a53
--- /dev/null
+++ b/core/res/res/drawable/screen_progress_inner.9.png
Binary files differ
diff --git a/core/res/res/drawable/scrollbar_handle_accelerated_anim2.9.png b/core/res/res/drawable/scrollbar_handle_accelerated_anim2.9.png
new file mode 100755
index 0000000..85caddd
--- /dev/null
+++ b/core/res/res/drawable/scrollbar_handle_accelerated_anim2.9.png
Binary files differ
diff --git a/core/res/res/drawable/scrollbar_handle_horizontal.9.png b/core/res/res/drawable/scrollbar_handle_horizontal.9.png
new file mode 100755
index 0000000..324b4bd
--- /dev/null
+++ b/core/res/res/drawable/scrollbar_handle_horizontal.9.png
Binary files differ
diff --git a/core/res/res/drawable/scrollbar_handle_vertical.9.png b/core/res/res/drawable/scrollbar_handle_vertical.9.png
new file mode 100755
index 0000000..3519d68
--- /dev/null
+++ b/core/res/res/drawable/scrollbar_handle_vertical.9.png
Binary files differ
diff --git a/core/res/res/drawable/scrollbar_horizontal.9.png b/core/res/res/drawable/scrollbar_horizontal.9.png
new file mode 100644
index 0000000..40faa82
--- /dev/null
+++ b/core/res/res/drawable/scrollbar_horizontal.9.png
Binary files differ
diff --git a/core/res/res/drawable/scrollbar_vertical.9.png b/core/res/res/drawable/scrollbar_vertical.9.png
new file mode 100755
index 0000000..08f5ca9
--- /dev/null
+++ b/core/res/res/drawable/scrollbar_vertical.9.png
Binary files differ
diff --git a/core/res/res/drawable/search_plate.9.png b/core/res/res/drawable/search_plate.9.png
new file mode 100755
index 0000000..8c42f10
--- /dev/null
+++ b/core/res/res/drawable/search_plate.9.png
Binary files differ
diff --git a/core/res/res/drawable/seek_thumb.xml b/core/res/res/drawable/seek_thumb.xml
new file mode 100644
index 0000000..7fe51b3
--- /dev/null
+++ b/core/res/res/drawable/seek_thumb.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<!-- This is the rating bar drawable that is used to a show a filled star. -->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <item android:state_pressed="true"
+          android:state_window_focused="true"
+          android:drawable="@drawable/seek_thumb_pressed" />
+
+    <item android:state_focused="true"
+          android:state_window_focused="true"
+          android:drawable="@drawable/seek_thumb_selected" />
+
+    <item android:state_selected="true"
+          android:state_window_focused="true"
+          android:drawable="@drawable/seek_thumb_selected" />
+
+    <item android:drawable="@drawable/seek_thumb_normal" />
+
+</selector>
diff --git a/core/res/res/drawable/seek_thumb_normal.png b/core/res/res/drawable/seek_thumb_normal.png
new file mode 100644
index 0000000..dbaae91
--- /dev/null
+++ b/core/res/res/drawable/seek_thumb_normal.png
Binary files differ
diff --git a/core/res/res/drawable/seek_thumb_pressed.png b/core/res/res/drawable/seek_thumb_pressed.png
new file mode 100644
index 0000000..dbaae91
--- /dev/null
+++ b/core/res/res/drawable/seek_thumb_pressed.png
Binary files differ
diff --git a/core/res/res/drawable/seek_thumb_selected.png b/core/res/res/drawable/seek_thumb_selected.png
new file mode 100644
index 0000000..dbaae91
--- /dev/null
+++ b/core/res/res/drawable/seek_thumb_selected.png
Binary files differ
diff --git a/core/res/res/drawable/settings_header.xml b/core/res/res/drawable/settings_header.xml
new file mode 100644
index 0000000..c820319
--- /dev/null
+++ b/core/res/res/drawable/settings_header.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<bitmap xmlns:android="http://schemas.android.com/apk/res/android"
+    android:src="@drawable/settings_header_raw"
+    android:dither="true"
+/>
diff --git a/core/res/res/drawable/settings_header_raw.9.png b/core/res/res/drawable/settings_header_raw.9.png
new file mode 100644
index 0000000..6b8134d
--- /dev/null
+++ b/core/res/res/drawable/settings_header_raw.9.png
Binary files differ
diff --git a/core/res/res/drawable/spinner_background.xml b/core/res/res/drawable/spinner_background.xml
new file mode 100644
index 0000000..458b3a9
--- /dev/null
+++ b/core/res/res/drawable/spinner_background.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_pressed="true"
+          android:drawable="@drawable/spinner_press" />
+    <item android:state_pressed="false" android:state_focused="true"
+          android:drawable="@drawable/spinner_select" />
+    <item android:drawable="@drawable/spinner_normal" />
+</selector>
diff --git a/core/res/res/drawable/spinner_dropdown_background.xml b/core/res/res/drawable/spinner_dropdown_background.xml
new file mode 100644
index 0000000..9c37286
--- /dev/null
+++ b/core/res/res/drawable/spinner_dropdown_background.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_above_anchor="true"
+        android:drawable="@drawable/spinner_dropdown_background_up" />
+    <item android:drawable="@drawable/spinner_dropdown_background_down" />
+</selector>
+
diff --git a/core/res/res/drawable/spinner_dropdown_background_down.9.png b/core/res/res/drawable/spinner_dropdown_background_down.9.png
new file mode 100644
index 0000000..8fd22f4
--- /dev/null
+++ b/core/res/res/drawable/spinner_dropdown_background_down.9.png
Binary files differ
diff --git a/core/res/res/drawable/spinner_dropdown_background_up.9.png b/core/res/res/drawable/spinner_dropdown_background_up.9.png
new file mode 100644
index 0000000..1354feb
--- /dev/null
+++ b/core/res/res/drawable/spinner_dropdown_background_up.9.png
Binary files differ
diff --git a/core/res/res/drawable/spinner_normal.9.png b/core/res/res/drawable/spinner_normal.9.png
new file mode 100644
index 0000000..e0bab34
--- /dev/null
+++ b/core/res/res/drawable/spinner_normal.9.png
Binary files differ
diff --git a/core/res/res/drawable/spinner_press.9.png b/core/res/res/drawable/spinner_press.9.png
new file mode 100644
index 0000000..a51c7ad
--- /dev/null
+++ b/core/res/res/drawable/spinner_press.9.png
Binary files differ
diff --git a/core/res/res/drawable/spinner_select.9.png b/core/res/res/drawable/spinner_select.9.png
new file mode 100644
index 0000000..1bb19be
--- /dev/null
+++ b/core/res/res/drawable/spinner_select.9.png
Binary files differ
diff --git a/core/res/res/drawable/spinnerbox_arrow_first.9.png b/core/res/res/drawable/spinnerbox_arrow_first.9.png
new file mode 100644
index 0000000..d8e268d
--- /dev/null
+++ b/core/res/res/drawable/spinnerbox_arrow_first.9.png
Binary files differ
diff --git a/core/res/res/drawable/spinnerbox_arrow_last.9.png b/core/res/res/drawable/spinnerbox_arrow_last.9.png
new file mode 100644
index 0000000..087e650
--- /dev/null
+++ b/core/res/res/drawable/spinnerbox_arrow_last.9.png
Binary files differ
diff --git a/core/res/res/drawable/spinnerbox_arrow_middle.9.png b/core/res/res/drawable/spinnerbox_arrow_middle.9.png
new file mode 100644
index 0000000..f1f2ff5
--- /dev/null
+++ b/core/res/res/drawable/spinnerbox_arrow_middle.9.png
Binary files differ
diff --git a/core/res/res/drawable/spinnerbox_arrow_single.9.png b/core/res/res/drawable/spinnerbox_arrow_single.9.png
new file mode 100644
index 0000000..f537b3b
--- /dev/null
+++ b/core/res/res/drawable/spinnerbox_arrow_single.9.png
Binary files differ
diff --git a/core/res/res/drawable/spinnerbox_arrows.xml b/core/res/res/drawable/spinnerbox_arrows.xml
new file mode 100644
index 0000000..276a0f0
--- /dev/null
+++ b/core/res/res/drawable/spinnerbox_arrows.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* //device/apps/common/res/drawable/spinnerbox_arrows.xml
+**
+** Copyright 2007, 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.
+*/
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_single="true" android:drawable="@drawable/spinnerbox_arrow_single" />
+    <item android:state_first="true" android:drawable="@drawable/spinnerbox_arrow_first" />
+    <item android:state_last="true" android:drawable="@drawable/spinnerbox_arrow_last" />
+    <item android:state_middle="true" android:drawable="@drawable/spinnerbox_arrow_middle" />
+    <item android:state_pressed="true" android:drawable="@drawable/spinnerbox_arrow_single" />
+</selector>
diff --git a/core/res/res/drawable/star_big_off.png b/core/res/res/drawable/star_big_off.png
new file mode 100644
index 0000000..34ab4ab
--- /dev/null
+++ b/core/res/res/drawable/star_big_off.png
Binary files differ
diff --git a/core/res/res/drawable/star_big_on.png b/core/res/res/drawable/star_big_on.png
new file mode 100644
index 0000000..7aaf2bc
--- /dev/null
+++ b/core/res/res/drawable/star_big_on.png
Binary files differ
diff --git a/core/res/res/drawable/star_off.png b/core/res/res/drawable/star_off.png
new file mode 100644
index 0000000..ada53fc
--- /dev/null
+++ b/core/res/res/drawable/star_off.png
Binary files differ
diff --git a/core/res/res/drawable/star_on.png b/core/res/res/drawable/star_on.png
new file mode 100644
index 0000000..49a57b6
--- /dev/null
+++ b/core/res/res/drawable/star_on.png
Binary files differ
diff --git a/core/res/res/drawable/stat_notify_alarm.png b/core/res/res/drawable/stat_notify_alarm.png
new file mode 100644
index 0000000..1b01b85
--- /dev/null
+++ b/core/res/res/drawable/stat_notify_alarm.png
Binary files differ
diff --git a/core/res/res/drawable/stat_notify_call_mute.png b/core/res/res/drawable/stat_notify_call_mute.png
new file mode 100644
index 0000000..6da8313
--- /dev/null
+++ b/core/res/res/drawable/stat_notify_call_mute.png
Binary files differ
diff --git a/core/res/res/drawable/stat_notify_chat.png b/core/res/res/drawable/stat_notify_chat.png
new file mode 100644
index 0000000..238f043
--- /dev/null
+++ b/core/res/res/drawable/stat_notify_chat.png
Binary files differ
diff --git a/core/res/res/drawable/stat_notify_disk_full.png b/core/res/res/drawable/stat_notify_disk_full.png
new file mode 100755
index 0000000..9120f00
--- /dev/null
+++ b/core/res/res/drawable/stat_notify_disk_full.png
Binary files differ
diff --git a/core/res/res/drawable/stat_notify_error.png b/core/res/res/drawable/stat_notify_error.png
new file mode 100644
index 0000000..6ced2b7
--- /dev/null
+++ b/core/res/res/drawable/stat_notify_error.png
Binary files differ
diff --git a/core/res/res/drawable/stat_notify_missed_call.png b/core/res/res/drawable/stat_notify_missed_call.png
new file mode 100644
index 0000000..fe746b3
--- /dev/null
+++ b/core/res/res/drawable/stat_notify_missed_call.png
Binary files differ
diff --git a/core/res/res/drawable/stat_notify_more.png b/core/res/res/drawable/stat_notify_more.png
new file mode 100644
index 0000000..e129ba9
--- /dev/null
+++ b/core/res/res/drawable/stat_notify_more.png
Binary files differ
diff --git a/core/res/res/drawable/stat_notify_sdcard.png b/core/res/res/drawable/stat_notify_sdcard.png
new file mode 100644
index 0000000..aaf1f74
--- /dev/null
+++ b/core/res/res/drawable/stat_notify_sdcard.png
Binary files differ
diff --git a/core/res/res/drawable/stat_notify_sdcard_usb.png b/core/res/res/drawable/stat_notify_sdcard_usb.png
new file mode 100644
index 0000000..8bc6661
--- /dev/null
+++ b/core/res/res/drawable/stat_notify_sdcard_usb.png
Binary files differ
diff --git a/core/res/res/drawable/stat_notify_sim_toolkit.png b/core/res/res/drawable/stat_notify_sim_toolkit.png
new file mode 100755
index 0000000..c1ce8f2
--- /dev/null
+++ b/core/res/res/drawable/stat_notify_sim_toolkit.png
Binary files differ
diff --git a/core/res/res/drawable/stat_notify_sync.png b/core/res/res/drawable/stat_notify_sync.png
new file mode 100644
index 0000000..0edf692
--- /dev/null
+++ b/core/res/res/drawable/stat_notify_sync.png
Binary files differ
diff --git a/core/res/res/drawable/stat_notify_sync_anim0.png b/core/res/res/drawable/stat_notify_sync_anim0.png
new file mode 100644
index 0000000..0edf692
--- /dev/null
+++ b/core/res/res/drawable/stat_notify_sync_anim0.png
Binary files differ
diff --git a/core/res/res/drawable/stat_notify_sync_error.png b/core/res/res/drawable/stat_notify_sync_error.png
new file mode 100644
index 0000000..3078b8c
--- /dev/null
+++ b/core/res/res/drawable/stat_notify_sync_error.png
Binary files differ
diff --git a/core/res/res/drawable/stat_notify_voicemail.png b/core/res/res/drawable/stat_notify_voicemail.png
new file mode 100644
index 0000000..658fa05
--- /dev/null
+++ b/core/res/res/drawable/stat_notify_voicemail.png
Binary files differ
diff --git a/core/res/res/drawable/stat_notify_wifi_in_range.png b/core/res/res/drawable/stat_notify_wifi_in_range.png
new file mode 100644
index 0000000..e9c74b4
--- /dev/null
+++ b/core/res/res/drawable/stat_notify_wifi_in_range.png
Binary files differ
diff --git a/core/res/res/drawable/stat_sys_battery.xml b/core/res/res/drawable/stat_sys_battery.xml
new file mode 100644
index 0000000..968595d
--- /dev/null
+++ b/core/res/res/drawable/stat_sys_battery.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* //device/apps/common/res/drawable/stat_sys_battery.xml
+**
+** Copyright 2007, 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.
+*/
+-->
+
+<level-list xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:maxLevel="4" android:drawable="@android:drawable/stat_sys_battery_0" />
+    <item android:maxLevel="14" android:drawable="@android:drawable/stat_sys_battery_10" />
+    <item android:maxLevel="29" android:drawable="@android:drawable/stat_sys_battery_20" />
+    <item android:maxLevel="49" android:drawable="@android:drawable/stat_sys_battery_40" />
+    <item android:maxLevel="69" android:drawable="@android:drawable/stat_sys_battery_60" />
+    <item android:maxLevel="89" android:drawable="@android:drawable/stat_sys_battery_80" />
+    <item android:maxLevel="100" android:drawable="@android:drawable/stat_sys_battery_100" />
+</level-list>
+
diff --git a/core/res/res/drawable/stat_sys_battery_0.png b/core/res/res/drawable/stat_sys_battery_0.png
new file mode 100644
index 0000000..4a5e99e
--- /dev/null
+++ b/core/res/res/drawable/stat_sys_battery_0.png
Binary files differ
diff --git a/core/res/res/drawable/stat_sys_battery_10.png b/core/res/res/drawable/stat_sys_battery_10.png
new file mode 100755
index 0000000..b789f23
--- /dev/null
+++ b/core/res/res/drawable/stat_sys_battery_10.png
Binary files differ
diff --git a/core/res/res/drawable/stat_sys_battery_100.png b/core/res/res/drawable/stat_sys_battery_100.png
new file mode 100644
index 0000000..d280aeb
--- /dev/null
+++ b/core/res/res/drawable/stat_sys_battery_100.png
Binary files differ
diff --git a/core/res/res/drawable/stat_sys_battery_20.png b/core/res/res/drawable/stat_sys_battery_20.png
new file mode 100644
index 0000000..009a9fd
--- /dev/null
+++ b/core/res/res/drawable/stat_sys_battery_20.png
Binary files differ
diff --git a/core/res/res/drawable/stat_sys_battery_40.png b/core/res/res/drawable/stat_sys_battery_40.png
new file mode 100644
index 0000000..15b57f4
--- /dev/null
+++ b/core/res/res/drawable/stat_sys_battery_40.png
Binary files differ
diff --git a/core/res/res/drawable/stat_sys_battery_60.png b/core/res/res/drawable/stat_sys_battery_60.png
new file mode 100644
index 0000000..21078fd
--- /dev/null
+++ b/core/res/res/drawable/stat_sys_battery_60.png
Binary files differ
diff --git a/core/res/res/drawable/stat_sys_battery_80.png b/core/res/res/drawable/stat_sys_battery_80.png
new file mode 100644
index 0000000..9268f7b
--- /dev/null
+++ b/core/res/res/drawable/stat_sys_battery_80.png
Binary files differ
diff --git a/core/res/res/drawable/stat_sys_battery_charge.xml b/core/res/res/drawable/stat_sys_battery_charge.xml
new file mode 100644
index 0000000..92d7c4f
--- /dev/null
+++ b/core/res/res/drawable/stat_sys_battery_charge.xml
@@ -0,0 +1,74 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* //device/apps/common/res/drawable/stat_sys_battery.xml
+**
+** Copyright 2007, 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.
+*/
+-->
+
+<level-list xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:maxLevel="14">
+        <animation-list
+                xmlns:android="http://schemas.android.com/apk/res/android"
+                android:oneshot="false">
+            <item android:drawable="@drawable/stat_sys_battery_charge_anim0" android:duration="2000" />
+            <item android:drawable="@drawable/stat_sys_battery_charge_anim1" android:duration="1000" />
+            <item android:drawable="@drawable/stat_sys_battery_charge_anim2" android:duration="1000" />
+            <item android:drawable="@drawable/stat_sys_battery_charge_anim3" android:duration="1000" />
+            <item android:drawable="@drawable/stat_sys_battery_charge_anim4" android:duration="1000" />
+            <item android:drawable="@drawable/stat_sys_battery_charge_anim5" android:duration="1000" />
+        </animation-list>
+    </item>
+    <item android:maxLevel="29">
+        <animation-list
+                xmlns:android="http://schemas.android.com/apk/res/android"
+                android:oneshot="false">
+            <item android:drawable="@drawable/stat_sys_battery_charge_anim1" android:duration="2000" />
+            <item android:drawable="@drawable/stat_sys_battery_charge_anim2" android:duration="1000" />
+            <item android:drawable="@drawable/stat_sys_battery_charge_anim3" android:duration="1000" />
+            <item android:drawable="@drawable/stat_sys_battery_charge_anim4" android:duration="1000" />
+            <item android:drawable="@drawable/stat_sys_battery_charge_anim5" android:duration="1000" />
+        </animation-list>
+    </item>
+    <item android:maxLevel="49">
+        <animation-list
+                xmlns:android="http://schemas.android.com/apk/res/android"
+                android:oneshot="false">
+            <item android:drawable="@drawable/stat_sys_battery_charge_anim2" android:duration="2000" />
+            <item android:drawable="@drawable/stat_sys_battery_charge_anim3" android:duration="1000" />
+            <item android:drawable="@drawable/stat_sys_battery_charge_anim4" android:duration="1000" />
+            <item android:drawable="@drawable/stat_sys_battery_charge_anim5" android:duration="1000" />
+        </animation-list>
+    </item>
+    <item android:maxLevel="69">
+        <animation-list
+                xmlns:android="http://schemas.android.com/apk/res/android"
+                android:oneshot="false">
+            <item android:drawable="@drawable/stat_sys_battery_charge_anim3" android:duration="2000" />
+            <item android:drawable="@drawable/stat_sys_battery_charge_anim4" android:duration="1000" />
+            <item android:drawable="@drawable/stat_sys_battery_charge_anim5" android:duration="1000" />
+        </animation-list>
+    </item>
+    <item android:maxLevel="89">
+        <animation-list
+                xmlns:android="http://schemas.android.com/apk/res/android"
+                android:oneshot="false">
+            <item android:drawable="@drawable/stat_sys_battery_charge_anim4" android:duration="2000" />
+            <item android:drawable="@drawable/stat_sys_battery_charge_anim5" android:duration="1000" />
+        </animation-list>
+    </item>
+    <item android:maxLevel="101" android:drawable="@drawable/stat_sys_battery_charge_anim5" />
+</level-list>
+
diff --git a/core/res/res/drawable/stat_sys_battery_charge_anim0.png b/core/res/res/drawable/stat_sys_battery_charge_anim0.png
new file mode 100644
index 0000000..ff3cabd
--- /dev/null
+++ b/core/res/res/drawable/stat_sys_battery_charge_anim0.png
Binary files differ
diff --git a/core/res/res/drawable/stat_sys_battery_charge_anim1.png b/core/res/res/drawable/stat_sys_battery_charge_anim1.png
new file mode 100644
index 0000000..b563701
--- /dev/null
+++ b/core/res/res/drawable/stat_sys_battery_charge_anim1.png
Binary files differ
diff --git a/core/res/res/drawable/stat_sys_battery_charge_anim2.png b/core/res/res/drawable/stat_sys_battery_charge_anim2.png
new file mode 100644
index 0000000..904989e
--- /dev/null
+++ b/core/res/res/drawable/stat_sys_battery_charge_anim2.png
Binary files differ
diff --git a/core/res/res/drawable/stat_sys_battery_charge_anim3.png b/core/res/res/drawable/stat_sys_battery_charge_anim3.png
new file mode 100644
index 0000000..ba011c9
--- /dev/null
+++ b/core/res/res/drawable/stat_sys_battery_charge_anim3.png
Binary files differ
diff --git a/core/res/res/drawable/stat_sys_battery_charge_anim4.png b/core/res/res/drawable/stat_sys_battery_charge_anim4.png
new file mode 100644
index 0000000..4f1c485
--- /dev/null
+++ b/core/res/res/drawable/stat_sys_battery_charge_anim4.png
Binary files differ
diff --git a/core/res/res/drawable/stat_sys_battery_charge_anim5.png b/core/res/res/drawable/stat_sys_battery_charge_anim5.png
new file mode 100644
index 0000000..4d3396d
--- /dev/null
+++ b/core/res/res/drawable/stat_sys_battery_charge_anim5.png
Binary files differ
diff --git a/core/res/res/drawable/stat_sys_battery_unknown.png b/core/res/res/drawable/stat_sys_battery_unknown.png
new file mode 100644
index 0000000..ed72ebf
--- /dev/null
+++ b/core/res/res/drawable/stat_sys_battery_unknown.png
Binary files differ
diff --git a/core/res/res/drawable/stat_sys_data_bluetooth.png b/core/res/res/drawable/stat_sys_data_bluetooth.png
new file mode 100644
index 0000000..7a8b78f
--- /dev/null
+++ b/core/res/res/drawable/stat_sys_data_bluetooth.png
Binary files differ
diff --git a/core/res/res/drawable/stat_sys_data_bluetooth_connected.png b/core/res/res/drawable/stat_sys_data_bluetooth_connected.png
new file mode 100755
index 0000000..f09b83b
--- /dev/null
+++ b/core/res/res/drawable/stat_sys_data_bluetooth_connected.png
Binary files differ
diff --git a/core/res/res/drawable/stat_sys_data_connected_3g.png b/core/res/res/drawable/stat_sys_data_connected_3g.png
new file mode 100644
index 0000000..a109280
--- /dev/null
+++ b/core/res/res/drawable/stat_sys_data_connected_3g.png
Binary files differ
diff --git a/core/res/res/drawable/stat_sys_data_connected_e.png b/core/res/res/drawable/stat_sys_data_connected_e.png
new file mode 100644
index 0000000..c552644
--- /dev/null
+++ b/core/res/res/drawable/stat_sys_data_connected_e.png
Binary files differ
diff --git a/core/res/res/drawable/stat_sys_data_connected_g.png b/core/res/res/drawable/stat_sys_data_connected_g.png
new file mode 100644
index 0000000..f7edb49
--- /dev/null
+++ b/core/res/res/drawable/stat_sys_data_connected_g.png
Binary files differ
diff --git a/core/res/res/drawable/stat_sys_data_in_3g.png b/core/res/res/drawable/stat_sys_data_in_3g.png
new file mode 100644
index 0000000..01b003c
--- /dev/null
+++ b/core/res/res/drawable/stat_sys_data_in_3g.png
Binary files differ
diff --git a/core/res/res/drawable/stat_sys_data_in_e.png b/core/res/res/drawable/stat_sys_data_in_e.png
new file mode 100644
index 0000000..bffa0eb
--- /dev/null
+++ b/core/res/res/drawable/stat_sys_data_in_e.png
Binary files differ
diff --git a/core/res/res/drawable/stat_sys_data_in_g.png b/core/res/res/drawable/stat_sys_data_in_g.png
new file mode 100644
index 0000000..8884b48
--- /dev/null
+++ b/core/res/res/drawable/stat_sys_data_in_g.png
Binary files differ
diff --git a/core/res/res/drawable/stat_sys_data_inandout_3g.png b/core/res/res/drawable/stat_sys_data_inandout_3g.png
new file mode 100644
index 0000000..3651300
--- /dev/null
+++ b/core/res/res/drawable/stat_sys_data_inandout_3g.png
Binary files differ
diff --git a/core/res/res/drawable/stat_sys_data_inandout_e.png b/core/res/res/drawable/stat_sys_data_inandout_e.png
new file mode 100644
index 0000000..99533e0
--- /dev/null
+++ b/core/res/res/drawable/stat_sys_data_inandout_e.png
Binary files differ
diff --git a/core/res/res/drawable/stat_sys_data_inandout_g.png b/core/res/res/drawable/stat_sys_data_inandout_g.png
new file mode 100644
index 0000000..f4e5a12
--- /dev/null
+++ b/core/res/res/drawable/stat_sys_data_inandout_g.png
Binary files differ
diff --git a/core/res/res/drawable/stat_sys_data_out_3g.png b/core/res/res/drawable/stat_sys_data_out_3g.png
new file mode 100644
index 0000000..f7f0f89
--- /dev/null
+++ b/core/res/res/drawable/stat_sys_data_out_3g.png
Binary files differ
diff --git a/core/res/res/drawable/stat_sys_data_out_e.png b/core/res/res/drawable/stat_sys_data_out_e.png
new file mode 100644
index 0000000..c915426
--- /dev/null
+++ b/core/res/res/drawable/stat_sys_data_out_e.png
Binary files differ
diff --git a/core/res/res/drawable/stat_sys_data_out_g.png b/core/res/res/drawable/stat_sys_data_out_g.png
new file mode 100644
index 0000000..5d36035
--- /dev/null
+++ b/core/res/res/drawable/stat_sys_data_out_g.png
Binary files differ
diff --git a/core/res/res/drawable/stat_sys_data_usb.png b/core/res/res/drawable/stat_sys_data_usb.png
new file mode 100644
index 0000000..2d0da4c
--- /dev/null
+++ b/core/res/res/drawable/stat_sys_data_usb.png
Binary files differ
diff --git a/core/res/res/drawable/stat_sys_download.xml b/core/res/res/drawable/stat_sys_download.xml
new file mode 100644
index 0000000..77ecf85
--- /dev/null
+++ b/core/res/res/drawable/stat_sys_download.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* //device/apps/common/res/drawable/status_icon_background.xml
+**
+** Copyright 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.
+*/
+-->
+<animation-list
+        xmlns:android="http://schemas.android.com/apk/res/android"
+        android:oneshot="false">
+    <item android:drawable="@drawable/stat_sys_download_anim0" android:duration="200" />
+    <item android:drawable="@drawable/stat_sys_download_anim1" android:duration="200" />
+    <item android:drawable="@drawable/stat_sys_download_anim2" android:duration="200" />
+    <item android:drawable="@drawable/stat_sys_download_anim3" android:duration="200" />
+    <item android:drawable="@drawable/stat_sys_download_anim4" android:duration="200" />
+    <item android:drawable="@drawable/stat_sys_download_anim5" android:duration="200" />
+</animation-list>
+
diff --git a/core/res/res/drawable/stat_sys_download_anim0.png b/core/res/res/drawable/stat_sys_download_anim0.png
new file mode 100755
index 0000000..69b95cd
--- /dev/null
+++ b/core/res/res/drawable/stat_sys_download_anim0.png
Binary files differ
diff --git a/core/res/res/drawable/stat_sys_download_anim1.png b/core/res/res/drawable/stat_sys_download_anim1.png
new file mode 100755
index 0000000..1e18eb5
--- /dev/null
+++ b/core/res/res/drawable/stat_sys_download_anim1.png
Binary files differ
diff --git a/core/res/res/drawable/stat_sys_download_anim2.png b/core/res/res/drawable/stat_sys_download_anim2.png
new file mode 100755
index 0000000..d7f2312
--- /dev/null
+++ b/core/res/res/drawable/stat_sys_download_anim2.png
Binary files differ
diff --git a/core/res/res/drawable/stat_sys_download_anim3.png b/core/res/res/drawable/stat_sys_download_anim3.png
new file mode 100755
index 0000000..83f8d0f
--- /dev/null
+++ b/core/res/res/drawable/stat_sys_download_anim3.png
Binary files differ
diff --git a/core/res/res/drawable/stat_sys_download_anim4.png b/core/res/res/drawable/stat_sys_download_anim4.png
new file mode 100755
index 0000000..9c1bd47
--- /dev/null
+++ b/core/res/res/drawable/stat_sys_download_anim4.png
Binary files differ
diff --git a/core/res/res/drawable/stat_sys_download_anim5.png b/core/res/res/drawable/stat_sys_download_anim5.png
new file mode 100755
index 0000000..3a81164
--- /dev/null
+++ b/core/res/res/drawable/stat_sys_download_anim5.png
Binary files differ
diff --git a/core/res/res/drawable/stat_sys_gps_acquiring.png b/core/res/res/drawable/stat_sys_gps_acquiring.png
new file mode 100644
index 0000000..31bc94e
--- /dev/null
+++ b/core/res/res/drawable/stat_sys_gps_acquiring.png
Binary files differ
diff --git a/core/res/res/drawable/stat_sys_gps_acquiring_anim.xml b/core/res/res/drawable/stat_sys_gps_acquiring_anim.xml
new file mode 100644
index 0000000..954c19c
--- /dev/null
+++ b/core/res/res/drawable/stat_sys_gps_acquiring_anim.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 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.
+*/
+-->
+<animation-list
+        xmlns:android="http://schemas.android.com/apk/res/android"
+        android:oneshot="false">
+    <item android:drawable="@drawable/stat_sys_gps_acquiring" android:duration="500" />
+    <item android:drawable="@drawable/stat_sys_gps_on" android:duration="500" />
+</animation-list>
diff --git a/core/res/res/drawable/stat_sys_gps_on.png b/core/res/res/drawable/stat_sys_gps_on.png
new file mode 100755
index 0000000..a2c677d
--- /dev/null
+++ b/core/res/res/drawable/stat_sys_gps_on.png
Binary files differ
diff --git a/core/res/res/drawable/stat_sys_headset.png b/core/res/res/drawable/stat_sys_headset.png
new file mode 100644
index 0000000..45fbea2
--- /dev/null
+++ b/core/res/res/drawable/stat_sys_headset.png
Binary files differ
diff --git a/core/res/res/drawable/stat_sys_no_sim.png b/core/res/res/drawable/stat_sys_no_sim.png
new file mode 100644
index 0000000..2134d49
--- /dev/null
+++ b/core/res/res/drawable/stat_sys_no_sim.png
Binary files differ
diff --git a/core/res/res/drawable/stat_sys_phone_call.png b/core/res/res/drawable/stat_sys_phone_call.png
new file mode 100644
index 0000000..ad53693
--- /dev/null
+++ b/core/res/res/drawable/stat_sys_phone_call.png
Binary files differ
diff --git a/core/res/res/drawable/stat_sys_phone_call_forward.png b/core/res/res/drawable/stat_sys_phone_call_forward.png
new file mode 100755
index 0000000..ed4b6ec
--- /dev/null
+++ b/core/res/res/drawable/stat_sys_phone_call_forward.png
Binary files differ
diff --git a/core/res/res/drawable/stat_sys_phone_call_on_hold.png b/core/res/res/drawable/stat_sys_phone_call_on_hold.png
new file mode 100644
index 0000000..9216447
--- /dev/null
+++ b/core/res/res/drawable/stat_sys_phone_call_on_hold.png
Binary files differ
diff --git a/core/res/res/drawable/stat_sys_r_signal_0.png b/core/res/res/drawable/stat_sys_r_signal_0.png
new file mode 100644
index 0000000..bfbf18e
--- /dev/null
+++ b/core/res/res/drawable/stat_sys_r_signal_0.png
Binary files differ
diff --git a/core/res/res/drawable/stat_sys_r_signal_1.png b/core/res/res/drawable/stat_sys_r_signal_1.png
new file mode 100644
index 0000000..896ba4d
--- /dev/null
+++ b/core/res/res/drawable/stat_sys_r_signal_1.png
Binary files differ
diff --git a/core/res/res/drawable/stat_sys_r_signal_2.png b/core/res/res/drawable/stat_sys_r_signal_2.png
new file mode 100644
index 0000000..af79eff
--- /dev/null
+++ b/core/res/res/drawable/stat_sys_r_signal_2.png
Binary files differ
diff --git a/core/res/res/drawable/stat_sys_r_signal_3.png b/core/res/res/drawable/stat_sys_r_signal_3.png
new file mode 100644
index 0000000..92c09c8
--- /dev/null
+++ b/core/res/res/drawable/stat_sys_r_signal_3.png
Binary files differ
diff --git a/core/res/res/drawable/stat_sys_r_signal_4.png b/core/res/res/drawable/stat_sys_r_signal_4.png
new file mode 100644
index 0000000..f04fb11
--- /dev/null
+++ b/core/res/res/drawable/stat_sys_r_signal_4.png
Binary files differ
diff --git a/core/res/res/drawable/stat_sys_ringer_silent.png b/core/res/res/drawable/stat_sys_ringer_silent.png
new file mode 100644
index 0000000..d125ce5
--- /dev/null
+++ b/core/res/res/drawable/stat_sys_ringer_silent.png
Binary files differ
diff --git a/core/res/res/drawable/stat_sys_ringer_vibrate.png b/core/res/res/drawable/stat_sys_ringer_vibrate.png
new file mode 100644
index 0000000..665ca38
--- /dev/null
+++ b/core/res/res/drawable/stat_sys_ringer_vibrate.png
Binary files differ
diff --git a/core/res/res/drawable/stat_sys_signal_0.png b/core/res/res/drawable/stat_sys_signal_0.png
new file mode 100644
index 0000000..cb7b7b3
--- /dev/null
+++ b/core/res/res/drawable/stat_sys_signal_0.png
Binary files differ
diff --git a/core/res/res/drawable/stat_sys_signal_1.png b/core/res/res/drawable/stat_sys_signal_1.png
new file mode 100644
index 0000000..5376e92
--- /dev/null
+++ b/core/res/res/drawable/stat_sys_signal_1.png
Binary files differ
diff --git a/core/res/res/drawable/stat_sys_signal_2.png b/core/res/res/drawable/stat_sys_signal_2.png
new file mode 100644
index 0000000..fd54363
--- /dev/null
+++ b/core/res/res/drawable/stat_sys_signal_2.png
Binary files differ
diff --git a/core/res/res/drawable/stat_sys_signal_3.png b/core/res/res/drawable/stat_sys_signal_3.png
new file mode 100644
index 0000000..6c4873a
--- /dev/null
+++ b/core/res/res/drawable/stat_sys_signal_3.png
Binary files differ
diff --git a/core/res/res/drawable/stat_sys_signal_4.png b/core/res/res/drawable/stat_sys_signal_4.png
new file mode 100644
index 0000000..a3320cb
--- /dev/null
+++ b/core/res/res/drawable/stat_sys_signal_4.png
Binary files differ
diff --git a/core/res/res/drawable/stat_sys_signal_flightmode.png b/core/res/res/drawable/stat_sys_signal_flightmode.png
new file mode 100755
index 0000000..516ec2f
--- /dev/null
+++ b/core/res/res/drawable/stat_sys_signal_flightmode.png
Binary files differ
diff --git a/core/res/res/drawable/stat_sys_signal_null.png b/core/res/res/drawable/stat_sys_signal_null.png
new file mode 100644
index 0000000..5aa23f6
--- /dev/null
+++ b/core/res/res/drawable/stat_sys_signal_null.png
Binary files differ
diff --git a/core/res/res/drawable/stat_sys_speakerphone.png b/core/res/res/drawable/stat_sys_speakerphone.png
new file mode 100644
index 0000000..642dfd4
--- /dev/null
+++ b/core/res/res/drawable/stat_sys_speakerphone.png
Binary files differ
diff --git a/core/res/res/drawable/stat_sys_upload.xml b/core/res/res/drawable/stat_sys_upload.xml
new file mode 100644
index 0000000..a9d9609
--- /dev/null
+++ b/core/res/res/drawable/stat_sys_upload.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* //device/apps/common/res/drawable/status_icon_background.xml
+**
+** Copyright 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.
+*/
+-->
+<animation-list
+        xmlns:android="http://schemas.android.com/apk/res/android"
+        android:oneshot="false">
+    <item android:drawable="@drawable/stat_sys_upload_anim0" android:duration="200" />
+    <item android:drawable="@drawable/stat_sys_upload_anim1" android:duration="200" />
+    <item android:drawable="@drawable/stat_sys_upload_anim2" android:duration="200" />
+    <item android:drawable="@drawable/stat_sys_upload_anim3" android:duration="200" />
+    <item android:drawable="@drawable/stat_sys_upload_anim4" android:duration="200" />
+    <item android:drawable="@drawable/stat_sys_upload_anim5" android:duration="200" />
+</animation-list>
+
diff --git a/core/res/res/drawable/stat_sys_upload_anim0.png b/core/res/res/drawable/stat_sys_upload_anim0.png
new file mode 100755
index 0000000..b7a5978
--- /dev/null
+++ b/core/res/res/drawable/stat_sys_upload_anim0.png
Binary files differ
diff --git a/core/res/res/drawable/stat_sys_upload_anim1.png b/core/res/res/drawable/stat_sys_upload_anim1.png
new file mode 100755
index 0000000..a203e15
--- /dev/null
+++ b/core/res/res/drawable/stat_sys_upload_anim1.png
Binary files differ
diff --git a/core/res/res/drawable/stat_sys_upload_anim2.png b/core/res/res/drawable/stat_sys_upload_anim2.png
new file mode 100755
index 0000000..4af7630
--- /dev/null
+++ b/core/res/res/drawable/stat_sys_upload_anim2.png
Binary files differ
diff --git a/core/res/res/drawable/stat_sys_upload_anim3.png b/core/res/res/drawable/stat_sys_upload_anim3.png
new file mode 100755
index 0000000..1dd76b1
--- /dev/null
+++ b/core/res/res/drawable/stat_sys_upload_anim3.png
Binary files differ
diff --git a/core/res/res/drawable/stat_sys_upload_anim4.png b/core/res/res/drawable/stat_sys_upload_anim4.png
new file mode 100755
index 0000000..36c18bf
--- /dev/null
+++ b/core/res/res/drawable/stat_sys_upload_anim4.png
Binary files differ
diff --git a/core/res/res/drawable/stat_sys_upload_anim5.png b/core/res/res/drawable/stat_sys_upload_anim5.png
new file mode 100755
index 0000000..748331f
--- /dev/null
+++ b/core/res/res/drawable/stat_sys_upload_anim5.png
Binary files differ
diff --git a/core/res/res/drawable/stat_sys_warning.png b/core/res/res/drawable/stat_sys_warning.png
new file mode 100644
index 0000000..be00f47
--- /dev/null
+++ b/core/res/res/drawable/stat_sys_warning.png
Binary files differ
diff --git a/core/res/res/drawable/stat_sys_wifi_signal_0.png b/core/res/res/drawable/stat_sys_wifi_signal_0.png
new file mode 100644
index 0000000..8ee3421
--- /dev/null
+++ b/core/res/res/drawable/stat_sys_wifi_signal_0.png
Binary files differ
diff --git a/core/res/res/drawable/stat_sys_wifi_signal_1.png b/core/res/res/drawable/stat_sys_wifi_signal_1.png
new file mode 100644
index 0000000..184fa36
--- /dev/null
+++ b/core/res/res/drawable/stat_sys_wifi_signal_1.png
Binary files differ
diff --git a/core/res/res/drawable/stat_sys_wifi_signal_2.png b/core/res/res/drawable/stat_sys_wifi_signal_2.png
new file mode 100644
index 0000000..79935bb
--- /dev/null
+++ b/core/res/res/drawable/stat_sys_wifi_signal_2.png
Binary files differ
diff --git a/core/res/res/drawable/stat_sys_wifi_signal_3.png b/core/res/res/drawable/stat_sys_wifi_signal_3.png
new file mode 100644
index 0000000..d2099e6
--- /dev/null
+++ b/core/res/res/drawable/stat_sys_wifi_signal_3.png
Binary files differ
diff --git a/core/res/res/drawable/stat_sys_wifi_signal_4.png b/core/res/res/drawable/stat_sys_wifi_signal_4.png
new file mode 100644
index 0000000..2062aad
--- /dev/null
+++ b/core/res/res/drawable/stat_sys_wifi_signal_4.png
Binary files differ
diff --git a/core/res/res/drawable/status_bar_background.9.png b/core/res/res/drawable/status_bar_background.9.png
new file mode 100644
index 0000000..fd754a8
--- /dev/null
+++ b/core/res/res/drawable/status_bar_background.9.png
Binary files differ
diff --git a/core/res/res/drawable/status_bar_close_on.9.png b/core/res/res/drawable/status_bar_close_on.9.png
new file mode 100644
index 0000000..e91e4fa
--- /dev/null
+++ b/core/res/res/drawable/status_bar_close_on.9.png
Binary files differ
diff --git a/core/res/res/drawable/status_bar_divider_shadow.9.png b/core/res/res/drawable/status_bar_divider_shadow.9.png
new file mode 100644
index 0000000..ad58dbe
--- /dev/null
+++ b/core/res/res/drawable/status_bar_divider_shadow.9.png
Binary files differ
diff --git a/core/res/res/drawable/status_bar_item_app_background.xml b/core/res/res/drawable/status_bar_item_app_background.xml
new file mode 100644
index 0000000..4f6f605
--- /dev/null
+++ b/core/res/res/drawable/status_bar_item_app_background.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_pressed="true"
+    	android:drawable="@drawable/status_bar_item_background_pressed" />
+    <item android:state_focused="true" android:state_pressed="false"
+        android:drawable="@drawable/status_bar_item_background_focus" />
+    <item android:drawable="@drawable/status_bar_item_app_background_normal" />
+</selector>
diff --git a/core/res/res/drawable/status_bar_item_app_background_normal.9.png b/core/res/res/drawable/status_bar_item_app_background_normal.9.png
new file mode 100644
index 0000000..c079615
--- /dev/null
+++ b/core/res/res/drawable/status_bar_item_app_background_normal.9.png
Binary files differ
diff --git a/core/res/res/drawable/status_bar_item_background.xml b/core/res/res/drawable/status_bar_item_background.xml
new file mode 100644
index 0000000..088389b
--- /dev/null
+++ b/core/res/res/drawable/status_bar_item_background.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_pressed="true"
+    	android:drawable="@drawable/status_bar_item_background_pressed" />
+    <item android:state_focused="true" android:state_pressed="false"
+        android:drawable="@drawable/status_bar_item_background_focus" />
+    <item android:drawable="@drawable/status_bar_item_background_normal" />
+</selector>
diff --git a/core/res/res/drawable/status_bar_item_background_focus.9.png b/core/res/res/drawable/status_bar_item_background_focus.9.png
new file mode 100644
index 0000000..c3e24158
--- /dev/null
+++ b/core/res/res/drawable/status_bar_item_background_focus.9.png
Binary files differ
diff --git a/core/res/res/drawable/status_bar_item_background_normal.9.png b/core/res/res/drawable/status_bar_item_background_normal.9.png
new file mode 100644
index 0000000..6b76740
--- /dev/null
+++ b/core/res/res/drawable/status_bar_item_background_normal.9.png
Binary files differ
diff --git a/core/res/res/drawable/status_bar_item_background_pressed.9.png b/core/res/res/drawable/status_bar_item_background_pressed.9.png
new file mode 100644
index 0000000..02b4e9a
--- /dev/null
+++ b/core/res/res/drawable/status_bar_item_background_pressed.9.png
Binary files differ
diff --git a/core/res/res/drawable/status_icon_background.xml b/core/res/res/drawable/status_icon_background.xml
new file mode 100644
index 0000000..9846165
--- /dev/null
+++ b/core/res/res/drawable/status_icon_background.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* //device/apps/common/res/drawable/status_icon_background.xml
+**
+** Copyright 2007, 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.
+*/
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_selected="true" android:drawable="@drawable/icon_highlight_rectangle" />
+    <item android:drawable="@color/transparent" />
+</selector>
diff --git a/core/res/res/drawable/statusbar_background.png b/core/res/res/drawable/statusbar_background.png
new file mode 100644
index 0000000..945ad92
--- /dev/null
+++ b/core/res/res/drawable/statusbar_background.png
Binary files differ
diff --git a/core/res/res/drawable/submenu_arrow.xml b/core/res/res/drawable/submenu_arrow.xml
new file mode 100644
index 0000000..8480c1d
--- /dev/null
+++ b/core/res/res/drawable/submenu_arrow.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* //device/apps/common/res/drawable/ui_tab_icon.xml
+**
+** Copyright 2007, 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.
+*/
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:drawable="@android:drawable/submenu_arrow_nofocus" />
+</selector>
diff --git a/core/res/res/drawable/submenu_arrow_nofocus.png b/core/res/res/drawable/submenu_arrow_nofocus.png
new file mode 100644
index 0000000..cead09e
--- /dev/null
+++ b/core/res/res/drawable/submenu_arrow_nofocus.png
Binary files differ
diff --git a/core/res/res/drawable/sym_action_add.png b/core/res/res/drawable/sym_action_add.png
new file mode 100644
index 0000000..af637b3
--- /dev/null
+++ b/core/res/res/drawable/sym_action_add.png
Binary files differ
diff --git a/core/res/res/drawable/sym_action_call.png b/core/res/res/drawable/sym_action_call.png
new file mode 100644
index 0000000..a442758
--- /dev/null
+++ b/core/res/res/drawable/sym_action_call.png
Binary files differ
diff --git a/core/res/res/drawable/sym_action_chat.png b/core/res/res/drawable/sym_action_chat.png
new file mode 100644
index 0000000..9f6419e
--- /dev/null
+++ b/core/res/res/drawable/sym_action_chat.png
Binary files differ
diff --git a/core/res/res/drawable/sym_action_email.png b/core/res/res/drawable/sym_action_email.png
new file mode 100644
index 0000000..5fea417
--- /dev/null
+++ b/core/res/res/drawable/sym_action_email.png
Binary files differ
diff --git a/core/res/res/drawable/sym_call_incoming.png b/core/res/res/drawable/sym_call_incoming.png
new file mode 100644
index 0000000..652b882
--- /dev/null
+++ b/core/res/res/drawable/sym_call_incoming.png
Binary files differ
diff --git a/core/res/res/drawable/sym_call_missed.png b/core/res/res/drawable/sym_call_missed.png
new file mode 100644
index 0000000..ed859d0
--- /dev/null
+++ b/core/res/res/drawable/sym_call_missed.png
Binary files differ
diff --git a/core/res/res/drawable/sym_call_outgoing.png b/core/res/res/drawable/sym_call_outgoing.png
new file mode 100644
index 0000000..bdf675d
--- /dev/null
+++ b/core/res/res/drawable/sym_call_outgoing.png
Binary files differ
diff --git a/core/res/res/drawable/sym_contact_card.png b/core/res/res/drawable/sym_contact_card.png
new file mode 100644
index 0000000..023ea6f
--- /dev/null
+++ b/core/res/res/drawable/sym_contact_card.png
Binary files differ
diff --git a/core/res/res/drawable/sym_def_app_icon.png b/core/res/res/drawable/sym_def_app_icon.png
new file mode 100644
index 0000000..7502484
--- /dev/null
+++ b/core/res/res/drawable/sym_def_app_icon.png
Binary files differ
diff --git a/core/res/res/drawable/tab_bottom_left.xml b/core/res/res/drawable/tab_bottom_left.xml
new file mode 100644
index 0000000..5544906
--- /dev/null
+++ b/core/res/res/drawable/tab_bottom_left.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_pressed="true" android:drawable="@drawable/tab_press_bar_left"/>
+    <item android:state_focused="false" android:drawable="@drawable/tab_selected_bar_left"/>
+    <item android:state_focused="true" android:drawable="@drawable/tab_focus_bar_left"/>
+</selector>
diff --git a/core/res/res/drawable/tab_bottom_right.xml b/core/res/res/drawable/tab_bottom_right.xml
new file mode 100644
index 0000000..f7f5c2f
--- /dev/null
+++ b/core/res/res/drawable/tab_bottom_right.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_pressed="true" android:drawable="@drawable/tab_press_bar_right"/>
+    <item android:state_focused="false" android:state_pressed="false" android:drawable="@drawable/tab_selected_bar_right"/>
+    <item android:state_focused="true" android:state_pressed="false" android:drawable="@drawable/tab_focus_bar_right"/>
+</selector>
diff --git a/core/res/res/drawable/tab_focus.9.png b/core/res/res/drawable/tab_focus.9.png
new file mode 100755
index 0000000..2806da9
--- /dev/null
+++ b/core/res/res/drawable/tab_focus.9.png
Binary files differ
diff --git a/core/res/res/drawable/tab_focus_bar_left.9.png b/core/res/res/drawable/tab_focus_bar_left.9.png
new file mode 100755
index 0000000..21421cb
--- /dev/null
+++ b/core/res/res/drawable/tab_focus_bar_left.9.png
Binary files differ
diff --git a/core/res/res/drawable/tab_focus_bar_right.9.png b/core/res/res/drawable/tab_focus_bar_right.9.png
new file mode 100755
index 0000000..b6304d9
--- /dev/null
+++ b/core/res/res/drawable/tab_focus_bar_right.9.png
Binary files differ
diff --git a/core/res/res/drawable/tab_indicator.xml b/core/res/res/drawable/tab_indicator.xml
new file mode 100644
index 0000000..65df805
--- /dev/null
+++ b/core/res/res/drawable/tab_indicator.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <!-- Non focused states -->
+    <item android:state_focused="false" android:state_selected="false" android:state_pressed="false" android:drawable="@drawable/tab_unselected" />
+    <item android:state_focused="false" android:state_selected="true" android:state_pressed="false" android:drawable="@drawable/tab_selected" />
+
+    <!-- Focused states -->
+    <item android:state_focused="true" android:state_selected="false" android:state_pressed="false" android:drawable="@drawable/tab_focus" />
+    <item android:state_focused="true" android:state_selected="true" android:state_pressed="false" android:drawable="@drawable/tab_focus" />
+
+    <!-- Pressed -->
+    <item android:state_pressed="true" android:drawable="@drawable/tab_press" />
+</selector>
diff --git a/core/res/res/drawable/tab_press.9.png b/core/res/res/drawable/tab_press.9.png
new file mode 100755
index 0000000..3fb717c
--- /dev/null
+++ b/core/res/res/drawable/tab_press.9.png
Binary files differ
diff --git a/core/res/res/drawable/tab_press_bar_left.9.png b/core/res/res/drawable/tab_press_bar_left.9.png
new file mode 100755
index 0000000..95ef2d3
--- /dev/null
+++ b/core/res/res/drawable/tab_press_bar_left.9.png
Binary files differ
diff --git a/core/res/res/drawable/tab_press_bar_right.9.png b/core/res/res/drawable/tab_press_bar_right.9.png
new file mode 100755
index 0000000..7ae938b5
--- /dev/null
+++ b/core/res/res/drawable/tab_press_bar_right.9.png
Binary files differ
diff --git a/core/res/res/drawable/tab_selected.9.png b/core/res/res/drawable/tab_selected.9.png
new file mode 100644
index 0000000..f929d99
--- /dev/null
+++ b/core/res/res/drawable/tab_selected.9.png
Binary files differ
diff --git a/core/res/res/drawable/tab_selected_bar_left.9.png b/core/res/res/drawable/tab_selected_bar_left.9.png
new file mode 100755
index 0000000..58e2d35
--- /dev/null
+++ b/core/res/res/drawable/tab_selected_bar_left.9.png
Binary files differ
diff --git a/core/res/res/drawable/tab_selected_bar_right.9.png b/core/res/res/drawable/tab_selected_bar_right.9.png
new file mode 100755
index 0000000..0c9c8dd
--- /dev/null
+++ b/core/res/res/drawable/tab_selected_bar_right.9.png
Binary files differ
diff --git a/core/res/res/drawable/tab_unselected.9.png b/core/res/res/drawable/tab_unselected.9.png
new file mode 100644
index 0000000..9036c1d
--- /dev/null
+++ b/core/res/res/drawable/tab_unselected.9.png
Binary files differ
diff --git a/core/res/res/drawable/textfield_default.9.png b/core/res/res/drawable/textfield_default.9.png
new file mode 100644
index 0000000..cc78e6c
--- /dev/null
+++ b/core/res/res/drawable/textfield_default.9.png
Binary files differ
diff --git a/core/res/res/drawable/textfield_disabled.9.png b/core/res/res/drawable/textfield_disabled.9.png
new file mode 100644
index 0000000..9c77149
--- /dev/null
+++ b/core/res/res/drawable/textfield_disabled.9.png
Binary files differ
diff --git a/core/res/res/drawable/textfield_disabled_selected.9.png b/core/res/res/drawable/textfield_disabled_selected.9.png
new file mode 100644
index 0000000..6d47708
--- /dev/null
+++ b/core/res/res/drawable/textfield_disabled_selected.9.png
Binary files differ
diff --git a/core/res/res/drawable/textfield_pressed.9.png b/core/res/res/drawable/textfield_pressed.9.png
new file mode 100644
index 0000000..c909ad2
--- /dev/null
+++ b/core/res/res/drawable/textfield_pressed.9.png
Binary files differ
diff --git a/core/res/res/drawable/textfield_selected.9.png b/core/res/res/drawable/textfield_selected.9.png
new file mode 100644
index 0000000..0c1b446
--- /dev/null
+++ b/core/res/res/drawable/textfield_selected.9.png
Binary files differ
diff --git a/core/res/res/drawable/timepicker_down_btn.xml b/core/res/res/drawable/timepicker_down_btn.xml
new file mode 100644
index 0000000..61a252a
--- /dev/null
+++ b/core/res/res/drawable/timepicker_down_btn.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <item android:state_pressed="false" android:state_enabled="true"
+          android:state_focused="false" android:drawable="@drawable/timepicker_down_normal" />
+    <item android:state_pressed="true" android:state_enabled="true"
+          android:drawable="@drawable/timepicker_down_pressed" />
+    <item android:state_pressed="false" android:state_enabled="true"
+          android:state_focused="true" android:drawable="@drawable/timepicker_down_selected" />
+    <item android:state_pressed="false" android:state_enabled="false"
+          android:state_focused="false" android:drawable="@drawable/timepicker_down_disabled" />
+    <item android:state_pressed="false" android:state_enabled="false"
+          android:state_focused="true" android:drawable="@drawable/timepicker_down_disabled_focused" />
+
+</selector>
diff --git a/core/res/res/drawable/timepicker_down_disabled.9.png b/core/res/res/drawable/timepicker_down_disabled.9.png
new file mode 100755
index 0000000..7d8e0b9
--- /dev/null
+++ b/core/res/res/drawable/timepicker_down_disabled.9.png
Binary files differ
diff --git a/core/res/res/drawable/timepicker_down_disabled_focused.9.png b/core/res/res/drawable/timepicker_down_disabled_focused.9.png
new file mode 100755
index 0000000..6f2373e
--- /dev/null
+++ b/core/res/res/drawable/timepicker_down_disabled_focused.9.png
Binary files differ
diff --git a/core/res/res/drawable/timepicker_down_normal.9.png b/core/res/res/drawable/timepicker_down_normal.9.png
new file mode 100755
index 0000000..a946355
--- /dev/null
+++ b/core/res/res/drawable/timepicker_down_normal.9.png
Binary files differ
diff --git a/core/res/res/drawable/timepicker_down_pressed.9.png b/core/res/res/drawable/timepicker_down_pressed.9.png
new file mode 100755
index 0000000..fb4d817
--- /dev/null
+++ b/core/res/res/drawable/timepicker_down_pressed.9.png
Binary files differ
diff --git a/core/res/res/drawable/timepicker_down_selected.9.png b/core/res/res/drawable/timepicker_down_selected.9.png
new file mode 100755
index 0000000..1db8bc1
--- /dev/null
+++ b/core/res/res/drawable/timepicker_down_selected.9.png
Binary files differ
diff --git a/core/res/res/drawable/timepicker_input.xml b/core/res/res/drawable/timepicker_input.xml
new file mode 100644
index 0000000..b811d4e3
--- /dev/null
+++ b/core/res/res/drawable/timepicker_input.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <item android:state_pressed="false" android:state_enabled="true"
+          android:state_focused="false" android:drawable="@drawable/timepicker_input_normal" />
+    <item android:state_pressed="true" android:state_enabled="true"
+          android:drawable="@drawable/timepicker_input_pressed" />
+    <item android:state_pressed="false" android:state_enabled="true"
+          android:state_focused="true" android:drawable="@drawable/timepicker_input_selected" />
+    <item android:state_pressed="false" android:state_enabled="false"
+          android:state_focused="false" android:drawable="@drawable/timepicker_input_disabled" />
+    <item android:state_pressed="false" android:state_enabled="false"
+          android:state_focused="true" android:drawable="@drawable/timepicker_input_normal" />
+
+</selector>
diff --git a/core/res/res/drawable/timepicker_input_disabled.9.png b/core/res/res/drawable/timepicker_input_disabled.9.png
new file mode 100755
index 0000000..f73658e
--- /dev/null
+++ b/core/res/res/drawable/timepicker_input_disabled.9.png
Binary files differ
diff --git a/core/res/res/drawable/timepicker_input_normal.9.png b/core/res/res/drawable/timepicker_input_normal.9.png
new file mode 100755
index 0000000..8032ada
--- /dev/null
+++ b/core/res/res/drawable/timepicker_input_normal.9.png
Binary files differ
diff --git a/core/res/res/drawable/timepicker_input_pressed.9.png b/core/res/res/drawable/timepicker_input_pressed.9.png
new file mode 100755
index 0000000..30d8d5f
--- /dev/null
+++ b/core/res/res/drawable/timepicker_input_pressed.9.png
Binary files differ
diff --git a/core/res/res/drawable/timepicker_input_selected.9.png b/core/res/res/drawable/timepicker_input_selected.9.png
new file mode 100755
index 0000000..874f18f
--- /dev/null
+++ b/core/res/res/drawable/timepicker_input_selected.9.png
Binary files differ
diff --git a/core/res/res/drawable/timepicker_up_btn.xml b/core/res/res/drawable/timepicker_up_btn.xml
new file mode 100644
index 0000000..5428aee
--- /dev/null
+++ b/core/res/res/drawable/timepicker_up_btn.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <item android:state_pressed="false" android:state_enabled="true"
+          android:state_focused="false" android:drawable="@drawable/timepicker_up_normal" />
+    <item android:state_pressed="true" android:state_enabled="true"
+          android:drawable="@drawable/timepicker_up_pressed" />
+    <item android:state_pressed="false" android:state_enabled="true"
+          android:state_focused="true" android:drawable="@drawable/timepicker_up_selected" />
+    <item android:state_pressed="false" android:state_enabled="false"
+          android:state_focused="false" android:drawable="@drawable/timepicker_up_disabled" />
+    <item android:state_pressed="false" android:state_enabled="false"
+          android:state_focused="true" android:drawable="@drawable/timepicker_up_disabled_focused" />
+
+</selector>
diff --git a/core/res/res/drawable/timepicker_up_disabled.9.png b/core/res/res/drawable/timepicker_up_disabled.9.png
new file mode 100755
index 0000000..93d0db4
--- /dev/null
+++ b/core/res/res/drawable/timepicker_up_disabled.9.png
Binary files differ
diff --git a/core/res/res/drawable/timepicker_up_disabled_focused.9.png b/core/res/res/drawable/timepicker_up_disabled_focused.9.png
new file mode 100755
index 0000000..b5bf68d
--- /dev/null
+++ b/core/res/res/drawable/timepicker_up_disabled_focused.9.png
Binary files differ
diff --git a/core/res/res/drawable/timepicker_up_normal.9.png b/core/res/res/drawable/timepicker_up_normal.9.png
new file mode 100755
index 0000000..978251d
--- /dev/null
+++ b/core/res/res/drawable/timepicker_up_normal.9.png
Binary files differ
diff --git a/core/res/res/drawable/timepicker_up_pressed.9.png b/core/res/res/drawable/timepicker_up_pressed.9.png
new file mode 100755
index 0000000..924b03c
--- /dev/null
+++ b/core/res/res/drawable/timepicker_up_pressed.9.png
Binary files differ
diff --git a/core/res/res/drawable/timepicker_up_selected.9.png b/core/res/res/drawable/timepicker_up_selected.9.png
new file mode 100755
index 0000000..5c94d0d
--- /dev/null
+++ b/core/res/res/drawable/timepicker_up_selected.9.png
Binary files differ
diff --git a/core/res/res/drawable/title_bar.xml b/core/res/res/drawable/title_bar.xml
new file mode 100644
index 0000000..24402dc
--- /dev/null
+++ b/core/res/res/drawable/title_bar.xml
@@ -0,0 +1,20 @@
+<?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.
+-->
+
+<nine-patch xmlns:android="http://schemas.android.com/apk/res/android"
+    android:src="@drawable/activity_title_bar"
+    android:dither="true"
+/>
diff --git a/core/res/res/drawable/title_bar_shadow.9.png b/core/res/res/drawable/title_bar_shadow.9.png
new file mode 100644
index 0000000..08723665
--- /dev/null
+++ b/core/res/res/drawable/title_bar_shadow.9.png
Binary files differ
diff --git a/core/res/res/drawable/title_bar_tall.png b/core/res/res/drawable/title_bar_tall.png
new file mode 100644
index 0000000..cd565dc
--- /dev/null
+++ b/core/res/res/drawable/title_bar_tall.png
Binary files differ
diff --git a/core/res/res/drawable/toast_frame.9.png b/core/res/res/drawable/toast_frame.9.png
new file mode 100755
index 0000000..08c4f86
--- /dev/null
+++ b/core/res/res/drawable/toast_frame.9.png
Binary files differ
diff --git a/core/res/res/drawable/unknown_image.png b/core/res/res/drawable/unknown_image.png
new file mode 100644
index 0000000..b1c3e92
--- /dev/null
+++ b/core/res/res/drawable/unknown_image.png
Binary files differ
diff --git a/core/res/res/drawable/zoom_plate.9.png b/core/res/res/drawable/zoom_plate.9.png
new file mode 100644
index 0000000..c8c1a08
--- /dev/null
+++ b/core/res/res/drawable/zoom_plate.9.png
Binary files differ
diff --git a/core/res/res/drawable/zoom_ring_arrows.png b/core/res/res/drawable/zoom_ring_arrows.png
new file mode 100644
index 0000000..e443de7
--- /dev/null
+++ b/core/res/res/drawable/zoom_ring_arrows.png
Binary files differ
diff --git a/core/res/res/drawable/zoom_ring_overview_tab.9.png b/core/res/res/drawable/zoom_ring_overview_tab.9.png
new file mode 100644
index 0000000..d2658d8
--- /dev/null
+++ b/core/res/res/drawable/zoom_ring_overview_tab.9.png
Binary files differ
diff --git a/core/res/res/drawable/zoom_ring_thumb.png b/core/res/res/drawable/zoom_ring_thumb.png
new file mode 100644
index 0000000..1002724
--- /dev/null
+++ b/core/res/res/drawable/zoom_ring_thumb.png
Binary files differ
diff --git a/core/res/res/drawable/zoom_ring_thumb_minus.png b/core/res/res/drawable/zoom_ring_thumb_minus.png
new file mode 100644
index 0000000..faed674
--- /dev/null
+++ b/core/res/res/drawable/zoom_ring_thumb_minus.png
Binary files differ
diff --git a/core/res/res/drawable/zoom_ring_thumb_minus_arrow.png b/core/res/res/drawable/zoom_ring_thumb_minus_arrow.png
new file mode 100644
index 0000000..abe1b8a
--- /dev/null
+++ b/core/res/res/drawable/zoom_ring_thumb_minus_arrow.png
Binary files differ
diff --git a/core/res/res/drawable/zoom_ring_thumb_minus_arrow_rotatable.xml b/core/res/res/drawable/zoom_ring_thumb_minus_arrow_rotatable.xml
new file mode 100644
index 0000000..f2d495b
--- /dev/null
+++ b/core/res/res/drawable/zoom_ring_thumb_minus_arrow_rotatable.xml
@@ -0,0 +1,22 @@
+<?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.
+-->
+
+<rotate xmlns:android="http://schemas.android.com/apk/res/android"
+    android:pivotX="50%"
+    android:pivotY="50%"
+    android:fromDegrees="0"
+    android:toDegrees="360"
+    android:drawable="@drawable/zoom_ring_thumb_minus_arrow" />
diff --git a/core/res/res/drawable/zoom_ring_thumb_plus.png b/core/res/res/drawable/zoom_ring_thumb_plus.png
new file mode 100644
index 0000000..ba7aff2
--- /dev/null
+++ b/core/res/res/drawable/zoom_ring_thumb_plus.png
Binary files differ
diff --git a/core/res/res/drawable/zoom_ring_thumb_plus_arrow.png b/core/res/res/drawable/zoom_ring_thumb_plus_arrow.png
new file mode 100644
index 0000000..d01ccb4
--- /dev/null
+++ b/core/res/res/drawable/zoom_ring_thumb_plus_arrow.png
Binary files differ
diff --git a/core/res/res/drawable/zoom_ring_thumb_plus_arrow_rotatable.xml b/core/res/res/drawable/zoom_ring_thumb_plus_arrow_rotatable.xml
new file mode 100644
index 0000000..b77eaa4
--- /dev/null
+++ b/core/res/res/drawable/zoom_ring_thumb_plus_arrow_rotatable.xml
@@ -0,0 +1,22 @@
+<?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.
+-->
+
+<rotate xmlns:android="http://schemas.android.com/apk/res/android"
+    android:pivotX="50%"
+    android:pivotY="50%"
+    android:fromDegrees="0"
+    android:toDegrees="360"
+    android:drawable="@drawable/zoom_ring_thumb_plus_arrow" />
diff --git a/core/res/res/drawable/zoom_ring_track.png b/core/res/res/drawable/zoom_ring_track.png
new file mode 100644
index 0000000..1d5a44c
--- /dev/null
+++ b/core/res/res/drawable/zoom_ring_track.png
Binary files differ
diff --git a/core/res/res/drawable/zoom_ring_track_absolute.png b/core/res/res/drawable/zoom_ring_track_absolute.png
new file mode 100644
index 0000000..2b87699
--- /dev/null
+++ b/core/res/res/drawable/zoom_ring_track_absolute.png
Binary files differ
diff --git a/core/res/res/drawable/zoom_ring_trail.xml b/core/res/res/drawable/zoom_ring_trail.xml
new file mode 100644
index 0000000..08931ac
--- /dev/null
+++ b/core/res/res/drawable/zoom_ring_trail.xml
@@ -0,0 +1,37 @@
+<?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.
+-->
+
+<rotate xmlns:android="http://schemas.android.com/apk/res/android"
+    android:pivotX="50%"
+    android:pivotY="50%"
+    android:fromDegrees="0"
+    android:toDegrees="360">
+
+    <shape
+        android:shape="ring"
+        android:innerRadius="60dip"
+        android:thickness="14dip"
+        android:useLevel="true">
+
+        <gradient
+            android:type="sweep"
+            android:useLevel="true"
+            android:startColor="#1000ceff"
+            android:endColor="#ffadd252" />
+
+    </shape>
+
+ </rotate>
diff --git a/core/res/res/layout-land/icon_menu_layout.xml b/core/res/res/layout-land/icon_menu_layout.xml
new file mode 100644
index 0000000..d1b25d9
--- /dev/null
+++ b/core/res/res/layout-land/icon_menu_layout.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2007 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.
+-->
+
+<com.android.internal.view.menu.IconMenuView xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+android:id/icon_menu" 
+    android:layout_width="fill_parent"
+    android:layout_height="wrap_content"
+    android:rowHeight="65dip"
+    android:maxItems="6"
+    android:maxRows="2"
+    android:maxItemsPerRow="6" />
diff --git a/core/res/res/layout-port/icon_menu_layout.xml b/core/res/res/layout-port/icon_menu_layout.xml
new file mode 100644
index 0000000..08edfcc
--- /dev/null
+++ b/core/res/res/layout-port/icon_menu_layout.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2007 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.
+-->
+
+<com.android.internal.view.menu.IconMenuView xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+android:id/icon_menu" 
+    android:layout_width="fill_parent"
+    android:layout_height="wrap_content"
+    android:rowHeight="65dip"
+    android:maxItems="6"
+    android:maxRows="3"
+    android:maxItemsPerRow="3" />
diff --git a/core/res/res/layout/activity_list.xml b/core/res/res/layout/activity_list.xml
new file mode 100644
index 0000000..2967f0f
--- /dev/null
+++ b/core/res/res/layout/activity_list.xml
@@ -0,0 +1,38 @@
+<?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.
+-->
+
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="fill_parent"
+    android:layout_height="fill_parent"
+    >
+
+    <ListView
+        android:id="@android:id/list"
+        android:layout_width="fill_parent"
+        android:layout_height="fill_parent"
+        />
+
+    <TextView
+        android:id="@android:id/empty"
+        android:layout_width="fill_parent"
+        android:layout_height="fill_parent"
+        android:gravity="center"
+        android:text="@string/activity_list_empty"
+        android:visibility="gone"
+        android:textAppearance="?android:attr/textAppearanceMedium"
+        />
+
+</FrameLayout>
diff --git a/core/res/res/layout/activity_list_item.xml b/core/res/res/layout/activity_list_item.xml
new file mode 100644
index 0000000..7a2a0e2
--- /dev/null
+++ b/core/res/res/layout/activity_list_item.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* //device/apps/common/res/any/layout/resolve_list_item.xml
+**
+** Copyright 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.
+*/
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="fill_parent"
+    android:layout_height="wrap_content"
+    android:paddingTop="1dip"
+    android:paddingBottom="1dip"
+    android:paddingLeft="6dip"
+    android:paddingRight="6dip">
+
+    <ImageView android:id="@+id/icon"
+        android:layout_width="24dip"
+        android:layout_height="24dip"/>
+
+    <TextView android:id="@android:id/text1"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center_horizontal"
+        android:paddingLeft="6dip" />
+</LinearLayout>
+
diff --git a/core/res/res/layout/activity_list_item_2.xml b/core/res/res/layout/activity_list_item_2.xml
new file mode 100644
index 0000000..78eca02
--- /dev/null
+++ b/core/res/res/layout/activity_list_item_2.xml
@@ -0,0 +1,25 @@
+<?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.
+-->
+
+<TextView xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="fill_parent"
+    android:layout_height="wrap_content"
+    android:minHeight="?android:attr/listPreferredItemHeight"
+    android:textAppearance="?android:attr/textAppearanceLarge"
+    android:gravity="center_vertical"
+    android:drawablePadding="14dip"
+    android:paddingLeft="15dip"
+    android:paddingRight="15dip" />
diff --git a/core/res/res/layout/alert_dialog.xml b/core/res/res/layout/alert_dialog.xml
new file mode 100644
index 0000000..cf2de05
--- /dev/null
+++ b/core/res/res/layout/alert_dialog.xml
@@ -0,0 +1,147 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* //device/apps/common/res/layout/alert_dialog.xml
+**
+** Copyright 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.
+*/
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/parentPanel"
+    android:layout_width="fill_parent"
+    android:layout_height="wrap_content"
+    android:orientation="vertical"
+    android:paddingTop="9dip"
+    android:paddingBottom="3dip"
+    android:paddingLeft="3dip"
+    android:paddingRight="1dip"
+    >
+
+    <LinearLayout android:id="@+id/topPanel"
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content"
+        android:minHeight="54dip"
+        android:orientation="vertical">
+        <LinearLayout android:id="@+id/title_template"
+            android:layout_width="fill_parent"
+            android:layout_height="wrap_content"
+            android:orientation="horizontal"
+            android:gravity="center_vertical"
+            android:layout_marginTop="6dip"
+            android:layout_marginBottom="9dip"
+            android:layout_marginLeft="10dip"
+            android:layout_marginRight="10dip">
+            <ImageView android:id="@+id/icon"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_gravity="top"
+                android:paddingTop="6dip"
+                android:paddingRight="10dip"
+                android:src="@drawable/ic_dialog_info" />
+            <com.android.internal.widget.DialogTitle android:id="@+id/alertTitle" 
+                style="?android:attr/textAppearanceLarge"
+                android:singleLine="true"
+                android:ellipsize="end"
+                android:layout_width="fill_parent" 
+                android:layout_height="wrap_content" />
+        </LinearLayout>
+        <ImageView android:id="@+id/titleDivider"
+            android:layout_width="fill_parent"
+            android:layout_height="1dip"
+            android:visibility="gone"
+            android:scaleType="fitXY"
+            android:gravity="fill_horizontal"
+            android:src="@android:drawable/dialog_divider_horizontal_light"
+            android:layout_marginLeft="10dip"
+            android:layout_marginRight="10dip"/>
+        <!-- If the client uses a customTitle, it will be added here. -->
+    </LinearLayout>
+
+    <LinearLayout android:id="@+id/contentPanel"
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content"
+        android:layout_weight="1"
+        android:orientation="vertical">
+        <ScrollView android:id="@+id/scrollView"
+            android:layout_width="fill_parent"
+            android:layout_height="wrap_content"
+            android:paddingTop="2dip"
+            android:paddingBottom="12dip"
+            android:paddingLeft="14dip"
+            android:paddingRight="10dip">
+            <TextView android:id="@+id/message"
+                style="?android:attr/textAppearanceMedium"
+                android:layout_width="fill_parent"
+                android:layout_height="wrap_content"
+                android:padding="5dip" />
+        </ScrollView>
+    </LinearLayout>
+        
+    <FrameLayout android:id="@+id/customPanel"
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content"
+        android:layout_weight="1">
+        <FrameLayout android:id="@+android:id/custom"
+            android:layout_width="fill_parent"
+            android:layout_height="wrap_content"
+            android:paddingTop="5dip"
+            android:paddingBottom="5dip" />
+    </FrameLayout>
+        
+    <LinearLayout android:id="@+id/buttonPanel"
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content"
+        android:minHeight="54dip"
+        android:orientation="vertical" >     
+        <LinearLayout 
+            android:layout_width="fill_parent"
+            android:layout_height="wrap_content"
+            android:orientation="horizontal"
+            android:paddingTop="4dip"
+            android:paddingLeft="2dip"
+            android:paddingRight="2dip" >
+            <LinearLayout android:id="@+id/leftSpacer"
+                android:layout_weight="0.25"
+                android:layout_width="0dip"
+                android:layout_height="wrap_content"
+                android:orientation="horizontal"
+                android:visibility="gone" />
+            <Button android:id="@+id/button1"
+                android:layout_width="0dip"
+                android:layout_gravity="left"
+                android:layout_weight="1"
+                android:maxLines="2"
+                android:layout_height="wrap_content" />
+            <Button android:id="@+id/button3"
+                android:layout_width="0dip"
+                android:layout_gravity="center_horizontal"
+                android:layout_weight="1"
+                android:maxLines="2"
+                android:layout_height="wrap_content" />
+            <Button android:id="@+id/button2"
+                android:layout_width="0dip"
+                android:layout_gravity="right"
+                android:layout_weight="1"
+                android:maxLines="2"
+                android:layout_height="wrap_content" />
+            <LinearLayout android:id="@+id/rightSpacer"
+                android:layout_width="0dip"
+                android:layout_weight="0.25"
+                android:layout_height="wrap_content"
+                android:orientation="horizontal"
+                android:visibility="gone" />
+        </LinearLayout>
+     </LinearLayout>
+</LinearLayout>
diff --git a/core/res/res/layout/alert_dialog_progress.xml b/core/res/res/layout/alert_dialog_progress.xml
new file mode 100644
index 0000000..9279eff
--- /dev/null
+++ b/core/res/res/layout/alert_dialog_progress.xml
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2007 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.
+-->
+
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="wrap_content" android:layout_height="fill_parent">
+        <ProgressBar android:id="@+id/progress"
+            style="?android:attr/progressBarStyleHorizontal"
+            android:layout_width="fill_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginTop="12dip"
+            android:layout_marginBottom="12dip"
+            android:layout_marginLeft="10dip"
+            android:layout_marginRight="10dip"
+            android:layout_centerHorizontal="true" />
+        <TextView
+            android:id="@+id/progress_percent"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:paddingBottom="12dip"
+            android:layout_marginLeft="15dip"
+            android:layout_marginRight="10dip"
+            android:layout_alignParentLeft="true"
+            android:layout_below="@id/progress"
+        />
+        <TextView
+            android:id="@+id/progress_number"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:paddingBottom="12dip"
+            android:layout_marginLeft="10dip"
+            android:layout_marginRight="15dip"
+            android:layout_alignParentRight="true"
+            android:layout_below="@id/progress"
+        />
+</RelativeLayout>
diff --git a/core/res/res/layout/alert_dialog_simple_text.xml b/core/res/res/layout/alert_dialog_simple_text.xml
new file mode 100644
index 0000000..ab82be7
--- /dev/null
+++ b/core/res/res/layout/alert_dialog_simple_text.xml
@@ -0,0 +1,26 @@
+<?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.
+-->
+
+<!-- This layout can be set as the AlertDialog's view to display vertically centered text. -->
+<TextView xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@android:id/text1"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content"
+    android:textAppearance="?android:attr/textAppearanceMedium"
+    android:padding="10dip"
+    />
+
+
diff --git a/core/res/res/layout/always_use_checkbox.xml b/core/res/res/layout/always_use_checkbox.xml
new file mode 100644
index 0000000..20fc403
--- /dev/null
+++ b/core/res/res/layout/always_use_checkbox.xml
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<!-- Check box that is displayed in the activity resolver UI for the user
+     to make their selection the preferred activity. -->
+<RelativeLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="fill_parent"
+    android:layout_height="wrap_content"
+    android:paddingLeft="14dip"
+    android:paddingRight="15dip">
+    
+    <CheckBox
+        android:id="@+id/alwaysUse"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_alignParentLeft="true"
+        android:focusable="true"
+        android:clickable="true" />
+    
+    <TextView 
+        android:id="@+id/clearDefaultHint"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:textAppearance="?android:attr/textAppearanceSmall"
+        android:paddingLeft="36dip"
+        android:visibility="gone"
+        android:layout_below="@id/alwaysUse"
+        android:text="@string/clearDefaultHintMsg" />
+</RelativeLayout>
diff --git a/core/res/res/layout/app_permission_item.xml b/core/res/res/layout/app_permission_item.xml
new file mode 100644
index 0000000..8db4dd7
--- /dev/null
+++ b/core/res/res/layout/app_permission_item.xml
@@ -0,0 +1,55 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<!--
+  Defines the layout of a single permission item.
+  Contains the group name and a list of permission labels under the group.
+-->
+
+<RelativeLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="fill_parent"
+    android:layout_height="wrap_content">
+
+    <ImageView
+        android:id="@+id/perm_icon"
+        android:layout_width="30dip"
+        android:layout_height="30dip"
+        android:layout_alignParentLeft="true" 
+        android:scaleType="fitCenter" />
+
+
+    <TextView
+        android:id="@+id/permission_group"
+        android:textAppearance="?android:attr/textAppearanceMedium"
+        android:textStyle="bold"
+        android:paddingLeft="6dip"
+        android:layout_toRightOf="@id/perm_icon"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content" />
+
+    <TextView
+        android:id="@+id/permission_list"
+        android:textAppearance="?android:attr/textAppearanceSmall"
+        android:layout_marginTop="-4dip"
+        android:paddingBottom="8dip"
+        android:paddingLeft="6dip"
+        android:layout_below="@id/permission_group"
+        android:layout_toRightOf="@id/perm_icon"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content" />
+
+</RelativeLayout>
diff --git a/core/res/res/layout/app_perms_summary.xml b/core/res/res/layout/app_perms_summary.xml
new file mode 100755
index 0000000..713c179
--- /dev/null
+++ b/core/res/res/layout/app_perms_summary.xml
@@ -0,0 +1,102 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<!-- Describes permission item consisting of a group name and the list of permisisons under the group -->
+
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="fill_parent"
+    android:layout_height="fill_parent"
+    android:orientation="vertical">
+
+    <TextView
+        android:id="@+id/no_permissions"
+        android:text="@string/no_permissions"
+        android:textAppearance="?android:attr/textAppearanceMedium"
+        android:paddingLeft="16dip"
+        android:paddingRight="12dip"
+        android:visibility="gone"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content" />
+
+    <!-- List view containing list of dangerous permissions categorized by groups. -->
+    <LinearLayout
+        android:id="@+id/dangerous_perms_list"
+        android:orientation="vertical"
+        android:layout_width="fill_parent"
+        android:paddingLeft="16dip"
+        android:paddingRight="12dip"
+        android:layout_height="wrap_content" />
+
+    <!-- Clickable area letting user display additional permissions. -->
+    <LinearLayout
+        android:id="@+id/show_more"
+        android:orientation="vertical"
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content"
+        android:visibility="gone"
+        android:layout_marginTop="12dip"
+        android:layout_marginBottom="16dip">
+
+        <View
+            android:layout_width="fill_parent"
+            android:layout_height="1dip"
+            android:background="@android:drawable/divider_horizontal_dark" />
+
+        <LinearLayout
+            android:orientation="horizontal"
+            android:layout_width="fill_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginTop="16dip"
+            android:layout_marginBottom="12dip"
+            android:layout_marginLeft="16dip"
+            android:duplicateParentState="true">
+
+            <ImageView
+                android:id="@+id/show_more_icon"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content" />
+
+            <TextView
+                android:id="@+id/show_more_text"
+                android:textAppearance="?android:attr/textAppearanceMedium"
+                android:duplicateParentState="true"
+                android:layout_alignTop="@id/show_more_icon"
+                android:layout_gravity="center_vertical"
+                android:paddingLeft="6dip"
+                android:layout_width="fill_parent"
+                android:layout_height="wrap_content" />
+
+        </LinearLayout>
+
+        <View
+            android:layout_width="fill_parent"
+            android:layout_height="1dip"
+            android:background="@android:drawable/divider_horizontal_dark" />
+
+    </LinearLayout>
+
+    <!-- List view containing list of permissions that aren't dangerous. -->
+    <LinearLayout
+        android:id="@+id/non_dangerous_perms_list"
+        android:orientation="vertical"
+        android:paddingLeft="16dip"
+        android:paddingRight="12dip"
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content" />
+
+</LinearLayout>
+
diff --git a/core/res/res/layout/auto_complete_list.xml b/core/res/res/layout/auto_complete_list.xml
new file mode 100644
index 0000000..addda11
--- /dev/null
+++ b/core/res/res/layout/auto_complete_list.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 
+    android:id="@+id/content"
+    android:orientation="vertical"
+    android:layout_width="fill_parent" 
+    android:layout_height="wrap_content"
+    android:background="@android:drawable/edit_text"
+    android:divider="@android:drawable/divider_horizontal_textfield"
+    android:addStatesFromChildren="true">
+
+    <LinearLayout android:id="@+id/container"
+        android:orientation="vertical"
+        android:layout_width="fill_parent" 
+        android:layout_height="wrap_content"
+        android:paddingRight="0dip"
+    />
+
+    <AutoCompleteTextView android:id="@+id/edit"
+        android:completionThreshold="1"
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content"
+        android:singleLine="true"
+        android:layout_gravity="center_vertical"
+    />   
+</LinearLayout>
diff --git a/core/res/res/layout/battery_low.xml b/core/res/res/layout/battery_low.xml
new file mode 100644
index 0000000..116eae7
--- /dev/null
+++ b/core/res/res/layout/battery_low.xml
@@ -0,0 +1,57 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* //device/apps/common/res/layout/keyguard.xml
+**
+** Copyright 2007, 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.
+*/
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/padding"
+    android:orientation="vertical"
+    android:gravity="center"
+    >
+
+    <TextView android:id="@+id/subtitle"
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content"
+        android:textSize="18dp"
+        android:paddingLeft="19dp"
+        android:textColor="#ffffffff"
+        android:gravity="left"
+        android:text="@string/battery_low_subtitle"
+        />
+    
+    <TextView android:id="@+id/level_percent"
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content"
+        android:textSize="18dp"
+        android:textColor="#ffffffff"
+        android:gravity="left"
+        android:paddingBottom="10px"
+        android:paddingLeft="19dp"
+        />
+
+    <ImageView android:id="@+id/image"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:paddingBottom="15px"
+        android:src="@drawable/battery_low_battery"
+        android:paddingTop="10px"
+        />
+
+</LinearLayout>
+
+
diff --git a/core/res/res/layout/battery_status.xml b/core/res/res/layout/battery_status.xml
new file mode 100644
index 0000000..8b9828c
--- /dev/null
+++ b/core/res/res/layout/battery_status.xml
@@ -0,0 +1,81 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/frame"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content"
+    android:orientation="vertical"
+    android:gravity="center_horizontal"
+    >
+    
+    <FrameLayout
+        android:layout_width="141px" 
+        android:layout_height="184px"
+        android:background="@drawable/battery_charge_background"
+        android:paddingTop="25px"
+        android:paddingLeft="1px"
+        >
+
+        <LinearLayout
+            android:id="@+id/meter"
+            android:layout_width="fill_parent"
+            android:layout_height="fill_parent"
+            android:orientation="vertical"
+            >
+
+            <ImageView
+                android:layout_width="fill_parent"
+                android:layout_height="15dip"
+                />
+            <ImageView
+                android:id="@+id/spacer"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                />
+            <ImageView
+                android:id="@+id/level"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+            />
+        
+        </LinearLayout>
+
+        <TextView android:id="@+id/level_percent"
+            android:layout_width="fill_parent"
+            android:layout_height="fill_parent"
+            android:textStyle="bold"
+            android:textSize="48dp"
+            android:textColor="#ffffffff"
+            android:gravity="center"
+            />
+    </FrameLayout>
+
+    <TextView android:id="@+id/status"
+        android:paddingTop="35dp"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:singleLine="true"
+        android:textStyle="bold"
+        android:textSize="30dp"
+        android:textColor="#ffffffff"
+        android:gravity="center_horizontal"
+        android:text="@string/battery_status_charging"
+        />
+
+</LinearLayout>
+
+
diff --git a/core/res/res/layout/browser_link_context_header.xml b/core/res/res/layout/browser_link_context_header.xml
new file mode 100644
index 0000000..b09ee1f
--- /dev/null
+++ b/core/res/res/layout/browser_link_context_header.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<TextView xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/title"
+    android:textAppearance="?android:attr/textAppearanceLarge"
+    android:textColor="@color/white"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content"
+    android:maxLines="2"
+    android:paddingLeft="10dip"
+    android:paddingRight="10dip"
+    />
diff --git a/core/res/res/layout/character_picker.xml b/core/res/res/layout/character_picker.xml
new file mode 100644
index 0000000..bb4955a
--- /dev/null
+++ b/core/res/res/layout/character_picker.xml
@@ -0,0 +1,49 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:orientation="vertical"
+    android:layout_width="304dp"
+    android:layout_height="fill_parent">
+
+    <GridView
+        android:id="@+id/characterPicker"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:padding="12dp"
+        android:verticalSpacing="8dp"
+        android:horizontalSpacing="8dp"
+        android:stretchMode="spacingWidth"
+        android:gravity="left"
+        android:drawSelectorOnTop="false"
+        android:listSelector="@drawable/grid_selector_background"
+        android:numColumns="4"
+        android:columnWidth="64dp"
+        android:fadingEdge="none"
+        android:layout_gravity="center_horizontal"
+    />
+
+    <Button
+        android:id="@+id/cancel"
+        android:text="@string/cancel"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:paddingLeft="50dp"
+        android:paddingRight="50dp"
+        android:gravity="center"
+        android:layout_gravity="center_horizontal"
+    />
+</LinearLayout>
diff --git a/core/res/res/layout/character_picker_button.xml b/core/res/res/layout/character_picker_button.xml
new file mode 100644
index 0000000..40078fe
--- /dev/null
+++ b/core/res/res/layout/character_picker_button.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<Button xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="fill_parent"
+    android:layout_height="wrap_content"
+    android:clickable="false"
+    android:focusable="false"
+    android:textAppearance="?android:attr/textAppearanceLargeInverse"
+    android:textColor="#FF000000"
+/>
+
diff --git a/core/res/res/layout/date_picker.xml b/core/res/res/layout/date_picker.xml
new file mode 100644
index 0000000..0760cc0
--- /dev/null
+++ b/core/res/res/layout/date_picker.xml
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+**
+** Copyright 2007, 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.
+*/
+-->
+
+<!-- Layout of date picker-->
+
+<!-- Warning: everything within the parent is removed and re-ordered depending
+     on the date format selected by the user. -->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/parent"
+    android:orientation="horizontal"
+    android:layout_gravity="center_horizontal"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content">
+
+    <!-- Month -->
+    <com.android.internal.widget.NumberPicker
+        android:id="@+id/month"
+        android:layout_width="80dip"
+        android:layout_height="wrap_content"
+        android:layout_marginLeft="1dip"
+        android:layout_marginRight="1dip"
+        android:focusable="true"
+        android:focusableInTouchMode="true"
+        />
+
+    <!-- Day -->
+    <com.android.internal.widget.NumberPicker
+        android:id="@+id/day"
+        android:layout_width="80dip"
+        android:layout_height="wrap_content"
+        android:layout_marginLeft="1dip"
+        android:layout_marginRight="1dip"
+        android:focusable="true"
+        android:focusableInTouchMode="true"
+        />
+
+    <!-- Year -->
+    <com.android.internal.widget.NumberPicker
+        android:id="@+id/year"
+        android:layout_width="95dip"
+        android:layout_height="wrap_content"
+        android:layout_marginLeft="1dip"
+        android:layout_marginRight="1dip"
+        android:focusable="true"
+        android:focusableInTouchMode="true"
+        />
+</LinearLayout>
diff --git a/core/res/res/layout/date_picker_dialog.xml b/core/res/res/layout/date_picker_dialog.xml
new file mode 100644
index 0000000..949c8a3
--- /dev/null
+++ b/core/res/res/layout/date_picker_dialog.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+**
+** Copyright 2007, 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.
+*/
+-->
+
+<DatePicker xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/datePicker"
+    android:padding="5dip"
+    android:layout_gravity="center_horizontal"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content"/>
diff --git a/core/res/res/layout/dialog_custom_title.xml b/core/res/res/layout/dialog_custom_title.xml
new file mode 100644
index 0000000..68578f5
--- /dev/null
+++ b/core/res/res/layout/dialog_custom_title.xml
@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<!--
+This is an custom layout for a dialog.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 
+    android:orientation="vertical" 
+    android:fitsSystemWindows="true">
+    <FrameLayout android:id="@android:id/title_container" 
+        android:layout_width="fill_parent" 
+        android:layout_height="24dip"
+        android:layout_weight="0"
+        style="?android:attr/windowTitleBackgroundStyle">
+    </FrameLayout>
+    <FrameLayout
+        android:layout_width="fill_parent" android:layout_height="wrap_content"
+        android:layout_weight="1"
+        android:orientation="vertical"
+        android:foreground="?android:attr/windowContentOverlay">
+        <FrameLayout android:id="@android:id/content"
+            android:layout_width="fill_parent"
+            android:layout_height="fill_parent"
+            android:paddingTop="6dip"
+            android:paddingBottom="10dip"
+            android:paddingLeft="10dip"
+            android:paddingRight="10dip" />
+    </FrameLayout>
+</LinearLayout>
+
diff --git a/core/res/res/layout/dialog_title.xml b/core/res/res/layout/dialog_title.xml
new file mode 100644
index 0000000..8cfc716
--- /dev/null
+++ b/core/res/res/layout/dialog_title.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* //device/apps/common/assets/res/layout/dialog_title.xml
+**
+** Copyright 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.
+*/
+
+This is an optimized layout for a screen, with the minimum set of features
+enabled.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 
+    android:orientation="vertical" 
+    android:fitsSystemWindows="true">
+    <TextView android:id="@android:id/title" style="?android:attr/windowTitleStyle"
+        android:layout_width="fill_parent" 
+        android:layout_height="wrap_content"
+        android:minHeight="53dip"
+        android:paddingTop="9dip"
+        android:paddingBottom="9dip"
+        android:paddingLeft="10dip"
+        android:paddingRight="10dip" />
+    <FrameLayout
+        android:layout_width="fill_parent" android:layout_height="wrap_content"
+        android:layout_weight="1"
+        android:orientation="vertical"
+        android:foreground="?android:attr/windowContentOverlay">
+        <FrameLayout android:id="@android:id/content"
+            android:layout_width="fill_parent"
+            android:layout_height="fill_parent" />
+    </FrameLayout>
+</LinearLayout>
+
diff --git a/core/res/res/layout/dialog_title_icons.xml b/core/res/res/layout/dialog_title_icons.xml
new file mode 100644
index 0000000..7c3f274
--- /dev/null
+++ b/core/res/res/layout/dialog_title_icons.xml
@@ -0,0 +1,69 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<!--
+This is an optimized layout for a screen, with the minimum set of features
+enabled.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 
+    android:orientation="vertical" 
+    android:fitsSystemWindows="true">
+    
+    <LinearLayout android:id="@+id/title_container"
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content"
+        android:orientation="horizontal"
+        android:gravity="center_vertical"
+        android:minHeight="53dip"
+        android:paddingTop="6dip"
+        android:paddingBottom="9dip"
+        android:paddingLeft="10dip"
+        android:paddingRight="10dip">
+        <ImageView android:id="@+id/left_icon"
+            android:layout_width="32dip"
+            android:layout_height="32dip"
+            android:layout_marginTop="6dip"
+            android:layout_gravity="top"
+            android:scaleType="fitCenter" />
+        <TextView android:id="@android:id/title"
+        	style="?android:attr/windowTitleStyle"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_weight="0"
+            android:paddingTop="2dip"
+            android:paddingBottom="1dip"
+            android:paddingLeft="14dip"
+            android:paddingRight="14dip" />
+        <ImageView android:id="@+id/right_icon"
+            android:layout_width="32dip"
+            android:layout_height="32dip"
+            android:layout_marginTop="6dip"
+            android:layout_gravity="top"
+            android:scaleType="fitCenter" />
+    </LinearLayout>
+    
+    <FrameLayout
+        android:layout_width="fill_parent" android:layout_height="wrap_content"
+        android:layout_weight="1"
+        android:orientation="vertical"
+        android:foreground="?android:attr/windowContentOverlay">
+        <FrameLayout android:id="@android:id/content"
+            android:layout_width="fill_parent"
+            android:layout_height="fill_parent" />
+    </FrameLayout>
+</LinearLayout>
+
diff --git a/core/res/res/layout/expandable_list_content.xml b/core/res/res/layout/expandable_list_content.xml
new file mode 100644
index 0000000..05d74a6
--- /dev/null
+++ b/core/res/res/layout/expandable_list_content.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* //device/apps/common/assets/res/layout/expandable_list_content.xml
+**
+** Copyright 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.
+*/
+-->
+<ExpandableListView xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@android:id/list"
+    android:layout_width="fill_parent" 
+    android:layout_height="fill_parent"
+    android:drawSelectorOnTop="false" />
diff --git a/core/res/res/layout/expanded_menu_layout.xml b/core/res/res/layout/expanded_menu_layout.xml
new file mode 100644
index 0000000..5d98773
--- /dev/null
+++ b/core/res/res/layout/expanded_menu_layout.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2007 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.
+-->
+
+<com.android.internal.view.menu.ExpandedMenuView xmlns:android="http://schemas.android.com/apk/res/android"
+	android:id="@+android:id/expanded_menu" 
+	android:layout_width="296dip"
+	android:layout_height="wrap_content" />
diff --git a/core/res/res/layout/global_actions_item.xml b/core/res/res/layout/global_actions_item.xml
new file mode 100644
index 0000000..63bb0f4
--- /dev/null
+++ b/core/res/res/layout/global_actions_item.xml
@@ -0,0 +1,61 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="fill_parent"
+    android:layout_height="?android:attr/listPreferredItemHeight"
+
+    android:paddingLeft="11dip"
+    android:paddingTop="6dip"
+    android:paddingBottom="6dip"
+    >
+
+    <ImageView android:id="@+id/icon"
+        android:layout_width="wrap_content"
+        android:layout_height="fill_parent"
+        android:layout_alignParentTop="true"
+        android:layout_alignParentBottom="true"
+        android:layout_marginRight="9dip"
+            />
+
+
+    <TextView android:id="@+id/status"
+        android:layout_width="fill_parent"
+        android:layout_height="26dip"
+
+        android:layout_toRightOf="@id/icon"
+        android:layout_alignParentBottom="true"
+        android:layout_alignParentRight="true"
+
+        android:textAppearance="?android:attr/textAppearanceSmallInverse"
+        />
+
+
+    <TextView android:id="@+id/message"
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content"
+
+        android:layout_toRightOf="@id/icon"
+        android:layout_alignParentRight="true"
+        android:layout_alignParentTop="true"
+        android:layout_above="@id/status"
+        android:layout_alignWithParentIfMissing="true"
+        android:gravity="center_vertical"
+        android:textAppearance="?android:attr/textAppearanceLargeInverse"
+        />
+
+
+</RelativeLayout>
diff --git a/core/res/res/layout/google_web_content_helper_layout.xml b/core/res/res/layout/google_web_content_helper_layout.xml
new file mode 100644
index 0000000..40f84bf
--- /dev/null
+++ b/core/res/res/layout/google_web_content_helper_layout.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+        android:foregroundGravity="center"
+        android:measureAllChildren="false">
+        
+    <!-- Include the indeterminate progress dialog's layout. -->
+    <include
+            android:id="@+id/progressContainer"
+            layout="@android:layout/progress_dialog" />
+            
+    <WebView
+            android:id="@+id/web"
+            android:layout_width="fill_parent"
+            android:layout_height="fill_parent"
+            android:visibility="invisible"
+            />
+    
+    <TextView
+            android:id="@+id/text"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center"
+            android:visibility="gone"
+            android:padding="10dip"
+            />
+    
+</FrameLayout> 
diff --git a/core/res/res/layout/icon_menu_item_layout.xml b/core/res/res/layout/icon_menu_item_layout.xml
new file mode 100644
index 0000000..c6d9496
--- /dev/null
+++ b/core/res/res/layout/icon_menu_item_layout.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2007 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.
+-->
+
+<com.android.internal.view.menu.IconMenuItemView xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/title"
+    android:layout_width="fill_parent"
+    android:layout_height="fill_parent"
+    android:paddingBottom="1dip"
+    android:paddingLeft="3dip"
+    android:paddingRight="3dip"
+    android:singleLine="true"
+    android:ellipsize="marquee"
+    android:fadingEdge="horizontal" />
diff --git a/core/res/res/layout/input_method.xml b/core/res/res/layout/input_method.xml
new file mode 100644
index 0000000..a21bbe8
--- /dev/null
+++ b/core/res/res/layout/input_method.xml
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* //device/apps/common/res/layout/alert_dialog.xml
+**
+** Copyright 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.
+*/
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/parentPanel"
+    android:layout_width="fill_parent"
+    android:layout_height="wrap_content"
+    android:orientation="vertical"
+    >
+
+    <FrameLayout android:id="@android:id/extractArea"
+        android:layout_width="fill_parent"
+        android:layout_height="0px"
+        android:layout_weight="1"
+        android:visibility="gone">
+    </FrameLayout>
+    
+    <FrameLayout android:id="@android:id/candidatesArea"
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content"
+        android:visibility="invisible">
+    </FrameLayout>
+    
+    <FrameLayout android:id="@android:id/inputArea"
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content"
+        android:visibility="gone">
+    </FrameLayout>
+</LinearLayout>
diff --git a/core/res/res/layout/input_method_extract_view.xml b/core/res/res/layout/input_method_extract_view.xml
new file mode 100644
index 0000000..13b619a
--- /dev/null
+++ b/core/res/res/layout/input_method_extract_view.xml
@@ -0,0 +1,55 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* //device/apps/common/res/layout/alert_dialog.xml
+**
+** Copyright 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.
+*/
+-->
+
+<LinearLayout
+        xmlns:android="http://schemas.android.com/apk/res/android"
+        android:orientation="horizontal">
+        
+    <android.inputmethodservice.ExtractEditText
+            android:id="@+id/inputExtractEditText"
+            android:layout_width="0px"
+            android:layout_height="fill_parent"
+            android:layout_weight="1"
+            android:scrollbars="vertical"
+            android:gravity="top"
+            android:minLines="1"
+            android:inputType="text"
+            android:background="@android:drawable/extract_edit_text"
+        >
+    </android.inputmethodservice.ExtractEditText>
+
+    <FrameLayout
+            android:id="@+id/inputExtractAccessories"
+            android:layout_width="wrap_content"
+            android:layout_height="fill_parent"
+            android:paddingLeft="10dip"
+            android:paddingRight="10dip"
+            android:background="@android:drawable/keyboard_accessory_bg_landscape"
+        >
+        
+        <Button android:id="@+id/inputExtractAction"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center"
+            />
+            
+    </FrameLayout>
+    
+</LinearLayout>
diff --git a/core/res/res/layout/js_prompt.xml b/core/res/res/layout/js_prompt.xml
new file mode 100644
index 0000000..86974ba
--- /dev/null
+++ b/core/res/res/layout/js_prompt.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+        android:orientation="vertical"
+        android:layout_width="fill_parent" 
+        android:layout_height="wrap_content"
+        android:gravity="center_horizontal"
+        >
+
+    <TextView android:id="@+id/message"
+            android:layout_width="fill_parent"
+            android:layout_height="wrap_content"
+            />
+
+	<EditText android:id="@+id/value"
+            android:layout_width="fill_parent"
+            android:layout_height="wrap_content"
+            android:textStyle="bold"
+            android:inputType="text"
+            android:selectAllOnFocus="true"
+            android:scrollHorizontally="true"
+            android:layout_marginTop="6dip"
+            />
+                
+</LinearLayout>
diff --git a/core/res/res/layout/keyboard_key_preview.xml b/core/res/res/layout/keyboard_key_preview.xml
new file mode 100644
index 0000000..a6e096b
--- /dev/null
+++ b/core/res/res/layout/keyboard_key_preview.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* 
+**
+** Copyright 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.
+*/
+-->
+
+<TextView xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="wrap_content" 
+    android:layout_height="80sp"
+    android:textSize="40sp"
+    android:textColor="?android:attr/textColorPrimaryInverse"
+    android:minWidth="32dip"
+    android:gravity="center"
+    android:background="@drawable/keyboard_key_feedback"
+    />
diff --git a/core/res/res/layout/keyboard_popup_keyboard.xml b/core/res/res/layout/keyboard_popup_keyboard.xml
new file mode 100644
index 0000000..0cdd9da
--- /dev/null
+++ b/core/res/res/layout/keyboard_popup_keyboard.xml
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* 
+**
+** Copyright 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.
+*/
+-->
+<LinearLayout
+        xmlns:android="http://schemas.android.com/apk/res/android"
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content"
+        android:orientation="horizontal"
+        android:background="@android:drawable/keyboard_popup_panel_background"
+        >
+        
+    <android.inputmethodservice.KeyboardView
+            android:id="@android:id/keyboardView"
+            android:background="@android:color/transparent"
+            android:layout_alignParentBottom="true"
+            android:layout_width="fill_parent"
+            android:layout_height="wrap_content"
+            android:keyPreviewLayout="@layout/keyboard_key_preview"
+            android:popupLayout="@layout/keyboard_popup_keyboard"
+            android:keyTextSize="22sp"
+            />
+    <ImageButton android:id="@android:id/button_close"
+        android:background="@android:color/transparent"
+        android:src="@drawable/btn_close" 
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center"
+        android:layout_marginLeft="8dp"
+        android:clickable="true"
+        />
+</LinearLayout>
\ No newline at end of file
diff --git a/core/res/res/layout/keyguard.xml b/core/res/res/layout/keyguard.xml
new file mode 100644
index 0000000..ca629f8
--- /dev/null
+++ b/core/res/res/layout/keyguard.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* //device/apps/common/res/layout/keyguard.xml
+**
+** Copyright 2007, 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.
+*/
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 
+    android:paddingLeft="20dip"
+    android:paddingTop="20dip"
+    android:paddingRight="20dip"
+    android:paddingBottom="20dip"
+    android:orientation="vertical" 
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content"
+    android:background="#ff000000">
+
+    <TextView
+        android:id="@+id/label"
+        android:textSize="16sp" 
+        android:textStyle="bold" 
+        android:textColor="#FFFFFFFF" 
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content" 
+        android:text="@string/keyguard_label_text" />
+</LinearLayout>
+
diff --git a/core/res/res/layout/keyguard_screen_glogin_unlock.xml b/core/res/res/layout/keyguard_screen_glogin_unlock.xml
new file mode 100644
index 0000000..b78bb94
--- /dev/null
+++ b/core/res/res/layout/keyguard_screen_glogin_unlock.xml
@@ -0,0 +1,126 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+**
+** Copyright 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.
+*/
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="fill_parent"
+    android:layout_height="fill_parent"
+    android:orientation="vertical"
+    android:background="#A0000000"
+        >
+    <ScrollView
+        android:layout_width="fill_parent"
+        android:layout_height="0px"
+        android:layout_weight="1"
+        android:layout_above="@+id/emergencyCall">
+        <RelativeLayout 
+            android:layout_width="fill_parent"
+            android:layout_height="fill_parent"
+                >
+        
+            <TextView 
+                android:id="@+id/topHeader"
+                android:layout_width="fill_parent"
+                android:layout_height="wrap_content"
+                android:layout_alignParentTop="true"
+                android:textSize="24sp"
+                android:layout_marginTop="8dip"
+                android:drawableLeft="@drawable/ic_lock_idle_lock"
+                android:drawablePadding="5dip"
+                android:text="@android:string/lockscreen_glogin_too_many_attempts"
+                />
+        
+            <!-- spacer below header -->
+            <View
+                android:id="@+id/spacerTop"
+                android:layout_width="fill_parent"
+                android:layout_height="1dip"
+                android:layout_below="@id/topHeader"
+                android:layout_marginTop="8dip"
+                android:background="@android:drawable/divider_horizontal_bright"/>
+        
+            <TextView
+                android:id="@+id/instructions"
+                android:layout_width="fill_parent"
+                android:layout_height="wrap_content"
+                android:layout_below="@+id/spacerTop"
+                android:layout_marginTop="8dip"
+                android:gravity="center"
+                android:textSize="18sp"
+                android:text="@android:string/lockscreen_glogin_instructions"
+                />
+        
+            <EditText
+                android:id="@+id/login"
+                android:layout_width="fill_parent"
+                android:layout_height="wrap_content"
+                android:layout_below="@id/instructions"
+                android:layout_marginTop="8dip"
+                android:hint="@android:string/lockscreen_glogin_username_hint"
+                android:inputType="textEmailAddress"
+                />
+        
+            <EditText
+                android:id="@+id/password"
+                android:layout_width="fill_parent"
+                android:layout_height="wrap_content"
+                android:layout_below="@id/login"
+                android:layout_marginTop="8dip"
+                android:inputType="textPassword"
+                android:hint="@android:string/lockscreen_glogin_password_hint"
+                android:nextFocusRight="@+id/ok"
+                android:nextFocusDown="@+id/ok"
+                />
+        
+            <!-- ok below password, aligned to right of screen -->
+            <Button
+                android:id="@+id/ok"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_below="@id/password"
+                android:layout_marginTop="8dip"
+                android:layout_alignParentRight="true"
+                android:textSize="18sp"
+                android:text="@android:string/lockscreen_glogin_submit_button"
+                />
+        
+        </RelativeLayout>
+    </ScrollView>
+    
+    <View android:layout_width="1dp" android:layout_height="3dp" />
+    
+    <!-- emergency call button at bottom center -->
+    <Button
+        android:id="@+id/emergencyCall"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center"
+        android:layout_weight="0"
+        android:textSize="18sp"
+        android:drawableLeft="@drawable/ic_emergency"
+        android:drawablePadding="3dip"
+        android:text="@android:string/lockscreen_emergency_call"
+        />
+
+    <!-- spacer above emergency call (doesn't fit in landscape...)-->
+    <!--View
+        android:layout_width="fill_parent"
+        android:layout_height="1dip"
+        android:layout_above="@id/emergencyCall"
+        android:layout_marginBottom="8dip"
+        android:background="@android:drawable/divider_horizontal_bright"/-->
+</LinearLayout>
diff --git a/core/res/res/layout/keyguard_screen_lock.xml b/core/res/res/layout/keyguard_screen_lock.xml
new file mode 100644
index 0000000..fbbc1a4
--- /dev/null
+++ b/core/res/res/layout/keyguard_screen_lock.xml
@@ -0,0 +1,219 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+**
+** Copyright 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.
+*/
+-->
+        
+<!-- This is the general lock screen which shows information about the
+  state of the device, as well as instructions on how to get past it
+  depending on the state of the device.  It is the same for landscape
+  and portrait.-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:orientation="vertical"
+    android:gravity="bottom"
+    android:layout_width="fill_parent"
+    android:layout_height="fill_parent"
+        >
+
+    <LinearLayout
+        android:orientation="vertical"
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center"
+        android:layout_marginBottom="15dip"
+        android:layout_marginLeft="15dip"
+        android:layout_marginRight="15dip"
+	android:paddingTop="20dip"
+	android:paddingBottom="20dip"
+        android:background="@android:drawable/popup_full_dark"
+        >
+
+        <!-- when sim is present -->
+        <TextView android:id="@+id/headerSimOk1"
+                  android:layout_width="fill_parent"
+                  android:layout_height="wrap_content"
+                  android:gravity="center"
+                  android:textSize="34sp"/>
+        <TextView android:id="@+id/headerSimOk2"
+                  android:layout_width="fill_parent"
+                  android:layout_height="wrap_content"
+                  android:gravity="center"
+                  android:textSize="34sp"/>
+
+        <!-- when sim is missing / locked -->
+        <TextView android:id="@+id/headerSimBad1"
+                  android:layout_width="fill_parent"
+                  android:layout_height="wrap_content"
+                  android:gravity="center"
+                  android:text="@android:string/lockscreen_missing_sim_message"
+                  android:textSize="22sp"/>
+        <TextView android:id="@+id/headerSimBad2"
+                  android:layout_width="fill_parent"
+                  android:layout_height="wrap_content"
+                  android:gravity="center"
+                  android:text="@android:string/lockscreen_missing_sim_instructions"
+                  android:textSize="20sp"/>
+
+        <!-- spacer after carrier info / sim messages -->
+        <View
+            android:layout_width="fill_parent"
+            android:layout_height="1dip"
+            android:layout_marginTop="8dip"
+            android:background="@android:drawable/divider_horizontal_dark"/>
+
+        <!-- time and date -->
+        <TextView android:id="@+id/time"
+                  android:layout_width="fill_parent"
+                  android:layout_height="wrap_content"
+                  android:gravity="center"
+                  android:textSize="34sp"/>
+
+        <TextView android:id="@+id/date"
+                  android:layout_width="fill_parent"
+                  android:layout_height="wrap_content"
+                  android:gravity="center"
+                  android:textSize="18sp"/>
+
+        <!-- spacer after time and date -->
+        <View
+            android:layout_width="fill_parent"
+            android:layout_height="1dip"
+            android:layout_marginBottom="8dip"
+            android:background="@android:drawable/divider_horizontal_dark"
+                />
+
+        <!-- battery info -->
+        <LinearLayout android:id="@+id/batteryInfo"
+                android:orientation="horizontal"
+                android:layout_width="fill_parent"
+                android:layout_height="wrap_content"
+                android:gravity="center"
+              >
+
+            <ImageView android:id="@+id/batteryInfoIcon"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginRight="6dip"
+                android:baselineAligned="true"
+                android:gravity="center"
+            />
+
+            <TextView android:id="@+id/batteryInfoText"
+                      android:layout_width="wrap_content"
+                      android:layout_height="wrap_content"
+                      android:textSize="18sp"
+                      android:gravity="center"
+            />
+
+        </LinearLayout>
+
+        <!-- spacer after battery info -->
+        <View android:id="@+id/batteryInfoSpacer"
+            android:layout_width="fill_parent"
+            android:layout_height="1dip"
+            android:layout_marginTop="8dip"
+            android:layout_marginBottom="8dip"
+            android:background="@android:drawable/divider_horizontal_dark"
+                />
+
+        <!-- next alarm info -->
+
+        <LinearLayout android:id="@+id/nextAlarmInfo"
+                android:orientation="horizontal"
+                android:layout_width="fill_parent"
+                android:layout_height="wrap_content"
+                android:gravity="center"
+              >
+
+            <ImageView
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginRight="6dip"
+                android:baselineAligned="true"
+                android:src="@android:drawable/ic_lock_idle_alarm"
+                android:gravity="center"
+            />
+
+            <TextView android:id="@+id/nextAlarmText"
+                      android:layout_width="wrap_content"
+                      android:layout_height="wrap_content"
+                      android:textSize="18sp"
+                      android:gravity="center"
+            />
+        </LinearLayout>
+
+        <!-- spacer after alarm info -->
+        <View android:id="@+id/nextAlarmSpacer"
+            android:layout_width="fill_parent"
+            android:layout_height="1dip"
+            android:layout_marginTop="8dip"
+            android:layout_marginBottom="8dip"
+            android:background="@android:drawable/divider_horizontal_dark"/>
+
+        <!-- lock icon with 'screen locked' message
+             (shown when SIM card is present) -->
+        <LinearLayout android:id="@+id/screenLockedInfo"
+            android:orientation="horizontal"
+            android:layout_width="fill_parent"
+            android:layout_height="wrap_content"
+            android:gravity="center"
+            >
+
+            <ImageView
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginRight="6dip"
+                android:baselineAligned="true"
+                android:src="@android:drawable/ic_lock_idle_lock"
+                android:gravity="center"
+            />
+
+            <TextView
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:textSize="18sp"
+                android:text="@android:string/lockscreen_screen_locked"
+                android:gravity="center"
+                    />
+        </LinearLayout>
+
+        <!-- message about how to unlock
+             (shown when SIM card is present) -->
+        <TextView android:id="@+id/lockInstructions"
+                  android:layout_width="fill_parent"
+                  android:layout_height="wrap_content"
+                  android:layout_marginBottom="5dip"
+                  android:gravity="center"
+                  android:textSize="14sp"/>
+
+
+        <!-- emergency call button shown when sim is missing or PUKd -->
+        <Button
+            android:id="@+id/emergencyCallButton"
+            android:layout_width="fill_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginLeft="5dip"
+            android:layout_marginRight="5dip"
+            android:layout_marginBottom="5dip"
+            android:drawableTop="@drawable/ic_emergency"
+            android:drawablePadding="3dip"
+            android:text="@android:string/lockscreen_emergency_call"
+            android:textSize="14sp"
+           />
+
+
+    </LinearLayout>
+</LinearLayout>
diff --git a/core/res/res/layout/keyguard_screen_sim_pin_landscape.xml b/core/res/res/layout/keyguard_screen_sim_pin_landscape.xml
new file mode 100644
index 0000000..19305c5
--- /dev/null
+++ b/core/res/res/layout/keyguard_screen_sim_pin_landscape.xml
@@ -0,0 +1,118 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+**
+** Copyright 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.
+*/
+-->
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:orientation="vertical"
+    android:layout_width="fill_parent"
+    android:layout_height="fill_parent"
+    android:background="#A0000000"
+        >
+
+    <!-- displays dots as user enters pin -->
+    <LinearLayout android:id="@+id/pinDisplayGroup"
+        android:orientation="horizontal"
+        android:layout_centerInParent="true"
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content"
+        android:addStatesFromChildren="true"
+        android:gravity="center_vertical"
+        android:baselineAligned="false"
+        android:paddingRight="0dip"
+        android:layout_marginRight="30dip"
+        android:layout_marginLeft="30dip"
+        android:background="@android:drawable/edit_text"
+    >
+
+        <EditText android:id="@+id/pinDisplay"
+            android:layout_width="0dip"
+            android:layout_weight="1"
+            android:layout_height="fill_parent"
+            android:maxLines="1"
+            android:background="@null"
+            android:textSize="32sp"
+            android:inputType="textPassword"
+            />
+
+        <ImageButton android:id="@+id/backspace"
+             style="@android:style/Widget.Button.Inset"
+             android:src="@android:drawable/ic_input_delete"
+             android:layout_width="wrap_content"
+             android:layout_height="fill_parent"
+             android:layout_marginTop="2dip"
+             android:layout_marginRight="2dip"
+             android:layout_marginBottom="2dip"
+             android:gravity="center"
+            />
+
+    </LinearLayout>
+        
+    <!-- header text ('Enter Pin Code') -->
+    <TextView android:id="@+id/headerText"
+        android:layout_above="@id/pinDisplayGroup"
+        android:layout_centerHorizontal="true"
+        android:layout_marginBottom="30dip"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:textSize="24sp"
+            />
+
+    <LinearLayout
+        android:orientation="horizontal"
+        android:layout_alignParentBottom="true"
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginBottom="8dip"
+        android:layout_marginLeft="8dip"
+        android:layout_marginRight="8dip">
+
+        <Button android:id="@+id/ok"
+            android:text="@android:string/ok"
+            android:layout_alignParentBottom="true"
+            android:layout_width="0dip"
+            android:layout_height="wrap_content"
+            android:layout_weight="1.0"
+            android:layout_marginBottom="8dip"
+            android:layout_marginRight="8dip"
+            android:paddingLeft="4dip"
+            android:paddingRight="4dip"
+            android:paddingTop="8dip"
+            android:paddingBottom="8dip"
+            android:textSize="18sp"
+            android:drawablePadding="3dip"
+            />
+
+        <Button android:id="@+id/emergencyCall"
+            android:text="@android:string/lockscreen_emergency_call"
+            android:layout_alignParentBottom="true"
+            android:layout_centerHorizontal="true"
+            android:layout_width="0dip"
+            android:layout_height="wrap_content"
+            android:layout_weight="1.0"
+            android:layout_marginBottom="8dip"
+            android:layout_marginLeft="8dip"
+            android:paddingLeft="4dip"
+            android:paddingRight="4dip"
+            android:paddingTop="8dip"
+            android:paddingBottom="8dip"
+            android:textSize="18sp"
+            android:drawableLeft="@drawable/ic_emergency"
+            android:drawablePadding="3dip"
+        />
+    </LinearLayout>
+
+</RelativeLayout>
diff --git a/core/res/res/layout/keyguard_screen_sim_pin_portrait.xml b/core/res/res/layout/keyguard_screen_sim_pin_portrait.xml
new file mode 100644
index 0000000..6a05488
--- /dev/null
+++ b/core/res/res/layout/keyguard_screen_sim_pin_portrait.xml
@@ -0,0 +1,246 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+**
+** Copyright 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.
+*/
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:orientation="vertical"
+    android:layout_width="fill_parent"
+    android:layout_height="fill_parent"
+    android:background="#A0000000"
+        >
+
+    <!-- header text ('Enter Pin Code') -->
+    <TextView android:id="@+id/headerText"
+        android:layout_width="fill_parent"
+        android:layout_height="0dip"
+        android:layout_weight="1"
+        android:gravity="center"
+        android:textAppearance="?android:attr/textAppearanceLarge"
+        android:textSize="26sp"
+            />
+
+    <!-- displays dots as user enters pin -->
+    <LinearLayout
+        android:orientation="horizontal"
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content"
+        android:addStatesFromChildren="true"
+        android:gravity="center_vertical"
+        android:baselineAligned="false"
+        android:paddingRight="0dip"
+        android:layout_marginTop="8dip"
+        android:layout_marginBottom="5dip"
+        android:layout_marginLeft="5dip"
+        android:background="@android:drawable/edit_text"
+    >
+
+        <EditText android:id="@+id/pinDisplay"
+            android:layout_width="0dip"
+            android:layout_weight="1"
+            android:layout_height="fill_parent"
+            android:maxLines="1"
+            android:background="@null"
+            android:inputType="textPassword"
+            />
+
+        <ImageButton android:id="@+id/backspace"
+             style="@android:style/Widget.Button.Inset"
+             android:src="@android:drawable/ic_input_delete"
+             android:layout_width="wrap_content"
+             android:layout_height="fill_parent"
+             android:layout_marginTop="2dip"
+             android:layout_marginRight="2dip"
+             android:layout_marginBottom="2dip"
+             android:gravity="center"
+            />
+
+    </LinearLayout>
+
+    <!-- Keypad section -->
+    <LinearLayout
+        android:layout_width="fill_parent"
+        android:layout_height="0sp"
+        android:layout_weight="1"
+        android:layout_marginLeft="5dip"
+        android:layout_marginRight="4dip"
+        android:orientation="horizontal"
+    >
+
+        <Button android:id="@+id/one"
+            android:layout_width="0sp"
+            android:layout_height="fill_parent"
+            android:layout_weight="1"
+            android:paddingLeft="4dip"
+            style="?android:attr/buttonStyleSmall"
+            android:textSize="22sp"
+        />
+
+        <Button android:id="@+id/two"
+            android:layout_width="0sp"
+            android:layout_height="fill_parent"
+            android:layout_weight="1"
+            android:paddingLeft="4dip"
+            style="?android:attr/buttonStyleSmall"
+            android:textSize="22sp"
+        />
+
+        <Button android:id="@+id/three"
+            android:layout_width="0sp"
+            android:layout_height="fill_parent"
+            android:layout_weight="1"
+            android:paddingLeft="4dip"
+            style="?android:attr/buttonStyleSmall"
+            android:textSize="22sp"
+        />
+    </LinearLayout>
+
+    <LinearLayout
+        android:layout_width="fill_parent"
+        android:layout_height="0sp"
+        android:layout_weight="1"
+        android:layout_marginLeft="5dip"
+        android:layout_marginRight="4dip"
+        android:orientation="horizontal"
+    >
+
+        <Button android:id="@+id/four"
+            android:layout_width="0sp"
+            android:layout_height="fill_parent"
+            android:layout_weight="1"
+            android:paddingLeft="4dip"
+            style="?android:attr/buttonStyleSmall"
+            android:textSize="22sp"
+        />
+
+        <Button android:id="@+id/five"
+            android:layout_width="0sp"
+            android:layout_height="fill_parent"
+            android:layout_weight="1"
+            android:paddingLeft="4dip"
+            style="?android:attr/buttonStyleSmall"
+            android:textSize="22sp"
+        />
+
+        <Button android:id="@+id/six"
+            android:layout_width="0sp"
+            android:layout_height="fill_parent"
+            android:layout_weight="1"
+            android:paddingLeft="4dip"
+            style="?android:attr/buttonStyleSmall"
+            android:textSize="22sp"
+        />
+    </LinearLayout>
+
+    <LinearLayout
+        android:layout_width="fill_parent"
+        android:layout_height="0sp"
+        android:layout_weight="1"
+        android:layout_marginLeft="5dip"
+        android:layout_marginRight="4dip"
+        android:orientation="horizontal"
+    >
+
+        <Button android:id="@+id/seven"
+            android:layout_width="0sp"
+            android:layout_height="fill_parent"
+            android:layout_weight="1"
+            android:paddingLeft="4dip"
+            style="?android:attr/buttonStyleSmall"
+            android:textSize="22sp"
+        />
+
+        <Button android:id="@+id/eight"
+            android:layout_width="0sp"
+            android:layout_height="fill_parent"
+            android:layout_weight="1"
+            android:paddingLeft="4dip"
+            style="?android:attr/buttonStyleSmall"
+            android:textSize="22sp"
+        />
+
+        <Button android:id="@+id/nine"
+            android:layout_width="0sp"
+            android:layout_height="fill_parent"
+            android:layout_weight="1"
+            android:paddingLeft="4dip"
+            style="?android:attr/buttonStyleSmall"
+            android:textSize="22sp"
+        />
+    </LinearLayout>
+
+    <LinearLayout
+        android:layout_width="fill_parent"
+        android:layout_height="0sp"
+        android:layout_weight="1"
+        android:layout_marginLeft="5dip"
+        android:layout_marginRight="4dip"
+        android:orientation="horizontal"
+    >
+
+        <Button android:id="@+id/ok"
+            android:layout_width="0sp"
+            android:layout_height="fill_parent"
+            android:layout_weight="1"
+            android:paddingLeft="4dip"
+            style="?android:attr/buttonStyleSmall"
+            android:textSize="18sp"
+            android:text="@android:string/ok"
+        />
+
+        <Button android:id="@+id/zero"
+            android:layout_width="0sp"
+            android:layout_height="fill_parent"
+            android:layout_weight="1"
+            android:paddingLeft="4dip"
+            style="?android:attr/buttonStyleSmall"
+            android:textSize="22sp"
+        />
+
+        <Button android:id="@+id/cancel"
+            android:layout_width="0sp"
+            android:layout_height="fill_parent"
+            android:layout_weight="1"
+            android:paddingLeft="4dip"
+            style="?android:attr/buttonStyleSmall"
+            android:textSize="18sp"
+            android:text="@android:string/cancel"
+        />
+    </LinearLayout>
+
+
+    <!-- emergency call button -->
+    <RelativeLayout
+        android:layout_width="fill_parent"
+        android:layout_height="0dip"
+        android:layout_weight="1"
+        >
+
+        <Button
+            android:id="@+id/emergencyCall"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_centerInParent="true"
+            android:drawableLeft="@drawable/ic_emergency"
+            android:drawablePadding="3dip"
+            android:paddingTop="4dip"
+            android:paddingBottom="4dip"
+            android:text="@android:string/lockscreen_emergency_call"
+            />
+
+    </RelativeLayout>
+
+</LinearLayout>
diff --git a/core/res/res/layout/keyguard_screen_unlock_landscape.xml b/core/res/res/layout/keyguard_screen_unlock_landscape.xml
new file mode 100644
index 0000000..ba1640a
--- /dev/null
+++ b/core/res/res/layout/keyguard_screen_unlock_landscape.xml
@@ -0,0 +1,127 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+**
+** Copyright 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.
+*/
+-->
+
+<!-- This is the screen that shows the 9 circle unlock widget and instructs
+     the user how to unlock their device, or make an emergency call.  This
+     is the portrait layout.  -->
+
+<com.android.internal.widget.LinearLayoutWithDefaultTouchRecepient xmlns:android="http://schemas.android.com/apk/res/android"
+    android:orientation="horizontal"
+    android:layout_width="fill_parent"
+    android:layout_height="fill_parent"
+    android:background="#A0000000"
+        >
+
+    <!-- left side: instructions and emergency call button -->
+    <LinearLayout
+            android:orientation="vertical"
+            android:layout_width="0dip"
+            android:layout_height="fill_parent"
+            android:layout_weight="1.0"
+            >
+
+        <!-- lock icon next to header text -->
+        <LinearLayout
+                android:orientation="horizontal"
+                android:layout_width="fill_parent"
+                android:layout_height="wrap_content"
+                android:layout_marginTop="3dip"
+                android:gravity="center"
+                >
+            <ImageView android:id="@+id/unlockLockIcon"
+                       android:layout_width="wrap_content"
+                       android:layout_height="wrap_content"
+                       android:layout_marginRight="3dip"
+                       android:baselineAligned="true"
+                       android:gravity="center"
+                       android:src="@android:drawable/ic_lock_idle_lock"
+                    />
+
+            <TextView android:id="@+id/headerText"
+                      android:layout_width="wrap_content"
+                      android:layout_height="wrap_content"
+                      android:gravity="center"
+                      android:textSize="18sp"/>
+        </LinearLayout>
+
+
+        <!-- fill space between header and button below -->
+        <View
+            android:layout_weight="1.0"
+            android:layout_width="fill_parent"
+            android:layout_height="0dip"
+            />
+
+        <!-- footer -->
+        <FrameLayout
+            android:layout_width="fill_parent"
+            android:layout_height="wrap_content"
+            >
+
+            <!-- option 1: a single emergency call button -->
+            <RelativeLayout android:id="@+id/footerNormal"
+                android:layout_width="fill_parent"
+                android:layout_height="wrap_content"
+                >
+                <Button android:id="@+id/emergencyCallAlone"
+                    android:layout_width="fill_parent"
+                    android:layout_height="wrap_content"
+                    android:text="@android:string/lockscreen_emergency_call"
+                    android:textSize="14sp"
+                    android:drawableLeft="@drawable/ic_emergency"
+                    android:drawablePadding="3dip"
+                    />
+            </RelativeLayout>
+
+            <!-- option 2: an emergency call button, and a 'forgot pattern?' button -->
+            <LinearLayout android:id="@+id/footerForgotPattern"
+                android:orientation="vertical"
+                android:layout_width="fill_parent"
+                android:layout_height="wrap_content"
+                android:gravity="center"
+                >
+                <Button android:id="@+id/forgotPattern"
+                    android:layout_width="fill_parent"
+                    android:layout_height="wrap_content"
+                    android:textSize="14sp"
+                    android:visibility="invisible"
+                    />
+                <Button android:id="@+id/emergencyCallTogether"
+                    android:layout_width="fill_parent"
+                    android:layout_height="wrap_content"
+                    android:text="@android:string/lockscreen_emergency_call"
+                    android:textSize="14sp"
+                    android:drawableLeft="@drawable/ic_emergency"
+                    android:drawablePadding="3dip"
+                    />
+            </LinearLayout>
+        </FrameLayout>
+    </LinearLayout>
+
+    <!--View
+         android:background="@android:drawable/code_lock_left"
+         android:layout_width="2dip"
+         android:layout_height="fill_parent" /-->
+
+    <!-- right side: lock pattern -->
+    <com.android.internal.widget.LockPatternView android:id="@+id/lockPattern"
+         android:layout_width="wrap_content"
+         android:layout_height="wrap_content" />
+
+</com.android.internal.widget.LinearLayoutWithDefaultTouchRecepient>
diff --git a/core/res/res/layout/keyguard_screen_unlock_portrait.xml b/core/res/res/layout/keyguard_screen_unlock_portrait.xml
new file mode 100644
index 0000000..f09c422
--- /dev/null
+++ b/core/res/res/layout/keyguard_screen_unlock_portrait.xml
@@ -0,0 +1,128 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+**
+** Copyright 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.
+*/
+-->
+
+<!-- This is the screen that shows the 9 circle unlock widget and instructs
+     the user how to unlock their device, or make an emergency call.  This
+     is the portrait layout.  -->
+<com.android.internal.widget.LinearLayoutWithDefaultTouchRecepient
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:orientation="vertical"
+    android:layout_width="wrap_content"
+    android:layout_height="fill_parent"
+    android:background="#A0000000"
+        >
+
+    <!-- lock icon and header message -->
+    <LinearLayout
+        android:orientation="horizontal"
+        android:layout_width="fill_parent"
+        android:layout_height="0dip"
+        android:layout_weight="1.0"
+        android:gravity="center"
+            >
+
+        <ImageView android:id="@+id/unlockLockIcon"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginRight="6dip"
+            android:baselineAligned="true"
+            android:gravity="center"
+            android:src="@android:drawable/ic_lock_idle_lock"
+        />
+
+        <TextView android:id="@+id/headerText"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:gravity="center"
+            android:textSize="18sp"/>
+    </LinearLayout>
+
+    <!--View
+         android:background="@android:drawable/code_lock_top"
+         android:layout_width="fill_parent"
+         android:layout_height="2dip" /-->
+    <com.android.internal.widget.LockPatternView android:id="@+id/lockPattern"
+         android:layout_width="wrap_content"
+         android:layout_height="wrap_content"
+         />
+    <!--View
+         android:background="@android:drawable/code_lock_bottom"
+         android:layout_width="fill_parent"
+         android:layout_height="8dip" /-->
+
+    <!-- footer -->
+    <FrameLayout
+        android:layout_width="fill_parent"
+        android:layout_height="0dip"
+        android:layout_weight="1.0"
+        >
+
+        <!-- option 1: a single emergency call button -->
+        <RelativeLayout android:id="@+id/footerNormal"
+            android:layout_width="fill_parent"
+            android:layout_height="fill_parent"
+            >
+            <Button android:id="@+id/emergencyCallAlone"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_centerInParent="true"
+                android:text="@android:string/lockscreen_emergency_call"
+                android:textSize="14sp"
+                android:drawableLeft="@drawable/ic_emergency"
+                android:drawablePadding="3dip"
+                />
+
+        </RelativeLayout>
+
+        <!-- option 2: an emergency call button, and a 'forgot pattern?' button -->
+        <LinearLayout android:id="@+id/footerForgotPattern"
+            android:orientation="horizontal"
+            android:layout_width="fill_parent"
+            android:layout_height="fill_parent"
+            android:gravity="center"
+            >
+            <Button android:id="@+id/emergencyCallTogether"
+                android:layout_width="0dip"
+                android:layout_height="fill_parent"
+                android:layout_weight="1.0"
+                android:layout_marginTop="4dip"
+                android:layout_marginBottom="4dip"
+                android:layout_marginLeft="4dip"
+                android:layout_marginRight="2dip"
+                android:text="@android:string/lockscreen_emergency_call"
+                android:textSize="14sp"
+                android:drawableLeft="@drawable/ic_emergency"
+                android:drawablePadding="3dip"
+                />
+            <Button android:id="@+id/forgotPattern"
+                android:layout_width="0dip"
+                android:layout_height="fill_parent"
+                android:layout_weight="1.0"
+                android:layout_marginTop="4dip"
+                android:layout_marginBottom="4dip"
+                android:layout_marginLeft="2dip"
+                android:layout_marginRight="4dip"
+                android:textSize="14sp"
+                android:visibility="invisible"
+                />
+        </LinearLayout>
+
+    </FrameLayout>
+
+</com.android.internal.widget.LinearLayoutWithDefaultTouchRecepient>
diff --git a/core/res/res/layout/list_content.xml b/core/res/res/layout/list_content.xml
new file mode 100644
index 0000000..a7f3e2d
--- /dev/null
+++ b/core/res/res/layout/list_content.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* //device/apps/common/assets/res/layout/list_content.xml
+**
+** Copyright 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.
+*/
+-->
+<ListView xmlns:android="http://schemas.android.com/apk/res/android" android:id="@android:id/list"
+    android:layout_width="fill_parent" 
+    android:layout_height="fill_parent"
+    android:drawSelectorOnTop="false"
+    />
diff --git a/core/res/res/layout/list_menu_item_checkbox.xml b/core/res/res/layout/list_menu_item_checkbox.xml
new file mode 100644
index 0000000..dc02a1e
--- /dev/null
+++ b/core/res/res/layout/list_menu_item_checkbox.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2007 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.
+-->
+
+<CheckBox xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/checkbox"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content"
+    android:layout_gravity="center_vertical"
+    android:focusable="false"
+    android:clickable="false"
+    android:duplicateParentState="true" />
+
+
diff --git a/core/res/res/layout/list_menu_item_icon.xml b/core/res/res/layout/list_menu_item_icon.xml
new file mode 100644
index 0000000..2be9fab
--- /dev/null
+++ b/core/res/res/layout/list_menu_item_icon.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2007 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.
+-->
+
+<ImageView xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/icon"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content"
+    android:layout_gravity="center_vertical"
+    android:layout_marginLeft="2dip"
+    android:duplicateParentState="true" />
+
diff --git a/core/res/res/layout/list_menu_item_layout.xml b/core/res/res/layout/list_menu_item_layout.xml
new file mode 100644
index 0000000..df4958f
--- /dev/null
+++ b/core/res/res/layout/list_menu_item_layout.xml
@@ -0,0 +1,59 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2007 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.
+-->
+
+<com.android.internal.view.menu.ListMenuItemView xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="fill_parent"
+    android:layout_height="?android:attr/listPreferredItemHeight">
+    
+    <!-- Icon will be inserted here. -->
+    
+    <!-- The title and summary have some gap between them, and this 'group' should be centered vertically. -->
+    <RelativeLayout
+        android:layout_width="0dip"
+        android:layout_weight="1"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center_vertical"
+        android:layout_marginLeft="6dip"
+        android:layout_marginRight="6dip"
+        android:duplicateParentState="true">
+        
+        <TextView 
+            android:id="@+id/title"
+            android:layout_width="fill_parent"
+            android:layout_height="wrap_content"
+            android:layout_alignParentTop="true"
+            android:layout_alignParentLeft="true"
+            android:textAppearance="?android:attr/textAppearanceLargeInverse"
+            android:singleLine="true"
+            android:duplicateParentState="true"
+            android:ellipsize="marquee"
+            android:fadingEdge="horizontal" />
+
+        <TextView
+            android:id="@+id/shortcut"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_below="@id/title"
+            android:layout_alignParentLeft="true"
+            android:textAppearance="?android:attr/textAppearanceSmallInverse"
+            android:singleLine="true"
+            android:duplicateParentState="true" />
+
+    </RelativeLayout>
+
+    <!-- Checkbox, and/or radio button will be inserted here. -->
+    
+</com.android.internal.view.menu.ListMenuItemView>
diff --git a/core/res/res/layout/list_menu_item_radio.xml b/core/res/res/layout/list_menu_item_radio.xml
new file mode 100644
index 0000000..ac4459e
--- /dev/null
+++ b/core/res/res/layout/list_menu_item_radio.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2007 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.
+-->
+
+<RadioButton xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/radio"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content"
+    android:layout_gravity="center_vertical"
+    android:focusable="false"
+    android:clickable="false"
+    android:duplicateParentState="true" />
diff --git a/core/res/res/layout/media_controller.xml b/core/res/res/layout/media_controller.xml
new file mode 100644
index 0000000..c49835d
--- /dev/null
+++ b/core/res/res/layout/media_controller.xml
@@ -0,0 +1,77 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2007 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.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="fill_parent"
+    android:layout_height="wrap_content"
+    android:background="#CC666666"
+    android:orientation="vertical">
+
+    <ImageView android:layout_width="fill_parent"
+        android:layout_height="1px"
+        android:background="@android:drawable/divider_horizontal_dark" />
+
+    <LinearLayout
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content"
+        android:gravity="center"
+        android:paddingTop="4dip"
+        android:orientation="horizontal">
+
+        <ImageButton android:id="@+id/prev" style="@android:style/MediaButton.Previous" />
+        <ImageButton android:id="@+id/rew" style="@android:style/MediaButton.Rew" />
+        <ImageButton android:id="@+id/pause" style="@android:style/MediaButton.Play" />
+        <ImageButton android:id="@+id/ffwd" style="@android:style/MediaButton.Ffwd" />
+        <ImageButton android:id="@+id/next" style="@android:style/MediaButton.Next" />
+
+    </LinearLayout>
+
+    <LinearLayout
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content"
+        android:orientation="horizontal">
+
+        <TextView android:id="@+id/time_current"
+            android:textSize="14sp"
+            android:textStyle="bold"
+            android:paddingTop="4dip"
+            android:paddingLeft="4dip"
+            android:layout_gravity="center_horizontal"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:paddingRight="4dip" />
+
+        <SeekBar
+            android:id="@+id/mediacontroller_progress"
+            style="?android:attr/progressBarStyleHorizontal"
+            android:layout_width="0dip"
+            android:layout_weight="1"
+            android:layout_height="30px"
+            android:layout_alignParentLeft="true"
+            android:layout_alignParentRight="true" />
+
+        <TextView android:id="@+id/time"
+            android:textSize="14sp"
+            android:textStyle="bold"
+            android:paddingTop="4dip"
+            android:paddingRight="4dip"
+            android:layout_gravity="center_horizontal"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:paddingLeft="4dip" />
+    </LinearLayout>
+
+</LinearLayout>
diff --git a/core/res/res/layout/menu_item.xml b/core/res/res/layout/menu_item.xml
new file mode 100644
index 0000000..7e9859f
--- /dev/null
+++ b/core/res/res/layout/menu_item.xml
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* //device/apps/common/assets/res/any/layout/menu_item.xml
+**
+** Copyright 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.
+*/
+-->
+<MenuItemView xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="horizontal" android:focusable="true" >
+
+    <TextView android:id="@+id/index"
+        android:paddingLeft="0dip" android:paddingTop="1dip"
+        android:paddingRight="8dip" android:paddingBottom="0dip"
+        android:layout_width="17dip" android:layout_height="wrap_content"
+        android:includeFontPadding="false" />
+
+    <ImageView android:id="@+id/check"
+        android:paddingLeft="3dip" android:paddingTop="3dip"
+        android:paddingRight="3dip" android:paddingBottom="0dip"
+        android:src="@drawable/menuitem_checkbox" android:scaleType="fitCenter"
+        android:layout_width="wrap_content" android:layout_height="wrap_content" />
+
+    <TextView android:id="@+id/title"
+        android:paddingLeft="0dip" android:paddingTop="1dip"
+        android:paddingRight="0dip" android:paddingBottom="2dip"
+        android:layout_width="wrap_content" android:layout_height="wrap_content"
+        android:layout_weight="1" android:includeFontPadding="false" />
+
+    <TextView android:id="@+id/shortcut"
+        android:paddingLeft="8dip" android:paddingTop="0dip"
+        android:paddingRight="0dip" android:paddingBottom="0dip"
+        android:layout_width="20dip" android:layout_height="wrap_content"
+        android:gravity="center_horizontal"  android:includeFontPadding="true"/>
+
+</MenuItemView>
+
diff --git a/core/res/res/layout/menu_item_divider.xml b/core/res/res/layout/menu_item_divider.xml
new file mode 100644
index 0000000..042662f
--- /dev/null
+++ b/core/res/res/layout/menu_item_divider.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* //device/apps/common/assets/res/any/layout/menu_item.xml
+**
+** Copyright 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.
+*/
+-->
+<Divider xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/separator"
+         android:paddingLeft="0dip" android:paddingTop="4dip"
+         android:paddingRight="0dip" android:paddingBottom="4dip"
+         android:src="@drawable/menu_separator"
+         android:layout_width="fill_parent"
+         android:layout_height="wrap_content"/>
diff --git a/core/res/res/layout/number_picker.xml b/core/res/res/layout/number_picker.xml
new file mode 100644
index 0000000..bbdb31c
--- /dev/null
+++ b/core/res/res/layout/number_picker.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+**
+** Copyright 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.
+*/
+-->
+
+<merge xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <com.android.internal.widget.NumberPickerButton android:id="@+id/increment"
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content"
+        android:background="@drawable/timepicker_up_btn" />
+
+    <EditText android:id="@+id/timepicker_input"
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content"
+        android:gravity="center"
+        android:singleLine="true"
+        style="?android:attr/textAppearanceLargeInverse"
+        android:textColor="@android:color/primary_text_light"
+        android:textSize="30sp"
+        android:background="@drawable/timepicker_input" />
+
+    <com.android.internal.widget.NumberPickerButton android:id="@+id/decrement"
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content"
+        android:background="@drawable/timepicker_down_btn" />
+
+</merge>
diff --git a/core/res/res/layout/number_picker_edit.xml b/core/res/res/layout/number_picker_edit.xml
new file mode 100644
index 0000000..f3af6e9
--- /dev/null
+++ b/core/res/res/layout/number_picker_edit.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+**
+** Copyright 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.
+*/
+-->
+
+<EditText xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="fill_parent"
+    android:layout_height="wrap_content"
+    android:gravity="center_horizontal"
+    android:singleLine="true"
+    style="?android:attr/textAppearanceLargeInverse"
+    android:textColor="@android:color/primary_text_light"
+    android:textSize="30sp"
+    android:background="@drawable/timepicker_input"
+    />
diff --git a/core/res/res/layout/popup_menu_layout.xml b/core/res/res/layout/popup_menu_layout.xml
new file mode 100644
index 0000000..1e8083a
--- /dev/null
+++ b/core/res/res/layout/popup_menu_layout.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2007 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.
+-->
+
+<PopupMenuView xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="320sp"
+    android:layout_height="wrap_content"
+    android:orientation="vertical"
+    android:padding="0sp">
+
+    <LinearLayout
+        android:id="@+id/header"
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content"
+        android:orientation="horizontal" />
+
+    <ListView
+        android:id="@+id/listview" 
+        android:layout_width="fill_parent"
+        android:layout_height="fill_parent"
+        android:layout_weight="1" />
+
+</PopupMenuView>
diff --git a/core/res/res/layout/power_dialog.xml b/core/res/res/layout/power_dialog.xml
new file mode 100644
index 0000000..7c59ab4
--- /dev/null
+++ b/core/res/res/layout/power_dialog.xml
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 2007, 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.
+*/
+-->
+
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="fill_parent"
+    android:layout_height="wrap_content">
+
+    <LinearLayout
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content"
+        android:orientation="vertical">
+
+        <Button android:id="@+id/keyguard"
+            android:layout_width="fill_parent"
+            android:layout_height="wrap_content"
+         />
+
+        <Button android:id="@+id/off"
+            android:layout_width="fill_parent"
+            android:layout_height="wrap_content"
+            android:text="@string/power_off" />    
+            
+        <Button android:id="@+id/silent"
+            android:layout_width="fill_parent"
+            android:layout_height="wrap_content" />
+            
+        <Button android:id="@+id/radio_power"
+            android:layout_width="fill_parent"
+            android:layout_height="wrap_content" />
+        
+    </LinearLayout>
+</FrameLayout>
diff --git a/core/res/res/layout/preference.xml b/core/res/res/layout/preference.xml
new file mode 100644
index 0000000..00745b4
--- /dev/null
+++ b/core/res/res/layout/preference.xml
@@ -0,0 +1,61 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<!-- Layout for a Preference in a PreferenceActivity. The
+     Preference is able to place a specific widget for its particular
+     type in the "widget_frame" layout. -->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 
+    android:layout_width="fill_parent"
+    android:layout_height="wrap_content"
+    android:minHeight="?android:attr/listPreferredItemHeight"
+    android:gravity="center_vertical"
+    android:paddingRight="?android:attr/scrollbarSize">
+    
+    <RelativeLayout
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginLeft="15dip"
+        android:layout_marginRight="6dip"
+        android:layout_marginTop="6dip"
+        android:layout_marginBottom="6dip"
+        android:layout_weight="1">
+    
+        <TextView android:id="@+android:id/title"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:singleLine="true"
+            android:textAppearance="?android:attr/textAppearanceLarge"
+            android:ellipsize="marquee"
+            android:fadingEdge="horizontal" />
+            
+        <TextView android:id="@+android:id/summary"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_below="@android:id/title"
+            android:layout_alignLeft="@android:id/title"
+            android:textAppearance="?android:attr/textAppearanceSmall"
+            android:maxLines="2" />
+
+    </RelativeLayout>
+    
+    <!-- Preference should place its actual preference widget here. -->
+    <LinearLayout android:id="@+android:id/widget_frame"
+        android:layout_width="wrap_content"
+        android:layout_height="fill_parent"
+        android:gravity="center_vertical"
+        android:orientation="vertical" />
+
+</LinearLayout>
diff --git a/core/res/res/layout/preference_category.xml b/core/res/res/layout/preference_category.xml
new file mode 100644
index 0000000..280d952
--- /dev/null
+++ b/core/res/res/layout/preference_category.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<!-- Layout used for PreferenceCategory in a PreferenceActivity. -->
+<TextView xmlns:android="http://schemas.android.com/apk/res/android"
+    style="?android:attr/listSeparatorTextViewStyle"
+    android:id="@+android:id/title"
+/>
diff --git a/core/res/res/layout/preference_child.xml b/core/res/res/layout/preference_child.xml
new file mode 100644
index 0000000..5f8ddd4
--- /dev/null
+++ b/core/res/res/layout/preference_child.xml
@@ -0,0 +1,60 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<!-- Layout for a visually child-like Preference in a PreferenceActivity. -->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 
+    android:layout_width="fill_parent"
+    android:layout_height="wrap_content"
+    android:minHeight="?android:attr/listPreferredItemHeight"
+    android:gravity="center_vertical"
+    android:paddingRight="?android:attr/scrollbarSize">
+    
+    <RelativeLayout
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginLeft="20dip"
+        android:layout_marginRight="6dip"
+        android:layout_marginTop="6dip"
+        android:layout_marginBottom="6dip"
+        android:layout_weight="1">
+    
+        <TextView android:id="@+android:id/title"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:singleLine="true"
+            android:textAppearance="?android:attr/textAppearanceLarge"
+            android:ellipsize="marquee"
+            android:fadingEdge="horizontal" />
+            
+        <TextView android:id="@+android:id/summary"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_below="@android:id/title"
+            android:layout_alignLeft="@android:id/title"
+            android:textAppearance="?android:attr/textAppearanceSmall"
+            android:maxLines="2"
+            android:textColor="?android:attr/textColorSecondary" />
+
+    </RelativeLayout>
+    
+    <!-- Preference should place its actual preference widget here. -->
+    <LinearLayout android:id="@+android:id/widget_frame"
+        android:layout_width="wrap_content"
+        android:layout_height="fill_parent"
+        android:gravity="center_vertical"
+        android:orientation="vertical" />
+
+</LinearLayout>
diff --git a/core/res/res/layout/preference_dialog_edittext.xml b/core/res/res/layout/preference_dialog_edittext.xml
new file mode 100644
index 0000000..7d1faac
--- /dev/null
+++ b/core/res/res/layout/preference_dialog_edittext.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<!-- Layout used as the dialog's content View for EditTextPreference. -->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+android:id/edittext_container"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content"
+    android:padding="5dip"
+    android:orientation="vertical">
+    
+    <TextView android:id="@+android:id/message"
+        style="?android:attr/textAppearanceSmall"
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content"
+        android:textColor="?android:attr/textColorSecondary" />
+        
+</LinearLayout>
diff --git a/core/res/res/layout/preference_information.xml b/core/res/res/layout/preference_information.xml
new file mode 100644
index 0000000..8f05a8e
--- /dev/null
+++ b/core/res/res/layout/preference_information.xml
@@ -0,0 +1,61 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<!-- Layout for a Preference in a PreferenceActivity. The
+     Preference is able to place a specific widget for its particular
+     type in the "widget_frame" layout. -->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 
+    android:layout_width="fill_parent"
+    android:layout_height="wrap_content"
+    android:minHeight="?android:attr/listPreferredItemHeight"
+    android:gravity="center_vertical"
+    android:paddingRight="?android:attr/scrollbarSize">
+    
+    <RelativeLayout
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginLeft="16sp"
+        android:layout_marginRight="6sp"
+        android:layout_marginTop="6sp"
+        android:layout_marginBottom="6sp"
+        android:layout_weight="1">
+    
+        <TextView android:id="@+android:id/title"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:singleLine="true"
+            android:textAppearance="?android:attr/textAppearanceLarge"
+            android:textColor="?android:attr/textColorSecondary" />
+            
+        <TextView android:id="@+android:id/summary"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_below="@android:id/title"
+            android:layout_alignLeft="@android:id/title"
+            android:textAppearance="?android:attr/textAppearanceSmall"
+            android:textColor="?android:attr/textColorSecondary"
+            android:maxLines="2" />
+
+    </RelativeLayout>
+    
+    <!-- Preference should place its actual preference widget here. -->
+    <LinearLayout android:id="@+android:id/widget_frame"
+        android:layout_width="wrap_content"
+        android:layout_height="fill_parent"
+        android:gravity="center_vertical"
+        android:orientation="vertical" />
+
+</LinearLayout>
diff --git a/core/res/res/layout/preference_list_content.xml b/core/res/res/layout/preference_list_content.xml
new file mode 100644
index 0000000..31113e1
--- /dev/null
+++ b/core/res/res/layout/preference_list_content.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* //device/apps/common/assets/res/layout/list_content.xml
+**
+** Copyright 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.
+*/
+-->
+<ListView xmlns:android="http://schemas.android.com/apk/res/android" android:id="@android:id/list"
+    android:layout_width="fill_parent" 
+    android:layout_height="fill_parent"
+    android:drawSelectorOnTop="false"
+    android:scrollbarAlwaysDrawVerticalTrack="true"
+    />
diff --git a/core/res/res/layout/preference_widget_checkbox.xml b/core/res/res/layout/preference_widget_checkbox.xml
new file mode 100644
index 0000000..c1ad360
--- /dev/null
+++ b/core/res/res/layout/preference_widget_checkbox.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<!-- Layout used by CheckBoxPreference for the checkbox style. This is inflated
+     inside android.R.layout.preference. -->
+<CheckBox xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+android:id/checkbox" 
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content"
+    android:layout_marginRight="4dip"
+    android:layout_gravity="center_vertical"
+    android:focusable="false"
+    android:clickable="false" />
diff --git a/core/res/res/layout/preferences.xml b/core/res/res/layout/preferences.xml
new file mode 100644
index 0000000..e6876ff
--- /dev/null
+++ b/core/res/res/layout/preferences.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<!-- Layout used by PreferenceScreen. This is inflated inside 
+        android.R.layout.preference. -->
+<ImageView xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content"
+    android:layout_marginRight="7dip"
+    android:layout_gravity="center_vertical"
+    android:src="@drawable/ic_settings_indicator_next_page" />
+
diff --git a/core/res/res/layout/progress_dialog.xml b/core/res/res/layout/progress_dialog.xml
new file mode 100644
index 0000000..2d7afd6
--- /dev/null
+++ b/core/res/res/layout/progress_dialog.xml
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* //device/apps/common/res/layout/alert_dialog.xml
+**
+** Copyright 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.
+*/
+-->
+
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="fill_parent"
+    android:layout_height="wrap_content">
+
+    <LinearLayout android:id="@+id/body"
+        android:orientation="horizontal"
+        android:layout_width="fill_parent"
+        android:layout_height="fill_parent"
+        android:baselineAligned="false"
+        android:paddingLeft="8dip"
+        android:paddingTop="10dip"
+        android:paddingRight="8dip"
+        android:paddingBottom="10dip">
+
+        <ProgressBar android:id="@android:id/progress"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:max="10000"
+            android:layout_marginRight="12dip" />
+
+        <TextView android:id="@+id/message"
+            android:layout_width="fill_parent"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center_vertical" />
+    </LinearLayout>
+</FrameLayout>
diff --git a/core/res/res/layout/recent_apps_dialog.xml b/core/res/res/layout/recent_apps_dialog.xml
new file mode 100644
index 0000000..852b2f1
--- /dev/null
+++ b/core/res/res/layout/recent_apps_dialog.xml
@@ -0,0 +1,83 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 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.
+*/
+-->
+
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="fill_parent"
+    android:layout_height="wrap_content">
+    
+    <LinearLayout
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:padding="3dip"
+        android:orientation="vertical">
+        
+        <!-- This is only intended to be visible when all buttons (below) are invisible -->
+        <TextView
+            android:id="@+id/no_applications_message"
+            android:layout_width="285dip"
+            android:layout_height="wrap_content"
+            android:layout_marginTop="15dip"
+            android:layout_marginBottom="15dip"
+            android:gravity="center"
+            android:textAppearance="?android:attr/textAppearanceMedium"
+            android:text="@android:string/no_recent_tasks" />
+    
+        <!-- The first row has a fixed-width because the UI spec requires the box
+             to display with full-width no matter how many icons are visible, but to
+             adjust height based on number of rows. -->
+        <!-- TODO Adjust all sizes, padding, etc. to meet pixel-perfect specs -->
+        <LinearLayout
+            android:layout_width="285dip"
+            android:layout_height="wrap_content"
+            android:orientation="horizontal" >
+
+            <include
+                layout="@android:layout/recent_apps_icon"
+                android:id="@+id/button1" />
+    
+            <include
+                layout="@android:layout/recent_apps_icon"
+                android:id="@+id/button2" />
+    
+            <include
+                layout="@android:layout/recent_apps_icon"
+                android:id="@+id/button3" />
+    
+        </LinearLayout>
+        
+        <LinearLayout
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:orientation="horizontal" >
+            
+            <include
+                layout="@android:layout/recent_apps_icon"
+                android:id="@+id/button4" />
+            
+            <include
+                layout="@android:layout/recent_apps_icon"
+                android:id="@+id/button5" />
+            
+            <include
+                layout="@android:layout/recent_apps_icon"
+                android:id="@+id/button6" />
+                
+        </LinearLayout>    
+    </LinearLayout>
+</FrameLayout>
diff --git a/core/res/res/layout/recent_apps_icon.xml b/core/res/res/layout/recent_apps_icon.xml
new file mode 100644
index 0000000..b8cf089
--- /dev/null
+++ b/core/res/res/layout/recent_apps_icon.xml
@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 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.
+*/
+-->
+
+<!-- This is not a standalone element - it is imported into recent_apps_dialog.xml -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="87dip"
+    android:layout_height="78dip"
+    android:layout_margin="3dip"
+    android:orientation="vertical"
+    android:gravity="center_vertical"
+    style="?android:attr/buttonStyle"
+    android:background="@drawable/btn_application_selector">
+    <ImageView android:id="@+id/icon"
+        android:layout_width="@android:dimen/app_icon_size"
+        android:layout_height="@android:dimen/app_icon_size"
+        android:layout_gravity="center_horizontal"
+        android:scaleType="fitCenter" />
+    <TextView android:id="@+id/label"
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content"
+        android:textSize="12dip"
+        android:maxLines="1"
+        android:ellipsize="end"
+        android:duplicateParentState="true"
+        android:textColor="@color/primary_text_dark_focused"
+        android:gravity="center_horizontal" />
+</LinearLayout>
diff --git a/core/res/res/layout/resolve_list_item.xml b/core/res/res/layout/resolve_list_item.xml
new file mode 100644
index 0000000..5e296c5
--- /dev/null
+++ b/core/res/res/layout/resolve_list_item.xml
@@ -0,0 +1,54 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* //device/apps/common/res/any/layout/resolve_list_item.xml
+**
+** Copyright 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.
+*/
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:gravity="center_vertical"
+    android:orientation="horizontal"
+    android:minHeight="?android:attr/listPreferredItemHeight"
+    android:layout_height="wrap_content"
+    android:layout_width="fill_parent"
+    android:paddingLeft="14dip"
+    android:paddingRight="15dip">
+
+    <!-- Activity icon when presenting dialog -->
+    <ImageView android:id="@+id/icon"
+        android:layout_width="@android:dimen/app_icon_size"
+        android:layout_height="@android:dimen/app_icon_size"
+        android:scaleType="fitCenter" />
+
+    <LinearLayout
+        android:orientation="vertical"
+        android:gravity="center_vertical"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content" >
+        <!-- Activity name -->
+        <TextView android:id="@android:id/text1"
+            android:textAppearance="?android:attr/textAppearanceLargeInverse"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:paddingLeft="6dip" />
+        <!-- Extended activity info to distinguish between duplicate activity names -->
+        <TextView android:id="@android:id/text2"
+            android:textAppearance="?android:attr/textAppearanceMediumInverse"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:paddingLeft="6dip" />
+    </LinearLayout>
+</LinearLayout>
+
diff --git a/core/res/res/layout/safe_mode.xml b/core/res/res/layout/safe_mode.xml
new file mode 100644
index 0000000..8a8d19e
--- /dev/null
+++ b/core/res/res/layout/safe_mode.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<TextView xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="wrap_content" android:layout_height="wrap_content"
+    android:textAppearance="?android:attr/textAppearanceLarge"
+    android:gravity="center"
+    android:padding="3dp"
+    android:background="@drawable/safe_mode_background"
+    android:textColor="@color/safe_mode_text"
+    android:text="@string/safeMode"
+/>
diff --git a/core/res/res/layout/screen.xml b/core/res/res/layout/screen.xml
new file mode 100644
index 0000000..ded97e2
--- /dev/null
+++ b/core/res/res/layout/screen.xml
@@ -0,0 +1,99 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<!--
+This is the basic layout for a screen, with all of its features enabled.
+-->
+
+<!-- Title bar and content -->
+<LinearLayout  xmlns:android="http://schemas.android.com/apk/res/android"
+    android:fitsSystemWindows="true"
+    android:orientation="vertical"
+    android:layout_width="fill_parent"
+    android:layout_height="fill_parent"
+>
+    <!-- Title bar -->
+    <RelativeLayout android:id="@android:id/title_container"
+        style="?android:attr/windowTitleBackgroundStyle"
+        android:layout_width="fill_parent"
+        android:layout_height="?android:attr/windowTitleSize"
+    >
+        <ImageView android:id="@android:id/left_icon"
+            android:layout_width="16dip"
+            android:layout_height="16dip"
+            android:layout_marginRight="5dip"
+            android:layout_alignParentLeft="true"
+            android:layout_centerVertical="true"
+            android:visibility="gone"
+            android:scaleType="fitCenter"
+        />
+        <LinearLayout android:id="@+android:id/right_container"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_alignParentRight="true"
+            android:layout_centerVertical="true"
+        >
+            <ImageView android:id="@android:id/right_icon"
+                android:layout_width="16dip"
+                android:layout_height="16dip"
+                android:layout_marginLeft="5dip"
+                android:layout_gravity="center_vertical"
+                android:visibility="gone"
+                android:scaleType="fitCenter"
+            />
+            <ProgressBar android:id="@+id/progress_circular"
+                style="?android:attr/progressBarStyleSmallTitle"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginLeft="5dip"
+                android:layout_gravity="center_vertical"
+                android:visibility="gone"
+                android:max="10000"
+            />
+        </LinearLayout>
+        <ProgressBar android:id="@+id/progress_horizontal"
+            style="?android:attr/progressBarStyleHorizontal"
+            android:layout_width="fill_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginLeft="2dip"
+            android:layout_toLeftOf="@android:id/right_container"
+            android:layout_toRightOf="@android:id/left_icon"
+            android:layout_centerVertical="true"
+            android:visibility="gone"
+            android:max="10000"
+        />
+        <TextView android:id="@android:id/title"
+            style="?android:attr/windowTitleStyle"
+            android:layout_width="fill_parent"
+            android:layout_height="fill_parent"
+            android:background="@null"
+            android:fadingEdge="horizontal"
+            android:scrollHorizontally="true"
+            android:gravity="center_vertical"
+            android:layout_toLeftOf="@android:id/right_container"
+            android:layout_toRightOf="@id/left_icon"
+        />
+    </RelativeLayout>
+
+    <!-- Content -->
+    <FrameLayout android:id="@android:id/content"
+        android:layout_width="fill_parent"
+        android:layout_height="0dip"
+        android:layout_weight="1"
+        android:foregroundGravity="fill_horizontal|top"
+        android:foreground="?android:attr/windowContentOverlay"
+    />
+</LinearLayout>
diff --git a/core/res/res/layout/screen_custom_title.xml b/core/res/res/layout/screen_custom_title.xml
new file mode 100644
index 0000000..12ed1d0
--- /dev/null
+++ b/core/res/res/layout/screen_custom_title.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<!--
+This is an custom layout for a screen.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:orientation="vertical"
+    android:fitsSystemWindows="true">
+    <FrameLayout android:id="@android:id/title_container" 
+        android:layout_width="fill_parent" 
+        android:layout_height="?android:attr/windowTitleSize"
+        style="?android:attr/windowTitleBackgroundStyle">
+    </FrameLayout>
+    <FrameLayout android:id="@android:id/content"
+        android:layout_width="fill_parent" 
+        android:layout_height="0dip"
+        android:layout_weight="1"
+        android:foregroundGravity="fill_horizontal|top"
+        android:foreground="?android:attr/windowContentOverlay" />
+</LinearLayout>
+
diff --git a/core/res/res/layout/screen_progress.xml b/core/res/res/layout/screen_progress.xml
new file mode 100644
index 0000000..e3347e4
--- /dev/null
+++ b/core/res/res/layout/screen_progress.xml
@@ -0,0 +1,77 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* //device/apps/common/assets/res/layout/screen_full.xml
+**
+** Copyright 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.
+*/
+
+This is the basic layout for a screen, with all of its features enabled.
+-->
+
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:fitsSystemWindows="true"
+>
+    <LinearLayout
+        android:orientation="vertical"
+        android:layout_width="fill_parent"
+        android:layout_height="fill_parent"
+    >
+        <RelativeLayout android:id="@android:id/title_container" 
+            style="?android:attr/windowTitleBackgroundStyle"
+            android:layout_width="fill_parent" 
+            android:layout_height="?android:attr/windowTitleSize"
+        >
+            <ProgressBar android:id="@+android:id/progress_circular"
+                style="?android:attr/progressBarStyleSmallTitle"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginLeft="5dip"
+                android:layout_alignParentRight="true"
+                android:layout_centerVertical="true"
+                android:visibility="gone"
+                android:max="10000"
+            />
+            <ProgressBar android:id="@+android:id/progress_horizontal"
+                style="?android:attr/progressBarStyleHorizontal"
+                android:layout_width="fill_parent"
+                android:layout_height="wrap_content"
+                android:layout_marginLeft="2dip"
+                android:layout_alignParentLeft="true"
+                android:layout_toLeftOf="@android:id/progress_circular"
+                android:layout_centerVertical="true"
+                android:visibility="gone"
+                android:max="10000" 
+            />
+            <TextView android:id="@android:id/title"
+                style="?android:attr/windowTitleStyle"
+                android:layout_width="fill_parent" 
+                android:layout_height="fill_parent"
+                android:layout_alignParentLeft="true"
+                android:layout_toLeftOf="@android:id/progress_circular"
+                android:background="@null"
+                android:fadingEdge="horizontal"
+                android:gravity="center_vertical"
+                android:scrollHorizontally="true" 
+            />
+        </RelativeLayout>
+        <FrameLayout android:id="@android:id/content"
+            android:layout_width="fill_parent"
+            android:layout_height="0dip"
+            android:layout_weight="1"
+            android:foregroundGravity="fill_horizontal|top"
+            android:foreground="?android:attr/windowContentOverlay"
+        />
+    </LinearLayout>
+</FrameLayout>
diff --git a/core/res/res/layout/screen_simple.xml b/core/res/res/layout/screen_simple.xml
new file mode 100644
index 0000000..62e737a
--- /dev/null
+++ b/core/res/res/layout/screen_simple.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* //device/apps/common/assets/res/layout/screen_simple.xml
+**
+** Copyright 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.
+*/
+
+This is an optimized layout for a screen, with the minimum set of features
+enabled.
+-->
+
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+     android:id="@android:id/content"
+     android:fitsSystemWindows="true">
+</FrameLayout>
+
diff --git a/core/res/res/layout/screen_title.xml b/core/res/res/layout/screen_title.xml
new file mode 100644
index 0000000..5fcd2dd
--- /dev/null
+++ b/core/res/res/layout/screen_title.xml
@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<!--
+This is an optimized layout for a screen, with the minimum set of features
+enabled.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:orientation="vertical"
+    android:fitsSystemWindows="true">
+    <FrameLayout
+        android:layout_width="fill_parent" 
+        android:layout_height="?android:attr/windowTitleSize"
+        style="?android:attr/windowTitleBackgroundStyle">
+        <TextView android:id="@android:id/title" 
+            style="?android:attr/windowTitleStyle"
+            android:background="@null"
+            android:fadingEdge="horizontal"
+            android:gravity="center_vertical"
+            android:layout_width="fill_parent"
+            android:layout_height="fill_parent" />
+    </FrameLayout>
+    <FrameLayout android:id="@android:id/content"
+        android:layout_width="fill_parent" 
+        android:layout_height="0dip"
+        android:layout_weight="1"
+        android:foregroundGravity="fill_horizontal|top"
+        android:foreground="?android:attr/windowContentOverlay" />
+</LinearLayout>
+
diff --git a/core/res/res/layout/screen_title_icons.xml b/core/res/res/layout/screen_title_icons.xml
new file mode 100644
index 0000000..4d7a6c8
--- /dev/null
+++ b/core/res/res/layout/screen_title_icons.xml
@@ -0,0 +1,94 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<!--
+This is the basic layout for a screen, with all of its features enabled.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:fitsSystemWindows="true"
+    android:orientation="vertical"
+    android:layout_width="fill_parent"
+    android:layout_height="fill_parent">
+    <RelativeLayout android:id="@android:id/title_container"
+        style="?android:attr/windowTitleBackgroundStyle"
+        android:layout_width="fill_parent"
+        android:layout_height="?android:attr/windowTitleSize">
+        <!-- The title background has 9px left padding. -->
+        <ImageView android:id="@android:id/left_icon"
+            android:visibility="gone"
+            android:layout_marginRight="9dip"
+            android:layout_width="16dip"
+            android:layout_height="16dip"
+            android:scaleType="fitCenter"
+            android:layout_alignParentLeft="true"
+            android:layout_centerVertical="true" />
+        <ProgressBar android:id="@+id/progress_circular"
+            style="?android:attr/progressBarStyleSmallTitle"
+            android:visibility="gone"
+            android:max="10000"
+            android:layout_centerVertical="true"
+            android:layout_alignParentRight="true"
+            android:layout_marginLeft="6dip"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content" />
+        <!-- There are 6dip between this and the circular progress on the right, we
+             also make 6dip (with the -3dip margin_left) to the icon on the left or
+             the screen left edge if no icon. This also places our left edge 3dip to
+             the left of the title text left edge. -->
+        <ProgressBar android:id="@+id/progress_horizontal"
+            style="?android:attr/progressBarStyleHorizontal"
+            android:layout_width="fill_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginLeft="-3dip"
+            android:layout_toLeftOf="@android:id/progress_circular"
+            android:layout_toRightOf="@android:id/left_icon"
+            android:layout_centerVertical="true"
+            android:visibility="gone"
+            android:max="10000" />
+        <LinearLayout
+            android:layout_width="fill_parent"
+            android:layout_height="fill_parent"
+            android:orientation="horizontal"
+            android:layout_toLeftOf="@id/progress_circular"
+            android:layout_toRightOf="@android:id/left_icon"
+            >
+            <!-- 2dip between the icon and the title text, if icon is present. -->
+            <ImageView android:id="@android:id/right_icon"
+                android:visibility="gone"
+                android:layout_width="16dip"
+                android:layout_height="16dip"
+                android:layout_gravity="center_vertical"
+                android:scaleType="fitCenter"
+                android:layout_marginRight="2dip" />
+            <TextView android:id="@android:id/title"
+                style="?android:attr/windowTitleStyle"
+                android:layout_width="fill_parent"
+                android:layout_height="fill_parent"
+                android:background="@null"
+                android:fadingEdge="horizontal"
+                android:scrollHorizontally="true"
+                android:gravity="center_vertical"
+                />
+            </LinearLayout>
+    </RelativeLayout>
+    <FrameLayout android:id="@android:id/content"
+        android:layout_width="fill_parent"
+        android:layout_height="0dip"
+        android:layout_weight="1"
+        android:foregroundGravity="fill_horizontal|top"
+        android:foreground="?android:attr/windowContentOverlay" />
+</LinearLayout>
diff --git a/core/res/res/layout/search_bar.xml b/core/res/res/layout/search_bar.xml
new file mode 100644
index 0000000..ef347da
--- /dev/null
+++ b/core/res/res/layout/search_bar.xml
@@ -0,0 +1,108 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* apps/common/res/layout/SearchBar.xml
+**
+** Copyright 2007, 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.
+*/
+-->
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/search_bar"
+    android:layout_width="fill_parent"
+    android:layout_height="fill_parent"
+    android:paddingBottom="200dip"
+    android:orientation="vertical" 
+    android:focusable="true"
+    android:descendantFocusability="afterDescendants">
+    <!-- android:paddingBottom="14dip"  TODO MUST FIX - it's a hack to get the popup to show -->
+
+    <!-- Outer layout defines the entire search bar at the top of the screen -->
+    <!-- Bottom padding of 16 is due to the graphic, with 9 extra pixels of drop
+         shadow, plus the desired padding of "8" against the user-visible (grey)
+         pixels, minus "1" to correct for positioning of the edittext & button. -->
+    <LinearLayout
+        android:id="@+id/search_plate"
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content"
+        android:orientation="vertical"
+        android:paddingLeft="8dip"
+        android:paddingRight="8dip"
+        android:paddingTop="6dip"
+        android:paddingBottom="16dip"
+        android:baselineAligned="false"
+        android:background="@android:drawable/search_plate"
+        android:addStatesFromChildren="true" >
+
+        <!-- This is actually used for the badge icon *or* the badge label (or neither) -->
+        <TextView 
+            android:id="@+id/search_badge"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:paddingLeft="2dip"
+            android:drawablePadding="0dip"
+            android:textAppearance="?android:attr/textAppearanceSmall"
+            android:textColor="?android:attr/textColorPrimary" />
+
+        <!-- Inner layout contains the button(s) and EditText -->
+        <!-- The layout_marginTop of "1" corrects for the extra 1 pixel of padding at the top of 
+             textfield_selected.9.png.  The "real" margin as displayed is "2". -->
+        <!-- The layout_marginBottom of "-5" corrects for the spacing we see at the 
+             bottom of the edittext and button images.  The "real" margin as displayed is "8" -->
+        <LinearLayout
+            android:id="@+id/search_edit_frame"
+            android:layout_width="fill_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginTop="1dip"
+            android:layout_marginBottom="-5dip"
+            android:orientation="horizontal"
+            android:addStatesFromChildren="true"
+            android:gravity="center_vertical"
+            android:baselineAligned="false" >
+
+            <view class="android.app.SearchDialog$SearchAutoComplete"
+                android:id="@+id/search_src_text"
+                android:layout_height="wrap_content"
+                android:layout_width="0dip"
+                android:layout_weight="1.0"
+                android:paddingLeft="8dip"
+                android:paddingRight="6dip"
+                android:inputType="text|textAutoComplete"
+                android:dropDownWidth="fill_parent"
+                android:dropDownAnchor="@id/search_plate"
+                android:dropDownVerticalOffset="-15dip"
+                />
+                <!-- android:focusableInTouchMode="false" -->
+                <!-- android:singleLine="true" -->
+                <!-- android:selectAllOnFocus="true" -->
+                
+            <!-- This button can switch between text and icon "modes" -->
+            <Button 
+                android:id="@+id/search_go_btn"
+                android:layout_marginLeft="1dip"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:drawableLeft="@android:drawable/ic_btn_search"
+            />
+
+            <ImageButton android:id="@+id/search_voice_btn"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:src="@android:drawable/ic_btn_speak_now"
+            />
+        </LinearLayout>
+        
+    </LinearLayout>
+
+</LinearLayout>
diff --git a/core/res/res/layout/search_dropdown_app_selector.xml b/core/res/res/layout/search_dropdown_app_selector.xml
new file mode 100644
index 0000000..f86645f
--- /dev/null
+++ b/core/res/res/layout/search_dropdown_app_selector.xml
@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* //device/apps/common/res/layout/search_dropdown_app_selector.xml
+**
+** Copyright 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.
+*/
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="fill_parent"
+    android:layout_height="?android:attr/listPreferredItemHeight"
+    android:orientation="horizontal"
+    android:gravity="center_vertical"
+    android:baselineAligned="false"
+    >
+
+    <ImageView android:id="@+id/search_app_icon1"
+        android:layout_width="32dip"
+        android:layout_height="32dip"
+        android:layout_gravity="center_vertical"
+        android:scaleType="fitCenter"
+        android:src="@android:drawable/ic_search_category_default" />
+        
+    <TextView android:id="@+id/search_app_text1"
+        style="?android:attr/dropDownItemStyle"
+        android:singleLine="true"
+        android:layout_height="wrap_content"
+        android:layout_width="0dip"
+        android:layout_weight="1"
+        android:layout_gravity="center_vertical" />
+    
+</LinearLayout>
diff --git a/core/res/res/layout/search_dropdown_item_1line.xml b/core/res/res/layout/search_dropdown_item_1line.xml
new file mode 100644
index 0000000..3827206
--- /dev/null
+++ b/core/res/res/layout/search_dropdown_item_1line.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* //device/apps/common/assets/res/any/layout/simple_spinner_item.xml
+**
+** Copyright 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.
+*/
+-->
+<TextView xmlns:android="http://schemas.android.com/apk/res/android" 
+    android:id="@android:id/text1"
+    style="?android:attr/dropDownItemStyle"
+    android:textAppearance="?android:attr/textAppearanceMediumInverse"
+    android:singleLine="true"
+    android:layout_width="fill_parent"
+    android:layout_height="?android:attr/listPreferredItemHeight" />
diff --git a/core/res/res/layout/search_dropdown_item_2line.xml b/core/res/res/layout/search_dropdown_item_2line.xml
new file mode 100644
index 0000000..96d6005
--- /dev/null
+++ b/core/res/res/layout/search_dropdown_item_2line.xml
@@ -0,0 +1,58 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* //device/apps/common/assets/res/any/layout/simple_spinner_item.xml
+**
+** Copyright 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.
+*/
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 
+    android:layout_width="fill_parent"
+    android:layout_height="?android:attr/listPreferredItemHeight"
+    android:orientation="horizontal"
+    android:gravity="center_vertical"
+    android:baselineAligned="false"
+    >
+    
+    <TwoLineListItem
+        android:paddingTop="2dip"
+        android:paddingBottom="2dip"
+        android:layout_width="0dip"
+        android:layout_weight="1"
+        android:layout_height="wrap_content"
+        android:mode="twoLine" >
+    
+        <TextView
+            android:id="@android:id/text1"
+            style="?android:attr/dropDownItemStyle"
+            android:textAppearance="?android:attr/textAppearanceMediumInverse"
+            android:singleLine="true"
+            android:layout_width="fill_parent"
+            android:layout_height="wrap_content" />
+    
+        <TextView
+            android:id="@android:id/text2"
+            style="?android:attr/dropDownItemStyle"
+            android:textAppearance="?android:attr/textAppearanceSmallInverse"
+            android:textColor="?android:attr/textColorSecondaryInverse"
+            android:singleLine="true"
+            android:layout_width="fill_parent"
+            android:layout_height="wrap_content"
+            android:layout_below="@android:id/text1"
+            android:layout_alignLeft="@android:id/text1" />
+    
+    </TwoLineListItem>
+
+</LinearLayout>
diff --git a/core/res/res/layout/search_dropdown_item_icons_1line.xml b/core/res/res/layout/search_dropdown_item_icons_1line.xml
new file mode 100644
index 0000000..c0713d5
--- /dev/null
+++ b/core/res/res/layout/search_dropdown_item_icons_1line.xml
@@ -0,0 +1,52 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* //device/apps/common/assets/res/any/layout/simple_spinner_item.xml
+**
+** Copyright 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.
+*/
+-->
+
+    <!-- NOTE: The appearance of the inner text element must match the appearance -->
+    <!-- of the text element in apps/common/res/layout/simple_dropdown_item_1line.xml -->
+    
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 
+    android:layout_width="fill_parent"
+    android:layout_height="?android:attr/listPreferredItemHeight"
+    android:orientation="horizontal"
+    android:gravity="center_vertical"
+    android:baselineAligned="false"
+    >
+    
+    <ImageView android:id="@android:id/icon1"
+        android:layout_width="32dip"
+        android:layout_height="32dip"
+        android:layout_gravity="center_vertical"
+        android:scaleType="fitCenter" />
+
+    <TextView android:id="@android:id/text1"
+        style="?android:attr/dropDownItemStyle"
+        android:textAppearance="?android:attr/textAppearanceMediumInverse"
+        android:singleLine="true"
+        android:layout_height="wrap_content"
+        android:layout_width="0dip"
+        android:layout_weight="1"  />
+
+    <ImageView android:id="@android:id/icon2"
+        android:layout_width="32dip"
+        android:layout_height="32dip"
+        android:layout_gravity="center_vertical"
+        android:scaleType="fitCenter" />
+
+</LinearLayout>
diff --git a/core/res/res/layout/search_dropdown_item_icons_2line.xml b/core/res/res/layout/search_dropdown_item_icons_2line.xml
new file mode 100644
index 0000000..ad1c905
--- /dev/null
+++ b/core/res/res/layout/search_dropdown_item_icons_2line.xml
@@ -0,0 +1,73 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* //device/apps/common/assets/res/any/layout/simple_spinner_item.xml
+**
+** Copyright 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.
+*/
+-->
+
+    <!-- NOTE: The appearance of the inner text element must match the appearance -->
+    <!-- of the text element in apps/common/res/layout/simple_dropdown_item_2line.xml -->
+    
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 
+    android:layout_width="fill_parent"
+    android:layout_height="?android:attr/listPreferredItemHeight"
+    android:orientation="horizontal"
+    android:gravity="center_vertical"
+    android:baselineAligned="false"
+    >
+    
+    <ImageView android:id="@android:id/icon1"
+        android:layout_width="32dip"
+        android:layout_height="32dip"
+        android:layout_gravity="center_vertical"
+        android:scaleType="fitCenter" />
+
+    <TwoLineListItem
+        android:paddingTop="2dip"
+        android:paddingBottom="2dip"
+        android:layout_width="0dip"
+        android:layout_weight="1"
+        android:layout_height="wrap_content"
+        android:mode="twoLine" >
+    
+        <TextView
+            android:id="@android:id/text1"
+            style="?android:attr/dropDownItemStyle"
+            android:textAppearance="?android:attr/textAppearanceMediumInverse"
+            android:singleLine="true"
+            android:layout_width="fill_parent"
+            android:layout_height="wrap_content" />
+    
+        <TextView
+            android:id="@android:id/text2"
+            style="?android:attr/dropDownItemStyle"
+            android:textAppearance="?android:attr/textAppearanceSmallInverse"
+            android:textColor="?android:attr/textColorSecondaryInverse"
+            android:singleLine="true"
+            android:layout_width="fill_parent"
+            android:layout_height="wrap_content"
+            android:layout_below="@android:id/text1"
+            android:layout_alignLeft="@android:id/text1" />
+    
+    </TwoLineListItem>
+
+    <ImageView android:id="@android:id/icon2"
+        android:layout_width="32dip"
+        android:layout_height="32dip"
+        android:layout_gravity="center_vertical"
+        android:scaleType="fitCenter" />
+
+</LinearLayout>
diff --git a/core/res/res/layout/seekbar_dialog.xml b/core/res/res/layout/seekbar_dialog.xml
new file mode 100644
index 0000000..f61f435
--- /dev/null
+++ b/core/res/res/layout/seekbar_dialog.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+        android:layout_width="fill_parent"
+        android:layout_height="fill_parent"
+        android:orientation="vertical"
+        android:gravity="center_horizontal">
+        
+    <ImageView android:id="@+id/icon"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:paddingTop="20dip" />
+            
+    <SeekBar android:id="@+id/seekbar"
+            android:layout_width="fill_parent"
+            android:layout_height="wrap_content"
+            android:padding="20dip" />
+        
+</LinearLayout>
+ 
diff --git a/core/res/res/layout/select_dialog.xml b/core/res/res/layout/select_dialog.xml
new file mode 100644
index 0000000..8e48ae2
--- /dev/null
+++ b/core/res/res/layout/select_dialog.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* //device/apps/common/res/layout/select_dialog.xml
+**
+** Copyright 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.
+*/
+-->
+
+<!--
+    This layout file is used by the AlertDialog when displaying a list of items.
+    This layout file is inflated and used as the ListView to display the items.
+    Assign an ID so its state will be saved/restored.
+-->
+<ListView xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+android:id/select_dialog_listview"
+    android:layout_width="fill_parent"
+    android:layout_height="fill_parent"
+    android:layout_marginTop="5px"
+    android:cacheColorHint="@null"
+    android:divider="@android:drawable/divider_horizontal_bright"
+    android:scrollbars="vertical" />
diff --git a/core/res/res/layout/select_dialog_item.xml b/core/res/res/layout/select_dialog_item.xml
new file mode 100644
index 0000000..f1840ba
--- /dev/null
+++ b/core/res/res/layout/select_dialog_item.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* //device/apps/common/res/layout/select_dialog_item.xml
+**
+** Copyright 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.
+*/
+-->
+
+<!--
+    This layout file is used by the AlertDialog when displaying a list of items.
+    This layout file is inflated and used as the TextView to display individual
+    items.
+-->
+<TextView xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@android:id/text1"
+    android:layout_width="fill_parent"
+    android:layout_height="wrap_content"
+    android:minHeight="?android:attr/listPreferredItemHeight"
+    android:textAppearance="?android:attr/textAppearanceLarge"
+    android:textColor="@android:color/bright_foreground_light"
+    android:gravity="center_vertical"
+    android:paddingLeft="14dip"
+    android:paddingRight="15dip"
+    android:ellipsize="marquee"
+/>
diff --git a/core/res/res/layout/select_dialog_multichoice.xml b/core/res/res/layout/select_dialog_multichoice.xml
new file mode 100644
index 0000000..3bd1a48
--- /dev/null
+++ b/core/res/res/layout/select_dialog_multichoice.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<CheckedTextView xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@android:id/text1"
+    android:layout_width="fill_parent"
+    android:layout_height="wrap_content"
+    android:minHeight="?android:attr/listPreferredItemHeight"
+    android:textAppearance="?android:attr/textAppearanceLargeInverse"
+    android:gravity="center_vertical"
+    android:paddingLeft="12dip"
+    android:paddingRight="7dip"
+    android:checkMark="@android:drawable/btn_check"
+    android:ellipsize="marquee"
+/>
+
diff --git a/core/res/res/layout/select_dialog_singlechoice.xml b/core/res/res/layout/select_dialog_singlechoice.xml
new file mode 100644
index 0000000..ec97d7b
--- /dev/null
+++ b/core/res/res/layout/select_dialog_singlechoice.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<CheckedTextView xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@android:id/text1"
+    android:layout_width="fill_parent"
+    android:layout_height="wrap_content"
+    android:minHeight="?android:attr/listPreferredItemHeight"
+    android:textAppearance="?android:attr/textAppearanceLargeInverse"
+    android:gravity="center_vertical"
+    android:paddingLeft="12dip"
+    android:paddingRight="7dip"
+    android:checkMark="@android:drawable/btn_radio"
+    android:ellipsize="marquee"
+/>
diff --git a/core/res/res/layout/setting_list_category.xml b/core/res/res/layout/setting_list_category.xml
new file mode 100644
index 0000000..e605d17
--- /dev/null
+++ b/core/res/res/layout/setting_list_category.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2007 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.
+-->
+
+<!--  List item layout for an unexpanded category -->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:orientation="vertical"
+    android:layout_width="fill_parent"
+    android:layout_height="wrap_content"
+    android:paddingLeft="32dip"
+    android:paddingBottom="5dip"
+    android:paddingTop="5dip">
+    
+    <TextView
+        android:id="@+id/category_name"
+        android:textStyle="bold"
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content" />
+        
+    <TextView
+        android:id="@+id/category_description"
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content"
+        android:lines="1" 
+        android:textColor="@color/darker_gray" />
+        
+</LinearLayout>
diff --git a/core/res/res/layout/setting_list_expanded_category.xml b/core/res/res/layout/setting_list_expanded_category.xml
new file mode 100644
index 0000000..64de0e6
--- /dev/null
+++ b/core/res/res/layout/setting_list_expanded_category.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2007 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.
+-->
+
+<!--  List item layout for an expanded category -->
+<TextView xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/category_name"
+    android:textStyle="bold" 
+    android:paddingLeft="32dip"
+    android:paddingBottom="5dip"
+    android:paddingTop="5dip"/>
diff --git a/core/res/res/layout/setting_list_setting.xml b/core/res/res/layout/setting_list_setting.xml
new file mode 100644
index 0000000..6b7d254
--- /dev/null
+++ b/core/res/res/layout/setting_list_setting.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2007 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.
+-->
+
+<!--  List item layout for a setting -->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="fill_parent"
+    android:layout_height="wrap_content"
+    android:paddingLeft="48dip"
+    android:paddingTop="3dip"
+    android:paddingBottom="3dip"
+    android:orientation="vertical">
+
+    <TextView
+        android:id="@+id/setting_name"
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content"
+        android:textColor="@color/lighter_gray" />
+
+    <LinearLayout
+        android:id="@+id/setting_value_layout"
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content"
+        android:paddingLeft="10dip" />
+
+</LinearLayout>
diff --git a/core/res/res/layout/setting_list_setting_value_text.xml b/core/res/res/layout/setting_list_setting_value_text.xml
new file mode 100644
index 0000000..621298e
--- /dev/null
+++ b/core/res/res/layout/setting_list_setting_value_text.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2007 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.
+-->
+
+<!--  Template for the generic static text in a setting's value -->
+<TextView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/setting_value_text"
+    android:layout_width="fill_parent"
+    android:layout_height="wrap_content"
+    android:textColor="@color/darker_gray" />
diff --git a/core/res/res/layout/simple_dropdown_hint.xml b/core/res/res/layout/simple_dropdown_hint.xml
new file mode 100644
index 0000000..44be46d
--- /dev/null
+++ b/core/res/res/layout/simple_dropdown_hint.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 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.
+*/
+-->
+<TextView xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@android:id/text1"
+    android:textAppearance="?android:attr/dropDownHintAppearance"
+    android:singleLine="true"
+    android:layout_marginLeft="3dip"
+    android:layout_marginTop="3dip"
+    android:layout_marginRight="3dip"
+    android:layout_marginBottom="3dip"
+    android:layout_width="fill_parent"
+    android:layout_height="wrap_content" />
diff --git a/core/res/res/layout/simple_dropdown_item_1line.xml b/core/res/res/layout/simple_dropdown_item_1line.xml
new file mode 100644
index 0000000..5745d15
--- /dev/null
+++ b/core/res/res/layout/simple_dropdown_item_1line.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* //device/apps/common/assets/res/any/layout/simple_spinner_item.xml
+**
+** Copyright 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.
+*/
+-->
+<TextView xmlns:android="http://schemas.android.com/apk/res/android" 
+    android:id="@android:id/text1"
+    style="?android:attr/dropDownItemStyle"
+    android:textAppearance="?android:attr/textAppearanceLargeInverse"
+    android:singleLine="true"
+    android:layout_width="fill_parent"
+    android:layout_height="?android:attr/listPreferredItemHeight"
+    android:ellipsize="marquee" />
diff --git a/core/res/res/layout/simple_dropdown_item_2line.xml b/core/res/res/layout/simple_dropdown_item_2line.xml
new file mode 100644
index 0000000..a04c849
--- /dev/null
+++ b/core/res/res/layout/simple_dropdown_item_2line.xml
@@ -0,0 +1,58 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* //device/apps/common/assets/res/any/layout/simple_spinner_item.xml
+**
+** Copyright 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.
+*/
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 
+    android:layout_width="fill_parent"
+    android:layout_height="?android:attr/listPreferredItemHeight"
+    android:orientation="horizontal"
+    android:gravity="center_vertical"
+    android:baselineAligned="false"
+    >
+    
+    <TwoLineListItem
+        android:paddingTop="2dip"
+        android:paddingBottom="2dip"
+        android:layout_width="0dip"
+        android:layout_weight="1"
+        android:layout_height="wrap_content"
+        android:mode="twoLine" >
+    
+        <TextView
+            android:id="@android:id/text1"
+            style="?android:attr/dropDownItemStyle"
+            android:textAppearance="?android:attr/textAppearanceLargeInverse"
+            android:singleLine="true"
+            android:layout_width="fill_parent"
+            android:layout_height="wrap_content" />
+    
+        <TextView
+            android:id="@android:id/text2"
+            style="?android:attr/dropDownItemStyle"
+            android:textAppearance="?android:attr/textAppearanceSmallInverse"
+            android:textColor="#323232"
+            android:singleLine="true"
+            android:layout_width="fill_parent"
+            android:layout_height="wrap_content"
+            android:layout_below="@android:id/text1"
+            android:layout_alignLeft="@android:id/text1" />
+    
+    </TwoLineListItem>
+
+</LinearLayout>
diff --git a/core/res/res/layout/simple_expandable_list_item_1.xml b/core/res/res/layout/simple_expandable_list_item_1.xml
new file mode 100644
index 0000000..052b353
--- /dev/null
+++ b/core/res/res/layout/simple_expandable_list_item_1.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<TextView xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@android:id/text1"
+    android:layout_width="fill_parent"
+    android:layout_height="?android:attr/listPreferredItemHeight"
+    android:paddingLeft="?android:attr/expandableListPreferredItemPaddingLeft"
+    android:textAppearance="?android:attr/textAppearanceLarge"
+    android:gravity="center_vertical"
+/>
diff --git a/core/res/res/layout/simple_expandable_list_item_2.xml b/core/res/res/layout/simple_expandable_list_item_2.xml
new file mode 100644
index 0000000..741f1db
--- /dev/null
+++ b/core/res/res/layout/simple_expandable_list_item_2.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<TwoLineListItem xmlns:android="http://schemas.android.com/apk/res/android" 
+    android:layout_width="fill_parent"
+    android:layout_height="?android:attr/listPreferredItemHeight"
+    android:paddingTop="2dip"
+    android:paddingBottom="2dip"
+    android:paddingLeft="?android:attr/expandableListPreferredItemPaddingLeft"
+    android:mode="twoLine"
+>
+
+    <TextView android:id="@android:id/text1"
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="6dip"
+        android:textAppearance="?android:attr/textAppearanceLarge"
+    />
+
+    <TextView android:id="@android:id/text2"
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content"
+        android:layout_below="@android:id/text1"
+        android:layout_alignLeft="@android:id/text1"
+        android:textAppearance="?android:attr/textAppearanceSmall"
+    />
+
+</TwoLineListItem>
diff --git a/core/res/res/layout/simple_gallery_item.xml b/core/res/res/layout/simple_gallery_item.xml
new file mode 100644
index 0000000..28cb15b
--- /dev/null
+++ b/core/res/res/layout/simple_gallery_item.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* //device/apps/common/res/layout/simple_gallery_item.xml
+**
+** Copyright 2007, 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.
+*/
+-->
+
+<TextView xmlns:android="http://schemas.android.com/apk/res/android" android:id="@android:id/text1"
+    android:textAppearance="?android:attr/textAppearanceMedium"
+    android:textColor="?android:attr/textColorPrimaryDisableOnly"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content"
+	android:maxLines="1" />
diff --git a/core/res/res/layout/simple_list_item_1.xml b/core/res/res/layout/simple_list_item_1.xml
new file mode 100644
index 0000000..fe617ac
--- /dev/null
+++ b/core/res/res/layout/simple_list_item_1.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<TextView xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@android:id/text1"
+    android:layout_width="fill_parent"
+    android:layout_height="wrap_content"
+    android:textAppearance="?android:attr/textAppearanceLarge"
+    android:gravity="center_vertical"
+    android:paddingLeft="6dip"
+    android:minHeight="?android:attr/listPreferredItemHeight"
+/>
diff --git a/core/res/res/layout/simple_list_item_2.xml b/core/res/res/layout/simple_list_item_2.xml
new file mode 100644
index 0000000..b5e2385
--- /dev/null
+++ b/core/res/res/layout/simple_list_item_2.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<TwoLineListItem xmlns:android="http://schemas.android.com/apk/res/android" 
+	android:paddingTop="2dip"
+	android:paddingBottom="2dip"
+    android:layout_width="fill_parent"
+    android:layout_height="wrap_content"
+    android:minHeight="?android:attr/listPreferredItemHeight"
+    android:mode="twoLine"
+>
+    
+	<TextView android:id="@android:id/text1"
+		android:layout_width="fill_parent"
+		android:layout_height="wrap_content"
+        android:layout_marginLeft="6dip"
+        android:layout_marginTop="6dip"
+		android:textAppearance="?android:attr/textAppearanceLarge"
+	/>
+		
+	<TextView android:id="@android:id/text2"
+		android:layout_width="fill_parent"
+		android:layout_height="wrap_content"
+		android:layout_below="@android:id/text1"
+        android:layout_alignLeft="@android:id/text1"
+		android:textAppearance="?android:attr/textAppearanceSmall"
+	/>
+
+</TwoLineListItem>
diff --git a/core/res/res/layout/simple_list_item_checked.xml b/core/res/res/layout/simple_list_item_checked.xml
new file mode 100644
index 0000000..95612f6
--- /dev/null
+++ b/core/res/res/layout/simple_list_item_checked.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<CheckedTextView xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@android:id/text1"
+    android:layout_width="fill_parent"
+    android:layout_height="?android:attr/listPreferredItemHeight"
+    android:textAppearance="?android:attr/textAppearanceLarge"
+    android:gravity="center_vertical"
+    android:checkMark="?android:attr/textCheckMark"
+    android:paddingLeft="6dip"
+    android:paddingRight="6dip"
+/>
diff --git a/core/res/res/layout/simple_list_item_multiple_choice.xml b/core/res/res/layout/simple_list_item_multiple_choice.xml
new file mode 100644
index 0000000..102e5fc
--- /dev/null
+++ b/core/res/res/layout/simple_list_item_multiple_choice.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<CheckedTextView xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@android:id/text1"
+    android:layout_width="fill_parent"
+    android:layout_height="?android:attr/listPreferredItemHeight"
+    android:textAppearance="?android:attr/textAppearanceLarge"
+    android:gravity="center_vertical"
+    android:checkMark="?android:attr/listChoiceIndicatorMultiple"
+    android:paddingLeft="6dip"
+    android:paddingRight="6dip"
+/>
diff --git a/core/res/res/layout/simple_list_item_single_choice.xml b/core/res/res/layout/simple_list_item_single_choice.xml
new file mode 100644
index 0000000..326de1d
--- /dev/null
+++ b/core/res/res/layout/simple_list_item_single_choice.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<CheckedTextView xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@android:id/text1"
+    android:layout_width="fill_parent"
+    android:layout_height="?android:attr/listPreferredItemHeight"
+    android:textAppearance="?android:attr/textAppearanceLarge"
+    android:gravity="center_vertical"
+    android:checkMark="?android:attr/listChoiceIndicatorSingle"
+    android:paddingLeft="6dip"
+    android:paddingRight="6dip"
+/>
diff --git a/core/res/res/layout/simple_spinner_dropdown_item.xml b/core/res/res/layout/simple_spinner_dropdown_item.xml
new file mode 100644
index 0000000..7006b09
--- /dev/null
+++ b/core/res/res/layout/simple_spinner_dropdown_item.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* //device/apps/common/assets/res/any/layout/simple_spinner_item.xml
+**
+** Copyright 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.
+*/
+-->
+<CheckedTextView xmlns:android="http://schemas.android.com/apk/res/android" 
+    android:id="@android:id/text1"
+    style="?android:attr/spinnerDropDownItemStyle"
+    android:singleLine="true"
+    android:layout_width="fill_parent"
+    android:layout_height="?android:attr/listPreferredItemHeight"
+    android:ellipsize="marquee" />
diff --git a/core/res/res/layout/simple_spinner_item.xml b/core/res/res/layout/simple_spinner_item.xml
new file mode 100644
index 0000000..4dd739f
--- /dev/null
+++ b/core/res/res/layout/simple_spinner_item.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* //device/apps/common/assets/res/any/layout/simple_spinner_item.xml
+**
+** Copyright 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.
+*/
+-->
+<TextView xmlns:android="http://schemas.android.com/apk/res/android" 
+    android:id="@android:id/text1"
+	style="?android:attr/spinnerItemStyle"
+    android:singleLine="true"
+    android:layout_width="fill_parent"
+    android:layout_height="wrap_content"
+    android:ellipsize="marquee" />
diff --git a/core/res/res/layout/status_bar.xml b/core/res/res/layout/status_bar.xml
new file mode 100644
index 0000000..9a6b7e8
--- /dev/null
+++ b/core/res/res/layout/status_bar.xml
@@ -0,0 +1,103 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* apps/common/assets/default/default/skins/StatusBar.xml
+**
+** Copyright 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.
+*/
+-->
+
+<!--    android:background="@drawable/status_bar_closed_default_background" -->
+<com.android.server.status.StatusBarView xmlns:android="http://schemas.android.com/apk/res/android" 
+    android:background="@drawable/statusbar_background"
+    android:orientation="vertical"
+    android:focusable="true"
+    android:descendantFocusability="afterDescendants"
+    >
+
+    <LinearLayout android:id="@+id/icons"
+        android:layout_width="fill_parent"
+        android:layout_height="fill_parent"
+        android:orientation="horizontal">
+            
+        <com.android.server.status.IconMerger android:id="@+id/notificationIcons"
+            android:layout_width="0dip"
+            android:layout_weight="1"
+            android:layout_height="fill_parent"
+            android:layout_alignParentLeft="true"
+            android:paddingLeft="6dip"
+            android:gravity="center_vertical"
+            android:orientation="horizontal"/>  
+            
+        <LinearLayout android:id="@+id/statusIcons"
+            android:layout_width="wrap_content"
+            android:layout_height="fill_parent"
+            android:layout_alignParentRight="true"
+            android:paddingRight="6dip"
+            android:gravity="center_vertical"
+            android:orientation="horizontal"/>    
+    </LinearLayout>
+        
+    <LinearLayout android:id="@+id/ticker"
+        android:layout_width="fill_parent"
+        android:layout_height="fill_parent"
+        android:paddingLeft="6dip"
+        android:animationCache="false"
+        android:orientation="horizontal" >
+        <ImageSwitcher android:id="@+id/tickerIcon"
+            android:layout_width="wrap_content"
+            android:layout_height="fill_parent"
+            android:layout_marginRight="8dip"
+            >
+            <com.android.server.status.AnimatedImageView
+                android:layout_width="25dip"
+                android:layout_height="25dip"
+                />
+            <com.android.server.status.AnimatedImageView
+                android:layout_width="25dip"
+                android:layout_height="25dip"
+                />
+        </ImageSwitcher>
+        <com.android.server.status.TickerView android:id="@+id/tickerText"
+            android:layout_width="0dip"
+            android:layout_weight="1"
+            android:layout_height="wrap_content"
+            android:paddingTop="2dip"
+            android:paddingRight="10dip">
+            <TextView
+                android:layout_width="fill_parent"
+                android:layout_height="wrap_content"
+                android:singleLine="true"
+                android:textColor="#ff000000" />
+            <TextView
+                android:layout_width="fill_parent"
+                android:layout_height="wrap_content"
+                android:singleLine="true"
+                android:textColor="#ff000000" />
+        </com.android.server.status.TickerView>
+    </LinearLayout>
+
+    <com.android.server.status.DateView android:id="@+id/date"
+        android:layout_width="wrap_content"
+        android:layout_height="fill_parent"
+        android:singleLine="true"
+        android:textSize="16sp"
+        android:textStyle="bold"
+        android:gravity="center_vertical|left"
+        android:paddingLeft="6px"
+        android:paddingRight="6px"
+        android:textColor="#ff000000"
+        android:background="@drawable/statusbar_background"
+        />
+</com.android.server.status.StatusBarView>
diff --git a/core/res/res/layout/status_bar_expanded.xml b/core/res/res/layout/status_bar_expanded.xml
new file mode 100644
index 0000000..a6a188e
--- /dev/null
+++ b/core/res/res/layout/status_bar_expanded.xml
@@ -0,0 +1,131 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* apps/common/assets/default/default/skins/StatusBar.xml
+**
+** Copyright 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.
+*/
+-->
+
+<com.android.server.status.ExpandedView xmlns:android="http://schemas.android.com/apk/res/android"
+    android:orientation="vertical"
+    android:background="@drawable/status_bar_background"
+    android:focusable="true"
+    android:descendantFocusability="afterDescendants">
+
+    <LinearLayout
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content"
+        android:orientation="horizontal"
+        android:paddingTop="3dp"
+        android:paddingBottom="5dp"
+        android:paddingRight="3dp"
+        android:background="@drawable/status_bar_divider_shadow"
+        >
+        <LinearLayout
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:layout_weight="1"
+            android:layout_marginLeft="5dp"
+            android:layout_gravity="center_vertical"
+            android:paddingBottom="1dp"
+            android:orientation="vertical"
+            >
+                <TextView android:id="@+id/plmnLabel"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:layout_gravity="center_vertical"
+                    android:textSize="18sp"
+                    android:textStyle="bold"
+                    android:textColor="#ff000000"
+                    />
+                <TextView android:id="@+id/spnLabel"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:textSize="18sp"
+                    android:textStyle="bold"
+                    android:textColor="#ff000000"
+                    android:paddingBottom="1dp"
+                    />
+        </LinearLayout>
+        <TextView android:id="@+id/clear_all_button"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center_vertical"
+            android:textSize="14sp"
+            android:textColor="#ff000000"
+            android:text="@string/status_bar_clear_all_button"
+            style="?android:attr/buttonStyle"
+            />
+    </LinearLayout>
+
+
+    <!-- This view has the same background as the tracking view.  Normally it isn't shown,
+         except in the case where our copy of the close button is visible.  That button is
+         translucent.  Even though it moves up and down, it's only visible when it's aligned
+         at the bottom.
+    -->
+    <ScrollView
+        android:id="@+id/scroll"
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content"
+        android:layout_weight="1"
+        >
+        <com.android.server.status.NotificationLinearLayout
+            android:id="@+id/notificationLinearLayout"
+            android:layout_width="fill_parent"
+            android:layout_height="wrap_content"
+            android:layout_weight="1"
+            android:orientation="vertical"
+            >
+            
+            <TextView android:id="@+id/noNotificationsTitle"
+                android:layout_width="fill_parent"
+                android:layout_height="wrap_content"
+                android:background="#ff888888"
+                android:paddingLeft="5dp"
+                android:textAppearance="@style/TextAppearance.StatusBarTitle"
+                android:text="@string/status_bar_no_notifications_title"
+                />
+
+            <TextView android:id="@+id/ongoingTitle"
+                android:layout_width="fill_parent"
+                android:layout_height="wrap_content"
+                android:background="#ff888888"
+                android:paddingLeft="5dp"
+                android:textAppearance="@style/TextAppearance.StatusBarTitle"
+                android:text="@string/status_bar_ongoing_events_title"
+                />
+            <LinearLayout android:id="@+id/ongoingItems"
+                android:layout_width="fill_parent"
+                android:layout_height="wrap_content"
+                android:orientation="vertical"
+                />
+
+            <TextView android:id="@+id/latestTitle"
+                android:layout_width="fill_parent"
+                android:layout_height="wrap_content"
+                android:background="#ff888888"
+                android:paddingLeft="5dp"
+                android:textAppearance="@style/TextAppearance.StatusBarTitle"
+                android:text="@string/status_bar_latest_events_title"
+                />
+            <LinearLayout android:id="@+id/latestItems"
+                android:layout_width="fill_parent"
+                android:layout_height="wrap_content"
+                android:orientation="vertical"
+                />
+        </com.android.server.status.NotificationLinearLayout>
+    </ScrollView>
+</com.android.server.status.ExpandedView>
diff --git a/core/res/res/layout/status_bar_icon.xml b/core/res/res/layout/status_bar_icon.xml
new file mode 100644
index 0000000..1516036
--- /dev/null
+++ b/core/res/res/layout/status_bar_icon.xml
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* apps/common/assets/default/default/skins/StatusBar.xml
+**
+** Copyright 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.
+*/
+-->
+
+<!-- The icons are a fixed size so an app can't mess everything up with bogus images -->
+<!-- TODO: the icons are hard coded to 25x25 pixels.  Their size should come froem a theme -->
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" 
+    android:layout_width="25dp" 
+    android:layout_height="25dp"
+    >
+
+    <com.android.server.status.AnimatedImageView android:id="@+id/image"
+        android:layout_width="fill_parent" 
+        android:layout_height="fill_parent"
+        />
+
+    <TextView android:id="@+id/number"
+        android:layout_width="wrap_content" 
+        android:layout_height="wrap_content"
+        android:layout_gravity="right|bottom"
+        android:layout_marginRight="1dp"
+        android:layout_marginBottom="1dp"
+        android:textSize="10sp"
+        android:textColor="#ffffffff"
+        android:background="@drawable/ic_notification_overlay"
+        android:gravity="center"
+        android:textStyle="bold"
+        />
+
+</FrameLayout>
diff --git a/core/res/res/layout/status_bar_latest_event.xml b/core/res/res/layout/status_bar_latest_event.xml
new file mode 100644
index 0000000..d524bb6
--- /dev/null
+++ b/core/res/res/layout/status_bar_latest_event.xml
@@ -0,0 +1,24 @@
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="fill_parent"
+    android:layout_height="65sp"
+    android:orientation="vertical"
+    >
+
+    <com.android.server.status.LatestItemView android:id="@+id/content"
+            android:layout_width="fill_parent"
+            android:layout_height="64sp"
+            android:background="@drawable/status_bar_item_background"
+            android:focusable="true"
+            android:clickable="true"
+            android:paddingRight="6sp"
+            >
+    </com.android.server.status.LatestItemView>
+
+    <View
+        android:layout_width="fill_parent"
+        android:layout_height="1sp"
+        android:background="@drawable/divider_horizontal_bright"
+        />
+
+</LinearLayout>
+
diff --git a/core/res/res/layout/status_bar_latest_event_content.xml b/core/res/res/layout/status_bar_latest_event_content.xml
new file mode 100644
index 0000000..eeb9d9d
--- /dev/null
+++ b/core/res/res/layout/status_bar_latest_event_content.xml
@@ -0,0 +1,57 @@
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+        android:layout_width="fill_parent"
+        android:layout_height="fill_parent"
+        android:orientation="vertical"
+        android:paddingTop="7dp"
+        android:paddingLeft="5dp"
+        >
+
+    <LinearLayout
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content"
+        android:orientation="horizontal"
+        android:paddingTop="3dp"
+        >
+        <com.android.server.status.AnimatedImageView android:id="@+id/icon"
+            android:layout_width="25dp"
+            android:layout_height="25dp"
+            android:scaleType="fitCenter"
+            android:src="@drawable/arrow_down_float"/>
+        <TextView android:id="@+id/title"
+            android:layout_width="fill_parent"
+            android:layout_height="wrap_content"
+            android:layout_weight="1"
+            android:singleLine="true"
+            android:ellipsize="marquee"
+            android:fadingEdge="horizontal"
+            android:textStyle="bold"
+            android:textSize="18sp"
+            android:paddingLeft="4dp"
+            android:textColor="#ff000000" />
+    </LinearLayout>
+    <LinearLayout
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content"
+        android:orientation="horizontal"
+        >
+        <TextView android:id="@+id/text"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_weight="1"
+            android:textColor="#ff000000"
+            android:singleLine="true"
+            android:ellipsize="marquee"
+            android:fadingEdge="horizontal"
+            android:textSize="14sp"
+            android:paddingLeft="4dp"
+            />
+        <TextView android:id="@+id/time"
+            android:layout_marginLeft="4dp"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:singleLine="true"
+            android:textSize="14sp"
+            android:paddingRight="5dp"
+            android:textColor="#ff000000" />
+    </LinearLayout>
+</LinearLayout>
diff --git a/core/res/res/layout/status_bar_tracking.xml b/core/res/res/layout/status_bar_tracking.xml
new file mode 100644
index 0000000..661ce86
--- /dev/null
+++ b/core/res/res/layout/status_bar_tracking.xml
@@ -0,0 +1,54 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    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.
+-->
+
+<com.android.server.status.TrackingView xmlns:android="http://schemas.android.com/apk/res/android"
+    android:orientation="vertical"
+    android:visibility="gone"
+    android:focusable="true"
+    android:descendantFocusability="afterDescendants"
+    android:background="@drawable/status_bar_background"
+    android:paddingBottom="0px"
+    android:paddingLeft="0px"
+    android:paddingRight="0px"
+    >
+
+    <View
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content"
+        android:layout_weight="1"
+        />
+
+    <com.android.server.status.CloseDragHandle android:id="@+id/close"
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content"
+        android:orientation="vertical"
+        >
+
+        <View
+            android:layout_width="fill_parent"
+            android:layout_height="5dp"
+            />
+        <ImageView
+            android:layout_width="fill_parent"
+            android:layout_height="wrap_content"
+            android:layout_gravity="bottom"
+            android:scaleType="fitXY"
+            android:src="@drawable/status_bar_close_on"/>
+
+    </com.android.server.status.CloseDragHandle>
+
+</com.android.server.status.TrackingView>
diff --git a/core/res/res/layout/submenu_item.xml b/core/res/res/layout/submenu_item.xml
new file mode 100644
index 0000000..3ec474a
--- /dev/null
+++ b/core/res/res/layout/submenu_item.xml
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* //device/apps/common/assets/res/any/layout/menu_item.xml
+**
+** Copyright 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.
+*/
+-->
+<MenuItemView xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="horizontal" android:focusable="true">
+
+    <TextView android:id="@+id/index"
+        android:paddingLeft="0dip" android:paddingTop="1dip"
+        android:paddingRight="8dip" android:paddingBottom="0dip"
+        android:layout_width="17dip" android:layout_height="wrap_content"
+        android:includeFontPadding="false" />
+
+    <ImageView android:id="@+id/check"
+        android:paddingLeft="3dip" android:paddingTop="3dip"
+        android:paddingRight="3dip" android:paddingBottom="0dip"
+        android:src="@drawable/menuitem_checkbox" android:scaleType="fitCenter"
+        android:layout_width="wrap_content" android:layout_height="wrap_content" />
+
+    <TextView android:id="@+id/title"
+        android:paddingLeft="0dip" android:paddingTop="1dip"
+        android:paddingRight="0dip" android:paddingBottom="2dip"
+        android:layout_width="wrap_content" android:layout_height="wrap_content"
+        android:layout_weight="1"  android:includeFontPadding="false"/>
+
+    <ImageView android:id="@+id/arrow"
+        android:paddingLeft="8dip" android:paddingTop="3dip"
+        android:paddingRight="0dip" android:paddingBottom="0dip"
+        android:src="@drawable/submenu_arrow" android:scaleType="fitCenter"
+        android:layout_width="wrap_content" android:layout_height="wrap_content" />
+
+</MenuItemView>
+
diff --git a/core/res/res/layout/tab_content.xml b/core/res/res/layout/tab_content.xml
new file mode 100644
index 0000000..8f67af0
--- /dev/null
+++ b/core/res/res/layout/tab_content.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* //device/apps/common/assets/res/layout/tab_content.xml
+**
+** Copyright 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.
+*/
+-->
+
+<TabHost xmlns:android="http://schemas.android.com/apk/res/android" android:id="@android:id/tabhost"
+	android:layout_width="fill_parent" android:layout_height="fill_parent">
+	<LinearLayout android:orientation="vertical"
+    	android:layout_width="fill_parent" android:layout_height="fill_parent">
+        <TabWidget android:id="@android:id/tabs" android:layout_width="fill_parent"
+        	android:layout_height="wrap_content" android:layout_weight="0" />
+        <FrameLayout android:id="@android:id/tabcontent"
+        	android:layout_width="fill_parent" android:layout_height="0dip"
+            android:layout_weight="1"/>
+	</LinearLayout>
+</TabHost>
+
diff --git a/core/res/res/layout/tab_indicator.xml b/core/res/res/layout/tab_indicator.xml
new file mode 100644
index 0000000..fcf0b5e
--- /dev/null
+++ b/core/res/res/layout/tab_indicator.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="0dip"
+    android:layout_height="64dip"
+    android:layout_weight="1"
+    android:orientation="vertical"
+    android:background="@android:drawable/tab_indicator">
+
+    <ImageView android:id="@+id/icon"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_centerHorizontal="true"
+    />
+
+    <TextView android:id="@+id/title"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_alignParentBottom="true"
+        android:layout_centerHorizontal="true"
+        style="?android:attr/tabWidgetStyle"
+    />
+
+</RelativeLayout>
diff --git a/core/res/res/layout/test_list_item.xml b/core/res/res/layout/test_list_item.xml
new file mode 100644
index 0000000..f4e0d3c
--- /dev/null
+++ b/core/res/res/layout/test_list_item.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<TextView xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@android:id/text1"
+    android:textAppearance="?android:attr/textAppearanceSmall"
+    android:paddingTop="2dip"
+    android:paddingBottom="3dip"
+    android:layout_width="fill_parent"
+    android:layout_height="wrap_content"
+/>
diff --git a/core/res/res/layout/textview_hint.xml b/core/res/res/layout/textview_hint.xml
new file mode 100644
index 0000000..d69a2f6
--- /dev/null
+++ b/core/res/res/layout/textview_hint.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<TextView xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="fill_parent"
+    android:layout_height="fill_parent"
+    android:background="@drawable/popup_inline_error"
+    android:textAppearance="?android:attr/textAppearanceSmallInverse"
+/>
diff --git a/core/res/res/layout/time_picker.xml b/core/res/res/layout/time_picker.xml
new file mode 100644
index 0000000..c601e0e
--- /dev/null
+++ b/core/res/res/layout/time_picker.xml
@@ -0,0 +1,61 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+**
+** Copyright 2007, 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.
+*/
+-->
+
+<!-- Layout of time picker-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:orientation="horizontal"
+    android:layout_gravity="center_horizontal"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content">
+
+    <!-- hour -->
+    <com.android.internal.widget.NumberPicker
+        android:id="@+id/hour"
+        android:layout_width="70dip"
+        android:layout_height="wrap_content"
+        android:focusable="true"
+        android:focusableInTouchMode="true"
+        />
+    
+    <!-- minute -->
+    <com.android.internal.widget.NumberPicker
+        android:id="@+id/minute"
+        android:layout_width="70dip"
+        android:layout_height="wrap_content"
+        android:layout_marginLeft="5dip"
+        android:focusable="true"
+        android:focusableInTouchMode="true"
+        />
+    
+    <!-- AM / PM -->
+    <Button
+        android:id="@+id/amPm"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="46dip"
+        android:layout_marginLeft="5dip"
+        android:paddingTop="2dip"
+        android:paddingBottom="2dip"
+        android:paddingLeft="20dip"
+        android:paddingRight="20dip"
+        style="?android:attr/textAppearanceLargeInverse"
+        android:textColor="@android:color/primary_text_light_nodisable"
+        />
+</LinearLayout>
diff --git a/core/res/res/layout/time_picker_dialog.xml b/core/res/res/layout/time_picker_dialog.xml
new file mode 100644
index 0000000..d5a6b5e
--- /dev/null
+++ b/core/res/res/layout/time_picker_dialog.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+**
+** Copyright 2007, 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.
+*/
+-->
+
+<TimePicker xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/timePicker"
+    android:layout_gravity="center_horizontal"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content"
+    android:padding="5dip" />
diff --git a/core/res/res/layout/transient_notification.xml b/core/res/res/layout/transient_notification.xml
new file mode 100644
index 0000000..1d3be14
--- /dev/null
+++ b/core/res/res/layout/transient_notification.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* //device/apps/common/res/layout/transient_notification.xml
+**
+** Copyright 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.
+*/
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="fill_parent"
+    android:layout_height="fill_parent"
+    android:orientation="vertical"
+    android:background="@drawable/toast_frame">
+
+    <TextView
+        android:id="@android:id/message"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_weight="1"
+        android:textAppearance="@style/TextAppearance.Small"
+        android:textColor="@color/bright_foreground_dark"
+        android:shadowColor="#BB000000"
+        android:shadowRadius="2.75"
+        />
+
+</LinearLayout>
+
+
diff --git a/core/res/res/layout/two_line_list_item.xml b/core/res/res/layout/two_line_list_item.xml
new file mode 100644
index 0000000..2a2e759
--- /dev/null
+++ b/core/res/res/layout/two_line_list_item.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* //device/apps/common/assets/res/any/layout/two_line_list_item.xml
+**
+** Copyright 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.
+*/
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="fill_parent"
+    android:layout_height="wrap_content"
+    android:orientation="vertical">
+
+    <TextView android:id="@android:id/text1"
+        android:textSize="16sp"
+        android:textStyle="bold"
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content"/>
+
+    <TextView android:id="@android:id/text2"
+        android:textSize="16sp"
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content"/>
+
+</LinearLayout>
diff --git a/core/res/res/layout/typing_filter.xml b/core/res/res/layout/typing_filter.xml
new file mode 100644
index 0000000..d8d0a40
--- /dev/null
+++ b/core/res/res/layout/typing_filter.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<EditText xmlns:android="http://schemas.android.com/apk/res/android"
+    android:textSize="36dp"
+    android:textColor="#99FFFFFF"
+    android:background="#BB000000"
+    android:minWidth="240dip"
+    android:maxWidth="240dip"
+    android:padding="10dip"
+    android:gravity="center_horizontal"
+    android:focusable="false"
+/>
diff --git a/core/res/res/layout/volume_adjust.xml b/core/res/res/layout/volume_adjust.xml
new file mode 100644
index 0000000..946ca7e
--- /dev/null
+++ b/core/res/res/layout/volume_adjust.xml
@@ -0,0 +1,68 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2007 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.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content"
+    android:background="@android:drawable/panel_background"
+    android:orientation="vertical"
+    android:gravity="center_horizontal">
+
+    <LinearLayout
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="14dip"
+        android:gravity="center_vertical">
+    
+        <ImageView
+            android:id="@+id/other_stream_icon"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginRight="6dip" />
+
+        <TextView
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:id="@+id/message"
+            android:textAppearance="?android:attr/textAppearanceMedium" />
+
+    </LinearLayout>
+
+    <TextView
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:id="@+id/additional_message"
+        android:textAppearance="?android:attr/textAppearanceSmall" />
+
+    <ImageView
+        android:id="@+id/ringer_stream_icon"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="14dip" />
+
+    <ProgressBar
+        style="?android:attr/progressBarStyleHorizontal"
+        android:id="@+id/level"
+        android:layout_width="200dip"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="14dip"
+        android:layout_marginBottom="14dip"
+        android:layout_marginLeft="25dip"
+        android:layout_marginRight="25dip" />
+
+</LinearLayout>
+
+
diff --git a/core/res/res/layout/zoom_controls.xml b/core/res/res/layout/zoom_controls.xml
new file mode 100644
index 0000000..729af1b
--- /dev/null
+++ b/core/res/res/layout/zoom_controls.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 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.
+*/
+-->
+<merge xmlns:android="http://schemas.android.com/apk/res/android">
+    <ZoomButton android:id="@+id/zoomIn" 
+        android:background="@android:drawable/btn_plus"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        />
+    <ZoomButton android:id="@+id/zoomOut" 
+        android:background="@android:drawable/btn_minus" 
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        />
+</merge>
diff --git a/core/res/res/layout/zoom_magnify.xml b/core/res/res/layout/zoom_magnify.xml
new file mode 100644
index 0000000..374819e
--- /dev/null
+++ b/core/res/res/layout/zoom_magnify.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 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.
+*/
+-->
+<merge xmlns:android="http://schemas.android.com/apk/res/android">
+    <ZoomControls android:id="@+id/zoomControls"
+        android:layout_gravity="bottom|center_horizontal"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        style="@style/ZoomControls"
+        />
+    <ImageView android:id="@+id/zoomMagnify"
+        android:focusable="true"
+        android:layout_gravity="bottom|right"
+        android:paddingRight="2dip"
+        android:src="@drawable/btn_zoom_page"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        />
+</merge>
diff --git a/core/res/res/raw-ar/loaderror.html b/core/res/res/raw-ar/loaderror.html
new file mode 100644
index 0000000..edcd63a
--- /dev/null
+++ b/core/res/res/raw-ar/loaderror.html
@@ -0,0 +1,18 @@
+<html>
+    <head>
+        <title>صفحة الويب غير متوفرة</title>
+        <style type="text/css">
+            body { margin-top: 0px; padding-top: 0px; }
+            h2   { margin-top: 5px; padding-top: 0px; }
+        </style>
+
+        <body>
+
+            <img src="file:///android_asset/webkit/android-weberror.png" align="top" />
+            <h2>صفحة الويب غير متوفرة</h2>
+            <p>تعذر تحميل صفحة الويب الموجودة على <a href="%s">%s</a> كـ:</p>
+            <!-- The %e is replaced by a localized error string -->
+            <p>%e</p>
+        </body>
+    </head>
+</html>
diff --git a/core/res/res/raw-ar/nodomain.html b/core/res/res/raw-ar/nodomain.html
new file mode 100644
index 0000000..bc070f6
--- /dev/null
+++ b/core/res/res/raw-ar/nodomain.html
@@ -0,0 +1,24 @@
+<html>
+    <head>
+        <title>صفحة الويب غير متوفرة</title>
+        <style type="text/css">
+            body { margin-top: 0px; padding-top: 0px; }
+            h2   { margin-top: 5px; padding-top: 0px; }
+        </style>
+
+        <body>
+
+            <img src="file:///android_asset/webkit/android-weberror.png" align="top" />
+            <h2>صفحة الويب غير متوفرة</h2>
+            <p>قد تكون صفحة الويب الموجودة على <a href="%s">%s</a> معطلة مؤقتًا، أو قد تمّ نقلها نهائيًا إلى عنوان ويب جديد.</p>
+
+            <p><b>فيما يلي بعض الاقتراحات:</b></p>
+            <ul>
+                <li>تأكد من أن جهازك به وصلة للإشارة والبيانات.</li>
+                <li>أعد تحميل صفحة الويب لاحقًا.</li>
+                <li>قم بعرض نسخة مخبأة من صفحة الويب من صفحة Google.</li>
+
+            </ul>
+        </body>
+    </head>
+</html>
diff --git a/core/res/res/raw-cs/loaderror.html b/core/res/res/raw-cs/loaderror.html
new file mode 100644
index 0000000..ce88981
--- /dev/null
+++ b/core/res/res/raw-cs/loaderror.html
@@ -0,0 +1,18 @@
+<html>
+    <head>
+        <title>Webov&aacute; str&aacute;nka nen&iacute; dostupn&aacute;</title>
+        <style type="text/css">
+            body { margin-top: 0px; padding-top: 0px; }
+            h2   { margin-top: 5px; padding-top: 0px; }
+        </style>
+
+        <body>
+
+            <img src="file:///android_asset/webkit/android-weberror.png" align="top" />
+            <h2>Webov&aacute; str&aacute;nka nen&iacute; dostupn&aacute;</h2>
+            <p>Webov&aacute; str&aacute;nka na adrese <a href="%s">%s</a> nemohla b&yacute;t načtena. Chyba:</p>
+            <!-- The %e is replaced by a localized error string -->
+            <p>%e</p>
+        </body>
+    </head>
+</html>
diff --git a/core/res/res/raw-cs/nodomain.html b/core/res/res/raw-cs/nodomain.html
new file mode 100644
index 0000000..26479a9
--- /dev/null
+++ b/core/res/res/raw-cs/nodomain.html
@@ -0,0 +1,24 @@
+<html>
+    <head>
+        <title>Webov&aacute; str&aacute;nka nen&iacute; dostupn&aacute;</title>
+        <style type="text/css">
+            body { margin-top: 0px; padding-top: 0px; }
+            h2   { margin-top: 5px; padding-top: 0px; }
+        </style>
+
+        <body>
+
+            <img src="file:///android_asset/webkit/android-weberror.png" align="top" />
+            <h2>Webov&aacute; str&aacute;nka nen&iacute; dostupn&aacute;</h2>
+            <p>Webov&aacute; str&aacute;nka na adrese <a href="%s">%s</a> je možn&aacute; dočasně nedostupn&aacute; nebo byla možn&aacute; přesunuta na novou adresu. </p>
+
+            <p><b>Možnosti dalš&iacute;ho postupu:</b></p>
+            <ul>
+                <li>Zkontrolujte na sv&eacute;m zař&iacute;zen&iacute;, zda je sign&aacute;l dostatečně siln&yacute; a zda je funkčn&iacute; datov&eacute; připojen&iacute;.</li>
+                <li>Otevřete tuto webovou str&aacute;nku později.</li>
+                <li>Pod&iacute;vejte se na kopii t&eacute;to webov&eacute; str&aacute;nky v mezipaměti vyhled&aacute;vače Google</li>
+
+            </ul>
+        </body>
+    </head>
+</html>
diff --git a/core/res/res/raw-da/loaderror.html b/core/res/res/raw-da/loaderror.html
new file mode 100644
index 0000000..12a75c6c
--- /dev/null
+++ b/core/res/res/raw-da/loaderror.html
@@ -0,0 +1,18 @@
+<html>
+    <head>
+        <title>Websiden er ikke tilg&aelig;ngelig</title>
+        <style type="text/css">
+            body { margin-top: 0px; padding-top: 0px; }
+            h2   { margin-top: 5px; padding-top: 0px; }
+        </style>
+
+        <body>
+
+            <img src="file:///android_asset/webkit/android-weberror.png" align="top" />
+            <h2>Websiden er ikke tilg&aelig;ngelig</h2>
+            <p>Websiden p&aring; <a href="%s">%s</a> kunne ikke indl&aelig;ses som:</p>
+            <!-- The %e is replaced by a localized error string -->
+            <p>%e</p>
+        </body>
+    </head>
+</html>
diff --git a/core/res/res/raw-da/nodomain.html b/core/res/res/raw-da/nodomain.html
new file mode 100644
index 0000000..3b8fe78
--- /dev/null
+++ b/core/res/res/raw-da/nodomain.html
@@ -0,0 +1,24 @@
+<html>
+    <head>
+        <title>Websiden er ikke tilg&aelig;ngelig</title>
+        <style type="text/css">
+            body { margin-top: 0px; padding-top: 0px; }
+            h2   { margin-top: 5px; padding-top: 0px; }
+        </style>
+
+        <body>
+
+            <img src="file:///android_asset/webkit/android-weberror.png" align="top" />
+            <h2>Websiden er ikke tilg&aelig;ngelig</h2>
+            <p>Websiden p&aring; <a href="%s">%s</a> kan v&aelig;re midlertidigt nede eller flyttet permanent til en ny internetadresse.</p>
+
+            <p><b>Her er nogle forslag:</b></p>
+            <ul>
+                <li>Kontroller, at dit udstyr har signal- og dataforbindelse</li>
+                <li>Genindl&aelig;s websiden senere</li>
+                <li>Se en cached kopi af websiden fra Google</li>
+
+            </ul>
+        </body>
+    </head>
+</html>
diff --git a/core/res/res/raw-de/loaderror.html b/core/res/res/raw-de/loaderror.html
new file mode 100644
index 0000000..bece2d7
--- /dev/null
+++ b/core/res/res/raw-de/loaderror.html
@@ -0,0 +1,18 @@
+<html>
+    <head>
+        <title>Webseite nicht verf&uuml;gbar</title>
+        <style type="text/css">
+            body { margin-top: 0px; padding-top: 0px; }
+            h2   { margin-top: 5px; padding-top: 0px; }
+        </style>
+
+        <body>
+
+            <img src="file:///android_asset/webkit/android-weberror.png" align="top" />
+            <h2>Webseite nicht verf&uuml;gbar</h2>
+            <p>Die Webseite unter <a href="%s">%s</a> konnte nicht geladen werden als:</p>
+            <!-- The %e is replaced by a localized error string -->
+            <p>%e</p>
+        </body>
+    </head>
+</html>
diff --git a/core/res/res/raw-de/nodomain.html b/core/res/res/raw-de/nodomain.html
new file mode 100644
index 0000000..9fd8094
--- /dev/null
+++ b/core/res/res/raw-de/nodomain.html
@@ -0,0 +1,24 @@
+<html>
+    <head>
+        <title>Webseite nicht verf&uuml;gbar</title>
+        <style type="text/css">
+            body { margin-top: 0px; padding-top: 0px; }
+            h2   { margin-top: 5px; padding-top: 0px; }
+        </style>
+
+        <body>
+
+            <img src="file:///android_asset/webkit/android-weberror.png" align="top" />
+            <h2>Webseite nicht verf&uuml;gbar</h2>
+            <p>Die Webseite unter <a href="%s">%s</a> ist m&ouml;glicherweise vor&uuml;bergehend deaktiviert oder dauerhaft an eine neue Webadresse verschoben worden.</p>
+
+            <p><b>Hier sind einige Vorschl&auml;ge:</b></p>
+            <ul>
+                <li>Stellen Sie sicher, dass Ihr Ger&auml;t ein Signal empf&auml;ngt und &uuml;ber eine Datenverbindung verf&uuml;gt.</li>
+                <li>Laden Sie die Webseite sp&auml;ter erneut.</li>
+                <li>Zeigen Sie eine im Cache gespeicherte Kopie der Webseite von Google an.</li>
+
+            </ul>
+        </body>
+    </head>
+</html>
diff --git a/core/res/res/raw-en-rGB/loaderror.html b/core/res/res/raw-en-rGB/loaderror.html
new file mode 100644
index 0000000..359a1e7
--- /dev/null
+++ b/core/res/res/raw-en-rGB/loaderror.html
@@ -0,0 +1,18 @@
+<html>
+    <head>
+        <title>Web page not available</title>
+        <style type="text/css">
+            body { margin-top: 0px; padding-top: 0px; }
+            h2   { margin-top: 5px; padding-top: 0px; }
+        </style>
+
+        <body>
+
+            <img src="file:///android_asset/webkit/android-weberror.png" align="top" />
+            <h2>Web page not available</h2>
+            <p>The Web page at <a href="%s">%s</a> could not be loaded as:</p>
+            <!-- The %e is replaced by a localized error string -->
+            <p>%e</p>
+        </body>
+    </head>
+</html>
diff --git a/core/res/res/raw-en-rGB/nodomain.html b/core/res/res/raw-en-rGB/nodomain.html
new file mode 100644
index 0000000..01dd603
--- /dev/null
+++ b/core/res/res/raw-en-rGB/nodomain.html
@@ -0,0 +1,24 @@
+<html>
+    <head>
+        <title>Web page not available</title>
+        <style type="text/css">
+            body { margin-top: 0px; padding-top: 0px; }
+            h2   { margin-top: 5px; padding-top: 0px; }
+        </style>
+
+        <body>
+
+            <img src="file:///android_asset/webkit/android-weberror.png" align="top" />
+            <h2>Web page not available</h2>
+            <p>The Web page at <a href="%s">%s</a> might be temporarily down or it may have moved permanently to a new web address.</p>
+
+            <p><b>Here are some suggestions:</b></p>
+            <ul>
+                <li>Check to make sure that your device has a signal and data connection</li>
+                <li>Reload this web page later.</li>
+                <li>View a cached copy of the web page from Google</li>
+
+            </ul>
+        </body>
+    </head>
+</html>
diff --git a/core/res/res/raw-es/loaderror.html b/core/res/res/raw-es/loaderror.html
new file mode 100644
index 0000000..8829bf5
--- /dev/null
+++ b/core/res/res/raw-es/loaderror.html
@@ -0,0 +1,18 @@
+<html>
+    <head>
+        <title>P&aacute;gina web no disponible</title>
+        <style type="text/css">
+            body { margin-top: 0px; padding-top: 0px; }
+            h2   { margin-top: 5px; padding-top: 0px; }
+        </style>
+
+        <body>
+
+            <img src="file:///android_asset/webkit/android-weberror.png" align="top" />
+            <h2>P&aacute;gina web no disponible</h2>
+            <p>La p&aacute;gina web <a href="%s">%s</a> no se ha podido cargar como:</p>
+            <!-- The %e is replaced by a localized error string -->
+            <p>%e</p>
+        </body>
+    </head>
+</html>
diff --git a/core/res/res/raw-es/nodomain.html b/core/res/res/raw-es/nodomain.html
new file mode 100644
index 0000000..a11333e
--- /dev/null
+++ b/core/res/res/raw-es/nodomain.html
@@ -0,0 +1,24 @@
+<html>
+    <head>
+        <title>P&aacute;gina web no disponible</title>
+        <style type="text/css">
+            body { margin-top: 0px; padding-top: 0px; }
+            h2   { margin-top: 5px; padding-top: 0px; }
+        </style>
+
+        <body>
+
+            <img src="file:///android_asset/webkit/android-weberror.png" align="top" />
+            <h2>P&aacute;gina web no disponible</h2>
+            <p>Es posible que la p&aacute;gina web <a href="%s">%s</a> se encuentre temporalmente fuera de servicio o se haya trasladado a otra direcci&oacute;n web de forma permanente.</p>
+
+            <p><b>Sugerencias:</b></p>
+            <ul>
+                <li>Aseg&uacute;rate de que tu dispositivo disponga de se&ntilde;al y de una conexi&oacute;n de datos.</li>
+                <li>Vuelve a cargar la p&aacute;gina m&aacute;s tarde.</li>
+                <li>Accede a una copia de la p&aacute;gina almacenada en cach&eacute; desde Google.</li>
+
+            </ul>
+        </body>
+    </head>
+</html>
diff --git a/core/res/res/raw-fi/loaderror.html b/core/res/res/raw-fi/loaderror.html
new file mode 100644
index 0000000..3b1ec97
--- /dev/null
+++ b/core/res/res/raw-fi/loaderror.html
@@ -0,0 +1,18 @@
+<html>
+    <head>
+        <title>Verkkosivu ei ole k&auml;ytett&auml;viss&auml;</title>
+        <style type="text/css">
+            body { margin-top: 0px; padding-top: 0px; }
+            h2   { margin-top: 5px; padding-top: 0px; }
+        </style>
+
+        <body>
+
+            <img src="file:///android_asset/webkit/android-weberror.png" align="top" />
+            <h2>Verkkosivu ei ole k&auml;ytett&auml;viss&auml;</h2>
+            <p>Verkkosivua osoitteessa <a href="%s">%s</a> ei voi ladata, koska:</p>
+            <!-- The %e is replaced by a localized error string -->
+            <p>%e</p>
+        </body>
+    </head>
+</html>
diff --git a/core/res/res/raw-fi/nodomain.html b/core/res/res/raw-fi/nodomain.html
new file mode 100644
index 0000000..84fedb4
--- /dev/null
+++ b/core/res/res/raw-fi/nodomain.html
@@ -0,0 +1,24 @@
+<html>
+    <head>
+        <title>Verkkosivu ei ole k&auml;ytett&auml;viss&auml;</title>
+        <style type="text/css">
+            body { margin-top: 0px; padding-top: 0px; }
+            h2   { margin-top: 5px; padding-top: 0px; }
+        </style>
+
+        <body>
+
+            <img src="file:///android_asset/webkit/android-weberror.png" align="top" />
+            <h2>Verkkosivu ei ole k&auml;ytett&auml;viss&auml;</h2>
+            <p>Verkkosivu osoitteessa <a href="%s">%s</a> saattaa olla v&auml;liaikaisesti pois k&auml;yt&ouml;st&auml; tai muuttanut pysyv&auml;sti uuteen osoitteeseen.</p>
+
+            <p><b>T&auml;ss&auml; muutamia ehdotuksia:</b></p>
+            <ul>
+                <li>Tarkista, ett&auml; laitteesi signaali ja verkkoyhteys ovat kunnossa</li>
+                <li>Lataa t&auml;m&auml; verkkosivu my&ouml;hemmin uudelleen.</li>
+                <li>Katsele Googlessa verkkosivun v&auml;limuistissa olevaa versiota </li>
+
+            </ul>
+        </body>
+    </head>
+</html>
diff --git a/core/res/res/raw-fr/loaderror.html b/core/res/res/raw-fr/loaderror.html
new file mode 100644
index 0000000..f61f50b
--- /dev/null
+++ b/core/res/res/raw-fr/loaderror.html
@@ -0,0 +1,18 @@
+<html>
+    <head>
+        <title>Page Web non disponible</title>
+        <style type="text/css">
+            body { margin-top: 0px; padding-top: 0px; }
+            h2   { margin-top: 5px; padding-top: 0px; }
+        </style>
+
+        <body>
+
+            <img src="file:///android_asset/webkit/android-weberror.png" align="top" />
+            <h2>Page Web non disponible</h2>
+            <p>Impossible de charger la page Web &agrave; l'adresse <a href="%s">%s</a> en tant que&nbsp;:</p>
+            <!-- The %e is replaced by a localized error string -->
+            <p>%e</p>
+        </body>
+    </head>
+</html>
diff --git a/core/res/res/raw-fr/nodomain.html b/core/res/res/raw-fr/nodomain.html
new file mode 100644
index 0000000..b3b93b3
--- /dev/null
+++ b/core/res/res/raw-fr/nodomain.html
@@ -0,0 +1,24 @@
+<html>
+    <head>
+        <title>Page Web non disponible</title>
+        <style type="text/css">
+            body { margin-top: 0px; padding-top: 0px; }
+            h2   { margin-top: 5px; padding-top: 0px; }
+        </style>
+
+        <body>
+
+            <img src="file:///android_asset/webkit/android-weberror.png" align="top" />
+            <h2>Page Web non disponible</h2>
+            <p>La page Web &agrave; l'adresse <a href="%s">%s</a> est peut-&ecirc;tre temporairement inaccessible ou a &eacute;t&eacute; d&eacute;plac&eacute;e d&eacute;finitivement vers une nouvelle adresse Web.</p>
+
+            <p><b>Voici quelques suggestions&nbsp;:</b></p>
+            <ul>
+                <li>assurez-vous que votre appareil &eacute;met un signal et dispose d'une connexion de donn&eacute;es&nbsp;;</li>
+                <li>rechargez cette page Web ult&eacute;rieurement&nbsp;;</li>
+                <li>affichez une copie en cache de la page Web &agrave; partir de Google.</li>
+
+            </ul>
+        </body>
+    </head>
+</html>
diff --git a/core/res/res/raw-hu/loaderror.html b/core/res/res/raw-hu/loaderror.html
new file mode 100644
index 0000000..6b3d45e
--- /dev/null
+++ b/core/res/res/raw-hu/loaderror.html
@@ -0,0 +1,18 @@
+<html>
+    <head>
+        <title>A weboldal nem &eacute;rhető el</title>
+        <style type="text/css">
+            body { margin-top: 0px; padding-top: 0px; }
+            h2   { margin-top: 5px; padding-top: 0px; }
+        </style>
+
+        <body>
+
+            <img src="file:///android_asset/webkit/android-weberror.png" align="top" />
+            <h2>A weboldal nem &eacute;rhető el</h2>
+            <p>A  k&ouml;vetkező <a href="%s">%s</a> weboldalt nem siker&uuml;lt bet&ouml;lteni, mert:</p>
+            <!-- The %e is replaced by a localized error string -->
+            <p>%e</p>
+        </body>
+    </head>
+</html>
diff --git a/core/res/res/raw-hu/nodomain.html b/core/res/res/raw-hu/nodomain.html
new file mode 100644
index 0000000..d3465ff
--- /dev/null
+++ b/core/res/res/raw-hu/nodomain.html
@@ -0,0 +1,24 @@
+<html>
+    <head>
+        <title>A weboldal nem &eacute;rhető el</title>
+        <style type="text/css">
+            body { margin-top: 0px; padding-top: 0px; }
+            h2   { margin-top: 5px; padding-top: 0px; }
+        </style>
+
+        <body>
+
+            <img src="file:///android_asset/webkit/android-weberror.png" align="top" />
+            <h2>A weboldal nem &eacute;rhető el</h2>
+            <p>A k&ouml;vetkező <a href="%s">%s</a> weboldal val&oacute;sz&iacute;nűleg ideiglenesen &uuml;zemen k&iacute;v&uuml;l van vagy v&eacute;glegesen &uacute;j webc&iacute;mre k&ouml;lt&ouml;z&ouml;tt.</p>
+
+            <p><b>&Iacute;me p&aacute;r javaslat:</b></p>
+            <ul>
+                <li>Ellenőrizze, hogy k&eacute;sz&uuml;l&eacute;ke fogja-e a jelet &eacute;s k&eacute;pes-e adat&aacute;tvitelre.</li>
+                <li>T&ouml;ltse &uacute;jra ezt a weboldalt k&eacute;sőbb.</li>
+                <li>A weboldal t&aacute;rolt v&aacute;ltozat&aacute;nak megtekint&eacute;se a Google-r&oacute;l</li>
+
+            </ul>
+        </body>
+    </head>
+</html>
diff --git a/core/res/res/raw-it/loaderror.html b/core/res/res/raw-it/loaderror.html
new file mode 100644
index 0000000..e81466a
--- /dev/null
+++ b/core/res/res/raw-it/loaderror.html
@@ -0,0 +1,18 @@
+<html>
+    <head>
+        <title>Pagina web non disponibile</title>
+        <style type="text/css">
+            body { margin-top: 0px; padding-top: 0px; }
+            h2   { margin-top: 5px; padding-top: 0px; }
+        </style>
+
+        <body>
+
+            <img src="file:///android_asset/webkit/android-weberror.png" align="top" />
+            <h2>Pagina web non disponibile</h2>
+            <p>Non &egrave; stato possibile caricare la pagina web all'indirizzo <a href="%s">%s</a>. Errore:</p>
+            <!-- The %e is replaced by a localized error string -->
+            <p>%e</p>
+        </body>
+    </head>
+</html>
diff --git a/core/res/res/raw-it/nodomain.html b/core/res/res/raw-it/nodomain.html
new file mode 100644
index 0000000..a2321c7
--- /dev/null
+++ b/core/res/res/raw-it/nodomain.html
@@ -0,0 +1,24 @@
+<html>
+    <head>
+        <title>Pagina web non disponibile</title>
+        <style type="text/css">
+            body { margin-top: 0px; padding-top: 0px; }
+            h2   { margin-top: 5px; padding-top: 0px; }
+        </style>
+
+        <body>
+
+            <img src="file:///android_asset/webkit/android-weberror.png" align="top" />
+            <h2>Pagina web non disponibile</h2>
+            <p>La pagina web all'indirizzo <a href="%s">%s</a> potrebbe essere temporaneamente non disponibile oppure essere stata spostata permanentemente a un nuovo indirizzo.</p>
+
+            <p><b>Ecco alcuni suggerimenti:</b></p>
+            <ul>
+                <li>Controlla che ci sia segnale e che la connessione dati sia attiva</li>
+                <li>Ricarica la pagina web in seguito</li>
+                <li>Visualizza la copia cache della pagina web su Google</li>
+
+            </ul>
+        </body>
+    </head>
+</html>
diff --git a/core/res/res/raw-iw/loaderror.html b/core/res/res/raw-iw/loaderror.html
new file mode 100644
index 0000000..8155432
--- /dev/null
+++ b/core/res/res/raw-iw/loaderror.html
@@ -0,0 +1,18 @@
+<html>
+    <head>
+        <title>דף אינטרנט לא זמין</title>
+        <style type="text/css">
+            body { margin-top: 0px; padding-top: 0px; }
+            h2   { margin-top: 5px; padding-top: 0px; }
+        </style>
+
+        <body>
+
+            <img src="file:///android_asset/webkit/android-weberror.png" align="top" />
+            <h2>דף אינטרנט לא זמין</h2>
+            <p>דף האינטרנט ב-&lrm;<a href="%s">%s</a>&lrm; לא ניטן לטעינה בתור:</p>
+            <!-- The %e is replaced by a localized error string -->
+            <p>&lrm;%e</p>
+        </body>
+    </head>
+</html>
diff --git a/core/res/res/raw-iw/nodomain.html b/core/res/res/raw-iw/nodomain.html
new file mode 100644
index 0000000..f7b9f42
--- /dev/null
+++ b/core/res/res/raw-iw/nodomain.html
@@ -0,0 +1,24 @@
+<html>
+    <head>
+        <title>דף אינטרנט לא זמין</title>
+        <style type="text/css">
+            body { margin-top: 0px; padding-top: 0px; }
+            h2   { margin-top: 5px; padding-top: 0px; }
+        </style>
+
+        <body>
+
+            <img src="file:///android_asset/webkit/android-weberror.png" align="top" />
+            <h2>דף אינטרנט לא זמין</h2>
+            <p>ייתכן שדף האינטרנט ב-&lrm;<a href="%s">%s</a>&lrm; מושבת באופן זמני או שעבר לצמיתות לכתובת אינטרנט חדשה.</p>
+
+            <p><b>להלן מספר הצעות:</b></p>
+            <ul>
+                <li>בדוק כדי לוודא שההתקן שלך כולל חיבור אותות ונתונים</li>
+                <li>טען מחדש דף אינטרנט זה במועד מאוחר יותר.</li>
+                <li>הצג עותק של דף האינטרנט של Google שנשמר במטמון</li>
+
+            </ul>
+        </body>
+    </head>
+</html>
diff --git a/core/res/res/raw-ja/loaderror.html b/core/res/res/raw-ja/loaderror.html
new file mode 100644
index 0000000..68e568b
--- /dev/null
+++ b/core/res/res/raw-ja/loaderror.html
@@ -0,0 +1,18 @@
+<html>
+    <head>
+        <title>ページが見つかりませんでした</title>
+        <style type="text/css">
+            body { margin-top: 0px; padding-top: 0px; }
+            h2   { margin-top: 5px; padding-top: 0px; }
+        </style>
+
+        <body>
+
+            <img src="file:///android_asset/webkit/android-weberror.png" align="top" />
+            <h2>ページが見つかりませんでした</h2>
+            <p>次の原因によりウェブページ <a href="%s">%s</a> を読み込めませんでした。</p>
+            <!-- The %e is replaced by a localized error string -->
+            <p>%e</p>
+        </body>
+    </head>
+</html>
diff --git a/core/res/res/raw-ja/nodomain.html b/core/res/res/raw-ja/nodomain.html
new file mode 100644
index 0000000..1dff1d4
--- /dev/null
+++ b/core/res/res/raw-ja/nodomain.html
@@ -0,0 +1,24 @@
+<html>
+    <head>
+        <title>ページが見つかりませんでした</title>
+        <style type="text/css">
+            body { margin-top: 0px; padding-top: 0px; }
+            h2   { margin-top: 5px; padding-top: 0px; }
+        </style>
+
+        <body>
+
+            <img src="file:///android_asset/webkit/android-weberror.png" align="top" />
+            <h2>ページが見つかりませんでした</h2>
+            <p>ウェブページ <a href="%s">%s</a> は一時的にご利用いただけないか、URLが変更された可能性があります。</p>
+
+            <p><b>ヒント:</b></p>
+            <ul>
+                <li>端末を圏内で使用していてデータ接続がアクティブであることを確認します</li>
+                <li>しばらくしてからページをリロードします</li>
+                <li>Googleでキャッシュされたページを表示します</li>
+
+            </ul>
+        </body>
+    </head>
+</html>
diff --git a/core/res/res/raw-ko/loaderror.html b/core/res/res/raw-ko/loaderror.html
new file mode 100644
index 0000000..59f0f25
--- /dev/null
+++ b/core/res/res/raw-ko/loaderror.html
@@ -0,0 +1,18 @@
+<html>
+    <head>
+        <title>웹페이지를 표시할 수 없습니다.</title>
+        <style type="text/css">
+            body { margin-top: 0px; padding-top: 0px; }
+            h2   { margin-top: 5px; padding-top: 0px; }
+        </style>
+
+        <body>
+
+            <img src="file:///android_asset/webkit/android-weberror.png" align="top" />
+            <h2>웹페이지를 표시할 수 없습니다.</h2>
+            <p><a href="%s">%s</a>에 있는 웹페이지를 다음으로 로드할 수 없습니다.</p>
+            <!-- The %e is replaced by a localized error string -->
+            <p>%e</p>
+        </body>
+    </head>
+</html>
diff --git a/core/res/res/raw-ko/nodomain.html b/core/res/res/raw-ko/nodomain.html
new file mode 100644
index 0000000..0eadc39
--- /dev/null
+++ b/core/res/res/raw-ko/nodomain.html
@@ -0,0 +1,24 @@
+<html>
+    <head>
+        <title>웹페이지를 표시할 수 없습니다.</title>
+        <style type="text/css">
+            body { margin-top: 0px; padding-top: 0px; }
+            h2   { margin-top: 5px; padding-top: 0px; }
+        </style>
+
+        <body>
+
+            <img src="file:///android_asset/webkit/android-weberror.png" align="top" />
+            <h2>웹페이지를 표시할 수 없습니다.</h2>
+            <p><a href="%s">%s</a>에 있는 웹페이지가 일시적으로 중단되었거나 새 웹 주소가 영구적으로 이동했을 수 있습니다.</p>
+
+            <p><b>다음은 몇 가지 제안사항입니다.</b></p>
+            <ul>
+                <li>휴대기기의 신호 및 데이터 접속 상태를 확인하세요.</li>
+                <li>나중에 웹페이지를 다시 로드하세요.</li>
+                <li>Google에서 웹페이지의 캐시된 사본을 확인하세요.</li>
+
+            </ul>
+        </body>
+    </head>
+</html>
diff --git a/core/res/res/raw-nl/loaderror.html b/core/res/res/raw-nl/loaderror.html
new file mode 100644
index 0000000..76bb07c
--- /dev/null
+++ b/core/res/res/raw-nl/loaderror.html
@@ -0,0 +1,18 @@
+<html>
+    <head>
+        <title>Webpagina niet beschikbaar</title>
+        <style type="text/css">
+            body { margin-top: 0px; padding-top: 0px; }
+            h2   { margin-top: 5px; padding-top: 0px; }
+        </style>
+
+        <body>
+
+            <img src="file:///android_asset/webkit/android-weberror.png" align="top" />
+            <h2>Webpagina niet beschikbaar</h2>
+            <p>De webpagina op <a href="%s">%s</a> kan niet worden geladen als:</p>
+            <!-- The %e is replaced by a localized error string -->
+            <p>%e</p>
+        </body>
+    </head>
+</html>
diff --git a/core/res/res/raw-nl/nodomain.html b/core/res/res/raw-nl/nodomain.html
new file mode 100644
index 0000000..ffac947
--- /dev/null
+++ b/core/res/res/raw-nl/nodomain.html
@@ -0,0 +1,24 @@
+<html>
+    <head>
+        <title>Webpagina niet beschikbaar</title>
+        <style type="text/css">
+            body { margin-top: 0px; padding-top: 0px; }
+            h2   { margin-top: 5px; padding-top: 0px; }
+        </style>
+
+        <body>
+
+            <img src="file:///android_asset/webkit/android-weberror.png" align="top" />
+            <h2>Webpagina niet beschikbaar</h2>
+            <p>De webpagina op <a href="%s">%s</a> is mogelijk tijdelijk niet beschikbaar of is permanent verplaatst naar een nieuw webadres.</p>
+
+            <p><b>Hier volgen enkele suggesties:</b></p>
+            <ul>
+                <li>Controleer of uw apparaat een signaal en gegevensverbinding heeft.</li>
+                <li>Laad deze webpagina later opnieuw.</li>
+                <li>Bekijk een exemplaar van de webpagina uit het cachegeheugen van Google.</li>
+
+            </ul>
+        </body>
+    </head>
+</html>
diff --git a/core/res/res/raw-pl/loaderror.html b/core/res/res/raw-pl/loaderror.html
new file mode 100644
index 0000000..9cc1342
--- /dev/null
+++ b/core/res/res/raw-pl/loaderror.html
@@ -0,0 +1,18 @@
+<html>
+    <head>
+        <title>Strona internetowa jest niedostępna</title>
+        <style type="text/css">
+            body { margin-top: 0px; padding-top: 0px; }
+            h2   { margin-top: 5px; padding-top: 0px; }
+        </style>
+
+        <body>
+
+            <img src="file:///android_asset/webkit/android-weberror.png" align="top" />
+            <h2>Strona internetowa jest niedostępna</h2>
+            <p>Nie można załadować strony internetowej pod adresem <a href="%s">%s</a> jako:</p>
+            <!-- The %e is replaced by a localized error string -->
+            <p>%e</p>
+        </body>
+    </head>
+</html>
diff --git a/core/res/res/raw-pl/nodomain.html b/core/res/res/raw-pl/nodomain.html
new file mode 100644
index 0000000..23f529d
--- /dev/null
+++ b/core/res/res/raw-pl/nodomain.html
@@ -0,0 +1,24 @@
+<html>
+    <head>
+        <title>Strona internetowa jest niedostępna</title>
+        <style type="text/css">
+            body { margin-top: 0px; padding-top: 0px; }
+            h2   { margin-top: 5px; padding-top: 0px; }
+        </style>
+
+        <body>
+
+            <img src="file:///android_asset/webkit/android-weberror.png" align="top" />
+            <h2>Strona internetowa jest niedostępna</h2>
+            <p>Strona internetowa pod adresem <a href="%s">%s</a> może być czasowo niedostępna lub mogła zostać trwale przeniesiona na nowy adres internetowy.</p>
+
+            <p><b>Oto kilka sugestii:</b></p>
+            <ul>
+                <li>Upewnij się, że w urządzeniu jest sygnał i połączenie transmisji danych.</li>
+                <li>Ponownie załaduj tę stronę internetową p&oacute;źniej.</li>
+                <li>Wyświetl kopię zapasową strony internetowej z serwisu Google.</li>
+
+            </ul>
+        </body>
+    </head>
+</html>
diff --git a/core/res/res/raw-pt-rBR/loaderror.html b/core/res/res/raw-pt-rBR/loaderror.html
new file mode 100644
index 0000000..3476239
--- /dev/null
+++ b/core/res/res/raw-pt-rBR/loaderror.html
@@ -0,0 +1,18 @@
+<html>
+    <head>
+        <title>P&aacute;gina da web n&atilde;o dispon&iacute;vel</title>
+        <style type="text/css">
+            body { margin-top: 0px; padding-top: 0px; }
+            h2   { margin-top: 5px; padding-top: 0px; }
+        </style>
+
+        <body>
+
+            <img src="file:///android_asset/webkit/android-weberror.png" align="top" />
+            <h2>P&aacute;gina da web n&atilde;o dispon&iacute;vel</h2>
+            <p>N&atilde;o foi poss&iacute;vel carregar a p&aacute;gina da web localizada em <a href="%s">%s</a> como:</p>
+            <!-- The %e is replaced by a localized error string -->
+            <p>%e</p>
+        </body>
+    </head>
+</html>
diff --git a/core/res/res/raw-pt-rBR/nodomain.html b/core/res/res/raw-pt-rBR/nodomain.html
new file mode 100644
index 0000000..546c610
--- /dev/null
+++ b/core/res/res/raw-pt-rBR/nodomain.html
@@ -0,0 +1,24 @@
+<html>
+    <head>
+        <title>P&aacute;gina da web n&atilde;o dispon&iacute;vel</title>
+        <style type="text/css">
+            body { margin-top: 0px; padding-top: 0px; }
+            h2   { margin-top: 5px; padding-top: 0px; }
+        </style>
+
+        <body>
+
+            <img src="file:///android_asset/webkit/android-weberror.png" align="top" />
+            <h2>P&aacute;gina da web n&atilde;o dispon&iacute;vel</h2>
+            <p>A p&aacute;gina da web localizada em <a href="%s">%s</a> pode estar temporariamente inativa ou pode ter sido movida permanentemente para um novo endere&ccedil;o da web.</p>
+
+            <p><b>Veja algumas sugest&otilde;es:</b></p>
+            <ul>
+                <li>Verifique se seu aparelho possui sinal e uma conex&atilde;o de dados.</li>
+                <li>Carregue esta p&aacute;gina da web novamente mais tarde.</li>
+                <li>Visualize uma c&oacute;pia da p&aacute;gina da web armazenada em cache proveniente do Google.</li>
+
+            </ul>
+        </body>
+    </head>
+</html>
diff --git a/core/res/res/raw-ru/loaderror.html b/core/res/res/raw-ru/loaderror.html
new file mode 100644
index 0000000..5a0312e
--- /dev/null
+++ b/core/res/res/raw-ru/loaderror.html
@@ -0,0 +1,18 @@
+<html>
+    <head>
+        <title>Веб-страница недоступна</title>
+        <style type="text/css">
+            body { margin-top: 0px; padding-top: 0px; }
+            h2   { margin-top: 5px; padding-top: 0px; }
+        </style>
+
+        <body>
+
+            <img src="file:///android_asset/webkit/android-weberror.png" align="top" />
+            <h2>Веб-страница недоступна</h2>
+            <p>Невозможно загрузить веб-страницу по адресу <a href="%s">%s</a> по следующей причине:</p>
+            <!-- The %e is replaced by a localized error string -->
+            <p>%e</p>
+        </body>
+    </head>
+</html>
diff --git a/core/res/res/raw-ru/nodomain.html b/core/res/res/raw-ru/nodomain.html
new file mode 100644
index 0000000..86a42a1
--- /dev/null
+++ b/core/res/res/raw-ru/nodomain.html
@@ -0,0 +1,24 @@
+<html>
+    <head>
+        <title>Веб-страница недоступна</title>
+        <style type="text/css">
+            body { margin-top: 0px; padding-top: 0px; }
+            h2   { margin-top: 5px; padding-top: 0px; }
+        </style>
+
+        <body>
+
+            <img src="file:///android_asset/webkit/android-weberror.png" align="top" />
+            <h2>Веб-страница недоступна</h2>
+            <p>Возможно, веб-страница по адресу <a href="%s">%s</a> временно отключена или навсегда перемещена на новый веб-адрес.</p>
+
+            <p><b>Вот несколько советов:</b></p>
+            <ul>
+                <li>Убедитесь, что у вашего устройства есть сигнал и подключение для передачи данных.</li>
+                <li>Повторите загрузку веб-страницы позже.</li>
+                <li>Просмотрите копию веб-страницы, сохраненную в кэше Google.</li>
+
+            </ul>
+        </body>
+    </head>
+</html>
diff --git a/core/res/res/raw-th/loaderror.html b/core/res/res/raw-th/loaderror.html
new file mode 100644
index 0000000..05310a7
--- /dev/null
+++ b/core/res/res/raw-th/loaderror.html
@@ -0,0 +1,18 @@
+<html>
+    <head>
+        <title>ไม่มีเว็บเพจนี้</title>
+        <style type="text/css">
+            body { margin-top: 0px; padding-top: 0px; }
+            h2   { margin-top: 5px; padding-top: 0px; }
+        </style>
+
+        <body>
+
+            <img src="file:///android_asset/webkit/android-weberror.png" align="top" />
+            <h2>ไม่มีเว็บเพจนี้</h2>
+            <p>เว็บเพจนี้ที่ <a href="%s">%s</a> ไม่สามารถโหลดเป็น:</p>
+            <!-- The %e is replaced by a localized error string -->
+            <p>%e</p>
+        </body>
+    </head>
+</html>
diff --git a/core/res/res/raw-th/nodomain.html b/core/res/res/raw-th/nodomain.html
new file mode 100644
index 0000000..37b8593
--- /dev/null
+++ b/core/res/res/raw-th/nodomain.html
@@ -0,0 +1,24 @@
+<html>
+    <head>
+        <title>ไม่มีเว็บเพจนี้</title>
+        <style type="text/css">
+            body { margin-top: 0px; padding-top: 0px; }
+            h2   { margin-top: 5px; padding-top: 0px; }
+        </style>
+
+        <body>
+
+            <img src="file:///android_asset/webkit/android-weberror.png" align="top" />
+            <h2>ไม่มีเว็บเพจนี้</h2>
+            <p>เว็บเพจที่ <a href="%s">%s</a> อาจใช้งานไม่ได้ชั่วคราว หรืออาจถูกย้ายไปยังที่อยู่เว็บใหม่เป็นการถาวร</p>
+
+            <p><b>ต่อไปนี้เป็นคำแนะนำบางประการ:</b></p>
+            <ul>
+                <li>ตรวจสอบให้แน่ใจว่าอุปกรณ์ของคุณมีสัญญาณและการเชื่อมต่อข้อมูล</li>
+                <li>โหลดเว็บเพจนี้ใหม่ภายหลัง</li>
+                <li>ดูสำเนาเว็บเพจที่เก็บไว้ในแคชจาก Google</li>
+
+            </ul>
+        </body>
+    </head>
+</html>
diff --git a/core/res/res/raw-tr/loaderror.html b/core/res/res/raw-tr/loaderror.html
new file mode 100644
index 0000000..b6f4890
--- /dev/null
+++ b/core/res/res/raw-tr/loaderror.html
@@ -0,0 +1,18 @@
+<html>
+    <head>
+        <title>Web sayfası yok</title>
+        <style type="text/css">
+            body { margin-top: 0px; padding-top: 0px; }
+            h2   { margin-top: 5px; padding-top: 0px; }
+        </style>
+
+        <body>
+
+            <img src="file:///android_asset/webkit/android-weberror.png" align="top" />
+            <h2>Web sayfası yok</h2>
+            <p><a href="%s">%s</a> adresindeki web sayfası y&uuml;klenemedi:</p>
+            <!-- The %e is replaced by a localized error string -->
+            <p>%e</p>
+        </body>
+    </head>
+</html>
diff --git a/core/res/res/raw-tr/nodomain.html b/core/res/res/raw-tr/nodomain.html
new file mode 100644
index 0000000..a79c21b
--- /dev/null
+++ b/core/res/res/raw-tr/nodomain.html
@@ -0,0 +1,24 @@
+<html>
+    <head>
+        <title>Web sayfası yok</title>
+        <style type="text/css">
+            body { margin-top: 0px; padding-top: 0px; }
+            h2   { margin-top: 5px; padding-top: 0px; }
+        </style>
+
+        <body>
+
+            <img src="file:///android_asset/webkit/android-weberror.png" align="top" />
+            <h2>Web sayfası yok</h2>
+            <p><a href="%s">%s</a> adresindeki web sayfası ge&ccedil;ici olarak arızalı veya kalıcı olarak yeni bir web adresine taşınmış olabilir.</p>
+
+            <p><b>Bazı &ouml;neriler:</b></p>
+            <ul>
+                <li>Aygıtınızın sinyal aldığını ve veri bağlantısı bulunduğunu kontrol edin.</li>
+                <li>Bu web sayfasını daha sonra tekrar y&uuml;kleyin.</li>
+                <li>Google'dan web sayfasının &ouml;nbelleğe alınmış kopyasına bakın</li>
+
+            </ul>
+        </body>
+    </head>
+</html>
diff --git a/core/res/res/raw-zh-rCN/loaderror.html b/core/res/res/raw-zh-rCN/loaderror.html
new file mode 100644
index 0000000..809d31f
--- /dev/null
+++ b/core/res/res/raw-zh-rCN/loaderror.html
@@ -0,0 +1,18 @@
+<html>
+    <head>
+        <title>找不到网页</title>
+        <style type="text/css">
+            body { margin-top: 0px; padding-top: 0px; }
+            h2   { margin-top: 5px; padding-top: 0px; }
+        </style>
+
+        <body>
+
+            <img src="file:///android_asset/webkit/android-weberror.png" align="top" />
+            <h2>找不到网页</h2>
+            <p><a href="%s">%s</a> 处的网页不能载入为:</p>
+            <!-- The %e is replaced by a localized error string -->
+            <p>%e</p>
+        </body>
+    </head>
+</html>
diff --git a/core/res/res/raw-zh-rCN/nodomain.html b/core/res/res/raw-zh-rCN/nodomain.html
new file mode 100644
index 0000000..eb03187
--- /dev/null
+++ b/core/res/res/raw-zh-rCN/nodomain.html
@@ -0,0 +1,24 @@
+<html>
+    <head>
+        <title>找不到网页</title>
+        <style type="text/css">
+            body { margin-top: 0px; padding-top: 0px; }
+            h2   { margin-top: 5px; padding-top: 0px; }
+        </style>
+
+        <body>
+
+            <img src="file:///android_asset/webkit/android-weberror.png" align="top" />
+            <h2>找不到网页</h2>
+            <p><a href="%s">%s</a> 处的网页可能暂时出现故障,也可能已永久移至某个新的网络地址。</p>
+
+            <p><b>以下是几点建议:</b></p>
+            <ul>
+                <li>进行检查以确保您的设备具有信号和数据连接</li>
+                <li>稍后重新载入该网页。</li>
+                <li>查看 Google 提供的该网页的缓存副本</li>
+
+            </ul>
+        </body>
+    </head>
+</html>
diff --git a/core/res/res/raw-zh-rTW/loaderror.html b/core/res/res/raw-zh-rTW/loaderror.html
new file mode 100644
index 0000000..0b4695a
--- /dev/null
+++ b/core/res/res/raw-zh-rTW/loaderror.html
@@ -0,0 +1,18 @@
+<html>
+    <head>
+        <title>您所查詢的網頁不存在或已移除</title>
+        <style type="text/css">
+            body { margin-top: 0px; padding-top: 0px; }
+            h2   { margin-top: 5px; padding-top: 0px; }
+        </style>
+
+        <body>
+
+            <img src="file:///android_asset/webkit/android-weberror.png" align="top" />
+            <h2>您所查詢的網頁不存在或已移除</h2>
+            <p>由於以下原因,此位址 <a href="%s">%s</a> 的網頁無法開啟:</p>
+            <!-- The %e is replaced by a localized error string -->
+            <p>%e</p>
+        </body>
+    </head>
+</html>
diff --git a/core/res/res/raw-zh-rTW/nodomain.html b/core/res/res/raw-zh-rTW/nodomain.html
new file mode 100644
index 0000000..3577a9d
--- /dev/null
+++ b/core/res/res/raw-zh-rTW/nodomain.html
@@ -0,0 +1,24 @@
+<html>
+    <head>
+        <title>您所查詢的網頁不存在或已移除</title>
+        <style type="text/css">
+            body { margin-top: 0px; padding-top: 0px; }
+            h2   { margin-top: 5px; padding-top: 0px; }
+        </style>
+
+        <body>
+
+            <img src="file:///android_asset/webkit/android-weberror.png" align="top" />
+            <h2>您所查詢的網頁不存在或已移除</h2>
+            <p>此網頁位址:<a href="%s">%s</a> 可能暫時無法存取或已經被移到新的網頁位址。</p>
+
+            <p><b>建議您嘗試以下動作:</b></p>
+            <ul>
+                <li>檢查裝置是否有訊號、資料連線是否正常。</li>
+                <li>稍後重新載入網頁</li>
+                <li>從 Google 檢視網頁快取複本</li>
+
+            </ul>
+        </body>
+    </head>
+</html>
diff --git a/core/res/res/raw/fallbackring.ogg b/core/res/res/raw/fallbackring.ogg
new file mode 100644
index 0000000..0cbf55d
--- /dev/null
+++ b/core/res/res/raw/fallbackring.ogg
Binary files differ
diff --git a/core/res/res/raw/loaderror.html b/core/res/res/raw/loaderror.html
new file mode 100644
index 0000000..359a1e7
--- /dev/null
+++ b/core/res/res/raw/loaderror.html
@@ -0,0 +1,18 @@
+<html>
+    <head>
+        <title>Web page not available</title>
+        <style type="text/css">
+            body { margin-top: 0px; padding-top: 0px; }
+            h2   { margin-top: 5px; padding-top: 0px; }
+        </style>
+
+        <body>
+
+            <img src="file:///android_asset/webkit/android-weberror.png" align="top" />
+            <h2>Web page not available</h2>
+            <p>The Web page at <a href="%s">%s</a> could not be loaded as:</p>
+            <!-- The %e is replaced by a localized error string -->
+            <p>%e</p>
+        </body>
+    </head>
+</html>
diff --git a/core/res/res/raw/nodomain.html b/core/res/res/raw/nodomain.html
new file mode 100644
index 0000000..7a107fb
--- /dev/null
+++ b/core/res/res/raw/nodomain.html
@@ -0,0 +1,27 @@
+<html>
+    <head>
+        <title>Web page not available</title>
+        <style type="text/css">
+            body { margin-top: 0px; padding-top: 0px; }
+            h2   { margin-top: 5px; padding-top: 0px; }
+        </style>
+
+        <body>
+
+            <img src="file:///android_asset/webkit/android-weberror.png" align="top" />
+            <h2>Web page not available</h2>
+            <p>The Web page at <a href="%s">%s</a> might be
+            temporarily down or it may have moved permanently to a new web
+            address.</p>
+
+            <p><b>Here are some suggestions:</b></p>
+            <ul>
+                <li>Check to make sure your device has a signal and data
+                connection</li>
+                <li>Reload this web page later.</li>
+                <li>View a cached copy of the web page from Google</li>
+
+            </ul>
+        </body>
+    </head>
+</html>
diff --git a/core/res/res/values-cs/strings.xml b/core/res/res/values-cs/strings.xml
new file mode 100644
index 0000000..13aca371
--- /dev/null
+++ b/core/res/res/values-cs/strings.xml
@@ -0,0 +1,815 @@
+<?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.
+-->
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="byteShort">"B"</string>
+    <string name="kilobyteShort">"kB"</string>
+    <string name="megabyteShort">"MB"</string>
+    <string name="gigabyteShort">"GB"</string>
+    <string name="terabyteShort">"TB"</string>
+    <string name="petabyteShort">"PB"</string>
+    <string name="untitled">"&lt;bez názvu&gt;"</string>
+    <string name="ellipsis">"…"</string>
+    <string name="emptyPhoneNumber">"(žádné telefonní číslo)"</string>
+    <string name="unknownName">"(Neznámé)"</string>
+    <string name="defaultVoiceMailAlphaTag">"Hlasová schránka"</string>
+    <string name="defaultMsisdnAlphaTag">"MSISDN1"</string>
+    <string name="mmiError">"Problém s připojením nebo neplatný kód MMI."</string>
+    <string name="serviceEnabled">"Služba byla zapnuta."</string>
+    <string name="serviceEnabledFor">"Služba byla zapnuta pro:"</string>
+    <string name="serviceDisabled">"Služba byla vypnuta."</string>
+    <string name="serviceRegistered">"Registrace byla úspěšná."</string>
+    <string name="serviceErased">"Smazaní proběhlo úspěšně."</string>
+    <string name="passwordIncorrect">"Nesprávné heslo."</string>
+    <string name="mmiComplete">"Funkce MMI byla dokončena."</string>
+    <string name="badPin">"Původní kód PIN byl zadán nesprávně."</string>
+    <string name="badPuk">"Kód PUK byl zadán nesprávně."</string>
+    <string name="mismatchPin">"Zadané kódy PIN se neshodují."</string>
+    <string name="invalidPin">"Zadejte kód PIN o délce 4-8 číslic."</string>
+    <string name="needPuk">"Karta SIM je blokována pomocí kódu PUK. Odblokujete ji zadáním kódu PUK."</string>
+    <string name="needPuk2">"Chcete-li odblokovat kartu SIM, zadejte kód PUK2."</string>
+    <string name="ClipMmi">"Příchozí identifikace volajícího"</string>
+    <string name="ClirMmi">"Odchozí identifikace volajícího"</string>
+    <string name="CfMmi">"Přesměrování hovorů"</string>
+    <string name="CwMmi">"Další hovor na lince"</string>
+    <string name="BaMmi">"Blokování hovorů"</string>
+    <string name="PwdMmi">"Změna hesla"</string>
+    <string name="PinMmi">"Změna kódu PIN"</string>
+    <string name="CLIRDefaultOnNextCallOn">"Ve výchozím nastavení je identifikace volajícího omezena. Příští hovor: Omezeno"</string>
+    <string name="CLIRDefaultOnNextCallOff">"Ve výchozím nastavení je identifikace volajícího omezena. Příští hovor: Neomezeno"</string>
+    <string name="CLIRDefaultOffNextCallOn">"Ve výchozím nastavení není identifikace volajícího omezena. Příští hovor: Omezeno"</string>
+    <string name="CLIRDefaultOffNextCallOff">"Ve výchozím nastavení není identifikace volajícího omezena. Příští hovor: Neomezeno"</string>
+    <string name="serviceNotProvisioned">"Služba není zřízena."</string>
+    <string name="CLIRPermanent">"Nelze změnit nastavení identifikace volajícího."</string>
+    <string name="serviceClassVoice">"Hlas"</string>
+    <string name="serviceClassData">"Data"</string>
+    <string name="serviceClassFAX">"FAX"</string>
+    <string name="serviceClassSMS">"SMS"</string>
+    <string name="serviceClassDataAsync">"Async"</string>
+    <string name="serviceClassDataSync">"Synchronizace"</string>
+    <string name="serviceClassPacket">"Pakety"</string>
+    <string name="serviceClassPAD">"PAD"</string>
+    <string name="cfTemplateNotForwarded">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: Nepřesměrováno"</string>
+    <string name="cfTemplateForwarded">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
+    <string name="cfTemplateForwardedTime">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> po <xliff:g id="TIME_DELAY">{2}</xliff:g> sek."</string>
+    <string name="cfTemplateRegistered">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: Nepřesměrováno"</string>
+    <string name="cfTemplateRegisteredTime">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: Nepřesměrováno"</string>
+    <string name="httpErrorOk">"OK"</string>
+    <string name="httpError">"Webová stránka obsahuje chybu."</string>
+    <string name="httpErrorLookup">"Adresu URL nelze najít."</string>
+    <string name="httpErrorUnsupportedAuthScheme">"Schéma ověření webu není podporováno."</string>
+    <string name="httpErrorAuth">"Ověření nebylo úspěšné."</string>
+    <string name="httpErrorProxyAuth">"Ověření pomocí serveru proxy bylo neúspěšné."</string>
+    <string name="httpErrorConnect">"Připojení k serveru bylo neúspěšné."</string>
+    <string name="httpErrorIO">"Komunikace se serverem se nezdařila. Opakujte akci později."</string>
+    <string name="httpErrorTimeout">"Spojení se serverem vypršelo."</string>
+    <string name="httpErrorRedirectLoop">"Stránka obsahuje příliš mnoho přesměrování serveru."</string>
+    <string name="httpErrorUnsupportedScheme">"Protokol není podporován."</string>
+    <string name="httpErrorFailedSslHandshake">"Nelze navázat zabezpečené spojení."</string>
+    <string name="httpErrorBadUrl">"Stránku nelze otevřít, protože adresa URL je neplatná."</string>
+    <string name="httpErrorFile">"K souboru nelze získat přístup."</string>
+    <string name="httpErrorFileNotFound">"Požadovaný soubor nebyl nalezen."</string>
+    <string name="httpErrorTooManyRequests">"Je zpracováváno příliš mnoho požadavků. Opakujte akci později."</string>
+    <string name="contentServiceSync">"Synchronizace"</string>
+    <string name="contentServiceSyncNotificationTitle">"Synchronizace"</string>
+    <string name="contentServiceTooManyDeletesNotificationDesc">"Příliš mnoho smazaných položek služby <xliff:g id="CONTENT_TYPE">%s</xliff:g>."</string>
+    <string name="low_memory">"Paměť telefonu je plná. Smažte některé soubory a uvolněte místo."</string>
+    <string name="me">"Já"</string>
+    <string name="power_dialog">"Možnosti telefonu"</string>
+    <string name="silent_mode">"Tichý režim"</string>
+    <string name="turn_on_radio">"Zapnout bezdrátové připojení"</string>
+    <string name="turn_off_radio">"Vypnout bezdrátové připojení"</string>
+    <string name="screen_lock">"Zámek obrazovky"</string>
+    <string name="power_off">"Vypnout"</string>
+    <string name="shutdown_progress">"Vypínání..."</string>
+    <string name="shutdown_confirm">"Váš telefon bude vypnut."</string>
+    <string name="no_recent_tasks">"Žádné nedávno použité aplikace."</string>
+    <string name="global_actions">"Možnosti telefonu"</string>
+    <string name="global_action_lock">"Zámek obrazovky"</string>
+    <string name="global_action_power_off">"Vypnout"</string>
+    <string name="global_action_toggle_silent_mode">"Tichý režim"</string>
+    <string name="global_action_silent_mode_on_status">"Zvuk je VYPNUTÝ."</string>
+    <string name="global_action_silent_mode_off_status">"Zvuk je zapnutý"</string>
+    <!-- no translation found for global_actions_toggle_airplane_mode (5884330306926307456) -->
+    <skip />
+    <!-- no translation found for global_actions_airplane_mode_on_status (2719557982608919750) -->
+    <skip />
+    <!-- no translation found for global_actions_airplane_mode_off_status (5075070442854490296) -->
+    <skip />
+    <string name="safeMode">"Nouzový režim"</string>
+    <!-- no translation found for android_system_label (6577375335728551336) -->
+    <skip />
+    <string name="permgrouplab_costMoney">"Zpoplatněné služby"</string>
+    <string name="permgroupdesc_costMoney">"Umožňuje aplikacím provádět činnosti, které vás mohou stát peníze."</string>
+    <string name="permgrouplab_messages">"Vaše zprávy"</string>
+    <string name="permgroupdesc_messages">"Čtení a zápis zpráv SMS, e-mailů a dalších zpráv."</string>
+    <string name="permgrouplab_personalInfo">"Vaše osobní informace"</string>
+    <string name="permgroupdesc_personalInfo">"Přímý přístup k vašim kontaktům a kalendáři v telefonu."</string>
+    <string name="permgrouplab_location">"Vaše poloha"</string>
+    <string name="permgroupdesc_location">"Sleduje vaši fyzickou polohu"</string>
+    <string name="permgrouplab_network">"Síťová komunikace"</string>
+    <string name="permgroupdesc_network">"Umožňuje aplikacím získat přístup k různým funkcím sítě."</string>
+    <string name="permgrouplab_accounts">"Vaše účty Google"</string>
+    <string name="permgroupdesc_accounts">"Přístup k dostupným účtům Google."</string>
+    <string name="permgrouplab_hardwareControls">"Řízení hardwaru"</string>
+    <string name="permgroupdesc_hardwareControls">"Přímý přístup k hardwaru telefonu."</string>
+    <string name="permgrouplab_phoneCalls">"Telefonní hovory"</string>
+    <string name="permgroupdesc_phoneCalls">"Sledování, záznam a zpracování telefonních hovorů."</string>
+    <string name="permgrouplab_systemTools">"Systémové nástroje"</string>
+    <string name="permgroupdesc_systemTools">"Nízkoúrovňový přístup a kontrola nad systémem."</string>
+    <string name="permgrouplab_developmentTools">"Nástroje pro vývojáře"</string>
+    <string name="permgroupdesc_developmentTools">"Funkce pouze pro vývojáře aplikací"</string>
+    <string name="permlab_statusBar">"zakázání či změny stavového řádku"</string>
+    <string name="permdesc_statusBar">"Umožňuje aplikaci zakázat stavový řádek nebo přidat či odebrat systémové ikony."</string>
+    <string name="permlab_expandStatusBar">"rozbalení a sbalení stavového řádku"</string>
+    <string name="permdesc_expandStatusBar">"Umožňuje aplikaci rozbalit či sbalit stavový řádek."</string>
+    <string name="permlab_processOutgoingCalls">"zachycení odchozích hovorů"</string>
+    <string name="permdesc_processOutgoingCalls">"Umožňuje aplikaci zpracovat odchozí hovory a změnit číslo, které má být vytočeno. Škodlivé aplikace mohou sledovat či přesměrovat odchozí hovory nebo jim zabránit."</string>
+    <string name="permlab_receiveSms">"příjem zpráv SMS"</string>
+    <string name="permdesc_receiveSms">"Umožňuje aplikaci přijímat a zpracovávat zprávy SMS. Škodlivé aplikace mohou sledovat vaše zprávy nebo je smazat, aniž by vám byly zobrazeny."</string>
+    <string name="permlab_receiveMms">"příjem zpráv MMS"</string>
+    <string name="permdesc_receiveMms">"Umožňuje aplikaci přijímat a zpracovávat zprávy MMS. Škodlivé aplikace mohou sledovat vaše zprávy nebo je smazat, aniž by vám byly zobrazeny."</string>
+    <string name="permlab_sendSms">"odesílat zprávy SMS"</string>
+    <string name="permdesc_sendSms">"Umožňuje aplikaci odesílat zprávy SMS. Škodlivé aplikace mohou bez vašeho potvrzení odesílat zpoplatněné zprávy."</string>
+    <string name="permlab_readSms">"čtení zpráv SMS a MMS"</string>
+    <string name="permdesc_readSms">"Umožňuje aplikaci číst zprávy SMS uložené ve vašem telefonu nebo na kartě SIM. Škodlivé aplikace mohou načíst vaše soukromé zprávy."</string>
+    <string name="permlab_writeSms">"úprava zpráv SMS a MMS"</string>
+    <string name="permdesc_writeSms">"Umožňuje aplikaci zapisovat do zpráv SMS uložených ve vašem telefonu nebo na kartě SIM. Škodlivé aplikace mohou smazat vaše zprávy."</string>
+    <string name="permlab_receiveWapPush">"příjem WAP"</string>
+    <string name="permdesc_receiveWapPush">"Umožňuje aplikaci přijímat a zpracovávat zprávy WAP. Škodlivé aplikace mohou sledovat vaše zprávy nebo je smazat, aniž by vám byly zobrazeny."</string>
+    <string name="permlab_getTasks">"načtení spuštěných aplikací"</string>
+    <string name="permdesc_getTasks">"Umožňuje aplikaci načíst informace o aktuálně a nedávno spuštěných úlohách. Toto nastavení může škodlivým aplikacím umožnit odhalení soukromých informací o jiných aplikacích."</string>
+    <string name="permlab_reorderTasks">"změna uspořádání spuštěných aplikací"</string>
+    <string name="permdesc_reorderTasks">"Umožňuje aplikaci přesouvat úlohy do popředí či pozadí. Škodlivé aplikace mohou vynutit své přesunutí do popředí bez vašeho přičinění."</string>
+    <string name="permlab_setDebugApp">"povolit ladění aplikací"</string>
+    <string name="permdesc_setDebugApp">"Umožňuje aplikaci povolit ladění jiné aplikace. Škodlivé aplikace mohou pomocí tohoto nastavení ukončit jiné aplikace."</string>
+    <string name="permlab_changeConfiguration">"změny vašeho nastavení uživatelského rozhraní"</string>
+    <string name="permdesc_changeConfiguration">"Umožňuje aplikaci změnit aktuální konfiguraci, např. národní prostředí či obecnou velikost písma."</string>
+    <string name="permlab_restartPackages">"restartování ostatních aplikací"</string>
+    <string name="permdesc_restartPackages">"Umožňuje aplikaci vynutit restartování jiných aplikací."</string>
+    <string name="permlab_setProcessForeground">"zamezení zastavení aplikace"</string>
+    <string name="permdesc_setProcessForeground">"Umožňuje aplikaci spustit jakýkoli proces v popředí tak, že ho nelze ukončit. Běžné aplikace by toto nastavení nikdy neměly používat."</string>
+    <string name="permlab_forceBack">"vynucení zavření aplikace"</string>
+    <string name="permdesc_forceBack">"Umožňuje aplikaci vynutit zavření a přesunutí libovolné činnosti v popředí na pozadí. Běžné aplikace by toto nastavení neměly nikdy využívat."</string>
+    <string name="permlab_dump">"načtení interního stavu systému"</string>
+    <string name="permdesc_dump">"Umožňuje aplikaci načíst interní stav systému. Škodlivé aplikace mohou načíst řádu soukromých a zabezpečených informací, které by nikdy neměly potřebovat."</string>
+    <string name="permlab_addSystemService">"zveřejnění nízkoúrovňových služeb"</string>
+    <string name="permdesc_addSystemService">"Umožňuje aplikaci zveřejnit své vlastní nízkoúrovňové systémové služby. Škodlivé aplikace mohou převzít kontrolu nad systémem a získat či poškodit jakákoli data v něm obsažená."</string>
+    <string name="permlab_runSetActivityWatcher">"sledování a řízení spouštění všech aplikací"</string>
+    <string name="permdesc_runSetActivityWatcher">"Umožňuje aplikaci sledovat a řídit spouštění činností systémem. Škodlivé aplikace mohou zcela ovládnout systém. Toto oprávnění je zapotřebí pouze pro účely vývoje, nikdy pro běžné použití telefonu."</string>
+    <string name="permlab_broadcastPackageRemoved">"odeslání vysílání o odstranění balíčku"</string>
+    <string name="permdesc_broadcastPackageRemoved">"Umožňuje aplikaci vysílat oznámení o odstranění balíčku aplikace. Škodlivé aplikace mohou pomocí tohoto nastavení ukončit libovolnou další spuštěnou aplikaci."</string>
+    <string name="permlab_broadcastSmsReceived">"odeslání vysílání o přijaté zprávě SMS"</string>
+    <string name="permdesc_broadcastSmsReceived">"Umožňuje aplikaci vysílat oznámení o přijetí zprávy SMS. Škodlivé aplikace mohou pomocí tohoto nastavení falšovat příchozí zprávy SMS."</string>
+    <string name="permlab_broadcastWapPush">"odeslání vysílání typu WAP-PUSH-received"</string>
+    <string name="permdesc_broadcastWapPush">"Umožňuje aplikaci vysílat oznámení o přijetí zprávy WAP PUSH. Škodlivé aplikace mohou pomocí tohoto nastavení zfalšovat výpis o doručení zprávy MMS nebo nepozorovaně nahradit obsah jakékoli webové stránky škodlivým obsahem."</string>
+    <string name="permlab_setProcessLimit">"omezení počtu spuštěných procesů"</string>
+    <string name="permdesc_setProcessLimit">"Umožňuje aplikaci řídit maximální počet spuštěných procesů. Běžné aplikace toto nastavení nikdy nevyužívají."</string>
+    <string name="permlab_setAlwaysFinish">"zavření všech aplikací na pozadí"</string>
+    <string name="permdesc_setAlwaysFinish">"Umožňuje aplikaci ovládat, zda jsou činnosti vždy dokončeny po přesunutí do pozadí. Běžné aplikace toto nastavení nikdy nevyužívají."</string>
+    <string name="permlab_fotaUpdate">"automatická instalace aktualizací systému"</string>
+    <string name="permdesc_fotaUpdate">"Umožňuje aplikaci přijímat oznámení o čekajících aktualizacích systému a spouštět jejich instalaci. Škodlivé aplikace mohou díky tomuto nastavení poškodit systém pomocí neoprávněných aktualizací nebo celkově narušovat proces aktualizace."</string>
+    <string name="permlab_batteryStats">"změna statistických údajů o baterii"</string>
+    <string name="permdesc_batteryStats">"Umožňuje změnu shromážděných statistických údajů o baterii. Není určeno pro běžné aplikace."</string>
+    <string name="permlab_internalSystemWindow">"zobrazení nepovolených oken"</string>
+    <string name="permdesc_internalSystemWindow">"Umožňuje vytvoření oken, která mají být použita interním systémem uživatelského rozhraní. Běžné aplikace toto nastavení nepoužívají."</string>
+    <string name="permlab_systemAlertWindow">"zobrazení upozornění systémové úrovně"</string>
+    <string name="permdesc_systemAlertWindow">"Umožňuje aplikaci zobrazit okna s výstrahami systému. Škodlivé aplikace mohou převzít kontrolu nad celou obrazovkou telefonu."</string>
+    <string name="permlab_setAnimationScale">"globální změny rychlosti animace"</string>
+    <string name="permdesc_setAnimationScale">"Umožňuje aplikaci kdykoli globálně změnit rychlost animace (rychlejší či pomalejší animace)."</string>
+    <string name="permlab_manageAppTokens">"správa tokenů aplikací"</string>
+    <string name="permdesc_manageAppTokens">"Umožňuje aplikaci vytvořit a spravovat své vlastní tokeny a obejít jejich obvyklé řazení typu Z. Toto nastavení by nikdy nemělo být potřeba pro běžné aplikace."</string>
+    <string name="permlab_injectEvents">"používání kláves a tlačítek"</string>
+    <string name="permdesc_injectEvents">"Umožňuje aplikaci doručit své vlastní vstupní události (stisknutí tlačítek, apod.) dalším aplikacím. Škodlivé aplikace mohou pomocí tohoto nastavení převzít kontrolu nad telefonem."</string>
+    <string name="permlab_readInputState">"zaznamenání psaného textu a prováděných činností"</string>
+    <string name="permdesc_readInputState">"Umožňuje aplikacím sledovat, které klávesy používáte, a to i při práci s jinými aplikacemi (například při zadávání hesla). Běžné aplikace by toto nastavení nikdy neměly vyžadovat."</string>
+    <string name="permlab_bindInputMethod">"vazba k metodě zadávání dat"</string>
+    <string name="permdesc_bindInputMethod">"Umožňuje držiteli vázat se na nejvyšší úroveň rozhraní pro zadávání dat. Běžné aplikace by toto nastavení nikdy neměly využívat."</string>
+    <string name="permlab_setOrientation">"změna orientace obrazovky"</string>
+    <string name="permdesc_setOrientation">"Umožňuje aplikaci kdykoli změnit orientaci obrazovky. Běžné aplikace by toto nastavení nikdy neměly využívat."</string>
+    <string name="permlab_signalPersistentProcesses">"odeslání signálů Linux aplikacím"</string>
+    <string name="permdesc_signalPersistentProcesses">"Umožňuje aplikaci vyžádat zaslání poskytnutého signálu všem trvalým procesům."</string>
+    <string name="permlab_persistentActivity">"trvalé spuštění aplikace"</string>
+    <string name="permdesc_persistentActivity">"Umožňuje aplikaci učinit své části trvalými, takže je systém nemůže použít pro jiné aplikace."</string>
+    <string name="permlab_deletePackages">"smazání aplikací"</string>
+    <string name="permdesc_deletePackages">"Umožňuje aplikaci smazat balíčky systému Android. Škodlivé aplikace mohou pomocí tohoto nastavení smazat důležité aplikace."</string>
+    <string name="permlab_clearAppUserData">"smazání dat ostatních aplikací"</string>
+    <string name="permdesc_clearAppUserData">"Umožňuje aplikaci smazat data uživatele."</string>
+    <string name="permlab_deleteCacheFiles">"smazání mezipaměti ostatních aplikací"</string>
+    <string name="permdesc_deleteCacheFiles">"Umožňuje aplikaci smazat soubory v mezipaměti."</string>
+    <string name="permlab_getPackageSize">"výpočet místa pro ukládání aplikací"</string>
+    <string name="permdesc_getPackageSize">"Umožňuje aplikaci načtení svého kódu, dat a velikostí mezipaměti"</string>
+    <string name="permlab_installPackages">"přímá instalace aplikací"</string>
+    <string name="permdesc_installPackages">"Umožňuje aplikaci nainstalovat nové či aktualizované balíčky systému Android. Škodlivé aplikace mohou pomocí tohoto nastavení přidat nové aplikace s libovolnými oprávněními."</string>
+    <string name="permlab_clearAppCache">"smazání všech dat v mezipaměti aplikace"</string>
+    <string name="permdesc_clearAppCache">"Umožňuje aplikaci uvolnit paměť telefonu smazáním souborů v adresáři mezipaměti aplikace. Přístup je velmi omezený, většinou pouze pro systémové procesy."</string>
+    <string name="permlab_readLogs">"čtení systémových souborů protokolu"</string>
+    <string name="permdesc_readLogs">"Umožňuje aplikaci číst různé systémové soubory protokolů. Toto nastavení aplikaci umožní získat obecné informace o činnostech s telefonem, ale neměly by obsahovat žádné osobní či soukromé informace."</string>
+    <string name="permlab_diagnostic">"čtení nebo zápis do prostředků funkce diag"</string>
+    <string name="permdesc_diagnostic">"Umožňuje aplikaci číst libovolné prostředky ve skupině diag, např. soubory ve složce /dev, a zapisovat do nich. Může dojít k ovlivnění stability a bezpečnosti systému. Toto nastavení by měl používat pouze výrobce či operátor pro diagnostiku hardwaru."</string>
+    <string name="permlab_changeComponentState">"povolení či zakázání komponent aplikací"</string>
+    <string name="permdesc_changeComponentState">"Umožňuje aplikaci změnit, zda je komponenta jiné aplikace povolena nebo ne. Škodlivé aplikace mohou pomocí tohoto nastavení vypnout důležité funkce telefonu. Je třeba postupovat opatrně, protože je možné způsobit nepoužitelnost, nekonzistenci či nestabilitu komponent aplikací."</string>
+    <string name="permlab_setPreferredApplications">"nastavení upřednostňovaných aplikací"</string>
+    <string name="permdesc_setPreferredApplications">"Umožňuje aplikaci změnit vaše upřednostňované aplikace. Toto nastavení může škodlivým aplikacím umožnit nepozorovaně změnit spouštěné aplikace a oklamat vaše existující aplikace tak, aby shromažďovaly vaše soukromá data."</string>
+    <string name="permlab_writeSettings">"změny globálních nastavení systému"</string>
+    <string name="permdesc_writeSettings">"Umožňuje aplikaci upravit data nastavení systému. Škodlivé aplikace mohou poškodit konfiguraci vašeho systému."</string>
+    <string name="permlab_writeSecureSettings">"změny zabezpečených nastavení systému"</string>
+    <string name="permdesc_writeSecureSettings">"Umožňuje aplikaci změnit data zabezpečených nastavení systému. Běžné aplikace toto nastavení nevyužívají."</string>
+    <string name="permlab_writeGservices">"změny mapy služeb Google"</string>
+    <string name="permdesc_writeGservices">"Umožňuje aplikaci změnit mapu služeb Google. Běžné aplikace toto nastavení nevyužívají."</string>
+    <string name="permlab_receiveBootCompleted">"automatické spuštění při startu"</string>
+    <string name="permdesc_receiveBootCompleted">"Umožňuje aplikaci spuštění ihned po spuštění systému. Toto nastavení může zpomalit spuštění telefonu a umožnit aplikaci celkově zpomalit telefon, protože bude neustále spuštěna."</string>
+    <string name="permlab_broadcastSticky">"odeslání trvalého vysílání"</string>
+    <string name="permdesc_broadcastSticky">"Umožňuje aplikaci odeslat trvalá vysílání, která přetrvávají i po skončení vysílání. Škodlivé aplikace mohou telefon zpomalit či způsobit jeho nestabilitu, protože bude používat příliš mnoho paměti."</string>
+    <string name="permlab_readContacts">"čtení dat kontaktů"</string>
+    <string name="permdesc_readContacts">"Umožňuje aplikaci načíst všechna data kontaktů (adresy) uložená ve vašem telefonu. Škodlivé aplikace poté mohou dalším lidem odeslat vaše data."</string>
+    <string name="permlab_writeContacts">"zápis dat kontaktů"</string>
+    <string name="permdesc_writeContacts">"Umožňuje aplikaci změnit kontaktní údaje (adresu) uložené v telefonu. Škodlivé aplikace mohou pomocí tohoto nastavení vymazat či pozměnit kontaktní údaje."</string>
+    <string name="permlab_writeOwnerData">"zápis informací o vlastníkovi"</string>
+    <string name="permdesc_writeOwnerData">"Umožňuje aplikaci změnit informace o vlastníkovi telefonu uložené v telefonu. Škodlivé aplikace mohou pomocí tohoto nastavení vymazat či pozměnit informace o vlastníkovi."</string>
+    <string name="permlab_readOwnerData">"čtení informací o vlastníkovi"</string>
+    <string name="permdesc_readOwnerData">"Umožňuje aplikaci číst informace o vlastníkovi telefonu uložená v telefonu. Škodlivé aplikace mohou pomocí tohoto nastavení načíst informace o vlastníkovi."</string>
+    <string name="permlab_readCalendar">"čtení dat kalendáře"</string>
+    <string name="permdesc_readCalendar">"Umožňuje aplikaci načíst všechny události kalendáře uložené ve vašem telefonu. Škodlivé aplikace poté mohou dalším lidem odeslat události z vašeho kalendáře."</string>
+    <string name="permlab_writeCalendar">"zápis dat kalendáře"</string>
+    <string name="permdesc_writeCalendar">"Umožňuje aplikaci změnit události kalendáře uložené v telefonu. Škodlivé aplikace mohou pomocí tohoto nastavení vymazat či pozměnit vaše data v kalendáři."</string>
+    <string name="permlab_accessMockLocation">"simulace zdrojů polohy pro účely testování"</string>
+    <string name="permdesc_accessMockLocation">"Vytváří simulované zdroje polohy pro účely testování. Škodlivé aplikace mohou pomocí tohoto nastavení změnit polohu či stav vrácený zdroji skutečné polohy, jako je např. jednotka GPS či poskytovatelé sítě."</string>
+    <string name="permlab_accessLocationExtraCommands">"přístup k dalším příkazům poskytovatele polohy"</string>
+    <string name="permdesc_accessLocationExtraCommands">"Umožňuje získat přístup k dalším příkazům poskytovatele polohy. Škodlivé aplikace mohou pomocí tohoto nastavení narušit funkci GPS či jiných zdrojů polohy."</string>
+    <string name="permlab_accessFineLocation">"upřesnění polohy (GPS)"</string>
+    <string name="permdesc_accessFineLocation">"Umožňuje aplikaci přístup ke zdrojům přesné polohy v telefonu, jako je například systém GPS, je-li k dispozici. Škodlivé aplikace mohou pomocí tohoto nastavení zjistit vaši polohu a mohou zvýšit spotřebu baterie."</string>
+    <string name="permlab_accessCoarseLocation">"přibližná poloha (pomocí sítě)"</string>
+    <string name="permdesc_accessCoarseLocation">"Umožňuje získat přístup ke zdrojům přibližné polohy, jako je například databáze mobilní sítě, je-li k dispozici, a určit přibližnou pozici telefonu. Škodlivé aplikace takto mohou zjistit, kde se přibližně nacházíte."</string>
+    <string name="permlab_accessSurfaceFlinger">"přístup ke službě SurfaceFlinger"</string>
+    <string name="permdesc_accessSurfaceFlinger">"Umožňuje aplikaci používat nízkoúrovňové funkce SurfaceFlinger."</string>
+    <string name="permlab_readFrameBuffer">"čtení vyrovnávací paměti snímků"</string>
+    <string name="permdesc_readFrameBuffer">"Umožňuje aplikaci načíst obsah vyrovnávací paměti snímků."</string>
+    <string name="permlab_modifyAudioSettings">"změna vašeho nastavení zvuku"</string>
+    <string name="permdesc_modifyAudioSettings">"Umožňuje aplikaci změnit globální nastavení zvuku, například hlasitost či směrování."</string>
+    <string name="permlab_recordAudio">"nahrání zvuku"</string>
+    <string name="permdesc_recordAudio">"Umožňuje aplikaci získat přístup k nahrávání zvuku."</string>
+    <string name="permlab_camera">"pořizování fotografií"</string>
+    <string name="permdesc_camera">"Umožňuje aplikaci pořizovat fotografie pomocí fotoaparátu. Toto nastavení aplikaci umožní shromažďovat fotografie toho, na co je zrovna fotoaparát namířen."</string>
+    <string name="permlab_brick">"trvalé vypnutí telefonu"</string>
+    <string name="permdesc_brick">"Umožňuje aplikaci trvale vypnout celý telefon. Toto je velmi nebezpečné nastavení."</string>
+    <string name="permlab_reboot">"vynucení restartování telefonu"</string>
+    <string name="permdesc_reboot">"Umožňuje aplikaci vynutit restartování telefonu."</string>
+    <string name="permlab_mount_unmount_filesystems">"připojení a odpojení souborových systémů"</string>
+    <string name="permdesc_mount_unmount_filesystems">"Umožňuje aplikaci připojit či odpojit souborové systémy ve vyměnitelných úložištích."</string>
+    <string name="permlab_mount_format_filesystems">"formátovat externí úložiště"</string>
+    <string name="permdesc_mount_format_filesystems">"Umožňuje aplikaci formátovat vyměnitelná úložiště."</string>
+    <string name="permlab_vibrate">"ovládání vibrací"</string>
+    <string name="permdesc_vibrate">"Umožňuje aplikaci ovládat vibrace."</string>
+    <string name="permlab_flashlight">"ovládání kontrolky"</string>
+    <string name="permdesc_flashlight">"Umožňuje aplikaci ovládat kontrolku."</string>
+    <string name="permlab_hardware_test">"testování hardwaru"</string>
+    <string name="permdesc_hardware_test">"Umožňuje aplikaci ovládat různé periferie pro účely testování hardwaru."</string>
+    <string name="permlab_callPhone">"přímé volání na telefonní čísla"</string>
+    <string name="permdesc_callPhone">"Umožňuje aplikaci bez vašeho zásahu volat na telefonní čísla. Škodlivé aplikace mohou na váš telefonní účet připsat neočekávané hovory. Toto nastavení aplikaci neumožňuje volat na tísňové linky."</string>
+    <string name="permlab_callPrivileged">"přímé volání na libovolná telefonní čísla"</string>
+    <string name="permdesc_callPrivileged">"Umožňuje aplikaci bez vašeho zásahu vytočit jakékoli telefonní číslo, včetně čísel tísňového volání. Škodlivé aplikace mohou provádět zbytečná a nezákonná volání na tísňové linky."</string>
+    <string name="permlab_locationUpdates">"ovládání oznámení o aktualizaci polohy"</string>
+    <string name="permdesc_locationUpdates">"Umožňuje povolit či zakázat aktualizace polohy prostřednictvím bezdrátového připojení. Aplikace toto nastavení obvykle nepoužívají."</string>
+    <string name="permlab_checkinProperties">"přístup k vlastnostem Checkin"</string>
+    <string name="permdesc_checkinProperties">"Umožňuje čtení i zápis vlastností nahraných službou Checkin. Běžné aplikace toto nastavení obvykle nevyužívají."</string>
+    <string name="permlab_bindGadget">"zvolit gadgety"</string>
+    <string name="permdesc_bindGadget">"Umožňuje aplikaci sdělit systému, které aplikace mohou použít které gadgety. Aplikace s tímto oprávněním mohou zpřístupnit osobní údaje jiným aplikacím. Není určeno pro normální aplikace."</string>
+    <string name="permlab_modifyPhoneState">"změny stavu telefonu"</string>
+    <string name="permdesc_modifyPhoneState">"Umožňuje aplikaci ovládat telefonní funkce zařízení. Aplikace s tímto oprávněním může přepínat sítě nebo zapnout či vypnout bezdrátové připojení telefonu bez vašeho svolení."</string>
+    <string name="permlab_readPhoneState">"zjistit stav telefonu"</string>
+    <string name="permdesc_readPhoneState">"Umožňuje aplikaci získat přístup k telefonním funkcím zařízení. Aplikace s tímto oprávněním mohou určit telefonní číslo tohoto telefonu, zda zrovna probíhá hovor, spojené telefonní číslo a podobně."</string>
+    <string name="permlab_wakeLock">"zabránění přechodu telefonu do režimu spánku"</string>
+    <string name="permdesc_wakeLock">"Umožňuje aplikaci zabránit přechodu telefonu do režimu spánku."</string>
+    <string name="permlab_devicePower">"zapnutí či vypnutí telefonu"</string>
+    <string name="permdesc_devicePower">"Umožňuje aplikaci zapnout či vypnout telefon."</string>
+    <string name="permlab_factoryTest">"spuštění v režimu továrního testu"</string>
+    <string name="permdesc_factoryTest">"Umožňuje aplikaci spuštění v režimu nízkoúrovňového testu výrobce a povolí přístup k hardwaru telefonu. K dispozici pouze, je-li telefon spuštěn v režimu testování výrobce."</string>
+    <string name="permlab_setWallpaper">"nastavení tapety"</string>
+    <string name="permdesc_setWallpaper">"Umožňuje aplikaci nastavit tapetu systému."</string>
+    <string name="permlab_setWallpaperHints">"nastavení nápovědy pro velikost tapety"</string>
+    <string name="permdesc_setWallpaperHints">"Umožňuje aplikaci nastavit nápovědu pro velikost tapety systému."</string>
+    <string name="permlab_masterClear">"obnovení továrního nastavení systému"</string>
+    <string name="permdesc_masterClear">"Umožňuje aplikaci kompletně obnovit systém do továrního nastavení a vymazat všechna data, konfiguraci a nainstalované aplikace."</string>
+    <string name="permlab_setTimeZone">"nastavení časového pásma"</string>
+    <string name="permdesc_setTimeZone">"Umožňuje aplikaci změnit časové pásmo telefonu."</string>
+    <string name="permlab_getAccounts">"odhalení známých účtů"</string>
+    <string name="permdesc_getAccounts">"Umožňuje aplikaci získat seznam účtů v telefonu."</string>
+    <string name="permlab_accessNetworkState">"zobrazení stavu sítě"</string>
+    <string name="permdesc_accessNetworkState">"Umožňuje aplikaci zobrazit stav všech sítí."</string>
+    <string name="permlab_createNetworkSockets">"plný přístup k Internetu"</string>
+    <string name="permdesc_createNetworkSockets">"Umožňuje aplikaci vytvořit síťové sokety."</string>
+    <string name="permlab_writeApnSettings">"zápis nastavení pro název přístupového bodu (APN)"</string>
+    <string name="permdesc_writeApnSettings">"Umožňuje aplikaci změnit nastavení APN, jako je například proxy či port APN."</string>
+    <string name="permlab_changeNetworkState">"změna připojení k síti"</string>
+    <string name="permdesc_changeNetworkState">"Umožňuje aplikaci změnit stav připojení k síti."</string>
+    <string name="permlab_changeBackgroundDataSetting">"změnit nastavení použití dat na pozadí"</string>
+    <string name="permdesc_changeBackgroundDataSetting">"Umožňuje aplikaci změnit nastavení použití dat na pozadí."</string>
+    <string name="permlab_accessWifiState">"zobrazení stavu WiFi"</string>
+    <string name="permdesc_accessWifiState">"Umožňuje aplikaci zobrazit informace o stavu připojení WiFi."</string>
+    <string name="permlab_changeWifiState">"Změnit stav WiFi"</string>
+    <string name="permdesc_changeWifiState">"Umožňuje aplikaci připojit se k přístupovým bodům WiFi či se od nich odpojit a provádět změny nakonfigurovaných sítí WiFi."</string>
+    <string name="permlab_bluetoothAdmin">"správa rozhraní Bluetooth"</string>
+    <string name="permdesc_bluetoothAdmin">"Umožňuje aplikaci konfigurovat místní telefon s rozhraním Bluetooth a vyhledávat a párovat vzdálená zařízení."</string>
+    <string name="permlab_bluetooth">"vytvoření připojení Bluetooth"</string>
+    <string name="permdesc_bluetooth">"Umožňuje aplikaci zobrazit konfiguraci místního telefonu s rozhraním Bluetooth, vytvářet připojení ke spárovaným zařízením a přijímat tato připojení."</string>
+    <string name="permlab_disableKeyguard">"vypnutí zámku kláves"</string>
+    <string name="permdesc_disableKeyguard">"Umožňuje aplikaci vypnout zámek kláves a související zabezpečení heslem. Příkladem oprávněného použití této funkce je vypnutí zámku klávesnice při příchozím hovoru a jeho opětovné zapnutí po skončení hovoru."</string>
+    <string name="permlab_readSyncSettings">"čtení nastavení synchronizace"</string>
+    <string name="permdesc_readSyncSettings">"Umožňuje aplikaci načíst nastavení synchronizace, např. zda má být povolena synchronizace kontaktů."</string>
+    <string name="permlab_writeSyncSettings">"zápis nastavení synchronizace"</string>
+    <string name="permdesc_writeSyncSettings">"Umožňuje aplikaci změnit nastavení synchronizace, např. zda má být povolena synchronizace kontaktů."</string>
+    <string name="permlab_readSyncStats">"čtení statistických údajů o synchronizaci"</string>
+    <string name="permdesc_readSyncStats">"Umožňuje aplikaci číst statistické informace o synchronizaci, např. historii proběhlých synchronizací."</string>
+    <string name="permlab_subscribedFeedsRead">"čtení zdrojů přihlášených k odběru"</string>
+    <string name="permdesc_subscribedFeedsRead">"Umožňuje aplikaci získat podrobnosti o aktuálně synchronizovaných zdrojích."</string>
+    <string name="permlab_subscribedFeedsWrite">"zápis odebíraných zdrojů"</string>
+    <string name="permdesc_subscribedFeedsWrite">"Umožňuje aplikaci upravit vaše aktuálně synchronizované zdroje. To může škodlivým aplikacím umožnit změnu vašich synchronizovaných zdrojů."</string>
+    <string name="permlab_readDictionary">"číst slovník definovaný uživatelem"</string>
+    <string name="permdesc_readDictionary">"Umožní aplikaci číst soukromá slova, jména a fráze, která uživatel mohl uložit do svého slovníku."</string>
+    <string name="permlab_writeDictionary">"zapisovat do slovníku definovaného uživatelem"</string>
+    <string name="permdesc_writeDictionary">"Umožní aplikaci zapisovat nová slova do uživatelského slovníku."</string>
+  <string-array name="phoneTypes">
+    <item>"Domů"</item>
+    <item>"Mobil"</item>
+    <item>"Práce"</item>
+    <item>"Pracovní fax"</item>
+    <item>"Fax domů"</item>
+    <item>"Pager"</item>
+    <item>"Ostatní"</item>
+    <item>"Vlastní"</item>
+  </string-array>
+  <string-array name="emailAddressTypes">
+    <item>"Domů"</item>
+    <item>"Práce"</item>
+    <item>"Ostatní"</item>
+    <item>"Vlastní"</item>
+  </string-array>
+  <string-array name="postalAddressTypes">
+    <item>"Domů"</item>
+    <item>"Práce"</item>
+    <item>"Ostatní"</item>
+    <item>"Vlastní"</item>
+  </string-array>
+  <string-array name="imAddressTypes">
+    <item>"Domů"</item>
+    <item>"Práce"</item>
+    <item>"Ostatní"</item>
+    <item>"Vlastní"</item>
+  </string-array>
+  <string-array name="organizationTypes">
+    <item>"Práce"</item>
+    <item>"Ostatní"</item>
+    <item>"Vlastní"</item>
+  </string-array>
+  <string-array name="imProtocols">
+    <item>"AIM"</item>
+    <item>"Windows Live"</item>
+    <item>"Yahoo!"</item>
+    <item>"Skype"</item>
+    <item>"QQ"</item>
+    <item>"Google Talk"</item>
+    <item>"ICQ"</item>
+    <item>"Jabber"</item>
+  </string-array>
+    <string name="keyguard_password_enter_pin_code">"Zadejte kód PIN"</string>
+    <string name="keyguard_password_wrong_pin_code">"Nesprávný kód PIN"</string>
+    <string name="keyguard_label_text">"Chcete-li telefon odemknout, stiskněte Menu a poté 0."</string>
+    <string name="emergency_call_dialog_number_for_display">"Číslo tísňové linky"</string>
+    <string name="lockscreen_carrier_default">"(Není signál)"</string>
+    <string name="lockscreen_screen_locked">"Obrazovka uzamčena."</string>
+    <string name="lockscreen_instructions_when_pattern_enabled">"Chcete-li odemknout telefon nebo provést tísňové volání, stiskněte Menu."</string>
+    <string name="lockscreen_instructions_when_pattern_disabled">"Telefon odemknete stisknutím tlačítka Menu."</string>
+    <string name="lockscreen_pattern_instructions">"Odblokujte pomocí gesta"</string>
+    <string name="lockscreen_emergency_call">"Tísňové volání"</string>
+    <string name="lockscreen_pattern_correct">"Správně!"</string>
+    <string name="lockscreen_pattern_wrong">"Zkuste to prosím znovu"</string>
+    <!-- no translation found for lockscreen_plugged_in (613343852842944435) -->
+    <skip />
+    <string name="lockscreen_low_battery">"Připojte dobíjecí zařízení."</string>
+    <string name="lockscreen_missing_sim_message_short">"Není vložena SIM karta."</string>
+    <string name="lockscreen_missing_sim_message">"V telefonu není žádná karta SIM."</string>
+    <string name="lockscreen_missing_sim_instructions">"Prosím vložte kartu SIM."</string>
+    <string name="lockscreen_network_locked_message">"Síť je blokována"</string>
+    <string name="lockscreen_sim_puk_locked_message">"Karta SIM je zablokována pomocí kódu PUK."</string>
+    <string name="lockscreen_sim_puk_locked_instructions">"Prosím kontaktujte podporu zákazníků."</string>
+    <string name="lockscreen_sim_locked_message">"Karta SIM je zablokována."</string>
+    <string name="lockscreen_sim_unlock_progress_dialog_message">"Odblokování karty SIM..."</string>
+    <string name="lockscreen_too_many_failed_attempts_dialog_message">"<xliff:g id="NUMBER_0">%d</xliff:g>krát jste nakreslili nesprávné bezpečnostní gesto. "\n\n"Opakujte prosím akci za <xliff:g id="NUMBER_1">%d</xliff:g> s."</string>
+    <string name="lockscreen_failed_attempts_almost_glogin">"<xliff:g id="NUMBER_0">%d</xliff:g>krát jste nesprávně nakreslili své bezpečnostní gesto. Po dalších neúspěšných pokusech (<xliff:g id="NUMBER_1">%d</xliff:g>) budete požádáni o odemčení telefonu pomocí přihlášení do účtu Google."\n\n" Akci prosím opakujte za několik sekund (<xliff:g id="NUMBER_2">%d</xliff:g>)."</string>
+    <string name="lockscreen_too_many_failed_attempts_countdown">"Sekundy zbývající do dalšího pokusu: <xliff:g id="NUMBER">%d</xliff:g>."</string>
+    <string name="lockscreen_forgot_pattern_button_text">"Zapomněli jste gesto?"</string>
+    <string name="lockscreen_glogin_too_many_attempts">"Gesta: Příliš mnoho pokusů"</string>
+    <string name="lockscreen_glogin_instructions">"Chcete-li telefon odemknout,"\n"přihlaste se pomocí svého účtu Google"</string>
+    <string name="lockscreen_glogin_username_hint">"Uživatelské jméno (e-mail)"</string>
+    <string name="lockscreen_glogin_password_hint">"Heslo"</string>
+    <string name="lockscreen_glogin_submit_button">"Přihlásit se"</string>
+    <string name="lockscreen_glogin_invalid_input">"Neplatné uživatelské jméno nebo heslo."</string>
+    <string name="status_bar_time_format">"<xliff:g id="HOUR">h</xliff:g>:<xliff:g id="MINUTE">mm</xliff:g> <xliff:g id="AMPM">AA</xliff:g>"</string>
+    <string name="hour_minute_ampm">"<xliff:g id="HOUR">%-l</xliff:g>:<xliff:g id="MINUTE">%M</xliff:g><xliff:g id="AMPM">%P</xliff:g>"</string>
+    <string name="hour_minute_cap_ampm">"<xliff:g id="HOUR">%-l</xliff:g>:<xliff:g id="MINUTE">%M</xliff:g><xliff:g id="AMPM">%p</xliff:g>"</string>
+    <!-- no translation found for hour_ampm (4329881288269772723) -->
+    <skip />
+    <!-- no translation found for hour_cap_ampm (1829009197680861107) -->
+    <skip />
+    <string name="status_bar_clear_all_button">"Vymazat oznámení"</string>
+    <string name="status_bar_no_notifications_title">"Žádná oznámení"</string>
+    <string name="status_bar_ongoing_events_title">"Probíhající"</string>
+    <string name="status_bar_latest_events_title">"Oznámení"</string>
+    <!-- no translation found for battery_status_text_percent_format (7660311274698797147) -->
+    <skip />
+    <string name="battery_status_charging">"Nabíjení..."</string>
+    <string name="battery_low_title">"Prosím připojte dobíjecí zařízení"</string>
+    <string name="battery_low_subtitle">"Baterie je vybitá:"</string>
+    <string name="battery_low_percent_format">"zbývá méně než <xliff:g id="NUMBER">%d%%</xliff:g>."</string>
+    <string name="factorytest_failed">"Test továrního nastavení se nezdařil"</string>
+    <string name="factorytest_not_system">"Test FACTORY_TEST lze provést pouze u balíčků nainstalovaných ve složce /system/app."</string>
+    <string name="factorytest_no_action">"Nebyl nalezen žádný balíček umožňující test FACTORY_TEST."</string>
+    <string name="factorytest_reboot">"Restartovat"</string>
+    <string name="js_dialog_title">"Stránka <xliff:g id="TITLE">%s</xliff:g> uvádí:"</string>
+    <string name="js_dialog_title_default">"JavaScript"</string>
+    <string name="js_dialog_before_unload">"Chcete opustit tuto stránku?"\n\n"<xliff:g id="MESSAGE">%s</xliff:g>"\n\n"Vyberte OK, chcete-li pokračovat, nebo Zrušit, chcete-li na stránce zůstat."</string>
+    <string name="save_password_label">"Potvrdit"</string>
+    <string name="save_password_message">"Chcete, aby si prohlížeč zapamatoval toto heslo?"</string>
+    <string name="save_password_notnow">"Nyní ne"</string>
+    <string name="save_password_remember">"Zapamatovat"</string>
+    <string name="save_password_never">"Nikdy"</string>
+    <string name="open_permission_deny">"Nemáte povolení otevřít tuto stránku."</string>
+    <string name="text_copied">"Text byl zkopírován do schránky."</string>
+    <string name="more_item_label">"Více"</string>
+    <string name="prepend_shortcut_label">"Menu+"</string>
+    <string name="menu_space_shortcut_label">"mezerník"</string>
+    <string name="menu_enter_shortcut_label">"enter"</string>
+    <string name="menu_delete_shortcut_label">"smazat"</string>
+    <string name="search_go">"Hledat"</string>
+    <string name="today">"Dnes"</string>
+    <string name="yesterday">"Včera"</string>
+    <string name="tomorrow">"Zítra"</string>
+    <string name="oneMonthDurationPast">"před 1 měsícem"</string>
+    <string name="beforeOneMonthDurationPast">"Déle než před 1 měsícem"</string>
+  <plurals name="num_seconds_ago">
+    <item quantity="one">"před 1 sekundou"</item>
+    <item quantity="other">"před <xliff:g id="COUNT">%d</xliff:g> sek."</item>
+  </plurals>
+  <plurals name="num_minutes_ago">
+    <item quantity="one">"před 1 minutou"</item>
+    <item quantity="other">"před <xliff:g id="COUNT">%d</xliff:g> min."</item>
+  </plurals>
+  <plurals name="num_hours_ago">
+    <item quantity="one">"před 1 hodinou"</item>
+    <item quantity="other">"před <xliff:g id="COUNT">%d</xliff:g> hod."</item>
+  </plurals>
+  <plurals name="num_days_ago">
+    <item quantity="one">"včera"</item>
+    <item quantity="other">"před <xliff:g id="COUNT">%d</xliff:g> dny"</item>
+  </plurals>
+  <plurals name="in_num_seconds">
+    <item quantity="one">"za 1 sekundu"</item>
+    <item quantity="other">"za <xliff:g id="COUNT">%d</xliff:g> s"</item>
+  </plurals>
+  <plurals name="in_num_minutes">
+    <item quantity="one">"za 1 minutu"</item>
+    <item quantity="other">"za <xliff:g id="COUNT">%d</xliff:g> min."</item>
+  </plurals>
+  <plurals name="in_num_hours">
+    <item quantity="one">"za 1 hodinu"</item>
+    <item quantity="other">"za <xliff:g id="COUNT">%d</xliff:g> hod."</item>
+  </plurals>
+  <plurals name="in_num_days">
+    <item quantity="one">"zítra"</item>
+    <item quantity="other">"zbývající počet dní: <xliff:g id="COUNT">%d</xliff:g>"</item>
+  </plurals>
+  <plurals name="abbrev_num_seconds_ago">
+    <item quantity="one">"před 1 s"</item>
+    <item quantity="other">"před <xliff:g id="COUNT">%d</xliff:g> s"</item>
+  </plurals>
+  <plurals name="abbrev_num_minutes_ago">
+    <item quantity="one">"před 1 min."</item>
+    <item quantity="other">"před <xliff:g id="COUNT">%d</xliff:g> min."</item>
+  </plurals>
+  <plurals name="abbrev_num_hours_ago">
+    <item quantity="one">"před 1 hodinou"</item>
+    <item quantity="other">"před <xliff:g id="COUNT">%d</xliff:g> hod."</item>
+  </plurals>
+  <plurals name="abbrev_num_days_ago">
+    <item quantity="one">"včera"</item>
+    <item quantity="other">"před <xliff:g id="COUNT">%d</xliff:g> dny"</item>
+  </plurals>
+  <plurals name="abbrev_in_num_seconds">
+    <item quantity="one">"za 1 s"</item>
+    <item quantity="other">"za <xliff:g id="COUNT">%d</xliff:g> s"</item>
+  </plurals>
+  <plurals name="abbrev_in_num_minutes">
+    <item quantity="one">"za 1 min."</item>
+    <item quantity="other">"za <xliff:g id="COUNT">%d</xliff:g> min."</item>
+  </plurals>
+  <plurals name="abbrev_in_num_hours">
+    <item quantity="one">"za 1 hodinu"</item>
+    <item quantity="other">"za <xliff:g id="COUNT">%d</xliff:g> hod."</item>
+  </plurals>
+  <plurals name="abbrev_in_num_days">
+    <item quantity="one">"zítra"</item>
+    <item quantity="other">"zbývající počet dní: <xliff:g id="COUNT">%d</xliff:g>"</item>
+  </plurals>
+    <string name="preposition_for_date">"%s"</string>
+    <string name="preposition_for_time">"%s"</string>
+    <string name="preposition_for_year">"v roce %s"</string>
+    <string name="day">"den"</string>
+    <string name="days">"d."</string>
+    <string name="hour">"hodina"</string>
+    <string name="hours">"hod."</string>
+    <string name="minute">"min."</string>
+    <string name="minutes">"min."</string>
+    <string name="second">"s"</string>
+    <string name="seconds">"s"</string>
+    <string name="week">"týden"</string>
+    <string name="weeks">"týd."</string>
+    <string name="year">"rokem"</string>
+    <string name="years">"lety"</string>
+    <string name="sunday">"neděle"</string>
+    <string name="monday">"pondělí"</string>
+    <string name="tuesday">"úterý"</string>
+    <string name="wednesday">"středa"</string>
+    <string name="thursday">"čtvrtek"</string>
+    <string name="friday">"pátek"</string>
+    <string name="saturday">"sobota"</string>
+    <string name="every_weekday">"Každý pracovní den (Po – Pá)"</string>
+    <string name="daily">"Denně"</string>
+    <string name="weekly">"Každý týden v <xliff:g id="DAY">%s</xliff:g>"</string>
+    <string name="monthly">"Měsíčně"</string>
+    <string name="yearly">"Ročně"</string>
+    <string name="VideoView_error_title">"Video nelze přehrát"</string>
+    <string name="VideoView_error_text_unknown">"Toto video bohužel nelze přehrát."</string>
+    <string name="VideoView_error_button">"OK"</string>
+    <string name="am">"dop."</string>
+    <string name="pm">"odp."</string>
+    <string name="numeric_date">"<xliff:g id="MONTH">%m</xliff:g>/<xliff:g id="DAY">%d</xliff:g>/<xliff:g id="YEAR">%Y</xliff:g>"</string>
+    <string name="wday1_date1_time1_wday2_date2_time2">"<xliff:g id="WEEKDAY1">%1$s</xliff:g>, <xliff:g id="DATE1">%2$s</xliff:g>, <xliff:g id="TIME1">%3$s</xliff:g> – <xliff:g id="WEEKDAY2">%4$s</xliff:g>, <xliff:g id="DATE2">%5$s</xliff:g>, <xliff:g id="TIME2">%6$s</xliff:g>"</string>
+    <string name="wday1_date1_wday2_date2">"<xliff:g id="WEEKDAY1">%1$s</xliff:g>, <xliff:g id="DATE1">%2$s</xliff:g> – <xliff:g id="WEEKDAY2">%4$s</xliff:g>, <xliff:g id="DATE2">%5$s</xliff:g>"</string>
+    <string name="date1_time1_date2_time2">"<xliff:g id="DATE1">%2$s</xliff:g>, <xliff:g id="TIME1">%3$s</xliff:g> – <xliff:g id="DATE2">%5$s</xliff:g>, <xliff:g id="TIME2">%6$s</xliff:g>"</string>
+    <string name="date1_date2">"<xliff:g id="DATE1">%2$s</xliff:g> – <xliff:g id="DATE2">%5$s</xliff:g>"</string>
+    <string name="time1_time2">"<xliff:g id="TIME1">%1$s</xliff:g> – <xliff:g id="TIME2">%2$s</xliff:g>"</string>
+    <string name="time_wday_date">"<xliff:g id="TIME_RANGE">%1$s</xliff:g>, <xliff:g id="WEEKDAY">%2$s</xliff:g>, <xliff:g id="DATE">%3$s</xliff:g>"</string>
+    <string name="wday_date">"<xliff:g id="WEEKDAY">%2$s</xliff:g>, <xliff:g id="DATE">%3$s</xliff:g>"</string>
+    <string name="time_date">"<xliff:g id="TIME_RANGE">%1$s</xliff:g>, <xliff:g id="DATE">%3$s</xliff:g>"</string>
+    <string name="date_time">"<xliff:g id="DATE">%1$s</xliff:g>, <xliff:g id="TIME">%2$s</xliff:g>"</string>
+    <string name="relative_time">"<xliff:g id="DATE">%1$s</xliff:g>, <xliff:g id="TIME">%2$s</xliff:g>"</string>
+    <string name="time_wday">"<xliff:g id="TIME_RANGE">%1$s</xliff:g>, <xliff:g id="WEEKDAY">%2$s</xliff:g>"</string>
+    <string name="full_date_month_first" format="date">"<xliff:g id="MONTH">MMMM</xliff:g>' '<xliff:g id="DAY">d</xliff:g>', '<xliff:g id="YEAR">yyyy</xliff:g>"</string>
+    <string name="full_date_day_first" format="date">"<xliff:g id="DAY">d</xliff:g>'. '<xliff:g id="MONTH">MMMM</xliff:g>' '<xliff:g id="YEAR">yyyy</xliff:g>"</string>
+    <string name="medium_date_month_first" format="date">"<xliff:g id="DAY">d</xliff:g>'. '<xliff:g id="MONTH">MMM</xliff:g>' '<xliff:g id="YEAR">yyyy</xliff:g>"</string>
+    <string name="medium_date_day_first" format="date">"<xliff:g id="DAY">d</xliff:g>'. '<xliff:g id="MONTH">MMM</xliff:g>' '<xliff:g id="YEAR">yyyy</xliff:g>"</string>
+    <string name="twelve_hour_time_format" format="date">"<xliff:g id="HOUR">h</xliff:g>':'<xliff:g id="MINUTE">mm</xliff:g>' '<xliff:g id="AMPM">a</xliff:g>"</string>
+    <string name="twenty_four_hour_time_format" format="date">"<xliff:g id="HOUR">H</xliff:g>':'<xliff:g id="MINUTE">mm</xliff:g>"</string>
+    <string name="noon">"poledne"</string>
+    <string name="Noon">"Poledne"</string>
+    <string name="midnight">"půlnoc"</string>
+    <string name="Midnight">"Půlnoc"</string>
+    <!-- no translation found for month_day (3693060561170538204) -->
+    <skip />
+    <!-- no translation found for month (1976700695144952053) -->
+    <skip />
+    <string name="month_day_year">"<xliff:g id="MONTH">%B</xliff:g> <xliff:g id="DAY">%-d</xliff:g>, <xliff:g id="YEAR">%Y</xliff:g>"</string>
+    <!-- no translation found for month_year (2106203387378728384) -->
+    <skip />
+    <string name="time_of_day">"<xliff:g id="HOUR">%H</xliff:g>:<xliff:g id="MINUTE">%M</xliff:g>:<xliff:g id="SECOND">%S</xliff:g>"</string>
+    <string name="date_and_time">"<xliff:g id="HOUR">%H</xliff:g>:<xliff:g id="MINUTE">%M</xliff:g>:<xliff:g id="SECOND">%S</xliff:g> <xliff:g id="DAY">%-d</xliff:g>. <xliff:g id="MONTH">%B</xliff:g> <xliff:g id="YEAR">%Y</xliff:g>"</string>
+    <string name="same_year_md1_md2">"<xliff:g id="DAY1">%3$s</xliff:g>. <xliff:g id="MONTH1">%2$s</xliff:g> – <xliff:g id="DAY2">%8$s</xliff:g>. <xliff:g id="MONTH2">%7$s</xliff:g>"</string>
+    <string name="same_year_wday1_md1_wday2_md2">"<xliff:g id="WEEKDAY1">%1$s</xliff:g>, <xliff:g id="DAY1_0">%3$s</xliff:g>. <xliff:g id="MONTH1">%2$s</xliff:g> – <xliff:g id="WEEKDAY2">%6$s</xliff:g>, <xliff:g id="DAY2_1">%8$s</xliff:g>. <xliff:g id="MONTH2">%7$s</xliff:g>"</string>
+    <string name="same_year_mdy1_mdy2">"<xliff:g id="DAY1">%3$s</xliff:g>. <xliff:g id="MONTH1">%2$s</xliff:g> – <xliff:g id="DAY2">%8$s</xliff:g>. <xliff:g id="MONTH2">%7$s</xliff:g> <xliff:g id="YEAR">%9$s</xliff:g>"</string>
+    <string name="same_year_wday1_mdy1_wday2_mdy2">"<xliff:g id="WEEKDAY1">%1$s</xliff:g>, <xliff:g id="DAY1_0">%3$s</xliff:g>. <xliff:g id="MONTH1">%2$s</xliff:g> – <xliff:g id="WEEKDAY2">%6$s</xliff:g>, <xliff:g id="DAY2_1">%8$s</xliff:g>. <xliff:g id="MONTH2">%7$s</xliff:g> <xliff:g id="YEAR">%9$s</xliff:g>"</string>
+    <string name="same_year_md1_time1_md2_time2">"<xliff:g id="DAY1">%3$s</xliff:g>. <xliff:g id="MONTH1">%2$s</xliff:g>, <xliff:g id="TIME1">%5$s</xliff:g> – <xliff:g id="DAY2">%8$s</xliff:g>. <xliff:g id="MONTH2">%7$s</xliff:g>, <xliff:g id="TIME2">%10$s</xliff:g>"</string>
+    <string name="same_year_wday1_md1_time1_wday2_md2_time2">"<xliff:g id="WEEKDAY1">%1$s</xliff:g>, <xliff:g id="DAY1_0">%3$s</xliff:g>. <xliff:g id="MONTH1">%2$s</xliff:g>, <xliff:g id="TIME1">%5$s</xliff:g> – <xliff:g id="WEEKDAY2">%6$s</xliff:g>, <xliff:g id="DAY2_1">%8$s</xliff:g>. <xliff:g id="MONTH2">%7$s</xliff:g>, <xliff:g id="TIME2">%10$s</xliff:g>"</string>
+    <string name="same_year_mdy1_time1_mdy2_time2">"<xliff:g id="DAY1">%3$s</xliff:g>. <xliff:g id="MONTH1">%2$s</xliff:g> <xliff:g id="YEAR1">%4$s</xliff:g>, <xliff:g id="TIME1">%5$s</xliff:g> – <xliff:g id="DAY2">%8$s</xliff:g>. <xliff:g id="MONTH2">%7$s</xliff:g> <xliff:g id="YEAR2">%9$s</xliff:g>, <xliff:g id="TIME2">%10$s</xliff:g>"</string>
+    <string name="same_year_wday1_mdy1_time1_wday2_mdy2_time2">"<xliff:g id="WEEKDAY1">%1$s</xliff:g>, <xliff:g id="DAY1_0">%3$s</xliff:g>. <xliff:g id="MONTH1">%2$s</xliff:g> <xliff:g id="YEAR1">%4$s</xliff:g>, <xliff:g id="TIME1">%5$s</xliff:g> – <xliff:g id="WEEKDAY2">%6$s</xliff:g>, <xliff:g id="DAY2_1">%8$s</xliff:g>. <xliff:g id="MONTH2">%7$s</xliff:g> <xliff:g id="YEAR2">%9$s</xliff:g>, <xliff:g id="TIME2">%10$s</xliff:g>"</string>
+    <string name="numeric_md1_md2">"<xliff:g id="DAY1">%3$s</xliff:g>. <xliff:g id="MONTH1">%2$s</xliff:g>. – <xliff:g id="DAY2">%8$s</xliff:g>. <xliff:g id="MONTH2">%7$s</xliff:g>."</string>
+    <string name="numeric_wday1_md1_wday2_md2">"<xliff:g id="WEEKDAY1">%1$s</xliff:g>, <xliff:g id="DAY1_0">%3$s</xliff:g>. <xliff:g id="MONTH1">%2$s</xliff:g> – <xliff:g id="WEEKDAY2">%6$s</xliff:g>, <xliff:g id="DAY2_1">%8$s</xliff:g>. <xliff:g id="MONTH2">%7$s</xliff:g>"</string>
+    <string name="numeric_mdy1_mdy2">"<xliff:g id="DAY1">%3$s</xliff:g>. <xliff:g id="MONTH1">%2$s</xliff:g>. <xliff:g id="YEAR1">%4$s</xliff:g> – <xliff:g id="DAY2">%8$s</xliff:g>. <xliff:g id="MONTH2">%7$s</xliff:g>. <xliff:g id="YEAR2">%9$s</xliff:g>"</string>
+    <string name="numeric_wday1_mdy1_wday2_mdy2">"<xliff:g id="WEEKDAY1">%1$s</xliff:g>, <xliff:g id="DAY1_0">%3$s</xliff:g>. <xliff:g id="MONTH1">%2$s</xliff:g> <xliff:g id="YEAR1">%4$s</xliff:g> – <xliff:g id="WEEKDAY2">%6$s</xliff:g>, <xliff:g id="DAY2_1">%8$s</xliff:g>. <xliff:g id="MONTH2">%7$s</xliff:g> <xliff:g id="YEAR2">%9$s</xliff:g>"</string>
+    <string name="numeric_md1_time1_md2_time2">"<xliff:g id="DAY1">%3$s</xliff:g>. <xliff:g id="MONTH1">%2$s</xliff:g>., <xliff:g id="TIME1">%5$s</xliff:g> – <xliff:g id="DAY2">%8$s</xliff:g>. <xliff:g id="MONTH2">%7$s</xliff:g>., <xliff:g id="TIME2">%10$s</xliff:g>"</string>
+    <string name="numeric_wday1_md1_time1_wday2_md2_time2">"<xliff:g id="WEEKDAY1">%1$s</xliff:g>, <xliff:g id="DAY1_0">%3$s</xliff:g>. <xliff:g id="MONTH1">%2$s</xliff:g>, <xliff:g id="TIME1">%5$s</xliff:g> – <xliff:g id="WEEKDAY2">%6$s</xliff:g>, <xliff:g id="DAY2_1">%8$s</xliff:g>. <xliff:g id="MONTH2">%7$s</xliff:g>, <xliff:g id="TIME2">%10$s</xliff:g>"</string>
+    <string name="numeric_mdy1_time1_mdy2_time2">"<xliff:g id="MONTH1">%2$s</xliff:g>/<xliff:g id="DAY1">%3$s</xliff:g>/<xliff:g id="YEAR1">%4$s</xliff:g>, <xliff:g id="TIME1">%5$s</xliff:g> – <xliff:g id="MONTH2">%7$s</xliff:g>/<xliff:g id="DAY2">%8$s</xliff:g>/<xliff:g id="YEAR2">%9$s</xliff:g>, <xliff:g id="TIME2">%10$s</xliff:g>"</string>
+    <string name="numeric_wday1_mdy1_time1_wday2_mdy2_time2">"<xliff:g id="WEEKDAY1">%1$s</xliff:g>, <xliff:g id="DAY1_0">%3$s</xliff:g>. <xliff:g id="MONTH1">%2$s</xliff:g> <xliff:g id="YEAR1">%4$s</xliff:g>, <xliff:g id="TIME1">%5$s</xliff:g> – <xliff:g id="WEEKDAY2">%6$s</xliff:g>, <xliff:g id="DAY2_1">%8$s</xliff:g>. <xliff:g id="MONTH2">%7$s</xliff:g> <xliff:g id="YEAR2">%9$s</xliff:g>, <xliff:g id="TIME2">%10$s</xliff:g>"</string>
+    <string name="same_month_md1_md2">"<xliff:g id="DAY1">%3$s</xliff:g>. – <xliff:g id="DAY2">%8$s</xliff:g>. <xliff:g id="MONTH1">%2$s</xliff:g>"</string>
+    <string name="same_month_wday1_md1_wday2_md2">"<xliff:g id="WEEKDAY1">%1$s</xliff:g>, <xliff:g id="DAY1_0">%3$s</xliff:g>. <xliff:g id="MONTH1">%2$s</xliff:g> – <xliff:g id="WEEKDAY2">%6$s</xliff:g>, <xliff:g id="DAY2_1">%8$s</xliff:g>. <xliff:g id="MONTH2">%7$s</xliff:g>"</string>
+    <string name="same_month_mdy1_mdy2">"<xliff:g id="DAY1">%3$s</xliff:g>. – <xliff:g id="DAY2">%8$s</xliff:g>. <xliff:g id="MONTH1">%2$s</xliff:g> <xliff:g id="YEAR2">%9$s</xliff:g>"</string>
+    <string name="same_month_wday1_mdy1_wday2_mdy2">"<xliff:g id="WEEKDAY1">%1$s</xliff:g>, <xliff:g id="DAY1_0">%3$s</xliff:g>. <xliff:g id="MONTH1">%2$s</xliff:g> <xliff:g id="YEAR1">%4$s</xliff:g> – <xliff:g id="WEEKDAY2">%6$s</xliff:g>, <xliff:g id="DAY2_1">%8$s</xliff:g>. <xliff:g id="MONTH2">%7$s</xliff:g> <xliff:g id="YEAR2">%9$s</xliff:g>"</string>
+    <string name="same_month_md1_time1_md2_time2">"<xliff:g id="DAY1">%3$s</xliff:g>. <xliff:g id="MONTH1">%2$s</xliff:g>, <xliff:g id="TIME1">%5$s</xliff:g> – <xliff:g id="DAY2">%8$s</xliff:g>. <xliff:g id="MONTH2">%7$s</xliff:g>, <xliff:g id="TIME2">%10$s</xliff:g>"</string>
+    <string name="same_month_wday1_md1_time1_wday2_md2_time2">"<xliff:g id="WEEKDAY1">%1$s</xliff:g>, <xliff:g id="DAY1_0">%3$s</xliff:g>. <xliff:g id="MONTH1">%2$s</xliff:g> <xliff:g id="TIME1">%5$s</xliff:g> – <xliff:g id="WEEKDAY2">%6$s</xliff:g>, <xliff:g id="DAY2_1">%8$s</xliff:g>. <xliff:g id="MONTH2">%7$s</xliff:g> <xliff:g id="TIME2">%10$s</xliff:g>"</string>
+    <string name="same_month_mdy1_time1_mdy2_time2">"<xliff:g id="MONTH1">%2$s</xliff:g> <xliff:g id="DAY1">%3$s</xliff:g>, <xliff:g id="YEAR1">%4$s</xliff:g>, <xliff:g id="TIME1">%5$s</xliff:g> – <xliff:g id="MONTH2">%7$s</xliff:g> <xliff:g id="DAY2">%8$s</xliff:g>, <xliff:g id="YEAR2">%9$s</xliff:g>, <xliff:g id="TIME2">%10$s</xliff:g>"</string>
+    <string name="same_month_wday1_mdy1_time1_wday2_mdy2_time2">"<xliff:g id="WEEKDAY1">%1$s</xliff:g>, <xliff:g id="DAY1_0">%3$s</xliff:g>. <xliff:g id="MONTH1">%2$s</xliff:g> <xliff:g id="YEAR1">%4$s</xliff:g>, <xliff:g id="TIME1">%5$s</xliff:g> – <xliff:g id="WEEKDAY2">%6$s</xliff:g>, <xliff:g id="DAY2_1">%8$s</xliff:g>. <xliff:g id="MONTH2">%7$s</xliff:g> <xliff:g id="YEAR2">%9$s</xliff:g>, <xliff:g id="TIME2">%10$s</xliff:g>"</string>
+    <string name="abbrev_month_day_year">"<xliff:g id="DAY">%-d</xliff:g>. <xliff:g id="MONTH">%b</xliff:g> <xliff:g id="YEAR">%Y</xliff:g>"</string>
+    <!-- no translation found for abbrev_month_year (5966980891147982768) -->
+    <skip />
+    <!-- no translation found for abbrev_month_day (3156047263406783231) -->
+    <skip />
+    <!-- no translation found for abbrev_month (7304935052615731208) -->
+    <skip />
+    <string name="day_of_week_long_sunday">"neděle"</string>
+    <string name="day_of_week_long_monday">"pondělí"</string>
+    <string name="day_of_week_long_tuesday">"úterý"</string>
+    <string name="day_of_week_long_wednesday">"středa"</string>
+    <string name="day_of_week_long_thursday">"čtvrtek"</string>
+    <string name="day_of_week_long_friday">"pátek"</string>
+    <string name="day_of_week_long_saturday">"sobota"</string>
+    <string name="day_of_week_medium_sunday">"Ne"</string>
+    <string name="day_of_week_medium_monday">"Po"</string>
+    <string name="day_of_week_medium_tuesday">"Út"</string>
+    <string name="day_of_week_medium_wednesday">"St"</string>
+    <string name="day_of_week_medium_thursday">"Čt"</string>
+    <string name="day_of_week_medium_friday">"Pá"</string>
+    <string name="day_of_week_medium_saturday">"So"</string>
+    <string name="day_of_week_short_sunday">"Ne"</string>
+    <string name="day_of_week_short_monday">"Po"</string>
+    <string name="day_of_week_short_tuesday">"Út"</string>
+    <string name="day_of_week_short_wednesday">"St"</string>
+    <string name="day_of_week_short_thursday">"Čt"</string>
+    <string name="day_of_week_short_friday">"Pá"</string>
+    <string name="day_of_week_short_saturday">"So"</string>
+    <string name="day_of_week_shorter_sunday">"Ne"</string>
+    <string name="day_of_week_shorter_monday">"Po"</string>
+    <string name="day_of_week_shorter_tuesday">"Út"</string>
+    <string name="day_of_week_shorter_wednesday">"St"</string>
+    <string name="day_of_week_shorter_thursday">"Čt"</string>
+    <string name="day_of_week_shorter_friday">"Pá"</string>
+    <string name="day_of_week_shorter_saturday">"So"</string>
+    <string name="day_of_week_shortest_sunday">"Ne"</string>
+    <string name="day_of_week_shortest_monday">"Po"</string>
+    <string name="day_of_week_shortest_tuesday">"Čt"</string>
+    <string name="day_of_week_shortest_wednesday">"St"</string>
+    <string name="day_of_week_shortest_thursday">"Čt"</string>
+    <string name="day_of_week_shortest_friday">"Pá"</string>
+    <string name="day_of_week_shortest_saturday">"So"</string>
+    <string name="month_long_january">"leden"</string>
+    <string name="month_long_february">"únor"</string>
+    <string name="month_long_march">"březen"</string>
+    <string name="month_long_april">"duben"</string>
+    <string name="month_long_may">"květen"</string>
+    <string name="month_long_june">"červen"</string>
+    <string name="month_long_july">"červenec"</string>
+    <string name="month_long_august">"srpen"</string>
+    <string name="month_long_september">"září"</string>
+    <string name="month_long_october">"říjen"</string>
+    <string name="month_long_november">"listopad"</string>
+    <string name="month_long_december">"prosinec"</string>
+    <string name="month_medium_january">"leden"</string>
+    <string name="month_medium_february">"únor"</string>
+    <string name="month_medium_march">"březen"</string>
+    <string name="month_medium_april">"duben"</string>
+    <string name="month_medium_may">"květen"</string>
+    <string name="month_medium_june">"červen"</string>
+    <string name="month_medium_july">"červenec"</string>
+    <string name="month_medium_august">"srpen"</string>
+    <string name="month_medium_september">"září"</string>
+    <string name="month_medium_october">"říjen"</string>
+    <string name="month_medium_november">"listopad"</string>
+    <string name="month_medium_december">"prosinec"</string>
+    <string name="month_shortest_january">"1."</string>
+    <string name="month_shortest_february">"2."</string>
+    <string name="month_shortest_march">"Po"</string>
+    <string name="month_shortest_april">"4."</string>
+    <string name="month_shortest_may">"5."</string>
+    <string name="month_shortest_june">"6."</string>
+    <string name="month_shortest_july">"7."</string>
+    <string name="month_shortest_august">"8."</string>
+    <string name="month_shortest_september">"9."</string>
+    <string name="month_shortest_october">"10."</string>
+    <string name="month_shortest_november">"11."</string>
+    <string name="month_shortest_december">"12."</string>
+    <string name="elapsed_time_short_format_mm_ss">"<xliff:g id="MINUTES">%1$02d</xliff:g>:<xliff:g id="SECONDS">%2$02d</xliff:g>"</string>
+    <string name="elapsed_time_short_format_h_mm_ss">"<xliff:g id="HOURS">%1$d</xliff:g>:<xliff:g id="MINUTES">%2$02d</xliff:g>:<xliff:g id="SECONDS">%3$02d</xliff:g>"</string>
+    <string name="selectAll">"Vybrat vše"</string>
+    <string name="selectText">"Označit text"</string>
+    <string name="stopSelectingText">"Zastavit označování textu"</string>
+    <string name="cut">"Vyjmout"</string>
+    <string name="cutAll">"Vyjmout vše"</string>
+    <string name="copy">"Kopírovat"</string>
+    <string name="copyAll">"Kopírovat vše"</string>
+    <string name="paste">"Vložit"</string>
+    <string name="copyUrl">"Kopírovat adresu URL"</string>
+    <string name="inputMethod">"Metoda zadávání dat"</string>
+    <string name="addToDictionary">"Přidat „%s“ do slovníku"</string>
+    <string name="editTextMenuTitle">"Úpravy textu"</string>
+    <string name="low_internal_storage_view_title">"Málo paměti"</string>
+    <string name="low_internal_storage_view_text">"V telefonu zbývá málo místa pro ukládání dat."</string>
+    <string name="ok">"OK"</string>
+    <string name="cancel">"Zrušit"</string>
+    <string name="yes">"OK"</string>
+    <string name="no">"Zrušit"</string>
+    <string name="dialog_alert_title">"Upozornění"</string>
+    <string name="capital_on">"ZAPNUTO"</string>
+    <string name="capital_off">"VYPNOUT"</string>
+    <string name="whichApplication">"Dokončit akci pomocí aplikace"</string>
+    <string name="alwaysUse">"Použít jako výchozí nastavení pro tuto činnost."</string>
+    <string name="clearDefaultHintMsg">"Vymažte výchozí hodnoty v Nastavení plochy &gt; Aplikace &gt; Správa aplikací."</string>
+    <string name="chooseActivity">"Vyberte akci"</string>
+    <string name="noApplications">"Tuto činnost nemohou provádět žádné aplikace."</string>
+    <string name="aerr_title">"Omlouváme se"</string>
+    <string name="aerr_application">"Aplikace <xliff:g id="APPLICATION">%1$s</xliff:g> (proces <xliff:g id="PROCESS">%2$s</xliff:g>) byla neočekávaně ukončena. Zkuste to znovu."</string>
+    <string name="aerr_process">"Proces <xliff:g id="PROCESS">%1$s</xliff:g> byl neočekávaně ukončen. Opakujte prosím akci."</string>
+    <string name="anr_title">"Omlouváme se"</string>
+    <string name="anr_activity_application">"Činnost <xliff:g id="ACTIVITY">%1$s</xliff:g> (v aplikaci <xliff:g id="APPLICATION">%2$s</xliff:g>) neodpovídá."</string>
+    <string name="anr_activity_process">"Služba <xliff:g id="ACTIVITY">%1$s</xliff:g> (<xliff:g id="PROCESS">%2$s</xliff:g>) nereaguje."</string>
+    <string name="anr_application_process">"Služba <xliff:g id="APPLICATION">%1$s</xliff:g> (<xliff:g id="PROCESS">%2$s</xliff:g>) nereaguje."</string>
+    <string name="anr_process">"Proces <xliff:g id="PROCESS">%1$s</xliff:g> neodpovídá."</string>
+    <string name="force_close">"Ukončit aplikaci"</string>
+    <string name="wait">"Počkat"</string>
+    <string name="debug">"Ladit"</string>
+    <string name="sendText">"Vyberte činnost s textem"</string>
+    <string name="volume_ringtone">"Hlasitost vyzvánění"</string>
+    <string name="volume_music">"Hlasitost médií"</string>
+    <string name="volume_music_hint_playing_through_bluetooth">"Přehrávání pomocí rozhraní Bluetooth"</string>
+    <string name="volume_call">"Hlasitost hovoru"</string>
+    <string name="volume_bluetooth_call">"Hlasitost příchozích hovorů při připojení Bluetooth"</string>
+    <string name="volume_alarm">"Hlasitost upozornění a budíku"</string>
+    <string name="volume_notification">"Hlasitost oznámení"</string>
+    <string name="volume_unknown">"Hlasitost"</string>
+    <string name="ringtone_default">"Výchozí vyzváněcí tón"</string>
+    <string name="ringtone_default_with_actual">"Výchozí vyzváněcí tón (<xliff:g id="ACTUAL_RINGTONE">%1$s</xliff:g>)"</string>
+    <string name="ringtone_silent">"Ticho"</string>
+    <string name="ringtone_picker_title">"Vyzváněcí tóny"</string>
+    <string name="ringtone_unknown">"Neznámý vyzváněcí tón"</string>
+  <plurals name="wifi_available">
+    <item quantity="one">"K dispozici je síť WiFi."</item>
+    <item quantity="other">"Jsou k dispozici sítě WiFi."</item>
+  </plurals>
+  <plurals name="wifi_available_detailed">
+    <item quantity="one">"K dispozici je veřejná síť WiFi"</item>
+    <item quantity="other">"Jsou k dispozici veřejné sítě WiFi"</item>
+  </plurals>
+    <string name="select_character">"Vkládání znaků"</string>
+    <string name="sms_control_default_app_name">"Neznámá aplikace"</string>
+    <string name="sms_control_title">"Odesílání zpráv SMS"</string>
+    <string name="sms_control_message">"Je odesílán velký počet zpráv SMS. Vyberte OK, chcete-li pokračovat, nebo Zrušit, chcete-li odesílání ukončit."</string>
+    <string name="sms_control_yes">"OK"</string>
+    <string name="sms_control_no">"Zrušit"</string>
+    <string name="date_time_set">"Nastavit"</string>
+    <string name="default_permission_group">"Výchozí"</string>
+    <string name="no_permissions">"Nejsou vyžadována žádná oprávnění"</string>
+    <string name="perms_hide"><b>"Skrýt"</b></string>
+    <string name="perms_show_all"><b>"Zobrazit vše"</b></string>
+    <string name="googlewebcontenthelper_loading">"Načítání..."</string>
+    <string name="usb_storage_title">"USB připojeno"</string>
+    <string name="usb_storage_message">"Připojili jste svůj telefon k počítači pomocí USB. Vyberte možnost Připojit, chcete-li zkopírovat soubory mezi vaším počítačem a kartou SD v telefonu."</string>
+    <string name="usb_storage_button_mount">"Připojit"</string>
+    <string name="usb_storage_button_unmount">"Nepřipojovat"</string>
+    <string name="usb_storage_error_message">"Při používání vaší karty SD jako úložiště USB došlo k problému."</string>
+    <string name="usb_storage_notification_title">"USB připojeno"</string>
+    <string name="usb_storage_notification_message">"Vyberte, chcete-li kopírovat soubory do nebo z počítače."</string>
+    <string name="usb_storage_stop_notification_title">"Vypnout úložiště USB"</string>
+    <string name="usb_storage_stop_notification_message">"Vyberte, chcete-li vypnout úložiště USB."</string>
+    <string name="usb_storage_stop_title">"Vypnout úložiště USB"</string>
+    <string name="usb_storage_stop_message">"Před vypnutím úložiště USB se přesvědčte, zda byl hostitel USB odpojen. Úložiště USB vypnete volbou Vypnout."</string>
+    <string name="usb_storage_stop_button_mount">"Vypnout"</string>
+    <string name="usb_storage_stop_button_unmount">"Zrušit"</string>
+    <string name="usb_storage_stop_error_message">"Při vypínání úložiště USB došlo k problémům. Zkontrolujte, zda byl hostitel USB odpojen, a zkuste to znovu."</string>
+    <string name="extmedia_format_title">"Formátovat kartu SD"</string>
+    <string name="extmedia_format_message">"Opravdu chcete kartu SD naformátovat? Všechna data na kartě budou ztracena."</string>
+    <string name="extmedia_format_button_format">"Formátovat"</string>
+    <string name="select_input_method">"Výběr metody zadávání dat"</string>
+    <string name="fast_scroll_alphabet">" AÁBCČDĎEÉĚFGHCHIÍJKLMNŇOÓPQRŘSŠTŤUÚVWXYÝZŽ"</string>
+    <string name="fast_scroll_numeric_alphabet">" 0123456789AÁBCČDĎEÉĚFGHCHIÍJKLMNŇOÓPQRŘSŠTŤUÚVWXYÝZŽ"</string>
+    <string name="candidates_style"><u>"kandidáti"</u></string>
+    <string name="ext_media_checking_notification_title">"Příprava karty SD"</string>
+    <string name="ext_media_checking_notification_message">"Kontrola chyb"</string>
+    <string name="ext_media_nofs_notification_title">"Prázdná karta SD"</string>
+    <string name="ext_media_nofs_notification_message">"Karta SD je prázdná nebo používá nepodporovaný systém souborů."</string>
+    <string name="ext_media_unmountable_notification_title">"Poškozená karta SD"</string>
+    <string name="ext_media_unmountable_notification_message">"Karta SD je poškozena. Pravděpodobně ji bude nutné znovu formátovat."</string>
+    <string name="ext_media_badremoval_notification_title">"Karta SD byla neočekávaně odebrána"</string>
+    <string name="ext_media_badremoval_notification_message">"Chcete-li zabránit ztrátě dat, kartu SD před odebráním odpojte."</string>
+    <string name="ext_media_safe_unmount_notification_title">"Kartu SD je možné bezpečně odebrat"</string>
+    <string name="ext_media_safe_unmount_notification_message">"Kartu SD lze nyní bezpečně vyjmout."</string>
+    <string name="ext_media_nomedia_notification_title">"Karta SD byla odstraněna"</string>
+    <string name="ext_media_nomedia_notification_message">"Karta SD byla odebrána. Chcete-li zvětšit úložiště svého zařízení, vložte kartu SD."</string>
+    <string name="activity_list_empty">"Nebyly nalezeny žádné odpovídající aktivity."</string>
+    <string name="permlab_pkgUsageStats">"aktualizovat statistiku použití součástí"</string>
+    <string name="permdesc_pkgUsageStats">"Umožňuje změnu shromážděných statistických údajů o použití součástí. Není určeno pro běžné aplikace."</string>
+    <!-- no translation found for tutorial_double_tap_to_zoom_message_short (1311810005957319690) -->
+    <skip />
+    <!-- no translation found for gadget_host_error_inflating (2613287218853846830) -->
+    <skip />
+    <!-- no translation found for ime_action_go (8320845651737369027) -->
+    <skip />
+    <!-- no translation found for ime_action_search (658110271822807811) -->
+    <skip />
+    <!-- no translation found for ime_action_send (2316166556349314424) -->
+    <skip />
+    <!-- no translation found for ime_action_next (3138843904009813834) -->
+    <skip />
+    <!-- no translation found for ime_action_default (2840921885558045721) -->
+    <skip />
+</resources>
diff --git a/core/res/res/values-de/strings.xml b/core/res/res/values-de/strings.xml
new file mode 100644
index 0000000..cda8cbc
--- /dev/null
+++ b/core/res/res/values-de/strings.xml
@@ -0,0 +1,815 @@
+<?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.
+-->
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="byteShort">"B"</string>
+    <string name="kilobyteShort">"KB"</string>
+    <string name="megabyteShort">"MB"</string>
+    <string name="gigabyteShort">"GB"</string>
+    <string name="terabyteShort">"TB"</string>
+    <string name="petabyteShort">"PB"</string>
+    <string name="untitled">"&lt;Unbenannt&gt;"</string>
+    <string name="ellipsis">"…"</string>
+    <string name="emptyPhoneNumber">"(Keine Telefonnummer)"</string>
+    <string name="unknownName">"(Unbekannt)"</string>
+    <string name="defaultVoiceMailAlphaTag">"Mailbox"</string>
+    <string name="defaultMsisdnAlphaTag">"MSISDN1"</string>
+    <string name="mmiError">"Verbindungsproblem oder ungültiger MMI-Code."</string>
+    <string name="serviceEnabled">"Dienst wurde aktiviert."</string>
+    <string name="serviceEnabledFor">"Dienst wurde aktiviert für:"</string>
+    <string name="serviceDisabled">"Dienst wurde deaktiviert."</string>
+    <string name="serviceRegistered">"Registrierung war erfolgreich."</string>
+    <string name="serviceErased">"Löschvorgang erfolgreich."</string>
+    <string name="passwordIncorrect">"Falsches Passwort."</string>
+    <string name="mmiComplete">"MMI abgeschlossen."</string>
+    <string name="badPin">"Die von Ihnen eingegebene alte PIN ist nicht korrekt."</string>
+    <string name="badPuk">"Der von Ihnen eingegebene PUK ist nicht korrekt."</string>
+    <string name="mismatchPin">"Die von Ihnen eingegebenen PIN-Nummern stimmen nicht überein."</string>
+    <string name="invalidPin">"Geben Sie eine PIN ein, die 4 bis 8 Zahlen enthält."</string>
+    <string name="needPuk">"Ihre SIM-Karte ist mit einem PUK gesperrt. Geben Sie zum Entsperren den PUK-Code ein."</string>
+    <string name="needPuk2">"Geben Sie zum Entsperren der SIM-Karte den PUK2 ein."</string>
+    <string name="ClipMmi">"Anrufer-ID für eingehenden Anruf"</string>
+    <string name="ClirMmi">"Anrufer-ID für abgehenden Anruf"</string>
+    <string name="CfMmi">"Anrufweiterleitung"</string>
+    <string name="CwMmi">"Anklopfen"</string>
+    <string name="BaMmi">"Anrufsperre"</string>
+    <string name="PwdMmi">"Passwort-Änderung"</string>
+    <string name="PinMmi">"PIN-Änderung"</string>
+    <string name="CLIRDefaultOnNextCallOn">"Anrufer-ID ist standardmäßig beschränkt. Nächster Anruf: Beschränkt"</string>
+    <string name="CLIRDefaultOnNextCallOff">"Anrufer-ID ist standardmäßig beschränkt. Nächster Anruf: Nicht beschränkt"</string>
+    <string name="CLIRDefaultOffNextCallOn">"Anrufer-ID ist standardmäßig nicht beschränkt. Nächster Anruf: Beschränkt"</string>
+    <string name="CLIRDefaultOffNextCallOff">"Anrufer-ID ist standardmäßig nicht beschränkt. Nächster Anruf: Nicht beschränkt"</string>
+    <string name="serviceNotProvisioned">"Dienst nicht eingerichtet."</string>
+    <string name="CLIRPermanent">"Die Einstellung für die Anrufer-ID kann nicht geändert werden."</string>
+    <string name="serviceClassVoice">"Sprachnotiz"</string>
+    <string name="serviceClassData">"Daten"</string>
+    <string name="serviceClassFAX">"FAX"</string>
+    <string name="serviceClassSMS">"SMS"</string>
+    <string name="serviceClassDataAsync">"Asynchron"</string>
+    <string name="serviceClassDataSync">"Synchron"</string>
+    <string name="serviceClassPacket">"Paket"</string>
+    <string name="serviceClassPAD">"PAD"</string>
+    <string name="cfTemplateNotForwarded">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: Nicht weitergeleitet"</string>
+    <string name="cfTemplateForwarded">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
+    <string name="cfTemplateForwardedTime">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g><xliff:g id="DIALING_NUMBER">{1}</xliff:g> nach <xliff:g id="TIME_DELAY">{2}</xliff:g> Sekunden."</string>
+    <string name="cfTemplateRegistered">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: Nicht weitergeleitet"</string>
+    <string name="cfTemplateRegisteredTime">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: Nicht weitergeleitet"</string>
+    <string name="httpErrorOk">"OK"</string>
+    <string name="httpError">"Auf der Webseite ist ein Fehler aufgetreten."</string>
+    <string name="httpErrorLookup">"Die URL konnte nicht gefunden werden."</string>
+    <string name="httpErrorUnsupportedAuthScheme">"Das Authentifizierungsschema für die Site wird nicht unterstützt."</string>
+    <string name="httpErrorAuth">"Authentifizierung ist fehlgeschlagen."</string>
+    <string name="httpErrorProxyAuth">"Authentifizierung via Proxy-Server ist fehlgeschlagen."</string>
+    <string name="httpErrorConnect">"Es konnte keine Verbindung zum Server hergestellt werden."</string>
+    <string name="httpErrorIO">"Die Server-Kommunikation ist fehlgeschlagen. Versuchen Sie es später erneut."</string>
+    <string name="httpErrorTimeout">"Zeitüberschreitung bei Serververbindung."</string>
+    <string name="httpErrorRedirectLoop">"Die Seite enthält zu viele Server-Redirects."</string>
+    <string name="httpErrorUnsupportedScheme">"Das Protokoll wird nicht unterstützt."</string>
+    <string name="httpErrorFailedSslHandshake">"Es konnte keine sichere Verbindung aufgebaut werden."</string>
+    <string name="httpErrorBadUrl">"Die Seite konnte nicht geöffnet werden, weil die URL ungültig ist."</string>
+    <string name="httpErrorFile">"Auf die Datei konnte nicht zugegriffen werden."</string>
+    <string name="httpErrorFileNotFound">"Die angeforderte Datei wurde nicht gefunden."</string>
+    <string name="httpErrorTooManyRequests">"Es werden zurzeit zu viele Anfragen verarbeitet. Versuchen Sie es später erneut."</string>
+    <string name="contentServiceSync">"Synchronisieren"</string>
+    <string name="contentServiceSyncNotificationTitle">"Synchronisieren"</string>
+    <string name="contentServiceTooManyDeletesNotificationDesc">"Zu viele <xliff:g id="CONTENT_TYPE">%s</xliff:g> gelöscht."</string>
+    <string name="low_memory">"Telefonspeicher ist voll! Löschen Sie Dateien, um Speicherplatz freizugeben."</string>
+    <string name="me">"Eigene"</string>
+    <string name="power_dialog">"Telefonoptionen"</string>
+    <string name="silent_mode">"Lautlos-Modus"</string>
+    <string name="turn_on_radio">"Funk einschalten"</string>
+    <string name="turn_off_radio">"Funk ausschalten"</string>
+    <string name="screen_lock">"Bildschirmsperre"</string>
+    <string name="power_off">"Ausschalten"</string>
+    <string name="shutdown_progress">"Herunterfahren..."</string>
+    <string name="shutdown_confirm">"Ihr Telefon wird heruntergefahren."</string>
+    <string name="no_recent_tasks">"Keine neuen Anwendungen."</string>
+    <string name="global_actions">"Telefonoptionen"</string>
+    <string name="global_action_lock">"Bildschirmsperre"</string>
+    <string name="global_action_power_off">"Ausschalten"</string>
+    <string name="global_action_toggle_silent_mode">"Lautlos"</string>
+    <string name="global_action_silent_mode_on_status">"Ton ist bereits AUS"</string>
+    <string name="global_action_silent_mode_off_status">"Ton ist momentan AN"</string>
+    <!-- no translation found for global_actions_toggle_airplane_mode (5884330306926307456) -->
+    <skip />
+    <!-- no translation found for global_actions_airplane_mode_on_status (2719557982608919750) -->
+    <skip />
+    <!-- no translation found for global_actions_airplane_mode_off_status (5075070442854490296) -->
+    <skip />
+    <string name="safeMode">"Abgesicherter Modus"</string>
+    <!-- no translation found for android_system_label (6577375335728551336) -->
+    <skip />
+    <string name="permgrouplab_costMoney">"Kostenpflichtige Dienste"</string>
+    <string name="permgroupdesc_costMoney">"Ermöglicht Anwendungen die Ausführung eventuell kostenpflichtiger Aktionen."</string>
+    <string name="permgrouplab_messages">"Ihre Nachrichten"</string>
+    <string name="permgroupdesc_messages">"Lesen und schreiben Sie Ihre SMS, E-Mails und anderen Nachrichten."</string>
+    <string name="permgrouplab_personalInfo">"Ihre persönlichen Informationen"</string>
+    <string name="permgroupdesc_personalInfo">"Direkter Zugriff auf die Kontakte und den Kalender Ihres Telefons."</string>
+    <string name="permgrouplab_location">"Ihr Standort"</string>
+    <string name="permgroupdesc_location">"Ihren physischen Standort überwachen"</string>
+    <string name="permgrouplab_network">"Netzwerkkommunikation"</string>
+    <string name="permgroupdesc_network">"Ermöglicht Anwendungen den Zugriff auf verschiedene Netzwerkfunktionen."</string>
+    <string name="permgrouplab_accounts">"Ihre Google-Konten"</string>
+    <string name="permgroupdesc_accounts">"Greift auf verfügbare Google-Konten zu."</string>
+    <string name="permgrouplab_hardwareControls">"Hardware-Steuerelemente"</string>
+    <string name="permgroupdesc_hardwareControls">"Direkter Zugriff auf Hardware über Headset"</string>
+    <string name="permgrouplab_phoneCalls">"Anrufe"</string>
+    <string name="permgroupdesc_phoneCalls">"Überwachen, Aufzeichnen und Verarbeiten von Telefonanrufen"</string>
+    <string name="permgrouplab_systemTools">"System-Tools"</string>
+    <string name="permgroupdesc_systemTools">"Zugriff und Steuerung des Systems auf niedrigerer Ebene."</string>
+    <string name="permgrouplab_developmentTools">"Entwickler-Tools"</string>
+    <string name="permgroupdesc_developmentTools">"Funktionen nur für Anwendungsentwickler vorgesehen."</string>
+    <string name="permlab_statusBar">"Statusleiste deaktivieren oder ändern"</string>
+    <string name="permdesc_statusBar">"Ermöglicht der Anwendung, die Statusanzeige zu deaktivieren oder Systemsymbole hinzuzufügen oder zu entfernen."</string>
+    <string name="permlab_expandStatusBar">"Statusleiste ein-/ausblenden"</string>
+    <string name="permdesc_expandStatusBar">"Ermöglicht der Anwendung, die Statusleiste ein- oder auszublenden."</string>
+    <string name="permlab_processOutgoingCalls">"Abgehende Anrufe abfangen"</string>
+    <string name="permdesc_processOutgoingCalls">"Ermöglicht einer Anwendung, abgehende Anrufe zu verarbeiten und die zu wählende Nummer zu ändern. Schädliche Anwendungen können so abgehende Anrufe eventuell überwachen, umleiten oder verhindern."</string>
+    <string name="permlab_receiveSms">"SMS empfangen"</string>
+    <string name="permdesc_receiveSms">"Ermöglicht der Anwendung, Kurzmitteilungen zu empfangen und zu verarbeiten. Schädliche Anwendungen können Ihre Nachrichten möglicherweise überwachen oder löschen, bevor sie angezeigt werden."</string>
+    <string name="permlab_receiveMms">"MMS empfangen"</string>
+    <string name="permdesc_receiveMms">"Ermöglicht der Anwendung, MMS-Mitteilungen zu empfangen und zu verarbeiten. Schädliche Anwendungen können Ihre Nachrichten möglicherweise überwachen oder löschen, bevor sie angezeigt werden."</string>
+    <string name="permlab_sendSms">"Kurznachrichten senden"</string>
+    <string name="permdesc_sendSms">"Ermöglicht Anwendungen das Senden von SMS-Nachrichten. Bei schädlichen Anwendungen können Kosten entstehen, wenn diese Nachrichten ohne Ihre Zustimmung versenden."</string>
+    <string name="permlab_readSms">"SMS oder MMS lesen"</string>
+    <string name="permdesc_readSms">"Ermöglicht einer Anwendung, auf Ihrem Telefon oder Ihrer SIM-Karte gespeicherte Kurznachrichten zu lesen. Schädliche Anwendungen lesen so möglicherweise Ihre  vertraulichen Nachrichten."</string>
+    <string name="permlab_writeSms">"SMS oder MMS bearbeiten"</string>
+    <string name="permdesc_writeSms">"Ermöglicht einer Anwendung, auf Ihrem Telefon oder Ihrer SIM-Karte gespeicherte Kurznachrichten zu bearbeiten. Schädliche Anwendungen löschen möglicherweise Ihre Nachrichten."</string>
+    <string name="permlab_receiveWapPush">"WAP-Nachrichten empfangen"</string>
+    <string name="permdesc_receiveWapPush">"Ermöglicht der Anwendung, WAP-Mitteilungen zu empfangen und zu verarbeiten. Schädliche Anwendungen können Ihre Nachrichten möglicherweise überwachen oder löschen, bevor sie angezeigt werden."</string>
+    <string name="permlab_getTasks">"Laufende Anwendungen abrufen"</string>
+    <string name="permdesc_getTasks">"Ermöglicht der Anwendung, Informationen zu aktuellen und kürzlich ausführten Aufgaben abzurufen. Schädliche Anwendungen können so eventuell geheime Informationen zu anderen Anwendungen entdecken."</string>
+    <string name="permlab_reorderTasks">"Laufende Anwendungen neu ordnen"</string>
+    <string name="permdesc_reorderTasks">"Ermöglicht einer Anwendung, Aufgaben in den Vorder- und Hintergrund zu verschieben. Schädliche Anwendungen können so ohne Ihr Zutun eine Anzeige im Vordergrund erzwingen."</string>
+    <string name="permlab_setDebugApp">"Fehlerbeseitigung für Anwendung aktivieren"</string>
+    <string name="permdesc_setDebugApp">"Ermöglicht einer Anwendung, die Fehlerbeseitigung für eine andere Anwendung zu aktivieren. Schädliche Anwendungen können so andere Anwendungen löschen."</string>
+    <string name="permlab_changeConfiguration">"UI-Einstellungen ändern"</string>
+    <string name="permdesc_changeConfiguration">"Ermöglicht einer Anwendung, die aktuelle Konfiguration zu ändern, etwa das Gebietsschema oder die Schriftgröße."</string>
+    <string name="permlab_restartPackages">"Andere Anwendungen neu starten"</string>
+    <string name="permdesc_restartPackages">"Ermöglicht einer Anwendung, den Neustart anderer Anwendungen zu erzwingen."</string>
+    <string name="permlab_setProcessForeground">"Beenden nicht zulassen"</string>
+    <string name="permdesc_setProcessForeground">"Ermöglicht einer Anwendung, beliebige Prozesse im Vordergrund auszuführen, damit diese nicht beendet werden können. Sollte nicht für normale Anwendungen benötigt werden."</string>
+    <string name="permlab_forceBack">"Schließen von Anwendung erzwingen"</string>
+    <string name="permdesc_forceBack">"Ermöglicht einer Anwendung, alle Aktivitäten, die im Vordergrund ablaufen, zu beenden und in den Hintergrund zu schieben. Sollte nicht für normale Anwendungen benötigt werden."</string>
+    <string name="permlab_dump">"Systeminternen Status abrufen"</string>
+    <string name="permdesc_dump">"Ermöglicht einer Anwendung, den internen Status des Systems abzurufen. Schädliche Anwendungen rufen hierbei möglicherweise eine Vielzahl an privaten und geschützten Daten ab, die Sie in der Regel nicht benötigen würden."</string>
+    <string name="permlab_addSystemService">"systemnahe Dienste veröffentlichen"</string>
+    <string name="permdesc_addSystemService">"Ermöglicht der Anwendung, ihre eigenen systemnahen Dienste anzubieten. Schädliche Anwendungen könnten in das System eindringen und darin befindliche Daten stehlen oder manipulieren."</string>
+    <string name="permlab_runSetActivityWatcher">"Start von Anwendungen überwachen und steuern"</string>
+    <string name="permdesc_runSetActivityWatcher">"Ermöglicht der Anwendung, den Start von Systemaktivitäten zu überwachen und zu steuern. Schädliche Anwendungen können so das gesamte System beeinträchtigen. Diese Berechtigung wird nur zu Entwicklungszwecken und nie für die normale Telefonnutzung benötigt."</string>
+    <string name="permlab_broadcastPackageRemoved">"Broadcast ohne Paket senden"</string>
+    <string name="permdesc_broadcastPackageRemoved">"Ermöglicht einer Anwendung, eine Benachrichtigung zur Entfernung eines Anwendungspakets zu senden. Schädliche Anwendungen können so laufende Anwendungen beenden."</string>
+    <string name="permlab_broadcastSmsReceived">"per SMS empfangenen Broadcast senden"</string>
+    <string name="permdesc_broadcastSmsReceived">"Ermöglicht einer Anwendung, eine Benachrichtigung zu senden, dass eine Kurzmitteilung empfangen wurde. Schädliche Anwendungen könnten diese Option verwenden, um den Eingang von Kurzmitteilungen zu erzwingen."</string>
+    <string name="permlab_broadcastWapPush">"von WAP-PUSH empfangenen Broadcast senden"</string>
+    <string name="permdesc_broadcastWapPush">"Ermöglicht einer Anwendung, eine Benachrichtigung zu senden, dass eine WAP PUSH-Nachricht empfangen wurde. Schädliche Anwendungen könnten diese Option verwenden, um den Erhalt von MMS-Mitteilungen zu erzwingen, oder um unbemerkt den Inhalt einer beliebigen Webseite durch schädliche Inhalte zu ersetzen."</string>
+    <string name="permlab_setProcessLimit">"Anzahl der laufenden Prozesse beschränken"</string>
+    <string name="permdesc_setProcessLimit">"Ermöglicht einer Anwendung, die maximale Anzahl an laufenden Prozessen zu steuern. Wird nicht für normale Anwendungen benötigt."</string>
+    <string name="permlab_setAlwaysFinish">"alle Anwendungen im Hintergrund schließen"</string>
+    <string name="permdesc_setAlwaysFinish">"Überlässt einer Anwendung die Entscheidung, ob Aktivitäten beendet werden, sobald Sie in den Hintergrund rücken. Wird nicht für normale Anwendungen benötigt."</string>
+    <string name="permlab_fotaUpdate">"System-Updates automatisch installieren"</string>
+    <string name="permdesc_fotaUpdate">"Ermöglicht einer Anwendung, Benachrichtigungen zu ausstehenden System-Updates zu erhalten und deren Installation einzuleiten. Schädliche Anwendungen können so das System durch nicht autorisierte Updates beschädigen oder in den Update-Prozess eingreifen."</string>
+    <string name="permlab_batteryStats">"Akku-Daten ändern"</string>
+    <string name="permdesc_batteryStats">"Ermöglicht die Änderung von gesammelten Akku-Daten. Nicht für normale Anwendungen vorgesehen."</string>
+    <string name="permlab_internalSystemWindow">"nicht autorisierte Fenster anzeigen"</string>
+    <string name="permdesc_internalSystemWindow">"Ermöglicht die Erstellung von Fenstern, die von der Benutzeroberfläche des internen Systems verwendet werden. Nicht für normale Anwendungen geeignet."</string>
+    <string name="permlab_systemAlertWindow">"Warnungen auf Systemebene anzeigen"</string>
+    <string name="permdesc_systemAlertWindow">"Ermöglicht einer Anwendung, Fenster mit Systemwarnungen anzuzeigen. Schädliche Anwendungen können so das gesamte Display des Telefons einnehmen."</string>
+    <string name="permlab_setAnimationScale">"Allgemeine Animationsgeschwindigkeit einstellen"</string>
+    <string name="permdesc_setAnimationScale">"Ermöglicht einer Anwendung, die allgemeine Animationsgeschwindigkeit (schnellere oder langsamere Animationen) jederzeit anzupassen."</string>
+    <string name="permlab_manageAppTokens">"Anwendungs-Tokens verwalten"</string>
+    <string name="permdesc_manageAppTokens">"Ermöglicht Anwendungen, Ihre eigenen Tokens zu erstellen und zu verwalten. Hierbei wird die normale Z-Reihenfolge umgangen. Dies sollte nicht für normale Anwendungen benötigt werden."</string>
+    <string name="permlab_injectEvents">"Tasten und Steuerungstasten drücken"</string>
+    <string name="permdesc_injectEvents">"Ermöglicht einer Anwendung, ihre eigenen Eingabeaktionen (Drücken von Tasten etc.) an andere Anwendungen zu liefern.  Schädliche Anwendungen können so die Kontrolle über Ihr Telefon übernehmen."</string>
+    <string name="permlab_readInputState">"Tastatureingaben und Aktionen aufzeichnen"</string>
+    <string name="permdesc_readInputState">"Ermöglicht Anwendungen, die von Ihnen gedrückten Tasten zu überwachen (etwa die Eingabe eines Passworts). Dies gilt auch für die Interaktion mit anderen Anwendungen. Sollte für normale Anwendungen nicht benötigt werden."</string>
+    <string name="permlab_bindInputMethod">"An eine Eingabemethode binden"</string>
+    <string name="permdesc_bindInputMethod">"Ermöglicht dem Halter, sich an die Oberfläche einer Eingabemethode auf oberster Ebene zu binden. Sollte nie für normale Anwendungen benötigt werden."</string>
+    <string name="permlab_setOrientation">"Bildschirmausrichtung ändern"</string>
+    <string name="permdesc_setOrientation">"Ermöglicht der Anwendung, die Bildschirmdrehung jederzeit zu ändern. Sollte nicht für normale Anwendungen benötigt werden."</string>
+    <string name="permlab_signalPersistentProcesses">"Linux-Signale an Anwendungen senden"</string>
+    <string name="permdesc_signalPersistentProcesses">"Ermöglicht der Anwendung, das Senden des gelieferten Signals an alle anhaltenden Prozesse zu fordern."</string>
+    <string name="permlab_persistentActivity">"Anwendungen permanent ausführen"</string>
+    <string name="permdesc_persistentActivity">"Ermöglicht einer Anwendung, eigene Komponenten persistent zu machen, damit das System diese nicht für andere Anwendungen nutzen kann."</string>
+    <string name="permlab_deletePackages">"Anwendungen löschen"</string>
+    <string name="permdesc_deletePackages">"Ermöglicht einer Anwendung, Android-Pakete zu löschen. Schädliche Anwendungen können so wichtige Anwendungen löschen."</string>
+    <string name="permlab_clearAppUserData">"Daten anderer Anwendungen löschen"</string>
+    <string name="permdesc_clearAppUserData">"Ermöglicht einer Anwendung das Löschen von Nutzerdaten."</string>
+    <string name="permlab_deleteCacheFiles">"Caches anderer Anwendungen löschen"</string>
+    <string name="permdesc_deleteCacheFiles">"Ermöglicht einer Anwendung, Cache-Dateien zu löschen."</string>
+    <string name="permlab_getPackageSize">"Speicherplatz der Anwendung abrufen"</string>
+    <string name="permdesc_getPackageSize">"Ermöglicht einer Anwendung, ihre Code-, Daten- und Cache-Größe abzurufen."</string>
+    <string name="permlab_installPackages">"Anwendungen direkt installieren"</string>
+    <string name="permdesc_installPackages">"Ermöglicht einer Anwendung, neue oder aktualisierte Android-Pakete zu installieren. Schädliche Anwendungen können so neue Anwendungen mit beliebig umfangreichen Berechtigungen hinzufügen."</string>
+    <string name="permlab_clearAppCache">"Alle Cache-Daten der Anwendung löschen"</string>
+    <string name="permdesc_clearAppCache">"Ermöglicht einer Anwendung, Telefonspeicher durch das Löschen von Dateien im Cache-Verzeichnis der Anwendung freizugeben. Der Zugriff beschränkt sich in der Regel auf Systemprozesse."</string>
+    <string name="permlab_readLogs">"System-Protokolldateien lesen"</string>
+    <string name="permdesc_readLogs">"Ermöglicht einer Anwendung, die verschiedenen Protokolldateien des Systems zu lesen. So können allgemeine Informationen zu den auf Ihrem Telefon durchgeführten Aktionen eingesehen werden, diese sollten jedoch keine persönlichen oder geheimen Daten enthalten."</string>
+    <string name="permlab_diagnostic">"Lese-/Schreibberechtigung für zu Diagnosegruppe gehörige Elemente"</string>
+    <string name="permdesc_diagnostic">"Ermöglicht einer Anwendung, alle Elemente in der Diagnosegruppe zu lesen und zu bearbeiten, etwa Dateien in \"/dev\". Dies könnte eine potenzielle Gefährdung für die Stabilität und Sicherheit des Systems darstellen und sollte NUR für Hardware-spezifische Diagnosen des Herstellers oder Netzbetreibers verwendet werden."</string>
+    <string name="permlab_changeComponentState">"Anwendungskomponenten aktivieren oder deaktivieren"</string>
+    <string name="permdesc_changeComponentState">"Ermöglicht einer Anwendung, die Komponente einer anderen Anwendung nach Belieben zu aktivieren oder zu deaktivieren. Schädliche Anwendungen können so wichtige Funktionen des Telefons deaktivieren. Bei der Erteilung von Berechtigungen ist daher Vorsicht geboten, da die Anwendungskomponenten unbrauchbar, inkonsistent und unstabil werden können."</string>
+    <string name="permlab_setPreferredApplications">"Bevorzugte Einstellungen festlegen"</string>
+    <string name="permdesc_setPreferredApplications">"Ermöglicht einer Anwendung, Ihre bevorzugten Einstellungen zu ändern. Schädliche Anwendungen können so laufende Anwendungen ohne Ihr Wissen ändern, damit die vorhandenen Anwendungen private Daten von Ihnen sammeln."</string>
+    <string name="permlab_writeSettings">"Allgemeine Systemeinstellungen ändern"</string>
+    <string name="permdesc_writeSettings">"Ermöglicht einer Anwendung, die Einstellungsdaten des Systems zu ändern. Schädliche Anwendungen können so die Systemkonfiguration beschädigen."</string>
+    <string name="permlab_writeSecureSettings">"Sicherheitseinstellungen für das System ändern"</string>
+    <string name="permdesc_writeSecureSettings">"Ermöglicht einer Anwendung, die Daten der Sicherheitseinstellungen des Systems zu ändern. Nicht für normale Anwendungen vorgesehen."</string>
+    <string name="permlab_writeGservices">"Google Services Map ändern"</string>
+    <string name="permdesc_writeGservices">"Ermöglicht einer Anwendung, Änderungen an der Google Services Map vorzunehmen. Nicht für normale Anwendungen vorgesehen."</string>
+    <string name="permlab_receiveBootCompleted">"Automatisch nach dem Booten starten"</string>
+    <string name="permdesc_receiveBootCompleted">"Ermöglicht einer Anwendung, sich selbst zu starten, sobald das System gebootet wurde. Dadurch kann es länger dauern, bis das Telefon gestartet wird, und durch die ständige Aktivität der Anwendung wird die gesamte Leistung des Telefons beeinträchtigt."</string>
+    <string name="permlab_broadcastSticky">"dauerhaften Broadcast senden"</string>
+    <string name="permdesc_broadcastSticky">"Ermöglicht einer Anwendung, dauerhafte Broadcasts zu senden, die auch nach dem Ende des Broadcasts bestehen bleiben. Schädliche Anwendungen können das Telefon langsam oder unstabil machen, da zuviel Speicherplatz belegt ist."</string>
+    <string name="permlab_readContacts">"Kontaktdaten lesen"</string>
+    <string name="permdesc_readContacts">"Ermöglicht einer Anwendung, alle auf Ihrem Telefon gespeicherten Kontaktdaten (Adressen) zu lesen. Schädliche Anwendungen können so Ihre Daten an andere Personen senden."</string>
+    <string name="permlab_writeContacts">"Kontaktdaten schreiben"</string>
+    <string name="permdesc_writeContacts">"Ermöglicht einer Anwendung, die auf Ihrem Telefon gespeicherten Kontaktdaten (Adressen) zu ändern. Schädliche Anwendungen können so Ihre Kontaktdaten löschen oder verändern."</string>
+    <string name="permlab_writeOwnerData">"Eigentümerdaten schreiben"</string>
+    <string name="permdesc_writeOwnerData">"Ermöglicht einer Anwendung, die auf Ihrem Telefon gespeicherten Eigentümerdaten zu ändern. Schädliche Anwendungen können so Eigentümerdaten löschen oder verändern."</string>
+    <string name="permlab_readOwnerData">"Eigentümerdaten lesen"</string>
+    <string name="permdesc_readOwnerData">"Ermöglicht einer Anwendung, die auf Ihrem Telefon gespeicherten Eigentümerdaten zu lesen. Schädliche Anwendungen können so Eigentümerdaten lesen."</string>
+    <string name="permlab_readCalendar">"Kalenderdaten lesen"</string>
+    <string name="permdesc_readCalendar">"Ermöglicht einer Anwendung, alle auf Ihrem Telefon gespeicherten Kalenderereignisse zu lesen. Schädliche Anwendungen können so Ihre Kalenderereignisse an andere Personen senden."</string>
+    <string name="permlab_writeCalendar">"Kalenderdaten schreiben"</string>
+    <string name="permdesc_writeCalendar">"Ermöglicht einer Anwendung, die auf Ihrem Telefon gespeicherten Kalenderereignisse zu ändern. Schädliche Anwendungen können so Ihre Kalenderdaten löschen oder verändern."</string>
+    <string name="permlab_accessMockLocation">"Falsche Standortquellen für Testzwecke"</string>
+    <string name="permdesc_accessMockLocation">"Erstellt falsche Standortquellen für Testzwecke. Schädliche Anwendungen können so den von den echten Standortquellen wie GPS oder Netzwerkanbieter zurückgegebenen Standort und/oder Status überschreiben."</string>
+    <string name="permlab_accessLocationExtraCommands">"Auf zusätzliche Dienstanbieterbefehle für Standort zugreifen"</string>
+    <string name="permdesc_accessLocationExtraCommands">"Zugriff auf zusätzliche Dienstanbieterbefehle für Standort. Schädliche Anwendungen könnten so die Funktionsweise von GPS oder anderen Standortquellen beeinträchtigen."</string>
+    <string name="permlab_accessFineLocation">"genauer (GPS-) Standort"</string>
+    <string name="permdesc_accessFineLocation">"Zugriff auf genaue Standortquellen wie GPS auf dem Telefon (falls verfügbar). Schädliche Anwendungen können damit bestimmen, so Sie sich befinden und so Ihren Akku zusätzlich belasten."</string>
+    <string name="permlab_accessCoarseLocation">"Ungefährer (netzwerkbasierter) Standort"</string>
+    <string name="permdesc_accessCoarseLocation">"Greift auf Quellen mit ungefähren Standortbestimmungen wie die Datenbank des Mobilfunknetzwerks zu, um falls möglich den ungefähren Standort des Telefons zu bestimmen. Schädliche Anwendungen können damit herauszufinden, wo Sie sich ungefähr befinden."</string>
+    <string name="permlab_accessSurfaceFlinger">"Auf SurfaceFlinger zugreifen"</string>
+    <string name="permdesc_accessSurfaceFlinger">"Ermöglicht einer Anwendung, die systemnahen SurfaceFlinger-Funktionen zu verwenden."</string>
+    <string name="permlab_readFrameBuffer">"Frame-Puffer lesen"</string>
+    <string name="permdesc_readFrameBuffer">"Ermöglicht einer Anwendung, den Inhalt des Frame-Puffers zu lesen."</string>
+    <string name="permlab_modifyAudioSettings">"Audio-Einstellungen ändern"</string>
+    <string name="permdesc_modifyAudioSettings">"Ermöglicht der Anwendung, Änderungen an allgemeinen Audioeinstellungen wie Lautstärke und Weiterleitung vorzunehmen."</string>
+    <string name="permlab_recordAudio">"Audio aufnehmen"</string>
+    <string name="permdesc_recordAudio">"Ermöglicht der Anwendung, auf den Pfad für Audioaufzeichnungen zuzugreifen."</string>
+    <string name="permlab_camera">"Fotos aufnehmen"</string>
+    <string name="permdesc_camera">"Ermöglicht der Anwendung, Fotos mit der Kamera aufzunehmen. So kann die Anwendung jederzeit Bilder zusammentragen, die von der Kamera erfasst werden."</string>
+    <string name="permlab_brick">"Telefon dauerhaft deaktivieren."</string>
+    <string name="permdesc_brick">"Ermöglicht der Anwendung, das gesamte Telefon dauerhaft zu deaktivieren. Dies birgt hohe Risiken."</string>
+    <string name="permlab_reboot">"Neustart des Telefons erzwingen"</string>
+    <string name="permdesc_reboot">"Ermöglicht der Anwendung, einen Neustart des Telefons zu erzwingen."</string>
+    <string name="permlab_mount_unmount_filesystems">"Dateisysteme bereitstellen oder Bereitstellung aufheben"</string>
+    <string name="permdesc_mount_unmount_filesystems">"Ermöglicht der Anwendung, Dateisysteme für austauschbare Speicherplätze bereitzustellen oder die Bereitstellung aufzuheben."</string>
+    <string name="permlab_mount_format_filesystems">"Externen Speicher formatieren"</string>
+    <string name="permdesc_mount_format_filesystems">"Erlaubt der Anwendung, austauschbaren Speicher zu formatieren."</string>
+    <string name="permlab_vibrate">"Vibrationsalarm steuern"</string>
+    <string name="permdesc_vibrate">"Ermöglicht der Anwendung, den Vibrationsalarm zu steuern."</string>
+    <string name="permlab_flashlight">"Lichtanzeige steuern"</string>
+    <string name="permdesc_flashlight">"Ermöglicht der Anwendung, die Lichtanzeige zu steuern."</string>
+    <string name="permlab_hardware_test">"Hardware testen"</string>
+    <string name="permdesc_hardware_test">"Ermöglicht einer Anwendung, verschiedene Peripherie-Geräte zu Hardware-Testzwecken zu steuern."</string>
+    <string name="permlab_callPhone">"Telefonnummern direkt anrufen"</string>
+    <string name="permdesc_callPhone">"Ermöglicht dem Anwendungen, Rufnummern ohne Ihr Eingreifen zu wählen. Schädliche Anwendungen können für unerwartete Anrufe auf Ihrer Telefonrechnung verantwortlich sein. Das Wählen von Notrufnummern ist allerdings nicht möglich."</string>
+    <string name="permlab_callPrivileged">"Alle Telefonnummern direkt anrufen"</string>
+    <string name="permdesc_callPrivileged">"Ermöglicht der Anwendung, ohne Ihr Eingreifen eine beliebige Telefonnummer zu wählen, einschließlich Notfallnummern. Schädliche Anwendungen können so unnötige und illegale Anrufe an Notdienste tätigen."</string>
+    <string name="permlab_locationUpdates">"Benachrichtigungen für Standortaktualisierung steuern"</string>
+    <string name="permdesc_locationUpdates">"Ermöglicht die Aktivierung/Deaktivierung der Radio-Benachrichtigungen über Standort-Updates. Nicht für normale Anwendungen vorgesehen."</string>
+    <string name="permlab_checkinProperties">"Auf Check-In-Eigenschaften zugreifen"</string>
+    <string name="permdesc_checkinProperties">"Ermöglicht den Schreib-/Lesezugriff auf vom Check-In-Service hochgeladene Elemente. Nicht für normale Anwendungen vorgesehen."</string>
+    <string name="permlab_bindGadget">"Gadgets auswählen"</string>
+    <string name="permdesc_bindGadget">"Bei dieser Option meldet die Anwendung dem System, welche Gadgets von welcher Anwendung verwendet werden können. Mit dieser Genehmigung können Anwendungen anderen Anwendungen Zugriff auf persönliche Daten geben. Nicht für normale Anwendungen vorgesehen."</string>
+    <string name="permlab_modifyPhoneState">"Telefonstatus ändern"</string>
+    <string name="permdesc_modifyPhoneState">"Ermöglicht einer Anwendung, die Telefonfunktionen des Gerätes zu steuern. Eine Anwendung mit dieser Berechtigung kann unter anderem das Netzwerk wechseln oder das Radio des Telefons ein- und ausschalten, ohne Sie darüber zu informieren."</string>
+    <string name="permlab_readPhoneState">"Telefonstatus lesen"</string>
+    <string name="permdesc_readPhoneState">"Ermöglicht der Anwendung, auf die Telefonfunktionen des Gerätes zuzugreifen. Eine Anwendung mit dieser Berechtigung kann unter anderem bestimmen, welche Telefonnummer dieses Telefon verwendet, ob ein Anruf aktiv ist oder mit welcher Nummer der Anrufer verbunden ist."</string>
+    <string name="permlab_wakeLock">"Standby-Modus deaktivieren"</string>
+    <string name="permdesc_wakeLock">"Ermöglicht einer Anwendung, den Standby-Modus des Telefons zu deaktivieren."</string>
+    <string name="permlab_devicePower">"Gerät ein- oder ausschalten"</string>
+    <string name="permdesc_devicePower">"Ermöglicht der Anwendung, das Telefon ein- oder auszuschalten."</string>
+    <string name="permlab_factoryTest">"In Werkstestmodus ausführen"</string>
+    <string name="permdesc_factoryTest">"Führt einen systemnahen Herstellertest durch, in dessen Rahmen auf die gesamte Telefonhardware zugegriffen werden kann. Nur verfügbar, wenn ein Telefon im Werkstestmodus ausgeführt wird."</string>
+    <string name="permlab_setWallpaper">"Hintergrundbild festlegen"</string>
+    <string name="permdesc_setWallpaper">"Ermöglicht der Anwendung, das System-Hintergrundbild festzulegen."</string>
+    <string name="permlab_setWallpaperHints">"Größenhinweise für Hintergrundbild festlegen"</string>
+    <string name="permdesc_setWallpaperHints">"Ermöglicht der Anwendung, die Größenhinweise für das Hintergrundbild festzulegen."</string>
+    <string name="permlab_masterClear">"System auf Werkseinstellung zurücksetzen"</string>
+    <string name="permdesc_masterClear">"Ermöglicht einer Anwendung, das System komplett auf Werkseinstellung zurückzusetzen. Hierbei werden alle Daten, Konfigurationen und installierten Anwendungen gelöscht."</string>
+    <string name="permlab_setTimeZone">"Zeitzone festlegen"</string>
+    <string name="permdesc_setTimeZone">"Ermöglicht einer Anwendung, die Zeitzone des Telefons zu ändern."</string>
+    <string name="permlab_getAccounts">"bekannte Konten suchen"</string>
+    <string name="permdesc_getAccounts">"Ermöglicht einer Anwendung, eine Liste der dem Telefon bekannten Konten abzurufen."</string>
+    <string name="permlab_accessNetworkState">"Netzwerkstatus anzeigen"</string>
+    <string name="permdesc_accessNetworkState">"Ermöglicht einer Anwendung, den Status aller Netzwerke anzuzeigen."</string>
+    <string name="permlab_createNetworkSockets">"Uneingeschränkter Internetzugriff"</string>
+    <string name="permdesc_createNetworkSockets">"Ermöglicht einer Anwendung, Netzwerk-Sockets einzurichten."</string>
+    <string name="permlab_writeApnSettings">"Einstellungen für Zugriffspunktname schreiben"</string>
+    <string name="permdesc_writeApnSettings">"Ermöglicht einer Anwendung, die APN-Einstellungen wie Proxy und Port eines Zugriffspunkts zu ändern."</string>
+    <string name="permlab_changeNetworkState">"Netzwerkkonnektivität ändern"</string>
+    <string name="permdesc_changeNetworkState">"Ermöglicht einer Anwendung, den Status der Netzwerkkonnektivität zu ändern."</string>
+    <string name="permlab_changeBackgroundDataSetting">"Einstellung zur Verwendung von Hintergrunddaten ändern"</string>
+    <string name="permdesc_changeBackgroundDataSetting">"Ermöglicht einer Anwendung, die Einstellung der Verwendung von Hintergrunddaten zu ändern."</string>
+    <string name="permlab_accessWifiState">"WLAN-Status anzeigen"</string>
+    <string name="permdesc_accessWifiState">"Ermöglicht einer Anwendung, die Informationen zum WLAN-Status einzusehen."</string>
+    <string name="permlab_changeWifiState">"WLAN-Status ändern"</string>
+    <string name="permdesc_changeWifiState">"Ermöglicht einer Anwendung, eine Verbindung zu den WLAN-Zugangspunkten herzustellen und diese zu trennen oder Änderungen an den konfigurierten WLAN-Netzwerken vorzunehmen."</string>
+    <string name="permlab_bluetoothAdmin">"Bluetooth-Verwaltung"</string>
+    <string name="permdesc_bluetoothAdmin">"Ermöglicht einer Anwendung, das lokale Bluetooth-Telefon zu konfigurieren, Remote-Geräte zu erkennen und eine Verbindung zu diesen herzustellen."</string>
+    <string name="permlab_bluetooth">"Bluetooth-Verbindungen herstellen"</string>
+    <string name="permdesc_bluetooth">"Ermöglicht einer Anwendung, die Konfiguration des lokalen Bluetooth-Telefons einzusehen und Verbindungen mit Partnergeräten herzustellen und zu akzeptieren."</string>
+    <string name="permlab_disableKeyguard">"Tastensperre deaktivieren"</string>
+    <string name="permdesc_disableKeyguard">"Ermöglicht einer Anwendung, die Tastensperre sowie den damit verbundenen Passwortschutz zu deaktivieren. So wird die Tastensperre vom Telefon deaktiviert, wenn ein Anruf eingeht, und nach Beendigung des Anrufs wieder aktiviert."</string>
+    <string name="permlab_readSyncSettings">"Synchronisierungseinstellungen lesen"</string>
+    <string name="permdesc_readSyncSettings">"Ermöglicht einer Anwendung, die Synchronisierungseinstellungen zu lesen, etwa ob die Synchronisierung für Kontakte aktiviert ist oder nicht."</string>
+    <string name="permlab_writeSyncSettings">"Synchronisierungseinstellungen schreiben"</string>
+    <string name="permdesc_writeSyncSettings">"Ermöglicht einer Anwendung, die Synchronisierungseinstellungen zu ändern, etwa ob die Synchronisierung für Kontakte aktiviert ist oder nicht."</string>
+    <string name="permlab_readSyncStats">"Synchronisierungsstatistiken lesen"</string>
+    <string name="permdesc_readSyncStats">"Ermöglicht einer Anwendung, die Synchronisierungsstatistiken zu lesen, etwa den Verlauf der bereits durchgeführten Synchronisierungen."</string>
+    <string name="permlab_subscribedFeedsRead">"Abonnierte Feeds lesen"</string>
+    <string name="permdesc_subscribedFeedsRead">"Ermöglicht einer Anwendung, Details zu den zurzeit synchronisierten Feeds abzurufen."</string>
+    <string name="permlab_subscribedFeedsWrite">"Abonnierte Feeds schreiben"</string>
+    <string name="permdesc_subscribedFeedsWrite">"Ermöglicht einer Anwendung, Änderungen an den kürzlich synchronisierten Feeds vorzunehmen. Schädliche Anwendungen könnten so Ihre synchronisierten Feeds ändern."</string>
+    <string name="permlab_readDictionary">"Nutzerdefiniertes Wörterbuch lesen"</string>
+    <string name="permdesc_readDictionary">"Erlaubt einer Anwendung, alle privaten Wörter, Namen und Ausdrücke zu lesen, die ein Nutzer in seinem Wörterbuch gespeichert hat."</string>
+    <string name="permlab_writeDictionary">"in nutzerdefiniertes Wörterbuch schreiben"</string>
+    <string name="permdesc_writeDictionary">"Erlaubt einer Anwendung, neue Wörter in das Wörterbuch des Nutzers zu schreiben."</string>
+  <string-array name="phoneTypes">
+    <item>"Privat"</item>
+    <item>"Mobil"</item>
+    <item>"Arbeit"</item>
+    <item>"Fax (Arbeit)"</item>
+    <item>"Fax (privat)"</item>
+    <item>"Pager"</item>
+    <item>"Andere"</item>
+    <item>"Benutzerdefiniert"</item>
+  </string-array>
+  <string-array name="emailAddressTypes">
+    <item>"Privat"</item>
+    <item>"Arbeit"</item>
+    <item>"Andere"</item>
+    <item>"Benutzerdefiniert"</item>
+  </string-array>
+  <string-array name="postalAddressTypes">
+    <item>"Privat"</item>
+    <item>"Arbeit"</item>
+    <item>"Andere"</item>
+    <item>"Benutzerdefiniert"</item>
+  </string-array>
+  <string-array name="imAddressTypes">
+    <item>"Privat"</item>
+    <item>"Arbeit"</item>
+    <item>"Andere"</item>
+    <item>"Benutzerdefiniert"</item>
+  </string-array>
+  <string-array name="organizationTypes">
+    <item>"Arbeit"</item>
+    <item>"Andere"</item>
+    <item>"Benutzerdefiniert"</item>
+  </string-array>
+  <string-array name="imProtocols">
+    <item>"AIM"</item>
+    <item>"Windows Live"</item>
+    <item>"Yahoo"</item>
+    <item>"Skype"</item>
+    <item>"QQ"</item>
+    <item>"Google Talk"</item>
+    <item>"ICQ"</item>
+    <item>"Jabber"</item>
+  </string-array>
+    <string name="keyguard_password_enter_pin_code">"PIN-Code eingeben"</string>
+    <string name="keyguard_password_wrong_pin_code">"Falscher PIN-Code!"</string>
+    <string name="keyguard_label_text">"Drücken Sie zum Entsperren auf \"Menü\" und dann auf \"0\"."</string>
+    <string name="emergency_call_dialog_number_for_display">"Notrufnummer"</string>
+    <string name="lockscreen_carrier_default">"(kein Dienst)"</string>
+    <string name="lockscreen_screen_locked">"Display gesperrt."</string>
+    <string name="lockscreen_instructions_when_pattern_enabled">"Drücken Sie auf \"Menü\", um das Telefon zu entsperren oder einen Notruf zu tätigen."</string>
+    <string name="lockscreen_instructions_when_pattern_disabled">"Drücken Sie zum Entsperren auf \"Menü\"."</string>
+    <string name="lockscreen_pattern_instructions">"Schema für Entsperrung zeichnen"</string>
+    <string name="lockscreen_emergency_call">"Notruf"</string>
+    <string name="lockscreen_pattern_correct">"Korrekt!"</string>
+    <string name="lockscreen_pattern_wrong">"Tut uns leid. Versuchen Sie es noch einmal."</string>
+    <!-- no translation found for lockscreen_plugged_in (613343852842944435) -->
+    <skip />
+    <string name="lockscreen_low_battery">"Bitte Ladegerät anschließen"</string>
+    <string name="lockscreen_missing_sim_message_short">"Keine SIM-Karte."</string>
+    <string name="lockscreen_missing_sim_message">"Keine SIM-Karte im Telefon."</string>
+    <string name="lockscreen_missing_sim_instructions">"Bitte legen Sie eine SIM-Karte ein."</string>
+    <string name="lockscreen_network_locked_message">"Netzwerk gesperrt"</string>
+    <string name="lockscreen_sim_puk_locked_message">"SIM-Karte ist gesperrt. PUK-Eingabe erforderlich."</string>
+    <string name="lockscreen_sim_puk_locked_instructions">"Wenden Sie sich an den Kunden-Support."</string>
+    <string name="lockscreen_sim_locked_message">"SIM-Karte ist gesperrt."</string>
+    <string name="lockscreen_sim_unlock_progress_dialog_message">"SIM-Karte wird entsperrt..."</string>
+    <string name="lockscreen_too_many_failed_attempts_dialog_message">"Sie haben Ihr Entsperrungsmuster <xliff:g id="NUMBER_0">%d</xliff:g>-mal falsch gezeichnet. "\n\n"Versuchen Sie es in <xliff:g id="NUMBER_1">%d</xliff:g> Sekunden erneut."</string>
+    <string name="lockscreen_failed_attempts_almost_glogin">"Sie haben Ihr Entsperrungsmuster <xliff:g id="NUMBER_0">%d</xliff:g>-mal falsch gezeichnet. Nach <xliff:g id="NUMBER_1">%d</xliff:g> weiteren erfolglosen Versuchen werden Sie aufgefordert, Ihr Telefon mithilfe Ihrer Google-Anmeldeinformationen zu entsperren. "\n\n"Versuchen Sie es in <xliff:g id="NUMBER_2">%d</xliff:g> Sekunden erneut."</string>
+    <string name="lockscreen_too_many_failed_attempts_countdown">"Versuchen Sie es in <xliff:g id="NUMBER">%d</xliff:g> Sekunden erneut."</string>
+    <string name="lockscreen_forgot_pattern_button_text">"Muster vergessen?"</string>
+    <string name="lockscreen_glogin_too_many_attempts">"Zu viele Versuche!"</string>
+    <string name="lockscreen_glogin_instructions">"Melden Sie sich zum Entsperren"\n"mit Ihrem Google-Konto an."</string>
+    <string name="lockscreen_glogin_username_hint">"Nutzername (E-Mail)"</string>
+    <string name="lockscreen_glogin_password_hint">"Passwort"</string>
+    <string name="lockscreen_glogin_submit_button">"Anmelden"</string>
+    <string name="lockscreen_glogin_invalid_input">"Ungültiger  Nutzername oder ungültiges Passwort."</string>
+    <string name="status_bar_time_format">"<xliff:g id="HOUR">h</xliff:g>:<xliff:g id="MINUTE">mm</xliff:g> <xliff:g id="AMPM">AA</xliff:g>"</string>
+    <string name="hour_minute_ampm">"<xliff:g id="HOUR">%-l</xliff:g>:<xliff:g id="MINUTE">%M</xliff:g><xliff:g id="AMPM">%P</xliff:g>"</string>
+    <string name="hour_minute_cap_ampm">"<xliff:g id="HOUR">%-l</xliff:g>:<xliff:g id="MINUTE">%M</xliff:g><xliff:g id="AMPM">%p</xliff:g>"</string>
+    <!-- no translation found for hour_ampm (4329881288269772723) -->
+    <skip />
+    <!-- no translation found for hour_cap_ampm (1829009197680861107) -->
+    <skip />
+    <string name="status_bar_clear_all_button">"Benachrichtigungen löschen"</string>
+    <string name="status_bar_no_notifications_title">"Keine Benachrichtigungen"</string>
+    <string name="status_bar_ongoing_events_title">"Aktuell"</string>
+    <string name="status_bar_latest_events_title">"Benachrichtigungen"</string>
+    <!-- no translation found for battery_status_text_percent_format (7660311274698797147) -->
+    <skip />
+    <string name="battery_status_charging">"Wird aufgeladen..."</string>
+    <string name="battery_low_title">"Ladegerät anschließen"</string>
+    <string name="battery_low_subtitle">"Akku ist fast leer."</string>
+    <string name="battery_low_percent_format">"Nur noch weniger als <xliff:g id="NUMBER">%d%%</xliff:g> vorhanden."</string>
+    <string name="factorytest_failed">"Werkstest fehlgeschlagen"</string>
+    <string name="factorytest_not_system">"Die Aktion FACTORY_TEST wird nur für unter \"/system/app\" gespeicherte Pakete unterstützt."</string>
+    <string name="factorytest_no_action">"Es wurden kein Paket mit der Aktion FACTORY_TEST gefunden."</string>
+    <string name="factorytest_reboot">"Neu booten"</string>
+    <string name="js_dialog_title">"Die Seite auf \'<xliff:g id="TITLE">%s</xliff:g>\' sagt:"</string>
+    <string name="js_dialog_title_default">"JavaScript"</string>
+    <string name="js_dialog_before_unload">"Von dieser Seite navigieren?"\n\n"<xliff:g id="MESSAGE">%s</xliff:g>"\n\n"Wählen Sie \"OK\", um fortzufahren, oder wählen Sie \"Abbrechen\", um auf der aktuellen Seite zu bleiben."</string>
+    <string name="save_password_label">"Bestätigen"</string>
+    <string name="save_password_message">"Möchten Sie, dass der Browser dieses Passwort speichert?"</string>
+    <string name="save_password_notnow">"Nicht jetzt"</string>
+    <string name="save_password_remember">"Speichern"</string>
+    <string name="save_password_never">"Niemals"</string>
+    <string name="open_permission_deny">"Sie sind zum Öffnen dieser Seite nicht berechtigt."</string>
+    <string name="text_copied">"Text in Zwischenablage kopiert."</string>
+    <string name="more_item_label">"Mehr"</string>
+    <string name="prepend_shortcut_label">"Menü+"</string>
+    <string name="menu_space_shortcut_label">"Leerzeichen"</string>
+    <string name="menu_enter_shortcut_label">"Enter"</string>
+    <string name="menu_delete_shortcut_label">"löschen"</string>
+    <string name="search_go">"Suche"</string>
+    <string name="today">"Heute"</string>
+    <string name="yesterday">"Gestern"</string>
+    <string name="tomorrow">"Morgen"</string>
+    <string name="oneMonthDurationPast">"Vor 1 Monat"</string>
+    <string name="beforeOneMonthDurationPast">"Vor mehr als 1 Monat"</string>
+  <plurals name="num_seconds_ago">
+    <item quantity="one">"Vor 1 Sekunde"</item>
+    <item quantity="other">"Vor <xliff:g id="COUNT">%d</xliff:g> Sekunden"</item>
+  </plurals>
+  <plurals name="num_minutes_ago">
+    <item quantity="one">"Vor 1 Minute"</item>
+    <item quantity="other">"Vor <xliff:g id="COUNT">%d</xliff:g> Minuten"</item>
+  </plurals>
+  <plurals name="num_hours_ago">
+    <item quantity="one">"Vor 1 Stunde"</item>
+    <item quantity="other">"Vor <xliff:g id="COUNT">%d</xliff:g> Stunden"</item>
+  </plurals>
+  <plurals name="num_days_ago">
+    <item quantity="one">"Gestern"</item>
+    <item quantity="other">"Vor <xliff:g id="COUNT">%d</xliff:g> Tagen"</item>
+  </plurals>
+  <plurals name="in_num_seconds">
+    <item quantity="one">"in 1 Sekunde"</item>
+    <item quantity="other">"in <xliff:g id="COUNT">%d</xliff:g> Sekunden"</item>
+  </plurals>
+  <plurals name="in_num_minutes">
+    <item quantity="one">"in 1 Minute"</item>
+    <item quantity="other">"in <xliff:g id="COUNT">%d</xliff:g> Minuten"</item>
+  </plurals>
+  <plurals name="in_num_hours">
+    <item quantity="one">"in 1 Stunde"</item>
+    <item quantity="other">"In <xliff:g id="COUNT">%d</xliff:g> Stunden"</item>
+  </plurals>
+  <plurals name="in_num_days">
+    <item quantity="one">"morgen"</item>
+    <item quantity="other">"in <xliff:g id="COUNT">%d</xliff:g> Tagen"</item>
+  </plurals>
+  <plurals name="abbrev_num_seconds_ago">
+    <item quantity="one">"vor 1 Sekunde"</item>
+    <item quantity="other">"Vor <xliff:g id="COUNT">%d</xliff:g> Sekunden"</item>
+  </plurals>
+  <plurals name="abbrev_num_minutes_ago">
+    <item quantity="one">"vor 1 Minute"</item>
+    <item quantity="other">"vor <xliff:g id="COUNT">%d</xliff:g> Minuten"</item>
+  </plurals>
+  <plurals name="abbrev_num_hours_ago">
+    <item quantity="one">"Vor 1 Stunde"</item>
+    <item quantity="other">"Vor <xliff:g id="COUNT">%d</xliff:g> Stunden"</item>
+  </plurals>
+  <plurals name="abbrev_num_days_ago">
+    <item quantity="one">"Gestern"</item>
+    <item quantity="other">"Vor <xliff:g id="COUNT">%d</xliff:g> Tagen"</item>
+  </plurals>
+  <plurals name="abbrev_in_num_seconds">
+    <item quantity="one">"in 1 Sekunde"</item>
+    <item quantity="other">"in <xliff:g id="COUNT">%d</xliff:g> Sekunden"</item>
+  </plurals>
+  <plurals name="abbrev_in_num_minutes">
+    <item quantity="one">"in 1 Minute"</item>
+    <item quantity="other">"in <xliff:g id="COUNT">%d</xliff:g> Minuten"</item>
+  </plurals>
+  <plurals name="abbrev_in_num_hours">
+    <item quantity="one">"in 1 Stunde"</item>
+    <item quantity="other">"In <xliff:g id="COUNT">%d</xliff:g> Stunden"</item>
+  </plurals>
+  <plurals name="abbrev_in_num_days">
+    <item quantity="one">"morgen"</item>
+    <item quantity="other">"in <xliff:g id="COUNT">%d</xliff:g> Tagen"</item>
+  </plurals>
+    <string name="preposition_for_date">"am %s"</string>
+    <string name="preposition_for_time">"am %s"</string>
+    <string name="preposition_for_year">"in %s"</string>
+    <string name="day">"Tag"</string>
+    <string name="days">"Tage"</string>
+    <string name="hour">"Stunde"</string>
+    <string name="hours">"Stunden"</string>
+    <string name="minute">"Min"</string>
+    <string name="minutes">"Minuten"</string>
+    <string name="second">"Sek"</string>
+    <string name="seconds">"s"</string>
+    <string name="week">"Woche"</string>
+    <string name="weeks">"Wochen"</string>
+    <string name="year">"Jahr"</string>
+    <string name="years">"Jahre"</string>
+    <string name="sunday">"Sonntag"</string>
+    <string name="monday">"Montag"</string>
+    <string name="tuesday">"Dienstag"</string>
+    <string name="wednesday">"Mittwoch"</string>
+    <string name="thursday">"Donnerstag"</string>
+    <string name="friday">"Freitag"</string>
+    <string name="saturday">"Samstag"</string>
+    <string name="every_weekday">"Jeden Wochentag (Mo-Fr)"</string>
+    <string name="daily">"Täglich"</string>
+    <string name="weekly">"Jede Woche am <xliff:g id="DAY">%s</xliff:g>"</string>
+    <string name="monthly">"Monatlich"</string>
+    <string name="yearly">"Jährlich"</string>
+    <string name="VideoView_error_title">"Video kann nicht wiedergegeben werden."</string>
+    <string name="VideoView_error_text_unknown">"Dieses Video kann leider nicht abgespielt werden."</string>
+    <string name="VideoView_error_button">"OK"</string>
+    <string name="am">"AM"</string>
+    <string name="pm">".."</string>
+    <string name="numeric_date">"<xliff:g id="DAY">%d</xliff:g>/<xliff:g id="MONTH">%m</xliff:g>/<xliff:g id="YEAR">%Y</xliff:g>"</string>
+    <string name="wday1_date1_time1_wday2_date2_time2">"<xliff:g id="WEEKDAY1">%1$s</xliff:g>, <xliff:g id="DATE1">%2$s</xliff:g>, <xliff:g id="TIME1">%3$s</xliff:g> – <xliff:g id="WEEKDAY2">%4$s</xliff:g>, <xliff:g id="DATE2">%5$s</xliff:g>, <xliff:g id="TIME2">%6$s</xliff:g>"</string>
+    <string name="wday1_date1_wday2_date2">"<xliff:g id="WEEKDAY1">%1$s</xliff:g>, <xliff:g id="DATE1">%2$s</xliff:g> – <xliff:g id="WEEKDAY2">%4$s</xliff:g>, <xliff:g id="DATE2">%5$s</xliff:g>"</string>
+    <string name="date1_time1_date2_time2">"<xliff:g id="DATE1">%2$s</xliff:g>, <xliff:g id="TIME1">%3$s</xliff:g> – <xliff:g id="DATE2">%5$s</xliff:g>, <xliff:g id="TIME2">%6$s</xliff:g>"</string>
+    <string name="date1_date2">"<xliff:g id="DATE1">%2$s</xliff:g> – <xliff:g id="DATE2">%5$s</xliff:g>"</string>
+    <string name="time1_time2">"<xliff:g id="TIME1">%1$s</xliff:g> – <xliff:g id="TIME2">%2$s</xliff:g>"</string>
+    <string name="time_wday_date">"<xliff:g id="TIME_RANGE">%1$s</xliff:g>, <xliff:g id="WEEKDAY">%2$s</xliff:g>, <xliff:g id="DATE">%3$s</xliff:g>"</string>
+    <string name="wday_date">"<xliff:g id="WEEKDAY">%2$s</xliff:g>, <xliff:g id="DATE">%3$s</xliff:g>"</string>
+    <string name="time_date">"<xliff:g id="TIME_RANGE">%1$s</xliff:g>, <xliff:g id="DATE">%3$s</xliff:g>"</string>
+    <string name="date_time">"<xliff:g id="DATE">%1$s</xliff:g>, <xliff:g id="TIME">%2$s</xliff:g>"</string>
+    <string name="relative_time">"<xliff:g id="DATE">%1$s</xliff:g>, <xliff:g id="TIME">%2$s</xliff:g>"</string>
+    <string name="time_wday">"<xliff:g id="TIME_RANGE">%1$s</xliff:g>, <xliff:g id="WEEKDAY">%2$s</xliff:g>"</string>
+    <string name="full_date_month_first" format="date">"<xliff:g id="DAY">d</xliff:g>'. '<xliff:g id="MONTH">MMMM</xliff:g>' '<xliff:g id="YEAR">yyyy</xliff:g>"</string>
+    <string name="full_date_day_first" format="date">"<xliff:g id="DAY">d</xliff:g>'. '<xliff:g id="MONTH">MMMM</xliff:g>' '<xliff:g id="YEAR">yyyy</xliff:g>"</string>
+    <string name="medium_date_month_first" format="date">"<xliff:g id="DAY">d</xliff:g>'. '<xliff:g id="MONTH">MMM</xliff:g>' '<xliff:g id="YEAR">yyyy</xliff:g>"</string>
+    <string name="medium_date_day_first" format="date">"<xliff:g id="DAY">d</xliff:g>'. '<xliff:g id="MONTH">MMM</xliff:g>' '<xliff:g id="YEAR">yyyy</xliff:g>"</string>
+    <string name="twelve_hour_time_format" format="date">"<xliff:g id="HOUR">h</xliff:g>':'<xliff:g id="MINUTE">mm</xliff:g>' '<xliff:g id="AMPM">a</xliff:g>"</string>
+    <string name="twenty_four_hour_time_format" format="date">"<xliff:g id="HOUR">H</xliff:g>':'<xliff:g id="MINUTE">mm</xliff:g>"</string>
+    <string name="noon">"Mittag"</string>
+    <string name="Noon">"Mittag"</string>
+    <string name="midnight">"Mitternacht"</string>
+    <string name="Midnight">"Mitternacht"</string>
+    <!-- no translation found for month_day (3693060561170538204) -->
+    <skip />
+    <!-- no translation found for month (1976700695144952053) -->
+    <skip />
+    <string name="month_day_year">"<xliff:g id="DAY">%-d</xliff:g>. <xliff:g id="MONTH">%B</xliff:g> <xliff:g id="YEAR">%Y</xliff:g>"</string>
+    <!-- no translation found for month_year (2106203387378728384) -->
+    <skip />
+    <string name="time_of_day">"<xliff:g id="HOUR">%H</xliff:g>:<xliff:g id="MINUTE">%M</xliff:g>:<xliff:g id="SECOND">%S</xliff:g>"</string>
+    <string name="date_and_time">"<xliff:g id="HOUR">%H</xliff:g>:<xliff:g id="MINUTE">%M</xliff:g>:<xliff:g id="SECOND">%S</xliff:g> <xliff:g id="DAY">%-d</xliff:g>. <xliff:g id="MONTH">%B</xliff:g> <xliff:g id="YEAR">%Y</xliff:g>"</string>
+    <string name="same_year_md1_md2">"<xliff:g id="DAY1">%3$s</xliff:g>. <xliff:g id="MONTH1">%2$s</xliff:g> –  <xliff:g id="DAY2">%8$s</xliff:g>. <xliff:g id="MONTH2">%7$s</xliff:g>"</string>
+    <string name="same_year_wday1_md1_wday2_md2">"<xliff:g id="WEEKDAY1">%1$s</xliff:g>,  <xliff:g id="DAY1_0">%3$s</xliff:g>. <xliff:g id="MONTH1">%2$s</xliff:g> – <xliff:g id="WEEKDAY2">%6$s</xliff:g>, <xliff:g id="DAY2_1">%8$s</xliff:g>. <xliff:g id="MONTH2">%7$s</xliff:g>"</string>
+    <string name="same_year_mdy1_mdy2">"<xliff:g id="DAY1">%3$s</xliff:g>. <xliff:g id="MONTH1">%2$s</xliff:g> – <xliff:g id="DAY2">%8$s</xliff:g>. <xliff:g id="MONTH2">%7$s</xliff:g> <xliff:g id="YEAR">%9$s</xliff:g>"</string>
+    <string name="same_year_wday1_mdy1_wday2_mdy2">"<xliff:g id="WEEKDAY1">%1$s</xliff:g>, <xliff:g id="DAY1_0">%3$s</xliff:g>. <xliff:g id="MONTH1">%2$s</xliff:g> – <xliff:g id="WEEKDAY2">%6$s</xliff:g>, <xliff:g id="DAY2_1">%8$s</xliff:g>. <xliff:g id="MONTH2">%7$s</xliff:g> <xliff:g id="YEAR">%9$s</xliff:g>"</string>
+    <string name="same_year_md1_time1_md2_time2">"<xliff:g id="DAY1">%3$s</xliff:g>. <xliff:g id="MONTH1">%2$s</xliff:g> <xliff:g id="TIME1">%5$s</xliff:g> – <xliff:g id="DAY2">%8$s</xliff:g>. <xliff:g id="MONTH2">%7$s</xliff:g> <xliff:g id="TIME2">%10$s</xliff:g>"</string>
+    <string name="same_year_wday1_md1_time1_wday2_md2_time2">"<xliff:g id="WEEKDAY1">%1$s</xliff:g>, <xliff:g id="DAY1_0">%3$s</xliff:g>. <xliff:g id="MONTH1">%2$s</xliff:g>, <xliff:g id="TIME1">%5$s</xliff:g> – <xliff:g id="WEEKDAY2">%6$s</xliff:g>, <xliff:g id="DAY2_1">%8$s</xliff:g>. <xliff:g id="MONTH2">%7$s</xliff:g> <xliff:g id="TIME2">%10$s</xliff:g>"</string>
+    <string name="same_year_mdy1_time1_mdy2_time2">"<xliff:g id="DAY1">%3$s</xliff:g>. <xliff:g id="MONTH1">%2$s</xliff:g>, <xliff:g id="YEAR1">%4$s</xliff:g>, <xliff:g id="TIME1">%5$s</xliff:g> – <xliff:g id="DAY2">%8$s</xliff:g>. <xliff:g id="MONTH2">%7$s</xliff:g> <xliff:g id="YEAR2">%9$s</xliff:g>, <xliff:g id="TIME2">%10$s</xliff:g>"</string>
+    <string name="same_year_wday1_mdy1_time1_wday2_mdy2_time2">"<xliff:g id="WEEKDAY1">%1$s</xliff:g>, <xliff:g id="DAY1_0">%3$s</xliff:g>. <xliff:g id="MONTH1">%2$s</xliff:g> <xliff:g id="YEAR1">%4$s</xliff:g>, <xliff:g id="TIME1">%5$s</xliff:g> – <xliff:g id="WEEKDAY2">%6$s</xliff:g>, <xliff:g id="DAY2_1">%8$s</xliff:g>. <xliff:g id="MONTH2">%7$s</xliff:g> <xliff:g id="YEAR2">%9$s</xliff:g>, <xliff:g id="TIME2">%10$s</xliff:g>"</string>
+    <string name="numeric_md1_md2">"<xliff:g id="DAY1">%3$s</xliff:g>/<xliff:g id="MONTH1">%2$s</xliff:g> – <xliff:g id="DAY2">%8$s</xliff:g>/<xliff:g id="MONTH2">%7$s</xliff:g>"</string>
+    <string name="numeric_wday1_md1_wday2_md2">"<xliff:g id="WEEKDAY1">%1$s</xliff:g>, <xliff:g id="DAY1_0">%3$s</xliff:g>/<xliff:g id="MONTH1">%2$s</xliff:g> – <xliff:g id="WEEKDAY2">%6$s</xliff:g>, <xliff:g id="DAY2_1">%8$s</xliff:g>/<xliff:g id="MONTH2">%7$s</xliff:g>"</string>
+    <string name="numeric_mdy1_mdy2">"<xliff:g id="DAY1">%3$s</xliff:g>/<xliff:g id="MONTH1">%2$s</xliff:g>/<xliff:g id="YEAR1">%4$s</xliff:g> – <xliff:g id="DAY2">%8$s</xliff:g>/<xliff:g id="MONTH2">%7$s</xliff:g>/<xliff:g id="YEAR2">%9$s</xliff:g>"</string>
+    <string name="numeric_wday1_mdy1_wday2_mdy2">"<xliff:g id="WEEKDAY1">%1$s</xliff:g>, <xliff:g id="DAY1_0">%3$s</xliff:g>/<xliff:g id="MONTH1">%2$s</xliff:g>/<xliff:g id="YEAR1">%4$s</xliff:g> – <xliff:g id="WEEKDAY2">%6$s</xliff:g>, <xliff:g id="DAY2_1">%8$s</xliff:g>/<xliff:g id="MONTH2">%7$s</xliff:g>/<xliff:g id="YEAR2">%9$s</xliff:g>"</string>
+    <string name="numeric_md1_time1_md2_time2">"<xliff:g id="DAY1">%3$s</xliff:g>/<xliff:g id="MONTH1">%2$s</xliff:g>, <xliff:g id="TIME1">%5$s</xliff:g> – <xliff:g id="DAY2">%8$s</xliff:g>/<xliff:g id="MONTH2">%7$s</xliff:g>, <xliff:g id="TIME2">%10$s</xliff:g>"</string>
+    <string name="numeric_wday1_md1_time1_wday2_md2_time2">"<xliff:g id="WEEKDAY1">%1$s</xliff:g>, <xliff:g id="DAY1_0">%3$s</xliff:g>/<xliff:g id="MONTH1">%2$s</xliff:g>, <xliff:g id="TIME1">%5$s</xliff:g> – <xliff:g id="WEEKDAY2">%6$s</xliff:g>, <xliff:g id="DAY2_1">%8$s</xliff:g>/<xliff:g id="MONTH2">%7$s</xliff:g>, <xliff:g id="TIME2">%10$s</xliff:g>"</string>
+    <string name="numeric_mdy1_time1_mdy2_time2">"<xliff:g id="DAY1">%3$s</xliff:g>/<xliff:g id="MONTH1">%2$s</xliff:g>/<xliff:g id="YEAR1">%4$s</xliff:g>, <xliff:g id="TIME1">%5$s</xliff:g> – <xliff:g id="DAY2">%8$s</xliff:g>/<xliff:g id="MONTH2">%7$s</xliff:g>/<xliff:g id="YEAR2">%9$s</xliff:g>, <xliff:g id="TIME2">%10$s</xliff:g>"</string>
+    <string name="numeric_wday1_mdy1_time1_wday2_mdy2_time2">"<xliff:g id="WEEKDAY1">%1$s</xliff:g>, <xliff:g id="DAY1_0">%3$s</xliff:g>/<xliff:g id="MONTH1">%2$s</xliff:g>/<xliff:g id="YEAR1">%4$s</xliff:g>, <xliff:g id="TIME1">%5$s</xliff:g> – <xliff:g id="WEEKDAY2">%6$s</xliff:g>, <xliff:g id="DAY2_1">%8$s</xliff:g>/<xliff:g id="MONTH2">%7$s</xliff:g>/<xliff:g id="YEAR2">%9$s</xliff:g>, <xliff:g id="TIME2">%10$s</xliff:g>"</string>
+    <string name="same_month_md1_md2">"<xliff:g id="DAY1">%3$s</xliff:g>. – <xliff:g id="DAY2">%8$s</xliff:g>. <xliff:g id="MONTH1">%2$s</xliff:g>"</string>
+    <string name="same_month_wday1_md1_wday2_md2">"<xliff:g id="WEEKDAY1">%1$s</xliff:g>, <xliff:g id="DAY1_0">%3$s</xliff:g>. <xliff:g id="MONTH1">%2$s</xliff:g> – <xliff:g id="WEEKDAY2">%6$s</xliff:g>, <xliff:g id="DAY2_1">%8$s</xliff:g>. <xliff:g id="MONTH2">%7$s</xliff:g>"</string>
+    <string name="same_month_mdy1_mdy2">"<xliff:g id="DAY1">%3$s</xliff:g>. – <xliff:g id="DAY2">%8$s</xliff:g>. <xliff:g id="MONTH1">%2$s</xliff:g> <xliff:g id="YEAR2">%9$s</xliff:g>"</string>
+    <string name="same_month_wday1_mdy1_wday2_mdy2">"<xliff:g id="WEEKDAY1">%1$s</xliff:g>, <xliff:g id="DAY1_0">%3$s</xliff:g>. <xliff:g id="MONTH1">%2$s</xliff:g> <xliff:g id="YEAR1">%4$s</xliff:g> – <xliff:g id="WEEKDAY2">%6$s</xliff:g>, <xliff:g id="DAY2_1">%8$s</xliff:g>. <xliff:g id="MONTH2">%7$s</xliff:g> <xliff:g id="YEAR2">%9$s</xliff:g>"</string>
+    <string name="same_month_md1_time1_md2_time2">"<xliff:g id="DAY1">%3$s</xliff:g>. <xliff:g id="MONTH1">%2$s</xliff:g>, <xliff:g id="TIME1">%5$s</xliff:g> – <xliff:g id="DAY2">%8$s</xliff:g>. <xliff:g id="MONTH2">%7$s</xliff:g>, <xliff:g id="TIME2">%10$s</xliff:g>"</string>
+    <string name="same_month_wday1_md1_time1_wday2_md2_time2">"<xliff:g id="WEEKDAY1">%1$s</xliff:g>, <xliff:g id="DAY1_0">%3$s</xliff:g>. <xliff:g id="MONTH1">%2$s</xliff:g>, <xliff:g id="TIME1">%5$s</xliff:g> – <xliff:g id="WEEKDAY2">%6$s</xliff:g>, <xliff:g id="DAY2_1">%8$s</xliff:g>. <xliff:g id="MONTH2">%7$s</xliff:g>, <xliff:g id="TIME2">%10$s</xliff:g>"</string>
+    <string name="same_month_mdy1_time1_mdy2_time2">"<xliff:g id="DAY1">%3$s</xliff:g>. <xliff:g id="MONTH1">%2$s</xliff:g> <xliff:g id="YEAR1">%4$s</xliff:g>, <xliff:g id="TIME1">%5$s</xliff:g> – <xliff:g id="DAY2">%8$s</xliff:g>. <xliff:g id="MONTH2">%7$s</xliff:g> <xliff:g id="YEAR2">%9$s</xliff:g>, <xliff:g id="TIME2">%10$s</xliff:g>"</string>
+    <string name="same_month_wday1_mdy1_time1_wday2_mdy2_time2">"<xliff:g id="WEEKDAY1">%1$s</xliff:g>, <xliff:g id="DAY1_0">%3$s</xliff:g>. <xliff:g id="MONTH1">%2$s</xliff:g> <xliff:g id="YEAR1">%4$s</xliff:g>, <xliff:g id="TIME1">%5$s</xliff:g> – <xliff:g id="WEEKDAY2">%6$s</xliff:g>, <xliff:g id="DAY2_1">%8$s</xliff:g>. <xliff:g id="MONTH2">%7$s</xliff:g> <xliff:g id="YEAR2">%9$s</xliff:g>, <xliff:g id="TIME2">%10$s</xliff:g>"</string>
+    <string name="abbrev_month_day_year">"<xliff:g id="DAY">%-d</xliff:g>. <xliff:g id="MONTH">%b</xliff:g> <xliff:g id="YEAR">%Y</xliff:g>"</string>
+    <!-- no translation found for abbrev_month_year (5966980891147982768) -->
+    <skip />
+    <!-- no translation found for abbrev_month_day (3156047263406783231) -->
+    <skip />
+    <!-- no translation found for abbrev_month (7304935052615731208) -->
+    <skip />
+    <string name="day_of_week_long_sunday">"Sonntag"</string>
+    <string name="day_of_week_long_monday">"Montag"</string>
+    <string name="day_of_week_long_tuesday">"Dienstag"</string>
+    <string name="day_of_week_long_wednesday">"Mittwoch"</string>
+    <string name="day_of_week_long_thursday">"Donnerstag"</string>
+    <string name="day_of_week_long_friday">"Freitag"</string>
+    <string name="day_of_week_long_saturday">"Samstag"</string>
+    <string name="day_of_week_medium_sunday">"So"</string>
+    <string name="day_of_week_medium_monday">"Mo"</string>
+    <string name="day_of_week_medium_tuesday">"Di"</string>
+    <string name="day_of_week_medium_wednesday">"Mi"</string>
+    <string name="day_of_week_medium_thursday">"Do"</string>
+    <string name="day_of_week_medium_friday">"Fr"</string>
+    <string name="day_of_week_medium_saturday">"Sa"</string>
+    <string name="day_of_week_short_sunday">"So"</string>
+    <string name="day_of_week_short_monday">"Mo"</string>
+    <string name="day_of_week_short_tuesday">"Di"</string>
+    <string name="day_of_week_short_wednesday">"Mi"</string>
+    <string name="day_of_week_short_thursday">"Do"</string>
+    <string name="day_of_week_short_friday">"Fr"</string>
+    <string name="day_of_week_short_saturday">"Sa"</string>
+    <string name="day_of_week_shorter_sunday">"So"</string>
+    <string name="day_of_week_shorter_monday">"März"</string>
+    <string name="day_of_week_shorter_tuesday">"Di"</string>
+    <string name="day_of_week_shorter_wednesday">"Mi"</string>
+    <string name="day_of_week_shorter_thursday">"Do"</string>
+    <string name="day_of_week_shorter_friday">"Fr"</string>
+    <string name="day_of_week_shorter_saturday">"Sa"</string>
+    <string name="day_of_week_shortest_sunday">"Sep"</string>
+    <string name="day_of_week_shortest_monday">"Mo"</string>
+    <string name="day_of_week_shortest_tuesday">"Do"</string>
+    <string name="day_of_week_shortest_wednesday">"Mi"</string>
+    <string name="day_of_week_shortest_thursday">"Do"</string>
+    <string name="day_of_week_shortest_friday">"Fr"</string>
+    <string name="day_of_week_shortest_saturday">"Sa"</string>
+    <string name="month_long_january">"Januar"</string>
+    <string name="month_long_february">"Februar"</string>
+    <string name="month_long_march">"März"</string>
+    <string name="month_long_april">"April"</string>
+    <string name="month_long_may">"Mai"</string>
+    <string name="month_long_june">"Juni"</string>
+    <string name="month_long_july">"Juli"</string>
+    <string name="month_long_august">"August"</string>
+    <string name="month_long_september">"September"</string>
+    <string name="month_long_october">"Oktober"</string>
+    <string name="month_long_november">"November"</string>
+    <string name="month_long_december">"Dezember"</string>
+    <string name="month_medium_january">"Jan."</string>
+    <string name="month_medium_february">"Feb."</string>
+    <string name="month_medium_march">"März"</string>
+    <string name="month_medium_april">"Apr."</string>
+    <string name="month_medium_may">"Mai"</string>
+    <string name="month_medium_june">"Juni"</string>
+    <string name="month_medium_july">"Juli"</string>
+    <string name="month_medium_august">"Aug"</string>
+    <string name="month_medium_september">"Sep."</string>
+    <string name="month_medium_october">"Okt."</string>
+    <string name="month_medium_november">"Nov."</string>
+    <string name="month_medium_december">"Dez."</string>
+    <string name="month_shortest_january">"Juli"</string>
+    <string name="month_shortest_february">"Fr"</string>
+    <string name="month_shortest_march">"März"</string>
+    <string name="month_shortest_april">"Apr"</string>
+    <string name="month_shortest_may">"Mo"</string>
+    <string name="month_shortest_june">"Juni"</string>
+    <string name="month_shortest_july">"Juli"</string>
+    <string name="month_shortest_august">"Aug."</string>
+    <string name="month_shortest_september">"Sep"</string>
+    <string name="month_shortest_october">"Okt."</string>
+    <string name="month_shortest_november">"No"</string>
+    <string name="month_shortest_december">"Dez."</string>
+    <string name="elapsed_time_short_format_mm_ss">"<xliff:g id="MINUTES">%1$02d</xliff:g>:<xliff:g id="SECONDS">%2$02d</xliff:g>"</string>
+    <string name="elapsed_time_short_format_h_mm_ss">"<xliff:g id="HOURS">%1$d</xliff:g>:<xliff:g id="MINUTES">%2$02d</xliff:g>:<xliff:g id="SECONDS">%3$02d</xliff:g>"</string>
+    <string name="selectAll">"Alles auswählen"</string>
+    <string name="selectText">"Text auswählen"</string>
+    <string name="stopSelectingText">"Textauswahl beenden"</string>
+    <string name="cut">"Ausschneiden"</string>
+    <string name="cutAll">"Alles ausschneiden"</string>
+    <string name="copy">"Kopieren"</string>
+    <string name="copyAll">"Alles kopieren"</string>
+    <string name="paste">"Einfügen"</string>
+    <string name="copyUrl">"URL kopieren"</string>
+    <string name="inputMethod">"Eingabemethode"</string>
+    <string name="addToDictionary">"\"%s\" dem Wörterbuch hinzufügen"</string>
+    <string name="editTextMenuTitle">"Text bearbeiten"</string>
+    <string name="low_internal_storage_view_title">"Geringer Speicher"</string>
+    <string name="low_internal_storage_view_text">"Kaum noch freier Telefonspeicher verfügbar."</string>
+    <string name="ok">"OK"</string>
+    <string name="cancel">"Abbrechen"</string>
+    <string name="yes">"OK"</string>
+    <string name="no">"Abbrechen"</string>
+    <string name="dialog_alert_title">"Achtung"</string>
+    <string name="capital_on">"EIN"</string>
+    <string name="capital_off">"AUS"</string>
+    <string name="whichApplication">"Aktion beenden mit"</string>
+    <string name="alwaysUse">"Standardmäßig für diese Aktion verwenden."</string>
+    <string name="clearDefaultHintMsg">"Löschen Sie die Standardeinstellungen unter \"Starteinstellungen &gt; Anwendungen &gt; Anwendungen verwalten\"."</string>
+    <string name="chooseActivity">"Aktion auswählen"</string>
+    <string name="noApplications">"Diese Aktion kann von keiner Anwendung ausgeführt werden."</string>
+    <string name="aerr_title">"Tut uns leid!"</string>
+    <string name="aerr_application">"Die Anwendung <xliff:g id="APPLICATION">%1$s</xliff:g> (Prozess <xliff:g id="PROCESS">%2$s</xliff:g>) wurde unerwartet beendet. Versuchen Sie es erneut."</string>
+    <string name="aerr_process">"Der Prozess <xliff:g id="PROCESS">%1$s</xliff:g> wurde unerwartet beendet. Versuchen Sie es erneut."</string>
+    <string name="anr_title">"Tut uns leid!"</string>
+    <string name="anr_activity_application">"Aktivität <xliff:g id="ACTIVITY">%1$s</xliff:g> (in Anwendung <xliff:g id="APPLICATION">%2$s</xliff:g>) reagiert nicht."</string>
+    <string name="anr_activity_process">"Aktivität <xliff:g id="ACTIVITY">%1$s</xliff:g> (in Prozess <xliff:g id="PROCESS">%2$s</xliff:g>) reagiert nicht."</string>
+    <string name="anr_application_process">"Anwendung <xliff:g id="APPLICATION">%1$s</xliff:g> (in Prozess <xliff:g id="PROCESS">%2$s</xliff:g>) reagiert nicht."</string>
+    <string name="anr_process">"Prozess <xliff:g id="PROCESS">%1$s</xliff:g> reagiert nicht."</string>
+    <string name="force_close">"Schließen erzwingen"</string>
+    <string name="wait">"Warten"</string>
+    <string name="debug">"Fehler suchen"</string>
+    <string name="sendText">"Aktion für Text auswählen"</string>
+    <string name="volume_ringtone">"Klingeltonlautstärke"</string>
+    <string name="volume_music">"Medienlautstärke"</string>
+    <string name="volume_music_hint_playing_through_bluetooth">"Wiedergabe durch Bluetooth"</string>
+    <string name="volume_call">"Hörerlautstärke"</string>
+    <string name="volume_bluetooth_call">"Lautstärke bei eingehendem Bluetooth-Anruf"</string>
+    <string name="volume_alarm">"Lautstärke für Alarm"</string>
+    <string name="volume_notification">"Benachrichtigungslautstärke"</string>
+    <string name="volume_unknown">"Lautstärke"</string>
+    <string name="ringtone_default">"Standard-Klingelton"</string>
+    <string name="ringtone_default_with_actual">"Standard-Klingelton (<xliff:g id="ACTUAL_RINGTONE">%1$s</xliff:g>)"</string>
+    <string name="ringtone_silent">"Lautlos"</string>
+    <string name="ringtone_picker_title">"Klingeltöne"</string>
+    <string name="ringtone_unknown">"Unbekannter Klingelton"</string>
+  <plurals name="wifi_available">
+    <item quantity="one">"WLAN-Netzwerk verfügbar"</item>
+    <item quantity="other">"WLAN-Netzwerke verfügbar"</item>
+  </plurals>
+  <plurals name="wifi_available_detailed">
+    <item quantity="one">"Verfügbares WLAN-Netzwerk öffnen"</item>
+    <item quantity="other">"Verfügbare WLAN-Netzwerke öffnen"</item>
+  </plurals>
+    <string name="select_character">"Zeichen einfügen"</string>
+    <string name="sms_control_default_app_name">"Unbekannte Anwendung"</string>
+    <string name="sms_control_title">"Kurznachrichten werden gesendet"</string>
+    <string name="sms_control_message">"Es werden eine große Anzahl an Kurznachrichten versendet. Wählen Sie \"OK\", um fortzufahren, oder drücken Sie auf \"Abbrechen\", um den Sendevorgang zu beenden."</string>
+    <string name="sms_control_yes">"OK"</string>
+    <string name="sms_control_no">"Abbrechen"</string>
+    <string name="date_time_set">"Einstellen"</string>
+    <string name="default_permission_group">"Standard"</string>
+    <string name="no_permissions">"Keine Berechtigungen erforderlich"</string>
+    <string name="perms_hide"><b>"Ausblenden"</b></string>
+    <string name="perms_show_all"><b>"Alle anzeigen"</b></string>
+    <string name="googlewebcontenthelper_loading">"Ladevorgang läuft..."</string>
+    <string name="usb_storage_title">"USB-Verbindung"</string>
+    <string name="usb_storage_message">"Sie haben Ihr Telefon über einen USB-Anschluss mit Ihrem Computer verbunden. Wählen Sie \"Bereitstellen\", wenn Sie Dateien auf Ihren Computer oder die SD-Karte Ihres Telefons kopieren möchten."</string>
+    <string name="usb_storage_button_mount">"Bereitstellen"</string>
+    <string name="usb_storage_button_unmount">"Nicht bereitstellen"</string>
+    <string name="usb_storage_error_message">"Bei der Verwendung Ihrer SD-Karte als USB-Speicher ist ein Problem aufgetreten."</string>
+    <string name="usb_storage_notification_title">"USB-Verbindung"</string>
+    <string name="usb_storage_notification_message">"Wählen Sie die Dateien aus, die von Ihrem oder auf Ihren Computer kopiert werden sollen."</string>
+    <string name="usb_storage_stop_notification_title">"USB-Speicher deaktivieren"</string>
+    <string name="usb_storage_stop_notification_message">"Auswählen, um USB-Speicher zu deaktivieren."</string>
+    <string name="usb_storage_stop_title">"USB-Speicher deaktivieren"</string>
+    <string name="usb_storage_stop_message">"Bevor Sie den USB-Speicher deaktivieren, stellen Sie sicher, dass Sie Ihn vom USB-Host getrennt haben. Wählen Sie \"Deaktivieren\", um den USB-Speicher zu deaktivieren."</string>
+    <string name="usb_storage_stop_button_mount">"Ausschalten"</string>
+    <string name="usb_storage_stop_button_unmount">"Abbrechen"</string>
+    <string name="usb_storage_stop_error_message">"Wir haben beim Deaktivieren des USB-Speichers ein Problem festgestellt. Überprüfen Sie, ob Sie den USB-Host getrennt haben, und versuchen Sie es erneut."</string>
+    <string name="extmedia_format_title">"SD-Karte formatieren"</string>
+    <string name="extmedia_format_message">"Möchten Sie die SD-Karte wirklich formatieren? Alle Daten auf Ihrer Karte gehen dann verloren."</string>
+    <string name="extmedia_format_button_format">"Format"</string>
+    <string name="select_input_method">"Eingabemethode auswählen"</string>
+    <string name="fast_scroll_alphabet">" ABCDEFGHIJKLMNOPQRSTUVWXYZ"</string>
+    <string name="fast_scroll_numeric_alphabet">" 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"</string>
+    <string name="candidates_style"><u>"Kandidaten"</u></string>
+    <string name="ext_media_checking_notification_title">"SD-Karte wird vorbereitet"</string>
+    <string name="ext_media_checking_notification_message">"Nach Fehlern wird gesucht"</string>
+    <string name="ext_media_nofs_notification_title">"SD-Karte leer"</string>
+    <string name="ext_media_nofs_notification_message">"Die SD-Karte ist leer oder verwendet ein Dateisystem, das nicht unterstützt wird."</string>
+    <string name="ext_media_unmountable_notification_title">"Beschädigte SD-Karte"</string>
+    <string name="ext_media_unmountable_notification_message">"Die SD-Karte ist beschädigt. Sie müssen Ihre Karte eventuell neu formatieren."</string>
+    <string name="ext_media_badremoval_notification_title">"SD-Karte unerwartet entfernt"</string>
+    <string name="ext_media_badremoval_notification_message">"SD-Karte vor dem Entnehmen trennen, um Datenverlust zu vermeiden."</string>
+    <string name="ext_media_safe_unmount_notification_title">"SD-Karte\nkann entfernt werden."</string>
+    <string name="ext_media_safe_unmount_notification_message">"Die SD-Karte kann jetzt entfernt werden."</string>
+    <string name="ext_media_nomedia_notification_title">"SD-Karte entfernt"</string>
+    <string name="ext_media_nomedia_notification_message">"Die SD-Karte wurde entfernt. Legen Sie eine neue SD-Karte ein, um den Speicherplatz Ihres Geräts zu erweitern."</string>
+    <string name="activity_list_empty">"Keine passenden Aktivitäten gefunden"</string>
+    <string name="permlab_pkgUsageStats">"Nutzungsstatistik der Komponente aktualisieren"</string>
+    <string name="permdesc_pkgUsageStats">"Ermöglicht die Änderung von gesammelten Nutzungsstatistiken der Komponente. Nicht für normale Anwendungen vorgesehen."</string>
+    <!-- no translation found for tutorial_double_tap_to_zoom_message_short (1311810005957319690) -->
+    <skip />
+    <!-- no translation found for gadget_host_error_inflating (2613287218853846830) -->
+    <skip />
+    <!-- no translation found for ime_action_go (8320845651737369027) -->
+    <skip />
+    <!-- no translation found for ime_action_search (658110271822807811) -->
+    <skip />
+    <!-- no translation found for ime_action_send (2316166556349314424) -->
+    <skip />
+    <!-- no translation found for ime_action_next (3138843904009813834) -->
+    <skip />
+    <!-- no translation found for ime_action_default (2840921885558045721) -->
+    <skip />
+</resources>
diff --git a/core/res/res/values-en-rAU/arrays.xml b/core/res/res/values-en-rAU/arrays.xml
new file mode 100644
index 0000000..46351e6
--- /dev/null
+++ b/core/res/res/values-en-rAU/arrays.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* //device/apps/common/assets/res/any/colors.xml
+**
+** Copyright 2006, Google Inc.
+**
+** 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.
+*/
+-->
+<resources>
+
+    <!-- Do not translate. -->
+    <integer-array name="maps_starting_lat_lng">
+        <item>-26037042</item>
+        <item>137197266</item>
+    </integer-array>
+    <!-- Do not translate. -->
+    <integer-array name="maps_starting_zoom">
+        <item>3</item>
+    </integer-array>
+
+</resources>
diff --git a/core/res/res/values-en-rAU/strings.xml b/core/res/res/values-en-rAU/strings.xml
new file mode 100644
index 0000000..9da879b
--- /dev/null
+++ b/core/res/res/values-en-rAU/strings.xml
@@ -0,0 +1,1262 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="byteShort">"B"</string>
+    <!-- no translation found for kilobyteShort (5865542430193761682) -->
+    <skip />
+    <!-- no translation found for megabyteShort (112984851085937882) -->
+    <skip />
+    <!-- no translation found for gigabyteShort (8586075069559273847) -->
+    <skip />
+    <!-- no translation found for terabyteShort (5828502357595687794) -->
+    <skip />
+    <!-- no translation found for petabyteShort (7523248732657962413) -->
+    <skip />
+    <!-- no translation found for untitled (284687023829080340) -->
+    <skip />
+    <!-- no translation found for ellipsis (8538883953764277342) -->
+    <skip />
+    <!-- no translation found for emptyPhoneNumber (6416283285732095329) -->
+    <skip />
+    <!-- no translation found for unknownName (3974255879290140525) -->
+    <skip />
+    <!-- no translation found for defaultVoiceMailAlphaTag (6484324201071049939) -->
+    <skip />
+    <!-- no translation found for defaultMsisdnAlphaTag (4953008223227371928) -->
+    <skip />
+    <!-- no translation found for mmiError (7480678835624852655) -->
+    <skip />
+    <!-- no translation found for serviceEnabled (4042194305396115167) -->
+    <skip />
+    <!-- no translation found for serviceEnabledFor (638808419103886277) -->
+    <skip />
+    <!-- no translation found for serviceDisabled (1059935666763511541) -->
+    <skip />
+    <!-- no translation found for serviceRegistered (7639869107156932038) -->
+    <skip />
+    <!-- no translation found for serviceErased (4602215208593071820) -->
+    <skip />
+    <!-- no translation found for passwordIncorrect (5142040651297346232) -->
+    <skip />
+    <!-- no translation found for mmiComplete (3178168770150013486) -->
+    <skip />
+    <!-- no translation found for badPin (5103184589972647739) -->
+    <skip />
+    <!-- no translation found for badPuk (2200634943393540609) -->
+    <skip />
+    <!-- no translation found for mismatchPin (5055729703806180857) -->
+    <skip />
+    <!-- no translation found for invalidPin (6201854814319326475) -->
+    <skip />
+    <!-- no translation found for needPuk (4788728144863892764) -->
+    <skip />
+    <!-- no translation found for needPuk2 (7056908944942451033) -->
+    <skip />
+    <!-- no translation found for ClipMmi (5649729434121615509) -->
+    <skip />
+    <!-- no translation found for ClirMmi (5220979296096544477) -->
+    <skip />
+    <!-- no translation found for CfMmi (4998483717856803914) -->
+    <skip />
+    <!-- no translation found for CwMmi (5678103638951836350) -->
+    <skip />
+    <!-- no translation found for BaMmi (6030555200442855833) -->
+    <skip />
+    <!-- no translation found for PwdMmi (2549941247959366670) -->
+    <skip />
+    <!-- no translation found for PinMmi (2463353963837922189) -->
+    <skip />
+    <!-- no translation found for CLIRDefaultOnNextCallOn (4005921990799469144) -->
+    <skip />
+    <!-- no translation found for CLIRDefaultOnNextCallOff (1497360760012205230) -->
+    <skip />
+    <!-- no translation found for CLIRDefaultOffNextCallOn (604591440398078227) -->
+    <skip />
+    <!-- no translation found for CLIRDefaultOffNextCallOff (5114039908683246336) -->
+    <skip />
+    <!-- no translation found for serviceNotProvisioned (3754416031529306610) -->
+    <skip />
+    <!-- no translation found for CLIRPermanent (3819908477891272611) -->
+    <skip />
+    <!-- no translation found for serviceClassVoice (3059107563169935913) -->
+    <skip />
+    <!-- no translation found for serviceClassData (2669025626575716504) -->
+    <skip />
+    <!-- no translation found for serviceClassFAX (973109472405729679) -->
+    <skip />
+    <!-- no translation found for serviceClassSMS (3857383928743625711) -->
+    <skip />
+    <!-- no translation found for serviceClassDataAsync (8526461993032174729) -->
+    <skip />
+    <!-- no translation found for serviceClassDataSync (2461138395498381801) -->
+    <skip />
+    <!-- no translation found for serviceClassPacket (8119604233041078065) -->
+    <skip />
+    <!-- no translation found for serviceClassPAD (202892636830042266) -->
+    <skip />
+    <!-- no translation found for cfTemplateNotForwarded (3171755805856206604) -->
+    <skip />
+    <!-- no translation found for cfTemplateForwarded (2468661573318024785) -->
+    <skip />
+    <!-- no translation found for cfTemplateForwardedTime (5151810870794744740) -->
+    <skip />
+    <!-- no translation found for cfTemplateRegistered (5685211900474527085) -->
+    <skip />
+    <!-- no translation found for cfTemplateRegisteredTime (2978918277762252776) -->
+    <skip />
+    <!-- no translation found for httpErrorOk (984913805621139001) -->
+    <skip />
+    <!-- no translation found for httpError (9177990053748151835) -->
+    <skip />
+    <!-- no translation found for httpErrorLookup (5251341716070330936) -->
+    <skip />
+    <!-- no translation found for httpErrorUnsupportedAuthScheme (2865679883634239474) -->
+    <skip />
+    <!-- no translation found for httpErrorAuth (1637382600929594620) -->
+    <skip />
+    <!-- no translation found for httpErrorProxyAuth (5947648983995807455) -->
+    <skip />
+    <!-- no translation found for httpErrorConnect (129984292497034683) -->
+    <skip />
+    <!-- no translation found for httpErrorIO (8128922048686581131) -->
+    <skip />
+    <!-- no translation found for httpErrorTimeout (8357966263983739012) -->
+    <skip />
+    <!-- no translation found for httpErrorRedirectLoop (4122379005100433886) -->
+    <skip />
+    <!-- no translation found for httpErrorUnsupportedScheme (4072339858288462569) -->
+    <skip />
+    <!-- no translation found for httpErrorFailedSslHandshake (2316625025255452595) -->
+    <skip />
+    <!-- no translation found for httpErrorBadUrl (8885244563103716039) -->
+    <skip />
+    <!-- no translation found for httpErrorFile (1408273621719669493) -->
+    <skip />
+    <!-- no translation found for httpErrorFileNotFound (2309088465300506314) -->
+    <skip />
+    <!-- no translation found for httpErrorTooManyRequests (3764334538393544875) -->
+    <skip />
+    <!-- no translation found for contentServiceSync (4863236165350475642) -->
+    <skip />
+    <!-- no translation found for contentServiceSyncNotificationTitle (6855304679069026824) -->
+    <skip />
+    <!-- no translation found for contentServiceTooManyDeletesNotificationDesc (8477597194404210723) -->
+    <skip />
+    <!-- no translation found for low_memory (4191592786596642367) -->
+    <skip />
+    <!-- no translation found for me (4616693653158602117) -->
+    <skip />
+    <!-- no translation found for power_dialog (8210256011408959109) -->
+    <skip />
+    <!-- no translation found for silent_mode (5218239246946854300) -->
+    <skip />
+    <!-- no translation found for turn_on_radio (1901054698789840131) -->
+    <skip />
+    <!-- no translation found for turn_off_radio (2870296409392615956) -->
+    <skip />
+    <!-- no translation found for screen_lock (1560333453597081877) -->
+    <skip />
+    <!-- no translation found for power_off (2412024417733516836) -->
+    <skip />
+    <!-- no translation found for shutdown_progress (3735034517335251808) -->
+    <skip />
+    <!-- no translation found for shutdown_confirm (699224922526414097) -->
+    <skip />
+    <!-- no translation found for no_recent_tasks (1367712919998349373) -->
+    <skip />
+    <!-- no translation found for global_actions (8299888906525675157) -->
+    <skip />
+    <!-- no translation found for global_action_lock (5943677976245541105) -->
+    <skip />
+    <!-- no translation found for global_action_power_off (3143027278596694254) -->
+    <skip />
+    <!-- no translation found for global_action_toggle_silent_mode (5849335789367070450) -->
+    <skip />
+    <!-- no translation found for global_action_silent_mode_on_status (6053429980569202260) -->
+    <skip />
+    <!-- no translation found for global_action_silent_mode_off_status (1994514127029249081) -->
+    <skip />
+    <!-- no translation found for safeMode (3375134507868534320) -->
+    <skip />
+    <!-- no translation found for permgrouplab_costMoney (904087853776533085) -->
+    <skip />
+    <!-- no translation found for permgroupdesc_costMoney (4662370555643969515) -->
+    <skip />
+    <!-- no translation found for permgrouplab_messages (2984053976424233925) -->
+    <skip />
+    <!-- no translation found for permgroupdesc_messages (2129093134354989379) -->
+    <skip />
+    <!-- no translation found for permgrouplab_personalInfo (4548406335021507392) -->
+    <skip />
+    <!-- no translation found for permgroupdesc_personalInfo (8499310823817958034) -->
+    <skip />
+    <!-- no translation found for permgrouplab_location (8535677827151907069) -->
+    <skip />
+    <!-- no translation found for permgroupdesc_location (2341662219604651887) -->
+    <skip />
+    <!-- no translation found for permgrouplab_network (3597781730625751831) -->
+    <skip />
+    <!-- no translation found for permgroupdesc_network (8332572695347918340) -->
+    <skip />
+    <!-- no translation found for permgrouplab_accounts (8631201594657951893) -->
+    <skip />
+    <!-- no translation found for permgroupdesc_accounts (443982868906396781) -->
+    <skip />
+    <!-- no translation found for permgrouplab_hardwareControls (5074512938567152139) -->
+    <skip />
+    <!-- no translation found for permgroupdesc_hardwareControls (8772503144945278440) -->
+    <skip />
+    <!-- no translation found for permgrouplab_phoneCalls (7096448531266882376) -->
+    <skip />
+    <!-- no translation found for permgroupdesc_phoneCalls (6703873478653366233) -->
+    <skip />
+    <!-- no translation found for permgrouplab_systemTools (1840847965111633430) -->
+    <skip />
+    <!-- no translation found for permgroupdesc_systemTools (2810337951496685271) -->
+    <skip />
+    <!-- no translation found for permgrouplab_developmentTools (692844635256963358) -->
+    <skip />
+    <!-- no translation found for permgroupdesc_developmentTools (5253915519857796400) -->
+    <skip />
+    <!-- no translation found for permlab_statusBar (8789506912215455922) -->
+    <skip />
+    <!-- no translation found for permdesc_statusBar (5034247171231682403) -->
+    <skip />
+    <!-- no translation found for permlab_expandStatusBar (6382500803293284173) -->
+    <skip />
+    <!-- no translation found for permdesc_expandStatusBar (90953162060681436) -->
+    <skip />
+    <!-- no translation found for permlab_processOutgoingCalls (786316295241100144) -->
+    <skip />
+    <!-- no translation found for permdesc_processOutgoingCalls (1655242138991854396) -->
+    <skip />
+    <!-- no translation found for permlab_receiveSms (5820796051959871222) -->
+    <skip />
+    <!-- no translation found for permdesc_receiveSms (2265740044646990161) -->
+    <skip />
+    <!-- no translation found for permlab_receiveMms (7983091218880782611) -->
+    <skip />
+    <!-- no translation found for permdesc_receiveMms (3641275586518289960) -->
+    <skip />
+    <!-- no translation found for permlab_sendSms (4713837923748234081) -->
+    <skip />
+    <!-- no translation found for permdesc_sendSms (7126594387176704010) -->
+    <skip />
+    <!-- no translation found for permlab_readSms (4256004535185449429) -->
+    <skip />
+    <!-- no translation found for permdesc_readSms (4586480500886941902) -->
+    <skip />
+    <!-- no translation found for permlab_writeSms (8453452414726246828) -->
+    <skip />
+    <!-- no translation found for permdesc_writeSms (1036408118901361812) -->
+    <skip />
+    <!-- no translation found for permlab_receiveWapPush (5726837205927152203) -->
+    <skip />
+    <!-- no translation found for permdesc_receiveWapPush (4779188629794134886) -->
+    <skip />
+    <!-- no translation found for permlab_getTasks (357640569227780364) -->
+    <skip />
+    <!-- no translation found for permdesc_getTasks (2916615403728003200) -->
+    <skip />
+    <!-- no translation found for permlab_reorderTasks (4758862288285224517) -->
+    <skip />
+    <!-- no translation found for permdesc_reorderTasks (7507060843941912021) -->
+    <skip />
+    <!-- no translation found for permlab_setDebugApp (2973363275929449444) -->
+    <skip />
+    <!-- no translation found for permdesc_setDebugApp (5720449860498265972) -->
+    <skip />
+    <!-- no translation found for permlab_changeConfiguration (8581093564179818627) -->
+    <skip />
+    <!-- no translation found for permdesc_changeConfiguration (4055366453803187171) -->
+    <skip />
+    <!-- no translation found for permlab_restartPackages (5836367540766044606) -->
+    <skip />
+    <!-- no translation found for permdesc_restartPackages (1764965996765573321) -->
+    <skip />
+    <!-- no translation found for permlab_setProcessForeground (4860990420780868638) -->
+    <skip />
+    <!-- no translation found for permdesc_setProcessForeground (3795477299954784360) -->
+    <skip />
+    <!-- no translation found for permlab_forceBack (4737517869935566733) -->
+    <skip />
+    <!-- no translation found for permdesc_forceBack (5579316297001154697) -->
+    <skip />
+    <!-- no translation found for permlab_dump (3177569414212943167) -->
+    <skip />
+    <!-- no translation found for permdesc_dump (1815913623373011608) -->
+    <skip />
+    <!-- no translation found for permlab_addSystemService (9166015020584794942) -->
+    <skip />
+    <!-- no translation found for permdesc_addSystemService (2310425587289835743) -->
+    <skip />
+    <!-- no translation found for permlab_runSetActivityWatcher (2615943932761994905) -->
+    <skip />
+    <!-- no translation found for permdesc_runSetActivityWatcher (2488524206195482220) -->
+    <skip />
+    <!-- no translation found for permlab_broadcastPackageRemoved (355775368495637820) -->
+    <skip />
+    <!-- no translation found for permdesc_broadcastPackageRemoved (6486181398191058385) -->
+    <skip />
+    <!-- no translation found for permlab_broadcastSmsReceived (1994692154847312518) -->
+    <skip />
+    <!-- no translation found for permdesc_broadcastSmsReceived (6072362543164841432) -->
+    <skip />
+    <!-- no translation found for permlab_broadcastWapPush (3070023012636951639) -->
+    <skip />
+    <!-- no translation found for permdesc_broadcastWapPush (726912255218924336) -->
+    <skip />
+    <!-- no translation found for permlab_setProcessLimit (5190694306017260601) -->
+    <skip />
+    <!-- no translation found for permdesc_setProcessLimit (593938303319848578) -->
+    <skip />
+    <!-- no translation found for permlab_setAlwaysFinish (8745533365504920540) -->
+    <skip />
+    <!-- no translation found for permdesc_setAlwaysFinish (2437195869854312148) -->
+    <skip />
+    <!-- no translation found for permlab_fotaUpdate (1813039882829307079) -->
+    <skip />
+    <!-- no translation found for permdesc_fotaUpdate (2544137712607584763) -->
+    <skip />
+    <!-- no translation found for permlab_batteryStats (1598947993704535568) -->
+    <skip />
+    <!-- no translation found for permdesc_batteryStats (6247598531831307989) -->
+    <skip />
+    <!-- no translation found for permlab_internalSystemWindow (5780262737320556654) -->
+    <skip />
+    <!-- no translation found for permdesc_internalSystemWindow (6495031598062517795) -->
+    <skip />
+    <!-- no translation found for permlab_systemAlertWindow (843729657746130626) -->
+    <skip />
+    <!-- no translation found for permdesc_systemAlertWindow (2731854380682210852) -->
+    <skip />
+    <!-- no translation found for permlab_setAnimationScale (2419250686027992384) -->
+    <skip />
+    <!-- no translation found for permdesc_setAnimationScale (8518027785481727264) -->
+    <skip />
+    <!-- no translation found for permlab_manageAppTokens (1033424552444304594) -->
+    <skip />
+    <!-- no translation found for permdesc_manageAppTokens (7285840918912623550) -->
+    <skip />
+    <!-- no translation found for permlab_injectEvents (1383601196263145482) -->
+    <skip />
+    <!-- no translation found for permdesc_injectEvents (840097509341464737) -->
+    <skip />
+    <!-- no translation found for permlab_readInputState (2723668746963882102) -->
+    <skip />
+    <!-- no translation found for permdesc_readInputState (4651137638757852001) -->
+    <skip />
+    <!-- no translation found for permlab_setOrientation (1112555600323148680) -->
+    <skip />
+    <!-- no translation found for permdesc_setOrientation (1960269530378827858) -->
+    <skip />
+    <!-- no translation found for permlab_signalPersistentProcesses (8511163028160623175) -->
+    <skip />
+    <!-- no translation found for permdesc_signalPersistentProcesses (1099349638354917733) -->
+    <skip />
+    <!-- no translation found for permlab_persistentActivity (8163108526929094627) -->
+    <skip />
+    <!-- no translation found for permdesc_persistentActivity (5258975883823299624) -->
+    <skip />
+    <!-- no translation found for permlab_deletePackages (5005536434839333208) -->
+    <skip />
+    <!-- no translation found for permdesc_deletePackages (2687196995215591923) -->
+    <skip />
+    <!-- no translation found for permlab_clearAppUserData (3858185484601410171) -->
+    <skip />
+    <!-- no translation found for permdesc_clearAppUserData (7233537744753081136) -->
+    <skip />
+    <!-- no translation found for permlab_deleteCacheFiles (7362746182961997888) -->
+    <skip />
+    <!-- no translation found for permdesc_deleteCacheFiles (8293849509208181266) -->
+    <skip />
+    <!-- no translation found for permlab_getPackageSize (6743556676630447973) -->
+    <skip />
+    <!-- no translation found for permdesc_getPackageSize (2893996655828539776) -->
+    <skip />
+    <!-- no translation found for permlab_installPackages (1637554234554641998) -->
+    <skip />
+    <!-- no translation found for permdesc_installPackages (4747794850590875195) -->
+    <skip />
+    <!-- no translation found for permlab_clearAppCache (7860214328511700776) -->
+    <skip />
+    <!-- no translation found for permdesc_clearAppCache (5203820862573167878) -->
+    <skip />
+    <!-- no translation found for permlab_readLogs (6653488552442991707) -->
+    <skip />
+    <!-- no translation found for permdesc_readLogs (356352685800884319) -->
+    <skip />
+    <!-- no translation found for permlab_diagnostic (2955142476313469329) -->
+    <skip />
+    <!-- no translation found for permdesc_diagnostic (1282409892215520166) -->
+    <skip />
+    <!-- no translation found for permlab_changeComponentState (8107835954049971459) -->
+    <skip />
+    <!-- no translation found for permdesc_changeComponentState (1791096057705836844) -->
+    <skip />
+    <!-- no translation found for permlab_setPreferredApplications (4355701371185331520) -->
+    <skip />
+    <!-- no translation found for permdesc_setPreferredApplications (9029326613767614711) -->
+    <skip />
+    <!-- no translation found for permlab_writeSettings (2915467191611898256) -->
+    <skip />
+    <!-- no translation found for permdesc_writeSettings (8492982548350342641) -->
+    <skip />
+    <!-- no translation found for permlab_writeSecureSettings (4851801872124242319) -->
+    <skip />
+    <!-- no translation found for permdesc_writeSecureSettings (2080620249472761366) -->
+    <skip />
+    <!-- no translation found for permlab_writeGservices (296370685945777755) -->
+    <skip />
+    <!-- no translation found for permdesc_writeGservices (2496928471286495053) -->
+    <skip />
+    <!-- no translation found for permlab_receiveBootCompleted (5598819384278633845) -->
+    <skip />
+    <!-- no translation found for permdesc_receiveBootCompleted (3197439472472771192) -->
+    <skip />
+    <!-- no translation found for permlab_broadcastSticky (8142357333543531543) -->
+    <skip />
+    <!-- no translation found for permdesc_broadcastSticky (8488762822718743531) -->
+    <skip />
+    <!-- no translation found for permlab_readContacts (5116003370450871686) -->
+    <skip />
+    <!-- no translation found for permdesc_readContacts (3499378044902258770) -->
+    <skip />
+    <!-- no translation found for permlab_writeContacts (1555136823460617179) -->
+    <skip />
+    <!-- no translation found for permdesc_writeContacts (4787318403287293114) -->
+    <skip />
+    <!-- no translation found for permlab_writeOwnerData (8036840529708535113) -->
+    <skip />
+    <!-- no translation found for permdesc_writeOwnerData (5873447528845878348) -->
+    <skip />
+    <!-- no translation found for permlab_readOwnerData (1847040178513733757) -->
+    <skip />
+    <!-- no translation found for permdesc_readOwnerData (7563299529149214764) -->
+    <skip />
+    <!-- no translation found for permlab_readCalendar (2111238731453410895) -->
+    <skip />
+    <!-- no translation found for permdesc_readCalendar (4408253940601239114) -->
+    <skip />
+    <!-- no translation found for permlab_writeCalendar (7518052789370653396) -->
+    <skip />
+    <!-- no translation found for permdesc_writeCalendar (8057304232140147596) -->
+    <skip />
+    <!-- no translation found for permlab_accessMockLocation (321094551062270213) -->
+    <skip />
+    <!-- no translation found for permdesc_accessMockLocation (3651565866471419739) -->
+    <skip />
+    <!-- no translation found for permlab_accessLocationExtraCommands (8291822077788811687) -->
+    <skip />
+    <!-- no translation found for permdesc_accessLocationExtraCommands (5135782633548630731) -->
+    <skip />
+    <!-- no translation found for permlab_accessFineLocation (4846261651944924865) -->
+    <skip />
+    <!-- no translation found for permdesc_accessFineLocation (3572307331039348419) -->
+    <skip />
+    <!-- no translation found for permlab_accessCoarseLocation (1760779730797169189) -->
+    <skip />
+    <!-- no translation found for permdesc_accessCoarseLocation (8878785899768310712) -->
+    <skip />
+    <!-- no translation found for permlab_accessSurfaceFlinger (6405475452322847618) -->
+    <skip />
+    <!-- no translation found for permdesc_accessSurfaceFlinger (5348283543622360967) -->
+    <skip />
+    <!-- no translation found for permlab_readFrameBuffer (4655248388550039199) -->
+    <skip />
+    <!-- no translation found for permdesc_readFrameBuffer (794888199105081402) -->
+    <skip />
+    <!-- no translation found for permlab_modifyAudioSettings (1587341813207960943) -->
+    <skip />
+    <!-- no translation found for permdesc_modifyAudioSettings (1447143004892708149) -->
+    <skip />
+    <!-- no translation found for permlab_recordAudio (4447848534036991667) -->
+    <skip />
+    <!-- no translation found for permdesc_recordAudio (6936874682400894820) -->
+    <skip />
+    <!-- no translation found for permlab_camera (1944473855727060380) -->
+    <skip />
+    <!-- no translation found for permdesc_camera (5978058582323766022) -->
+    <skip />
+    <!-- no translation found for permlab_brick (4749832243303289777) -->
+    <skip />
+    <!-- no translation found for permdesc_brick (7428524578693695766) -->
+    <skip />
+    <!-- no translation found for permlab_reboot (8844650672567077423) -->
+    <skip />
+    <!-- no translation found for permdesc_reboot (4704919552870918328) -->
+    <skip />
+    <!-- no translation found for permlab_mount_unmount_filesystems (1009574821038043781) -->
+    <skip />
+    <!-- no translation found for permdesc_mount_unmount_filesystems (100792065894811109) -->
+    <skip />
+    <!-- no translation found for permlab_vibrate (61984555644467146) -->
+    <skip />
+    <!-- no translation found for permdesc_vibrate (7831723100758509238) -->
+    <skip />
+    <!-- no translation found for permlab_flashlight (9097145977808182652) -->
+    <skip />
+    <!-- no translation found for permdesc_flashlight (7851502731988978358) -->
+    <skip />
+    <!-- no translation found for permlab_hardware_test (4103324677866524254) -->
+    <skip />
+    <!-- no translation found for permdesc_hardware_test (7315242723603994769) -->
+    <skip />
+    <!-- no translation found for permlab_callPhone (168275616535116686) -->
+    <skip />
+    <!-- no translation found for permdesc_callPhone (1852033967965785973) -->
+    <skip />
+    <!-- no translation found for permlab_callPrivileged (2166923597287697159) -->
+    <skip />
+    <!-- no translation found for permdesc_callPrivileged (5109789447971735501) -->
+    <skip />
+    <!-- no translation found for permlab_locationUpdates (4216418293360456836) -->
+    <skip />
+    <!-- no translation found for permdesc_locationUpdates (7635814693478743648) -->
+    <skip />
+    <!-- no translation found for permlab_checkinProperties (2260796787386280708) -->
+    <skip />
+    <!-- no translation found for permdesc_checkinProperties (3508022022841741945) -->
+    <skip />
+    <!-- no translation found for permlab_modifyPhoneState (7791696535097912313) -->
+    <skip />
+    <!-- no translation found for permdesc_modifyPhoneState (6352405226410454770) -->
+    <skip />
+    <!-- no translation found for permlab_readPhoneState (7320082586621086653) -->
+    <skip />
+    <!-- no translation found for permdesc_readPhoneState (8004450067066407969) -->
+    <skip />
+    <!-- no translation found for permlab_wakeLock (1591164750935072136) -->
+    <skip />
+    <!-- no translation found for permdesc_wakeLock (160471538196734936) -->
+    <skip />
+    <!-- no translation found for permlab_devicePower (9214865067086065548) -->
+    <skip />
+    <!-- no translation found for permdesc_devicePower (5608364066480036402) -->
+    <skip />
+    <!-- no translation found for permlab_factoryTest (7786199300637896247) -->
+    <skip />
+    <!-- no translation found for permdesc_factoryTest (3466066005210542042) -->
+    <skip />
+    <!-- no translation found for permlab_setWallpaper (2256730637138641725) -->
+    <skip />
+    <!-- no translation found for permdesc_setWallpaper (3034653140208685093) -->
+    <skip />
+    <!-- no translation found for permlab_setWallpaperHints (4192438316932517807) -->
+    <skip />
+    <!-- no translation found for permdesc_setWallpaperHints (738757439960921674) -->
+    <skip />
+    <!-- no translation found for permlab_masterClear (6155403967270586906) -->
+    <skip />
+    <!-- no translation found for permdesc_masterClear (4213553172342689754) -->
+    <skip />
+    <!-- no translation found for permlab_setTimeZone (477196167239548690) -->
+    <skip />
+    <!-- no translation found for permdesc_setTimeZone (8564892020460841198) -->
+    <skip />
+    <!-- no translation found for permlab_getAccounts (2764070033402295170) -->
+    <skip />
+    <!-- no translation found for permdesc_getAccounts (1203491378748649898) -->
+    <skip />
+    <!-- no translation found for permlab_accessNetworkState (2032916924886010827) -->
+    <skip />
+    <!-- no translation found for permdesc_accessNetworkState (7081329402551195933) -->
+    <skip />
+    <!-- no translation found for permlab_createNetworkSockets (4706698319966917864) -->
+    <skip />
+    <!-- no translation found for permdesc_createNetworkSockets (2580337178778551792) -->
+    <skip />
+    <!-- no translation found for permlab_writeApnSettings (3190585220761979369) -->
+    <skip />
+    <!-- no translation found for permdesc_writeApnSettings (4093875220468761052) -->
+    <skip />
+    <!-- no translation found for permlab_changeNetworkState (2710779001260856872) -->
+    <skip />
+    <!-- no translation found for permdesc_changeNetworkState (8076109230787022270) -->
+    <skip />
+    <!-- no translation found for permlab_accessWifiState (3613679494230374297) -->
+    <skip />
+    <!-- no translation found for permdesc_accessWifiState (8226508433563326925) -->
+    <skip />
+    <!-- no translation found for permlab_changeWifiState (6043889338995432957) -->
+    <skip />
+    <!-- no translation found for permdesc_changeWifiState (7829372845909567994) -->
+    <skip />
+    <!-- no translation found for permlab_bluetoothAdmin (5513286736585647334) -->
+    <skip />
+    <!-- no translation found for permdesc_bluetoothAdmin (1838208497914347365) -->
+    <skip />
+    <!-- no translation found for permlab_bluetooth (6378797624765639115) -->
+    <skip />
+    <!-- no translation found for permdesc_bluetooth (8592386018922265273) -->
+    <skip />
+    <!-- no translation found for permlab_disableKeyguard (4574886811903233903) -->
+    <skip />
+    <!-- no translation found for permdesc_disableKeyguard (815972646344251271) -->
+    <skip />
+    <!-- no translation found for permlab_readSyncSettings (8818819977141505127) -->
+    <skip />
+    <!-- no translation found for permdesc_readSyncSettings (8454705401908767847) -->
+    <skip />
+    <!-- no translation found for permlab_writeSyncSettings (4514911143753152941) -->
+    <skip />
+    <!-- no translation found for permdesc_writeSyncSettings (7630627689635091836) -->
+    <skip />
+    <!-- no translation found for permlab_readSyncStats (5748337739678952863) -->
+    <skip />
+    <!-- no translation found for permdesc_readSyncStats (582551457321957183) -->
+    <skip />
+    <!-- no translation found for permlab_subscribedFeedsRead (2043206814904506589) -->
+    <skip />
+    <!-- no translation found for permdesc_subscribedFeedsRead (6977343942680042449) -->
+    <skip />
+    <!-- no translation found for permlab_subscribedFeedsWrite (2556727307229571556) -->
+    <skip />
+    <!-- no translation found for permdesc_subscribedFeedsWrite (4134783294590266220) -->
+    <skip />
+    <!-- no translation found for phoneTypes:0 (6070018634209800981) -->
+    <!-- no translation found for phoneTypes:1 (1514509689885965711) -->
+    <!-- no translation found for phoneTypes:2 (497473201754095234) -->
+    <!-- no translation found for phoneTypes:3 (5554432614281047787) -->
+    <!-- no translation found for phoneTypes:4 (2222084401110150993) -->
+    <!-- no translation found for phoneTypes:5 (2290007103906353121) -->
+    <!-- no translation found for phoneTypes:6 (6930783706213719251) -->
+    <!-- no translation found for phoneTypes:7 (1326005699931077792) -->
+    <!-- no translation found for emailAddressTypes:0 (1540640638077615417) -->
+    <!-- no translation found for emailAddressTypes:1 (4252853367575831977) -->
+    <!-- no translation found for emailAddressTypes:2 (7158046581744435718) -->
+    <!-- no translation found for emailAddressTypes:3 (3625034471181268169) -->
+    <!-- no translation found for postalAddressTypes:0 (5732960259696659380) -->
+    <!-- no translation found for postalAddressTypes:1 (7132240704786130285) -->
+    <!-- no translation found for postalAddressTypes:2 (1317604357745852817) -->
+    <!-- no translation found for postalAddressTypes:3 (1582953598462826702) -->
+    <!-- no translation found for imAddressTypes:0 (7806620012096518833) -->
+    <!-- no translation found for imAddressTypes:1 (5748846799950672787) -->
+    <!-- no translation found for imAddressTypes:2 (6196536810275073680) -->
+    <!-- no translation found for imAddressTypes:3 (8519128375350623648) -->
+    <!-- no translation found for organizationTypes:0 (1299224825223821142) -->
+    <!-- no translation found for organizationTypes:1 (2455717447227299354) -->
+    <!-- no translation found for organizationTypes:2 (7027570839313438290) -->
+    <!-- no translation found for imProtocols:0 (3318725788774688043) -->
+    <!-- no translation found for imProtocols:1 (1787713387022932886) -->
+    <!-- no translation found for imProtocols:2 (6751174158442316516) -->
+    <!-- no translation found for imProtocols:3 (1151283347465052653) -->
+    <!-- no translation found for imProtocols:4 (2157980008878817934) -->
+    <!-- no translation found for imProtocols:5 (7836237460308230767) -->
+    <!-- no translation found for imProtocols:6 (1180789904462172516) -->
+    <!-- no translation found for imProtocols:7 (21955111672779862) -->
+    <!-- no translation found for keyguard_password_enter_pin_code (6779835451906812518) -->
+    <skip />
+    <!-- no translation found for keyguard_password_wrong_pin_code (230312338493035499) -->
+    <skip />
+    <!-- no translation found for keyguard_label_text (3902954467573892533) -->
+    <skip />
+    <!-- no translation found for emergency_call_dialog_number_for_display (6256361184251050511) -->
+    <skip />
+    <!-- no translation found for lockscreen_carrier_default (5222269885486229730) -->
+    <skip />
+    <!-- no translation found for lockscreen_screen_locked (1922273663462058967) -->
+    <skip />
+    <!-- no translation found for lockscreen_instructions_when_pattern_enabled (7535864145009679967) -->
+    <skip />
+    <!-- no translation found for lockscreen_instructions_when_pattern_disabled (6526504555912746785) -->
+    <skip />
+    <!-- no translation found for lockscreen_pattern_instructions (8984964506352089877) -->
+    <skip />
+    <!-- no translation found for lockscreen_emergency_call (422835617844547383) -->
+    <skip />
+    <!-- no translation found for lockscreen_pattern_correct (7104753084746383672) -->
+    <skip />
+    <!-- no translation found for lockscreen_pattern_wrong (7517004470797680361) -->
+    <skip />
+    <!-- no translation found for lockscreen_plugged_in (8806977650003537118) -->
+    <skip />
+    <!-- no translation found for lockscreen_low_battery (9002637795199621345) -->
+    <skip />
+    <!-- no translation found for lockscreen_missing_sim_message_short (5051192587315492957) -->
+    <skip />
+    <!-- no translation found for lockscreen_missing_sim_message (8912914495901434841) -->
+    <skip />
+    <!-- no translation found for lockscreen_missing_sim_instructions (8125847194365725429) -->
+    <skip />
+    <!-- no translation found for lockscreen_network_locked_message (323609607922245071) -->
+    <skip />
+    <!-- no translation found for lockscreen_sim_puk_locked_message (1005803622871256359) -->
+    <skip />
+    <!-- no translation found for lockscreen_sim_puk_locked_instructions (5033160098036646955) -->
+    <skip />
+    <!-- no translation found for lockscreen_sim_locked_message (7398401200962556379) -->
+    <skip />
+    <!-- no translation found for lockscreen_sim_unlock_progress_dialog_message (5939537246164692076) -->
+    <skip />
+    <!-- no translation found for lockscreen_too_many_failed_attempts_dialog_message (6709066241494622136) -->
+    <skip />
+    <!-- no translation found for lockscreen_failed_attempts_almost_glogin (1569017295989454551) -->
+    <skip />
+    <!-- no translation found for lockscreen_too_many_failed_attempts_countdown (8823588000022797566) -->
+    <skip />
+    <!-- no translation found for lockscreen_forgot_pattern_button_text (4219994639843985488) -->
+    <skip />
+    <!-- no translation found for lockscreen_glogin_too_many_attempts (7504679498838839295) -->
+    <skip />
+    <!-- no translation found for lockscreen_glogin_instructions (6542400673357252011) -->
+    <skip />
+    <!-- no translation found for lockscreen_glogin_username_hint (6378418320242015111) -->
+    <skip />
+    <!-- no translation found for lockscreen_glogin_password_hint (3224230234042131153) -->
+    <skip />
+    <!-- no translation found for lockscreen_glogin_submit_button (5562051040043760034) -->
+    <skip />
+    <!-- no translation found for lockscreen_glogin_invalid_input (4881057177478491580) -->
+    <skip />
+    <!-- no translation found for status_bar_time_format (2168573805413119180) -->
+    <string name="status_bar_time_format">"<xliff:g id="HOUR">h</xliff:g>:<xliff:g id="MINUTE">mm</xliff:g> <xliff:g id="AMPM">AA</xliff:g>"</string>
+    <!-- no translation found for hour_minute_ampm (1850330605794978742) -->
+    <skip />
+    <!-- no translation found for hour_minute_cap_ampm (1122840227537374196) -->
+    <skip />
+    <!-- no translation found for hour_ampm (7665432130905376251) -->
+    <skip />
+    <!-- no translation found for hour_cap_ampm (3600295014648400268) -->
+    <skip />
+    <!-- no translation found for status_bar_clear_all_button (2202004591253243750) -->
+    <skip />
+    <!-- no translation found for status_bar_no_notifications_title (5123133188102094464) -->
+    <skip />
+    <!-- no translation found for status_bar_ongoing_events_title (799961521630569167) -->
+    <skip />
+    <!-- no translation found for status_bar_latest_events_title (5414094466807164279) -->
+    <skip />
+    <!-- no translation found for battery_status_text_percent_format (7391464609447031944) -->
+    <skip />
+    <!-- no translation found for battery_status_charging (5078780715755132756) -->
+    <skip />
+    <!-- no translation found for battery_low_title (3665400828395001695) -->
+    <skip />
+    <!-- no translation found for battery_low_subtitle (7537149915372180016) -->
+    <skip />
+    <!-- no translation found for battery_low_percent_format (8635359708781261154) -->
+    <skip />
+    <!-- no translation found for factorytest_failed (5784901108608196679) -->
+    <skip />
+    <!-- no translation found for factorytest_not_system (6330339565054095688) -->
+    <skip />
+    <!-- no translation found for factorytest_no_action (1662569013408679347) -->
+    <skip />
+    <!-- no translation found for factorytest_reboot (6080912029718954885) -->
+    <skip />
+    <!-- no translation found for save_password_label (4129493019621348626) -->
+    <skip />
+    <!-- no translation found for save_password_message (7412617920202682045) -->
+    <skip />
+    <!-- no translation found for save_password_notnow (3887362423496820832) -->
+    <skip />
+    <!-- no translation found for save_password_remember (4319688896716308569) -->
+    <skip />
+    <!-- no translation found for save_password_never (1836981952883642377) -->
+    <skip />
+    <!-- no translation found for open_permission_deny (6408502671105717111) -->
+    <skip />
+    <!-- no translation found for text_copied (6106873823411904723) -->
+    <skip />
+    <!-- no translation found for more_item_label (5204075544750360778) -->
+    <skip />
+    <!-- no translation found for prepend_shortcut_label (6091430648975237047) -->
+    <skip />
+    <!-- no translation found for menu_space_shortcut_label (194586306440382711) -->
+    <skip />
+    <!-- no translation found for menu_enter_shortcut_label (7214761412193519345) -->
+    <skip />
+    <!-- no translation found for menu_delete_shortcut_label (2854936426194985313) -->
+    <skip />
+    <!-- no translation found for search_go (4823831235057123206) -->
+    <skip />
+    <!-- no translation found for today (6914914811057683636) -->
+    <skip />
+    <!-- no translation found for yesterday (5280495043584636271) -->
+    <skip />
+    <!-- no translation found for tomorrow (561215115479060939) -->
+    <skip />
+    <!-- no translation found for oneMonthDurationPast (3402179395240209557) -->
+    <skip />
+    <!-- no translation found for beforeOneMonthDurationPast (7578100953282866827) -->
+    <skip />
+    <!-- no translation found for num_seconds_ago:one (7416512229671810725) -->
+    <!-- no translation found for num_seconds_ago:other (8138756910300398447) -->
+    <!-- no translation found for num_minutes_ago:one (8620869479299420562) -->
+    <!-- no translation found for num_minutes_ago:other (5065488162050522741) -->
+    <!-- no translation found for num_hours_ago:one (853404611989669641) -->
+    <!-- no translation found for num_hours_ago:other (3558873784561756849) -->
+    <!-- no translation found for num_days_ago:one (4222479980812128212) -->
+    <!-- no translation found for num_days_ago:other (5445701370433601703) -->
+    <!-- no translation found for in_num_seconds:one (4253290037777327003) -->
+    <!-- no translation found for in_num_seconds:other (1280033870920841404) -->
+    <!-- no translation found for in_num_minutes:one (1487585791027953091) -->
+    <!-- no translation found for in_num_minutes:other (6274204576475209932) -->
+    <!-- no translation found for in_num_hours:one (6501470863235186391) -->
+    <!-- no translation found for in_num_hours:other (4415358752953289251) -->
+    <!-- no translation found for in_num_days:one (5608475533104443893) -->
+    <!-- no translation found for in_num_days:other (3827193006163842267) -->
+    <!-- no translation found for preposition_for_date (2689847983632851560) -->
+    <skip />
+    <!-- no translation found for preposition_for_time (2613388053493148013) -->
+    <skip />
+    <!-- no translation found for preposition_for_year (6968468294728152393) -->
+    <skip />
+    <!-- no translation found for day (7849249054576985912) -->
+    <skip />
+    <!-- no translation found for days (8381828105391141169) -->
+    <skip />
+    <!-- no translation found for hour (1044439788994278057) -->
+    <skip />
+    <!-- no translation found for hours (9008157371441255845) -->
+    <skip />
+    <!-- no translation found for minute (2434431396283136076) -->
+    <skip />
+    <!-- no translation found for minutes (8176836254200264856) -->
+    <skip />
+    <!-- no translation found for second (6620645953323664299) -->
+    <skip />
+    <!-- no translation found for seconds (6416703426008384360) -->
+    <skip />
+    <!-- no translation found for week (7738046527402739781) -->
+    <skip />
+    <!-- no translation found for weeks (3178327674459887377) -->
+    <skip />
+    <!-- no translation found for year (8024790425994085153) -->
+    <skip />
+    <!-- no translation found for years (8592090054773244417) -->
+    <skip />
+    <!-- no translation found for sunday (4811082193700148223) -->
+    <skip />
+    <!-- no translation found for monday (7543713499896911033) -->
+    <skip />
+    <!-- no translation found for tuesday (7962192298359117585) -->
+    <skip />
+    <!-- no translation found for wednesday (5768878309383390437) -->
+    <skip />
+    <!-- no translation found for thursday (5690060634904123607) -->
+    <skip />
+    <!-- no translation found for friday (2718325370375116889) -->
+    <skip />
+    <!-- no translation found for saturday (222899317300942333) -->
+    <skip />
+    <!-- no translation found for every_weekday (8466333034903391066) -->
+    <skip />
+    <!-- no translation found for daily (1661712840773846970) -->
+    <skip />
+    <!-- no translation found for weekly (578642117234613009) -->
+    <skip />
+    <!-- no translation found for monthly (8526124657540210537) -->
+    <skip />
+    <!-- no translation found for yearly (8083067713764127070) -->
+    <skip />
+    <!-- no translation found for VideoView_error_title (1024334251681931859) -->
+    <skip />
+    <!-- no translation found for VideoView_error_text_unknown (3398417247398476771) -->
+    <skip />
+    <!-- no translation found for VideoView_error_button (3144127115413163445) -->
+    <skip />
+    <!-- no translation found for am (5354895493921411502) -->
+    <skip />
+    <!-- no translation found for pm (7206933220587555766) -->
+    <skip />
+    <!-- from values-de/strings.xml and removal of all the german craziyness-->
+    <skip />
+    <!-- no translation found for numeric_date (5120078478872821100) -->
+    <string name="numeric_date">"<xliff:g id="DAY">%d</xliff:g>/<xliff:g id="MONTH">%m</xliff:g>/<xliff:g id="YEAR">%Y</xliff:g>"</string>
+    <!-- no translation found for wday1_date1_time1_wday2_date2_time2 (7066878981949584861) -->
+    <string name="wday1_date1_time1_wday2_date2_time2">"<xliff:g id="WEEKDAY1">%1$s</xliff:g>, <xliff:g id="DATE1">%2$s</xliff:g>, <xliff:g id="TIME1">%3$s</xliff:g> – <xliff:g id="WEEKDAY2">%4$s</xliff:g>, <xliff:g id="DATE2">%5$s</xliff:g>, <xliff:g id="TIME2">%6$s</xliff:g>"</string>
+    <!-- no translation found for wday1_date1_wday2_date2 (8671068747172261907) -->
+    <string name="wday1_date1_wday2_date2">"<xliff:g id="WEEKDAY1">%1$s</xliff:g>, <xliff:g id="DATE1">%2$s</xliff:g> – <xliff:g id="WEEKDAY2">%4$s</xliff:g>, <xliff:g id="DATE2">%5$s</xliff:g>"</string>
+    <!-- no translation found for numeric_date (5537215108967329745) -->
+    <skip />
+    <!-- no translation found for date1_time1_date2_time2 (3645498975775629615) -->
+    <string name="date1_time1_date2_time2">"<xliff:g id="DATE1">%2$s</xliff:g>, <xliff:g id="TIME1">%3$s</xliff:g> – <xliff:g id="DATE2">%5$s</xliff:g>, <xliff:g id="TIME2">%6$s</xliff:g>"</string>
+    <!-- no translation found for date1_date2 (377057563556488062) -->
+    <string name="date1_date2">"<xliff:g id="DATE1">%2$s</xliff:g> – <xliff:g id="DATE2">%5$s</xliff:g>"</string>
+    <!-- no translation found for time1_time2 (3173474242109288305) -->
+    <string name="time1_time2">"<xliff:g id="TIME1">%1$s</xliff:g> – <xliff:g id="TIME2">%2$s</xliff:g>"</string>
+    <!-- no translation found for time_wday_date (8928955562064570313) -->
+    <string name="time_wday_date">"<xliff:g id="TIME_RANGE">%1$s</xliff:g>, <xliff:g id="WEEKDAY">%2$s</xliff:g>, <xliff:g id="DATE">%3$s</xliff:g>"</string>
+    <!-- no translation found for wday_date (8794741400546136975) -->
+    <string name="wday_date">"<xliff:g id="WEEKDAY">%2$s</xliff:g>, <xliff:g id="DATE">%3$s</xliff:g>"</string>
+    <!-- no translation found for time_date (1922644512833014496) -->
+    <string name="time_date">"<xliff:g id="TIME_RANGE">%1$s</xliff:g>, <xliff:g id="DATE">%3$s</xliff:g>"</string>
+    <!-- no translation found for time_wday (1422050241301754712) -->
+    <skip />
+    <!-- no translation found for full_date_month_first (6011143962222283357) -->
+    <skip />
+    <!-- no translation found for full_date_day_first (8621594762705478189) -->
+    <string name="full_date_day_first">"<xliff:g id="DAY">dd</xliff:g> <xliff:g id="MONTH">MMMM</xliff:g> <xliff:g id="YEAR">yyyy</xliff:g>"</string>
+    <!-- no translation found for medium_date_month_first (48990963718825728) -->
+    <skip />
+    <!-- no translation found for medium_date_day_first (2898992016440387123) -->
+    <string name="medium_date_day_first">"<xliff:g id="DAY">dd</xliff:g>  <xliff:g id="MONTH">MMM</xliff:g> <xliff:g id="YEAR">yyyy</xliff:g>"</string>
+    <!-- no translation found for twelve_hour_time_format (6015557937879492156) -->
+    <skip />
+    <!-- no translation found for twenty_four_hour_time_format (5176807998669709535) -->
+    <skip />
+    <!-- no translation found for noon (8390796001560682897) -->
+    <skip />
+    <!-- no translation found for Noon (7698941576181064429) -->
+    <skip />
+    <!-- no translation found for midnight (7773339795626486146) -->
+    <skip />
+    <!-- no translation found for Midnight (1260172107848123187) -->
+    <skip />
+    <!-- no translation found for month_day (3356633704511426364) -->
+    <string name="month_day">"<xliff:g id="day" example="9">%-d</xliff:g> <xliff:g id="month" example="October">%B</xliff:g>"</string>
+    <!-- no translation found for month (3017405760734206414) -->
+    <skip />
+    <!-- no translation found for month_day_year (2435948225709176752) -->
+    <string name="month_day_year">"<xliff:g id="DAY">%-d</xliff:g>  <xliff:g id="MONTH">%B</xliff:g> <xliff:g id="YEAR">%Y</xliff:g>"</string>
+    <!-- no translation found for month_year (6228414124777343135) -->
+    <skip />
+    <!-- no translation found for time_of_day (8375993139317154157) -->
+    <string name="time_of_day">"<xliff:g id="HOUR">%H</xliff:g>:<xliff:g id="MINUTE">%M</xliff:g>:<xliff:g id="SECOND">%S</xliff:g>"</string>
+    <!-- no translation found for date_and_time (9197690194373107109) -->
+    <skip />
+    <!-- no translation found for same_year_md1_md2 (9199324363135981317) -->
+    <string name="same_year_md1_md2">"<xliff:g id="DAY1">%3$s</xliff:g> <xliff:g id="MONTH1">%2$s</xliff:g> –  <xliff:g id="DAY2">%8$s</xliff:g> <xliff:g id="MONTH2">%7$s</xliff:g>"</string>
+    <!-- no translation found for same_year_wday1_md1_wday2_md2 (6006392413355305178) -->
+    <string name="same_year_wday1_md1_wday2_md2">"<xliff:g id="WEEKDAY1">%1$s</xliff:g>,  <xliff:g id="DAY1_0">%3$s</xliff:g> <xliff:g id="MONTH1">%2$s</xliff:g> – <xliff:g id="WEEKDAY2">%6$s</xliff:g>, <xliff:g id="DAY2_1">%8$s</xliff:g> <xliff:g id="MONTH2">%7$s</xliff:g>"</string>
+     <!-- no translation found for date_and_time (353898423108629694) -->
+    <string name="date_and_time">"<xliff:g id="HOUR">%H</xliff:g>:<xliff:g id="MINUTE">%M</xliff:g>:<xliff:g id="SECOND">%S</xliff:g> <xliff:g id="DAY">%-d</xliff:g> <xliff:g id="MONTH">%B</xliff:g> <xliff:g id="YEAR">%Y</xliff:g>"</string>
+    <!-- no translation found for same_year_mdy1_mdy2 (1576657593937827090) -->
+    <string name="same_year_mdy1_mdy2">"<xliff:g id="DAY1">%3$s</xliff:g> <xliff:g id="MONTH1">%2$s</xliff:g> – <xliff:g id="DAY2">%8$s</xliff:g> <xliff:g id="MONTH2">%7$s</xliff:g> <xliff:g id="YEAR">%9$s</xliff:g>"</string>
+    <!-- no translation found for same_year_wday1_mdy1_wday2_mdy2 (9135935796468891580) -->
+    <string name="same_year_wday1_mdy1_wday2_mdy2">"<xliff:g id="WEEKDAY1">%1$s</xliff:g>, <xliff:g id="DAY1_0">%3$s</xliff:g> <xliff:g id="MONTH1">%2$s</xliff:g> – <xliff:g id="WEEKDAY2">%6$s</xliff:g>, <xliff:g id="DAY2_1">%8$s</xliff:g> <xliff:g id="MONTH2">%7$s</xliff:g> <xliff:g id="YEAR">%9$s</xliff:g>"</string>
+    <!-- no translation found for same_year_md1_time1_md2_time2 (2172964106375558081) -->
+    <string name="same_year_md1_time1_md2_time2">" <xliff:g id="DAY1">%3$s</xliff:g> <xliff:g id="MONTH1">%2$s</xliff:g> <xliff:g id="TIME1">%5$s</xliff:g> – <xliff:g id="DAY2">%8$s</xliff:g> <xliff:g id="MONTH2">%7$s</xliff:g> <xliff:g id="TIME2">%10$s</xliff:g>"</string>
+    <!-- no translation found for same_year_wday1_md1_time1_wday2_md2_time2 (1702879534101786310) -->
+    <string name="same_year_wday1_md1_time1_wday2_md2_time2">"<xliff:g id="WEEKDAY1">%1$s</xliff:g>, <xliff:g id="DAY1_0">%3$s</xliff:g> <xliff:g id="MONTH1">%2$s</xliff:g>, <xliff:g id="TIME1">%5$s</xliff:g> – <xliff:g id="WEEKDAY2">%6$s</xliff:g>, <xliff:g id="DAY2_1">%8$s</xliff:g> <xliff:g id="MONTH2">%7$s</xliff:g> <xliff:g id="TIME2">%10$s</xliff:g>"</string>
+    <!-- no translation found for same_year_mdy1_time1_mdy2_time2 (2476443311723358767) -->
+    <string name="same_year_mdy1_time1_mdy2_time2">"<xliff:g id="DAY1">%3$s</xliff:g> <xliff:g id="MONTH1">%2$s</xliff:g>, <xliff:g id="YEAR1">%4$s</xliff:g>, <xliff:g id="TIME1">%5$s</xliff:g> – <xliff:g id="DAY2">%8$s</xliff:g> <xliff:g id="MONTH2">%7$s</xliff:g> <xliff:g id="YEAR2">%9$s</xliff:g>, <xliff:g id="TIME2">%10$s</xliff:g>"</string>
+    <!-- no translation found for same_year_wday1_mdy1_time1_wday2_mdy2_time2 (1564837340334069879) -->
+    <string name="same_year_wday1_mdy1_time1_wday2_mdy2_time2">"<xliff:g id="WEEKDAY1">%1$s</xliff:g>, <xliff:g id="DAY1_0">%3$s</xliff:g> <xliff:g id="MONTH1">%2$s</xliff:g> <xliff:g id="YEAR1">%4$s</xliff:g>, <xliff:g id="TIME1">%5$s</xliff:g> – <xliff:g id="WEEKDAY2">%6$s</xliff:g>, <xliff:g id="DAY2_1">%8$s</xliff:g> <xliff:g id="MONTH2">%7$s</xliff:g> <xliff:g id="YEAR2">%9$s</xliff:g>, <xliff:g id="TIME2">%10$s</xliff:g>"</string>
+    <!-- no translation found for numeric_md1_md2 (8908376522875100300) -->
+    <string name="numeric_md1_md2">"<xliff:g id="DAY1">%3$s</xliff:g>/<xliff:g id="MONTH1">%2$s</xliff:g> – <xliff:g id="DAY2">%8$s</xliff:g>/<xliff:g id="MONTH2">%7$s</xliff:g>"</string>
+    <!-- no translation found for numeric_wday1_md1_wday2_md2 (3239690882018292077) -->
+    <string name="numeric_wday1_md1_wday2_md2">"<xliff:g id="WEEKDAY1">%1$s</xliff:g>, <xliff:g id="DAY1_0">%3$s</xliff:g>/<xliff:g id="MONTH1">%2$s</xliff:g> – <xliff:g id="WEEKDAY2">%6$s</xliff:g>, <xliff:g id="DAY2_1">%8$s</xliff:g>/<xliff:g id="MONTH2">%7$s</xliff:g>"</string>
+    <!-- no translation found for numeric_mdy1_mdy2 (8883797176939233525) -->
+    <string name="numeric_mdy1_mdy2">"<xliff:g id="DAY1">%3$s</xliff:g>/<xliff:g id="MONTH1">%2$s</xliff:g>/<xliff:g id="YEAR1">%4$s</xliff:g> – <xliff:g id="DAY2">%8$s</xliff:g>/<xliff:g id="MONTH2">%7$s</xliff:g>/<xliff:g id="YEAR2">%9$s</xliff:g>"</string>
+    <!-- no translation found for numeric_wday1_mdy1_wday2_mdy2 (4150475769255828954) -->
+    <string name="numeric_wday1_mdy1_wday2_mdy2">"<xliff:g id="WEEKDAY1">%1$s</xliff:g>, <xliff:g id="DAY1_0">%3$s</xliff:g>/<xliff:g id="MONTH1">%2$s</xliff:g>/<xliff:g id="YEAR1">%4$s</xliff:g> – <xliff:g id="WEEKDAY2">%6$s</xliff:g>, <xliff:g id="DAY2_1">%8$s</xliff:g>/<xliff:g id="MONTH2">%7$s</xliff:g>/<xliff:g id="YEAR2">%9$s</xliff:g>"</string>
+    <!-- no translation found for numeric_md1_time1_md2_time2 (3624746590607741419) -->
+    <string name="numeric_md1_time1_md2_time2">"<xliff:g id="DAY1">%3$s</xliff:g>/<xliff:g id="MONTH1">%2$s</xliff:g>, <xliff:g id="TIME1">%5$s</xliff:g> – <xliff:g id="DAY2">%8$s</xliff:g>/<xliff:g id="MONTH2">%7$s</xliff:g>, <xliff:g id="TIME2">%10$s</xliff:g>"</string>
+    <!-- no translation found for numeric_wday1_md1_time1_wday2_md2_time2 (4258040955467298134) -->
+    <string name="numeric_wday1_md1_time1_wday2_md2_time2">"<xliff:g id="WEEKDAY1">%1$s</xliff:g>, <xliff:g id="DAY1_0">%3$s</xliff:g>/<xliff:g id="MONTH1">%2$s</xliff:g>, <xliff:g id="TIME1">%5$s</xliff:g> – <xliff:g id="WEEKDAY2">%6$s</xliff:g>, <xliff:g id="DAY2_1">%8$s</xliff:g>/<xliff:g id="MONTH2">%7$s</xliff:g>, <xliff:g id="TIME2">%10$s</xliff:g>"</string>
+    <!-- no translation found for numeric_mdy1_time1_mdy2_time2 (3598215409314517987) -->
+    <string name="numeric_mdy1_time1_mdy2_time2">"<xliff:g id="DAY1">%3$s</xliff:g>/<xliff:g id="MONTH1">%2$s</xliff:g>/<xliff:g id="YEAR1">%4$s</xliff:g>, <xliff:g id="TIME1">%5$s</xliff:g> – <xliff:g id="DAY2">%8$s</xliff:g>/<xliff:g id="MONTH2">%7$s</xliff:g>/<xliff:g id="YEAR2">%9$s</xliff:g>, <xliff:g id="TIME2">%10$s</xliff:g>"</string>
+    <!-- no translation found for numeric_wday1_mdy1_time1_wday2_mdy2_time2 (264076937155877259) -->
+    <string name="numeric_wday1_mdy1_time1_wday2_mdy2_time2">"<xliff:g id="WEEKDAY1">%1$s</xliff:g>, <xliff:g id="DAY1_0">%3$s</xliff:g>/<xliff:g id="MONTH1">%2$s</xliff:g>/<xliff:g id="YEAR1">%4$s</xliff:g>, <xliff:g id="TIME1">%5$s</xliff:g> – <xliff:g id="WEEKDAY2">%6$s</xliff:g>, <xliff:g id="DAY2_1">%8$s</xliff:g>/<xliff:g id="MONTH2">%7$s</xliff:g>/<xliff:g id="YEAR2">%9$s</xliff:g>, <xliff:g id="TIME2">%10$s</xliff:g>"</string>
+    <!-- no translation found for same_month_md1_md2 (2393563617438036111) -->
+    <string name="same_month_md1_md2">"<xliff:g id="DAY1" example="31">%3$s</xliff:g> \u2013 <xliff:g id="DAY2" example="3">%8$s</xliff:g>  <xliff:g id="MONTH1" example="Oct">%2$s</xliff:g>"</string>
+    <!-- no translation found for same_month_wday1_md1_wday2_md2 (1208946773794057819) -->
+    <string name="same_month_wday1_md1_wday2_md2">"<xliff:g id="WEEKDAY1">%1$s</xliff:g>, <xliff:g id="DAY1_0">%3$s</xliff:g>  <xliff:g id="MONTH1">%2$s</xliff:g> – <xliff:g id="WEEKDAY2">%6$s</xliff:g>, <xliff:g id="DAY2_1">%8$s</xliff:g>  <xliff:g id="MONTH2">%7$s</xliff:g>"</string>
+    <!-- no translation found for same_month_mdy1_mdy2 (3713236637869030492) -->
+    <string name="same_month_mdy1_mdy2">"<xliff:g id="DAY1" example="31">%3$s</xliff:g>  \u2013 <xliff:g id="DAY2" example="3">%8$s</xliff:g>  <xliff:g id="MONTH1" example="Oct">%2$s</xliff:g> <xliff:g id="YEAR2" example="2007">%9$s</xliff:g>"</string>
+    <!-- no translation found for same_month_wday1_mdy1_wday2_mdy2 (389638922479870472) -->
+    <string name="same_month_wday1_mdy1_wday2_mdy2">"<xliff:g id="WEEKDAY1">%1$s</xliff:g>, <xliff:g id="DAY1_0">%3$s</xliff:g>  <xliff:g id="MONTH1">%2$s</xliff:g> <xliff:g id="YEAR1">%4$s</xliff:g> – <xliff:g id="WEEKDAY2">%6$s</xliff:g>, <xliff:g id="DAY2_1">%8$s</xliff:g>  <xliff:g id="MONTH2">%7$s</xliff:g> <xliff:g id="YEAR2">%9$s</xliff:g>"</string>
+    <!-- no translation found for same_month_md1_time1_md2_time2 (7477075526337542685) -->
+    <string name="same_month_md1_time1_md2_time2">"<xliff:g id="DAY1">%3$s</xliff:g>  <xliff:g id="MONTH1">%2$s</xliff:g>, <xliff:g id="TIME1">%5$s</xliff:g> – <xliff:g id="DAY2">%8$s</xliff:g>  <xliff:g id="MONTH2">%7$s</xliff:g>, <xliff:g id="TIME2">%10$s</xliff:g>"</string>
+    <!-- no translation found for same_month_wday1_md1_time1_wday2_md2_time2 (3516978303779391173) -->
+    <string name="same_month_wday1_md1_time1_wday2_md2_time2">"<xliff:g id="WEEKDAY1">%1$s</xliff:g>, <xliff:g id="DAY1_0">%3$s</xliff:g>  <xliff:g id="MONTH1">%2$s</xliff:g>, <xliff:g id="TIME1">%5$s</xliff:g> – <xliff:g id="WEEKDAY2">%6$s</xliff:g>, <xliff:g id="DAY2_1">%8$s</xliff:g>  <xliff:g id="MONTH2">%7$s</xliff:g>, <xliff:g id="TIME2">%10$s</xliff:g>"</string>
+    <!-- no translation found for same_month_mdy1_time1_mdy2_time2 (7320410992514057310) -->
+    <string name="same_month_mdy1_time1_mdy2_time2">"<xliff:g id="DAY1">%3$s</xliff:g>  <xliff:g id="MONTH1">%2$s</xliff:g> <xliff:g id="YEAR1">%4$s</xliff:g>, <xliff:g id="TIME1">%5$s</xliff:g> – <xliff:g id="DAY2">%8$s</xliff:g>  <xliff:g id="MONTH2">%7$s</xliff:g> <xliff:g id="YEAR2">%9$s</xliff:g>, <xliff:g id="TIME2">%10$s</xliff:g>"</string>
+    <!-- no translation found for same_month_wday1_mdy1_time1_wday2_mdy2_time2 (1332950588774239228) -->
+    <string name="same_month_wday1_mdy1_time1_wday2_mdy2_time2">"<xliff:g id="WEEKDAY1">%1$s</xliff:g>, <xliff:g id="DAY1_0">%3$s</xliff:g>  <xliff:g id="MONTH1">%2$s</xliff:g> <xliff:g id="YEAR1">%4$s</xliff:g>, <xliff:g id="TIME1">%5$s</xliff:g> – <xliff:g id="WEEKDAY2">%6$s</xliff:g>, <xliff:g id="DAY2_1">%8$s</xliff:g>  <xliff:g id="MONTH2">%7$s</xliff:g> <xliff:g id="YEAR2">%9$s</xliff:g>, <xliff:g id="TIME2">%10$s</xliff:g>"</string>
+    <!-- no translation found for abbrev_month_day_year (5767271534015320250) -->
+    <string name="abbrev_month_day_year">"<xliff:g id="DAY">%-d</xliff:g>  <xliff:g id="MONTH">%b</xliff:g> <xliff:g id="YEAR">%Y</xliff:g>"</string>
+    <!-- no translation found for abbrev_month_year (8058929633673942490) -->
+    <skip />
+    <!-- no translation found for abbrev_month_day (458867920693482757) -->
+    <string name="abbrev_month_day">"<xliff:g id="day" example="31">%-d</xliff:g>  <xliff:g id="month" example="Oct">%b</xliff:g>"</string>
+    <!-- no translation found for abbrev_month (1674509986330181349) -->
+    <skip />
+    <!-- no translation found for day_of_week_long_sunday (9057662850446501884) -->
+    <skip />
+    <!-- no translation found for day_of_week_long_monday (7358451993082888343) -->
+    <skip />
+    <!-- no translation found for day_of_week_long_tuesday (2282901451170509613) -->
+    <skip />
+    <!-- no translation found for day_of_week_long_wednesday (2100217950343286482) -->
+    <skip />
+    <!-- no translation found for day_of_week_long_thursday (5475158963242863176) -->
+    <skip />
+    <!-- no translation found for day_of_week_long_friday (4081018004819837155) -->
+    <skip />
+    <!-- no translation found for day_of_week_long_saturday (1929694088305891795) -->
+    <skip />
+    <!-- no translation found for day_of_week_medium_sunday (6462580883948669820) -->
+    <skip />
+    <!-- no translation found for day_of_week_medium_monday (6960587654241349502) -->
+    <skip />
+    <!-- no translation found for day_of_week_medium_tuesday (7004462235990108936) -->
+    <skip />
+    <!-- no translation found for day_of_week_medium_wednesday (5688564741951314696) -->
+    <skip />
+    <!-- no translation found for day_of_week_medium_thursday (1784339868453982400) -->
+    <skip />
+    <!-- no translation found for day_of_week_medium_friday (4314577583604069357) -->
+    <skip />
+    <!-- no translation found for day_of_week_medium_saturday (70321191398427845) -->
+    <skip />
+    <!-- no translation found for day_of_week_short_sunday (7403409454572591357) -->
+    <skip />
+    <!-- no translation found for day_of_week_short_monday (5278358100012478239) -->
+    <skip />
+    <!-- no translation found for day_of_week_short_tuesday (5121116040712487059) -->
+    <skip />
+    <!-- no translation found for day_of_week_short_wednesday (1601079579293330319) -->
+    <skip />
+    <!-- no translation found for day_of_week_short_thursday (5863422096017401812) -->
+    <skip />
+    <!-- no translation found for day_of_week_short_friday (2916686031099723960) -->
+    <skip />
+    <!-- no translation found for day_of_week_short_saturday (8521564973195542073) -->
+    <skip />
+    <!-- no translation found for day_of_week_shorter_sunday (1650484495176707638) -->
+    <skip />
+    <!-- no translation found for day_of_week_shorter_monday (9133193697786876074) -->
+    <skip />
+    <!-- no translation found for day_of_week_shorter_tuesday (4012095408481489663) -->
+    <skip />
+    <!-- no translation found for day_of_week_shorter_wednesday (6279056612496078470) -->
+    <skip />
+    <!-- no translation found for day_of_week_shorter_thursday (2748599403545071011) -->
+    <skip />
+    <!-- no translation found for day_of_week_shorter_friday (5037282109124849673) -->
+    <skip />
+    <!-- no translation found for day_of_week_shorter_saturday (3208167155877833783) -->
+    <skip />
+    <!-- no translation found for day_of_week_shortest_sunday (4683862964821549758) -->
+    <skip />
+    <!-- no translation found for day_of_week_shortest_monday (6701142261471667000) -->
+    <skip />
+    <!-- no translation found for day_of_week_shortest_tuesday (9098171980161292477) -->
+    <skip />
+    <!-- no translation found for day_of_week_shortest_wednesday (655049238289460956) -->
+    <skip />
+    <!-- no translation found for day_of_week_shortest_thursday (7816913627500884083) -->
+    <skip />
+    <!-- no translation found for day_of_week_shortest_friday (903301878650619398) -->
+    <skip />
+    <!-- no translation found for day_of_week_shortest_saturday (5359692489649817988) -->
+    <skip />
+    <!-- no translation found for month_long_january (7128497801440564337) -->
+    <skip />
+    <!-- no translation found for month_long_february (7808570514581190617) -->
+    <skip />
+    <!-- no translation found for month_long_march (2061328556983796034) -->
+    <skip />
+    <!-- no translation found for month_long_april (6575007959043269919) -->
+    <skip />
+    <!-- no translation found for month_long_may (8404051103463071121) -->
+    <skip />
+    <!-- no translation found for month_long_june (6255771619238859451) -->
+    <skip />
+    <!-- no translation found for month_long_july (4129177743136800884) -->
+    <skip />
+    <!-- no translation found for month_long_august (5494331003296804494) -->
+    <skip />
+    <!-- no translation found for month_long_september (2691137479752033087) -->
+    <skip />
+    <!-- no translation found for month_long_october (7501261567327243313) -->
+    <skip />
+    <!-- no translation found for month_long_november (8759690753068763664) -->
+    <skip />
+    <!-- no translation found for month_long_december (4505008719696569497) -->
+    <skip />
+    <!-- no translation found for month_medium_january (2315492772833932512) -->
+    <skip />
+    <!-- no translation found for month_medium_february (118412521324313430) -->
+    <skip />
+    <!-- no translation found for month_medium_march (5546835583839352358) -->
+    <skip />
+    <!-- no translation found for month_medium_april (7052559668687733702) -->
+    <skip />
+    <!-- no translation found for month_medium_may (2825303871720116018) -->
+    <skip />
+    <!-- no translation found for month_medium_june (829843667101495271) -->
+    <skip />
+    <!-- no translation found for month_medium_july (5029778226925324789) -->
+    <skip />
+    <!-- no translation found for month_medium_august (8851230594641162805) -->
+    <skip />
+    <!-- no translation found for month_medium_september (8420590486625304647) -->
+    <skip />
+    <!-- no translation found for month_medium_october (1787382806172930239) -->
+    <skip />
+    <!-- no translation found for month_medium_november (675513809622370603) -->
+    <skip />
+    <!-- no translation found for month_medium_december (2934948295928978783) -->
+    <skip />
+    <!-- no translation found for month_shortest_january (6070060405144675883) -->
+    <skip />
+    <!-- no translation found for month_shortest_february (5632605004902176653) -->
+    <skip />
+    <!-- no translation found for month_shortest_march (4304231552356086624) -->
+    <skip />
+    <!-- no translation found for month_shortest_april (1166434066469385532) -->
+    <skip />
+    <!-- no translation found for month_shortest_may (9131326028845529001) -->
+    <skip />
+    <!-- no translation found for month_shortest_june (1875723154506665289) -->
+    <skip />
+    <!-- no translation found for month_shortest_july (2003596275389810773) -->
+    <skip />
+    <!-- no translation found for month_shortest_august (9120245162625763214) -->
+    <skip />
+    <!-- no translation found for month_shortest_september (7980651111022693669) -->
+    <skip />
+    <!-- no translation found for month_shortest_october (3640405450427788312) -->
+    <skip />
+    <!-- no translation found for month_shortest_november (4002935318566146993) -->
+    <skip />
+    <!-- no translation found for month_shortest_december (6213739417171334040) -->
+    <skip />
+    <!-- no translation found for elapsed_time_short_format_mm_ss (1294409362352514646) -->
+    <string name="elapsed_time_short_format_mm_ss">"<xliff:g id="MINUTES">%1$02d</xliff:g>:<xliff:g id="SECONDS">%2$02d</xliff:g>"</string>
+    <!-- no translation found for elapsed_time_short_format_h_mm_ss (2997059666628785039) -->
+    <string name="elapsed_time_short_format_h_mm_ss">"<xliff:g id="HOURS">%1$d</xliff:g>:<xliff:g id="MINUTES">%2$02d</xliff:g>:<xliff:g id="SECONDS">%3$02d</xliff:g>"</string>
+    <!-- no translation found for selectAll (691691810023908884) -->
+    <skip />
+    <!-- no translation found for cut (5845613239192595662) -->
+    <skip />
+    <!-- no translation found for cutAll (4474519683293791451) -->
+    <skip />
+    <!-- no translation found for copy (8603721575469529820) -->
+    <skip />
+    <!-- no translation found for copyAll (4777548804630476932) -->
+    <skip />
+    <!-- no translation found for paste (6458036735811828538) -->
+    <skip />
+    <!-- no translation found for copyUrl (5785708478767435812) -->
+    <skip />
+    <!-- no translation found for inputMethod (7911866729148111492) -->
+    <skip />
+    <!-- no translation found for editTextMenuTitle (3984253728638788023) -->
+    <skip />
+    <!-- no translation found for low_internal_storage_view_title (5997772070488639934) -->
+    <skip />
+    <!-- no translation found for low_internal_storage_view_text (2230118755295375293) -->
+    <skip />
+    <!-- no translation found for ok (4003878536083514869) -->
+    <skip />
+    <!-- no translation found for cancel (1527674037280267012) -->
+    <skip />
+    <!-- no translation found for yes (8185296114406773873) -->
+    <skip />
+    <!-- no translation found for no (2300685350903156262) -->
+    <skip />
+    <!-- no translation found for capital_on (8418242581217554942) -->
+    <skip />
+    <!-- no translation found for capital_off (8870368560477693851) -->
+    <skip />
+    <!-- no translation found for whichApplication (2828159696176255212) -->
+    <skip />
+    <!-- no translation found for alwaysUse (6433627451071144629) -->
+    <skip />
+    <!-- no translation found for clearDefaultHintMsg (5742432113023174321) -->
+    <skip />
+    <!-- no translation found for chooseActivity (7588691622928031978) -->
+    <skip />
+    <!-- no translation found for noApplications (4068560364116066745) -->
+    <skip />
+    <!-- no translation found for aerr_title (2654390351574026098) -->
+    <skip />
+    <!-- no translation found for aerr_application (4917288809565116720) -->
+    <skip />
+    <!-- no translation found for aerr_process (1273819861108073461) -->
+    <skip />
+    <!-- no translation found for anr_title (3305935690891435915) -->
+    <skip />
+    <!-- no translation found for anr_activity_application (1653036325679156678) -->
+    <skip />
+    <!-- no translation found for anr_activity_process (2674027618362070465) -->
+    <skip />
+    <!-- no translation found for anr_application_process (2163656674970221928) -->
+    <skip />
+    <!-- no translation found for anr_process (7747550780123472160) -->
+    <skip />
+    <!-- no translation found for force_close (9020954128872810669) -->
+    <skip />
+    <!-- no translation found for wait (7973775702304037058) -->
+    <skip />
+    <!-- no translation found for debug (857932504764728770) -->
+    <skip />
+    <!-- no translation found for sendText (6158329286172492543) -->
+    <skip />
+    <!-- no translation found for volume_ringtone (4121694816346562058) -->
+    <skip />
+    <!-- no translation found for volume_music (4869950240104717493) -->
+    <skip />
+    <!-- no translation found for volume_call (5723421277753250395) -->
+    <skip />
+    <!-- no translation found for volume_alarm (2752102730973081294) -->
+    <skip />
+    <!-- no translation found for volume_unknown (6908187627672375742) -->
+    <skip />
+    <!-- no translation found for ringtone_default (2873893375149093475) -->
+    <skip />
+    <!-- no translation found for ringtone_default_with_actual (5474076151665761913) -->
+    <skip />
+    <!-- no translation found for ringtone_silent (7477159279081654685) -->
+    <skip />
+    <!-- no translation found for ringtone_picker_title (7055241890764367884) -->
+    <skip />
+    <!-- no translation found for ringtone_unknown (6888219771401173795) -->
+    <skip />
+    <!-- no translation found for wifi_available:one (8168012881468888470) -->
+    <!-- no translation found for wifi_available:other (4666122955807117718) -->
+    <!-- no translation found for wifi_available_detailed:one (5107769161192143259) -->
+    <!-- no translation found for wifi_available_detailed:other (853347657960575809) -->
+    <!-- no translation found for select_character (3735110139249491726) -->
+    <skip />
+    <!-- no translation found for sms_control_default_app_name (7522184737840550841) -->
+    <skip />
+    <!-- no translation found for sms_control_title (2742400596989418394) -->
+    <skip />
+    <!-- no translation found for sms_control_message (3447126217666595989) -->
+    <skip />
+    <!-- no translation found for sms_control_yes (8839660939359273650) -->
+    <skip />
+    <!-- no translation found for sms_control_no (909756849988183801) -->
+    <skip />
+    <!-- no translation found for date_time_set (2495199891239480952) -->
+    <skip />
+    <!-- no translation found for default_permission_group (7742780381379652409) -->
+    <skip />
+    <!-- no translation found for no_permissions (85461124044682315) -->
+    <skip />
+    <!-- no translation found for perms_hide (4145325555929151849) -->
+    <skip />
+    <!-- no translation found for perms_show_all (6040194843455403173) -->
+    <skip />
+    <!-- no translation found for googlewebcontenthelper_loading (2140804350507245589) -->
+    <skip />
+    <!-- no translation found for usb_storage_title (8699631567051394409) -->
+    <skip />
+    <!-- no translation found for usb_storage_message (5344039189213308733) -->
+    <skip />
+    <!-- no translation found for usb_storage_button_mount (6700104384375121662) -->
+    <skip />
+    <!-- no translation found for usb_storage_button_unmount (465869657252626688) -->
+    <skip />
+    <!-- no translation found for usb_storage_error_message (3192564550748426087) -->
+    <skip />
+    <!-- no translation found for usb_storage_notification_title (6237028017872246940) -->
+    <skip />
+    <!-- no translation found for usb_storage_notification_message (7371717280517625905) -->
+    <skip />
+    <!-- no translation found for select_input_method (2658280517827502015) -->
+    <skip />
+    <!-- no translation found for fast_scroll_alphabet (1017432309285755759) -->
+    <skip />
+    <!-- no translation found for fast_scroll_numeric_alphabet (3092587363718901074) -->
+    <skip />
+    <!-- no translation found for candidates_style (7738463880139922176) -->
+    <skip />
+</resources>
diff --git a/core/res/res/values-en-rGB/arrays.xml b/core/res/res/values-en-rGB/arrays.xml
new file mode 100644
index 0000000..02a0e0f
--- /dev/null
+++ b/core/res/res/values-en-rGB/arrays.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* 
+**
+** Copyright 2006, Google Inc.
+**
+** 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.
+*/
+-->
+<resources>
+
+    <!-- Do not translate. -->
+    <integer-array name="maps_starting_lat_lng">
+        <item>51500208</item>
+        <item>-126729</item>
+    </integer-array>
+    <!-- Do not translate. -->
+    <integer-array name="maps_starting_zoom">
+        <item>5</item>
+    </integer-array>
+
+</resources>
diff --git a/core/res/res/values-en-rGB/strings.xml b/core/res/res/values-en-rGB/strings.xml
new file mode 100644
index 0000000..d9cf3d5
--- /dev/null
+++ b/core/res/res/values-en-rGB/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="byteShort">B</string>
+</resources>
diff --git a/core/res/res/values-en-rSG/arrays.xml b/core/res/res/values-en-rSG/arrays.xml
new file mode 100644
index 0000000..ee1e64b
--- /dev/null
+++ b/core/res/res/values-en-rSG/arrays.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* //device/apps/common/assets/res/any/colors.xml
+**
+** Copyright 2006, Google Inc.
+**
+** 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.
+*/
+-->
+<resources>
+
+    <!-- Do not translate. -->
+    <integer-array name="maps_starting_lat_lng">
+        <item>1333333</item>
+        <item>103875000</item>
+    </integer-array>
+    <!-- Do not translate. -->
+    <integer-array name="maps_starting_zoom">
+        <item>4</item>
+    </integer-array>
+
+</resources>
diff --git a/core/res/res/values-en-rSG/strings.xml b/core/res/res/values-en-rSG/strings.xml
new file mode 100644
index 0000000..6850a5d
--- /dev/null
+++ b/core/res/res/values-en-rSG/strings.xml
@@ -0,0 +1,1257 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="byteShort">"B"</string>
+    <!-- no translation found for kilobyteShort (5865542430193761682) -->
+    <skip />
+    <!-- no translation found for megabyteShort (112984851085937882) -->
+    <skip />
+    <!-- no translation found for gigabyteShort (8586075069559273847) -->
+    <skip />
+    <!-- no translation found for terabyteShort (5828502357595687794) -->
+    <skip />
+    <!-- no translation found for petabyteShort (7523248732657962413) -->
+    <skip />
+    <!-- no translation found for untitled (284687023829080340) -->
+    <skip />
+    <!-- no translation found for ellipsis (8538883953764277342) -->
+    <skip />
+    <!-- no translation found for emptyPhoneNumber (6416283285732095329) -->
+    <skip />
+    <!-- no translation found for unknownName (3974255879290140525) -->
+    <skip />
+    <!-- no translation found for defaultVoiceMailAlphaTag (6484324201071049939) -->
+    <skip />
+    <!-- no translation found for defaultMsisdnAlphaTag (4953008223227371928) -->
+    <skip />
+    <!-- no translation found for mmiError (7480678835624852655) -->
+    <skip />
+    <!-- no translation found for serviceEnabled (4042194305396115167) -->
+    <skip />
+    <!-- no translation found for serviceEnabledFor (638808419103886277) -->
+    <skip />
+    <!-- no translation found for serviceDisabled (1059935666763511541) -->
+    <skip />
+    <!-- no translation found for serviceRegistered (7639869107156932038) -->
+    <skip />
+    <!-- no translation found for serviceErased (4602215208593071820) -->
+    <skip />
+    <!-- no translation found for passwordIncorrect (5142040651297346232) -->
+    <skip />
+    <!-- no translation found for mmiComplete (3178168770150013486) -->
+    <skip />
+    <!-- no translation found for badPin (5103184589972647739) -->
+    <skip />
+    <!-- no translation found for badPuk (2200634943393540609) -->
+    <skip />
+    <!-- no translation found for mismatchPin (5055729703806180857) -->
+    <skip />
+    <!-- no translation found for invalidPin (6201854814319326475) -->
+    <skip />
+    <!-- no translation found for needPuk (4788728144863892764) -->
+    <skip />
+    <!-- no translation found for needPuk2 (7056908944942451033) -->
+    <skip />
+    <!-- no translation found for ClipMmi (5649729434121615509) -->
+    <skip />
+    <!-- no translation found for ClirMmi (5220979296096544477) -->
+    <skip />
+    <!-- no translation found for CfMmi (4998483717856803914) -->
+    <skip />
+    <!-- no translation found for CwMmi (5678103638951836350) -->
+    <skip />
+    <!-- no translation found for BaMmi (6030555200442855833) -->
+    <skip />
+    <!-- no translation found for PwdMmi (2549941247959366670) -->
+    <skip />
+    <!-- no translation found for PinMmi (2463353963837922189) -->
+    <skip />
+    <!-- no translation found for CLIRDefaultOnNextCallOn (4005921990799469144) -->
+    <skip />
+    <!-- no translation found for CLIRDefaultOnNextCallOff (1497360760012205230) -->
+    <skip />
+    <!-- no translation found for CLIRDefaultOffNextCallOn (604591440398078227) -->
+    <skip />
+    <!-- no translation found for CLIRDefaultOffNextCallOff (5114039908683246336) -->
+    <skip />
+    <!-- no translation found for serviceNotProvisioned (3754416031529306610) -->
+    <skip />
+    <!-- no translation found for CLIRPermanent (3819908477891272611) -->
+    <skip />
+    <!-- no translation found for serviceClassVoice (3059107563169935913) -->
+    <skip />
+    <!-- no translation found for serviceClassData (2669025626575716504) -->
+    <skip />
+    <!-- no translation found for serviceClassFAX (973109472405729679) -->
+    <skip />
+    <!-- no translation found for serviceClassSMS (3857383928743625711) -->
+    <skip />
+    <!-- no translation found for serviceClassDataAsync (8526461993032174729) -->
+    <skip />
+    <!-- no translation found for serviceClassDataSync (2461138395498381801) -->
+    <skip />
+    <!-- no translation found for serviceClassPacket (8119604233041078065) -->
+    <skip />
+    <!-- no translation found for serviceClassPAD (202892636830042266) -->
+    <skip />
+    <!-- no translation found for cfTemplateNotForwarded (3171755805856206604) -->
+    <skip />
+    <!-- no translation found for cfTemplateForwarded (2468661573318024785) -->
+    <skip />
+    <!-- no translation found for cfTemplateForwardedTime (5151810870794744740) -->
+    <skip />
+    <!-- no translation found for cfTemplateRegistered (5685211900474527085) -->
+    <skip />
+    <!-- no translation found for cfTemplateRegisteredTime (2978918277762252776) -->
+    <skip />
+    <!-- no translation found for httpErrorOk (984913805621139001) -->
+    <skip />
+    <!-- no translation found for httpError (9177990053748151835) -->
+    <skip />
+    <!-- no translation found for httpErrorLookup (5251341716070330936) -->
+    <skip />
+    <!-- no translation found for httpErrorUnsupportedAuthScheme (2865679883634239474) -->
+    <skip />
+    <!-- no translation found for httpErrorAuth (1637382600929594620) -->
+    <skip />
+    <!-- no translation found for httpErrorProxyAuth (5947648983995807455) -->
+    <skip />
+    <!-- no translation found for httpErrorConnect (129984292497034683) -->
+    <skip />
+    <!-- no translation found for httpErrorIO (8128922048686581131) -->
+    <skip />
+    <!-- no translation found for httpErrorTimeout (8357966263983739012) -->
+    <skip />
+    <!-- no translation found for httpErrorRedirectLoop (4122379005100433886) -->
+    <skip />
+    <!-- no translation found for httpErrorUnsupportedScheme (4072339858288462569) -->
+    <skip />
+    <!-- no translation found for httpErrorFailedSslHandshake (2316625025255452595) -->
+    <skip />
+    <!-- no translation found for httpErrorBadUrl (8885244563103716039) -->
+    <skip />
+    <!-- no translation found for httpErrorFile (1408273621719669493) -->
+    <skip />
+    <!-- no translation found for httpErrorFileNotFound (2309088465300506314) -->
+    <skip />
+    <!-- no translation found for httpErrorTooManyRequests (3764334538393544875) -->
+    <skip />
+    <!-- no translation found for contentServiceSync (4863236165350475642) -->
+    <skip />
+    <!-- no translation found for contentServiceSyncNotificationTitle (6855304679069026824) -->
+    <skip />
+    <!-- no translation found for contentServiceTooManyDeletesNotificationDesc (8477597194404210723) -->
+    <skip />
+    <!-- no translation found for low_memory (4191592786596642367) -->
+    <skip />
+    <!-- no translation found for me (4616693653158602117) -->
+    <skip />
+    <!-- no translation found for power_dialog (8210256011408959109) -->
+    <skip />
+    <!-- no translation found for silent_mode (5218239246946854300) -->
+    <skip />
+    <!-- no translation found for turn_on_radio (1901054698789840131) -->
+    <skip />
+    <!-- no translation found for turn_off_radio (2870296409392615956) -->
+    <skip />
+    <!-- no translation found for screen_lock (1560333453597081877) -->
+    <skip />
+    <!-- no translation found for power_off (2412024417733516836) -->
+    <skip />
+    <!-- no translation found for shutdown_progress (3735034517335251808) -->
+    <skip />
+    <!-- no translation found for shutdown_confirm (699224922526414097) -->
+    <skip />
+    <!-- no translation found for no_recent_tasks (1367712919998349373) -->
+    <skip />
+    <!-- no translation found for global_actions (8299888906525675157) -->
+    <skip />
+    <!-- no translation found for global_action_lock (5943677976245541105) -->
+    <skip />
+    <!-- no translation found for global_action_power_off (3143027278596694254) -->
+    <skip />
+    <!-- no translation found for global_action_toggle_silent_mode (5849335789367070450) -->
+    <skip />
+    <!-- no translation found for global_action_silent_mode_on_status (6053429980569202260) -->
+    <skip />
+    <!-- no translation found for global_action_silent_mode_off_status (1994514127029249081) -->
+    <skip />
+    <!-- no translation found for safeMode (3375134507868534320) -->
+    <skip />
+    <!-- no translation found for permgrouplab_costMoney (904087853776533085) -->
+    <skip />
+    <!-- no translation found for permgroupdesc_costMoney (4662370555643969515) -->
+    <skip />
+    <!-- no translation found for permgrouplab_messages (2984053976424233925) -->
+    <skip />
+    <!-- no translation found for permgroupdesc_messages (2129093134354989379) -->
+    <skip />
+    <!-- no translation found for permgrouplab_personalInfo (4548406335021507392) -->
+    <skip />
+    <!-- no translation found for permgroupdesc_personalInfo (8499310823817958034) -->
+    <skip />
+    <!-- no translation found for permgrouplab_location (8535677827151907069) -->
+    <skip />
+    <!-- no translation found for permgroupdesc_location (2341662219604651887) -->
+    <skip />
+    <!-- no translation found for permgrouplab_network (3597781730625751831) -->
+    <skip />
+    <!-- no translation found for permgroupdesc_network (8332572695347918340) -->
+    <skip />
+    <!-- no translation found for permgrouplab_accounts (8631201594657951893) -->
+    <skip />
+    <!-- no translation found for permgroupdesc_accounts (443982868906396781) -->
+    <skip />
+    <!-- no translation found for permgrouplab_hardwareControls (5074512938567152139) -->
+    <skip />
+    <!-- no translation found for permgroupdesc_hardwareControls (8772503144945278440) -->
+    <skip />
+    <!-- no translation found for permgrouplab_phoneCalls (7096448531266882376) -->
+    <skip />
+    <!-- no translation found for permgroupdesc_phoneCalls (6703873478653366233) -->
+    <skip />
+    <!-- no translation found for permgrouplab_systemTools (1840847965111633430) -->
+    <skip />
+    <!-- no translation found for permgroupdesc_systemTools (2810337951496685271) -->
+    <skip />
+    <!-- no translation found for permgrouplab_developmentTools (692844635256963358) -->
+    <skip />
+    <!-- no translation found for permgroupdesc_developmentTools (5253915519857796400) -->
+    <skip />
+    <!-- no translation found for permlab_statusBar (8789506912215455922) -->
+    <skip />
+    <!-- no translation found for permdesc_statusBar (5034247171231682403) -->
+    <skip />
+    <!-- no translation found for permlab_expandStatusBar (6382500803293284173) -->
+    <skip />
+    <!-- no translation found for permdesc_expandStatusBar (90953162060681436) -->
+    <skip />
+    <!-- no translation found for permlab_processOutgoingCalls (786316295241100144) -->
+    <skip />
+    <!-- no translation found for permdesc_processOutgoingCalls (1655242138991854396) -->
+    <skip />
+    <!-- no translation found for permlab_receiveSms (5820796051959871222) -->
+    <skip />
+    <!-- no translation found for permdesc_receiveSms (2265740044646990161) -->
+    <skip />
+    <!-- no translation found for permlab_receiveMms (7983091218880782611) -->
+    <skip />
+    <!-- no translation found for permdesc_receiveMms (3641275586518289960) -->
+    <skip />
+    <!-- no translation found for permlab_sendSms (4713837923748234081) -->
+    <skip />
+    <!-- no translation found for permdesc_sendSms (7126594387176704010) -->
+    <skip />
+    <!-- no translation found for permlab_readSms (4256004535185449429) -->
+    <skip />
+    <!-- no translation found for permdesc_readSms (4586480500886941902) -->
+    <skip />
+    <!-- no translation found for permlab_writeSms (8453452414726246828) -->
+    <skip />
+    <!-- no translation found for permdesc_writeSms (1036408118901361812) -->
+    <skip />
+    <!-- no translation found for permlab_receiveWapPush (5726837205927152203) -->
+    <skip />
+    <!-- no translation found for permdesc_receiveWapPush (4779188629794134886) -->
+    <skip />
+    <!-- no translation found for permlab_getTasks (357640569227780364) -->
+    <skip />
+    <!-- no translation found for permdesc_getTasks (2916615403728003200) -->
+    <skip />
+    <!-- no translation found for permlab_reorderTasks (4758862288285224517) -->
+    <skip />
+    <!-- no translation found for permdesc_reorderTasks (7507060843941912021) -->
+    <skip />
+    <!-- no translation found for permlab_setDebugApp (2973363275929449444) -->
+    <skip />
+    <!-- no translation found for permdesc_setDebugApp (5720449860498265972) -->
+    <skip />
+    <!-- no translation found for permlab_changeConfiguration (8581093564179818627) -->
+    <skip />
+    <!-- no translation found for permdesc_changeConfiguration (4055366453803187171) -->
+    <skip />
+    <!-- no translation found for permlab_restartPackages (5836367540766044606) -->
+    <skip />
+    <!-- no translation found for permdesc_restartPackages (1764965996765573321) -->
+    <skip />
+    <!-- no translation found for permlab_setProcessForeground (4860990420780868638) -->
+    <skip />
+    <!-- no translation found for permdesc_setProcessForeground (3795477299954784360) -->
+    <skip />
+    <!-- no translation found for permlab_forceBack (4737517869935566733) -->
+    <skip />
+    <!-- no translation found for permdesc_forceBack (5579316297001154697) -->
+    <skip />
+    <!-- no translation found for permlab_dump (3177569414212943167) -->
+    <skip />
+    <!-- no translation found for permdesc_dump (1815913623373011608) -->
+    <skip />
+    <!-- no translation found for permlab_addSystemService (9166015020584794942) -->
+    <skip />
+    <!-- no translation found for permdesc_addSystemService (2310425587289835743) -->
+    <skip />
+    <!-- no translation found for permlab_runSetActivityWatcher (2615943932761994905) -->
+    <skip />
+    <!-- no translation found for permdesc_runSetActivityWatcher (2488524206195482220) -->
+    <skip />
+    <!-- no translation found for permlab_broadcastPackageRemoved (355775368495637820) -->
+    <skip />
+    <!-- no translation found for permdesc_broadcastPackageRemoved (6486181398191058385) -->
+    <skip />
+    <!-- no translation found for permlab_broadcastSmsReceived (1994692154847312518) -->
+    <skip />
+    <!-- no translation found for permdesc_broadcastSmsReceived (6072362543164841432) -->
+    <skip />
+    <!-- no translation found for permlab_broadcastWapPush (3070023012636951639) -->
+    <skip />
+    <!-- no translation found for permdesc_broadcastWapPush (726912255218924336) -->
+    <skip />
+    <!-- no translation found for permlab_setProcessLimit (5190694306017260601) -->
+    <skip />
+    <!-- no translation found for permdesc_setProcessLimit (593938303319848578) -->
+    <skip />
+    <!-- no translation found for permlab_setAlwaysFinish (8745533365504920540) -->
+    <skip />
+    <!-- no translation found for permdesc_setAlwaysFinish (2437195869854312148) -->
+    <skip />
+    <!-- no translation found for permlab_fotaUpdate (1813039882829307079) -->
+    <skip />
+    <!-- no translation found for permdesc_fotaUpdate (2544137712607584763) -->
+    <skip />
+    <!-- no translation found for permlab_batteryStats (1598947993704535568) -->
+    <skip />
+    <!-- no translation found for permdesc_batteryStats (6247598531831307989) -->
+    <skip />
+    <!-- no translation found for permlab_internalSystemWindow (5780262737320556654) -->
+    <skip />
+    <!-- no translation found for permdesc_internalSystemWindow (6495031598062517795) -->
+    <skip />
+    <!-- no translation found for permlab_systemAlertWindow (843729657746130626) -->
+    <skip />
+    <!-- no translation found for permdesc_systemAlertWindow (2731854380682210852) -->
+    <skip />
+    <!-- no translation found for permlab_setAnimationScale (2419250686027992384) -->
+    <skip />
+    <!-- no translation found for permdesc_setAnimationScale (8518027785481727264) -->
+    <skip />
+    <!-- no translation found for permlab_manageAppTokens (1033424552444304594) -->
+    <skip />
+    <!-- no translation found for permdesc_manageAppTokens (7285840918912623550) -->
+    <skip />
+    <!-- no translation found for permlab_injectEvents (1383601196263145482) -->
+    <skip />
+    <!-- no translation found for permdesc_injectEvents (840097509341464737) -->
+    <skip />
+    <!-- no translation found for permlab_readInputState (2723668746963882102) -->
+    <skip />
+    <!-- no translation found for permdesc_readInputState (4651137638757852001) -->
+    <skip />
+    <!-- no translation found for permlab_setOrientation (1112555600323148680) -->
+    <skip />
+    <!-- no translation found for permdesc_setOrientation (1960269530378827858) -->
+    <skip />
+    <!-- no translation found for permlab_signalPersistentProcesses (8511163028160623175) -->
+    <skip />
+    <!-- no translation found for permdesc_signalPersistentProcesses (1099349638354917733) -->
+    <skip />
+    <!-- no translation found for permlab_persistentActivity (8163108526929094627) -->
+    <skip />
+    <!-- no translation found for permdesc_persistentActivity (5258975883823299624) -->
+    <skip />
+    <!-- no translation found for permlab_deletePackages (5005536434839333208) -->
+    <skip />
+    <!-- no translation found for permdesc_deletePackages (2687196995215591923) -->
+    <skip />
+    <!-- no translation found for permlab_clearAppUserData (3858185484601410171) -->
+    <skip />
+    <!-- no translation found for permdesc_clearAppUserData (7233537744753081136) -->
+    <skip />
+    <!-- no translation found for permlab_deleteCacheFiles (7362746182961997888) -->
+    <skip />
+    <!-- no translation found for permdesc_deleteCacheFiles (8293849509208181266) -->
+    <skip />
+    <!-- no translation found for permlab_getPackageSize (6743556676630447973) -->
+    <skip />
+    <!-- no translation found for permdesc_getPackageSize (2893996655828539776) -->
+    <skip />
+    <!-- no translation found for permlab_installPackages (1637554234554641998) -->
+    <skip />
+    <!-- no translation found for permdesc_installPackages (4747794850590875195) -->
+    <skip />
+    <!-- no translation found for permlab_clearAppCache (7860214328511700776) -->
+    <skip />
+    <!-- no translation found for permdesc_clearAppCache (5203820862573167878) -->
+    <skip />
+    <!-- no translation found for permlab_readLogs (6653488552442991707) -->
+    <skip />
+    <!-- no translation found for permdesc_readLogs (356352685800884319) -->
+    <skip />
+    <!-- no translation found for permlab_diagnostic (2955142476313469329) -->
+    <skip />
+    <!-- no translation found for permdesc_diagnostic (1282409892215520166) -->
+    <skip />
+    <!-- no translation found for permlab_changeComponentState (8107835954049971459) -->
+    <skip />
+    <!-- no translation found for permdesc_changeComponentState (1791096057705836844) -->
+    <skip />
+    <!-- no translation found for permlab_setPreferredApplications (4355701371185331520) -->
+    <skip />
+    <!-- no translation found for permdesc_setPreferredApplications (9029326613767614711) -->
+    <skip />
+    <!-- no translation found for permlab_writeSettings (2915467191611898256) -->
+    <skip />
+    <!-- no translation found for permdesc_writeSettings (8492982548350342641) -->
+    <skip />
+    <!-- no translation found for permlab_writeSecureSettings (4851801872124242319) -->
+    <skip />
+    <!-- no translation found for permdesc_writeSecureSettings (2080620249472761366) -->
+    <skip />
+    <!-- no translation found for permlab_writeGservices (296370685945777755) -->
+    <skip />
+    <!-- no translation found for permdesc_writeGservices (2496928471286495053) -->
+    <skip />
+    <!-- no translation found for permlab_receiveBootCompleted (5598819384278633845) -->
+    <skip />
+    <!-- no translation found for permdesc_receiveBootCompleted (3197439472472771192) -->
+    <skip />
+    <!-- no translation found for permlab_broadcastSticky (8142357333543531543) -->
+    <skip />
+    <!-- no translation found for permdesc_broadcastSticky (8488762822718743531) -->
+    <skip />
+    <!-- no translation found for permlab_readContacts (5116003370450871686) -->
+    <skip />
+    <!-- no translation found for permdesc_readContacts (3499378044902258770) -->
+    <skip />
+    <!-- no translation found for permlab_writeContacts (1555136823460617179) -->
+    <skip />
+    <!-- no translation found for permdesc_writeContacts (4787318403287293114) -->
+    <skip />
+    <!-- no translation found for permlab_writeOwnerData (8036840529708535113) -->
+    <skip />
+    <!-- no translation found for permdesc_writeOwnerData (5873447528845878348) -->
+    <skip />
+    <!-- no translation found for permlab_readOwnerData (1847040178513733757) -->
+    <skip />
+    <!-- no translation found for permdesc_readOwnerData (7563299529149214764) -->
+    <skip />
+    <!-- no translation found for permlab_readCalendar (2111238731453410895) -->
+    <skip />
+    <!-- no translation found for permdesc_readCalendar (4408253940601239114) -->
+    <skip />
+    <!-- no translation found for permlab_writeCalendar (7518052789370653396) -->
+    <skip />
+    <!-- no translation found for permdesc_writeCalendar (8057304232140147596) -->
+    <skip />
+    <!-- no translation found for permlab_accessMockLocation (321094551062270213) -->
+    <skip />
+    <!-- no translation found for permdesc_accessMockLocation (3651565866471419739) -->
+    <skip />
+    <!-- no translation found for permlab_accessLocationExtraCommands (8291822077788811687) -->
+    <skip />
+    <!-- no translation found for permdesc_accessLocationExtraCommands (5135782633548630731) -->
+    <skip />
+    <!-- no translation found for permlab_accessFineLocation (4846261651944924865) -->
+    <skip />
+    <!-- no translation found for permdesc_accessFineLocation (3572307331039348419) -->
+    <skip />
+    <!-- no translation found for permlab_accessCoarseLocation (1760779730797169189) -->
+    <skip />
+    <!-- no translation found for permdesc_accessCoarseLocation (8878785899768310712) -->
+    <skip />
+    <!-- no translation found for permlab_accessSurfaceFlinger (6405475452322847618) -->
+    <skip />
+    <!-- no translation found for permdesc_accessSurfaceFlinger (5348283543622360967) -->
+    <skip />
+    <!-- no translation found for permlab_readFrameBuffer (4655248388550039199) -->
+    <skip />
+    <!-- no translation found for permdesc_readFrameBuffer (794888199105081402) -->
+    <skip />
+    <!-- no translation found for permlab_modifyAudioSettings (1587341813207960943) -->
+    <skip />
+    <!-- no translation found for permdesc_modifyAudioSettings (1447143004892708149) -->
+    <skip />
+    <!-- no translation found for permlab_recordAudio (4447848534036991667) -->
+    <skip />
+    <!-- no translation found for permdesc_recordAudio (6936874682400894820) -->
+    <skip />
+    <!-- no translation found for permlab_camera (1944473855727060380) -->
+    <skip />
+    <!-- no translation found for permdesc_camera (5978058582323766022) -->
+    <skip />
+    <!-- no translation found for permlab_brick (4749832243303289777) -->
+    <skip />
+    <!-- no translation found for permdesc_brick (7428524578693695766) -->
+    <skip />
+    <!-- no translation found for permlab_reboot (8844650672567077423) -->
+    <skip />
+    <!-- no translation found for permdesc_reboot (4704919552870918328) -->
+    <skip />
+    <!-- no translation found for permlab_mount_unmount_filesystems (1009574821038043781) -->
+    <skip />
+    <!-- no translation found for permdesc_mount_unmount_filesystems (100792065894811109) -->
+    <skip />
+    <!-- no translation found for permlab_vibrate (61984555644467146) -->
+    <skip />
+    <!-- no translation found for permdesc_vibrate (7831723100758509238) -->
+    <skip />
+    <!-- no translation found for permlab_flashlight (9097145977808182652) -->
+    <skip />
+    <!-- no translation found for permdesc_flashlight (7851502731988978358) -->
+    <skip />
+    <!-- no translation found for permlab_hardware_test (4103324677866524254) -->
+    <skip />
+    <!-- no translation found for permdesc_hardware_test (7315242723603994769) -->
+    <skip />
+    <!-- no translation found for permlab_callPhone (168275616535116686) -->
+    <skip />
+    <!-- no translation found for permdesc_callPhone (1852033967965785973) -->
+    <skip />
+    <!-- no translation found for permlab_callPrivileged (2166923597287697159) -->
+    <skip />
+    <!-- no translation found for permdesc_callPrivileged (5109789447971735501) -->
+    <skip />
+    <!-- no translation found for permlab_locationUpdates (4216418293360456836) -->
+    <skip />
+    <!-- no translation found for permdesc_locationUpdates (7635814693478743648) -->
+    <skip />
+    <!-- no translation found for permlab_checkinProperties (2260796787386280708) -->
+    <skip />
+    <!-- no translation found for permdesc_checkinProperties (3508022022841741945) -->
+    <skip />
+    <!-- no translation found for permlab_modifyPhoneState (7791696535097912313) -->
+    <skip />
+    <!-- no translation found for permdesc_modifyPhoneState (6352405226410454770) -->
+    <skip />
+    <!-- no translation found for permlab_readPhoneState (7320082586621086653) -->
+    <skip />
+    <!-- no translation found for permdesc_readPhoneState (8004450067066407969) -->
+    <skip />
+    <!-- no translation found for permlab_wakeLock (1591164750935072136) -->
+    <skip />
+    <!-- no translation found for permdesc_wakeLock (160471538196734936) -->
+    <skip />
+    <!-- no translation found for permlab_devicePower (9214865067086065548) -->
+    <skip />
+    <!-- no translation found for permdesc_devicePower (5608364066480036402) -->
+    <skip />
+    <!-- no translation found for permlab_factoryTest (7786199300637896247) -->
+    <skip />
+    <!-- no translation found for permdesc_factoryTest (3466066005210542042) -->
+    <skip />
+    <!-- no translation found for permlab_setWallpaper (2256730637138641725) -->
+    <skip />
+    <!-- no translation found for permdesc_setWallpaper (3034653140208685093) -->
+    <skip />
+    <!-- no translation found for permlab_setWallpaperHints (4192438316932517807) -->
+    <skip />
+    <!-- no translation found for permdesc_setWallpaperHints (738757439960921674) -->
+    <skip />
+    <!-- no translation found for permlab_masterClear (6155403967270586906) -->
+    <skip />
+    <!-- no translation found for permdesc_masterClear (4213553172342689754) -->
+    <skip />
+    <!-- no translation found for permlab_setTimeZone (477196167239548690) -->
+    <skip />
+    <!-- no translation found for permdesc_setTimeZone (8564892020460841198) -->
+    <skip />
+    <!-- no translation found for permlab_getAccounts (2764070033402295170) -->
+    <skip />
+    <!-- no translation found for permdesc_getAccounts (1203491378748649898) -->
+    <skip />
+    <!-- no translation found for permlab_accessNetworkState (2032916924886010827) -->
+    <skip />
+    <!-- no translation found for permdesc_accessNetworkState (7081329402551195933) -->
+    <skip />
+    <!-- no translation found for permlab_createNetworkSockets (4706698319966917864) -->
+    <skip />
+    <!-- no translation found for permdesc_createNetworkSockets (2580337178778551792) -->
+    <skip />
+    <!-- no translation found for permlab_writeApnSettings (3190585220761979369) -->
+    <skip />
+    <!-- no translation found for permdesc_writeApnSettings (4093875220468761052) -->
+    <skip />
+    <!-- no translation found for permlab_changeNetworkState (2710779001260856872) -->
+    <skip />
+    <!-- no translation found for permdesc_changeNetworkState (8076109230787022270) -->
+    <skip />
+    <!-- no translation found for permlab_accessWifiState (3613679494230374297) -->
+    <skip />
+    <!-- no translation found for permdesc_accessWifiState (8226508433563326925) -->
+    <skip />
+    <!-- no translation found for permlab_changeWifiState (6043889338995432957) -->
+    <skip />
+    <!-- no translation found for permdesc_changeWifiState (7829372845909567994) -->
+    <skip />
+    <!-- no translation found for permlab_bluetoothAdmin (5513286736585647334) -->
+    <skip />
+    <!-- no translation found for permdesc_bluetoothAdmin (1838208497914347365) -->
+    <skip />
+    <!-- no translation found for permlab_bluetooth (6378797624765639115) -->
+    <skip />
+    <!-- no translation found for permdesc_bluetooth (8592386018922265273) -->
+    <skip />
+    <!-- no translation found for permlab_disableKeyguard (4574886811903233903) -->
+    <skip />
+    <!-- no translation found for permdesc_disableKeyguard (815972646344251271) -->
+    <skip />
+    <!-- no translation found for permlab_readSyncSettings (8818819977141505127) -->
+    <skip />
+    <!-- no translation found for permdesc_readSyncSettings (8454705401908767847) -->
+    <skip />
+    <!-- no translation found for permlab_writeSyncSettings (4514911143753152941) -->
+    <skip />
+    <!-- no translation found for permdesc_writeSyncSettings (7630627689635091836) -->
+    <skip />
+    <!-- no translation found for permlab_readSyncStats (5748337739678952863) -->
+    <skip />
+    <!-- no translation found for permdesc_readSyncStats (582551457321957183) -->
+    <skip />
+    <!-- no translation found for permlab_subscribedFeedsRead (2043206814904506589) -->
+    <skip />
+    <!-- no translation found for permdesc_subscribedFeedsRead (6977343942680042449) -->
+    <skip />
+    <!-- no translation found for permlab_subscribedFeedsWrite (2556727307229571556) -->
+    <skip />
+    <!-- no translation found for permdesc_subscribedFeedsWrite (4134783294590266220) -->
+    <skip />
+    <!-- no translation found for phoneTypes:0 (6070018634209800981) -->
+    <!-- no translation found for phoneTypes:1 (1514509689885965711) -->
+    <!-- no translation found for phoneTypes:2 (497473201754095234) -->
+    <!-- no translation found for phoneTypes:3 (5554432614281047787) -->
+    <!-- no translation found for phoneTypes:4 (2222084401110150993) -->
+    <!-- no translation found for phoneTypes:5 (2290007103906353121) -->
+    <!-- no translation found for phoneTypes:6 (6930783706213719251) -->
+    <!-- no translation found for phoneTypes:7 (1326005699931077792) -->
+    <!-- no translation found for emailAddressTypes:0 (1540640638077615417) -->
+    <!-- no translation found for emailAddressTypes:1 (4252853367575831977) -->
+    <!-- no translation found for emailAddressTypes:2 (7158046581744435718) -->
+    <!-- no translation found for emailAddressTypes:3 (3625034471181268169) -->
+    <!-- no translation found for postalAddressTypes:0 (5732960259696659380) -->
+    <!-- no translation found for postalAddressTypes:1 (7132240704786130285) -->
+    <!-- no translation found for postalAddressTypes:2 (1317604357745852817) -->
+    <!-- no translation found for postalAddressTypes:3 (1582953598462826702) -->
+    <!-- no translation found for imAddressTypes:0 (7806620012096518833) -->
+    <!-- no translation found for imAddressTypes:1 (5748846799950672787) -->
+    <!-- no translation found for imAddressTypes:2 (6196536810275073680) -->
+    <!-- no translation found for imAddressTypes:3 (8519128375350623648) -->
+    <!-- no translation found for organizationTypes:0 (1299224825223821142) -->
+    <!-- no translation found for organizationTypes:1 (2455717447227299354) -->
+    <!-- no translation found for organizationTypes:2 (7027570839313438290) -->
+    <!-- no translation found for imProtocols:0 (3318725788774688043) -->
+    <!-- no translation found for imProtocols:1 (1787713387022932886) -->
+    <!-- no translation found for imProtocols:2 (6751174158442316516) -->
+    <!-- no translation found for imProtocols:3 (1151283347465052653) -->
+    <!-- no translation found for imProtocols:4 (2157980008878817934) -->
+    <!-- no translation found for imProtocols:5 (7836237460308230767) -->
+    <!-- no translation found for imProtocols:6 (1180789904462172516) -->
+    <!-- no translation found for imProtocols:7 (21955111672779862) -->
+    <!-- no translation found for keyguard_password_enter_pin_code (6779835451906812518) -->
+    <skip />
+    <!-- no translation found for keyguard_password_wrong_pin_code (230312338493035499) -->
+    <skip />
+    <!-- no translation found for keyguard_label_text (3902954467573892533) -->
+    <skip />
+    <!-- no translation found for emergency_call_dialog_number_for_display (6256361184251050511) -->
+    <skip />
+    <!-- no translation found for lockscreen_carrier_default (5222269885486229730) -->
+    <skip />
+    <!-- no translation found for lockscreen_screen_locked (1922273663462058967) -->
+    <skip />
+    <!-- no translation found for lockscreen_instructions_when_pattern_enabled (7535864145009679967) -->
+    <skip />
+    <!-- no translation found for lockscreen_instructions_when_pattern_disabled (6526504555912746785) -->
+    <skip />
+    <!-- no translation found for lockscreen_pattern_instructions (8984964506352089877) -->
+    <skip />
+    <!-- no translation found for lockscreen_emergency_call (422835617844547383) -->
+    <skip />
+    <!-- no translation found for lockscreen_pattern_correct (7104753084746383672) -->
+    <skip />
+    <!-- no translation found for lockscreen_pattern_wrong (7517004470797680361) -->
+    <skip />
+    <!-- no translation found for lockscreen_plugged_in (8806977650003537118) -->
+    <skip />
+    <!-- no translation found for lockscreen_low_battery (9002637795199621345) -->
+    <skip />
+    <!-- no translation found for lockscreen_missing_sim_message_short (5051192587315492957) -->
+    <skip />
+    <!-- no translation found for lockscreen_missing_sim_message (8912914495901434841) -->
+    <skip />
+    <!-- no translation found for lockscreen_missing_sim_instructions (8125847194365725429) -->
+    <skip />
+    <!-- no translation found for lockscreen_network_locked_message (323609607922245071) -->
+    <skip />
+    <!-- no translation found for lockscreen_sim_puk_locked_message (1005803622871256359) -->
+    <skip />
+    <!-- no translation found for lockscreen_sim_puk_locked_instructions (5033160098036646955) -->
+    <skip />
+    <!-- no translation found for lockscreen_sim_locked_message (7398401200962556379) -->
+    <skip />
+    <!-- no translation found for lockscreen_sim_unlock_progress_dialog_message (5939537246164692076) -->
+    <skip />
+    <!-- no translation found for lockscreen_too_many_failed_attempts_dialog_message (6709066241494622136) -->
+    <skip />
+    <!-- no translation found for lockscreen_failed_attempts_almost_glogin (1569017295989454551) -->
+    <skip />
+    <!-- no translation found for lockscreen_too_many_failed_attempts_countdown (8823588000022797566) -->
+    <skip />
+    <!-- no translation found for lockscreen_forgot_pattern_button_text (4219994639843985488) -->
+    <skip />
+    <!-- no translation found for lockscreen_glogin_too_many_attempts (7504679498838839295) -->
+    <skip />
+    <!-- no translation found for lockscreen_glogin_instructions (6542400673357252011) -->
+    <skip />
+    <!-- no translation found for lockscreen_glogin_username_hint (6378418320242015111) -->
+    <skip />
+    <!-- no translation found for lockscreen_glogin_password_hint (3224230234042131153) -->
+    <skip />
+    <!-- no translation found for lockscreen_glogin_submit_button (5562051040043760034) -->
+    <skip />
+    <!-- no translation found for lockscreen_glogin_invalid_input (4881057177478491580) -->
+    <skip />
+    <!-- no translation found for status_bar_time_format (2168573805413119180) -->
+    <string name="status_bar_time_format">"<xliff:g id="HOUR">h</xliff:g>:<xliff:g id="MINUTE">mm</xliff:g> <xliff:g id="AMPM">AA</xliff:g>"</string>
+    <!-- no translation found for hour_minute_ampm (1850330605794978742) -->
+    <skip />
+    <!-- no translation found for hour_minute_cap_ampm (1122840227537374196) -->
+    <skip />
+    <!-- no translation found for hour_ampm (7665432130905376251) -->
+    <skip />
+    <!-- no translation found for hour_cap_ampm (3600295014648400268) -->
+    <skip />
+    <!-- no translation found for status_bar_clear_all_button (2202004591253243750) -->
+    <skip />
+    <!-- no translation found for status_bar_no_notifications_title (5123133188102094464) -->
+    <skip />
+    <!-- no translation found for status_bar_ongoing_events_title (799961521630569167) -->
+    <skip />
+    <!-- no translation found for status_bar_latest_events_title (5414094466807164279) -->
+    <skip />
+    <!-- no translation found for battery_status_text_percent_format (7391464609447031944) -->
+    <skip />
+    <!-- no translation found for battery_status_charging (5078780715755132756) -->
+    <skip />
+    <!-- no translation found for battery_low_title (3665400828395001695) -->
+    <skip />
+    <!-- no translation found for battery_low_subtitle (7537149915372180016) -->
+    <skip />
+    <!-- no translation found for battery_low_percent_format (8635359708781261154) -->
+    <skip />
+    <!-- no translation found for factorytest_failed (5784901108608196679) -->
+    <skip />
+    <!-- no translation found for factorytest_not_system (6330339565054095688) -->
+    <skip />
+    <!-- no translation found for factorytest_no_action (1662569013408679347) -->
+    <skip />
+    <!-- no translation found for factorytest_reboot (6080912029718954885) -->
+    <skip />
+    <!-- no translation found for save_password_label (4129493019621348626) -->
+    <skip />
+    <!-- no translation found for save_password_message (7412617920202682045) -->
+    <skip />
+    <!-- no translation found for save_password_notnow (3887362423496820832) -->
+    <skip />
+    <!-- no translation found for save_password_remember (4319688896716308569) -->
+    <skip />
+    <!-- no translation found for save_password_never (1836981952883642377) -->
+    <skip />
+    <!-- no translation found for open_permission_deny (6408502671105717111) -->
+    <skip />
+    <!-- no translation found for text_copied (6106873823411904723) -->
+    <skip />
+    <!-- no translation found for more_item_label (5204075544750360778) -->
+    <skip />
+    <!-- no translation found for prepend_shortcut_label (6091430648975237047) -->
+    <skip />
+    <!-- no translation found for menu_space_shortcut_label (194586306440382711) -->
+    <skip />
+    <!-- no translation found for menu_enter_shortcut_label (7214761412193519345) -->
+    <skip />
+    <!-- no translation found for menu_delete_shortcut_label (2854936426194985313) -->
+    <skip />
+    <!-- no translation found for search_go (4823831235057123206) -->
+    <skip />
+    <!-- no translation found for today (6914914811057683636) -->
+    <skip />
+    <!-- no translation found for yesterday (5280495043584636271) -->
+    <skip />
+    <!-- no translation found for tomorrow (561215115479060939) -->
+    <skip />
+    <!-- no translation found for oneMonthDurationPast (3402179395240209557) -->
+    <skip />
+    <!-- no translation found for beforeOneMonthDurationPast (7578100953282866827) -->
+    <skip />
+    <!-- no translation found for num_seconds_ago:one (7416512229671810725) -->
+    <!-- no translation found for num_seconds_ago:other (8138756910300398447) -->
+    <!-- no translation found for num_minutes_ago:one (8620869479299420562) -->
+    <!-- no translation found for num_minutes_ago:other (5065488162050522741) -->
+    <!-- no translation found for num_hours_ago:one (853404611989669641) -->
+    <!-- no translation found for num_hours_ago:other (3558873784561756849) -->
+    <!-- no translation found for num_days_ago:one (4222479980812128212) -->
+    <!-- no translation found for num_days_ago:other (5445701370433601703) -->
+    <!-- no translation found for in_num_seconds:one (4253290037777327003) -->
+    <!-- no translation found for in_num_seconds:other (1280033870920841404) -->
+    <!-- no translation found for in_num_minutes:one (1487585791027953091) -->
+    <!-- no translation found for in_num_minutes:other (6274204576475209932) -->
+    <!-- no translation found for in_num_hours:one (6501470863235186391) -->
+    <!-- no translation found for in_num_hours:other (4415358752953289251) -->
+    <!-- no translation found for in_num_days:one (5608475533104443893) -->
+    <!-- no translation found for in_num_days:other (3827193006163842267) -->
+    <!-- no translation found for preposition_for_date (2689847983632851560) -->
+    <skip />
+    <!-- no translation found for preposition_for_time (2613388053493148013) -->
+    <skip />
+    <!-- no translation found for preposition_for_year (6968468294728152393) -->
+    <skip />
+    <!-- no translation found for day (7849249054576985912) -->
+    <skip />
+    <!-- no translation found for days (8381828105391141169) -->
+    <skip />
+    <!-- no translation found for hour (1044439788994278057) -->
+    <skip />
+    <!-- no translation found for hours (9008157371441255845) -->
+    <skip />
+    <!-- no translation found for minute (2434431396283136076) -->
+    <skip />
+    <!-- no translation found for minutes (8176836254200264856) -->
+    <skip />
+    <!-- no translation found for second (6620645953323664299) -->
+    <skip />
+    <!-- no translation found for seconds (6416703426008384360) -->
+    <skip />
+    <!-- no translation found for week (7738046527402739781) -->
+    <skip />
+    <!-- no translation found for weeks (3178327674459887377) -->
+    <skip />
+    <!-- no translation found for year (8024790425994085153) -->
+    <skip />
+    <!-- no translation found for years (8592090054773244417) -->
+    <skip />
+    <!-- no translation found for sunday (4811082193700148223) -->
+    <skip />
+    <!-- no translation found for monday (7543713499896911033) -->
+    <skip />
+    <!-- no translation found for tuesday (7962192298359117585) -->
+    <skip />
+    <!-- no translation found for wednesday (5768878309383390437) -->
+    <skip />
+    <!-- no translation found for thursday (5690060634904123607) -->
+    <skip />
+    <!-- no translation found for friday (2718325370375116889) -->
+    <skip />
+    <!-- no translation found for saturday (222899317300942333) -->
+    <skip />
+    <!-- no translation found for every_weekday (8466333034903391066) -->
+    <skip />
+    <!-- no translation found for daily (1661712840773846970) -->
+    <skip />
+    <!-- no translation found for weekly (578642117234613009) -->
+    <skip />
+    <!-- no translation found for monthly (8526124657540210537) -->
+    <skip />
+    <!-- no translation found for yearly (8083067713764127070) -->
+    <skip />
+    <!-- no translation found for VideoView_error_title (1024334251681931859) -->
+    <skip />
+    <!-- no translation found for VideoView_error_text_unknown (3398417247398476771) -->
+    <skip />
+    <!-- no translation found for VideoView_error_button (3144127115413163445) -->
+    <skip />
+    <!-- no translation found for am (5354895493921411502) -->
+    <skip />
+    <!-- no translation found for pm (7206933220587555766) -->
+    <skip />
+    <!-- copied from values-de/strings.xml with the crazyness of the . removed-->
+    <!-- no translation found for wday1_date1_time1_wday2_date2_time2 (7066878981949584861) -->
+    <string name="wday1_date1_time1_wday2_date2_time2">"<xliff:g id="WEEKDAY1">%1$s</xliff:g>, <xliff:g id="DATE1">%2$s</xliff:g>, <xliff:g id="TIME1">%3$s</xliff:g> – <xliff:g id="WEEKDAY2">%4$s</xliff:g>, <xliff:g id="DATE2">%5$s</xliff:g>, <xliff:g id="TIME2">%6$s</xliff:g>"</string>
+    <!-- no translation found for wday1_date1_wday2_date2 (8671068747172261907) -->
+    <string name="wday1_date1_wday2_date2">"<xliff:g id="WEEKDAY1">%1$s</xliff:g>, <xliff:g id="DATE1">%2$s</xliff:g> – <xliff:g id="WEEKDAY2">%4$s</xliff:g>, <xliff:g id="DATE2">%5$s</xliff:g>"</string>
+    <!-- no translation found for numeric_date (5537215108967329745) -->
+    <string name="numeric_date">"<xliff:g id="DAY">%d</xliff:g>/<xliff:g id="MONTH">%m</xliff:g>/<xliff:g id="YEAR">%Y</xliff:g>"</string>
+    <!-- no translation found for date1_time1_date2_time2 (3645498975775629615) -->
+    <string name="date1_time1_date2_time2">"<xliff:g id="DATE1">%2$s</xliff:g>, <xliff:g id="TIME1">%3$s</xliff:g> – <xliff:g id="DATE2">%5$s</xliff:g>, <xliff:g id="TIME2">%6$s</xliff:g>"</string>
+    <!-- no translation found for date1_date2 (377057563556488062) -->
+    <string name="date1_date2">"<xliff:g id="DATE1">%2$s</xliff:g> – <xliff:g id="DATE2">%5$s</xliff:g>"</string>
+    <!-- no translation found for time1_time2 (3173474242109288305) -->
+    <string name="time1_time2">"<xliff:g id="TIME1">%1$s</xliff:g> – <xliff:g id="TIME2">%2$s</xliff:g>"</string>
+    <!-- no translation found for time_wday_date (8928955562064570313) -->
+    <string name="time_wday_date">"<xliff:g id="TIME_RANGE">%1$s</xliff:g>, <xliff:g id="WEEKDAY">%2$s</xliff:g>, <xliff:g id="DATE">%3$s</xliff:g>"</string>
+    <!-- no translation found for wday_date (8794741400546136975) -->
+    <string name="wday_date">"<xliff:g id="WEEKDAY">%2$s</xliff:g>, <xliff:g id="DATE">%3$s</xliff:g>"</string>
+    <!-- no translation found for time_date (1922644512833014496) -->
+    <string name="time_date">"<xliff:g id="TIME_RANGE">%1$s</xliff:g>, <xliff:g id="DATE">%3$s</xliff:g>"</string>
+    <!-- no translation found for time_wday (1422050241301754712) -->
+    <string name="time_wday">"<xliff:g id="TIME_RANGE">%1$s</xliff:g>, <xliff:g id="WEEKDAY">%2$s</xliff:g>"</string>
+    <!-- no translation found for full_date_month_first (6011143962222283357) -->
+    <skip />
+    <!-- no translation found for full_date_day_first (8621594762705478189) -->
+    <string name="full_date_day_first">"<xliff:g id="DAY">dd</xliff:g>  <xliff:g id="MONTH">MMMM</xliff:g> <xliff:g id="YEAR">yyyy</xliff:g>"</string>
+    <!-- no translation found for medium_date_month_first (48990963718825728) -->
+    <skip />
+    <!-- no translation found for medium_date_day_first (2898992016440387123) -->
+    <string name="medium_date_day_first">"<xliff:g id="DAY">dd</xliff:g>  <xliff:g id="MONTH">MMM</xliff:g> <xliff:g id="YEAR">yyyy</xliff:g>"</string>
+    <!-- no translation found for twelve_hour_time_format (6015557937879492156) -->
+    <skip />
+    <!-- no translation found for twenty_four_hour_time_format (5176807998669709535) -->
+    <skip />
+    <!-- no translation found for noon (8390796001560682897) -->
+    <skip />
+    <!-- no translation found for Noon (7698941576181064429) -->
+    <skip />
+    <!-- no translation found for midnight (7773339795626486146) -->
+    <skip />
+    <!-- no translation found for Midnight (1260172107848123187) -->
+    <skip />
+    <!-- no translation found for month_day (3356633704511426364) -->
+    <string name="month_day">"<xliff:g id="day" example="9">%-d</xliff:g>  <xliff:g id="month" example="October">%B</xliff:g>"</string>
+    <!-- no translation found for month (3017405760734206414) -->
+    <skip />
+    <!-- no translation found for month_day_year (2435948225709176752) -->
+    <string name="month_day_year">"<xliff:g id="DAY">%-d</xliff:g>  <xliff:g id="MONTH">%B</xliff:g> <xliff:g id="YEAR">%Y</xliff:g>"</string>
+    <!-- no translation found for month_year (6228414124777343135) -->
+    <skip />
+    <!-- no translation found for time_of_day (8375993139317154157) -->
+    <string name="time_of_day">"<xliff:g id="HOUR">%H</xliff:g>:<xliff:g id="MINUTE">%M</xliff:g>:<xliff:g id="SECOND">%S</xliff:g>"</string>
+    <!-- no translation found for date_and_time (9197690194373107109) -->
+    <string name="date_and_time">"<xliff:g id="HOUR">%H</xliff:g>:<xliff:g id="MINUTE">%M</xliff:g>:<xliff:g id="SECOND">%S</xliff:g> <xliff:g id="DAY">%-d</xliff:g>  <xliff:g id="MONTH">%B</xliff:g> <xliff:g id="YEAR">%Y</xliff:g>"</string>
+    <!-- no translation found for same_year_md1_md2 (9199324363135981317) -->
+    <string name="same_year_md1_md2">"<xliff:g id="DAY1">%3$s</xliff:g>  <xliff:g id="MONTH1">%2$s</xliff:g> –  <xliff:g id="DAY2">%8$s</xliff:g>  <xliff:g id="MONTH2">%7$s</xliff:g>"</string>
+    <!-- no translation found for same_year_wday1_md1_wday2_md2 (6006392413355305178) -->
+    <string name="same_year_wday1_md1_wday2_md2">"<xliff:g id="WEEKDAY1">%1$s</xliff:g>,  <xliff:g id="DAY1_0">%3$s</xliff:g>  <xliff:g id="MONTH1">%2$s</xliff:g> – <xliff:g id="WEEKDAY2">%6$s</xliff:g>, <xliff:g id="DAY2_1">%8$s</xliff:g>  <xliff:g id="MONTH2">%7$s</xliff:g>"</string>
+    <!-- no translation found for same_year_mdy1_mdy2 (1576657593937827090) -->
+    <string name="same_year_mdy1_mdy2">"<xliff:g id="DAY1">%3$s</xliff:g>  <xliff:g id="MONTH1">%2$s</xliff:g> – <xliff:g id="DAY2">%8$s</xliff:g>  <xliff:g id="MONTH2">%7$s</xliff:g> <xliff:g id="YEAR">%9$s</xliff:g>"</string>
+    <!-- no translation found for same_year_wday1_mdy1_wday2_mdy2 (9135935796468891580) -->
+    <string name="same_year_wday1_mdy1_wday2_mdy2">"<xliff:g id="WEEKDAY1">%1$s</xliff:g>, <xliff:g id="DAY1_0">%3$s</xliff:g>  <xliff:g id="MONTH1">%2$s</xliff:g> – <xliff:g id="WEEKDAY2">%6$s</xliff:g>, <xliff:g id="DAY2_1">%8$s</xliff:g>  <xliff:g id="MONTH2">%7$s</xliff:g> <xliff:g id="YEAR">%9$s</xliff:g>"</string>
+    <!-- no translation found for same_year_md1_time1_md2_time2 (2172964106375558081) -->
+    <skip />
+    <!-- no translation found for same_year_wday1_md1_time1_wday2_md2_time2 (1702879534101786310) -->
+    <string name="same_year_wday1_md1_time1_wday2_md2_time2">"<xliff:g id="WEEKDAY1">%1$s</xliff:g>, <xliff:g id="DAY1_0">%3$s</xliff:g>  <xliff:g id="MONTH1">%2$s</xliff:g>, <xliff:g id="TIME1">%5$s</xliff:g> – <xliff:g id="WEEKDAY2">%6$s</xliff:g>, <xliff:g id="DAY2_1">%8$s</xliff:g>  <xliff:g id="MONTH2">%7$s</xliff:g> <xliff:g id="TIME2">%10$s</xliff:g>"</string>
+    <!-- no translation found for same_year_mdy1_time1_mdy2_time2 (2476443311723358767) -->
+    <string name="same_year_mdy1_time1_mdy2_time2">"<xliff:g id="DAY1">%3$s</xliff:g>  <xliff:g id="MONTH1">%2$s</xliff:g>, <xliff:g id="YEAR1">%4$s</xliff:g>, <xliff:g id="TIME1">%5$s</xliff:g> – <xliff:g id="DAY2">%8$s</xliff:g>  <xliff:g id="MONTH2">%7$s</xliff:g> <xliff:g id="YEAR2">%9$s</xliff:g>, <xliff:g id="TIME2">%10$s</xliff:g>"</string>
+    <!-- no translation found for same_year_wday1_mdy1_time1_wday2_mdy2_time2 (1564837340334069879) -->
+    <string name="same_year_wday1_mdy1_time1_wday2_mdy2_time2">"<xliff:g id="WEEKDAY1">%1$s</xliff:g>, <xliff:g id="DAY1_0">%3$s</xliff:g>  <xliff:g id="MONTH1">%2$s</xliff:g> <xliff:g id="YEAR1">%4$s</xliff:g>, <xliff:g id="TIME1">%5$s</xliff:g> – <xliff:g id="WEEKDAY2">%6$s</xliff:g>, <xliff:g id="DAY2_1">%8$s</xliff:g>  <xliff:g id="MONTH2">%7$s</xliff:g> <xliff:g id="YEAR2">%9$s</xliff:g>, <xliff:g id="TIME2">%10$s</xliff:g>"</string>
+    <!-- no translation found for numeric_md1_md2 (8908376522875100300) -->
+    <string name="numeric_md1_md2">"<xliff:g id="DAY1">%3$s</xliff:g>/<xliff:g id="MONTH1">%2$s</xliff:g> – <xliff:g id="DAY2">%8$s</xliff:g>/<xliff:g id="MONTH2">%7$s</xliff:g>"</string>
+    <!-- no translation found for numeric_wday1_md1_wday2_md2 (3239690882018292077) -->
+    <string name="numeric_wday1_md1_wday2_md2">"<xliff:g id="WEEKDAY1">%1$s</xliff:g>, <xliff:g id="DAY1_0">%3$s</xliff:g>/<xliff:g id="MONTH1">%2$s</xliff:g> – <xliff:g id="WEEKDAY2">%6$s</xliff:g>, <xliff:g id="DAY2_1">%8$s</xliff:g>/<xliff:g id="MONTH2">%7$s</xliff:g>"</string>
+    <!-- no translation found for numeric_mdy1_mdy2 (8883797176939233525) -->
+    <string name="numeric_mdy1_mdy2">"<xliff:g id="DAY1">%3$s</xliff:g>/<xliff:g id="MONTH1">%2$s</xliff:g>/<xliff:g id="YEAR1">%4$s</xliff:g> – <xliff:g id="DAY2">%8$s</xliff:g>/<xliff:g id="MONTH2">%7$s</xliff:g>/<xliff:g id="YEAR2">%9$s</xliff:g>"</string>
+    <!-- no translation found for numeric_wday1_mdy1_wday2_mdy2 (4150475769255828954) -->
+    <string name="numeric_wday1_mdy1_wday2_mdy2">"<xliff:g id="WEEKDAY1">%1$s</xliff:g>, <xliff:g id="DAY1_0">%3$s</xliff:g>/<xliff:g id="MONTH1">%2$s</xliff:g>/<xliff:g id="YEAR1">%4$s</xliff:g> – <xliff:g id="WEEKDAY2">%6$s</xliff:g>, <xliff:g id="DAY2_1">%8$s</xliff:g>/<xliff:g id="MONTH2">%7$s</xliff:g>/<xliff:g id="YEAR2">%9$s</xliff:g>"</string>
+    <!-- no translation found for numeric_md1_time1_md2_time2 (3624746590607741419) -->
+    <string name="numeric_md1_time1_md2_time2">"<xliff:g id="DAY1">%3$s</xliff:g>/<xliff:g id="MONTH1">%2$s</xliff:g>, <xliff:g id="TIME1">%5$s</xliff:g> – <xliff:g id="DAY2">%8$s</xliff:g>/<xliff:g id="MONTH2">%7$s</xliff:g>, <xliff:g id="TIME2">%10$s</xliff:g>"</string>
+    <!-- no translation found for numeric_wday1_md1_time1_wday2_md2_time2 (4258040955467298134) -->
+    <string name="numeric_wday1_md1_time1_wday2_md2_time2">"<xliff:g id="WEEKDAY1">%1$s</xliff:g>, <xliff:g id="DAY1_0">%3$s</xliff:g>/<xliff:g id="MONTH1">%2$s</xliff:g>, <xliff:g id="TIME1">%5$s</xliff:g> – <xliff:g id="WEEKDAY2">%6$s</xliff:g>, <xliff:g id="DAY2_1">%8$s</xliff:g>/<xliff:g id="MONTH2">%7$s</xliff:g>, <xliff:g id="TIME2">%10$s</xliff:g>"</string>
+    <!-- no translation found for numeric_mdy1_time1_mdy2_time2 (3598215409314517987) -->
+    <string name="numeric_mdy1_time1_mdy2_time2">"<xliff:g id="DAY1">%3$s</xliff:g>/<xliff:g id="MONTH1">%2$s</xliff:g>/<xliff:g id="YEAR1">%4$s</xliff:g>, <xliff:g id="TIME1">%5$s</xliff:g> – <xliff:g id="DAY2">%8$s</xliff:g>/<xliff:g id="MONTH2">%7$s</xliff:g>/<xliff:g id="YEAR2">%9$s</xliff:g>, <xliff:g id="TIME2">%10$s</xliff:g>"</string>
+    <!-- no translation found for numeric_wday1_mdy1_time1_wday2_mdy2_time2 (264076937155877259) -->
+    <string name="numeric_wday1_mdy1_time1_wday2_mdy2_time2">"<xliff:g id="WEEKDAY1">%1$s</xliff:g>, <xliff:g id="DAY1_0">%3$s</xliff:g>/<xliff:g id="MONTH1">%2$s</xliff:g>/<xliff:g id="YEAR1">%4$s</xliff:g>, <xliff:g id="TIME1">%5$s</xliff:g> – <xliff:g id="WEEKDAY2">%6$s</xliff:g>, <xliff:g id="DAY2_1">%8$s</xliff:g>/<xliff:g id="MONTH2">%7$s</xliff:g>/<xliff:g id="YEAR2">%9$s</xliff:g>, <xliff:g id="TIME2">%10$s</xliff:g>"</string>
+    <!-- no translation found for same_month_md1_md2 (2393563617438036111) -->
+    <string name="same_month_md1_md2">"<xliff:g id="DAY1" example="31">%3$s</xliff:g>  \u2013 <xliff:g id="DAY2" example="3">%8$s</xliff:g>  <xliff:g id="MONTH1" example="Oct">%2$s</xliff:g>"</string>
+    <!-- no translation found for same_month_wday1_md1_wday2_md2 (1208946773794057819) -->
+    <string name="same_month_wday1_md1_wday2_md2">"<xliff:g id="WEEKDAY1">%1$s</xliff:g>, <xliff:g id="DAY1_0">%3$s</xliff:g>  <xliff:g id="MONTH1">%2$s</xliff:g> – <xliff:g id="WEEKDAY2">%6$s</xliff:g>, <xliff:g id="DAY2_1">%8$s</xliff:g>  <xliff:g id="MONTH2">%7$s</xliff:g>"</string>
+    <!-- no translation found for same_month_mdy1_mdy2 (3713236637869030492) -->
+    <string name="same_month_mdy1_mdy2">"<xliff:g id="DAY1" example="31">%3$s</xliff:g>  \u2013 <xliff:g id="DAY2" example="3">%8$s</xliff:g>  <xliff:g id="MONTH1" example="Oct">%2$s</xliff:g> <xliff:g id="YEAR2" example="2007">%9$s</xliff:g>"</string>
+    <!-- no translation found for same_month_wday1_mdy1_wday2_mdy2 (389638922479870472) -->
+    <string name="same_month_wday1_mdy1_wday2_mdy2">"<xliff:g id="WEEKDAY1">%1$s</xliff:g>, <xliff:g id="DAY1_0">%3$s</xliff:g>  <xliff:g id="MONTH1">%2$s</xliff:g> <xliff:g id="YEAR1">%4$s</xliff:g> – <xliff:g id="WEEKDAY2">%6$s</xliff:g>, <xliff:g id="DAY2_1">%8$s</xliff:g>  <xliff:g id="MONTH2">%7$s</xliff:g> <xliff:g id="YEAR2">%9$s</xliff:g>"</string>
+    <!-- no translation found for same_month_md1_time1_md2_time2 (7477075526337542685) -->
+    <string name="same_month_md1_time1_md2_time2">"<xliff:g id="DAY1">%3$s</xliff:g>  <xliff:g id="MONTH1">%2$s</xliff:g>, <xliff:g id="TIME1">%5$s</xliff:g> – <xliff:g id="DAY2">%8$s</xliff:g>  <xliff:g id="MONTH2">%7$s</xliff:g>, <xliff:g id="TIME2">%10$s</xliff:g>"</string>
+    <!-- no translation found for same_month_wday1_md1_time1_wday2_md2_time2 (3516978303779391173) -->
+    <string name="same_month_wday1_md1_time1_wday2_md2_time2">"<xliff:g id="WEEKDAY1">%1$s</xliff:g>, <xliff:g id="DAY1_0">%3$s</xliff:g>  <xliff:g id="MONTH1">%2$s</xliff:g>, <xliff:g id="TIME1">%5$s</xliff:g> – <xliff:g id="WEEKDAY2">%6$s</xliff:g>, <xliff:g id="DAY2_1">%8$s</xliff:g>  <xliff:g id="MONTH2">%7$s</xliff:g>, <xliff:g id="TIME2">%10$s</xliff:g>"</string>
+    <!-- no translation found for same_month_mdy1_time1_mdy2_time2 (7320410992514057310) -->
+    <string name="same_month_mdy1_time1_mdy2_time2">"<xliff:g id="DAY1">%3$s</xliff:g>  <xliff:g id="MONTH1">%2$s</xliff:g> <xliff:g id="YEAR1">%4$s</xliff:g>, <xliff:g id="TIME1">%5$s</xliff:g> – <xliff:g id="DAY2">%8$s</xliff:g>  <xliff:g id="MONTH2">%7$s</xliff:g> <xliff:g id="YEAR2">%9$s</xliff:g>, <xliff:g id="TIME2">%10$s</xliff:g>"</string>
+    <!-- no translation found for same_month_wday1_mdy1_time1_wday2_mdy2_time2 (1332950588774239228) -->
+    <string name="same_month_wday1_mdy1_time1_wday2_mdy2_time2">"<xliff:g id="WEEKDAY1">%1$s</xliff:g>, <xliff:g id="DAY1_0">%3$s</xliff:g>  <xliff:g id="MONTH1">%2$s</xliff:g> <xliff:g id="YEAR1">%4$s</xliff:g>, <xliff:g id="TIME1">%5$s</xliff:g> – <xliff:g id="WEEKDAY2">%6$s</xliff:g>, <xliff:g id="DAY2_1">%8$s</xliff:g>  <xliff:g id="MONTH2">%7$s</xliff:g> <xliff:g id="YEAR2">%9$s</xliff:g>, <xliff:g id="TIME2">%10$s</xliff:g>"</string>
+    <!-- no translation found for abbrev_month_day_year (5767271534015320250) -->
+    <string name="abbrev_month_day_year">"<xliff:g id="DAY">%-d</xliff:g>  <xliff:g id="MONTH">%b</xliff:g> <xliff:g id="YEAR">%Y</xliff:g>"</string>
+    <!-- no translation found for abbrev_month_year (8058929633673942490) -->
+    <skip />
+    <!-- no translation found for abbrev_month_day (458867920693482757) -->
+    <string name="abbrev_month_day">"<xliff:g id="day" example="31">%-d</xliff:g>  <xliff:g id="month" example="Oct">%b</xliff:g>"</string>
+    <!-- no translation found for abbrev_month (1674509986330181349) -->
+    <skip />
+    <!-- no translation found for day_of_week_long_sunday (9057662850446501884) -->
+    <skip />
+    <!-- no translation found for day_of_week_long_monday (7358451993082888343) -->
+    <skip />
+    <!-- no translation found for day_of_week_long_tuesday (2282901451170509613) -->
+    <skip />
+    <!-- no translation found for day_of_week_long_wednesday (2100217950343286482) -->
+    <skip />
+    <!-- no translation found for day_of_week_long_thursday (5475158963242863176) -->
+    <skip />
+    <!-- no translation found for day_of_week_long_friday (4081018004819837155) -->
+    <skip />
+    <!-- no translation found for day_of_week_long_saturday (1929694088305891795) -->
+    <skip />
+    <!-- no translation found for day_of_week_medium_sunday (6462580883948669820) -->
+    <skip />
+    <!-- no translation found for day_of_week_medium_monday (6960587654241349502) -->
+    <skip />
+    <!-- no translation found for day_of_week_medium_tuesday (7004462235990108936) -->
+    <skip />
+    <!-- no translation found for day_of_week_medium_wednesday (5688564741951314696) -->
+    <skip />
+    <!-- no translation found for day_of_week_medium_thursday (1784339868453982400) -->
+    <skip />
+    <!-- no translation found for day_of_week_medium_friday (4314577583604069357) -->
+    <skip />
+    <!-- no translation found for day_of_week_medium_saturday (70321191398427845) -->
+    <skip />
+    <!-- no translation found for day_of_week_short_sunday (7403409454572591357) -->
+    <skip />
+    <!-- no translation found for day_of_week_short_monday (5278358100012478239) -->
+    <skip />
+    <!-- no translation found for day_of_week_short_tuesday (5121116040712487059) -->
+    <skip />
+    <!-- no translation found for day_of_week_short_wednesday (1601079579293330319) -->
+    <skip />
+    <!-- no translation found for day_of_week_short_thursday (5863422096017401812) -->
+    <skip />
+    <!-- no translation found for day_of_week_short_friday (2916686031099723960) -->
+    <skip />
+    <!-- no translation found for day_of_week_short_saturday (8521564973195542073) -->
+    <skip />
+    <!-- no translation found for day_of_week_shorter_sunday (1650484495176707638) -->
+    <skip />
+    <!-- no translation found for day_of_week_shorter_monday (9133193697786876074) -->
+    <skip />
+    <!-- no translation found for day_of_week_shorter_tuesday (4012095408481489663) -->
+    <skip />
+    <!-- no translation found for day_of_week_shorter_wednesday (6279056612496078470) -->
+    <skip />
+    <!-- no translation found for day_of_week_shorter_thursday (2748599403545071011) -->
+    <skip />
+    <!-- no translation found for day_of_week_shorter_friday (5037282109124849673) -->
+    <skip />
+    <!-- no translation found for day_of_week_shorter_saturday (3208167155877833783) -->
+    <skip />
+    <!-- no translation found for day_of_week_shortest_sunday (4683862964821549758) -->
+    <skip />
+    <!-- no translation found for day_of_week_shortest_monday (6701142261471667000) -->
+    <skip />
+    <!-- no translation found for day_of_week_shortest_tuesday (9098171980161292477) -->
+    <skip />
+    <!-- no translation found for day_of_week_shortest_wednesday (655049238289460956) -->
+    <skip />
+    <!-- no translation found for day_of_week_shortest_thursday (7816913627500884083) -->
+    <skip />
+    <!-- no translation found for day_of_week_shortest_friday (903301878650619398) -->
+    <skip />
+    <!-- no translation found for day_of_week_shortest_saturday (5359692489649817988) -->
+    <skip />
+    <!-- no translation found for month_long_january (7128497801440564337) -->
+    <skip />
+    <!-- no translation found for month_long_february (7808570514581190617) -->
+    <skip />
+    <!-- no translation found for month_long_march (2061328556983796034) -->
+    <skip />
+    <!-- no translation found for month_long_april (6575007959043269919) -->
+    <skip />
+    <!-- no translation found for month_long_may (8404051103463071121) -->
+    <skip />
+    <!-- no translation found for month_long_june (6255771619238859451) -->
+    <skip />
+    <!-- no translation found for month_long_july (4129177743136800884) -->
+    <skip />
+    <!-- no translation found for month_long_august (5494331003296804494) -->
+    <skip />
+    <!-- no translation found for month_long_september (2691137479752033087) -->
+    <skip />
+    <!-- no translation found for month_long_october (7501261567327243313) -->
+    <skip />
+    <!-- no translation found for month_long_november (8759690753068763664) -->
+    <skip />
+    <!-- no translation found for month_long_december (4505008719696569497) -->
+    <skip />
+    <!-- no translation found for month_medium_january (2315492772833932512) -->
+    <skip />
+    <!-- no translation found for month_medium_february (118412521324313430) -->
+    <skip />
+    <!-- no translation found for month_medium_march (5546835583839352358) -->
+    <skip />
+    <!-- no translation found for month_medium_april (7052559668687733702) -->
+    <skip />
+    <!-- no translation found for month_medium_may (2825303871720116018) -->
+    <skip />
+    <!-- no translation found for month_medium_june (829843667101495271) -->
+    <skip />
+    <!-- no translation found for month_medium_july (5029778226925324789) -->
+    <skip />
+    <!-- no translation found for month_medium_august (8851230594641162805) -->
+    <skip />
+    <!-- no translation found for month_medium_september (8420590486625304647) -->
+    <skip />
+    <!-- no translation found for month_medium_october (1787382806172930239) -->
+    <skip />
+    <!-- no translation found for month_medium_november (675513809622370603) -->
+    <skip />
+    <!-- no translation found for month_medium_december (2934948295928978783) -->
+    <skip />
+    <!-- no translation found for month_shortest_january (6070060405144675883) -->
+    <skip />
+    <!-- no translation found for month_shortest_february (5632605004902176653) -->
+    <skip />
+    <!-- no translation found for month_shortest_march (4304231552356086624) -->
+    <skip />
+    <!-- no translation found for month_shortest_april (1166434066469385532) -->
+    <skip />
+    <!-- no translation found for month_shortest_may (9131326028845529001) -->
+    <skip />
+    <!-- no translation found for month_shortest_june (1875723154506665289) -->
+    <skip />
+    <!-- no translation found for month_shortest_july (2003596275389810773) -->
+    <skip />
+    <!-- no translation found for month_shortest_august (9120245162625763214) -->
+    <skip />
+    <!-- no translation found for month_shortest_september (7980651111022693669) -->
+    <skip />
+    <!-- no translation found for month_shortest_october (3640405450427788312) -->
+    <skip />
+    <!-- no translation found for month_shortest_november (4002935318566146993) -->
+    <skip />
+    <!-- no translation found for month_shortest_december (6213739417171334040) -->
+    <skip />
+    <!-- no translation found for elapsed_time_short_format_mm_ss (1294409362352514646) -->
+    <string name="elapsed_time_short_format_mm_ss">"<xliff:g id="MINUTES">%1$02d</xliff:g>:<xliff:g id="SECONDS">%2$02d</xliff:g>"</string>
+    <!-- no translation found for elapsed_time_short_format_h_mm_ss (2997059666628785039) -->
+    <string name="elapsed_time_short_format_h_mm_ss">"<xliff:g id="HOURS">%1$d</xliff:g>:<xliff:g id="MINUTES">%2$02d</xliff:g>:<xliff:g id="SECONDS">%3$02d</xliff:g>"</string>
+    <!-- no translation found for selectAll (691691810023908884) -->
+    <skip />
+    <!-- no translation found for cut (5845613239192595662) -->
+    <skip />
+    <!-- no translation found for cutAll (4474519683293791451) -->
+    <skip />
+    <!-- no translation found for copy (8603721575469529820) -->
+    <skip />
+    <!-- no translation found for copyAll (4777548804630476932) -->
+    <skip />
+    <!-- no translation found for paste (6458036735811828538) -->
+    <skip />
+    <!-- no translation found for copyUrl (5785708478767435812) -->
+    <skip />
+    <!-- no translation found for inputMethod (7911866729148111492) -->
+    <skip />
+    <!-- no translation found for editTextMenuTitle (3984253728638788023) -->
+    <skip />
+    <!-- no translation found for low_internal_storage_view_title (5997772070488639934) -->
+    <skip />
+    <!-- no translation found for low_internal_storage_view_text (2230118755295375293) -->
+    <skip />
+    <!-- no translation found for ok (4003878536083514869) -->
+    <skip />
+    <!-- no translation found for cancel (1527674037280267012) -->
+    <skip />
+    <!-- no translation found for yes (8185296114406773873) -->
+    <skip />
+    <!-- no translation found for no (2300685350903156262) -->
+    <skip />
+    <!-- no translation found for capital_on (8418242581217554942) -->
+    <skip />
+    <!-- no translation found for capital_off (8870368560477693851) -->
+    <skip />
+    <!-- no translation found for whichApplication (2828159696176255212) -->
+    <skip />
+    <!-- no translation found for alwaysUse (6433627451071144629) -->
+    <skip />
+    <!-- no translation found for clearDefaultHintMsg (5742432113023174321) -->
+    <skip />
+    <!-- no translation found for chooseActivity (7588691622928031978) -->
+    <skip />
+    <!-- no translation found for noApplications (4068560364116066745) -->
+    <skip />
+    <!-- no translation found for aerr_title (2654390351574026098) -->
+    <skip />
+    <!-- no translation found for aerr_application (4917288809565116720) -->
+    <skip />
+    <!-- no translation found for aerr_process (1273819861108073461) -->
+    <skip />
+    <!-- no translation found for anr_title (3305935690891435915) -->
+    <skip />
+    <!-- no translation found for anr_activity_application (1653036325679156678) -->
+    <skip />
+    <!-- no translation found for anr_activity_process (2674027618362070465) -->
+    <skip />
+    <!-- no translation found for anr_application_process (2163656674970221928) -->
+    <skip />
+    <!-- no translation found for anr_process (7747550780123472160) -->
+    <skip />
+    <!-- no translation found for force_close (9020954128872810669) -->
+    <skip />
+    <!-- no translation found for wait (7973775702304037058) -->
+    <skip />
+    <!-- no translation found for debug (857932504764728770) -->
+    <skip />
+    <!-- no translation found for sendText (6158329286172492543) -->
+    <skip />
+    <!-- no translation found for volume_ringtone (4121694816346562058) -->
+    <skip />
+    <!-- no translation found for volume_music (4869950240104717493) -->
+    <skip />
+    <!-- no translation found for volume_call (5723421277753250395) -->
+    <skip />
+    <!-- no translation found for volume_alarm (2752102730973081294) -->
+    <skip />
+    <!-- no translation found for volume_unknown (6908187627672375742) -->
+    <skip />
+    <!-- no translation found for ringtone_default (2873893375149093475) -->
+    <skip />
+    <!-- no translation found for ringtone_default_with_actual (5474076151665761913) -->
+    <skip />
+    <!-- no translation found for ringtone_silent (7477159279081654685) -->
+    <skip />
+    <!-- no translation found for ringtone_picker_title (7055241890764367884) -->
+    <skip />
+    <!-- no translation found for ringtone_unknown (6888219771401173795) -->
+    <skip />
+    <!-- no translation found for wifi_available:one (8168012881468888470) -->
+    <!-- no translation found for wifi_available:other (4666122955807117718) -->
+    <!-- no translation found for wifi_available_detailed:one (5107769161192143259) -->
+    <!-- no translation found for wifi_available_detailed:other (853347657960575809) -->
+    <!-- no translation found for select_character (3735110139249491726) -->
+    <skip />
+    <!-- no translation found for sms_control_default_app_name (7522184737840550841) -->
+    <skip />
+    <!-- no translation found for sms_control_title (2742400596989418394) -->
+    <skip />
+    <!-- no translation found for sms_control_message (3447126217666595989) -->
+    <skip />
+    <!-- no translation found for sms_control_yes (8839660939359273650) -->
+    <skip />
+    <!-- no translation found for sms_control_no (909756849988183801) -->
+    <skip />
+    <!-- no translation found for date_time_set (2495199891239480952) -->
+    <skip />
+    <!-- no translation found for default_permission_group (7742780381379652409) -->
+    <skip />
+    <!-- no translation found for no_permissions (85461124044682315) -->
+    <skip />
+    <!-- no translation found for perms_hide (4145325555929151849) -->
+    <skip />
+    <!-- no translation found for perms_show_all (6040194843455403173) -->
+    <skip />
+    <!-- no translation found for googlewebcontenthelper_loading (2140804350507245589) -->
+    <skip />
+    <!-- no translation found for usb_storage_title (8699631567051394409) -->
+    <skip />
+    <!-- no translation found for usb_storage_message (5344039189213308733) -->
+    <skip />
+    <!-- no translation found for usb_storage_button_mount (6700104384375121662) -->
+    <skip />
+    <!-- no translation found for usb_storage_button_unmount (465869657252626688) -->
+    <skip />
+    <!-- no translation found for usb_storage_error_message (3192564550748426087) -->
+    <skip />
+    <!-- no translation found for usb_storage_notification_title (6237028017872246940) -->
+    <skip />
+    <!-- no translation found for usb_storage_notification_message (7371717280517625905) -->
+    <skip />
+    <!-- no translation found for select_input_method (2658280517827502015) -->
+    <skip />
+    <!-- no translation found for fast_scroll_alphabet (1017432309285755759) -->
+    <skip />
+    <!-- no translation found for fast_scroll_numeric_alphabet (3092587363718901074) -->
+    <skip />
+    <!-- no translation found for candidates_style (7738463880139922176) -->
+    <skip />
+</resources>
diff --git a/core/res/res/values-en-rUS/strings.xml b/core/res/res/values-en-rUS/strings.xml
new file mode 100644
index 0000000..b9df983
--- /dev/null
+++ b/core/res/res/values-en-rUS/strings.xml
@@ -0,0 +1,1256 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="byteShort">"B"</string>
+    <!-- no translation found for kilobyteShort (5865542430193761682) -->
+    <skip />
+    <!-- no translation found for megabyteShort (112984851085937882) -->
+    <skip />
+    <!-- no translation found for gigabyteShort (8586075069559273847) -->
+    <skip />
+    <!-- no translation found for terabyteShort (5828502357595687794) -->
+    <skip />
+    <!-- no translation found for petabyteShort (7523248732657962413) -->
+    <skip />
+    <!-- no translation found for untitled (284687023829080340) -->
+    <skip />
+    <!-- no translation found for ellipsis (8538883953764277342) -->
+    <skip />
+    <!-- no translation found for emptyPhoneNumber (6416283285732095329) -->
+    <skip />
+    <!-- no translation found for unknownName (3974255879290140525) -->
+    <skip />
+    <!-- no translation found for defaultVoiceMailAlphaTag (6484324201071049939) -->
+    <skip />
+    <!-- no translation found for defaultMsisdnAlphaTag (4953008223227371928) -->
+    <skip />
+    <!-- no translation found for mmiError (7480678835624852655) -->
+    <skip />
+    <!-- no translation found for serviceEnabled (4042194305396115167) -->
+    <skip />
+    <!-- no translation found for serviceEnabledFor (638808419103886277) -->
+    <skip />
+    <!-- no translation found for serviceDisabled (1059935666763511541) -->
+    <skip />
+    <!-- no translation found for serviceRegistered (7639869107156932038) -->
+    <skip />
+    <!-- no translation found for serviceErased (4602215208593071820) -->
+    <skip />
+    <!-- no translation found for passwordIncorrect (5142040651297346232) -->
+    <skip />
+    <!-- no translation found for mmiComplete (3178168770150013486) -->
+    <skip />
+    <!-- no translation found for badPin (5103184589972647739) -->
+    <skip />
+    <!-- no translation found for badPuk (2200634943393540609) -->
+    <skip />
+    <!-- no translation found for mismatchPin (5055729703806180857) -->
+    <skip />
+    <!-- no translation found for invalidPin (6201854814319326475) -->
+    <skip />
+    <!-- no translation found for needPuk (4788728144863892764) -->
+    <skip />
+    <!-- no translation found for needPuk2 (7056908944942451033) -->
+    <skip />
+    <!-- no translation found for ClipMmi (5649729434121615509) -->
+    <skip />
+    <!-- no translation found for ClirMmi (5220979296096544477) -->
+    <skip />
+    <!-- no translation found for CfMmi (4998483717856803914) -->
+    <skip />
+    <!-- no translation found for CwMmi (5678103638951836350) -->
+    <skip />
+    <!-- no translation found for BaMmi (6030555200442855833) -->
+    <skip />
+    <!-- no translation found for PwdMmi (2549941247959366670) -->
+    <skip />
+    <!-- no translation found for PinMmi (2463353963837922189) -->
+    <skip />
+    <!-- no translation found for CLIRDefaultOnNextCallOn (4005921990799469144) -->
+    <skip />
+    <!-- no translation found for CLIRDefaultOnNextCallOff (1497360760012205230) -->
+    <skip />
+    <!-- no translation found for CLIRDefaultOffNextCallOn (604591440398078227) -->
+    <skip />
+    <!-- no translation found for CLIRDefaultOffNextCallOff (5114039908683246336) -->
+    <skip />
+    <!-- no translation found for serviceNotProvisioned (3754416031529306610) -->
+    <skip />
+    <!-- no translation found for CLIRPermanent (3819908477891272611) -->
+    <skip />
+    <!-- no translation found for serviceClassVoice (3059107563169935913) -->
+    <skip />
+    <!-- no translation found for serviceClassData (2669025626575716504) -->
+    <skip />
+    <!-- no translation found for serviceClassFAX (973109472405729679) -->
+    <skip />
+    <!-- no translation found for serviceClassSMS (3857383928743625711) -->
+    <skip />
+    <!-- no translation found for serviceClassDataAsync (8526461993032174729) -->
+    <skip />
+    <!-- no translation found for serviceClassDataSync (2461138395498381801) -->
+    <skip />
+    <!-- no translation found for serviceClassPacket (8119604233041078065) -->
+    <skip />
+    <!-- no translation found for serviceClassPAD (202892636830042266) -->
+    <skip />
+    <!-- no translation found for cfTemplateNotForwarded (3171755805856206604) -->
+    <skip />
+    <!-- no translation found for cfTemplateForwarded (2468661573318024785) -->
+    <skip />
+    <!-- no translation found for cfTemplateForwardedTime (5151810870794744740) -->
+    <skip />
+    <!-- no translation found for cfTemplateRegistered (5685211900474527085) -->
+    <skip />
+    <!-- no translation found for cfTemplateRegisteredTime (2978918277762252776) -->
+    <skip />
+    <!-- no translation found for httpErrorOk (984913805621139001) -->
+    <skip />
+    <!-- no translation found for httpError (9177990053748151835) -->
+    <skip />
+    <!-- no translation found for httpErrorLookup (5251341716070330936) -->
+    <skip />
+    <!-- no translation found for httpErrorUnsupportedAuthScheme (2865679883634239474) -->
+    <skip />
+    <!-- no translation found for httpErrorAuth (1637382600929594620) -->
+    <skip />
+    <!-- no translation found for httpErrorProxyAuth (5947648983995807455) -->
+    <skip />
+    <!-- no translation found for httpErrorConnect (129984292497034683) -->
+    <skip />
+    <!-- no translation found for httpErrorIO (8128922048686581131) -->
+    <skip />
+    <!-- no translation found for httpErrorTimeout (8357966263983739012) -->
+    <skip />
+    <!-- no translation found for httpErrorRedirectLoop (4122379005100433886) -->
+    <skip />
+    <!-- no translation found for httpErrorUnsupportedScheme (4072339858288462569) -->
+    <skip />
+    <!-- no translation found for httpErrorFailedSslHandshake (2316625025255452595) -->
+    <skip />
+    <!-- no translation found for httpErrorBadUrl (8885244563103716039) -->
+    <skip />
+    <!-- no translation found for httpErrorFile (1408273621719669493) -->
+    <skip />
+    <!-- no translation found for httpErrorFileNotFound (2309088465300506314) -->
+    <skip />
+    <!-- no translation found for httpErrorTooManyRequests (3764334538393544875) -->
+    <skip />
+    <!-- no translation found for contentServiceSync (4863236165350475642) -->
+    <skip />
+    <!-- no translation found for contentServiceSyncNotificationTitle (6855304679069026824) -->
+    <skip />
+    <!-- no translation found for contentServiceTooManyDeletesNotificationDesc (8477597194404210723) -->
+    <skip />
+    <!-- no translation found for low_memory (4191592786596642367) -->
+    <skip />
+    <!-- no translation found for me (4616693653158602117) -->
+    <skip />
+    <!-- no translation found for power_dialog (8210256011408959109) -->
+    <skip />
+    <!-- no translation found for silent_mode (5218239246946854300) -->
+    <skip />
+    <!-- no translation found for turn_on_radio (1901054698789840131) -->
+    <skip />
+    <!-- no translation found for turn_off_radio (2870296409392615956) -->
+    <skip />
+    <!-- no translation found for screen_lock (1560333453597081877) -->
+    <skip />
+    <!-- no translation found for power_off (2412024417733516836) -->
+    <skip />
+    <!-- no translation found for shutdown_progress (3735034517335251808) -->
+    <skip />
+    <!-- no translation found for shutdown_confirm (699224922526414097) -->
+    <skip />
+    <!-- no translation found for no_recent_tasks (1367712919998349373) -->
+    <skip />
+    <!-- no translation found for global_actions (8299888906525675157) -->
+    <skip />
+    <!-- no translation found for global_action_lock (5943677976245541105) -->
+    <skip />
+    <!-- no translation found for global_action_power_off (3143027278596694254) -->
+    <skip />
+    <!-- no translation found for global_action_toggle_silent_mode (5849335789367070450) -->
+    <skip />
+    <!-- no translation found for global_action_silent_mode_on_status (6053429980569202260) -->
+    <skip />
+    <!-- no translation found for global_action_silent_mode_off_status (1994514127029249081) -->
+    <skip />
+    <!-- no translation found for safeMode (3375134507868534320) -->
+    <skip />
+    <!-- no translation found for permgrouplab_costMoney (904087853776533085) -->
+    <skip />
+    <!-- no translation found for permgroupdesc_costMoney (4662370555643969515) -->
+    <skip />
+    <!-- no translation found for permgrouplab_messages (2984053976424233925) -->
+    <skip />
+    <!-- no translation found for permgroupdesc_messages (2129093134354989379) -->
+    <skip />
+    <!-- no translation found for permgrouplab_personalInfo (4548406335021507392) -->
+    <skip />
+    <!-- no translation found for permgroupdesc_personalInfo (8499310823817958034) -->
+    <skip />
+    <!-- no translation found for permgrouplab_location (8535677827151907069) -->
+    <skip />
+    <!-- no translation found for permgroupdesc_location (2341662219604651887) -->
+    <skip />
+    <!-- no translation found for permgrouplab_network (3597781730625751831) -->
+    <skip />
+    <!-- no translation found for permgroupdesc_network (8332572695347918340) -->
+    <skip />
+    <!-- no translation found for permgrouplab_accounts (8631201594657951893) -->
+    <skip />
+    <!-- no translation found for permgroupdesc_accounts (443982868906396781) -->
+    <skip />
+    <!-- no translation found for permgrouplab_hardwareControls (5074512938567152139) -->
+    <skip />
+    <!-- no translation found for permgroupdesc_hardwareControls (8772503144945278440) -->
+    <skip />
+    <!-- no translation found for permgrouplab_phoneCalls (7096448531266882376) -->
+    <skip />
+    <!-- no translation found for permgroupdesc_phoneCalls (6703873478653366233) -->
+    <skip />
+    <!-- no translation found for permgrouplab_systemTools (1840847965111633430) -->
+    <skip />
+    <!-- no translation found for permgroupdesc_systemTools (2810337951496685271) -->
+    <skip />
+    <!-- no translation found for permgrouplab_developmentTools (692844635256963358) -->
+    <skip />
+    <!-- no translation found for permgroupdesc_developmentTools (5253915519857796400) -->
+    <skip />
+    <!-- no translation found for permlab_statusBar (8789506912215455922) -->
+    <skip />
+    <!-- no translation found for permdesc_statusBar (5034247171231682403) -->
+    <skip />
+    <!-- no translation found for permlab_expandStatusBar (6382500803293284173) -->
+    <skip />
+    <!-- no translation found for permdesc_expandStatusBar (90953162060681436) -->
+    <skip />
+    <!-- no translation found for permlab_processOutgoingCalls (786316295241100144) -->
+    <skip />
+    <!-- no translation found for permdesc_processOutgoingCalls (1655242138991854396) -->
+    <skip />
+    <!-- no translation found for permlab_receiveSms (5820796051959871222) -->
+    <skip />
+    <!-- no translation found for permdesc_receiveSms (2265740044646990161) -->
+    <skip />
+    <!-- no translation found for permlab_receiveMms (7983091218880782611) -->
+    <skip />
+    <!-- no translation found for permdesc_receiveMms (3641275586518289960) -->
+    <skip />
+    <!-- no translation found for permlab_sendSms (4713837923748234081) -->
+    <skip />
+    <!-- no translation found for permdesc_sendSms (7126594387176704010) -->
+    <skip />
+    <!-- no translation found for permlab_readSms (4256004535185449429) -->
+    <skip />
+    <!-- no translation found for permdesc_readSms (4586480500886941902) -->
+    <skip />
+    <!-- no translation found for permlab_writeSms (8453452414726246828) -->
+    <skip />
+    <!-- no translation found for permdesc_writeSms (1036408118901361812) -->
+    <skip />
+    <!-- no translation found for permlab_receiveWapPush (5726837205927152203) -->
+    <skip />
+    <!-- no translation found for permdesc_receiveWapPush (4779188629794134886) -->
+    <skip />
+    <!-- no translation found for permlab_getTasks (357640569227780364) -->
+    <skip />
+    <!-- no translation found for permdesc_getTasks (2916615403728003200) -->
+    <skip />
+    <!-- no translation found for permlab_reorderTasks (4758862288285224517) -->
+    <skip />
+    <!-- no translation found for permdesc_reorderTasks (7507060843941912021) -->
+    <skip />
+    <!-- no translation found for permlab_setDebugApp (2973363275929449444) -->
+    <skip />
+    <!-- no translation found for permdesc_setDebugApp (5720449860498265972) -->
+    <skip />
+    <!-- no translation found for permlab_changeConfiguration (8581093564179818627) -->
+    <skip />
+    <!-- no translation found for permdesc_changeConfiguration (4055366453803187171) -->
+    <skip />
+    <!-- no translation found for permlab_restartPackages (5836367540766044606) -->
+    <skip />
+    <!-- no translation found for permdesc_restartPackages (1764965996765573321) -->
+    <skip />
+    <!-- no translation found for permlab_setProcessForeground (4860990420780868638) -->
+    <skip />
+    <!-- no translation found for permdesc_setProcessForeground (3795477299954784360) -->
+    <skip />
+    <!-- no translation found for permlab_forceBack (4737517869935566733) -->
+    <skip />
+    <!-- no translation found for permdesc_forceBack (5579316297001154697) -->
+    <skip />
+    <!-- no translation found for permlab_dump (3177569414212943167) -->
+    <skip />
+    <!-- no translation found for permdesc_dump (1815913623373011608) -->
+    <skip />
+    <!-- no translation found for permlab_addSystemService (9166015020584794942) -->
+    <skip />
+    <!-- no translation found for permdesc_addSystemService (2310425587289835743) -->
+    <skip />
+    <!-- no translation found for permlab_runSetActivityWatcher (2615943932761994905) -->
+    <skip />
+    <!-- no translation found for permdesc_runSetActivityWatcher (2488524206195482220) -->
+    <skip />
+    <!-- no translation found for permlab_broadcastPackageRemoved (355775368495637820) -->
+    <skip />
+    <!-- no translation found for permdesc_broadcastPackageRemoved (6486181398191058385) -->
+    <skip />
+    <!-- no translation found for permlab_broadcastSmsReceived (1994692154847312518) -->
+    <skip />
+    <!-- no translation found for permdesc_broadcastSmsReceived (6072362543164841432) -->
+    <skip />
+    <!-- no translation found for permlab_broadcastWapPush (3070023012636951639) -->
+    <skip />
+    <!-- no translation found for permdesc_broadcastWapPush (726912255218924336) -->
+    <skip />
+    <!-- no translation found for permlab_setProcessLimit (5190694306017260601) -->
+    <skip />
+    <!-- no translation found for permdesc_setProcessLimit (593938303319848578) -->
+    <skip />
+    <!-- no translation found for permlab_setAlwaysFinish (8745533365504920540) -->
+    <skip />
+    <!-- no translation found for permdesc_setAlwaysFinish (2437195869854312148) -->
+    <skip />
+    <!-- no translation found for permlab_fotaUpdate (1813039882829307079) -->
+    <skip />
+    <!-- no translation found for permdesc_fotaUpdate (2544137712607584763) -->
+    <skip />
+    <!-- no translation found for permlab_batteryStats (1598947993704535568) -->
+    <skip />
+    <!-- no translation found for permdesc_batteryStats (6247598531831307989) -->
+    <skip />
+    <!-- no translation found for permlab_internalSystemWindow (5780262737320556654) -->
+    <skip />
+    <!-- no translation found for permdesc_internalSystemWindow (6495031598062517795) -->
+    <skip />
+    <!-- no translation found for permlab_systemAlertWindow (843729657746130626) -->
+    <skip />
+    <!-- no translation found for permdesc_systemAlertWindow (2731854380682210852) -->
+    <skip />
+    <!-- no translation found for permlab_setAnimationScale (2419250686027992384) -->
+    <skip />
+    <!-- no translation found for permdesc_setAnimationScale (8518027785481727264) -->
+    <skip />
+    <!-- no translation found for permlab_manageAppTokens (1033424552444304594) -->
+    <skip />
+    <!-- no translation found for permdesc_manageAppTokens (7285840918912623550) -->
+    <skip />
+    <!-- no translation found for permlab_injectEvents (1383601196263145482) -->
+    <skip />
+    <!-- no translation found for permdesc_injectEvents (840097509341464737) -->
+    <skip />
+    <!-- no translation found for permlab_readInputState (2723668746963882102) -->
+    <skip />
+    <!-- no translation found for permdesc_readInputState (4651137638757852001) -->
+    <skip />
+    <!-- no translation found for permlab_setOrientation (1112555600323148680) -->
+    <skip />
+    <!-- no translation found for permdesc_setOrientation (1960269530378827858) -->
+    <skip />
+    <!-- no translation found for permlab_signalPersistentProcesses (8511163028160623175) -->
+    <skip />
+    <!-- no translation found for permdesc_signalPersistentProcesses (1099349638354917733) -->
+    <skip />
+    <!-- no translation found for permlab_persistentActivity (8163108526929094627) -->
+    <skip />
+    <!-- no translation found for permdesc_persistentActivity (5258975883823299624) -->
+    <skip />
+    <!-- no translation found for permlab_deletePackages (5005536434839333208) -->
+    <skip />
+    <!-- no translation found for permdesc_deletePackages (2687196995215591923) -->
+    <skip />
+    <!-- no translation found for permlab_clearAppUserData (3858185484601410171) -->
+    <skip />
+    <!-- no translation found for permdesc_clearAppUserData (7233537744753081136) -->
+    <skip />
+    <!-- no translation found for permlab_deleteCacheFiles (7362746182961997888) -->
+    <skip />
+    <!-- no translation found for permdesc_deleteCacheFiles (8293849509208181266) -->
+    <skip />
+    <!-- no translation found for permlab_getPackageSize (6743556676630447973) -->
+    <skip />
+    <!-- no translation found for permdesc_getPackageSize (2893996655828539776) -->
+    <skip />
+    <!-- no translation found for permlab_installPackages (1637554234554641998) -->
+    <skip />
+    <!-- no translation found for permdesc_installPackages (4747794850590875195) -->
+    <skip />
+    <!-- no translation found for permlab_clearAppCache (7860214328511700776) -->
+    <skip />
+    <!-- no translation found for permdesc_clearAppCache (5203820862573167878) -->
+    <skip />
+    <!-- no translation found for permlab_readLogs (6653488552442991707) -->
+    <skip />
+    <!-- no translation found for permdesc_readLogs (356352685800884319) -->
+    <skip />
+    <!-- no translation found for permlab_diagnostic (2955142476313469329) -->
+    <skip />
+    <!-- no translation found for permdesc_diagnostic (1282409892215520166) -->
+    <skip />
+    <!-- no translation found for permlab_changeComponentState (8107835954049971459) -->
+    <skip />
+    <!-- no translation found for permdesc_changeComponentState (1791096057705836844) -->
+    <skip />
+    <!-- no translation found for permlab_setPreferredApplications (4355701371185331520) -->
+    <skip />
+    <!-- no translation found for permdesc_setPreferredApplications (9029326613767614711) -->
+    <skip />
+    <!-- no translation found for permlab_writeSettings (2915467191611898256) -->
+    <skip />
+    <!-- no translation found for permdesc_writeSettings (8492982548350342641) -->
+    <skip />
+    <!-- no translation found for permlab_writeSecureSettings (4851801872124242319) -->
+    <skip />
+    <!-- no translation found for permdesc_writeSecureSettings (2080620249472761366) -->
+    <skip />
+    <!-- no translation found for permlab_writeGservices (296370685945777755) -->
+    <skip />
+    <!-- no translation found for permdesc_writeGservices (2496928471286495053) -->
+    <skip />
+    <!-- no translation found for permlab_receiveBootCompleted (5598819384278633845) -->
+    <skip />
+    <!-- no translation found for permdesc_receiveBootCompleted (3197439472472771192) -->
+    <skip />
+    <!-- no translation found for permlab_broadcastSticky (8142357333543531543) -->
+    <skip />
+    <!-- no translation found for permdesc_broadcastSticky (8488762822718743531) -->
+    <skip />
+    <!-- no translation found for permlab_readContacts (5116003370450871686) -->
+    <skip />
+    <!-- no translation found for permdesc_readContacts (3499378044902258770) -->
+    <skip />
+    <!-- no translation found for permlab_writeContacts (1555136823460617179) -->
+    <skip />
+    <!-- no translation found for permdesc_writeContacts (4787318403287293114) -->
+    <skip />
+    <!-- no translation found for permlab_writeOwnerData (8036840529708535113) -->
+    <skip />
+    <!-- no translation found for permdesc_writeOwnerData (5873447528845878348) -->
+    <skip />
+    <!-- no translation found for permlab_readOwnerData (1847040178513733757) -->
+    <skip />
+    <!-- no translation found for permdesc_readOwnerData (7563299529149214764) -->
+    <skip />
+    <!-- no translation found for permlab_readCalendar (2111238731453410895) -->
+    <skip />
+    <!-- no translation found for permdesc_readCalendar (4408253940601239114) -->
+    <skip />
+    <!-- no translation found for permlab_writeCalendar (7518052789370653396) -->
+    <skip />
+    <!-- no translation found for permdesc_writeCalendar (8057304232140147596) -->
+    <skip />
+    <!-- no translation found for permlab_accessMockLocation (321094551062270213) -->
+    <skip />
+    <!-- no translation found for permdesc_accessMockLocation (3651565866471419739) -->
+    <skip />
+    <!-- no translation found for permlab_accessLocationExtraCommands (8291822077788811687) -->
+    <skip />
+    <!-- no translation found for permdesc_accessLocationExtraCommands (5135782633548630731) -->
+    <skip />
+    <!-- no translation found for permlab_accessFineLocation (4846261651944924865) -->
+    <skip />
+    <!-- no translation found for permdesc_accessFineLocation (3572307331039348419) -->
+    <skip />
+    <!-- no translation found for permlab_accessCoarseLocation (1760779730797169189) -->
+    <skip />
+    <!-- no translation found for permdesc_accessCoarseLocation (8878785899768310712) -->
+    <skip />
+    <!-- no translation found for permlab_accessSurfaceFlinger (6405475452322847618) -->
+    <skip />
+    <!-- no translation found for permdesc_accessSurfaceFlinger (5348283543622360967) -->
+    <skip />
+    <!-- no translation found for permlab_readFrameBuffer (4655248388550039199) -->
+    <skip />
+    <!-- no translation found for permdesc_readFrameBuffer (794888199105081402) -->
+    <skip />
+    <!-- no translation found for permlab_modifyAudioSettings (1587341813207960943) -->
+    <skip />
+    <!-- no translation found for permdesc_modifyAudioSettings (1447143004892708149) -->
+    <skip />
+    <!-- no translation found for permlab_recordAudio (4447848534036991667) -->
+    <skip />
+    <!-- no translation found for permdesc_recordAudio (6936874682400894820) -->
+    <skip />
+    <!-- no translation found for permlab_camera (1944473855727060380) -->
+    <skip />
+    <!-- no translation found for permdesc_camera (5978058582323766022) -->
+    <skip />
+    <!-- no translation found for permlab_brick (4749832243303289777) -->
+    <skip />
+    <!-- no translation found for permdesc_brick (7428524578693695766) -->
+    <skip />
+    <!-- no translation found for permlab_reboot (8844650672567077423) -->
+    <skip />
+    <!-- no translation found for permdesc_reboot (4704919552870918328) -->
+    <skip />
+    <!-- no translation found for permlab_mount_unmount_filesystems (1009574821038043781) -->
+    <skip />
+    <!-- no translation found for permdesc_mount_unmount_filesystems (100792065894811109) -->
+    <skip />
+    <!-- no translation found for permlab_vibrate (61984555644467146) -->
+    <skip />
+    <!-- no translation found for permdesc_vibrate (7831723100758509238) -->
+    <skip />
+    <!-- no translation found for permlab_flashlight (9097145977808182652) -->
+    <skip />
+    <!-- no translation found for permdesc_flashlight (7851502731988978358) -->
+    <skip />
+    <!-- no translation found for permlab_hardware_test (4103324677866524254) -->
+    <skip />
+    <!-- no translation found for permdesc_hardware_test (7315242723603994769) -->
+    <skip />
+    <!-- no translation found for permlab_callPhone (168275616535116686) -->
+    <skip />
+    <!-- no translation found for permdesc_callPhone (1852033967965785973) -->
+    <skip />
+    <!-- no translation found for permlab_callPrivileged (2166923597287697159) -->
+    <skip />
+    <!-- no translation found for permdesc_callPrivileged (5109789447971735501) -->
+    <skip />
+    <!-- no translation found for permlab_locationUpdates (4216418293360456836) -->
+    <skip />
+    <!-- no translation found for permdesc_locationUpdates (7635814693478743648) -->
+    <skip />
+    <!-- no translation found for permlab_checkinProperties (2260796787386280708) -->
+    <skip />
+    <!-- no translation found for permdesc_checkinProperties (3508022022841741945) -->
+    <skip />
+    <!-- no translation found for permlab_modifyPhoneState (7791696535097912313) -->
+    <skip />
+    <!-- no translation found for permdesc_modifyPhoneState (6352405226410454770) -->
+    <skip />
+    <!-- no translation found for permlab_readPhoneState (7320082586621086653) -->
+    <skip />
+    <!-- no translation found for permdesc_readPhoneState (8004450067066407969) -->
+    <skip />
+    <!-- no translation found for permlab_wakeLock (1591164750935072136) -->
+    <skip />
+    <!-- no translation found for permdesc_wakeLock (160471538196734936) -->
+    <skip />
+    <!-- no translation found for permlab_devicePower (9214865067086065548) -->
+    <skip />
+    <!-- no translation found for permdesc_devicePower (5608364066480036402) -->
+    <skip />
+    <!-- no translation found for permlab_factoryTest (7786199300637896247) -->
+    <skip />
+    <!-- no translation found for permdesc_factoryTest (3466066005210542042) -->
+    <skip />
+    <!-- no translation found for permlab_setWallpaper (2256730637138641725) -->
+    <skip />
+    <!-- no translation found for permdesc_setWallpaper (3034653140208685093) -->
+    <skip />
+    <!-- no translation found for permlab_setWallpaperHints (4192438316932517807) -->
+    <skip />
+    <!-- no translation found for permdesc_setWallpaperHints (738757439960921674) -->
+    <skip />
+    <!-- no translation found for permlab_masterClear (6155403967270586906) -->
+    <skip />
+    <!-- no translation found for permdesc_masterClear (4213553172342689754) -->
+    <skip />
+    <!-- no translation found for permlab_setTimeZone (477196167239548690) -->
+    <skip />
+    <!-- no translation found for permdesc_setTimeZone (8564892020460841198) -->
+    <skip />
+    <!-- no translation found for permlab_getAccounts (2764070033402295170) -->
+    <skip />
+    <!-- no translation found for permdesc_getAccounts (1203491378748649898) -->
+    <skip />
+    <!-- no translation found for permlab_accessNetworkState (2032916924886010827) -->
+    <skip />
+    <!-- no translation found for permdesc_accessNetworkState (7081329402551195933) -->
+    <skip />
+    <!-- no translation found for permlab_createNetworkSockets (4706698319966917864) -->
+    <skip />
+    <!-- no translation found for permdesc_createNetworkSockets (2580337178778551792) -->
+    <skip />
+    <!-- no translation found for permlab_writeApnSettings (3190585220761979369) -->
+    <skip />
+    <!-- no translation found for permdesc_writeApnSettings (4093875220468761052) -->
+    <skip />
+    <!-- no translation found for permlab_changeNetworkState (2710779001260856872) -->
+    <skip />
+    <!-- no translation found for permdesc_changeNetworkState (8076109230787022270) -->
+    <skip />
+    <!-- no translation found for permlab_accessWifiState (3613679494230374297) -->
+    <skip />
+    <!-- no translation found for permdesc_accessWifiState (8226508433563326925) -->
+    <skip />
+    <!-- no translation found for permlab_changeWifiState (6043889338995432957) -->
+    <skip />
+    <!-- no translation found for permdesc_changeWifiState (7829372845909567994) -->
+    <skip />
+    <!-- no translation found for permlab_bluetoothAdmin (5513286736585647334) -->
+    <skip />
+    <!-- no translation found for permdesc_bluetoothAdmin (1838208497914347365) -->
+    <skip />
+    <!-- no translation found for permlab_bluetooth (6378797624765639115) -->
+    <skip />
+    <!-- no translation found for permdesc_bluetooth (8592386018922265273) -->
+    <skip />
+    <!-- no translation found for permlab_disableKeyguard (4574886811903233903) -->
+    <skip />
+    <!-- no translation found for permdesc_disableKeyguard (815972646344251271) -->
+    <skip />
+    <!-- no translation found for permlab_readSyncSettings (8818819977141505127) -->
+    <skip />
+    <!-- no translation found for permdesc_readSyncSettings (8454705401908767847) -->
+    <skip />
+    <!-- no translation found for permlab_writeSyncSettings (4514911143753152941) -->
+    <skip />
+    <!-- no translation found for permdesc_writeSyncSettings (7630627689635091836) -->
+    <skip />
+    <!-- no translation found for permlab_readSyncStats (5748337739678952863) -->
+    <skip />
+    <!-- no translation found for permdesc_readSyncStats (582551457321957183) -->
+    <skip />
+    <!-- no translation found for permlab_subscribedFeedsRead (2043206814904506589) -->
+    <skip />
+    <!-- no translation found for permdesc_subscribedFeedsRead (6977343942680042449) -->
+    <skip />
+    <!-- no translation found for permlab_subscribedFeedsWrite (2556727307229571556) -->
+    <skip />
+    <!-- no translation found for permdesc_subscribedFeedsWrite (4134783294590266220) -->
+    <skip />
+    <!-- no translation found for phoneTypes:0 (6070018634209800981) -->
+    <!-- no translation found for phoneTypes:1 (1514509689885965711) -->
+    <!-- no translation found for phoneTypes:2 (497473201754095234) -->
+    <!-- no translation found for phoneTypes:3 (5554432614281047787) -->
+    <!-- no translation found for phoneTypes:4 (2222084401110150993) -->
+    <!-- no translation found for phoneTypes:5 (2290007103906353121) -->
+    <!-- no translation found for phoneTypes:6 (6930783706213719251) -->
+    <!-- no translation found for phoneTypes:7 (1326005699931077792) -->
+    <!-- no translation found for emailAddressTypes:0 (1540640638077615417) -->
+    <!-- no translation found for emailAddressTypes:1 (4252853367575831977) -->
+    <!-- no translation found for emailAddressTypes:2 (7158046581744435718) -->
+    <!-- no translation found for emailAddressTypes:3 (3625034471181268169) -->
+    <!-- no translation found for postalAddressTypes:0 (5732960259696659380) -->
+    <!-- no translation found for postalAddressTypes:1 (7132240704786130285) -->
+    <!-- no translation found for postalAddressTypes:2 (1317604357745852817) -->
+    <!-- no translation found for postalAddressTypes:3 (1582953598462826702) -->
+    <!-- no translation found for imAddressTypes:0 (7806620012096518833) -->
+    <!-- no translation found for imAddressTypes:1 (5748846799950672787) -->
+    <!-- no translation found for imAddressTypes:2 (6196536810275073680) -->
+    <!-- no translation found for imAddressTypes:3 (8519128375350623648) -->
+    <!-- no translation found for organizationTypes:0 (1299224825223821142) -->
+    <!-- no translation found for organizationTypes:1 (2455717447227299354) -->
+    <!-- no translation found for organizationTypes:2 (7027570839313438290) -->
+    <!-- no translation found for imProtocols:0 (3318725788774688043) -->
+    <!-- no translation found for imProtocols:1 (1787713387022932886) -->
+    <!-- no translation found for imProtocols:2 (6751174158442316516) -->
+    <!-- no translation found for imProtocols:3 (1151283347465052653) -->
+    <!-- no translation found for imProtocols:4 (2157980008878817934) -->
+    <!-- no translation found for imProtocols:5 (7836237460308230767) -->
+    <!-- no translation found for imProtocols:6 (1180789904462172516) -->
+    <!-- no translation found for imProtocols:7 (21955111672779862) -->
+    <!-- no translation found for keyguard_password_enter_pin_code (6779835451906812518) -->
+    <skip />
+    <!-- no translation found for keyguard_password_wrong_pin_code (230312338493035499) -->
+    <skip />
+    <!-- no translation found for keyguard_label_text (3902954467573892533) -->
+    <skip />
+    <!-- no translation found for emergency_call_dialog_number_for_display (6256361184251050511) -->
+    <skip />
+    <!-- no translation found for lockscreen_carrier_default (5222269885486229730) -->
+    <skip />
+    <!-- no translation found for lockscreen_screen_locked (1922273663462058967) -->
+    <skip />
+    <!-- no translation found for lockscreen_instructions_when_pattern_enabled (7535864145009679967) -->
+    <skip />
+    <!-- no translation found for lockscreen_instructions_when_pattern_disabled (6526504555912746785) -->
+    <skip />
+    <!-- no translation found for lockscreen_pattern_instructions (8984964506352089877) -->
+    <skip />
+    <!-- no translation found for lockscreen_emergency_call (422835617844547383) -->
+    <skip />
+    <!-- no translation found for lockscreen_pattern_correct (7104753084746383672) -->
+    <skip />
+    <!-- no translation found for lockscreen_pattern_wrong (7517004470797680361) -->
+    <skip />
+    <!-- no translation found for lockscreen_plugged_in (8806977650003537118) -->
+    <skip />
+    <!-- no translation found for lockscreen_low_battery (9002637795199621345) -->
+    <skip />
+    <!-- no translation found for lockscreen_missing_sim_message_short (5051192587315492957) -->
+    <skip />
+    <!-- no translation found for lockscreen_missing_sim_message (8912914495901434841) -->
+    <skip />
+    <!-- no translation found for lockscreen_missing_sim_instructions (8125847194365725429) -->
+    <skip />
+    <!-- no translation found for lockscreen_network_locked_message (323609607922245071) -->
+    <skip />
+    <!-- no translation found for lockscreen_sim_puk_locked_message (1005803622871256359) -->
+    <skip />
+    <!-- no translation found for lockscreen_sim_puk_locked_instructions (5033160098036646955) -->
+    <skip />
+    <!-- no translation found for lockscreen_sim_locked_message (7398401200962556379) -->
+    <skip />
+    <!-- no translation found for lockscreen_sim_unlock_progress_dialog_message (5939537246164692076) -->
+    <skip />
+    <!-- no translation found for lockscreen_too_many_failed_attempts_dialog_message (6709066241494622136) -->
+    <skip />
+    <!-- no translation found for lockscreen_failed_attempts_almost_glogin (1569017295989454551) -->
+    <skip />
+    <!-- no translation found for lockscreen_too_many_failed_attempts_countdown (8823588000022797566) -->
+    <skip />
+    <!-- no translation found for lockscreen_forgot_pattern_button_text (4219994639843985488) -->
+    <skip />
+    <!-- no translation found for lockscreen_glogin_too_many_attempts (7504679498838839295) -->
+    <skip />
+    <!-- no translation found for lockscreen_glogin_instructions (6542400673357252011) -->
+    <skip />
+    <!-- no translation found for lockscreen_glogin_username_hint (6378418320242015111) -->
+    <skip />
+    <!-- no translation found for lockscreen_glogin_password_hint (3224230234042131153) -->
+    <skip />
+    <!-- no translation found for lockscreen_glogin_submit_button (5562051040043760034) -->
+    <skip />
+    <!-- no translation found for lockscreen_glogin_invalid_input (4881057177478491580) -->
+    <skip />
+    <!-- no translation found for status_bar_time_format (2168573805413119180) -->
+    <skip />
+    <!-- no translation found for hour_minute_ampm (1850330605794978742) -->
+    <skip />
+    <!-- no translation found for hour_minute_cap_ampm (1122840227537374196) -->
+    <skip />
+    <!-- no translation found for hour_ampm (7665432130905376251) -->
+    <skip />
+    <!-- no translation found for hour_cap_ampm (3600295014648400268) -->
+    <skip />
+    <!-- no translation found for status_bar_clear_all_button (2202004591253243750) -->
+    <skip />
+    <!-- no translation found for status_bar_no_notifications_title (5123133188102094464) -->
+    <skip />
+    <!-- no translation found for status_bar_ongoing_events_title (799961521630569167) -->
+    <skip />
+    <!-- no translation found for status_bar_latest_events_title (5414094466807164279) -->
+    <skip />
+    <!-- no translation found for battery_status_text_percent_format (7391464609447031944) -->
+    <skip />
+    <!-- no translation found for battery_status_charging (5078780715755132756) -->
+    <skip />
+    <!-- no translation found for battery_low_title (3665400828395001695) -->
+    <skip />
+    <!-- no translation found for battery_low_subtitle (7537149915372180016) -->
+    <skip />
+    <!-- no translation found for battery_low_percent_format (8635359708781261154) -->
+    <skip />
+    <!-- no translation found for factorytest_failed (5784901108608196679) -->
+    <skip />
+    <!-- no translation found for factorytest_not_system (6330339565054095688) -->
+    <skip />
+    <!-- no translation found for factorytest_no_action (1662569013408679347) -->
+    <skip />
+    <!-- no translation found for factorytest_reboot (6080912029718954885) -->
+    <skip />
+    <!-- no translation found for save_password_label (4129493019621348626) -->
+    <skip />
+    <!-- no translation found for save_password_message (7412617920202682045) -->
+    <skip />
+    <!-- no translation found for save_password_notnow (3887362423496820832) -->
+    <skip />
+    <!-- no translation found for save_password_remember (4319688896716308569) -->
+    <skip />
+    <!-- no translation found for save_password_never (1836981952883642377) -->
+    <skip />
+    <!-- no translation found for open_permission_deny (6408502671105717111) -->
+    <skip />
+    <!-- no translation found for text_copied (6106873823411904723) -->
+    <skip />
+    <!-- no translation found for more_item_label (5204075544750360778) -->
+    <skip />
+    <!-- no translation found for prepend_shortcut_label (6091430648975237047) -->
+    <skip />
+    <!-- no translation found for menu_space_shortcut_label (194586306440382711) -->
+    <skip />
+    <!-- no translation found for menu_enter_shortcut_label (7214761412193519345) -->
+    <skip />
+    <!-- no translation found for menu_delete_shortcut_label (2854936426194985313) -->
+    <skip />
+    <!-- no translation found for search_go (4823831235057123206) -->
+    <skip />
+    <!-- no translation found for today (6914914811057683636) -->
+    <skip />
+    <!-- no translation found for yesterday (5280495043584636271) -->
+    <skip />
+    <!-- no translation found for tomorrow (561215115479060939) -->
+    <skip />
+    <!-- no translation found for oneMonthDurationPast (3402179395240209557) -->
+    <skip />
+    <!-- no translation found for beforeOneMonthDurationPast (7578100953282866827) -->
+    <skip />
+    <!-- no translation found for num_seconds_ago:one (7416512229671810725) -->
+    <!-- no translation found for num_seconds_ago:other (8138756910300398447) -->
+    <!-- no translation found for num_minutes_ago:one (8620869479299420562) -->
+    <!-- no translation found for num_minutes_ago:other (5065488162050522741) -->
+    <!-- no translation found for num_hours_ago:one (853404611989669641) -->
+    <!-- no translation found for num_hours_ago:other (3558873784561756849) -->
+    <!-- no translation found for num_days_ago:one (4222479980812128212) -->
+    <!-- no translation found for num_days_ago:other (5445701370433601703) -->
+    <!-- no translation found for in_num_seconds:one (4253290037777327003) -->
+    <!-- no translation found for in_num_seconds:other (1280033870920841404) -->
+    <!-- no translation found for in_num_minutes:one (1487585791027953091) -->
+    <!-- no translation found for in_num_minutes:other (6274204576475209932) -->
+    <!-- no translation found for in_num_hours:one (6501470863235186391) -->
+    <!-- no translation found for in_num_hours:other (4415358752953289251) -->
+    <!-- no translation found for in_num_days:one (5608475533104443893) -->
+    <!-- no translation found for in_num_days:other (3827193006163842267) -->
+    <!-- no translation found for preposition_for_date (2689847983632851560) -->
+    <skip />
+    <!-- no translation found for preposition_for_time (2613388053493148013) -->
+    <skip />
+    <!-- no translation found for preposition_for_year (6968468294728152393) -->
+    <skip />
+    <!-- no translation found for day (7849249054576985912) -->
+    <skip />
+    <!-- no translation found for days (8381828105391141169) -->
+    <skip />
+    <!-- no translation found for hour (1044439788994278057) -->
+    <skip />
+    <!-- no translation found for hours (9008157371441255845) -->
+    <skip />
+    <!-- no translation found for minute (2434431396283136076) -->
+    <skip />
+    <!-- no translation found for minutes (8176836254200264856) -->
+    <skip />
+    <!-- no translation found for second (6620645953323664299) -->
+    <skip />
+    <!-- no translation found for seconds (6416703426008384360) -->
+    <skip />
+    <!-- no translation found for week (7738046527402739781) -->
+    <skip />
+    <!-- no translation found for weeks (3178327674459887377) -->
+    <skip />
+    <!-- no translation found for year (8024790425994085153) -->
+    <skip />
+    <!-- no translation found for years (8592090054773244417) -->
+    <skip />
+    <!-- no translation found for sunday (4811082193700148223) -->
+    <skip />
+    <!-- no translation found for monday (7543713499896911033) -->
+    <skip />
+    <!-- no translation found for tuesday (7962192298359117585) -->
+    <skip />
+    <!-- no translation found for wednesday (5768878309383390437) -->
+    <skip />
+    <!-- no translation found for thursday (5690060634904123607) -->
+    <skip />
+    <!-- no translation found for friday (2718325370375116889) -->
+    <skip />
+    <!-- no translation found for saturday (222899317300942333) -->
+    <skip />
+    <!-- no translation found for every_weekday (8466333034903391066) -->
+    <skip />
+    <!-- no translation found for daily (1661712840773846970) -->
+    <skip />
+    <!-- no translation found for weekly (578642117234613009) -->
+    <skip />
+    <!-- no translation found for monthly (8526124657540210537) -->
+    <skip />
+    <!-- no translation found for yearly (8083067713764127070) -->
+    <skip />
+    <!-- no translation found for VideoView_error_title (1024334251681931859) -->
+    <skip />
+    <!-- no translation found for VideoView_error_text_unknown (3398417247398476771) -->
+    <skip />
+    <!-- no translation found for VideoView_error_button (3144127115413163445) -->
+    <skip />
+    <!-- no translation found for am (5354895493921411502) -->
+    <skip />
+    <!-- no translation found for pm (7206933220587555766) -->
+    <skip />
+    <!-- no translation found for numeric_date (5120078478872821100) -->
+    <skip />
+    <!-- no translation found for wday1_date1_time1_wday2_date2_time2 (7066878981949584861) -->
+    <skip />
+    <!-- no translation found for wday1_date1_wday2_date2 (8671068747172261907) -->
+    <skip />
+    <!-- no translation found for date1_time1_date2_time2 (3645498975775629615) -->
+    <skip />
+    <!-- no translation found for date1_date2 (377057563556488062) -->
+    <skip />
+    <!-- no translation found for time1_time2 (3173474242109288305) -->
+    <skip />
+    <!-- no translation found for time_wday_date (8928955562064570313) -->
+    <skip />
+    <!-- no translation found for wday_date (8794741400546136975) -->
+    <skip />
+    <!-- no translation found for time_date (1922644512833014496) -->
+    <skip />
+    <!-- no translation found for time_wday (1422050241301754712) -->
+    <skip />
+    <!-- no translation found for full_date_month_first (6011143962222283357) -->
+    <skip />
+    <!-- no translation found for full_date_day_first (8621594762705478189) -->
+    <skip />
+    <!-- no translation found for medium_date_month_first (48990963718825728) -->
+    <skip />
+    <!-- no translation found for medium_date_day_first (2898992016440387123) -->
+    <skip />
+    <!-- no translation found for twelve_hour_time_format (6015557937879492156) -->
+    <skip />
+    <!-- no translation found for twenty_four_hour_time_format (5176807998669709535) -->
+    <skip />
+    <!-- no translation found for noon (8390796001560682897) -->
+    <skip />
+    <!-- no translation found for Noon (7698941576181064429) -->
+    <skip />
+    <!-- no translation found for midnight (7773339795626486146) -->
+    <skip />
+    <!-- no translation found for Midnight (1260172107848123187) -->
+    <skip />
+    <!-- no translation found for month_day (3356633704511426364) -->
+    <skip />
+    <!-- no translation found for month (3017405760734206414) -->
+    <skip />
+    <!-- no translation found for month_day_year (2435948225709176752) -->
+    <skip />
+    <!-- no translation found for month_year (6228414124777343135) -->
+    <skip />
+    <!-- no translation found for time_of_day (8375993139317154157) -->
+    <skip />
+    <!-- no translation found for date_and_time (9197690194373107109) -->
+    <skip />
+    <!-- no translation found for same_year_md1_md2 (9199324363135981317) -->
+    <skip />
+    <!-- no translation found for same_year_wday1_md1_wday2_md2 (6006392413355305178) -->
+    <skip />
+    <!-- no translation found for same_year_mdy1_mdy2 (1576657593937827090) -->
+    <skip />
+    <!-- no translation found for same_year_wday1_mdy1_wday2_mdy2 (9135935796468891580) -->
+    <skip />
+    <!-- no translation found for same_year_md1_time1_md2_time2 (2172964106375558081) -->
+    <skip />
+    <!-- no translation found for same_year_wday1_md1_time1_wday2_md2_time2 (1702879534101786310) -->
+    <skip />
+    <!-- no translation found for same_year_mdy1_time1_mdy2_time2 (2476443311723358767) -->
+    <skip />
+    <!-- no translation found for same_year_wday1_mdy1_time1_wday2_mdy2_time2 (1564837340334069879) -->
+    <skip />
+    <!-- no translation found for numeric_md1_md2 (8908376522875100300) -->
+    <skip />
+    <!-- no translation found for numeric_wday1_md1_wday2_md2 (3239690882018292077) -->
+    <skip />
+    <!-- no translation found for numeric_mdy1_mdy2 (8883797176939233525) -->
+    <skip />
+    <!-- no translation found for numeric_wday1_mdy1_wday2_mdy2 (4150475769255828954) -->
+    <skip />
+    <!-- no translation found for numeric_md1_time1_md2_time2 (3624746590607741419) -->
+    <skip />
+    <!-- no translation found for numeric_wday1_md1_time1_wday2_md2_time2 (4258040955467298134) -->
+    <skip />
+    <!-- no translation found for numeric_mdy1_time1_mdy2_time2 (3598215409314517987) -->
+    <skip />
+    <!-- no translation found for numeric_wday1_mdy1_time1_wday2_mdy2_time2 (264076937155877259) -->
+    <skip />
+    <!-- no translation found for same_month_md1_md2 (2393563617438036111) -->
+    <skip />
+    <!-- no translation found for same_month_wday1_md1_wday2_md2 (1208946773794057819) -->
+    <skip />
+    <!-- no translation found for same_month_mdy1_mdy2 (3713236637869030492) -->
+    <skip />
+    <!-- no translation found for same_month_wday1_mdy1_wday2_mdy2 (389638922479870472) -->
+    <skip />
+    <!-- no translation found for same_month_md1_time1_md2_time2 (7477075526337542685) -->
+    <skip />
+    <!-- no translation found for same_month_wday1_md1_time1_wday2_md2_time2 (3516978303779391173) -->
+    <skip />
+    <!-- no translation found for same_month_mdy1_time1_mdy2_time2 (7320410992514057310) -->
+    <skip />
+    <!-- no translation found for same_month_wday1_mdy1_time1_wday2_mdy2_time2 (1332950588774239228) -->
+    <skip />
+    <!-- no translation found for abbrev_month_day_year (5767271534015320250) -->
+    <skip />
+    <!-- no translation found for abbrev_month_year (8058929633673942490) -->
+    <skip />
+    <!-- no translation found for abbrev_month_day (458867920693482757) -->
+    <skip />
+    <!-- no translation found for abbrev_month (1674509986330181349) -->
+    <skip />
+    <!-- no translation found for day_of_week_long_sunday (9057662850446501884) -->
+    <skip />
+    <!-- no translation found for day_of_week_long_monday (7358451993082888343) -->
+    <skip />
+    <!-- no translation found for day_of_week_long_tuesday (2282901451170509613) -->
+    <skip />
+    <!-- no translation found for day_of_week_long_wednesday (2100217950343286482) -->
+    <skip />
+    <!-- no translation found for day_of_week_long_thursday (5475158963242863176) -->
+    <skip />
+    <!-- no translation found for day_of_week_long_friday (4081018004819837155) -->
+    <skip />
+    <!-- no translation found for day_of_week_long_saturday (1929694088305891795) -->
+    <skip />
+    <!-- no translation found for day_of_week_medium_sunday (6462580883948669820) -->
+    <skip />
+    <!-- no translation found for day_of_week_medium_monday (6960587654241349502) -->
+    <skip />
+    <!-- no translation found for day_of_week_medium_tuesday (7004462235990108936) -->
+    <skip />
+    <!-- no translation found for day_of_week_medium_wednesday (5688564741951314696) -->
+    <skip />
+    <!-- no translation found for day_of_week_medium_thursday (1784339868453982400) -->
+    <skip />
+    <!-- no translation found for day_of_week_medium_friday (4314577583604069357) -->
+    <skip />
+    <!-- no translation found for day_of_week_medium_saturday (70321191398427845) -->
+    <skip />
+    <!-- no translation found for day_of_week_short_sunday (7403409454572591357) -->
+    <skip />
+    <!-- no translation found for day_of_week_short_monday (5278358100012478239) -->
+    <skip />
+    <!-- no translation found for day_of_week_short_tuesday (5121116040712487059) -->
+    <skip />
+    <!-- no translation found for day_of_week_short_wednesday (1601079579293330319) -->
+    <skip />
+    <!-- no translation found for day_of_week_short_thursday (5863422096017401812) -->
+    <skip />
+    <!-- no translation found for day_of_week_short_friday (2916686031099723960) -->
+    <skip />
+    <!-- no translation found for day_of_week_short_saturday (8521564973195542073) -->
+    <skip />
+    <!-- no translation found for day_of_week_shorter_sunday (1650484495176707638) -->
+    <skip />
+    <!-- no translation found for day_of_week_shorter_monday (9133193697786876074) -->
+    <skip />
+    <!-- no translation found for day_of_week_shorter_tuesday (4012095408481489663) -->
+    <skip />
+    <!-- no translation found for day_of_week_shorter_wednesday (6279056612496078470) -->
+    <skip />
+    <!-- no translation found for day_of_week_shorter_thursday (2748599403545071011) -->
+    <skip />
+    <!-- no translation found for day_of_week_shorter_friday (5037282109124849673) -->
+    <skip />
+    <!-- no translation found for day_of_week_shorter_saturday (3208167155877833783) -->
+    <skip />
+    <!-- no translation found for day_of_week_shortest_sunday (4683862964821549758) -->
+    <skip />
+    <!-- no translation found for day_of_week_shortest_monday (6701142261471667000) -->
+    <skip />
+    <!-- no translation found for day_of_week_shortest_tuesday (9098171980161292477) -->
+    <skip />
+    <!-- no translation found for day_of_week_shortest_wednesday (655049238289460956) -->
+    <skip />
+    <!-- no translation found for day_of_week_shortest_thursday (7816913627500884083) -->
+    <skip />
+    <!-- no translation found for day_of_week_shortest_friday (903301878650619398) -->
+    <skip />
+    <!-- no translation found for day_of_week_shortest_saturday (5359692489649817988) -->
+    <skip />
+    <!-- no translation found for month_long_january (7128497801440564337) -->
+    <skip />
+    <!-- no translation found for month_long_february (7808570514581190617) -->
+    <skip />
+    <!-- no translation found for month_long_march (2061328556983796034) -->
+    <skip />
+    <!-- no translation found for month_long_april (6575007959043269919) -->
+    <skip />
+    <!-- no translation found for month_long_may (8404051103463071121) -->
+    <skip />
+    <!-- no translation found for month_long_june (6255771619238859451) -->
+    <skip />
+    <!-- no translation found for month_long_july (4129177743136800884) -->
+    <skip />
+    <!-- no translation found for month_long_august (5494331003296804494) -->
+    <skip />
+    <!-- no translation found for month_long_september (2691137479752033087) -->
+    <skip />
+    <!-- no translation found for month_long_october (7501261567327243313) -->
+    <skip />
+    <!-- no translation found for month_long_november (8759690753068763664) -->
+    <skip />
+    <!-- no translation found for month_long_december (4505008719696569497) -->
+    <skip />
+    <!-- no translation found for month_medium_january (2315492772833932512) -->
+    <skip />
+    <!-- no translation found for month_medium_february (118412521324313430) -->
+    <skip />
+    <!-- no translation found for month_medium_march (5546835583839352358) -->
+    <skip />
+    <!-- no translation found for month_medium_april (7052559668687733702) -->
+    <skip />
+    <!-- no translation found for month_medium_may (2825303871720116018) -->
+    <skip />
+    <!-- no translation found for month_medium_june (829843667101495271) -->
+    <skip />
+    <!-- no translation found for month_medium_july (5029778226925324789) -->
+    <skip />
+    <!-- no translation found for month_medium_august (8851230594641162805) -->
+    <skip />
+    <!-- no translation found for month_medium_september (8420590486625304647) -->
+    <skip />
+    <!-- no translation found for month_medium_october (1787382806172930239) -->
+    <skip />
+    <!-- no translation found for month_medium_november (675513809622370603) -->
+    <skip />
+    <!-- no translation found for month_medium_december (2934948295928978783) -->
+    <skip />
+    <!-- no translation found for month_shortest_january (6070060405144675883) -->
+    <skip />
+    <!-- no translation found for month_shortest_february (5632605004902176653) -->
+    <skip />
+    <!-- no translation found for month_shortest_march (4304231552356086624) -->
+    <skip />
+    <!-- no translation found for month_shortest_april (1166434066469385532) -->
+    <skip />
+    <!-- no translation found for month_shortest_may (9131326028845529001) -->
+    <skip />
+    <!-- no translation found for month_shortest_june (1875723154506665289) -->
+    <skip />
+    <!-- no translation found for month_shortest_july (2003596275389810773) -->
+    <skip />
+    <!-- no translation found for month_shortest_august (9120245162625763214) -->
+    <skip />
+    <!-- no translation found for month_shortest_september (7980651111022693669) -->
+    <skip />
+    <!-- no translation found for month_shortest_october (3640405450427788312) -->
+    <skip />
+    <!-- no translation found for month_shortest_november (4002935318566146993) -->
+    <skip />
+    <!-- no translation found for month_shortest_december (6213739417171334040) -->
+    <skip />
+    <!-- no translation found for elapsed_time_short_format_mm_ss (1294409362352514646) -->
+    <skip />
+    <!-- no translation found for elapsed_time_short_format_h_mm_ss (2997059666628785039) -->
+    <skip />
+    <!-- no translation found for selectAll (691691810023908884) -->
+    <skip />
+    <!-- no translation found for cut (5845613239192595662) -->
+    <skip />
+    <!-- no translation found for cutAll (4474519683293791451) -->
+    <skip />
+    <!-- no translation found for copy (8603721575469529820) -->
+    <skip />
+    <!-- no translation found for copyAll (4777548804630476932) -->
+    <skip />
+    <!-- no translation found for paste (6458036735811828538) -->
+    <skip />
+    <!-- no translation found for copyUrl (5785708478767435812) -->
+    <skip />
+    <!-- no translation found for inputMethod (7911866729148111492) -->
+    <skip />
+    <!-- no translation found for editTextMenuTitle (3984253728638788023) -->
+    <skip />
+    <!-- no translation found for low_internal_storage_view_title (5997772070488639934) -->
+    <skip />
+    <!-- no translation found for low_internal_storage_view_text (2230118755295375293) -->
+    <skip />
+    <!-- no translation found for ok (4003878536083514869) -->
+    <skip />
+    <!-- no translation found for cancel (1527674037280267012) -->
+    <skip />
+    <!-- no translation found for yes (8185296114406773873) -->
+    <skip />
+    <!-- no translation found for no (2300685350903156262) -->
+    <skip />
+    <!-- no translation found for capital_on (8418242581217554942) -->
+    <skip />
+    <!-- no translation found for capital_off (8870368560477693851) -->
+    <skip />
+    <!-- no translation found for whichApplication (2828159696176255212) -->
+    <skip />
+    <!-- no translation found for alwaysUse (6433627451071144629) -->
+    <skip />
+    <!-- no translation found for clearDefaultHintMsg (5742432113023174321) -->
+    <skip />
+    <!-- no translation found for chooseActivity (7588691622928031978) -->
+    <skip />
+    <!-- no translation found for noApplications (4068560364116066745) -->
+    <skip />
+    <!-- no translation found for aerr_title (2654390351574026098) -->
+    <skip />
+    <!-- no translation found for aerr_application (4917288809565116720) -->
+    <skip />
+    <!-- no translation found for aerr_process (1273819861108073461) -->
+    <skip />
+    <!-- no translation found for anr_title (3305935690891435915) -->
+    <skip />
+    <!-- no translation found for anr_activity_application (1653036325679156678) -->
+    <skip />
+    <!-- no translation found for anr_activity_process (2674027618362070465) -->
+    <skip />
+    <!-- no translation found for anr_application_process (2163656674970221928) -->
+    <skip />
+    <!-- no translation found for anr_process (7747550780123472160) -->
+    <skip />
+    <!-- no translation found for force_close (9020954128872810669) -->
+    <skip />
+    <!-- no translation found for wait (7973775702304037058) -->
+    <skip />
+    <!-- no translation found for debug (857932504764728770) -->
+    <skip />
+    <!-- no translation found for sendText (6158329286172492543) -->
+    <skip />
+    <!-- no translation found for volume_ringtone (4121694816346562058) -->
+    <skip />
+    <!-- no translation found for volume_music (4869950240104717493) -->
+    <skip />
+    <!-- no translation found for volume_call (5723421277753250395) -->
+    <skip />
+    <!-- no translation found for volume_alarm (2752102730973081294) -->
+    <skip />
+    <!-- no translation found for volume_unknown (6908187627672375742) -->
+    <skip />
+    <!-- no translation found for ringtone_default (2873893375149093475) -->
+    <skip />
+    <!-- no translation found for ringtone_default_with_actual (5474076151665761913) -->
+    <skip />
+    <!-- no translation found for ringtone_silent (7477159279081654685) -->
+    <skip />
+    <!-- no translation found for ringtone_picker_title (7055241890764367884) -->
+    <skip />
+    <!-- no translation found for ringtone_unknown (6888219771401173795) -->
+    <skip />
+    <!-- no translation found for wifi_available:one (8168012881468888470) -->
+    <!-- no translation found for wifi_available:other (4666122955807117718) -->
+    <!-- no translation found for wifi_available_detailed:one (5107769161192143259) -->
+    <!-- no translation found for wifi_available_detailed:other (853347657960575809) -->
+    <!-- no translation found for select_character (3735110139249491726) -->
+    <skip />
+    <!-- no translation found for sms_control_default_app_name (7522184737840550841) -->
+    <skip />
+    <!-- no translation found for sms_control_title (2742400596989418394) -->
+    <skip />
+    <!-- no translation found for sms_control_message (3447126217666595989) -->
+    <skip />
+    <!-- no translation found for sms_control_yes (8839660939359273650) -->
+    <skip />
+    <!-- no translation found for sms_control_no (909756849988183801) -->
+    <skip />
+    <!-- no translation found for date_time_set (2495199891239480952) -->
+    <skip />
+    <!-- no translation found for default_permission_group (7742780381379652409) -->
+    <skip />
+    <!-- no translation found for no_permissions (85461124044682315) -->
+    <skip />
+    <!-- no translation found for perms_hide (4145325555929151849) -->
+    <skip />
+    <!-- no translation found for perms_show_all (6040194843455403173) -->
+    <skip />
+    <!-- no translation found for googlewebcontenthelper_loading (2140804350507245589) -->
+    <skip />
+    <!-- no translation found for usb_storage_title (8699631567051394409) -->
+    <skip />
+    <!-- no translation found for usb_storage_message (5344039189213308733) -->
+    <skip />
+    <!-- no translation found for usb_storage_button_mount (6700104384375121662) -->
+    <skip />
+    <!-- no translation found for usb_storage_button_unmount (465869657252626688) -->
+    <skip />
+    <!-- no translation found for usb_storage_error_message (3192564550748426087) -->
+    <skip />
+    <!-- no translation found for usb_storage_notification_title (6237028017872246940) -->
+    <skip />
+    <!-- no translation found for usb_storage_notification_message (7371717280517625905) -->
+    <skip />
+    <!-- no translation found for select_input_method (2658280517827502015) -->
+    <skip />
+    <!-- no translation found for fast_scroll_alphabet (1017432309285755759) -->
+    <skip />
+    <!-- no translation found for fast_scroll_numeric_alphabet (3092587363718901074) -->
+    <skip />
+    <!-- no translation found for candidates_style (7738463880139922176) -->
+    <skip />
+</resources>
diff --git a/core/res/res/values-es-rES/arrays.xml b/core/res/res/values-es-rES/arrays.xml
new file mode 100644
index 0000000..7f5667c
--- /dev/null
+++ b/core/res/res/values-es-rES/arrays.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* //device/apps/common/assets/res/any/colors.xml
+**
+** Copyright 2006, Google Inc.
+**
+** 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.
+*/
+-->
+<resources>
+
+    <!-- Do not translate. -->
+    <integer-array name="maps_starting_lat_lng">
+        <item>40413496</item>
+        <item>-3713379</item>
+    </integer-array>
+    <!-- Do not translate. -->
+    <integer-array name="maps_starting_zoom">
+        <item>6</item>
+    </integer-array>
+
+</resources>
diff --git a/core/res/res/values-es/strings.xml b/core/res/res/values-es/strings.xml
new file mode 100644
index 0000000..495f8aa
--- /dev/null
+++ b/core/res/res/values-es/strings.xml
@@ -0,0 +1,815 @@
+<?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.
+-->
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="byteShort">"B"</string>
+    <string name="kilobyteShort">"KB"</string>
+    <string name="megabyteShort">"MB"</string>
+    <string name="gigabyteShort">"GB"</string>
+    <string name="terabyteShort">"TB"</string>
+    <string name="petabyteShort">"PB"</string>
+    <string name="untitled">"&lt;sin título&gt;"</string>
+    <string name="ellipsis">"…"</string>
+    <string name="emptyPhoneNumber">"(Sin número de teléfono)"</string>
+    <string name="unknownName">"Desconocido"</string>
+    <string name="defaultVoiceMailAlphaTag">"Buzón de voz"</string>
+    <string name="defaultMsisdnAlphaTag">"MSISDN1"</string>
+    <string name="mmiError">"Se ha producido un problema de conexión o el código MMI no es válido."</string>
+    <string name="serviceEnabled">"El servicio se ha habilitado."</string>
+    <string name="serviceEnabledFor">"Se ha habilitado el servicio para:"</string>
+    <string name="serviceDisabled">"El servicio se ha inhabilitado."</string>
+    <string name="serviceRegistered">"El registro se ha realizado correctamente."</string>
+    <string name="serviceErased">"El elemento se ha borrado correctamente."</string>
+    <string name="passwordIncorrect">"Contraseña incorrecta"</string>
+    <string name="mmiComplete">"MMI completo"</string>
+    <string name="badPin">"El PIN antiguo que has introducido no es correcto."</string>
+    <string name="badPuk">"El código PUK que has introducido no es correcto."</string>
+    <string name="mismatchPin">"Los códigos PIN introducidos no coinciden."</string>
+    <string name="invalidPin">"Introduce un código PIN con una longitud comprendida entre cuatro y ocho dígitos."</string>
+    <string name="needPuk">"La tarjeta SIM está bloqueada con el código PUK. Introduce el código PUK para desbloquearla."</string>
+    <string name="needPuk2">"Introduce el código PUK2 para desbloquear la tarjeta SIM."</string>
+    <string name="ClipMmi">"ID de emisor de llamada entrante"</string>
+    <string name="ClirMmi">"ID de emisor de llamada saliente"</string>
+    <string name="CfMmi">"Desvío de llamada"</string>
+    <string name="CwMmi">"Llamada en espera"</string>
+    <string name="BaMmi">"Bloqueo de llamada"</string>
+    <string name="PwdMmi">"Cambio de contraseña"</string>
+    <string name="PinMmi">"Cambio de PIN"</string>
+    <string name="CLIRDefaultOnNextCallOn">"El ID de emisor presenta el valor predeterminado de restringido. Siguiente llamada: Restringido"</string>
+    <string name="CLIRDefaultOnNextCallOff">"El ID de emisor presenta el valor predeterminado de restringido. Siguiente llamada: No restringido"</string>
+    <string name="CLIRDefaultOffNextCallOn">"El ID de emisor presenta el valor predeterminado de no restringido. Siguiente llamada: Restringido"</string>
+    <string name="CLIRDefaultOffNextCallOff">"El ID de emisor presenta el valor predeterminado de no restringido. Siguiente llamada: No restringido"</string>
+    <string name="serviceNotProvisioned">"El servicio no se suministra."</string>
+    <string name="CLIRPermanent">"El ID del emisor no se puede modificar."</string>
+    <string name="serviceClassVoice">"Voz"</string>
+    <string name="serviceClassData">"Datos"</string>
+    <string name="serviceClassFAX">"FAX"</string>
+    <string name="serviceClassSMS">"SMS"</string>
+    <string name="serviceClassDataAsync">"Asíncronos"</string>
+    <string name="serviceClassDataSync">"Sincronización"</string>
+    <string name="serviceClassPacket">"Paquete"</string>
+    <string name="serviceClassPAD">"PAD"</string>
+    <string name="cfTemplateNotForwarded">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: No desviada"</string>
+    <string name="cfTemplateForwarded">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
+    <string name="cfTemplateForwardedTime">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> transcurridos <xliff:g id="TIME_DELAY">{2}</xliff:g> segundos"</string>
+    <string name="cfTemplateRegistered">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: No desviada"</string>
+    <string name="cfTemplateRegisteredTime">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: No desviada"</string>
+    <string name="httpErrorOk">"Aceptar"</string>
+    <string name="httpError">"La página web contiene un error."</string>
+    <string name="httpErrorLookup">"No se ha podido encontrar la URL."</string>
+    <string name="httpErrorUnsupportedAuthScheme">"No se admite el esquema de autenticación del sitio."</string>
+    <string name="httpErrorAuth">"La autenticación no se ha realizado correctamente."</string>
+    <string name="httpErrorProxyAuth">"La autenticación mediante el servidor proxy no se ha realizado correctamente."</string>
+    <string name="httpErrorConnect">"La conexión al servidor no se ha realizado correctamente."</string>
+    <string name="httpErrorIO">"El servidor no ha podido establecer la comunicación. Vuelve a intentarlo más tarde."</string>
+    <string name="httpErrorTimeout">"Se ha agotado el tiempo de espera de conexión al servidor."</string>
+    <string name="httpErrorRedirectLoop">"La página contiene demasiados redireccionamientos de servidor."</string>
+    <string name="httpErrorUnsupportedScheme">"Protocolo no admitido"</string>
+    <string name="httpErrorFailedSslHandshake">"No se ha podido establecer una conexión segura."</string>
+    <string name="httpErrorBadUrl">"La página no se ha podido abrir porque la URL no es válida."</string>
+    <string name="httpErrorFile">"No se ha podido acceder al archivo."</string>
+    <string name="httpErrorFileNotFound">"No se ha encontrado el archivo solicitado."</string>
+    <string name="httpErrorTooManyRequests">"Se están procesando demasiadas solicitudes. Vuelve a intentarlo más tarde."</string>
+    <string name="contentServiceSync">"Sincronización"</string>
+    <string name="contentServiceSyncNotificationTitle">"Sincronización"</string>
+    <string name="contentServiceTooManyDeletesNotificationDesc">"Demasiadas eliminaciones de <xliff:g id="CONTENT_TYPE">%s</xliff:g>"</string>
+    <string name="low_memory">"Se ha agotado el espacio de almacenamiento del teléfono. Elimina algunos archivos para liberar espacio."</string>
+    <string name="me">"Yo"</string>
+    <string name="power_dialog">"Opciones del teléfono"</string>
+    <string name="silent_mode">"Modo silencio"</string>
+    <string name="turn_on_radio">"Activar conexión inalámbrica"</string>
+    <string name="turn_off_radio">"Desactivar función inalámbrica"</string>
+    <string name="screen_lock">"Bloqueo de pantalla"</string>
+    <string name="power_off">"Apagar"</string>
+    <string name="shutdown_progress">"Apagando..."</string>
+    <string name="shutdown_confirm">"El teléfono se apagará."</string>
+    <string name="no_recent_tasks">"No hay aplicaciones recientes"</string>
+    <string name="global_actions">"Opciones del teléfono"</string>
+    <string name="global_action_lock">"Bloqueo de pantalla"</string>
+    <string name="global_action_power_off">"Apagar"</string>
+    <string name="global_action_toggle_silent_mode">"Modo silencio"</string>
+    <string name="global_action_silent_mode_on_status">"El sonido está desactivado. Activar."</string>
+    <string name="global_action_silent_mode_off_status">"El sonido está activado. Desactivar."</string>
+    <!-- no translation found for global_actions_toggle_airplane_mode (5884330306926307456) -->
+    <skip />
+    <!-- no translation found for global_actions_airplane_mode_on_status (2719557982608919750) -->
+    <skip />
+    <!-- no translation found for global_actions_airplane_mode_off_status (5075070442854490296) -->
+    <skip />
+    <string name="safeMode">"Modo seguro"</string>
+    <!-- no translation found for android_system_label (6577375335728551336) -->
+    <skip />
+    <string name="permgrouplab_costMoney">"Servicios por los que tienes que pagar"</string>
+    <string name="permgroupdesc_costMoney">"Permite que las aplicaciones realicen acciones por las que puede que tengas que pagar."</string>
+    <string name="permgrouplab_messages">"Tus mensajes"</string>
+    <string name="permgroupdesc_messages">"Leer y escribir SMS, mensajes de correo electrónico y otros mensajes"</string>
+    <string name="permgrouplab_personalInfo">"Tu información personal"</string>
+    <string name="permgroupdesc_personalInfo">"Acceso directo al calendario y a los contactos almacenados en el teléfono"</string>
+    <string name="permgrouplab_location">"Tu ubicación"</string>
+    <string name="permgroupdesc_location">"Controlar su ubicación física"</string>
+    <string name="permgrouplab_network">"Comunicación de red"</string>
+    <string name="permgroupdesc_network">"Permite que las aplicaciones accedan a distintas funciones de red."</string>
+    <string name="permgrouplab_accounts">"Tus cuentas de Google"</string>
+    <string name="permgroupdesc_accounts">"Acceder a las cuentas de Google disponibles"</string>
+    <string name="permgrouplab_hardwareControls">"Controles de hardware"</string>
+    <string name="permgroupdesc_hardwareControls">"Acceso directo al hardware del móvil"</string>
+    <string name="permgrouplab_phoneCalls">"Llamadas de teléfono"</string>
+    <string name="permgroupdesc_phoneCalls">"Controlar, registrar y procesar llamadas telefónicas"</string>
+    <string name="permgrouplab_systemTools">"Herramientas del sistema"</string>
+    <string name="permgroupdesc_systemTools">"Acceso de nivel inferior y control del sistema"</string>
+    <string name="permgrouplab_developmentTools">"Herramientas de desarrollo"</string>
+    <string name="permgroupdesc_developmentTools">"Funciones necesarias sólo para desarrolladores de aplicaciones"</string>
+    <string name="permlab_statusBar">"inhabilitar o modificar la barra de estado"</string>
+    <string name="permdesc_statusBar">"Permite que las aplicaciones inhabiliten la barra de estado, o añadan y eliminen iconos del sistema."</string>
+    <string name="permlab_expandStatusBar">"expandir/contraer la barra de estado"</string>
+    <string name="permdesc_expandStatusBar">"Permite que la aplicación expanda y contraiga la barra de estado."</string>
+    <string name="permlab_processOutgoingCalls">"interceptar llamadas salientes"</string>
+    <string name="permdesc_processOutgoingCalls">"Permite que la aplicación procese llamadas salientes y cambie el número que se va a marcar. Las aplicaciones malintencionadas pueden controlar, redirigir o impedir las llamadas salientes."</string>
+    <string name="permlab_receiveSms">"recibir SMS"</string>
+    <string name="permdesc_receiveSms">"Permite que la aplicación reciba y procese mensajes SMS. Las aplicaciones malintencionadas pueden controlar los mensajes o eliminarlos sin mostrarlos al usuario."</string>
+    <string name="permlab_receiveMms">"recibir MMS"</string>
+    <string name="permdesc_receiveMms">"Permite que la aplicación reciba y procese mensajes MMS. Las aplicaciones malintencionadas pueden controlar los mensajes o eliminarlos sin mostrarlos al usuario."</string>
+    <string name="permlab_sendSms">"enviar mensajes SMS"</string>
+    <string name="permdesc_sendSms">"Permite que la aplicación envíe mensajes SMS. Es posible que tengas que pagar si las aplicaciones malintencionadas envían mensajes sin tu confirmación."</string>
+    <string name="permlab_readSms">"leer SMS o MMS"</string>
+    <string name="permdesc_readSms">"Permite que la aplicación lea mensajes SMS almacenados en el teléfono o en la tarjeta SIM. Las aplicaciones malintencionadas pueden leer los mensajes confidenciales."</string>
+    <string name="permlab_writeSms">"editar SMS o MMS"</string>
+    <string name="permdesc_writeSms">"Permite que la aplicación escriba en mensajes SMS almacenados en el teléfono o en la tarjeta SIM. Las aplicaciones malintencionadas pueden borrar los mensajes."</string>
+    <string name="permlab_receiveWapPush">"recibir WAP"</string>
+    <string name="permdesc_receiveWapPush">"Permite que la aplicación reciba y procese mensajes WAP. Las aplicaciones malintencionadas pueden controlar los mensajes o eliminarlos sin mostrarlos al usuario."</string>
+    <string name="permlab_getTasks">"recuperar aplicaciones en ejecución"</string>
+    <string name="permdesc_getTasks">"Permite que la aplicación recupere información sobre tareas que se están ejecutando en este momento o que se han ejecutado recientemente. Puede permitir que las aplicaciones malintencionadas vean información privada sobre otras aplicaciones."</string>
+    <string name="permlab_reorderTasks">"reorganizar aplicaciones en ejecución"</string>
+    <string name="permdesc_reorderTasks">"Permite que una aplicación mueva tareas a segundo plano y a primer plano. Las aplicaciones malintencionadas pueden aparecer en primer plano sin su control."</string>
+    <string name="permlab_setDebugApp">"habilitar depuración de aplicación"</string>
+    <string name="permdesc_setDebugApp">"Permite que una aplicación active la depuración de otra aplicación. Las aplicaciones malintencionadas pueden utilizar este permiso para desactivar otras aplicaciones."</string>
+    <string name="permlab_changeConfiguration">"cambiar la configuración de la interfaz de usuario"</string>
+    <string name="permdesc_changeConfiguration">"Permite que una aplicación cambie la configuración actual como, por ejemplo, la configuración local o el tamaño de fuente general."</string>
+    <string name="permlab_restartPackages">"reiniciar otras aplicaciones"</string>
+    <string name="permdesc_restartPackages">"Permite que una aplicación reinicie de forma forzosa otras aplicaciones."</string>
+    <string name="permlab_setProcessForeground">"impedir su interrupción"</string>
+    <string name="permdesc_setProcessForeground">"Permite que una aplicación ejecute cualquier proceso en segundo plano, de forma que no se pueda interrumpir. No debería ser necesario nunca para las aplicaciones normales."</string>
+    <string name="permlab_forceBack">"forzar el cierre de la aplicación"</string>
+    <string name="permdesc_forceBack">"Permite que una aplicación fuerce a cualquier actividad en segundo plano a cerrarse y volver a la pantalla anterior. No debería ser necesario nunca para las aplicaciones normales."</string>
+    <string name="permlab_dump">"recuperar estado interno del sistema"</string>
+    <string name="permdesc_dump">"Permite que la aplicación recupere el estado interno del sistema. Las aplicaciones malintencionadas pueden recuperar una amplia variedad de información protegida y privada que normalmente no deberían necesitar."</string>
+    <string name="permlab_addSystemService">"publicar servicios de nivel inferior"</string>
+    <string name="permdesc_addSystemService">"Permite que la aplicación publique sus propios servicios de sistema de nivel inferior. Las aplicaciones malintencionadas pueden hacerse con el control del sistema, y robar o dañar los datos contenidos en él."</string>
+    <string name="permlab_runSetActivityWatcher">"supervisar y controlar la ejecución de todas las aplicaciones"</string>
+    <string name="permdesc_runSetActivityWatcher">"Permite que una aplicación supervise y controle la ejecución de las actividades por parte del sistema. Las aplicaciones malintencionadas pueden vulnerar la seguridad del sistema. Este permiso sólo es necesario para tareas de desarrollo, nunca para el uso habitual del teléfono."</string>
+    <string name="permlab_broadcastPackageRemoved">"enviar emisión eliminada de paquete"</string>
+    <string name="permdesc_broadcastPackageRemoved">"Permite que una aplicación emita una notificación de que se ha eliminado un paquete de aplicación. Las aplicaciones malintencionadas pueden utilizar este permiso para interrumpir la ejecución de cualquier otra aplicación."</string>
+    <string name="permlab_broadcastSmsReceived">"enviar una emisión recibida mediante SMS"</string>
+    <string name="permdesc_broadcastSmsReceived">"Permite que una aplicación emita una notificación de que se ha recibido un mensaje SMS. Las aplicaciones malintencionadas  pueden utilizar este permiso para falsificar mensajes SMS entrantes."</string>
+    <string name="permlab_broadcastWapPush">"enviar emisión recibida mediante mensaje WAP PUSH"</string>
+    <string name="permdesc_broadcastWapPush">"Permite que una aplicación emita una notificación de que se ha recibido un mensaje WAP PUSH. Las aplicaciones malintencionadas pueden utilizar este permiso para falsificar la recepción de un mensaje MMS o para reemplazar de forma silenciosa el contenido de cualquier página web con variantes malintencionadas."</string>
+    <string name="permlab_setProcessLimit">"limitar el número de procesos en ejecución"</string>
+    <string name="permdesc_setProcessLimit">"Permite que una aplicación controle el número máximo de procesos que se ejecutarán. No es necesario nunca para las aplicaciones normales."</string>
+    <string name="permlab_setAlwaysFinish">"hacer que se cierren todas las aplicaciones en segundo plano"</string>
+    <string name="permdesc_setAlwaysFinish">"Permite que una aplicación controle si las actividades finalizan siempre en cuanto pasan a segundo plano. No es necesario nunca para las aplicaciones normales."</string>
+    <string name="permlab_fotaUpdate">"instalar actualizaciones del sistema de forma automática"</string>
+    <string name="permdesc_fotaUpdate">"Permite que una aplicación reciba notificaciones sobre actualizaciones pendientes del sistema e inicie su instalación. Las aplicaciones malintencionadas pueden utilizar este permiso para provocar daños en el sistema con actualizaciones no autorizadas o interferir de forma general en el proceso de actualización."</string>
+    <string name="permlab_batteryStats">"modificar estadísticas de la batería"</string>
+    <string name="permdesc_batteryStats">"Permite la modificación de estadísticas recopiladas sobre la batería. No está destinado al uso por parte de aplicaciones normales."</string>
+    <string name="permlab_internalSystemWindow">"mostrar ventanas no autorizadas"</string>
+    <string name="permdesc_internalSystemWindow">"Permite la creación de ventanas destinadas al uso por parte de la interfaz de usuario interna del sistema. No está destinado al uso por parte de aplicaciones normales."</string>
+    <string name="permlab_systemAlertWindow">"mostrar alertas de nivel del sistema"</string>
+    <string name="permdesc_systemAlertWindow">"Permite que una aplicación muestre ventanas de alerta del sistema. Las aplicaciones malintencionadas pueden controlar toda la pantalla del teléfono."</string>
+    <string name="permlab_setAnimationScale">"modificar velocidad de animación global"</string>
+    <string name="permdesc_setAnimationScale">"Permite que una aplicación cambie la velocidad de animación global (animaciones más rápidas o más lentas) en cualquier momento."</string>
+    <string name="permlab_manageAppTokens">"administrar tokens de aplicación"</string>
+    <string name="permdesc_manageAppTokens">"Permite que las aplicaciones creen y administren sus propios tokens, ignorando su orden z normal. Nunca debería ser necesario para las aplicaciones normales."</string>
+    <string name="permlab_injectEvents">"pulsar teclas y botones de control"</string>
+    <string name="permdesc_injectEvents">"Permite que la aplicación proporcione sus propios eventos de entrada (pulsación de teclas, etc.) a otras aplicaciones. Las aplicaciones malintencionadas pueden utilizar este permiso para controlar el teléfono."</string>
+    <string name="permlab_readInputState">"registrar lo que se escribe y las acciones que se realizan"</string>
+    <string name="permdesc_readInputState">"Permite que las aplicaciones observen las teclas que pulsas incluso cuando interactúas con otra aplicación (como, por ejemplo, al introducir una contraseña). No debería ser necesario nunca para las aplicaciones normales."</string>
+    <string name="permlab_bindInputMethod">"enlazar con un método de introducción de texto"</string>
+    <string name="permdesc_bindInputMethod">"Permite enlazar con la interfaz de nivel superior de un método de introducción de texto. No debe ser necesario para las aplicaciones normales."</string>
+    <string name="permlab_setOrientation">"cambiar orientación de la pantalla"</string>
+    <string name="permdesc_setOrientation">"Permite que una aplicación cambie la rotación de la pantalla en cualquier momento. No debería ser necesario nunca para las aplicaciones normales."</string>
+    <string name="permlab_signalPersistentProcesses">"enviar señales Linux a aplicaciones"</string>
+    <string name="permdesc_signalPersistentProcesses">"Permite que la aplicación solicite que la señal suministrada se envíe a todos los procesos persistentes."</string>
+    <string name="permlab_persistentActivity">"hacer que la aplicación se ejecute siempre"</string>
+    <string name="permdesc_persistentActivity">"Permite que una aplicación vuelva persistentes algunas de sus partes, de forma que el sistema no la pueda utilizar para otras aplicaciones."</string>
+    <string name="permlab_deletePackages">"eliminar aplicaciones"</string>
+    <string name="permdesc_deletePackages">"Permite que una aplicación elimine paquetes Android. Las aplicaciones malintencionadas pueden utilizar este permiso para eliminar aplicaciones importantes."</string>
+    <string name="permlab_clearAppUserData">"eliminar los datos de otras aplicaciones"</string>
+    <string name="permdesc_clearAppUserData">"Permite que una aplicación borre los datos de usuario."</string>
+    <string name="permlab_deleteCacheFiles">"eliminar las cachés de otras aplicaciones"</string>
+    <string name="permdesc_deleteCacheFiles">"Permite que una aplicación elimine archivos de caché."</string>
+    <string name="permlab_getPackageSize">"medir el espacio de almacenamiento de la aplicación"</string>
+    <string name="permdesc_getPackageSize">"Permite que la aplicación recupere su código, sus datos y los tamaños de caché."</string>
+    <string name="permlab_installPackages">"instalar aplicaciones directamente"</string>
+    <string name="permdesc_installPackages">"Permite que una aplicación instale paquetes Android nuevos o actualizados. Las aplicaciones malintencionadas pueden utilizar este permiso para añadir aplicaciones nuevas con permisos arbitrariamente potentes."</string>
+    <string name="permlab_clearAppCache">"eliminar todos los datos de caché de la aplicación"</string>
+    <string name="permdesc_clearAppCache">"Permite que una aplicación libere espacio de almacenamiento en el teléfono mediante la eliminación de archivos en el directorio de caché de la aplicación. El acceso al proceso del sistema suele estar muy restringido."</string>
+    <string name="permlab_readLogs">"leer archivos de registro del sistema"</string>
+    <string name="permdesc_readLogs">"Permite que una aplicación lea los distintos archivos de registro del sistema. Con este permiso, la aplicación puede ver información general sobre las acciones que realiza el usuario con el teléfono, pero los registros no deberían contener información personal o privada."</string>
+    <string name="permlab_diagnostic">"leer/escribir en los recursos propiedad del grupo de diagnóstico"</string>
+    <string name="permdesc_diagnostic">"Permite que una aplicación lea y escriba en cualquier recurso propiedad del grupo de diagnóstico como, por ejemplo, archivos in/dev. Este permiso podría afectar a la seguridad y estabilidad del sistema. SÓLO se debe utilizar para diagnósticos específicos de hardware realizados por el fabricante o el operador."</string>
+    <string name="permlab_changeComponentState">"habilitar o inhabilitar componentes de la aplicación"</string>
+    <string name="permdesc_changeComponentState">"Permite que una aplicación cambie si un componente de otra aplicación está habilitado o inhabilitado. Las aplicaciones malintencionadas pueden utilizar este permiso para inhabilitar funciones importantes del teléfono. El permiso se debe utilizar con precaución, ya que es posible que los componentes se vuelvan inutilizables, inconsistentes o inestables."</string>
+    <string name="permlab_setPreferredApplications">"establecer aplicaciones preferidas"</string>
+    <string name="permdesc_setPreferredApplications">"Permite que una aplicación modifique las aplicaciones preferidas del usuario. De esta forma, las aplicaciones malintencionadas pueden cambiar de forma silenciosa las aplicaciones que se están ejecutando, falsificando las aplicaciones existentes para recopilar datos privados del usuario."</string>
+    <string name="permlab_writeSettings">"modificar la configuración global del sistema"</string>
+    <string name="permdesc_writeSettings">"Permite que una aplicación modifique los datos de configuración del sistema. Las aplicaciones malintencionadas pueden dañar la configuración del sistema."</string>
+    <string name="permlab_writeSecureSettings">"modificar la configuración segura del sistema"</string>
+    <string name="permdesc_writeSecureSettings">"Permite que una aplicación modifique los datos de configuración segura del sistema. No está destinado al uso por parte de aplicaciones normales."</string>
+    <string name="permlab_writeGservices">"modificar la asignación de servicios de Google"</string>
+    <string name="permdesc_writeGservices">"Permite que una aplicación modifique la asignación de servicios de Google. No está destinado al uso por parte de aplicaciones normales."</string>
+    <string name="permlab_receiveBootCompleted">"ejecutar automáticamente al iniciar"</string>
+    <string name="permdesc_receiveBootCompleted">"Permite que una aplicación se ejecute automáticamente en cuanto se haya terminado de iniciar el sistema. Esto puede provocar que el teléfono tarde más en iniciarse y permite que la aplicación ralentice el funcionamiento global del teléfono al ejecutarse continuamente."</string>
+    <string name="permlab_broadcastSticky">"enviar emisión persistente"</string>
+    <string name="permdesc_broadcastSticky">"Permite que una aplicación envíe emisiones persistentes, que permanecen en el teléfono una vez que la emisión finaliza. Las aplicaciones malintencionadas pueden ralentizar el teléfono o volverlo inestable al hacer que emplee demasiada memoria."</string>
+    <string name="permlab_readContacts">"leer los datos de contacto"</string>
+    <string name="permdesc_readContacts">"Permite que una aplicación lea todos los datos de contacto (direcciones) almacenados en el teléfono. Las aplicaciones malintencionadas pueden utilizar este permiso para enviar tus datos a otras personas."</string>
+    <string name="permlab_writeContacts">"escribir datos de contacto"</string>
+    <string name="permdesc_writeContacts">"Permite que una aplicación modifique los datos de contacto (direcciones) almacenados en el teléfono. Las aplicaciones malintencionadas pueden utilizar este permiso para borrar o modificar tus datos de contacto."</string>
+    <string name="permlab_writeOwnerData">"escribir datos de propietario"</string>
+    <string name="permdesc_writeOwnerData">"Permite que una aplicación modifique los datos del propietario del teléfono almacenados en el teléfono. Las aplicaciones malintencionadas pueden utilizar este permiso para borrar o modificar los datos del propietario."</string>
+    <string name="permlab_readOwnerData">"leer datos del propietario"</string>
+    <string name="permdesc_readOwnerData">"Permite que una aplicación lea los datos del propietario del teléfono almacenados en el teléfono. Las aplicaciones malintencionadas pueden utilizar este permiso para leer los datos del propietario del teléfono."</string>
+    <string name="permlab_readCalendar">"leer datos de calendario"</string>
+    <string name="permdesc_readCalendar">"Permite que una aplicación lea todos los eventos de calendario almacenados en el teléfono. Las aplicaciones malintencionadas pueden utilizar este permiso para enviar tus eventos de calendario a otras personas."</string>
+    <string name="permlab_writeCalendar">"escribir datos de calendario"</string>
+    <string name="permdesc_writeCalendar">"Permite que una aplicación modifique los eventos de calendario almacenados en el teléfono. Las aplicaciones malintencionadas pueden utilizar este permiso para borrar o modificar tus datos de calendario."</string>
+    <string name="permlab_accessMockLocation">"simular fuentes de ubicación para prueba"</string>
+    <string name="permdesc_accessMockLocation">"Crear fuentes de origen simuladas para realizar pruebas. Las aplicaciones malintencionadas pueden utilizar este permiso para sobrescribir la ubicación o el estado devueltos por orígenes de ubicación reales, tales como los proveedores de red o GPS."</string>
+    <string name="permlab_accessLocationExtraCommands">"acceder a comandos de proveedor de ubicación adicional"</string>
+    <string name="permdesc_accessLocationExtraCommands">"Acceder a comandos de proveedor de ubicación adicional. Las aplicaciones malintencionadas podrían utilizar este permiso para interferir en el funcionamiento del sistema GPS o de otras fuentes de ubicación."</string>
+    <string name="permlab_accessFineLocation">"precisar la ubicación (GPS)"</string>
+    <string name="permdesc_accessFineLocation">"Permite precisar las fuentes de ubicación como, por ejemplo, el sistema de posicionamiento global, en el teléfono, en los casos en que estén disponibles. Las aplicaciones malintencionadas pueden utilizar este permiso para determinar dónde se encuentra en usuario y pueden consumir batería adicional."</string>
+    <string name="permlab_accessCoarseLocation">"ubicación común (basada en red)"</string>
+    <string name="permdesc_accessCoarseLocation">"Acceder a fuentes de ubicación comunes como, por ejemplo, la base de datos de red de un teléfono móvil, para determinar una ubicación telefónica aproximada, en los casos en que esté disponible. Las aplicaciones malintencionadas pueden utilizar este permiso para determinar dónde te encuentras aproximadamente."</string>
+    <string name="permlab_accessSurfaceFlinger">"acceder a SurfaceFlinger"</string>
+    <string name="permdesc_accessSurfaceFlinger">"Permite que la aplicación utilice funciones de SurfaceFlinger de nivel inferior."</string>
+    <string name="permlab_readFrameBuffer">"leer memoria de almacenamiento intermedio"</string>
+    <string name="permdesc_readFrameBuffer">"Permite que la aplicación que se va a utilizar lea el contenido de la memoria de almacenamiento intermedio."</string>
+    <string name="permlab_modifyAudioSettings">"cambiar la configuración de audio"</string>
+    <string name="permdesc_modifyAudioSettings">"Permite que la aplicación modifique la configuración de audio global como, por ejemplo, el volumen y la salida."</string>
+    <string name="permlab_recordAudio">"grabar sonido"</string>
+    <string name="permdesc_recordAudio">"Permite que la aplicación acceda a la ruta de grabación de audio."</string>
+    <string name="permlab_camera">"realizar fotografías"</string>
+    <string name="permdesc_camera">"Permite que la aplicación realice fotografías con la cámara. De esta forma, la aplicación puede recopilar en cualquier momento las imágenes que ve la cámara."</string>
+    <string name="permlab_brick">"inhabilitar el teléfono de forma permanente"</string>
+    <string name="permdesc_brick">"Permite que la aplicación inhabilite todas las funciones del teléfono de forma permanente. Este permiso es muy peligroso."</string>
+    <string name="permlab_reboot">"forzar reinicio del teléfono"</string>
+    <string name="permdesc_reboot">"Permite que la aplicación fuerce al teléfono a reiniciarse."</string>
+    <string name="permlab_mount_unmount_filesystems">"activar y desactivar sistemas de archivos"</string>
+    <string name="permdesc_mount_unmount_filesystems">"Permite que las aplicaciones activen y desactiven sistemas de archivos para un almacenamiento extraíble."</string>
+    <string name="permlab_mount_format_filesystems">"formatear almacenamiento externo"</string>
+    <string name="permdesc_mount_format_filesystems">"Permite a la aplicación formatear un almacenamiento extraíble."</string>
+    <string name="permlab_vibrate">"controlar vibración"</string>
+    <string name="permdesc_vibrate">"Permite que la aplicación controle la función de vibración."</string>
+    <string name="permlab_flashlight">"controlar linterna"</string>
+    <string name="permdesc_flashlight">"Permite que la aplicación controle la función de linterna."</string>
+    <string name="permlab_hardware_test">"probar hardware"</string>
+    <string name="permdesc_hardware_test">"Permite que la aplicación controle distintos periféricos con fines de prueba del hardware."</string>
+    <string name="permlab_callPhone">"llamar directamente a números de teléfono"</string>
+    <string name="permdesc_callPhone">"Permite que la aplicación llame a números de teléfono sin la intervención del usuario. Las aplicaciones malintencionadas pueden originar llamadas inesperadas en la factura telefónica. Ten en cuenta que con este permiso la aplicación no puede realizar llamadas a números de emergencia."</string>
+    <string name="permlab_callPrivileged">"llamar directamente a cualquier número de teléfono"</string>
+    <string name="permdesc_callPrivileged">"Permite que la aplicación llame a cualquier número de teléfono, incluidos los números de emergencia, sin que el usuario intervenga. Las aplicaciones malintencionadas pueden realizar llamadas innecesarias e ilícitas a los servicios de emergencias."</string>
+    <string name="permlab_locationUpdates">"controlar las notificaciones de actualización de la ubicación"</string>
+    <string name="permdesc_locationUpdates">"Permite habilitar/inhabilitar las notificaciones de actualización de la señal móvil. No está destinado al uso por parte de aplicaciones normales."</string>
+    <string name="permlab_checkinProperties">"acceder a propiedades de registro"</string>
+    <string name="permdesc_checkinProperties">"Permite el acceso de lectura/escritura a las propiedades cargadas por el servicio de registro. No está destinado al uso por parte de aplicaciones normales."</string>
+    <string name="permlab_bindGadget">"seleccionar gadgets"</string>
+    <string name="permdesc_bindGadget">"Permite a la aplicación indicar al sistema qué gadgets puede utilizar cada aplicación. Con este permiso, las aplicaciones pueden permitir a otras aplicaciones acceder a datos personales. No está destinado al uso por parte de aplicaciones normales."</string>
+    <string name="permlab_modifyPhoneState">"modificar estado del teléfono"</string>
+    <string name="permdesc_modifyPhoneState">"Permite que la aplicación controle las funciones de teléfono del dispositivo. Una aplicación con este permiso puede cambiar redes, activar y desactivar la señal móvil, etc., sin necesidad de notificar al usuario."</string>
+    <string name="permlab_readPhoneState">"leer el estado del teléfono"</string>
+    <string name="permdesc_readPhoneState">"Permite que la aplicación acceda a las funciones de teléfono del dispositivo. Una aplicación con este permiso puede determinar el número de teléfono de este teléfono, si una llamada está activa, el número al que está vinculado esa llamada, etc."</string>
+    <string name="permlab_wakeLock">"impedir que el teléfono entre en modo de suspensión"</string>
+    <string name="permdesc_wakeLock">"Permite que una aplicación impida que el teléfono entre en modo de suspensión."</string>
+    <string name="permlab_devicePower">"encender o apagar el teléfono"</string>
+    <string name="permdesc_devicePower">"Permite que la aplicación active o desactive el teléfono."</string>
+    <string name="permlab_factoryTest">"ejecutar en modo de prueba de fábrica"</string>
+    <string name="permdesc_factoryTest">"Ejecutar como prueba de fabricante de nivel inferior, permitiendo un acceso íntegro al hardware del teléfono. Sólo está disponible cuando un teléfono se está ejecutando en modo de prueba."</string>
+    <string name="permlab_setWallpaper">"establecer fondo de pantalla"</string>
+    <string name="permdesc_setWallpaper">"Permite que la aplicación establezca el fondo de pantalla del sistema."</string>
+    <string name="permlab_setWallpaperHints">"establecer el tamaño del fondo de pantalla"</string>
+    <string name="permdesc_setWallpaperHints">"Permite que la aplicación establezca el tamaño del fondo de pantalla del sistema."</string>
+    <string name="permlab_masterClear">"restablecer el sistema a los valores predeterminados de fábrica"</string>
+    <string name="permdesc_masterClear">"Permite que una aplicación restablezca por completo el sistema a su configuración de fábrica, borrando todos los datos, la configuración y las aplicaciones instaladas."</string>
+    <string name="permlab_setTimeZone">"establecer zona horaria"</string>
+    <string name="permdesc_setTimeZone">"Permite que una aplicación cambie la zona horaria del teléfono."</string>
+    <string name="permlab_getAccounts">"ver cuentas reconocidas"</string>
+    <string name="permdesc_getAccounts">"Permite que una aplicación obtenga una lista de cuentas reconocidas por el teléfono."</string>
+    <string name="permlab_accessNetworkState">"ver estado de la red"</string>
+    <string name="permdesc_accessNetworkState">"Permite que una aplicación vea el estado de todas las redes."</string>
+    <string name="permlab_createNetworkSockets">"acceso íntegro a Internet"</string>
+    <string name="permdesc_createNetworkSockets">"Permite que una aplicación cree sockets de red."</string>
+    <string name="permlab_writeApnSettings">"escribir la configuración de nombre de punto de acceso"</string>
+    <string name="permdesc_writeApnSettings">"Permite que una aplicación modifique los valores de configuración de un APN como, por ejemplo, el proxy y el puerto de cualquier APN."</string>
+    <string name="permlab_changeNetworkState">"cambiar la conectividad de red"</string>
+    <string name="permdesc_changeNetworkState">"Permite que una aplicación cambie la conectividad de red de estado."</string>
+    <string name="permlab_changeBackgroundDataSetting">"cambiar configuración de uso de datos de referencia"</string>
+    <string name="permdesc_changeBackgroundDataSetting">"Permite a una aplicación cambiar la configuración de uso de los datos de referencia."</string>
+    <string name="permlab_accessWifiState">"ver estado de la conectividad Wi-Fi"</string>
+    <string name="permdesc_accessWifiState">"Permite que una aplicación vea la información sobre el estado de la conectividad Wi-Fi."</string>
+    <string name="permlab_changeWifiState">"cambiar estado de Wi-Fi"</string>
+    <string name="permdesc_changeWifiState">"Permite que una aplicación se conecte a puntos de acceso Wi-Fi y se desconecte de ellos, y realice modificaciones en las redes Wi-Fi configuradas."</string>
+    <string name="permlab_bluetoothAdmin">"administración de Bluetooth"</string>
+    <string name="permdesc_bluetoothAdmin">"Permite que una aplicación configure el teléfono Bluetooth local, y vea dispositivos remotos y sincronice el teléfono con ellos."</string>
+    <string name="permlab_bluetooth">"crear conexiones de Bluetooth"</string>
+    <string name="permdesc_bluetooth">"Permite que una aplicación vea la configuración del teléfono Bluetooth local, y cree y acepte conexiones con los dispositivos sincronizados."</string>
+    <string name="permlab_disableKeyguard">"inhabilitar bloqueo del teclado"</string>
+    <string name="permdesc_disableKeyguard">"Permite que una aplicación inhabilite el bloqueo del teclado y cualquier protección con contraseña asociada. Un ejemplo legítimo de este permiso es la inhabilitación por parte del teléfono del bloqueo del teclado cuando recibe una llamada telefónica entrante y su posterior habilitación cuando finaliza la llamada."</string>
+    <string name="permlab_readSyncSettings">"leer la configuración de sincronización"</string>
+    <string name="permdesc_readSyncSettings">"Permite que una aplicación lea la configuración de sincronización como, por ejemplo, si la sincronización está habilitada para el menú \"Contactos\"."</string>
+    <string name="permlab_writeSyncSettings">"escribir configuración de sincronización"</string>
+    <string name="permdesc_writeSyncSettings">"Permite que una aplicación modifique la configuración de sincronización como, por ejemplo, si la sincronización está habilitada para el menú \"Contactos\"."</string>
+    <string name="permlab_readSyncStats">"leer estadísticas de sincronización"</string>
+    <string name="permdesc_readSyncStats">"Permite que una aplicación lea las estadísticas de sincronización como, por ejemplo, el historial de sincronizaciones que se han realizado."</string>
+    <string name="permlab_subscribedFeedsRead">"leer feeds a los que está suscrito el usuario"</string>
+    <string name="permdesc_subscribedFeedsRead">"Permite que una aplicación obtenga detalles sobre los feeds sincronizados en este momento."</string>
+    <string name="permlab_subscribedFeedsWrite">"escribir feeds a los que está suscrito el usuario"</string>
+    <string name="permdesc_subscribedFeedsWrite">"Permite que una aplicación modifique los feeds sincronizados actualmente. Este permiso podría provocar que una aplicación malintencionada cambie los feeds sincronizados."</string>
+    <string name="permlab_readDictionary">"leer diccionario definido por el usuario"</string>
+    <string name="permdesc_readDictionary">"Permite a una aplicación leer cualquier frase, palabra o nombre privado que el usuario haya almacenado en su diccionario."</string>
+    <string name="permlab_writeDictionary">"escribir en el diccionario definido por el usuario"</string>
+    <string name="permdesc_writeDictionary">"Permite a una aplicación escribir palabras nuevas en el diccionario del usuario."</string>
+  <string-array name="phoneTypes">
+    <item>"Casa"</item>
+    <item>"Móvil"</item>
+    <item>"Trabajo"</item>
+    <item>"Fax del trabajo"</item>
+    <item>"Fax de casa"</item>
+    <item>"Buscapersonas"</item>
+    <item>"Otro"</item>
+    <item>"Personalizar"</item>
+  </string-array>
+  <string-array name="emailAddressTypes">
+    <item>"Personal"</item>
+    <item>"Trabajo"</item>
+    <item>"Otra"</item>
+    <item>"Personalizar"</item>
+  </string-array>
+  <string-array name="postalAddressTypes">
+    <item>"Casa"</item>
+    <item>"Trabajo"</item>
+    <item>"Otra"</item>
+    <item>"Personalizar"</item>
+  </string-array>
+  <string-array name="imAddressTypes">
+    <item>"Casa"</item>
+    <item>"Trabajo"</item>
+    <item>"Otro"</item>
+    <item>"Personalizar"</item>
+  </string-array>
+  <string-array name="organizationTypes">
+    <item>"Trabajo"</item>
+    <item>"Otra"</item>
+    <item>"Personalizar"</item>
+  </string-array>
+  <string-array name="imProtocols">
+    <item>"AIM"</item>
+    <item>"Windows Live"</item>
+    <item>"Yahoo!"</item>
+    <item>"Skype"</item>
+    <item>"QQ"</item>
+    <item>"Google Talk"</item>
+    <item>"ICQ"</item>
+    <item>"Jabber"</item>
+  </string-array>
+    <string name="keyguard_password_enter_pin_code">"Introduce el código PIN"</string>
+    <string name="keyguard_password_wrong_pin_code">"El código PIN es incorrecto."</string>
+    <string name="keyguard_label_text">"Para desbloquear el teléfono, pulsa la tecla de menú y, a continuación, pulsa 0."</string>
+    <string name="emergency_call_dialog_number_for_display">"Número de emergencia"</string>
+    <string name="lockscreen_carrier_default">"(Sin cobertura)"</string>
+    <string name="lockscreen_screen_locked">"Pantalla bloqueada"</string>
+    <string name="lockscreen_instructions_when_pattern_enabled">"Pulsa la tecla de menú para desbloquear el teléfono o realizar una llamada de emergencia."</string>
+    <string name="lockscreen_instructions_when_pattern_disabled">"Pulsa la tecla de menú para desbloquear la pantalla."</string>
+    <string name="lockscreen_pattern_instructions">"Dibujar patrón de desbloqueo"</string>
+    <string name="lockscreen_emergency_call">"Llamada de emergencia"</string>
+    <string name="lockscreen_pattern_correct">"Correcto"</string>
+    <string name="lockscreen_pattern_wrong">"Inténtalo de nuevo"</string>
+    <!-- no translation found for lockscreen_plugged_in (613343852842944435) -->
+    <skip />
+    <string name="lockscreen_low_battery">"Conecta el cargador"</string>
+    <string name="lockscreen_missing_sim_message_short">"Falta la tarjeta SIM"</string>
+    <string name="lockscreen_missing_sim_message">"No se ha insertado ninguna tarjeta SIM en el teléfono."</string>
+    <string name="lockscreen_missing_sim_instructions">"Inserta una tarjeta SIM."</string>
+    <string name="lockscreen_network_locked_message">"Bloqueada para la red"</string>
+    <string name="lockscreen_sim_puk_locked_message">"La tarjeta SIM está bloqueada con el código PUK."</string>
+    <string name="lockscreen_sim_puk_locked_instructions">"Ponte en contacto con el servicio de atención al cliente."</string>
+    <string name="lockscreen_sim_locked_message">"La tarjeta SIM está bloqueada."</string>
+    <string name="lockscreen_sim_unlock_progress_dialog_message">"Desbloqueando tarjeta SIM..."</string>
+    <string name="lockscreen_too_many_failed_attempts_dialog_message">"Has realizado <xliff:g id="NUMBER_0">%d</xliff:g> intentos fallidos de creación de un patrón de desbloqueo. "\n\n"Inténtalo de nuevo dentro de <xliff:g id="NUMBER_1">%d</xliff:g> segundos."</string>
+    <string name="lockscreen_failed_attempts_almost_glogin">"Has realizado <xliff:g id="NUMBER_0">%d</xliff:g> intentos fallidos de creación del patrón de desbloqueo. Si realizas <xliff:g id="NUMBER_1">%d</xliff:g> intentos fallidos más, se te pedirá que desbloquees el teléfono con tus credenciales de acceso de Google."\n\n" Espera <xliff:g id="NUMBER_2">%d</xliff:g> segundos e inténtalo de nuevo."</string>
+    <string name="lockscreen_too_many_failed_attempts_countdown">"Espera <xliff:g id="NUMBER">%d</xliff:g> segundos y vuelve a intentarlo."</string>
+    <string name="lockscreen_forgot_pattern_button_text">"¿Has olvidado el patrón?"</string>
+    <string name="lockscreen_glogin_too_many_attempts">"Se han realizado demasiados intentos incorrectos de creación del patrón."</string>
+    <string name="lockscreen_glogin_instructions">"Para desbloquear el teléfono,"\n"accede a tu cuenta de Google"</string>
+    <string name="lockscreen_glogin_username_hint">"Nombre de usuario (correo electrónico)"</string>
+    <string name="lockscreen_glogin_password_hint">"Contraseña"</string>
+    <string name="lockscreen_glogin_submit_button">"Acceder"</string>
+    <string name="lockscreen_glogin_invalid_input">"Nombre de usuario o contraseña no válido"</string>
+    <string name="status_bar_time_format">"<xliff:g id="HOUR">h</xliff:g>:<xliff:g id="MINUTE">mm</xliff:g> <xliff:g id="AMPM">AA</xliff:g>"</string>
+    <string name="hour_minute_ampm">"<xliff:g id="HOUR">%-l</xliff:g>:<xliff:g id="MINUTE">%M</xliff:g><xliff:g id="AMPM">%P</xliff:g>"</string>
+    <string name="hour_minute_cap_ampm">"<xliff:g id="HOUR">%-l</xliff:g>:<xliff:g id="MINUTE">%M</xliff:g><xliff:g id="AMPM">%p</xliff:g>"</string>
+    <!-- no translation found for hour_ampm (4329881288269772723) -->
+    <skip />
+    <!-- no translation found for hour_cap_ampm (1829009197680861107) -->
+    <skip />
+    <string name="status_bar_clear_all_button">"Cerrar notificaciones"</string>
+    <string name="status_bar_no_notifications_title">"No tienes notificaciones"</string>
+    <string name="status_bar_ongoing_events_title">"Entrante"</string>
+    <string name="status_bar_latest_events_title">"Notificaciones"</string>
+    <!-- no translation found for battery_status_text_percent_format (7660311274698797147) -->
+    <skip />
+    <string name="battery_status_charging">"Cargando..."</string>
+    <string name="battery_low_title">"Conecta el cargador"</string>
+    <string name="battery_low_subtitle">"Se está agotando la batería:"</string>
+    <string name="battery_low_percent_format">"menos del <xliff:g id="NUMBER">%d%%</xliff:g> disponible."</string>
+    <string name="factorytest_failed">"Fallo en la prueba de fábrica"</string>
+    <string name="factorytest_not_system">"La acción FACTORY_TEST sólo es compatible con los paquetes instalados en /system/app."</string>
+    <string name="factorytest_no_action">"No se ha encontrado ningún paquete que proporcione la acción FACTORY_TEST."</string>
+    <string name="factorytest_reboot">"Reiniciar"</string>
+    <string name="js_dialog_title">"La página \"<xliff:g id="TITLE">%s</xliff:g>\" dice:"</string>
+    <string name="js_dialog_title_default">"JavaScript"</string>
+    <string name="js_dialog_before_unload">"¿Quieres salir de esta página?"\n\n"<xliff:g id="MESSAGE">%s</xliff:g>"\n\n"Selecciona \"Aceptar\" para continuar o \"Cancelar\" para permanecer en la página actual."</string>
+    <string name="save_password_label">"Confirmar"</string>
+    <string name="save_password_message">"¿Deseas que el navegador recuerde esta contraseña?"</string>
+    <string name="save_password_notnow">"Ahora no"</string>
+    <string name="save_password_remember">"Recordar"</string>
+    <string name="save_password_never">"Nunca"</string>
+    <string name="open_permission_deny">"No dispones de permiso para abrir esta página."</string>
+    <string name="text_copied">"Texto copiado al portapapeles."</string>
+    <string name="more_item_label">"Más"</string>
+    <string name="prepend_shortcut_label">"MENU+"</string>
+    <string name="menu_space_shortcut_label">"espacio"</string>
+    <string name="menu_enter_shortcut_label">"intro"</string>
+    <string name="menu_delete_shortcut_label">"suprimir"</string>
+    <string name="search_go">"Buscar"</string>
+    <string name="today">"Hoy"</string>
+    <string name="yesterday">"Ayer"</string>
+    <string name="tomorrow">"Mañana"</string>
+    <string name="oneMonthDurationPast">"Hace un mes"</string>
+    <string name="beforeOneMonthDurationPast">"Hace más de un mes"</string>
+  <plurals name="num_seconds_ago">
+    <item quantity="one">"Hace 1 segundo"</item>
+    <item quantity="other">"Hace <xliff:g id="COUNT">%d</xliff:g> segundos"</item>
+  </plurals>
+  <plurals name="num_minutes_ago">
+    <item quantity="one">"Hace 1 minuto"</item>
+    <item quantity="other">"Hace <xliff:g id="COUNT">%d</xliff:g> minutos"</item>
+  </plurals>
+  <plurals name="num_hours_ago">
+    <item quantity="one">"Hace 1 hora"</item>
+    <item quantity="other">"Hace <xliff:g id="COUNT">%d</xliff:g> horas"</item>
+  </plurals>
+  <plurals name="num_days_ago">
+    <item quantity="one">"ayer"</item>
+    <item quantity="other">"Hace <xliff:g id="COUNT">%d</xliff:g> días"</item>
+  </plurals>
+  <plurals name="in_num_seconds">
+    <item quantity="one">"dentro de 1 segundo"</item>
+    <item quantity="other">"dentro de <xliff:g id="COUNT">%d</xliff:g> segundos"</item>
+  </plurals>
+  <plurals name="in_num_minutes">
+    <item quantity="one">"dentro de 1 minuto"</item>
+    <item quantity="other">"dentro de <xliff:g id="COUNT">%d</xliff:g> minutos"</item>
+  </plurals>
+  <plurals name="in_num_hours">
+    <item quantity="one">"dentro de 1 hora"</item>
+    <item quantity="other">"dentro de <xliff:g id="COUNT">%d</xliff:g>  horas"</item>
+  </plurals>
+  <plurals name="in_num_days">
+    <item quantity="one">"mañana"</item>
+    <item quantity="other">"dentro de <xliff:g id="COUNT">%d</xliff:g> días"</item>
+  </plurals>
+  <plurals name="abbrev_num_seconds_ago">
+    <item quantity="one">"hace 1 segundo"</item>
+    <item quantity="other">"hace <xliff:g id="COUNT">%d</xliff:g> segundos"</item>
+  </plurals>
+  <plurals name="abbrev_num_minutes_ago">
+    <item quantity="one">"hace 1 minuto"</item>
+    <item quantity="other">"hace <xliff:g id="COUNT">%d</xliff:g> minutos"</item>
+  </plurals>
+  <plurals name="abbrev_num_hours_ago">
+    <item quantity="one">"hace 1 hora"</item>
+    <item quantity="other">"hace <xliff:g id="COUNT">%d</xliff:g> horas"</item>
+  </plurals>
+  <plurals name="abbrev_num_days_ago">
+    <item quantity="one">"ayer"</item>
+    <item quantity="other">"hace <xliff:g id="COUNT">%d</xliff:g> días"</item>
+  </plurals>
+  <plurals name="abbrev_in_num_seconds">
+    <item quantity="one">"dentro de 1 segundo"</item>
+    <item quantity="other">"dentro de <xliff:g id="COUNT">%d</xliff:g> segundos"</item>
+  </plurals>
+  <plurals name="abbrev_in_num_minutes">
+    <item quantity="one">"dentro de 1 minuto"</item>
+    <item quantity="other">"dentro de <xliff:g id="COUNT">%d</xliff:g> minutos"</item>
+  </plurals>
+  <plurals name="abbrev_in_num_hours">
+    <item quantity="one">"dentro de 1 hora"</item>
+    <item quantity="other">"dentro de <xliff:g id="COUNT">%d</xliff:g> horas"</item>
+  </plurals>
+  <plurals name="abbrev_in_num_days">
+    <item quantity="one">"mañana"</item>
+    <item quantity="other">"dentro de <xliff:g id="COUNT">%d</xliff:g> días"</item>
+  </plurals>
+    <string name="preposition_for_date">"el %s"</string>
+    <string name="preposition_for_time">"a las %s"</string>
+    <string name="preposition_for_year">"en %s"</string>
+    <string name="day">"día"</string>
+    <string name="days">"días"</string>
+    <string name="hour">"hora"</string>
+    <string name="hours">"horas"</string>
+    <string name="minute">"min"</string>
+    <string name="minutes">"minutos"</string>
+    <string name="second">"segundos"</string>
+    <string name="seconds">"segundos"</string>
+    <string name="week">"semana"</string>
+    <string name="weeks">"semanas"</string>
+    <string name="year">"año"</string>
+    <string name="years">"años"</string>
+    <string name="sunday">"Domingo"</string>
+    <string name="monday">"Lunes"</string>
+    <string name="tuesday">"Martes"</string>
+    <string name="wednesday">"Miércoles"</string>
+    <string name="thursday">"Jueves"</string>
+    <string name="friday">"Viernes"</string>
+    <string name="saturday">"Sábado"</string>
+    <string name="every_weekday">"Todos los días laborables (Lun-Vie)"</string>
+    <string name="daily">"Diariamente"</string>
+    <string name="weekly">"Semanalmente, el <xliff:g id="DAY">%s</xliff:g>"</string>
+    <string name="monthly">"Mensualmente"</string>
+    <string name="yearly">"Anualmente"</string>
+    <string name="VideoView_error_title">"No se puede reproducir el vídeo."</string>
+    <string name="VideoView_error_text_unknown">"Este vídeo no se puede reproducir."</string>
+    <string name="VideoView_error_button">"Aceptar"</string>
+    <string name="am">"a.m."</string>
+    <string name="pm">"p.m."</string>
+    <string name="numeric_date">"<xliff:g id="DAY">%d</xliff:g>/<xliff:g id="MONTH">%m</xliff:g>/<xliff:g id="YEAR">%Y</xliff:g>"</string>
+    <string name="wday1_date1_time1_wday2_date2_time2">"<xliff:g id="WEEKDAY1">%1$s</xliff:g>, <xliff:g id="DATE1">%2$s</xliff:g>, <xliff:g id="TIME1">%3$s</xliff:g> – <xliff:g id="WEEKDAY2">%4$s</xliff:g>, <xliff:g id="DATE2">%5$s</xliff:g>, <xliff:g id="TIME2">%6$s</xliff:g>"</string>
+    <string name="wday1_date1_wday2_date2">"<xliff:g id="WEEKDAY1">%1$s</xliff:g>, <xliff:g id="DATE1">%2$s</xliff:g> – <xliff:g id="WEEKDAY2">%4$s</xliff:g>, <xliff:g id="DATE2">%5$s</xliff:g>"</string>
+    <string name="date1_time1_date2_time2">"<xliff:g id="DATE1">%2$s</xliff:g>, <xliff:g id="TIME1">%3$s</xliff:g> – <xliff:g id="DATE2">%5$s</xliff:g>, <xliff:g id="TIME2">%6$s</xliff:g>"</string>
+    <string name="date1_date2">"<xliff:g id="DATE1">%2$s</xliff:g> – <xliff:g id="DATE2">%5$s</xliff:g>"</string>
+    <string name="time1_time2">"<xliff:g id="TIME1">%1$s</xliff:g> – <xliff:g id="TIME2">%2$s</xliff:g>"</string>
+    <string name="time_wday_date">"<xliff:g id="TIME_RANGE">%1$s</xliff:g>, <xliff:g id="WEEKDAY">%2$s</xliff:g>, <xliff:g id="DATE">%3$s</xliff:g>"</string>
+    <string name="wday_date">"<xliff:g id="WEEKDAY">%2$s</xliff:g>, <xliff:g id="DATE">%3$s</xliff:g>"</string>
+    <string name="time_date">"<xliff:g id="TIME_RANGE">%1$s</xliff:g>, <xliff:g id="DATE">%3$s</xliff:g>"</string>
+    <string name="date_time">"<xliff:g id="DATE">%1$s</xliff:g>, <xliff:g id="TIME">%2$s</xliff:g>"</string>
+    <string name="relative_time">"<xliff:g id="DATE">%1$s</xliff:g>, <xliff:g id="TIME">%2$s</xliff:g>"</string>
+    <string name="time_wday">"<xliff:g id="TIME_RANGE">%1$s</xliff:g>, <xliff:g id="WEEKDAY">%2$s</xliff:g>"</string>
+    <string name="full_date_month_first" format="date">"<xliff:g id="DAY">d</xliff:g>' de '<xliff:g id="MONTH">MMMM</xliff:g>' de '<xliff:g id="YEAR">yyyy</xliff:g>"</string>
+    <string name="full_date_day_first" format="date">"<xliff:g id="DAY">d</xliff:g>' de '<xliff:g id="MONTH">MMMM</xliff:g>' de '<xliff:g id="YEAR">yyyy</xliff:g>"</string>
+    <string name="medium_date_month_first" format="date">"<xliff:g id="DAY">d</xliff:g>' de '<xliff:g id="MONTH">MMM</xliff:g>' de '<xliff:g id="YEAR">yyyy</xliff:g>"</string>
+    <string name="medium_date_day_first" format="date">"<xliff:g id="DAY">d</xliff:g>' '<xliff:g id="MONTH">MMM</xliff:g>', '<xliff:g id="YEAR">yyyy</xliff:g>"</string>
+    <string name="twelve_hour_time_format" format="date">"<xliff:g id="HOUR">h</xliff:g>':'<xliff:g id="MINUTE">mm</xliff:g>' '<xliff:g id="AMPM">a</xliff:g>"</string>
+    <string name="twenty_four_hour_time_format" format="date">"<xliff:g id="HOUR">H</xliff:g>':'<xliff:g id="MINUTE">mm</xliff:g>"</string>
+    <string name="noon">"mediodía"</string>
+    <string name="Noon">"Mediodía"</string>
+    <string name="midnight">"medianoche"</string>
+    <string name="Midnight">"Medianoche"</string>
+    <!-- no translation found for month_day (3693060561170538204) -->
+    <skip />
+    <!-- no translation found for month (1976700695144952053) -->
+    <skip />
+    <string name="month_day_year">"<xliff:g id="DAY">%-d</xliff:g> <xliff:g id="MONTH">%B</xliff:g>, <xliff:g id="YEAR">%Y</xliff:g>"</string>
+    <!-- no translation found for month_year (2106203387378728384) -->
+    <skip />
+    <string name="time_of_day">"<xliff:g id="HOUR">%H</xliff:g>:<xliff:g id="MINUTE">%M</xliff:g>:<xliff:g id="SECOND">%S</xliff:g>"</string>
+    <string name="date_and_time">"<xliff:g id="HOUR">%H</xliff:g>:<xliff:g id="MINUTE">%M</xliff:g>:<xliff:g id="SECOND">%S</xliff:g> <xliff:g id="MONTH">%B</xliff:g> <xliff:g id="DAY">%-d</xliff:g>, <xliff:g id="YEAR">%Y</xliff:g>"</string>
+    <string name="same_year_md1_md2">"<xliff:g id="DAY1">%3$s</xliff:g> <xliff:g id="MONTH1">%2$s</xliff:g> – <xliff:g id="DAY2">%8$s</xliff:g> <xliff:g id="MONTH2">%7$s</xliff:g>"</string>
+    <string name="same_year_wday1_md1_wday2_md2">"<xliff:g id="WEEKDAY1">%1$s</xliff:g>, <xliff:g id="DAY1_0">%3$s</xliff:g> <xliff:g id="MONTH1">%2$s</xliff:g> – <xliff:g id="WEEKDAY2">%6$s</xliff:g>, <xliff:g id="DAY2_1">%8$s</xliff:g> <xliff:g id="MONTH2">%7$s</xliff:g>"</string>
+    <string name="same_year_mdy1_mdy2">"<xliff:g id="DAY1">%3$s</xliff:g> <xliff:g id="MONTH1">%2$s</xliff:g> – <xliff:g id="DAY2">%8$s</xliff:g> <xliff:g id="MONTH2">%7$s</xliff:g>, <xliff:g id="YEAR">%9$s</xliff:g>"</string>
+    <string name="same_year_wday1_mdy1_wday2_mdy2">"<xliff:g id="WEEKDAY1">%1$s</xliff:g>, <xliff:g id="MONTH1">%2$s</xliff:g> <xliff:g id="DAY1_0">%3$s</xliff:g> – <xliff:g id="WEEKDAY2">%6$s</xliff:g>, <xliff:g id="MONTH2">%7$s</xliff:g> <xliff:g id="DAY2_1">%8$s</xliff:g>, <xliff:g id="YEAR">%9$s</xliff:g>"</string>
+    <string name="same_year_md1_time1_md2_time2">"<xliff:g id="DAY1">%3$s</xliff:g> de <xliff:g id="MONTH1">%2$s</xliff:g>, <xliff:g id="TIME1">%5$s</xliff:g> – <xliff:g id="DAY2">%8$s</xliff:g> de <xliff:g id="MONTH2">%7$s</xliff:g>, <xliff:g id="TIME2">%10$s</xliff:g>"</string>
+    <string name="same_year_wday1_md1_time1_wday2_md2_time2">"<xliff:g id="WEEKDAY1">%1$s</xliff:g>, <xliff:g id="DAY1_0">%3$s</xliff:g> <xliff:g id="MONTH1">%2$s</xliff:g>, <xliff:g id="TIME1">%5$s</xliff:g> – <xliff:g id="WEEKDAY2">%6$s</xliff:g>, <xliff:g id="DAY2_1">%8$s</xliff:g> <xliff:g id="MONTH2">%7$s</xliff:g>, <xliff:g id="TIME2">%10$s</xliff:g>"</string>
+    <string name="same_year_mdy1_time1_mdy2_time2">"<xliff:g id="DAY1">%3$s</xliff:g> de <xliff:g id="MONTH1">%2$s</xliff:g> de <xliff:g id="YEAR1">%4$s</xliff:g>, <xliff:g id="TIME1">%5$s</xliff:g> –  <xliff:g id="DAY2">%8$s</xliff:g> de <xliff:g id="MONTH2">%7$s</xliff:g> de <xliff:g id="YEAR2">%9$s</xliff:g>, <xliff:g id="TIME2">%10$s</xliff:g>"</string>
+    <string name="same_year_wday1_mdy1_time1_wday2_mdy2_time2">"<xliff:g id="WEEKDAY1">%1$s</xliff:g>, <xliff:g id="DAY1_0">%3$s</xliff:g> <xliff:g id="MONTH1">%2$s</xliff:g>, <xliff:g id="YEAR1">%4$s</xliff:g>, <xliff:g id="TIME1">%5$s</xliff:g> – <xliff:g id="WEEKDAY2">%6$s</xliff:g>, <xliff:g id="DAY2_1">%8$s</xliff:g> <xliff:g id="MONTH2">%7$s</xliff:g>, <xliff:g id="YEAR2">%9$s</xliff:g>, <xliff:g id="TIME2">%10$s</xliff:g>"</string>
+    <string name="numeric_md1_md2">"<xliff:g id="DAY1">%3$s</xliff:g>/<xliff:g id="MONTH1">%2$s</xliff:g> – <xliff:g id="DAY2">%8$s</xliff:g>/<xliff:g id="MONTH2">%7$s</xliff:g>"</string>
+    <string name="numeric_wday1_md1_wday2_md2">"<xliff:g id="WEEKDAY1">%1$s</xliff:g>, <xliff:g id="MONTH1">%2$s</xliff:g>/<xliff:g id="DAY1_0">%3$s</xliff:g> – <xliff:g id="WEEKDAY2">%6$s</xliff:g>, <xliff:g id="MONTH2">%7$s</xliff:g>/<xliff:g id="DAY2_1">%8$s</xliff:g>"</string>
+    <string name="numeric_mdy1_mdy2">"<xliff:g id="DAY1">%3$s</xliff:g>/<xliff:g id="MONTH1">%2$s</xliff:g>/<xliff:g id="YEAR1">%4$s</xliff:g> – <xliff:g id="DAY2">%8$s</xliff:g>/<xliff:g id="MONTH2">%7$s</xliff:g>/<xliff:g id="YEAR2">%9$s</xliff:g>"</string>
+    <string name="numeric_wday1_mdy1_wday2_mdy2">"<xliff:g id="WEEKDAY1">%1$s</xliff:g>, <xliff:g id="DAY1_0">%3$s</xliff:g>/<xliff:g id="MONTH1">%2$s</xliff:g>/<xliff:g id="YEAR1">%4$s</xliff:g> – <xliff:g id="WEEKDAY2">%6$s</xliff:g>, <xliff:g id="DAY2_1">%8$s</xliff:g>/<xliff:g id="MONTH2">%7$s</xliff:g>/<xliff:g id="YEAR2">%9$s</xliff:g>"</string>
+    <string name="numeric_md1_time1_md2_time2">"<xliff:g id="DAY1">%3$s</xliff:g>/<xliff:g id="MONTH1">%2$s</xliff:g>, <xliff:g id="TIME1">%5$s</xliff:g> – <xliff:g id="DAY2">%8$s</xliff:g>/<xliff:g id="MONTH2">%7$s</xliff:g>, <xliff:g id="TIME2">%10$s</xliff:g>"</string>
+    <string name="numeric_wday1_md1_time1_wday2_md2_time2">"<xliff:g id="WEEKDAY1">%1$s</xliff:g>, <xliff:g id="DAY1_0">%3$s</xliff:g>/<xliff:g id="MONTH1">%2$s</xliff:g>, <xliff:g id="TIME1">%5$s</xliff:g> – <xliff:g id="WEEKDAY2">%6$s</xliff:g>, <xliff:g id="DAY2_1">%8$s</xliff:g>/<xliff:g id="MONTH2">%7$s</xliff:g>, <xliff:g id="TIME2">%10$s</xliff:g>"</string>
+    <string name="numeric_mdy1_time1_mdy2_time2">"<xliff:g id="DAY1">%3$s</xliff:g>/<xliff:g id="MONTH1">%2$s</xliff:g>/<xliff:g id="YEAR1">%4$s</xliff:g>, <xliff:g id="TIME1">%5$s</xliff:g> – <xliff:g id="DAY2">%8$s</xliff:g>/<xliff:g id="MONTH2">%7$s</xliff:g>/<xliff:g id="YEAR2">%9$s</xliff:g>, <xliff:g id="TIME2">%10$s</xliff:g>"</string>
+    <string name="numeric_wday1_mdy1_time1_wday2_mdy2_time2">"<xliff:g id="WEEKDAY1">%1$s</xliff:g>, <xliff:g id="DAY1_0">%3$s</xliff:g>/<xliff:g id="MONTH1">%2$s</xliff:g>/<xliff:g id="YEAR1">%4$s</xliff:g>, <xliff:g id="TIME1">%5$s</xliff:g> – <xliff:g id="WEEKDAY2">%6$s</xliff:g>, <xliff:g id="DAY2_1">%8$s</xliff:g>/<xliff:g id="MONTH2">%7$s</xliff:g>/<xliff:g id="YEAR2">%9$s</xliff:g>, <xliff:g id="TIME2">%10$s</xliff:g>"</string>
+    <string name="same_month_md1_md2">"<xliff:g id="DAY1">%3$s</xliff:g> – <xliff:g id="DAY2">%8$s</xliff:g>  <xliff:g id="MONTH1">%2$s</xliff:g>"</string>
+    <string name="same_month_wday1_md1_wday2_md2">"<xliff:g id="WEEKDAY1">%1$s</xliff:g>, <xliff:g id="MONTH1">%2$s</xliff:g> <xliff:g id="DAY1_0">%3$s</xliff:g> – <xliff:g id="WEEKDAY2">%6$s</xliff:g>, <xliff:g id="MONTH2">%7$s</xliff:g> <xliff:g id="DAY2_1">%8$s</xliff:g>"</string>
+    <string name="same_month_mdy1_mdy2">"<xliff:g id="DAY1">%3$s</xliff:g> – <xliff:g id="DAY2">%8$s</xliff:g> <xliff:g id="MONTH1">%2$s</xliff:g>, <xliff:g id="YEAR2">%9$s</xliff:g>"</string>
+    <string name="same_month_wday1_mdy1_wday2_mdy2">"<xliff:g id="WEEKDAY1">%1$s</xliff:g>, <xliff:g id="DAY1_0">%3$s</xliff:g> de <xliff:g id="MONTH1">%2$s</xliff:g> de <xliff:g id="YEAR1">%4$s</xliff:g> – <xliff:g id="WEEKDAY2">%6$s</xliff:g>, <xliff:g id="DAY2_1">%8$s</xliff:g> de <xliff:g id="MONTH2">%7$s</xliff:g> de <xliff:g id="YEAR2">%9$s</xliff:g>"</string>
+    <string name="same_month_md1_time1_md2_time2">"<xliff:g id="DAY1">%3$s</xliff:g> <xliff:g id="MONTH1">%2$s</xliff:g>, <xliff:g id="TIME1">%5$s</xliff:g> – <xliff:g id="DAY2">%8$s</xliff:g> <xliff:g id="MONTH2">%7$s</xliff:g>, <xliff:g id="TIME2">%10$s</xliff:g>"</string>
+    <string name="same_month_wday1_md1_time1_wday2_md2_time2">"<xliff:g id="WEEKDAY1">%1$s</xliff:g>, <xliff:g id="DAY1_0">%3$s</xliff:g> <xliff:g id="MONTH1">%2$s</xliff:g>, <xliff:g id="TIME1">%5$s</xliff:g> – <xliff:g id="WEEKDAY2">%6$s</xliff:g>, <xliff:g id="DAY2_1">%8$s</xliff:g> <xliff:g id="MONTH2">%7$s</xliff:g>, <xliff:g id="TIME2">%10$s</xliff:g>"</string>
+    <string name="same_month_mdy1_time1_mdy2_time2">"<xliff:g id="MONTH1">%2$s</xliff:g> <xliff:g id="DAY1">%3$s</xliff:g>, <xliff:g id="YEAR1">%4$s</xliff:g>, <xliff:g id="TIME1">%5$s</xliff:g> – <xliff:g id="MONTH2">%7$s</xliff:g> <xliff:g id="DAY2">%8$s</xliff:g>, <xliff:g id="YEAR2">%9$s</xliff:g>, <xliff:g id="TIME2">%10$s</xliff:g>"</string>
+    <string name="same_month_wday1_mdy1_time1_wday2_mdy2_time2">"<xliff:g id="WEEKDAY1">%1$s</xliff:g>, <xliff:g id="MONTH1">%2$s</xliff:g> <xliff:g id="DAY1_0">%3$s</xliff:g>, <xliff:g id="YEAR1">%4$s</xliff:g>, <xliff:g id="TIME1">%5$s</xliff:g> – <xliff:g id="WEEKDAY2">%6$s</xliff:g>, <xliff:g id="MONTH2">%7$s</xliff:g> <xliff:g id="DAY2_1">%8$s</xliff:g>, <xliff:g id="YEAR2">%9$s</xliff:g>, <xliff:g id="TIME2">%10$s</xliff:g>"</string>
+    <string name="abbrev_month_day_year">"<xliff:g id="DAY">%-d</xliff:g> de <xliff:g id="MONTH">%b</xliff:g> de <xliff:g id="YEAR">%Y</xliff:g>"</string>
+    <!-- no translation found for abbrev_month_year (5966980891147982768) -->
+    <skip />
+    <!-- no translation found for abbrev_month_day (3156047263406783231) -->
+    <skip />
+    <!-- no translation found for abbrev_month (7304935052615731208) -->
+    <skip />
+    <string name="day_of_week_long_sunday">"Domingo"</string>
+    <string name="day_of_week_long_monday">"Lunes"</string>
+    <string name="day_of_week_long_tuesday">"Martes"</string>
+    <string name="day_of_week_long_wednesday">"Miércoles"</string>
+    <string name="day_of_week_long_thursday">"Jueves"</string>
+    <string name="day_of_week_long_friday">"Viernes"</string>
+    <string name="day_of_week_long_saturday">"Sábado"</string>
+    <string name="day_of_week_medium_sunday">"Dom"</string>
+    <string name="day_of_week_medium_monday">"Lun"</string>
+    <string name="day_of_week_medium_tuesday">"Mar"</string>
+    <string name="day_of_week_medium_wednesday">"Mié"</string>
+    <string name="day_of_week_medium_thursday">"Jue"</string>
+    <string name="day_of_week_medium_friday">"Vie"</string>
+    <string name="day_of_week_medium_saturday">"Sáb"</string>
+    <string name="day_of_week_short_sunday">"Do"</string>
+    <string name="day_of_week_short_monday">"Lu"</string>
+    <string name="day_of_week_short_tuesday">"Ma"</string>
+    <string name="day_of_week_short_wednesday">"Mi"</string>
+    <string name="day_of_week_short_thursday">"Ju"</string>
+    <string name="day_of_week_short_friday">"Vi"</string>
+    <string name="day_of_week_short_saturday">"Sá"</string>
+    <string name="day_of_week_shorter_sunday">"Do"</string>
+    <string name="day_of_week_shorter_monday">"L"</string>
+    <string name="day_of_week_shorter_tuesday">"Ma"</string>
+    <string name="day_of_week_shorter_wednesday">"Mi"</string>
+    <string name="day_of_week_shorter_thursday">"Ju"</string>
+    <string name="day_of_week_shorter_friday">"V"</string>
+    <string name="day_of_week_shorter_saturday">"S"</string>
+    <string name="day_of_week_shortest_sunday">"D"</string>
+    <string name="day_of_week_shortest_monday">"Mz"</string>
+    <string name="day_of_week_shortest_tuesday">"M"</string>
+    <string name="day_of_week_shortest_wednesday">"Mi"</string>
+    <string name="day_of_week_shortest_thursday">"M"</string>
+    <string name="day_of_week_shortest_friday">"V"</string>
+    <string name="day_of_week_shortest_saturday">"D"</string>
+    <string name="month_long_january">"Enero"</string>
+    <string name="month_long_february">"Febrero"</string>
+    <string name="month_long_march">"Marzo"</string>
+    <string name="month_long_april">"Abril"</string>
+    <string name="month_long_may">"Mayo"</string>
+    <string name="month_long_june">"Junio"</string>
+    <string name="month_long_july">"Julio"</string>
+    <string name="month_long_august">"Agosto"</string>
+    <string name="month_long_september">"Septiembre"</string>
+    <string name="month_long_october">"Octubre"</string>
+    <string name="month_long_november">"Noviembre"</string>
+    <string name="month_long_december">"Diciembre"</string>
+    <string name="month_medium_january">"Ene"</string>
+    <string name="month_medium_february">"Feb"</string>
+    <string name="month_medium_march">"Mar"</string>
+    <string name="month_medium_april">"Abr"</string>
+    <string name="month_medium_may">"May"</string>
+    <string name="month_medium_june">"Jun"</string>
+    <string name="month_medium_july">"Jul"</string>
+    <string name="month_medium_august">"Ago"</string>
+    <string name="month_medium_september">"Sep"</string>
+    <string name="month_medium_october">"Oct"</string>
+    <string name="month_medium_november">"Nov"</string>
+    <string name="month_medium_december">"Dic"</string>
+    <string name="month_shortest_january">"E"</string>
+    <string name="month_shortest_february">"V"</string>
+    <string name="month_shortest_march">"Mz"</string>
+    <string name="month_shortest_april">"A"</string>
+    <string name="month_shortest_may">"My"</string>
+    <string name="month_shortest_june">"J"</string>
+    <string name="month_shortest_july">"E"</string>
+    <string name="month_shortest_august">"Ag"</string>
+    <string name="month_shortest_september">"S"</string>
+    <string name="month_shortest_october">"O"</string>
+    <string name="month_shortest_november">"N"</string>
+    <string name="month_shortest_december">"D"</string>
+    <string name="elapsed_time_short_format_mm_ss">"<xliff:g id="MINUTES">%1$02d</xliff:g>:<xliff:g id="SECONDS">%2$02d</xliff:g>"</string>
+    <string name="elapsed_time_short_format_h_mm_ss">"<xliff:g id="HOURS">%1$d</xliff:g>:<xliff:g id="MINUTES">%2$02d</xliff:g>:<xliff:g id="SECONDS">%3$02d</xliff:g>"</string>
+    <string name="selectAll">"Seleccionar todo"</string>
+    <string name="selectText">"Seleccionar texto"</string>
+    <string name="stopSelectingText">"Detener selección de texto"</string>
+    <string name="cut">"Cortar"</string>
+    <string name="cutAll">"Cortar todo"</string>
+    <string name="copy">"Copiar"</string>
+    <string name="copyAll">"Copiar todo"</string>
+    <string name="paste">"Pegar"</string>
+    <string name="copyUrl">"Copiar URL"</string>
+    <string name="inputMethod">"Método de introducción de texto"</string>
+    <string name="addToDictionary">"Añadir \"%s\" al diccionario"</string>
+    <string name="editTextMenuTitle">"Editar texto"</string>
+    <string name="low_internal_storage_view_title">"Poco espacio"</string>
+    <string name="low_internal_storage_view_text">"Se está agotando el espacio de almacenamiento del teléfono."</string>
+    <string name="ok">"Aceptar"</string>
+    <string name="cancel">"Cancelar"</string>
+    <string name="yes">"Aceptar"</string>
+    <string name="no">"Cancelar"</string>
+    <string name="dialog_alert_title">"Atención"</string>
+    <string name="capital_on">"Activado"</string>
+    <string name="capital_off">"Desconectado"</string>
+    <string name="whichApplication">"Completar acción utilizando"</string>
+    <string name="alwaysUse">"Utilizar de forma predeterminada para esta acción"</string>
+    <string name="clearDefaultHintMsg">"Borrar valores predeterminados en la página de configuración de la pantalla de inicio del teléfono &gt; Aplicaciones &gt; Administrar aplicaciones\"."</string>
+    <string name="chooseActivity">"Seleccionar una acción"</string>
+    <string name="noApplications">"Ninguna aplicación puede realizar esta acción."</string>
+    <string name="aerr_title">"Lo sentimos."</string>
+    <string name="aerr_application">"La aplicación <xliff:g id="APPLICATION">%1$s</xliff:g> (proceso <xliff:g id="PROCESS">%2$s</xliff:g>) se ha interrumpido inesperadamente. Inténtalo de nuevo."</string>
+    <string name="aerr_process">"El proceso <xliff:g id="PROCESS">%1$s</xliff:g> se ha interrumpido inesperadamente. Inténtalo de nuevo."</string>
+    <string name="anr_title">"Lo sentimos."</string>
+    <string name="anr_activity_application">"La actividad <xliff:g id="ACTIVITY">%1$s</xliff:g> (<xliff:g id="APPLICATION">%2$s</xliff:g> en aplicación) no está respondiendo."</string>
+    <string name="anr_activity_process">"La actividad <xliff:g id="ACTIVITY">%1$s</xliff:g> (<xliff:g id="PROCESS">%2$s</xliff:g> en curso) no está respondiendo."</string>
+    <string name="anr_application_process">"La aplicación <xliff:g id="APPLICATION">%1$s</xliff:g> (<xliff:g id="PROCESS">%2$s</xliff:g> en curso) no está respondiendo."</string>
+    <string name="anr_process">"El proceso <xliff:g id="PROCESS">%1$s</xliff:g> no está respondiendo."</string>
+    <string name="force_close">"Forzar cierre"</string>
+    <string name="wait">"Esperar"</string>
+    <string name="debug">"Depurar"</string>
+    <string name="sendText">"Seleccionar la opción para compartir"</string>
+    <string name="volume_ringtone">"Volumen del timbre"</string>
+    <string name="volume_music">"Volumen multimedia"</string>
+    <string name="volume_music_hint_playing_through_bluetooth">"Reproduciendo a través de Bluetooth"</string>
+    <string name="volume_call">"Volumen de la llamada"</string>
+    <string name="volume_bluetooth_call">"Volumen de la llamada de Bluetooth"</string>
+    <string name="volume_alarm">"Volumen de alarma"</string>
+    <string name="volume_notification">"Volumen de notificaciones"</string>
+    <string name="volume_unknown">"Volumen"</string>
+    <string name="ringtone_default">"Tono predeterminado"</string>
+    <string name="ringtone_default_with_actual">"Tono predeterminado (<xliff:g id="ACTUAL_RINGTONE">%1$s</xliff:g>)"</string>
+    <string name="ringtone_silent">"Silencio"</string>
+    <string name="ringtone_picker_title">"Tonos"</string>
+    <string name="ringtone_unknown">"Tono desconocido"</string>
+  <plurals name="wifi_available">
+    <item quantity="one">"Red Wi-Fi disponible"</item>
+    <item quantity="other">"Redes Wi-Fi disponibles"</item>
+  </plurals>
+  <plurals name="wifi_available_detailed">
+    <item quantity="one">"Red Wi-Fi abierta disponible"</item>
+    <item quantity="other">"Redes Wi-Fi abiertas disponibles"</item>
+  </plurals>
+    <string name="select_character">"Insertar carácter"</string>
+    <string name="sms_control_default_app_name">"Aplicación desconocida"</string>
+    <string name="sms_control_title">"Enviando mensajes SMS..."</string>
+    <string name="sms_control_message">"Se ha enviado un número elevado de mensajes SMS. Selecciona \"Aceptar\" para continuar o \"Cancelar\" para interrumpir el envío."</string>
+    <string name="sms_control_yes">"Aceptar"</string>
+    <string name="sms_control_no">"Cancelar"</string>
+    <string name="date_time_set">"Establecer"</string>
+    <string name="default_permission_group">"Predeterminado"</string>
+    <string name="no_permissions">"No es necesario ningún permiso"</string>
+    <string name="perms_hide"><b>"Ocultar"</b></string>
+    <string name="perms_show_all"><b>"Mostrar todos"</b></string>
+    <string name="googlewebcontenthelper_loading">"Cargando..."</string>
+    <string name="usb_storage_title">"Conectado por USB"</string>
+    <string name="usb_storage_message">"Has conectado el teléfono al equipo mediante USB. Selecciona \"Activar\" si deseas copiar archivos entre el equipo y la tarjeta SD del teléfono."</string>
+    <string name="usb_storage_button_mount">"Activar"</string>
+    <string name="usb_storage_button_unmount">"No activar"</string>
+    <string name="usb_storage_error_message">"Se ha producido un problema al intentar utilizar la tarjeta SD para el almacenamiento USB."</string>
+    <string name="usb_storage_notification_title">"Conectado por USB"</string>
+    <string name="usb_storage_notification_message">"Seleccionar para copiar archivos al/desde el equipo"</string>
+    <string name="usb_storage_stop_notification_title">"Desactivar almacenar en USB"</string>
+    <string name="usb_storage_stop_notification_message">"Seleccionar para desactivar USB."</string>
+    <string name="usb_storage_stop_title">"Desactivar almacenamiento USB"</string>
+    <string name="usb_storage_stop_message">"Antes de desactivar el almacenamiento USB, asegúrate de haber desactivado el host USB. Selecciona \"Desactivar\" para desactivar el almacenamiento USB."</string>
+    <string name="usb_storage_stop_button_mount">"Desactivar"</string>
+    <string name="usb_storage_stop_button_unmount">"Cancelar"</string>
+    <string name="usb_storage_stop_error_message">"Se ha producido un problema al desactivar el almacenamiento USB. Asegúrate de que has desactivado el host USB e inténtalo de nuevo."</string>
+    <string name="extmedia_format_title">"Formatear tarjeta SD"</string>
+    <string name="extmedia_format_message">"¿Estás seguro de que quieres formatear la tarjeta SD? Se perderán todos los datos de la tarjeta."</string>
+    <string name="extmedia_format_button_format">"Formato"</string>
+    <string name="select_input_method">"Seleccionar método de introducción de texto"</string>
+    <string name="fast_scroll_alphabet">" ABCDEFGHIJKLMNOPQRSTUVWXYZ"</string>
+    <string name="fast_scroll_numeric_alphabet">" 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"</string>
+    <string name="candidates_style"><u>"candidatos"</u></string>
+    <string name="ext_media_checking_notification_title">"Preparando tarjeta SD"</string>
+    <string name="ext_media_checking_notification_message">"Comprobando errores"</string>
+    <string name="ext_media_nofs_notification_title">"Tarjeta SD vacía"</string>
+    <string name="ext_media_nofs_notification_message">"La tarjeta SD está vacía o utiliza un sistema de archivos incompatible."</string>
+    <string name="ext_media_unmountable_notification_title">"Tarjeta SD dañada"</string>
+    <string name="ext_media_unmountable_notification_message">"La tarjeta SD está dañada. Es posible que sea necesario volver a formatearla."</string>
+    <string name="ext_media_badremoval_notification_title">"La tarjeta SD se ha extraído inesperadamente."</string>
+    <string name="ext_media_badremoval_notification_message">"Desactiva la tarjeta SD antes de extraerla para evitar la pérdida de datos."</string>
+    <string name="ext_media_safe_unmount_notification_title">"Es seguro extraer la tarjeta SD."</string>
+    <string name="ext_media_safe_unmount_notification_message">"Ya puedes extraer la tarjeta SD."</string>
+    <string name="ext_media_nomedia_notification_title">"Tarjeta SD extraída"</string>
+    <string name="ext_media_nomedia_notification_message">"La tarjeta SD se ha extraído. Inserta una nueva tarjeta SD para aumentar la capacidad de almacenamiento de tu dispositivo."</string>
+    <string name="activity_list_empty">"No se ha encontrado ninguna actividad coincidente."</string>
+    <string name="permlab_pkgUsageStats">"actualizar estadísticas de uso de componentes"</string>
+    <string name="permdesc_pkgUsageStats">"Permite la modificación de estadísticas recopiladas sobre el uso de componentes. No está destinado al uso por parte de aplicaciones normales."</string>
+    <!-- no translation found for tutorial_double_tap_to_zoom_message_short (1311810005957319690) -->
+    <skip />
+    <!-- no translation found for gadget_host_error_inflating (2613287218853846830) -->
+    <skip />
+    <!-- no translation found for ime_action_go (8320845651737369027) -->
+    <skip />
+    <!-- no translation found for ime_action_search (658110271822807811) -->
+    <skip />
+    <!-- no translation found for ime_action_send (2316166556349314424) -->
+    <skip />
+    <!-- no translation found for ime_action_next (3138843904009813834) -->
+    <skip />
+    <!-- no translation found for ime_action_default (2840921885558045721) -->
+    <skip />
+</resources>
diff --git a/core/res/res/values-fr-rFR/arrays.xml b/core/res/res/values-fr-rFR/arrays.xml
new file mode 100644
index 0000000..504ab0f
--- /dev/null
+++ b/core/res/res/values-fr-rFR/arrays.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* //device/apps/common/assets/res/any/colors.xml
+**
+** Copyright 2006, Google Inc.
+**
+** 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.
+*/
+-->
+<resources>
+
+    <!-- Do not translate. -->
+    <integer-array name="maps_starting_lat_lng">
+        <item>48850258</item>
+        <item>2351074</item>
+    </integer-array>
+    <!-- Do not translate. -->
+    <integer-array name="maps_starting_zoom">
+        <item>6</item>
+    </integer-array>
+
+</resources>
diff --git a/core/res/res/values-fr/strings.xml b/core/res/res/values-fr/strings.xml
new file mode 100644
index 0000000..8bd064d
--- /dev/null
+++ b/core/res/res/values-fr/strings.xml
@@ -0,0 +1,811 @@
+<?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.
+-->
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="byteShort">"O"</string>
+    <string name="kilobyteShort">"Ko"</string>
+    <string name="megabyteShort">"Mo"</string>
+    <string name="gigabyteShort">"Go"</string>
+    <string name="terabyteShort">"To"</string>
+    <string name="petabyteShort">"Po"</string>
+    <string name="untitled">"&lt;sans titre&gt;"</string>
+    <string name="ellipsis">"…"</string>
+    <string name="emptyPhoneNumber">"(Aucun numéro de téléphone)"</string>
+    <string name="unknownName">"(Inconnu)"</string>
+    <string name="defaultVoiceMailAlphaTag">"Messagerie vocale"</string>
+    <string name="defaultMsisdnAlphaTag">"MSISDN1"</string>
+    <string name="mmiError">"Problème de connexion ou code MMI non valide."</string>
+    <string name="serviceEnabled">"Le service a été activé."</string>
+    <string name="serviceEnabledFor">"Ce service a été activé pour :"</string>
+    <string name="serviceDisabled">"Ce service a été désactivé."</string>
+    <string name="serviceRegistered">"Enregistrement réussi."</string>
+    <string name="serviceErased">"Effacement réussi."</string>
+    <string name="passwordIncorrect">"Le mot de passe est incorrect."</string>
+    <string name="mmiComplete">"MMI terminé."</string>
+    <string name="badPin">"L\'ancien code PIN saisi est incorrect."</string>
+    <string name="badPuk">"Le code PUK saisi est incorrect."</string>
+    <string name="mismatchPin">"Les codes PIN saisis ne correspondent pas."</string>
+    <string name="invalidPin">"Le code PIN doit compter de 4 à 8 chiffres."</string>
+    <string name="needPuk">"Votre carte SIM est verrouillée par code PUK. Saisissez le code PUK pour la déverrouiller."</string>
+    <string name="needPuk2">"Saisissez le code PUK2 pour débloquer la carte SIM."</string>
+    <string name="ClipMmi">"Identifiant d\'appelant entrant"</string>
+    <string name="ClirMmi">"Identifiant d\'appelant sortant"</string>
+    <string name="CfMmi">"Transfert d\'appel"</string>
+    <string name="CwMmi">"Appel en attente"</string>
+    <string name="BaMmi">"Interdiction d\'appel"</string>
+    <string name="PwdMmi">"Modification du mot de passe"</string>
+    <string name="PinMmi">"Modification du code PIN"</string>
+    <string name="CLIRDefaultOnNextCallOn">"Par défaut, les identifiants d\'appelant sont restreints. Appel suivant : restreint"</string>
+    <string name="CLIRDefaultOnNextCallOff">"Par défaut, les identifiants d\'appelant sont restreints. Appel suivant : non restreint"</string>
+    <string name="CLIRDefaultOffNextCallOn">"Par défaut, les identifiants d\'appelant ne sont pas restreints. Appel suivant : restreint"</string>
+    <string name="CLIRDefaultOffNextCallOff">"Par défaut, les identifiants d\'appelant ne sont pas restreints. Appel suivant : non restreint"</string>
+    <string name="serviceNotProvisioned">"Ce service n\'est pas pris en charge."</string>
+    <string name="CLIRPermanent">"Le paramètre Identifiant d\'appelant ne peut pas être modifié."</string>
+    <string name="serviceClassVoice">"Voix"</string>
+    <string name="serviceClassData">"Données"</string>
+    <string name="serviceClassFAX">"Télécopie"</string>
+    <string name="serviceClassSMS">"SMS"</string>
+    <string name="serviceClassDataAsync">"Asynchrones"</string>
+    <string name="serviceClassDataSync">"Synchrones"</string>
+    <string name="serviceClassPacket">"Paquet"</string>
+    <string name="serviceClassPAD">"PAD"</string>
+    <string name="cfTemplateNotForwarded">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g> : non transféré"</string>
+    <string name="cfTemplateForwarded">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g> : <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
+    <string name="cfTemplateForwardedTime">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g> : <xliff:g id="DIALING_NUMBER">{1}</xliff:g> au bout de <xliff:g id="TIME_DELAY">{2}</xliff:g> secondes"</string>
+    <string name="cfTemplateRegistered">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g> : non transféré"</string>
+    <string name="cfTemplateRegisteredTime">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g> : non transféré"</string>
+    <string name="httpErrorOk">"OK"</string>
+    <string name="httpError">"La page Web contient une erreur."</string>
+    <string name="httpErrorLookup">"URL introuvable."</string>
+    <string name="httpErrorUnsupportedAuthScheme">"Le modèle d\'authentification du site n\'est pas pris en charge."</string>
+    <string name="httpErrorAuth">"Échec de l\'authentification."</string>
+    <string name="httpErrorProxyAuth">"Échec de l\'authentification par un serveur proxy."</string>
+    <string name="httpErrorConnect">"Échec de la connexion au serveur."</string>
+    <string name="httpErrorIO">"Échec de la communication avec le serveur. Veuillez réessayer ultérieurement."</string>
+    <string name="httpErrorTimeout">"Délai de connexion au serveur dépassé."</string>
+    <string name="httpErrorRedirectLoop">"Cette page contient trop de redirections de serveurs."</string>
+    <string name="httpErrorUnsupportedScheme">"Ce protocole n\'est pas pris en charge."</string>
+    <string name="httpErrorFailedSslHandshake">"Aucune connexion sécurisée n\'a pu être établie."</string>
+    <string name="httpErrorBadUrl">"Impossible d\'ouvrir cette page. L\'URL n\'est pas correcte."</string>
+    <string name="httpErrorFile">"Impossible d\'accéder au fichier."</string>
+    <string name="httpErrorFileNotFound">"Le fichier demandé est introuvable."</string>
+    <string name="httpErrorTooManyRequests">"Trop de requêtes sont en cours de traitement. Veuillez réessayer ultérieurement."</string>
+    <string name="contentServiceSync">"Synchroniser"</string>
+    <string name="contentServiceSyncNotificationTitle">"Synchronisation"</string>
+    <string name="contentServiceTooManyDeletesNotificationDesc">"Trop de contenus supprimés (<xliff:g id="CONTENT_TYPE">%s</xliff:g>)."</string>
+    <string name="low_memory">"La mémoire du téléphone est pleine ! Supprimez des fichiers pour libérer de l\'espace."</string>
+    <string name="me">"Moi"</string>
+    <string name="power_dialog">"Options du téléphone"</string>
+    <string name="silent_mode">"Mode silencieux"</string>
+    <string name="turn_on_radio">"Activer le mode sans fil"</string>
+    <string name="turn_off_radio">"Désactiver le mode sans fil"</string>
+    <string name="screen_lock">"Verrouillage de l\'écran"</string>
+    <string name="power_off">"Éteindre"</string>
+    <string name="shutdown_progress">"Arrêt en cours..."</string>
+    <string name="shutdown_confirm">"Votre téléphone va s\'éteindre."</string>
+    <string name="no_recent_tasks">"Aucune application récente"</string>
+    <string name="global_actions">"Options du téléphone"</string>
+    <string name="global_action_lock">"Verrouillage de l\'écran"</string>
+    <string name="global_action_power_off">"Éteindre"</string>
+    <string name="global_action_toggle_silent_mode">"Mode silencieux"</string>
+    <string name="global_action_silent_mode_on_status">"Le son est désactivé."</string>
+    <string name="global_action_silent_mode_off_status">"Le son est activé."</string>
+    <!-- no translation found for global_actions_toggle_airplane_mode (5884330306926307456) -->
+    <skip />
+    <!-- no translation found for global_actions_airplane_mode_on_status (2719557982608919750) -->
+    <skip />
+    <!-- no translation found for global_actions_airplane_mode_off_status (5075070442854490296) -->
+    <skip />
+    <string name="safeMode">"Mode sécurisé"</string>
+    <!-- no translation found for android_system_label (6577375335728551336) -->
+    <skip />
+    <string name="permgrouplab_costMoney">"Services payants"</string>
+    <string name="permgroupdesc_costMoney">"Permet aux applications d\'effectuer des opérations payantes."</string>
+    <string name="permgrouplab_messages">"Vos messages"</string>
+    <string name="permgroupdesc_messages">"Permet de lire et rédiger vos SMS, e-mails et autres messages."</string>
+    <string name="permgrouplab_personalInfo">"Vos informations personnelles"</string>
+    <string name="permgroupdesc_personalInfo">"Accédez directement aux contacts et à l\'agenda enregistrés sur votre téléphone."</string>
+    <string name="permgrouplab_location">"Votre position"</string>
+    <string name="permgroupdesc_location">"Suivre votre position géographique"</string>
+    <string name="permgrouplab_network">"Communications réseau"</string>
+    <string name="permgroupdesc_network">"Permet à des applications d\'accéder à différentes fonctionnalités du réseau."</string>
+    <string name="permgrouplab_accounts">"Vos comptes Google"</string>
+    <string name="permgroupdesc_accounts">"Accédez aux comptes Google disponibles."</string>
+    <string name="permgrouplab_hardwareControls">"Commandes du matériel"</string>
+    <string name="permgroupdesc_hardwareControls">"Permet d\'accéder directement au matériel de l\'appareil."</string>
+    <string name="permgrouplab_phoneCalls">"Appels"</string>
+    <string name="permgroupdesc_phoneCalls">"Suivre, enregistrer et traiter les appels téléphoniques"</string>
+    <string name="permgrouplab_systemTools">"Outils système"</string>
+    <string name="permgroupdesc_systemTools">"Accès et contrôle de faible niveau du système."</string>
+    <string name="permgrouplab_developmentTools">"Outils de développement"</string>
+    <string name="permgroupdesc_developmentTools">"Ces fonctionnalités sont réservées aux développeurs d\'applications."</string>
+    <string name="permlab_statusBar">"Désactivation ou modification de la barre d\'état"</string>
+    <string name="permdesc_statusBar">"Permet à une application de désactiver la barre d\'état ou d\'ajouter/supprimer des icônes système."</string>
+    <string name="permlab_expandStatusBar">"Agrandir/réduire la barre d\'état"</string>
+    <string name="permdesc_expandStatusBar">"Permet à l\'application de réduire ou d\'agrandir la barre d\'état."</string>
+    <string name="permlab_processOutgoingCalls">"Interception d\'appels sortants"</string>
+    <string name="permdesc_processOutgoingCalls">"Permet à l\'application de traiter des appels en cours et de modifier le numéro à composer. Des applications malveillantes peuvent suivre, rediriger ou empêcher des appels sortants."</string>
+    <string name="permlab_receiveSms">"Réception de SMS"</string>
+    <string name="permdesc_receiveSms">"Permet à une application de recevoir et traiter des messages SMS. Des applications malveillantes peuvent surveiller ou effacer vos messages sans que vous les ayez lus."</string>
+    <string name="permlab_receiveMms">"Réception des MMS"</string>
+    <string name="permdesc_receiveMms">"Permet à une application de recevoir et traiter des messages MMS. Des applications malveillantes peuvent utiliser cette fonctionnalité pour surveiller ou effacer vos messages sans que vous les ayez lus."</string>
+    <string name="permlab_sendSms">"Envoi de messages SMS"</string>
+    <string name="permdesc_sendSms">"Permet aux applications d\'envoyer des messages SMS. Des applications malveillantes peuvent entraîner des frais en envoyant des messages sans vous en demander la confirmation."</string>
+    <string name="permlab_readSms">"Lecture des SMS ou MMS"</string>
+    <string name="permdesc_readSms">"Permet à l\'application de lire les SMS enregistrés dans la mémoire de votre téléphone ou sur votre carte SIM. Des applications malveillantes peuvent lire vos messages confidentiels."</string>
+    <string name="permlab_writeSms">"Modification de SMS ou de MMS"</string>
+    <string name="permdesc_writeSms">"Permet à une application de modifier des messages SMS enregistrés sur votre téléphone ou sur votre carte SIM. Des applications malveillantes peuvent ainsi supprimer vos messages."</string>
+    <string name="permlab_receiveWapPush">"Réception de WAP"</string>
+    <string name="permdesc_receiveWapPush">"Permet à l\'application de recevoir et de traiter des messages WAP. Des applications malveillantes peuvent ainsi surveiller vos messages ou les effacer sans que vous en ayez pris connaissance."</string>
+    <string name="permlab_getTasks">"Récupération des applications en cours d\'exécution"</string>
+    <string name="permdesc_getTasks">"Permet à l\'application de récupérer des informations sur des tâches en cours d\'exécution ou récemment utilisées. Des applications malveillantes peuvent ainsi obtenir des informations d\'ordre privé concernant d\'autres applications."</string>
+    <string name="permlab_reorderTasks">"Réorganisation des applications en cours d\'exécution"</string>
+    <string name="permdesc_reorderTasks">"Permet à une application de placer des tâches au premier plan ou en arrière-plan. Des applications malveillantes peuvent se placer inopinément au premier plan sans votre autorisation."</string>
+    <string name="permlab_setDebugApp">"Activation du débogage de l\'application"</string>
+    <string name="permdesc_setDebugApp">"Permet à une application d\'activer le mode de débogage d\'une autre application. Des applications malveillantes peuvent utiliser cette fonctionnalité pour interrompre d\'autres applications de façon inopinée."</string>
+    <string name="permlab_changeConfiguration">"Modification des paramètres de l\'IU"</string>
+    <string name="permdesc_changeConfiguration">"Permet à une application de modifier la configuration actuelle (par ex. : la taille de la police générale ou des paramètres régionaux)."</string>
+    <string name="permlab_restartPackages">"Démarrage d\'autres applications"</string>
+    <string name="permdesc_restartPackages">"Permet à une application de forcer le lancement d\'autres applications."</string>
+    <string name="permlab_setProcessForeground">"Non-possibilité d\'interruption"</string>
+    <string name="permdesc_setProcessForeground">"Permet à une application d\'exécuter tout processus au premier plan afin qu\'il ne puisse pas être interrompu. Les applications normales ne devraient jamais nécessiter cette fonctionnalité."</string>
+    <string name="permlab_forceBack">"Fermeture forcée de l\'application"</string>
+    <string name="permdesc_forceBack">"Permet à une application de forcer une autre application exécutée au premier plan à se fermer et à passer en arrière-plan. Les applications normales ne devraient jamais avoir recours à cette fonctionnalité."</string>
+    <string name="permlab_dump">"Vérification de l\'état interne du système"</string>
+    <string name="permdesc_dump">"Permet à l\'application de récupérer l\'état interne du système. Des applications malveillantes peuvent obtenir de nombreuses informations personnelles et sécurisées auxquelles elles ne devraient pas avoir accès."</string>
+    <string name="permlab_addSystemService">"Éditer des services à faible niveau"</string>
+    <string name="permdesc_addSystemService">"Permet à l\'application de publier ses propres services de système de niveau inférieur. Des applications malveillantes peuvent prendre le contrôle du système et subtiliser ou endommager ses données."</string>
+    <string name="permlab_runSetActivityWatcher">"Contrôle du lancement des applications"</string>
+    <string name="permdesc_runSetActivityWatcher">"Permet à une application de suivre et de contrôler la façon dont le système lance des activités. Des applications malveillantes peuvent entièrement déstabiliser le système. Cette autorisation est uniquement nécessaire au développement et non pour l\'utilisation normale du téléphone."</string>
+    <string name="permlab_broadcastPackageRemoved">"Envoyer une diffusion sans paquet"</string>
+    <string name="permdesc_broadcastPackageRemoved">"Permet à une application de diffuser une notification lorsqu\'un paquet d\'application a été supprimé. Des applications malveillantes peuvent utiliser cette fonctionnalité pour interrompre d\'autres applications en cours d\'exécution."</string>
+    <string name="permlab_broadcastSmsReceived">"Envoyer une diffusion reçue par SMS"</string>
+    <string name="permdesc_broadcastSmsReceived">"Permet à une application de diffuser une notification lors de la réception d\'un message SMS. Des applications malveillantes peuvent utiliser cette fonctionnalité pour falsifier des messages SMS entrants."</string>
+    <string name="permlab_broadcastWapPush">"Envoi de diffusion de réception de WAP PUSH"</string>
+    <string name="permdesc_broadcastWapPush">"Permet à une application d\'envoyer une notification lors de la réception d\'un message WAP PUSH. Des applications malveillantes peuvent utiliser cette fonctionnalité pour créer de faux accusés de réception de MMS ou remplacer le contenu de toute page Web par des données malveillantes."</string>
+    <string name="permlab_setProcessLimit">"Nombre maximal de processus en cours d\'exécution"</string>
+    <string name="permdesc_setProcessLimit">"Permet à une application de contrôler le nombre de processus maximal exécutés en même temps. Les applications normales n\'ont jamais recours à cette fonctionnalité."</string>
+    <string name="permlab_setAlwaysFinish">"Fermeture de toutes les applications en tâche de fond"</string>
+    <string name="permdesc_setAlwaysFinish">"Permet à une application de vérifier si des activités sont systématiquement interrompues lorsqu\'elles sont placées en tâche de fond. Cette fonctionnalité n\'est jamais utilisée par les applications normales."</string>
+    <string name="permlab_fotaUpdate">"Installation des mises à jour du système"</string>
+    <string name="permdesc_fotaUpdate">"Permet à une application de recevoir des notifications sur des mises à jour système en cours et de lancer leur installation. Des applications malveillantes peuvent utiliser cette fonctionnalité pour endommager le système avec des mises à jour non autorisées ou interférer avec le processus de mise à jour."</string>
+    <string name="permlab_batteryStats">"Modification des statistiques de la batterie"</string>
+    <string name="permdesc_batteryStats">"Autoriser la modification des statistiques de la batterie. Les applications normales n\'utilisent pas cette fonctionnalité."</string>
+    <string name="permlab_internalSystemWindow">"Affichage de fenêtres non autorisées"</string>
+    <string name="permdesc_internalSystemWindow">"Permet de créer des fenêtres conçues pour l\'interface utilisateur du système interne. Les applications normales n\'utilisent pas cette fonctionnalité."</string>
+    <string name="permlab_systemAlertWindow">"Affichage d\'alertes système"</string>
+    <string name="permdesc_systemAlertWindow">"Permet à une application d\'afficher des fenêtres d\'alerte système. Des applications malveillantes peuvent masquer la totalité de l\'écran du téléphone."</string>
+    <string name="permlab_setAnimationScale">"Réglage de la vitesse des animations"</string>
+    <string name="permdesc_setAnimationScale">"Permet à une application de modifier à tout moment la vitesse générale des animations (pour les rendre plus lentes ou plus rapides)."</string>
+    <string name="permlab_manageAppTokens">"Gestion des repères des applications"</string>
+    <string name="permdesc_manageAppTokens">"Permet à des applications de créer et gérer leurs propres jetons en ignorant leur ordre de plan normal. Les applications normales ne devraient jamais avoir recours à cette fonctionnalité."</string>
+    <string name="permlab_injectEvents">"Utilisation des touches ou contrôle des commandes"</string>
+    <string name="permdesc_injectEvents">"Permet à une application de fournir ses propres commandes (touches enfoncées, etc.) à d\'autres applications. Des applications malveillantes peuvent utiliser cette fonctionnalité pour prendre le contrôle de votre téléphone."</string>
+    <string name="permlab_readInputState">"Enregistrer le texte saisi et les actions effectuées"</string>
+    <string name="permdesc_readInputState">"Permet à des applications d\'identifier les touches sur lesquelles vous appuyez même lorsque vous utilisez une autre application (lors de la saisie d\'un mot de passe, par exemple). Les applications normales ne devraient jamais avoir recours à cette fonctionnalité."</string>
+    <string name="permlab_bindInputMethod">"Association à un mode de saisie"</string>
+    <string name="permdesc_bindInputMethod">"Permet au support de se connecter à l\'interface de plus haut niveau d\'un mode de saisie. Les applications normales ne devraient jamais avoir recours à cette fonctionnalité."</string>
+    <string name="permlab_setOrientation">"Changement d\'orientation de l\'écran"</string>
+    <string name="permdesc_setOrientation">"Permet à une application de modifier la rotation de l\'écran à tout moment. Les applications normales ne devraient jamais avoir recours à cette fonctionnalité."</string>
+    <string name="permlab_signalPersistentProcesses">"Envoi de signaux Linux aux applications"</string>
+    <string name="permdesc_signalPersistentProcesses">"Permet à une application de demander que le signal fourni soit envoyé à tous les processus persistants."</string>
+    <string name="permlab_persistentActivity">"Exécution de l\'application en continu"</string>
+    <string name="permdesc_persistentActivity">"Permet à une application de perdurer en partie afin que le système ne puisse pas l\'utiliser pour d\'autres applications."</string>
+    <string name="permlab_deletePackages">"Supprimer des applications"</string>
+    <string name="permdesc_deletePackages">"Permet à une application de supprimer des paquets de données Android. Des applications malveillantes peuvent utiliser cette fonctionnalité pour supprimer des applications importantes."</string>
+    <string name="permlab_clearAppUserData">"Suppression des données d\'autres applications"</string>
+    <string name="permdesc_clearAppUserData">"Permet à une application d\'effacer les données de l\'utilisateur."</string>
+    <string name="permlab_deleteCacheFiles">"Suppression du cache d\'autres applications"</string>
+    <string name="permdesc_deleteCacheFiles">"Permet à une application de supprimer des fichiers du cache."</string>
+    <string name="permlab_getPackageSize">"Évaluation de l\'espace de stockage de l\'application"</string>
+    <string name="permdesc_getPackageSize">"Permet à une application de récupérer la taille de son code, de ses données et de son cache."</string>
+    <string name="permlab_installPackages">"Installation directe d\'applications"</string>
+    <string name="permdesc_installPackages">"Permet à une application d\'installer des nouveaux paquets de données ou des mises à jour Android. Des applications malveillantes peuvent utiliser cette fonctionnalité pour ajouter de nouvelles applications disposant d\'autorisations anormalement élevées."</string>
+    <string name="permlab_clearAppCache">"Suppression des données du cache de toutes les applications"</string>
+    <string name="permdesc_clearAppCache">"Permet à une application de libérer de l\'espace dans la mémoire du téléphone en supprimant des fichiers du répertoire du cache des applications. Cet accès est en général limité aux processus système."</string>
+    <string name="permlab_readLogs">"Lecture des fichiers journaux du système"</string>
+    <string name="permdesc_readLogs">"Permet à une application de lire les différents fichiers journaux du système afin d\'obtenir des informations générales sur la façon dont vous utilisez votre téléphone,  sans pour autant récupérer des informations d\'ordre personnel ou privé."</string>
+    <string name="permlab_diagnostic">"Lecture/écriture dans les ressources appartenant aux diagnostics"</string>
+    <string name="permdesc_diagnostic">"Permet à une application de lire et d\'éditer toute ressource appartenant au groupe de diagnostics (par exemple, les fichiers in/dev). Ceci peut affecter la stabilité et la sécurité du système. Cette fonctionnalité est UNIQUEMENT réservée aux diagnostics matériels effectués par le fabricant ou l\'opérateur."</string>
+    <string name="permlab_changeComponentState">"Activer ou désactiver des éléments de l\'application"</string>
+    <string name="permdesc_changeComponentState">"Permet à une application d\'envoyer une notification lors de la réception d\'un message WAP PUSH. Des applications malveillantes peuvent utiliser cette fonctionnalité pour créer de faux accusés de réception de MMS ou remplacer le contenu de toute page Web par des données malveillantes."</string>
+    <string name="permlab_setPreferredApplications">"Définition des applications préférées"</string>
+    <string name="permdesc_setPreferredApplications">"Permet à une application de modifier vos applications préférées. Des applications malveillantes peuvent utiliser cette fonctionnalité pour modifier discrètement les applications en cours d\'exécution, en imitant vos applications existantes afin de récupérer des données personnelles vous concernant."</string>
+    <string name="permlab_writeSettings">"Modification des paramètres généraux du système"</string>
+    <string name="permdesc_writeSettings">"Permet à une application de modifier les données des paramètres système. Des applications malveillantes peuvent utiliser cette fonctionnalité pour endommager la configuration de votre système."</string>
+    <string name="permlab_writeSecureSettings">"Modifier les paramètres de sécurité du système"</string>
+    <string name="permdesc_writeSecureSettings">"Permet à une application de modifier les données des paramètres de sécurité du système. Les applications normales n\'utilisent pas cette fonctionnalité."</string>
+    <string name="permlab_writeGservices">"Modification de la carte des services Google"</string>
+    <string name="permdesc_writeGservices">"Permet à une application de modifier la carte des services Google. Les applications normales n\'utilisent pas cette fonctionnalité."</string>
+    <string name="permlab_receiveBootCompleted">"Lancement automatique au démarrage"</string>
+    <string name="permdesc_receiveBootCompleted">"Permet à une application de se lancer dès la fin du démarrage du système. Cela peut rallonger le temps de démarrage requis par le téléphone. L\'application étant alors constamment en cours d\'exécution, le fonctionnement général du téléphone risque d\'être ralenti."</string>
+    <string name="permlab_broadcastSticky">"Envoi d\'une diffusion persistante"</string>
+    <string name="permdesc_broadcastSticky">"Permet à une application d\'envoyer des diffusions \"persistantes\", qui perdurent après la fin de la diffusion. Des applications malveillantes peuvent ainsi ralentir le téléphone ou le rendre instable en l\'obligeant à utiliser trop de mémoire."</string>
+    <string name="permlab_readContacts">"Accès aux données des contacts"</string>
+    <string name="permdesc_readContacts">"Permet à une application de lire toutes les données des contacts (adresses) enregistrées sur votre téléphone. Des applications malveillantes peuvent utiliser cette fonctionnalité pour envoyer vos données à d\'autres personnes."</string>
+    <string name="permlab_writeContacts">"Édition des données d\'un contact"</string>
+    <string name="permdesc_writeContacts">"Permet à une application de modifier toutes les données de contact (adresses) enregistrées sur le téléphone. Des applications malveillantes peuvent utiliser cette fonctionnalité pour effacer ou modifier vos données de contact."</string>
+    <string name="permlab_writeOwnerData">"Édition les données du propriétaire"</string>
+    <string name="permdesc_writeOwnerData">"Permet à une application de modifier les données du propriétaire du téléphone enregistrées sur votre appareil. Des applications malveillantes peuvent utiliser cette fonctionnalité pour effacer ou modifier ces données."</string>
+    <string name="permlab_readOwnerData">"Lecture des données du propriétaire"</string>
+    <string name="permdesc_readOwnerData">"Permet à une application de lire les données du propriétaire du téléphone enregistrées sur votre appareil. Des applications malveillantes peuvent utiliser cette fonctionnalité pour lire ces données."</string>
+    <string name="permlab_readCalendar">"Lecture des données de l\'agenda"</string>
+    <string name="permdesc_readCalendar">"Permet à une application de lire tous les événements de l\'agenda enregistrés sur votre téléphone. Des applications malveillantes peuvent utiliser cette fonctionnalité pour envoyer les événements de votre agenda à d\'autres personnes."</string>
+    <string name="permlab_writeCalendar">"Écriture des données de l\'agenda"</string>
+    <string name="permdesc_writeCalendar">"Permet à une application de modifier les événements de l\'agenda enregistrés sur votre téléphone. Des applications malveillantes peuvent utiliser cette fonctionnalité pour effacer ou modifier les données de votre agenda."</string>
+    <string name="permlab_accessMockLocation">"Création de sources géographiques fictives à des fins de test"</string>
+    <string name="permdesc_accessMockLocation">"Permet de créer des sources de position géographique fictives à des fins de test. Des applications malveillantes peuvent utiliser cette fonctionnalité pour remplacer la position géographique et/ou l\'état fournis par des sources réelles comme le GPS ou les fournisseurs d\'accès."</string>
+    <string name="permlab_accessLocationExtraCommands">"Accès aux commandes de fournisseur de position géographique supplémentaires"</string>
+    <string name="permdesc_accessLocationExtraCommands">"Permet d\'accéder à des commandes de fournisseur de position géographique supplémentaires. Des applications malveillantes peuvent utiliser cette fonctionnalité pour interférer avec l\'utilisation du GPS ou d\'autres sources de positionnement géographique."</string>
+    <string name="permlab_accessFineLocation">"Localisation OK (GPS)"</string>
+    <string name="permdesc_accessFineLocation">"Permet d\'accéder à des sources de positionnement géographique précises comme le Global Positioning System (GPS) sur le téléphone, lorsque ces services sont disponibles. Des applications malveillantes peuvent utiliser cette fonctionnalité pour déterminer l\'endroit où vous vous trouvez et augmenter la consommation de la batterie de votre téléphone."</string>
+    <string name="permlab_accessCoarseLocation">"Position géo. approximative (selon le réseau)"</string>
+    <string name="permdesc_accessCoarseLocation">"Accès à des sources de positionnement approximatif (par ex. des bases de données de réseaux mobiles) pour déterminer la position géographique du téléphone, lorsque cette option est disponible. Des applications malveillantes peuvent utiliser cette fonctionnalité pour déterminer approximativement l\'endroit où vous vous trouvez."</string>
+    <string name="permlab_accessSurfaceFlinger">"Accès à SurfaceFlinger"</string>
+    <string name="permdesc_accessSurfaceFlinger">"Permet à certaines applications d\'utiliser les fonctionnalités SurfaceFlinger de bas niveau."</string>
+    <string name="permlab_readFrameBuffer">"Lecture de la mémoire tampon graphique"</string>
+    <string name="permdesc_readFrameBuffer">"Permet aux applications de lire/utiliser le contenu de la mémoire tampon graphique."</string>
+    <string name="permlab_modifyAudioSettings">"Modification de vos paramètres audio"</string>
+    <string name="permdesc_modifyAudioSettings">"Permet à l\'application de modifier les paramètres audio généraux (p. ex. le volume et le routage)."</string>
+    <string name="permlab_recordAudio">"Enregistrement de fichier audio"</string>
+    <string name="permdesc_recordAudio">"Permet à l\'application d\'accéder au chemin de l\'enregistrement audio."</string>
+    <string name="permlab_camera">"Prise de photos"</string>
+    <string name="permdesc_camera">"Permet à l\'application de prendre des clichés avec l\'appareil photo. Cette fonctionnalité permet à l\'application de récupérer à tout moment les images perçues par l\'appareil."</string>
+    <string name="permlab_brick">"Désactivation définitive du téléphone"</string>
+    <string name="permdesc_brick">"Permet à l\'application de désactiver définitivement le téléphone. Cette fonctionnalité est très dangereuse."</string>
+    <string name="permlab_reboot">"Redémarrage forcé du téléphone"</string>
+    <string name="permdesc_reboot">"Permet à l\'application de forcer le redémarrage du téléphone."</string>
+    <string name="permlab_mount_unmount_filesystems">"Monter et démonter des systèmes de fichiers"</string>
+    <string name="permdesc_mount_unmount_filesystems">"Permet à l\'application de monter et démonter des systèmes de fichiers pour des périphériques de stockage amovibles."</string>
+    <string name="permlab_mount_format_filesystems">"Formatage du périphérique de stockage externe"</string>
+    <string name="permdesc_mount_format_filesystems">"Permet à l\'application de formater le périphérique de stockage amovible."</string>
+    <string name="permlab_vibrate">"Contrôle du vibreur"</string>
+    <string name="permdesc_vibrate">"Permet à l\'application de contrôler le vibreur."</string>
+    <string name="permlab_flashlight">"Contrôle de la lampe de poche"</string>
+    <string name="permdesc_flashlight">"Permet à l\'application de contrôler la lampe de poche."</string>
+    <string name="permlab_hardware_test">"Tests du matériel"</string>
+    <string name="permdesc_hardware_test">"Permet à l\'application de contrôler différents périphériques à des fins de test matériel."</string>
+    <string name="permlab_callPhone">"Appel direct des numéros de téléphone"</string>
+    <string name="permdesc_callPhone">"Permet à l\'application d\'appeler des numéros sans votre intervention. Des applications malveillantes peuvent ainsi passer des appels à votre insu qui s\'ajoutent à votre facture téléphonique. Cette fonctionnalité ne permet pas à l\'application d\'appeler des numéros d\'urgence."</string>
+    <string name="permlab_callPrivileged">"Appel direct de tout numéro de téléphone"</string>
+    <string name="permdesc_callPrivileged">"Permet à une application d\'appeler tout numéro de téléphone (y compris les numéros d\'urgence) sans votre intervention. Des applications malveillantes peuvent passer des appels non nécessaires ou illégitimes à des services d\'urgence."</string>
+    <string name="permlab_locationUpdates">"Contrôle des notifications de mise à jour de position géo."</string>
+    <string name="permdesc_locationUpdates">"Permet l\'activation/la désactivation des notifications de mises à jour de la position géographique provenant de la radio. Les applications normales n\'utilisent pas cette fonctionnalité."</string>
+    <string name="permlab_checkinProperties">"Accès aux propriétés d\'enregistrement"</string>
+    <string name="permdesc_checkinProperties">"Permet un accès en lecture/écriture à des propriétés envoyées par le service d\'inscription. Les applications normales n\'utilisent pas cette fonctionnalité."</string>
+    <string name="permlab_bindGadget">"choisir les gadgets"</string>
+    <string name="permdesc_bindGadget">"Permet à l\'application de signaler au système quels gadgets peuvent être utilisés pour quelle application. Cette autorisation permet aux applications de fournir l\'accès à des données personnelles à d\'autres applications. Cette option n\'est pas utilisée par les applications standard."</string>
+    <string name="permlab_modifyPhoneState">"Modification de l\'état du téléphone"</string>
+    <string name="permdesc_modifyPhoneState">"Permet à une application de contrôler les fonctionnalités téléphoniques de l\'appareil. Une application bénéficiant de cette autorisation peut changer de réseau, éteindre et allumer la radio du téléphone, etc., sans vous en avertir."</string>
+    <string name="permlab_readPhoneState">"Lecture de l\'état du téléphone"</string>
+    <string name="permdesc_readPhoneState">"Permet à l\'application d\'accéder aux fonctionnalités d\'appel du téléphone. L\'application peut alors déterminer le numéro de téléphone de l\'appareil, savoir si un appel est en cours, identifier le numéro appelé, etc."</string>
+    <string name="permlab_wakeLock">"Arrêt du mode veille sur le téléphone"</string>
+    <string name="permdesc_wakeLock">"Permet à une application d\'empêcher votre téléphone de passer en mode veille."</string>
+    <string name="permlab_devicePower">"Éteindre ou allumer le téléphone"</string>
+    <string name="permdesc_devicePower">"Permet à l\'application d\'éteindre et d\'allumer le téléphone."</string>
+    <string name="permlab_factoryTest">"Exécution en mode Test d\'usine"</string>
+    <string name="permdesc_factoryTest">"Permet d\'exécuter en tant que test fabricant de faible niveau en autorisant l\'accès au matériel du téléphone. Cette fonctionnalité est uniquement disponible lorsque le téléphone est en mode de test fabricant."</string>
+    <string name="permlab_setWallpaper">"Configuration du fond d\'écran"</string>
+    <string name="permdesc_setWallpaper">"Permet à une application de définir l\'arrière-plan du système."</string>
+    <string name="permlab_setWallpaperHints">"Sélection de la la taille du fond d\'écran"</string>
+    <string name="permdesc_setWallpaperHints">"Permet à une application de définir la taille d\'arrière-plan du système."</string>
+    <string name="permlab_masterClear">"Réinitialisation du système à ses paramètres d\'usine"</string>
+    <string name="permdesc_masterClear">"Permet à une application de réinitialiser entièrement le système afin de rétablir ses valeurs d\'usine et d\'effacer toutes les données, configurations et applications installées."</string>
+    <string name="permlab_setTimeZone">"Sélection du fuseau horaire"</string>
+    <string name="permdesc_setTimeZone">"Permet à l\'application de modifier le fuseau horaire du téléphone."</string>
+    <string name="permlab_getAccounts">"Identification des comptes connus"</string>
+    <string name="permdesc_getAccounts">"Permet à une application d\'obtenir la liste des comptes connus du téléphone."</string>
+    <string name="permlab_accessNetworkState">"Affichage de l\'état du réseau"</string>
+    <string name="permdesc_accessNetworkState">"Permet à une application d\'afficher l\'état de tous les réseaux."</string>
+    <string name="permlab_createNetworkSockets">"Accès Internet complet"</string>
+    <string name="permdesc_createNetworkSockets">"Permet à une application de créer des connecteurs réseau."</string>
+    <string name="permlab_writeApnSettings">"Écriture des paramètres \"Nom des points d\'accès\""</string>
+    <string name="permdesc_writeApnSettings">"Permet à une application de modifier les paramètres APN (Nom des points d\'accès), comme le proxy ou le port de tout APN."</string>
+    <string name="permlab_changeNetworkState">"Modification de la connectivité du réseau"</string>
+    <string name="permdesc_changeNetworkState">"Permet à une application de modifier la connectivité du réseau."</string>
+    <string name="permlab_changeBackgroundDataSetting">"modifier le paramètre d\'utilisation des données en arrière-plan"</string>
+    <string name="permdesc_changeBackgroundDataSetting">"Permet à une application de modifier le paramètre d\'utilisation des données en arrière-plan."</string>
+    <string name="permlab_accessWifiState">"Affichage de l\'état du Wi-Fi"</string>
+    <string name="permdesc_accessWifiState">"Permet à une application d\'afficher des informations concernant l\'état du Wi-Fi."</string>
+    <string name="permlab_changeWifiState">"Modifier l\'état du Wi-Fi"</string>
+    <string name="permdesc_changeWifiState">"Permet à une application de se connecter à des points d\'accès Wi-Fi, de s\'en déconnecter et de modifier des réseaux Wi-Fi configurés."</string>
+    <string name="permlab_bluetoothAdmin">"Gestion Bluetooth"</string>
+    <string name="permdesc_bluetoothAdmin">"Permet à une application de configurer le téléphone Bluetooth local, d\'identifier des périphériques distants et de les associer au téléphone."</string>
+    <string name="permlab_bluetooth">"Création de connexions Bluetooth"</string>
+    <string name="permdesc_bluetooth">"Permet à une application d\'obtenir la configuration du téléphone Bluetooth local et de créer et accepter des connexions à des appareils associés."</string>
+    <string name="permlab_disableKeyguard">"Désactivation du verrouillage des touches"</string>
+    <string name="permdesc_disableKeyguard">"Permet à une application de désactiver le verrouillage des touches et toute sécurité par mot de passe. Exemple : Votre téléphone désactive le verrouillage du clavier lorsque vous recevez un appel, puis le réactive lorsque vous raccrochez."</string>
+    <string name="permlab_readSyncSettings">"Lecture des paramètres de synchronisation"</string>
+    <string name="permdesc_readSyncSettings">"Permet à une application de lire les paramètres de synchronisation (par ex. savoir si la synchronisation est activée pour les Contacts)."</string>
+    <string name="permlab_writeSyncSettings">"Écriture des paramètres de synchronisation"</string>
+    <string name="permdesc_writeSyncSettings">"Permet à une application de modifier les paramètres de synchronisation (p. ex. si la synchronisation est activée pour les contacts)."</string>
+    <string name="permlab_readSyncStats">"Lecture des statistiques de synchronisation"</string>
+    <string name="permdesc_readSyncStats">"Permet à une application de lire les statistiques de synchronisation (par ex. l\'historique des synchronisations effectuées)."</string>
+    <string name="permlab_subscribedFeedsRead">"Lecture des flux auxquels vous êtes abonné"</string>
+    <string name="permdesc_subscribedFeedsRead">"Permet à une application d\'obtenir des informations sur les flux récemment synchronisés."</string>
+    <string name="permlab_subscribedFeedsWrite">"Écriture des flux auxquels vous êtes abonné"</string>
+    <string name="permdesc_subscribedFeedsWrite">"Permet à une application de modifier vos flux synchronisés actuels. Cette fonctionnalité peut permettre à des applications malveillantes de modifier vos flux synchronisés."</string>
+    <string name="permlab_readDictionary">"Lecture du dictionnaire défini par l\'utilisateur"</string>
+    <string name="permdesc_readDictionary">"Permet à une application de lire tous les mots, noms et expressions que l\'utilisateur a pu enregistrer dans son dictionnaire personnel."</string>
+    <string name="permlab_writeDictionary">"Enregistrement dans le dictionnaire défini par l\'utilisateur"</string>
+    <string name="permdesc_writeDictionary">"Permet à une application d\'enregistrer de nouveaux mots dans le dictionnaire personnel de l\'utilisateur."</string>
+  <string-array name="phoneTypes">
+    <item>"Domicile"</item>
+    <item>"Mobile"</item>
+    <item>"Bureau"</item>
+    <item>"Télécopie bureau"</item>
+    <item>"Télécopie domicile"</item>
+    <item>"Récepteur d\'appel"</item>
+    <item>"Autre"</item>
+    <item>"Personnalisé"</item>
+  </string-array>
+  <string-array name="emailAddressTypes">
+    <item>"Domicile"</item>
+    <item>"Bureau"</item>
+    <item>"Autre"</item>
+    <item>"Personnalisée"</item>
+  </string-array>
+  <string-array name="postalAddressTypes">
+    <item>"Domicile"</item>
+    <item>"Bureau"</item>
+    <item>"Autre"</item>
+    <item>"Personnalisée"</item>
+  </string-array>
+  <string-array name="imAddressTypes">
+    <item>"Domicile"</item>
+    <item>"Bureau"</item>
+    <item>"Autre"</item>
+    <item>"Personnalisé"</item>
+  </string-array>
+  <string-array name="organizationTypes">
+    <item>"Bureau"</item>
+    <item>"Autre"</item>
+    <item>"Personnalisée"</item>
+  </string-array>
+  <string-array name="imProtocols">
+    <item>"AIM"</item>
+    <item>"Windows Live"</item>
+    <item>"Yahoo"</item>
+    <item>"Skype"</item>
+    <item>"QQ"</item>
+    <item>"Google Talk"</item>
+    <item>"ICQ"</item>
+    <item>"Jabber"</item>
+  </string-array>
+    <string name="keyguard_password_enter_pin_code">"Saisissez le code PIN"</string>
+    <string name="keyguard_password_wrong_pin_code">"Le code PIN est incorrect !"</string>
+    <string name="keyguard_label_text">"Pour débloquer le clavier, appuyez sur \"Menu\" puis sur 0."</string>
+    <string name="emergency_call_dialog_number_for_display">"Numéro d\'urgence"</string>
+    <string name="lockscreen_carrier_default">"(Aucun service)"</string>
+    <string name="lockscreen_screen_locked">"Écran verrouillé"</string>
+    <string name="lockscreen_instructions_when_pattern_enabled">"Appuyez sur \"Menu\" pour débloquer le téléphone ou appeler un numéro d\'urgence"</string>
+    <string name="lockscreen_instructions_when_pattern_disabled">"Appuyez sur \"Menu\" pour déverrouiller le téléphone."</string>
+    <string name="lockscreen_pattern_instructions">"Dessinez un motif pour déverrouiller le téléphone"</string>
+    <string name="lockscreen_emergency_call">"Appel d\'urgence"</string>
+    <string name="lockscreen_pattern_correct">"Combinaison correcte !"</string>
+    <string name="lockscreen_pattern_wrong">"Désolé. Merci de réessayer."</string>
+    <!-- no translation found for lockscreen_plugged_in (613343852842944435) -->
+    <skip />
+    <string name="lockscreen_low_battery">"Branchez votre chargeur."</string>
+    <string name="lockscreen_missing_sim_message_short">"Aucune carte SIM n\'a été trouvée."</string>
+    <string name="lockscreen_missing_sim_message">"Aucune carte SIM n\'est insérée dans le téléphone."</string>
+    <string name="lockscreen_missing_sim_instructions">"Insérez une carte SIM."</string>
+    <string name="lockscreen_network_locked_message">"Réseau bloqué"</string>
+    <string name="lockscreen_sim_puk_locked_message">"La carte SIM est verrouillée par code PUK."</string>
+    <string name="lockscreen_sim_puk_locked_instructions">"Veuillez contacter l\'assistance clientèle."</string>
+    <string name="lockscreen_sim_locked_message">"La carte SIM est bloquée."</string>
+    <string name="lockscreen_sim_unlock_progress_dialog_message">"Déblocage de la carte SIM..."</string>
+    <string name="lockscreen_too_many_failed_attempts_dialog_message">"Vous avez mal reproduit le motif de déverrouillage <xliff:g id="NUMBER_0">%d</xliff:g> fois. "\n\n"Veuillez réessayer dans <xliff:g id="NUMBER_1">%d</xliff:g> secondes."</string>
+    <string name="lockscreen_failed_attempts_almost_glogin">"Vous avez mal saisi le motif de déverrouillage <xliff:g id="NUMBER_0">%d</xliff:g> fois. Au bout de <xliff:g id="NUMBER_1">%d</xliff:g> tentatives supplémentaires, vous devrez débloquer votre téléphone à l\'aide de votre identifiant Google."\n\n"Merci de réessayer dans <xliff:g id="NUMBER_2">%d</xliff:g> secondes."</string>
+    <string name="lockscreen_too_many_failed_attempts_countdown">"Réessayez dans <xliff:g id="NUMBER">%d</xliff:g> secondes."</string>
+    <string name="lockscreen_forgot_pattern_button_text">"Motif oublié ?"</string>
+    <string name="lockscreen_glogin_too_many_attempts">"Trop de tentatives de motif !"</string>
+    <string name="lockscreen_glogin_instructions">"Pour débloquer votre téléphone,"\n"connectez-vous à l\'aide de votre compte Google"</string>
+    <string name="lockscreen_glogin_username_hint">"Nom d\'utilisateur (e-mail)"</string>
+    <string name="lockscreen_glogin_password_hint">"Mot de passe"</string>
+    <string name="lockscreen_glogin_submit_button">"Se connecter"</string>
+    <string name="lockscreen_glogin_invalid_input">"Nom d\'utilisateur ou mot de passe incorrect."</string>
+    <string name="status_bar_time_format">"<xliff:g id="HOUR">h</xliff:g>:<xliff:g id="MINUTE">mm</xliff:g> <xliff:g id="AMPM">AA</xliff:g>"</string>
+    <string name="hour_minute_ampm">"<xliff:g id="HOUR">%-l</xliff:g>:<xliff:g id="MINUTE">%M</xliff:g><xliff:g id="AMPM">%P</xliff:g>"</string>
+    <string name="hour_minute_cap_ampm">"<xliff:g id="HOUR">%-l</xliff:g>:<xliff:g id="MINUTE">%M</xliff:g><xliff:g id="AMPM">%p</xliff:g>"</string>
+    <!-- no translation found for hour_ampm (4329881288269772723) -->
+    <skip />
+    <!-- no translation found for hour_cap_ampm (1829009197680861107) -->
+    <skip />
+    <string name="status_bar_clear_all_button">"Effacer les notifications"</string>
+    <string name="status_bar_no_notifications_title">"Aucune notification"</string>
+    <string name="status_bar_ongoing_events_title">"En cours"</string>
+    <string name="status_bar_latest_events_title">"Notifications"</string>
+    <string name="battery_status_text_percent_format">"<xliff:g id="NUMBER">%d</xliff:g> <xliff:g id="PERCENT">%%</xliff:g>"</string>
+    <string name="battery_status_charging">"Chargement..."</string>
+    <string name="battery_low_title">"Branchez le chargeur"</string>
+    <string name="battery_low_subtitle">"Le niveau de la batterie est bas :"</string>
+    <string name="battery_low_percent_format">"Batterie restante inférieure à <xliff:g id="NUMBER">%d%%</xliff:g>."</string>
+    <string name="factorytest_failed">"Échec du test usine"</string>
+    <string name="factorytest_not_system">"L\'action FACTORY_TEST est uniquement prise en charge pour les paquets de données installés dans in/system/app."</string>
+    <string name="factorytest_no_action">"Impossible de trouver un paquet proposant l\'action FACTORY_TEST."</string>
+    <string name="factorytest_reboot">"Redémarrer"</string>
+    <string name="js_dialog_title">"La page \"<xliff:g id="TITLE">%s</xliff:g>\" affirme :"</string>
+    <string name="js_dialog_title_default">"JavaScript"</string>
+    <string name="js_dialog_before_unload">"Vous souhaitez quitter cette page ?"\n\n"<xliff:g id="MESSAGE">%s</xliff:g>"\n\n"Sélectionnez OK pour continuer ou Annuler pour rester sur la page actuelle."</string>
+    <string name="save_password_label">"Confirmer"</string>
+    <string name="save_password_message">"Voulez-vous que le navigateur se souvienne de ce mot de passe ?"</string>
+    <string name="save_password_notnow">"Pas maintenant"</string>
+    <string name="save_password_remember">"Se souvenir du mot de passe"</string>
+    <string name="save_password_never">"Jamais"</string>
+    <string name="open_permission_deny">"Vous n\'êtes pas autorisé à ouvrir cette page."</string>
+    <string name="text_copied">"Le texte a été copié dans le presse-papier."</string>
+    <string name="more_item_label">"Plus"</string>
+    <string name="prepend_shortcut_label">"Menu+"</string>
+    <string name="menu_space_shortcut_label">"espace"</string>
+    <string name="menu_enter_shortcut_label">"entrée"</string>
+    <string name="menu_delete_shortcut_label">"supprimer"</string>
+    <string name="search_go">"Rechercher"</string>
+    <string name="today">"Aujourd\'hui"</string>
+    <string name="yesterday">"Hier"</string>
+    <string name="tomorrow">"Demain"</string>
+    <string name="oneMonthDurationPast">"Il y a 1 mois"</string>
+    <string name="beforeOneMonthDurationPast">"Il y a plus d\'un mois"</string>
+  <plurals name="num_seconds_ago">
+    <item quantity="one">"Il y a 1 seconde"</item>
+    <item quantity="other">"Il y a <xliff:g id="COUNT">%d</xliff:g> secondes"</item>
+  </plurals>
+  <plurals name="num_minutes_ago">
+    <item quantity="one">"Il y a 1 minute"</item>
+    <item quantity="other">"Il y a <xliff:g id="COUNT">%d</xliff:g> minutes"</item>
+  </plurals>
+  <plurals name="num_hours_ago">
+    <item quantity="one">"il y a 1 heure"</item>
+    <item quantity="other">"Il y a <xliff:g id="COUNT">%d</xliff:g> heures"</item>
+  </plurals>
+  <plurals name="num_days_ago">
+    <item quantity="one">"hier"</item>
+    <item quantity="other">"Il y a <xliff:g id="COUNT">%d</xliff:g> jours"</item>
+  </plurals>
+  <plurals name="in_num_seconds">
+    <item quantity="one">"dans 1 seconde"</item>
+    <item quantity="other">"dans <xliff:g id="COUNT">%d</xliff:g> secondes"</item>
+  </plurals>
+  <plurals name="in_num_minutes">
+    <item quantity="one">"dans 1 minute"</item>
+    <item quantity="other">"dans <xliff:g id="COUNT">%d</xliff:g> minutes"</item>
+  </plurals>
+  <plurals name="in_num_hours">
+    <item quantity="one">"dans 1 heure"</item>
+    <item quantity="other">"dans <xliff:g id="COUNT">%d</xliff:g> heures"</item>
+  </plurals>
+  <plurals name="in_num_days">
+    <item quantity="one">"demain"</item>
+    <item quantity="other">"dans <xliff:g id="COUNT">%d</xliff:g> jours"</item>
+  </plurals>
+  <plurals name="abbrev_num_seconds_ago">
+    <item quantity="one">"il y a 1 seconde"</item>
+    <item quantity="other">"il y a <xliff:g id="COUNT">%d</xliff:g> secondes"</item>
+  </plurals>
+  <plurals name="abbrev_num_minutes_ago">
+    <item quantity="one">"il y a 1 minute"</item>
+    <item quantity="other">"il y a <xliff:g id="COUNT">%d</xliff:g> minutes"</item>
+  </plurals>
+  <plurals name="abbrev_num_hours_ago">
+    <item quantity="one">"il y a 1 heure"</item>
+    <item quantity="other">"Il y a <xliff:g id="COUNT">%d</xliff:g> heures"</item>
+  </plurals>
+  <plurals name="abbrev_num_days_ago">
+    <item quantity="one">"hier"</item>
+    <item quantity="other">"Il y a <xliff:g id="COUNT">%d</xliff:g> jours"</item>
+  </plurals>
+  <plurals name="abbrev_in_num_seconds">
+    <item quantity="one">"dans 1 seconde"</item>
+    <item quantity="other">"dans <xliff:g id="COUNT">%d</xliff:g> secondes"</item>
+  </plurals>
+  <plurals name="abbrev_in_num_minutes">
+    <item quantity="one">"dans 1 minute"</item>
+    <item quantity="other">"dans <xliff:g id="COUNT">%d</xliff:g> minutes"</item>
+  </plurals>
+  <plurals name="abbrev_in_num_hours">
+    <item quantity="one">"dans 1 heure"</item>
+    <item quantity="other">"dans <xliff:g id="COUNT">%d</xliff:g> heures"</item>
+  </plurals>
+  <plurals name="abbrev_in_num_days">
+    <item quantity="one">"demain"</item>
+    <item quantity="other">"dans <xliff:g id="COUNT">%d</xliff:g> jours"</item>
+  </plurals>
+    <string name="preposition_for_date">"%s"</string>
+    <string name="preposition_for_time">"à %s"</string>
+    <string name="preposition_for_year">"en %s"</string>
+    <string name="day">"jour"</string>
+    <string name="days">"jours"</string>
+    <string name="hour">"heure"</string>
+    <string name="hours">"heures"</string>
+    <string name="minute">"mn"</string>
+    <string name="minutes">"mn"</string>
+    <string name="second">"s"</string>
+    <string name="seconds">"s"</string>
+    <string name="week">"semaine"</string>
+    <string name="weeks">"semaines"</string>
+    <string name="year">"année"</string>
+    <string name="years">"années"</string>
+    <string name="sunday">"dimanche"</string>
+    <string name="monday">"lundi"</string>
+    <string name="tuesday">"mardi"</string>
+    <string name="wednesday">"mercredi"</string>
+    <string name="thursday">"jeudi"</string>
+    <string name="friday">"vendredi"</string>
+    <string name="saturday">"samedi"</string>
+    <string name="every_weekday">"Tous les jours ouvrés (lun.- ven.)"</string>
+    <string name="daily">"Tous les jours"</string>
+    <string name="weekly">"Toutes les semaines le <xliff:g id="DAY">%s</xliff:g>"</string>
+    <string name="monthly">"Tous les mois"</string>
+    <string name="yearly">"Tous les ans"</string>
+    <string name="VideoView_error_title">"Échec de la lecture de la vidéo"</string>
+    <string name="VideoView_error_text_unknown">"Désolé, impossible de lire cette vidéo."</string>
+    <string name="VideoView_error_button">"OK"</string>
+    <string name="am">"AM"</string>
+    <string name="pm">"PM"</string>
+    <string name="numeric_date">"<xliff:g id="DAY">%d</xliff:g>/<xliff:g id="MONTH">%m</xliff:g>/<xliff:g id="YEAR">%Y</xliff:g>"</string>
+    <string name="wday1_date1_time1_wday2_date2_time2">"<xliff:g id="WEEKDAY1">%1$s</xliff:g> <xliff:g id="DATE1">%2$s</xliff:g>, <xliff:g id="TIME1">%3$s</xliff:g> – <xliff:g id="WEEKDAY2">%4$s</xliff:g> <xliff:g id="DATE2">%5$s</xliff:g>, <xliff:g id="TIME2">%6$s</xliff:g>"</string>
+    <string name="wday1_date1_wday2_date2">"<xliff:g id="WEEKDAY1">%1$s</xliff:g>, <xliff:g id="DATE1">%2$s</xliff:g> – <xliff:g id="WEEKDAY2">%4$s</xliff:g>, <xliff:g id="DATE2">%5$s</xliff:g>"</string>
+    <string name="date1_time1_date2_time2">"<xliff:g id="DATE1">%2$s</xliff:g>, <xliff:g id="TIME1">%3$s</xliff:g> – <xliff:g id="DATE2">%5$s</xliff:g>, <xliff:g id="TIME2">%6$s</xliff:g>"</string>
+    <string name="date1_date2">"<xliff:g id="DATE1">%2$s</xliff:g> – <xliff:g id="DATE2">%5$s</xliff:g>"</string>
+    <string name="time1_time2">"<xliff:g id="TIME1">%1$s</xliff:g> – <xliff:g id="TIME2">%2$s</xliff:g>"</string>
+    <string name="time_wday_date">"<xliff:g id="TIME_RANGE">%1$s</xliff:g>, <xliff:g id="WEEKDAY">%2$s</xliff:g>, <xliff:g id="DATE">%3$s</xliff:g>"</string>
+    <string name="wday_date">"<xliff:g id="WEEKDAY">%2$s</xliff:g>, <xliff:g id="DATE">%3$s</xliff:g>"</string>
+    <string name="time_date">"<xliff:g id="TIME_RANGE">%1$s</xliff:g>, <xliff:g id="DATE">%3$s</xliff:g>"</string>
+    <string name="date_time">"<xliff:g id="DATE">%1$s</xliff:g>, <xliff:g id="TIME">%2$s</xliff:g>"</string>
+    <string name="relative_time">"<xliff:g id="DATE">%1$s</xliff:g>, <xliff:g id="TIME">%2$s</xliff:g>"</string>
+    <string name="time_wday">"<xliff:g id="TIME_RANGE">%1$s</xliff:g>, <xliff:g id="WEEKDAY">%2$s</xliff:g>"</string>
+    <string name="full_date_month_first" format="date">"<xliff:g id="DAY">d</xliff:g>' '<xliff:g id="MONTH">MMMM</xliff:g>' '<xliff:g id="YEAR">yyyy</xliff:g>"</string>
+    <string name="full_date_day_first" format="date">"<xliff:g id="DAY">d</xliff:g>' '<xliff:g id="MONTH">MMMM</xliff:g>' '<xliff:g id="YEAR">yyyy</xliff:g>"</string>
+    <string name="medium_date_month_first" format="date">"<xliff:g id="DAY">d</xliff:g>' '<xliff:g id="MONTH">MMM</xliff:g>' '<xliff:g id="YEAR">yyyy</xliff:g>"</string>
+    <string name="medium_date_day_first" format="date">"<xliff:g id="DAY">d</xliff:g>' '<xliff:g id="MONTH">MMM</xliff:g>' '<xliff:g id="YEAR">yyyy</xliff:g>"</string>
+    <string name="twelve_hour_time_format" format="date">"<xliff:g id="HOUR">h</xliff:g>':'<xliff:g id="MINUTE">mm</xliff:g>' '<xliff:g id="AMPM">a</xliff:g>"</string>
+    <string name="twenty_four_hour_time_format" format="date">"<xliff:g id="HOUR">H</xliff:g>':'<xliff:g id="MINUTE">mm</xliff:g>"</string>
+    <string name="noon">"midi"</string>
+    <string name="Noon">"Midi"</string>
+    <string name="midnight">"minuit"</string>
+    <string name="Midnight">"Minuit"</string>
+    <string name="month_day">"<xliff:g id="DAY">%-d</xliff:g> <xliff:g id="MONTH">%B</xliff:g>"</string>
+    <!-- no translation found for month (1976700695144952053) -->
+    <skip />
+    <string name="month_day_year">"<xliff:g id="DAY">%-d</xliff:g> <xliff:g id="MONTH">%B</xliff:g> <xliff:g id="YEAR">%Y</xliff:g>"</string>
+    <!-- no translation found for month_year (2106203387378728384) -->
+    <skip />
+    <string name="time_of_day">"<xliff:g id="HOUR">%H</xliff:g>:<xliff:g id="MINUTE">%M</xliff:g>:<xliff:g id="SECOND">%S</xliff:g>"</string>
+    <string name="date_and_time">"<xliff:g id="HOUR">%H</xliff:g>:<xliff:g id="MINUTE">%M</xliff:g>:<xliff:g id="SECOND">%S</xliff:g> <xliff:g id="DAY">%-d</xliff:g> <xliff:g id="MONTH">%B</xliff:g> <xliff:g id="YEAR">%Y</xliff:g>"</string>
+    <string name="same_year_md1_md2">"<xliff:g id="DAY1">%3$s</xliff:g> <xliff:g id="MONTH1">%2$s</xliff:g> – <xliff:g id="DAY2">%8$s</xliff:g> <xliff:g id="MONTH2">%7$s</xliff:g>"</string>
+    <string name="same_year_wday1_md1_wday2_md2">"<xliff:g id="WEEKDAY1">%1$s</xliff:g> <xliff:g id="DAY1_0">%3$s</xliff:g> <xliff:g id="MONTH1">%2$s</xliff:g> – <xliff:g id="WEEKDAY2">%6$s</xliff:g> <xliff:g id="DAY2_1">%8$s</xliff:g> <xliff:g id="MONTH2">%7$s</xliff:g>"</string>
+    <string name="same_year_mdy1_mdy2">"<xliff:g id="DAY1">%3$s</xliff:g> <xliff:g id="MONTH1">%2$s</xliff:g> – <xliff:g id="DAY2">%8$s</xliff:g> <xliff:g id="MONTH2">%7$s</xliff:g> <xliff:g id="YEAR">%9$s</xliff:g>"</string>
+    <string name="same_year_wday1_mdy1_wday2_mdy2">"<xliff:g id="WEEKDAY1">%1$s</xliff:g>, <xliff:g id="DAY1_0">%3$s</xliff:g> <xliff:g id="MONTH1">%2$s</xliff:g> – <xliff:g id="WEEKDAY2">%6$s</xliff:g>, <xliff:g id="DAY2_1">%8$s</xliff:g> <xliff:g id="MONTH2">%7$s</xliff:g> <xliff:g id="YEAR">%9$s</xliff:g>"</string>
+    <string name="same_year_md1_time1_md2_time2">"<xliff:g id="DAY1">%3$s</xliff:g> <xliff:g id="MONTH1">%2$s</xliff:g> <xliff:g id="TIME1">%5$s</xliff:g> – <xliff:g id="DAY2">%8$s</xliff:g> <xliff:g id="MONTH2">%7$s</xliff:g> <xliff:g id="TIME2">%10$s</xliff:g>"</string>
+    <string name="same_year_wday1_md1_time1_wday2_md2_time2">"<xliff:g id="WEEKDAY1">%1$s</xliff:g>, <xliff:g id="DAY1_0">%3$s</xliff:g> <xliff:g id="MONTH1">%2$s</xliff:g>, <xliff:g id="TIME1">%5$s</xliff:g> – <xliff:g id="WEEKDAY2">%6$s</xliff:g>, <xliff:g id="DAY2_1">%8$s</xliff:g> <xliff:g id="MONTH2">%7$s</xliff:g>, <xliff:g id="TIME2">%10$s</xliff:g>"</string>
+    <string name="same_year_mdy1_time1_mdy2_time2">"<xliff:g id="DAY1">%3$s</xliff:g> <xliff:g id="MONTH1">%2$s</xliff:g> <xliff:g id="YEAR1">%4$s</xliff:g>, <xliff:g id="TIME1">%5$s</xliff:g> – <xliff:g id="DAY2">%8$s</xliff:g> <xliff:g id="MONTH2">%7$s</xliff:g> <xliff:g id="YEAR2">%9$s</xliff:g>, <xliff:g id="TIME2">%10$s</xliff:g>"</string>
+    <string name="same_year_wday1_mdy1_time1_wday2_mdy2_time2">"<xliff:g id="WEEKDAY1">%1$s</xliff:g>, <xliff:g id="DAY1_0">%3$s</xliff:g> <xliff:g id="MONTH1">%2$s</xliff:g> <xliff:g id="YEAR1">%4$s</xliff:g>, <xliff:g id="TIME1">%5$s</xliff:g> – <xliff:g id="WEEKDAY2">%6$s</xliff:g>, <xliff:g id="DAY2_1">%8$s</xliff:g> <xliff:g id="MONTH2">%7$s</xliff:g> <xliff:g id="YEAR2">%9$s</xliff:g>, <xliff:g id="TIME2">%10$s</xliff:g>"</string>
+    <string name="numeric_md1_md2">"<xliff:g id="DAY1">%3$s</xliff:g>/<xliff:g id="MONTH1">%2$s</xliff:g> – <xliff:g id="DAY2">%8$s</xliff:g>/<xliff:g id="MONTH2">%7$s</xliff:g>"</string>
+    <string name="numeric_wday1_md1_wday2_md2">"<xliff:g id="WEEKDAY1">%1$s</xliff:g> <xliff:g id="DAY1_0">%3$s</xliff:g>/<xliff:g id="MONTH1">%2$s</xliff:g> – <xliff:g id="WEEKDAY2">%6$s</xliff:g> <xliff:g id="DAY2_1">%8$s</xliff:g>/<xliff:g id="MONTH2">%7$s</xliff:g>"</string>
+    <string name="numeric_mdy1_mdy2">"<xliff:g id="DAY1">%3$s</xliff:g>/<xliff:g id="MONTH1">%2$s</xliff:g>/<xliff:g id="YEAR1">%4$s</xliff:g> – <xliff:g id="DAY2">%8$s</xliff:g>/<xliff:g id="MONTH2">%7$s</xliff:g>/<xliff:g id="YEAR2">%9$s</xliff:g>"</string>
+    <string name="numeric_wday1_mdy1_wday2_mdy2">"<xliff:g id="WEEKDAY1">%1$s</xliff:g>, <xliff:g id="DAY1_0">%3$s</xliff:g>/<xliff:g id="MONTH1">%2$s</xliff:g>/<xliff:g id="YEAR1">%4$s</xliff:g> – <xliff:g id="WEEKDAY2">%6$s</xliff:g>, <xliff:g id="DAY2_1">%8$s</xliff:g>/<xliff:g id="MONTH2">%7$s</xliff:g>/<xliff:g id="YEAR2">%9$s</xliff:g>"</string>
+    <string name="numeric_md1_time1_md2_time2">"<xliff:g id="DAY1">%3$s</xliff:g>/<xliff:g id="MONTH1">%2$s</xliff:g> <xliff:g id="TIME1">%5$s</xliff:g> – <xliff:g id="DAY2">%8$s</xliff:g>/<xliff:g id="MONTH2">%7$s</xliff:g> <xliff:g id="TIME2">%10$s</xliff:g>"</string>
+    <string name="numeric_wday1_md1_time1_wday2_md2_time2">"<xliff:g id="WEEKDAY1">%1$s</xliff:g>, <xliff:g id="DAY1_0">%3$s</xliff:g>/<xliff:g id="MONTH1">%2$s</xliff:g>, <xliff:g id="TIME1">%5$s</xliff:g> – <xliff:g id="WEEKDAY2">%6$s</xliff:g>, <xliff:g id="DAY2_1">%8$s</xliff:g>/<xliff:g id="MONTH2">%7$s</xliff:g>, <xliff:g id="TIME2">%10$s</xliff:g>"</string>
+    <string name="numeric_mdy1_time1_mdy2_time2">"<xliff:g id="DAY1">%3$s</xliff:g>/<xliff:g id="MONTH1">%2$s</xliff:g>/<xliff:g id="YEAR1">%4$s</xliff:g>, <xliff:g id="TIME1">%5$s</xliff:g> – <xliff:g id="DAY2">%8$s</xliff:g>/<xliff:g id="MONTH2">%7$s</xliff:g>/<xliff:g id="YEAR2">%9$s</xliff:g>, <xliff:g id="TIME2">%10$s</xliff:g>"</string>
+    <string name="numeric_wday1_mdy1_time1_wday2_mdy2_time2">"<xliff:g id="WEEKDAY1">%1$s</xliff:g> <xliff:g id="DAY1_0">%3$s</xliff:g>/<xliff:g id="MONTH1">%2$s</xliff:g>/<xliff:g id="YEAR1">%4$s</xliff:g>, <xliff:g id="TIME1">%5$s</xliff:g> – <xliff:g id="WEEKDAY2">%6$s</xliff:g> <xliff:g id="DAY2_1">%8$s</xliff:g>/<xliff:g id="MONTH2">%7$s</xliff:g>/<xliff:g id="YEAR2">%9$s</xliff:g>, <xliff:g id="TIME2">%10$s</xliff:g>"</string>
+    <string name="same_month_md1_md2">"<xliff:g id="DAY1">%3$s</xliff:g> – <xliff:g id="DAY2">%8$s</xliff:g> <xliff:g id="MONTH1">%2$s</xliff:g>"</string>
+    <string name="same_month_wday1_md1_wday2_md2">"<xliff:g id="WEEKDAY1">%1$s</xliff:g>, <xliff:g id="DAY1_0">%3$s</xliff:g> <xliff:g id="MONTH1">%2$s</xliff:g> – <xliff:g id="WEEKDAY2">%6$s</xliff:g>, <xliff:g id="DAY2_1">%8$s</xliff:g> <xliff:g id="MONTH2">%7$s</xliff:g>"</string>
+    <string name="same_month_mdy1_mdy2">"<xliff:g id="DAY1">%3$s</xliff:g> – <xliff:g id="DAY2">%8$s</xliff:g> <xliff:g id="MONTH1">%2$s</xliff:g> <xliff:g id="YEAR2">%9$s</xliff:g>"</string>
+    <string name="same_month_wday1_mdy1_wday2_mdy2">"<xliff:g id="WEEKDAY1">%1$s</xliff:g> <xliff:g id="DAY1_0">%3$s</xliff:g> <xliff:g id="MONTH1">%2$s</xliff:g> <xliff:g id="YEAR1">%4$s</xliff:g> – <xliff:g id="WEEKDAY2">%6$s</xliff:g> <xliff:g id="DAY2_1">%8$s</xliff:g> <xliff:g id="MONTH2">%7$s</xliff:g> <xliff:g id="YEAR2">%9$s</xliff:g>"</string>
+    <string name="same_month_md1_time1_md2_time2">"<xliff:g id="DAY1">%3$s</xliff:g> <xliff:g id="MONTH1">%2$s</xliff:g> <xliff:g id="TIME1">%5$s</xliff:g> – <xliff:g id="DAY2">%8$s</xliff:g> <xliff:g id="MONTH2">%7$s</xliff:g> <xliff:g id="TIME2">%10$s</xliff:g>"</string>
+    <string name="same_month_wday1_md1_time1_wday2_md2_time2">"<xliff:g id="WEEKDAY1">%1$s</xliff:g>, <xliff:g id="DAY1_0">%3$s</xliff:g> <xliff:g id="MONTH1">%2$s</xliff:g>, <xliff:g id="TIME1">%5$s</xliff:g> – <xliff:g id="WEEKDAY2">%6$s</xliff:g>, <xliff:g id="DAY2_1">%8$s</xliff:g> <xliff:g id="MONTH2">%7$s</xliff:g>, <xliff:g id="TIME2">%10$s</xliff:g>"</string>
+    <string name="same_month_mdy1_time1_mdy2_time2">"<xliff:g id="DAY1">%3$s</xliff:g> <xliff:g id="MONTH1">%2$s</xliff:g> <xliff:g id="YEAR1">%4$s</xliff:g>, <xliff:g id="TIME1">%5$s</xliff:g> – <xliff:g id="DAY2">%8$s</xliff:g> <xliff:g id="MONTH2">%7$s</xliff:g> <xliff:g id="YEAR2">%9$s</xliff:g>, <xliff:g id="TIME2">%10$s</xliff:g>"</string>
+    <string name="same_month_wday1_mdy1_time1_wday2_mdy2_time2">"<xliff:g id="WEEKDAY1">%1$s</xliff:g> <xliff:g id="DAY1_0">%3$s</xliff:g> <xliff:g id="MONTH1">%2$s</xliff:g> <xliff:g id="YEAR1">%4$s</xliff:g>, <xliff:g id="TIME1">%5$s</xliff:g> – <xliff:g id="WEEKDAY2">%6$s</xliff:g> <xliff:g id="DAY2_1">%8$s</xliff:g> <xliff:g id="MONTH2">%7$s</xliff:g> <xliff:g id="YEAR2">%9$s</xliff:g>, <xliff:g id="TIME2">%10$s</xliff:g>"</string>
+    <string name="abbrev_month_day_year">"<xliff:g id="DAY">%-d</xliff:g> <xliff:g id="MONTH">%b</xliff:g> <xliff:g id="YEAR">%Y</xliff:g>"</string>
+    <!-- no translation found for abbrev_month_year (5966980891147982768) -->
+    <skip />
+    <string name="abbrev_month_day">"<xliff:g id="DAY">%-d</xliff:g> <xliff:g id="MONTH">%b</xliff:g>"</string>
+    <!-- no translation found for abbrev_month (7304935052615731208) -->
+    <skip />
+    <string name="day_of_week_long_sunday">"dimanche"</string>
+    <string name="day_of_week_long_monday">"lundi"</string>
+    <string name="day_of_week_long_tuesday">"mardi"</string>
+    <string name="day_of_week_long_wednesday">"mercredi"</string>
+    <string name="day_of_week_long_thursday">"jeudi"</string>
+    <string name="day_of_week_long_friday">"vendredi"</string>
+    <string name="day_of_week_long_saturday">"samedi"</string>
+    <string name="day_of_week_medium_sunday">"dim."</string>
+    <string name="day_of_week_medium_monday">"Lun"</string>
+    <string name="day_of_week_medium_tuesday">"Mar"</string>
+    <string name="day_of_week_medium_wednesday">"Mer"</string>
+    <string name="day_of_week_medium_thursday">"Jeu"</string>
+    <string name="day_of_week_medium_friday">"Ven"</string>
+    <string name="day_of_week_medium_saturday">"Sam"</string>
+    <string name="day_of_week_short_sunday">"Dim"</string>
+    <string name="day_of_week_short_monday">"Lun"</string>
+    <string name="day_of_week_short_tuesday">"Mar"</string>
+    <string name="day_of_week_short_wednesday">"Mer"</string>
+    <string name="day_of_week_short_thursday">"Jeu"</string>
+    <string name="day_of_week_short_friday">"Ven"</string>
+    <string name="day_of_week_short_saturday">"Sam"</string>
+    <string name="day_of_week_shorter_sunday">"Dim"</string>
+    <string name="day_of_week_shorter_monday">"Lun"</string>
+    <string name="day_of_week_shorter_tuesday">"Mar"</string>
+    <string name="day_of_week_shorter_wednesday">"Mer"</string>
+    <string name="day_of_week_shorter_thursday">"Jeu"</string>
+    <string name="day_of_week_shorter_friday">"Ven"</string>
+    <string name="day_of_week_shorter_saturday">"sam."</string>
+    <string name="day_of_week_shortest_sunday">"Dim"</string>
+    <string name="day_of_week_shortest_monday">"Lun"</string>
+    <string name="day_of_week_shortest_tuesday">"Mar"</string>
+    <string name="day_of_week_shortest_wednesday">"Mer"</string>
+    <string name="day_of_week_shortest_thursday">"Jeu"</string>
+    <string name="day_of_week_shortest_friday">"Ven"</string>
+    <string name="day_of_week_shortest_saturday">"Sam"</string>
+    <string name="month_long_january">"janvier"</string>
+    <string name="month_long_february">"février"</string>
+    <string name="month_long_march">"mars"</string>
+    <string name="month_long_april">"avril"</string>
+    <string name="month_long_may">"mai"</string>
+    <string name="month_long_june">"juin"</string>
+    <string name="month_long_july">"juillet"</string>
+    <string name="month_long_august">"août"</string>
+    <string name="month_long_september">"septembre"</string>
+    <string name="month_long_october">"octobre"</string>
+    <string name="month_long_november">"novembre"</string>
+    <string name="month_long_december">"décembre"</string>
+    <string name="month_medium_january">"janv."</string>
+    <string name="month_medium_february">"févr."</string>
+    <string name="month_medium_march">"mars"</string>
+    <string name="month_medium_april">"avr."</string>
+    <string name="month_medium_may">"mai"</string>
+    <string name="month_medium_june">"juin"</string>
+    <string name="month_medium_july">"juil."</string>
+    <string name="month_medium_august">"août"</string>
+    <string name="month_medium_september">"sept."</string>
+    <string name="month_medium_october">"oct."</string>
+    <string name="month_medium_november">"nov."</string>
+    <string name="month_medium_december">"déc."</string>
+    <string name="month_shortest_january">"jan."</string>
+    <string name="month_shortest_february">"Ven"</string>
+    <string name="month_shortest_march">"mars"</string>
+    <string name="month_shortest_april">"avr."</string>
+    <string name="month_shortest_may">"mai"</string>
+    <string name="month_shortest_june">"juin"</string>
+    <string name="month_shortest_july">"juil."</string>
+    <string name="month_shortest_august">"août"</string>
+    <string name="month_shortest_september">"sept."</string>
+    <string name="month_shortest_october">"oct."</string>
+    <string name="month_shortest_november">"nov."</string>
+    <string name="month_shortest_december">"déc."</string>
+    <string name="elapsed_time_short_format_mm_ss">"<xliff:g id="MINUTES">%1$02d</xliff:g>:<xliff:g id="SECONDS">%2$02d</xliff:g>"</string>
+    <string name="elapsed_time_short_format_h_mm_ss">"<xliff:g id="HOURS">%1$d</xliff:g>:<xliff:g id="MINUTES">%2$02d</xliff:g>:<xliff:g id="SECONDS">%3$02d</xliff:g>"</string>
+    <string name="selectAll">"Tout sélectionner"</string>
+    <string name="selectText">"Sélectionner le texte"</string>
+    <string name="stopSelectingText">"Arrêter sélection de texte"</string>
+    <string name="cut">"Couper"</string>
+    <string name="cutAll">"Tout couper"</string>
+    <string name="copy">"Copier"</string>
+    <string name="copyAll">"Tout copier"</string>
+    <string name="paste">"Coller"</string>
+    <string name="copyUrl">"Copier l\'URL"</string>
+    <string name="inputMethod">"Mode de saisie"</string>
+    <string name="addToDictionary">"Ajouter \"%s\" au dictionnaire"</string>
+    <string name="editTextMenuTitle">"Modifier le texte"</string>
+    <string name="low_internal_storage_view_title">"Espace disponible faible"</string>
+    <string name="low_internal_storage_view_text">"La mémoire du téléphone commence à être pleine."</string>
+    <string name="ok">"OK"</string>
+    <string name="cancel">"Annuler"</string>
+    <string name="yes">"OK"</string>
+    <string name="no">"Annuler"</string>
+    <string name="dialog_alert_title">"Attention"</string>
+    <string name="capital_on">"ON"</string>
+    <string name="capital_off">"OFF"</string>
+    <string name="whichApplication">"Continuer avec"</string>
+    <string name="alwaysUse">"Utiliser cette application par défaut pour cette action"</string>
+    <string name="clearDefaultHintMsg">"Effacer les paramètres par défaut dans les Paramètres de page d\'accueil &gt; Applications &gt; Gérer les applications."</string>
+    <string name="chooseActivity">"Sélectionner une action"</string>
+    <string name="noApplications">"Aucune application ne peut effectuer cette action."</string>
+    <string name="aerr_title">"Désolé !"</string>
+    <string name="aerr_application">"Fermeture soudaine de l\'application <xliff:g id="APPLICATION">%1$s</xliff:g> (du processus <xliff:g id="PROCESS">%2$s</xliff:g>). Merci de réessayer."</string>
+    <string name="aerr_process">"Le processus <xliff:g id="PROCESS">%1$s</xliff:g> s\'est interrompu de façon inopinée. Merci de réessayer."</string>
+    <string name="anr_title">"Désolé !"</string>
+    <string name="anr_activity_application">"L\'activité <xliff:g id="ACTIVITY">%1$s</xliff:g> (de l\'application <xliff:g id="APPLICATION">%2$s</xliff:g>) ne répond pas."</string>
+    <string name="anr_activity_process">"L\'activité <xliff:g id="ACTIVITY">%1$s</xliff:g> (du processus <xliff:g id="PROCESS">%2$s</xliff:g>) ne répond pas."</string>
+    <string name="anr_application_process">"L\'application <xliff:g id="APPLICATION">%1$s</xliff:g> (du processus <xliff:g id="PROCESS">%2$s</xliff:g>) ne répond pas."</string>
+    <string name="anr_process">"Le processus <xliff:g id="PROCESS">%1$s</xliff:g> ne répond pas."</string>
+    <string name="force_close">"Forcer la fermeture"</string>
+    <string name="wait">"Attendre"</string>
+    <string name="debug">"Débogage"</string>
+    <string name="sendText">"Sélectionner une action pour le texte"</string>
+    <string name="volume_ringtone">"Volume de la sonnerie"</string>
+    <string name="volume_music">"Volume"</string>
+    <string name="volume_music_hint_playing_through_bluetooth">"Lecture via Bluetooth"</string>
+    <string name="volume_call">"Volume des appels entrants"</string>
+    <string name="volume_bluetooth_call">"Volume d\'appels entrants sur Bluetooth"</string>
+    <string name="volume_alarm">"Volume"</string>
+    <string name="volume_notification">"Volume des notifications"</string>
+    <string name="volume_unknown">"Volume"</string>
+    <string name="ringtone_default">"Sonnerie par défaut"</string>
+    <string name="ringtone_default_with_actual">"Sonnerie par défaut (<xliff:g id="ACTUAL_RINGTONE">%1$s</xliff:g>)"</string>
+    <string name="ringtone_silent">"Silencieux"</string>
+    <string name="ringtone_picker_title">"Sonneries"</string>
+    <string name="ringtone_unknown">"Sonnerie inconnue"</string>
+  <plurals name="wifi_available">
+    <item quantity="one">"Réseau Wi-Fi disponible"</item>
+    <item quantity="other">"Réseaux Wi-Fi disponibles"</item>
+  </plurals>
+  <plurals name="wifi_available_detailed">
+    <item quantity="one">"Ouvrir le réseau Wi-Fi disponible"</item>
+    <item quantity="other">"Ouvrir les réseaux Wi-Fi disponibles"</item>
+  </plurals>
+    <string name="select_character">"Insérer un caractère"</string>
+    <string name="sms_control_default_app_name">"Application inconnue"</string>
+    <string name="sms_control_title">"Envoi de messages SMS"</string>
+    <string name="sms_control_message">"Vous êtes sur le point d\'envoyer un grand nombre de messages SMS. Sélectionnez OK pour continuer ou Annuler pour interrompre l\'envoi."</string>
+    <string name="sms_control_yes">"OK"</string>
+    <string name="sms_control_no">"Annuler"</string>
+    <string name="date_time_set">"Définir"</string>
+    <string name="default_permission_group">"Par défaut"</string>
+    <string name="no_permissions">"Aucune autorisation requise"</string>
+    <string name="perms_hide"><b>"Masquer"</b></string>
+    <string name="perms_show_all"><b>"Tout afficher"</b></string>
+    <string name="googlewebcontenthelper_loading">"Chargement..."</string>
+    <string name="usb_storage_title">"Connecté à l\'aide d\'un câble USB"</string>
+    <string name="usb_storage_message">"Vous avez connecté votre téléphone à votre ordinateur à l\'aide d\'un câble USB. Sélectionnez Monter pour copier des fichiers depuis votre ordinateur vers votre carte SD ou inversement."</string>
+    <string name="usb_storage_button_mount">"Monter"</string>
+    <string name="usb_storage_button_unmount">"Ne pas monter"</string>
+    <string name="usb_storage_error_message">"Un problème est survenu lors de l\'utilisation de votre carte SD en tant que périphérique de stockage USB."</string>
+    <string name="usb_storage_notification_title">"Connecté avec un câble USB"</string>
+    <string name="usb_storage_notification_message">"Activez pour copier des fichiers vers/de votre ordinateur."</string>
+    <string name="usb_storage_stop_notification_title">"Éteindre le périphérique de stockage USB"</string>
+    <string name="usb_storage_stop_notification_message">"Sélectionner pour éteindre le périphérique de stockage USB"</string>
+    <string name="usb_storage_stop_title">"Éteindre le périphérique de stockage USB"</string>
+    <string name="usb_storage_stop_message">"Avant d\'éteindre le périphérique de stockage USB, assurez-vous d\'avoir désactivé l\'hôte USB. Sélectionnez \"Éteindre\" pour éteindre le périphérique de stockage USB."</string>
+    <string name="usb_storage_stop_button_mount">"Éteindre"</string>
+    <string name="usb_storage_stop_button_unmount">"Annuler"</string>
+    <string name="usb_storage_stop_error_message">"Un problème est survenu lors de la mise hors tension du périphérique de stockage USB. Assurez-vous que l\'hôte USB a bien été désactivé, puis essayez à nouveau."</string>
+    <string name="extmedia_format_title">"Formater la carte SD"</string>
+    <string name="extmedia_format_message">"Voulez-vous vraiment formater la carte SD ? Toutes les données de cette carte seront perdues."</string>
+    <string name="extmedia_format_button_format">"Format"</string>
+    <string name="select_input_method">"Sélectionner un mode de saisie"</string>
+    <string name="fast_scroll_alphabet">" ABCDEFGHIJKLMNOPQRSTUVWXYZ"</string>
+    <string name="fast_scroll_numeric_alphabet">" 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"</string>
+    <string name="candidates_style"><u>"candidats"</u></string>
+    <string name="ext_media_checking_notification_title">"Préparation de la carte SD"</string>
+    <string name="ext_media_checking_notification_message">"Recherche d\'erreurs"</string>
+    <string name="ext_media_nofs_notification_title">"Carte SD vide"</string>
+    <string name="ext_media_nofs_notification_message">"La carte SD est vide ou utilise un système de fichiers non pris en charge."</string>
+    <string name="ext_media_unmountable_notification_title">"Carte SD endommagée"</string>
+    <string name="ext_media_unmountable_notification_message">"La carte SD est endommagée. Vous devrez peut-être reformater votre carte."</string>
+    <string name="ext_media_badremoval_notification_title">"Carte SD retirée inopinément"</string>
+    <string name="ext_media_badremoval_notification_message">"Désactiver la carte SD avant de la retirer pour éviter toute perte de données."</string>
+    <string name="ext_media_safe_unmount_notification_title">"La carte SD peut être retirée en toute sécurité"</string>
+    <string name="ext_media_safe_unmount_notification_message">"Vous pouvez désormais retirer la carte SD en toute sécurité."</string>
+    <string name="ext_media_nomedia_notification_title">"Carte SD manquante"</string>
+    <string name="ext_media_nomedia_notification_message">"La carte SD a été retirée. Insérez une autre carte pour augmenter la capacité de stockage de votre appareil."</string>
+    <string name="activity_list_empty">"Aucune activité correspondante trouvée"</string>
+    <string name="permlab_pkgUsageStats">"mettre à jour les données statistiques du composant"</string>
+    <string name="permdesc_pkgUsageStats">"Permet de modifier les données statistiques collectées du composant. Cette option n\'est pas utilisée par les applications standard."</string>
+    <string name="tutorial_double_tap_to_zoom_message_short">"Tapez deux fois pour le zoom"</string>
+    <!-- no translation found for gadget_host_error_inflating (2613287218853846830) -->
+    <skip />
+    <!-- no translation found for ime_action_go (8320845651737369027) -->
+    <skip />
+    <!-- no translation found for ime_action_search (658110271822807811) -->
+    <skip />
+    <!-- no translation found for ime_action_send (2316166556349314424) -->
+    <skip />
+    <!-- no translation found for ime_action_next (3138843904009813834) -->
+    <skip />
+    <!-- no translation found for ime_action_default (2840921885558045721) -->
+    <skip />
+</resources>
diff --git a/core/res/res/values-it-rIT/arrays.xml b/core/res/res/values-it-rIT/arrays.xml
new file mode 100644
index 0000000..92e5260
--- /dev/null
+++ b/core/res/res/values-it-rIT/arrays.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* //device/apps/common/assets/res/any/colors.xml
+**
+** Copyright 2006, Google Inc.
+**
+** 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.
+*/
+-->
+<resources>
+
+    <!-- Do not translate. -->
+    <integer-array name="maps_starting_lat_lng">
+        <item>41795888</item>
+        <item>12480469</item>
+    </integer-array>
+    <!-- Do not translate. -->
+    <integer-array name="maps_starting_zoom">
+        <item>6</item>
+    </integer-array>
+
+</resources>
diff --git a/core/res/res/values-it/strings.xml b/core/res/res/values-it/strings.xml
new file mode 100644
index 0000000..b4cf458
--- /dev/null
+++ b/core/res/res/values-it/strings.xml
@@ -0,0 +1,815 @@
+<?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.
+-->
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="byteShort">"B"</string>
+    <string name="kilobyteShort">"KB"</string>
+    <string name="megabyteShort">"MB"</string>
+    <string name="gigabyteShort">"GB"</string>
+    <string name="terabyteShort">"TB"</string>
+    <string name="petabyteShort">"PB"</string>
+    <string name="untitled">"&lt;senza nome&gt;"</string>
+    <string name="ellipsis">"…"</string>
+    <string name="emptyPhoneNumber">"(Nessun numero di telefono)"</string>
+    <string name="unknownName">"(Sconosciuto)"</string>
+    <string name="defaultVoiceMailAlphaTag">"Segreteria"</string>
+    <string name="defaultMsisdnAlphaTag">"MSISDN1"</string>
+    <string name="mmiError">"Problema di connessione o codice MMI non valido."</string>
+    <string name="serviceEnabled">"Il servizio è stato attivato."</string>
+    <string name="serviceEnabledFor">"Il servizio è stato attivato per:"</string>
+    <string name="serviceDisabled">"Il servizio è stato disattivato."</string>
+    <string name="serviceRegistered">"Registrazione effettuata."</string>
+    <string name="serviceErased">"Eliminazione effettuata."</string>
+    <string name="passwordIncorrect">"Password errata."</string>
+    <string name="mmiComplete">"MMI completo."</string>
+    <string name="badPin">"Il PIN attuale digitato è errato."</string>
+    <string name="badPuk">"Il PUK digitato è errato."</string>
+    <string name="mismatchPin">"I PIN inseriti non corrispondono."</string>
+    <string name="invalidPin">"Il PIN deve essere di 4-8 numeri."</string>
+    <string name="needPuk">"La SIM è bloccata tramite PUK. Digita il codice PUK per sbloccarla."</string>
+    <string name="needPuk2">"Digita il PUK2 per sbloccare la SIM."</string>
+    <string name="ClipMmi">"ID chiamante in entrata"</string>
+    <string name="ClirMmi">"ID chiamante in uscita"</string>
+    <string name="CfMmi">"Deviazione chiamate"</string>
+    <string name="CwMmi">"Avviso di chiamata"</string>
+    <string name="BaMmi">"Blocco chiamate"</string>
+    <string name="PwdMmi">"Modifica password"</string>
+    <string name="PinMmi">"Modifica PIN"</string>
+    <string name="CLIRDefaultOnNextCallOn">"ID chiamante generalmente limitato. Prossima chiamata: limitato"</string>
+    <string name="CLIRDefaultOnNextCallOff">"ID chiamante generalmente limitato. Prossima chiamata: non limitato"</string>
+    <string name="CLIRDefaultOffNextCallOn">"ID chiamante generalmente non limitato. Prossima chiamata: limitato"</string>
+    <string name="CLIRDefaultOffNextCallOff">"ID chiamante generalmente non limitato. Prossima chiamata: non limitato"</string>
+    <string name="serviceNotProvisioned">"Servizio non fornito."</string>
+    <string name="CLIRPermanent">"Impossibile modificare l\'impostazione dell\'ID del chiamante."</string>
+    <string name="serviceClassVoice">"Voce"</string>
+    <string name="serviceClassData">"Dati"</string>
+    <string name="serviceClassFAX">"FAX"</string>
+    <string name="serviceClassSMS">"SMS"</string>
+    <string name="serviceClassDataAsync">"Asinc"</string>
+    <string name="serviceClassDataSync">"Sinc"</string>
+    <string name="serviceClassPacket">"Pacchetto"</string>
+    <string name="serviceClassPAD">"PAD"</string>
+    <string name="cfTemplateNotForwarded">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: inoltro non effettuato"</string>
+    <string name="cfTemplateForwarded">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
+    <string name="cfTemplateForwardedTime">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g><xliff:g id="DIALING_NUMBER">{1}</xliff:g> dopo <xliff:g id="TIME_DELAY">{2}</xliff:g> secondi"</string>
+    <string name="cfTemplateRegistered">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: inoltro non effettuato"</string>
+    <string name="cfTemplateRegisteredTime">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: inoltro non effettuato"</string>
+    <string name="httpErrorOk">"OK"</string>
+    <string name="httpError">"La pagina web contiene un errore."</string>
+    <string name="httpErrorLookup">"Impossibile trovare l\'URL."</string>
+    <string name="httpErrorUnsupportedAuthScheme">"Schema di autenticazione del sito non supportato."</string>
+    <string name="httpErrorAuth">"Autenticazione non riuscita."</string>
+    <string name="httpErrorProxyAuth">"Autenticazione tramite il server proxy non riuscita."</string>
+    <string name="httpErrorConnect">"Connessione al server non riuscita."</string>
+    <string name="httpErrorIO">"Impossibile comunicare con il server. Riprova più tardi."</string>
+    <string name="httpErrorTimeout">"Tempo esaurito per la connessione al server."</string>
+    <string name="httpErrorRedirectLoop">"La pagina contiene troppi reindirizzamenti sul server."</string>
+    <string name="httpErrorUnsupportedScheme">"Protocollo non supportato."</string>
+    <string name="httpErrorFailedSslHandshake">"Impossibile stabilire una connessione protetta."</string>
+    <string name="httpErrorBadUrl">"Impossibile aprire la pagina. URL non valido."</string>
+    <string name="httpErrorFile">"Impossibile accedere al file."</string>
+    <string name="httpErrorFileNotFound">"Impossibile trovare il file richiesto."</string>
+    <string name="httpErrorTooManyRequests">"Troppe richieste in fase di elaborazione. Riprova più tardi."</string>
+    <string name="contentServiceSync">"Sinc"</string>
+    <string name="contentServiceSyncNotificationTitle">"Sincronizzazione"</string>
+    <string name="contentServiceTooManyDeletesNotificationDesc">"Troppe eliminazioni di <xliff:g id="CONTENT_TYPE">%s</xliff:g>."</string>
+    <string name="low_memory">"Spazio di archiviazione del telefono esaurito. Elimina alcuni file per liberare spazio."</string>
+    <string name="me">"Io"</string>
+    <string name="power_dialog">"Opzioni telefono"</string>
+    <string name="silent_mode">"Modalità silenziosa"</string>
+    <string name="turn_on_radio">"Attiva wireless"</string>
+    <string name="turn_off_radio">"Disattiva wireless"</string>
+    <string name="screen_lock">"Blocco schermo"</string>
+    <string name="power_off">"Spegni"</string>
+    <string name="shutdown_progress">"Spegnimento..."</string>
+    <string name="shutdown_confirm">"Il telefono verrà spento."</string>
+    <string name="no_recent_tasks">"Nessuna applicazione recente."</string>
+    <string name="global_actions">"Opzioni telefono"</string>
+    <string name="global_action_lock">"Blocco schermo"</string>
+    <string name="global_action_power_off">"Spegni"</string>
+    <string name="global_action_toggle_silent_mode">"Modalità silenziosa"</string>
+    <string name="global_action_silent_mode_on_status">"Audio non attivo"</string>
+    <string name="global_action_silent_mode_off_status">"Audio attivo"</string>
+    <!-- no translation found for global_actions_toggle_airplane_mode (5884330306926307456) -->
+    <skip />
+    <!-- no translation found for global_actions_airplane_mode_on_status (2719557982608919750) -->
+    <skip />
+    <!-- no translation found for global_actions_airplane_mode_off_status (5075070442854490296) -->
+    <skip />
+    <string name="safeMode">"Modalità provvisoria"</string>
+    <!-- no translation found for android_system_label (6577375335728551336) -->
+    <skip />
+    <string name="permgrouplab_costMoney">"Servizi che prevedono un costo"</string>
+    <string name="permgroupdesc_costMoney">"Consentono alle applicazioni di svolgere operazioni che possono comportare un costo."</string>
+    <string name="permgrouplab_messages">"I tuoi messaggi"</string>
+    <string name="permgroupdesc_messages">"Leggere e scrivere SMS, email e altri messaggi."</string>
+    <string name="permgrouplab_personalInfo">"Informazioni personali"</string>
+    <string name="permgroupdesc_personalInfo">"Accedere direttamente ai contatti e al calendario memorizzati sul telefono."</string>
+    <string name="permgrouplab_location">"La tua posizione"</string>
+    <string name="permgroupdesc_location">"Monitorare la posizione fisica dell\'utente"</string>
+    <string name="permgrouplab_network">"Comunicazione di rete"</string>
+    <string name="permgroupdesc_network">"Consentono l\'accesso delle applicazioni a varie funzionalità di rete."</string>
+    <string name="permgrouplab_accounts">"I tuoi account Google"</string>
+    <string name="permgroupdesc_accounts">"Accedere agli account Google disponibili."</string>
+    <string name="permgrouplab_hardwareControls">"Controlli hardware"</string>
+    <string name="permgroupdesc_hardwareControls">"Accedere direttamente all\'hardware del ricevitore."</string>
+    <string name="permgrouplab_phoneCalls">"Telefonate"</string>
+    <string name="permgroupdesc_phoneCalls">"Monitorare, registrare ed elaborare le telefonate."</string>
+    <string name="permgrouplab_systemTools">"Strumenti di sistema"</string>
+    <string name="permgroupdesc_systemTools">"Accesso al sistema e controllo di livello inferiore."</string>
+    <string name="permgrouplab_developmentTools">"Strumenti di sviluppo"</string>
+    <string name="permgroupdesc_developmentTools">"Funzionalità necessarie soltanto agli sviluppatori di applicazioni."</string>
+    <string name="permlab_statusBar">"disattivare o modificare la barra di stato"</string>
+    <string name="permdesc_statusBar">"Consente all\'applicazione di disattivare la barra di stato o di aggiungere e rimuovere icone di sistema."</string>
+    <string name="permlab_expandStatusBar">"espansione/compressione barra di stato"</string>
+    <string name="permdesc_expandStatusBar">"Consente all\'applicazione di espandere o comprimere la barra di stato."</string>
+    <string name="permlab_processOutgoingCalls">"intercettazione chiamate in uscita"</string>
+    <string name="permdesc_processOutgoingCalls">"Consente all\'applicazione di elaborare le chiamate in uscita e di modificare il numero da comporre. Le applicazioni dannose potrebbero monitorare, deviare o impedire le chiamate in uscita."</string>
+    <string name="permlab_receiveSms">"ricezione SMS"</string>
+    <string name="permdesc_receiveSms">"Consente il ricevimento e l\'elaborazione di SMS da parte dell\'applicazione. Le applicazioni dannose potrebbero monitorare i messaggi o eliminarli senza visualizzarli."</string>
+    <string name="permlab_receiveMms">"ricezione MMS"</string>
+    <string name="permdesc_receiveMms">"Consente il ricevimento e l\'elaborazione di MMS da parte dell\'applicazione. Le applicazioni dannose potrebbero monitorare i messaggi o eliminarli senza visualizzarli."</string>
+    <string name="permlab_sendSms">"invio SMS"</string>
+    <string name="permdesc_sendSms">"Consente all\'applicazione di inviare messaggi SMS. Le applicazioni dannose potrebbero inviare messaggi a tua insaputa facendoti sostenere dei costi."</string>
+    <string name="permlab_readSms">"lettura SMS o MMS"</string>
+    <string name="permdesc_readSms">"Consente all\'applicazione di leggere SMS memorizzati sul telefono o sulla SIM. Le applicazioni dannose potrebbero leggere messaggi riservati."</string>
+    <string name="permlab_writeSms">"modifica SMS o MMS"</string>
+    <string name="permdesc_writeSms">"Consente all\'applicazione di rispondere a SMS memorizzati sul telefono o sulla SIM. Le applicazioni dannose potrebbero eliminare i messaggi."</string>
+    <string name="permlab_receiveWapPush">"ricezione WAP"</string>
+    <string name="permdesc_receiveWapPush">"Consente il ricevimento e l\'elaborazione di messaggi WAP da parte dell\'applicazione. Le applicazioni dannose potrebbero monitorare i messaggi o eliminarli senza visualizzarli."</string>
+    <string name="permlab_getTasks">"recupero applicazioni in esecuzione"</string>
+    <string name="permdesc_getTasks">"Consente all\'applicazione di recuperare informazioni sulle attività in esecuzione ed eseguite di recente. Le applicazioni dannose potrebbero essere in grado di scoprire informazioni riservate su altre applicazioni."</string>
+    <string name="permlab_reorderTasks">"riordinamento applicazioni in esecuz."</string>
+    <string name="permdesc_reorderTasks">"Consente a un\'applicazione di spostare attività in primo e secondo piano. Le applicazioni dannose possono imporsi ponendosi automaticamente in primo piano."</string>
+    <string name="permlab_setDebugApp">"attivazione debug delle applicazioni"</string>
+    <string name="permdesc_setDebugApp">"Consente a un\'applicazione di attivare il debug per un\'altra applicazione. Le applicazioni dannose possono sfruttare questa possibilità per interrompere altre applicazioni."</string>
+    <string name="permlab_changeConfiguration">"modifica impostazioni UI"</string>
+    <string name="permdesc_changeConfiguration">"Consente a un\'applicazione di modificare la configurazione corrente, come le dimensioni dei caratteri locali o complessive."</string>
+    <string name="permlab_restartPackages">"riavvio altre applicazioni"</string>
+    <string name="permdesc_restartPackages">"Consente a un\'applicazione di riavviare forzatamente altre applicazioni."</string>
+    <string name="permlab_setProcessForeground">"impedire l\'interruzione"</string>
+    <string name="permdesc_setProcessForeground">"Consente a un\'applicazione di eseguire i processi in primo piano in modo che non possano essere interrotti. Non dovrebbe essere mai necessario per le normali applicazioni."</string>
+    <string name="permlab_forceBack">"chiusura forzata dell\'applicazione"</string>
+    <string name="permdesc_forceBack">"Consente a un\'applicazione di forzare la chiusura di attività in primo piano. Non dovrebbe essere mai necessario per le normali applicazioni."</string>
+    <string name="permlab_dump">"recupero stato interno del sistema"</string>
+    <string name="permdesc_dump">"Consente all\'applicazione di recuperare lo stato interno del sistema. Le applicazioni dannose potrebbero recuperare molte informazioni riservate e protette di cui non dovrebbero avere mai bisogno."</string>
+    <string name="permlab_addSystemService">"pubblicaz. servizi di basso livello"</string>
+    <string name="permdesc_addSystemService">"Consente a un\'applicazione di pubblicare i suoi servizi di sistema di basso livello. Le applicazioni dannose potrebbero assumere il controllo del sistema e impossessarsi di dati o danneggiarli."</string>
+    <string name="permlab_runSetActivityWatcher">"monitoraggio e controllo avvio applicazioni"</string>
+    <string name="permdesc_runSetActivityWatcher">"Consente a un\'applicazione di monitorare e controllare la modalità di avvio delle attività nel sistema. Le applicazioni dannose potrebbero compromettere totalmente il sistema. Questa autorizzazione è necessaria soltanto per lo sviluppo, mai per il normale utilizzo del telefono."</string>
+    <string name="permlab_broadcastPackageRemoved">"invio broadcast rimossi dal pacchetto"</string>
+    <string name="permdesc_broadcastPackageRemoved">"Consente a un\'applicazione di trasmettere una notifica di rimozione del pacchetto di un\'applicazione. Le applicazioni dannose potrebbero sfruttare questa possibilità per interrompere ogni altra applicazione in esecuzione."</string>
+    <string name="permlab_broadcastSmsReceived">"invio broadcast ricevuti tramite SMS"</string>
+    <string name="permdesc_broadcastSmsReceived">"Consente a un\'applicazione di trasmettere una notifica di ricevimento di un SMS. Le applicazioni dannose potrebbero sfruttare questa possibilità per far credere che siano stati ricevuti SMS."</string>
+    <string name="permlab_broadcastWapPush">"invio broadcast ricevuti tramite WAP-PUSH"</string>
+    <string name="permdesc_broadcastWapPush">"Consente a un\'applicazione di trasmettere una notifica di ricevimento di un messaggio WAP PUSH. Le applicazioni dannose potrebbero sfruttare questa possibilità per far credere che sia stato ricevuto un MMS o per sostituire automaticamente contenuti di pagine web con varianti dannose."</string>
+    <string name="permlab_setProcessLimit">"numero limite di processi in esecuzione"</string>
+    <string name="permdesc_setProcessLimit">"Consente a un\'applicazione di stabilire il numero massimo di processi in esecuzione. Mai necessario per le normali applicazioni."</string>
+    <string name="permlab_setAlwaysFinish">"chiusura applicazioni in background"</string>
+    <string name="permdesc_setAlwaysFinish">"Consente a un\'applicazione di controllare se le attività sono sempre completate quando vengono messe in secondo piano. Mai necessario per le normali applicazioni."</string>
+    <string name="permlab_fotaUpdate">"installazione autom. aggiornamenti di sistema"</string>
+    <string name="permdesc_fotaUpdate">"Consente a un\'applicazione di ricevere notifiche sugli aggiornamenti del sistema in sospeso e di attivarne l\'installazione. Le applicazioni dannose possono sfruttare questa possibilità per danneggiare il sistema con aggiornamenti non autorizzati, o interferire con il processo di aggiornamento."</string>
+    <string name="permlab_batteryStats">"modifica statistiche batteria"</string>
+    <string name="permdesc_batteryStats">"Consente la modifica delle statistiche sulla batteria raccolte. Da non usare per normali applicazioni."</string>
+    <string name="permlab_internalSystemWindow">"visualizzazione finestre non autorizzate"</string>
+    <string name="permdesc_internalSystemWindow">"Consente la creazione di finestre destinate all\'uso nell\'interfaccia utente di sistema interna. Da non usare per normali applicazioni."</string>
+    <string name="permlab_systemAlertWindow">"visualizzazione avvisi di sistema"</string>
+    <string name="permdesc_systemAlertWindow">"Consente a un\'applicazione di visualizzare finestre di avviso del sistema. Le applicazioni dannose possono sfruttare questa opzione per riempire lo schermo del telefono di messaggi."</string>
+    <string name="permlab_setAnimationScale">"modifica velocità di animazione globale"</string>
+    <string name="permdesc_setAnimationScale">"Consente a un\'applicazione di modificare la velocità di animazione globale (animazioni più veloci o più lente) in qualsiasi momento."</string>
+    <string name="permlab_manageAppTokens">"gestione token applicazioni"</string>
+    <string name="permdesc_manageAppTokens">"Consente alle applicazioni di creare e gestire i propri token, ignorando il normale ordinamento Z. Non dovrebbe essere mai necessario per le normali applicazioni."</string>
+    <string name="permlab_injectEvents">"uso tasti e pulsanti di controllo"</string>
+    <string name="permdesc_injectEvents">"Consente a un\'applicazione di offrire i suoi eventi di input (pressioni di tasti etc.) ad altre applicazioni. Le applicazioni dannose possono sfruttare questa possibilità per assumere il controllo del telefono."</string>
+    <string name="permlab_readInputState">"registrazione testo digitato e azioni eseguite"</string>
+    <string name="permdesc_readInputState">"Consente il rilevamento da parte delle applicazioni dei tasti premuti anche durante l\'interazione con un\'altra applicazione (come nel caso di inserimento di una password). Non dovrebbe essere mai necessario per le normali applicazioni."</string>
+    <string name="permlab_bindInputMethod">"associaz. a un metodo di inserimento"</string>
+    <string name="permdesc_bindInputMethod">"Consente l\'associazione all\'interfaccia principale di un metodo di inserimento. Non dovrebbe essere mai necessario per le normali applicazioni."</string>
+    <string name="permlab_setOrientation">"modifica orientamento dello schermo"</string>
+    <string name="permdesc_setOrientation">"Consente a un\'applicazione di cambiare la rotazione dello schermo in qualsiasi momento. Non dovrebbe essere mai necessario per le normali applicazioni."</string>
+    <string name="permlab_signalPersistentProcesses">"invio segnali Linuz alle applicazioni"</string>
+    <string name="permdesc_signalPersistentProcesses">"Consente all\'applicazione di richiedere l\'invio del segnale fornito a tutti i processi persistenti."</string>
+    <string name="permlab_persistentActivity">"esecuzione permanente delle applicazioni"</string>
+    <string name="permdesc_persistentActivity">"Consente a un\'applicazione di rendere delle sue parti costanti in modo che il sistema non possa usarla per altre applicazioni."</string>
+    <string name="permlab_deletePackages">"eliminazione applicazioni"</string>
+    <string name="permdesc_deletePackages">"Consente a un\'applicazione di eliminare pacchetti Android. Le applicazioni dannose possono sfruttare questa possibilità per eliminare importanti applicazioni."</string>
+    <string name="permlab_clearAppUserData">"eliminazione dati di altre applicazioni"</string>
+    <string name="permdesc_clearAppUserData">"Consente a un\'applicazione di cancellare dati dell\'utente."</string>
+    <string name="permlab_deleteCacheFiles">"eliminazione cache altre applicazioni"</string>
+    <string name="permdesc_deleteCacheFiles">"Consente a un\'applicazione di eliminare file della cache."</string>
+    <string name="permlab_getPackageSize">"calcolo spazio di archiviazione applicazioni"</string>
+    <string name="permdesc_getPackageSize">"Consente a un\'applicazione di recuperare i suoi codici, dati e dimensioni della cache"</string>
+    <string name="permlab_installPackages">"installazione diretta di applicazioni"</string>
+    <string name="permdesc_installPackages">"Consente a un\'applicazione di installare nuovi pacchetti Android o aggiornamenti. Le applicazioni dannose possono sfruttare questa possibilità per aggiungere nuove applicazioni con potenti autorizzazioni arbitrarie."</string>
+    <string name="permlab_clearAppCache">"eliminazione dati della cache applicazioni"</string>
+    <string name="permdesc_clearAppCache">"Consente a un\'applicazione di liberare spazio sul telefono eliminando file nella directory della cache dell\'applicazione. L\'accesso è generalmente limitato a processi di sistema."</string>
+    <string name="permlab_readLogs">"lettura file di registro sistema"</string>
+    <string name="permdesc_readLogs">"Consente a un\'applicazione di leggere vari file di registro del sistema per trovare informazioni generali sulle operazioni effettuate con il telefono. Tali file non dovrebbero contenere informazioni personali o riservate."</string>
+    <string name="permlab_diagnostic">"lettura/scrittura risorse di proprietà di diag"</string>
+    <string name="permdesc_diagnostic">"Consente a un\'applicazione di leggere le risorse del gruppo diag e scrivere a esse, per esempio i file in /dev. Questa capacità potrebbe influire sulla stabilità e sicurezza del sistema. Dovrebbe essere utilizzata SOLTANTO per diagnostiche specifiche dell\'hardware effettuate dal produttore o dall\'operatore."</string>
+    <string name="permlab_changeComponentState">"attivazione/disattivazione componenti applicazioni"</string>
+    <string name="permdesc_changeComponentState">"Consente a un\'applicazione di attivare o disattivare un componente di un\'altra applicazione. Le applicazioni dannose possono sfruttare questa possibilità per disattivare importanti funzionalità del telefono. Prestare attenzione con questa autorizzazione perché è possibile rendere inutilizzabili, incoerenti o instabili i componenti delle applicazioni."</string>
+    <string name="permlab_setPreferredApplications">"impostazione applicazioni preferite"</string>
+    <string name="permdesc_setPreferredApplications">"Consente la modifica da parte di un\'applicazione delle applicazioni preferite. Le applicazioni dannose potrebbero essere in grado di modificare automaticamente le applicazioni in esecuzione, effettuando lo spoofing delle applicazioni esistenti per raccogliere dati riservati."</string>
+    <string name="permlab_writeSettings">"modifica impostazioni di sistema globali"</string>
+    <string name="permdesc_writeSettings">"Consente la modifica in un\'applicazione dei dati delle impostazioni del sistema. Le applicazioni dannose possono danneggiare la configurazione del sistema."</string>
+    <string name="permlab_writeSecureSettings">"modificare le impostazioni di protezione del sistema"</string>
+    <string name="permdesc_writeSecureSettings">"Consente a un\'applicazione di modificare i dati delle impostazioni di protezione del sistema. Da non usare per normali applicazioni."</string>
+    <string name="permlab_writeGservices">"modifica mappa servizi Google"</string>
+    <string name="permdesc_writeGservices">"Consente a un\'applicazione di modificare la mappa dei servizi Google. Da non usare per normali applicazioni."</string>
+    <string name="permlab_receiveBootCompleted">"apertura automatica all\'avvio"</string>
+    <string name="permdesc_receiveBootCompleted">"Consente a un\'applicazione di aprirsi automaticamente al termine dell\'avvio del sistema. Potrebbe essere necessario più tempo per l\'avvio del telefono e l\'applicazione potrebbe rallentare tutte le funzioni del telefono rimanendo sempre in esecuzione."</string>
+    <string name="permlab_broadcastSticky">"invio broadcast permanenti"</string>
+    <string name="permdesc_broadcastSticky">"Consente a un\'applicazione di inviare broadcast permanenti, che permangono anche al termine del broadcast. Le applicazioni dannose possono rendere il telefono lento o instabile tramite un uso eccessivo della memoria."</string>
+    <string name="permlab_readContacts">"lettura dati di contatto"</string>
+    <string name="permdesc_readContacts">"Consente la lettura da parte di un\'applicazione di tutti i dati (gli indirizzi) di contatto memorizzati sul telefono. Le applicazioni dannose possono sfruttare questa possibilità per inviare i dati ad altre persone."</string>
+    <string name="permlab_writeContacts">"scrittura dati di contatto"</string>
+    <string name="permdesc_writeContacts">"Consente a un\'applicazione di modificare i dati (gli indirizzi) di contatto memorizzati sul telefono. Le applicazioni dannose possono sfruttare questa possibilità per cancellare o modificare i dati di contatto."</string>
+    <string name="permlab_writeOwnerData">"scrittura dati proprietario"</string>
+    <string name="permdesc_writeOwnerData">"Consente a un\'applicazione di modificare i dati del proprietario del telefono memorizzati sul telefono. Le applicazioni dannose possono sfruttare questa possibilità per cancellare o modificare tali dati."</string>
+    <string name="permlab_readOwnerData">"lettura dati proprietario"</string>
+    <string name="permdesc_readOwnerData">"Consente a un\'applicazione di leggere i dati del proprietario del telefono memorizzati sul telefono. Le applicazioni dannose possono sfruttare questa possibilità per leggere tali dati."</string>
+    <string name="permlab_readCalendar">"lettura dati di calendario"</string>
+    <string name="permdesc_readCalendar">"Consente la lettura da parte di un\'applicazione di tutti gli eventi di calendario memorizzati sul telefono. Le applicazioni dannose possono sfruttare questa possibilità per inviare i tuoi eventi di calendario ad altre persone."</string>
+    <string name="permlab_writeCalendar">"scrittura dati di calendario"</string>
+    <string name="permdesc_writeCalendar">"Consente a un\'applicazione di modificare gli eventi di calendario memorizzati sul telefono. Le applicazioni dannose possono sfruttare questa possibilità per cancellare o modificare i dati del calendario."</string>
+    <string name="permlab_accessMockLocation">"fonti di localizzazione fittizie per test"</string>
+    <string name="permdesc_accessMockLocation">"Creare fonti di localizzazione fittizie per test. Le applicazioni dannose possono sfruttare questa possibilità per sostituire la posizione e/o lo stato restituito da reali fonti di localizzazione come GPS o provider di rete."</string>
+    <string name="permlab_accessLocationExtraCommands">"accesso a comandi aggiuntivi del provider di localizz."</string>
+    <string name="permdesc_accessLocationExtraCommands">"Accedere a comandi aggiuntivi del provider di localizzazione. Le applicazioni dannose possono sfruttare questa possibilità per interferire con il funzionamento del GPS o di altre fonti di localizzazione."</string>
+    <string name="permlab_accessFineLocation">"localizzazione precisa (GPS)"</string>
+    <string name="permdesc_accessFineLocation">"Consente l\'accesso a fonti di localizzazione precisa, come il sistema GPS del telefono, se disponibile. Le applicazioni dannose possono sfruttare questa possibilità per determinare la tua posizione e, nel farlo, far esaurire più in fretta la batteria."</string>
+    <string name="permlab_accessCoarseLocation">"localizzazione approssimativa (basata sulla rete)"</string>
+    <string name="permdesc_accessCoarseLocation">"Consente l\'accesso a fonti di localizzazione geografica non puntuale (come il database della rete cellulare) per determinare una posizione approssimativa del telefono, quando possibile. Le applicazioni dannose possono sfruttare questa possibilità per determinare approssimativamente dove ti trovi."</string>
+    <string name="permlab_accessSurfaceFlinger">"accesso a SurfaceFlinger"</string>
+    <string name="permdesc_accessSurfaceFlinger">"Consente l\'utilizzo dell\'applicazione di funzioni di basso livello SurfaceFlinger."</string>
+    <string name="permlab_readFrameBuffer">"lettura buffer di frame"</string>
+    <string name="permdesc_readFrameBuffer">"Consente la lettura da parte dell\'applicazione dei contenuti del buffer di frame."</string>
+    <string name="permlab_modifyAudioSettings">"modifica impostazioni audio"</string>
+    <string name="permdesc_modifyAudioSettings">"Consente all\'applicazione di modificare impostazioni audio globali come volume e routing."</string>
+    <string name="permlab_recordAudio">"registrazione audio"</string>
+    <string name="permdesc_recordAudio">"Consente l\'accesso dell\'applicazione al percorso di registrazione dell\'audio."</string>
+    <string name="permlab_camera">"acquisizione foto"</string>
+    <string name="permdesc_camera">"Consente di scattare foto nell\'applicazione con la fotocamera. L\'applicazione può acquisire in qualsiasi momento le immagini rilevate dalla fotocamera."</string>
+    <string name="permlab_brick">"disattivazione telefono"</string>
+    <string name="permdesc_brick">"Consente all\'applicazione di disattivare l\'intero telefono in modo definitivo. Questa autorizzazione è molto pericolosa."</string>
+    <string name="permlab_reboot">"riavvio forzato del telefono"</string>
+    <string name="permdesc_reboot">"Consente all\'applicazione di imporre il riavvio del telefono."</string>
+    <string name="permlab_mount_unmount_filesystems">"installazione/disinstallazione filesystem"</string>
+    <string name="permdesc_mount_unmount_filesystems">"Consente montaggio e smontaggio da parte dell\'applicazione dei filesystem degli archivi rimovibili."</string>
+    <string name="permlab_mount_format_filesystems">"formattazione archivio esterno"</string>
+    <string name="permdesc_mount_format_filesystems">"Consente all\'applicazione di formattare l\'archivio rimovibile."</string>
+    <string name="permlab_vibrate">"controllo vibrazione"</string>
+    <string name="permdesc_vibrate">"Consente all\'applicazione di controllare la vibrazione."</string>
+    <string name="permlab_flashlight">"controllo flash"</string>
+    <string name="permdesc_flashlight">"Consente all\'applicazione di controllare il flash."</string>
+    <string name="permlab_hardware_test">"esecuzione test hardware"</string>
+    <string name="permdesc_hardware_test">"Consente all\'applicazione di controllare varie periferiche per il test dell\'hardware."</string>
+    <string name="permlab_callPhone">"chiamata diretta n. telefono"</string>
+    <string name="permdesc_callPhone">"Consente all\'applicazione di chiamare numeri automaticamente. Le applicazioni dannose potrebbero far risultare chiamate impreviste sulla bolletta telefonica. Questa autorizzazione non consente all\'applicazione di chiamare numeri di emergenza."</string>
+    <string name="permlab_callPrivileged">"chiamata diretta di tutti i n. telefono"</string>
+    <string name="permdesc_callPrivileged">"Consente all\'applicazione di chiamare qualsiasi numero, compresi quelli di emergenza, automaticamente. Le applicazioni dannose potrebbero effettuare chiamate non necessarie e illegali a servizi di emergenza."</string>
+    <string name="permlab_locationUpdates">"controllo notifiche aggiornamento posizione"</string>
+    <string name="permdesc_locationUpdates">"Consente l\'attivazione/disattivazione delle notifiche di aggiornamento della posizione dal segnale cellulare. Da non usare per normali applicazioni."</string>
+    <string name="permlab_checkinProperties">"accesso a proprietà di archiviazione"</string>
+    <string name="permdesc_checkinProperties">"Consente l\'accesso di lettura/scrittura alle proprietà caricate dal servizio di archiviazione. Da non usare per normali applicazioni."</string>
+    <string name="permlab_bindGadget">"scegliere gadget"</string>
+    <string name="permdesc_bindGadget">"Consente all\'applicazione di indicare al sistema quali gadget possono essere utilizzati e da quale applicazione. Con questa autorizzazione, le applicazioni possono consentire ad altre applicazioni di accedere a dati personali. Da non usare per normali applicazioni."</string>
+    <string name="permlab_modifyPhoneState">"modifica stato del telefono"</string>
+    <string name="permdesc_modifyPhoneState">"Consente all\'applicazione di controllare le funzioni telefoniche del dispositivo. Un\'applicazione con questa autorizzazione può cambiare rete, attivare e disattivare il segnale cellulare e così via, senza alcuna notifica."</string>
+    <string name="permlab_readPhoneState">"lettura stato del telefono"</string>
+    <string name="permdesc_readPhoneState">"Consente l\'accesso dell\'applicazione alle funzioni telefoniche del dispositivo. Un\'applicazione con questa autorizzazione può determinare il numero del telefono in uso, se una chiamata è attiva o meno, il numero a cui è collegata la chiamata e simili."</string>
+    <string name="permlab_wakeLock">"disattivazione stand-by del telefono"</string>
+    <string name="permdesc_wakeLock">"Consente a un\'applicazione di impedire lo stand-by del telefono."</string>
+    <string name="permlab_devicePower">"accensione o spegnimento del telefono"</string>
+    <string name="permdesc_devicePower">"Consente all\'applicazione di accendere o spegnere il telefono."</string>
+    <string name="permlab_factoryTest">"esecuzione in modalità test di fabbrica"</string>
+    <string name="permdesc_factoryTest">"In esecuzione come test del produttore di basso livello, consentendo l\'accesso completo all\'hardware del telefono. Disponibile soltanto quando il telefono è in esecuzione in modalità test del produttore."</string>
+    <string name="permlab_setWallpaper">"impostazione sfondo"</string>
+    <string name="permdesc_setWallpaper">"Consente all\'applicazione di impostare lo sfondo del sistema."</string>
+    <string name="permlab_setWallpaperHints">"impostaz. suggerimenti dimensioni sfondo"</string>
+    <string name="permdesc_setWallpaperHints">"Consente all\'applicazione di impostare i suggerimenti per le dimensioni dello sfondo del sistema."</string>
+    <string name="permlab_masterClear">"ripristino impostazioni predef. di fabbrica"</string>
+    <string name="permdesc_masterClear">"Consente a un\'applicazione di ripristinare le impostazioni di fabbrica del sistema, eliminando tutti i dati, le configurazioni e le applicazioni installate."</string>
+    <string name="permlab_setTimeZone">"impostazione fuso orario"</string>
+    <string name="permdesc_setTimeZone">"Consente a un\'applicazione di modificare il fuso orario del telefono."</string>
+    <string name="permlab_getAccounts">"rilevamento account noti"</string>
+    <string name="permdesc_getAccounts">"Consente a un\'applicazione di recuperare l\'elenco di account memorizzati sul telefono."</string>
+    <string name="permlab_accessNetworkState">"visualizzazione stato della rete"</string>
+    <string name="permdesc_accessNetworkState">"Consente a un\'applicazione di visualizzare lo stato di tutte le reti."</string>
+    <string name="permlab_createNetworkSockets">"accesso completo a Internet"</string>
+    <string name="permdesc_createNetworkSockets">"Consente a un\'applicazione di creare socket di rete."</string>
+    <string name="permlab_writeApnSettings">"scrittura impostazioni APN"</string>
+    <string name="permdesc_writeApnSettings">"Consente a un\'applicazione di modificare le impostazioni APN, come proxy e porta di qualsiasi APN."</string>
+    <string name="permlab_changeNetworkState">"modifica connettività di rete"</string>
+    <string name="permdesc_changeNetworkState">"Consente a un\'applicazione di modificare lo stato di connettività di rete."</string>
+    <string name="permlab_changeBackgroundDataSetting">"cambiare l\'impostazione di utilizzo dei dati in background"</string>
+    <string name="permdesc_changeBackgroundDataSetting">"Consente a un\'applicazione di cambiare l\'impostazione di utilizzo dei dati in background."</string>
+    <string name="permlab_accessWifiState">"visualizzazione stato Wi-Fi"</string>
+    <string name="permdesc_accessWifiState">"Consente a un\'applicazione di visualizzare le informazioni relative allo stato della connessione Wi-Fi."</string>
+    <string name="permlab_changeWifiState">"modifica stato Wi-Fi"</string>
+    <string name="permdesc_changeWifiState">"Consente a un\'applicazione di connettersi/disconnettersi da punti di accesso Wi-Fi e di apportare modifiche alle reti Wi-Fi configurate."</string>
+    <string name="permlab_bluetoothAdmin">"gestione Bluetooth"</string>
+    <string name="permdesc_bluetoothAdmin">"Consente a un\'applicazione di configurare il telefono Bluetooth locale e di rilevare e abbinare dispositivi remoti."</string>
+    <string name="permlab_bluetooth">"creazione connessioni Bluetooth"</string>
+    <string name="permdesc_bluetooth">"Consente a un\'applicazione di visualizzare la configurazione del telefono Bluetooth locale e di stabilire e accettare connessioni con dispositivi associati."</string>
+    <string name="permlab_disableKeyguard">"disattivazione blocco tastiera"</string>
+    <string name="permdesc_disableKeyguard">"Consente la disattivazione da parte di un\'applicazione del blocco tastiera e di eventuali protezioni tramite password associate. Un valido esempio è la disattivazione da parte del telefono del blocco tastiera quando riceve una telefonata in entrata, e la successiva riattivazione del blocco al termine della chiamata."</string>
+    <string name="permlab_readSyncSettings">"lettura impostazioni di sincronizz."</string>
+    <string name="permdesc_readSyncSettings">"Consente a un\'applicazione di leggere le impostazioni di sincronizzazione, come l\'attivazione o meno della sincronizzazione per Contatti."</string>
+    <string name="permlab_writeSyncSettings">"scrittura impostazioni di sincronizz."</string>
+    <string name="permdesc_writeSyncSettings">"Consente a un\'applicazione di modificare le impostazioni di sincronizzazione, come l\'attivazione o meno della sincronizzazione per Contatti."</string>
+    <string name="permlab_readSyncStats">"lettura statistiche di sincronizz."</string>
+    <string name="permdesc_readSyncStats">"Consente a un\'applicazione di leggere le statistiche di sincronizzazione, per esempio la cronologia delle sincronizzazioni effettuate."</string>
+    <string name="permlab_subscribedFeedsRead">"lettura feed sottoscritti"</string>
+    <string name="permdesc_subscribedFeedsRead">"Consente a un\'applicazione di ottenere dettagli sui feed attualmente sincronizzati."</string>
+    <string name="permlab_subscribedFeedsWrite">"scrittura feed sottoscritti"</string>
+    <string name="permdesc_subscribedFeedsWrite">"Consente la modifica da parte di un\'applicazione dei feed attualmente sincronizzati. Le applicazioni dannose potrebbero essere in grado di modificare i feed sincronizzati."</string>
+    <string name="permlab_readDictionary">"lettura dizionario definito dall\'utente"</string>
+    <string name="permdesc_readDictionary">"Consente a un\'applicazione di leggere parole, nomi e frasi private che l\'utente potrebbe aver memorizzato nel dizionario utente."</string>
+    <string name="permlab_writeDictionary">"scrittura nel dizionario definito dall\'utente"</string>
+    <string name="permdesc_writeDictionary">"Consente a un\'applicazione di scrivere nuove parole nel dizionario utente."</string>
+  <string-array name="phoneTypes">
+    <item>"Casa"</item>
+    <item>"Cellulare"</item>
+    <item>"Ufficio"</item>
+    <item>"Fax ufficio"</item>
+    <item>"Fax casa"</item>
+    <item>"Cercapersone"</item>
+    <item>"Altro"</item>
+    <item>"Personalizzato"</item>
+  </string-array>
+  <string-array name="emailAddressTypes">
+    <item>"Casa"</item>
+    <item>"Ufficio"</item>
+    <item>"Altro"</item>
+    <item>"Personalizzato"</item>
+  </string-array>
+  <string-array name="postalAddressTypes">
+    <item>"Casa"</item>
+    <item>"Ufficio"</item>
+    <item>"Altro"</item>
+    <item>"Personalizzato"</item>
+  </string-array>
+  <string-array name="imAddressTypes">
+    <item>"Casa"</item>
+    <item>"Uffico"</item>
+    <item>"Altro"</item>
+    <item>"Personalizzato"</item>
+  </string-array>
+  <string-array name="organizationTypes">
+    <item>"Ufficio"</item>
+    <item>"Altro"</item>
+    <item>"Personalizzato"</item>
+  </string-array>
+  <string-array name="imProtocols">
+    <item>"AIM"</item>
+    <item>"Windows Live"</item>
+    <item>"Yahoo"</item>
+    <item>"Skype"</item>
+    <item>"QQ"</item>
+    <item>"Google Talk"</item>
+    <item>"ICQ"</item>
+    <item>"Jabber"</item>
+  </string-array>
+    <string name="keyguard_password_enter_pin_code">"Inserisci il PIN"</string>
+    <string name="keyguard_password_wrong_pin_code">"Codice PIN errato."</string>
+    <string name="keyguard_label_text">"Per sbloccare, premi Menu, poi 0."</string>
+    <string name="emergency_call_dialog_number_for_display">"Numero di emergenza"</string>
+    <string name="lockscreen_carrier_default">"(Nessun servizio)"</string>
+    <string name="lockscreen_screen_locked">"Schermo bloccato."</string>
+    <string name="lockscreen_instructions_when_pattern_enabled">"Premi Menu per sbloccare o effettuare chiamate di emergenza."</string>
+    <string name="lockscreen_instructions_when_pattern_disabled">"Premi Menu per sbloccare."</string>
+    <string name="lockscreen_pattern_instructions">"Traccia la sequenza di sblocco"</string>
+    <string name="lockscreen_emergency_call">"Chiamata di emergenza"</string>
+    <string name="lockscreen_pattern_correct">"Corretta."</string>
+    <string name="lockscreen_pattern_wrong">"Riprova"</string>
+    <!-- no translation found for lockscreen_plugged_in (613343852842944435) -->
+    <skip />
+    <string name="lockscreen_low_battery">"Collegare il caricabatterie."</string>
+    <string name="lockscreen_missing_sim_message_short">"Nessuna SIM presente."</string>
+    <string name="lockscreen_missing_sim_message">"Nessuna SIM presente nel telefono."</string>
+    <string name="lockscreen_missing_sim_instructions">"Inserisci una SIM."</string>
+    <string name="lockscreen_network_locked_message">"Rete bloccata"</string>
+    <string name="lockscreen_sim_puk_locked_message">"La SIM è bloccata tramite PUK."</string>
+    <string name="lockscreen_sim_puk_locked_instructions">"Contatta il servizio clienti."</string>
+    <string name="lockscreen_sim_locked_message">"La SIM è bloccata."</string>
+    <string name="lockscreen_sim_unlock_progress_dialog_message">"Sblocco SIM..."</string>
+    <string name="lockscreen_too_many_failed_attempts_dialog_message">"<xliff:g id="NUMBER_0">%d</xliff:g> tentativi di inserimento della sequenza di sblocco. "\n\n"Riprova fra <xliff:g id="NUMBER_1">%d</xliff:g> secondi."</string>
+    <string name="lockscreen_failed_attempts_almost_glogin">"<xliff:g id="NUMBER_0">%d</xliff:g> tentativi di inserimento della sequenza di sblocco. Dopo altri <xliff:g id="NUMBER_1">%d</xliff:g> tentativi falliti, ti verrà chiesto di sbloccare il telefono tramite i dati di accesso di Google."\n\n"Riprova fra <xliff:g id="NUMBER_2">%d</xliff:g> secondi."</string>
+    <string name="lockscreen_too_many_failed_attempts_countdown">"Riprova fra <xliff:g id="NUMBER">%d</xliff:g> secondi."</string>
+    <string name="lockscreen_forgot_pattern_button_text">"Hai dimenticato la sequenza?"</string>
+    <string name="lockscreen_glogin_too_many_attempts">"Troppi tentativi di inserimento della sequenza."</string>
+    <string name="lockscreen_glogin_instructions">"Per sbloccare,"\n"accedi tramite il tuo account Google"</string>
+    <string name="lockscreen_glogin_username_hint">"Nome utente (email)"</string>
+    <string name="lockscreen_glogin_password_hint">"Password"</string>
+    <string name="lockscreen_glogin_submit_button">"Accedi"</string>
+    <string name="lockscreen_glogin_invalid_input">"Password o nome utente non valido."</string>
+    <string name="status_bar_time_format">"<xliff:g id="HOUR">h</xliff:g>:<xliff:g id="MINUTE">mm</xliff:g> <xliff:g id="AMPM">AA</xliff:g>"</string>
+    <string name="hour_minute_ampm">"<xliff:g id="HOUR">%-l</xliff:g>:<xliff:g id="MINUTE">%M</xliff:g><xliff:g id="AMPM">%P</xliff:g>"</string>
+    <string name="hour_minute_cap_ampm">"<xliff:g id="HOUR">%-l</xliff:g>:<xliff:g id="MINUTE">%M</xliff:g><xliff:g id="AMPM">%p</xliff:g>"</string>
+    <!-- no translation found for hour_ampm (4329881288269772723) -->
+    <skip />
+    <!-- no translation found for hour_cap_ampm (1829009197680861107) -->
+    <skip />
+    <string name="status_bar_clear_all_button">"Cancella notifiche"</string>
+    <string name="status_bar_no_notifications_title">"Nessuna notifica"</string>
+    <string name="status_bar_ongoing_events_title">"In corso"</string>
+    <string name="status_bar_latest_events_title">"Notifiche"</string>
+    <!-- no translation found for battery_status_text_percent_format (7660311274698797147) -->
+    <skip />
+    <string name="battery_status_charging">"In carica..."</string>
+    <string name="battery_low_title">"Collegare il caricabatterie"</string>
+    <string name="battery_low_subtitle">"Batteria quasi scarica:"</string>
+    <string name="battery_low_percent_format">"energia residua inferiore a <xliff:g id="NUMBER">%d%%</xliff:g>."</string>
+    <string name="factorytest_failed">"Test di fabbrica non riuscito"</string>
+    <string name="factorytest_not_system">"L\'azione FACTORY_TEST è supportata soltanto per i pacchetti installati in /system/app."</string>
+    <string name="factorytest_no_action">"Nessun pacchetto trovato che fornisca l\'azione FACTORY_TEST."</string>
+    <string name="factorytest_reboot">"Riavvia"</string>
+    <string name="js_dialog_title">"La pagina all\'indirizzo <xliff:g id="TITLE">%s</xliff:g> indica:"</string>
+    <string name="js_dialog_title_default">"JavaScript"</string>
+    <string name="js_dialog_before_unload">"Uscire da questa pagina?"\n\n"<xliff:g id="MESSAGE">%s</xliff:g>"\n\n"Seleziona OK per continuare o Annulla per rimanere nella pagina corrente."</string>
+    <string name="save_password_label">"Conferma"</string>
+    <string name="save_password_message">"Memorizzare la password nel browser?"</string>
+    <string name="save_password_notnow">"Non ora"</string>
+    <string name="save_password_remember">"Memorizza"</string>
+    <string name="save_password_never">"Mai"</string>
+    <string name="open_permission_deny">"L\'utente non è autorizzato ad aprire questa pagina."</string>
+    <string name="text_copied">"Testo copiato negli appunti."</string>
+    <string name="more_item_label">"Altro"</string>
+    <string name="prepend_shortcut_label">"Menu+"</string>
+    <string name="menu_space_shortcut_label">"spazio"</string>
+    <string name="menu_enter_shortcut_label">"Invio"</string>
+    <string name="menu_delete_shortcut_label">"Canc"</string>
+    <string name="search_go">"Cerca"</string>
+    <string name="today">"Oggi"</string>
+    <string name="yesterday">"Ieri"</string>
+    <string name="tomorrow">"Domani"</string>
+    <string name="oneMonthDurationPast">"1 mese fa"</string>
+    <string name="beforeOneMonthDurationPast">"Oltre 1 mese fa"</string>
+  <plurals name="num_seconds_ago">
+    <item quantity="one">"1 secondo fa"</item>
+    <item quantity="other">"<xliff:g id="COUNT">%d</xliff:g> secondi fa"</item>
+  </plurals>
+  <plurals name="num_minutes_ago">
+    <item quantity="one">"1 minuto fa"</item>
+    <item quantity="other">"<xliff:g id="COUNT">%d</xliff:g> minuti fa"</item>
+  </plurals>
+  <plurals name="num_hours_ago">
+    <item quantity="one">"1 ora fa"</item>
+    <item quantity="other">"<xliff:g id="COUNT">%d</xliff:g> ore fa"</item>
+  </plurals>
+  <plurals name="num_days_ago">
+    <item quantity="one">"ieri"</item>
+    <item quantity="other">"<xliff:g id="COUNT">%d</xliff:g> giorni fa"</item>
+  </plurals>
+  <plurals name="in_num_seconds">
+    <item quantity="one">"tra 1 secondo"</item>
+    <item quantity="other">"tra <xliff:g id="COUNT">%d</xliff:g> secondi"</item>
+  </plurals>
+  <plurals name="in_num_minutes">
+    <item quantity="one">"tra 1 minuto"</item>
+    <item quantity="other">"tra <xliff:g id="COUNT">%d</xliff:g> minuti"</item>
+  </plurals>
+  <plurals name="in_num_hours">
+    <item quantity="one">"tra 1 ora"</item>
+    <item quantity="other">"tra <xliff:g id="COUNT">%d</xliff:g> ore"</item>
+  </plurals>
+  <plurals name="in_num_days">
+    <item quantity="one">"domani"</item>
+    <item quantity="other">"tra <xliff:g id="COUNT">%d</xliff:g> giorni"</item>
+  </plurals>
+  <plurals name="abbrev_num_seconds_ago">
+    <item quantity="one">"1 sec fa"</item>
+    <item quantity="other">"<xliff:g id="COUNT">%d</xliff:g> sec fa"</item>
+  </plurals>
+  <plurals name="abbrev_num_minutes_ago">
+    <item quantity="one">"1 min fa"</item>
+    <item quantity="other">"<xliff:g id="COUNT">%d</xliff:g> min fa"</item>
+  </plurals>
+  <plurals name="abbrev_num_hours_ago">
+    <item quantity="one">"1 ora fa"</item>
+    <item quantity="other">"<xliff:g id="COUNT">%d</xliff:g> ore fa"</item>
+  </plurals>
+  <plurals name="abbrev_num_days_ago">
+    <item quantity="one">"ieri"</item>
+    <item quantity="other">"<xliff:g id="COUNT">%d</xliff:g> giorni fa"</item>
+  </plurals>
+  <plurals name="abbrev_in_num_seconds">
+    <item quantity="one">"tra 1 sec"</item>
+    <item quantity="other">"tra <xliff:g id="COUNT">%d</xliff:g> sec"</item>
+  </plurals>
+  <plurals name="abbrev_in_num_minutes">
+    <item quantity="one">"tra 1 min"</item>
+    <item quantity="other">"tra <xliff:g id="COUNT">%d</xliff:g> min"</item>
+  </plurals>
+  <plurals name="abbrev_in_num_hours">
+    <item quantity="one">"tra 1 ora"</item>
+    <item quantity="other">"tra <xliff:g id="COUNT">%d</xliff:g> ore"</item>
+  </plurals>
+  <plurals name="abbrev_in_num_days">
+    <item quantity="one">"domani"</item>
+    <item quantity="other">"tra <xliff:g id="COUNT">%d</xliff:g> giorni"</item>
+  </plurals>
+    <string name="preposition_for_date">"il %s"</string>
+    <string name="preposition_for_time">"alle %s"</string>
+    <string name="preposition_for_year">"nel %s"</string>
+    <string name="day">"giorno"</string>
+    <string name="days">"giorni"</string>
+    <string name="hour">"ora"</string>
+    <string name="hours">"ore"</string>
+    <string name="minute">"min"</string>
+    <string name="minutes">"min"</string>
+    <string name="second">"sec"</string>
+    <string name="seconds">"sec"</string>
+    <string name="week">"settimana"</string>
+    <string name="weeks">"settimane"</string>
+    <string name="year">"anno"</string>
+    <string name="years">"anni"</string>
+    <string name="sunday">"Domenica"</string>
+    <string name="monday">"Lunedì"</string>
+    <string name="tuesday">"Martedì"</string>
+    <string name="wednesday">"Mercoledì"</string>
+    <string name="thursday">"Giovedì"</string>
+    <string name="friday">"Venerdì"</string>
+    <string name="saturday">"Sabato"</string>
+    <string name="every_weekday">"Ogni giorno feriale (lun-ven)"</string>
+    <string name="daily">"Quotidianamente"</string>
+    <string name="weekly">"Ogni settimana il <xliff:g id="DAY">%s</xliff:g>"</string>
+    <string name="monthly">"Mensilmente"</string>
+    <string name="yearly">"Annualmente"</string>
+    <string name="VideoView_error_title">"Impossibile riprodurre il video"</string>
+    <string name="VideoView_error_text_unknown">"Spiacenti. Impossibile riprodurre il video."</string>
+    <string name="VideoView_error_button">"OK"</string>
+    <string name="am">"AM"</string>
+    <string name="pm">"PM"</string>
+    <string name="numeric_date">"<xliff:g id="DAY">%d</xliff:g>/<xliff:g id="MONTH">%m</xliff:g>/<xliff:g id="YEAR">%Y</xliff:g>"</string>
+    <string name="wday1_date1_time1_wday2_date2_time2">"<xliff:g id="WEEKDAY1">%1$s</xliff:g>, <xliff:g id="DATE1">%2$s</xliff:g>, <xliff:g id="TIME1">%3$s</xliff:g> – <xliff:g id="WEEKDAY2">%4$s</xliff:g>, <xliff:g id="DATE2">%5$s</xliff:g>, <xliff:g id="TIME2">%6$s</xliff:g>"</string>
+    <string name="wday1_date1_wday2_date2">"<xliff:g id="WEEKDAY1">%1$s</xliff:g>, <xliff:g id="DATE1">%2$s</xliff:g> – <xliff:g id="WEEKDAY2">%4$s</xliff:g>, <xliff:g id="DATE2">%5$s</xliff:g>"</string>
+    <string name="date1_time1_date2_time2">"<xliff:g id="DATE1">%2$s</xliff:g>, <xliff:g id="TIME1">%3$s</xliff:g> – <xliff:g id="DATE2">%5$s</xliff:g>, <xliff:g id="TIME2">%6$s</xliff:g>"</string>
+    <string name="date1_date2">"<xliff:g id="DATE1">%2$s</xliff:g> – <xliff:g id="DATE2">%5$s</xliff:g>"</string>
+    <string name="time1_time2">"<xliff:g id="TIME1">%1$s</xliff:g> – <xliff:g id="TIME2">%2$s</xliff:g>"</string>
+    <string name="time_wday_date">"<xliff:g id="TIME_RANGE">%1$s</xliff:g>, <xliff:g id="WEEKDAY">%2$s</xliff:g>, <xliff:g id="DATE">%3$s</xliff:g>"</string>
+    <string name="wday_date">"<xliff:g id="WEEKDAY">%2$s</xliff:g>, <xliff:g id="DATE">%3$s</xliff:g>"</string>
+    <string name="time_date">"<xliff:g id="TIME_RANGE">%1$s</xliff:g>, <xliff:g id="DATE">%3$s</xliff:g>"</string>
+    <string name="date_time">"<xliff:g id="DATE">%1$s</xliff:g>, <xliff:g id="TIME">%2$s</xliff:g>"</string>
+    <string name="relative_time">"<xliff:g id="DATE">%1$s</xliff:g>, <xliff:g id="TIME">%2$s</xliff:g>"</string>
+    <string name="time_wday">"<xliff:g id="TIME_RANGE">%1$s</xliff:g>, <xliff:g id="WEEKDAY">%2$s</xliff:g>"</string>
+    <string name="full_date_month_first" format="date">"<xliff:g id="MONTH">MMMM</xliff:g>' '<xliff:g id="DAY">d</xliff:g>', '<xliff:g id="YEAR">yyyy</xliff:g>"</string>
+    <string name="full_date_day_first" format="date">"<xliff:g id="DAY">d</xliff:g>' '<xliff:g id="MONTH">MMMM</xliff:g>' '<xliff:g id="YEAR">yyyy</xliff:g>"</string>
+    <string name="medium_date_month_first" format="date">"<xliff:g id="MONTH">MMM</xliff:g>' '<xliff:g id="DAY">d</xliff:g>', '<xliff:g id="YEAR">yyyy</xliff:g>"</string>
+    <string name="medium_date_day_first" format="date">"<xliff:g id="DAY">d</xliff:g>' '<xliff:g id="MONTH">MMM</xliff:g>' '<xliff:g id="YEAR">yyyy</xliff:g>"</string>
+    <string name="twelve_hour_time_format" format="date">"<xliff:g id="HOUR">h</xliff:g>':'<xliff:g id="MINUTE">mm</xliff:g>' '<xliff:g id="AMPM">a</xliff:g>"</string>
+    <string name="twenty_four_hour_time_format" format="date">"<xliff:g id="HOUR">H</xliff:g>':'<xliff:g id="MINUTE">mm</xliff:g>"</string>
+    <string name="noon">"mezzogiorno"</string>
+    <string name="Noon">"Mezzogiorno"</string>
+    <string name="midnight">"mezzanotte"</string>
+    <string name="Midnight">"Mezzanotte"</string>
+    <!-- no translation found for month_day (3693060561170538204) -->
+    <skip />
+    <!-- no translation found for month (1976700695144952053) -->
+    <skip />
+    <string name="month_day_year">"<xliff:g id="MONTH">%B</xliff:g> <xliff:g id="DAY">%-d</xliff:g>, <xliff:g id="YEAR">%Y</xliff:g>"</string>
+    <!-- no translation found for month_year (2106203387378728384) -->
+    <skip />
+    <string name="time_of_day">"<xliff:g id="HOUR">%H</xliff:g>:<xliff:g id="MINUTE">%M</xliff:g>:<xliff:g id="SECOND">%S</xliff:g>"</string>
+    <string name="date_and_time">"<xliff:g id="HOUR">%H</xliff:g>:<xliff:g id="MINUTE">%M</xliff:g>:<xliff:g id="SECOND">%S</xliff:g> <xliff:g id="DAY">%-d</xliff:g> <xliff:g id="MONTH">%B</xliff:g>, <xliff:g id="YEAR">%Y</xliff:g>"</string>
+    <string name="same_year_md1_md2">"<xliff:g id="DAY1">%3$s</xliff:g> <xliff:g id="MONTH1">%2$s</xliff:g> – <xliff:g id="DAY2">%8$s</xliff:g> <xliff:g id="MONTH2">%7$s</xliff:g>"</string>
+    <string name="same_year_wday1_md1_wday2_md2">"<xliff:g id="WEEKDAY1">%1$s</xliff:g>, <xliff:g id="DAY1_0">%3$s</xliff:g> <xliff:g id="MONTH1">%2$s</xliff:g> – <xliff:g id="WEEKDAY2">%6$s</xliff:g>, <xliff:g id="DAY2_1">%8$s</xliff:g> <xliff:g id="MONTH2">%7$s</xliff:g>"</string>
+    <string name="same_year_mdy1_mdy2">"<xliff:g id="DAY1">%3$s</xliff:g> <xliff:g id="MONTH1">%2$s</xliff:g> – <xliff:g id="DAY2">%8$s</xliff:g> <xliff:g id="MONTH2">%7$s</xliff:g>, <xliff:g id="YEAR">%9$s</xliff:g>"</string>
+    <string name="same_year_wday1_mdy1_wday2_mdy2">"<xliff:g id="WEEKDAY1">%1$s</xliff:g>, <xliff:g id="DAY1_0">%3$s</xliff:g> <xliff:g id="MONTH1">%2$s</xliff:g> – <xliff:g id="WEEKDAY2">%6$s</xliff:g>, <xliff:g id="DAY2_1">%8$s</xliff:g> <xliff:g id="MONTH2">%7$s</xliff:g>, <xliff:g id="YEAR">%9$s</xliff:g>"</string>
+    <string name="same_year_md1_time1_md2_time2">"<xliff:g id="DAY1">%3$s</xliff:g> <xliff:g id="MONTH1">%2$s</xliff:g>, <xliff:g id="TIME1">%5$s</xliff:g> – <xliff:g id="DAY2">%8$s</xliff:g> <xliff:g id="MONTH2">%7$s</xliff:g>, <xliff:g id="TIME2">%10$s</xliff:g>"</string>
+    <string name="same_year_wday1_md1_time1_wday2_md2_time2">"<xliff:g id="WEEKDAY1">%1$s</xliff:g>, <xliff:g id="DAY1_0">%3$s</xliff:g> <xliff:g id="MONTH1">%2$s</xliff:g>, <xliff:g id="TIME1">%5$s</xliff:g> – <xliff:g id="WEEKDAY2">%6$s</xliff:g>, <xliff:g id="DAY2_1">%8$s</xliff:g> <xliff:g id="MONTH2">%7$s</xliff:g>, <xliff:g id="TIME2">%10$s</xliff:g>"</string>
+    <string name="same_year_mdy1_time1_mdy2_time2">"<xliff:g id="DAY1">%3$s</xliff:g> <xliff:g id="MONTH1">%2$s</xliff:g>, <xliff:g id="YEAR1">%4$s</xliff:g>, <xliff:g id="TIME1">%5$s</xliff:g> – <xliff:g id="DAY2">%8$s</xliff:g> <xliff:g id="MONTH2">%7$s</xliff:g>, <xliff:g id="YEAR2">%9$s</xliff:g>, <xliff:g id="TIME2">%10$s</xliff:g>"</string>
+    <string name="same_year_wday1_mdy1_time1_wday2_mdy2_time2">"<xliff:g id="WEEKDAY1">%1$s</xliff:g>, <xliff:g id="DAY1_0">%3$s</xliff:g> <xliff:g id="MONTH1">%2$s</xliff:g> <xliff:g id="YEAR1">%4$s</xliff:g>, <xliff:g id="TIME1">%5$s</xliff:g> – <xliff:g id="WEEKDAY2">%6$s</xliff:g>, <xliff:g id="DAY2_1">%8$s</xliff:g> <xliff:g id="MONTH2">%7$s</xliff:g> <xliff:g id="YEAR2">%9$s</xliff:g>, <xliff:g id="TIME2">%10$s</xliff:g>"</string>
+    <string name="numeric_md1_md2">"<xliff:g id="DAY1">%3$s</xliff:g>/<xliff:g id="MONTH1">%2$s</xliff:g> – <xliff:g id="DAY2">%8$s</xliff:g>/<xliff:g id="MONTH2">%7$s</xliff:g>"</string>
+    <string name="numeric_wday1_md1_wday2_md2">"<xliff:g id="WEEKDAY1">%1$s</xliff:g>, <xliff:g id="DAY1_0">%3$s</xliff:g>/<xliff:g id="MONTH1">%2$s</xliff:g> – <xliff:g id="WEEKDAY2">%6$s</xliff:g>, <xliff:g id="DAY2_1">%8$s</xliff:g>/<xliff:g id="MONTH2">%7$s</xliff:g>"</string>
+    <string name="numeric_mdy1_mdy2">"<xliff:g id="DAY1">%3$s</xliff:g>/<xliff:g id="MONTH1">%2$s</xliff:g>/<xliff:g id="YEAR1">%4$s</xliff:g> – <xliff:g id="DAY2">%8$s</xliff:g>/<xliff:g id="MONTH2">%7$s</xliff:g>/<xliff:g id="YEAR2">%9$s</xliff:g>"</string>
+    <string name="numeric_wday1_mdy1_wday2_mdy2">"<xliff:g id="WEEKDAY1">%1$s</xliff:g>, <xliff:g id="DAY1_0">%3$s</xliff:g>/<xliff:g id="MONTH1">%2$s</xliff:g>/<xliff:g id="YEAR1">%4$s</xliff:g> – <xliff:g id="WEEKDAY2">%6$s</xliff:g>, <xliff:g id="DAY2_1">%8$s</xliff:g>/<xliff:g id="MONTH2">%7$s</xliff:g>/<xliff:g id="YEAR2">%9$s</xliff:g>"</string>
+    <string name="numeric_md1_time1_md2_time2">"<xliff:g id="DAY1">%3$s</xliff:g>/<xliff:g id="MONTH1">%2$s</xliff:g>, <xliff:g id="TIME1">%5$s</xliff:g> – <xliff:g id="DAY2">%8$s</xliff:g>/<xliff:g id="MONTH2">%7$s</xliff:g>, <xliff:g id="TIME2">%10$s</xliff:g>"</string>
+    <string name="numeric_wday1_md1_time1_wday2_md2_time2">"<xliff:g id="WEEKDAY1">%1$s</xliff:g>, <xliff:g id="DAY1_0">%3$s</xliff:g>/<xliff:g id="MONTH1">%2$s</xliff:g>, <xliff:g id="TIME1">%5$s</xliff:g> – <xliff:g id="WEEKDAY2">%6$s</xliff:g>, <xliff:g id="DAY2_1">%8$s</xliff:g>/<xliff:g id="MONTH2">%7$s</xliff:g>, <xliff:g id="TIME2">%10$s</xliff:g>"</string>
+    <string name="numeric_mdy1_time1_mdy2_time2">"<xliff:g id="DAY1">%3$s</xliff:g>/<xliff:g id="MONTH1">%2$s</xliff:g>/<xliff:g id="YEAR1">%4$s</xliff:g>, <xliff:g id="TIME1">%5$s</xliff:g> – <xliff:g id="DAY2">%8$s</xliff:g>/<xliff:g id="MONTH2">%7$s</xliff:g>/<xliff:g id="YEAR2">%9$s</xliff:g>, <xliff:g id="TIME2">%10$s</xliff:g>"</string>
+    <string name="numeric_wday1_mdy1_time1_wday2_mdy2_time2">"<xliff:g id="WEEKDAY1">%1$s</xliff:g>, <xliff:g id="DAY1_0">%3$s</xliff:g>/<xliff:g id="MONTH1">%2$s</xliff:g>/<xliff:g id="YEAR1">%4$s</xliff:g>, <xliff:g id="TIME1">%5$s</xliff:g> – <xliff:g id="WEEKDAY2">%6$s</xliff:g>, <xliff:g id="DAY2_1">%8$s</xliff:g>/<xliff:g id="MONTH2">%7$s</xliff:g>/<xliff:g id="YEAR2">%9$s</xliff:g>, <xliff:g id="TIME2">%10$s</xliff:g>"</string>
+    <string name="same_month_md1_md2">"<xliff:g id="DAY1">%3$s</xliff:g> – <xliff:g id="DAY2">%8$s</xliff:g> <xliff:g id="MONTH1">%2$s</xliff:g>"</string>
+    <string name="same_month_wday1_md1_wday2_md2">"<xliff:g id="WEEKDAY1">%1$s</xliff:g>, <xliff:g id="DAY1_0">%3$s</xliff:g> <xliff:g id="MONTH1">%2$s</xliff:g> – <xliff:g id="WEEKDAY2">%6$s</xliff:g>, <xliff:g id="DAY2_1">%8$s</xliff:g> <xliff:g id="MONTH2">%7$s</xliff:g>"</string>
+    <string name="same_month_mdy1_mdy2">"<xliff:g id="DAY1">%3$s</xliff:g> – <xliff:g id="DAY2">%8$s</xliff:g> <xliff:g id="MONTH1">%2$s</xliff:g>, <xliff:g id="YEAR2">%9$s</xliff:g>"</string>
+    <string name="same_month_wday1_mdy1_wday2_mdy2">"<xliff:g id="WEEKDAY1">%1$s</xliff:g>, <xliff:g id="DAY1_0">%3$s</xliff:g> <xliff:g id="MONTH1">%2$s</xliff:g>, <xliff:g id="YEAR1">%4$s</xliff:g> – <xliff:g id="WEEKDAY2">%6$s</xliff:g>, <xliff:g id="DAY2_1">%8$s</xliff:g> <xliff:g id="MONTH2">%7$s</xliff:g>, <xliff:g id="YEAR2">%9$s</xliff:g>"</string>
+    <string name="same_month_md1_time1_md2_time2">"<xliff:g id="DAY1">%3$s</xliff:g> <xliff:g id="MONTH1">%2$s</xliff:g>, <xliff:g id="TIME1">%5$s</xliff:g> – <xliff:g id="DAY2">%8$s</xliff:g> <xliff:g id="MONTH2">%7$s</xliff:g>, <xliff:g id="TIME2">%10$s</xliff:g>"</string>
+    <string name="same_month_wday1_md1_time1_wday2_md2_time2">"<xliff:g id="WEEKDAY1">%1$s</xliff:g>, <xliff:g id="DAY1_0">%3$s</xliff:g> <xliff:g id="MONTH1">%2$s</xliff:g>, <xliff:g id="TIME1">%5$s</xliff:g> – <xliff:g id="WEEKDAY2">%6$s</xliff:g>, <xliff:g id="DAY2_1">%8$s</xliff:g> <xliff:g id="MONTH2">%7$s</xliff:g>, <xliff:g id="TIME2">%10$s</xliff:g>"</string>
+    <string name="same_month_mdy1_time1_mdy2_time2">"<xliff:g id="DAY1">%3$s</xliff:g> <xliff:g id="MONTH1">%2$s</xliff:g> <xliff:g id="YEAR1">%4$s</xliff:g>, <xliff:g id="TIME1">%5$s</xliff:g> – <xliff:g id="DAY2">%8$s</xliff:g> <xliff:g id="MONTH2">%7$s</xliff:g> <xliff:g id="YEAR2">%9$s</xliff:g>, <xliff:g id="TIME2">%10$s</xliff:g>"</string>
+    <string name="same_month_wday1_mdy1_time1_wday2_mdy2_time2">"<xliff:g id="WEEKDAY1">%1$s</xliff:g>, <xliff:g id="DAY1_0">%3$s</xliff:g> <xliff:g id="MONTH1">%2$s</xliff:g>, <xliff:g id="YEAR1">%4$s</xliff:g>, <xliff:g id="TIME1">%5$s</xliff:g> – <xliff:g id="WEEKDAY2">%6$s</xliff:g>, <xliff:g id="DAY2_1">%8$s</xliff:g> <xliff:g id="MONTH2">%7$s</xliff:g>, <xliff:g id="YEAR2">%9$s</xliff:g>, <xliff:g id="TIME2">%10$s</xliff:g>"</string>
+    <string name="abbrev_month_day_year">"<xliff:g id="DAY">%-d</xliff:g> <xliff:g id="MONTH">%b</xliff:g>, <xliff:g id="YEAR">%Y</xliff:g>"</string>
+    <!-- no translation found for abbrev_month_year (5966980891147982768) -->
+    <skip />
+    <!-- no translation found for abbrev_month_day (3156047263406783231) -->
+    <skip />
+    <!-- no translation found for abbrev_month (7304935052615731208) -->
+    <skip />
+    <string name="day_of_week_long_sunday">"Domenica"</string>
+    <string name="day_of_week_long_monday">"Lunedì"</string>
+    <string name="day_of_week_long_tuesday">"Martedì"</string>
+    <string name="day_of_week_long_wednesday">"Mercoledì"</string>
+    <string name="day_of_week_long_thursday">"Giovedì"</string>
+    <string name="day_of_week_long_friday">"Venerdì"</string>
+    <string name="day_of_week_long_saturday">"Sabato"</string>
+    <string name="day_of_week_medium_sunday">"Dom"</string>
+    <string name="day_of_week_medium_monday">"Lun"</string>
+    <string name="day_of_week_medium_tuesday">"Mar"</string>
+    <string name="day_of_week_medium_wednesday">"Mer"</string>
+    <string name="day_of_week_medium_thursday">"Gio"</string>
+    <string name="day_of_week_medium_friday">"Ven"</string>
+    <string name="day_of_week_medium_saturday">"Sab"</string>
+    <string name="day_of_week_short_sunday">"Do"</string>
+    <string name="day_of_week_short_monday">"Lu"</string>
+    <string name="day_of_week_short_tuesday">"Ma"</string>
+    <string name="day_of_week_short_wednesday">"Me"</string>
+    <string name="day_of_week_short_thursday">"Gi"</string>
+    <string name="day_of_week_short_friday">"Ve"</string>
+    <string name="day_of_week_short_saturday">"Sa"</string>
+    <string name="day_of_week_shorter_sunday">"Do"</string>
+    <string name="day_of_week_shorter_monday">"Lu"</string>
+    <string name="day_of_week_shorter_tuesday">"Ma"</string>
+    <string name="day_of_week_shorter_wednesday">"Me"</string>
+    <string name="day_of_week_shorter_thursday">"Gi"</string>
+    <string name="day_of_week_shorter_friday">"V"</string>
+    <string name="day_of_week_shorter_saturday">"Sa"</string>
+    <string name="day_of_week_shortest_sunday">"D"</string>
+    <string name="day_of_week_shortest_monday">"Lun"</string>
+    <string name="day_of_week_shortest_tuesday">"M"</string>
+    <string name="day_of_week_shortest_wednesday">"Me"</string>
+    <string name="day_of_week_shortest_thursday">"G"</string>
+    <string name="day_of_week_shortest_friday">"V"</string>
+    <string name="day_of_week_shortest_saturday">"Sa"</string>
+    <string name="month_long_january">"Gennaio"</string>
+    <string name="month_long_february">"Febbraio"</string>
+    <string name="month_long_march">"Marzo"</string>
+    <string name="month_long_april">"Aprile"</string>
+    <string name="month_long_may">"Maggio"</string>
+    <string name="month_long_june">"Giugno"</string>
+    <string name="month_long_july">"Luglio"</string>
+    <string name="month_long_august">"Agosto"</string>
+    <string name="month_long_september">"Settembre"</string>
+    <string name="month_long_october">"Ottobre"</string>
+    <string name="month_long_november">"Novembre"</string>
+    <string name="month_long_december">"Dicembre"</string>
+    <string name="month_medium_january">"Gen"</string>
+    <string name="month_medium_february">"Feb"</string>
+    <string name="month_medium_march">"Mar"</string>
+    <string name="month_medium_april">"Apr"</string>
+    <string name="month_medium_may">"Mag"</string>
+    <string name="month_medium_june">"Giu"</string>
+    <string name="month_medium_july">"Lug"</string>
+    <string name="month_medium_august">"Ago"</string>
+    <string name="month_medium_september">"Set"</string>
+    <string name="month_medium_october">"Ott"</string>
+    <string name="month_medium_november">"Nov"</string>
+    <string name="month_medium_december">"Dic"</string>
+    <string name="month_shortest_january">"G"</string>
+    <string name="month_shortest_february">"F"</string>
+    <string name="month_shortest_march">"M"</string>
+    <string name="month_shortest_april">"Ap"</string>
+    <string name="month_shortest_may">"Mag"</string>
+    <string name="month_shortest_june">"Gi"</string>
+    <string name="month_shortest_july">"Lug"</string>
+    <string name="month_shortest_august">"Ago"</string>
+    <string name="month_shortest_september">"Set"</string>
+    <string name="month_shortest_october">"O"</string>
+    <string name="month_shortest_november">"N"</string>
+    <string name="month_shortest_december">"Di"</string>
+    <string name="elapsed_time_short_format_mm_ss">"<xliff:g id="MINUTES">%1$02d</xliff:g>:<xliff:g id="SECONDS">%2$02d</xliff:g>"</string>
+    <string name="elapsed_time_short_format_h_mm_ss">"<xliff:g id="HOURS">%1$d</xliff:g>:<xliff:g id="MINUTES">%2$02d</xliff:g>:<xliff:g id="SECONDS">%3$02d</xliff:g>"</string>
+    <string name="selectAll">"Seleziona tutto"</string>
+    <string name="selectText">"Seleziona testo"</string>
+    <string name="stopSelectingText">"Termina selezione testo"</string>
+    <string name="cut">"Taglia"</string>
+    <string name="cutAll">"Taglia tutto"</string>
+    <string name="copy">"Copia"</string>
+    <string name="copyAll">"Copia tutto"</string>
+    <string name="paste">"Incolla"</string>
+    <string name="copyUrl">"Copia URL"</string>
+    <string name="inputMethod">"Metodo inserimento"</string>
+    <string name="addToDictionary">"Aggiungi \"%s\" al dizionario"</string>
+    <string name="editTextMenuTitle">"Modifica testo"</string>
+    <string name="low_internal_storage_view_title">"Spazio in esaurimento"</string>
+    <string name="low_internal_storage_view_text">"Spazio di archiviazione del telefono in esaurimento."</string>
+    <string name="ok">"OK"</string>
+    <string name="cancel">"Annulla"</string>
+    <string name="yes">"OK"</string>
+    <string name="no">"Annulla"</string>
+    <string name="dialog_alert_title">"Attenzione"</string>
+    <string name="capital_on">"ON"</string>
+    <string name="capital_off">"OFF"</string>
+    <string name="whichApplication">"Completa l\'azione con"</string>
+    <string name="alwaysUse">"Usa come predefinita per questa azione."</string>
+    <string name="clearDefaultHintMsg">"Cancella predefinita in Home &gt; Impostazioni &gt; Applicazioni &gt; Gestisci applicazioni."</string>
+    <string name="chooseActivity">"Seleziona un\'azione"</string>
+    <string name="noApplications">"Nessuna applicazione è in grado di svolgere questa azione."</string>
+    <string name="aerr_title">"Spiacenti."</string>
+    <string name="aerr_application">"Interruzione imprevista dell\'applicazione <xliff:g id="APPLICATION">%1$s</xliff:g> (processo<xliff:g id="PROCESS">%2$s</xliff:g>). Riprova."</string>
+    <string name="aerr_process">"Interruzione imprevista del processo <xliff:g id="PROCESS">%1$s</xliff:g>. Riprova."</string>
+    <string name="anr_title">"Spiacenti."</string>
+    <string name="anr_activity_application">"L\'attività <xliff:g id="ACTIVITY">%1$s</xliff:g> (nell\'applicazione <xliff:g id="APPLICATION">%2$s</xliff:g>) non risponde."</string>
+    <string name="anr_activity_process">"L\'attività <xliff:g id="ACTIVITY">%1$s</xliff:g> (nel processo <xliff:g id="PROCESS">%2$s</xliff:g>) non risponde."</string>
+    <string name="anr_application_process">"L\'applicazione <xliff:g id="APPLICATION">%1$s</xliff:g> (nel processo <xliff:g id="PROCESS">%2$s</xliff:g>) non risponde."</string>
+    <string name="anr_process">"Il processo <xliff:g id="PROCESS">%1$s</xliff:g> non risponde."</string>
+    <string name="force_close">"Forza chiusura"</string>
+    <string name="wait">"Attendi"</string>
+    <string name="debug">"Debug"</string>
+    <string name="sendText">"Seleziona un\'azione per il testo"</string>
+    <string name="volume_ringtone">"Volume suoneria"</string>
+    <string name="volume_music">"Volume app. multimediali"</string>
+    <string name="volume_music_hint_playing_through_bluetooth">"Riproduzione tramite Bluetooth"</string>
+    <string name="volume_call">"Volume chiamate"</string>
+    <string name="volume_bluetooth_call">"Volume chiamate Bluetooth"</string>
+    <string name="volume_alarm">"Volume allarme"</string>
+    <string name="volume_notification">"Volume notifiche"</string>
+    <string name="volume_unknown">"Volume"</string>
+    <string name="ringtone_default">"Suoneria predefinita"</string>
+    <string name="ringtone_default_with_actual">"Suoneria predefinita (<xliff:g id="ACTUAL_RINGTONE">%1$s</xliff:g>)"</string>
+    <string name="ringtone_silent">"Silenzioso"</string>
+    <string name="ringtone_picker_title">"Suonerie"</string>
+    <string name="ringtone_unknown">"Suoneria sconosciuta"</string>
+  <plurals name="wifi_available">
+    <item quantity="one">"Rete Wi-Fi disponibile"</item>
+    <item quantity="other">"Reti Wi-Fi disponibili"</item>
+  </plurals>
+  <plurals name="wifi_available_detailed">
+    <item quantity="one">"Rete Wi-Fi aperta disponibile"</item>
+    <item quantity="other">"Reti Wi-Fi aperte disponibili"</item>
+  </plurals>
+    <string name="select_character">"Inserisci carattere"</string>
+    <string name="sms_control_default_app_name">"Applicazione sconosciuta"</string>
+    <string name="sms_control_title">"Invio SMS"</string>
+    <string name="sms_control_message">"È in corso l\'invio di numerosi SMS. Seleziona \"OK\" per continuare, oppure \"Annulla\" per interrompere l\'invio."</string>
+    <string name="sms_control_yes">"OK"</string>
+    <string name="sms_control_no">"Annulla"</string>
+    <string name="date_time_set">"Imposta"</string>
+    <string name="default_permission_group">"Predefinito"</string>
+    <string name="no_permissions">"Nessuna autorizzazione richiesta"</string>
+    <string name="perms_hide"><b>"Nascondi"</b></string>
+    <string name="perms_show_all"><b>"Mostra tutto"</b></string>
+    <string name="googlewebcontenthelper_loading">"Caricamento..."</string>
+    <string name="usb_storage_title">"USB collegata"</string>
+    <string name="usb_storage_message">"Il telefono è stato collegato al computer tramite USB. Seleziona \"Collega\" se desideri copiare file tra il computer e la scheda SD del telefono."</string>
+    <string name="usb_storage_button_mount">"Collega"</string>
+    <string name="usb_storage_button_unmount">"Non collegare"</string>
+    <string name="usb_storage_error_message">"Problema di utilizzo della scheda SD per l\'archiviazione USB."</string>
+    <string name="usb_storage_notification_title">"USB collegata"</string>
+    <string name="usb_storage_notification_message">"Seleziona per copiare file sul/dal tuo computer."</string>
+    <string name="usb_storage_stop_notification_title">"Disattiva archivio USB"</string>
+    <string name="usb_storage_stop_notification_message">"Seleziona per disattivare archivio USB."</string>
+    <string name="usb_storage_stop_title">"Disattiva archivio USB"</string>
+    <string name="usb_storage_stop_message">"Prima di disattivare l\'archivio USB, verifica di aver smontato l\'host USB. A tale scopo, seleziona \"Disattiva\"."</string>
+    <string name="usb_storage_stop_button_mount">"Disattiva"</string>
+    <string name="usb_storage_stop_button_unmount">"Annulla"</string>
+    <string name="usb_storage_stop_error_message">"Abbiamo riscontrato un problema disattivando l\'archivio USB. Verifica di aver smontato l\'host USB e riprova."</string>
+    <string name="extmedia_format_title">"Formatta scheda SD"</string>
+    <string name="extmedia_format_message">"Formattare la scheda SD? Tutti i dati sulla scheda verranno persi."</string>
+    <string name="extmedia_format_button_format">"Formatta"</string>
+    <string name="select_input_method">"Seleziona metodo di inserimento"</string>
+    <string name="fast_scroll_alphabet">" ABCDEFGHIJKLMNOPQRSTUVWXYZ"</string>
+    <string name="fast_scroll_numeric_alphabet">" 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"</string>
+    <string name="candidates_style"><u>"candidati"</u></string>
+    <string name="ext_media_checking_notification_title">"Preparazione scheda SD"</string>
+    <string name="ext_media_checking_notification_message">"Ricerca errori"</string>
+    <string name="ext_media_nofs_notification_title">"Scheda SD vuota"</string>
+    <string name="ext_media_nofs_notification_message">"La scheda SD è vuota o utilizza un file system non supportato."</string>
+    <string name="ext_media_unmountable_notification_title">"Scheda SD danneggiata"</string>
+    <string name="ext_media_unmountable_notification_message">"La scheda SD è danneggiata. Potrebbe essere necessario riformattarla."</string>
+    <string name="ext_media_badremoval_notification_title">"Rimozione imprevista della scheda SD"</string>
+    <string name="ext_media_badremoval_notification_message">"Smonta scheda SD prima della rimozione per evitare la perdita di dati."</string>
+    <string name="ext_media_safe_unmount_notification_title">"È possibile rimuovere la scheda SD"</string>
+    <string name="ext_media_safe_unmount_notification_message">"È ora possibile rimuovere la scheda SD in modo sicuro."</string>
+    <string name="ext_media_nomedia_notification_title">"Scheda SD rimossa"</string>
+    <string name="ext_media_nomedia_notification_message">"Inserisci una nuova scheda SD per aumentare la memoria del dispositivo."</string>
+    <string name="activity_list_empty">"Nessuna attività corrispondente trovata"</string>
+    <string name="permlab_pkgUsageStats">"aggiornare le statistiche di utilizzo dei componenti"</string>
+    <string name="permdesc_pkgUsageStats">"Consente la modifica delle statistiche di utilizzo dei componenti raccolte. Da non usare per normali applicazioni."</string>
+    <!-- no translation found for tutorial_double_tap_to_zoom_message_short (1311810005957319690) -->
+    <skip />
+    <!-- no translation found for gadget_host_error_inflating (2613287218853846830) -->
+    <skip />
+    <!-- no translation found for ime_action_go (8320845651737369027) -->
+    <skip />
+    <!-- no translation found for ime_action_search (658110271822807811) -->
+    <skip />
+    <!-- no translation found for ime_action_send (2316166556349314424) -->
+    <skip />
+    <!-- no translation found for ime_action_next (3138843904009813834) -->
+    <skip />
+    <!-- no translation found for ime_action_default (2840921885558045721) -->
+    <skip />
+</resources>
diff --git a/core/res/res/values-ja-rJP/arrays.xml b/core/res/res/values-ja-rJP/arrays.xml
new file mode 100644
index 0000000..74e8a59
--- /dev/null
+++ b/core/res/res/values-ja-rJP/arrays.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* //device/apps/common/assets/res/any/colors.xml
+**
+** Copyright 2009, Google Inc.
+**
+** 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.
+*/
+-->
+<resources>
+
+    <!-- Do not translate. -->
+    <integer-array name="maps_starting_lat_lng">
+        <item>35666667</item>
+        <item>139750000</item>
+     </integer-array>
+     <!-- Do not translate. -->
+     <integer-array name="maps_starting_zoom">
+         <item>5</item>
+     </integer-array>
+
+</resources>
diff --git a/core/res/res/values-ja/strings.xml b/core/res/res/values-ja/strings.xml
new file mode 100644
index 0000000..2a21bd3
--- /dev/null
+++ b/core/res/res/values-ja/strings.xml
@@ -0,0 +1,815 @@
+<?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.
+-->
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="byteShort">"B"</string>
+    <string name="kilobyteShort">"KB"</string>
+    <string name="megabyteShort">"MB"</string>
+    <string name="gigabyteShort">"GB"</string>
+    <string name="terabyteShort">"TB"</string>
+    <string name="petabyteShort">"PB"</string>
+    <string name="untitled">"&lt;新規&gt;"</string>
+    <string name="ellipsis">"..."</string>
+    <string name="emptyPhoneNumber">"(電話番号なし)"</string>
+    <string name="unknownName">"(名前)"</string>
+    <string name="defaultVoiceMailAlphaTag">"ボイスメール"</string>
+    <string name="defaultMsisdnAlphaTag">"MSISDN1"</string>
+    <string name="mmiError">"接続に問題があるか、MMIコードが正しくありません。"</string>
+    <string name="serviceEnabled">"サービスが有効になりました。"</string>
+    <string name="serviceEnabledFor">"次のサービスが有効になりました:"</string>
+    <string name="serviceDisabled">"サービスが無効になりました。"</string>
+    <string name="serviceRegistered">"登録されました。"</string>
+    <string name="serviceErased">"消去されました。"</string>
+    <string name="passwordIncorrect">"パスワードが正しくありません。"</string>
+    <string name="mmiComplete">"MMIが完了しました。"</string>
+    <string name="badPin">"入力した古いPINは正しくありません。"</string>
+    <string name="badPuk">"入力したPUKは正しくありません。"</string>
+    <string name="mismatchPin">"入力したPINが一致しません。"</string>
+    <string name="invalidPin">"4~8桁の数字のPINを入力してください。"</string>
+    <string name="needPuk">"SIMカードはPUKでロックされています。ロックを解除するにはPUKコードを入力してください。"</string>
+    <string name="needPuk2">"SIMカードのロック解除のためPUK2を入力します。"</string>
+    <string name="ClipMmi">"着信時の発信者番号"</string>
+    <string name="ClirMmi">"発信時の発信者番号"</string>
+    <string name="CfMmi">"電話の転送"</string>
+    <string name="CwMmi">"通話中着信"</string>
+    <string name="BaMmi">"発信制限"</string>
+    <string name="PwdMmi">"パスワードの変更"</string>
+    <string name="PinMmi">"PINの変更"</string>
+    <string name="CLIRDefaultOnNextCallOn">"既定: 発信者番号非通知、次の発信: 非通知"</string>
+    <string name="CLIRDefaultOnNextCallOff">"既定: 発信者番号非通知、次の発信: 通知"</string>
+    <string name="CLIRDefaultOffNextCallOn">"既定: 発信者番号通知、次の発信: 非通知"</string>
+    <string name="CLIRDefaultOffNextCallOff">"既定: 発信者番号通知、次の発信: 通知"</string>
+    <string name="serviceNotProvisioned">"提供可能なサービスがありません。"</string>
+    <string name="CLIRPermanent">"発信者番号の設定は変更できません。"</string>
+    <string name="serviceClassVoice">"音声"</string>
+    <string name="serviceClassData">"データ"</string>
+    <string name="serviceClassFAX">"FAX"</string>
+    <string name="serviceClassSMS">"SMS"</string>
+    <string name="serviceClassDataAsync">"非同期"</string>
+    <string name="serviceClassDataSync">"同期"</string>
+    <string name="serviceClassPacket">"パケット"</string>
+    <string name="serviceClassPAD">"PAD"</string>
+    <string name="cfTemplateNotForwarded">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>:転送できません"</string>
+    <string name="cfTemplateForwarded">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>:<xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
+    <string name="cfTemplateForwardedTime">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>:<xliff:g id="DIALING_NUMBER">{1}</xliff:g> (<xliff:g id="TIME_DELAY">{2}</xliff:g>秒後)"</string>
+    <string name="cfTemplateRegistered">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>:転送できません"</string>
+    <string name="cfTemplateRegisteredTime">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>:転送できません"</string>
+    <string name="httpErrorOk">"OK"</string>
+    <string name="httpError">"ウェブページにエラーがあります。"</string>
+    <string name="httpErrorLookup">"URLが見つかりませんでした。"</string>
+    <string name="httpErrorUnsupportedAuthScheme">"このサイトの認証方式には対応していません。"</string>
+    <string name="httpErrorAuth">"認証できませんでした。"</string>
+    <string name="httpErrorProxyAuth">"プロキシサーバーを使用した認証に失敗しました。"</string>
+    <string name="httpErrorConnect">"サーバーに接続できませんでした。"</string>
+    <string name="httpErrorIO">"サーバーと通信できませんでした。しばらくしてからもう一度試してください。"</string>
+    <string name="httpErrorTimeout">"サーバーへの接続がタイムアウトになりました。"</string>
+    <string name="httpErrorRedirectLoop">"このページはサーバーのリダイレクトが多すぎます。"</string>
+    <string name="httpErrorUnsupportedScheme">"このプロトコルには対応していません。"</string>
+    <string name="httpErrorFailedSslHandshake">"安全な接続を確立できませんでした。"</string>
+    <string name="httpErrorBadUrl">"URLが無効なのでページを表示できませんでした。"</string>
+    <string name="httpErrorFile">"ファイルにアクセスできませんでした。"</string>
+    <string name="httpErrorFileNotFound">"要求されたファイルが見つかりませんでした。"</string>
+    <string name="httpErrorTooManyRequests">"処理中のリクエストが多すぎます。しばらくしてからもう一度試してください。"</string>
+    <string name="contentServiceSync">"同期"</string>
+    <string name="contentServiceSyncNotificationTitle">"同期"</string>
+    <string name="contentServiceTooManyDeletesNotificationDesc">"<xliff:g id="CONTENT_TYPE">%s</xliff:g>での削除が多すぎます。"</string>
+    <string name="low_memory">"電話のメモリがいっぱいです。ファイルを削除してメモリを解放してください。"</string>
+    <string name="me">"自分"</string>
+    <string name="power_dialog">"携帯電話オプション"</string>
+    <string name="silent_mode">"マナーモード"</string>
+    <string name="turn_on_radio">"ワイヤレス接続をONにする"</string>
+    <string name="turn_off_radio">"ワイヤレス接続をOFFにする"</string>
+    <string name="screen_lock">"画面をロック"</string>
+    <string name="power_off">"電源オフ"</string>
+    <string name="shutdown_progress">"シャットダウン中..."</string>
+    <string name="shutdown_confirm">"携帯電話の電源をオフにします。"</string>
+    <string name="no_recent_tasks">"最近使ったアプリケーションはありません。"</string>
+    <string name="global_actions">"携帯電話オプション"</string>
+    <string name="global_action_lock">"画面ロック"</string>
+    <string name="global_action_power_off">"電源オフ"</string>
+    <string name="global_action_toggle_silent_mode">"マナーモード"</string>
+    <string name="global_action_silent_mode_on_status">"音声オフ"</string>
+    <string name="global_action_silent_mode_off_status">"サウンド:オン"</string>
+    <!-- no translation found for global_actions_toggle_airplane_mode (5884330306926307456) -->
+    <skip />
+    <!-- no translation found for global_actions_airplane_mode_on_status (2719557982608919750) -->
+    <skip />
+    <!-- no translation found for global_actions_airplane_mode_off_status (5075070442854490296) -->
+    <skip />
+    <string name="safeMode">"セーフモード"</string>
+    <!-- no translation found for android_system_label (6577375335728551336) -->
+    <skip />
+    <string name="permgrouplab_costMoney">"料金の発生するサービス"</string>
+    <string name="permgroupdesc_costMoney">"料金の発生する操作をアプリケーションに許可します。"</string>
+    <string name="permgrouplab_messages">"送受信したメッセージ"</string>
+    <string name="permgroupdesc_messages">"SMS、メールなどのメッセージの読み書き"</string>
+    <string name="permgrouplab_personalInfo">"個人情報"</string>
+    <string name="permgroupdesc_personalInfo">"端末の連絡先とカレンダーに直接アクセス"</string>
+    <string name="permgrouplab_location">"現在地"</string>
+    <string name="permgroupdesc_location">"現在地を追跡"</string>
+    <string name="permgrouplab_network">"ネットワーク通信"</string>
+    <string name="permgroupdesc_network">"ネットワークのさまざまな機能へのアクセスをアプリケーションに許可します。"</string>
+    <string name="permgrouplab_accounts">"Googleアカウント"</string>
+    <string name="permgroupdesc_accounts">"利用可能なGoogleアカウントへのアクセス"</string>
+    <string name="permgrouplab_hardwareControls">"ハードウェアの制御"</string>
+    <string name="permgroupdesc_hardwareControls">"携帯電話のハードウェアに直接アクセスします。"</string>
+    <string name="permgrouplab_phoneCalls">"電話/通話"</string>
+    <string name="permgroupdesc_phoneCalls">"通話の監視、記録、処理"</string>
+    <string name="permgrouplab_systemTools">"システムツール"</string>
+    <string name="permgroupdesc_systemTools">"システムの低レベルのアクセスと制御"</string>
+    <string name="permgrouplab_developmentTools">"開発ツール"</string>
+    <string name="permgroupdesc_developmentTools">"アプリケーションのデベロッパーにのみ必要な機能です。"</string>
+    <string name="permlab_statusBar">"ステータスバーの無効化や変更"</string>
+    <string name="permdesc_statusBar">"ステータスバーの無効化やシステムアイコンの追加や削除をアプリケーションに許可します。"</string>
+    <string name="permlab_expandStatusBar">"ステータスバーの拡大/縮小"</string>
+    <string name="permdesc_expandStatusBar">"ステータスバーの拡大や縮小をアプリケーションに許可します。"</string>
+    <string name="permlab_processOutgoingCalls">"発信の傍受"</string>
+    <string name="permdesc_processOutgoingCalls">"通話の発信とダイヤルする番号の変更とをアプリケーションに許可します。悪意のあるアプリケーションが発信を監視、転送、阻止する恐れがあります。"</string>
+    <string name="permlab_receiveSms">"SMSの受信"</string>
+    <string name="permdesc_receiveSms">"SMSメッセージの受信と処理をアプリケーションに許可します。悪意のあるアプリケーションがメッセージを監視したり、表示せずに削除する恐れがあります。"</string>
+    <string name="permlab_receiveMms">"MMSの受信"</string>
+    <string name="permdesc_receiveMms">"MMSメッセージの受信と処理をアプリケーションに許可します。悪意のあるアプリケーションがメッセージを監視したり、表示せずに削除する恐れがあります。"</string>
+    <string name="permlab_sendSms">"SMSメッセージの送信"</string>
+    <string name="permdesc_sendSms">"SMSメッセージの送信をアプリケーションに許可します。悪意のあるアプリケーションが確認なしでメッセージを送信し、料金が発生する恐れがあります。"</string>
+    <string name="permlab_readSms">"SMSやMMSの読み取り"</string>
+    <string name="permdesc_readSms">"携帯電話やSIMカードに保存したSMSメッセージの読み取りをアプリケーションに許可します。悪意のあるアプリケーションが機密メッセージを読み取る恐れがあります。"</string>
+    <string name="permlab_writeSms">"SMSやMMSの編集"</string>
+    <string name="permdesc_writeSms">"携帯電話やSIMカードに保存したSMSメッセージへの書き込みをアプリケーションに許可します。悪意のあるアプリケーションがメッセージを削除する恐れがあります。"</string>
+    <string name="permlab_receiveWapPush">"WAPの受信"</string>
+    <string name="permdesc_receiveWapPush">"WAPメッセージの受信と処理をアプリケーションに許可します。悪意のあるアプリケーションがメッセージを監視したり、表示せずに削除する恐れがあります。"</string>
+    <string name="permlab_getTasks">"実行中のアプリケーションの取得"</string>
+    <string name="permdesc_getTasks">"現在実行中または最近実行したタスクに関する情報の取得をアプリケーションに許可します。悪意のあるアプリケーションが他のアプリケーションの非公開情報を取得する恐れがあります。"</string>
+    <string name="permlab_reorderTasks">"実行中のアプリケーションの順序の変更"</string>
+    <string name="permdesc_reorderTasks">"タスクをフォアグラウンドやバックグラウンドに移動することをアプリケーションに許可します。悪意のあるアプリケーションが優先されて、コントロールできなくなる恐れがあります。"</string>
+    <string name="permlab_setDebugApp">"アプリケーションのデバッグを有効にする"</string>
+    <string name="permdesc_setDebugApp">"別のアプリケーションをデバッグモードにすることをアプリケーションに許可します。悪意のあるアプリケーションが別のアプリケーションを終了させる恐れがあります。"</string>
+    <string name="permlab_changeConfiguration">"UI設定の変更"</string>
+    <string name="permdesc_changeConfiguration">"地域/言語やフォントのサイズなど、現在の設定の変更をアプリケーションに許可します。"</string>
+    <string name="permlab_restartPackages">"他のアプリケーションの再起動"</string>
+    <string name="permdesc_restartPackages">"他のアプリケーションの強制的な再起動をアプリケーションに許可します。"</string>
+    <string name="permlab_setProcessForeground">"停止の阻止"</string>
+    <string name="permdesc_setProcessForeground">"フォアグラウンドでプロセスを実行して、強制終了できないようにすることをアプリケーションに許可します。通常のアプリケーションではまったく必要ありません。"</string>
+    <string name="permlab_forceBack">"アプリケーションの強制終了"</string>
+    <string name="permdesc_forceBack">"フォアグラウンドで実行されている操作を強制終了して戻ることをアプリケーションに許可します。通常のアプリケーションではまったく必要ありません。"</string>
+    <string name="permlab_dump">"システムの内部状態の取得"</string>
+    <string name="permdesc_dump">"システムの内部状態の取得をアプリケーションに許可します。悪意のあるアプリケーションが、通常は必要としない広範囲にわたる非公開の機密情報を取得する恐れがあります。"</string>
+    <string name="permlab_addSystemService">"低レベルサービスの公開"</string>
+    <string name="permdesc_addSystemService">"独自の低レベルのシステムサービスを公開することをアプリケーションに許可します。悪意のあるアプリケーションがシステムを乗っ取って、データの盗用や破壊をする恐れがあります。"</string>
+    <string name="permlab_runSetActivityWatcher">"起動中のすべてのアプリケーションの監視と制御"</string>
+    <string name="permdesc_runSetActivityWatcher">"システムが起動する操作の監視と制御をアプリケーションに許可します。悪意のあるアプリケーションがシステムを完全に破壊する恐れがあります。この許可は開発にのみ必要で、携帯電話の通常の使用にはまったく必要ありません。"</string>
+    <string name="permlab_broadcastPackageRemoved">"パッケージ削除ブロードキャストの送信"</string>
+    <string name="permdesc_broadcastPackageRemoved">"アプリケーションパッケージの削除の通知を配信することをアプリケーションに許可します。悪意のあるアプリケーションが他の実行中のアプリケーションを強制終了する恐れがあります。"</string>
+    <string name="permlab_broadcastSmsReceived">"SMS受信ブロードキャストの送信"</string>
+    <string name="permdesc_broadcastSmsReceived">"SMSメッセージの受信通知の配信をアプリケーションに許可します。悪意のあるアプリケーションが受信SMSメッセージを偽造する恐れがあります。"</string>
+    <string name="permlab_broadcastWapPush">"WAP-PUSH受信ブロードキャストの送信"</string>
+    <string name="permdesc_broadcastWapPush">"WAP PUSHメッセージの受信の通知を配信することをアプリケーションに許可します。悪意のあるアプリケーションがMMS受信メッセージを偽造したり、ウェブページのコンテンツを密かに改ざんする恐れがあります。"</string>
+    <string name="permlab_setProcessLimit">"実行中のプロセスの数を制限"</string>
+    <string name="permdesc_setProcessLimit">"実行するプロセス数の上限の制御をアプリケーションに許可します。通常のアプリケーションにはまったく必要ありません。"</string>
+    <string name="permlab_setAlwaysFinish">"バックグラウンドアプリケーションをすべて終了する"</string>
+    <string name="permdesc_setAlwaysFinish">"バックグラウンドになり次第必ず操作を終了させるかどうかの制御をアプリケーションに許可します。通常のアプリケーションではまったく必要ありません。"</string>
+    <string name="permlab_fotaUpdate">"システムアップデートの自動インストール"</string>
+    <string name="permdesc_fotaUpdate">"保留中のシステムアップデートに関する通知の受信とインストールの開始をアプリケーションに許可します。悪意のあるアプリケーションが許可なく更新を行ってシステムを破壊したり、更新処理を妨害する恐れがあります。"</string>
+    <string name="permlab_batteryStats">"電池統計情報の変国"</string>
+    <string name="permdesc_batteryStats">"収集した電池統計情報の変更を許可します。通常のアプリケーションでは使用しません。"</string>
+    <string name="permlab_internalSystemWindow">"未許可のウィンドウの表示"</string>
+    <string name="permdesc_internalSystemWindow">"内部システムのユーザーインターフェースで使用するためのウィンドウ作成を許可します。通常のアプリケーションでは使用しません。"</string>
+    <string name="permlab_systemAlertWindow">"システムレベルの警告の表示"</string>
+    <string name="permdesc_systemAlertWindow">"システムの警告ウィンドウの表示をアプリケーションに許可します。悪意のあるアプリケーションが携帯電話の画面全体を偽装する恐れがあります。"</string>
+    <string name="permlab_setAnimationScale">"アニメーションのプリセット速度の変更"</string>
+    <string name="permdesc_setAnimationScale">"いつでもアニメーション全般の速度を変更する (アニメーションを速くまたは遅くする) ことをアプリケーションに許可します。"</string>
+    <string name="permlab_manageAppTokens">"アプリケーショントークンの管理"</string>
+    <string name="permdesc_manageAppTokens">"通常のZ-orderingを回避して、独自のトークンを作成、管理することをアプリケーションに許可します。通常のアプリケーションではまったく必要ありません。"</string>
+    <string name="permlab_injectEvents">"キーを押してボタンをコントロール"</string>
+    <string name="permdesc_injectEvents">"入力イベント (キーを押すなど) を別のアプリケーションに伝えることをアプリケーションに許可します。これにより悪意のあるアプリケーションが、携帯電話を乗っ取る恐れがあります。"</string>
+    <string name="permlab_readInputState">"入力や操作の記録"</string>
+    <string name="permdesc_readInputState">"別のアプリケーションへの入力(パスワードなど)でもキー入力を監視することをアプリケーションに許可します。通常のアプリケーションではまったく必要ありません。"</string>
+    <string name="permlab_bindInputMethod">"入力方法に関連付ける"</string>
+    <string name="permdesc_bindInputMethod">"入力方法のトップレベルインターフェースに関連付けることを所有者に許可します。通常のアプリケーションにはまったく必要ありません。"</string>
+    <string name="permlab_setOrientation">"画面の向きの変更"</string>
+    <string name="permdesc_setOrientation">"いつでも画面の回転を変更することをアプリケーションに許可します。通常のアプリケーションにはまったく必要ありません。"</string>
+    <string name="permlab_signalPersistentProcesses">"Linuxのシグナルをアプリケーションに送信"</string>
+    <string name="permdesc_signalPersistentProcesses">"受信した電波を継続プロセスに送信することをアプリケーションに許可します。"</string>
+    <string name="permlab_persistentActivity">"アプリケーションを常に実行する"</string>
+    <string name="permdesc_persistentActivity">"自身を部分的に永続させて、他のアプリケーション用にはその領域をシステムに使わせないようにすることをアプリケーションに許可します。"</string>
+    <string name="permlab_deletePackages">"アプリケーションの削除"</string>
+    <string name="permdesc_deletePackages">"Androidパッケージの削除をアプリケーションに許可します。悪意のあるアプリケーションが、重要なアプリケーションを削除する恐れがあります。"</string>
+    <string name="permlab_clearAppUserData">"他のアプリケーションのデータを削除"</string>
+    <string name="permdesc_clearAppUserData">"ユーザーデータの消去をアプリケーションに許可します。"</string>
+    <string name="permlab_deleteCacheFiles">"他のアプリケーションのキャッシュを削除"</string>
+    <string name="permdesc_deleteCacheFiles">"キャッシュファイルの削除をアプリケーションに許可します。"</string>
+    <string name="permlab_getPackageSize">"アプリケーションのメモリ容量の計測"</string>
+    <string name="permdesc_getPackageSize">"アプリケーションのコード、データ、キャッシュのサイズを取得することを許可します。"</string>
+    <string name="permlab_installPackages">"アプリケーションを直接インストール"</string>
+    <string name="permdesc_installPackages">"Androidパッケージのインストール/更新をアプリケーションに許可します。悪意のあるアプリケーションが、勝手に強力な権限を持つ新しいアプリケーションを追加する恐れがあります。"</string>
+    <string name="permlab_clearAppCache">"アプリケーションキャッシュデータの削除"</string>
+    <string name="permdesc_clearAppCache">"アプリケーションのキャッシュディレクトリからファイルを削除して携帯電話のメモリを解放することをアプリケーションに許可します。通常、アクセスはシステムプロセスのみに制限されます。"</string>
+    <string name="permlab_readLogs">"システムログファイルの読み取り"</string>
+    <string name="permdesc_readLogs">"システムのさまざまなログファイルの読み取りをアプリケーションに許可します。これにより携帯電話の使用状況に関する全般情報が取得されますが、個人情報や非公開情報が含まれることはありません。"</string>
+    <string name="permlab_diagnostic">"diagが所有するリソースの読み書き"</string>
+    <string name="permdesc_diagnostic">"diagグループが所有するリソース(例:/dev内のファイル)への読み書きをアプリケーションに許可します。システムの安定性とセキュリティに影響する恐れがあります。メーカー/オペレーターによるハードウェア固有の診断以外には使用しないでください。"</string>
+    <string name="permlab_changeComponentState">"アプリケーションのコンポーネントを有効/無効にする"</string>
+    <string name="permdesc_changeComponentState">"別アプリケーションのコンポーネントの有効/無効を変更することをアプリケーションに許可します。これにより悪意のあるアプリケーションが、携帯電話の重要な機能を無効にする恐れがあります。アプリケーションコンポーネントが利用できない、整合性が取れない、または不安定な状態になる恐れがあるので、許可には注意が必要です。"</string>
+    <string name="permlab_setPreferredApplications">"優先アプリケーションの設定"</string>
+    <string name="permdesc_setPreferredApplications">"優先アプリケーションを変更することをアプリケーションに許可します。悪意のあるアプリケーションが実行中のアプリケーションを密かに変更し、既存のアプリケーションになりすまして非公開データを収集する恐れがあります。"</string>
+    <string name="permlab_writeSettings">"システムの全般設定の変更"</string>
+    <string name="permdesc_writeSettings">"システム設定データの変更をアプリケーションに許可します。悪意のあるアプリケーションがシステム設定を破壊する恐れがあります。"</string>
+    <string name="permlab_writeSecureSettings">"システムのセキュリティ設定の変更"</string>
+    <string name="permdesc_writeSecureSettings">"システムのセキュリティ設定の変更をアプリケーションに許可します。通常のアプリケーションでは使用しません。"</string>
+    <string name="permlab_writeGservices">"Googleサービスの地図の変更"</string>
+    <string name="permdesc_writeGservices">"Googleサービスマップの変更をアプリケーションに許可します。通常のアプリケーションでは使用しません。"</string>
+    <string name="permlab_receiveBootCompleted">"起動時に自動的に開始"</string>
+    <string name="permdesc_receiveBootCompleted">"システムの起動後に自動的に起動することをアプリケーションに許可します。携帯電話の起動に時間がかかるようになり、アプリケーションが常に実行されるために携帯電話の全体的な動作が遅くなります。"</string>
+    <string name="permlab_broadcastSticky">"stickyブロードキャストの配信"</string>
+    <string name="permdesc_broadcastSticky">"配信が終了してもメモリに残るようなstickyブロードキャストをアプリケーションに許可します。悪意のあるアプリケーションがメモリを使いすぎて、携帯電話の動作が遅くなったり、不安定になる恐れがあります。"</string>
+    <string name="permlab_readContacts">"連絡先データの読み取り"</string>
+    <string name="permdesc_readContacts">"端末に保存した連絡先(アドレス)データの読み取りをアプリケーションに許可します。悪意のあるアプリケーションがデータを他人に送信する恐れがあります。"</string>
+    <string name="permlab_writeContacts">"連絡先データの書き込み"</string>
+    <string name="permdesc_writeContacts">"端末に保存した連絡先(アドレス)データの変更をアプリケーションに許可します。悪意のあるアプリケーションが連絡先データを消去/変更する恐れがあります。"</string>
+    <string name="permlab_writeOwnerData">"所有者データの書き込み"</string>
+    <string name="permdesc_writeOwnerData">"端末に保存した所有者のデータの変更をアプリケーションに許可します。悪意のあるアプリケーションが所有者のデータを消去/変更する恐れがあります。"</string>
+    <string name="permlab_readOwnerData">"所有者データの読み取り"</string>
+    <string name="permdesc_readOwnerData">"携帯電話に保存した所有者データの読み取りをアプリケーションに許可します。悪意のあるアプリケーションが所有者データを読み取る恐れがあります。"</string>
+    <string name="permlab_readCalendar">"カレンダーデータの読み取り"</string>
+    <string name="permdesc_readCalendar">"端末に保存したカレンダーの予定の読み取りをアプリケーションに許可します。悪意のあるアプリケーションがカレンダーの予定を他人に送信する恐れがあります。"</string>
+    <string name="permlab_writeCalendar">"カレンダーデータの書き込み"</string>
+    <string name="permdesc_writeCalendar">"端末に保存したカレンダーの予定の変更をアプリケーションに許可します。悪意のあるアプリケーションが、カレンダーデータを消去/変更する恐れがあります。"</string>
+    <string name="permlab_accessMockLocation">"仮の位置情報でテスト"</string>
+    <string name="permdesc_accessMockLocation">"テスト用に仮の位置情報源を作成します。これにより悪意のあるアプリケーションが、GPS、ネットワークプロバイダなどから返される本当の位置情報や状況を改ざんする恐れがあります。"</string>
+    <string name="permlab_accessLocationExtraCommands">"位置情報プロバイダのその他のコマンドへのアクセス"</string>
+    <string name="permdesc_accessLocationExtraCommands">"位置情報提供元の追加コマンドにアクセスします。悪意のあるアプリケーションがGPSなどの位置提供の動作を妨害する恐れがあります。"</string>
+    <string name="permlab_accessFineLocation">"精細な位置情報(GPS)"</string>
+    <string name="permdesc_accessFineLocation">"GPSなど携帯電話の位置情報にアクセスします(可能な場合)。今いる場所が悪意のあるアプリケーションに検出されたり、バッテリーの消費が増える恐れがあります。"</string>
+    <string name="permlab_accessCoarseLocation">"おおよその位置情報(ネットワーク基地局)"</string>
+    <string name="permdesc_accessCoarseLocation">"セルラーネットワークデータベースなど、携帯電話のおおよその位置を特定する情報源が利用可能な場合にアクセスします。これにより悪意のあるアプリケーションが、ユーザーのおおよその位置を特定できる恐れがあります。"</string>
+    <string name="permlab_accessSurfaceFlinger">"SurfaceFlingerへのアクセス"</string>
+    <string name="permdesc_accessSurfaceFlinger">"SurfaceFlingerの低レベルの機能の使用をアプリケーションに許可します。"</string>
+    <string name="permlab_readFrameBuffer">"フレームバッファの読み取り"</string>
+    <string name="permdesc_readFrameBuffer">"フレームバッファの内容の読み取りと使用をアプリケーションに許可します。"</string>
+    <string name="permlab_modifyAudioSettings">"音声設定の変更"</string>
+    <string name="permdesc_modifyAudioSettings">"音量や転送などの音声全般の設定の変更をアプリケーションに許可します。"</string>
+    <string name="permlab_recordAudio">"録音"</string>
+    <string name="permdesc_recordAudio">"オーディオ録音パスへのアクセスをアプリケーションに許可します。"</string>
+    <string name="permlab_camera">"写真の撮影"</string>
+    <string name="permdesc_camera">"カメラでの写真撮影をアプリケーションに許可します。アプリケーションはカメラから画像をいつでも収集できます。"</string>
+    <string name="permlab_brick">"端末を永続的に無効にする"</string>
+    <string name="permdesc_brick">"携帯電話全体を永続的に無効にすることをアプリケーションに許可します。この許可は非常に危険です。"</string>
+    <string name="permlab_reboot">"端末の再起動"</string>
+    <string name="permdesc_reboot">"端末の強制的な再起動をアプリケーションに許可します。"</string>
+    <string name="permlab_mount_unmount_filesystems">"ファイルシステムのマウントとマウント解除"</string>
+    <string name="permdesc_mount_unmount_filesystems">"リムーバブルメモリのファイルシステムのマウントとマウント解除をアプリケーションに許可します。"</string>
+    <string name="permlab_mount_format_filesystems">"外部ストレージのフォーマット"</string>
+    <string name="permdesc_mount_format_filesystems">"アプリケーションがリムーバブルストレージをフォーマットすることを許可します。"</string>
+    <string name="permlab_vibrate">"バイブレーション制御"</string>
+    <string name="permdesc_vibrate">"バイブレーションの制御をアプリケーションに許可します。"</string>
+    <string name="permlab_flashlight">"ライトのコントロール"</string>
+    <string name="permdesc_flashlight">"ライトの制御をアプリケーションに許可します。"</string>
+    <string name="permlab_hardware_test">"ハードウェアのテスト"</string>
+    <string name="permdesc_hardware_test">"ハードウェアのテストのためにさまざまな周辺機器を制御することをアプリケーションに許可します。"</string>
+    <string name="permlab_callPhone">"電話番号発信"</string>
+    <string name="permdesc_callPhone">"アプリケーションが電話を自動発信することを許可します。悪意のあるアプリケーションが意図しない電話をかけて料金が発生する恐れがあります。緊急呼への発信は許可しません。"</string>
+    <string name="permlab_callPrivileged">"電話番号発信"</string>
+    <string name="permdesc_callPrivileged">"緊急呼を含めあらゆる電話番号に自動発信することをアプリケーションに許可します。悪意のあるアプリケーションが緊急サービスに不正な通報をする恐れがあります。"</string>
+    <string name="permlab_locationUpdates">"位置情報の更新通知"</string>
+    <string name="permdesc_locationUpdates">"無線通信からの位置更新通知を有効/無効にすることを許可します。通常のアプリケーションでは使用しません。"</string>
+    <string name="permlab_checkinProperties">"チェックインプロパティへのアクセス"</string>
+    <string name="permdesc_checkinProperties">"チェックインサービスがアップロードしたプロパティへの読み書きを許可します。通常のアプリケーションでは使用しません。"</string>
+    <string name="permlab_bindGadget">"ガジェットの選択"</string>
+    <string name="permdesc_bindGadget">"特定のアプリケーションで使用可能なガジェットをシステムに指定することをアプリケーションに許可します。この許可を受けたアプリケーションは、他のアプリケーションに個人データへのアクセスを許可することができます。通常のアプリケーションでは使用しません。"</string>
+    <string name="permlab_modifyPhoneState">"端末ステータスの変更"</string>
+    <string name="permdesc_modifyPhoneState">"端末の電話機能のコントロールをアプリケーションに許可します。アプリケーションは、ネットワークの切り替え、携帯電話の無線通信のオン/オフなどを通知せずに行うことができます。"</string>
+    <string name="permlab_readPhoneState">"端末ステータスの読み取り"</string>
+    <string name="permdesc_readPhoneState">"端末の電話機能へのアクセスをアプリケーションに許可します。アプリケーションは、この携帯電話の電話番号、通話中かどうか、通話相手の電話番号などを特定できます。"</string>
+    <string name="permlab_wakeLock">"端末のスリープを無効にする"</string>
+    <string name="permdesc_wakeLock">"端末のスリープを無効にすることをアプリケーションに許可します。"</string>
+    <string name="permlab_devicePower">"携帯電話の電源のオン/オフ"</string>
+    <string name="permdesc_devicePower">"携帯電話の電源のオン/オフをアプリケーションに許可します。"</string>
+    <string name="permlab_factoryTest">"出荷時試験モードでの実行"</string>
+    <string name="permdesc_factoryTest">"携帯電話のハードウェアへのアクセスを完全に許可して、低レベルのメーカーテストとして実行します。メーカーのテストモードで携帯電話を使用するときのみ利用できます。"</string>
+    <string name="permlab_setWallpaper">"壁紙の設定"</string>
+    <string name="permdesc_setWallpaper">"システムの壁紙の設定をアプリケーションに許可します。"</string>
+    <string name="permlab_setWallpaperHints">"壁紙サイズのヒントの設定"</string>
+    <string name="permdesc_setWallpaperHints">"システムの壁紙サイズのヒントの設定をアプリケーションに許可します。"</string>
+    <string name="permlab_masterClear">"システムを出荷時設定にリセット"</string>
+    <string name="permdesc_masterClear">"データ、設定、インストールしたアプリケーションをすべて消去して、完全に出荷時の設定にシステムをリセットすることをアプリケーションに許可します。"</string>
+    <string name="permlab_setTimeZone">"タイムゾーンの設定"</string>
+    <string name="permdesc_setTimeZone">"端末のタイムゾーンの変更をアプリケーションに許可します。"</string>
+    <string name="permlab_getAccounts">"既知のアカウントの取得"</string>
+    <string name="permdesc_getAccounts">"端末内にあるアカウントのリストの取得をアプリケーションに許可します。"</string>
+    <string name="permlab_accessNetworkState">"ネットワーク状態の表示"</string>
+    <string name="permdesc_accessNetworkState">"すべてのネットワーク状態の表示をアプリケーションに許可します。"</string>
+    <string name="permlab_createNetworkSockets">"完全なインターネットアクセス"</string>
+    <string name="permdesc_createNetworkSockets">"ネットワークソケットの作成をアプリケーションに許可します。"</string>
+    <string name="permlab_writeApnSettings">"アクセスポイント名設定の書き込み"</string>
+    <string name="permdesc_writeApnSettings">"APNのプロキシやポートなどのAPN設定の変更をアプリケーションに許可します。"</string>
+    <string name="permlab_changeNetworkState">"ネットワーク接続の変更"</string>
+    <string name="permdesc_changeNetworkState">"ネットワークの接続状態の変更をアプリケーションに許可します。"</string>
+    <string name="permlab_changeBackgroundDataSetting">"バックグラウンドデータ使用の設定の変更"</string>
+    <string name="permdesc_changeBackgroundDataSetting">"バックグラウンドデータ使用の設定の変更をアプリケーションに許可します。"</string>
+    <string name="permlab_accessWifiState">"Wi-Fi状態の表示"</string>
+    <string name="permdesc_accessWifiState">"Wi-Fi状態に関する情報の表示をアプリケーションに許可します。"</string>
+    <string name="permlab_changeWifiState">"Wi-Fi状態の変更"</string>
+    <string name="permdesc_changeWifiState">"Wi-Fiアクセスポイントへの接続や接続の切断、設定されたWi-Fiネットワークの変更をアプリケーションに許可します。"</string>
+    <string name="permlab_bluetoothAdmin">"Bluetoothの管理"</string>
+    <string name="permdesc_bluetoothAdmin">"このBluetooth端末の設定、およびリモート端末を検出してペアに設定することをアプリケーションに許可します。"</string>
+    <string name="permlab_bluetooth">"Bluetooth接続の作成"</string>
+    <string name="permdesc_bluetooth">"このBluetooth端末の設定表示、および別の端末をペアとして設定し接続を承認することをアプリケーションに許可します。"</string>
+    <string name="permlab_disableKeyguard">"キーロックを無効にする"</string>
+    <string name="permdesc_disableKeyguard">"キーロックや関連するパスワードセキュリティを無効にすることをアプリケーションに許可します。正当な利用の例では、かかってきた電話を受信する際にキーロックを無効にし、通話の終了時にキーロックを有効にし直します。"</string>
+    <string name="permlab_readSyncSettings">"同期設定の読み取り"</string>
+    <string name="permdesc_readSyncSettings">"連絡先の同期の有効/無効など、同期設定の読み取りをアプリケーションに許可します。"</string>
+    <string name="permlab_writeSyncSettings">"同期設定の書き込み"</string>
+    <string name="permdesc_writeSyncSettings">"連絡先の同期の有効/無効など、同期設定の変更をアプリケーションに許可します。"</string>
+    <string name="permlab_readSyncStats">"同期統計の読み取り"</string>
+    <string name="permdesc_readSyncStats">"同期状態(同期履歴など)の読み取りをアプリケーションに許可します。"</string>
+    <string name="permlab_subscribedFeedsRead">"登録したフィードの読み取り"</string>
+    <string name="permdesc_subscribedFeedsRead">"現在同期しているフィードの詳細の取得をアプリケーションに許可します。"</string>
+    <string name="permlab_subscribedFeedsWrite">"登録したフィードの書き込み"</string>
+    <string name="permdesc_subscribedFeedsWrite">"現在同期しているフィードの変更をアプリケーションに許可します。悪意のあるアプリケーションが同期フィードを変更する恐れがあります。"</string>
+    <string name="permlab_readDictionary">"ユーザー定義辞書の読み込み"</string>
+    <string name="permdesc_readDictionary">"アプリケーションがユーザー辞書に登録されている個人的な語句や名前を読み込むことを許可します。"</string>
+    <string name="permlab_writeDictionary">"ユーザー定義辞書への書き込み"</string>
+    <string name="permdesc_writeDictionary">"アプリケーションがユーザー辞書に新しい語句を書き込むことを許可します。"</string>
+  <string-array name="phoneTypes">
+    <item>"自宅"</item>
+    <item>"携帯"</item>
+    <item>"仕事"</item>
+    <item>"FAX(仕事)"</item>
+    <item>"FAX(自宅)"</item>
+    <item>"ポケベル"</item>
+    <item>"その他"</item>
+    <item>"カスタム"</item>
+  </string-array>
+  <string-array name="emailAddressTypes">
+    <item>"自宅"</item>
+    <item>"仕事"</item>
+    <item>"その他"</item>
+    <item>"カスタム"</item>
+  </string-array>
+  <string-array name="postalAddressTypes">
+    <item>"自宅"</item>
+    <item>"仕事"</item>
+    <item>"その他"</item>
+    <item>"カスタム"</item>
+  </string-array>
+  <string-array name="imAddressTypes">
+    <item>"自宅"</item>
+    <item>"仕事"</item>
+    <item>"その他"</item>
+    <item>"カスタム"</item>
+  </string-array>
+  <string-array name="organizationTypes">
+    <item>"仕事"</item>
+    <item>"その他"</item>
+    <item>"カスタム"</item>
+  </string-array>
+  <string-array name="imProtocols">
+    <item>"AIM"</item>
+    <item>"Windows Live"</item>
+    <item>"Yahoo"</item>
+    <item>"Skype"</item>
+    <item>"QQ"</item>
+    <item>"Googleトーク"</item>
+    <item>"ICQ"</item>
+    <item>"Jabber"</item>
+  </string-array>
+    <string name="keyguard_password_enter_pin_code">"PINコードを入力"</string>
+    <string name="keyguard_password_wrong_pin_code">"PINコードが正しくありません。"</string>
+    <string name="keyguard_label_text">"ロックを解除するにはMENU、0キーの順に押します。"</string>
+    <string name="emergency_call_dialog_number_for_display">"緊急呼番号"</string>
+    <string name="lockscreen_carrier_default">"(サービス登録なし)"</string>
+    <string name="lockscreen_screen_locked">"画面ロック中"</string>
+    <string name="lockscreen_instructions_when_pattern_enabled">"MENUキーでロック解除(または緊急呼)"</string>
+    <string name="lockscreen_instructions_when_pattern_disabled">"MENUキーでロック解除"</string>
+    <string name="lockscreen_pattern_instructions">"ロックを解除するパターンを入力"</string>
+    <string name="lockscreen_emergency_call">"緊急呼"</string>
+    <string name="lockscreen_pattern_correct">"一致しました"</string>
+    <string name="lockscreen_pattern_wrong">"やり直してください"</string>
+    <!-- no translation found for lockscreen_plugged_in (613343852842944435) -->
+    <skip />
+    <string name="lockscreen_low_battery">"充電してください。"</string>
+    <string name="lockscreen_missing_sim_message_short">"SIMカードが挿入されていません"</string>
+    <string name="lockscreen_missing_sim_message">"SIMカードが挿入されていません"</string>
+    <string name="lockscreen_missing_sim_instructions">"SIMカードを挿入してください。"</string>
+    <string name="lockscreen_network_locked_message">"ネットワークがロックされました"</string>
+    <string name="lockscreen_sim_puk_locked_message">"SIMカードはPUKでロックされています。"</string>
+    <string name="lockscreen_sim_puk_locked_instructions">"お客様サポートにお問い合わせください。"</string>
+    <string name="lockscreen_sim_locked_message">"SIMカードはロックされています。"</string>
+    <string name="lockscreen_sim_unlock_progress_dialog_message">"SIMカードのロック解除中..."</string>
+    <string name="lockscreen_too_many_failed_attempts_dialog_message">"ロック解除のパターンは<xliff:g id="NUMBER_0">%d</xliff:g>回とも正しく指定されていません。"\n\n"<xliff:g id="NUMBER_1">%d</xliff:g>秒後にもう一度指定してください。"</string>
+    <string name="lockscreen_failed_attempts_almost_glogin">"指定したパターンは<xliff:g id="NUMBER_0">%d</xliff:g>回とも正しくありません。あと<xliff:g id="NUMBER_1">%d</xliff:g>回指定に失敗すると、携帯電話のロックの解除にGoogleへのログインが必要になります。"\n\n"<xliff:g id="NUMBER_2">%d</xliff:g>秒後にもう一度指定してください。"</string>
+    <string name="lockscreen_too_many_failed_attempts_countdown">"<xliff:g id="NUMBER">%d</xliff:g>秒後にやり直してください。"</string>
+    <string name="lockscreen_forgot_pattern_button_text">"パターンを忘れた場合"</string>
+    <string name="lockscreen_glogin_too_many_attempts">"パターンのエラーが多すぎます"</string>
+    <string name="lockscreen_glogin_instructions">"ロックを解除するには、"\n"Googleアカウントでログインしてください。"</string>
+    <string name="lockscreen_glogin_username_hint">"ユーザー名 (メール)"</string>
+    <string name="lockscreen_glogin_password_hint">"パスワード"</string>
+    <string name="lockscreen_glogin_submit_button">"ログイン"</string>
+    <string name="lockscreen_glogin_invalid_input">"ユーザー名またはパスワードが正しくありません。"</string>
+    <string name="status_bar_time_format">"<xliff:g id="HOUR">h</xliff:g>:<xliff:g id="MINUTE">mm</xliff:g> <xliff:g id="AMPM">AA</xliff:g>"</string>
+    <string name="hour_minute_ampm">"<xliff:g id="HOUR">%-l</xliff:g>:<xliff:g id="MINUTE">%M</xliff:g><xliff:g id="AMPM">%P</xliff:g>"</string>
+    <string name="hour_minute_cap_ampm">"<xliff:g id="HOUR">%-l</xliff:g>:<xliff:g id="MINUTE">%M</xliff:g><xliff:g id="AMPM">%p</xliff:g>"</string>
+    <!-- no translation found for hour_ampm (4329881288269772723) -->
+    <skip />
+    <!-- no translation found for hour_cap_ampm (1829009197680861107) -->
+    <skip />
+    <string name="status_bar_clear_all_button">"通知を消去"</string>
+    <string name="status_bar_no_notifications_title">"通知なし"</string>
+    <string name="status_bar_ongoing_events_title">"継続中"</string>
+    <string name="status_bar_latest_events_title">"通知"</string>
+    <!-- no translation found for battery_status_text_percent_format (7660311274698797147) -->
+    <skip />
+    <string name="battery_status_charging">"充電中..."</string>
+    <string name="battery_low_title">"充電してください"</string>
+    <string name="battery_low_subtitle">"電池が残り少なくなっています:"</string>
+    <string name="battery_low_percent_format">"残り<xliff:g id="NUMBER">%d%%</xliff:g>未満"</string>
+    <string name="factorytest_failed">"出荷時試験が失敗"</string>
+    <string name="factorytest_not_system">"FACTORY_TEST操作は、/system/appにインストールされたパッケージのみが対象です。"</string>
+    <string name="factorytest_no_action">"FACTORY_TEST操作を行うパッケージは見つかりませんでした。"</string>
+    <string name="factorytest_reboot">"再起動"</string>
+    <string name="js_dialog_title">"ページ「<xliff:g id="TITLE">%s</xliff:g>」の記述:"</string>
+    <string name="js_dialog_title_default">"JavaScript"</string>
+    <string name="js_dialog_before_unload">"このページから移動しますか?"\n\n"<xliff:g id="MESSAGE">%s</xliff:g>"\n\n"続行する場合は[OK]、今のページに残る場合は[キャンセル]を選択してください。"</string>
+    <string name="save_password_label">"確認"</string>
+    <string name="save_password_message">"このパスワードをブラウザで保存しますか?"</string>
+    <string name="save_password_notnow">"今は保存しない"</string>
+    <string name="save_password_remember">"保存"</string>
+    <string name="save_password_never">"保存しない"</string>
+    <string name="open_permission_deny">"このページへのアクセスは許可されていません。"</string>
+    <string name="text_copied">"テキストをクリップボードにコピーしました。"</string>
+    <string name="more_item_label">"その他"</string>
+    <string name="prepend_shortcut_label">"MENU+"</string>
+    <string name="menu_space_shortcut_label">"Space"</string>
+    <string name="menu_enter_shortcut_label">"Enter"</string>
+    <string name="menu_delete_shortcut_label">"Del"</string>
+    <string name="search_go">"検索"</string>
+    <string name="today">"今日"</string>
+    <string name="yesterday">"昨日"</string>
+    <string name="tomorrow">"明日"</string>
+    <string name="oneMonthDurationPast">"1か月前"</string>
+    <string name="beforeOneMonthDurationPast">"1か月前"</string>
+  <plurals name="num_seconds_ago">
+    <item quantity="one">"1秒前"</item>
+    <item quantity="other">"<xliff:g id="COUNT">%d</xliff:g>秒前"</item>
+  </plurals>
+  <plurals name="num_minutes_ago">
+    <item quantity="one">"1分前"</item>
+    <item quantity="other">"<xliff:g id="COUNT">%d</xliff:g>分前"</item>
+  </plurals>
+  <plurals name="num_hours_ago">
+    <item quantity="one">"1時間前"</item>
+    <item quantity="other">"<xliff:g id="COUNT">%d</xliff:g>時間前"</item>
+  </plurals>
+  <plurals name="num_days_ago">
+    <item quantity="one">"昨日"</item>
+    <item quantity="other">"<xliff:g id="COUNT">%d</xliff:g>日前"</item>
+  </plurals>
+  <plurals name="in_num_seconds">
+    <item quantity="one">"1秒後"</item>
+    <item quantity="other">"<xliff:g id="COUNT">%d</xliff:g>秒後"</item>
+  </plurals>
+  <plurals name="in_num_minutes">
+    <item quantity="one">"1分後"</item>
+    <item quantity="other">"<xliff:g id="COUNT">%d</xliff:g>分後"</item>
+  </plurals>
+  <plurals name="in_num_hours">
+    <item quantity="one">"1時間後"</item>
+    <item quantity="other">"<xliff:g id="COUNT">%d</xliff:g>時間後"</item>
+  </plurals>
+  <plurals name="in_num_days">
+    <item quantity="one">"明日"</item>
+    <item quantity="other">"<xliff:g id="COUNT">%d</xliff:g>日後"</item>
+  </plurals>
+  <plurals name="abbrev_num_seconds_ago">
+    <item quantity="one">"1秒前"</item>
+    <item quantity="other">"<xliff:g id="COUNT">%d</xliff:g>秒前"</item>
+  </plurals>
+  <plurals name="abbrev_num_minutes_ago">
+    <item quantity="one">"1分前"</item>
+    <item quantity="other">"<xliff:g id="COUNT">%d</xliff:g>分後"</item>
+  </plurals>
+  <plurals name="abbrev_num_hours_ago">
+    <item quantity="one">"1時間前"</item>
+    <item quantity="other">"<xliff:g id="COUNT">%d</xliff:g>時間前"</item>
+  </plurals>
+  <plurals name="abbrev_num_days_ago">
+    <item quantity="one">"昨日"</item>
+    <item quantity="other">"<xliff:g id="COUNT">%d</xliff:g>日前"</item>
+  </plurals>
+  <plurals name="abbrev_in_num_seconds">
+    <item quantity="one">"1秒後"</item>
+    <item quantity="other">"<xliff:g id="COUNT">%d</xliff:g>秒後"</item>
+  </plurals>
+  <plurals name="abbrev_in_num_minutes">
+    <item quantity="one">"1分後"</item>
+    <item quantity="other">"<xliff:g id="COUNT">%d</xliff:g>分後"</item>
+  </plurals>
+  <plurals name="abbrev_in_num_hours">
+    <item quantity="one">"1時間後"</item>
+    <item quantity="other">"<xliff:g id="COUNT">%d</xliff:g>時間後"</item>
+  </plurals>
+  <plurals name="abbrev_in_num_days">
+    <item quantity="one">"明日"</item>
+    <item quantity="other">"<xliff:g id="COUNT">%d</xliff:g>日後"</item>
+  </plurals>
+    <string name="preposition_for_date">"%s"</string>
+    <string name="preposition_for_time">"%s"</string>
+    <string name="preposition_for_year">"%s年"</string>
+    <string name="day">"日"</string>
+    <string name="days">"日"</string>
+    <string name="hour">"時間"</string>
+    <string name="hours">"時間"</string>
+    <string name="minute">"分"</string>
+    <string name="minutes">"分"</string>
+    <string name="second">"秒"</string>
+    <string name="seconds">"秒"</string>
+    <string name="week">"週間"</string>
+    <string name="weeks">"週間"</string>
+    <string name="year">"年"</string>
+    <string name="years">"年"</string>
+    <string name="sunday">"日曜日"</string>
+    <string name="monday">"月曜日"</string>
+    <string name="tuesday">"火曜日"</string>
+    <string name="wednesday">"水曜日"</string>
+    <string name="thursday">"木曜日"</string>
+    <string name="friday">"金曜日"</string>
+    <string name="saturday">"土曜日"</string>
+    <string name="every_weekday">"平日(月~金)"</string>
+    <string name="daily">"毎日"</string>
+    <string name="weekly">"毎週<xliff:g id="DAY">%s</xliff:g>"</string>
+    <string name="monthly">"毎月"</string>
+    <string name="yearly">"毎年"</string>
+    <string name="VideoView_error_title">"動画を再生できません"</string>
+    <string name="VideoView_error_text_unknown">"この動画は再生できません。"</string>
+    <string name="VideoView_error_button">"OK"</string>
+    <string name="am">"AM"</string>
+    <string name="pm">"PM"</string>
+    <string name="numeric_date">"<xliff:g id="YEAR">%Y</xliff:g>/<xliff:g id="MONTH">%m</xliff:g>/<xliff:g id="DAY">%d</xliff:g>"</string>
+    <string name="wday1_date1_time1_wday2_date2_time2">"<xliff:g id="DATE1">%2$s</xliff:g><xliff:g id="WEEKDAY1">%1$s</xliff:g><xliff:g id="TIME1">%3$s</xliff:g>~<xliff:g id="DATE2">%5$s</xliff:g><xliff:g id="WEEKDAY2">%4$s</xliff:g><xliff:g id="TIME2">%6$s</xliff:g>"</string>
+    <string name="wday1_date1_wday2_date2">"<xliff:g id="DATE1">%2$s</xliff:g><xliff:g id="WEEKDAY1">%1$s</xliff:g>~<xliff:g id="DATE2">%5$s</xliff:g><xliff:g id="WEEKDAY2">%4$s</xliff:g>"</string>
+    <string name="date1_time1_date2_time2">"<xliff:g id="DATE1">%2$s</xliff:g> <xliff:g id="TIME1">%3$s</xliff:g>~<xliff:g id="DATE2">%5$s</xliff:g> <xliff:g id="TIME2">%6$s</xliff:g>"</string>
+    <string name="date1_date2">"<xliff:g id="DATE1">%2$s</xliff:g>~<xliff:g id="DATE2">%5$s</xliff:g>"</string>
+    <string name="time1_time2">"<xliff:g id="TIME1">%1$s</xliff:g>~<xliff:g id="TIME2">%2$s</xliff:g>"</string>
+    <string name="time_wday_date">"<xliff:g id="DATE">%3$s</xliff:g><xliff:g id="WEEKDAY">%2$s</xliff:g><xliff:g id="TIME_RANGE">%1$s</xliff:g>"</string>
+    <string name="wday_date">"<xliff:g id="DATE">%3$s</xliff:g><xliff:g id="WEEKDAY">%2$s</xliff:g>"</string>
+    <string name="time_date">"<xliff:g id="DATE">%3$s</xliff:g>、<xliff:g id="TIME_RANGE">%1$s</xliff:g>"</string>
+    <string name="date_time">"<xliff:g id="DATE">%1$s</xliff:g>、<xliff:g id="TIME">%2$s</xliff:g>"</string>
+    <string name="relative_time">"<xliff:g id="DATE">%1$s</xliff:g>、<xliff:g id="TIME">%2$s</xliff:g>"</string>
+    <string name="time_wday">"<xliff:g id="WEEKDAY">%2$s</xliff:g>、<xliff:g id="TIME_RANGE">%1$s</xliff:g>"</string>
+    <string name="full_date_month_first" format="date">"<xliff:g id="YEAR">yyyy</xliff:g>'/'<xliff:g id="MONTH">MMMM</xliff:g>'/'<xliff:g id="DAY">d</xliff:g>"</string>
+    <string name="full_date_day_first" format="date">"<xliff:g id="YEAR">yyyy</xliff:g>'年'<xliff:g id="MONTH">MMMM</xliff:g>'月'<xliff:g id="DAY">d</xliff:g>'日'"</string>
+    <string name="medium_date_month_first" format="date">"<xliff:g id="MONTH">MMM</xliff:g>'/'<xliff:g id="DAY">d</xliff:g>' '<xliff:g id="YEAR">yyyy</xliff:g>'年'"</string>
+    <string name="medium_date_day_first" format="date">"<xliff:g id="DAY">d</xliff:g>'/'<xliff:g id="MONTH">MMM</xliff:g>'/'<xliff:g id="YEAR">yyyy</xliff:g>"</string>
+    <string name="twelve_hour_time_format" format="date">"<xliff:g id="HOUR">h</xliff:g>':'<xliff:g id="MINUTE">mm</xliff:g>' '<xliff:g id="AMPM">a</xliff:g>"</string>
+    <string name="twenty_four_hour_time_format" format="date">"<xliff:g id="HOUR">H</xliff:g>':'<xliff:g id="MINUTE">mm</xliff:g>"</string>
+    <string name="noon">"正午"</string>
+    <string name="Noon">"正午"</string>
+    <string name="midnight">"午前0時"</string>
+    <string name="Midnight">"午前0時"</string>
+    <!-- no translation found for month_day (3693060561170538204) -->
+    <skip />
+    <!-- no translation found for month (1976700695144952053) -->
+    <skip />
+    <string name="month_day_year">"<xliff:g id="YEAR">%Y</xliff:g>年<xliff:g id="MONTH">%B</xliff:g><xliff:g id="DAY">%-d</xliff:g>日"</string>
+    <!-- no translation found for month_year (2106203387378728384) -->
+    <skip />
+    <string name="time_of_day">"<xliff:g id="HOUR">%H</xliff:g>:<xliff:g id="MINUTE">%M</xliff:g>:<xliff:g id="SECOND">%S</xliff:g>"</string>
+    <string name="date_and_time">"<xliff:g id="YEAR">%Y</xliff:g>/<xliff:g id="MONTH">%B</xliff:g>/<xliff:g id="DAY">%-d</xliff:g> <xliff:g id="HOUR">%H</xliff:g>:<xliff:g id="MINUTE">%M</xliff:g>:<xliff:g id="SECOND">%S</xliff:g>"</string>
+    <string name="same_year_md1_md2">"<xliff:g id="MONTH1">%2$s</xliff:g>月<xliff:g id="DAY1">%3$s</xliff:g>日~<xliff:g id="MONTH2">%7$s</xliff:g>月<xliff:g id="DAY2">%8$s</xliff:g>日"</string>
+    <string name="same_year_wday1_md1_wday2_md2">"<xliff:g id="MONTH1">%2$s</xliff:g>/<xliff:g id="DAY1_0">%3$s</xliff:g><xliff:g id="WEEKDAY1">%1$s</xliff:g>~<xliff:g id="MONTH2">%7$s</xliff:g>/<xliff:g id="DAY2_1">%8$s</xliff:g><xliff:g id="WEEKDAY2">%6$s</xliff:g>"</string>
+    <string name="same_year_mdy1_mdy2">"<xliff:g id="YEAR">%9$s</xliff:g>年<xliff:g id="MONTH1">%2$s</xliff:g>月<xliff:g id="DAY1">%3$s</xliff:g>日~<xliff:g id="MONTH2">%7$s</xliff:g>月<xliff:g id="DAY2">%8$s</xliff:g>日"</string>
+    <string name="same_year_wday1_mdy1_wday2_mdy2">"<xliff:g id="YEAR">%9$s</xliff:g>/<xliff:g id="MONTH1">%2$s</xliff:g>/<xliff:g id="DAY1_0">%3$s</xliff:g><xliff:g id="WEEKDAY1">%1$s</xliff:g>~<xliff:g id="MONTH2">%7$s</xliff:g>/<xliff:g id="DAY2_1">%8$s</xliff:g><xliff:g id="WEEKDAY2">%6$s</xliff:g>"</string>
+    <string name="same_year_md1_time1_md2_time2">"<xliff:g id="MONTH1">%2$s</xliff:g>月<xliff:g id="DAY1">%3$s</xliff:g>日<xliff:g id="TIME1">%5$s</xliff:g>~<xliff:g id="MONTH2">%7$s</xliff:g>月<xliff:g id="DAY2">%8$s</xliff:g>日<xliff:g id="TIME2">%10$s</xliff:g>"</string>
+    <string name="same_year_wday1_md1_time1_wday2_md2_time2">"<xliff:g id="MONTH1">%2$s</xliff:g>/<xliff:g id="DAY1_0">%3$s</xliff:g><xliff:g id="WEEKDAY1">%1$s</xliff:g><xliff:g id="TIME1">%5$s</xliff:g>~<xliff:g id="MONTH2">%7$s</xliff:g>/<xliff:g id="DAY2_1">%8$s</xliff:g><xliff:g id="WEEKDAY2">%6$s</xliff:g><xliff:g id="TIME2">%10$s</xliff:g>"</string>
+    <string name="same_year_mdy1_time1_mdy2_time2">"<xliff:g id="YEAR1">%4$s</xliff:g>年<xliff:g id="MONTH1">%2$s</xliff:g>月<xliff:g id="DAY1">%3$s</xliff:g>日 <xliff:g id="TIME1">%5$s</xliff:g>~<xliff:g id="YEAR2">%9$s</xliff:g>年<xliff:g id="MONTH2">%7$s</xliff:g>月<xliff:g id="DAY2">%8$s</xliff:g>日 <xliff:g id="TIME2">%10$s</xliff:g>"</string>
+    <string name="same_year_wday1_mdy1_time1_wday2_mdy2_time2">"<xliff:g id="YEAR1">%4$s</xliff:g>/<xliff:g id="MONTH1">%2$s</xliff:g>/<xliff:g id="DAY1_0">%3$s</xliff:g><xliff:g id="WEEKDAY1">%1$s</xliff:g><xliff:g id="TIME1">%5$s</xliff:g>~<xliff:g id="YEAR2">%9$s</xliff:g>/<xliff:g id="MONTH2">%7$s</xliff:g>/<xliff:g id="DAY2_1">%8$s</xliff:g><xliff:g id="WEEKDAY2">%6$s</xliff:g><xliff:g id="TIME2">%10$s</xliff:g>"</string>
+    <string name="numeric_md1_md2">"<xliff:g id="MONTH1">%2$s</xliff:g>/<xliff:g id="DAY1">%3$s</xliff:g>~<xliff:g id="MONTH2">%7$s</xliff:g>/<xliff:g id="DAY2">%8$s</xliff:g>"</string>
+    <string name="numeric_wday1_md1_wday2_md2">"<xliff:g id="MONTH1">%2$s</xliff:g>/<xliff:g id="DAY1_0">%3$s</xliff:g><xliff:g id="WEEKDAY1">%1$s</xliff:g>~<xliff:g id="MONTH2">%7$s</xliff:g>/<xliff:g id="DAY2_1">%8$s</xliff:g><xliff:g id="WEEKDAY2">%6$s</xliff:g>"</string>
+    <string name="numeric_mdy1_mdy2">"<xliff:g id="YEAR1">%4$s</xliff:g>/<xliff:g id="MONTH1">%2$s</xliff:g>/<xliff:g id="DAY1">%3$s</xliff:g>~<xliff:g id="YEAR2">%9$s</xliff:g>/<xliff:g id="MONTH2">%7$s</xliff:g>/<xliff:g id="DAY2">%8$s</xliff:g>"</string>
+    <string name="numeric_wday1_mdy1_wday2_mdy2">"<xliff:g id="YEAR1">%4$s</xliff:g>/<xliff:g id="MONTH1">%2$s</xliff:g>/<xliff:g id="DAY1_0">%3$s</xliff:g><xliff:g id="WEEKDAY1">%1$s</xliff:g>~<xliff:g id="YEAR2">%9$s</xliff:g>/<xliff:g id="MONTH2">%7$s</xliff:g>/<xliff:g id="DAY2_1">%8$s</xliff:g><xliff:g id="WEEKDAY2">%6$s</xliff:g>"</string>
+    <string name="numeric_md1_time1_md2_time2">"<xliff:g id="MONTH1">%2$s</xliff:g>/<xliff:g id="DAY1">%3$s</xliff:g> <xliff:g id="TIME1">%5$s</xliff:g>~<xliff:g id="MONTH2">%7$s</xliff:g>/<xliff:g id="DAY2">%8$s</xliff:g> <xliff:g id="TIME2">%10$s</xliff:g>"</string>
+    <string name="numeric_wday1_md1_time1_wday2_md2_time2">"<xliff:g id="MONTH1">%2$s</xliff:g>/<xliff:g id="DAY1_0">%3$s</xliff:g><xliff:g id="WEEKDAY1">%1$s</xliff:g><xliff:g id="TIME1">%5$s</xliff:g>~<xliff:g id="MONTH2">%7$s</xliff:g>/<xliff:g id="DAY2_1">%8$s</xliff:g><xliff:g id="WEEKDAY2">%6$s</xliff:g><xliff:g id="TIME2">%10$s</xliff:g>"</string>
+    <string name="numeric_mdy1_time1_mdy2_time2">"<xliff:g id="YEAR1">%4$s</xliff:g>/<xliff:g id="MONTH1">%2$s</xliff:g>/<xliff:g id="DAY1">%3$s</xliff:g> <xliff:g id="TIME1">%5$s</xliff:g>~<xliff:g id="YEAR2">%9$s</xliff:g>/<xliff:g id="MONTH2">%7$s</xliff:g>/<xliff:g id="DAY2">%8$s</xliff:g> <xliff:g id="TIME2">%10$s</xliff:g>"</string>
+    <string name="numeric_wday1_mdy1_time1_wday2_mdy2_time2">"<xliff:g id="YEAR1">%4$s</xliff:g>/<xliff:g id="MONTH1">%2$s</xliff:g>/<xliff:g id="DAY1_0">%3$s</xliff:g><xliff:g id="WEEKDAY1">%1$s</xliff:g><xliff:g id="TIME1">%5$s</xliff:g>~<xliff:g id="YEAR2">%9$s</xliff:g>/<xliff:g id="MONTH2">%7$s</xliff:g>/<xliff:g id="DAY2_1">%8$s</xliff:g><xliff:g id="WEEKDAY2">%6$s</xliff:g><xliff:g id="TIME2">%10$s</xliff:g>"</string>
+    <string name="same_month_md1_md2">"<xliff:g id="MONTH1">%2$s</xliff:g>月<xliff:g id="DAY1">%3$s</xliff:g>日~<xliff:g id="DAY2">%8$s</xliff:g>日"</string>
+    <string name="same_month_wday1_md1_wday2_md2">"<xliff:g id="MONTH1">%2$s</xliff:g>/<xliff:g id="DAY1_0">%3$s</xliff:g><xliff:g id="WEEKDAY1">%1$s</xliff:g>~<xliff:g id="MONTH2">%7$s</xliff:g>/<xliff:g id="DAY2_1">%8$s</xliff:g><xliff:g id="WEEKDAY2">%6$s</xliff:g>"</string>
+    <string name="same_month_mdy1_mdy2">"<xliff:g id="YEAR2">%9$s</xliff:g>年<xliff:g id="MONTH1">%2$s</xliff:g>月<xliff:g id="DAY1">%3$s</xliff:g>日~<xliff:g id="DAY2">%8$s</xliff:g>日"</string>
+    <string name="same_month_wday1_mdy1_wday2_mdy2">"<xliff:g id="YEAR1">%4$s</xliff:g>/<xliff:g id="MONTH1">%2$s</xliff:g>/<xliff:g id="DAY1_0">%3$s</xliff:g><xliff:g id="WEEKDAY1">%1$s</xliff:g>~<xliff:g id="YEAR2">%9$s</xliff:g>/<xliff:g id="MONTH2">%7$s</xliff:g>/<xliff:g id="DAY2_1">%8$s</xliff:g><xliff:g id="WEEKDAY2">%6$s</xliff:g>"</string>
+    <string name="same_month_md1_time1_md2_time2">"<xliff:g id="MONTH1">%2$s</xliff:g>/<xliff:g id="DAY1">%3$s</xliff:g><xliff:g id="TIME1">%5$s</xliff:g>~<xliff:g id="MONTH2">%7$s</xliff:g>/<xliff:g id="DAY2">%8$s</xliff:g><xliff:g id="TIME2">%10$s</xliff:g>"</string>
+    <string name="same_month_wday1_md1_time1_wday2_md2_time2">"<xliff:g id="MONTH1">%2$s</xliff:g>/<xliff:g id="DAY1_0">%3$s</xliff:g><xliff:g id="WEEKDAY1">%1$s</xliff:g><xliff:g id="TIME1">%5$s</xliff:g>~<xliff:g id="MONTH2">%7$s</xliff:g>/<xliff:g id="DAY2_1">%8$s</xliff:g><xliff:g id="WEEKDAY2">%6$s</xliff:g><xliff:g id="TIME2">%10$s</xliff:g>"</string>
+    <string name="same_month_mdy1_time1_mdy2_time2">"<xliff:g id="YEAR1">%4$s</xliff:g>/<xliff:g id="MONTH1">%2$s</xliff:g>/<xliff:g id="DAY1">%3$s</xliff:g><xliff:g id="TIME1">%5$s</xliff:g>~<xliff:g id="YEAR2">%9$s</xliff:g>/<xliff:g id="MONTH2">%7$s</xliff:g>/<xliff:g id="DAY2">%8$s</xliff:g><xliff:g id="TIME2">%10$s</xliff:g>"</string>
+    <string name="same_month_wday1_mdy1_time1_wday2_mdy2_time2">"<xliff:g id="YEAR1">%4$s</xliff:g>/<xliff:g id="MONTH1">%2$s</xliff:g>/<xliff:g id="DAY1_0">%3$s</xliff:g><xliff:g id="WEEKDAY1">%1$s</xliff:g><xliff:g id="TIME1">%5$s</xliff:g>~<xliff:g id="YEAR2">%9$s</xliff:g>/<xliff:g id="MONTH2">%7$s</xliff:g>/<xliff:g id="DAY2_1">%8$s</xliff:g><xliff:g id="WEEKDAY2">%6$s</xliff:g><xliff:g id="TIME2">%10$s</xliff:g>"</string>
+    <string name="abbrev_month_day_year">"<xliff:g id="YEAR">%Y</xliff:g>/<xliff:g id="MONTH">%b</xliff:g>/<xliff:g id="DAY">%-d</xliff:g>"</string>
+    <!-- no translation found for abbrev_month_year (5966980891147982768) -->
+    <skip />
+    <!-- no translation found for abbrev_month_day (3156047263406783231) -->
+    <skip />
+    <!-- no translation found for abbrev_month (7304935052615731208) -->
+    <skip />
+    <string name="day_of_week_long_sunday">"日曜日"</string>
+    <string name="day_of_week_long_monday">"月曜日"</string>
+    <string name="day_of_week_long_tuesday">"火曜日"</string>
+    <string name="day_of_week_long_wednesday">"水曜日"</string>
+    <string name="day_of_week_long_thursday">"木曜日"</string>
+    <string name="day_of_week_long_friday">"金曜日"</string>
+    <string name="day_of_week_long_saturday">"土曜日"</string>
+    <string name="day_of_week_medium_sunday">"(日)"</string>
+    <string name="day_of_week_medium_monday">"(月)"</string>
+    <string name="day_of_week_medium_tuesday">"(火)"</string>
+    <string name="day_of_week_medium_wednesday">"(水)"</string>
+    <string name="day_of_week_medium_thursday">"(木)"</string>
+    <string name="day_of_week_medium_friday">"(金)"</string>
+    <string name="day_of_week_medium_saturday">"(土)"</string>
+    <string name="day_of_week_short_sunday">"(日)"</string>
+    <string name="day_of_week_short_monday">"(月)"</string>
+    <string name="day_of_week_short_tuesday">"(火)"</string>
+    <string name="day_of_week_short_wednesday">"(水)"</string>
+    <string name="day_of_week_short_thursday">"(木)"</string>
+    <string name="day_of_week_short_friday">"(金)"</string>
+    <string name="day_of_week_short_saturday">"(土)"</string>
+    <string name="day_of_week_shorter_sunday">"(日)"</string>
+    <string name="day_of_week_shorter_monday">"月"</string>
+    <string name="day_of_week_shorter_tuesday">"(火)"</string>
+    <string name="day_of_week_shorter_wednesday">"水"</string>
+    <string name="day_of_week_shorter_thursday">"(木)"</string>
+    <string name="day_of_week_shorter_friday">"金"</string>
+    <string name="day_of_week_shorter_saturday">"(土)"</string>
+    <string name="day_of_week_shortest_sunday">"日"</string>
+    <string name="day_of_week_shortest_monday">"月"</string>
+    <string name="day_of_week_shortest_tuesday">"火"</string>
+    <string name="day_of_week_shortest_wednesday">"水"</string>
+    <string name="day_of_week_shortest_thursday">"火"</string>
+    <string name="day_of_week_shortest_friday">"金"</string>
+    <string name="day_of_week_shortest_saturday">"土"</string>
+    <string name="month_long_january">"1月"</string>
+    <string name="month_long_february">"2月"</string>
+    <string name="month_long_march">"3月"</string>
+    <string name="month_long_april">"4月"</string>
+    <string name="month_long_may">"5月"</string>
+    <string name="month_long_june">"6月"</string>
+    <string name="month_long_july">"7月"</string>
+    <string name="month_long_august">"8月"</string>
+    <string name="month_long_september">"9月"</string>
+    <string name="month_long_october">"10月"</string>
+    <string name="month_long_november">"11月"</string>
+    <string name="month_long_december">"12月"</string>
+    <string name="month_medium_january">"1月"</string>
+    <string name="month_medium_february">"2月"</string>
+    <string name="month_medium_march">"3月"</string>
+    <string name="month_medium_april">"4月"</string>
+    <string name="month_medium_may">"5月"</string>
+    <string name="month_medium_june">"6月"</string>
+    <string name="month_medium_july">"7月"</string>
+    <string name="month_medium_august">"8月"</string>
+    <string name="month_medium_september">"9月"</string>
+    <string name="month_medium_october">"10月"</string>
+    <string name="month_medium_november">"11月"</string>
+    <string name="month_medium_december">"12月"</string>
+    <string name="month_shortest_january">"1"</string>
+    <string name="month_shortest_february">"2"</string>
+    <string name="month_shortest_march">"3"</string>
+    <string name="month_shortest_april">"4"</string>
+    <string name="month_shortest_may">"5"</string>
+    <string name="month_shortest_june">"6"</string>
+    <string name="month_shortest_july">"7"</string>
+    <string name="month_shortest_august">"8"</string>
+    <string name="month_shortest_september">"9"</string>
+    <string name="month_shortest_october">"10"</string>
+    <string name="month_shortest_november">"11"</string>
+    <string name="month_shortest_december">"12"</string>
+    <string name="elapsed_time_short_format_mm_ss">"<xliff:g id="MINUTES">%1$02d</xliff:g>:<xliff:g id="SECONDS">%2$02d</xliff:g>"</string>
+    <string name="elapsed_time_short_format_h_mm_ss">"<xliff:g id="HOURS">%1$d</xliff:g>:<xliff:g id="MINUTES">%2$02d</xliff:g>:<xliff:g id="SECONDS">%3$02d</xliff:g>"</string>
+    <string name="selectAll">"すべて選択"</string>
+    <string name="selectText">"テキストを選択"</string>
+    <string name="stopSelectingText">"テキストの選択を終了"</string>
+    <string name="cut">"切り取り"</string>
+    <string name="cutAll">"すべて切り取り"</string>
+    <string name="copy">"コピー"</string>
+    <string name="copyAll">"すべてコピー"</string>
+    <string name="paste">"貼り付け"</string>
+    <string name="copyUrl">"URLをコピー"</string>
+    <string name="inputMethod">"入力方法"</string>
+    <string name="addToDictionary">"辞書に「%s」を追加"</string>
+    <string name="editTextMenuTitle">"テキストを編集"</string>
+    <string name="low_internal_storage_view_title">"空き容量低下"</string>
+    <string name="low_internal_storage_view_text">"携帯電話の空き容量が少なくなっています。"</string>
+    <string name="ok">"OK"</string>
+    <string name="cancel">"キャンセル"</string>
+    <string name="yes">"OK"</string>
+    <string name="no">"キャンセル"</string>
+    <string name="dialog_alert_title">"注意"</string>
+    <string name="capital_on">"オン"</string>
+    <string name="capital_off">"オフ"</string>
+    <string name="whichApplication">"操作の完了に使用"</string>
+    <string name="alwaysUse">"常にこの操作で使用する"</string>
+    <string name="clearDefaultHintMsg">"ホームの[設定]&gt;[アプリケーション]&gt;[アプリケーションの管理]でデフォルト設定をクリアします。"</string>
+    <string name="chooseActivity">"操作の選択"</string>
+    <string name="noApplications">"この操作を実行できるアプリケーションはありません。"</string>
+    <string name="aerr_title">"エラー"</string>
+    <string name="aerr_application">"<xliff:g id="APPLICATION">%1$s</xliff:g>(<xliff:g id="PROCESS">%2$s</xliff:g>)が予期せず停止しました。やり直してください。"</string>
+    <string name="aerr_process">"<xliff:g id="PROCESS">%1$s</xliff:g>が予期せず停止しました。やり直してください。"</string>
+    <string name="anr_title">"エラー"</string>
+    <string name="anr_activity_application">"<xliff:g id="ACTIVITY">%1$s</xliff:g>(<xliff:g id="APPLICATION">%2$s</xliff:g>)は応答していません。"</string>
+    <string name="anr_activity_process">"<xliff:g id="ACTIVITY">%1$s</xliff:g>(プロセス: <xliff:g id="PROCESS">%2$s</xliff:g>)は応答していません。"</string>
+    <string name="anr_application_process">"<xliff:g id="APPLICATION">%1$s</xliff:g>(<xliff:g id="PROCESS">%2$s</xliff:g>)は応答していません。"</string>
+    <string name="anr_process">"<xliff:g id="PROCESS">%1$s</xliff:g>は応答していません。"</string>
+    <string name="force_close">"強制終了"</string>
+    <string name="wait">"待機"</string>
+    <string name="debug">"デバッグ"</string>
+    <string name="sendText">"テキストの操作"</string>
+    <string name="volume_ringtone">"着信音の音量"</string>
+    <string name="volume_music">"メディアの音量"</string>
+    <string name="volume_music_hint_playing_through_bluetooth">"Bluetooth経由で再生中です"</string>
+    <string name="volume_call">"着信音の音量"</string>
+    <string name="volume_bluetooth_call">"Bluetooth着信音の音量"</string>
+    <string name="volume_alarm">"アラームの音量"</string>
+    <string name="volume_notification">"通知音の音量"</string>
+    <string name="volume_unknown">"音量"</string>
+    <string name="ringtone_default">"デフォルトの着信音"</string>
+    <string name="ringtone_default_with_actual">"端末既定の着信音(<xliff:g id="ACTUAL_RINGTONE">%1$s</xliff:g>)"</string>
+    <string name="ringtone_silent">"無音"</string>
+    <string name="ringtone_picker_title">"着信音"</string>
+    <string name="ringtone_unknown">"不明な着信音"</string>
+  <plurals name="wifi_available">
+    <item quantity="one">"Wi-Fiを利用できます"</item>
+    <item quantity="other">"Wi-Fiを利用できます"</item>
+  </plurals>
+  <plurals name="wifi_available_detailed">
+    <item quantity="one">"Wi-Fiオープンネットワークが利用できます"</item>
+    <item quantity="other">"Wi-Fiオープンネットワークが利用できます"</item>
+  </plurals>
+    <string name="select_character">"文字を挿入"</string>
+    <string name="sms_control_default_app_name">"不明なアプリケーション"</string>
+    <string name="sms_control_title">"SMSメッセージの送信中"</string>
+    <string name="sms_control_message">"大量のSMSメッセージを送信しようとしています。[OK]で送信、[キャンセル]で中止します。"</string>
+    <string name="sms_control_yes">"OK"</string>
+    <string name="sms_control_no">"キャンセル"</string>
+    <string name="date_time_set">"設定"</string>
+    <string name="default_permission_group">"端末既定"</string>
+    <string name="no_permissions">"権限付与の必要はありません"</string>
+    <string name="perms_hide"><b>"隠す"</b></string>
+    <string name="perms_show_all"><b>"すべて表示"</b></string>
+    <string name="googlewebcontenthelper_loading">"読み込み中..."</string>
+    <string name="usb_storage_title">"USB接続"</string>
+    <string name="usb_storage_message">"USB経由で携帯電話をコンピュータに接続しました。コンピュータと携帯電話のSDカード間でファイルをコピーするには、[マウント]を選択します。"</string>
+    <string name="usb_storage_button_mount">"マウント"</string>
+    <string name="usb_storage_button_unmount">"マウントしない"</string>
+    <string name="usb_storage_error_message">"USBメモリにSDカードを使用する際に問題が発生しました。"</string>
+    <string name="usb_storage_notification_title">"USB接続"</string>
+    <string name="usb_storage_notification_message">"パソコンとの間でファイルをコピーします。"</string>
+    <string name="usb_storage_stop_notification_title">"USBストレージをOFFにする"</string>
+    <string name="usb_storage_stop_notification_message">"USBストレージをOFFにする場合に選択します。"</string>
+    <string name="usb_storage_stop_title">"USBストレージをOFFにする"</string>
+    <string name="usb_storage_stop_message">"USBストレージをOFFにする前にUSBホストのマウントを解除したことを確認してください。USBストレージをOFFにするには[OFF]を選択します。"</string>
+    <string name="usb_storage_stop_button_mount">"OFF"</string>
+    <string name="usb_storage_stop_button_unmount">"キャンセル"</string>
+    <string name="usb_storage_stop_error_message">"USBストレージをOFFにする際に問題が発生しました。USBホストのマウントが解除されていることを確認してからもう一度お試しください。"</string>
+    <string name="extmedia_format_title">"SDカードをフォーマット"</string>
+    <string name="extmedia_format_message">"SDカードをフォーマットしてもよろしいですか?カード内のすべてのデータが失われます。"</string>
+    <string name="extmedia_format_button_format">"フォーマット"</string>
+    <string name="select_input_method">"入力方法の選択"</string>
+    <string name="fast_scroll_alphabet">" ABCDEFGHIJKLMNOPQRSTUVWXYZ"</string>
+    <string name="fast_scroll_numeric_alphabet">" 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"</string>
+    <string name="candidates_style"><u>"候補"</u></string>
+    <string name="ext_media_checking_notification_title">"SDカードの準備中"</string>
+    <string name="ext_media_checking_notification_message">"エラーを確認中"</string>
+    <string name="ext_media_nofs_notification_title">"空のSDカード"</string>
+    <string name="ext_media_nofs_notification_message">"SDカードが空か、サポート対象外のファイルシステムを使用しています。"</string>
+    <string name="ext_media_unmountable_notification_title">"破損したSDカード"</string>
+    <string name="ext_media_unmountable_notification_message">"SDカードが破損しています。カードのフォーマットが必要な可能性があります。"</string>
+    <string name="ext_media_badremoval_notification_title">"SDカードが予期せず取り外されました"</string>
+    <string name="ext_media_badremoval_notification_message">"データの喪失を防ぐためSDカードを取り外す前にマウントを解除してください。"</string>
+    <string name="ext_media_safe_unmount_notification_title">"SDカードを安全に取り外しました"</string>
+    <string name="ext_media_safe_unmount_notification_message">"SDカードを安全に取り外せます。"</string>
+    <string name="ext_media_nomedia_notification_title">"SDカードが取り外されています"</string>
+    <string name="ext_media_nomedia_notification_message">"SDカードが取り外されました。新しいSDカードを挿入して端末のメモリを増やしてください。"</string>
+    <string name="activity_list_empty">"一致するアクティビティが見つかりません"</string>
+    <string name="permlab_pkgUsageStats">"コンポーネント使用状況に関する統計情報の更新"</string>
+    <string name="permdesc_pkgUsageStats">"収集されたコンポーネント使用状況に関する統計情報の変更を許可します。通常のアプリケーションでは使用しません。"</string>
+    <!-- no translation found for tutorial_double_tap_to_zoom_message_short (1311810005957319690) -->
+    <skip />
+    <!-- no translation found for gadget_host_error_inflating (2613287218853846830) -->
+    <skip />
+    <!-- no translation found for ime_action_go (8320845651737369027) -->
+    <skip />
+    <!-- no translation found for ime_action_search (658110271822807811) -->
+    <skip />
+    <!-- no translation found for ime_action_send (2316166556349314424) -->
+    <skip />
+    <!-- no translation found for ime_action_next (3138843904009813834) -->
+    <skip />
+    <!-- no translation found for ime_action_default (2840921885558045721) -->
+    <skip />
+</resources>
diff --git a/core/res/res/values-ko/strings.xml b/core/res/res/values-ko/strings.xml
new file mode 100644
index 0000000..8fc7d12
--- /dev/null
+++ b/core/res/res/values-ko/strings.xml
@@ -0,0 +1,815 @@
+<?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.
+-->
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="byteShort">"B"</string>
+    <string name="kilobyteShort">"KB"</string>
+    <string name="megabyteShort">"MB"</string>
+    <string name="gigabyteShort">"GB"</string>
+    <string name="terabyteShort">"TB"</string>
+    <string name="petabyteShort">"PB"</string>
+    <string name="untitled">"&lt;제목없음&gt;"</string>
+    <string name="ellipsis">"…"</string>
+    <string name="emptyPhoneNumber">"(전화번호 없음)"</string>
+    <string name="unknownName">"(알 수 없음)"</string>
+    <string name="defaultVoiceMailAlphaTag">"음성메일"</string>
+    <string name="defaultMsisdnAlphaTag">"MSISDN1"</string>
+    <string name="mmiError">"연결에 문제가 있거나 MMI 코드가 잘못되었습니다."</string>
+    <string name="serviceEnabled">"서비스가 활성화되었습니다."</string>
+    <string name="serviceEnabledFor">"사용 설정된 서비스 목록:"</string>
+    <string name="serviceDisabled">"서비스가 비활성화되었습니다."</string>
+    <string name="serviceRegistered">"등록이 완료되었습니다."</string>
+    <string name="serviceErased">"지웠습니다."</string>
+    <string name="passwordIncorrect">"비밀번호가 잘못되었습니다."</string>
+    <string name="mmiComplete">"MMI 완료"</string>
+    <string name="badPin">"이전 PIN이 올바르지 않습니다."</string>
+    <string name="badPuk">"입력한 PUK가 올바르지 않습니다."</string>
+    <string name="mismatchPin">"입력한 PIN이 일치하지 않습니다."</string>
+    <string name="invalidPin">"4~8자리 숫자로 된 PIN을 입력하세요."</string>
+    <string name="needPuk">"SIM 카드의 PUK가 잠겨 있습니다. 잠금해제하려면 PUK 코드를 입력하세요."</string>
+    <string name="needPuk2">"SIM 카드 잠금을 해제하려면 PUK2를 입력하세요."</string>
+    <string name="ClipMmi">"수신 발신자 번호"</string>
+    <string name="ClirMmi">"발신 발신자 번호"</string>
+    <string name="CfMmi">"착신전환"</string>
+    <string name="CwMmi">"통화중 대기"</string>
+    <string name="BaMmi">"착발신 제한"</string>
+    <string name="PwdMmi">"비밀번호 변경"</string>
+    <string name="PinMmi">"PIN 변경"</string>
+    <string name="CLIRDefaultOnNextCallOn">"발신자 번호가 기본적으로 제한됨으로 설정됩니다. 다음 통화: 제한됨"</string>
+    <string name="CLIRDefaultOnNextCallOff">"발신자 번호가 기본적으로 제한됨으로 설정됩니다. 다음 통화: 제한되지 않음"</string>
+    <string name="CLIRDefaultOffNextCallOn">"발신자 번호가 기본적으로 제한되지 않음으로 설정됩니다. 다음 통화: 제한됨"</string>
+    <string name="CLIRDefaultOffNextCallOff">"발신자 번호가 기본적으로 제한되지 않음으로 설정됩니다. 다음 통화: 제한되지 않음"</string>
+    <string name="serviceNotProvisioned">"서비스가 준비되지 않았습니다."</string>
+    <string name="CLIRPermanent">"발신자 번호 설정을 변경할 수 없습니다."</string>
+    <string name="serviceClassVoice">"음성"</string>
+    <string name="serviceClassData">"데이터"</string>
+    <string name="serviceClassFAX">"팩스"</string>
+    <string name="serviceClassSMS">"SMS"</string>
+    <string name="serviceClassDataAsync">"비동기"</string>
+    <string name="serviceClassDataSync">"동기화"</string>
+    <string name="serviceClassPacket">"패킷"</string>
+    <string name="serviceClassPAD">"PAD"</string>
+    <string name="cfTemplateNotForwarded">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: 착신전환 안 됨"</string>
+    <string name="cfTemplateForwarded">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
+    <string name="cfTemplateForwardedTime">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="TIME_DELAY">{2}</xliff:g>초 후 <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
+    <string name="cfTemplateRegistered">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: 착신전환 안 됨"</string>
+    <string name="cfTemplateRegisteredTime">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: 착신전환 안 됨"</string>
+    <string name="httpErrorOk">"확인"</string>
+    <string name="httpError">"웹페이지에 오류가 있습니다."</string>
+    <string name="httpErrorLookup">"URL을 찾을 수 없습니다."</string>
+    <string name="httpErrorUnsupportedAuthScheme">"사이트 인증 스키마가 지원되지 않습니다."</string>
+    <string name="httpErrorAuth">"인증에 실패했습니다."</string>
+    <string name="httpErrorProxyAuth">"프록시 서버를 통한 인증에 실패했습니다."</string>
+    <string name="httpErrorConnect">"서버에 연결하지 못했습니다."</string>
+    <string name="httpErrorIO">"서버와 통신할 수 없습니다. 나중에 다시 시도하세요."</string>
+    <string name="httpErrorTimeout">"서버 연결 제한시간이 초과되었습니다."</string>
+    <string name="httpErrorRedirectLoop">"페이지에 서버 리디렉션이 너무 많이 포함되어 있습니다."</string>
+    <string name="httpErrorUnsupportedScheme">"지원되지 않는 프로토콜입니다."</string>
+    <string name="httpErrorFailedSslHandshake">"보안 연결을 설정하지 못했습니다."</string>
+    <string name="httpErrorBadUrl">"URL이 올바르지 않아 페이지를 열 수 없습니다."</string>
+    <string name="httpErrorFile">"파일에 액세스할 수 없습니다."</string>
+    <string name="httpErrorFileNotFound">"요청한 파일을 찾을 수 없습니다."</string>
+    <string name="httpErrorTooManyRequests">"처리 중인 요청이 너무 많습니다. 잠시 후에 다시 시도하세요."</string>
+    <string name="contentServiceSync">"동기화"</string>
+    <string name="contentServiceSyncNotificationTitle">"동기화"</string>
+    <string name="contentServiceTooManyDeletesNotificationDesc">"<xliff:g id="CONTENT_TYPE">%s</xliff:g> 삭제가 너무 많습니다."</string>
+    <string name="low_memory">"전화기 저장공간이 꽉 찼습니다. 일부 파일을 삭제하여 저장 여유 공간을 늘리세요."</string>
+    <string name="me">"나"</string>
+    <string name="power_dialog">"전화기 옵션"</string>
+    <string name="silent_mode">"무음 모드"</string>
+    <string name="turn_on_radio">"무선 켜기"</string>
+    <string name="turn_off_radio">"무선 끄기"</string>
+    <string name="screen_lock">"화면 잠금"</string>
+    <string name="power_off">"끄기"</string>
+    <string name="shutdown_progress">"종료 중..."</string>
+    <string name="shutdown_confirm">"전화기가 종료됩니다."</string>
+    <string name="no_recent_tasks">"최신 응용프로그램이 아닙니다."</string>
+    <string name="global_actions">"전화기 옵션"</string>
+    <string name="global_action_lock">"화면 잠금"</string>
+    <string name="global_action_power_off">"끄기"</string>
+    <string name="global_action_toggle_silent_mode">"무음 모드"</string>
+    <string name="global_action_silent_mode_on_status">"소리 꺼짐"</string>
+    <string name="global_action_silent_mode_off_status">"소리 켜짐"</string>
+    <!-- no translation found for global_actions_toggle_airplane_mode (5884330306926307456) -->
+    <skip />
+    <!-- no translation found for global_actions_airplane_mode_on_status (2719557982608919750) -->
+    <skip />
+    <!-- no translation found for global_actions_airplane_mode_off_status (5075070442854490296) -->
+    <skip />
+    <string name="safeMode">"안전 모드"</string>
+    <!-- no translation found for android_system_label (6577375335728551336) -->
+    <skip />
+    <string name="permgrouplab_costMoney">"요금이 부과되는 서비스"</string>
+    <string name="permgroupdesc_costMoney">"응용프로그램이 요금이 부과될 수 있는 작업을 할 수 있습니다."</string>
+    <string name="permgrouplab_messages">"메시지"</string>
+    <string name="permgroupdesc_messages">"SMS, 이메일 및 기타 메시지를 읽고 씁니다."</string>
+    <string name="permgrouplab_personalInfo">"개인 정보"</string>
+    <string name="permgroupdesc_personalInfo">"전화기에 저장된 연락처 및 캘린더에 직접 액세스합니다."</string>
+    <string name="permgrouplab_location">"위치"</string>
+    <string name="permgroupdesc_location">"물리적 위치 모니터링"</string>
+    <string name="permgrouplab_network">"네트워크 통신"</string>
+    <string name="permgroupdesc_network">"응용프로그램이 다양한 네트워크 기능에 액세스할 수 있습니다."</string>
+    <string name="permgrouplab_accounts">"Google 계정"</string>
+    <string name="permgroupdesc_accounts">"사용가능한 Google 계정에 액세스합니다."</string>
+    <string name="permgrouplab_hardwareControls">"하드웨어 제어"</string>
+    <string name="permgroupdesc_hardwareControls">"핸드셋의 하드웨어에 직접 액세스합니다."</string>
+    <string name="permgrouplab_phoneCalls">"전화 통화"</string>
+    <string name="permgroupdesc_phoneCalls">"전화 통화를 모니터링, 기록 및 처리합니다."</string>
+    <string name="permgrouplab_systemTools">"시스템 도구"</string>
+    <string name="permgroupdesc_systemTools">"하위 수준의 액세스 및 시스템 제어"</string>
+    <string name="permgrouplab_developmentTools">"개발도구"</string>
+    <string name="permgroupdesc_developmentTools">"응용프로그램 개발자에게만 필요한 기능입니다."</string>
+    <string name="permlab_statusBar">"상태 표시줄 사용 안 함 또는 수정"</string>
+    <string name="permdesc_statusBar">"응용프로그램이 상태 표시줄을 비활성화하거나 시스템 아이콘을 추가 및 제거할 수 있습니다."</string>
+    <string name="permlab_expandStatusBar">"상태 표시줄 확장/축소"</string>
+    <string name="permdesc_expandStatusBar">"응용프로그램이 상태 표시줄을 확장하거나 축소할 수 있습니다."</string>
+    <string name="permlab_processOutgoingCalls">"발신전화 가로채기"</string>
+    <string name="permdesc_processOutgoingCalls">"응용프로그램이 발신전화를 처리하고 전화를 걸 번호를 변경할 수 있습니다. 악성 응용프로그램은 이 기능을 이용하여 발신전화를 모니터링하거나, 다른 방향으로 돌리거나, 중단시킬 수 있습니다."</string>
+    <string name="permlab_receiveSms">"SMS 받기"</string>
+    <string name="permdesc_receiveSms">"응용프로그램이 SMS 메시지를 받고 처리할 수 있습니다. 악성 응용프로그램은 이 기능을 이용하여 메시지를 모니터링하거나 사용자가 보기 전에 삭제할 수 있습니다."</string>
+    <string name="permlab_receiveMms">"MMS 받기"</string>
+    <string name="permdesc_receiveMms">"응용프로그램이 MMS 메시지를 받고 처리할 수 있습니다. 악성 응용프로그램은 이 기능을 이용하여 메시지를 모니터링하거나 사용자가 보기 전에 삭제할 수 있습니다."</string>
+    <string name="permlab_sendSms">"SMS 메시지 보내기"</string>
+    <string name="permdesc_sendSms">"응용프로그램이 SMS 메시지를 보낼 수 있습니다. 악성 응용프로그램은 사용자의 확인 없이 메시지를 전송하여 요금을 부과할 수 있습니다."</string>
+    <string name="permlab_readSms">"SMS 또는 MMS 읽기"</string>
+    <string name="permdesc_readSms">"응용프로그램이 전화기 또는 SIM 카드에 저장된 SMS 메시지를 읽을 수 있습니다. 악성 응용프로그램은 이 기능을 이용하여 기밀 메시지를 읽을 수 있습니다."</string>
+    <string name="permlab_writeSms">"SMS 또는 MMS 편집"</string>
+    <string name="permdesc_writeSms">"응용프로그램이 전화기 또는 SIM 카드에 저장된 SMS 메시지에 쓸 수 있습니다. 악성 응용프로그램은 이 기능을 이용하여 메시지를 삭제할 수 있습니다."</string>
+    <string name="permlab_receiveWapPush">"WAP 받기"</string>
+    <string name="permdesc_receiveWapPush">"응용프로그램이 WAP 메시지를 받고 처리할 수 있습니다. 악성 응용프로그램은 이 기능을 이용하여 메시지를 모니터링하거나 사용자가 보기 전에 삭제할 수 있습니다."</string>
+    <string name="permlab_getTasks">"실행 중인 응용프로그램 검색"</string>
+    <string name="permdesc_getTasks">"응용프로그램이 현재 실행 중이거나 최근에 실행된 작업에 대한 정보를 검색할 수 있습니다. 악성 응용프로그램은 이 기능을 이용하여 다른 응용프로그램에 대한 개인 정보를 검색할 수 있습니다."</string>
+    <string name="permlab_reorderTasks">"실행 중인 응용프로그램 순서 재지정"</string>
+    <string name="permdesc_reorderTasks">"응용프로그램이 작업을 포그라운드나 백그라운드로 이동할 수 있습니다. 악성 응용프로그램은 이 기능을 이용하여 사용자의 조작 없이 작업을 강제로 앞으로 이동할 수 있습니다."</string>
+    <string name="permlab_setDebugApp">"응용프로그램 디버깅 사용"</string>
+    <string name="permdesc_setDebugApp">"응용프로그램이 다른 응용프로그램에 대한 디버깅을 설정할 수 있습니다. 악성 응용프로그램은 이 기능을 이용하여 다른 응용프로그램을 중지시킬 수 있습니다."</string>
+    <string name="permlab_changeConfiguration">"UI 설정 변경"</string>
+    <string name="permdesc_changeConfiguration">"응용프로그램이 로케일 또는 전체 글꼴 크기 같은 현재 구성을 변경할 수 있습니다."</string>
+    <string name="permlab_restartPackages">"다른 응용프로그램 다시 시작"</string>
+    <string name="permdesc_restartPackages">"응용프로그램이 다른 응용프로그램을 강제로 다시 시작할 수 있습니다."</string>
+    <string name="permlab_setProcessForeground">"중지되지 않도록 하기"</string>
+    <string name="permdesc_setProcessForeground">"응용프로그램이 프로세스를 포그라운드에서 실행되도록 하여 프로세스를 중지할 수 있습니다. 일반 응용프로그램에는 필요하지 않습니다."</string>
+    <string name="permlab_forceBack">"강제로 응용프로그램 닫기"</string>
+    <string name="permdesc_forceBack">"응용프로그램이 포그라운드에 있는 활동을 강제로 닫을 수 있습니다. 일반 응용프로그램에는 필요하지 않습니다."</string>
+    <string name="permlab_dump">"시스템 내부 상태 검색"</string>
+    <string name="permdesc_dump">"응용프로그램이 시스템의 내부 상태를 검색할 수 있습니다. 악성 응용프로그램은 이 기능을 이용하여 일반적으로 필요하지 않은 다양한 개인 정보와 보안 정보를 검색할 수 있습니다."</string>
+    <string name="permlab_addSystemService">"하위 수준 서비스 게시"</string>
+    <string name="permdesc_addSystemService">"응용프로그램이 자체 하위 수준 시스템 서비스를 게시할 수 있습니다. 악성 응용프로그램은 이 기능을 이용하여 시스템을 하이재킹하거나 시스템의 데이터를 도용 또는 손상시킬 수 있습니다."</string>
+    <string name="permlab_runSetActivityWatcher">"실행 중인 모든 응용프로그램 모니터링 및 제어"</string>
+    <string name="permdesc_runSetActivityWatcher">"응용프로그램이 시스템에서 활동이 시작되는 방식을 모니터링하고 제어할 수 있습니다. 악성 응용프로그램은 이 기능을 이용하여 시스템을 완전히 손상시킬 수 있습니다. 이 권한은 개발 과정에만 필요하며 일반 전화기 사용 시에는 필요하지 않습니다."</string>
+    <string name="permlab_broadcastPackageRemoved">"패키지 제거 브로드캐스트 보내기"</string>
+    <string name="permdesc_broadcastPackageRemoved">"응용프로그램이 응용프로그램 패키지가 제거되었다는 알림을 브로드캐스트할 수 있습니다. 악성 응용프로그램은 이 기능을 이용하여 실행 중인 다른 응용프로그램을 중지시킬 수 있습니다."</string>
+    <string name="permlab_broadcastSmsReceived">"SMS 수신 브로드캐스트 보내기"</string>
+    <string name="permdesc_broadcastSmsReceived">"응용프로그램이 SMS 메시지를 받았다는 알림을 브로드캐스트할 수 있습니다. 악성 응용프로그램은 이 기능을 이용하여 들어오는 SMS 메시지처럼 위장할 수 있습니다."</string>
+    <string name="permlab_broadcastWapPush">"WAP-PUSH-수신 브로드캐스트 보내기"</string>
+    <string name="permdesc_broadcastWapPush">"응용프로그램이 WAP PUSH 메시지를 받았다는 알림을 브로드캐스트할 수 있습니다. 악성 응용프로그램은 이 기능을 이용하여 MMS 메시지를 받은 것처럼 위장하거나 웹페이지의 콘텐츠를 악성 변종으로 바꿀 수 있습니다."</string>
+    <string name="permlab_setProcessLimit">"실행 중인 프로세스 수 제한"</string>
+    <string name="permdesc_setProcessLimit">"응용프로그램이 실행할 최대 프로세스 수를 제어할 수 있습니다. 일반 응용프로그램에는 필요하지 않습니다."</string>
+    <string name="permlab_setAlwaysFinish">"모든 백그라운드 응용프로그램이 닫히도록 하기"</string>
+    <string name="permdesc_setAlwaysFinish">"응용프로그램이 백그라운드로 이동한 활동을 항상 바로 마칠지 여부를 제어할 수 있습니다. 일반 응용프로그램에는 필요하지 않습니다."</string>
+    <string name="permlab_fotaUpdate">"시스템 업데이트 자동으로 설치"</string>
+    <string name="permdesc_fotaUpdate">"응용프로그램이 대기 중인 시스템 업데이트에 대한 알림을 받고 설치를 트리거할 수 있습니다. 악성 응용프로그램은 이 기능을 이용하여 인증되지 않은 업데이트로 시스템을 손상시키거나 업데이트 절차를 방해할 수 있습니다."</string>
+    <string name="permlab_batteryStats">"배터리 통계 수정"</string>
+    <string name="permdesc_batteryStats">"수집된 배터리 통계를 수정할 수 있습니다. 일반 응용프로그램에서는 사용하지 않습니다."</string>
+    <string name="permlab_internalSystemWindow">"인증되지 않은 창 표시"</string>
+    <string name="permdesc_internalSystemWindow">"내부 시스템 사용자 인터페이스에서 사용하는 창을 만들 수 있습니다. 일반 응용프로그램에서는 사용하지 않습니다."</string>
+    <string name="permlab_systemAlertWindow">"시스템 수준 경고 표시"</string>
+    <string name="permdesc_systemAlertWindow">"응용프로그램이 시스템 경고 창을 표시할 수 있습니다. 악성 응용프로그램은 전화기 화면 전체를 차지할 수 있습니다."</string>
+    <string name="permlab_setAnimationScale">"전체 애니메이션 속도 수정"</string>
+    <string name="permdesc_setAnimationScale">"응용프로그램이 언제든지 전체 애니메이션 속도를 빠르게 또는 느리게 변경할 수 있습니다."</string>
+    <string name="permlab_manageAppTokens">"응용프로그램 토큰 관리"</string>
+    <string name="permdesc_manageAppTokens">"응용프로그램이 일반적인 Z-순서를 무시하여 자체 토큰을 만들고 관리할 수 있습니다. 일반 응용프로그램에는 필요하지 않습니다."</string>
+    <string name="permlab_injectEvents">"키 및 컨트롤 버튼 누르기"</string>
+    <string name="permdesc_injectEvents">"응용프로그램이 입력 이벤트(예: 키 누름)를 다른 응용프로그램에 전달할 수 있습니다. 악성 응용프로그램은 이 기능을 이용하여 전화기를 완전히 제어할 수 있습니다."</string>
+    <string name="permlab_readInputState">"사용자가 입력한 내용 및 수행한 작업 기록"</string>
+    <string name="permdesc_readInputState">"응용프로그램이 다른 응용프로그램과 상호작용할 때에도 사용자가 누르는 키(예: 비밀번호 입력)를 볼 수 있습니다. 일반 응용프로그램에는 필요하지 않습니다."</string>
+    <string name="permlab_bindInputMethod">"입력 방법에 고정"</string>
+    <string name="permdesc_bindInputMethod">"보유자가 입력 방법의 최상위 인터페이스에 고정할 수 있습니다. 일반 응용프로그램에는 필요하지 않습니다."</string>
+    <string name="permlab_setOrientation">"화면 방향 변경"</string>
+    <string name="permdesc_setOrientation">"응용프로그램이 언제든지 화면 회전을 변경할 수 있습니다. 일반 응용프로그램에는 필요하지 않습니다."</string>
+    <string name="permlab_signalPersistentProcesses">"응용프로그램에 Linux 신호 보내기"</string>
+    <string name="permdesc_signalPersistentProcesses">"응용프로그램이 제공된 신호를 모든 영구 프로세스로 보내도록 요청할 수 있습니다."</string>
+    <string name="permlab_persistentActivity">"응용프로그램이 항상 실행되도록 설정"</string>
+    <string name="permdesc_persistentActivity">"응용프로그램이 연결된 일부 구성요소를 지속하여 다른 응용프로그램에 사용할 수 없도록 합니다."</string>
+    <string name="permlab_deletePackages">"응용프로그램 삭제"</string>
+    <string name="permdesc_deletePackages">"응용프로그램이 Android 패키지를 삭제할 수 있습니다. 악성 응용프로그램은 이 기능을 이용하여 중요한 응용프로그램을 삭제할 수 있습니다."</string>
+    <string name="permlab_clearAppUserData">"다른 응용프로그램의 데이터 삭제"</string>
+    <string name="permdesc_clearAppUserData">"응용프로그램이 사용자 데이터를 지울 수 있습니다."</string>
+    <string name="permlab_deleteCacheFiles">"다른 응용프로그램의 캐시 삭제"</string>
+    <string name="permdesc_deleteCacheFiles">"응용프로그램이 캐시 파일을 삭제할 수 있습니다."</string>
+    <string name="permlab_getPackageSize">"응용프로그램 저장공간 계산"</string>
+    <string name="permdesc_getPackageSize">"응용프로그램이 해당 코드, 데이터 및 캐시 크기를 검색할 수 있습니다."</string>
+    <string name="permlab_installPackages">"응용프로그램 직접 설치"</string>
+    <string name="permdesc_installPackages">"응용프로그램이 새로운 또는 업데이트된 Android 패키지를 설치할 수 있습니다. 악성 응용프로그램은 이 기능을 이용하여 임의의 강력한 권한으로 새 응용프로그램을 추가할 수 있습니다."</string>
+    <string name="permlab_clearAppCache">"모든 응용프로그램 캐시 데이터 삭제"</string>
+    <string name="permdesc_clearAppCache">"응용프로그램이 응용프로그램 캐시 디렉토리에 있는 파일을 삭제하여 전화기의 저장공간을 늘릴 수 있습니다. 액세스는 일반적으로 시스템 프로세스로 제한됩니다."</string>
+    <string name="permlab_readLogs">"시스템 로그 파일 읽기"</string>
+    <string name="permdesc_readLogs">"응용프로그램이 시스템의 다양한 로그 파일을 읽을 수 있습니다. 이 경우 응용프로그램은 사용자가 전화기로 수행하는 작업에 대한 일반적인 정보를 검색할 수 있지만 여기에 개인 정보는 포함되어서는 안 됩니다."</string>
+    <string name="permlab_diagnostic">"진단 그룹 소유의 리소스 읽기/작성"</string>
+    <string name="permdesc_diagnostic">"응용프로그램이 진단 그룹 소유의 리소스(예: /dev에 있는 파일)를 읽고 쓸 수 있습니다. 이 기능은 시스템 안정성 및 보안에 영향을 미칠 수 있으므로, 제조업체 또는 사업자가 하드웨어 관련 진단을 수행하는 경우에만 사용해야 합니다."</string>
+    <string name="permlab_changeComponentState">"응용프로그램 구성요소 사용 또는 사용 안 함"</string>
+    <string name="permdesc_changeComponentState">"응용프로그램이 다른 응용프로그램 구성요소의 사용 여부를 변경할 수 있습니다. 악성 응용프로그램은 이 기능을 이용하여 중요한 전화기 기능을 사용하지 않도록 설정할 수 있습니다. 응용프로그램 구성요소가 쓸모없게 되거나, 일관성이 없어지거나, 불안정해질 수 있으므로 이 권한을 설정할 때는 주의해야 합니다."</string>
+    <string name="permlab_setPreferredApplications">"기본 응용프로그램 설정"</string>
+    <string name="permdesc_setPreferredApplications">"응용프로그램이 기본 응용프로그램을 수정할 수 있습니다. 악성 응용프로그램은 이 기능을 이용하여 기존 응용프로그램이 사용자의 개인 데이터를 수집하도록 스푸핑하여 실행되는 응용프로그램을 변경할 수 있습니다."</string>
+    <string name="permlab_writeSettings">"전체 시스템 설정 수정"</string>
+    <string name="permdesc_writeSettings">"응용프로그램이 시스템의 설정 데이터를 수정할 수 있습니다. 악성 응용프로그램은 이 기능을 이용하여 시스템 구성을 손상시킬 수 있습니다."</string>
+    <string name="permlab_writeSecureSettings">"보안 시스템 설정 수정"</string>
+    <string name="permdesc_writeSecureSettings">"응용프로그램이 시스템 보안 설정값 데이터를 수정할 수 있습니다. 일반 응용프로그램에서는 사용하지 않습니다."</string>
+    <string name="permlab_writeGservices">"Google 서비스 지도 수정"</string>
+    <string name="permdesc_writeGservices">"응용프로그램이 Google 서비스 지도를 수정할 수 있습니다. 일반 응용프로그램에서는 사용하지 않습니다."</string>
+    <string name="permlab_receiveBootCompleted">"부팅할 때 자동 시작"</string>
+    <string name="permdesc_receiveBootCompleted">"응용프로그램이 시스템 부팅이 끝난 후 바로 시작할 수 있습니다. 이 경우 전화기가 시작하는 데 시간이 오래 걸리고 응용프로그램이 항상 실행되어 전체 전화기 속도가 느려질 수 있습니다."</string>
+    <string name="permlab_broadcastSticky">"남은 브로드캐스트 보내기"</string>
+    <string name="permdesc_broadcastSticky">"응용프로그램이 브로드캐스트가 끝난 후에 남은 브로드캐스트를 보낼 수 있습니다. 악성 응용프로그램은 전화기가 메모리를 너무 많이 사용하도록 하여 속도를 저하시키거나 불안정하게 만들 수 있습니다."</string>
+    <string name="permlab_readContacts">"연락처 데이터 읽기"</string>
+    <string name="permdesc_readContacts">"응용프로그램이 전화기에 저장된 모든 연락처(주소) 데이터를 읽을 수 있습니다. 악성 응용프로그램은 이 기능을 이용하여 데이터를 다른 사람에게 보낼 수 있습니다."</string>
+    <string name="permlab_writeContacts">"연락처 데이터 작성"</string>
+    <string name="permdesc_writeContacts">"응용프로그램이 전화기에 저장된 연락처(주소) 데이터를 수정할 수 있습니다. 악성 응용프로그램은 이 기능을 이용하여 연락처 데이터를 지우거나 수정할 수 있습니다."</string>
+    <string name="permlab_writeOwnerData">"소유자 데이터 작성"</string>
+    <string name="permdesc_writeOwnerData">"응용프로그램이 전화기에 저장된 전화기 소유자 데이터를 수정할 수 있습니다. 악성 응용프로그램은 이 기능을 이용하여 소유자 데이터를 지우거나 수정할 수 있습니다."</string>
+    <string name="permlab_readOwnerData">"소유자 데이터 읽기"</string>
+    <string name="permdesc_readOwnerData">"응용프로그램이 전화기에 저장된 전화기 소유자 데이터를 읽을 수 있습니다. 악성 응용프로그램은 이 기능을 이용하여 전화기 소유자 데이터를 읽을 수 있습니다."</string>
+    <string name="permlab_readCalendar">"캘린더 데이터 읽기"</string>
+    <string name="permdesc_readCalendar">"응용프로그램이 전화기에 저장된 모든 캘린더 일정을 읽을 수 있습니다. 악성 응용프로그램은 이 기능을 이용하여 캘린더 일정을 다른 사람에게 보낼 수 있습니다."</string>
+    <string name="permlab_writeCalendar">"캘린더 데이터 작성"</string>
+    <string name="permdesc_writeCalendar">"응용프로그램이 전화기에 저장된 캘린더 일정을 수정할 수 있습니다. 악성 응용프로그램은 이 기능을 이용하여 캘린더 데이터를 지우거나 수정할 수 있습니다."</string>
+    <string name="permlab_accessMockLocation">"테스트를 위해 위치 소스로 가장"</string>
+    <string name="permdesc_accessMockLocation">"테스트용 가짜 위치 소스를 만듭니다. 악성 응용프로그램은 이 기능을 이용하여 GPS, 네트워크 제공업체 같은 실제 위치 소스에서 반환한 위치 및/또는 상태를 덮어쓸 수 있습니다."</string>
+    <string name="permlab_accessLocationExtraCommands">"추가 위치 제공업체 명령 액세스"</string>
+    <string name="permdesc_accessLocationExtraCommands">"추가 위치 제공업체 명령에 액세스합니다. 악성 응용프로그램은 이 기능을 이용하여 GPS 또는 기타 위치 소스의 작동을 방해할 수 있습니다."</string>
+    <string name="permlab_accessFineLocation">"자세한 (GPS) 위치"</string>
+    <string name="permdesc_accessFineLocation">"가능한 경우 전화기에서 GPS(범지구 위치 측정 시스템) 등의 자세한 위치 소스에 액세스합니다. 악성 응용프로그램은 이 기능을 이용하여 사용자의 위치를 측정하고 추가 배터리 전원을 소비할 수 있습니다."</string>
+    <string name="permlab_accessCoarseLocation">"광범위한 네트워크 기반 위치"</string>
+    <string name="permdesc_accessCoarseLocation">"전화기의 대략적인 위치를 측정하기 위해 셀룰러 네트워크 데이터베이스와 같은 광범위한 위치 소스에 액세스합니다. 악성 응용프로그램은 이 기능을 이용하여 사용자의 위치를 대략적으로 측정할 수 있습니다."</string>
+    <string name="permlab_accessSurfaceFlinger">"SurfaceFlinger 액세스"</string>
+    <string name="permdesc_accessSurfaceFlinger">"응용프로그램이 SurfaceFlinger의 하위 수준 기능을 사용할 수 있습니다."</string>
+    <string name="permlab_readFrameBuffer">"프레임 버퍼 읽기"</string>
+    <string name="permdesc_readFrameBuffer">"응용프로그램이 프레임 버퍼의 콘텐츠를 읽을 수 있습니다."</string>
+    <string name="permlab_modifyAudioSettings">"오디오 설정 변경"</string>
+    <string name="permdesc_modifyAudioSettings">"응용프로그램이 볼륨 및 경로 지정 같은 전체 오디오 설정을 수정할 수 있습니다."</string>
+    <string name="permlab_recordAudio">"오디오 녹음"</string>
+    <string name="permdesc_recordAudio">"응용프로그램이 오디오 레코드 경로에 액세스할 수 있습니다."</string>
+    <string name="permlab_camera">"사진 촬영"</string>
+    <string name="permdesc_camera">"응용프로그램이 카메라로 사진을 찍을 수 있습니다. 이 경우 응용프로그램은 카메라에 표시되는 이미지를 언제든지 수집할 수 있습니다."</string>
+    <string name="permlab_brick">"전화기를 영구적으로 비활성화"</string>
+    <string name="permdesc_brick">"응용프로그램이 전화기를 영구적으로 사용하지 않도록 설정할 수 있습니다. 이 기능은 매우 위험합니다."</string>
+    <string name="permlab_reboot">"전화기 강제로 다시 부팅"</string>
+    <string name="permdesc_reboot">"응용프로그램이 전화기를 강제로 다시 부팅할 수 있습니다."</string>
+    <string name="permlab_mount_unmount_filesystems">"파일시스템 마운트 및 마운트 해제"</string>
+    <string name="permdesc_mount_unmount_filesystems">"응용프로그램이 이동식 저장소의 파일시스템을 마운트하고 마운트 해제할 수 있습니다."</string>
+    <string name="permlab_mount_format_filesystems">"외부 저장소 포맷"</string>
+    <string name="permdesc_mount_format_filesystems">"응용프로그램이 제거 가능한 저장소를 포맷하도록 합니다."</string>
+    <string name="permlab_vibrate">"진동 제어"</string>
+    <string name="permdesc_vibrate">"응용프로그램이 진동을 제어할 수 있습니다."</string>
+    <string name="permlab_flashlight">"손전등 제어"</string>
+    <string name="permdesc_flashlight">"응용프로그램이 손전등을 제어할 수 있습니다."</string>
+    <string name="permlab_hardware_test">"하드웨어 테스트"</string>
+    <string name="permdesc_hardware_test">"응용프로그램이 하드웨어를 테스트할 목적으로 다양한 주변장치를 제어할 수 있습니다."</string>
+    <string name="permlab_callPhone">"전화번호로 직접 전화걸기"</string>
+    <string name="permdesc_callPhone">"응용프로그램이 사용자의 조작 없이 전화번호로 전화를 걸 수 있습니다. 악성 응용프로그램은 전화요금 청구서에 예상치 못한 통화 요금이 부과되도록 할 수 있습니다. 이 권한으로 응용프로그램은 비상 전화를 걸 수 없습니다."</string>
+    <string name="permlab_callPrivileged">"전화번호로 직접 전화걸기"</string>
+    <string name="permdesc_callPrivileged">"응용프로그램이 사용자의 조작 없이 비상 번호를 포함한 전화번호로 전화를 걸 수 있습니다. 악성 응용프로그램은 이 기능을 이용하여 응급 서비스를 불필요하거나 불법적으로 호출할 수 있습니다."</string>
+    <string name="permlab_locationUpdates">"위치 업데이트 알림 제어"</string>
+    <string name="permdesc_locationUpdates">"무선의 위치 업데이트 알림을 활성화하거나 비활성화할 수 있습니다. 일반 응용프로그램에서는 사용하지 않습니다."</string>
+    <string name="permlab_checkinProperties">"체크인 속성 액세스"</string>
+    <string name="permdesc_checkinProperties">"체크인 서비스에서 업로드한 속성에 대한 읽기/쓰기 액세스를 허용합니다. 일반 응용프로그램에서는 사용하지 않습니다."</string>
+    <string name="permlab_bindGadget">"가젯 선택"</string>
+    <string name="permdesc_bindGadget">"어느 응용프로그램이 어느 가젯을 사용할 수 있는지 시스템에 알려 주는 권한을 본 응용프로그램에 부여합니다. 이 권한을 사용하면 응용프로그램이 개인 데이터에 대한 액세스 권한을 다른 응용프로그램에 제공할 수 있습니다. 일반 응용프로그램은 이 권한을 사용할 수 없습니다."</string>
+    <string name="permlab_modifyPhoneState">"전화기 상태 수정"</string>
+    <string name="permdesc_modifyPhoneState">"응용프로그램이 장치의 전화 기능을 제어할 수 있습니다. 이 권한을 갖는 응용프로그램은 사용자에게 알리지 않고 네트워크를 전환하거나, 전화 무선 기능을 켜고 끌 수 있습니다."</string>
+    <string name="permlab_readPhoneState">"전화기 상태 읽기"</string>
+    <string name="permdesc_readPhoneState">"응용프로그램이 장치의 전화 기능에 액세스할 수 있습니다. 이 권한을 갖는 응용프로그램은 전화기의 전화번호, 통화가 활성인지 여부, 통화가 연결된 번호 등을 확인할 수 있습니다."</string>
+    <string name="permlab_wakeLock">"전화기가 절전 모드로 전환되지 않도록 설정"</string>
+    <string name="permdesc_wakeLock">"응용프로그램이 전화기가 절전 모드로 전환되지 않도록 할 수 있습니다."</string>
+    <string name="permlab_devicePower">"전화기 전원 켜고 끄기"</string>
+    <string name="permdesc_devicePower">"응용프로그램이 전화기를 켜거나 끌 수 있습니다."</string>
+    <string name="permlab_factoryTest">"출고 테스트 모드로 실행"</string>
+    <string name="permdesc_factoryTest">"전화기 하드웨어에 대한 완전한 액세스를 허용하는 하위 수준의 제조업체 테스트로 실행됩니다. 전화기가 제조업체 테스트 모드로 실행 중일 때만 사용할 수 있습니다."</string>
+    <string name="permlab_setWallpaper">"배경화면 설정"</string>
+    <string name="permdesc_setWallpaper">"응용프로그램이 시스템 배경화면을 설정할 수 있습니다."</string>
+    <string name="permlab_setWallpaperHints">"배경화면 크기 힌트 설정"</string>
+    <string name="permdesc_setWallpaperHints">"응용프로그램이 시스템 배경화면 크기 힌트를 설정할 수 있습니다."</string>
+    <string name="permlab_masterClear">"시스템을 공장 기본값으로 재설정"</string>
+    <string name="permdesc_masterClear">"응용프로그램이 모든 데이터, 구성 및 설치된 응용프로그램을 지워서 시스템을 공장 기본값으로 완전히 재설정할 수 있습니다."</string>
+    <string name="permlab_setTimeZone">"표준시간대 설정"</string>
+    <string name="permdesc_setTimeZone">"응용프로그램이 전화기의 표준시간대를 변경할 수 있습니다."</string>
+    <string name="permlab_getAccounts">"알려진 계정 검색"</string>
+    <string name="permdesc_getAccounts">"응용프로그램이 전화기에 알려진 계정 목록을 가져올 수 있습니다."</string>
+    <string name="permlab_accessNetworkState">"네트워크 상태 보기"</string>
+    <string name="permdesc_accessNetworkState">"응용프로그램이 모든 네트워크의 상태를 볼 수 있습니다."</string>
+    <string name="permlab_createNetworkSockets">"인터넷에 완전히 액세스"</string>
+    <string name="permdesc_createNetworkSockets">"응용프로그램이 네트워크 소켓을 만들 수 있습니다."</string>
+    <string name="permlab_writeApnSettings">"액세스포인트 이름 설정 쓰기"</string>
+    <string name="permdesc_writeApnSettings">"응용프로그램이 APN의 프록시 및 포트 같은 APN 설정을 수정할 수 있습니다."</string>
+    <string name="permlab_changeNetworkState">"네트워크 연결 변경"</string>
+    <string name="permdesc_changeNetworkState">"응용프로그램이 네트워크 연결 상태를 변경할 수 있습니다."</string>
+    <string name="permlab_changeBackgroundDataSetting">"백그라운드 데이터 사용 설정 변경"</string>
+    <string name="permdesc_changeBackgroundDataSetting">"백그라운드 데이터 사용 설정을 변경할 수 있는 권한을 응용프로그램에 부여합니다."</string>
+    <string name="permlab_accessWifiState">"Wi-Fi 상태 보기"</string>
+    <string name="permdesc_accessWifiState">"응용프로그램이 Wi-Fi의 상태에 대한 정보를 볼 수 있습니다."</string>
+    <string name="permlab_changeWifiState">"Wi-Fi 상태 변경"</string>
+    <string name="permdesc_changeWifiState">"응용프로그램이 Wi-Fi 액세스포인트에 연결하거나 연결을 끊고, 구성된 Wi-Fi 네트워크를 변경할 수 있습니다."</string>
+    <string name="permlab_bluetoothAdmin">"Bluetooth 관리"</string>
+    <string name="permdesc_bluetoothAdmin">"응용프로그램이 로컬 Bluetooth 전화를 구성한 다음 원격 장치를 검색하여 페어링할 수 있습니다."</string>
+    <string name="permlab_bluetooth">"Bluetooth 연결 만들기"</string>
+    <string name="permdesc_bluetooth">"응용프로그램이 로컬 Bluetooth 전화의 구성을 보고 페어링된 장치에 연결하거나 연결을 수락할 수 있습니다."</string>
+    <string name="permlab_disableKeyguard">"키 잠금 사용 안 함"</string>
+    <string name="permdesc_disableKeyguard">"응용프로그램이 키 잠금 및 연결된 비밀번호 보안을 비활성화할 수 있습니다. 예를 들어, 전화기가 수신전화를 받을 때 키 잠금을 비활성화했다가 통화가 끝나면 키 잠금을 다시 활성화할 수 있습니다."</string>
+    <string name="permlab_readSyncSettings">"동기화 설정 읽기"</string>
+    <string name="permdesc_readSyncSettings">"응용프로그램이 연락처에 대해 동기화를 활성화할지 여부 같은 동기화 설정을 읽을 수 있습니다."</string>
+    <string name="permlab_writeSyncSettings">"동기화 설정 쓰기"</string>
+    <string name="permdesc_writeSyncSettings">"응용프로그램이 연락처에 대해 동기화를 활성화할지 여부 같은 동기화 설정을 수정할 수 있습니다."</string>
+    <string name="permlab_readSyncStats">"동기화 통계 읽기"</string>
+    <string name="permdesc_readSyncStats">"응용프로그램이 동기화 통계(예: 실행된 동기화 기록)을 읽을 수 있습니다."</string>
+    <string name="permlab_subscribedFeedsRead">"가입된 피드 읽기"</string>
+    <string name="permdesc_subscribedFeedsRead">"응용프로그램이 현재 동기화된 피드에 대한 상세정보를 가져올 수 있습니다."</string>
+    <string name="permlab_subscribedFeedsWrite">"가입 피드 작성"</string>
+    <string name="permdesc_subscribedFeedsWrite">"응용프로그램이 현재 동기화된 피드를 수정할 수 있습니다. 악성 응용프로그램은 이 기능을 이용하여 동기화된 피드를 변경할 수 있습니다."</string>
+    <string name="permlab_readDictionary">"상용자 정의 사전 읽기"</string>
+    <string name="permdesc_readDictionary">"응용프로그램이 사용자 사전에 보관되어 있을 수 있는 비공개 단어, 이름 및 구문을 읽도록 합니다."</string>
+    <string name="permlab_writeDictionary">"상용자 정의 사전에 작성"</string>
+    <string name="permdesc_writeDictionary">"응용프로그램이 사용자 사전에 새 단어를 입력할 수 있습니다."</string>
+  <string-array name="phoneTypes">
+    <item>"집"</item>
+    <item>"휴대전화"</item>
+    <item>"회사"</item>
+    <item>"회사 팩스"</item>
+    <item>"집(팩스)"</item>
+    <item>"호출기"</item>
+    <item>"기타"</item>
+    <item>"사용자설정"</item>
+  </string-array>
+  <string-array name="emailAddressTypes">
+    <item>"집"</item>
+    <item>"회사"</item>
+    <item>"기타"</item>
+    <item>"사용자설정"</item>
+  </string-array>
+  <string-array name="postalAddressTypes">
+    <item>"집"</item>
+    <item>"회사"</item>
+    <item>"기타"</item>
+    <item>"사용자설정"</item>
+  </string-array>
+  <string-array name="imAddressTypes">
+    <item>"집"</item>
+    <item>"회사"</item>
+    <item>"기타"</item>
+    <item>"사용자설정"</item>
+  </string-array>
+  <string-array name="organizationTypes">
+    <item>"회사"</item>
+    <item>"기타"</item>
+    <item>"사용자설정"</item>
+  </string-array>
+  <string-array name="imProtocols">
+    <item>"AIM"</item>
+    <item>"Windows Live"</item>
+    <item>"Yahoo"</item>
+    <item>"Skype"</item>
+    <item>"QQ"</item>
+    <item>"Google 토크"</item>
+    <item>"ICQ"</item>
+    <item>"Jabber"</item>
+  </string-array>
+    <string name="keyguard_password_enter_pin_code">"PIN 코드 입력"</string>
+    <string name="keyguard_password_wrong_pin_code">"PIN 코드가 잘못되었습니다."</string>
+    <string name="keyguard_label_text">"잠금해제하려면 메뉴를 누른 다음 0을 누릅니다."</string>
+    <string name="emergency_call_dialog_number_for_display">"비상 전화번호"</string>
+    <string name="lockscreen_carrier_default">"(서비스 안 됨)"</string>
+    <string name="lockscreen_screen_locked">"화면 잠김"</string>
+    <string name="lockscreen_instructions_when_pattern_enabled">"비상 전화를 걸거나 잠금해제하려면 메뉴를 누르세요."</string>
+    <string name="lockscreen_instructions_when_pattern_disabled">"잠금해제하려면 메뉴를 누르세요."</string>
+    <string name="lockscreen_pattern_instructions">"잠금해제를 위해 패턴 그리기"</string>
+    <string name="lockscreen_emergency_call">"비상 전화"</string>
+    <string name="lockscreen_pattern_correct">"맞습니다."</string>
+    <string name="lockscreen_pattern_wrong">"죄송합니다. 다시 시도하세요."</string>
+    <!-- no translation found for lockscreen_plugged_in (613343852842944435) -->
+    <skip />
+    <string name="lockscreen_low_battery">"충전기를 연결하세요."</string>
+    <string name="lockscreen_missing_sim_message_short">"SIM 카드가 없습니다."</string>
+    <string name="lockscreen_missing_sim_message">"전화기에 SIM 카드가 없습니다."</string>
+    <string name="lockscreen_missing_sim_instructions">"SIM 카드를 삽입하세요."</string>
+    <string name="lockscreen_network_locked_message">"네트워크 잠김"</string>
+    <string name="lockscreen_sim_puk_locked_message">"SIM 카드의 PUK가 잠겨 있습니다."</string>
+    <string name="lockscreen_sim_puk_locked_instructions">"고객지원팀에 문의하세요."</string>
+    <string name="lockscreen_sim_locked_message">"SIM 카드가 잠겨 있습니다."</string>
+    <string name="lockscreen_sim_unlock_progress_dialog_message">"SIM 카드 잠금해제 중..."</string>
+    <string name="lockscreen_too_many_failed_attempts_dialog_message">"잠금해제 패턴을 <xliff:g id="NUMBER_0">%d</xliff:g>회 잘못 가져왔습니다. "\n\n"<xliff:g id="NUMBER_1">%d</xliff:g>초 후에 다시 시도하세요."</string>
+    <string name="lockscreen_failed_attempts_almost_glogin">"잠금해제 패턴을 <xliff:g id="NUMBER_0">%d</xliff:g>회 잘못 그렸습니다. <xliff:g id="NUMBER_1">%d</xliff:g>회 더 실패하면 Google 로그인을 통해 전화기를 잠금해제하도록 요청됩니다."\n\n" <xliff:g id="NUMBER_2">%d</xliff:g>초 후에 다시 시도하세요."</string>
+    <string name="lockscreen_too_many_failed_attempts_countdown">"<xliff:g id="NUMBER">%d</xliff:g>초 후에 다시 입력하세요."</string>
+    <string name="lockscreen_forgot_pattern_button_text">"패턴을 잊으셨나요?"</string>
+    <string name="lockscreen_glogin_too_many_attempts">"패턴을 너무 많이 시도했습니다."</string>
+    <string name="lockscreen_glogin_instructions">"잠금해제하려면"\n"Google 계정으로 로그인하세요."</string>
+    <string name="lockscreen_glogin_username_hint">"사용자 이름(이메일)"</string>
+    <string name="lockscreen_glogin_password_hint">"비밀번호"</string>
+    <string name="lockscreen_glogin_submit_button">"로그인"</string>
+    <string name="lockscreen_glogin_invalid_input">"사용자 이름 또는 비밀번호가 잘못되었습니다."</string>
+    <string name="status_bar_time_format">"<xliff:g id="HOUR">h</xliff:g>:<xliff:g id="MINUTE">mm</xliff:g> <xliff:g id="AMPM">AA</xliff:g>"</string>
+    <string name="hour_minute_ampm">"<xliff:g id="HOUR">%-l</xliff:g>:<xliff:g id="MINUTE">%M</xliff:g><xliff:g id="AMPM">%P</xliff:g>"</string>
+    <string name="hour_minute_cap_ampm">"<xliff:g id="HOUR">%-l</xliff:g>:<xliff:g id="MINUTE">%M</xliff:g><xliff:g id="AMPM">%p</xliff:g>"</string>
+    <!-- no translation found for hour_ampm (4329881288269772723) -->
+    <skip />
+    <!-- no translation found for hour_cap_ampm (1829009197680861107) -->
+    <skip />
+    <string name="status_bar_clear_all_button">"알림 지우기"</string>
+    <string name="status_bar_no_notifications_title">"알림 없음"</string>
+    <string name="status_bar_ongoing_events_title">"사용 중"</string>
+    <string name="status_bar_latest_events_title">"알림"</string>
+    <!-- no translation found for battery_status_text_percent_format (7660311274698797147) -->
+    <skip />
+    <string name="battery_status_charging">"충전 중..."</string>
+    <string name="battery_low_title">"충전기를 연결하세요."</string>
+    <string name="battery_low_subtitle">"배터리 전원이 부족합니다."</string>
+    <string name="battery_low_percent_format">"<xliff:g id="NUMBER">%d%%</xliff:g> 미만 남음"</string>
+    <string name="factorytest_failed">"출고 테스트 불합격"</string>
+    <string name="factorytest_not_system">"FACTORY_TEST 작업은 /system/app 디렉토리에 설치된 패키지에 대해서만 지원됩니다."</string>
+    <string name="factorytest_no_action">"FACTORY_TEST 작업을 제공하는 패키지가 없습니다."</string>
+    <string name="factorytest_reboot">"다시 부팅"</string>
+    <string name="js_dialog_title">"\'<xliff:g id="TITLE">%s</xliff:g>\' 페이지 내용:"</string>
+    <string name="js_dialog_title_default">"자바스크립트"</string>
+    <string name="js_dialog_before_unload">"다른 페이지를 탐색하시겠습니까?"\n\n"<xliff:g id="MESSAGE">%s</xliff:g>"\n\n"계속하려면 \'확인\'을 선택하고 현재 페이지에 그대로 있으려면 \'취소\'를 선택하세요."</string>
+    <string name="save_password_label">"확인"</string>
+    <string name="save_password_message">"브라우저에 이 비밀번호를 저장하시겠습니까?"</string>
+    <string name="save_password_notnow">"나중에"</string>
+    <string name="save_password_remember">"저장"</string>
+    <string name="save_password_never">"저장 안 함"</string>
+    <string name="open_permission_deny">"페이지를 열 수 있는 권한이 없습니다."</string>
+    <string name="text_copied">"텍스트가 클립보드에 복사되었습니다."</string>
+    <string name="more_item_label">"자세히"</string>
+    <string name="prepend_shortcut_label">"Menu+"</string>
+    <string name="menu_space_shortcut_label">"스페이스바"</string>
+    <string name="menu_enter_shortcut_label">"입력"</string>
+    <string name="menu_delete_shortcut_label">"삭제"</string>
+    <string name="search_go">"검색"</string>
+    <string name="today">"오늘"</string>
+    <string name="yesterday">"어제"</string>
+    <string name="tomorrow">"내일"</string>
+    <string name="oneMonthDurationPast">"한 달 전"</string>
+    <string name="beforeOneMonthDurationPast">"한 달 전"</string>
+  <plurals name="num_seconds_ago">
+    <item quantity="one">"1초 전"</item>
+    <item quantity="other">"<xliff:g id="COUNT">%d</xliff:g>초 전"</item>
+  </plurals>
+  <plurals name="num_minutes_ago">
+    <item quantity="one">"1분 전"</item>
+    <item quantity="other">"<xliff:g id="COUNT">%d</xliff:g>분 전"</item>
+  </plurals>
+  <plurals name="num_hours_ago">
+    <item quantity="one">"1시간 전"</item>
+    <item quantity="other">"<xliff:g id="COUNT">%d</xliff:g>시간 전"</item>
+  </plurals>
+  <plurals name="num_days_ago">
+    <item quantity="one">"어제"</item>
+    <item quantity="other">"<xliff:g id="COUNT">%d</xliff:g>일 전"</item>
+  </plurals>
+  <plurals name="in_num_seconds">
+    <item quantity="one">"1초 내"</item>
+    <item quantity="other">"<xliff:g id="COUNT">%d</xliff:g>초 후"</item>
+  </plurals>
+  <plurals name="in_num_minutes">
+    <item quantity="one">"1분 내"</item>
+    <item quantity="other">"<xliff:g id="COUNT">%d</xliff:g>분 후"</item>
+  </plurals>
+  <plurals name="in_num_hours">
+    <item quantity="one">"1시간 내"</item>
+    <item quantity="other">"<xliff:g id="COUNT">%d</xliff:g>시간 후"</item>
+  </plurals>
+  <plurals name="in_num_days">
+    <item quantity="one">"내일"</item>
+    <item quantity="other">"<xliff:g id="COUNT">%d</xliff:g>일 후"</item>
+  </plurals>
+  <plurals name="abbrev_num_seconds_ago">
+    <item quantity="one">"1초 전"</item>
+    <item quantity="other">"<xliff:g id="COUNT">%d</xliff:g> 초 전"</item>
+  </plurals>
+  <plurals name="abbrev_num_minutes_ago">
+    <item quantity="one">"1분 전"</item>
+    <item quantity="other">"<xliff:g id="COUNT">%d</xliff:g>분 전"</item>
+  </plurals>
+  <plurals name="abbrev_num_hours_ago">
+    <item quantity="one">"1시간 전"</item>
+    <item quantity="other">"<xliff:g id="COUNT">%d</xliff:g>시간 전"</item>
+  </plurals>
+  <plurals name="abbrev_num_days_ago">
+    <item quantity="one">"어제"</item>
+    <item quantity="other">"<xliff:g id="COUNT">%d</xliff:g>일 전"</item>
+  </plurals>
+  <plurals name="abbrev_in_num_seconds">
+    <item quantity="one">"1초 후"</item>
+    <item quantity="other">"<xliff:g id="COUNT">%d</xliff:g> 초 후"</item>
+  </plurals>
+  <plurals name="abbrev_in_num_minutes">
+    <item quantity="one">"1분 후"</item>
+    <item quantity="other">"<xliff:g id="COUNT">%d</xliff:g>분 후"</item>
+  </plurals>
+  <plurals name="abbrev_in_num_hours">
+    <item quantity="one">"1시간 후"</item>
+    <item quantity="other">"<xliff:g id="COUNT">%d</xliff:g>시간 후"</item>
+  </plurals>
+  <plurals name="abbrev_in_num_days">
+    <item quantity="one">"내일"</item>
+    <item quantity="other">"<xliff:g id="COUNT">%d</xliff:g>일 후"</item>
+  </plurals>
+    <string name="preposition_for_date">"%s"</string>
+    <string name="preposition_for_time">"%s"</string>
+    <string name="preposition_for_year">"%s년"</string>
+    <string name="day">"일"</string>
+    <string name="days">"일"</string>
+    <string name="hour">"시간"</string>
+    <string name="hours">"시간"</string>
+    <string name="minute">"분"</string>
+    <string name="minutes">"분"</string>
+    <string name="second">"초"</string>
+    <string name="seconds">"초"</string>
+    <string name="week">"주"</string>
+    <string name="weeks">"주"</string>
+    <string name="year">"년"</string>
+    <string name="years">"년"</string>
+    <string name="sunday">"일요일"</string>
+    <string name="monday">"월요일"</string>
+    <string name="tuesday">"화요일"</string>
+    <string name="wednesday">"수요일"</string>
+    <string name="thursday">"목요일"</string>
+    <string name="friday">"금요일"</string>
+    <string name="saturday">"토요일"</string>
+    <string name="every_weekday">"주중 매일(월-금)"</string>
+    <string name="daily">"매일"</string>
+    <string name="weekly">"매주 <xliff:g id="DAY">%s</xliff:g>"</string>
+    <string name="monthly">"매월"</string>
+    <string name="yearly">"매년"</string>
+    <string name="VideoView_error_title">"동영상 재생 안 됨"</string>
+    <string name="VideoView_error_text_unknown">"죄송합니다. 동영상을 재생할 수 없습니다."</string>
+    <string name="VideoView_error_button">"확인"</string>
+    <string name="am">"AM"</string>
+    <string name="pm">"PM"</string>
+    <string name="numeric_date">"<xliff:g id="MONTH">%m</xliff:g>/<xliff:g id="DAY">%d</xliff:g>/<xliff:g id="YEAR">%Y</xliff:g>"</string>
+    <string name="wday1_date1_time1_wday2_date2_time2">"<xliff:g id="DATE1">%2$s</xliff:g>, <xliff:g id="WEEKDAY1">%1$s</xliff:g>, <xliff:g id="TIME1">%3$s</xliff:g> – <xliff:g id="DATE2">%5$s</xliff:g>, <xliff:g id="WEEKDAY2">%4$s</xliff:g>, <xliff:g id="TIME2">%6$s</xliff:g>"</string>
+    <string name="wday1_date1_wday2_date2">"<xliff:g id="DATE1">%2$s</xliff:g>, <xliff:g id="WEEKDAY1">%1$s</xliff:g> – <xliff:g id="DATE2">%5$s</xliff:g>, <xliff:g id="WEEKDAY2">%4$s</xliff:g>"</string>
+    <string name="date1_time1_date2_time2">"<xliff:g id="DATE1">%2$s</xliff:g>, <xliff:g id="TIME1">%3$s</xliff:g> – <xliff:g id="DATE2">%5$s</xliff:g>, <xliff:g id="TIME2">%6$s</xliff:g>"</string>
+    <string name="date1_date2">"<xliff:g id="DATE1">%2$s</xliff:g> – <xliff:g id="DATE2">%5$s</xliff:g>"</string>
+    <string name="time1_time2">"<xliff:g id="TIME1">%1$s</xliff:g> – <xliff:g id="TIME2">%2$s</xliff:g>"</string>
+    <string name="time_wday_date">"<xliff:g id="DATE">%3$s</xliff:g>, <xliff:g id="WEEKDAY">%2$s</xliff:g>, <xliff:g id="TIME_RANGE">%1$s</xliff:g>"</string>
+    <string name="wday_date">"<xliff:g id="WEEKDAY">%2$s</xliff:g>, <xliff:g id="DATE">%3$s</xliff:g>"</string>
+    <string name="time_date">"<xliff:g id="DATE">%3$s</xliff:g>, <xliff:g id="TIME_RANGE">%1$s</xliff:g>"</string>
+    <string name="date_time">"<xliff:g id="DATE">%1$s</xliff:g>, <xliff:g id="TIME">%2$s</xliff:g>"</string>
+    <string name="relative_time">"<xliff:g id="DATE">%1$s</xliff:g>, <xliff:g id="TIME">%2$s</xliff:g>"</string>
+    <string name="time_wday">"<xliff:g id="WEEKDAY">%2$s</xliff:g>, <xliff:g id="TIME_RANGE">%1$s</xliff:g>"</string>
+    <string name="full_date_month_first" format="date">"<xliff:g id="YEAR">yyyy</xliff:g>' '<xliff:g id="MONTH">MMMM</xliff:g>', '<xliff:g id="DAY">d</xliff:g>"</string>
+    <string name="full_date_day_first" format="date">"<xliff:g id="YEAR">yyyy</xliff:g>' '<xliff:g id="MONTH">MMMM</xliff:g>', '<xliff:g id="DAY">d</xliff:g>"</string>
+    <string name="medium_date_month_first" format="date">"<xliff:g id="MONTH">MMM</xliff:g>' '<xliff:g id="DAY">d</xliff:g>', '<xliff:g id="YEAR">yyyy</xliff:g>"</string>
+    <string name="medium_date_day_first" format="date">"<xliff:g id="DAY">d</xliff:g>' '<xliff:g id="MONTH">MMM</xliff:g>', '<xliff:g id="YEAR">yyyy</xliff:g>"</string>
+    <string name="twelve_hour_time_format" format="date">"<xliff:g id="HOUR">h</xliff:g>':'<xliff:g id="MINUTE">mm</xliff:g>' '<xliff:g id="AMPM">a</xliff:g>"</string>
+    <string name="twenty_four_hour_time_format" format="date">"<xliff:g id="HOUR">H</xliff:g>':'<xliff:g id="MINUTE">mm</xliff:g>"</string>
+    <string name="noon">"정오"</string>
+    <string name="Noon">"정오"</string>
+    <string name="midnight">"자정"</string>
+    <string name="Midnight">"자정"</string>
+    <!-- no translation found for month_day (3693060561170538204) -->
+    <skip />
+    <!-- no translation found for month (1976700695144952053) -->
+    <skip />
+    <string name="month_day_year">"<xliff:g id="YEAR">%Y</xliff:g>, <xliff:g id="MONTH">%B</xliff:g> <xliff:g id="DAY">%-d</xliff:g>"</string>
+    <!-- no translation found for month_year (2106203387378728384) -->
+    <skip />
+    <string name="time_of_day">"<xliff:g id="HOUR">%H</xliff:g>:<xliff:g id="MINUTE">%M</xliff:g>:<xliff:g id="SECOND">%S</xliff:g>"</string>
+    <string name="date_and_time">"<xliff:g id="YEAR">%Y</xliff:g>, <xliff:g id="MONTH">%B</xliff:g> <xliff:g id="DAY">%-d</xliff:g>, <xliff:g id="HOUR">%H</xliff:g>:<xliff:g id="MINUTE">%M</xliff:g>:<xliff:g id="SECOND">%S</xliff:g>"</string>
+    <string name="same_year_md1_md2">"<xliff:g id="MONTH1">%2$s</xliff:g> <xliff:g id="DAY1">%3$s</xliff:g> – <xliff:g id="MONTH2">%7$s</xliff:g> <xliff:g id="DAY2">%8$s</xliff:g>"</string>
+    <string name="same_year_wday1_md1_wday2_md2">"<xliff:g id="MONTH1">%2$s</xliff:g> <xliff:g id="DAY1_0">%3$s</xliff:g>, <xliff:g id="WEEKDAY1">%1$s</xliff:g> – <xliff:g id="MONTH2">%7$s</xliff:g> <xliff:g id="DAY2_1">%8$s</xliff:g>, <xliff:g id="WEEKDAY2">%6$s</xliff:g>"</string>
+    <string name="same_year_mdy1_mdy2">"<xliff:g id="YEAR">%9$s</xliff:g>, <xliff:g id="MONTH1">%2$s</xliff:g> <xliff:g id="DAY1">%3$s</xliff:g> – <xliff:g id="MONTH2">%7$s</xliff:g> <xliff:g id="DAY2">%8$s</xliff:g>"</string>
+    <string name="same_year_wday1_mdy1_wday2_mdy2">"<xliff:g id="YEAR">%9$s</xliff:g>, <xliff:g id="MONTH1">%2$s</xliff:g> <xliff:g id="DAY1_0">%3$s</xliff:g>, <xliff:g id="WEEKDAY1">%1$s</xliff:g> – <xliff:g id="MONTH2">%7$s</xliff:g> <xliff:g id="DAY2_1">%8$s</xliff:g>, <xliff:g id="WEEKDAY2">%6$s</xliff:g>"</string>
+    <string name="same_year_md1_time1_md2_time2">"<xliff:g id="MONTH1">%2$s</xliff:g> <xliff:g id="DAY1">%3$s</xliff:g>, <xliff:g id="TIME1">%5$s</xliff:g> – <xliff:g id="MONTH2">%7$s</xliff:g> <xliff:g id="DAY2">%8$s</xliff:g>, <xliff:g id="TIME2">%10$s</xliff:g>"</string>
+    <string name="same_year_wday1_md1_time1_wday2_md2_time2">"<xliff:g id="MONTH1">%2$s</xliff:g> <xliff:g id="DAY1_0">%3$s</xliff:g>, <xliff:g id="WEEKDAY1">%1$s</xliff:g>, <xliff:g id="TIME1">%5$s</xliff:g> – <xliff:g id="MONTH2">%7$s</xliff:g> <xliff:g id="DAY2_1">%8$s</xliff:g>, <xliff:g id="WEEKDAY2">%6$s</xliff:g>, <xliff:g id="TIME2">%10$s</xliff:g>"</string>
+    <string name="same_year_mdy1_time1_mdy2_time2">"<xliff:g id="YEAR1">%4$s</xliff:g>, <xliff:g id="MONTH1">%2$s</xliff:g> <xliff:g id="DAY1">%3$s</xliff:g>, <xliff:g id="TIME1">%5$s</xliff:g> – <xliff:g id="YEAR2">%9$s</xliff:g>, <xliff:g id="MONTH2">%7$s</xliff:g> <xliff:g id="DAY2">%8$s</xliff:g>, <xliff:g id="TIME2">%10$s</xliff:g>"</string>
+    <string name="same_year_wday1_mdy1_time1_wday2_mdy2_time2">"<xliff:g id="WEEKDAY1">%1$s</xliff:g>, <xliff:g id="MONTH1">%2$s</xliff:g> <xliff:g id="DAY1_0">%3$s</xliff:g>, <xliff:g id="YEAR1">%4$s</xliff:g>, <xliff:g id="TIME1">%5$s</xliff:g> – <xliff:g id="WEEKDAY2">%6$s</xliff:g>, <xliff:g id="MONTH2">%7$s</xliff:g> <xliff:g id="DAY2_1">%8$s</xliff:g>, <xliff:g id="YEAR2">%9$s</xliff:g>, <xliff:g id="TIME2">%10$s</xliff:g>"</string>
+    <string name="numeric_md1_md2">"<xliff:g id="MONTH1">%2$s</xliff:g>/<xliff:g id="DAY1">%3$s</xliff:g> – <xliff:g id="MONTH2">%7$s</xliff:g>/<xliff:g id="DAY2">%8$s</xliff:g>"</string>
+    <string name="numeric_wday1_md1_wday2_md2">"<xliff:g id="MONTH1">%2$s</xliff:g>/<xliff:g id="DAY1_0">%3$s</xliff:g>, <xliff:g id="WEEKDAY1">%1$s</xliff:g> – <xliff:g id="MONTH2">%7$s</xliff:g>/<xliff:g id="DAY2_1">%8$s</xliff:g>, <xliff:g id="WEEKDAY2">%6$s</xliff:g>"</string>
+    <string name="numeric_mdy1_mdy2">"<xliff:g id="YEAR1">%4$s</xliff:g>/<xliff:g id="MONTH1">%2$s</xliff:g>/<xliff:g id="DAY1">%3$s</xliff:g> – <xliff:g id="YEAR2">%9$s</xliff:g>/<xliff:g id="MONTH2">%7$s</xliff:g>/<xliff:g id="DAY2">%8$s</xliff:g>/"</string>
+    <string name="numeric_wday1_mdy1_wday2_mdy2">"<xliff:g id="YEAR1">%4$s</xliff:g>/<xliff:g id="MONTH1">%2$s</xliff:g>/<xliff:g id="DAY1_0">%3$s</xliff:g>, <xliff:g id="WEEKDAY1">%1$s</xliff:g> – <xliff:g id="YEAR2">%9$s</xliff:g>/<xliff:g id="MONTH2">%7$s</xliff:g>/<xliff:g id="DAY2_1">%8$s</xliff:g>, <xliff:g id="WEEKDAY2">%6$s</xliff:g>"</string>
+    <string name="numeric_md1_time1_md2_time2">"<xliff:g id="MONTH1">%2$s</xliff:g>/<xliff:g id="DAY1">%3$s</xliff:g>, <xliff:g id="TIME1">%5$s</xliff:g> – <xliff:g id="MONTH2">%7$s</xliff:g>/<xliff:g id="DAY2">%8$s</xliff:g>, <xliff:g id="TIME2">%10$s</xliff:g>"</string>
+    <string name="numeric_wday1_md1_time1_wday2_md2_time2">"<xliff:g id="MONTH1">%2$s</xliff:g>/<xliff:g id="DAY1_0">%3$s</xliff:g>, <xliff:g id="WEEKDAY1">%1$s</xliff:g>, <xliff:g id="TIME1">%5$s</xliff:g> – <xliff:g id="MONTH2">%7$s</xliff:g>/<xliff:g id="DAY2_1">%8$s</xliff:g>, <xliff:g id="WEEKDAY2">%6$s</xliff:g>, <xliff:g id="TIME2">%10$s</xliff:g>"</string>
+    <string name="numeric_mdy1_time1_mdy2_time2">"<xliff:g id="MONTH1">%2$s</xliff:g>/<xliff:g id="DAY1">%3$s</xliff:g>/<xliff:g id="YEAR1">%4$s</xliff:g>, <xliff:g id="TIME1">%5$s</xliff:g> – <xliff:g id="MONTH2">%7$s</xliff:g>/<xliff:g id="DAY2">%8$s</xliff:g>/<xliff:g id="YEAR2">%9$s</xliff:g>, <xliff:g id="TIME2">%10$s</xliff:g>"</string>
+    <string name="numeric_wday1_mdy1_time1_wday2_mdy2_time2">"<xliff:g id="WEEKDAY1">%1$s</xliff:g>, <xliff:g id="MONTH1">%2$s</xliff:g>/<xliff:g id="DAY1_0">%3$s</xliff:g>/<xliff:g id="YEAR1">%4$s</xliff:g>, <xliff:g id="TIME1">%5$s</xliff:g> – <xliff:g id="WEEKDAY2">%6$s</xliff:g>, <xliff:g id="MONTH2">%7$s</xliff:g>/<xliff:g id="DAY2_1">%8$s</xliff:g>/<xliff:g id="YEAR2">%9$s</xliff:g>, <xliff:g id="TIME2">%10$s</xliff:g>"</string>
+    <string name="same_month_md1_md2">"<xliff:g id="MONTH1">%2$s</xliff:g> <xliff:g id="DAY1">%3$s</xliff:g> – <xliff:g id="DAY2">%8$s</xliff:g>"</string>
+    <string name="same_month_wday1_md1_wday2_md2">"<xliff:g id="MONTH1">%2$s</xliff:g> <xliff:g id="DAY1_0">%3$s</xliff:g>, <xliff:g id="WEEKDAY1">%1$s</xliff:g> – <xliff:g id="MONTH2">%7$s</xliff:g> <xliff:g id="DAY2_1">%8$s</xliff:g>, <xliff:g id="WEEKDAY2">%6$s</xliff:g>"</string>
+    <string name="same_month_mdy1_mdy2">"<xliff:g id="YEAR2">%9$s</xliff:g>, <xliff:g id="MONTH1">%2$s</xliff:g> <xliff:g id="DAY1">%3$s</xliff:g> – <xliff:g id="DAY2">%8$s</xliff:g>"</string>
+    <string name="same_month_wday1_mdy1_wday2_mdy2">"<xliff:g id="YEAR1">%4$s</xliff:g>, <xliff:g id="MONTH1">%2$s</xliff:g> <xliff:g id="DAY1_0">%3$s</xliff:g>, <xliff:g id="WEEKDAY1">%1$s</xliff:g> – <xliff:g id="YEAR2">%9$s</xliff:g>, <xliff:g id="MONTH2">%7$s</xliff:g> <xliff:g id="DAY2_1">%8$s</xliff:g>, <xliff:g id="WEEKDAY2">%6$s</xliff:g>"</string>
+    <string name="same_month_md1_time1_md2_time2">"<xliff:g id="MONTH1">%2$s</xliff:g> <xliff:g id="DAY1">%3$s</xliff:g>, <xliff:g id="TIME1">%5$s</xliff:g> – <xliff:g id="MONTH2">%7$s</xliff:g> <xliff:g id="DAY2">%8$s</xliff:g>, <xliff:g id="TIME2">%10$s</xliff:g>"</string>
+    <string name="same_month_wday1_md1_time1_wday2_md2_time2">"<xliff:g id="WEEKDAY1">%1$s</xliff:g>, <xliff:g id="MONTH1">%2$s</xliff:g> <xliff:g id="DAY1_0">%3$s</xliff:g>, <xliff:g id="TIME1">%5$s</xliff:g> – <xliff:g id="WEEKDAY2">%6$s</xliff:g>, <xliff:g id="MONTH2">%7$s</xliff:g> <xliff:g id="DAY2_1">%8$s</xliff:g>, <xliff:g id="TIME2">%10$s</xliff:g>"</string>
+    <string name="same_month_mdy1_time1_mdy2_time2">"<xliff:g id="MONTH1">%2$s</xliff:g> <xliff:g id="DAY1">%3$s</xliff:g>, <xliff:g id="YEAR1">%4$s</xliff:g>, <xliff:g id="TIME1">%5$s</xliff:g> – <xliff:g id="MONTH2">%7$s</xliff:g> <xliff:g id="DAY2">%8$s</xliff:g>, <xliff:g id="YEAR2">%9$s</xliff:g>, <xliff:g id="TIME2">%10$s</xliff:g>"</string>
+    <string name="same_month_wday1_mdy1_time1_wday2_mdy2_time2">"<xliff:g id="YEAR1">%4$s</xliff:g>, <xliff:g id="MONTH1">%2$s</xliff:g> <xliff:g id="DAY1_0">%3$s</xliff:g>, <xliff:g id="WEEKDAY1">%1$s</xliff:g>, <xliff:g id="TIME1">%5$s</xliff:g> – <xliff:g id="YEAR2">%9$s</xliff:g>, <xliff:g id="MONTH2">%7$s</xliff:g> <xliff:g id="DAY2_1">%8$s</xliff:g>, <xliff:g id="WEEKDAY2">%6$s</xliff:g>, <xliff:g id="TIME2">%10$s</xliff:g>"</string>
+    <string name="abbrev_month_day_year">"<xliff:g id="YEAR">%Y</xliff:g> <xliff:g id="MONTH">%b</xliff:g>, <xliff:g id="DAY">%-d</xliff:g>"</string>
+    <!-- no translation found for abbrev_month_year (5966980891147982768) -->
+    <skip />
+    <!-- no translation found for abbrev_month_day (3156047263406783231) -->
+    <skip />
+    <!-- no translation found for abbrev_month (7304935052615731208) -->
+    <skip />
+    <string name="day_of_week_long_sunday">"일요일"</string>
+    <string name="day_of_week_long_monday">"월요일"</string>
+    <string name="day_of_week_long_tuesday">"화요일"</string>
+    <string name="day_of_week_long_wednesday">"수요일"</string>
+    <string name="day_of_week_long_thursday">"목요일"</string>
+    <string name="day_of_week_long_friday">"금요일"</string>
+    <string name="day_of_week_long_saturday">"토요일"</string>
+    <string name="day_of_week_medium_sunday">"일요일"</string>
+    <string name="day_of_week_medium_monday">"월"</string>
+    <string name="day_of_week_medium_tuesday">"화"</string>
+    <string name="day_of_week_medium_wednesday">"수"</string>
+    <string name="day_of_week_medium_thursday">"목"</string>
+    <string name="day_of_week_medium_friday">"금"</string>
+    <string name="day_of_week_medium_saturday">"토"</string>
+    <string name="day_of_week_short_sunday">"일"</string>
+    <string name="day_of_week_short_monday">"월"</string>
+    <string name="day_of_week_short_tuesday">"화"</string>
+    <string name="day_of_week_short_wednesday">"수"</string>
+    <string name="day_of_week_short_thursday">"목"</string>
+    <string name="day_of_week_short_friday">"금"</string>
+    <string name="day_of_week_short_saturday">"토"</string>
+    <string name="day_of_week_shorter_sunday">"일"</string>
+    <string name="day_of_week_shorter_monday">"월"</string>
+    <string name="day_of_week_shorter_tuesday">"화"</string>
+    <string name="day_of_week_shorter_wednesday">"수"</string>
+    <string name="day_of_week_shorter_thursday">"목"</string>
+    <string name="day_of_week_shorter_friday">"금"</string>
+    <string name="day_of_week_shorter_saturday">"토"</string>
+    <string name="day_of_week_shortest_sunday">"일"</string>
+    <string name="day_of_week_shortest_monday">"3월"</string>
+    <string name="day_of_week_shortest_tuesday">"목"</string>
+    <string name="day_of_week_shortest_wednesday">"수"</string>
+    <string name="day_of_week_shortest_thursday">"목"</string>
+    <string name="day_of_week_shortest_friday">"금"</string>
+    <string name="day_of_week_shortest_saturday">"토"</string>
+    <string name="month_long_january">"1월"</string>
+    <string name="month_long_february">"2월"</string>
+    <string name="month_long_march">"3월"</string>
+    <string name="month_long_april">"4월"</string>
+    <string name="month_long_may">"5월"</string>
+    <string name="month_long_june">"6월"</string>
+    <string name="month_long_july">"7월"</string>
+    <string name="month_long_august">"8월"</string>
+    <string name="month_long_september">"9월"</string>
+    <string name="month_long_october">"10월"</string>
+    <string name="month_long_november">"11월"</string>
+    <string name="month_long_december">"12월"</string>
+    <string name="month_medium_january">"1월"</string>
+    <string name="month_medium_february">"2월"</string>
+    <string name="month_medium_march">"3월"</string>
+    <string name="month_medium_april">"4월"</string>
+    <string name="month_medium_may">"5월"</string>
+    <string name="month_medium_june">"6월"</string>
+    <string name="month_medium_july">"7월"</string>
+    <string name="month_medium_august">"8월"</string>
+    <string name="month_medium_september">"9월"</string>
+    <string name="month_medium_october">"10월"</string>
+    <string name="month_medium_november">"11월"</string>
+    <string name="month_medium_december">"12월"</string>
+    <string name="month_shortest_january">"1월"</string>
+    <string name="month_shortest_february">"금"</string>
+    <string name="month_shortest_march">"3월"</string>
+    <string name="month_shortest_april">"4월"</string>
+    <string name="month_shortest_may">"5월"</string>
+    <string name="month_shortest_june">"6월"</string>
+    <string name="month_shortest_july">"7월"</string>
+    <string name="month_shortest_august">"8월"</string>
+    <string name="month_shortest_september">"9월"</string>
+    <string name="month_shortest_october">"10월"</string>
+    <string name="month_shortest_november">"11월"</string>
+    <string name="month_shortest_december">"12월"</string>
+    <string name="elapsed_time_short_format_mm_ss">"<xliff:g id="MINUTES">%1$02d</xliff:g>:<xliff:g id="SECONDS">%2$02d</xliff:g>"</string>
+    <string name="elapsed_time_short_format_h_mm_ss">"<xliff:g id="HOURS">%1$d</xliff:g>:<xliff:g id="MINUTES">%2$02d</xliff:g>:<xliff:g id="SECONDS">%3$02d</xliff:g>"</string>
+    <string name="selectAll">"모두 선택"</string>
+    <string name="selectText">"텍스트 선택"</string>
+    <string name="stopSelectingText">"텍스트 선택 중지"</string>
+    <string name="cut">"잘라내기"</string>
+    <string name="cutAll">"모두 잘라내기"</string>
+    <string name="copy">"복사"</string>
+    <string name="copyAll">"모두 복사"</string>
+    <string name="paste">"붙여넣기"</string>
+    <string name="copyUrl">"URL 복사"</string>
+    <string name="inputMethod">"입력 방법"</string>
+    <string name="addToDictionary">"사전에 \'%s\' 추가"</string>
+    <string name="editTextMenuTitle">"텍스트 수정"</string>
+    <string name="low_internal_storage_view_title">"저장공간 부족"</string>
+    <string name="low_internal_storage_view_text">"전화기 저장공간이 부족합니다."</string>
+    <string name="ok">"확인"</string>
+    <string name="cancel">"취소"</string>
+    <string name="yes">"확인"</string>
+    <string name="no">"취소"</string>
+    <string name="dialog_alert_title">"주의"</string>
+    <string name="capital_on">"켜짐"</string>
+    <string name="capital_off">"끄기"</string>
+    <string name="whichApplication">"작업을 수행할 때 사용하는 권한"</string>
+    <string name="alwaysUse">"이 작업에 대해 기본값으로 사용"</string>
+    <string name="clearDefaultHintMsg">"홈 설정 &gt; 응용프로그램 &gt; 응용프로그램 관리에서 기본값을 지웁니다."</string>
+    <string name="chooseActivity">"작업 선택"</string>
+    <string name="noApplications">"작업을 수행할 수 있는 응용프로그램이 없습니다."</string>
+    <string name="aerr_title">"죄송합니다."</string>
+    <string name="aerr_application">"<xliff:g id="APPLICATION">%1$s</xliff:g> 응용프로그램(<xliff:g id="PROCESS">%2$s</xliff:g> 프로세스)이 예상치 않게 중지되었습니다. 다시 시도하세요."</string>
+    <string name="aerr_process">"<xliff:g id="PROCESS">%1$s</xliff:g> 프로세스가 예상치 않게 중지되었습니다. 다시 시도하세요."</string>
+    <string name="anr_title">"죄송합니다."</string>
+    <string name="anr_activity_application">"<xliff:g id="ACTIVITY">%1$s</xliff:g> 활동(<xliff:g id="APPLICATION">%2$s</xliff:g> 응용프로그램)이 응답하지 않습니다."</string>
+    <string name="anr_activity_process">"<xliff:g id="ACTIVITY">%1$s</xliff:g> 활동(<xliff:g id="PROCESS">%2$s</xliff:g> 프로세스)이 응답하지 않습니다."</string>
+    <string name="anr_application_process">"<xliff:g id="APPLICATION">%1$s</xliff:g> 응용프로그램(<xliff:g id="PROCESS">%2$s</xliff:g> 프로세스)이 응답하지 않습니다."</string>
+    <string name="anr_process">"<xliff:g id="PROCESS">%1$s</xliff:g> 프로세스가 응답하지 않습니다."</string>
+    <string name="force_close">"강제로 닫기"</string>
+    <string name="wait">"대기"</string>
+    <string name="debug">"디버깅"</string>
+    <string name="sendText">"텍스트에 대한 작업 선택"</string>
+    <string name="volume_ringtone">"벨소리 볼륨"</string>
+    <string name="volume_music">"미디어 볼륨"</string>
+    <string name="volume_music_hint_playing_through_bluetooth">"Bluetooth를 통해 재생"</string>
+    <string name="volume_call">"통화볼륨"</string>
+    <string name="volume_bluetooth_call">"Bluetooth 통화 볼륨"</string>
+    <string name="volume_alarm">"알람 볼륨"</string>
+    <string name="volume_notification">"알림 볼륨"</string>
+    <string name="volume_unknown">"볼륨"</string>
+    <string name="ringtone_default">"기본 벨소리"</string>
+    <string name="ringtone_default_with_actual">"기본 벨소리(<xliff:g id="ACTUAL_RINGTONE">%1$s</xliff:g>)"</string>
+    <string name="ringtone_silent">"무음"</string>
+    <string name="ringtone_picker_title">"벨소리"</string>
+    <string name="ringtone_unknown">"알 수 없는 벨소리"</string>
+  <plurals name="wifi_available">
+    <item quantity="one">"Wi-Fi 네트워크 사용가능"</item>
+    <item quantity="other">"Wi-Fi 네트워크 사용가능"</item>
+  </plurals>
+  <plurals name="wifi_available_detailed">
+    <item quantity="one">"개방형 Wi-Fi 네트워크 사용가능"</item>
+    <item quantity="other">"개방형 Wi-Fi 네트워크 사용가능"</item>
+  </plurals>
+    <string name="select_character">"문자 삽입"</string>
+    <string name="sms_control_default_app_name">"알 수 없는 응용프로그램"</string>
+    <string name="sms_control_title">"SMS 메시지를 보내는 중"</string>
+    <string name="sms_control_message">"여러 개의 SMS 메시지를 보내는 중입니다. 계속하려면 \'확인\'을 선택하고 전송을 중지하려면 \'취소\'를 선택하세요."</string>
+    <string name="sms_control_yes">"확인"</string>
+    <string name="sms_control_no">"취소"</string>
+    <string name="date_time_set">"설정"</string>
+    <string name="default_permission_group">"기본값"</string>
+    <string name="no_permissions">"권한이 필요하지 않음"</string>
+    <string name="perms_hide"><b>"숨기기"</b></string>
+    <string name="perms_show_all"><b>"모두 표시"</b></string>
+    <string name="googlewebcontenthelper_loading">"로드 중..."</string>
+    <string name="usb_storage_title">"USB 연결됨"</string>
+    <string name="usb_storage_message">"USB를 통해 전화기를 컴퓨터에 연결했습니다. 컴퓨터와 전화기 SD 카드 간에 파일을 복사하려면 \'마운트\'를 선택하세요."</string>
+    <string name="usb_storage_button_mount">"마운트"</string>
+    <string name="usb_storage_button_unmount">"마운트 안 함"</string>
+    <string name="usb_storage_error_message">"USB 저장소에 SD 카드를 사용하는 동안 문제가 발생했습니다."</string>
+    <string name="usb_storage_notification_title">"USB 연결됨"</string>
+    <string name="usb_storage_notification_message">"컴퓨터에 파일을 복사하거나 컴퓨터의 파일을 복사하려면 선택합니다."</string>
+    <string name="usb_storage_stop_notification_title">"USB 저장소 끄기"</string>
+    <string name="usb_storage_stop_notification_message">"USB 저장소 끄기를 선택하세요."</string>
+    <string name="usb_storage_stop_title">"USB 저장소 끄기"</string>
+    <string name="usb_storage_stop_message">"USB 저장소를 끄기 전에 반드시 USB 호스를 마운트 해제하세요. USB 저장소를 끄려면 \'끄기\'를 선택하세요."</string>
+    <string name="usb_storage_stop_button_mount">"USB 저장소 끄기"</string>
+    <string name="usb_storage_stop_button_unmount">"취소"</string>
+    <string name="usb_storage_stop_error_message">"USB 저장소를 끄는 동안 Weve에 문제가 발행했습니다. USB 호스트를 마운트 해제했는지 확인한 후 다시 시도하세요."</string>
+    <string name="extmedia_format_title">"SD 카드 포맷"</string>
+    <string name="extmedia_format_message">"SD 카드를 포맷하시겠습니까? 포맷하면 카드의 모든 데이터를 잃게 됩니다."</string>
+    <string name="extmedia_format_button_format">"포맷"</string>
+    <string name="select_input_method">"입력 방법 선택"</string>
+    <string name="fast_scroll_alphabet">" ABCDEFGHIJKLMNOPQRSTUVWXYZ"</string>
+    <string name="fast_scroll_numeric_alphabet">" 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"</string>
+    <string name="candidates_style"><u>"가능한 원인"</u></string>
+    <string name="ext_media_checking_notification_title">"SD 카드 준비 중"</string>
+    <string name="ext_media_checking_notification_message">"오류 확인 중"</string>
+    <string name="ext_media_nofs_notification_title">"빈 SD 카드"</string>
+    <string name="ext_media_nofs_notification_message">"SD 카드가 비어 있거나 지원되지 않는 파일시스템입니다."</string>
+    <string name="ext_media_unmountable_notification_title">"손상된 SD 카드"</string>
+    <string name="ext_media_unmountable_notification_message">"SD 카드가 손상되었습니다. 카드를 다시 포맷하시기 바랍니다."</string>
+    <string name="ext_media_badremoval_notification_title">"SD 카드가 예상치 않게 제거되었습니다."</string>
+    <string name="ext_media_badremoval_notification_message">"데이터 손실을 피하려면 SD 카드를 제거하기 전에 마운트 해제합니다."</string>
+    <string name="ext_media_safe_unmount_notification_title">"SD 카드를 안전하게 제거할 수 있습니다."</string>
+    <string name="ext_media_safe_unmount_notification_message">"이제 SD 카드를 안전하게 제거할 수 있습니다."</string>
+    <string name="ext_media_nomedia_notification_title">"SD 카드를 제거했습니다."</string>
+    <string name="ext_media_nomedia_notification_message">"SD가 제거되었습니다. 기기의 저장 용량을 늘리려면 새 SD 카드를 삽입하세요."</string>
+    <string name="activity_list_empty">"일치하는 활동이 없습니다."</string>
+    <string name="permlab_pkgUsageStats">"구성요소 사용 통계 업데이트"</string>
+    <string name="permdesc_pkgUsageStats">"수집된 구성요소 사용 통계를 수정할 수 있는 권한을 부여합니다. 일반 응용프로그램은 이 권한을 사용할 수 없습니다."</string>
+    <!-- no translation found for tutorial_double_tap_to_zoom_message_short (1311810005957319690) -->
+    <skip />
+    <!-- no translation found for gadget_host_error_inflating (2613287218853846830) -->
+    <skip />
+    <!-- no translation found for ime_action_go (8320845651737369027) -->
+    <skip />
+    <!-- no translation found for ime_action_search (658110271822807811) -->
+    <skip />
+    <!-- no translation found for ime_action_send (2316166556349314424) -->
+    <skip />
+    <!-- no translation found for ime_action_next (3138843904009813834) -->
+    <skip />
+    <!-- no translation found for ime_action_default (2840921885558045721) -->
+    <skip />
+</resources>
diff --git a/core/res/res/values-mcc204-cs/strings.xml b/core/res/res/values-mcc204-cs/strings.xml
new file mode 100644
index 0000000..7d96230
--- /dev/null
+++ b/core/res/res/values-mcc204-cs/strings.xml
@@ -0,0 +1,19 @@
+<?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.
+-->
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="locale_replacement">"nl_nl"</string>
+</resources>
diff --git a/core/res/res/values-mcc204-de/strings.xml b/core/res/res/values-mcc204-de/strings.xml
new file mode 100644
index 0000000..7d96230
--- /dev/null
+++ b/core/res/res/values-mcc204-de/strings.xml
@@ -0,0 +1,19 @@
+<?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.
+-->
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="locale_replacement">"nl_nl"</string>
+</resources>
diff --git a/core/res/res/values-mcc204-es/strings.xml b/core/res/res/values-mcc204-es/strings.xml
new file mode 100644
index 0000000..7d96230
--- /dev/null
+++ b/core/res/res/values-mcc204-es/strings.xml
@@ -0,0 +1,19 @@
+<?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.
+-->
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="locale_replacement">"nl_nl"</string>
+</resources>
diff --git a/core/res/res/values-mcc204-fr/strings.xml b/core/res/res/values-mcc204-fr/strings.xml
new file mode 100644
index 0000000..7d96230
--- /dev/null
+++ b/core/res/res/values-mcc204-fr/strings.xml
@@ -0,0 +1,19 @@
+<?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.
+-->
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="locale_replacement">"nl_nl"</string>
+</resources>
diff --git a/core/res/res/values-mcc204-it/strings.xml b/core/res/res/values-mcc204-it/strings.xml
new file mode 100644
index 0000000..7d96230
--- /dev/null
+++ b/core/res/res/values-mcc204-it/strings.xml
@@ -0,0 +1,19 @@
+<?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.
+-->
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="locale_replacement">"nl_nl"</string>
+</resources>
diff --git a/core/res/res/values-mcc204-ja/strings.xml b/core/res/res/values-mcc204-ja/strings.xml
new file mode 100644
index 0000000..7d96230
--- /dev/null
+++ b/core/res/res/values-mcc204-ja/strings.xml
@@ -0,0 +1,19 @@
+<?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.
+-->
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="locale_replacement">"nl_nl"</string>
+</resources>
diff --git a/core/res/res/values-mcc204-ko/strings.xml b/core/res/res/values-mcc204-ko/strings.xml
new file mode 100644
index 0000000..7d96230
--- /dev/null
+++ b/core/res/res/values-mcc204-ko/strings.xml
@@ -0,0 +1,19 @@
+<?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.
+-->
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="locale_replacement">"nl_nl"</string>
+</resources>
diff --git a/core/res/res/values-mcc204-nl/strings.xml b/core/res/res/values-mcc204-nl/strings.xml
new file mode 100644
index 0000000..7d96230
--- /dev/null
+++ b/core/res/res/values-mcc204-nl/strings.xml
@@ -0,0 +1,19 @@
+<?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.
+-->
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="locale_replacement">"nl_nl"</string>
+</resources>
diff --git a/core/res/res/values-mcc204-pl/strings.xml b/core/res/res/values-mcc204-pl/strings.xml
new file mode 100644
index 0000000..7d96230
--- /dev/null
+++ b/core/res/res/values-mcc204-pl/strings.xml
@@ -0,0 +1,19 @@
+<?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.
+-->
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="locale_replacement">"nl_nl"</string>
+</resources>
diff --git a/core/res/res/values-mcc204-ru/strings.xml b/core/res/res/values-mcc204-ru/strings.xml
new file mode 100644
index 0000000..7d96230
--- /dev/null
+++ b/core/res/res/values-mcc204-ru/strings.xml
@@ -0,0 +1,19 @@
+<?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.
+-->
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="locale_replacement">"nl_nl"</string>
+</resources>
diff --git a/core/res/res/values-mcc204-zh-rCN/strings.xml b/core/res/res/values-mcc204-zh-rCN/strings.xml
new file mode 100644
index 0000000..7d96230
--- /dev/null
+++ b/core/res/res/values-mcc204-zh-rCN/strings.xml
@@ -0,0 +1,19 @@
+<?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.
+-->
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="locale_replacement">"nl_nl"</string>
+</resources>
diff --git a/core/res/res/values-mcc204-zh-rTW/strings.xml b/core/res/res/values-mcc204-zh-rTW/strings.xml
new file mode 100644
index 0000000..7d96230
--- /dev/null
+++ b/core/res/res/values-mcc204-zh-rTW/strings.xml
@@ -0,0 +1,19 @@
+<?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.
+-->
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="locale_replacement">"nl_nl"</string>
+</resources>
diff --git a/core/res/res/values-mcc204/arrays.xml b/core/res/res/values-mcc204/arrays.xml
new file mode 100644
index 0000000..7902a13
--- /dev/null
+++ b/core/res/res/values-mcc204/arrays.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* 
+**
+** Copyright 2006, Google Inc.
+**
+** 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.
+*/
+-->
+<resources>
+
+    <!-- Do not translate. -->
+    <integer-array name="maps_starting_lat_lng">
+        <item>51589256</item>
+        <item>4774396</item>
+    </integer-array>
+    <!-- Do not translate. -->
+    <integer-array name="maps_starting_zoom">
+        <item>6</item>
+    </integer-array>
+
+</resources>
diff --git a/core/res/res/values-mcc204/strings.xml b/core/res/res/values-mcc204/strings.xml
new file mode 100644
index 0000000..c3fff3d
--- /dev/null
+++ b/core/res/res/values-mcc204/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/**
+ * Copyright (c) 2008, Google Inc.
+ *
+ * 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.
+ */
+-->
+
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+
+    <!-- A string used to replace %s in a URL to fill in the locale for countries  -->
+    <!-- whose locale we don't natively support.  A 0 length string triggers no replacement -->
+    <string name="locale_replacement">nl_nl</string>    
+</resources>
diff --git a/core/res/res/values-mcc230-cs/strings.xml b/core/res/res/values-mcc230-cs/strings.xml
new file mode 100644
index 0000000..d3ecdbb
--- /dev/null
+++ b/core/res/res/values-mcc230-cs/strings.xml
@@ -0,0 +1,19 @@
+<?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.
+-->
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="locale_replacement">"cs_cz"</string>
+</resources>
diff --git a/core/res/res/values-mcc230-de/strings.xml b/core/res/res/values-mcc230-de/strings.xml
new file mode 100644
index 0000000..d3ecdbb
--- /dev/null
+++ b/core/res/res/values-mcc230-de/strings.xml
@@ -0,0 +1,19 @@
+<?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.
+-->
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="locale_replacement">"cs_cz"</string>
+</resources>
diff --git a/core/res/res/values-mcc230-es/strings.xml b/core/res/res/values-mcc230-es/strings.xml
new file mode 100644
index 0000000..d3ecdbb
--- /dev/null
+++ b/core/res/res/values-mcc230-es/strings.xml
@@ -0,0 +1,19 @@
+<?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.
+-->
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="locale_replacement">"cs_cz"</string>
+</resources>
diff --git a/core/res/res/values-mcc230-fr/strings.xml b/core/res/res/values-mcc230-fr/strings.xml
new file mode 100644
index 0000000..d3ecdbb
--- /dev/null
+++ b/core/res/res/values-mcc230-fr/strings.xml
@@ -0,0 +1,19 @@
+<?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.
+-->
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="locale_replacement">"cs_cz"</string>
+</resources>
diff --git a/core/res/res/values-mcc230-it/strings.xml b/core/res/res/values-mcc230-it/strings.xml
new file mode 100644
index 0000000..d3ecdbb
--- /dev/null
+++ b/core/res/res/values-mcc230-it/strings.xml
@@ -0,0 +1,19 @@
+<?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.
+-->
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="locale_replacement">"cs_cz"</string>
+</resources>
diff --git a/core/res/res/values-mcc230-ja/strings.xml b/core/res/res/values-mcc230-ja/strings.xml
new file mode 100644
index 0000000..d3ecdbb
--- /dev/null
+++ b/core/res/res/values-mcc230-ja/strings.xml
@@ -0,0 +1,19 @@
+<?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.
+-->
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="locale_replacement">"cs_cz"</string>
+</resources>
diff --git a/core/res/res/values-mcc230-ko/strings.xml b/core/res/res/values-mcc230-ko/strings.xml
new file mode 100644
index 0000000..d3ecdbb
--- /dev/null
+++ b/core/res/res/values-mcc230-ko/strings.xml
@@ -0,0 +1,19 @@
+<?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.
+-->
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="locale_replacement">"cs_cz"</string>
+</resources>
diff --git a/core/res/res/values-mcc230-nl/strings.xml b/core/res/res/values-mcc230-nl/strings.xml
new file mode 100644
index 0000000..d3ecdbb
--- /dev/null
+++ b/core/res/res/values-mcc230-nl/strings.xml
@@ -0,0 +1,19 @@
+<?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.
+-->
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="locale_replacement">"cs_cz"</string>
+</resources>
diff --git a/core/res/res/values-mcc230-pl/strings.xml b/core/res/res/values-mcc230-pl/strings.xml
new file mode 100644
index 0000000..d3ecdbb
--- /dev/null
+++ b/core/res/res/values-mcc230-pl/strings.xml
@@ -0,0 +1,19 @@
+<?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.
+-->
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="locale_replacement">"cs_cz"</string>
+</resources>
diff --git a/core/res/res/values-mcc230-ru/strings.xml b/core/res/res/values-mcc230-ru/strings.xml
new file mode 100644
index 0000000..d3ecdbb
--- /dev/null
+++ b/core/res/res/values-mcc230-ru/strings.xml
@@ -0,0 +1,19 @@
+<?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.
+-->
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="locale_replacement">"cs_cz"</string>
+</resources>
diff --git a/core/res/res/values-mcc230-zh-rCN/strings.xml b/core/res/res/values-mcc230-zh-rCN/strings.xml
new file mode 100644
index 0000000..d3ecdbb
--- /dev/null
+++ b/core/res/res/values-mcc230-zh-rCN/strings.xml
@@ -0,0 +1,19 @@
+<?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.
+-->
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="locale_replacement">"cs_cz"</string>
+</resources>
diff --git a/core/res/res/values-mcc230-zh-rTW/strings.xml b/core/res/res/values-mcc230-zh-rTW/strings.xml
new file mode 100644
index 0000000..d3ecdbb
--- /dev/null
+++ b/core/res/res/values-mcc230-zh-rTW/strings.xml
@@ -0,0 +1,19 @@
+<?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.
+-->
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="locale_replacement">"cs_cz"</string>
+</resources>
diff --git a/core/res/res/values-mcc230/arrays.xml b/core/res/res/values-mcc230/arrays.xml
new file mode 100644
index 0000000..71c3ed6
--- /dev/null
+++ b/core/res/res/values-mcc230/arrays.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* 
+**
+** Copyright 2006, Google Inc.
+**
+** 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.
+*/
+-->
+<resources>
+
+    <!-- Do not translate. -->
+    <integer-array name="maps_starting_lat_lng">
+        <item>50087811</item>
+        <item>14420460</item>
+    </integer-array>
+    <!-- Do not translate. -->
+    <integer-array name="maps_starting_zoom">
+        <item>6</item>
+    </integer-array>
+
+</resources>
diff --git a/core/res/res/values-mcc230/strings.xml b/core/res/res/values-mcc230/strings.xml
new file mode 100644
index 0000000..559b858
--- /dev/null
+++ b/core/res/res/values-mcc230/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/**
+ * Copyright (c) 2008, Google Inc.
+ *
+ * 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.
+ */
+-->
+
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+
+    <!-- A string used to replace %s in a URL to fill in the locale for countries  -->
+    <!-- whose locale we don't natively support.  A 0 length string triggers no replacement -->
+    <string name="locale_replacement">cs_cz</string>    
+</resources>
diff --git a/core/res/res/values-mcc232-cs/strings.xml b/core/res/res/values-mcc232-cs/strings.xml
new file mode 100644
index 0000000..4773838
--- /dev/null
+++ b/core/res/res/values-mcc232-cs/strings.xml
@@ -0,0 +1,19 @@
+<?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.
+-->
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="locale_replacement">"de_at"</string>
+</resources>
diff --git a/core/res/res/values-mcc232-de/strings.xml b/core/res/res/values-mcc232-de/strings.xml
new file mode 100644
index 0000000..4773838
--- /dev/null
+++ b/core/res/res/values-mcc232-de/strings.xml
@@ -0,0 +1,19 @@
+<?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.
+-->
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="locale_replacement">"de_at"</string>
+</resources>
diff --git a/core/res/res/values-mcc232-es/strings.xml b/core/res/res/values-mcc232-es/strings.xml
new file mode 100644
index 0000000..4773838
--- /dev/null
+++ b/core/res/res/values-mcc232-es/strings.xml
@@ -0,0 +1,19 @@
+<?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.
+-->
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="locale_replacement">"de_at"</string>
+</resources>
diff --git a/core/res/res/values-mcc232-fr/strings.xml b/core/res/res/values-mcc232-fr/strings.xml
new file mode 100644
index 0000000..4773838
--- /dev/null
+++ b/core/res/res/values-mcc232-fr/strings.xml
@@ -0,0 +1,19 @@
+<?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.
+-->
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="locale_replacement">"de_at"</string>
+</resources>
diff --git a/core/res/res/values-mcc232-it/strings.xml b/core/res/res/values-mcc232-it/strings.xml
new file mode 100644
index 0000000..4773838
--- /dev/null
+++ b/core/res/res/values-mcc232-it/strings.xml
@@ -0,0 +1,19 @@
+<?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.
+-->
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="locale_replacement">"de_at"</string>
+</resources>
diff --git a/core/res/res/values-mcc232-ja/strings.xml b/core/res/res/values-mcc232-ja/strings.xml
new file mode 100644
index 0000000..4773838
--- /dev/null
+++ b/core/res/res/values-mcc232-ja/strings.xml
@@ -0,0 +1,19 @@
+<?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.
+-->
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="locale_replacement">"de_at"</string>
+</resources>
diff --git a/core/res/res/values-mcc232-ko/strings.xml b/core/res/res/values-mcc232-ko/strings.xml
new file mode 100644
index 0000000..4773838
--- /dev/null
+++ b/core/res/res/values-mcc232-ko/strings.xml
@@ -0,0 +1,19 @@
+<?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.
+-->
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="locale_replacement">"de_at"</string>
+</resources>
diff --git a/core/res/res/values-mcc232-nl/strings.xml b/core/res/res/values-mcc232-nl/strings.xml
new file mode 100644
index 0000000..4773838
--- /dev/null
+++ b/core/res/res/values-mcc232-nl/strings.xml
@@ -0,0 +1,19 @@
+<?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.
+-->
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="locale_replacement">"de_at"</string>
+</resources>
diff --git a/core/res/res/values-mcc232-pl/strings.xml b/core/res/res/values-mcc232-pl/strings.xml
new file mode 100644
index 0000000..4773838
--- /dev/null
+++ b/core/res/res/values-mcc232-pl/strings.xml
@@ -0,0 +1,19 @@
+<?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.
+-->
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="locale_replacement">"de_at"</string>
+</resources>
diff --git a/core/res/res/values-mcc232-ru/strings.xml b/core/res/res/values-mcc232-ru/strings.xml
new file mode 100644
index 0000000..4773838
--- /dev/null
+++ b/core/res/res/values-mcc232-ru/strings.xml
@@ -0,0 +1,19 @@
+<?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.
+-->
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="locale_replacement">"de_at"</string>
+</resources>
diff --git a/core/res/res/values-mcc232-zh-rCN/strings.xml b/core/res/res/values-mcc232-zh-rCN/strings.xml
new file mode 100644
index 0000000..4773838
--- /dev/null
+++ b/core/res/res/values-mcc232-zh-rCN/strings.xml
@@ -0,0 +1,19 @@
+<?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.
+-->
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="locale_replacement">"de_at"</string>
+</resources>
diff --git a/core/res/res/values-mcc232-zh-rTW/strings.xml b/core/res/res/values-mcc232-zh-rTW/strings.xml
new file mode 100644
index 0000000..4773838
--- /dev/null
+++ b/core/res/res/values-mcc232-zh-rTW/strings.xml
@@ -0,0 +1,19 @@
+<?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.
+-->
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="locale_replacement">"de_at"</string>
+</resources>
diff --git a/core/res/res/values-mcc232/arrays.xml b/core/res/res/values-mcc232/arrays.xml
new file mode 100644
index 0000000..98458f8
--- /dev/null
+++ b/core/res/res/values-mcc232/arrays.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* 
+**
+** Copyright 2006, Google Inc.
+**
+** 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.
+*/
+-->
+<resources>
+
+    <!-- Do not translate. -->
+    <integer-array name="maps_starting_lat_lng">
+        <item>48209206</item>
+        <item>16372778</item>
+    </integer-array>
+    <!-- Do not translate. -->
+    <integer-array name="maps_starting_zoom">
+        <item>6</item>
+    </integer-array>
+
+</resources>
diff --git a/core/res/res/values-mcc232/strings.xml b/core/res/res/values-mcc232/strings.xml
new file mode 100644
index 0000000..494770f
--- /dev/null
+++ b/core/res/res/values-mcc232/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/**
+ * Copyright (c) 2008, Google Inc.
+ *
+ * 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.
+ */
+-->
+
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+
+    <!-- A string used to replace %s in a URL to fill in the locale for countries  -->
+    <!-- whose locale we don't natively support.  A 0 length string triggers no replacement -->
+    <string name="locale_replacement">de_at</string>    
+</resources>
diff --git a/core/res/res/values-mcc234-cs/strings.xml b/core/res/res/values-mcc234-cs/strings.xml
new file mode 100644
index 0000000..2538b73
--- /dev/null
+++ b/core/res/res/values-mcc234-cs/strings.xml
@@ -0,0 +1,19 @@
+<?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.
+-->
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="locale_replacement">"en_gb"</string>
+</resources>
diff --git a/core/res/res/values-mcc234-de/strings.xml b/core/res/res/values-mcc234-de/strings.xml
new file mode 100644
index 0000000..2538b73
--- /dev/null
+++ b/core/res/res/values-mcc234-de/strings.xml
@@ -0,0 +1,19 @@
+<?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.
+-->
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="locale_replacement">"en_gb"</string>
+</resources>
diff --git a/core/res/res/values-mcc234-es/strings.xml b/core/res/res/values-mcc234-es/strings.xml
new file mode 100644
index 0000000..2538b73
--- /dev/null
+++ b/core/res/res/values-mcc234-es/strings.xml
@@ -0,0 +1,19 @@
+<?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.
+-->
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="locale_replacement">"en_gb"</string>
+</resources>
diff --git a/core/res/res/values-mcc234-fr/strings.xml b/core/res/res/values-mcc234-fr/strings.xml
new file mode 100644
index 0000000..2538b73
--- /dev/null
+++ b/core/res/res/values-mcc234-fr/strings.xml
@@ -0,0 +1,19 @@
+<?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.
+-->
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="locale_replacement">"en_gb"</string>
+</resources>
diff --git a/core/res/res/values-mcc234-it/strings.xml b/core/res/res/values-mcc234-it/strings.xml
new file mode 100644
index 0000000..2538b73
--- /dev/null
+++ b/core/res/res/values-mcc234-it/strings.xml
@@ -0,0 +1,19 @@
+<?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.
+-->
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="locale_replacement">"en_gb"</string>
+</resources>
diff --git a/core/res/res/values-mcc234-ja/strings.xml b/core/res/res/values-mcc234-ja/strings.xml
new file mode 100644
index 0000000..2538b73
--- /dev/null
+++ b/core/res/res/values-mcc234-ja/strings.xml
@@ -0,0 +1,19 @@
+<?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.
+-->
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="locale_replacement">"en_gb"</string>
+</resources>
diff --git a/core/res/res/values-mcc234-ko/strings.xml b/core/res/res/values-mcc234-ko/strings.xml
new file mode 100644
index 0000000..2538b73
--- /dev/null
+++ b/core/res/res/values-mcc234-ko/strings.xml
@@ -0,0 +1,19 @@
+<?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.
+-->
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="locale_replacement">"en_gb"</string>
+</resources>
diff --git a/core/res/res/values-mcc234-nl/strings.xml b/core/res/res/values-mcc234-nl/strings.xml
new file mode 100644
index 0000000..2538b73
--- /dev/null
+++ b/core/res/res/values-mcc234-nl/strings.xml
@@ -0,0 +1,19 @@
+<?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.
+-->
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="locale_replacement">"en_gb"</string>
+</resources>
diff --git a/core/res/res/values-mcc234-pl/strings.xml b/core/res/res/values-mcc234-pl/strings.xml
new file mode 100644
index 0000000..2538b73
--- /dev/null
+++ b/core/res/res/values-mcc234-pl/strings.xml
@@ -0,0 +1,19 @@
+<?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.
+-->
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="locale_replacement">"en_gb"</string>
+</resources>
diff --git a/core/res/res/values-mcc234-ru/strings.xml b/core/res/res/values-mcc234-ru/strings.xml
new file mode 100644
index 0000000..2538b73
--- /dev/null
+++ b/core/res/res/values-mcc234-ru/strings.xml
@@ -0,0 +1,19 @@
+<?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.
+-->
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="locale_replacement">"en_gb"</string>
+</resources>
diff --git a/core/res/res/values-mcc234-zh-rCN/strings.xml b/core/res/res/values-mcc234-zh-rCN/strings.xml
new file mode 100644
index 0000000..2538b73
--- /dev/null
+++ b/core/res/res/values-mcc234-zh-rCN/strings.xml
@@ -0,0 +1,19 @@
+<?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.
+-->
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="locale_replacement">"en_gb"</string>
+</resources>
diff --git a/core/res/res/values-mcc234-zh-rTW/strings.xml b/core/res/res/values-mcc234-zh-rTW/strings.xml
new file mode 100644
index 0000000..2538b73
--- /dev/null
+++ b/core/res/res/values-mcc234-zh-rTW/strings.xml
@@ -0,0 +1,19 @@
+<?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.
+-->
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="locale_replacement">"en_gb"</string>
+</resources>
diff --git a/core/res/res/values-mcc234/strings.xml b/core/res/res/values-mcc234/strings.xml
new file mode 100644
index 0000000..2e6a3f3
--- /dev/null
+++ b/core/res/res/values-mcc234/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/**
+ * Copyright (c) 2008, Google Inc.
+ *
+ * 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.
+ */
+-->
+
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+
+    <!-- A string used to replace %s in a URL to fill in the locale for countries  -->
+    <!-- whose locale we don't natively support.  A 0 length string triggers no replacement -->
+    <string name="locale_replacement">en_gb</string>    
+</resources>
diff --git a/core/res/res/values-mcc260-cs/strings.xml b/core/res/res/values-mcc260-cs/strings.xml
new file mode 100644
index 0000000..1161f9a
--- /dev/null
+++ b/core/res/res/values-mcc260-cs/strings.xml
@@ -0,0 +1,19 @@
+<?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.
+-->
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="locale_replacement">"pl_pl"</string>
+</resources>
diff --git a/core/res/res/values-mcc260-de/strings.xml b/core/res/res/values-mcc260-de/strings.xml
new file mode 100644
index 0000000..1161f9a
--- /dev/null
+++ b/core/res/res/values-mcc260-de/strings.xml
@@ -0,0 +1,19 @@
+<?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.
+-->
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="locale_replacement">"pl_pl"</string>
+</resources>
diff --git a/core/res/res/values-mcc260-es/strings.xml b/core/res/res/values-mcc260-es/strings.xml
new file mode 100644
index 0000000..1161f9a
--- /dev/null
+++ b/core/res/res/values-mcc260-es/strings.xml
@@ -0,0 +1,19 @@
+<?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.
+-->
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="locale_replacement">"pl_pl"</string>
+</resources>
diff --git a/core/res/res/values-mcc260-fr/strings.xml b/core/res/res/values-mcc260-fr/strings.xml
new file mode 100644
index 0000000..1161f9a
--- /dev/null
+++ b/core/res/res/values-mcc260-fr/strings.xml
@@ -0,0 +1,19 @@
+<?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.
+-->
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="locale_replacement">"pl_pl"</string>
+</resources>
diff --git a/core/res/res/values-mcc260-it/strings.xml b/core/res/res/values-mcc260-it/strings.xml
new file mode 100644
index 0000000..1161f9a
--- /dev/null
+++ b/core/res/res/values-mcc260-it/strings.xml
@@ -0,0 +1,19 @@
+<?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.
+-->
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="locale_replacement">"pl_pl"</string>
+</resources>
diff --git a/core/res/res/values-mcc260-ja/strings.xml b/core/res/res/values-mcc260-ja/strings.xml
new file mode 100644
index 0000000..1161f9a
--- /dev/null
+++ b/core/res/res/values-mcc260-ja/strings.xml
@@ -0,0 +1,19 @@
+<?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.
+-->
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="locale_replacement">"pl_pl"</string>
+</resources>
diff --git a/core/res/res/values-mcc260-ko/strings.xml b/core/res/res/values-mcc260-ko/strings.xml
new file mode 100644
index 0000000..1161f9a
--- /dev/null
+++ b/core/res/res/values-mcc260-ko/strings.xml
@@ -0,0 +1,19 @@
+<?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.
+-->
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="locale_replacement">"pl_pl"</string>
+</resources>
diff --git a/core/res/res/values-mcc260-nl/strings.xml b/core/res/res/values-mcc260-nl/strings.xml
new file mode 100644
index 0000000..1161f9a
--- /dev/null
+++ b/core/res/res/values-mcc260-nl/strings.xml
@@ -0,0 +1,19 @@
+<?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.
+-->
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="locale_replacement">"pl_pl"</string>
+</resources>
diff --git a/core/res/res/values-mcc260-pl/strings.xml b/core/res/res/values-mcc260-pl/strings.xml
new file mode 100644
index 0000000..1161f9a
--- /dev/null
+++ b/core/res/res/values-mcc260-pl/strings.xml
@@ -0,0 +1,19 @@
+<?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.
+-->
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="locale_replacement">"pl_pl"</string>
+</resources>
diff --git a/core/res/res/values-mcc260-ru/strings.xml b/core/res/res/values-mcc260-ru/strings.xml
new file mode 100644
index 0000000..1161f9a
--- /dev/null
+++ b/core/res/res/values-mcc260-ru/strings.xml
@@ -0,0 +1,19 @@
+<?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.
+-->
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="locale_replacement">"pl_pl"</string>
+</resources>
diff --git a/core/res/res/values-mcc260-zh-rCN/strings.xml b/core/res/res/values-mcc260-zh-rCN/strings.xml
new file mode 100644
index 0000000..1161f9a
--- /dev/null
+++ b/core/res/res/values-mcc260-zh-rCN/strings.xml
@@ -0,0 +1,19 @@
+<?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.
+-->
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="locale_replacement">"pl_pl"</string>
+</resources>
diff --git a/core/res/res/values-mcc260-zh-rTW/strings.xml b/core/res/res/values-mcc260-zh-rTW/strings.xml
new file mode 100644
index 0000000..1161f9a
--- /dev/null
+++ b/core/res/res/values-mcc260-zh-rTW/strings.xml
@@ -0,0 +1,19 @@
+<?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.
+-->
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="locale_replacement">"pl_pl"</string>
+</resources>
diff --git a/core/res/res/values-mcc260/arrays.xml b/core/res/res/values-mcc260/arrays.xml
new file mode 100644
index 0000000..03447ab
--- /dev/null
+++ b/core/res/res/values-mcc260/arrays.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* 
+**
+** Copyright 2006, Google Inc.
+**
+** 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.
+*/
+-->
+<resources>
+
+    <!-- Do not translate. -->
+    <integer-array name="maps_starting_lat_lng">
+        <item>59910761</item>
+        <item>10749092</item>
+    </integer-array>
+    <!-- Do not translate. -->
+    <integer-array name="maps_starting_zoom">
+        <item>5</item>
+    </integer-array>
+
+</resources>
diff --git a/core/res/res/values-mcc260/strings.xml b/core/res/res/values-mcc260/strings.xml
new file mode 100644
index 0000000..20c19dd
--- /dev/null
+++ b/core/res/res/values-mcc260/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/**
+ * Copyright (c) 2008, Google Inc.
+ *
+ * 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.
+ */
+-->
+
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+
+    <!-- A string used to replace %s in a URL to fill in the locale for countries  -->
+    <!-- whose locale we don't natively support.  A 0 length string triggers no replacement -->
+    <string name="locale_replacement">pl_pl</string>    
+</resources>
diff --git a/core/res/res/values-mcc262-cs/strings.xml b/core/res/res/values-mcc262-cs/strings.xml
new file mode 100644
index 0000000..9505cf4
--- /dev/null
+++ b/core/res/res/values-mcc262-cs/strings.xml
@@ -0,0 +1,19 @@
+<?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.
+-->
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="locale_replacement">"de_de"</string>
+</resources>
diff --git a/core/res/res/values-mcc262-de/strings.xml b/core/res/res/values-mcc262-de/strings.xml
new file mode 100644
index 0000000..9505cf4
--- /dev/null
+++ b/core/res/res/values-mcc262-de/strings.xml
@@ -0,0 +1,19 @@
+<?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.
+-->
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="locale_replacement">"de_de"</string>
+</resources>
diff --git a/core/res/res/values-mcc262-es/strings.xml b/core/res/res/values-mcc262-es/strings.xml
new file mode 100644
index 0000000..9505cf4
--- /dev/null
+++ b/core/res/res/values-mcc262-es/strings.xml
@@ -0,0 +1,19 @@
+<?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.
+-->
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="locale_replacement">"de_de"</string>
+</resources>
diff --git a/core/res/res/values-mcc262-fr/strings.xml b/core/res/res/values-mcc262-fr/strings.xml
new file mode 100644
index 0000000..9505cf4
--- /dev/null
+++ b/core/res/res/values-mcc262-fr/strings.xml
@@ -0,0 +1,19 @@
+<?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.
+-->
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="locale_replacement">"de_de"</string>
+</resources>
diff --git a/core/res/res/values-mcc262-it/strings.xml b/core/res/res/values-mcc262-it/strings.xml
new file mode 100644
index 0000000..9505cf4
--- /dev/null
+++ b/core/res/res/values-mcc262-it/strings.xml
@@ -0,0 +1,19 @@
+<?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.
+-->
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="locale_replacement">"de_de"</string>
+</resources>
diff --git a/core/res/res/values-mcc262-ja/strings.xml b/core/res/res/values-mcc262-ja/strings.xml
new file mode 100644
index 0000000..9505cf4
--- /dev/null
+++ b/core/res/res/values-mcc262-ja/strings.xml
@@ -0,0 +1,19 @@
+<?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.
+-->
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="locale_replacement">"de_de"</string>
+</resources>
diff --git a/core/res/res/values-mcc262-ko/strings.xml b/core/res/res/values-mcc262-ko/strings.xml
new file mode 100644
index 0000000..9505cf4
--- /dev/null
+++ b/core/res/res/values-mcc262-ko/strings.xml
@@ -0,0 +1,19 @@
+<?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.
+-->
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="locale_replacement">"de_de"</string>
+</resources>
diff --git a/core/res/res/values-mcc262-nl/strings.xml b/core/res/res/values-mcc262-nl/strings.xml
new file mode 100644
index 0000000..9505cf4
--- /dev/null
+++ b/core/res/res/values-mcc262-nl/strings.xml
@@ -0,0 +1,19 @@
+<?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.
+-->
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="locale_replacement">"de_de"</string>
+</resources>
diff --git a/core/res/res/values-mcc262-pl/strings.xml b/core/res/res/values-mcc262-pl/strings.xml
new file mode 100644
index 0000000..9505cf4
--- /dev/null
+++ b/core/res/res/values-mcc262-pl/strings.xml
@@ -0,0 +1,19 @@
+<?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.
+-->
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="locale_replacement">"de_de"</string>
+</resources>
diff --git a/core/res/res/values-mcc262-ru/strings.xml b/core/res/res/values-mcc262-ru/strings.xml
new file mode 100644
index 0000000..9505cf4
--- /dev/null
+++ b/core/res/res/values-mcc262-ru/strings.xml
@@ -0,0 +1,19 @@
+<?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.
+-->
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="locale_replacement">"de_de"</string>
+</resources>
diff --git a/core/res/res/values-mcc262-zh-rCN/strings.xml b/core/res/res/values-mcc262-zh-rCN/strings.xml
new file mode 100644
index 0000000..9505cf4
--- /dev/null
+++ b/core/res/res/values-mcc262-zh-rCN/strings.xml
@@ -0,0 +1,19 @@
+<?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.
+-->
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="locale_replacement">"de_de"</string>
+</resources>
diff --git a/core/res/res/values-mcc262-zh-rTW/strings.xml b/core/res/res/values-mcc262-zh-rTW/strings.xml
new file mode 100644
index 0000000..9505cf4
--- /dev/null
+++ b/core/res/res/values-mcc262-zh-rTW/strings.xml
@@ -0,0 +1,19 @@
+<?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.
+-->
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="locale_replacement">"de_de"</string>
+</resources>
diff --git a/core/res/res/values-mcc262/arrays.xml b/core/res/res/values-mcc262/arrays.xml
new file mode 100644
index 0000000..955df7d
--- /dev/null
+++ b/core/res/res/values-mcc262/arrays.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* 
+**
+** Copyright 2006, Google Inc.
+**
+** 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.
+*/
+-->
+<resources>
+
+    <!-- Do not translate. -->
+    <integer-array name="maps_starting_lat_lng">
+        <item>52372026</item>
+        <item>9735672</item>
+    </integer-array>
+    <!-- Do not translate. -->
+    <integer-array name="maps_starting_zoom">
+        <item>5</item>
+    </integer-array>
+
+</resources>
diff --git a/core/res/res/values-mcc262/strings.xml b/core/res/res/values-mcc262/strings.xml
new file mode 100644
index 0000000..8ca0e31
--- /dev/null
+++ b/core/res/res/values-mcc262/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/**
+ * Copyright (c) 2008, Google Inc.
+ *
+ * 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.
+ */
+-->
+
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+
+    <!-- A string used to replace %s in a URL to fill in the locale for countries  -->
+    <!-- whose locale we don't natively support.  A 0 length string triggers no replacement -->
+    <string name="locale_replacement">de_de</string>    
+</resources>
diff --git a/core/res/res/values-nb/strings.xml b/core/res/res/values-nb/strings.xml
new file mode 100644
index 0000000..c1944a4
--- /dev/null
+++ b/core/res/res/values-nb/strings.xml
@@ -0,0 +1,815 @@
+<?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.
+-->
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="byteShort">"B"</string>
+    <string name="kilobyteShort">"kB"</string>
+    <string name="megabyteShort">"MB"</string>
+    <string name="gigabyteShort">"GB"</string>
+    <string name="terabyteShort">"TB"</string>
+    <string name="petabyteShort">"PB"</string>
+    <string name="untitled">"&lt;uten navn&gt;"</string>
+    <string name="ellipsis">"…"</string>
+    <string name="emptyPhoneNumber">"(Mangler telefonnummer)"</string>
+    <string name="unknownName">"(Ukjent)"</string>
+    <string name="defaultVoiceMailAlphaTag">"Telefonsvarer"</string>
+    <string name="defaultMsisdnAlphaTag">"MSISDN1"</string>
+    <string name="mmiError">"Tilkoblingsproblem eller ugyldig MMI-kode."</string>
+    <string name="serviceEnabled">"Tjenesten ble aktivert."</string>
+    <string name="serviceEnabledFor">"Tjenesten ble aktivert for:"</string>
+    <string name="serviceDisabled">"Tjenesten ble deaktivert."</string>
+    <string name="serviceRegistered">"Registreringen er vellykket."</string>
+    <string name="serviceErased">"Registreringen ble fjernet."</string>
+    <string name="passwordIncorrect">"Ugyldig passord."</string>
+    <string name="mmiComplete">"MMI utført."</string>
+    <string name="badPin">"Den gamle PIN-koden du skrev inn er feil."</string>
+    <string name="badPuk">"PUK-koden du skrev inn er feil."</string>
+    <string name="mismatchPin">"PIN-kodene stemmer ikke overens."</string>
+    <string name="invalidPin">"PIN-koden må være mellom fire og åtte siffer."</string>
+    <string name="needPuk">"SIM-kortet ditt er PUK-låst. Skriv inn PUK-koden for å låse det opp."</string>
+    <string name="needPuk2">"Skriv inn PUK2 for å låse opp SIM-kortet."</string>
+    <string name="ClipMmi">"Inngående nummervisning"</string>
+    <string name="ClirMmi">"Utgående nummervisning"</string>
+    <string name="CfMmi">"Viderekobling"</string>
+    <string name="CwMmi">"Samtale venter"</string>
+    <string name="BaMmi">"Samtaleblokkering"</string>
+    <string name="PwdMmi">"Passordbytte"</string>
+    <string name="PinMmi">"PIN-kode-bytte"</string>
+    <string name="CLIRDefaultOnNextCallOn">"Nummervisning er begrenset som standard. Neste anrop: Begrenset"</string>
+    <string name="CLIRDefaultOnNextCallOff">"Nummervisning er begrenset som standard. Neste anrop: Ikke begrenset"</string>
+    <string name="CLIRDefaultOffNextCallOn">"Nummervisning er ikke begrenset som standard. Neste anrop: Begrenset"</string>
+    <string name="CLIRDefaultOffNextCallOff">"Nummervisning er ikke begrenset som standard. Neste anrop: Ikke begrenset"</string>
+    <string name="serviceNotProvisioned">"SIM-kortet er ikke tilrettelagt for tjenesten."</string>
+    <string name="CLIRPermanent">"Kunne ikke endre innstilling for nummervisning."</string>
+    <string name="serviceClassVoice">"Tale"</string>
+    <string name="serviceClassData">"Data"</string>
+    <string name="serviceClassFAX">"Fax"</string>
+    <string name="serviceClassSMS">"SMS"</string>
+    <string name="serviceClassDataAsync">"Asynkron"</string>
+    <string name="serviceClassDataSync">"Synkron"</string>
+    <string name="serviceClassPacket">"Pakkedata"</string>
+    <string name="serviceClassPAD">"PAD"</string>
+    <string name="cfTemplateNotForwarded">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: Ikke viderekoblet"</string>
+    <string name="cfTemplateForwarded">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
+    <string name="cfTemplateForwardedTime">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> etter <xliff:g id="TIME_DELAY">{2}</xliff:g> sekunder"</string>
+    <string name="cfTemplateRegistered">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: Ikke viderekoblet"</string>
+    <string name="cfTemplateRegisteredTime">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: Ikke viderekoblet"</string>
+    <string name="httpErrorOk">"OK"</string>
+    <string name="httpError">"Nettsiden inneholder en feil."</string>
+    <string name="httpErrorLookup">"Kunne ikke finne adressen."</string>
+    <string name="httpErrorUnsupportedAuthScheme">"Støtter ikke sidens autentiseringsmetode."</string>
+    <string name="httpErrorAuth">"Autentiseringen feilet."</string>
+    <string name="httpErrorProxyAuth">"Autentisering via mellomtjeneren feilet."</string>
+    <string name="httpErrorConnect">"Kunne ikke koble til tjeneren."</string>
+    <string name="httpErrorIO">"Klarte ikke å kommunisere med tjeneren. Prøv igjen senere."</string>
+    <string name="httpErrorTimeout">"Det oppsto et tidsavbrudd under tilkobling til tjeneren."</string>
+    <string name="httpErrorRedirectLoop">"Siden inneholder for mange videresendinger."</string>
+    <string name="httpErrorUnsupportedScheme">"Protokollen er ikke støttet."</string>
+    <string name="httpErrorFailedSslHandshake">"Kunne ikke opprette en sikker tilkobling."</string>
+    <string name="httpErrorBadUrl">"Kunne ikke åpne siden, siden adressen er ugyldig."</string>
+    <string name="httpErrorFile">"Kunne ikke åpne filen."</string>
+    <string name="httpErrorFileNotFound">"Fant ikke den forespurte filen."</string>
+    <string name="httpErrorTooManyRequests">"For mange forespørsler blir behandlet. Prøv igjen senere."</string>
+    <string name="contentServiceSync">"Synkronisering"</string>
+    <string name="contentServiceSyncNotificationTitle">"Synkronisering"</string>
+    <string name="contentServiceTooManyDeletesNotificationDesc">"For mange slettinger av <xliff:g id="CONTENT_TYPE">%s</xliff:g>."</string>
+    <string name="low_memory">"Telefonens lagringsminne er fullt! Slett noen filer for å frigjøre plass."</string>
+    <string name="me">"Meg"</string>
+    <string name="power_dialog">"Telefoninnstillinger"</string>
+    <string name="silent_mode">"Stillemodus"</string>
+    <string name="turn_on_radio">"Slå på trådløst nett"</string>
+    <string name="turn_off_radio">"Slå av trådløst nett"</string>
+    <string name="screen_lock">"Lås skjermen"</string>
+    <string name="power_off">"Slå av"</string>
+    <string name="shutdown_progress">"Avslutter…"</string>
+    <string name="shutdown_confirm">"Telefonen vil bli slått av."</string>
+    <string name="no_recent_tasks">"Ingen nylig brukte applikasjoner."</string>
+    <string name="global_actions">"Telefoninnstillinger"</string>
+    <string name="global_action_lock">"Lås skjermen"</string>
+    <string name="global_action_power_off">"Slå av"</string>
+    <string name="global_action_toggle_silent_mode">"Stillemodus"</string>
+    <string name="global_action_silent_mode_on_status">"Lyden er av"</string>
+    <string name="global_action_silent_mode_off_status">"Lyden er på"</string>
+    <!-- no translation found for global_actions_toggle_airplane_mode (5884330306926307456) -->
+    <skip />
+    <!-- no translation found for global_actions_airplane_mode_on_status (2719557982608919750) -->
+    <skip />
+    <!-- no translation found for global_actions_airplane_mode_off_status (5075070442854490296) -->
+    <skip />
+    <string name="safeMode">"Sikkermodus"</string>
+    <!-- no translation found for android_system_label (6577375335728551336) -->
+    <skip />
+    <string name="permgrouplab_costMoney">"Betaltjenester"</string>
+    <string name="permgroupdesc_costMoney">"Lar applikasjoner utføre operasjoner som kan koste deg penger."</string>
+    <string name="permgrouplab_messages">"Meldinger"</string>
+    <string name="permgroupdesc_messages">"Lese og skrive SMS, e-post og andre meldinger på telefonen."</string>
+    <string name="permgrouplab_personalInfo">"Personlig informasjon"</string>
+    <string name="permgroupdesc_personalInfo">"Direkte tilgang til kontakter og kalendre lagret på telefonen."</string>
+    <string name="permgrouplab_location">"Plassering"</string>
+    <string name="permgroupdesc_location">"Overvåking av telefonens fysiske plassering"</string>
+    <string name="permgrouplab_network">"Nettverkstilgang"</string>
+    <string name="permgroupdesc_network">"Gir applikasjoner tilgang til diverse nettverksfunksjoner."</string>
+    <string name="permgrouplab_accounts">"Google-kontoer"</string>
+    <string name="permgroupdesc_accounts">"Tilgang til tilgjengelige Google-kontoer."</string>
+    <string name="permgrouplab_hardwareControls">"Maskinvarekontroll"</string>
+    <string name="permgroupdesc_hardwareControls">"Direkte tilgang til maskinvaren på telefonen."</string>
+    <string name="permgrouplab_phoneCalls">"Telefonsamtaler"</string>
+    <string name="permgroupdesc_phoneCalls">"Overvåk, ta opp, og behandle telefonsamtaler."</string>
+    <string name="permgrouplab_systemTools">"Systemverktøy"</string>
+    <string name="permgroupdesc_systemTools">"Lavnivå tilgang og kontroll over systemet."</string>
+    <string name="permgrouplab_developmentTools">"Utviklingsverktøy"</string>
+    <string name="permgroupdesc_developmentTools">"Funksjonalitet kun utviklere trenger."</string>
+    <string name="permlab_statusBar">"deaktivere eller endre statusfeltet"</string>
+    <string name="permdesc_statusBar">"Lar applikasjonen deaktivere statusfeltet, samt legge til og fjerne systemikoner."</string>
+    <string name="permlab_expandStatusBar">"utvide/slå sammen statusfeltet"</string>
+    <string name="permdesc_expandStatusBar">"Lar applikasjonen utvide eller slå sammen statusfeltet."</string>
+    <string name="permlab_processOutgoingCalls">"avskjære utgående anrop"</string>
+    <string name="permdesc_processOutgoingCalls">"Lar applikasjonen behandle utgående anrop og endre nummeret som ringes. Ondsinnede applikasjoner kan overvåke, videresende, eller hindre utgående anrop."</string>
+    <string name="permlab_receiveSms">"motta SMS"</string>
+    <string name="permdesc_receiveSms">"Lar applikasjonen motta og behandle SMS-meldinger. Ondsinnede applikasjoner kan overvåke meldinger eller slette dem uten at de vises."</string>
+    <string name="permlab_receiveMms">"motta MMS"</string>
+    <string name="permdesc_receiveMms">"Lar applikasjonen motta og behandle MMS-meldinger. Ondsinnede applikasjoner kan overvåke meldinger eller slette dem uten at de vises."</string>
+    <string name="permlab_sendSms">"sende SMS-meldinger"</string>
+    <string name="permdesc_sendSms">"Lar applikasjonen sende SMS-meldinger. Ondsinnede applikasjoner kan koste deg penger ved å sende meldinger uten bekreftelse."</string>
+    <string name="permlab_readSms">"lese SMS- og MMS-meldinger"</string>
+    <string name="permdesc_readSms">"Lar applikasjonen lese SMS-meldinger lagret i telefonen eller på SIM-kortet. Ondsinnede applikasjoner kan lese private meldinger."</string>
+    <string name="permlab_writeSms">"redigere SMS- og MMS-meldinger"</string>
+    <string name="permdesc_writeSms">"Lar applikasjonen skrive til SMS-meldinger lagret i telefonen eller på SIM-kortet. Ondsinnede applikasjoner kan slette meldinger."</string>
+    <string name="permlab_receiveWapPush">"motta WAP"</string>
+    <string name="permdesc_receiveWapPush">"Lar applikasjonen motta og behandle WAP-meldinger. Ondsinnede applikasjoner kan overvåke meldinger eller slette dem uten at de vises."</string>
+    <string name="permlab_getTasks">"se kjørende applikasjoner"</string>
+    <string name="permdesc_getTasks">"Tillater applikasjonen å hente informasjon om aktive og nylig kjørte programmer. Kan tillate ondsinnede applikasjoner å oppdage privat informasjon om andre applikasjoner."</string>
+    <string name="permlab_reorderTasks">"omordne kjørende applikasjoner"</string>
+    <string name="permdesc_reorderTasks">"Tillater applikasjonen å flytte programmer til forgrunnen eller bakgrunnen. Ondsinnede applikasjoner kan tvinge seg selv til fronten."</string>
+    <string name="permlab_setDebugApp">"aktiver applikasjonsdebugging"</string>
+    <string name="permdesc_setDebugApp">"Lar applikasjonen skru på debugging for en annen applikasjon. Ondsinnede applikasjoner kan bruke dette til å drepe andre applikasjoner."</string>
+    <string name="permlab_changeConfiguration">"endre innstillingene for brukergrensesnitt"</string>
+    <string name="permdesc_changeConfiguration">"Tillater applikasjonen å endre gjeldende innstillinger, slik som språk eller skriftstørrelse."</string>
+    <string name="permlab_restartPackages">"omstarte andre applikasjoner"</string>
+    <string name="permdesc_restartPackages">"Lar applikasjonen tvinge andre applikasjoner til å starte på nytt."</string>
+    <string name="permlab_setProcessForeground">"unngå å bli stoppet"</string>
+    <string name="permdesc_setProcessForeground">"Lar applikasjonen sette en vilkårlig prosess i forgrunnen, så den ikke kan bli drept. Vanlige applikasjoner bør aldri trenge dette."</string>
+    <string name="permlab_forceBack">"tvinge applikasjoner til å lukkes"</string>
+    <string name="permdesc_forceBack">"Lar applikasjonen tvinge enhver aktivitet som er i forgrunnen til å lukkes og gå tilbake. Vanlige applikasjoner bør aldri trenge dette."</string>
+    <string name="permlab_dump">"hente intern systemtilstand"</string>
+    <string name="permdesc_dump">"Lar applikasjonen hente intern tilstand fra systemet. Onsdinnede applikasjoner kan hente et bredt spekter av privat og sikker informasjon som de vanligvis aldri burde ha behov for."</string>
+    <string name="permlab_addSystemService">"publisere lavnivåtjenester"</string>
+    <string name="permdesc_addSystemService">"Lar applikasjonen publisere sine egne lavnivås systemtjenester. Ondsinnede applikasjoner kan kapre systemet, og stjele eller ødelegge alle data på det."</string>
+    <string name="permlab_runSetActivityWatcher">"overvåke og kontrollere all applikasjonsoppstart"</string>
+    <string name="permdesc_runSetActivityWatcher">"Lar applikasjonen overvåke og kontrollere hvordan systemet starter applikasjoner. Ondsinnede applikasjoner kan ta over systemet helt. Denne rettigheten behøves bare for utvikling, aldri for vanlig bruk av telefonen."</string>
+    <string name="permlab_broadcastPackageRemoved">"kringkaste melding om fjernet pakke"</string>
+    <string name="permdesc_broadcastPackageRemoved">"Lar applikasjonen kringkaste en melding om at en applikasjonspakke er blitt fjernet. Ondsinnede applikasjoner kan bruke dette til å drepe vilkårlige andre kjørende applikasjoner."</string>
+    <string name="permlab_broadcastSmsReceived">"kringkaste melding om mottatt SMS"</string>
+    <string name="permdesc_broadcastSmsReceived">"Lar applikasjonen kringkaste en melding om at en SMS-melding er mottatt. Ondsinnede applikasjoner kan bruke dette til å forfalske innkommende SMS-meldinger."</string>
+    <string name="permlab_broadcastWapPush">"kringkaste melding om mottatt WAP-PUSH"</string>
+    <string name="permdesc_broadcastWapPush">"Lar applikasjonen kringkaste en melding om at en WAP-PUSH-melding er blitt mottatt. Ondsinnede applikasjoner kan bruke dette for å forfalske MMS-kvitteringer eller i det stille erstatte innholdet av vilkårlige nettsider med ondsinnede varianter."</string>
+    <string name="permlab_setProcessLimit">"begrense antallet kjørende prosesser"</string>
+    <string name="permdesc_setProcessLimit">"Lar applikasjonen kontrollere maksimalt antall kjørende prosesser. Behøves aldri for vanlige applikasjoner."</string>
+    <string name="permlab_setAlwaysFinish">"få alle bakgrunnsapplikasjoner til å lukkes"</string>
+    <string name="permdesc_setAlwaysFinish">"Lar applikasjonen kontrollere om aktiviteter alltid avsluttes når de sendes til bakgrunnen. Behøves aldri for vanlige applikasjoner."</string>
+    <string name="permlab_fotaUpdate">"installere systemoppdateringer automatisk"</string>
+    <string name="permdesc_fotaUpdate">"Lar applikasjonen motta meldinger om pågående systemoppdateringer, og starte installeringen av dem. Ondsinnede applikasjoner kan bruke dette for å skade systemet med uautoriserte oppdateringer, eller generelt forstyrre oppdateringsprosessen."</string>
+    <string name="permlab_batteryStats">"endre batteristatistikk"</string>
+    <string name="permdesc_batteryStats">"Lar applikasjonen endre på innsamlet batteristatistikk. Ikke ment for vanlige applikasjoner."</string>
+    <string name="permlab_internalSystemWindow">"vis uautoriserte vinduer"</string>
+    <string name="permdesc_internalSystemWindow">"Tillater at det opprettes vinduer ment for bruk av systemets interne brukergrensesnitt. Ikke ment for vanlige applikasjoner."</string>
+    <string name="permlab_systemAlertWindow">"vise advarsler på systemnivå"</string>
+    <string name="permdesc_systemAlertWindow">"Lar applikasjonen vise systemadvarselvinduer. Ondsinnede applikasjoner kan ta over hele skjermen."</string>
+    <string name="permlab_setAnimationScale">"endre global animasjonshastighet"</string>
+    <string name="permdesc_setAnimationScale">"Lar applikasjonen endre den globale animasjonshastigheten (raskere eller tregere animasjoner) når som helst."</string>
+    <string name="permlab_manageAppTokens">"styre applikasjonssymboler"</string>
+    <string name="permdesc_manageAppTokens">"Lar applikasjoner lage og vedlikeholde sine egne symboler, noe som lar dem overstyre den vanlige Z-ordningen. Vanlige applikasjoner bør aldri trenge dette."</string>
+    <string name="permlab_injectEvents">"trykke taster og kontrolknapper"</string>
+    <string name="permdesc_injectEvents">"Lar applikasjonen levere sine egne inndatahendelser (tastetrykk osv.) til andre applikasjoner. Ondsinnede applikasjoner kan bruke dette for å ta over telefonen."</string>
+    <string name="permlab_readInputState">"ta opp hva som skrives og gjøres"</string>
+    <string name="permdesc_readInputState">"Lar applikasjonen overvåke tastetrykk selv når interaksjonen er med et annet program (som å skrive inn et passord). Vanlige applikasjoner bør aldri trenge dette."</string>
+    <string name="permlab_bindInputMethod">"binde til en inndatametode"</string>
+    <string name="permdesc_bindInputMethod">"Lar applikasjonen binde til toppnivågrensesnittet for en inndatametode. Vanlige applikasjoner bør aldri trenge dette."</string>
+    <string name="permlab_setOrientation">"snu skjermen"</string>
+    <string name="permdesc_setOrientation">"Lar applikasjonen rotere skjermen når som helst. Vanlige applikasjoner bør aldri trenge dette."</string>
+    <string name="permlab_signalPersistentProcesses">"sende Linux-signaler til applikasjoner"</string>
+    <string name="permdesc_signalPersistentProcesses">"Lar applikasjonen spørre om at et gitt signal blir sendt til alle varige prosesser."</string>
+    <string name="permlab_persistentActivity">"forbli kjørende"</string>
+    <string name="permdesc_persistentActivity">"Lar applikasjonen gjøre deler av seg selv varig, så systemet ikke kan bruke det til andre applikasjoner."</string>
+    <string name="permlab_deletePackages">"slette applikasjoner"</string>
+    <string name="permdesc_deletePackages">"Lar applikasjonen slette Android-pakker. Ondsinnede applikasjoner kan bruke dette for å slette viktige applikasjoner."</string>
+    <string name="permlab_clearAppUserData">"slette andre applikasjoners data"</string>
+    <string name="permdesc_clearAppUserData">"Lar applikasjonen fjerne brukerdata."</string>
+    <string name="permlab_deleteCacheFiles">"slette andre applikasjoners hurtigbuffer"</string>
+    <string name="permdesc_deleteCacheFiles">"Lar applikasjonen to slette hurtigbufferfiler."</string>
+    <string name="permlab_getPackageSize">"måle lagringsplass for applikasjon"</string>
+    <string name="permdesc_getPackageSize">"Lar applikasjonen hente kode-, data- og hurtigbufferstørrelser"</string>
+    <string name="permlab_installPackages">"installere applikasjoner direkte"</string>
+    <string name="permdesc_installPackages">"Lar applikasjonen installere nye eller oppdaterte Android-pakker. Ondsinnede applikasjoner kan bruke dette for å legge til nye applikasjoner med vilkårlig kraftige rettigheter."</string>
+    <string name="permlab_clearAppCache">"slette hurtigbufferdata for alle applikasjoner"</string>
+    <string name="permdesc_clearAppCache">"Lar applikasjonen frigjøre lagringsplass ved å slette filer i applikasjoners hurtigbufferkatalog. Tilgangen er vanligvis sterkt begrenset, til systemprosesser."</string>
+    <string name="permlab_readLogs">"lese systemets loggfiler"</string>
+    <string name="permdesc_readLogs">"Lar applikasjonen to lese fra diverse loggfiler på systemet. Disse inneholder generell informasjon om hva som gjøres med telefonen, men skal ikke inneholde personlig eller privat informasjon."</string>
+    <string name="permlab_diagnostic">"lese/skrive ressurser eid av diag"</string>
+    <string name="permdesc_diagnostic">"Lar applikasjonen to lese og skrive enhver ressurs eid av gruppen diag; for eksempel, filer i /dev. Dette kan potensielt påvirke systemets sikkerhet og stabilitet. Dette bør KUN brukes for maskinvarespesifikke diagnoseverktøy laget av operatøren eller produsenten."</string>
+    <string name="permlab_changeComponentState">"aktivere eller deaktigere applikasjonskomponenter"</string>
+    <string name="permdesc_changeComponentState">"Lar applikasjonen endre om en komponent i en annen applikasjon er aktivert eller ikke. Ondsinnede applikasjoner kan bruke dette for å deaktivere viktige telefonfunksjoner. Denne rettigheten må brukes med forsiktighet, ettersom det er mulig å få applikasjonskomponenter inn i en ubrukelig, inkonsistent eller ustabil tilstand."</string>
+    <string name="permlab_setPreferredApplications">"velge foretrukne applikasjoner"</string>
+    <string name="permdesc_setPreferredApplications">"Lar applikasjonen endre valgene for foretrukne applikasjoner. Dette kan gi ondsinnede applikasjoner tilgang til i det stille å endre hvilke applikasjoner som kjøres, og slik gi seg ut for å være en eksisterende applikasjon og samle private data."</string>
+    <string name="permlab_writeSettings">"endre globale systeminnstillinger"</string>
+    <string name="permdesc_writeSettings">"Lar applikasjonen endre systemets innstillingsdata. Ondsinnede applikasjoner kan skade systemets innstillinger."</string>
+    <string name="permlab_writeSecureSettings">"endre sikre systeminnstillinger"</string>
+    <string name="permdesc_writeSecureSettings">"Lar applikasjonen endre systemets sikre innstillingsdata. Ikke ment for bruk av vanlige applikasjoner."</string>
+    <string name="permlab_writeGservices">"redigere Google-tjenestekartet"</string>
+    <string name="permdesc_writeGservices">"Lar applikasjonen redigere Google-tjenestekartet. Ikke ment for bruk av vanlige applikasjoner."</string>
+    <string name="permlab_receiveBootCompleted">"starte automatisk sammen med systemet"</string>
+    <string name="permdesc_receiveBootCompleted">"Lar applikasjonen sette opp at den selv skal starte så fort systemet er ferdig med å slå seg på. Dette kan gjøre at det tar lengre tid å starte telefonen, og at den kan bli tregere fordi applikasjonen alltid kjører."</string>
+    <string name="permlab_broadcastSticky">"sende varige kringkastinger"</string>
+    <string name="permdesc_broadcastSticky">"Lar applikasjonen sende varige kringkastinger, som forblir på systemet etter at kringkastingen er avsluttet. Ondsinnede applikasjoner kan gjøre telefonen treg eller ustabil ved å få den til å bruke for mye minne."</string>
+    <string name="permlab_readContacts">"lese kontaktinformasjon"</string>
+    <string name="permdesc_readContacts">"Lar applikasjonen lese all kontakt- og adresseinformasjon lagret på telefonen. Ondsinnede applikasjoner kan bruke dette for å sende personlige data til andre."</string>
+    <string name="permlab_writeContacts">"skrive kontaktinformasjon"</string>
+    <string name="permdesc_writeContacts">"Lar applikasjonen endre kontakt- og adresseinformasjon lagret på telefonen. Ondsinnede applikasjoner kan bruke dette for å redigere eller endre kontaktinformasjonen."</string>
+    <string name="permlab_writeOwnerData">"skrive eierinformasjon"</string>
+    <string name="permdesc_writeOwnerData">"Lar applikasjonen endre dataene om telefoneieren. Ondsinnede applikasjoner kan bruke dette til å slette eller redigere telefonens eierdata."</string>
+    <string name="permlab_readOwnerData">"lese eierinformasjon"</string>
+    <string name="permdesc_readOwnerData">"Lar applikasjonen lese dataene om telefoneieren. Ondsinnede applikasjoner kan bruke dette til å lese telefonens eierdata."</string>
+    <string name="permlab_readCalendar">"lese kalenderinformasjon"</string>
+    <string name="permdesc_readCalendar">"Lar applikasjonen lese alle kalenderhendelser lagret på telefonen. Ondsinnede applikasjoner kan bruke dette til å sende kalenderhendelser til andre."</string>
+    <string name="permlab_writeCalendar">"skrive kalenderinformasjon"</string>
+    <string name="permdesc_writeCalendar">"Lar applikasjonen endre kalenderhendelser lagret på telefonen. Ondsinnede applikasjoner kan bruke dette til å slette eller endre kalenderinformasjon."</string>
+    <string name="permlab_accessMockLocation">"lage falske plasseringskilder for testing"</string>
+    <string name="permdesc_accessMockLocation">"Lage falske plassingskilder for testing. Ondsinnede applikasjoner kan bruke dette for å overstyre plasseringen og/eller statusen rapportert av ekte plasseringskilder slik som GPS eller nettverksoperatører."</string>
+    <string name="permlab_accessLocationExtraCommands">"få tilgang til ekstra plasseringskommandoer"</string>
+    <string name="permdesc_accessLocationExtraCommands">"Få tilgang til ekstra kommandoer for plasseringskilder. Ondsinnede applikasjoner kan bruke dette til å forstyrre GPS eller andre plasseringskilder."</string>
+    <string name="permlab_accessFineLocation">"nøyaktig (GPS-) plassering"</string>
+    <string name="permdesc_accessFineLocation">"Få tilgang til nøyaktige plasseringskilder som Global Positioning System (GPS) på telefonen, når det er tilgjengelig. Ondsinnede applikasjoner kan bruke dette for å finne ut hvor du er, og kan bruke mer batteri."</string>
+    <string name="permlab_accessCoarseLocation">"grov (nettverksbasert) plassering"</string>
+    <string name="permdesc_accessCoarseLocation">"Få tilgang til grove plasseringskilder som databasen over basestasjoner for å finne ut omtrent hvor telefonen er, når det er tilgjengelig. Ondsinnede applikasjoner kan bruke dette for å finne ut omtrent hvor du er."</string>
+    <string name="permlab_accessSurfaceFlinger">"få tilgang til SurfaceFlinger"</string>
+    <string name="permdesc_accessSurfaceFlinger">"Lar applikasjonen bruke lavnivåfunksjonalitet i SurfaceFlinger."</string>
+    <string name="permlab_readFrameBuffer">"lese skjermbufferet"</string>
+    <string name="permdesc_readFrameBuffer">"Lar applikasjonen lese innholdet i skjermbufferet."</string>
+    <string name="permlab_modifyAudioSettings">"endre lydinnstillinger"</string>
+    <string name="permdesc_modifyAudioSettings">"Lar applikasjonen endre globale lydinnstillinger som volum og ruting."</string>
+    <string name="permlab_recordAudio">"ta opp lyd"</string>
+    <string name="permdesc_recordAudio">"Gir applikasjonen tilgang til opptaksstien for lyd."</string>
+    <string name="permlab_camera">"ta bilder"</string>
+    <string name="permdesc_camera">"Lar applikasjonen ta bilder med kameraet. Dette gir applikasjonen til når som helst å se og lagre det kameraet ser."</string>
+    <string name="permlab_brick">"deaktivere telefonen permanent"</string>
+    <string name="permdesc_brick">"Lar applikasjonen deaktivere hele telefonen permanent. Dette er svært farlig."</string>
+    <string name="permlab_reboot">"tvinge omstart av telefon"</string>
+    <string name="permdesc_reboot">"Lar applikasjonen tvinge telefonen til å starte på nytt."</string>
+    <string name="permlab_mount_unmount_filesystems">"montere og avmontere filsystemer"</string>
+    <string name="permdesc_mount_unmount_filesystems">"Lar applikasjonen montere og avmontere filsystemer for uttagbar lagring."</string>
+    <string name="permlab_mount_format_filesystems">"formatere ekstern lagringsplass"</string>
+    <string name="permdesc_mount_format_filesystems">"Lar applikasjonen formatere ekstern lagringsplass."</string>
+    <string name="permlab_vibrate">"kontrollere vibratoren"</string>
+    <string name="permdesc_vibrate">"Lar applikasjonen kontrollere vibratoren."</string>
+    <string name="permlab_flashlight">"kontrollere lommelykten"</string>
+    <string name="permdesc_flashlight">"Lar applikasjonen kontrollere lommelykten."</string>
+    <string name="permlab_hardware_test">"teste maskinvare"</string>
+    <string name="permdesc_hardware_test">"Lar applikasjonen styre diverse enheter med det formål å teste maskinvaren."</string>
+    <string name="permlab_callPhone">"ringe telefonnummer direkte"</string>
+    <string name="permdesc_callPhone">"Lar applikasjonen ringe telefonnummer uten inngripen fra brukeren. Ondsinnede applikasjoner kan forårsake uventede oppringinger på telefonregningen. Merk at dette ikke gir applikasjonen lov til å ringe nødnummer."</string>
+    <string name="permlab_callPrivileged">"ringe vilkårlige telefonnummer direkte"</string>
+    <string name="permdesc_callPrivileged">"Lar applikasjonen ringe hvilket som helst telefonnummer, inkludert nødnummer, uten inngripen fra brukeren. Ondsinnede applikasjoner kan forårsake unødvendige og ulovlige samtaler til nødtjenester."</string>
+    <string name="permlab_locationUpdates">"kontrollere varsling for plasseringsendring"</string>
+    <string name="permdesc_locationUpdates">"Lar applikasjonen slå av/på varsling om plasseringsendringer fra radioen. Ikke ment for vanlige applikasjoner."</string>
+    <string name="permlab_checkinProperties">"få tilgang til egenskaper for innsjekking"</string>
+    <string name="permdesc_checkinProperties">"Gir lese- og skrivetilgang til egenskaper lastet opp av innsjekkingstjenesten. Ikke ment for vanlige applikasjoner."</string>
+    <string name="permlab_bindGadget">"velg gadgeter"</string>
+    <string name="permdesc_bindGadget">"Lar applikasjonen fortelle systemet hvilke gadgeter som kan brukes av hvilke applikasjoner. Med denne rettigheten kan applikasjoner andre applikasjoner tilgang til personlig data. Ikke ment for vanlige applikasjoner."</string>
+    <string name="permlab_modifyPhoneState">"endre telefontilstand"</string>
+    <string name="permdesc_modifyPhoneState">"Lar applikasjonen kontrollere telefonfunksjonaliteten i enheten. En applikasjon med denne rettigheten kan endre nettverk, slå telefonens radio av eller på og lignende uten noensinne å varsle brukeren."</string>
+    <string name="permlab_readPhoneState">"lese telefontilstand"</string>
+    <string name="permdesc_readPhoneState">"Lar applikasjonen få tilgang til telefonfunksjonaliteten i enheten. En applikasjon med denne rettigheten kan få vite enhetens telefonnummer, om en samtale pågår, nummeret samtalen er koblet til og lignende."</string>
+    <string name="permlab_wakeLock">"forhindre telefonen fra å sove"</string>
+    <string name="permdesc_wakeLock">"Lar applikasjonen forhindre telefonen fra å gå i hvilemodus."</string>
+    <string name="permlab_devicePower">"slå telefonen av eller på"</string>
+    <string name="permdesc_devicePower">"Lar applikasjonen skru telefonen av eller på."</string>
+    <string name="permlab_factoryTest">"kjøre i fabrikktestmodus"</string>
+    <string name="permdesc_factoryTest">"Kjøre som en lavnivås produsenttest, med full tilgang til telefonens maskinvare. Kun tilgjengelig når telefonen kjører i produsenttestmodus."</string>
+    <string name="permlab_setWallpaper">"endre bakgrunnsbilde"</string>
+    <string name="permdesc_setWallpaper">"Lar applikasjonen sette systemets bakgrunnsbilde."</string>
+    <string name="permlab_setWallpaperHints">"sette størrelseshint for bakgrunn"</string>
+    <string name="permdesc_setWallpaperHints">"Lar applikasjonen sette størrelseshint for systemets bakgrunnsbilde."</string>
+    <string name="permlab_masterClear">"nullstille systemet til fabrikkinnstillinger"</string>
+    <string name="permdesc_masterClear">"Lar applikasjonen nullstille systemet til fabrikkinnstillinger, noe som vil fjerne alle data, alt oppsett, og alle installerte applikasjoner."</string>
+    <string name="permlab_setTimeZone">"endre tidssone"</string>
+    <string name="permdesc_setTimeZone">"Lar applikasjonen endre telefonens tidssone."</string>
+    <string name="permlab_getAccounts">"oppdage kjente kontoer"</string>
+    <string name="permdesc_getAccounts">"Lar applikasjonen hente listen over kontoer telefonen kjenner til."</string>
+    <string name="permlab_accessNetworkState">"se nettverkstilstand"</string>
+    <string name="permdesc_accessNetworkState">"Lar applikasjonen se tilstanden til alle nettverk."</string>
+    <string name="permlab_createNetworkSockets">"full internett-tilgang"</string>
+    <string name="permdesc_createNetworkSockets">"Lar applikasjonen opprette vilkårlige nettverkstilkoblinger."</string>
+    <string name="permlab_writeApnSettings">"skrive APN-innstillinger"</string>
+    <string name="permdesc_writeApnSettings">"Lar applikasjonen to endre APN-innstillinger slik som mellomtjener eller port for hvilket som helst aksesspunkt."</string>
+    <string name="permlab_changeNetworkState">"endre nettverkskonnektivitet"</string>
+    <string name="permdesc_changeNetworkState">"Lar applikasjonen endre tilstanden til nettverkskonnektivitet."</string>
+    <string name="permlab_changeBackgroundDataSetting">"endre innstilling for bakgrunnsdata"</string>
+    <string name="permdesc_changeBackgroundDataSetting">"Lar applikasjonen endre innstillingen for bakgrunnsdata."</string>
+    <string name="permlab_accessWifiState">"se tilstand for trådløse nettverk"</string>
+    <string name="permdesc_accessWifiState">"Lar applikasjonen få se informasjon om tilstanden til de trådløse nettene."</string>
+    <string name="permlab_changeWifiState">"endre tilstand for trådløse nettverk"</string>
+    <string name="permdesc_changeWifiState">"Lar applikasjonen koble til og fra trådløse aksesspunkt, og å gjøre endringer i konfigurerte trådløse nettverk."</string>
+    <string name="permlab_bluetoothAdmin">"Bluetooth-administrasjon"</string>
+    <string name="permdesc_bluetoothAdmin">"Lar applikasjonen konfigurere den lokale Bluetooth-telefonen, og å oppdage og pare med andre enheter."</string>
+    <string name="permlab_bluetooth">"opprette Bluetooth-tilkoblinger"</string>
+    <string name="permdesc_bluetooth">"Lar applikasjonen se konfigurasjonen til den lokale Bluetooth-telefonen, og å opprette og godta tilkoblinger med parede enheter."</string>
+    <string name="permlab_disableKeyguard">"slå av tastaturlås"</string>
+    <string name="permdesc_disableKeyguard">"Lar applikasjonen slå av tastaturlåsen og enhver tilknyttet passordsikkerhet. Et legitimt eksempel på dette er at telefonen slår av tastaturlåsen når den mottar et innkommende anrop, og så slår den på igjen når samtalen er over."</string>
+    <string name="permlab_readSyncSettings">"lese synkroniseringsinnstillinger"</string>
+    <string name="permdesc_readSyncSettings">"Lar applikasjonen lese synkroniseringsinnstillingene, som for eksempel om kontakter blir synkronisert."</string>
+    <string name="permlab_writeSyncSettings">"skrive synkroniseringsinnstillinger"</string>
+    <string name="permdesc_writeSyncSettings">"Lar applikasjonen to endre på synkroniseringsinnstillingene, som for eksempel om kontakter blir synkronisert."</string>
+    <string name="permlab_readSyncStats">"lese synkroniseringsstatistikk"</string>
+    <string name="permdesc_readSyncStats">"Lar applikasjonen lese synkroniseringsstatistikk, som for eksempel historien over alle synkroniseringer utført."</string>
+    <string name="permlab_subscribedFeedsRead">"lese abonnement på nyhetskilder"</string>
+    <string name="permdesc_subscribedFeedsRead">"Lar applikasjonen hente detaljer om hvilke nyhetskilder som synkroniseres."</string>
+    <string name="permlab_subscribedFeedsWrite">"endre abonnement på nyhetskilder"</string>
+    <string name="permdesc_subscribedFeedsWrite">"Lar applikasjonen redigere hvilke nyhetskilder som synkroniseres. Dette kan gi en ondsinnet applikasjon tilgang til å endre hvilke nyhetskilder som synkroniseres."</string>
+    <string name="permlab_readDictionary">"lese brukerdefinert ordliste"</string>
+    <string name="permdesc_readDictionary">"Lar applikasjonen lese private ord, navn og uttrykk som brukeren har lagret i den brukerdefinerte ordlisten."</string>
+    <string name="permlab_writeDictionary">"skrive til brukerdefinert ordliste"</string>
+    <string name="permdesc_writeDictionary">"Lar applikasjonen skrive nye ord til den brukerdefinerte ordlisten."</string>
+  <string-array name="phoneTypes">
+    <item>"Hjemme"</item>
+    <item>"Mobil"</item>
+    <item>"Arbeid"</item>
+    <item>"Faks arbeid"</item>
+    <item>"Faks hjemme"</item>
+    <item>"Personsøker"</item>
+    <item>"Annen"</item>
+    <item>"Egendefinert…"</item>
+  </string-array>
+  <string-array name="emailAddressTypes">
+    <item>"Hjemme"</item>
+    <item>"Arbeid"</item>
+    <item>"Annen"</item>
+    <item>"Egendefinert…"</item>
+  </string-array>
+  <string-array name="postalAddressTypes">
+    <item>"Hjemme"</item>
+    <item>"Arbeid"</item>
+    <item>"Annen"</item>
+    <item>"Egendefinert…"</item>
+  </string-array>
+  <string-array name="imAddressTypes">
+    <item>"Hjemme"</item>
+    <item>"Arbeid"</item>
+    <item>"Annen"</item>
+    <item>"Egendefinert…"</item>
+  </string-array>
+  <string-array name="organizationTypes">
+    <item>"Arbeid"</item>
+    <item>"Annen"</item>
+    <item>"Egendefinert…"</item>
+  </string-array>
+  <string-array name="imProtocols">
+    <item>"AIM"</item>
+    <item>"Windows Live"</item>
+    <item>"Yahoo"</item>
+    <item>"Skype"</item>
+    <item>"QQ"</item>
+    <item>"Google Talk"</item>
+    <item>"ICQ"</item>
+    <item>"Jabber"</item>
+  </string-array>
+    <string name="keyguard_password_enter_pin_code">"Skriv inn PIN-kode:"</string>
+    <string name="keyguard_password_wrong_pin_code">"Gal PIN-kode!"</string>
+    <string name="keyguard_label_text">"For å låse opp, trykk menuknappen fulgt av 0."</string>
+    <string name="emergency_call_dialog_number_for_display">"Nødnummer"</string>
+    <string name="lockscreen_carrier_default">"(Ingen operatør)"</string>
+    <string name="lockscreen_screen_locked">"Skjermen er låst"</string>
+    <string name="lockscreen_instructions_when_pattern_enabled">"Trykk på menyknappen for å låse opp eller ringe et nødnummer."</string>
+    <string name="lockscreen_instructions_when_pattern_disabled">"Trykk på menyknappen for å låse opp."</string>
+    <string name="lockscreen_pattern_instructions">"Tegn mønster for å låse opp"</string>
+    <string name="lockscreen_emergency_call">"Nødanrop"</string>
+    <string name="lockscreen_pattern_correct">"Riktig!"</string>
+    <string name="lockscreen_pattern_wrong">"Beklager, prøv igjen:"</string>
+    <!-- no translation found for lockscreen_plugged_in (613343852842944435) -->
+    <skip />
+    <string name="lockscreen_low_battery">"Koble til en batterilader."</string>
+    <string name="lockscreen_missing_sim_message_short">"Mangler SIM-kort."</string>
+    <string name="lockscreen_missing_sim_message">"Ikke noe SIM-kort i telefonen."</string>
+    <string name="lockscreen_missing_sim_instructions">"Sett inn et SIM-kort."</string>
+    <string name="lockscreen_network_locked_message">"Nettverk ikke tillatt"</string>
+    <string name="lockscreen_sim_puk_locked_message">"SIM-kortet er PUK-låst."</string>
+    <string name="lockscreen_sim_puk_locked_instructions">"Vennligst ring kundeservice."</string>
+    <string name="lockscreen_sim_locked_message">"SIM-kortet er låst."</string>
+    <string name="lockscreen_sim_unlock_progress_dialog_message">"Låser opp SIM-kort…"</string>
+    <string name="lockscreen_too_many_failed_attempts_dialog_message">"You have incorrectly drawn your unlock pattern <xliff:g id="NUMBER_0">%d</xliff:g> times. "\n\n"Please try again in <xliff:g id="NUMBER_1">%d</xliff:g> seconds."</string>
+    <string name="lockscreen_failed_attempts_almost_glogin">"You have incorrectly drawn your unlock pattern <xliff:g id="NUMBER_0">%d</xliff:g> times. After <xliff:g id="NUMBER_1">%d</xliff:g> more unsuccessful attempts, you will be asked to unlock your phone using your Google sign-in."\n\n" Please try again in <xliff:g id="NUMBER_2">%d</xliff:g> seconds."</string>
+    <string name="lockscreen_too_many_failed_attempts_countdown">"Prøv igjen om <xliff:g id="NUMBER">%d</xliff:g> sekunder."</string>
+    <string name="lockscreen_forgot_pattern_button_text">"Glemt mønsteret?"</string>
+    <string name="lockscreen_glogin_too_many_attempts">"Too many pattern attempts!"</string>
+    <string name="lockscreen_glogin_instructions">"To unlock,"\n"sign in with your Google account"</string>
+    <string name="lockscreen_glogin_username_hint">"Username (email)"</string>
+    <string name="lockscreen_glogin_password_hint">"Password"</string>
+    <string name="lockscreen_glogin_submit_button">"Sign in"</string>
+    <string name="lockscreen_glogin_invalid_input">"Invalid username or password."</string>
+    <string name="status_bar_time_format">"<xliff:g id="HOUR">h</xliff:g>:<xliff:g id="MINUTE">mm</xliff:g> <xliff:g id="AMPM">AA</xliff:g>"</string>
+    <string name="hour_minute_ampm">"<xliff:g id="HOUR">%-l</xliff:g>:<xliff:g id="MINUTE">%M</xliff:g><xliff:g id="AMPM">%P</xliff:g>"</string>
+    <string name="hour_minute_cap_ampm">"<xliff:g id="HOUR">%-l</xliff:g>:<xliff:g id="MINUTE">%M</xliff:g><xliff:g id="AMPM">%p</xliff:g>"</string>
+    <!-- no translation found for hour_ampm (4329881288269772723) -->
+    <skip />
+    <!-- no translation found for hour_cap_ampm (1829009197680861107) -->
+    <skip />
+    <string name="status_bar_clear_all_button">"Fjern varslinger"</string>
+    <string name="status_bar_no_notifications_title">"Ingen varslinger"</string>
+    <string name="status_bar_ongoing_events_title">"Aktiviteter"</string>
+    <string name="status_bar_latest_events_title">"Varslinger"</string>
+    <!-- no translation found for battery_status_text_percent_format (7660311274698797147) -->
+    <skip />
+    <string name="battery_status_charging">"Lader…"</string>
+    <string name="battery_low_title">"Koble til en lader"</string>
+    <string name="battery_low_subtitle">"Batteriet er nesten tomt:"</string>
+    <string name="battery_low_percent_format">"mindre enn <xliff:g id="NUMBER">%d%%</xliff:g> igjen."</string>
+    <string name="factorytest_failed">"Factory test failed"</string>
+    <string name="factorytest_not_system">"The FACTORY_TEST action is only supported for packages installed in /system/app."</string>
+    <string name="factorytest_no_action">"No package was found that provides the FACTORY_TEST action."</string>
+    <string name="factorytest_reboot">"Reboot"</string>
+    <string name="js_dialog_title">"Siden \'<xliff:g id="TITLE">%s</xliff:g> sier:\""</string>
+    <string name="js_dialog_title_default">"JavaScript"</string>
+    <string name="js_dialog_before_unload">"Naviger bort fra denne siden?"\n\n"<xliff:g id="MESSAGE">%s</xliff:g>"\n\n"Velg OK for å fortsette, eller Avbryt for å forbli på denne siden."</string>
+    <string name="save_password_label">"Bekreft"</string>
+    <string name="save_password_message">"Ønsker du at nettleseren skal huske dette passordet?"</string>
+    <string name="save_password_notnow">"Ikke nå"</string>
+    <string name="save_password_remember">"Husk"</string>
+    <string name="save_password_never">"Aldri"</string>
+    <string name="open_permission_deny">"Du har ikke de nødvendige rettighetene til å åpne denne siden."</string>
+    <string name="text_copied">"Kopierte tekst til utklippstavlen."</string>
+    <string name="more_item_label">"Mer"</string>
+    <string name="prepend_shortcut_label">"menyknapp+"</string>
+    <string name="menu_space_shortcut_label">"mellomrom"</string>
+    <string name="menu_enter_shortcut_label">"enter"</string>
+    <string name="menu_delete_shortcut_label">"slett"</string>
+    <string name="search_go">"Søk"</string>
+    <string name="today">"I dag"</string>
+    <string name="yesterday">"I går"</string>
+    <string name="tomorrow">"I morgen"</string>
+    <string name="oneMonthDurationPast">"For en måned siden"</string>
+    <string name="beforeOneMonthDurationPast">"For over en måned siden"</string>
+  <plurals name="num_seconds_ago">
+    <item quantity="one">"for et sekund siden"</item>
+    <item quantity="other">"for <xliff:g id="COUNT">%d</xliff:g> sekunder siden"</item>
+  </plurals>
+  <plurals name="num_minutes_ago">
+    <item quantity="one">"for et minutt siden"</item>
+    <item quantity="other">"for <xliff:g id="COUNT">%d</xliff:g> minutter siden"</item>
+  </plurals>
+  <plurals name="num_hours_ago">
+    <item quantity="one">"for en time siden"</item>
+    <item quantity="other">"for <xliff:g id="COUNT">%d</xliff:g> timer siden"</item>
+  </plurals>
+  <plurals name="num_days_ago">
+    <item quantity="one">"i går"</item>
+    <item quantity="other">"for <xliff:g id="COUNT">%d</xliff:g> dager siden"</item>
+  </plurals>
+  <plurals name="in_num_seconds">
+    <item quantity="one">"om et sekund"</item>
+    <item quantity="other">"om <xliff:g id="COUNT">%d</xliff:g> sekunder"</item>
+  </plurals>
+  <plurals name="in_num_minutes">
+    <item quantity="one">"om et minutt"</item>
+    <item quantity="other">"om <xliff:g id="COUNT">%d</xliff:g> minutter"</item>
+  </plurals>
+  <plurals name="in_num_hours">
+    <item quantity="one">"om et minutt"</item>
+    <item quantity="other">"om <xliff:g id="COUNT">%d</xliff:g> timer"</item>
+  </plurals>
+  <plurals name="in_num_days">
+    <item quantity="one">"i morgen"</item>
+    <item quantity="other">"om <xliff:g id="COUNT">%d</xliff:g> dager"</item>
+  </plurals>
+  <plurals name="abbrev_num_seconds_ago">
+    <item quantity="one">"1 sek siden"</item>
+    <item quantity="other">"<xliff:g id="COUNT">%d</xliff:g> sek siden"</item>
+  </plurals>
+  <plurals name="abbrev_num_minutes_ago">
+    <item quantity="one">"1 min siden"</item>
+    <item quantity="other">"<xliff:g id="COUNT">%d</xliff:g> min siden"</item>
+  </plurals>
+  <plurals name="abbrev_num_hours_ago">
+    <item quantity="one">"1 t siden"</item>
+    <item quantity="other">"<xliff:g id="COUNT">%d</xliff:g> t siden"</item>
+  </plurals>
+  <plurals name="abbrev_num_days_ago">
+    <item quantity="one">"i går"</item>
+    <item quantity="other">"<xliff:g id="COUNT">%d</xliff:g> d siden"</item>
+  </plurals>
+  <plurals name="abbrev_in_num_seconds">
+    <item quantity="one">"om 1 sek"</item>
+    <item quantity="other">"om <xliff:g id="COUNT">%d</xliff:g> sek"</item>
+  </plurals>
+  <plurals name="abbrev_in_num_minutes">
+    <item quantity="one">"om 1 min"</item>
+    <item quantity="other">"om <xliff:g id="COUNT">%d</xliff:g> min"</item>
+  </plurals>
+  <plurals name="abbrev_in_num_hours">
+    <item quantity="one">"om 1 t"</item>
+    <item quantity="other">"om <xliff:g id="COUNT">%d</xliff:g> t"</item>
+  </plurals>
+  <plurals name="abbrev_in_num_days">
+    <item quantity="one">"i morgen"</item>
+    <item quantity="other">"om <xliff:g id="COUNT">%d</xliff:g> d"</item>
+  </plurals>
+    <string name="preposition_for_date">"%s"</string>
+    <string name="preposition_for_time">"%s"</string>
+    <string name="preposition_for_year">"%s"</string>
+    <string name="day">"dag"</string>
+    <string name="days">"dager"</string>
+    <string name="hour">"time"</string>
+    <string name="hours">"timer"</string>
+    <string name="minute">"min"</string>
+    <string name="minutes">"min"</string>
+    <string name="second">"s"</string>
+    <string name="seconds">"s"</string>
+    <string name="week">"uke"</string>
+    <string name="weeks">"uker"</string>
+    <string name="year">"år"</string>
+    <string name="years">"år"</string>
+    <string name="sunday">"søndag"</string>
+    <string name="monday">"mandag"</string>
+    <string name="tuesday">"tirsdag"</string>
+    <string name="wednesday">"onsdag"</string>
+    <string name="thursday">"torsdag"</string>
+    <string name="friday">"fredag"</string>
+    <string name="saturday">"lørdag"</string>
+    <string name="every_weekday">"Hverdager (man–fre)"</string>
+    <string name="daily">"Hver dag"</string>
+    <string name="weekly">"Hver <xliff:g id="DAY">%s</xliff:g>"</string>
+    <string name="monthly">"En gang i måneden"</string>
+    <string name="yearly">"En gang i året"</string>
+    <string name="VideoView_error_title">"Cannot play video"</string>
+    <string name="VideoView_error_text_unknown">"Sorry, this video cannot be played."</string>
+    <string name="VideoView_error_button">"OK"</string>
+    <string name="am">"AM"</string>
+    <string name="pm">"PM"</string>
+    <string name="numeric_date">"<xliff:g id="YEAR">%Y</xliff:g>-<xliff:g id="MONTH">%m</xliff:g>-<xliff:g id="DAY">%d</xliff:g>"</string>
+    <string name="wday1_date1_time1_wday2_date2_time2">"<xliff:g id="WEEKDAY1">%1$s</xliff:g> <xliff:g id="DATE1">%2$s</xliff:g> <xliff:g id="TIME1">%3$s</xliff:g> – <xliff:g id="WEEKDAY2">%4$s</xliff:g> <xliff:g id="DATE2">%5$s</xliff:g> <xliff:g id="TIME2">%6$s</xliff:g>"</string>
+    <string name="wday1_date1_wday2_date2">"<xliff:g id="WEEKDAY1">%1$s</xliff:g> <xliff:g id="DATE1">%2$s</xliff:g> – <xliff:g id="WEEKDAY2">%4$s</xliff:g> <xliff:g id="DATE2">%5$s</xliff:g>"</string>
+    <string name="date1_time1_date2_time2">"<xliff:g id="DATE1">%2$s</xliff:g> <xliff:g id="TIME1">%3$s</xliff:g> – <xliff:g id="DATE2">%5$s</xliff:g> <xliff:g id="TIME2">%6$s</xliff:g>"</string>
+    <string name="date1_date2">"<xliff:g id="DATE1">%2$s</xliff:g> – <xliff:g id="DATE2">%5$s</xliff:g>"</string>
+    <string name="time1_time2">"<xliff:g id="TIME1">%1$s</xliff:g> – <xliff:g id="TIME2">%2$s</xliff:g>"</string>
+    <string name="time_wday_date">"<xliff:g id="TIME_RANGE">%1$s</xliff:g>, <xliff:g id="WEEKDAY">%2$s</xliff:g> <xliff:g id="DATE">%3$s</xliff:g>"</string>
+    <string name="wday_date">"<xliff:g id="WEEKDAY">%2$s</xliff:g> <xliff:g id="DATE">%3$s</xliff:g>PLACEHOLDERplaceholder"</string>
+    <string name="time_date">"<xliff:g id="TIME_RANGE">%1$s</xliff:g>, <xliff:g id="DATE">%3$s</xliff:g>"</string>
+    <string name="date_time">"<xliff:g id="DATE">%1$s</xliff:g>, <xliff:g id="TIME">%2$s</xliff:g>"</string>
+    <string name="relative_time">"<xliff:g id="DATE">%1$s</xliff:g>, <xliff:g id="TIME">%2$s</xliff:g>"</string>
+    <string name="time_wday">"<xliff:g id="TIME_RANGE">%1$s</xliff:g>, <xliff:g id="WEEKDAY">%2$s</xliff:g>"</string>
+    <string name="full_date_month_first" format="date">"<xliff:g id="MONTH">MMMM</xliff:g>' '<xliff:g id="DAY">d</xliff:g>'., '<xliff:g id="YEAR">yyyy</xliff:g>"</string>
+    <string name="full_date_day_first" format="date">"<xliff:g id="DAY">d</xliff:g>'. '<xliff:g id="MONTH">MMMM</xliff:g>' '<xliff:g id="YEAR">yyyy</xliff:g>"</string>
+    <string name="medium_date_month_first" format="date">"<xliff:g id="MONTH">MMM</xliff:g>' '<xliff:g id="DAY">d</xliff:g>', '<xliff:g id="YEAR">yyyy</xliff:g>"</string>
+    <string name="medium_date_day_first" format="date">"<xliff:g id="DAY">d</xliff:g>'. '<xliff:g id="MONTH">MMM</xliff:g>' '<xliff:g id="YEAR">yyyy</xliff:g>"</string>
+    <string name="twelve_hour_time_format" format="date">"<xliff:g id="HOUR">h</xliff:g>':'<xliff:g id="MINUTE">mm</xliff:g>' '<xliff:g id="AMPM">a</xliff:g>"</string>
+    <string name="twenty_four_hour_time_format" format="date">"<xliff:g id="HOUR">H</xliff:g>':'<xliff:g id="MINUTE">mm</xliff:g>"</string>
+    <string name="noon">"middag"</string>
+    <string name="Noon">"Middag"</string>
+    <string name="midnight">"midnatt"</string>
+    <string name="Midnight">"Midnatt"</string>
+    <!-- no translation found for month_day (3693060561170538204) -->
+    <skip />
+    <!-- no translation found for month (1976700695144952053) -->
+    <skip />
+    <string name="month_day_year">"<xliff:g id="DAY">%-d</xliff:g>. <xliff:g id="MONTH">%B</xliff:g> <xliff:g id="YEAR">%Y</xliff:g>"</string>
+    <!-- no translation found for month_year (2106203387378728384) -->
+    <skip />
+    <string name="time_of_day">"<xliff:g id="HOUR">%H</xliff:g>:<xliff:g id="MINUTE">%M</xliff:g>:<xliff:g id="SECOND">%S</xliff:g>"</string>
+    <string name="date_and_time">"<xliff:g id="HOUR">%H</xliff:g>:<xliff:g id="MINUTE">%M</xliff:g>:<xliff:g id="SECOND">%S</xliff:g> <xliff:g id="DAY">%-d</xliff:g>. <xliff:g id="MONTH">%B</xliff:g> <xliff:g id="YEAR">%Y</xliff:g>"</string>
+    <string name="same_year_md1_md2">"<xliff:g id="MONTH1">%2$s</xliff:g> <xliff:g id="DAY1">%3$s</xliff:g> – <xliff:g id="MONTH2">%7$s</xliff:g> <xliff:g id="DAY2">%8$s</xliff:g>"</string>
+    <string name="same_year_wday1_md1_wday2_md2">"<xliff:g id="WEEKDAY1">%1$s</xliff:g> <xliff:g id="DAY1_0">%3$s</xliff:g>. <xliff:g id="MONTH1">%2$s</xliff:g> – <xliff:g id="WEEKDAY2">%6$s</xliff:g> <xliff:g id="DAY2_1">%8$s</xliff:g>. <xliff:g id="MONTH2">%7$s</xliff:g>"</string>
+    <string name="same_year_mdy1_mdy2">"<xliff:g id="DAY1">%3$s</xliff:g>. <xliff:g id="MONTH1">%2$s</xliff:g> – <xliff:g id="DAY2">%8$s</xliff:g>. <xliff:g id="MONTH2">%7$s</xliff:g> <xliff:g id="YEAR">%9$s</xliff:g>"</string>
+    <string name="same_year_wday1_mdy1_wday2_mdy2">"<xliff:g id="WEEKDAY1">%1$s</xliff:g> <xliff:g id="MONTH1">%2$s</xliff:g> <xliff:g id="DAY1_0">%3$s</xliff:g> – <xliff:g id="WEEKDAY2">%6$s</xliff:g> <xliff:g id="MONTH2">%7$s</xliff:g> <xliff:g id="DAY2_1">%8$s</xliff:g>, <xliff:g id="YEAR">%9$s</xliff:g>"</string>
+    <string name="same_year_md1_time1_md2_time2">"<xliff:g id="DAY1">%3$s</xliff:g>. <xliff:g id="MONTH1">%2$s</xliff:g> <xliff:g id="TIME1">%5$s</xliff:g> – <xliff:g id="DAY2">%8$s</xliff:g>. <xliff:g id="MONTH2">%7$s</xliff:g> <xliff:g id="TIME2">%10$s</xliff:g>"</string>
+    <string name="same_year_wday1_md1_time1_wday2_md2_time2">"<xliff:g id="WEEKDAY1">%1$s</xliff:g> <xliff:g id="DAY1_0">%3$s</xliff:g>. <xliff:g id="MONTH1">%2$s</xliff:g> <xliff:g id="TIME1">%5$s</xliff:g> – <xliff:g id="WEEKDAY2">%6$s</xliff:g> <xliff:g id="DAY2_1">%8$s</xliff:g>. <xliff:g id="MONTH2">%7$s</xliff:g> <xliff:g id="TIME2">%10$s</xliff:g>"</string>
+    <string name="same_year_mdy1_time1_mdy2_time2">"<xliff:g id="DAY1">%3$s</xliff:g>. <xliff:g id="MONTH1">%2$s</xliff:g> <xliff:g id="YEAR1">%4$s</xliff:g> <xliff:g id="TIME1">%5$s</xliff:g> – <xliff:g id="DAY2">%8$s</xliff:g>. <xliff:g id="MONTH2">%7$s</xliff:g> <xliff:g id="YEAR2">%9$s</xliff:g> <xliff:g id="TIME2">%10$s</xliff:g>"</string>
+    <string name="same_year_wday1_mdy1_time1_wday2_mdy2_time2">"<xliff:g id="WEEKDAY1">%1$s</xliff:g> <xliff:g id="DAY1_0">%3$s</xliff:g>. <xliff:g id="MONTH1">%2$s</xliff:g> <xliff:g id="YEAR1">%4$s</xliff:g> <xliff:g id="TIME1">%5$s</xliff:g> – <xliff:g id="WEEKDAY2">%6$s</xliff:g> <xliff:g id="DAY2_1">%8$s</xliff:g>. <xliff:g id="MONTH2">%7$s</xliff:g> <xliff:g id="YEAR2">%9$s</xliff:g> <xliff:g id="TIME2">%10$s</xliff:g>"</string>
+    <string name="numeric_md1_md2">"<xliff:g id="DAY1">%3$s</xliff:g>.<xliff:g id="MONTH1">%2$s</xliff:g>. – <xliff:g id="DAY2">%8$s</xliff:g>.<xliff:g id="MONTH2">%7$s</xliff:g>."</string>
+    <string name="numeric_wday1_md1_wday2_md2">"<xliff:g id="WEEKDAY1">%1$s</xliff:g> <xliff:g id="DAY1_0">%3$s</xliff:g>.<xliff:g id="MONTH1">%2$s</xliff:g>. – <xliff:g id="WEEKDAY2">%6$s</xliff:g> <xliff:g id="DAY2_1">%8$s</xliff:g>.<xliff:g id="MONTH2">%7$s</xliff:g>."</string>
+    <string name="numeric_mdy1_mdy2">"<xliff:g id="DAY1">%3$s</xliff:g>.<xliff:g id="MONTH1">%2$s</xliff:g>.<xliff:g id="YEAR1">%4$s</xliff:g> – <xliff:g id="DAY2">%8$s</xliff:g>.<xliff:g id="MONTH2">%7$s</xliff:g>.<xliff:g id="YEAR2">%9$s</xliff:g>"</string>
+    <string name="numeric_wday1_mdy1_wday2_mdy2">"<xliff:g id="WEEKDAY1">%1$s</xliff:g> <xliff:g id="DAY1_0">%3$s</xliff:g>.<xliff:g id="MONTH1">%2$s</xliff:g>.<xliff:g id="YEAR1">%4$s</xliff:g> – <xliff:g id="WEEKDAY2">%6$s</xliff:g> <xliff:g id="DAY2_1">%8$s</xliff:g>.<xliff:g id="MONTH2">%7$s</xliff:g>.<xliff:g id="YEAR2">%9$s</xliff:g>"</string>
+    <string name="numeric_md1_time1_md2_time2">"<xliff:g id="DAY1">%3$s</xliff:g>.<xliff:g id="MONTH1">%2$s</xliff:g>. <xliff:g id="TIME1">%5$s</xliff:g> – <xliff:g id="DAY2">%8$s</xliff:g>.<xliff:g id="MONTH2">%7$s</xliff:g>. <xliff:g id="TIME2">%10$s</xliff:g>"</string>
+    <string name="numeric_wday1_md1_time1_wday2_md2_time2">"<xliff:g id="WEEKDAY1">%1$s</xliff:g> <xliff:g id="DAY1_0">%3$s</xliff:g>.<xliff:g id="MONTH1">%2$s</xliff:g>. <xliff:g id="TIME1">%5$s</xliff:g> – <xliff:g id="WEEKDAY2">%6$s</xliff:g> <xliff:g id="DAY2_1">%8$s</xliff:g>.<xliff:g id="MONTH2">%7$s</xliff:g>. <xliff:g id="TIME2">%10$s</xliff:g>"</string>
+    <string name="numeric_mdy1_time1_mdy2_time2">"<xliff:g id="DAY1">%3$s</xliff:g>.<xliff:g id="MONTH1">%2$s</xliff:g>.<xliff:g id="YEAR1">%4$s</xliff:g> <xliff:g id="TIME1">%5$s</xliff:g> – <xliff:g id="DAY2">%8$s</xliff:g>.<xliff:g id="MONTH2">%7$s</xliff:g>.<xliff:g id="YEAR2">%9$s</xliff:g> <xliff:g id="TIME2">%10$s</xliff:g>"</string>
+    <string name="numeric_wday1_mdy1_time1_wday2_mdy2_time2">"<xliff:g id="WEEKDAY1">%1$s</xliff:g> <xliff:g id="DAY1_0">%3$s</xliff:g>.<xliff:g id="MONTH1">%2$s</xliff:g>.<xliff:g id="YEAR1">%4$s</xliff:g> <xliff:g id="TIME1">%5$s</xliff:g> – <xliff:g id="WEEKDAY2">%6$s</xliff:g> <xliff:g id="DAY2_1">%8$s</xliff:g>.<xliff:g id="MONTH2">%7$s</xliff:g>.<xliff:g id="YEAR2">%9$s</xliff:g> <xliff:g id="TIME2">%10$s</xliff:g>"</string>
+    <string name="same_month_md1_md2">"<xliff:g id="DAY1">%3$s</xliff:g>.–<xliff:g id="DAY2">%8$s</xliff:g>. <xliff:g id="MONTH1">%2$s</xliff:g>"</string>
+    <string name="same_month_wday1_md1_wday2_md2">"<xliff:g id="WEEKDAY1">%1$s</xliff:g> <xliff:g id="DAY1_0">%3$s</xliff:g>. <xliff:g id="MONTH1">%2$s</xliff:g> – <xliff:g id="WEEKDAY2">%6$s</xliff:g> <xliff:g id="DAY2_1">%8$s</xliff:g>. <xliff:g id="MONTH2">%7$s</xliff:g>"</string>
+    <string name="same_month_mdy1_mdy2">"<xliff:g id="DAY1">%3$s</xliff:g>.–<xliff:g id="DAY2">%8$s</xliff:g> <xliff:g id="MONTH1">%2$s</xliff:g> <xliff:g id="YEAR2">%9$s</xliff:g>"</string>
+    <string name="same_month_wday1_mdy1_wday2_mdy2">"<xliff:g id="WEEKDAY1">%1$s</xliff:g> <xliff:g id="DAY1_0">%3$s</xliff:g>. <xliff:g id="MONTH1">%2$s</xliff:g> <xliff:g id="YEAR1">%4$s</xliff:g> – <xliff:g id="WEEKDAY2">%6$s</xliff:g> <xliff:g id="DAY2_1">%8$s</xliff:g>. <xliff:g id="MONTH2">%7$s</xliff:g> <xliff:g id="YEAR2">%9$s</xliff:g>"</string>
+    <string name="same_month_md1_time1_md2_time2">"<xliff:g id="DAY1">%3$s</xliff:g>. <xliff:g id="MONTH1">%2$s</xliff:g> <xliff:g id="TIME1">%5$s</xliff:g> – <xliff:g id="DAY2">%8$s</xliff:g>. <xliff:g id="MONTH2">%7$s</xliff:g> <xliff:g id="TIME2">%10$s</xliff:g>"</string>
+    <string name="same_month_wday1_md1_time1_wday2_md2_time2">"<xliff:g id="WEEKDAY1">%1$s</xliff:g> <xliff:g id="DAY1_0">%3$s</xliff:g>. <xliff:g id="MONTH1">%2$s</xliff:g> <xliff:g id="TIME1">%5$s</xliff:g> – <xliff:g id="WEEKDAY2">%6$s</xliff:g> <xliff:g id="DAY2_1">%8$s</xliff:g>. <xliff:g id="MONTH2">%7$s</xliff:g> <xliff:g id="TIME2">%10$s</xliff:g>"</string>
+    <string name="same_month_mdy1_time1_mdy2_time2">"<xliff:g id="DAY1">%3$s</xliff:g>. <xliff:g id="MONTH1">%2$s</xliff:g> <xliff:g id="YEAR1">%4$s</xliff:g> <xliff:g id="TIME1">%5$s</xliff:g> – <xliff:g id="DAY2">%8$s</xliff:g>. <xliff:g id="MONTH2">%7$s</xliff:g> <xliff:g id="YEAR2">%9$s</xliff:g> <xliff:g id="TIME2">%10$s</xliff:g>"</string>
+    <string name="same_month_wday1_mdy1_time1_wday2_mdy2_time2">"<xliff:g id="WEEKDAY1">%1$s</xliff:g> <xliff:g id="DAY1_0">%3$s</xliff:g>. <xliff:g id="MONTH1">%2$s</xliff:g> <xliff:g id="YEAR1">%4$s</xliff:g> <xliff:g id="TIME1">%5$s</xliff:g> – <xliff:g id="WEEKDAY2">%6$s</xliff:g> <xliff:g id="DAY2_1">%8$s</xliff:g>. <xliff:g id="MONTH2">%7$s</xliff:g> <xliff:g id="YEAR2">%9$s</xliff:g> <xliff:g id="TIME2">%10$s</xliff:g>"</string>
+    <string name="abbrev_month_day_year">"<xliff:g id="DAY">%-d</xliff:g>. <xliff:g id="MONTH">%b</xliff:g> <xliff:g id="YEAR">%Y</xliff:g>"</string>
+    <!-- no translation found for abbrev_month_year (5966980891147982768) -->
+    <skip />
+    <!-- no translation found for abbrev_month_day (3156047263406783231) -->
+    <skip />
+    <!-- no translation found for abbrev_month (7304935052615731208) -->
+    <skip />
+    <string name="day_of_week_long_sunday">"søndag"</string>
+    <string name="day_of_week_long_monday">"mandag"</string>
+    <string name="day_of_week_long_tuesday">"tirsdag"</string>
+    <string name="day_of_week_long_wednesday">"onsdag"</string>
+    <string name="day_of_week_long_thursday">"torsdag"</string>
+    <string name="day_of_week_long_friday">"fredag"</string>
+    <string name="day_of_week_long_saturday">"lørdag"</string>
+    <string name="day_of_week_medium_sunday">"søn"</string>
+    <string name="day_of_week_medium_monday">"man"</string>
+    <string name="day_of_week_medium_tuesday">"tir"</string>
+    <string name="day_of_week_medium_wednesday">"ons"</string>
+    <string name="day_of_week_medium_thursday">"tor"</string>
+    <string name="day_of_week_medium_friday">"fre"</string>
+    <string name="day_of_week_medium_saturday">"lør"</string>
+    <string name="day_of_week_short_sunday">"sø"</string>
+    <string name="day_of_week_short_monday">"ma"</string>
+    <string name="day_of_week_short_tuesday">"ti"</string>
+    <string name="day_of_week_short_wednesday">"on"</string>
+    <string name="day_of_week_short_thursday">"to"</string>
+    <string name="day_of_week_short_friday">"fr"</string>
+    <string name="day_of_week_short_saturday">"lø"</string>
+    <string name="day_of_week_shorter_sunday">"S"</string>
+    <string name="day_of_week_shorter_monday">"M"</string>
+    <string name="day_of_week_shorter_tuesday">"Ti"</string>
+    <string name="day_of_week_shorter_wednesday">"O"</string>
+    <string name="day_of_week_shorter_thursday">"To"</string>
+    <string name="day_of_week_shorter_friday">"F"</string>
+    <string name="day_of_week_shorter_saturday">"L"</string>
+    <string name="day_of_week_shortest_sunday">"S"</string>
+    <string name="day_of_week_shortest_monday">"M"</string>
+    <string name="day_of_week_shortest_tuesday">"T"</string>
+    <string name="day_of_week_shortest_wednesday">"O"</string>
+    <string name="day_of_week_shortest_thursday">"T"</string>
+    <string name="day_of_week_shortest_friday">"F"</string>
+    <string name="day_of_week_shortest_saturday">"L"</string>
+    <string name="month_long_january">"januar"</string>
+    <string name="month_long_february">"februar"</string>
+    <string name="month_long_march">"mars"</string>
+    <string name="month_long_april">"april"</string>
+    <string name="month_long_may">"mai"</string>
+    <string name="month_long_june">"juni"</string>
+    <string name="month_long_july">"juli"</string>
+    <string name="month_long_august">"august"</string>
+    <string name="month_long_september">"september"</string>
+    <string name="month_long_october">"oktober"</string>
+    <string name="month_long_november">"november"</string>
+    <string name="month_long_december">"desember"</string>
+    <string name="month_medium_january">"jan"</string>
+    <string name="month_medium_february">"feb"</string>
+    <string name="month_medium_march">"mar"</string>
+    <string name="month_medium_april">"apr"</string>
+    <string name="month_medium_may">"mai"</string>
+    <string name="month_medium_june">"jun"</string>
+    <string name="month_medium_july">"jul"</string>
+    <string name="month_medium_august">"aug"</string>
+    <string name="month_medium_september">"sep"</string>
+    <string name="month_medium_october">"okt"</string>
+    <string name="month_medium_november">"nov"</string>
+    <string name="month_medium_december">"des"</string>
+    <string name="month_shortest_january">"J"</string>
+    <string name="month_shortest_february">"F"</string>
+    <string name="month_shortest_march">"M"</string>
+    <string name="month_shortest_april">"A"</string>
+    <string name="month_shortest_may">"M"</string>
+    <string name="month_shortest_june">"J"</string>
+    <string name="month_shortest_july">"J"</string>
+    <string name="month_shortest_august">"A"</string>
+    <string name="month_shortest_september">"S"</string>
+    <string name="month_shortest_october">"O"</string>
+    <string name="month_shortest_november">"N"</string>
+    <string name="month_shortest_december">"D"</string>
+    <string name="elapsed_time_short_format_mm_ss">"<xliff:g id="MINUTES">%1$02d</xliff:g>:<xliff:g id="SECONDS">%2$02d</xliff:g>"</string>
+    <string name="elapsed_time_short_format_h_mm_ss">"<xliff:g id="HOURS">%1$d</xliff:g>:<xliff:g id="MINUTES">%2$02d</xliff:g>:<xliff:g id="SECONDS">%3$02d</xliff:g>"</string>
+    <string name="selectAll">"Merk alt"</string>
+    <string name="selectText">"Merk tekst"</string>
+    <string name="stopSelectingText">"Slutt å merke tekst"</string>
+    <string name="cut">"Klipp ut"</string>
+    <string name="cutAll">"Klipp ut alt"</string>
+    <string name="copy">"Kopier"</string>
+    <string name="copyAll">"Kopier alt"</string>
+    <string name="paste">"Lim inn"</string>
+    <string name="copyUrl">"Kopier URL"</string>
+    <string name="inputMethod">"Inndatametode"</string>
+    <string name="addToDictionary">"Legg \\\"%s\\\" til ordlisten"</string>
+    <string name="editTextMenuTitle">"Rediger tekst"</string>
+    <string name="low_internal_storage_view_title">"Lite plass"</string>
+    <string name="low_internal_storage_view_text">"Det begynner å bli lite lagringsplass på telefonen."</string>
+    <string name="ok">"OK"</string>
+    <string name="cancel">"Avbryt"</string>
+    <string name="yes">"OK"</string>
+    <string name="no">"Avbryt"</string>
+    <string name="dialog_alert_title">"Merk"</string>
+    <string name="capital_on">"På"</string>
+    <string name="capital_off">"Av"</string>
+    <string name="whichApplication">"Complete action using"</string>
+    <string name="alwaysUse">"Use by default for this action."</string>
+    <string name="clearDefaultHintMsg">"Clear default in Home Settings &gt; Applications &gt; Manage applications."</string>
+    <string name="chooseActivity">"Select an action"</string>
+    <string name="noApplications">"No applications can perform this action."</string>
+    <string name="aerr_title">"Sorry!"</string>
+    <string name="aerr_application">"The application <xliff:g id="APPLICATION">%1$s</xliff:g> (process <xliff:g id="PROCESS">%2$s</xliff:g>) has stopped unexpectedly. Please try again."</string>
+    <string name="aerr_process">"The process <xliff:g id="PROCESS">%1$s</xliff:g> has stopped unexpectedly. Please try again."</string>
+    <string name="anr_title">"Sorry!"</string>
+    <string name="anr_activity_application">"Activity <xliff:g id="ACTIVITY">%1$s</xliff:g> (in application <xliff:g id="APPLICATION">%2$s</xliff:g>) is not responding."</string>
+    <string name="anr_activity_process">"Activity <xliff:g id="ACTIVITY">%1$s</xliff:g> (in process <xliff:g id="PROCESS">%2$s</xliff:g>) is not responding."</string>
+    <string name="anr_application_process">"Application <xliff:g id="APPLICATION">%1$s</xliff:g> (in process <xliff:g id="PROCESS">%2$s</xliff:g>) is not responding."</string>
+    <string name="anr_process">"Process <xliff:g id="PROCESS">%1$s</xliff:g> is not responding."</string>
+    <string name="force_close">"Force close"</string>
+    <string name="wait">"Wait"</string>
+    <string name="debug">"Debug"</string>
+    <string name="sendText">"Select an action for text"</string>
+    <string name="volume_ringtone">"Ringetonevolum"</string>
+    <string name="volume_music">"Medievolum"</string>
+    <string name="volume_music_hint_playing_through_bluetooth">"Spiller over Bluetooth"</string>
+    <string name="volume_call">"Samtalevolum"</string>
+    <string name="volume_bluetooth_call">"Bluetooth-samtalevolum"</string>
+    <string name="volume_alarm">"Alarmvolum"</string>
+    <string name="volume_notification">"Varslingsvolum"</string>
+    <string name="volume_unknown">"Volum"</string>
+    <string name="ringtone_default">"Standard ringetone"</string>
+    <string name="ringtone_default_with_actual">"Standard ringetone (<xliff:g id="ACTUAL_RINGTONE">%1$s</xliff:g>)"</string>
+    <string name="ringtone_silent">"Stille"</string>
+    <string name="ringtone_picker_title">"Ringetoner"</string>
+    <string name="ringtone_unknown">"Ukjent ringetone"</string>
+  <plurals name="wifi_available">
+    <item quantity="one">"Trådløsnett i nærheten"</item>
+    <item quantity="other">"Trådløsnett i nærheten"</item>
+  </plurals>
+  <plurals name="wifi_available_detailed">
+    <item quantity="one">"Åpent trådløsnett i nærheten"</item>
+    <item quantity="other">"Åpne trådløsnett i nærheten"</item>
+  </plurals>
+    <string name="select_character">"Sett inn tegn"</string>
+    <string name="sms_control_default_app_name">"Ukjent applikasjon"</string>
+    <string name="sms_control_title">"Sending SMS messages"</string>
+    <string name="sms_control_message">"A large number of SMS messages are being sent. Select \"OK\" to continue, or \"Cancel\" to stop sending."</string>
+    <string name="sms_control_yes">"OK"</string>
+    <string name="sms_control_no">"Avbryt"</string>
+    <string name="date_time_set">"Lagre"</string>
+    <string name="default_permission_group">"Standard"</string>
+    <string name="no_permissions">"Trenger ingen rettigheter"</string>
+    <string name="perms_hide"><b>"Skjul"</b></string>
+    <string name="perms_show_all"><b>"Vis alle"</b></string>
+    <string name="googlewebcontenthelper_loading">"Laster inn…"</string>
+    <string name="usb_storage_title">"USB koblet til"</string>
+    <string name="usb_storage_message">"Du har koblet telefonen til en datamaskin via USB. Velg \"Monter\" dersom du ønsker å kopiere filer mellom datmaskinen og minnekortet i telefonen."</string>
+    <string name="usb_storage_button_mount">"Monter"</string>
+    <string name="usb_storage_button_unmount">"Ikke monter"</string>
+    <string name="usb_storage_error_message">"Det oppsto et problem med å bruke minnekortet ditt for USB-lagring."</string>
+    <string name="usb_storage_notification_title">"USB tilkoblet"</string>
+    <string name="usb_storage_notification_message">"Velg om du ønsker å kopiere filer til/fra en datamaskin."</string>
+    <string name="usb_storage_stop_notification_title">"Slå av USB-lagring"</string>
+    <string name="usb_storage_stop_notification_message">"Velg for å slå av USB-lagring."</string>
+    <string name="usb_storage_stop_title">"Slå av USB-lagring"</string>
+    <string name="usb_storage_stop_message">"Før du slår av USB-lagring, sjekk at du har avmontert enheten i USB-verten. Velg «slå av» for å slå av USB-lagring."</string>
+    <string name="usb_storage_stop_button_mount">"Slå av"</string>
+    <string name="usb_storage_stop_button_unmount">"Avbryt"</string>
+    <string name="usb_storage_stop_error_message">"Det oppsto et problem under avslutningen av USB-lagring. Sjekk at USB-verten har avmontert og prøv igjen."</string>
+    <string name="extmedia_format_title">"Formatere minnekort"</string>
+    <string name="extmedia_format_message">"Er du sikker på at du ønsker å formatere minnekortet? Alle data på kortet vil gå tapt."</string>
+    <string name="extmedia_format_button_format">"Format"</string>
+    <string name="select_input_method">"Velg inndatametode"</string>
+    <string name="fast_scroll_alphabet">" ABCDEFGHIJKLMNOPQRSTUVWXYZÆØÅ"</string>
+    <string name="fast_scroll_numeric_alphabet">" 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZÆØÅ"</string>
+    <string name="candidates_style">"TAG_FONT"<u>"kandidater"</u>"CLOSE_FONT"</string>
+    <string name="ext_media_checking_notification_title">"Forbereder minnekort"</string>
+    <string name="ext_media_checking_notification_message">"Sjekker for feil"</string>
+    <string name="ext_media_nofs_notification_title">"Tomt minnekort"</string>
+    <string name="ext_media_nofs_notification_message">"Minnekortet er tomt eller bruker et ustøttet filsystem."</string>
+    <string name="ext_media_unmountable_notification_title">"Skadet minnekort"</string>
+    <string name="ext_media_unmountable_notification_message">"Minnekortet er skadet. Det kan være du må formatere kortet."</string>
+    <string name="ext_media_badremoval_notification_title">"Minnekortet ble tatt ut uventet"</string>
+    <string name="ext_media_badremoval_notification_message">"Avmonter minnekortet før det tas ut, for å unngå datatap."</string>
+    <string name="ext_media_safe_unmount_notification_title">"Trygt å ta ut minnekort"</string>
+    <string name="ext_media_safe_unmount_notification_message">"Minnekortet kan nå trygt tas ut."</string>
+    <string name="ext_media_nomedia_notification_title">"Minnekortet ble tatt ut"</string>
+    <string name="ext_media_nomedia_notification_message">"Minnekortet ble tatt ut. Sett inn et nytt minnekort for å øke lagringsplassen."</string>
+    <string name="activity_list_empty">"Fant ingen tilsvarende aktiviteter"</string>
+    <string name="permlab_pkgUsageStats">"oppdater statistikk over komponentbruk"</string>
+    <string name="permdesc_pkgUsageStats">"Tillater endring av innsamlet data om bruk av komponenter. Ikke ment for vanlige applikasjoner."</string>
+    <!-- no translation found for tutorial_double_tap_to_zoom_message_short (1311810005957319690) -->
+    <skip />
+    <!-- no translation found for gadget_host_error_inflating (2613287218853846830) -->
+    <skip />
+    <!-- no translation found for ime_action_go (8320845651737369027) -->
+    <skip />
+    <!-- no translation found for ime_action_search (658110271822807811) -->
+    <skip />
+    <!-- no translation found for ime_action_send (2316166556349314424) -->
+    <skip />
+    <!-- no translation found for ime_action_next (3138843904009813834) -->
+    <skip />
+    <!-- no translation found for ime_action_default (2840921885558045721) -->
+    <skip />
+</resources>
diff --git a/core/res/res/values-nl/strings.xml b/core/res/res/values-nl/strings.xml
new file mode 100644
index 0000000..725e369
--- /dev/null
+++ b/core/res/res/values-nl/strings.xml
@@ -0,0 +1,815 @@
+<?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.
+-->
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="byteShort">"B"</string>
+    <string name="kilobyteShort">"kB"</string>
+    <string name="megabyteShort">"MB"</string>
+    <string name="gigabyteShort">"GB"</string>
+    <string name="terabyteShort">"TB"</string>
+    <string name="petabyteShort">"PB"</string>
+    <string name="untitled">"&lt;zonder titel&gt;"</string>
+    <string name="ellipsis">"…"</string>
+    <string name="emptyPhoneNumber">"(Geen telefoonnummer)"</string>
+    <string name="unknownName">"(Onbekend)"</string>
+    <string name="defaultVoiceMailAlphaTag">"Voicemail"</string>
+    <string name="defaultMsisdnAlphaTag">"MSISDN1"</string>
+    <string name="mmiError">"Verbindingsprobleem of ongeldige MMI-code."</string>
+    <string name="serviceEnabled">"Service is ingeschakeld."</string>
+    <string name="serviceEnabledFor">"Service is ingeschakeld voor:"</string>
+    <string name="serviceDisabled">"Service is uitgeschakeld."</string>
+    <string name="serviceRegistered">"De registratie is voltooid."</string>
+    <string name="serviceErased">"Wissen uitgevoerd."</string>
+    <string name="passwordIncorrect">"Onjuist wachtwoord."</string>
+    <string name="mmiComplete">"MMI voltooid."</string>
+    <string name="badPin">"De oude PIN-code die u heeft ingevoerd, is onjuist."</string>
+    <string name="badPuk">"De PUK-code die u heeft ingevoerd, is onjuist."</string>
+    <string name="mismatchPin">"De PIN-codes die u heeft ingevoerd, komen niet overeen."</string>
+    <string name="invalidPin">"Voer een PIN-code van 4 tot 8 cijfers in."</string>
+    <string name="needPuk">"Uw SIM-kaart is geblokkeerd met de PUK-code. Typ de PUK-code om de blokkering op te heffen."</string>
+    <string name="needPuk2">"Voer de PUK2-code in om de SIM-kaart te ontgrendelen."</string>
+    <string name="ClipMmi">"Inkomende beller-id"</string>
+    <string name="ClirMmi">"Uitgaande beller-id"</string>
+    <string name="CfMmi">"Oproep doorschakelen"</string>
+    <string name="CwMmi">"Wisselgesprek"</string>
+    <string name="BaMmi">"Oproep blokkeren"</string>
+    <string name="PwdMmi">"Wachtwoordwijziging"</string>
+    <string name="PinMmi">"PIN-wijziging"</string>
+    <string name="CLIRDefaultOnNextCallOn">"Beller-id standaard ingesteld op \'beperkt\'. Volgende oproep: beperkt."</string>
+    <string name="CLIRDefaultOnNextCallOff">"Beller-id standaard ingesteld op \'beperkt\'. Volgende oproep: onbeperkt."</string>
+    <string name="CLIRDefaultOffNextCallOn">"Beller-id standaard ingesteld op \'onbeperkt\'. Volgende oproep: beperkt."</string>
+    <string name="CLIRDefaultOffNextCallOff">"Beller-id standaard ingesteld op \'onbeperkt\'. Volgende oproep: onbeperkt."</string>
+    <string name="serviceNotProvisioned">"Service niet voorzien."</string>
+    <string name="CLIRPermanent">"De instelling voor beller-id kan niet worden gewijzigd."</string>
+    <string name="serviceClassVoice">"Spraak"</string>
+    <string name="serviceClassData">"Gegevens"</string>
+    <string name="serviceClassFAX">"FAX"</string>
+    <string name="serviceClassSMS">"SMS"</string>
+    <string name="serviceClassDataAsync">"Asynchroon"</string>
+    <string name="serviceClassDataSync">"Synchroniseren"</string>
+    <string name="serviceClassPacket">"Pakket"</string>
+    <string name="serviceClassPAD">"PAD"</string>
+    <string name="cfTemplateNotForwarded">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: niet doorgeschakeld"</string>
+    <string name="cfTemplateForwarded">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
+    <string name="cfTemplateForwardedTime">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> na <xliff:g id="TIME_DELAY">{2}</xliff:g> seconden"</string>
+    <string name="cfTemplateRegistered">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: niet doorgeschakeld"</string>
+    <string name="cfTemplateRegisteredTime">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: niet doorgeschakeld"</string>
+    <string name="httpErrorOk">"OK"</string>
+    <string name="httpError">"De webpagina bevat een fout."</string>
+    <string name="httpErrorLookup">"De URL kan niet worden gevonden."</string>
+    <string name="httpErrorUnsupportedAuthScheme">"Het schema voor de siteverificatie wordt niet ondersteund."</string>
+    <string name="httpErrorAuth">"Verificatie mislukt."</string>
+    <string name="httpErrorProxyAuth">"Verificatie via de proxyserver is mislukt."</string>
+    <string name="httpErrorConnect">"Er kan geen verbinding met de server worden gemaakt."</string>
+    <string name="httpErrorIO">"De server kan niet communiceren. Probeer het later opnieuw."</string>
+    <string name="httpErrorTimeout">"Er is een time-out voor de serververbinding opgetreden."</string>
+    <string name="httpErrorRedirectLoop">"De pagina bevat te veel serveromleidingen."</string>
+    <string name="httpErrorUnsupportedScheme">"Het protocol wordt niet ondersteund."</string>
+    <string name="httpErrorFailedSslHandshake">"Er kan geen beveiligde verbinding tot stand worden gebracht."</string>
+    <string name="httpErrorBadUrl">"De pagina kan niet worden geopend, omdat de URL ongeldig is."</string>
+    <string name="httpErrorFile">"Het bestand kan niet worden geopend."</string>
+    <string name="httpErrorFileNotFound">"Het opgevraagde bestand is niet gevonden."</string>
+    <string name="httpErrorTooManyRequests">"Er worden te veel aanvragen verwerkt. Probeer het later opnieuw."</string>
+    <string name="contentServiceSync">"Synchroniseren"</string>
+    <string name="contentServiceSyncNotificationTitle">"Synchroniseren"</string>
+    <string name="contentServiceTooManyDeletesNotificationDesc">"Te veel verwijderen voor <xliff:g id="CONTENT_TYPE">%s</xliff:g>."</string>
+    <string name="low_memory">"Telefoongeheugen is vol! Verwijder enkele bestanden om ruimte vrij te maken."</string>
+    <string name="me">"Ik"</string>
+    <string name="power_dialog">"Telefoonopties"</string>
+    <string name="silent_mode">"Stille modus"</string>
+    <string name="turn_on_radio">"Draadloos inschakelen"</string>
+    <string name="turn_off_radio">"Draadloos uitschakelen"</string>
+    <string name="screen_lock">"Schermblokkering"</string>
+    <string name="power_off">"Uitschakelen"</string>
+    <string name="shutdown_progress">"Uitschakelen..."</string>
+    <string name="shutdown_confirm">"Uw telefoon wordt uitgeschakeld."</string>
+    <string name="no_recent_tasks">"Geen recente toepassingen."</string>
+    <string name="global_actions">"Telefoonopties"</string>
+    <string name="global_action_lock">"Schermblokkering"</string>
+    <string name="global_action_power_off">"Uitschakelen"</string>
+    <string name="global_action_toggle_silent_mode">"Stille modus"</string>
+    <string name="global_action_silent_mode_on_status">"Geluid is UIT"</string>
+    <string name="global_action_silent_mode_off_status">"Geluid is AAN"</string>
+    <!-- no translation found for global_actions_toggle_airplane_mode (5884330306926307456) -->
+    <skip />
+    <!-- no translation found for global_actions_airplane_mode_on_status (2719557982608919750) -->
+    <skip />
+    <!-- no translation found for global_actions_airplane_mode_off_status (5075070442854490296) -->
+    <skip />
+    <string name="safeMode">"Veilige modus"</string>
+    <!-- no translation found for android_system_label (6577375335728551336) -->
+    <skip />
+    <string name="permgrouplab_costMoney">"Services waarvoor u moet betalen"</string>
+    <string name="permgroupdesc_costMoney">"Toepassingen toestaan activiteiten uit te voeren waarvoor mogelijk kosten in rekening worden gebracht."</string>
+    <string name="permgrouplab_messages">"Uw berichten"</string>
+    <string name="permgroupdesc_messages">"SMS, e-mail en andere berichten lezen en schrijven."</string>
+    <string name="permgrouplab_personalInfo">"Uw persoonlijke informatie"</string>
+    <string name="permgroupdesc_personalInfo">"Rechtstreekse toegang tot de op uw telefoon opgeslagen contacten en agenda."</string>
+    <string name="permgrouplab_location">"Uw locatie"</string>
+    <string name="permgroupdesc_location">"Uw fysieke locatie bijhouden"</string>
+    <string name="permgrouplab_network">"Netwerkcommunicatie"</string>
+    <string name="permgroupdesc_network">"Toepassingen toestaan verschillende netwerkfuncties te openen."</string>
+    <string name="permgrouplab_accounts">"Uw Google-accounts"</string>
+    <string name="permgroupdesc_accounts">"Toegang tot de beschikbare Google-accounts."</string>
+    <string name="permgrouplab_hardwareControls">"Bedieningselementen hardware"</string>
+    <string name="permgroupdesc_hardwareControls">"Rechtstreekse toegang tot hardware op de handset."</string>
+    <string name="permgrouplab_phoneCalls">"Telefoonoproepen"</string>
+    <string name="permgroupdesc_phoneCalls">"Oproepen bijhouden, registreren en verwerken."</string>
+    <string name="permgrouplab_systemTools">"Systeemhulpprogramma\'s"</string>
+    <string name="permgroupdesc_systemTools">"Toegang tot en beheer van het systeem op lager niveau."</string>
+    <string name="permgrouplab_developmentTools">"Ontwikkelingshulpprogramma\'s"</string>
+    <string name="permgroupdesc_developmentTools">"Functies die alleen door toepassingsontwikkelaars worden gebruikt."</string>
+    <string name="permlab_statusBar">"statusbalk uitschakelen of wijzigen"</string>
+    <string name="permdesc_statusBar">"Hiermee kan een toepassing de statusbalk uitschakelen of systeempictogrammen toevoegen en verwijderen."</string>
+    <string name="permlab_expandStatusBar">"statusbalk uitvouwen/samenvouwen"</string>
+    <string name="permdesc_expandStatusBar">"Hiermee kan de toepassing de statusbalk uitvouwen of samenvouwen."</string>
+    <string name="permlab_processOutgoingCalls">"uitgaande oproepen onderscheppen"</string>
+    <string name="permdesc_processOutgoingCalls">"Hiermee kan een toepassing uitgaande oproepen verwerken en het nummer wijzigen dat wordt gebeld. Schadelijke toepassingen kunnen uitgaande oproepen bijhouden, omleiden of tegenhouden."</string>
+    <string name="permlab_receiveSms">"SMS ontvangen"</string>
+    <string name="permdesc_receiveSms">"Hiermee kan een toepassing SMS-berichten ontvangen en verwerken. Schadelijke toepassingen kunnen uw berichten bijhouden of deze verwijderen zonder dat u ze te zien krijgt."</string>
+    <string name="permlab_receiveMms">"MMS ontvangen"</string>
+    <string name="permdesc_receiveMms">"Hiermee kan een toepassing MMS-berichten ontvangen en verwerken. Schadelijke toepassingen kunnen uw berichten bijhouden of deze verwijderen zonder dat u ze te zien krijgt."</string>
+    <string name="permlab_sendSms">"SMS-berichten verzenden"</string>
+    <string name="permdesc_sendSms">"Hiermee kan de toepassing SMS-berichten verzenden. Schadelijke toepassingen kunnen u geld kosten door berichten te verzenden zonder uw toestemming."</string>
+    <string name="permlab_readSms">"SMS of MMS lezen"</string>
+    <string name="permdesc_readSms">"Hiermee kan een toepassing de op uw telefoon of SIM-kaart opgeslagen SMS-berichten lezen. Schadelijke toepassingen kunnen uw vertrouwelijke berichten mogelijk lezen."</string>
+    <string name="permlab_writeSms">"SMS of MMS bewerken"</string>
+    <string name="permdesc_writeSms">"Hiermee kan een toepassing naar de op uw telefoon of SIM-kaart opgeslagen SMS-berichten schrijven. Schadelijke toepassingen kunnen uw berichten mogelijk verwijderen."</string>
+    <string name="permlab_receiveWapPush">"WAP ontvangen"</string>
+    <string name="permdesc_receiveWapPush">"Hiermee kan een toepassing WAP-berichten ontvangen en verwerken. Schadelijke toepassingen kunnen uw berichten bijhouden of deze verwijderen zonder dat u ze te zien krijgt."</string>
+    <string name="permlab_getTasks">"actieve toepassingen ophalen"</string>
+    <string name="permdesc_getTasks">"Hiermee kan een toepassing informatie over huidige en recent uitgevoerde taken ophalen. Schadelijke toepassingen kunnen op deze manier mogelijk privé-informatie over andere toepassingen achterhalen."</string>
+    <string name="permlab_reorderTasks">"actieve toepassingen opnieuw indelen"</string>
+    <string name="permdesc_reorderTasks">"Hiermee kan een toepassing taken naar de voor- en achtergrond verplaatsen. Schadelijke toepassingen kunnen zichzelf op de voorgrond plaatsen zonder dat u hier iets aan kunt doen."</string>
+    <string name="permlab_setDebugApp">"foutopsporing in toepassingen inschakelen"</string>
+    <string name="permdesc_setDebugApp">"Hiermee kan een toepassing de foutopsporing voor een andere toepassing inschakelen. Schadelijke toepassingen kunnen dit gebruiken om andere toepassingen af te sluiten."</string>
+    <string name="permlab_changeConfiguration">"uw UI-instellingen wijzigen"</string>
+    <string name="permdesc_changeConfiguration">"Hiermee kan een toepassing de huidige configuratie, zoals de landinstelling of de algemene lettergrootte, wijzigen."</string>
+    <string name="permlab_restartPackages">"andere toepassingen opnieuw starten"</string>
+    <string name="permdesc_restartPackages">"Hiermee kan een toepassing andere toepassingen opnieuw starten."</string>
+    <string name="permlab_setProcessForeground">"stoppen voorkomen"</string>
+    <string name="permdesc_setProcessForeground">"Hiermee kan een toepassing ervoor zorgen dat elk willekeurig proces op de voorgrond wordt uitgevoerd en dus niet kan worden afgesloten. Nooit vereist voor normale toepassingen."</string>
+    <string name="permlab_forceBack">"toepassing nu sluiten"</string>
+    <string name="permdesc_forceBack">"Hiermee kan een toepassing elke willekeurige activiteit die op de voorgrond wordt uitgevoerd, sluiten en naar de achtergrond verplaatsen. Nooit vereist voor normale toepassingen."</string>
+    <string name="permlab_dump">"interne systeemstatus ophalen"</string>
+    <string name="permdesc_dump">"Hiermee kan een toepassing de interne status van het systeem ophalen. Schadelijke toepassingen kunnen privé- of veiligheidsgegevens ophalen die ze normaal niet nodig hebben."</string>
+    <string name="permlab_addSystemService">"services op laag niveau publiceren"</string>
+    <string name="permdesc_addSystemService">"Hiermee kunnen toepassingen hun eigen systeemservices op laag niveau publiceren. Schadelijke toepassingen kunnen het systeem mogelijk kapen en willekeurige gegevens van het systeem stelen of beschadigen."</string>
+    <string name="permlab_runSetActivityWatcher">"alle startende toepassingen bijhouden en beheren"</string>
+    <string name="permdesc_runSetActivityWatcher">"Hiermee kan een toepassing de manier waarop het systeem activiteiten start, bijhouden en beheren. Schadelijke toepassingen kunnen het systeem volledig in gevaar brengen. Deze machtiging is alleen voor ontwikkeling vereist, nooit voor normaal telefoongebruik."</string>
+    <string name="permlab_broadcastPackageRemoved">"melding verzenden dat pakket is verwijderd"</string>
+    <string name="permdesc_broadcastPackageRemoved">"Hiermee kan een toepassing een melding verzenden dat een toepassingspakket is verwijderd. Schadelijke toepassingen kunnen hiervan gebruik maken om alle andere actieve toepassingen af te sluiten."</string>
+    <string name="permlab_broadcastSmsReceived">"melding over ontvangen SMS-bericht verzenden"</string>
+    <string name="permdesc_broadcastSmsReceived">"Hiermee kan een toepassing een melding verzenden dat een SMS-bericht is ontvangen. Schadelijke toepassingen kunnen hiervan gebruik maken om inkomende SMS-berichten te vervalsen."</string>
+    <string name="permlab_broadcastWapPush">"melding over ontvangen WAP-PUSH-bericht verzenden"</string>
+    <string name="permdesc_broadcastWapPush">"Hiermee kan een toepassing een melding verzenden dat een WAP PUSH-bericht is ontvangen. Schadelijke toepassingen kunnen hiervan gebruik maken om een valse MMS-ontvangst te melden of de inhoud van willekeurige webpagina\'s door schadelijke varianten te vervangen."</string>
+    <string name="permlab_setProcessLimit">"aantal actieve processen beperken"</string>
+    <string name="permdesc_setProcessLimit">"Hiermee kan een toepassing het maximum aantal processen bepalen dat wordt uitgevoerd. Nooit vereist voor normale toepassingen."</string>
+    <string name="permlab_setAlwaysFinish">"alle achtergrondtoepassingen sluiten"</string>
+    <string name="permdesc_setAlwaysFinish">"Hiermee kan een toepassing bepalen of activiteiten altijd worden afgesloten zodra deze naar de achtergrond gaan. Nooit nodig voor normale toepassingen."</string>
+    <string name="permlab_fotaUpdate">"systeemupdates automatisch installeren"</string>
+    <string name="permdesc_fotaUpdate">"Hiermee ontvangt een toepassing meldingen over beschikbare systeemupdates en kan hun installatie starten. Schadelijke toepassingen kunnen hiervan gebruik maken om het systeem met ongeautoriseerde updates te beschadigen of het updateproces in het algemeen te verstoren."</string>
+    <string name="permlab_batteryStats">"accustatistieken aanpassen"</string>
+    <string name="permdesc_batteryStats">"Hiermee kunnen verzamelde accustatistieken worden gewijzigd. Niet voor gebruik door normale toepassingen."</string>
+    <string name="permlab_internalSystemWindow">"niet-geautoriseerde vensters weergeven"</string>
+    <string name="permdesc_internalSystemWindow">"Hiermee kunnen vensters worden gemaakt die door de interne systeemgebruikersinterface worden gebruikt. Niet voor gebruik door normale toepassingen."</string>
+    <string name="permlab_systemAlertWindow">"waarschuwingen op systeemniveau weergeven"</string>
+    <string name="permdesc_systemAlertWindow">"Hiermee kan een toepassing systeemwaarschuwingen weergeven. Schadelijke toepassingen kunnen op deze manier het hele scherm van de telefoon overnemen."</string>
+    <string name="permlab_setAnimationScale">"algemene animatiesnelheid wijzigen"</string>
+    <string name="permdesc_setAnimationScale">"Hiermee kan een toepassing op elk gewenst moment de algemene animatiesnelheid wijzigen (snellere of tragere animaties)."</string>
+    <string name="permlab_manageAppTokens">"toepassingstokens beheren"</string>
+    <string name="permdesc_manageAppTokens">"Hiermee kunnen toepassingen hun eigen tokens maken en beheren, waarbij de normale Z-volgorde wordt overgeslagen. Nooit nodig voor normale toepassingen."</string>
+    <string name="permlab_injectEvents">"drukken op toetsen en bedieningselementen"</string>
+    <string name="permdesc_injectEvents">"Hiermee kan een toepassing zijn eigen invoergebeurtenissen (toetsaanslagen, enzovoort) aan andere toepassingen doorgeven. Schadelijke toepassingen kunnen dit gebruiken om de telefoon over te nemen."</string>
+    <string name="permlab_readInputState">"uw invoer en acties vastleggen"</string>
+    <string name="permdesc_readInputState">"Hiermee kan een toepassing uw toetsaanslagen registreren, zelfs tijdens de interactie met een andere toepassing (zoals de invoer van een wachtwoord). Nooit vereist voor normale toepassingen."</string>
+    <string name="permlab_bindInputMethod">"verbinden aan een invoermethode"</string>
+    <string name="permdesc_bindInputMethod">"Hiermee staat u de houder toe zich te verbinden met de hoofdinterface van een invoermethode. Nooit vereist voor normale toepassingen."</string>
+    <string name="permlab_setOrientation">"schermstand wijzigen"</string>
+    <string name="permdesc_setOrientation">"Hiermee kan een toepassing op elk gewenst moment de oriëntatie van het scherm wijzigen. Nooit vereist voor normale toepassingen."</string>
+    <string name="permlab_signalPersistentProcesses">"Linux-signalen verzenden naar toepassingen"</string>
+    <string name="permdesc_signalPersistentProcesses">"Hiermee kan de toepassing ervoor zorgen dat het geleverde signaal wordt verzonden naar alle persistente processen."</string>
+    <string name="permlab_persistentActivity">"toepassing altijd laten uitvoeren"</string>
+    <string name="permdesc_persistentActivity">"Hiermee kan een toepassing delen van zichzelf persistent maken, zodat het systeem dat deel niet voor andere toepassingen kan gebruiken."</string>
+    <string name="permlab_deletePackages">"toepassingen verwijderen"</string>
+    <string name="permdesc_deletePackages">"Hiermee kan een toepassing Android-pakketten verwijderen. Schadelijke toepassingen kunnen dit gebruiken om belangrijke toepassingen te verwijderen."</string>
+    <string name="permlab_clearAppUserData">"gegevens van andere toepassingen verwijderen"</string>
+    <string name="permdesc_clearAppUserData">"Hiermee kan een toepassing gebruikersgegevens wissen."</string>
+    <string name="permlab_deleteCacheFiles">"caches van andere toepassingen verwijderen"</string>
+    <string name="permdesc_deleteCacheFiles">"Hiermee kan een toepassing cachebestanden verwijderen."</string>
+    <string name="permlab_getPackageSize">"opslagruimte van toepassing bepalen"</string>
+    <string name="permdesc_getPackageSize">"Hiermee kan een toepassing de bijbehorende code, gegevens en cachegrootten ophalen."</string>
+    <string name="permlab_installPackages">"toepassingen rechtstreeks installeren"</string>
+    <string name="permdesc_installPackages">"Hiermee kan een toepassing nieuwe of bijgewerkte Android-pakketten installeren. Schadelijke toepassingen kunnen hiervan gebruik maken om nieuwe toepassingen met willekeurig krachtige machtigingen toe te voegen."</string>
+    <string name="permlab_clearAppCache">"alle cachegegevens van toepassing verwijderen"</string>
+    <string name="permdesc_clearAppCache">"Hiermee kan een toepassing opslagruimte op de telefoon vrij maken door bestanden te verwijderen uit de cachemap van de toepassing. De toegang is doorgaans beperkt tot het systeemproces."</string>
+    <string name="permlab_readLogs">"systeemlogbestanden lezen"</string>
+    <string name="permdesc_readLogs">"Hiermee kan een toepassing de verschillende logbestanden van het systeem lezen. De toepassing kan op deze manier algemene informatie achterhalen over uw telefoongebruik. Hierin is geen persoonlijke of privé-informatie opgenomen."</string>
+    <string name="permlab_diagnostic">"lezen/schrijven naar bronnen van diag"</string>
+    <string name="permdesc_diagnostic">"Hiermee kan een toepassing lezen en schrijven naar elke bron die hoort bij de diagnostische groep, zoals bestanden in /dev. Hierdoor kan de systeemstabiliteit en -veiligheid worden beïnvloed. Dit mag ALLEEN worden gebruikt voor hardwarespecifieke diagnostiek door de fabrikant of operator."</string>
+    <string name="permlab_changeComponentState">"toepassingscomponenten in- of uitschakelen"</string>
+    <string name="permdesc_changeComponentState">"Hiermee kan een toepassing bepalen of een component van een andere toepassing is ingeschakeld. Schadelijke toepassingen kunnen hiervan gebruik maken om belangrijke telefoonfuncties uit te schakelen. Een machtiging moet zorgvuldig worden overwogen, aangezien toepassingscomponenten onbruikbaar, inconsistent of instabiel kunnen worden."</string>
+    <string name="permlab_setPreferredApplications">"voorkeurstoepassingen instellen"</string>
+    <string name="permdesc_setPreferredApplications">"Hiermee kan een toepassing uw voorkeurstoepassingen wijzigen. Schadelijke toepassingen kunnen op deze manier de actieve toepassingen zonder uw medeweten wijzigen en uw bestaande toepassingen doorzoeken om privégegevens van u te verzamelen."</string>
+    <string name="permlab_writeSettings">"algemene systeeminstellingen wijzigen"</string>
+    <string name="permdesc_writeSettings">"Hiermee kan een toepassing de systeeminstellingen wijzigen. Schadelijke toepassingen kunnen hiermee uw systeemconfiguratie beschadigen."</string>
+    <string name="permlab_writeSecureSettings">"beveiligde systeeminstellingen wijzigen"</string>
+    <string name="permdesc_writeSecureSettings">"Hiermee kan een toepassing beveiligde systeeminstellingen wijzigen. Niet voor gebruik door normale toepassingen."</string>
+    <string name="permlab_writeGservices">"de Google-serviceskaart wijzigen"</string>
+    <string name="permdesc_writeGservices">"Hiermee kan een toepassing de Google-serviceskaart wijzigen. Niet voor gebruik door normale toepassingen."</string>
+    <string name="permlab_receiveBootCompleted">"automatisch starten bij opstarten"</string>
+    <string name="permdesc_receiveBootCompleted">"Hiermee kan een toepassing zichzelf starten zodra het systeem klaar is met opstarten. Hierdoor kan het langer duren voordat de telefoon is opgestart en kan de toepassing de telefoonprocessen vertragen door altijd actief te zijn."</string>
+    <string name="permlab_broadcastSticky">"sticky broadcast verzenden"</string>
+    <string name="permdesc_broadcastSticky">"Hiermee kan een toepassing sticky broadcasts verzenden die achterblijven als de broadcast eindigt. Schadelijke toepassingen kunnen hiermee de telefoon traag of instabiel maken door ervoor te zorgen dat er te veel geheugenruimte wordt gebruikt."</string>
+    <string name="permlab_readContacts">"contactgegevens lezen"</string>
+    <string name="permdesc_readContacts">"Hiermee kan een toepassing alle contactgegevens (adresgegevens) zien die op uw telefoon zijn opgeslagen. Schadelijke toepassingen kunnen hiervan gebruik maken om uw gegevens te verzenden naar andere personen."</string>
+    <string name="permlab_writeContacts">"contactgegevens schrijven"</string>
+    <string name="permdesc_writeContacts">"Hiermee kan een toepassing de op uw telefoon opgeslagen contactgegevens (adresgegevens) wijzigen. Schadelijke toepassingen kunnen hiermee uw contactgegevens verwijderen of wijzigen."</string>
+    <string name="permlab_writeOwnerData">"gegevens eigenaar schrijven"</string>
+    <string name="permdesc_writeOwnerData">"Hiermee kan een toepassing de op uw telefoon opgeslagen gegevens van de eigenaar wijzigen. Schadelijke toepassingen kunnen hiermee gegevens van de eigenaar verwijderen of wijzigen."</string>
+    <string name="permlab_readOwnerData">"gegevens eigenaar lezen"</string>
+    <string name="permdesc_readOwnerData">"Hiermee kan een toepassing de op uw telefoon opgeslagen gegevens van de eigenaar lezen. Schadelijke toepassingen kunnen hiermee gegevens van de eigenaar lezen."</string>
+    <string name="permlab_readCalendar">"agendagegevens lezen"</string>
+    <string name="permdesc_readCalendar">"Hiermee kan een toepassing alle agendagebeurtenissen lezen die zijn opgeslagen op uw telefoon. Schadelijke toepassingen kunnen hiervan gebruik maken om uw agendagebeurtenissen te verzenden naar andere personen."</string>
+    <string name="permlab_writeCalendar">"agendagegevens schrijven"</string>
+    <string name="permdesc_writeCalendar">"Hiermee kan een toepassing de op uw telefoon opgeslagen agendagebeurtenissen wijzigen. Schadelijke toepassingen kunnen hiermee uw agendagegevens verwijderen of wijzigen."</string>
+    <string name="permlab_accessMockLocation">"neplocatiebronnen voor test"</string>
+    <string name="permdesc_accessMockLocation">"Neplocatiebronnen voor testdoeleinden maken. Schadelijke toepassingen kunnen dit gebruiken om de locatie en/of status te overschrijven die door de echte locatiebronnen wordt aangegeven, zoals GPS of netwerkaanbieders."</string>
+    <string name="permlab_accessLocationExtraCommands">"toegang opdrachten aanbieder extra locaties"</string>
+    <string name="permdesc_accessLocationExtraCommands">"Toegang tot opdrachten aanbieder extra locaties. Schadelijke toepassingen kunnen hiervan gebruik maken om de werking van GPS of andere locatiebronnen te verstoren."</string>
+    <string name="permlab_accessFineLocation">"nauwkeurige (GPS) locatie"</string>
+    <string name="permdesc_accessFineLocation">"Toegang tot exacte locatiebronnen, zoals het Global Positioning System op de telefoon, indien beschikbaar. Schadelijke toepassingen kunnen dit gebruiken om te bepalen waar u zich bevindt en mogelijk extra acculading verbruiken."</string>
+    <string name="permlab_accessCoarseLocation">"globale (netwerkgebaseerde) locatie"</string>
+    <string name="permdesc_accessCoarseLocation">"Toegang tot globale locatiebronnen, zoals de mobiele netwerkdatabase om een globale telefoonlocatie te bepalen, indien beschikbaar. Schadelijke toepassingen kunnen hiervan gebruik maken om bij benadering te bepalen waar u zich bevindt."</string>
+    <string name="permlab_accessSurfaceFlinger">"toegang tot SurfaceFlinger"</string>
+    <string name="permdesc_accessSurfaceFlinger">"Hiermee kan een toepassing SurfaceFlinger-functies op laag niveau gebruiken."</string>
+    <string name="permlab_readFrameBuffer">"framebuffer lezen"</string>
+    <string name="permdesc_readFrameBuffer">"Hiermee kan een toepassing de inhoud van de framebuffer lezen."</string>
+    <string name="permlab_modifyAudioSettings">"uw audio-instellingen wijzigen"</string>
+    <string name="permdesc_modifyAudioSettings">"Hiermee kan een toepassing de algemene audio-instellingen, zoals volume en omleiding, wijzigen."</string>
+    <string name="permlab_recordAudio">"audio opnemen"</string>
+    <string name="permdesc_recordAudio">"Hiermee krijgt de toepassing toegang tot het audio-opnamepad."</string>
+    <string name="permlab_camera">"foto\'s maken"</string>
+    <string name="permdesc_camera">"Hiermee kan een toepassing foto\'s maken met de camera. De toepassing kan op deze manier op elk gewenste moment foto\'s verzamelen van wat de camera ziet."</string>
+    <string name="permlab_brick">"telefoon permanent uitschakelen"</string>
+    <string name="permdesc_brick">"Hiermee kan de toepassing de telefoon permanent uitschakelen. Dit is erg gevaarlijk."</string>
+    <string name="permlab_reboot">"telefoon nu opnieuw opstarten"</string>
+    <string name="permdesc_reboot">"Hiermee kan de toepassing de telefoon nu opnieuw opstarten."</string>
+    <string name="permlab_mount_unmount_filesystems">"bestandssystemen koppelen en ontkoppelen"</string>
+    <string name="permdesc_mount_unmount_filesystems">"Hiermee kan de toepassing bestandssystemen koppelen en ontkoppelen voor verwisselbare opslagruimte."</string>
+    <string name="permlab_mount_format_filesystems">"externe opslag formatteren"</string>
+    <string name="permdesc_mount_format_filesystems">"Hiermee kan de toepassing de externe opslag formatteren."</string>
+    <string name="permlab_vibrate">"trilstand beheren"</string>
+    <string name="permdesc_vibrate">"Hiermee kan de toepassing de trilstand beheren."</string>
+    <string name="permlab_flashlight">"zaklamp bedienen"</string>
+    <string name="permdesc_flashlight">"Hiermee kan de toepassing de zaklamp bedienen."</string>
+    <string name="permlab_hardware_test">"hardware testen"</string>
+    <string name="permdesc_hardware_test">"Hiermee kan de toepassing verschillende randapparaten beheren om de hardware te testen."</string>
+    <string name="permlab_callPhone">"telefoonnummers rechtstreeks bellen"</string>
+    <string name="permdesc_callPhone">"Hiermee kan de toepassing telefoonnummers bellen zonder uw tussenkomst. Door schadelijke toepassingen kunnen onverwachte oproepen op uw telefoonrekening verschijnen. De toepassing kan hiermee geen alarmnummers bellen."</string>
+    <string name="permlab_callPrivileged">"alle telefoonnummers rechtstreeks bellen"</string>
+    <string name="permdesc_callPrivileged">"Hiermee kan een toepassing elk telefoonnummer, inclusief alarmnummers, bellen zonder uw tussenkomst. Schadelijke toepassingen kunnen onnodige en illegale oproepen uitvoeren naar alarmdiensten."</string>
+    <string name="permlab_locationUpdates">"meldingen over locatie-updates beheren"</string>
+    <string name="permdesc_locationUpdates">"Hiermee kunnen updatemeldingen voor locaties van de radio worden ingeschakeld/uitgeschakeld. Niet voor gebruik door normale toepassingen."</string>
+    <string name="permlab_checkinProperties">"toegang tot checkin-eigenschappen"</string>
+    <string name="permdesc_checkinProperties">"Hiermee wordt lees-/schrijftoegang gegeven tot eigenschappen die door de checkin-service zijn geüpload. Niet voor gebruik door normale toepassingen."</string>
+    <string name="permlab_bindGadget">"gadgets kiezen"</string>
+    <string name="permdesc_bindGadget">"Hiermee kan een toepassing het systeem melden welke gadgets door welke toepassing kunnen worden gebruikt. Met deze toestemming kunnen toepassingen andere toepassingen toegang geven tot persoonlijke gegevens. Niet voor gebruik door normale toepassingen."</string>
+    <string name="permlab_modifyPhoneState">"telefoonstatus wijzigen"</string>
+    <string name="permdesc_modifyPhoneState">"Hiermee kan de toepassing de telefoonfuncties van het apparaat beheren. Een toepassing met deze machtiging kan schakelen tussen netwerken, de radio van de telefoon in- of uitschakelen en dergelijke zonder dat u hiervan op de hoogte wordt gesteld."</string>
+    <string name="permlab_readPhoneState">"telefoonstatus lezen"</string>
+    <string name="permdesc_readPhoneState">"Hiermee krijgt de toepassing toegang tot de telefoonfuncties van het apparaat. Een toepassing met de betreffende machtiging kan het telefoonnummer van deze telefoon achterhalen, bepalen of een oproep actief is, het gekozen nummer achterhalen en dergelijke."</string>
+    <string name="permlab_wakeLock">"voorkomen dat telefoon overschakelt naar slaapmodus"</string>
+    <string name="permdesc_wakeLock">"Hiermee kan een toepassing voorkomen dat de telefoon overschakelt naar de slaapmodus."</string>
+    <string name="permlab_devicePower">"telefoon in- of uitschakelen"</string>
+    <string name="permdesc_devicePower">"Hiermee kan de toepassing de telefoon in- of uitschakelen."</string>
+    <string name="permlab_factoryTest">"uitvoeren in fabriekstestmodus"</string>
+    <string name="permdesc_factoryTest">"Uitvoeren als fabrikanttest op laag niveau, waardoor toegang wordt gegeven tot de hardware van de telefoon. Alleen beschikbaar als een telefoon zich in de fabrikanttestmodus bevindt."</string>
+    <string name="permlab_setWallpaper">"achtergrond instellen"</string>
+    <string name="permdesc_setWallpaper">"Hiermee kan de toepassing de systeemachtergrond instellen."</string>
+    <string name="permlab_setWallpaperHints">"grootte achtergrond instellen"</string>
+    <string name="permdesc_setWallpaperHints">"Hiermee kan de toepassing de grootte van de achtergrond instellen."</string>
+    <string name="permlab_masterClear">"systeem terugzetten op fabrieksinstellingen"</string>
+    <string name="permdesc_masterClear">"Hiermee kan een toepassing het systeem terugzetten op de fabrieksinstellingen, waarbij alle gegevens, configuraties en geïnstalleerde toepassingen worden verwijderd."</string>
+    <string name="permlab_setTimeZone">"tijdzone instellen"</string>
+    <string name="permdesc_setTimeZone">"Hiermee kan een toepassing de tijdzone van de telefoon wijzigen."</string>
+    <string name="permlab_getAccounts">"bekende accounts zoeken"</string>
+    <string name="permdesc_getAccounts">"Hiermee kan een toepassing de lijst met accounts van een telefoon ophalen."</string>
+    <string name="permlab_accessNetworkState">"netwerkstatus bekijken"</string>
+    <string name="permdesc_accessNetworkState">"Hiermee kan een toepassing de status van alle netwerken bekijken."</string>
+    <string name="permlab_createNetworkSockets">"volledige internettoegang"</string>
+    <string name="permdesc_createNetworkSockets">"Hiermee kan een toepassing netwerksockets maken."</string>
+    <string name="permlab_writeApnSettings">"instellingen voor toegangspuntnaam schrijven"</string>
+    <string name="permdesc_writeApnSettings">"Hiermee kan een toepassing de APN-instellingen, zoals proxy en poort, van elke APN wijzigen."</string>
+    <string name="permlab_changeNetworkState">"netwerkverbinding wijzigen"</string>
+    <string name="permdesc_changeNetworkState">"Hiermee kan een toepassing de verbindingsstatus van het netwerk wijzigen."</string>
+    <string name="permlab_changeBackgroundDataSetting">"instelling voor gebruik van achtergrondgegevens van gegevens wijzigen"</string>
+    <string name="permdesc_changeBackgroundDataSetting">"Hiermee kan een toepassing de instelling voor gebruik van achtergrondgegevens wijzigen."</string>
+    <string name="permlab_accessWifiState">"Wi-Fi-status bekijken"</string>
+    <string name="permdesc_accessWifiState">"Hiermee kan een toepassing informatie over de Wi-Fi-status bekijken."</string>
+    <string name="permlab_changeWifiState">"Wi-Fi-status wijzigen"</string>
+    <string name="permdesc_changeWifiState">"Hiermee kan een toepassing zich koppelen aan en loskoppelen van Wi-Fi toegangspunten en wijzigingen aanbrengen in geconfigureerde Wi-Fi-netwerken."</string>
+    <string name="permlab_bluetoothAdmin">"bluetooth-beheer"</string>
+    <string name="permdesc_bluetoothAdmin">"Hiermee kan een toepassing de lokale Bluetooth-telefoon configureren en externe apparaten zoeken en aansluiten."</string>
+    <string name="permlab_bluetooth">"Bluetooth-verbindingen maken"</string>
+    <string name="permdesc_bluetooth">"Hiermee kan een toepassing de configuratie van een lokale Bluetooth-telefoon bekijken en verbindingen met gekoppelde apparaten maken en accepteren."</string>
+    <string name="permlab_disableKeyguard">"toetsblokkering uitschakelen"</string>
+    <string name="permdesc_disableKeyguard">"Hiermee kan een toepassing de toetsblokkering en bijbehorende wachtwoordbeveiliging uitschakelen. Een voorbeeld: de telefoon schakelt de toetsblokkering uit als er een oproep binnenkomt en schakelt de toetsblokkering weer in als de oproep is beëindigd."</string>
+    <string name="permlab_readSyncSettings">"synchronisatie-instellingen lezen"</string>
+    <string name="permdesc_readSyncSettings">"Hiermee kan een toepassing de synchronisatie-instellingen lezen, bijvoorbeeld of de synchronisatie van contacten is ingeschakeld."</string>
+    <string name="permlab_writeSyncSettings">"synchronisatie-instellingen schrijven"</string>
+    <string name="permdesc_writeSyncSettings">"Hiermee kan een toepassing uw synchronisatie-instellingen wijzigen, bijvoorbeeld of de synchronisatie van contacten is ingeschakeld."</string>
+    <string name="permlab_readSyncStats">"synchronisatiestatistieken lezen"</string>
+    <string name="permdesc_readSyncStats">"Hiermee kan een toepassing de synchronisatiestatistieken lezen, zoals de geschiedenis van uitgevoerde synchronisaties."</string>
+    <string name="permlab_subscribedFeedsRead">"geabonneerde feeds lezen"</string>
+    <string name="permdesc_subscribedFeedsRead">"Hiermee kan een toepassing details over de huidige gesynchroniseerde feeds achterhalen."</string>
+    <string name="permlab_subscribedFeedsWrite">"geabonneerde feeds schrijven"</string>
+    <string name="permdesc_subscribedFeedsWrite">"Hiermee kan een toepassing uw huidige gesynchroniseerde feeds wijzigen. Een schadelijke toepassing kan op deze manier uw gesynchroniseerde feeds wijzigen."</string>
+    <string name="permlab_readDictionary">"door gebruiker gedefinieerd woordenboek lezen"</string>
+    <string name="permdesc_readDictionary">"Hiermee kan een toepassing privéwoorden, namen en woordcombinaties lezen die de gebruiker heeft opgeslagen in het gebruikerswoordenboek."</string>
+    <string name="permlab_writeDictionary">"schrijven naar door gebruiker gedefinieerd woordenboek"</string>
+    <string name="permdesc_writeDictionary">"Hiermee kan een toepassing nieuwe woorden schrijven naar het gebruikerswoordenboek."</string>
+  <string-array name="phoneTypes">
+    <item>"Thuis"</item>
+    <item>"Mobiel"</item>
+    <item>"Werk"</item>
+    <item>"Fax werk"</item>
+    <item>"Fax thuis"</item>
+    <item>"Semafoon"</item>
+    <item>"Overig"</item>
+    <item>"Aangepast"</item>
+  </string-array>
+  <string-array name="emailAddressTypes">
+    <item>"Thuis"</item>
+    <item>"Werk"</item>
+    <item>"Overig"</item>
+    <item>"Aangepast"</item>
+  </string-array>
+  <string-array name="postalAddressTypes">
+    <item>"Thuis"</item>
+    <item>"Werk"</item>
+    <item>"Overig"</item>
+    <item>"Aangepast"</item>
+  </string-array>
+  <string-array name="imAddressTypes">
+    <item>"Thuis"</item>
+    <item>"Werk"</item>
+    <item>"Overig"</item>
+    <item>"Aangepast"</item>
+  </string-array>
+  <string-array name="organizationTypes">
+    <item>"Werk"</item>
+    <item>"Overig"</item>
+    <item>"Aangepast"</item>
+  </string-array>
+  <string-array name="imProtocols">
+    <item>"AIM"</item>
+    <item>"Windows Live"</item>
+    <item>"Yahoo"</item>
+    <item>"Skype"</item>
+    <item>"QQ"</item>
+    <item>"Google Talk"</item>
+    <item>"ICQ"</item>
+    <item>"Jabber"</item>
+  </string-array>
+    <string name="keyguard_password_enter_pin_code">"PIN-code invoeren"</string>
+    <string name="keyguard_password_wrong_pin_code">"Onjuiste PIN-code!"</string>
+    <string name="keyguard_label_text">"Druk op \'Menu\' en vervolgens op 0 om te ontgrendelen."</string>
+    <string name="emergency_call_dialog_number_for_display">"Alarmnummer"</string>
+    <string name="lockscreen_carrier_default">"(Geen service)"</string>
+    <string name="lockscreen_screen_locked">"Scherm geblokkeerd."</string>
+    <string name="lockscreen_instructions_when_pattern_enabled">"Druk op \'Menu\' om te ontgrendelen of noodoproep te plaatsen."</string>
+    <string name="lockscreen_instructions_when_pattern_disabled">"Druk op \'Menu\' om te ontgrendelen."</string>
+    <string name="lockscreen_pattern_instructions">"Patroon tekenen om te ontgrendelen"</string>
+    <string name="lockscreen_emergency_call">"Noodoproep"</string>
+    <string name="lockscreen_pattern_correct">"Juist!"</string>
+    <string name="lockscreen_pattern_wrong">"Probeer het opnieuw"</string>
+    <!-- no translation found for lockscreen_plugged_in (613343852842944435) -->
+    <skip />
+    <string name="lockscreen_low_battery">"Sluit de oplader aan."</string>
+    <string name="lockscreen_missing_sim_message_short">"Geen SIM-kaart."</string>
+    <string name="lockscreen_missing_sim_message">"Geen SIM-kaart in telefoon."</string>
+    <string name="lockscreen_missing_sim_instructions">"Plaats een SIM-kaart."</string>
+    <string name="lockscreen_network_locked_message">"Netwerk geblokkeerd"</string>
+    <string name="lockscreen_sim_puk_locked_message">"SIM-kaart is geblokkeerd met PUK-code."</string>
+    <string name="lockscreen_sim_puk_locked_instructions">"Neem contact op met de klantenservice."</string>
+    <string name="lockscreen_sim_locked_message">"SIM-kaart is geblokkeerd."</string>
+    <string name="lockscreen_sim_unlock_progress_dialog_message">"SIM-kaart ontgrendelen..."</string>
+    <string name="lockscreen_too_many_failed_attempts_dialog_message">"U heeft uw deblokkeringspatroon <xliff:g id="NUMBER_0">%d</xliff:g> keer onjuist getekend. "\n\n"Probeer het over <xliff:g id="NUMBER_1">%d</xliff:g> seconden opnieuw."</string>
+    <string name="lockscreen_failed_attempts_almost_glogin">"U heeft uw deblokkeringspatroon <xliff:g id="NUMBER_0">%d</xliff:g> keer onjuist getekend. Na nog eens <xliff:g id="NUMBER_1">%d</xliff:g> mislukte pogingen, wordt u gevraagd om uw telefoon te ontgrendelen met uw Google aanmelding."\n\n" Probeer het over <xliff:g id="NUMBER_2">%d</xliff:g> seconden opnieuw."</string>
+    <string name="lockscreen_too_many_failed_attempts_countdown">"Probeer het over <xliff:g id="NUMBER">%d</xliff:g> seconden opnieuw."</string>
+    <string name="lockscreen_forgot_pattern_button_text">"Patroon vergeten?"</string>
+    <string name="lockscreen_glogin_too_many_attempts">"Te veel patroonpogingen!"</string>
+    <string name="lockscreen_glogin_instructions">"U moet zich aanmelden bij uw Google-account"\n"om te ontgrendelen"</string>
+    <string name="lockscreen_glogin_username_hint">"Gebruikersnaam (e-mail)"</string>
+    <string name="lockscreen_glogin_password_hint">"Wachtwoord"</string>
+    <string name="lockscreen_glogin_submit_button">"Aanmelden"</string>
+    <string name="lockscreen_glogin_invalid_input">"Gebruikersnaam of wachtwoord ongeldig."</string>
+    <string name="status_bar_time_format">"<xliff:g id="HOUR">h</xliff:g>:<xliff:g id="MINUTE">mm</xliff:g> <xliff:g id="AMPM">AA</xliff:g>"</string>
+    <string name="hour_minute_ampm">"<xliff:g id="HOUR">%-l</xliff:g>:<xliff:g id="MINUTE">%M</xliff:g><xliff:g id="AMPM">%P</xliff:g>"</string>
+    <string name="hour_minute_cap_ampm">"<xliff:g id="HOUR">%-l</xliff:g>:<xliff:g id="MINUTE">%M</xliff:g><xliff:g id="AMPM">%p</xliff:g>"</string>
+    <!-- no translation found for hour_ampm (4329881288269772723) -->
+    <skip />
+    <!-- no translation found for hour_cap_ampm (1829009197680861107) -->
+    <skip />
+    <string name="status_bar_clear_all_button">"Meldingen wissen"</string>
+    <string name="status_bar_no_notifications_title">"Geen meldingen"</string>
+    <string name="status_bar_ongoing_events_title">"Actief"</string>
+    <string name="status_bar_latest_events_title">"Meldingen"</string>
+    <!-- no translation found for battery_status_text_percent_format (7660311274698797147) -->
+    <skip />
+    <string name="battery_status_charging">"Opladen..."</string>
+    <string name="battery_low_title">"Sluit de oplader aan"</string>
+    <string name="battery_low_subtitle">"De accu raakt op:"</string>
+    <string name="battery_low_percent_format">"minder dan <xliff:g id="NUMBER">%d%%</xliff:g> resterend."</string>
+    <string name="factorytest_failed">"Fabriekstest mislukt"</string>
+    <string name="factorytest_not_system">"De actie FACTORY_TEST wordt alleen ondersteund voor pakketten die zijn geïnstalleerd in /system/app."</string>
+    <string name="factorytest_no_action">"Er is geen pakket gevonden dat de actie FACTORY_TEST levert."</string>
+    <string name="factorytest_reboot">"Opnieuw opstarten"</string>
+    <string name="js_dialog_title">"De pagina op \'<xliff:g id="TITLE">%s</xliff:g>\' zegt:"</string>
+    <string name="js_dialog_title_default">"JavaScript"</string>
+    <string name="js_dialog_before_unload">"Wilt u deze pagina verlaten?"\n\n"<xliff:g id="MESSAGE">%s</xliff:g>"\n\n"Kies OK om door te gaan of Annuleren om op de huidige pagina te blijven."</string>
+    <string name="save_password_label">"Bevestigen"</string>
+    <string name="save_password_message">"Wilt u dat de browser dit wachtwoord onthoudt?"</string>
+    <string name="save_password_notnow">"Niet nu"</string>
+    <string name="save_password_remember">"Onthouden"</string>
+    <string name="save_password_never">"Nooit"</string>
+    <string name="open_permission_deny">"U heeft geen toestemming om deze pagina te openen."</string>
+    <string name="text_copied">"Tekst naar klembord gekopieerd."</string>
+    <string name="more_item_label">"Meer"</string>
+    <string name="prepend_shortcut_label">"Menu+"</string>
+    <string name="menu_space_shortcut_label">"ruimte"</string>
+    <string name="menu_enter_shortcut_label">"invoeren"</string>
+    <string name="menu_delete_shortcut_label">"verwijderen"</string>
+    <string name="search_go">"Zoeken"</string>
+    <string name="today">"Vandaag"</string>
+    <string name="yesterday">"Gisteren"</string>
+    <string name="tomorrow">"Morgen"</string>
+    <string name="oneMonthDurationPast">"1 maand geleden"</string>
+    <string name="beforeOneMonthDurationPast">"Meer dan 1 maand geleden"</string>
+  <plurals name="num_seconds_ago">
+    <item quantity="one">"1 seconde geleden"</item>
+    <item quantity="other">"<xliff:g id="COUNT">%d</xliff:g> seconden geleden"</item>
+  </plurals>
+  <plurals name="num_minutes_ago">
+    <item quantity="one">"1 minuut geleden"</item>
+    <item quantity="other">"<xliff:g id="COUNT">%d</xliff:g> minuten geleden"</item>
+  </plurals>
+  <plurals name="num_hours_ago">
+    <item quantity="one">"1 uur geleden"</item>
+    <item quantity="other">"<xliff:g id="COUNT">%d</xliff:g> uur geleden"</item>
+  </plurals>
+  <plurals name="num_days_ago">
+    <item quantity="one">"gisteren"</item>
+    <item quantity="other">"<xliff:g id="COUNT">%d</xliff:g> dagen geleden"</item>
+  </plurals>
+  <plurals name="in_num_seconds">
+    <item quantity="one">"over 1 seconde"</item>
+    <item quantity="other">"over <xliff:g id="COUNT">%d</xliff:g> seconden"</item>
+  </plurals>
+  <plurals name="in_num_minutes">
+    <item quantity="one">"over 1 minuut"</item>
+    <item quantity="other">"over <xliff:g id="COUNT">%d</xliff:g> minuten"</item>
+  </plurals>
+  <plurals name="in_num_hours">
+    <item quantity="one">"over 1 uur"</item>
+    <item quantity="other">"over <xliff:g id="COUNT">%d</xliff:g> uur"</item>
+  </plurals>
+  <plurals name="in_num_days">
+    <item quantity="one">"morgen"</item>
+    <item quantity="other">"over <xliff:g id="COUNT">%d</xliff:g> dagen"</item>
+  </plurals>
+  <plurals name="abbrev_num_seconds_ago">
+    <item quantity="one">"1 seconde geleden"</item>
+    <item quantity="other">"<xliff:g id="COUNT">%d</xliff:g> seconden geleden"</item>
+  </plurals>
+  <plurals name="abbrev_num_minutes_ago">
+    <item quantity="one">"1 minuut geleden"</item>
+    <item quantity="other">"<xliff:g id="COUNT">%d</xliff:g> minuten geleden"</item>
+  </plurals>
+  <plurals name="abbrev_num_hours_ago">
+    <item quantity="one">"1 uur geleden"</item>
+    <item quantity="other">"<xliff:g id="COUNT">%d</xliff:g> uur geleden"</item>
+  </plurals>
+  <plurals name="abbrev_num_days_ago">
+    <item quantity="one">"gisteren"</item>
+    <item quantity="other">"<xliff:g id="COUNT">%d</xliff:g> dagen geleden"</item>
+  </plurals>
+  <plurals name="abbrev_in_num_seconds">
+    <item quantity="one">"over 1 seconde"</item>
+    <item quantity="other">"over <xliff:g id="COUNT">%d</xliff:g> seconden"</item>
+  </plurals>
+  <plurals name="abbrev_in_num_minutes">
+    <item quantity="one">"over 1 minuut"</item>
+    <item quantity="other">"over <xliff:g id="COUNT">%d</xliff:g> minuten"</item>
+  </plurals>
+  <plurals name="abbrev_in_num_hours">
+    <item quantity="one">"over 1 uur"</item>
+    <item quantity="other">"over <xliff:g id="COUNT">%d</xliff:g> uur"</item>
+  </plurals>
+  <plurals name="abbrev_in_num_days">
+    <item quantity="one">"morgen"</item>
+    <item quantity="other">"over <xliff:g id="COUNT">%d</xliff:g> dagen"</item>
+  </plurals>
+    <string name="preposition_for_date">"op %s"</string>
+    <string name="preposition_for_time">"om %s"</string>
+    <string name="preposition_for_year">"in %s"</string>
+    <string name="day">"dag"</string>
+    <string name="days">"dagen"</string>
+    <string name="hour">"uur"</string>
+    <string name="hours">"uren"</string>
+    <string name="minute">"min"</string>
+    <string name="minutes">"minuten"</string>
+    <string name="second">"sec"</string>
+    <string name="seconds">"seconden"</string>
+    <string name="week">"week"</string>
+    <string name="weeks">"weken"</string>
+    <string name="year">"jaar"</string>
+    <string name="years">"jaren"</string>
+    <string name="sunday">"Zondag"</string>
+    <string name="monday">"Maandag"</string>
+    <string name="tuesday">"Dinsdag"</string>
+    <string name="wednesday">"Woensdag"</string>
+    <string name="thursday">"Donderdag"</string>
+    <string name="friday">"Vrijdag"</string>
+    <string name="saturday">"Zaterdag"</string>
+    <string name="every_weekday">"Elke weekdag (ma-vr)"</string>
+    <string name="daily">"Dagelijks"</string>
+    <string name="weekly">"Wekelijks op <xliff:g id="DAY">%s</xliff:g>"</string>
+    <string name="monthly">"Maandelijks"</string>
+    <string name="yearly">"Jaarlijks"</string>
+    <string name="VideoView_error_title">"Video kan niet worden afgespeeld"</string>
+    <string name="VideoView_error_text_unknown">"Deze video kan niet worden afgespeeld."</string>
+    <string name="VideoView_error_button">"OK"</string>
+    <string name="am">"am"</string>
+    <string name="pm">"pm"</string>
+    <string name="numeric_date">"<xliff:g id="MONTH">%m</xliff:g>/<xliff:g id="DAY">%d</xliff:g>/<xliff:g id="YEAR">%Y</xliff:g>"</string>
+    <string name="wday1_date1_time1_wday2_date2_time2">"<xliff:g id="WEEKDAY1">%1$s</xliff:g>, <xliff:g id="DATE1">%2$s</xliff:g>, <xliff:g id="TIME1">%3$s</xliff:g> – <xliff:g id="WEEKDAY2">%4$s</xliff:g>, <xliff:g id="DATE2">%5$s</xliff:g>, <xliff:g id="TIME2">%6$s</xliff:g>"</string>
+    <string name="wday1_date1_wday2_date2">"<xliff:g id="WEEKDAY1">%1$s</xliff:g>, <xliff:g id="DATE1">%2$s</xliff:g> – <xliff:g id="WEEKDAY2">%4$s</xliff:g>, <xliff:g id="DATE2">%5$s</xliff:g>"</string>
+    <string name="date1_time1_date2_time2">"<xliff:g id="DATE1">%2$s</xliff:g>, <xliff:g id="TIME1">%3$s</xliff:g> – <xliff:g id="DATE2">%5$s</xliff:g>, <xliff:g id="TIME2">%6$s</xliff:g>"</string>
+    <string name="date1_date2">"<xliff:g id="DATE1">%2$s</xliff:g> – <xliff:g id="DATE2">%5$s</xliff:g>"</string>
+    <string name="time1_time2">"<xliff:g id="TIME1">%1$s</xliff:g> – <xliff:g id="TIME2">%2$s</xliff:g>"</string>
+    <string name="time_wday_date">"<xliff:g id="TIME_RANGE">%1$s</xliff:g>, <xliff:g id="WEEKDAY">%2$s</xliff:g>, <xliff:g id="DATE">%3$s</xliff:g>"</string>
+    <string name="wday_date">"<xliff:g id="WEEKDAY">%2$s</xliff:g>, <xliff:g id="DATE">%3$s</xliff:g>"</string>
+    <string name="time_date">"<xliff:g id="TIME_RANGE">%1$s</xliff:g>, <xliff:g id="DATE">%3$s</xliff:g>"</string>
+    <string name="date_time">"<xliff:g id="DATE">%1$s</xliff:g>, <xliff:g id="TIME">%2$s</xliff:g>"</string>
+    <string name="relative_time">"<xliff:g id="DATE">%1$s</xliff:g>, <xliff:g id="TIME">%2$s</xliff:g>"</string>
+    <string name="time_wday">"<xliff:g id="TIME_RANGE">%1$s</xliff:g>, <xliff:g id="WEEKDAY">%2$s</xliff:g>"</string>
+    <string name="full_date_month_first" format="date">"<xliff:g id="MONTH">MMMM</xliff:g>' '<xliff:g id="DAY">d</xliff:g>', '<xliff:g id="YEAR">yyyy</xliff:g>"</string>
+    <string name="full_date_day_first" format="date">"<xliff:g id="DAY">d</xliff:g>' '<xliff:g id="MONTH">MMMM</xliff:g>', '<xliff:g id="YEAR">yyyy</xliff:g>"</string>
+    <string name="medium_date_month_first" format="date">"<xliff:g id="MONTH">MMM</xliff:g>' '<xliff:g id="DAY">d</xliff:g>', '<xliff:g id="YEAR">yyyy</xliff:g>"</string>
+    <string name="medium_date_day_first" format="date">"<xliff:g id="DAY">d</xliff:g>' '<xliff:g id="MONTH">MMM</xliff:g>', '<xliff:g id="YEAR">yyyy</xliff:g>"</string>
+    <string name="twelve_hour_time_format" format="date">"<xliff:g id="HOUR">h</xliff:g>':'<xliff:g id="MINUTE">mm</xliff:g>' '<xliff:g id="AMPM">a</xliff:g>"</string>
+    <string name="twenty_four_hour_time_format" format="date">"<xliff:g id="HOUR">H</xliff:g>':'<xliff:g id="MINUTE">mm</xliff:g>"</string>
+    <string name="noon">"twaalf uur \'s middags"</string>
+    <string name="Noon">"Twaalf uur \'s middags"</string>
+    <string name="midnight">"middernacht"</string>
+    <string name="Midnight">"Middernacht"</string>
+    <!-- no translation found for month_day (3693060561170538204) -->
+    <skip />
+    <!-- no translation found for month (1976700695144952053) -->
+    <skip />
+    <string name="month_day_year">"<xliff:g id="MONTH">%B</xliff:g> <xliff:g id="DAY">%-d</xliff:g>, <xliff:g id="YEAR">%Y</xliff:g>"</string>
+    <!-- no translation found for month_year (2106203387378728384) -->
+    <skip />
+    <string name="time_of_day">"<xliff:g id="HOUR">%H</xliff:g>:<xliff:g id="MINUTE">%M</xliff:g>:<xliff:g id="SECOND">%S</xliff:g>"</string>
+    <string name="date_and_time">"<xliff:g id="HOUR">%H</xliff:g>:<xliff:g id="MINUTE">%M</xliff:g>:<xliff:g id="SECOND">%S</xliff:g> <xliff:g id="MONTH">%B</xliff:g> <xliff:g id="DAY">%-d</xliff:g>, <xliff:g id="YEAR">%Y</xliff:g>"</string>
+    <string name="same_year_md1_md2">"<xliff:g id="MONTH1">%2$s</xliff:g> <xliff:g id="DAY1">%3$s</xliff:g> – <xliff:g id="MONTH2">%7$s</xliff:g> <xliff:g id="DAY2">%8$s</xliff:g>"</string>
+    <string name="same_year_wday1_md1_wday2_md2">"<xliff:g id="WEEKDAY1">%1$s</xliff:g>, <xliff:g id="MONTH1">%2$s</xliff:g> <xliff:g id="DAY1_0">%3$s</xliff:g> – <xliff:g id="WEEKDAY2">%6$s</xliff:g>, <xliff:g id="MONTH2">%7$s</xliff:g> <xliff:g id="DAY2_1">%8$s</xliff:g>"</string>
+    <string name="same_year_mdy1_mdy2">"<xliff:g id="MONTH1">%2$s</xliff:g> <xliff:g id="DAY1">%3$s</xliff:g> – <xliff:g id="MONTH2">%7$s</xliff:g> <xliff:g id="DAY2">%8$s</xliff:g>, <xliff:g id="YEAR">%9$s</xliff:g>"</string>
+    <string name="same_year_wday1_mdy1_wday2_mdy2">"<xliff:g id="WEEKDAY1">%1$s</xliff:g>, <xliff:g id="MONTH1">%2$s</xliff:g> <xliff:g id="DAY1_0">%3$s</xliff:g> – <xliff:g id="WEEKDAY2">%6$s</xliff:g>, <xliff:g id="MONTH2">%7$s</xliff:g> <xliff:g id="DAY2_1">%8$s</xliff:g>, <xliff:g id="YEAR">%9$s</xliff:g>"</string>
+    <string name="same_year_md1_time1_md2_time2">"<xliff:g id="MONTH1">%2$s</xliff:g> <xliff:g id="DAY1">%3$s</xliff:g>, <xliff:g id="TIME1">%5$s</xliff:g> – <xliff:g id="MONTH2">%7$s</xliff:g> <xliff:g id="DAY2">%8$s</xliff:g>, <xliff:g id="TIME2">%10$s</xliff:g>"</string>
+    <string name="same_year_wday1_md1_time1_wday2_md2_time2">"<xliff:g id="WEEKDAY1">%1$s</xliff:g>, <xliff:g id="MONTH1">%2$s</xliff:g> <xliff:g id="DAY1_0">%3$s</xliff:g>, <xliff:g id="TIME1">%5$s</xliff:g> – <xliff:g id="WEEKDAY2">%6$s</xliff:g>, <xliff:g id="MONTH2">%7$s</xliff:g> <xliff:g id="DAY2_1">%8$s</xliff:g>, <xliff:g id="TIME2">%10$s</xliff:g>"</string>
+    <string name="same_year_mdy1_time1_mdy2_time2">"<xliff:g id="MONTH1">%2$s</xliff:g> <xliff:g id="DAY1">%3$s</xliff:g>, <xliff:g id="YEAR1">%4$s</xliff:g>, <xliff:g id="TIME1">%5$s</xliff:g> – <xliff:g id="MONTH2">%7$s</xliff:g> <xliff:g id="DAY2">%8$s</xliff:g>, <xliff:g id="YEAR2">%9$s</xliff:g>, <xliff:g id="TIME2">%10$s</xliff:g>"</string>
+    <string name="same_year_wday1_mdy1_time1_wday2_mdy2_time2">"<xliff:g id="WEEKDAY1">%1$s</xliff:g>, <xliff:g id="MONTH1">%2$s</xliff:g> <xliff:g id="DAY1_0">%3$s</xliff:g>, <xliff:g id="YEAR1">%4$s</xliff:g>, <xliff:g id="TIME1">%5$s</xliff:g> – <xliff:g id="WEEKDAY2">%6$s</xliff:g>, <xliff:g id="MONTH2">%7$s</xliff:g> <xliff:g id="DAY2_1">%8$s</xliff:g>, <xliff:g id="YEAR2">%9$s</xliff:g>, <xliff:g id="TIME2">%10$s</xliff:g>"</string>
+    <string name="numeric_md1_md2">"<xliff:g id="MONTH1">%2$s</xliff:g>/<xliff:g id="DAY1">%3$s</xliff:g> – <xliff:g id="MONTH2">%7$s</xliff:g>/<xliff:g id="DAY2">%8$s</xliff:g>"</string>
+    <string name="numeric_wday1_md1_wday2_md2">"<xliff:g id="WEEKDAY1">%1$s</xliff:g>, <xliff:g id="MONTH1">%2$s</xliff:g>/<xliff:g id="DAY1_0">%3$s</xliff:g> – <xliff:g id="WEEKDAY2">%6$s</xliff:g>, <xliff:g id="MONTH2">%7$s</xliff:g>/<xliff:g id="DAY2_1">%8$s</xliff:g>"</string>
+    <string name="numeric_mdy1_mdy2">"<xliff:g id="MONTH1">%2$s</xliff:g>/<xliff:g id="DAY1">%3$s</xliff:g>/<xliff:g id="YEAR1">%4$s</xliff:g> – <xliff:g id="MONTH2">%7$s</xliff:g>/<xliff:g id="DAY2">%8$s</xliff:g>/<xliff:g id="YEAR2">%9$s</xliff:g>"</string>
+    <string name="numeric_wday1_mdy1_wday2_mdy2">"<xliff:g id="WEEKDAY1">%1$s</xliff:g>, <xliff:g id="MONTH1">%2$s</xliff:g>/<xliff:g id="DAY1_0">%3$s</xliff:g>/<xliff:g id="YEAR1">%4$s</xliff:g> – <xliff:g id="WEEKDAY2">%6$s</xliff:g>, <xliff:g id="MONTH2">%7$s</xliff:g>/<xliff:g id="DAY2_1">%8$s</xliff:g>/<xliff:g id="YEAR2">%9$s</xliff:g>"</string>
+    <string name="numeric_md1_time1_md2_time2">"<xliff:g id="MONTH1">%2$s</xliff:g>/<xliff:g id="DAY1">%3$s</xliff:g>, <xliff:g id="TIME1">%5$s</xliff:g> – <xliff:g id="MONTH2">%7$s</xliff:g>/<xliff:g id="DAY2">%8$s</xliff:g>, <xliff:g id="TIME2">%10$s</xliff:g>"</string>
+    <string name="numeric_wday1_md1_time1_wday2_md2_time2">"<xliff:g id="WEEKDAY1">%1$s</xliff:g>, <xliff:g id="MONTH1">%2$s</xliff:g>/<xliff:g id="DAY1_0">%3$s</xliff:g>, <xliff:g id="TIME1">%5$s</xliff:g> – <xliff:g id="WEEKDAY2">%6$s</xliff:g>, <xliff:g id="MONTH2">%7$s</xliff:g>/<xliff:g id="DAY2_1">%8$s</xliff:g>, <xliff:g id="TIME2">%10$s</xliff:g>"</string>
+    <string name="numeric_mdy1_time1_mdy2_time2">"<xliff:g id="MONTH1">%2$s</xliff:g>/<xliff:g id="DAY1">%3$s</xliff:g>/<xliff:g id="YEAR1">%4$s</xliff:g>, <xliff:g id="TIME1">%5$s</xliff:g> – <xliff:g id="MONTH2">%7$s</xliff:g>/<xliff:g id="DAY2">%8$s</xliff:g>/<xliff:g id="YEAR2">%9$s</xliff:g>, <xliff:g id="TIME2">%10$s</xliff:g>"</string>
+    <string name="numeric_wday1_mdy1_time1_wday2_mdy2_time2">"<xliff:g id="WEEKDAY1">%1$s</xliff:g>, <xliff:g id="MONTH1">%2$s</xliff:g>/<xliff:g id="DAY1_0">%3$s</xliff:g>/<xliff:g id="YEAR1">%4$s</xliff:g>, <xliff:g id="TIME1">%5$s</xliff:g> – <xliff:g id="WEEKDAY2">%6$s</xliff:g>, <xliff:g id="MONTH2">%7$s</xliff:g>/<xliff:g id="DAY2_1">%8$s</xliff:g>/<xliff:g id="YEAR2">%9$s</xliff:g>, <xliff:g id="TIME2">%10$s</xliff:g>"</string>
+    <string name="same_month_md1_md2">"<xliff:g id="MONTH1">%2$s</xliff:g> <xliff:g id="DAY1">%3$s</xliff:g> – <xliff:g id="DAY2">%8$s</xliff:g>"</string>
+    <string name="same_month_wday1_md1_wday2_md2">"<xliff:g id="WEEKDAY1">%1$s</xliff:g>, <xliff:g id="MONTH1">%2$s</xliff:g> <xliff:g id="DAY1_0">%3$s</xliff:g> – <xliff:g id="WEEKDAY2">%6$s</xliff:g>, <xliff:g id="MONTH2">%7$s</xliff:g> <xliff:g id="DAY2_1">%8$s</xliff:g>"</string>
+    <string name="same_month_mdy1_mdy2">"<xliff:g id="MONTH1">%2$s</xliff:g> <xliff:g id="DAY1">%3$s</xliff:g> – <xliff:g id="DAY2">%8$s</xliff:g>, <xliff:g id="YEAR2">%9$s</xliff:g>"</string>
+    <string name="same_month_wday1_mdy1_wday2_mdy2">"<xliff:g id="WEEKDAY1">%1$s</xliff:g>, <xliff:g id="MONTH1">%2$s</xliff:g> <xliff:g id="DAY1_0">%3$s</xliff:g>, <xliff:g id="YEAR1">%4$s</xliff:g> – <xliff:g id="WEEKDAY2">%6$s</xliff:g>, <xliff:g id="MONTH2">%7$s</xliff:g> <xliff:g id="DAY2_1">%8$s</xliff:g>, <xliff:g id="YEAR2">%9$s</xliff:g>"</string>
+    <string name="same_month_md1_time1_md2_time2">"<xliff:g id="MONTH1">%2$s</xliff:g> <xliff:g id="DAY1">%3$s</xliff:g>, <xliff:g id="TIME1">%5$s</xliff:g> – <xliff:g id="MONTH2">%7$s</xliff:g> <xliff:g id="DAY2">%8$s</xliff:g>, <xliff:g id="TIME2">%10$s</xliff:g>"</string>
+    <string name="same_month_wday1_md1_time1_wday2_md2_time2">"<xliff:g id="WEEKDAY1">%1$s</xliff:g>, <xliff:g id="MONTH1">%2$s</xliff:g> <xliff:g id="DAY1_0">%3$s</xliff:g>, <xliff:g id="TIME1">%5$s</xliff:g> – <xliff:g id="WEEKDAY2">%6$s</xliff:g>, <xliff:g id="MONTH2">%7$s</xliff:g> <xliff:g id="DAY2_1">%8$s</xliff:g>, <xliff:g id="TIME2">%10$s</xliff:g>"</string>
+    <string name="same_month_mdy1_time1_mdy2_time2">"<xliff:g id="MONTH1">%2$s</xliff:g> <xliff:g id="DAY1">%3$s</xliff:g>, <xliff:g id="YEAR1">%4$s</xliff:g>, <xliff:g id="TIME1">%5$s</xliff:g> – <xliff:g id="MONTH2">%7$s</xliff:g> <xliff:g id="DAY2">%8$s</xliff:g>, <xliff:g id="YEAR2">%9$s</xliff:g>, <xliff:g id="TIME2">%10$s</xliff:g>"</string>
+    <string name="same_month_wday1_mdy1_time1_wday2_mdy2_time2">"<xliff:g id="WEEKDAY1">%1$s</xliff:g>, <xliff:g id="MONTH1">%2$s</xliff:g> <xliff:g id="DAY1_0">%3$s</xliff:g>, <xliff:g id="YEAR1">%4$s</xliff:g>, <xliff:g id="TIME1">%5$s</xliff:g> – <xliff:g id="WEEKDAY2">%6$s</xliff:g>, <xliff:g id="MONTH2">%7$s</xliff:g> <xliff:g id="DAY2_1">%8$s</xliff:g>, <xliff:g id="YEAR2">%9$s</xliff:g>, <xliff:g id="TIME2">%10$s</xliff:g>"</string>
+    <string name="abbrev_month_day_year">"<xliff:g id="MONTH">%b</xliff:g> <xliff:g id="DAY">%-d</xliff:g>, <xliff:g id="YEAR">%Y</xliff:g>"</string>
+    <!-- no translation found for abbrev_month_year (5966980891147982768) -->
+    <skip />
+    <!-- no translation found for abbrev_month_day (3156047263406783231) -->
+    <skip />
+    <!-- no translation found for abbrev_month (7304935052615731208) -->
+    <skip />
+    <string name="day_of_week_long_sunday">"Zondag"</string>
+    <string name="day_of_week_long_monday">"Maandag"</string>
+    <string name="day_of_week_long_tuesday">"Dinsdag"</string>
+    <string name="day_of_week_long_wednesday">"Woensdag"</string>
+    <string name="day_of_week_long_thursday">"Donderdag"</string>
+    <string name="day_of_week_long_friday">"Vrijdag"</string>
+    <string name="day_of_week_long_saturday">"Zaterdag"</string>
+    <string name="day_of_week_medium_sunday">"Zo"</string>
+    <string name="day_of_week_medium_monday">"Ma"</string>
+    <string name="day_of_week_medium_tuesday">"Di"</string>
+    <string name="day_of_week_medium_wednesday">"Wo"</string>
+    <string name="day_of_week_medium_thursday">"Do"</string>
+    <string name="day_of_week_medium_friday">"Vr"</string>
+    <string name="day_of_week_medium_saturday">"Za"</string>
+    <string name="day_of_week_short_sunday">"Zo"</string>
+    <string name="day_of_week_short_monday">"Ma"</string>
+    <string name="day_of_week_short_tuesday">"Di"</string>
+    <string name="day_of_week_short_wednesday">"Wo"</string>
+    <string name="day_of_week_short_thursday">"Do"</string>
+    <string name="day_of_week_short_friday">"Vr"</string>
+    <string name="day_of_week_short_saturday">"Za"</string>
+    <string name="day_of_week_shorter_sunday">"Zo"</string>
+    <string name="day_of_week_shorter_monday">"M"</string>
+    <string name="day_of_week_shorter_tuesday">"Di"</string>
+    <string name="day_of_week_shorter_wednesday">"W"</string>
+    <string name="day_of_week_shorter_thursday">"Do"</string>
+    <string name="day_of_week_shorter_friday">"V"</string>
+    <string name="day_of_week_shorter_saturday">"Za"</string>
+    <string name="day_of_week_shortest_sunday">"Z"</string>
+    <string name="day_of_week_shortest_monday">"M"</string>
+    <string name="day_of_week_shortest_tuesday">"D"</string>
+    <string name="day_of_week_shortest_wednesday">"W"</string>
+    <string name="day_of_week_shortest_thursday">"D"</string>
+    <string name="day_of_week_shortest_friday">"V"</string>
+    <string name="day_of_week_shortest_saturday">"Z"</string>
+    <string name="month_long_january">"Januari"</string>
+    <string name="month_long_february">"Februari"</string>
+    <string name="month_long_march">"Maart"</string>
+    <string name="month_long_april">"April"</string>
+    <string name="month_long_may">"Mei"</string>
+    <string name="month_long_june">"Juni"</string>
+    <string name="month_long_july">"Juli"</string>
+    <string name="month_long_august">"Augustus"</string>
+    <string name="month_long_september">"September"</string>
+    <string name="month_long_october">"Oktober"</string>
+    <string name="month_long_november">"November"</string>
+    <string name="month_long_december">"December"</string>
+    <string name="month_medium_january">"Jan"</string>
+    <string name="month_medium_february">"Feb"</string>
+    <string name="month_medium_march">"Mrt"</string>
+    <string name="month_medium_april">"Apr"</string>
+    <string name="month_medium_may">"Mei"</string>
+    <string name="month_medium_june">"Jun"</string>
+    <string name="month_medium_july">"Jul"</string>
+    <string name="month_medium_august">"Aug"</string>
+    <string name="month_medium_september">"Sep"</string>
+    <string name="month_medium_october">"Okt"</string>
+    <string name="month_medium_november">"Nov"</string>
+    <string name="month_medium_december">"Dec"</string>
+    <string name="month_shortest_january">"J"</string>
+    <string name="month_shortest_february">"V"</string>
+    <string name="month_shortest_march">"M"</string>
+    <string name="month_shortest_april">"A"</string>
+    <string name="month_shortest_may">"M"</string>
+    <string name="month_shortest_june">"J"</string>
+    <string name="month_shortest_july">"J"</string>
+    <string name="month_shortest_august">"A"</string>
+    <string name="month_shortest_september">"S"</string>
+    <string name="month_shortest_october">"O"</string>
+    <string name="month_shortest_november">"N"</string>
+    <string name="month_shortest_december">"D"</string>
+    <string name="elapsed_time_short_format_mm_ss">"<xliff:g id="MINUTES">%1$02d</xliff:g>:<xliff:g id="SECONDS">%2$02d</xliff:g>"</string>
+    <string name="elapsed_time_short_format_h_mm_ss">"<xliff:g id="HOURS">%1$d</xliff:g>:<xliff:g id="MINUTES">%2$02d</xliff:g>:<xliff:g id="SECONDS">%3$02d</xliff:g>"</string>
+    <string name="selectAll">"Alles selecteren"</string>
+    <string name="selectText">"Tekst selecteren"</string>
+    <string name="stopSelectingText">"Stoppen met tekst selecteren"</string>
+    <string name="cut">"Knippen"</string>
+    <string name="cutAll">"Alles knippen"</string>
+    <string name="copy">"Kopiëren"</string>
+    <string name="copyAll">"Alles kopiëren"</string>
+    <string name="paste">"Plakken"</string>
+    <string name="copyUrl">"URL kopiëren"</string>
+    <string name="inputMethod">"Invoermethode"</string>
+    <string name="addToDictionary">"%s\' toevoegen aan woordenboek"</string>
+    <string name="editTextMenuTitle">"Tekst bewerken"</string>
+    <string name="low_internal_storage_view_title">"Weinig ruimte"</string>
+    <string name="low_internal_storage_view_text">"Opslagruimte van telefoon raakt op."</string>
+    <string name="ok">"OK"</string>
+    <string name="cancel">"Annuleren"</string>
+    <string name="yes">"OK"</string>
+    <string name="no">"Annuleren"</string>
+    <string name="dialog_alert_title">"Let op"</string>
+    <string name="capital_on">"AAN"</string>
+    <string name="capital_off">"UIT"</string>
+    <string name="whichApplication">"Actie voltooien met"</string>
+    <string name="alwaysUse">"Standaard gebruiken voor deze actie."</string>
+    <string name="clearDefaultHintMsg">"Wis standaardinstelling via startscherm: \'Instellingen\' &gt; \'Toepassingen\' &gt; \'Toepassingen beheren\'."</string>
+    <string name="chooseActivity">"Een actie selecteren"</string>
+    <string name="noApplications">"Geen enkele toepassing kan deze actie uitvoeren."</string>
+    <string name="aerr_title">"Helaas!"</string>
+    <string name="aerr_application">"De toepassing <xliff:g id="APPLICATION">%1$s</xliff:g> (proces <xliff:g id="PROCESS">%2$s</xliff:g>) is onverwachts gestopt. Probeer het opnieuw."</string>
+    <string name="aerr_process">"Het proces <xliff:g id="PROCESS">%1$s</xliff:g> is onverwachts gestopt. Probeer het opnieuw."</string>
+    <string name="anr_title">"Helaas!"</string>
+    <string name="anr_activity_application">"Activiteit <xliff:g id="ACTIVITY">%1$s</xliff:g> (in toepassing <xliff:g id="APPLICATION">%2$s</xliff:g>) reageert niet."</string>
+    <string name="anr_activity_process">"Activiteit <xliff:g id="ACTIVITY">%1$s</xliff:g> (in proces <xliff:g id="PROCESS">%2$s</xliff:g>) reageert niet."</string>
+    <string name="anr_application_process">"Toepassing <xliff:g id="APPLICATION">%1$s</xliff:g> (in proces <xliff:g id="PROCESS">%2$s</xliff:g>) reageert niet."</string>
+    <string name="anr_process">"Proces <xliff:g id="PROCESS">%1$s</xliff:g> reageert niet."</string>
+    <string name="force_close">"Nu sluiten"</string>
+    <string name="wait">"Wachten"</string>
+    <string name="debug">"Foutopsporing"</string>
+    <string name="sendText">"Selecteer een actie voor tekst"</string>
+    <string name="volume_ringtone">"Belvolume"</string>
+    <string name="volume_music">"Mediavolume"</string>
+    <string name="volume_music_hint_playing_through_bluetooth">"Afspelen via Bluetooth"</string>
+    <string name="volume_call">"Volume inkomende oproep"</string>
+    <string name="volume_bluetooth_call">"Volume tijdens gesprek in Bluetooth-modus"</string>
+    <string name="volume_alarm">"Alarmvolume"</string>
+    <string name="volume_notification">"Meldingsvolume"</string>
+    <string name="volume_unknown">"Volume"</string>
+    <string name="ringtone_default">"Standaardbeltoon"</string>
+    <string name="ringtone_default_with_actual">"Standaardbeltoon (<xliff:g id="ACTUAL_RINGTONE">%1$s</xliff:g>)"</string>
+    <string name="ringtone_silent">"Stil"</string>
+    <string name="ringtone_picker_title">"Beltonen"</string>
+    <string name="ringtone_unknown">"Onbekende beltoon"</string>
+  <plurals name="wifi_available">
+    <item quantity="one">"Wi-Fi-netwerk beschikbaar"</item>
+    <item quantity="other">"Wi-Fi-netwerken beschikbaar"</item>
+  </plurals>
+  <plurals name="wifi_available_detailed">
+    <item quantity="one">"Open Wi-Fi-netwerk beschikbaar"</item>
+    <item quantity="other">"Open Wi-Fi-netwerken beschikbaar"</item>
+  </plurals>
+    <string name="select_character">"Teken invoegen"</string>
+    <string name="sms_control_default_app_name">"Onbekende toepassing"</string>
+    <string name="sms_control_title">"SMS-berichten verzenden"</string>
+    <string name="sms_control_message">"Er wordt een groot aantal SMS-berichten verzonden. Selecteer \'OK\' om door te gaan of \'Annuleren\' om de verzending te stoppen."</string>
+    <string name="sms_control_yes">"OK"</string>
+    <string name="sms_control_no">"Annuleren"</string>
+    <string name="date_time_set">"Instellen"</string>
+    <string name="default_permission_group">"Standaard"</string>
+    <string name="no_permissions">"Geen machtigingen vereist"</string>
+    <string name="perms_hide"><b>"Verbergen"</b></string>
+    <string name="perms_show_all"><b>"Alles weergeven"</b></string>
+    <string name="googlewebcontenthelper_loading">"Laden..."</string>
+    <string name="usb_storage_title">"USB-verbinding"</string>
+    <string name="usb_storage_message">"U heeft uw telefoon via USB op uw computer aangesloten. Selecteer \'Koppelen\' als u bestanden tussen uw computer en de SD-kaart van uw telefoon wilt kopiëren."</string>
+    <string name="usb_storage_button_mount">"Koppelen"</string>
+    <string name="usb_storage_button_unmount">"Niet koppelen"</string>
+    <string name="usb_storage_error_message">"Er is een probleem bij het gebruik van uw SD-kaart voor USB-opslag."</string>
+    <string name="usb_storage_notification_title">"USB-verbinding"</string>
+    <string name="usb_storage_notification_message">"Selecteer dit om bestanden naar/van uw computer te kopiëren."</string>
+    <string name="usb_storage_stop_notification_title">"USB-opslag uitschakelen"</string>
+    <string name="usb_storage_stop_notification_message">"Selecteer dit om USB-opslag uit te schakelen."</string>
+    <string name="usb_storage_stop_title">"USB-opslag uitschakelen"</string>
+    <string name="usb_storage_stop_message">"Voordat u de USB-opslag uitschakelt, moet u de koppeling met de USB-host verbreken. Selecteer \'Uitschakelen\' om USB-opslag uit te schakelen."</string>
+    <string name="usb_storage_stop_button_mount">"Uitschakelen"</string>
+    <string name="usb_storage_stop_button_unmount">"Annuleren"</string>
+    <string name="usb_storage_stop_error_message">"Er is een probleem opgetreden tijdens het uitschakelen van USB-opslag. Controleer of u de USB-host heeft losgekoppeld en probeer het opnieuw."</string>
+    <string name="extmedia_format_title">"SD-kaart formatteren"</string>
+    <string name="extmedia_format_message">"Weet u zeker dat u de SD-kaart wilt formatteren? Alle gegevens op uw kaart gaan dan verloren."</string>
+    <string name="extmedia_format_button_format">"Formatteren"</string>
+    <string name="select_input_method">"Invoermethode selecteren"</string>
+    <string name="fast_scroll_alphabet">" ABCDEFGHIJKLMNOPQRSTUVWXYZ"</string>
+    <string name="fast_scroll_numeric_alphabet">" 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"</string>
+    <string name="candidates_style"><u>"kandidaten"</u></string>
+    <string name="ext_media_checking_notification_title">"SD-kaart voorbereiden"</string>
+    <string name="ext_media_checking_notification_message">"Controleren op fouten"</string>
+    <string name="ext_media_nofs_notification_title">"Lege SD-kaart"</string>
+    <string name="ext_media_nofs_notification_message">"De SD-kaart is leeg of gebruikt een niet-ondersteund bestandssysteem."</string>
+    <string name="ext_media_unmountable_notification_title">"Beschadigde SD-kaart"</string>
+    <string name="ext_media_unmountable_notification_message">"De SD-kaart is beschadigd. U moet de kaart mogelijk opnieuw formatteren."</string>
+    <string name="ext_media_badremoval_notification_title">"SD-kaart onverwachts verwijderd"</string>
+    <string name="ext_media_badremoval_notification_message">"Ontkoppel de SD-kaart voordat u deze verwijdert om gegevensverlies te voorkomen."</string>
+    <string name="ext_media_safe_unmount_notification_title">"De SD-kaart kan veilig worden verwijderd"</string>
+    <string name="ext_media_safe_unmount_notification_message">"De SD-kaart kan nu veilig worden verwijderd."</string>
+    <string name="ext_media_nomedia_notification_title">"SD-kaart is verwijderd"</string>
+    <string name="ext_media_nomedia_notification_message">"De SD-kaart is verwijderd. Plaats een nieuwe SD-kaart om de opslagcapaciteit van uw apparaat te vergroten."</string>
+    <string name="activity_list_empty">"Geen overeenkomende activiteiten gevonden"</string>
+    <string name="permlab_pkgUsageStats">"gebruiksstatistieken van component bijwerken"</string>
+    <string name="permdesc_pkgUsageStats">"Hiermee kunnen verzamelde gebruiksstatistieken van een component worden gewijzigd. Niet voor gebruik door normale toepassingen."</string>
+    <!-- no translation found for tutorial_double_tap_to_zoom_message_short (1311810005957319690) -->
+    <skip />
+    <!-- no translation found for gadget_host_error_inflating (2613287218853846830) -->
+    <skip />
+    <!-- no translation found for ime_action_go (8320845651737369027) -->
+    <skip />
+    <!-- no translation found for ime_action_search (658110271822807811) -->
+    <skip />
+    <!-- no translation found for ime_action_send (2316166556349314424) -->
+    <skip />
+    <!-- no translation found for ime_action_next (3138843904009813834) -->
+    <skip />
+    <!-- no translation found for ime_action_default (2840921885558045721) -->
+    <skip />
+</resources>
diff --git a/core/res/res/values-pl/strings.xml b/core/res/res/values-pl/strings.xml
new file mode 100644
index 0000000..641d335
--- /dev/null
+++ b/core/res/res/values-pl/strings.xml
@@ -0,0 +1,815 @@
+<?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.
+-->
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="byteShort">"B"</string>
+    <string name="kilobyteShort">"KB"</string>
+    <string name="megabyteShort">"MB"</string>
+    <string name="gigabyteShort">"GB"</string>
+    <string name="terabyteShort">"TB"</string>
+    <string name="petabyteShort">"PB"</string>
+    <string name="untitled">"&lt;bez nazwy&gt;"</string>
+    <string name="ellipsis">"…"</string>
+    <string name="emptyPhoneNumber">"(Brak numeru telefonu)"</string>
+    <string name="unknownName">"(Nieznany)"</string>
+    <string name="defaultVoiceMailAlphaTag">"Poczta głosowa"</string>
+    <string name="defaultMsisdnAlphaTag">"MSISDN1"</string>
+    <string name="mmiError">"Problem z połączeniem lub błędny kod MMI."</string>
+    <string name="serviceEnabled">"Usługa była włączona."</string>
+    <string name="serviceEnabledFor">"Usługa została włączona dla:"</string>
+    <string name="serviceDisabled">"Usługa została wyłączona."</string>
+    <string name="serviceRegistered">"Rejestracja powiodła się."</string>
+    <string name="serviceErased">"Wymazywanie zakończone pomyślnie."</string>
+    <string name="passwordIncorrect">"Błędne hasło."</string>
+    <string name="mmiComplete">"MMI zakończone."</string>
+    <string name="badPin">"Wprowadzony stary kod PIN jest nieprawidłowy."</string>
+    <string name="badPuk">"Wprowadzony kod PUK jest nieprawidłowy."</string>
+    <string name="mismatchPin">"Wprowadzone kody PIN nie są identyczne."</string>
+    <string name="invalidPin">"Wpisz kod PIN o długości od 4 do 8 cyfr."</string>
+    <string name="needPuk">"Karta SIM jest zablokowana kodem PUK. Wprowadź kod PUK, aby odblokować kartę."</string>
+    <string name="needPuk2">"Wprowadź kod PUK2, aby odblokować kartę SIM."</string>
+    <string name="ClipMmi">"Identyfikator dzwoniącego przy połączeniach przychodzących"</string>
+    <string name="ClirMmi">"Identyfikator dzwoniącego przy połączeniach wychodzących"</string>
+    <string name="CfMmi">"Przekierowania połączeń"</string>
+    <string name="CwMmi">"Połączenia oczekujące"</string>
+    <string name="BaMmi">"Blokada dzwonienia"</string>
+    <string name="PwdMmi">"Zmiana hasła"</string>
+    <string name="PinMmi">"Zmiana kodu PIN"</string>
+    <string name="CLIRDefaultOnNextCallOn">"Identyfikator dzwoniącego ustawiony jest domyślnie na „zastrzeżony”. Następne połączenie: zastrzeżony"</string>
+    <string name="CLIRDefaultOnNextCallOff">"Identyfikator dzwoniącego ustawiony jest domyślnie na „zastrzeżony”. Następne połączenie: nie zastrzeżony"</string>
+    <string name="CLIRDefaultOffNextCallOn">"Identyfikator dzwoniącego ustawiony jest domyślnie na „nie zastrzeżony”. Następne połączenie: zastrzeżony"</string>
+    <string name="CLIRDefaultOffNextCallOff">"Identyfikator dzwoniącego ustawiony jest domyślnie na „nie zastrzeżony”. Następne połączenie: nie zastrzeżony"</string>
+    <string name="serviceNotProvisioned">"Usługa nie jest świadczona."</string>
+    <string name="CLIRPermanent">"Nie można zmienić ustawienia identyfikatora dzwoniącego."</string>
+    <string name="serviceClassVoice">"Głos"</string>
+    <string name="serviceClassData">"Dane"</string>
+    <string name="serviceClassFAX">"FAKS"</string>
+    <string name="serviceClassSMS">"SMS"</string>
+    <string name="serviceClassDataAsync">"Dane asynchroniczne"</string>
+    <string name="serviceClassDataSync">"Synchronizacja"</string>
+    <string name="serviceClassPacket">"Pakiet"</string>
+    <string name="serviceClassPAD">"PAD"</string>
+    <string name="cfTemplateNotForwarded">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: nieprzekierowane"</string>
+    <string name="cfTemplateForwarded">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
+    <string name="cfTemplateForwardedTime">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> po <xliff:g id="TIME_DELAY">{2}</xliff:g> sekundach"</string>
+    <string name="cfTemplateRegistered">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: nieprzekierowane"</string>
+    <string name="cfTemplateRegisteredTime">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: nieprzekierowane"</string>
+    <string name="httpErrorOk">"OK"</string>
+    <string name="httpError">"Strona sieci Web zawiera błąd."</string>
+    <string name="httpErrorLookup">"Nie można odszukać adresu URL."</string>
+    <string name="httpErrorUnsupportedAuthScheme">"Schemat uwierzytelniania strony nie jest obsługiwany."</string>
+    <string name="httpErrorAuth">"Nieudane uwierzytelnianie."</string>
+    <string name="httpErrorProxyAuth">"Autoryzacja przez serwer proxy zakończyła się niepowodzeniem."</string>
+    <string name="httpErrorConnect">"Nieudane połączenie z serwerem."</string>
+    <string name="httpErrorIO">"Nie udało się połączyć z serwerem. Spróbuj ponownie później."</string>
+    <string name="httpErrorTimeout">"Zbyt długi czas oczekiwania na połączenie z serwerem."</string>
+    <string name="httpErrorRedirectLoop">"Strona zawiera zbyt wiele przekierowań do serwerów."</string>
+    <string name="httpErrorUnsupportedScheme">"Protokół nie jest obsługiwany"</string>
+    <string name="httpErrorFailedSslHandshake">"Nie można ustanowić bezpiecznego połączenia."</string>
+    <string name="httpErrorBadUrl">"Nie można otworzyć strony, ponieważ adres URL jest nieprawidłowy."</string>
+    <string name="httpErrorFile">"Nie można uzyskać dostępu do pliku."</string>
+    <string name="httpErrorFileNotFound">"Nie znaleziono żądanego pliku."</string>
+    <string name="httpErrorTooManyRequests">"Zbyt wiele żądań jest przetwarzanych. Spróbuj ponownie później."</string>
+    <string name="contentServiceSync">"Synchronizacja"</string>
+    <string name="contentServiceSyncNotificationTitle">"Synchronizuj"</string>
+    <string name="contentServiceTooManyDeletesNotificationDesc">"Zbyt wiele usuwanych <xliff:g id="CONTENT_TYPE">%s</xliff:g>."</string>
+    <string name="low_memory">"Pamięć telefonu jest pełna! Usuń niektóre pliki, aby zwolnić miejsce."</string>
+    <string name="me">"Ja"</string>
+    <string name="power_dialog">"Opcje telefonu"</string>
+    <string name="silent_mode">"Tryb cichy"</string>
+    <string name="turn_on_radio">"Włącz połączenia bezprzewodowe"</string>
+    <string name="turn_off_radio">"Wyłącz połączenia bezprzewodowe"</string>
+    <string name="screen_lock">"Blokada ekranu"</string>
+    <string name="power_off">"Wyłącz"</string>
+    <string name="shutdown_progress">"Wyłączanie..."</string>
+    <string name="shutdown_confirm">"Telefon zostanie wyłączony"</string>
+    <string name="no_recent_tasks">"Brak ostatnio używanych aplikacji."</string>
+    <string name="global_actions">"Opcje telefonu"</string>
+    <string name="global_action_lock">"Blokada ekranu"</string>
+    <string name="global_action_power_off">"Wyłącz"</string>
+    <string name="global_action_toggle_silent_mode">"Tryb cichy"</string>
+    <string name="global_action_silent_mode_on_status">"Dźwięk jest wyłączony"</string>
+    <string name="global_action_silent_mode_off_status">"Dźwięk jest włączony"</string>
+    <!-- no translation found for global_actions_toggle_airplane_mode (5884330306926307456) -->
+    <skip />
+    <!-- no translation found for global_actions_airplane_mode_on_status (2719557982608919750) -->
+    <skip />
+    <!-- no translation found for global_actions_airplane_mode_off_status (5075070442854490296) -->
+    <skip />
+    <string name="safeMode">"Tryb awaryjny"</string>
+    <!-- no translation found for android_system_label (6577375335728551336) -->
+    <skip />
+    <string name="permgrouplab_costMoney">"Usługi płatne"</string>
+    <string name="permgroupdesc_costMoney">"Pozwól aplikacjom na wykonywanie płatnych operacji."</string>
+    <string name="permgrouplab_messages">"Twoje wiadomości"</string>
+    <string name="permgroupdesc_messages">"Czytanie i zapisywanie wiadomości SMS, e-mail i innych"</string>
+    <string name="permgrouplab_personalInfo">"Informacje osobiste"</string>
+    <string name="permgroupdesc_personalInfo">"Bezpośredni dostęp do kontaktów i kalendarza zapisanych w telefonie."</string>
+    <string name="permgrouplab_location">"Twoje położenie"</string>
+    <string name="permgroupdesc_location">"Monitorowanie fizycznego położenia"</string>
+    <string name="permgrouplab_network">"Połączenia sieciowe"</string>
+    <string name="permgroupdesc_network">"Pozwól aplikacjom na dostęp do różnych funkcji sieci."</string>
+    <string name="permgrouplab_accounts">"Twoje konta Google"</string>
+    <string name="permgroupdesc_accounts">"Uzyskaj dostęp do dostępnych kont Google."</string>
+    <string name="permgrouplab_hardwareControls">"Sterowanie sprzętowe"</string>
+    <string name="permgroupdesc_hardwareControls">"Bezpośredni dostęp do elementów sprzętowych telefonu."</string>
+    <string name="permgrouplab_phoneCalls">"Połączenia telefoniczne"</string>
+    <string name="permgroupdesc_phoneCalls">"Monitorowanie, nagrywanie i przetwarzanie połączeń telefonicznych."</string>
+    <string name="permgrouplab_systemTools">"Narzędzia systemowe"</string>
+    <string name="permgroupdesc_systemTools">"Dostęp i kontrola systemu niższego poziomu."</string>
+    <string name="permgrouplab_developmentTools">"Narzędzia programistyczne"</string>
+    <string name="permgroupdesc_developmentTools">"Funkcje potrzebne jedynie programistom"</string>
+    <string name="permlab_statusBar">"wyłączanie lub zmienianie paska stanu"</string>
+    <string name="permdesc_statusBar">"Pozwala aplikacjom na wyłączenie paska stanu lub dodawanie i usuwanie ikon systemowych."</string>
+    <string name="permlab_expandStatusBar">"rozwijanie/zwijanie paska stanu"</string>
+    <string name="permdesc_expandStatusBar">"Pozwala aplikacji na rozwijanie lub zwijanie paska stanu."</string>
+    <string name="permlab_processOutgoingCalls">"przechwytywanie połączeń wychodzących"</string>
+    <string name="permdesc_processOutgoingCalls">"Pozwala aplikacji na przetwarzanie połączeń wychodzących i zmianę wybieranego numeru. Szkodliwe aplikacje mogą monitorować, przekierowywać lub blokować połączenia wychodzące."</string>
+    <string name="permlab_receiveSms">"odbieranie wiadomości SMS"</string>
+    <string name="permdesc_receiveSms">"Pozwala aplikacjom na odbieranie i przetwarzanie wiadomości SMS. Szkodliwe aplikacje mogą monitorować wiadomości lub usuwać je bez wyświetlania ich użytkownikowi."</string>
+    <string name="permlab_receiveMms">"odbieranie wiadomości MMS"</string>
+    <string name="permdesc_receiveMms">"Pozwala aplikacji na odbieranie i przetwarzanie wiadomości MMS. Szkodliwe aplikacje mogą monitorować wiadomości lub usuwać je bez pokazywania ich użytkownikowi."</string>
+    <string name="permlab_sendSms">"wysyłanie wiadomości SMS"</string>
+    <string name="permdesc_sendSms">"Pozwól aplikacjom na wysyłanie wiadomości SMS. Szkodliwe aplikacje mogą generować koszty, wysyłając wiadomości bez wiedzy użytkownika."</string>
+    <string name="permlab_readSms">"czytanie wiadomości SMS lub MMS"</string>
+    <string name="permdesc_readSms">"Pozwala aplikacji na czytanie wiadomości SMS zapisanych w telefonie lub na karcie SIM. Szkodliwe aplikacje mogą czytać poufne wiadomości."</string>
+    <string name="permlab_writeSms">"edytowanie wiadomości SMS lub MMS"</string>
+    <string name="permdesc_writeSms">"Pozwala aplikacji na zapisywanie wiadomości SMS przechowywanych w telefonie lub na karcie SIM. Szkodliwe aplikacje mogą usunąć wiadomości."</string>
+    <string name="permlab_receiveWapPush">"odbieranie WAP"</string>
+    <string name="permdesc_receiveWapPush">"Pozwala aplikacjom na odbieranie i przetwarzanie wiadomości WAP. Szkodliwe aplikacje mogą monitorować wiadomości lub usuwać je bez wyświetlania ich użytkownikowi."</string>
+    <string name="permlab_getTasks">"pobieranie uruchomionych aplikacji"</string>
+    <string name="permdesc_getTasks">"Umożliwia aplikacji pobieranie informacji na temat obecnie i ostatnio uruchomionych zadań. Może pozwolić szkodliwym aplikacjom na uzyskanie prywatnych informacji na temat innych aplikacji."</string>
+    <string name="permlab_reorderTasks">"zmienianie porządku uruchomionych aplikacji"</string>
+    <string name="permdesc_reorderTasks">"Pozwala aplikacji na przenoszenie zadań z tła na pierwszy plan. Szkodliwe aplikacje mogą wymusić działanie pierwszoplanowe bez kontroli użytkownika."</string>
+    <string name="permlab_setDebugApp">"włączenie debugowania aplikacji"</string>
+    <string name="permdesc_setDebugApp">"Pozwala aplikacji na włączenie debugowania innej aplikacji. Szkodliwe aplikacje mogą to wykorzystać do wyłączenia innych programów."</string>
+    <string name="permlab_changeConfiguration">"zmienianie ustawień interfejsu użytkownika"</string>
+    <string name="permdesc_changeConfiguration">"Pozwala aplikacji zmieniać bieżącą konfigurację, na przykład lokalny lub globalny rozmiar czcionki."</string>
+    <string name="permlab_restartPackages">"resetowanie innych aplikacji"</string>
+    <string name="permdesc_restartPackages">"Pozwala aplikacji na wymuszenie ponownego uruchomienia innych aplikacji."</string>
+    <string name="permlab_setProcessForeground">"zapobieganie zatrzymaniu"</string>
+    <string name="permdesc_setProcessForeground">"Pozwala aplikacji na uruchamianie dowolnego procesu na pierwszym planie tak, że nie można go wyłączyć. Nigdy nie powinno być potrzebne normalnym aplikacjom."</string>
+    <string name="permlab_forceBack">"wymuszanie zamknięcia aplikacji"</string>
+    <string name="permdesc_forceBack">"Pozwala aplikacji na wymuszenie zamknięcia i cofnięcia dowolnej operacji działającej na pierwszym planie. Nigdy nie powinno być potrzebne normalnym aplikacjom."</string>
+    <string name="permlab_dump">"pobieranie informacji o wewnętrznym stanie systemu"</string>
+    <string name="permdesc_dump">"Pozwala aplikacjom na pobieranie informacji o wewnętrznym stanie systemu. Szkodliwe aplikacje mogą pobrać szeroką gamę osobistych i zabezpieczonych informacji, które normalnie nie powinny im być nigdy potrzebne."</string>
+    <string name="permlab_addSystemService">"publikowanie usług niskiego poziomu"</string>
+    <string name="permdesc_addSystemService">"Pozwala aplikacji na publikowanie własnych usług systemowych niskiego poziomu. Szkodliwe aplikacje mogą przejąć kontrolę nad systemem oraz wykraść lub uszkodzić znajdujące się w nim dane."</string>
+    <string name="permlab_runSetActivityWatcher">"monitorowanie i kontrolowanie wszystkich uruchamianych aplikacji"</string>
+    <string name="permdesc_runSetActivityWatcher">"Pozwala aplikacji na monitorowanie i kontrolowanie sposobu, w jaki w systemie uruchamiane są różne działania. Szkodliwe aplikacje mogą całkowicie przejąć system. Te uprawnienia potrzebne są tylko programistom, nigdy w przypadku normalnego wykorzystywania telefonu."</string>
+    <string name="permlab_broadcastPackageRemoved">"wysyłanie transmisji informującej o usuniętym pakiecie"</string>
+    <string name="permdesc_broadcastPackageRemoved">"Pozwala aplikacji na wysyłanie powiadomienia, że pakiet aplikacji został usunięty. Szkodliwe aplikacje mogą z niego skorzystać w celu wyłączania innych działających aplikacji."</string>
+    <string name="permlab_broadcastSmsReceived">"wysyłanie transmisji otrzymanych w wiadomości SMS"</string>
+    <string name="permdesc_broadcastSmsReceived">"Pozwala aplikacji na wysyłanie powiadomienia, że została odebrana wiadomość SMS. Szkodliwe aplikacje mogą to wykorzystać do fałszowania przychodzących wiadomości SMS."</string>
+    <string name="permlab_broadcastWapPush">"wysyłanie transmisji informującej o otrzymaniu wiadomości WAP-PUSH"</string>
+    <string name="permdesc_broadcastWapPush">"Pozwala aplikacji na nadanie powiadomienia o otrzymaniu wiadomości WAP PUSH. Szkodliwe aplikacje mogą to wykorzystać do fałszowania potwierdzenia odbioru wiadomości MMS lub do niezauważalnego podmieniania zawartości dowolnej strony internetowej jej szkodliwymi wariantami."</string>
+    <string name="permlab_setProcessLimit">"ograniczanie liczby uruchomionych procesów"</string>
+    <string name="permdesc_setProcessLimit">"Pozwala aplikacji na kontrolowanie maksymalnej liczby uruchamianych procesów. Nigdy nie wykorzystywane przez normalne aplikacje."</string>
+    <string name="permlab_setAlwaysFinish">"zamykanie wszystkich aplikacji działających w tle"</string>
+    <string name="permdesc_setAlwaysFinish">"Pozwala aplikacji na kontrolowanie, czy czynności są zawsze kończone, kiedy zaczynają działać w tle. Nigdy nie jest potrzebne normalnym aplikacjom."</string>
+    <string name="permlab_fotaUpdate">"automatyczne instalowanie aktualizacji systemu"</string>
+    <string name="permdesc_fotaUpdate">"Pozwala aplikacji na otrzymywanie powiadomień o oczekujących aktualizacjach systemu i uruchamianie ich instalacji. Szkodliwe aplikacje mogą to wykorzystać do uszkodzenia systemu za pomocą nieuwierzytelnionych aktualizacji lub ogólnie wpłynąć na proces aktualizowania."</string>
+    <string name="permlab_batteryStats">"zmienianie statystyk dotyczących baterii"</string>
+    <string name="permdesc_batteryStats">"Pozwala na zmianę zebranych statystyk dotyczących baterii. Nie do wykorzystania przez normalne aplikacje."</string>
+    <string name="permlab_internalSystemWindow">"wyświetlanie nieuwierzytelnionych okien"</string>
+    <string name="permdesc_internalSystemWindow">"Pozwala na tworzenie okien, które przeznaczone są do wykorzystania przez wewnętrzny interfejs użytkownika systemu. Nie do wykorzystania przez normalne aplikacje."</string>
+    <string name="permlab_systemAlertWindow">"wyświetlanie ostrzeżeń systemowych"</string>
+    <string name="permdesc_systemAlertWindow">"Pozwala aplikacji na pokazywanie okien alarmów systemowych. Szkodliwe aplikacje mogą przejąć kontrolę nad całym ekranem telefonu."</string>
+    <string name="permlab_setAnimationScale">"zmienianie ogólnej prędkości animacji"</string>
+    <string name="permdesc_setAnimationScale">"Pozwala aplikacji na zmianę ogólnej prędkości animacji (szybsze lub wolniejsze animacje) w dowolnym momencie."</string>
+    <string name="permlab_manageAppTokens">"zarządzanie tokenami aplikacji"</string>
+    <string name="permdesc_manageAppTokens">"Pozwala aplikacjom na tworzenie własnych tokenów i zarządzanie nimi z pominięciem zwykłego porządku warstw. Nigdy nie powinno być potrzebne normalnym aplikacjom."</string>
+    <string name="permlab_injectEvents">"naciskanie klawiszy oraz przycisków sterujących"</string>
+    <string name="permdesc_injectEvents">"Pozwala aplikacjom na dostarczanie własnych zdarzeń wprowadzania danych (naciśnięcie klawisza itp.) do innych aplikacji. Szkodliwe aplikacje mogą to wykorzystać do przejęcia kontroli nad telefonem."</string>
+    <string name="permlab_readInputState">"zapamiętywanie wpisywanych znaków oraz wykonywanych czynności"</string>
+    <string name="permdesc_readInputState">"Pozwala aplikacjom na śledzenie naciskanych klawiszy, nawet podczas pracy z innym programem (na przykład podczas wpisywania hasła). Nigdy nie powinno być potrzebne normalnym aplikacjom."</string>
+    <string name="permlab_bindInputMethod">"tworzenie powiązania z metodą wejściową"</string>
+    <string name="permdesc_bindInputMethod">"Pozwala na tworzenie powiązania z interfejsem najwyższego poziomu metody wejściowej. To uprawnienie nie powinno być nigdy wymagane przez zwykłe aplikacje."</string>
+    <string name="permlab_setOrientation">"zmienianie orientacji ekranu"</string>
+    <string name="permdesc_setOrientation">"Pozwala aplikacji na zmianę orientacji ekranu w dowolnym momencie. Nigdy nie powinno być potrzeby stosowania w normalnych aplikacjach."</string>
+    <string name="permlab_signalPersistentProcesses">"wysyłanie sygnałów systemu Linux do aplikacji"</string>
+    <string name="permdesc_signalPersistentProcesses">"Pozwala aplikacjom żądać, aby dostarczany sygnał był wysyłany do wszystkich trwających procesów."</string>
+    <string name="permlab_persistentActivity">"sprawianie, że aplikacja jest cały czas uruchomiona"</string>
+    <string name="permdesc_persistentActivity">"Dzięki temu uprawnieniu część elementów aplikacji może być trwała, przez co system nie może ich wykorzystać do innych aplikacji."</string>
+    <string name="permlab_deletePackages">"usuwanie aplikacji"</string>
+    <string name="permdesc_deletePackages">"Pozwala aplikacjom na usuwanie pakietów systemu Android. Szkodliwe aplikacje mogą wykorzystać to do usuwania ważnych aplikacji."</string>
+    <string name="permlab_clearAppUserData">"usuwanie danych innych aplikacji"</string>
+    <string name="permdesc_clearAppUserData">"Pozwala aplikacji na czyszczenie danych użytkowników."</string>
+    <string name="permlab_deleteCacheFiles">"usuwanie pamięci podręcznej innych aplikacji"</string>
+    <string name="permdesc_deleteCacheFiles">"Pozwala aplikacji na usuwanie plików z pamięci podręcznej."</string>
+    <string name="permlab_getPackageSize">"mierzenie rozmiaru pamięci aplikacji"</string>
+    <string name="permdesc_getPackageSize">"Pozwala aplikacji na pobieranie własnego kodu, danych oraz rozmiarów pamięci podręcznej"</string>
+    <string name="permlab_installPackages">"bezpośrednie instalowanie aplikacji"</string>
+    <string name="permdesc_installPackages">"Pozwala aplikacji na instalowanie nowych lub zaktualizowanych pakietów systemu Android. Szkodliwe aplikacje mogą to wykorzystać, aby dodać nowe aplikacje z arbitralnie nadanymi rozległymi uprawnieniami."</string>
+    <string name="permlab_clearAppCache">"usuwanie wszystkich danych aplikacji z pamięci podręcznej"</string>
+    <string name="permdesc_clearAppCache">"Pozwala aplikacji na zwalnianie pamięci telefonu przez usuwanie plików z katalogu pamięci podręcznej aplikacji. Dostęp jest bardzo ograniczony, z reguły do procesów systemu."</string>
+    <string name="permlab_readLogs">"czytanie plików dziennika systemu"</string>
+    <string name="permdesc_readLogs">"Umożliwia aplikacji czytanie różnych plików dziennika systemowego. Pozwala to na uzyskanie ogólnych informacji o czynnościach wykonywanych w telefonie, ale bez ujawniania danych osobowych lub osobistych informacji."</string>
+    <string name="permlab_diagnostic">"czytanie/zapisywanie w zasobach należących do diagnostyki"</string>
+    <string name="permdesc_diagnostic">"Pozwala aplikacji na czytanie i zapisywanie we wszystkich zasobach posiadanych przez diagnozowaną grupę, jak na przykład pliki w katalogu /dev. Może to potencjalnie wpłynąć na stabilność i bezpieczeństwo systemu. Powinno być wykorzystywane TYLKO w celach diagnozowania sprzętu przez producenta lub operatora."</string>
+    <string name="permlab_changeComponentState">"włączanie lub wyłączanie składników aplikacji"</string>
+    <string name="permdesc_changeComponentState">"Pozwala aplikacji na włączenie lub wyłączenie składnika innej aplikacji. Szkodliwe aplikacje mogą wykorzystać to uprawnienie do wyłączenia ważnych funkcji telefonu. Przy włączaniu uprawnienia należy zachować ostrożność, ponieważ istnieje możliwość wprowadzenia składników aplikacji w stan nieużywalności, niespójności lub niestabilności."</string>
+    <string name="permlab_setPreferredApplications">"ustawianie preferowanych aplikacji"</string>
+    <string name="permdesc_setPreferredApplications">"Umożliwia aplikacji zmianę preferowanych programów użytkownika. Może to pozwolić szkodliwym aplikacjom na niezauważalną podmianę uruchamianych programów, aby zbierać prywatne dane użytkownika."</string>
+    <string name="permlab_writeSettings">"modyfikowanie ogólnych ustawień systemu"</string>
+    <string name="permdesc_writeSettings">"Pozwala aplikacji na zmianę danych ustawień systemowych. Szkodliwe aplikacje mogą uszkodzić konfigurację systemu."</string>
+    <string name="permlab_writeSecureSettings">"modyfikowanie ustawień systemu dotyczących zabezpieczeń"</string>
+    <string name="permdesc_writeSecureSettings">"Pozwala aplikacji na modyfikowanie danych ustawień zabezpieczeń systemu. To uprawnienie nie jest wykorzystywane przez normalne aplikacje."</string>
+    <string name="permlab_writeGservices">"zmienianie mapy usług Google"</string>
+    <string name="permdesc_writeGservices">"Pozwala aplikacji na modyfikowanie mapy usług Google. Nie wykorzystywane przez normalne aplikacje."</string>
+    <string name="permlab_receiveBootCompleted">"automatyczne uruchamianie podczas uruchamiania urządzenia"</string>
+    <string name="permdesc_receiveBootCompleted">"Pozwala aplikacji na samoczynne uruchamianie zaraz po zakończeniu uruchamiania systemu. Może to spowodować, że telefon będzie się dłużej uruchamiał oraz może ogólnie spowolnić działanie urządzenia, ponieważ aplikacja będzie cały czas uruchomiona."</string>
+    <string name="permlab_broadcastSticky">"wysyłanie transmisji trwałej"</string>
+    <string name="permdesc_broadcastSticky">"Pozwala aplikacji na wysyłanie transmisji trwałych, które pozostają aktywne po zakończeniu połączenia. Szkodliwe aplikacje mogą spowolnić lub zdestabilizować telefon, przez wymuszenie zbyt dużego zużycia pamięci."</string>
+    <string name="permlab_readContacts">"czytanie danych kontaktów"</string>
+    <string name="permdesc_readContacts">"Pozwala aplikacji na czytanie wszystkich danych kontaktowych (adresowych) zapisanych w telefonie. Szkodliwe aplikacje mogą to wykorzystać, aby wysyłać dane użytkownika do innych ludzi."</string>
+    <string name="permlab_writeContacts">"zapisywanie danych kontaktowych"</string>
+    <string name="permdesc_writeContacts">"Pozwala aplikacji na zmianę danych kontaktowych (adresowych) zapisanych w telefonie. Szkodliwe aplikacje mogą to wykorzystać, aby usunąć lub zmienić dane kontaktowe."</string>
+    <string name="permlab_writeOwnerData">"zapisywanie danych właściciela"</string>
+    <string name="permdesc_writeOwnerData">"Pozwala aplikacji na zmianę danych właściciela zapisanych w telefonie. Szkodliwe aplikacje mogą to wykorzystać, aby wymazać lub zmienić dane właściciela."</string>
+    <string name="permlab_readOwnerData">"czytanie danych właściciela"</string>
+    <string name="permdesc_readOwnerData">"Pozwala aplikacji na czytanie danych właściciela zapisanych w telefonie. Szkodliwe aplikacje mogą to wykorzystać do odczytania danych właściciela."</string>
+    <string name="permlab_readCalendar">"czytanie danych kalendarza"</string>
+    <string name="permdesc_readCalendar">"Pozwala aplikacji na odczytywanie wszystkich wydarzeń z kalendarza, zapisanych w telefonie. Szkodliwe aplikacje mogą to wykorzystać do rozsyłania wydarzeń z kalendarza do innych ludzi."</string>
+    <string name="permlab_writeCalendar">"zapisywanie danych kalendarza"</string>
+    <string name="permdesc_writeCalendar">"Pozwala aplikacji na zmianę wydarzeń z kalendarza zapisanych w telefonie. Szkodliwe aplikacje mogą to wykorzystać, aby usunąć lub zmienić dane w kalendarzu."</string>
+    <string name="permlab_accessMockLocation">"udawanie źródeł położenia dla testów"</string>
+    <string name="permdesc_accessMockLocation">"Tworzenie pozorowanych źródeł ustalania położenia dla testów. Szkodliwe aplikacje mogą to wykorzystać, aby zastąpić prawdziwe położenie i/lub stan zwracany przez prawdziwe źródła, takie jak GPS lub dostawcy usługi sieciowej."</string>
+    <string name="permlab_accessLocationExtraCommands">"dostęp do dodatkowych poleceń dostawcy informacji o położeniu"</string>
+    <string name="permdesc_accessLocationExtraCommands">"Dostęp do dodatkowych poleceń dostawcy informacji o położeniu. Szkodliwe aplikacje mogą go wykorzystać, aby wpływać na działanie urządzenia GPS lub innych źródeł ustalania położenia."</string>
+    <string name="permlab_accessFineLocation">"dokładne położenie (GPS)"</string>
+    <string name="permdesc_accessFineLocation">"Uzyskiwanie dostępu do dokładnych źródeł ustalania położenia w telefonie, takich jak system GPS, tam, gdzie są one dostępne. Szkodliwe aplikacje mogą to wykorzystać do określenia położenia użytkownika oraz mogą zużywać więcej energii baterii."</string>
+    <string name="permlab_accessCoarseLocation">"przybliżone ustalanie położenia (oparte o sieć)"</string>
+    <string name="permdesc_accessCoarseLocation">"Dostęp do źródeł, takich jak bazy danych sieci komórkowych, jeśli są dostępne, które pozwalają określić przybliżone położenie telefonu. Szkodliwe aplikacje mogą go wykorzystać do określenia, gdzie w przybliżeniu znajduje się użytkownik."</string>
+    <string name="permlab_accessSurfaceFlinger">"dostęp do usługi SurfaceFlinger"</string>
+    <string name="permdesc_accessSurfaceFlinger">"Pozwala aplikacji na wykorzystanie funkcji niskiego poziomu usługi SurfaceFlinger."</string>
+    <string name="permlab_readFrameBuffer">"czytanie bufora ramki"</string>
+    <string name="permdesc_readFrameBuffer">"Pozwala aplikacji na wykorzystanie odczytanej zawartości bufora ramki."</string>
+    <string name="permlab_modifyAudioSettings">"zmienianie ustawień audio"</string>
+    <string name="permdesc_modifyAudioSettings">"Pozwala aplikacjom na zmianę globalnych ustawień audio, takich jak głośność i routing."</string>
+    <string name="permlab_recordAudio">"nagrywanie dźwięku"</string>
+    <string name="permdesc_recordAudio">"Pozwala aplikacji na dostęp do ścieżki nagrywania dźwięku."</string>
+    <string name="permlab_camera">"robienie zdjęć"</string>
+    <string name="permdesc_camera">"Pozwala aplikacji na wykonywanie zdjęć za pomocą aparatu. Dzięki temu może ona pobierać zdjęcia z aparatu w dowolnym momencie."</string>
+    <string name="permlab_brick">"wyłączenie telefonu na stałe"</string>
+    <string name="permdesc_brick">"Pozwala aplikacji na wyłączenie całego telefonu na stałe. Jest to bardzo niebezpieczne."</string>
+    <string name="permlab_reboot">"wymuszanie ponownego uruchomienia telefonu"</string>
+    <string name="permdesc_reboot">"Pozwala aplikacji na wymuszenie ponownego uruchomienia telefonu."</string>
+    <string name="permlab_mount_unmount_filesystems">"podłączanie i odłączanie systemów plików"</string>
+    <string name="permdesc_mount_unmount_filesystems">"Pozwala aplikacjom na podłączanie i odłączanie systemów plików w pamięciach przenośnych."</string>
+    <string name="permlab_mount_format_filesystems">"formatowanie pamięci zewnętrznej"</string>
+    <string name="permdesc_mount_format_filesystems">"Zezwala aplikacji na formatowanie wymiennych nośników."</string>
+    <string name="permlab_vibrate">"kontrolowanie wibracji"</string>
+    <string name="permdesc_vibrate">"Pozwala aplikacjom na kontrolowanie wibracji."</string>
+    <string name="permlab_flashlight">"kontrolowanie latarki"</string>
+    <string name="permdesc_flashlight">"Pozwala aplikacji kontrolować latarkę."</string>
+    <string name="permlab_hardware_test">"testowanie sprzętu"</string>
+    <string name="permdesc_hardware_test">"Pozwala aplikacji na kontrolowanie różnych urządzeń peryferyjnych w celu testowania sprzętu."</string>
+    <string name="permlab_callPhone">"bezpośrednie wybieranie numerów telefonów"</string>
+    <string name="permdesc_callPhone">"Pozwala aplikacjom na dzwonienie pod numery telefonów bez interwencji użytkownika. Szkodliwe aplikacje mogą powodować występowanie niespodziewanych połączeń na rachunku telefonicznym. Należy zauważyć, że aplikacje nie mogą dzwonić na numery alarmowe."</string>
+    <string name="permlab_callPrivileged">"bezpośrednie wybieranie dowolnych numerów telefonu"</string>
+    <string name="permdesc_callPrivileged">"Pozwala aplikacji dzwonić na dowolny numer telefonu, włącznie z numerami alarmowymi, bez interwencji użytkownika. Szkodliwe aplikacje mogą wykonywać niepotrzebne i nielegalne połączenia z usługami alarmowymi."</string>
+    <string name="permlab_locationUpdates">"kontrolowanie powiadomień o aktualizacji położenia"</string>
+    <string name="permdesc_locationUpdates">"Pozwala włączyć/wyłączyć powiadomienia o aktualizacji położenia przez radio. Nie wykorzystywane przez normalne aplikacje."</string>
+    <string name="permlab_checkinProperties">"dostęp do właściwości usługi rezerwacji"</string>
+    <string name="permdesc_checkinProperties">"Pozwala na dostęp z uprawnieniami do odczytu/zapisu do właściwości przesłanych przez usługę rezerwacji. Nie wykorzystywane przez normalne aplikacje."</string>
+    <string name="permlab_bindGadget">"wybieranie gadżetów"</string>
+    <string name="permdesc_bindGadget">"Zezwala aplikacjom na wskazywanie systemowi, które gadżety mogą być używane przez inne aplikacje. Z użyciem tego pozwolenia aplikacje mogą udzielać dostępu do danych osobistych innym aplikacjom. Nie jest ono przeznaczone dla zwykłych aplikacji."</string>
+    <string name="permlab_modifyPhoneState">"zmiana stanu telefonu"</string>
+    <string name="permdesc_modifyPhoneState">"Pozwala aplikacji na kontrolowanie funkcji telefonu w urządzeniu. Aplikacja z tymi uprawnieniami może przełączać sieci, włączać i wyłączać radio itp. bez informowania użytkownika."</string>
+    <string name="permlab_readPhoneState">"czytanie stanu telefonu"</string>
+    <string name="permdesc_readPhoneState">"Pozwala aplikacji na dostęp do funkcji telefonu w urządzeniu. Aplikacja z takim uprawnieniem może określić numer tego telefonu, czy jest nawiązane połączenie, numer, z którym jest ono nawiązane, itp."</string>
+    <string name="permlab_wakeLock">"zapobieganie przejściu telefonu w stan uśpienia"</string>
+    <string name="permdesc_wakeLock">"Pozwala aplikacji na zapobieganie przejściu telefonu w stan uśpienia."</string>
+    <string name="permlab_devicePower">"włączanie lub wyłączanie telefonu"</string>
+    <string name="permdesc_devicePower">"Pozwala aplikacji włączać i wyłączać telefon."</string>
+    <string name="permlab_factoryTest">"uruchamianie w trybie testu fabrycznego"</string>
+    <string name="permdesc_factoryTest">"Uruchom jako niskopoziomowy test producenta, pozwalając na całkowity dostęp do elementów sprzętowych telefonu. Dostępne tylko jeśli telefon działa w trybie testu producenta."</string>
+    <string name="permlab_setWallpaper">"ustawianie tapety"</string>
+    <string name="permdesc_setWallpaper">"Pozwala aplikacji na ustawianie tapety systemu."</string>
+    <string name="permlab_setWallpaperHints">"ustawianie wskazówek dotyczących rozmiaru tapety"</string>
+    <string name="permdesc_setWallpaperHints">"Pozwala aplikacji na ustawianie wskazówek dotyczących rozmiaru tapety."</string>
+    <string name="permlab_masterClear">"resetowanie systemu do ustawień fabrycznych"</string>
+    <string name="permdesc_masterClear">"Pozwala aplikacji na całkowite zresetowanie systemu do ustawień fabrycznych, z wymazaniem wszystkich danych, konfiguracji oraz zainstalowanych aplikacji."</string>
+    <string name="permlab_setTimeZone">"ustawianie strefy czasowej"</string>
+    <string name="permdesc_setTimeZone">"Pozwala aplikacji na zmianę strefy czasowej w telefonie."</string>
+    <string name="permlab_getAccounts">"wykrywanie znanych kont"</string>
+    <string name="permdesc_getAccounts">"Pozwala aplikacji na pobranie listy kont zapisanych w telefonie."</string>
+    <string name="permlab_accessNetworkState">"wyświetlanie stanu sieci"</string>
+    <string name="permdesc_accessNetworkState">"Pozwala aplikacji na wyświetlanie stanu wszystkich sieci."</string>
+    <string name="permlab_createNetworkSockets">"pełen dostęp do Internetu"</string>
+    <string name="permdesc_createNetworkSockets">"Pozwala aplikacji na tworzenie gniazd sieciowych."</string>
+    <string name="permlab_writeApnSettings">"zapisywanie ustawień nazwy punktu dostępowego (APN, Access Point Name)"</string>
+    <string name="permdesc_writeApnSettings">"Pozwala aplikacji na zmianę ustawień APN, takich jak serwer proxy oraz port dowolnego APN."</string>
+    <string name="permlab_changeNetworkState">"zmienianie połączeń sieci"</string>
+    <string name="permdesc_changeNetworkState">"Pozwala aplikacji na zmianę stanu połączeń sieciowych."</string>
+    <string name="permlab_changeBackgroundDataSetting">"zmienianie ustawienia używania danych w tle"</string>
+    <string name="permdesc_changeBackgroundDataSetting">"Zezwala aplikacji na zmianę ustawień użycia danych w tle."</string>
+    <string name="permlab_accessWifiState">"wyświetlanie stanu Wi-Fi"</string>
+    <string name="permdesc_accessWifiState">"Pozwala aplikacji na wyświetlanie informacji o stanie Wi-Fi."</string>
+    <string name="permlab_changeWifiState">"zmiana stanu Wi-Fi"</string>
+    <string name="permdesc_changeWifiState">"Pozwala aplikacji na łączenie i rozłączanie z punktami dostępowymi Wi-Fi oraz na dokonywanie zmian skonfigurowanych sieci Wi-Fi."</string>
+    <string name="permlab_bluetoothAdmin">"administrowanie Bluetooth"</string>
+    <string name="permdesc_bluetoothAdmin">"Pozwala aplikacji na konfigurowanie lokalnego telefonu Bluetooth, wyszukiwanie urządzeń zdalnych i łączenie się z nimi."</string>
+    <string name="permlab_bluetooth">"tworzenie połączeń Bluetooth"</string>
+    <string name="permdesc_bluetooth">"Pozwala aplikacji na wyświetlanie konfiguracji lokalnego telefonu Bluetooth oraz na tworzenie i akceptowanie połączeń ze sparowanymi urządzeniami."</string>
+    <string name="permlab_disableKeyguard">"wyłączanie blokady klawiatury"</string>
+    <string name="permdesc_disableKeyguard">"Pozwala aplikacji na wyłączenie blokady klawiatury i wszystkich związanych z tym haseł zabezpieczających. Typowym przykładem takiego działania jest wyłączanie blokady klawiatury, gdy pojawia się połączenie przychodzące, a następnie ponowne jej włączanie po zakończeniu połączenia."</string>
+    <string name="permlab_readSyncSettings">"czytanie ustawień synchronizowania"</string>
+    <string name="permdesc_readSyncSettings">"Pozwala aplikacji na czytanie ustawień synchronizacji, takich jak informacje, czy synchronizacja kontaktów jest włączona."</string>
+    <string name="permlab_writeSyncSettings">"zapisywanie ustawień synchronizowania"</string>
+    <string name="permdesc_writeSyncSettings">"Pozwala aplikacji na zmianę ustawień synchronizowania, takich jak ustalenie, czy synchronizacja dla kontaktów ma być włączona."</string>
+    <string name="permlab_readSyncStats">"czytanie statystyk dotyczących synchronizowania"</string>
+    <string name="permdesc_readSyncStats">"Pozwala aplikacji na czytanie statystyk synchronizowania, np. historii przeprowadzonych synchronizacji."</string>
+    <string name="permlab_subscribedFeedsRead">"czytanie subskrybowanych źródeł"</string>
+    <string name="permdesc_subscribedFeedsRead">"Pozwala aplikacjom na pobieranie informacji szczegółowych na temat obecnie zsynchronizowanych źródeł."</string>
+    <string name="permlab_subscribedFeedsWrite">"zapisywanie subskrybowanych źródeł"</string>
+    <string name="permdesc_subscribedFeedsWrite">"Umożliwia aplikacji zmianę obecnie zsynchronizowanych źródeł. Może to pozwolić szkodliwej aplikacji na zmianę zsynchronizowanych źródeł."</string>
+    <string name="permlab_readDictionary">"odczytywanie słownika zdefiniowanego przez użytkownika"</string>
+    <string name="permdesc_readDictionary">"Zezwala aplikacji na odczytywanie wszelkich prywatnych słów, nazw i wyrażeń zapisanych przez użytkownika w swoim słowniku."</string>
+    <string name="permlab_writeDictionary">"zapisywanie w słowniku zdefiniowanym przez użytkownika"</string>
+    <string name="permdesc_writeDictionary">"Zezwala aplikacjom na zapisywanie nowych słów w słowniku użytkownika."</string>
+  <string-array name="phoneTypes">
+    <item>"Dom"</item>
+    <item>"Komórka"</item>
+    <item>"Praca"</item>
+    <item>"Faks w pracy"</item>
+    <item>"Faks domowy"</item>
+    <item>"Pager"</item>
+    <item>"Inny"</item>
+    <item>"Niestandardowy"</item>
+  </string-array>
+  <string-array name="emailAddressTypes">
+    <item>"Dom"</item>
+    <item>"Praca"</item>
+    <item>"Inne"</item>
+    <item>"Niestandardowy"</item>
+  </string-array>
+  <string-array name="postalAddressTypes">
+    <item>"Dom"</item>
+    <item>"Praca"</item>
+    <item>"Inny"</item>
+    <item>"Niestandardowy"</item>
+  </string-array>
+  <string-array name="imAddressTypes">
+    <item>"Dom"</item>
+    <item>"Praca"</item>
+    <item>"Inne"</item>
+    <item>"Niestandardowy"</item>
+  </string-array>
+  <string-array name="organizationTypes">
+    <item>"Praca"</item>
+    <item>"Inne"</item>
+    <item>"Niestandardowy"</item>
+  </string-array>
+  <string-array name="imProtocols">
+    <item>"AIM"</item>
+    <item>"Windows Live"</item>
+    <item>"Yahoo"</item>
+    <item>"Skype"</item>
+    <item>"QQ"</item>
+    <item>"Google Talk"</item>
+    <item>"ICQ"</item>
+    <item>"Jabber"</item>
+  </string-array>
+    <string name="keyguard_password_enter_pin_code">"Wprowadź kod PIN"</string>
+    <string name="keyguard_password_wrong_pin_code">"Błędny kod PIN!"</string>
+    <string name="keyguard_label_text">"Aby odblokować, naciśnij Menu, a następnie 0."</string>
+    <string name="emergency_call_dialog_number_for_display">"Numer alarmowy"</string>
+    <string name="lockscreen_carrier_default">"(Brak usługi)"</string>
+    <string name="lockscreen_screen_locked">"Ekran zablokowany."</string>
+    <string name="lockscreen_instructions_when_pattern_enabled">"Naciśnij Menu, aby odblokować lub wykonać połączenie alarmowe."</string>
+    <string name="lockscreen_instructions_when_pattern_disabled">"Naciśnij Menu, aby odblokować."</string>
+    <string name="lockscreen_pattern_instructions">"Narysuj wzór, aby odblokować"</string>
+    <string name="lockscreen_emergency_call">"Połączenie alarmowe"</string>
+    <string name="lockscreen_pattern_correct">"Poprawnie!"</string>
+    <string name="lockscreen_pattern_wrong">"Niestety, spróbuj ponownie"</string>
+    <!-- no translation found for lockscreen_plugged_in (613343852842944435) -->
+    <skip />
+    <string name="lockscreen_low_battery">"Podłącz ładowarkę."</string>
+    <string name="lockscreen_missing_sim_message_short">"Brak karty SIM."</string>
+    <string name="lockscreen_missing_sim_message">"Brak karty SIM w telefonie."</string>
+    <string name="lockscreen_missing_sim_instructions">"Włóż kartę SIM."</string>
+    <string name="lockscreen_network_locked_message">"Sieć zablokowana"</string>
+    <string name="lockscreen_sim_puk_locked_message">"Karta SIM jest zablokowana kodem PUK."</string>
+    <string name="lockscreen_sim_puk_locked_instructions">"Skontaktuj się z działem obsługi klienta."</string>
+    <string name="lockscreen_sim_locked_message">"Karta SIM jest zablokowana."</string>
+    <string name="lockscreen_sim_unlock_progress_dialog_message">"Odblokowywanie karty SIM..."</string>
+    <string name="lockscreen_too_many_failed_attempts_dialog_message">"Wzór odblokowania został nieprawidłowo narysowany <xliff:g id="NUMBER_0">%d</xliff:g> razy. "\n\n"Spróbuj ponownie za <xliff:g id="NUMBER_1">%d</xliff:g> sekund."</string>
+    <string name="lockscreen_failed_attempts_almost_glogin">"Wzór odblokowania został narysowany nieprawidłowo <xliff:g id="NUMBER_0">%d</xliff:g> razy. Po kolejnych <xliff:g id="NUMBER_1">%d</xliff:g> nieudanych próbach telefon trzeba będzie odblokować przez zalogowanie na koncie Google."\n\n" Spróbuj ponownie za <xliff:g id="NUMBER_2">%d</xliff:g> sekund."</string>
+    <string name="lockscreen_too_many_failed_attempts_countdown">"Spróbuj ponownie za <xliff:g id="NUMBER">%d</xliff:g> sekund."</string>
+    <string name="lockscreen_forgot_pattern_button_text">"Zapomniałeś wzoru?"</string>
+    <string name="lockscreen_glogin_too_many_attempts">"Zbyt wiele prób narysowania wzoru!"</string>
+    <string name="lockscreen_glogin_instructions">"Aby odblokować,"\n"zaloguj się na koncie Google"</string>
+    <string name="lockscreen_glogin_username_hint">"Nazwa użytkownika (e-mail)"</string>
+    <string name="lockscreen_glogin_password_hint">"Hasło"</string>
+    <string name="lockscreen_glogin_submit_button">"Zaloguj"</string>
+    <string name="lockscreen_glogin_invalid_input">"Błędna nazwa użytkownika lub hasło."</string>
+    <string name="status_bar_time_format">"<xliff:g id="HOUR">h</xliff:g>:<xliff:g id="MINUTE">mm</xliff:g> <xliff:g id="AMPM">AA</xliff:g>"</string>
+    <string name="hour_minute_ampm">"<xliff:g id="HOUR">%-l</xliff:g>:<xliff:g id="MINUTE">%M</xliff:g><xliff:g id="AMPM">%P</xliff:g>"</string>
+    <string name="hour_minute_cap_ampm">"<xliff:g id="HOUR">%-l</xliff:g>:<xliff:g id="MINUTE">%M</xliff:g><xliff:g id="AMPM">%p</xliff:g>"</string>
+    <!-- no translation found for hour_ampm (4329881288269772723) -->
+    <skip />
+    <!-- no translation found for hour_cap_ampm (1829009197680861107) -->
+    <skip />
+    <string name="status_bar_clear_all_button">"Wyczyść powiadomienia"</string>
+    <string name="status_bar_no_notifications_title">"Brak powiadomień"</string>
+    <string name="status_bar_ongoing_events_title">"Trwające"</string>
+    <string name="status_bar_latest_events_title">"Powiadomienia"</string>
+    <!-- no translation found for battery_status_text_percent_format (7660311274698797147) -->
+    <skip />
+    <string name="battery_status_charging">"Ładowanie..."</string>
+    <string name="battery_low_title">"Podłącz ładowarkę"</string>
+    <string name="battery_low_subtitle">"Bateria się rozładowuje:"</string>
+    <string name="battery_low_percent_format">"pozostało mniej niż <xliff:g id="NUMBER">%d%%</xliff:g>."</string>
+    <string name="factorytest_failed">"Nieudany test fabryczny"</string>
+    <string name="factorytest_not_system">"Czynność FACTORY_TEST jest obsługiwana tylko dla pakietów zainstalowanych w katalogu /system/app."</string>
+    <string name="factorytest_no_action">"Nie znaleziono żadnego pakietu, który zapewnia działanie FACTORY_TEST."</string>
+    <string name="factorytest_reboot">"Uruchom ponownie"</string>
+    <string name="js_dialog_title">"Komunikat ze strony pod adresem „<xliff:g id="TITLE">%s</xliff:g>”:"</string>
+    <string name="js_dialog_title_default">"JavaScript"</string>
+    <string name="js_dialog_before_unload">"Czy opuścić tę stronę?"\n\n"<xliff:g id="MESSAGE">%s</xliff:g>"\n\n"Wybierz opcję OK, aby kontynuować, lub opcję Anuluj, aby pozostać na tej stronie."</string>
+    <string name="save_password_label">"Potwierdź"</string>
+    <string name="save_password_message">"Czy chcesz, aby zapamiętać to hasło w przeglądarce?"</string>
+    <string name="save_password_notnow">"Nie teraz"</string>
+    <string name="save_password_remember">"Zapamiętaj"</string>
+    <string name="save_password_never">"Nigdy"</string>
+    <string name="open_permission_deny">"Brak uprawnień do otwierania tej strony."</string>
+    <string name="text_copied">"Tekst został skopiowany do schowka."</string>
+    <string name="more_item_label">"Więcej"</string>
+    <string name="prepend_shortcut_label">"Menu+"</string>
+    <string name="menu_space_shortcut_label">"spacja"</string>
+    <string name="menu_enter_shortcut_label">"enter"</string>
+    <string name="menu_delete_shortcut_label">"usuń"</string>
+    <string name="search_go">"Szukaj"</string>
+    <string name="today">"Dzisiaj"</string>
+    <string name="yesterday">"Wczoraj"</string>
+    <string name="tomorrow">"Jutro"</string>
+    <string name="oneMonthDurationPast">"1 miesiąc temu"</string>
+    <string name="beforeOneMonthDurationPast">"Ponad 1 miesiąc temu"</string>
+  <plurals name="num_seconds_ago">
+    <item quantity="one">"sekundę temu"</item>
+    <item quantity="other">"<xliff:g id="COUNT">%d</xliff:g> sekund temu"</item>
+  </plurals>
+  <plurals name="num_minutes_ago">
+    <item quantity="one">"1 minutę temu"</item>
+    <item quantity="other">"<xliff:g id="COUNT">%d</xliff:g> minut temu"</item>
+  </plurals>
+  <plurals name="num_hours_ago">
+    <item quantity="one">"godzinę temu"</item>
+    <item quantity="other">"<xliff:g id="COUNT">%d</xliff:g> godzin temu"</item>
+  </plurals>
+  <plurals name="num_days_ago">
+    <item quantity="one">"wczoraj"</item>
+    <item quantity="other">"<xliff:g id="COUNT">%d</xliff:g> dni temu"</item>
+  </plurals>
+  <plurals name="in_num_seconds">
+    <item quantity="one">"za sekundę"</item>
+    <item quantity="other">"za <xliff:g id="COUNT">%d</xliff:g> sekund"</item>
+  </plurals>
+  <plurals name="in_num_minutes">
+    <item quantity="one">"za minutę"</item>
+    <item quantity="other">"za <xliff:g id="COUNT">%d</xliff:g> minut"</item>
+  </plurals>
+  <plurals name="in_num_hours">
+    <item quantity="one">"za godzinę"</item>
+    <item quantity="other">"za <xliff:g id="COUNT">%d</xliff:g> godzin"</item>
+  </plurals>
+  <plurals name="in_num_days">
+    <item quantity="one">"jutro"</item>
+    <item quantity="other">"za <xliff:g id="COUNT">%d</xliff:g> dni"</item>
+  </plurals>
+  <plurals name="abbrev_num_seconds_ago">
+    <item quantity="one">"sekundę temu"</item>
+    <item quantity="other">"<xliff:g id="COUNT">%d</xliff:g> sek. temu"</item>
+  </plurals>
+  <plurals name="abbrev_num_minutes_ago">
+    <item quantity="one">"minutę temu"</item>
+    <item quantity="other">"<xliff:g id="COUNT">%d</xliff:g> min temu"</item>
+  </plurals>
+  <plurals name="abbrev_num_hours_ago">
+    <item quantity="one">"godzinę temu"</item>
+    <item quantity="other">"<xliff:g id="COUNT">%d</xliff:g> godz. temu"</item>
+  </plurals>
+  <plurals name="abbrev_num_days_ago">
+    <item quantity="one">"wczoraj"</item>
+    <item quantity="other">"<xliff:g id="COUNT">%d</xliff:g> dni temu"</item>
+  </plurals>
+  <plurals name="abbrev_in_num_seconds">
+    <item quantity="one">"za sekundę"</item>
+    <item quantity="other">"za <xliff:g id="COUNT">%d</xliff:g> sek."</item>
+  </plurals>
+  <plurals name="abbrev_in_num_minutes">
+    <item quantity="one">"za minutę"</item>
+    <item quantity="other">"za <xliff:g id="COUNT">%d</xliff:g> min"</item>
+  </plurals>
+  <plurals name="abbrev_in_num_hours">
+    <item quantity="one">"za godzinę"</item>
+    <item quantity="other">"za <xliff:g id="COUNT">%d</xliff:g> godz."</item>
+  </plurals>
+  <plurals name="abbrev_in_num_days">
+    <item quantity="one">"jutro"</item>
+    <item quantity="other">"za <xliff:g id="COUNT">%d</xliff:g> dni"</item>
+  </plurals>
+    <string name="preposition_for_date">"dnia %s"</string>
+    <string name="preposition_for_time">"o %s"</string>
+    <string name="preposition_for_year">"w %s"</string>
+    <string name="day">"dzień"</string>
+    <string name="days">"dni"</string>
+    <string name="hour">"godzina"</string>
+    <string name="hours">"godzin"</string>
+    <string name="minute">"min"</string>
+    <string name="minutes">"minut"</string>
+    <string name="second">"s"</string>
+    <string name="seconds">"S"</string>
+    <string name="week">"tydzień"</string>
+    <string name="weeks">"tygodni"</string>
+    <string name="year">"rok"</string>
+    <string name="years">"lat"</string>
+    <string name="sunday">"niedziela"</string>
+    <string name="monday">"poniedziałek"</string>
+    <string name="tuesday">"wtorek"</string>
+    <string name="wednesday">"środa"</string>
+    <string name="thursday">"czwartek"</string>
+    <string name="friday">"piątek"</string>
+    <string name="saturday">"sobota"</string>
+    <string name="every_weekday">"W każdy dzień roboczy (pon–pt)"</string>
+    <string name="daily">"Codziennie"</string>
+    <string name="weekly">"Co tydzień w <xliff:g id="DAY">%s</xliff:g>"</string>
+    <string name="monthly">"Miesięcznie"</string>
+    <string name="yearly">"Co roku"</string>
+    <string name="VideoView_error_title">"Nie można odtworzyć filmu wideo"</string>
+    <string name="VideoView_error_text_unknown">"Niestety, nie można odtworzyć tego filmu wideo."</string>
+    <string name="VideoView_error_button">"OK"</string>
+    <string name="am">"rano"</string>
+    <string name="pm">"po południu"</string>
+    <string name="numeric_date">"<xliff:g id="MONTH">%m</xliff:g>/<xliff:g id="DAY">%d</xliff:g>/<xliff:g id="YEAR">%Y</xliff:g>"</string>
+    <string name="wday1_date1_time1_wday2_date2_time2">"<xliff:g id="WEEKDAY1">%1$s</xliff:g>, <xliff:g id="DATE1">%2$s</xliff:g>, <xliff:g id="TIME1">%3$s</xliff:g> – <xliff:g id="WEEKDAY2">%4$s</xliff:g>, <xliff:g id="DATE2">%5$s</xliff:g>, <xliff:g id="TIME2">%6$s</xliff:g>"</string>
+    <string name="wday1_date1_wday2_date2">"<xliff:g id="WEEKDAY1">%1$s</xliff:g>, <xliff:g id="DATE1">%2$s</xliff:g> – <xliff:g id="WEEKDAY2">%4$s</xliff:g>, <xliff:g id="DATE2">%5$s</xliff:g>"</string>
+    <string name="date1_time1_date2_time2">"<xliff:g id="DATE1">%2$s</xliff:g>, <xliff:g id="TIME1">%3$s</xliff:g> – <xliff:g id="DATE2">%5$s</xliff:g>, <xliff:g id="TIME2">%6$s</xliff:g>"</string>
+    <string name="date1_date2">"<xliff:g id="DATE1">%2$s</xliff:g> – <xliff:g id="DATE2">%5$s</xliff:g>"</string>
+    <string name="time1_time2">"<xliff:g id="TIME1">%1$s</xliff:g> – <xliff:g id="TIME2">%2$s</xliff:g>"</string>
+    <string name="time_wday_date">"<xliff:g id="TIME_RANGE">%1$s</xliff:g>, <xliff:g id="WEEKDAY">%2$s</xliff:g>, <xliff:g id="DATE">%3$s</xliff:g>"</string>
+    <string name="wday_date">"<xliff:g id="WEEKDAY">%2$s</xliff:g>, <xliff:g id="DATE">%3$s</xliff:g>"</string>
+    <string name="time_date">"<xliff:g id="TIME_RANGE">%1$s</xliff:g>, <xliff:g id="DATE">%3$s</xliff:g>"</string>
+    <string name="date_time">"<xliff:g id="DATE">%1$s</xliff:g>, <xliff:g id="TIME">%2$s</xliff:g>"</string>
+    <string name="relative_time">"<xliff:g id="DATE">%1$s</xliff:g>, <xliff:g id="TIME">%2$s</xliff:g>"</string>
+    <string name="time_wday">"<xliff:g id="TIME_RANGE">%1$s</xliff:g>, <xliff:g id="WEEKDAY">%2$s</xliff:g>"</string>
+    <string name="full_date_month_first" format="date">"<xliff:g id="MONTH">MMMM</xliff:g>' '<xliff:g id="DAY">d</xliff:g>', '<xliff:g id="YEAR">yyyy</xliff:g>"</string>
+    <string name="full_date_day_first" format="date">"<xliff:g id="DAY">d</xliff:g>' '<xliff:g id="MONTH">MMMM</xliff:g>', '<xliff:g id="YEAR">yyyy</xliff:g>"</string>
+    <string name="medium_date_month_first" format="date">"<xliff:g id="MONTH">MMM</xliff:g>' '<xliff:g id="DAY">d</xliff:g>', '<xliff:g id="YEAR">yyyy</xliff:g>"</string>
+    <string name="medium_date_day_first" format="date">"<xliff:g id="DAY">d</xliff:g>' '<xliff:g id="MONTH">MMM</xliff:g>', '<xliff:g id="YEAR">yyyy</xliff:g>"</string>
+    <string name="twelve_hour_time_format" format="date">"<xliff:g id="HOUR">h</xliff:g>':'<xliff:g id="MINUTE">mm</xliff:g>' '<xliff:g id="AMPM">a</xliff:g>"</string>
+    <string name="twenty_four_hour_time_format" format="date">"<xliff:g id="HOUR">H</xliff:g>':'<xliff:g id="MINUTE">mm</xliff:g>"</string>
+    <string name="noon">"południe"</string>
+    <string name="Noon">"Południe"</string>
+    <string name="midnight">"północ"</string>
+    <string name="Midnight">"Północ"</string>
+    <!-- no translation found for month_day (3693060561170538204) -->
+    <skip />
+    <!-- no translation found for month (1976700695144952053) -->
+    <skip />
+    <string name="month_day_year">"<xliff:g id="MONTH">%B</xliff:g> <xliff:g id="DAY">%-d</xliff:g>, <xliff:g id="YEAR">%Y</xliff:g>"</string>
+    <!-- no translation found for month_year (2106203387378728384) -->
+    <skip />
+    <string name="time_of_day">"<xliff:g id="HOUR">%H</xliff:g>:<xliff:g id="MINUTE">%M</xliff:g>:<xliff:g id="SECOND">%S</xliff:g>"</string>
+    <string name="date_and_time">"<xliff:g id="HOUR">%H</xliff:g>:<xliff:g id="MINUTE">%M</xliff:g>:<xliff:g id="SECOND">%S</xliff:g> <xliff:g id="MONTH">%B</xliff:g> <xliff:g id="DAY">%-d</xliff:g>, <xliff:g id="YEAR">%Y</xliff:g>"</string>
+    <string name="same_year_md1_md2">"<xliff:g id="MONTH1">%2$s</xliff:g> <xliff:g id="DAY1">%3$s</xliff:g> – <xliff:g id="MONTH2">%7$s</xliff:g> <xliff:g id="DAY2">%8$s</xliff:g>"</string>
+    <string name="same_year_wday1_md1_wday2_md2">"<xliff:g id="WEEKDAY1">%1$s</xliff:g>, <xliff:g id="MONTH1">%2$s</xliff:g> <xliff:g id="DAY1_0">%3$s</xliff:g> – <xliff:g id="WEEKDAY2">%6$s</xliff:g>, <xliff:g id="MONTH2">%7$s</xliff:g> <xliff:g id="DAY2_1">%8$s</xliff:g>"</string>
+    <string name="same_year_mdy1_mdy2">"<xliff:g id="MONTH1">%2$s</xliff:g> <xliff:g id="DAY1">%3$s</xliff:g> – <xliff:g id="MONTH2">%7$s</xliff:g> <xliff:g id="DAY2">%8$s</xliff:g>, <xliff:g id="YEAR">%9$s</xliff:g>"</string>
+    <string name="same_year_wday1_mdy1_wday2_mdy2">"<xliff:g id="WEEKDAY1">%1$s</xliff:g>, <xliff:g id="MONTH1">%2$s</xliff:g> <xliff:g id="DAY1_0">%3$s</xliff:g> – <xliff:g id="WEEKDAY2">%6$s</xliff:g>, <xliff:g id="MONTH2">%7$s</xliff:g> <xliff:g id="DAY2_1">%8$s</xliff:g>, <xliff:g id="YEAR">%9$s</xliff:g>"</string>
+    <string name="same_year_md1_time1_md2_time2">"<xliff:g id="MONTH1">%2$s</xliff:g> <xliff:g id="DAY1">%3$s</xliff:g>, <xliff:g id="TIME1">%5$s</xliff:g> – <xliff:g id="MONTH2">%7$s</xliff:g> <xliff:g id="DAY2">%8$s</xliff:g>, <xliff:g id="TIME2">%10$s</xliff:g>"</string>
+    <string name="same_year_wday1_md1_time1_wday2_md2_time2">"<xliff:g id="WEEKDAY1">%1$s</xliff:g>, <xliff:g id="MONTH1">%2$s</xliff:g> <xliff:g id="DAY1_0">%3$s</xliff:g>, <xliff:g id="TIME1">%5$s</xliff:g> – <xliff:g id="WEEKDAY2">%6$s</xliff:g>, <xliff:g id="MONTH2">%7$s</xliff:g> <xliff:g id="DAY2_1">%8$s</xliff:g>, <xliff:g id="TIME2">%10$s</xliff:g>"</string>
+    <string name="same_year_mdy1_time1_mdy2_time2">"<xliff:g id="MONTH1">%2$s</xliff:g> <xliff:g id="DAY1">%3$s</xliff:g>, <xliff:g id="YEAR1">%4$s</xliff:g>, <xliff:g id="TIME1">%5$s</xliff:g> – <xliff:g id="MONTH2">%7$s</xliff:g> <xliff:g id="DAY2">%8$s</xliff:g>, <xliff:g id="YEAR2">%9$s</xliff:g>, <xliff:g id="TIME2">%10$s</xliff:g>"</string>
+    <string name="same_year_wday1_mdy1_time1_wday2_mdy2_time2">"<xliff:g id="WEEKDAY1">%1$s</xliff:g>, <xliff:g id="MONTH1">%2$s</xliff:g> <xliff:g id="DAY1_0">%3$s</xliff:g>, <xliff:g id="YEAR1">%4$s</xliff:g>, <xliff:g id="TIME1">%5$s</xliff:g> – <xliff:g id="WEEKDAY2">%6$s</xliff:g>, <xliff:g id="MONTH2">%7$s</xliff:g> <xliff:g id="DAY2_1">%8$s</xliff:g>, <xliff:g id="YEAR2">%9$s</xliff:g>, <xliff:g id="TIME2">%10$s</xliff:g>"</string>
+    <string name="numeric_md1_md2">"<xliff:g id="MONTH1">%2$s</xliff:g>/<xliff:g id="DAY1">%3$s</xliff:g> – <xliff:g id="MONTH2">%7$s</xliff:g>/<xliff:g id="DAY2">%8$s</xliff:g>"</string>
+    <string name="numeric_wday1_md1_wday2_md2">"<xliff:g id="WEEKDAY1">%1$s</xliff:g>, <xliff:g id="MONTH1">%2$s</xliff:g>/<xliff:g id="DAY1_0">%3$s</xliff:g> – <xliff:g id="WEEKDAY2">%6$s</xliff:g>, <xliff:g id="MONTH2">%7$s</xliff:g>/<xliff:g id="DAY2_1">%8$s</xliff:g>"</string>
+    <string name="numeric_mdy1_mdy2">"<xliff:g id="MONTH1">%2$s</xliff:g>/<xliff:g id="DAY1">%3$s</xliff:g>/<xliff:g id="YEAR1">%4$s</xliff:g> – <xliff:g id="MONTH2">%7$s</xliff:g>/<xliff:g id="DAY2">%8$s</xliff:g>/<xliff:g id="YEAR2">%9$s</xliff:g>"</string>
+    <string name="numeric_wday1_mdy1_wday2_mdy2">"<xliff:g id="WEEKDAY1">%1$s</xliff:g>, <xliff:g id="MONTH1">%2$s</xliff:g>/<xliff:g id="DAY1_0">%3$s</xliff:g>/<xliff:g id="YEAR1">%4$s</xliff:g> – <xliff:g id="WEEKDAY2">%6$s</xliff:g>, <xliff:g id="MONTH2">%7$s</xliff:g>/<xliff:g id="DAY2_1">%8$s</xliff:g>/<xliff:g id="YEAR2">%9$s</xliff:g>"</string>
+    <string name="numeric_md1_time1_md2_time2">"<xliff:g id="MONTH1">%2$s</xliff:g>/<xliff:g id="DAY1">%3$s</xliff:g>, <xliff:g id="TIME1">%5$s</xliff:g> – <xliff:g id="MONTH2">%7$s</xliff:g>/<xliff:g id="DAY2">%8$s</xliff:g>, <xliff:g id="TIME2">%10$s</xliff:g>"</string>
+    <string name="numeric_wday1_md1_time1_wday2_md2_time2">"<xliff:g id="WEEKDAY1">%1$s</xliff:g>, <xliff:g id="MONTH1">%2$s</xliff:g>/<xliff:g id="DAY1_0">%3$s</xliff:g>, <xliff:g id="TIME1">%5$s</xliff:g> – <xliff:g id="WEEKDAY2">%6$s</xliff:g>, <xliff:g id="MONTH2">%7$s</xliff:g>/<xliff:g id="DAY2_1">%8$s</xliff:g>, <xliff:g id="TIME2">%10$s</xliff:g>"</string>
+    <string name="numeric_mdy1_time1_mdy2_time2">"<xliff:g id="MONTH1">%2$s</xliff:g>/<xliff:g id="DAY1">%3$s</xliff:g>/<xliff:g id="YEAR1">%4$s</xliff:g>, <xliff:g id="TIME1">%5$s</xliff:g> – <xliff:g id="MONTH2">%7$s</xliff:g>/<xliff:g id="DAY2">%8$s</xliff:g>/<xliff:g id="YEAR2">%9$s</xliff:g>, <xliff:g id="TIME2">%10$s</xliff:g>"</string>
+    <string name="numeric_wday1_mdy1_time1_wday2_mdy2_time2">"<xliff:g id="WEEKDAY1">%1$s</xliff:g>, <xliff:g id="MONTH1">%2$s</xliff:g>/<xliff:g id="DAY1_0">%3$s</xliff:g>/<xliff:g id="YEAR1">%4$s</xliff:g>, <xliff:g id="TIME1">%5$s</xliff:g> – <xliff:g id="WEEKDAY2">%6$s</xliff:g>, <xliff:g id="MONTH2">%7$s</xliff:g>/<xliff:g id="DAY2_1">%8$s</xliff:g>/<xliff:g id="YEAR2">%9$s</xliff:g>, <xliff:g id="TIME2">%10$s</xliff:g>"</string>
+    <string name="same_month_md1_md2">"<xliff:g id="MONTH1">%2$s</xliff:g> <xliff:g id="DAY1">%3$s</xliff:g> – <xliff:g id="DAY2">%8$s</xliff:g>"</string>
+    <string name="same_month_wday1_md1_wday2_md2">"<xliff:g id="WEEKDAY1">%1$s</xliff:g>, <xliff:g id="MONTH1">%2$s</xliff:g> <xliff:g id="DAY1_0">%3$s</xliff:g> – <xliff:g id="WEEKDAY2">%6$s</xliff:g>, <xliff:g id="MONTH2">%7$s</xliff:g> <xliff:g id="DAY2_1">%8$s</xliff:g>"</string>
+    <string name="same_month_mdy1_mdy2">"<xliff:g id="MONTH1">%2$s</xliff:g> <xliff:g id="DAY1">%3$s</xliff:g> – <xliff:g id="DAY2">%8$s</xliff:g>, <xliff:g id="YEAR2">%9$s</xliff:g>"</string>
+    <string name="same_month_wday1_mdy1_wday2_mdy2">"<xliff:g id="WEEKDAY1">%1$s</xliff:g>, <xliff:g id="MONTH1">%2$s</xliff:g> <xliff:g id="DAY1_0">%3$s</xliff:g>, <xliff:g id="YEAR1">%4$s</xliff:g> – <xliff:g id="WEEKDAY2">%6$s</xliff:g>, <xliff:g id="MONTH2">%7$s</xliff:g> <xliff:g id="DAY2_1">%8$s</xliff:g>, <xliff:g id="YEAR2">%9$s</xliff:g>"</string>
+    <string name="same_month_md1_time1_md2_time2">"<xliff:g id="MONTH1">%2$s</xliff:g> <xliff:g id="DAY1">%3$s</xliff:g>, <xliff:g id="TIME1">%5$s</xliff:g> – <xliff:g id="MONTH2">%7$s</xliff:g> <xliff:g id="DAY2">%8$s</xliff:g>, <xliff:g id="TIME2">%10$s</xliff:g>"</string>
+    <string name="same_month_wday1_md1_time1_wday2_md2_time2">"<xliff:g id="WEEKDAY1">%1$s</xliff:g>, <xliff:g id="MONTH1">%2$s</xliff:g> <xliff:g id="DAY1_0">%3$s</xliff:g>, <xliff:g id="TIME1">%5$s</xliff:g> – <xliff:g id="WEEKDAY2">%6$s</xliff:g>, <xliff:g id="MONTH2">%7$s</xliff:g> <xliff:g id="DAY2_1">%8$s</xliff:g>, <xliff:g id="TIME2">%10$s</xliff:g>"</string>
+    <string name="same_month_mdy1_time1_mdy2_time2">"<xliff:g id="MONTH1">%2$s</xliff:g> <xliff:g id="DAY1">%3$s</xliff:g>, <xliff:g id="YEAR1">%4$s</xliff:g>, <xliff:g id="TIME1">%5$s</xliff:g> – <xliff:g id="MONTH2">%7$s</xliff:g> <xliff:g id="DAY2">%8$s</xliff:g>, <xliff:g id="YEAR2">%9$s</xliff:g>, <xliff:g id="TIME2">%10$s</xliff:g>"</string>
+    <string name="same_month_wday1_mdy1_time1_wday2_mdy2_time2">"<xliff:g id="WEEKDAY1">%1$s</xliff:g>, <xliff:g id="MONTH1">%2$s</xliff:g> <xliff:g id="DAY1_0">%3$s</xliff:g>, <xliff:g id="YEAR1">%4$s</xliff:g>, <xliff:g id="TIME1">%5$s</xliff:g> – <xliff:g id="WEEKDAY2">%6$s</xliff:g>, <xliff:g id="MONTH2">%7$s</xliff:g> <xliff:g id="DAY2_1">%8$s</xliff:g>, <xliff:g id="YEAR2">%9$s</xliff:g>, <xliff:g id="TIME2">%10$s</xliff:g>"</string>
+    <string name="abbrev_month_day_year">"<xliff:g id="MONTH">%b</xliff:g> <xliff:g id="DAY">%-d</xliff:g>, <xliff:g id="YEAR">%Y</xliff:g>"</string>
+    <!-- no translation found for abbrev_month_year (5966980891147982768) -->
+    <skip />
+    <!-- no translation found for abbrev_month_day (3156047263406783231) -->
+    <skip />
+    <!-- no translation found for abbrev_month (7304935052615731208) -->
+    <skip />
+    <string name="day_of_week_long_sunday">"niedziela"</string>
+    <string name="day_of_week_long_monday">"poniedziałek"</string>
+    <string name="day_of_week_long_tuesday">"wtorek"</string>
+    <string name="day_of_week_long_wednesday">"środa"</string>
+    <string name="day_of_week_long_thursday">"czwartek"</string>
+    <string name="day_of_week_long_friday">"piątek"</string>
+    <string name="day_of_week_long_saturday">"sobota"</string>
+    <string name="day_of_week_medium_sunday">"Nie"</string>
+    <string name="day_of_week_medium_monday">"Pon"</string>
+    <string name="day_of_week_medium_tuesday">"Wt"</string>
+    <string name="day_of_week_medium_wednesday">"Śro"</string>
+    <string name="day_of_week_medium_thursday">"Czw"</string>
+    <string name="day_of_week_medium_friday">"Pią"</string>
+    <string name="day_of_week_medium_saturday">"Sob"</string>
+    <string name="day_of_week_short_sunday">"Nd"</string>
+    <string name="day_of_week_short_monday">"Pn"</string>
+    <string name="day_of_week_short_tuesday">"Wt"</string>
+    <string name="day_of_week_short_wednesday">"Śr"</string>
+    <string name="day_of_week_short_thursday">"Czw"</string>
+    <string name="day_of_week_short_friday">"Pt"</string>
+    <string name="day_of_week_short_saturday">"So"</string>
+    <string name="day_of_week_shorter_sunday">"Nd"</string>
+    <string name="day_of_week_shorter_monday">"Pon"</string>
+    <string name="day_of_week_shorter_tuesday">"Wt"</string>
+    <string name="day_of_week_shorter_wednesday">"Śr"</string>
+    <string name="day_of_week_shorter_thursday">"Czw"</string>
+    <string name="day_of_week_shorter_friday">"Pt"</string>
+    <string name="day_of_week_shorter_saturday">"So"</string>
+    <string name="day_of_week_shortest_sunday">"Nd"</string>
+    <string name="day_of_week_shortest_monday">"Pon"</string>
+    <string name="day_of_week_shortest_tuesday">"Czw"</string>
+    <string name="day_of_week_shortest_wednesday">"Śr"</string>
+    <string name="day_of_week_shortest_thursday">"Czw"</string>
+    <string name="day_of_week_shortest_friday">"Pt"</string>
+    <string name="day_of_week_shortest_saturday">"Sob"</string>
+    <string name="month_long_january">"Styczeń"</string>
+    <string name="month_long_february">"Luty"</string>
+    <string name="month_long_march">"Marzec"</string>
+    <string name="month_long_april">"Kwiecień"</string>
+    <string name="month_long_may">"Maj"</string>
+    <string name="month_long_june">"Czerwiec"</string>
+    <string name="month_long_july">"Lipiec"</string>
+    <string name="month_long_august">"Sierpień"</string>
+    <string name="month_long_september">"Wrzesień"</string>
+    <string name="month_long_october">"Październik"</string>
+    <string name="month_long_november">"Listopad"</string>
+    <string name="month_long_december">"Grudzień"</string>
+    <string name="month_medium_january">"Sty"</string>
+    <string name="month_medium_february">"Lut"</string>
+    <string name="month_medium_march">"Mar"</string>
+    <string name="month_medium_april">"Kwi"</string>
+    <string name="month_medium_may">"Maj"</string>
+    <string name="month_medium_june">"Cze"</string>
+    <string name="month_medium_july">"Lip"</string>
+    <string name="month_medium_august">"Sie"</string>
+    <string name="month_medium_september">"Wrz"</string>
+    <string name="month_medium_october">"Paź"</string>
+    <string name="month_medium_november">"Lis"</string>
+    <string name="month_medium_december">"Gru"</string>
+    <string name="month_shortest_january">"Sty"</string>
+    <string name="month_shortest_february">"Lut"</string>
+    <string name="month_shortest_march">"Pon"</string>
+    <string name="month_shortest_april">"Kwi"</string>
+    <string name="month_shortest_may">"Maj"</string>
+    <string name="month_shortest_june">"Cze"</string>
+    <string name="month_shortest_july">"Lip"</string>
+    <string name="month_shortest_august">"Sie"</string>
+    <string name="month_shortest_september">"Wrz"</string>
+    <string name="month_shortest_october">"Paź"</string>
+    <string name="month_shortest_november">"Lis"</string>
+    <string name="month_shortest_december">"Gru"</string>
+    <string name="elapsed_time_short_format_mm_ss">"<xliff:g id="MINUTES">%1$02d</xliff:g>:<xliff:g id="SECONDS">%2$02d</xliff:g>"</string>
+    <string name="elapsed_time_short_format_h_mm_ss">"<xliff:g id="HOURS">%1$d</xliff:g>:<xliff:g id="MINUTES">%2$02d</xliff:g>:<xliff:g id="SECONDS">%3$02d</xliff:g>"</string>
+    <string name="selectAll">"Zaznacz wszystko"</string>
+    <string name="selectText">"Zaznacz tekst"</string>
+    <string name="stopSelectingText">"Zatrzymaj wybieranie tekstu"</string>
+    <string name="cut">"Wytnij"</string>
+    <string name="cutAll">"Wytnij wszystko"</string>
+    <string name="copy">"Kopiuj"</string>
+    <string name="copyAll">"Kopiuj wszystko"</string>
+    <string name="paste">"Wklej"</string>
+    <string name="copyUrl">"Kopiuj adres URL"</string>
+    <string name="inputMethod">"Wprowadzanie tekstu"</string>
+    <string name="addToDictionary">"Dodaj „%s” do słownika"</string>
+    <string name="editTextMenuTitle">"Edytuj tekst"</string>
+    <string name="low_internal_storage_view_title">"Mało miejsca"</string>
+    <string name="low_internal_storage_view_text">"Maleje ilość dostępnej pamięci telefonu."</string>
+    <string name="ok">"OK"</string>
+    <string name="cancel">"Anuluj"</string>
+    <string name="yes">"OK"</string>
+    <string name="no">"Anuluj"</string>
+    <string name="dialog_alert_title">"Uwaga"</string>
+    <string name="capital_on">"Włącz"</string>
+    <string name="capital_off">"Wyłącz"</string>
+    <string name="whichApplication">"Zakończ czynność korzystając z"</string>
+    <string name="alwaysUse">"Używaj domyślnie dla tej czynności."</string>
+    <string name="clearDefaultHintMsg">"Wyczyść domyślne w: Ustawienia strony głównej &gt; Aplikacje &gt; Zarządzaj aplikacjami."</string>
+    <string name="chooseActivity">"Wybierz czynność"</string>
+    <string name="noApplications">"Żadna z aplikacji nie może wykonać tej czynności."</string>
+    <string name="aerr_title">"Przepraszamy!"</string>
+    <string name="aerr_application">"Aplikacja <xliff:g id="APPLICATION">%1$s</xliff:g> (proces <xliff:g id="PROCESS">%2$s</xliff:g>) została niespodziewanie zatrzymana. Spróbuj ponownie."</string>
+    <string name="aerr_process">"Proces <xliff:g id="PROCESS">%1$s</xliff:g> został niespodziewanie zatrzymany. Spróbuj ponownie."</string>
+    <string name="anr_title">"Przepraszamy!"</string>
+    <string name="anr_activity_application">"<xliff:g id="ACTIVITY">%1$s</xliff:g> (w aplikacji <xliff:g id="APPLICATION">%2$s</xliff:g>) nie odpowiada."</string>
+    <string name="anr_activity_process">"<xliff:g id="ACTIVITY">%1$s</xliff:g> (w procesie <xliff:g id="PROCESS">%2$s</xliff:g>) nie odpowiada."</string>
+    <string name="anr_application_process">"Aplikacja <xliff:g id="APPLICATION">%1$s</xliff:g> (w procesie <xliff:g id="PROCESS">%2$s</xliff:g>) nie odpowiada."</string>
+    <string name="anr_process">"Proces <xliff:g id="PROCESS">%1$s</xliff:g> nie odpowiada."</string>
+    <string name="force_close">"Wymuś zamknięcie"</string>
+    <string name="wait">"Czekaj"</string>
+    <string name="debug">"Debuguj"</string>
+    <string name="sendText">"Wybierz czynność dla wpisanego tekstu"</string>
+    <string name="volume_ringtone">"Głośność dzwonka"</string>
+    <string name="volume_music">"Głośność multimediów"</string>
+    <string name="volume_music_hint_playing_through_bluetooth">"Odtwarzanie przez Bluetooth"</string>
+    <string name="volume_call">"Głośność podczas połączenia"</string>
+    <string name="volume_bluetooth_call">"Głośność Bluetooth w czasie połączenia"</string>
+    <string name="volume_alarm">"Głośność alarmu"</string>
+    <string name="volume_notification">"Głośność powiadomienia"</string>
+    <string name="volume_unknown">"Głośność"</string>
+    <string name="ringtone_default">"Domyślny dzwonek"</string>
+    <string name="ringtone_default_with_actual">"Domyślny dzwonek (<xliff:g id="ACTUAL_RINGTONE">%1$s</xliff:g>)"</string>
+    <string name="ringtone_silent">"Cichy"</string>
+    <string name="ringtone_picker_title">"Dzwonki"</string>
+    <string name="ringtone_unknown">"Nieznany dzwonek"</string>
+  <plurals name="wifi_available">
+    <item quantity="one">"Sieć Wi-Fi jest dostępna"</item>
+    <item quantity="other">"Dostępne sieci Wi-Fi"</item>
+  </plurals>
+  <plurals name="wifi_available_detailed">
+    <item quantity="one">"Otwórz dostępne sieci Wi-Fi"</item>
+    <item quantity="other">"Otwórz dostępne sieci Wi-Fi"</item>
+  </plurals>
+    <string name="select_character">"Wstaw znak"</string>
+    <string name="sms_control_default_app_name">"Nieznana aplikacja"</string>
+    <string name="sms_control_title">"Wysyłanie wiadomości SMS"</string>
+    <string name="sms_control_message">"Wysyłana jest duża liczba wiadomości SMS. Wybierz „OK”, aby kontynuować, lub „Anuluj”, aby zatrzymać wysyłanie."</string>
+    <string name="sms_control_yes">"OK"</string>
+    <string name="sms_control_no">"Anuluj"</string>
+    <string name="date_time_set">"Ustaw"</string>
+    <string name="default_permission_group">"Domyślne"</string>
+    <string name="no_permissions">"Nie są wymagane żadne uprawnienia"</string>
+    <string name="perms_hide"><b>"Ukryj"</b></string>
+    <string name="perms_show_all"><b>"Pokaż wszystko"</b></string>
+    <string name="googlewebcontenthelper_loading">"Ładowanie..."</string>
+    <string name="usb_storage_title">"Połączenie przez USB"</string>
+    <string name="usb_storage_message">"Telefon został podłączony do komputera przez port USB. Wybierz polecenie „Podłącz”, jeśli chcesz skopiować pliki między komputerem a kartą SD w telefonie."</string>
+    <string name="usb_storage_button_mount">"Podłącz"</string>
+    <string name="usb_storage_button_unmount">"Nie podłączaj"</string>
+    <string name="usb_storage_error_message">"Wystąpił problem z wykorzystaniem karty SD dla pamięci USB."</string>
+    <string name="usb_storage_notification_title">"Połączenie przez USB"</string>
+    <string name="usb_storage_notification_message">"Wybierz, aby skopiować pliki do/z komputera"</string>
+    <string name="usb_storage_stop_notification_title">"Wyłącz nośnik USB"</string>
+    <string name="usb_storage_stop_notification_message">"Wybierz, aby wyłączyć nośnik USB."</string>
+    <string name="usb_storage_stop_title">"Wyłącz nośnik USB"</string>
+    <string name="usb_storage_stop_message">"Przed wyłączeniem nośnika USB upewnij się, że odłączono go od hosta USB. Wybierz „Wyłącz”, aby wyłączyć nośnik USB."</string>
+    <string name="usb_storage_stop_button_mount">"Wyłącz"</string>
+    <string name="usb_storage_stop_button_unmount">"Anuluj"</string>
+    <string name="usb_storage_stop_error_message">"Napotkano problem przy wyłączaniu nośnika USB. Sprawdź, czy host USB został odłączony i spróbuj ponownie."</string>
+    <string name="extmedia_format_title">"Formatuj kartę SD"</string>
+    <string name="extmedia_format_message">"Czy na pewno sformatować kartę SD? Wszystkie dane na karcie zostaną utracone."</string>
+    <string name="extmedia_format_button_format">"Formatuj"</string>
+    <string name="select_input_method">"Sposób wprowadzania tekstu"</string>
+    <string name="fast_scroll_alphabet">" AĄBCĆDEĘFGHIJKLŁMNŃOÓPQRSŚTUVWXYZŹŻ"</string>
+    <string name="fast_scroll_numeric_alphabet">" 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"</string>
+    <string name="candidates_style"><u>"kandydaci"</u></string>
+    <string name="ext_media_checking_notification_title">"Przygotowywanie karty SD"</string>
+    <string name="ext_media_checking_notification_message">"Sprawdzanie w poszukiwaniu błędów"</string>
+    <string name="ext_media_nofs_notification_title">"Pusta karta SD"</string>
+    <string name="ext_media_nofs_notification_message">"Karta SD jest pusta lub używa nieobsługiwanego systemu plików."</string>
+    <string name="ext_media_unmountable_notification_title">"Uszkodzona karta SD"</string>
+    <string name="ext_media_unmountable_notification_message">"Karta SD jest uszkodzona. Konieczne może być przeformatowanie karty."</string>
+    <string name="ext_media_badremoval_notification_title">"Karta SD została nieoczekiwanie wyjęta"</string>
+    <string name="ext_media_badremoval_notification_message">"Odłącz kartę SD przed jej wyjęciem, aby uniknąć utraty danych."</string>
+    <string name="ext_media_safe_unmount_notification_title">"Można bezpiecznie usunąć kartę SD"</string>
+    <string name="ext_media_safe_unmount_notification_message">"Można teraz bezpiecznie usunąć kartę SD."</string>
+    <string name="ext_media_nomedia_notification_title">"Usunięta karta SD"</string>
+    <string name="ext_media_nomedia_notification_message">"Karta SD została usunięta. Włóż nową kartę SD, aby zwiększyć pamięć urządzenia."</string>
+    <string name="activity_list_empty">"Nie znaleziono pasujących działań"</string>
+    <string name="permlab_pkgUsageStats">"aktualizowanie statystyk użycia komponentu"</string>
+    <string name="permdesc_pkgUsageStats">"Zezwala na modyfikacje zebranych statystyk użycia komponentu. Nieprzeznaczone dla zwykłych aplikacji."</string>
+    <!-- no translation found for tutorial_double_tap_to_zoom_message_short (1311810005957319690) -->
+    <skip />
+    <!-- no translation found for gadget_host_error_inflating (2613287218853846830) -->
+    <skip />
+    <!-- no translation found for ime_action_go (8320845651737369027) -->
+    <skip />
+    <!-- no translation found for ime_action_search (658110271822807811) -->
+    <skip />
+    <!-- no translation found for ime_action_send (2316166556349314424) -->
+    <skip />
+    <!-- no translation found for ime_action_next (3138843904009813834) -->
+    <skip />
+    <!-- no translation found for ime_action_default (2840921885558045721) -->
+    <skip />
+</resources>
diff --git a/core/res/res/values-ru/strings.xml b/core/res/res/values-ru/strings.xml
new file mode 100644
index 0000000..ba88667
--- /dev/null
+++ b/core/res/res/values-ru/strings.xml
@@ -0,0 +1,815 @@
+<?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.
+-->
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="byteShort">"б"</string>
+    <string name="kilobyteShort">"Кб"</string>
+    <string name="megabyteShort">"Мб"</string>
+    <string name="gigabyteShort">"Гб"</string>
+    <string name="terabyteShort">"Тб"</string>
+    <string name="petabyteShort">"Пб"</string>
+    <string name="untitled">"&lt;без названия&gt;"</string>
+    <string name="ellipsis">"…"</string>
+    <string name="emptyPhoneNumber">"(Нет номера телефона)"</string>
+    <string name="unknownName">"(Неизвестно)"</string>
+    <string name="defaultVoiceMailAlphaTag">"Голосовая почта"</string>
+    <string name="defaultMsisdnAlphaTag">"MSISDN1"</string>
+    <string name="mmiError">"Проблема с подключением или недействительный код MMI."</string>
+    <string name="serviceEnabled">"Служба включена."</string>
+    <string name="serviceEnabledFor">"Служба включена для следующего:"</string>
+    <string name="serviceDisabled">"Служба отключена."</string>
+    <string name="serviceRegistered">"Регистрация прошла успешно."</string>
+    <string name="serviceErased">"Удаление прошло успешно."</string>
+    <string name="passwordIncorrect">"Неверный пароль."</string>
+    <string name="mmiComplete">"Код MMI завершен."</string>
+    <string name="badPin">"Указан неверный старый PIN."</string>
+    <string name="badPuk">"Указан неверный PUK."</string>
+    <string name="mismatchPin">"Указанные PIN-коды не совпадают."</string>
+    <string name="invalidPin">"Введите PIN, содержащий от 4 до 8 цифр."</string>
+    <string name="needPuk">"Ваша SIM-карта заблокирована PUK-кодом. Введите PUK, чтобы разблокировать ее."</string>
+    <string name="needPuk2">"Введите PUK2 для разблокирования SIM-карты."</string>
+    <string name="ClipMmi">"Идентификатор звонящего"</string>
+    <string name="ClirMmi">"Идентификатор принимающего вызов"</string>
+    <string name="CfMmi">"Перенаправление вызовов"</string>
+    <string name="CwMmi">"Ожидание вызова"</string>
+    <string name="BaMmi">"Запрет вызовов"</string>
+    <string name="PwdMmi">"Изменение пароля"</string>
+    <string name="PinMmi">"Изменение PIN"</string>
+    <string name="CLIRDefaultOnNextCallOn">"Идентификатор звонящего по умолчанию ограничен. Следующий вызов: ограничен"</string>
+    <string name="CLIRDefaultOnNextCallOff">"Идентификатор звонящего по умолчанию ограничен. Следующий вызов: не ограничен"</string>
+    <string name="CLIRDefaultOffNextCallOn">"Идентификатор звонящего по умолчанию не ограничен. Следующий вызов: ограничен"</string>
+    <string name="CLIRDefaultOffNextCallOff">"Идентификатор звонящего по умолчанию не ограничен. Следующий вызов: не ограничен"</string>
+    <string name="serviceNotProvisioned">"Услуга не предоставляется."</string>
+    <string name="CLIRPermanent">"Нельзя изменить настройки идентификатора звонящего."</string>
+    <string name="serviceClassVoice">"Голос"</string>
+    <string name="serviceClassData">"Данные"</string>
+    <string name="serviceClassFAX">"ФАКС"</string>
+    <string name="serviceClassSMS">"SMS"</string>
+    <string name="serviceClassDataAsync">"Асинхр."</string>
+    <string name="serviceClassDataSync">"Синхронизация"</string>
+    <string name="serviceClassPacket">"Пакетные данные"</string>
+    <string name="serviceClassPAD">"PAD"</string>
+    <string name="cfTemplateNotForwarded">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: не перенаправлено"</string>
+    <string name="cfTemplateForwarded">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
+    <string name="cfTemplateForwardedTime">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> через <xliff:g id="TIME_DELAY">{2}</xliff:g> сек."</string>
+    <string name="cfTemplateRegistered">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: не перенаправлено"</string>
+    <string name="cfTemplateRegisteredTime">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: не перенаправлено"</string>
+    <string name="httpErrorOk">"ОК"</string>
+    <string name="httpError">"Веб-страница содержит ошибку."</string>
+    <string name="httpErrorLookup">"Не удается найти URL."</string>
+    <string name="httpErrorUnsupportedAuthScheme">"Схема аутентификации сайта не поддерживается."</string>
+    <string name="httpErrorAuth">"Не удалось выполнить аутентификацию."</string>
+    <string name="httpErrorProxyAuth">"Не удалось выполнить аутентификацию через прокси-сервер."</string>
+    <string name="httpErrorConnect">"Не удалось связаться с сервером."</string>
+    <string name="httpErrorIO">"Сервер не отвечает. Повторите попытку позже."</string>
+    <string name="httpErrorTimeout">"Время подключения к серверу истекло."</string>
+    <string name="httpErrorRedirectLoop">"Страница содержит слишком много перенаправлений."</string>
+    <string name="httpErrorUnsupportedScheme">"Протокол не поддерживается"</string>
+    <string name="httpErrorFailedSslHandshake">"Невозможно установить безопасное соединение."</string>
+    <string name="httpErrorBadUrl">"Невозможно открыть страницу, поскольку URL недействителен."</string>
+    <string name="httpErrorFile">"Нет доступа к файлу."</string>
+    <string name="httpErrorFileNotFound">"Нужный файл не найден."</string>
+    <string name="httpErrorTooManyRequests">"Обрабатывается слишком много запросов. Повторите попытку позже."</string>
+    <string name="contentServiceSync">"Синхронизация"</string>
+    <string name="contentServiceSyncNotificationTitle">"Синхронизация"</string>
+    <string name="contentServiceTooManyDeletesNotificationDesc">"Слишком много удалений <xliff:g id="CONTENT_TYPE">%s</xliff:g>."</string>
+    <string name="low_memory">"Память телефона полна! Удалите какие-нибудь файлы, чтобы освободить место."</string>
+    <string name="me">"Я"</string>
+    <string name="power_dialog">"Параметры телефона"</string>
+    <string name="silent_mode">"Беззвучный режим"</string>
+    <string name="turn_on_radio">"Включить беспроводную связь"</string>
+    <string name="turn_off_radio">"Отключить беспроводную связь"</string>
+    <string name="screen_lock">"Заблокировать экран"</string>
+    <string name="power_off">"Отключить питание"</string>
+    <string name="shutdown_progress">"Идет выключение…"</string>
+    <string name="shutdown_confirm">"Телефон будет выключен."</string>
+    <string name="no_recent_tasks">"Нет новых приложений."</string>
+    <string name="global_actions">"Параметры телефона"</string>
+    <string name="global_action_lock">"Заблокировать экран"</string>
+    <string name="global_action_power_off">"Отключить питание"</string>
+    <string name="global_action_toggle_silent_mode">"Беззвучный режим"</string>
+    <string name="global_action_silent_mode_on_status">"Звук выключен"</string>
+    <string name="global_action_silent_mode_off_status">"Звук включен"</string>
+    <!-- no translation found for global_actions_toggle_airplane_mode (5884330306926307456) -->
+    <skip />
+    <!-- no translation found for global_actions_airplane_mode_on_status (2719557982608919750) -->
+    <skip />
+    <!-- no translation found for global_actions_airplane_mode_off_status (5075070442854490296) -->
+    <skip />
+    <string name="safeMode">"Безопасный режим"</string>
+    <!-- no translation found for android_system_label (6577375335728551336) -->
+    <skip />
+    <string name="permgrouplab_costMoney">"Платные службы"</string>
+    <string name="permgroupdesc_costMoney">"Разрешить приложениям выполнять действия, за которые может взиматься плата."</string>
+    <string name="permgrouplab_messages">"Сообщения"</string>
+    <string name="permgroupdesc_messages">"Чтение и запись SMS, электронной почты и других сообщений."</string>
+    <string name="permgrouplab_personalInfo">"Личная информация"</string>
+    <string name="permgroupdesc_personalInfo">"Прямой доступ к вашим контактам и календарю, сохраненным на телефоне."</string>
+    <string name="permgrouplab_location">"Ваше местоположение"</string>
+    <string name="permgroupdesc_location">"Наблюдение за вашим местоположением"</string>
+    <string name="permgrouplab_network">"Связь с сетью"</string>
+    <string name="permgroupdesc_network">"Разрешить приложениям использовать сетевые функции."</string>
+    <string name="permgrouplab_accounts">"Ваши аккаунты Google"</string>
+    <string name="permgroupdesc_accounts">"Доступ к имеющимся аккаунтам Google."</string>
+    <string name="permgrouplab_hardwareControls">"Управление оборудованием"</string>
+    <string name="permgroupdesc_hardwareControls">"Прямой доступ к оборудованию телефона."</string>
+    <string name="permgrouplab_phoneCalls">"Телефонные вызовы"</string>
+    <string name="permgroupdesc_phoneCalls">"Слежение, запись и обработка вызовов."</string>
+    <string name="permgrouplab_systemTools">"Системные инструменты"</string>
+    <string name="permgroupdesc_systemTools">"Доступ и управление системой на низком уровне."</string>
+    <string name="permgrouplab_developmentTools">"Средства для разработки"</string>
+    <string name="permgroupdesc_developmentTools">"Функции, необходимые только разработчикам приложений."</string>
+    <string name="permlab_statusBar">"отключать или изменять панель состояния"</string>
+    <string name="permdesc_statusBar">"Разрешает приложению отключать панель состояния или добавлять и удалять системные значки."</string>
+    <string name="permlab_expandStatusBar">"разворачивать/сворачивать панель состояния"</string>
+    <string name="permdesc_expandStatusBar">"Разрешает приложению разворачивать и сворачивать панель состояния."</string>
+    <string name="permlab_processOutgoingCalls">"перехватывать исходящие вызовы"</string>
+    <string name="permdesc_processOutgoingCalls">"Разрешает приложению обрабатывать исходящие вызовы и изменять набираемый номер. Вредоносное ПО может следить, перенаправлять или блокировать исходящие вызовы."</string>
+    <string name="permlab_receiveSms">"получать SMS"</string>
+    <string name="permdesc_receiveSms">"Разрешает приложениям получать и обрабатывать сообщения SMS. Вредоносное ПО может отслеживать ваши сообщения или удалять до того, как вы их увидите."</string>
+    <string name="permlab_receiveMms">"получать MMS"</string>
+    <string name="permdesc_receiveMms">"Разрешает приложениям получать и обрабатывать сообщения MMS. Вредоносное ПО может отслеживать ваши сообщения или удалять их, не показав вам."</string>
+    <string name="permlab_sendSms">"отправлять SMS"</string>
+    <string name="permdesc_sendSms">"Разрешает приложениям отправлять SMS. Вредоносное ПО может тратить ваши деньги, отправляя сообщения без вашего ведома."</string>
+    <string name="permlab_readSms">"читать SMS и MMS"</string>
+    <string name="permdesc_readSms">"Разрешает приложению записывать SMS, сохраненные в телефоне или на SIM-карте. Вредоносное ПО может читать конфиденциальные сообщения."</string>
+    <string name="permlab_writeSms">"изменять SMS и MMS"</string>
+    <string name="permdesc_writeSms">"Разрешает приложению записывать данные в SMS, сохраненные в телефоне или на SIM-карте. Вредоносное ПО может удалять сообщения."</string>
+    <string name="permlab_receiveWapPush">"получать через WAP"</string>
+    <string name="permdesc_receiveWapPush">"Разрешает приложениям получать и обрабатывать сообщения WAP. Вредоносное ПО может отслеживать ваши сообщения или удалять до того, как вы их увидите."</string>
+    <string name="permlab_getTasks">"получать работающие приложения"</string>
+    <string name="permdesc_getTasks">"Разрешает приложению получать информацию о работающих и недавно выполненных задачах. Может позволить вредоносному ПО получать конфиденциальную информацию о других приложениях."</string>
+    <string name="permlab_reorderTasks">"переупорядочивать работающие приложения"</string>
+    <string name="permdesc_reorderTasks">"Разрешает приложению изменять приоритет задач. Вредоносное ПО может выходить на передний план без вашего разрешения."</string>
+    <string name="permlab_setDebugApp">"включать отладку приложений"</string>
+    <string name="permdesc_setDebugApp">"Разрешает приложению включать отладку других приложений. Вредоносное ПО может таким образом закрывать другие приложения."</string>
+    <string name="permlab_changeConfiguration">"изменять настройки интерфейса"</string>
+    <string name="permdesc_changeConfiguration">"Позволяет приложению изменять текущую конфигурацию, например локаль и общий размер шрифта."</string>
+    <string name="permlab_restartPackages">"перезапускать другие приложения"</string>
+    <string name="permdesc_restartPackages">"Разрешает приложению принудительно перезапускать другие приложения."</string>
+    <string name="permlab_setProcessForeground">"предотвращать остановку"</string>
+    <string name="permdesc_setProcessForeground">"Разрешает приложению запускать любые процессы на переднем плане так, что их нельзя прекратить. Не требуется обычным приложениям."</string>
+    <string name="permlab_forceBack">"принудительно закрывать приложения"</string>
+    <string name="permdesc_forceBack">"Позволяет приложению принудительно закрывать и переводить в фоновый режим действия, работающие на переднем плане. Не требуется обычным приложениям."</string>
+    <string name="permlab_dump">"получать внутреннее состояние системы"</string>
+    <string name="permdesc_dump">"Разрешает приложениям получать внутреннее состояние системы. Вредоносное ПО может получать множество личной и защищенной информации, которая обычно не была бы им доступна."</string>
+    <string name="permlab_addSystemService">"публиковать службы низкого уровня"</string>
+    <string name="permdesc_addSystemService">"Разрешает приложению публиковать собственные системные службы низкого уровня. Вредоносное ПО может взломать систему и украсть или повредить данные в ней."</string>
+    <string name="permlab_runSetActivityWatcher">"наблюдать и управлять запуском всех приложений"</string>
+    <string name="permdesc_runSetActivityWatcher">"Разрешает приложению следить и управлять тем, как система запускает действия. Вредоносное ПО может полностью нарушить работу системы. Это разрешение нужно только для разработки, но не при обычном использовании телефона."</string>
+    <string name="permlab_broadcastPackageRemoved">"отправлять оповещения об удалении пакетов"</string>
+    <string name="permdesc_broadcastPackageRemoved">"Позволяет приложению распространять уведомление об удалении пакета приложения. Вредоносное ПО может использовать это для остановки любых других работающих приложений."</string>
+    <string name="permlab_broadcastSmsReceived">"отправлять оповещения о получении SMS"</string>
+    <string name="permdesc_broadcastSmsReceived">"Разрешает приложению распространять уведомление о том, что получено сообщение SMS. Вредоносное ПО может пользоваться этим для фальсификации входящих сообщений SMS."</string>
+    <string name="permlab_broadcastWapPush">"отправлять оповещения о получении WAP-PUSH"</string>
+    <string name="permdesc_broadcastWapPush">"Разрешает приложению распространять уведомление о получении сообщения WAP PUSH. Вредоносное ПО может использовать это для подделки отчета о получении MMS или просто заменять содержание любой веб-страницы вредоносными вариантами."</string>
+    <string name="permlab_setProcessLimit">"ограничивать количество выполняемых процессов"</string>
+    <string name="permdesc_setProcessLimit">"Позволяет приложению контролировать максимальное количество выполняемых процессов. Не требуется обычным приложениям."</string>
+    <string name="permlab_setAlwaysFinish">"закрывать все фоновые приложения"</string>
+    <string name="permdesc_setAlwaysFinish">"Разрешает приложению следить, чтобы действия всегда завершались после перехода в фоновый режим. Не требуется обычным приложениям."</string>
+    <string name="permlab_fotaUpdate">"автоматически устанавливать системные обновления"</string>
+    <string name="permdesc_fotaUpdate">"Разрешает приложению получать уведомления о предстоящих обновлениях системы и запускать их установку. Это дает вредоносному ПО возможность повредить систему неавторизованными обновлениями или помешать выполнению обновления."</string>
+    <string name="permlab_batteryStats">"изменять данные о батарее"</string>
+    <string name="permdesc_batteryStats">"Разрешает изменять данные о батарее. Не используется обычными приложениями."</string>
+    <string name="permlab_internalSystemWindow">"отображать неавторизованные окна"</string>
+    <string name="permdesc_internalSystemWindow">"Разрешает создавать окна, используемые внутренним системным интерфейсом пользователя. Не для использования обычными программами."</string>
+    <string name="permlab_systemAlertWindow">"отображать системные предупреждения"</string>
+    <string name="permdesc_systemAlertWindow">"Разрешает приложению показывать окна системных предупреждений. Вредоносное ПО может захватить весь экран телефона."</string>
+    <string name="permlab_setAnimationScale">"изменять общую скорость анимации"</string>
+    <string name="permdesc_setAnimationScale">"Позволяет приложению в любой момент изменять общую скорость анимации (ускорять и замедлять анимацию)."</string>
+    <string name="permlab_manageAppTokens">"управлять маркерами приложений"</string>
+    <string name="permdesc_manageAppTokens">"Разрешает приложениям создавать и управлять собственными маркерами вместо обычного порядка Z. Не требуется обычным приложениям."</string>
+    <string name="permlab_injectEvents">"нажимать на клавиши и кнопки управления"</string>
+    <string name="permdesc_injectEvents">"Позволяет приложению передавать другим приложениям собственные события ввода (нажатия клавиш и т.д.). Вредоносное ПО может воспользоваться этим для захвата телефона."</string>
+    <string name="permlab_readInputState">"записывать нажимаемые клавиши и действия"</string>
+    <string name="permdesc_readInputState">"Разрешает приложениям отслеживать нажимаемые клавиши даже при работе в другом приложении (например набор пароля). Никогда не используется обычными приложениями."</string>
+    <string name="permlab_bindInputMethod">"выполнять привязку к способу ввода"</string>
+    <string name="permdesc_bindInputMethod">"Разрешает владельцу привязку к интерфейсу верхнего уровня способа ввода. Не требуется обычным приложениям."</string>
+    <string name="permlab_setOrientation">"изменять положение экрана"</string>
+    <string name="permdesc_setOrientation">"Позволяет приложению в любой момент изменять положение экрана. Не требуется обычным приложениям."</string>
+    <string name="permlab_signalPersistentProcesses">"отправлять приложениям Linux-сигналы"</string>
+    <string name="permdesc_signalPersistentProcesses">"Разрешает приложению запрашивать отправку поступающего сигнала всем постоянным процессам."</string>
+    <string name="permlab_persistentActivity">"выполнять приложение постоянно"</string>
+    <string name="permdesc_persistentActivity">"Позволяет приложению переводить свои компоненты в постоянное состояние так, что система не сможет использовать их для других приложений."</string>
+    <string name="permlab_deletePackages">"удалять приложения"</string>
+    <string name="permdesc_deletePackages">"Разрешает приложению удалять пакеты Android. Вредоносное ПО может воспользоваться этим для удаления важных приложений."</string>
+    <string name="permlab_clearAppUserData">"удалять данные других приложений"</string>
+    <string name="permdesc_clearAppUserData">"Разрешает приложению удалять данные пользователя."</string>
+    <string name="permlab_deleteCacheFiles">"удалять кэш других приложений"</string>
+    <string name="permdesc_deleteCacheFiles">"Позволяет приложению удалять файлы из кэша."</string>
+    <string name="permlab_getPackageSize">"измерять место для хранения данных приложений"</string>
+    <string name="permdesc_getPackageSize">"Разрешает приложению получать размеры своего кода, данных и кэша"</string>
+    <string name="permlab_installPackages">"напрямую устанавливать приложения"</string>
+    <string name="permdesc_installPackages">"Разрешает приложению устанавливать новые или обновленные пакеты Android. Вредоносное ПО может использовать это для добавления новых приложений с абсолютными полномочиями."</string>
+    <string name="permlab_clearAppCache">"удалять все данные из кэша приложений"</string>
+    <string name="permdesc_clearAppCache">"Разрешает приложению освобождать место на телефоне, удаляя файлы из каталога кэша приложения. Обычно доступ разрешен только системным процессам."</string>
+    <string name="permlab_readLogs">"читать файлы системного журнала"</string>
+    <string name="permdesc_readLogs">"Разрешает приложению считывать различные файлы журналов системы. Это позволяет получать сведения о том, что вы делаете с телефоном, но в этих файлах не должно быть личной или конфиденциальной информации."</string>
+    <string name="permlab_diagnostic">"считывать и записывать ресурсы, принадлежащих diag"</string>
+    <string name="permdesc_diagnostic">"Разрешает приложению считывать и записывать в любой ресурс, принадлежащий группе diag, например файлы в /dev. Это может влиять на стабильность и безопасность системы. Следует использовать ТОЛЬКО при диагностике оборудования производителем или оператором."</string>
+    <string name="permlab_changeComponentState">"включать и выключать компоненты приложений"</string>
+    <string name="permdesc_changeComponentState">"Разрешает приложению включать или отключать компоненты другого приложения. Вредоносное ПО сможет отключать важные функции телефона. Эту возможность следует использовать с осторожностью, так как можно привести компоненты приложений в нерабочее, несовместимое или нестабильное состояние."</string>
+    <string name="permlab_setPreferredApplications">"задавать предпочитаемые приложения"</string>
+    <string name="permdesc_setPreferredApplications">"Позволяет приложению изменять предпочитаемые приложения. Это может позволить вредоносному ПО незаметно изменить работающие приложения поддельными  и собрать ваши личные данные."</string>
+    <string name="permlab_writeSettings">"изменять глобальные настройки системы"</string>
+    <string name="permdesc_writeSettings">"Разрешает приложению изменять данные настройки системы. Вредоносное ПО может повредить конфигурацию вашей системы."</string>
+    <string name="permlab_writeSecureSettings">"изменять защищенные настройки системы"</string>
+    <string name="permdesc_writeSecureSettings">"Разрешает приложению изменять данные в защищенных настройках системы. Не используется обычными приложениями."</string>
+    <string name="permlab_writeGservices">"изменять карту служб Google"</string>
+    <string name="permdesc_writeGservices">"Разрешает приложению изменять карту служб Google. Не используется обычными приложениями."</string>
+    <string name="permlab_receiveBootCompleted">"автоматически запускаться при загрузке"</string>
+    <string name="permdesc_receiveBootCompleted">"Разрешает приложению запускаться сразу после загрузки системы. Это может увеличить время запуска телефона, а постоянная работа приложения может снизить общую скорость работы."</string>
+    <string name="permlab_broadcastSticky">"отправлять постоянное оповещение"</string>
+    <string name="permdesc_broadcastSticky">"Разрешает приложению отправлять постоянные оповещения, остающиеся после окончания рассылки. Вредоносное ПО может замедлить работу телефона или привести к нестабильности, используя слишком много памяти."</string>
+    <string name="permlab_readContacts">"считывать данные контактов"</string>
+    <string name="permdesc_readContacts">"Разрешает приложению считывать полную контактную информацию (адрес), сохраненную в вашем телефоне. Может использоваться вредоносным ПО для отправки этих данных другим людям."</string>
+    <string name="permlab_writeContacts">"записывать данные контактов"</string>
+    <string name="permdesc_writeContacts">"Разрешает приложению изменять контактную информацию (адрес), сохраненную в телефоне. Вредоносное ПО может использовать это для удаления или изменения данных контактов."</string>
+    <string name="permlab_writeOwnerData">"записывать данные о владельце"</string>
+    <string name="permdesc_writeOwnerData">"Разрешает приложению изменять данные о владельце, сохраненные в телефоне. Вредоносное ПО может использовать это для удаления или изменения данных о владельце."</string>
+    <string name="permlab_readOwnerData">"считывать данные о владельце"</string>
+    <string name="permdesc_readOwnerData">"Разрешает приложению считывать данные о владельце, сохраненные в телефоне. Вредоносное ПО может использовать это для чтения данных о владельце."</string>
+    <string name="permlab_readCalendar">"считывать данные календаря"</string>
+    <string name="permdesc_readCalendar">"Разрешает приложению считывать все события календаря, сохраненные в телефоне. Вредоносное ПО может использовать это для отправки событий вашего календаря другим людям."</string>
+    <string name="permlab_writeCalendar">"записывать данные календаря"</string>
+    <string name="permdesc_writeCalendar">"Позволяет приложению изменять сохраненные в телефоне события календаря. Вредоносное ПО сможет стирать или изменять данные календаря."</string>
+    <string name="permlab_accessMockLocation">"имитировать источники данных о положении для тестирования"</string>
+    <string name="permdesc_accessMockLocation">"Создание имитаций источников данных о местоположении для тестирования. Вредоносное ПО может использовать это для изменения данных о местоположении и/или состоянии, сообщаемых настоящими источниками таких данных, например GPS или оператором связи."</string>
+    <string name="permlab_accessLocationExtraCommands">"получать доступ к дополнительным командам местоположения от поставщика связи"</string>
+    <string name="permdesc_accessLocationExtraCommands">"Доступ к дополнительным командам местоположения от поставщика связи. Это дает вредоносному ПО возможность мешать работе GPS или других источников данных о местоположении."</string>
+    <string name="permlab_accessFineLocation">"определять точное местоположение (GPS)"</string>
+    <string name="permdesc_accessFineLocation">"Доступ к имеющимся источникам точных данных о местоположении телефона, например к GPS. Вредоносное ПО сможет узнать, где вы находитесь, а также истратить заряд батареи."</string>
+    <string name="permlab_accessCoarseLocation">"определять приблизительное местоположение (на основе данных сети)"</string>
+    <string name="permdesc_accessCoarseLocation">"Доступ (при наличии) к источникам сведений о приблизительном расположении, например базам данных сотовой сети. Вредоносное ПО может использовать эту функцию для того, чтобы определять ваше местоположение."</string>
+    <string name="permlab_accessSurfaceFlinger">"открывать SurfaceFlinger"</string>
+    <string name="permdesc_accessSurfaceFlinger">"Разрешает приложению использовать функции SurfaceFlinger низкого уровня."</string>
+    <string name="permlab_readFrameBuffer">"считывать данные из буфера фреймов"</string>
+    <string name="permdesc_readFrameBuffer">"Разрешает приложению считывать содержание буфера фрейма."</string>
+    <string name="permlab_modifyAudioSettings">"изменять настройки звука"</string>
+    <string name="permdesc_modifyAudioSettings">"Разрешает приложениям изменять глобальные настройки звука, например громкость и маршрут сигнала."</string>
+    <string name="permlab_recordAudio">"записывать звук"</string>
+    <string name="permdesc_recordAudio">"Разрешает приложению получать доступ к пути записи звука."</string>
+    <string name="permlab_camera">"снимать фотографии"</string>
+    <string name="permdesc_camera">"Разрешает приложению делать снимки с помощью камеры. Это позволяет приложению в любой момент записывать то, что видно через камеру."</string>
+    <string name="permlab_brick">"отключать телефон навсегда"</string>
+    <string name="permdesc_brick">"Разрешает приложению навсегда отключить телефон. Это очень опасно."</string>
+    <string name="permlab_reboot">"принудительно перезагружать телефон"</string>
+    <string name="permdesc_reboot">"Разрешает приложению принудительно перезагружать телефон."</string>
+    <string name="permlab_mount_unmount_filesystems">"подключаться и отключаться от файловых систем"</string>
+    <string name="permdesc_mount_unmount_filesystems">"Разрешает приложению подключаться и отключаться от файловых систем съемных устройств хранения."</string>
+    <string name="permlab_mount_format_filesystems">"форматировать внешний накопитель"</string>
+    <string name="permdesc_mount_format_filesystems">"Позволяет приложению форматировать съемный накопитель."</string>
+    <string name="permlab_vibrate">"управлять вибрацией"</string>
+    <string name="permdesc_vibrate">"Разрешает приложению управлять вибровызовом."</string>
+    <string name="permlab_flashlight">"управлять фонарем"</string>
+    <string name="permdesc_flashlight">"Разрешает приложению управлять фонарем."</string>
+    <string name="permlab_hardware_test">"проверять оборудование"</string>
+    <string name="permdesc_hardware_test">"Разрешает приложению управлять внешними устройствами для тестирования оборудования."</string>
+    <string name="permlab_callPhone">"напрямую вызывать телефонные номера"</string>
+    <string name="permdesc_callPhone">"Разрешает приложениям вызывать телефонные номера без вашего участия. Вредоносное ПО может выполнять вызовы, за которые придется платить. Заметьте, что это не разрешает приложению вызывать номера экстренных служб."</string>
+    <string name="permlab_callPrivileged">"напрямую вызывать любые телефонные номера"</string>
+    <string name="permdesc_callPrivileged">"Разрешает приложению вызывать любой номер, включая экстренные, без вашего участия. Вредоносное ПО может совершать ненужные и незаконные вызовы в службы неотложной помощи."</string>
+    <string name="permlab_locationUpdates">"управлять уведомлениями об обновлении местоположения"</string>
+    <string name="permdesc_locationUpdates">"Разрешает включение/отключение уведомлений о местоположении по радиосвязи. Не используется обычными приложениями."</string>
+    <string name="permlab_checkinProperties">"открывать свойства проверки"</string>
+    <string name="permdesc_checkinProperties">"Разрешает доступ на чтение и запись к свойствам, загруженным службой проверки. Не используется обычными приложениями."</string>
+    <string name="permlab_bindGadget">"выбирать гаджеты"</string>
+    <string name="permdesc_bindGadget">"Позволяет приложению сообщить системе, какие приложения могут использовать какие гаджеты. Это разрешение позволяет приложениям предоставлять другим приложениям доступ к личной информации. Не предназначено для использования обычными приложениями."</string>
+    <string name="permlab_modifyPhoneState">"изменять состояние телефона"</string>
+    <string name="permdesc_modifyPhoneState">"Позволяет приложению управлять телефонными функциями устройства. Приложение с такими полномочиями может переключать сети, включать и выключать радиосвязь и т.д., не сообщая вам об этом."</string>
+    <string name="permlab_readPhoneState">"считывать состояние телефона"</string>
+    <string name="permdesc_readPhoneState">"Разрешает приложению использовать телефонные функции устройства. Приложение с такими полномочиями может определить номер данного телефона, наличие вызова, номер, с которым связан вызов и так далее."</string>
+    <string name="permlab_wakeLock">"предотвращать переход телефона в режим ожидания"</string>
+    <string name="permdesc_wakeLock">"Разрешает приложению блокировать переход телефона в режим ожидания."</string>
+    <string name="permlab_devicePower">"включать и отключать телефон"</string>
+    <string name="permdesc_devicePower">"Позволяет приложениям включать и выключать телефон."</string>
+    <string name="permlab_factoryTest">"работа в режиме заводского тестирования"</string>
+    <string name="permdesc_factoryTest">"Работа в качестве теста производителя низкого уровня, что дает полный доступ к оборудованию телефона. Доступно только при работе телефона в режиме теста производителя."</string>
+    <string name="permlab_setWallpaper">"устанавливать фоновый рисунок"</string>
+    <string name="permdesc_setWallpaper">"Разрешает приложению устанавливать системный фоновый рисунок."</string>
+    <string name="permlab_setWallpaperHints">"устанавливать подсказки по размеру фонового рисунка"</string>
+    <string name="permdesc_setWallpaperHints">"Разрешает приложению устанавливать системные подсказки по размеру фонового рисунка."</string>
+    <string name="permlab_masterClear">"выполнять сброс системы и восстановление заводских настроек"</string>
+    <string name="permdesc_masterClear">"Разрешает приложению полностью сбрасывать настройки системы до заводских, удаляя все данные, конфигурацию и установленные приложения."</string>
+    <string name="permlab_setTimeZone">"устанавливать часовой пояс"</string>
+    <string name="permdesc_setTimeZone">"Разрешает приложению изменять часовой пояс телефона."</string>
+    <string name="permlab_getAccounts">"обнаруживать известные аккаунты"</string>
+    <string name="permdesc_getAccounts">"Разрешает приложению получать список аккаунтов, известных телефону."</string>
+    <string name="permlab_accessNetworkState">"просматривать состояние сети"</string>
+    <string name="permdesc_accessNetworkState">"Позволяет приложению видеть состояние всех сетей."</string>
+    <string name="permlab_createNetworkSockets">"обладать полным доступом в Интернет"</string>
+    <string name="permdesc_createNetworkSockets">"Позволяет приложению создавать сетевые сокеты."</string>
+    <string name="permlab_writeApnSettings">"записывать настройки названий точек доступа"</string>
+    <string name="permdesc_writeApnSettings">"Разрешает приложению изменять настройки APN, например прокси и порт любого APN."</string>
+    <string name="permlab_changeNetworkState">"изменять подключение к сети"</string>
+    <string name="permdesc_changeNetworkState">"Позволяет приложению изменять подключение к сети."</string>
+    <string name="permlab_changeBackgroundDataSetting">"изменить настройку использования фоновых данных"</string>
+    <string name="permdesc_changeBackgroundDataSetting">"Позволяет приложению изменять настройку использования фоновых данных."</string>
+    <string name="permlab_accessWifiState">"просматривать состояние Wi-Fi"</string>
+    <string name="permdesc_accessWifiState">"Разрешает приложению просматривать сведения о состоянии Wi-Fi."</string>
+    <string name="permlab_changeWifiState">"изменять состояние Wi-Fi"</string>
+    <string name="permdesc_changeWifiState">"Разрешает приложению подключаться и отключаться от точек доступа Wi-Fi и вносить изменения в настроенные сети Wi-Fi."</string>
+    <string name="permlab_bluetoothAdmin">"управлять Bluetooth"</string>
+    <string name="permdesc_bluetoothAdmin">"Разрешает приложению настраивать локальный телефон с Bluetooth, а также обнаруживать удаленные устройства и соединяться с ними."</string>
+    <string name="permlab_bluetooth">"создавать Bluetooth-подключения"</string>
+    <string name="permdesc_bluetooth">"Позволяет приложению просматривать конфигурацию локального телефона Bluetooth, создавать и разрешать подключение к связанным устройствам."</string>
+    <string name="permlab_disableKeyguard">"отключать блокировку клавиатуры"</string>
+    <string name="permdesc_disableKeyguard">"Разрешает приложению отключать блокировку клавиш и связанную с ней защиту паролем. Пример нормального использования – отключение блокировки телефоном, когда он принимает вызов, и ее включение после окончания вызова."</string>
+    <string name="permlab_readSyncSettings">"считывать настройки синхронизации"</string>
+    <string name="permdesc_readSyncSettings">"Разрешает приложению считывать настройки синхронизации, например наличие синхронизации контактов."</string>
+    <string name="permlab_writeSyncSettings">"записывать настройки синхронизации"</string>
+    <string name="permdesc_writeSyncSettings">"Разрешает приложению изменять настройки синхронизации, например синхронизацию контактов."</string>
+    <string name="permlab_readSyncStats">"считывать данные о синхронизации"</string>
+    <string name="permdesc_readSyncStats">"Разрешает приложению считывать данные о синхронизации, например историю выполненных синхронизаций."</string>
+    <string name="permlab_subscribedFeedsRead">"считывать фиды с подпиской"</string>
+    <string name="permdesc_subscribedFeedsRead">"Позволяет приложению получать сведения о синхронизированных фидах."</string>
+    <string name="permlab_subscribedFeedsWrite">"записывать фиды с подпиской"</string>
+    <string name="permdesc_subscribedFeedsWrite">"Разрешает приложению изменять ваши синхронизированные фиды. Это может позволить вредоносному ПО изменять ваши синхронизированные фиды."</string>
+    <string name="permlab_readDictionary">"выполнять чтение из пользовательского словаря"</string>
+    <string name="permdesc_readDictionary">"Позволяет приложению считывать любые слова, имена и фразы личного пользования, которые могут храниться в пользовательском словаре."</string>
+    <string name="permlab_writeDictionary">"записывать в пользовательский словарь"</string>
+    <string name="permdesc_writeDictionary">"Позволяет приложению записывать новые слова в пользовательский словарь."</string>
+  <string-array name="phoneTypes">
+    <item>"Домашний"</item>
+    <item>"Мобильный"</item>
+    <item>"Рабочий"</item>
+    <item>"Рабочий факс"</item>
+    <item>"Домашний факс"</item>
+    <item>"Пейджер"</item>
+    <item>"Другое"</item>
+    <item>"Другой"</item>
+  </string-array>
+  <string-array name="emailAddressTypes">
+    <item>"Домашний"</item>
+    <item>"Рабочий"</item>
+    <item>"Другое"</item>
+    <item>"Другой"</item>
+  </string-array>
+  <string-array name="postalAddressTypes">
+    <item>"Домашний"</item>
+    <item>"Рабочий"</item>
+    <item>"Другое"</item>
+    <item>"Другой"</item>
+  </string-array>
+  <string-array name="imAddressTypes">
+    <item>"Домашний"</item>
+    <item>"Рабочий"</item>
+    <item>"Другое"</item>
+    <item>"Другой"</item>
+  </string-array>
+  <string-array name="organizationTypes">
+    <item>"Рабочий"</item>
+    <item>"Другое"</item>
+    <item>"Другой"</item>
+  </string-array>
+  <string-array name="imProtocols">
+    <item>"AIM"</item>
+    <item>"Windows Live"</item>
+    <item>"Yahoo"</item>
+    <item>"Skype"</item>
+    <item>"QQ"</item>
+    <item>"Google Talk"</item>
+    <item>"ICQ"</item>
+    <item>"Jabber"</item>
+  </string-array>
+    <string name="keyguard_password_enter_pin_code">"Введите PIN-код"</string>
+    <string name="keyguard_password_wrong_pin_code">"Неверный PIN-код!"</string>
+    <string name="keyguard_label_text">"Для разблокировки нажмите Menu и 0."</string>
+    <string name="emergency_call_dialog_number_for_display">"Номер экстренной службы"</string>
+    <string name="lockscreen_carrier_default">"(Вне зоны обслуживания)"</string>
+    <string name="lockscreen_screen_locked">"Экран заблокирован."</string>
+    <string name="lockscreen_instructions_when_pattern_enabled">"Нажмите Menu для разблокировки или выполните экстренный вызов."</string>
+    <string name="lockscreen_instructions_when_pattern_disabled">"Чтобы снять блокировку, нажмите Menu."</string>
+    <string name="lockscreen_pattern_instructions">"Для разблокировки воспроизведите комбинацию"</string>
+    <string name="lockscreen_emergency_call">"Экстренный вызов"</string>
+    <string name="lockscreen_pattern_correct">"Верно!"</string>
+    <string name="lockscreen_pattern_wrong">"Неверно, попробуйте еще раз"</string>
+    <!-- no translation found for lockscreen_plugged_in (613343852842944435) -->
+    <skip />
+    <string name="lockscreen_low_battery">"Подключите зарядное устройство."</string>
+    <string name="lockscreen_missing_sim_message_short">"Нет SIM-карты."</string>
+    <string name="lockscreen_missing_sim_message">"В телефоне нет SIM-карты."</string>
+    <string name="lockscreen_missing_sim_instructions">"Вставьте SIM-карту."</string>
+    <string name="lockscreen_network_locked_message">"Заблокирована сетью"</string>
+    <string name="lockscreen_sim_puk_locked_message">"SIM-карта заблокирована PUK-кодом."</string>
+    <string name="lockscreen_sim_puk_locked_instructions">"Свяжитесь со службой поддержки."</string>
+    <string name="lockscreen_sim_locked_message">"SIM-карта заблокирована."</string>
+    <string name="lockscreen_sim_unlock_progress_dialog_message">"Разблокировка SIM-карты..."</string>
+    <string name="lockscreen_too_many_failed_attempts_dialog_message">"Вы неправильно воспроизвели комбинацию разблокировки <xliff:g id="NUMBER_0">%d</xliff:g> раз. "\n\n"Повторите попытку через <xliff:g id="NUMBER_1">%d</xliff:g> сек."</string>
+    <string name="lockscreen_failed_attempts_almost_glogin">"Вы неверно воспроизвели комбинацию разблокировки <xliff:g id="NUMBER_0">%d</xliff:g> раз(а). После <xliff:g id="NUMBER_1">%d</xliff:g> неудачных попыток(ки) придется разблокировать телефон с помощью входа Google."\n\n" Повторите попытку через <xliff:g id="NUMBER_2">%d</xliff:g> сек."</string>
+    <string name="lockscreen_too_many_failed_attempts_countdown">"Повторите попытку через <xliff:g id="NUMBER">%d</xliff:g> сек."</string>
+    <string name="lockscreen_forgot_pattern_button_text">"Забыли комбинацию?"</string>
+    <string name="lockscreen_glogin_too_many_attempts">"Слишком много попыток ввести комбинацию!"</string>
+    <string name="lockscreen_glogin_instructions">"Для разблокировки"\n"войдите с помощью своего аккаунта Google"</string>
+    <string name="lockscreen_glogin_username_hint">"Имя пользователя (адрес электронной почты)"</string>
+    <string name="lockscreen_glogin_password_hint">"Пароль"</string>
+    <string name="lockscreen_glogin_submit_button">"Войти"</string>
+    <string name="lockscreen_glogin_invalid_input">"Недействительное имя пользователя или пароль."</string>
+    <string name="status_bar_time_format">"<xliff:g id="HOUR">h</xliff:g>:<xliff:g id="MINUTE">mm</xliff:g> <xliff:g id="AMPM">AA</xliff:g>"</string>
+    <string name="hour_minute_ampm">"<xliff:g id="HOUR">%-l</xliff:g>:<xliff:g id="MINUTE">%M</xliff:g><xliff:g id="AMPM">%P</xliff:g>"</string>
+    <string name="hour_minute_cap_ampm">"<xliff:g id="HOUR">%-l</xliff:g>:<xliff:g id="MINUTE">%M</xliff:g><xliff:g id="AMPM">%p</xliff:g>"</string>
+    <!-- no translation found for hour_ampm (4329881288269772723) -->
+    <skip />
+    <!-- no translation found for hour_cap_ampm (1829009197680861107) -->
+    <skip />
+    <string name="status_bar_clear_all_button">"Очистить уведомления"</string>
+    <string name="status_bar_no_notifications_title">"Нет уведомлений"</string>
+    <string name="status_bar_ongoing_events_title">"Текущие"</string>
+    <string name="status_bar_latest_events_title">"Уведомления"</string>
+    <!-- no translation found for battery_status_text_percent_format (7660311274698797147) -->
+    <skip />
+    <string name="battery_status_charging">"Идет зарядка..."</string>
+    <string name="battery_low_title">"Подключите зарядное устройство"</string>
+    <string name="battery_low_subtitle">"Батарея садится:"</string>
+    <string name="battery_low_percent_format">"осталось менее <xliff:g id="NUMBER">%d%%</xliff:g>."</string>
+    <string name="factorytest_failed">"Ошибка заводского теста"</string>
+    <string name="factorytest_not_system">"Действие FACTORY_TEST поддерживается только для пакетов, установленных в папке /system/app."</string>
+    <string name="factorytest_no_action">"Пакет, предоставляющий действие FACTORY_TEST, не найден."</string>
+    <string name="factorytest_reboot">"Перезагрузить"</string>
+    <string name="js_dialog_title">"На странице по адресу \"<xliff:g id="TITLE">%s</xliff:g>\" сказано:"</string>
+    <string name="js_dialog_title_default">"JavaScript"</string>
+    <string name="js_dialog_before_unload">"Перейти с этой страницы?"\n\n"<xliff:g id="MESSAGE">%s</xliff:g>"\n\n"Нажмите \"ОК\", чтобы продолжить, или \"Отмена\", чтобы остаться на текущей странице."</string>
+    <string name="save_password_label">"Подтверждение"</string>
+    <string name="save_password_message">"Сохранить этот пароль в браузере?"</string>
+    <string name="save_password_notnow">"Не сейчас"</string>
+    <string name="save_password_remember">"Запомнить"</string>
+    <string name="save_password_never">"Никогда"</string>
+    <string name="open_permission_deny">"У вас нет разрешения открывать эту страницу."</string>
+    <string name="text_copied">"Текст скопирован в буфер обмена."</string>
+    <string name="more_item_label">"Еще"</string>
+    <string name="prepend_shortcut_label">"Мenu+"</string>
+    <string name="menu_space_shortcut_label">"место"</string>
+    <string name="menu_enter_shortcut_label">"ввод"</string>
+    <string name="menu_delete_shortcut_label">"удалить"</string>
+    <string name="search_go">"Поиск"</string>
+    <string name="today">"Сегодня"</string>
+    <string name="yesterday">"Вчера"</string>
+    <string name="tomorrow">"Завтра"</string>
+    <string name="oneMonthDurationPast">"1 месяц назад"</string>
+    <string name="beforeOneMonthDurationPast">"Больше 1 месяца назад"</string>
+  <plurals name="num_seconds_ago">
+    <item quantity="one">"1 секунду назад"</item>
+    <item quantity="other">"<xliff:g id="COUNT">%d</xliff:g> сек. назад"</item>
+  </plurals>
+  <plurals name="num_minutes_ago">
+    <item quantity="one">"1 минуту назад"</item>
+    <item quantity="other">"<xliff:g id="COUNT">%d</xliff:g> мин. назад"</item>
+  </plurals>
+  <plurals name="num_hours_ago">
+    <item quantity="one">"1 час назад"</item>
+    <item quantity="other">"<xliff:g id="COUNT">%d</xliff:g> ч. назад"</item>
+  </plurals>
+  <plurals name="num_days_ago">
+    <item quantity="one">"вчера"</item>
+    <item quantity="other">"<xliff:g id="COUNT">%d</xliff:g> дн. назад"</item>
+  </plurals>
+  <plurals name="in_num_seconds">
+    <item quantity="one">"через 1 секунду"</item>
+    <item quantity="other">"через <xliff:g id="COUNT">%d</xliff:g> сек."</item>
+  </plurals>
+  <plurals name="in_num_minutes">
+    <item quantity="one">"через 1 минуту"</item>
+    <item quantity="other">"через <xliff:g id="COUNT">%d</xliff:g> мин."</item>
+  </plurals>
+  <plurals name="in_num_hours">
+    <item quantity="one">"через 1 час"</item>
+    <item quantity="other">"через <xliff:g id="COUNT">%d</xliff:g> час."</item>
+  </plurals>
+  <plurals name="in_num_days">
+    <item quantity="one">"завтра"</item>
+    <item quantity="other">"через <xliff:g id="COUNT">%d</xliff:g> дн."</item>
+  </plurals>
+  <plurals name="abbrev_num_seconds_ago">
+    <item quantity="one">"1 сек. назад"</item>
+    <item quantity="other">"<xliff:g id="COUNT">%d</xliff:g> сек. назад"</item>
+  </plurals>
+  <plurals name="abbrev_num_minutes_ago">
+    <item quantity="one">"1 мин. назад"</item>
+    <item quantity="other">"<xliff:g id="COUNT">%d</xliff:g> мин. назад"</item>
+  </plurals>
+  <plurals name="abbrev_num_hours_ago">
+    <item quantity="one">"1 час назад"</item>
+    <item quantity="other">"<xliff:g id="COUNT">%d</xliff:g> ч. назад"</item>
+  </plurals>
+  <plurals name="abbrev_num_days_ago">
+    <item quantity="one">"вчера"</item>
+    <item quantity="other">"<xliff:g id="COUNT">%d</xliff:g> дн. назад"</item>
+  </plurals>
+  <plurals name="abbrev_in_num_seconds">
+    <item quantity="one">"через 1 сек."</item>
+    <item quantity="other">"через <xliff:g id="COUNT">%d</xliff:g> сек."</item>
+  </plurals>
+  <plurals name="abbrev_in_num_minutes">
+    <item quantity="one">"через 1 мин."</item>
+    <item quantity="other">"через <xliff:g id="COUNT">%d</xliff:g> мин."</item>
+  </plurals>
+  <plurals name="abbrev_in_num_hours">
+    <item quantity="one">"через 1 час"</item>
+    <item quantity="other">"через <xliff:g id="COUNT">%d</xliff:g> час."</item>
+  </plurals>
+  <plurals name="abbrev_in_num_days">
+    <item quantity="one">"завтра"</item>
+    <item quantity="other">"через <xliff:g id="COUNT">%d</xliff:g> дн."</item>
+  </plurals>
+    <string name="preposition_for_date">"%s"</string>
+    <string name="preposition_for_time">"в %s"</string>
+    <string name="preposition_for_year">"в %s"</string>
+    <string name="day">"день"</string>
+    <string name="days">"дни"</string>
+    <string name="hour">"час"</string>
+    <string name="hours">"часы"</string>
+    <string name="minute">"мин"</string>
+    <string name="minutes">"мин."</string>
+    <string name="second">"сек"</string>
+    <string name="seconds">"сек."</string>
+    <string name="week">"неделя"</string>
+    <string name="weeks">"недели"</string>
+    <string name="year">"год"</string>
+    <string name="years">"годы"</string>
+    <string name="sunday">"воскресенье"</string>
+    <string name="monday">"понедельник"</string>
+    <string name="tuesday">"вторник"</string>
+    <string name="wednesday">"среда"</string>
+    <string name="thursday">"четверг"</string>
+    <string name="friday">"пятница"</string>
+    <string name="saturday">"суббота"</string>
+    <string name="every_weekday">"По рабочим дням (пн-пт)"</string>
+    <string name="daily">"Ежедневно"</string>
+    <string name="weekly">"Еженедельно в: <xliff:g id="DAY">%s</xliff:g>"</string>
+    <string name="monthly">"Ежемесячно"</string>
+    <string name="yearly">"Ежегодно"</string>
+    <string name="VideoView_error_title">"Не удается воспроизвести видео"</string>
+    <string name="VideoView_error_text_unknown">"К сожалению, это видео нельзя воспроизвести."</string>
+    <string name="VideoView_error_button">"ОК"</string>
+    <string name="am">"AM"</string>
+    <string name="pm">"PM"</string>
+    <string name="numeric_date">"<xliff:g id="DAY">%d</xliff:g>/<xliff:g id="MONTH">%m</xliff:g>/<xliff:g id="YEAR">%Y</xliff:g>"</string>
+    <string name="wday1_date1_time1_wday2_date2_time2">"<xliff:g id="WEEKDAY1">%1$s</xliff:g>, <xliff:g id="DATE1">%2$s</xliff:g>, <xliff:g id="TIME1">%3$s</xliff:g> – <xliff:g id="WEEKDAY2">%4$s</xliff:g>, <xliff:g id="DATE2">%5$s</xliff:g>, <xliff:g id="TIME2">%6$s</xliff:g>"</string>
+    <string name="wday1_date1_wday2_date2">"<xliff:g id="WEEKDAY1">%1$s</xliff:g>, <xliff:g id="DATE1">%2$s</xliff:g> – <xliff:g id="WEEKDAY2">%4$s</xliff:g>, <xliff:g id="DATE2">%5$s</xliff:g>"</string>
+    <string name="date1_time1_date2_time2">"<xliff:g id="DATE1">%2$s</xliff:g>, <xliff:g id="TIME1">%3$s</xliff:g> – <xliff:g id="DATE2">%5$s</xliff:g>, <xliff:g id="TIME2">%6$s</xliff:g>"</string>
+    <string name="date1_date2">"<xliff:g id="DATE1">%2$s</xliff:g> – <xliff:g id="DATE2">%5$s</xliff:g>"</string>
+    <string name="time1_time2">"<xliff:g id="TIME1">%1$s</xliff:g> – <xliff:g id="TIME2">%2$s</xliff:g>"</string>
+    <string name="time_wday_date">"<xliff:g id="TIME_RANGE">%1$s</xliff:g>, <xliff:g id="WEEKDAY">%2$s</xliff:g>, <xliff:g id="DATE">%3$s</xliff:g>"</string>
+    <string name="wday_date">"<xliff:g id="WEEKDAY">%2$s</xliff:g>, <xliff:g id="DATE">%3$s</xliff:g>"</string>
+    <string name="time_date">"<xliff:g id="TIME_RANGE">%1$s</xliff:g>, <xliff:g id="DATE">%3$s</xliff:g>"</string>
+    <string name="date_time">"<xliff:g id="DATE">%1$s</xliff:g>, <xliff:g id="TIME">%2$s</xliff:g>"</string>
+    <string name="relative_time">"<xliff:g id="DATE">%1$s</xliff:g>, <xliff:g id="TIME">%2$s</xliff:g>"</string>
+    <string name="time_wday">"<xliff:g id="TIME_RANGE">%1$s</xliff:g>, <xliff:g id="WEEKDAY">%2$s</xliff:g>"</string>
+    <string name="full_date_month_first" format="date">"<xliff:g id="DAY">d</xliff:g>' '<xliff:g id="MONTH">MMMM</xliff:g>' '<xliff:g id="YEAR">yyyy</xliff:g>"</string>
+    <string name="full_date_day_first" format="date">"<xliff:g id="DAY">d</xliff:g>' '<xliff:g id="MONTH">MMMM</xliff:g>' '<xliff:g id="YEAR">yyyy</xliff:g>"</string>
+    <string name="medium_date_month_first" format="date">"<xliff:g id="DAY">d</xliff:g>' '<xliff:g id="MONTH">MMM</xliff:g>' '<xliff:g id="YEAR">yyyy</xliff:g>"</string>
+    <string name="medium_date_day_first" format="date">"<xliff:g id="DAY">d</xliff:g>' '<xliff:g id="MONTH">MMM</xliff:g>' '<xliff:g id="YEAR">yyyy</xliff:g>"</string>
+    <string name="twelve_hour_time_format" format="date">"<xliff:g id="HOUR">h</xliff:g>':'<xliff:g id="MINUTE">mm</xliff:g>' '<xliff:g id="AMPM">a</xliff:g>"</string>
+    <string name="twenty_four_hour_time_format" format="date">"<xliff:g id="HOUR">H</xliff:g>':'<xliff:g id="MINUTE">mm</xliff:g>"</string>
+    <string name="noon">"полдень"</string>
+    <string name="Noon">"Полдень"</string>
+    <string name="midnight">"полночь"</string>
+    <string name="Midnight">"Полночь"</string>
+    <!-- no translation found for month_day (3693060561170538204) -->
+    <skip />
+    <!-- no translation found for month (1976700695144952053) -->
+    <skip />
+    <string name="month_day_year">"<xliff:g id="DAY">%-d</xliff:g> <xliff:g id="MONTH">%B</xliff:g> <xliff:g id="YEAR">%Y</xliff:g>"</string>
+    <!-- no translation found for month_year (2106203387378728384) -->
+    <skip />
+    <string name="time_of_day">"<xliff:g id="HOUR">%H</xliff:g>:<xliff:g id="MINUTE">%M</xliff:g>:<xliff:g id="SECOND">%S</xliff:g>"</string>
+    <string name="date_and_time">"<xliff:g id="HOUR">%H</xliff:g>:<xliff:g id="MINUTE">%M</xliff:g>:<xliff:g id="SECOND">%S</xliff:g> <xliff:g id="DAY">%-d</xliff:g> <xliff:g id="MONTH">%B</xliff:g> <xliff:g id="YEAR">%Y</xliff:g>"</string>
+    <string name="same_year_md1_md2">"<xliff:g id="DAY1">%3$s</xliff:g> <xliff:g id="MONTH1">%2$s</xliff:g> – <xliff:g id="DAY2">%8$s</xliff:g> <xliff:g id="MONTH2">%7$s</xliff:g>"</string>
+    <string name="same_year_wday1_md1_wday2_md2">"<xliff:g id="WEEKDAY1">%1$s</xliff:g>, <xliff:g id="DAY1_0">%3$s</xliff:g> <xliff:g id="MONTH1">%2$s</xliff:g> – <xliff:g id="WEEKDAY2">%6$s</xliff:g>, <xliff:g id="DAY2_1">%8$s</xliff:g> <xliff:g id="MONTH2">%7$s</xliff:g>"</string>
+    <string name="same_year_mdy1_mdy2">"<xliff:g id="DAY1">%3$s</xliff:g> <xliff:g id="MONTH1">%2$s</xliff:g> – <xliff:g id="DAY2">%8$s</xliff:g> <xliff:g id="MONTH2">%7$s</xliff:g> <xliff:g id="YEAR">%9$s</xliff:g>"</string>
+    <string name="same_year_wday1_mdy1_wday2_mdy2">"<xliff:g id="WEEKDAY1">%1$s</xliff:g>, <xliff:g id="DAY1_0">%3$s</xliff:g> <xliff:g id="MONTH1">%2$s</xliff:g> – <xliff:g id="WEEKDAY2">%6$s</xliff:g>, <xliff:g id="DAY2_1">%8$s</xliff:g> <xliff:g id="MONTH2">%7$s</xliff:g> <xliff:g id="YEAR">%9$s</xliff:g>"</string>
+    <string name="same_year_md1_time1_md2_time2">"<xliff:g id="DAY1">%3$s</xliff:g> <xliff:g id="MONTH1">%2$s</xliff:g>, <xliff:g id="TIME1">%5$s</xliff:g> – <xliff:g id="DAY2">%8$s</xliff:g> <xliff:g id="MONTH2">%7$s</xliff:g>, <xliff:g id="TIME2">%10$s</xliff:g>"</string>
+    <string name="same_year_wday1_md1_time1_wday2_md2_time2">"<xliff:g id="WEEKDAY1">%1$s</xliff:g>, <xliff:g id="DAY1_0">%3$s</xliff:g> <xliff:g id="MONTH1">%2$s</xliff:g>, <xliff:g id="TIME1">%5$s</xliff:g> – <xliff:g id="WEEKDAY2">%6$s</xliff:g>, <xliff:g id="DAY2_1">%8$s</xliff:g> <xliff:g id="MONTH2">%7$s</xliff:g>, <xliff:g id="TIME2">%10$s</xliff:g>"</string>
+    <string name="same_year_mdy1_time1_mdy2_time2">"<xliff:g id="DAY1">%3$s</xliff:g> <xliff:g id="MONTH1">%2$s</xliff:g> <xliff:g id="YEAR1">%4$s</xliff:g>, <xliff:g id="TIME1">%5$s</xliff:g> – <xliff:g id="DAY2">%8$s</xliff:g> <xliff:g id="MONTH2">%7$s</xliff:g> <xliff:g id="YEAR2">%9$s</xliff:g>, <xliff:g id="TIME2">%10$s</xliff:g>"</string>
+    <string name="same_year_wday1_mdy1_time1_wday2_mdy2_time2">"<xliff:g id="WEEKDAY1">%1$s</xliff:g>, <xliff:g id="DAY1_0">%3$s</xliff:g> <xliff:g id="MONTH1">%2$s</xliff:g> <xliff:g id="YEAR1">%4$s</xliff:g>, <xliff:g id="TIME1">%5$s</xliff:g> – <xliff:g id="WEEKDAY2">%6$s</xliff:g>, <xliff:g id="DAY2_1">%8$s</xliff:g> <xliff:g id="MONTH2">%7$s</xliff:g> <xliff:g id="YEAR2">%9$s</xliff:g>, <xliff:g id="TIME2">%10$s</xliff:g>"</string>
+    <string name="numeric_md1_md2">"<xliff:g id="DAY1">%3$s</xliff:g>/<xliff:g id="MONTH1">%2$s</xliff:g> – <xliff:g id="DAY2">%8$s</xliff:g>/<xliff:g id="MONTH2">%7$s</xliff:g>"</string>
+    <string name="numeric_wday1_md1_wday2_md2">"<xliff:g id="WEEKDAY1">%1$s</xliff:g>, <xliff:g id="DAY1_0">%3$s</xliff:g>/<xliff:g id="MONTH1">%2$s</xliff:g> – <xliff:g id="WEEKDAY2">%6$s</xliff:g>, <xliff:g id="DAY2_1">%8$s</xliff:g>/<xliff:g id="MONTH2">%7$s</xliff:g>"</string>
+    <string name="numeric_mdy1_mdy2">"<xliff:g id="DAY1">%3$s</xliff:g>/<xliff:g id="MONTH1">%2$s</xliff:g>/<xliff:g id="YEAR1">%4$s</xliff:g> – <xliff:g id="DAY2">%8$s</xliff:g>/<xliff:g id="MONTH2">%7$s</xliff:g>/<xliff:g id="YEAR2">%9$s</xliff:g>"</string>
+    <string name="numeric_wday1_mdy1_wday2_mdy2">"<xliff:g id="WEEKDAY1">%1$s</xliff:g>, <xliff:g id="DAY1_0">%3$s</xliff:g>/<xliff:g id="MONTH1">%2$s</xliff:g>/<xliff:g id="YEAR1">%4$s</xliff:g> – <xliff:g id="WEEKDAY2">%6$s</xliff:g>, <xliff:g id="DAY2_1">%8$s</xliff:g>/<xliff:g id="MONTH2">%7$s</xliff:g>/<xliff:g id="YEAR2">%9$s</xliff:g>"</string>
+    <string name="numeric_md1_time1_md2_time2">"<xliff:g id="DAY1">%3$s</xliff:g>/<xliff:g id="MONTH1">%2$s</xliff:g>, <xliff:g id="TIME1">%5$s</xliff:g> – <xliff:g id="DAY2">%8$s</xliff:g>/<xliff:g id="MONTH2">%7$s</xliff:g>, <xliff:g id="TIME2">%10$s</xliff:g>"</string>
+    <string name="numeric_wday1_md1_time1_wday2_md2_time2">"<xliff:g id="WEEKDAY1">%1$s</xliff:g>, <xliff:g id="DAY1_0">%3$s</xliff:g>/<xliff:g id="MONTH1">%2$s</xliff:g>, <xliff:g id="TIME1">%5$s</xliff:g> – <xliff:g id="WEEKDAY2">%6$s</xliff:g>, <xliff:g id="DAY2_1">%8$s</xliff:g>/<xliff:g id="MONTH2">%7$s</xliff:g>, <xliff:g id="TIME2">%10$s</xliff:g>"</string>
+    <string name="numeric_mdy1_time1_mdy2_time2">"<xliff:g id="DAY1">%3$s</xliff:g>/<xliff:g id="MONTH1">%2$s</xliff:g>/<xliff:g id="YEAR1">%4$s</xliff:g>, <xliff:g id="TIME1">%5$s</xliff:g> – <xliff:g id="DAY2">%8$s</xliff:g>/<xliff:g id="MONTH2">%7$s</xliff:g>/<xliff:g id="YEAR2">%9$s</xliff:g>, <xliff:g id="TIME2">%10$s</xliff:g>"</string>
+    <string name="numeric_wday1_mdy1_time1_wday2_mdy2_time2">"<xliff:g id="WEEKDAY1">%1$s</xliff:g>, <xliff:g id="DAY1_0">%3$s</xliff:g>/<xliff:g id="MONTH1">%2$s</xliff:g>/<xliff:g id="YEAR1">%4$s</xliff:g>, <xliff:g id="TIME1">%5$s</xliff:g> – <xliff:g id="WEEKDAY2">%6$s</xliff:g>, <xliff:g id="DAY2_1">%8$s</xliff:g>/<xliff:g id="MONTH2">%7$s</xliff:g>/<xliff:g id="YEAR2">%9$s</xliff:g>, <xliff:g id="TIME2">%10$s</xliff:g>"</string>
+    <string name="same_month_md1_md2">"<xliff:g id="DAY1">%3$s</xliff:g> – <xliff:g id="DAY2">%8$s</xliff:g> <xliff:g id="MONTH1">%2$s</xliff:g>"</string>
+    <string name="same_month_wday1_md1_wday2_md2">"<xliff:g id="WEEKDAY1">%1$s</xliff:g>, <xliff:g id="DAY1_0">%3$s</xliff:g> <xliff:g id="MONTH1">%2$s</xliff:g> – <xliff:g id="WEEKDAY2">%6$s</xliff:g>, <xliff:g id="DAY2_1">%8$s</xliff:g> <xliff:g id="MONTH2">%7$s</xliff:g>"</string>
+    <string name="same_month_mdy1_mdy2">"<xliff:g id="DAY1">%3$s</xliff:g> – <xliff:g id="DAY2">%8$s</xliff:g> <xliff:g id="MONTH1">%2$s</xliff:g> <xliff:g id="YEAR2">%9$s</xliff:g>"</string>
+    <string name="same_month_wday1_mdy1_wday2_mdy2">"<xliff:g id="WEEKDAY1">%1$s</xliff:g>, <xliff:g id="DAY1_0">%3$s</xliff:g> <xliff:g id="MONTH1">%2$s</xliff:g> <xliff:g id="YEAR1">%4$s</xliff:g> – <xliff:g id="WEEKDAY2">%6$s</xliff:g>, <xliff:g id="DAY2_1">%8$s</xliff:g> <xliff:g id="MONTH2">%7$s</xliff:g> <xliff:g id="YEAR2">%9$s</xliff:g>"</string>
+    <string name="same_month_md1_time1_md2_time2">"<xliff:g id="DAY1">%3$s</xliff:g> <xliff:g id="MONTH1">%2$s</xliff:g>, <xliff:g id="TIME1">%5$s</xliff:g> – <xliff:g id="DAY2">%8$s</xliff:g> <xliff:g id="MONTH2">%7$s</xliff:g>, <xliff:g id="TIME2">%10$s</xliff:g>"</string>
+    <string name="same_month_wday1_md1_time1_wday2_md2_time2">"<xliff:g id="WEEKDAY1">%1$s</xliff:g>, <xliff:g id="DAY1_0">%3$s</xliff:g> <xliff:g id="MONTH1">%2$s</xliff:g>, <xliff:g id="TIME1">%5$s</xliff:g> – <xliff:g id="WEEKDAY2">%6$s</xliff:g>, <xliff:g id="DAY2_1">%8$s</xliff:g> <xliff:g id="MONTH2">%7$s</xliff:g>, <xliff:g id="TIME2">%10$s</xliff:g>"</string>
+    <string name="same_month_mdy1_time1_mdy2_time2">"<xliff:g id="DAY1">%3$s</xliff:g> <xliff:g id="MONTH1">%2$s</xliff:g> <xliff:g id="YEAR1">%4$s</xliff:g>, <xliff:g id="TIME1">%5$s</xliff:g> – <xliff:g id="DAY2">%8$s</xliff:g> <xliff:g id="MONTH2">%7$s</xliff:g> <xliff:g id="YEAR2">%9$s</xliff:g>, <xliff:g id="TIME2">%10$s</xliff:g>"</string>
+    <string name="same_month_wday1_mdy1_time1_wday2_mdy2_time2">"<xliff:g id="WEEKDAY1">%1$s</xliff:g>, <xliff:g id="DAY1_0">%3$s</xliff:g> <xliff:g id="MONTH1">%2$s</xliff:g> <xliff:g id="YEAR1">%4$s</xliff:g>, <xliff:g id="TIME1">%5$s</xliff:g> – <xliff:g id="WEEKDAY2">%6$s</xliff:g>, <xliff:g id="DAY2_1">%8$s</xliff:g> <xliff:g id="MONTH2">%7$s</xliff:g> <xliff:g id="YEAR2">%9$s</xliff:g>, <xliff:g id="TIME2">%10$s</xliff:g>"</string>
+    <string name="abbrev_month_day_year">"<xliff:g id="DAY">%-d</xliff:g> <xliff:g id="MONTH">%b</xliff:g> <xliff:g id="YEAR">%Y</xliff:g>"</string>
+    <!-- no translation found for abbrev_month_year (5966980891147982768) -->
+    <skip />
+    <!-- no translation found for abbrev_month_day (3156047263406783231) -->
+    <skip />
+    <!-- no translation found for abbrev_month (7304935052615731208) -->
+    <skip />
+    <string name="day_of_week_long_sunday">"воскресенье"</string>
+    <string name="day_of_week_long_monday">"понедельник"</string>
+    <string name="day_of_week_long_tuesday">"вторник"</string>
+    <string name="day_of_week_long_wednesday">"среда"</string>
+    <string name="day_of_week_long_thursday">"четверг"</string>
+    <string name="day_of_week_long_friday">"пятница"</string>
+    <string name="day_of_week_long_saturday">"суббота"</string>
+    <string name="day_of_week_medium_sunday">"вс"</string>
+    <string name="day_of_week_medium_monday">"пн"</string>
+    <string name="day_of_week_medium_tuesday">"вт"</string>
+    <string name="day_of_week_medium_wednesday">"ср"</string>
+    <string name="day_of_week_medium_thursday">"чт"</string>
+    <string name="day_of_week_medium_friday">"пт"</string>
+    <string name="day_of_week_medium_saturday">"сб"</string>
+    <string name="day_of_week_short_sunday">"вс"</string>
+    <string name="day_of_week_short_monday">"пн"</string>
+    <string name="day_of_week_short_tuesday">"вт"</string>
+    <string name="day_of_week_short_wednesday">"ср"</string>
+    <string name="day_of_week_short_thursday">"чт"</string>
+    <string name="day_of_week_short_friday">"пт"</string>
+    <string name="day_of_week_short_saturday">"сб"</string>
+    <string name="day_of_week_shorter_sunday">"вс"</string>
+    <string name="day_of_week_shorter_monday">"пн"</string>
+    <string name="day_of_week_shorter_tuesday">"вт"</string>
+    <string name="day_of_week_shorter_wednesday">"с"</string>
+    <string name="day_of_week_shorter_thursday">"чт"</string>
+    <string name="day_of_week_shorter_friday">"пт"</string>
+    <string name="day_of_week_shorter_saturday">"сб"</string>
+    <string name="day_of_week_shortest_sunday">"в"</string>
+    <string name="day_of_week_shortest_monday">"п"</string>
+    <string name="day_of_week_shortest_tuesday">"в"</string>
+    <string name="day_of_week_shortest_wednesday">"с"</string>
+    <string name="day_of_week_shortest_thursday">"ч"</string>
+    <string name="day_of_week_shortest_friday">"п"</string>
+    <string name="day_of_week_shortest_saturday">"с"</string>
+    <string name="month_long_january">"январь"</string>
+    <string name="month_long_february">"февраль"</string>
+    <string name="month_long_march">"март"</string>
+    <string name="month_long_april">"апрель"</string>
+    <string name="month_long_may">"май"</string>
+    <string name="month_long_june">"июнь"</string>
+    <string name="month_long_july">"июль"</string>
+    <string name="month_long_august">"август"</string>
+    <string name="month_long_september">"сентябрь"</string>
+    <string name="month_long_october">"октябрь"</string>
+    <string name="month_long_november">"ноябрь"</string>
+    <string name="month_long_december">"декабрь"</string>
+    <string name="month_medium_january">"янв"</string>
+    <string name="month_medium_february">"фев"</string>
+    <string name="month_medium_march">"мар"</string>
+    <string name="month_medium_april">"апр"</string>
+    <string name="month_medium_may">"май"</string>
+    <string name="month_medium_june">"июн"</string>
+    <string name="month_medium_july">"июл"</string>
+    <string name="month_medium_august">"авг"</string>
+    <string name="month_medium_september">"сен"</string>
+    <string name="month_medium_october">"окт"</string>
+    <string name="month_medium_november">"ноя"</string>
+    <string name="month_medium_december">"дек"</string>
+    <string name="month_shortest_january">"Я"</string>
+    <string name="month_shortest_february">"ф"</string>
+    <string name="month_shortest_march">"м"</string>
+    <string name="month_shortest_april">"а"</string>
+    <string name="month_shortest_may">"м"</string>
+    <string name="month_shortest_june">"и"</string>
+    <string name="month_shortest_july">"и"</string>
+    <string name="month_shortest_august">"а"</string>
+    <string name="month_shortest_september">"с"</string>
+    <string name="month_shortest_october">"о"</string>
+    <string name="month_shortest_november">"н"</string>
+    <string name="month_shortest_december">"д"</string>
+    <string name="elapsed_time_short_format_mm_ss">"<xliff:g id="MINUTES">%1$02d</xliff:g>:<xliff:g id="SECONDS">%2$02d</xliff:g>"</string>
+    <string name="elapsed_time_short_format_h_mm_ss">"<xliff:g id="HOURS">%1$d</xliff:g>:<xliff:g id="MINUTES">%2$02d</xliff:g>:<xliff:g id="SECONDS">%3$02d</xliff:g>"</string>
+    <string name="selectAll">"Выбрать все"</string>
+    <string name="selectText">"Выбрать текст"</string>
+    <string name="stopSelectingText">"Прекратить выбор текста"</string>
+    <string name="cut">"Вырезать"</string>
+    <string name="cutAll">"Вырезать все"</string>
+    <string name="copy">"Копировать"</string>
+    <string name="copyAll">"Копировать все"</string>
+    <string name="paste">"Вставить"</string>
+    <string name="copyUrl">"Копировать URL"</string>
+    <string name="inputMethod">"Способ ввода"</string>
+    <string name="addToDictionary">"Добавить \"%s\" в словарь"</string>
+    <string name="editTextMenuTitle">"Правка текста"</string>
+    <string name="low_internal_storage_view_title">"Недостаточно места"</string>
+    <string name="low_internal_storage_view_text">"В памяти телефона осталось мало места."</string>
+    <string name="ok">"ОК"</string>
+    <string name="cancel">"Отмена"</string>
+    <string name="yes">"ОК"</string>
+    <string name="no">"Отмена"</string>
+    <string name="dialog_alert_title">"Внимание"</string>
+    <string name="capital_on">"ВКЛ"</string>
+    <string name="capital_off">"ВЫКЛ"</string>
+    <string name="whichApplication">"Выполнить действие с помощью"</string>
+    <string name="alwaysUse">"Использовать для этого действия по умолчанию."</string>
+    <string name="clearDefaultHintMsg">"Значения по умолчанию можно сбросить в разделе Настройки главного экрана &gt; Приложения &gt; Управление приложениями."</string>
+    <string name="chooseActivity">"Выберите действие"</string>
+    <string name="noApplications">"Нет приложений, которые могли бы выполнить это действие."</string>
+    <string name="aerr_title">"Ой!"</string>
+    <string name="aerr_application">"Приложение <xliff:g id="APPLICATION">%1$s</xliff:g> (процесс <xliff:g id="PROCESS">%2$s</xliff:g>) неожиданно остановилось. Повторите попытку."</string>
+    <string name="aerr_process">"Процесс <xliff:g id="PROCESS">%1$s</xliff:g> неожиданно остановился. Повторите попытку."</string>
+    <string name="anr_title">"Ой!"</string>
+    <string name="anr_activity_application">"Действие <xliff:g id="ACTIVITY">%1$s</xliff:g> (в приложении <xliff:g id="APPLICATION">%2$s</xliff:g>) не отвечает."</string>
+    <string name="anr_activity_process">"Действие <xliff:g id="ACTIVITY">%1$s</xliff:g> (в процессе <xliff:g id="PROCESS">%2$s</xliff:g>) не отвечает."</string>
+    <string name="anr_application_process">"Приложение <xliff:g id="APPLICATION">%1$s</xliff:g> (в процессе <xliff:g id="PROCESS">%2$s</xliff:g>) не отвечает."</string>
+    <string name="anr_process">"Процесс <xliff:g id="PROCESS">%1$s</xliff:g> не отвечает."</string>
+    <string name="force_close">"Закрыть принудительно"</string>
+    <string name="wait">"Подождать"</string>
+    <string name="debug">"Отладка"</string>
+    <string name="sendText">"Выберите действие для текста"</string>
+    <string name="volume_ringtone">"Громкость звонка"</string>
+    <string name="volume_music">"Громкость звука мультимедиа"</string>
+    <string name="volume_music_hint_playing_through_bluetooth">"Воспроизводится через Bluetooth"</string>
+    <string name="volume_call">"Громкость звонка"</string>
+    <string name="volume_bluetooth_call">"Громкость входящего вызова Bluetooth"</string>
+    <string name="volume_alarm">"Громкость будильника"</string>
+    <string name="volume_notification">"Громкость уведомления"</string>
+    <string name="volume_unknown">"Громкость"</string>
+    <string name="ringtone_default">"Мелодия звонка по умолчанию"</string>
+    <string name="ringtone_default_with_actual">"Мелодия звонка по умолчанию (<xliff:g id="ACTUAL_RINGTONE">%1$s</xliff:g>)"</string>
+    <string name="ringtone_silent">"Тишина"</string>
+    <string name="ringtone_picker_title">"Мелодии звонка"</string>
+    <string name="ringtone_unknown">"Неизвестная мелодия звонка"</string>
+  <plurals name="wifi_available">
+    <item quantity="one">"Доступна сеть Wi-Fi"</item>
+    <item quantity="other">"Доступны сети Wi-Fi"</item>
+  </plurals>
+  <plurals name="wifi_available_detailed">
+    <item quantity="one">"Доступна открытая сеть Wi-Fi"</item>
+    <item quantity="other">"Доступны открытые сети Wi-Fi"</item>
+  </plurals>
+    <string name="select_character">"Вставка символа"</string>
+    <string name="sms_control_default_app_name">"Неизвестное приложение"</string>
+    <string name="sms_control_title">"Отправка SMS"</string>
+    <string name="sms_control_message">"Отправляется большое количество сообщений SMS. Выберите \"ОК\", чтобы продолжить, или \"Отмена\", чтобы остановить отправку."</string>
+    <string name="sms_control_yes">"ОК"</string>
+    <string name="sms_control_no">"Отмена"</string>
+    <string name="date_time_set">"Установить"</string>
+    <string name="default_permission_group">"По умолчанию"</string>
+    <string name="no_permissions">"Не требуется разрешений"</string>
+    <string name="perms_hide"><b>"Скрыть"</b></string>
+    <string name="perms_show_all"><b>"Показать все"</b></string>
+    <string name="googlewebcontenthelper_loading">"Идет загрузка…"</string>
+    <string name="usb_storage_title">"Подключение через USB"</string>
+    <string name="usb_storage_message">"Вы подключили телефон к компьютеру через USB. Выберите \"Подключиться\" для копирования файлов между компьютером и картой SD телефона."</string>
+    <string name="usb_storage_button_mount">"Подключиться"</string>
+    <string name="usb_storage_button_unmount">"Не подключаться"</string>
+    <string name="usb_storage_error_message">"Не удается использовать карту SD в качестве USB-хранилища."</string>
+    <string name="usb_storage_notification_title">"Подключение через USB"</string>
+    <string name="usb_storage_notification_message">"Выберите для копирования файлов на/с компьютера."</string>
+    <string name="usb_storage_stop_notification_title">"Выключить USB-накопитель"</string>
+    <string name="usb_storage_stop_notification_message">"Выберите, чтобы выключить USB-накопитель."</string>
+    <string name="usb_storage_stop_title">"Выключить USB-накопитель"</string>
+    <string name="usb_storage_stop_message">"Перед выключением USB-накопителя обязательно отключите USB-хост. Выберите \"Выключить\", чтобы выключить USB-накопитель."</string>
+    <string name="usb_storage_stop_button_mount">"Выключить"</string>
+    <string name="usb_storage_stop_button_unmount">"Отмена"</string>
+    <string name="usb_storage_stop_error_message">"При выключении USB-накопителя произошла проблема. Убедитесь, что USB-хост отключен, и повторите попытку."</string>
+    <string name="extmedia_format_title">"Форматировать карту SD"</string>
+    <string name="extmedia_format_message">"Отформатировать карту SD? Все данные, находящиеся на карте, будут уничтожены."</string>
+    <string name="extmedia_format_button_format">"Формат"</string>
+    <string name="select_input_method">"Выбор способа ввода"</string>
+    <string name="fast_scroll_alphabet">" АБВГДЕЁЖЗИЙКЛМНОПРСТУФХЦЧШЩЫЭЮЯ"</string>
+    <string name="fast_scroll_numeric_alphabet">" 0123456789АБВГДЕЁЖЗИЙКЛМНОПРСТУФХЦЧШЩЫЭЮЯ"</string>
+    <string name="candidates_style"><u>"кандидаты"</u></string>
+    <string name="ext_media_checking_notification_title">"Подготовка карты SD"</string>
+    <string name="ext_media_checking_notification_message">"Поиск ошибок"</string>
+    <string name="ext_media_nofs_notification_title">"Пустая карта SD"</string>
+    <string name="ext_media_nofs_notification_message">"Карта SD пуста или использует неподдерживаемую файловую систему."</string>
+    <string name="ext_media_unmountable_notification_title">"Поврежденная карта SD"</string>
+    <string name="ext_media_unmountable_notification_message">"Карта SD повреждена. Может потребоваться переформатировать ее."</string>
+    <string name="ext_media_badremoval_notification_title">"Карта SD неожиданно извлечена"</string>
+    <string name="ext_media_badremoval_notification_message">"Перед извлечением карты SD отключите ее во избежание потери данных."</string>
+    <string name="ext_media_safe_unmount_notification_title">"Безопасное удаление карты SD"</string>
+    <string name="ext_media_safe_unmount_notification_message">"Теперь карту SD можно безопасно удалить."</string>
+    <string name="ext_media_nomedia_notification_title">"Карта SD удалена"</string>
+    <string name="ext_media_nomedia_notification_message">"Карта SD была удалена. Для увеличения емкости устройства вставьте новую карту SD."</string>
+    <string name="activity_list_empty">"Подходящих действий не найдено"</string>
+    <string name="permlab_pkgUsageStats">"обновлять статистику использования компонентов"</string>
+    <string name="permdesc_pkgUsageStats">"Позволяет изменять собранную статистику использования компонентов. Не предназначено для использования обычными приложениями."</string>
+    <!-- no translation found for tutorial_double_tap_to_zoom_message_short (1311810005957319690) -->
+    <skip />
+    <!-- no translation found for gadget_host_error_inflating (2613287218853846830) -->
+    <skip />
+    <!-- no translation found for ime_action_go (8320845651737369027) -->
+    <skip />
+    <!-- no translation found for ime_action_search (658110271822807811) -->
+    <skip />
+    <!-- no translation found for ime_action_send (2316166556349314424) -->
+    <skip />
+    <!-- no translation found for ime_action_next (3138843904009813834) -->
+    <skip />
+    <!-- no translation found for ime_action_default (2840921885558045721) -->
+    <skip />
+</resources>
diff --git a/core/res/res/values-zh-rCN/strings.xml b/core/res/res/values-zh-rCN/strings.xml
new file mode 100644
index 0000000..346e254
--- /dev/null
+++ b/core/res/res/values-zh-rCN/strings.xml
@@ -0,0 +1,815 @@
+<?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.
+-->
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="byteShort">"B"</string>
+    <string name="kilobyteShort">"KB"</string>
+    <string name="megabyteShort">"MB"</string>
+    <string name="gigabyteShort">"GB"</string>
+    <string name="terabyteShort">"TB"</string>
+    <string name="petabyteShort">"PB"</string>
+    <string name="untitled">"&lt;无标题&gt;"</string>
+    <string name="ellipsis">"..."</string>
+    <string name="emptyPhoneNumber">"(无电话号码)"</string>
+    <string name="unknownName">"(未知)"</string>
+    <string name="defaultVoiceMailAlphaTag">"语音信箱"</string>
+    <string name="defaultMsisdnAlphaTag">"MSISDN1"</string>
+    <string name="mmiError">"出现连接问题或 MMI 码无效。"</string>
+    <string name="serviceEnabled">"服务已启用。"</string>
+    <string name="serviceEnabledFor">"已针对以下内容启用了服务:"</string>
+    <string name="serviceDisabled">"服务已被禁用。"</string>
+    <string name="serviceRegistered">"注册成功。"</string>
+    <string name="serviceErased">"清除已成功。"</string>
+    <string name="passwordIncorrect">"密码不正确。"</string>
+    <string name="mmiComplete">"MMI 码已完成。"</string>
+    <string name="badPin">"您键入的旧 PIN 码不正确。"</string>
+    <string name="badPuk">"您键入的 PUK 码不正确。"</string>
+    <string name="mismatchPin">"您输入的 PIN 码不匹配。"</string>
+    <string name="invalidPin">"键入一个 4 到 8 位数的 PIN 码。"</string>
+    <string name="needPuk">"已对 SIM 卡进行 PUK 码锁定。键入 PUK 码将其解锁。"</string>
+    <string name="needPuk2">"键入 PUK2 码以解锁 SIM 卡。"</string>
+    <string name="ClipMmi">"来电者 ID"</string>
+    <string name="ClirMmi">"去电者 ID"</string>
+    <string name="CfMmi">"呼叫转移"</string>
+    <string name="CwMmi">"呼叫等待"</string>
+    <string name="BaMmi">"呼叫限制"</string>
+    <string name="PwdMmi">"密码更改"</string>
+    <string name="PinMmi">"PIN 码更改"</string>
+    <string name="CLIRDefaultOnNextCallOn">"呼叫者 ID 默认情况下受限制。下一个呼叫:受限制"</string>
+    <string name="CLIRDefaultOnNextCallOff">"呼叫者 ID 默认情况下受限制。下一个呼叫:不受限制"</string>
+    <string name="CLIRDefaultOffNextCallOn">"呼叫者 ID 默认情况下不受限制。下一个呼叫:受限制"</string>
+    <string name="CLIRDefaultOffNextCallOff">"呼叫者 ID 默认情况下不受限制。下一个呼叫:不受限制"</string>
+    <string name="serviceNotProvisioned">"未提供服务。"</string>
+    <string name="CLIRPermanent">"不能更改呼叫者 ID 设置。"</string>
+    <string name="serviceClassVoice">"语音"</string>
+    <string name="serviceClassData">"数据"</string>
+    <string name="serviceClassFAX">"传真"</string>
+    <string name="serviceClassSMS">"短信"</string>
+    <string name="serviceClassDataAsync">"异步"</string>
+    <string name="serviceClassDataSync">"同步"</string>
+    <string name="serviceClassPacket">"包"</string>
+    <string name="serviceClassPAD">"PAD"</string>
+    <string name="cfTemplateNotForwarded">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>:未转移呼叫"</string>
+    <string name="cfTemplateForwarded">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>:<xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
+    <string name="cfTemplateForwardedTime">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>:<xliff:g id="TIME_DELAY">{2}</xliff:g> 秒后转移呼叫 <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
+    <string name="cfTemplateRegistered">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>:未转移呼叫"</string>
+    <string name="cfTemplateRegisteredTime">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>:未转移呼叫"</string>
+    <string name="httpErrorOk">"确定"</string>
+    <string name="httpError">"此网页包含错误。"</string>
+    <string name="httpErrorLookup">"找不到该网址。"</string>
+    <string name="httpErrorUnsupportedAuthScheme">"不支持此站点验证方案。"</string>
+    <string name="httpErrorAuth">"验证失败。"</string>
+    <string name="httpErrorProxyAuth">"通过代理服务器进行验证失败。"</string>
+    <string name="httpErrorConnect">"与服务器的连接失败。"</string>
+    <string name="httpErrorIO">"服务器无法通讯。请稍后重试。"</string>
+    <string name="httpErrorTimeout">"与服务器的连接超时。"</string>
+    <string name="httpErrorRedirectLoop">"该页面包含太多服务器重定向。"</string>
+    <string name="httpErrorUnsupportedScheme">"不支持该协议。"</string>
+    <string name="httpErrorFailedSslHandshake">"不能建立安全连接。"</string>
+    <string name="httpErrorBadUrl">"网址无效,此页面无法打开。"</string>
+    <string name="httpErrorFile">"此文件无法访问。"</string>
+    <string name="httpErrorFileNotFound">"找不到请求的文件。"</string>
+    <string name="httpErrorTooManyRequests">"正在处理的请求太多。请稍后重试。"</string>
+    <string name="contentServiceSync">"同步"</string>
+    <string name="contentServiceSyncNotificationTitle">"同步"</string>
+    <string name="contentServiceTooManyDeletesNotificationDesc">"太多<xliff:g id="CONTENT_TYPE">%s</xliff:g>删除项。"</string>
+    <string name="low_memory">"手机存储空间已满!请删除一些文件来腾出空间。"</string>
+    <string name="me">"我"</string>
+    <string name="power_dialog">"手机选项"</string>
+    <string name="silent_mode">"静音模式"</string>
+    <string name="turn_on_radio">"打开收音机"</string>
+    <string name="turn_off_radio">"关闭收音机"</string>
+    <string name="screen_lock">"屏幕锁定"</string>
+    <string name="power_off">"关机"</string>
+    <string name="shutdown_progress">"正在关机..."</string>
+    <string name="shutdown_confirm">"您的手机会关机。"</string>
+    <string name="no_recent_tasks">"没有最近的应用程序。"</string>
+    <string name="global_actions">"手机选项"</string>
+    <string name="global_action_lock">"屏幕锁定"</string>
+    <string name="global_action_power_off">"关机"</string>
+    <string name="global_action_toggle_silent_mode">"静音模式"</string>
+    <string name="global_action_silent_mode_on_status">"声音已关闭"</string>
+    <string name="global_action_silent_mode_off_status">"声音已开启"</string>
+    <!-- no translation found for global_actions_toggle_airplane_mode (5884330306926307456) -->
+    <skip />
+    <!-- no translation found for global_actions_airplane_mode_on_status (2719557982608919750) -->
+    <skip />
+    <!-- no translation found for global_actions_airplane_mode_off_status (5075070442854490296) -->
+    <skip />
+    <string name="safeMode">"安全模式"</string>
+    <!-- no translation found for android_system_label (6577375335728551336) -->
+    <skip />
+    <string name="permgrouplab_costMoney">"需要您支付费用的服务"</string>
+    <string name="permgroupdesc_costMoney">"允许应用程序执行可能需要您支付费用的操作。"</string>
+    <string name="permgrouplab_messages">"您的信息"</string>
+    <string name="permgroupdesc_messages">"阅读并编写您的短信、电子邮件和其他消息。"</string>
+    <string name="permgrouplab_personalInfo">"您的个人信息"</string>
+    <string name="permgroupdesc_personalInfo">"直接访问手机中存储的联系人和日历。"</string>
+    <string name="permgrouplab_location">"您所处的位置"</string>
+    <string name="permgroupdesc_location">"监视您的物理位置"</string>
+    <string name="permgrouplab_network">"网络通讯"</string>
+    <string name="permgroupdesc_network">"允许应用程序访问各种网络功能。"</string>
+    <string name="permgrouplab_accounts">"您的 Google 帐户"</string>
+    <string name="permgroupdesc_accounts">"访问可用的 Google 帐户。"</string>
+    <string name="permgrouplab_hardwareControls">"硬件控件"</string>
+    <string name="permgroupdesc_hardwareControls">"直接在手机上访问硬件。"</string>
+    <string name="permgrouplab_phoneCalls">"手机通话"</string>
+    <string name="permgroupdesc_phoneCalls">"监视、记录和处理手机呼叫。"</string>
+    <string name="permgrouplab_systemTools">"系统工具"</string>
+    <string name="permgroupdesc_systemTools">"对系统进行低级访问和控制。"</string>
+    <string name="permgrouplab_developmentTools">"开发工具"</string>
+    <string name="permgroupdesc_developmentTools">"只有应用程序开发人员才需要的功能。"</string>
+    <string name="permlab_statusBar">"禁用或修改状态栏"</string>
+    <string name="permdesc_statusBar">"允许应用程序禁用状态栏或者添加和删除系统图标。"</string>
+    <string name="permlab_expandStatusBar">"展开/收拢状态栏"</string>
+    <string name="permdesc_expandStatusBar">"允许应用程序展开或收拢状态栏。"</string>
+    <string name="permlab_processOutgoingCalls">"拦截去电"</string>
+    <string name="permdesc_processOutgoingCalls">"允许应用程序处理去电和更改要拨打的号码。恶意应用程序可能会借此监视、重定向或阻止去电。"</string>
+    <string name="permlab_receiveSms">"接收短信"</string>
+    <string name="permdesc_receiveSms">"允许应用程序接收和处理短信。恶意应用程序可能会借此监视您的信息,或将信息删除,而不向您显示。"</string>
+    <string name="permlab_receiveMms">"接收彩信"</string>
+    <string name="permdesc_receiveMms">"允许应用程序接收和处理彩信。恶意应用程序可能会借此监视您的信息,或在您尚未看到的情况下就将其删除。"</string>
+    <string name="permlab_sendSms">"发送短信"</string>
+    <string name="permdesc_sendSms">"允许应用程序发送短信。恶意应用程序可能会借此在未经您确认的情况下发送信息从而让您支付费用。"</string>
+    <string name="permlab_readSms">"读取短信或彩信"</string>
+    <string name="permdesc_readSms">"允许应用程序读取您的手机或 SIM 卡中存储的短信。恶意应用程序可能会借此读取您的机密信息。"</string>
+    <string name="permlab_writeSms">"编辑短信或彩信"</string>
+    <string name="permdesc_writeSms">"允许应用程序写入手机或 SIM 卡中存储的短信。恶意应用程序可能会借此删除您的信息。"</string>
+    <string name="permlab_receiveWapPush">"接收 WAP"</string>
+    <string name="permdesc_receiveWapPush">"允许应用程序接收和处理 WAP 信息。恶意应用程序可能会借此监视您的信息,或将信息删除,而不向您显示。"</string>
+    <string name="permlab_getTasks">"检索正在运行的应用程序"</string>
+    <string name="permdesc_getTasks">"恶意应用程序可能会借此找到有关其他应用程序的私有信息。恶意应用程序可能会借此发现有关其他应用程序的私有信息。"</string>
+    <string name="permlab_reorderTasks">"对正在运行的应用程序重新排序"</string>
+    <string name="permdesc_reorderTasks">"允许应用程序将任务移至前端和后台。恶意应用程序可能会借此强行进到前台,而不受您的控制。"</string>
+    <string name="permlab_setDebugApp">"启用应用程序调试"</string>
+    <string name="permdesc_setDebugApp">"允许应用程序启动对其他应用程序的调试。恶意应用程序可能会借此终止其他应用程序。"</string>
+    <string name="permlab_changeConfiguration">"更改您的 UI 设置"</string>
+    <string name="permdesc_changeConfiguration">"允许应用程序更改当前配置,例如语言设置或整体的字体大小。"</string>
+    <string name="permlab_restartPackages">"重新启动其他应用程序"</string>
+    <string name="permdesc_restartPackages">"允许应用程序强制重新启动其他应用程序。"</string>
+    <string name="permlab_setProcessForeground">"防止停止"</string>
+    <string name="permdesc_setProcessForeground">"允许应用程序在前台运行任何进程,因此该进程不能被终止。普通应用程序从不需要使用此权限。"</string>
+    <string name="permlab_forceBack">"强制应用程序关闭"</string>
+    <string name="permdesc_forceBack">"允许应用程序强制前台的任何活动关闭和重新开始。普通应用程序从不需要使用此权限。"</string>
+    <string name="permlab_dump">"检索系统内部状态"</string>
+    <string name="permdesc_dump">"允许应用程序检索系统的内部状态。恶意应用程序可能会借此检索通常它们本不需要的各种私有和安全信息。"</string>
+    <string name="permlab_addSystemService">"发布低级服务"</string>
+    <string name="permdesc_addSystemService">"允许应用程序发布自己的低级系统服务。恶意应用程序可能会借此攻击系统,以及盗取或破坏系统中的任何数据。"</string>
+    <string name="permlab_runSetActivityWatcher">"监视和控制所有应用程序启动"</string>
+    <string name="permdesc_runSetActivityWatcher">"允许应用程序监视和控制系统启动活动的方式。恶意应用程序可能会借此彻底损坏系统。此权限仅在开发时才需要,普通的手机应用不需要。"</string>
+    <string name="permlab_broadcastPackageRemoved">"发送包删除的广播"</string>
+    <string name="permdesc_broadcastPackageRemoved">"允许应用程序广播已删除某应用程序包的通知。恶意应用程序可能会借此来终止任何其他正在运行的应用程序。"</string>
+    <string name="permlab_broadcastSmsReceived">"发送短信收到的广播"</string>
+    <string name="permdesc_broadcastSmsReceived">"允许应用程序广播已收到短信的通知。恶意应用程序可能会借此伪造收到的短信。"</string>
+    <string name="permlab_broadcastWapPush">"发送 WAP-PUSH 收到的广播"</string>
+    <string name="permdesc_broadcastWapPush">"允许应用程序播报收到 WAP PUSH 消息的通知。恶意应用程序可能会借此乱发彩信或暗中用恶意内容替换任意网页中的内容。"</string>
+    <string name="permlab_setProcessLimit">"限制正在运行的进程数"</string>
+    <string name="permdesc_setProcessLimit">"允许应用程序控制运行的进程数上限。普通应用程序从不需要使用此权限。"</string>
+    <string name="permlab_setAlwaysFinish">"关闭所有后台应用程序"</string>
+    <string name="permdesc_setAlwaysFinish">"允许应用程序控制活动是否始终是一转至后台就完成。普通应用程序从不需要使用此权限。"</string>
+    <string name="permlab_fotaUpdate">"自动安装系统更新"</string>
+    <string name="permdesc_fotaUpdate">"允许应用程序接收有关未决系统更新的通知并安装这些更新。恶意应用程序可能会借此通过未经授权的更新破坏系统,通常情况下,它们会干扰更新过程。"</string>
+    <string name="permlab_batteryStats">"修改电池统计信息"</string>
+    <string name="permdesc_batteryStats">"允许修改收集的电池统计信息。普通应用程序不能使用此权限。"</string>
+    <string name="permlab_internalSystemWindow">"显示未授权的窗口"</string>
+    <string name="permdesc_internalSystemWindow">"允许创建专用于内部系统用户界面的窗口。普通应用程序不能使用此权限。"</string>
+    <string name="permlab_systemAlertWindow">"显示系统级警报"</string>
+    <string name="permdesc_systemAlertWindow">"允许应用程序显示系统警报窗口。恶意应用程序可能会借此掌控整个手机屏幕。"</string>
+    <string name="permlab_setAnimationScale">"修改全局动画速度"</string>
+    <string name="permdesc_setAnimationScale">"允许应用程序随时更改全局动画速度(加快或减慢动画)。"</string>
+    <string name="permlab_manageAppTokens">"管理应用程序令牌"</string>
+    <string name="permdesc_manageAppTokens">"允许应用程序创建和管理自己的令牌,从而绕开其常规的 Z 方向。普通应用程序从不需要使用此权限。"</string>
+    <string name="permlab_injectEvents">"按键和控制按钮"</string>
+    <string name="permdesc_injectEvents">"允许应用程序将其自己的输入活动(按键等)提供给其他应用程序。恶意应用程序可能会借此掌控手机。"</string>
+    <string name="permlab_readInputState">"记录您键入的内容和执行的操作"</string>
+    <string name="permdesc_readInputState">"即使在与其他应用程序交互时(例如输入密码),也允许应用程序查看您按的键。普通应用程序从不需要使用此权限。"</string>
+    <string name="permlab_bindInputMethod">"绑定到输入方法"</string>
+    <string name="permdesc_bindInputMethod">"允许持有者绑定到输入方法的顶级接口。普通应用程序从不需要使用此权限。"</string>
+    <string name="permlab_setOrientation">"更改屏幕方向"</string>
+    <string name="permdesc_setOrientation">"允许应用程序随时更改屏幕的旋转方向。普通应用程序从不需要使用此权限。"</string>
+    <string name="permlab_signalPersistentProcesses">"向应用程序发送 Linux 信号"</string>
+    <string name="permdesc_signalPersistentProcesses">"允许应用程序请求将提供的信号发送给所有持久进程。"</string>
+    <string name="permlab_persistentActivity">"让应用程序始终运行"</string>
+    <string name="permdesc_persistentActivity">"允许应用程序部分持续运行,这样系统便不能将其用于其他应用程序。"</string>
+    <string name="permlab_deletePackages">"删除应用程序"</string>
+    <string name="permdesc_deletePackages">"允许应用程序删除 Android 包。恶意应用程序可能会借此删除重要的应用程序。"</string>
+    <string name="permlab_clearAppUserData">"删除其他应用程序的数据"</string>
+    <string name="permdesc_clearAppUserData">"允许应用程序清除用户数据。"</string>
+    <string name="permlab_deleteCacheFiles">"删除其他应用程序的缓存"</string>
+    <string name="permdesc_deleteCacheFiles">"允许应用程序删除缓存文件。"</string>
+    <string name="permlab_getPackageSize">"计算应用程序存储空间"</string>
+    <string name="permdesc_getPackageSize">"允许应用程序检索其代码、数据和缓存大小"</string>
+    <string name="permlab_installPackages">"直接安装应用程序"</string>
+    <string name="permdesc_installPackages">"允许应用程序安装新的或更新的 Android 包。恶意应用程序可能会借此添加其具有任意权限的新应用程序。"</string>
+    <string name="permlab_clearAppCache">"删除所有应用程序缓存数据"</string>
+    <string name="permdesc_clearAppCache">"允许应用程序通过删除应用程序缓存目录中的文件释放手机存储空间。通常只限于访问系统进程。"</string>
+    <string name="permlab_readLogs">"读取系统日志文件"</string>
+    <string name="permdesc_readLogs">"允许应用程序从系统的各日志文件中进行读取。这样应用程序可以发现有关您正在通过手机执行的操作的常规信息,但这些信息不应包含任何个人或私有信息。"</string>
+    <string name="permlab_diagnostic">"读取/写入诊断拥有的资源"</string>
+    <string name="permdesc_diagnostic">"允许应用程序读取和写入诊断组拥有的任何资源;例如 /dev 中的文件。这可能会潜在地影响系统稳定性和安全性。此权限只应用于由制造商或操作员执行的硬件特定的诊断。"</string>
+    <string name="permlab_changeComponentState">"启用或禁用应用程序组件"</string>
+    <string name="permdesc_changeComponentState">"允许应用程序更改是否启用其他应用程序的组件。恶意应用程序可能会借此来禁用重要的手机功能。使用此权限时务必谨慎,因为这可能导致应用程序组件进入不可用、不一致或不稳定的状态。"</string>
+    <string name="permlab_setPreferredApplications">"设置首选的应用程序"</string>
+    <string name="permdesc_setPreferredApplications">"允许应用程序修改首选的应用程序。恶意应用程序可能会借此暗中更改运行的应用程序,从而骗过您的现有应用程序来收集您的私有数据。"</string>
+    <string name="permlab_writeSettings">"修改全局系统设置"</string>
+    <string name="permdesc_writeSettings">"允许应用程序修改系统的设置数据。恶意应用程序可能会借此破坏您的系统配置。"</string>
+    <string name="permlab_writeSecureSettings">"修改安全系统设置"</string>
+    <string name="permdesc_writeSecureSettings">"允许应用程序修改系统安全设置数据。普通应用程序不能使用此权限。"</string>
+    <string name="permlab_writeGservices">"修改 Google 服务地图"</string>
+    <string name="permdesc_writeGservices">"允许应用程序修改 Google 服务地图。普通应用程序不能使用此权限。"</string>
+    <string name="permlab_receiveBootCompleted">"引导时自动启动"</string>
+    <string name="permdesc_receiveBootCompleted">"允许应用程序在系统完成引导后即自行启动。这样会加长启动手机所需的时间,而且如果应用程序一直运行,会降低手机的整体速度。"</string>
+    <string name="permlab_broadcastSticky">"发送顽固广播"</string>
+    <string name="permdesc_broadcastSticky">"允许应用程序发送顽固广播,这些广播在结束后仍会保留。恶意应用程序可能会借此使手机使用太多内存,从而降低其速度和稳定性。"</string>
+    <string name="permlab_readContacts">"读取联系数据"</string>
+    <string name="permdesc_readContacts">"允许应用程序读取您手机中存储的所有联系(地址)数据。恶意应用程序可能会借此将您的数据发送给其他人。"</string>
+    <string name="permlab_writeContacts">"写入联系数据"</string>
+    <string name="permdesc_writeContacts">"允许应用程序修改您手机中存储的联系(地址)数据。恶意应用程序可能会借此清除或修改您的联系数据。"</string>
+    <string name="permlab_writeOwnerData">"写入所有者数据"</string>
+    <string name="permdesc_writeOwnerData">"允许应用程序修改您手机中存储的手机所有者数据。恶意应用程序可能会借此清除或修改所有者数据。"</string>
+    <string name="permlab_readOwnerData">"读取所有者数据"</string>
+    <string name="permdesc_readOwnerData">"允许应用程序读取您手机中存储的手机所有者数据。恶意应用程序可能会借此读取手机所有者数据。"</string>
+    <string name="permlab_readCalendar">"读取日历数据"</string>
+    <string name="permdesc_readCalendar">"允许应用程序读取您手机中存储的所有日历活动。恶意应用程序可能会借此将您的日历活动发送给其他人。"</string>
+    <string name="permlab_writeCalendar">"写入日历数据"</string>
+    <string name="permdesc_writeCalendar">"允许应用程序修改您手机中存储的日历活动。恶意应用程序可能会借此清除或修改您的日历数据。"</string>
+    <string name="permlab_accessMockLocation">"用于测试的模仿位置源"</string>
+    <string name="permdesc_accessMockLocation">"创建用于测试的模仿位置源。恶意应用程序可能会借此替代真正的位置源(例如 GPS 或网络提供商)返回的位置和/或状态。"</string>
+    <string name="permlab_accessLocationExtraCommands">"访问额外的位置提供程序命令"</string>
+    <string name="permdesc_accessLocationExtraCommands">"访问额外的位置提供程序命令。恶意应用程序可能会借此干扰 GPS 或其他位置源的操作。"</string>
+    <string name="permlab_accessFineLocation">"精准 (GPS) 位置"</string>
+    <string name="permdesc_accessFineLocation">"访问精准的位置源,例如手机上的全球定位系统(如果有)。恶意应用程序可能会借此确定您所处的位置,并可能消耗额外的电池电量。"</string>
+    <string name="permlab_accessCoarseLocation">"粗略(基于网络的)位置"</string>
+    <string name="permdesc_accessCoarseLocation">"访问粗略的位置源(例如蜂窝网络数据库)以确定手机的大体位置(如果适用)。恶意应用程序可能会借此来确定您的大体位置。"</string>
+    <string name="permlab_accessSurfaceFlinger">"访问 SurfaceFlinger"</string>
+    <string name="permdesc_accessSurfaceFlinger">"允许应用程序使用 SurfaceFlinger 低级功能。"</string>
+    <string name="permlab_readFrameBuffer">"读取帧缓冲区"</string>
+    <string name="permdesc_readFrameBuffer">"允许应用程序使用(读取)帧缓冲区中的内容。"</string>
+    <string name="permlab_modifyAudioSettings">"更改您的音频设置"</string>
+    <string name="permdesc_modifyAudioSettings">"允许应用程序修改全局音频设置,例如音量和路由。"</string>
+    <string name="permlab_recordAudio">"录音"</string>
+    <string name="permdesc_recordAudio">"允许应用程序访问录音路径。"</string>
+    <string name="permlab_camera">"拍照"</string>
+    <string name="permdesc_camera">"允许应用程序通过相机拍照。这样应用程序可随时收集相机正在拍摄的图片。"</string>
+    <string name="permlab_brick">"永久禁用手机"</string>
+    <string name="permdesc_brick">"允许应用程序永久禁用整个手机,这是很危险的。"</string>
+    <string name="permlab_reboot">"强制手机重新引导"</string>
+    <string name="permdesc_reboot">"允许应用程序强制手机重新引导。"</string>
+    <string name="permlab_mount_unmount_filesystems">"装载和卸载文件系统"</string>
+    <string name="permdesc_mount_unmount_filesystems">"允许应用程序装载和卸载文件系统以进行可移动存储。"</string>
+    <string name="permlab_mount_format_filesystems">"格式化外部存储设备"</string>
+    <string name="permdesc_mount_format_filesystems">"允许应用程序格式化可移除的存储设备。"</string>
+    <string name="permlab_vibrate">"控制振动器"</string>
+    <string name="permdesc_vibrate">"允许应用程序控制振动器。"</string>
+    <string name="permlab_flashlight">"控制闪光灯"</string>
+    <string name="permdesc_flashlight">"允许应用程序控制闪光灯。"</string>
+    <string name="permlab_hardware_test">"测试硬件"</string>
+    <string name="permdesc_hardware_test">"允许应用程序控制各外围设备以进行硬件测试。"</string>
+    <string name="permlab_callPhone">"直接呼叫电话号码"</string>
+    <string name="permdesc_callPhone">"允许应用程序在没有您干预的情况下呼叫电话号码。恶意应用程序可能会借此在您的电话帐单上产生意外呼叫。请注意,此权限不允许应用程序呼叫紧急电话号码。"</string>
+    <string name="permlab_callPrivileged">"直接呼叫任何电话号码"</string>
+    <string name="permdesc_callPrivileged">"允许应用程序在没有您干预的情况下呼叫任何电话号码(包括紧急电话号码)。恶意应用程序可能会借此对紧急服务拨打骚扰电话和非法电话。"</string>
+    <string name="permlab_locationUpdates">"控制位置更新通知"</string>
+    <string name="permdesc_locationUpdates">"允许启用/禁用来自收音机的位置更新通知。普通应用程序不能使用此权限。"</string>
+    <string name="permlab_checkinProperties">"访问检入属性"</string>
+    <string name="permdesc_checkinProperties">"允许对检入服务上传的属性进行读/写访问。普通应用程序不能使用此权限。"</string>
+    <string name="permlab_bindGadget">"选择小工具"</string>
+    <string name="permdesc_bindGadget">"允许应用程序告诉系统哪些应用程序可以使用哪些小工具。应用程序可以借此授予其他应用程序访问个人数据的权限。普通应用程序不能使用此权限。"</string>
+    <string name="permlab_modifyPhoneState">"修改手机状态"</string>
+    <string name="permdesc_modifyPhoneState">"允许应用程序控制设备的手机功能。具有此权限的应用程序可能会切换网络,打开和关闭手机收音机以及类似操作,而不会通知您。"</string>
+    <string name="permlab_readPhoneState">"读取手机状态"</string>
+    <string name="permdesc_readPhoneState">"允许应用程序访问设备的电话功能。具有此权限的应用程序可确定此电话的电话号码、是否在进行通话以及通话对方的号码等。"</string>
+    <string name="permlab_wakeLock">"防止手机休眠"</string>
+    <string name="permdesc_wakeLock">"允许应用程序防止手机进入休眠状态。"</string>
+    <string name="permlab_devicePower">"开机或关机"</string>
+    <string name="permdesc_devicePower">"允许应用程序打开或关闭手机。"</string>
+    <string name="permlab_factoryTest">"在出厂测试模式下运行"</string>
+    <string name="permdesc_factoryTest">"作为一项低级制造商测试来运行,从而允许对手机硬件进行完全访问。此权限仅当手机在制造商测试模式下运行时才可用。"</string>
+    <string name="permlab_setWallpaper">"设置壁纸"</string>
+    <string name="permdesc_setWallpaper">"允许应用程序设置系统壁纸。"</string>
+    <string name="permlab_setWallpaperHints">"大体设置壁纸大小"</string>
+    <string name="permdesc_setWallpaperHints">"允许应用程序大体设置系统壁纸大小。"</string>
+    <string name="permlab_masterClear">"将系统重设为出厂默认值"</string>
+    <string name="permdesc_masterClear">"允许应用程序将系统完全重设为其出厂设置,即清除所有数据、配置和安装的应用程序。"</string>
+    <string name="permlab_setTimeZone">"设置时区"</string>
+    <string name="permdesc_setTimeZone">"允许应用程序更改手机的时区。"</string>
+    <string name="permlab_getAccounts">"发现已知帐户"</string>
+    <string name="permdesc_getAccounts">"允许应用程序获取手机已知的帐户列表。"</string>
+    <string name="permlab_accessNetworkState">"查看网络状态"</string>
+    <string name="permdesc_accessNetworkState">"允许应用程序查看所有网络的状态。"</string>
+    <string name="permlab_createNetworkSockets">"完全的互联网访问"</string>
+    <string name="permdesc_createNetworkSockets">"允许应用程序创建网络套接字。"</string>
+    <string name="permlab_writeApnSettings">"写入“接入点名称”设置"</string>
+    <string name="permdesc_writeApnSettings">"允许应用程序修改 APN 设置,例如任何 APN 的代理和端口。"</string>
+    <string name="permlab_changeNetworkState">"更改网络连接性"</string>
+    <string name="permdesc_changeNetworkState">"允许应用程序更改状态网络连接性。"</string>
+    <string name="permlab_changeBackgroundDataSetting">"更改背景数据使用设置"</string>
+    <string name="permdesc_changeBackgroundDataSetting">"允许应用程序更改背景数据使用设置。"</string>
+    <string name="permlab_accessWifiState">"查看 Wi-Fi 状态"</string>
+    <string name="permdesc_accessWifiState">"允许应用程序查看有关 Wi-Fi 状态的信息。"</string>
+    <string name="permlab_changeWifiState">"更改 Wi-Fi 状态"</string>
+    <string name="permdesc_changeWifiState">"允许应用程序连接至 Wi-Fi 接入点以及与 Wi-Fi 接入点断开连接,并允许应用程序对配置的 Wi-Fi 网络进行更改。"</string>
+    <string name="permlab_bluetoothAdmin">"蓝牙管理"</string>
+    <string name="permdesc_bluetoothAdmin">"允许应用程序配置本地蓝牙手机以及发现远程设备并与其配对。"</string>
+    <string name="permlab_bluetooth">"创建蓝牙连接"</string>
+    <string name="permdesc_bluetooth">"允许应用程序查看本地蓝牙手机的配置以及建立和接受与配对设备的连接。"</string>
+    <string name="permlab_disableKeyguard">"禁用键锁"</string>
+    <string name="permdesc_disableKeyguard">"允许应用程序禁用键锁和任何关联的密码安全措施。这种情况的一个恰当示例就是这样一个手机:当收到来电时禁用键锁,当通话结束后再重新启用键锁。"</string>
+    <string name="permlab_readSyncSettings">"读取同步设置"</string>
+    <string name="permdesc_readSyncSettings">"允许应用程序读取同步设置,例如是否为“联系人”启用同步。"</string>
+    <string name="permlab_writeSyncSettings">"写入同步设置"</string>
+    <string name="permdesc_writeSyncSettings">"允许应用程序修改同步设置,例如是否针对“联系人”启用同步。"</string>
+    <string name="permlab_readSyncStats">"读取同步统计信息"</string>
+    <string name="permdesc_readSyncStats">"允许应用程序读取同步统计信息;例如已发生的同步的历史记录。"</string>
+    <string name="permlab_subscribedFeedsRead">"读取订阅的供稿"</string>
+    <string name="permdesc_subscribedFeedsRead">"允许应用程序获取有关当前同步的供稿的详情。"</string>
+    <string name="permlab_subscribedFeedsWrite">"写入订阅的供稿"</string>
+    <string name="permdesc_subscribedFeedsWrite">"允许应用程序修改您当前同步的供稿。这样恶意程序可以更改您同步的供稿。"</string>
+    <string name="permlab_readDictionary">"读取用户定义的词典"</string>
+    <string name="permdesc_readDictionary">"允许应用程序读取用户在用户词典中存储的任意私有字词、名称和短语。"</string>
+    <string name="permlab_writeDictionary">"写入用户定义的词典"</string>
+    <string name="permdesc_writeDictionary">"允许应用程序向用户词典中写入新词。"</string>
+  <string-array name="phoneTypes">
+    <item>"家庭"</item>
+    <item>"手机"</item>
+    <item>"工作"</item>
+    <item>"工作传真"</item>
+    <item>"家庭传真"</item>
+    <item>"寻呼机"</item>
+    <item>"其他"</item>
+    <item>"自定义"</item>
+  </string-array>
+  <string-array name="emailAddressTypes">
+    <item>"家庭"</item>
+    <item>"工作"</item>
+    <item>"其他"</item>
+    <item>"自定义"</item>
+  </string-array>
+  <string-array name="postalAddressTypes">
+    <item>"家庭"</item>
+    <item>"工作"</item>
+    <item>"其他"</item>
+    <item>"自定义"</item>
+  </string-array>
+  <string-array name="imAddressTypes">
+    <item>"家庭"</item>
+    <item>"工作"</item>
+    <item>"其他"</item>
+    <item>"自定义"</item>
+  </string-array>
+  <string-array name="organizationTypes">
+    <item>"工作"</item>
+    <item>"其他"</item>
+    <item>"自定义"</item>
+  </string-array>
+  <string-array name="imProtocols">
+    <item>"AIM"</item>
+    <item>"Windows Live"</item>
+    <item>"中国雅虎"</item>
+    <item>"Skype"</item>
+    <item>"QQ"</item>
+    <item>"Google Talk"</item>
+    <item>"ICQ"</item>
+    <item>"Jabber"</item>
+  </string-array>
+    <string name="keyguard_password_enter_pin_code">"输入 PIN 码"</string>
+    <string name="keyguard_password_wrong_pin_code">"PIN 码不正确!"</string>
+    <string name="keyguard_label_text">"要解锁,请按“菜单”,然后按 0。"</string>
+    <string name="emergency_call_dialog_number_for_display">"紧急电话号码"</string>
+    <string name="lockscreen_carrier_default">"(无服务)"</string>
+    <string name="lockscreen_screen_locked">"屏幕已锁定。"</string>
+    <string name="lockscreen_instructions_when_pattern_enabled">"按“菜单”解锁或拨打紧急电话。"</string>
+    <string name="lockscreen_instructions_when_pattern_disabled">"按“菜单”解锁。"</string>
+    <string name="lockscreen_pattern_instructions">"绘制解锁图案"</string>
+    <string name="lockscreen_emergency_call">"紧急电话"</string>
+    <string name="lockscreen_pattern_correct">"正确!"</string>
+    <string name="lockscreen_pattern_wrong">"很抱歉,请重试"</string>
+    <!-- no translation found for lockscreen_plugged_in (613343852842944435) -->
+    <skip />
+    <string name="lockscreen_low_battery">"连接您的充电器。"</string>
+    <string name="lockscreen_missing_sim_message_short">"没有 SIM 卡。"</string>
+    <string name="lockscreen_missing_sim_message">"手机中无 SIM 卡。"</string>
+    <string name="lockscreen_missing_sim_instructions">"请插入 SIM 卡。"</string>
+    <string name="lockscreen_network_locked_message">"网络已锁定"</string>
+    <string name="lockscreen_sim_puk_locked_message">"已对 SIM 卡进行 PUK 码锁定。"</string>
+    <string name="lockscreen_sim_puk_locked_instructions">"请联系客服部门。"</string>
+    <string name="lockscreen_sim_locked_message">"SIM 卡已被锁定。"</string>
+    <string name="lockscreen_sim_unlock_progress_dialog_message">"正在解锁 SIM 卡..."</string>
+    <string name="lockscreen_too_many_failed_attempts_dialog_message">"您 <xliff:g id="NUMBER_0">%d</xliff:g> 次错误地绘制了您的解锁图案。"\n\n"请在 <xliff:g id="NUMBER_1">%d</xliff:g> 秒后重试。"</string>
+    <string name="lockscreen_failed_attempts_almost_glogin">"您已错误地绘制了您的解锁图案 <xliff:g id="NUMBER_0">%d</xliff:g> 次。如果再尝试 <xliff:g id="NUMBER_1">%d</xliff:g> 次后仍不成功,系统会要求您使用您的 Google 登录帐户解锁手机。"\n\n"请在 <xliff:g id="NUMBER_2">%d</xliff:g> 秒后重试。"</string>
+    <string name="lockscreen_too_many_failed_attempts_countdown">"<xliff:g id="NUMBER">%d</xliff:g> 秒后重试。"</string>
+    <string name="lockscreen_forgot_pattern_button_text">"忘记了图案?"</string>
+    <string name="lockscreen_glogin_too_many_attempts">"图案尝试次数太多!"</string>
+    <string name="lockscreen_glogin_instructions">"要解锁,"\n"请用您的 Google 帐户登录"</string>
+    <string name="lockscreen_glogin_username_hint">"用户名(电子邮件)"</string>
+    <string name="lockscreen_glogin_password_hint">"密码"</string>
+    <string name="lockscreen_glogin_submit_button">"登录"</string>
+    <string name="lockscreen_glogin_invalid_input">"用户名或密码无效。"</string>
+    <string name="status_bar_time_format">"<xliff:g id="HOUR">h</xliff:g>:<xliff:g id="MINUTE">mm</xliff:g> <xliff:g id="AMPM">AA</xliff:g>"</string>
+    <string name="hour_minute_ampm">"<xliff:g id="HOUR">%-l</xliff:g>:<xliff:g id="MINUTE">%M</xliff:g><xliff:g id="AMPM">%P</xliff:g>"</string>
+    <string name="hour_minute_cap_ampm">"<xliff:g id="HOUR">%-l</xliff:g>:<xliff:g id="MINUTE">%M</xliff:g><xliff:g id="AMPM">%p</xliff:g>"</string>
+    <!-- no translation found for hour_ampm (4329881288269772723) -->
+    <skip />
+    <!-- no translation found for hour_cap_ampm (1829009197680861107) -->
+    <skip />
+    <string name="status_bar_clear_all_button">"清除通知"</string>
+    <string name="status_bar_no_notifications_title">"无通知"</string>
+    <string name="status_bar_ongoing_events_title">"正在进行的"</string>
+    <string name="status_bar_latest_events_title">"通知"</string>
+    <!-- no translation found for battery_status_text_percent_format (7660311274698797147) -->
+    <skip />
+    <string name="battery_status_charging">"正在充电..."</string>
+    <string name="battery_low_title">"请连接充电器"</string>
+    <string name="battery_low_subtitle">"电量在减少:"</string>
+    <string name="battery_low_percent_format">"剩余电量不足 <xliff:g id="NUMBER">%d%%</xliff:g>。"</string>
+    <string name="factorytest_failed">"出厂测试失败"</string>
+    <string name="factorytest_not_system">"只有在 /system/app 中安装的包支持 FACTORY_TEST 操作。"</string>
+    <string name="factorytest_no_action">"未发现支持 FACTORY_TEST 操作的包。"</string>
+    <string name="factorytest_reboot">"重新引导"</string>
+    <string name="js_dialog_title">"“<xliff:g id="TITLE">%s</xliff:g>”处的页面表明:"</string>
+    <string name="js_dialog_title_default">"JavaScript"</string>
+    <string name="js_dialog_before_unload">"是否从该页面导航至它处?"\n\n"<xliff:g id="MESSAGE">%s</xliff:g>"\n\n"选择“确定”继续,或选择“取消”留在当前页面。"</string>
+    <string name="save_password_label">"确认"</string>
+    <string name="save_password_message">"是否希望浏览器记住此密码?"</string>
+    <string name="save_password_notnow">"暂不保存"</string>
+    <string name="save_password_remember">"记住"</string>
+    <string name="save_password_never">"从不"</string>
+    <string name="open_permission_deny">"您无权打开此页面。"</string>
+    <string name="text_copied">"文本已复制到剪贴板。"</string>
+    <string name="more_item_label">"更多"</string>
+    <string name="prepend_shortcut_label">"“菜单”+"</string>
+    <string name="menu_space_shortcut_label">"空格"</string>
+    <string name="menu_enter_shortcut_label">"Enter 键"</string>
+    <string name="menu_delete_shortcut_label">"删除"</string>
+    <string name="search_go">"搜索"</string>
+    <string name="today">"今天"</string>
+    <string name="yesterday">"昨天"</string>
+    <string name="tomorrow">"明天"</string>
+    <string name="oneMonthDurationPast">"1 个月前"</string>
+    <string name="beforeOneMonthDurationPast">"1 个月前"</string>
+  <plurals name="num_seconds_ago">
+    <item quantity="one">"1 秒前"</item>
+    <item quantity="other">"<xliff:g id="COUNT">%d</xliff:g> 秒前"</item>
+  </plurals>
+  <plurals name="num_minutes_ago">
+    <item quantity="one">"1 分钟前"</item>
+    <item quantity="other">"<xliff:g id="COUNT">%d</xliff:g> 分钟前"</item>
+  </plurals>
+  <plurals name="num_hours_ago">
+    <item quantity="one">"1 小时前"</item>
+    <item quantity="other">"<xliff:g id="COUNT">%d</xliff:g> 小时前"</item>
+  </plurals>
+  <plurals name="num_days_ago">
+    <item quantity="one">"昨天"</item>
+    <item quantity="other">"<xliff:g id="COUNT">%d</xliff:g> 天前"</item>
+  </plurals>
+  <plurals name="in_num_seconds">
+    <item quantity="one">"1 秒后"</item>
+    <item quantity="other">"<xliff:g id="COUNT">%d</xliff:g> 秒后"</item>
+  </plurals>
+  <plurals name="in_num_minutes">
+    <item quantity="one">"1 分钟后"</item>
+    <item quantity="other">"<xliff:g id="COUNT">%d</xliff:g> 分钟后"</item>
+  </plurals>
+  <plurals name="in_num_hours">
+    <item quantity="one">"1 小时后"</item>
+    <item quantity="other">"<xliff:g id="COUNT">%d</xliff:g> 小时后"</item>
+  </plurals>
+  <plurals name="in_num_days">
+    <item quantity="one">"明天"</item>
+    <item quantity="other">"<xliff:g id="COUNT">%d</xliff:g> 天后"</item>
+  </plurals>
+  <plurals name="abbrev_num_seconds_ago">
+    <item quantity="one">"1 秒前"</item>
+    <item quantity="other">"<xliff:g id="COUNT">%d</xliff:g> 秒前"</item>
+  </plurals>
+  <plurals name="abbrev_num_minutes_ago">
+    <item quantity="one">"1 分钟前"</item>
+    <item quantity="other">"<xliff:g id="COUNT">%d</xliff:g> 分钟前"</item>
+  </plurals>
+  <plurals name="abbrev_num_hours_ago">
+    <item quantity="one">"1 小时前"</item>
+    <item quantity="other">"<xliff:g id="COUNT">%d</xliff:g> 小时前"</item>
+  </plurals>
+  <plurals name="abbrev_num_days_ago">
+    <item quantity="one">"昨天"</item>
+    <item quantity="other">"<xliff:g id="COUNT">%d</xliff:g> 天前"</item>
+  </plurals>
+  <plurals name="abbrev_in_num_seconds">
+    <item quantity="one">"1 秒后"</item>
+    <item quantity="other">"<xliff:g id="COUNT">%d</xliff:g> 秒后"</item>
+  </plurals>
+  <plurals name="abbrev_in_num_minutes">
+    <item quantity="one">"1 分钟后"</item>
+    <item quantity="other">"<xliff:g id="COUNT">%d</xliff:g> 分钟后"</item>
+  </plurals>
+  <plurals name="abbrev_in_num_hours">
+    <item quantity="one">"1 小时后"</item>
+    <item quantity="other">"<xliff:g id="COUNT">%d</xliff:g> 小时后"</item>
+  </plurals>
+  <plurals name="abbrev_in_num_days">
+    <item quantity="one">"明天"</item>
+    <item quantity="other">"<xliff:g id="COUNT">%d</xliff:g> 天后"</item>
+  </plurals>
+    <string name="preposition_for_date">"在 %s"</string>
+    <string name="preposition_for_time">"在 %s"</string>
+    <string name="preposition_for_year">"%s 年内"</string>
+    <string name="day">"天"</string>
+    <string name="days">"天"</string>
+    <string name="hour">"小时"</string>
+    <string name="hours">"小时"</string>
+    <string name="minute">"分钟"</string>
+    <string name="minutes">"分钟"</string>
+    <string name="second">"秒"</string>
+    <string name="seconds">"秒"</string>
+    <string name="week">"周"</string>
+    <string name="weeks">"周"</string>
+    <string name="year">"年"</string>
+    <string name="years">"年"</string>
+    <string name="sunday">"周日"</string>
+    <string name="monday">"周一"</string>
+    <string name="tuesday">"周二"</string>
+    <string name="wednesday">"周三"</string>
+    <string name="thursday">"周四"</string>
+    <string name="friday">"周五"</string>
+    <string name="saturday">"周六"</string>
+    <string name="every_weekday">"每个工作日(周一到周五)"</string>
+    <string name="daily">"每天"</string>
+    <string name="weekly">"每周的<xliff:g id="DAY">%s</xliff:g>"</string>
+    <string name="monthly">"每月"</string>
+    <string name="yearly">"每年"</string>
+    <string name="VideoView_error_title">"无法播放视频"</string>
+    <string name="VideoView_error_text_unknown">"很抱歉,此视频不能播放。"</string>
+    <string name="VideoView_error_button">"确定"</string>
+    <string name="am">"上午"</string>
+    <string name="pm">"下午"</string>
+    <string name="numeric_date">"<xliff:g id="YEAR">%Y</xliff:g> 年 <xliff:g id="MONTH">%m</xliff:g> 月 <xliff:g id="DAY">%d</xliff:g> 日"</string>
+    <string name="wday1_date1_time1_wday2_date2_time2">"<xliff:g id="DATE1">%2$s</xliff:g><xliff:g id="WEEKDAY1">%1$s</xliff:g> <xliff:g id="TIME1">%3$s</xliff:g> 至 <xliff:g id="DATE2">%5$s</xliff:g><xliff:g id="WEEKDAY2">%4$s</xliff:g> <xliff:g id="TIME2">%6$s</xliff:g>"</string>
+    <string name="wday1_date1_wday2_date2">"<xliff:g id="DATE1">%2$s</xliff:g><xliff:g id="WEEKDAY1">%1$s</xliff:g>至 <xliff:g id="DATE2">%5$s</xliff:g><xliff:g id="WEEKDAY2">%4$s</xliff:g>"</string>
+    <string name="date1_time1_date2_time2">"<xliff:g id="DATE1">%2$s</xliff:g> <xliff:g id="TIME1">%3$s</xliff:g> 至 <xliff:g id="DATE2">%5$s</xliff:g> <xliff:g id="TIME2">%6$s</xliff:g>"</string>
+    <string name="date1_date2">"<xliff:g id="DATE1">%2$s</xliff:g>至 <xliff:g id="DATE2">%5$s</xliff:g>"</string>
+    <string name="time1_time2">"<xliff:g id="TIME1">%1$s</xliff:g> 至 <xliff:g id="TIME2">%2$s</xliff:g>"</string>
+    <string name="time_wday_date">"<xliff:g id="DATE">%3$s</xliff:g><xliff:g id="WEEKDAY">%2$s</xliff:g> <xliff:g id="TIME_RANGE">%1$s</xliff:g>"</string>
+    <string name="wday_date">"<xliff:g id="DATE">%3$s</xliff:g><xliff:g id="WEEKDAY">%2$s</xliff:g>"</string>
+    <string name="time_date">"<xliff:g id="DATE">%3$s</xliff:g> <xliff:g id="TIME_RANGE">%1$s</xliff:g>"</string>
+    <string name="date_time">"<xliff:g id="DATE">%1$s</xliff:g><xliff:g id="TIME">%2$s</xliff:g>"</string>
+    <string name="relative_time">"<xliff:g id="DATE">%1$s</xliff:g><xliff:g id="TIME">%2$s</xliff:g>"</string>
+    <string name="time_wday">"<xliff:g id="WEEKDAY">%2$s</xliff:g> <xliff:g id="TIME_RANGE">%1$s</xliff:g>"</string>
+    <string name="full_date_month_first" format="date">"<xliff:g id="YEAR">yyyy</xliff:g>' 年 '<xliff:g id="MONTH">MMMM</xliff:g>' 月 '<xliff:g id="DAY">d</xliff:g>' 日'"</string>
+    <string name="full_date_day_first" format="date">"<xliff:g id="YEAR">yyyy</xliff:g>' 年 '<xliff:g id="MONTH">MMMM</xliff:g>' 月 '<xliff:g id="DAY">d</xliff:g>' 日'"</string>
+    <string name="medium_date_month_first" format="date">"<xliff:g id="YEAR">yyyy</xliff:g>' 年 '<xliff:g id="MONTH">MMM</xliff:g>' 月 '<xliff:g id="DAY">d</xliff:g>' 日'"</string>
+    <string name="medium_date_day_first" format="date">"<xliff:g id="YEAR">yyyy</xliff:g>' 年 '<xliff:g id="DAY">d</xliff:g>' 月 '<xliff:g id="MONTH">MMM</xliff:g>' 日'"</string>
+    <string name="twelve_hour_time_format" format="date">"<xliff:g id="HOUR">h</xliff:g>':'<xliff:g id="MINUTE">mm</xliff:g>' '<xliff:g id="AMPM">a</xliff:g>"</string>
+    <string name="twenty_four_hour_time_format" format="date">"<xliff:g id="HOUR">H</xliff:g>':'<xliff:g id="MINUTE">mm</xliff:g>"</string>
+    <string name="noon">"中午"</string>
+    <string name="Noon">"中午"</string>
+    <string name="midnight">"午夜"</string>
+    <string name="Midnight">"午夜"</string>
+    <!-- no translation found for month_day (3693060561170538204) -->
+    <skip />
+    <!-- no translation found for month (1976700695144952053) -->
+    <skip />
+    <string name="month_day_year">"<xliff:g id="YEAR">%Y</xliff:g> 年 <xliff:g id="MONTH">%B</xliff:g> 月 <xliff:g id="DAY">%-d</xliff:g> 日"</string>
+    <!-- no translation found for month_year (2106203387378728384) -->
+    <skip />
+    <string name="time_of_day">"<xliff:g id="HOUR">%H</xliff:g>:<xliff:g id="MINUTE">%M</xliff:g>:<xliff:g id="SECOND">%S</xliff:g>"</string>
+    <string name="date_and_time">"<xliff:g id="YEAR">%Y</xliff:g> 年 <xliff:g id="MONTH">%B</xliff:g> 月 <xliff:g id="DAY">%-d</xliff:g> 日 <xliff:g id="HOUR">%H</xliff:g>:<xliff:g id="MINUTE">%M</xliff:g>:<xliff:g id="SECOND">%S</xliff:g>"</string>
+    <string name="same_year_md1_md2">"<xliff:g id="MONTH1">%2$s</xliff:g> 月 <xliff:g id="DAY1">%3$s</xliff:g> 日至 <xliff:g id="MONTH2">%7$s</xliff:g> 月 <xliff:g id="DAY2">%8$s</xliff:g> 日"</string>
+    <string name="same_year_wday1_md1_wday2_md2">"<xliff:g id="MONTH1">%2$s</xliff:g> 月 <xliff:g id="DAY1_0">%3$s</xliff:g> 日<xliff:g id="WEEKDAY1">%1$s</xliff:g>至 <xliff:g id="MONTH2">%7$s</xliff:g> 月 <xliff:g id="DAY2_1">%8$s</xliff:g> 日<xliff:g id="WEEKDAY2">%6$s</xliff:g>"</string>
+    <string name="same_year_mdy1_mdy2">"<xliff:g id="YEAR">%9$s</xliff:g> 年 <xliff:g id="MONTH1">%2$s</xliff:g> 月 <xliff:g id="DAY1">%3$s</xliff:g> 日至 <xliff:g id="MONTH2">%7$s</xliff:g> 月 <xliff:g id="DAY2">%8$s</xliff:g> 日"</string>
+    <string name="same_year_wday1_mdy1_wday2_mdy2">"<xliff:g id="YEAR">%9$s</xliff:g> 年 <xliff:g id="MONTH1">%2$s</xliff:g> 月 <xliff:g id="DAY1_0">%3$s</xliff:g> 日<xliff:g id="WEEKDAY1">%1$s</xliff:g>至 <xliff:g id="MONTH2">%7$s</xliff:g> 月 <xliff:g id="DAY2_1">%8$s</xliff:g> 日<xliff:g id="WEEKDAY2">%6$s</xliff:g>"</string>
+    <string name="same_year_md1_time1_md2_time2">"<xliff:g id="MONTH1">%2$s</xliff:g> 月 <xliff:g id="DAY1">%3$s</xliff:g> 日 <xliff:g id="TIME1">%5$s</xliff:g> 至 <xliff:g id="MONTH2">%7$s</xliff:g> 月 <xliff:g id="DAY2">%8$s</xliff:g> 日 <xliff:g id="TIME2">%10$s</xliff:g>"</string>
+    <string name="same_year_wday1_md1_time1_wday2_md2_time2">"<xliff:g id="MONTH1">%2$s</xliff:g> 月 <xliff:g id="DAY1_0">%3$s</xliff:g> 日<xliff:g id="WEEKDAY1">%1$s</xliff:g> <xliff:g id="TIME1">%5$s</xliff:g> 至 <xliff:g id="MONTH2">%7$s</xliff:g> 月 <xliff:g id="DAY2_1">%8$s</xliff:g> 日<xliff:g id="WEEKDAY2">%6$s</xliff:g> <xliff:g id="TIME2">%10$s</xliff:g>"</string>
+    <string name="same_year_mdy1_time1_mdy2_time2">"<xliff:g id="YEAR1">%4$s</xliff:g> 年 <xliff:g id="MONTH1">%2$s</xliff:g> 月 <xliff:g id="DAY1">%3$s</xliff:g> 日 <xliff:g id="TIME1">%5$s</xliff:g> 至 <xliff:g id="YEAR2">%9$s</xliff:g> 年 <xliff:g id="MONTH2">%7$s</xliff:g> 月 <xliff:g id="DAY2">%8$s</xliff:g> 日 <xliff:g id="TIME2">%10$s</xliff:g>"</string>
+    <string name="same_year_wday1_mdy1_time1_wday2_mdy2_time2">"<xliff:g id="YEAR1">%4$s</xliff:g> 年 <xliff:g id="MONTH1">%2$s</xliff:g> 月 <xliff:g id="DAY1_0">%3$s</xliff:g> 日<xliff:g id="WEEKDAY1">%1$s</xliff:g> <xliff:g id="TIME1">%5$s</xliff:g> 至 – <xliff:g id="YEAR2">%9$s</xliff:g> 年 <xliff:g id="MONTH2">%7$s</xliff:g> 月 <xliff:g id="DAY2_1">%8$s</xliff:g> 日<xliff:g id="WEEKDAY2">%6$s</xliff:g> <xliff:g id="TIME2">%10$s</xliff:g>"</string>
+    <string name="numeric_md1_md2">"<xliff:g id="MONTH1">%2$s</xliff:g> 月 <xliff:g id="DAY1">%3$s</xliff:g> 日至 <xliff:g id="MONTH2">%7$s</xliff:g> 月 <xliff:g id="DAY2">%8$s</xliff:g> 日"</string>
+    <string name="numeric_wday1_md1_wday2_md2">"<xliff:g id="MONTH1">%2$s</xliff:g> 月 <xliff:g id="DAY1_0">%3$s</xliff:g> 日<xliff:g id="WEEKDAY1">%1$s</xliff:g>至 <xliff:g id="MONTH2">%7$s</xliff:g> 月 <xliff:g id="DAY2_1">%8$s</xliff:g> 日<xliff:g id="WEEKDAY2">%6$s</xliff:g>"</string>
+    <string name="numeric_mdy1_mdy2">"<xliff:g id="YEAR1">%4$s</xliff:g> 年 <xliff:g id="MONTH1">%2$s</xliff:g> 月 <xliff:g id="DAY1">%3$s</xliff:g> 日至 <xliff:g id="YEAR2">%9$s</xliff:g> 年 <xliff:g id="MONTH2">%7$s</xliff:g> 月 <xliff:g id="DAY2">%8$s</xliff:g> 日"</string>
+    <string name="numeric_wday1_mdy1_wday2_mdy2">"<xliff:g id="YEAR1">%4$s</xliff:g> 年 <xliff:g id="MONTH1">%2$s</xliff:g> 月 <xliff:g id="DAY1_0">%3$s</xliff:g> 日<xliff:g id="WEEKDAY1">%1$s</xliff:g>至 <xliff:g id="YEAR2">%9$s</xliff:g> 年 <xliff:g id="MONTH2">%7$s</xliff:g> 月 <xliff:g id="DAY2_1">%8$s</xliff:g> 日<xliff:g id="WEEKDAY2">%6$s</xliff:g>"</string>
+    <string name="numeric_md1_time1_md2_time2">"<xliff:g id="MONTH1">%2$s</xliff:g> 月 <xliff:g id="DAY1">%3$s</xliff:g> 日 <xliff:g id="TIME1">%5$s</xliff:g> 至 <xliff:g id="MONTH2">%7$s</xliff:g> 月 <xliff:g id="DAY2">%8$s</xliff:g> 日 <xliff:g id="TIME2">%10$s</xliff:g>"</string>
+    <string name="numeric_wday1_md1_time1_wday2_md2_time2">"<xliff:g id="MONTH1">%2$s</xliff:g> 月 <xliff:g id="DAY1_0">%3$s</xliff:g> 日<xliff:g id="WEEKDAY1">%1$s</xliff:g> <xliff:g id="TIME1">%5$s</xliff:g> 至 <xliff:g id="MONTH2">%7$s</xliff:g> 月 <xliff:g id="DAY2_1">%8$s</xliff:g> 日<xliff:g id="WEEKDAY2">%6$s</xliff:g> <xliff:g id="TIME2">%10$s</xliff:g>"</string>
+    <string name="numeric_mdy1_time1_mdy2_time2">"<xliff:g id="YEAR1">%4$s</xliff:g> 年 <xliff:g id="MONTH1">%2$s</xliff:g> 月 <xliff:g id="DAY1">%3$s</xliff:g> 日 <xliff:g id="TIME1">%5$s</xliff:g> 至 <xliff:g id="YEAR2">%9$s</xliff:g> 年 <xliff:g id="MONTH2">%7$s</xliff:g> 月 <xliff:g id="DAY2">%8$s</xliff:g> 日 <xliff:g id="TIME2">%10$s</xliff:g>"</string>
+    <string name="numeric_wday1_mdy1_time1_wday2_mdy2_time2">"<xliff:g id="YEAR1">%4$s</xliff:g> 年 <xliff:g id="MONTH1">%2$s</xliff:g> 月 <xliff:g id="DAY1_0">%3$s</xliff:g> 日<xliff:g id="WEEKDAY1">%1$s</xliff:g> <xliff:g id="TIME1">%5$s</xliff:g> 至 <xliff:g id="YEAR2">%9$s</xliff:g> 年 <xliff:g id="MONTH2">%7$s</xliff:g> 月 <xliff:g id="DAY2_1">%8$s</xliff:g> 日<xliff:g id="WEEKDAY2">%6$s</xliff:g> <xliff:g id="TIME2">%10$s</xliff:g>"</string>
+    <string name="same_month_md1_md2">"<xliff:g id="MONTH1">%2$s</xliff:g> 月 <xliff:g id="DAY1">%3$s</xliff:g> 日至 <xliff:g id="DAY2">%8$s</xliff:g> 日"</string>
+    <string name="same_month_wday1_md1_wday2_md2">"<xliff:g id="MONTH1">%2$s</xliff:g> 月 <xliff:g id="DAY1_0">%3$s</xliff:g> 日<xliff:g id="WEEKDAY1">%1$s</xliff:g>至<xliff:g id="MONTH2">%7$s</xliff:g> 月 <xliff:g id="DAY2_1">%8$s</xliff:g> 日<xliff:g id="WEEKDAY2">%6$s</xliff:g>"</string>
+    <string name="same_month_mdy1_mdy2">"<xliff:g id="YEAR2">%9$s</xliff:g> 年 <xliff:g id="MONTH1">%2$s</xliff:g> 月 <xliff:g id="DAY1">%3$s</xliff:g> 日至 <xliff:g id="DAY2">%8$s</xliff:g> 日"</string>
+    <string name="same_month_wday1_mdy1_wday2_mdy2">"<xliff:g id="YEAR1">%4$s</xliff:g> 年 <xliff:g id="MONTH1">%2$s</xliff:g> 月 <xliff:g id="DAY1_0">%3$s</xliff:g> 日<xliff:g id="WEEKDAY1">%1$s</xliff:g>至 <xliff:g id="YEAR2">%9$s</xliff:g> 年 <xliff:g id="MONTH2">%7$s</xliff:g> 月 <xliff:g id="DAY2_1">%8$s</xliff:g> 日<xliff:g id="WEEKDAY2">%6$s</xliff:g>"</string>
+    <string name="same_month_md1_time1_md2_time2">"<xliff:g id="MONTH1">%2$s</xliff:g> 月 <xliff:g id="DAY1">%3$s</xliff:g> 日 <xliff:g id="TIME1">%5$s</xliff:g> 至 <xliff:g id="MONTH2">%7$s</xliff:g> 月 <xliff:g id="DAY2">%8$s</xliff:g> 日 <xliff:g id="TIME2">%10$s</xliff:g>"</string>
+    <string name="same_month_wday1_md1_time1_wday2_md2_time2">"<xliff:g id="MONTH1">%2$s</xliff:g> 月 <xliff:g id="DAY1_0">%3$s</xliff:g> 日<xliff:g id="WEEKDAY1">%1$s</xliff:g> <xliff:g id="TIME1">%5$s</xliff:g> 至 <xliff:g id="MONTH2">%7$s</xliff:g> 月 <xliff:g id="DAY2_1">%8$s</xliff:g> 日<xliff:g id="WEEKDAY2">%6$s</xliff:g> <xliff:g id="TIME2">%10$s</xliff:g>"</string>
+    <string name="same_month_mdy1_time1_mdy2_time2">"<xliff:g id="YEAR1">%4$s</xliff:g> 年 <xliff:g id="MONTH1">%2$s</xliff:g> 月 <xliff:g id="DAY1">%3$s</xliff:g> 日 <xliff:g id="TIME1">%5$s</xliff:g> 至 <xliff:g id="YEAR2">%9$s</xliff:g> 年 <xliff:g id="MONTH2">%7$s</xliff:g> 月 <xliff:g id="DAY2">%8$s</xliff:g> 日 <xliff:g id="TIME2">%10$s</xliff:g>"</string>
+    <string name="same_month_wday1_mdy1_time1_wday2_mdy2_time2">"<xliff:g id="YEAR1">%4$s</xliff:g> 年 <xliff:g id="MONTH1">%2$s</xliff:g> 月 <xliff:g id="DAY1_0">%3$s</xliff:g> 日<xliff:g id="WEEKDAY1">%1$s</xliff:g> <xliff:g id="TIME1">%5$s</xliff:g> 至 <xliff:g id="YEAR2">%9$s</xliff:g> 年 <xliff:g id="MONTH2">%7$s</xliff:g> 月 <xliff:g id="DAY2_1">%8$s</xliff:g> 日<xliff:g id="WEEKDAY2">%6$s</xliff:g> <xliff:g id="TIME2">%10$s</xliff:g>"</string>
+    <string name="abbrev_month_day_year">"<xliff:g id="YEAR">%Y</xliff:g> 年 <xliff:g id="MONTH">%b</xliff:g> 月 <xliff:g id="DAY">%-d</xliff:g> 日"</string>
+    <!-- no translation found for abbrev_month_year (5966980891147982768) -->
+    <skip />
+    <!-- no translation found for abbrev_month_day (3156047263406783231) -->
+    <skip />
+    <!-- no translation found for abbrev_month (7304935052615731208) -->
+    <skip />
+    <string name="day_of_week_long_sunday">"周日"</string>
+    <string name="day_of_week_long_monday">"周一"</string>
+    <string name="day_of_week_long_tuesday">"周二"</string>
+    <string name="day_of_week_long_wednesday">"周三"</string>
+    <string name="day_of_week_long_thursday">"周四"</string>
+    <string name="day_of_week_long_friday">"周五"</string>
+    <string name="day_of_week_long_saturday">"周六"</string>
+    <string name="day_of_week_medium_sunday">"周日"</string>
+    <string name="day_of_week_medium_monday">"周一"</string>
+    <string name="day_of_week_medium_tuesday">"周二"</string>
+    <string name="day_of_week_medium_wednesday">"周三"</string>
+    <string name="day_of_week_medium_thursday">"周四"</string>
+    <string name="day_of_week_medium_friday">"周五"</string>
+    <string name="day_of_week_medium_saturday">"周六"</string>
+    <string name="day_of_week_short_sunday">"周日"</string>
+    <string name="day_of_week_short_monday">"周一"</string>
+    <string name="day_of_week_short_tuesday">"周二"</string>
+    <string name="day_of_week_short_wednesday">"周三"</string>
+    <string name="day_of_week_short_thursday">"周四"</string>
+    <string name="day_of_week_short_friday">"周五"</string>
+    <string name="day_of_week_short_saturday">"周六"</string>
+    <string name="day_of_week_shorter_sunday">"周日"</string>
+    <string name="day_of_week_shorter_monday">"周一"</string>
+    <string name="day_of_week_shorter_tuesday">"周二"</string>
+    <string name="day_of_week_shorter_wednesday">"周三"</string>
+    <string name="day_of_week_shorter_thursday">"周四"</string>
+    <string name="day_of_week_shorter_friday">"周五"</string>
+    <string name="day_of_week_shorter_saturday">"周六"</string>
+    <string name="day_of_week_shortest_sunday">"周日"</string>
+    <string name="day_of_week_shortest_monday">"周一"</string>
+    <string name="day_of_week_shortest_tuesday">"周二"</string>
+    <string name="day_of_week_shortest_wednesday">"周三"</string>
+    <string name="day_of_week_shortest_thursday">"周四"</string>
+    <string name="day_of_week_shortest_friday">"周五"</string>
+    <string name="day_of_week_shortest_saturday">"周六"</string>
+    <string name="month_long_january">"1 月"</string>
+    <string name="month_long_february">"2 月"</string>
+    <string name="month_long_march">"3 月"</string>
+    <string name="month_long_april">"4 月"</string>
+    <string name="month_long_may">"5 月"</string>
+    <string name="month_long_june">"6 月"</string>
+    <string name="month_long_july">"7 月"</string>
+    <string name="month_long_august">"8 月"</string>
+    <string name="month_long_september">"9 月"</string>
+    <string name="month_long_october">"10 月"</string>
+    <string name="month_long_november">"11 月"</string>
+    <string name="month_long_december">"12 月"</string>
+    <string name="month_medium_january">"1 月"</string>
+    <string name="month_medium_february">"2 月"</string>
+    <string name="month_medium_march">"3 月"</string>
+    <string name="month_medium_april">"4 月"</string>
+    <string name="month_medium_may">"5 月"</string>
+    <string name="month_medium_june">"6 月"</string>
+    <string name="month_medium_july">"7 月"</string>
+    <string name="month_medium_august">"8 月"</string>
+    <string name="month_medium_september">"9 月"</string>
+    <string name="month_medium_october">"10 月"</string>
+    <string name="month_medium_november">"11 月"</string>
+    <string name="month_medium_december">"12 月"</string>
+    <string name="month_shortest_january">"1 月"</string>
+    <string name="month_shortest_february">"2 月"</string>
+    <string name="month_shortest_march">"3 月"</string>
+    <string name="month_shortest_april">"4 月"</string>
+    <string name="month_shortest_may">"5 月"</string>
+    <string name="month_shortest_june">"6 月"</string>
+    <string name="month_shortest_july">"7 月"</string>
+    <string name="month_shortest_august">"8 月"</string>
+    <string name="month_shortest_september">"9 月"</string>
+    <string name="month_shortest_october">"10 月"</string>
+    <string name="month_shortest_november">"11 月"</string>
+    <string name="month_shortest_december">"12 月"</string>
+    <string name="elapsed_time_short_format_mm_ss">"<xliff:g id="MINUTES">%1$02d</xliff:g>:<xliff:g id="SECONDS">%2$02d</xliff:g>"</string>
+    <string name="elapsed_time_short_format_h_mm_ss">"<xliff:g id="HOURS">%1$d</xliff:g>:<xliff:g id="MINUTES">%2$02d</xliff:g>:<xliff:g id="SECONDS">%3$02d</xliff:g>"</string>
+    <string name="selectAll">"全选"</string>
+    <string name="selectText">"选择文本"</string>
+    <string name="stopSelectingText">"停止选择文本"</string>
+    <string name="cut">"剪切"</string>
+    <string name="cutAll">"全部剪切"</string>
+    <string name="copy">"复制"</string>
+    <string name="copyAll">"全部复制"</string>
+    <string name="paste">"粘贴"</string>
+    <string name="copyUrl">"复制网址"</string>
+    <string name="inputMethod">"输入方法"</string>
+    <string name="addToDictionary">"将“%s”添加到词典"</string>
+    <string name="editTextMenuTitle">"编辑文本"</string>
+    <string name="low_internal_storage_view_title">"存储空间不足"</string>
+    <string name="low_internal_storage_view_text">"手机存储空间在减少。"</string>
+    <string name="ok">"正常"</string>
+    <string name="cancel">"取消"</string>
+    <string name="yes">"正常"</string>
+    <string name="no">"取消"</string>
+    <string name="dialog_alert_title">"注意事项"</string>
+    <string name="capital_on">"开启"</string>
+    <string name="capital_off">"关闭"</string>
+    <string name="whichApplication">"使用以下内容完成操作"</string>
+    <string name="alwaysUse">"默认用于执行此操作。"</string>
+    <string name="clearDefaultHintMsg">"通过“主页设置”&gt;“应用程序”&gt;“管理应用程序”清除默认值。"</string>
+    <string name="chooseActivity">"选择操作"</string>
+    <string name="noApplications">"没有应用程序可执行此操作。"</string>
+    <string name="aerr_title">"很抱歉!"</string>
+    <string name="aerr_application">"应用程序<xliff:g id="APPLICATION">%1$s</xliff:g>(在进程 <xliff:g id="PROCESS">%2$s</xliff:g> 中)已意外停止。请重试。"</string>
+    <string name="aerr_process">"进程 <xliff:g id="PROCESS">%1$s</xliff:g> 已意外停止。请重试。"</string>
+    <string name="anr_title">"很抱歉!"</string>
+    <string name="anr_activity_application">"活动<xliff:g id="ACTIVITY">%1$s</xliff:g>(在应用程序 <xliff:g id="APPLICATION">%2$s</xliff:g> 中)无响应。"</string>
+    <string name="anr_activity_process">"活动<xliff:g id="ACTIVITY">%1$s</xliff:g>(在进程 <xliff:g id="PROCESS">%2$s</xliff:g> 中)无响应。"</string>
+    <string name="anr_application_process">"应用程序<xliff:g id="APPLICATION">%1$s</xliff:g>(在进程 <xliff:g id="PROCESS">%2$s</xliff:g> 中)无响应。"</string>
+    <string name="anr_process">"进程 <xliff:g id="PROCESS">%1$s</xliff:g> 无响应。"</string>
+    <string name="force_close">"强制关闭"</string>
+    <string name="wait">"等待"</string>
+    <string name="debug">"调试"</string>
+    <string name="sendText">"选择一个文本操作"</string>
+    <string name="volume_ringtone">"响铃音量"</string>
+    <string name="volume_music">"媒体音量"</string>
+    <string name="volume_music_hint_playing_through_bluetooth">"正通过蓝牙播放"</string>
+    <string name="volume_call">"来电音量"</string>
+    <string name="volume_bluetooth_call">"使用蓝牙时的通话音量"</string>
+    <string name="volume_alarm">"警告音量"</string>
+    <string name="volume_notification">"通知音量"</string>
+    <string name="volume_unknown">"音量"</string>
+    <string name="ringtone_default">"默认的手机铃声"</string>
+    <string name="ringtone_default_with_actual">"默认的手机铃声(<xliff:g id="ACTUAL_RINGTONE">%1$s</xliff:g>)"</string>
+    <string name="ringtone_silent">"静音"</string>
+    <string name="ringtone_picker_title">"铃声"</string>
+    <string name="ringtone_unknown">"未知手机铃声"</string>
+  <plurals name="wifi_available">
+    <item quantity="one">"有可用的 Wi-Fi 网络"</item>
+    <item quantity="other">"有可用的 Wi-Fi 网络"</item>
+  </plurals>
+  <plurals name="wifi_available_detailed">
+    <item quantity="one">"打开可用的 Wi-Fi 网络"</item>
+    <item quantity="other">"打开可用的 Wi-Fi 网络"</item>
+  </plurals>
+    <string name="select_character">"插入字符"</string>
+    <string name="sms_control_default_app_name">"未知应用程序"</string>
+    <string name="sms_control_title">"正在发送短信"</string>
+    <string name="sms_control_message">"正在发送大量短信。选择“确定”继续,或选择“取消”停止发送。"</string>
+    <string name="sms_control_yes">"确定"</string>
+    <string name="sms_control_no">"取消"</string>
+    <string name="date_time_set">"设置"</string>
+    <string name="default_permission_group">"默认"</string>
+    <string name="no_permissions">"不需要任何权限"</string>
+    <string name="perms_hide"><b>"隐藏"</b></string>
+    <string name="perms_show_all"><b>"全部显示"</b></string>
+    <string name="googlewebcontenthelper_loading">"正在载入..."</string>
+    <string name="usb_storage_title">"USB 已连接"</string>
+    <string name="usb_storage_message">"您已通过 USB 将手机连接至计算机。如果要在计算机和手机的 SD 卡之间复制文件,请选择“装载”。"</string>
+    <string name="usb_storage_button_mount">"装载"</string>
+    <string name="usb_storage_button_unmount">"不装载"</string>
+    <string name="usb_storage_error_message">"使用 SD 卡进行 USB 存储时出现问题。"</string>
+    <string name="usb_storage_notification_title">"USB 已连接"</string>
+    <string name="usb_storage_notification_message">"选择以将文件复制到计算机或从计算机复制文件。"</string>
+    <string name="usb_storage_stop_notification_title">"关闭 USB 存储设备"</string>
+    <string name="usb_storage_stop_notification_message">"选中以关闭 USB 存储设备。"</string>
+    <string name="usb_storage_stop_title">"关闭 USB 存储设备"</string>
+    <string name="usb_storage_stop_message">"在关闭 USB 存储设备前,请确保您已卸载了 USB 主设备。选择“关闭”关闭 USB 存储设备。"</string>
+    <string name="usb_storage_stop_button_mount">"关闭"</string>
+    <string name="usb_storage_stop_button_unmount">"取消"</string>
+    <string name="usb_storage_stop_error_message">"关闭 USB 存储设备时遇到问题。请检查是否卸载了 USB 主设备,然后重试。"</string>
+    <string name="extmedia_format_title">"格式化 SD 卡"</string>
+    <string name="extmedia_format_message">"您确定要格式化 SD 卡?卡上的所有数据都会丢失。"</string>
+    <string name="extmedia_format_button_format">"格式化"</string>
+    <string name="select_input_method">"选择输入方法"</string>
+    <string name="fast_scroll_alphabet">" ABCDEFGHIJKLMNOPQRSTUVWXYZ"</string>
+    <string name="fast_scroll_numeric_alphabet">" 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"</string>
+    <string name="candidates_style"><u>"候选"</u></string>
+    <string name="ext_media_checking_notification_title">"正在准备 SD 卡"</string>
+    <string name="ext_media_checking_notification_message">"检查是否有错误"</string>
+    <string name="ext_media_nofs_notification_title">"空 SD 卡"</string>
+    <string name="ext_media_nofs_notification_message">"SD 卡为空或使用不支持的文件系统。"</string>
+    <string name="ext_media_unmountable_notification_title">"SD 卡受损"</string>
+    <string name="ext_media_unmountable_notification_message">"SD 卡受损。您可能需要重新格式化您的卡。"</string>
+    <string name="ext_media_badremoval_notification_title">"SD 卡被意外拔除"</string>
+    <string name="ext_media_badremoval_notification_message">"先卸载 SD 卡再拔除,以避免数据丢失。"</string>
+    <string name="ext_media_safe_unmount_notification_title">"SD 卡已安全移除"</string>
+    <string name="ext_media_safe_unmount_notification_message">"现在可以安全移除 SD 卡。"</string>
+    <string name="ext_media_nomedia_notification_title">"已移除 SD 卡"</string>
+    <string name="ext_media_nomedia_notification_message">"SD 卡已移除。请插入新 SD 卡来增加您的设备存储空间。"</string>
+    <string name="activity_list_empty">"找不到匹配的活动"</string>
+    <string name="permlab_pkgUsageStats">"更新组件使用情况统计"</string>
+    <string name="permdesc_pkgUsageStats">"允许修改收集的组件使用情况统计。普通应用程序不能使用此权限。"</string>
+    <!-- no translation found for tutorial_double_tap_to_zoom_message_short (1311810005957319690) -->
+    <skip />
+    <!-- no translation found for gadget_host_error_inflating (2613287218853846830) -->
+    <skip />
+    <!-- no translation found for ime_action_go (8320845651737369027) -->
+    <skip />
+    <!-- no translation found for ime_action_search (658110271822807811) -->
+    <skip />
+    <!-- no translation found for ime_action_send (2316166556349314424) -->
+    <skip />
+    <!-- no translation found for ime_action_next (3138843904009813834) -->
+    <skip />
+    <!-- no translation found for ime_action_default (2840921885558045721) -->
+    <skip />
+</resources>
diff --git a/core/res/res/values-zh-rTW/strings.xml b/core/res/res/values-zh-rTW/strings.xml
new file mode 100644
index 0000000..6d4b8214
--- /dev/null
+++ b/core/res/res/values-zh-rTW/strings.xml
@@ -0,0 +1,815 @@
+<?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.
+-->
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="byteShort">"位元組"</string>
+    <string name="kilobyteShort">"KB"</string>
+    <string name="megabyteShort">"MB"</string>
+    <string name="gigabyteShort">"GB"</string>
+    <string name="terabyteShort">"TB"</string>
+    <string name="petabyteShort">"PB"</string>
+    <string name="untitled">"(未命名)"</string>
+    <string name="ellipsis">"..."</string>
+    <string name="emptyPhoneNumber">"(沒有電話號碼)"</string>
+    <string name="unknownName">"(未知的)"</string>
+    <string name="defaultVoiceMailAlphaTag">"語音信箱"</string>
+    <string name="defaultMsisdnAlphaTag">"MSISDN1"</string>
+    <string name="mmiError">"連線發生問題或錯誤的 MMI 碼。"</string>
+    <string name="serviceEnabled">"服務已啟動。"</string>
+    <string name="serviceEnabledFor">"已啟動服務:"</string>
+    <string name="serviceDisabled">"服務已停用。"</string>
+    <string name="serviceRegistered">"註冊成功。"</string>
+    <string name="serviceErased">"清除成功。"</string>
+    <string name="passwordIncorrect">"密碼錯誤。"</string>
+    <string name="mmiComplete">"MMI 完成。"</string>
+    <string name="badPin">"您輸入的舊 PIN 不正確。"</string>
+    <string name="badPuk">"您輸入的 PUK 不正確。"</string>
+    <string name="mismatchPin">"您輸入的 PIN 不符合。"</string>
+    <string name="invalidPin">"輸入 4~8 個數字的 PIN。"</string>
+    <string name="needPuk">"SIM 卡的 PUK 已鎖定。請輸入 PUK 碼解除鎖定。"</string>
+    <string name="needPuk2">"請輸入 PUK2 以解鎖 SIM 卡。"</string>
+    <string name="ClipMmi">"來電號碼"</string>
+    <string name="ClirMmi">"發話號碼"</string>
+    <string name="CfMmi">"通話指定轉接"</string>
+    <string name="CwMmi">"話中插接"</string>
+    <string name="BaMmi">"通話限制"</string>
+    <string name="PwdMmi">"變更密碼"</string>
+    <string name="PinMmi">"PIN 已變更"</string>
+    <string name="CLIRDefaultOnNextCallOn">"預設不顯示本機號碼,下一通也不顯示。"</string>
+    <string name="CLIRDefaultOnNextCallOff">"預設不顯示本機號碼,但下一通電話顯示。"</string>
+    <string name="CLIRDefaultOffNextCallOn">"預設顯示本機號碼,但下一通不顯示。"</string>
+    <string name="CLIRDefaultOffNextCallOff">"預設顯示本機號碼,下一通也顯示。"</string>
+    <string name="serviceNotProvisioned">"服務未設定完成。"</string>
+    <string name="CLIRPermanent">"本機號碼設定無法變更。"</string>
+    <string name="serviceClassVoice">"語音"</string>
+    <string name="serviceClassData">"資料"</string>
+    <string name="serviceClassFAX">"傳真"</string>
+    <string name="serviceClassSMS">"SMS"</string>
+    <string name="serviceClassDataAsync">"非同步"</string>
+    <string name="serviceClassDataSync">"同步處理"</string>
+    <string name="serviceClassPacket">"封包"</string>
+    <string name="serviceClassPAD">"按鍵"</string>
+    <string name="cfTemplateNotForwarded">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>:未轉接"</string>
+    <string name="cfTemplateForwarded">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
+    <string name="cfTemplateForwardedTime">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>:<xliff:g id="TIME_DELAY">{2}</xliff:g> 秒後 <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
+    <string name="cfTemplateRegistered">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>:未轉接"</string>
+    <string name="cfTemplateRegisteredTime">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>:未轉接"</string>
+    <string name="httpErrorOk">"確定"</string>
+    <string name="httpError">"網頁內容錯誤。"</string>
+    <string name="httpErrorLookup">"找不到網址。"</string>
+    <string name="httpErrorUnsupportedAuthScheme">"不支援此網站驗證機制。"</string>
+    <string name="httpErrorAuth">"驗證失敗。"</string>
+    <string name="httpErrorProxyAuth">"透過 proxy 伺服器驗證失敗。"</string>
+    <string name="httpErrorConnect">"連線到伺服器失敗。"</string>
+    <string name="httpErrorIO">"無法與伺服器溝通,請稍後再試一次 。"</string>
+    <string name="httpErrorTimeout">"連線到伺服器逾時。"</string>
+    <string name="httpErrorRedirectLoop">"此網頁包含太多伺服器轉址。"</string>
+    <string name="httpErrorUnsupportedScheme">"不支援此通訊協定。"</string>
+    <string name="httpErrorFailedSslHandshake">"無法建立安全連線。"</string>
+    <string name="httpErrorBadUrl">"由於網址錯誤,無法開啟此網頁。"</string>
+    <string name="httpErrorFile">"無法存取此檔案。"</string>
+    <string name="httpErrorFileNotFound">"找不到要求的檔案。"</string>
+    <string name="httpErrorTooManyRequests">"太多執行要求。請稍後再試一次。"</string>
+    <string name="contentServiceSync">"同步處理"</string>
+    <string name="contentServiceSyncNotificationTitle">"同步處理"</string>
+    <string name="contentServiceTooManyDeletesNotificationDesc">"太多 <xliff:g id="CONTENT_TYPE">%s</xliff:g> 要刪除。"</string>
+    <string name="low_memory">"手機儲存空間已滿!請刪除一些檔案增加空間。"</string>
+    <string name="me">"我"</string>
+    <string name="power_dialog">"電話選項"</string>
+    <string name="silent_mode">"靜音模式"</string>
+    <string name="turn_on_radio">"開啟無線網路"</string>
+    <string name="turn_off_radio">"關閉無線網路"</string>
+    <string name="screen_lock">"螢幕鎖定"</string>
+    <string name="power_off">"關機"</string>
+    <string name="shutdown_progress">"關機中..."</string>
+    <string name="shutdown_confirm">"手機即將關機。"</string>
+    <string name="no_recent_tasks">"最近沒有存取應用程式。"</string>
+    <string name="global_actions">"電話選項"</string>
+    <string name="global_action_lock">"螢幕鎖定"</string>
+    <string name="global_action_power_off">"關機"</string>
+    <string name="global_action_toggle_silent_mode">"靜音模式"</string>
+    <string name="global_action_silent_mode_on_status">"音效已關閉"</string>
+    <string name="global_action_silent_mode_off_status">"聲音已開啟"</string>
+    <!-- no translation found for global_actions_toggle_airplane_mode (5884330306926307456) -->
+    <skip />
+    <!-- no translation found for global_actions_airplane_mode_on_status (2719557982608919750) -->
+    <skip />
+    <!-- no translation found for global_actions_airplane_mode_off_status (5075070442854490296) -->
+    <skip />
+    <string name="safeMode">"安全模式"</string>
+    <!-- no translation found for android_system_label (6577375335728551336) -->
+    <skip />
+    <string name="permgrouplab_costMoney">"需要額外費用的服務。"</string>
+    <string name="permgroupdesc_costMoney">"若您允許應用程式執行此操作,可能需要支付一些費用。"</string>
+    <string name="permgrouplab_messages">"您的簡訊"</string>
+    <string name="permgroupdesc_messages">"讀取編輯 SMS、電子郵件與其他簡訊。"</string>
+    <string name="permgrouplab_personalInfo">"您的個人資訊"</string>
+    <string name="permgroupdesc_personalInfo">"直接存取手機上的通訊錄與行事曆。"</string>
+    <string name="permgrouplab_location">"您的位置"</string>
+    <string name="permgroupdesc_location">"監視實際位置"</string>
+    <string name="permgrouplab_network">"網路通訊"</string>
+    <string name="permgroupdesc_network">"允許應用程式存取多項網路功能。"</string>
+    <string name="permgrouplab_accounts">"您的 Google 帳戶"</string>
+    <string name="permgroupdesc_accounts">"存取可用 Google 帳戶。"</string>
+    <string name="permgrouplab_hardwareControls">"硬體控制"</string>
+    <string name="permgroupdesc_hardwareControls">"在免持設備上直接存取硬體。"</string>
+    <string name="permgrouplab_phoneCalls">"撥打電話"</string>
+    <string name="permgroupdesc_phoneCalls">"監控、記錄與進行通話。"</string>
+    <string name="permgrouplab_systemTools">"系統工具"</string>
+    <string name="permgroupdesc_systemTools">"系統低階存取與控制。"</string>
+    <string name="permgrouplab_developmentTools">"開發工具"</string>
+    <string name="permgroupdesc_developmentTools">"只有開發者需要此功能。"</string>
+    <string name="permlab_statusBar">"停用或變更狀態列"</string>
+    <string name="permdesc_statusBar">"允許應用程式停用狀態列或新增、移除系統圖示。"</string>
+    <string name="permlab_expandStatusBar">"展開/收攏狀態列"</string>
+    <string name="permdesc_expandStatusBar">"允許應用程式展開或收攏狀態列。"</string>
+    <string name="permlab_processOutgoingCalls">"攔截發話"</string>
+    <string name="permdesc_processOutgoingCalls">"允許應用程式撥打電話、變更撥號號碼。惡意程式可能會以此監控,轉向或阻擋發話。"</string>
+    <string name="permlab_receiveSms">"接收 SMS"</string>
+    <string name="permdesc_receiveSms">"允許應用程式接收、處理 SMS 簡訊。惡意程式可使用此功能監控簡訊或在您讀取前擅自刪除。"</string>
+    <string name="permlab_receiveMms">"接收 MMS"</string>
+    <string name="permdesc_receiveMms">"允許應用程式接收、處理 MMS 簡訊。惡意程式可使用此功能監控簡訊或在您讀取前擅自刪除。"</string>
+    <string name="permlab_sendSms">"傳送 SMS 簡訊"</string>
+    <string name="permdesc_sendSms">"允許應用程式傳送 SMS 簡訊。惡意程式可能會擅自傳送簡訊,增加您的花費。"</string>
+    <string name="permlab_readSms">"讀取 SMS 或 MMS"</string>
+    <string name="permdesc_readSms">"允許應用程式讀取手機或 SIM 卡上的 SMS 簡訊。惡意程式可能會利用此功能讀取您的機密簡訊。"</string>
+    <string name="permlab_writeSms">"編輯 SMS 或 MMS"</string>
+    <string name="permdesc_writeSms">"允許應用程式編輯 SMS 簡訊,存入手機或 SIM 卡。惡意程式可能會刪除您的簡訊。"</string>
+    <string name="permlab_receiveWapPush">"接收 WAP"</string>
+    <string name="permdesc_receiveWapPush">"允許應用程式接收、處理 WAP 簡訊。惡意程式可使用此功能監控簡訊或在您讀取前擅自刪除。"</string>
+    <string name="permlab_getTasks">"取得執行中應用程式"</string>
+    <string name="permdesc_getTasks">"允許應用程式取得最近執行任務的資訊。惡意程式可利用此功能找出其他應用程式的隱私資訊。"</string>
+    <string name="permlab_reorderTasks">"重新安排執行中的應用程式"</string>
+    <string name="permdesc_reorderTasks">"允許應用程式將任務移至前端與背景處理。惡意程式可使用此功能將自己強制拉到前端。"</string>
+    <string name="permlab_setDebugApp">"啟動應用程式除錯"</string>
+    <string name="permdesc_setDebugApp">"允許應用程式為其他程式開啟除錯功能。惡意程式可利用此功能終止其他應用程式。"</string>
+    <string name="permlab_changeConfiguration">"變更介面設定"</string>
+    <string name="permdesc_changeConfiguration">"允許應用程式變更目前設定,例如:地區或字型大小。"</string>
+    <string name="permlab_restartPackages">"重新啟動其他應用程式"</string>
+    <string name="permdesc_restartPackages">"允許應用程式強制重新啟動其他應用程式。"</string>
+    <string name="permlab_setProcessForeground">"保持已停止狀態"</string>
+    <string name="permdesc_setProcessForeground">"允許應用程式在前端執行任何程序 (無法中止)。一般應用程式不需要此功能。"</string>
+    <string name="permlab_forceBack">"強制關閉應用程式"</string>
+    <string name="permdesc_forceBack">"允許應用程式強制關閉在前端運作的活動並返回。一般應用程式不需要此功能。"</string>
+    <string name="permlab_dump">"接收系統內部狀態"</string>
+    <string name="permdesc_dump">"允許應用程式取得系統內部狀態。惡意程式可利用此功能,取得他們應該不需要的私人、安全資料。"</string>
+    <string name="permlab_addSystemService">"發行低階服務"</string>
+    <string name="permdesc_addSystemService">"允許應用程式發行自有低階系統服務。惡意程式可利用此功能綁架系統或偷取、竄改資料內容。"</string>
+    <string name="permlab_runSetActivityWatcher">"監視控制所有應用程式啟動狀態。"</string>
+    <string name="permdesc_runSetActivityWatcher">"允許應用程式監控管理系統啟動活動。惡意程式可能因此癱瘓整個系統。此權限只在開發時需要,一般手機使用不需要此權限。"</string>
+    <string name="permlab_broadcastPackageRemoved">"傳送程式已移除廣播"</string>
+    <string name="permdesc_broadcastPackageRemoved">"允許應用程式在程式被移除時,發送廣播通知。惡意程式可利用此功能,關閉其他正在執行的程式。"</string>
+    <string name="permlab_broadcastSmsReceived">"傳送已接收 SMS 廣播"</string>
+    <string name="permdesc_broadcastSmsReceived">"允許應用程式收到 SMS 簡訊時,廣播收訊通知。惡意程式可利用此功能,偽造 SMS 簡訊。"</string>
+    <string name="permlab_broadcastWapPush">"送出「WAP PUSH 已接收」廣播"</string>
+    <string name="permdesc_broadcastWapPush">"允許應用程式收到 WAP PUSH 簡訊時,廣播收訊通知。惡意程式可利用此功能,偽造 MMS 簡訊回條或秘密更換網頁內容。"</string>
+    <string name="permlab_setProcessLimit">"執行程序限制數"</string>
+    <string name="permdesc_setProcessLimit">"允許應用程式控制可使用的最大執行緒。一般應用程式不需要此功能。"</string>
+    <string name="permlab_setAlwaysFinish">"關閉所有背景程式"</string>
+    <string name="permdesc_setAlwaysFinish">"允許應用程式控制哪些活動在被移到背景執行時,儘速結束。一般應用程式不需要此功能。"</string>
+    <string name="permlab_fotaUpdate">"自動安裝系統更新"</string>
+    <string name="permdesc_fotaUpdate">"允許應用程式接收系統更新擱置通知,並觸發安裝程序。惡意程式可使用未授權更新竄改系統,或干擾更新程序。"</string>
+    <string name="permlab_batteryStats">"編輯電池狀態"</string>
+    <string name="permdesc_batteryStats">"允許修改電池狀態。一般應用程式不會使用此功能。"</string>
+    <string name="permlab_internalSystemWindow">"顯示未授權視窗"</string>
+    <string name="permdesc_internalSystemWindow">"允許內部系統使用介面建立視窗。一般應用程式不會使用此功能。"</string>
+    <string name="permlab_systemAlertWindow">"顯示系統警告"</string>
+    <string name="permdesc_systemAlertWindow">"允許應用程式顯示系統警告視窗。惡意程式可使用此功能接管手機螢幕。"</string>
+    <string name="permlab_setAnimationScale">"編輯全域動畫速度"</string>
+    <string name="permdesc_setAnimationScale">"允許應用程式變更全域動畫速度 (更快或更慢)。"</string>
+    <string name="permlab_manageAppTokens">"管理應用程式 token"</string>
+    <string name="permdesc_manageAppTokens">"允許應用程式略過一般 Z-ordering,建立與管理自己的 token。一般應用程式不需要此功能。"</string>
+    <string name="permlab_injectEvents">"按下按鍵以及控制各按鈕"</string>
+    <string name="permdesc_injectEvents">"允許應用程式發送輸入事件 (按鍵等) 給其他應用程式。惡意程式可使用此功能接管手機。"</string>
+    <string name="permlab_readInputState">"錄製輸入的內容與動作"</string>
+    <string name="permdesc_readInputState">"允許應用程式在使用者操作其他程式時 (例如:輸入密碼),仍可監看輸入的按鍵。一般應用程式應不需要此功能。"</string>
+    <string name="permlab_bindInputMethod">"連結至輸入法"</string>
+    <string name="permdesc_bindInputMethod">"允許擁有人連結至輸入法的最頂層介面。一般應用程式不需使用此選項。"</string>
+    <string name="permlab_setOrientation">"變更螢幕顯示方向"</string>
+    <string name="permdesc_setOrientation">"允許應用程式隨時變更螢幕顯示方向。一般應用程式不需要此功能。"</string>
+    <string name="permlab_signalPersistentProcesses">"傳送 Linux 訊號到應用程式"</string>
+    <string name="permdesc_signalPersistentProcesses">"允許應用程式要求將支援的訊號傳送到所有持續的程序。"</string>
+    <string name="permlab_persistentActivity">"設定應用程式持續執行"</string>
+    <string name="permdesc_persistentActivity">"允許應用程式持續執行,避免系統將它應用到其他程式。"</string>
+    <string name="permlab_deletePackages">"刪除應用程式"</string>
+    <string name="permdesc_deletePackages">"允許應用程式刪除 Android 程式。惡意程式可利用此功能刪除重要應用程式。"</string>
+    <string name="permlab_clearAppUserData">"刪除其他應用程式資料"</string>
+    <string name="permdesc_clearAppUserData">"允許應用程式清除使用者資料。"</string>
+    <string name="permlab_deleteCacheFiles">"刪除其他應用程式快取"</string>
+    <string name="permdesc_deleteCacheFiles">"允許應用程式刪除快取檔案。"</string>
+    <string name="permlab_getPackageSize">"估算應用程式占用的儲存空間"</string>
+    <string name="permdesc_getPackageSize">"允許應用程式取得程式碼、資料與快取大小"</string>
+    <string name="permlab_installPackages">"直接安裝應用程式"</string>
+    <string name="permdesc_installPackages">"允許應用程式安裝新的 Android 程式或更新。惡意程式可利用此功能新增具有高權限的程式。"</string>
+    <string name="permlab_clearAppCache">"刪除所有應用程式快取資料。"</string>
+    <string name="permdesc_clearAppCache">"允許應用程式刪除快取目錄裡的檔案,釋放儲存空間。此操作通常受到系統程序嚴格限制。"</string>
+    <string name="permlab_readLogs">"讀取系統記錄檔"</string>
+    <string name="permdesc_readLogs">"允許應用程式讀取系統記錄檔。此項操作可讓應用程式了解目前手機操作狀態,但內容應不含任何個人或隱私資訊。"</string>
+    <string name="permlab_diagnostic">"讀寫 diag 擁有的資源"</string>
+    <string name="permdesc_diagnostic">"允許應用程式讀寫 diag 群組的資源;例如:/dev 裡的檔案。這可能會影響系統穩定性與安全性。此功能僅供製造商或技術人員用於硬體規格偵測。"</string>
+    <string name="permlab_changeComponentState">"啟用或停用應用程式元件"</string>
+    <string name="permdesc_changeComponentState">"允許應用程式變更是否啟用其他元件或應用程式。惡意程式可利用此功能,停用重要的手機功能。請在取得許可的狀態下小心使用,此功能可能導致應用程式元件無法使用、不一致或不穩定。"</string>
+    <string name="permlab_setPreferredApplications">"設定喜好的應用程式"</string>
+    <string name="permdesc_setPreferredApplications">"允許應用程式修改您偏好使用的應用程式。惡意程式可能依此秘密竄改執行的程式,偽造已存在的程式以收集私人資料。"</string>
+    <string name="permlab_writeSettings">"編輯全域系統設定"</string>
+    <string name="permdesc_writeSettings">"允許應用程式修改系統設定。惡意程式可使用此功能中斷系統設定。"</string>
+    <string name="permlab_writeSecureSettings">"編輯安全系統設定"</string>
+    <string name="permdesc_writeSecureSettings">"允許應用程式修改系統的安全設定資料。一般應用程式不會使用此功能。"</string>
+    <string name="permlab_writeGservices">"修改 Google 服務地圖"</string>
+    <string name="permdesc_writeGservices">"允許應用程式修改 Google 服務地圖。一般應用程式不會使用此功能。"</string>
+    <string name="permlab_receiveBootCompleted">"開機時自動啟用"</string>
+    <string name="permdesc_receiveBootCompleted">"允許應用程式在開機後盡快啟動。此項設定會讓開機時間拉長,並允許應用程式持續執行,因此拖慢手機速度。"</string>
+    <string name="permlab_broadcastSticky">"傳送附屬廣播"</string>
+    <string name="permdesc_broadcastSticky">"允許應用程式在廣播結束後,持續送出附屬廣播。惡意程式可利用此功能,佔據過多記憶體,讓手機變慢或不穩定。"</string>
+    <string name="permlab_readContacts">"讀取聯絡人資料"</string>
+    <string name="permdesc_readContacts">"允許應用程式讀取手機上所有聯絡人 (地址)。惡意程式可利用此功能將您的資料傳送給其他人。"</string>
+    <string name="permlab_writeContacts">"輸入聯絡人資料"</string>
+    <string name="permdesc_writeContacts">"允許應用程式更改聯絡資訊 (地址)。惡意程式可利用此功能,清除或修改聯絡資料。"</string>
+    <string name="permlab_writeOwnerData">"輸入擁有者資料"</string>
+    <string name="permdesc_writeOwnerData">"允許應用程式更改手機擁有者資料。惡意程式可利用此功能,清除或修改擁有者資料。"</string>
+    <string name="permlab_readOwnerData">"讀取擁有者資料"</string>
+    <string name="permdesc_readOwnerData">"允許應用程式讀取手機擁有者資料。惡意程式可利用此功能讀取擁有者資料。"</string>
+    <string name="permlab_readCalendar">"讀取行事曆資料"</string>
+    <string name="permdesc_readCalendar">"允許應用程式讀取手機上所有行事曆行程。惡意程式可利用此功能將您的行事曆行程傳送給其他人。"</string>
+    <string name="permlab_writeCalendar">"寫入行事曆資料"</string>
+    <string name="permdesc_writeCalendar">"允許應用程式更改行事曆行程。惡意程式可利用此功能,清除或修改行事曆資料。"</string>
+    <string name="permlab_accessMockLocation">"模擬位置來源以供測試"</string>
+    <string name="permdesc_accessMockLocation">"建立模擬位置來源以供測試。惡意程式可利用此功能覆寫 GPS 或網路服務商回傳的位置及/或狀態。"</string>
+    <string name="permlab_accessLocationExtraCommands">"存取額外位置提供者命令"</string>
+    <string name="permdesc_accessLocationExtraCommands">"存取額外位置提供者命令。惡意程式可利用此功能干擾 GPS 或其他位置來源。"</string>
+    <string name="permlab_accessFineLocation">"良好的 (GPS) 位置"</string>
+    <string name="permdesc_accessFineLocation">"存取正確位置來源,例如:手機 GPS。惡意程式可使用此功能知道您的位置,並且可能額外消耗電力。"</string>
+    <string name="permlab_accessCoarseLocation">"大略位置 (以網路為基準)"</string>
+    <string name="permdesc_accessCoarseLocation">"接收粗略的位置來源 (例如:行動網路資料庫),計算出目前大概位置。惡意程式可使用此功能得知您的所在地。"</string>
+    <string name="permlab_accessSurfaceFlinger">"存取 SurfaceFlinger"</string>
+    <string name="permdesc_accessSurfaceFlinger">"允許應用程式使用 SurfaceFlinger 低階功能。"</string>
+    <string name="permlab_readFrameBuffer">"讀取框架緩衝"</string>
+    <string name="permdesc_readFrameBuffer">"允許應用程式讀取框架緩衝的內容。"</string>
+    <string name="permlab_modifyAudioSettings">"變更音訊設定"</string>
+    <string name="permdesc_modifyAudioSettings">"允許應用程式編輯全域音訊設定,例如音量與路由。"</string>
+    <string name="permlab_recordAudio">"錄製音訊"</string>
+    <string name="permdesc_recordAudio">"允許應用程式存取音訊錄製路徑。"</string>
+    <string name="permlab_camera">"照相"</string>
+    <string name="permdesc_camera">"允許應用程式使用相機照相。此功能可讓應用程式隨時透過相機拍攝照片。"</string>
+    <string name="permlab_brick">"永久停用電話"</string>
+    <string name="permdesc_brick">"允許應用程式永久停用手機。此項操作非常危險。"</string>
+    <string name="permlab_reboot">"強制重開機"</string>
+    <string name="permdesc_reboot">"允許應用程式強制重開機。"</string>
+    <string name="permlab_mount_unmount_filesystems">"掛載/卸載檔案系統"</string>
+    <string name="permdesc_mount_unmount_filesystems">"允許應用程式掛載/卸載抽取式儲存設備的檔案系統。"</string>
+    <string name="permlab_mount_format_filesystems">"將外接式儲存裝置格式化"</string>
+    <string name="permdesc_mount_format_filesystems">"允許應用程式將可移除式儲存裝置格式化。"</string>
+    <string name="permlab_vibrate">"控制震動器"</string>
+    <string name="permdesc_vibrate">"允許應用程式控制震動器。"</string>
+    <string name="permlab_flashlight">"控制閃光燈"</string>
+    <string name="permdesc_flashlight">"允許應用程式控制閃光燈。"</string>
+    <string name="permlab_hardware_test">"測試硬體"</string>
+    <string name="permdesc_hardware_test">"允許應用程式控制各種週邊設備,以供測試用。"</string>
+    <string name="permlab_callPhone">"直接撥打電話號碼"</string>
+    <string name="permdesc_callPhone">"允許應用程式自行撥打電話。惡意程式可能會撥打其他電話,造成額外支出。但請注意此選項不允許應用程式撥打緊急電話號碼。"</string>
+    <string name="permlab_callPrivileged">"直接撥打任何電話號碼"</string>
+    <string name="permdesc_callPrivileged">"允許應用程式自行撥打任何電話號碼,包括緊急電話號碼。惡意程式可利用此功能濫用緊急服務,撥打不需要或違法的電話。"</string>
+    <string name="permlab_locationUpdates">"控制位置更新通知"</string>
+    <string name="permdesc_locationUpdates">"允許啟用/停用無線通訊位置更新通知。一般應用程式不會使用此功能。"</string>
+    <string name="permlab_checkinProperties">"存取登機選項"</string>
+    <string name="permdesc_checkinProperties">"允許讀寫登機服務上傳的資料。一般應用程式不會使用此功能。"</string>
+    <string name="permlab_bindGadget">"選擇小工具"</string>
+    <string name="permdesc_bindGadget">"允許應用程式告知系統哪些應用程式可以使用哪些小工具。擁有此權限的應用程式可以讓其他應用程式使用個人資料。一般應用程式不會使用此功能。"</string>
+    <string name="permlab_modifyPhoneState">"修改手機狀態"</string>
+    <string name="permdesc_modifyPhoneState">"允許應用程式控制電話功能。擁有此權限的程式可自行切換網路、開關無線通訊功能。"</string>
+    <string name="permlab_readPhoneState">"讀取手機狀態"</string>
+    <string name="permdesc_readPhoneState">"允許應用程式存取裝置的電話功能。通話時,有此權限的應用程式可設定手機是否通話、撥出的號碼等等。"</string>
+    <string name="permlab_wakeLock">"防止手機進入待命狀態"</string>
+    <string name="permdesc_wakeLock">"允許應用程式阻止手機進入待命。"</string>
+    <string name="permlab_devicePower">"開啟或關閉電源"</string>
+    <string name="permdesc_devicePower">"允許應用程式開啟或關閉電話。"</string>
+    <string name="permlab_factoryTest">"在出廠測試模式下執行"</string>
+    <string name="permdesc_factoryTest">"執行低階製造商測試,允許完全存取手機硬體。此功能只能在手機是製造商測試模式下才可執行。"</string>
+    <string name="permlab_setWallpaper">"設定桌布"</string>
+    <string name="permdesc_setWallpaper">"允許應用程式設定系統桌布。"</string>
+    <string name="permlab_setWallpaperHints">"設定桌布大小提示"</string>
+    <string name="permdesc_setWallpaperHints">"允許應用程式設定系統桌布大小提示。"</string>
+    <string name="permlab_masterClear">"將系統回復出廠預設值"</string>
+    <string name="permdesc_masterClear">"允許應用程式將手機完全重設至出廠設定,清除所有資料、設定與已安裝程式。"</string>
+    <string name="permlab_setTimeZone">"設定時區"</string>
+    <string name="permdesc_setTimeZone">"允許應用程式變更時區。"</string>
+    <string name="permlab_getAccounts">"發現已知帳戶。"</string>
+    <string name="permdesc_getAccounts">"允許應用程式取得手機上的帳戶清單。"</string>
+    <string name="permlab_accessNetworkState">"檢視網路狀態"</string>
+    <string name="permdesc_accessNetworkState">"允許應用程式檢視網路狀態。"</string>
+    <string name="permlab_createNetworkSockets">"完整網際網路存取"</string>
+    <string name="permdesc_createNetworkSockets">"允許應用程式建立網路設定。"</string>
+    <string name="permlab_writeApnSettings">"輸入存取點名稱設定"</string>
+    <string name="permdesc_writeApnSettings">"允許應用程式修改 APN 設定,例如:Proxy 及 APN 的連接埠。"</string>
+    <string name="permlab_changeNetworkState">"變更網路連線"</string>
+    <string name="permdesc_changeNetworkState">"允許應用程式變更網路連線狀態。"</string>
+    <string name="permlab_changeBackgroundDataSetting">"變更背景資料使用設定"</string>
+    <string name="permdesc_changeBackgroundDataSetting">"允許應用程式變更背景資料使用設定。"</string>
+    <string name="permlab_accessWifiState">"檢視 Wi-Fi 狀態"</string>
+    <string name="permdesc_accessWifiState">"允許應用程式檢視 Wi-Fi 狀態資訊。"</string>
+    <string name="permlab_changeWifiState">"變更 Wi-Fi 狀態"</string>
+    <string name="permdesc_changeWifiState">"允許應用程式與 Wi-Fi 存取點連線或斷線,並可變更 Wi-Fi 網路設定。"</string>
+    <string name="permlab_bluetoothAdmin">"藍牙管理"</string>
+    <string name="permdesc_bluetoothAdmin">"允許應用程式設定本機藍牙電話,以及偵測與配對其他遠端裝置。"</string>
+    <string name="permlab_bluetooth">"建立藍牙連線"</string>
+    <string name="permdesc_bluetooth">"允許應用程式檢視本地藍牙電話設定,並與其他配對裝置連線。"</string>
+    <string name="permlab_disableKeyguard">"停用按鍵鎖定"</string>
+    <string name="permdesc_disableKeyguard">"允許應用程式停用按鍵鎖定以及其他相關的密碼安全性。合理的範例是:收到來電時解開按鍵鎖定,通話結束後重新啟動按鍵鎖定。"</string>
+    <string name="permlab_readSyncSettings">"讀取同步處理設定"</string>
+    <string name="permdesc_readSyncSettings">"允許應用程式讀取同步處理設定,例如:是否同步處理 [通訊錄]。"</string>
+    <string name="permlab_writeSyncSettings">"編輯同步處理設定"</string>
+    <string name="permdesc_writeSyncSettings">"允許應用程式修改同步處理設定,例如:是否要同步處理 [通訊錄]。"</string>
+    <string name="permlab_readSyncStats">"讀取同步處理狀態"</string>
+    <string name="permdesc_readSyncStats">"允許應用程式讀取同步處理狀態;例如:同步處理記錄。"</string>
+    <string name="permlab_subscribedFeedsRead">"讀取訂閱資訊提供"</string>
+    <string name="permdesc_subscribedFeedsRead">"允許應用程式取得目前已同步處理的資訊提供。"</string>
+    <string name="permlab_subscribedFeedsWrite">"寫入訂閱資訊提供"</string>
+    <string name="permdesc_subscribedFeedsWrite">"允許應用程式修改已同步處理的資訊提供。惡意程式可使用此功能變更已同步處理的資訊提供。"</string>
+    <string name="permlab_readDictionary">"讀取使用者定義的字典"</string>
+    <string name="permdesc_readDictionary">"允許應用程式讀取使用者儲存在使用者字典內的任何私人字詞、名稱和詞組。"</string>
+    <string name="permlab_writeDictionary">"寫入使用者定義的字典"</string>
+    <string name="permdesc_writeDictionary">"允許應用程式將新字詞寫入使用者的字典。"</string>
+  <string-array name="phoneTypes">
+    <item>"首頁"</item>
+    <item>"行動"</item>
+    <item>"工作"</item>
+    <item>"辦公傳真"</item>
+    <item>"家用傳真"</item>
+    <item>"呼叫器"</item>
+    <item>"其他"</item>
+    <item>"自訂"</item>
+  </string-array>
+  <string-array name="emailAddressTypes">
+    <item>"首頁"</item>
+    <item>"工作"</item>
+    <item>"其他"</item>
+    <item>"自訂"</item>
+  </string-array>
+  <string-array name="postalAddressTypes">
+    <item>"首頁"</item>
+    <item>"工作"</item>
+    <item>"其他"</item>
+    <item>"自訂"</item>
+  </string-array>
+  <string-array name="imAddressTypes">
+    <item>"首頁"</item>
+    <item>"工作"</item>
+    <item>"其他"</item>
+    <item>"自訂"</item>
+  </string-array>
+  <string-array name="organizationTypes">
+    <item>"工作"</item>
+    <item>"其他"</item>
+    <item>"自訂"</item>
+  </string-array>
+  <string-array name="imProtocols">
+    <item>"AIM"</item>
+    <item>"Windows Live"</item>
+    <item>"Yahoo"</item>
+    <item>"Skype"</item>
+    <item>"QQ"</item>
+    <item>"Google Talk"</item>
+    <item>"ICQ"</item>
+    <item>"Jabber"</item>
+  </string-array>
+    <string name="keyguard_password_enter_pin_code">"輸入 PIN 碼"</string>
+    <string name="keyguard_password_wrong_pin_code">"PIN 碼錯誤!"</string>
+    <string name="keyguard_label_text">"若要解鎖,按下 [選單]、[0]。"</string>
+    <string name="emergency_call_dialog_number_for_display">"緊急電話號碼"</string>
+    <string name="lockscreen_carrier_default">"(沒有服務)"</string>
+    <string name="lockscreen_screen_locked">"螢幕已鎖定。"</string>
+    <string name="lockscreen_instructions_when_pattern_enabled">"按下 [選單] 解鎖或撥打緊急電話。"</string>
+    <string name="lockscreen_instructions_when_pattern_disabled">"按下 [選單] 解鎖。"</string>
+    <string name="lockscreen_pattern_instructions">"畫出解鎖圖形"</string>
+    <string name="lockscreen_emergency_call">"緊急電話"</string>
+    <string name="lockscreen_pattern_correct">"正確!"</string>
+    <string name="lockscreen_pattern_wrong">"抱歉,請再試一次"</string>
+    <!-- no translation found for lockscreen_plugged_in (613343852842944435) -->
+    <skip />
+    <string name="lockscreen_low_battery">"請連接充電器。"</string>
+    <string name="lockscreen_missing_sim_message_short">"沒有 SIM  卡。"</string>
+    <string name="lockscreen_missing_sim_message">"手機未插入 SIM 卡。"</string>
+    <string name="lockscreen_missing_sim_instructions">"請插入 SIM 卡。"</string>
+    <string name="lockscreen_network_locked_message">"網路已鎖定"</string>
+    <string name="lockscreen_sim_puk_locked_message">"SIM 的 PUK 已鎖定。"</string>
+    <string name="lockscreen_sim_puk_locked_instructions">"請聯絡客服中心。"</string>
+    <string name="lockscreen_sim_locked_message">"SIM 卡已鎖定。"</string>
+    <string name="lockscreen_sim_unlock_progress_dialog_message">"解鎖 SIM 卡中..."</string>
+    <string name="lockscreen_too_many_failed_attempts_dialog_message">"畫出解鎖圖形已錯誤 <xliff:g id="NUMBER_0">%d</xliff:g> 次。"\n\n" 請在 <xliff:g id="NUMBER_1">%d</xliff:g> 秒後再嘗試。"</string>
+    <string name="lockscreen_failed_attempts_almost_glogin">"畫出解鎖圖形已錯誤 <xliff:g id="NUMBER_0">%d</xliff:g> 次。再錯誤 <xliff:g id="NUMBER_1">%d</xliff:g> 次後,系統會要求使用 Google 登入來解鎖。"\n\n" 請在 <xliff:g id="NUMBER_2">%d</xliff:g> 秒後再試一次。"</string>
+    <string name="lockscreen_too_many_failed_attempts_countdown">"<xliff:g id="NUMBER">%d</xliff:g> 秒後再試一次。"</string>
+    <string name="lockscreen_forgot_pattern_button_text">"忘記解鎖圖形?"</string>
+    <string name="lockscreen_glogin_too_many_attempts">"解鎖圖形出錯次數過多!"</string>
+    <string name="lockscreen_glogin_instructions">"若要解鎖,"\n"請以您的 Google 帳戶登入"</string>
+    <string name="lockscreen_glogin_username_hint">"使用者名稱 (電子郵件)"</string>
+    <string name="lockscreen_glogin_password_hint">"密碼"</string>
+    <string name="lockscreen_glogin_submit_button">"登入"</string>
+    <string name="lockscreen_glogin_invalid_input">"使用者名稱或密碼錯誤。"</string>
+    <string name="status_bar_time_format">"<xliff:g id="HOUR">h</xliff:g>:<xliff:g id="MINUTE">mm</xliff:g> <xliff:g id="AMPM">AA</xliff:g>"</string>
+    <string name="hour_minute_ampm">"<xliff:g id="HOUR">%-l</xliff:g>:<xliff:g id="MINUTE">%M</xliff:g><xliff:g id="AMPM">%P</xliff:g>"</string>
+    <string name="hour_minute_cap_ampm">"<xliff:g id="HOUR">%-l</xliff:g>:<xliff:g id="MINUTE">%M</xliff:g><xliff:g id="AMPM">%p</xliff:g>"</string>
+    <!-- no translation found for hour_ampm (4329881288269772723) -->
+    <skip />
+    <!-- no translation found for hour_cap_ampm (1829009197680861107) -->
+    <skip />
+    <string name="status_bar_clear_all_button">"清除通知"</string>
+    <string name="status_bar_no_notifications_title">"沒有通知"</string>
+    <string name="status_bar_ongoing_events_title">"進行中"</string>
+    <string name="status_bar_latest_events_title">"通知"</string>
+    <!-- no translation found for battery_status_text_percent_format (7660311274698797147) -->
+    <skip />
+    <string name="battery_status_charging">"充電中"</string>
+    <string name="battery_low_title">"請連接充電器"</string>
+    <string name="battery_low_subtitle">"電池電量即將不足:"</string>
+    <string name="battery_low_percent_format">"電池電量不到 <xliff:g id="NUMBER">%d%%</xliff:g>。"</string>
+    <string name="factorytest_failed">"出廠測試失敗"</string>
+    <string name="factorytest_not_system">"FACTORY_TEST 動作只支援安裝在 /system/app 裡的程式。"</string>
+    <string name="factorytest_no_action">"找不到提供 FACTORY_TEST 的程式。"</string>
+    <string name="factorytest_reboot">"重新開機"</string>
+    <string name="js_dialog_title">"「<xliff:g id="TITLE">%s</xliff:g>」的網頁指出:"</string>
+    <string name="js_dialog_title_default">"JavaScript"</string>
+    <string name="js_dialog_before_unload">"離開此頁?"\n\n"<xliff:g id="MESSAGE">%s</xliff:g>"\n\n" 選取 [確定] 離開此頁;或 [取消] 留在此頁。"</string>
+    <string name="save_password_label">"確認"</string>
+    <string name="save_password_message">"是否記憶此密碼?"</string>
+    <string name="save_password_notnow">"現在不要"</string>
+    <string name="save_password_remember">"記住"</string>
+    <string name="save_password_never">"從不"</string>
+    <string name="open_permission_deny">"您沒有開啟此頁的權限。"</string>
+    <string name="text_copied">"已複製到剪貼簿的文字。"</string>
+    <string name="more_item_label">"更多"</string>
+    <string name="prepend_shortcut_label">"[選單]+"</string>
+    <string name="menu_space_shortcut_label">"空白鍵"</string>
+    <string name="menu_enter_shortcut_label">"輸入"</string>
+    <string name="menu_delete_shortcut_label">"刪除"</string>
+    <string name="search_go">"搜尋"</string>
+    <string name="today">"今天"</string>
+    <string name="yesterday">"昨天"</string>
+    <string name="tomorrow">"明天"</string>
+    <string name="oneMonthDurationPast">"1 個月以前"</string>
+    <string name="beforeOneMonthDurationPast">"1 個月前"</string>
+  <plurals name="num_seconds_ago">
+    <item quantity="one">"1 秒以前"</item>
+    <item quantity="other">"<xliff:g id="COUNT">%d</xliff:g> 秒以前"</item>
+  </plurals>
+  <plurals name="num_minutes_ago">
+    <item quantity="one">"1 分鐘以前"</item>
+    <item quantity="other">"<xliff:g id="COUNT">%d</xliff:g> 分鐘以前"</item>
+  </plurals>
+  <plurals name="num_hours_ago">
+    <item quantity="one">"1 小時以前"</item>
+    <item quantity="other">"<xliff:g id="COUNT">%d</xliff:g> 小時以前"</item>
+  </plurals>
+  <plurals name="num_days_ago">
+    <item quantity="one">"昨天"</item>
+    <item quantity="other">"<xliff:g id="COUNT">%d</xliff:g> 天以前"</item>
+  </plurals>
+  <plurals name="in_num_seconds">
+    <item quantity="one">"1 秒內"</item>
+    <item quantity="other">"在 <xliff:g id="COUNT">%d</xliff:g> 秒內"</item>
+  </plurals>
+  <plurals name="in_num_minutes">
+    <item quantity="one">"1 分鐘內"</item>
+    <item quantity="other">"在 <xliff:g id="COUNT">%d</xliff:g> 分鐘內"</item>
+  </plurals>
+  <plurals name="in_num_hours">
+    <item quantity="one">"1 小時內"</item>
+    <item quantity="other">"<xliff:g id="COUNT">%d</xliff:g> 小時內"</item>
+  </plurals>
+  <plurals name="in_num_days">
+    <item quantity="one">"明天"</item>
+    <item quantity="other">"<xliff:g id="COUNT">%d</xliff:g> 天內"</item>
+  </plurals>
+  <plurals name="abbrev_num_seconds_ago">
+    <item quantity="one">"1 秒以前"</item>
+    <item quantity="other">"<xliff:g id="COUNT">%d</xliff:g> 秒以前"</item>
+  </plurals>
+  <plurals name="abbrev_num_minutes_ago">
+    <item quantity="one">"1 分鐘以前"</item>
+    <item quantity="other">"<xliff:g id="COUNT">%d</xliff:g> 分鐘以前"</item>
+  </plurals>
+  <plurals name="abbrev_num_hours_ago">
+    <item quantity="one">"1 小時以前"</item>
+    <item quantity="other">"<xliff:g id="COUNT">%d</xliff:g> 小時以前"</item>
+  </plurals>
+  <plurals name="abbrev_num_days_ago">
+    <item quantity="one">"昨天"</item>
+    <item quantity="other">"<xliff:g id="COUNT">%d</xliff:g> 天以前"</item>
+  </plurals>
+  <plurals name="abbrev_in_num_seconds">
+    <item quantity="one">"1 秒內"</item>
+    <item quantity="other">"<xliff:g id="COUNT">%d</xliff:g> 秒內"</item>
+  </plurals>
+  <plurals name="abbrev_in_num_minutes">
+    <item quantity="one">"1 分鐘內"</item>
+    <item quantity="other">"<xliff:g id="COUNT">%d</xliff:g> 分鐘內"</item>
+  </plurals>
+  <plurals name="abbrev_in_num_hours">
+    <item quantity="one">"1 小時內"</item>
+    <item quantity="other">"<xliff:g id="COUNT">%d</xliff:g> 小時內"</item>
+  </plurals>
+  <plurals name="abbrev_in_num_days">
+    <item quantity="one">"明天"</item>
+    <item quantity="other">"<xliff:g id="COUNT">%d</xliff:g> 天內"</item>
+  </plurals>
+    <string name="preposition_for_date">"%s"</string>
+    <string name="preposition_for_time">"%s"</string>
+    <string name="preposition_for_year">"%s"</string>
+    <string name="day">"天"</string>
+    <string name="days">"天"</string>
+    <string name="hour">"小時"</string>
+    <string name="hours">"小時"</string>
+    <string name="minute">"分鐘"</string>
+    <string name="minutes">"分鐘"</string>
+    <string name="second">"秒"</string>
+    <string name="seconds">"秒"</string>
+    <string name="week">"週"</string>
+    <string name="weeks">"週"</string>
+    <string name="year">"年"</string>
+    <string name="years">"年"</string>
+    <string name="sunday">"星期日"</string>
+    <string name="monday">"星期一"</string>
+    <string name="tuesday">"星期二"</string>
+    <string name="wednesday">"星期三"</string>
+    <string name="thursday">"星期四"</string>
+    <string name="friday">"星期五"</string>
+    <string name="saturday">"星期六"</string>
+    <string name="every_weekday">"每天 (週一至週五)"</string>
+    <string name="daily">"每天"</string>
+    <string name="weekly">"每週 <xliff:g id="DAY">%s</xliff:g>"</string>
+    <string name="monthly">"每月"</string>
+    <string name="yearly">"每年"</string>
+    <string name="VideoView_error_title">"無法播放影片"</string>
+    <string name="VideoView_error_text_unknown">"抱歉,無法撥放此影片。"</string>
+    <string name="VideoView_error_button">"確定"</string>
+    <string name="am">"上午"</string>
+    <string name="pm">"下午"</string>
+    <string name="numeric_date">"<xliff:g id="MONTH">%m</xliff:g>/<xliff:g id="DAY">%d</xliff:g>/<xliff:g id="YEAR">%Y</xliff:g>"</string>
+    <string name="wday1_date1_time1_wday2_date2_time2">"<xliff:g id="WEEKDAY1">%1$s</xliff:g>,<xliff:g id="DATE1">%2$s</xliff:g>,<xliff:g id="TIME1">%3$s</xliff:g> – <xliff:g id="WEEKDAY2">%4$s</xliff:g>,<xliff:g id="DATE2">%5$s</xliff:g>,<xliff:g id="TIME2">%6$s</xliff:g>"</string>
+    <string name="wday1_date1_wday2_date2">"<xliff:g id="WEEKDAY1">%1$s</xliff:g>,<xliff:g id="DATE1">%2$s</xliff:g> – <xliff:g id="WEEKDAY2">%4$s</xliff:g>,<xliff:g id="DATE2">%5$s</xliff:g>"</string>
+    <string name="date1_time1_date2_time2">"<xliff:g id="DATE1">%2$s</xliff:g>,<xliff:g id="TIME1">%3$s</xliff:g> – <xliff:g id="DATE2">%5$s</xliff:g>,<xliff:g id="TIME2">%6$s</xliff:g>"</string>
+    <string name="date1_date2">"<xliff:g id="DATE1">%2$s</xliff:g> – <xliff:g id="DATE2">%5$s</xliff:g>"</string>
+    <string name="time1_time2">"<xliff:g id="TIME1">%1$s</xliff:g> – <xliff:g id="TIME2">%2$s</xliff:g>"</string>
+    <string name="time_wday_date">"<xliff:g id="TIME_RANGE">%1$s</xliff:g>,<xliff:g id="WEEKDAY">%2$s</xliff:g>,<xliff:g id="DATE">%3$s</xliff:g>"</string>
+    <string name="wday_date">"<xliff:g id="WEEKDAY">%2$s</xliff:g>,<xliff:g id="DATE">%3$s</xliff:g>"</string>
+    <string name="time_date">"<xliff:g id="TIME_RANGE">%1$s</xliff:g>,<xliff:g id="DATE">%3$s</xliff:g>"</string>
+    <string name="date_time">"<xliff:g id="DATE">%1$s</xliff:g>,<xliff:g id="TIME">%2$s</xliff:g>"</string>
+    <string name="relative_time">"<xliff:g id="DATE">%1$s</xliff:g>,<xliff:g id="TIME">%2$s</xliff:g>"</string>
+    <string name="time_wday">"<xliff:g id="TIME_RANGE">%1$s</xliff:g>,<xliff:g id="WEEKDAY">%2$s</xliff:g>"</string>
+    <string name="full_date_month_first" format="date">"<xliff:g id="MONTH">MMMM</xliff:g>' '<xliff:g id="DAY">d</xliff:g>','<xliff:g id="YEAR">yyyy</xliff:g>"</string>
+    <string name="full_date_day_first" format="date">"<xliff:g id="DAY">d</xliff:g>' '<xliff:g id="MONTH">MMMM</xliff:g>','<xliff:g id="YEAR">yyyy</xliff:g>"</string>
+    <string name="medium_date_month_first" format="date">"<xliff:g id="MONTH">MMM</xliff:g>' '<xliff:g id="DAY">d</xliff:g>','<xliff:g id="YEAR">yyyy</xliff:g>"</string>
+    <string name="medium_date_day_first" format="date">"<xliff:g id="DAY">d</xliff:g>' '<xliff:g id="MONTH">MMM</xliff:g>','<xliff:g id="YEAR">yyyy</xliff:g>"</string>
+    <string name="twelve_hour_time_format" format="date">"<xliff:g id="HOUR">h</xliff:g>':'<xliff:g id="MINUTE">mm</xliff:g>' '<xliff:g id="AMPM">a</xliff:g>"</string>
+    <string name="twenty_four_hour_time_format" format="date">"<xliff:g id="HOUR">H</xliff:g>':'<xliff:g id="MINUTE">mm</xliff:g>"</string>
+    <string name="noon">"中午"</string>
+    <string name="Noon">"中午"</string>
+    <string name="midnight">"午夜"</string>
+    <string name="Midnight">"午夜"</string>
+    <!-- no translation found for month_day (3693060561170538204) -->
+    <skip />
+    <!-- no translation found for month (1976700695144952053) -->
+    <skip />
+    <string name="month_day_year">"<xliff:g id="MONTH">%B</xliff:g> <xliff:g id="DAY">%-d</xliff:g>,<xliff:g id="YEAR">%Y</xliff:g>"</string>
+    <!-- no translation found for month_year (2106203387378728384) -->
+    <skip />
+    <string name="time_of_day">"<xliff:g id="HOUR">%H</xliff:g>:<xliff:g id="MINUTE">%M</xliff:g>:<xliff:g id="SECOND">%S</xliff:g>"</string>
+    <string name="date_and_time">"<xliff:g id="HOUR">%H</xliff:g>:<xliff:g id="MINUTE">%M</xliff:g>:<xliff:g id="SECOND">%S</xliff:g> <xliff:g id="MONTH">%B</xliff:g> <xliff:g id="DAY">%-d</xliff:g>,<xliff:g id="YEAR">%Y</xliff:g>"</string>
+    <string name="same_year_md1_md2">"<xliff:g id="MONTH1">%2$s</xliff:g> <xliff:g id="DAY1">%3$s</xliff:g> – <xliff:g id="MONTH2">%7$s</xliff:g> <xliff:g id="DAY2">%8$s</xliff:g>"</string>
+    <string name="same_year_wday1_md1_wday2_md2">"<xliff:g id="WEEKDAY1">%1$s</xliff:g>,<xliff:g id="MONTH1">%2$s</xliff:g> <xliff:g id="DAY1_0">%3$s</xliff:g> – <xliff:g id="WEEKDAY2">%6$s</xliff:g>,<xliff:g id="MONTH2">%7$s</xliff:g> <xliff:g id="DAY2_1">%8$s</xliff:g>"</string>
+    <string name="same_year_mdy1_mdy2">"<xliff:g id="MONTH1">%2$s</xliff:g> <xliff:g id="DAY1">%3$s</xliff:g> – <xliff:g id="MONTH2">%7$s</xliff:g> <xliff:g id="DAY2">%8$s</xliff:g>,<xliff:g id="YEAR">%9$s</xliff:g>"</string>
+    <string name="same_year_wday1_mdy1_wday2_mdy2">"<xliff:g id="WEEKDAY1">%1$s</xliff:g>,<xliff:g id="MONTH1">%2$s</xliff:g> <xliff:g id="DAY1_0">%3$s</xliff:g> – <xliff:g id="WEEKDAY2">%6$s</xliff:g>,<xliff:g id="MONTH2">%7$s</xliff:g> <xliff:g id="DAY2_1">%8$s</xliff:g>,<xliff:g id="YEAR">%9$s</xliff:g>"</string>
+    <string name="same_year_md1_time1_md2_time2">"<xliff:g id="MONTH1">%2$s</xliff:g> <xliff:g id="DAY1">%3$s</xliff:g>,<xliff:g id="TIME1">%5$s</xliff:g> – <xliff:g id="MONTH2">%7$s</xliff:g> <xliff:g id="DAY2">%8$s</xliff:g>,<xliff:g id="TIME2">%10$s</xliff:g>"</string>
+    <string name="same_year_wday1_md1_time1_wday2_md2_time2">"<xliff:g id="WEEKDAY1">%1$s</xliff:g>,<xliff:g id="MONTH1">%2$s</xliff:g> <xliff:g id="DAY1_0">%3$s</xliff:g>,<xliff:g id="TIME1">%5$s</xliff:g> – <xliff:g id="WEEKDAY2">%6$s</xliff:g>,<xliff:g id="MONTH2">%7$s</xliff:g> <xliff:g id="DAY2_1">%8$s</xliff:g>,<xliff:g id="TIME2">%10$s</xliff:g>"</string>
+    <string name="same_year_mdy1_time1_mdy2_time2">"<xliff:g id="MONTH1">%2$s</xliff:g> <xliff:g id="DAY1">%3$s</xliff:g>,<xliff:g id="YEAR1">%4$s</xliff:g>,<xliff:g id="TIME1">%5$s</xliff:g> – <xliff:g id="MONTH2">%7$s</xliff:g> <xliff:g id="DAY2">%8$s</xliff:g>,<xliff:g id="YEAR2">%9$s</xliff:g>,<xliff:g id="TIME2">%10$s</xliff:g>"</string>
+    <string name="same_year_wday1_mdy1_time1_wday2_mdy2_time2">"<xliff:g id="WEEKDAY1">%1$s</xliff:g>,<xliff:g id="MONTH1">%2$s</xliff:g> <xliff:g id="DAY1_0">%3$s</xliff:g>,<xliff:g id="YEAR1">%4$s</xliff:g>,<xliff:g id="TIME1">%5$s</xliff:g> – <xliff:g id="WEEKDAY2">%6$s</xliff:g>,<xliff:g id="MONTH2">%7$s</xliff:g> <xliff:g id="DAY2_1">%8$s</xliff:g>,<xliff:g id="YEAR2">%9$s</xliff:g>,<xliff:g id="TIME2">%10$s</xliff:g>"</string>
+    <string name="numeric_md1_md2">"<xliff:g id="MONTH1">%2$s</xliff:g>/<xliff:g id="DAY1">%3$s</xliff:g> – <xliff:g id="MONTH2">%7$s</xliff:g>/<xliff:g id="DAY2">%8$s</xliff:g>"</string>
+    <string name="numeric_wday1_md1_wday2_md2">"<xliff:g id="WEEKDAY1">%1$s</xliff:g>,<xliff:g id="MONTH1">%2$s</xliff:g>/<xliff:g id="DAY1_0">%3$s</xliff:g> – <xliff:g id="WEEKDAY2">%6$s</xliff:g>,<xliff:g id="MONTH2">%7$s</xliff:g>/<xliff:g id="DAY2_1">%8$s</xliff:g>"</string>
+    <string name="numeric_mdy1_mdy2">"<xliff:g id="MONTH1">%2$s</xliff:g>/<xliff:g id="DAY1">%3$s</xliff:g>/<xliff:g id="YEAR1">%4$s</xliff:g> – <xliff:g id="MONTH2">%7$s</xliff:g>/<xliff:g id="DAY2">%8$s</xliff:g>/<xliff:g id="YEAR2">%9$s</xliff:g>"</string>
+    <string name="numeric_wday1_mdy1_wday2_mdy2">"<xliff:g id="WEEKDAY1">%1$s</xliff:g>,<xliff:g id="MONTH1">%2$s</xliff:g>/<xliff:g id="DAY1_0">%3$s</xliff:g>/<xliff:g id="YEAR1">%4$s</xliff:g> – <xliff:g id="WEEKDAY2">%6$s</xliff:g>,<xliff:g id="MONTH2">%7$s</xliff:g>/<xliff:g id="DAY2_1">%8$s</xliff:g>/<xliff:g id="YEAR2">%9$s</xliff:g>"</string>
+    <string name="numeric_md1_time1_md2_time2">"<xliff:g id="MONTH1">%2$s</xliff:g>/<xliff:g id="DAY1">%3$s</xliff:g>,<xliff:g id="TIME1">%5$s</xliff:g> – <xliff:g id="MONTH2">%7$s</xliff:g>/<xliff:g id="DAY2">%8$s</xliff:g>,<xliff:g id="TIME2">%10$s</xliff:g>"</string>
+    <string name="numeric_wday1_md1_time1_wday2_md2_time2">"<xliff:g id="WEEKDAY1">%1$s</xliff:g>,<xliff:g id="MONTH1">%2$s</xliff:g>/<xliff:g id="DAY1_0">%3$s</xliff:g>,<xliff:g id="TIME1">%5$s</xliff:g> – <xliff:g id="WEEKDAY2">%6$s</xliff:g>,<xliff:g id="MONTH2">%7$s</xliff:g>/<xliff:g id="DAY2_1">%8$s</xliff:g>,<xliff:g id="TIME2">%10$s</xliff:g>"</string>
+    <string name="numeric_mdy1_time1_mdy2_time2">"<xliff:g id="MONTH1">%2$s</xliff:g>/<xliff:g id="DAY1">%3$s</xliff:g>/<xliff:g id="YEAR1">%4$s</xliff:g>,<xliff:g id="TIME1">%5$s</xliff:g> – <xliff:g id="MONTH2">%7$s</xliff:g>/<xliff:g id="DAY2">%8$s</xliff:g>/<xliff:g id="YEAR2">%9$s</xliff:g>,<xliff:g id="TIME2">%10$s</xliff:g>"</string>
+    <string name="numeric_wday1_mdy1_time1_wday2_mdy2_time2">"<xliff:g id="WEEKDAY1">%1$s</xliff:g>,<xliff:g id="MONTH1">%2$s</xliff:g>/<xliff:g id="DAY1_0">%3$s</xliff:g>/<xliff:g id="YEAR1">%4$s</xliff:g>,<xliff:g id="TIME1">%5$s</xliff:g> – <xliff:g id="WEEKDAY2">%6$s</xliff:g>,<xliff:g id="MONTH2">%7$s</xliff:g>/<xliff:g id="DAY2_1">%8$s</xliff:g>/<xliff:g id="YEAR2">%9$s</xliff:g>,<xliff:g id="TIME2">%10$s</xliff:g>"</string>
+    <string name="same_month_md1_md2">"<xliff:g id="MONTH1">%2$s</xliff:g> <xliff:g id="DAY1">%3$s</xliff:g> – <xliff:g id="DAY2">%8$s</xliff:g>"</string>
+    <string name="same_month_wday1_md1_wday2_md2">"<xliff:g id="WEEKDAY1">%1$s</xliff:g>,<xliff:g id="MONTH1">%2$s</xliff:g> <xliff:g id="DAY1_0">%3$s</xliff:g> – <xliff:g id="WEEKDAY2">%6$s</xliff:g>,<xliff:g id="MONTH2">%7$s</xliff:g> <xliff:g id="DAY2_1">%8$s</xliff:g>"</string>
+    <string name="same_month_mdy1_mdy2">"<xliff:g id="MONTH1">%2$s</xliff:g> <xliff:g id="DAY1">%3$s</xliff:g> – <xliff:g id="DAY2">%8$s</xliff:g>, <xliff:g id="YEAR2">%9$s</xliff:g>"</string>
+    <string name="same_month_wday1_mdy1_wday2_mdy2">"<xliff:g id="WEEKDAY1">%1$s</xliff:g>,<xliff:g id="MONTH1">%2$s</xliff:g> <xliff:g id="DAY1_0">%3$s</xliff:g>,<xliff:g id="YEAR1">%4$s</xliff:g> – <xliff:g id="WEEKDAY2">%6$s</xliff:g>,<xliff:g id="MONTH2">%7$s</xliff:g> <xliff:g id="DAY2_1">%8$s</xliff:g>,<xliff:g id="YEAR2">%9$s</xliff:g>"</string>
+    <string name="same_month_md1_time1_md2_time2">"<xliff:g id="MONTH1">%2$s</xliff:g> <xliff:g id="DAY1">%3$s</xliff:g>,<xliff:g id="TIME1">%5$s</xliff:g> – <xliff:g id="MONTH2">%7$s</xliff:g> <xliff:g id="DAY2">%8$s</xliff:g>,<xliff:g id="TIME2">%10$s</xliff:g>"</string>
+    <string name="same_month_wday1_md1_time1_wday2_md2_time2">"<xliff:g id="WEEKDAY1">%1$s</xliff:g>,<xliff:g id="MONTH1">%2$s</xliff:g> <xliff:g id="DAY1_0">%3$s</xliff:g>,<xliff:g id="TIME1">%5$s</xliff:g> – <xliff:g id="WEEKDAY2">%6$s</xliff:g>,<xliff:g id="MONTH2">%7$s</xliff:g> <xliff:g id="DAY2_1">%8$s</xliff:g>,<xliff:g id="TIME2">%10$s</xliff:g>"</string>
+    <string name="same_month_mdy1_time1_mdy2_time2">"<xliff:g id="MONTH1">%2$s</xliff:g> <xliff:g id="DAY1">%3$s</xliff:g>,<xliff:g id="YEAR1">%4$s</xliff:g>,<xliff:g id="TIME1">%5$s</xliff:g> – <xliff:g id="MONTH2">%7$s</xliff:g> <xliff:g id="DAY2">%8$s</xliff:g>,<xliff:g id="YEAR2">%9$s</xliff:g>,<xliff:g id="TIME2">%10$s</xliff:g>"</string>
+    <string name="same_month_wday1_mdy1_time1_wday2_mdy2_time2">"<xliff:g id="WEEKDAY1">%1$s</xliff:g>,<xliff:g id="MONTH1">%2$s</xliff:g> <xliff:g id="DAY1_0">%3$s</xliff:g>,<xliff:g id="YEAR1">%4$s</xliff:g>,<xliff:g id="TIME1">%5$s</xliff:g> – <xliff:g id="WEEKDAY2">%6$s</xliff:g>,<xliff:g id="MONTH2">%7$s</xliff:g> <xliff:g id="DAY2_1">%8$s</xliff:g>,<xliff:g id="YEAR2">%9$s</xliff:g>,<xliff:g id="TIME2">%10$s</xliff:g>"</string>
+    <string name="abbrev_month_day_year">"<xliff:g id="MONTH">%b</xliff:g> <xliff:g id="DAY">%-d</xliff:g>,<xliff:g id="YEAR">%Y</xliff:g>"</string>
+    <!-- no translation found for abbrev_month_year (5966980891147982768) -->
+    <skip />
+    <!-- no translation found for abbrev_month_day (3156047263406783231) -->
+    <skip />
+    <!-- no translation found for abbrev_month (7304935052615731208) -->
+    <skip />
+    <string name="day_of_week_long_sunday">"星期日"</string>
+    <string name="day_of_week_long_monday">"星期一"</string>
+    <string name="day_of_week_long_tuesday">"星期二"</string>
+    <string name="day_of_week_long_wednesday">"星期三"</string>
+    <string name="day_of_week_long_thursday">"星期四"</string>
+    <string name="day_of_week_long_friday">"星期五"</string>
+    <string name="day_of_week_long_saturday">"星期六"</string>
+    <string name="day_of_week_medium_sunday">"週日"</string>
+    <string name="day_of_week_medium_monday">"週一"</string>
+    <string name="day_of_week_medium_tuesday">"週二"</string>
+    <string name="day_of_week_medium_wednesday">"週三"</string>
+    <string name="day_of_week_medium_thursday">"週四"</string>
+    <string name="day_of_week_medium_friday">"週五"</string>
+    <string name="day_of_week_medium_saturday">"週六"</string>
+    <string name="day_of_week_short_sunday">"週日"</string>
+    <string name="day_of_week_short_monday">"週一"</string>
+    <string name="day_of_week_short_tuesday">"週二"</string>
+    <string name="day_of_week_short_wednesday">"週三"</string>
+    <string name="day_of_week_short_thursday">"週四"</string>
+    <string name="day_of_week_short_friday">"週五"</string>
+    <string name="day_of_week_short_saturday">"週六"</string>
+    <string name="day_of_week_shorter_sunday">"週日"</string>
+    <string name="day_of_week_shorter_monday">"一"</string>
+    <string name="day_of_week_shorter_tuesday">"週二"</string>
+    <string name="day_of_week_shorter_wednesday">"三"</string>
+    <string name="day_of_week_shorter_thursday">"週四"</string>
+    <string name="day_of_week_shorter_friday">"五"</string>
+    <string name="day_of_week_shorter_saturday">"週六"</string>
+    <string name="day_of_week_shortest_sunday">"日"</string>
+    <string name="day_of_week_shortest_monday">"週一"</string>
+    <string name="day_of_week_shortest_tuesday">"二"</string>
+    <string name="day_of_week_shortest_wednesday">"週三"</string>
+    <string name="day_of_week_shortest_thursday">"四"</string>
+    <string name="day_of_week_shortest_friday">"五"</string>
+    <string name="day_of_week_shortest_saturday">"六"</string>
+    <string name="month_long_january">"1 月"</string>
+    <string name="month_long_february">"2 月"</string>
+    <string name="month_long_march">"3 月"</string>
+    <string name="month_long_april">"4 月"</string>
+    <string name="month_long_may">"5 月"</string>
+    <string name="month_long_june">"6 月"</string>
+    <string name="month_long_july">"7 月"</string>
+    <string name="month_long_august">"8 月"</string>
+    <string name="month_long_september">"9 月"</string>
+    <string name="month_long_october">"10 月"</string>
+    <string name="month_long_november">"11 月"</string>
+    <string name="month_long_december">"12 月"</string>
+    <string name="month_medium_january">"1 月"</string>
+    <string name="month_medium_february">"2 月"</string>
+    <string name="month_medium_march">"3 月"</string>
+    <string name="month_medium_april">"4 月"</string>
+    <string name="month_medium_may">"5 月"</string>
+    <string name="month_medium_june">"6 月"</string>
+    <string name="month_medium_july">"7 月"</string>
+    <string name="month_medium_august">"8 月"</string>
+    <string name="month_medium_september">"9 月"</string>
+    <string name="month_medium_october">"10 月"</string>
+    <string name="month_medium_november">"11 月"</string>
+    <string name="month_medium_december">"12 月"</string>
+    <string name="month_shortest_january">"1"</string>
+    <string name="month_shortest_february">"2"</string>
+    <string name="month_shortest_march">"3"</string>
+    <string name="month_shortest_april">"4"</string>
+    <string name="month_shortest_may">"5"</string>
+    <string name="month_shortest_june">"6"</string>
+    <string name="month_shortest_july">"7"</string>
+    <string name="month_shortest_august">"8"</string>
+    <string name="month_shortest_september">"9"</string>
+    <string name="month_shortest_october">"10"</string>
+    <string name="month_shortest_november">"11"</string>
+    <string name="month_shortest_december">"12"</string>
+    <string name="elapsed_time_short_format_mm_ss">"<xliff:g id="MINUTES">%1$02d</xliff:g>:<xliff:g id="SECONDS">%2$02d</xliff:g>"</string>
+    <string name="elapsed_time_short_format_h_mm_ss">"<xliff:g id="HOURS">%1$d</xliff:g>:<xliff:g id="MINUTES">%2$02d</xliff:g>:<xliff:g id="SECONDS">%3$02d</xliff:g>"</string>
+    <string name="selectAll">"全部選取"</string>
+    <string name="selectText">"選取文字"</string>
+    <string name="stopSelectingText">"停止選取文字"</string>
+    <string name="cut">"剪下"</string>
+    <string name="cutAll">"全部剪下"</string>
+    <string name="copy">"複製"</string>
+    <string name="copyAll">"全部複製"</string>
+    <string name="paste">"貼上"</string>
+    <string name="copyUrl">"複製網址"</string>
+    <string name="inputMethod">"輸入法"</string>
+    <string name="addToDictionary">"將「%s」新增到字典"</string>
+    <string name="editTextMenuTitle">"編輯文字"</string>
+    <string name="low_internal_storage_view_title">"儲存空間太少"</string>
+    <string name="low_internal_storage_view_text">"手機儲存空間即將不足。"</string>
+    <string name="ok">"確定"</string>
+    <string name="cancel">"取消"</string>
+    <string name="yes">"確定"</string>
+    <string name="no">"取消"</string>
+    <string name="dialog_alert_title">"注意"</string>
+    <string name="capital_on">"開啟"</string>
+    <string name="capital_off">"關閉"</string>
+    <string name="whichApplication">"選取...完成動作"</string>
+    <string name="alwaysUse">"以此為本操作預設值。"</string>
+    <string name="clearDefaultHintMsg">"清除首頁設定 (應用程式) 管理應用程式的預設值。"</string>
+    <string name="chooseActivity">"選取一項操作"</string>
+    <string name="noApplications">"沒有應用程式可進行此操作。"</string>
+    <string name="aerr_title">"抱歉!"</string>
+    <string name="aerr_application">"應用程式 <xliff:g id="APPLICATION">%1$s</xliff:g> (程序:<xliff:g id="PROCESS">%2$s</xliff:g>) 未正常終止。請再試一次。"</string>
+    <string name="aerr_process">"<xliff:g id="PROCESS">%1$s</xliff:g> 未正常終止。請再試一次。"</string>
+    <string name="anr_title">"抱歉!"</string>
+    <string name="anr_activity_application">"<xliff:g id="ACTIVITY">%1$s</xliff:g> (應用程式:<xliff:g id="APPLICATION">%2$s</xliff:g>) 無回應。"</string>
+    <string name="anr_activity_process">"<xliff:g id="ACTIVITY">%1$s</xliff:g> (程序:<xliff:g id="PROCESS">%2$s</xliff:g>) 無回應。"</string>
+    <string name="anr_application_process">"應用程式 <xliff:g id="APPLICATION">%1$s</xliff:g> (程序:<xliff:g id="PROCESS">%2$s</xliff:g>) 無回應。"</string>
+    <string name="anr_process">"程序 <xliff:g id="PROCESS">%1$s</xliff:g> 沒有回應。"</string>
+    <string name="force_close">"強制關閉"</string>
+    <string name="wait">"等待"</string>
+    <string name="debug">"除錯"</string>
+    <string name="sendText">"選取文字動作"</string>
+    <string name="volume_ringtone">"鈴聲音量"</string>
+    <string name="volume_music">"媒體音量"</string>
+    <string name="volume_music_hint_playing_through_bluetooth">"透過藍牙播放"</string>
+    <string name="volume_call">"來電音量"</string>
+    <string name="volume_bluetooth_call">"藍牙通話音量"</string>
+    <string name="volume_alarm">"鬧鐘音量"</string>
+    <string name="volume_notification">"通知音量"</string>
+    <string name="volume_unknown">"音量"</string>
+    <string name="ringtone_default">"預設鈴聲"</string>
+    <string name="ringtone_default_with_actual">"預設鈴聲 (<xliff:g id="ACTUAL_RINGTONE">%1$s</xliff:g>)"</string>
+    <string name="ringtone_silent">"靜音"</string>
+    <string name="ringtone_picker_title">"鈴聲"</string>
+    <string name="ringtone_unknown">"未知的鈴聲"</string>
+  <plurals name="wifi_available">
+    <item quantity="one">"已偵測到 Wi-Fi 網路"</item>
+    <item quantity="other">"已偵測到 Wi-Fi 網路"</item>
+  </plurals>
+  <plurals name="wifi_available_detailed">
+    <item quantity="one">"開啟可用 Wi-Fi 網路"</item>
+    <item quantity="other">"開啟可用 Wi-Fi 網路"</item>
+  </plurals>
+    <string name="select_character">"插入字元"</string>
+    <string name="sms_control_default_app_name">"未知的應用程式"</string>
+    <string name="sms_control_title">"傳送 SMS 簡訊"</string>
+    <string name="sms_control_message">"即將傳送大量 SMS 簡訊。選取 [確定] 繼續或 [取消] 停止傳送。"</string>
+    <string name="sms_control_yes">"確定"</string>
+    <string name="sms_control_no">"取消"</string>
+    <string name="date_time_set">"設定"</string>
+    <string name="default_permission_group">"預設值"</string>
+    <string name="no_permissions">"無須許可"</string>
+    <string name="perms_hide"><b>" 隱藏"</b></string>
+    <string name="perms_show_all"><b>"顯示全部"</b></string>
+    <string name="googlewebcontenthelper_loading">"載入中..."</string>
+    <string name="usb_storage_title">"USB 已連接"</string>
+    <string name="usb_storage_message">"已透過 USB 連接手機與電腦。若要從電腦或 SD 卡複製檔案,請選取 [掛載]。"</string>
+    <string name="usb_storage_button_mount">"掛載"</string>
+    <string name="usb_storage_button_unmount">"不要掛載"</string>
+    <string name="usb_storage_error_message">"把 SD 卡當成 USB 儲存裝置時發生問題。"</string>
+    <string name="usb_storage_notification_title">"USB 已連接"</string>
+    <string name="usb_storage_notification_message">"選取此項將檔案複製到電腦,或從電腦複製。"</string>
+    <string name="usb_storage_stop_notification_title">"關閉 USB 儲存裝置"</string>
+    <string name="usb_storage_stop_notification_message">"選取此處關閉 USB 儲存裝置。"</string>
+    <string name="usb_storage_stop_title">"關閉 USB 儲存裝置"</string>
+    <string name="usb_storage_stop_message">"關閉 USB 儲存裝置之前,請確定先從 USB Host 卸載。選取 [關閉] 即可關閉 USB 儲存裝置。"</string>
+    <string name="usb_storage_stop_button_mount">"關閉"</string>
+    <string name="usb_storage_stop_button_unmount">"取消"</string>
+    <string name="usb_storage_stop_error_message">"關閉 USB 儲存裝置時發生問題。請檢查確認您是否已卸載 USB Host,然後再試一次。"</string>
+    <string name="extmedia_format_title">"將 SD 卡格式化"</string>
+    <string name="extmedia_format_message">"確定要將 SD 卡格式化嗎?該 SD 卡中的所有資料將會遺失。"</string>
+    <string name="extmedia_format_button_format">"格式化"</string>
+    <string name="select_input_method">"選取輸入法"</string>
+    <string name="fast_scroll_alphabet">" ABCDEFGHIJKLMNOPQRSTUVWXYZ"</string>
+    <string name="fast_scroll_numeric_alphabet">" 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"</string>
+    <string name="candidates_style"><u>"待選項目"</u></string>
+    <string name="ext_media_checking_notification_title">"正在準備 SD 卡"</string>
+    <string name="ext_media_checking_notification_message">"正在檢查錯誤"</string>
+    <string name="ext_media_nofs_notification_title">"SD 卡為空白"</string>
+    <string name="ext_media_nofs_notification_message">"SD 卡為空白或使用不支援的檔案系統。"</string>
+    <string name="ext_media_unmountable_notification_title">"SD 卡已損壞"</string>
+    <string name="ext_media_unmountable_notification_message">"SD 卡已損壞。您可能需要將 SD 卡重新格式化。"</string>
+    <string name="ext_media_badremoval_notification_title">"SD 卡未預期移除"</string>
+    <string name="ext_media_badremoval_notification_message">"請先卸載 SD 卡,再將其移除,以免資料遺失。"</string>
+    <string name="ext_media_safe_unmount_notification_title">"可安全移除 SD 卡"</string>
+    <string name="ext_media_safe_unmount_notification_message">"現在可以安全移除 SD 卡。"</string>
+    <string name="ext_media_nomedia_notification_title">"已移除 SD 卡"</string>
+    <string name="ext_media_nomedia_notification_message">"已移除 SD 卡。請插入新的 SD 卡來增加裝置的儲存容量。"</string>
+    <string name="activity_list_empty">"找不到符合的活動"</string>
+    <string name="permlab_pkgUsageStats">"更新元件使用統計資料"</string>
+    <string name="permdesc_pkgUsageStats">"允許修改收集到的元件使用統計資料。一般應用程式不會使用此功能。"</string>
+    <!-- no translation found for tutorial_double_tap_to_zoom_message_short (1311810005957319690) -->
+    <skip />
+    <!-- no translation found for gadget_host_error_inflating (2613287218853846830) -->
+    <skip />
+    <!-- no translation found for ime_action_go (8320845651737369027) -->
+    <skip />
+    <!-- no translation found for ime_action_search (658110271822807811) -->
+    <skip />
+    <!-- no translation found for ime_action_send (2316166556349314424) -->
+    <skip />
+    <!-- no translation found for ime_action_next (3138843904009813834) -->
+    <skip />
+    <!-- no translation found for ime_action_default (2840921885558045721) -->
+    <skip />
+</resources>
diff --git a/core/res/res/values/arrays.xml b/core/res/res/values/arrays.xml
new file mode 100644
index 0000000..18a13554
--- /dev/null
+++ b/core/res/res/values/arrays.xml
@@ -0,0 +1,131 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* //device/apps/common/assets/res/any/colors.xml
+**
+** Copyright 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.
+*/
+-->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+
+    <!-- Do not translate. These are all of the drawable resources that should be preloaded by
+         the zygote process before it starts forking application processes. -->
+    <array name="preloaded_drawables">
+        <item>@drawable/sym_def_app_icon</item>
+        <item>@drawable/arrow_down_float</item>
+        <item>@drawable/btn_check</item>
+        <item>@drawable/btn_check_label_background</item>
+        <item>@drawable/btn_check_off</item>
+        <item>@drawable/btn_check_on</item>
+        <item>@drawable/btn_default</item>
+        <item>@drawable/btn_default_small</item>
+        <item>@drawable/btn_dropdown</item>
+        <item>@drawable/btn_plus</item>
+        <item>@drawable/btn_minus</item>
+        <item>@drawable/btn_radio</item>
+        <item>@drawable/btn_star</item>
+        <item>@drawable/btn_toggle</item>
+        <item>@drawable/ic_emergency</item>
+        <item>@drawable/divider_horizontal_bright</item>
+        <item>@drawable/divider_horizontal_dark</item>
+        <item>@drawable/edit_text</item>
+        <item>@drawable/expander_group</item>
+        <item>@drawable/list_selector_background</item>
+        <item>@drawable/menu_background</item>
+        <item>@drawable/menu_background_fill_parent_width</item>
+        <item>@drawable/menu_selector</item>
+        <item>@drawable/panel_background</item>
+        <item>@drawable/popup_bottom_bright</item>
+        <item>@drawable/popup_bottom_dark</item>
+        <item>@drawable/popup_bottom_medium</item>
+        <item>@drawable/popup_center_bright</item>
+        <item>@drawable/popup_center_dark</item>
+        <item>@drawable/popup_full_dark</item>
+        <item>@drawable/popup_top_bright</item>
+        <item>@drawable/popup_top_dark</item>
+        <item>@drawable/progress_horizontal</item>
+        <item>@drawable/progress_indeterminate_horizontal</item>
+        <item>@drawable/progress_small</item>
+        <item>@drawable/progress_small_titlebar</item>
+        <item>@drawable/screen_background_dark</item>
+        <item>@drawable/screen_background_light</item>
+        <item>@drawable/scrollbar_handle_horizontal</item>
+        <item>@drawable/scrollbar_handle_vertical</item>
+        <item>@drawable/spinner_dropdown_background</item>
+        <item>@drawable/title_bar</item>
+        <item>@drawable/title_bar_shadow</item>
+        <item>@drawable/zoom_ring_arrows</item>
+        <item>@drawable/zoom_ring_overview_tab</item>
+        <item>@drawable/zoom_ring_thumb</item>
+        <item>@drawable/zoom_ring_thumb_minus_arrow_rotatable</item>
+        <item>@drawable/zoom_ring_thumb_plus_arrow_rotatable</item>
+        <item>@drawable/zoom_ring_track</item>
+        <item>@drawable/zoom_ring_track_absolute</item>
+        <!-- Visual lock screen -->
+        <item>@drawable/indicator_code_lock_drag_direction_green_up</item>
+        <item>@drawable/indicator_code_lock_drag_direction_red_up</item>
+        <item>@drawable/indicator_code_lock_point_area_default</item>
+        <item>@drawable/indicator_code_lock_point_area_green</item>
+        <item>@drawable/indicator_code_lock_point_area_red</item>
+    </array>
+
+    <!-- Do not translate. These are all of the color state list resources that should be
+         preloaded by the zygote process before it starts forking application processes. -->
+    <array name="preloaded_color_state_lists">
+        <item>@color/hint_foreground_dark</item>
+        <item>@color/hint_foreground_light</item>
+        <item>@color/primary_text_dark</item>
+        <item>@color/primary_text_dark_disable_only</item>
+        <item>@color/primary_text_light</item>
+        <item>@color/primary_text_light_disable_only</item>
+        <item>@color/primary_text_light_nodisable</item>
+        <item>@color/secondary_text_dark</item>
+        <item>@color/secondary_text_light</item>
+        <item>@color/tab_indicator_text</item>
+        <item>@color/tertiary_text_dark</item>
+        <item>@color/tertiary_text_light</item>
+        <item>#ff000000</item>
+        <item>#00000000</item>
+        <item>#ffffffff</item>
+    </array>
+
+    <!-- Do not translate. -->
+    <integer-array name="maps_starting_lat_lng">
+        <item>36149777</item>
+        <item>-95993398</item>
+    </integer-array>
+    <!-- Do not translate. -->
+    <integer-array name="maps_starting_zoom">
+        <item>3</item>
+    </integer-array>
+
+    <!-- Do not translate. Defines the slots for the right-hand side icons.  That is to say, the
+         icons in the status bar that are not notifications. -->
+    <string-array name="status_bar_icon_order">
+        <item><xliff:g id="id">clock</xliff:g></item>
+        <item><xliff:g id="id">alarm_clock</xliff:g></item>
+        <item><xliff:g id="id">battery</xliff:g></item>
+        <item><xliff:g id="id">phone_signal</xliff:g></item>
+        <item><xliff:g id="id">data_connection</xliff:g></item>
+        <item><xliff:g id="id">volume</xliff:g></item>
+        <item><xliff:g id="id">mute</xliff:g></item>
+        <item><xliff:g id="id">speakerphone</xliff:g></item>
+        <item><xliff:g id="id">wifi</xliff:g></item>
+        <item><xliff:g id="id">bluetooth</xliff:g></item>
+        <item><xliff:g id="id">gps</xliff:g></item>
+        <item><xliff:g id="id">sync_active</xliff:g></item>
+        <item><xliff:g id="id">sync_failing</xliff:g></item>
+        <item><xliff:g id="id">ime</xliff:g></item>
+    </string-array>
+</resources>
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
new file mode 100644
index 0000000..44da1d5
--- /dev/null
+++ b/core/res/res/values/attrs.xml
@@ -0,0 +1,3121 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<resources>
+    <!-- These are the standard attributes that make up a complete theme. -->
+    <declare-styleable name="Theme">
+        <!-- ============== -->
+        <!-- Generic styles -->
+        <!-- ============== -->
+        <eat-comment />
+
+        <!-- Default color of foreground imagery. -->
+        <attr name="colorForeground" format="color" />
+        <!-- Default color of foreground imagery on an inverted background. -->
+        <attr name="colorForegroundInverse" format="color" />
+        <!-- Color that matches (as closely as possible) the window background. -->
+        <attr name="colorBackground" format="color" />
+        <!-- Default disabled alpha for widgets that set enabled/disabled alpha programmatically. -->
+        <attr name="disabledAlpha" format="float" />
+        <!-- Default background dim amount when a menu, dialog, or something similar pops up. -->
+        <attr name="backgroundDimAmount" format="float" />
+        <!-- Control whether dimming behind the window is enabled.  The default
+             theme does not set this value, meaning it is based on whether the
+             window is floating. -->
+        <attr name="backgroundDimEnabled" format="boolean" />
+
+        <!-- =========== -->
+        <!-- Text styles -->
+        <!-- =========== -->
+        <eat-comment />
+
+        <!-- Default appearance of text: color, typeface, size, and style -->
+        <attr name="textAppearance" format="reference" />
+        <!-- Default appearance of text against an inverted background:
+             color, typeface, size, and style -->
+        <attr name="textAppearanceInverse" format="reference" />
+
+        <!-- The most prominent text color, for the  -->
+        <attr name="textColorPrimary" format="reference|color" />
+        <!-- Secondary text color -->
+        <attr name="textColorSecondary" format="reference|color" />
+        <!-- Tertiary text color -->
+        <attr name="textColorTertiary" format="reference|color" />
+
+        <!-- Primary inverse text color, useful for inverted backgrounds -->
+        <attr name="textColorPrimaryInverse" format="reference|color" />
+        <!-- Secondary inverse text color, useful for inverted backgrounds -->
+        <attr name="textColorSecondaryInverse" format="reference|color" />
+        <!-- Tertiary inverse text color, useful for inverted backgrounds -->
+        <attr name="textColorTertiaryInverse" format="reference|color" />
+
+        <!-- Inverse hint text color -->
+        <attr name="textColorHintInverse" format="reference|color" />
+        
+        <!-- Bright text color. Only differentiates based on the disabled state. -->
+        <attr name="textColorPrimaryDisableOnly" format="reference|color" />
+
+        <!-- Bright text color. This does not differentiate the disabled state. As an example,
+             buttons use this since they display the disabled state via the background and not the
+             foreground text color. -->
+        <attr name="textColorPrimaryNoDisable" format="reference|color" />
+        <!-- Dim text color. This does not differentiate the disabled state. -->
+        <attr name="textColorSecondaryNoDisable" format="reference|color" />
+
+        <!-- Bright inverse text color. This does not differentiate the disabled state. -->
+        <attr name="textColorPrimaryInverseNoDisable" format="reference|color" />
+        <!-- Dim inverse text color. This does not differentiate the disabled state. -->
+        <attr name="textColorSecondaryInverseNoDisable" format="reference|color" />
+
+        <!-- Text color, typeface, size, and style for "large" text. Defaults to primary text color. -->
+        <attr name="textAppearanceLarge" format="reference" />
+        <!-- Text color, typeface, size, and style for "medium" text. Defaults to primary text color. -->
+        <attr name="textAppearanceMedium" format="reference" />
+        <!-- Text color, typeface, size, and style for "small" text. Defaults to secondary text color. -->
+        <attr name="textAppearanceSmall" format="reference" />
+
+        <!-- Text color, typeface, size, and style for "large" inverse text. Defaults to primary inverse text color. -->
+        <attr name="textAppearanceLargeInverse" format="reference" />
+        <!-- Text color, typeface, size, and style for "medium" inverse text. Defaults to primary inverse text color. -->
+        <attr name="textAppearanceMediumInverse" format="reference" />
+        <!-- Text color, typeface, size, and style for "small" inverse text. Defaults to secondary inverse text color. -->
+        <attr name="textAppearanceSmallInverse" format="reference" />
+
+        <!-- Text color, typeface, size, and style for the text inside of a button. -->
+        <attr name="textAppearanceButton" format="reference" />
+        
+        <!-- A styled string, specifying the style to be used for showing
+             inline candidate text when composing with an input method.  The
+             text itself will be ignored, but the style spans will be applied
+             to the candidate text as it is edited. -->
+        <attr name="candidatesTextStyleSpans" format="reference|string" />
+        
+        <!-- Drawable to use for check marks -->
+        <attr name="textCheckMark" format="reference" />
+        <attr name="textCheckMarkInverse" format="reference" />
+
+        <!-- Drawable to use for multiple choice indicators-->
+        <attr name="listChoiceIndicatorMultiple" format="reference" />
+        
+        <!-- Drawable to use for single choice indicators-->
+        <attr name="listChoiceIndicatorSingle" format="reference" />
+
+        <!-- ============= -->
+        <!-- Button styles -->
+        <!-- ============= -->
+        <eat-comment />
+
+        <!-- Normal Button style. -->
+        <attr name="buttonStyle" format="reference" />
+
+        <!-- Small Button style. -->
+        <attr name="buttonStyleSmall" format="reference" />
+
+        <!-- Button style to inset into an EditText. -->
+        <attr name="buttonStyleInset" format="reference" />
+
+        <!-- ToggleButton style. -->
+        <attr name="buttonStyleToggle" format="reference" />
+
+        <!-- ============== -->
+        <!-- Gallery styles -->
+        <!-- ============== -->
+        <eat-comment />
+        
+        <!-- The preferred background for gallery items. This should be set
+             as the background of any Views you provide from the Adapter. -->
+        <attr name="galleryItemBackground" format="reference" />
+
+        <!-- =========== -->
+        <!-- List styles -->
+        <!-- =========== -->
+        <eat-comment />
+
+        <!-- The preferred list item height -->
+        <attr name="listPreferredItemHeight" format="dimension" />
+        <!-- The drawable for the list divider -->
+        <attr name="listDivider" format="reference" />
+        <!-- TextView style for list separators. -->
+        <attr name="listSeparatorTextViewStyle" format="reference" />
+        <!-- The preferred left padding for an expandable list item (for child-specific layouts,
+             use expandableListPreferredChildPaddingLeft). This takes into account
+             the indicator that will be shown to next to the item. --> 
+        <attr name="expandableListPreferredItemPaddingLeft" format="dimension" />
+        <!-- The preferred left padding for an expandable list item that is a child.
+             If this is not provided, it defaults to the expandableListPreferredItemPaddingLeft. --> 
+        <attr name="expandableListPreferredChildPaddingLeft" format="dimension" />
+        <!-- The preferred left bound for an expandable list item's indicator. For a child-specific
+             indicator, use expandableListPreferredChildIndicatorLeft. --> 
+        <attr name="expandableListPreferredItemIndicatorLeft" format="dimension" />
+        <!-- The preferred right bound for an expandable list item's indicator. For a child-specific
+             indicator, use expandableListPreferredChildIndicatorRight. --> 
+        <attr name="expandableListPreferredItemIndicatorRight" format="dimension" />
+        <!-- The preferred left bound for an expandable list child's indicator. --> 
+        <attr name="expandableListPreferredChildIndicatorLeft" format="dimension" />
+        <!-- The preferred right bound for an expandable list child's indicator. --> 
+        <attr name="expandableListPreferredChildIndicatorRight" format="dimension" />
+
+        <!-- ============= -->
+        <!-- Window styles -->
+        <!-- ============= -->
+        <eat-comment />
+
+        <!-- Drawable to use as the overall window background.  There are a
+             few special considerations you should use when settings this
+             drawable:
+             <ul>
+             <li> This information will be used to infer the pixel format
+                  for your window's surface.  If the drawable has any
+                  non-opaque pixels, your window will be translucent
+                  (32 bpp).
+             <li> If you want to draw the entire background
+                  yourself, you should set this drawable to some solid
+                  color that closely matches that background (so the
+                  system's preview of your window will match), and
+                  then in code manually set your window's background to
+                  null so it will not be drawn.
+             </ul> -->
+        <attr name="windowBackground" format="reference" />
+        <!-- Drawable to use as a frame around the window. -->
+        <attr name="windowFrame" format="reference" />
+        <!-- Flag indicating whether there should be no title on this window. -->
+        <attr name="windowNoTitle" format="boolean" />
+        <!-- Flag indicating whether this window should fill the entire screen. -->
+        <attr name="windowFullscreen" format="boolean" />
+        <!-- Flag indicating whether this is a floating window. -->
+        <attr name="windowIsFloating" format="boolean" />
+        <!-- Flag indicating whether this is a translucent window. -->
+        <attr name="windowIsTranslucent" format="boolean" />
+        <!-- This Drawable is overlaid over the foreground of the Window's content area, usually
+             to place a shadow below the title.  -->
+        <attr name="windowContentOverlay" format="reference" />
+        <!-- The style resource to use for a window's title bar height. -->
+        <attr name="windowTitleSize" format="dimension" />
+        <!-- The style resource to use for a window's title text. -->
+        <attr name="windowTitleStyle" format="reference" />
+        <!-- The style resource to use for a window's title area. -->
+        <attr name="windowTitleBackgroundStyle" format="reference" />
+
+        <!-- Reference to a style resource holding
+             the set of window animations to use, which can be
+             any of the attributes defined by
+             {@link android.R.styleable#WindowAnimation}. -->
+        <attr name="windowAnimationStyle" format="reference" />
+    
+        <!-- Defines the default soft input state that this window would
+             like when it is displayed. -->
+        <attr name="windowSoftInputMode">
+            <!-- Not specified, use what the system thinks is best.  This
+                 is the default. -->
+            <flag name="stateUnspecified" value="0" />
+            <!-- Leave the soft input window as-is, in whatever state it
+                 last was. -->
+            <flag name="stateUnchanged" value="1" />
+            <!-- Make the soft input area hidden when normally appropriate
+                 (when the user is navigating forward to your window). -->
+            <flag name="stateHidden" value="2" />
+            <!-- Always make the soft input area hidden when this window
+                 has input focus. -->
+            <flag name="stateAlwaysHidden" value="3" />
+            <!-- Make the soft input area visible when normally appropriate
+                 (when the user is navigating forward to your window). -->
+            <flag name="stateVisible" value="4" />
+            <!-- Always make the soft input area visible when this window
+                 has input focus. -->
+            <flag name="stateAlwaysVisible" value="5" />
+            
+            <!-- The window resize/pan adjustment has not been specified,
+                 the system will automatically select between resize and pan
+                 modes, depending
+                 on whether the content of the window has any layout views
+                 that can scroll their contents.  If there is such a view,
+                 then the window will be resized, with the assumption being
+                 that the resizeable area can be reduced to make room for
+                 the input UI. -->
+            <flag name="adjustUnspecified" value="0x00" />
+            <!-- Always resize the window: the content area of the window is
+                 reduced to make room for the soft input area. -->
+            <flag name="adjustResize" value="0x10" />
+            <!-- Don't resize the window to make room for the soft input area;
+                 instead pan the contents of the window as focus moves inside
+                 of it so that the user can see what they are typing.  This is
+                 generally less desireable than panning because the user may
+                 need to close the input area to get at and interact with
+                 parts of the window. -->
+            <flag name="adjustPan" value="0x20" />
+        </attr>
+
+        <!-- Flag allowing you to disable the preview animation for a window.
+             The default value is false; if set to true, the system can never
+             use the window's theme to show a preview of it before your
+             actual instance is shown to the user. -->
+        <attr name="windowDisablePreview" format="boolean" />
+        
+        <!-- Flag indicating that this window should not be displayed at all.
+             The default value is false; if set to true, and this window is
+             the main window of an Activity, then it will never actually
+             be added to the window manager.  This means that your activity
+             must immediately quit without waiting for user interaction,
+             because there will be no such interaction coming. -->
+        <attr name="windowNoDisplay" format="boolean" />
+        
+        <!-- ============ -->
+        <!-- Alert Dialog styles -->
+        <!-- ============ -->
+        <eat-comment />
+        <attr name="alertDialogStyle" format="reference" />
+        
+        <!-- ============ -->
+        <!-- Panel styles -->
+        <!-- ============ -->
+        <eat-comment />
+
+        <!-- The background of a panel when it is inset from the left and right edges of the screen. -->
+        <attr name="panelBackground" format="reference|color" />
+        <!-- The background of a panel when it extends to the left and right edges of the screen. -->
+        <attr name="panelFullBackground" format="reference|color" />
+        <!-- Default color of foreground panel imagery. -->
+        <attr name="panelColorForeground" format="reference|color" />
+        <!-- Color that matches (as closely as possible) the panel background. -->
+        <attr name="panelColorBackground" format="reference|color" />
+        <!-- Default appearance of panel text. -->
+        <attr name="panelTextAppearance" format="reference" />
+
+        <!-- =================== -->
+        <!-- Other widget styles -->
+        <!-- =================== -->
+        <eat-comment />
+
+        <!-- Default AbsListView style. -->
+        <attr name="absListViewStyle" format="reference" />
+        <!-- Default AutoCompleteTextView style. -->
+        <attr name="autoCompleteTextViewStyle" format="reference" />
+        <!-- Default Checkbox style. -->
+        <attr name="checkboxStyle" format="reference" />
+        <!-- Default ListView style for drop downs. -->
+        <attr name="dropDownListViewStyle" format="reference" />
+        <!-- Default EditText style. -->
+        <attr name="editTextStyle" format="reference" />
+        <!-- Default ExpandableListView style. -->
+        <attr name="expandableListViewStyle" format="reference" />
+        <!-- Default Gallery style. -->
+        <attr name="galleryStyle" format="reference" />
+        <!-- Default GridView style. -->
+        <attr name="gridViewStyle" format="reference" />
+        <!-- The style resource to use for an ImageButton -->
+        <attr name="imageButtonStyle" format="reference" />
+        <!-- The style resource to use for an ImageButton that is an image well -->
+        <attr name="imageWellStyle" format="reference" />
+        <!-- Default ListView style. -->
+        <attr name="listViewStyle" format="reference" />
+        <!-- ListView with white background. -->
+        <attr name="listViewWhiteStyle" format="reference" />
+        <!-- Default PopupWindow style. -->
+        <attr name="popupWindowStyle" format="reference" />
+        <!-- Default ProgressBar style. This is a medium circular progress bar. -->
+        <attr name="progressBarStyle" format="reference" />
+        <!-- Horizontal ProgressBar style. This is a horizontal progress bar. -->
+        <attr name="progressBarStyleHorizontal" format="reference" />
+        <!-- Small ProgressBar style. This is a small circular progress bar. -->
+        <attr name="progressBarStyleSmall" format="reference" />
+        <!-- Small ProgressBar in title style. This is a small circular progress bar that will be placed in title bars. -->
+        <attr name="progressBarStyleSmallTitle" format="reference" />
+        <!-- Large ProgressBar style. This is a large circular progress bar. -->
+        <attr name="progressBarStyleLarge" format="reference" />
+        <!-- Default SeekBar style. -->
+        <attr name="seekBarStyle" format="reference" />
+        <!-- Default RatingBar style. -->
+        <attr name="ratingBarStyle" format="reference" />
+        <!-- Indicator RatingBar style. -->
+        <attr name="ratingBarStyleIndicator" format="reference" />
+        <!-- Small indicator RatingBar style. -->
+        <attr name="ratingBarStyleSmall" format="reference" />
+        <!-- Default RadioButton style. -->
+        <attr name="radioButtonStyle" format="reference" />
+        <!-- Default ScrollView style. -->
+        <attr name="scrollViewStyle" format="reference" />
+        <!-- Default HorizontalScrollView style. -->
+        <attr name="horizontalScrollViewStyle" format="reference" />
+        <!-- Default Spinner style. -->
+        <attr name="spinnerStyle" format="reference" />
+        <!-- Default Star style. -->
+        <attr name="starStyle" format="reference" />
+        <!-- Buttonless Star style. -->
+        <attr name="starStyleButtonless" format="reference" />
+        <!-- Default TabWidget style. -->
+        <attr name="tabWidgetStyle" format="reference" />
+        <!-- Default TextView style. -->
+        <attr name="textViewStyle" format="reference" />
+        <!-- Default WebView style. -->
+        <attr name="webViewStyle" format="reference" />
+        <!-- Default style for drop down items. -->
+        <attr name="dropDownItemStyle" format="reference" />
+         <!-- Default style for spinner drop down items. -->
+        <attr name="spinnerDropDownItemStyle" format="reference" />
+        <!-- Default style for drop down hints. -->
+        <attr name="dropDownHintAppearance" format="reference" />
+        <!-- Default spinner item style. -->
+        <attr name="spinnerItemStyle" format="reference" />
+        <!-- Default MapView style. -->
+        <attr name="mapViewStyle" format="reference" />
+
+        <!-- =================== -->
+        <!-- Preference styles   -->
+        <!-- =================== -->
+        <eat-comment />
+        
+        <!-- Default style for PreferenceScreen. -->
+        <attr name="preferenceScreenStyle" format="reference" />
+        <!-- Default style for PreferenceCategory. -->
+        <attr name="preferenceCategoryStyle" format="reference" />
+        <!-- Default style for Preference. -->
+        <attr name="preferenceStyle" format="reference" />
+        <!-- Default style for informational Preference. -->
+        <attr name="preferenceInformationStyle" format="reference" />
+        <!-- Default style for CheckBoxPreference. -->
+        <attr name="checkBoxPreferenceStyle" format="reference" />
+        <!-- Default style for YesNoPreference. -->
+        <attr name="yesNoPreferenceStyle" format="reference" />
+        <!-- Default style for DialogPreference. -->
+        <attr name="dialogPreferenceStyle" format="reference" />
+        <!-- Default style for EditTextPreference. -->
+        <attr name="editTextPreferenceStyle" format="reference" />
+        <!-- Default style for RingtonePreference. -->
+        <attr name="ringtonePreferenceStyle" format="reference" />
+        <!-- The preference layout that has the child/tabbed effect. -->
+        <attr name="preferenceLayoutChild" format="reference" />
+        
+    </declare-styleable>
+
+    <!-- **************************************************************** -->
+    <!-- Other non-theme attributes. -->
+    <!-- **************************************************************** -->
+    <eat-comment />
+
+    <!-- Size of text. Recommended dimension type for text is "sp" for scaled-pixels (example: 15sp).
+         Supported values include the following:<p/>
+    <ul>
+        <li><b>px</b> Pixels</li>
+        <li><b>sp</b> Scaled pixels (scaled to relative pixel size on screen). See {@link android.util.DisplayMetrics} for more information.</li>
+        <li><b>pt</b> Points</li>
+        <li><b>dip</b> Device independent pixels. See {@link android.util.DisplayMetrics} for more information.</li>
+    </ul>
+        -->
+    <attr name="textSize" format="dimension" />
+    
+    <!-- Default text typeface. -->
+    <attr name="typeface">
+        <enum name="normal" value="0" />
+        <enum name="sans" value="1" />
+        <enum name="serif" value="2" />
+        <enum name="monospace" value="3" />
+    </attr>
+    
+    <!-- Default text typeface style. -->
+    <attr name="textStyle">
+        <flag name="normal" value="0" />
+        <flag name="bold" value="1" />
+        <flag name="italic" value="2" />
+    </attr>
+    
+    <!-- Color of text (usually same as colorForeground). -->
+    <attr name="textColor" format="reference|color" />
+    
+    <!-- Color of highlighted text. -->
+    <attr name="textColorHighlight" format="reference|color" />
+    
+    <!-- Color of hint text (displayed when the field is empty). -->
+    <attr name="textColorHint" format="reference|color" />
+    
+    <!-- Color of link text (URLs). -->
+    <attr name="textColorLink" format="reference|color" />
+    
+    <!-- Where to ellipsize text. -->
+    <attr name="ellipsize">
+        <enum name="none" value="0" />
+        <enum name="start" value="1" />
+        <enum name="middle" value="2" />
+        <enum name="end" value="3" />
+        <enum name="marquee" value="4" />
+    </attr>
+    
+    <!-- The type of data being placed in a text field, used to help an
+         input method decide how to let the user enter text.  The constants
+         here correspond to those defined by
+         {@link android.text.InputType}.  Generally you can select
+         a single value, though some can be combined together as
+         indicated.  Setting this attribute to anything besides
+         <var>none</var> also implies that the text is editable. -->
+    <attr name="inputType">
+        <!-- There is no content type.  The text is not editable. -->
+        <flag name="none" value="0x00000000" />
+        <!-- Just plain old text.  Corresponds to
+             {@link android.text.InputType#TYPE_CLASS_TEXT} |
+             {@link android.text.InputType#TYPE_TEXT_VARIATION_NORMAL}. -->
+        <flag name="text" value="0x00000001" />
+        <!-- Can be combined with <var>text</var> and its variations to
+             request capitalization of all characters.  Corresponds to
+             {@link android.text.InputType#TYPE_TEXT_FLAG_CAP_CHARACTERS}. -->
+        <flag name="textCapCharacters" value="0x00001001" />
+        <!-- Can be combined with <var>text</var> and its variations to
+             request capitalization of the first character of every word.  Corresponds to
+             {@link android.text.InputType#TYPE_TEXT_FLAG_CAP_WORDS}. -->
+        <flag name="textCapWords" value="0x00002001" />
+        <!-- Can be combined with <var>text</var> and its variations to
+             request capitalization of the first character of every sentence.  Corresponds to
+             {@link android.text.InputType#TYPE_TEXT_FLAG_CAP_SENTENCES}. -->
+        <flag name="textCapSentences" value="0x00004001" />
+        <!-- Can be combined with <var>text</var> and its variations to
+             request auto-correction of text being input.  Corresponds to
+             {@link android.text.InputType#TYPE_TEXT_FLAG_AUTO_CORRECT}. -->
+        <flag name="textAutoCorrect" value="0x00008001" />
+        <!-- Can be combined with <var>text</var> and its variations to
+             specify that this field will be doing its own auto-completion and
+             talking with the input method appropriately.  Corresponds to
+             {@link android.text.InputType#TYPE_TEXT_FLAG_AUTO_COMPLETE}. -->
+        <flag name="textAutoComplete" value="0x00010001" />
+        <!-- Can be combined with <var>text</var> and its variations to
+             allow multiple lines of text in the field.  If this flag is not set,
+             the text field will be constrained to a single line.  Corresponds to
+             {@link android.text.InputType#TYPE_TEXT_FLAG_MULTI_LINE}. -->
+        <flag name="textMultiLine" value="0x00020001" />
+        <!-- Can be combined with <var>text</var> and its variations to
+             indicate that though the regular text view should not be multiple
+             lines, the IME should provide multiple lines if it can.  Corresponds to
+             {@link android.text.InputType#TYPE_TEXT_FLAG_IME_MULTI_LINE}. -->
+        <flag name="textImeMultiLine" value="0x00040001" />
+        <!-- Text that will be used as a URI.  Corresponds to
+             {@link android.text.InputType#TYPE_CLASS_TEXT} |
+             {@link android.text.InputType#TYPE_TEXT_VARIATION_URI}. -->
+        <flag name="textUri" value="0x00000011" />
+        <!-- Text that will be used as an e-mail address.  Corresponds to
+             {@link android.text.InputType#TYPE_CLASS_TEXT} |
+             {@link android.text.InputType#TYPE_TEXT_VARIATION_EMAIL_ADDRESS}. -->
+        <flag name="textEmailAddress" value="0x00000021" />
+        <!-- Text that is being supplied as the subject of an e-mail.  Corresponds to
+             {@link android.text.InputType#TYPE_CLASS_TEXT} |
+             {@link android.text.InputType#TYPE_TEXT_VARIATION_EMAIL_SUBJECT}. -->
+        <flag name="textEmailSubject" value="0x00000031" />
+        <!-- Text that is the content of a short message.  Corresponds to
+             {@link android.text.InputType#TYPE_CLASS_TEXT} |
+             {@link android.text.InputType#TYPE_TEXT_VARIATION_SHORT_MESSAGE}. -->
+        <flag name="textShortMessage" value="0x00000041" />
+        <!-- Text that is the content of a long message.  Corresponds to
+             {@link android.text.InputType#TYPE_CLASS_TEXT} |
+             {@link android.text.InputType#TYPE_TEXT_VARIATION_LONG_MESSAGE}. -->
+        <flag name="textLongMessage" value="0x00000051" />
+        <!-- Text that is the name of a person.  Corresponds to
+             {@link android.text.InputType#TYPE_CLASS_TEXT} |
+             {@link android.text.InputType#TYPE_TEXT_VARIATION_PERSON_NAME}. -->
+        <flag name="textPersonName" value="0x00000061" />
+        <!-- Text that is being supplied as a postal mailing address.  Corresponds to
+             {@link android.text.InputType#TYPE_CLASS_TEXT} |
+             {@link android.text.InputType#TYPE_TEXT_VARIATION_POSTAL_ADDRESS}. -->
+        <flag name="textPostalAddress" value="0x00000071" />
+        <!-- Text that is a password.  Corresponds to
+             {@link android.text.InputType#TYPE_CLASS_TEXT} |
+             {@link android.text.InputType#TYPE_TEXT_VARIATION_PASSWORD}. -->
+        <flag name="textPassword" value="0x00000081" />
+        <!-- Text that is being supplied as text in a web form.  Corresponds to
+             {@link android.text.InputType#TYPE_CLASS_TEXT} |
+             {@link android.text.InputType#TYPE_TEXT_VARIATION_WEB_EDIT_TEXT}. -->
+        <flag name="textWebEditText" value="0x00000091" />
+        <!-- A numeric only field.  Corresponds to
+             {@link android.text.InputType#TYPE_CLASS_NUMBER}. -->
+        <flag name="number" value="0x00000002" />
+        <!-- Can be combined with <var>number</var> and its other options to
+             allow a signed number.  Corresponds to
+             {@link android.text.InputType#TYPE_CLASS_NUMBER} |
+             {@link android.text.InputType#TYPE_NUMBER_FLAG_SIGNED}. -->
+        <flag name="numberSigned" value="0x00001002" />
+        <!-- Can be combined with <var>number</var> and its other options to
+             allow a decimal (fractional) number.  Corresponds to
+             {@link android.text.InputType#TYPE_CLASS_NUMBER} |
+             {@link android.text.InputType#TYPE_NUMBER_FLAG_DECIMAL}. -->
+        <flag name="numberDecimal" value="0x00002002" />
+        <!-- For entering a phone number.  Corresponds to
+             {@link android.text.InputType#TYPE_CLASS_PHONE}. -->
+        <flag name="phone" value="0x00000003" />
+        <!-- For entering a date and time.  Corresponds to
+             {@link android.text.InputType#TYPE_CLASS_DATETIME} |
+             {@link android.text.InputType#TYPE_DATETIME_VARIATION_NORMAL}. -->
+        <flag name="datetime" value="0x00000004" />
+        <!-- For entering a date.  Corresponds to
+             {@link android.text.InputType#TYPE_CLASS_DATETIME} |
+             {@link android.text.InputType#TYPE_DATETIME_VARIATION_DATE}. -->
+        <flag name="date" value="0x00000014" />
+        <!-- For entering a time.  Corresponds to
+             {@link android.text.InputType#TYPE_CLASS_DATETIME} |
+             {@link android.text.InputType#TYPE_DATETIME_VARIATION_TIME}. -->
+        <flag name="time" value="0x00000024" />
+    </attr>
+
+    <!-- Additional features you can enable in an IME associated with an editor,
+         to improve the integration with your application.  The constants
+         here correspond to those defined by
+         {@link android.view.inputmethod.EditorInfo#imeOptions}. -->
+    <attr name="imeOptions">
+        <!-- There are no special semantics associated with this editor. -->
+        <flag name="normal" value="0x00000000" />
+        <!-- There is no special action associated with this editor.
+             Corresponds to
+             {@link android.view.inputmethod.EditorInfo#IME_ACTION_NONE}. -->
+        <flag name="actionNone" value="0x00000000" />
+        <!-- The action key performs a "go"
+             operation to take the user to the target of the text they typed.
+             Typically used, for example, when entering a URL.
+             {@link android.view.inputmethod.EditorInfo#IME_ACTION_GO}. -->
+        <flag name="actionGo" value="0x00000001" />
+        <!-- The action key performs a "search"
+             operation, taking the user to the results of searching for the text
+             the have typed (in whatever context is appropriate).
+             {@link android.view.inputmethod.EditorInfo#IME_ACTION_SEARCH}. -->
+        <flag name="actionSearch" value="0x00000002" />
+        <!-- The action key performs a "send"
+             operation, delivering the text to its target.  This is typically used
+             when composing a message.
+             {@link android.view.inputmethod.EditorInfo#IME_ACTION_SEND}. -->
+        <flag name="actionSend" value="0x00000003" />
+        <!-- The action key performs a "next"
+             operation, taking the user to the next field that will accept text.
+             {@link android.view.inputmethod.EditorInfo#IME_ACTION_NEXT}. -->
+        <flag name="actionNext" value="0x00000004" />
+        <!-- Used in conjunction with a custom action,
+             this indicates that the action should not
+             be available in-line as the same as a "enter" key.  Typically this is
+             because the action has such a significant impact or is not recoverable
+             enough that accidentally hitting it should be avoided, such as sending
+             a message.
+             {@link android.view.inputmethod.EditorInfo#IME_FLAG_NO_ENTER_ACTION}. -->
+        <flag name="flagNoEnterAction" value="0x40000000" />
+    </attr>
+
+    <!-- A coordinate in the X dimension. -->
+    <attr name="x" format="dimension" />
+    <!-- A coordinate in the Y dimension. -->
+    <attr name="y" format="dimension" />
+          
+    <!-- Specifies how to place an object, both
+         its x and y axis, within a larger containing object. -->
+    <attr name="gravity">
+        <!-- Push object to the top of its container, not changing its size. -->
+        <flag name="top" value="0x30" />
+        <!-- Push object to the bottom of its container, not changing its size. -->
+        <flag name="bottom" value="0x50" />
+        <!-- Push object to the left of its container, not changing its size. -->
+        <flag name="left" value="0x03" />
+        <!-- Push object to the right of its container, not changing its size. -->
+        <flag name="right" value="0x05" />
+        <!-- Place object in the vertical center of its container, not changing its size. -->
+        <flag name="center_vertical" value="0x10" />
+        <!-- Grow the vertical size of the object if needed so it completely fills its container. -->
+        <flag name="fill_vertical" value="0x70" />
+        <!-- Place object in the horizontal center of its container, not changing its size. -->
+        <flag name="center_horizontal" value="0x01" />
+        <!-- Grow the horizontal size of the object if needed so it completely fills its container. -->
+        <flag name="fill_horizontal" value="0x07" />
+        <!-- Place the object in the center of its container in both the vertical and horizontal axis, not changing its size. -->
+        <flag name="center" value="0x11" />
+        <!-- Grow the horizontal and vertical size of the object if needed so it completely fills its container. -->
+        <flag name="fill" value="0x77" />
+        <!-- Additional option that can be set to have the top and/or bottom edges of
+             the child clipped to its container's bounds.
+             The clip will be based on the vertical gravity: a top gravity will clip the bottom
+             edge, a bottom gravity will clip the top edge, and neither will clip both edges. -->
+        <flag name="clip_vertical" value="0x80" />
+        <!-- Additional option that can be set to have the left and/or right edges of
+             the child clipped to its container's bounds.
+             The clip will be based on the horizontal gravity: a left gravity will clip the right
+             edge, a right gravity will clip the left edge, and neither will clip both edges. -->
+        <flag name="clip_horizontal" value="0x08" />
+    </attr>
+
+    <!-- Controls whether links such as urls and email addresses are
+         automatically found and converted to clickable links.  The default
+         value is "none", disabling this feature. -->
+    <attr name="autoLink">
+        <!-- Match no patterns (default) -->
+        <flag name="none" value="0x00" />
+        <!-- Match Web URLs -->
+        <flag name="web" value="0x01" />
+        <!-- Match email addresses -->
+        <flag name="email" value="0x02" />
+        <!-- Match phone numbers -->
+        <flag name="phone" value="0x04" />
+        <!-- Match map addresses -->
+        <flag name="map" value="0x08" />
+        <!-- Match all patterns (equivalent to web|email|phone|map) -->
+        <flag name="all" value="0x0f" />
+    </attr>
+
+    <!-- Reference to an array resource that will populate a list/adapter -->
+    <attr name="entries" format="reference" />
+
+    <!-- Standard gravity constant that a child can supply to its parent.
+         Defines how to place an object, both
+         its x and y axis, within a larger containing object. -->
+    <attr name="layout_gravity">
+        <!-- Push object to the top of its container, not changing its size. -->
+        <flag name="top" value="0x30" />
+        <!-- Push object to the bottom of its container, not changing its size. -->
+        <flag name="bottom" value="0x50" />
+        <!-- Push object to the left of its container, not changing its size. -->
+        <flag name="left" value="0x03" />
+        <!-- Push object to the right of its container, not changing its size. -->
+        <flag name="right" value="0x05" />
+        <!-- Place object in the vertical center of its container, not changing its size. -->
+        <flag name="center_vertical" value="0x10" />
+        <!-- Grow the vertical size of the object if needed so it completely fills its container. -->
+        <flag name="fill_vertical" value="0x70" />
+        <!-- Place object in the horizontal center of its container, not changing its size. -->
+        <flag name="center_horizontal" value="0x01" />
+        <!-- Grow the horizontal size of the object if needed so it completely fills its container. -->
+        <flag name="fill_horizontal" value="0x07" />
+        <!-- Place the object in the center of its container in both the vertical and horizontal axis, not changing its size. -->
+        <flag name="center" value="0x11" />
+        <!-- Grow the horizontal and vertical size of the object if needed so it completely fills its container. -->
+        <flag name="fill" value="0x77" />
+        <!-- Additional option that can be set to have the top and/or bottom edges of
+             the child clipped to its container's bounds.
+             The clip will be based on the vertical gravity: a top gravity will clip the bottom
+             edge, a bottom gravity will clip the top edge, and neither will clip both edges. -->
+        <flag name="clip_vertical" value="0x80" />
+        <!-- Additional option that can be set to have the left and/or right edges of
+             the child clipped to its container's bounds.
+             The clip will be based on the horizontal gravity: a left gravity will clip the right
+             edge, a right gravity will clip the left edge, and neither will clip both edges. -->
+        <flag name="clip_horizontal" value="0x08" />
+    </attr>
+
+    <!-- Standard orientation constant. -->
+    <attr name="orientation">
+        <!-- Defines an horizontal widget. -->
+        <enum name="horizontal" value="0" />
+        <!-- Defines a vertical widget. -->
+        <enum name="vertical" value="1" />
+    </attr>
+
+    <!-- ========================== -->
+    <!-- Key Codes                  -->
+    <!-- ========================== -->
+    <eat-comment />
+
+    <!-- This enum provides the same keycode values as can be found in 
+        {@link android.view.KeyEvent} -->
+    <attr name="keycode">
+        <enum name="KEYCODE_UNKNOWN" value="0" />
+        <enum name="KEYCODE_SOFT_LEFT" value="1" />    
+        <enum name="KEYCODE_SOFT_RIGHT" value="2" />
+        <enum name="KEYCODE_HOME" value="3" />
+        <enum name="KEYCODE_BACK" value="4" />
+        <enum name="KEYCODE_CALL" value="5" />
+        <enum name="KEYCODE_ENDCALL" value="6" />
+        <enum name="KEYCODE_0" value="7" />
+        <enum name="KEYCODE_1" value="8" />
+        <enum name="KEYCODE_2" value="9" />
+        <enum name="KEYCODE_3" value="10" />
+        <enum name="KEYCODE_4" value="11" />
+        <enum name="KEYCODE_5" value="12" />
+        <enum name="KEYCODE_6" value="13" />
+        <enum name="KEYCODE_7" value="14" />
+        <enum name="KEYCODE_8" value="15" />
+        <enum name="KEYCODE_9" value="16" />
+        <enum name="KEYCODE_STAR" value="17" />
+        <enum name="KEYCODE_POUND" value="18" />
+        <enum name="KEYCODE_DPAD_UP" value="19" />
+        <enum name="KEYCODE_DPAD_DOWN" value="20" />
+        <enum name="KEYCODE_DPAD_LEFT" value="21" />
+        <enum name="KEYCODE_DPAD_RIGHT" value="22" />
+        <enum name="KEYCODE_DPAD_CENTER" value="23" />
+        <enum name="KEYCODE_VOLUME_UP" value="24" />
+        <enum name="KEYCODE_VOLUME_DOWN" value="25" />
+        <enum name="KEYCODE_POWER" value="26" />
+        <enum name="KEYCODE_CAMERA" value="27" />
+        <enum name="KEYCODE_CLEAR" value="28" />
+        <enum name="KEYCODE_A" value="29" />
+        <enum name="KEYCODE_B" value="30" />
+        <enum name="KEYCODE_C" value="31" />
+        <enum name="KEYCODE_D" value="32" />
+        <enum name="KEYCODE_E" value="33" />
+        <enum name="KEYCODE_F" value="34" />
+        <enum name="KEYCODE_G" value="35" />
+        <enum name="KEYCODE_H" value="36" />
+        <enum name="KEYCODE_I" value="37" />
+        <enum name="KEYCODE_J" value="38" />
+        <enum name="KEYCODE_K" value="39" />
+        <enum name="KEYCODE_L" value="40" />
+        <enum name="KEYCODE_M" value="41" />
+        <enum name="KEYCODE_N" value="42" />
+        <enum name="KEYCODE_O" value="43" />
+        <enum name="KEYCODE_P" value="44" />
+        <enum name="KEYCODE_Q" value="45" />
+        <enum name="KEYCODE_R" value="46" />
+        <enum name="KEYCODE_S" value="47" />
+        <enum name="KEYCODE_T" value="48" />
+        <enum name="KEYCODE_U" value="49" />
+        <enum name="KEYCODE_V" value="50" />
+        <enum name="KEYCODE_W" value="51" />
+        <enum name="KEYCODE_X" value="52" />
+        <enum name="KEYCODE_Y" value="53" />
+        <enum name="KEYCODE_Z" value="54" />
+        <enum name="KEYCODE_COMMA" value="55" />
+        <enum name="KEYCODE_PERIOD" value="56" />
+        <enum name="KEYCODE_ALT_LEFT" value="57" />
+        <enum name="KEYCODE_ALT_RIGHT" value="58" />
+        <enum name="KEYCODE_SHIFT_LEFT" value="59" />
+        <enum name="KEYCODE_SHIFT_RIGHT" value="60" />
+        <enum name="KEYCODE_TAB" value="61" />
+        <enum name="KEYCODE_SPACE" value="62" />
+        <enum name="KEYCODE_SYM" value="63" />
+        <enum name="KEYCODE_EXPLORER" value="64" />
+        <enum name="KEYCODE_ENVELOPE" value="65" />
+        <enum name="KEYCODE_ENTER" value="66" />
+        <enum name="KEYCODE_DEL" value="67" />
+        <enum name="KEYCODE_GRAVE" value="68" />
+        <enum name="KEYCODE_MINUS" value="69" />
+        <enum name="KEYCODE_EQUALS" value="70" />
+        <enum name="KEYCODE_LEFT_BRACKET" value="71" />
+        <enum name="KEYCODE_RIGHT_BRACKET" value="72" />
+        <enum name="KEYCODE_BACKSLASH" value="73" />
+        <enum name="KEYCODE_SEMICOLON" value="74" />
+        <enum name="KEYCODE_APOSTROPHE" value="75" />
+        <enum name="KEYCODE_SLASH" value="76" />
+        <enum name="KEYCODE_AT" value="77" />
+        <enum name="KEYCODE_NUM" value="78" />
+        <enum name="KEYCODE_HEADSETHOOK" value="79" />
+        <enum name="KEYCODE_FOCUS" value="80" />
+        <enum name="KEYCODE_PLUS" value="81" />
+        <enum name="KEYCODE_MENU" value="82" />
+        <enum name="KEYCODE_NOTIFICATION" value="83" />
+        <enum name="KEYCODE_SEARCH" value="84" />
+        <enum name="KEYCODE_PLAYPAUSE" value="85" />
+        <enum name="KEYCODE_STOP" value="86" />
+        <enum name="KEYCODE_NEXTSONG" value="87" />
+        <enum name="KEYCODE_PREVIOUSSONG" value="88" />
+        <enum name="KEYCODE_REWIND" value="89" />
+        <enum name="KEYCODE_FORWARD" value="90" />
+        <enum name="KEYCODE_MUTE" value="91" />
+    </attr>
+
+    <!-- ***************************************************************** -->
+    <!-- These define collections of attributes that can are with classes. -->
+    <!-- ***************************************************************** -->
+
+    <!-- ========================== -->
+    <!-- Special attribute classes. -->
+    <!-- ========================== -->
+    <eat-comment />
+
+    <!-- The set of attributes that describe a Windows's theme. -->
+    <declare-styleable name="Window">
+        <attr name="windowBackground" />
+        <attr name="windowContentOverlay" />
+        <attr name="windowFrame" />
+        <attr name="windowNoTitle" />
+        <attr name="windowFullscreen" />
+        <attr name="windowIsFloating" />
+        <attr name="windowIsTranslucent" />
+        <attr name="windowAnimationStyle" />
+        <attr name="windowSoftInputMode" />
+        <attr name="windowDisablePreview" />
+        <attr name="windowNoDisplay" />
+        <attr name="textColor" />
+        <attr name="backgroundDimEnabled" />
+        <attr name="backgroundDimAmount" />
+    </declare-styleable>
+
+    <!-- The set of attributes that describe a AlertDialog's theme. -->
+    <declare-styleable name="AlertDialog">
+        <attr name="fullDark" format="reference|color" />
+        <attr name="topDark" format="reference|color" />
+        <attr name="centerDark" format="reference|color" />
+        <attr name="bottomDark" format="reference|color" />
+        <attr name="fullBright" format="reference|color" />
+        <attr name="topBright" format="reference|color" />
+        <attr name="centerBright" format="reference|color" />
+        <attr name="bottomBright" format="reference|color" />
+        <attr name="bottomMedium" format="reference|color" />
+        <attr name="centerMedium" format="reference|color" />
+    </declare-styleable>
+    
+    <!-- Window animation class attributes. -->
+    <declare-styleable name="WindowAnimation">
+        <!-- The animation used when a window is being added. -->
+        <attr name="windowEnterAnimation" format="reference" />
+        <!-- The animation used when a window is being removed. -->
+        <attr name="windowExitAnimation" format="reference" />
+        <!-- The animation used when a window is going from INVISIBLE to VISIBLE. -->
+        <attr name="windowShowAnimation" format="reference" />
+        <!-- The animation used when a window is going from VISIBLE to INVISIBLE. -->
+        <attr name="windowHideAnimation" format="reference" />
+        <attr name="activityOpenEnterAnimation" format="reference" />
+        <attr name="activityOpenExitAnimation" format="reference" />
+        <attr name="activityCloseEnterAnimation" format="reference" />
+        <attr name="activityCloseExitAnimation" format="reference" />
+        <attr name="taskOpenEnterAnimation" format="reference" />
+        <attr name="taskOpenExitAnimation" format="reference" />
+        <attr name="taskCloseEnterAnimation" format="reference" />
+        <attr name="taskCloseExitAnimation" format="reference" />
+        <attr name="taskToFrontEnterAnimation" format="reference" />
+        <attr name="taskToFrontExitAnimation" format="reference" />
+        <attr name="taskToBackEnterAnimation" format="reference" />
+        <attr name="taskToBackExitAnimation" format="reference" />
+    </declare-styleable>
+
+    <!-- ============================= -->
+    <!-- View package class attributes -->
+    <!-- ============================= -->
+    <eat-comment />
+
+    <!-- Attributes that can be used with {@link android.view.View} or
+         any of its subclasses.  Also see {@link #ViewGroup_Layout} for
+         attributes that are processed by the view's parent. -->
+    <declare-styleable name="View">
+        <!-- Supply an identifier name for this view, to later retrieve it
+             with {@link android.view.View#findViewById View.findViewById()} or
+             {@link android.app.Activity#findViewById Activity.findViewById()}.
+             This must be a
+             resource reference; typically you set this using the
+             <code>@+</code> syntax to create a new ID resources.
+             For example: <code>android:id="@+id/my_id"</code> which
+             allows you to later retrieve the view
+             with <code>findViewById(R.id.my_id)</code>. -->
+        <attr name="id" format="reference" />
+        
+        <!-- Supply a tag for this view containing a String, to be retrieved
+             later with {@link android.view.View#getTag View.getTag()} or
+             searched for with {@link android.view.View#findViewWithTag
+             View.findViewWithTag()}.  It is generally preferable to use
+             IDs (through the android:id attribute) instead of tags because
+             they are faster and allow for compile-time type checking. -->
+        <attr name="tag" format="string" />
+        
+        <!-- The initial horizontal scroll offset, in pixels.-->
+        <attr name="scrollX" format="dimension" />
+
+        <!-- The initial vertical scroll offset, in pixels. -->
+        <attr name="scrollY" format="dimension" />
+
+        <!-- A drawable to use as the background.  This can be either a reference
+             to a full drawable resource (such as a PNG image, 9-patch,
+             XML state list description, etc), or a solid color such as "#ff000000"
+            (black). -->
+        <attr name="background" format="reference|color" />
+
+        <!-- Sets the padding, in pixels, of all four edges.  Padding is defined as
+             space between the edges of the view and the view's content. A views size 
+             will include it's padding.  If a {@link android.R.attr#background}
+             is provided, the padding will initially be set to that (0 if the
+             drawable does not have padding).  Explicitly setting a padding value
+             will override the corresponding padding found in the background. -->
+        <attr name="padding" format="dimension" />
+        <!-- Sets the padding, in pixels, of the left edge; see {@link android.R.attr#padding}. -->
+        <attr name="paddingLeft" format="dimension" />
+        <!-- Sets the padding, in pixels, of the top edge; see {@link android.R.attr#padding}. -->
+        <attr name="paddingTop" format="dimension" />
+        <!-- Sets the padding, in pixels, of the right edge; see {@link android.R.attr#padding}. -->
+        <attr name="paddingRight" format="dimension" />
+        <!-- Sets the padding, in pixels, of the bottom edge; see {@link android.R.attr#padding}. -->
+        <attr name="paddingBottom" format="dimension" />
+
+        <!-- Boolean that controls whether a view can take focus.  By default the user can not
+             move focus to a view; by setting this attribute to true the view is
+             allowed to take focus.  This value does not impact the behavior of
+             directly calling {@link android.view.View#requestFocus}, which will
+             always request focus regardless of this view.  It only impacts where
+             focus navigation will try to move focus. -->
+        <attr name="focusable" format="boolean" />
+
+        <!-- Boolean that controls whether a view can take focus while in touch mode.
+             If this is true for a view, that view can gain focus when clicked on, and can keep
+             focus if another view is clicked on that doesn't have this attribute set to true. -->
+        <attr name="focusableInTouchMode" format="boolean" />
+
+        <!-- Controls the initial visibility of the view.  -->
+        <attr name="visibility">
+            <!-- Visible on screen; the default value. -->
+            <enum name="visible" value="0" />
+            <!-- Not displayed, but taken into account during layout (space is left for it). -->
+            <enum name="invisible" value="1" />
+            <!-- Completely hidden, as if the view had not been added. -->
+            <enum name="gone" value="2" />
+        </attr>
+
+        <!-- Boolean internal attribute to adjust view layout based on
+             system windows such as the status bar.
+             If true, adjusts the padding of this view to leave space for the system windows.
+             Will only take effect if this view is in a non-embedded activity. -->
+        <attr name="fitsSystemWindows" format="boolean" />
+
+        <!-- Defines which scrollbars should be displayed on scrolling or not. -->
+        <attr name="scrollbars">
+            <!-- No scrollbar is displayed. -->
+            <flag name="none" value="0x00000000" />
+            <!-- Displays horizontal scrollbar only. -->
+            <flag name="horizontal" value="0x00000100" />
+            <!-- Displays vertical scrollbar only. -->
+            <flag name="vertical" value="0x00000200" />
+        </attr>
+
+        <!-- Controls the scrollbar style and position. The scrollbars can be overlaid or
+             inset. When inset, they add to the padding of the view. And the 
+             scrollbars can be drawn inside the padding area or on the edge of 
+             the view. For example, if a view has a background drawable and you 
+             want to draw the scrollbars inside the padding specified by the 
+             drawable, you can use insideOverlay or insideInset. If you want them 
+             to appear at the edge of the view, ignoring the padding, then you can 
+             use outsideOverlay or outsideInset.-->
+        <attr name="scrollbarStyle">
+            <!-- Inside the padding and overlaid -->
+            <enum name="insideOverlay" value="0x0" />
+            <!-- Inside the padding and inset -->
+            <enum name="insideInset" value="0x01000000" />
+            <!-- Edge of the view and overlaid -->
+            <enum name="outsideOverlay" value="0x02000000" />
+            <!-- Edge of the view and inset -->
+            <enum name="outsideInset" value="0x03000000" />
+        </attr>
+        
+        <!-- Set this if the view will serve as a scrolling container, meaing
+             that it can be resized to shrink its overall window so that there
+             will be space for an input method.  If not set, the default
+             value will be true if "scrollbars" has the vertical scrollbar
+             set, else it will be false. -->
+        <attr name="isScrollContainer" format="boolean" />
+        
+        <!-- Sets the width of vertical scrollbars and height of horizontal scrollbars. -->
+        <attr name="scrollbarSize" format="dimension" />
+        <!-- Defines the horizontal scrollbar thumb drawable. -->
+        <attr name="scrollbarThumbHorizontal" format="reference" />
+        <!-- Defines the vertical scrollbar thumb drawable. -->
+        <attr name="scrollbarThumbVertical" format="reference" />
+        <!-- Defines the horizontal scrollbar track drawable. -->
+        <attr name="scrollbarTrackHorizontal" format="reference" />
+        <!-- Defines the vertical scrollbar track drawable. -->
+        <attr name="scrollbarTrackVertical" format="reference" />
+        <!-- Defines whether the horizontal scrollbar track should always be drawn. -->
+        <attr name="scrollbarAlwaysDrawHorizontalTrack" format="boolean" />
+        <!-- Defines whether the vertical scrollbar track should always be drawn -->
+        <attr name="scrollbarAlwaysDrawVerticalTrack" format="boolean" />
+
+        <!-- Defines which edges should be fadeded on scrolling. -->
+        <attr name="fadingEdge">
+            <!-- No edge is faded. -->
+            <flag name="none" value="0x00000000" />
+            <!-- Fades horizontal edges only. -->
+            <flag name="horizontal" value="0x00001000" />
+            <!-- Fades vertical edges only. -->
+            <flag name="vertical" value="0x00002000" />
+        </attr>
+        <!-- Defines the length of the fading edges. -->
+        <attr name="fadingEdgeLength" format="dimension" />
+
+        <!-- Defines the next view to give focus to when the next focus is
+             {@link android.view.View#FOCUS_LEFT}.
+
+             If the reference refers to a view that does not exist or is part
+             of a hierarchy that is invisible, a {@link java.lang.RuntimeException}
+             will result when the reference is accessed.-->
+        <attr name="nextFocusLeft" format="reference"/>
+
+        <!-- Defines the next view to give focus to when the next focus is
+             {@link android.view.View#FOCUS_RIGHT}
+
+             If the reference refers to a view that does not exist or is part
+             of a hierarchy that is invisible, a {@link java.lang.RuntimeException}
+             will result when the reference is accessed.-->
+        <attr name="nextFocusRight" format="reference"/>
+
+        <!-- Defines the next view to give focus to when the next focus is
+             {@link android.view.View#FOCUS_UP}
+
+             If the reference refers to a view that does not exist or is part
+             of a hierarchy that is invisible, a {@link java.lang.RuntimeException}
+             will result when the reference is accessed.-->
+        <attr name="nextFocusUp" format="reference"/>
+
+        <!-- Defines the next view to give focus to when the next focus is
+             {@link android.view.View#FOCUS_DOWN}
+
+             If the reference refers to a view that does not exist or is part
+             of a hierarchy that is invisible, a {@link java.lang.RuntimeException}
+             will result when the reference is accessed.-->
+        <attr name="nextFocusDown" format="reference"/>
+
+        <!-- Defines whether this view reacts to click events. -->
+        <attr name="clickable" format="boolean" />
+        
+        <!-- Defines whether this view reacts to long click events. -->
+        <attr name="longClickable" format="boolean" />
+        
+        <!-- If unset, no state will be saved for this view when it is being
+             frozen. The default is true, allowing the view to be saved
+             (however it also must have an ID assigned to it for its
+             state to be saved).  Setting this to false only disables the
+             state for this view, not for its children which may still
+             be saved. -->
+        <attr name="saveEnabled" format="boolean" />
+        
+        <!-- Defines the quality of translucent drawing caches. This property is used
+             only when the drawing cache is enabled and translucent. The default value is auto. -->
+        <attr name="drawingCacheQuality">
+            <!-- Lets the framework decide what quality level should be used
+                 for the drawing cache. -->
+            <enum name="auto" value="0" />
+            <!-- Low quality. When set to low quality, the drawing cache uses a lower color
+                 depth, thus losing precision in rendering gradients, but uses less memory. -->
+            <enum name="low" value="1" />
+            <!-- High quality. When set to high quality, the drawing cache uses a higher
+                 color depth but uses more memory. -->
+            <enum name="high" value="2" />
+        </attr>
+
+        <!-- Controls whether the view's window should keep the screen on
+             while visible. -->
+        <attr name="keepScreenOn" format="boolean" />
+        
+        <!-- When this attribute is set to true, the view gets its drawable state
+             (focused, pressed, etc.) from its direct parent rather than from itself. -->
+        <attr name="duplicateParentState" format="boolean" />
+        
+        <!-- Defines the minimum height of the view. It is not guaranteed
+             the view will be able to achieve this minimum height (for example,
+             if its parent layout constrains it with less available height). -->
+        <attr name="minHeight" />
+        
+        <!-- Defines the minimum width of the view. It is not guaranteed
+             the view will be able to achieve this minimum width (for example,
+             if its parent layout constrains it with less available width). -->
+        <attr name="minWidth" />
+
+        <!-- Boolean that controls whether a view should have sound effects
+             enabled for events such as clicking and touching. -->
+        <attr name="soundEffectsEnabled" format="boolean" />
+
+        <!-- Boolean that controls whether a view should have haptic feedback
+             enabled for events such as long presses. -->
+        <attr name="hapticFeedbackEnabled" format="boolean" />
+
+    </declare-styleable>
+
+    <!-- Attributes that can be used with a {@link android.view.ViewGroup} or any
+         of its subclasses.  Also see {@link #ViewGroup_Layout} for
+         attributes that this class processes in its children. -->
+    <declare-styleable name="ViewGroup">
+        <!-- Defines whether a child is limited to draw inside of its bounds or not.
+             This is useful with animations that scale the size of the children to more
+             than 100% for instance. In such a case, this property should be set to false
+             to allow the children to draw outside of their bounds. The default value of
+             this property is true. -->
+        <attr name="clipChildren" format="boolean" />
+        <!-- Defines whether the ViewGroup will clip its drawing surface so as to exclude
+             the padding area. This property is set to true by default. -->
+        <attr name="clipToPadding" format="boolean" />
+        <!-- Defines the layout animation to use the first time the ViewGroup is laid out.
+             Layout animations can also be started manually after the first layout. -->
+        <attr name="layoutAnimation" format="reference" />
+        <!-- Defines whether layout animations should create a drawing cache for their
+             children. Enabling the animation cache consumes more memory and requires
+             a longer initialization but provides better performance. The animation
+             cache is enabled by default. -->
+        <attr name="animationCache" format="boolean" />
+        <!-- Defines the persistence of the drawing cache. The drawing cache might be
+             enabled by a ViewGroup for all its children in specific situations (for
+             instance during a scrolling.) This property lets you persist the cache
+             in memory after its initial usage. Persisting the cache consumes more
+             memory but may prevent frequent garbage collection is the cache is created
+             over and over again. By default the persistence is set to scrolling. -->
+        <attr name="persistentDrawingCache">
+            <!-- The drawing cache is not persisted after use. -->
+            <flag name="none" value="0x0" />
+            <!-- The drawing cache is persisted after a layout animation. -->
+            <flag name="animation" value="0x1" />
+            <!-- The drawing cache is persisted after a scroll. -->
+            <flag name="scrolling" value="0x2" />
+            <!-- The drawing cache is always persisted. -->            
+            <flag name="all" value="0x3" />
+        </attr>
+        <!-- Defines whether the ViewGroup should always draw its children using their
+             drawing cache or not. The default value is true. -->
+        <attr name="alwaysDrawnWithCache" format="boolean" />
+        <!-- Sets whether this ViewGroup's drawable states also include
+             its children's drawable states.  This is used, for example, to
+             make a group appear to be focused when its child EditText or button
+             is focused. -->
+        <attr name="addStatesFromChildren" format="boolean" />
+
+        <!-- Defines the relationship between the ViewGroup and its descendants
+             when looking for a View to take focus. -->
+        <attr name="descendantFocusability">
+            <!-- The ViewGroup will get focus before any of its descendants. -->
+            <enum name="beforeDescendants" value="0" />
+            <!-- The ViewGroup will get focus only if none of its descendants want it. -->
+            <enum name="afterDescendants" value="1" />
+            <!-- The ViewGroup will block its descendants from receiving focus. -->
+            <enum name="blocksDescendants" value="2" />
+        </attr>
+
+    </declare-styleable>
+
+    <!-- Attributes that can be used with {@link android.view.ViewStub}. -->
+    <declare-styleable name="ViewStub">
+        <!-- Supply an identifier for the layout resource to inflate when the ViewStub
+             becomes visible or when forced to do so. The layout resource must be a
+             valid reference to a layout. -->
+        <attr name="layout" format="reference" />
+        <!-- Overrides the id of the inflated View with this value. -->
+        <attr name="inflatedId" format="reference" />
+    </declare-styleable>
+
+    <!-- ===================================== -->
+    <!-- View package parent layout attributes -->
+    <!-- ===================================== -->
+    <eat-comment />
+
+    <!-- This is the basic set of layout attributes that are common to all
+         layout managers.  These attributes are specified with the rest of
+         a view's normal attributes (such as {@link android.R.attr#background},
+         but will be parsed by the view's parent and ignored by the child.
+        <p>The values defined here correspond to the base layout attribute
+        class {@link android.view.ViewGroup.LayoutParams}. -->
+    <declare-styleable name="ViewGroup_Layout">
+        <!-- Specifies the basic width of the view.  This is a required attribute
+             for any view inside of a containing layout manager.  Its value may
+             be a dimension (such as "12dip") for a constant width or one of
+             the special constants. -->
+        <attr name="layout_width" format="dimension">
+            <!-- The view should be as big as its parent (minus padding). -->
+            <enum name="fill_parent" value="-1" />
+            <!-- The view should be only big enough to enclose its content (plus padding). -->
+            <enum name="wrap_content" value="-2" />
+        </attr>
+
+        <!-- Specifies the basic height of the view.  This is a required attribute
+             for any view inside of a containing layout manager.  Its value may
+             be a dimension (such as "12dip") for a constant height or one of
+             the special constants. -->
+        <attr name="layout_height" format="dimension">
+            <!-- The view should be as big as its parent (minus padding). -->
+            <enum name="fill_parent" value="-1" />
+            <!-- The view should be only big enough to enclose its content (plus padding). -->
+            <enum name="wrap_content" value="-2" />
+        </attr>
+    </declare-styleable>
+
+    <!-- This is the basic set of layout attributes for layout managers that
+         wish to place margins around their child views.
+         These attributes are specified with the rest of
+         a view's normal attributes (such as {@link android.R.attr#background},
+         but will be parsed by the view's parent and ignored by the child.
+        <p>The values defined here correspond to the base layout attribute
+        class {@link android.view.ViewGroup.MarginLayoutParams}. -->
+    <declare-styleable name="ViewGroup_MarginLayout">
+        <attr name="layout_width" />
+        <attr name="layout_height" />
+        <!--  Specifies extra space on the left, top, right and bottom
+              sides of this view. This space is outside this view's bounds. -->
+        <attr name="layout_margin" format="dimension"  />
+        <!--  Specifies extra space on the left side of this view.
+              This space is outside this view's bounds. -->
+        <attr name="layout_marginLeft" format="dimension"  />
+        <!--  Specifies extra space on the top side of this view.
+              This space is outside this view's bounds. -->
+        <attr name="layout_marginTop" format="dimension" />
+        <!--  Specifies extra space on the right side of this view.
+              This space is outside this view's bounds. -->
+        <attr name="layout_marginRight" format="dimension"  />
+        <!--  Specifies extra space on the bottom side of this view.
+              This space is outside this view's bounds. -->
+        <attr name="layout_marginBottom" format="dimension"  />
+    </declare-styleable>
+
+    <!-- Use <code>input-method</code> as the root tag of the XML resource that
+         describes an
+         {@link android.view.inputmethod.InputMethod} service, which is
+         referenced from its
+         {@link android.view.inputmethod.InputMethod#SERVICE_META_DATA}
+         meta-data entry.  Described here are the attributes that can be
+         included in that tag. -->
+    <declare-styleable name="InputMethod">
+        <!-- Component name of an activity that allows the user to modify
+             the settings for this input method. -->
+        <attr name="settingsActivity" format="string" />
+        <!-- Set to true in all of the configurations for which this input
+             method should be considered an option as the default. -->
+        <attr name="isDefault" format="boolean" />
+    </declare-styleable>
+    
+    <!-- =============================== -->
+    <!-- Widget package class attributes -->
+    <!-- =============================== -->
+    <eat-comment />
+
+    <declare-styleable name="AbsListView">
+         <!-- Drawable used to indicate the currently selected item in the list. -->
+        <attr name="listSelector" format="color|reference" />
+        <!-- When set to true, the selector will be drawn over the selected item. 
+             Otherwise the selector is drawn behind the selected item. The default
+             value is false. -->
+        <attr name="drawSelectorOnTop" format="boolean" />
+        <!-- Used by ListView and GridView to stack their content from the bottom. -->
+        <attr name="stackFromBottom" format="boolean" />
+        <!-- When set to true, the list uses a drawing cache during scrolling.
+             This makes the rendering faster but uses more memory. The default
+             value is true. -->
+        <attr name="scrollingCache" format="boolean" />
+        <!-- When set to true, the list will filter results as the user types. The
+             List's adapter must support the Filterable interface for this to work -->
+        <attr name="textFilterEnabled" format="boolean" />
+        <!-- Sets the transcript mode for the list. In transcript mode, the list
+             scrolls to the bottom to make new items visible when they are added. -->
+        <attr name="transcriptMode">
+            <!-- Disables transcript mode. This is the default value. -->
+            <enum name="disabled" value="0"/>
+            <!-- The list will automatically scroll to the bottom when
+                 a data set change notification is received and only if the last item is
+                 already visible on screen. -->
+            <enum name="normal" value="1" />
+            <!-- The list will automatically scroll to the bottom, no matter what items
+                 are currently visible. --> 
+            <enum name="alwaysScroll" value="2" />
+        </attr>
+        <!-- Indicates that this list will always be drawn on top of solid, single-color
+             opaque background. This allows the list to optimize drawing. -->
+        <attr name="cacheColorHint" format="color" />
+        <!-- Enables the fast scroll thumb that can be dragged to quickly scroll through 
+             the list. -->
+        <attr name="fastScrollEnabled" format="boolean" />
+        <!-- When set to true, the list will use a more refined calculation
+             method based on the pixels height of the items visible on screen. This
+             property is set to true by default but should be set to false if your adapter
+             will display items of varying heights. When this property is set to true and
+             your adapter displays items of varying heights, the scrollbar thumb will
+             change size as the user scrolls through the list. When set to fale, the list
+             will use only the number of items in the adapter and the number of items visible
+             on screen to determine the scrollbar's properties. -->
+        <attr name="smoothScrollbar" format="boolean" />
+    </declare-styleable>
+    <declare-styleable name="AbsSpinner">
+        <!-- Reference to an array resource that will populate the Spinner.  For static content,
+             this is simpler than populating the Spinner programmatically. -->
+        <attr name="entries" />
+    </declare-styleable>
+    <declare-styleable name="AnalogClock">
+        <attr name="dial" format="reference"/>
+        <attr name="hand_hour" format="reference"/>
+        <attr name="hand_minute" format="reference"/>
+    </declare-styleable>
+    <declare-styleable name="Button">
+    </declare-styleable>
+    <declare-styleable name="Chronometer">
+        <!-- Format string: if specified, the Chronometer will display this
+             string, with the first "%s" replaced by the current timer value
+             in "MM:SS" or "H:MM:SS" form.
+             If no format string is specified, the Chronometer will simply display
+             "MM:SS" or "H:MM:SS". -->
+        <attr name="format" format="string" localization="suggested" />
+    </declare-styleable>
+    <declare-styleable name="CompoundButton">
+        <!-- Indicates the initial checked state of this button -->
+        <attr name="checked" format="boolean" />
+        <!-- Drawable used for the button graphic (e.g. checkbox, radio button, etc). -->
+        <attr name="button" format="reference"/>
+    </declare-styleable>
+    <declare-styleable name="CheckedTextView">
+        <!-- Indicates the initial checked state of this text -->
+        <attr name="checked" />
+        <!-- Drawable used for the check mark graphic -->
+        <attr name="checkMark" format="reference"/>
+    </declare-styleable>
+    <declare-styleable name="EditText">
+    </declare-styleable>
+    <declare-styleable name="FrameLayout">
+        <!-- Defines the drawable to draw over the content. This can be used as an overlay.
+             The foreground drawable participates in the padding of the content. -->
+        <attr name="foreground" format="reference|color" />
+        <!-- Defines the gravity to apply to the foreground drawable. The gravity defaults
+             to fill. -->
+        <attr name="foregroundGravity">
+            <!-- Push object to the top of its container, not changing its size. -->
+            <flag name="top" value="0x30" />
+            <!-- Push object to the bottom of its container, not changing its size. -->
+            <flag name="bottom" value="0x50" />
+            <!-- Push object to the left of its container, not changing its size. -->
+            <flag name="left" value="0x03" />
+            <!-- Push object to the right of its container, not changing its size. -->
+            <flag name="right" value="0x05" />
+            <!-- Place object in the vertical center of its container, not changing its size. -->
+            <flag name="center_vertical" value="0x10" />
+            <!-- Grow the vertical size of the object if needed so it completely fills its container. -->
+            <flag name="fill_vertical" value="0x70" />
+            <!-- Place object in the horizontal center of its container, not changing its size. -->
+            <flag name="center_horizontal" value="0x01" />
+            <!-- Grow the horizontal size of the object if needed so it completely fills its container. -->
+            <flag name="fill_horizontal" value="0x07" />
+            <!-- Place the object in the center of its container in both the vertical and horizontal axis, not changing its size. -->
+            <flag name="center" value="0x11" />
+            <!-- Grow the horizontal and vertical size of the object if needed so it completely fills its container. -->
+            <flag name="fill" value="0x77" />
+            <!-- Additional option that can be set to have the top and/or bottom edges of
+                 the child clipped to its container's bounds.
+                 The clip will be based on the vertical gravity: a top gravity will clip the bottom
+                 edge, a bottom gravity will clip the top edge, and neither will clip both edges. -->
+            <flag name="clip_vertical" value="0x80" />
+            <!-- Additional option that can be set to have the left and/or right edges of
+                 the child clipped to its container's bounds.
+                 The clip will be based on the horizontal gravity: a left gravity will clip the right
+                 edge, a right gravity will clip the left edge, and neither will clip both edges. -->
+            <flag name="clip_horizontal" value="0x08" />
+        </attr>
+        <!-- Determines whether to measure all children or just those in 
+             the VISIBLE or INVISIBLE state when measuring. Defaults to false. -->
+        <attr name="measureAllChildren" format="boolean" />
+    </declare-styleable>
+    <declare-styleable name="ExpandableListView">
+        <!-- Indicator shown beside the group View. This can be a stateful Drawable. -->
+        <attr name="groupIndicator" format="reference" />
+        <!-- Indicator shown beside the child View. This can be a stateful Drawable. -->
+        <attr name="childIndicator" format="reference" />
+        <!-- The left bound for an item's indicator. To specify a left bound specific to children,
+             use childIndicatorLeft. --> 
+        <attr name="indicatorLeft" format="dimension" />
+        <!-- The right bound for an item's indicator. To specify a right bound specific to children,
+             use childIndicatorRight. --> 
+        <attr name="indicatorRight" format="dimension" />
+        <!-- The left bound for a child's indicator. --> 
+        <attr name="childIndicatorLeft" format="dimension" />
+        <!-- The right bound for a child's indicator. --> 
+        <attr name="childIndicatorRight" format="dimension" />
+        <!-- Drawable or color that is used as a divider for children. (It will drawn
+             below and above child items.) The height of this will be the same as
+             the height of the normal list item divider. -->
+        <attr name="childDivider" format="reference|color" />
+    </declare-styleable>
+    <declare-styleable name="Gallery">
+        <attr name="gravity" />
+        <!-- Sets how long a transition animation should run (in milliseconds)
+             when layout has changed.  Only relevant if animation is turned on. -->
+        <attr name="animationDuration" format="integer" min="0" />
+        <attr name="spacing" format="dimension" />
+        <!-- Sets the alpha on the items that are not selected. -->
+        <attr name="unselectedAlpha" format="float" />
+    </declare-styleable>
+    <declare-styleable name="GridView">
+        <attr name="horizontalSpacing" format="dimension" />
+        <attr name="verticalSpacing" format="dimension" />
+        <attr name="stretchMode">
+            <enum name="none" value="0"/>
+            <enum name="spacingWidth" value="1" />
+            <enum name="columnWidth" value="2" />
+            <enum name="spacingWidthUniform" value="3" />
+        </attr>
+        <attr name="columnWidth" format="dimension" />
+        <attr name="numColumns" format="integer" min="0">
+            <enum name="auto_fit" value="-1" />
+        </attr>
+        <attr name="gravity" />
+    </declare-styleable>
+    <declare-styleable name="ImageSwitcher">
+    </declare-styleable>
+    <declare-styleable name="ImageView">
+        <!-- Sets a drawable as the content of this ImageView. -->
+        <attr name="src" format="reference|color" />
+        <!-- Controls how the image should be resized or moved to match the size
+             of this ImageView. -->
+        <attr name="scaleType">
+            <enum name="matrix" value="0" />
+            <enum name="fitXY" value="1" />
+            <enum name="fitStart" value="2" />
+            <enum name="fitCenter" value="3" />
+            <enum name="fitEnd" value="4" />
+            <enum name="center" value="5" />
+            <enum name="centerCrop" value="6" />
+            <enum name="centerInside" value="7" />
+        </attr>
+        <!-- Set this to true if you want the ImageView to adjust its bounds
+             to preserve the aspect ratio of its drawable. -->
+        <attr name="adjustViewBounds" format="boolean" />
+        <!-- An optional argument to supply a maximum width for this view. 
+             See {see android.widget.ImageView#setMaxWidth} for details. -->
+        <attr name="maxWidth" format="dimension" />
+        <!-- An optional argument to supply a maximum height for this view. 
+             See {see android.widget.ImageView#setMaxHeight} for details. -->
+        <attr name="maxHeight" format="dimension" />
+        <!-- Set a tinting color for the image -->
+        <attr name="tint" format="color" />
+        <!-- If true, the image view will be baseline aligned with based on its
+             bottom edge -->
+        <attr name="baselineAlignBottom" format="boolean" />
+         <!-- If true, the image will be cropped to fit within its padding -->
+        <attr name="cropToPadding" format="boolean" />
+    </declare-styleable>
+    <declare-styleable name="ToggleButton">
+        <!-- The text for the button when it is checked. -->
+        <attr name="textOn" format="string" />
+        <!-- The text for the button when it is not checked. -->
+        <attr name="textOff" format="string" />
+        <!-- The alpha to apply to the indicator when disabled. -->
+        <attr name="disabledAlpha" />
+    </declare-styleable>
+    <declare-styleable name="RelativeLayout">
+        <attr name="gravity" />
+        <!-- Indicates what view should not be affected by gravity. -->
+        <attr name="ignoreGravity" format="reference" />
+    </declare-styleable>
+    <declare-styleable name="LinearLayout">
+        <!-- Should the layout be a column or a row?  Use "horizontal"
+             for a row, "vertical" for a column.  The default is
+             horizontal. -->
+        <attr name="orientation" />
+        <attr name="gravity" />
+        <!-- When set to false, prevents the layout from aligning its children's
+             baselines. This attribute is particularly useful when the children
+             use different values for gravity. The default value is true. -->
+        <attr name="baselineAligned" format="boolean" />
+        <!-- When a linear layout is part of another layout that is baseline
+          aligned, it can specify which of its children to baseline align to
+          (i.e which child TextView).-->
+        <attr name="baselineAlignedChildIndex" format="integer" min="0"/>
+        <!-- Defines the maximum weight sum. If unspecified, the sum is computed
+             by adding the layout_weight of all of the children. This can be
+             used for instance to give a single child 50% of the total available
+             space by giving it a layout_weight of 0.5 and setting the weightSum
+             to 1.0. -->
+        <attr name="weightSum" format="float" />
+    </declare-styleable>
+    <declare-styleable name="ListView">
+        <!-- Reference to an array resource that will populate the ListView.  For static content,
+             this is simpler than populating the ListView programmatically. -->
+        <attr name="entries" />
+        <!-- Drawable or color to draw between list items. -->
+        <attr name="divider" format="reference|color" />
+        <!-- Height of the divider. Will use the intrinsic height of the divider if this
+             is not specified. -->
+        <attr name="dividerHeight" format="dimension" />
+        <!-- Defines the choice behavior for the List. By default, Lists do not have 
+             any choice behavior. By setting the choiceMode to singleChoice, the List
+             allows up to one item to be in a chosen state. By setting the choiceMode to 
+             multipleChoice, the list allows any number of items to be chosen. -->
+        <attr name="choiceMode">
+            <!-- Normal list that does not indicate choices -->
+            <enum name="none" value="0" />
+            <!-- The list allows up to one choice -->
+            <enum name="singleChoice" value="1" />
+            <!-- The list allows multiple choices -->
+            <enum name="multipleChoice" value="2" />
+        </attr>
+        <!-- When set to false, the ListView will not draw the divider after each header view.
+             The default value is true. -->
+        <attr name="headerDividersEnabled" format="boolean" />
+        <!-- When set to false, the ListView will not draw the divider before each footer view.
+             The default value is true. -->
+        <attr name="footerDividersEnabled" format="boolean" />
+    </declare-styleable>
+    <declare-styleable name="MenuView">
+        <!-- Default appearance of menu item text. -->
+        <attr name="itemTextAppearance" format="reference" />
+        <!-- Default horizontal divider between rows of menu items. -->
+        <attr name="horizontalDivider" format="reference" />
+        <!-- Default vertical divider between menu items. -->
+        <attr name="verticalDivider" format="reference" />
+        <!-- Default background for the menu header. -->
+        <attr name="headerBackground" format="color|reference" />
+        <!-- Default background for each menu item. -->
+        <attr name="itemBackground" format="color|reference" />
+        <!-- Default animations for the menu -->
+        <attr name="windowAnimationStyle" />
+        <!-- Default disabled icon alpha for each menu item that shows an icon. -->
+        <attr name="itemIconDisabledAlpha" format="float" />
+    </declare-styleable>
+    <declare-styleable name="IconMenuView">
+        <!-- Defines the height of each row. -->
+        <attr name="rowHeight" format="dimension" />
+        <!-- Defines the maximum number of rows displayed. -->
+        <attr name="maxRows" format="integer" />
+        <!-- Defines the maximum number of items per row. -->
+        <attr name="maxItemsPerRow" format="integer" />
+        <!-- Defines the maximum number of items to show. -->
+        <attr name="maxItems" format="integer" />
+        <!-- 'More' icon -->
+        <attr name="moreIcon" format="reference" />
+    </declare-styleable>
+    
+    <declare-styleable name="ProgressBar">
+        <!-- Defines the maximum value the progress can take. -->
+        <attr name="max" format="integer" />
+        <!-- Defines the default progress value, between 0 and max. -->
+        <attr name="progress" format="integer" />
+        <!-- Defines the secondary progress value, between 0 and max. This progress is drawn between
+             the primary progress and the background.  It can be ideal for media scenarios such as
+             showing the buffering progress while the default progress shows the play progress. -->
+        <attr name="secondaryProgress" format="integer" />
+        <!-- Allows to enable the indeterminate mode. In this mode the progress
+         bar plays an infinite looping animation. -->
+        <attr name="indeterminate" format="boolean" />
+        <!-- Restricts to ONLY indeterminate mode (state-keeping progress mode will not work). -->
+        <attr name="indeterminateOnly" format="boolean" />
+        <!-- Drawable used for the indeterminate mode. -->
+        <attr name="indeterminateDrawable" format="reference" />
+        <!-- Drawable used for the progress mode. -->
+        <attr name="progressDrawable" format="reference" />
+        <!-- Duration of the indeterminate animation. -->
+        <attr name="indeterminateDuration" format="integer" min="1" />
+        <!-- Defines how the indeterminate mode should behave when the progress
+        reaches max. -->
+        <attr name="indeterminateBehavior">
+            <!-- Progress starts over from 0. -->
+            <enum name="repeat" value="1" />
+            <!-- Progress keeps the current value and goes back to 0. -->
+            <enum name="cycle" value="2" />
+        </attr>
+        <attr name="minWidth" format="dimension" />
+        <attr name="maxWidth" />
+        <attr name="minHeight" format="dimension" />
+        <attr name="maxHeight" />
+        <attr name="interpolator" format="reference" />        
+    </declare-styleable>
+    
+    <declare-styleable name="SeekBar">
+        <!-- Draws the thumb on a seekbar -->    
+        <attr name="thumb" format="reference" />
+        <!-- An offset for the thumb that allows it to extend out of the range of the track. -->
+        <attr name="thumbOffset" format="dimension" /> 
+    </declare-styleable>
+    
+    <declare-styleable name="RatingBar">
+        <!-- The number of stars (or rating items) to show. -->    
+        <attr name="numStars" format="integer" /> 
+        <!-- The rating to set by default. -->    
+        <attr name="rating" format="float" /> 
+        <!-- The step size of the rating. -->    
+        <attr name="stepSize" format="float" /> 
+        <!-- Whether this rating bar is an indicator (and non-changeable by the user). -->    
+        <attr name="isIndicator" format="boolean" /> 
+    </declare-styleable>
+    
+    <declare-styleable name="RadioGroup">
+        <!-- The id of the child radio button that should be checked by default
+             within this radio group. -->
+        <attr name="checkedButton" format="integer" />
+        <!-- Should the radio group be a column or a row?  Use "horizontal"
+             for a row, "vertical" for a column.  The default is
+             vertical. -->
+        <attr name="orientation" />
+    </declare-styleable>
+    <declare-styleable name="TableLayout">
+        <!-- The 0 based index of the columns to stretch. The column indices
+             must be separated by a comma: 1, 2, 5. Illegal and duplicate
+             indices are ignored. You can stretch all columns by using the
+             value "*" instead. Note that a column can be marked stretchable
+             and shrinkable at the same time. -->
+        <attr name="stretchColumns" format="string" />
+       <!-- The 0 based index of the columns to shrink. The column indices
+             must be separated by a comma: 1, 2, 5. Illegal and duplicate
+             indices are ignored. You can shrink all columns by using the
+             value "*" instead. Note that a column can be marked stretchable
+             and shrinkable at the same time. -->
+        <attr name="shrinkColumns" format="string" /> 
+        <!-- The 0 based index of the columns to collapse. The column indices
+             must be separated by a comma: 1, 2, 5. Illegal and duplicate
+             indices are ignored. -->
+        <attr name="collapseColumns" format="string" />
+    </declare-styleable>
+    <declare-styleable name="TableRow">
+
+    </declare-styleable>
+    <declare-styleable name="TableRow_Cell">
+        <!-- The index of the column in which this child should be. -->
+        <attr name="layout_column" format="integer" />
+        <!-- Defines how many columns this child should span.  Must be >= 1.-->
+        <attr name="layout_span" format="integer" />
+    </declare-styleable>
+    <declare-styleable name="TabWidget">
+    </declare-styleable>
+    <declare-styleable name="TextAppearance">
+        <!-- Text color. -->
+        <attr name="textColor" />
+        <!-- Size of the text. Recommended dimension type for text is "sp" for scaled-pixels (example: 15sp). -->
+        <attr name="textSize" />
+        <!-- Style (bold, italic, bolditalic) for the text. -->
+        <attr name="textStyle" />
+        <!-- Typeface (normal, sans, serif, monospace) for the text. -->
+        <attr name="typeface" />
+        <!-- Color of the text selection highlight. -->
+        <attr name="textColorHighlight" />
+        <!-- Color of the hint text. -->
+        <attr name="textColorHint" />
+        <!-- Color of the links. -->
+        <attr name="textColorLink" />
+    </declare-styleable>
+    <declare-styleable name="TextSwitcher">
+    </declare-styleable>
+    <declare-styleable name="TextView">
+        <!-- Determines the minimum type that getText() will return.
+             The default is "normal".
+             Note that EditText and LogTextBox always return Editable,
+             even if you specify something less powerful here. -->
+        <attr name="bufferType">
+            <!-- Can return any CharSequence, possibly a
+             Spanned one if the source text was Spanned. -->
+            <enum name="normal" value="0" />
+            <!-- Can only return Spannable. -->
+            <enum name="spannable" value="1" />
+            <!-- Can only return Spannable and Editable. -->
+            <enum name="editable" value="2" />
+        </attr>
+        <!-- Text to display. -->
+        <attr name="text" format="string" localization="suggested" />
+        <!-- Hint text to display when the text is empty. -->
+        <attr name="hint" format="string" />
+        <!-- Text color. -->
+        <attr name="textColor" />
+        <!-- Color of the text selection highlight. -->
+        <attr name="textColorHighlight" />
+        <!-- Color of the hint text. -->
+        <attr name="textColorHint" />
+        <!-- Base text color, typeface, size, and style. -->
+        <attr name="textAppearance" />
+        <!-- Size of the text. Recommended dimension type for text is "sp" for scaled-pixels (example: 15sp). -->
+        <attr name="textSize" />
+        <!-- Sets the horizontal scaling factor for the text -->
+        <attr name="textScaleX" format="float" />
+        <!-- Typeface (normal, sans, serif, monospace) for the text. -->
+        <attr name="typeface" />
+        <!-- Style (bold, italic, bolditalic) for the text. -->
+        <attr name="textStyle" />
+        <!-- Text color for links. -->
+        <attr name="textColorLink" />
+        <!-- Makes the cursor visible (the default) or invisible -->
+        <attr name="cursorVisible" format="boolean" />
+        <!-- Makes the TextView be at most this many lines tall -->
+        <attr name="maxLines" format="integer" min="0" />
+        <!-- Makes the TextView be at most this many pixels tall -->
+        <attr name="maxHeight" />
+        <!-- Makes the TextView be exactly this many lines tall -->
+        <attr name="lines" format="integer" min="0" />
+        <!-- Makes the TextView be exactly this many pixels tall.
+             You could get the same effect by specifying this number in the
+             layout parameters. -->
+        <attr name="height" format="dimension" />
+        <!-- Makes the TextView be at least this many lines tall -->
+        <attr name="minLines" format="integer" min="0" />
+        <!-- Makes the TextView be at least this many pixels tall -->
+        <attr name="minHeight" />
+        <!-- Makes the TextView be at most this many ems wide -->
+        <attr name="maxEms" format="integer" min="0" />
+        <!-- Makes the TextView be at most this many pixels wide -->
+        <attr name="maxWidth" />
+        <!-- Makes the TextView be exactly this many ems wide -->
+        <attr name="ems" format="integer" min="0" />
+        <!-- Makes the TextView be exactly this many pixels wide.
+             You could get the same effect by specifying this number in the
+             layout parameters. -->
+        <attr name="width" format="dimension" />
+        <!-- Makes the TextView be at least this many ems wide -->
+        <attr name="minEms" format="integer" min="0" />
+        <!-- Makes the TextView be at least this many pixels wide -->
+        <attr name="minWidth" />
+        <!-- Specifies how to align the text by the view's x and/or y axis 
+             when the text is smaller than the view. -->
+        <attr name="gravity" />
+        <!-- Whether the text is allowed to be wider than the view (and
+             therefore can be scrolled horizontally). -->
+        <attr name="scrollHorizontally" format="boolean" />
+        <!-- Whether the characters of the field are displayed as
+             password dots instead of themselves.
+             {@deprecated Use inputType instead.} -->
+        <attr name="password" format="boolean" />
+        <!-- Constrains the text to a single horizontally scrolling line
+             instead of letting it wrap onto multiple lines, and advances
+             focus instead of inserting a newline when you press the
+             enter key.  Note: for editable text views, it is better
+             to control this using the textMultiLine flag in the inputType
+             attribute.  (If both singleLine and inputType are supplied,
+             the inputType flags will override the value of singleLine.)
+             {@deprecated This attribute is deprecated and is replaced by the textMultiLine flag
+             in the inputType attribute.  Use caution when altering existing layouts, as the 
+             default value of singeLine is false (multi-line mode), but if you specify any 
+             value for inputType, the default is single-line mode.  (If both singleLine and 
+             inputType attributes are found,  the inputType flags will override the value of 
+             singleLine.) } -->
+        <attr name="singleLine" format="boolean" />
+        <!-- {@deprecated Use state_enabled instead.} -->
+        <attr name="enabled" format="boolean" />
+        <!-- If the text is selectable, select it all when the view takes
+             focus instead of moving the cursor to the start or end. -->
+        <attr name="selectAllOnFocus" format="boolean" />
+        <!-- Leave enough room for ascenders and descenders instead of
+             using the font ascent and descent strictly.  (Normally true). -->
+        <attr name="includeFontPadding" format="boolean" />
+        <!-- Set an input filter to constrain the text length to the
+             specified number. -->
+        <attr name="maxLength" format="integer" min="0" />
+        <!-- Place a shadow of the specified color behind the text. -->
+        <attr name="shadowColor" format="color" />
+        <!-- Horizontal offset of the shadow. -->
+        <attr name="shadowDx" format="float" />
+        <!-- Vertical offset of the shadow. -->
+        <attr name="shadowDy" format="float" />
+        <!-- Radius of the shadow. -->
+        <attr name="shadowRadius" format="float" />
+        <attr name="autoLink" />
+        <!-- If set to false, keeps the movement method from being set
+             to the link movement method even if autoLink causes links
+             to be found. -->
+        <attr name="linksClickable" format="boolean" />
+        <!-- If set, specifies that this TextView has a numeric input method.
+             The default is false.
+             {@deprecated Use inputType instead.} -->
+        <attr name="numeric">
+            <!-- Input is numeric. -->
+            <flag name="integer" value="0x01" />
+            <!-- Input is numeric, with sign allowed. -->
+            <flag name="signed" value="0x003" />
+            <!-- Input is numeric, with decimals allowed. -->
+            <flag name="decimal" value="0x05" />
+        </attr>
+        <!-- If set, specifies that this TextView has a numeric input method
+             and that these specific characters are the ones that it will
+             accept.
+             If this is set, numeric is implied to be true.
+             The default is false. -->
+        <attr name="digits" format="string" />
+        <!-- If set, specifies that this TextView has a phone number input
+             method. The default is false.
+             {@deprecated Use inputType instead.} -->
+        <attr name="phoneNumber" format="boolean" />
+        <!-- If set, specifies that this TextView should use the specified
+             input method (specified by fully-qualified class name).
+             {@deprecated Use inputType instead.} -->
+        <attr name="inputMethod" format="string" />
+        <!-- If set, specifies that this TextView has a textual input method
+             and should automatically capitalize what the user types.
+             The default is "none".
+             {@deprecated Use inputType instead.} -->
+        <attr name="capitalize">
+            <!-- Don't automatically capitalize anything. -->
+            <enum name="none" value="0" />
+            <!-- Capitalize the first word of each sentence. -->
+            <enum name="sentences" value="1" />
+            <!-- Capitalize the first letter of every word. -->
+            <enum name="words" value="2" />
+            <!-- Capitalize every character. -->
+            <enum name="characters" value="3" />
+        </attr>
+        <!-- If set, specifies that this TextView has a textual input method
+             and automatically corrects some common spelling errors.
+             The default is "false".
+             {@deprecated Use inputType instead.} -->
+        <attr name="autoText" format="boolean" />
+        <!-- If set, specifies that this TextView has an input method.
+             It will be a textual one unless it has otherwise been specified.
+             For TextView, this is false by default.  For EditText, it is
+             true by default.
+             {@deprecated Use inputType instead.} -->
+        <attr name="editable" format="boolean" />
+        <!-- If set, the text view will include its current complete text
+             inside of its frozen icicle in addition to meta-data such as
+             the current cursor position.  By default this is disabled;
+             it can be useful when the contents of a text view is not stored
+             in a persistent place such as a content provider. -->
+        <attr name="freezesText" format="boolean" />
+        <!-- If set, causes words that are longer than the view is wide
+             to be ellipsized instead of broken in the middle.
+             You will often also want to set scrollHorizontally or singleLine
+             as well so that the text as a whole is also constrained to
+             a single line instead of still allowed to be broken onto
+             multiple lines. -->
+        <attr name="ellipsize" />
+        <!-- The drawable to be drawn above the text. -->
+        <attr name="drawableTop" format="reference|color" />
+        <!-- The drawable to be drawn below the text. -->
+        <attr name="drawableBottom" format="reference|color" />
+        <!-- The drawable to be drawn to the left of the text. -->
+        <attr name="drawableLeft" format="reference|color" />
+        <!-- The drawable to be drawn to the right of the text. -->
+        <attr name="drawableRight" format="reference|color" />
+        <!-- The padding between the drawables and the text. -->
+        <attr name="drawablePadding" format="dimension" />
+        <!-- Extra spacing between lines of text. -->
+        <attr name="lineSpacingExtra" format="dimension" />
+        <!-- Extra spacing between lines of text, as a multiplier. -->
+        <attr name="lineSpacingMultiplier" format="float" />
+        <!-- The number of times to repeat the marquee animation. Only applied if the 
+             TextView has marquee enabled. -->
+        <attr name="marqueeRepeatLimit" format="integer">
+            <!-- Indicates that marquee should repeat indefinitely  -->
+            <enum name="marquee_forever" value="-1" />
+        </attr>
+        <attr name="inputType" />
+        <attr name="imeOptions" />
+        <!-- An addition content type description to supply to the input
+             method attached to the text view, which is private to the
+             implementation of the input method.  This simply fills in
+             the {@link android.view.inputmethod.EditorInfo#privateImeOptions
+             EditorInfo.privateImeOptions} field when the input
+             method is connected. -->
+        <attr name="privateImeOptions" format="string" />
+        <!-- Supply a value for
+             {@link android.view.inputmethod.EditorInfo#actionLabel EditorInfo.actionLabel}
+             used when an input method is connected to the text view. -->
+        <attr name="imeActionLabel" format="string" />
+        <!-- Supply a value for
+             {@link android.view.inputmethod.EditorInfo#actionId EditorInfo.actionId}
+             used when an input method is connected to the text view. -->
+        <attr name="imeActionId" format="integer" />
+        <!-- Reference to an
+             {@link android.R.styleable#InputExtras &lt;input-extras&gt;}
+             XML resource containing additional data to
+             supply to an input method, which is private to the implementation
+             of the input method.  This simply fills in
+             the {@link android.view.inputmethod.EditorInfo#extras
+             EditorInfo.extras} field when the input
+             method is connected. -->
+        <attr name="editorExtras" format="reference" />
+    </declare-styleable>
+    <!-- An <code>input-extras</code> is a container for extra data to supply to
+         an input method.  Contains
+         one more more {@link #Extra <extra>} tags.  -->
+    <declare-styleable name="InputExtras">
+    </declare-styleable>
+    <declare-styleable name="AutoCompleteTextView">
+        <!-- Defines the hint displayed in the drop down menu. -->
+        <attr name="completionHint" format="string" />
+        <!-- Defines the hint view displayed in the drop down menu. -->
+        <attr name="completionHintView" format="reference" />
+        <!-- Defines the number of characters that the user must type before
+         completion suggestions are displayed in a drop down menu. -->
+        <attr name="completionThreshold" format="integer" min="1" />
+        <!-- Selector in a drop down list. -->
+        <attr name="dropDownSelector" format="reference|color" />
+        <!-- Amount of pixels by which the drop down should be offset vertically. -->
+        <attr name="dropDownVerticalOffset" format="dimension" />
+        <!-- Amount of pixels by which the drop down should be offset horizontally. -->
+        <attr name="dropDownHorizontalOffset" format="dimension" />
+        <!-- View to anchor the auto-complete dropdown to. If not specified, the text view itself
+             is used. -->
+        <attr name="dropDownAnchor" format="reference" />
+        <!-- Specifies the basic width of the dropdown. Its value may
+             be a dimension (such as "12dip") for a constant width, fill_parent
+             to fill the width of the screen, or wrap_content to match the width
+             of the anchored view. -->
+        <attr name="dropDownWidth" format="dimension">
+            <!-- The dropdown should fill the width of the screen. -->
+            <enum name="fill_parent" value="-1" />
+            <!-- The dropdown should fit the width of its anchor. -->
+            <enum name="wrap_content" value="-2" />
+        </attr>
+        <attr name="inputType" />
+    </declare-styleable>
+    <declare-styleable name="PopupWindow">
+        <attr name="popupBackground" format="reference|color" />
+    </declare-styleable>
+    <declare-styleable name="ViewAnimator">
+        <attr name="inAnimation" format="reference" />
+        <attr name="outAnimation" format="reference" />
+    </declare-styleable>
+    <declare-styleable name="ViewFlipper">
+        <attr name="flipInterval" format="integer" min="0" />
+    </declare-styleable>
+    <declare-styleable name="ViewSwitcher">
+    </declare-styleable>
+    <declare-styleable name="ScrollView">
+        <!-- Defines whether the scrollview should stretch its content to fill the viewport. -->
+        <attr name="fillViewport" format="boolean" />
+    </declare-styleable>
+    <declare-styleable name="HorizontalScrollView">
+        <!-- Defines whether the scrollview should stretch its content to fill the viewport. -->
+        <attr name="fillViewport" />
+    </declare-styleable>
+    <declare-styleable name="Spinner">
+        <!-- The prompt to display when the spinner's dialog is shown. -->
+        <attr name="prompt" format="reference" />
+    </declare-styleable>
+    <declare-styleable name="DatePicker">  
+        <!-- The first year (inclusive) i.e. 1940 -->
+        <attr name="startYear" format="integer" />
+        <!-- The last year (inclusive) i.e. 2010 -->
+        <attr name="endYear" format="integer" />
+    </declare-styleable>
+
+    <declare-styleable name="TwoLineListItem">
+        <attr name="mode">
+            <!-- Always show only the first line. -->
+            <enum name="oneLine" value="1" />
+            <!-- When selected show both lines, otherwise show only the first line.
+                 This is the default mode-->
+            <enum name="collapsing" value="2" />
+            <!-- Always show both lines. -->
+            <enum name="twoLine" value="3" />
+        </attr>
+    </declare-styleable>
+
+    <!-- SlidingDrawer specific attributes. These attributes are used to configure
+         a SlidingDrawer from XML. -->
+    <declare-styleable name="SlidingDrawer">
+        <!-- Identifier for the child that represents the drawer's handle. -->
+        <attr name="handle" format="reference" />
+        <!-- Identifier for the child that represents the drawer's content. -->
+        <attr name="content" format="reference" />
+        <!-- Orientation of the SlidingDrawer. -->
+        <attr name="orientation" />
+        <!-- Extra offset for the handle at the bottom of the SlidingDrawer. -->
+        <attr name="bottomOffset" format="dimension"  />
+        <!-- Extra offset for the handle at the top of the SlidingDrawer. -->
+        <attr name="topOffset" format="dimension"  />
+        <!-- Indicates whether the drawer can be opened/closed by a single tap
+             on the handle.  (If false, the user must drag or fling, or click
+             using the trackball, to open/close the drawer.)  Default is true. -->
+        <attr name="allowSingleTap" format="boolean" />
+        <!-- Indicates whether the drawer should be opened/closed with an animation
+             when the user clicks the handle. Default is true. -->
+        <attr name="animateOnClick" format="boolean" />
+    </declare-styleable>
+
+    <!-- ======================================= -->
+    <!-- Widget package parent layout attributes -->
+    <!-- ======================================= -->
+    <eat-comment />
+
+    <declare-styleable name="AbsoluteLayout_Layout">
+        <attr name="layout_x" format="dimension" />
+        <attr name="layout_y" format="dimension" />
+    </declare-styleable>
+    <declare-styleable name="LinearLayout_Layout">
+        <attr name="layout_width" />
+        <attr name="layout_height" />
+        <attr name="layout_weight" format="float" />
+        <attr name="layout_gravity" />
+    </declare-styleable>
+    <declare-styleable name="FrameLayout_Layout">
+        <attr name="layout_gravity" />
+    </declare-styleable>
+    <declare-styleable name="RelativeLayout_Layout">
+        <!-- Positions the right edge of this view to the left of the given anchor view ID.
+             Accommodates right margin of this view and left margin of anchor view. -->
+        <attr name="layout_toLeftOf" format="reference" />
+        <!-- Positions the left edge of this view to the right of the given anchor view ID.
+            Accommodates left margin of this view and right margin of anchor view. -->
+        <attr name="layout_toRightOf" format="reference" />
+        <!-- Positions the bottom edge of this view above the given anchor view ID.
+            Accommodates bottom margin of this view and top margin of anchor view. -->
+        <attr name="layout_above" format="reference" />
+        <!-- Positions the top edge of this view below the given anchor view ID.
+            Accommodates top margin of this view and bottom margin of anchor view. -->
+        <attr name="layout_below" format="reference" />
+        <!-- Positions the baseline of this view on the baseline of the given anchor view ID. -->
+        <attr name="layout_alignBaseline" format="reference" />
+        <!-- Makes the left edge of this view match the left edge of the given anchor view ID.
+            Accommodates left margin. -->
+        <attr name="layout_alignLeft" format="reference" />
+        <!-- Makes the top edge of this view match the top edge of the given anchor view ID.
+            Accommodates top margin. -->
+        <attr name="layout_alignTop" format="reference" />
+        <!-- Makes the right edge of this view match the right edge of the given anchor view ID.
+            Accommodates right margin. -->
+        <attr name="layout_alignRight" format="reference" />
+        <!-- Makes the bottom edge of this view match the bottom edge of the given anchor view ID.
+            Accommodates bottom margin. -->
+        <attr name="layout_alignBottom" format="reference" />
+        <!-- If true, makes the left edge of this view match the left edge of the parent.
+            Accommodates left margin. -->
+        <attr name="layout_alignParentLeft" format="boolean" />
+        <!-- If true, makes the top edge of this view match the top edge of the parent.
+            Accommodates top margin. -->
+        <attr name="layout_alignParentTop" format="boolean" />
+        <!-- If true, makes the right edge of this view match the right edge of the parent.
+            Accommodates right margin. -->
+        <attr name="layout_alignParentRight" format="boolean" />
+        <!-- If true, makes the bottom edge of this view match the bottom edge of the parent.
+            Accommodates bottom margin. -->
+        <attr name="layout_alignParentBottom" format="boolean" />
+        <!-- If true, centers this child horizontally and vertically within its parent. -->
+        <attr name="layout_centerInParent" format="boolean" />
+        <!-- If true, centers this child horizontally within its parent. -->
+        <attr name="layout_centerHorizontal" format="boolean" />
+        <!-- If true, centers this child vertically within its parent. -->
+        <attr name="layout_centerVertical" format="boolean" />
+        <!-- If set to true, the parent will be used as the anchor when the anchor cannot be
+             be found for layout_toLeftOf, layout_toRightOf, etc. -->
+        <attr name="layout_alignWithParentIfMissing" format="boolean" />
+    </declare-styleable>
+    <declare-styleable name="VerticalSlider_Layout">
+        <attr name="layout_scale" format="float" />
+    </declare-styleable>
+
+    <!-- ========================= -->
+    <!-- Drawable class attributes -->
+    <!-- ========================= -->
+    <eat-comment />
+
+    <!-- Base attributes that are available to all Drawable objects. -->
+    <declare-styleable name="Drawable">
+        <!-- Provides initial visibility state of the drawable; the default
+             value is false.  See
+             {@link android.graphics.drawable.Drawable#setVisible} -->
+        <attr name="visible" format="boolean" />
+    </declare-styleable>
+    
+    <declare-styleable name="StateListDrawable">
+        <attr name="visible" />
+        <!-- If true, allows the drawable's padding to change based on the
+             current state that is selected.  If false, the padding will
+             stay the same (based on the maximum padding of all the states).
+             Enabling this feature requires that the owner of the drawable
+             deal with performing layout when the state changes, which is
+             often not supported. -->
+        <attr name="variablePadding" format="boolean" />
+        <!-- If true, the drawable's reported internal size will remain
+             constant as the state changes; the size is the maximum of all
+             of the states.  If false, the size will vary based on the
+             current state. -->
+        <attr name="constantSize" format="boolean" />
+    </declare-styleable>
+    
+    <declare-styleable name="AnimationDrawable">
+        <attr name="visible" />
+        <attr name="variablePadding" />
+        <!-- If true, the animation will only run a single time and then
+             stop.  If false (the default), it will continually run,
+             restarting at the first frame after the last has finished. -->
+        <attr name="oneshot" format="boolean" />
+    </declare-styleable>
+    
+    <declare-styleable name="AnimationDrawableItem">
+        <!-- Amount of time (in milliseconds) to display this frame. -->
+        <attr name="duration" format="integer" /> 
+        <!-- Reference to a drawable resource to use for the frame.  If not
+             given, the drawable must be defined by the first child tag. -->
+        <attr name="drawable" format="reference" />
+    </declare-styleable>
+    
+    <declare-styleable name="GradientDrawable">
+        <attr name="visible" />
+        <attr name="shape">
+            <enum name="rectangle" value="0" />
+            <enum name="oval" value="1" />
+            <enum name="line" value="2" />
+            <enum name="ring" value="3" />
+        </attr>
+        <!-- Inner radius of the ring expressed as a ratio of the ring's width. For instance,
+             if innerRadiusRatio=9, then the inner radius equals the ring's width divided by 9.
+             This value is ignored if innerRadius is defined. Default value is 9. -->
+        <attr name="innerRadiusRatio" format="float" />
+        <!-- Thickness of the ring expressed as a ratio of the ring's width. For instance,
+             if thicknessRatio=3, then the thickness equals the ring's width divided by 3.
+             This value is ignored if innerRadius is defined. Default value is 3. -->
+        <attr name="thicknessRatio" format="float" />
+        <!-- Inner radius of the ring. When defined, innerRadiusRatio is ignored. -->
+        <attr name="innerRadius" format="dimension" />
+        <!-- Thickness of the ring. When defined, thicknessRatio is ignored. -->
+        <attr name="thickness" format="dimension" />
+        <attr name="useLevel" />
+    </declare-styleable>
+    
+    <declare-styleable name="GradientDrawableSize">
+        <attr name="width" />
+        <attr name="height" />
+    </declare-styleable>
+    
+    <declare-styleable name="GradientDrawableGradient">
+        <attr name="startColor" format="color" />
+        <!-- Optional center color. For linear gradients, use centerX or centerY to place the center color. -->
+        <attr name="centerColor" format="color" />
+        <attr name="endColor" format="color" />
+        <attr name="useLevel" format="boolean" />        
+        <attr name="angle" format="float" />
+        <attr name="type">
+            <enum name="linear" value="0" />
+            <enum name="radial" value="1" />
+            <enum name="sweep"  value="2" />
+        </attr>
+        <attr name="centerX" format="float|fraction" />
+        <attr name="centerY" format="float|fraction" />
+        <attr name="gradientRadius" format="float|fraction" />
+    </declare-styleable>
+    
+    <declare-styleable name="GradientDrawableSolid">
+        <attr name="color" format="color" />
+    </declare-styleable>
+    
+    <declare-styleable name="GradientDrawableStroke">
+        <attr name="width" />
+        <attr name="color" />
+        <attr name="dashWidth" format="dimension" />
+        <attr name="dashGap" format="dimension" />
+    </declare-styleable>
+    
+    <declare-styleable name="DrawableCorners">
+        <attr name="radius" format="dimension" />
+        <attr name="topLeftRadius" format="dimension" />
+        <attr name="topRightRadius" format="dimension" />
+        <attr name="bottomLeftRadius" format="dimension" />
+        <attr name="bottomRightRadius" format="dimension" />
+    </declare-styleable>
+    
+    <declare-styleable name="GradientDrawablePadding">
+        <attr name="left" format="dimension" />
+        <attr name="top" format="dimension" />
+        <attr name="right" format="dimension" />
+        <attr name="bottom" format="dimension" />
+    </declare-styleable>
+    
+    <declare-styleable name="LayerDrawableItem">
+        <attr name="left" />
+        <attr name="top" />
+        <attr name="right" />
+        <attr name="bottom" />
+        <attr name="drawable" />
+        <attr name="id" />
+    </declare-styleable>
+    
+    <declare-styleable name="LevelListDrawableItem">
+        <attr name="minLevel" format="integer" />
+        <attr name="maxLevel" format="integer" />
+        <attr name="drawable" />
+    </declare-styleable>
+    
+    <declare-styleable name="RotateDrawable">
+        <attr name="visible" />
+        <attr name="fromDegrees" format="float" />
+        <attr name="toDegrees" format="float" />
+        <attr name="pivotX" format="float|fraction" />
+        <attr name="pivotY" format="float|fraction" />
+        <attr name="drawable" />
+    </declare-styleable>
+
+    <declare-styleable name="InsetDrawable">
+        <attr name="visible" />
+        <attr name="drawable" />
+        <attr name="insetLeft" format="dimension" />
+        <attr name="insetRight" format="dimension" />
+        <attr name="insetTop" format="dimension" />
+        <attr name="insetBottom" format="dimension" />
+    </declare-styleable>
+
+    <!-- Drawable used to draw bitmaps. -->
+    <declare-styleable name="BitmapDrawable">
+        <!-- Identifier of the bitmap file. This attribute is mandatory. -->
+        <attr name="src" />
+        <!-- Enables or disables antialiasing. -->
+        <attr name="antialias" format="boolean" />
+        <!-- Enables or disables bitmap filtering. Filtering is used when the bitmap is
+             shrunk or stretched to smooth its apperance. -->
+        <attr name="filter" format="boolean" />
+        <!-- Enables or disables dithering of the bitmap if the bitmap does not have the
+             same pixel configuration as the screen (for instance: a ARGB 8888 bitmap with
+             an RGB 565 screen.) -->
+        <attr name="dither" format="boolean" />
+        <!-- Defines the gravity for the bitmap. The gravity indicates where to position
+             the drawable in its container if the bitmap is smaller than the container. -->
+        <attr name="gravity" />
+        <!-- Defines the tile mode. When the tile mode is enabled, the bitmap is repeated.
+             Gravity is ignored when the tile mode is enabled. -->
+        <attr name="tileMode">
+            <!-- Do not tile the bitmap. This is the default value. -->
+            <enum name="disabled" value="-1" />
+            <!-- Replicates the edge color. -->
+            <enum name="clamp" value="0" />
+            <!-- Repeats the bitmap in both direction. -->
+            <enum name="repeat" value="1" />
+            <!-- Repeats the shader's image horizontally and vertically, alternating
+                 mirror images so that adjacent images always seam. -->
+            <enum name="mirror" value="2" />
+        </attr>
+    </declare-styleable>
+
+    <!-- Drawable used to draw 9-patches. -->
+    <declare-styleable name="NinePatchDrawable">
+        <!-- Identifier of the bitmap file. This attribute is mandatory. -->
+        <attr name="src" />
+        <!-- Enables or disables dithering of the bitmap if the bitmap does not have the
+             same pixel configuration as the screen (for instance: a ARGB 8888 bitmap with
+             an RGB 565 screen.) -->
+        <attr name="dither" />
+    </declare-styleable>
+
+    <!-- Drawable used to draw a single color. -->
+    <declare-styleable name="ColorDrawable">
+        <!-- The color to use. -->
+        <attr name="color" />
+    </declare-styleable>
+
+    <declare-styleable name="ScaleDrawable">
+        <!-- Scale width, expressed as a percentage of the drawable's bound. The value's
+             format is XX%. For instance: 100%, 12.5%, etc.-->
+        <attr name="scaleWidth" format="string" />
+        <!-- Scale height, expressed as a percentage of the drawable's bound. The value's
+             format is XX%. For instance: 100%, 12.5%, etc.-->
+        <attr name="scaleHeight" format="string" />
+        <!-- Specifies where the drawable is positioned after scaling. The default value is
+             left. -->
+        <attr name="scaleGravity">
+            <!-- Push object to the top of its container, not changing its size. -->
+            <flag name="top" value="0x30" />
+            <!-- Push object to the bottom of its container, not changing its size. -->
+            <flag name="bottom" value="0x50" />
+            <!-- Push object to the left of its container, not changing its size. -->
+            <flag name="left" value="0x03" />
+            <!-- Push object to the right of its container, not changing its size. -->
+            <flag name="right" value="0x05" />
+            <!-- Place object in the vertical center of its container, not changing its size. -->
+            <flag name="center_vertical" value="0x10" />
+            <!-- Grow the vertical size of the object if needed so it completely fills its container. -->
+            <flag name="fill_vertical" value="0x70" />
+            <!-- Place object in the horizontal center of its container, not changing its size. -->
+            <flag name="center_horizontal" value="0x01" />
+            <!-- Grow the horizontal size of the object if needed so it completely fills its container. -->
+            <flag name="fill_horizontal" value="0x07" />
+            <!-- Place the object in the center of its container in both the vertical and horizontal axis, not changing its size. -->
+            <flag name="center" value="0x11" />
+            <!-- Grow the horizontal and vertical size of the object if needed so it completely fills its container. -->
+            <flag name="fill" value="0x77" />
+            <!-- Additional option that can be set to have the top and/or bottom edges of
+                 the child clipped to its container's bounds.
+                 The clip will be based on the vertical gravity: a top gravity will clip the bottom
+                 edge, a bottom gravity will clip the top edge, and neither will clip both edges. -->
+            <flag name="clip_vertical" value="0x80" />
+            <!-- Additional option that can be set to have the left and/or right edges of
+                 the child clipped to its container's bounds.
+                 The clip will be based on the horizontal gravity: a left gravity will clip the right
+                 edge, a right gravity will clip the left edge, and neither will clip both edges. -->
+            <flag name="clip_horizontal" value="0x08" />
+        </attr>
+        <!-- Reference to a drawable resource to draw with the specified scale. -->
+        <attr name="drawable" />
+    </declare-styleable>
+
+    <declare-styleable name="ClipDrawable">
+        <!-- The orientation for the clip. -->
+        <attr name="clipOrientation">
+            <!-- Clip the drawable horizontally. -->
+            <flag name="horizontal" value="1" />
+            <!-- Clip the drawable vertically. -->
+            <flag name="vertical" value="2" />
+        </attr>
+        <!-- Specifies where to clip within the drawable. The default value is
+             left. -->
+        <attr name="gravity" />
+        <!-- Reference to a drawable resource to draw with the specified scale. -->
+        <attr name="drawable" />
+    </declare-styleable>
+
+    <!-- Defines the padding of a ShapeDrawable. -->
+    <declare-styleable name="ShapeDrawablePadding">
+        <!-- Left padding. -->
+        <attr name="left" />
+        <!-- Top padding. -->
+        <attr name="top" />
+        <!-- Right padding. -->
+        <attr name="right" />
+        <!-- Bottom padding. -->
+        <attr name="bottom" />
+    </declare-styleable>
+
+    <!-- Drawable used to draw shapes. -->
+    <declare-styleable name="ShapeDrawable">
+        <!-- Defines the color of the shape. -->
+        <attr name="color" />
+        <!-- Defines the width of the shape. -->
+        <attr name="width" />
+        <!-- Defines the height of the shape. -->
+        <attr name="height" />
+    </declare-styleable>
+
+    <!-- ========================== -->
+    <!-- Animation class attributes -->
+    <!-- ========================== -->
+    <eat-comment />
+
+    <declare-styleable name="AnimationSet">
+        <attr name="shareInterpolator" format="boolean" />
+    </declare-styleable>
+    
+    <declare-styleable name="Animation">
+        <!-- Defines the interpolator used to smooth the animation movement in time. -->
+        <attr name="interpolator" />
+        <!-- When set to true, fillAfter is taken into account. -->
+        <attr name="fillEnabled" format="boolean" />
+        <!-- When set to true, the animation transformation is applied before the animation has
+             started. The default value is true. If fillEnabled is not set to true, fillBefore
+             is assumed to be true. -->
+        <attr name="fillBefore" format="boolean" />
+        <!-- When set to true, the animation transformation is applied after the animation is
+             over. The default value is false. If fillEnabled is not set to true and the animation
+             is not set on a View, fillAfter is assumed to be true. -->
+        <attr name="fillAfter" format="boolean" />
+        <!-- Amount of time (in milliseconds) for the animation to run. -->
+        <attr name="duration" />
+        <!-- Delay in milliseconds before the animation runs, once start time is reached. -->
+        <attr name="startOffset" format="integer" />
+        <!-- Defines how many times the animation should repeat. The default value is 0. -->
+        <attr name="repeatCount" format="integer">
+            <enum name="infinite" value="-1" />
+        </attr>
+        <!-- Defines the animation behavior when it reaches the end and the repeat count is
+             greater than 0 or infinite. The default value is restart. -->
+        <attr name="repeatMode">
+            <!-- The animation starts again from the beginning. -->
+            <enum name="restart" value="1" />
+            <!-- The animation plays backward. -->
+            <enum name="reverse" value="2" />
+        </attr>
+        <!-- Allows for an adjustment of the Z ordering of the content being
+             animated for the duration of the animation.  The default value is normal. -->
+        <attr name="zAdjustment">
+            <!-- The content being animated be kept in its current Z order. -->
+            <enum name="normal" value="0" />
+            <!-- The content being animated is forced on top of all other
+                 content for the duration of the animation. -->
+            <enum name="top" value="1" />
+            <!-- The content being animated is forced under all other
+                 content for the duration of the animation. -->
+            <enum name="bottom" value="-1" />
+        </attr>
+    </declare-styleable>
+    
+    <declare-styleable name="RotateAnimation">
+        <attr name="fromDegrees" />
+        <attr name="toDegrees" />
+        <attr name="pivotX" />
+        <attr name="pivotY" />
+    </declare-styleable>
+    
+    <declare-styleable name="ScaleAnimation">
+        <attr name="fromXScale" format="float" />
+        <attr name="toXScale" format="float" />
+        <attr name="fromYScale" format="float" />
+        <attr name="toYScale" format="float" />
+        <attr name="pivotX" />
+        <attr name="pivotY" />
+    </declare-styleable>
+    
+    <declare-styleable name="TranslateAnimation">
+        <attr name="fromXDelta" format="float|fraction" />
+        <attr name="toXDelta" format="float|fraction" />
+        <attr name="fromYDelta" format="float|fraction" />
+        <attr name="toYDelta" format="float|fraction" />
+    </declare-styleable>
+    
+    <declare-styleable name="AlphaAnimation">
+        <attr name="fromAlpha" format="float" />
+        <attr name="toAlpha" format="float" />
+    </declare-styleable>
+
+    <declare-styleable name="LayoutAnimation">
+        <!-- Fraction of the animation duration used to delay the beginning of
+         the animation of each child. -->
+        <attr name="delay" format="float|fraction" />
+        <!-- Animation to use on each child. -->
+        <attr name="animation" format="reference" />
+        <!-- The order in which the animations will be started. -->
+        <attr name="animationOrder">
+            <!-- Animations are started in the natural order. -->
+            <enum name="normal" value="0" />
+            <!-- Animations are started in the reverse order. -->
+            <enum name="reverse" value="1" />
+            <!-- Animations are started randomly. -->
+            <enum name="random" value="2" />
+        </attr>
+        <!-- Interpolator used to interpolate the delay between the start of
+         each animation. -->
+        <attr name="interpolator" />
+    </declare-styleable>
+
+    <declare-styleable name="GridLayoutAnimation">
+        <!-- Fraction of the animation duration used to delay the beginning of
+         the animation of each column. -->
+        <attr name="columnDelay" format="float|fraction" />
+        <!-- Fraction of the animation duration used to delay the beginning of
+         the animation of each row. -->
+        <attr name="rowDelay" format="float|fraction" />
+        <!-- Direction of the animation in the grid. -->
+        <attr name="direction">
+            <!-- Animates columns from left to right. -->
+            <flag name="left_to_right" value="0x0" />
+            <!-- Animates columns from right to left. -->
+            <flag name="right_to_left" value="0x1" />
+            <!-- Animates rows from top to bottom. -->
+            <flag name="top_to_bottom" value="0x0" />
+            <!-- Animates rows from bottom to top. -->
+            <flag name="bottom_to_top" value="0x2" />
+        </attr>
+        <!-- Priority of the rows and columns. When the priority is none,
+         both rows and columns have the same priority. When the priority is
+         column, the animations will be applied on the columns first. The same
+         goes for rows. -->
+        <attr name="directionPriority">
+            <!-- Rows and columns are animated at the same time. -->
+            <enum name="none"   value="0" />
+            <!-- Columns are animated first. -->
+            <enum name="column" value="1" />
+            <!-- Rows are animated first. -->
+            <enum name="row"    value="2" />
+        </attr>
+    </declare-styleable>
+
+    <declare-styleable name="AccelerateInterpolator">
+        <!-- This is the amount of deceleration to ad when easing in. -->
+        <attr name="factor" format="float" />
+    </declare-styleable>
+    
+    <declare-styleable name="DecelerateInterpolator">
+        <!-- This is the amount of acceleration to ad when easing out. -->
+        <attr name="factor" />
+    </declare-styleable>
+    
+    <declare-styleable name="CycleInterpolator">
+        <attr name="cycles" format="float" />
+    </declare-styleable>
+
+    <!-- ========================== -->
+    <!-- State attributes           -->
+    <!-- ========================== -->
+    <eat-comment />
+
+    <!-- Drawable states.
+         The mapping of Drawable states to a particular drawables is specified
+         in the "state" elements of a Widget's "selector" element.
+         Possible values:
+         <ul>
+         <li>"state_focused"
+         <li>"state_window_focused"
+         <li>"state_enabled"
+         <li>"state_checked"
+         <li>"state_selected"
+         <li>"state_active"
+         <li>"state_single"
+         <li>"state_first"
+         <li>"state_mid"
+         <li>"state_last"
+         <li>"state_only"
+         <li>"state_pressed"
+         <li>"state_error"
+         <li>"state_circle"
+         <li>"state_rect"
+         <li>"state_grow"
+         <li>"state_move"
+         </ul>  -->
+    <declare-styleable name="DrawableStates">
+        <!--  State value for {@link android.graphics.drawable.StateListDrawable StateListDrawable}.-->
+        <attr name="state_focused" format="boolean" />
+        <!--  State value for {@link android.graphics.drawable.StateListDrawable StateListDrawable}.-->
+        <attr name="state_window_focused" format="boolean" />
+        <!--  State value for {@link android.graphics.drawable.StateListDrawable StateListDrawable}.-->
+        <attr name="state_enabled" format="boolean" />
+        <!--  State identifier indicating that the object <var>may</var> display a check mark.
+              See {@link R.attr#state_checked} for the identifier that indicates whether it is
+              actually checked. -->
+        <attr name="state_checkable" format="boolean"/>
+        <!--  State identifier indicating that the object is currently checked.  See
+              {@link R.attr#state_checkable} for an additional identifier that can indicate if
+              any object may ever display a check, regardless of whether state_checked is
+              currently set. -->
+        <attr name="state_checked" format="boolean"/>
+        <!--  State value for {@link android.graphics.drawable.StateListDrawable StateListDrawable}.-->
+        <attr name="state_selected" format="boolean" />
+        <!--  State value for {@link android.graphics.drawable.StateListDrawable StateListDrawable}.-->
+        <attr name="state_active" format="boolean" />
+        <!--  State value for {@link android.graphics.drawable.StateListDrawable StateListDrawable}.-->
+        <attr name="state_single" format="boolean" />
+        <!--  State value for {@link android.graphics.drawable.StateListDrawable StateListDrawable}.-->
+        <attr name="state_first" format="boolean" />
+        <!--  State value for {@link android.graphics.drawable.StateListDrawable StateListDrawable}.-->
+        <attr name="state_middle" format="boolean" />
+        <!--  State value for {@link android.graphics.drawable.StateListDrawable StateListDrawable}.-->
+        <attr name="state_last" format="boolean" />
+        <!--  State value for {@link android.graphics.drawable.StateListDrawable StateListDrawable}.-->
+        <attr name="state_pressed" format="boolean" />
+    </declare-styleable>
+    <declare-styleable name="ViewDrawableStates">
+        <attr name="state_pressed" />
+        <attr name="state_focused" />
+        <attr name="state_selected" />
+        <attr name="state_window_focused" />
+        <attr name="state_enabled" />
+    </declare-styleable>
+    <!-- State array representing a menu item that is currently checked. -->
+    <declare-styleable name="MenuItemCheckedState">
+        <attr name="state_checkable" />
+        <attr name="state_checked" />
+    </declare-styleable>
+    <!-- State array representing a menu item that is checkable but is not currently checked. -->
+    <declare-styleable name="MenuItemUncheckedState">
+        <attr name="state_checkable" />
+    </declare-styleable>
+    <!-- State array representing a menu item that is currently focused and checked. -->
+    <declare-styleable name="MenuItemCheckedFocusedState">
+        <attr name="state_checkable" />
+        <attr name="state_checked" />
+        <attr name="state_focused" />
+    </declare-styleable>
+    <!-- State array representing a menu item that is focused and checkable but is not currently checked. -->
+    <declare-styleable name="MenuItemUncheckedFocusedState">
+        <attr name="state_checkable" />
+        <attr name="state_focused" />
+    </declare-styleable>
+    <!-- State array representing an expandable list child's indicator. -->
+    <declare-styleable name="ExpandableListChildIndicatorState">
+        <!-- State identifier indicating the child is the last child within its group. --> 
+        <attr name="state_last" />
+    </declare-styleable>
+    <!-- State array representing an expandable list group's indicator. -->
+    <declare-styleable name="ExpandableListGroupIndicatorState">
+        <!-- State identifier indicating the group is expanded. --> 
+        <attr name="state_expanded" format="boolean" />
+        <!-- State identifier indicating the group is empty (has no children). --> 
+        <attr name="state_empty" format="boolean" />
+    </declare-styleable>
+    <declare-styleable name="PopupWindowBackgroundState">
+        <!-- State identifier indicating the popup will be above the anchor. --> 
+        <attr name="state_above_anchor" format="boolean" />
+    </declare-styleable>
+
+    <!-- ***************************************************************** -->
+    <!-- Support for Searchable activities. -->
+    <!-- ***************************************************************** -->
+    <eat-comment />
+
+    <!-- Searchable activities and applications must provide search configuration information
+        in an XML file, typically called searchable.xml.  This file is referenced in your manifest. 
+        For a more in-depth discussion of search configuration, please refer to
+        {@link android.app.SearchManager}. -->
+    <declare-styleable name="Searchable">
+        <!-- If provided, this icon will be shown in place of the label.  It is typically used
+             in order to identify a searchable application via a logo or branding, instead of
+             plain text.  This is a reference to a drawable (icon) resource.
+             <i>Optional attribute.</i> -->
+        <attr name="icon" />
+        <!-- This is the user-displayed name of the searchable activity.  <i>Required 
+            attribute.</i> -->
+        <attr name="label" />
+        <!-- If supplied, this string will be displayed as a hint to the user.  <i>Optional 
+            attribute.</i> -->
+        <attr name="hint" />
+        <!-- If supplied, this string will be displayed as the text of the "Search" button.
+          <i>Optional attribute.</i> 
+          {@deprecated This will create a non-standard UI appearance, because the search bar UI is
+                       changing to use only icons for its buttons.}-->
+        <attr name="searchButtonText" format="string" />
+        <attr name="inputType" />
+        <attr name="imeOptions" />
+        
+        <!-- Additional features are controlled by mode bits in this field.  Omitting
+            this field, or setting to zero, provides default behavior.  <i>Optional attribute.</i> 
+        -->
+        <attr name="searchMode">
+          <!-- If set, this flag enables the display of the search target (label) within the
+               search bar.  If neither bad mode is selected, no badge will be shown. -->
+          <flag name="showSearchLabelAsBadge" value="0x04" />
+          <!-- If set, this flag enables the display of the search target (icon) within the
+               search bar.  (Note, overrides showSearchLabel)  If neither bad mode is selected, 
+               no badge will be shown.-->
+          <flag name="showSearchIconAsBadge" value="0x08" />
+          <!-- If set, this flag causes the suggestion column SUGGEST_COLUMN_INTENT_DATA to
+               be considered as the text for suggestion query rewriting.  This should only
+               be used when the values in SUGGEST_COLUMN_INTENT_DATA are suitable for user
+               inspection and editing - typically, HTTP/HTTPS Uri's. -->
+          <flag name="queryRewriteFromData" value="0x10" />
+          <!-- If set, this flag causes the suggestion column SUGGEST_COLUMN_TEXT_1 to
+               be considered as the text for suggestion query rewriting.  This should be used
+               for suggestions in which no query text is provided and the SUGGEST_COLUMN_INTENT_DATA
+               values are not suitable for user inspection and editing. -->
+          <flag name="queryRewriteFromText" value="0x20" />
+        </attr>
+        
+        <!-- Voice search features are controlled by mode bits in this field.  Omitting
+            this field, or setting to zero, provides default behavior.
+            If showVoiceSearchButton is set, then launchWebSearch or launchRecognizer must
+            also be set.  <i>Optional attribute.</i> 
+        -->
+        <attr name="voiceSearchMode">
+          <!-- If set, display a voice search button.  This only takes effect if voice search is
+               available on the device. -->
+          <flag name="showVoiceSearchButton" value="0x01" />
+          <!-- If set, the voice search button will take the user directly to a built-in
+               voice web search activity.  Most applications will not use this flag, as it
+               will take the user away from your searchable activity. -->
+          <flag name="launchWebSearch" value="0x02" />
+          <!-- If set, the voice search button will take the user directly to a built-in
+               voice recording activity.  This activity will prompt the user to speak,
+               transcribe the spoke text, and forward the resulting query text to your
+               searchable activity, just as if the user had typed it into the search UI. -->
+          <flag name="launchRecognizer" value="0x04" />
+        </attr>
+
+        <!-- If provided, this specifies the language model that should be used by the
+             voice recognition system.  See 
+             {@link android.speech.RecognizerIntent#EXTRA_LANGUAGE_MODEL } for more information. 
+             If not provided, the default value 
+             {@link android.speech.RecognizerIntent#LANGUAGE_MODEL_FREE_FORM } will be used. -->
+        <attr name="voiceLanguageModel" format="string" />
+        <!-- If provided, this specifies a prompt that will be displayed during voice input. -->
+        <attr name="voicePromptText" format="string" />
+        <!-- If provided, this specifies the spoken language to be expected, and that it will be
+             different than the one set in the {@link java.util.Locale#getDefault()}. -->
+        <attr name="voiceLanguage" format="string" />
+        <!-- If provided, enforces the maximum number of results to return, including the "best"
+             result which will always be provided as the SEARCH intent's primary query.  Must be one
+             or greater.  If not provided, the recognizer will choose how many results to return. 
+             -->
+        <attr name="voiceMaxResults" format="integer" />
+
+        <!-- If provided, this is the trigger indicating that the searchable activity
+            provides suggestions as well.  The value must be a fully-qualified content provider
+            authority (e.g. "com.example.android.apis.SuggestionProvider") and should match the 
+            "android:authorities" tag in your content provider's manifest entry.  <i>Optional 
+            attribute.</i> -->
+        <attr name="searchSuggestAuthority" format="string" />
+        <!-- If provided, this will be inserted in the suggestions query Uri, after the authority
+            you have provide but before the standard suggestions path. <i>Optional attribute.</i>
+            -->
+        <attr name="searchSuggestPath" format="string" />
+        <!-- If provided, suggestion queries will be passed into your query function
+            as the <i>selection</i> parameter.  Typically this will be a WHERE clause for your 
+            database, and will contain a single question mark, which represents the actual query 
+            string that has been typed by the user.  If not provided, then the user query text
+            will be appended to the query Uri (after an additional "/".)  <i>Optional 
+            attribute.</i> -->
+        <attr name="searchSuggestSelection" format="string" />
+
+        <!-- If provided, and not overridden by an action in the selected suggestion, this 
+            string will be placed in the action field of the {@link android.content.Intent Intent}
+            when the user clicks a suggestion.  <i>Optional attribute.</i> -->
+        <attr name="searchSuggestIntentAction" format="string" />
+        <!-- If provided, and not overridden by an action in the selected suggestion, this 
+            string will be placed in the data field of the {@link android.content.Intent Intent} 
+            when the user clicks a suggestion.  <i>Optional attribute.</i> -->
+        <attr name="searchSuggestIntentData" format="string" />
+        
+    </declare-styleable>
+
+    <!-- In order to process special action keys during search, you must define them using
+            one or more "ActionKey" elements in your Searchable metadata.  For a more in-depth
+            discussion of action code handling, please refer to {@link android.app.SearchManager}.
+    -->
+    <declare-styleable name="SearchableActionKey">
+        <!-- This attribute denotes the action key you wish to respond to.  Note that not
+            all action keys are actually supported using this mechanism, as many of them are
+            used for typing, navigation, or system functions.  This will be added to the 
+            {@link android.content.Intent#ACTION_SEARCH ACTION_SEARCH} intent that is passed to your
+            searchable activity.  To examine the key code, use 
+            {@link android.content.Intent#getIntExtra getIntExtra(SearchManager.ACTION_KEY)}.
+            <p>Note, in addition to the keycode, you must also provide one or more of the action 
+            specifier attributes.  <i>Required attribute.</i> -->
+        <attr name="keycode" />
+        
+        <!-- If you wish to handle an action key during normal search query entry, you
+            must define an action string here.  This will be added to the 
+            {@link android.content.Intent#ACTION_SEARCH ACTION_SEARCH} intent that is passed to your
+            searchable activity.  To examine the string, use 
+            {@link android.content.Intent#getStringExtra getStringExtra(SearchManager.ACTION_MSG)}.
+            <i>Optional attribute.</i> -->
+        <attr name="queryActionMsg"  format="string" />
+        
+        <!-- If you wish to handle an action key while a suggestion is being displayed <i>and
+            selected</i>, there are two ways to handle this.  If <i>all</i> of your suggestions
+            can handle the action key, you can simply define the action message using this 
+            attribute.  This will be added to the 
+            {@link android.content.Intent#ACTION_SEARCH ACTION_SEARCH} intent that is passed to your
+            searchable activity.  To examine the string, use 
+            {@link android.content.Intent#getStringExtra getStringExtra(SearchManager.ACTION_MSG)}.
+            <i>Optional attribute.</i> -->
+        <attr name="suggestActionMsg"  format="string" />
+        
+        <!-- If you wish to handle an action key while a suggestion is being displayed <i>and
+            selected</i>, but you do not wish to enable this action key for every suggestion, 
+            then you can use this attribute to control it on a suggestion-by-suggestion basis.
+            First, you must define a column (and name it here) where your suggestions will include 
+            the action string.  Then, in your content provider, you must provide this column, and
+            when desired, provide data in this column.
+            The search manager will look at your suggestion cursor, using the string 
+            provided here in order to select a column, and will use that to select a string from 
+            the cursor.  That string will be added to the 
+            {@link android.content.Intent#ACTION_SEARCH ACTION_SEARCH} intent that is passed to 
+            your searchable activity.  To examine the string, use 
+            {@link android.content.Intent#getStringExtra 
+            getStringExtra(SearchManager.ACTION_MSG)}.  <i>If the data does not exist for the
+            selection suggestion, the action key will be ignored.</i><i>Optional attribute.</i> -->
+        <attr name="suggestActionMsgColumn" format="string" />
+
+    </declare-styleable>
+    
+    <!-- ***************************************************************** -->
+    <!-- Support for MapView. -->
+    <!-- ***************************************************************** -->
+    <eat-comment />
+    
+    <!-- The set of attributes for a MapView. -->
+    <declare-styleable name="MapView">
+        <!-- Value is a string that specifies the Maps API Key to use. -->
+        <attr name="apiKey" format="string" />
+    </declare-styleable>
+    
+    <!-- **************************************************************** -->
+    <!-- Menu XML inflation. -->
+    <!-- **************************************************************** -->
+    <eat-comment />
+    
+    <!-- Base attributes that are available to all Menu objects. -->
+    <declare-styleable name="Menu">
+    </declare-styleable>
+    
+    <!-- Base attributes that are available to all groups. -->
+    <declare-styleable name="MenuGroup">
+
+        <!-- The ID of the group. -->
+        <attr name="id" />
+
+        <!-- The category applied to all items within this group.
+             (This will be or'ed with the orderInCategory attribute.) -->
+        <attr name="menuCategory">
+            <!-- Items are part of a container. -->
+            <enum name="container" value="0x00010000" />
+            <!-- Items are provided by the system. -->
+            <enum name="system" value="0x00020000" />
+            <!-- Items are user-supplied secondary (infrequently used). -->
+            <enum name="secondary" value="0x00030000" />
+            <!-- Items are alternative actions. -->
+            <enum name="alternative" value="0x00040000" />
+        </attr>
+        
+        <!-- The order within the category applied to all items within this group.
+             (This will be or'ed with the category attribute.) -->
+        <attr name="orderInCategory" format="integer" />
+        
+        <!-- Whether the items are capable of displaying a check mark. -->
+        <attr name="checkableBehavior">
+            <!-- The items are not checkable. -->
+            <enum name="none" value="0" />
+            <!-- The items are all checkable. -->
+            <enum name="all" value="1" />
+            <!-- The items are checkable and there will only be a single checked item in
+                 this group. -->
+            <enum name="single" value="2" />
+        </attr>
+                
+        <!-- Whether the items are shown/visible. -->
+        <attr name="visible" />
+
+        <!-- Whether the items are enabled. -->
+        <attr name="enabled" />
+
+    </declare-styleable>
+
+    <!-- Base attributes that are available to all Item objects. -->
+    <declare-styleable name="MenuItem">
+
+        <!-- The ID of the item. -->
+        <attr name="id" />
+        
+        <!-- The category applied to the item.
+             (This will be or'ed with the orderInCategory attribute.) -->
+        <attr name="menuCategory" />
+
+        <!-- The order within the category applied to the item.
+             (This will be or'ed with the category attribute.) -->
+        <attr name="orderInCategory" />
+
+        <!-- The title associated with the item. -->
+        <attr name="title" format="string" />
+        
+        <!-- The condensed title associated with the item.  This is used in situations where the
+             normal title may be too long to be displayed. -->
+        <attr name="titleCondensed" format="string" />
+
+        <!-- The icon associated with this item.  This icon will not always be shown, so
+             the title should be sufficient in describing this item. -->        
+        <attr name="icon" />
+        
+        <!-- The alphabetic shortcut key.  This is the shortcut when using a keyboard
+             with alphabetic keys. -->
+        <attr name="alphabeticShortcut" format="string" />
+
+        <!-- The numeric shortcut key.  This is the shortcut when using a numeric (e.g., 12-key)
+             keyboard. -->
+        <attr name="numericShortcut" format="string" />
+        
+        <!-- Whether the item is capable of displaying a check mark. -->
+        <attr name="checkable" format="boolean" />
+        
+        <!-- Whether the item is checked.  Note that you must first have enabled checking with 
+             the checkable attribute or else the check mark will not appear. -->
+        <attr name="checked" />
+        
+        <!-- Whether the item is shown/visible. -->
+        <attr name="visible" />
+
+        <!-- Whether the item is enabled. -->
+        <attr name="enabled" />
+
+    </declare-styleable>
+
+    <!-- **************************************************************** -->
+    <!-- Preferences framework. -->
+    <!-- **************************************************************** -->
+    <eat-comment />
+
+    <!-- Base attributes available to PreferenceGroup. -->
+    <declare-styleable name="PreferenceGroup">
+        <!-- Whether to order the Preference under this group as they appear in the XML file.
+             If this is false, the ordering will follow the Preference order attribute and
+             default to alphabetic for those without the order attribute. -->
+        <attr name="orderingFromXml" format="boolean" />
+    </declare-styleable>
+
+    <!-- WARNING:  If adding attributes to Preference, make sure it does not conflict
+                   with a View's attributes.  Some subclasses (e.g., EditTextPreference)
+                   proxy all attributes to its EditText widget. -->
+    <eat-comment />
+    
+    <!-- Base attributes available to Preference. -->
+    <declare-styleable name="Preference">
+        <!-- The key to store the Preference value. -->
+        <attr name="key" format="string" />
+        <!-- The title for the Preference in a PreferenceActivity screen. -->
+        <attr name="title" />
+        <!-- The summary for the Preference in a PreferenceActivity screen. -->
+        <attr name="summary" format="string" />
+        <!-- The order for the Preference (lower values are to be ordered first). If this is not
+             specified, the default orderin will be alphabetic. -->
+        <attr name="order" format="integer" />
+        <!-- The layout for the Preference in a PreferenceActivity screen. This should
+             rarely need to be changed, look at widgetLayout instead. -->
+        <attr name="layout" />
+        <!-- The layout for the controllable widget portion of a Preference. This is inflated
+             into the layout for a Preference and should be used more frequently than
+             the layout attribute. For example, a checkbox preference would specify
+             a custom layout (consisting of just the CheckBox) here. -->
+        <attr name="widgetLayout" format="reference" />
+        <!-- Whether the Preference is enabled. -->
+        <attr name="enabled" />
+        <!-- Whether the Preference is selectable. -->
+        <attr name="selectable" format="boolean" />
+        <!-- The key of another Preference that this Preference will depend on.  If the other
+             Preference is not set or is off, this Preference will be disabled. -->
+        <attr name="dependency" format="string" />
+        <!-- Whether the Preference stores its value to the shared preferences. -->
+        <attr name="persistent" />
+        <!-- The default value for the preference, which will be set either if persistence
+             is off or persistence is on and the preference is not found in the persistent
+             storage.  -->
+        <attr name="defaultValue" format="string|boolean|integer|reference|float" />
+        <!-- Whether the view of this Preference should be disabled when
+             this Preference is disabled. -->
+        <attr name="shouldDisableView" format="boolean" />
+    </declare-styleable>
+
+    <!-- Base attributes available to CheckBoxPreference. -->
+    <declare-styleable name="CheckBoxPreference">
+        <!-- The summary for the Preference in a PreferenceActivity screen when the
+             CheckBoxPreference is checked. If separate on/off summaries are not
+             needed, the summary attribute can be used instead. -->
+        <attr name="summaryOn" format="string" />
+        <!-- The summary for the Preference in a PreferenceActivity screen when the
+             CheckBoxPreference is unchecked. If separate on/off summaries are not
+             needed, the summary attribute can be used instead. -->
+        <attr name="summaryOff" format="string" />
+        <!-- The state (true for on, or false for off) that causes dependents to be disabled. By default,
+             dependents will be disabled when this is unchecked, so the value of this preference is false. -->
+        <attr name="disableDependentsState" format="boolean" />
+    </declare-styleable>
+
+    <!-- Base attributes available to DialogPreference. -->
+    <declare-styleable name="DialogPreference">
+        <!-- The title in the dialog. -->
+        <attr name="dialogTitle" format="string" />
+        <!-- The message in the dialog. If a dialogLayout is provided and contains
+             a TextView with ID android:id/message, this message will be placed in there. -->
+        <attr name="dialogMessage" format="string" />
+        <!-- The icon for the dialog. -->
+        <attr name="dialogIcon" format="reference" />
+        <!-- The positive button text for the dialog. Set to @null to hide the positive button. -->
+        <attr name="positiveButtonText" format="string" />
+        <!-- The negative button text for the dialog. Set to @null to hide the negative button. -->
+        <attr name="negativeButtonText" format="string" />
+        <!-- A layout to be used as the content View for the dialog. By default, this shouldn't
+             be needed. If a custom DialogPreference is required, this should be set. For example,
+             the EditTextPreference uses a layout with an EditText as this attribute. -->
+        <attr name="dialogLayout" format="reference" />
+    </declare-styleable>
+
+    <!-- Base attributes available to ListPreference. -->
+    <declare-styleable name="ListPreference">
+        <!-- The human-readable array to present as a list. Each entry must have a corresponding
+             index in entryValues. -->
+        <attr name="entries" />
+        <!-- The array to find the value to save for a preference when an entry from
+             entries is selected. If a user clicks on the second item in entries, the
+             second item in this array will be saved to the preference. -->
+        <attr name="entryValues" format="reference" />
+    </declare-styleable>
+
+    <!-- Base attributes available to RingtonePreference. -->
+    <declare-styleable name="RingtonePreference">
+        <!-- Which ringtone type(s) to show in the picker. -->
+        <attr name="ringtoneType">
+            <!-- Ringtones. -->
+            <flag name="ringtone" value="1" />
+            <!-- Notification sounds. -->
+            <flag name="notification" value="2" />
+            <!-- Alarm sounds. -->
+            <flag name="alarm" value="4" />
+            <!-- All available ringtone sounds. -->
+            <flag name="all" value="7" />
+        </attr>
+        <!-- Whether to show an item for a default sound. -->
+        <attr name="showDefault" format="boolean" />
+        <!-- Whether to show an item for 'Silent'. -->
+        <attr name="showSilent" format="boolean" />
+    </declare-styleable>
+
+    <!-- Base attributes available to VolumePreference. -->
+    <declare-styleable name="VolumePreference">
+        <!-- Different audio stream types. -->
+        <attr name="streamType">
+            <enum name="voice" value="0" />
+            <enum name="system" value="1" />
+            <enum name="ring" value="2" />
+            <enum name="music" value="3" />
+            <enum name="alarm" value="4" />
+        </attr>
+    </declare-styleable>
+
+    <declare-styleable name="KeyboardView">
+        <!-- Default KeyboardView style. -->
+        <attr name="keyboardViewStyle" format="reference" />
+
+        <!-- Image for the key. This image needs to be a StateListDrawable, with the following
+             possible states: normal, pressed, checkable, checkable+pressed, checkable+checked,
+             checkable+checked+pressed. -->
+        <attr name="keyBackground" format="reference" />
+
+        <!-- Size of the text for character keys. -->
+        <attr name="keyTextSize" format="dimension" />
+
+        <!-- Size of the text for custom keys with some text and no icon. -->
+        <attr name="labelTextSize" format="dimension" />
+
+        <!-- Color to use for the label in a key -->
+        <attr name="keyTextColor" format="color" />
+
+        <!-- Layout resource for key press feedback.-->
+        <attr name="keyPreviewLayout" format="reference" />
+
+        <!-- Vertical offset of the key press feedback from the key. -->
+        <attr name="keyPreviewOffset" format="dimension" />
+
+        <!-- Height of the key press feedback popup. -->
+        <attr name="keyPreviewHeight" format="dimension" />
+
+        <!-- Amount to offset the touch Y coordinate by, for bias correction. -->
+        <attr name="verticalCorrection" format="dimension" />
+
+        <!-- Layout resource for popup keyboards -->
+        <attr name="popupLayout" format="reference" />
+        
+        <attr name="shadowColor" />
+        <attr name="shadowRadius" />
+    </declare-styleable>
+    
+    <declare-styleable name="KeyboardViewPreviewState">
+        <!-- State for {@link android.inputmethodservice.KeyboardView KeyboardView} 
+                key preview background -->
+        <attr name="state_long_pressable" format="boolean" />
+    </declare-styleable>
+
+    <declare-styleable name="Keyboard">
+        <!-- Default width of a key, in pixels or percentage of display width -->
+        <attr name="keyWidth" format="dimension|fraction" />
+        <!-- Default height of a key, in pixels or percentage of display width -->
+        <attr name="keyHeight" format="dimension|fraction" />
+        <!-- Default horizontal gap between keys -->
+        <attr name="horizontalGap" format="dimension|fraction" />
+        <!-- Default vertical gap between rows of keys -->
+        <attr name="verticalGap" format="dimension|fraction" />
+    </declare-styleable>
+
+    <declare-styleable name="Keyboard_Row">
+        <!-- Row edge flags-->
+        <attr name="rowEdgeFlags">
+            <!-- Row is anchored to the top of the keyboard -->
+            <flag name="top" value="4" />
+            <!-- Row is anchored to the bottom of the keyboard -->
+            <flag name="bottom" value="8" />
+        </attr>
+        <!-- Mode of the keyboard. If the mode doesn't match the
+             requested keyboard mode, the row will be skipped -->
+        <attr name="keyboardMode" format="reference" />
+    </declare-styleable>
+
+    <declare-styleable name="Keyboard_Key">
+        <!-- The unicode value or comma-separated values that this key outputs -->
+        <attr name="codes" format="integer|string" />
+        <!-- The XML keyboard layout of any popup keyboard -->
+        <attr name="popupKeyboard" format="reference" />
+        <!-- The characters to display in the popup keyboard -->
+        <attr name="popupCharacters" format="string" />
+        <!-- Key edge flags -->
+        <attr name="keyEdgeFlags">
+            <!-- Key is anchored to the left of the keyboard -->
+            <flag name="left" value="1" />
+            <!-- Key is anchored to the right of the keyboard -->
+            <flag name="right" value="2" />
+        </attr>
+        <!-- Whether this is a modifier key such as Alt or Shift -->
+        <attr name="isModifier" format="boolean" />
+        <!-- Whether this is a toggle key -->
+        <attr name="isSticky" format="boolean" />
+        <!-- Whether long-pressing on this key will make it repeat -->
+        <attr name="isRepeatable" format="boolean" />
+        <!-- The icon to show in the popup preview -->
+        <attr name="iconPreview" format="reference" />
+        <!-- The string of characters to output when this key is pressed -->
+        <attr name="keyOutputText" format="string" />
+        <!-- The label to display on the key -->
+        <attr name="keyLabel" format="string" />
+        <!-- The icon to display on the key instead of the label -->
+        <attr name="keyIcon" format="reference" />
+        <!-- Mode of the keyboard. If the mode doesn't match the
+             requested keyboard mode, the key will be skipped -->
+        <attr name="keyboardMode" />
+    </declare-styleable>
+
+    <!-- =============================== -->
+    <!-- Gadget package class attributes -->
+    <!-- =============================== -->
+
+    <!-- Use <code>gadget-provider</code> as the root tag of the XML resource that
+         describes a gadget provider.  See TODO android.gadget package
+         for more info.
+     -->
+    <declare-styleable name="GadgetProviderInfo">
+        <!-- Minimum width of the gadget. -->
+        <attr name="minWidth"/>
+        <!-- Minimum height of the gadget. -->
+        <attr name="minHeight"/>
+        <!-- Update period in milliseconds, or 0 if the gadget will update itself. -->
+        <attr name="updatePeriodMillis" format="integer" />
+        <!-- A resource id of a layout. -->
+        <attr name="initialLayout" format="reference" />
+        <!-- A class name in the gadget's package to be launched to configure.
+             If not supplied, then no activity will be launched. -->
+        <attr name="configure" format="string" />
+    </declare-styleable>
+    
+    
+</resources>
+
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
new file mode 100644
index 0000000..2ff0962
--- /dev/null
+++ b/core/res/res/values/attrs_manifest.xml
@@ -0,0 +1,1171 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* Copyright 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.
+*/
+-->
+<resources>
+    <!-- **************************************************************** -->
+    <!-- These are the attributes used in AndroidManifest.xml. -->
+    <!-- **************************************************************** -->
+    <eat-comment />
+
+    <!-- The overall theme to use for an activity.  Use with either the
+         application tag (to supply a default theme for all activities) or
+         the activity tag (to supply a specific theme for that activity).
+    
+         <p>This automatically sets
+         your activity's Context to use this theme, and may also be used
+         for "starting" animations prior to the activity being launched (to
+         better match what the activity actually looks like).  It is a reference
+         to a style resource defining the theme.  If not set, the default
+         system theme will be used. -->
+    <attr name="theme" format="reference" />
+
+    <!-- A user-legible name for the given item.  Use with the
+         application tag (to supply a default label for all application
+         components), or with the activity, receiver, service, or instrumentation
+         tag (to supply a specific label for that component).  It may also be
+         used with the intent-filter tag to supply a label to show to the
+         user when an activity is being selected based on a particular Intent.
+    
+         <p>The given label will be used wherever the user sees information
+         about its associated component; for example, as the name of a
+         main activity that is displayed in the launcher.  You should
+         generally set this to a reference to a string resource, so that
+         it can be localized, however it is also allowed to supply a plain
+         string for quick and dirty programming. -->
+    <attr name="label" format="reference|string" />
+    
+    <!-- A Drawable resource providing a graphical representation of its
+         associated item.  Use with the
+         application tag (to supply a default icon for all application
+         components), or with the activity, receiver, service, or instrumentation
+         tag (to supply a specific icon for that component).  It may also be
+         used with the intent-filter tag to supply an icon to show to the
+         user when an activity is being selected based on a particular Intent.
+    
+         <p>The given icon will be used to display to the user a graphical
+         representation of its associated component; for example, as the icon
+         for main activity that is displayed in the launcher.  This must be
+         a reference to a Drawable resource containing the image definition. -->
+    <attr name="icon" format="reference" />
+
+    <!-- Name of the activity to be launched to manage application's space on
+         device. The specified activity gets automatically launched when the
+         application's space needs to be managed and is usually invoked 
+         through user actions. Applications can thus provide their own custom
+         behavior for managing space for various scenarios like out of memory
+         conditions. This is an optional attribute and
+         applications can choose not to specify a default activity to 
+         manage space. -->
+    <attr name="manageSpaceActivity" format="string" />
+
+    <!-- Option to let applications specify that user data can/cannot be 
+         cleared. Some applications might not want to clear user data. Such
+         applications can explicitly set this value to false. This flag is
+         turned on by default unless explicitly set to false 
+         by applications. -->
+    <attr name="allowClearUserData" format="boolean" />
+    
+    <!-- A unique name for the given item.  This must use a Java-style naming
+         convention to ensure the name is unique, for example
+         "com.mycompany.MyName". -->  
+    <attr name="name" format="string" />
+    
+    <!-- Specify a permission that a client is required to have in order to
+    	 use the associated object.  If the client does not hold the named
+    	 permission, its request will fail.  See the
+         <a href="{@docRoot}guide/topics/security/security.html">Security and Permissions</a>
+         document for more information on permissions. -->
+    <attr name="permission" format="string" />
+    
+    <!-- A specific {@link android.R.attr#permission} name for read-only
+         access to a {@link android.content.ContentProvider}.  See the
+         <a href="{@docRoot}guide/topics/security/security.html">Security and Permissions</a>
+         document for more information on permissions. -->
+    <attr name="readPermission" format="string" />
+    
+    <!-- A specific {@link android.R.attr#permission} name for write
+         access to a {@link android.content.ContentProvider}.  See the
+         <a href="{@docRoot}guide/topics/security/security.html">Security and Permissions</a>
+         document for more information on permissions. -->
+    <attr name="writePermission" format="string" />
+    
+    <!-- If true, the {@link android.content.Context#grantUriPermission
+         Context.grantUriPermission} or corresponding Intent flags can
+         be used to allow others to access specific URIs in the content
+         provider, even if they do not have an explicit read or write
+         permission.  If you are supporting this feature, you must be
+         sure to call {@link android.content.Context#revokeUriPermission
+         Context.revokeUriPermission} when URIs are deleted from your
+         provider.-->
+    <attr name="grantUriPermissions" format="boolean" />
+    
+    <!-- Characterizes the potential risk implied in a permission and
+         indicates the procedure the system should follow when determining
+         whether to grant the permission to an application requesting it. {@link
+         android.Manifest.permission Standard permissions} have a predefined and
+         permanent protectionLevel. If you are creating a custom permission in an
+         application, you can define a protectionLevel attribute with one of the
+         values listed below. If no protectionLevel is defined for a custom
+         permission, the system assigns the default ("normal"). -->
+    <attr name="protectionLevel">
+        <!-- A lower-risk permission that gives an application access to isolated
+             application-level features, with minimal risk to other applications,
+             the system, or the user. The system automatically grants this type
+             of permission to a requesting application at installation, without
+             asking for the user's explicit approval (though the user always
+             has the option to review these permissions before installing). -->
+        <enum name="normal" value="0" />
+        <!-- A higher-risk permission that would give a requesting application
+             access to private user data or control over the device that can
+             negatively impact the user.  Because this type of permission
+             introduces potential risk, the system may not automatically
+             grant it to the requesting application.  For example, any dangerous
+             permissions requested by an application may be displayed to the
+             user and require confirmation before proceeding, or some other
+             approach may be taken to avoid the user automatically allowing
+             the use of such facilities.  -->
+        <enum name="dangerous" value="1" />
+        <!-- A permission that the system is to grant only if the requesting
+             application is signed with the same certificate as the application
+             that declared the permission. If the certificates match, the system
+             automatically grants the permission without notifying the user or
+             asking for the user's explicit approval. -->
+        <enum name="signature" value="2" />
+        <!-- A permission that the system is to grant only to packages in the
+             Android system image <em>or</em> that are signed with the same
+             certificates. Please avoid using this option, as the
+             signature protection level should be sufficient for most needs and
+             works regardless of exactly where applications are installed.  This
+             permission is used for certain special situations where multiple
+             vendors have applications built in to a system image which need
+             to share specific features explicitly because they are being built
+             together. -->
+        <enum name="signatureOrSystem" value="3" />
+    </attr>
+    
+    <!-- Specified the name of a group that this permission is associated
+         with.  The group must have been defined with the
+         {@link android.R.styleable#AndroidManifestPermissionGroup permission-group} tag. -->
+    <attr name="permissionGroup" format="string" />
+    
+    <!-- Specify the name of a user ID that will be shared between multiple
+         packages.  By default, each package gets its own unique user-id.
+         By setting this value on two or more packages, each of these packages
+         will be given a single shared user ID, so they can for example run
+         in the same process.  Note that for them to actually get the same
+         user ID, they must also be signed with the same signature. -->
+    <attr name="sharedUserId" format="string" />
+    
+    <!-- Specify a label for the shared user UID of this package.  This is
+         only used if you have also used android:sharedUserId.  This must
+         be a reference to a string resource; it can not be an explicit
+         string. -->
+    <attr name="sharedUserLabel" format="reference" />
+    
+    <!-- Internal version code.  This is the number used to determine whether
+         one version is more recent than another: it has no other meaning than
+         that higher numbers are more recent.  You could use this number to
+         encode a "x.y" in the lower and upper 16 bits, make it a build
+         number, simply increase it by one each time a new version is
+         released, or define it however else you want, as long as each
+         successive version has a higher number.  This is not a version
+         number generally shown to the user, that is usually supplied 
+         with {@link android.R.attr#versionName}. -->
+    <attr name="versionCode" format="integer" />
+    
+    <!-- The text shown to the user to indicate the version they have.  This
+         is used for no other purpose than display to the user; the actual
+         significant version number is given by {@link android.R.attr#versionCode}. -->
+    <attr name="versionName" format="string" />
+    
+    <!-- Flag to control special persistent mode of an application.  This should
+         not normally be used by applications; it requires that the system keep
+         your application running at all times. -->
+    <attr name="persistent" format="boolean" />
+    
+    <!-- Flag indicating whether the application can be debugged, even when
+         running on a device that is running in user mode. -->
+    <attr name="debuggable" format="boolean" />
+    
+    <!-- Flag indicating whether the given application component is available
+         to other applications.  If false, it can only be accessed by
+         applications with its same user id (which usually means only by
+         code in its own package).  If true, it can be invoked by external
+         entities, though which ones can do so may be controlled through
+         permissions.  The default value is false for activity, receiver,
+         and service components that do not specify any intent filters; it
+         is true for activity, receiver, and service components that do
+         have intent filters (implying they expect to be invoked by others
+         who do not know their particular component name) and for all
+         content providers. -->
+    <attr name="exported" format="boolean" />
+    
+    <!-- Specify a specific process that the associated code is to run in.
+         Use with the application tag (to supply a default process for all
+         application components), or with the activity, receiver, service,
+         or provider tag (to supply a specific icon for that component).
+    
+         <p>Application components are normally run in a single process that
+         is created for the entire application.  You can use this tag to modify
+         where they run.  If the process name begins with a ':' character,
+         a new process private to that application will be created when needed
+         to run that component (allowing you to spread your application across
+         multiple processes).  If the process name begins with a lower-case
+         character, the component will be run in a global process of that name,
+         provided that you have permission to do so, allowing multiple
+         applications to share one process to reduce resource usage. -->
+    <attr name="process" format="string" />
+    
+    <!-- Specify a task name that activities have an "affinity" to.
+         Use with the application tag (to supply a default affinity for all
+         activities in the application), or with the activity tag (to supply
+         a specific affinity for that component).
+    
+         <p>The default value for this attribute is the same as the package
+         name, indicating that all activities in the manifest should generally
+         be considered a single "application" to the user.  You can use this
+         attribute to modify that behavior: either giving them an affinity
+         for another task, if the activities are intended to be part of that
+         task from the user's perspective, or using an empty string for
+         activities that have no affinity to a task. -->
+    <attr name="taskAffinity" format="string" />
+    
+    <!-- Specify that an activity can be moved out of a task it is in to
+         the task it has an affinity for when appropriate.  Use with the
+         application tag (to supply a default for all activities in the
+         application), or with an activity tag (to supply a specific
+         setting for that component).
+    
+         <p>Normally when an application is started, it is associated with
+         the task of the activity that started it and stays there for its
+         entire lifetime.  You can use the allowTaskReparenting feature to force an
+         activity to be re-parented to a different task when the task it is
+         in goes to the background.  Typically this is used to cause the
+         activities of an application to move back to the main task associated
+         with that application.  The activity is re-parented to the task
+         with the same {@link android.R.attr#taskAffinity} as it has. -->
+    <attr name="allowTaskReparenting" format="boolean" />
+    
+    <!-- Specify whether a component is allowed to have multiple instances
+         of itself running in different processes.  Use with the activity
+         and provider tags.
+    
+         <p>Normally the system will ensure that all instances of a particular
+         component are only running in a single process.  You can use this
+         attribute to disable that behavior, allowing the system to create
+         instances wherever they are used (provided permissions allow it).
+         This is most often used with content providers, so that instances
+         of a provider can be created in each client process, allowing them
+         to be used without performing IPC.  -->
+    <attr name="multiprocess" format="boolean" />
+    
+    <!-- Specify whether an activity should be finished when its task is
+         brought to the foreground by relaunching from the home screen.
+         
+         <p>If both this option and {@link android.R.attr#allowTaskReparenting} are
+         specified, the finish trumps the affinity: the affinity will be
+         ignored and the activity simply finished. -->
+    <attr name="finishOnTaskLaunch" format="boolean" />
+    
+    <!-- Specify whether an activity's task should be cleared when it
+         is re-launched from the home screen.  As a result, every time the
+         user starts the task, they will be brought to its root activity,
+         regardless of whether they used BACK or HOME to last leave it.
+         This flag only applies to activities that
+         are used to start the root of a new task.
+         
+         <p>An example of the use of this flag would be for the case where
+         a user launches activity A from home, and from there goes to
+         activity B.  They now press home, and then return to activity A.
+         Normally they would see activity B, since that is what they were
+         last doing in A's task.  However, if A has set this flag to true,
+         then upon going to the background all of the tasks on top of it (B
+         in this case) are removed, so when the user next returns to A they
+         will restart at its original activity.
+         
+         <p>When this option is used in conjunction with
+         {@link android.R.attr#allowTaskReparenting}, the allowTaskReparenting trumps the
+         clear.  That is, all activities above the root activity of the
+         task will be removed: those that have an affinity will be moved
+         to the task they are associated with, otherwise they will simply
+         be dropped as described here. -->
+    <attr name="clearTaskOnLaunch" format="boolean" />
+    
+    <!-- Specify whether an activity should be kept in its history stack.
+         If this attribute is set, then as soon as the user navigates away
+         from the activity it will be finished and they will no longer be
+         able to return to it. -->
+    <attr name="noHistory" format="boolean" />
+    
+    <!-- Specify whether an acitivty's task state should always be maintained
+         by the system, or if it is allowed to reset the task to its initial
+         state in certain situations.
+         
+         <p>Normally the system will reset a task (remove all activities from
+         the stack and reset the root activity) in certain situations when
+         the user re-selects that task from the home screen.  Typically this
+         will be done if the user hasn't visited that task for a certain
+         amount of time, such as 30 minutes.
+         
+         <p>By setting this attribute, the user will always return to your
+         task in its last state, regardless of how they get there.  This is
+         useful, for example, in an application like the web browser where there
+         is a lot of state (such as multiple open tabs) that the application
+         would not like to lose. -->
+    <attr name="alwaysRetainTaskState" format="boolean" />
+    
+    <!-- Indicates that an Activity does not need to have its freeze state
+         (as returned by {@link android.app.Activity#onSaveInstanceState}
+         retained in order to be restarted.  Generally you use this for activities
+         that do not store any state.  When this flag is set, if for some reason
+         the activity is killed before it has a chance to save its state,
+         then the system will not remove it from the activity stack like
+         it normally would.  Instead, the next time the user navigates to
+         it its {@link android.app.Activity#onCreate} method will be called
+         with a null icicle, just like it was starting for the first time.
+         
+         <p>This is used by the Home activity to make sure it does not get
+         removed if it crashes for some reason. -->
+    <attr name="stateNotNeeded" format="boolean" />
+
+    <!-- Indicates that an Activity should be excluded from the list of
+         recently launched activities. -->
+    <attr name="excludeFromRecents" format="boolean" />
+
+    <!-- Specify the authorities under which this content provider can be
+         found.  Multiple authorities may be supplied by separating them
+         with a semicolon.  Authority names should use a Java-style naming
+         convention (such as <code>com.google.provider.MyProvider</code>)
+         in order to avoid conflicts.  Typically this name is the same
+         as the class implementation describing the provider's data structure. -->
+    <attr name="authorities" format="string" />
+    
+    <!-- Flag indicating whether this content provider would like to
+         participate in data synchronization. -->
+    <attr name="syncable" format="boolean" />
+    
+    <!-- Specify the order in which content providers hosted by a process
+         are instantiated when that process is created.  Not needed unless
+         you have providers with dependencies between each other, to make
+         sure that they are created in the order needed by those dependencies.
+         The value is a simple integer, with higher numbers being
+         initialized first. -->
+    <attr name="initOrder" format="integer" />
+    
+    <!-- Specify the relative importance or ability in handling a particular
+         Intent.  For receivers, this controls the order in which they are
+         executed to receive a broadcast (note that for
+         asynchronous broadcasts, this order is ignored).  For activities,
+         this provides information about how good an activity is handling an
+         Intent; when multiple activities match an intent and have different
+         priorities, only those with the higher priority value will be
+         considered a match.
+         
+         <p>Only use if you really need to impose some specific
+         order in which the broadcasts are received, or want to forcibly
+         place an activity to always be preferred over others.  The value is a
+         single integer, with higher numbers considered to be better. -->
+    <attr name="priority" format="integer" />
+    
+    <!-- Specify how an activity should be launched.  See the
+         <a href="{@docRoot}guide/topics/fundamentals.html#acttask">Application Fundamentals</a>
+         documentation for important information on how these options impact
+         the behavior of your application.
+         
+         <p>If this attribute is not specified, <code>standard</code> launch
+         mode will be used.  Note that the particular launch behavior can
+         be changed in some ways at runtime through the
+         {@link android.content.Intent} flags
+         {@link android.content.Intent#FLAG_ACTIVITY_SINGLE_TOP},
+         {@link android.content.Intent#FLAG_ACTIVITY_NEW_TASK}, and
+         {@link android.content.Intent#FLAG_ACTIVITY_MULTIPLE_TASK}. -->
+    <attr name="launchMode">
+        <!-- The default mode, which will usually create a new instance of
+             the activity when it is started, though this behavior may change
+             with the introduction of other options such as
+             {@link android.content.Intent#FLAG_ACTIVITY_NEW_TASK
+             Intent.FLAG_ACTIVITY_NEW_TASK}. -->
+        <enum name="standard" value="0" />
+        <!-- If, when starting the activity, there is already an
+            instance of the same activity class in the foreground that is
+            interacting with the user, then
+            re-use that instance.  This existing instance will receive a call to
+            {@link android.app.Activity#onNewIntent Activity.onNewIntent()} with
+            the new Intent that is being started. -->
+        <enum name="singleTop" value="1" />
+        <!-- If, when starting the activity, there is already a task running
+            that starts with this activity, then instead of starting a new
+            instance the current task is brought to the front.  The existing
+            instance will receive a call to {@link android.app.Activity#onNewIntent
+            Activity.onNewIntent()}
+            with the new Intent that is being started, and with the
+            {@link android.content.Intent#FLAG_ACTIVITY_BROUGHT_TO_FRONT
+            Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT} flag set.  This is a superset
+            of the singleTop mode, where if there is already an instance
+            of the activity being started at the top of the stack, it will
+            receive the Intent as described there (without the
+            FLAG_ACTIVITY_BROUGHT_TO_FRONT flag set).  See the
+            <a href="{@docRoot}guide/topics/fundamentals.html#acttask">Application Fundamentals</a>
+            documentation for more details on tasks.-->
+        <enum name="singleTask" value="2" />
+        <!-- Only allow one instance of this activity to ever be 
+            running.  This activity gets a unique task with only itself running 
+            in it; if it is ever launched again with the same Intent, then that 
+            task will be brought forward and its 
+            {@link android.app.Activity#onNewIntent Activity.onNewIntent()}
+            method called.  If this 
+            activity tries to start a new activity, that new activity will be 
+            launched in a separate task.  See the
+            <a href="{@docRoot}guide/topics/fundamentals.html#acttask">Application Fundamentals</a>
+            documentation for more details on tasks. -->
+        <enum name="singleInstance" value="3" />
+    </attr>
+    
+    <!-- Specify the orientation an activity should be run in.  If not
+         specified, it will run in the current preferred orientation
+         of the screen. -->
+    <attr name="screenOrientation">
+        <!-- No preference specified: let the system decide the best
+             orientation.  This will either be the orientation selected
+             by the activity below, or the user's preferred orientation
+             if this activity is the bottom of a task. If the user
+             explicitly turned off sensor based orientation through settings
+             sensor based device rotation will be ignored. If not by default
+             sensor based orientation will be taken into account and the 
+             orientation will changed based on how the user rotates the device -->
+        <enum name="unspecified" value="-1" />
+        <!-- Would like to have the screen in a landscape orientation: that
+             is, with the display wider than it is tall. -->
+        <enum name="landscape" value="0" />
+        <!-- Would like to have the screen in a portrait orientation: that
+             is, with the display taller than it is wide. -->
+        <enum name="portrait" value="1" />
+        <!-- Use the user's current preferred orientation of the handset. -->
+        <enum name="user" value="2" />
+        <!-- Keep the screen in the same orientation as whatever is behind
+             this activity. -->
+        <enum name="behind" value="3" />
+        <!-- Orientation is determined by a physical orientation sensor:
+             the display will rotate based on how the user moves the device. -->
+        <enum name="sensor" value="4" />
+        <!-- Always ignore orientation determined by orientation sensor:
+             tthe display will not rotate when the user moves the device. -->
+        <enum name="nosensor" value="5" />
+    </attr>
+    
+    <!-- Specify one or more configuration changes that the activity will
+         handle itself.  If not specified, the activity will be restarted
+         if any of these configuration changes happen in the system.  Otherwise,
+         the activity will remain running and its
+         {@link android.app.Activity#onConfigurationChanged Activity.onConfigurationChanged}
+         method called with the new configuration.
+         
+         <p>Note that all of these configuration changes can impact the
+         resource values seen by the application, so you will generally need
+         to re-retrieve all resources (including view layouts, drawables, etc)
+         to correctly handle any configuration change.
+         
+         <p>These values must be kept in sync with those in
+         {@link android.content.pm.ActivityInfo} and
+         include/utils/ResourceTypes.h. -->
+    <attr name="configChanges">
+        <!-- The IMSI MCC has changed, that is a SIM has been detected and
+             updated the Mobile Country Code. -->
+        <flag name="mcc" value="0x0001" />
+        <!-- The IMSI MNC has changed, that is a SIM has been detected and
+             updated the Mobile Network Code. -->
+        <flag name="mnc" value="0x0002" />
+        <!-- The locale has changed, that is the user has selected a new
+             language that text should be displayed in. -->
+        <flag name="locale" value="0x0004" />
+        <!-- The touchscreen has changed.  Should never normally happen. -->
+        <flag name="touchscreen" value="0x0008" />
+        <!-- The keyboard type has changed, for example the user has plugged
+             in an external keyboard. -->
+        <flag name="keyboard" value="0x0010" />
+        <!-- The keyboard accessibility has changed, for example the user has
+             slid the keyboard out to expose it. -->
+        <flag name="keyboardHidden" value="0x0020" />
+        <!-- The navigation type has changed.  Should never normally happen. -->
+        <flag name="navigation" value="0x0040" />
+        <!-- The screen orientation has changed, that is the user has
+             rotated the device. -->
+        <flag name="orientation" value="0x0080" />
+        <!-- The font scaling factor has changed, that is the user has
+             selected a new global font size. -->
+        <flag name="fontScale" value="0x40000000" />
+    </attr>
+    
+    <!-- A longer descriptive text about a particular application or
+         permission that can be granted.  This must be a reference 
+         to a string resource; unlike
+    	 the {@link android.R.attr#label} attribute, this can not be a
+    	 raw string. -->
+    <attr name="description" format="reference" />
+    
+    <!-- The name of the application package that an Instrumentation object
+         will run against. -->
+    <attr name="targetPackage" format="string" />
+    
+    <!-- Flag indicating that an Instrumentation class wants to take care
+         of starting/stopping profiling itself, rather than relying on
+         the default behavior of profiling the complete time it is running.
+         This allows it to target profiling data at a specific set of
+         operations. -->
+    <attr name="handleProfiling" format="boolean" />
+    
+    <!-- Flag indicating that an Instrumentation class should be run as a
+         functional test. -->
+    <attr name="functionalTest" format="boolean" />
+
+    <!-- The touch screen type used by an application. -->
+    <attr name="reqTouchScreen">
+        <enum name="undefined" value="0" />
+        <enum name="notouch" value="1" />
+        <enum name="stylus" value="2" />
+        <enum name="finger" value="3" />
+    </attr>
+
+    <!-- The input method preferred by an application. -->
+    <attr name="reqKeyboardType">
+        <enum name="undefined" value="0" />
+        <enum name="nokeys" value="1" />
+        <enum name="qwerty" value="2" />
+        <enum name="twelvekey" value="3" />
+    </attr>
+
+    <!-- Application's requirement for a hard keyboard -->
+    <attr name="reqHardKeyboard" format="boolean" />
+
+    <!-- The navigation device preferred by an application. -->
+    <attr name="reqNavigation">
+        <enum name="undefined" value="0" />
+        <enum name="nonav" value="1" />
+        <enum name="dpad" value="2" />
+        <enum name="trackball" value="3" />
+        <enum name="wheel" value="4" />
+    </attr>
+
+    <!-- Application's requirement for five way navigation -->
+    <attr name="reqFiveWayNav" format="boolean" />
+
+    <!-- The <code>manifest</code> tag is the root of an
+         <code>AndroidManifest.xml</code> file,
+         describing the contents of an Android package (.apk) file.  One
+         attribute must always be supplied: <code>package</code> gives a
+         unique name for the package, using a Java-style naming convention
+         to avoid name collisions.  For example, applications published
+         by Google could have names of the form
+         <code>com.google.app.<em>appname</em></code>
+         
+         <p>Inside of the manifest tag, may appear the following tags
+         in any order: {@link #AndroidManifestPermission permission},
+         {@link #AndroidManifestPermissionGroup permission-group},
+         {@link #AndroidManifestPermissionTree permission-tree},
+         {@link #AndroidManifestUsesSdk uses-sdk},
+         {@link #AndroidManifestUsesPermission uses-permission},
+         {@link #AndroidManifestUsesConfiguration uses-configuration},
+         {@link #AndroidManifestApplication application},
+         {@link #AndroidManifestInstrumentation instrumentation}.  -->
+    <declare-styleable name="AndroidManifest">
+        <attr name="versionCode" />
+        <attr name="versionName" />
+        <attr name="sharedUserId" />
+        <attr name="sharedUserLabel" />
+    </declare-styleable>
+    
+    <!-- The <code>application</code> tag describes application-level components
+         contained in the package, as well as general application
+         attributes.  Many of the attributes you can supply here (such
+         as theme, label, icon, permission, process, taskAffinity,
+         and allowTaskReparenting) serve
+         as default values for the corresponding attributes of components
+         declared inside of the application.
+         
+         <p>Inside of this element you specify what the application contains,
+         using the elements {@link #AndroidManifestProvider provider},
+         {@link #AndroidManifestService service},
+         {@link #AndroidManifestReceiver receiver},
+         {@link #AndroidManifestActivity activity},
+         {@link #AndroidManifestActivityAlias activity-alias}, and
+         {@link #AndroidManifestUsesLibrary uses-library}.  The application tag
+         appears as a child of the root {@link #AndroidManifest manifest} tag. -->
+    <declare-styleable name="AndroidManifestApplication" parent="AndroidManifest">
+        <!-- An optional name of a class implementing the overall
+             {@link android.app.Application} for this package.  When the
+             process for your package is started, this class is instantiated
+             before any of the other application components.  Note that this
+             is not required, and in fact most applications will probably
+             not need it. -->
+        <attr name="name" />
+        <attr name="theme" />
+        <attr name="label" />
+        <attr name="icon" />
+        <attr name="description" />
+        <attr name="permission" />
+        <attr name="process" />
+        <attr name="taskAffinity" />
+        <attr name="allowTaskReparenting" />
+        <!-- Indicate whether this application contains code.  If set to false,
+             there is no code associated with it and thus the system will not
+             try to load its code when launching components.  The default is true
+             for normal behavior. -->
+        <attr name="hasCode" format="boolean" />
+        <attr name="persistent" />
+        <!-- Specify whether the components in this application are enabled or not (i.e. can be
+             instantiated by the system).
+             If "false", it overrides any component specific values (a value of "true" will not
+             override the component specific values). -->
+        <attr name="enabled" />
+        <attr name="debuggable" />
+        <!-- Name of activity to be launched for managing the application's space on the device. -->
+        <attr name="manageSpaceActivity" />
+        <attr name="allowClearUserData" />
+    </declare-styleable>
+    
+    <!-- The <code>permission</code> tag declares a security permission that can be
+         used to control access from other packages to specific components or
+         features in your package (or other packages).  See the
+         <a href="{@docRoot}guide/topics/security/security.html">Security and Permissions</a>
+         document for more information on permissions.
+         
+         <p>This appears as a child tag of the root
+         {@link #AndroidManifest manifest} tag. -->
+    <declare-styleable name="AndroidManifestPermission" parent="AndroidManifest">
+        <!-- Required public name of the permission, which other components and
+        packages will use when referring to this permission.  This is a string using
+        Java-style scoping to ensure it is unique.  The prefix will often
+        be the same as our overall package name, for example
+        "com.mycompany.android.myapp.SomePermission". -->
+        <attr name="name" />
+        <attr name="label" />
+        <attr name="icon" />
+        <attr name="permissionGroup" />
+        <attr name="description" />
+        <attr name="protectionLevel" />
+    </declare-styleable>
+    
+    <!-- The <code>permission-group</code> tag declares a logical grouping of
+         related permissions.
+         
+         <p>Note that this tag does not declare a permission itself, only
+         a namespace in which further permissions can be placed.  See
+         the {@link #AndroidManifestPermission &lt;permission&gt;} tag for
+         more information.
+         
+         <p>This appears as a child tag of the root
+         {@link #AndroidManifest manifest} tag. -->
+    <declare-styleable name="AndroidManifestPermissionGroup" parent="AndroidManifest">
+        <!-- Required public name of the permission group, permissions will use
+        to specify the group they are in.  This is a string using
+        Java-style scoping to ensure it is unique.  The prefix will often
+        be the same as our overall package name, for example
+        "com.mycompany.android.myapp.SomePermission". -->
+        <attr name="name" />
+        <attr name="label" />
+        <attr name="icon" />
+        <attr name="description" />
+    </declare-styleable>
+    
+    <!-- The <code>permission-tree</code> tag declares the base of a tree of
+         permission values: it declares that this package has ownership of
+         the given permission name, as well as all names underneath it
+         (separated by '.').  This allows you to use the
+         {@link android.content.pm.PackageManager#addPermission
+         PackageManager.addPermission()} method to dynamically add new
+         permissions under this tree.
+         
+         <p>Note that this tag does not declare a permission itself, only
+         a namespace in which further permissions can be placed.  See
+         the {@link #AndroidManifestPermission &lt;permission&gt;} tag for
+         more information.
+         
+         <p>This appears as a child tag of the root
+         {@link #AndroidManifest manifest} tag. -->
+    <declare-styleable name="AndroidManifestPermissionTree" parent="AndroidManifest">
+        <!-- Required public name of the permission tree, which is the base name
+        of all permissions under it.  This is a string using
+        Java-style scoping to ensure it is unique.  The prefix will often
+        be the same as our overall package name, for example
+        "com.mycompany.android.myapp.SomePermission".  A permission tree name
+        must have more than two segments in its path; that is,
+        "com.me.foo" is okay, but not "com.me" or "com". -->
+        <attr name="name" />
+        <attr name="label" />
+        <attr name="icon" />
+    </declare-styleable>
+    
+    <!-- The <code>uses-permission</code> tag requests a
+         {@link #AndroidManifestPermission &lt;permission&gt;} that the containing
+         package must be granted in order for it to operate correctly.
+         See the <a href="{@docRoot}guide/topics/security/security.html">Security and Permissions</a>
+         document for more information on permissions.  Also available is a
+         {@link android.Manifest.permission list of permissions} included
+         with the base platform.
+         
+         <p>This appears as a child tag of the root
+         {@link #AndroidManifest manifest} tag. -->
+    <declare-styleable name="AndroidManifestUsesPermission" parent="AndroidManifest">
+        <!-- Required name of the permission you use, as published with the
+        corresponding name attribute of a
+        {@link android.R.styleable#AndroidManifestPermission &lt;permission&gt;}
+        tag; often this is one of the {@link android.Manifest.permission standard
+        system permissions}. -->
+        <attr name="name" />
+    </declare-styleable>
+
+    <!-- The <code>uses-configuration</code> tag specifies
+         a specific hardware configuration value used by the application.
+         For example an application might specify that it requires
+         a physical keyboard or a particular navigation method like
+         trackball. Multiple such attribute values can be specified by the
+         application.
+
+         <p>This appears as a child tag of the root
+         {@link #AndroidManifest manifest} tag. -->
+    <declare-styleable name="AndroidManifestUsesConfiguration" parent="AndroidManifest">
+        <!-- The type of touch screen used by an application. -->
+        <attr name="reqTouchScreen" />
+        <attr name="reqKeyboardType" />
+        <attr name="reqHardKeyboard" />
+        <attr name="reqNavigation" />
+        <attr name="reqFiveWayNav" />
+    </declare-styleable>
+
+    <!-- The <code>uses-sdk</code> tag describes the SDK features that the
+         containing package must be running on to operate correctly.
+         
+         <p>This appears as a child tag of the root
+         {@link #AndroidManifest manifest} tag. -->
+    <declare-styleable name="AndroidManifestUsesSdk" parent="AndroidManifest">
+        <!-- This is the minimum SDK version number that the application
+             requires.  Currently there is only one SDK version, 1.  If
+             not supplied, the application will work on any SDK. -->
+        <attr name="minSdkVersion" format="integer" />
+    </declare-styleable>
+    
+    <!-- The <code>uses-libraries</code> specifies a shared library that this
+         package requires to be linked against.  Specifying this flag tells the
+         system to include this library's code in your class loader.
+         
+         <p>This appears as a child tag of the
+         {@link #AndroidManifestApplication application} tag. -->
+    <declare-styleable name="AndroidManifestUsesLibrary" parent="AndroidManifestApplication">
+        <!-- Required name of the library you use. -->
+        <attr name="name" />
+    </declare-styleable>
+    
+    <!-- The <code>provider</code> tag declares a
+         {@link android.content.ContentProvider} class that is available
+         as part of the package's application components, supplying structured
+         access to data managed by the application.
+         
+         <p>This appears as a child tag of the
+         {@link #AndroidManifestApplication application} tag. -->
+    <declare-styleable name="AndroidManifestProvider" parent="AndroidManifestApplication">
+        <!-- Required name of the class implementing the provider, deriving from
+            {@link android.content.ContentProvider}.  This is a fully
+            qualified class name (i.e., com.mycompany.myapp.MyProvider); as a
+            short-hand if the first character of the class
+            is a period then it is appended to your package name. -->
+        <attr name="name" />
+        <attr name="label" />
+        <attr name="icon" />
+        <attr name="process" />
+        <attr name="authorities" />
+        <attr name="syncable" />
+        <attr name="readPermission" />
+        <attr name="writePermission" />
+        <attr name="grantUriPermissions" />
+        <attr name="permission" />
+        <attr name="multiprocess" />
+        <attr name="initOrder" />
+        <!-- Specify whether this provider is enabled or not (i.e. can be instantiated by the system).
+             It can also be specified for an application as a whole, in which case a value of "false"
+             will override any component specific values (a value of "true" will not override the
+             component specific values). -->
+        <attr name="enabled" />
+        <attr name="exported" />
+    </declare-styleable>
+    
+    <!-- Attributes that can be supplied in an AndroidManifest.xml
+         <code>grant-uri-permission</code> tag, a child of the
+         {@link #AndroidManifestProvider provider} tag, describing a specific
+         URI path that can be granted as a permission.  This tag can be
+         specified multiple time to supply multiple paths. -->
+    <declare-styleable name="AndroidManifestGrantUriPermission"  parent="AndroidManifestProvider">
+        <!-- Specify a URI path that must exactly match, as per
+             {@link android.os.PatternMatcher} with
+             {@link android.os.PatternMatcher#PATTERN_LITERAL}. -->
+        <attr name="path" format="string" />
+        <!-- Specify a URI path that must be a prefix to match, as per
+             {@link android.os.PatternMatcher} with
+             {@link android.os.PatternMatcher#PATTERN_PREFIX}. -->
+        <attr name="pathPrefix" format="string" />
+        <!-- Specify a URI path that matches a simple pattern, as per
+             {@link android.os.PatternMatcher} with
+             {@link android.os.PatternMatcher#PATTERN_SIMPLE_GLOB}. 
+             Note that because '\' is used as an escape character when
+             reading the string from XML (before it is parsed as a pattern),
+             you will need to double-escape: for example a literal "*" would
+             be written as "\\*" and a literal "\" would be written as
+             "\\\\".  This is basically the same as what you would need to
+             write if constructing the string in Java code. -->
+        <attr name="pathPattern" format="string" />
+    </declare-styleable>
+    
+    <!-- The <code>service</code> tag declares a
+         {@link android.app.Service} class that is available
+         as part of the package's application components, implementing
+         long-running background operations or a rich communication API
+         that can be called by other packages.
+         
+         <p>Zero or more {@link #AndroidManifestIntentFilter intent-filter}
+         tags can be included inside of a service, to specify the Intents
+         that can connect with it.  If none are specified, the service can
+         only be accessed by direct specification of its class name.
+         The service tag appears as a child tag of the
+         {@link #AndroidManifestApplication application} tag. -->
+    <declare-styleable name="AndroidManifestService" parent="AndroidManifestApplication">
+        <!-- Required name of the class implementing the service, deriving from
+            {@link android.app.Service}.  This is a fully
+            qualified class name (i.e., com.mycompany.myapp.MyService); as a
+            short-hand if the first character of the class
+            is a period then it is appended to your package name. -->
+        <attr name="name" />
+        <attr name="label" />
+        <attr name="icon" />
+        <attr name="permission" />
+        <attr name="process" />
+        <!-- Specify whether the service is enabled or not (i.e. can be instantiated by the system).
+             It can also be specified for an application as a whole, in which case a value of "false"
+             will override any component specific values (a value of "true" will not override the
+             component specific values). -->
+        <attr name="enabled" />
+        <attr name="exported" />
+    </declare-styleable>
+    
+    <!-- The <code>receiver</code> tag declares an
+         {@link android.content.BroadcastReceiver} class that is available
+         as part of the package's application components, allowing the
+         application to receive actions or data broadcast by other
+         applications even if it is not currently running.
+         
+         <p>Zero or more {@link #AndroidManifestIntentFilter intent-filter}
+         tags can be included inside of a receiver, to specify the Intents
+         it will receive.  If none are specified, the receiver will only
+         be run when an Intent is broadcast that is directed at its specific
+         class name.  The receiver tag appears as a child tag of the
+         {@link #AndroidManifestApplication application} tag. -->
+    <declare-styleable name="AndroidManifestReceiver" parent="AndroidManifestApplication">
+        <!-- Required name of the class implementing the receiver, deriving from
+            {@link android.content.BroadcastReceiver}.  This is a fully
+            qualified class name (i.e., com.mycompany.myapp.MyReceiver); as a
+            short-hand if the first character of the class
+            is a period then it is appended to your package name. -->
+        <attr name="name" />
+        <attr name="label" />
+        <attr name="icon" />
+        <attr name="permission" />
+        <attr name="process" />
+        <!-- Specify whether the receiver is enabled or not (i.e. can be instantiated by the system).
+             It can also be specified for an application as a whole, in which case a value of "false"
+             will override any component specific values (a value of "true" will not override the
+             component specific values). -->
+        <attr name="enabled" />
+        <attr name="exported" />
+    </declare-styleable>
+    
+    <!-- The <code>activity</code> tag declares an
+         {@link android.app.Activity} class that is available
+         as part of the package's application components, implementing
+         a part of the application's user interface.
+         
+         <p>Zero or more {@link #AndroidManifestIntentFilter intent-filter}
+         tags can be included inside of an activity, to specify the Intents
+         that it can handle.  If none are specified, the activity can
+         only be started through direct specification of its class name.
+         The activity tag appears as a child tag of the
+         {@link #AndroidManifestApplication application} tag. -->
+    <declare-styleable name="AndroidManifestActivity" parent="AndroidManifestApplication">
+        <!-- Required name of the class implementing the activity, deriving from
+            {@link android.app.Activity}.  This is a fully
+            qualified class name (i.e., com.mycompany.myapp.MyActivity); as a
+            short-hand if the first character of the class
+            is a period then it is appended to your package name. -->
+        <attr name="name" />
+        <attr name="theme" />
+        <attr name="label" />
+        <attr name="icon" />
+        <attr name="launchMode" />
+        <attr name="screenOrientation" />
+        <attr name="configChanges" />
+        <attr name="permission" />
+        <attr name="multiprocess" />
+        <attr name="process" />
+        <attr name="taskAffinity" />
+        <attr name="allowTaskReparenting" />
+        <attr name="finishOnTaskLaunch" />
+        <attr name="clearTaskOnLaunch" />
+        <attr name="noHistory" />
+        <attr name="alwaysRetainTaskState" />
+        <attr name="stateNotNeeded" />
+        <attr name="excludeFromRecents" />
+        <!-- Specify whether the activity is enabled or not (i.e. can be instantiated by the system).
+             It can also be specified for an application as a whole, in which case a value of "false"
+             will override any component specific values (a value of "true" will not override the
+             component specific values). -->
+        <attr name="enabled" />
+        <attr name="exported" />
+        <!-- Specify the default soft-input mode for the main window of
+             this activity.  A value besides "unspecified" here overrides
+             any value in the theme. -->
+        <attr name="windowSoftInputMode" />
+    </declare-styleable>
+    
+    <!-- The <code>activity-alias</code> tag declares a new
+         name for an existing {@link #AndroidManifestActivity activity}
+         tag.
+         
+         <p>Zero or more {@link #AndroidManifestIntentFilter intent-filter}
+         tags can be included inside of an activity-alias, to specify the Intents
+         that it can handle.  If none are specified, the activity can
+         only be started through direct specification of its class name.
+         The activity-alias tag appears as a child tag of the
+         {@link #AndroidManifestApplication application} tag. -->
+    <declare-styleable name="AndroidManifestActivityAlias" parent="AndroidManifestApplication">
+        <!-- Required name of the class implementing the activity, deriving from
+            {@link android.app.Activity}.  This is a fully
+            qualified class name (i.e., com.mycompany.myapp.MyActivity); as a
+            short-hand if the first character of the class
+            is a period then it is appended to your package name. -->
+        <attr name="name" />
+        <!-- The name of the activity this alias should launch.  The activity
+             must be in the same manifest as the alias, and have been defined
+             in that manifest before the alias here.  This must use a Java-style
+             naming convention to ensure the name is unique, for example
+             "com.mycompany.MyName". -->  
+        <attr name="targetActivity" format="string" />
+        <attr name="label" />
+        <attr name="icon" />
+        <attr name="permission" />
+        <!-- Specify whether the activity-alias is enabled or not (i.e. can be instantiated by the system).
+             It can also be specified for an application as a whole, in which case a value of "false"
+             will override any component specific values (a value of "true" will not override the
+             component specific values). -->
+        <attr name="enabled" />
+        <attr name="exported" />
+    </declare-styleable>
+    
+    <!-- The <code>meta-data</code> tag is used to attach additional
+         arbitrary data to an application component.  The data can later
+         be retrieved programmatically from the
+         {@link android.content.pm.ComponentInfo#metaData
+         ComponentInfo.metaData} field.  There is no meaning given to this
+         data by the system.  You may supply the data through either the
+         <code>value</code> or <code>resource</code> attribute; if both
+         are given, then <code>resource</code> will be used.
+         
+         <p>It is highly recommended that you avoid supplying related data as
+         multiple separate meta-data entries.  Instead, if you have complex
+         data to associate with a component, then use the <code>resource</code>
+         attribute to assign an XML resource that the client can parse to
+         retrieve the complete data. -->
+    <declare-styleable name="AndroidManifestMetaData"
+         parent="AndroidManifestApplication
+                 AndroidManifestActivity
+                 AndroidManifestReceiver
+                 AndroidManifestProvider
+                 AndroidManifestService
+                 AndroidManifestPermission
+                 AndroidManifestPermissionGroup
+                 AndroidManifestInstrumentation">
+        <attr name="name" />
+        <!-- Concrete value to assign to this piece of named meta-data.
+             The data can later be retrieved from the meta data Bundle
+             through {@link android.os.Bundle#getString Bundle.getString},
+             {@link android.os.Bundle#getInt Bundle.getInt},
+             {@link android.os.Bundle#getBoolean Bundle.getBoolean},
+             or {@link android.os.Bundle#getFloat Bundle.getFloat} depending
+             on the type used here. -->
+        <attr name="value" format="string|integer|color|float|boolean" />
+        <!-- Resource identifier to assign to this piece of named meta-data.
+             The resource identifier can later be retrieved from the meta data
+             Bundle through {@link android.os.Bundle#getInt Bundle.getInt}. -->
+        <attr name="resource" format="reference" />
+    </declare-styleable>
+    
+    <!-- The <code>intent-filter</code> tag is used to construct an
+         {@link android.content.IntentFilter} object that will be used
+         to determine which component can handle a particular
+         {@link android.content.Intent} that has been given to the system.
+         It can be used as a child of the
+         {@link #AndroidManifestActivity activity},
+         {@link #AndroidManifestReceiver receiver} and 
+         {@link #AndroidManifestService service}
+         tags.
+         
+         <p> Zero or more {@link #AndroidManifestAction action},
+         {@link #AndroidManifestCategory category}, and/or
+         {@link #AndroidManifestData data} tags should be
+         included inside to describe the contents of the filter.
+         
+         <p> The optional label and icon attributes here are used with
+         an activity to supply an alternative description of that activity
+         when it is being started through an Intent matching this filter. -->
+    <declare-styleable name="AndroidManifestIntentFilter"
+         parent="AndroidManifestActivity AndroidManifestReceiver AndroidManifestService">
+        <attr name="label" />
+        <attr name="icon" />
+        <attr name="priority" />
+    </declare-styleable>
+    
+    <!-- Attributes that can be supplied in an AndroidManifest.xml
+         <code>action</code> tag, a child of the
+         {@link #AndroidManifestIntentFilter intent-filter} tag.
+         See {@link android.content.IntentFilter#addAction} for
+         more information. -->
+    <declare-styleable name="AndroidManifestAction" parent="AndroidManifestIntentFilter">
+        <!-- The name of an action that is handled, using the Java-style
+             naming convention.  For example, to support
+             {@link android.content.Intent#ACTION_VIEW Intent.ACTION_VIEW}
+             you would put <code>android.intent.action.VIEW</code> here.
+             Custom actions should generally use a prefix matching the
+             package name. -->
+        <attr name="name" />
+    </declare-styleable>
+    
+    <!-- Attributes that can be supplied in an AndroidManifest.xml
+         <code>data</code> tag, a child of the
+         {@link #AndroidManifestIntentFilter intent-filter} tag, describing
+         the types of data that match.  This tag can be specified multiple
+         times to supply multiple data options, as described in the
+         {@link android.content.IntentFilter} class.  Note that all such
+         tags are adding options to the same IntentFilter so that, for example,
+         <code>&lt;data android:scheme="myscheme" android:host="me.com" /&gt;</code>
+         is equivalent to <code>&lt;data android:scheme="myscheme" /&gt;
+         &lt;data android:host="me.com" /&gt;</code>. -->
+    <declare-styleable name="AndroidManifestData" parent="AndroidManifestIntentFilter">
+        <!-- Specify a MIME type that is handled, as per
+             {@link android.content.IntentFilter#addDataType
+             IntentFilter.addDataType()}. -->
+        <attr name="mimeType" format="string" />
+        <!-- Specify a URI scheme that is handled, as per
+             {@link android.content.IntentFilter#addDataScheme
+             IntentFilter.addDataScheme()}. -->
+        <attr name="scheme" format="string" />
+        <!-- Specify a URI authority host that is handled, as per
+             {@link android.content.IntentFilter#addDataAuthority
+             IntentFilter.addDataAuthority()}. -->
+        <attr name="host" format="string" />
+        <!-- Specify a URI authority port that is handled, as per
+             {@link android.content.IntentFilter#addDataAuthority
+             IntentFilter.addDataAuthority()}.  If a host is supplied
+             but not a port, any port is matched. -->
+        <attr name="port" format="string" />
+        <!-- Specify a URI path that must exactly match, as per
+             {@link android.content.IntentFilter#addDataPath
+             IntentFilter.addDataAuthority()} with
+             {@link android.os.PatternMatcher#PATTERN_LITERAL}. -->
+        <attr name="path" />
+        <!-- Specify a URI path that must be a prefix to match, as per
+             {@link android.content.IntentFilter#addDataPath
+             IntentFilter.addDataAuthority()} with
+             {@link android.os.PatternMatcher#PATTERN_PREFIX}. -->
+        <attr name="pathPrefix" />
+        <!-- Specify a URI path that matches a simple pattern, as per
+             {@link android.content.IntentFilter#addDataPath
+             IntentFilter.addDataAuthority()} with
+             {@link android.os.PatternMatcher#PATTERN_SIMPLE_GLOB}. 
+             Note that because '\' is used as an escape character when
+             reading the string from XML (before it is parsed as a pattern),
+             you will need to double-escape: for example a literal "*" would
+             be written as "\\*" and a literal "\" would be written as
+             "\\\\".  This is basically the same as what you would need to
+             write if constructing the string in Java code. -->
+        <attr name="pathPattern" />
+    </declare-styleable>
+    
+    <!-- Attributes that can be supplied in an AndroidManifest.xml
+         <code>category</code> tag, a child of the
+         {@link #AndroidManifestIntentFilter intent-filter} tag. 
+         See {@link android.content.IntentFilter#addCategory} for
+         more information. -->
+    <declare-styleable name="AndroidManifestCategory" parent="AndroidManifestIntentFilter">
+        <!-- The name of category that is handled, using the Java-style
+             naming convention.  For example, to support
+             {@link android.content.Intent#CATEGORY_LAUNCHER Intent.CATEGORY_LAUNCHER}
+             you would put <code>android.intent.category.LAUNCHER</code> here.
+             Custom actions should generally use a prefix matching the
+             package name. -->
+        <attr name="name" />
+    </declare-styleable>
+    
+    <!-- Attributes that can be supplied in an AndroidManifest.xml
+         <code>instrumentation</code> tag, a child of the root
+         {@link #AndroidManifest manifest} tag. -->
+    <declare-styleable name="AndroidManifestInstrumentation" parent="AndroidManifest">
+        <!-- Required name of the class implementing the instrumentation, deriving from
+            {@link android.app.Instrumentation}.  This is a fully
+            qualified class name (i.e., com.mycompany.myapp.MyActivity); as a
+            short-hand if the first character of the class
+            is a period then it is appended to your package name. -->
+        <attr name="name" />
+        <attr name="targetPackage" />
+        <attr name="label" />
+        <attr name="icon" />
+        <attr name="handleProfiling" />
+        <attr name="functionalTest" />
+    </declare-styleable>
+    
+    <!-- Declaration of an {@link android.content.Intent} object in XML.  May
+         also include zero or more {@link #IntentCategory <category> and
+         {@link #Extra <extra>} tags. -->
+    <declare-styleable name="Intent">
+        <!-- The action name to assign to the Intent, as per
+            {@link android.content.Intent#setAction Intent.setAction()}. -->
+        <attr name="action" format="string" />
+        <!-- The data URI to assign to the Intent, as per
+            {@link android.content.Intent#setData Intent.setData()}. -->
+        <attr name="data" format="string" />
+        <!-- The MIME type name to assign to the Intent, as per
+            {@link android.content.Intent#setType Intent.setType()}. -->
+        <attr name="mimeType" />
+        <!-- The package part of the ComponentName to assign to the Intent, as per
+            {@link android.content.Intent#setComponent Intent.setComponent()}. -->
+        <attr name="targetPackage" />
+        <!-- The class part of the ComponentName to assign to the Intent, as per
+            {@link android.content.Intent#setComponent Intent.setComponent()}. -->
+        <attr name="targetClass" format="string" />
+    </declare-styleable>
+    
+    <!-- A category to add to an Intent, as per
+            {@link android.content.Intent#addCategory Intent.addCategory()}. -->
+    <declare-styleable name="IntentCategory" parent="Intent">
+        <!-- Required name of the category. -->
+        <attr name="name" />
+    </declare-styleable>
+    
+    <!-- An extra data value to place into a an extra/name value pair held
+            in a Bundle, as per {@link android.os.Bundle}. -->
+    <declare-styleable name="Extra" parent="Intent">
+        <!-- Required name of the extra data. -->
+        <attr name="name" />
+        <!-- Concrete value to put for this named extra data. -->
+        <attr name="value" />
+    </declare-styleable>
+</resources>
diff --git a/core/res/res/values/colors.xml b/core/res/res/values/colors.xml
new file mode 100644
index 0000000..3fb7e86
--- /dev/null
+++ b/core/res/res/values/colors.xml
@@ -0,0 +1,77 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* //device/apps/common/assets/res/any/colors.xml
+**
+** Copyright 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.
+*/
+-->
+<resources>
+	<drawable name="screen_background_light">#ffffffff</drawable>
+	<drawable name="screen_background_dark">#ff262626</drawable>
+    <drawable name="status_bar_closed_default_background">#ff000000</drawable>
+    <drawable name="status_bar_opened_default_background">#ff000000</drawable>
+    <drawable name="search_bar_default_color">#ff000000</drawable>
+    <drawable name="safe_mode_background">#60000000</drawable>
+    <color name="safe_mode_text">#80ffffff</color>
+    <color name="white">#ffffffff</color>
+    <color name="black">#ff000000</color>
+    <color name="transparent">#00000000</color>
+    <color name="background_dark">#ff262626</color>
+    <color name="bright_foreground_dark">#ffffffff</color>
+    <color name="bright_foreground_dark_disabled">#80ffffff</color>
+    <color name="bright_foreground_dark_inverse">#ff000000</color>
+    <color name="dim_foreground_dark">#bebebe</color>
+    <color name="dim_foreground_dark_disabled">#80bebebe</color>
+    <color name="dim_foreground_dark_inverse">#323232</color>
+    <color name="dim_foreground_dark_inverse_disabled">#80323232</color>
+    <color name="hint_foreground_dark">#808080</color>
+    <color name="background_light">#ffffffff</color>
+    <color name="bright_foreground_light">#ff000000</color>
+    <color name="bright_foreground_light_inverse">#ffffffff</color>
+    <color name="bright_foreground_light_disabled">#80000000</color>
+    <color name="dim_foreground_light">#323232</color>
+    <color name="dim_foreground_light_disabled">#80323232</color>
+    <color name="dim_foreground_light_inverse">#bebebe</color>
+    <color name="dim_foreground_light_inverse_disabled">#80bebebe</color>
+    <color name="hint_foreground_light">#808080</color>
+
+    <drawable name="stat_notify_sync_noanim">@drawable/stat_notify_sync_anim0</drawable>
+    <drawable name="stat_sys_download_done">@drawable/stat_sys_download_anim0</drawable>
+    <drawable name="stat_sys_upload_done">@drawable/stat_sys_upload_anim0</drawable>
+    <drawable name="dialog_frame">@drawable/panel_background</drawable>
+    <drawable name="alert_dark_frame">@drawable/popup_full_dark</drawable>
+    <drawable name="alert_light_frame">@drawable/popup_full_bright</drawable>
+    <drawable name="menu_frame">@drawable/menu_background</drawable>
+    <drawable name="menu_full_frame">@drawable/menu_background_fill_parent_width</drawable>
+    <drawable name="editbox_dropdown_dark_frame">@drawable/editbox_dropdown_background_dark</drawable>
+    <drawable name="editbox_dropdown_light_frame">@drawable/editbox_dropdown_background</drawable>
+    
+    <drawable name="input_method_fullscreen_background">#ffffffff</drawable>
+    
+    <!-- For date picker widget -->
+    <drawable name="selected_day_background">#ff0092f4</drawable>
+  
+    <!-- For settings framework -->
+    <color name="lighter_gray">#ddd</color>
+    <color name="darker_gray">#aaa</color>
+
+    <!-- For security permissions -->
+    <color name="perms_dangerous_grp_color">#ffd57e</color>
+    <color name="perms_dangerous_perm_color">#ddb66a</color>
+    <color name="perms_normal_grp_color">#eeeeee</color>
+    <color name="perms_normal_perm_color">#c0c0c0</color>
+    
+</resources>
+
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
new file mode 100644
index 0000000..f370151
--- /dev/null
+++ b/core/res/res/values/config.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 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.
+*/
+-->
+
+<!-- These resources are around just to allow their values to be customized
+     for different hardware and product builds. -->
+<resources>
+    <!-- Flag indicating whether the surface flinger has limited
+         alpha compositing functionality in hardware.  If set, the window
+         manager will disable alpha trasformation in animations where not
+         strictly needed. -->
+    <bool name="config_sf_limitedAlpha">false</bool>
+</resources>
diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml
new file mode 100644
index 0000000..6461460a
--- /dev/null
+++ b/core/res/res/values/dimens.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* //device/apps/common/assets/res/any/dimens.xml
+**
+** Copyright 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.
+*/
+-->
+<resources>
+    <!-- The width that is used when creating thumbnails of applications. -->
+    <dimen name="thumbnail_width">84dp</dimen>
+    <!-- The height that is used when creating thumbnails of applications. -->
+    <dimen name="thumbnail_height">63dp</dimen>
+    <!-- The standard size (both width and height) of an application icon that
+         will be displayed in the app launcher and elsewhere. -->
+    <dimen name="app_icon_size">48dip</dimen>
+    <dimen name="toast_y_offset">64dip</dimen>
+    <!-- Height of the status bar -->
+    <dimen name="status_bar_height">25dip</dimen>
+</resources>
diff --git a/core/res/res/values/ids.xml b/core/res/res/values/ids.xml
new file mode 100644
index 0000000..7e9b7ea
--- /dev/null
+++ b/core/res/res/values/ids.xml
@@ -0,0 +1,68 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+**
+** Copyright 2007, 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.
+*/
+-->
+<resources>
+  <item type="id" name="background" />
+  <item type="id" name="checkbox" />
+  <item type="id" name="content" />
+  <item type="id" name="empty" />
+  <item type="id" name="hint" />
+  <item type="id" name="icon" />
+  <item type="id" name="icon1" />
+  <item type="id" name="icon2" />
+  <item type="id" name="input" />
+  <item type="id" name="left_icon" />
+  <item type="id" name="list" />
+  <item type="id" name="menu" />
+  <item type="id" name="message" />
+  <item type="id" name="primary" />
+  <item type="id" name="progress" />
+  <item type="id" name="right_icon" />
+  <item type="id" name="summary" />
+  <item type="id" name="selectedIcon" />
+  <item type="id" name="tabcontent" />
+  <item type="id" name="tabhost" />
+  <item type="id" name="tabs" />
+  <item type="id" name="text1" />
+  <item type="id" name="text2" />
+  <item type="id" name="title" />
+  <item type="id" name="title_container" />
+  <item type="id" name="toggle" />
+  <item type="id" name="secondaryProgress" />
+  <item type="id" name="lock_screen" />
+  <item type="id" name="edit" />
+  <item type="id" name="widget_frame" />
+  <item type="id" name="button1" />
+  <item type="id" name="button2" />
+  <item type="id" name="button3" />
+  <item type="id" name="extractArea" />
+  <item type="id" name="candidatesArea" />
+  <item type="id" name="inputArea" />
+  <item type="id" name="inputExtractEditText" />
+  <item type="id" name="selectAll" />
+  <item type="id" name="cut" />
+  <item type="id" name="copy" />
+  <item type="id" name="paste" />
+  <item type="id" name="copyUrl" />
+  <item type="id" name="switchInputMethod" />
+  <item type="id" name="keyboardView" />
+  <item type="id" name="button_close" />
+  <item type="id" name="startSelectingText" />
+  <item type="id" name="stopSelectingText" />
+  <item type="id" name="addToDictionary" />
+</resources>
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
new file mode 100644
index 0000000..94d0793
--- /dev/null
+++ b/core/res/res/values/public.xml
@@ -0,0 +1,1097 @@
+<!-- This file defines the base public resources exported by the
+     platform, which must always exist. -->
+
+<!-- ***************************************************************
+     ***************************************************************
+     IMPORTANT NOTE FOR ANYONE MODIFYING THIS FILE
+     READ THIS BEFORE YOU MAKE ANY CHANGES
+     
+     This file defines the binary compatibility for resources.  As such,
+     you must be very careful when making changes here, or you will
+     completely break backwards compatibility with old applications.
+     
+     To avoid breaking compatibility, all new resources must be placed
+     at the end of the list of resources of the same type.  Placing a resource
+     in the middle of type will cause all following resources to be
+     assigned new resource numbers, breaking compatibility.
+     
+     ***************************************************************
+     *************************************************************** -->
+<resources>
+
+  <!-- We don't want to publish private symbols in android.R as part of the
+       SDK.  Instead, put them here. -->
+  <private-symbols package="com.android.internal" />
+    
+  <!-- AndroidManifest.xml attributes. -->
+  <eat-comment />
+
+<!-- ===============================================================
+     Resources for version 1 of the platform.
+     =============================================================== -->
+  <eat-comment />
+
+  <public type="attr" name="theme" id="0x01010000" />
+  <public type="attr" name="label" id="0x01010001" />
+  <public type="attr" name="icon" id="0x01010002" />
+  <public type="attr" name="name" id="0x01010003" />
+  <public type="attr" name="manageSpaceActivity" id="0x01010004" />
+  <public type="attr" name="allowClearUserData" id="0x01010005" />
+  <public type="attr" name="permission" id="0x01010006" />
+  <public type="attr" name="readPermission" id="0x01010007" />
+  <public type="attr" name="writePermission" id="0x01010008" />
+  <public type="attr" name="protectionLevel" id="0x01010009" />
+  <public type="attr" name="permissionGroup" id="0x0101000a" />
+  <public type="attr" name="sharedUserId" id="0x0101000b" />
+  <public type="attr" name="hasCode" id="0x0101000c" />
+  <public type="attr" name="persistent" id="0x0101000d" />
+  <public type="attr" name="enabled" id="0x0101000e" />
+  <public type="attr" name="debuggable" id="0x0101000f" />
+  <public type="attr" name="exported" id="0x01010010" />
+  <public type="attr" name="process" id="0x01010011" />
+  <public type="attr" name="taskAffinity" id="0x01010012" />
+  <public type="attr" name="multiprocess" id="0x01010013" />
+  <public type="attr" name="finishOnTaskLaunch" id="0x01010014" />
+  <public type="attr" name="clearTaskOnLaunch" id="0x01010015" />
+  <public type="attr" name="stateNotNeeded" id="0x01010016" />
+  <public type="attr" name="excludeFromRecents" id="0x01010017" />
+  <public type="attr" name="authorities" id="0x01010018" />
+  <public type="attr" name="syncable" id="0x01010019" />
+  <public type="attr" name="initOrder" id="0x0101001a" />
+  <public type="attr" name="grantUriPermissions" id="0x0101001b" />
+  <public type="attr" name="priority" id="0x0101001c" />
+  <public type="attr" name="launchMode" id="0x0101001d" />
+  <public type="attr" name="screenOrientation" id="0x0101001e" />
+  <public type="attr" name="configChanges" id="0x0101001f" />
+  <public type="attr" name="description" id="0x01010020" />
+  <public type="attr" name="targetPackage" id="0x01010021" />
+  <public type="attr" name="handleProfiling" id="0x01010022" />
+  <public type="attr" name="functionalTest" id="0x01010023" />
+  <public type="attr" name="value" id="0x01010024" />
+  <public type="attr" name="resource" id="0x01010025" />
+  <public type="attr" name="mimeType" id="0x01010026" />
+  <public type="attr" name="scheme" id="0x01010027" />
+  <public type="attr" name="host" id="0x01010028" />
+  <public type="attr" name="port" id="0x01010029" />
+  <public type="attr" name="path" id="0x0101002a" />
+  <public type="attr" name="pathPrefix" id="0x0101002b" />
+  <public type="attr" name="pathPattern" id="0x0101002c" />
+  <public type="attr" name="action" id="0x0101002d" />
+  <public type="attr" name="data" id="0x0101002e" />
+  <public type="attr" name="targetClass" id="0x0101002f" />
+  <public type="attr" name="colorForeground" id="0x01010030" />
+  <public type="attr" name="colorBackground" id="0x01010031" />
+  <public type="attr" name="backgroundDimAmount" id="0x01010032" />
+  <public type="attr" name="disabledAlpha" id="0x01010033" />
+  <public type="attr" name="textAppearance" id="0x01010034" />
+  <public type="attr" name="textAppearanceInverse" id="0x01010035" />
+  <public type="attr" name="textColorPrimary" id="0x01010036" />
+  <public type="attr" name="textColorPrimaryDisableOnly" id="0x01010037" />
+  <public type="attr" name="textColorSecondary" id="0x01010038" />
+  <public type="attr" name="textColorPrimaryInverse" id="0x01010039" />
+  <public type="attr" name="textColorSecondaryInverse" id="0x0101003a" />
+  <public type="attr" name="textColorPrimaryNoDisable" id="0x0101003b" />
+  <public type="attr" name="textColorSecondaryNoDisable" id="0x0101003c" />
+  <public type="attr" name="textColorPrimaryInverseNoDisable" id="0x0101003d" />
+  <public type="attr" name="textColorSecondaryInverseNoDisable" id="0x0101003e" />
+  <public type="attr" name="textColorHintInverse" id="0x0101003f" />
+  <public type="attr" name="textAppearanceLarge" id="0x01010040" />
+  <public type="attr" name="textAppearanceMedium" id="0x01010041" />
+  <public type="attr" name="textAppearanceSmall" id="0x01010042" />
+  <public type="attr" name="textAppearanceLargeInverse" id="0x01010043" />
+  <public type="attr" name="textAppearanceMediumInverse" id="0x01010044" />
+  <public type="attr" name="textAppearanceSmallInverse" id="0x01010045" />
+  <public type="attr" name="textCheckMark" id="0x01010046" />
+  <public type="attr" name="textCheckMarkInverse" id="0x01010047" />
+  <public type="attr" name="buttonStyle" id="0x01010048" />
+  <public type="attr" name="buttonStyleSmall" id="0x01010049" />
+  <public type="attr" name="buttonStyleInset" id="0x0101004a" />
+  <public type="attr" name="buttonStyleToggle" id="0x0101004b" />
+  <public type="attr" name="galleryItemBackground" id="0x0101004c" />
+  <public type="attr" name="listPreferredItemHeight" id="0x0101004d" />
+  <public type="attr" name="expandableListPreferredItemPaddingLeft" id="0x0101004e" />
+  <public type="attr" name="expandableListPreferredChildPaddingLeft" id="0x0101004f" />
+  <public type="attr" name="expandableListPreferredItemIndicatorLeft" id="0x01010050" />
+  <public type="attr" name="expandableListPreferredItemIndicatorRight" id="0x01010051" />
+  <public type="attr" name="expandableListPreferredChildIndicatorLeft" id="0x01010052" />
+  <public type="attr" name="expandableListPreferredChildIndicatorRight" id="0x01010053" />
+  <public type="attr" name="windowBackground" id="0x01010054" />
+  <public type="attr" name="windowFrame" id="0x01010055" />
+  <public type="attr" name="windowNoTitle" id="0x01010056" />
+  <public type="attr" name="windowIsFloating" id="0x01010057" />
+  <public type="attr" name="windowIsTranslucent" id="0x01010058" />
+  <public type="attr" name="windowContentOverlay" id="0x01010059" />
+  <public type="attr" name="windowTitleSize" id="0x0101005a" />
+  <public type="attr" name="windowTitleStyle" id="0x0101005b" />
+  <public type="attr" name="windowTitleBackgroundStyle" id="0x0101005c" />
+  <public type="attr" name="alertDialogStyle" id="0x0101005d" />
+  <public type="attr" name="panelBackground" id="0x0101005e" />
+  <public type="attr" name="panelFullBackground" id="0x0101005f" />
+  <public type="attr" name="panelColorForeground" id="0x01010060" />
+  <public type="attr" name="panelColorBackground" id="0x01010061" />
+  <public type="attr" name="panelTextAppearance" id="0x01010062" />
+  <public type="attr" name="scrollbarSize" id="0x01010063" />
+  <public type="attr" name="scrollbarThumbHorizontal" id="0x01010064" />
+  <public type="attr" name="scrollbarThumbVertical" id="0x01010065" />
+  <public type="attr" name="scrollbarTrackHorizontal" id="0x01010066" />
+  <public type="attr" name="scrollbarTrackVertical" id="0x01010067" />
+  <public type="attr" name="scrollbarAlwaysDrawHorizontalTrack" id="0x01010068" />
+  <public type="attr" name="scrollbarAlwaysDrawVerticalTrack" id="0x01010069" />
+  <public type="attr" name="absListViewStyle" id="0x0101006a" />
+  <public type="attr" name="autoCompleteTextViewStyle" id="0x0101006b" />
+  <public type="attr" name="checkboxStyle" id="0x0101006c" />
+  <public type="attr" name="dropDownListViewStyle" id="0x0101006d" />
+  <public type="attr" name="editTextStyle" id="0x0101006e" />
+  <public type="attr" name="expandableListViewStyle" id="0x0101006f" />
+  <public type="attr" name="galleryStyle" id="0x01010070" />
+  <public type="attr" name="gridViewStyle" id="0x01010071" />
+  <public type="attr" name="imageButtonStyle" id="0x01010072" />
+  <public type="attr" name="imageWellStyle" id="0x01010073" />
+  <public type="attr" name="listViewStyle" id="0x01010074" />
+  <public type="attr" name="listViewWhiteStyle" id="0x01010075" />
+  <public type="attr" name="popupWindowStyle" id="0x01010076" />
+  <public type="attr" name="progressBarStyle" id="0x01010077" />
+  <public type="attr" name="progressBarStyleHorizontal" id="0x01010078" />
+  <public type="attr" name="progressBarStyleSmall" id="0x01010079" />
+  <public type="attr" name="progressBarStyleLarge" id="0x0101007a" />
+  <public type="attr" name="seekBarStyle" id="0x0101007b" />
+  <public type="attr" name="ratingBarStyle" id="0x0101007c" />
+  <public type="attr" name="ratingBarStyleSmall" id="0x0101007d" />
+  <public type="attr" name="radioButtonStyle" id="0x0101007e" />
+  <public type="attr" name="scrollbarStyle" id="0x0101007f" />
+  <public type="attr" name="scrollViewStyle" id="0x01010080" />
+  <public type="attr" name="spinnerStyle" id="0x01010081" />
+  <public type="attr" name="starStyle" id="0x01010082" />
+  <public type="attr" name="tabWidgetStyle" id="0x01010083" />
+  <public type="attr" name="textViewStyle" id="0x01010084" />
+  <public type="attr" name="webViewStyle" id="0x01010085" />
+  <public type="attr" name="dropDownItemStyle" id="0x01010086" />
+  <public type="attr" name="spinnerDropDownItemStyle" id="0x01010087" />
+  <public type="attr" name="dropDownHintAppearance" id="0x01010088" />
+  <public type="attr" name="spinnerItemStyle" id="0x01010089" />
+  <public type="attr" name="mapViewStyle" id="0x0101008a" />
+  <public type="attr" name="preferenceScreenStyle" id="0x0101008b" />
+  <public type="attr" name="preferenceCategoryStyle" id="0x0101008c" />
+  <public type="attr" name="preferenceInformationStyle" id="0x0101008d" />
+  <public type="attr" name="preferenceStyle" id="0x0101008e" />
+  <public type="attr" name="checkBoxPreferenceStyle" id="0x0101008f" />
+  <public type="attr" name="yesNoPreferenceStyle" id="0x01010090" />
+  <public type="attr" name="dialogPreferenceStyle" id="0x01010091" />
+  <public type="attr" name="editTextPreferenceStyle" id="0x01010092" />
+  <public type="attr" name="ringtonePreferenceStyle" id="0x01010093" />
+  <public type="attr" name="preferenceLayoutChild" id="0x01010094" />
+  <public type="attr" name="textSize" id="0x01010095" />
+  <public type="attr" name="typeface" id="0x01010096" />
+  <public type="attr" name="textStyle" id="0x01010097" />
+  <public type="attr" name="textColor" id="0x01010098" />
+  <public type="attr" name="textColorHighlight" id="0x01010099" />
+  <public type="attr" name="textColorHint" id="0x0101009a" />
+  <public type="attr" name="textColorLink" id="0x0101009b" />
+  <public type="attr" name="state_focused" id="0x0101009c" />
+  <public type="attr" name="state_window_focused" id="0x0101009d" />
+  <public type="attr" name="state_enabled" id="0x0101009e" />
+  <public type="attr" name="state_checkable" id="0x0101009f" />
+  <public type="attr" name="state_checked" id="0x010100a0" />
+  <public type="attr" name="state_selected" id="0x010100a1" />
+  <public type="attr" name="state_active" id="0x010100a2" />
+  <public type="attr" name="state_single" id="0x010100a3" />
+  <public type="attr" name="state_first" id="0x010100a4" />
+  <public type="attr" name="state_middle" id="0x010100a5" />
+  <public type="attr" name="state_last" id="0x010100a6" />
+  <public type="attr" name="state_pressed" id="0x010100a7" />
+  <public type="attr" name="state_expanded" id="0x010100a8" />
+  <public type="attr" name="state_empty" id="0x010100a9" />
+  <public type="attr" name="state_above_anchor" id="0x010100aa" />
+  <public type="attr" name="ellipsize" id="0x010100ab" />
+  <public type="attr" name="x" id="0x010100ac" />
+  <public type="attr" name="y" id="0x010100ad" />
+  <public type="attr" name="windowAnimationStyle" id="0x010100ae" />
+  <public type="attr" name="gravity" id="0x010100af" />
+  <public type="attr" name="autoLink" id="0x010100b0" />
+  <public type="attr" name="linksClickable" id="0x010100b1" />
+  <public type="attr" name="entries" id="0x010100b2" />
+  <public type="attr" name="layout_gravity" id="0x010100b3" />
+  <public type="attr" name="windowEnterAnimation" id="0x010100b4" />
+  <public type="attr" name="windowExitAnimation" id="0x010100b5" />
+  <public type="attr" name="windowShowAnimation" id="0x010100b6" />
+  <public type="attr" name="windowHideAnimation" id="0x010100b7" />
+  <public type="attr" name="activityOpenEnterAnimation" id="0x010100b8" />
+  <public type="attr" name="activityOpenExitAnimation" id="0x010100b9" />
+  <public type="attr" name="activityCloseEnterAnimation" id="0x010100ba" />
+  <public type="attr" name="activityCloseExitAnimation" id="0x010100bb" />
+  <public type="attr" name="taskOpenEnterAnimation" id="0x010100bc" />
+  <public type="attr" name="taskOpenExitAnimation" id="0x010100bd" />
+  <public type="attr" name="taskCloseEnterAnimation" id="0x010100be" />
+  <public type="attr" name="taskCloseExitAnimation" id="0x010100bf" />
+  <public type="attr" name="taskToFrontEnterAnimation" id="0x010100c0" />
+  <public type="attr" name="taskToFrontExitAnimation" id="0x010100c1" />
+  <public type="attr" name="taskToBackEnterAnimation" id="0x010100c2" />
+  <public type="attr" name="taskToBackExitAnimation" id="0x010100c3" />
+  <public type="attr" name="orientation" id="0x010100c4" />
+  <public type="attr" name="keycode" id="0x010100c5" />
+  <public type="attr" name="fullDark" id="0x010100c6" />
+  <public type="attr" name="topDark" id="0x010100c7" />
+  <public type="attr" name="centerDark" id="0x010100c8" />
+  <public type="attr" name="bottomDark" id="0x010100c9" />
+  <public type="attr" name="fullBright" id="0x010100ca" />
+  <public type="attr" name="topBright" id="0x010100cb" />
+  <public type="attr" name="centerBright" id="0x010100cc" />
+  <public type="attr" name="bottomBright" id="0x010100cd" />
+  <public type="attr" name="bottomMedium" id="0x010100ce" />
+  <public type="attr" name="centerMedium" id="0x010100cf" />
+  <public type="attr" name="id" id="0x010100d0" />
+  <public type="attr" name="tag" id="0x010100d1" />
+  <public type="attr" name="scrollX" id="0x010100d2" />
+  <public type="attr" name="scrollY" id="0x010100d3" />
+  <public type="attr" name="background" id="0x010100d4" />
+  <public type="attr" name="padding" id="0x010100d5" />
+  <public type="attr" name="paddingLeft" id="0x010100d6" />
+  <public type="attr" name="paddingTop" id="0x010100d7" />
+  <public type="attr" name="paddingRight" id="0x010100d8" />
+  <public type="attr" name="paddingBottom" id="0x010100d9" />
+  <public type="attr" name="focusable" id="0x010100da" />
+  <public type="attr" name="focusableInTouchMode" id="0x010100db" />
+  <public type="attr" name="visibility" id="0x010100dc" />
+  <public type="attr" name="fitsSystemWindows" id="0x010100dd" />
+  <public type="attr" name="scrollbars" id="0x010100de" />
+  <public type="attr" name="fadingEdge" id="0x010100df" />
+  <public type="attr" name="fadingEdgeLength" id="0x010100e0" />
+  <public type="attr" name="nextFocusLeft" id="0x010100e1" />
+  <public type="attr" name="nextFocusRight" id="0x010100e2" />
+  <public type="attr" name="nextFocusUp" id="0x010100e3" />
+  <public type="attr" name="nextFocusDown" id="0x010100e4" />
+  <public type="attr" name="clickable" id="0x010100e5" />
+  <public type="attr" name="longClickable" id="0x010100e6" />
+  <public type="attr" name="saveEnabled" id="0x010100e7" />
+  <public type="attr" name="drawingCacheQuality" id="0x010100e8" />
+  <public type="attr" name="duplicateParentState" id="0x010100e9" />
+  <public type="attr" name="clipChildren" id="0x010100ea" />
+  <public type="attr" name="clipToPadding" id="0x010100eb" />
+  <public type="attr" name="layoutAnimation" id="0x010100ec" />
+  <public type="attr" name="animationCache" id="0x010100ed" />
+  <public type="attr" name="persistentDrawingCache" id="0x010100ee" />
+  <public type="attr" name="alwaysDrawnWithCache" id="0x010100ef" />
+  <public type="attr" name="addStatesFromChildren" id="0x010100f0" />
+  <public type="attr" name="descendantFocusability" id="0x010100f1" />
+  <public type="attr" name="layout" id="0x010100f2" />
+  <public type="attr" name="inflatedId" id="0x010100f3" />
+  <public type="attr" name="layout_width" id="0x010100f4" />
+  <public type="attr" name="layout_height" id="0x010100f5" />
+  <public type="attr" name="layout_margin" id="0x010100f6" />
+  <public type="attr" name="layout_marginLeft" id="0x010100f7" />
+  <public type="attr" name="layout_marginTop" id="0x010100f8" />
+  <public type="attr" name="layout_marginRight" id="0x010100f9" />
+  <public type="attr" name="layout_marginBottom" id="0x010100fa" />
+  <public type="attr" name="listSelector" id="0x010100fb" />
+  <public type="attr" name="drawSelectorOnTop" id="0x010100fc" />
+  <public type="attr" name="stackFromBottom" id="0x010100fd" />
+  <public type="attr" name="scrollingCache" id="0x010100fe" />
+  <public type="attr" name="textFilterEnabled" id="0x010100ff" />
+  <public type="attr" name="transcriptMode" id="0x01010100" />
+  <public type="attr" name="cacheColorHint" id="0x01010101" />
+  <public type="attr" name="dial" id="0x01010102" />
+  <public type="attr" name="hand_hour" id="0x01010103" />
+  <public type="attr" name="hand_minute" id="0x01010104" />
+  <public type="attr" name="format" id="0x01010105" />
+  <public type="attr" name="checked" id="0x01010106" />
+  <public type="attr" name="button" id="0x01010107" />
+  <public type="attr" name="checkMark" id="0x01010108" />
+  <public type="attr" name="foreground" id="0x01010109" />
+  <public type="attr" name="measureAllChildren" id="0x0101010a" />
+  <public type="attr" name="groupIndicator" id="0x0101010b" />
+  <public type="attr" name="childIndicator" id="0x0101010c" />
+  <public type="attr" name="indicatorLeft" id="0x0101010d" />
+  <public type="attr" name="indicatorRight" id="0x0101010e" />
+  <public type="attr" name="childIndicatorLeft" id="0x0101010f" />
+  <public type="attr" name="childIndicatorRight" id="0x01010110" />
+  <public type="attr" name="childDivider" id="0x01010111" />
+  <public type="attr" name="animationDuration" id="0x01010112" />
+  <public type="attr" name="spacing" id="0x01010113" />
+  <public type="attr" name="horizontalSpacing" id="0x01010114" />
+  <public type="attr" name="verticalSpacing" id="0x01010115" />
+  <public type="attr" name="stretchMode" id="0x01010116" />
+  <public type="attr" name="columnWidth" id="0x01010117" />
+  <public type="attr" name="numColumns" id="0x01010118" />
+  <public type="attr" name="src" id="0x01010119" />
+  <public type="attr" name="antialias" id="0x0101011a" />
+  <public type="attr" name="filter" id="0x0101011b" />
+  <public type="attr" name="dither" id="0x0101011c" />
+  <public type="attr" name="scaleType" id="0x0101011d" />
+  <public type="attr" name="adjustViewBounds" id="0x0101011e" />
+  <public type="attr" name="maxWidth" id="0x0101011f" />
+  <public type="attr" name="maxHeight" id="0x01010120" />
+  <public type="attr" name="tint" id="0x01010121" />
+  <public type="attr" name="baselineAlignBottom" id="0x01010122" />
+  <public type="attr" name="cropToPadding" id="0x01010123" />
+  <public type="attr" name="textOn" id="0x01010124" />
+  <public type="attr" name="textOff" id="0x01010125" />
+  <public type="attr" name="baselineAligned" id="0x01010126" />
+  <public type="attr" name="baselineAlignedChildIndex" id="0x01010127" />
+  <public type="attr" name="weightSum" id="0x01010128" />
+  <public type="attr" name="divider" id="0x01010129" />
+  <public type="attr" name="dividerHeight" id="0x0101012a" />
+  <public type="attr" name="choiceMode" id="0x0101012b" />
+  <public type="attr" name="itemTextAppearance" id="0x0101012c" />
+  <public type="attr" name="horizontalDivider" id="0x0101012d" />
+  <public type="attr" name="verticalDivider" id="0x0101012e" />
+  <public type="attr" name="headerBackground" id="0x0101012f" />
+  <public type="attr" name="itemBackground" id="0x01010130" />
+  <public type="attr" name="itemIconDisabledAlpha" id="0x01010131" />
+  <public type="attr" name="rowHeight" id="0x01010132" />
+  <public type="attr" name="maxRows" id="0x01010133" />
+  <public type="attr" name="maxItemsPerRow" id="0x01010134" />
+  <public type="attr" name="moreIcon" id="0x01010135" />
+  <public type="attr" name="max" id="0x01010136" />
+  <public type="attr" name="progress" id="0x01010137" />
+  <public type="attr" name="secondaryProgress" id="0x01010138" />
+  <public type="attr" name="indeterminate" id="0x01010139" />
+  <public type="attr" name="indeterminateOnly" id="0x0101013a" />
+  <public type="attr" name="indeterminateDrawable" id="0x0101013b" />
+  <public type="attr" name="progressDrawable" id="0x0101013c" />
+  <public type="attr" name="indeterminateDuration" id="0x0101013d" />
+  <public type="attr" name="indeterminateBehavior" id="0x0101013e" />
+  <public type="attr" name="minWidth" id="0x0101013f" />
+  <public type="attr" name="minHeight" id="0x01010140" />
+  <public type="attr" name="interpolator" id="0x01010141" />
+  <public type="attr" name="thumb" id="0x01010142" />
+  <public type="attr" name="thumbOffset" id="0x01010143" />
+  <public type="attr" name="numStars" id="0x01010144" />
+  <public type="attr" name="rating" id="0x01010145" />
+  <public type="attr" name="stepSize" id="0x01010146" />
+  <public type="attr" name="isIndicator" id="0x01010147" />
+  <public type="attr" name="checkedButton" id="0x01010148" />
+  <public type="attr" name="stretchColumns" id="0x01010149" />
+  <public type="attr" name="shrinkColumns" id="0x0101014a" />
+  <public type="attr" name="collapseColumns" id="0x0101014b" />
+  <public type="attr" name="layout_column" id="0x0101014c" />
+  <public type="attr" name="layout_span" id="0x0101014d" />
+  <public type="attr" name="bufferType" id="0x0101014e" />
+  <public type="attr" name="text" id="0x0101014f" />
+  <public type="attr" name="hint" id="0x01010150" />
+  <public type="attr" name="textScaleX" id="0x01010151" />
+  <public type="attr" name="cursorVisible" id="0x01010152" />
+  <public type="attr" name="maxLines" id="0x01010153" />
+  <public type="attr" name="lines" id="0x01010154" />
+  <public type="attr" name="height" id="0x01010155" />
+  <public type="attr" name="minLines" id="0x01010156" />
+  <public type="attr" name="maxEms" id="0x01010157" />
+  <public type="attr" name="ems" id="0x01010158" />
+  <public type="attr" name="width" id="0x01010159" />
+  <public type="attr" name="minEms" id="0x0101015a" />
+  <public type="attr" name="scrollHorizontally" id="0x0101015b" />
+  <public type="attr" name="password" id="0x0101015c" />
+  <public type="attr" name="singleLine" id="0x0101015d" />
+  <public type="attr" name="selectAllOnFocus" id="0x0101015e" />
+  <public type="attr" name="includeFontPadding" id="0x0101015f" />
+  <public type="attr" name="maxLength" id="0x01010160" />
+  <public type="attr" name="shadowColor" id="0x01010161" />
+  <public type="attr" name="shadowDx" id="0x01010162" />
+  <public type="attr" name="shadowDy" id="0x01010163" />
+  <public type="attr" name="shadowRadius" id="0x01010164" />
+  <public type="attr" name="numeric" id="0x01010165" />
+  <public type="attr" name="digits" id="0x01010166" />
+  <public type="attr" name="phoneNumber" id="0x01010167" />
+  <public type="attr" name="inputMethod" id="0x01010168" />
+  <public type="attr" name="capitalize" id="0x01010169" />
+  <public type="attr" name="autoText" id="0x0101016a" />
+  <public type="attr" name="editable" id="0x0101016b" />
+  <public type="attr" name="freezesText" id="0x0101016c" />
+  <public type="attr" name="drawableTop" id="0x0101016d" />
+  <public type="attr" name="drawableBottom" id="0x0101016e" />
+  <public type="attr" name="drawableLeft" id="0x0101016f" />
+  <public type="attr" name="drawableRight" id="0x01010170" />
+  <public type="attr" name="drawablePadding" id="0x01010171" />
+  <public type="attr" name="completionHint" id="0x01010172" />
+  <public type="attr" name="completionHintView" id="0x01010173" />
+  <public type="attr" name="completionThreshold" id="0x01010174" />
+  <public type="attr" name="dropDownSelector" id="0x01010175" />
+  <public type="attr" name="popupBackground" id="0x01010176" />
+  <public type="attr" name="inAnimation" id="0x01010177" />
+  <public type="attr" name="outAnimation" id="0x01010178" />
+  <public type="attr" name="flipInterval" id="0x01010179" />
+  <public type="attr" name="fillViewport" id="0x0101017a" />
+  <public type="attr" name="prompt" id="0x0101017b" />
+  <public type="attr" name="startYear" id="0x0101017c" />
+  <public type="attr" name="endYear" id="0x0101017d" />
+  <public type="attr" name="mode" id="0x0101017e" />
+  <public type="attr" name="layout_x" id="0x0101017f" />
+  <public type="attr" name="layout_y" id="0x01010180" />
+  <public type="attr" name="layout_weight" id="0x01010181" />
+  <public type="attr" name="layout_toLeftOf" id="0x01010182" />
+  <public type="attr" name="layout_toRightOf" id="0x01010183" />
+  <public type="attr" name="layout_above" id="0x01010184" />
+  <public type="attr" name="layout_below" id="0x01010185" />
+  <public type="attr" name="layout_alignBaseline" id="0x01010186" />
+  <public type="attr" name="layout_alignLeft" id="0x01010187" />
+  <public type="attr" name="layout_alignTop" id="0x01010188" />
+  <public type="attr" name="layout_alignRight" id="0x01010189" />
+  <public type="attr" name="layout_alignBottom" id="0x0101018a" />
+  <public type="attr" name="layout_alignParentLeft" id="0x0101018b" />
+  <public type="attr" name="layout_alignParentTop" id="0x0101018c" />
+  <public type="attr" name="layout_alignParentRight" id="0x0101018d" />
+  <public type="attr" name="layout_alignParentBottom" id="0x0101018e" />
+  <public type="attr" name="layout_centerInParent" id="0x0101018f" />
+  <public type="attr" name="layout_centerHorizontal" id="0x01010190" />
+  <public type="attr" name="layout_centerVertical" id="0x01010191" />
+  <public type="attr" name="layout_alignWithParentIfMissing" id="0x01010192" />
+  <public type="attr" name="layout_scale" id="0x01010193" />
+  <public type="attr" name="visible" id="0x01010194" />
+  <public type="attr" name="variablePadding" id="0x01010195" />
+  <public type="attr" name="constantSize" id="0x01010196" />
+  <public type="attr" name="oneshot" id="0x01010197" />
+  <public type="attr" name="duration" id="0x01010198" />
+  <public type="attr" name="drawable" id="0x01010199" />
+  <public type="attr" name="shape" id="0x0101019a" />
+  <public type="attr" name="innerRadiusRatio" id="0x0101019b" />
+  <public type="attr" name="thicknessRatio" id="0x0101019c" />
+  <public type="attr" name="startColor" id="0x0101019d" />
+  <public type="attr" name="endColor" id="0x0101019e" />
+  <public type="attr" name="useLevel" id="0x0101019f" />
+  <public type="attr" name="angle" id="0x010101a0" />
+  <public type="attr" name="type" id="0x010101a1" />
+  <public type="attr" name="centerX" id="0x010101a2" />
+  <public type="attr" name="centerY" id="0x010101a3" />
+  <public type="attr" name="gradientRadius" id="0x010101a4" />
+  <public type="attr" name="color" id="0x010101a5" />
+  <public type="attr" name="dashWidth" id="0x010101a6" />
+  <public type="attr" name="dashGap" id="0x010101a7" />
+  <public type="attr" name="radius" id="0x010101a8" />
+  <public type="attr" name="topLeftRadius" id="0x010101a9" />
+  <public type="attr" name="topRightRadius" id="0x010101aa" />
+  <public type="attr" name="bottomLeftRadius" id="0x010101ab" />
+  <public type="attr" name="bottomRightRadius" id="0x010101ac" />
+  <public type="attr" name="left" id="0x010101ad" />
+  <public type="attr" name="top" id="0x010101ae" />
+  <public type="attr" name="right" id="0x010101af" />
+  <public type="attr" name="bottom" id="0x010101b0" />
+  <public type="attr" name="minLevel" id="0x010101b1" />
+  <public type="attr" name="maxLevel" id="0x010101b2" />
+  <public type="attr" name="fromDegrees" id="0x010101b3" />
+  <public type="attr" name="toDegrees" id="0x010101b4" />
+  <public type="attr" name="pivotX" id="0x010101b5" />
+  <public type="attr" name="pivotY" id="0x010101b6" />
+  <public type="attr" name="insetLeft" id="0x010101b7" />
+  <public type="attr" name="insetRight" id="0x010101b8" />
+  <public type="attr" name="insetTop" id="0x010101b9" />
+  <public type="attr" name="insetBottom" id="0x010101ba" />
+  <public type="attr" name="shareInterpolator" id="0x010101bb" />
+  <public type="attr" name="fillBefore" id="0x010101bc" />
+  <public type="attr" name="fillAfter" id="0x010101bd" />
+  <public type="attr" name="startOffset" id="0x010101be" />
+  <public type="attr" name="repeatCount" id="0x010101bf" />
+  <public type="attr" name="repeatMode" id="0x010101c0" />
+  <public type="attr" name="zAdjustment" id="0x010101c1" />
+  <public type="attr" name="fromXScale" id="0x010101c2" />
+  <public type="attr" name="toXScale" id="0x010101c3" />
+  <public type="attr" name="fromYScale" id="0x010101c4" />
+  <public type="attr" name="toYScale" id="0x010101c5" />
+  <public type="attr" name="fromXDelta" id="0x010101c6" />
+  <public type="attr" name="toXDelta" id="0x010101c7" />
+  <public type="attr" name="fromYDelta" id="0x010101c8" />
+  <public type="attr" name="toYDelta" id="0x010101c9" />
+  <public type="attr" name="fromAlpha" id="0x010101ca" />
+  <public type="attr" name="toAlpha" id="0x010101cb" />
+  <public type="attr" name="delay" id="0x010101cc" />
+  <public type="attr" name="animation" id="0x010101cd" />
+  <public type="attr" name="animationOrder" id="0x010101ce" />
+  <public type="attr" name="columnDelay" id="0x010101cf" />
+  <public type="attr" name="rowDelay" id="0x010101d0" />
+  <public type="attr" name="direction" id="0x010101d1" />
+  <public type="attr" name="directionPriority" id="0x010101d2" />
+  <public type="attr" name="factor" id="0x010101d3" />
+  <public type="attr" name="cycles" id="0x010101d4" />
+  <public type="attr" name="searchMode" id="0x010101d5" />
+  <public type="attr" name="searchSuggestAuthority" id="0x010101d6" />
+  <public type="attr" name="searchSuggestPath" id="0x010101d7" />
+  <public type="attr" name="searchSuggestSelection" id="0x010101d8" />
+  <public type="attr" name="searchSuggestIntentAction" id="0x010101d9" />
+  <public type="attr" name="searchSuggestIntentData" id="0x010101da" />
+  <public type="attr" name="queryActionMsg" id="0x010101db" />
+  <public type="attr" name="suggestActionMsg" id="0x010101dc" />
+  <public type="attr" name="suggestActionMsgColumn" id="0x010101dd" />
+  <public type="attr" name="menuCategory" id="0x010101de" />
+  <public type="attr" name="orderInCategory" id="0x010101df" />
+  <public type="attr" name="checkableBehavior" id="0x010101e0" />
+  <public type="attr" name="title" id="0x010101e1" />
+  <public type="attr" name="titleCondensed" id="0x010101e2" />
+  <public type="attr" name="alphabeticShortcut" id="0x010101e3" />
+  <public type="attr" name="numericShortcut" id="0x010101e4" />
+  <public type="attr" name="checkable" id="0x010101e5" />
+  <public type="attr" name="selectable" id="0x010101e6" />
+  <public type="attr" name="orderingFromXml" id="0x010101e7" />
+  <public type="attr" name="key" id="0x010101e8" />
+  <public type="attr" name="summary" id="0x010101e9" />
+  <public type="attr" name="order" id="0x010101ea" />
+  <public type="attr" name="widgetLayout" id="0x010101eb" />
+  <public type="attr" name="dependency" id="0x010101ec" />
+  <public type="attr" name="defaultValue" id="0x010101ed" />
+  <public type="attr" name="shouldDisableView" id="0x010101ee" />
+  <public type="attr" name="summaryOn" id="0x010101ef" />
+  <public type="attr" name="summaryOff" id="0x010101f0" />
+  <public type="attr" name="disableDependentsState" id="0x010101f1" />
+  <public type="attr" name="dialogTitle" id="0x010101f2" />
+  <public type="attr" name="dialogMessage" id="0x010101f3" />
+  <public type="attr" name="dialogIcon" id="0x010101f4" />
+  <public type="attr" name="positiveButtonText" id="0x010101f5" />
+  <public type="attr" name="negativeButtonText" id="0x010101f6" />
+  <public type="attr" name="dialogLayout" id="0x010101f7" />
+  <public type="attr" name="entryValues" id="0x010101f8" />
+  <public type="attr" name="ringtoneType" id="0x010101f9" />
+  <public type="attr" name="showDefault" id="0x010101fa" />
+  <public type="attr" name="showSilent" id="0x010101fb" />
+  <public type="attr" name="scaleWidth" id="0x010101fc" />
+  <public type="attr" name="scaleHeight" id="0x010101fd" />
+  <public type="attr" name="scaleGravity" id="0x010101fe" />
+  <public type="attr" name="ignoreGravity" id="0x010101ff" />
+  <public type="attr" name="foregroundGravity" id="0x01010200" />
+  <public type="attr" name="tileMode" id="0x01010201" />
+  <public type="attr" name="targetActivity" id="0x01010202" />
+  <public type="attr" name="alwaysRetainTaskState" id="0x01010203" />
+  <public type="attr" name="allowTaskReparenting" id="0x01010204" />
+  <public type="attr" name="searchButtonText" id="0x01010205" />
+  <public type="attr" name="colorForegroundInverse" id="0x01010206" />
+  <public type="attr" name="textAppearanceButton" id="0x01010207" />
+  <public type="attr" name="listSeparatorTextViewStyle" id="0x01010208" />
+  <public type="attr" name="streamType" id="0x01010209" />
+  <public type="attr" name="clipOrientation" id="0x0101020a" />
+  <public type="attr" name="centerColor" id="0x0101020b" />
+  <public type="attr" name="minSdkVersion" id="0x0101020c" />
+  <public type="attr" name="windowFullscreen" id="0x0101020d" />
+  <public type="attr" name="unselectedAlpha" id="0x0101020e" />
+  <public type="attr" name="progressBarStyleSmallTitle" id="0x0101020f" />
+  <public type="attr" name="ratingBarStyleIndicator" id="0x01010210" />
+  <public type="attr" name="apiKey" id="0x01010211" />
+  <public type="attr" name="textColorTertiary" id="0x01010212" />
+  <public type="attr" name="textColorTertiaryInverse" id="0x01010213" />
+  <public type="attr" name="listDivider" id="0x01010214" />
+  <public type="attr" name="soundEffectsEnabled" id="0x01010215" />
+  <public type="attr" name="keepScreenOn" id="0x01010216" />
+  <public type="attr" name="lineSpacingExtra" id="0x01010217" />
+  <public type="attr" name="lineSpacingMultiplier" id="0x01010218" />
+  <public type="attr" name="listChoiceIndicatorSingle" id="0x01010219" />
+  <public type="attr" name="listChoiceIndicatorMultiple" id="0x0101021a" />    
+  <public type="attr" name="versionCode" id="0x0101021b" />
+  <public type="attr" name="versionName" id="0x0101021c" />
+  
+  <public type="id" name="background" id="0x01020000" />
+  <public type="id" name="checkbox" id="0x01020001" />
+  <public type="id" name="content" id="0x01020002" />
+  <public type="id" name="edit" id="0x01020003" />
+  <public type="id" name="empty" id="0x01020004" />
+  <public type="id" name="hint" id="0x01020005" />
+  <public type="id" name="icon" id="0x01020006" />
+  <public type="id" name="icon1" id="0x01020007" />
+  <public type="id" name="icon2" id="0x01020008" />
+  <public type="id" name="input" id="0x01020009" />
+  <public type="id" name="list" id="0x0102000a" />
+  <public type="id" name="message" id="0x0102000b" />
+  <public type="id" name="primary" id="0x0102000c" />
+  <public type="id" name="progress" id="0x0102000d" />
+  <public type="id" name="selectedIcon" id="0x0102000e" />
+  <public type="id" name="secondaryProgress" id="0x0102000f" />
+  <public type="id" name="summary" id="0x01020010" />
+  <public type="id" name="tabcontent" id="0x01020011" />
+  <public type="id" name="tabhost" id="0x01020012" />
+  <public type="id" name="tabs" id="0x01020013" />
+  <public type="id" name="text1" id="0x01020014" />
+  <public type="id" name="text2" id="0x01020015" />
+  <public type="id" name="title" id="0x01020016" />
+  <public type="id" name="toggle" id="0x01020017" />
+  <public type="id" name="widget_frame" id="0x01020018" />
+  <public type="id" name="button1" id="0x01020019" />
+  <public type="id" name="button2" id="0x0102001a" />
+  <public type="id" name="button3" id="0x0102001b" />
+  
+  <public type="style" name="Animation" id="0x01030000" />
+  <public type="style" name="Animation.Activity" id="0x01030001" />
+  <public type="style" name="Animation.Dialog" id="0x01030002" />
+  <public type="style" name="Animation.Translucent" id="0x01030003" />
+  <public type="style" name="Animation.Toast" id="0x01030004" />
+  <public type="style" name="Theme" id="0x01030005" />
+  <public type="style" name="Theme.NoTitleBar" id="0x01030006" />
+  <public type="style" name="Theme.NoTitleBar.Fullscreen" id="0x01030007" />
+  <public type="style" name="Theme.Black" id="0x01030008" />
+  <public type="style" name="Theme.Black.NoTitleBar" id="0x01030009" />
+  <public type="style" name="Theme.Black.NoTitleBar.Fullscreen" id="0x0103000a" />
+  <public type="style" name="Theme.Dialog" id="0x0103000b" />
+  <public type="style" name="Theme.Light" id="0x0103000c" />
+  <public type="style" name="Theme.Light.NoTitleBar" id="0x0103000d" />
+  <public type="style" name="Theme.Light.NoTitleBar.Fullscreen" id="0x0103000e" />
+  <public type="style" name="Theme.Translucent" id="0x0103000f" />
+  <public type="style" name="Theme.Translucent.NoTitleBar" id="0x01030010" />
+  <public type="style" name="Theme.Translucent.NoTitleBar.Fullscreen" id="0x01030011" />
+  <public type="style" name="Widget" id="0x01030012" />
+  <public type="style" name="Widget.AbsListView" id="0x01030013" />
+  <public type="style" name="Widget.Button" id="0x01030014" />
+  <public type="style" name="Widget.Button.Inset" id="0x01030015" />
+  <public type="style" name="Widget.Button.Small" id="0x01030016" />
+  <public type="style" name="Widget.Button.Toggle" id="0x01030017" />
+  <public type="style" name="Widget.CompoundButton" id="0x01030018" />
+  <public type="style" name="Widget.CompoundButton.CheckBox" id="0x01030019" />
+  <public type="style" name="Widget.CompoundButton.RadioButton" id="0x0103001a" />
+  <public type="style" name="Widget.CompoundButton.Star" id="0x0103001b" />
+  <public type="style" name="Widget.ProgressBar" id="0x0103001c" />
+  <public type="style" name="Widget.ProgressBar.Large" id="0x0103001d" />
+  <public type="style" name="Widget.ProgressBar.Small" id="0x0103001e" />
+  <public type="style" name="Widget.ProgressBar.Horizontal" id="0x0103001f" />
+  <public type="style" name="Widget.SeekBar" id="0x01030020" />
+  <public type="style" name="Widget.RatingBar" id="0x01030021" />
+  <public type="style" name="Widget.TextView" id="0x01030022" />
+  <public type="style" name="Widget.EditText" id="0x01030023" />
+  <public type="style" name="Widget.ExpandableListView" id="0x01030024" />
+  <public type="style" name="Widget.ImageWell" id="0x01030025" />
+  <public type="style" name="Widget.ImageButton" id="0x01030026" />
+  <public type="style" name="Widget.AutoCompleteTextView" id="0x01030027" />
+  <public type="style" name="Widget.Spinner" id="0x01030028" />
+  <public type="style" name="Widget.TextView.PopupMenu" id="0x01030029" />
+  <public type="style" name="Widget.TextView.SpinnerItem" id="0x0103002a" />
+  <public type="style" name="Widget.DropDownItem" id="0x0103002b" />
+  <public type="style" name="Widget.DropDownItem.Spinner" id="0x0103002c" />
+  <public type="style" name="Widget.ScrollView" id="0x0103002d" />
+  <public type="style" name="Widget.ListView" id="0x0103002e" />
+  <public type="style" name="Widget.ListView.White" id="0x0103002f" />
+  <public type="style" name="Widget.ListView.DropDown" id="0x01030030" />
+  <public type="style" name="Widget.ListView.Menu" id="0x01030031" />
+  <public type="style" name="Widget.GridView" id="0x01030032" />
+  <public type="style" name="Widget.WebView" id="0x01030033" />
+  <public type="style" name="Widget.TabWidget" id="0x01030034" />
+  <public type="style" name="Widget.Gallery" id="0x01030035" />
+  <public type="style" name="Widget.PopupWindow" id="0x01030036" />
+  <public type="style" name="MediaButton" id="0x01030037" />
+  <public type="style" name="MediaButton.Previous" id="0x01030038" />
+  <public type="style" name="MediaButton.Next" id="0x01030039" />
+  <public type="style" name="MediaButton.Play" id="0x0103003a" />
+  <public type="style" name="MediaButton.Ffwd" id="0x0103003b" />
+  <public type="style" name="MediaButton.Rew" id="0x0103003c" />
+  <public type="style" name="MediaButton.Pause" id="0x0103003d" />
+  <public type="style" name="TextAppearance" id="0x0103003e" />
+  <public type="style" name="TextAppearance.Inverse" id="0x0103003f" />
+  <public type="style" name="TextAppearance.Theme" id="0x01030040" />
+  <public type="style" name="TextAppearance.DialogWindowTitle" id="0x01030041" />
+  <public type="style" name="TextAppearance.Large" id="0x01030042" />
+  <public type="style" name="TextAppearance.Large.Inverse" id="0x01030043" />
+  <public type="style" name="TextAppearance.Medium" id="0x01030044" />
+  <public type="style" name="TextAppearance.Medium.Inverse" id="0x01030045" />
+  <public type="style" name="TextAppearance.Small" id="0x01030046" />
+  <public type="style" name="TextAppearance.Small.Inverse" id="0x01030047" />
+  <public type="style" name="TextAppearance.Theme.Dialog" id="0x01030048" />
+  <public type="style" name="TextAppearance.Widget" id="0x01030049" />
+  <public type="style" name="TextAppearance.Widget.Button" id="0x0103004a" />
+  <public type="style" name="TextAppearance.Widget.IconMenu.Item" id="0x0103004b" />
+  <public type="style" name="TextAppearance.Widget.EditText" id="0x0103004c" />
+  <public type="style" name="TextAppearance.Widget.TabWidget" id="0x0103004d" />
+  <public type="style" name="TextAppearance.Widget.TextView" id="0x0103004e" />
+  <public type="style" name="TextAppearance.Widget.TextView.PopupMenu" id="0x0103004f" />
+  <public type="style" name="TextAppearance.Widget.DropDownHint" id="0x01030050" />
+  <public type="style" name="TextAppearance.Widget.DropDownItem" id="0x01030051" />
+  <public type="style" name="TextAppearance.Widget.TextView.SpinnerItem" id="0x01030052" />
+  <public type="style" name="TextAppearance.WindowTitle" id="0x01030053" />
+
+  <public type="string" name="cancel" id="0x01040000" />
+  <public type="string" name="copy" id="0x01040001" />
+  <public type="string" name="copyUrl" id="0x01040002" />
+  <public type="string" name="cut" id="0x01040003" />
+  <public type="string" name="defaultVoiceMailAlphaTag" id="0x01040004" />
+  <public type="string" name="defaultMsisdnAlphaTag" id="0x01040005" />
+  <public type="string" name="emptyPhoneNumber" id="0x01040006" />
+  <public type="string" name="httpErrorBadUrl" id="0x01040007" />
+  <public type="string" name="httpErrorUnsupportedScheme" id="0x01040008" />
+  <public type="string" name="no" id="0x01040009" />
+  <public type="string" name="ok" id="0x0104000a" />
+  <public type="string" name="paste" id="0x0104000b" />
+  <public type="string" name="search_go" id="0x0104000c" />
+  <public type="string" name="selectAll" id="0x0104000d" />
+  <public type="string" name="unknownName" id="0x0104000e" />
+  <public type="string" name="untitled" id="0x0104000f" />
+  <public type="string" name="VideoView_error_button" id="0x01040010" />
+  <public type="string" name="VideoView_error_text_unknown" id="0x01040011" />
+  <public type="string" name="VideoView_error_title" id="0x01040012" />
+  <public type="string" name="yes" id="0x01040013" />
+
+  <public type="dimen" name="app_icon_size" id="0x01050000" />
+  <public type="dimen" name="thumbnail_height" id="0x01050001" />
+  <public type="dimen" name="thumbnail_width" id="0x01050002" />
+
+  <public type="color" name="darker_gray" id="0x01060000" />
+  <public type="color" name="primary_text_dark" id="0x01060001" />
+  <public type="color" name="primary_text_dark_nodisable" id="0x01060002" />
+  <public type="color" name="primary_text_light" id="0x01060003" />
+  <public type="color" name="primary_text_light_nodisable" id="0x01060004" />
+  <public type="color" name="secondary_text_dark" id="0x01060005" />
+  <public type="color" name="secondary_text_dark_nodisable" id="0x01060006" />
+  <public type="color" name="secondary_text_light" id="0x01060007" />
+  <public type="color" name="secondary_text_light_nodisable" id="0x01060008" />
+  <public type="color" name="tab_indicator_text" id="0x01060009" />
+  <public type="color" name="widget_edittext_dark" id="0x0106000a" />
+  <public type="color" name="white" id="0x0106000b" />
+  <public type="color" name="black" id="0x0106000c" />
+  <public type="color" name="transparent" id="0x0106000d" />
+  <public type="color" name="background_dark" id="0x0106000e" />
+  <public type="color" name="background_light" id="0x0106000f" />
+  <public type="color" name="tertiary_text_dark" id="0x01060010" />
+  <public type="color" name="tertiary_text_light" id="0x01060011" />
+
+  <public type="array" name="emailAddressTypes" id="0x01070000" />
+  <public type="array" name="imProtocols" id="0x01070001" />
+  <public type="array" name="organizationTypes" id="0x01070002" />
+  <public type="array" name="phoneTypes" id="0x01070003" />
+  <public type="array" name="postalAddressTypes" id="0x01070004" />
+
+  <public type="drawable" name="alert_dark_frame" id="0x01080000" />
+  <public type="drawable" name="alert_light_frame" id="0x01080001" />
+  <public type="drawable" name="arrow_down_float" id="0x01080002" />
+  <public type="drawable" name="arrow_up_float" id="0x01080003" />
+  <public type="drawable" name="btn_default" id="0x01080004" />
+  <public type="drawable" name="btn_default_small" id="0x01080005" />
+  <public type="drawable" name="btn_dropdown" id="0x01080006" />
+  <public type="drawable" name="btn_minus" id="0x01080007" />
+  <public type="drawable" name="btn_plus" id="0x01080008" />
+  <public type="drawable" name="btn_radio" id="0x01080009" />
+  <public type="drawable" name="btn_star" id="0x0108000a" />
+  <public type="drawable" name="btn_star_big_off" id="0x0108000b" /> 
+  <public type="drawable" name="btn_star_big_on" id="0x0108000c" />
+  <public type="drawable" name="button_onoff_indicator_on" id="0x0108000d" />
+  <public type="drawable" name="button_onoff_indicator_off" id="0x0108000e" />
+  <public type="drawable" name="checkbox_off_background" id="0x0108000f" />
+  <public type="drawable" name="checkbox_on_background" id="0x01080010" />
+  <public type="drawable" name="dialog_frame" id="0x01080011" />
+  <public type="drawable" name="divider_horizontal_bright" id="0x01080012" />
+  <public type="drawable" name="divider_horizontal_textfield" id="0x01080013" />
+  <public type="drawable" name="divider_horizontal_dark" id="0x01080014" />
+  <public type="drawable" name="divider_horizontal_dim_dark" id="0x01080015" />
+  <public type="drawable" name="edit_text" id="0x01080016" />
+  <public type="drawable" name="btn_dialog" id="0x01080017" />
+  <public type="drawable" name="editbox_background" id="0x01080018" />
+  <public type="drawable" name="editbox_background_normal" id="0x01080019" />
+  <public type="drawable" name="editbox_dropdown_dark_frame" id="0x0108001a" />
+  <public type="drawable" name="editbox_dropdown_light_frame" id="0x0108001b" />
+  <public type="drawable" name="gallery_thumb" id="0x0108001c" />
+  <public type="drawable" name="ic_delete" id="0x0108001d" />
+  <public type="drawable" name="ic_lock_idle_charging" id="0x0108001e" />
+  <public type="drawable" name="ic_lock_idle_lock" id="0x0108001f" />
+  <public type="drawable" name="ic_lock_idle_low_battery" id="0x01080020" />
+  <public type="drawable" name="ic_media_ff" id="0x01080021" />
+  <public type="drawable" name="ic_media_next" id="0x01080022" />
+  <public type="drawable" name="ic_media_pause" id="0x01080023" />
+  <public type="drawable" name="ic_media_play" id="0x01080024" />
+  <public type="drawable" name="ic_media_previous" id="0x01080025" />
+  <public type="drawable" name="ic_media_rew" id="0x01080026" />
+  <public type="drawable" name="ic_dialog_alert" id="0x01080027" />
+  <public type="drawable" name="ic_dialog_dialer" id="0x01080028" />
+  <public type="drawable" name="ic_dialog_email" id="0x01080029" />
+  <public type="drawable" name="ic_dialog_map" id="0x0108002a" />
+  <public type="drawable" name="ic_input_add" id="0x0108002b" />
+  <public type="drawable" name="ic_input_delete" id="0x0108002c" />
+  <public type="drawable" name="ic_input_get" id="0x0108002d" />
+  <public type="drawable" name="ic_lock_idle_alarm" id="0x0108002e" />
+  <public type="drawable" name="ic_lock_lock" id="0x0108002f" />
+  <public type="drawable" name="ic_lock_power_off" id="0x01080030" />
+  <public type="drawable" name="ic_lock_silent_mode" id="0x01080031" />
+  <public type="drawable" name="ic_lock_silent_mode_off" id="0x01080032" />
+  <public type="drawable" name="ic_menu_add" id="0x01080033" />
+  <public type="drawable" name="ic_menu_agenda" id="0x01080034" />
+  <public type="drawable" name="ic_menu_always_landscape_portrait" id="0x01080035" />
+  <public type="drawable" name="ic_menu_call" id="0x01080036" />
+  <public type="drawable" name="ic_menu_camera" id="0x01080037" />
+  <public type="drawable" name="ic_menu_close_clear_cancel" id="0x01080038" />
+  <public type="drawable" name="ic_menu_compass" id="0x01080039" />
+  <public type="drawable" name="ic_menu_crop" id="0x0108003a" />
+  <public type="drawable" name="ic_menu_day" id="0x0108003b" />
+  <public type="drawable" name="ic_menu_delete" id="0x0108003c" />
+  <public type="drawable" name="ic_menu_directions" id="0x0108003d" />
+  <public type="drawable" name="ic_menu_edit" id="0x0108003e" />
+  <public type="drawable" name="ic_menu_gallery" id="0x0108003f" />
+  <public type="drawable" name="ic_menu_help" id="0x01080040" />
+  <public type="drawable" name="ic_menu_info_details" id="0x01080041" />
+  <public type="drawable" name="ic_menu_manage" id="0x01080042" />
+  <public type="drawable" name="ic_menu_mapmode" id="0x01080043" />
+  <public type="drawable" name="ic_menu_month" id="0x01080044" />
+  <public type="drawable" name="ic_menu_more" id="0x01080045" />
+  <public type="drawable" name="ic_menu_my_calendar" id="0x01080046" />
+  <public type="drawable" name="ic_menu_mylocation" id="0x01080047" />
+  <public type="drawable" name="ic_menu_myplaces" id="0x01080048" />
+  <public type="drawable" name="ic_menu_preferences" id="0x01080049" />
+  <public type="drawable" name="ic_menu_recent_history" id="0x0108004a" />
+  <public type="drawable" name="ic_menu_report_image" id="0x0108004b" />
+  <public type="drawable" name="ic_menu_revert" id="0x0108004c" />
+  <public type="drawable" name="ic_menu_rotate" id="0x0108004d" />
+  <public type="drawable" name="ic_menu_save" id="0x0108004e" />
+  <public type="drawable" name="ic_menu_search" id="0x0108004f" />
+  <public type="drawable" name="ic_menu_send" id="0x01080050" />
+  <public type="drawable" name="ic_menu_set_as" id="0x01080051" />
+  <public type="drawable" name="ic_menu_share" id="0x01080052" />
+  <public type="drawable" name="ic_menu_slideshow" id="0x01080053" />
+  <public type="drawable" name="ic_menu_today" id="0x01080054" />
+  <public type="drawable" name="ic_menu_upload" id="0x01080055" />
+  <public type="drawable" name="ic_menu_upload_you_tube" id="0x01080056" />
+  <public type="drawable" name="ic_menu_view" id="0x01080057" />
+  <public type="drawable" name="ic_menu_week" id="0x01080058" />
+  <public type="drawable" name="ic_menu_zoom" id="0x01080059" />
+  <public type="drawable" name="ic_notification_clear_all" id="0x0108005a" />
+  <public type="drawable" name="ic_notification_overlay" id="0x0108005b" />
+  <public type="drawable" name="ic_partial_secure" id="0x0108005c" />
+  <public type="drawable" name="ic_popup_disk_full" id="0x0108005d" />
+  <public type="drawable" name="ic_popup_reminder" id="0x0108005e" />
+  <public type="drawable" name="ic_popup_sync" id="0x0108005f" />
+  <public type="drawable" name="ic_search_category_default" id="0x01080060" />
+  <public type="drawable" name="ic_secure" id="0x01080061" />
+  <public type="drawable" name="list_selector_background" id="0x01080062" />
+  <public type="drawable" name="menu_frame" id="0x01080063" />
+  <public type="drawable" name="menu_full_frame" id="0x01080064" />
+  <public type="drawable" name="menuitem_background" id="0x01080065" />
+  <public type="drawable" name="picture_frame" id="0x01080066" />
+  <public type="drawable" name="presence_away" id="0x01080067" />
+  <public type="drawable" name="presence_busy" id="0x01080068" />
+  <public type="drawable" name="presence_invisible" id="0x01080069" />
+  <public type="drawable" name="presence_offline" id="0x0108006a" />
+  <public type="drawable" name="presence_online" id="0x0108006b" />
+  <public type="drawable" name="progress_horizontal" id="0x0108006c" />
+  <public type="drawable" name="progress_indeterminate_horizontal" id="0x0108006d" />
+  <public type="drawable" name="radiobutton_off_background" id="0x0108006e" />
+  <public type="drawable" name="radiobutton_on_background" id="0x0108006f" />
+  <public type="drawable" name="spinner_background" id="0x01080070" />
+  <public type="drawable" name="spinner_dropdown_background" id="0x01080071" />
+  <public type="drawable" name="star_big_on" id="0x01080072" />
+  <public type="drawable" name="star_big_off" id="0x01080073" />
+  <public type="drawable" name="star_on" id="0x01080074" />
+  <public type="drawable" name="star_off" id="0x01080075" />
+  <public type="drawable" name="stat_notify_call_mute" id="0x01080076" />
+  <public type="drawable" name="stat_notify_chat" id="0x01080077" />
+  <public type="drawable" name="stat_notify_error" id="0x01080078" />
+  <public type="drawable" name="stat_notify_more" id="0x01080079" />
+  <public type="drawable" name="stat_notify_sdcard" id="0x0108007a" />
+  <public type="drawable" name="stat_notify_sdcard_usb" id="0x0108007b" />
+  <public type="drawable" name="stat_notify_sync" id="0x0108007c" />
+  <public type="drawable" name="stat_notify_sync_noanim" id="0x0108007d" />
+  <public type="drawable" name="stat_notify_voicemail" id="0x0108007e" />
+  <public type="drawable" name="stat_notify_missed_call" id="0x0108007f" />
+  <public type="drawable" name="stat_sys_data_bluetooth" id="0x01080080" />
+  <public type="drawable" name="stat_sys_download" id="0x01080081" />
+  <public type="drawable" name="stat_sys_download_done" id="0x01080082" />
+  <public type="drawable" name="stat_sys_headset" id="0x01080083" />
+  <public type="drawable" name="stat_sys_phone_call" id="0x01080084" />
+  <public type="drawable" name="stat_sys_phone_call_forward" id="0x01080085" />
+  <public type="drawable" name="stat_sys_phone_call_on_hold" id="0x01080086" />
+  <public type="drawable" name="stat_sys_speakerphone" id="0x01080087" />
+  <public type="drawable" name="stat_sys_upload" id="0x01080088" />
+  <public type="drawable" name="stat_sys_upload_done" id="0x01080089" />
+  <public type="drawable" name="stat_sys_warning" id="0x0108008a" />
+  <public type="drawable" name="status_bar_item_app_background" id="0x0108008b" />
+  <public type="drawable" name="status_bar_item_background" id="0x0108008c" />
+  <public type="drawable" name="sym_action_call" id="0x0108008d" />
+  <public type="drawable" name="sym_action_chat" id="0x0108008e" />
+  <public type="drawable" name="sym_action_email" id="0x0108008f" />
+  <public type="drawable" name="sym_call_incoming" id="0x01080090" />
+  <public type="drawable" name="sym_call_missed" id="0x01080091" />
+  <public type="drawable" name="sym_call_outgoing" id="0x01080092" />
+  <public type="drawable" name="sym_def_app_icon" id="0x01080093" />
+  <public type="drawable" name="sym_contact_card" id="0x01080094" />
+  <public type="drawable" name="title_bar" id="0x01080095" />
+  <public type="drawable" name="toast_frame" id="0x01080096" />
+  <public type="drawable" name="zoom_plate" id="0x01080097" />
+  <public type="drawable" name="screen_background_dark" id="0x01080098" />
+  <public type="drawable" name="screen_background_light" id="0x01080099" />
+  <public type="drawable" name="bottom_bar" id="0x0108009a" />
+  <public type="drawable" name="ic_dialog_info" id="0x0108009b" />
+  <public type="drawable" name="ic_menu_sort_alphabetically" id="0x0108009c" />
+  <public type="drawable" name="ic_menu_sort_by_size" id="0x0108009d" />
+
+  <public type="layout" name="activity_list_item" id="0x01090000" />
+  <public type="layout" name="expandable_list_content" id="0x01090001" />
+  <public type="layout" name="preference_category" id="0x01090002" />
+  <public type="layout" name="simple_list_item_1" id="0x01090003" />
+  <public type="layout" name="simple_list_item_2" id="0x01090004" />
+  <public type="layout" name="simple_list_item_checked" id="0x01090005" />
+  <public type="layout" name="simple_expandable_list_item_1" id="0x01090006" />
+  <public type="layout" name="simple_expandable_list_item_2" id="0x01090007" />
+  <public type="layout" name="simple_spinner_item" id="0x01090008" />
+  <public type="layout" name="simple_spinner_dropdown_item" id="0x01090009" />
+  <public type="layout" name="simple_dropdown_item_1line" id="0x0109000a" />
+  <public type="layout" name="simple_gallery_item" id="0x0109000b" />
+  <public type="layout" name="test_list_item" id="0x0109000c" />
+  <public type="layout" name="two_line_list_item" id="0x0109000d" />
+  <public type="layout" name="browser_link_context_header" id="0x0109000e" />
+  <public type="layout" name="simple_list_item_single_choice" id="0x0109000f" />
+  <public type="layout" name="simple_list_item_multiple_choice" id="0x01090010" />
+  <public type="layout" name="select_dialog_item" id="0x01090011" />
+  <public type="layout" name="select_dialog_singlechoice" id="0x01090012" />
+  <public type="layout" name="select_dialog_multichoice" id="0x01090013" />
+  
+  <public type="anim" name="fade_in" id="0x010a0000" />
+  <public type="anim" name="fade_out" id="0x010a0001" />
+  <public type="anim" name="slide_in_left" id="0x010a0002" />
+  <public type="anim" name="slide_out_right" id="0x010a0003" />
+  <public type="anim" name="accelerate_decelerate_interpolator" id="0x010a0004" />
+  <public type="anim" name="accelerate_interpolator" id="0x010a0005" />
+  <public type="anim" name="decelerate_interpolator" id="0x010a0006" />
+
+<!-- ===============================================================
+     Resources added in version 2 of the platform.
+     =============================================================== -->
+  <eat-comment />
+  
+  <public type="attr" name="marqueeRepeatLimit" id="0x0101021d" />
+  
+<!-- ===============================================================
+     Resources added in version 3 of the platform.
+     =============================================================== -->
+  <eat-comment />
+
+  <public type="attr" name="windowNoDisplay" id="0x0101021e" />
+  <public type="attr" name="backgroundDimEnabled" id="0x0101021f" />
+  <public type="attr" name="inputType" id="0x01010220" />
+  <public type="attr" name="isDefault" id="0x01010221" />
+  <public type="attr" name="windowDisablePreview" id="0x01010222" />
+  <public type="attr" name="privateImeOptions" id="0x01010223" />
+  <public type="attr" name="editorExtras" id="0x01010224" />
+  <public type="attr" name="settingsActivity" id="0x01010225" />
+  <public type="attr" name="fastScrollEnabled" id="0x01010226" />
+  <public type="attr" name="reqTouchScreen" id="0x01010227" />
+  <public type="attr" name="reqKeyboardType" id="0x01010228" />
+  <public type="attr" name="reqHardKeyboard" id="0x01010229" />
+  <public type="attr" name="reqNavigation" id="0x0101022a" />
+  <public type="attr" name="windowSoftInputMode" id="0x0101022b" />
+  <public type="attr" name="starStyleButtonless" id="0x0101022c" />
+  <public type="attr" name="noHistory" id="0x0101022d" />
+  <public type="attr" name="headerDividersEnabled" id="0x0101022e" />
+  <public type="attr" name="footerDividersEnabled" id="0x0101022f" />
+  <public type="attr" name="candidatesTextStyleSpans" id="0x01010230" />
+  <public type="attr" name="smoothScrollbar" id="0x01010231" />
+  <public type="attr" name="reqFiveWayNav" id="0x01010232" />
+  <public type="attr" name="keyBackground" id="0x01010233" />
+  <public type="attr" name="keyTextSize" id="0x01010234" />
+  <public type="attr" name="labelTextSize" id="0x01010235" />
+  <public type="attr" name="keyTextColor" id="0x01010236" />
+  <public type="attr" name="keyPreviewLayout" id="0x01010237" />
+  <public type="attr" name="keyPreviewOffset" id="0x01010238" />
+  <public type="attr" name="keyPreviewHeight" id="0x01010239" />
+  <public type="attr" name="verticalCorrection" id="0x0101023a" />
+  <public type="attr" name="popupLayout" id="0x0101023b" />
+  <public type="attr" name="state_long_pressable" id="0x0101023c" />
+  <public type="attr" name="keyWidth" id="0x0101023d" />
+  <public type="attr" name="keyHeight" id="0x0101023e" />
+  <public type="attr" name="horizontalGap" id="0x0101023f" />
+  <public type="attr" name="verticalGap" id="0x01010240" />
+  <public type="attr" name="rowEdgeFlags" id="0x01010241" />
+  <public type="attr" name="codes" id="0x01010242" />
+  <public type="attr" name="popupKeyboard" id="0x01010243" />
+  <public type="attr" name="popupCharacters" id="0x01010244" />
+  <public type="attr" name="keyEdgeFlags" id="0x01010245" />
+  <public type="attr" name="isModifier" id="0x01010246" />
+  <public type="attr" name="isSticky" id="0x01010247" />
+  <public type="attr" name="isRepeatable" id="0x01010248" />
+  <public type="attr" name="iconPreview" id="0x01010249" />
+  <public type="attr" name="keyOutputText" id="0x0101024a" />
+  <public type="attr" name="keyLabel" id="0x0101024b" />
+  <public type="attr" name="keyIcon" id="0x0101024c" />
+  <public type="attr" name="keyboardMode" id="0x0101024d" />
+  <public type="attr" name="isScrollContainer" id="0x0101024e" />
+  <public type="attr" name="fillEnabled" id="0x0101024f" />
+  <public type="attr" name="updatePeriodMillis" id="0x01010250" />
+  <public type="attr" name="initialLayout" id="0x01010251" />
+  <public type="attr" name="voiceSearchMode" id="0x01010252" />
+  <public type="attr" name="voiceLanguageModel" id="0x01010253" />
+  <public type="attr" name="voicePromptText" id="0x01010254" />
+  <public type="attr" name="voiceLanguage" id="0x01010255" />
+  <public type="attr" name="voiceMaxResults" id="0x01010256" />
+  <public type="attr" name="bottomOffset" id="0x01010257" />
+  <public type="attr" name="topOffset" id="0x01010258" />
+  <public type="attr" name="allowSingleTap" id="0x01010259" />
+  <public type="attr" name="handle" id="0x0101025a" />
+  <public type="attr" name="content" id="0x0101025b" />
+  <public type="attr" name="animateOnClick" id="0x0101025c" />
+  <public type="attr" name="configure" id="0x0101025d" />
+  <public type="attr" name="hapticFeedbackEnabled" id="0x0101025e" />
+  <public type="attr" name="innerRadius" id="0x0101025f" />
+  <public type="attr" name="thickness" id="0x01010260" />
+  <public type="attr" name="sharedUserLabel" id="0x01010261" />
+  <public type="attr" name="dropDownWidth" id="0x01010262" />
+  <public type="attr" name="dropDownAnchor" id="0x01010263" />
+  <public type="attr" name="imeOptions" id="0x01010264" />
+  <public type="attr" name="imeActionLabel" id="0x01010265" />
+  <public type="attr" name="imeActionId" id="0x01010266" />
+
+  <!-- The part of the UI shown by an
+       {@link android.inputmethodservice.InputMethodService} that contains the
+       views for interacting with the user in extraction mode. -->
+  <public type="id" name="extractArea" id="0x0102001c" />
+
+  <!-- The part of the UI shown by an
+       {@link android.inputmethodservice.InputMethodService} that contains the
+       views for displaying candidates for what the user has entered. -->
+  <public type="id" name="candidatesArea" id="0x0102001d" />
+
+  <!-- The part of the UI shown by an
+       {@link android.inputmethodservice.InputMethodService} that contains the
+       views for entering text using the screen. -->
+  <public type="id" name="inputArea" id="0x0102001e" />
+
+  <!-- Context menu ID for the "Select All" menu item to select all text
+       in a text view. -->
+  <public type="id" name="selectAll" id="0x0102001f" />
+  <!-- Context menu ID for the "Cut" menu item to copy and delete the currently
+       selected (or all) text in a text view to the clipboard. -->
+  <public type="id" name="cut" id="0x01020020" />
+  <!-- Context menu ID for the "Copy" menu item to copy the currently
+       selected (or all) text in a text view to the clipboard. -->
+  <public type="id" name="copy" id="0x01020021" />
+  <!-- Context menu ID for the "Paste" menu item to copy the current contents
+       of the clipboard into the text view. -->
+  <public type="id" name="paste" id="0x01020022" />
+  <!-- Context menu ID for the "Copy URL" menu item to copy the currently
+       selected URL from the text view to the clipboard. -->
+  <public type="id" name="copyUrl" id="0x01020023" />
+  <!-- Context menu ID for the "Input Method" menu item to being up the
+       input method picker dialog, allowing the user to switch to another
+       input method. -->
+  <public type="id" name="switchInputMethod" id="0x01020024" />
+  <!-- View ID of the text editor inside of an extracted text layout. -->
+  <public type="id" name="inputExtractEditText" id="0x01020025" />
+
+  <!-- View ID of the {@link android.inputmethodservice.KeyboardView} within
+        an input method's input area. -->
+  <public type="id" name="keyboardView" id="0x01020026" />
+  <!-- View ID of a {@link android.view.View} to close a popup keyboard -->
+  <public type="id" name="button_close" id="0x01020027" />
+
+  <!-- Menu ID to perform a "start selecting text" operation. -->
+  <public type="id" name="startSelectingText" id="0x01020028" />
+  <!-- Menu ID to perform a "stop selecting text" operation. -->
+  <public type="id" name="stopSelectingText" id="0x01020029" />
+  <!-- Menu ID to perform a "add to dictionary" operation. -->
+  <public type="id" name="addToDictionary" id="0x0102002a" />
+  
+  <public type="style" name="Theme.InputMethod" id="0x01030054" />
+  <public type="style" name="Theme.NoDisplay" id="0x01030055" />
+  <public type="style" name="Animation.InputMethod" id="0x01030056" />
+  <public type="style" name="Widget.KeyboardView" id="0x01030057" />
+  <public type="style" name="ButtonBar" id="0x01030058" />
+  
+  <public type="string" name="dialog_alert_title" id="0x01040014" />
+
+  <public type="drawable" name="emo_im_angel"               id="0x010800a4" />
+  <public type="drawable" name="emo_im_cool"                id="0x010800a5" />
+  <public type="drawable" name="emo_im_crying"              id="0x010800a6" />
+  <public type="drawable" name="emo_im_embarrassed"         id="0x010800a7" />
+  <public type="drawable" name="emo_im_foot_in_mouth"       id="0x010800a8" />
+  <public type="drawable" name="emo_im_happy"               id="0x010800a9" />
+  <public type="drawable" name="emo_im_kissing"             id="0x010800aa" />
+  <public type="drawable" name="emo_im_laughing"            id="0x010800ab" />
+  <public type="drawable" name="emo_im_lips_are_sealed"     id="0x010800ac" />
+  <public type="drawable" name="emo_im_money_mouth"         id="0x010800ad" />
+  <public type="drawable" name="emo_im_sad"                 id="0x010800ae" />
+  <public type="drawable" name="emo_im_surprised"           id="0x010800af" />
+  <public type="drawable" name="emo_im_tongue_sticking_out" id="0x010800b0" />
+  <public type="drawable" name="emo_im_undecided"           id="0x010800b1" />
+  <public type="drawable" name="emo_im_winking"             id="0x010800b2" />
+  <public type="drawable" name="emo_im_wtf"                 id="0x010800b3" />
+  <public type="drawable" name="emo_im_yelling"             id="0x010800b4" />
+  
+  <public type="drawable" name="ic_btn_speak_now" id="0x010800b5" />
+
+  <!--  Drawable to use as a background for separators on a list with a dark background -->
+  <public type="drawable" name="dark_header" id="0x010800b6" />
+
+  <!--  Drawable to use as a background for a taller version of the titlebar -->
+  <public type="drawable" name="title_bar_tall" id="0x010800b7" />
+</resources>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
new file mode 100644
index 0000000..346fa80
--- /dev/null
+++ b/core/res/res/values/strings.xml
@@ -0,0 +1,2299 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 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.
+*/
+-->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- Suffix added to a number to signify size in bytes. -->
+    <string name="byteShort">B</string>
+    <!-- Suffix added to a number to signify size in kilobytes. -->
+    <string name="kilobyteShort">KB</string>
+    <!-- Suffix added to a number to signify size in megabytes. -->
+    <string name="megabyteShort">MB</string>
+    <!-- Suffix added to a number to signify size in gigabytes. -->
+    <string name="gigabyteShort">GB</string>
+    <!-- Suffix added to a number to signify size in terabytes. -->
+    <string name="terabyteShort">TB</string>
+    <!-- Suffix added to a number to signify size in petabytes. -->
+    <string name="petabyteShort">PB</string>
+
+    <!-- Used in Contacts for a field that has no label and in Note Pad
+         for a note with no name. -->
+    <string name="untitled">&lt;untitled&gt;</string>
+
+    <!-- Used to replace a range of characters in text that is too wide
+         for the space allocated to it. -->
+    <string name="ellipsis">\u2026</string>
+
+    <!-- How to display the lack of a phone number -->
+    <string name="emptyPhoneNumber">(No phone number)</string>
+
+    <!-- How to display the lack of a name -->
+    <string name="unknownName">(Unknown)</string>
+
+    <!-- What the UI should display for "voice mail" unless overridden by the SIM-->
+    <string name="defaultVoiceMailAlphaTag">Voicemail</string>
+
+    <!-- What the UI should display for "Msisdn" unless overridden by the SIM-->
+    <string name="defaultMsisdnAlphaTag">MSISDN1</string>
+
+    <!-- For GsmMmiCode.java --> <skip />
+    <!-- Displayed when the user dialed an MMI code whose function
+         could not be performed. This will be displayed in a toast. -->
+    <string name="mmiError">Connection problem or invalid MMI code.</string>
+    <!-- Displayed when a phone feature such as call barring was activated. -->
+    <string name="serviceEnabled">Service was enabled.</string>
+    <!-- Displayed in front of the list of a set of service classes
+         (voice, data, fax, etc.) that were enabled. -->
+    <string name="serviceEnabledFor">Service was enabled for:</string>
+    <!-- Displayed when a phone feature such as call forwarding was deactivated. -->
+    <string name="serviceDisabled">Service has been disabled.</string>
+    <!-- Displayed when a phone property such as a SIM password was registered. -->
+    <string name="serviceRegistered">Registration was successful.</string>
+    <!-- Displayed when a phone property such as a SIM password was erased. -->
+    <string name="serviceErased">Erasure was successful.</string>
+    <!-- Displayed when a SIM password was entered incorrectly. -->
+    <string name="passwordIncorrect">Incorrect password.</string>
+    <!-- Displayed when a phone feature triggered by an MMI code is complete. -->
+    <string name="mmiComplete">MMI complete.</string>
+    <!-- Displayed when a SIM PIN password is entered incorrectly. -->
+    <string name="badPin">The old PIN you typed is not correct.</string>
+    <!-- Displayed when a SIM PUK password is entered incorrectly. -->
+    <string name="badPuk">The PUK you typed is not correct.</string>
+    <!-- Displayed when SIM PIN passwords are entered inconsistently. -->
+    <string name="mismatchPin">The PINs you entered do not match.</string>
+    <!-- Displayed when a SIM PIN password is too long or too short. -->
+    <string name="invalidPin">Type a PIN that is 4 to 8 numbers.</string>
+    <!-- Displayed to prompt the user to type the PUK password to unlock
+         the SIM card. -->
+    <string name="needPuk">Your SIM card is PUK-locked. Type the PUK code to unlock it.</string>
+    <string name="needPuk2">Type PUK2 to unblock SIM card.</string>
+
+    <!-- Displayed as the title for a success/failure report enabling/disabling caller ID. -->
+    <string name="ClipMmi">Incoming Caller ID</string>
+    <!-- Displayed as the title for a success/failure report enabling/disabling caller ID. -->
+    <string name="ClirMmi">Outgoing Caller ID</string>
+    <!-- Displayed as the title for a success/failure report enabling/disabling call forwarding. -->
+    <string name="CfMmi">Call forwarding</string>
+    <!-- Displayed as the title for a success/failure report enabling/disabling call waiting. -->
+    <string name="CwMmi">Call waiting</string>
+    <!-- Displayed as the title for a success/failure report enabling/disabling call barring. -->
+    <string name="BaMmi">Call barring</string>
+    <!-- Displayed as the title for a success/failure report changing the SIM password. -->
+    <string name="PwdMmi">Password change</string>
+    <!-- Displayed as the title for a success/failure report changing the SIM PIN. -->
+    <string name="PinMmi">PIN change</string>
+
+    <!-- Displayed to confirm to the user that caller ID will be restricted on the next call as usual. -->
+    <string name="CLIRDefaultOnNextCallOn">Caller ID defaults to restricted. Next call: Restricted</string>
+    <!-- Displayed to confirm to the user that caller ID will be not restricted on the next call even though it usually is. -->
+    <string name="CLIRDefaultOnNextCallOff">Caller ID defaults to restricted. Next call: Not restricted</string>
+    <!-- Displayed to confirm to the user that caller ID will not be restricted on the next call but usually is. -->
+    <string name="CLIRDefaultOffNextCallOn">Caller ID defaults to not restricted. Next call: Restricted</string>
+    <!-- Displayed to confirm to the user that caller ID will not be restricted on the next call or in general. -->
+    <string name="CLIRDefaultOffNextCallOff">Caller ID defaults to not restricted. Next call: Not restricted</string>
+
+
+    <!-- Displayed to tell the user that caller ID is not provisioned for their SIM. -->
+    <string name="serviceNotProvisioned">Service not provisioned.</string>
+    <!-- Displayed to tell the user that they cannot change the caller ID setting. -->
+    <string name="CLIRPermanent">The caller ID setting cannot be changed.</string>
+
+    <!-- Mappings between TS 27.007 +CFCC/+CLCK "service classes" and human-readable strings--> <skip />
+    <!-- Example: Service was enabled for: Voice, Data -->
+    <string name="serviceClassVoice">Voice</string>
+    <!-- Example: Service was enabled for: Voice, Data -->
+    <string name="serviceClassData">Data</string>
+    <!-- Example: Service was enabled for: Voice, FAX -->
+    <string name="serviceClassFAX">FAX</string>
+    <!-- Example: Service was enabled for: Voice, SMS -->
+    <string name="serviceClassSMS">SMS</string>
+    <!-- Meaning: asynchronous data.  Example: Service was enabled for: Voice, Async -->
+    <string name="serviceClassDataAsync">Async</string>
+    <!-- Meaning: synchronous data.  Example: Service was enabled for: Voice, Async -->
+    <string name="serviceClassDataSync">Sync</string>
+    <!-- Meaning: packet data.  Example: Service was enabled for: Voice, Packet -->
+    <string name="serviceClassPacket">Packet</string>
+    <!-- Meaning: unknown.  Example: Service was enabled for: Voice, PAD -->
+    <string name="serviceClassPAD">PAD</string>
+
+    <!--
+        {0} is one of "bearerServiceCode*"
+        {1} is dialing number
+        {2} is time in seconds
+
+        cfTemplateRegistered and cfTemplateRegisteredTime mean that a phone number
+        has been set but forwarding is not on.
+    --> <skip />
+    <!-- Displayed when the call forwarding query was not able to be forwarded. -->
+    <string name="cfTemplateNotForwarded"><xliff:g id="bearer_service_code">{0}</xliff:g>: Not forwarded</string>
+    <!-- Displayed when the call forwarding query was forwarded. -->
+    <string name="cfTemplateForwarded"><xliff:g id="bearer_service_code">{0}</xliff:g>: <xliff:g id="dialing_number">{1}</xliff:g></string>
+    <!-- Displayed when the call forwarding query will be forwarded after some time. -->
+    <string name="cfTemplateForwardedTime"><xliff:g id="bearer_service_code">{0}</xliff:g>: <xliff:g id="dialing_number">{1}</xliff:g> after <xliff:g id="time_delay">{2}</xliff:g> seconds</string>
+    <!-- Displayed when the call forwarding query was set but forwarding is not enabled. -->
+    <string name="cfTemplateRegistered"><xliff:g id="bearer_service_code">{0}</xliff:g>: Not forwarded</string>
+    <!-- Displayed when the call forwarding query was set but forwarding is not enabled. -->
+    <string name="cfTemplateRegisteredTime"><xliff:g id="bearer_service_code">{0}</xliff:g>: Not forwarded</string>
+
+    <!-- android.net.http Error strings --> <skip />
+    <!-- Displayed when a web request was successful. -->
+    <string name="httpErrorOk">OK</string>
+    <!-- Displayed when a web request failed because we don't know the exact reason. -->
+    <string name="httpError">The Web page contains an error.</string>
+    <!-- Displayed when a web request failed because the URL could not be found. -->
+    <string name="httpErrorLookup">The URL could not be found.</string>
+    <!-- Displayed when a web request failed because the site's authentication scheme is not supported by us. -->
+    <string name="httpErrorUnsupportedAuthScheme">The site authentication scheme is not supported.</string>
+    <!-- Displayed when a web request failed because the authentication failed. -->
+    <string name="httpErrorAuth">Authentication was unsuccessful.</string>
+    <!-- Displayed when a web request failed because the authentication with the proxy failed. -->
+    <string name="httpErrorProxyAuth">Authentication via the proxy server was unsuccessful.</string>
+    <!-- Displayed when a web request failed because there was a connection error. -->
+    <string name="httpErrorConnect">The connection to the server was unsuccessful.</string>
+    <!-- Displayed when a web request failed because there was an input or output error. -->
+    <string name="httpErrorIO">The server failed to communicate. Try again later.</string>
+    <!-- Displayed when a web request failed because the request timed out -->
+    <string name="httpErrorTimeout">The connection to the server timed out.</string>
+    <!-- Displayed when a web request failed because the site tried to redirect us one too many times -->
+    <string name="httpErrorRedirectLoop">The page contains too many server redirects.</string>
+    <!-- Displayed when a web request failed because the protocol of the server is not supported. -->
+    <string name="httpErrorUnsupportedScheme">The protocol is not supported.</string>
+    <!-- Displayed when a web request failed because the a secure connection couldn't be made to the server.-->
+    <string name="httpErrorFailedSslHandshake">A secure connection could not be established.</string>
+    <!-- Displayed when a web request failed because the URL isn't in a valid form. -->
+    <string name="httpErrorBadUrl">The page could not be opened because the URL is invalid.</string>
+    <!-- Displayed when a request failed because we failed to open the file. -->
+    <string name="httpErrorFile">The file could not be accessed.</string>
+    <!-- Displayed when a request failed because the file wasn't found. -->
+    <string name="httpErrorFileNotFound">The requested file was not found.</string>
+    <!-- Displayed when a request failed because there are too many requests right now. -->
+    <string name="httpErrorTooManyRequests">Too many requests are being processed. Try again later.</string>
+
+    <!-- Sync notifications --> <skip />
+    <!-- A notification is shown when there is a sync error.  This is the text that will scroll through the notification bar (will be seen by the user as he uses another application). -->
+    <string name="contentServiceSync">Sync</string>
+    <!-- A notification is shown when there is a sync error.  This is the title of the notification.  It will be seen in the pull-down notification tray. -->
+    <string name="contentServiceSyncNotificationTitle">Sync</string>
+    <!-- A notification is shown when there is a sync error.  This is the message of the notification.  It describes the error, in this case is there were too many deletes. The argument is the type of content, for example Gmail or Calendar. It will be seen in the pull-down notification tray. -->
+    <string name="contentServiceTooManyDeletesNotificationDesc">Too many <xliff:g id="content_type">%s</xliff:g> deletes.</string>
+
+    <!-- If MMS discovers there isn't much space left on the device, it will show a toast with this message. -->
+    <string name="low_memory">Phone storage is full! Delete some files to free space.</string>
+
+
+    <!-- Display name for any time a piece of data refers to the owner of the phone. For example, this could be used in place of the phone's phone number. -->
+    <string name="me">Me</string>
+
+    <!-- Power Dialog --> <skip />
+    <!-- Title for the Phone Options dialog to lock the screen, turn off the phone etc. -->
+    <string name="power_dialog">Phone options</string>
+    <!-- Button to turn on silent mode, within the Phone Options dialog -->
+    <string name="silent_mode">Silent mode</string>
+    <!-- Button to turn on the radio, within the Phone Options dialog -->
+    <string name="turn_on_radio">Turn on wireless</string>
+    <!-- Button to turn off the radio, within the Phone Options dialog -->
+    <string name="turn_off_radio">Turn off wireless</string>
+    <!-- Button to lock the screen, within the Phone Options dialog -->
+    <string name="screen_lock">Screen lock</string>
+    <!-- Button to turn off the phone, within the Phone Options dialog -->
+    <string name="power_off">Power off</string>
+
+    <!-- Shutdown Progress Dialog. This is shown if the user chooses to power off the phone. -->
+    <string name="shutdown_progress">Shutting down\u2026</string>
+
+    <!-- Shutdown Confirmation Dialog.  When the user chooses to power off the phone, there will be a confirmation dialog.  This is the message. -->
+    <string name="shutdown_confirm">Your phone will shut down.</string>
+
+    <!-- Recent Tasks dialog -->
+    <string name="no_recent_tasks">No recent applications.</string>
+
+    <!-- Title of the Global Actions Dialog -->
+    <string name="global_actions">Phone options</string>
+
+    <!-- label for item that locks the phone in the phone options dialog -->
+    <string name="global_action_lock">Screen lock</string>
+
+    <!-- label for item that turns off power in phone options dialog -->
+    <string name="global_action_power_off">Power off</string>
+
+    <!-- label for item that enables silent mode in phone options dialog -->
+    <string name="global_action_toggle_silent_mode">Silent mode</string>
+
+    <!-- status message in phone options dialog for when silent mode is enabled -->
+    <string name="global_action_silent_mode_on_status">Sound is OFF</string>
+
+    <!-- status message in phone options dialog for when silent mode is disabled -->
+    <string name="global_action_silent_mode_off_status">Sound is ON</string>
+
+    <!-- label for item that toggles airplane mode -->
+    <string name="global_actions_toggle_airplane_mode">Airplane mode</string>
+
+    <!-- status message in phone options dialog for when airplane mode is on -->
+    <string name="global_actions_airplane_mode_on_status">Airplane mode is ON</string>
+
+    <!-- status message in phone options dialog for when airplane mode is off -->
+    <string name="global_actions_airplane_mode_off_status">Airplane mode is OFF</string>
+
+    <!-- Displayed to the user to tell them that they have started up the phone in "safe mode" -->
+    <string name="safeMode">Safe mode</string>
+
+    <!-- Label for the Android system components when they are shown to the user. -->
+    <string name="android_system_label">Android System</string>
+    
+    <!-- Title of a category of application permissions, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permgrouplab_costMoney">Services that cost you money</string>
+    <!-- Description of a category of application permissions, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permgroupdesc_costMoney">Allow applications to do things
+        that can cost you money.</string>
+
+    <!-- Title of a category of application permissions, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permgrouplab_messages">Your messages</string>
+    <!-- Description of a category of application permissions, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permgroupdesc_messages">Read and write your SMS,
+        e-mail, and other messages.</string>
+
+    <!-- Title of a category of application permissions, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permgrouplab_personalInfo">Your personal information</string>
+    <!-- Description of a category of application permissions, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permgroupdesc_personalInfo">Direct access to your contacts
+        and calendar stored on the phone.</string>
+
+    <!-- Title of a category of application permissions, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permgrouplab_location">Your location</string>
+    <!-- Description of a category of application permissions, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permgroupdesc_location">Monitor your physical location</string>
+
+    <!-- Title of a category of application permissions, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permgrouplab_network">Network communication</string>
+    <!-- Description of a category of application permissions, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permgroupdesc_network">Allow applications to access
+        various network features.</string>
+
+    <!-- Title of a category of application permissions, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permgrouplab_accounts">Your Google accounts</string>
+    <!-- Description of a category of application permissions, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permgroupdesc_accounts">Access the available Google accounts.</string>
+
+    <!-- Title of a category of application permissions, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permgrouplab_hardwareControls">Hardware controls</string>
+    <!-- Description of a category of application permissions, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permgroupdesc_hardwareControls">Direct access to hardware on
+        the handset.</string>
+
+    <!-- Title of a category of application permissions, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permgrouplab_phoneCalls">Phone calls</string>
+    <!-- Description of a category of application permissions, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permgroupdesc_phoneCalls">Monitor, record, and process
+        phone calls.</string>
+
+    <!-- Title of a category of application permissions, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permgrouplab_systemTools">System tools</string>
+    <!-- Description of a category of application permissions, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permgroupdesc_systemTools">Lower-level access and control
+        of the system.</string>
+
+    <!-- Title of a category of application permissions, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permgrouplab_developmentTools">Development tools</string>
+    <!-- Description of a category of application permissions, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permgroupdesc_developmentTools">Features only needed for
+        application developers.</string>
+
+    <!--  Permissions -->
+
+    <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permlab_statusBar">disable or modify status bar</string>
+    <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permdesc_statusBar">Allows application to disable
+        the status bar or add and remove system icons.</string>
+
+    <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permlab_expandStatusBar">expand/collapse status bar</string>
+    <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permdesc_expandStatusBar">Allows application to
+        expand or collapse the status bar.</string>
+
+    <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permlab_processOutgoingCalls">intercept outgoing calls</string>
+    <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permdesc_processOutgoingCalls">Allows application to
+        process outgoing calls and change the number to be dialed.  Malicious
+        applications may monitor, redirect, or prevent outgoing calls.</string>
+
+    <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permlab_receiveSms">receive SMS</string>
+    <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permdesc_receiveSms">Allows application to receive
+      and process SMS messages. Malicious applications may monitor
+      your messages or delete them without showing them to you.</string>
+
+    <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permlab_receiveMms">receive MMS</string>
+    <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permdesc_receiveMms">Allows application to receive
+      and process MMS messages. Malicious applications may monitor
+      your messages or delete them without showing them to you.</string>
+
+    <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permlab_sendSms">send SMS messages</string>
+    <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permdesc_sendSms">Allows application to send SMS
+      messages. Malicious applications may cost you money by sending
+      messages without your confirmation.</string>
+
+    <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permlab_readSms">read SMS or MMS</string>
+    <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permdesc_readSms">Allows application to read
+      SMS messages stored on your phone or SIM card. Malicious applications
+      may read your confidential messages.</string>
+
+    <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permlab_writeSms">edit SMS or MMS</string>
+    <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permdesc_writeSms">Allows application to write
+      to SMS messages stored on your phone or SIM card. Malicious applications
+      may delete your messages.</string>
+
+    <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permlab_receiveWapPush">receive WAP</string>
+    <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permdesc_receiveWapPush">Allows application to receive
+      and process WAP messages. Malicious applications may monitor
+      your messages or delete them without showing them to you.</string>
+
+    <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permlab_getTasks">retrieve running applications</string>
+    <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permdesc_getTasks">Allows application to retrieve
+        information about currently and recently running tasks. May allow
+        malicious applications to discover private information about other applications.</string>
+
+    <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permlab_reorderTasks">reorder running applications</string>
+    <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permdesc_reorderTasks">Allows an application to move
+        tasks to the foreground and background. Malicious applications can force
+        themselves to the front without your control.</string>
+
+    <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permlab_setDebugApp">enable application debugging</string>
+    <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permdesc_setDebugApp">Allows an application to turn
+        on debugging for another application. Malicious applications can use this
+        to kill other applications.</string>
+
+    <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permlab_changeConfiguration">change your UI settings</string>
+    <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permdesc_changeConfiguration">Allows an application to
+        change the current configuration, such as the locale or overall font
+        size.</string>
+
+    <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permlab_restartPackages">restart other applications</string>
+    <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permdesc_restartPackages">Allows an application to
+        forcibly restart other applications.</string>
+
+    <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permlab_setProcessForeground">keep from being stopped</string>
+    <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permdesc_setProcessForeground">Allows an application to make
+        any process run in the foreground, so it can\'t be killed.
+        Should never be needed for normal applications.</string>
+
+    <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permlab_forceBack">force application to close</string>
+    <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permdesc_forceBack">Allows an application to force any
+        activity that is in the foreground to close and go back.
+        Should never be needed for normal applications.</string>
+
+    <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permlab_dump">retrieve system internal state</string>
+    <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permdesc_dump">Allows application to retrieve
+        internal state of the system. Malicious applications may retrieve
+        a wide variety of private and secure information that they should
+        never normally need.</string>
+
+    <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permlab_addSystemService">publish low-level services</string>
+    <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permdesc_addSystemService">Allows application to publish
+        its own low-level system services. Malicious applications may hijack
+        the system, and steal or corrupt any data on it.</string>
+
+    <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permlab_runSetActivityWatcher">monitor and control all application launching</string>
+    <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permdesc_runSetActivityWatcher">Allows an application to
+        monitor and control how the system launches activities.
+        Malicious applications may completely compromise the system. This
+        permission is only needed for development, never for normal
+        phone usage.</string>
+
+    <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permlab_broadcastPackageRemoved">send package removed broadcast</string>
+    <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permdesc_broadcastPackageRemoved">Allows an application to
+        broadcast a notification that an application package has been removed.
+        Malicious applications may use this to kill any other running
+        application.</string>
+
+    <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permlab_broadcastSmsReceived">send SMS-received broadcast</string>
+    <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permdesc_broadcastSmsReceived">Allows an application to
+        broadcast a notification that an SMS message has been received.
+        Malicious applications may use this to forge incoming SMS messages.</string>
+
+    <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permlab_broadcastWapPush">send WAP-PUSH-received broadcast</string>
+    <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permdesc_broadcastWapPush">Allows an application to
+        broadcast a notification that a WAP PUSH message has been received.
+        Malicious applications may use this to forge MMS message receipt or to
+        silently replace the content of any web page with malicious variants.</string>
+
+    <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permlab_setProcessLimit">limit number of running processes</string>
+    <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permdesc_setProcessLimit">Allows an application
+        to control the maximum number of processes that will run. Never
+        needed for normal applications.</string>
+
+    <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permlab_setAlwaysFinish">make all background applications close</string>
+    <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permdesc_setAlwaysFinish">Allows an application
+        to control whether activities are always finished as soon as they
+        go to the background. Never needed for normal applications.</string>
+
+    <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permlab_fotaUpdate">automatically install system updates</string>
+    <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permdesc_fotaUpdate">Allows an application to receive
+        notifications about pending system updates and trigger their
+        installation. Malicious applications may use this to corrupt the system
+        with unauthorized updates, or generally interfere with the update
+        process.</string>
+
+    <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permlab_batteryStats">modify battery statistics</string>
+    <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permdesc_batteryStats">Allows the modification of
+        collected battery statistics. Not for use by normal applications.</string>
+
+    <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permlab_internalSystemWindow">display unauthorized windows</string>
+    <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permdesc_internalSystemWindow">Allows the creation of
+        windows that are intended to be used by the internal system
+        user interface. Not for use by normal applications.</string>
+
+    <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permlab_systemAlertWindow">display system-level alerts</string>
+    <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permdesc_systemAlertWindow">Allows an application to
+        show system alert windows. Malicious applications can take over the
+        entire screen of the phone.</string>
+
+    <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permlab_setAnimationScale">modify global animation speed</string>
+    <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permdesc_setAnimationScale">Allows an application to change
+        the global animation speed (faster or slower animations) at any time.</string>
+
+    <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permlab_manageAppTokens">manage application tokens</string>
+    <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permdesc_manageAppTokens">Allows applications to
+        create and manage their own tokens, bypassing their normal
+        Z-ordering. Should never be needed for normal applications.</string>
+
+    <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permlab_injectEvents">press keys and control buttons</string>
+    <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permdesc_injectEvents">Allows an application to deliver
+        its own input events (key presses, etc.) to other applications. Malicious
+        applications can use this to take over the phone.</string>
+
+    <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permlab_readInputState">record what you type and actions you take</string>
+    <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permdesc_readInputState">Allows applications to watch the
+        keys you press even when interacting with another application (such
+        as entering a password). Should never be needed for normal applications.</string>
+
+    <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permlab_bindInputMethod">bind to an input method</string>
+    <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permdesc_bindInputMethod">Allows the holder to bind to the top-level
+        interface of an input method. Should never be needed for normal applications.</string>
+
+    <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permlab_setOrientation">change screen orientation</string>
+    <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permdesc_setOrientation">Allows an application to change
+        the rotation of the screen at any time. Should never be needed for
+        normal applications.</string>
+
+    <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permlab_signalPersistentProcesses">send Linux signals to applications</string>
+    <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permdesc_signalPersistentProcesses">Allows application to request that the
+        supplied signal be sent to all persistent processes.</string>
+
+    <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permlab_persistentActivity">make application always run</string>
+    <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permdesc_persistentActivity">Allows an application to make
+        parts of itself persistent, so the system can\'t use it for other
+        applications.</string>
+
+    <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permlab_deletePackages">delete applications</string>
+    <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permdesc_deletePackages">Allows an application to delete
+        Android packages. Malicious applications can use this to delete important applications.</string>
+
+    <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permlab_clearAppUserData">delete other applications\' data</string>
+    <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permdesc_clearAppUserData">Allows an application to clear user data.</string>
+    <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permlab_deleteCacheFiles">delete other applications\' caches</string>
+    <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permdesc_deleteCacheFiles">Allows an application to delete
+        cache files.</string>
+
+    <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permlab_getPackageSize">measure application storage space</string>
+    <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permdesc_getPackageSize">Allows an application to retrieve
+        its code, data, and cache sizes</string>
+
+    <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permlab_installPackages">directly install applications</string>
+    <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permdesc_installPackages">Allows an application to install new or updated
+        Android packages. Malicious applications can use this to add new applications with arbitrarily
+        powerful permissions.</string>
+
+    <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permlab_clearAppCache">delete all application cache data</string>
+    <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permdesc_clearAppCache">Allows an application to free phone storage
+        by deleting files in application cache directory. Access is very
+        restricted usually to system process.</string>
+
+    <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permlab_readLogs">read system log files</string>
+    <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permdesc_readLogs">Allows an application to read from the
+        system\'s various log files.  This allows it to discover general
+        information about what you are doing with the phone, but they should
+        not contain any personal or private information.</string>
+
+    <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permlab_diagnostic">read/write to resources owned by diag</string>
+    <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permdesc_diagnostic">Allows an application to read and write to
+    any resource owned by the diag group; for example, files in /dev. This could
+    potentially affect system stability and security. This should be ONLY be used
+    for hardware-specific diagnostics by the manufacturer or operator.</string>
+
+    <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permlab_changeComponentState">enable or disable application components</string>
+    <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permdesc_changeComponentState">Allows an application to change whether a
+        component of another application is enabled or not. Malicious applications can use this
+        to disable important phone capabilities. Care must be used with permission, as it is
+        possible to get application components into an unusable, inconsistent, or unstable state.
+    </string>
+
+    <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permlab_setPreferredApplications">set preferred applications</string>
+    <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permdesc_setPreferredApplications">Allows an application to
+        modify your preferred applications. This can allow malicious applications
+        to silently change the applications that are run, spoofing your
+        existing applications to collect private data from you.</string>
+
+    <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permlab_writeSettings">modify global system settings</string>
+    <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permdesc_writeSettings">Allows an application to modify the
+        system\'s settings data. Malicious applications can corrupt your system\'s
+        configuration.</string>
+
+    <string name="permlab_writeSecureSettings">modify secure system settings</string>
+    <string name="permdesc_writeSecureSettings">Allows an application to modify the
+        system's secure settings data. Not for use by normal applications.</string>
+
+    <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permlab_writeGservices">modify the Google services map</string>
+    <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permdesc_writeGservices">Allows an application to modify the
+        Google services map.  Not for use by normal applications.</string>
+
+    <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permlab_receiveBootCompleted">automatically start at boot</string>
+    <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permdesc_receiveBootCompleted">Allows an application to
+        have itself started as soon as the system has finished booting.
+        This can make it take longer to start the phone and allow the
+        application to slow down the overall phone by always running.</string>
+
+    <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permlab_broadcastSticky">send sticky broadcast</string>
+    <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permdesc_broadcastSticky">Allows an application to send
+        sticky broadcasts, which remain after the broadcast ends.
+        Malicious applications can make the phone slow or unstable by causing it
+        to use too much memory.</string>
+
+    <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permlab_readContacts">read contact data</string>
+    <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permdesc_readContacts">Allows an application to read all
+        of the contact (address) data stored on your phone. Malicious applications
+        can use this to send your data to other people.</string>
+
+    <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permlab_writeContacts">write contact data</string>
+    <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permdesc_writeContacts">Allows an application to modify the
+        contact (address) data stored on your phone. Malicious
+        applications can use this to erase or modify your contact data.</string>
+
+    <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permlab_writeOwnerData">write owner data</string>
+    <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permdesc_writeOwnerData">Allows an application to modify the
+        phone owner data stored on your phone. Malicious
+        applications can use this to erase or modify owner data.</string>
+
+    <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permlab_readOwnerData">read owner data</string>
+    <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permdesc_readOwnerData">Allows an application read the
+        phone owner data stored on your phone. Malicious
+        applications can use this to read phone owner data.</string>
+
+    <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permlab_readCalendar">read calendar data</string>
+    <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permdesc_readCalendar">Allows an application to read all
+        of the calendar events stored on your phone. Malicious applications
+        can use this to send your calendar events to other people.</string>
+
+    <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permlab_writeCalendar">write calendar data</string>
+    <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permdesc_writeCalendar">Allows an application to modify the
+        calendar events stored on your phone. Malicious
+        applications can use this to erase or modify your calendar data.</string>
+
+    <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permlab_accessMockLocation">mock location sources for testing</string>
+    <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permdesc_accessMockLocation">Create mock location sources for testing.
+        Malicious applications can use this to override the location and/or status returned by real
+        location sources such as GPS or Network providers.</string>
+
+    <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permlab_accessLocationExtraCommands">access extra location provider commands</string>
+    <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permdesc_accessLocationExtraCommands">Access extra location provider commands.
+        Malicious applications could use this to interfere with the operation of the GPS
+        or other location sources.</string>
+
+    <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permlab_accessFineLocation">fine (GPS) location</string>
+    <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permdesc_accessFineLocation">Access fine location sources such as the
+        Global Positioning System on the phone, where available.
+        Malicious applications can use this to determine where you are, and may
+        consume additional battery power.</string>
+
+    <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permlab_accessCoarseLocation">coarse (network-based) location</string>
+    <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permdesc_accessCoarseLocation">Access coarse location sources such as the cellular
+        network database to determine an approximate phone location, where available. Malicious
+        applications can use this to determine approximately where you are.</string>
+
+    <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permlab_accessSurfaceFlinger">access SurfaceFlinger</string>
+    <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permdesc_accessSurfaceFlinger">Allows application to use
+        SurfaceFlinger low-level features.</string>
+
+    <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permlab_readFrameBuffer">read frame buffer</string>
+    <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permdesc_readFrameBuffer">Allows application to use
+        read the content of the frame buffer.</string>
+
+    <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permlab_modifyAudioSettings">change your audio settings</string>
+    <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permdesc_modifyAudioSettings">Allows application to modify
+        global audio settings such as volume and routing.</string>
+
+    <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permlab_recordAudio">record audio</string>
+    <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permdesc_recordAudio">Allows application to access
+        the audio record path.</string>
+
+    <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permlab_camera">take pictures</string>
+    <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permdesc_camera">Allows application to take pictures
+        with the camera. This allows the application at any time to collect
+        images the camera is seeing.</string>
+
+    <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permlab_brick">permanently disable phone</string>
+    <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permdesc_brick">Allows the application to
+        disable the entire phone permanently. This is very dangerous.</string>
+
+    <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permlab_reboot">force phone reboot</string>
+    <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permdesc_reboot">Allows the application to
+        force the phone to reboot.</string>
+
+    <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permlab_mount_unmount_filesystems">mount and unmount filesystems</string>
+    <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permdesc_mount_unmount_filesystems">Allows the application to mount and
+        unmount filesystems for removable storage.</string>
+
+    <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permlab_mount_format_filesystems">format external storage</string>
+    <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permdesc_mount_format_filesystems">Allows the application to format removable storage.</string>
+
+    <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permlab_vibrate">control vibrator</string>
+    <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permdesc_vibrate">Allows the application to control
+        the vibrator.</string>
+
+    <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permlab_flashlight">control flashlight</string>
+    <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permdesc_flashlight">Allows the application to control
+        the flashlight.</string>
+
+    <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permlab_hardware_test">test hardware</string>
+    <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permdesc_hardware_test">Allows the application to control
+        various peripherals for the purpose of hardware testing.</string>
+
+    <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permlab_callPhone">directly call phone numbers</string>
+    <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permdesc_callPhone">Allows the application to call
+        phone numbers without your intervention. Malicious applications may
+        cause unexpected calls on your phone bill. Note that this does not
+        allow the application to call emergency numbers.</string>
+
+    <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permlab_callPrivileged">directly call any phone numbers</string>
+    <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permdesc_callPrivileged">Allows the application to call
+        any phone number, including emergency numbers, without your intervention.
+        Malicious applications may place unnecessary and illegal calls to emergency
+        services.</string>
+
+    <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permlab_locationUpdates">control location update notifications</string>
+    <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permdesc_locationUpdates">Allows enabling/disabling location
+        update notifications from the radio.  Not for use by normal applications.</string>
+
+    <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permlab_checkinProperties">access checkin properties</string>
+    <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permdesc_checkinProperties">Allows read/write access to
+        properties uploaded by the checkin service.  Not for use by normal
+        applications.</string>
+
+
+    <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permlab_bindGadget">choose gadgets</string>
+    <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permdesc_bindGadget">Allows the application to tell the system
+        which gadgets can be used by which application.  With this permission,
+        applications can give access to personal data to other applications.
+        Not for use by normal applications.</string>
+
+    <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permlab_modifyPhoneState">modify phone state</string>
+    <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permdesc_modifyPhoneState">Allows the application to control the
+        phone features of the device. An application with this permission can switch
+        networks, turn the phone radio on and off and the like without ever notifying
+        you.</string>
+
+    <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permlab_readPhoneState">read phone state</string>
+    <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permdesc_readPhoneState">Allows the application to access the phone
+        features of the device.  An application with this permission can determine the phone
+        number of this phone, whether a call is active, the number that call is connected to
+        and the like.</string>
+
+    <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permlab_wakeLock">prevent phone from sleeping</string>
+    <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permdesc_wakeLock">Allows an application to prevent
+        the phone from going to sleep.</string>
+
+    <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permlab_devicePower">power phone on or off</string>
+    <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permdesc_devicePower">Allows the application to turn the
+        phone on or off.</string>
+
+    <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permlab_factoryTest">run in factory test mode</string>
+    <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permdesc_factoryTest">Run as a low-level manufacturer test,
+        allowing complete access to the phone hardware. Only available
+        when a phone is running in manufacturer test mode.</string>
+
+    <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permlab_setWallpaper">set wallpaper</string>
+    <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permdesc_setWallpaper">Allows the application
+        to set the system wallpaper.</string>
+
+    <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permlab_setWallpaperHints">set wallpaper size hints</string>
+    <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permdesc_setWallpaperHints">Allows the application
+        to set the system wallpaper size hints.</string>
+
+    <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permlab_masterClear">reset system to factory defaults</string>
+    <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permdesc_masterClear">Allows an application to completely
+        reset the system to its factory settings, erasing all data,
+        configuration, and installed applications.</string>
+
+    <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permlab_setTimeZone">set time zone</string>
+    <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permdesc_setTimeZone">Allows an application to change
+        the phone\'s time zone.</string>
+
+    <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permlab_getAccounts">discover known accounts</string>
+    <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permdesc_getAccounts">Allows an application to get
+      the list of accounts known by the phone.</string>
+
+    <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permlab_accessNetworkState">view network state</string>
+    <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permdesc_accessNetworkState">Allows an application to view
+      the state of all networks.</string>
+
+    <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permlab_createNetworkSockets">full Internet access</string>
+    <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permdesc_createNetworkSockets">Allows an application to
+      create network sockets.</string>
+
+    <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permlab_writeApnSettings">write Access Point Name settings</string>
+    <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permdesc_writeApnSettings">Allows an application to modify the APN
+        settings, such as Proxy and Port of any APN.</string>
+
+    <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permlab_changeNetworkState">change network connectivity</string>
+    <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permdesc_changeNetworkState">Allows an application to change
+      the state network connectivity.</string>
+
+    <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permlab_changeBackgroundDataSetting">change background data usage setting</string>
+    <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permdesc_changeBackgroundDataSetting">Allows an application to change
+      the background data usage setting.</string>
+
+    <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permlab_accessWifiState">view Wi-Fi state</string>
+    <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permdesc_accessWifiState">Allows an application to view
+      the information about the state of Wi-Fi.</string>
+
+    <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permlab_changeWifiState">change Wi-Fi state</string>
+    <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permdesc_changeWifiState">Allows an application to connect
+      to and disconnect from Wi-Fi access points, and to make changes to
+      configured Wi-Fi networks.</string>
+
+    <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permlab_bluetoothAdmin">bluetooth administration</string>
+    <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permdesc_bluetoothAdmin">Allows an application to configure
+      the local Bluetooth phone, and to discover and pair with remote
+      devices.</string>
+
+    <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permlab_bluetooth">create Bluetooth connections</string>
+    <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permdesc_bluetooth">Allows an application to view
+      configuration of the local Bluetooth phone, and to make and accept
+      connections with paired devices.</string>
+
+    <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permlab_disableKeyguard">disable keylock</string>
+    <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permdesc_disableKeyguard">Allows an application to disable
+      the keylock and any associated password security. A legitimate example of
+      this is the phone disabling the keylock when receiving an incoming phone call,
+      then re-enabling the keylock when the call is finished.</string>
+
+    <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permlab_readSyncSettings">read sync settings</string>
+    <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permdesc_readSyncSettings">Allows an application to read the sync settings,
+        such as whether sync is enabled for Contacts.</string>
+
+    <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permlab_writeSyncSettings">write sync settings</string>
+    <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permdesc_writeSyncSettings">Allows an application to modify the sync
+        settings, such as whether sync is enabled for Contacts.</string>
+
+    <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permlab_readSyncStats">read sync statistics</string>
+    <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permdesc_readSyncStats">Allows an application to read the sync stats; e.g., the
+        history of syncs that have occurred.</string>
+
+    <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permlab_subscribedFeedsRead">read subscribed feeds</string>
+    <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permdesc_subscribedFeedsRead">Allows an application to get details about the currently synced feeds.</string>
+
+    <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permlab_subscribedFeedsWrite">write subscribed feeds</string>
+    <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permdesc_subscribedFeedsWrite">Allows an application to modify
+      your currently synced feeds. This could allow a malicious application to
+      change your synced feeds.</string>
+
+    <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permlab_readDictionary">read user defined dictionary</string>
+    <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permdesc_readDictionary">Allows an application to read any private
+      words, names and phrases that the user may have stored in the user dictionary.</string>
+
+    <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permlab_writeDictionary">write to user defined dictionary</string>
+    <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permdesc_writeDictionary">Allows an application to write new words into the
+      user dictionary.</string>
+
+    <!-- The order of these is important, don't reorder without changing Contacts.java --> <skip />
+    <!-- Phone number types from android.provider.Contacts. This could be used when adding a new phone number for a contact, for example. -->
+    <string-array name="phoneTypes">
+        <item>Home</item>
+        <item>Mobile</item>
+        <item>Work</item>
+        <item>Work Fax</item>
+        <item>Home Fax</item>
+        <item>Pager</item>
+        <item>Other</item>
+        <item>Custom</item>
+    </string-array>
+
+    <!-- The order of these is important, don't reorder without changing Contacts.java --> <skip />
+    <!-- Email address types from android.provider.Contacts. This could be used when adding a new e-mail address for a contact, for example. -->
+    <string-array name="emailAddressTypes">
+        <item>Home</item>
+        <item>Work</item>
+        <item>Other</item>
+        <item>Custom</item>
+    </string-array>
+
+    <!-- The order of these is important, don't reorder without changing Contacts.java --> <skip />
+    <!-- Postal address types from android.provider.Contacts. This could be used when adding a new address for a contact, for example. -->
+    <string-array name="postalAddressTypes">
+        <item>Home</item>
+        <item>Work</item>
+        <item>Other</item>
+        <item>Custom</item>
+    </string-array>
+
+    <!-- The order of these is important, don't reorder without changing Contacts.java --> <skip />
+    <!-- Instant Messenger ID types from android.provider.Contacts. This could be used when adding a new IM for a contact, for example. -->
+    <string-array name="imAddressTypes">
+        <item>Home</item>
+        <item>Work</item>
+        <item>Other</item>
+        <item>Custom</item>
+    </string-array>
+
+    <!-- The order of these is important, don't reorder without changing Contacts.java --> <skip />
+    <!-- Organization types from android.provider.Contacts. This could be used when adding a new organization for a contact, for example. -->
+    <string-array name="organizationTypes">
+        <item>Work</item>
+        <item>Other</item>
+        <item>Custom</item>
+    </string-array>
+
+    <!-- The order of these is important, don't reorder without changing Contacts.java --> <skip />
+    <!-- Instant Message protocols/providers from android.provider.Contacts -->
+    <string-array name="imProtocols">
+        <item>AIM</item>
+        <item>Windows Live</item>
+        <item>Yahoo</item>
+        <item>Skype</item>
+        <item>QQ</item>
+        <item>Google Talk</item>
+        <item>ICQ</item>
+        <item>Jabber</item>
+    </string-array>
+
+    <!-- Instructions telling the user to enter their pin to unlock the keyguard.
+         Displayed in one line in a large font.  -->
+    <string name="keyguard_password_enter_pin_code">Enter PIN code</string>
+
+    <!-- Instructions telling the user that they entered the wrong pin while trying
+         to unlock the keyguard.  Displayed in one line in a large font.  -->
+    <string name="keyguard_password_wrong_pin_code">Incorrect PIN code!</string>
+
+    <!-- Instructions telling the user how to unlock the phone. -->
+    <string name="keyguard_label_text">To unlock, press Menu then 0.</string>
+
+    <!-- This can be used in any application wanting to disable the text "Emergency number" -->
+    <string name="emergency_call_dialog_number_for_display">Emergency number</string>
+
+    <!--
+       *** touch based lock / unlock ***
+                                          --> <skip />
+
+    <!-- On the keyguard screen, it shows the carrier the phone is connected to.  This is displayed if the phone is not connected to a carrier.-->
+    <string name="lockscreen_carrier_default">(No service)</string>
+
+    <!-- Shown in the lock screen to tell the user that the screen is locked. -->
+    <string name="lockscreen_screen_locked">Screen locked.</string>
+
+    <!-- when pattern lock is enabled, tell them about the emergency dial -->
+    <string name="lockscreen_instructions_when_pattern_enabled">Press Menu to unlock or place emergency call.</string>
+
+    <!-- On the keyguard screen, when pattern lock is disabled, only tell them to press menu to unlock.  This is shown in small font at the bottom. -->
+    <string name="lockscreen_instructions_when_pattern_disabled">Press Menu to unlock.</string>
+
+    <!-- On the unlock pattern screen, shown at the top of the unlock screen to tell the user what to do. Below this text is the place for theu ser to draw the pattern. -->
+    <string name="lockscreen_pattern_instructions">Draw pattern to unlock</string>
+    <!-- Button at the bottom of the unlock screen to make an emergency call. -->
+    <string name="lockscreen_emergency_call">Emergency call</string>
+    <!-- Shown to confirm that the user entered their lock pattern correctly. -->
+    <string name="lockscreen_pattern_correct">Correct!</string>
+    <!-- On the unlock pattern screen, shown when the user enters the wrong lock pattern and must try again. -->
+    <string name="lockscreen_pattern_wrong">Sorry, try again</string>
+
+    <!-- When the lock screen is showing and the phone plugged in, show the current
+         charge %.  -->
+    <string name="lockscreen_plugged_in">Charging (<xliff:g id="number">%d</xliff:g><xliff:g id="percent">%%</xliff:g>)</string>
+
+    <!-- When the lock screen is showing and the battery is low, warn user to plug
+         in the phone soon. -->
+    <string name="lockscreen_low_battery">Connect your charger.</string>
+
+    <!-- Shown in the lock screen when there is no SIM card. -->
+    <string name="lockscreen_missing_sim_message_short">No SIM card.</string>
+    <!-- Shown in the lock screen when there is no SIM card. -->
+    <string name="lockscreen_missing_sim_message">No SIM card in phone.</string>
+    <!-- Shown in the lock screen to ask the user to insert a SIM card. -->
+    <string name="lockscreen_missing_sim_instructions">Please insert a SIM card.</string>
+
+
+    <!-- When the user inserts a sim card from an unsupported network, it becomes network
+         locked -->
+    <string name="lockscreen_network_locked_message">Network locked</string>
+
+
+    <!-- When the user enters a wrong sim pin too many times, it becomes
+         PUK locked (Pin Unlock Kode) -->
+    <string name="lockscreen_sim_puk_locked_message">SIM card is PUK-locked.</string>
+    <!-- Shown in the lock screen when the SIM has become PUK locked and the user must call customer care to unlock it. -->
+    <string name="lockscreen_sim_puk_locked_instructions">Please contact Customer Care.</string>
+
+    <!-- Shown in the lock screen to tell the user that their SIM is locked and they must unlock it. -->
+    <string name="lockscreen_sim_locked_message">SIM card is locked.</string>
+
+    <!-- For the unlock screen, When the user enters a sim unlock code, it takes a little while to check
+         whether it is valid, and to unlock the sim if it is valid.  we display a
+         progress dialog in the meantime.  this is the emssage. -->
+    <string name="lockscreen_sim_unlock_progress_dialog_message">Unlocking SIM card\u2026</string>
+
+    <!-- For the unlock screen, Information message shown in dialog when user has too many failed attempts -->
+    <string name="lockscreen_too_many_failed_attempts_dialog_message">
+        You have incorrectly drawn your unlock pattern <xliff:g id="number">%d</xliff:g> times.
+        \n\nPlease try again in <xliff:g id="number">%d</xliff:g> seconds.
+    </string>
+
+    <!-- For the unlock screen, Information message shown in dialog when user is almost at the limit
+         where they will be locked out and may have to enter an alternate username/password to unlock the phone -->
+    <string name="lockscreen_failed_attempts_almost_glogin">
+        You have incorrectly drawn your unlock pattern <xliff:g id="number">%d</xliff:g> times.
+       After <xliff:g id="number">%d</xliff:g> more unsuccessful attempts,
+       you will be asked to unlock your phone using your Google sign-in.\n\n
+       Please try again in <xliff:g id="number">%d</xliff:g> seconds.
+    </string>
+
+    <!-- On the unlock screen, countdown message shown while user is waiting to try again after too many
+         failed attempts -->
+    <string name="lockscreen_too_many_failed_attempts_countdown">Try again in <xliff:g id="number">%d</xliff:g> seconds.</string>
+
+    <!-- On the unlock screen, message shown on button that appears once it's apparent the user may have forgotten
+         their lock gesture -->
+    <string name="lockscreen_forgot_pattern_button_text">Forgot pattern?</string>
+
+    <!-- Title of the unlock screen that uses your Google login and password -->
+    <string name="lockscreen_glogin_too_many_attempts">Too many pattern attempts!</string>
+    <!-- In the unlock screen, message telling the user that they need to use their Google login and password to unlock the phone -->
+    <string name="lockscreen_glogin_instructions">To unlock,\nsign in with your Google account</string>
+    <!-- Hint caption for the username field when unlocking the phone using login and password -->
+    <string name="lockscreen_glogin_username_hint">Username (email)</string>
+    <!-- Hint caption for the password field when unlocking the phone using login and password -->
+    <string name="lockscreen_glogin_password_hint">Password</string>
+    <!-- Button to try to unlock the phone using username and password -->
+    <string name="lockscreen_glogin_submit_button">Sign in</string>
+    <!-- Displayed to the user when unlocking the phone with a username and password fails. -->
+    <string name="lockscreen_glogin_invalid_input">Invalid username or password.</string>
+
+    <!-- A format string for 12-hour time of day (example: "12:30 PM"). -->
+    <string name="status_bar_time_format">"<xliff:g id="hour" example="12">h</xliff:g>:<xliff:g id="minute" example="30">mm</xliff:g> <xliff:g id="ampm" example="AM">AA</xliff:g>"</string>
+
+    <!-- A format string for 12-hour time of day, with lower-case "am" or "pm" (example: "12:30pm"). -->
+    <string name="hour_minute_ampm">"<xliff:g id="hour" example="12">%-l</xliff:g>:<xliff:g id="minute" example="30">%M</xliff:g><xliff:g id="ampm" example="am">%P</xliff:g>"</string>
+
+    <!-- A format string for 12-hour time of day, with capital "AM" or "PM" (example: "12:30PM"). -->
+    <string name="hour_minute_cap_ampm">"<xliff:g id="hour" example="12">%-l</xliff:g>:<xliff:g id="minute" example="30">%M</xliff:g><xliff:g id="ampm" example="AM">%p</xliff:g>"</string>
+
+    <!-- A format string for 12-hour time of day, just the hour, not the minute, with lower-case "am" or "pm" (example: "3pm"). -->
+    <string name="hour_ampm">"<xliff:g id="hour" example="3">%-l</xliff:g><xliff:g id="ampm" example="pm">%P</xliff:g>"</string>
+
+    <!-- A format string for 12-hour time of day, just the hour, not the minute, with capital "AM" or "PM" (example: "3PM"). -->
+    <string name="hour_cap_ampm">"<xliff:g id="hour" example="3">%-l</xliff:g><xliff:g id="ampm" example="PM">%p</xliff:g>"</string>
+
+    <!-- The text for the button in the notification window-shade that clears
+         all of the currently visible notifications. -->
+    <string name="status_bar_clear_all_button">Clear notifications</string>
+
+    <!-- The label in the bar at the top of the status bar when there are no notifications
+         showing. -->
+    <string name="status_bar_no_notifications_title">No notifications</string>
+
+    <!-- The label for the group of notifications for ongoing events in the opened version of
+         the status bar.  An ongoing call is the prime example of this.  The MP3 music player
+         might be another example.  -->
+    <string name="status_bar_ongoing_events_title">Ongoing</string>
+
+    <!-- The label for the group of notifications for recent events in the opened version of
+         the status bar.  Recently received text messsages (SMS), emails, calendar alerts, etc. -->
+    <string name="status_bar_latest_events_title">Notifications</string>
+
+    <!-- The big percent text in the middle of the battery icon that appears when you plug in
+         the charger. -->
+    <string name="battery_status_text_percent_format"><xliff:g id="number" example="50">%d</xliff:g><xliff:g id="percent" example="%">%%</xliff:g></string>
+
+    <!-- The big percent text in the middle of the battery icon that appears when you plug in
+         the charger. This indicates the current status of the battery.  -->
+    <string name="battery_status_charging">Charging\u2026</string>
+
+    <!-- When the battery is low, this is displayed to the user in a dialog.  The title of the low battery alert. -->
+    <string name="battery_low_title">Please connect charger</string>
+
+    <!-- When the battery is low, this is displayed to the user in a dialog. The subtitle of the low battery alert. -->
+    <string name="battery_low_subtitle">The battery is getting low:</string>
+
+    <!-- A message that appears when the battery level is getting low in a dialog.  This is appened to the subtitle of the low battery alert. -->
+    <string name="battery_low_percent_format">less than <xliff:g id="number">%d%%</xliff:g>
+    remaining.</string>
+
+
+    <!-- Title of the alert when something went wrong in the factory test. -->
+    <string name="factorytest_failed">Factory test failed</string>
+    <!-- Error message displayed when a non-system application tries to start a factory test. -->
+    <string name="factorytest_not_system">The FACTORY_TEST action
+        is only supported for packages installed in /system/app.</string>
+    <!-- Error message displayed when the factory test could not be started. -->
+    <string name="factorytest_no_action">No package was found that provides the
+        FACTORY_TEST action.</string>
+    <!-- Button to restart the device after the factory test. -->
+    <string name="factorytest_reboot">Reboot</string>
+
+    <!-- Do not translate.  WebView User Agent string -->
+    <string name="web_user_agent"><xliff:g id="x">Mozilla/5.0 (Linux; U; Android %s)
+        AppleWebKit/528.5+ (KHTML, like Gecko) Version/3.1.2 Mobile Safari/525.20.1</xliff:g></string>
+
+    <!-- Title for a JavaScript dialog. "The page at <url of current page> says:" -->
+    <string name="js_dialog_title">The page at \'<xliff:g id="title">%s</xliff:g>\' says:</string>
+    <!-- Default title for a javascript dialog -->
+    <string name="js_dialog_title_default">JavaScript</string>
+    <!-- Message in a javascript dialog asking if the user wishes to leave the
+             current page -->
+    <string name="js_dialog_before_unload">Navigate away from this page?\n\n<xliff:g id="message">%s</xliff:g>\n\nSelect OK to continue, or Cancel to stay on the current page.</string>
+
+    <!-- Title of the WebView save password dialog.  If the user enters a password in a form on a website, a dialog will come up asking if they want to save the password. -->
+    <string name="save_password_label">Confirm</string>
+    
+    <!-- If the user enters a password in a form on a website, a dialog will come up asking if they want to save the password. Text in the save password dialog, asking if the browser should remember a password. -->
+    <string name="save_password_message">Do you want the browser to remember this password?</string>
+    <!-- If the user enters a password in a form on a website, a dialog will come up asking if they want to save the password. Button in the save password dialog, saying not to remember this password. -->
+    <string name="save_password_notnow">Not now</string>
+    <!-- If the user enters a password in a form on a website, a dialog will come up asking if they want to save the password. Button in the save password dialog, saying to remember this password. -->
+    <string name="save_password_remember">Remember</string>
+    <!-- Button in the save password dialog, saying never to remember this password. This should be short. Should be "Never for this site". But it is too long, use "Never" instead -->
+    <string name="save_password_never">Never</string>
+
+    <!-- Displayed to the user when they do not have permission to open a particular web page. -->
+    <string name="open_permission_deny">You do not have permission to open this page.</string>
+
+    <!-- Displayed to the user to confirm that they have copied text from a web page to the clipboard. -->
+    <string name="text_copied">Text copied to clipboard.</string>
+
+    <!-- Menu item displayed at the end of a menu to allow users to see another page worth of menu items. This is shown on any app's menu as long as the app has too many items in the menu.-->
+    <string name="more_item_label">More</string>
+    <!-- Prepended to the shortcut for a menu item to indicate that the user should hold the MENU button together with the shortcut to invoke the item. For example, if the shortcut to open a new tab in browser is MENU and B together, then this would be prepended to the letter "B" -->
+    <string name="prepend_shortcut_label">Menu+</string>
+    <!-- Displayed in place of the regular shortcut letter when a menu item has Menu+space for the shortcut. -->
+    <string name="menu_space_shortcut_label">space</string>
+    <!-- Displayed in place of the regular shortcut letter when a menu item has Menu+enter for the shortcut. -->
+    <string name="menu_enter_shortcut_label">enter</string>
+    <!-- Displayed in place of the regular shortcut letter when a menu item has Menu+delete for the shortcut. -->
+    <string name="menu_delete_shortcut_label">delete</string>
+
+    <!-- Strings used for search bar --><skip />
+    
+    <!-- This is the default button label in the system-wide search UI. 
+         It is also used by the home screen's search "widget". It should be short -->
+    <string name="search_go">Search</string>
+
+    <!-- String used to display the date. This is shown instead of a date if the date is today's date. -->
+    <string name="today">Today</string>
+    <!-- String used to display the date. This is shown instead of a date if the date is yesterday's date. -->
+    <string name="yesterday">Yesterday</string>
+    <!-- String used to display the date. This is shown instead of a date if the date is tomorrow's date. -->
+    <string name="tomorrow">Tomorrow</string>
+    <!-- String used to display the date. This is the string to say something happened 1 month ago. -->
+    <string name="oneMonthDurationPast">1 month ago</string>
+    <!-- String used to display the date. This is the string to say something happened more than 1 month ago. -->
+    <string name="beforeOneMonthDurationPast">Before 1 month ago</string>
+
+    <!-- This is used to express that something occurred some number of seconds in the past (e.g., 5 seconds ago). -->
+    <plurals name="num_seconds_ago">
+        <item quantity="one">1 second ago</item>
+        <item quantity="other"><xliff:g id="count">%d</xliff:g> seconds ago</item>
+    </plurals>
+
+    <!-- This is used to express that something occurred some number of minutes in the past (e.g., 5 minutes ago). -->
+    <plurals name="num_minutes_ago">
+        <item quantity="one">1 minute ago</item>
+        <item quantity="other"><xliff:g id="count">%d</xliff:g> minutes ago</item>
+    </plurals>
+
+    <!-- This is used to express that something occurred some number of hours in the past (e.g., 5 hours ago). -->
+    <plurals name="num_hours_ago">
+        <item quantity="one">1 hour ago</item>
+        <item quantity="other"><xliff:g id="count">%d</xliff:g> hours ago</item>
+    </plurals>
+
+    <!-- This is used to express that something occurred some number of days in the past (e.g., 5 days ago). -->
+    <plurals name="num_days_ago">
+        <item quantity="one">yesterday</item>
+        <item quantity="other"><xliff:g id="count">%d</xliff:g> days ago</item>
+    </plurals>
+
+    <!-- This is used to express that something will occur some number of seconds in the future (e.g., in 5 seconds). -->
+    <plurals name="in_num_seconds">
+        <item quantity="one">in 1 second</item>
+        <item quantity="other">in <xliff:g id="count">%d</xliff:g> seconds</item>
+    </plurals>
+
+    <!-- This is used to express that something will occur some number of minutes in the future (e.g., in 5 minutes). -->
+    <plurals name="in_num_minutes">
+        <item quantity="one">in 1 minute</item>
+        <item quantity="other">in <xliff:g id="count">%d</xliff:g> minutes</item>
+    </plurals>
+
+    <!-- This is used to express that something will occur some number of hours in the future (e.g., in 5 hours). -->
+    <plurals name="in_num_hours">
+        <item quantity="one">in 1 hour</item>
+        <item quantity="other">in <xliff:g id="count">%d</xliff:g> hours</item>
+    </plurals>
+
+    <!-- This is used to express that something will occur some number of days in the future (e.g., in 5 days). -->
+    <plurals name="in_num_days">
+        <item quantity="one">tomorrow</item>
+        <item quantity="other">in <xliff:g id="count">%d</xliff:g> days</item>
+    </plurals>
+    
+    <!-- This is used to express that something occurred some number of abbreviated seconds in the past (e.g., 5 secs ago). -->
+    <plurals name="abbrev_num_seconds_ago">
+        <item quantity="one">1 sec ago</item>
+        <item quantity="other"><xliff:g id="count">%d</xliff:g> secs ago</item>
+    </plurals>
+
+    <!-- This is used to express that something occurred some number of abbreviated minutes in the past (e.g., 5 mins ago). -->
+    <plurals name="abbrev_num_minutes_ago">
+        <item quantity="one">1 min ago</item>
+        <item quantity="other"><xliff:g id="count">%d</xliff:g> mins ago</item>
+    </plurals>
+
+    <!-- This is used to express that something occurred some number of abbreviated hours in the past (e.g., 5 hrs ago). -->
+    <plurals name="abbrev_num_hours_ago">
+        <item quantity="one">1 hour ago</item>
+        <item quantity="other"><xliff:g id="count">%d</xliff:g> hours ago</item>
+    </plurals>
+
+    <!-- This is used to express that something occurred some number of abbreviated days in the past (e.g., 5 days ago). -->
+    <plurals name="abbrev_num_days_ago">
+        <item quantity="one">yesterday</item>
+        <item quantity="other"><xliff:g id="count">%d</xliff:g> days ago</item>
+    </plurals>
+
+    <!-- This is used to express that something will occur some number of abbreviated seconds in the future (e.g., in 5 secs). -->
+    <plurals name="abbrev_in_num_seconds">
+        <item quantity="one">in 1 sec</item>
+        <item quantity="other">in <xliff:g id="count">%d</xliff:g> secs</item>
+    </plurals>
+
+    <!-- This is used to express that something will occur some number of abbreviated minutes in the future (e.g., in 5 mins). -->
+    <plurals name="abbrev_in_num_minutes">
+        <item quantity="one">in 1 min</item>
+        <item quantity="other">in <xliff:g id="count">%d</xliff:g> mins</item>
+    </plurals>
+
+    <!-- This is used to express that something will occur some number of abbreviated hours in the future (e.g., in 5 hrs). -->
+    <plurals name="abbrev_in_num_hours">
+        <item quantity="one">in 1 hour</item>
+        <item quantity="other">in <xliff:g id="count">%d</xliff:g> hours</item>
+    </plurals>
+
+    <!-- This is used to express that something will occur some number of abbreviated days in the future (e.g., in 5 days). -->
+    <plurals name="abbrev_in_num_days">
+        <item quantity="one">tomorrow</item>
+        <item quantity="other">in <xliff:g id="count">%d</xliff:g> days</item>
+    </plurals>
+
+    <!-- String used to display the date. Preposition for date display ("on May 29") -->
+    <string name="preposition_for_date">on %s</string>
+    <!-- String used to display the date. Preposition for time display ("at 2:33am") -->
+    <string name="preposition_for_time">at %s</string>
+    <!-- String used to display the date. Preposition for year display ("in 2008") -->
+    <string name="preposition_for_year">in %s</string>
+
+    <!-- Appened to express the value is this unit of time: singular day -->
+    <string name="day">day</string>
+    <!-- Appened to express the value is this unit of time: plural days -->
+    <string name="days">days</string>
+    <!-- Appened to express the value is this unit of time: singular hour -->
+    <string name="hour">hour</string>
+    <!-- Appened to express the value is this unit of time: plural hours -->
+    <string name="hours">hours</string>
+    <!-- Appened to express the value is this unit of time: singular minute -->
+    <string name="minute">min</string>
+    <!-- Appened to express the value is this unit of time: plural minutes -->
+    <string name="minutes">mins</string>
+    <!-- Appened to express the value is this unit of time. -->
+    <string name="second">sec</string>
+    <!-- Appened to express the value is this unit of time. -->
+    <string name="seconds">secs</string>
+    <!-- Appened to express the value is this unit of time. -->
+    <string name="week">week</string>
+    <!-- Appened to express the value is this unit of time. -->
+    <string name="weeks">weeks</string>
+    <!-- Appened to express the value is this unit of time. -->
+    <string name="year">year</string>
+    <!-- Appened to express the value is this unit of time. -->
+    <string name="years">years</string>
+
+    <!-- Used in the list of which days of the week a calendar event recurrs on -->
+    <string name="sunday">Sunday</string>
+    <!-- Used in the list of which days of the week a calendar event recurrs on -->
+    <string name="monday">Monday</string>
+    <!-- Used in the list of which days of the week a calendar event recurrs on -->
+    <string name="tuesday">Tuesday</string>
+    <!-- Used in the list of which days of the week a calendar event recurrs on -->
+    <string name="wednesday">Wednesday</string>
+    <!-- Used in the list of which days of the week a calendar event recurrs on -->
+    <string name="thursday">Thursday</string>
+    <!-- Used in the list of which days of the week a calendar event recurrs on -->
+    <string name="friday">Friday</string>
+    <!-- Used in the list of which days of the week a calendar event recurrs on -->
+    <string name="saturday">Saturday</string>
+
+    <!-- Calendar spinner item, to select that an event recurs every weekday. -->
+    <string name="every_weekday">"Every weekday (Mon\u2013Fri)"</string>
+    <!-- Calendar spinner item, to select that an event recurs every day. -->
+    <string name="daily">Daily</string>
+    <!-- Calendar spinner item, to select that an event recurs every week on a particular day of the week. -->
+    <string name="weekly">"Weekly on <xliff:g id="day">%s</xliff:g>"</string>
+    <!-- Calendar spinner item, to select that an event recurs every month. -->
+    <string name="monthly">Monthly</string>
+    <!-- Calendar spinner item, to select that an event recurs every year. -->
+    <string name="yearly">Yearly</string>
+
+
+    <!-- Title for error alert when a video cannot be played.  it can be used by any app. -->
+    <string name="VideoView_error_title">Cannot play video</string>
+    <!-- Text for error alert when a video cannot be played. it can be used by any app. -->
+    <string name="VideoView_error_text_unknown">Sorry, this video cannot be played.</string>
+    <!-- Button to close error alert when a video cannot be played -->
+    <string name="VideoView_error_button">OK</string>
+
+
+    <!-- AM - as in morning - as in 10:30 AM -->
+    <string name="am">"AM"</string>
+
+    <!-- PM - as in afternoon - as in 10:30 PM -->
+    <string name="pm">"PM"</string>
+
+
+    <!-- Numeric form of the day. Example: "12/31/2007" -->
+    <string name="numeric_date">"<xliff:g id="month" example="12">%m</xliff:g>/<xliff:g id="day" example="31">%d</xliff:g>/<xliff:g id="year" example="2008">%Y</xliff:g>"</string>
+
+    <!-- Format indicating a range of time, from a time on one day to a time on another day. 
+         Example: "Mon, Dec 31, 2007, 8am - Tue, Jan 1, 2008, 5pm" -->
+    <string name="wday1_date1_time1_wday2_date2_time2">"<xliff:g id="weekday1" example="Monday">%1$s</xliff:g>, <xliff:g id="date1" example="December 31, 2007">%2$s</xliff:g>, <xliff:g id="time1" example="8am">%3$s</xliff:g> \u2013 <xliff:g id="weekday2" example="Tuesday">%4$s</xliff:g>, <xliff:g id="date2" example="January 1, 2008">%5$s</xliff:g>, <xliff:g id="time2" example="5pm">%6$s</xliff:g>"</string>
+
+    <!-- Format indicating a range of dates, from one date to another.
+         Example: "Mon, Dec 31, 2007 - Tue, Jan 1, 2008" -->
+    <string name="wday1_date1_wday2_date2">"<xliff:g id="weekday1" example="Monday">%1$s</xliff:g>, <xliff:g id="date1" example="Dec 31, 2007">%2$s</xliff:g> \u2013 <xliff:g id="weekday2" example="Thursday">%4$s</xliff:g>, <xliff:g id="date2" example="Jan 1, 2008">%5$s</xliff:g>"</string>
+
+    <!-- Format indicating a range of time, from a time on one day to a time on another day. 
+         Example: "Dec 31, 2007, 8am - Jan 1, 2008, 5pm" -->
+    <string name="date1_time1_date2_time2">"<xliff:g id="date1" example="Dec 31, 2007">%2$s</xliff:g>, <xliff:g id="time1" example="8am">%3$s</xliff:g> \u2013 <xliff:g id="date2" example="Jan 1, 2008">%5$s</xliff:g>, <xliff:g id="time2" example="5pm">%6$s</xliff:g>"</string>
+
+    <!-- Format indicating a range of dates, from one date to another.
+         Example: "Dec 31, 2007 - Jan 1, 2008" -->
+    <string name="date1_date2">"<xliff:g id="date1" example="Dec 31, 2007">%2$s</xliff:g> \u2013 <xliff:g id="date2" example="Jan 1, 2008">%5$s</xliff:g>"</string>
+
+    <!-- Format indicating a range of times, from one time to another.
+         Example: "10:00 - 11:00 am" -->
+    <string name="time1_time2">"<xliff:g id="time1" example="10:00">%1$s</xliff:g> \u2013 <xliff:g id="time2" example="11:00">%2$s</xliff:g>"</string>
+
+    <!-- Format indicating a range of times on a particular date.
+         Example: "8:00 - 11:00 am, Mon, Dec 31, 2007" -->
+    <string name="time_wday_date">"<xliff:g id="time_range" example="8:00 - 11:00 am">%1$s</xliff:g>, <xliff:g id="weekday" example="Mon">%2$s</xliff:g>, <xliff:g id="date" example="Dec 31, 2007">%3$s</xliff:g>"</string>
+
+    <!-- Format indicating a weekday and date.
+         Example: "Mon, Dec 31, 2007" -->
+    <string name="wday_date">"<xliff:g id="weekday" example="Monday">%2$s</xliff:g>, <xliff:g id="date" example="Dec 31, 2007">%3$s</xliff:g>"</string>
+
+    <!-- Format indicating a range of times on a particular date.
+         Example: "8:00 - 11:00 am, Dec 31, 2007" -->
+    <string name="time_date">"<xliff:g id="time_range" example="8:00 - 11:00 am">%1$s</xliff:g>, <xliff:g id="date" example="Dec 31, 2007">%3$s</xliff:g>"</string>
+
+    <!-- Format indicating a specific date and time.
+         Example: "Dec 31, 2007, 11:00 am" -->
+    <string name="date_time">"<xliff:g id="date" example="Dec 31, 2007">%1$s</xliff:g>, <xliff:g id="time" example="11:00 am">%2$s</xliff:g>"</string>
+
+    <!-- Format indicating a relative expression and time.
+         Example: "4 hours ago, 11:00 am" -->
+    <string name="relative_time">"<xliff:g id="date" example="4 hours ago">%1$s</xliff:g>, <xliff:g id="time" example="11:00 am">%2$s</xliff:g>"</string>
+
+    <!-- Format indicating a range of times on a particular day of the week.
+         Example: "8:00 - 11:00 am, Mon" -->
+    <string name="time_wday">"<xliff:g id="time_range" example="8:00 - 11:00 am">%1$s</xliff:g>, <xliff:g id="weekday" example="Mon">%2$s</xliff:g>"</string>
+
+    <!-- Date format string used in contexts where the user has said they
+         want the month first, as used in the USA, with the month fully
+         spelled out.  You can remove the comma or add a period,
+         or make other punctuation changes appropriate for your locale. -->
+    <string name="full_date_month_first" format="date"><xliff:g id="month" example="December">MMMM</xliff:g> <xliff:g id="day" example="31">d</xliff:g>, <xliff:g id="year" example="1972">yyyy</xliff:g></string>
+
+    <!-- Date format string used in contexts where the user has said they
+         want the day of the month first, as used in Europe, with the month
+         fully spelled out.  You can remove the comma or add a period,
+         or make other punctuation changes appropriate for your locale. -->
+    <string name="full_date_day_first" format="date"><xliff:g id="day" example="31">d</xliff:g> <xliff:g id="month" example="December">MMMM</xliff:g>, <xliff:g id="year" example="1972">yyyy</xliff:g></string>
+
+    <!-- Date format string used in contexts where the user has said they
+         want the month first, as used in the USA, with the month
+         abbreviated.  You can remove the comma or add a period,
+         or make other punctuation changes appropriate for your locale. -->
+    <string name="medium_date_month_first" format="date"><xliff:g id="month" example="Dec.">MMM</xliff:g> <xliff:g id="day" example="31">d</xliff:g>, <xliff:g id="year" example="1972">yyyy</xliff:g></string>
+
+    <!-- Date format string used in contexts where the user has said they
+         want the day of the month first, as used in Europe, with the month
+         abbreviated.  You can remove the comma or add a period,
+         or make other punctuation changes appropriate for your locale. -->
+    <string name="medium_date_day_first" format="date"><xliff:g id="day" example="31">d</xliff:g> <xliff:g id="month" example="December">MMM</xliff:g>, <xliff:g id="year" example="1972">yyyy</xliff:g></string>
+
+    <!-- Time format string used in the status bar when the user has said they
+         want a 12-hour clock with AM and PM.
+         You can remove the colon
+         or make other punctuation changes appropriate for your locale. -->
+    <string name="twelve_hour_time_format" format="date"><xliff:g id="hour" example="11">h</xliff:g>:<xliff:g id="minute" example="59">mm</xliff:g> <xliff:g id="ampm" example="AM">a</xliff:g></string>
+
+    <!-- Time format string used in the status bar when the user has said they
+         want a 24-hour clock.
+         You can remove the colon
+         or make other punctuation changes appropriate for your locale. -->
+    <string name="twenty_four_hour_time_format" format="date"><xliff:g id="hour" example="23">H</xliff:g>:<xliff:g id="minute" example="59">mm</xliff:g></string>
+
+    <!-- Quoted name for 12pm, lowercase -->
+    <string name="noon">"noon"</string>
+    <!-- Quoted name for 12pm, uppercase first letter -->
+    <string name="Noon">"Noon"</string>
+    <!-- Quoted name for 12am, lowercase -->
+    <string name="midnight">"midnight"</string>
+    <!-- Quoted name for 12am, uppercase first letter -->
+    <string name="Midnight">"Midnight"</string>
+
+    <!-- Date format for month and day of month.
+         Example: "October 9". -->
+    <string name="month_day">"<xliff:g id="month" example="October">%B</xliff:g> <xliff:g id="day" example="9">%-d</xliff:g>"</string>
+
+    <!-- Date format for month alone.
+         Example: "October" -->
+    <string name="month">"<xliff:g id="month" example="October">%B</xliff:g>"</string>
+
+    <!-- Date format for month, day, and year.
+         Example: "October 9, 2007" -->
+    <string name="month_day_year">"<xliff:g id="month" example="October">%B</xliff:g> <xliff:g id="day" example="9">%-d</xliff:g>, <xliff:g id="year" example="2007">%Y</xliff:g>"</string>
+
+    <!-- Date format for month and year.
+         Example: "October 2007" -->
+    <string name="month_year">"<xliff:g id="month" example="October">%B</xliff:g> <xliff:g id="year" example="2007">%Y</xliff:g>"</string>
+
+    <!-- A format string for 24-hour time of day (example "23:59"). -->
+    <string name="time_of_day">"<xliff:g id="hour" example="23">%H</xliff:g>:<xliff:g id="minute" example="59">%M</xliff:g>:<xliff:g id="second" example="59">%S</xliff:g>"</string>
+
+    <!-- Format string for date and 24-hour time of day.
+         Example: 23:59:15 Jan 31 2008 -->
+    <string name="date_and_time">"<xliff:g id="hour" example="23">%H</xliff:g>:<xliff:g id="minute" example="59">%M</xliff:g>:<xliff:g id="second" example="59">%S</xliff:g> <xliff:g id="month" example="Jan">%B</xliff:g> <xliff:g id="day" example="31">%-d</xliff:g>, <xliff:g id="year" example="2008">%Y</xliff:g>"</string>
+
+    <!-- Format indicating a range of dates in the same year.
+         Example: "Oct 31 - Nov 3" -->
+    <string name="same_year_md1_md2">"<xliff:g id="month1" example="Oct">%2$s</xliff:g> <xliff:g id="day1" example="31">%3$s</xliff:g> \u2013 <xliff:g id="month2" example="Nov">%7$s</xliff:g> <xliff:g id="day2" example="3">%8$s</xliff:g>"</string>
+
+    <!-- Format indicating a range of dates in the same year, with weekday.
+         Example: "Wed, Oct 31 - Sat, Nov 3" -->
+    <string name="same_year_wday1_md1_wday2_md2">"<xliff:g id="weekday1" example="Wed">%1$s</xliff:g>, <xliff:g id="month1" example="Oct">%2$s</xliff:g> <xliff:g id="day1" example="31">%3$s</xliff:g> \u2013 <xliff:g id="weekday2" example="Sat">%6$s</xliff:g>, <xliff:g id="month2" example="Nov">%7$s</xliff:g> <xliff:g id="day2" example="3">%8$s</xliff:g>"</string>
+
+    <!-- Format indicating a range of dates in the same year.
+         Example: "Oct 31 - Nov 3, 2007" -->
+    <string name="same_year_mdy1_mdy2">"<xliff:g id="month1" example="Oct">%2$s</xliff:g> <xliff:g id="day1" example="31">%3$s</xliff:g> \u2013 <xliff:g id="month2" example="Nov">%7$s</xliff:g> <xliff:g id="day2" example="3">%8$s</xliff:g>, <xliff:g id="year" example="2007">%9$s</xliff:g>"</string>
+
+    <!-- Format indicating a range of dates in the same year, with weekdays.
+         Example: "Wed, Oct 31 - Sat, Nov 3, 2007" -->
+    <string name="same_year_wday1_mdy1_wday2_mdy2">"<xliff:g id="weekday1" example="Wed">%1$s</xliff:g>, <xliff:g id="month1" example="Oct">%2$s</xliff:g> <xliff:g id="day1" example="31">%3$s</xliff:g> \u2013 <xliff:g id="weekday2" example="Sat">%6$s</xliff:g>, <xliff:g id="month2" example="Nov">%7$s</xliff:g> <xliff:g id="day2" example="3">%8$s</xliff:g>, <xliff:g id="year" example="2007">%9$s</xliff:g>"</string>
+
+    <!-- Format indicating a range of time from a time on one day to a time on another.
+         Example: "Oct 31, 8:00am - Nov 3, 5:00pm" -->
+    <string name="same_year_md1_time1_md2_time2">"<xliff:g id="month1" example="Oct">%2$s</xliff:g> <xliff:g id="day1" example="31">%3$s</xliff:g>, <xliff:g id="time1" example="8:00am">%5$s</xliff:g> \u2013 <xliff:g id="month2" example="Nov">%7$s</xliff:g> <xliff:g id="day2" example="3">%8$s</xliff:g>, <xliff:g id="time2" example="5:00pm">%10$s</xliff:g>"</string>
+
+    <!-- Format indicating a range of time from a time on one day to a time on another, with weekdays.
+         Example: "Wed, Oct 31, 8:00am - Sat, Nov 3, 5:00pm" -->
+    <string name="same_year_wday1_md1_time1_wday2_md2_time2">"<xliff:g id="weekday1" example="Wed">%1$s</xliff:g>, <xliff:g id="month1" example="Oct">%2$s</xliff:g> <xliff:g id="day1" example="31">%3$s</xliff:g>, <xliff:g id="time1" example="8:00am">%5$s</xliff:g> \u2013 <xliff:g id="weekday2" example="Sat">%6$s</xliff:g>, <xliff:g id="month2" example="Nov">%7$s</xliff:g> <xliff:g id="day2" example="3">%8$s</xliff:g>, <xliff:g id ="time2" example="5:00pm">%10$s</xliff:g>"</string>
+
+    <!-- Format indicating a range of time from a time on one day to a time on another, with years and weekdays.
+         Example: "Oct 31, 2007, 8:00am - Nov 3, 2007, 5:00pm" -->
+    <string name="same_year_mdy1_time1_mdy2_time2">"<xliff:g id="month1" example="Oct">%2$s</xliff:g> <xliff:g id="day1" example="31">%3$s</xliff:g>, <xliff:g id="year1" example="2007">%4$s</xliff:g>, <xliff:g id="time1" example="8:00am">%5$s</xliff:g> \u2013 <xliff:g id="month2" example="Nov">%7$s</xliff:g> <xliff:g id="day2" example="3">%8$s</xliff:g>, <xliff:g id="year2" example="2007">%9$s</xliff:g>, <xliff:g id="time2" example="5:00pm">%10$s</xliff:g>"</string>
+
+    <!-- Format indicating a range of time from a time on one day to a time on another.
+     Example: "Wed, Oct 31, 2007, 8:00am - Sat, Nov 3, 2007, 5:00pm" -->
+    <string name="same_year_wday1_mdy1_time1_wday2_mdy2_time2">"<xliff:g id="weekday1" example="Wed">%1$s</xliff:g>, <xliff:g id="month1" example="Oct">%2$s</xliff:g> <xliff:g id="day1" example="31">%3$s</xliff:g>, <xliff:g id="year1" example="2007">%4$s</xliff:g>, <xliff:g id="time1" example="8:00am">%5$s</xliff:g> \u2013 <xliff:g id="weekday2" example="Sat">%6$s</xliff:g>, <xliff:g id="month2" example="Nov">%7$s</xliff:g> <xliff:g id="day2" example="3">%8$s</xliff:g>, <xliff:g id="year2" example="2007">%9$s</xliff:g>, <xliff:g id="time2" example="5:00pm">%10$s</xliff:g>"</string>
+
+
+    <!-- Format indicating a range of (numeric) dates.
+          Example: "10/31 - 11/3" -->
+    <string name="numeric_md1_md2">"<xliff:g id="month1" example="10">%2$s</xliff:g>/<xliff:g id="day1" example="31">%3$s</xliff:g> \u2013 <xliff:g id="month2" example="11">%7$s</xliff:g>/<xliff:g id="day2" example="30">%8$s</xliff:g>"</string>
+
+    <!-- Format indicating a range of (numeric) dates.
+          Example: "Wed, 10/31 - Sat, 11/3" -->
+    <string name="numeric_wday1_md1_wday2_md2">"<xliff:g id="weekday1" example="Wed">%1$s</xliff:g>, <xliff:g id="month1" example="10">%2$s</xliff:g>/<xliff:g id="day1" example="31">%3$s</xliff:g> \u2013 <xliff:g id="weekday2" example="Sat">%6$s</xliff:g>, <xliff:g id="month2" example="11">%7$s</xliff:g>/<xliff:g id="day2" example="30">%8$s</xliff:g>"</string>
+
+    <!-- Format indicating a range of (numeric) dates.
+          Example: "10/31/2007 - 11/3/2007" -->
+    <string name="numeric_mdy1_mdy2">"<xliff:g id="month1" example="10">%2$s</xliff:g>/<xliff:g id="day1" example="31">%3$s</xliff:g>/<xliff:g id="year1" example="2007">%4$s</xliff:g> \u2013 <xliff:g id="month2" example="11">%7$s</xliff:g>/<xliff:g id="day2" example="30">%8$s</xliff:g>/<xliff:g id="year2" example="2007">%9$s</xliff:g>"</string>
+
+    <!-- Format indicating a range of (numeric) dates.
+          Example: "Wed, 10/31/2007 - Sat, 11/3/2007" -->
+    <string name="numeric_wday1_mdy1_wday2_mdy2">"<xliff:g id="weekday1" example="Wed">%1$s</xliff:g>, <xliff:g id="month1" example="10">%2$s</xliff:g>/<xliff:g id="day1" example="31">%3$s</xliff:g>/<xliff:g id="year1" example="2007">%4$s</xliff:g> \u2013 <xliff:g id="weekday2" example="Sat">%6$s</xliff:g>, <xliff:g id="month2" example="11">%7$s</xliff:g>/<xliff:g id="day2" example="30">%8$s</xliff:g>/<xliff:g id="year2" example="2007">%9$s</xliff:g>"</string>
+
+    <!-- Format indicating a range of (numeric) dates and times.
+          Example: "10/31, 8:00am - 11/3, 5:00pm" -->
+    <string name="numeric_md1_time1_md2_time2">"<xliff:g id="month1" example="10">%2$s</xliff:g>/<xliff:g id="day1" example="31">%3$s</xliff:g>, <xliff:g id="time1" example="8:00am">%5$s</xliff:g> \u2013 <xliff:g id="month2" example="11">%7$s</xliff:g>/<xliff:g id="day2" example="30">%8$s</xliff:g>, <xliff:g id="time2" example="5:00pm">%10$s</xliff:g>"</string>
+
+    <!-- Format indicating a range of (numeric) dates and times.
+          Example: "Wed, 10/31, 8:00am - Sat, 11/3, 5:00pm" -->
+    <string name="numeric_wday1_md1_time1_wday2_md2_time2">"<xliff:g id="weekday1" example="Wed">%1$s</xliff:g>, <xliff:g id="month1" example="10">%2$s</xliff:g>/<xliff:g id="day1" example="31">%3$s</xliff:g>, <xliff:g id="time1" example="8:00am">%5$s</xliff:g> \u2013 <xliff:g id="weekday2" example="Sat">%6$s</xliff:g>, <xliff:g id="month2" example="11">%7$s</xliff:g>/<xliff:g id="day2" example="30">%8$s</xliff:g>, <xliff:g id="time2" example="5:00pm">%10$s</xliff:g>"</string>
+
+    <!-- Format indicating a range of (numeric) dates and times.
+          Example: "10/31/2007, 8:00am - 11/3/2007, 5:00pm"  -->
+    <string name="numeric_mdy1_time1_mdy2_time2">"<xliff:g id="month1" example="10">%2$s</xliff:g>/<xliff:g id="day1" example="31">%3$s</xliff:g>/<xliff:g id="year1" example="2007">%4$s</xliff:g>, <xliff:g id="time1" example="8:00am">%5$s</xliff:g> \u2013 <xliff:g id="month2" example="11">%7$s</xliff:g>/<xliff:g id="day2" example="30">%8$s</xliff:g>/<xliff:g id="year2" example="2007">%9$s</xliff:g>, <xliff:g id="time2" example="5:00pm">%10$s</xliff:g>"</string>
+
+    <!-- Format indicating a range of (numeric) dates and times.
+          Example: "Wed, 10/31/2007, 8:00am - Sat, 11/3/2007, 5:00pm" -->
+    <string name="numeric_wday1_mdy1_time1_wday2_mdy2_time2">"<xliff:g id="weekday1" example="Wed">%1$s</xliff:g>, <xliff:g id="month1" example="10">%2$s</xliff:g>/<xliff:g id="day1" example="31">%3$s</xliff:g>/<xliff:g id="year1" example="2007">%4$s</xliff:g>, <xliff:g id="time1" example="8:00am">%5$s</xliff:g> \u2013 <xliff:g id="weekday2" example="Sat">%6$s</xliff:g>, <xliff:g id="month2" example="11">%7$s</xliff:g>/<xliff:g id="day2" example="30">%8$s</xliff:g>/<xliff:g id="year2" example="2007">%9$s</xliff:g>, <xliff:g id="time2" example="5:00pm">%10$s</xliff:g>"</string>
+
+
+    <!-- Format indicating a range of dates.
+          Example: "Oct 9 - 10" -->
+    <string name="same_month_md1_md2">"<xliff:g id="month1" example="Oct">%2$s</xliff:g> <xliff:g id="day1" example="31">%3$s</xliff:g> \u2013 <xliff:g id="day2" example="3">%8$s</xliff:g>"</string>
+
+    <!-- Format indicating a range of dates.
+          Example: "Tue, Oct 9 - Wed, Oct 10"  -->
+    <string name="same_month_wday1_md1_wday2_md2">"<xliff:g id="weekday1" example="Wed">%1$s</xliff:g>, <xliff:g id="month1" example="Oct">%2$s</xliff:g> <xliff:g id="day1" example="31">%3$s</xliff:g> \u2013 <xliff:g id="weekday2" example="Sat">%6$s</xliff:g>, <xliff:g id="month2" example="Nov">%7$s</xliff:g> <xliff:g id="day2" example="3">%8$s</xliff:g>"</string>
+
+    <!-- Format indicating a range of dates.
+          Example: "Oct 9 - 10, 2007" -->
+    <string name="same_month_mdy1_mdy2">"<xliff:g id="month1" example="Oct">%2$s</xliff:g> <xliff:g id="day1" example="31">%3$s</xliff:g> \u2013 <xliff:g id="day2" example="3">%8$s</xliff:g>, <xliff:g id="year2" example="2007">%9$s</xliff:g>"</string>
+
+    <!-- Format indicating a range of dates.
+          Example: "Tue, Oct 9, 2007 - Wed, Oct 10, 2007"  -->
+    <string name="same_month_wday1_mdy1_wday2_mdy2">"<xliff:g id="weekday1" example="Wed">%1$s</xliff:g>, <xliff:g id="month1" example="Oct">%2$s</xliff:g> <xliff:g id="day1" example="31">%3$s</xliff:g>, <xliff:g id="year1" example="2007">%4$s</xliff:g> \u2013 <xliff:g id="weekday2" example="Sat">%6$s</xliff:g>, <xliff:g id="month2" example="Nov">%7$s</xliff:g> <xliff:g id="day2" example="3">%8$s</xliff:g>, <xliff:g id="year2" example="2007">%9$s</xliff:g>"</string>
+
+    <!-- Format indicating a range of dates and times.
+          Example: "Oct 9, 8:00am - Oct 10, 5:00pm"  -->
+    <string name="same_month_md1_time1_md2_time2">"<xliff:g id="month1" example="Oct">%2$s</xliff:g> <xliff:g id="day1" example="31">%3$s</xliff:g>, <xliff:g id="time1" example="8:00am">%5$s</xliff:g> \u2013 <xliff:g id="month2" example="Nov">%7$s</xliff:g> <xliff:g id="day2" example="3">%8$s</xliff:g>, <xliff:g id="time2" example="5:00pm">%10$s</xliff:g>"</string>
+
+    <!-- Format indicating a range of dates and times.
+          Example: "Tue, Oct 9, 8:00am - Wed, Oct 10, 5:00pm"  -->
+    <string name="same_month_wday1_md1_time1_wday2_md2_time2">"<xliff:g id="weekday1" example="Wed">%1$s</xliff:g>, <xliff:g id="month1" example="Oct">%2$s</xliff:g> <xliff:g id="day1" example="31">%3$s</xliff:g>, <xliff:g id="time1" example="8:00am">%5$s</xliff:g> \u2013 <xliff:g id="weekday2" example="Sat">%6$s</xliff:g>, <xliff:g id="month2" example="Nov">%7$s</xliff:g> <xliff:g id="day2" example="3">%8$s</xliff:g>, <xliff:g id="time2" example="5:00pm">%10$s</xliff:g>"</string>
+
+    <!-- Format indicating a range of dates and times.
+          Example: "Oct 9, 2007, 8:00am - Oct 10, 2007, 5:00pm"  -->
+    <string name="same_month_mdy1_time1_mdy2_time2">"<xliff:g id="month1" example="Oct">%2$s</xliff:g> <xliff:g id="day1" example="31">%3$s</xliff:g>, <xliff:g id="year1" example="2007">%4$s</xliff:g>, <xliff:g id="time1" example="8:00am">%5$s</xliff:g> \u2013 <xliff:g id="month2" example="Nov">%7$s</xliff:g> <xliff:g id="day2" example="3">%8$s</xliff:g>, <xliff:g id="year2" example="2007">%9$s</xliff:g>, <xliff:g id="time2" example="5:00pm">%10$s</xliff:g>"</string>
+
+    <!-- Format indicating a range of dates and times.
+          Example: "Tue, Oct 9, 2007, 8:00am - Wed, Oct 10, 2007, 5:00pm" -->
+    <string name="same_month_wday1_mdy1_time1_wday2_mdy2_time2">"<xliff:g id="weekday1" example="Wed">%1$s</xliff:g>, <xliff:g id="month1" example="Oct">%2$s</xliff:g> <xliff:g id="day1" example="31">%3$s</xliff:g>, <xliff:g id="year1" example="2007">%4$s</xliff:g>, <xliff:g id="time1" example="8:00am">%5$s</xliff:g> \u2013 <xliff:g id="weekday2" example="Sat">%6$s</xliff:g>, <xliff:g id="month2" example="Nov">%7$s</xliff:g> <xliff:g id="day2" example="3">%8$s</xliff:g>, <xliff:g id="year2" example="2007">%9$s</xliff:g>, <xliff:g id="time2" example="5:00pm">%10$s</xliff:g>"</string>
+
+    <!-- Format string for abbreviated month, day, and year.
+         Example: "Oct 9, 2007" -->
+    <string name="abbrev_month_day_year">"<xliff:g id="month" example="Oct">%b</xliff:g> <xliff:g id="day" example="9">%-d</xliff:g>, <xliff:g id="year" example="2007">%Y</xliff:g>"</string>
+
+    <!-- Format string for abbreviated month and year.
+         Example: "Oct 2007" -->
+    <string name="abbrev_month_year">"<xliff:g id="month" example="Oct">%b</xliff:g> <xliff:g id="year" example="2007">%Y</xliff:g>"</string>
+
+    <!-- Format string for abbreviated month and day.
+         Example: "Oct 9" -->
+    <string name="abbrev_month_day">"<xliff:g id="month" example="Oct">%b</xliff:g> <xliff:g id="day" example="31">%-d</xliff:g>"</string>
+
+    <!-- Format string for abbreviated month alone.
+         Example: "Oct" -->
+    <string name="abbrev_month">"<xliff:g id="month" example="Oct">%b</xliff:g>"</string>
+
+    <!-- The full spelled out version of the day of the week. -->
+    <string name="day_of_week_long_sunday">Sunday</string>
+
+    <!-- The full spelled out version of the day of the week. -->
+    <string name="day_of_week_long_monday">Monday</string>
+
+    <!-- The full spelled out version of the day of the week. -->
+    <string name="day_of_week_long_tuesday">Tuesday</string>
+
+    <!-- The full spelled out version of the day of the week. -->
+    <string name="day_of_week_long_wednesday">Wednesday</string>
+
+    <!-- The full spelled out version of the day of the week. -->
+    <string name="day_of_week_long_thursday">Thursday</string>
+
+    <!-- The full spelled out version of the day of the week. -->
+    <string name="day_of_week_long_friday">Friday</string>
+
+    <!-- The full spelled out version of the day of the week. -->
+    <string name="day_of_week_long_saturday">Saturday</string>
+
+
+    <!-- An abbreviated day of the week.  Three characters typically in western languages.
+         In US English: "Sun" stands for Sunday -->
+    <string name="day_of_week_medium_sunday">Sun</string>
+
+    <!-- An abbreviated day of the week.  Three characters typically in western languages.
+         In US English: "Mon" stands for Monday -->
+    <string name="day_of_week_medium_monday">Mon</string>
+
+    <!-- An abbreviated day of the week.  Three characters typically in western languages.
+         In US English: "Tue" stands for Tuesday -->
+    <string name="day_of_week_medium_tuesday">Tue</string>
+
+    <!-- An abbreviated day of the week.  Three characters typically in western languages.
+         In US English: "Wed" stands for Wednesday -->
+    <string name="day_of_week_medium_wednesday">Wed</string>
+
+    <!-- An abbreviated day of the week.  Three characters typically in western languages.
+         In US English: "Thu" stands for Thursday -->
+    <string name="day_of_week_medium_thursday">Thu</string>
+
+    <!-- An abbreviated day of the week.  Three characters typically in western languages.
+         In US English: "Fri" stands for Friday -->
+    <string name="day_of_week_medium_friday">Fri</string>
+
+    <!-- An abbreviated day of the week.  Three characters typically in western languages.
+         In US English: "Sat" stands for Saturday -->
+    <string name="day_of_week_medium_saturday">Sat</string>
+
+
+    <!-- An abbreviated day of the week.  Two characters typically in western languages.
+         In US English: "Su" stands for Sunday -->
+    <string name="day_of_week_short_sunday">Su</string>
+
+    <!-- An abbreviated day of the week.  Two characters typically in western languages.
+         In US English: "Mo" stands for Monday -->
+    <string name="day_of_week_short_monday">Mo</string>
+
+    <!-- An abbreviated day of the week.  Two characters typically in western languages.
+         In US English: "Tu" stands for Tuesday -->
+    <string name="day_of_week_short_tuesday">Tu</string>
+
+    <!-- An abbreviated day of the week.  Two characters typically in western languages.
+         In US English: "We" stands for Wednesday -->
+    <string name="day_of_week_short_wednesday">We</string>
+
+    <!-- An abbreviated day of the week.  Two characters typically in western languages.
+         In US English: "Th" stands for Thursday -->
+    <string name="day_of_week_short_thursday">Th</string>
+
+    <!-- An abbreviated day of the week.  Two characters typically in western languages.
+         In US English: "Fr" stands for Friday -->
+    <string name="day_of_week_short_friday">Fr</string>
+
+    <!-- An abbreviated day of the week.  Two characters typically in western languages.
+         In US English: "Sa" stands for Saturday -->
+    <string name="day_of_week_short_saturday">Sa</string>
+
+
+    <!-- An abbreviated day of the week.  One character if that is unique.  Two if necessary.
+         In US English: "Su" stands for Sunday -->
+    <string name="day_of_week_shorter_sunday">Su</string>
+
+    <!-- An abbreviated day of the week.  One character if that is unique.  Two if necessary.
+         In US English: "M" stands for Monday -->
+    <string name="day_of_week_shorter_monday">M</string>
+
+    <!-- An abbreviated day of the week.  One character if that is unique.  Two if necessary.
+         In US English: "Tu" stands for Tuesday -->
+    <string name="day_of_week_shorter_tuesday">Tu</string>
+
+    <!-- An abbreviated day of the week.  One character if that is unique.  Two if necessary.
+         In US English: "W" stands for Wednesday -->
+    <string name="day_of_week_shorter_wednesday">W</string>
+
+    <!-- An abbreviated day of the week.  One character if that is unique.  Two if necessary.
+         In US English: "Th" stands for Thursday -->
+    <string name="day_of_week_shorter_thursday">Th</string>
+
+    <!-- An abbreviated day of the week.  One character if that is unique.  Two if necessary.
+         In US English: "F" stands for Friday -->
+    <string name="day_of_week_shorter_friday">F</string>
+
+    <!-- An abbreviated day of the week.  One character if that is unique.  Two if necessary.
+         In US English: "Sa" stands for Saturday -->
+    <string name="day_of_week_shorter_saturday">Sa</string>
+
+
+    <!-- An abbreviated day of the week.  One character long if it makes sense.  Does not have
+         to be unique.
+         In US English: "S" stands for Sunday -->
+    <string name="day_of_week_shortest_sunday">S</string>
+
+    <!-- An abbreviated day of the week.  One character long if it makes sense.  Does not have
+         to be unique.
+         In US English: "M" stands for Monday -->
+    <string name="day_of_week_shortest_monday">M</string>
+
+    <!-- An abbreviated day of the week.  One character long if it makes sense.  Does not have
+         to be unique.
+         In US English: "T" stands for Tuesday -->
+    <string name="day_of_week_shortest_tuesday">T</string>
+
+    <!-- An abbreviated day of the week.  One character long if it makes sense.  Does not have
+         to be unique.
+         In US English: "W" stands for Wednesday -->
+    <string name="day_of_week_shortest_wednesday">W</string>
+
+    <!-- An abbreviated day of the week.  One character long if it makes sense.  Does not have
+         to be unique.
+         In US English: "T" stands for Thursday -->
+    <string name="day_of_week_shortest_thursday">T</string>
+
+    <!-- An abbreviated day of the week.  One character long if it makes sense.  Does not have
+         to be unique.
+         In US English: "F" stands for Friday -->
+    <string name="day_of_week_shortest_friday">F</string>
+
+    <!-- An abbreviated day of the week.  One character long if it makes sense.  Does not have
+         to be unique.
+         In US English: "S" stands for Saturday -->
+    <string name="day_of_week_shortest_saturday">S</string>
+
+
+    <!-- The full spelled out version of the month. -->
+    <string name="month_long_january">January</string>
+
+    <!-- The full spelled out version of the month. -->
+    <string name="month_long_february">February</string>
+
+    <!-- The full spelled out version of the month. -->
+    <string name="month_long_march">March</string>
+
+    <!-- The full spelled out version of the month. -->
+    <string name="month_long_april">April</string>
+
+    <!-- The full spelled out version of the month. -->
+    <string name="month_long_may">May</string>
+
+    <!-- The full spelled out version of the month. -->
+    <string name="month_long_june">June</string>
+
+    <!-- The full spelled out version of the month. -->
+    <string name="month_long_july">July</string>
+
+    <!-- The full spelled out version of the month. -->
+    <string name="month_long_august">August</string>
+
+    <!-- The full spelled out version of the month. -->
+    <string name="month_long_september">September</string>
+
+    <!-- The full spelled out version of the month. -->
+    <string name="month_long_october">October</string>
+
+    <!-- The full spelled out version of the month. -->
+    <string name="month_long_november">November</string>
+
+    <!-- The full spelled out version of the month. -->
+    <string name="month_long_december">December</string>
+
+
+    <!-- An abbreviated month name.
+        In US English: "Jan" stands for January. -->
+    <string name="month_medium_january">Jan</string>
+
+    <!-- An abbreviated month name.
+        In US English: "Feb" stands for February. -->
+    <string name="month_medium_february">Feb</string>
+
+    <!-- An abbreviated month name.
+        In US English: "Mar" stands for March. -->
+    <string name="month_medium_march">Mar</string>
+
+    <!-- An abbreviated month name.
+        In US English: "Apr" stands for April. -->
+    <string name="month_medium_april">Apr</string>
+
+    <!-- An abbreviated month name.
+        In US English: "May" stands for May. -->
+    <string name="month_medium_may">May</string>
+
+    <!-- An abbreviated month name.
+        In US English: "Jun" stands for June. -->
+    <string name="month_medium_june">Jun</string>
+
+    <!-- An abbreviated month name.
+        In US English: "Jul" stands for July. -->
+    <string name="month_medium_july">Jul</string>
+
+    <!-- An abbreviated month name.
+        In US English: "Aug" stands for August. -->
+    <string name="month_medium_august">Aug</string>
+
+    <!-- An abbreviated month name.
+        In US English: "Sep" stands for September. -->
+    <string name="month_medium_september">Sep</string>
+
+    <!-- An abbreviated month name.
+        In US English: "Oct" stands for October. -->
+    <string name="month_medium_october">Oct</string>
+
+    <!-- An abbreviated month name.
+        In US English: "Nov" stands for November. -->
+    <string name="month_medium_november">Nov</string>
+
+    <!-- An abbreviated month name.
+        In US English: "Dec" stands for December. -->
+    <string name="month_medium_december">Dec</string>
+
+
+    <!-- An abbreviated month name.  One character long if it makes sense.  Does not have
+         to be unique.
+         In US English: "J" stands for January -->
+    <string name="month_shortest_january">J</string>
+
+    <!-- An abbreviated month name.  One character long if it makes sense.  Does not have
+         to be unique.
+        In US English: "F" stands for February. -->
+    <string name="month_shortest_february">F</string>
+
+    <!-- An abbreviated month name.  One character long if it makes sense.  Does not have
+         to be unique.
+         In US English: "M" stands for March. -->
+    <string name="month_shortest_march">M</string>
+
+    <!-- An abbreviated month name.  One character long if it makes sense.  Does not have
+         to be unique.
+         In US English: "A" stands for April. -->
+    <string name="month_shortest_april">A</string>
+
+    <!-- An abbreviated month name.  One character long if it makes sense.  Does not have
+         to be unique.
+         In US English: "M" stands for May. -->
+    <string name="month_shortest_may">M</string>
+
+    <!-- An abbreviated month name.  One character long if it makes sense.  Does not have
+         to be unique.
+         In US English: "J" stands for June. -->
+    <string name="month_shortest_june">J</string>
+
+    <!-- An abbreviated month name.  One character long if it makes sense.  Does not have
+         to be unique.
+         In US English: "J" stands for July. -->
+    <string name="month_shortest_july">J</string>
+
+    <!-- An abbreviated month name.  One character long if it makes sense.  Does not have
+         to be unique.
+         In US English: "A" stands for August. -->
+    <string name="month_shortest_august">A</string>
+
+    <!-- An abbreviated month name.  One character long if it makes sense.  Does not have
+         to be unique.
+         In US English: "S" stands for September. -->
+    <string name="month_shortest_september">S</string>
+
+    <!-- An abbreviated month name.  One character long if it makes sense.  Does not have
+         to be unique.
+         In US English: "O" stands for October. -->
+    <string name="month_shortest_october">O</string>
+
+    <!-- An abbreviated month name.  One character long if it makes sense.  Does not have
+         to be unique.
+         In US English: "N" stands for November. -->
+    <string name="month_shortest_november">N</string>
+
+    <!-- An abbreviated month name.  One character long if it makes sense.  Does not have
+         to be unique.
+         In US English: "D" stands for December. -->
+    <string name="month_shortest_december">D</string>
+
+    <!-- Format string for durations like "01:23" (1 minute, 23 seconds) -->
+    <string name="elapsed_time_short_format_mm_ss"><xliff:g id="minutes" example="1">%1$02d</xliff:g>:<xliff:g id="seconds" example="23">%2$02d</xliff:g></string>
+
+    <!-- Format string for times like "1:43:33" (1 hour, 43 minutes, 33 seconds) -->
+    <string name="elapsed_time_short_format_h_mm_ss"><xliff:g id="hours" example="1">%1$d</xliff:g>:<xliff:g id="minutes" example="43">%2$02d</xliff:g>:<xliff:g id="seconds" example="33">%3$02d</xliff:g></string>
+
+    <!-- Item on EditText context menu. This action is used to select all text in the edit field. -->
+    <string name="selectAll">Select all</string>
+
+    <!-- Item on EditText context menu. This action is used to start selecting text in the edit field. -->
+    <string name="selectText">Select text</string>
+
+    <!-- Item on EditText context menu. This action is used to start selecting text in the edit field. -->
+    <string name="stopSelectingText">Stop selecting text</string>
+
+    <!-- Item on EditText context menu.  This action is used to cut selected the text into the clipboard.  -->
+    <string name="cut">Cut</string>
+
+    <!-- Item on EditText context menu. This action is used to cut all the text into the clipboard. -->
+    <string name="cutAll">Cut all</string>
+
+    <!-- Item on EditText context menu. This action is used to cut selected the text into the clipboard. -->
+    <string name="copy">Copy</string>
+
+    <!-- Item on EditText context menu. This action is used to copy all the text into the clipboard. -->
+    <string name="copyAll">Copy all</string>
+
+    <!-- Item on EditText context menu. This action is used t o paste from the clipboard into the eidt field -->
+    <string name="paste">Paste</string>
+
+    <!-- Item on EditText context menu. This action is used to copy a URL from the edit field into the clipboard. -->
+    <string name="copyUrl">Copy URL</string>
+
+    <!-- EditText context menu -->
+    <string name="inputMethod">Input Method</string>
+
+    <!-- Item on EditText context menu, used to add a word to the
+         input method dictionary. -->
+    <string name="addToDictionary">"Add \"%s\" to dictionary</string>
+
+    <!-- Title for EditText context menu -->
+    <string name="editTextMenuTitle">Edit text</string>
+
+    <!-- If the device is getting low on internal storage, a notification is shown to the user.  This is the title of that notification. -->
+    <string name="low_internal_storage_view_title">Low on space</string>
+    <!-- If the device is getting low on internal storage, a notification is shown to the user.  This is the message of that notification. -->
+    <string name="low_internal_storage_view_text">Phone storage space is getting low.</string>
+
+    <!-- Preference framework strings. -->
+    <string name="ok">OK</string>
+    <!-- Preference framework strings. -->
+    <string name="cancel">Cancel</string>
+    <!-- Preference framework strings. -->
+    <string name="yes">OK</string>
+    <!-- Preference framework strings. -->
+    <string name="no">Cancel</string>
+    <!-- This is the generic "attention" string to be used in attention dialogs.  Typically
+         combined with setIcon(android.R.drawable.ic_dialog_alert) -->
+    <string name="dialog_alert_title">Attention</string>
+
+    <!-- Default text for a button that can be toggled on and off. -->
+    <string name="capital_on">ON</string>
+    <!-- Default text for a button that can be toggled on and off. -->
+    <string name="capital_off">OFF</string>
+
+    <!-- Title of intent resolver dialog when selecting an application to run. -->
+    <string name="whichApplication">Complete action using</string>
+    <!-- Option to always use the selected application resolution in the future. See the "Complete action using" dialog title-->
+    <string name="alwaysUse">Use by default for this action.</string>
+    <!-- Text displayed when the user selects the check box for setting default application.  See the "Use by default for this action" check box. -->
+    <string name="clearDefaultHintMsg">Clear default in Home Settings &gt; Applications &gt; Manage applications.</string>
+    <!-- Default title for the activity chooser, when one is not given. Android allows multiple activities to perform an action.  for example, there may be many ringtone pickers installed.  A dialog is shown to the user allowing him to pick which activity should be used.  This is the title. -->
+    <string name="chooseActivity">Select an action</string>
+    <!-- Text to display when there are no activities found to display in the
+         activity chooser. See the "Select an action" title. -->
+    <string name="noApplications">No applications can perform this action.</string>
+    <!-- Title of the alert when an application has crashed. -->
+    <string name="aerr_title">Sorry!</string>
+    <!-- Text of the alert that is displayed when an application is not responding. -->
+    <string name="aerr_application">The application <xliff:g id="application">%1$s</xliff:g>
+        (process <xliff:g id="process">%2$s</xliff:g>) has stopped unexpectedly. Please try again.</string>
+    <!-- Text of the alert that is displayed when an application has crashed. -->
+    <string name="aerr_process">The process <xliff:g id="process">%1$s</xliff:g> has
+        stopped unexpectedly. Please try again.</string>
+    <!-- Title of the alert when an application is not responding. -->
+    <string name="anr_title">Sorry!</string>
+    <!-- Text of the alert that is displayed when an application is not responding. -->
+    <string name="anr_activity_application">Activity <xliff:g id="activity">%1$s</xliff:g> (in application <xliff:g id="application">%2$s</xliff:g>) is not responding.</string>
+    <!-- Text of the alert that is displayed when an application is not responding. -->
+    <string name="anr_activity_process">Activity <xliff:g id="activity">%1$s</xliff:g> (in process <xliff:g id="process">%2$s</xliff:g>) is not responding.</string>
+    <!-- Text of the alert that is displayed when an application is not responding. -->
+    <string name="anr_application_process">Application <xliff:g id="application">%1$s</xliff:g> (in process <xliff:g id="process">%2$s</xliff:g>) is not responding.</string>
+    <!-- Text of the alert that is displayed when an application is not responding. -->
+    <string name="anr_process">Process <xliff:g id="process">%1$s</xliff:g> is not responding.</string>
+    <!-- Button allowing the user to close an application that is not responding. This will kill the application. -->
+    <string name="force_close">Force close</string>
+    <!-- Button allowing the user to choose to wait for an application that is not responding to become responsive again. -->
+    <string name="wait">Wait</string>
+    <!-- Button allowing a developer to connect a debugger to an application that is not responding. -->
+    <string name="debug">Debug</string>
+
+    <!-- Displayed in the title of the chooser for things to do with text that
+         is to be sent to another application. For example, I can send text through SMS or IM.  A dialog with those choices would be shown, and this would be the title. -->
+    <string name="sendText">Select an action for text</string>
+
+    <!-- Title of the dialog where the user is adjusting the phone ringer volume -->
+    <string name="volume_ringtone">Ringer volume</string>
+    <!-- Title of the dialog where the user is adjusting the music volume -->
+    <string name="volume_music">Media volume</string>
+    <!-- Hint shown in the volume toast to inform the user that the media audio is playing through Bluetooth. -->
+    <string name="volume_music_hint_playing_through_bluetooth">Playing through Bluetooth</string>
+    <!-- Title of the dialog where the user is adjusting the phone call volume -->
+    <string name="volume_call">In-call volume</string>
+    <!-- Title of the dialog where the user is adjusting the phone call volume when connected on bluetooth-->
+    <string name="volume_bluetooth_call">Bluetooth in-call volume</string>
+    <!-- Title of the dialog where the user is adjusting the audio volume for alarms -->
+    <string name="volume_alarm">Alarm volume</string>
+    <!-- Title of the dialog where the user is adjusting the audio volume for notifications -->
+    <string name="volume_notification">Notification volume</string>
+    <!-- Title of the dialog where the user is adjusting the general audio volume -->
+    <string name="volume_unknown">Volume</string>
+
+    <!-- Ringtone picker strings --> <skip />
+    <!-- Choice in the ringtone picker.  If chosen, the default ringtone will be used. -->
+    <string name="ringtone_default">Default ringtone</string>
+    <!-- Choice in the ringtone picker.  If chosen, the default ringtone will be used. This fills in the actual ringtone's title into the message. -->
+    <string name="ringtone_default_with_actual">Default ringtone (<xliff:g id="actual_ringtone">%1$s</xliff:g>)</string>
+    <!-- Choice in the ringtone picker.  If chosen, there will be silence instead of a ringtone played. -->
+    <string name="ringtone_silent">Silent</string>
+    <!-- The title of the ringtone picker dialog. -->
+    <string name="ringtone_picker_title">Ringtones</string>
+    <!-- If there is ever a ringtone set for some setting, but that ringtone can no longer be resolved, t his is shown instead.  For example, if the ringtone was on a SD card and it had been removed, this woudl be shown for ringtones on that SD card. -->
+    <string name="ringtone_unknown">Unknown ringtone</string>
+
+    <!-- A notification is shown when there are open wireless networks nearby.  This is the notification's title. -->
+    <plurals name="wifi_available">
+        <item quantity="one">Wi-Fi network available</item>
+        <item quantity="other">Wi-Fi networks available</item>
+    </plurals>
+    <!-- A notification is shown when there are open wireless networks nearby.  This is the notification's message. -->
+    <plurals name="wifi_available_detailed">
+        <item quantity="one">Open Wi-Fi network available</item>
+        <item quantity="other">Open Wi-Fi networks available</item>
+    </plurals>
+
+    <!-- Name of the dialog that lets the user choose an accented character to insert -->
+    <string name="select_character">Insert character</string>
+
+    <!-- SMS per-application rate control Dialog --> <skip />
+    <!-- See SMS_DIALOG.  This is shown if the current application's name cannot be figuerd out. -->
+    <string name="sms_control_default_app_name">Unknown application</string>
+    <!-- SMS_DIALOG: An SMS dialog is shown if an application tries to send too many SMSes.  This is the title of that dialog. -->
+    <string name="sms_control_title">Sending SMS messages</string>
+    <!-- See SMS_DIALOG.  This is the message shown in that dialog. -->
+    <string name="sms_control_message">A large number of SMS messages are being sent. Select \"OK\" to continue, or \"Cancel\" to stop sending.</string>
+    <!-- See SMS_DIALOG.  This is a button choice to allow sending the SMSes. -->
+    <string name="sms_control_yes">OK</string>
+    <!-- See SMS_DIALOG.  This is a button choice to disallow sending the SMSes.. -->
+    <string name="sms_control_no">Cancel</string>
+
+    <!-- Name of the button in the date/time picker to accept the date/time change -->
+    <string name="date_time_set">Set</string>
+
+    <!-- Security Permissions strings-->
+    <!-- The default permission group for any permissions that have not explicitly set a group. -->
+    <string name="default_permission_group">Default</string>
+    <!-- Do not translate. -->
+    <string name="permissions_format"><xliff:g id="perm_line1">%1$s</xliff:g>, <xliff:g id="perm_line2">%2$s</xliff:g></string>
+    <!-- Shown for an application when it doesn't require any permission grants. -->
+    <string name="no_permissions">No permissions required</string>
+    <!-- When installing an application, the less-dangerous permissions are hidden.  If the user showed those, this is the text to hide them again.  -->
+    <string name="perms_hide"><b>Hide</b></string>
+    <!-- When installing an application, the less-dangerous permissions are hidden.  This is the text to show those. -->
+    <string name="perms_show_all"><b>Show all</b></string>
+
+    <!-- Shown when there is content loading from the internet into a dialog. -->
+    <string name="googlewebcontenthelper_loading">Loading\u2026</string>
+
+    <!-- USB storage dialog strings -->
+    <!-- This is the label for the activity, and should never be visible to the user. -->
+    <!-- See USB_STORAGE.  USB_STORAGE_DIALOG:  After the user selects the notification, a dialog is shown asking if he wants to mount.  This is the title. -->
+    <string name="usb_storage_title">USB connected</string>
+    <!-- See USB_STORAGE.    This is the message. -->
+    <string name="usb_storage_message">You have connected your phone to your computer via USB. Select \"Mount\" if you want to copy files between your computer and your phone\'s SD card.</string>
+    <!-- See USB_STORAGE.    This is the button text to mount the phone on the computer. -->
+    <string name="usb_storage_button_mount">Mount</string>
+    <!-- See USB_STORAGE.   This is the button text to ignore the plugging in of the phone.. -->
+    <string name="usb_storage_button_unmount">Don\'t mount</string>
+    <!-- See USB_STORAGE_DIALOG.  If there was an error mounting, this is the text. --> 
+    <string name="usb_storage_error_message">There is a problem using your SD card for USB storage.</string>
+    <!-- USB_STORAGE: When the user connects the phone to a computer via USB, we show a notification asking if he wants to share files across.  This is the title -->
+    <string name="usb_storage_notification_title">USB connected</string>
+    <!-- See USB_STORAGE. This is the message. -->
+    <string name="usb_storage_notification_message">Select to copy files to/from your computer.</string>
+    <!-- USB_STORAGE_STOP: While USB storage is enabled, we show a notification dialog asking if he wants to stop. This is the title -->
+    <string name="usb_storage_stop_notification_title">Turn off USB storage</string>
+    <!-- See USB_STORAGE. This is the message. -->
+    <string name="usb_storage_stop_notification_message">Select to turn off USB storage.</string>
+
+    <!-- USB storage stop dialog strings -->
+    <!-- This is the label for the activity, and should never be visible to the user. -->
+    <!-- See USB_STORAGE_STOP.  USB_STORAGE_STOP_DIALOG:  After the user selects the notification, a dialog is shown asking if he wants to stop usb storage.  This is the title. -->
+    <string name="usb_storage_stop_title">Turn off USB storage</string>
+    <!-- See USB_STORAGE_STOP.    This is the message. -->
+    <string name="usb_storage_stop_message">Before turning off USB storage, make sure you have unmounted on the USB host. Select \"Turn Off\" to turn off USB storage.</string>
+    <!-- See USB_STORAGE_STOP.    This is the button text to stop usb storage. -->
+    <string name="usb_storage_stop_button_mount">Turn Off</string>
+    <!-- See USB_STORAGE_STOP.   This is the button text to cancel stoping usb storage. -->
+    <string name="usb_storage_stop_button_unmount">Cancel</string>
+    <!-- See USB_STORAGE_STOP_DIALOG.  If there was an error stopping, this is the text. --> 
+    <string name="usb_storage_stop_error_message">We've encountered a problem turning off USB storage. Check to make sure you have unmounted the USB host, then try again.</string>
+
+    <!-- External media format dialog strings -->
+    <!-- This is the label for the activity, and should never be visible to the user. -->
+    <!-- See EXTMEDIA_FORMAT.  EXTMEDIA_FORMAT_DIALOG:  After the user selects the notification, a dialog is shown asking if he wants to format the SD card.  This is the title. -->
+    <string name="extmedia_format_title">Format SD card</string>
+    <!-- See EXTMEDIA_FORMAT.   This is the message. -->
+    <string name="extmedia_format_message">Are you sure you want to format the SD card? All data on your card will be lost.</string>
+    <!-- See EXTMEDIA_FORMAT.    This is the button text to format the sd card. -->
+    <string name="extmedia_format_button_format">Format</string>
+
+    <!-- Used to replace %s in urls retreived from the signin server with locales.  For Some        -->
+    <!-- devices we don't support all the locales we ship to and need to replace the '%s' with a    -->
+    <!-- locale string based on mcc values.  By default (0-length string) we don't replace the %s   -->
+    <!-- at all and later replace it with a locale string based on the users chosen locale          -->
+    <!-- DO NOT TRANSLATE -->
+    <string name="locale_replacement">""</string>
+
+    <!-- Title of the pop-up dialog in which the user switches input method components. -->
+    <string name="select_input_method">Select Input Method</string>
+    
+    <string name="fast_scroll_alphabet">\u0020ABCDEFGHIJKLMNOPQRSTUVWXYZ</string>
+    <string name="fast_scroll_numeric_alphabet">\u00200123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ</string>
+    
+    <string name="candidates_style"><u>candidates</u></string>
+
+    <!-- External media notification strings -->
+    <!-- Shown when external media is being checked -->
+    <string name="ext_media_checking_notification_title">Preparing SD card</string>
+    <string name="ext_media_checking_notification_message">Checking for errors</string>
+
+    <!-- Shown when external media is blank (or unsupported filesystem) -->
+    <string name="ext_media_nofs_notification_title">Blank SD card</string>
+    <string name="ext_media_nofs_notification_message">The SD card is blank or using an unsupported filesystem.</string>
+
+    <!-- Shown when external media is unmountable (corrupt)) -->
+    <string name="ext_media_unmountable_notification_title">Damaged SD card</string>
+    <string name="ext_media_unmountable_notification_message">The SD card is damaged. You may have to reformat your card.</string>
+
+    <!-- Shown when external media is unsafely removed -->
+    <string name="ext_media_badremoval_notification_title">SD card unexpectedly removed</string>
+    <string name="ext_media_badremoval_notification_message">Unmount SD card before removing to avoid data loss.</string>
+
+    <!-- Shown when external media has been safely removed -->
+    <string name="ext_media_safe_unmount_notification_title">SD card safe to remove</string>
+    <string name="ext_media_safe_unmount_notification_message">The SD card can now be safely removed.</string>
+
+    <!-- Shown when external media is missing -->
+    <string name="ext_media_nomedia_notification_title">Removed SD card</string>
+    <string name="ext_media_nomedia_notification_message">The SD has been removed. Insert a new SD card to increase your device storage.</string>
+
+    <!-- Shown in LauncherActivity when the requested target Intent didn't return any matching Activities, leaving the list empty. -->
+    <string name="activity_list_empty">No matching activities found</string>
+
+    <!-- permission attributes related to package usage statistics -->
+    <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permlab_pkgUsageStats">update component usage statistics</string>
+    <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permdesc_pkgUsageStats">Allows the modification of collected component usage statistics. Not for use by normal applications.</string>
+
+    <!-- Shown in the tutorial for tap twice for zoom control. -->
+    <string name="tutorial_double_tap_to_zoom_message_short">Tap twice for zoom control</string>
+
+    <!-- Shown in gadget hosts (e.g. the home screen) when there was an error inflating
+    the gadget. -->
+    <string name="gadget_host_error_inflating">Error inflating widget</string>
+
+    <!-- Long label for a button on a full-screen input method for the "Go" action. -->
+    <string name="ime_action_go">Go</string>
+    
+    <!-- Long label for a button on a full-screen input method for the "Search" action. -->
+    <string name="ime_action_search">Search</string>
+    
+    <!-- Long label for a button on a full-screen input method for the "Send" action. -->
+    <string name="ime_action_send">Send</string>
+    
+    <!-- Long label for a button on a full-screen input method for the "Next" action. -->
+    <string name="ime_action_next">Next</string>
+    
+    <!-- Long label for a button on a full-screen input method for an unknown action. -->
+    <string name="ime_action_default">Execute</string>
+    
+</resources>
diff --git a/core/res/res/values/styles.xml b/core/res/res/values/styles.xml
new file mode 100644
index 0000000..54eba62
--- /dev/null
+++ b/core/res/res/values/styles.xml
@@ -0,0 +1,675 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<resources>
+    <!-- Global Theme Styles -->
+    <eat-comment />
+    
+
+    <style name="WindowTitleBackground">
+        <item name="android:background">@android:drawable/title_bar</item>
+    </style>
+
+    <style name="WindowTitle">
+        <item name="android:singleLine">true</item>
+        <item name="android:textAppearance">@style/TextAppearance.WindowTitle</item>
+        <item name="android:shadowColor">#BB000000</item>
+        <item name="android:shadowRadius">2.75</item>
+    </style>
+
+    <style name="DialogWindowTitle">
+        <item name="android:maxLines">1</item>
+        <item name="android:scrollHorizontally">true</item>
+        <item name="android:textAppearance">@style/TextAppearance.DialogWindowTitle</item>
+    </style>
+
+    <style name="AlertDialog">
+        <item name="fullDark">@android:drawable/popup_full_dark</item>
+        <item name="topDark">@android:drawable/popup_top_dark</item>
+        <item name="centerDark">@android:drawable/popup_center_dark</item>
+        <item name="bottomDark">@android:drawable/popup_bottom_dark</item>
+        <item name="fullBright">@android:drawable/popup_full_bright</item>
+        <item name="topBright">@android:drawable/popup_top_bright</item>
+        <item name="centerBright">@android:drawable/popup_center_bright</item>
+        <item name="bottomBright">@android:drawable/popup_bottom_bright</item>
+        <item name="bottomMedium">@android:drawable/popup_bottom_medium</item>
+        <item name="centerMedium">@android:drawable/popup_center_medium</item>
+    </style>
+    
+    <!-- Animations -->
+    <style name="Animation" />
+    
+    <!-- Standard animations for a full-screen window or activity. -->
+    <style name="Animation.Activity">
+        <item name="activityOpenEnterAnimation">@anim/task_open_enter</item>
+        <item name="activityOpenExitAnimation">@anim/task_open_exit</item>
+        <item name="activityCloseEnterAnimation">@anim/task_close_enter</item>
+        <item name="activityCloseExitAnimation">@anim/task_close_exit</item>
+        <item name="taskOpenEnterAnimation">@anim/task_open_enter</item>
+        <item name="taskOpenExitAnimation">@anim/task_open_exit</item>
+        <item name="taskCloseEnterAnimation">@anim/task_close_enter</item>
+        <item name="taskCloseExitAnimation">@anim/task_close_exit</item>
+        <item name="taskToFrontEnterAnimation">@anim/task_open_enter</item>
+        <item name="taskToFrontExitAnimation">@anim/task_open_exit</item>
+        <item name="taskToBackEnterAnimation">@anim/task_close_enter</item>
+        <item name="taskToBackExitAnimation">@anim/task_close_exit</item>
+    </style>
+
+    <!-- Standard animations for a non-full-screen window or activity. -->
+    <style name="Animation.Dialog">
+        <item name="windowEnterAnimation">@anim/dialog_enter</item>
+        <item name="windowExitAnimation">@anim/dialog_exit</item>
+    </style>
+
+    <!-- Standard animations for hiding and showing the status bar. -->
+    <style name="Animation.StatusBar">
+        <item name="windowEnterAnimation">@anim/status_bar_enter</item>
+        <item name="windowExitAnimation">@anim/status_bar_exit</item>
+    </style>
+
+    <!-- Standard animations for a translucent window or activity. -->
+    <style name="Animation.Translucent">
+    </style>
+
+    <!-- Standard animations for a non-full-screen window or activity. -->
+    <style name="Animation.LockScreen">
+        <item name="windowExitAnimation">@anim/lock_screen_exit</item>
+    </style>
+
+    <style name="Animation.OptionsPanel">
+        <item name="windowEnterAnimation">@anim/options_panel_enter</item>
+        <item name="windowExitAnimation">@anim/options_panel_exit</item>
+    </style>
+
+    <style name="Animation.SubMenuPanel">
+        <item name="windowEnterAnimation">@anim/submenu_enter</item>
+        <item name="windowExitAnimation">@anim/submenu_exit</item>
+    </style>
+
+    <style name="Animation.TypingFilter">
+        <item name="windowEnterAnimation">@anim/grow_fade_in_center</item>
+        <item name="windowExitAnimation">@anim/shrink_fade_out_center</item>
+    </style>
+    
+    <style name="Animation.TypingFilterRestore">
+        <item name="windowEnterAnimation">@null</item>
+        <item name="windowExitAnimation">@anim/shrink_fade_out_center</item>
+    </style>
+
+    <style name="Animation.Toast">
+        <item name="windowEnterAnimation">@anim/toast_enter</item>
+        <item name="windowExitAnimation">@anim/toast_exit</item>
+    </style>
+
+    <style name="Animation.DropDownDown">
+        <item name="windowEnterAnimation">@anim/grow_fade_in</item>
+        <item name="windowExitAnimation">@anim/shrink_fade_out</item>
+    </style>
+
+    <style name="Animation.DropDownUp">
+        <item name="windowEnterAnimation">@anim/grow_fade_in_from_bottom</item>
+        <item name="windowExitAnimation">@anim/shrink_fade_out_from_bottom</item>
+    </style>
+
+    <!-- Window animations that are applied to input method overlay windows.
+         {@hide Pending API council approval} -->
+    <style name="Animation.InputMethod">
+        <item name="windowEnterAnimation">@anim/input_method_enter</item>
+        <item name="windowExitAnimation">@anim/input_method_exit</item>
+    </style>
+
+    <!-- Special optional fancy IM animations. @hide -->
+    <style name="Animation.InputMethodFancy">
+        <item name="windowEnterAnimation">@anim/input_method_fancy_enter</item>
+        <item name="windowExitAnimation">@anim/input_method_fancy_exit</item>
+    </style>
+
+    <!-- Window animations that are applied to the search bar overlay window.
+         {@hide Pending API council approval} -->
+    <style name="Animation.SearchBar">
+        <item name="windowEnterAnimation">@anim/search_bar_enter</item>
+        <item name="windowExitAnimation">@anim/search_bar_exit</item>
+    </style>
+
+    <!-- Status Bar Styles -->
+
+    <style name="TextAppearance.StatusBarTitle">
+        <item name="android:textSize">14sp</item>
+        <item name="android:textStyle">bold</item>
+        <item name="android:textColor">#ffffffff</item>
+    </style>
+
+
+    <!-- Widget Styles -->
+
+    <style name="Widget">
+        <item name="android:textAppearance">?textAppearance</item>
+    </style>
+
+    <style name="Widget.AbsListView">
+        <item name="android:scrollbars">vertical</item>
+        <item name="android:fadingEdge">vertical</item>
+    </style>
+
+    <style name="Widget.Button">
+        <item name="android:background">@android:drawable/btn_default</item>
+        <item name="android:focusable">true</item>
+        <item name="android:clickable">true</item>
+        <item name="android:textAppearance">?android:attr/textAppearanceSmallInverse</item>
+        <item name="android:textColor">@android:color/primary_text_light_nodisable</item>
+        <item name="android:gravity">center_vertical|center_horizontal</item>
+    </style>
+
+    <style name="Widget.Button.Small">
+        <item name="android:background">@android:drawable/btn_default_small</item>
+    </style>
+
+    <style name="Widget.Button.Inset">
+        <item name="android:background">@android:drawable/button_inset</item>
+    </style>
+
+    <style name="Widget.CompoundButton">
+        <item name="android:focusable">true</item>
+        <item name="android:clickable">true</item>
+        <item name="android:textAppearance">?android:attr/textAppearance</item>
+        <item name="android:textColor">?android:attr/textColorPrimaryDisableOnly</item>
+        <item name="android:gravity">center_vertical|left</item>
+    </style>
+
+    <style name="Widget.CompoundButton.CheckBox">
+        <item name="android:background">@android:drawable/btn_check_label_background</item>
+        <item name="android:button">@android:drawable/btn_check</item>
+    </style>
+
+    <style name="Widget.CompoundButton.RadioButton">
+        <item name="android:background">@android:drawable/btn_radio_label_background</item>
+        <item name="android:button">@android:drawable/btn_radio</item>
+    </style>
+
+    <style name="Widget.CompoundButton.Star">
+        <item name="android:background">@android:drawable/btn_star_label_background</item>
+        <item name="android:button">@android:drawable/btn_star</item>
+    </style>
+
+    <style name="Widget.CompoundButton.StarButtonless">
+        <item name="android:background">@android:drawable/btn_star_label_background</item>
+        <item name="android:button">@android:drawable/btn_star_buttonless</item>
+    </style>
+
+    <style name="Widget.Button.Toggle">
+        <item name="android:background">@android:drawable/btn_toggle_bg</item>
+        <item name="android:textOn">@android:string/capital_on</item>
+        <item name="android:textOff">@android:string/capital_off</item>
+        <item name="android:disabledAlpha">?android:attr/disabledAlpha</item>
+    </style>
+
+    <style name="Widget.ProgressBar">
+        <item name="android:indeterminateOnly">true</item>
+        <item name="android:indeterminateDrawable">@android:drawable/progress_medium</item>
+        <item name="android:indeterminateBehavior">repeat</item>
+        <item name="android:indeterminateDuration">3500</item>
+        <item name="android:minWidth">48dip</item>
+        <item name="android:maxWidth">48dip</item>
+        <item name="android:minHeight">48dip</item>
+        <item name="android:maxHeight">48dip</item>
+    </style>
+
+    <style name="Widget.ProgressBar.Large">
+        <item name="android:indeterminateDrawable">@android:drawable/progress_large</item>
+        <item name="android:minWidth">76dip</item>
+        <item name="android:maxWidth">76dip</item>
+        <item name="android:minHeight">76dip</item>
+        <item name="android:maxHeight">76dip</item>
+    </style>
+    
+    <style name="Widget.ProgressBar.Small">
+        <item name="android:indeterminateDrawable">@android:drawable/progress_small</item>
+        <item name="android:minWidth">16dip</item>
+        <item name="android:maxWidth">16dip</item>
+        <item name="android:minHeight">16dip</item>
+        <item name="android:maxHeight">16dip</item>
+    </style>
+
+    <style name="Widget.ProgressBar.Small.Title">
+        <item name="android:indeterminateDrawable">@android:drawable/progress_small_titlebar</item>
+    </style>
+
+    <style name="Widget.ProgressBar.Horizontal">
+        <item name="android:indeterminateOnly">false</item>
+        <item name="android:progressDrawable">@android:drawable/progress_horizontal</item>
+        <item name="android:indeterminateDrawable">@android:drawable/progress_indeterminate_horizontal</item>
+        <item name="android:minHeight">20dip</item>
+        <item name="android:maxHeight">20dip</item>
+    </style>
+
+    <style name="Widget.SeekBar">
+        <item name="android:indeterminateOnly">false</item>
+        <item name="android:progressDrawable">@android:drawable/progress_horizontal</item>
+        <item name="android:indeterminateDrawable">@android:drawable/progress_horizontal</item>
+        <item name="android:minHeight">20dip</item>
+        <item name="android:maxHeight">20dip</item>
+        <item name="android:thumb">@android:drawable/seek_thumb</item>
+        <item name="android:thumbOffset">8px</item>
+        <item name="android:focusable">true</item>
+    </style>
+
+    <style name="Widget.RatingBar">
+        <item name="android:indeterminateOnly">false</item>
+        <item name="android:progressDrawable">@android:drawable/ratingbar_full</item>
+        <item name="android:indeterminateDrawable">@android:drawable/ratingbar_full</item>
+        <item name="android:minHeight">57dip</item>
+        <item name="android:maxHeight">57dip</item>
+        <item name="android:thumb">@null</item>
+    </style>
+
+    <style name="Widget.RatingBar.Indicator">
+        <item name="android:indeterminateOnly">false</item>
+        <item name="android:progressDrawable">@android:drawable/ratingbar</item>
+        <item name="android:indeterminateDrawable">@android:drawable/ratingbar</item>
+        <item name="android:minHeight">38dip</item>
+        <item name="android:maxHeight">38dip</item>
+        <item name="android:thumb">@null</item>
+        <item name="android:isIndicator">true</item>
+    </style>
+
+    <style name="Widget.RatingBar.Small">
+        <item name="android:indeterminateOnly">false</item>
+        <item name="android:progressDrawable">@android:drawable/ratingbar_small</item>
+        <item name="android:indeterminateDrawable">@android:drawable/ratingbar_small</item>
+        <item name="android:minHeight">14dip</item>
+        <item name="android:maxHeight">14dip</item>
+        <item name="android:thumb">@null</item>
+        <item name="android:isIndicator">true</item>
+    </style>
+
+    <style name="Widget.TextView">
+        <item name="android:textAppearance">?android:attr/textAppearanceSmall</item>
+    </style>
+    
+    <style name="Widget.TextView.ListSeparator">
+        <item name="android:background">@android:drawable/dark_header</item>
+        <item name="android:layout_width">fill_parent</item>
+        <item name="android:layout_height">25dip</item>
+        <item name="android:textStyle">bold</item>
+        <item name="android:textColor">?textColorSecondary</item>
+        <item name="android:textSize">14sp</item>
+        <item name="android:gravity">center_vertical</item>
+        <item name="android:paddingLeft">5sp</item>
+    </style>
+
+    <style name="Widget.EditText">
+        <item name="android:focusable">true</item>
+        <item name="android:focusableInTouchMode">true</item>
+        <item name="android:clickable">true</item>
+        <item name="android:background">@android:drawable/edit_text</item>
+        <item name="android:textAppearance">?android:attr/textAppearanceMediumInverse</item>
+        <item name="android:textColor">@android:color/primary_text_light</item>
+        <item name="android:gravity">center_vertical</item>
+    </style>
+    
+    <style name="Widget.ExpandableListView" parent="Widget.ListView">
+        <item name="android:groupIndicator">@android:drawable/expander_group</item>
+        <item name="android:indicatorLeft">?android:attr/expandableListPreferredItemIndicatorLeft</item>
+        <item name="android:indicatorRight">?android:attr/expandableListPreferredItemIndicatorRight</item>
+        <item name="android:childDivider">@android:drawable/divider_horizontal_dark</item>
+    </style>
+
+    <style name="Widget.ImageWell">
+        <item name="android:background">@android:drawable/panel_picture_frame_background</item>
+    </style>
+
+    <style name="Widget.ImageButton">
+        <item name="android:focusable">true</item>
+        <item name="android:clickable">true</item>
+        <item name="android:scaleType">center</item>
+        <item name="android:background">@android:drawable/btn_default</item>
+    </style>
+
+    <style name="Widget.AutoCompleteTextView">
+        <item name="android:focusable">true</item>
+        <item name="android:focusableInTouchMode">true</item>
+        <item name="android:clickable">true</item>
+        <item name="android:background">@android:drawable/edit_text</item>
+        <item name="android:completionHintView">@android:layout/simple_dropdown_hint</item>
+        <item name="android:textAppearance">?android:attr/textAppearanceMediumInverse</item>
+        <item name="android:gravity">center_vertical</item>
+        <item name="android:completionThreshold">2</item>
+        <item name="android:dropDownSelector">@android:drawable/list_selector_background</item>
+        <item name="android:popupBackground">@android:drawable/spinner_dropdown_background</item>
+        <item name="android:dropDownVerticalOffset">-6px</item>
+        <item name="android:dropDownHorizontalOffset">0px</item>
+        <item name="android:dropDownWidth">wrap_content</item>
+    </style>
+
+    <style name="Widget.Spinner">
+        <item name="android:background">@android:drawable/btn_dropdown</item>
+        <item name="android:clickable">true</item>
+    </style>
+
+    <style name="Widget.TextView.PopupMenu">
+        <item name="android:clickable">true</item>
+        <item name="android:textAppearance">@style/TextAppearance.Widget.TextView.PopupMenu</item>
+    </style>
+
+    <style name="Widget.TextView.SpinnerItem">
+        <item name="android:textAppearance">@style/TextAppearance.Widget.TextView.SpinnerItem</item>
+    </style>
+
+    <style name="Widget.DropDownItem">
+        <item name="android:textAppearance">@style/TextAppearance.Widget.DropDownItem</item>
+        <item name="android:paddingLeft">6dip</item>
+        <item name="android:paddingRight">6dip</item>
+        <item name="android:gravity">center_vertical</item>
+    </style>
+    
+    <style name="Widget.DropDownItem.Spinner">
+        <item name="android:checkMark">@android:drawable/btn_radio</item>
+    </style>
+
+    <style name="Widget.ScrollView">
+        <item name="android:scrollbars">vertical</item>
+        <item name="android:fadingEdge">vertical</item>
+    </style>
+
+    <style name="Widget.HorizontalScrollView">
+        <item name="android:scrollbars">horizontal</item>
+        <item name="android:fadingEdge">horizontal</item>
+    </style>
+
+    <style name="Widget.ListView" parent="Widget.AbsListView">
+        <item name="android:listSelector">@android:drawable/list_selector_background</item>
+        <item name="android:cacheColorHint">?android:attr/colorBackground</item>
+        <item name="android:divider">@android:drawable/divider_horizontal_dark</item>
+    </style>
+    
+    <style name="Widget.ListView.White" parent="Widget.AbsListView">
+        <item name="android:listSelector">@android:drawable/list_selector_background</item>
+        <item name="android:background">@android:color/white</item>
+        <item name="android:divider">@android:drawable/divider_horizontal_bright</item>
+    </style>    
+
+    <style name="Widget.ListView.DropDown">
+    	<item name="android:cacheColorHint">@null</item>
+        <item name="android:divider">@android:drawable/divider_horizontal_bright</item>
+    </style>
+
+    <style name="Widget.ListView.Menu">
+		<item name="android:cacheColorHint">@null</item>
+        <item name="android:scrollbars">vertical</item>
+        <item name="android:fadingEdge">vertical</item>
+        <item name="listSelector">@android:drawable/menu_selector</item>
+        <!-- Light background for the list in menus, so the divider for bright themes -->
+        <item name="android:divider">@android:drawable/divider_horizontal_bright</item>
+    </style>
+
+    <style name="Widget.GridView" parent="Widget.AbsListView">
+        <item name="android:listSelector">@android:drawable/grid_selector_background</item>
+    </style>
+
+    <style name="Widget.WebView">
+        <item name="android:focusable">true</item>
+        <item name="android:scrollbars">horizontal|vertical</item>
+    </style>
+
+    <style name="Widget.TabWidget">
+        <item name="android:textAppearance">@style/TextAppearance.Widget.TabWidget</item>
+        <item name="ellipsize">marquee</item>
+        <item name="singleLine">true</item>
+    </style>
+
+    <style name="Widget.Gallery">
+        <item name="android:fadingEdge">horizontal</item>
+        <item name="android:gravity">center_vertical</item>
+        <item name="android:spacing">-20px</item>
+        <item name="android:unselectedAlpha">0.85</item>
+    </style>
+    
+    <style name="Widget.PopupWindow">
+        <item name="android:popupBackground">@android:drawable/editbox_dropdown_background_dark</item>
+    </style>
+
+    <style name="Widget.KeyboardView" parent="android:Widget">
+        <item name="android:background">@android:drawable/keyboard_background</item>
+        <item name="android:keyBackground">@android:drawable/btn_keyboard_key</item>
+        <item name="android:keyTextSize">22sp</item>
+        <item name="android:keyTextColor">#FFFFFFFF</item>
+        <item name="android:keyPreviewLayout">@android:layout/keyboard_key_preview</item>
+        <item name="android:keyPreviewOffset">-12dp</item>
+        <item name="android:keyPreviewHeight">80dp</item>
+        <item name="android:labelTextSize">14sp</item>
+        <item name="android:popupLayout">@android:layout/keyboard_popup_keyboard</item>
+        <item name="android:verticalCorrection">-10dip</item>
+        <item name="android:shadowColor">#BB000000</item>
+        <item name="android:shadowRadius">2.75</item>
+    </style>
+
+    <!-- Text Appearances -->
+    <eat-comment />
+
+    <style name="TextAppearance">
+        <item name="android:textColor">?textColorPrimary</item>
+        <item name="android:textColorHighlight">#FFFF9200</item>
+        <item name="android:textColorHint">?textColorHint</item>
+        <item name="android:textColorLink">#5C5CFF</item>
+        <item name="android:textSize">16sp</item>
+        <item name="android:textStyle">normal</item>
+    </style>
+
+    <style name="TextAppearance.Inverse">
+        <item name="textColor">?textColorPrimaryInverse</item>
+        <item name="android:textColorHint">?textColorHintInverse</item>
+        <item name="android:textColorLink">#0000EE</item>
+    </style>
+
+    <style name="TextAppearance.Theme">
+    </style>
+
+    <style name="TextAppearance.DialogWindowTitle">
+        <item name="android:textSize">18sp</item>
+        <item name="android:textStyle">normal</item>
+        <item name="android:textColor">?textColorPrimary</item>
+    </style>
+
+    <style name="TextAppearance.Large">
+        <item name="android:textSize">22sp</item>
+        <item name="android:textStyle">normal</item>
+        <item name="android:textColor">?textColorPrimary</item>
+    </style>
+
+    <style name="TextAppearance.Large.Inverse">
+        <item name="android:textColor">?textColorPrimaryInverse</item>
+        <item name="android:textColorHint">?textColorHintInverse</item>
+    </style>
+
+    <style name="TextAppearance.Medium">
+        <item name="android:textSize">18sp</item>
+        <item name="android:textStyle">normal</item>
+        <item name="android:textColor">?textColorPrimary</item>
+    </style>
+
+    <style name="TextAppearance.Medium.Inverse">
+        <item name="android:textColor">?textColorPrimaryInverse</item>
+        <item name="android:textColorHint">?textColorHintInverse</item>
+    </style>
+
+    <style name="TextAppearance.Small">
+        <item name="android:textSize">14sp</item>
+        <item name="android:textStyle">normal</item>
+        <item name="android:textColor">?textColorSecondary</item>
+    </style>
+
+    <style name="TextAppearance.Small.Inverse">
+        <item name="android:textColor">?textColorSecondaryInverse</item>
+        <item name="android:textColorHint">?textColorHintInverse</item>
+    </style>
+
+    <style name="TextAppearance.Theme.Dialog" parent="TextAppearance.Theme">
+    </style>
+
+    <style name="TextAppearance.Theme.Dialog.AppError">
+        <item name="android:textColor">#ffffc0c0</item>
+    </style>
+
+    <style name="TextAppearance.Widget">
+    </style>
+
+    <style name="TextAppearance.Widget.Button" parent="TextAppearance.Small.Inverse">
+        <item name="android:textColor">@android:color/primary_text_light_nodisable</item>
+    </style>
+
+    <style name="TextAppearance.Widget.IconMenu.Item" parent="TextAppearance.Small">
+        <item name="android:textColor">?textColorPrimaryInverse</item>
+    </style>
+
+    <style name="TextAppearance.Widget.EditText">
+        <item name="android:textColor">@color/widget_edittext_dark</item>
+        <item name="android:textColorHint">@android:color/hint_foreground_light</item>
+    </style>
+
+    <style name="TextAppearance.Widget.TabWidget">
+        <item name="android:textSize">14sp</item>
+        <item name="android:textStyle">normal</item>
+        <item name="android:textColor">@android:color/tab_indicator_text</item>
+    </style>
+    
+    <style name="TextAppearance.Widget.TextView">
+        <item name="android:textColor">?textColorPrimaryDisableOnly</item>
+        <item name="android:textColorHint">?textColorHint</item>
+    </style>
+
+    <style name="TextAppearance.Widget.TextView.PopupMenu">
+        <item name="android:textSize">18sp</item>
+        <item name="android:textColor">?textColorPrimaryDisableOnly</item>
+        <item name="android:textColorHint">?textColorHint</item>
+    </style>
+
+    <style name="TextAppearance.Widget.DropDownHint">
+        <item name="android:textColor">?textColorPrimaryInverse</item>
+        <item name="android:textSize">14sp</item>
+    </style>
+
+    <style name="TextAppearance.Widget.DropDownItem">
+        <item name="android:textColor">@android:color/primary_text_light_disable_only</item>
+    </style>
+
+    <style name="TextAppearance.Widget.TextView.SpinnerItem">
+        <item name="android:textColor">@android:color/primary_text_light_disable_only</item>
+    </style>
+
+    <style name="TextAppearance.WindowTitle">
+        <item name="android:textColor">#fff</item>
+        <item name="android:textSize">14sp</item>
+        <item name="android:textStyle">bold</item>
+    </style>
+
+    <!-- Preference Styles -->
+
+    <style name="Preference">
+        <item name="android:layout">@android:layout/preference</item>
+    </style>
+    
+    <style name="Preference.Information">
+        <item name="android:layout">@android:layout/preference_information</item>
+        <item name="android:enabled">false</item>
+        <item name="android:shouldDisableView">false</item>
+    </style>
+    
+    <style name="Preference.Category">
+        <item name="android:layout">@android:layout/preference_category</item>
+        <!-- The title should not dim if the category is disabled, instead only the preference children should dim. -->
+        <item name="android:shouldDisableView">false</item>
+        <item name="android:selectable">false</item>
+    </style>
+    
+    <style name="Preference.CheckBoxPreference">
+        <item name="android:widgetLayout">@android:layout/preference_widget_checkbox</item>
+    </style>
+    
+    <style name="Preference.PreferenceScreen">
+        <item name="android:widgetLayout">@android:layout/preferences</item>
+    </style>
+
+    <style name="Preference.DialogPreference">
+        <item name="android:positiveButtonText">@android:string/ok</item>
+        <item name="android:negativeButtonText">@android:string/cancel</item>
+    </style>
+    
+    <style name="Preference.DialogPreference.YesNoPreference">
+        <item name="android:positiveButtonText">@android:string/yes</item>
+        <item name="android:negativeButtonText">@android:string/no</item>
+    </style>
+    
+    <style name="Preference.DialogPreference.EditTextPreference">
+        <item name="android:dialogLayout">@android:layout/preference_dialog_edittext</item>
+    </style>
+    
+    <style name="Preference.RingtonePreference">
+        <item name="android:ringtoneType">ringtone</item>
+        <item name="android:showSilent">true</item>
+        <item name="android:showDefault">true</item>
+    </style>
+
+    <!-- Other Misc Styles -->
+    <eat-comment />
+
+    <style name="MediaButton">
+        <item name="android:background">@android:drawable/media_button_background</item>
+        <item name="android:layout_width">71px</item>
+        <item name="android:layout_height">52px</item>
+    </style>
+
+    <style name="MediaButton.Previous">
+        <item name="android:src">@android:drawable/ic_media_previous</item>
+    </style>
+
+    <style name="MediaButton.Next">
+        <item name="android:src">@android:drawable/ic_media_next</item>
+    </style>
+
+    <style name="MediaButton.Play">
+        <item name="android:src">@android:drawable/ic_media_play</item>
+    </style>
+
+    <style name="MediaButton.Ffwd">
+        <item name="android:src">@android:drawable/ic_media_ff</item>
+    </style>
+
+    <style name="MediaButton.Rew">
+        <item name="android:src">@android:drawable/ic_media_rew</item>
+    </style>
+
+    <style name="MediaButton.Pause">
+        <item name="android:src">@android:drawable/ic_media_pause</item>
+    </style>
+
+    <style name="ZoomControls">
+        <item name="android:background">@android:drawable/zoom_plate</item>
+        <item name="android:gravity">bottom</item>
+        <item name="android:paddingLeft">15dip</item>
+        <item name="android:paddingRight">15dip</item>
+    </style>
+    
+    <!-- Style you can use with a container (typically a horizontal
+         LinearLayout) to get the standard "button bar" background and
+         spacing. @hide -->
+    <style name="ButtonBar">
+        <item name="android:paddingTop">5dip</item>
+        <item name="android:paddingLeft">4dip</item>
+        <item name="android:paddingRight">4dip</item>
+        <item name="android:paddingBottom">1dip</item>
+        <item name="android:background">@android:drawable/bottom_bar</item>
+    </style>
+</resources>
diff --git a/core/res/res/values/themes.xml b/core/res/res/values/themes.xml
new file mode 100644
index 0000000..01c46de
--- /dev/null
+++ b/core/res/res/values/themes.xml
@@ -0,0 +1,364 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<resources>
+    <!-- The default system theme. This is the theme used for activities
+         that have not explicitly set their own theme.
+         
+         <p>You can count on this being a dark
+         background with light text on top, but should try to make no
+         other assumptions about its appearance. In particular, the text
+         inside of widgets using this theme may be completely different,
+         with the widget container being a light color and the text on top
+         of it a dark color.
+    -->
+    <style name="Theme">
+    
+        <item name="colorForeground">@android:color/bright_foreground_dark</item>
+        <item name="colorForegroundInverse">@android:color/bright_foreground_dark_inverse</item>
+        <item name="colorBackground">@android:color/background_dark</item>
+        <item name="disabledAlpha">0.5</item>
+        <item name="backgroundDimAmount">0.6</item>
+
+        <!-- Text styles -->
+        <item name="textAppearance">@android:style/TextAppearance</item>
+        <item name="textAppearanceInverse">@android:style/TextAppearance.Inverse</item>
+
+        <item name="textColorPrimary">@android:color/primary_text_dark</item>
+        <item name="textColorSecondary">@android:color/secondary_text_dark</item>
+        <item name="textColorTertiary">@android:color/tertiary_text_dark</item>
+        <item name="textColorPrimaryInverse">@android:color/primary_text_light</item>
+        <item name="textColorSecondaryInverse">@android:color/secondary_text_light</item>
+        <item name="textColorTertiaryInverse">@android:color/tertiary_text_light</item>
+        <item name="textColorPrimaryDisableOnly">@android:color/primary_text_dark_disable_only</item>
+        <item name="textColorPrimaryNoDisable">@android:color/primary_text_dark_nodisable</item>
+        <item name="textColorSecondaryNoDisable">@android:color/secondary_text_dark_nodisable</item>
+        <item name="textColorPrimaryInverseNoDisable">@android:color/primary_text_light_nodisable</item>
+        <item name="textColorSecondaryInverseNoDisable">@android:color/secondary_text_light_nodisable</item>
+        <item name="textColorHint">@android:color/hint_foreground_dark</item>
+        <item name="textColorHintInverse">@android:color/hint_foreground_light</item>
+
+        <item name="textAppearanceLarge">@android:style/TextAppearance.Large</item>
+        <item name="textAppearanceMedium">@android:style/TextAppearance.Medium</item>
+        <item name="textAppearanceSmall">@android:style/TextAppearance.Small</item>
+        <item name="textAppearanceLargeInverse">@android:style/TextAppearance.Large.Inverse</item>
+        <item name="textAppearanceMediumInverse">@android:style/TextAppearance.Medium.Inverse</item>
+        <item name="textAppearanceSmallInverse">@android:style/TextAppearance.Small.Inverse</item>
+        
+        <item name="textAppearanceButton">@android:style/TextAppearance.Widget.Button</item>
+        
+        <item name="candidatesTextStyleSpans">@android:string/candidates_style</item>
+        
+        <item name="textCheckMark">@android:drawable/indicator_check_mark_dark</item>
+        <item name="textCheckMarkInverse">@android:drawable/indicator_check_mark_light</item>
+
+        <!-- Button styles -->
+        <item name="buttonStyle">@android:style/Widget.Button</item>
+
+        <item name="buttonStyleSmall">@android:style/Widget.Button.Small</item>
+        <item name="buttonStyleInset">@android:style/Widget.Button.Inset</item>
+
+        <item name="buttonStyleToggle">@android:style/Widget.Button.Toggle</item>
+
+        <!-- List attributes -->
+        <item name="listPreferredItemHeight">64dip</item>
+        <item name="listDivider">@drawable/divider_horizontal_dark</item>
+        <item name="listSeparatorTextViewStyle">@android:style/Widget.TextView.ListSeparator</item>   
+        
+		<item name="listChoiceIndicatorSingle">@android:drawable/btn_radio</item>
+    	<item name="listChoiceIndicatorMultiple">@android:drawable/btn_check</item>    
+
+        <item name="expandableListPreferredItemPaddingLeft">40dip</item>
+        <item name="expandableListPreferredChildPaddingLeft">
+                ?android:attr/expandableListPreferredItemPaddingLeft</item>
+
+        <item name="expandableListPreferredItemIndicatorLeft">3dip</item>
+        <item name="expandableListPreferredItemIndicatorRight">33dip</item>
+        <item name="expandableListPreferredChildIndicatorLeft">
+                ?android:attr/expandableListPreferredItemIndicatorLeft</item>
+        <item name="expandableListPreferredChildIndicatorRight">
+                ?android:attr/expandableListPreferredItemIndicatorRight</item>
+
+        <!-- Gallery attributes -->
+        <item name="galleryItemBackground">@android:drawable/gallery_item_background</item>
+        
+        <!-- Window attributes -->
+        <item name="windowBackground">@android:drawable/screen_background_dark</item>
+        <item name="windowFrame">@null</item>
+        <item name="windowNoTitle">false</item>
+        <item name="windowFullscreen">false</item>
+        <item name="windowIsFloating">false</item>
+        <item name="windowContentOverlay">@android:drawable/title_bar_shadow</item>
+        <item name="windowTitleStyle">@android:style/WindowTitle</item>
+        <item name="windowTitleSize">25dip</item>
+        <item name="windowTitleBackgroundStyle">@android:style/WindowTitleBackground</item>
+        <item name="android:windowAnimationStyle">@android:style/Animation.Activity</item>
+        <item name="android:windowSoftInputMode">stateUnspecified|adjustUnspecified</item>
+
+        <!-- Dialog attributes -->
+        <item name="alertDialogStyle">@android:style/AlertDialog</item>
+        
+        <!-- Panel attributes -->
+        <item name="panelBackground">@android:drawable/menu_background</item>
+        <item name="panelFullBackground">@android:drawable/menu_background_fill_parent_width</item>
+        <item name="panelColorBackground">#fff</item>
+        <item name="panelColorForeground">?android:attr/textColorPrimaryInverse</item>
+        <item name="panelTextAppearance">?android:attr/textAppearanceInverse</item>
+
+        <!-- Scrollbar attributes -->
+        <item name="scrollbarSize">10dip</item>
+        <item name="scrollbarThumbHorizontal">@android:drawable/scrollbar_handle_horizontal</item>
+        <item name="scrollbarThumbVertical">@android:drawable/scrollbar_handle_vertical</item>
+        <item name="scrollbarTrackHorizontal">@null</item>
+        <item name="scrollbarTrackVertical">@null</item>
+
+        <!-- Widget styles -->
+        <item name="absListViewStyle">@android:style/Widget.AbsListView</item>
+        <item name="autoCompleteTextViewStyle">@android:style/Widget.AutoCompleteTextView</item>        
+        <item name="checkboxStyle">@android:style/Widget.CompoundButton.CheckBox</item>
+        <item name="dropDownListViewStyle">@android:style/Widget.ListView.DropDown</item>
+        <item name="editTextStyle">@android:style/Widget.EditText</item>
+        <item name="expandableListViewStyle">@android:style/Widget.ExpandableListView</item>
+        <item name="galleryStyle">@android:style/Widget.Gallery</item>
+        <item name="gridViewStyle">@android:style/Widget.GridView</item>        
+        <item name="imageButtonStyle">@android:style/Widget.ImageButton</item>
+        <item name="imageWellStyle">@android:style/Widget.ImageWell</item>
+        <item name="listViewStyle">@android:style/Widget.ListView</item>
+        <item name="listViewWhiteStyle">@android:style/Widget.ListView.White</item>
+        <item name="popupWindowStyle">@android:style/Widget.PopupWindow</item>
+        <item name="progressBarStyle">@android:style/Widget.ProgressBar</item>
+        <item name="progressBarStyleHorizontal">@android:style/Widget.ProgressBar.Horizontal</item>
+        <item name="progressBarStyleSmall">@android:style/Widget.ProgressBar.Small</item>
+        <item name="progressBarStyleSmallTitle">@android:style/Widget.ProgressBar.Small.Title</item>
+        <item name="progressBarStyleLarge">@android:style/Widget.ProgressBar.Large</item>
+        <item name="seekBarStyle">@android:style/Widget.SeekBar</item>
+        <item name="ratingBarStyle">@android:style/Widget.RatingBar</item>
+        <item name="ratingBarStyleIndicator">@android:style/Widget.RatingBar.Indicator</item>
+        <item name="ratingBarStyleSmall">@android:style/Widget.RatingBar.Small</item>
+        <item name="radioButtonStyle">@android:style/Widget.CompoundButton.RadioButton</item>
+        <item name="scrollViewStyle">@android:style/Widget.ScrollView</item>
+        <item name="horizontalScrollViewStyle">@android:style/Widget.HorizontalScrollView</item>
+        <item name="spinnerStyle">@android:style/Widget.Spinner</item>
+        <item name="starStyle">@android:style/Widget.CompoundButton.Star</item>
+        <item name="starStyleButtonless">@android:style/Widget.CompoundButton.StarButtonless</item>
+        <item name="tabWidgetStyle">@android:style/Widget.TabWidget</item>
+        <item name="textViewStyle">@android:style/Widget.TextView</item>
+        <item name="webViewStyle">@android:style/Widget.WebView</item>
+        <item name="dropDownItemStyle">@android:style/Widget.DropDownItem</item>
+        <item name="spinnerDropDownItemStyle">@android:style/Widget.DropDownItem.Spinner</item>
+        <item name="spinnerItemStyle">@android:style/Widget.TextView.SpinnerItem</item>
+        <item name="dropDownHintAppearance">@android:style/TextAppearance.Widget.DropDownHint</item>
+        <item name="keyboardViewStyle">@android:style/Widget.KeyboardView</item>
+        
+        <!-- Preference styles -->
+        <item name="preferenceScreenStyle">@android:style/Preference.PreferenceScreen</item>
+        <item name="preferenceCategoryStyle">@android:style/Preference.Category</item>
+        <item name="preferenceStyle">@android:style/Preference</item>
+        <item name="preferenceInformationStyle">@android:style/Preference.Information</item>
+        <item name="checkBoxPreferenceStyle">@android:style/Preference.CheckBoxPreference</item>
+        <item name="yesNoPreferenceStyle">@android:style/Preference.DialogPreference.YesNoPreference</item>
+        <item name="dialogPreferenceStyle">@android:style/Preference.DialogPreference</item>
+        <item name="editTextPreferenceStyle">@android:style/Preference.DialogPreference.EditTextPreference</item>
+        <item name="ringtonePreferenceStyle">@android:style/Preference.RingtonePreference</item>
+        <item name="preferenceLayoutChild">@android:layout/preference_child</item>
+    </style>
+    
+    <!-- Variant of the default (dark) theme with no title bar -->
+    <style name="Theme.NoTitleBar">
+        <item name="android:windowNoTitle">true</item>
+    </style>
+    
+    <!-- Variant of the default (dark) theme that has no title bar and
+         fills the entire screen -->
+    <style name="Theme.NoTitleBar.Fullscreen">
+        <item name="android:windowFullscreen">true</item>
+    </style>
+    
+    <!-- Theme for a light background with dark text on top.  Set your activity
+         to this theme if you would like such an appearance.  As with the
+         default theme, you should try to assume little more than that the
+         background will be a light color. -->
+    <style name="Theme.Light">
+        <item name="windowBackground">@drawable/screen_background_light</item>
+        <item name="colorBackground">@android:color/background_light</item>
+        <item name="colorForeground">@color/bright_foreground_light</item>
+        <item name="colorForegroundInverse">@android:color/bright_foreground_light_inverse</item>
+
+        <item name="textColorPrimary">@android:color/primary_text_light</item>
+        <item name="textColorSecondary">@android:color/secondary_text_light</item>
+        <item name="textColorTertiary">@android:color/tertiary_text_light</item>
+        <item name="textColorPrimaryInverse">@android:color/primary_text_dark</item>
+        <item name="textColorSecondaryInverse">@android:color/secondary_text_dark</item>
+        <item name="textColorTertiaryInverse">@android:color/tertiary_text_dark</item>
+        <item name="textColorPrimaryDisableOnly">@android:color/primary_text_light_disable_only</item>
+        <item name="textColorPrimaryNoDisable">@android:color/primary_text_light_nodisable</item>
+        <item name="textColorSecondaryNoDisable">@android:color/secondary_text_light_nodisable</item>
+        <item name="textColorPrimaryInverseNoDisable">@android:color/primary_text_dark_nodisable</item>
+        <item name="textColorSecondaryInverseNoDisable">@android:color/secondary_text_dark_nodisable</item>
+        <item name="textColorHint">@android:color/hint_foreground_light</item>
+        <item name="textColorHintInverse">@android:color/hint_foreground_dark</item>
+        
+        <item name="popupWindowStyle">@android:style/Widget.PopupWindow</item>
+        
+        <item name="textCheckMark">@android:drawable/indicator_check_mark_light</item>
+        <item name="textCheckMarkInverse">@android:drawable/indicator_check_mark_dark</item>
+
+        <item name="listViewStyle">@android:style/Widget.ListView.White</item>
+        <item name="listDivider">@drawable/divider_horizontal_bright</item>
+    </style>
+    
+    <!-- Variant of the light theme with no title bar -->
+    <style name="Theme.Light.NoTitleBar">
+        <item name="android:windowNoTitle">true</item>
+    </style>
+
+    <!-- Variant of the light theme that has no title bar and
+         fills the entire screen -->
+    <style name="Theme.Light.NoTitleBar.Fullscreen">
+        <item name="android:windowFullscreen">true</item>
+    </style>
+    
+    <!-- Special variation on the default theme that ensures the background is
+         completely black.  This is useful for things like image viewers and
+         media players.   If you want the normal (dark background) theme
+         do <em>not<em> use this, use {@link #Theme}. -->
+    <style name="Theme.Black">
+        <item name="android:windowBackground">@android:color/black</item>
+        <item name="android:colorBackground">@android:color/black</item>
+    </style>
+    
+    <!-- Variant of the black theme with no title bar -->
+    <style name="Theme.Black.NoTitleBar">
+        <item name="android:windowNoTitle">true</item>
+    </style>
+
+    <!-- Variant of the black theme that has no title bar and
+         fills the entire screen -->
+    <style name="Theme.Black.NoTitleBar.Fullscreen">
+        <item name="android:windowFullscreen">true</item>
+    </style>
+    
+    <!-- Default theme for translucent activities, that is windows that allow you
+         to see through them to the windows behind.  This sets up the translucent
+         flag and appropriate animations for your windows.  -->
+    <style name="Theme.Translucent">
+        <item name="android:windowBackground">@android:color/transparent</item>
+        <item name="android:windowIsTranslucent">true</item>
+        <item name="android:windowAnimationStyle">@android:style/Animation.Translucent</item>
+    </style>
+
+    <!-- Variant of the translucent theme with no title bar -->
+    <style name="Theme.Translucent.NoTitleBar">
+        <item name="android:windowNoTitle">true</item>
+    </style>
+
+    <!-- Variant of the translucent theme that has no title bar and
+         fills the entire screen -->
+    <style name="Theme.Translucent.NoTitleBar.Fullscreen">
+        <item name="android:windowFullscreen">true</item>
+    </style>
+    
+    <!-- Default theme for activities that don't actually display a UI; that
+         is, they finish themselves before being resumed.  -->
+    <style name="Theme.NoDisplay">
+        <item name="android:windowBackground">@null</item>
+        <item name="android:windowIsTranslucent">false</item>
+        <item name="android:windowAnimationStyle">@null</item>
+        <item name="android:windowDisablePreview">true</item>
+        <item name="android:windowNoDisplay">true</item>
+    </style>
+
+    <!-- Default theme for dialog windows and activities, which is used by the
+         {@link android.app.Dialog} class.  This changes the window to be
+         floating (not fill the entire screen), and puts a frame around its
+         contents.  You can set this theme on an activity if you would like to
+         make an activity that looks like a Dialog. -->
+    <style name="Theme.Dialog">
+        <item name="android:windowFrame">@null</item>
+        <item name="android:windowTitleStyle">@android:style/DialogWindowTitle</item>
+        <item name="android:windowBackground">@android:drawable/panel_background</item>
+        <item name="android:windowIsFloating">true</item>
+        <item name="android:windowContentOverlay">@android:drawable/panel_separator</item>
+        <item name="android:windowAnimationStyle">@android:style/Animation.Dialog</item>
+        <item name="android:windowSoftInputMode">stateUnspecified|adjustPan</item>
+    </style>
+
+    <!-- Default theme for alert dialog windows, which is used by the
+         {@link android.app.AlertDialog} class.  This is basically a dialog
+         but sets the background to empty so it can do two-tone backgrounds. -->
+    <style name="Theme.Dialog.Alert">
+        <item name="windowBackground">@android:color/transparent</item>
+        <item name="windowTitleStyle">@android:style/DialogWindowTitle</item>
+        <item name="windowIsFloating">true</item>
+        <item name="windowContentOverlay">@drawable/panel_separator</item>
+    </style>
+    
+    <!-- Default theme for dialog windows and activities, which is used by the
+         {@link android.app.Dialog} class.  This changes the window to be
+         floating (not fill the entire screen), and puts a frame around its
+         contents.  You can set this theme on an activity if you would like to
+         make an activity that looks like a Dialog. -->
+    <style name="Theme.InputMethod" parent="Theme.Light.NoTitleBar">
+        <item name="android:windowBackground">@android:color/transparent</item>
+        <item name="android:windowFrame">@null</item>
+        <item name="android:windowIsFloating">true</item>
+        <item name="android:backgroundDimEnabled">false</item>
+        <item name="android:windowIsTranslucent">true</item>
+        <item name="android:windowAnimationStyle">@android:style/Animation.InputMethod</item>
+    </style>
+
+    <!-- Theme for the search input bar. -->
+    <style name="Theme.SearchBar" parent="Theme.Translucent.NoTitleBar">
+        <item name="android:windowBackground">@android:color/transparent</item>
+        <item name="android:windowFrame">@null</item>
+        <item name="android:windowIsFloating">true</item>
+        <item name="android:windowIsTranslucent">true</item>
+        <item name="android:windowAnimationStyle">@android:style/Animation.SearchBar</item>
+    </style>
+
+    <!-- Menu Themes -->
+    <eat-comment />
+
+    <style name="Theme.IconMenu">
+        <!-- Menu/item attributes -->
+        <item name="android:itemTextAppearance">@android:style/TextAppearance.Widget.IconMenu.Item</item>
+        <item name="android:itemBackground">@android:drawable/menu_selector</item>
+        <item name="android:itemIconDisabledAlpha">?android:attr/disabledAlpha</item>
+        <item name="android:horizontalDivider">@android:drawable/divider_horizontal_bright</item>
+        <item name="android:verticalDivider">@android:drawable/divider_vertical_bright</item>
+        <item name="android:windowAnimationStyle">@android:style/Animation.OptionsPanel</item>
+        <item name="android:moreIcon">@android:drawable/ic_menu_more</item>
+        <item name="android:background">@null</item>
+    </style>
+
+    <style name="Theme.ExpandedMenu">
+        <!-- Menu/item attributes -->
+        <item name="android:itemTextAppearance">?android:attr/textAppearanceLargeInverse</item>
+        <item name="android:listViewStyle">@android:style/Widget.ListView.Menu</item>
+        <item name="android:windowAnimationStyle">@android:style/Animation.OptionsPanel</item>
+        <item name="android:background">@null</item>
+    </style>
+
+    <!-- @hide -->
+    <style name="Theme.Dialog.AppError">
+        <item name="windowFrame">@null</item>
+        <item name="windowTitleStyle">@android:style/DialogWindowTitle</item>
+        <item name="windowBackground">@android:color/transparent</item>
+        <item name="windowIsFloating">true</item>
+        <item name="windowContentOverlay">@drawable/panel_separator</item>
+        <item name="textAppearance">@style/TextAppearance.Theme.Dialog.AppError</item>
+    </style>
+</resources>
diff --git a/core/res/res/xml-en/autotext.xml b/core/res/res/xml-en/autotext.xml
new file mode 100644
index 0000000..4c02a00
--- /dev/null
+++ b/core/res/res/xml-en/autotext.xml
@@ -0,0 +1,195 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+**
+** Copyright 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.
+*/
+-->
+<words>
+    <word src="abouta">about a</word>
+    <word src="aboutit">about it</word>
+    <word src="aboutthe">about the</word>
+    <word src="acheive">achieve</word>
+    <word src="acheived">achieved</word>
+    <word src="acheiving">achieving</word>
+    <word src="acomodate">accommodate</word>
+    <word src="accomodate">accommodate</word>
+    <word src="acn">can</word>
+    <word src="adn">and</word>
+    <word src="agian">again</word>
+    <word src="ahd">had</word>
+    <word src="ahve">have</word>
+    <word src="aint">ain't</word>
+    <word src="alot">a lot</word>
+    <word src="amde">made</word>
+    <word src="amke">make</word>
+    <word src="andone">and one</word>
+    <word src="andteh">and the</word>
+    <word src="anothe">another</word>
+    <word src="arent">aren't</word>
+    <word src="asthe">as the</word>
+    <word src="atthe">at the</word>
+    <word src="bakc">back</word>
+    <word src="beacuse">because</word>
+    <word src="becasue">because</word>
+    <word src="becaus">because</word>
+    <word src="becausea">because a</word>
+    <word src="becauseof">because of</word>
+    <word src="becausethe">because the</word>
+    <word src="becauseyou">because you</word>
+    <word src="becuase">because</word>
+    <word src="becuse">because</word>
+    <word src="beleive">believe</word>
+    <word src="butthe">but the</word>
+    <word src="cant">can't</word>
+    <word src="certian">certain</word>
+    <word src="changable">changeable</word>
+    <word src="chekc">check</word>
+    <word src="chnage">change</word>
+    <word src="couldnt">couldn't</word>
+    <word src="couldthe">could the</word>
+    <word src="couldve">could've</word>
+    <word src="cna">can</word>
+    <word src="committment">commitment</word>
+    <word src="committments">commitments</word>
+    <word src="companys">company's</word>
+    <word src="cxan">can</word>
+    <word src="didint">didn't</word>
+    <word src="didnot">did not</word>
+    <word src="didnt">didn't</word>
+    <word src="doesnt">doesn't</word>
+    <word src="dont">don't</word>
+    <word src="eyt">yet</word>
+    <word src="fidn">find</word>
+    <word src="fora">for a</word>
+    <word src="freind">friend</word>
+    <word src="friday">Friday</word>
+    <word src="hadbeen">had been</word>
+    <word src="hadnt">hadn't</word>
+    <word src="haev">have</word>
+    <word src="hasbeen">has been</word>
+    <word src="hasnt">hasn't</word>
+    <word src="havent">haven't</word>
+    <word src="hed">he'd</word>
+    <word src="hel">he'll</word>
+    <word src="heres">here's</word>
+    <word src="hes">he's</word>
+    <word src="hlep">help</word>
+    <word src="howd">how'd</word>
+    <word src="howll">how'll</word>
+    <word src="hows">how's</word>
+    <word src="howve">how've</word>
+    <word src="hte">the</word>
+    <word src="htis">this</word>
+    <word src="hvae">have</word>
+    <word src="i">I</word>
+    <word src="il">I'll</word>
+    <word src="im">I'm</word>
+    <word src="i'm">I'm</word>
+    <word src="i'll">I'll</word>
+    <word src="i've">I've</word>
+    <word src="inteh">in the</word>
+    <word src="isnt">isn't</word>
+    <word src="isthe">is the</word>
+    <word src="itd">it'd</word>
+    <word src="itis">it is</word>
+    <word src="itll">it'll</word>
+    <word src="itsa">it's a</word>
+    <word src="ive">I've</word>
+    <word src="lets">let's</word>
+    <word src="maam">ma'am</word>
+    <word src="mkae">make</word>
+    <word src="mkaes">makes</word>
+    <word src="monday">Monday</word>
+    <word src="mustnt">mustn't</word>
+    <word src="neednt">needn't</word>
+    <word src="oclock">o'clock</word>
+    <word src="ofits">of its</word>
+    <word src="ofthe">of the</word>
+    <word src="omre">more</word>
+    <word src="oneof">one of</word>
+    <word src="otehr">other</word>
+    <word src="outof">out of</word>
+    <word src="overthe">over the</word>
+    <word src="owrk">work</word>
+    <word src="percentof">percent of</word>
+    <word src="recieve">receive</word>
+    <word src="recieved">received</word>
+    <word src="recieving">receiving</word>
+    <word src="saidthat">said that</word>
+    <word src="saidthe">said the</word>
+    <word src="saturday">Saturday</word>
+    <word src="seh">she</word>
+    <word src="shant">shan't</word>
+    <word src="she'">she'll</word>
+    <word src="shel">she'll</word>
+    <word src="shes">she's</word>
+    <word src="shouldent">shouldn't</word>
+    <word src="shouldnt">shouldn't</word>
+    <word src="shouldve">should've</word>
+    <word src="sunday">Sunday</word>
+    <word src="tahn">than</word>
+    <word src="taht">that</word>
+    <word src="teh">the</word>
+    <word src="thatd">that'd</word>
+    <word src="thatll">that'll</word>
+    <word src="thats">that's</word>
+    <word src="thatthe">that the</word>
+    <word src="theres">there's</word>
+    <word src="theyd">they'd</word>
+    <word src="theyll">they'll</word>
+    <word src="theyre">they're</word>
+    <word src="theyve">they've</word>
+    <word src="thier">their</word>
+    <word src="thsi">this</word>
+    <word src="thursday">Thursday</word>
+    <word src="tothe">to the</word>
+    <word src="tuesday">Tuesday</word>
+    <word src="UnitedStates">United States</word>
+    <word src="unitedstates">United States</word>
+    <word src="visavis">vis-a-vis</word>
+    <word src="wasnt">wasn't</word>
+    <word src="wednesday">Wednesday</word>
+    <word src="wierd">weird</word>
+    <word src="wel">we'll</word>
+    <word src="wer">we're</word>
+    <word src="werent">weren't</word>
+    <word src="weve">we've</word>
+    <word src="whatd">what'd</word>
+    <word src="whatll">what'll</word>
+    <word src="whatm">what'm</word>
+    <word src="whatre">what're</word>
+    <word src="whats">what's</word>
+    <word src="whens">when's</word>
+    <word src="whered">where'd</word>
+    <word src="wherell">where'll</word>
+    <word src="wheres">where's</word>
+    <word src="whod">who'd</word>
+    <word src="wholl">who'll</word>
+    <word src="whos">who's</word>
+    <word src="whove">who've</word>
+    <word src="whyd">why'd</word>
+    <word src="whyll">why'll</word>
+    <word src="whys">why's</word>
+    <word src="whyve">why've</word>
+    <word src="witha">with a</word>
+    <word src="wont">won't</word>
+    <word src="wouldnt">wouldn't</word>
+    <word src="wouldve">would've</word>
+    <word src="yall">y'all</word>
+    <word src="youd">you'd</word>
+    <word src="youll">you'll</word>
+    <word src="youre">you're</word>
+    <word src="youve">you've</word>
+</words>
diff --git a/core/res/res/xml/apns.xml b/core/res/res/xml/apns.xml
new file mode 100644
index 0000000..2c69b40
--- /dev/null
+++ b/core/res/res/xml/apns.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 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.
+*/
+-->
+
+<!-- use empty string to specify no proxy or port -->
+
+<!-- If you edit this version, also edit the version in the partner-supplied 
+    apns-conf.xml configuration file -->
+<apns version="6">
+
+</apns>
diff --git a/core/res/res/xml/autotext.xml b/core/res/res/xml/autotext.xml
new file mode 100644
index 0000000..d964003
--- /dev/null
+++ b/core/res/res/xml/autotext.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+**
+** Copyright 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.
+*/
+-->
+<words>
+</words>
diff --git a/core/res/res/xml/preferred_time_zones.xml b/core/res/res/xml/preferred_time_zones.xml
new file mode 100644
index 0000000..da8553f
--- /dev/null
+++ b/core/res/res/xml/preferred_time_zones.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* //device/apps/common/assets/default/default/data/preferred_time_zones.xml
+**
+** Copyright 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.
+*/
+-->
+<timezones>
+	<timezone offset="-18000000">America/New_York</timezone>
+	<timezone offset="-21600000">America/Chicago</timezone>
+	<timezone offset="-25200000">America/Denver</timezone>
+	<timezone offset="-28800000">America/Los_Angeles</timezone>
+</timezones>
diff --git a/core/res/res/xml/time_zones_by_country.xml b/core/res/res/xml/time_zones_by_country.xml
new file mode 100644
index 0000000..2d3e3fe
--- /dev/null
+++ b/core/res/res/xml/time_zones_by_country.xml
@@ -0,0 +1,1305 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+**
+** Copyright 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.
+*/
+-->
+<timezones>
+    <!-- ANDORRA, 1:00 -->
+
+    <timezone code="ad">Europe/Andorra</timezone>
+
+    <!-- UNITED ARAB EMIRATES, 4:00 -->
+
+    <timezone code="ae">Asia/Dubai</timezone>
+
+    <!-- AFGHANISTAN, 4:30 -->
+
+    <timezone code="af">Asia/Kabul</timezone>
+
+    <!-- ANTIGUA AND BARBUDA, -4:00 -->
+
+    <timezone code="ag">America/Antigua</timezone>
+
+    <!-- ANGUILLA, -4:00 -->
+
+    <timezone code="ai">America/Anguilla</timezone>
+
+    <!-- ALBANIA, 1:00 -->
+
+    <timezone code="al">Europe/Tirane</timezone>
+
+    <!-- ARMENIA, 4:00 -->
+
+    <timezone code="am">Asia/Yerevan</timezone>
+
+    <!-- NETHERLANDS ANTILLES, -4:00 -->
+
+    <timezone code="an">America/Curacao</timezone>
+
+    <!-- ANGOLA, 1:00 -->
+
+    <timezone code="ao">Africa/Luanda</timezone>
+
+    <!-- ANTARCTICA, 12:00 -->
+
+    <timezone code="aq">Antarctica/McMurdo</timezone>
+    <timezone code="aq">Antarctica/South_Pole</timezone>
+
+    <!-- ANTARCTICA, 10:00 -->
+
+    <timezone code="aq">Antarctica/DumontDUrville</timezone>
+
+    <!-- ANTARCTICA, 8:00 -->
+
+    <timezone code="aq">Antarctica/Casey</timezone>
+
+    <!-- ANTARCTICA, 7:00 -->
+
+    <timezone code="aq">Antarctica/Davis</timezone>
+
+    <!-- ANTARCTICA, 6:00 -->
+
+    <timezone code="aq">Antarctica/Mawson</timezone>
+    <timezone code="aq">Antarctica/Vostok</timezone>
+
+    <!-- ANTARCTICA, 3:00 -->
+
+    <timezone code="aq">Antarctica/Syowa</timezone>
+
+    <!-- ANTARCTICA, -3:00 -->
+
+    <timezone code="aq">Antarctica/Rothera</timezone>
+
+    <!-- ANTARCTICA, -4:00 -->
+
+    <timezone code="aq">Antarctica/Palmer</timezone>
+
+    <!-- ARGENTINA, -3:00 -->
+
+    <timezone code="ar">America/Argentina/Buenos_Aires</timezone>
+    <timezone code="ar">America/Argentina/Cordoba</timezone>
+    <timezone code="ar">America/Argentina/Jujuy</timezone>
+    <timezone code="ar">America/Argentina/Tucuman</timezone>
+    <timezone code="ar">America/Argentina/Catamarca</timezone>
+    <timezone code="ar">America/Argentina/La_Rioja</timezone>
+    <timezone code="ar">America/Argentina/San_Juan</timezone>
+    <timezone code="ar">America/Argentina/Mendoza</timezone>
+    <timezone code="ar">America/Argentina/Rio_Gallegos</timezone>
+    <timezone code="ar">America/Argentina/Ushuaia</timezone>
+
+    <!-- AMERICAN SAMOA, -11:00 -->
+
+    <timezone code="as">Pacific/Pago_Pago</timezone>
+
+    <!-- AUSTRIA, 1:00 -->
+
+    <timezone code="at">Europe/Vienna</timezone>
+
+    <!-- AUSTRALIA, 10:00 -->
+
+    <timezone code="au">Australia/Sydney</timezone>
+    <timezone code="au">Australia/Melbourne</timezone>
+    <timezone code="au">Australia/Brisbane</timezone>
+    <timezone code="au">Australia/Hobart</timezone>
+    <timezone code="au">Australia/Currie</timezone>
+    <timezone code="au">Australia/Lindeman</timezone>
+
+    <!-- AUSTRALIA, 10:30 -->
+
+    <timezone code="au">Australia/Lord_Howe</timezone>
+
+    <!-- AUSTRALIA, 9:30 -->
+
+    <timezone code="au">Australia/Adelaide</timezone>
+    <timezone code="au">Australia/Broken_Hill</timezone>
+    <timezone code="au">Australia/Darwin</timezone>
+
+    <!-- AUSTRALIA, 8:00 -->
+
+    <timezone code="au">Australia/Perth</timezone>
+
+    <!-- AUSTRALIA, 8:45 -->
+
+    <timezone code="au">Australia/Eucla</timezone>
+
+    <!-- ARUBA, -4:00 -->
+
+    <timezone code="aw">America/Aruba</timezone>
+
+    <!-- ALAND ISLANDS, 2:00 -->
+
+    <timezone code="ax">Europe/Mariehamn</timezone>
+
+    <!-- AZERBAIJAN, 4:00 -->
+
+    <timezone code="az">Asia/Baku</timezone>
+
+    <!-- BOSNIA AND HERZEGOVINA, 1:00 -->
+
+    <timezone code="ba">Europe/Sarajevo</timezone>
+
+    <!-- BARBADOS, -4:00 -->
+
+    <timezone code="bb">America/Barbados</timezone>
+
+    <!-- BANGLADESH, 6:00 -->
+
+    <timezone code="bd">Asia/Dhaka</timezone>
+
+    <!-- BELGIUM, 1:00 -->
+
+    <timezone code="be">Europe/Brussels</timezone>
+
+    <!-- BURKINA FASO, 0:00 -->
+
+    <timezone code="bf">Africa/Ouagadougou</timezone>
+
+    <!-- BULGARIA, 2:00 -->
+
+    <timezone code="bg">Europe/Sofia</timezone>
+
+    <!-- BAHRAIN, 3:00 -->
+
+    <timezone code="bh">Asia/Bahrain</timezone>
+
+    <!-- BURUNDI, 2:00 -->
+
+    <timezone code="bi">Africa/Bujumbura</timezone>
+
+    <!-- BENIN, 1:00 -->
+
+    <timezone code="bj">Africa/Porto-Novo</timezone>
+
+    <!-- BERMUDA, -4:00 -->
+
+    <timezone code="bm">Atlantic/Bermuda</timezone>
+
+    <!-- BRUNEI DARUSSALAM, 8:00 -->
+
+    <timezone code="bn">Asia/Brunei</timezone>
+
+    <!-- BOLIVIA, -4:00 -->
+
+    <timezone code="bo">America/La_Paz</timezone>
+
+    <!-- BRAZIL, -2:00 -->
+
+    <timezone code="br">America/Noronha</timezone>
+
+    <!-- BRAZIL, -3:00 -->
+
+    <timezone code="br">America/Sao_Paulo</timezone>
+    <timezone code="br">America/Belem</timezone>
+    <timezone code="br">America/Fortaleza</timezone>
+    <timezone code="br">America/Recife</timezone>
+    <timezone code="br">America/Araguaina</timezone>
+    <timezone code="br">America/Maceio</timezone>
+    <timezone code="br">America/Bahia</timezone>
+
+    <!-- BRAZIL, -4:00 -->
+
+    <timezone code="br">America/Manaus</timezone>
+    <timezone code="br">America/Campo_Grande</timezone>
+    <timezone code="br">America/Cuiaba</timezone>
+    <timezone code="br">America/Porto_Velho</timezone>
+    <timezone code="br">America/Boa_Vista</timezone>
+    <timezone code="br">America/Eirunepe</timezone>
+    <timezone code="br">America/Rio_Branco</timezone>
+
+    <!-- BAHAMAS, -5:00 -->
+
+    <timezone code="bs">America/Nassau</timezone>
+
+    <!-- BHUTAN, 6:00 -->
+
+    <timezone code="bt">Asia/Thimphu</timezone>
+
+    <!-- BOTSWANA, 2:00 -->
+
+    <timezone code="bw">Africa/Gaborone</timezone>
+
+    <!-- BELARUS, 2:00 -->
+
+    <timezone code="by">Europe/Minsk</timezone>
+
+    <!-- BELIZE, -6:00 -->
+
+    <timezone code="bz">America/Belize</timezone>
+
+    <!-- CANADA, -3:30 -->
+
+    <timezone code="ca">America/St_Johns</timezone>
+
+    <!-- CANADA, -4:00 -->
+
+    <timezone code="ca">America/Halifax</timezone>
+    <timezone code="ca">America/Glace_Bay</timezone>
+    <timezone code="ca">America/Moncton</timezone>
+    <timezone code="ca">America/Goose_Bay</timezone>
+    <timezone code="ca">America/Blanc-Sablon</timezone>
+
+    <!-- CANADA, -5:00 -->
+
+    <timezone code="ca">America/Toronto</timezone>
+    <timezone code="ca">America/Montreal</timezone>
+    <timezone code="ca">America/Nipigon</timezone>
+    <timezone code="ca">America/Thunder_Bay</timezone>
+    <timezone code="ca">America/Iqaluit</timezone>
+    <timezone code="ca">America/Pangnirtung</timezone>
+    <timezone code="ca">America/Resolute</timezone>
+    <timezone code="ca">America/Atikokan</timezone>
+
+    <!-- CANADA, -6:00 -->
+
+    <timezone code="ca">America/Winnipeg</timezone>
+    <timezone code="ca">America/Regina</timezone>
+    <timezone code="ca">America/Rankin_Inlet</timezone>
+    <timezone code="ca">America/Rainy_River</timezone>
+    <timezone code="ca">America/Swift_Current</timezone>
+
+    <!-- CANADA, -7:00 -->
+
+    <timezone code="ca">America/Edmonton</timezone>
+    <timezone code="ca">America/Cambridge_Bay</timezone>
+    <timezone code="ca">America/Yellowknife</timezone>
+    <timezone code="ca">America/Inuvik</timezone>
+    <timezone code="ca">America/Dawson_Creek</timezone>
+
+    <!-- CANADA, -8:00 -->
+
+    <timezone code="ca">America/Vancouver</timezone>
+    <timezone code="ca">America/Whitehorse</timezone>
+    <timezone code="ca">America/Dawson</timezone>
+
+    <!-- COCOS (KEELING) ISLANDS, 6:30 -->
+
+    <timezone code="cc">Indian/Cocos</timezone>
+
+    <!-- CONGO, THE DEMOCRATIC REPUBLIC OF THE, 2:00 -->
+
+    <timezone code="cd">Africa/Lubumbashi</timezone>
+
+    <!-- CONGO, THE DEMOCRATIC REPUBLIC OF THE, 1:00 -->
+
+    <timezone code="cd">Africa/Kinshasa</timezone>
+
+    <!-- CENTRAL AFRICAN REPUBLIC, 1:00 -->
+
+    <timezone code="cf">Africa/Bangui</timezone>
+
+    <!-- CONGO, 1:00 -->
+
+    <timezone code="cg">Africa/Brazzaville</timezone>
+
+    <!-- SWITZERLAND, 1:00 -->
+
+    <timezone code="ch">Europe/Zurich</timezone>
+
+    <!-- COTE D'IVOIRE, 0:00 -->
+
+    <timezone code="ci">Africa/Abidjan</timezone>
+
+    <!-- COOK ISLANDS, -10:00 -->
+
+    <timezone code="ck">Pacific/Rarotonga</timezone>
+
+    <!-- CHILE, -4:00 -->
+
+    <timezone code="cl">America/Santiago</timezone>
+
+    <!-- CHILE, -6:00 -->
+
+    <timezone code="cl">Pacific/Easter</timezone>
+
+    <!-- CAMEROON, 1:00 -->
+
+    <timezone code="cm">Africa/Douala</timezone>
+
+    <!-- CHINA, 8:00 -->
+
+    <timezone code="cn">Asia/Shanghai</timezone>
+    <timezone code="cn">Asia/Harbin</timezone>
+    <timezone code="cn">Asia/Chongqing</timezone>
+    <timezone code="cn">Asia/Urumqi</timezone>
+    <timezone code="cn">Asia/Kashgar</timezone>
+
+    <!-- COLOMBIA, -5:00 -->
+
+    <timezone code="co">America/Bogota</timezone>
+
+    <!-- COSTA RICA, -6:00 -->
+
+    <timezone code="cr">America/Costa_Rica</timezone>
+
+    <!-- CUBA, -5:00 -->
+
+    <timezone code="cu">America/Havana</timezone>
+
+    <!-- CAPE VERDE, -1:00 -->
+
+    <timezone code="cv">Atlantic/Cape_Verde</timezone>
+
+    <!-- CHRISTMAS ISLAND, 7:00 -->
+
+    <timezone code="cx">Indian/Christmas</timezone>
+
+    <!-- CYPRUS, 2:00 -->
+
+    <timezone code="cy">Asia/Nicosia</timezone>
+
+    <!-- CZECH REPUBLIC, 1:00 -->
+
+    <timezone code="cz">Europe/Prague</timezone>
+
+    <!-- GERMANY, 1:00 -->
+
+    <timezone code="de">Europe/Berlin</timezone>
+
+    <!-- DJIBOUTI, 3:00 -->
+
+    <timezone code="dj">Africa/Djibouti</timezone>
+
+    <!-- DENMARK, 1:00 -->
+
+    <timezone code="dk">Europe/Copenhagen</timezone>
+
+    <!-- DOMINICA, -4:00 -->
+
+    <timezone code="dm">America/Dominica</timezone>
+
+    <!-- DOMINICAN REPUBLIC, -4:00 -->
+
+    <timezone code="do">America/Santo_Domingo</timezone>
+
+    <!-- ALGERIA, 1:00 -->
+
+    <timezone code="dz">Africa/Algiers</timezone>
+
+    <!-- ECUADOR, -5:00 -->
+
+    <timezone code="ec">America/Guayaquil</timezone>
+
+    <!-- ECUADOR, -6:00 -->
+
+    <timezone code="ec">Pacific/Galapagos</timezone>
+
+    <!-- ESTONIA, 2:00 -->
+
+    <timezone code="ee">Europe/Tallinn</timezone>
+
+    <!-- EGYPT, 2:00 -->
+
+    <timezone code="eg">Africa/Cairo</timezone>
+
+    <!-- WESTERN SAHARA, 0:00 -->
+
+    <timezone code="eh">Africa/El_Aaiun</timezone>
+
+    <!-- ERITREA, 3:00 -->
+
+    <timezone code="er">Africa/Asmara</timezone>
+
+    <!-- SPAIN, 1:00 -->
+
+    <timezone code="es">Europe/Madrid</timezone>
+    <timezone code="es">Africa/Ceuta</timezone>
+
+    <!-- SPAIN, 0:00 -->
+
+    <timezone code="es">Atlantic/Canary</timezone>
+
+    <!-- ETHIOPIA, 3:00 -->
+
+    <timezone code="et">Africa/Addis_Ababa</timezone>
+
+    <!-- FINLAND, 2:00 -->
+
+    <timezone code="fi">Europe/Helsinki</timezone>
+
+    <!-- FIJI, 12:00 -->
+
+    <timezone code="fj">Pacific/Fiji</timezone>
+
+    <!-- FALKLAND ISLANDS (MALVINAS), -4:00 -->
+
+    <timezone code="fk">Atlantic/Stanley</timezone>
+
+    <!-- MICRONESIA, FEDERATED STATES OF, 11:00 -->
+
+    <timezone code="fm">Pacific/Ponape</timezone>
+    <timezone code="fm">Pacific/Kosrae</timezone>
+
+    <!-- MICRONESIA, FEDERATED STATES OF, 10:00 -->
+
+    <timezone code="fm">Pacific/Truk</timezone>
+
+    <!-- FAROE ISLANDS, 0:00 -->
+
+    <timezone code="fo">Atlantic/Faroe</timezone>
+
+    <!-- FRANCE, 1:00 -->
+
+    <timezone code="fr">Europe/Paris</timezone>
+
+    <!-- GABON, 1:00 -->
+
+    <timezone code="ga">Africa/Libreville</timezone>
+
+    <!-- UNITED KINGDOM, 0:00 -->
+
+    <timezone code="gb">Europe/London</timezone>
+
+    <!-- GRENADA, -4:00 -->
+
+    <timezone code="gd">America/Grenada</timezone>
+
+    <!-- GEORGIA, 4:00 -->
+
+    <timezone code="ge">Asia/Tbilisi</timezone>
+
+    <!-- FRENCH GUIANA, -3:00 -->
+
+    <timezone code="gf">America/Cayenne</timezone>
+
+    <!-- GUERNSEY, 0:00 -->
+
+    <timezone code="gg">Europe/Guernsey</timezone>
+
+    <!-- GHANA, 0:00 -->
+
+    <timezone code="gh">Africa/Accra</timezone>
+
+    <!-- GIBRALTAR, 1:00 -->
+
+    <timezone code="gi">Europe/Gibraltar</timezone>
+
+    <!-- GREENLAND, 0:00 -->
+
+    <timezone code="gl">America/Danmarkshavn</timezone>
+
+    <!-- GREENLAND, -1:00 -->
+
+    <timezone code="gl">America/Scoresbysund</timezone>
+
+    <!-- GREENLAND, -3:00 -->
+
+    <timezone code="gl">America/Godthab</timezone>
+
+    <!-- GREENLAND, -4:00 -->
+
+    <timezone code="gl">America/Thule</timezone>
+
+    <!-- GAMBIA, 0:00 -->
+
+    <timezone code="gm">Africa/Banjul</timezone>
+
+    <!-- GUINEA, 0:00 -->
+
+    <timezone code="gn">Africa/Conakry</timezone>
+
+    <!-- GUADELOUPE, -4:00 -->
+
+    <timezone code="gp">America/Guadeloupe</timezone>
+
+    <!-- EQUATORIAL GUINEA, 1:00 -->
+
+    <timezone code="gq">Africa/Malabo</timezone>
+
+    <!-- GREECE, 2:00 -->
+
+    <timezone code="gr">Europe/Athens</timezone>
+
+    <!-- SOUTH GEORGIA AND THE SOUTH SANDWICH ISLANDS, -2:00 -->
+
+    <timezone code="gs">Atlantic/South_Georgia</timezone>
+
+    <!-- GUATEMALA, -6:00 -->
+
+    <timezone code="gt">America/Guatemala</timezone>
+
+    <!-- GUAM, 10:00 -->
+
+    <timezone code="gu">Pacific/Guam</timezone>
+
+    <!-- GUINEA-BISSAU, 0:00 -->
+
+    <timezone code="gw">Africa/Bissau</timezone>
+
+    <!-- GUYANA, -4:00 -->
+
+    <timezone code="gy">America/Guyana</timezone>
+
+    <!-- HONG KONG, 8:00 -->
+
+    <timezone code="hk">Asia/Hong_Kong</timezone>
+
+    <!-- HONDURAS, -6:00 -->
+
+    <timezone code="hn">America/Tegucigalpa</timezone>
+
+    <!-- CROATIA, 1:00 -->
+
+    <timezone code="hr">Europe/Zagreb</timezone>
+
+    <!-- HAITI, -5:00 -->
+
+    <timezone code="ht">America/Port-au-Prince</timezone>
+
+    <!-- HUNGARY, 1:00 -->
+
+    <timezone code="hu">Europe/Budapest</timezone>
+
+    <!-- INDONESIA, 9:00 -->
+
+    <timezone code="id">Asia/Jayapura</timezone>
+
+    <!-- INDONESIA, 8:00 -->
+
+    <timezone code="id">Asia/Makassar</timezone>
+
+    <!-- INDONESIA, 7:00 -->
+
+    <timezone code="id">Asia/Jakarta</timezone>
+    <timezone code="id">Asia/Pontianak</timezone>
+
+    <!-- IRELAND, 0:00 -->
+
+    <timezone code="ie">Europe/Dublin</timezone>
+
+    <!-- ISRAEL, 2:00 -->
+
+    <timezone code="il">Asia/Jerusalem</timezone>
+
+    <!-- ISLE OF MAN, 0:00 -->
+
+    <timezone code="im">Europe/Isle_of_Man</timezone>
+
+    <!-- INDIA, 5:30 -->
+
+    <timezone code="in">Asia/Calcutta</timezone>
+
+    <!-- BRITISH INDIAN OCEAN TERRITORY, 6:00 -->
+
+    <timezone code="io">Indian/Chagos</timezone>
+
+    <!-- IRAQ, 3:00 -->
+
+    <timezone code="iq">Asia/Baghdad</timezone>
+
+    <!-- IRAN, ISLAMIC REPUBLIC OF, 3:30 -->
+
+    <timezone code="ir">Asia/Tehran</timezone>
+
+    <!-- ICELAND, 0:00 -->
+
+    <timezone code="is">Atlantic/Reykjavik</timezone>
+
+    <!-- ITALY, 1:00 -->
+
+    <timezone code="it">Europe/Rome</timezone>
+
+    <!-- JERSEY, 0:00 -->
+
+    <timezone code="je">Europe/Jersey</timezone>
+
+    <!-- JAMAICA, -5:00 -->
+
+    <timezone code="jm">America/Jamaica</timezone>
+
+    <!-- JORDAN, 2:00 -->
+
+    <timezone code="jo">Asia/Amman</timezone>
+
+    <!-- JAPAN, 9:00 -->
+
+    <timezone code="jp">Asia/Tokyo</timezone>
+
+    <!-- KENYA, 3:00 -->
+
+    <timezone code="ke">Africa/Nairobi</timezone>
+
+    <!-- KYRGYZSTAN, 6:00 -->
+
+    <timezone code="kg">Asia/Bishkek</timezone>
+
+    <!-- CAMBODIA, 7:00 -->
+
+    <timezone code="kh">Asia/Phnom_Penh</timezone>
+
+    <!-- KIRIBATI, 14:00 -->
+
+    <timezone code="ki">Pacific/Kiritimati</timezone>
+
+    <!-- KIRIBATI, 13:00 -->
+
+    <timezone code="ki">Pacific/Enderbury</timezone>
+
+    <!-- KIRIBATI, 12:00 -->
+
+    <timezone code="ki">Pacific/Tarawa</timezone>
+
+    <!-- COMOROS, 3:00 -->
+
+    <timezone code="km">Indian/Comoro</timezone>
+
+    <!-- SAINT KITTS AND NEVIS, -4:00 -->
+
+    <timezone code="kn">America/St_Kitts</timezone>
+
+    <!-- KOREA, DEMOCRATIC PEOPLE'S REPUBLIC OF, 9:00 -->
+
+    <timezone code="kp">Asia/Pyongyang</timezone>
+
+    <!-- KOREA, REPUBLIC OF, 9:00 -->
+
+    <timezone code="kr">Asia/Seoul</timezone>
+
+    <!-- KUWAIT, 3:00 -->
+
+    <timezone code="kw">Asia/Kuwait</timezone>
+
+    <!-- CAYMAN ISLANDS, -5:00 -->
+
+    <timezone code="ky">America/Cayman</timezone>
+
+    <!-- KAZAKHSTAN, 6:00 -->
+
+    <timezone code="kz">Asia/Almaty</timezone>
+    <timezone code="kz">Asia/Qyzylorda</timezone>
+
+    <!-- KAZAKHSTAN, 5:00 -->
+
+    <timezone code="kz">Asia/Aqtau</timezone>
+    <timezone code="kz">Asia/Oral</timezone>
+    <timezone code="kz">Asia/Aqtobe</timezone>
+
+    <!-- LAO PEOPLE'S DEMOCRATIC REPUBLIC, 7:00 -->
+
+    <timezone code="la">Asia/Vientiane</timezone>
+
+    <!-- LEBANON, 2:00 -->
+
+    <timezone code="lb">Asia/Beirut</timezone>
+
+    <!-- SAINT LUCIA, -4:00 -->
+
+    <timezone code="lc">America/St_Lucia</timezone>
+
+    <!-- LIECHTENSTEIN, 1:00 -->
+
+    <timezone code="li">Europe/Vaduz</timezone>
+
+    <!-- SRI LANKA, 5:30 -->
+
+    <timezone code="lk">Asia/Colombo</timezone>
+
+    <!-- LIBERIA, 0:00 -->
+
+    <timezone code="lr">Africa/Monrovia</timezone>
+
+    <!-- LESOTHO, 2:00 -->
+
+    <timezone code="ls">Africa/Maseru</timezone>
+
+    <!-- LITHUANIA, 2:00 -->
+
+    <timezone code="lt">Europe/Vilnius</timezone>
+
+    <!-- LUXEMBOURG, 1:00 -->
+
+    <timezone code="lu">Europe/Luxembourg</timezone>
+
+    <!-- LATVIA, 2:00 -->
+
+    <timezone code="lv">Europe/Riga</timezone>
+
+    <!-- LIBYAN ARAB JAMAHIRIYA, 2:00 -->
+
+    <timezone code="ly">Africa/Tripoli</timezone>
+
+    <!-- MOROCCO, 0:00 -->
+
+    <timezone code="ma">Africa/Casablanca</timezone>
+
+    <!-- MONACO, 1:00 -->
+
+    <timezone code="mc">Europe/Monaco</timezone>
+
+    <!-- MOLDOVA, 2:00 -->
+
+    <timezone code="md">Europe/Chisinau</timezone>
+
+    <!-- MONTENEGRO, 1:00 -->
+
+    <timezone code="me">Europe/Podgorica</timezone>
+
+    <!-- MADAGASCAR, 3:00 -->
+
+    <timezone code="mg">Indian/Antananarivo</timezone>
+
+    <!-- MARSHALL ISLANDS, 12:00 -->
+
+    <timezone code="mh">Pacific/Majuro</timezone>
+    <timezone code="mh">Pacific/Kwajalein</timezone>
+
+    <!-- MACEDONIA, THE FORMER YUGOSLAV REPUBLIC OF, 1:00 -->
+
+    <timezone code="mk">Europe/Skopje</timezone>
+
+    <!-- MALI, 0:00 -->
+
+    <timezone code="ml">Africa/Bamako</timezone>
+
+    <!-- MYANMAR, 6:30 -->
+
+    <timezone code="mm">Asia/Rangoon</timezone>
+
+    <!-- MONGOLIA, 8:00 -->
+
+    <timezone code="mn">Asia/Choibalsan</timezone>
+    <timezone code="mn">Asia/Ulaanbaatar</timezone>
+
+    <!-- MONGOLIA, 7:00 -->
+
+    <timezone code="mn">Asia/Hovd</timezone>
+
+    <!-- MACAO, 8:00 -->
+
+    <timezone code="mo">Asia/Macau</timezone>
+
+    <!-- NORTHERN MARIANA ISLANDS, 10:00 -->
+
+    <timezone code="mp">Pacific/Saipan</timezone>
+
+    <!-- MARTINIQUE, -4:00 -->
+
+    <timezone code="mq">America/Martinique</timezone>
+
+    <!-- MAURITANIA, 0:00 -->
+
+    <timezone code="mr">Africa/Nouakchott</timezone>
+
+    <!-- MONTSERRAT, -4:00 -->
+
+    <timezone code="ms">America/Montserrat</timezone>
+
+    <!-- MALTA, 1:00 -->
+
+    <timezone code="mt">Europe/Malta</timezone>
+
+    <!-- MAURITIUS, 4:00 -->
+
+    <timezone code="mu">Indian/Mauritius</timezone>
+
+    <!-- MALDIVES, 5:00 -->
+
+    <timezone code="mv">Indian/Maldives</timezone>
+
+    <!-- MALAWI, 2:00 -->
+
+    <timezone code="mw">Africa/Blantyre</timezone>
+
+    <!-- MEXICO, -6:00 -->
+
+    <timezone code="mx">America/Mexico_City</timezone>
+    <timezone code="mx">America/Cancun</timezone>
+    <timezone code="mx">America/Merida</timezone>
+    <timezone code="mx">America/Monterrey</timezone>
+
+    <!-- MEXICO, -7:00 -->
+
+    <timezone code="mx">America/Chihuahua</timezone>
+    <timezone code="mx">America/Hermosillo</timezone>
+    <timezone code="mx">America/Mazatlan</timezone>
+
+    <!-- MEXICO, -8:00 -->
+
+    <timezone code="mx">America/Tijuana</timezone>
+
+    <!-- MALAYSIA, 8:00 -->
+
+    <timezone code="my">Asia/Kuala_Lumpur</timezone>
+    <timezone code="my">Asia/Kuching</timezone>
+
+    <!-- MOZAMBIQUE, 2:00 -->
+
+    <timezone code="mz">Africa/Maputo</timezone>
+
+    <!-- NAMIBIA, 1:00 -->
+
+    <timezone code="na">Africa/Windhoek</timezone>
+
+    <!-- NEW CALEDONIA, 11:00 -->
+
+    <timezone code="nc">Pacific/Noumea</timezone>
+
+    <!-- NIGER, 1:00 -->
+
+    <timezone code="ne">Africa/Niamey</timezone>
+
+    <!-- NORFOLK ISLAND, 11:30 -->
+
+    <timezone code="nf">Pacific/Norfolk</timezone>
+
+    <!-- NIGERIA, 1:00 -->
+
+    <timezone code="ng">Africa/Lagos</timezone>
+
+    <!-- NICARAGUA, -6:00 -->
+
+    <timezone code="ni">America/Managua</timezone>
+
+    <!-- NETHERLANDS, 1:00 -->
+
+    <timezone code="nl">Europe/Amsterdam</timezone>
+
+    <!-- NORWAY, 1:00 -->
+
+    <timezone code="no">Europe/Oslo</timezone>
+
+    <!-- NEPAL, 5:45 -->
+
+    <timezone code="np">Asia/Katmandu</timezone>
+
+    <!-- NAURU, 12:00 -->
+
+    <timezone code="nr">Pacific/Nauru</timezone>
+
+    <!-- NIUE, -11:00 -->
+
+    <timezone code="nu">Pacific/Niue</timezone>
+
+    <!-- NEW ZEALAND, 12:00 -->
+
+    <timezone code="nz">Pacific/Auckland</timezone>
+
+    <!-- NEW ZEALAND, 12:45 -->
+
+    <timezone code="nz">Pacific/Chatham</timezone>
+
+    <!-- OMAN, 4:00 -->
+
+    <timezone code="om">Asia/Muscat</timezone>
+
+    <!-- PANAMA, -5:00 -->
+
+    <timezone code="pa">America/Panama</timezone>
+
+    <!-- PERU, -5:00 -->
+
+    <timezone code="pe">America/Lima</timezone>
+
+    <!-- FRENCH POLYNESIA, -9:00 -->
+
+    <timezone code="pf">Pacific/Gambier</timezone>
+
+    <!-- FRENCH POLYNESIA, -9:30 -->
+
+    <timezone code="pf">Pacific/Marquesas</timezone>
+
+    <!-- FRENCH POLYNESIA, -10:00 -->
+
+    <timezone code="pf">Pacific/Tahiti</timezone>
+
+    <!-- PAPUA NEW GUINEA, 10:00 -->
+
+    <timezone code="pg">Pacific/Port_Moresby</timezone>
+
+    <!-- PHILIPPINES, 8:00 -->
+
+    <timezone code="ph">Asia/Manila</timezone>
+
+    <!-- PAKISTAN, 5:00 -->
+
+    <timezone code="pk">Asia/Karachi</timezone>
+
+    <!-- POLAND, 1:00 -->
+
+    <timezone code="pl">Europe/Warsaw</timezone>
+
+    <!-- SAINT PIERRE AND MIQUELON, -3:00 -->
+
+    <timezone code="pm">America/Miquelon</timezone>
+
+    <!-- PITCAIRN, -8:00 -->
+
+    <timezone code="pn">Pacific/Pitcairn</timezone>
+
+    <!-- PUERTO RICO, -4:00 -->
+
+    <timezone code="pr">America/Puerto_Rico</timezone>
+
+    <!-- PALESTINIAN TERRITORY, OCCUPIED, 2:00 -->
+
+    <timezone code="ps">Asia/Gaza</timezone>
+
+    <!-- PORTUGAL, 0:00 -->
+
+    <timezone code="pt">Europe/Lisbon</timezone>
+    <timezone code="pt">Atlantic/Madeira</timezone>
+
+    <!-- PORTUGAL, -1:00 -->
+
+    <timezone code="pt">Atlantic/Azores</timezone>
+
+    <!-- PALAU, 9:00 -->
+
+    <timezone code="pw">Pacific/Palau</timezone>
+
+    <!-- PARAGUAY, -4:00 -->
+
+    <timezone code="py">America/Asuncion</timezone>
+
+    <!-- QATAR, 3:00 -->
+
+    <timezone code="qa">Asia/Qatar</timezone>
+
+    <!-- REUNION, 4:00 -->
+
+    <timezone code="re">Indian/Reunion</timezone>
+
+    <!-- ROMANIA, 2:00 -->
+
+    <timezone code="ro">Europe/Bucharest</timezone>
+
+    <!-- SERBIA, 1:00 -->
+
+    <timezone code="rs">Europe/Belgrade</timezone>
+
+    <!-- RUSSIAN FEDERATION, 12:00 -->
+
+    <timezone code="ru">Asia/Kamchatka</timezone>
+    <timezone code="ru">Asia/Anadyr</timezone>
+
+    <!-- RUSSIAN FEDERATION, 11:00 -->
+
+    <timezone code="ru">Asia/Magadan</timezone>
+
+    <!-- RUSSIAN FEDERATION, 10:00 -->
+
+    <timezone code="ru">Asia/Vladivostok</timezone>
+    <timezone code="ru">Asia/Sakhalin</timezone>
+
+    <!-- RUSSIAN FEDERATION, 9:00 -->
+
+    <timezone code="ru">Asia/Yakutsk</timezone>
+
+    <!-- RUSSIAN FEDERATION, 8:00 -->
+
+    <timezone code="ru">Asia/Irkutsk</timezone>
+
+    <!-- RUSSIAN FEDERATION, 7:00 -->
+
+    <timezone code="ru">Asia/Krasnoyarsk</timezone>
+
+    <!-- RUSSIAN FEDERATION, 6:00 -->
+
+    <timezone code="ru">Asia/Novosibirsk</timezone>
+    <timezone code="ru">Asia/Omsk</timezone>
+
+    <!-- RUSSIAN FEDERATION, 5:00 -->
+
+    <timezone code="ru">Asia/Yekaterinburg</timezone>
+
+    <!-- RUSSIAN FEDERATION, 4:00 -->
+
+    <timezone code="ru">Europe/Samara</timezone>
+
+    <!-- RUSSIAN FEDERATION, 3:00 -->
+
+    <timezone code="ru">Europe/Moscow</timezone>
+    <timezone code="ru">Europe/Volgograd</timezone>
+
+    <!-- RUSSIAN FEDERATION, 2:00 -->
+
+    <timezone code="ru">Europe/Kaliningrad</timezone>
+
+    <!-- RWANDA, 2:00 -->
+
+    <timezone code="rw">Africa/Kigali</timezone>
+
+    <!-- SAUDI ARABIA, 3:00 -->
+
+    <timezone code="sa">Asia/Riyadh</timezone>
+
+    <!-- SOLOMON ISLANDS, 11:00 -->
+
+    <timezone code="sb">Pacific/Guadalcanal</timezone>
+
+    <!-- SEYCHELLES, 4:00 -->
+
+    <timezone code="sc">Indian/Mahe</timezone>
+
+    <!-- SUDAN, 3:00 -->
+
+    <timezone code="sd">Africa/Khartoum</timezone>
+
+    <!-- SWEDEN, 1:00 -->
+
+    <timezone code="se">Europe/Stockholm</timezone>
+
+    <!-- SINGAPORE, 8:00 -->
+
+    <timezone code="sg">Asia/Singapore</timezone>
+
+    <!-- SAINT HELENA, 0:00 -->
+
+    <timezone code="sh">Atlantic/St_Helena</timezone>
+
+    <!-- SLOVENIA, 1:00 -->
+
+    <timezone code="si">Europe/Ljubljana</timezone>
+
+    <!-- SVALBARD AND JAN MAYEN, 1:00 -->
+
+    <timezone code="sj">Arctic/Longyearbyen</timezone>
+
+    <!-- SLOVAKIA, 1:00 -->
+
+    <timezone code="sk">Europe/Bratislava</timezone>
+
+    <!-- SIERRA LEONE, 0:00 -->
+
+    <timezone code="sl">Africa/Freetown</timezone>
+
+    <!-- SAN MARINO, 1:00 -->
+
+    <timezone code="sm">Europe/San_Marino</timezone>
+
+    <!-- SENEGAL, 0:00 -->
+
+    <timezone code="sn">Africa/Dakar</timezone>
+
+    <!-- SOMALIA, 3:00 -->
+
+    <timezone code="so">Africa/Mogadishu</timezone>
+
+    <!-- SURINAME, -3:00 -->
+
+    <timezone code="sr">America/Paramaribo</timezone>
+
+    <!-- SAO TOME AND PRINCIPE, 0:00 -->
+
+    <timezone code="st">Africa/Sao_Tome</timezone>
+
+    <!-- EL SALVADOR, -6:00 -->
+
+    <timezone code="sv">America/El_Salvador</timezone>
+
+    <!-- SYRIAN ARAB REPUBLIC, 2:00 -->
+
+    <timezone code="sy">Asia/Damascus</timezone>
+
+    <!-- SWAZILAND, 2:00 -->
+
+    <timezone code="sz">Africa/Mbabane</timezone>
+
+    <!-- TURKS AND CAICOS ISLANDS, -5:00 -->
+
+    <timezone code="tc">America/Grand_Turk</timezone>
+
+    <!-- CHAD, 1:00 -->
+
+    <timezone code="td">Africa/Ndjamena</timezone>
+
+    <!-- FRENCH SOUTHERN TERRITORIES, 5:00 -->
+
+    <timezone code="tf">Indian/Kerguelen</timezone>
+
+    <!-- TOGO, 0:00 -->
+
+    <timezone code="tg">Africa/Lome</timezone>
+
+    <!-- THAILAND, 7:00 -->
+
+    <timezone code="th">Asia/Bangkok</timezone>
+
+    <!-- TAJIKISTAN, 5:00 -->
+
+    <timezone code="tj">Asia/Dushanbe</timezone>
+
+    <!-- TOKELAU, -10:00 -->
+
+    <timezone code="tk">Pacific/Fakaofo</timezone>
+
+    <!-- TIMOR-LESTE, 9:00 -->
+
+    <timezone code="tl">Asia/Dili</timezone>
+
+    <!-- TURKMENISTAN, 5:00 -->
+
+    <timezone code="tm">Asia/Ashgabat</timezone>
+
+    <!-- TUNISIA, 1:00 -->
+
+    <timezone code="tn">Africa/Tunis</timezone>
+
+    <!-- TONGA, 13:00 -->
+
+    <timezone code="to">Pacific/Tongatapu</timezone>
+
+    <!-- TURKEY, 2:00 -->
+
+    <timezone code="tr">Europe/Istanbul</timezone>
+
+    <!-- TRINIDAD AND TOBAGO, -4:00 -->
+
+    <timezone code="tt">America/Port_of_Spain</timezone>
+
+    <!-- TUVALU, 12:00 -->
+
+    <timezone code="tv">Pacific/Funafuti</timezone>
+
+    <!-- TAIWAN, PROVINCE OF CHINA, 8:00 -->
+
+    <timezone code="tw">Asia/Taipei</timezone>
+
+    <!-- TANZANIA, UNITED REPUBLIC OF, 3:00 -->
+
+    <timezone code="tz">Africa/Dar_es_Salaam</timezone>
+
+    <!-- UKRAINE, 2:00 -->
+
+    <timezone code="ua">Europe/Kiev</timezone>
+    <timezone code="ua">Europe/Uzhgorod</timezone>
+    <timezone code="ua">Europe/Zaporozhye</timezone>
+    <timezone code="ua">Europe/Simferopol</timezone>
+
+    <!-- UGANDA, 3:00 -->
+
+    <timezone code="ug">Africa/Kampala</timezone>
+
+    <!-- UNITED STATES MINOR OUTLYING ISLANDS, 12:00 -->
+
+    <timezone code="um">Pacific/Wake</timezone>
+
+    <!-- UNITED STATES MINOR OUTLYING ISLANDS, -10:00 -->
+
+    <timezone code="um">Pacific/Johnston</timezone>
+
+    <!-- UNITED STATES MINOR OUTLYING ISLANDS, -11:00 -->
+
+    <timezone code="um">Pacific/Midway</timezone>
+
+    <!-- UNITED STATES, -5:00 -->
+
+    <timezone code="us">America/New_York</timezone>
+    <timezone code="us">America/Detroit</timezone>
+    <timezone code="us">America/Kentucky/Louisville</timezone>
+    <timezone code="us">America/Kentucky/Monticello</timezone>
+    <timezone code="us">America/Indiana/Indianapolis</timezone>
+    <timezone code="us">America/Indiana/Vincennes</timezone>
+    <timezone code="us">America/Indiana/Winamac</timezone>
+    <timezone code="us">America/Indiana/Marengo</timezone>
+    <timezone code="us">America/Indiana/Petersburg</timezone>
+    <timezone code="us">America/Indiana/Vevay</timezone>
+
+    <!-- UNITED STATES, -6:00 -->
+
+    <timezone code="us">America/Chicago</timezone>
+    <timezone code="us">America/Indiana/Knox</timezone>
+    <timezone code="us">America/Menominee</timezone>
+    <timezone code="us">America/North_Dakota/Center</timezone>
+    <timezone code="us">America/North_Dakota/New_Salem</timezone>
+
+    <!-- UNITED STATES, -7:00 -->
+
+    <timezone code="us">America/Denver</timezone>
+    <timezone code="us">America/Boise</timezone>
+    <timezone code="us">America/Shiprock</timezone>
+    <timezone code="us">America/Phoenix</timezone>
+
+    <!-- UNITED STATES, -8:00 -->
+
+    <timezone code="us">America/Los_Angeles</timezone>
+
+    <!-- UNITED STATES, -9:00 -->
+
+    <timezone code="us">America/Anchorage</timezone>
+    <timezone code="us">America/Juneau</timezone>
+    <timezone code="us">America/Yakutat</timezone>
+    <timezone code="us">America/Nome</timezone>
+
+    <!-- UNITED STATES, -10:00 -->
+
+    <timezone code="us">Pacific/Honolulu</timezone>
+    <timezone code="us">America/Adak</timezone>
+
+    <!-- URUGUAY, -3:00 -->
+
+    <timezone code="uy">America/Montevideo</timezone>
+
+    <!-- UZBEKISTAN, 5:00 -->
+
+    <timezone code="uz">Asia/Tashkent</timezone>
+    <timezone code="uz">Asia/Samarkand</timezone>
+
+    <!-- HOLY SEE (VATICAN CITY STATE), 1:00 -->
+
+    <timezone code="va">Europe/Vatican</timezone>
+
+    <!-- SAINT VINCENT AND THE GRENADINES, -4:00 -->
+
+    <timezone code="vc">America/St_Vincent</timezone>
+
+    <!-- VENEZUELA, -4:30 -->
+
+    <timezone code="ve">America/Caracas</timezone>
+
+    <!-- VIRGIN ISLANDS, BRITISH, -4:00 -->
+
+    <timezone code="vg">America/Tortola</timezone>
+
+    <!-- VIRGIN ISLANDS, U.S., -4:00 -->
+
+    <timezone code="vi">America/St_Thomas</timezone>
+
+    <!-- VIET NAM, 7:00 -->
+
+    <timezone code="vn">Asia/Saigon</timezone>
+
+    <!-- VANUATU, 11:00 -->
+
+    <timezone code="vu">Pacific/Efate</timezone>
+
+    <!-- WALLIS AND FUTUNA, 12:00 -->
+
+    <timezone code="wf">Pacific/Wallis</timezone>
+
+    <!-- SAMOA, -11:00 -->
+
+    <timezone code="ws">Pacific/Apia</timezone>
+
+    <!-- YEMEN, 3:00 -->
+
+    <timezone code="ye">Asia/Aden</timezone>
+
+    <!-- MAYOTTE, 3:00 -->
+
+    <timezone code="yt">Indian/Mayotte</timezone>
+
+    <!-- SOUTH AFRICA, 2:00 -->
+
+    <timezone code="za">Africa/Johannesburg</timezone>
+
+    <!-- ZAMBIA, 2:00 -->
+
+    <timezone code="zm">Africa/Lusaka</timezone>
+
+    <!-- ZIMBABWE, 2:00 -->
+
+    <timezone code="zw">Africa/Harare</timezone>
+</timezones>